Initial commit of BRLTTY

Derived from BRLTTY 6.6.
Includes HID support.
diff --git a/Android/Gradle/Makefile.in b/Android/Gradle/Makefile.in
new file mode 100644
index 0000000..9a2d657
--- /dev/null
+++ b/Android/Gradle/Makefile.in
@@ -0,0 +1,17 @@
+all: debug
+
+core: core-debug
+app: app-debug
+api: api-debug
+apitest: apitest-debug
+
+debug: app-debug
+release: app-release
+bundle: bundle-release
+
+lint: lint-debug
+install: install-debug
+
+include $(SRC_DIR)/common.mk
+include $(SRC_DIR)/targets.mk
+include $(SRC_DIR)/publish.mk
diff --git a/Android/Gradle/api/brltty.gradle b/Android/Gradle/api/brltty.gradle
new file mode 100644
index 0000000..84a48e9
--- /dev/null
+++ b/Android/Gradle/api/brltty.gradle
@@ -0,0 +1,16 @@
+brltty.addTasks([
+  project: project,
+  componentName: "api",
+  makeSubdirectory: brltty.native.javaSubdirectory,
+  jars: ["brlapi"],
+
+  libraries: [
+    "brlapi_java",
+    "brlapi"
+  ],
+
+  librarySubdirectories: [
+    brltty.native.javaSubdirectory,
+    brltty.native.coreSubdirectory
+  ],
+])
diff --git a/Android/Gradle/api/build.gradle b/Android/Gradle/api/build.gradle
new file mode 100644
index 0000000..44f04db
--- /dev/null
+++ b/Android/Gradle/api/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+  id "com.android.library"
+  id "maven-publish"
+}
+
+description = """
+"${project.name}" provides the Java client bindings for "${rootProject.name}" code.
+"""
+
+apply from: "brltty.gradle"
+
+dependencies {
+  api files("src/main/libs/brlapi.jar")
+}
+
+android {
+  compileSdkVersion "android-31"
+  buildToolsVersion "29.0.3"
+
+  defaultConfig {
+    targetSdkVersion 30
+    minSdkVersion 24
+  }
+}
+
+afterEvaluate {
+  publishing {
+    publications {
+      release(MavenPublication) {
+        from components.release
+        groupId = "org.a11y.brltty"
+        artifactId = "brlapi-android"
+        version = brltty.config.apiRelease
+      }
+    }
+  }
+}
diff --git a/Android/Gradle/api/src/main/AndroidManifest.xml b/Android/Gradle/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..dbf127f
--- /dev/null
+++ b/Android/Gradle/api/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="org.a11y.brlapi"
+  >
+
+</manifest>
diff --git a/Android/Gradle/apitest/build.gradle b/Android/Gradle/apitest/build.gradle
new file mode 100644
index 0000000..dd388fe
--- /dev/null
+++ b/Android/Gradle/apitest/build.gradle
@@ -0,0 +1,19 @@
+plugins {
+  id "com.android.application"
+}
+
+description = """
+A test program to verify that the Maven dependency on BrlAPI is working.
+"""
+
+apply from: rootProject.file("brlapi-android.gradle")
+
+android {
+  compileSdkVersion "android-29"
+  buildToolsVersion "29.0.3"
+
+  defaultConfig {
+    targetSdkVersion 29
+    minSdkVersion 26
+  }
+}
diff --git a/Android/Gradle/apitest/src/main/AndroidManifest.xml b/Android/Gradle/apitest/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a0c9e31
--- /dev/null
+++ b/Android/Gradle/apitest/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="org.a11y.brlapi.android"
+  >
+
+</manifest>
diff --git a/Android/Gradle/apitest/src/main/java/org/a11y/brlapi/android/TestProgram.java b/Android/Gradle/apitest/src/main/java/org/a11y/brlapi/android/TestProgram.java
new file mode 100644
index 0000000..d460e55
--- /dev/null
+++ b/Android/Gradle/apitest/src/main/java/org/a11y/brlapi/android/TestProgram.java
@@ -0,0 +1,7 @@
+package org.a11y.brlapi.android;
+
+
+public class TestProgram {
+  public static void main (String[] arguments) {
+  }
+}
diff --git a/Android/Gradle/app/brltty-assets.gradle b/Android/Gradle/app/brltty-assets.gradle
new file mode 100644
index 0000000..e872fb6
--- /dev/null
+++ b/Android/Gradle/app/brltty-assets.gradle
@@ -0,0 +1,83 @@
+def assetsDirectory = brltty.getAssetsPath(project)
+def messagesDirectory = assetsDirectory + File.separator + "locale"
+def tablesDirectory = assetsDirectory + File.separator + "tables"
+
+task brlttyAddTables (type: Copy) {
+  group "build"
+  description "Add the tables (text, contraction, attributes, keyboard, input) to the build (as assets)."
+
+  from brltty.native.tablesDirectory
+  into tablesDirectory
+  include 'Text/**/*.tt?'
+  include 'Contraction/**/*.ct?'
+  include 'Attributes/**/*.at?'
+  include 'Keyboard/**/*.kt?'
+  include 'Input/**/*.kt?'
+}
+
+task brlttyRemoveTables (type: Delete) {
+  group "build"
+  description "Remove the tables (text, contraction, attributes, keyboard, input) from the build."
+
+  delete tablesDirectory
+}
+
+tasks.register("brlttyAddMessages") {
+  group "build"
+  description "Add the message catalogs (.mo files) to the build (as assets)."
+
+  doLast {
+    def commandProcesses = []
+
+    try {
+      def sourceDirectory = new File(brltty.native.messagesDirectory)
+      def sourceFiles = layout.files {sourceDirectory.listFiles()}
+
+      sourceFiles.each { sourceFile ->
+        def sourceName = sourceFile.name
+
+        if (sourceName.endsWith(".po")) {
+          def languageCode = sourceName.replaceAll(/\..+?$/, '')
+          def targetDirectory = new File(messagesDirectory + File.separator + languageCode + File.separator + "LC_MESSAGES")
+
+          if (targetDirectory.isDirectory() || targetDirectory.mkdirs()) {
+            def targetFile = new File(targetDirectory, "brltty.mo")
+
+            def commandArguments = [
+              "msgfmt", "--no-hash",
+              "--output-file=" + targetFile.absolutePath,
+              "--", sourceFile.absolutePath
+            ]
+
+            def commandProcess = commandArguments.execute()
+            commandProcess.waitForProcessOutput(new StringBuffer(), System.err)
+            commandProcesses += commandProcess
+          } else {
+            throw new IOException("messages directory not created: " + targetDirectory.absolutePath)
+          }
+        }
+      }
+    } finally {
+      def ok = true
+
+      commandProcesses.each { process ->
+        int exitValue = process.waitFor()
+        if (exitValue) ok = false
+      }
+
+      if (!ok) throw new RuntimeException("installing messages failed")
+    }
+  }
+}
+
+task brlttyRemoveMessages (type: Delete) {
+  group "build"
+  description "Remove the message catalogs (.mo files) from the build."
+
+  delete messagesDirectory
+}
+
+brltty.addAssembleTask project, brlttyAddMessages
+brltty.addAssembleTask project, brlttyAddTables
+clean.dependsOn brlttyRemoveMessages
+clean.dependsOn brlttyRemoveTables
diff --git a/Android/Gradle/app/brltty-config.gradle b/Android/Gradle/app/brltty-config.gradle
new file mode 100644
index 0000000..fe15b81
--- /dev/null
+++ b/Android/Gradle/app/brltty-config.gradle
@@ -0,0 +1,26 @@
+brltty.config.revisionIdentifier = [
+  brltty.native.rootDirectory + File.separator + "getrevid",
+  "-m", "+",
+  brltty.native.rootDirectory
+].execute().text.trim()
+
+brltty.config.versionCode = [
+  brltty.native.rootDirectory + File.separator + "getrevid",
+  "-r", brltty.config.revisionIdentifier, "-i",
+  brltty.native.rootDirectory
+].execute().text.trim()
+
+brltty.config.versionName = brltty.config.revisionIdentifier.replaceAll(/^.*?-(\d)/, /$1/).replaceAll(/-g.*$/, "")
+brltty.config.sourceRevision = brltty.config.revisionIdentifier.replaceAll(/^.*-g(.*)$/, /git:$1/)
+
+brltty.config.buildTime = Instant.now().toString()
+                                 .replace('T', '@')
+                                 .replaceAll(/:\d{2}\..*/, " UTC")
+
+android {
+  defaultConfig {
+    brltty.config.propertyNames().each { name ->
+      resValue "string", "appConfig_${name}", brltty.config[name]
+    }
+  }
+}
diff --git a/Android/Gradle/app/build.gradle b/Android/Gradle/app/build.gradle
new file mode 100644
index 0000000..736cf97
--- /dev/null
+++ b/Android/Gradle/app/build.gradle
@@ -0,0 +1,91 @@
+plugins {
+  id "com.android.application"
+  id "com.github.triplet.play" version "3.4.0"
+}
+
+description = """
+"${project.name}" implements the accessibility service for "${rootProject.name}" on Android.
+"""
+
+apply from: "brltty-config.gradle"
+apply from: "brltty-assets.gradle"
+
+dependencies {
+  implementation project(":core")
+  implementation "androidx.core:core:1.5.0"
+}
+
+android {
+  compileSdkVersion "android-31"
+  buildToolsVersion "29.0.3"
+
+//ndkVersion "21.4.7075529" // r21e - LTS
+//ndkVersion "22.1.7171670" // r22b - stable
+
+  compileOptions {
+    sourceCompatibility JavaVersion.VERSION_1_7
+    targetCompatibility JavaVersion.VERSION_1_7
+  }
+
+  lintOptions {
+    warning "MissingTranslation"
+    error "Untranslatable"
+  }
+
+  defaultConfig {
+    targetSdkVersion 30
+    minSdkVersion 16
+
+    versionName brltty.config.versionName
+    versionCode brltty.config.versionCode as int
+
+    ndk {
+      splits {
+        abi {
+          enable true
+          universalApk true
+          reset()
+
+          brltty.native.abiList.each { abi ->
+            include abi
+          }
+        }
+      }
+    }
+  }
+
+  signingConfigs {
+    release {
+      def signingProperties = brltty.loadProperties("credentials/sign-gradle.properties")
+      storeFile     signingProperties.storeFile
+      storePassword signingProperties.storePassword
+      keyAlias      signingProperties.keyAlias
+      keyPassword   signingProperties.keyPassword
+    }
+  }
+
+  buildTypes {
+    debug {
+      ndk {
+        abiFilters "armeabi-v7a"
+      }
+    }
+
+    release {
+      signingConfig signingConfigs.release
+
+      minifyEnabled false
+      proguardFile getDefaultProguardFile("proguard-android.txt")
+
+      ndk {
+        debugSymbolLevel "FULL" // FULL, SYMBOL_TABLE
+      }
+    }
+  }
+
+  play {
+    track = "production" // internal, alpha, beta, production
+    defaultToAppBundles = true
+    serviceAccountCredentials = rootProject.file("credentials/publish-play.json")
+  }
+}
diff --git a/Android/Gradle/app/src/main/AndroidManifest.xml b/Android/Gradle/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..deb6c53
--- /dev/null
+++ b/Android/Gradle/app/src/main/AndroidManifest.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:tools="http://schemas.android.com/tools"
+  package="org.a11y.brltty.android"
+  android:versionCode="0"
+  android:versionName="@string/app_version"
+  >
+
+  <!-- for creating a foreground notification -->
+  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
+  <!-- for resetting the device's lock timer -->
+  <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+  <!-- for communicating with a braille device via Bluetooth -->
+  <uses-permission android:name="android.permission.BLUETOOTH" />
+  <uses-feature android:name="android.hardware.bluetooth" />
+
+  <!-- for communicating with a braille device via USB -->
+  <uses-feature android:name="android.hardware.usb.host" />
+
+  <!-- for listening on a TCP/IP port for BrlAPI client connection requests -->
+  <uses-permission android:name="android.permission.INTERNET" />
+
+  <!-- for reading customized data files -->
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+  <!-- for presenting the Accessibility Actions chooser -->
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+  <!-- for knowing when locked storage can be accessed after a reboot -->
+  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+  <!-- for upgrading to a newer release -->
+  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+  <!-- for getting Wi-Fi status values (for the INDICATORS command) -->
+  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+  <uses-feature android:name="android.hardware.wifi" />
+
+  <!-- for getting the Wi-Fi SSID (for the INDICATORS command) -->
+  <!-- for getting cell information (for the INDICATORS command) -->
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+  <uses-feature android:name="android.hardware.location" />
+
+  <!-- for getting the cell signal strength (for the INDICATORS command) -->
+  <uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" />
+
+  <!-- for getting the cell data network type (for the INDICATORS command) -->
+  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+  <application
+    android:label="@string/app_name"
+    android:icon="@drawable/braille_service"
+    android:name=".BrailleApplication"
+    android:persistent="true"
+    android:supportsRtl="false"
+    android:allowBackup="true"
+    android:fullBackupContent="@xml/backup_rules"
+    >
+
+    <service
+      android:name=".BrailleService"
+      android:label="@string/app_name"
+      android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+      tools:ignore="UnusedAttribute"
+      android:directBootAware="true"
+      >
+
+      <intent-filter>
+        <action android:name="android.accessibilityservice.AccessibilityService" />
+      </intent-filter>
+
+      <meta-data
+        android:name="android.accessibilityservice"
+        android:resource="@xml/accessibility_service"
+       />
+    </service>
+
+    <service
+      android:name=".InputService"
+      android:label="@string/inputService_name"
+      android:permission="android.permission.BIND_INPUT_METHOD"
+      tools:ignore="UnusedAttribute"
+      android:directBootAware="true"
+      >
+
+      <intent-filter>
+        <action android:name="android.view.InputMethod" />
+      </intent-filter>
+
+      <meta-data
+        android:name="android.view.im"
+        android:resource="@xml/input_service"
+       />
+    </service>
+
+    <activity
+      android:name=".activities.ActionsActivity"
+      android:label="@string/app_name"
+      >
+    </activity>
+
+    <activity
+      android:name=".settings.SettingsActivity"
+      android:label="@string/SETTINGS_SCREEN_MAIN"
+      >
+    </activity>
+
+    <activity
+      android:name=".activities.AboutActivity"
+      android:label="@string/about_label_activity"
+      >
+    </activity>
+
+    <activity
+      android:name=".settings.UsbDeviceAttachedMonitor"
+      android:label="@string/usbMonitor_label"
+      android:exported="false"
+      android:noHistory="true"
+      android:excludeFromRecents="true"
+      android:directBootAware="true"
+      >
+
+      <intent-filter>
+        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+      </intent-filter>
+
+      <meta-data
+        android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+        android:resource="@xml/usb_devices"
+       />
+    </activity>
+
+    <receiver
+      android:name=".HostMonitor"
+      android:exported="false"
+      >
+
+      <intent-filter>
+        <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+      </intent-filter>
+    </receiver>
+
+    <provider
+      android:name="androidx.core.content.FileProvider"
+      android:authorities="org.a11y.brltty.android.fileprovider"
+      android:exported="false"
+      android:grantUriPermissions="true"
+      >
+
+      <meta-data
+        android:name="android.support.FILE_PROVIDER_PATHS"
+        android:resource="@xml/file_provider_paths"
+       />
+    </provider>
+  </application>
+</manifest>
diff --git a/Android/Gradle/app/src/main/assets/UBraille.ttf b/Android/Gradle/app/src/main/assets/UBraille.ttf
new file mode 100644
index 0000000..18a9f12
--- /dev/null
+++ b/Android/Gradle/app/src/main/assets/UBraille.ttf
Binary files differ
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/APITests.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/APITests.java
new file mode 100644
index 0000000..29b9721
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/APITests.java
@@ -0,0 +1,78 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.os.Build;
+
+public abstract class APITests {
+  private APITests () {
+  }
+
+  private static boolean haveAPILevel (int level) {
+    return Build.VERSION.SDK_INT >= level;
+  }
+
+  public final static boolean haveIceCreamSandwich
+  = haveAPILevel(Build.VERSION_CODES.ICE_CREAM_SANDWICH);
+
+  public final static boolean haveJellyBean
+  = haveAPILevel(Build.VERSION_CODES.JELLY_BEAN);
+
+  public final static boolean haveJellyBeanMR1
+  = haveAPILevel(Build.VERSION_CODES.JELLY_BEAN_MR1);
+
+  public final static boolean haveJellyBeanMR2
+  = haveAPILevel(Build.VERSION_CODES.JELLY_BEAN_MR2);
+
+  public final static boolean haveKitkat
+  = haveAPILevel(Build.VERSION_CODES.KITKAT);
+
+  public final static boolean haveLollipop
+  = haveAPILevel(Build.VERSION_CODES.LOLLIPOP);
+
+  public final static boolean haveLollipopMR1
+  = haveAPILevel(Build.VERSION_CODES.LOLLIPOP_MR1);
+
+  public final static boolean haveMarshmallow
+  = haveAPILevel(Build.VERSION_CODES.M);
+
+  public final static boolean haveNougat
+  = haveAPILevel(Build.VERSION_CODES.N);
+
+  public final static boolean haveNougatMR1
+  = haveAPILevel(Build.VERSION_CODES.N_MR1);
+
+  public final static boolean haveOreo
+  = haveAPILevel(Build.VERSION_CODES.O);
+
+  public final static boolean haveOreoMR1
+  = haveAPILevel(Build.VERSION_CODES.O_MR1);
+
+  public final static boolean havePie
+  = haveAPILevel(Build.VERSION_CODES.P);
+
+  public final static boolean haveQ
+  = haveAPILevel(Build.VERSION_CODES.Q);
+
+  public final static boolean haveR
+  = haveAPILevel(Build.VERSION_CODES.R);
+
+  public final static boolean haveS
+  = haveAPILevel(Build.VERSION_CODES.S);
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/AccessibilityButtonCallbacks.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/AccessibilityButtonCallbacks.java
new file mode 100644
index 0000000..bc6238d
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/AccessibilityButtonCallbacks.java
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.accessibilityservice.AccessibilityButtonController;
+import android.annotation.SuppressLint;
+import org.a11y.brltty.android.activities.ActionsActivity;
+
+@SuppressLint("NewApi")
+public class AccessibilityButtonCallbacks extends AccessibilityButtonController.AccessibilityButtonCallback {
+  @Override
+  public void onClicked (AccessibilityButtonController controller) {
+    ActionsActivity.launch();
+  }
+
+  @Override
+  public void onAvailabilityChanged (AccessibilityButtonController controller, boolean isAvailable) {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationParameters.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationParameters.java
new file mode 100644
index 0000000..fd67076
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationParameters.java
@@ -0,0 +1,38 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+public abstract class ApplicationParameters {
+  private ApplicationParameters () {
+  }
+
+  public final static boolean ENABLE_SET_TEXT = false;
+  public final static boolean ENABLE_MOTION_LOGGING = true;
+
+  public final static int BRAILLE_COLUMN_SPACING = 2;
+  public final static int BRAILLE_ROW_SPACING = 0;
+
+  public final static int CORE_WAIT_DURATION = Integer.MAX_VALUE;
+
+  public final static long FOCUS_WAIT_TIMEOUT = 1000;
+  public final static long FOCUS_WAIT_INTERVAL = 100;
+
+  public final static long LONG_PRESS_DELAY = 100;
+  public final static long FOCUS_SETTER_DELAY = 500;
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationSettings.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationSettings.java
new file mode 100644
index 0000000..2498cc9
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationSettings.java
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+
+public abstract class ApplicationSettings {
+  private ApplicationSettings () {
+  }
+
+  public static volatile boolean RELEASE_BRAILLE_DEVICE = false;
+  public static volatile boolean BRAILLE_DEVICE_ONLINE = false;
+
+  public static volatile boolean SHOW_NOTIFICATIONS = false;
+  public static volatile boolean SHOW_ALERTS = false;
+  public static volatile boolean SHOW_ANNOUNCEMENTS = false;
+
+  public static volatile boolean LOG_ACCESSIBILITY_EVENTS = false;
+  public static volatile boolean LOG_RENDERED_SCREEN = false;
+  public static volatile boolean LOG_KEYBOARD_EVENTS = false;
+  public static volatile boolean LOG_UNHANDLED_EVENTS = false;
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationUtilities.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationUtilities.java
new file mode 100644
index 0000000..4a90613
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ApplicationUtilities.java
@@ -0,0 +1,104 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+public abstract class ApplicationUtilities {
+  private final static String LOG_TAG = ApplicationUtilities.class.getName();
+
+  private ApplicationUtilities () {
+  }
+
+  public static long getAbsoluteTime () {
+    return SystemClock.uptimeMillis();
+  }
+
+  public static long getRelativeTime (long from) {
+    return getAbsoluteTime() - from;
+  }
+
+  public static String getResourceString (int identifier) {
+    return BrailleApplication.get().getString(identifier);
+  }
+
+  public static ContentResolver getContentResolver () {
+    return BrailleApplication.get().getContentResolver();
+  }
+
+  public static String getSecureSetting (String setting) {
+    return Settings.Secure.getString(getContentResolver(), setting);
+  }
+
+  public static String getSelectedInputMethodIdentifier () {
+    return getSecureSetting(Settings.Secure.DEFAULT_INPUT_METHOD);
+  }
+
+  private final static SingletonReference<PowerManager> powerManagerReference
+    = new SystemServiceReference<PowerManager>(Context.POWER_SERVICE);
+  public static PowerManager getPowerManager () {
+    return powerManagerReference.get();
+  }
+
+  private final static SingletonReference<KeyguardManager> keyguardManagerReference
+    = new SystemServiceReference<KeyguardManager>(Context.KEYGUARD_SERVICE);
+  public static KeyguardManager getKeyguardManager () {
+    return keyguardManagerReference.get();
+  }
+
+  private final static SingletonReference<InputMethodManager> inputMethodManagerReference
+    = new SystemServiceReference<InputMethodManager>(Context.INPUT_METHOD_SERVICE);
+  public static InputMethodManager getInputMethodManager () {
+    return inputMethodManagerReference.get();
+  }
+
+  private final static SingletonReference<WindowManager> windowManagerReference
+    = new SystemServiceReference<WindowManager>(Context.WINDOW_SERVICE);
+  public static WindowManager getWindowManager () {
+    return windowManagerReference.get();
+  }
+
+  private final static SingletonReference<UsbManager> usbManagerReference
+    = new SystemServiceReference<UsbManager>(Context.USB_SERVICE);
+  public static UsbManager getUsbManager () {
+    return usbManagerReference.get();
+  }
+
+  public static void launch (Class<? extends Activity> activityClass) {
+    Intent intent = new Intent(BrailleApplication.get(), activityClass);
+
+    intent.addFlags(
+      Intent.FLAG_ACTIVITY_NEW_TASK |
+      Intent.FLAG_ACTIVITY_CLEAR_TOP |
+      Intent.FLAG_FROM_BACKGROUND
+    );
+
+    BrailleApplication.get().startActivity(intent);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BluetoothConnection.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BluetoothConnection.java
new file mode 100644
index 0000000..b91ce24
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BluetoothConnection.java
@@ -0,0 +1,346 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.os.ParcelUuid;
+import android.util.Log;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.*;
+import java.util.Set;
+import java.util.UUID;
+
+public class BluetoothConnection {
+  private final static String LOG_TAG = BluetoothConnection.class.getName();
+
+  protected final static UUID SERIAL_PROFILE_UUID = UUID.fromString(
+    "00001101-0000-1000-8000-00805F9B34FB"
+  );
+
+  protected final static BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+  protected final long remoteAddressValue;
+  protected final byte[] remoteAddressBytes = new byte[6];
+  protected final String remoteAddressString;
+
+  protected BluetoothSocket bluetoothSocket = null;
+  protected InputStream inputStream = null;
+  protected OutputStream outputStream = null;
+  protected OutputStream pipeStream = null;
+  protected InputThread inputThread = null;
+
+  private class InputThread extends Thread {
+    volatile boolean stop = false;
+
+    InputThread () {
+      super(("bluetooth-input-" + remoteAddressString));
+    }
+
+    @Override
+    public void run () {
+      try {
+        byte[] buffer = new byte[0X80];
+
+        while (!stop) {
+          int byteCount;
+
+          try {
+            byteCount = inputStream.read(buffer);
+          } catch (IOException exception) {
+            Log.d(
+                LOG_TAG,
+                ("Bluetooth input exception: "
+                    + remoteAddressString
+                    + ": "
+                    + exception.getMessage()));
+            break;
+          }
+
+          if (byteCount > 0) {
+            pipeStream.write(buffer, 0, byteCount);
+            pipeStream.flush();
+          } else if (byteCount < 0) {
+            Log.d(LOG_TAG, ("Bluetooth input end: " + remoteAddressString));
+            break;
+          }
+        }
+      } catch (Throwable cause) {
+        Log.e(LOG_TAG, ("Bluetooth input failed: " + remoteAddressString), cause);
+      }
+
+      closePipeStream();
+    }
+  }
+
+  public static boolean isUp () {
+    if (bluetoothAdapter == null) return false;
+    if (!bluetoothAdapter.isEnabled()) return false;
+    return true;
+  }
+
+  public final BluetoothDevice getDevice () {
+    if (!isUp()) return null;
+
+    if (APITests.haveJellyBean) {
+      return bluetoothAdapter.getRemoteDevice(remoteAddressBytes);
+    } else {
+      return bluetoothAdapter.getRemoteDevice(remoteAddressString);
+    }
+  }
+
+  public final String getName () {
+    BluetoothDevice device = getDevice();
+    if (device == null) return null;
+    return device.getName();
+  }
+
+  public static String getName (long address) {
+    return new BluetoothConnection(address).getName();
+  }
+
+  public BluetoothConnection (long address) {
+    remoteAddressValue = address;
+
+    {
+      long value = remoteAddressValue;
+      int i = remoteAddressBytes.length;
+
+      while (i > 0) {
+        remoteAddressBytes[--i] = (byte)(value & 0XFF);
+        value >>= 8;
+      }
+    }
+
+    {
+      StringBuilder sb = new StringBuilder();
+
+      for (byte octet : remoteAddressBytes) {
+        if (sb.length() > 0) sb.append(':');
+        sb.append(String.format("%02X", octet));
+      }
+
+      remoteAddressString = sb.toString();
+    }
+  }
+
+  private void closeBluetoothSocket () {
+    if (bluetoothSocket != null) {
+      try {
+        bluetoothSocket.close();
+      } catch (IOException exception) {
+        Log.w(LOG_TAG, ("Bluetooth socket close failure: " + remoteAddressString), exception);
+      }
+
+      bluetoothSocket = null;
+    }
+  }
+
+  private void closeInputStream () {
+    if (inputStream != null) {
+      try {
+        inputStream.close();
+      } catch (IOException exception) {
+        Log.w(LOG_TAG, ("Bluetooth input stream close failure: " + remoteAddressString), exception);
+      }
+
+      inputStream = null;
+    }
+  }
+
+  private void closeOutputStream () {
+    if (outputStream != null) {
+      try {
+        outputStream.close();
+      } catch (IOException exception) {
+        Log.w(
+            LOG_TAG, ("Bluetooth output stream close failure: " + remoteAddressString), exception);
+      }
+
+      outputStream = null;
+    }
+  }
+
+  private void closePipeStream () {
+    if (pipeStream != null) {
+      try {
+        pipeStream.close();
+      } catch (IOException exception) {
+        Log.w(LOG_TAG, ("Bluetooth pipe stream close failure: " + remoteAddressString), exception);
+      }
+
+      pipeStream = null;
+    }
+  }
+
+  private void stopInputThread () {
+    if (inputThread != null) {
+      inputThread.stop = true;
+      inputThread = null;
+    }
+  }
+
+  public void close () {
+    stopInputThread();
+    closePipeStream();
+    closeInputStream();
+    closeOutputStream();
+    closeBluetoothSocket();
+  }
+
+  public boolean canDiscover () {
+    BluetoothDevice device = getDevice();
+
+    for (ParcelUuid uuid : device.getUuids()) {
+      if (uuid.getUuid().equals(SERIAL_PROFILE_UUID)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static BluetoothSocket createRfcommSocket (BluetoothDevice device, int channel) {
+    String className = device.getClass().getName();
+    String methodName = "createRfcommSocket";
+    String reference = className + "." + methodName;
+
+    try {
+      Class[] arguments = new Class[] {int.class};
+      Method method = device.getClass().getMethod(methodName, arguments);
+      return (BluetoothSocket)method.invoke(device, channel);
+    } catch (NoSuchMethodException exception) {
+      Log.w(LOG_TAG, "cannot find method: " + reference);
+    } catch (IllegalAccessException exception) {
+      Log.w(LOG_TAG, "cannot access method: " + reference);
+    } catch (InvocationTargetException exception) {
+      Log.w(LOG_TAG, reference + " failed", exception.getCause());
+    }
+
+    return null;
+  }
+
+  public boolean open (int inputPipe, int channel, boolean secure) {
+    BluetoothDevice device = getDevice();
+    if (device == null) return false;
+    if (bluetoothAdapter.isDiscovering()) return false;
+
+    if (channel == 0) {
+      try {
+        bluetoothSocket = secure? device.createRfcommSocketToServiceRecord(SERIAL_PROFILE_UUID):
+                                  device.createInsecureRfcommSocketToServiceRecord(SERIAL_PROFILE_UUID);
+      } catch (IOException exception) {
+        Log.e(
+            LOG_TAG,
+            ("Bluetooth UUID resolution failed: "
+                + remoteAddressString
+                + ": "
+                + exception.getMessage()));
+        bluetoothSocket = null;
+      }
+    } else {
+      bluetoothSocket = createRfcommSocket(device, channel);
+    }
+
+    if (bluetoothSocket != null) {
+      try {
+        bluetoothSocket.connect();
+
+        inputStream = bluetoothSocket.getInputStream();
+        outputStream = bluetoothSocket.getOutputStream();
+
+        {
+          File pipeFile = new File("/proc/self/fd/" + inputPipe);
+          pipeStream = new FileOutputStream(pipeFile);
+        }
+
+        inputThread = new InputThread();
+        inputThread.start();
+
+        return true;
+      } catch (IOException openException) {
+        Log.d(
+            LOG_TAG,
+            ("Bluetooth connect failed: "
+                + remoteAddressString
+                + ": "
+                + openException.getMessage()));
+      }
+    }
+
+    close();
+    return false;
+  }
+
+  public boolean write (byte[] bytes) {
+    try {
+      outputStream.write(bytes);
+      outputStream.flush();
+      return true;
+    } catch (IOException exception) {
+      Log.e(LOG_TAG, ("Bluetooth write failed: " + remoteAddressString), exception);
+    }
+
+    return false;
+  }
+
+  private static BluetoothDevice[] pairedDevices = null;
+
+  public static int getPairedDeviceCount () {
+    if (isUp()) {
+      Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
+
+      if (devices != null) {
+        pairedDevices = devices.toArray(new BluetoothDevice[devices.size()]);
+        return pairedDevices.length;
+      }
+    }
+
+    pairedDevices = null;
+    return 0;
+  }
+
+  private static BluetoothDevice getPairedDevice (int index) {
+    if (index >= 0) {
+      if (pairedDevices != null) {
+        if (index < pairedDevices.length) {
+          return pairedDevices[index];
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public static String getPairedDeviceAddress (int index) {
+    BluetoothDevice device = getPairedDevice(index);
+    if (device == null) return null;
+    return device.getAddress();
+  }
+
+  public static String getPairedDeviceName (int index) {
+    BluetoothDevice device = getPairedDevice(index);
+    if (device == null) return null;
+    return device.getName();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleApplication.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleApplication.java
new file mode 100644
index 0000000..4b64259
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleApplication.java
@@ -0,0 +1,68 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.app.Application;
+import android.os.Handler;
+import java.util.Locale;
+
+public class BrailleApplication extends Application {
+  private static BrailleApplication applicationObject = null;
+  private static Handler applicationHandler = null;
+
+  @Override
+  public void onCreate () {
+    super.onCreate();
+    applicationObject = this;
+    applicationHandler = new Handler();
+  }
+
+  public static BrailleApplication get () {
+    return applicationObject;
+  }
+
+  public static void unpost (Runnable callback) {
+    applicationHandler.removeCallbacks(callback);
+  }
+
+  public static boolean post (Runnable callback) {
+    return applicationHandler.post(callback);
+  }
+
+  public static boolean postIn (long delay, Runnable callback) {
+    return applicationHandler.postDelayed(callback, delay);
+  }
+
+  public static boolean postAt (long when, Runnable callback) {
+    return applicationHandler.postAtTime(callback, when);
+  }
+
+  public static String getCurrentLocale () {
+    Locale locale;
+
+    if (APITests.haveNougat) {
+      locale = get().getResources().getConfiguration().getLocales().get(0);
+    } else {
+      locale = get().getResources().getConfiguration().locale;
+    }
+
+    if (locale == null) return null;
+    return locale.toString();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleMessage.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleMessage.java
new file mode 100644
index 0000000..1cd5a2e
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleMessage.java
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import org.a11y.brltty.core.*;
+
+public enum BrailleMessage {
+  PLAIN(null),
+  WINDOW("win"),
+  WARNING("warn"),
+  NOTIFICATION("ntfc"),
+  ALERT("alrt"),
+  DEBUG("dbg"),
+  ANNOUNCEMENT("ann"),
+  ; // end of enumeration
+
+  private final String messageLabel;
+
+  BrailleMessage (String label) {
+    messageLabel = label;
+  }
+
+  public final String getLabel () {
+    return messageLabel;
+  }
+
+  public final void show (String text) {
+    if (text == null) return;
+    text = text.replace('\n', ' ').trim();
+    if (text.isEmpty()) return;
+
+    String label = getLabel();
+    if (!((label == null) || label.isEmpty())) text = label + ": " + text;
+    final String message = text;
+
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.showMessage(message);
+        }
+      }
+    );
+  }
+
+  public final void show (int text) {
+    show(BrailleApplication.get().getString(text));
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleNotification.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleNotification.java
new file mode 100644
index 0000000..51a0b12
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleNotification.java
@@ -0,0 +1,192 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import org.a11y.brltty.android.activities.ActionsActivity;
+import org.a11y.brltty.android.settings.DeviceManager;
+
+public abstract class BrailleNotification {
+  private BrailleNotification () {
+  }
+
+  private final static Integer NOTIFICATION_IDENTIFIER = 1;
+  private final static String NOTIFICATION_CHANNEL = "braille";
+
+  private static Context applicationContext = null;
+  private static NotificationManager notificationManager = null;
+  private static Notification.Builder notificationBuilder = null;
+
+  private static Context getContext () {
+    if (applicationContext == null) {
+      applicationContext = BrailleApplication.get();
+    }
+
+    return applicationContext;
+  }
+
+  private static String getString (int identifier) {
+    return getContext().getString(identifier);
+  }
+
+  private static PendingIntent newPendingIntent (Class<? extends Activity> activityClass) {
+    Context context = getContext();
+    Intent intent = new Intent(context, activityClass);
+
+    intent.addFlags(
+      Intent.FLAG_ACTIVITY_CLEAR_TASK |
+      Intent.FLAG_ACTIVITY_NEW_TASK
+    );
+
+    return PendingIntent.getActivity(context, 0, intent, 0);
+  }
+
+  private static NotificationManager getManager () {
+    if (notificationManager == null) {
+      notificationManager = (NotificationManager)
+                            getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    return notificationManager;
+  }
+
+  private static boolean isActive () {
+    return notificationBuilder != null;
+  }
+
+  private static void makeBuilder () {
+    Context context = getContext();
+
+    if (APITests.haveOreo) {
+      NotificationManager nm = getManager();
+      NotificationChannel channel = nm.getNotificationChannel(NOTIFICATION_CHANNEL);
+
+      if (channel == null) {
+        channel = new NotificationChannel(
+          NOTIFICATION_CHANNEL,
+          getString(R.string.braille_channel_name),
+          NotificationManager.IMPORTANCE_LOW
+        );
+
+        nm.createNotificationChannel(channel);
+      }
+
+      notificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL);
+    } else {
+      notificationBuilder = new Notification.Builder(context)
+        .setPriority(Notification.PRIORITY_LOW)
+        ;
+    }
+
+    notificationBuilder
+      .setOngoing(true)
+      .setOnlyAlertOnce(true)
+
+      .setSmallIcon(R.drawable.braille_notification)
+      .setSubText(getString(R.string.braille_hint_tap))
+      .setContentIntent(newPendingIntent(ActionsActivity.class))
+      ;
+
+    if (APITests.haveJellyBeanMR1) {
+      notificationBuilder.setShowWhen(false);
+    }
+
+    if (APITests.haveLollipop) {
+      notificationBuilder.setCategory(Notification.CATEGORY_SERVICE);
+    }
+  }
+
+  private static Notification buildNotification () {
+    return notificationBuilder.build();
+  }
+
+  private static void updateNotification () {
+    getManager().notify(NOTIFICATION_IDENTIFIER, buildNotification());
+  }
+
+  private static void setState () {
+    int state;
+
+    if (ApplicationSettings.RELEASE_BRAILLE_DEVICE) {
+      state = R.string.braille_state_released;
+    } else if (ApplicationSettings.BRAILLE_DEVICE_ONLINE) {
+      state = R.string.braille_state_connected;
+    } else {
+      state = R.string.braille_state_waiting;
+    }
+
+    notificationBuilder.setContentTitle(getString(state));
+  }
+
+  public static void updateState () {
+    synchronized (NOTIFICATION_IDENTIFIER) {
+      if (isActive()) {
+        setState();
+        updateNotification();
+      }
+    }
+  }
+
+  private static void setDevice (String device) {
+    if (device == null) device = "";
+    if (device.isEmpty()) device = getString(R.string.SELECTED_DEVICE_UNSELECTED);
+    notificationBuilder.setContentText(device);
+  }
+
+  public static void updateDevice (String device) {
+    synchronized (NOTIFICATION_IDENTIFIER) {
+      if (isActive()) {
+        setDevice(device);
+        updateNotification();
+      }
+    }
+  }
+
+  public static void create () {
+    synchronized (NOTIFICATION_IDENTIFIER) {
+      if (isActive()) {
+        throw new IllegalStateException("already active");
+      }
+
+      makeBuilder();
+      setState();
+      setDevice(DeviceManager.getSelectedDevice());
+
+      BrailleService.getBrailleService()
+                    .startForeground(NOTIFICATION_IDENTIFIER, buildNotification());
+    }
+  }
+
+  public static void destroy () {
+    synchronized (NOTIFICATION_IDENTIFIER) {
+      if (!isActive()) {
+        throw new IllegalStateException("not active");
+      }
+
+      notificationBuilder = null;
+      getManager().cancel(NOTIFICATION_IDENTIFIER);
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleRenderer.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleRenderer.java
new file mode 100644
index 0000000..1a26aa5
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleRenderer.java
@@ -0,0 +1,157 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.accessibilityservice.AccessibilityService;
+import android.graphics.Rect;
+import java.util.List;
+
+public abstract class BrailleRenderer {
+  private final static String LOG_TAG = BrailleRenderer.class.getName();
+
+  private static volatile BrailleRenderer brailleRenderer = new ListBrailleRenderer();
+
+  public static BrailleRenderer getBrailleRenderer () {
+    return brailleRenderer;
+  }
+
+  public static void setBrailleRenderer (BrailleRenderer renderer) {
+    brailleRenderer = renderer;
+  }
+
+  public static void setBrailleRenderer (String type) {
+    StringBuilder name = new StringBuilder(BrailleRenderer.class.getName());
+    int index = name.lastIndexOf(".");
+    name.insert(index+1, type);
+    setBrailleRenderer((BrailleRenderer)LanguageUtilities.newInstance(name.toString()));
+  }
+
+  protected abstract void setBrailleLocations (ScreenElementList elements);
+
+  protected static int getTextWidth (String[] lines) {
+    int width = 1;
+
+    for (String line : lines) {
+      width = Math.max(width, line.length());
+    }
+
+    return width;
+  }
+
+  private void addVirtualElements (ScreenElementList elements) {
+    if (APITests.haveJellyBean) {
+      elements.addAtTop(
+        R.string.GLOBAL_BUTTON_NOTIFICATIONS,
+        AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS
+      );
+    }
+
+    if (APITests.haveJellyBeanMR1) {
+      elements.addAtTop(
+        R.string.GLOBAL_BUTTON_QUICK_SETTINGS,
+        AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS
+      );
+    }
+
+    if (APITests.haveJellyBean) {
+      elements.addAtBottom(
+        R.string.GLOBAL_BUTTON_BACK,
+        AccessibilityService.GLOBAL_ACTION_BACK
+      );
+    }
+
+    if (APITests.haveJellyBean) {
+      elements.addAtBottom(
+        R.string.GLOBAL_BUTTON_HOME,
+        AccessibilityService.GLOBAL_ACTION_HOME
+      );
+    }
+
+    if (APITests.haveJellyBean) {
+      elements.addAtBottom(
+        APITests.haveLollipop?
+          R.string.GLOBAL_BUTTON_OVERVIEW:
+          R.string.GLOBAL_BUTTON_RECENT_APPS,
+        AccessibilityService.GLOBAL_ACTION_RECENTS
+      );
+    }
+  }
+
+  private void setScreenRegion (List<String> rows, Rect location, String[] lines) {
+    int width = location.right - location.left + 1;
+
+    while (rows.size() <= location.bottom) {
+      rows.add("");
+    }
+
+    for (int rowIndex=location.top; rowIndex<=location.bottom; rowIndex+=1) {
+      StringBuilder row = new StringBuilder(rows.get(rowIndex));
+      while (row.length() <= location.right) row.append(' ');
+
+      int lineIndex = rowIndex - location.top;
+      String line = (lineIndex < lines.length)? lines[lineIndex]: "";
+      if (line.length() > width) line = line.substring(0, width);
+
+      int end = location.left + line.length();
+      row.replace(location.left, end, line);
+      while (end <= location.right) row.setCharAt(end++, ' ');
+
+      rows.set(rowIndex, row.toString());
+    }
+  }
+
+  public final void renderScreenElements (ScreenElementList elements, List<String> rows) {
+    setBrailleLocations(elements);
+    addVirtualElements(elements);
+
+    boolean wasVirtual = false;
+    int horizontalOffset = 0;
+    int verticalOffset = 0;
+
+    for (ScreenElement element : elements) {
+      boolean isVirtual = element.getVisualLocation() == null;
+
+      if (isVirtual != wasVirtual) {
+        horizontalOffset = 0;
+        verticalOffset = rows.size();
+        wasVirtual = isVirtual;
+      }
+
+      if (isVirtual) {
+        String[] text = element.getBrailleText();
+        int left = horizontalOffset;
+        int top = 0;
+        int right = left + getTextWidth(text) - 1;
+        int bottom = top + text.length - 1;
+        element.setBrailleLocation(left, top, right, bottom);
+        horizontalOffset = right + 1 + ApplicationParameters.BRAILLE_COLUMN_SPACING;
+      }
+
+      Rect location = element.getBrailleLocation();
+      if (location != null) {
+        location.top += verticalOffset;
+        location.bottom += verticalOffset;
+        setScreenRegion(rows, location, element.getBrailleText());
+      }
+    }
+  }
+
+  protected BrailleRenderer () {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleService.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleService.java
new file mode 100644
index 0000000..861b83a
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/BrailleService.java
@@ -0,0 +1,125 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.accessibilityservice.AccessibilityButtonController;
+import android.accessibilityservice.AccessibilityService;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import org.a11y.brltty.core.*;
+
+public class BrailleService extends AccessibilityService {
+  private final static String LOG_TAG = BrailleService.class.getName();
+
+  private static volatile BrailleService brailleService = null;
+  private AccessibilityButtonCallbacks accessibilityButtonCallbacks = null;
+  private Thread coreThread = null;
+
+  public static BrailleService getBrailleService () {
+    return brailleService;
+  }
+
+  private final void enableAccessibilityButton () {
+    if (APITests.haveOreo) {
+      AccessibilityButtonController controller = getAccessibilityButtonController();
+
+      if (controller != null) {
+        synchronized (controller) {
+          if (accessibilityButtonCallbacks == null) {
+            accessibilityButtonCallbacks = new AccessibilityButtonCallbacks();
+            controller.registerAccessibilityButtonCallback(accessibilityButtonCallbacks);
+          }
+        }
+      }
+    }
+  }
+
+  private final void disableAccessibilityButton () {
+    if (APITests.haveOreo) {
+      AccessibilityButtonController controller = getAccessibilityButtonController();
+
+      if (controller != null) {
+        synchronized (controller) {
+          if (accessibilityButtonCallbacks != null) {
+            controller.unregisterAccessibilityButtonCallback(accessibilityButtonCallbacks);
+            accessibilityButtonCallbacks = null;
+          }
+        }
+      }
+    }
+  }
+
+  private final void startCoreThread () {
+    Log.d(LOG_TAG, "starting core thread");
+    coreThread = new CoreThread(this);
+    coreThread.start();
+  }
+
+  private final void stopCoreThread () {
+    try {
+      Log.d(LOG_TAG, "stopping core thread");
+      CoreWrapper.stop();
+      Log.d(LOG_TAG, "waiting for core thread to stop");
+
+      try {
+        coreThread.join();
+        Log.d(LOG_TAG, "core thread stopped");
+      } catch (InterruptedException exception) {
+        Log.w(LOG_TAG, "core thread join interrupted", exception);
+      }
+    } finally {
+      coreThread = null;
+    }
+  }
+
+  @Override
+  public void onCreate () {
+    super.onCreate();
+
+    Log.d(LOG_TAG, "braille service starting");
+    brailleService = this;
+
+    enableAccessibilityButton();
+    startCoreThread();
+    BrailleNotification.create();
+  }
+
+  @Override
+  public void onDestroy () {
+    try {
+      stopCoreThread();
+      disableAccessibilityButton();
+      BrailleNotification.destroy();
+
+      brailleService = null;
+      Log.d(LOG_TAG, "braille service stopped");
+    } finally {
+      super.onDestroy();
+    }
+  }
+
+  @Override
+  public void onAccessibilityEvent (AccessibilityEvent event) {
+    ScreenDriver.onAccessibilityEvent(event);
+  }
+
+  @Override
+  public void onInterrupt () {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Characters.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Characters.java
new file mode 100644
index 0000000..5f6e23f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Characters.java
@@ -0,0 +1,104 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import org.a11y.brltty.core.Braille;
+
+public abstract class Characters {
+  private Characters () {
+  }
+
+  public final static char CHECKBOX_BEGIN = Braille.ROW
+                                          | Braille.DOT4
+                                          | Braille.DOT1
+                                          | Braille.DOT2
+                                          | Braille.DOT3
+                                          | Braille.DOT7
+                                          | Braille.DOT8
+                                          ;
+
+  public final static char CHECKBOX_MARK = Braille.ROW
+                                         | Braille.DOT2
+                                         | Braille.DOT5
+                                         | Braille.DOT3
+                                         | Braille.DOT6
+                                         ;
+
+  public final static char CHECKBOX_END = Braille.ROW
+                                        | Braille.DOT1
+                                        | Braille.DOT4
+                                        | Braille.DOT5
+                                        | Braille.DOT6
+                                        | Braille.DOT8
+                                        | Braille.DOT7
+                                        ;
+
+  public final static char RADIO_BEGIN = Braille.ROW
+                                       | Braille.DOT4
+                                       | Braille.DOT2
+                                       | Braille.DOT3
+                                       | Braille.DOT8
+                                       ;
+
+  public final static char RADIO_MARK = Braille.ROW
+                                      | Braille.DOT2
+                                      | Braille.DOT5
+                                      | Braille.DOT3
+                                      | Braille.DOT6
+                                      ;
+
+  public final static char RADIO_END = Braille.ROW
+                                     | Braille.DOT1
+                                     | Braille.DOT5
+                                     | Braille.DOT6
+                                     | Braille.DOT7
+                                     ;
+
+  public final static char SWITCH_BEGIN = Braille.ROW
+                                        | Braille.DOT4
+                                        | Braille.DOT5
+                                        | Braille.DOT6
+                                        | Braille.DOT8
+                                        ;
+
+  public final static char SWITCH_OFF = Braille.ROW
+                                      | Braille.DOT1
+                                      | Braille.DOT4
+                                      | Braille.DOT3
+                                      | Braille.DOT6
+                                      | Braille.DOT7
+                                      | Braille.DOT8
+                                      ;
+
+  public final static char SWITCH_ON = Braille.ROW
+                                     | Braille.DOT1
+                                     | Braille.DOT4
+                                     | Braille.DOT2
+                                     | Braille.DOT5
+                                     | Braille.DOT7
+                                     | Braille.DOT8
+                                     ;
+
+  public final static char SWITCH_END = Braille.ROW
+                                      | Braille.DOT1
+                                      | Braille.DOT2
+                                      | Braille.DOT3
+                                      | Braille.DOT7
+                                      ;
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChooserWindow.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChooserWindow.java
new file mode 100644
index 0000000..fa1e1c5
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChooserWindow.java
@@ -0,0 +1,82 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class ChooserWindow extends OverlayWindow {
+  private final ListView itemList;
+  private final Button dismissButton;
+
+  private final void dismiss () {
+    itemList.setOnItemClickListener(null);
+    dismissButton.setOnClickListener(null);
+    removeView();
+  }
+
+  public static interface ItemClickListener {
+    public void onClick (int position);
+  }
+
+  public ChooserWindow (CharSequence[] itemLabels, int title, final ItemClickListener itemClickListener) {
+    super();
+
+    View view = setView(R.layout.chooser);
+    itemList = (ListView)view.findViewById(R.id.chooser_list);
+    dismissButton = (Button)view.findViewById(R.id.chooser_dismiss);
+
+    {
+      TextView titleView = (TextView)view.findViewById(R.id.chooser_title);
+      titleView.setText(title);
+    }
+
+    {
+      ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(
+        getContext(), android.R.layout.simple_list_item_1, itemLabels
+      );
+
+      itemList.setAdapter(adapter);
+    }
+
+    itemList.setOnItemClickListener(
+      new AdapterView.OnItemClickListener() {
+        @Override
+        public void onItemClick (AdapterView adapter, View view, int position, long id) {
+          itemClickListener.onClick(position);
+          dismiss();
+        }
+      }
+    );
+    dismissButton.setOnClickListener(
+      new View.OnClickListener() {
+        @Override
+        public void onClick (View view) {
+          dismiss();
+        }
+      }
+    );
+
+    itemList.requestFocus();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChromeRole.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChromeRole.java
new file mode 100644
index 0000000..9817304
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ChromeRole.java
@@ -0,0 +1,280 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ChromeRole {
+  cell("col",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        return makeCoordinatesLabel(role, node);
+      }
+    }
+  ),
+
+  columnHeader("hdr",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        return makeCoordinatesLabel(role, node);
+      }
+    }
+  ),
+
+  heading("hdg",
+    "heading 1", "hd1",
+    "heading 2", "hd2",
+    "heading 3", "hd3",
+    "heading 4", "hd4",
+    "heading 5", "hd5",
+    "heading 6", "hd6"
+  ),
+
+  link("lnk",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        StringBuilder label = new StringBuilder(role.genericLabel);
+
+        if (false) {
+          String url = ScreenUtilities.getStringExtra(node, EXTRA_TARGET_URL);
+
+          if ((url != null) && !url.isEmpty()) {
+            label.append(' ').append(url);
+          }
+        }
+
+        return label.toString();
+      }
+    }
+  ),
+
+  list("lst",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        StringBuilder label = new StringBuilder(role.genericLabel);
+
+        if (APITests.haveKitkat) {
+          AccessibilityNodeInfo.CollectionInfo collection = node.getCollectionInfo();
+
+          if (collection != null) {
+            int columns = collection.getColumnCount();
+            if (columns == 0) columns = 1;
+
+            int rows = collection.getRowCount();
+            if (rows == 0) rows = 1;
+
+            label.append(' ')
+                 .append(columns)
+                 .append('x')
+                 .append(rows)
+                 ;
+          }
+        }
+
+        return label.toString();
+      }
+    }
+  ),
+
+  listItem("lsi",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        StringBuilder label = new StringBuilder(role.genericLabel);
+
+        if (APITests.haveKitkat) {
+          AccessibilityNodeInfo.CollectionItemInfo item = node.getCollectionItemInfo();
+
+          if (item != null) {
+            label.append(' ')
+                 .append(item.getColumnIndex() + 1)
+                 .append('@')
+                 .append(item.getRowIndex() + 1)
+                 ;
+          }
+        }
+
+        return label.toString();
+      }
+    }
+  ),
+
+  table("tbl",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        return makeDimensionsLabel(role, node);
+      }
+    }
+  ),
+
+  textField("txt",
+    new LabelMaker() {
+      @Override
+      public String makeLabel (ChromeRole role, AccessibilityNodeInfo node) {
+        if ((node.getActions() & AccessibilityNodeInfo.ACTION_EXPAND) == 0) return "";
+        if (node.isPassword()) return "pwd";
+        return null;
+      }
+    }
+  ),
+
+  anchor(),
+  button("btn"),
+  caption("cap"),
+  checkBox(),
+  form("frm"),
+  genericContainer(""),
+  lineBreak(),
+  listMarker("lsm"),
+  paragraph(),
+  popUpButton("pop"),
+  radioButton(),
+  rootWebArea(),
+  row("row"),
+  splitter("--------"),
+  staticText(),
+  ; // end of enumeration
+
+  private final static String LOG_TAG = ChromeRole.class.getName();
+
+  public final static String EXTRA_CHROME_ROLE = "AccessibilityNodeInfo.chromeRole";
+  public final static String EXTRA_ROLE_DESCRIPTION = "AccessibilityNodeInfo.roleDescription";
+  public final static String EXTRA_HINT = "AccessibilityNodeInfo.hint";
+  public final static String EXTRA_TARGET_URL = "AccessibilityNodeInfo.targetUrl";
+
+  private interface LabelMaker {
+    public String makeLabel (ChromeRole role, AccessibilityNodeInfo node);
+  }
+
+  private final String genericLabel;
+  private final LabelMaker labelMaker;
+  private final Map<String, String> descriptionLabels;
+
+  ChromeRole (String generic, LabelMaker maker, String... descriptions) {
+    genericLabel = generic;
+    labelMaker = maker;
+
+    {
+      int count = descriptions.length;
+
+      if (count > 0) {
+        descriptionLabels = new HashMap<String, String>();
+        int index = 0;
+
+        while (index < count) {
+          String description = descriptions[index++];
+          String label = descriptions[index++];
+          descriptionLabels.put(description, label);
+        }
+      } else {
+        descriptionLabels = null;
+      }
+    }
+  }
+
+  ChromeRole (String generic, String... descriptions) {
+    this(generic, null, descriptions);
+  }
+
+  ChromeRole () {
+    this(null);
+  }
+
+  public static String makeCoordinatesLabel (ChromeRole role, AccessibilityNodeInfo node) {
+    if (APITests.haveKitkat) {
+      AccessibilityNodeInfo.CollectionItemInfo item = node.getCollectionItemInfo();
+
+      if (item != null) {
+        StringBuilder label = new StringBuilder(role.genericLabel);
+        label.append(' ')
+             .append(item.getColumnIndex() + 1)
+             .append('@')
+             .append(item.getRowIndex() + 1)
+             ;
+        return label.toString();
+      }
+    }
+
+    return null;
+  }
+
+  public static String makeDimensionsLabel (ChromeRole role, AccessibilityNodeInfo node) {
+    if (APITests.haveKitkat) {
+      AccessibilityNodeInfo.CollectionInfo collection = node.getCollectionInfo();
+
+      if (collection != null) {
+        StringBuilder label = new StringBuilder(role.genericLabel);
+        label.append(' ')
+             .append(collection.getColumnCount())
+             .append('x')
+             .append(collection.getRowCount())
+             ;
+
+        return label.toString();
+      }
+    }
+
+    return null;
+  }
+
+  public static ChromeRole getChromeRole (AccessibilityNodeInfo node) {
+    String name = ScreenUtilities.getStringExtra(node, EXTRA_CHROME_ROLE);
+    if (name == null) return null;
+    if (name.isEmpty()) return null;
+
+    try {
+      return ChromeRole.valueOf(name);
+    } catch (IllegalArgumentException exception) {
+      Log.w(LOG_TAG, ("unrecognized Chrome role: " + name));
+    }
+
+    return null;
+  }
+
+  public static String getLabel (AccessibilityNodeInfo node) {
+    if (node == null) return null;
+
+    ChromeRole role = getChromeRole(node);
+    if (role == null) return null;
+
+    if (role.labelMaker != null) {
+      String label = role.labelMaker.makeLabel(role, node);
+      if (label != null) return label;
+    }
+
+    if (role.descriptionLabels != null) {
+      final String description = ScreenUtilities.getStringExtra(node, EXTRA_ROLE_DESCRIPTION);
+
+      if (description != null) {
+        String label = role.descriptionLabels.get(description);
+        if (label != null) return label;
+      }
+    }
+
+    return role.genericLabel;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/CoreThread.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/CoreThread.java
new file mode 100644
index 0000000..78b001c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/CoreThread.java
@@ -0,0 +1,341 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.os.Environment;
+import android.util.Log;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import org.a11y.brltty.android.settings.DeviceDescriptor;
+import org.a11y.brltty.android.settings.DeviceManager;
+import org.a11y.brltty.core.*;
+
+public class CoreThread extends Thread {
+  private final static String LOG_TAG = CoreThread.class.getName();
+
+  private final Context coreContext;
+
+  private final void emptyDirectory (File directory) {
+    if (!directory.canWrite()) {
+      directory.setWritable(true, true);
+    }
+
+    for (File file : directory.listFiles()) {
+      if (file.isDirectory()) {
+        emptyDirectory(file);
+      }
+
+      file.delete();
+    }
+  }
+
+  private final void extractAsset (AssetManager assets, String asset, File path) {
+    try {
+      InputStream input = null;
+      OutputStream output = null;
+
+      try {
+        input = assets.open(asset);
+        output = new FileOutputStream(path);
+
+        byte[] buffer = new byte[0X4000];
+        int count;
+
+        while ((count = input.read(buffer)) > 0) {
+          output.write(buffer, 0, count);
+        }
+      } finally {
+        if (input != null) {
+          input.close();
+        }
+
+        if (output != null) {
+          output.close();
+        }
+      }
+    } catch (IOException exception) {
+      Log.e(LOG_TAG, "cannot extract asset: " + asset + " -> " + path, exception);
+    }
+  }
+
+  private final void extractAssets (AssetManager assets, String asset, File path, boolean executable) {
+    try {
+      String[] names = assets.list(asset);
+
+      if (names.length == 0) {
+        extractAsset(assets, asset, path);
+
+        if (executable) {
+          path.setExecutable(true, false);
+        }
+      } else {
+        if (!path.exists()) {
+          path.mkdir();
+        } else if (!path.isDirectory()) {
+          Log.d(LOG_TAG, "not a directory: " + path);
+          return;
+        }
+
+        for (String name : names) {
+          extractAssets(assets, new File(asset, name).getPath(), new File(path, name), executable);
+        }
+      }
+
+      path.setReadOnly();
+    } catch (IOException exception) {
+      Log.e(LOG_TAG, "cannot list asset: " + asset, exception);
+    }
+  }
+
+  private final void extractAssets (AssetManager assets, DataType type, boolean executable) {
+    File directory = type.getDirectory();
+    emptyDirectory(directory);
+    extractAssets(assets, type.getName(), directory, executable);
+  }
+
+  private final void extractAssets () {
+    Log.d(LOG_TAG, "extracting assets");
+    AssetManager assets = coreContext.getAssets();
+    extractAssets(assets, DataType.LOCALE, false);
+    extractAssets(assets, DataType.TABLES, false);
+    Log.d(LOG_TAG, "assets extracted");
+  }
+
+  private String getStringResource (int resource) {
+    return coreContext.getResources().getString(resource);
+  }
+
+  private static SharedPreferences getPreferences () {
+    return DataType.getPreferences();
+  }
+
+  private boolean getBooleanSetting (int key, boolean defaultValue) {
+    return getPreferences().getBoolean(getStringResource(key), defaultValue);
+  }
+
+  private boolean getBooleanSetting (int key) {
+    return getBooleanSetting(key, false);
+  }
+
+  private String getStringSetting (int key, String defaultValue) {
+    return getPreferences().getString(getStringResource(key), defaultValue);
+  }
+
+  private String getStringSetting (int key, int defaultValue) {
+    return getStringSetting(key, getStringResource(defaultValue));
+  }
+
+  private String getStringSetting (int key) {
+    return getStringSetting(key, "");
+  }
+
+  private Set<String> getStringSetSetting (int key) {
+    return getPreferences().getStringSet(getStringResource(key), Collections.EMPTY_SET);
+  }
+
+  private final void updateDataFiles () {
+    SharedPreferences prefs = getPreferences();
+    File file = new File(coreContext.getPackageCodePath());
+
+    String prefKey_size = getStringResource(R.string.PREF_KEY_PACKAGE_SIZE);
+    long oldSize = prefs.getLong(prefKey_size, -1);
+    long newSize = file.length();
+
+    String prefKey_time = getStringResource(R.string.PREF_KEY_PACKAGE_TIME);
+    long oldTime = prefs.getLong(prefKey_time, -1);
+    long newTime = file.lastModified();
+
+    if ((newSize != oldSize) || (newTime != oldTime)) {
+      Log.d(LOG_TAG, "package size: " + oldSize + " -> " + newSize);
+      Log.d(LOG_TAG, "package time: " + oldTime + " -> " + newTime);
+      extractAssets();
+
+      {
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putLong(prefKey_size, newSize);
+        editor.putLong(prefKey_time, newTime);
+        editor.apply();
+      }
+    }
+  }
+
+  private static File getStateFile (File directory, String extension) {
+    String newName = "brltty." + extension;
+    File newFile = new File(directory, newName);
+
+    if (!newFile.exists()) {
+      String oldName = "default." + extension;
+      File oldFile = new File(directory, oldName);
+
+      if (oldFile.exists()) {
+        if (oldFile.renameTo(newFile)) {
+          Log.d(LOG_TAG,
+            String.format(
+              "\"%s\" renamed to \"%s\"",
+              oldName, newName
+            )
+          );
+        } else {
+          Log.w(LOG_TAG,
+            String.format(
+              "couldn't rename \"%s\" to \"%s\"",
+              oldName, newName
+            )
+          );
+        }
+      } else {
+        try {
+          if (!newFile.createNewFile()) {
+            Log.w(LOG_TAG,
+              String.format(
+                "couldn't create \"%s\"", newName
+              )
+            );
+          }
+        } catch (IOException exception) {
+          Log.w(LOG_TAG,
+            String.format(
+              "file creation error: %s: %s",
+              newName, exception.getMessage()
+            )
+          );
+        }
+      }
+    }
+
+    return newFile;
+  }
+
+  private String[] makeArguments () {
+    ArgumentsBuilder builder = new ArgumentsBuilder();
+
+    builder.setForegroundExecution(true);
+    builder.setReleaseDevice(true);
+
+    builder.setLocaleDirectory(DataType.LOCALE.getDirectory());
+    builder.setTablesDirectory(DataType.TABLES.getDirectory());
+    builder.setWritableDirectory(DataType.WRITABLE.getDirectory());
+
+    {
+      File directory = DataType.STATE.getDirectory();
+      builder.setUpdatableDirectory(directory);
+
+      builder.setConfigurationFile(getStateFile(directory, "conf"));
+      builder.setPreferencesFile(getStateFile(directory, "prefs"));
+    }
+
+    builder.setTextTable(getStringSetting(R.string.PREF_KEY_TEXT_TABLE, R.string.DEFAULT_TEXT_TABLE));
+    builder.setAttributesTable(getStringSetting(R.string.PREF_KEY_ATTRIBUTES_TABLE, R.string.DEFAULT_ATTRIBUTES_TABLE));
+    builder.setContractionTable(getStringSetting(R.string.PREF_KEY_CONTRACTION_TABLE, R.string.DEFAULT_CONTRACTION_TABLE));
+    builder.setKeyboardTable(getStringSetting(R.string.PREF_KEY_KEYBOARD_TABLE, R.string.DEFAULT_KEYBOARD_TABLE));
+
+    {
+      DeviceDescriptor descriptor = DeviceManager.getDeviceDescriptor();
+      builder.setBrailleDevice(descriptor.getIdentifier());
+      builder.setBrailleDriver(descriptor.getDriver());
+    }
+
+    builder.setSpeechDriver(getStringSetting(R.string.PREF_KEY_SPEECH_SUPPORT, R.string.DEFAULT_SPEECH_SUPPORT));
+    builder.setQuietIfNoBraille(true);
+
+    builder.setApiEnabled(true);
+    builder.setApiParameters("host=127.0.0.1:0,auth=none");
+
+    {
+      ArrayList<String> keywords = new ArrayList<String>();
+      keywords.add(getStringSetting(R.string.PREF_KEY_LOG_LEVEL, R.string.DEFAULT_LOG_LEVEL));
+      keywords.addAll(getStringSetSetting(R.string.PREF_KEY_LOG_CATEGORIES));
+      StringBuilder operand = new StringBuilder();
+
+      for (String keyword : keywords) {
+        if (keyword.length() > 0) {
+          if (operand.length() > 0) operand.append(',');
+          operand.append(keyword);
+        }
+      }
+
+      builder.setLogLevel(operand.toString());
+    }
+
+    return builder.getArguments();
+  }
+
+  @Override
+  public void run () {
+    updateDataFiles();
+
+    {
+      UsbHelper.begin();
+
+      try {
+        CoreWrapper.run(makeArguments(), ApplicationParameters.CORE_WAIT_DURATION);
+      } finally {
+        UsbHelper.end();
+      }
+    }
+  }
+
+  private final void restoreSettings () {
+    BrailleRenderer.setBrailleRenderer(getStringSetting(R.string.PREF_KEY_NAVIGATION_MODE, R.string.DEFAULT_NAVIGATION_MODE));
+
+    ApplicationSettings.RELEASE_BRAILLE_DEVICE = getBooleanSetting(R.string.PREF_KEY_RELEASE_BRAILLE_DEVICE);
+
+    ApplicationSettings.SHOW_NOTIFICATIONS = getBooleanSetting(R.string.PREF_KEY_SHOW_NOTIFICATIONS);
+    ApplicationSettings.SHOW_ALERTS = getBooleanSetting(R.string.PREF_KEY_SHOW_ALERTS);
+    ApplicationSettings.SHOW_ANNOUNCEMENTS = getBooleanSetting(R.string.PREF_KEY_SHOW_ANNOUNCEMENTS);
+
+    ApplicationSettings.LOG_ACCESSIBILITY_EVENTS = getBooleanSetting(R.string.PREF_KEY_LOG_ACCESSIBILITY_EVENTS);
+    ApplicationSettings.LOG_RENDERED_SCREEN = getBooleanSetting(R.string.PREF_KEY_LOG_RENDERED_SCREEN);
+    ApplicationSettings.LOG_KEYBOARD_EVENTS = getBooleanSetting(R.string.PREF_KEY_LOG_KEYBOARD_EVENTS);
+    ApplicationSettings.LOG_UNHANDLED_EVENTS = getBooleanSetting(R.string.PREF_KEY_LOG_UNHANDLED_EVENTS);
+  }
+
+  private final void setOverrideDirectories () {
+    StringBuilder paths = new StringBuilder();
+
+    File[] directories = new File[] {
+      Environment.getExternalStorageDirectory()
+    };
+
+    for (File directory : directories) {
+      String path = directory.getAbsolutePath();
+      if (paths.length() > 0) paths.append(':');
+      paths.append(path);
+    }
+
+    CoreWrapper.setEnvironmentVariable("XDG_CONFIG_DIRS", paths.toString());
+  }
+
+  public CoreThread (Context context) {
+    super("Core");
+    coreContext = context;
+
+    restoreSettings();
+    setOverrideDirectories();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DataType.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DataType.java
new file mode 100644
index 0000000..ee5f839
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DataType.java
@@ -0,0 +1,61 @@
+package org.a11y.brltty.android;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import java.io.File;
+import java.util.Locale;
+
+public enum DataType {
+  LOCALE,
+  TABLES,
+  STATE,
+  WRITABLE,
+  ;
+
+  private final String typeName;
+
+  DataType () {
+    typeName = name().toLowerCase(Locale.ROOT);
+  }
+
+  public final String getName () {
+    return typeName;
+  }
+
+  private final static Object DATA_CONTEXT_LOCK = new Object();
+  private static Context dataContext = null;
+
+  public static Context getContext () {
+    synchronized (DATA_CONTEXT_LOCK) {
+      if (dataContext == null) {
+        Context context = BrailleApplication.get();
+
+        if (APITests.haveNougat) {
+          dataContext = context.createDeviceProtectedStorageContext();
+        } else {
+          dataContext = context;
+        }
+      }
+    }
+
+    return dataContext;
+  }
+
+  public static SharedPreferences getPreferences () {
+    return PreferenceManager.getDefaultSharedPreferences(getContext());
+  }
+
+  private final static Integer DIRECTORY_MODE = Context.MODE_PRIVATE;
+  private File directoryObject;
+
+  public final File getDirectory () {
+    synchronized (DIRECTORY_MODE) {
+      if (directoryObject == null) {
+        directoryObject = getContext().getDir(typeName, DIRECTORY_MODE);
+      }
+    }
+
+    return directoryObject;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DeferredTask.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DeferredTask.java
new file mode 100644
index 0000000..2d31df8
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/DeferredTask.java
@@ -0,0 +1,77 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+public abstract class DeferredTask implements Runnable {
+  protected abstract void runTask ();
+
+  protected boolean isStartable () {
+    return true;
+  }
+
+  protected void releaseResources () {
+  }
+
+  private final long taskDelay;
+  private boolean isScheduled = false;
+
+  protected DeferredTask (long delay) {
+    taskDelay = delay;
+  }
+
+  private final void scheduleTask () {
+    synchronized (this) {
+      if (!isScheduled) {
+        BrailleApplication.postIn(taskDelay, this);
+        isScheduled = true;
+      }
+    }
+  }
+
+  private final void cancelTask () {
+    synchronized (this) {
+      if (isScheduled) {
+        BrailleApplication.unpost(this);
+        isScheduled = false;
+      }
+    }
+  }
+
+  public final void restart () {
+    synchronized (this) {
+      cancelTask();
+      if (isStartable()) scheduleTask();
+    }
+  }
+
+  public final void stop () {
+    synchronized (this) {
+      cancelTask();
+      releaseResources();
+    }
+  }
+
+  @Override
+  public final void run () {
+    synchronized (this) {
+      if (isScheduled) runTask();
+      stop();
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/GridBrailleRenderer.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/GridBrailleRenderer.java
new file mode 100644
index 0000000..109ef2c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/GridBrailleRenderer.java
@@ -0,0 +1,438 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ListIterator;
+
+public class GridBrailleRenderer extends BrailleRenderer {
+  public class Grid {
+    protected abstract class Coordinate implements Comparator<Cell> {
+      private final int coordinateValue;
+
+      private final List<Cell> cells = new ArrayList<Cell>();
+
+      private int brailleOffset = 0;
+
+      public abstract int compare (Cell cell1, Cell cell2);
+      protected abstract void setPreviousCell (Cell cell, Cell previous);
+      protected abstract void setNextCell (Cell cell, Cell Next);
+      protected abstract Cell getAdjacentCell (Cell cell);
+      protected abstract Coordinate getCoordinate (Cell cell);
+      protected abstract int getSize (Cell cell);
+      protected abstract int getSpacing ();
+
+      public final int getValue () {
+        return coordinateValue;
+      }
+
+      public List<Cell> getCells () {
+        return cells;
+      }
+
+      public int getBrailleOffset () {
+        return brailleOffset;
+      }
+
+      public void setBrailleOffset (int offset) {
+        if (offset > brailleOffset) brailleOffset = offset;
+      }
+
+      public void addCell (Cell cell) {
+        cells.add(cell);
+      }
+
+      public void linkCells () {
+        Collections.sort(cells, this);
+        Cell previous = null;
+
+        for (Cell next : cells) {
+          if (previous != null) {
+            setPreviousCell(next, previous);
+            setNextCell(previous, next);
+          }
+
+          previous = next;
+        }
+      }
+
+      public void setOffsets () {
+        for (Cell cell : cells) {
+          Cell next = getAdjacentCell(cell);
+
+          if (next != null) {
+            getCoordinate(next).setBrailleOffset(getBrailleOffset() + getSize(cell) + getSpacing());
+          }
+        }
+      }
+
+      public Coordinate (int value) {
+        coordinateValue = value;
+      }
+    }
+
+    public class Column extends Coordinate {
+      @Override
+      public final int compare (Cell cell1, Cell cell2) {
+        return LanguageUtilities.compare(
+          cell1.getGridRow().getValue(),
+          cell2.getGridRow().getValue()
+        );
+      }
+
+      @Override
+      protected final void setPreviousCell (Cell cell, Cell previous) {
+        cell.setNorthCell(previous);
+      }
+
+      @Override
+      protected final void setNextCell (Cell cell, Cell next) {
+        cell.setSouthCell(next);
+      }
+
+      @Override
+      protected final Cell getAdjacentCell (Cell cell) {
+        return cell.getEastCell();
+      }
+
+      @Override
+      protected final Coordinate getCoordinate (Cell cell) {
+        return cell.getGridColumn();
+      }
+
+      @Override
+      protected final int getSize (Cell cell) {
+        return cell.getWidth();
+      }
+
+      @Override
+      protected final int getSpacing () {
+        return ApplicationParameters.BRAILLE_COLUMN_SPACING;
+      }
+
+      public Column (int value) {
+        super(value);
+      }
+    }
+
+    public class Row extends Coordinate {
+      @Override
+      public final int compare (Cell cell1, Cell cell2) {
+        return LanguageUtilities.compare(
+          cell1.getGridColumn().getValue(),
+          cell2.getGridColumn().getValue()
+        );
+      }
+
+      @Override
+      protected final void setPreviousCell (Cell cell, Cell previous) {
+        cell.setWestCell(previous);
+      }
+
+      @Override
+      protected final void setNextCell (Cell cell, Cell next) {
+        cell.setEastCell(next);
+      }
+
+      @Override
+      protected final Cell getAdjacentCell (Cell cell) {
+        return cell.getSouthCell();
+      }
+
+      @Override
+      protected final Coordinate getCoordinate (Cell cell) {
+        return cell.getGridRow();
+      }
+
+      @Override
+      protected final int getSize (Cell cell) {
+        return cell.getHeight();
+      }
+
+      @Override
+      protected final int getSpacing () {
+        return ApplicationParameters.BRAILLE_ROW_SPACING;
+      }
+
+      public Row (int value) {
+        super(value);
+      }
+    }
+
+    protected abstract class Coordinates {
+      private final int coordinateSpacing;
+
+      private final List<Coordinate> coordinates = new ArrayList<Coordinate>();
+
+      protected abstract Coordinate newCoordinate (int value);
+
+      public final List<Coordinate> getCoordinates () {
+        return coordinates;
+      }
+
+      public final Coordinate getCoordinate (int value) {
+        ListIterator<Coordinate> iterator = coordinates.listIterator();
+
+        while (iterator.hasNext()) {
+          Coordinate coordinate = iterator.next();
+          int current = coordinate.getValue();
+          if (current == value) return coordinate;
+
+          if (current > value) {
+            iterator.previous();
+            break;
+          }
+        }
+
+        {
+          Coordinate coordinate = newCoordinate(value);
+          iterator.add(coordinate);
+          return coordinate;
+        }
+      }
+
+      public void linkCells () {
+        for (Coordinate coordinate : coordinates) {
+          coordinate.linkCells();
+        }
+      }
+
+      public void setOffsets () {
+        int offset = 0;
+        int increment = coordinateSpacing;
+        if (increment == 0) increment = 1;
+
+        for (Coordinate coordinate : coordinates) {
+          coordinate.setBrailleOffset(offset);
+          offset += increment;
+          coordinate.setOffsets();
+        }
+      }
+
+      public Coordinates (int spacing) {
+        coordinateSpacing = spacing;
+      }
+    }
+
+    public class Columns extends Coordinates {
+      @Override
+      protected final Coordinate newCoordinate (int value) {
+        return new Column(value);
+      }
+
+      public Columns () {
+        super(ApplicationParameters.BRAILLE_COLUMN_SPACING);
+      }
+    }
+
+    public class Rows extends Coordinates {
+      @Override
+      protected final Coordinate newCoordinate (int value) {
+        return new Row(value);
+      }
+
+      public Rows () {
+        super(ApplicationParameters.BRAILLE_ROW_SPACING);
+      }
+    }
+
+    public class Cell {
+      private final Coordinate gridColumn;
+      private final Coordinate gridRow;
+      private final ScreenElement screenElement;
+
+      private final int cellWidth;
+      private final int cellHeight;
+
+      private Cell northCell = null;
+      private Cell eastCell = null;
+      private Cell southCell = null;
+      private Cell westCell = null;
+
+      public final Coordinate getGridColumn () {
+        return gridColumn;
+      }
+
+      public final Coordinate getGridRow () {
+        return gridRow;
+      }
+
+      public final ScreenElement getScreenElement () {
+        return screenElement;
+      }
+
+      public final int getWidth () {
+        return cellWidth;
+      }
+
+      public final int getHeight () {
+        return cellHeight;
+      }
+
+      public Cell getNorthCell () {
+        return northCell;
+      }
+
+      public Cell getEastCell () {
+        return eastCell;
+      }
+
+      public Cell getSouthCell () {
+        return southCell;
+      }
+
+      public Cell getWestCell () {
+        return westCell;
+      }
+
+      public void setNorthCell (Cell cell) {
+        northCell = cell;
+      }
+
+      public void setEastCell (Cell cell) {
+        eastCell = cell;
+      }
+
+      public void setSouthCell (Cell cell) {
+        southCell = cell;
+      }
+
+      public void setWestCell (Cell cell) {
+        westCell = cell;
+      }
+
+      public Cell (Coordinate column, Coordinate row, ScreenElement element) {
+        gridColumn = column;
+        gridRow = row;
+        screenElement = element;
+
+        String[] text = element.getBrailleText();
+        cellWidth = getTextWidth(text);
+        cellHeight = text.length;
+      }
+    }
+
+    private final Coordinates columns = new Columns();
+    private final Coordinates rows = new Rows();
+
+    public final List<Coordinate> getColumns () {
+      return columns.getCoordinates();
+    }
+
+    public final List<Coordinate> getRows () {
+      return rows.getCoordinates();
+    }
+
+    private final Coordinate getColumn (int value) {
+      return columns.getCoordinate(value);
+    }
+
+    private final Coordinate getRow (int value) {
+      return rows.getCoordinate(value);
+    }
+
+    public Point getPoint (ScreenElement element) {
+      AccessibilityNodeInfo node = element.getAccessibilityNode();
+      if (node == null) return null;
+
+      Rect location = new Rect();
+      boolean leaf = node.getChildCount() == 0;
+
+      do {
+        node.getBoundsInScreen(location);
+
+        AccessibilityNodeInfo parent = node.getParent();
+        if (parent == null) break;
+
+        node.recycle();
+        node = parent;
+      } while (node.getChildCount() == 1);
+
+      node.recycle();
+      node = null;
+
+      int column = location.left;
+      int row = leaf? ((location.top + location.bottom) / 2): location.top;
+      return new Point(column, row);
+    }
+
+    public final Cell addCell (ScreenElement element) {
+      String[] text = element.getBrailleText();
+      if (text == null) return null;
+
+      Point point = getPoint(element);
+      if (point == null) return null;
+
+      Coordinate column = getColumn(point.x);
+      Coordinate row = getRow(point.y);
+      Cell cell = new Cell(column, row, element);
+
+      column.addCell(cell);
+      row.addCell(cell);
+
+      return cell;
+    }
+
+    public final void finish () {
+      columns.linkCells();
+      rows.linkCells();
+
+      columns.setOffsets();
+      rows.setOffsets();
+    }
+
+    public Grid () {
+    }
+  }
+
+  @Override
+  protected final void setBrailleLocations (ScreenElementList elements) {
+    Grid grid = new Grid();
+
+    for (ScreenElement element : elements) {
+      grid.addCell(element);
+    }
+    grid.finish();
+
+    for (Grid.Coordinate row : grid.getRows()) {
+      int top = row.getBrailleOffset();
+
+      for (Grid.Cell cell : row.getCells()) {
+        Grid.Coordinate column = cell.getGridColumn();
+        int left = column.getBrailleOffset();
+
+        int right = left + cell.getWidth() - 1;
+        int bottom = top + cell.getHeight() - 1;
+
+        ScreenElement element = cell.getScreenElement();
+        element.setBrailleLocation(left, top, right, bottom);
+      }
+    }
+  }
+
+  public GridBrailleRenderer () {
+    super();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/HostMonitor.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/HostMonitor.java
new file mode 100644
index 0000000..8eae142
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/HostMonitor.java
@@ -0,0 +1,97 @@
+package org.a11y.brltty.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+public class HostMonitor extends BroadcastReceiver {
+  private final static String LOG_TAG = HostMonitor.class.getName();
+
+  private static void migrateData (File from, File to) {
+    if (from.exists()) {
+      if (from.isDirectory()) {
+        if (!to.exists()) {
+          if (!to.mkdir()) {
+            Log.w(LOG_TAG, ("directory not created: " + to.getAbsolutePath()));
+            return;
+          }
+        }
+
+        for (String name : from.list()) {
+          migrateData(
+            new File(from, name),
+            new File(to, name)
+          );
+        }
+      } else if (!to.exists()) {
+        try {
+          FileChannel input = new FileInputStream(from).getChannel();
+
+          try {
+            FileChannel output = new FileOutputStream(to).getChannel();
+
+            try {
+              input.transferTo(0, input.size(), output);
+            } finally {
+              output.close();
+              output = null;
+            }
+          } finally {
+            input.close();
+            input = null;
+          }
+        } catch (IOException exception) {
+          Log.w(LOG_TAG, ("data migration error: " + exception.getMessage()));
+        }
+      }
+
+      from.delete();
+    }
+  }
+
+  private static void migrateData (Context fromContext) {
+    if (APITests.haveNougat) {
+      Context toContext = DataType.getContext();
+
+      if (toContext != fromContext) {
+        {
+          String name = PreferenceManager.getDefaultSharedPreferencesName(fromContext);
+          toContext.moveSharedPreferencesFrom(fromContext, name);
+        }
+
+        for (DataType type : DataType.values()) {
+          migrateData(
+            fromContext.getDir(type.getName(),
+            Context.MODE_PRIVATE),
+            type.getDirectory()
+          );
+        }
+      }
+    }
+  }
+
+  @Override
+  public void onReceive (Context context, Intent intent) {
+    String action = intent.getAction();
+    if (action == null) return;
+    Log.d(LOG_TAG, ("host event: " + action));
+
+    if (APITests.haveNougat) {
+      if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
+        return;
+      }
+    }
+
+    if (action.equals(Intent.ACTION_MY_PACKAGE_REPLACED)) {
+      migrateData(context);
+      return;
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputHandlers.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputHandlers.java
new file mode 100644
index 0000000..2fc6545
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputHandlers.java
@@ -0,0 +1,1029 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.accessibilityservice.AccessibilityService;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.InputConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.a11y.brltty.android.activities.ActionsActivity;
+import org.a11y.brltty.core.Braille;
+
+public abstract class InputHandlers {
+  private InputHandlers () {
+  }
+
+  private final static int NO_SELECTION = -1;
+
+  private static boolean performGlobalAction (int action) {
+    return BrailleService.getBrailleService().performGlobalAction(action);
+  }
+
+  private static AccessibilityNodeInfo getCursorNode () {
+    RenderedScreen screen = ScreenDriver.getCurrentRenderedScreen();
+    if (screen == null) return null;
+    return screen.getCursorNode();
+  }
+
+  private static CharSequence getWindowTitle (AccessibilityWindowInfo window) {
+    if (APITests.haveNougat) {
+      CharSequence title = window.getTitle();
+      if ((title != null) && (title.length() > 0)) return title;
+    }
+
+    if (APITests.havePie) {
+      AccessibilityNodeInfo node = window.getRoot();
+
+      if (node != null) {
+        try {
+          while (true) {
+            {
+              CharSequence title = node.getPaneTitle();
+              if ((title != null) && (title.length() > 0)) return title;
+            }
+
+            if (node.getChildCount() != 1) break;
+            AccessibilityNodeInfo child = node.getChild(0);
+            if (child == null) break;
+
+            node.recycle();
+            node = child;
+            child = null;
+          }
+        } finally {
+          node.recycle();
+          node = null;
+        }
+      }
+    }
+
+    return "unnamed";
+  }
+
+  private static void showWindowTitle (AccessibilityWindowInfo window) {
+    CharSequence title = getWindowTitle(window);
+    BrailleMessage.WINDOW.show(title.toString());
+  }
+
+  private static void showWindowTitle (ScreenWindow window) {
+    AccessibilityWindowInfo info = window.getWindowInfo();
+
+    if (info != null) {
+      try {
+        showWindowTitle(info);
+      } finally {
+        if (APITests.haveLollipop) {
+          info.recycle();
+        }
+
+        info = null;
+      }
+    }
+  }
+
+  private static void showWindowTitle () {
+    showWindowTitle(ScreenDriver.getCurrentScreenWindow());
+  }
+
+  private static List<AccessibilityWindowInfo> getVisibleWindows () {
+    if (APITests.haveLollipop) {
+      return BrailleService.getBrailleService().getWindows();
+    } else {
+      return Collections.EMPTY_LIST;
+    }
+  }
+
+  private static AccessibilityWindowInfo[] getVisibleWindows (final Comparator<Integer> comparator) {
+    if (APITests.haveLollipop) {
+      List<AccessibilityWindowInfo> list = getVisibleWindows();
+      AccessibilityWindowInfo[] array = list.toArray(new AccessibilityWindowInfo[list.size()]);
+
+      Arrays.sort(array,
+        new Comparator<AccessibilityWindowInfo>() {
+          @Override
+          public int compare (AccessibilityWindowInfo window1, AccessibilityWindowInfo window2) {
+            return comparator.compare(window1.getId(), window2.getId());
+          }
+        }
+      );
+
+      return array;
+    }
+
+    return null;
+  }
+
+  private static boolean switchToWindow (AccessibilityWindowInfo window) {
+    if (APITests.haveLollipop) {
+      AccessibilityNodeInfo root = window.getRoot();
+
+      if (root != null) {
+        try {
+          RenderedScreen screen = new RenderedScreen(root);
+          showWindowTitle(window);
+
+          ScreenDriver.lockScreenWindow(
+            ScreenWindow.getScreenWindow(window)
+                        .setRenderedScreen(screen)
+          );
+
+          ScreenDriver.setCurrentNode(root);
+          return true;
+        } finally {
+          root.recycle();
+          root = null;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private static boolean switchToWindow (Comparator<Integer> comparator) {
+    boolean found = false;
+
+    if (APITests.haveLollipop) {
+      AccessibilityWindowInfo[] windows = getVisibleWindows(comparator);
+
+      if (windows != null) {
+        AccessibilityNodeInfo cursorNode = getCursorNode();
+
+        if (cursorNode != null) {
+          try {
+            int referenceIdentifier = cursorNode.getWindowId();
+
+            for (AccessibilityWindowInfo window : windows) {
+              try {
+                if (!found) {
+                  if (comparator.compare(window.getId(), referenceIdentifier) > 0) {
+                    if (switchToWindow(window)) {
+                      found = true;
+                    }
+                  }
+                }
+              } finally {
+                window.recycle();
+                window = null;
+              }
+            }
+          } finally {
+            cursorNode.recycle();
+            cursorNode = null;
+          }
+        }
+      }
+    }
+
+    return found;
+  }
+
+  private static boolean moveFocus (RenderedScreen.SearchDirection direction) {
+    RenderedScreen screen = ScreenDriver.getCurrentRenderedScreen();
+
+    if (screen != null) {
+      if (screen.moveFocus(direction)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private static int getTextStartOffset (AccessibilityNodeInfo node) {
+    int offset;
+
+    if (APITests.haveJellyBeanMR2) {
+      offset = node.getTextSelectionStart();
+    } else {
+      offset = NO_SELECTION;
+    }
+
+    if (offset == NO_SELECTION) return 0;
+    return offset;
+  }
+
+  private static int getTextEndOffset (AccessibilityNodeInfo node) {
+    int offset;
+
+    if (APITests.haveJellyBeanMR2) {
+      offset = node.getTextSelectionEnd();
+    } else {
+      offset = NO_SELECTION;
+    }
+
+    if (offset == NO_SELECTION) return 0;
+    if (offset != getTextStartOffset(node)) offset -= 1;
+    return offset;
+  }
+
+  private static Integer findNextLine (CharSequence text, int offset) {
+    int length = text.length();
+
+    while (offset < length) {
+      if (text.charAt(offset++) == '\n') return offset;
+    }
+
+    return null;
+  }
+
+  private static int findCurrentLine (CharSequence text, int offset) {
+    while (offset > 0) {
+      if (text.charAt(--offset) == '\n') {
+        offset += 1;
+        break;
+      }
+    }
+
+    return offset;
+  }
+
+  private static Integer findPreviousLine (CharSequence text, int offset) {
+    offset = findCurrentLine(text, offset);
+    if (offset == 0) return null;
+    return findCurrentLine(text, offset-1);
+  }
+
+  public static boolean setSelection (AccessibilityNodeInfo node, int start, int end) {
+    if (APITests.haveJellyBeanMR2) {
+      Bundle arguments = new Bundle();
+      arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, start);
+      arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, end);
+      return node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+    }
+
+    {
+      InputConnection connection = InputService.getInputConnection();
+      if (connection != null) return connection.setSelection(start, end);
+    }
+
+    return false;
+  }
+
+  public static boolean placeCursor (AccessibilityNodeInfo node, int offset) {
+    return setSelection(node, offset, offset);
+  }
+
+  private abstract static class TextEditor {
+    public TextEditor () {
+    }
+
+    protected abstract boolean editText (InputConnection connection);
+    protected abstract Integer editText (Editable editor, int start, int end);
+
+    private final boolean editText (AccessibilityNodeInfo node) {
+      if (APITests.haveLollipop) {
+        if (node.isFocused()) {
+          CharSequence text = node.getText();
+          int start = node.getTextSelectionStart();
+          int end = node.getTextSelectionEnd();
+
+          if (start == NO_SELECTION) text = null;
+          if (text == null) text = "";
+          if (text.length() == 0) start = end = 0;
+
+          Editable editor = (text instanceof Editable)?
+                            (Editable)text:
+                            new SpannableStringBuilder(text);
+
+          if ((0 <= start) && (start <= end) && (end <= text.length())) {
+            Integer offset = editText(editor, start, end);
+
+            if (offset != null) {
+              Bundle arguments = new Bundle();
+              arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, editor);
+
+              if (node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)) {
+                if (offset == editor.length()) return true;
+                return placeCursor(node, offset);
+              }
+            }
+          }
+        }
+      }
+
+      return false;
+    }
+
+    public final boolean editText () {
+      {
+        AccessibilityNodeInfo node = getCursorNode();
+
+        if (node != null) {
+          try {
+            if (ScreenUtilities.isEditable(node)) {
+              if (ApplicationParameters.ENABLE_SET_TEXT) {
+                if (APITests.haveLollipop) {
+                  if (!node.isPassword()) {
+                    return editText(node);
+                  }
+                }
+              }
+            }
+          } finally {
+            node.recycle();
+            node = null;
+          }
+        }
+      }
+
+      {
+        InputConnection connection = InputService.getInputConnection();
+        if (connection != null) return editText(connection);
+      }
+
+      return false;
+    }
+  }
+
+  public static boolean inputCharacter (final char character) {
+    return new TextEditor() {
+      @Override
+      protected boolean editText (InputConnection connection) {
+        return connection.commitText(Character.toString(character), 1);
+      }
+
+      @Override
+      protected Integer editText (Editable editor, int start, int end) {
+        editor.replace(start, end, Character.toString(character));
+        return start + 1;
+      }
+    }.editText();
+  }
+
+  private static char structuralMotionDirectionDot = StructuralMotion.DOT_NEXT;
+
+  public static boolean performStructuralMotion (byte dots) {
+    char character = (char)dots;
+    character &= Braille.DOTS_ALL;
+
+    boolean next = (character & StructuralMotion.DOT_NEXT) != 0;
+    boolean previous = (character & StructuralMotion.DOT_PREVIOUS) != 0;
+
+    if (next && previous) return false;
+    boolean noDirection = !(next || previous);
+
+    if ((character & ~StructuralMotion.DOTS_DIRECTION) == 0) {
+      if (noDirection) return false;
+      structuralMotionDirectionDot = next? StructuralMotion.DOT_NEXT: StructuralMotion.DOT_PREVIOUS;
+      return true;
+    }
+
+    if (noDirection) character |= structuralMotionDirectionDot;
+    character |= Braille.ROW;
+
+    StructuralMotion motion = StructuralMotion.get(character);
+    if (motion == null) return false;
+
+    AccessibilityNodeInfo node = getCursorNode();
+    if (node == null) return false;
+
+    try {
+      return motion.apply(node);
+    } finally {
+      node.recycle();
+      node = null;
+    }
+  }
+
+  private static boolean injectKey (int code) {
+    return InputService.injectKey(code);
+  }
+
+  private abstract static class KeyHandler {
+    private final int keyCode;
+
+    public KeyHandler (int code) {
+      keyCode = code;
+    }
+
+    protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+      throw new UnsupportedOperationException();
+    }
+
+    protected boolean performEditAction (AccessibilityNodeInfo node) {
+      return performNavigationAction(node);
+    }
+
+    public final boolean handleKey () {
+      try {
+        AccessibilityNodeInfo node = getCursorNode();
+
+        if (node != null) {
+          try {
+            return ScreenUtilities.isEditable(node)?
+                   performEditAction(node):
+                   performNavigationAction(node);
+          } finally {
+            node.recycle();
+            node = null;
+          }
+        }
+
+        throw new UnsupportedOperationException();
+      } catch (UnsupportedOperationException exception) {
+        return injectKey(keyCode);
+      }
+    }
+  }
+
+  public static boolean keyHandler_enter () {
+    return new KeyHandler(KeyEvent.KEYCODE_ENTER) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBean) {
+          return ScreenUtilities.performClick(node);
+        }
+
+        return super.performNavigationAction(node);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveLollipop) {
+          if (node.isMultiLine()) {
+            return inputCharacter('\n');
+          }
+        }
+
+        throw new UnsupportedOperationException();
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_tab () {
+    return new KeyHandler(KeyEvent.KEYCODE_TAB) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.FORWARD);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_backspace () {
+    return new TextEditor() {
+      @Override
+      protected boolean editText (InputConnection connection) {
+        return InputService.injectKey(connection, KeyEvent.KEYCODE_DEL, false);
+      }
+
+      @Override
+      protected Integer editText (Editable editor, int start, int end) {
+        if (start == end) {
+          if (start < 1) return null;
+          start -= 1;
+        }
+
+        editor.delete(start, end);
+        return start;
+      }
+    }.editText();
+  }
+
+  public static boolean keyHandler_escape () {
+    if (APITests.haveJellyBean) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
+    }
+
+    return injectKey(KeyEvent.KEYCODE_ESCAPE);
+  }
+
+  public static boolean keyHandler_cursorLeft () {
+    return new KeyHandler(KeyEvent.KEYCODE_DPAD_LEFT) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.LEFT);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int offset = node.getTextSelectionStart();
+          if (offset == NO_SELECTION) return false;
+          if (offset == node.getTextSelectionEnd()) offset -= 1;
+          if (offset < 0) return false;
+          return placeCursor(node, offset);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_cursorRight () {
+    return new KeyHandler(KeyEvent.KEYCODE_DPAD_RIGHT) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.RIGHT);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int offset = node.getTextSelectionEnd();
+          if (offset == NO_SELECTION) return false;
+          if (offset == node.getTextSelectionStart()) offset += 1;
+          if (offset > node.getText().length()) return false;
+          return placeCursor(node, offset);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_cursorUp () {
+    return new KeyHandler(KeyEvent.KEYCODE_DPAD_UP) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.UP);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int offset = getTextStartOffset(node);
+
+          CharSequence text = node.getText();
+          int current = findCurrentLine(text, offset);
+          if (current == 0) return false;
+
+          int end = current - 1;
+          int previous = findCurrentLine(text, end);
+
+          int position = Math.min(offset-current, end-previous);
+          return placeCursor(node, previous+position);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_cursorDown () {
+    return new KeyHandler(KeyEvent.KEYCODE_DPAD_DOWN) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.DOWN);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int offset = getTextEndOffset(node);
+
+          CharSequence text = node.getText();
+          Integer next = findNextLine(text, offset);
+          if (next == null) return false;
+
+          int current = findCurrentLine(text, offset);
+          int position = offset - current;
+
+          current = next;
+          next = findNextLine(text, current);
+
+          int end = ((next != null)? next-1: text.length()) - current;
+          if (position > end) position = end;
+          return placeCursor(node, current+position);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_pageUp () {
+    return new KeyHandler(KeyEvent.KEYCODE_PAGE_UP) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBean) {
+          return ScreenUtilities.performScrollBackward(node);
+        }
+
+        return super.performNavigationAction(node);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int from = getTextStartOffset(node);
+
+          final int to = 0;
+          if (to == from) return false;
+
+          return placeCursor(node, to);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_pageDown () {
+    return new KeyHandler(KeyEvent.KEYCODE_PAGE_DOWN) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBean) {
+          return ScreenUtilities.performScrollForward(node);
+        }
+
+        return super.performNavigationAction(node);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int from = getTextEndOffset(node);
+
+          final int to = node.getText().length();
+          if (to == from) return false;
+
+          return placeCursor(node, to);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_home () {
+    return new KeyHandler(KeyEvent.KEYCODE_MOVE_HOME) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.FIRST);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int from = getTextStartOffset(node);
+          if (from == 0) return false;
+
+          CharSequence text = node.getText();
+          int to = findCurrentLine(text, from);
+          if (to == from) return false;
+
+          return placeCursor(node, to);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_end () {
+    return new KeyHandler(KeyEvent.KEYCODE_MOVE_END) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        return moveFocus(RenderedScreen.SearchDirection.LAST);
+      }
+
+      @Override
+      protected boolean performEditAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBeanMR2) {
+          int from = getTextEndOffset(node);
+
+          CharSequence text = node.getText();
+          Integer next = findNextLine(text, from);
+
+          int to = (next != null)? next-1: text.length();
+          if (from == to) return false;
+
+          return placeCursor(node, to);
+        }
+
+        return super.performEditAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_insert () {
+    return new KeyHandler(KeyEvent.KEYCODE_INSERT) {
+      @Override
+      protected boolean performNavigationAction (AccessibilityNodeInfo node) {
+        if (APITests.haveJellyBean) {
+          return ScreenUtilities.performLongClick(node);
+        }
+
+        return super.performNavigationAction(node);
+      }
+    }.handleKey();
+  }
+
+  public static boolean keyHandler_delete () {
+    return new TextEditor() {
+      @Override
+      protected boolean editText (InputConnection connection) {
+        return InputService.injectKey(connection, KeyEvent.KEYCODE_FORWARD_DEL, false);
+      }
+
+      @Override
+      protected Integer editText (Editable editor, int start, int end) {
+        if (start == end) {
+          if (end == editor.length()) return null;
+          end += 1;
+        }
+
+        editor.delete(start, end);
+        return start;
+      }
+    }.editText();
+  }
+
+  public static boolean globalAction_brailleActions () {
+    ActionsActivity.launch();
+    return true;
+  }
+
+  public static boolean globalAction_showStatusIndicators () {
+    String indicators = StatusIndicators.get();
+    if (indicators == null) return false;
+    if (indicators.isEmpty()) return false;
+
+    BrailleMessage.PLAIN.show(indicators);
+    return true;
+  }
+
+  public static boolean globalAction_showWindowTitle () {
+    showWindowTitle();
+    return true;
+  }
+
+  public static boolean globalAction_toActiveWindow () {
+    ScreenDriver.unlockScreenWindow();
+    AccessibilityNodeInfo root = ScreenUtilities.getRootNode();
+
+    try {
+      ScreenDriver.setCurrentNode(root);
+    } finally {
+      root.recycle();
+      root = null;
+    }
+
+    return true;
+  }
+
+  public static boolean globalAction_toPreviousWindow () {
+    if (APITests.haveKitkat) {
+      Comparator<Integer> comparator =
+        new Comparator<Integer>() {
+          @Override
+          public int compare (Integer id1, Integer id2) {
+            return -Integer.compare(id1, id2);
+          }
+        };
+
+      return switchToWindow(comparator);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_toNextWindow () {
+    if (APITests.haveKitkat) {
+      Comparator<Integer> comparator =
+        new Comparator<Integer>() {
+          @Override
+          public int compare (Integer id1, Integer id2) {
+            return Integer.compare(id1, id2);
+          }
+        };
+
+      return switchToWindow(comparator);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_toFirstItem () {
+    return moveFocus(RenderedScreen.SearchDirection.FIRST);
+  }
+
+  public static boolean globalAction_toPreviousItem () {
+    return moveFocus(RenderedScreen.SearchDirection.BACKWARD);
+  }
+
+  public static boolean globalAction_toNextItem () {
+    return moveFocus(RenderedScreen.SearchDirection.FORWARD);
+  }
+
+  public static boolean globalAction_toLastItem () {
+    return moveFocus(RenderedScreen.SearchDirection.LAST);
+  }
+
+  public static boolean globalAction_homeScreen () {
+    if (APITests.haveJellyBean) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_notificationsShade () {
+    if (APITests.haveJellyBean) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_quickSettings () {
+    if (APITests.haveJellyBeanMR1) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_recentApplications () {
+    if (APITests.haveJellyBean) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_deviceOptions () {
+    if (APITests.haveLollipop) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_backButton () {
+    if (APITests.haveJellyBean) {
+      return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
+    }
+
+    return false;
+  }
+
+  public static boolean globalAction_menuButton () {
+    return new KeyHandler(KeyEvent.KEYCODE_MENU) {
+    }.handleKey();
+  }
+
+  public static boolean globalAction_logScreen () {
+    try {
+      ScreenLogger.logToFile();
+    } finally {
+      BrailleMessage.PLAIN.show("screen logged");
+    }
+
+    return true;
+  }
+
+  public static boolean keyHandler_function (int key) {
+    switch (key + 1) {
+      case  1: return globalAction_homeScreen();
+      case  2: return globalAction_backButton();
+      case  3: return globalAction_notificationsShade();
+      case  4: return globalAction_recentApplications();
+      case  5: return globalAction_brailleActions();
+      case  6: return globalAction_toFirstItem();
+      case  7: return globalAction_toPreviousItem();
+      case  8: return globalAction_toNextItem();
+      case  9: return globalAction_toLastItem();
+      case 10: return globalAction_menuButton();
+      case 11: return globalAction_toActiveWindow();
+      case 12: return globalAction_toPreviousWindow();
+      case 13: return globalAction_toNextWindow();
+      case 14: return globalAction_showWindowTitle();
+      case 15: return globalAction_showStatusIndicators();
+      case 16: return globalAction_logScreen();
+      default: return false;
+    }
+  }
+
+  public static boolean textHandler_setSelection (int startColumn, int startRow, int endColumn, int endRow) {
+    RenderedScreen screen = ScreenDriver.getCurrentRenderedScreen();
+    if (screen == null) return false;
+
+    ScreenElement element = screen.findScreenElement(startColumn, startRow);
+    if (element == null) return false;
+
+    {
+      ScreenElement end = screen.findScreenElement(endColumn, endRow);
+      if (end != element) return false;
+    }
+
+    AccessibilityNodeInfo node = element.getAccessibilityNode();
+    if (node == null) return false;
+
+    try {
+      Rect location = element.getBrailleLocation();
+      int left = location.left;
+      int top = location.top;
+
+      int start = element.getTextOffset((startColumn - left), (startRow - top));
+      int end = element.getTextOffset((endColumn - left), (endRow - top)) + 1;
+
+      return setSelection(node, start, end);
+    } finally {
+      node.recycle();
+      node = null;
+    }
+  }
+
+  public abstract static class NodeHandler {
+    public abstract boolean handleNode (AccessibilityNodeInfo node);
+
+    public final boolean handleNode () {
+      AccessibilityNodeInfo node = getCursorNode();
+      if (node == null) return false;
+
+      try {
+        return handleNode(node);
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    public NodeHandler () {
+    }
+  }
+
+  public static boolean textHandler_selectAll () {
+    return new NodeHandler() {
+      @Override
+      public boolean handleNode (AccessibilityNodeInfo node) {
+        return setSelection(node, 0, node.getText().length());
+      }
+    }.handleNode();
+  }
+
+  private static boolean performNodeAction (final int action) {
+    return new NodeHandler() {
+      @Override
+      public boolean handleNode (AccessibilityNodeInfo node) {
+        return node.performAction(action);
+      }
+    }.handleNode();
+  }
+
+  public static boolean textHandler_clearSelection () {
+    if (APITests.haveJellyBeanMR2) {
+      return new NodeHandler() {
+        @Override
+        public boolean handleNode (AccessibilityNodeInfo node) {
+          int start = node.getTextSelectionStart();
+          int end = node.getTextSelectionEnd();
+          if (end <= start) return false;
+          return placeCursor(node, end);
+        }
+      }.handleNode();
+    }
+
+    return false;
+  }
+
+  public static boolean textHandler_copySelection () {
+    if (APITests.haveJellyBeanMR2) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_COPY);
+    }
+
+    return false;
+  }
+
+  public static boolean textHandler_cutSelection () {
+    if (APITests.haveJellyBeanMR2) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_CUT);
+    }
+
+    return false;
+  }
+
+  public static boolean textHandler_pasteClipboard () {
+    if (APITests.haveJellyBeanMR2) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_PASTE);
+    }
+
+    return false;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputService.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputService.java
new file mode 100644
index 0000000..2ebd76e
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/InputService.java
@@ -0,0 +1,291 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.content.Context;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodInfo;
+import org.a11y.brltty.core.*;
+
+public class InputService extends InputMethodService {
+  private final static String LOG_TAG = InputService.class.getName();
+
+  private static volatile InputService inputService = null;
+
+  public static InputService getInputService () {
+    return inputService;
+  }
+
+  @Override
+  public void onCreate () {
+    super.onCreate();
+    inputService = this;
+    Log.d(LOG_TAG, "input service started");
+  }
+
+  @Override
+  public void onDestroy () {
+    try {
+      inputService = null;
+      Log.d(LOG_TAG, "input service stopped");
+    } finally {
+      super.onDestroy();
+    }
+  }
+
+  @Override
+  public void onBindInput () {
+    Log.d(LOG_TAG, "input service bound");
+  }
+
+  @Override
+  public void onUnbindInput () {
+    Log.d(LOG_TAG, "input service unbound");
+  }
+
+  @Override
+  public void onStartInput (EditorInfo info, boolean restarting) {
+    Log.d(LOG_TAG, "input service " + (restarting? "reconnected": "connected"));
+    if (info.actionLabel != null) Log.d(LOG_TAG, "action label: " + info.actionLabel);
+    Log.d(LOG_TAG, "action id: " + info.actionId);
+  }
+
+  @Override
+  public void onFinishInput () {
+    Log.d(LOG_TAG, "input service disconnected");
+  }
+
+  public static void logKeyEvent (int code, boolean press, String description) {
+    if (ApplicationSettings.LOG_KEYBOARD_EVENTS) {
+      StringBuilder sb = new StringBuilder();
+
+      sb.append("key ");
+      sb.append((press? "press": "release"));
+      sb.append(' ');
+      sb.append(description);
+
+      sb.append(": ");
+      sb.append(code);
+
+      sb.append(" (");
+      sb.append(KeyEvent.keyCodeToString(code));
+      sb.append(")");
+
+      Log.d(LOG_TAG, sb.toString());
+    }
+  }
+
+  public static void logKeyEventReceived (int code, boolean press) {
+    logKeyEvent(code, press, "received");
+  }
+
+  public static void logKeyEventSent (int code, boolean press) {
+    logKeyEvent(code, press, "sent");
+  }
+
+  public final native boolean handleKeyEvent (int code, boolean press);
+
+  public void forwardKeyEvent (int code, boolean press) {
+    InputConnection connection = getCurrentInputConnection();
+
+    if (connection != null) {
+      int action = press? KeyEvent.ACTION_DOWN: KeyEvent.ACTION_UP;
+      KeyEvent event = new KeyEvent(action, code);
+      event = KeyEvent.changeFlags(event, KeyEvent.FLAG_SOFT_KEYBOARD);
+
+      if (connection.sendKeyEvent(event)) {
+        logKeyEvent(code, press, "forwarded");
+      } else {
+        logKeyEvent(code, press, "not forwarded");
+      }
+    } else {
+      logKeyEvent(code, press, "unforwardable");
+    }
+  }
+
+  public boolean acceptKeyEvent (final int code, final boolean press) {
+    switch (code) {
+      case KeyEvent.KEYCODE_POWER:
+      case KeyEvent.KEYCODE_HOME:
+      case KeyEvent.KEYCODE_BACK:
+      case KeyEvent.KEYCODE_MENU:
+        logKeyEvent(code, press, "rejected");
+        return false;
+
+      default:
+        logKeyEvent(code, press, "accepted");
+        break;
+    }
+
+    if (BrailleService.getBrailleService() == null) {
+      Log.w(LOG_TAG, "braille service not started");
+      return false;
+    }
+
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          logKeyEvent(code, press, "delivered");
+
+          if (handleKeyEvent(code, press)) {
+            logKeyEvent(code, press, "handled");
+          } else {
+            forwardKeyEvent(code, press);
+          }
+        }
+      }
+    );
+
+    return true;
+  }
+
+  public static boolean isSystemKeyCode (int code) {
+    switch (code) {
+      case KeyEvent.KEYCODE_HOME:
+      case KeyEvent.KEYCODE_BACK:
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+  @Override
+  public boolean onKeyDown (int code, KeyEvent event) {
+    logKeyEventReceived(code, true);
+
+    if (!isSystemKeyCode(code)) {
+      if (acceptKeyEvent(code, true)) {
+        return true;
+      }
+    }
+
+    return super.onKeyDown(code, event);
+  }
+
+  @Override
+  public boolean onKeyUp (int code, KeyEvent event) {
+    logKeyEventReceived(code, false);
+
+    if (!isSystemKeyCode(code)) {
+      if (acceptKeyEvent(code, false)) {
+        return true;
+      }
+    }
+
+    return super.onKeyUp(code, event);
+  }
+
+  public static InputMethodInfo getInputMethodInfo (Class classObject) {
+    String className = classObject.getName();
+
+    for (InputMethodInfo info : ApplicationUtilities.getInputMethodManager().getEnabledInputMethodList()) {
+      if (info.getComponent().getClassName().equals(className)) {
+        return info;
+      }
+    }
+
+    return null;
+  }
+
+  public static InputMethodInfo getInputMethodInfo () {
+    return getInputMethodInfo(InputService.class);
+  }
+
+  public static boolean isEnabled () {
+    return getInputMethodInfo() != null;
+  }
+
+  public static boolean isSelected () {
+    InputMethodInfo info = getInputMethodInfo();
+
+    if (info != null) {
+      if (info.getId().equals(ApplicationUtilities.getSelectedInputMethodIdentifier())) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static void switchInputMethod () {
+    Log.w(LOG_TAG, "showing input method picker");
+    ApplicationUtilities.getInputMethodManager().showInputMethodPicker();
+  }
+
+  private static void reportInputProblem (int message) {
+    Context context = BrailleApplication.get();
+    Log.w(LOG_TAG, context.getResources().getString(message));
+    BrailleMessage.WARNING.show(context.getString(message));
+  }
+
+  public static InputConnection getInputConnection () {
+    InputService service = InputService.getInputService();
+
+    if (service != null) {
+      InputConnection connection = service.getCurrentInputConnection();
+      if (connection != null) return connection;
+      reportInputProblem(R.string.inputService_not_connected);
+    } else if (isSelected()) {
+      reportInputProblem(R.string.inputService_not_started);
+    } else if (isEnabled()) {
+      reportInputProblem(R.string.inputService_not_selected);
+    } else {
+      reportInputProblem(R.string.inputService_not_enabled);
+    }
+
+    return null;
+  }
+
+  public static boolean injectKey (InputConnection connection, int keyCode, boolean longPress) {
+    if (connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode))) {
+      logKeyEventSent(keyCode, true);
+
+      if (longPress) {
+        try {
+          Thread.sleep(ViewConfiguration.getLongPressTimeout() + ApplicationParameters.LONG_PRESS_DELAY);
+        } catch (InterruptedException exception) {
+        }
+      }
+
+      if (connection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode))) {
+        logKeyEventSent(keyCode, false);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static boolean injectKey (int keyCode, boolean longPress) {
+    InputConnection connection = getInputConnection();
+    if (connection == null) return false;
+    return injectKey(connection, keyCode, longPress);
+  }
+
+  public static boolean injectKey (int keyCode) {
+    return injectKey(keyCode, false);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LanguageUtilities.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LanguageUtilities.java
new file mode 100644
index 0000000..7a5036d
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LanguageUtilities.java
@@ -0,0 +1,113 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.util.Log;
+import java.lang.reflect.*;
+
+public final class LanguageUtilities {
+  private final static String LOG_TAG = LanguageUtilities.class.getName();
+
+  public static String[] newStringArray (CharSequence[] charSequences) {
+    int size = charSequences.length;
+    String[] strings = new String[size];
+
+    for (int i=0; i<size; i+=1) {
+      strings[i] = charSequences[i].toString();
+    }
+
+    return strings;
+  }
+
+  public static Object newInstance (String instanceType, String[] argumentTypes, Object[] arguments) {
+    Class instanceClass;
+    try {
+      instanceClass = Class.forName(instanceType);
+    } catch (ClassNotFoundException exception) {
+      Log.w(LOG_TAG, "class not found: " + instanceType);
+      return null;
+    }
+
+    Class[] argumentClasses = new Class[argumentTypes.length];
+    for (int i=0; i<argumentTypes.length; i+=1) {
+      String type = argumentTypes[i];
+
+      if (type == null) {
+        argumentClasses[i] = arguments[i].getClass();
+      } else {
+        try {
+          argumentClasses[i] = Class.forName(type);
+        } catch (ClassNotFoundException exception) {
+          Log.w(LOG_TAG, "class not found: " + type);
+          return null;
+        }
+      }
+    }
+
+    Constructor constructor;
+    try {
+      constructor = instanceClass.getConstructor(argumentClasses);
+    } catch (NoSuchMethodException exception) {
+      Log.w(LOG_TAG, "constructor not found: " + instanceType);
+      return null;
+    }
+
+    Object instance;
+    try {
+      instance = constructor.newInstance(arguments);
+    } catch (java.lang.InstantiationException exception) {
+      Log.w(LOG_TAG, "uninstantiatable class: " + instanceType);
+      return null;
+    } catch (IllegalAccessException exception) {
+      Log.w(LOG_TAG, "inaccessible constructor: " + instanceType);
+      return null;
+    } catch (InvocationTargetException exception) {
+      Log.w(LOG_TAG, "construction failed: " + instanceType);
+      return null;
+    }
+
+    return instance;
+  }
+
+  public static Object newInstance (String instanceType) {
+    return newInstance(instanceType, new String[0], new Object[0]);
+  }
+
+  public static boolean canAssign (Class target, Class source) {
+    return target.isAssignableFrom(source);
+  }
+
+  public static boolean canAssign (Class target, String source) {
+    try {
+      return canAssign(target, Class.forName(source));
+    } catch (ClassNotFoundException exception) {
+    }
+
+    return false;
+  }
+
+  public static int compare (int value1, int value2) {
+    if (value1 < value2) return -1;
+    if (value1 > value2) return 1;
+    return 0;
+  }
+
+  private LanguageUtilities () {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ListBrailleRenderer.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ListBrailleRenderer.java
new file mode 100644
index 0000000..8eea573
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ListBrailleRenderer.java
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+
+public class ListBrailleRenderer extends BrailleRenderer {
+  @Override
+  protected final void setBrailleLocations (ScreenElementList elements) {
+    int left = 0;
+    int top = 0;
+
+    for (ScreenElement element : elements) {
+      String[] text = element.getBrailleText();
+
+      if (text != null) {
+        int right = left + getTextWidth(text) - 1;
+        int bottom = top + text.length - 1;
+
+        element.setBrailleLocation(left, top, right, bottom);
+        top = bottom + 1;
+      }
+    }
+  }
+
+  public ListBrailleRenderer () {
+    super();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LockUtilities.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LockUtilities.java
new file mode 100644
index 0000000..bfd5d93
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/LockUtilities.java
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.os.PowerManager;
+import java.util.concurrent.TimeUnit;
+
+public abstract class LockUtilities {
+  private LockUtilities () {
+  }
+
+  private final static PowerManager.WakeLock wakeLock =
+    ApplicationUtilities.getPowerManager().newWakeLock(
+      PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+      BrailleApplication.get().getString(R.string.app_name)
+    );
+
+  private final static long LOCK_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+  public static boolean isLocked () {
+    if (!ApplicationUtilities.getPowerManager().isScreenOn()) {
+      if (ApplicationUtilities.getKeyguardManager().inKeyguardRestrictedInputMode()) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static void resetTimer () {
+    wakeLock.acquire(LOCK_TIMEOUT);
+    wakeLock.release();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Logger.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Logger.java
new file mode 100644
index 0000000..7fdd868
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/Logger.java
@@ -0,0 +1,58 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.util.Log;
+import java.io.IOException;
+
+public abstract class Logger {
+  private final static String LOG_TAG = Logger.class.getName();
+
+  protected String getLogTag () {
+    return LOG_TAG;
+  }
+
+  protected void log (String message) throws IOException {
+    Log.d(LOG_TAG, message);
+  }
+
+  protected Logger () {
+  }
+
+  public static String shrinkText (String text) {
+    if (text == null) return null;
+
+    final int threshold = 50;
+    final char delimiter = '\n';
+
+    int length = text.length();
+    int to = text.lastIndexOf(delimiter);
+    int from = (to == -1)? length: text.indexOf(delimiter);
+    to += 1;
+
+    from = Math.min(from, threshold);
+    to = Math.max(to, (length - threshold));
+
+    if (from < to) {
+      text = text.substring(0, from) + "[...]" + text.substring(to);
+    }
+
+    return text;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeComparator.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeComparator.java
new file mode 100644
index 0000000..dcecb6f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeComparator.java
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.Comparator;
+
+public interface NodeComparator extends Comparator<AccessibilityNodeInfo> {
+  public int compare (AccessibilityNodeInfo node1, AccessibilityNodeInfo node2);
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeTester.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeTester.java
new file mode 100644
index 0000000..d256f66
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/NodeTester.java
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+public interface NodeTester {
+  public boolean testNode (AccessibilityNodeInfo node);
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/OverlayWindow.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/OverlayWindow.java
new file mode 100644
index 0000000..d283ea7
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/OverlayWindow.java
@@ -0,0 +1,82 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+public abstract class OverlayWindow {
+  protected static Context getContext () {
+    return BrailleService.getBrailleService();
+  }
+
+  protected static WindowManager getWindowManager () {
+    return (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+  }
+
+  private final LayoutParams layoutParameters = new LayoutParams();
+  private View currentView = null;
+
+  protected OverlayWindow () {
+    layoutParameters.format = PixelFormat.OPAQUE;
+    layoutParameters.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+
+    layoutParameters.width = LayoutParams.WRAP_CONTENT;
+    layoutParameters.height = LayoutParams.WRAP_CONTENT;
+
+    layoutParameters.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
+
+    if (APITests.haveLollipopMR1) {
+      layoutParameters.type = LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+    } else {
+      layoutParameters.type = LayoutParams.TYPE_SYSTEM_OVERLAY;
+    }
+  }
+
+  protected final void setView (View newView) {
+    synchronized (this) {
+      if (newView != currentView) {
+        WindowManager wm = getWindowManager();
+        if (currentView != null) wm.removeView(currentView);
+
+        if ((currentView = newView) != null) {
+          wm.addView(currentView, layoutParameters);
+          currentView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+          currentView.requestFocus();
+        }
+      }
+    }
+  }
+
+  protected final View setView (int resource) {
+    LayoutInflater inflater = LayoutInflater.from(getContext());
+    View view = inflater.inflate(resource, null);
+    setView(view);
+    return view;
+  }
+
+  protected final void removeView () {
+    setView(null);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/PcmDevice.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/PcmDevice.java
new file mode 100644
index 0000000..0f28104
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/PcmDevice.java
@@ -0,0 +1,180 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.annotation.SuppressLint;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.util.Log;
+
+@SuppressLint("NewApi")
+public final class PcmDevice implements AutoCloseable {
+  private final static String LOG_TAG = PcmDevice.class.getName();
+
+  private final static int STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
+  private final static int TRACK_MODE = AudioTrack.MODE_STREAM;
+  private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+
+  private int sampleRate = 8000;
+  private int channelCount = 1;
+  private AudioTrack audioTrack = null;
+
+  public final boolean isStarted () {
+    return audioTrack != null;
+  }
+
+  public final boolean isPlaying () {
+    return audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;
+  }
+
+  private final void checkNotStarted () {
+    if (isStarted()) throw new IllegalStateException("track already started");
+  }
+
+  public final int getSampleRate () {
+    return sampleRate;
+  }
+
+  public final void setSampleRate (int rate) {
+    checkNotStarted();
+    sampleRate = rate;
+  }
+
+  public final int getChannelPositionMask () {
+    switch (channelCount) {
+      case 1:
+        return AudioFormat.CHANNEL_OUT_MONO;
+
+      case 2:
+        return AudioFormat.CHANNEL_OUT_STEREO;
+
+      case 3:
+        return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+
+      case 4:
+        return AudioFormat.CHANNEL_OUT_QUAD;
+
+      case 5:
+        return AudioFormat.CHANNEL_OUT_5POINT1;
+
+      case 6:
+        return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
+
+      case 7:
+        return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
+
+      default:
+        Log.w(LOG_TAG, ("unsupported channel count: " + channelCount));
+        return 0;
+    }
+  }
+
+  public final int getChannelCount () {
+    return channelCount;
+  }
+
+  public final void setChannelCount (int count) {
+    checkNotStarted();
+    channelCount = count;
+  }
+
+  public final int getBufferSize () {
+    return AudioTrack.getMinBufferSize(
+      getSampleRate(),
+      getChannelPositionMask(),
+      AUDIO_ENCODING
+    );
+  }
+
+  private final AudioTrack newAudioTrack () {
+    if (APITests.haveOreo) {
+      AudioAttributes attributes = new AudioAttributes.Builder()
+        .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
+        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+        .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+        .build();
+
+      AudioFormat format = new AudioFormat.Builder()
+        .setEncoding(AUDIO_ENCODING)
+        .setSampleRate(getSampleRate())
+        .setChannelMask(getChannelPositionMask())
+        .build();
+
+      return new AudioTrack.Builder()
+        .setAudioAttributes(attributes)
+        .setAudioFormat(format)
+        .setBufferSizeInBytes(getBufferSize())
+        .setTransferMode(AudioTrack.MODE_STREAM)
+        .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
+        .build();
+    }
+
+    return new AudioTrack(STREAM_TYPE, getSampleRate(),
+      getChannelPositionMask(), AUDIO_ENCODING,
+      getBufferSize(), TRACK_MODE
+    );
+  }
+
+  protected final void start () {
+    if (!isStarted()) audioTrack = newAudioTrack();
+  }
+
+  public final boolean write (short[] samples) {
+    start();
+
+    int offset = 0;
+    int length = samples.length;
+
+    while (length > 0) {
+      int count = audioTrack.write(samples, offset, length);
+      if (count < 1) return false;
+
+      offset += count;
+      length -= count;
+
+      if (!isPlaying()) audioTrack.play();
+    }
+
+    return true;
+  }
+
+  public final void push () {
+  }
+
+  public final void await () {
+    if (isPlaying()) audioTrack.stop();
+  }
+
+  public final void cancel () {
+    if (isPlaying()) audioTrack.pause();
+    audioTrack.flush();
+  }
+
+  @Override
+  public final void close () {
+    await();
+    audioTrack.release();
+    audioTrack = null;
+  }
+
+  public PcmDevice () {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RealScreenElement.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RealScreenElement.java
new file mode 100644
index 0000000..a49e96c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RealScreenElement.java
@@ -0,0 +1,349 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.List;
+
+public class RealScreenElement extends ScreenElement {
+  private final static String LOG_TAG = RealScreenElement.class.getName();
+
+  private final AccessibilityNodeInfo accessibilityNode;
+
+  public RealScreenElement (String text, AccessibilityNodeInfo node) {
+    super(text);
+    accessibilityNode = AccessibilityNodeInfo.obtain(node);
+
+    {
+      Rect location = new Rect();
+      node.getBoundsInScreen(location);
+      location.right -= 1;
+      location.bottom -= 1;
+      setVisualLocation(location);
+    }
+  }
+
+  @Override
+  public AccessibilityNodeInfo getAccessibilityNode () {
+    return AccessibilityNodeInfo.obtain(accessibilityNode);
+  }
+
+  @Override
+  public boolean isEditable () {
+    return ScreenUtilities.isEditable(accessibilityNode);
+  }
+
+  private static boolean setInputFocus (AccessibilityNodeInfo node) {
+    if (node.isFocused()) return true;
+
+    if (!node.performAction(AccessibilityNodeInfo.ACTION_FOCUS)) return false;
+    final long startTime = ApplicationUtilities.getAbsoluteTime();
+
+    while (true) {
+      try {
+        Thread.sleep(ApplicationParameters.FOCUS_WAIT_INTERVAL);
+      } catch (InterruptedException exception) {
+      }
+
+      {
+        AccessibilityNodeInfo refreshed = ScreenUtilities.getRefreshedNode(node);
+
+        if (refreshed != null) {
+          try {
+            if (refreshed.isFocused()) return true;
+          } finally {
+            refreshed.recycle();
+            refreshed = null;
+          }
+        }
+      }
+
+      if (ApplicationUtilities.getRelativeTime(startTime) >= ApplicationParameters.FOCUS_WAIT_TIMEOUT) {
+        return false;
+      }
+    }
+  }
+
+  private final boolean setInputFocus () {
+    AccessibilityNodeInfo node = ScreenUtilities.getRefreshedNode(accessibilityNode);
+
+    if (node != null) {
+      try {
+        return setInputFocus(node);
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    return false;
+  }
+
+  private AccessibilityNodeInfo getFocusableNode () {
+    return ScreenUtilities.findActionableNode(accessibilityNode,
+      AccessibilityNodeInfo.ACTION_FOCUS |
+      AccessibilityNodeInfo.ACTION_CLEAR_FOCUS
+    );
+  }
+
+  private boolean performNodeAction (int action) {
+    return ScreenUtilities.performAction(accessibilityNode, action);
+  }
+
+  private boolean performNodeAction (AccessibilityNodeInfo.AccessibilityAction action) {
+    return ScreenUtilities.performAction(accessibilityNode, action);
+  }
+
+  public boolean injectKey (int keyCode, boolean longPress) {
+    AccessibilityNodeInfo node = getFocusableNode();
+
+    if (node != null) {
+      try {
+        if (setInputFocus(node)) {
+          if (InputService.injectKey(keyCode, longPress)) {
+            return true;
+          }
+        }
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  public boolean bringCursor () {
+    if (APITests.haveJellyBean) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+    }
+
+    return false;
+  }
+
+  @Override
+  public boolean onBringCursor () {
+    if (APITests.haveJellyBean) {
+      {
+        AccessibilityNodeInfo node = accessibilityNode.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+
+        if (node != null) {
+          try {
+            return node.equals(accessibilityNode);
+          } finally {
+            node.recycle();
+            node = null;
+          }
+        }
+      }
+
+      if (performNodeAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)) return true;
+    }
+
+    if (APITests.haveIceCreamSandwich) {
+      AccessibilityNodeInfo node = getFocusableNode();
+
+      if (node != null) {
+        boolean isFocused = node.isFocused();
+
+        if (!isFocused) {
+          if (node.performAction(AccessibilityNodeInfo.ACTION_FOCUS)) {
+            isFocused = true;
+          }
+        }
+
+        node.recycle();
+        node = null;
+
+        if (isFocused) return true;
+      }
+    }
+
+    return super.onBringCursor();
+  }
+
+  @Override
+  public boolean onClick () {
+    if (APITests.haveIceCreamSandwich) {
+      if (isEditable()) {
+        return performNodeAction(AccessibilityNodeInfo.ACTION_FOCUS);
+      }
+    }
+
+    if (APITests.haveJellyBean) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_CLICK);
+    }
+
+    if (APITests.haveIceCreamSandwich) {
+      return injectKey(KeyEvent.KEYCODE_DPAD_CENTER, false);
+    }
+
+    return super.onClick();
+  }
+
+  @Override
+  public boolean onLongClick () {
+    if (APITests.haveJellyBean) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+    }
+
+    if (APITests.haveIceCreamSandwich) {
+      return injectKey(KeyEvent.KEYCODE_DPAD_CENTER, true);
+    }
+
+    return super.onLongClick();
+  }
+
+  @Override
+  public boolean onScrollBackward () {
+    if (APITests.haveJellyBean) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+    }
+
+    if (APITests.haveIceCreamSandwich) {
+      return injectKey(KeyEvent.KEYCODE_PAGE_UP, false);
+    }
+
+    return super.onScrollBackward();
+  }
+
+  @Override
+  public boolean onScrollForward () {
+    if (APITests.haveJellyBean) {
+      return performNodeAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+    }
+
+    if (APITests.haveIceCreamSandwich) {
+      return injectKey(KeyEvent.KEYCODE_PAGE_DOWN, false);
+    }
+
+    return super.onScrollForward();
+  }
+
+  @Override
+  public boolean onContextClick () {
+    if (APITests.haveMarshmallow) {
+      return performNodeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CONTEXT_CLICK);
+    }
+
+    return super.onContextClick();
+  }
+
+  @Override
+  public boolean onAccessibilityActions () {
+    if (APITests.haveLollipop) {
+      List<AccessibilityNodeInfo.AccessibilityAction> actions = accessibilityNode.getActionList();
+
+      if (actions != null) {
+        final CharSequence[] labels;
+        final int[] ids;
+
+        {
+          int size = actions.size();
+          CharSequence[] labelBuffer = new CharSequence[size];
+          int[] idBuffer = new int[size];
+          int count = 0;
+
+          for (AccessibilityNodeInfo.AccessibilityAction action : actions) {
+            CharSequence label = action.getLabel();
+            if (label == null) continue;
+            if (label.length() == 0) continue;
+
+            labelBuffer[count] = label;
+            idBuffer[count] = action.getId();
+            count += 1;
+          }
+
+          if (count > 0) {
+            labels = new CharSequence[count];
+            System.arraycopy(labelBuffer, 0, labels, 0, count);
+
+            ids = new int[count];
+            System.arraycopy(idBuffer, 0, ids, 0, count);
+          } else {
+            labels = null;
+            ids = null;
+          }
+        }
+
+        if (labels != null) {
+          BrailleApplication.post(
+            new Runnable() {
+              @Override
+              public void run () {
+                new ChooserWindow(
+                  labels,
+                  R.string.CHOOSER_TITLE_ACCESSIBILITY_ACTIONS,
+                  new ChooserWindow.ItemClickListener() {
+                    @Override
+                    public void onClick (int position) {
+                      accessibilityNode.performAction(ids[position]);
+                    }
+                  }
+                );
+              }
+            }
+          );
+
+          return true;
+        }
+      }
+    }
+
+    return super.onAccessibilityActions();
+  }
+
+  @Override
+  public boolean onDescribeElement () {
+    BrailleMessage.DEBUG.show(ScreenLogger.toString(accessibilityNode));
+    return true;
+  }
+
+  @Override
+  public boolean performAction (int column, int row) {
+    if (ScreenUtilities.isEditable(accessibilityNode)) {
+      if (!onBringCursor()) return false;
+      setInputFocus();
+
+      int offset = getTextOffset(column, row);
+      return InputHandlers.placeCursor(accessibilityNode, offset);
+    }
+
+    return super.performAction(column, row);
+  }
+
+  @Override
+  protected String[] makeBrailleText (String text) {
+    String[] lines = super.makeBrailleText(text);
+    if (lines == null) return null;
+
+    if (ScreenUtilities.isEditable(accessibilityNode)) {
+      int count = lines.length;
+
+      for (int index=0; index<count; index+=1) {
+        lines[index] += ' ';
+      }
+    }
+
+    return lines;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RenderedScreen.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RenderedScreen.java
new file mode 100644
index 0000000..e4a8338
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/RenderedScreen.java
@@ -0,0 +1,859 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+public class RenderedScreen {
+  private final static String LOG_TAG = RenderedScreen.class.getName();
+
+  private final AccessibilityNodeInfo eventNode;
+  private final AccessibilityNodeInfo rootNode;
+
+  private final ScreenElementList screenElements = new ScreenElementList();
+  private final List<String> screenRows = new ArrayList<>();
+
+  private final int screenWidth;
+  private final AccessibilityNodeInfo cursorNode;
+
+  private final AccessibilityNodeInfo getNode (AccessibilityNodeInfo node) {
+    if (node == null) return null;
+    return AccessibilityNodeInfo.obtain(node);
+  }
+
+  public final AccessibilityNodeInfo getRootNode () {
+    return getNode(rootNode);
+  }
+
+  public final int getScreenWidth () {
+    return screenWidth;
+  }
+
+  public final int getScreenHeight () {
+    return screenRows.size();
+  }
+
+  public final String getScreenRow (int index) {
+    return screenRows.get(index);
+  }
+
+  public final AccessibilityNodeInfo getCursorNode () {
+    return getNode(cursorNode);
+  }
+
+  public final ScreenElement getScreenElement (AccessibilityNodeInfo node) {
+    if (node == null) return null;
+    return screenElements.get(node);
+  }
+
+  public final ScreenElement findScreenElement (AccessibilityNodeInfo node) {
+    ScreenElement element = getScreenElement(node);
+
+    if (element != null) {
+      if (element.getBrailleLocation() != null) {
+        return element;
+      }
+    }
+
+    {
+      int childCount = node.getChildCount();
+
+      for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+        AccessibilityNodeInfo child = node.getChild(childIndex);
+
+        if (child != null) {
+          element = findScreenElement(child);
+
+          child.recycle();
+          child = null;
+
+          if (element != null) return element;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public final ScreenElement findScreenElement (int column, int row) {
+    return screenElements.findByBrailleLocation(column, row);
+  }
+
+  public final boolean performAction (int column, int row) {
+    ScreenElement element = findScreenElement(column, row);
+    if (element == null) return false;
+
+    Rect location = element.getBrailleLocation();
+    return element.performAction((column - location.left), (row - location.top));
+  }
+
+  private final static NodeTester[] significantNodeFilters = new NodeTester[] {
+    new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        if (!TextUtils.equals(node.getPackageName(), "com.samsung.android.messaging")) return false;
+        if (!ScreenUtilities.isSubclassOf(node, LinearLayout.class)) return false;
+        if (node.getChildCount() != 1) return false;
+        return true;
+      }
+    }
+  };
+
+  public static interface NodeActionVerifier {
+    public boolean verify (AccessibilityNodeInfo node);
+  }
+
+  private final static Map<Integer, NodeActionVerifier> nodeActionVerifiers =
+               new HashMap<Integer, NodeActionVerifier>()
+  {
+    {
+      put(
+        AccessibilityNodeInfo.ACTION_CLICK,
+        new NodeActionVerifier() {
+          @Override
+          public boolean verify (AccessibilityNodeInfo node) {
+            return node.isClickable();
+          }
+        }
+      );
+
+      put(
+        AccessibilityNodeInfo.ACTION_LONG_CLICK,
+        new NodeActionVerifier() {
+          @Override
+          public boolean verify (AccessibilityNodeInfo node) {
+            return node.isLongClickable();
+          }
+        }
+      );
+
+      put(
+        AccessibilityNodeInfo.ACTION_SCROLL_FORWARD,
+        new NodeActionVerifier() {
+          @Override
+          public boolean verify (AccessibilityNodeInfo node) {
+            return node.isScrollable();
+          }
+        }
+      );
+
+      put(
+        AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD,
+        new NodeActionVerifier() {
+          @Override
+          public boolean verify (AccessibilityNodeInfo node) {
+            return node.isScrollable();
+          }
+        }
+      );
+    }
+  };
+
+  public static Set<Integer> getVerifiableNodeActions () {
+    return nodeActionVerifiers.keySet();
+  }
+
+  private final static int SIGNIFICANT_NODE_ACTIONS;
+  static {
+    int actions = 0;
+
+    for (Integer action : getVerifiableNodeActions()) {
+      actions |= action;
+    }
+
+    SIGNIFICANT_NODE_ACTIONS = actions;
+  }
+
+  public static int getSignificantActions (AccessibilityNodeInfo node) {
+    int actions = node.getActions() & SIGNIFICANT_NODE_ACTIONS;
+
+    if (actions != 0) {
+      for (Integer action : getVerifiableNodeActions()) {
+        if ((actions & action) != 0) {
+          if (!nodeActionVerifiers.get(action).verify(node)) {
+            if ((actions &= ~action) == 0) {
+              break;
+            }
+          }
+        }
+      }
+    }
+
+    return actions;
+  }
+
+  public static boolean hasSignificantAction (AccessibilityNodeInfo node) {
+    return getSignificantActions(node) != 0;
+  }
+
+  private static String makeText (AccessibilityNodeInfo node) {
+    StringBuilder sb = new StringBuilder();
+    boolean allowZeroLength = false;
+    boolean includeDescription = false;
+
+    if (node.isCheckable()) {
+      includeDescription = true;
+      if (sb.length() > 0) sb.append(' ');
+
+      if (ScreenUtilities.isSwitch(node)) {
+        sb.append(Characters.SWITCH_BEGIN);
+        sb.append(node.isChecked()? Characters.SWITCH_ON: Characters.SWITCH_OFF);
+        sb.append(Characters.SWITCH_END);
+      } else if (ScreenUtilities.isRadioButton(node)) {
+        sb.append(Characters.RADIO_BEGIN);
+        sb.append(node.isChecked()? Characters.RADIO_MARK: ' ');
+        sb.append(Characters.RADIO_END);
+      } else {
+        sb.append(Characters.CHECKBOX_BEGIN);
+        sb.append(node.isChecked()? Characters.CHECKBOX_MARK: ' ');
+        sb.append(Characters.CHECKBOX_END);
+      }
+    }
+
+    {
+      String text = ScreenUtilities.getText(node);
+
+      if (text != null) {
+        allowZeroLength = true;
+
+        if (text.length() > 0) {
+          if (sb.length() > 0) sb.append(' ');
+          sb.append(text);
+        }
+      } else if (ScreenUtilities.isSubclassOf(node, ImageView.class)) {
+        includeDescription = true;
+      }
+    }
+
+    if (includeDescription) {
+      String description = ScreenUtilities.getDescription(node);
+
+      if (description != null) {
+        if (sb.length() > 0) sb.append(' ');
+        sb.append('[');
+        sb.append(description);
+        sb.append(']');
+      }
+    }
+
+    if (APITests.haveKitkat) {
+      AccessibilityNodeInfo.RangeInfo range = node.getRangeInfo();
+
+      if (range != null) {
+        if (sb.length() == 0) {
+          CharSequence description = ScreenUtilities.getDescription(node);
+          if (description != null) sb.append(description);
+        }
+
+        if (sb.length() > 0) sb.append(' ');
+        String format = ScreenUtilities.getRangeValueFormat(range);
+
+        sb.append("@");
+        sb.append(String.format(format, range.getCurrent()));
+        sb.append(" (");
+        sb.append(String.format(format, range.getMin()));
+        sb.append(" - ");
+        sb.append(String.format(format, range.getMax()));
+        sb.append(")");
+      }
+    }
+
+    if (!(allowZeroLength || (sb.length() > 0))) {
+      for (NodeTester filter : significantNodeFilters) {
+        if (filter.testNode(node)) {
+          String description = ScreenUtilities.getDescription(node);
+          if (description != null) sb.append(description);
+          break;
+        }
+      }
+
+      if (sb.length() == 0) return null;
+    }
+
+    return sb.toString();
+  }
+
+  private static String getDescription (AccessibilityNodeInfo node) {
+    {
+      String description = ScreenUtilities.getDescription(node);
+      if (description != null) return description;
+    }
+
+    {
+      StringBuilder sb = new StringBuilder();
+      int childCount = node.getChildCount();
+
+      for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+        AccessibilityNodeInfo child = node.getChild(childIndex);
+
+        if (child != null) {
+          if (!hasSignificantAction(child)) {
+            String description = ScreenUtilities.getDescription(child);
+
+            if (description != null) {
+              if (sb.length() > 0) sb.append(' ');
+              sb.append(description);
+            }
+          }
+
+          child.recycle();
+          child = null;
+        }
+      }
+
+      if (sb.length() > 0) return sb.toString();
+    }
+
+    return null;
+  }
+
+  private final int addScreenElements (AccessibilityNodeInfo root) {
+    int propagatedActions = 0;
+
+    if (root != null) {
+      {
+        int childCount = root.getChildCount();
+
+        for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+          AccessibilityNodeInfo child = root.getChild(childIndex);
+
+          if (child != null) {
+            try {
+              propagatedActions |= addScreenElements(child);
+            } finally {
+              child.recycle();
+              child = null;
+            }
+          }
+        }
+      }
+
+      int actions = getSignificantActions(root);
+      boolean hasActions = (actions & ~propagatedActions) != 0;
+      propagatedActions &= ~actions;
+
+      if (ScreenUtilities.isVisible(root)) {
+        String text = makeText(root);
+        boolean hasText = text != null;
+        boolean isSkippable = false;
+
+        {
+          String label = ChromeRole.getLabel(root);
+
+          if (label != null) {
+            if (label.isEmpty()) {
+              isSkippable = true;
+            } else {
+              label = String.format("(%s)", label);
+
+              if (text != null) {
+                label += ' ';
+                label += text;
+              }
+
+              text = label;
+            }
+          }
+        }
+
+        if (hasActions && !hasText) {
+          String description = getDescription(root);
+
+          if (!isSkippable) {
+            if ((description == null) && (text == null)) {
+              description = ScreenUtilities.getClassName(root);
+
+              if (APITests.haveJellyBeanMR2) {
+                String name = root.getViewIdResourceName();
+
+                if (name != null) {
+                  String marker = ":id/";
+                  int index = name.indexOf(marker);
+                  name = (index < 0)? null: name.substring(index + marker.length());
+                }
+
+                if (description == null) {
+                  description = name;
+                } else if (name != null) {
+                  description += " " + name;
+                }
+              }
+
+              if (description == null) description = "?";
+              description = "(" + description + ")";
+            }
+
+            if (description != null) {
+              if (text == null) {
+                text = description;
+              } else {
+                text += ' ';
+                text += description;
+              }
+            }
+          }
+        }
+
+        if (text != null) {
+          propagatedActions |= SIGNIFICANT_NODE_ACTIONS & ~actions;
+          if (!root.isEnabled()) text += " (disabled)";
+          screenElements.add(text, root);
+        }
+      }
+    }
+
+    return propagatedActions;
+  }
+
+  private final int findScreenWidth () {
+    int width = 1;
+
+    if (screenRows.isEmpty()) {
+      screenRows.add("waiting for screen update");
+    }
+
+    for (String row : screenRows) {
+      int length = row.length();
+      if (length > width) width = length;
+    }
+
+    return width;
+  }
+
+  private final AccessibilityNodeInfo findCursorNode () {
+    AccessibilityNodeInfo root = getRootNode();
+
+    if (root != null) {
+      if (APITests.haveJellyBean) {
+        AccessibilityNodeInfo node = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+
+        if (node != null) {
+          root.recycle();
+          root = node;
+          node = ScreenUtilities.findTextNode(root);
+
+          if (node != null) {
+            root.recycle();
+            root = node;
+            node = null;
+          }
+
+          return root;
+        }
+      }
+
+      {
+        AccessibilityNodeInfo node;
+
+        if (APITests.haveJellyBean) {
+          node = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+        } else if (APITests.haveIceCreamSandwich) {
+          node = ScreenUtilities.findFocusedNode(root);
+        } else {
+          node = null;
+        }
+
+        if (!APITests.haveJellyBean) {
+          if (node == null) {
+            if (APITests.haveIceCreamSandwich) {
+              if ((node = ScreenUtilities.findFocusableNode(root)) != null) {
+                if (!node.performAction(AccessibilityNodeInfo.ACTION_FOCUS)) {
+                  node.recycle();
+                  node = null;
+                }
+              }
+            }
+          }
+        }
+
+        if (node != null) {
+          root.recycle();
+          root = node;
+          node = ScreenUtilities.findSelectedNode(root);
+
+          if (node != null) {
+            root.recycle();
+            root = node;
+            node = null;
+          }
+
+          if ((node = ScreenUtilities.findTextNode(root)) == null) {
+            node =  ScreenUtilities.findDescribedNode(root);
+          }
+
+          if (node != null) {
+            root.recycle();
+            root = node;
+            node = null;
+          }
+
+          return root;
+        }
+      }
+    }
+
+    return root;
+  }
+
+  private static void log (String message) {
+    Log.d(LOG_TAG, message);
+  }
+
+  private final void logRenderedElements () {
+    log(("screen element count: " + screenElements.size()));
+    int elementIndex = 0;
+
+    for (ScreenElement element : screenElements) {
+      log(
+        String.format(Locale.ROOT,
+          "screen element %d: %s", elementIndex++, element.getElementText()
+        )
+      );
+    }
+  }
+
+  private final void logRenderedRows () {
+    log(("screen row count: " + screenRows.size()));
+    log(("screen width: " + screenWidth));
+    int rowIndex = 0;
+
+    for (String row : screenRows) {
+      log(
+        String.format(Locale.ROOT,
+          "screen row %d: %s", rowIndex++, row.toString()
+        )
+      );
+    }
+  }
+
+  public final void logRenderedScreen () {
+    log("begin rendered screen");
+    logRenderedElements();
+    logRenderedRows();
+    log("end rendered screen");
+  }
+
+  public RenderedScreen (AccessibilityNodeInfo node) {
+    if (node != null) node = AccessibilityNodeInfo.obtain(node);
+
+    eventNode = node;
+    rootNode = ScreenUtilities.findRootNode(node);
+
+    addScreenElements(rootNode);
+    screenElements.finish();
+    BrailleRenderer.getBrailleRenderer().renderScreenElements(screenElements, screenRows);
+
+    screenWidth = findScreenWidth();
+    cursorNode = findCursorNode();
+
+    if (ApplicationSettings.LOG_RENDERED_SCREEN) {
+      logRenderedScreen();
+    }
+  }
+
+  private abstract static class NextElementGetter {
+    public abstract ScreenElement getNextElement (ScreenElement from);
+  }
+
+  private final static NextElementGetter forwardElementGetter =
+    new NextElementGetter() {
+      @Override
+      public final ScreenElement getNextElement (ScreenElement from) {
+        return from.getForwardElement();
+      }
+    };
+
+  private final static NextElementGetter backwardElementGetter =
+    new NextElementGetter() {
+      @Override
+      public final ScreenElement getNextElement (ScreenElement from) {
+        return from.getBackwardElement();
+      }
+    };
+
+  private abstract class NextElementFinder extends NextElementGetter {
+    protected abstract ScreenElement getCachedElement (ScreenElement from);
+    protected abstract void setCachedElement (ScreenElement from, ScreenElement to);
+
+    protected abstract boolean goForward ();
+    protected abstract int getLesserEdge (Rect location);
+    protected abstract int getGreaterEdge (Rect location);
+    protected abstract int getLesserSide (Rect location);
+    protected abstract int getGreaterSide (Rect location);
+
+    private final ScreenElement findNextElement (ScreenElement from) {
+      final Rect fromLocation = from.getVisualLocation();
+      if (fromLocation == null) return null;
+
+      final boolean forward = goForward();
+      final int fromEdge = forward? getGreaterEdge(fromLocation):
+                                    getLesserEdge(fromLocation);
+
+      double bestAngle = Double.MAX_VALUE;
+      double bestDistance = Double.MAX_VALUE;
+      ScreenElement bestElement = null;
+
+      for (ScreenElement to : screenElements) {
+        if (to == from) continue;
+
+        Rect toLocation = to.getVisualLocation();
+        if (toLocation == null) continue;
+
+        double edgeDistance = forward? (getLesserEdge(toLocation) - fromEdge):
+                                       (fromEdge - getGreaterEdge(toLocation));
+
+        if (edgeDistance < 0d) continue;
+        double sideDistance = getLesserSide(fromLocation) - getGreaterSide(toLocation);
+        if (sideDistance < 0d) sideDistance = getLesserSide(toLocation) - getGreaterSide(fromLocation);
+        if (sideDistance < 0d) sideDistance = 0d;
+
+        double angle = Math.atan2(sideDistance, edgeDistance);
+        double distance = Math.hypot(sideDistance, edgeDistance);
+
+        if (bestElement != null) {
+          if (angle > bestAngle) continue;
+
+          if (angle == bestAngle) {
+            if (distance >= bestDistance) {
+              continue;
+            }
+          }
+        }
+
+        bestAngle = angle;
+        bestDistance = distance;
+        bestElement = to;
+      }
+
+      return bestElement;
+    }
+
+    @Override
+    public final ScreenElement getNextElement (ScreenElement from) {
+      ScreenElement to = getCachedElement(from);
+
+      if (to == from) {
+        to = findNextElement(from);
+        setCachedElement(from, to);
+      }
+
+      return to;
+    }
+  }
+
+  private abstract class VerticalElementFinder extends NextElementFinder {
+    @Override
+    protected final int getLesserEdge (Rect location) {
+      return location.top;
+    }
+
+    @Override
+    protected final int getGreaterEdge (Rect location) {
+      return location.bottom;
+    }
+
+    @Override
+    protected final int getLesserSide (Rect location) {
+      return location.left;
+    }
+
+    @Override
+    protected final int getGreaterSide (Rect location) {
+      return location.right;
+    }
+  }
+
+  private final NextElementGetter upwardElementFinder =
+    new VerticalElementFinder() {
+      @Override
+      protected final ScreenElement getCachedElement (ScreenElement from) {
+        return from.getUpwardElement();
+      }
+
+      @Override
+      protected final void setCachedElement (ScreenElement from, ScreenElement to) {
+        from.setUpwardElement(to);
+      }
+
+      @Override
+      protected final boolean goForward () {
+        return false;
+      }
+    };
+
+  private final NextElementGetter downwardElementFinder =
+    new VerticalElementFinder() {
+      @Override
+      protected final ScreenElement getCachedElement (ScreenElement from) {
+        return from.getDownwardElement();
+      }
+
+      @Override
+      protected final void setCachedElement (ScreenElement from, ScreenElement to) {
+        from.setDownwardElement(to);
+      }
+
+      @Override
+      protected final boolean goForward () {
+        return true;
+      }
+    };
+
+  private abstract class HorizontalElementFinder extends NextElementFinder {
+    @Override
+    protected final int getLesserEdge (Rect location) {
+      return location.left;
+    }
+
+    @Override
+    protected final int getGreaterEdge (Rect location) {
+      return location.right;
+    }
+
+    @Override
+    protected final int getLesserSide (Rect location) {
+      return location.top;
+    }
+
+    @Override
+    protected final int getGreaterSide (Rect location) {
+      return location.bottom;
+    }
+  }
+
+  private final NextElementGetter leftwardElementFinder =
+    new HorizontalElementFinder() {
+      @Override
+      protected final ScreenElement getCachedElement (ScreenElement from) {
+        return from.getLeftwardElement();
+      }
+
+      @Override
+      protected final void setCachedElement (ScreenElement from, ScreenElement to) {
+        from.setLeftwardElement(to);
+      }
+
+      @Override
+      protected final boolean goForward () {
+        return false;
+      }
+    };
+
+  private final NextElementGetter rightwardElementFinder =
+    new HorizontalElementFinder() {
+      @Override
+      protected final ScreenElement getCachedElement (ScreenElement from) {
+        return from.getRightwardElement();
+      }
+
+      @Override
+      protected final void setCachedElement (ScreenElement from, ScreenElement to) {
+        from.setRightwardElement(to);
+      }
+
+      @Override
+      protected final boolean goForward () {
+        return true;
+      }
+    };
+
+  private final boolean moveFocus (
+    ScreenElement element, NextElementGetter nextElementGetter, boolean inclusive
+  ) {
+    ScreenElement end = element;
+
+    while (true) {
+      if (inclusive) {
+        inclusive = false;
+      } else if ((element = nextElementGetter.getNextElement(element)) == end) {
+        return false;
+      } else if (element == null) {
+        return false;
+      }
+
+      if (element.bringCursor()) return true;
+    }
+  }
+
+  public enum SearchDirection {
+    FIRST, LAST,
+    FORWARD, BACKWARD,
+    UP, DOWN,
+    LEFT, RIGHT,
+  }
+
+  public final boolean moveFocus (SearchDirection direction) {
+    AccessibilityNodeInfo node = getCursorNode();
+    if (node == null) return false;
+
+    try {
+      ScreenElement element = getScreenElement(node);
+      if (element == null) return false;
+
+      switch (direction) {
+        case FIRST: {
+          ScreenElement first = screenElements.getFirstElement();
+          if (element == first) return false;
+          return moveFocus(first, forwardElementGetter, true);
+        }
+
+        case LAST: {
+          ScreenElement last = screenElements.getLastElement();
+          if (element == last) return false;
+          return moveFocus(last, backwardElementGetter, true);
+        }
+
+        case FORWARD:
+          return moveFocus(element, forwardElementGetter, false);
+
+        case BACKWARD:
+          return moveFocus(element, backwardElementGetter, false);
+
+        case UP:
+          return moveFocus(element, upwardElementFinder, false);
+
+        case DOWN:
+          return moveFocus(element, downwardElementFinder, false);
+
+        case LEFT:
+          return moveFocus(element, leftwardElementFinder, false);
+
+        case RIGHT:
+          return moveFocus(element, rightwardElementFinder, false);
+
+        default:
+          return false;
+      }
+    } finally {
+      node.recycle();
+      node = null;
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenDriver.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenDriver.java
new file mode 100644
index 0000000..f245849
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenDriver.java
@@ -0,0 +1,579 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.app.Notification;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.a11y.brltty.core.*;
+
+public abstract class ScreenDriver {
+  private final static String LOG_TAG = ScreenDriver.class.getName();
+
+  private ScreenDriver () {
+  }
+
+  private static ScreenLogger screenLogger = null;
+
+  private static ScreenLogger getScreenLogger () {
+    if (screenLogger == null) screenLogger = new ScreenLogger();
+    return screenLogger;
+  }
+
+  private static void log (AccessibilityNodeInfo node) {
+    try {
+      getScreenLogger().log(node, false);
+    } catch (IOException exception) {
+      Log.d(LOG_TAG, ("node log failure: " + exception.getMessage()));
+    }
+  }
+
+  private static String toText (Collection<CharSequence> lines) {
+    if (lines == null) return null;
+
+    StringBuilder text = new StringBuilder();
+    boolean first = true;
+
+    for (CharSequence line : lines) {
+      if (line == null) continue;
+
+      if (first) {
+        first = false;
+      } else {
+        text.append('\n');
+      }
+
+      text.append(line);
+    }
+
+    if (text.length() == 0) return null;
+    return text.toString();
+  }
+
+  private static String toText (Notification notification) {
+    if (notification == null) return null;
+
+    Collection<CharSequence> lines = new ArrayList<CharSequence>();
+    CharSequence tickerText = notification.tickerText;
+
+    if (APITests.haveKitkat) {
+      Bundle extras = notification.extras;
+
+      if (extras != null) {
+        {
+          CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
+          if (!TextUtils.isEmpty(title)) lines.add(title);
+        }
+
+        {
+          CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
+
+          if (!TextUtils.isEmpty(text)) {
+            lines.add(text);
+            tickerText = null;
+          }
+        }
+      }
+    }
+
+    if (!TextUtils.isEmpty(tickerText)) lines.add(tickerText);
+    if (lines.isEmpty()) return null;
+    return toText(lines);
+  }
+
+  private static String toText (AccessibilityEvent event) {
+    return toText(event.getText());
+  }
+
+  private static void showEventText (BrailleMessage message, AccessibilityEvent event) {
+    message.show(toText(event));
+  }
+
+  private static void showNotification (AccessibilityEvent event) {
+    BrailleMessage message;
+    String text = null;
+    Notification notification = (Notification)event.getParcelableData();
+
+    if (notification != null) {
+      if (!ApplicationSettings.SHOW_NOTIFICATIONS) return;
+      if (!APITests.haveJellyBean) return;
+      if (notification.priority < Notification.PRIORITY_DEFAULT) return;
+
+      message = BrailleMessage.NOTIFICATION;
+      text = toText(notification);
+    } else {
+      if (!ApplicationSettings.SHOW_ALERTS) return;
+      message = BrailleMessage.ALERT;
+    }
+
+    if (text == null) text = toText(event);
+    message.show(text);
+  }
+
+  private static void logUnhandledEvent (AccessibilityEvent event, AccessibilityNodeInfo node) {
+    if (ApplicationSettings.LOG_UNHANDLED_EVENTS) {
+      StringBuilder log = new StringBuilder();
+
+      log.append("unhandled accessibility event: ");
+      log.append(event.toString());
+
+      if (node != null) {
+        log.append("; Source: ");
+        log.append(ScreenLogger.toString(node));
+      }
+
+      Log.w(LOG_TAG, log.toString());
+    }
+  }
+
+  private static boolean setFocus (AccessibilityNodeInfo node) {
+    if (APITests.haveJellyBean) {
+      if (node.isAccessibilityFocused()) return true;
+      node = AccessibilityNodeInfo.obtain(node);
+
+      {
+        AccessibilityNodeInfo subnode = node.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+
+        if (subnode != null) {
+          node.recycle();
+          node = subnode;
+          subnode = null;
+        }
+      }
+
+      {
+        AccessibilityNodeInfo subnode = ScreenUtilities.findTextNode(node);
+        if (subnode == null) subnode = ScreenUtilities.findDescribedNode(node);
+
+        if (subnode != null) {
+          node.recycle();
+          node = subnode;
+          subnode = null;
+        }
+      }
+
+      try {
+        if (node.isAccessibilityFocused()) return true;
+
+        if (node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)) {
+          return true;
+        }
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    return false;
+  }
+
+  private static class FocusSetter extends DeferredTask {
+    private AccessibilityNodeInfo focusNode = null;
+
+    public FocusSetter () {
+      super(ApplicationParameters.FOCUS_SETTER_DELAY);
+    }
+
+    @Override
+    protected final boolean isStartable () {
+      return focusNode != null;
+    }
+
+    @Override
+    protected final void releaseResources () {
+      if (focusNode != null) {
+        focusNode.recycle();
+        focusNode = null;
+      }
+    }
+
+    public final void start (AccessibilityNodeInfo node) {
+      synchronized (this) {
+        releaseResources();
+        if (node != null) focusNode = AccessibilityNodeInfo.obtain(node);
+        restart();
+      }
+    }
+
+    @Override
+    public void runTask () {
+      if (focusNode != null) setFocus(focusNode);
+    }
+  }
+
+  private final static FocusSetter focusSetter = new FocusSetter();
+
+  private native static void screenUpdated ();
+  private final static Object NODE_LOCK = new Object();
+  private volatile static AccessibilityNodeInfo currentNode = null;
+
+  public static void setCurrentNode (AccessibilityNodeInfo newNode) {
+    if (newNode != null) {
+      {
+        AccessibilityNodeInfo oldNode;
+
+        synchronized (NODE_LOCK) {
+          oldNode = currentNode;
+          currentNode = AccessibilityNodeInfo.obtain(newNode);
+          newNode = null;
+        }
+
+        if (oldNode != null) {
+          oldNode.recycle();
+          oldNode = null;
+        }
+      }
+
+      CoreWrapper.runOnCoreThread(
+        new Runnable() {
+          @Override
+          public void run () {
+            screenUpdated();
+          }
+        }
+      );
+    }
+  }
+
+  static {
+    AccessibilityNodeInfo root = ScreenUtilities.getRootNode();
+
+    if (root != null) {
+      try {
+        setFocus(root);
+        setCurrentNode(root);
+      } finally {
+        root.recycle();
+        root = null;
+      }
+    }
+  }
+
+  private static ScreenWindow lockedScreenWindow = null;
+
+  public static void lockScreenWindow (ScreenWindow window) {
+    lockedScreenWindow = window;
+  }
+
+  public static void unlockScreenWindow () {
+    lockScreenWindow(null);
+  }
+
+  private static ScreenWindow currentScreenWindow =
+    ScreenWindow.getScreenWindow(0)
+                .setRenderedScreen(new RenderedScreen(null))
+                ;
+
+  public static ScreenWindow getCurrentScreenWindow () {
+    if (lockedScreenWindow != null) return lockedScreenWindow;
+    return currentScreenWindow;
+  }
+
+  public static RenderedScreen getCurrentRenderedScreen () {
+    return getCurrentScreenWindow().getRenderedScreen();
+  }
+
+  public static void onAccessibilityEvent (AccessibilityEvent event) {
+    focusSetter.restart();
+
+    final boolean log = ApplicationSettings.LOG_ACCESSIBILITY_EVENTS;
+    if (log) Log.d(LOG_TAG, ("accessibility event: " + event.toString()));
+
+    int eventType = event.getEventType();
+    AccessibilityNodeInfo node = event.getSource();
+
+    if (node == null) {
+      //noinspection SwitchIntDef
+      switch (eventType) {
+        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
+          showNotification(event);
+          break;
+
+        case AccessibilityEvent.TYPE_ANNOUNCEMENT:
+          if (ApplicationSettings.SHOW_ANNOUNCEMENTS) showEventText(BrailleMessage.ANNOUNCEMENT, event);
+          break;
+
+        case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+          setCurrentNode(ScreenUtilities.getRootNode());
+          break;
+
+        default:
+          logUnhandledEvent(event, null);
+        case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
+        case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
+        case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
+        case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
+        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
+        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
+        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+        case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+        case AccessibilityEvent.TYPE_VIEW_CLICKED:
+          break;
+      }
+    } else {
+      try {
+        if (log) log(node);
+
+        if (APITests.haveLollipop) {
+          AccessibilityWindowInfo window = node.getWindow();
+
+          if (window != null) {
+            try {
+              boolean accept =
+                (lockedScreenWindow != null)?
+                (window.getId() == lockedScreenWindow.getWindowIdentifier()):
+                window.isActive();
+
+              if (!accept) return;
+            } finally {
+              window.recycle();
+              window = null;
+            }
+          }
+        }
+
+        //noinspection SwitchIntDef
+        switch (eventType) {
+          case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+            focusSetter.start(node);
+            break;
+
+          case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+            break;
+
+          case AccessibilityEvent.TYPE_VIEW_SCROLLED:
+            break;
+
+          case AccessibilityEvent.TYPE_VIEW_SELECTED:
+            break;
+
+          case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+            break;
+
+          case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+            focusSetter.stop();
+            break;
+
+          case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: {
+            if (!APITests.haveJellyBeanMR2) {
+              TextField field = TextField.get(node, true);
+              field.setCursor(event.getFromIndex() + event.getAddedCount());
+            }
+
+            break;
+          }
+
+          case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+            if (!APITests.haveJellyBeanMR2) {
+              TextField field = TextField.get(node, true);
+              field.setSelection(event.getFromIndex(), event.getToIndex());
+            }
+
+            break;
+          }
+
+          default:
+            logUnhandledEvent(event, node);
+          case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+          case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
+          case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+          case AccessibilityEvent.TYPE_VIEW_CLICKED:
+          case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
+          case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
+            return;
+        }
+
+        if (false && node.isPassword()) {
+          TextField field = TextField.get(node, true);
+          field.setAccessibilityText(toText(event));
+        }
+
+        setCurrentNode(node);
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+  }
+
+  private native static void exportScreenProperties (
+    int number, int columns, int rows,
+    int locLeft, int locTop, int locRight, int locBottom,
+    int selLeft, int selTop, int selRight, int selBottom
+  );
+
+  private static void exportScreenProperties () {
+    ScreenWindow window = getCurrentScreenWindow();
+    RenderedScreen screen = window.getRenderedScreen();
+    AccessibilityNodeInfo node = screen.getCursorNode();
+
+    int locationLeft = 0;
+    int locationTop = 0;
+    int locationRight = 0;
+    int locationBottom = 0;
+
+    int selectionLeft = 0;
+    int selectionTop = 0;
+    int selectionRight = 0;
+    int selectionBottom = 0;
+
+    if (node != null) {
+      try {
+        ScreenElement element = screen.findScreenElement(node);
+
+        if (element != null) {
+          Rect location = element.getBrailleLocation();
+          locationLeft = location.left;
+          locationTop = location.top;
+          locationRight = location.right;
+          locationBottom = location.bottom;
+
+          if (ScreenUtilities.isEditable(node)) {
+            int start = -1;
+            int end = -1;
+
+            if (APITests.haveJellyBeanMR2) {
+              start = node.getTextSelectionStart();
+              end = node.getTextSelectionEnd();
+            } else {
+              TextField field = TextField.get(node);
+
+              if (field != null) {
+                synchronized (field) {
+                  start = field.getSelectionStart();
+                  end = field.getSelectionEnd();
+                }
+              }
+            }
+
+            if ((0 <= start) && (start <= end)) {
+              Point topLeft = element.getBrailleCoordinates(start);
+
+              if (topLeft != null) {
+                if (start == end) {
+                  selectionLeft = selectionRight = topLeft.x;
+                  selectionTop = selectionBottom = topLeft.y;
+                } else {
+                  Point bottomRight = element.getBrailleCoordinates(end-1);
+
+                  if (bottomRight != null) {
+                    selectionLeft = topLeft.x;
+                    selectionTop = topLeft.y;
+                    selectionRight = bottomRight.x + 1;
+                    selectionBottom = bottomRight.y + 1;
+                  }
+                }
+              }
+            }
+          }
+        }
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    exportScreenProperties(
+      window.getWindowIdentifier(),
+      screen.getScreenWidth(), screen.getScreenHeight(),
+      locationLeft, locationTop, locationRight, locationBottom,
+      selectionLeft, selectionTop, selectionRight, selectionBottom
+    );
+  }
+
+  static {
+    exportScreenProperties();
+  }
+
+  private static void refreshScreen (AccessibilityNodeInfo node) {
+    currentScreenWindow = ScreenWindow.setRenderedScreen(node);
+    exportScreenProperties();
+  }
+
+  public static char refreshScreen () {
+    if (ApplicationSettings.RELEASE_BRAILLE_DEVICE) return 'r';
+    if (LockUtilities.isLocked()) return 'l';
+
+    AccessibilityNodeInfo node;
+
+    synchronized (NODE_LOCK) {
+      if ((node = currentNode) != null) {
+        currentNode = null;
+      }
+    }
+
+    if (node != null) {
+      try {
+        refreshScreen(node);
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+
+    return 0;
+  }
+
+  public static char[] getRowText (int row, int column) {
+    RenderedScreen screen = getCurrentRenderedScreen();
+    String text = (row < screen.getScreenHeight())? screen.getScreenRow(row): "";
+    int length = text.length();
+
+    if (column > length) column = length;
+    int count = length - column;
+
+    char[] characters = new char[count];
+    text.getChars(column, length, characters, 0);
+    return characters;
+  }
+
+  public static boolean routeCursor (int column, int row) {
+    if (row == -1) return false;
+    return getCurrentRenderedScreen().performAction(column, row);
+  }
+
+  public static void reportEvent (char event) {
+    switch (event) {
+      case 'b': // braille device online
+        ApplicationSettings.BRAILLE_DEVICE_ONLINE = true;
+        BrailleNotification.updateState();
+        break;
+
+      case 'B': // braille device offline
+        ApplicationSettings.BRAILLE_DEVICE_ONLINE = false;
+        BrailleNotification.updateState();
+        break;
+
+      case 'k': // braille key event
+        LockUtilities.resetTimer();
+        break;
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElement.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElement.java
new file mode 100644
index 0000000..5fdc88a
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElement.java
@@ -0,0 +1,267 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScreenElement {
+  private final String elementText;
+
+  public ScreenElement (String text) {
+    elementText = text;
+  }
+
+  public ScreenElement (int text) {
+    this(ApplicationUtilities.getResourceString(text));
+  }
+
+  public final String getElementText () {
+    return elementText;
+  }
+
+  private ScreenElement backwardElement = null;
+  private ScreenElement forwardElement = null;
+
+  public final ScreenElement getBackwardElement () {
+    return backwardElement;
+  }
+
+  public final ScreenElement setBackwardElement (ScreenElement element) {
+    backwardElement = element;
+    return this;
+  }
+
+  public final ScreenElement getForwardElement () {
+    return forwardElement;
+  }
+
+  public final ScreenElement setForwardElement (ScreenElement element) {
+    forwardElement = element;
+    return this;
+  }
+
+  // Since null needs to be a valid cached value, use a self reference
+  // to mean that the correct value hasn't been determined yet.
+  private ScreenElement upwardElement = this;
+  private ScreenElement downwardElement = this;
+  private ScreenElement leftwardElement = this;
+  private ScreenElement rightwardElement = this;
+
+  public final ScreenElement getUpwardElement () {
+    return upwardElement;
+  }
+
+  public final ScreenElement setUpwardElement (ScreenElement element) {
+    upwardElement = element;
+    return this;
+  }
+
+  public final ScreenElement getDownwardElement () {
+    return downwardElement;
+  }
+
+  public final ScreenElement setDownwardElement (ScreenElement element) {
+    downwardElement = element;
+    return this;
+  }
+
+  public final ScreenElement getLeftwardElement () {
+    return leftwardElement;
+  }
+
+  public final ScreenElement setLeftwardElement (ScreenElement element) {
+    leftwardElement = element;
+    return this;
+  }
+
+  public final ScreenElement getRightwardElement () {
+    return rightwardElement;
+  }
+
+  public final ScreenElement setRightwardElement (ScreenElement element) {
+    rightwardElement = element;
+    return this;
+  }
+
+  public AccessibilityNodeInfo getAccessibilityNode () {
+    return null;
+  }
+
+  public boolean isEditable () {
+    return false;
+  }
+
+  public boolean bringCursor () {
+    return false;
+  }
+
+  public boolean onBringCursor () {
+    return false;
+  }
+
+  public boolean onClick () {
+    return false;
+  }
+
+  public boolean onLongClick () {
+    return false;
+  }
+
+  public boolean onScrollBackward () {
+    return false;
+  }
+
+  public boolean onScrollForward () {
+    return false;
+  }
+
+  public boolean onContextClick () {
+    return false;
+  }
+
+  public boolean onAccessibilityActions () {
+    return false;
+  }
+
+  public boolean onDescribeElement () {
+    return false;
+  }
+
+  public boolean performAction (int column, int row) {
+    switch (column) {
+      case  0: return onBringCursor();
+      case  1: return onClick();
+      case  2: return onLongClick();
+      case  3: return onScrollBackward();
+      case  4: return onScrollForward();
+      case  5: return onContextClick();
+      case  6: return onAccessibilityActions();
+      case 15: return onDescribeElement();
+      default: return false;
+    }
+  }
+
+  private Rect visualLocation = null;
+
+  public final Rect getVisualLocation () {
+    return visualLocation;
+  }
+
+  public final ScreenElement setVisualLocation (Rect location) {
+    visualLocation = location;
+    return this;
+  }
+
+  private Rect brailleLocation = null;
+
+  public final Rect getBrailleLocation () {
+    return brailleLocation;
+  }
+
+  public final ScreenElement setBrailleLocation (Rect location) {
+    brailleLocation = location;
+    return this;
+  }
+
+  public final ScreenElement setBrailleLocation (int left, int top, int right, int bottom) {
+    return setBrailleLocation(new Rect(left, top, right, bottom));
+  }
+
+  private String[] brailleText = null;
+  private int[] lineOffsets = null;
+
+  protected String[] makeBrailleText (String text) {
+    if (text == null) return null;
+
+    List<String> lines = new ArrayList<String>();
+    int start = 0;
+
+    while (true) {
+      int end = text.indexOf('\n', start);
+      if (end == -1) break;
+
+      lines.add(text.substring(start, end));
+      start = end + 1;
+    }
+
+    if (start > 0) text = text.substring(start);
+    lines.add(text);
+
+    return lines.toArray(new String[lines.size()]);
+  }
+
+  public final String[] getBrailleText () {
+    synchronized (this) {
+      if (brailleText == null) {
+        brailleText = makeBrailleText(elementText);
+      }
+    }
+
+    return brailleText;
+  }
+
+  private final int[] makeLineOffsets (String[] lines) {
+    int[] offsets = new int[lines.length];
+
+    int index = 0;
+    int offset = 0;
+
+    for (String line : lines) {
+      offsets[index] = offset;
+      offset += lines[index].length();
+      index += 1;
+    }
+
+    return offsets;
+  }
+
+  public final int getTextOffset (int column, int row) {
+    String[] lines = getBrailleText();
+
+    synchronized (this) {
+      if (lineOffsets == null) {
+        lineOffsets = makeLineOffsets(lines);
+      }
+    }
+
+    return lineOffsets[row] + Math.min(column, (lines[row].length() - 1));
+  }
+
+  public final Point getBrailleCoordinates (int offset) {
+    String[] lines = getBrailleText();
+
+    if (lines != null) {
+      int row = 0;
+
+      for (String line : lines) {
+        int length = line.length();
+        if (offset < length) return new Point(offset, row);
+
+        row += 1;
+        offset -= length;
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElementList.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElementList.java
new file mode 100644
index 0000000..0a016ac
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenElementList.java
@@ -0,0 +1,253 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ScreenElementList extends ArrayList<ScreenElement> {
+  private final static String LOG_TAG = ScreenElementList.class.getName();
+
+  public ScreenElementList () {
+    super();
+  }
+
+  public void log () {
+    Log.d(LOG_TAG, "begin screen element list");
+
+    for (ScreenElement element : this) {
+      Log.d(LOG_TAG, "screen element: " + element.getElementText());
+    }
+
+    Log.d(LOG_TAG, "end screen element list");
+  }
+
+  private int atTopCount = 0;
+
+  public final void addAtTop (VirtualScreenElement element) {
+    add(atTopCount++, element);
+  }
+
+  public final void addAtTop (int text, int action, int key) {
+    addAtTop(new VirtualScreenElement(text, action, key));
+  }
+
+  public final void addAtTop (int text, int action) {
+    addAtTop(new VirtualScreenElement(text, action));
+  }
+
+  public final void addAtBottom (int text, int action, int key) {
+    add(new VirtualScreenElement(text, action, key));
+  }
+
+  public final void addAtBottom (int text, int action) {
+    add(new VirtualScreenElement(text, action));
+  }
+
+  private final Map<AccessibilityNodeInfo, ScreenElement> nodeToScreenElement =
+        new HashMap<AccessibilityNodeInfo, ScreenElement>();
+
+  public final void add (String text, AccessibilityNodeInfo node) {
+    node = AccessibilityNodeInfo.obtain(node);
+    ScreenElement element = new RealScreenElement(text, node);
+    add(element);
+    nodeToScreenElement.put(node, element);
+  }
+
+  public final ScreenElement get (AccessibilityNodeInfo node) {
+    return nodeToScreenElement.get(node);
+  }
+
+  private final void addByContainer (NodeComparator nodeComparator, AccessibilityNodeInfo... nodes) {
+    {
+      final int count = nodes.length;
+      if (count == 0) return;
+      if (count > 1) Arrays.sort(nodes, nodeComparator);
+    }
+
+    for (AccessibilityNodeInfo node : nodes) {
+      if (node == null) break;
+
+      try {
+        {
+          ScreenElement element = get(node);
+          if (element != null) add(element);
+        }
+
+        {
+          int count = node.getChildCount();
+
+          if (count > 0) {
+            AccessibilityNodeInfo[] children = new AccessibilityNodeInfo[count];
+
+            for (int index=0; index<count; index+=1) {
+              children[index] = node.getChild(index);
+            }
+
+            addByContainer(nodeComparator, children);
+          }
+        }
+      } finally {
+        node.recycle();
+        node = null;
+      }
+    }
+  }
+
+  private final void sortByVisualLocation () {
+    if (size() < 2) return;
+    AccessibilityNodeInfo node = get(0).getAccessibilityNode();
+
+    try {
+      AccessibilityNodeInfo root = ScreenUtilities.findRootNode(node);
+
+      if (root != null) {
+        NodeComparator comparator = new NodeComparator() {
+          private final Map<AccessibilityNodeInfo, Rect> nodeLocations =
+                new HashMap<AccessibilityNodeInfo, Rect>();
+
+          private final Rect getLocation (AccessibilityNodeInfo node) {
+            synchronized (nodeLocations) {
+              Rect location = nodeLocations.get(node);
+              if (location != null) return location;
+
+              location = new Rect();
+              node.getBoundsInScreen(location);
+
+              nodeLocations.put(node, location);
+              return location;
+            }
+          }
+
+          @Override
+          public int compare (AccessibilityNodeInfo node1, AccessibilityNodeInfo node2) {
+            if (node1 == null) {
+              return (node2 == null)? 0: 1;
+            } else if (node2 == null) {
+              return -1;
+            }
+
+            Rect location1 = getLocation(node1);
+            int top1 = location1.top;
+            int bottom1 = location1.bottom;
+            int left1 = location1.left;
+            int right1 = location1.right;
+
+            Rect location2 = getLocation(node2);
+            int top2 = location2.top;
+            int bottom2 = location2.bottom;
+            int left2 = location2.left;
+            int right2 = location2.right;
+
+            {
+              int middle1 = (top1 + bottom1) / 2;
+              int middle2 = (top2 + bottom2) / 2;
+              boolean sameRow = ((middle1 >= top2) && (middle1 < bottom2)) ||
+                                ((middle2 >= top1) && (middle2 < bottom1));
+
+              if (sameRow) {
+                if ((left1 < left2) && (right1 < right2)) return -1;
+                if ((left1 > left2) && (right1 > right2)) return 1;
+              }
+            }
+
+            if (top1 < top2) return -1;
+            if (top1 > top2) return 1;
+
+            if (left1 < left2) return -1;
+            if (left1 > left2) return 1;
+
+            if (right1 > right2) return -1;
+            if (right1 < right2) return 1;
+
+            if (bottom1 > bottom2) return -1;
+            if (bottom1 < bottom2) return 1;
+
+            return 0;
+          }
+        };
+
+        clear();
+        addByContainer(comparator, root);
+      }
+    } finally {
+      node.recycle();
+      node = null;
+    }
+  }
+
+  private ScreenElement firstElement = null;
+  private ScreenElement lastElement = null;
+
+  public final ScreenElement getFirstElement () {
+    return firstElement;
+  }
+
+  public final ScreenElement getLastElement () {
+    return lastElement;
+  }
+
+  private final void makeTraversalLinks () {
+    int count = size();
+    if (count == 0) return;
+
+    firstElement = get(0);
+    lastElement = get(count-1);
+    ScreenElement previousElement = lastElement;
+
+    for (ScreenElement nextElement : this) {
+      nextElement.setBackwardElement(previousElement);
+      previousElement.setForwardElement(nextElement);
+      previousElement = nextElement;
+    }
+  }
+
+  public final void finish () {
+    sortByVisualLocation();
+    makeTraversalLinks();
+  }
+
+  public final ScreenElement findByBrailleLocation (int column, int row) {
+    ScreenElement bestElement = null;
+    int bestLeft = -1;
+
+    for (ScreenElement element : this) {
+      Rect location = element.getBrailleLocation();
+      if (location == null) continue;
+
+      if (row < location.top) continue;
+      if (row > location.bottom) continue;
+
+      if (column < location.left) continue;
+      if (column <= location.right) return element;
+
+      if (location.left > bestLeft) {
+        bestLeft = location.left;
+        bestElement = element;
+      }
+    }
+
+    return bestElement;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenLogger.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenLogger.java
new file mode 100644
index 0000000..6f00b2f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenLogger.java
@@ -0,0 +1,651 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Spanned;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ScreenLogger extends Logger {
+  private final static String LOG_TAG = ScreenLogger.class.getName();
+
+  @Override
+  protected String getLogTag () {
+    return LOG_TAG;
+  }
+
+  public ScreenLogger () {
+    super();
+  }
+
+  private static String getText (AccessibilityNodeInfo node) {
+    String text = ScreenUtilities.getText(node);
+    if (text != null) return text;
+    return ScreenUtilities.getDescription(node);
+  }
+
+  private static void addText (StringBuilder sb, CharSequence text, String label, char begin, char end) {
+    if (text == null) return;
+    if (text.length() == 0) return;
+
+    if (sb.length() > 0) sb.append(' ');
+    if (label != null) sb.append(label).append(':');
+    sb.append(begin).append(text).append(end);
+  }
+
+  private static void addText (StringBuilder sb, CharSequence text, String label) {
+    addText(sb, text, label, '"', '"');
+  }
+
+  private final static void add (StringBuilder sb, String value) {
+    if (value != null) {
+      if (sb.length() > 0) sb.append(' ');
+      sb.append(value);
+    }
+  }
+
+  private final static void add (StringBuilder sb, boolean condition, String trueValue, String falseValue) {
+    add(sb, (condition? trueValue: falseValue));
+  }
+
+  private final static void add (StringBuilder sb, boolean condition, String trueValue) {
+    add(sb, condition, trueValue, null);
+  }
+
+  private final static void add (StringBuilder sb, String label, CharSequence value) {
+    if (value != null) add(sb, String.format("%s=%s", label, value));
+  }
+
+  private final static void add (StringBuilder sb, String label, int value) {
+    add(sb, label, Integer.toString(value));
+  }
+
+  private final static void add (StringBuilder sb, String label, int value, Map<Integer, String> names) {
+    String name = names.get(value);
+
+    if (name != null) {
+      add(sb, label, name);
+    } else {
+      add(sb, label, value);
+    }
+  }
+
+  private final void log (String label, String data) throws IOException {
+    log((label + ": " + data));
+  }
+
+  private static class ActionLabelMap extends LinkedHashMap<Integer, String> {
+    public final void put (String name, int action) {
+      put(action, name);
+    }
+
+    public final void put (String name, AccessibilityNodeInfo.AccessibilityAction action) {
+      if (APITests.haveLollipop) {
+        put(name, action.getId());
+      }
+    }
+  }
+
+  private final static ActionLabelMap actionLabels = new ActionLabelMap()
+  {
+    {
+      put("clk", AccessibilityNodeInfo.ACTION_CLICK);
+      put("lck", AccessibilityNodeInfo.ACTION_LONG_CLICK);
+      put("scf", AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+      put("scb", AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+      put("mgp", AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+      put("mgn", AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+      put("mep", AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+      put("men", AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+      put("sls", AccessibilityNodeInfo.ACTION_SELECT);
+      put("slc", AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+      put("ifs", AccessibilityNodeInfo.ACTION_FOCUS);
+      put("ifc", AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+      put("afs", AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+      put("afc", AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+
+      if (APITests.haveJellyBeanMR2) {
+        put("sel", AccessibilityNodeInfo.ACTION_SET_SELECTION);
+        put("cbc", AccessibilityNodeInfo.ACTION_COPY);
+        put("cbx", AccessibilityNodeInfo.ACTION_CUT);
+        put("cbp", AccessibilityNodeInfo.ACTION_PASTE);
+      }
+
+      if (APITests.haveKitkat) {
+        put("dsms", AccessibilityNodeInfo.ACTION_DISMISS);
+        put("clps", AccessibilityNodeInfo.ACTION_COLLAPSE);
+        put("xpnd", AccessibilityNodeInfo.ACTION_EXPAND);
+      }
+
+      if (APITests.haveLollipop) {
+        put("txs", AccessibilityNodeInfo.ACTION_SET_TEXT);
+      }
+
+      if (APITests.haveMarshmallow) {
+        put("cck", AccessibilityNodeInfo.AccessibilityAction.ACTION_CONTEXT_CLICK);
+      }
+    }
+  };
+
+  public static String toString (AccessibilityNodeInfo node) {
+    StringBuilder sb = new StringBuilder();
+    add(sb, ScreenUtilities.getClassName(node));
+
+    addText(sb, shrinkText(ScreenUtilities.getText(node)), null);
+    addText(sb, shrinkText(ScreenUtilities.getDescription(node)), null, '(', ')');
+
+    if (APITests.haveOreo) {
+      addText(sb, node.getHintText(), "hint");
+    }
+
+    if (APITests.havePie) {
+      addText(sb, node.getTooltipText(), "tip");
+    }
+
+    if (APITests.havePie) {
+      addText(sb, node.getPaneTitle(), "pane");
+    }
+
+    if (APITests.haveLollipop) {
+      addText(sb, node.getError(), "error");
+    }
+
+    {
+      AccessibilityNodeInfo parent = node.getParent();
+
+      if (parent != null) {
+        parent.recycle();
+        parent = null;
+      } else {
+        add(sb, "root");
+      }
+    }
+
+    {
+      int count = node.getChildCount();
+      if (count > 0) add(sb, "cld", count);
+    }
+
+    if (APITests.haveJellyBean) {
+      add(sb, !node.isVisibleToUser(), "inv");
+    }
+
+    add(sb, !node.isEnabled(), "dsb");
+    add(sb, node.isSelected(), "sld");
+    add(sb, node.isScrollable(), "scr");
+    add(sb, node.isFocusable(), "ifb");
+    add(sb, node.isFocused(), "ifd");
+
+    if (APITests.haveJellyBean) {
+      add(sb, node.isAccessibilityFocused(), "afd");
+    }
+
+    add(sb, node.isClickable(), "clb");
+    add(sb, node.isLongClickable(), "lcb");
+
+    if (APITests.haveMarshmallow) {
+      add(sb, node.isContextClickable(), "ccb");
+    }
+       
+    add(sb, node.isCheckable(), "ckb");
+    add(sb, node.isChecked(), "ckd");
+    add(sb, node.isPassword(), "pwd");
+
+    if (APITests.haveJellyBeanMR2) {
+      add(sb, ScreenUtilities.isEditable(node), "edt");
+
+      {
+        int start = node.getTextSelectionStart();
+        int end = node.getTextSelectionEnd();
+
+        if (!((start == -1) && (end == -1))) {
+          add(sb, "sel");
+          sb.append('(');
+          sb.append(start);
+
+          if (end != start) {
+            sb.append("..");
+            sb.append(end);
+          }
+
+          sb.append(')');
+        }
+      }
+    }
+
+    if (APITests.haveKitkat) {
+      {
+        AccessibilityNodeInfo.RangeInfo range = node.getRangeInfo();
+
+        if (range != null) {
+          add(sb, "rng");
+          String format = ScreenUtilities.getRangeValueFormat(range);
+
+          sb.append('(');
+          sb.append(String.format(format, range.getMin()));
+          sb.append("..");
+          sb.append(String.format(format, range.getMax()));
+          sb.append('@');
+          sb.append(String.format(format, range.getCurrent()));
+          sb.append(')');
+        }
+      }
+
+      {
+        AccessibilityNodeInfo.CollectionInfo collection = node.getCollectionInfo();
+
+        if (collection != null) {
+          add(sb, "col");
+          sb.append('(');
+
+          sb.append(collection.getColumnCount());
+          sb.append('x');
+          sb.append(collection.getRowCount());
+
+          sb.append(',');
+          sb.append(collection.isHierarchical()? "tree": "flat");
+
+          {
+            String mode = ScreenUtilities.getSelectionModeLabel(collection);
+
+            if (mode != null) {
+              sb.append(',');
+              sb.append(mode);
+            }
+          }
+
+          sb.append(')');
+        }
+      }
+
+      {
+        AccessibilityNodeInfo.CollectionItemInfo item = node.getCollectionItemInfo();
+
+        if (item != null) {
+          add(sb, "itm");
+          sb.append('(');
+
+          sb.append(item.getColumnSpan());
+          sb.append('x');
+          sb.append(item.getRowSpan());
+          sb.append('+');
+          sb.append(item.getColumnIndex());
+          sb.append('+');
+          sb.append(item.getRowIndex());
+
+          if (item.isHeading()) {
+            sb.append(',');
+            sb.append("hdg");
+          }
+
+          if (APITests.haveLollipop) {
+            if (item.isSelected()) {
+              sb.append(',');
+              sb.append("sel");
+            }
+          }
+
+          sb.append(')');
+        }
+      }
+    }
+
+    if (APITests.haveLollipop) {
+      for (AccessibilityNodeInfo.AccessibilityAction action : node.getActionList()) {
+        String label = actionLabels.get(action.getId());
+        if (label != null) add(sb, label);
+      }
+    } else {
+      int actions = node.getActions();
+
+      for (Integer action : actionLabels.keySet()) {
+        if ((actions & action) != 0) {
+          add(sb, actionLabels.get(action));
+        }
+      }
+    }
+
+    if (APITests.haveJellyBeanMR1) {
+      AccessibilityNodeInfo subnode = node.getLabelFor();
+
+      if (subnode != null) {
+        try {
+          add(sb, "lbf", getText(subnode));
+        } finally {
+          subnode.recycle();
+          subnode = null;
+        }
+      }
+    }
+
+    if (APITests.haveJellyBeanMR1) {
+      AccessibilityNodeInfo subnode = node.getLabeledBy();
+
+      if (subnode != null) {
+        try {
+          add(sb, "lbd", getText(subnode));
+        } finally {
+          subnode.recycle();
+          subnode = null;
+        }
+      }
+    }
+
+    {
+      Rect location = new Rect();
+      node.getBoundsInScreen(location);
+      add(sb, location.toShortString());
+    }
+
+    add(sb, "obj", node.getClassName());
+    add(sb, "pkg", node.getPackageName());
+    add(sb, "win", node.getWindowId());
+
+    if (APITests.haveJellyBeanMR2) {
+      add(sb, "vrn", node.getViewIdResourceName());
+    }
+
+    {
+      CharSequence text = node.getText();
+
+      if (text instanceof Spanned) {
+        Spanned spanned = (Spanned)text;
+        Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
+
+        if (spans != null) {
+          boolean first = true;
+
+          for (Object span : spans) {
+            if (first) {
+              first = false;
+              add(sb, "spans:[");
+            } else {
+              sb.append(", ");
+            }
+
+            sb.append(span.getClass().getSimpleName())
+              .append('(')
+              .append(spanned.getSpanStart(span))
+              .append("..")
+              .append(spanned.getSpanEnd(span))
+              .append(')')
+              ;
+          }
+
+          if (!first) sb.append(']');
+        }
+      }
+    }
+
+    if (APITests.haveKitkat) {
+      Bundle extras = node.getExtras();
+
+      if (extras != null) {
+        if (extras.size() > 0) {
+          add(sb, "extras: ");
+          sb.append(extras.toString());
+        }
+      }
+    }
+
+    if (APITests.haveLollipop) {
+      List<AccessibilityNodeInfo.AccessibilityAction> actions = node.getActionList();
+
+      if (actions != null) {
+        boolean first = true;
+
+        for (AccessibilityNodeInfo.AccessibilityAction action : actions) {
+          CharSequence label = action.getLabel();
+          if (label == null) continue;
+          if (label.length() == 0) continue;
+
+          if (first) {
+            first = false;
+            add(sb, "actions:");
+          } else {
+            sb.append(',');
+          }
+
+          sb.append(' ');
+          sb.append(label);
+        }
+      }
+    }
+
+    return sb.toString();
+  }
+
+  private final void log (AccessibilityNodeInfo node, boolean descend, String name) throws IOException {
+    log(name, toString(node));
+
+    if (descend) {
+      int childCount = node.getChildCount();
+
+      for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+        AccessibilityNodeInfo child = node.getChild(childIndex);
+
+        if (child != null) {
+          log(child, true, (name + "." + childIndex));
+          child.recycle();
+          child = null;
+        }
+      }
+    }
+  }
+
+  public final void log (AccessibilityNodeInfo root, boolean descend) throws IOException {
+    if (descend) log("begin node tree");
+
+    try {
+      log(root, descend, "root");
+    } finally {
+      if (descend) log("end node tree");
+    }
+  }
+
+  public final void log (AccessibilityNodeInfo root) throws IOException {
+    log(root, true);
+  }
+
+  private final static Map<Integer, String> windowTypeNames =
+               new HashMap<Integer, String>()
+  {
+    {
+      if (APITests.haveLollipop) {
+        put(AccessibilityWindowInfo.TYPE_SYSTEM, "sys");
+        put(AccessibilityWindowInfo.TYPE_APPLICATION, "app");
+        put(AccessibilityWindowInfo.TYPE_INPUT_METHOD, "ime");
+      }
+
+      if (APITests.haveMarshmallow) {
+        put(AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY, "acc");
+      }
+
+      if (APITests.haveNougat) {
+        put(AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER, "ssd");
+      }
+    }
+  };
+
+  public static String toString (AccessibilityWindowInfo window) {
+    StringBuilder sb = new StringBuilder();
+
+    if (APITests.haveLollipop) {
+      add(sb, "id", window.getId());
+    }
+
+    if (APITests.haveNougat) {
+      addText(sb, window.getTitle(), null);
+    }
+
+    if (APITests.haveLollipop) {
+      AccessibilityWindowInfo parent = window.getParent();
+
+      if (parent != null) {
+        parent.recycle();
+        parent = null;
+      } else {
+        add(sb, "root");
+      }
+    }
+
+    if (APITests.haveLollipop) {
+      int count = window.getChildCount();
+      if (count > 0) add(sb, "cld", count);
+    }
+
+    if (APITests.haveLollipop) {
+      add(sb, "type", window.getType(), windowTypeNames);
+      add(sb, "layer", window.getLayer());
+      add(sb, window.isActive(), "act");
+      add(sb, window.isFocused(), "ifd");
+      add(sb, window.isAccessibilityFocused(), "afd");
+    }
+
+    if (APITests.haveOreo) {
+      add(sb, window.isInPictureInPictureMode(), "pip");
+    }
+
+    if (APITests.haveLollipop) {
+      Rect location = new Rect();
+      window.getBoundsInScreen(location);
+      add(sb, location.toShortString());
+    }
+
+    return sb.toString();
+  }
+
+  private final void log (AccessibilityWindowInfo window, String name, boolean descend, boolean nodes) throws IOException {
+    log(name, toString(window));
+
+    if (nodes) {
+      if (APITests.haveLollipop) {
+        AccessibilityNodeInfo root = window.getRoot();
+
+        if (root != null) {
+          try {
+            log(root);
+          } finally {
+            root.recycle();
+            root = null;
+          }
+        }
+      }
+    }
+
+    if (descend) {
+      if (APITests.haveLollipop) {
+        int childCount = window.getChildCount();
+
+        for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+          AccessibilityWindowInfo child = window.getChild(childIndex);
+
+          if (child != null) {
+            log(child, (name + '.' + childIndex), true, nodes);
+            child.recycle();
+            child = null;
+          }
+        }
+      }
+    }
+  }
+
+  public final void log () throws IOException {
+    log("begin screen log");
+
+    if (APITests.haveLollipop) {
+      int index = 0;
+
+      for (AccessibilityWindowInfo window : ScreenUtilities.getWindows()) {
+        log(window, ("window." + index), true, true);
+        index += 1;
+      }
+    } else if (APITests.haveJellyBean) {
+      AccessibilityNodeInfo root = ScreenUtilities.getRootNode();
+
+      if (root != null) {
+        try {
+          log(root);
+        } finally {
+          root.recycle();
+          root = null;
+        }
+      }
+    }
+
+    log("end screen log");
+  }
+
+  public static void logToFile (File file) {
+    String path = file.getAbsolutePath();
+    Log.d(LOG_TAG, ("logging screen to file: " + path));
+
+    try {
+      OutputStream stream = new FileOutputStream(file);
+
+      try {
+        final Writer writer = new OutputStreamWriter(stream, "UTF-8");
+
+        try {
+          new ScreenLogger() {
+            @Override
+            protected void log (String message) throws IOException {
+              writer.write(message);
+              writer.write('\n');
+            }
+          }.log();
+        } finally {
+          writer.close();
+        }
+      } finally {
+        stream.close();
+      }
+    } catch (FileNotFoundException exception) {
+      Log.w(LOG_TAG, ("file not found: " + path));
+    } catch (IOException exception) {
+      Log.w(LOG_TAG, String.format("log file error: %s: %s", path, exception.getMessage()));
+    }
+  }
+
+  public static void logToFile (String path) {
+    logToFile(new File(path));
+  }
+
+  public static File logToFile () {
+    File directory = BrailleApplication.get().getExternalFilesDir(null);
+    File file = new File(directory, "screen.log");
+
+    logToFile(file);
+    return file;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenUtilities.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenUtilities.java
new file mode 100644
index 0000000..e7e2a78
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenUtilities.java
@@ -0,0 +1,546 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.webkit.WebView;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public abstract class ScreenUtilities {
+  private ScreenUtilities () {
+  }
+
+  public static String getClassPath (AccessibilityNodeInfo node) {
+    CharSequence path = node.getClassName();
+    if (path == null) return null;
+    return path.toString();
+  }
+
+  public static String getClassName (AccessibilityNodeInfo node) {
+    String path = getClassPath(node);
+    if (path == null) return null;
+
+    int index = path.lastIndexOf('.');
+    return path.substring(index+1);
+  }
+
+  public static AccessibilityNodeInfo getRefreshedNode (AccessibilityNodeInfo node) {
+    if (node != null) {
+      if (APITests.haveJellyBeanMR2) {
+        node = AccessibilityNodeInfo.obtain(node);
+
+        if (!node.refresh()) {
+          node.recycle();
+          node = null;
+        }
+
+        return node;
+      }
+
+      {
+        int childCount = node.getChildCount();
+
+        for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+          AccessibilityNodeInfo child = node.getChild(childIndex);
+
+          if (child != null) {
+            AccessibilityNodeInfo parent = child.getParent();
+
+            child.recycle();
+            child = null;
+
+            if (node.equals(parent)) {
+              return parent;
+            }
+
+            if (parent != null) {
+              parent.recycle();
+              parent = null;
+            }
+          }
+        }
+      }
+
+      {
+        AccessibilityNodeInfo parent = node.getParent();
+
+        if (parent != null) {
+          int childCount = parent.getChildCount();
+
+          for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+            AccessibilityNodeInfo child = parent.getChild(childIndex);
+
+            if (node.equals(child)) {
+              parent.recycle();
+              parent = null;
+              return child;
+            }
+
+            if (child != null) {
+              child.recycle();
+              child = null;
+            }
+          }
+
+          parent.recycle();
+          parent = null;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public static boolean isVisible (AccessibilityNodeInfo node) {
+    if (APITests.haveJellyBean) {
+      return node.isVisibleToUser();
+    }
+
+    Rect location = new Rect();
+    node.getBoundsInScreen(location);
+    return ScreenDriver.getCurrentScreenWindow().contains(location);
+  }
+
+  public static boolean isSubclassOf (AccessibilityNodeInfo node, Class type) {
+    String path = getClassPath(node);
+    if (path == null) return false;
+    return LanguageUtilities.canAssign(type, path);
+  }
+
+  public static boolean isCheckBox (AccessibilityNodeInfo node) {
+    return isSubclassOf(node, android.widget.CheckBox.class);
+  }
+
+  public static boolean isSwitch (AccessibilityNodeInfo node) {
+    return isSubclassOf(node, android.widget.Switch.class);
+  }
+
+  public static int getSelectionMode (AccessibilityNodeInfo node) {
+    if (APITests.haveLollipop) {
+      if (node.getCollectionItemInfo() != null) {
+        AccessibilityNodeInfo parent = node.getParent();
+
+        if (parent != null) {
+          try {
+            AccessibilityNodeInfo.CollectionInfo collection = parent.getCollectionInfo();
+
+            if (collection != null) {
+              return collection.getSelectionMode();
+            }
+          } finally {
+            parent.recycle();
+            parent = null;
+          }
+        }
+      }
+    }
+
+    return node.isCheckable()?
+           //noinspection InlinedApi
+           AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_MULTIPLE:
+           //noinspection InlinedApi
+           AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE;
+  }
+
+  public static boolean hasSelectionMode (AccessibilityNodeInfo item, int mode) {
+    return getSelectionMode(item) == mode;
+  }
+
+  public static boolean isRadioButton (AccessibilityNodeInfo node) {
+    if (hasSelectionMode(node, AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE)) return true;
+    return isSubclassOf(node, android.widget.RadioButton.class);
+  }
+
+  public static boolean isEditable (AccessibilityNodeInfo node) {
+    if (APITests.haveJellyBeanMR2) {
+      return node.isEditable();
+    } else {
+      return isSubclassOf(node, android.widget.EditText.class);
+    }
+  }
+
+  public static String normalizeText (CharSequence text) {
+    if (text != null) {
+      String string = text.toString().trim();
+      if (string.length() > 0) return string;
+    }
+
+    return null;
+  }
+
+  public static String getText (AccessibilityNodeInfo node) {
+    CharSequence text = null;
+
+    {
+      TextField field = TextField.get(node);
+
+      if (field != null) {
+        synchronized (field) {
+          text = field.getAccessibilityText();
+        }
+      }
+    }
+
+    if (text == null) text = node.getText();
+    if (!isEditable(node)) return normalizeText(text);
+
+    if (text == null) return "";
+    return text.toString();
+  }
+
+  public static String getDescription (AccessibilityNodeInfo node) {
+    return normalizeText(node.getContentDescription());
+  }
+
+  public static AccessibilityNodeInfo getRootNode () {
+    if (APITests.haveJellyBean) {
+      return BrailleService.getBrailleService().getRootInActiveWindow();
+    }
+
+    return null;
+  }
+
+  public static AccessibilityNodeInfo findContainingNode (AccessibilityNodeInfo node, NodeTester tester) {
+    if (node == null) return null;
+    node = AccessibilityNodeInfo.obtain(node);
+    boolean haveTester = tester != null;
+
+    while (true) {
+      if (haveTester && tester.testNode(node)) return node;
+
+      AccessibilityNodeInfo parent = node.getParent();
+      boolean atRoot = parent == null;
+
+      if (atRoot && !haveTester) return node;
+      node.recycle();
+      if (atRoot) return null;
+
+      node = parent;
+      parent = null;
+    }
+  }
+
+  public static AccessibilityNodeInfo findRootNode (AccessibilityNodeInfo node) {
+    return findContainingNode(node, null);
+  }
+
+  public static AccessibilityNodeInfo findContainingWebView (AccessibilityNodeInfo node) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return isSubclassOf(node, WebView.class);
+      }
+    };
+
+    return findContainingNode(node, tester);
+  }
+
+  public static AccessibilityNodeInfo findContainingAccessibilityFocus (AccessibilityNodeInfo node) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return node.isAccessibilityFocused();
+      }
+    };
+
+    return findContainingNode(node, tester);
+  }
+
+  public static AccessibilityNodeInfo findNode (AccessibilityNodeInfo root, NodeTester tester) {
+    if (root != null) {
+      if (isVisible(root)) {
+        if (tester.testNode(root)) {
+          return AccessibilityNodeInfo.obtain(root);
+        }
+      }
+
+      {
+        int childCount = root.getChildCount();
+
+        for (int childIndex=0; childIndex<childCount; childIndex+=1) {
+          AccessibilityNodeInfo child = root.getChild(childIndex);
+
+          if (child != null) {
+            AccessibilityNodeInfo node = findNode(child, tester);
+            child.recycle();
+            child = null;
+            if (node != null) return node;
+          }
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public static AccessibilityNodeInfo findSelectedNode (AccessibilityNodeInfo root) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return node.isSelected();
+      }
+    };
+
+    return findNode(root, tester);
+  }
+
+  public static AccessibilityNodeInfo findFocusedNode (AccessibilityNodeInfo root) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return node.isFocused();
+      }
+    };
+
+    return findNode(root, tester);
+  }
+
+  public static AccessibilityNodeInfo findFocusableNode (AccessibilityNodeInfo root) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        if (node.isFocusable()) {
+          if (isVisible(node)) {
+            return true;
+          }
+        }
+
+        return false;
+      }
+    };
+
+    return findNode(root, tester);
+  }
+
+  public static AccessibilityNodeInfo findTextNode (AccessibilityNodeInfo root) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return getText(node) != null;
+      }
+    };
+
+    return findNode(root, tester);
+  }
+
+  public static AccessibilityNodeInfo findDescribedNode (AccessibilityNodeInfo root) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        return getDescription(node) != null;
+      }
+    };
+
+    return findNode(root, tester);
+  }
+
+  public static AccessibilityNodeInfo findActionableNode (AccessibilityNodeInfo node, final int actions) {
+    NodeTester tester = new NodeTester() {
+      @Override
+      public boolean testNode (AccessibilityNodeInfo node) {
+        if (!node.isEnabled()) return false;
+        return (node.getActions() & actions) != 0;
+      }
+    };
+
+    return findContainingNode(node, tester);
+  }
+
+  public static boolean performAction (AccessibilityNodeInfo node, int action, Bundle arguments) {
+    node = findActionableNode(node, action);
+    if (node == null) return false;
+
+    try {
+      return node.performAction(action, arguments);
+    } finally {
+      node.recycle();
+      node = null;
+    }
+  }
+
+  public static boolean performAction (AccessibilityNodeInfo node, int action) {
+    return performAction(node, action, null);
+  }
+
+  public static boolean performClick (AccessibilityNodeInfo node) {
+    return performAction(node, AccessibilityNodeInfo.ACTION_CLICK);
+  }
+
+  public static boolean performLongClick (AccessibilityNodeInfo node) {
+    return performAction(node, AccessibilityNodeInfo.ACTION_LONG_CLICK);
+  }
+
+  public static boolean performScrollForward (AccessibilityNodeInfo node) {
+    return performAction(node, AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+  }
+
+  public static boolean performScrollBackward (AccessibilityNodeInfo node) {
+    return performAction(node, AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+  }
+
+  public static AccessibilityNodeInfo findActionableNode (
+    AccessibilityNodeInfo node,
+    AccessibilityNodeInfo.AccessibilityAction... actions
+  ) {
+    if (APITests.haveLollipop) {
+      final Comparator<AccessibilityNodeInfo.AccessibilityAction> comparator =
+        new Comparator<AccessibilityNodeInfo.AccessibilityAction>() {
+          @Override
+          public int compare (
+            AccessibilityNodeInfo.AccessibilityAction action1,
+            AccessibilityNodeInfo.AccessibilityAction action2
+          ) {
+            return Integer.compare(action1.getId(), action2.getId());
+          }
+        };
+
+      final AccessibilityNodeInfo.AccessibilityAction[] array = Arrays.copyOf(actions, actions.length);
+      Arrays.sort(array, 0, array.length, comparator);
+
+      NodeTester tester = new NodeTester() {
+        @Override
+        public boolean testNode (AccessibilityNodeInfo node) {
+          if (!node.isEnabled()) return false;
+
+          for (AccessibilityNodeInfo.AccessibilityAction action : node.getActionList()) {
+            if (Arrays.binarySearch(array, 0, array.length, action, comparator) >= 0) {
+              return true;
+            }
+          }
+
+          return false;
+        }
+      };
+
+      return findContainingNode(node, tester);
+    }
+
+    return null;
+  }
+
+  public static boolean performAction (
+    AccessibilityNodeInfo node,
+    AccessibilityNodeInfo.AccessibilityAction action
+  ) {
+    if (APITests.haveLollipop) {
+      node = findActionableNode(node, action);
+
+      if (node != null) {
+        try {
+          return node.performAction(action.getId());
+        } finally {
+          node.recycle();
+          node = null;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  public static List<AccessibilityWindowInfo> getWindows () {
+    if (APITests.haveLollipop) {
+      return BrailleService.getBrailleService().getWindows();
+    }
+
+    return Collections.EMPTY_LIST;
+  }
+
+  public static AccessibilityWindowInfo findWindow (int identifier) {
+    AccessibilityWindowInfo found = null;
+
+    if (APITests.haveLollipop) {
+      for (AccessibilityWindowInfo window : getWindows()) {
+        if (found == null) {
+          if (window.getId() == identifier) {
+            found = window;
+            continue;
+          }
+        }
+
+        window.recycle();
+        window = null;
+      }
+    }
+
+    return found;
+  }
+
+  public static String getRangeValueFormat (AccessibilityNodeInfo.RangeInfo range) {
+    int type;
+
+    if (APITests.haveKitkat) {
+      type = range.getType();
+    } else {
+      //noinspection InlinedApi
+      type = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_FLOAT;
+    }
+
+    switch (type) {
+      case AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT:
+        return "%.0f";
+
+      case AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_PERCENT:
+        return "%.0f%";
+
+      default:
+      case AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_FLOAT:
+        return "%.2f";
+    }
+  }
+
+  public static String getSelectionModeLabel (AccessibilityNodeInfo.CollectionInfo collection) {
+    if (APITests.haveLollipop) {
+      switch (collection.getSelectionMode()) {
+        case AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE:
+          return "none";
+
+        case AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE:
+          return "sngl";
+
+        case AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_MULTIPLE:
+          return "mult";
+      }
+    }
+
+    return null;
+  }
+
+  public static Bundle getExtras (AccessibilityNodeInfo node) {
+    if (node != null) {
+      if (APITests.haveKitkat) {
+        return node.getExtras();
+      }
+    }
+
+    return null;
+  }
+
+  public static String getStringExtra (AccessibilityNodeInfo node, String extra) {
+    Bundle extras = getExtras(node);
+    if (extras == null) return null;
+    return extras.getString(extra);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenWindow.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenWindow.java
new file mode 100644
index 0000000..4e314e5
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/ScreenWindow.java
@@ -0,0 +1,135 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ScreenWindow {
+  private final int windowIdentifier;
+  private AccessibilityWindowInfo windowInfo = null;
+
+  private ScreenWindow (int identifier) {
+    windowIdentifier = identifier;
+  }
+
+  public final int getWindowIdentifier () {
+    return windowIdentifier;
+  }
+
+  public final AccessibilityWindowInfo getWindowInfo () {
+    synchronized (this) {
+      if (windowInfo != null) {
+        if (APITests.haveLollipop) {
+          return AccessibilityWindowInfo.obtain(windowInfo);
+        }
+      }
+    }
+
+    return null;
+  }
+
+  private final static Map<Integer, ScreenWindow> screenWindowCache =
+               new HashMap<Integer, ScreenWindow>();
+
+  private RenderedScreen renderedScreen = null;
+
+  public static ScreenWindow getScreenWindow (Integer identifier) {
+    synchronized (screenWindowCache) {
+      ScreenWindow window = screenWindowCache.get(identifier);
+      if (window != null) return window;
+
+      window = new ScreenWindow(identifier);
+      screenWindowCache.put(identifier, window);
+      return window;
+    }
+  }
+
+  public static ScreenWindow getScreenWindow (AccessibilityWindowInfo info) {
+    ScreenWindow window = null;
+
+    if (APITests.haveLollipop) {
+      window = getScreenWindow(info.getId());
+
+      synchronized (window) {
+        if (window.windowInfo != null) window.windowInfo.recycle();
+        window.windowInfo = AccessibilityWindowInfo.obtain(info);
+      }
+    }
+
+    return window;
+  }
+
+  public static ScreenWindow getScreenWindow (AccessibilityNodeInfo node) {
+    if (APITests.haveLollipop) {
+      AccessibilityWindowInfo info = node.getWindow();
+
+      if (info != null) {
+        try {
+          return getScreenWindow(info);
+        } finally {
+          info.recycle();
+          info = null;
+        }
+      }
+    }
+
+    return getScreenWindow(node.getWindowId());
+  }
+
+  public final Rect getLocation () {
+    synchronized (this) {
+      Rect location = new Rect();
+
+      if (windowInfo != null) {
+        if (APITests.haveLollipop) {
+          windowInfo.getBoundsInScreen(location);
+          return location;
+        }
+      }
+
+      Point size = new Point();
+      ApplicationUtilities.getWindowManager().getDefaultDisplay().getSize(size);
+      location.set(0, 0, size.x, size.y);
+      return location;
+    }
+  }
+
+  public boolean contains (Rect location) {
+    return getLocation().contains(location);
+  }
+
+  public final RenderedScreen getRenderedScreen () {
+    return renderedScreen;
+  }
+
+  public final ScreenWindow setRenderedScreen (RenderedScreen screen) {
+    renderedScreen = screen;
+    return this;
+  }
+
+  public static ScreenWindow setRenderedScreen (AccessibilityNodeInfo node) {
+    ScreenWindow window = getScreenWindow(node);
+    return window.setRenderedScreen(new RenderedScreen(node));
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SingletonReference.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SingletonReference.java
new file mode 100644
index 0000000..44ddfc9
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SingletonReference.java
@@ -0,0 +1,38 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+public abstract class SingletonReference<T> {
+  private T reference = null;
+
+  protected abstract T onNeedReference ();
+
+  public final T get () {
+    synchronized (this) {
+      if (reference == null) {
+        reference = onNeedReference();
+      }
+    }
+
+    return reference;
+  }
+
+  SingletonReference () {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StatusIndicators.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StatusIndicators.java
new file mode 100644
index 0000000..683fa88
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StatusIndicators.java
@@ -0,0 +1,411 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
+import android.telephony.CellInfoTdscdma;
+import android.telephony.CellInfoWcdma;
+import android.telephony.CellSignalStrength;
+import android.telephony.TelephonyManager;
+import android.text.format.DateFormat;
+import android.util.Log;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public abstract class StatusIndicators {
+  private final static String LOG_TAG = StatusIndicators.class.getName();
+
+  private StatusIndicators () {
+  }
+
+  private final static Context context = BrailleApplication.get();
+
+  private final static TelephonyManager telephonyManager = (TelephonyManager)
+          context.getSystemService(Context.TELEPHONY_SERVICE);
+
+  private final static WifiManager wifiManager = (WifiManager)
+          context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+
+  public interface Item {
+    public String getLabel ();
+    public String getValue ();
+  }
+
+  public static class Group {
+    private final Item[] groupItems;
+
+    public final Item[] getItems () {
+      Item[] array = groupItems;
+      return Arrays.copyOf(array, array.length);
+    }
+
+    public Group (Item... items) {
+      groupItems = items;
+    }
+
+    public boolean prepare () {
+      return true;
+    }
+  }
+
+  private static class DeviceGroup extends Group {
+    public final static Item TIME = new Item() {
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @SuppressLint("ConstantLocale")
+      @Override
+      public String getValue () {
+        String format = DateFormat.is24HourFormat(context)? "HH:mm": "h:mma";
+        return new SimpleDateFormat(format, Locale.getDefault()).format(new Date());
+      }
+    };
+
+    public final static Item BATTERY = new Item() {
+      private final int INT_NO_VALUE = APITests.havePie? Integer.MIN_VALUE: 0;
+      private final long LONG_NO_VALUE = Long.MIN_VALUE;
+
+      @Override
+      public String getLabel () {
+        return "Bat";
+      }
+
+      @Override
+      public String getValue () {
+        if (APITests.haveLollipop) {
+          BatteryManager batteryManager = (BatteryManager)
+            context.getSystemService(Context.BATTERY_SERVICE);
+
+          int value = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+
+          if (value != INT_NO_VALUE) {
+            return Integer.toString(value) + '%';
+          }
+        }
+
+        return null;
+      }
+    };
+
+    public DeviceGroup () {
+      super(TIME, BATTERY);
+    }
+  }
+
+  private static class CellGroup extends Group {
+    public final static Item OPERATOR = new Item() {
+      @Override
+      public String getLabel () {
+        return "Cell";
+      }
+
+      @Override
+      public String getValue () {
+        String name = telephonyManager.getNetworkOperatorName();
+        return name;
+      }
+    };
+
+    public final static Item SIGNAL = new Item() {
+      @Override
+      public String getLabel () {
+        return "Sig";
+      }
+
+      private final CellInfo getCellInfo () {
+        List<CellInfo> infoList = null;
+
+        if (APITests.haveJellyBeanMR1) {
+          try {
+            infoList = telephonyManager.getAllCellInfo();
+          } catch (SecurityException exception) {
+            Log.w(LOG_TAG, ("security exception: " + exception.getMessage()));
+          }
+        }
+
+        if (infoList != null) {
+          if (!infoList.isEmpty()) {
+            return infoList.get(0);
+          }
+        }
+
+        return null;
+      }
+
+      private final CellSignalStrength getCellSignalStrength (CellInfo info) {
+        if (APITests.haveJellyBeanMR1) {
+          if (info instanceof CellInfoCdma) {
+            return ((CellInfoCdma)info).getCellSignalStrength();
+          }
+
+          if (info instanceof CellInfoGsm) {
+            return ((CellInfoGsm)info).getCellSignalStrength();
+          }
+
+          if (info instanceof CellInfoLte) {
+            return ((CellInfoLte)info).getCellSignalStrength();
+          }
+        }
+
+        if (APITests.haveJellyBeanMR2) {
+          if (info instanceof CellInfoWcdma) {
+            return ((CellInfoWcdma)info).getCellSignalStrength();
+          }
+        }
+
+        if (APITests.haveQ) {
+          if (info instanceof CellInfoNr) {
+            return ((CellInfoNr)info).getCellSignalStrength();
+          }
+
+          if (info instanceof CellInfoTdscdma) {
+            return ((CellInfoTdscdma)info).getCellSignalStrength();
+          }
+        }
+
+        return null;
+      }
+
+      private final String[] levelNames = new String[] {
+        "none", "poor", "fair", "good", "high"
+      };
+
+      @Override
+      public String getValue () {
+        if (APITests.haveJellyBeanMR1) {
+          CellInfo info = getCellInfo();
+
+          if (info != null) {
+            CellSignalStrength css = getCellSignalStrength(info);
+
+            if (css != null) {
+              int level = css.getLevel();
+              return levelNames[level];
+            }
+          }
+        }
+
+        return null;
+      }
+    };
+
+    public final static Item TECHNOLOGY = new Item() {
+      private final Map<Integer, String> networkTypes = new HashMap<>();
+
+      {
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_1xRTT, "1xRTT");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_CDMA, "CDMA");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_EDGE, "EDGE");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_EHRPD, "eHRPD");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_EVDO_0, "EVDO revision 0");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_EVDO_A, "EVDO revision A");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_EVDO_B, "EVDO revision B");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_GPRS, "GPRS");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_HSDPA, "HSDPA");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_HSPA, "HSPA");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_HSPAP, "HSPA+");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_HSUPA, "HSUPA");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_IDEN, "iDen");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_LTE, "LTE");
+        networkTypes.put(TelephonyManager.NETWORK_TYPE_UMTS, "UMTS");
+      }
+
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @Override
+      public String getValue () {
+        int type = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+        try {
+          type = telephonyManager.getNetworkType();
+        } catch (SecurityException exception) {
+        }
+
+        if (type == TelephonyManager.NETWORK_TYPE_UNKNOWN) return null;
+        return networkTypes.get(type);
+      }
+    };
+
+    public final static Item ROAMING = new Item() {
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @Override
+      public String getValue () {
+        return telephonyManager.isNetworkRoaming()? "roaming": null;
+      }
+    };
+
+    public CellGroup () {
+      super(OPERATOR, SIGNAL, TECHNOLOGY, ROAMING);
+    }
+  }
+
+  private static class WifiGroup extends Group {
+    private static WifiInfo wifiInfo = null;
+
+    @Override
+    public boolean prepare () {
+      if (!super.prepare()) return false;
+
+      try {
+        wifiInfo = wifiManager.getConnectionInfo();
+      } catch (SecurityException exception) {
+        wifiInfo = null;
+      }
+
+      return wifiInfo != null;
+    }
+
+    public final static Item SSID = new Item() {
+      @Override
+      public String getLabel () {
+        return "WiFi";
+      }
+
+      @Override
+      public String getValue () {
+        if (wifiInfo != null) {
+          String ssid = wifiInfo.getSSID();
+          if ((ssid != null) && !ssid.isEmpty()) return ssid;
+        }
+
+        return null;
+      }
+    };
+
+    public final static Item SIGNAL = new Item() {
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @Override
+      public String getValue () {
+        if (wifiInfo != null) {
+          int rssi = wifiInfo.getRssi();
+          int percentage = wifiManager.calculateSignalLevel(rssi, 100);
+          return Integer.toString(percentage) + '%';
+        }
+
+        return null;
+      }
+    };
+
+    public final static Item SPEED = new Item() {
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @Override
+      public String getValue () {
+        if (wifiInfo != null) {
+          int speed = wifiInfo.getLinkSpeed();
+          return Integer.toString(speed) + WifiInfo.LINK_SPEED_UNITS;
+        }
+
+        return null;
+      }
+    };
+
+    public final static Item FREQUENCY = new Item() {
+      @Override
+      public String getLabel () {
+        return null;
+      }
+
+      @Override
+      public String getValue () {
+        if (APITests.haveLollipop) {
+          if (wifiInfo != null) {
+            int frequency = wifiInfo.getFrequency();
+            return Integer.toString(frequency) + WifiInfo.FREQUENCY_UNITS;
+          }
+        }
+
+        return null;
+      }
+    };
+
+    public WifiGroup () {
+      super(SSID, SIGNAL, SPEED, FREQUENCY);
+    }
+  }
+
+  public final static Group DEVICE = new DeviceGroup();
+  public final static Group CELL = new CellGroup();
+  public final static Group WIFI = new WifiGroup();
+
+  private final static Group[] GROUPS = {
+    DEVICE, CELL, WIFI
+  };
+
+  public static String get () {
+    StringBuilder status = new StringBuilder();
+
+    for (Group group : GROUPS) {
+      if (!group.prepare()) continue;
+      boolean first = true;
+
+      for (Item item : group.getItems()) {
+        String value = item.getValue();
+
+        if ((value == null) || value.isEmpty()) {
+          if (first) break;
+          continue;
+        }
+
+        if (first) {
+          first = false;
+          if (status.length() > 0) status.append(',');
+        }
+
+        if (status.length() > 0) status.append(' ');
+        String label = item.getLabel();
+        if ((label != null) && !label.isEmpty()) status.append(label).append(':');
+        status.append(value);
+      }
+    }
+
+    if (status.length() == 0) return null;
+    return status.toString();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StructuralMotion.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StructuralMotion.java
new file mode 100644
index 0000000..a3cb45e
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/StructuralMotion.java
@@ -0,0 +1,321 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import org.a11y.brltty.core.Braille;
+
+public class StructuralMotion {
+  private final static String LOG_TAG = StructuralMotion.class.getName();
+
+  public abstract static class Type {
+    private final String typeName;
+
+    public final String getTypeName () {
+      return typeName;
+    }
+
+    protected Type (String name) {
+      typeName = name;
+    }
+
+    protected abstract void setTypeArgument (Bundle arguments);
+    protected abstract int getActionForNext ();
+    protected abstract int getActionForPrevious ();
+  }
+
+  public static class Text extends Type {
+    private final int movementGranularity;
+
+    public final int getMovementGranularity () {
+      return movementGranularity;
+    }
+
+    @Override
+    protected final void setTypeArgument (Bundle arguments) {
+      arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, movementGranularity);
+    }
+
+    @Override
+    protected final int getActionForNext () {
+      return AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
+    }
+
+    @Override
+    protected final int getActionForPrevious () {
+      return AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+    }
+
+    private Text (int granularity, String name) {
+      super(name);
+      movementGranularity = granularity;
+    }
+
+    public final static Type CHARACTER = new Text(
+      AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER, "CHARACTER"
+    );
+
+    public final static Type WORD = new Text(
+      AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD, "WORD"
+    );
+
+    public final static Type LINE = new Text(
+      AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE, "LINE"
+    );
+
+    public final static Type PARAGRAPH = new Text(
+      AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH, "PARAGRAPH"
+    );
+
+    public final static Type PAGE = new Text(
+      AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE, "PAGE"
+    );
+  }
+
+  public static class Element extends Type {
+    private final String roleName;
+
+    public final String getRoleName () {
+      return roleName;
+    }
+
+    @Override
+    protected final void setTypeArgument (Bundle arguments) {
+      arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, roleName);
+    }
+
+    @Override
+    protected final int getActionForNext () {
+      return AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT;
+    }
+
+    @Override
+    protected final int getActionForPrevious () {
+      return AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT;
+    }
+
+    private Element (String role, String name) {
+      super(name);
+      roleName = role;
+    }
+
+    private Element (String role) {
+      this(role, role);
+    }
+
+    public final static Type MAIN = new Element("MAIN");
+    public final static Type FOCUSABLE = new Element("FOCUSABLE");
+    public final static Type CONTROL = new Element("CONTROL");
+    public final static Type LANDMARK = new Element("LANDMARK");
+
+    public final static Type ARTICLE = new Element("ARTICLE");
+    public final static Type FRAME = new Element("FRAME");
+    public final static Type SECTION = new Element("SECTION");
+
+    public final static Type GRAPHIC = new Element("GRAPHIC");
+    public final static Type MEDIA = new Element("MEDIA");
+
+    public final static Type TABLE = new Element("TABLE");
+    public final static Type LIST = new Element("LIST");
+    public final static Type LIST_ITEM = new Element("LIST_ITEM");
+
+    public final static Type HEADING = new Element("HEADING");
+    public final static Type HEADING_1 = new Element("H1");
+    public final static Type HEADING_2 = new Element("H2");
+    public final static Type HEADING_3 = new Element("H3");
+    public final static Type HEADING_4 = new Element("H4");
+    public final static Type HEADING_5 = new Element("H5");
+    public final static Type HEADING_6 = new Element("H6");
+
+    public final static Type LINK = new Element("LINK");
+    public final static Type VISITED_LINK = new Element("VISITED_LINK");
+    public final static Type UNVISITED_LINK = new Element("UNVISITED_LINK");
+
+    public final static Type EDITABLE_TEXT = new Element("TEXT_FIELD", "EDITABLE_TEXT");
+    public final static Type CHECKBOX = new Element("CHECKBOX");
+    public final static Type COMBOBOX = new Element("COMBOBOX");
+    public final static Type BUTTON = new Element("BUTTON");
+    public final static Type RADIO_BUTTON = new Element("RADIO", "RADIO_BUTTON");
+  }
+
+  public enum Direction {
+    NEXT(
+      new ActionGetter() {
+        @Override
+        public int getAction (Type type) {
+          return type.getActionForNext();
+        }
+      }
+    ),
+
+    PREVIOUS(
+      new ActionGetter() {
+        @Override
+        public int getAction (Type type) {
+          return type.getActionForPrevious();
+        }
+      }
+    ),
+
+    ; // end of enumeration
+
+    public interface ActionGetter {
+      public int getAction (Type type);
+    }
+
+    public final ActionGetter actionGetter;
+
+    Direction (ActionGetter getter) {
+      actionGetter = getter;
+    }
+
+    public final int getAction (Type type) {
+      return actionGetter.getAction(type);
+    }
+  }
+
+  private final static Map<Character, StructuralMotion> structuralMotions =
+               new HashMap<Character, StructuralMotion>();
+
+  public static StructuralMotion get (char character) {
+    return structuralMotions.get(character);
+  }
+
+  private final Type motionType;
+  private final Direction motionDirection;
+
+  public final Type getType () {
+    return motionType;
+  }
+
+  public final Direction getDirection () {
+    return motionDirection;
+  }
+
+  private final int actionIdentifier;
+  private final Bundle actionArguments;
+
+  @Override
+  public String toString () {
+    return new StringBuilder()
+          .append(getClass().getSimpleName()).append('{')
+          .append("Dir:").append(motionDirection.name().toLowerCase(Locale.ROOT))
+          .append(" Type:").append(motionType.getTypeName().toLowerCase(Locale.ROOT))
+          .append(" Act:").append(actionIdentifier)
+          .append(" Args:").append(actionArguments.toString())
+          .append('}')
+          .toString();
+  }
+
+  public static String toString (StructuralMotion motion) {
+    return motion.toString();
+  }
+
+  public final boolean apply (AccessibilityNodeInfo node) {
+    if (ApplicationParameters.ENABLE_MOTION_LOGGING) {
+      Log.d(LOG_TAG,
+        String.format(
+          "applying: %s From:%s Afd:%b", toString(),
+          ScreenUtilities.getText(node), node.isAccessibilityFocused()
+        )
+      );
+    }
+
+    return ScreenUtilities.performAction(node, actionIdentifier, actionArguments);
+  }
+
+  private StructuralMotion (Type type, Direction direction) {
+    motionType = type;
+    motionDirection = direction;
+
+    actionIdentifier = direction.getAction(type);
+    actionArguments = new Bundle();
+    type.setTypeArgument(actionArguments);
+  }
+
+  private static void addMotion (char character, Type type, Direction direction) {
+    structuralMotions.put(character, new StructuralMotion(type, direction));
+  }
+
+  private static void addMotion (char previous, char next, Type type) {
+    addMotion(previous, type, Direction.PREVIOUS);
+    addMotion(next, type, Direction.NEXT);
+  }
+
+  public final static char DOT_PREVIOUS   = Braille.DOT7;
+  public final static char DOT_NEXT       = Braille.DOT8;
+  public final static char DOTS_DIRECTION = DOT_PREVIOUS | DOT_NEXT;
+
+  private static void addMotion (char character, Type type, Integer key) {
+    character |= Braille.ROW;
+
+    char previous = character;
+    previous |= DOT_PREVIOUS;
+
+    char next = character;
+    next |= DOT_NEXT;
+
+    addMotion(previous, next, type);
+  }
+
+  private static void addMotion (char character, Type type) {
+    addMotion(character, type, KeyEvent.KEYCODE_UNKNOWN);
+  }
+
+  static {
+    addMotion('[', ']', Text.PARAGRAPH);
+    addMotion('{', '}', Text.PAGE);
+
+    addMotion('<', '>', Element.MAIN);
+    addMotion('(', ')', Element.FRAME);
+
+    addMotion(Braille.DOTS_A, Element.ARTICLE);
+    addMotion(Braille.DOTS_B, Element.BUTTON, KeyEvent.KEYCODE_B);
+    addMotion(Braille.DOTS_C, Element.CONTROL, KeyEvent.KEYCODE_C);
+    addMotion(Braille.DOTS_D, Element.LANDMARK, KeyEvent.KEYCODE_D);
+    addMotion(Braille.DOTS_E, Element.EDITABLE_TEXT, KeyEvent.KEYCODE_E);
+    addMotion(Braille.DOTS_F, Element.FOCUSABLE, KeyEvent.KEYCODE_F);
+    addMotion(Braille.DOTS_G, Element.GRAPHIC, KeyEvent.KEYCODE_G);
+    addMotion(Braille.DOTS_H, Element.HEADING, KeyEvent.KEYCODE_H);
+    addMotion(Braille.DOTS_I, Element.LIST_ITEM, KeyEvent.KEYCODE_I);
+    addMotion(Braille.DOTS_L, Element.LINK, KeyEvent.KEYCODE_L);
+    addMotion(Braille.DOTS_M, Element.MEDIA);
+    addMotion(Braille.DOTS_O, Element.LIST, KeyEvent.KEYCODE_O);
+    addMotion(Braille.DOTS_R, Element.RADIO_BUTTON);
+    addMotion(Braille.DOTS_S, Element.SECTION);
+    addMotion(Braille.DOTS_T, Element.TABLE, KeyEvent.KEYCODE_T);
+    addMotion(Braille.DOTS_U, Element.UNVISITED_LINK);
+    addMotion(Braille.DOTS_V, Element.VISITED_LINK);
+    addMotion(Braille.DOTS_X, Element.CHECKBOX, KeyEvent.KEYCODE_X);
+    addMotion(Braille.DOTS_Z, Element.COMBOBOX, KeyEvent.KEYCODE_Z);
+
+    addMotion(Braille.DOTS_1, Element.HEADING_1, KeyEvent.KEYCODE_1);
+    addMotion(Braille.DOTS_2, Element.HEADING_2, KeyEvent.KEYCODE_2);
+    addMotion(Braille.DOTS_3, Element.HEADING_3, KeyEvent.KEYCODE_3);
+    addMotion(Braille.DOTS_4, Element.HEADING_4, KeyEvent.KEYCODE_4);
+    addMotion(Braille.DOTS_5, Element.HEADING_5, KeyEvent.KEYCODE_5);
+    addMotion(Braille.DOTS_6, Element.HEADING_6, KeyEvent.KEYCODE_6);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SystemServiceReference.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SystemServiceReference.java
new file mode 100644
index 0000000..fc5b3f3
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/SystemServiceReference.java
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+public final class SystemServiceReference<T> extends SingletonReference<T> {
+  private final String serviceName;
+
+  @Override
+  protected final T onNeedReference () {
+    return (T)BrailleApplication.get().getSystemService(serviceName);
+  }
+
+  SystemServiceReference (String name) {
+    super();
+    serviceName = name;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/TextField.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/TextField.java
new file mode 100644
index 0000000..fdb7186
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/TextField.java
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TextField {
+  public TextField () {
+  }
+
+  private CharSequence accessibilityText = null;
+  private int selectionStart = 0;
+  private int selectionEnd = 0;
+
+  public final CharSequence getAccessibilityText () {
+    return accessibilityText;
+  }
+
+  public final TextField setAccessibilityText (CharSequence text) {
+    synchronized (this) {
+      accessibilityText = text;
+    }
+
+    return this;
+  }
+
+  public final int getSelectionStart () {
+    return selectionStart;
+  }
+
+  public final int getSelectionEnd () {
+    return selectionEnd;
+  }
+
+  public final TextField setSelection (int start, int end) {
+    synchronized (this) {
+      selectionStart = start;
+      selectionEnd = end;
+    }
+
+    return this;
+  }
+
+  public final TextField setCursor (int offset) {
+    return setSelection(offset, offset);
+  }
+
+  private final static Map<AccessibilityNodeInfo, TextField> textFields =
+               new HashMap<AccessibilityNodeInfo, TextField>();
+
+  public static TextField get (AccessibilityNodeInfo node, boolean canCreate) {
+    synchronized (textFields) {
+      TextField field = textFields.get(node);
+      if (field != null) return field;
+      if (!canCreate) return null;
+
+      field = new TextField();
+      textFields.put(node, field);
+      return field;
+    }
+  }
+
+  public static TextField get (AccessibilityNodeInfo node) {
+    return get(node, false);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/UsbHelper.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/UsbHelper.java
new file mode 100644
index 0000000..b36dcf4
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/UsbHelper.java
@@ -0,0 +1,157 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.*;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class UsbHelper {
+  private final static String LOG_TAG = UsbHelper.class.getName();
+
+  private static Context usbContext;
+  private static UsbManager usbManager;
+
+  private static BroadcastReceiver permissionReceiver;
+  private static PendingIntent permissionIntent;
+
+  private final static String ACTION_USB_PERMISSION =
+    "org.a11y.brltty.android.USB_PERMISSION";
+
+  private static void makePermissionReceiver () {
+    permissionReceiver = new BroadcastReceiver () {
+      @Override
+      public void onReceive (Context context, Intent intent) {
+        String action = intent.getAction();
+
+        if (action.equals(ACTION_USB_PERMISSION)) {
+          synchronized (this) {
+            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+
+            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+              Log.i(LOG_TAG, "permission granted for USB device: " + device);
+            } else {
+              Log.w(LOG_TAG, "permission denied for USB device: " + device);
+            }
+
+            notify();
+          }
+        }
+      }
+    };
+
+    {
+      IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
+      usbContext.registerReceiver(permissionReceiver, filter);
+    }
+
+    permissionIntent = PendingIntent.getBroadcast(usbContext, 0, new Intent(ACTION_USB_PERMISSION), 0);
+  }
+
+  public static void begin () {
+    usbContext = BrailleApplication.get();
+    usbManager = ApplicationUtilities.getUsbManager();
+    makePermissionReceiver();
+  }
+
+  public static void end () {
+    usbContext.unregisterReceiver(permissionReceiver);
+  }
+
+  public static Iterator<UsbDevice> getDeviceIterator () {
+    HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+    return devices.values().iterator();
+  }
+
+  public static UsbDevice getNextDevice (Iterator<UsbDevice> iterator) {
+    return iterator.hasNext()? iterator.next(): null;
+  }
+
+  public static UsbInterface getDeviceInterface (UsbDevice device, int identifier) {
+    int count = device.getInterfaceCount();
+
+    for (int index=0; index<count; index+=1) {
+      UsbInterface intf = device.getInterface(index);
+      if (identifier == intf.getId()) return intf;
+    }
+
+    return null;
+  }
+
+  public static UsbEndpoint getInterfaceEndpoint (UsbInterface intf, int address) {
+    int count = intf.getEndpointCount();
+
+    for (int index=0; index<count; index+=1) {
+      UsbEndpoint endpoint = intf.getEndpoint(index);
+      if (address == endpoint.getAddress()) return endpoint;
+    }
+
+    return null;
+  }
+
+  public static boolean obtainPermission (UsbDevice device) {
+    if (usbManager.hasPermission(device)) return true;
+    Log.d(LOG_TAG, "requesting permission for USB device: " + device);
+
+    synchronized (permissionReceiver) {
+      usbManager.requestPermission(device, permissionIntent);
+
+      try {
+        permissionReceiver.wait();
+      } catch (InterruptedException exception) {
+      }
+    }
+
+    return usbManager.hasPermission(device);
+  }
+
+  public static UsbDeviceConnection openDeviceConnection (UsbDevice device) {
+    if (obtainPermission(device)) {
+      return usbManager.openDevice(device);
+    }
+
+    return null;
+  }
+
+  public static UsbRequest enqueueRequest (UsbDeviceConnection connection, UsbEndpoint endpoint, byte[] bytes) {
+    UsbRequest request = new UsbRequest();
+
+    if (request.initialize(connection, endpoint)) {
+      ByteBuffer buffer = ByteBuffer.wrap(bytes);
+      request.setClientData(bytes);
+      if (request.queue(buffer, bytes.length)) return request;
+
+      request.close();
+    }
+
+    return null;
+  }
+
+  public static void cancelRequest (UsbRequest request) {
+    request.cancel();
+    request.close();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/VirtualScreenElement.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/VirtualScreenElement.java
new file mode 100644
index 0000000..f73f8b0
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/VirtualScreenElement.java
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android;
+
+import android.view.KeyEvent;
+
+public class VirtualScreenElement extends ScreenElement {
+  private final int globalAction;
+  private final int keyCode;
+
+  @Override
+  public boolean onClick () {
+    if (APITests.haveJellyBean) {
+      return BrailleService.getBrailleService().performGlobalAction(globalAction);
+    }
+
+    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+      return InputService.injectKey(keyCode);
+    }
+
+    return super.onClick();
+  }
+
+  public VirtualScreenElement (int text, int action, int key) {
+    super(text);
+    globalAction = action;
+    keyCode = key;
+  }
+
+  public VirtualScreenElement (int text, int action) {
+    this(text, action, KeyEvent.KEYCODE_UNKNOWN);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/AboutActivity.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/AboutActivity.java
new file mode 100644
index 0000000..a768e95
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/AboutActivity.java
@@ -0,0 +1,62 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+import org.a11y.brltty.android.*;
+
+public class AboutActivity extends InternalActivity {
+  private final static String LOG_TAG = AboutActivity.class.getName();
+
+  public void viewPrivacyPolicy (View view) {
+    launch(R.string.privacy_policy_url);
+  }
+
+  public void viewGooglePlay (View view) {
+    launch(R.string.google_play_url);
+  }
+
+  private final void setText (int view, CharSequence text) {
+    ((TextView)findViewById(view)).setText(text);
+  }
+
+  @Override
+  protected void onCreate (Bundle savedState) {
+    super.onCreate(savedState);
+    setContentView(R.layout.about_activity);
+
+    String name = getPackageName();
+    try {
+      PackageInfo info = getPackageManager().getPackageInfo(name, 0);
+
+      setText(R.id.app_version_name, info.versionName);
+    } catch (PackageManager.NameNotFoundException exception) {
+      Log.w(LOG_TAG, ("package information not found: " + name));
+    }
+  }
+
+  public static void launch () {
+    ApplicationUtilities.launch(AboutActivity.class);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/ActionsActivity.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/ActionsActivity.java
new file mode 100644
index 0000000..91a451d
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/ActionsActivity.java
@@ -0,0 +1,142 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.CheckBox;
+import java.io.File;
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.android.settings.SettingsActivity;
+
+public class ActionsActivity extends InternalActivity {
+  public void switchInputMethod (View view) {
+    InputService.switchInputMethod();
+  }
+
+  public void launchSettingsActivity (View view) {
+    SettingsActivity.launch();
+  }
+
+  public void viewUserGuide (View view) {
+    launch(R.string.user_guide_url);
+  }
+
+  public void browseWebSite (View view) {
+    launch(R.string.appConfig_webSite);
+  }
+
+  public void browseCommunityMessages (View view) {
+    launch(R.string.community_messages_url);
+  }
+
+  public void postCommunityMessage (View view) {
+    Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"));
+
+    {
+      StringBuilder recipient = new StringBuilder();
+      recipient.append("BRLTTY Mailing List");
+      recipient.append(' ');
+      recipient.append('<');
+      recipient.append(getResourceString(R.string.appConfig_emailAddress));
+      recipient.append('>');
+      intent.putExtra(Intent.EXTRA_EMAIL, new String[] {recipient.toString()});
+    }
+
+    launch(intent);
+  }
+
+  public void manageCommunityMembership (View view) {
+    launch(R.string.community_membership_url);
+  }
+
+  private View[] updateApplicationOptions = null;
+  private CheckBox developerBuild = null;
+  private CheckBox allowDowngrade = null;
+
+  public void updateApplication (View view) {
+    File directory = new File(getCacheDir(), "public");
+    directory.mkdir();
+    File file = new File(directory, "latest.apk");
+
+    int url =
+      developerBuild.isChecked()?
+      R.string.developer_package_url:
+      R.string.application_package_url;
+
+    new PackageInstaller(this, url, file) {
+      @Override
+      protected void onInstallFailed (String message) {
+        showMessage(
+          String.format(
+            "%s: %s",
+            getString(R.string.updateApplication_problem_failed),
+            message
+          )
+        );
+      }
+    }.setAllowDowngrade(allowDowngrade.isChecked())
+     .startInstall();
+  }
+
+  public void launchAboutActivity (View view) {
+    AboutActivity.launch();
+  }
+
+  @Override
+  protected void onCreate (Bundle savedState) {
+    super.onCreate(savedState);
+    setContentView(R.layout.actions_activity);
+
+    developerBuild = findViewById(R.id.GLOBAL_CHECKBOX_DEVELOPER_BUILD);
+    allowDowngrade = findViewById(R.id.GLOBAL_CHECKBOX_ALLOW_DOWNGRADE);
+
+    updateApplicationOptions = new View[] {
+      developerBuild,
+      allowDowngrade
+    };
+
+    for (View option : updateApplicationOptions) {
+      option.setVisibility(View.GONE);
+    }
+
+    {
+      View button = findViewById(R.id.GLOBAL_BUTTON_UPDATE_APPLICATION);
+
+      button.setOnLongClickListener(
+        new View.OnLongClickListener() {
+          @Override
+          public boolean onLongClick (View view) {
+            for (View option : updateApplicationOptions) {
+              option.setVisibility(View.VISIBLE);
+            }
+
+            return true;
+          }
+        }
+      );
+    }
+  }
+
+  public static void launch () {
+    ApplicationUtilities.launch(ActionsActivity.class);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/FileDownloader.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/FileDownloader.java
new file mode 100644
index 0000000..73a3cbe
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/FileDownloader.java
@@ -0,0 +1,271 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Locale;
+import org.a11y.brltty.android.*;
+
+public class FileDownloader extends UpgradeComponent {
+  private final static String LOG_TAG = FileDownloader.class.getName();
+
+  protected final String sourceURL;
+  protected final File targetFile;
+
+  public FileDownloader (InternalActivity owner, String url, File file) {
+    super(owner);
+    sourceURL = url;
+    targetFile = file;
+  }
+
+  protected void onDownloadStarted () {
+  }
+
+  protected void onDownloadProgress (long time, long position, Long length) {
+    if (false) {
+      long remaining = (length == null)? -1: (length - position);
+      Log.d("dnld-prog", String.format("t=%d p=%d r=%d", time, position, remaining));
+    }
+  }
+
+  protected void onDownloadFinished () {
+  }
+
+  protected void onDownloadFailed (String message) {
+    getActivity().showMessage(
+      String.format(
+        "%s: %s",
+        getString(R.string.fileDownloader_problem_failed),
+        message
+      )
+    );
+  }
+
+  public final void startDownload () {
+    new AsyncTask<Object, Object, String>() {
+      private final Object PROGRESS_LOCK = new Object();
+      private boolean progressInitialized = false;
+
+      private long startTime;
+      private long currentTime;
+      private long currentPosition;
+      private Long contentLength;
+
+      private AlertDialog alertDialog;
+      private TextView stateView;
+      private View progressView;
+      private ProgressBar progressBar;
+      private TextView progressCurrent;
+      private TextView progressRemaining;
+
+      private long getTime () {
+        return System.currentTimeMillis();
+      }
+
+      private void setState (int state) {
+        stateView.setText(state);
+      }
+
+      private void setBytes (TextView view, long value) {
+        view.setText(String.format(Locale.getDefault(), "%d", value));
+      }
+
+      private AlertDialog makeDialog () {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+          .setTitle(R.string.fileDownloader_title)
+          .setMessage(sourceURL)
+          ;
+
+        {
+          int layout = R.layout.file_downloader;
+
+          if (APITests.haveLollipop) {
+            builder.setView(layout);
+          } else {
+            Context context = builder.getContext();
+            LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            builder.setView(inflater.inflate(layout, null));
+          }
+        }
+
+        return builder.create();
+      }
+
+      private void showDialog () {
+        alertDialog = makeDialog();
+        alertDialog.show();
+
+        stateView = (TextView)alertDialog.findViewById(R.id.state);
+        progressView = alertDialog.findViewById(R.id.progress);
+        progressBar = (ProgressBar)alertDialog.findViewById(R.id.bar);
+        progressCurrent = (TextView)alertDialog.findViewById(R.id.current);
+        progressRemaining = (TextView)alertDialog.findViewById(R.id.remaining);
+      }
+
+      @Override
+      protected void onProgressUpdate (Object... arguments) {
+        synchronized (PROGRESS_LOCK) {
+          if (!progressInitialized) {
+            progressView.setVisibility(View.VISIBLE);
+            setState(R.string.fileDownloader_state_downloading);
+
+            if (contentLength == null) {
+              progressBar.setIndeterminate(true);
+              progressRemaining.setVisibility(View.GONE);
+            } else {
+              progressBar.setIndeterminate(false);
+              progressBar.setMax((int)(long)contentLength);
+
+              if (APITests.haveOreo) {
+                progressBar.setMin(0);
+              }
+            }
+
+            progressInitialized = true;
+          }
+
+          progressBar.setProgress((int)currentPosition);
+          setBytes(progressCurrent, currentPosition);
+
+          if (contentLength != null) {
+            setBytes(progressRemaining, (currentPosition - contentLength));
+          }
+
+          onDownloadProgress(currentTime, currentPosition, contentLength);
+        }
+      }
+
+      private void copy (InputStream input, OutputStream output) throws IOException {
+        synchronized (PROGRESS_LOCK) {
+          currentPosition = 0;
+          publishProgress();
+        }
+
+        byte[] buffer = new byte[0X1000];
+        int count;
+
+        while ((count = input.read(buffer)) != -1) {
+          output.write(buffer, 0, count);
+          currentPosition += count;
+
+          synchronized (PROGRESS_LOCK) {
+            long newTime = getTime() - startTime;
+
+            if ((newTime - currentTime) > 100) {
+              currentTime = newTime;
+              publishProgress();
+            }
+          }
+        }
+
+        synchronized (PROGRESS_LOCK) {
+          publishProgress();
+        }
+      }
+
+      @Override
+      protected void onPreExecute () {
+        onDownloadStarted();
+        showDialog();
+        setState(R.string.fileDownloader_state_connecting);
+      }
+
+      @Override
+      protected String doInBackground (Object... arguments) {
+        try {
+          URL url = new URL(sourceURL);
+          HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+          connection.setRequestMethod("GET");
+          connection.connect();
+
+          try {
+            InputStream input = connection.getInputStream();
+
+            try {
+              File file = targetFile;
+              file.delete();
+
+              try {
+                OutputStream output = new FileOutputStream(file);
+
+                try {
+                  if (APITests.haveNougat) {
+                    contentLength = connection.getContentLengthLong();
+                  } else {
+                    contentLength = (long)connection.getContentLength();
+                  }
+
+                  if (contentLength < 0) contentLength = null;
+                  startTime = getTime();
+                  currentTime = 0;
+
+                  copy(input, output);
+                  output.flush();
+
+                  file = null;
+                  return null;
+                } finally {
+                  output.close();
+                  output = null;
+                }
+              } finally {
+                if (file != null) file.delete();
+              }
+            } finally {
+              input.close();
+              input = null;
+            }
+          } finally {
+            connection.disconnect();
+            connection = null;
+          }
+        } catch (IOException exception) {
+          String message = exception.getMessage();
+          Log.w(LOG_TAG, String.format("file download failed: %s: %s", sourceURL, message));
+          return message;
+        }
+      }
+
+      @Override
+      protected void onPostExecute (String message) {
+        alertDialog.dismiss();
+
+        if (message == null) {
+          onDownloadFinished();
+        } else {
+          onDownloadFailed(message);
+        }
+      }
+    }.execute();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/InternalActivity.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/InternalActivity.java
new file mode 100644
index 0000000..0f0b233
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/InternalActivity.java
@@ -0,0 +1,94 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+import java.io.File;
+import org.a11y.brltty.android.*;
+
+public abstract class InternalActivity extends Activity {
+  private final static String LOG_TAG = InternalActivity.class.getName();
+
+  protected final String getResourceString (int identifier) {
+    return getResources().getString(identifier);
+  }
+
+  protected final void showMessage (CharSequence message, boolean asToast) {
+    if (asToast) {
+      Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+    } else {
+      DialogInterface.OnClickListener listener =
+        new DialogInterface.OnClickListener() {
+          @Override
+          public void onClick (DialogInterface dialog, int button) {
+            dialog.dismiss();
+          }
+        };
+
+      new AlertDialog.Builder(this)
+        .setMessage(message)
+        .setNeutralButton(android.R.string.yes, listener)
+        .create()
+        .show();
+    }
+  }
+
+  protected final void showMessage (CharSequence message) {
+    showMessage(message, false);
+  }
+
+  protected final void showMessage (int message, boolean asToast) {
+    showMessage(getResourceString(message), asToast);
+  }
+
+  protected final void showMessage (int message) {
+    showMessage(message, false);
+  }
+
+  protected final void launch (Intent intent) {
+    try {
+      startActivity(intent);
+    } catch (ActivityNotFoundException exception) {
+      Log.w(LOG_TAG, "activity not found", exception);
+    }
+  }
+
+  protected final void launch (Uri uri) {
+    launch(new Intent(Intent.ACTION_VIEW, uri));
+  }
+
+  protected final void launch (File file) {
+    launch(Uri.fromFile(file));
+  }
+
+  protected final void launch (String url) {
+    launch(Uri.parse(url));
+  }
+
+  protected final void launch (int url) {
+    launch(getResourceString(url));
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/PackageInstaller.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/PackageInstaller.java
new file mode 100644
index 0000000..d8c8dde
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/PackageInstaller.java
@@ -0,0 +1,180 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.util.Log;
+import androidx.core.content.FileProvider;
+import java.io.File;
+import java.util.Locale;
+import org.a11y.brltty.android.*;
+
+public class PackageInstaller extends UpgradeComponent {
+  private final static String LOG_TAG = PackageInstaller.class.getName();
+
+  public final static String PROVIDER_AUTHORITY = "org.a11y.brltty.android.fileprovider";
+  public final static String MIME_TYPE = "application/vnd.android.package-archive";
+
+  private final String sourceURL;
+  private final File targetFile;
+  private boolean allowDowngrade = false;
+
+  public PackageInstaller (InternalActivity owner, String url, File file) {
+    super(owner);
+    sourceURL = url;
+    targetFile = file;
+  }
+
+  public PackageInstaller (InternalActivity owner, int url, File file) {
+    this(owner, owner.getResources().getString(url), file);
+  }
+
+  public final PackageInstaller setAllowDowngrade (boolean yes) {
+    allowDowngrade = yes;
+    return this;
+  }
+
+  private final PackageManager getPackageManager () {
+    return getActivity().getPackageManager();
+  }
+
+  private final String getPackageName () {
+    return getActivity().getPackageName();
+  }
+
+  private final PackageInfo getPackageInfo (String name) {
+    try {
+      return getPackageManager().getPackageInfo(name, 0);
+    } catch (PackageManager.NameNotFoundException exception) {
+      Log.w(LOG_TAG, ("package not installed: " + name));
+    }
+
+    return null;
+  }
+
+  private final PackageInfo getPackageInfo () {
+    return getPackageInfo(getPackageName());
+  }
+
+  private final PackageInfo getPackageInfo (File file) {
+    return getPackageManager().getPackageArchiveInfo(file.getAbsolutePath(), 0);
+  }
+
+  protected void onInstallFailed (String message) {
+    getActivity().showMessage(
+      String.format(
+        "%s: %s",
+        getString(R.string.packageInstaller_problem_failed),
+        message
+      )
+    );
+  }
+
+  private void onInstallFailed (Exception exception) {
+    String message = exception.getMessage();
+    Log.w(LOG_TAG, String.format("package install failed: %s: %s", sourceURL, message));
+    onInstallFailed(message);
+  }
+
+  private final boolean isUpgrade (PackageInfo oldInfo, PackageInfo newInfo) {
+    int oldCode = oldInfo.versionCode;
+    int newCode = newInfo.versionCode;
+
+    String oldName = oldInfo.versionName;
+    String newName = newInfo.versionName;
+
+    if (newCode == oldCode) {
+      onInstallFailed(
+        String.format(Locale.ROOT,
+          "%s: %s (%d)",
+          getString(R.string.packageInstaller_problem_same),
+          newName, newCode
+        )
+      );
+
+      return false;
+    }
+
+    if (newCode < oldCode) {
+      final String operator = "<";
+
+      onInstallFailed(
+        String.format(Locale.ROOT,
+          "%s: %s %s %s (%d %s %d)",
+          getString(R.string.packageInstaller_problem_downgrade),
+          newName, operator, oldName,
+          newCode, operator, oldCode
+        )
+      );
+
+      return false;
+    }
+
+    return true;
+  }
+
+  private final boolean canInstallPackage () {
+    if (allowDowngrade) return true;
+
+    PackageInfo oldInfo = getPackageInfo();
+    PackageInfo newInfo = getPackageInfo(targetFile);
+
+    if (oldInfo == null) return true;
+    return isUpgrade(oldInfo, newInfo);
+  }
+
+  public final void startInstall () {
+    new FileDownloader(getActivity(), sourceURL, targetFile) {
+      @Override
+      protected void onDownloadFinished () {
+        if (canInstallPackage()) {
+          Uri uri;
+          int flags;
+
+          if (APITests.haveNougat) {
+            uri = FileProvider.getUriForFile(getActivity(), PROVIDER_AUTHORITY, targetFile);
+            flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+          } else {
+            uri = Uri.fromFile(targetFile);
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK;
+          }
+
+          Intent intent = new Intent(Intent.ACTION_VIEW);
+          intent.setDataAndType(uri, MIME_TYPE);
+          intent.setFlags(flags);
+
+          try {
+            getActivity().startActivity(intent);
+          } catch (ActivityNotFoundException exception) {
+            onInstallFailed(exception);
+          }
+        }
+      }
+
+      @Override
+      protected void onDownloadFailed (String message) {
+        onInstallFailed(message);
+      }
+    }.startDownload();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/UpgradeComponent.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/UpgradeComponent.java
new file mode 100644
index 0000000..1973803
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/activities/UpgradeComponent.java
@@ -0,0 +1,47 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.activities;
+
+import android.content.res.Resources;
+import org.a11y.brltty.android.*;
+
+public abstract class UpgradeComponent {
+  private final InternalActivity owningActivity;
+
+  protected UpgradeComponent (InternalActivity owner) {
+    owningActivity = owner;
+  }
+
+  protected final InternalActivity getActivity () {
+    return owningActivity;
+  }
+
+  protected final Resources getResources () {
+    return getActivity().getResources();
+  }
+
+  protected final String getString (int resource, boolean untranslated) {
+    if (untranslated) getResources().getString(resource);
+    return getActivity().getString(resource);
+  }
+
+  protected final String getString (int resource) {
+    return getString(resource, false);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AdvancedSettings.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AdvancedSettings.java
new file mode 100644
index 0000000..2ec6f65
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AdvancedSettings.java
@@ -0,0 +1,52 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.os.Bundle;
+import org.a11y.brltty.android.*;
+
+public final class AdvancedSettings extends SettingsFragment {
+  private KeyboardTableSetting keyboardTableSetting = null;
+  private AttributesTableSetting attributesTableSetting = null;
+
+  private LogLevelSetting logLevelSetting = null;
+  private LogCategoriesSetting logCategoriesSetting = null;
+
+  private LogAccessibilityEventsSetting logAccessibilityEventsSetting = null;
+  private LogRenderedScreenSetting logRenderedScreenSetting = null;
+  private LogKeyboardEventsSetting logKeyboardEventsSetting = null;
+  private LogUnhandledEventsSetting logUnhandledEventsSetting = null;
+
+  @Override
+  public void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    addPreferencesFromResource(R.xml.settings_advanced);
+
+    keyboardTableSetting = new KeyboardTableSetting(this);
+    attributesTableSetting = new AttributesTableSetting(this);
+
+    logLevelSetting = new LogLevelSetting(this);
+    logCategoriesSetting = new LogCategoriesSetting(this);
+
+    logAccessibilityEventsSetting = new LogAccessibilityEventsSetting(this);
+    logRenderedScreenSetting = new LogRenderedScreenSetting(this);
+    logKeyboardEventsSetting = new LogKeyboardEventsSetting(this);
+    logUnhandledEventsSetting = new LogUnhandledEventsSetting(this);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AttributesTableSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AttributesTableSetting.java
new file mode 100644
index 0000000..853ff7d
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/AttributesTableSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class AttributesTableSetting extends SingleSelectionSetting {
+  public AttributesTableSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_ATTRIBUTES_TABLE);
+    sortElementsByLabel();
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeAttributesTable(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/BluetoothDeviceCollection.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/BluetoothDeviceCollection.java
new file mode 100644
index 0000000..91f4a26
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/BluetoothDeviceCollection.java
@@ -0,0 +1,72 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.bluetooth.*;
+import android.content.Context;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public final class BluetoothDeviceCollection extends DeviceCollection {
+  public final static String DEVICE_QUALIFIER = "bluetooth";
+
+  @Override
+  public final String getQualifier () {
+    return DEVICE_QUALIFIER;
+  }
+
+  private final Collection<BluetoothDevice> devices;
+
+  public BluetoothDeviceCollection (Context context) {
+    super();
+    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+    devices = (adapter != null)? adapter.getBondedDevices(): Collections.EMPTY_SET;
+  }
+
+  @Override
+  public final String[] makeValues () {
+    StringMaker<BluetoothDevice> stringMaker = new StringMaker<BluetoothDevice>() {
+      @Override
+      public String makeString (BluetoothDevice device) {
+        return device.getAddress();
+      }
+    };
+
+    return makeStringArray(devices, stringMaker);
+  }
+
+  @Override
+  public final String[] makeLabels () {
+    StringMaker<BluetoothDevice> stringMaker = new StringMaker<BluetoothDevice>() {
+      @Override
+      public String makeString (BluetoothDevice device) {
+        return device.getName();
+      }
+    };
+
+    return makeStringArray(devices, stringMaker);
+  }
+
+  @Override
+  protected final void putParameters (Map<String, String> parameters, String value) {
+    parameters.put("address", value);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/CheckBoxSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/CheckBoxSetting.java
new file mode 100644
index 0000000..6ded0f8
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/CheckBoxSetting.java
@@ -0,0 +1,58 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import org.a11y.brltty.android.*;
+
+public abstract class CheckBoxSetting extends PreferenceSetting<CheckBoxPreference> {
+  protected abstract void onStateChanged (boolean newState);
+
+  public final boolean isChecked () {
+    return preference.isChecked();
+  }
+
+  protected final void setSummary (boolean checked) {
+    setSummary(
+      getString(
+        checked? R.string.checkbox_state_checked: R.string.checkbox_state_unchecked
+      )
+    );
+  }
+
+  @Override
+  public final void setSummary () {
+    setSummary(isChecked());
+  }
+
+  @Override
+  public boolean onPreferenceChange (Preference preference, Object newValue) {
+    final boolean newState = (Boolean)newValue;
+
+    setSummary(newState);
+    onStateChanged(newState);
+
+    return true;
+  }
+
+  protected CheckBoxSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ContractionTableSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ContractionTableSetting.java
new file mode 100644
index 0000000..1878ba5
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ContractionTableSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class ContractionTableSetting extends SingleSelectionSetting {
+  public ContractionTableSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_CONTRACTION_TABLE);
+    sortElementsByLabel(1);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeContractionTable(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceCollection.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceCollection.java
new file mode 100644
index 0000000..bd6231d
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceCollection.java
@@ -0,0 +1,92 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public abstract class DeviceCollection {
+  protected DeviceCollection () {
+  }
+
+  protected static interface StringMaker<T> {
+    public String makeString (T object);
+  }
+
+  protected static <T> String[] makeStringArray (Collection<T> collection, StringMaker<T> stringMaker) {
+    List<String> strings = new ArrayList<String>(collection.size());
+
+    for (T element : collection) {
+      strings.add(stringMaker.makeString(element));
+    }
+
+    return strings.toArray(new String[strings.size()]);
+  }
+
+  public final static char IDENTIFIER_SEPARATOR = ',';
+  public final static char QUALIFIER_DELIMITER = ':';
+  public final static char PARAMETER_SEPARATOR = '+';
+  public final static char ASSIGNMENT_DELIMITER = '=';
+
+  public abstract String getQualifier ();
+  public abstract String[] makeValues ();
+  public abstract String[] makeLabels ();
+  protected abstract void putParameters (Map<String, String> parameters, String value);
+
+  public static String makeReference (Map<String, String> parameters) {
+    StringBuilder reference = new StringBuilder();
+
+    for (String key : parameters.keySet()) {
+      String value = parameters.get(key);
+      if (value == null) continue;
+      if (value.isEmpty()) continue;
+
+      if (value.indexOf(PARAMETER_SEPARATOR) >= 0) continue;
+      if (value.indexOf(IDENTIFIER_SEPARATOR) >= 0) continue;
+
+      if (reference.length() > 0) reference.append(PARAMETER_SEPARATOR);
+      reference.append(key);
+      reference.append(ASSIGNMENT_DELIMITER);
+      reference.append(value);
+    }
+
+    if (reference.length() == 0) return null;
+    return reference.toString();
+  }
+
+  public static Map<String, String> newParameters () {
+    return new LinkedHashMap<String, String>();
+  }
+
+  public final String makeReference (String value) {
+    Map<String, String> parameters = newParameters();
+    putParameters(parameters, value);
+    return makeReference(parameters);
+  }
+
+  public final String makeIdentifier (String value) {
+    String reference = makeReference(value);
+    if (reference == null) return null;
+    return getQualifier() + QUALIFIER_DELIMITER + reference;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceDescriptor.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceDescriptor.java
new file mode 100644
index 0000000..e77e8a6
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceDescriptor.java
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public final class DeviceDescriptor {
+  private final String deviceIdentifier;
+  private final String brailleDriver;
+
+  public DeviceDescriptor (String identifier, String driver) {
+    deviceIdentifier = identifier;
+    brailleDriver = driver;
+  }
+
+  public final String getIdentifier () {
+    return deviceIdentifier;
+  }
+
+  public final String getDriver () {
+    return brailleDriver;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceManager.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceManager.java
new file mode 100644
index 0000000..9b5eb0b
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/DeviceManager.java
@@ -0,0 +1,414 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public final class DeviceManager extends SettingsFragment {
+  private final static String LOG_TAG = DeviceManager.class.getName();
+
+  private Set<String> deviceNames;
+  private DeviceCollection deviceCollection;
+
+  private SingleSelectionSetting selectedDeviceSetting;
+  private PreferenceScreen addDeviceScreen;
+  private PreferenceScreen removeDeviceScreen;
+
+  private TextSetting deviceNameSetting;
+  private SingleSelectionSetting deviceMethodSetting;
+  private SingleSelectionSetting deviceIdentifierSetting;
+  private SingleSelectionSetting deviceDriverSetting;
+  private PreferenceButton addDeviceButton;
+
+  private Preference removeDevicePrompt;
+  private PreferenceButton removeDeviceCancellationButton;
+  private PreferenceButton removeDeviceConfirmationButton;
+
+  private final static String PREF_KEY_DEVICE_NAMES = "device-names";
+  private final static String PREF_KEY_DEVICE_IDENTIFIER = "device-identifier";
+  private final static String PREF_KEY_DEVICE_QUALIFIER = "device-qualifier";
+  private final static String PREF_KEY_DEVICE_REFERENCE = "device-reference";
+  private final static String PREF_KEY_DEVICE_DRIVER = "device-driver";
+
+  private final static String[] DEVICE_PROPERTY_KEYS = {
+    PREF_KEY_DEVICE_IDENTIFIER,
+    PREF_KEY_DEVICE_QUALIFIER,
+    PREF_KEY_DEVICE_REFERENCE,
+    PREF_KEY_DEVICE_DRIVER
+  };
+
+  private static Map<String, String> getDeviceProperties (SharedPreferences prefs, String name) {
+    return getProperties(prefs, name, DEVICE_PROPERTY_KEYS);
+  }
+
+  private static void removeDeviceProperties (SharedPreferences.Editor editor, String name) {
+    removeProperties(editor, name, DEVICE_PROPERTY_KEYS);
+  }
+
+  private static String getSelectedDevice (SharedPreferences prefs) {
+    Context context = BrailleApplication.get();
+    String key = context.getResources().getString(R.string.PREF_KEY_SELECTED_DEVICE);
+    return prefs.getString(key, "");
+  }
+
+  public static String getSelectedDevice () {
+    return getSelectedDevice(DataType.getPreferences());
+  }
+
+  private static DeviceDescriptor getDeviceDescriptor (SharedPreferences prefs, String device) {
+    String identifier = "";
+    String driver = "";
+
+    if (!device.isEmpty()) {
+      Map<String, String> properties = getDeviceProperties(prefs, device);
+      identifier = properties.get(PREF_KEY_DEVICE_IDENTIFIER);
+      driver = properties.get(PREF_KEY_DEVICE_DRIVER);
+
+      if (identifier.isEmpty()) {
+        String qualifier = properties.get(PREF_KEY_DEVICE_QUALIFIER);
+
+        if (!qualifier.isEmpty()) {
+          StringBuilder sb = new StringBuilder();
+          sb.append(qualifier);
+          sb.append(DeviceCollection.QUALIFIER_DELIMITER);
+          sb.append(properties.get(PREF_KEY_DEVICE_REFERENCE));
+          identifier = sb.toString();
+        }
+      }
+    }
+
+    if (identifier.isEmpty()) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(BluetoothDeviceCollection.DEVICE_QUALIFIER);
+      sb.append(DeviceCollection.QUALIFIER_DELIMITER);
+      sb.append(DeviceCollection.IDENTIFIER_SEPARATOR);
+      sb.append(UsbDeviceCollection.DEVICE_QUALIFIER);
+      sb.append(DeviceCollection.QUALIFIER_DELIMITER);
+      identifier = sb.toString();
+    }
+
+    if (driver.isEmpty()) {
+      driver = "auto";
+    }
+
+    return new DeviceDescriptor(identifier, driver);
+  }
+
+  private static DeviceDescriptor getDeviceDescriptor (SharedPreferences prefs) {
+    return getDeviceDescriptor(prefs, getSelectedDevice(prefs));
+  }
+
+  public static DeviceDescriptor getDeviceDescriptor () {
+    return getDeviceDescriptor(DataType.getPreferences());
+  }
+
+  private void updateRemoveDeviceScreen (String selectedDevice) {
+    boolean on = false;
+
+    if (selectedDeviceSetting.isEnabled()) {
+      if (selectedDevice != null) {
+        if (selectedDevice.length() > 0) {
+          on = true;
+          removeDevicePrompt.setSummary(selectedDevice);
+        }
+      }
+    }
+
+    removeDeviceScreen.setSelectable(on);
+  }
+
+  private void updateRemoveDeviceScreen () {
+    updateRemoveDeviceScreen(selectedDeviceSetting.getSelectedValue());
+  }
+
+  private void updateSelectedDeviceSetting () {
+    boolean haveDevices = !deviceNames.isEmpty();
+    selectedDeviceSetting.setEnabled(haveDevices);
+    CharSequence summary;
+
+    if (haveDevices) {
+      {
+        String[] names = new String[deviceNames.size()];
+        deviceNames.toArray(names);
+        selectedDeviceSetting.setElements(names);
+        selectedDeviceSetting.sortElementsByLabel();
+      }
+
+      summary = selectedDeviceSetting.getSelectedLabel();
+      if ((summary == null) || (summary.length() == 0)) {
+        summary = getString(R.string.SELECTED_DEVICE_UNSELECTED);
+      }
+    } else {
+      summary = getString(R.string.SELECTED_DEVICE_NONE);
+    }
+
+    selectedDeviceSetting.setSummary(summary);
+    updateRemoveDeviceScreen();
+  }
+
+  private String getDeviceMethod () {
+    return deviceMethodSetting.getSelectedValue();
+  }
+
+  private DeviceCollection makeDeviceCollection (String deviceMethod) {
+    String className = getClass().getPackage().getName() + "." + deviceMethod + "DeviceCollection";
+
+    String[] argumentTypes = new String[] {
+      "android.content.Context"
+    };
+
+    Object[] arguments = new Object[] {
+      getActivity()
+    };
+
+    return (DeviceCollection)LanguageUtilities.newInstance(
+      className, argumentTypes, arguments
+    );
+  }
+
+  private void updateDeviceIdentifierSetting (String deviceMethod) {
+    deviceCollection = makeDeviceCollection(deviceMethod);
+
+    deviceIdentifierSetting.setElements(
+      deviceCollection.makeValues(), 
+      deviceCollection.makeLabels()
+    );
+
+    deviceIdentifierSetting.sortElementsByLabel();
+
+    {
+      boolean haveIdentifiers = deviceIdentifierSetting.getElementCount() > 0;
+      deviceIdentifierSetting.setEnabled(haveIdentifiers);
+
+      if (haveIdentifiers) {
+        deviceIdentifierSetting.selectFirstElement();
+      } else {
+        deviceIdentifierSetting.setSummary(getString(R.string.ADD_DEVICE_NO_DEVICES));
+      }
+    }
+
+    deviceDriverSetting.selectFirstElement();
+  }
+
+  private void updateDeviceName (String name) {
+    String problem;
+
+    if (!deviceMethodSetting.isEnabled()) {
+      problem = getString(R.string.ADD_DEVICE_UNSELECTED_METHOD);
+    } else if (!deviceIdentifierSetting.isEnabled()) {
+      problem = getString(R.string.ADD_DEVICE_UNSELECTED_DEVICE);
+    } else if (!deviceDriverSetting.isEnabled()) {
+      problem = getString(R.string.ADD_DEVICE_UNSELECTED_DRIVER);
+    } else {
+      if (name.length() == 0) {
+        name = new StringBuilder()
+              .append(deviceDriverSetting.getSummary())
+              .append(' ')
+              .append(deviceMethodSetting.getSummary())
+              .append(' ')
+              .append(deviceIdentifierSetting.getSummary())
+              .toString();
+      }
+
+      if (deviceNames.contains(name)) {
+        problem = getString(R.string.ADD_DEVICE_DUPLICATE_NAME);
+      } else {
+        problem = "";
+      }
+    }
+
+    addDeviceButton.setSummary(problem);
+    addDeviceButton.setEnabled(problem.isEmpty());
+    deviceNameSetting.setSummary(name);
+  }
+
+  private void updateDeviceName () {
+    updateDeviceName(deviceNameSetting.getText().toString());
+  }
+
+  private static void restartBrailleDriver (final SharedPreferences prefs, final String device) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          DeviceDescriptor descriptor = getDeviceDescriptor(prefs, device);
+          CoreWrapper.changeBrailleDevice(descriptor.getIdentifier());
+          CoreWrapper.changeBrailleDriver(descriptor.getDriver());
+          CoreWrapper.restartBrailleDriver();
+        }
+      }
+    );
+  }
+
+  @Override
+  public void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    addPreferencesFromResource(R.xml.settings_devices);
+    final SharedPreferences prefs = getPreferences();
+
+    addDeviceScreen = getPreferenceScreen(R.string.PREF_KEY_ADD_DEVICE);
+    removeDeviceScreen = getPreferenceScreen(R.string.PREF_KEY_REMOVE_DEVICE);
+    removeDevicePrompt = getPreference(R.string.PREF_KEY_REMOVE_DEVICE_PROMPT);
+
+    selectedDeviceSetting = new SingleSelectionSetting(this, R.string.PREF_KEY_SELECTED_DEVICE) {
+      @Override
+      public void onSelectionChanged (String newDevice) {
+        BrailleNotification.updateDevice(newDevice);
+        updateRemoveDeviceScreen(newDevice);
+        restartBrailleDriver(prefs, newDevice);
+      }
+    };
+
+    deviceNameSetting = new TextSetting(this, R.string.PREF_KEY_DEVICE_NAME) {
+      @Override
+      public void onTextChanged (String newName) {
+        updateDeviceName(newName);
+      }
+    };
+
+    deviceMethodSetting = new SingleSelectionSetting(this, R.string.PREF_KEY_DEVICE_METHOD) {
+      @Override
+      public void onSelectionChanged (String newMethod) {
+        updateDeviceIdentifierSetting(newMethod);
+        updateDeviceName();
+      }
+    };
+
+    deviceIdentifierSetting = new SingleSelectionSetting(this, R.string.PREF_KEY_DEVICE_IDENTIFIER) {
+      @Override
+      public void onSelectionChanged (String newIdentifier) {
+        updateDeviceName();
+      }
+    };
+
+    deviceDriverSetting = new SingleSelectionSetting(this, R.string.PREF_KEY_DEVICE_DRIVER) {
+      @Override
+      public void onSelectionChanged (String newDriver) {
+        updateDeviceName();
+      }
+    };
+
+    addDeviceButton = new PreferenceButton(this, R.string.PREF_KEY_DEVICE_ADD) {
+      @Override
+      public void onButtonClicked () {
+        new AsyncTask<Object, Object, String>() {
+          String name;
+
+          @Override
+          protected void onPreExecute () {
+            name = deviceNameSetting.getSummary().toString();
+          }
+
+          @Override
+          protected String doInBackground (Object... arguments) {
+            try {
+              return deviceCollection.makeIdentifier(deviceIdentifierSetting.getSelectedValue());
+            } catch (SecurityException exception) {
+              return null;
+            }
+          }
+
+          @Override
+          protected void onPostExecute (String identifier) {
+            if (identifier == null) {
+              showProblem(R.string.ADD_DEVICE_NO_PERMISSION, name);
+            } else {
+              deviceNames.add(name);
+              updateSelectedDeviceSetting();
+              updateDeviceName();
+
+              {
+                final SharedPreferences.Editor editor = preference.getEditor();
+
+                {
+                  Map<String, String> properties = new LinkedHashMap();
+                  properties.put(PREF_KEY_DEVICE_IDENTIFIER, identifier);
+
+                  properties.put(
+                    PREF_KEY_DEVICE_DRIVER,
+                    deviceDriverSetting.getSelectedValue()
+                  );
+
+                  putProperties(editor, name, properties);
+                }
+
+                editor.putStringSet(PREF_KEY_DEVICE_NAMES, deviceNames);
+                editor.apply();
+              }
+
+              dismissScreen();
+            }
+          }
+        }.execute();
+      }
+    };
+
+    removeDeviceCancellationButton = new PreferenceButton (this, R.string.PREF_KEY_REMOVE_DEVICE_CANCEL) {
+      @Override
+      public void onButtonClicked () {
+        dismissScreen();
+      }
+    };
+
+    removeDeviceConfirmationButton = new PreferenceButton(this, R.string.PREF_KEY_REMOVE_DEVICE_CONFIRM) {
+      @Override
+      public void onButtonClicked () {
+        String name = selectedDeviceSetting.getSelectedValue();
+
+        if (name != null) {
+          BrailleNotification.updateDevice(null);
+          deviceNames.remove(name);
+          selectedDeviceSetting.selectValue("");
+          updateSelectedDeviceSetting();
+          updateDeviceName();
+
+          {
+            SharedPreferences.Editor editor = preference.getEditor();
+            editor.putStringSet(PREF_KEY_DEVICE_NAMES, deviceNames);
+            removeDeviceProperties(editor, name);
+            editor.apply();
+          }
+
+          restartBrailleDriver(prefs, "");
+        }
+
+        dismissScreen();
+      }
+    };
+
+    deviceDriverSetting.sortElementsByLabel(1);
+    deviceNames = new TreeSet<String>(prefs.getStringSet(PREF_KEY_DEVICE_NAMES, Collections.EMPTY_SET));
+
+    updateSelectedDeviceSetting();
+    updateDeviceIdentifierSetting(getDeviceMethod());
+    updateDeviceName();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/GeneralSettings.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/GeneralSettings.java
new file mode 100644
index 0000000..19b9560
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/GeneralSettings.java
@@ -0,0 +1,42 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.os.Bundle;
+import org.a11y.brltty.android.*;
+
+public final class GeneralSettings extends SettingsFragment {
+  private ReleaseBrailleDeviceSetting releaseBrailleDeviceSetting = null;
+  private NavigationModeSetting navigationModeSetting = null;
+  private TextTableSetting textTableSetting = null;
+  private ContractionTableSetting contractionTableSetting = null;
+  private SpeechSupportSetting speechSupportSetting = null;
+
+  @Override
+  public void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    addPreferencesFromResource(R.xml.settings_general);
+
+    releaseBrailleDeviceSetting = new ReleaseBrailleDeviceSetting(this);
+    navigationModeSetting = new NavigationModeSetting(this);
+    textTableSetting = new TextTableSetting(this);
+    contractionTableSetting = new ContractionTableSetting(this);
+    speechSupportSetting = new SpeechSupportSetting(this);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/KeyboardTableSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/KeyboardTableSetting.java
new file mode 100644
index 0000000..e77d42f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/KeyboardTableSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class KeyboardTableSetting extends SingleSelectionSetting {
+  public KeyboardTableSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_KEYBOARD_TABLE);
+    sortElementsByLabel(1);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeKeyboardTable(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogAccessibilityEventsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogAccessibilityEventsSetting.java
new file mode 100644
index 0000000..83514a7
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogAccessibilityEventsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class LogAccessibilityEventsSetting extends CheckBoxSetting {
+  public LogAccessibilityEventsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_ACCESSIBILITY_EVENTS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.LOG_ACCESSIBILITY_EVENTS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogCategoriesSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogCategoriesSetting.java
new file mode 100644
index 0000000..746626a
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogCategoriesSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import java.util.Set;
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class LogCategoriesSetting extends MultipleSelectionSetting {
+  public LogCategoriesSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_CATEGORIES);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final Set<String> newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeLogCategories(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogKeyboardEventsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogKeyboardEventsSetting.java
new file mode 100644
index 0000000..510fc48
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogKeyboardEventsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class LogKeyboardEventsSetting extends CheckBoxSetting {
+  public LogKeyboardEventsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_KEYBOARD_EVENTS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.LOG_KEYBOARD_EVENTS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogLevelSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogLevelSetting.java
new file mode 100644
index 0000000..6815fa2
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogLevelSetting.java
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class LogLevelSetting extends SingleSelectionSetting {
+  public LogLevelSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_LEVEL);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeLogLevel(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogRenderedScreenSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogRenderedScreenSetting.java
new file mode 100644
index 0000000..4cb5d67
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogRenderedScreenSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class LogRenderedScreenSetting extends CheckBoxSetting {
+  public LogRenderedScreenSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_RENDERED_SCREEN);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.LOG_RENDERED_SCREEN = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogUnhandledEventsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogUnhandledEventsSetting.java
new file mode 100644
index 0000000..619225b
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/LogUnhandledEventsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class LogUnhandledEventsSetting extends CheckBoxSetting {
+  public LogUnhandledEventsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_LOG_UNHANDLED_EVENTS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.LOG_UNHANDLED_EVENTS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MessageSettings.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MessageSettings.java
new file mode 100644
index 0000000..d24007f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MessageSettings.java
@@ -0,0 +1,38 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.os.Bundle;
+import org.a11y.brltty.android.*;
+
+public final class MessageSettings extends SettingsFragment {
+  private ShowNotificationsSetting showNotificationsSetting = null;
+  private ShowAlertsSetting showAlertsSetting = null;
+  private ShowAnnouncementsSetting showAnnouncementsSetting = null;
+
+  @Override
+  public void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    addPreferencesFromResource(R.xml.settings_message);
+
+    showNotificationsSetting = new ShowNotificationsSetting(this);
+    showAlertsSetting = new ShowAlertsSetting(this);
+    showAnnouncementsSetting = new ShowAnnouncementsSetting(this);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MultipleSelectionSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MultipleSelectionSetting.java
new file mode 100644
index 0000000..966b59e
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/MultipleSelectionSetting.java
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.MultiSelectListPreference;
+import android.preference.Preference;
+import java.util.Set;
+import org.a11y.brltty.android.*;
+
+public abstract class MultipleSelectionSetting extends SelectionSetting<MultiSelectListPreference> {
+  protected abstract void onSelectionChanged (Set<String> newSelection);
+
+  @Override
+  public final CharSequence[] getAllValues () {
+    return preference.getEntryValues();
+  }
+
+  @Override
+  public final CharSequence getValueAt (int index) {
+    return getAllValues()[index];
+  }
+
+  @Override
+  public final int indexOf (String value) {
+    return preference.findIndexOfValue(value);
+  }
+
+  @Override
+  public final CharSequence[] getAllLabels () {
+    return preference.getEntries();
+  }
+
+  @Override
+  public final CharSequence getLabelAt (int index) {
+    return getAllLabels()[index];
+  }
+
+  public final Set<String> getSelectedValues () {
+    return preference.getValues();
+  }
+
+  @Override
+  protected final void setElements (String[] values, String[] labels) {
+    preference.setEntryValues(values);
+    preference.setEntries(labels);
+  }
+
+  protected final void setSummary (Set<String> values) {
+    StringBuilder summary = new StringBuilder();
+
+    for (String value : values) {
+      if (value.length() > 0) {
+        if (summary.length() > 0) summary.append('\n');
+        summary.append(getLabelFor(value));
+      }
+    }
+
+    if (summary.length() == 0) {
+      summary.append(getString(R.string.SET_SELECTION_NONE));
+    }
+
+    setSummary(summary);
+  }
+
+  @Override
+  public final void setSummary () {
+    setSummary(getSelectedValues());
+  }
+
+  @Override
+  public boolean onPreferenceChange (Preference preference, Object newValue) {
+    final Set<String> newSelection = (Set<String>)newValue;
+
+    setSummary(newSelection);
+    onSelectionChanged(newSelection);
+
+    return true;
+  }
+
+  protected MultipleSelectionSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/NavigationModeSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/NavigationModeSetting.java
new file mode 100644
index 0000000..a75a527
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/NavigationModeSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class NavigationModeSetting extends SingleSelectionSetting {
+  public NavigationModeSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_NAVIGATION_MODE);
+  }
+
+  @Override
+  protected final void onSelectionChanged (String newSelection) {
+    BrailleRenderer.setBrailleRenderer(newSelection);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceButton.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceButton.java
new file mode 100644
index 0000000..db90d03
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceButton.java
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import org.a11y.brltty.android.*;
+
+public abstract class PreferenceButton
+extends PreferenceWrapper<Preference>
+implements Preference.OnPreferenceClickListener
+{
+  protected abstract void onButtonClicked ();
+
+  @Override
+  public boolean onPreferenceClick (Preference preference) {
+    onButtonClicked();
+    return true;
+  }
+
+  protected PreferenceButton (SettingsFragment fragment, int key) {
+    super(fragment, key);
+    preference.setOnPreferenceClickListener(this);
+  }
+
+  protected final PreferenceScreen findScreen () {
+    Preference pref = preference;
+
+    while (pref != null) {
+      if (pref instanceof PreferenceScreen) return (PreferenceScreen)pref;
+      pref = pref.getParent();
+    }
+
+    return null;
+  }
+
+  protected final void dismissScreen () {
+    findScreen().getDialog().dismiss();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceSetting.java
new file mode 100644
index 0000000..52c474c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceSetting.java
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.Preference;
+import org.a11y.brltty.android.*;
+
+public abstract class PreferenceSetting<P extends Preference>
+extends PreferenceWrapper<P>
+implements Preference.OnPreferenceChangeListener
+{
+  public abstract void setSummary ();
+
+  protected PreferenceSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+    setSummary();
+    preference.setOnPreferenceChangeListener(this);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceWrapper.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceWrapper.java
new file mode 100644
index 0000000..5626c8e
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/PreferenceWrapper.java
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.Preference;
+import org.a11y.brltty.android.*;
+
+public abstract class PreferenceWrapper<P extends Preference> {
+  protected final SettingsFragment settingsFragment;
+  protected final P preference;
+
+  protected PreferenceWrapper (SettingsFragment fragment, int key) {
+    settingsFragment = fragment;
+    preference = (P)settingsFragment.getPreference(key);
+  }
+
+  protected final String getString (int string) {
+    return settingsFragment.getString(string);
+  }
+
+  public final CharSequence getSummary () {
+    return preference.getSummary();
+  }
+
+  public final void setSummary (CharSequence summary) {
+    preference.setSummary(summary);
+  }
+
+  public final boolean isEnabled () {
+    return preference.isEnabled();
+  }
+
+  public final void setEnabled (boolean yes) {
+    preference.setEnabled(yes);
+  }
+
+  public final boolean isSelectable () {
+    return preference.isSelectable();
+  }
+
+  public final void setSelectable (boolean yes) {
+    preference.setSelectable(yes);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ReleaseBrailleDeviceSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ReleaseBrailleDeviceSetting.java
new file mode 100644
index 0000000..17bf5f6
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ReleaseBrailleDeviceSetting.java
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class ReleaseBrailleDeviceSetting extends CheckBoxSetting {
+  public ReleaseBrailleDeviceSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_RELEASE_BRAILLE_DEVICE);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.RELEASE_BRAILLE_DEVICE = newState;
+    BrailleNotification.updateState();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SelectionSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SelectionSetting.java
new file mode 100644
index 0000000..704fbdd
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SelectionSetting.java
@@ -0,0 +1,89 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.Preference;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public abstract class SelectionSetting<P extends Preference> extends PreferenceSetting<P> {
+  protected SelectionSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+  }
+
+  public abstract CharSequence[] getAllValues ();
+  public abstract CharSequence getValueAt (int index);
+  public abstract int indexOf (String value);
+
+  public abstract CharSequence[] getAllLabels ();
+  public abstract CharSequence getLabelAt (int index);
+
+  public final CharSequence getLabelFor (String value) {
+    int index = indexOf(value);
+    if (index < 0) return null;
+    return getLabelAt(index);
+  }
+
+  public final int getElementCount () {
+    return getAllValues().length;
+  }
+
+  protected abstract void setElements (String[] values, String[] labels);
+
+  protected final void setElements (String[] values) {
+    setElements(values, values);
+  }
+
+  protected final void sortElementsByLabel (int fromIndex) {
+    Collator collator = Collator.getInstance();
+    collator.setStrength(Collator.PRIMARY);
+    collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+
+    String[] values = LanguageUtilities.newStringArray(getAllValues());
+    String[] labels = LanguageUtilities.newStringArray(getAllLabels());
+
+    int size = values.length;
+    Map<String, String> map = new LinkedHashMap<String, String>();
+    CollationKey keys[] = new CollationKey[size];
+
+    for (int i=0; i<size; i+=1) {
+      String label = labels[i];
+      map.put(label, values[i]);
+      keys[i] = collator.getCollationKey(label);
+    }
+
+    Arrays.sort(keys, fromIndex, keys.length);
+
+    for (int i=0; i<size; i+=1) {
+      String label = keys[i].getSourceString();
+      labels[i] = label;
+      values[i] = map.get(label);
+    }
+
+    setElements(values, labels);
+  }
+
+  protected final void sortElementsByLabel () {
+    sortElementsByLabel(0);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SerialDeviceCollection.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SerialDeviceCollection.java
new file mode 100644
index 0000000..cd4c12c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SerialDeviceCollection.java
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.content.Context;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public final class SerialDeviceCollection extends DeviceCollection {
+  public final static String DEVICE_QUALIFIER = "serial";
+
+  @Override
+  public final String getQualifier () {
+    return DEVICE_QUALIFIER;
+  }
+
+  public SerialDeviceCollection (Context context) {
+    super();
+  }
+
+  @Override
+  public final String[] makeValues () {
+    return new String[0];
+  }
+
+  @Override
+  public final String[] makeLabels () {
+    return new String[0];
+  }
+
+  @Override
+  protected final void putParameters (Map<String, String> parameters, String value) {
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsActivity.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsActivity.java
new file mode 100644
index 0000000..8ace92f
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsActivity.java
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import java.util.List;
+import org.a11y.brltty.android.*;
+
+public class SettingsActivity extends PreferenceActivity {
+  private final static String LOG_TAG = SettingsActivity.class.getName();
+
+  @Override
+  protected void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setTitle(R.string.SETTINGS_SCREEN_MAIN);
+  }
+
+  @Override
+  public void onBuildHeaders (List<Header> headers) {
+    loadHeadersFromResource(R.xml.settings_headers, headers);
+  }
+
+  @Override
+  protected boolean isValidFragment (String name) {
+    return true;
+  }
+
+  public static void launch () {
+    ApplicationUtilities.launch(SettingsActivity.class);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsFragment.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsFragment.java
new file mode 100644
index 0000000..5449fa3
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SettingsFragment.java
@@ -0,0 +1,91 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.widget.Toast;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public abstract class SettingsFragment extends PreferenceFragment {
+  private final static String LOG_TAG = SettingsFragment.class.getName();
+
+  @Override
+  public void onCreate (Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    if (APITests.haveNougat) {
+      getPreferenceManager().setStorageDeviceProtected();
+    }
+  }
+
+  protected final void showProblem (int problem, String detail) {
+    StringBuilder message = new StringBuilder();
+    message.append(getString(problem));
+    if (detail != null) message.append(": ").append(detail);
+    Toast.makeText(getActivity(), message.toString(), Toast.LENGTH_LONG).show();
+  }
+
+  protected final void showProblem (int problem) {
+    showProblem(problem, null);
+  }
+
+  private static String makePropertyKey (String name, String key) {
+    return key + '-' + name;
+  }
+
+  protected static void putProperties (SharedPreferences.Editor editor, String name, Map<String, String> properties) {
+    for (Map.Entry<String, String> property : properties.entrySet()) {
+      editor.putString(makePropertyKey(name, property.getKey()), property.getValue());
+    }
+  }
+
+  protected static Map<String, String> getProperties (SharedPreferences prefs, String name, String... keys) {
+    Map<String, String> properties = new LinkedHashMap();
+
+    for (String key : keys) {
+      properties.put(key, prefs.getString(makePropertyKey(name, key), ""));
+    }
+
+    return properties;
+  }
+
+  protected static void removeProperties (SharedPreferences.Editor editor, String name, String... keys) {
+    for (String key : keys) {
+      editor.remove(makePropertyKey(name, key));
+    }
+  }
+
+  protected final SharedPreferences getPreferences () {
+    return getPreferenceManager().getSharedPreferences();
+  }
+
+  protected final Preference getPreference (int key) {
+    return findPreference(getResources().getString(key));
+  }
+
+  protected final PreferenceScreen getPreferenceScreen (int key) {
+    return (PreferenceScreen)getPreference(key);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAlertsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAlertsSetting.java
new file mode 100644
index 0000000..858112c
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAlertsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class ShowAlertsSetting extends CheckBoxSetting {
+  public ShowAlertsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_SHOW_ALERTS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.SHOW_ALERTS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAnnouncementsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAnnouncementsSetting.java
new file mode 100644
index 0000000..65b52da
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowAnnouncementsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class ShowAnnouncementsSetting extends CheckBoxSetting {
+  public ShowAnnouncementsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_SHOW_ANNOUNCEMENTS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.SHOW_ANNOUNCEMENTS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowNotificationsSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowNotificationsSetting.java
new file mode 100644
index 0000000..79ae153
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/ShowNotificationsSetting.java
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+
+public class ShowNotificationsSetting extends CheckBoxSetting {
+  public ShowNotificationsSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_SHOW_NOTIFICATIONS);
+  }
+
+  @Override
+  protected final void onStateChanged (boolean newState) {
+    ApplicationSettings.SHOW_NOTIFICATIONS = newState;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SingleSelectionSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SingleSelectionSetting.java
new file mode 100644
index 0000000..1974a07
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SingleSelectionSetting.java
@@ -0,0 +1,101 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.ListPreference;
+import android.preference.Preference;
+import org.a11y.brltty.android.*;
+
+public abstract class SingleSelectionSetting extends SelectionSetting<ListPreference> {
+  protected abstract void onSelectionChanged (String newSelection);
+
+  @Override
+  public final CharSequence[] getAllValues () {
+    return preference.getEntryValues();
+  }
+
+  @Override
+  public final CharSequence getValueAt (int index) {
+    return getAllValues()[index];
+  }
+
+  @Override
+  public final int indexOf (String value) {
+    return preference.findIndexOfValue(value);
+  }
+
+  @Override
+  public final CharSequence[] getAllLabels () {
+    return preference.getEntries();
+  }
+
+  @Override
+  public final CharSequence getLabelAt (int index) {
+    return getAllLabels()[index];
+  }
+
+  public final String getSelectedValue () {
+    return preference.getValue();
+  }
+
+  public final CharSequence getSelectedLabel () {
+    return preference.getEntry();
+  }
+
+  @Override
+  public final void setSummary () {
+    CharSequence label = getSelectedLabel();
+    if (label == null) label = "";
+    setSummary(label);
+  }
+
+  protected final void setSummary (int index) {
+    setSummary(getLabelAt(index));
+  }
+
+  @Override
+  public boolean onPreferenceChange (Preference preference, Object newValue) {
+    final String newSelection = (String)newValue;
+
+    setSummary(indexOf(newSelection));
+    onSelectionChanged(newSelection);
+
+    return true;
+  }
+
+  protected SingleSelectionSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+  }
+
+  @Override
+  protected final void setElements (String[] values, String[] labels) {
+    preference.setEntryValues(values);
+    preference.setEntries(labels);
+  }
+
+  protected final void selectFirstElement () {
+    preference.setValueIndex(0);
+    setSummary();
+  }
+
+  public final void selectValue (String newValue) {
+    preference.setValue(newValue);
+    setSummary();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SpeechSupportSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SpeechSupportSetting.java
new file mode 100644
index 0000000..45364d7
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/SpeechSupportSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class SpeechSupportSetting extends SingleSelectionSetting {
+  public SpeechSupportSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_SPEECH_SUPPORT);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeSpeechDriver(newSelection);
+          CoreWrapper.restartSpeechDriver();
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextSetting.java
new file mode 100644
index 0000000..fd52224
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextSetting.java
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import org.a11y.brltty.android.*;
+
+public abstract class TextSetting extends PreferenceSetting<EditTextPreference> {
+  protected abstract void onTextChanged (String newText);
+
+  public final CharSequence getText () {
+    return preference.getEditText().getText();
+  }
+
+  @Override
+  public final void setSummary () {
+    setSummary(getText());
+  }
+
+  @Override
+  public boolean onPreferenceChange (Preference preference, Object newValue) {
+    final String newText = (String)newValue;
+
+    setSummary(newText);
+    onTextChanged(newText);
+
+    return true;
+  }
+
+  protected TextSetting (SettingsFragment fragment, int key) {
+    super(fragment, key);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextTableSetting.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextTableSetting.java
new file mode 100644
index 0000000..33551b1
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/TextTableSetting.java
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import org.a11y.brltty.android.*;
+import org.a11y.brltty.core.CoreWrapper;
+
+public class TextTableSetting extends SingleSelectionSetting {
+  public TextTableSetting (SettingsFragment fragment) {
+    super(fragment, R.string.PREF_KEY_TEXT_TABLE);
+    sortElementsByLabel(1);
+  }
+
+  @Override
+  protected final void onSelectionChanged (final String newSelection) {
+    CoreWrapper.runOnCoreThread(
+      new Runnable() {
+        @Override
+        public void run () {
+          CoreWrapper.changeTextTable(newSelection);
+        }
+      }
+    );
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceAttachedMonitor.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceAttachedMonitor.java
new file mode 100644
index 0000000..461d319
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceAttachedMonitor.java
@@ -0,0 +1,87 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.usb.*;
+import android.os.Bundle;
+import android.util.Log;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public class UsbDeviceAttachedMonitor extends Activity {
+  private final static String LOG_TAG = UsbDeviceAttachedMonitor.class.getName();
+
+  private static void addProperty (Map<String, String> properties, String name, int value) {
+    if (value != 0) {
+      properties.put(name, String.format("%04X", value));
+    }
+  }
+
+  private static void addProperty (Map<String, String> properties, String name, String value) {
+    if (value != null) {
+      if (!value.isEmpty()) {
+        char quote = '"';
+        properties.put(name, String.format("%c%s%c", quote, value, quote));
+      }
+    }
+  }
+
+  private static Map<String, String> getProperties (UsbDevice device) {
+    Map<String, String> properties = new LinkedHashMap<>();
+
+    addProperty(properties, "VID", device.getVendorId());
+    addProperty(properties, "PID", device.getProductId());
+    addProperty(properties, "Serial", device.getSerialNumber());
+    addProperty(properties, "Manufacturer", device.getManufacturerName());
+    addProperty(properties, "Product", device.getProductName());
+
+    return properties;
+  }
+
+  private static String makeDescription (UsbDevice device) {
+    Map<String, String> properties = getProperties(device);
+    StringBuilder description = new StringBuilder();
+
+    for (String name : properties.keySet()) {
+      String value = properties.get(name);
+      if (description.length() > 0) description.append(' ');
+      description.append(name).append(':').append(value);
+    }
+
+    return description.toString();
+  }
+
+  @Override
+  protected void onCreate (Bundle savedState) {
+    super.onCreate(savedState);
+
+    Intent intent = getIntent();
+    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+    Log.i(LOG_TAG, ("device attached: " + makeDescription(device)));
+  }
+
+  @Override
+  protected void onResume () {
+    super.onResume();
+    finish();
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceCollection.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceCollection.java
new file mode 100644
index 0000000..6e7d8ff
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/settings/UsbDeviceCollection.java
@@ -0,0 +1,160 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.settings;
+
+import android.content.Context;
+import android.hardware.usb.*;
+import java.util.Collection;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public final class UsbDeviceCollection extends DeviceCollection {
+  public final static String DEVICE_QUALIFIER = "usb";
+
+  @Override
+  public final String getQualifier () {
+    return DEVICE_QUALIFIER;
+  }
+
+  private final static UsbManager usbManager = (UsbManager)
+    BrailleApplication.get().getSystemService(Context.USB_SERVICE);
+
+  private final Map<String, UsbDevice> usbDeviceMap;
+  private final Collection<UsbDevice> usbDevices;
+
+  public UsbDeviceCollection (Context context) {
+    super();
+    usbDeviceMap = usbManager.getDeviceList();
+    usbDevices = usbDeviceMap.values();
+  }
+
+  private final String getManufacturerName (UsbDevice device) {
+    if (APITests.haveLollipop) {
+      return device.getManufacturerName();
+    } else {
+      return null;
+    }
+  }
+
+  private final String getProductDescription (UsbDevice device) {
+    if (APITests.haveLollipop) {
+      return device.getProductName();
+    } else {
+      return null;
+    }
+  }
+
+  private static String getSerialNumber (UsbDevice device) {
+    if (APITests.haveLollipop) {
+      return device.getSerialNumber();
+    } else {
+      UsbDeviceConnection connection = usbManager.openDevice(device);
+      if (connection == null) return null;
+
+      try {
+        return connection.getSerial();
+      } finally {
+        connection.close();
+      }
+    }
+  }
+
+  @Override
+  public final String[] makeValues () {
+    StringMaker<UsbDevice> stringMaker = new StringMaker<UsbDevice>() {
+      @Override
+      public String makeString (UsbDevice device) {
+        return device.getDeviceName();
+      }
+    };
+
+    return makeStringArray(usbDevices, stringMaker);
+  }
+
+  @Override
+  public final String[] makeLabels () {
+    StringMaker<UsbDevice> stringMaker = new StringMaker<UsbDevice>() {
+      @Override
+      public String makeString (UsbDevice device) {
+        StringBuilder label = new StringBuilder();
+
+        {
+          boolean first = true;
+          String serialNumber;
+
+          try {
+            serialNumber = getSerialNumber(device);
+          } catch (SecurityException exception) {
+            serialNumber = null;
+          }
+
+          String[] components = new String[] {
+            getManufacturerName(device),
+            getProductDescription(device),
+            serialNumber
+          };
+
+          for (String component : components) {
+            if (component == null) continue;
+            component = component.trim();
+            if (component.isEmpty()) continue;
+
+            if (first) {
+              first = false;
+            } else {
+              label.append(',');
+            }
+
+            if (label.length() > 0) label.append(' ');
+            label.append(component);
+          }
+        }
+
+        if (label.length() == 0) {
+          label.append(
+            String.format(
+              "[%04X:%04X]",
+              device.getVendorId(),
+              device.getProductId()
+            )
+          );
+        }
+
+        return label.toString();
+      }
+    };
+
+    return makeStringArray(usbDevices, stringMaker);
+  }
+
+  public static void putParameters (Map<String, String> parameters, UsbDevice device) {
+    parameters.put("vendorIdentifier", Integer.toString(device.getVendorId()));
+    parameters.put("productIdentifier", Integer.toString(device.getProductId()));
+
+    if (UsbHelper.obtainPermission(device)) {
+      parameters.put("serialNumber", getSerialNumber(device));
+    }
+  }
+
+  @Override
+  protected final void putParameters (Map<String, String> parameters, String value) {
+    UsbDevice device = usbDeviceMap.get(value);
+    putParameters(parameters, device);
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/NewSpeechParadigm.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/NewSpeechParadigm.java
new file mode 100644
index 0000000..7b34363
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/NewSpeechParadigm.java
@@ -0,0 +1,114 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.speech;
+
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import org.a11y.brltty.android.*;
+
+public class NewSpeechParadigm extends SpeechParadigm {
+  private final static String LOG_TAG = NewSpeechParadigm.class.getName();
+
+  public NewSpeechParadigm () {
+    super();
+  }
+
+  private final Bundle parameters = new Bundle();
+
+  @Override
+  public final NewSpeechParadigm resetParameters () {
+    synchronized (parameters) {
+      parameters.clear();
+    }
+
+    return this;
+  }
+
+  @Override
+  public final NewSpeechParadigm resetParameter (String key) {
+    synchronized (parameters) {
+      parameters.remove(key);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final NewSpeechParadigm setParameter (String key, String value) {
+    synchronized (parameters) {
+      parameters.putString(key, value);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final NewSpeechParadigm setParameter (String key, int value) {
+    synchronized (parameters) {
+      parameters.putInt(key, value);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final NewSpeechParadigm setParameter (String key, float value) {
+    synchronized (parameters) {
+      parameters.putFloat(key, value);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final String getParameter (String key, String defaultValue) {
+    synchronized (parameters) {
+      return parameters.getString(key, defaultValue);
+    }
+  }
+
+  @Override
+  public final int getParameter (String key, int defaultValue) {
+    synchronized (parameters) {
+      return parameters.getInt(key, defaultValue);
+    }
+  }
+
+  @Override
+  public final float getParameter (String key, float defaultValue) {
+    synchronized (parameters) {
+      return parameters.getFloat(key, defaultValue);
+    }
+  }
+
+  @Override
+  public final boolean sayText (TextToSpeech tts, CharSequence text, int queueMode) {
+    int result;
+
+    if (APITests.haveLollipop) {
+      result = tts.speak(text, queueMode, parameters, getUtteranceIdentifier());
+    } else {
+      result = TextToSpeech.ERROR;
+    }
+
+    if (result == TextToSpeech.SUCCESS) return true;
+    logSpeechError("speak", result);
+    return false;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/OldSpeechParadigm.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/OldSpeechParadigm.java
new file mode 100644
index 0000000..231b936
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/OldSpeechParadigm.java
@@ -0,0 +1,106 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.speech;
+
+import android.speech.tts.TextToSpeech;
+import java.util.HashMap;
+import org.a11y.brltty.android.*;
+
+public class OldSpeechParadigm extends SpeechParadigm {
+  private final static String LOG_TAG = OldSpeechParadigm.class.getName();
+
+  public OldSpeechParadigm () {
+    super();
+  }
+
+  private final HashMap<String, String> parameters = new HashMap<>();
+
+  @Override
+  public final OldSpeechParadigm resetParameters () {
+    synchronized (parameters) {
+      parameters.clear();
+    }
+
+    return this;
+  }
+
+  @Override
+  public final OldSpeechParadigm resetParameter (String key) {
+    synchronized (parameters) {
+      parameters.remove(key);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final OldSpeechParadigm setParameter (String key, String value) {
+    synchronized (parameters) {
+      parameters.put(key, value);
+    }
+
+    return this;
+  }
+
+  @Override
+  public final OldSpeechParadigm setParameter (String key, int value) {
+    return setParameter(key, Integer.toString(value));
+  }
+
+  @Override
+  public final OldSpeechParadigm setParameter (String key, float value) {
+    return setParameter(key, Float.toString(value));
+  }
+
+  private final String getParameter (String key) {
+    synchronized (parameters) {
+      return parameters.get(key);
+    }
+  }
+
+  @Override
+  public final String getParameter (String key, String defaultValue) {
+    String string = getParameter(key);
+    if (string == null) return defaultValue;
+    return string;
+  }
+
+  @Override
+  public final int getParameter (String key, int defaultValue) {
+    String string = getParameter(key);
+    if (string == null) return defaultValue;
+    return Integer.valueOf(string);
+  }
+
+  @Override
+  public final float getParameter (String key, float defaultValue) {
+    String string = getParameter(key);
+    if (string == null) return defaultValue;
+    return Float.valueOf(string);
+  }
+
+  @Override
+  public final boolean sayText (TextToSpeech tts, CharSequence text, int queueMode) {
+    int result = tts.speak((String)text, queueMode, parameters);
+    if (result == TextToSpeech.SUCCESS) return true;
+
+    logSpeechError("speak", result);
+    return false;
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechComponent.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechComponent.java
new file mode 100644
index 0000000..32118d0
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechComponent.java
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.speech;
+
+import android.util.Log;
+import org.a11y.brltty.android.*;
+
+public abstract class SpeechComponent {
+  private final static String LOG_TAG = SpeechDriver.class.getName();
+
+  protected SpeechComponent () {
+  }
+
+  protected static void logSpeechError (String action, int result) {
+    Log.w(LOG_TAG, String.format("%s error: %d", action, result));
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechDriver.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechDriver.java
new file mode 100644
index 0000000..1ae5d35
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechDriver.java
@@ -0,0 +1,307 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.speech;
+
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import org.a11y.brltty.android.*;
+
+public class SpeechDriver extends SpeechComponent {
+  private final static String LOG_TAG = SpeechDriver.class.getName();
+
+  protected SpeechDriver () {
+    super();
+  }
+
+  enum EngineState {
+    STOPPED,
+    STARTED,
+    FAILED
+  }
+
+  private final static int maximumTextLength = (
+    APITests.haveJellyBeanMR2?  TextToSpeech.getMaxSpeechInputLength():
+    4000
+  ) - 1;
+
+  private final static SpeechParadigm ttsParadigm =
+    APITests.haveLollipop? new NewSpeechParadigm():
+    new OldSpeechParadigm();
+
+  private static volatile EngineState engineState = EngineState.STOPPED;
+  private static TextToSpeech ttsObject = null;
+
+  private native static void tellLocation (long synthesizer, int location);
+  private native static void tellFinished (long synthesizer);
+
+  private static class Utterance {
+    private final long speechSynthesizer;
+    private final CharSequence utteranceText;
+
+    public Utterance (long synthesizer, CharSequence text) {
+      speechSynthesizer = synthesizer;
+      utteranceText = text;
+    }
+
+    public final long getSynthesizer () {
+      return speechSynthesizer;
+    }
+
+    public final CharSequence getText () {
+      return utteranceText;
+    }
+
+    private int rangeStart = 0;
+    private int rangeEnd = 0;
+
+    public final int getRangeStart () {
+      return rangeStart;
+    }
+
+    public final int getRangeEnd () {
+      return rangeEnd;
+    }
+
+    public final void setRange (int start, int end) {
+      rangeStart = start;
+      rangeEnd = end;
+      tellLocation(getSynthesizer(), start);
+    }
+  }
+
+  private final static Map<CharSequence, Utterance> activeUtterances = new HashMap<>();
+
+  private static Utterance getUtterance (String identifier) {
+    synchronized (activeUtterances) {
+      return activeUtterances.get(identifier);
+    }
+  }
+
+  private static void logUtteranceState (String identifier, String state, boolean unexpected) {
+    StringBuilder log = new StringBuilder();
+    log.append("utterance ").append(state);
+
+    if ((identifier != null) && !identifier.isEmpty()) {
+      log.append(": ").append(identifier);
+      Utterance utterance = getUtterance(identifier);
+
+      if (utterance != null) {
+        String text = utterance.getText().toString();
+        text = Logger.shrinkText(text);
+        log.append(": ").append(text);
+      }
+    }
+
+    if (unexpected) {
+      Log.w(LOG_TAG, log.toString());
+    } else {
+      Log.d(LOG_TAG, log.toString());
+    }
+  }
+
+  private final static UtteranceProgressListener utteranceProgressListener =
+    new UtteranceProgressListener() {
+      private final void onUtteranceIncomplete (String identifier) {
+        synchronized (activeUtterances) {
+          Utterance utterance = activeUtterances.get(identifier);
+          activeUtterances.clear();
+
+          if (utterance != null) {
+            tellFinished(utterance.getSynthesizer());
+          }
+        }
+      }
+
+      // API Level 15 - called when an utterance starts to be processed
+      @Override
+      public final void onStart (String identifier) {
+        logUtteranceState(identifier, "started", false);
+      }
+
+      // API Level 15 - called when an utterance has successfully completed
+      @Override
+      public final void onDone (String identifier) {
+        logUtteranceState(identifier, "finished", false);
+
+        synchronized (activeUtterances) {
+          Utterance utterance = activeUtterances.remove(identifier);
+
+          if (utterance != null) {
+            if (activeUtterances.isEmpty()) {
+              long synthesizer = utterance.getSynthesizer();
+              tellLocation(synthesizer, utterance.getRangeEnd());
+              tellFinished(synthesizer);
+            }
+          }
+        }
+      }
+
+      // API Level 23 - called when an utterance is stopped or flushed
+      @Override
+      public final void onStop (String identifier, boolean wasInterrupted) {
+        logUtteranceState(identifier, (wasInterrupted? "interrupted": "cancelled"), false);
+        onUtteranceIncomplete(identifier);
+      }
+
+      // API Level 15 - called when an error has occurred
+      @Override
+      public final void onError (String identifier) {
+        logUtteranceState(identifier, "error", true);
+        onUtteranceIncomplete(identifier);
+      }
+
+      // API Level 21 - called when an error has occurred
+      @Override
+      public final void onError (String identifier, int code) {
+        logUtteranceState(identifier, ("error " + code), true);
+        onUtteranceIncomplete(identifier);
+      }
+
+      // API Level 24 - called when the engine begins to synthesize a request
+      @Override
+      public final void onBeginSynthesis (String identifier, int rate, int format, int channels) {
+      }
+
+      // API Level 24 - called when a chunk of audio has been generated
+      @Override
+      public final void onAudioAvailable (String identifier, byte[] audio) {
+      }
+
+      // API Level 26 - called when the specified range is about to be spoken
+      @Override
+      public final void onRangeStart (String identifier, int start, int end, int frame) {
+        Utterance utterance = getUtterance(identifier);
+        if (utterance != null) utterance.setRange(start, end);
+      }
+    };
+
+  public static boolean sayText (long synthesizer, CharSequence text) {
+    synchronized (ttsParadigm) {
+      String identifier = ttsParadigm.setUtteranceIdentifier();
+
+      synchronized (activeUtterances) {
+        activeUtterances.put(identifier, new Utterance(synthesizer, text));
+      }
+
+      if (ttsParadigm.sayText(ttsObject, text, TextToSpeech.QUEUE_ADD)) {
+        return true;
+      }
+
+      synchronized (activeUtterances) {
+        activeUtterances.remove(identifier);
+      }
+
+      logUtteranceState(identifier, "start failed", true);
+      return false;
+    }
+  }
+
+  public static boolean stopSpeaking () {
+    int result = ttsObject.stop();
+    if (result == TextToSpeech.SUCCESS) return true;
+
+    logSpeechError("stop speaking", result);
+    return false;
+  }
+
+  public static boolean setVolume (float volume) {
+    ttsParadigm.setParameter(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume);
+    return true;
+  }
+
+  public static boolean setBalance (float balance) {
+    ttsParadigm.setParameter(TextToSpeech.Engine.KEY_PARAM_PAN, balance);
+    return true;
+  }
+
+  public static boolean setRate (float rate) {
+    int result = ttsObject.setSpeechRate(rate);
+    if (result == TextToSpeech.SUCCESS) return true;
+
+    logSpeechError("set rate", result);
+    return false;
+  }
+
+  public static boolean setPitch (float pitch) {
+    int result = ttsObject.setPitch(pitch);
+    if (result == TextToSpeech.SUCCESS) return true;
+
+    logSpeechError("set pitch", result);
+    return false;
+  }
+
+  public static boolean startEngine () {
+    TextToSpeech.OnInitListener listener = new TextToSpeech.OnInitListener() {
+      @Override
+      public void onInit (int status) {
+        synchronized (this) {
+          switch (status) {
+            case TextToSpeech.SUCCESS:
+              engineState = EngineState.STARTED;
+              ttsObject.setOnUtteranceProgressListener(utteranceProgressListener);
+              break;
+
+            default:
+              Log.w(LOG_TAG, ("unknown text to speech engine startup status: " + status));
+            case TextToSpeech.ERROR:
+              engineState = EngineState.FAILED;
+              break;
+          }
+
+          notify();
+        }
+      }
+    };
+
+    synchronized (listener) {
+      Log.d(LOG_TAG, "starting text to speech engine");
+      engineState = EngineState.STOPPED;
+      ttsObject = new TextToSpeech(BrailleApplication.get(), listener);
+
+      while (engineState == EngineState.STOPPED) {
+        try {
+          listener.wait();
+        } catch (InterruptedException exception) {
+        }
+      }
+    }
+
+    if (engineState == EngineState.STARTED) {
+      ttsParadigm.resetParameters();
+      Log.d(LOG_TAG, "text to speech engine started");
+      return true;
+    }
+
+    Log.w(LOG_TAG, "text to speech engine failed");
+    ttsObject = null;
+    return false;
+  }
+
+  public static void stopEngine () {
+    if (ttsObject != null) {
+      Log.d(LOG_TAG, "stopping text to speech engine");
+      ttsObject.shutdown();
+      ttsObject = null;
+      engineState = EngineState.STOPPED;
+    }
+  }
+}
diff --git a/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechParadigm.java b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechParadigm.java
new file mode 100644
index 0000000..3e83d22
--- /dev/null
+++ b/Android/Gradle/app/src/main/java/org/a11y/brltty/android/speech/SpeechParadigm.java
@@ -0,0 +1,62 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.android.speech;
+
+import android.speech.tts.TextToSpeech;
+import org.a11y.brltty.android.*;
+
+public abstract class SpeechParadigm extends SpeechComponent {
+  private final static String LOG_TAG = SpeechParadigm.class.getName();
+
+  protected SpeechParadigm () {
+    super();
+  }
+
+  public abstract SpeechParadigm resetParameters ();
+  public abstract SpeechParadigm resetParameter (String key);
+
+  public abstract SpeechParadigm setParameter (String key, String value);
+  public abstract SpeechParadigm setParameter (String key, int value);
+  public abstract SpeechParadigm setParameter (String key, float value);
+
+  public abstract String getParameter (String key, String defaultValue);
+  public abstract int getParameter (String key, int defaultValue);
+  public abstract float getParameter (String key, float defaultValue);
+
+  public final String getUtteranceIdentifier () {
+    return getParameter(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, null);
+  }
+
+  private final static Object UTTERANCE_IDENTIFIER_LOCK = new Object();
+  private static int utteranceIdentifier = 0;
+
+  public final String newUtteranceIdentifier () {
+    synchronized (UTTERANCE_IDENTIFIER_LOCK) {
+      return "ut#" + ++utteranceIdentifier;
+    }
+  }
+
+  public final String setUtteranceIdentifier () {
+    String identifier = newUtteranceIdentifier();
+    setParameter(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, identifier);
+    return identifier;
+  }
+
+  public abstract boolean sayText (TextToSpeech tts, CharSequence text, int queueMode);
+}
diff --git a/Android/Gradle/app/src/main/play/contact-email.txt b/Android/Gradle/app/src/main/play/contact-email.txt
new file mode 100644
index 0000000..86a4db4
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/contact-email.txt
@@ -0,0 +1 @@
+Dave.A.Mielke@gmail.com
diff --git a/Android/Gradle/app/src/main/play/contact-website.txt b/Android/Gradle/app/src/main/play/contact-website.txt
new file mode 100644
index 0000000..3a992bd
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/contact-website.txt
@@ -0,0 +1 @@
+https://brltty.app/
diff --git a/Android/Gradle/app/src/main/play/default-language.txt b/Android/Gradle/app/src/main/play/default-language.txt
new file mode 100644
index 0000000..b59f16d
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/default-language.txt
@@ -0,0 +1 @@
+en-CA
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/full-description.txt b/Android/Gradle/app/src/main/play/listings/en-CA/full-description.txt
new file mode 100644
index 0000000..45be53a
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/full-description.txt
@@ -0,0 +1,18 @@
+BRLTTY is an accessibility app. It's a screen reader that's been primarily designed for those who use a refreshable braille display (although it also includes support for speech). Detailed documentation for using it on Android is at: https://brltty.app/doc/Android.html
+
+Bluetooth and USB braille devices are supported (USB devices require the use of an OTG cable). Both computer and literary (contracted) braille are supported. Braille tables for many languages are included.
+
+The content of the screen is rendered on the braille display. Standard widgets (check boxes, switches, radio buttons, etc) are rendered in braille-friendly ways. If the braille device has a keyboard then it can be used for typing within an input area.
+
+Many standard Android actions can be performed right from the braille device. These include:
+
+  * Going to the Home screen.
+  * Going Back to the previous screen.
+  * Going to the Overview (Recent Apps) screen.
+  * Going to the Notifications screen.
+  * Going to the Quick Settings screen.
+  * Going to the Device Options (Power Off) screen.
+  * Opening the current app's options menu.
+  * Tapping (clicking) and holding (long pressing) widgets.
+  * Opening a widget's context-dependent menu.
+  * Executing a widget's accessibility actions.
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/feature-graphic/1.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/feature-graphic/1.png
new file mode 100644
index 0000000..f3ce0a8
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/feature-graphic/1.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/icon/1.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/icon/1.png
new file mode 100644
index 0000000..f809bfa
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/icon/1.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/1.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/1.png
new file mode 100644
index 0000000..b3d491a
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/1.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/2.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/2.png
new file mode 100644
index 0000000..557607b
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/large-tablet-screenshots/2.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/1.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/1.png
new file mode 100644
index 0000000..c7e66d8
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/1.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/2.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/2.png
new file mode 100644
index 0000000..70c3131
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/phone-screenshots/2.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/1.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/1.png
new file mode 100644
index 0000000..9565749
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/1.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/2.png b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/2.png
new file mode 100644
index 0000000..933d4e9
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/graphics/tablet-screenshots/2.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/short-description.txt b/Android/Gradle/app/src/main/play/listings/en-CA/short-description.txt
new file mode 100644
index 0000000..8031e6a
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/short-description.txt
@@ -0,0 +1 @@
+Accessibility App - A screen reader for those who use a braille display.
diff --git a/Android/Gradle/app/src/main/play/listings/en-CA/title.txt b/Android/Gradle/app/src/main/play/listings/en-CA/title.txt
new file mode 100644
index 0000000..26cab0a
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/listings/en-CA/title.txt
@@ -0,0 +1 @@
+BRLTTY
diff --git a/Android/Gradle/app/src/main/play/release-notes/en-CA/beta.txt b/Android/Gradle/app/src/main/play/release-notes/en-CA/beta.txt
new file mode 100644
index 0000000..d997023
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/release-notes/en-CA/beta.txt
@@ -0,0 +1,2 @@
+BRLTTY is an accessibility app (it's a screen reader for braille users) so it won't be in the Apps list. To start/stop it, go to Settings -> Accessibility.
+Full details for using BRLTTY on Android are at: https://brltty.app/doc/Android.html
diff --git a/Android/Gradle/app/src/main/play/release-notes/en-CA/production.txt b/Android/Gradle/app/src/main/play/release-notes/en-CA/production.txt
new file mode 100644
index 0000000..d997023
--- /dev/null
+++ b/Android/Gradle/app/src/main/play/release-notes/en-CA/production.txt
@@ -0,0 +1,2 @@
+BRLTTY is an accessibility app (it's a screen reader for braille users) so it won't be in the Apps list. To start/stop it, go to Settings -> Accessibility.
+Full details for using BRLTTY on Android are at: https://brltty.app/doc/Android.html
diff --git a/Android/Gradle/app/src/main/res/drawable-hdpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-hdpi/braille_service.png
new file mode 100644
index 0000000..0bba52e
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-hdpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-ldpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-ldpi/braille_service.png
new file mode 100644
index 0000000..1f903a9
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-ldpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-mdpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-mdpi/braille_service.png
new file mode 100644
index 0000000..bc454fc
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-mdpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-nodpi/braille_notification.png b/Android/Gradle/app/src/main/res/drawable-nodpi/braille_notification.png
new file mode 100644
index 0000000..2f5e5e0
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-nodpi/braille_notification.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-xhdpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-xhdpi/braille_service.png
new file mode 100644
index 0000000..e187b6d
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-xhdpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-xxhdpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-xxhdpi/braille_service.png
new file mode 100644
index 0000000..7bbeafe
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-xxhdpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/drawable-xxxhdpi/braille_service.png b/Android/Gradle/app/src/main/res/drawable-xxxhdpi/braille_service.png
new file mode 100644
index 0000000..0d00ebc
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/drawable-xxxhdpi/braille_service.png
Binary files differ
diff --git a/Android/Gradle/app/src/main/res/layout/about_activity.xml b/Android/Gradle/app/src/main/res/layout/about_activity.xml
new file mode 100644
index 0000000..0c5acba
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/layout/about_activity.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_height="match_parent"
+  android:layout_width="match_parent"
+  >
+
+  <TableRow>
+    <TextView android:text="@string/about_label_copyright" />
+    <TextView android:text="@string/appConfig_copyrightNotice" />
+  </TableRow>
+
+  <TableRow>
+    <TextView android:text="@string/app_name" />
+    <TextView android:id="@+id/app_version_name" />
+  </TableRow>
+
+  <TableRow>
+    <TextView android:text="@string/api_name" />
+    <TextView android:text="@string/appConfig_apiRelease" />
+  </TableRow>
+
+  <TableRow>
+    <TextView android:text="@string/about_label_buildTime" />
+    <TextView android:text="@string/appConfig_buildTime" />
+  </TableRow>
+
+  <TableRow>
+    <TextView android:text="@string/about_label_sourceRevision" />
+    <TextView android:text="@string/appConfig_sourceRevision" />
+  </TableRow>
+
+  <Button
+    android:text="@string/about_label_privacyPolicy"
+    android:onClick="viewPrivacyPolicy"
+   />
+
+  <Button
+    android:text="@string/google_play_label"
+    android:onClick="viewGooglePlay"
+   />
+</TableLayout>
diff --git a/Android/Gradle/app/src/main/res/layout/actions_activity.xml b/Android/Gradle/app/src/main/res/layout/actions_activity.xml
new file mode 100644
index 0000000..a5bf237
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/layout/actions_activity.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_height="match_parent"
+  android:layout_width="match_parent"
+  android:orientation="vertical"
+  >
+
+  <TableLayout
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    >
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_SWITCH_INPUT_METHOD"
+      android:onClick="switchInputMethod"
+     />
+
+    <Button
+      android:text="@string/SETTINGS_SCREEN_MAIN"
+      android:onClick="launchSettingsActivity"
+     />
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_VIEW_USER_GUIDE"
+      android:onClick="viewUserGuide"
+     />
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_BROWSE_WEB_SITE"
+      android:onClick="browseWebSite"
+     />
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES"
+      android:onClick="browseCommunityMessages"
+     />
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE"
+      android:onClick="postCommunityMessage"
+     />
+
+    <Button
+      android:text="@string/GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP"
+      android:onClick="manageCommunityMembership"
+     />
+
+    <Button
+      android:id="@+id/GLOBAL_BUTTON_UPDATE_APPLICATION"
+      android:text="@string/GLOBAL_BUTTON_UPDATE_APPLICATION"
+      android:onClick="updateApplication"
+     />
+
+    <CheckBox
+      android:id="@+id/GLOBAL_CHECKBOX_DEVELOPER_BUILD"
+      android:text="@string/GLOBAL_CHECKBOX_DEVELOPER_BUILD"
+     />
+
+    <CheckBox
+      android:id="@+id/GLOBAL_CHECKBOX_ALLOW_DOWNGRADE"
+      android:text="@string/GLOBAL_CHECKBOX_ALLOW_DOWNGRADE"
+     />
+
+    <Button
+      android:text="@string/about_label_activity"
+      android:onClick="launchAboutActivity"
+     />
+  </TableLayout>
+</ScrollView>
diff --git a/Android/Gradle/app/src/main/res/layout/chooser.xml b/Android/Gradle/app/src/main/res/layout/chooser.xml
new file mode 100644
index 0000000..5a01368
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/layout/chooser.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_height="match_parent"
+  android:layout_width="match_parent"
+  >
+
+  <TextView
+    android:id="@+id/chooser_title"
+    android:textStyle="bold"
+   />
+
+  <ListView
+    android:id="@+id/chooser_list"
+   />
+
+  <Button
+    android:id="@+id/chooser_dismiss"
+    android:text="@android:string/cancel"
+   />
+</TableLayout>
diff --git a/Android/Gradle/app/src/main/res/layout/file_downloader.xml b/Android/Gradle/app/src/main/res/layout/file_downloader.xml
new file mode 100644
index 0000000..590b236
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/layout/file_downloader.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_height="match_parent"
+  android:layout_width="match_parent"
+  android:orientation="vertical"
+  >
+
+  <TextView android:id="@+id/state" />
+
+  <TableLayout
+    android:id="@+id/progress"
+    android:visibility="gone"
+    android:padding="25dp"
+    android:stretchColumns="1"
+    >
+
+    <ProgressBar android:id="@+id/bar" />
+
+    <TableRow>
+      <TextView android:id="@+id/current" android:gravity="left" />
+      <Space />
+      <TextView android:id="@+id/remaining" android:gravity="right" />
+    </TableRow>
+  </TableLayout>
+</TableLayout>
diff --git a/Android/Gradle/app/src/main/res/values-ar/strings.xml b/Android/Gradle/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..f01f575
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">يدعم BRLTTY أجهزة برايل المختلفة.</string>
+
+  <string name="about_label_activity">حول التطبيق</string>
+  <string name="about_label_copyright">حقوق النشر</string>
+  <string name="about_label_buildTime">وقت الإنشاء</string>
+  <string name="about_label_sourceRevision">مراجعة المصدر</string>
+  <string name="about_label_privacyPolicy">سياسة الخصوصية</string>
+
+  <string name="inputService_name">خدمة الإدخال في BRLTTY</string>
+  <string name="inputService_type">لوحات المفاتيح على أجهزة برايل</string>
+  <string name="inputService_not_enabled">خدمة الإدخال غير مُفعَّلة</string>
+  <string name="inputService_not_selected">خدمة الإدخال غير محددة</string>
+  <string name="inputService_not_started">خدمة الإدخال لم تبدأ</string>
+  <string name="inputService_not_connected">خدمة الإدخال غير متصلة</string>
+
+  <string name="updateApplication_problem_failed">لم يتم تحديث التطبيق</string>
+
+  <string name="packageInstaller_problem_failed">الحزمة غير مثبتة</string>
+  <string name="packageInstaller_problem_same">نفس الإصدار</string>
+  <string name="packageInstaller_problem_downgrade">الرجوع إلى إصدار أقدم غير مسموح به</string>
+
+  <string name="fileDownloader_title">أداة تنزيل الملفات من BRLTTY</string>
+  <string name="fileDownloader_problem_failed">لم يتم تنزيل الملف</string>
+  <string name="fileDownloader_state_connecting">يجري الاتصال</string>
+  <string name="fileDownloader_state_downloading">يجري التحميل</string>
+
+  <string name="setting_state_off">معطل</string>
+  <string name="checkbox_state_unchecked">لا</string>
+  <string name="checkbox_state_checked">نعم</string>
+
+  <string name="braille_channel_name">حالة جهاز برايل</string>
+  <string name="braille_hint_tap">انقر للحصول على الإجراءات</string>
+  <string name="braille_state_released">مفرغ</string>
+  <string name="braille_state_waiting">قيد الانتظار</string>
+  <string name="braille_state_connected">متصل</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">الإشعارات</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">إعدادات سريعة</string>
+  <string name="GLOBAL_BUTTON_BACK">رجوع</string>
+  <string name="GLOBAL_BUTTON_HOME">الشاشة الرئيسية</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">التطبيقات الحديثة</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">نظرة عامة</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">تبديل طريقة الإدخال</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">عرض دليل المستخدم</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">تصفح الموقع</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">تصفح رسائل المستخدمين</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">إرسال رسالة للمستخدمين</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">إدارة العضوية في مجتمع المستخدمين</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">تحديث التطبيق</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">نسخة للمطورين</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">السماح بالعودة للنسخ الأقدم</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">إجراآت إمكانية الوصول</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">إعدادات BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">الاعدادات العامة</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">إعدادات الرسائل</string>
+  <string name="SETTINGS_SCREEN_DEVICES">إدارة الأجهزة</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">إعدادات متقدمة</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">فصل جهاز برايل</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">نمط التنقل</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">دعم النطق</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">جدول النص</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">جدول السمات</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">جدول الاختصارات</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">جدول لوحة المفاتيح</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">عرض التنبيهات</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">عرض الإعلانات</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">عرض الإشعارات</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">مستوى السجل</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">أقسام السجل</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">سجل أحداث إمكانية الوصول</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">تسجيل الشاشة المعروضة</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">سجل أحداث لوحات المفاتيح</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">سجل الأحداث غير المعالجة</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">الجهاز المحدد</string>
+  <string name="DEVICES_BUTTON_ADD">إضافة جهاز</string>
+  <string name="DEVICES_BUTTON_REMOVE">إزالة جهاز</string>
+
+  <string name="SELECTED_DEVICE_NONE">لا توجد أجهزة</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">الجهاز غير محدد</string>
+
+  <string name="ADD_DEVICE_NAME">اسم الجهاز</string>
+  <string name="ADD_DEVICE_METHOD">طريقة الاتصال</string>
+  <string name="ADD_DEVICE_SELECT">اختيار جهاز</string>
+  <string name="ADD_DEVICE_DRIVER">سائقة برايل</string>
+  <string name="ADD_DEVICE_DONE">تم</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">طريقة الاتصال غير محددة</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">الجهاز غير محدد</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">لم يتم تحديد سائقة تشغيل برايل</string>
+  <string name="ADD_DEVICE_NO_DEVICES">لا توجد أجهزة</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">اسم الجهاز قيد الاستخدام بالفعل</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">لم يتم منح صلاحية الوصول إلى الجهاز</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">هل ترغب حقًا في إزالة هذا الجهاز؟</string>
+
+  <string name="SET_SELECTION_NONE">لم يتم تحديد أي شيء</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">قائمة عمودية</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">شبكة ثنائية الأبعاد</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">محلي</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">بلوتوث</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">الكابل التسلسلي Serial</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">تصحيح الأخطاء</string>
+  <string name="LOG_LEVEL_LABEL_information">معلومات</string>
+  <string name="LOG_LEVEL_LABEL_notice">ملاحظات</string>
+  <string name="LOG_LEVEL_LABEL_warning">تحذيرات</string>
+  <string name="LOG_LEVEL_LABEL_error">أخطاء</string>
+  <string name="LOG_LEVEL_LABEL_critical">أخطاء حرجة</string>
+  <string name="LOG_LEVEL_LABEL_alert">تنبيهات</string>
+  <string name="LOG_LEVEL_LABEL_emergency">طوارئ</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">حزم الإدخال</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">حزم العرض</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">أحداث مفاتيح جهاز برايل</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">أحداث أزرار لوحة المفاتيح</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">تعقب المؤشر</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">توجيه المؤشر</string>
+  <string name="LOG_CATEGORY_LABEL_update">تحديث الأحداث</string>
+  <string name="LOG_CATEGORY_LABEL_speech">أحداث النطق</string>
+  <string name="LOG_CATEGORY_LABEL_async">أحداث غير متزامنة</string>
+  <string name="LOG_CATEGORY_LABEL_server">أحداث الخادم</string>
+  <string name="LOG_CATEGORY_LABEL_gio">عام I / O</string>
+  <string name="LOG_CATEGORY_LABEL_serial">التسلسل I / O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">واجهة العرض I/O</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">أحداث سائقة برايل</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">أحداث سائقة النطق</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">أحداث سائقة الشاشة</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">الاكتشاف التلقائي</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">الاختيار التلقائي على أساس اللغة</string>
+  <string name="TEXT_TABLE_LABEL_ar">عربي (عام)</string>
+  <string name="TEXT_TABLE_LABEL_as">الأسامية</string>
+  <string name="TEXT_TABLE_LABEL_awa">الأوادية</string>
+  <string name="TEXT_TABLE_LABEL_bg">البلغارية</string>
+  <string name="TEXT_TABLE_LABEL_bh">البيهارية</string>
+  <string name="TEXT_TABLE_LABEL_bn">البنغالية</string>
+  <string name="TEXT_TABLE_LABEL_bo">التبتية</string>
+  <string name="TEXT_TABLE_LABEL_bra">البراغية</string>
+  <string name="TEXT_TABLE_LABEL_brf">تنسيق برايل الجاهز (لعرض ملفات .brf داخل تطبيقات تحرير المستندات)</string>
+  <string name="TEXT_TABLE_LABEL_cs">التشيكية</string>
+  <string name="TEXT_TABLE_LABEL_cy">الولزية</string>
+  <string name="TEXT_TABLE_LABEL_da">الدنمركية</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">الدنمركية (سفيند ثوغارد ، 2002-11-18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">الدنماركية (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">الألمانية</string>
+  <string name="TEXT_TABLE_LABEL_dra">درافيدان</string>
+  <string name="TEXT_TABLE_LABEL_el">اليونانية</string>
+  <string name="TEXT_TABLE_LABEL_en">الإنجليزية</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">الإنجليزية (كندا)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">الإنجليزية (المملكة المتحدة)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">الإنجليزية (أكواد برايل الحاسوبية لأمريكا الشمالية)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">الإنجليزية (الولايات المتحدة)</string>
+  <string name="TEXT_TABLE_LABEL_eo">اسبرانتو</string>
+  <string name="TEXT_TABLE_LABEL_es">الأسبانية</string>
+  <string name="TEXT_TABLE_LABEL_et">الإستونية</string>
+  <string name="TEXT_TABLE_LABEL_fi">الفنلندية</string>
+  <string name="TEXT_TABLE_LABEL_fr">الفرنسية</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">الفرنسية (الموحدة 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">الفرنسية (كندا)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">الفرنسية (رمز برايل للكمبيوتر الفرنسي القياسي)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">الفرنسية (فرنسا)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">الفرنسية (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">الايرلندية</string>
+  <string name="TEXT_TABLE_LABEL_gd">الغيلية</string>
+  <string name="TEXT_TABLE_LABEL_gon">الغاندية</string>
+  <string name="TEXT_TABLE_LABEL_gu">الغوجاراتية</string>
+  <string name="TEXT_TABLE_LABEL_he">العبرية</string>
+  <string name="TEXT_TABLE_LABEL_hi">الهندية</string>
+  <string name="TEXT_TABLE_LABEL_hr">الكرواتية</string>
+  <string name="TEXT_TABLE_LABEL_hu">المجرية</string>
+  <string name="TEXT_TABLE_LABEL_hy">الأرمينية</string>
+  <string name="TEXT_TABLE_LABEL_is">الأيسلاندية</string>
+  <string name="TEXT_TABLE_LABEL_it">الإيطالية</string>
+  <string name="TEXT_TABLE_LABEL_kha">الكازاخية</string>
+  <string name="TEXT_TABLE_LABEL_kn">الكانادا</string>
+  <string name="TEXT_TABLE_LABEL_kok">الكونكانية</string>
+  <string name="TEXT_TABLE_LABEL_kru">الكوروخ</string>
+  <string name="TEXT_TABLE_LABEL_lt">الليتوانية</string>
+  <string name="TEXT_TABLE_LABEL_lv">اللاتفية</string>
+  <string name="TEXT_TABLE_LABEL_mg">المدغشقرية</string>
+  <string name="TEXT_TABLE_LABEL_mi">الماوري</string>
+  <string name="TEXT_TABLE_LABEL_ml">المالايالامية</string>
+  <string name="TEXT_TABLE_LABEL_mni">المانيبوري</string>
+  <string name="TEXT_TABLE_LABEL_mr">الماراثى</string>
+  <string name="TEXT_TABLE_LABEL_mt">المالطية</string>
+  <string name="TEXT_TABLE_LABEL_mun">الموندا</string>
+  <string name="TEXT_TABLE_LABEL_mwr">المرواري</string>
+  <string name="TEXT_TABLE_LABEL_ne">النيبالية</string>
+  <string name="TEXT_TABLE_LABEL_new">النيوري</string>
+  <string name="TEXT_TABLE_LABEL_nl">الهولندية</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">الهولندية (بلجيكا)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">الهولندية (هولندا)</string>
+  <string name="TEXT_TABLE_LABEL_no">النرويجية</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">النرويجية (مع دعم للغات أخرى)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">النرويجية (الجمعية العامة لبرايل)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">النيواري (قديم)</string>
+  <string name="TEXT_TABLE_LABEL_or">الأوريا</string>
+  <string name="TEXT_TABLE_LABEL_pa">البنجابي</string>
+  <string name="TEXT_TABLE_LABEL_pi">البالي</string>
+  <string name="TEXT_TABLE_LABEL_pl">البولندية</string>
+  <string name="TEXT_TABLE_LABEL_pt">البرتغالية</string>
+  <string name="TEXT_TABLE_LABEL_ro">الرومانية</string>
+  <string name="TEXT_TABLE_LABEL_ru">الروسية</string>
+  <string name="TEXT_TABLE_LABEL_sa">السنسكريتية</string>
+  <string name="TEXT_TABLE_LABEL_sat">السنتالية</string>
+  <string name="TEXT_TABLE_LABEL_sd">السندية</string>
+  <string name="TEXT_TABLE_LABEL_se">السامي (الشمالية)</string>
+  <string name="TEXT_TABLE_LABEL_sk">السلوفاكية</string>
+  <string name="TEXT_TABLE_LABEL_sl">السلوفينية</string>
+  <string name="TEXT_TABLE_LABEL_sv">السويدية</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">السويدية (معيار 1989)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">السويدية (معيار 1996)</string>
+  <string name="TEXT_TABLE_LABEL_sw">السواحيلية</string>
+  <string name="TEXT_TABLE_LABEL_ta">التاميل</string>
+  <string name="TEXT_TABLE_LABEL_te">التيلجو</string>
+  <string name="TEXT_TABLE_LABEL_tr">التركية</string>
+  <string name="TEXT_TABLE_LABEL_uk">الأوكرانية</string>
+  <string name="TEXT_TABLE_LABEL_vi">الفيتنامية</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">عكس لون المقدمة في العمود الأيسر ولون الخلفية في العمود الأيمن</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">لون المقدمة في العمود الأيسر ولون الخلفية في العمود الأيمن</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">لون المقدمة في المربع العلوي ولون الخلفية في المربع السفلي</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">الاختيار التلقائي على أساس اللغة</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">الأفريكانية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">الأمهرية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">الألمانية</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">الألمانية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">الألمانية (الاختصارات الأساسية)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">الألمانية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">الألمانية (مختصر  - وفقا لمعايير 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">الألمانية (مختصر  - معيار 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">الإنجليزية</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">الإنجليزية (الولايات المتحدة)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">الإنجليزية (الموحد، مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">الإنجليزية (الولايات المتحدة ، مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">الإسبانية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">الفرنسية</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">الفرنسية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">الفرنسية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">الهوسا (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">الأندونيسية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">الأبجدية الصوتية الدولية</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">اليابانية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">الكورية</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">الكورية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">الكورية (مختصر جزئيا)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">الكورية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">الليتوانية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">المدغشقرية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">الموندا (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">الهولندية مختصرمتعاقد)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">الشيشيوا (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">البرتغالية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">الروسية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">السنهالية (غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">السواحيلية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">التايلاندية (مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">الصينية (تايوان ، غير مختصر)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">الزولو (مختصر)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">روابط للوحات مفاتيح برايل</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">روابط للوحات المفاتيح الكاملة</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">ارتباطات للتنقل عبر اللوحة الرقمية</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">ارتباطات للوحات المفاتيح التي ليس بها لوحة رقمية</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">روابط للوحات مفاتيح Sun Type 6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-cs/strings.xml b/Android/Gradle/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..d901da2
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY podporuje braillské řádky.</string>
+
+  <string name="usbMonitor_label">Připojený monitor zařízení USB</string>
+
+  <string name="about_label_activity">O aplikaci</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Čas sestavení</string>
+  <string name="about_label_sourceRevision">Revize zdrojového kódu</string>
+  <string name="about_label_privacyPolicy">Zásady zpracování osobních údajů</string>
+
+  <string name="inputService_name">Služba vstupu BRLTTY</string>
+  <string name="inputService_type">Klávesnice braillských zařízení</string>
+  <string name="inputService_not_enabled">služba vstupu nepovolena</string>
+  <string name="inputService_not_selected">služba vstupu nevybrána</string>
+  <string name="inputService_not_started">služba vstupu nespuštěna</string>
+  <string name="inputService_not_connected">služba vstupu nepřipojena</string>
+
+  <string name="updateApplication_problem_failed">aplikace nebyla aktualizována</string>
+
+  <string name="packageInstaller_problem_failed">balíček nebyl nainstalován</string>
+  <string name="packageInstaller_problem_same">shodná verze</string>
+  <string name="packageInstaller_problem_downgrade">instalace starší verze není možná</string>
+
+  <string name="fileDownloader_title">Nástroj stahování BRLTTY</string>
+  <string name="fileDownloader_problem_failed">soubor nebyl stažen</string>
+  <string name="fileDownloader_state_connecting">připojování</string>
+  <string name="fileDownloader_state_downloading">stahování</string>
+
+  <string name="setting_state_off">Vypnuto</string>
+  <string name="checkbox_state_unchecked">Vypnuto</string>
+  <string name="checkbox_state_checked">Zapnuto</string>
+
+  <string name="braille_channel_name">Stav braillského zařízení</string>
+  <string name="braille_hint_tap">klepněte pro akce</string>
+  <string name="braille_state_released">Uvolněno</string>
+  <string name="braille_state_waiting">Čeká</string>
+  <string name="braille_state_connected">Připojeno</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Oznámení</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Rychlé nastavení</string>
+  <string name="GLOBAL_BUTTON_BACK">Zpět</string>
+  <string name="GLOBAL_BUTTON_HOME">Domů</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Poslední aplikace</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Poslední aplikace</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Přepnout metodu vstupu</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Zobrazit uživatelskou příručku</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Otevřít webovou stránku</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Projít zprávy komunity</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Napsat zprávu komunitě</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Spravovat členství v komunitě</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Aktualizovat aplikaci</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Vývojová verze</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Povolit přechod na nižší verzi</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Akce zpřístupnění</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Nastavení BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Obecná nastavení</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Nastavení zpráv</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Správa zařízení</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Pokročilá nastavení</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Uvolnit braillské zařízení</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Režim navigace</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Hlasová podpora</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Tabulka pro převod textu</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Tabulka atributů</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Tabulka zkratkopisu</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tabulka klávesnice</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Zobrazovat výstrahy</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Zobrazovat hlášení</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Zobrazovat oznámení</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Úroveň protokolu</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Kategorie protokolu událostí</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Zaznamenávat události usnadnění</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Zaznamenávat vykreslený obsah obrazovky</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Zaznamenávat události klávesnice</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Zaznamenávat nezpracované události</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Vybrané zařízení</string>
+  <string name="DEVICES_BUTTON_ADD">Přidat zařízení</string>
+  <string name="DEVICES_BUTTON_REMOVE">Odebrat zařízení</string>
+
+  <string name="SELECTED_DEVICE_NONE">Žádné zařízení</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">Zařízení nevybráno</string>
+
+  <string name="ADD_DEVICE_NAME">Jméno zařízení</string>
+  <string name="ADD_DEVICE_METHOD">Způsob komunikace</string>
+  <string name="ADD_DEVICE_SELECT">Vybrat zařízení</string>
+  <string name="ADD_DEVICE_DRIVER">Braillský ovladač</string>
+  <string name="ADD_DEVICE_DONE">Dokončit</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">Způsob komunikace nevybrán</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">Zařízení nevybráno</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">Braillský ovladač nevybrán</string>
+  <string name="ADD_DEVICE_NO_DEVICES">Žádná zařízení</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">Jméno zařízení se již používá</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">přístup k zařízení nebyl udělen</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Opravdu si přejete odebrat toto zařízení?</string>
+
+  <string name="SET_SELECTION_NONE">Nic nevybráno</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Svislý seznam</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Dvourozměrná mřížka</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Nativní</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Sériová</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Ladění</string>
+  <string name="LOG_LEVEL_LABEL_information">Informace</string>
+  <string name="LOG_LEVEL_LABEL_notice">Upozornění</string>
+  <string name="LOG_LEVEL_LABEL_warning">Varování</string>
+  <string name="LOG_LEVEL_LABEL_error">Chyby</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritické</string>
+  <string name="LOG_LEVEL_LABEL_alert">Výstrahy</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Nouzové</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Vstupní pakety</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Výstupní pakety</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Události klávesnice braillského zařízení</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Události klávesnice</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Sledování kurzoru</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Navádění kurzoru</string>
+  <string name="LOG_CATEGORY_LABEL_update">Události aktualizací</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Události hlasu</string>
+  <string name="LOG_CATEGORY_LABEL_async">Asynchronní události</string>
+  <string name="LOG_CATEGORY_LABEL_server">Události serveru</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Obecný Vstup/Výstup</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Sériový Vstup/Výstup</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB Vstup/Výstup</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth Vstup/Výstup</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Vstup/Výstup Human Interface</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Události braillského ovladače</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Události ovladače hlasu</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Události ovladače obrazovky</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Autodetekce</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Automatický výběr dle národního prostředí</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabština (obecná)</string>
+  <string name="TEXT_TABLE_LABEL_as">Ásámština</string>
+  <string name="TEXT_TABLE_LABEL_awa">Avadhí</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulharština</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihárské jazyky</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengálština</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetština</string>
+  <string name="TEXT_TABLE_LABEL_bra">Bradžština</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (pro zobrazení souborů .brf v editoru či pageru)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Čeština</string>
+  <string name="TEXT_TABLE_LABEL_cy">Velština</string>
+  <string name="TEXT_TABLE_LABEL_da">Dánština</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Dánština (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Dánština (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Němčina</string>
+  <string name="TEXT_TABLE_LABEL_dra">Drávidské jazyky</string>
+  <string name="TEXT_TABLE_LABEL_el">Řečtina</string>
+  <string name="TEXT_TABLE_LABEL_en">Angličtina</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Angličtina (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Angličtina (Spojené Království)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Angličtina (North American Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Angličtina (Spojené státy)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Španělština</string>
+  <string name="TEXT_TABLE_LABEL_et">Estonština</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finština</string>
+  <string name="TEXT_TABLE_LABEL_fr">Francouzština</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Francouzština (unified 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Francouzština (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Francouzština (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Francouzština (Francie)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Francouzština (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irština</string>
+  <string name="TEXT_TABLE_LABEL_gd">Skotská gaelština</string>
+  <string name="TEXT_TABLE_LABEL_gon">Góndí</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gudžarátština</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebrejština</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindština</string>
+  <string name="TEXT_TABLE_LABEL_hr">Chorvatština</string>
+  <string name="TEXT_TABLE_LABEL_hu">Maďarština</string>
+  <string name="TEXT_TABLE_LABEL_hy">Arménština</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandština</string>
+  <string name="TEXT_TABLE_LABEL_it">Italština</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannadština</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkanština</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litevština</string>
+  <string name="TEXT_TABLE_LABEL_lv">Lotyština</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malgaština</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maorština</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malajálamština</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipurština</string>
+  <string name="TEXT_TABLE_LABEL_mr">Maráthština</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltština</string>
+  <string name="TEXT_TABLE_LABEL_mun">Mundské jazyky</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Márvárí</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepálština</string>
+  <string name="TEXT_TABLE_LABEL_new">Nevarština</string>
+  <string name="TEXT_TABLE_LABEL_nl">Nizozemština</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Nizozemština (Belgie)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Nizozemština (Nizozemí)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norština</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norština (s podporou ostatních jazyků)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norština (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Klasická nevarština (stará)</string>
+  <string name="TEXT_TABLE_LABEL_or">Urijština</string>
+  <string name="TEXT_TABLE_LABEL_pa">Paňdžábština</string>
+  <string name="TEXT_TABLE_LABEL_pi">Páli</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polština</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugalština</string>
+  <string name="TEXT_TABLE_LABEL_ro">Rumunština</string>
+  <string name="TEXT_TABLE_LABEL_ru">Ruština</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrt</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santálština</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhština</string>
+  <string name="TEXT_TABLE_LABEL_se">Sámština (severní)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovenština</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slovinština</string>
+  <string name="TEXT_TABLE_LABEL_sv">Švédština</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Švédština (standard 1989)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Švédština (standard 1996)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Svahilština</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamilština</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugština</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turečtina</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ukrajinština</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamština</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">Invertovaná (barva popředí v levém sloupci a barva pozadí v pravém sloupci)</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">Barva popředí v levém sloupci a barva pozadí v pravém sloupci</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">Barva popředí v horním čtyřbodu a barva pozadí v dolním čtyřbodu</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Automatický výběr dle národního prostředí</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikánština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Němčina</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Němčina (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Němčina (základní zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Němčina (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Němčina (zkratkopis - standart 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Němčina (zkratkopis - standart 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Angličtina</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Angličtina (Spojené státy)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Angličtina (Unified, plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Angličtina (Unified English Braille, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Angličtina (US, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Španělština (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Francouzština</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Francouzština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Francouzština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hauština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonéština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">International Phonetic Alphabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japonština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Korejština</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Korejština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Korejština (grade 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Korejština (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Litevština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malgaština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Mundské jazyky (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Nizozemština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Čičeva (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugalština (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Ruština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalština (plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Svahilština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thajština (zkratkopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Čínština (Tchaj-wan, plnopis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulština (zkratkopis)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">Pravidla pro braillské klávesnice</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">Pravidla pro standardní klávesnice</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">Pravidla pro klávesnice telefonů</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">Pravidla pro klávesnice notebooků</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">Pravidla pro klávesnice typu sun type6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-da/strings.xml b/Android/Gradle/app/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..88c5e05
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-da/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY understøtter braille enheder.</string>
+
+  <string name="usbMonitor_label">USB Enhed Tilsluttet Skærm</string>
+
+  <string name="about_label_activity">Om Applikationen</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Kompileringstidspunkt</string>
+  <string name="about_label_sourceRevision">Kildekode Revision</string>
+  <string name="about_label_privacyPolicy">Privatlivspolitik</string>
+
+  <string name="inputService_name">BRLTTY Input Service</string>
+  <string name="inputService_type">Tastaturer på Braille enheder</string>
+  <string name="inputService_not_enabled">input service ikke tilgængelig</string>
+  <string name="inputService_not_selected">input service ikke valgt</string>
+  <string name="inputService_not_started">input service ikke startet</string>
+  <string name="inputService_not_connected">input service ikke forbundet</string>
+
+  <string name="updateApplication_problem_failed">applikation blev ikke opdateret</string>
+
+  <string name="packageInstaller_problem_failed">pakke ikke installet</string>
+  <string name="packageInstaller_problem_same">samme version</string>
+  <string name="packageInstaller_problem_downgrade">nedgradering ikke tilladt</string>
+
+  <string name="fileDownloader_title">Fil Downloader</string>
+  <string name="fileDownloader_problem_failed">fil ikke hentet</string>
+  <string name="fileDownloader_state_connecting">forbinder</string>
+  <string name="fileDownloader_state_downloading">henter</string>
+
+  <string name="setting_state_off">Fra</string>
+  <string name="checkbox_state_unchecked">Nej</string>
+  <string name="checkbox_state_checked">Ja</string>
+
+  <string name="braille_channel_name">Braille Enhed Status</string>
+  <string name="braille_hint_tap">tryk for handlinger</string>
+  <string name="braille_state_released">Frigivet</string>
+  <string name="braille_state_waiting">Venter</string>
+  <string name="braille_state_connected">Forbindet</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Notifikationer</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Hurtig Indstillinger</string>
+  <string name="GLOBAL_BUTTON_BACK">Tilbage</string>
+  <string name="GLOBAL_BUTTON_HOME">Hjem</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Seneste Apps</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Overblik</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Skift Input Metode</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Vis Bruger Vejledning</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Læs Webside</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Læs Fællesskabs Beskeder</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Skriv Fællesskabs Besked</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Ændre Fællesskabs Indstillinger</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Updater Applikation</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Udvikler version</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Tillad nedgradering</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Tilgængelighedshandlinger</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Indstillinger</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Generalle indstillinger</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Besked Indstillinger</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Håndter enheder</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Advancerede indstillinger</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Udgave Braille Enhed</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigationstilstand</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Taleunderstøttelse</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Teksttabel</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributtabel</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Forkortelsestabel</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tastaturtabel</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Vis Alarmer</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Vis Meddelelser</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Vis Notofikationer</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Log Niveau</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Log Kategori</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Log Tilgængelighedshændelser</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Log Fremkaldt skærm</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Log Tastaturhændelser</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Log Ikke-håndterende Hændelser</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Valgte enhed</string>
+  <string name="DEVICES_BUTTON_ADD">Tilføj enhed</string>
+  <string name="DEVICES_BUTTON_REMOVE">Fjern enhed</string>
+
+  <string name="SELECTED_DEVICE_NONE">Ingen enheder</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">Enhed ikke valgt</string>
+
+  <string name="ADD_DEVICE_NAME">Enhedsnavn</string>
+  <string name="ADD_DEVICE_METHOD">Kommunikationsmetode</string>
+  <string name="ADD_DEVICE_SELECT">Vælg enhed</string>
+  <string name="ADD_DEVICE_DRIVER">Braille driver</string>
+  <string name="ADD_DEVICE_DONE">Udført</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">kommunikationsmetode ikke valgt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">enhed ikke valgt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">braille driver ikke valgt</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ingen enheder</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">enhedsnavn allerede i brug</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">ikke givet adgang til enheden</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Vil du virkelig gerne fjerne denne enhed ?</string>
+
+  <string name="SET_SELECTION_NONE">ingen valgt</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Vertikal liste</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">To dimensionelt gitter</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Medfødt</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Seriel</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Aflus</string>
+  <string name="LOG_LEVEL_LABEL_information">Information</string>
+  <string name="LOG_LEVEL_LABEL_notice">Opmærksomhed</string>
+  <string name="LOG_LEVEL_LABEL_warning">Advarsel</string>
+  <string name="LOG_LEVEL_LABEL_error">Fejl</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kirtisk</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alarm</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Uheld</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Input pakker</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Output pakker</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Braille enhed nøglehændelser</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Tastatur nøglehændelser</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Markør følgning</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Markør ruteplanlægning</string>
+  <string name="LOG_CATEGORY_LABEL_update">Opdateringshændelser</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Talehændelser</string>
+  <string name="LOG_CATEGORY_LABEL_async">Asynkrone hændelser</string>
+  <string name="LOG_CATEGORY_LABEL_server">Server hændelser</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Generisk I/O</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serielt I/O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Menneske Grænseflade I/O</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Braille-driver hændelser</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Tale-driver hændelser</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Skærm-river hændelser</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">autofind</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">lokal-baseret autovælg</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabisk (generisk)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamese</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgarsk</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetansk</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (for at vise .brf files i en editor eller fremviser)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Tjekkisk</string>
+  <string name="TEXT_TABLE_LABEL_cy">Welsk</string>
+  <string name="TEXT_TABLE_LABEL_da">Dansk</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Dansk (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Dansk (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Tysk</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidian</string>
+  <string name="TEXT_TABLE_LABEL_el">Græsk</string>
+  <string name="TEXT_TABLE_LABEL_en">Engelsk</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Engelsk (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Engelsk (United Kingdom)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Engelsk (Nord Amerikansk Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Engelsk (United States)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Spansk</string>
+  <string name="TEXT_TABLE_LABEL_et">Estisk</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finsk</string>
+  <string name="TEXT_TABLE_LABEL_fr">Fransk</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Fransk (unified 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Fransk (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Fransk (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Fransk (Frankrig)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Fransk (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irsk</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaelic</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebraisk</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Kroatisk</string>
+  <string name="TEXT_TABLE_LABEL_hu">Ungarnsk</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armenisk</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandsk</string>
+  <string name="TEXT_TABLE_LABEL_it">Italiensk</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litauisk</string>
+  <string name="TEXT_TABLE_LABEL_lv">Lettisk</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltese</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepalesisk</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Belgisk</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Belgisk (Belgien)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Hollansk (Holland)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norsk</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norsk (med understøttelse for andre sprog)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norsk (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (old)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polsk</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugisisk</string>
+  <string name="TEXT_TABLE_LABEL_ro">Romansk</string>
+  <string name="TEXT_TABLE_LABEL_ru">Russisk</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Samisk (Nordsamisk)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovakisk</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slovensk</string>
+  <string name="TEXT_TABLE_LABEL_sv">Svensk</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Svensk (1989 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Svensk (1996 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Tyrkisk</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ukraiensk</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamesisk</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">inverteret forgrundsfarve i venstre søjle og baggrundsfarve i højre søjle</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">forgrundsfarve i venstre søjle og baggrundsfarve i højre søjle</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">forgrundsfarve i øverste firkant og baggrundsfarve i nederste firkant</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">lokal-baseret autovælg</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikaans (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharic (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Tysk</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Tysk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Tysk (basis forkortelser)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Tysk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Tysk (forkortet - 1998 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Tysk (forkortet - 2015 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Engelsk</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Engelsk (Forenede stater)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Engelsk (Samlet, uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Engelsk (Unified English Braille, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Engelsk (US, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Spansk (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Fransk</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Fransk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Fransk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesisk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Intertionale aonetiske alfabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japansk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Koreansk</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Koreansk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Koreansk (grade 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Koreansk (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Litauisk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malaisisk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Belgisk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugiskisk (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Russisk (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalese (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thailandsk (Forkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Kinesisk (Taiwan, uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (forkortet)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">genveje for braille tastaturer</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">genveje for hele tastaturer</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">genveje for taste-baseret navigation</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">genveje for tastaturer uden nummerisk tastatur</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">genveje for Sun Type 6 tastaturer</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-de/strings.xml b/Android/Gradle/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..fe16fbc
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY unterstützt Braillegeräte.</string>
+
+  <string name="usbMonitor_label">Überwachung angeschlossener USB-Geräte</string>
+
+  <string name="about_label_activity">Über die Applikation</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Erstellungsdatum</string>
+  <string name="about_label_sourceRevision">Quellrevision</string>
+  <string name="about_label_privacyPolicy">Datenschutzrichtlinie</string>
+
+  <string name="inputService_name">BRLTTY Brailleeingabe Service</string>
+  <string name="inputService_type">Tastaturen auf Braillezeilen</string>
+  <string name="inputService_not_enabled">Eingabedienst nicht aktiviert</string>
+  <string name="inputService_not_selected">Eingabedienst nicht ausgewählt</string>
+  <string name="inputService_not_started">Eingabedienst nicht gestartet</string>
+  <string name="inputService_not_connected">Eingabedienst nicht verbunden</string>
+
+  <string name="updateApplication_problem_failed">Applikation nicht aktualisiert</string>
+
+  <string name="packageInstaller_problem_failed">Paket nicht installiert</string>
+  <string name="packageInstaller_problem_same">selbe Version</string>
+  <string name="packageInstaller_problem_downgrade">Downgrade ist nicht erlaubt</string>
+
+  <string name="fileDownloader_title">BRLTTY Datei Downloader</string>
+  <string name="fileDownloader_problem_failed">Datei nicht heruntergeladen</string>
+  <string name="fileDownloader_state_connecting">Verbindungsaufbau</string>
+  <string name="fileDownloader_state_downloading">Wird heruntergeladen</string>
+
+  <string name="setting_state_off">Aus</string>
+  <string name="checkbox_state_unchecked">Nein</string>
+  <string name="checkbox_state_checked">Ja</string>
+
+  <string name="braille_channel_name">Braillezeilenzustand</string>
+  <string name="braille_hint_tap">tippen für Aktionen</string>
+  <string name="braille_state_released">Freigegeben</string>
+  <string name="braille_state_waiting">Warten</string>
+  <string name="braille_state_connected">Verbunden</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Benachrichtigungen</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Schnelleinstellungen</string>
+  <string name="GLOBAL_BUTTON_BACK">Zurück</string>
+  <string name="GLOBAL_BUTTON_HOME">Startseite</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Verwendete Apps</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Übersicht</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Wechsle Eingabemetode</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Bedienungsanleitung anzeigen</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Webseite besuchen</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Gemeinschaftsnachrichten lesen</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Gemeinschaftsnachricht erstellen</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Gemeinschaftsmitgliedschaft verwalten</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Applikation aktualisieren</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Entwicklerversion</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Erlaube Downgrade</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Barrierefreiheits-Aktionen</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Einstellungen</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Allgemeine Einstellungen</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Nachrichteneinstellungen</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Geräte verwalten</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Erweiterte Einstellungen</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Braillezeile freigeben</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigationsmodus</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Sprachausgabenunterstützung</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Texttabelle</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributtabelle</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Kurzschrifttabelle</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tastaturtabelle</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Zeige Warnungen</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Zeige Ankündigungen</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Zeige Benachrichtigungen</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Protokollstufe</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Protokollkategorien</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Protokolliere Barrierefreiheitsereignisse</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Protokolliere den Bildschirminhalt</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Protokolliere Tastaturereignisse</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Unbehandelte Ereignisse protokollieren</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Gerät auswählen</string>
+  <string name="DEVICES_BUTTON_ADD">Gerät hinzufügen</string>
+  <string name="DEVICES_BUTTON_REMOVE">Gerät entfernen</string>
+
+  <string name="SELECTED_DEVICE_NONE">keine Geräte</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">Gerät nicht ausgewählt</string>
+
+  <string name="ADD_DEVICE_NAME">Name des Gerätes</string>
+  <string name="ADD_DEVICE_METHOD">Kommunikationsmethode</string>
+  <string name="ADD_DEVICE_SELECT">Gerät auswählen</string>
+  <string name="ADD_DEVICE_DRIVER">Brailletreiber auswählen</string>
+  <string name="ADD_DEVICE_DONE">Fertig</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">Kommunikationsmethode nicht ausgewählt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">Gerät nicht ausgewählt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">Brailletreiber nicht ausgewählt</string>
+  <string name="ADD_DEVICE_NO_DEVICES">keine Geräte</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">Gerätename ist bereits in Verwendung</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">Zugriff zum Gerät verweigert</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Möchten Sie dieses Gerät wirklich entfernen?</string>
+
+  <string name="SET_SELECTION_NONE">keine ausgewählt</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Vertikale Liste</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">2-Dimensionales Gitter</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Nativ</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Seriell</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Debug</string>
+  <string name="LOG_LEVEL_LABEL_information">Informationen</string>
+  <string name="LOG_LEVEL_LABEL_notice">Notizen</string>
+  <string name="LOG_LEVEL_LABEL_warning">Warnungen</string>
+  <string name="LOG_LEVEL_LABEL_error">Fehler</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritisch</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alarmsignale</string>
+  <string name="LOG_LEVEL_LABEL_emergency">unerwartete Ereignisse</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Eingabepakete</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Ausgabepakete</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Braillezeilentasten Ereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Tastatur Ereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Cursor Tracking</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Cursor Routing</string>
+  <string name="LOG_CATEGORY_LABEL_update">Update Ereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Sprachereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_async">Asynchrone Ereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_server">Server Ereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Allgemeine Ein-/Ausgabe</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serielle E/A</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB E/A</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth E/A</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Human Interface Ein-/Ausgabe</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Brailletreiberereignisse</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Sprachausgabentreiber</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Bildschirmtreiberereignisse</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Automatische Erkennung</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Automatisch anhand der Landeseinstellung</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabisch (allgemein)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamesisch</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhisch</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgarisch</string>
+  <string name="TEXT_TABLE_LABEL_bh">Biharisch</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengalisch</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetisch</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (zum Lesen von .brf Dateien in einem Editor oder pager)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Tschechisch</string>
+  <string name="TEXT_TABLE_LABEL_cy">Walisisch</string>
+  <string name="TEXT_TABLE_LABEL_da">Dänisch</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Dänisch (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Dänisch (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Deutsch</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidisch</string>
+  <string name="TEXT_TABLE_LABEL_el">Griechisch</string>
+  <string name="TEXT_TABLE_LABEL_en">Englisch</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Englisch (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Englisch (Gro⢼britannien)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Englisch (Nordamerikanisches Computer Braille)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Englisch (Vereinigte Staaten)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Spanisch</string>
+  <string name="TEXT_TABLE_LABEL_et">Estnisch</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finnisch</string>
+  <string name="TEXT_TABLE_LABEL_fr">Französisch</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Französisch (vereinheitlicht 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Französisch (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Französisch (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Französisch (Frankreich)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Französisch (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irisch</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gälisch</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondisch</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebräisch</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Kroatisch</string>
+  <string name="TEXT_TABLE_LABEL_hu">Ungarisch</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armenisch</string>
+  <string name="TEXT_TABLE_LABEL_is">Isländisch</string>
+  <string name="TEXT_TABLE_LABEL_it">Italienisch</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kanaresisch</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litauisch</string>
+  <string name="TEXT_TABLE_LABEL_lv">Lettisch</string>
+  <string name="TEXT_TABLE_LABEL_mg">Madagassisch</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltesisch</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepalesisch</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Niederländisch</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Niederländisch (Belgien)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Niederländisch (Niederlande)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norwegisch</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norwegisch (mit Unterstützung für andere Sprachen)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norwegisch (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (alt)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polnisch</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugiesisch</string>
+  <string name="TEXT_TABLE_LABEL_ro">Rumänisch</string>
+  <string name="TEXT_TABLE_LABEL_ru">Russisch</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Samisch (Nordsamisch)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slowakisch</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slowenisch</string>
+  <string name="TEXT_TABLE_LABEL_sv">Schwedisch</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Schwedisch (1989 Standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Schwedisch (1996 Standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamilisch</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Türkisch</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ukrainisch</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamesisch</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">Invertierte Fordergrundfarbe in der linken Spalte und Hintergrundfarbe in der rechten Spalte</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">Fordergrundfarbe in der linken Spalte und Hintergrundfarbe in der rechten Spalte</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">Fordergrundfarbe im oberen Quadrat und Hintergrundfarbe im unteren Quadrat</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Automatisch anhand der Landeseinstellung</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikaans (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Deutsch</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Deutsch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Deutsch (Basisschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Deutsch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Deutsch (Kurzschrift - 1998 Standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Deutsch (Kurzschrift - 2015 Standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Englisch</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Englisch (Vereinigte Staaten)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Englisch (UEB, Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Englisch (UEB, Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Englisch (Vereinigte Staaten, Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Spanisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Französisch</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Französisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Französisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Internationales Phonetisches Alphabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japanisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Koreanisch</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Koreanisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Koreanisch (Teilkurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Koreanisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Litauisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Madagassisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Niederländisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugiesisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Russisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Singhalesisch (Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thailändisch (Kurzschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Chinesisch (Taiwan, Vollschrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (Kurzschrift)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">Tastenzuweisungen für Braille Tastaturen</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">Tastenzuweisungen für desktop Tastaturen</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">Tastenzuweisungen für Nummern-Block bassierte Navigation</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">Tastenzuweisungen für Tastaturen ohne Nummernblock</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">Tastenzuweisungen für Sun Type 6 Tastaturen</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-es/strings.xml b/Android/Gradle/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..d9b16ba
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY apoya dispositivos de braille.</string>
+
+  <string name="usbMonitor_label">detector de dispositivo USB conectado</string>
+
+  <string name="about_label_activity">Acerca de aplicación</string>
+  <string name="about_label_copyright">Derechos de autor</string>
+  <string name="about_label_buildTime">Hora de Compilación</string>
+  <string name="about_label_sourceRevision">Revisión de Fuente</string>
+  <string name="about_label_privacyPolicy">Política de Privacidad</string>
+
+  <string name="inputService_name">BRLTTY Servicio de Entrada</string>
+  <string name="inputService_type">Teclados En Dispositivos de Braille</string>
+  <string name="inputService_not_enabled">servicio de entrada no habilitado</string>
+  <string name="inputService_not_selected">servicio de entrada no seleccionado</string>
+  <string name="inputService_not_started">servicio de entrada no iniciado</string>
+  <string name="inputService_not_connected">servicio de entrada no conectado</string>
+
+  <string name="updateApplication_problem_failed">aplicación no actualizada</string>
+
+  <string name="packageInstaller_problem_failed">paquete no instalado</string>
+  <string name="packageInstaller_problem_same">misma versión</string>
+  <string name="packageInstaller_problem_downgrade">degrado no permitido</string>
+
+  <string name="fileDownloader_title">BRLTTY Bajador de Archivos</string>
+  <string name="fileDownloader_problem_failed">archivo no bajado</string>
+  <string name="fileDownloader_state_connecting">conectando</string>
+  <string name="fileDownloader_state_downloading">bajando</string>
+
+  <string name="setting_state_off">Desactivado</string>
+  <string name="checkbox_state_unchecked">No</string>
+  <string name="checkbox_state_checked">Sí</string>
+
+  <string name="braille_channel_name">Estado del dispositivo Braille</string>
+  <string name="braille_hint_tap">Toca para acciones</string>
+  <string name="braille_state_released">Liberado</string>
+  <string name="braille_state_waiting">Esperando</string>
+  <string name="braille_state_connected">Conectado</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Notificaciones</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Ajustos Lijeros</string>
+  <string name="GLOBAL_BUTTON_BACK">De Vuelta</string>
+  <string name="GLOBAL_BUTTON_HOME">Hogar</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Aplicaciones Recientes</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Visión de conjunto</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Cambia método de entrada</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Mira la guía del usuario</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Navega por sitio web</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Navega por mensajes de comunidad</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Publica mensaje de comunidad</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Administra membresía de comunidad</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Actualiza aplicación</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Build de Desarrolladores</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">permitir bajar de categoría</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Acciones de la accesibilidad</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Ajustos de BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Configuración General</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Configuraciones de mensaje</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Administre Dispositivos</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Configuración Avanzada</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Libera dispositivo Braille</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Moda de Navigacción</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Supporte del Habla</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Tabla de Texto</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Tabla de Atributos</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Tabla de Contracciones</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tabla de Teclados</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Mostra alertas</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Mostra anuncios</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Mostra notificaciones</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Nivel de Registro</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Categorías de Registro</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Registra Eventos de Accesibilidad</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Registra Pantalla Renderizada</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Registra Eventos de Teclado</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Registra eventos no gestionados</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Dispositivo Seleccionado</string>
+  <string name="DEVICES_BUTTON_ADD">Añada un Dispositivo</string>
+  <string name="DEVICES_BUTTON_REMOVE">Retire el Dispositivo</string>
+
+  <string name="SELECTED_DEVICE_NONE">ningunos dispositivos</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">dispositivo no está seleccionado</string>
+
+  <string name="ADD_DEVICE_NAME">Nombre de Dispositivo</string>
+  <string name="ADD_DEVICE_METHOD">Método de Comunicación</string>
+  <string name="ADD_DEVICE_SELECT">Seleccione Dispositivo</string>
+  <string name="ADD_DEVICE_DRIVER">Controlador de Braille</string>
+  <string name="ADD_DEVICE_DONE">Terminado</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">método de communicacción no está seleccionado</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">depositivo no está seleccionado</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">controlador de braille no está seleccionado</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ningunos dispositivos</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">nombre de dispositivo ya está en uso</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">acceso al dispositivo no concedido</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">¿Realmente desea retirar este dispositivo?</string>
+
+  <string name="SET_SELECTION_NONE">ninguno seleccionado</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Lista Vertical</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Cuadrícula de Dos Dimensiones</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Nativo</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Serial</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Depure</string>
+  <string name="LOG_LEVEL_LABEL_information">Informacción</string>
+  <string name="LOG_LEVEL_LABEL_notice">Notificación</string>
+  <string name="LOG_LEVEL_LABEL_warning">Amonestación</string>
+  <string name="LOG_LEVEL_LABEL_error">Error</string>
+  <string name="LOG_LEVEL_LABEL_critical">Critico</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alerta</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Emergencia</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Paquetes de Entrada</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Paquetes de Salida</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Eventos de Teclas de Dispositivos de Braille</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Eventos de Teclas de Teclados</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Seguimiento de Cursor</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Enrutamiento de Cursor</string>
+  <string name="LOG_CATEGORY_LABEL_update">Eventos de Actualización</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Eventos del Habla</string>
+  <string name="LOG_CATEGORY_LABEL_async">Eventos de Async</string>
+  <string name="LOG_CATEGORY_LABEL_server">Eventos de Servidor</string>
+  <string name="LOG_CATEGORY_LABEL_gio">E/S genérica</string>
+  <string name="LOG_CATEGORY_LABEL_serial">E/S en Serie</string>
+  <string name="LOG_CATEGORY_LABEL_usb">E/S USB</string>
+  <string name="LOG_CATEGORY_LABEL_bt">E/S Bluetooth</string>
+  <string name="LOG_CATEGORY_LABEL_hid">E/S de interfaz humana</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Eventos de Controlador de Braille</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Eventos de Controlador de Habla</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Eventos de Controlador de Pantalla</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Autodetecte</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Selección Automática Basada en la Configuración Regional</string>
+  <string name="TEXT_TABLE_LABEL_ar">Árabe (genérico</string>
+  <string name="TEXT_TABLE_LABEL_as">Asamés</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadí</string>
+  <string name="TEXT_TABLE_LABEL_bg">Búlgaro</string>
+  <string name="TEXT_TABLE_LABEL_bh">Biharí</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengalí</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetano</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Formado de Lista en Braille (para visualización de archivos BRF dentro de un editor o paginador)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Checo</string>
+  <string name="TEXT_TABLE_LABEL_cy">Galés</string>
+  <string name="TEXT_TABLE_LABEL_da">Danés</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danés (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danés (TextoRegistro)</string>
+  <string name="TEXT_TABLE_LABEL_de">Alemán</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidiano</string>
+  <string name="TEXT_TABLE_LABEL_el">Griego</string>
+  <string name="TEXT_TABLE_LABEL_en">Inglés</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Inglés (Canadá)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Inglés (Reino Unido)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Inglés (Código braille de computadoras norteamericano)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Inglés (Estados Unidos)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Español</string>
+  <string name="TEXT_TABLE_LABEL_et">Estonio</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finlandés</string>
+  <string name="TEXT_TABLE_LABEL_fr">Francés</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Francés (unificado 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Francés (Canadá)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Francés (Code Braille Informatique Français estándar)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Francés (Francia)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Francés (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irlandés</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaélico</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondí</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujaratí</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebreo</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindí</string>
+  <string name="TEXT_TABLE_LABEL_hr">Croata</string>
+  <string name="TEXT_TABLE_LABEL_hu">Húngaro</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armenio</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandés</string>
+  <string name="TEXT_TABLE_LABEL_it">Italiano</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasí</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkaní</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Lituano</string>
+  <string name="TEXT_TABLE_LABEL_lv">Letón</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maorí</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipurí</string>
+  <string name="TEXT_TABLE_LABEL_mr">Maratí</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltés</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwarí</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepalí</string>
+  <string name="TEXT_TABLE_LABEL_new">Newarí</string>
+  <string name="TEXT_TABLE_LABEL_nl">Holandés</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Holandés (Bélgica)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Holandés (Países Bajos)</string>
+  <string name="TEXT_TABLE_LABEL_no">Noruego</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Noruego (con apoyo de otros idiomas)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Noruego (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newarí (viejo)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabí</string>
+  <string name="TEXT_TABLE_LABEL_pi">Palí</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polaco</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugués</string>
+  <string name="TEXT_TABLE_LABEL_ro">Rumano</string>
+  <string name="TEXT_TABLE_LABEL_ru">Ruso</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sánscrito</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santalí</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindí</string>
+  <string name="TEXT_TABLE_LABEL_se">Sami (Norteno)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Eslovaco</string>
+  <string name="TEXT_TABLE_LABEL_sl">esloveno</string>
+  <string name="TEXT_TABLE_LABEL_sv">Sueco</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Sueco (1989 estándar)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Sueco (1996 estándar)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahilí</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turco</string>
+  <string name="TEXT_TABLE_LABEL_uk">ucranio</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamita</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">color inverso del primer plano en la columna isquierda y color del fondo en la columna isquierda</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">color del primer plano en la columna isquierda y color del fondo en la columna derecha</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">color del primer plano en el cuadro superior y color del fondo en el cuadro inferior</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Selección Automática Basada en la Configuración Regional</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Africaans (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharico (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Alemán</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Alemán (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Alemán (contracciones básicas)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Alemán (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Alemán (contraccionado - estándar de 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Alemán (contraccionado - estándar de 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Inglés</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Inglés (Estados Unidos)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Inglés (Unificado, no contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Inglés (Braille Inglés unificado, grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Inglés (Estados Unidos, grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Español (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Francés  </string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Francés (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Francés (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesio (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Alfabeto Fonético Internacional</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japonés (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Coreano</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Coreano (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Coreano (grado 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Coreano (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Lituano (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Islandés (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugés (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Ruso (contratado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalés (sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahilí (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Tilandés (contraccionado)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Chino (Taiwan, sin contracciones)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulú (contraccionado)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">vinculantes para teclados braille</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">vinculantes para teclados plenos</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">vinculantes para navigacción de teclado numérico</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">vinculantes para teclados sin teclado numérico</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">vinculantes para teclados Sun Tipo 6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-fa/strings.xml b/Android/Gradle/app/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..cd2fc74
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-fa/strings.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY از دستگاههای خط بریل پشتیبانی میکند .</string>
+
+  <string name="about_label_activity">درباره نرم‌افزار</string>
+  <string name="about_label_copyright">حق انتشار</string>
+  <string name="about_label_buildTime">زمان ساخت</string>
+  <string name="about_label_sourceRevision">بازنگری منبع</string>
+  <string name="about_label_privacyPolicy">حریم شخصی</string>
+
+  <string name="inputService_name">BRLTTY خدمات ورودی</string>
+  <string name="inputService_type">صفحه کلیدهای دستگاه بریل</string>
+  <string name="inputService_not_enabled">خدمات ورودی فعال نشده است</string>
+  <string name="inputService_not_selected">خدمات ورودی انتخاب نشده است</string>
+  <string name="inputService_not_started">خدمات ورودی اجرا نشده است</string>
+  <string name="inputService_not_connected">خدمات ورودی وصل نیست</string>
+
+  <string name="updateApplication_problem_failed">برنامه بروزرسانی نشده</string>
+
+  <string name="packageInstaller_problem_failed">بسته نصب نشد</string>
+  <string name="packageInstaller_problem_same">نسخه یکسان</string>
+  <string name="packageInstaller_problem_downgrade">اجازه عقب‌گرد وجود ندارد</string>
+
+  <string name="fileDownloader_title">BRLTTY File دانلود کننده</string>
+  <string name="fileDownloader_problem_failed">فایل دانلود نشد</string>
+  <string name="fileDownloader_state_connecting">در حال اتصال</string>
+  <string name="fileDownloader_state_downloading">در حال دانلود</string>
+
+  <string name="setting_state_off">خاموش</string>
+  <string name="checkbox_state_unchecked">خیر</string>
+  <string name="checkbox_state_checked">بله</string>
+
+  <string name="braille_channel_name">وضعیت دستگاه بریل</string>
+  <string name="braille_hint_tap">برای راهنمایی این نقطه را بفشارید</string>
+  <string name="braille_state_released">وضعیت انتشار دستگاه</string>
+  <string name="braille_state_waiting">صبر کنید</string>
+  <string name="braille_state_connected">متصل شد</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">اعلانها</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">تنظیمات سریع</string>
+  <string name="GLOBAL_BUTTON_BACK">بازگشت</string>
+  <string name="GLOBAL_BUTTON_HOME">خانه</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">نرم‌افزار های اخیر</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">گزارش کلی</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">روش تغییر ورودی</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">نمایش راهنمای کاربر</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">گشتن در وب سایت</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">گشتن در پیام‌های گروه</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">ارسال پیام در گروه</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">مدیریت عضویت در گروه</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">به‌روز رسانی نرم‌افزار</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">ساخت توسعه دهنده</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">اجازه عقب‌گرد</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">عملیات دسترسی</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY تنظیمات</string>
+  <string name="SETTINGS_SCREEN_GENERAL">تنظیمات عمومی</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">تنظیمات پیام</string>
+  <string name="SETTINGS_SCREEN_DEVICES">مدیریت دستگاه‌ها</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">تنظیمات پیشرفته</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">انتشار دستگاه بریل</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">حالت مسیریابی</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">پشتیبانی از گفتار</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">جدول نوشته</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">صفات جدول</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">اختصار جدول</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">جدول صفحه کلید</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">نمایش اخطارها</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">نمایش اعلامیه ها</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">نمایش اعلان ها</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">سطح گزارش</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">دسته بندی های گزارش</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">گزارش لحظات دسترسی</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">گزارش رندر صفحه</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">گزارش لحظات صفحه کلید</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">گزارش لحظات غیر مترقبه</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">دستگاه انتخاب شده</string>
+  <string name="DEVICES_BUTTON_ADD">اضافه کردن دستگاه</string>
+  <string name="DEVICES_BUTTON_REMOVE">حذف دستگاه</string>
+
+  <string name="SELECTED_DEVICE_NONE">دستگاهی یافت نشد</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">دستگاهی انتخاب نشد</string>
+
+  <string name="ADD_DEVICE_NAME">نام دستگاه</string>
+  <string name="ADD_DEVICE_METHOD">روش ارتباط دستگاه</string>
+  <string name="ADD_DEVICE_SELECT">دستگاهی را انتخاب کنید</string>
+  <string name="ADD_DEVICE_DRIVER">راه انداز بریل را انتخاب کنید</string>
+  <string name="ADD_DEVICE_DONE">با موفقیت انجام شد</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">روش ارتباط انتخاب نشد</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">دستگاه انتخاب نشد</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">راه انداز بریل انتخاب نشد</string>
+  <string name="ADD_DEVICE_NO_DEVICES">دستگاهی یافت نشد</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">نام دستگاه تکراری است</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">آیا واقعا مایلید که این دستگاه را حذف کنید؟</string>
+
+  <string name="SET_SELECTION_NONE">چیزی انتخاب نشده</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">لیست عمودی</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">خط کشی دو بعدی</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">سازگار</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">بلوتوث</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">یو اس بی</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">پورت سریال</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">عیب یابی</string>
+  <string name="LOG_LEVEL_LABEL_information">اطلاعات</string>
+  <string name="LOG_LEVEL_LABEL_notice">توجه</string>
+  <string name="LOG_LEVEL_LABEL_warning">هشدار</string>
+  <string name="LOG_LEVEL_LABEL_error">خطا</string>
+  <string name="LOG_LEVEL_LABEL_critical">بحرانی</string>
+  <string name="LOG_LEVEL_LABEL_alert">اخطار</string>
+  <string name="LOG_LEVEL_LABEL_emergency">ضروری</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">بسته های ورودی</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">بسته های خروجی</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">لحظات کلیدهای دستگاه بریل</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">لحظات کلیدهای صفحه کلید</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Cursor پیگیری</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Cursor مسیر سنجی</string>
+  <string name="LOG_CATEGORY_LABEL_update">به روز رسانی لحظات</string>
+  <string name="LOG_CATEGORY_LABEL_speech">لحظات گفتار</string>
+  <string name="LOG_CATEGORY_LABEL_async">Async لحظات</string>
+  <string name="LOG_CATEGORY_LABEL_server">لحظات سرویس دهنده</string>
+  <string name="LOG_CATEGORY_LABEL_serial">فعالیت سریال</string>
+  <string name="LOG_CATEGORY_LABEL_usb">فعالیت یو اس بی</string>
+  <string name="LOG_CATEGORY_LABEL_bt">فعالیت بلوتوث</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">لحظات راه انداز بریل</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">لحظات راه انداز گفتار</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">لحظات راه انداز صفحه</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">شناسایی اتوماتیک</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">انتخاب اتوماتیک بر مبنای محل</string>
+  <string name="TEXT_TABLE_LABEL_ar">عربی</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamese</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">بلغاری</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">بنگالی</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetan</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (for viewing .brf files within an editor or pager)</string>
+  <string name="TEXT_TABLE_LABEL_cs">چک</string>
+  <string name="TEXT_TABLE_LABEL_cy">ولزی</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danish (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da">دانمارکی</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danish (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">آلمانی</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidian</string>
+  <string name="TEXT_TABLE_LABEL_el">یونانی</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">انگلیسی (کانادا)</string>
+  <string name="TEXT_TABLE_LABEL_en">انگلیسی</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">انگلیسی (انگلستان)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">انگلیسی (شمال آمریکا)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">انگلیسی (ایالات متحده آمریکا)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">اسپانیایی</string>
+  <string name="TEXT_TABLE_LABEL_et">استونیایی</string>
+  <string name="TEXT_TABLE_LABEL_fi">فنلاندی</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">French (unified 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">فرانسوی (کانادا)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">French (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr">فرانسوی</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">فرانسوی (فرانسه)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">French (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">ایرلندی</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaelic</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">عبری</string>
+  <string name="TEXT_TABLE_LABEL_hi">هندی</string>
+  <string name="TEXT_TABLE_LABEL_hr">کرواسیایی</string>
+  <string name="TEXT_TABLE_LABEL_hu">مجاری</string>
+  <string name="TEXT_TABLE_LABEL_hy">ارمنی</string>
+  <string name="TEXT_TABLE_LABEL_is">ایسلندی</string>
+  <string name="TEXT_TABLE_LABEL_it">ایتالیایی</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">لیتوانیایی</string>
+  <string name="TEXT_TABLE_LABEL_lv">لتونیایی</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltese</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">نپالی</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Dutch (Belgium)</string>
+  <string name="TEXT_TABLE_LABEL_nl">Dutch</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Dutch (Netherlands)</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norwegian (with support for other languages)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norwegian</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norwegian (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (old)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">پنجابی</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">لهستانی</string>
+  <string name="TEXT_TABLE_LABEL_pt">پرتغالی</string>
+  <string name="TEXT_TABLE_LABEL_ro">Romanian</string>
+  <string name="TEXT_TABLE_LABEL_ru">روسی</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Sami (Northern)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovak</string>
+  <string name="TEXT_TABLE_LABEL_sl">اسلونیایی</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Swedish (1989 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Swedish (1996 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv">سوئدی</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">ترکی</string>
+  <string name="TEXT_TABLE_LABEL_uk">اکراینی</string>
+  <string name="TEXT_TABLE_LABEL_vi">ویتنامی</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">جابه جایی رنگ پیش زمینه در ستون چپ با رنگ پس زمینه در ستون راست</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">رنگ پیش زمینه در ستون چپ و رنگ پس زمینه در ستون راست</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">رنگ پیش زمینه در مربع بالایی و رنگ پس زمینه در مربع پایینی</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">انتخاب اتوماتیک بر مبنای محل</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">آفریقایی (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharic (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">آلمانی</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">German (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">German (basic contractions)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">German (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">German (contracted - 1998 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">German (contracted - 2015 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">انگلیسی</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">انگلیسی (ایالات متحده)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">English (Unified English Braille, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">English (US, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Spanish (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">فراسوی</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">French (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">French (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesian (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">International Phonetic Alphabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japanese (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">کره‌ای</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Korean (grade 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Korean (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Korean (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Lithuanian (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Dutch (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portuguese (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">روسی</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalese (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thai (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Chinese (Taiwan, uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (contracted)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">پیوست بریل برای صفحه کلید بریل</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">پیوست دسکتاپ برای صفحه کلید کامل</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">پیوست کلید ماشین حساب برای مسیریابی بر پایه کلید ماشین حساب</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">پیوست لبتاپ برای کیبردهای بدون ماشین حساب</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">پیوست برای صفحه کلید 6 گانه خورشیدی</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-fi/strings.xml b/Android/Gradle/app/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..04018ce
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-fi/strings.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY tukee pistenäyttöjä.</string>
+
+  <string name="about_label_activity">Tietoa sovelluksesta</string>
+  <string name="about_label_copyright">Tekijänoikeus</string>
+  <string name="about_label_buildTime">Käännöksen ajankohta</string>
+  <string name="about_label_sourceRevision">Lähdekoodin revisio</string>
+  <string name="about_label_privacyPolicy">Tietoja yksityisyydensuojasta</string>
+
+  <string name="inputService_name">BRLTTY:n syötepalvelu</string>
+  <string name="inputService_type">Pistenäyttöjen näppäimistöt</string>
+  <string name="inputService_not_enabled">Syötepalvelu ei ole käytössä</string>
+  <string name="inputService_not_selected">Syötepalvelua ei ole valittu</string>
+  <string name="inputService_not_started">Syötepalvelua ei ole käynnistetty</string>
+  <string name="inputService_not_connected">Syötepalvelua ei ole yhdistetty</string>
+
+  <string name="updateApplication_problem_failed">sovellusta ei päivitetty</string>
+
+  <string name="packageInstaller_problem_failed">pakettia ei asennettu</string>
+  <string name="packageInstaller_problem_same">sama versio</string>
+  <string name="packageInstaller_problem_downgrade">alaspäin päivittämistä ei sallita</string>
+
+  <string name="fileDownloader_title">BRLTTY-tiedostolataaja</string>
+  <string name="fileDownloader_problem_failed">tiedostoa ei ladattu</string>
+  <string name="fileDownloader_state_connecting">yhdistetään</string>
+  <string name="fileDownloader_state_downloading">ladataan</string>
+
+  <string name="setting_state_off">Pois</string>
+  <string name="checkbox_state_unchecked">Ei</string>
+  <string name="checkbox_state_checked">Kyllä</string>
+
+  <string name="braille_channel_name">Pistekirjoituslaitteen tila</string>
+  <string name="braille_hint_tap">Napauta saadaksesi toimintoja</string>
+  <string name="braille_state_released">Vapautettu</string>
+  <string name="braille_state_waiting">Odottaa</string>
+  <string name="braille_state_connected">Yhdistetty</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Ilmoitukset</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Pika-asetukset</string>
+  <string name="GLOBAL_BUTTON_BACK">Takaisin</string>
+  <string name="GLOBAL_BUTTON_HOME">Koti</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Viimeisimmät sovellukset</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Yleiskatsaus</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Vaihda syöttömenetelmää</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Näytä käyttöopas</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Selaa verkkosivustoa</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Selaa yhteisön viestejä</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Lähetä yhteisöviesti</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Hallinnoi yhteisöjäsenyyttä</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Päivitä sovellus</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Kehittäjän koontiversio</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Salli alaspäin päivittäminen</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Helppokäyttötoiminnot</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY:n asetukset</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Yleiset</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Viestiasetukset</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Laitteiden hallinta</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Lisäasetukset</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Vapauta pistekirjoituslaite</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigointitila</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Puhetuki</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Tekstitaulukko</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Määretaulukko</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Lyhennetaulukko</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Näppäintaulukko</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Näytä hälytykset</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Näytä tiedotukset</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Näytä ilmoitukset</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Lokitaso</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Lokikategoriat</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Tallenna saavutettavuustapahtumat lokiin</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Tallenna hahmonnettu näyttö lokiin</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Tallenna näppäimistötapahtumat lokiin</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Log Käsittelemättömät tapahtumat</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Valittu laite</string>
+  <string name="DEVICES_BUTTON_ADD">Lisää laite</string>
+  <string name="DEVICES_BUTTON_REMOVE">Poista laite</string>
+
+  <string name="SELECTED_DEVICE_NONE">ei laitteita</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">laitetta ei ole valittu</string>
+
+  <string name="ADD_DEVICE_NAME">Laitteen nimi</string>
+  <string name="ADD_DEVICE_METHOD">Yhteysmenetelmä</string>
+  <string name="ADD_DEVICE_SELECT">Valitse laite</string>
+  <string name="ADD_DEVICE_DRIVER">Pistenäyttöajuri</string>
+  <string name="ADD_DEVICE_DONE">Valmis</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">yhteysmenetelmää ei ole valittu</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">laitetta ei ole valittu</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">pistenäyttöajuria ei ole valittu</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ei laitteita</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">laitenimi on jo käytössä</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Haluatko varmasti poistaa tämän laitteen?</string>
+
+  <string name="SET_SELECTION_NONE">ei valintaa</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Pystysuuntainen lista</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Kaksiulotteinen ruudukko</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Alkuperäinen</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Sarjaportti</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Virheenkorjaus</string>
+  <string name="LOG_LEVEL_LABEL_information">Tiedot</string>
+  <string name="LOG_LEVEL_LABEL_notice">Huomautus</string>
+  <string name="LOG_LEVEL_LABEL_warning">Varoitus</string>
+  <string name="LOG_LEVEL_LABEL_error">Virhe</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kriittinen</string>
+  <string name="LOG_LEVEL_LABEL_alert">Hälytys</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Hätätilanne</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Syötepaketit</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Tulostuspaketit</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Pistenäytön näppäintapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Näppäimistön näppäintapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Kohdistimen seuranta</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Kohdistimen siirtäminen</string>
+  <string name="LOG_CATEGORY_LABEL_update">Päivitystapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Puhetapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_async">Asynkroniset tapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_server">Palvelintapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Sarjaportin syöttö/tulostus</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB:n syöttö/tulostus</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetoothin syöttö/tulostus</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Pistenäyttöajurin tapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Puheajurin tapahtumat</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Näyttöajurin tapahtumat</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">tunnista automaattisesti</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">automaattinen valinta kieliasetusten mukaan</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabia (yleinen)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assami</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgaria</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tiibet</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (.brf-tiedostojen katselemiseen muokkain- tai katselusovelluksessa)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Tshekki</string>
+  <string name="TEXT_TABLE_LABEL_cy">Wales</string>
+  <string name="TEXT_TABLE_LABEL_da">Tanska</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Tanska (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Tanska (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Saksa</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidia</string>
+  <string name="TEXT_TABLE_LABEL_el">Kreikka</string>
+  <string name="TEXT_TABLE_LABEL_en">Englanti</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Englanti (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Englanti (Iso-Britannia)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Englanti (pohjoisamerikkalainen tietokonemerkistö)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Englanti (Yhdysvallat)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Espanja</string>
+  <string name="TEXT_TABLE_LABEL_et">Viro</string>
+  <string name="TEXT_TABLE_LABEL_fi">Suomi</string>
+  <string name="TEXT_TABLE_LABEL_fr">Ranska</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Ranska (yhdenmukaistettu 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Ranska (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Ranska (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Ranska (Ranska)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Ranska (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Iiri</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gael</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Heprea</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Kroatia</string>
+  <string name="TEXT_TABLE_LABEL_hu">Unkari</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armenia</string>
+  <string name="TEXT_TABLE_LABEL_is">Islanti</string>
+  <string name="TEXT_TABLE_LABEL_it">Italia</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">liettua</string>
+  <string name="TEXT_TABLE_LABEL_lv">latvia</string>
+  <string name="TEXT_TABLE_LABEL_mg">malagassi</string>
+  <string name="TEXT_TABLE_LABEL_mi">maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">malajalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">malta</string>
+  <string name="TEXT_TABLE_LABEL_mun">munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">nepali</string>
+  <string name="TEXT_TABLE_LABEL_new">newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">hollanti</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">hollanti (Belgia)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">hollanti (Hollanti)</string>
+  <string name="TEXT_TABLE_LABEL_no">norja</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">norja (tuki muille kielille)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">norja (Offentlig Utvalg for Blindeskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">newari (vanha)</string>
+  <string name="TEXT_TABLE_LABEL_or">orija</string>
+  <string name="TEXT_TABLE_LABEL_pa">punjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">puola</string>
+  <string name="TEXT_TABLE_LABEL_pt">portugali</string>
+  <string name="TEXT_TABLE_LABEL_ro">romania</string>
+  <string name="TEXT_TABLE_LABEL_ru">venäjä</string>
+  <string name="TEXT_TABLE_LABEL_sa">sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">saame (pohjoissaame)</string>
+  <string name="TEXT_TABLE_LABEL_sk">slovakki</string>
+  <string name="TEXT_TABLE_LABEL_sl">slovenia</string>
+  <string name="TEXT_TABLE_LABEL_sv">ruotsi</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">ruotsi (1989 standardi)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">ruotsi (1996 standardi)</string>
+  <string name="TEXT_TABLE_LABEL_sw">swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">tamili</string>
+  <string name="TEXT_TABLE_LABEL_te">telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">turkki</string>
+  <string name="TEXT_TABLE_LABEL_uk">ukraina</string>
+  <string name="TEXT_TABLE_LABEL_vi">vietnam</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">käänteinen edustan väri vasemmassa sarakkeessa ja taustaväri oikeassa sarakkeessa</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">edustan väri vasemmassa sarakkeessa ja taustaväri oikeassa sarakkeessa</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">edustan väri yläosassa ja taustaväri alaosassa</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">automaattinen valinta kieliasetusten mukaan</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">afrikaans (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">amhara (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">saksa</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">saksa (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">saksa (peruslyhenteet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">saksa (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">saksa (lyhennekirjoitus - 1998 standardi)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">saksa (lyhennekirjoitus - 2015 standardi)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">englanti</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Englanti (Yhdysvallat)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">englanti (Unified English Braille, taso 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">englanti (Yhdysvallat, taso 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">espanja (taso 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">ranska</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">ranska (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">ranska (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">hausa (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">indonesia (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Kansainvälinen foneettinen aakkosto</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">japani (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">korea</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">korea (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">korea (taso 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">korea (taso 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">liettua (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">malagassi (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">munda (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">hollanti (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">chichewa (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">portugali (taso 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Venäläinen (lyhennetty)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">singaleesi (tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">swahili (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">thai (lyhennekirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">kiina (Taiwan, tavallinen kirjoitus)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (lyhennekirjoitus)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">pistenäppäimistöjen näppäinmääritykset</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">täysikokoisten näppäimistöjen näppäinmääritykset</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">Numeronäppäimistönavigoinnin näppäinmääritykset</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">Numeronäppäimistöttömien näppäimistöjen näppäinmääritykset</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">Sun Type 6 -näppäimistöjen näppäinmääritykset</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-fr/strings.xml b/Android/Gradle/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..a59fbd0
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY supporte des afficheurs braille.</string>
+
+  <string name="usbMonitor_label">Visionneur de périphériques USB connectés</string>
+
+  <string name="about_label_activity">À propos de l\'application</string>
+  <string name="about_label_copyright">Droits d\'auteur</string>
+  <string name="about_label_buildTime">Date de construction</string>
+  <string name="about_label_sourceRevision">Révision des sources</string>
+  <string name="about_label_privacyPolicy">Politique de confidentialité</string>
+
+  <string name="inputService_name">Service d\'entrée BRLTTY</string>
+  <string name="inputService_type">Claviers des périphériques brailles</string>
+  <string name="inputService_not_enabled">Dispositif de saisie non activé</string>
+  <string name="inputService_not_selected">Dispositif de saisie non sélectionné</string>
+  <string name="inputService_not_started">Dispositif de saisie non démarré</string>
+  <string name="inputService_not_connected">Dispositif de saisie non connecté</string>
+
+  <string name="updateApplication_problem_failed">Application non mise à jour</string>
+   
+  <string name="packageInstaller_problem_failed">Paquet non installé</string>
+  <string name="packageInstaller_problem_same">Version identique</string>
+  <string name="packageInstaller_problem_downgrade">Rétrogradation non permise</string>
+   
+  <string name="fileDownloader_title">Téléchargeur du fichier de BRLTTY</string>
+  <string name="fileDownloader_problem_failed">Fichier non téléchargé</string>
+  <string name="fileDownloader_state_connecting">Connexion</string>
+  <string name="fileDownloader_state_downloading">Téléchargement</string>
+   
+  <string name="setting_state_off">Désactivé</string>
+  <string name="checkbox_state_unchecked">Non</string>
+  <string name="checkbox_state_checked">Oui</string>
+
+  <string name="braille_channel_name">État du périphérique braille</string>
+  <string name="braille_hint_tap">gestes d\'actions</string>
+  <string name="braille_state_released">Libéré</string>
+  <string name="braille_state_waiting">En attente</string>
+  <string name="braille_state_connected">Connecté</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Notifications</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Paramétrages rapides</string>
+  <string name="GLOBAL_BUTTON_BACK">Retour</string>
+  <string name="GLOBAL_BUTTON_HOME">Accueil</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Applis récentes</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Général</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Changer le mode de saisie</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Lire le guide utilisateur</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Visiter le site Web</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Parcourir les messages de la communauté</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Poster un message à la communauté</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Gérer l\'adhésion à la communauté</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Mettre à jour l\'application</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Version de développement</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Autoriser les mises à jour descendantes</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Actions d\'accessibilité</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Paramètres de BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Paramètres généraux</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Paramètres des messages</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Gestion des périphériques</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Paramètres avancés</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Libérer le périphérique braille</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Mode navigation</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Support de la parole</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Table de texte</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Table d\'attributs</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Table d\'abrégé</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Table des touches du clavier</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Afficher les alertes</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Afficher les annonces</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Afficher les notifications</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Niveau de journalisation</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Catégorie de journal</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Journaliser les événements liés à l\'accessibilité</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Journaliser l\'affichage de l\'écran</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Journaliser les événements du clavier</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Journaliser les événements non gérés</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Périphérique sélectionné</string>
+  <string name="DEVICES_BUTTON_ADD">Ajouter un périphérique</string>
+  <string name="DEVICES_BUTTON_REMOVE">Supprimer un périphérique</string>
+
+  <string name="SELECTED_DEVICE_NONE">Pas de périphériques</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">périphérique non sélectionné</string>
+
+  <string name="ADD_DEVICE_NAME">Nom du périphérique</string>
+  <string name="ADD_DEVICE_METHOD">Méthode de communication</string>
+  <string name="ADD_DEVICE_SELECT">Périphérique sélectionné</string>
+  <string name="ADD_DEVICE_DRIVER">Pilote braille</string>
+  <string name="ADD_DEVICE_DONE">Fait</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">méthode de communication non sélectionnée</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">périphérique non sélectionné</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">pilote braille non sélectionné</string>
+  <string name="ADD_DEVICE_NO_DEVICES">pas de périphériques</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">nom de périphérique déjà utilisé</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">Accès au périphérique interdit</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Voulez-vous vraiment supprimer ce périphérique ?</string>
+
+  <string name="SET_SELECTION_NONE">aucune sélection</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Liste verticale</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Grille 2D</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Natif</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Série</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Débogage</string>
+  <string name="LOG_LEVEL_LABEL_information">Informations</string>
+  <string name="LOG_LEVEL_LABEL_notice">Remarque</string>
+  <string name="LOG_LEVEL_LABEL_warning">Avertissement</string>
+  <string name="LOG_LEVEL_LABEL_error">Erreur</string>
+  <string name="LOG_LEVEL_LABEL_critical">Critique</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alerte</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Urgence</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Paquets en entrée</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Paquets en sortie</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Événements clavier du périphérique braille</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Événement touche du clavier</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Poursuite du curseur</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Routine curseur</string>
+  <string name="LOG_CATEGORY_LABEL_update">Événements liés à la mise à jour</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Événements liés à la parole</string>
+  <string name="LOG_CATEGORY_LABEL_async">Événements asynchrones</string>
+  <string name="LOG_CATEGORY_LABEL_server">Événements liés au serveur</string>
+  <string name="LOG_CATEGORY_LABEL_gio">E/S générique</string>
+  <string name="LOG_CATEGORY_LABEL_serial">E/S série</string>
+  <string name="LOG_CATEGORY_LABEL_usb">E/S USB</string>
+  <string name="LOG_CATEGORY_LABEL_bt">E/S Bluetooth</string>
+  <string name="LOG_CATEGORY_LABEL_hid">E/S interface homme-machine</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Pilote braille</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Pilote de synthèse vocale</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Pilote de l\'écran</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Détection automatique</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Sélection automatique basée sur la locale</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabe (générique)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamese</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgare</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibétain</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Format transcription braille (pour visualiser les fichiers .brf dans un éditeur ou un afficheur)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Tchèque</string>
+  <string name="TEXT_TABLE_LABEL_cy">Gallois</string>
+  <string name="TEXT_TABLE_LABEL_da">Danois</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danois (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danois (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Allemand</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidian</string>
+  <string name="TEXT_TABLE_LABEL_el">Grec</string>
+  <string name="TEXT_TABLE_LABEL_en">Anglais</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Anglais (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Anglais (Royaume-Uni)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Anglais (Code informatique braille nord-américain)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Anglais (États-Unis)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Espéranto</string>
+  <string name="TEXT_TABLE_LABEL_es">Espagnol</string>
+  <string name="TEXT_TABLE_LABEL_et">Estonien</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finnois</string>
+  <string name="TEXT_TABLE_LABEL_fr">Français</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Français (unifié 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Français (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Français (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Français (France)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Français (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irlandais</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaélique</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujrati</string>
+  <string name="TEXT_TABLE_LABEL_he">Hébreux</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Croate</string>
+  <string name="TEXT_TABLE_LABEL_hu">Hongrois</string>
+  <string name="TEXT_TABLE_LABEL_hy">Arménien</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandais</string>
+  <string name="TEXT_TABLE_LABEL_it">Italien</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Lituanien</string>
+  <string name="TEXT_TABLE_LABEL_lv">Letton</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malgache</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltais</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Népalais</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Autrichien</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Flamand (Belgique)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Flamand (Pays-Bas)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norvégien</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norvégien (avec prise en charge d\'autres langues)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norvégien (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (ancien)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Punjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polonais</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugais</string>
+  <string name="TEXT_TABLE_LABEL_ro">Roumain</string>
+  <string name="TEXT_TABLE_LABEL_ru">Russe</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanscrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Sami (du Nord)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovaque</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slovène</string>
+  <string name="TEXT_TABLE_LABEL_sv">Suédois</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Suédois (1989 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Suédois (1996 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamoul</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turc</string>
+  <string name="TEXT_TABLE_LABEL_uk">ukrainien</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamien</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">inversion des couleurs de premier plan dans la colonne de gauche et de fond dans la colonne de droite</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">couleur de premier plan dans la colonne de gauche et de fond dans la colonne de droite</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">couleur de premier plan dans le carré en majuscule et de fond dans le carré en minuscule</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Sélection automatique basée sur la locale</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikaans (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharique (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Allemand</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Allemand (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Allemand (abréviations de base)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Allemand (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Allemand (abrégé - standard 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Allemand (abrégé - standard 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Anglais</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Anglais (États-Unis)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Anglais (Braille anglais unifié, 1er degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Anglais (Braille anglais unifié, 2ème degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Anglais (EU, 2ème degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Espagnol (2ème degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Français</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Français (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Français (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonésien (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Alphabet Phonétique international</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japonais (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Coréen</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Coréen (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Coréen (1er degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Coréen (2ème degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Lituanien (non abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malgache (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Autrichien (Abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugais (2ème degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Russe (1er degré)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Cingalais (désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thaïlandais (abrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Chinois (Taiwan, désabrégé)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zoulou (abrégé)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">associations pour les claviers brailles</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">associations pour les claviers complets</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">associations pour la navigation au pavé numérique</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">associations pour les claviers sans pavé numérique</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">associations pour les claviers Sun Type 6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-it/strings.xml b/Android/Gradle/app/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..84805dc
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY permette l\'utilizzo di dispositivi braille.</string>
+
+  <string name="usbMonitor_label">Dispositivo USB Collegato Monitor</string>
+
+  <string name="about_label_activity">Info sull\'Applicazione</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Data e Ora del Rilascio</string>
+  <string name="about_label_sourceRevision">Revisione Sorgente</string>
+  <string name="about_label_privacyPolicy">Informativa sulla Privacy</string>
+
+  <string name="inputService_name">BRLTTY Servizio in Ingresso</string>
+  <string name="inputService_type">Tastiere su Dispositivi Braille</string>
+  <string name="inputService_not_enabled">servizio immissione non abilitato</string>
+  <string name="inputService_not_selected">servizio immissione non selezionato</string>
+  <string name="inputService_not_started">servizio immissione non avviato</string>
+  <string name="inputService_not_connected">servizio immissione non connesso</string>
+
+  <string name="updateApplication_problem_failed">applicazione nonon aggiornata</string>
+
+  <string name="packageInstaller_problem_failed">pacchetto non installato</string>
+  <string name="packageInstaller_problem_same">same version</string>
+  <string name="packageInstaller_problem_downgrade">downgrade non consentito</string>
+
+  <string name="fileDownloader_title">BRLTTY File Scaricato</string>
+  <string name="fileDownloader_problem_failed">file non scaricato</string>
+  <string name="fileDownloader_state_connecting">connessione in corso</string>
+  <string name="fileDownloader_state_downloading">scaricamento in corso</string>
+
+  <string name="setting_state_off">Off</string>
+  <string name="checkbox_state_unchecked">No</string>
+  <string name="checkbox_state_checked">Sì</string>
+
+  <string name="braille_channel_name">Stato Dispositivo Braille</string>
+  <string name="braille_hint_tap">tocca per opzioni</string>
+  <string name="braille_state_released">Scollegato</string>
+  <string name="braille_state_waiting">In Attesa</string>
+  <string name="braille_state_connected">Connesso</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Notifiche</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Impostazioni Rapide</string>
+  <string name="GLOBAL_BUTTON_BACK">Indietro</string>
+  <string name="GLOBAL_BUTTON_HOME">Home</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Applicazioni Recenti</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Panoramica</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Cambia Metodo di immissione</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Visualizza Guida Utente</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Naviga Sito Web</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Visualizza i Messaggi della Community</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Posta un Messaggio alla Community</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Gestisci Iscrizione alla Community</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Aggiorna Applicazione</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Build di Sviluppo</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Consenti Downgrade</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Azioni Accessibilità</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Impostazioni BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Impostazioni Generali</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Impostazioni Messaggi</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Gestione Dispositivi</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Impostazioni Avanzate</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Scollega Dispositivo Braille</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Modalità Navigazione</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Supporto Voce</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Tabella di Traduzione</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Tabella Attributi</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Tabella Braille Contratto</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tabella Tastiera</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Visualizza Avvisi</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Visualizza Annunci</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Visualizza Notifiche</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Livello di Log</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Categorie Log</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Log Eventi Accessibilità</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Log Layout Schermo</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Log Eventi Tastiera</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Log Eventi Non Gestiti</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Dispositivo Selezionato</string>
+  <string name="DEVICES_BUTTON_ADD">Aggiungi Dispositivo</string>
+  <string name="DEVICES_BUTTON_REMOVE">Rimuovi Dispositivo</string>
+
+  <string name="SELECTED_DEVICE_NONE">nessun dispositivo</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">dispositivo non selezionato</string>
+
+  <string name="ADD_DEVICE_NAME">Nome Dispositivo</string>
+  <string name="ADD_DEVICE_METHOD">Modalità di Collegamento</string>
+  <string name="ADD_DEVICE_SELECT">Seleziona Dispositivo</string>
+  <string name="ADD_DEVICE_DRIVER">Driver Braille</string>
+  <string name="ADD_DEVICE_DONE">Completato</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">modalità di comunicazione non selezionata</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">dispositivo non selezionato</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">driver braille non selezionato</string>
+  <string name="ADD_DEVICE_NO_DEVICES">nessun dispositivo</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">nome dispositivo già in uso</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">accesso al dispositivo negato</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Sei sicuro di voler rimuovere questo dispositivo?</string>
+
+  <string name="SET_SELECTION_NONE">nessuna selezione</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Elenco Verticale</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Griglia Bidimensionale</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Nativo</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Seriale</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Debug</string>
+  <string name="LOG_LEVEL_LABEL_information">Informazioni</string>
+  <string name="LOG_LEVEL_LABEL_notice">Notifica</string>
+  <string name="LOG_LEVEL_LABEL_warning">Avviso</string>
+  <string name="LOG_LEVEL_LABEL_error">Errore</string>
+  <string name="LOG_LEVEL_LABEL_critical">Critico</string>
+  <string name="LOG_LEVEL_LABEL_alert">Allarme</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Emergenza</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Pacchetti in Ingresso</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Pacchetti in uscita</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Eventi Chiave Dispositivo Braille</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Eventi Chiave Tastiera</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Tracciamento Cursore</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Cursor Routing</string>
+  <string name="LOG_CATEGORY_LABEL_update">Eventi di Aggiornamento</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Eventi Audio</string>
+  <string name="LOG_CATEGORY_LABEL_async">Eventi Asincroni</string>
+  <string name="LOG_CATEGORY_LABEL_server">Eventi Server</string>
+  <string name="LOG_CATEGORY_LABEL_gio">I/O generico</string>
+  <string name="LOG_CATEGORY_LABEL_serial">I/O Seriale</string>
+  <string name="LOG_CATEGORY_LABEL_usb">I/O USB</string>
+  <string name="LOG_CATEGORY_LABEL_bt">I/O Bluetooth</string>
+  <string name="LOG_CATEGORY_LABEL_hid">I/O Interfaccia Uomo-Macchina</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Eventi Driver Braille</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Eventi Driver Voce</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Eventi Driver Schermo</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Rilevamento Automatico</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Selezione Automatica in base al locale</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabo (generico)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamese</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgaro</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetano</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (Per visuaizzar file .brf all\'intero di un editr o di un pager)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Ceco</string>
+  <string name="TEXT_TABLE_LABEL_cy">Gallese</string>
+  <string name="TEXT_TABLE_LABEL_da">Danese</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danese (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danese (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Tedesco</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidico</string>
+  <string name="TEXT_TABLE_LABEL_el">Greco</string>
+  <string name="TEXT_TABLE_LABEL_en">Inglese</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Inglese (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Inglese (Regno Unito)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Inglese (North American Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Inglese (Stati Uniti)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Spanish</string>
+  <string name="TEXT_TABLE_LABEL_et">Estone</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finlandese</string>
+  <string name="TEXT_TABLE_LABEL_fr">Francese</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Francese (unificato 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Francese (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Francese (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Francese (Francia)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Francese (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irlandese</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaelico</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Ebraico</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Croato</string>
+  <string name="TEXT_TABLE_LABEL_hu">Ungherese</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armeno</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandese</string>
+  <string name="TEXT_TABLE_LABEL_it">Italiano</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Lituano</string>
+  <string name="TEXT_TABLE_LABEL_lv">Lettone</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltese</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepali</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Olandese</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Olandese (Belgio)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Olandese (Olanda)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norvegese</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norvegese (con supporto pr altre lingue)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norvegese (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (old)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Polacco</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portoghese</string>
+  <string name="TEXT_TABLE_LABEL_ro">Romeno</string>
+  <string name="TEXT_TABLE_LABEL_ru">Russo</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanscrito</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Sami (Settentrionale)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovacco</string>
+  <string name="TEXT_TABLE_LABEL_sl">Sloveno</string>
+  <string name="TEXT_TABLE_LABEL_sv">Svedese</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Svedese (standard 1989)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Swedish (standard 1996)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turco</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ucraino</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamita</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">inversione colore primo piano nella colonna sinistra e colore sfondo nella colonna destra</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">colore primo piano nella colonna sinistra e colore sfondo nella colonna destra</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">colore primo piano nel riquadro superiore e colore sfondo nel riquadro inferiore</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Selezione Automatica in base al locale</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikaans (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amarico (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Tedesco</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Tedesco (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Tesesco(contrazioni di base)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Tesesco (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Tesesco (contratto - standard 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Tesesco (contratto - standard 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Inglese</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Inglese (Stati Uniti)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Inglese (Unificato, non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Inglese (Unified English Braille, grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Inglese (USA, gradO 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Spagnolo (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Francese</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Francese (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Francese (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesiano (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Alfabeto Fonetico Internazionale</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Giapponese (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Coreano</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Coreano (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Coreano (grado 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Coreano (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Lituano (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Olandese (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portoghese (grado 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Russo (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalese (non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thai (contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Cinese (Taiwan, non contratto)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (contratto)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">combinazioni tasti per tastiere braille</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">combinazioni tasti per tastiere estese</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">combinazioni di tasti per navigazione da tastierino numerico</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">combinazioni di tasti per tastiere senza tastierino numerico</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">combinazioni di tasti per tastiere di sottotipo 6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-nb/defaults.xml b/Android/Gradle/app/src/main/res/values-nb/defaults.xml
new file mode 100644
index 0000000..55544f8
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-nb/defaults.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="DEFAULT_TEXT_TABLE">no</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-nb/strings.xml b/Android/Gradle/app/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..a3784cd
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-nb/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY støtter punktskriftenheter.</string>
+
+  <string name="usbMonitor_label">Observere tilkoplet USB-enhet</string>
+
+  <string name="about_label_activity">Om programmet</string>
+  <string name="about_label_copyright">Opphavsrett</string>
+  <string name="about_label_buildTime">Byggetid</string>
+  <string name="about_label_sourceRevision">kildekodeutgave</string>
+  <string name="about_label_privacyPolicy">Personvernregler</string>
+
+  <string name="inputService_name">Inndatatjeneste for BRLTTY</string>
+  <string name="inputService_type">Tastatur på punktskriftenheter</string>
+  <string name="inputService_not_enabled">inndatatjenesten er ikke aktivert</string>
+  <string name="inputService_not_selected">inndatatjenesten er ikke valgt</string>
+  <string name="inputService_not_started">inndatatjenesten er ikke startet</string>
+  <string name="inputService_not_connected">inndatatjenesten er ikke tilkoblet</string>
+
+  <string name="updateApplication_problem_failed">programmet er ikke oppdatert</string>
+
+  <string name="packageInstaller_problem_failed">pakken er ikke installert</string>
+  <string name="packageInstaller_problem_same">samme versjon</string>
+  <string name="packageInstaller_problem_downgrade">nedgradering er ikke tillatt</string>
+
+  <string name="fileDownloader_title">BRLTTY filnedlaster</string>
+  <string name="fileDownloader_problem_failed">fila er ikke lastet ned</string>
+  <string name="fileDownloader_state_connecting">kobler til</string>
+  <string name="fileDownloader_state_downloading">laster ned</string>
+
+  <string name="setting_state_off">av</string>
+  <string name="checkbox_state_unchecked">nei</string>
+  <string name="checkbox_state_checked">ja</string>
+
+  <string name="braille_channel_name">Punktskriftenhetsstatus</string>
+  <string name="braille_hint_tap">trykk for handlinger</string>
+  <string name="braille_state_released">Frigjort</string>
+  <string name="braille_state_waiting">Venter</string>
+  <string name="braille_state_connected">Tilkoblet</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Varslinger</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Hurtiginnstillinger</string>
+  <string name="GLOBAL_BUTTON_BACK">Tilbake</string>
+  <string name="GLOBAL_BUTTON_HOME">Hjem</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Nylig brukte apper</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Oversikt</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Bytt inndatametode</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Vis brukerveiledning</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Bla gjennom nettstedet</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Bla gjennom fellesskapsmeldinger</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Post fellesskapsmelding</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Administrer fellesskapsmedlemskap</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Oppdater programmet</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Utviklerutgave</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Tillat nedgradering</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Tilgjengelighetshandlinger</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Innstillinger for BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Generelle innstillinger</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Meldingsinnstillinger</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Administrere enheter</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Avanserte innstillinger</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Frigjør punktskriftenheten</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigasjonsmodus</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Støtte for tale</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Teksttabell</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributtabell</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Kortskrifttabell</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tastaturtabell</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Vis advarsler</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Vis meldinger</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Vis varsler</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Loggenivå</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Loggekategorier</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Logg tilgjengelighetshendelser</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Logg gjengitt skjermbilde</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Logg tastaturhendelser</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Logg ikke-håndterte hendelser</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Valgt enhet</string>
+  <string name="DEVICES_BUTTON_ADD">Legg til enhet</string>
+  <string name="DEVICES_BUTTON_REMOVE">Fjern enhet</string>
+
+  <string name="SELECTED_DEVICE_NONE">ingen enheter</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">enheten er ikke valgt</string>
+
+  <string name="ADD_DEVICE_NAME">Enhetsnavn</string>
+  <string name="ADD_DEVICE_METHOD">Kommunikasjonsmetode</string>
+  <string name="ADD_DEVICE_SELECT">Velg enhet</string>
+  <string name="ADD_DEVICE_DRIVER">Punktskriftdriver</string>
+  <string name="ADD_DEVICE_DONE">Ferdig</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">kommunikasjonsmetoden er ikke valgt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">enheten er ikke valgt</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">punktskriftdriveren er ikke valgt</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ingen enheter</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">enhetsnavnet er allerede i bruk</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">tilgang til enheten er ikke gitt</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Vil du virkelig fjerne denne enheten?</string>
+
+  <string name="SET_SELECTION_NONE">ingenting er valgt</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Vertikal liste</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Todimensjonalt rutenett</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">original</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Seriell</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Feilretting</string>
+  <string name="LOG_LEVEL_LABEL_information">Informasjon</string>
+  <string name="LOG_LEVEL_LABEL_notice">Note</string>
+  <string name="LOG_LEVEL_LABEL_warning">Advarsel</string>
+  <string name="LOG_LEVEL_LABEL_error">Feil</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritisk</string>
+  <string name="LOG_LEVEL_LABEL_alert">Varsel</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Nødstilfelle</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Inndatapakker</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Utdatapakker</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Hendelser knyttet til taster på en punktskriftenhet</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Hendelser knyttet til taster på et tastatur</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Markørfølging</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Markørhenting</string>
+  <string name="LOG_CATEGORY_LABEL_update">Hendelser knyttet til oppdatering</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Hendelser knyttet til tale</string>
+  <string name="LOG_CATEGORY_LABEL_async">Hendelser knyttet til asynkron kommunikasjon</string>
+  <string name="LOG_CATEGORY_LABEL_server">Hendelser knyttet til tjeneren</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Vanlige inn- eller utdata</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serielle inn- eller utdata</string>
+  <string name="LOG_CATEGORY_LABEL_usb">Inn- eller utdata for USB</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Inn- eller utdata for Bluetooth</string>
+  <string name="LOG_CATEGORY_LABEL_hid">HID (Human Interface) inn- eller utdata</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Hendelser med punktskriftdriveren</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Hendelser med taledriveren</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Hendelser med skjermdriveren</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">automatisk gjenkjenning</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">lokalitetsbasert, automatisk valg</string>
+  <string name="TEXT_TABLE_LABEL_ar">arabisk (generelt)</string>
+  <string name="TEXT_TABLE_LABEL_as">assamesisk</string>
+  <string name="TEXT_TABLE_LABEL_awa">awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">bulgarsk</string>
+  <string name="TEXT_TABLE_LABEL_bh">bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">tibetansk</string>
+  <string name="TEXT_TABLE_LABEL_bra">braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Format (for visning av .brf-filer i et redigerings- eller visningsprogram)</string>
+  <string name="TEXT_TABLE_LABEL_cs">tsjekkisk</string>
+  <string name="TEXT_TABLE_LABEL_cy">walisisk</string>
+  <string name="TEXT_TABLE_LABEL_da">dansk</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">dansk (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">dansk (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">tysk</string>
+  <string name="TEXT_TABLE_LABEL_dra">dravidisk</string>
+  <string name="TEXT_TABLE_LABEL_el">gresk</string>
+  <string name="TEXT_TABLE_LABEL_en">engelsk</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">engelsk (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">engelsk (Storbritania)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">engelsk (North American Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">engelsk (USA)</string>
+  <string name="TEXT_TABLE_LABEL_eo">esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">spansk</string>
+  <string name="TEXT_TABLE_LABEL_et">estisk</string>
+  <string name="TEXT_TABLE_LABEL_fi">finsk</string>
+  <string name="TEXT_TABLE_LABEL_fr">fransk</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">fransk (enhetlig 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">fransk (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">fransk (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">fransk (Frankrike)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">fransk (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">irsk</string>
+  <string name="TEXT_TABLE_LABEL_gd">gælisk</string>
+  <string name="TEXT_TABLE_LABEL_gon">gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">hebraisk</string>
+  <string name="TEXT_TABLE_LABEL_hi">hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">kroatisk</string>
+  <string name="TEXT_TABLE_LABEL_hu">ungarsk</string>
+  <string name="TEXT_TABLE_LABEL_hy">armensk</string>
+  <string name="TEXT_TABLE_LABEL_is">islandsk</string>
+  <string name="TEXT_TABLE_LABEL_it">italiensk</string>
+  <string name="TEXT_TABLE_LABEL_kha">khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">litauisk</string>
+  <string name="TEXT_TABLE_LABEL_lv">latvisk</string>
+  <string name="TEXT_TABLE_LABEL_mg">madagassisk</string>
+  <string name="TEXT_TABLE_LABEL_mi">maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">maltesisk</string>
+  <string name="TEXT_TABLE_LABEL_mun">munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">nepalsk</string>
+  <string name="TEXT_TABLE_LABEL_new">newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">nederlandsk</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">nederlandsk (Belgia)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">nederlandsk (Nederland)</string>
+  <string name="TEXT_TABLE_LABEL_no">norsk</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">norsk (med støtte for andre språk)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">norsk (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">newari (gammelt)</string>
+  <string name="TEXT_TABLE_LABEL_or">oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">polsk</string>
+  <string name="TEXT_TABLE_LABEL_pt">portugisisk</string>
+  <string name="TEXT_TABLE_LABEL_ro">rumensk</string>
+  <string name="TEXT_TABLE_LABEL_ru">russisk</string>
+  <string name="TEXT_TABLE_LABEL_sa">sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">samisk (nordsamisk)</string>
+  <string name="TEXT_TABLE_LABEL_sk">slovakisk</string>
+  <string name="TEXT_TABLE_LABEL_sl">slovensk</string>
+  <string name="TEXT_TABLE_LABEL_sv">svensk</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">svensk (1989-standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">svensk (1996-standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">tyrkisk</string>
+  <string name="TEXT_TABLE_LABEL_uk">ukrainsk</string>
+  <string name="TEXT_TABLE_LABEL_vi">vietnamesisk</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">omvendt forgrunnsfarge i venstre kolonne og bakgrunnsfarge i høyre kolonne</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">forgrunnsfarge i venstre kolonne og bakgrunnsfarge i høyre kolonne</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">forgrunnsfarge i øverste del og bakgrunnsfarge i nederste del</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">lokalitetsbasert, automatisk valg</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">afrikaans (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">amharisk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">tysk</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">tysk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">tysk (enkel kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">tysk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">tysk (kortskrift - 1998-standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">tysk (kortskrift - 2015-standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">engelsk</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">engelsk (USA)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">engelsk (enhetlig, fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">engelsk (enhetlig, kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">engelsk (Forente stater, kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">spansk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">fransk</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">fransk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">fransk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">hausa (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">indonesisk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">internasjonalt fonetisk alfabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">japansk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">koreansk</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">koreansk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">koreansk (enkel kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">koreansk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">latvisk (uforkortet)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">madagassisk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">munda (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">nederlandsk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">chichewa (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">portugisisk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">russisk (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">singalesisk (fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">swahili (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">thai (kortskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">kinesisk (Taiwan, fullskrift)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">zulu (kortskrift)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">alternativ for punktskrifttastatur</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">alternativ for fullt tastatur</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">alternativ for talltastaturbasert navigasjon</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">alternativ for tastatur uten talltastatur</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">alternativ for Sun type 6 tastatur</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-nl/strings.xml b/Android/Gradle/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..7ffda6e
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY ondersteunt brailleapparaten.</string>
+
+  <string name="usbMonitor_label">USB Apparaat Aangesloten Monitor</string>
+
+  <string name="about_label_activity">Over applicatie</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Moment Van Build</string>
+  <string name="about_label_sourceRevision">Bronherziening</string>
+  <string name="about_label_privacyPolicy">Privacyverklaring</string>
+
+  <string name="inputService_name">BRLTTY InvoerService</string>
+  <string name="inputService_type">Toetsenborden op brailleapparaten</string>
+  <string name="inputService_not_enabled">invoerservice niet geactiveerd</string>
+  <string name="inputService_not_selected">invoerservice niet geselecteerd</string>
+  <string name="inputService_not_started">invoerservice niet gestart</string>
+  <string name="inputService_not_connected">invoerservice niet verbonden</string>
+
+  <string name="updateApplication_problem_failed">applicatie niet geüpdate</string>
+
+  <string name="packageInstaller_problem_failed">pakket niet geïnstalleerd</string>
+  <string name="packageInstaller_problem_same">zelfde versie</string>
+  <string name="packageInstaller_problem_downgrade">downgrade niet toegestaan</string>
+
+  <string name="fileDownloader_title">BRLTTY BestandsDownloader</string>
+  <string name="fileDownloader_problem_failed">bestand niet gedownload</string>
+  <string name="fileDownloader_state_connecting">bezig met verbinden</string>
+  <string name="fileDownloader_state_downloading">bezig met downloaden</string>
+
+  <string name="setting_state_off">Uit</string>
+  <string name="checkbox_state_unchecked">Nee</string>
+  <string name="checkbox_state_checked">Ja</string>
+
+  <string name="braille_channel_name">Toestand Van Braille-apparaat</string>
+  <string name="braille_hint_tap">tik voor acties</string>
+  <string name="braille_state_released">Vrijgegeven</string>
+  <string name="braille_state_waiting">Aan het wachten</string>
+  <string name="braille_state_connected">Verbonden</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Meldingen</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Snelle Instellingen</string>
+  <string name="GLOBAL_BUTTON_BACK">Terug</string>
+  <string name="GLOBAL_BUTTON_HOME">Home</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Recente Apps</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Overzicht</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Invoermethode Schakelen</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Gebruikershandleiding</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">WebSite</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Berichten In De Community</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Plaats Een Communitybericht</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Beheer Communitylidmaatschap</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Update Applicatie</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Ontwikkelaarsbuild</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Downgraden Toestaan</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Toegankelijkheidsacties</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Instellingen</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Algemene Instellingen</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Instellingen voor berichten</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Beheer Apparaten</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Geavanceerde Instellingen</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Geef Brailleapparaat vrij</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigatiemodus</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Spraakondersteuning</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Teksttabel</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributentabel</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Contractietabel</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Toetsenbordtabel</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Toon Alerts</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Toon Aankondigingen</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Toon Meldingen</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Logniveau</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Logcategorieën</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Log Toegankelijkheidsgebeurtenissen</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Log Rendered Scherm</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Log Toetsenbordgebeurtenissen</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Log onverwerkte gebeurtenissen</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Geselecteerd Apparaat</string>
+  <string name="DEVICES_BUTTON_ADD">Voeg Apparaat Toe</string>
+  <string name="DEVICES_BUTTON_REMOVE">Verwijder Apparaat</string>
+
+  <string name="SELECTED_DEVICE_NONE">geen apparaten</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">apparaat niet geselecteerd</string>
+
+  <string name="ADD_DEVICE_NAME">Apparaatnaam</string>
+  <string name="ADD_DEVICE_METHOD">Communicatiemethode</string>
+  <string name="ADD_DEVICE_SELECT">Selecteer Apparaat</string>
+  <string name="ADD_DEVICE_DRIVER">Braille Driver</string>
+  <string name="ADD_DEVICE_DONE">Gereed</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">Communicatiemethode niet geselecteerd</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">apparaat niet geselecteerd</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">braille driver niet geselecteerd</string>
+  <string name="ADD_DEVICE_NO_DEVICES">geen apparaten</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">apparaatnaam al in gebruik</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">TOESTEMMING toegang tot apparaat niet verleend</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Weet u zeker dat u dit apparaat wilt verwijderen?</string>
+
+  <string name="SET_SELECTION_NONE">niets geselecteerd</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Verticale Lijst</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Tweedimensionaal Raster</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Standaard</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Serieel</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Debug</string>
+  <string name="LOG_LEVEL_LABEL_information">Informatie</string>
+  <string name="LOG_LEVEL_LABEL_notice">Melding</string>
+  <string name="LOG_LEVEL_LABEL_warning">Waarschuwing</string>
+  <string name="LOG_LEVEL_LABEL_error">Fout</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritiek</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alert</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Alarm</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Input Packets</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Output Packets</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Braille Device Key Events</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Keyboard Key Events</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Cursor Tracking</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Cursor Routing</string>
+  <string name="LOG_CATEGORY_LABEL_update">Update Events</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Speech Events</string>
+  <string name="LOG_CATEGORY_LABEL_async">Async Events</string>
+  <string name="LOG_CATEGORY_LABEL_server">Server Events</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Algemeen I/O</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serial I/O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Menselijk Interface I/O</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Braille Driver Events</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Speech Driver Events</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Screen Driver Events</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">autodetect</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">locaal gebaseerde autoselectie</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabisch (standaard)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assamees</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgaars</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetaans</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Braille Ready Formaat (voor het bekijken van .brf bestanden in een tekstverwerker of pager)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Czechisch</string>
+  <string name="TEXT_TABLE_LABEL_cy">Welsh</string>
+  <string name="TEXT_TABLE_LABEL_da">Deens</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Deens (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Deens (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Duits</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidiaans</string>
+  <string name="TEXT_TABLE_LABEL_el">Grieks</string>
+  <string name="TEXT_TABLE_LABEL_en">Engels</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Engels (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Engels (Verenigd Koninkrijk)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Engels (North American Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Engels (Verenigde Staten)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Spaans</string>
+  <string name="TEXT_TABLE_LABEL_et">Estlands</string>
+  <string name="TEXT_TABLE_LABEL_fi">Fins</string>
+  <string name="TEXT_TABLE_LABEL_fr">Frans</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Frans (unified 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Frans (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Frans (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Frans (Frankrijk)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Frans (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Iers</string>
+  <string name="TEXT_TABLE_LABEL_gd">Gaelic</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebreeuws</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr">Croatisch</string>
+  <string name="TEXT_TABLE_LABEL_hu">Hongaars</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armeens</string>
+  <string name="TEXT_TABLE_LABEL_is">Ijslands</string>
+  <string name="TEXT_TABLE_LABEL_it">Italiaans</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litauws</string>
+  <string name="TEXT_TABLE_LABEL_lv">Lets</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltees</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepali</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Nederlands</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Nederlands (België)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Nederlands (Nederland)</string>
+  <string name="TEXT_TABLE_LABEL_no">Noors</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Noors (met ondersteuning voor andere talen)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Noors (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (oud)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Pools</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugees</string>
+  <string name="TEXT_TABLE_LABEL_ro">Roemeens</string>
+  <string name="TEXT_TABLE_LABEL_ru">Russisch</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Sami (Noordelijk)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovaaks</string>
+  <string name="TEXT_TABLE_LABEL_sl">Sloveens</string>
+  <string name="TEXT_TABLE_LABEL_sv">Zweeds</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Zweeds (1989 standaard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Zweeds (1996 standaard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turks</string>
+  <string name="TEXT_TABLE_LABEL_uk">Oekraiens</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vietnamees</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">geïnverteerd voorgrondkleur in de linkerkolom en achtergrondkleur in de rechterkolom</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">voorgrondkleur in de linkerkolom en achtergrondkleur in de rechterkolom</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">voorgrondkleur in bovenste vierkant en achtergrondkleur in onderste vierkant</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">locaal gebaseerde autoselectie</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikaans (Verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharic (Onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Duits</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Duits (Onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Duits (standaard samentrekkingen)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Duits (Verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Duits (verkort - 1998 standaard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Duits (verkort - 2015 standaard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Engels</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Engels (Verenigde Staten)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Engels (verenigd, onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Engels (Unified English Braille, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Engels (VS, grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Spaans (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Frans</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Frans (onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Frans (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonesisch (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Internationaal Phonetisch Alfabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japans (onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Koreaans</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Koreaans (onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Koreaans (grade 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Koreaans (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Litauws (onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Nederlands (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugees (grade 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Russisch (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalese (onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Thais (verkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Chinees (Taiwan, onverkort)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (verkort)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">layout voor brailletoetsenborden</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">layout voor volledige toetsenborden</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">layout voor toetsenblokgebaseerde navigatie</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">layout voor toetsenborden zonder keypad</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">layout voor Sun Type 6 toetsenborden</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-ru/strings.xml b/Android/Gradle/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..2a93657
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY поддерживает брайлевские устройства.</string>
+
+  <string name="usbMonitor_label">Монитор подключён к USB-устройству</string>
+
+  <string name="about_label_activity">О приложении</string>
+  <string name="about_label_copyright">Авторские права</string>
+  <string name="about_label_buildTime">Время сборки</string>
+  <string name="about_label_sourceRevision">Версия исходного кода</string>
+  <string name="about_label_privacyPolicy">Политика конфиденциальности</string>
+
+  <string name="inputService_name">Служба ввода BRLTTY</string>
+  <string name="inputService_type">Клавиатуры на брайлевских устройствах</string>
+  <string name="inputService_not_enabled">служба ввода не включена</string>
+  <string name="inputService_not_selected">служба ввода не выбрана</string>
+  <string name="inputService_not_started">служба ввода не запущена</string>
+  <string name="inputService_not_connected">служба ввода не подключена</string>
+
+  <string name="updateApplication_problem_failed">приложение не обновлено</string>
+
+  <string name="packageInstaller_problem_failed">пакет не установлен</string>
+  <string name="packageInstaller_problem_same">та же версия</string>
+  <string name="packageInstaller_problem_downgrade">понижение версии не допускается</string>
+
+  <string name="fileDownloader_title">Загрузчик файлов BRLTTY</string>
+  <string name="fileDownloader_problem_failed">файл не загружен</string>
+  <string name="fileDownloader_state_connecting">подключение</string>
+  <string name="fileDownloader_state_downloading">скачивание</string>
+
+  <string name="setting_state_off">Выключено</string>
+  <string name="checkbox_state_unchecked">Нет</string>
+  <string name="checkbox_state_checked">Да</string>
+
+  <string name="braille_channel_name">Состояние брайлевского устройства</string>
+  <string name="braille_hint_tap">нажмите для выполнения действия</string>
+  <string name="braille_state_released">Разъединено</string>
+  <string name="braille_state_waiting">Ожидание</string>
+  <string name="braille_state_connected">Подключено</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Уведомления</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Быстрые настройки</string>
+  <string name="GLOBAL_BUTTON_BACK">Назад</string>
+  <string name="GLOBAL_BUTTON_HOME">Главная страница</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Недавние приложения</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Обзор</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Переключение метода ввода</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Просмотр руководства пользователя</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Просмотр web-сайта</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Просмотр сообщений сообщества</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Отправка сообщений сообщества</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Управление членством в сообществе</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Обновление приложения</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Сборка разработчика</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Разрешить переход на более раннюю версию</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Действия специальных возможностей</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">Настройки BRLTTY</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Общие настройки</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Настройки сообщений</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Управление устройствами</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Расширенные настройки</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Разъединение брайлевского устройства</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Режим навигации</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Поддержка речи</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Текстовая таблица</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Таблица атрибутов</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Таблица краткописи</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Клавиатурная таблица</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Показывать предупреждения</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Показывать извещения</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Показывать уведомления</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Уровень журнала</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Категории журнала</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Log событий доступности</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Log экранного представления</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Log клавиатурных событий</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Журнал необработанных событий</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Выбранное устройство</string>
+  <string name="DEVICES_BUTTON_ADD">Добавить устройство</string>
+  <string name="DEVICES_BUTTON_REMOVE">Удалить устройство</string>
+
+  <string name="SELECTED_DEVICE_NONE">ни одного устройства</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">устройство не выбрано</string>
+
+  <string name="ADD_DEVICE_NAME">Имя устройства</string>
+  <string name="ADD_DEVICE_METHOD">Способ связи</string>
+  <string name="ADD_DEVICE_SELECT">Выбор устройства</string>
+  <string name="ADD_DEVICE_DRIVER">Брайлевский драйвер</string>
+  <string name="ADD_DEVICE_DONE">Выполнено</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">способ связи не выбран</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">устройство не выбрано</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">брайлевский драйвер не выбран</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ни одного устройства</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">имя устройства уже используется</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">доступ к устройству не предоставлен</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Вы, действительно, хотите удалить это устройство?</string>
+
+  <string name="SET_SELECTION_NONE">не выбрано</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Вертикальный список</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Двумерная сетка</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Нативная</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Последовательный порт</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Отладка</string>
+  <string name="LOG_LEVEL_LABEL_information">Информация</string>
+  <string name="LOG_LEVEL_LABEL_notice">Уведомление</string>
+  <string name="LOG_LEVEL_LABEL_warning">Предупреждение</string>
+  <string name="LOG_LEVEL_LABEL_error">Ошибка</string>
+  <string name="LOG_LEVEL_LABEL_critical">Критический</string>
+  <string name="LOG_LEVEL_LABEL_alert">Оповещение</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Авария</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Входной пакет</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Выходной пакет</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Ключевые события брайлевского устройства</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Ключевые события клавиатуры</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Отслеживание курсора</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Маршрутизация курсора</string>
+  <string name="LOG_CATEGORY_LABEL_update">Обновление событий</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Речевые события</string>
+  <string name="LOG_CATEGORY_LABEL_async">Асинхронные события</string>
+  <string name="LOG_CATEGORY_LABEL_server">Серверные события</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Общий ввод/вывод</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Последовательный I/O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Ввод/вывод интерфейса пользователя</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">События брайлевского драйвера</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">События речевого драйвера</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">События драйвера программы экранного доступа</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">автоопределение</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">автовыбор языковой основы</string>
+  <string name="TEXT_TABLE_LABEL_ar">Арабский (общий)</string>
+  <string name="TEXT_TABLE_LABEL_as">Ассамский</string>
+  <string name="TEXT_TABLE_LABEL_awa">Авадхи</string>
+  <string name="TEXT_TABLE_LABEL_bg">Болгарский</string>
+  <string name="TEXT_TABLE_LABEL_bh">Бихари</string>
+  <string name="TEXT_TABLE_LABEL_bn">Бенгальский</string>
+  <string name="TEXT_TABLE_LABEL_bo">Тибетский</string>
+  <string name="TEXT_TABLE_LABEL_bra">Брадж</string>
+  <string name="TEXT_TABLE_LABEL_brf">Готовый брайлевский формат (для просмотра файлов .brf в редакторе или пейджере)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Чешский</string>
+  <string name="TEXT_TABLE_LABEL_cy">Валлийский</string>
+  <string name="TEXT_TABLE_LABEL_da">Датский</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Датский (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Датский (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Немецкий</string>
+  <string name="TEXT_TABLE_LABEL_dra">Дравидийский</string>
+  <string name="TEXT_TABLE_LABEL_el">Греческий</string>
+  <string name="TEXT_TABLE_LABEL_en">Английский</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Английский (Канада)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Английский (Великобритания)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Английский (Североамериканский ASCII код для представления шрифта Брайля)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Английский (США)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Эсперанто</string>
+  <string name="TEXT_TABLE_LABEL_es">Испанский</string>
+  <string name="TEXT_TABLE_LABEL_et">Эстонский</string>
+  <string name="TEXT_TABLE_LABEL_fi">Финский</string>
+  <string name="TEXT_TABLE_LABEL_fr">Французский</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Французский (унифицированный 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Французский (Канада)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Французский (французский стандартный компьютерный код Брайля)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Французский (Франция)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Французский (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Ирландский</string>
+  <string name="TEXT_TABLE_LABEL_gd">Гэльский</string>
+  <string name="TEXT_TABLE_LABEL_gon">Гонди</string>
+  <string name="TEXT_TABLE_LABEL_gu">Гуджарати</string>
+  <string name="TEXT_TABLE_LABEL_he">Иврит</string>
+  <string name="TEXT_TABLE_LABEL_hi">Хинди</string>
+  <string name="TEXT_TABLE_LABEL_hr">Хорватский</string>
+  <string name="TEXT_TABLE_LABEL_hu">Венгерский</string>
+  <string name="TEXT_TABLE_LABEL_hy">Армянский</string>
+  <string name="TEXT_TABLE_LABEL_is">Исландский</string>
+  <string name="TEXT_TABLE_LABEL_it">Итальянский</string>
+  <string name="TEXT_TABLE_LABEL_kha">Хаси</string>
+  <string name="TEXT_TABLE_LABEL_kn">Каннаде</string>
+  <string name="TEXT_TABLE_LABEL_kok">Конкани</string>
+  <string name="TEXT_TABLE_LABEL_kru">Курукх</string>
+  <string name="TEXT_TABLE_LABEL_lt">Литовский</string>
+  <string name="TEXT_TABLE_LABEL_lv">Латвийский</string>
+  <string name="TEXT_TABLE_LABEL_mg">Малагасийский</string>
+  <string name="TEXT_TABLE_LABEL_mi">Маори</string>
+  <string name="TEXT_TABLE_LABEL_ml">Малаялам</string>
+  <string name="TEXT_TABLE_LABEL_mni">Манипури</string>
+  <string name="TEXT_TABLE_LABEL_mr">Маратхи</string>
+  <string name="TEXT_TABLE_LABEL_mt">Мальтийский</string>
+  <string name="TEXT_TABLE_LABEL_mun">Мунда</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Марвари</string>
+  <string name="TEXT_TABLE_LABEL_ne">Непальский</string>
+  <string name="TEXT_TABLE_LABEL_new">Невари</string>
+  <string name="TEXT_TABLE_LABEL_nl">Голландский</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Голландский (Бельгия)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Голландский (Нидерланды)</string>
+  <string name="TEXT_TABLE_LABEL_no">Норвежский</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Норвежский (с поддержкой других языков)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Норвежский (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Невари (старый)</string>
+  <string name="TEXT_TABLE_LABEL_or">Ория</string>
+  <string name="TEXT_TABLE_LABEL_pa">Панджаби</string>
+  <string name="TEXT_TABLE_LABEL_pi">Пали</string>
+  <string name="TEXT_TABLE_LABEL_pl">Польский</string>
+  <string name="TEXT_TABLE_LABEL_pt">Португальский</string>
+  <string name="TEXT_TABLE_LABEL_ro">Румынский</string>
+  <string name="TEXT_TABLE_LABEL_ru">Русский</string>
+  <string name="TEXT_TABLE_LABEL_sa">Санскрит</string>
+  <string name="TEXT_TABLE_LABEL_sat">Сантали</string>
+  <string name="TEXT_TABLE_LABEL_sd">Синдхов</string>
+  <string name="TEXT_TABLE_LABEL_se">Северносаамский</string>
+  <string name="TEXT_TABLE_LABEL_sk">Словацкий</string>
+  <string name="TEXT_TABLE_LABEL_sl">Словенский</string>
+  <string name="TEXT_TABLE_LABEL_sv">Шведский</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Шведский (стандарт 1989)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Шведский (стандарт 1996)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Суахили</string>
+  <string name="TEXT_TABLE_LABEL_ta">Тамил</string>
+  <string name="TEXT_TABLE_LABEL_te">Телугу</string>
+  <string name="TEXT_TABLE_LABEL_tr">Турецкий</string>
+  <string name="TEXT_TABLE_LABEL_uk">Украинский</string>
+  <string name="TEXT_TABLE_LABEL_vi">Вьетнамский</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">инверсный цвет переднего плана в левой колонке и цвет фона в правой колонке</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">цвет переднего плана в левой колонке и цвет фона в правой колонке</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">цвет переднего плана в верхнем квадрате и цвет фона в нижнем квадрате</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">автовыбор языковой основы</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Африкаанс (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Амхарский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Немецкий</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Немецкий (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Немецкий (базовые сокращения)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Немецкий (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Немецкий (сокращённый - стандарт 1998)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Немецкий (сокращённый - стандарт 2015)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Английский</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Английский (США)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Английский (унифицированный, несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Английский (унифицированный английский Брайль, ступень 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">Английский (США, ступень 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Испанский (ступень 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Французский</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Французский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Французский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Хауса (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Индонезийский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Международный фонетический алфавит</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Японский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Корейский</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Корейский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Корейский (ступень 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Корейский (ступень 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Литовский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Малагасийский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Мунда (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Голландский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Чичева (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Португальский (ступень 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Русский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Сингальский (несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Суахили (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Тайский (сокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Китайский (Тайвань, несокращённый)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Зулу (сокращённый)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">привязки для брайлевских клавиатур</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">привязки для полных клавиатур</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">привязки для кнопочной панели навигации</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">привязки для клавиатур с отсутствующим дополнительным блоком</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">привязки для клавиатур Sun Type 6</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-sl/strings.xml b/Android/Gradle/app/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..33ee391
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-sl/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY podpira brajeve naprave.</string>
+
+  <string name="usbMonitor_label">Spremljanje Priključene USB Naprave</string>
+
+  <string name="about_label_activity">O Programu</string>
+  <string name="about_label_copyright">Avtorske Pravice</string>
+  <string name="about_label_buildTime">Čas Graditve</string>
+  <string name="about_label_sourceRevision">Različica IzvorneKode</string>
+  <string name="about_label_privacyPolicy">Politika Zasebnosti</string>
+
+  <string name="inputService_name">BRLTTY Storitev Vnosa</string>
+  <string name="inputService_type">Tipkovnice na Brajevih Vrsticah</string>
+  <string name="inputService_not_enabled">storitev vnosa ni omogočena</string>
+  <string name="inputService_not_selected">storitev vnosa ni izbrana</string>
+  <string name="inputService_not_started">storitev vnosa ni zagnana</string>
+  <string name="inputService_not_connected">storitev vnosa ni priključena</string>
+
+  <string name="updateApplication_problem_failed">program se ni posodobil</string>
+
+  <string name="packageInstaller_problem_failed">paket ni nameščen</string>
+  <string name="packageInstaller_problem_same">enaka različica</string>
+  <string name="packageInstaller_problem_downgrade">podgradnja se ne dovoli</string>
+
+  <string name="fileDownloader_title">BRLTTY Prenos Datoteke</string>
+  <string name="fileDownloader_problem_failed">datoteka se ni prenesla</string>
+  <string name="fileDownloader_state_connecting">povezovanje</string>
+  <string name="fileDownloader_state_downloading">prenašanje</string>
+
+  <string name="setting_state_off">Izključeno</string>
+  <string name="checkbox_state_unchecked">Ne</string>
+  <string name="checkbox_state_checked">Da</string>
+
+  <string name="braille_channel_name">Stanje Brajeve Naprave</string>
+  <string name="braille_hint_tap">tapnite za dejanja</string>
+  <string name="braille_state_released">Sproščena</string>
+  <string name="braille_state_waiting">Čakajoča</string>
+  <string name="braille_state_connected">Priključena</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Obvestila</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Hitre Nastavitve</string>
+  <string name="GLOBAL_BUTTON_BACK">Nazaj</string>
+  <string name="GLOBAL_BUTTON_HOME">Domov</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Trenutne Aplikacije</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Pregled</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Preklopi Vnosno Metodo</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Glej Uporabniški Vodič</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Prebrskaj Spletno Mesto</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Prebrskaj Sporočila Skupnosti</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Objavi Sporočilo Skupnosti</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Upravljaj Članstvo v Skupnosti</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Posodobi Program</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Graditev za Razvijalce</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Dovoli Podgraditev</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Dejanja Dostopnosti</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Settings</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Splošne Nastavitve</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Nastavitve Sporočila</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Upravljaj Naprave</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Napredne Nastavitve</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Sprosti Brajevo Napravo</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Delovanje krmarjenja</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Govorna Podpora</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Tabela Besedila</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributes Table Tabela Prilastkov</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Tabela Krajšav</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Tabela Tipk</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Pokaži Opozorila</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Pokaži Naznanila</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Pokaži Obvestila</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Nivo Dnevnika</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Kategorije Dnevnikov</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Beleži Dogodke Dostopnosti</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Beleži Dogodke v Zvezi z Upodabljanjem na Zaslon</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Beleži Dogodke s Tipkovnico</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Zabeleži Neobravnavane Dogodke</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Izbrana Naprava</string>
+  <string name="DEVICES_BUTTON_ADD">Dodaj Napravo</string>
+  <string name="DEVICES_BUTTON_REMOVE">Odstrani Napravo</string>
+
+  <string name="SELECTED_DEVICE_NONE">brez naprave</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">device ni izbrana</string>
+
+  <string name="ADD_DEVICE_NAME">Ime Naprave</string>
+  <string name="ADD_DEVICE_METHOD">Metoda Komunikacije</string>
+  <string name="ADD_DEVICE_SELECT">Izberi Napravo</string>
+  <string name="ADD_DEVICE_DRIVER">Gonilnik Braja</string>
+  <string name="ADD_DEVICE_DONE">Končano</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">način komunikacije ni izbran</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">naprava ni izbrana</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">gonilnik brajeve vrstice ni izbran</string>
+  <string name="ADD_DEVICE_NO_DEVICES">ni naprav</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">ime naprave že v uporabi</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">dostop do naprave ni dodeljen</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Ali res želite odstraniti to napravo?</string>
+
+  <string name="SET_SELECTION_NONE">nič ni izbrano</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Navpični Seznam</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Dvodimenzionalna Mreža</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Naravna</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Serial</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Razhroščevanje</string>
+  <string name="LOG_LEVEL_LABEL_information">Informacije</string>
+  <string name="LOG_LEVEL_LABEL_notice">Sporočilo</string>
+  <string name="LOG_LEVEL_LABEL_warning">Opozorilo</string>
+  <string name="LOG_LEVEL_LABEL_error">Napaka</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritično</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alarm</string>
+  <string name="LOG_LEVEL_LABEL_emergency">V sili</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Vnosna Polja</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Izhodna Polja</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Dogodki v Zvezi s Tipkami Na Brajevi Napravi</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Dogodki v Zvezi s Tipkami na Tipkovnici</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Spremljanje Utripalke</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Klicanje Utripalke</string>
+  <string name="LOG_CATEGORY_LABEL_update">Dogodki Posodobitev</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Govorni Dogodki</string>
+  <string name="LOG_CATEGORY_LABEL_async">Asinhroni Dogodki</string>
+  <string name="LOG_CATEGORY_LABEL_server">Dogodki Strežnika</string>
+  <string name="LOG_CATEGORY_LABEL_gio">splošni V/I</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serijski V/I</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB V/I</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth V/I</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Human Vmesnik V/I</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Dogodki Gonilnika za Brajico</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Dogodki Gonilnika Govora</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Dogodki Zaslonskega Gonilnika</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">samodejna prepoznava</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">locale-based samodejna izbira</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arabska (generična)</string>
+  <string name="TEXT_TABLE_LABEL_as">Asameška</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bolgarska</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengalska</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetanska</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">za Brajev Izpis Pripravljena Oblika (Namenjena Pregledovanju .brf datotek v urejevalniku ali pregledovalniku)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Češka</string>
+  <string name="TEXT_TABLE_LABEL_cy">Valižanska</string>
+  <string name="TEXT_TABLE_LABEL_da">Danska</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danska (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danska (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Nemška</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravidska</string>
+  <string name="TEXT_TABLE_LABEL_el">Grška</string>
+  <string name="TEXT_TABLE_LABEL_en">Angleška</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">Angleška (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">Angleška (Združeno Kraljestvo)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">Angleška (SevernoAmeriška Brajeva Računalniška Koda))</string>
+  <string name="TEXT_TABLE_LABEL_en_US">Angleška (Združene Države)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">Španska</string>
+  <string name="TEXT_TABLE_LABEL_et">Estonska</string>
+  <string name="TEXT_TABLE_LABEL_fi">Finnska</string>
+  <string name="TEXT_TABLE_LABEL_fr">Francoska</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Francoska (združena 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Francoska (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Francoska (Standardna Francoska Brajeva Računalniška Koda)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Francoska(Francija)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">French (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">Irska</string>
+  <string name="TEXT_TABLE_LABEL_gd">Galska</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he">Hebrejska</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hindujska</string>
+  <string name="TEXT_TABLE_LABEL_hr">Hrvaška</string>
+  <string name="TEXT_TABLE_LABEL_hu">Madžarska</string>
+  <string name="TEXT_TABLE_LABEL_hy">Armenska</string>
+  <string name="TEXT_TABLE_LABEL_is">Islandska</string>
+  <string name="TEXT_TABLE_LABEL_it">Italijanska</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litovska</string>
+  <string name="TEXT_TABLE_LABEL_lv">Latvijska</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagaška</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malajska</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipurijska</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Malteška</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepalska</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Nizozemska</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Nizozemska (Belgijska)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Nizozemska (Nizozemska)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norveška</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norveška (s podporo za druge jezike)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norveška (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (old)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Poljska</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portugalska</string>
+  <string name="TEXT_TABLE_LABEL_ro">Romunska</string>
+  <string name="TEXT_TABLE_LABEL_ru">Ruska</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Samijščina (Severna)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovaška</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slovenščina</string>
+  <string name="TEXT_TABLE_LABEL_sv">Švedska</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">Švedska (1989 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">Švedska (1996 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Turška</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ukrajinska</string>
+  <string name="TEXT_TABLE_LABEL_vi">Vijetnamska</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">Obrni barvo ospredja v levem stolpcu in barvo ozadja v desnem stolpcu</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">barva ospredja (črke in drugo) v levem stolpcu in barva ozadja v desnem stolpcu</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">barva ospredja v zgornjem prostoru in barva ozadja v spodnjem prostoru</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">locale-based samodejna izbira</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikanska (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharijska (brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Nemška</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Nemška (brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Nemška (osnovne krajšave)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Nemška (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Nemška (s krajšavami - 1998 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Nemška (s krajšavami - 2015 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">Angleška</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">Angleška (Združene Države)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">Angleška (Združena, brez kr</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">Angleška (Združena Angleška razvrstitev 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">English (ZDA, razvrstitev 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">Španska(razvrstitev 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Francoska</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Francoska (brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Francoska(s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Indonezijska(s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Mednarodna Fonetična Abeceda</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japonska(brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Korejska</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Korejska (brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Korejska (razvrstitev 1)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Korejska (razvrstitev 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Letonska (neskrajšana)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Nizozemska(s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portugalska (razvrstitev 2)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Ruska (skrajšana)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sinhalska(brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Tajska(s krajšavami)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Kitajska (Tajvan, brez krajšav)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (s krajšavami)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">bližnjice za brajeve tipkovnice</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">bližnjice za polne tipkovnice</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">bližnjice za krmarjenje ki bazira na številskih tipkovnicah</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">bližnjice za tipkovnice brez številskega dela</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">bližnjice za Sun Type 6 tipkovnice</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-tr/strings.xml b/Android/Gradle/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..6cade34
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY braille cihazları destekler.</string>
+
+  <string name="usbMonitor_label">Eklenen USB cihaz izlemesi</string>
+
+  <string name="about_label_activity">Uygulama hakkında</string>
+  <string name="about_label_copyright">Telif hakkı</string>
+  <string name="about_label_buildTime">Yapım zamanı</string>
+  <string name="about_label_sourceRevision">Kaynak Revizyonu</string>
+  <string name="about_label_privacyPolicy">Gizlilik Politikası</string>
+
+  <string name="inputService_name">BRLTTY Girdi Hizmeti</string>
+  <string name="inputService_type">Braille cihaz Klavyeleri</string>
+  <string name="inputService_not_enabled">Girdi hizmeti etkinleştirilmedi</string>
+  <string name="inputService_not_selected">Girdi hizmeti seçilmedi</string>
+  <string name="inputService_not_started">Girdi hizmeti başlatılmadı</string>
+  <string name="inputService_not_connected">Girdi hizmeti bağlı değil</string>
+
+  <string name="updateApplication_problem_failed">Uygulama güncellenemedi</string>
+
+  <string name="packageInstaller_problem_failed">Paket kurulamadı</string>
+  <string name="packageInstaller_problem_same">Aynı sürüm</string>
+  <string name="packageInstaller_problem_downgrade">Sürüm düşürmeye izin verilmemektedir</string>
+
+  <string name="fileDownloader_title">BRLTTY dosya indirici</string>
+  <string name="fileDownloader_problem_failed">Dosya indirilemedi</string>
+  <string name="fileDownloader_state_connecting">bağlanıyor</string>
+  <string name="fileDownloader_state_downloading">indiriliyor</string>
+
+  <string name="setting_state_off">Kapalı</string>
+  <string name="checkbox_state_unchecked">Hayır</string>
+  <string name="checkbox_state_checked">Evet</string>
+
+  <string name="braille_channel_name">Braille Cihaz Durumu</string>
+  <string name="braille_hint_tap">Eylemler için dokunun</string>
+  <string name="braille_state_released">Bağlı değil</string>
+  <string name="braille_state_waiting">Bekleniyor</string>
+  <string name="braille_state_connected">Bağlı</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Bildirimler</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Hızlı ayarlar</string>
+  <string name="GLOBAL_BUTTON_BACK">Geri</string>
+  <string name="GLOBAL_BUTTON_HOME">Ana sayfa</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Son uygulamalar</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Genel bakış</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Girdi yöntemini değiştir</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">Kullanıcı kılavuzunu görüntüle</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Web sayfasına git</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Topluluk mesajlarına gözat</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Topluluk mesajı yayınla</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Topluluk üyeliğini yönet</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Uygulamayı güncelle</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Geliştirici sürümü</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Sürüm düşürmeye izin ver</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Erişilebilirlik eylemleri</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Ayarları</string>
+  <string name="SETTINGS_SCREEN_GENERAL">Genel Ayarlar</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Mesaj Ayarları</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Cihazları Yönet</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Gelişmiş Ayarlar</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Braille Cihaz Bağlantısını Kes</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Dolaşım Modu</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Konuşma Desteği</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Metin Tablosu</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Öznitelik Tablosu</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Kısaltma Tablosu</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Klavye Tablosu</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Uyarıları Göster</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Duyuruları Göster</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Bildirimleri Göster</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Günlük Oluşturma Düzeyi</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Günlük Oluşturma Kategorileri</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Erişilebilirlik Etkinliklerini Günlük Dosyasına Kaydet</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Dönüştürülen Ekranı Günlük Dosyasına Kaydet</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Klavye Etkinliklerini Günlük Dosyasına Kaydet</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">İşlenemeyen Etkinlikleri Günlük Dosyasına Kaydet</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Seçili Cihaz</string>
+  <string name="DEVICES_BUTTON_ADD">Cihaz Ekle</string>
+  <string name="DEVICES_BUTTON_REMOVE">Cihazı Kaldır</string>
+
+  <string name="SELECTED_DEVICE_NONE">Seçili Cihaz Yok</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">Cihaz Seçili Değil</string>
+
+  <string name="ADD_DEVICE_NAME">Cihaz Adı</string>
+  <string name="ADD_DEVICE_METHOD">Bağlantı Yöntemi</string>
+  <string name="ADD_DEVICE_SELECT">Cihaz Seç</string>
+  <string name="ADD_DEVICE_DRIVER">Braille Sürücüsü</string>
+  <string name="ADD_DEVICE_DONE">Tamamlandı</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">Bağlantı yöntemi seçilmedi</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">Cihaz seçilmedi</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">braille sürücüsü seçilmedi</string>
+  <string name="ADD_DEVICE_NO_DEVICES">Cihaz bulunamadı</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">Cihaz adı zaten kullanılıyor</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">Cihaza erişim izni verilmemiş</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Bu cihazı gerçekten kaldırmak istiyor musunuz?</string>
+
+  <string name="SET_SELECTION_NONE">hiçbiri seçilmedi</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">Dikey Liste</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">İki Boyutlu Kılavuz</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">Yerel</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Serial</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">Hata Ayıklama</string>
+  <string name="LOG_LEVEL_LABEL_information">Bilgi</string>
+  <string name="LOG_LEVEL_LABEL_notice">Duyuru</string>
+  <string name="LOG_LEVEL_LABEL_warning">Uyarı</string>
+  <string name="LOG_LEVEL_LABEL_error">Hata</string>
+  <string name="LOG_LEVEL_LABEL_critical">Kritik</string>
+  <string name="LOG_LEVEL_LABEL_alert">Acil Uyarı</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Acil Durum</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">Girdi Paketleri</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Çıktı Paketleri</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Braille Cihaz Tuş Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Klavye Tuş Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">İmleç İzleme</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">İmleç Yönlendirme</string>
+  <string name="LOG_CATEGORY_LABEL_update">Etkinlikleri Güncelle</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Konuşma Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_async">Async Etkinlikler</string>
+  <string name="LOG_CATEGORY_LABEL_server">Sunucu Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Generic I/O</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serial I/O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">İnsan Arabirim Cihazı I/O</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Braille Sürücüsü Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Konuşma Sürücüsü Etkinlikleri</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Ekran Sürücüsü Etkinlikleri</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">Oto-algıla</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">Yerel Tabanlı Oto-seçim</string>
+  <string name="TEXT_TABLE_LABEL_ar">Arapça (genel)</string>
+  <string name="TEXT_TABLE_LABEL_as">Assam dili</string>
+  <string name="TEXT_TABLE_LABEL_awa">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg">Bulgarca</string>
+  <string name="TEXT_TABLE_LABEL_bh">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn">Bengalce</string>
+  <string name="TEXT_TABLE_LABEL_bo">Tibetçe</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">Hazır Braille Formatı BRF (.editördeki brf dosyalarını görüntülemek için)</string>
+  <string name="TEXT_TABLE_LABEL_cs">Çekçe</string>
+  <string name="TEXT_TABLE_LABEL_cy">Galce</string>
+  <string name="TEXT_TABLE_LABEL_da">Danca</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">Danca (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">Danca (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">Almanca</string>
+  <string name="TEXT_TABLE_LABEL_dra">Dravid Dili</string>
+  <string name="TEXT_TABLE_LABEL_el">Yunanca</string>
+  <string name="TEXT_TABLE_LABEL_en">İngilizce</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">İngilizce (Kanada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">İngilizce (İngiltere)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">İngilizce (Kuzey Amerika Braille Bilgisayar Kodu)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">İngilizce (Amerika Birleşik Devletleri)</string>
+  <string name="TEXT_TABLE_LABEL_eo">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es">İspanyolca</string>
+  <string name="TEXT_TABLE_LABEL_et">Estonca</string>
+  <string name="TEXT_TABLE_LABEL_fi">Fince</string>
+  <string name="TEXT_TABLE_LABEL_fr">Fransızca</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">Fransızca (birleşik 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">Fransızca (kanada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">Fransızca (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">Fransızca (Fransa)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">Fransızca (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">İrlandaca</string>
+  <string name="TEXT_TABLE_LABEL_gd">İskoç Galcesi</string>
+  <string name="TEXT_TABLE_LABEL_gon">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu">Guceratça</string>
+  <string name="TEXT_TABLE_LABEL_he">İbranice</string>
+  <string name="TEXT_TABLE_LABEL_hi">Hintçe</string>
+  <string name="TEXT_TABLE_LABEL_hr">Hırvatça</string>
+  <string name="TEXT_TABLE_LABEL_hu">Macarca</string>
+  <string name="TEXT_TABLE_LABEL_hy">Ermenice</string>
+  <string name="TEXT_TABLE_LABEL_is">İzlandaca</string>
+  <string name="TEXT_TABLE_LABEL_it">Italyanca</string>
+  <string name="TEXT_TABLE_LABEL_kha">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">Litvanca</string>
+  <string name="TEXT_TABLE_LABEL_lv">Letonca</string>
+  <string name="TEXT_TABLE_LABEL_mg">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt">Maltaca</string>
+  <string name="TEXT_TABLE_LABEL_mun">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne">Nepalce</string>
+  <string name="TEXT_TABLE_LABEL_new">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl">Felemenkçe</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">Felemenkçe (Belçika)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">Felemenkçe (Hollanda)</string>
+  <string name="TEXT_TABLE_LABEL_no">Norveççe</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">Norveççe (diğer dilleri destekleyen)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">Norveççe (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">Newari (eski)</string>
+  <string name="TEXT_TABLE_LABEL_or">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa">Pancapça</string>
+  <string name="TEXT_TABLE_LABEL_pi">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl">Lehçe</string>
+  <string name="TEXT_TABLE_LABEL_pt">Portekizce</string>
+  <string name="TEXT_TABLE_LABEL_ro">Romence</string>
+  <string name="TEXT_TABLE_LABEL_ru">Rusça</string>
+  <string name="TEXT_TABLE_LABEL_sa">Sanskritçe</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se">Samice (Kuzey)</string>
+  <string name="TEXT_TABLE_LABEL_sk">Slovakça</string>
+  <string name="TEXT_TABLE_LABEL_sl">Slovence</string>
+  <string name="TEXT_TABLE_LABEL_sv">İsveççe</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">İsveççe (1989 standart)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">İsveççe (1996 standart)</string>
+  <string name="TEXT_TABLE_LABEL_sw">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta">Tamilce</string>
+  <string name="TEXT_TABLE_LABEL_te">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr">Türkçe</string>
+  <string name="TEXT_TABLE_LABEL_uk">Ukraynaca</string>
+  <string name="TEXT_TABLE_LABEL_vi">Viyetnamca</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">Ön plan renkleri sol sütunda ve arka plan renkleri sağ sütunda ters</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">Ön plan renkleri sol sütunda ve arka plan renkleri sağ sütunda</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">Ön plan renkleri üst kısımda ve arka plan renkleri alt kısımda</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">Yerel oto-seçim</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">Afrikanca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">Amharca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">Almanca</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">Almanca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">Almanca (temel kısaltmalar)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">Almanca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">Almanca (kısaltmalı - 1998 standart)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">Almanca (kısaltmalı - 2015 standart)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">İngilizce</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">İngilizce (Amerika Birleşik Devletleri)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1">İngilizce (Birleşmiş, kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">İngilizce (Birleşmiş, kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">İngilizce (Amerika Birleşik Devletleri, kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">İspanyolca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">Fransızca</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">Fransızca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">Fransızca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">Hausa (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">Endonezyaca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">Uluslararası Fonetik Alfabesi</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">Japonca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">Korece</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">Korece (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">Korece (kısmi kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">Korece (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">Litvanca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">Malagasy (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">Munda (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">Felemenkçe (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">Chichewa (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">Portekizce (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">Rusça (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">Sri Lankaca (kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">Swahili (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">Tayca (kısaltmalı)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">Çince (Tayvan, kısaltmasız)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">Zulu (kısaltmalı)</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">Braille klavyeler için bağlam tuşları</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">Tam klavyeler için bağlam tuşları</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">Tuş takımı tabanlı dolaşım için tuş takımı bağlam tuşları</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">Tuş takımı olmayan klavyeler için bağlam tuşları</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">Sun Tip 6 Klavyeler için bağlam tuşları</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values-zh-rTW/strings.xml b/Android/Gradle/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..70fd0d1
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY 支援點字顯示器</string>
+
+  <string name="about_label_activity">關於軟體</string>
+  <string name="about_label_copyright">版權</string>
+  <string name="about_label_buildTime">建制時間</string>
+  <string name="about_label_sourceRevision">源碼修訂</string>
+  <string name="about_label_privacyPolicy">隱私政策</string>
+
+  <string name="inputService_name">BRLTTY 點字鍵盤輸入</string>
+  <string name="inputService_type">點顯器上的鍵盤</string>
+  <string name="inputService_not_enabled">輸入服務尚未啟用</string>
+  <string name="inputService_not_selected">輸入服務尚未選用</string>
+  <string name="inputService_not_started">輸入服務尚未開始</string>
+  <string name="inputService_not_connected">輸入服務尚未連線</string>
+
+  <string name="updateApplication_problem_failed">軟體未升級</string>
+
+  <string name="packageInstaller_problem_failed">套件未安裝</string>
+  <string name="packageInstaller_problem_same">版本相同</string>
+  <string name="packageInstaller_problem_downgrade">不允許降級</string>
+
+  <string name="fileDownloader_title">BRLTTY 檔案下載工具</string>
+  <string name="fileDownloader_problem_failed">檔案未下載</string>
+  <string name="fileDownloader_state_connecting">連接中</string>
+  <string name="fileDownloader_state_downloading">下載中</string>
+
+  <string name="setting_state_off">關閉</string>
+  <string name="checkbox_state_unchecked">否</string>
+  <string name="checkbox_state_checked">市</string>
+
+  <string name="braille_channel_name">點字設備狀態</string>
+  <string name="braille_hint_tap">點擊啟用</string>
+  <string name="braille_state_released">已釋放</string>
+  <string name="braille_state_waiting">等待中</string>
+  <string name="braille_state_connected">已連接</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">通知</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">快速設定</string>
+  <string name="GLOBAL_BUTTON_BACK">返回</string>
+  <string name="GLOBAL_BUTTON_HOME">主頁</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">最近開啟的應用城市</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">概覽</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">切換輸入法</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">觀看使用說明</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">瀏覽網站</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">瀏覽社群訊息</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">張貼社群訊息</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">管理社群會員</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">更新軟體</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">開發者建制</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">允許降級</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">無障礙活動</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY 設定</string>
+  <string name="SETTINGS_SCREEN_GENERAL">一般設定</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">訊息設定</string>
+  <string name="SETTINGS_SCREEN_DEVICES">點顯器設定</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">進階設定</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">釋放的點字設備</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">尋懶模式</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">語音支援</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">普通點字</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">屬性點字</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">縮寫點字</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">鍵盤對應表</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">顯示警示</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">顯示提醒</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">顯示通知</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">紀錄等級</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">紀錄分類</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">紀錄無障礙事件</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">紀錄捨棄的畫面</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">紀錄鍵盤事件</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">紀錄未受控制事件</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">選用的點顯器</string>
+  <string name="DEVICES_BUTTON_ADD">新增點顯器</string>
+  <string name="DEVICES_BUTTON_REMOVE">移除點顯器</string>
+
+  <string name="SELECTED_DEVICE_NONE">沒有設備</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">設備餵選取</string>
+
+  <string name="ADD_DEVICE_NAME">點顯器名稱</string>
+  <string name="ADD_DEVICE_METHOD">連接方式</string>
+  <string name="ADD_DEVICE_SELECT">選擇點顯器</string>
+  <string name="ADD_DEVICE_DRIVER">點顯器</string>
+  <string name="ADD_DEVICE_DONE">完成</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">溝通方式餵選取</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">設備餵選取</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">點字驅動餵選取</string>
+  <string name="ADD_DEVICE_NO_DEVICES">沒有設備</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">設備名稱以使用</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">您確定要移除這個點顯器嗎?</string>
+
+  <string name="SET_SELECTION_NONE">沒有選取</string>
+
+  <string name="NAVIGATION_MODE_LABEL_List">縱向清單</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">二維方格</string>
+
+  <string name="SPEECH_SUPPORT_LABEL_native">內建</string>
+
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">藍牙</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">序列埠</string>
+
+  <string name="LOG_LEVEL_LABEL_debug">除錯</string>
+  <string name="LOG_LEVEL_LABEL_information">資訊</string>
+  <string name="LOG_LEVEL_LABEL_notice">提示</string>
+  <string name="LOG_LEVEL_LABEL_warning">警示</string>
+  <string name="LOG_LEVEL_LABEL_error">錯誤</string>
+  <string name="LOG_LEVEL_LABEL_critical">關鍵</string>
+  <string name="LOG_LEVEL_LABEL_alert">警告</string>
+  <string name="LOG_LEVEL_LABEL_emergency">緊急</string>
+
+  <string name="LOG_CATEGORY_LABEL_inpkts">輸入封包</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">輸出封包</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">點字鍵盤事件</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">普通鍵盤事件</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">游標追蹤</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">游標定位</string>
+  <string name="LOG_CATEGORY_LABEL_update">更新事件</string>
+  <string name="LOG_CATEGORY_LABEL_speech">朗讀事件</string>
+  <string name="LOG_CATEGORY_LABEL_async">非同步事件</string>
+  <string name="LOG_CATEGORY_LABEL_server">服務事件</string>
+  <string name="LOG_CATEGORY_LABEL_serial">序列輸入輸出</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB 輸入輸出</string>
+  <string name="LOG_CATEGORY_LABEL_bt">藍牙輸入輸出</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">點字驅動事件</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">語音驅動事件</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">螢幕驅動事件</string>
+
+  <string name="BRAILLE_DRIVER_LABEL_auto">自動偵測</string>
+
+  <string name="TEXT_TABLE_LABEL_auto">依照語言設定自動選擇</string>
+  <string name="TEXT_TABLE_LABEL_ar">阿拉伯(一般)</string>
+  <string name="TEXT_TABLE_LABEL_as">阿薩姆</string>
+  <string name="TEXT_TABLE_LABEL_awa">阿瓦迪</string>
+  <string name="TEXT_TABLE_LABEL_bg">保加利亞</string>
+  <string name="TEXT_TABLE_LABEL_bh">比哈爾</string>
+  <string name="TEXT_TABLE_LABEL_bn">孟加拉</string>
+  <string name="TEXT_TABLE_LABEL_bo">西藏語</string>
+  <string name="TEXT_TABLE_LABEL_bra">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf">點字格式檔(閱讀 .brf 檔)</string>
+  <string name="TEXT_TABLE_LABEL_cs">捷克</string>
+  <string name="TEXT_TABLE_LABEL_cy">威爾斯</string>
+  <string name="TEXT_TABLE_LABEL_da">丹麥</string>
+  <string name="TEXT_TABLE_LABEL_da_1252">丹麥 (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt">丹麥 (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de">德文</string>
+  <string name="TEXT_TABLE_LABEL_dra">德拉維</string>
+  <string name="TEXT_TABLE_LABEL_el">希臘</string>
+  <string name="TEXT_TABLE_LABEL_en">英文</string>
+  <string name="TEXT_TABLE_LABEL_en_CA">英文(加拿大)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB">英文(英國)</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc">英文(北美電腦點字)</string>
+  <string name="TEXT_TABLE_LABEL_en_US">英文(美國)</string>
+  <string name="TEXT_TABLE_LABEL_eo">世界語</string>
+  <string name="TEXT_TABLE_LABEL_es">西班牙</string>
+  <string name="TEXT_TABLE_LABEL_et">愛沙尼亞</string>
+  <string name="TEXT_TABLE_LABEL_fi">芬蘭</string>
+  <string name="TEXT_TABLE_LABEL_fr">法文</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007">法文 (統一 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA">法文(加拿大)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs">法文(法國標準點字資訊碼)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR">法文(法蘭西)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs">法文 (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_ga">愛爾蘭英語</string>
+  <string name="TEXT_TABLE_LABEL_gd">蓋爾</string>
+  <string name="TEXT_TABLE_LABEL_gon">岡德</string>
+  <string name="TEXT_TABLE_LABEL_gu">印度古吉拉特</string>
+  <string name="TEXT_TABLE_LABEL_he">希伯來</string>
+  <string name="TEXT_TABLE_LABEL_hi">北印度語</string>
+  <string name="TEXT_TABLE_LABEL_hr">克羅埃西亞</string>
+  <string name="TEXT_TABLE_LABEL_hu">匈牙利</string>
+  <string name="TEXT_TABLE_LABEL_hy">亞美尼亞</string>
+  <string name="TEXT_TABLE_LABEL_is">冰島</string>
+  <string name="TEXT_TABLE_LABEL_it">義大利</string>
+  <string name="TEXT_TABLE_LABEL_kha">卡西</string>
+  <string name="TEXT_TABLE_LABEL_kn">坎那達語</string>
+  <string name="TEXT_TABLE_LABEL_kok">孔卡尼</string>
+  <string name="TEXT_TABLE_LABEL_kru">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt">立陶宛</string>
+  <string name="TEXT_TABLE_LABEL_lv">拉脫維亞</string>
+  <string name="TEXT_TABLE_LABEL_mg">馬達加斯加</string>
+  <string name="TEXT_TABLE_LABEL_mi">毛利語</string>
+  <string name="TEXT_TABLE_LABEL_ml">印度西南部的德拉威方言</string>
+  <string name="TEXT_TABLE_LABEL_mni">(印度)曼尼普爾</string>
+  <string name="TEXT_TABLE_LABEL_mr">馬拉地語</string>
+  <string name="TEXT_TABLE_LABEL_mt">馬爾他</string>
+  <string name="TEXT_TABLE_LABEL_mun">蒙達語</string>
+  <string name="TEXT_TABLE_LABEL_mwr">馬爾瓦爾</string>
+  <string name="TEXT_TABLE_LABEL_ne">尼泊爾</string>
+  <string name="TEXT_TABLE_LABEL_new">尼瓦爾</string>
+  <string name="TEXT_TABLE_LABEL_nl">荷蘭</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE">荷蘭(比利時)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL">荷蘭(尼德蘭)</string>
+  <string name="TEXT_TABLE_LABEL_no">挪威</string>
+  <string name="TEXT_TABLE_LABEL_no_generic">挪威(支援其他語言)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup">挪威 (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc">紐瓦里(舊)</string>
+  <string name="TEXT_TABLE_LABEL_or">歐利亞語</string>
+  <string name="TEXT_TABLE_LABEL_pa">旁遮普語</string>
+  <string name="TEXT_TABLE_LABEL_pi">巴歷語</string>
+  <string name="TEXT_TABLE_LABEL_pl">波蘭</string>
+  <string name="TEXT_TABLE_LABEL_pt">葡萄牙</string>
+  <string name="TEXT_TABLE_LABEL_ro">羅馬尼亞</string>
+  <string name="TEXT_TABLE_LABEL_ru">俄語</string>
+  <string name="TEXT_TABLE_LABEL_sa">梵語</string>
+  <string name="TEXT_TABLE_LABEL_sat">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd">信德語</string>
+  <string name="TEXT_TABLE_LABEL_se">北薩米</string>
+  <string name="TEXT_TABLE_LABEL_sk">斯洛法克</string>
+  <string name="TEXT_TABLE_LABEL_sl">斯洛凡尼亞語</string>
+  <string name="TEXT_TABLE_LABEL_sv">瑞典</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989">瑞典 (1989 標準)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996">瑞典 (1996 標準)</string>
+  <string name="TEXT_TABLE_LABEL_sw">斯華希裡</string>
+  <string name="TEXT_TABLE_LABEL_ta">坦米爾</string>
+  <string name="TEXT_TABLE_LABEL_te">泰盧固語</string>
+  <string name="TEXT_TABLE_LABEL_tr">土耳其</string>
+  <string name="TEXT_TABLE_LABEL_uk">烏克蘭語</string>
+  <string name="TEXT_TABLE_LABEL_vi">越南</string>
+
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right">對調左邊欄位的前景顏色與右邊欄位的背景顏色</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right">左邊欄位為前景顏色 右邊欄位為背景顏色</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower">上框為前景顏色 下框為背景顏色</string>
+
+  <string name="CONTRACTION_TABLE_LABEL_auto">依照語言設定自動選擇</string>
+  <string name="CONTRACTION_TABLE_LABEL_af">南非的荷蘭語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_am">阿比西尼亞官話無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_de">德文</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0">德文無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1">德文基本縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2">德文縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998">德文縮寫 1998 標準</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015">德文縮寫 2015 標準</string>
+  <string name="CONTRACTION_TABLE_LABEL_en">英文</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US">英文(美國)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2">英文統一二級點字</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2">英文二級</string>
+  <string name="CONTRACTION_TABLE_LABEL_es">西班牙語二級</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr">法文</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1">法文無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2">法文縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha">豪撒語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_id">印尼語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa">國際拼音字母</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja">日文無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko">韓文</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0">韓文無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1">韓文一級</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2">韓文二級</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt">立陶宛語 (沒縮寫)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg">馬達加斯加語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun">蒙達語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl">荷蘭語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny">奇切瓦 (縮寫)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt">葡萄牙語二級</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru">俄語(縮寫)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si">錫蘭語無縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw">斯瓦希裡語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_th">泰國語縮寫</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW">中文</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu">祖魯語縮寫</string>
+
+  <string name="KEYBOARD_TABLE_LABEL_braille">點字鍵盤對應</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop">全鍵盤對應</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad">九宮鍵尋懶對應</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop">沒有九宮鍵的鍵盤對應</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6">Sun 第六形鍵盤對應</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/attributes-table.xml b/Android/Gradle/app/src/main/res/values/attributes-table.xml
new file mode 100644
index 0000000..e3036ad
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/attributes-table.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- generated from brltty.conf.in by mksettings -->
+<!-- changes made here will be overwritten -->
+
+<resources>
+  <string name="ATTRIBUTES_TABLE_LABEL_invleft_right" translatable="true">inverse foreground colour in the left column and background colour in the right column</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_left_right" translatable="true">foreground colour in the left column and background colour in the right column</string>
+  <string name="ATTRIBUTES_TABLE_LABEL_upper_lower" translatable="true">foreground colour in the upper square and background colour in the lower square</string>
+
+  <string-array name="ATTRIBUTES_TABLE_LABELS">
+    <item>@string/ATTRIBUTES_TABLE_LABEL_invleft_right</item>
+    <item>@string/ATTRIBUTES_TABLE_LABEL_left_right</item>
+    <item>@string/ATTRIBUTES_TABLE_LABEL_upper_lower</item>
+  </string-array>
+
+  <string-array name="ATTRIBUTES_TABLE_VALUES">
+    <item>invleft_right</item>
+    <item>left_right</item>
+    <item>upper_lower</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/braille-driver.xml b/Android/Gradle/app/src/main/res/values/braille-driver.xml
new file mode 100644
index 0000000..8a63c6c
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/braille-driver.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- generated from brltty.conf.in by mksettings -->
+<!-- changes made here will be overwritten -->
+
+<resources>
+  <string name="BRAILLE_DRIVER_LABEL_auto" translatable="true">autodetect</string>
+  <string name="BRAILLE_DRIVER_LABEL_al" translatable="false">Alva</string>
+  <string name="BRAILLE_DRIVER_LABEL_at" translatable="false">Albatross</string>
+  <string name="BRAILLE_DRIVER_LABEL_ba" translatable="false">BrlAPI</string>
+  <string name="BRAILLE_DRIVER_LABEL_bc" translatable="false">BrailComm</string>
+  <string name="BRAILLE_DRIVER_LABEL_bd" translatable="false">Braudi</string>
+  <string name="BRAILLE_DRIVER_LABEL_bl" translatable="false">BrailleLite</string>
+  <string name="BRAILLE_DRIVER_LABEL_bm" translatable="false">Baum</string>
+  <string name="BRAILLE_DRIVER_LABEL_bn" translatable="false">BrailleNote</string>
+  <string name="BRAILLE_DRIVER_LABEL_cb" translatable="false">CombiBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_ce" translatable="false">Cebra</string>
+  <string name="BRAILLE_DRIVER_LABEL_cn" translatable="false">Canute</string>
+  <string name="BRAILLE_DRIVER_LABEL_dp" translatable="false">DotPad</string>
+  <string name="BRAILLE_DRIVER_LABEL_ec" translatable="false">EcoBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_eu" translatable="false">EuroBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_fa" translatable="false">FrankAudiodata</string>
+  <string name="BRAILLE_DRIVER_LABEL_fs" translatable="false">FreedomScientific</string>
+  <string name="BRAILLE_DRIVER_LABEL_hd" translatable="false">Hedo</string>
+  <string name="BRAILLE_DRIVER_LABEL_hm" translatable="false">HIMS</string>
+  <string name="BRAILLE_DRIVER_LABEL_ht" translatable="false">HandyTech</string>
+  <string name="BRAILLE_DRIVER_LABEL_hw" translatable="false">HumanWare</string>
+  <string name="BRAILLE_DRIVER_LABEL_ic" translatable="false">Inceptor</string>
+  <string name="BRAILLE_DRIVER_LABEL_ir" translatable="false">Iris</string>
+  <string name="BRAILLE_DRIVER_LABEL_lb" translatable="false">Libbraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_lt" translatable="false">LogText</string>
+  <string name="BRAILLE_DRIVER_LABEL_mb" translatable="false">MultiBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_md" translatable="false">MDV</string>
+  <string name="BRAILLE_DRIVER_LABEL_mm" translatable="false">BrailleMemo</string>
+  <string name="BRAILLE_DRIVER_LABEL_mn" translatable="false">MiniBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_mt" translatable="false">Metec</string>
+  <string name="BRAILLE_DRIVER_LABEL_np" translatable="false">NinePoint</string>
+  <string name="BRAILLE_DRIVER_LABEL_pg" translatable="false">Pegasus</string>
+  <string name="BRAILLE_DRIVER_LABEL_pm" translatable="false">Papenmeier</string>
+  <string name="BRAILLE_DRIVER_LABEL_sk" translatable="false">Seika</string>
+  <string name="BRAILLE_DRIVER_LABEL_tn" translatable="false">TechniBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_ts" translatable="false">TSI</string>
+  <string name="BRAILLE_DRIVER_LABEL_vd" translatable="false">VideoBraille</string>
+  <string name="BRAILLE_DRIVER_LABEL_vo" translatable="false">Voyager</string>
+  <string name="BRAILLE_DRIVER_LABEL_vs" translatable="false">VisioBraille</string>
+
+  <string-array name="BRAILLE_DRIVER_LABELS">
+    <item>@string/BRAILLE_DRIVER_LABEL_auto</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_al</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_at</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ba</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_bc</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_bd</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_bl</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_bm</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_bn</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_cb</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ce</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_cn</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_dp</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ec</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_eu</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_fa</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_fs</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_hd</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_hm</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ht</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_hw</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ic</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ir</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_lb</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_lt</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_mb</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_md</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_mm</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_mn</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_mt</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_np</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_pg</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_pm</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_sk</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_tn</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_ts</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_vd</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_vo</item>
+    <item>@string/BRAILLE_DRIVER_LABEL_vs</item>
+  </string-array>
+
+  <string-array name="BRAILLE_DRIVER_VALUES">
+    <item>auto</item>
+    <item>al</item>
+    <item>at</item>
+    <item>ba</item>
+    <item>bc</item>
+    <item>bd</item>
+    <item>bl</item>
+    <item>bm</item>
+    <item>bn</item>
+    <item>cb</item>
+    <item>ce</item>
+    <item>cn</item>
+    <item>dp</item>
+    <item>ec</item>
+    <item>eu</item>
+    <item>fa</item>
+    <item>fs</item>
+    <item>hd</item>
+    <item>hm</item>
+    <item>ht</item>
+    <item>hw</item>
+    <item>ic</item>
+    <item>ir</item>
+    <item>lb</item>
+    <item>lt</item>
+    <item>mb</item>
+    <item>md</item>
+    <item>mm</item>
+    <item>mn</item>
+    <item>mt</item>
+    <item>np</item>
+    <item>pg</item>
+    <item>pm</item>
+    <item>sk</item>
+    <item>tn</item>
+    <item>ts</item>
+    <item>vd</item>
+    <item>vo</item>
+    <item>vs</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/communication-method.xml b/Android/Gradle/app/src/main/res/values/communication-method.xml
new file mode 100644
index 0000000..8f56015
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/communication-method.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="COMMUNICATION_METHOD_LABEL_Bluetooth">Bluetooth</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Usb">USB</string>
+  <string name="COMMUNICATION_METHOD_LABEL_Serial">Serial</string>
+
+  <string-array name="DEVICE_METHOD_LABELS">
+    <item>@string/COMMUNICATION_METHOD_LABEL_Bluetooth</item>
+    <item>@string/COMMUNICATION_METHOD_LABEL_Usb</item>
+    <item>@string/COMMUNICATION_METHOD_LABEL_Serial</item>
+  </string-array>
+
+  <string-array name="DEVICE_METHOD_VALUES">
+    <item>Bluetooth</item>
+    <item>Usb</item>
+    <item>Serial</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/constants.xml b/Android/Gradle/app/src/main/res/values/constants.xml
new file mode 100644
index 0000000..3dfcd2c
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/constants.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_name" translatable="false">BRLTTY</string>
+  <string name="api_name" translatable="false">BrlAPI</string>
+
+  <string name="application_package_url" translatable="false">https://brltty.app/archive/Android/brltty-latest.apk</string>
+  <string name="developer_package_url" translatable="false">https://mielke.cc/~android/brltty-dev.apk</string>
+
+  <string name="privacy_policy_url" translatable="false">https://brltty.app/doc/PrivacyPolicy.html</string>
+  <string name="user_guide_url" translatable="false">https://brltty.app/doc/Android.html</string>
+
+  <string name="community_messages_url" translatable="false">https://brltty.app/pipermail/brltty/</string>
+  <string name="community_membership_url" translatable="false">https://brltty.app/mailman/listinfo/brltty</string>
+
+  <string name="google_play_url" translatable="false">https://play.google.com/store/apps/details?id=org.a11y.brltty.android</string>
+  <string name="google_play_label" translatable="false">Google Play</string>
+
+  <string-array name="EMPTY_STRING_ARRAY">
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/contraction-table.xml b/Android/Gradle/app/src/main/res/values/contraction-table.xml
new file mode 100644
index 0000000..90d8775
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/contraction-table.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- generated from brltty.conf.in by mksettings -->
+<!-- changes made here will be overwritten -->
+
+<resources>
+  <string name="CONTRACTION_TABLE_LABEL_auto" translatable="true">locale-based autoselection</string>
+  <string name="CONTRACTION_TABLE_LABEL_af" translatable="true">Afrikaans (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_am" translatable="true">Amharic (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de" translatable="true">German</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_1998" translatable="true">German (contracted - 1998 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_2015" translatable="true">German (contracted - 2015 standard)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g0" translatable="true">German (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g1" translatable="true">German (basic contractions)</string>
+  <string name="CONTRACTION_TABLE_LABEL_de_g2" translatable="true">German (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en" translatable="true">English</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g1" translatable="true">English (Unified, uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_ueb_g2" translatable="true">English (Unified, contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_us_g2" translatable="true">English (United States, contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_en_US" translatable="true">English (United States)</string>
+  <string name="CONTRACTION_TABLE_LABEL_es" translatable="true">Spanish (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr" translatable="true">French</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g1" translatable="true">French (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_fr_g2" translatable="true">French (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ha" translatable="true">Hausa (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_id" translatable="true">Indonesian (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ipa" translatable="true">International Phonetic Alphabet</string>
+  <string name="CONTRACTION_TABLE_LABEL_ja" translatable="true">Japanese (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko" translatable="true">Korean</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g0" translatable="true">Korean (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g1" translatable="true">Korean (partially contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ko_g2" translatable="true">Korean (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_lt" translatable="true">Lithuanian (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mg" translatable="true">Malagasy (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_mun" translatable="true">Munda (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_nl" translatable="true">Dutch (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ny" translatable="true">Chichewa (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_pt" translatable="true">Portuguese (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_ru" translatable="true">Russian (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_si" translatable="true">Sinhalese (uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_sw" translatable="true">Swahili (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_th" translatable="true">Thai (contracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zh_TW" translatable="true">Chinese (Taiwan, uncontracted)</string>
+  <string name="CONTRACTION_TABLE_LABEL_zu" translatable="true">Zulu (contracted)</string>
+
+  <string-array name="CONTRACTION_TABLE_LABELS">
+    <item>@string/CONTRACTION_TABLE_LABEL_auto</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_af</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_am</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de_1998</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de_2015</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de_g0</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de_g1</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_de_g2</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_en</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_en_ueb_g1</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_en_ueb_g2</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_en_us_g2</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_en_US</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_es</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_fr</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_fr_g1</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_fr_g2</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ha</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_id</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ipa</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ja</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ko</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ko_g0</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ko_g1</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ko_g2</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_lt</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_mg</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_mun</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_nl</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ny</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_pt</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_ru</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_si</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_sw</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_th</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_zh_TW</item>
+    <item>@string/CONTRACTION_TABLE_LABEL_zu</item>
+  </string-array>
+
+  <string-array name="CONTRACTION_TABLE_VALUES">
+    <item>auto</item>
+    <item>af</item>
+    <item>am</item>
+    <item>de</item>
+    <item>de-1998</item>
+    <item>de-2015</item>
+    <item>de-g0</item>
+    <item>de-g1</item>
+    <item>de-g2</item>
+    <item>en</item>
+    <item>en-ueb-g1</item>
+    <item>en-ueb-g2</item>
+    <item>en-us-g2</item>
+    <item>en_US</item>
+    <item>es</item>
+    <item>fr</item>
+    <item>fr-g1</item>
+    <item>fr-g2</item>
+    <item>ha</item>
+    <item>id</item>
+    <item>ipa</item>
+    <item>ja</item>
+    <item>ko</item>
+    <item>ko-g0</item>
+    <item>ko-g1</item>
+    <item>ko-g2</item>
+    <item>lt</item>
+    <item>mg</item>
+    <item>mun</item>
+    <item>nl</item>
+    <item>ny</item>
+    <item>pt</item>
+    <item>ru</item>
+    <item>si</item>
+    <item>sw</item>
+    <item>th</item>
+    <item>zh_TW</item>
+    <item>zu</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/defaults.xml b/Android/Gradle/app/src/main/res/values/defaults.xml
new file mode 100644
index 0000000..319d667
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/defaults.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+  <string name="DEFAULT_ATTRIBUTES_TABLE" translatable="false">upper_lower</string>
+  <string name="DEFAULT_BRAILLE_DRIVER" translatable="false">auto</string>
+  <string name="DEFAULT_COMMUNICATION_METHOD" translatable="false">Bluetooth</string>
+  <string name="DEFAULT_CONTRACTION_TABLE" tools:ignore="MissingTranslation">auto</string>
+  <string name="DEFAULT_KEYBOARD_TABLE" translatable="false">off</string>
+  <string name="DEFAULT_LOG_LEVEL" translatable="false">notice</string>
+  <string name="DEFAULT_NAVIGATION_MODE" translatable="false">List</string>
+  <string name="DEFAULT_SPEECH_SUPPORT" translatable="false">an</string>
+  <string name="DEFAULT_TEXT_TABLE" tools:ignore="MissingTranslation">auto</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/keyboard-table.xml b/Android/Gradle/app/src/main/res/values/keyboard-table.xml
new file mode 100644
index 0000000..f5c3c47
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/keyboard-table.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- generated from brltty.conf.in by mksettings -->
+<!-- changes made here will be overwritten -->
+
+<resources>
+  <string name="KEYBOARD_TABLE_LABEL_braille" translatable="true">bindings for braille keyboards</string>
+  <string name="KEYBOARD_TABLE_LABEL_desktop" translatable="true">bindings for full keyboards</string>
+  <string name="KEYBOARD_TABLE_LABEL_keypad" translatable="true">bindings for keypad-based navigation</string>
+  <string name="KEYBOARD_TABLE_LABEL_laptop" translatable="true">bindings for keyboards without a keypad</string>
+  <string name="KEYBOARD_TABLE_LABEL_sun_type6" translatable="true">bindings for Sun Type 6 keyboards</string>
+
+  <string-array name="KEYBOARD_TABLE_LABELS">
+    <item>@string/setting_state_off</item>
+    <item>@string/KEYBOARD_TABLE_LABEL_braille</item>
+    <item>@string/KEYBOARD_TABLE_LABEL_desktop</item>
+    <item>@string/KEYBOARD_TABLE_LABEL_keypad</item>
+    <item>@string/KEYBOARD_TABLE_LABEL_laptop</item>
+    <item>@string/KEYBOARD_TABLE_LABEL_sun_type6</item>
+  </string-array>
+
+  <string-array name="KEYBOARD_TABLE_VALUES">
+    <item>off</item>
+    <item>braille</item>
+    <item>desktop</item>
+    <item>keypad</item>
+    <item>laptop</item>
+    <item>sun_type6</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/log-category.xml b/Android/Gradle/app/src/main/res/values/log-category.xml
new file mode 100644
index 0000000..f79535d
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/log-category.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="LOG_CATEGORY_LABEL_inpkts">Input Packets</string>
+  <string name="LOG_CATEGORY_LABEL_outpkts">Output Packets</string>
+  <string name="LOG_CATEGORY_LABEL_brlkeys">Braille Device Key Events</string>
+  <string name="LOG_CATEGORY_LABEL_kbdkeys">Keyboard Key Events</string>
+  <string name="LOG_CATEGORY_LABEL_csrtrk">Cursor Tracking</string>
+  <string name="LOG_CATEGORY_LABEL_csrrtg">Cursor Routing</string>
+  <string name="LOG_CATEGORY_LABEL_update">Update Events</string>
+  <string name="LOG_CATEGORY_LABEL_speech">Speech Events</string>
+  <string name="LOG_CATEGORY_LABEL_async">Async Events</string>
+  <string name="LOG_CATEGORY_LABEL_server">Server Events</string>
+  <string name="LOG_CATEGORY_LABEL_gio">Generic I/O</string>
+  <string name="LOG_CATEGORY_LABEL_serial">Serial I/O</string>
+  <string name="LOG_CATEGORY_LABEL_usb">USB I/O</string>
+  <string name="LOG_CATEGORY_LABEL_bt">Bluetooth I/O</string>
+  <string name="LOG_CATEGORY_LABEL_hid">Human Interface I/O</string>
+  <string name="LOG_CATEGORY_LABEL_brldrv">Braille Driver Events</string>
+  <string name="LOG_CATEGORY_LABEL_spkdrv">Speech Driver Events</string>
+  <string name="LOG_CATEGORY_LABEL_scrdrv">Screen Driver Events</string>
+
+  <string-array name="LOG_CATEGORY_LABELS">
+    <item>@string/LOG_CATEGORY_LABEL_inpkts</item>
+    <item>@string/LOG_CATEGORY_LABEL_outpkts</item>
+    <item>@string/LOG_CATEGORY_LABEL_brlkeys</item>
+    <item>@string/LOG_CATEGORY_LABEL_kbdkeys</item>
+    <item>@string/LOG_CATEGORY_LABEL_csrtrk</item>
+    <item>@string/LOG_CATEGORY_LABEL_csrrtg</item>
+    <item>@string/LOG_CATEGORY_LABEL_update</item>
+    <item>@string/LOG_CATEGORY_LABEL_speech</item>
+    <item>@string/LOG_CATEGORY_LABEL_async</item>
+    <item>@string/LOG_CATEGORY_LABEL_server</item>
+    <item>@string/LOG_CATEGORY_LABEL_gio</item>
+    <item>@string/LOG_CATEGORY_LABEL_serial</item>
+    <item>@string/LOG_CATEGORY_LABEL_usb</item>
+    <item>@string/LOG_CATEGORY_LABEL_bt</item>
+    <item>@string/LOG_CATEGORY_LABEL_hid</item>
+    <item>@string/LOG_CATEGORY_LABEL_brldrv</item>
+    <item>@string/LOG_CATEGORY_LABEL_spkdrv</item>
+    <item>@string/LOG_CATEGORY_LABEL_scrdrv</item>
+  </string-array>
+
+  <string-array name="LOG_CATEGORY_VALUES">
+    <item>inpkts</item>
+    <item>outpkts</item>
+    <item>brlkeys</item>
+    <item>kbdkeys</item>
+    <item>csrtrk</item>
+    <item>csrrtg</item>
+    <item>update</item>
+    <item>speech</item>
+    <item>async</item>
+    <item>server</item>
+    <item>gio</item>
+    <item>serial</item>
+    <item>usb</item>
+    <item>bt</item>
+    <item>hid</item>
+    <item>brldrv</item>
+    <item>spkdrv</item>
+    <item>scrdrv</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/log-level.xml b/Android/Gradle/app/src/main/res/values/log-level.xml
new file mode 100644
index 0000000..182f711
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/log-level.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="LOG_LEVEL_LABEL_debug">Debug</string>
+  <string name="LOG_LEVEL_LABEL_information">Information</string>
+  <string name="LOG_LEVEL_LABEL_notice">Notice</string>
+  <string name="LOG_LEVEL_LABEL_warning">Warning</string>
+  <string name="LOG_LEVEL_LABEL_error">Error</string>
+  <string name="LOG_LEVEL_LABEL_critical">Critical</string>
+  <string name="LOG_LEVEL_LABEL_alert">Alert</string>
+  <string name="LOG_LEVEL_LABEL_emergency">Emergency</string>
+
+  <string-array name="LOG_LEVEL_LABELS">
+    <item>@string/LOG_LEVEL_LABEL_debug</item>
+    <item>@string/LOG_LEVEL_LABEL_information</item>
+    <item>@string/LOG_LEVEL_LABEL_notice</item>
+    <item>@string/LOG_LEVEL_LABEL_warning</item>
+    <item>@string/LOG_LEVEL_LABEL_error</item>
+    <item>@string/LOG_LEVEL_LABEL_critical</item>
+    <item>@string/LOG_LEVEL_LABEL_alert</item>
+    <item>@string/LOG_LEVEL_LABEL_emergency</item>
+  </string-array>
+
+  <string-array name="LOG_LEVEL_VALUES">
+    <item>debug</item>
+    <item>information</item>
+    <item>notice</item>
+    <item>warning</item>
+    <item>error</item>
+    <item>critical</item>
+    <item>alert</item>
+    <item>emergency</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/navigation-mode.xml b/Android/Gradle/app/src/main/res/values/navigation-mode.xml
new file mode 100644
index 0000000..56d47d8
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/navigation-mode.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="NAVIGATION_MODE_LABEL_List">Vertical List</string>
+  <string name="NAVIGATION_MODE_LABEL_Grid">Two Dimensional Grid</string>
+
+  <string-array name="NAVIGATION_MODE_LABELS">
+    <item>@string/NAVIGATION_MODE_LABEL_List</item>
+    <item>@string/NAVIGATION_MODE_LABEL_Grid</item>
+  </string-array>
+
+  <string-array name="NAVIGATION_MODE_VALUES">
+    <item>List</item>
+    <item>Grid</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/pref-keys.xml b/Android/Gradle/app/src/main/res/values/pref-keys.xml
new file mode 100644
index 0000000..fd545e7
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/pref-keys.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="PREF_KEY_PACKAGE_SIZE" translatable="false">package-size</string>
+  <string name="PREF_KEY_PACKAGE_TIME" translatable="false">package-time</string>
+
+  <string name="PREF_KEY_RELEASE_BRAILLE_DEVICE" translatable="false">release-braille-device</string>
+  <string name="PREF_KEY_NAVIGATION_MODE" translatable="false">navigation-mode</string>
+  <string name="PREF_KEY_SPEECH_SUPPORT" translatable="false">speech-support</string>
+
+  <string name="PREF_KEY_TEXT_TABLE" translatable="false">text-table</string>
+  <string name="PREF_KEY_ATTRIBUTES_TABLE" translatable="false">attributes-table</string>
+  <string name="PREF_KEY_CONTRACTION_TABLE" translatable="false">contraction-table</string>
+  <string name="PREF_KEY_KEYBOARD_TABLE" translatable="false">keyboard-table</string>
+
+  <string name="PREF_KEY_SHOW_ALERTS" translatable="false">show-alerts</string>
+  <string name="PREF_KEY_SHOW_ANNOUNCEMENTS" translatable="false">show-announcements</string>
+  <string name="PREF_KEY_SHOW_NOTIFICATIONS" translatable="false">show-notifications</string>
+
+  <string name="PREF_KEY_LOG_LEVEL" translatable="false">log-level</string>
+  <string name="PREF_KEY_LOG_CATEGORIES" translatable="false">log-categories</string>
+
+  <string name="PREF_KEY_LOG_ACCESSIBILITY_EVENTS" translatable="false">log-accessibility-events</string>
+  <string name="PREF_KEY_LOG_RENDERED_SCREEN" translatable="false">log-rendered-screen</string>
+  <string name="PREF_KEY_LOG_KEYBOARD_EVENTS" translatable="false">log-keyboard-events</string>
+  <string name="PREF_KEY_LOG_UNHANDLED_EVENTS" translatable="false">log-unhandled-events</string>
+
+  <string name="PREF_KEY_SELECTED_DEVICE" translatable="false">selected-device</string>
+  <string name="PREF_KEY_ADD_DEVICE" translatable="false">add-device</string>
+  <string name="PREF_KEY_REMOVE_DEVICE" translatable="false">remove-device</string>
+
+  <string name="PREF_KEY_REMOVE_DEVICE_PROMPT" translatable="false">remove-device-prompt</string>
+  <string name="PREF_KEY_REMOVE_DEVICE_CANCEL" translatable="false">remove-device-cancel</string>
+  <string name="PREF_KEY_REMOVE_DEVICE_CONFIRM" translatable="false">remove-device-confirm</string>
+
+  <string name="PREF_KEY_DEVICE_NAME" translatable="false">device-name</string>
+  <string name="PREF_KEY_DEVICE_METHOD" translatable="false">device-method</string>
+  <string name="PREF_KEY_DEVICE_IDENTIFIER" translatable="false">device-identifier</string>
+  <string name="PREF_KEY_DEVICE_DRIVER" translatable="false">device-driver</string>
+  <string name="PREF_KEY_DEVICE_ADD" translatable="false">device-add</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/speech-support.xml b/Android/Gradle/app/src/main/res/values/speech-support.xml
new file mode 100644
index 0000000..8650466
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/speech-support.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="SPEECH_SUPPORT_LABEL_native">Native</string>
+
+  <string-array name="SPEECH_SUPPORT_LABELS">
+    <item>@string/setting_state_off</item>
+    <item>@string/SPEECH_SUPPORT_LABEL_native</item>
+  </string-array>
+
+  <string-array name="SPEECH_SUPPORT_VALUES">
+    <item>no</item>
+    <item>an</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/strings.xml b/Android/Gradle/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7d48e0b
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/strings.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <string name="app_description">BRLTTY supports braille devices.</string>
+
+  <string name="usbMonitor_label">USB Device Attached Monitor</string>
+
+  <string name="about_label_activity">About Application</string>
+  <string name="about_label_copyright">Copyright</string>
+  <string name="about_label_buildTime">Build Time</string>
+  <string name="about_label_sourceRevision">Source Revision</string>
+  <string name="about_label_privacyPolicy">Privacy Policy</string>
+
+  <string name="inputService_name">BRLTTY Input Service</string>
+  <string name="inputService_type">Keyboards on Braille Devices</string>
+  <string name="inputService_not_enabled">input service not enabled</string>
+  <string name="inputService_not_selected">input service not selected</string>
+  <string name="inputService_not_started">input service not started</string>
+  <string name="inputService_not_connected">input service not connected</string>
+
+  <string name="updateApplication_problem_failed">application not updated</string>
+
+  <string name="packageInstaller_problem_failed">package not installed</string>
+  <string name="packageInstaller_problem_same">same version</string>
+  <string name="packageInstaller_problem_downgrade">downgrade not allowed</string>
+
+  <string name="fileDownloader_title">BRLTTY File Downloader</string>
+  <string name="fileDownloader_problem_failed">file not downloaded</string>
+  <string name="fileDownloader_state_connecting">connecting</string>
+  <string name="fileDownloader_state_downloading">downloading</string>
+
+  <string name="setting_state_off">Off</string>
+  <string name="checkbox_state_unchecked">No</string>
+  <string name="checkbox_state_checked">Yes</string>
+
+  <string name="braille_channel_name">Braille Device State</string>
+  <string name="braille_hint_tap">tap for actions</string>
+  <string name="braille_state_released">Released</string>
+  <string name="braille_state_waiting">Waiting</string>
+  <string name="braille_state_connected">Connected</string>
+
+  <string name="GLOBAL_BUTTON_NOTIFICATIONS">Notifications</string>
+  <string name="GLOBAL_BUTTON_QUICK_SETTINGS">Quick Settings</string>
+  <string name="GLOBAL_BUTTON_BACK">Back</string>
+  <string name="GLOBAL_BUTTON_HOME">Home</string>
+  <string name="GLOBAL_BUTTON_RECENT_APPS">Recent Apps</string>
+  <string name="GLOBAL_BUTTON_OVERVIEW">Overview</string>
+
+  <string name="GLOBAL_BUTTON_SWITCH_INPUT_METHOD">Switch Input Method</string>
+  <string name="GLOBAL_BUTTON_VIEW_USER_GUIDE">View User Guide</string>
+  <string name="GLOBAL_BUTTON_BROWSE_WEB_SITE">Browse Web Site</string>
+  <string name="GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES">Browse Community Messages</string>
+  <string name="GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE">Post Community Message</string>
+  <string name="GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP">Manage Community Membership</string>
+
+  <string name="GLOBAL_BUTTON_UPDATE_APPLICATION">Update Application</string>
+  <string name="GLOBAL_CHECKBOX_DEVELOPER_BUILD">Developer Build</string>
+  <string name="GLOBAL_CHECKBOX_ALLOW_DOWNGRADE">Allow Downgrade</string>
+
+  <string name="CHOOSER_TITLE_ACCESSIBILITY_ACTIONS">Accessibility Actions</string>
+
+  <string name="SETTINGS_SCREEN_MAIN">BRLTTY Settings</string>
+  <string name="SETTINGS_SCREEN_GENERAL">General Settings</string>
+  <string name="SETTINGS_SCREEN_MESSAGE">Message Settings</string>
+  <string name="SETTINGS_SCREEN_DEVICES">Manage Devices</string>
+  <string name="SETTINGS_SCREEN_ADVANCED">Advanced Settings</string>
+
+  <string name="SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE">Release Braille Device</string>
+  <string name="SETTINGS_BUTTON_NAVIGATION_MODE">Navigation Mode</string>
+  <string name="SETTINGS_BUTTON_SPEECH_SUPPORT">Speech Support</string>
+
+  <string name="SETTINGS_BUTTON_TEXT_TABLE">Text Table</string>
+  <string name="SETTINGS_BUTTON_ATTRIBUTES_TABLE">Attributes Table</string>
+  <string name="SETTINGS_BUTTON_CONTRACTION_TABLE">Contraction Table</string>
+  <string name="SETTINGS_BUTTON_KEYBOARD_TABLE">Keyboard Table</string>
+
+  <string name="SETTINGS_CHECKBOX_SHOW_ALERTS">Show Alerts</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS">Show Announcements</string>
+  <string name="SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS">Show Notifications</string>
+
+  <string name="SETTINGS_BUTTON_LOG_LEVEL">Log Level</string>
+  <string name="SETTINGS_BUTTON_LOG_CATEGORIES">Log Categories</string>
+  <string name="SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS">Log Accessibility Events</string>
+  <string name="SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN">Log Rendered Screen</string>
+  <string name="SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS">Log Keyboard Events</string>
+  <string name="SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS">Log Unhandled Events</string>
+
+  <string name="DEVICES_BUTTON_SELECTED">Selected Device</string>
+  <string name="DEVICES_BUTTON_ADD">Add Device</string>
+  <string name="DEVICES_BUTTON_REMOVE">Remove Device</string>
+
+  <string name="SELECTED_DEVICE_NONE">no devices</string>
+  <string name="SELECTED_DEVICE_UNSELECTED">device not selected</string>
+
+  <string name="ADD_DEVICE_NAME">Device Name</string>
+  <string name="ADD_DEVICE_METHOD">Communication Method</string>
+  <string name="ADD_DEVICE_SELECT">Select Device</string>
+  <string name="ADD_DEVICE_DRIVER">Braille Driver</string>
+  <string name="ADD_DEVICE_DONE">Done</string>
+  <string name="ADD_DEVICE_UNSELECTED_METHOD">communication method not selected</string>
+  <string name="ADD_DEVICE_UNSELECTED_DEVICE">device not selected</string>
+  <string name="ADD_DEVICE_UNSELECTED_DRIVER">braille driver not selected</string>
+  <string name="ADD_DEVICE_NO_DEVICES">no devices</string>
+  <string name="ADD_DEVICE_DUPLICATE_NAME">device name already in use</string>
+  <string name="ADD_DEVICE_NO_PERMISSION">access to device not granted</string>
+
+  <string name="REMOVE_DEVICE_PROMPT">Would you really like to remove this device?</string>
+
+  <string name="SET_SELECTION_NONE">none selected</string>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/values/text-table.xml b/Android/Gradle/app/src/main/res/values/text-table.xml
new file mode 100644
index 0000000..fd55a27
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/values/text-table.xml
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- generated from brltty.conf.in by mksettings -->
+<!-- changes made here will be overwritten -->
+
+<resources>
+  <string name="TEXT_TABLE_LABEL_auto" translatable="true">locale-based autoselection</string>
+  <string name="TEXT_TABLE_LABEL_ar" translatable="true">Arabic (generic)</string>
+  <string name="TEXT_TABLE_LABEL_as" translatable="true">Assamese</string>
+  <string name="TEXT_TABLE_LABEL_awa" translatable="true">Awadhi</string>
+  <string name="TEXT_TABLE_LABEL_bg" translatable="true">Bulgarian</string>
+  <string name="TEXT_TABLE_LABEL_bh" translatable="true">Bihari</string>
+  <string name="TEXT_TABLE_LABEL_bn" translatable="true">Bengali</string>
+  <string name="TEXT_TABLE_LABEL_bo" translatable="true">Tibetan</string>
+  <string name="TEXT_TABLE_LABEL_bra" translatable="true">Braj</string>
+  <string name="TEXT_TABLE_LABEL_brf" translatable="true">Braille Ready Format (for viewing .brf files within an editor or pager)</string>
+  <string name="TEXT_TABLE_LABEL_cs" translatable="true">Czech</string>
+  <string name="TEXT_TABLE_LABEL_cy" translatable="true">Welsh</string>
+  <string name="TEXT_TABLE_LABEL_da" translatable="true">Danish</string>
+  <string name="TEXT_TABLE_LABEL_da_1252" translatable="true">Danish (Svend Thougaard, 2002–11–18)</string>
+  <string name="TEXT_TABLE_LABEL_da_lt" translatable="true">Danish (LogText)</string>
+  <string name="TEXT_TABLE_LABEL_de" translatable="true">German</string>
+  <string name="TEXT_TABLE_LABEL_dra" translatable="true">Dravidian</string>
+  <string name="TEXT_TABLE_LABEL_el" translatable="true">Greek</string>
+  <string name="TEXT_TABLE_LABEL_en" translatable="true">English</string>
+  <string name="TEXT_TABLE_LABEL_en_nabcc" translatable="true">English (North American Braille Computer Code)</string>
+  <string name="TEXT_TABLE_LABEL_en_CA" translatable="true">English (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_en_GB" translatable="true">English (United Kingdom)</string>
+  <string name="TEXT_TABLE_LABEL_en_US" translatable="true">English (United States)</string>
+  <string name="TEXT_TABLE_LABEL_eo" translatable="true">Esperanto</string>
+  <string name="TEXT_TABLE_LABEL_es" translatable="true">Spanish</string>
+  <string name="TEXT_TABLE_LABEL_et" translatable="true">Estonian</string>
+  <string name="TEXT_TABLE_LABEL_fi" translatable="true">Finnish</string>
+  <string name="TEXT_TABLE_LABEL_fr" translatable="true">French</string>
+  <string name="TEXT_TABLE_LABEL_fr_2007" translatable="true">French (unified 2007)</string>
+  <string name="TEXT_TABLE_LABEL_fr_cbifs" translatable="true">French (Code Braille Informatique Français Standard)</string>
+  <string name="TEXT_TABLE_LABEL_fr_vs" translatable="true">French (VisioBraille)</string>
+  <string name="TEXT_TABLE_LABEL_fr_CA" translatable="true">French (Canada)</string>
+  <string name="TEXT_TABLE_LABEL_fr_FR" translatable="true">French (France)</string>
+  <string name="TEXT_TABLE_LABEL_ga" translatable="true">Irish</string>
+  <string name="TEXT_TABLE_LABEL_gd" translatable="true">Gaelic</string>
+  <string name="TEXT_TABLE_LABEL_gon" translatable="true">Gondi</string>
+  <string name="TEXT_TABLE_LABEL_gu" translatable="true">Gujarati</string>
+  <string name="TEXT_TABLE_LABEL_he" translatable="true">Hebrew</string>
+  <string name="TEXT_TABLE_LABEL_hi" translatable="true">Hindi</string>
+  <string name="TEXT_TABLE_LABEL_hr" translatable="true">Croatian</string>
+  <string name="TEXT_TABLE_LABEL_hu" translatable="true">Hungarian</string>
+  <string name="TEXT_TABLE_LABEL_hy" translatable="true">Armenian</string>
+  <string name="TEXT_TABLE_LABEL_is" translatable="true">Icelandic</string>
+  <string name="TEXT_TABLE_LABEL_it" translatable="true">Italian</string>
+  <string name="TEXT_TABLE_LABEL_kha" translatable="true">Khasi</string>
+  <string name="TEXT_TABLE_LABEL_kn" translatable="true">Kannada</string>
+  <string name="TEXT_TABLE_LABEL_kok" translatable="true">Konkani</string>
+  <string name="TEXT_TABLE_LABEL_kru" translatable="true">Kurukh</string>
+  <string name="TEXT_TABLE_LABEL_lt" translatable="true">Lituanian</string>
+  <string name="TEXT_TABLE_LABEL_lv" translatable="true">Latvian</string>
+  <string name="TEXT_TABLE_LABEL_mg" translatable="true">Malagasy</string>
+  <string name="TEXT_TABLE_LABEL_mi" translatable="true">Maori</string>
+  <string name="TEXT_TABLE_LABEL_ml" translatable="true">Malayalam</string>
+  <string name="TEXT_TABLE_LABEL_mni" translatable="true">Manipuri</string>
+  <string name="TEXT_TABLE_LABEL_mr" translatable="true">Marathi</string>
+  <string name="TEXT_TABLE_LABEL_mt" translatable="true">Maltese</string>
+  <string name="TEXT_TABLE_LABEL_mun" translatable="true">Munda</string>
+  <string name="TEXT_TABLE_LABEL_mwr" translatable="true">Marwari</string>
+  <string name="TEXT_TABLE_LABEL_ne" translatable="true">Nepali</string>
+  <string name="TEXT_TABLE_LABEL_new" translatable="true">Newari</string>
+  <string name="TEXT_TABLE_LABEL_nl" translatable="true">Dutch</string>
+  <string name="TEXT_TABLE_LABEL_nl_BE" translatable="true">Dutch (Belgium)</string>
+  <string name="TEXT_TABLE_LABEL_nl_NL" translatable="true">Dutch (Netherlands)</string>
+  <string name="TEXT_TABLE_LABEL_no" translatable="true">Norwegian</string>
+  <string name="TEXT_TABLE_LABEL_no_generic" translatable="true">Norwegian (with support for other languages)</string>
+  <string name="TEXT_TABLE_LABEL_no_oup" translatable="true">Norwegian (Offentlig utvalg for punktskrift)</string>
+  <string name="TEXT_TABLE_LABEL_nwc" translatable="true">Newari (old)</string>
+  <string name="TEXT_TABLE_LABEL_or" translatable="true">Oriya</string>
+  <string name="TEXT_TABLE_LABEL_pa" translatable="true">Panjabi</string>
+  <string name="TEXT_TABLE_LABEL_pi" translatable="true">Pali</string>
+  <string name="TEXT_TABLE_LABEL_pl" translatable="true">Polish</string>
+  <string name="TEXT_TABLE_LABEL_pt" translatable="true">Portuguese</string>
+  <string name="TEXT_TABLE_LABEL_ro" translatable="true">Romanian</string>
+  <string name="TEXT_TABLE_LABEL_ru" translatable="true">Russian</string>
+  <string name="TEXT_TABLE_LABEL_sa" translatable="true">Sanskrit</string>
+  <string name="TEXT_TABLE_LABEL_sat" translatable="true">Santali</string>
+  <string name="TEXT_TABLE_LABEL_sd" translatable="true">Sindhi</string>
+  <string name="TEXT_TABLE_LABEL_se" translatable="true">Sami (Northern)</string>
+  <string name="TEXT_TABLE_LABEL_sk" translatable="true">Slovak</string>
+  <string name="TEXT_TABLE_LABEL_sl" translatable="true">Slovenian</string>
+  <string name="TEXT_TABLE_LABEL_sv" translatable="true">Swedish</string>
+  <string name="TEXT_TABLE_LABEL_sv_1989" translatable="true">Swedish (1989 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sv_1996" translatable="true">Swedish (1996 standard)</string>
+  <string name="TEXT_TABLE_LABEL_sw" translatable="true">Swahili</string>
+  <string name="TEXT_TABLE_LABEL_ta" translatable="true">Tamil</string>
+  <string name="TEXT_TABLE_LABEL_te" translatable="true">Telugu</string>
+  <string name="TEXT_TABLE_LABEL_tr" translatable="true">Turkish</string>
+  <string name="TEXT_TABLE_LABEL_uk" translatable="true">Ukrainian</string>
+  <string name="TEXT_TABLE_LABEL_vi" translatable="true">Vietnamese</string>
+
+  <string-array name="TEXT_TABLE_LABELS">
+    <item>@string/TEXT_TABLE_LABEL_auto</item>
+    <item>@string/TEXT_TABLE_LABEL_ar</item>
+    <item>@string/TEXT_TABLE_LABEL_as</item>
+    <item>@string/TEXT_TABLE_LABEL_awa</item>
+    <item>@string/TEXT_TABLE_LABEL_bg</item>
+    <item>@string/TEXT_TABLE_LABEL_bh</item>
+    <item>@string/TEXT_TABLE_LABEL_bn</item>
+    <item>@string/TEXT_TABLE_LABEL_bo</item>
+    <item>@string/TEXT_TABLE_LABEL_bra</item>
+    <item>@string/TEXT_TABLE_LABEL_brf</item>
+    <item>@string/TEXT_TABLE_LABEL_cs</item>
+    <item>@string/TEXT_TABLE_LABEL_cy</item>
+    <item>@string/TEXT_TABLE_LABEL_da</item>
+    <item>@string/TEXT_TABLE_LABEL_da_1252</item>
+    <item>@string/TEXT_TABLE_LABEL_da_lt</item>
+    <item>@string/TEXT_TABLE_LABEL_de</item>
+    <item>@string/TEXT_TABLE_LABEL_dra</item>
+    <item>@string/TEXT_TABLE_LABEL_el</item>
+    <item>@string/TEXT_TABLE_LABEL_en</item>
+    <item>@string/TEXT_TABLE_LABEL_en_nabcc</item>
+    <item>@string/TEXT_TABLE_LABEL_en_CA</item>
+    <item>@string/TEXT_TABLE_LABEL_en_GB</item>
+    <item>@string/TEXT_TABLE_LABEL_en_US</item>
+    <item>@string/TEXT_TABLE_LABEL_eo</item>
+    <item>@string/TEXT_TABLE_LABEL_es</item>
+    <item>@string/TEXT_TABLE_LABEL_et</item>
+    <item>@string/TEXT_TABLE_LABEL_fi</item>
+    <item>@string/TEXT_TABLE_LABEL_fr</item>
+    <item>@string/TEXT_TABLE_LABEL_fr_2007</item>
+    <item>@string/TEXT_TABLE_LABEL_fr_cbifs</item>
+    <item>@string/TEXT_TABLE_LABEL_fr_vs</item>
+    <item>@string/TEXT_TABLE_LABEL_fr_CA</item>
+    <item>@string/TEXT_TABLE_LABEL_fr_FR</item>
+    <item>@string/TEXT_TABLE_LABEL_ga</item>
+    <item>@string/TEXT_TABLE_LABEL_gd</item>
+    <item>@string/TEXT_TABLE_LABEL_gon</item>
+    <item>@string/TEXT_TABLE_LABEL_gu</item>
+    <item>@string/TEXT_TABLE_LABEL_he</item>
+    <item>@string/TEXT_TABLE_LABEL_hi</item>
+    <item>@string/TEXT_TABLE_LABEL_hr</item>
+    <item>@string/TEXT_TABLE_LABEL_hu</item>
+    <item>@string/TEXT_TABLE_LABEL_hy</item>
+    <item>@string/TEXT_TABLE_LABEL_is</item>
+    <item>@string/TEXT_TABLE_LABEL_it</item>
+    <item>@string/TEXT_TABLE_LABEL_kha</item>
+    <item>@string/TEXT_TABLE_LABEL_kn</item>
+    <item>@string/TEXT_TABLE_LABEL_kok</item>
+    <item>@string/TEXT_TABLE_LABEL_kru</item>
+    <item>@string/TEXT_TABLE_LABEL_lt</item>
+    <item>@string/TEXT_TABLE_LABEL_lv</item>
+    <item>@string/TEXT_TABLE_LABEL_mg</item>
+    <item>@string/TEXT_TABLE_LABEL_mi</item>
+    <item>@string/TEXT_TABLE_LABEL_ml</item>
+    <item>@string/TEXT_TABLE_LABEL_mni</item>
+    <item>@string/TEXT_TABLE_LABEL_mr</item>
+    <item>@string/TEXT_TABLE_LABEL_mt</item>
+    <item>@string/TEXT_TABLE_LABEL_mun</item>
+    <item>@string/TEXT_TABLE_LABEL_mwr</item>
+    <item>@string/TEXT_TABLE_LABEL_ne</item>
+    <item>@string/TEXT_TABLE_LABEL_new</item>
+    <item>@string/TEXT_TABLE_LABEL_nl</item>
+    <item>@string/TEXT_TABLE_LABEL_nl_BE</item>
+    <item>@string/TEXT_TABLE_LABEL_nl_NL</item>
+    <item>@string/TEXT_TABLE_LABEL_no</item>
+    <item>@string/TEXT_TABLE_LABEL_no_generic</item>
+    <item>@string/TEXT_TABLE_LABEL_no_oup</item>
+    <item>@string/TEXT_TABLE_LABEL_nwc</item>
+    <item>@string/TEXT_TABLE_LABEL_or</item>
+    <item>@string/TEXT_TABLE_LABEL_pa</item>
+    <item>@string/TEXT_TABLE_LABEL_pi</item>
+    <item>@string/TEXT_TABLE_LABEL_pl</item>
+    <item>@string/TEXT_TABLE_LABEL_pt</item>
+    <item>@string/TEXT_TABLE_LABEL_ro</item>
+    <item>@string/TEXT_TABLE_LABEL_ru</item>
+    <item>@string/TEXT_TABLE_LABEL_sa</item>
+    <item>@string/TEXT_TABLE_LABEL_sat</item>
+    <item>@string/TEXT_TABLE_LABEL_sd</item>
+    <item>@string/TEXT_TABLE_LABEL_se</item>
+    <item>@string/TEXT_TABLE_LABEL_sk</item>
+    <item>@string/TEXT_TABLE_LABEL_sl</item>
+    <item>@string/TEXT_TABLE_LABEL_sv</item>
+    <item>@string/TEXT_TABLE_LABEL_sv_1989</item>
+    <item>@string/TEXT_TABLE_LABEL_sv_1996</item>
+    <item>@string/TEXT_TABLE_LABEL_sw</item>
+    <item>@string/TEXT_TABLE_LABEL_ta</item>
+    <item>@string/TEXT_TABLE_LABEL_te</item>
+    <item>@string/TEXT_TABLE_LABEL_tr</item>
+    <item>@string/TEXT_TABLE_LABEL_uk</item>
+    <item>@string/TEXT_TABLE_LABEL_vi</item>
+  </string-array>
+
+  <string-array name="TEXT_TABLE_VALUES">
+    <item>auto</item>
+    <item>ar</item>
+    <item>as</item>
+    <item>awa</item>
+    <item>bg</item>
+    <item>bh</item>
+    <item>bn</item>
+    <item>bo</item>
+    <item>bra</item>
+    <item>brf</item>
+    <item>cs</item>
+    <item>cy</item>
+    <item>da</item>
+    <item>da-1252</item>
+    <item>da-lt</item>
+    <item>de</item>
+    <item>dra</item>
+    <item>el</item>
+    <item>en</item>
+    <item>en-nabcc</item>
+    <item>en_CA</item>
+    <item>en_GB</item>
+    <item>en_US</item>
+    <item>eo</item>
+    <item>es</item>
+    <item>et</item>
+    <item>fi</item>
+    <item>fr</item>
+    <item>fr-2007</item>
+    <item>fr-cbifs</item>
+    <item>fr-vs</item>
+    <item>fr_CA</item>
+    <item>fr_FR</item>
+    <item>ga</item>
+    <item>gd</item>
+    <item>gon</item>
+    <item>gu</item>
+    <item>he</item>
+    <item>hi</item>
+    <item>hr</item>
+    <item>hu</item>
+    <item>hy</item>
+    <item>is</item>
+    <item>it</item>
+    <item>kha</item>
+    <item>kn</item>
+    <item>kok</item>
+    <item>kru</item>
+    <item>lt</item>
+    <item>lv</item>
+    <item>mg</item>
+    <item>mi</item>
+    <item>ml</item>
+    <item>mni</item>
+    <item>mr</item>
+    <item>mt</item>
+    <item>mun</item>
+    <item>mwr</item>
+    <item>ne</item>
+    <item>new</item>
+    <item>nl</item>
+    <item>nl_BE</item>
+    <item>nl_NL</item>
+    <item>no</item>
+    <item>no-generic</item>
+    <item>no-oup</item>
+    <item>nwc</item>
+    <item>or</item>
+    <item>pa</item>
+    <item>pi</item>
+    <item>pl</item>
+    <item>pt</item>
+    <item>ro</item>
+    <item>ru</item>
+    <item>sa</item>
+    <item>sat</item>
+    <item>sd</item>
+    <item>se</item>
+    <item>sk</item>
+    <item>sl</item>
+    <item>sv</item>
+    <item>sv-1989</item>
+    <item>sv-1996</item>
+    <item>sw</item>
+    <item>ta</item>
+    <item>te</item>
+    <item>tr</item>
+    <item>uk</item>
+    <item>vi</item>
+  </string-array>
+</resources>
diff --git a/Android/Gradle/app/src/main/res/xml/accessibility_service.xml b/Android/Gradle/app/src/main/res/xml/accessibility_service.xml
new file mode 100644
index 0000000..14d637a
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/accessibility_service.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+  android:isAccessibilityTool="true"
+  android:description="@string/app_description"
+  android:accessibilityEventTypes="typeAllMask"
+  android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility|flagRetrieveInteractiveWindows|flagReportViewIds|flagRequestAccessibilityButton"
+  android:accessibilityFeedbackType="feedbackGeneric"
+  android:notificationTimeout="100"
+  android:canRetrieveWindowContent="true"
+  android:settingsActivity="org.a11y.brltty.android.activities.ActionsActivity"
+ />
diff --git a/Android/Gradle/app/src/main/res/xml/backup_rules.xml b/Android/Gradle/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..8c01934
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<full-backup-content>
+  <include domain="sharedpref" path="." />
+  <include domain="external" path="." />
+</full-backup-content>
diff --git a/Android/Gradle/app/src/main/res/xml/file_provider_paths.xml b/Android/Gradle/app/src/main/res/xml/file_provider_paths.xml
new file mode 100644
index 0000000..7b967b5
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/file_provider_paths.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+  <cache-path name="cache-public" path="public/" />
+</paths>
diff --git a/Android/Gradle/app/src/main/res/xml/input_service.xml b/Android/Gradle/app/src/main/res/xml/input_service.xml
new file mode 100644
index 0000000..8796717
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/input_service.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+  android:isDefault="false"
+  >
+
+  <subtype
+    android:label="@string/inputService_type"
+   />
+</input-method>
diff --git a/Android/Gradle/app/src/main/res/xml/settings_advanced.xml b/Android/Gradle/app/src/main/res/xml/settings_advanced.xml
new file mode 100644
index 0000000..09dd6ca
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/settings_advanced.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+  android:title="@string/SETTINGS_SCREEN_ADVANCED"
+  >
+
+  <ListPreference
+    android:key="@string/PREF_KEY_KEYBOARD_TABLE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_KEYBOARD_TABLE"
+    android:entries="@array/KEYBOARD_TABLE_LABELS"
+    android:entryValues="@array/KEYBOARD_TABLE_VALUES"
+    android:defaultValue="@string/DEFAULT_KEYBOARD_TABLE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_ATTRIBUTES_TABLE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_ATTRIBUTES_TABLE"
+    android:entries="@array/ATTRIBUTES_TABLE_LABELS"
+    android:entryValues="@array/ATTRIBUTES_TABLE_VALUES"
+    android:defaultValue="@string/DEFAULT_ATTRIBUTES_TABLE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_LOG_LEVEL"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_LOG_LEVEL"
+    android:entries="@array/LOG_LEVEL_LABELS"
+    android:entryValues="@array/LOG_LEVEL_VALUES"
+    android:defaultValue="@string/DEFAULT_LOG_LEVEL"
+   />
+
+  <MultiSelectListPreference
+    android:key="@string/PREF_KEY_LOG_CATEGORIES"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_LOG_CATEGORIES"
+    android:entries="@array/LOG_CATEGORY_LABELS"
+    android:entryValues="@array/LOG_CATEGORY_VALUES"
+    android:defaultValue="@array/EMPTY_STRING_ARRAY"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_LOG_ACCESSIBILITY_EVENTS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_LOG_RENDERED_SCREEN"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_LOG_KEYBOARD_EVENTS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_LOG_UNHANDLED_EVENTS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS"
+   />
+</PreferenceScreen>
diff --git a/Android/Gradle/app/src/main/res/xml/settings_devices.xml b/Android/Gradle/app/src/main/res/xml/settings_devices.xml
new file mode 100644
index 0000000..ae03555
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/settings_devices.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+  android:title="@string/SETTINGS_SCREEN_DEVICES"
+  >
+
+  <ListPreference
+    android:key="@string/PREF_KEY_SELECTED_DEVICE"
+    android:persistent="true"
+    android:title="@string/DEVICES_BUTTON_SELECTED"
+   />
+
+  <PreferenceScreen
+    android:key="@string/PREF_KEY_ADD_DEVICE"
+    android:title="@string/DEVICES_BUTTON_ADD"
+    >
+
+    <EditTextPreference
+      android:key="@string/PREF_KEY_DEVICE_NAME"
+      android:persistent="false"
+      android:title="@string/ADD_DEVICE_NAME"
+      android:defaultValue=""
+     />
+
+    <ListPreference
+      android:key="@string/PREF_KEY_DEVICE_METHOD"
+      android:persistent="false"
+      android:title="@string/ADD_DEVICE_METHOD"
+      android:entries="@array/DEVICE_METHOD_LABELS"
+      android:entryValues="@array/DEVICE_METHOD_VALUES"
+      android:defaultValue="@string/DEFAULT_COMMUNICATION_METHOD"
+     />
+
+    <ListPreference
+      android:key="@string/PREF_KEY_DEVICE_IDENTIFIER"
+      android:persistent="false"
+      android:title="@string/ADD_DEVICE_SELECT"
+      android:entries="@array/EMPTY_STRING_ARRAY"
+      android:entryValues="@array/EMPTY_STRING_ARRAY"
+     />
+
+    <ListPreference
+      android:key="@string/PREF_KEY_DEVICE_DRIVER"
+      android:persistent="false"
+      android:title="@string/ADD_DEVICE_DRIVER"
+      android:entries="@array/BRAILLE_DRIVER_LABELS"
+      android:entryValues="@array/BRAILLE_DRIVER_VALUES"
+      android:defaultValue="@string/DEFAULT_BRAILLE_DRIVER"
+     />
+
+    <Preference
+      android:key="@string/PREF_KEY_DEVICE_ADD"
+      android:persistent="false"
+      android:title="@string/ADD_DEVICE_DONE"
+     />
+  </PreferenceScreen>
+
+  <PreferenceScreen
+    android:key="@string/PREF_KEY_REMOVE_DEVICE"
+    android:title="@string/DEVICES_BUTTON_REMOVE"
+    >
+
+    <Preference
+      android:key="@string/PREF_KEY_REMOVE_DEVICE_PROMPT"
+      android:persistent="false"
+      android:title="@string/REMOVE_DEVICE_PROMPT"
+     />
+
+    <Preference
+      android:key="@string/PREF_KEY_REMOVE_DEVICE_CANCEL"
+      android:persistent="false"
+      android:title="@string/android:no"
+     />
+
+    <Preference
+      android:key="@string/PREF_KEY_REMOVE_DEVICE_CONFIRM"
+      android:persistent="false"
+      android:title="@string/android:yes"
+     />
+  </PreferenceScreen>
+</PreferenceScreen>
diff --git a/Android/Gradle/app/src/main/res/xml/settings_general.xml b/Android/Gradle/app/src/main/res/xml/settings_general.xml
new file mode 100644
index 0000000..47859c1
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/settings_general.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+  android:title="@string/SETTINGS_SCREEN_GENERAL"
+  >
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_RELEASE_BRAILLE_DEVICE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_NAVIGATION_MODE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_NAVIGATION_MODE"
+    android:entries="@array/NAVIGATION_MODE_LABELS"
+    android:entryValues="@array/NAVIGATION_MODE_VALUES"
+    android:defaultValue="@string/DEFAULT_NAVIGATION_MODE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_TEXT_TABLE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_TEXT_TABLE"
+    android:entries="@array/TEXT_TABLE_LABELS"
+    android:entryValues="@array/TEXT_TABLE_VALUES"
+    android:defaultValue="@string/DEFAULT_TEXT_TABLE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_CONTRACTION_TABLE"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_CONTRACTION_TABLE"
+    android:entries="@array/CONTRACTION_TABLE_LABELS"
+    android:entryValues="@array/CONTRACTION_TABLE_VALUES"
+    android:defaultValue="@string/DEFAULT_CONTRACTION_TABLE"
+   />
+
+  <ListPreference
+    android:key="@string/PREF_KEY_SPEECH_SUPPORT"
+    android:persistent="true"
+    android:title="@string/SETTINGS_BUTTON_SPEECH_SUPPORT"
+    android:entries="@array/SPEECH_SUPPORT_LABELS"
+    android:entryValues="@array/SPEECH_SUPPORT_VALUES"
+    android:defaultValue="@string/DEFAULT_SPEECH_SUPPORT"
+   />
+</PreferenceScreen>
diff --git a/Android/Gradle/app/src/main/res/xml/settings_headers.xml b/Android/Gradle/app/src/main/res/xml/settings_headers.xml
new file mode 100644
index 0000000..2cf9630
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/settings_headers.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"
+  >
+
+  <header
+    android:fragment="org.a11y.brltty.android.settings.GeneralSettings"
+    android:title="@string/SETTINGS_SCREEN_GENERAL"
+   />
+
+  <header
+    android:fragment="org.a11y.brltty.android.settings.MessageSettings"
+    android:title="@string/SETTINGS_SCREEN_MESSAGE"
+   />
+
+  <header
+    android:fragment="org.a11y.brltty.android.settings.DeviceManager"
+    android:title="@string/SETTINGS_SCREEN_DEVICES"
+   />
+
+  <header
+    android:fragment="org.a11y.brltty.android.settings.AdvancedSettings"
+    android:title="@string/SETTINGS_SCREEN_ADVANCED"
+   />
+</preference-headers>
diff --git a/Android/Gradle/app/src/main/res/xml/settings_message.xml b/Android/Gradle/app/src/main/res/xml/settings_message.xml
new file mode 100644
index 0000000..e748a31
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/settings_message.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+  android:title="@string/SETTINGS_SCREEN_MESSAGE"
+  >
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_SHOW_NOTIFICATIONS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_SHOW_ALERTS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_SHOW_ALERTS"
+   />
+
+  <CheckBoxPreference
+    android:key="@string/PREF_KEY_SHOW_ANNOUNCEMENTS"
+    android:persistent="true"
+    android:title="@string/SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS"
+   />
+</PreferenceScreen>
diff --git a/Android/Gradle/app/src/main/res/xml/usb_devices.xml b/Android/Gradle/app/src/main/res/xml/usb_devices.xml
new file mode 100644
index 0000000..8d4c2fa
--- /dev/null
+++ b/Android/Gradle/app/src/main/res/xml/usb_devices.xml
@@ -0,0 +1,514 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+  <!-- BEGIN_USB_BRAILLE_DEVICES -->
+
+  <!-- Device: 0403:6001 -->
+  <!-- Generic Identifier -->
+  <!-- Vendor: Future Technology Devices International, Ltd. -->
+  <!-- Product: FT232 USB-Serial (UART) IC -->
+  <!-- Albatross [all models] -->
+  <!-- Cebra [all models] -->
+  <!-- HIMS [Sync Braille] -->
+  <!-- HandyTech [FTDI chip] -->
+  <!-- Hedo [MobilLine] -->
+  <!-- MDV [all models] -->
+  <usb-device vendor-id="1027" product-id="24577" />
+
+  <!-- Device: 0403:6010 -->
+  <!-- Generic Identifier -->
+  <!-- Vendor: Future Technology Devices International, Ltd -->
+  <!-- Product: FT2232C/D/H Dual UART/FIFO IC -->
+  <!-- DotPad [all models] -->
+  <usb-device vendor-id="1027" product-id="24592" />
+
+  <!-- Device: 0403:DE58 -->
+  <!-- Hedo [MobilLine] -->
+  <usb-device vendor-id="1027" product-id="56920" />
+
+  <!-- Device: 0403:DE59 -->
+  <!-- Hedo [ProfiLine] -->
+  <usb-device vendor-id="1027" product-id="56921" />
+
+  <!-- Device: 0403:F208 -->
+  <!-- Papenmeier [all models] -->
+  <usb-device vendor-id="1027" product-id="61960" />
+
+  <!-- Device: 0403:FE70 -->
+  <!-- Baum [Vario 40 (40 cells)] -->
+  <usb-device vendor-id="1027" product-id="65136" />
+
+  <!-- Device: 0403:FE71 -->
+  <!-- Baum [PocketVario (24 cells)] -->
+  <usb-device vendor-id="1027" product-id="65137" />
+
+  <!-- Device: 0403:FE72 -->
+  <!-- Baum [SuperVario 40 (40 cells)] -->
+  <usb-device vendor-id="1027" product-id="65138" />
+
+  <!-- Device: 0403:FE73 -->
+  <!-- Baum [SuperVario 32 (32 cells)] -->
+  <usb-device vendor-id="1027" product-id="65139" />
+
+  <!-- Device: 0403:FE74 -->
+  <!-- Baum [SuperVario 64 (64 cells)] -->
+  <usb-device vendor-id="1027" product-id="65140" />
+
+  <!-- Device: 0403:FE75 -->
+  <!-- Baum [SuperVario 80 (80 cells)] -->
+  <usb-device vendor-id="1027" product-id="65141" />
+
+  <!-- Device: 0403:FE76 -->
+  <!-- Baum [VarioPro 80 (80 cells)] -->
+  <usb-device vendor-id="1027" product-id="65142" />
+
+  <!-- Device: 0403:FE77 -->
+  <!-- Baum [VarioPro 64 (64 cells)] -->
+  <usb-device vendor-id="1027" product-id="65143" />
+
+  <!-- Device: 0452:0100 -->
+  <!-- Metec [all models] -->
+  <usb-device vendor-id="1106" product-id="256" />
+
+  <!-- Device: 045E:930A -->
+  <!-- HIMS [Braille Sense (USB 1.1)] -->
+  <!-- HIMS [Braille Sense (USB 2.0)] -->
+  <!-- HIMS [Braille Sense U2 (USB 2.0)] -->
+  <usb-device vendor-id="1118" product-id="37642" />
+
+  <!-- Device: 045E:930B -->
+  <!-- HIMS [Braille Edge and QBrailleXL] -->
+  <usb-device vendor-id="1118" product-id="37643" />
+
+  <!-- Device: 0483:A1D3 -->
+  <!-- Baum [Orbit Reader 20 (20 cells)] -->
+  <usb-device vendor-id="1155" product-id="41427" />
+
+  <!-- Device: 0483:A366 -->
+  <!-- Baum [Orbit Reader 40 (40 cells)] -->
+  <usb-device vendor-id="1155" product-id="41830" />
+
+  <!-- Device: 06B0:0001 -->
+  <!-- Alva [Satellite (5nn)] -->
+  <usb-device vendor-id="1712" product-id="1" />
+
+  <!-- Device: 0798:0001 -->
+  <!-- Voyager [all models] -->
+  <usb-device vendor-id="1944" product-id="1" />
+
+  <!-- Device: 0798:0600 -->
+  <!-- Alva [Voyager Protocol Converter] -->
+  <usb-device vendor-id="1944" product-id="1536" />
+
+  <!-- Device: 0798:0624 -->
+  <!-- Alva [BC624] -->
+  <usb-device vendor-id="1944" product-id="1572" />
+
+  <!-- Device: 0798:0640 -->
+  <!-- Alva [BC640] -->
+  <usb-device vendor-id="1944" product-id="1600" />
+
+  <!-- Device: 0798:0680 -->
+  <!-- Alva [BC680] -->
+  <usb-device vendor-id="1944" product-id="1664" />
+
+  <!-- Device: 0904:1016 -->
+  <!-- FrankAudiodata [B2K84 (before firmware installation)] -->
+  <usb-device vendor-id="2308" product-id="4118" />
+
+  <!-- Device: 0904:1017 -->
+  <!-- FrankAudiodata [B2K84 (after firmware installation)] -->
+  <usb-device vendor-id="2308" product-id="4119" />
+
+  <!-- Device: 0904:2000 -->
+  <!-- Baum [VarioPro 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="8192" />
+
+  <!-- Device: 0904:2001 -->
+  <!-- Baum [EcoVario 24 (24 cells)] -->
+  <usb-device vendor-id="2308" product-id="8193" />
+
+  <!-- Device: 0904:2002 -->
+  <!-- Baum [EcoVario 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="8194" />
+
+  <!-- Device: 0904:2007 -->
+  <!-- Baum [VarioConnect 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="8199" />
+
+  <!-- Device: 0904:2008 -->
+  <!-- Baum [VarioConnect 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="8200" />
+
+  <!-- Device: 0904:2009 -->
+  <!-- Baum [VarioConnect 24 (24 cells)] -->
+  <usb-device vendor-id="2308" product-id="8201" />
+
+  <!-- Device: 0904:2010 -->
+  <!-- Baum [VarioConnect 64 (64 cells)] -->
+  <usb-device vendor-id="2308" product-id="8208" />
+
+  <!-- Device: 0904:2011 -->
+  <!-- Baum [VarioConnect 80 (80 cells)] -->
+  <usb-device vendor-id="2308" product-id="8209" />
+
+  <!-- Device: 0904:2014 -->
+  <!-- Baum [EcoVario 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="8212" />
+
+  <!-- Device: 0904:2015 -->
+  <!-- Baum [EcoVario 64 (64 cells)] -->
+  <usb-device vendor-id="2308" product-id="8213" />
+
+  <!-- Device: 0904:2016 -->
+  <!-- Baum [EcoVario 80 (80 cells)] -->
+  <usb-device vendor-id="2308" product-id="8214" />
+
+  <!-- Device: 0904:3000 -->
+  <!-- Baum [Refreshabraille 18 (18 cells)] -->
+  <usb-device vendor-id="2308" product-id="12288" />
+
+  <!-- Device: 0904:3001 -->
+  <!-- Baum [Orbit in Refreshabraille Emulation Mode (18 cells)] -->
+  <!-- Baum [Refreshabraille 18 (18 cells)] -->
+  <usb-device vendor-id="2308" product-id="12289" />
+
+  <!-- Device: 0904:4004 -->
+  <!-- Baum [Pronto! V3 18 (18 cells)] -->
+  <usb-device vendor-id="2308" product-id="16388" />
+
+  <!-- Device: 0904:4005 -->
+  <!-- Baum [Pronto! V3 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="16389" />
+
+  <!-- Device: 0904:4007 -->
+  <!-- Baum [Pronto! V4 18 (18 cells)] -->
+  <usb-device vendor-id="2308" product-id="16391" />
+
+  <!-- Device: 0904:4008 -->
+  <!-- Baum [Pronto! V4 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="16392" />
+
+  <!-- Device: 0904:6001 -->
+  <!-- Baum [SuperVario2 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="24577" />
+
+  <!-- Device: 0904:6002 -->
+  <!-- Baum [PocketVario2 (24 cells)] -->
+  <usb-device vendor-id="2308" product-id="24578" />
+
+  <!-- Device: 0904:6003 -->
+  <!-- Baum [SuperVario2 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="24579" />
+
+  <!-- Device: 0904:6004 -->
+  <!-- Baum [SuperVario2 64 (64 cells)] -->
+  <usb-device vendor-id="2308" product-id="24580" />
+
+  <!-- Device: 0904:6005 -->
+  <!-- Baum [SuperVario2 80 (80 cells)] -->
+  <usb-device vendor-id="2308" product-id="24581" />
+
+  <!-- Device: 0904:6006 -->
+  <!-- Baum [Brailliant2 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="24582" />
+
+  <!-- Device: 0904:6007 -->
+  <!-- Baum [Brailliant2 24 (24 cells)] -->
+  <usb-device vendor-id="2308" product-id="24583" />
+
+  <!-- Device: 0904:6008 -->
+  <!-- Baum [Brailliant2 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="24584" />
+
+  <!-- Device: 0904:6009 -->
+  <!-- Baum [Brailliant2 64 (64 cells)] -->
+  <usb-device vendor-id="2308" product-id="24585" />
+
+  <!-- Device: 0904:600A -->
+  <!-- Baum [Brailliant2 80 (80 cells)] -->
+  <usb-device vendor-id="2308" product-id="24586" />
+
+  <!-- Device: 0904:6011 -->
+  <!-- Baum [VarioConnect 24 (24 cells)] -->
+  <usb-device vendor-id="2308" product-id="24593" />
+
+  <!-- Device: 0904:6012 -->
+  <!-- Baum [VarioConnect 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="24594" />
+
+  <!-- Device: 0904:6013 -->
+  <!-- Baum [VarioConnect 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="24595" />
+
+  <!-- Device: 0904:6101 -->
+  <!-- Baum [VarioUltra 20 (20 cells)] -->
+  <usb-device vendor-id="2308" product-id="24833" />
+
+  <!-- Device: 0904:6102 -->
+  <!-- Baum [VarioUltra 40 (40 cells)] -->
+  <usb-device vendor-id="2308" product-id="24834" />
+
+  <!-- Device: 0904:6103 -->
+  <!-- Baum [VarioUltra 32 (32 cells)] -->
+  <usb-device vendor-id="2308" product-id="24835" />
+
+  <!-- Device: 0921:1200 -->
+  <!-- HandyTech [GoHubs chip] -->
+  <usb-device vendor-id="2337" product-id="4608" />
+
+  <!-- Device: 0F4E:0100 -->
+  <!-- FreedomScientific [Focus 1] -->
+  <usb-device vendor-id="3918" product-id="256" />
+
+  <!-- Device: 0F4E:0111 -->
+  <!-- FreedomScientific [PAC Mate] -->
+  <usb-device vendor-id="3918" product-id="273" />
+
+  <!-- Device: 0F4E:0112 -->
+  <!-- FreedomScientific [Focus 2] -->
+  <usb-device vendor-id="3918" product-id="274" />
+
+  <!-- Device: 0F4E:0114 -->
+  <!-- FreedomScientific [Focus 3+] -->
+  <usb-device vendor-id="3918" product-id="276" />
+
+  <!-- Device: 10C4:EA60 -->
+  <!-- Generic Identifier -->
+  <!-- Vendor: Cygnal Integrated Products, Inc. -->
+  <!-- Product: CP210x UART Bridge / myAVR mySmartUSB light -->
+  <!-- BrailleMemo [Pocket] -->
+  <!-- Seika [Braille Display] -->
+  <usb-device vendor-id="4292" product-id="60000" />
+
+  <!-- Device: 10C4:EA80 -->
+  <!-- Generic Identifier -->
+  <!-- Vendor: Cygnal Integrated Products, Inc. -->
+  <!-- Product: CP210x UART Bridge -->
+  <!-- Seika [Note Taker] -->
+  <usb-device vendor-id="4292" product-id="60032" />
+
+  <!-- Device: 1148:0301 -->
+  <!-- BrailleMemo [Smart] -->
+  <usb-device vendor-id="4424" product-id="769" />
+
+  <!-- Device: 1209:ABC0 -->
+  <!-- Inceptor [all models] -->
+  <usb-device vendor-id="4617" product-id="43968" />
+
+  <!-- Device: 16C0:05E1 -->
+  <!-- Canute [all models] -->
+  <usb-device vendor-id="5824" product-id="1505" />
+
+  <!-- Device: 1A86:7523 -->
+  <!-- Generic Identifier -->
+  <!-- Vendor: Jiangsu QinHeng, Ltd. -->
+  <!-- Product: CH341 USB Bridge Controller -->
+  <!-- Baum [NLS eReader Zoomax (20 cells)] -->
+  <usb-device vendor-id="6790" product-id="29987" />
+
+  <!-- Device: 1C71:C004 -->
+  <!-- BrailleNote [HumanWare APEX] -->
+  <usb-device vendor-id="7281" product-id="49156" />
+
+  <!-- Device: 1C71:C005 -->
+  <!-- HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="49157" />
+
+  <!-- Device: 1C71:C006 -->
+  <!-- HumanWare [non-Touch models (HID protocol)] -->
+  <usb-device vendor-id="7281" product-id="49158" />
+
+  <!-- Device: 1C71:C00A -->
+  <!-- HumanWare [BrailleNote Touch (HID protocol)] -->
+  <usb-device vendor-id="7281" product-id="49162" />
+
+  <!-- Device: 1C71:C021 -->
+  <!-- HumanWare [Brailliant BI 14 (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="49185" />
+
+  <!-- Device: 1C71:C101 -->
+  <!-- HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="49409" />
+
+  <!-- Device: 1C71:C104 -->
+  <!-- HumanWare [APH Chameleon 20 (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="49412" />
+
+  <!-- Device: 1C71:C111 -->
+  <!-- HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="49425" />
+
+  <!-- Device: 1C71:C114 -->
+  <!-- HumanWare [APH Mantis Q40 (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="49428" />
+
+  <!-- Device: 1C71:C121 -->
+  <!-- HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="49441" />
+
+  <!-- Device: 1C71:C124 -->
+  <!-- HumanWare [Humanware BrailleOne (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="49444" />
+
+  <!-- Device: 1C71:C131 -->
+  <!-- HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="49457" />
+
+  <!-- Device: 1C71:C141 -->
+  <!-- HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="49473" />
+
+  <!-- Device: 1C71:CE01 -->
+  <!-- HumanWare [NLS eReader (HID protocol, firmware 1.0)] -->
+  <!-- HumanWare [NLS eReader (HID protocol, firmware 1.1)] -->
+  <usb-device vendor-id="7281" product-id="52737" />
+
+  <!-- Device: 1C71:CE04 -->
+  <!-- HumanWare [NLS eReader (serial protocol)] -->
+  <usb-device vendor-id="7281" product-id="52740" />
+
+  <!-- Device: 1FE4:0003 -->
+  <!-- HandyTech [USB-HID adapter] -->
+  <usb-device vendor-id="8164" product-id="3" />
+
+  <!-- Device: 1FE4:0044 -->
+  <!-- HandyTech [Easy Braille (HID)] -->
+  <usb-device vendor-id="8164" product-id="68" />
+
+  <!-- Device: 1FE4:0054 -->
+  <!-- HandyTech [Active Braille] -->
+  <usb-device vendor-id="8164" product-id="84" />
+
+  <!-- Device: 1FE4:0055 -->
+  <!-- HandyTech [Connect Braille 40] -->
+  <usb-device vendor-id="8164" product-id="85" />
+
+  <!-- Device: 1FE4:0061 -->
+  <!-- HandyTech [Actilino] -->
+  <usb-device vendor-id="8164" product-id="97" />
+
+  <!-- Device: 1FE4:0064 -->
+  <!-- HandyTech [Active Star 40] -->
+  <usb-device vendor-id="8164" product-id="100" />
+
+  <!-- Device: 1FE4:0074 -->
+  <!-- HandyTech [Braille Star 40 (HID)] -->
+  <usb-device vendor-id="8164" product-id="116" />
+
+  <!-- Device: 1FE4:0081 -->
+  <!-- HandyTech [Basic Braille 16] -->
+  <usb-device vendor-id="8164" product-id="129" />
+
+  <!-- Device: 1FE4:0082 -->
+  <!-- HandyTech [Basic Braille 20] -->
+  <usb-device vendor-id="8164" product-id="130" />
+
+  <!-- Device: 1FE4:0083 -->
+  <!-- HandyTech [Basic Braille 32] -->
+  <usb-device vendor-id="8164" product-id="131" />
+
+  <!-- Device: 1FE4:0084 -->
+  <!-- HandyTech [Basic Braille 40] -->
+  <usb-device vendor-id="8164" product-id="132" />
+
+  <!-- Device: 1FE4:0086 -->
+  <!-- HandyTech [Basic Braille 64] -->
+  <usb-device vendor-id="8164" product-id="134" />
+
+  <!-- Device: 1FE4:0087 -->
+  <!-- HandyTech [Basic Braille 80] -->
+  <usb-device vendor-id="8164" product-id="135" />
+
+  <!-- Device: 1FE4:008A -->
+  <!-- HandyTech [Basic Braille 48] -->
+  <usb-device vendor-id="8164" product-id="138" />
+
+  <!-- Device: 1FE4:008B -->
+  <!-- HandyTech [Basic Braille 160] -->
+  <usb-device vendor-id="8164" product-id="139" />
+
+  <!-- Device: 1FE4:00A4 -->
+  <!-- HandyTech [Activator] -->
+  <usb-device vendor-id="8164" product-id="164" />
+
+  <!-- Device: 4242:0001 -->
+  <!-- Pegasus [all models] -->
+  <usb-device vendor-id="16962" product-id="1" />
+
+  <!-- Device: C251:1122 -->
+  <!-- EuroBraille [Esys (version < 3.0, no SD card)] -->
+  <usb-device vendor-id="49745" product-id="4386" />
+
+  <!-- Device: C251:1123 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4387" />
+
+  <!-- Device: C251:1124 -->
+  <!-- EuroBraille [Esys (version < 3.0, with SD card)] -->
+  <usb-device vendor-id="49745" product-id="4388" />
+
+  <!-- Device: C251:1125 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4389" />
+
+  <!-- Device: C251:1126 -->
+  <!-- EuroBraille [Esys (version >= 3.0, no SD card)] -->
+  <usb-device vendor-id="49745" product-id="4390" />
+
+  <!-- Device: C251:1127 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4391" />
+
+  <!-- Device: C251:1128 -->
+  <!-- EuroBraille [Esys (version >= 3.0, with SD card)] -->
+  <usb-device vendor-id="49745" product-id="4392" />
+
+  <!-- Device: C251:1129 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4393" />
+
+  <!-- Device: C251:112A -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4394" />
+
+  <!-- Device: C251:112B -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4395" />
+
+  <!-- Device: C251:112C -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4396" />
+
+  <!-- Device: C251:112D -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4397" />
+
+  <!-- Device: C251:112E -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4398" />
+
+  <!-- Device: C251:112F -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4399" />
+
+  <!-- Device: C251:1130 -->
+  <!-- EuroBraille [Esytime (firmware 1.03, 2014-03-31)] -->
+  <!-- EuroBraille [Esytime] -->
+  <usb-device vendor-id="49745" product-id="4400" />
+
+  <!-- Device: C251:1131 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4401" />
+
+  <!-- Device: C251:1132 -->
+  <!-- EuroBraille [reserved] -->
+  <usb-device vendor-id="49745" product-id="4402" />
+
+  <!-- END_USB_BRAILLE_DEVICES -->
+</resources>
diff --git a/Android/Gradle/brlapi-android.gradle b/Android/Gradle/brlapi-android.gradle
new file mode 100644
index 0000000..33c6007
--- /dev/null
+++ b/Android/Gradle/brlapi-android.gradle
@@ -0,0 +1,16 @@
+// This will fail if minSdkVersion is less than 26.
+// Remember to change the dependency version as needed.
+
+repositories {
+  maven {
+    url "https://brltty.app/archive/Maven2/"
+  }
+}
+
+dependencies {
+  implementation(
+    group: "org.a11y.brltty",
+    name: "brlapi-android",
+    version: "0.8.2"
+  )
+}
diff --git a/Android/Gradle/brltty-build.gradle b/Android/Gradle/brltty-build.gradle
new file mode 100644
index 0000000..48404ee
--- /dev/null
+++ b/Android/Gradle/brltty-build.gradle
@@ -0,0 +1,212 @@
+String getJarsPath (Map properties) {
+  return brltty.getJarsPath(properties.project)
+}
+
+String getLibrariesPath (Map properties) {
+  return brltty.getLibrariesPath(properties.project)
+}
+
+String getABIPath (Map properties) {
+  return brltty.getABIPath(properties.project, properties.ABI)
+}
+
+Map getABI (String abi) {
+  if (!brltty.containsKey("ABI")) {
+    brltty.ABI = [:]
+  }
+
+  def ABI = brltty.ABI
+  if (!ABI.containsKey(abi)) ABI[abi] = [:]
+  return ABI[abi]
+}
+
+Map getABI (Map properties) {
+  return getABI(properties.ABI)
+}
+
+Task newTask (Project project, String name, Class<DefaultTask> type=DefaultTask, Closure closure) {
+  Task task = project.tasks.register(name, type, closure).get()
+
+  task.configure {
+    group "build"
+  }
+
+  return task
+}
+
+Task newTask (Map properties, String name, Class<DefaultTask> type=DefaultTask, Closure closure) {
+  name += properties.taskNameSuffix
+  return newTask(properties.project, name, type, closure)
+}
+
+Task newAssembleTask (Map properties) {
+  return newTask(properties.project, "brlttyAssembleAllComponents") {
+    description "Assemble all of the native jars, libraries, assets, etc."
+  }
+}
+
+void addAssembleDependency (Map properties, Task task) {
+  properties.assembleTask.dependsOn task
+}
+
+Task newCleanTask (Map properties) {
+  return newTask(properties.project, "brlttyCleanAllComponents") {
+    description "Clean all of the native jars, libraries, assets, etc."
+  }
+}
+
+void addCleanDependency (Map properties, Task task) {
+  properties.cleanTask.dependsOn task
+}
+
+Task newConfigureTask (Map properties) {
+  return newTask(properties, "brlttyConfigureABI", Exec) {
+    description "Configure the ${properties.componentName} for the ${properties.ABI} ABI."
+
+    workingDir properties.abiDirectory
+    ignoreExitValue false
+    commandLine "${brltty.native.rootDirectory}/cfg-android-abi", properties.ABI
+  }
+}
+
+Task getConfigureTask (Map properties) {
+  def abi = getABI(properties)
+
+  if (!abi.containsKey("configureTask")) {
+    abi.configureTask = newConfigureTask(properties)
+  }
+
+  return abi.configureTask
+}
+
+Task newMakeComponentsTask (Map properties) {
+  return newTask(properties, "brlttyMakeABI", Exec) {
+    description "Make the ${properties.ABI} components of the ${properties.componentName}."
+    dependsOn properties.configureTask
+
+    workingDir properties.makeDirectory
+    ignoreExitValue false
+
+    commandLine "make"
+    args properties.makeTargets
+  }
+}
+
+Task newAddJarsTask (Map properties) {
+  return newTask(properties, "brlttyAddJars", Copy) {
+    description "Add the ${properties.componentName}'s ${properties.ABI} jars to the build."
+    dependsOn properties.makeComponentsTask
+    into getJarsPath(properties)
+
+    properties.jarSubdirectories.each { subdirectory ->
+      from properties.abiDirectory + File.separator + subdirectory
+    }
+
+    properties.jars.each { jar ->
+      include "${jar}.jar"
+    }
+  }
+}
+
+Task newAddLibrariesTask (Map properties) {
+  return newTask(properties, "brlttyAddLibraries", Copy) {
+    description "Add the ${properties.componentName}'s ${properties.ABI} libraries to the build."
+    dependsOn properties.makeComponentsTask
+    into getABIPath(properties)
+
+    properties.librarySubdirectories.each { subdirectory ->
+      from properties.abiDirectory + File.separator + subdirectory
+    }
+
+    properties.libraries.each { library ->
+      include "lib${library}.so"
+    }
+  }
+}
+
+Task newMakeCleanTask (Map properties) {
+  return newTask(properties, "brlttyCleanABI", Exec) {
+    description "Remove (make clean) the ${properties.ABI} components of the ${properties.componentName}."
+    dependsOn properties.configureTask
+
+    workingDir properties.makeDirectory
+    ignoreExitValue true
+    commandLine "make", "clean"
+  }
+}
+
+Task newRemoveTask (Map properties) {
+  return newTask(properties, "brlttyRemoveAllComponents", Delete) {
+    description "Remove the ${properties.componentName}'s jars and libraries from the build."
+    delete getJarsPath(properties)
+    delete getLibrariesPath(properties)
+  }
+}
+
+void addTasks (Map properties, String abi) {
+  properties.ABI = abi
+  def isPrimaryABI = abi.equals(brltty.native.abiList[0])
+
+  properties.abiDirectory = brltty.native.abiDirectory + File.separator + properties.ABI
+  properties.configureTask = getConfigureTask(properties)
+
+  properties.makeDirectory = properties.abiDirectory + File.separator + properties.makeSubdirectory
+  properties.makeComponentsTask = newMakeComponentsTask(properties)
+
+  if (isPrimaryABI) {
+    def jars = properties.jars
+
+    if ((jars != null) && !jars.isEmpty()) {
+      def addJarsTask = newAddJarsTask(properties)
+      addAssembleDependency(properties, addJarsTask)
+    }
+  }
+
+  def libraries = properties.libraries
+  if ((libraries != null) && !libraries.isEmpty()) {
+    def addLibrariesTask = newAddLibrariesTask(properties)
+    addAssembleDependency(properties, addLibrariesTask)
+  }
+
+  def makeCleanTask = newMakeCleanTask(properties)
+  addCleanDependency(properties, makeCleanTask)
+}
+
+void addTasks (Map properties) {
+  def taskNameSuffix = ""
+  properties.taskNameSuffix = taskNameSuffix
+
+  properties.assembleTask = newAssembleTask(properties)
+  properties.cleanTask = newCleanTask(properties)
+
+  if (properties.makeTargets == null) properties.makeTargets = "all"
+  if (properties.makeTargets instanceof String) properties.makeTargets = [properties.makeTargets]
+
+  if (properties.jarSubdirectories == null) properties.jarSubdirectories = properties.makeSubdirectory
+  if (properties.jarSubdirectories instanceof String) properties.jarSubdirectories = [properties.jarSubdirectories]
+
+  if (properties.librarySubdirectories == null) properties.librarySubdirectories = properties.makeSubdirectory
+  if (properties.librarySubdirectories instanceof String) properties.librarySubdirectories = [properties.librarySubdirectories]
+
+  def removeTask = newRemoveTask(properties)
+  addCleanDependency(properties, removeTask)
+
+  brltty.native.abiList.each { abi ->
+    properties.taskNameSuffix = "${taskNameSuffix}_${abi}"
+    addTasks(properties, abi)
+  }
+}
+
+brltty.addAssembleTask = { Project project, Task assembleTask ->
+  project.afterEvaluate {
+    project.tasks.withType(JavaCompile).each { compileTask ->
+      compileTask.dependsOn assembleTask
+    }
+  }
+}
+
+brltty.addTasks = { Map properties ->
+  addTasks(properties)
+  brltty.addAssembleTask(properties.project, properties.assembleTask)
+  clean.dependsOn properties.cleanTask
+}
diff --git a/Android/Gradle/brltty-help.gradle b/Android/Gradle/brltty-help.gradle
new file mode 100644
index 0000000..d90f9a0
--- /dev/null
+++ b/Android/Gradle/brltty-help.gradle
@@ -0,0 +1,43 @@
+void listProperties (Object object, String name, int level=0) {
+  print ("| " * level)
+  level += 1
+  print name
+
+  if (object instanceof Map) {
+    println "[:]"
+    def map = object as Map
+
+    map.keySet().sort().each { key ->
+      listProperties(map[key], key, level)
+    }
+  } else if (object instanceof List) {
+    println "[]"
+    def list = object as List
+    int index = 0
+
+    list.each { element ->
+      listProperties(element, ('[' + index++ + ']'), level)
+    }
+  } else {
+    print ": "
+
+    if (object instanceof Project) {
+      print "object "
+      object = object.name
+    } else if (object instanceof Task) {
+      print "task "
+      object = object.name
+    }
+
+    println object
+  }
+}
+
+tasks.register("brlttyListProperties") {
+  group "help"
+  description "Lists all of brltty's properties."
+
+  doLast {
+    listProperties(rootProject.brltty, "brltty")
+  }
+}
diff --git a/Android/Gradle/brltty-properties.gradle b/Android/Gradle/brltty-properties.gradle
new file mode 100644
index 0000000..c868d3a
--- /dev/null
+++ b/Android/Gradle/brltty-properties.gradle
@@ -0,0 +1,79 @@
+def propertyFiles = [
+  config: "config.properties",
+  native: "native.properties",
+]
+
+void normalizeFileProperty (Properties properties, String name) {
+  properties[name] = rootProject.file(properties[name])
+}
+
+void normalizeDirectoryProperty (Properties properties, String name) {
+  normalizeFileProperty(properties, name)
+  properties[name] = properties[name].absolutePath
+}
+
+void normalizeSubdirectoryProperty (Properties properties, String subdirectoryName) {
+  def directoryName = subdirectoryName.replaceAll(/Subdirectory$/, "Directory")
+  properties[directoryName] = properties.rootDirectory + File.separator + properties[subdirectoryName]
+}
+
+void normalizeListProperty (Properties properties, String name) {
+  properties[name] = properties[name].split(',').collect{it as String}
+}
+
+Properties loadPropertiesFromFile (String path) {
+  def properties = new Properties()
+  def file = rootProject.file(path)
+
+  if (file.exists()) {
+    new FileInputStream(file).withStream { stream ->
+      new InputStreamReader(stream, "UTF-8").withReader { reader ->
+        properties.load(reader)
+      }
+    }
+  }
+
+  def directoryProperties = []
+  def subdirectoryProperties = []
+  def fileProperties = []
+  def listProperties = []
+
+  properties.propertyNames().each { name ->
+    if (name.endsWith("Directory")) {
+      directoryProperties += name
+    } else if (name.endsWith("Subdirectory")) {
+      subdirectoryProperties += name
+    } else if (name.endsWith("File")) {
+      fileProperties += name
+    } else if (name.endsWith("List")) {
+      listProperties += name
+    }
+  }
+
+  directoryProperties.each { name ->
+    normalizeDirectoryProperty(properties, name)
+  }
+
+  subdirectoryProperties.each { name ->
+    normalizeSubdirectoryProperty(properties, name)
+  }
+
+  fileProperties.each { name ->
+    normalizeFileProperty(properties, name)
+  }
+
+  listProperties.each { name ->
+    normalizeListProperty(properties, name)
+  }
+
+  return properties
+}
+
+propertyFiles.keySet().each { name ->
+  def path = propertyFiles[name]
+  brltty[name] = loadPropertiesFromFile(path)
+}
+
+brltty.loadProperties = { String filePath ->
+  loadPropertiesFromFile(filePath)
+}
diff --git a/Android/Gradle/brltty.gradle b/Android/Gradle/brltty.gradle
new file mode 100644
index 0000000..5e3fc79
--- /dev/null
+++ b/Android/Gradle/brltty.gradle
@@ -0,0 +1,29 @@
+ext.brltty = [:]
+
+brltty.getProjectPath = { Project project ->
+  project.projectDir.path
+}
+
+String getProjectPath (Project project, String subpath) {
+  return brltty.getProjectPath(project) + File.separator + subpath
+}
+
+brltty.getAssetsPath = { Project project ->
+  return getProjectPath(project, brltty.native.assetsPath)
+}
+
+brltty.getJarsPath = { Project project ->
+  return getProjectPath(project, brltty.native.jarsPath)
+}
+
+brltty.getLibrariesPath = { Project project ->
+  return getProjectPath(project, brltty.native.librariesPath)
+}
+
+brltty.getABIPath = { Project project, String abi ->
+  brltty.getLibrariesPath(project) + File.separator + abi
+}
+
+apply from: "brltty-properties.gradle"
+apply from: "brltty-build.gradle"
+apply from: "brltty-help.gradle"
diff --git a/Android/Gradle/build.gradle b/Android/Gradle/build.gradle
new file mode 100644
index 0000000..82894a8
--- /dev/null
+++ b/Android/Gradle/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+  repositories {
+    google()
+    mavenCentral()
+    jcenter()
+  }
+
+  dependencies {
+    classpath "com.android.tools.build:gradle:4.1.3"
+  }
+}
+
+plugins {
+  id "base"
+}
+
+description = """
+"${project.name}" is a screen reader for braille users.
+"""
+
+apply from: "brltty.gradle"
+
+subprojects {
+  repositories {
+    google()
+    mavenCentral()
+    jcenter()
+  }
+}
diff --git a/Android/Gradle/common.mk b/Android/Gradle/common.mk
new file mode 100644
index 0000000..ddc8a52
--- /dev/null
+++ b/Android/Gradle/common.mk
@@ -0,0 +1,43 @@
+GRADLE_WRAPPER_COMMAND = ./gradlew --quiet --console plain
+GRADLE_DUMP_COMMAND = aapt dump --values
+GRADLE_SYMLINK_COMMAND = ln -s -f
+
+GRADLE_ROOT_NAME = brltty
+GRADLE_CORE_NAME = core
+GRADLE_APP_NAME = app
+GRADLE_API_NAME = api
+GRADLE_APITEST_NAME = apitest
+
+GRADLE_DEBUG_VARIANT = debug
+GRADLE_RELEASE_VARIANT = release
+
+GRADLE_ALL_ABI = universal
+GRADLE_ARM32_ABI = armeabi-v7a
+GRADLE_ARM64_ABI = arm64-v8a
+GRADLE_X8632_ABI = x86
+GRADLE_X8664_ABI = x86_64
+
+GRADLE_BUILD_DIRECTORY = app/build
+GRADLE_OUTPUT_DIRECTORY = $(GRADLE_BUILD_DIRECTORY)/outputs
+
+GRADLE_PACKAGE_DIRECTORY = $(GRADLE_OUTPUT_DIRECTORY)/apk
+GRADLE_PACKAGE_EXTENSION = apk
+GRADLE_DEBUG_PACKAGE = $(GRADLE_PACKAGE_DIRECTORY)/$(GRADLE_DEBUG_VARIANT)/$(GRADLE_APP_NAME)-$(GRADLE_ALL_ABI)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_PACKAGE_EXTENSION)
+GRADLE_RELEASE_PACKAGE = $(GRADLE_PACKAGE_DIRECTORY)/$(GRADLE_RELEASE_VARIANT)/$(GRADLE_APP_NAME)-$(GRADLE_ALL_ABI)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_PACKAGE_EXTENSION)
+
+GRADLE_BUNDLE_DIRECTORY = $(GRADLE_OUTPUT_DIRECTORY)/bundle
+GRADLE_BUNDLE_EXTENSION = aab
+GRADLE_DEBUG_BUNDLE = $(GRADLE_BUNDLE_DIRECTORY)/$(GRADLE_DEBUG_VARIANT)/$(GRADLE_APP_NAME)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_BUNDLE_EXTENSION)
+GRADLE_RELEASE_BUNDLE = $(GRADLE_BUNDLE_DIRECTORY)/$(GRADLE_RELEASE_VARIANT)/$(GRADLE_APP_NAME)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_BUNDLE_EXTENSION)
+
+GRADLE_REPORT_DIRECTORY = $(GRADLE_BUILD_DIRECTORY)/reports
+GRADLE_REPORT_NAME = lint
+GRADLE_REPORT_TYPE = results
+GRADLE_REPORT_EXTENSION = html
+GRADLE_DEBUG_REPORT = $(GRADLE_REPORT_DIRECTORY)/$(GRADLE_REPORT_NAME)-$(GRADLE_REPORT_TYPE)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_REPORT_EXTENSION)
+GRADLE_RELEASE_REPORT = $(GRADLE_REPORT_DIRECTORY)/$(GRADLE_REPORT_NAME)-$(GRADLE_REPORT_TYPE)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_REPORT_EXTENSION)
+
+GRADLE_DUMP_EXTENSION = dump
+GRADLE_DUMP_NAME = $(@:-dump=)
+GRADLE_DUMP_FILE = $(GRADLE_ROOT_NAME)-$(GRADLE_DUMP_NAME).$(GRADLE_DUMP_EXTENSION)
+
diff --git a/Android/Gradle/config.properties.in b/Android/Gradle/config.properties.in
new file mode 100644
index 0000000..5c17bf7
--- /dev/null
+++ b/Android/Gradle/config.properties.in
@@ -0,0 +1,7 @@
+copyrightNotice=@PACKAGE_COPYRIGHT@
+webSite=@PACKAGE_URL@
+emailAddress=@PACKAGE_BUGREPORT@
+
+apiVersion=@api_version@
+apiRelease=@api_release@
+
diff --git a/Android/Gradle/core/brltty.gradle b/Android/Gradle/core/brltty.gradle
new file mode 100644
index 0000000..bcec98b
--- /dev/null
+++ b/Android/Gradle/core/brltty.gradle
@@ -0,0 +1,29 @@
+brltty.addTasks([
+  project: project,
+  componentName: "core",
+  makeSubdirectory: brltty.native.coreSubdirectory,
+
+  makeTargets: [
+    brltty.native.coreLibrary,
+    brltty.native.coreWrapper,
+    brltty.native.coreBindings,
+
+    "braille-drivers",
+    "speech-drivers",
+    "screen-drivers",
+  ],
+
+  libraries: [
+    "brltty_core",
+    "brltty_jni",
+
+    "brlttyb??",
+    "brlttys??",
+    "brlttyx??"
+  ],
+
+  librarySubdirectories: [
+    brltty.native.coreSubdirectory,
+    brltty.native.driversSubdirectory
+  ],
+])
diff --git a/Android/Gradle/core/build.gradle b/Android/Gradle/core/build.gradle
new file mode 100644
index 0000000..e1dcd0a
--- /dev/null
+++ b/Android/Gradle/core/build.gradle
@@ -0,0 +1,19 @@
+plugins {
+  id "com.android.library"
+}
+
+description = """
+"${project.name}" is a Java wrapper for the native "${rootProject.name}" code.
+"""
+
+apply from: "brltty.gradle"
+
+android {
+  compileSdkVersion "android-31"
+  buildToolsVersion "29.0.3"
+
+  defaultConfig {
+    targetSdkVersion 30
+    minSdkVersion 16
+  }
+}
diff --git a/Android/Gradle/core/src/main/AndroidManifest.xml b/Android/Gradle/core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d0e924d
--- /dev/null
+++ b/Android/Gradle/core/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="org.a11y.brltty.core"
+  >
+
+</manifest>
diff --git a/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ArgumentsBuilder.java b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ArgumentsBuilder.java
new file mode 100644
index 0000000..861d3d2
--- /dev/null
+++ b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ArgumentsBuilder.java
@@ -0,0 +1,339 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.core;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/* options not yet supported:
+ * -E           --environment-variables
+ * -A arg,...   --api-parameters=
+ * -N           --no-api
+ * -B arg,...   --braille-parameters=
+ * -r           --release-device
+ * -K arg,...   --keyboard-properties=
+ * -s driver    --speech-driver=
+ * -S arg,...   --speech-parameters=
+ * -i file      --speech-input=
+ * -x driver    --screen-driver=
+ * -X arg,...   --screen-parameters=
+ * -p device    --pcm-device=
+ * -m device    --midi-device=
+ * -U csecs     --update-interval=
+ * -M csecs     --message-delay=
+ * -e           --standard-error
+ * -v           --verify
+ */
+
+public class ArgumentsBuilder {
+  private boolean foregroundExecution = false;
+  private boolean releaseDevice = false;
+  private String logLevel = null;
+  private String logFile = null;
+  private String configurationFile = null;
+  private String preferencesFile = null;
+  private String localeDirectory = null;
+  private String driversDirectory = null;
+  private String writableDirectory = null;
+  private String updatableDirectory = null;
+  private String tablesDirectory = null;
+  private String textTable = null;
+  private String attributesTable = null;
+  private String contractionTable = null;
+  private String keyboardTable = null;
+  private String brailleDriver = null;
+  private String brailleDevice = null;
+  private String speechDriver = null;
+  private boolean quietIfNoBraille = false;
+  private boolean apiEnabled = false;
+  private String apiParameters = null;
+
+  public final boolean getForegroundExecution () {
+    return  foregroundExecution;
+  }
+
+  public final ArgumentsBuilder setForegroundExecution (boolean value) {
+    foregroundExecution = value;
+    return this;
+  }
+
+  public final boolean getReleaseDevice () {
+    return  releaseDevice;
+  }
+
+  public final ArgumentsBuilder setReleaseDevice (boolean value) {
+    releaseDevice = value;
+    return this;
+  }
+
+  public final String getLogLevel () {
+    return  logLevel;
+  }
+
+  public final ArgumentsBuilder setLogLevel (String value) {
+    logLevel = value;
+    return this;
+  }
+
+  public final String getLogFile () {
+    return  logFile;
+  }
+
+  public final ArgumentsBuilder setLogFile (String value) {
+    logFile = value;
+    return this;
+  }
+
+  public final String getConfigurationFile () {
+    return  configurationFile;
+  }
+
+  public final ArgumentsBuilder setConfigurationFile (String value) {
+    configurationFile = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setConfigurationFile (File value) {
+    return setConfigurationFile(value.getAbsolutePath());
+  }
+
+  public final String getPreferencesFile () {
+    return  preferencesFile;
+  }
+
+  public final ArgumentsBuilder setPreferencesFile (String value) {
+    preferencesFile = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setPreferencesFile (File value) {
+    return setPreferencesFile(value.getAbsolutePath());
+  }
+
+  public final String getLocaleDirectory () {
+    return  localeDirectory;
+  }
+
+  public final ArgumentsBuilder setLocaleDirectory (String value) {
+    localeDirectory = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setLocaleDirectory (File value) {
+    return setLocaleDirectory(value.getAbsolutePath());
+  }
+
+  public final String getDriversDirectory () {
+    return  driversDirectory;
+  }
+
+  public final ArgumentsBuilder setDriversDirectory (String value) {
+    driversDirectory = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setDriversDirectory (File value) {
+    return setDriversDirectory(value.getAbsolutePath());
+  }
+
+  public final String getWritableDirectory () {
+    return  writableDirectory;
+  }
+
+  public final ArgumentsBuilder setWritableDirectory (String value) {
+    writableDirectory = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setWritableDirectory (File value) {
+    return setWritableDirectory(value.getAbsolutePath());
+  }
+
+  public final String getUpdatableDirectory () {
+    return  updatableDirectory;
+  }
+
+  public final ArgumentsBuilder setUpdatableDirectory (String value) {
+    updatableDirectory = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setUpdatableDirectory (File value) {
+    return setUpdatableDirectory(value.getAbsolutePath());
+  }
+
+  public final String getTablesDirectory () {
+    return  tablesDirectory;
+  }
+
+  public final ArgumentsBuilder setTablesDirectory (String value) {
+    tablesDirectory = value;
+    return this;
+  }
+
+  public final ArgumentsBuilder setTablesDirectory (File value) {
+    return setTablesDirectory(value.getAbsolutePath());
+  }
+
+  public final String getTextTable () {
+    return  textTable;
+  }
+
+  public final ArgumentsBuilder setTextTable (String value) {
+    textTable = value;
+    return this;
+  }
+
+  public final String getAttributesTable () {
+    return  attributesTable;
+  }
+
+  public final ArgumentsBuilder setAttributesTable (String value) {
+    attributesTable = value;
+    return this;
+  }
+
+  public final String getContractionTable () {
+    return  contractionTable;
+  }
+
+  public final ArgumentsBuilder setContractionTable (String value) {
+    contractionTable = value;
+    return this;
+  }
+
+  public final String getKeyboardTable () {
+    return  keyboardTable;
+  }
+
+  public final ArgumentsBuilder setKeyboardTable (String value) {
+    keyboardTable = value;
+    return this;
+  }
+
+  public final String getBrailleDriver () {
+    return  brailleDriver;
+  }
+
+  public final ArgumentsBuilder setBrailleDriver (String value) {
+    brailleDriver = value;
+    return this;
+  }
+
+  public final String getBrailleDevice () {
+    return  brailleDevice;
+  }
+
+  public final ArgumentsBuilder setBrailleDevice (String value) {
+    brailleDevice = value;
+    return this;
+  }
+
+  public final String getSpeechDriver () {
+    return  speechDriver;
+  }
+
+  public final ArgumentsBuilder setSpeechDriver (String value) {
+    speechDriver = value;
+    return this;
+  }
+
+  public final boolean getQuietIfNoBraille () {
+    return  quietIfNoBraille;
+  }
+
+  public final ArgumentsBuilder setQuietIfNoBraille (boolean value) {
+    quietIfNoBraille = value;
+    return this;
+  }
+
+  public final boolean getApiEnabled () {
+    return  apiEnabled;
+  }
+
+  public final ArgumentsBuilder setApiEnabled (boolean value) {
+    apiEnabled = value;
+    return this;
+  }
+
+  public final String getApiParameters () {
+    return apiParameters;
+  }
+
+  public final ArgumentsBuilder setApiParameters (String value) {
+    apiParameters = value;
+    return this;
+  }
+
+  protected void addOption (List<String> arguments, String option, String value) {
+    if (value != null) {
+      if (value.length() > 0) {
+        arguments.add(option);
+        arguments.add(value);
+      }
+    }
+  }
+
+  protected void addOption (List<String> arguments, String option, Enum value) {
+    if (value != null) {
+      addOption(arguments, option, value.name());
+    }
+  }
+
+  protected void addOption (List<String> arguments, String option, boolean value) {
+    if (value) {
+      arguments.add(option);
+    }
+  }
+
+  public String[] getArguments () {
+    List<String> arguments = new ArrayList<String>();
+    addOption(arguments, "-n", foregroundExecution);
+
+    addOption(arguments, "-l", logLevel);
+    addOption(arguments, "-L", logFile);
+
+    addOption(arguments, "-f", configurationFile);
+    addOption(arguments, "-F", preferencesFile);
+
+    addOption(arguments, "--locale-directory", localeDirectory);
+    addOption(arguments, "-D", driversDirectory);
+    addOption(arguments, "-W", writableDirectory);
+    addOption(arguments, "-U", updatableDirectory);
+
+    addOption(arguments, "-T", tablesDirectory);
+    addOption(arguments, "-t", textTable);
+    addOption(arguments, "-a", attributesTable);
+    addOption(arguments, "-c", contractionTable);
+    addOption(arguments, "-k", keyboardTable);
+
+    addOption(arguments, "-b", brailleDriver);
+    addOption(arguments, "-d", brailleDevice);
+    addOption(arguments, "-r", releaseDevice);
+
+    addOption(arguments, "-s", speechDriver);
+    addOption(arguments, "-Q", quietIfNoBraille);
+
+    addOption(arguments, "-N", !apiEnabled);
+    addOption(arguments, "-A", apiParameters);
+
+    return arguments.toArray(new String[arguments.size()]);
+  }
+}
diff --git a/Android/Gradle/core/src/main/java/org/a11y/brltty/core/Braille.java b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/Braille.java
new file mode 100644
index 0000000..a66e162
--- /dev/null
+++ b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/Braille.java
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.core;
+
+public abstract class Braille {
+  private Braille () {
+  }
+
+  public final static char ROW  = 0x2800;
+  public final static char DOT1 = 0x0001;
+  public final static char DOT2 = 0x0002;
+  public final static char DOT3 = 0x0004;
+  public final static char DOT4 = 0x0008;
+  public final static char DOT5 = 0x0010;
+  public final static char DOT6 = 0x0020;
+  public final static char DOT7 = 0x0040;
+  public final static char DOT8 = 0x0080;
+
+  public final static char DOTS_LEFT  = DOT1 | DOT2 | DOT3 | DOT7;
+  public final static char DOTS_RIGHT = DOT4 | DOT5 | DOT6 | DOT8;
+  public final static char DOTS_ALL   = DOTS_LEFT | DOTS_RIGHT;
+
+  public final static char DOTS_UPPER  = DOT1 | DOT4;
+  public final static char DOTS_MIDDLE = DOT2 | DOT5;
+  public final static char DOTS_LOWER  = DOT3 | DOT6;
+  public final static char DOTS_BELOW  = DOT7 | DOT8;
+
+  public final static char DOTS_LITERARY = DOTS_UPPER | DOTS_MIDDLE | DOTS_LOWER;
+  public final static char DOTS_COMPUTER = DOTS_LITERARY | DOTS_BELOW;
+
+  public final static char DOTS_A = DOT1;
+  public final static char DOTS_B = DOT1 | DOT2;
+  public final static char DOTS_C = DOT1 | DOT4;
+  public final static char DOTS_D = DOT1 | DOT4 | DOT5;
+  public final static char DOTS_E = DOT1 | DOT5;
+  public final static char DOTS_F = DOT1 | DOT2 | DOT4;
+  public final static char DOTS_G = DOT1 | DOT2 | DOT4 | DOT5;
+  public final static char DOTS_H = DOT1 | DOT2 | DOT5;
+  public final static char DOTS_I = DOT2 | DOT4;
+  public final static char DOTS_J = DOT2 | DOT4 | DOT5;
+  public final static char DOTS_K = DOT1 | DOT3;
+  public final static char DOTS_L = DOT1 | DOT2 | DOT3;
+  public final static char DOTS_M = DOT1 | DOT3 | DOT4;
+  public final static char DOTS_N = DOT1 | DOT3 | DOT4 | DOT5;
+  public final static char DOTS_O = DOT1 | DOT3 | DOT5;
+  public final static char DOTS_P = DOT1 | DOT2 | DOT3 | DOT4;
+  public final static char DOTS_Q = DOT1 | DOT2 | DOT3 | DOT4 | DOT5;
+  public final static char DOTS_R = DOT1 | DOT2 | DOT3 | DOT5;
+  public final static char DOTS_S = DOT2 | DOT3 | DOT4;
+  public final static char DOTS_T = DOT2 | DOT3 | DOT4 | DOT5;
+  public final static char DOTS_U = DOT1 | DOT3 | DOT6;
+  public final static char DOTS_V = DOT1 | DOT2 | DOT3 | DOT6;
+  public final static char DOTS_W = DOT2 | DOT4 | DOT5 | DOT6;
+  public final static char DOTS_X = DOT1 | DOT3 | DOT4 | DOT6;
+  public final static char DOTS_Y = DOT1 | DOT3 | DOT4 | DOT5 | DOT6;
+  public final static char DOTS_Z = DOT1 | DOT3 | DOT5 | DOT6;
+
+  public final static char DOTS_0 = DOT3 | DOT5 | DOT6;
+  public final static char DOTS_1 = DOT2;
+  public final static char DOTS_2 = DOT2 | DOT3;
+  public final static char DOTS_3 = DOT2 | DOT5;
+  public final static char DOTS_4 = DOT2 | DOT5 | DOT6;
+  public final static char DOTS_5 = DOT2 | DOT6;
+  public final static char DOTS_6 = DOT2 | DOT3 | DOT5;
+  public final static char DOTS_7 = DOT2 | DOT3 | DOT5 | DOT6;
+  public final static char DOTS_8 = DOT2 | DOT3 | DOT6;
+  public final static char DOTS_9 = DOT3 | DOT5;
+}
diff --git a/Android/Gradle/core/src/main/java/org/a11y/brltty/core/CoreWrapper.java b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/CoreWrapper.java
new file mode 100644
index 0000000..a732e9e
--- /dev/null
+++ b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/CoreWrapper.java
@@ -0,0 +1,138 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.core;
+
+import java.util.AbstractQueue;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingDeque;
+
+public abstract class CoreWrapper {
+  private CoreWrapper () {
+  }
+
+  static {
+    System.loadLibrary("brltty_core");
+    System.loadLibrary("brltty_jni");
+  }
+
+  public native static int coreConstruct (String[] arguments, ClassLoader classLoader);
+  public native static boolean coreDestruct ();
+
+  public native static boolean coreEnableInterrupt ();
+  public native static boolean coreDisableInterrupt ();
+
+  public native static boolean coreInterrupt (boolean stop);
+  public native static boolean coreWait (int duration);
+
+  public native static boolean changeLogLevel (String level);
+  public native static boolean changeLogCategories (String categories);
+
+  public native static boolean changeTextTable (String name);
+  public native static boolean changeAttributesTable (String name);
+  public native static boolean changeContractionTable (String name);
+  public native static boolean changeKeyboardTable (String name);
+
+  public native static boolean restartBrailleDriver ();
+  public native static boolean changeBrailleDriver (String driver);
+  public native static boolean changeBrailleParameters (String parameters);
+  public native static boolean changeBrailleDevice (String device);
+
+  public native static boolean restartSpeechDriver ();
+  public native static boolean changeSpeechDriver (String driver);
+  public native static boolean changeSpeechParameters (String parameters);
+
+  public native static boolean restartScreenDriver ();
+  public native static boolean changeScreenDriver (String driver);
+  public native static boolean changeScreenParameters (String parameters);
+
+  public native static boolean changeMessageLocale (String locale);
+  public native static void showMessage (String text);
+
+  public native static boolean setEnvironmentVariable (String name, String value);
+
+  public static boolean changeLogCategories (Set<String> categories) {
+    StringBuilder sb = new StringBuilder();
+
+    for (String category : categories) {
+      if (sb.length() > 0) sb.append(',');
+      sb.append(category);
+    }
+
+    return changeLogCategories(sb.toString());
+  }
+
+  private static Thread coreThread = null;
+  private final static AbstractQueue<Runnable> runQueue = new LinkedBlockingDeque<Runnable>();
+
+  public static void clearRunQueue () {
+    runQueue.clear();
+  }
+
+  public static void processRunQueue () {
+    Runnable runnable;
+    while ((runnable = runQueue.poll()) != null) runnable.run();
+  }
+
+  public static boolean runOnCoreThread (Runnable runnable) {
+    if (Thread.currentThread() == coreThread) {
+      runnable.run();
+    } else {
+      if (!runQueue.offer(runnable)) return false;
+      coreInterrupt(false);
+    }
+
+    return true;
+  }
+
+  public static void stop () {
+    coreInterrupt(true);
+  }
+
+  public static int run (String[] arguments, int waitDuration) {
+    clearRunQueue();
+    coreThread = Thread.currentThread();
+
+    try {
+      int exitStatus = coreConstruct(arguments, CoreWrapper.class.getClassLoader());
+
+      if (exitStatus == ProgramExitStatus.SUCCESS.value) {
+        boolean interruptEnabled = coreEnableInterrupt();
+
+        while (coreWait(waitDuration)) {
+          processRunQueue();
+        }
+
+        if (interruptEnabled) {
+          coreDisableInterrupt();
+        }
+      } else if (exitStatus == ProgramExitStatus.FORCE.value) {
+        exitStatus = ProgramExitStatus.SUCCESS.value;
+      }
+
+      coreDestruct();
+      return exitStatus;
+    } finally {
+      coreThread = null;
+    }
+  }
+
+  public static void main (String[] arguments) {
+    System.exit(run(arguments, Integer.MAX_VALUE));
+  }
+}
diff --git a/Android/Gradle/core/src/main/java/org/a11y/brltty/core/LogLevel.java b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/LogLevel.java
new file mode 100644
index 0000000..c02cb0a
--- /dev/null
+++ b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/LogLevel.java
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.core;
+
+public enum LogLevel {
+  EMERGENCY   (0),
+  ALERT       (1),
+  CRITICAL    (2),
+  ERROR       (3),
+  WARNING     (4),
+  NOTICE      (5),
+  INFORMATION (6),
+  DEBUG       (7);
+
+  public final int value;
+
+  LogLevel (int value) {
+    this.value = value;
+  }
+}
diff --git a/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ProgramExitStatus.java b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ProgramExitStatus.java
new file mode 100644
index 0000000..e2df28d
--- /dev/null
+++ b/Android/Gradle/core/src/main/java/org/a11y/brltty/core/ProgramExitStatus.java
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brltty.core;
+
+public enum ProgramExitStatus {
+  SUCCESS  (0),
+  FORCE    (1),
+  SYNTAX   (2),
+  SEMANTIC (3),
+  FATAL    (4);
+
+  public final int value;
+
+  ProgramExitStatus (int value) {
+    this.value = value;
+  }
+}
diff --git a/Android/Gradle/core/src/main/res/values/strings.xml b/Android/Gradle/core/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1a0eb54
--- /dev/null
+++ b/Android/Gradle/core/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+</resources>
diff --git a/Android/Gradle/gradle.properties b/Android/Gradle/gradle.properties
new file mode 100644
index 0000000..8c66e7b
--- /dev/null
+++ b/Android/Gradle/gradle.properties
@@ -0,0 +1,24 @@
+# Project-wide Gradle settings.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+
diff --git a/Android/Gradle/gradle/wrapper/gradle-wrapper.properties b/Android/Gradle/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f5293ee
--- /dev/null
+++ b/Android/Gradle/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
diff --git a/Android/Gradle/gradlew b/Android/Gradle/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/Android/Gradle/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/Android/Gradle/gradlew.bat b/Android/Gradle/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/Android/Gradle/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Android/Gradle/native.properties b/Android/Gradle/native.properties
new file mode 100644
index 0000000..8b115b8
--- /dev/null
+++ b/Android/Gradle/native.properties
@@ -0,0 +1,21 @@
+abiList=armeabi-v7a,arm64-v8a,x86,x86_64
+rootDirectory=../..
+
+abiSubdirectory=Android/ABI
+driversSubdirectory=lib
+messagesSubdirectory=Messages
+tablesSubdirectory=Tables
+
+coreSubdirectory=Programs
+coreLibrary=libbrltty_core.so
+coreWrapper=libbrltty_jni.so
+coreBindings=libbrlapi.so
+
+javaSubdirectory=Bindings/Java
+javaBindings=libbrlapi_java.so
+javaClasses=brlapi_jar
+
+assetsPath=src/main/assets
+jarsPath=src/main/libs
+librariesPath=src/main/jniLibs
+
diff --git a/Android/Gradle/publish.mk b/Android/Gradle/publish.mk
new file mode 100644
index 0000000..8c7cd17
--- /dev/null
+++ b/Android/Gradle/publish.mk
@@ -0,0 +1,29 @@
+GRADLE_PUBLISH_COMMAND = $(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):publishReleaseBundle
+GRADLE_PROMOTE_COMMAND = $(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):promoteArtifact
+
+GRADLE_LISTING_DIRECTORY = app/src/main/play
+
+publish-alpha:
+	$(GRADLE_PUBLISH_COMMAND) --track alpha --release-status inProgress
+
+publish-beta:
+	$(GRADLE_PUBLISH_COMMAND) --track beta --release-status inProgress
+
+publish-production:
+	$(GRADLE_PUBLISH_COMMAND) --track production --release-status completed
+
+promote-to-beta:
+	$(GRADLE_PROMOTE_COMMAND) --from-track alpha --promote-track beta --release-status inProgress
+
+promote-to-production:
+	$(GRADLE_PROMOTE_COMMAND) --from-track beta --promote-track production --release-status completed
+
+download-listing:
+	$(GRADLE_WRAPPER_COMMAND) bootstrap
+
+publish-listing:
+	$(GRADLE_WRAPPER_COMMAND) publishListing
+
+clean-listing:
+	-rm -f -r $(GRADLE_LISTING_DIRECTORY)
+
diff --git a/Android/Gradle/settings.gradle b/Android/Gradle/settings.gradle
new file mode 100644
index 0000000..55e977b
--- /dev/null
+++ b/Android/Gradle/settings.gradle
@@ -0,0 +1,6 @@
+rootProject.name = "brltty"
+
+include ":core"
+include ":app"
+include "api"
+include "apitest"
diff --git a/Android/Gradle/targets.mk b/Android/Gradle/targets.mk
new file mode 100644
index 0000000..9c20dcf
--- /dev/null
+++ b/Android/Gradle/targets.mk
@@ -0,0 +1,97 @@
+core-debug:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_CORE_NAME):assembleDebug
+
+core-release:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_CORE_NAME):assembleRelease
+
+api-debug:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_API_NAME):assembleDebug
+
+api-release:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_API_NAME):assembleRelease
+
+api-publish:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_API_NAME):publishReleasePublicationToMavenLocal
+
+apitest-debug:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APITEST_NAME):assembleDebug
+
+apitest-release:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APITEST_NAME):assembleRelease
+
+app-debug:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):assembleDebug
+
+app-release:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):assembleRelease
+
+bundle-debug:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):bundleDebug
+
+bundle-release:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):bundleRelease
+
+lint-debug:
+	$(GRADLE_WRAPPER_COMMAND) lintDebug
+
+lint-release:
+	$(GRADLE_WRAPPER_COMMAND) lintRelease
+
+clean-build:
+	$(GRADLE_WRAPPER_COMMAND) clean
+
+install-debug: app-debug
+	adb install -r -d $(GRADLE_DEBUG_PACKAGE)
+
+install-release: app-release
+	adb install -r $(GRADLE_RELEASE_PACKAGE)
+
+assets: messages tables
+
+messages:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):brlttyAddMessages
+
+tables:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):brlttyAddTables
+
+remove-assets: remove-messages remove-tables
+
+remove-messages:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):brlttyRemoveMessages
+
+remove-tables:
+	$(GRADLE_WRAPPER_COMMAND) :$(GRADLE_APP_NAME):brlttyRemoveTables
+
+tasks-dump:
+	$(GRADLE_WRAPPER_COMMAND) $(GRADLE_DUMP_NAME) tasks --all >$(GRADLE_DUMP_FILE)
+
+badging-dump configurations-dump permissions-dump resources-dump strings-dump xmlstrings-dump xmltree-dump: app-debug
+	$(GRADLE_DUMP_COMMAND) $(GRADLE_DUMP_NAME) $(GRADLE_DEBUG_PACKAGE) >$(GRADLE_DUMP_FILE)
+
+symlinks: package-symlinks bundle-symlinks lint-symlinks
+
+package-symlinks:
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_DEBUG_PACKAGE) $(GRADLE_ROOT_NAME)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_PACKAGE_EXTENSION)
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_RELEASE_PACKAGE) $(GRADLE_ROOT_NAME)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_PACKAGE_EXTENSION)
+
+bundle-symlinks:
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_DEBUG_BUNDLE) $(GRADLE_ROOT_NAME)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_BUNDLE_EXTENSION)
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_RELEASE_BUNDLE) $(GRADLE_ROOT_NAME)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_BUNDLE_EXTENSION)
+
+lint-symlinks:
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_DEBUG_REPORT) $(GRADLE_REPORT_NAME)-$(GRADLE_DEBUG_VARIANT).$(GRADLE_REPORT_EXTENSION)
+	$(GRADLE_SYMLINK_COMMAND) $(GRADLE_RELEASE_REPORT) $(GRADLE_REPORT_NAME)-$(GRADLE_RELEASE_VARIANT).$(GRADLE_REPORT_EXTENSION)
+
+get-screen-log:
+	adb pull /sdcard/Android/data/org.a11y.brltty.android/files/screen.log
+
+clean::
+	-rm -f $(GRADLE_ROOT_NAME)-*.$(GRADLE_PACKAGE_EXTENSION)
+	-rm -f $(GRADLE_ROOT_NAME)-*.$(GRADLE_BUNDLE_EXTENSION)
+	-rm -f $(GRADLE_ROOT_NAME)-*.$(GRADLE_REPORT_EXTENSION)
+	-rm -f $(GRADLE_ROOT_NAME)-*.$(GRADLE_DUMP_EXTENSION)
+
+distclean::
+	-rm -f config.properties
+	-rm -f -r ../ABI
+
diff --git a/Android/Notes/ISSUES b/Android/Notes/ISSUES
new file mode 100644
index 0000000..90bc25c
--- /dev/null
+++ b/Android/Notes/ISSUES
@@ -0,0 +1,3 @@
+check action performability better (e.g. setting/clearing input/access focus)
+dispmd crashes when navigating off the top
+icon for input service
diff --git a/Android/Notes/PENDING b/Android/Notes/PENDING
new file mode 100644
index 0000000..3f6da31
--- /dev/null
+++ b/Android/Notes/PENDING
@@ -0,0 +1,3 @@
+lint deprecated api
+lint unsafe or unchecked operations - InputHandlers
+strip release libraries
diff --git a/Android/Notes/TODO b/Android/Notes/TODO
new file mode 100644
index 0000000..9262eaf
--- /dev/null
+++ b/Android/Notes/TODO
@@ -0,0 +1,22 @@
+accessibility focus built into talkback
+explore by touch built into talkback
+typing on braille keyboard and touch screen simultaneously
+typing PIN on lock screen
+
+automatic scroll at the top/bottom of a page
+switch amongst selected text/contraction tables
+monitor multiple braille devices
+
+messages enqueue while device isn't connected
+removing a device needs its own selector
+contracted braille doesn't count routing keys correctly
+
+show that text is editable
+a way to click on the background
+a way to drag and drop
+contracted braille and routing key associations
+support the dynamic loading of non-built-in drivers
+support serial braille devices
+actions on elements with short text (grid view)
+selecting multiple devices
+switching between preferred text/contraction tables
diff --git a/Android/Notes/braille_service.png b/Android/Notes/braille_service.png
new file mode 100644
index 0000000..f809bfa
--- /dev/null
+++ b/Android/Notes/braille_service.png
Binary files differ
diff --git a/Android/Notes/chrome-roles b/Android/Notes/chrome-roles
new file mode 100644
index 0000000..0ae59c5
--- /dev/null
+++ b/Android/Notes/chrome-roles
@@ -0,0 +1,12 @@
+descriptionList
+descriptionListDetail
+descriptionListTerm
+genericContainer
+heading
+link
+list
+listItem
+listMarker
+paragraph
+splitter
+staticText
diff --git a/Android/STRINGS b/Android/STRINGS
new file mode 100644
index 0000000..c2bb4b4
--- /dev/null
+++ b/Android/STRINGS
@@ -0,0 +1,284 @@
+app_description BRLTTY supports braille devices.
+
+usbMonitor_label USB Device Attached Monitor
+
+about_label_activity About Application
+about_label_copyright Copyright
+about_label_buildTime Build Time
+about_label_sourceRevision Source Revision
+about_label_privacyPolicy Privacy Policy
+
+inputService_name BRLTTY Input Service
+inputService_type Keyboards on Braille Devices
+inputService_not_enabled input service not enabled
+inputService_not_selected input service not selected
+inputService_not_started input service not started
+inputService_not_connected input service not connected
+
+updateApplication_problem_failed application not updated
+
+packageInstaller_problem_failed package not installed
+packageInstaller_problem_same same version
+packageInstaller_problem_downgrade downgrade not allowed
+
+fileDownloader_title BRLTTY File Downloader
+fileDownloader_problem_failed file not downloaded
+fileDownloader_state_connecting connecting
+fileDownloader_state_downloading downloading
+
+setting_state_off Off
+checkbox_state_unchecked No
+checkbox_state_checked Yes
+
+braille_channel_name Braille Device State
+braille_hint_tap tap for actions
+braille_state_released Released
+braille_state_waiting Waiting
+braille_state_connected Connected
+
+GLOBAL_BUTTON_NOTIFICATIONS Notifications
+GLOBAL_BUTTON_QUICK_SETTINGS Quick Settings
+GLOBAL_BUTTON_BACK Back
+GLOBAL_BUTTON_HOME Home
+GLOBAL_BUTTON_RECENT_APPS Recent Apps
+GLOBAL_BUTTON_OVERVIEW Overview
+
+GLOBAL_BUTTON_SWITCH_INPUT_METHOD Switch Input Method
+GLOBAL_BUTTON_VIEW_USER_GUIDE View User Guide
+GLOBAL_BUTTON_BROWSE_WEB_SITE Browse Web Site
+GLOBAL_BUTTON_BROWSE_COMMUNITY_MESSAGES Browse Community Messages
+GLOBAL_BUTTON_POST_COMMUNITY_MESSAGE Post Community Message
+GLOBAL_BUTTON_MANAGE_COMMUNITY_MEMBERSHIP Manage Community Membership
+
+GLOBAL_BUTTON_UPDATE_APPLICATION Update Application
+GLOBAL_CHECKBOX_DEVELOPER_BUILD Developer Build
+GLOBAL_CHECKBOX_ALLOW_DOWNGRADE Allow Downgrade
+
+CHOOSER_TITLE_ACCESSIBILITY_ACTIONS Accessibility Actions
+
+SETTINGS_SCREEN_MAIN BRLTTY Settings
+SETTINGS_SCREEN_GENERAL General Settings
+SETTINGS_SCREEN_MESSAGE Message Settings
+SETTINGS_SCREEN_DEVICES Manage Devices
+SETTINGS_SCREEN_ADVANCED Advanced Settings
+
+SETTINGS_CHECKBOX_RELEASE_BRAILLE_DEVICE Release Braille Device
+SETTINGS_BUTTON_NAVIGATION_MODE Navigation Mode
+SETTINGS_BUTTON_SPEECH_SUPPORT Speech Support
+
+SETTINGS_BUTTON_TEXT_TABLE Text Table
+SETTINGS_BUTTON_ATTRIBUTES_TABLE Attributes Table
+SETTINGS_BUTTON_CONTRACTION_TABLE Contraction Table
+SETTINGS_BUTTON_KEYBOARD_TABLE Keyboard Table
+
+SETTINGS_CHECKBOX_SHOW_ALERTS Show Alerts
+SETTINGS_CHECKBOX_SHOW_ANNOUNCEMENTS Show Announcements
+SETTINGS_CHECKBOX_SHOW_NOTIFICATIONS Show Notifications
+
+SETTINGS_BUTTON_LOG_LEVEL Log Level
+SETTINGS_BUTTON_LOG_CATEGORIES Log Categories
+SETTINGS_CHECKBOX_LOG_ACCESSIBILITY_EVENTS Log Accessibility Events
+SETTINGS_CHECKBOX_LOG_RENDERED_SCREEN Log Rendered Screen
+SETTINGS_CHECKBOX_LOG_KEYBOARD_EVENTS Log Keyboard Events
+SETTINGS_CHECKBOX_LOG_UNHANDLED_EVENTS Log Unhandled Events
+
+DEVICES_BUTTON_SELECTED Selected Device
+DEVICES_BUTTON_ADD Add Device
+DEVICES_BUTTON_REMOVE Remove Device
+
+SELECTED_DEVICE_NONE no devices
+SELECTED_DEVICE_UNSELECTED device not selected
+
+ADD_DEVICE_NAME Device Name
+ADD_DEVICE_METHOD Communication Method
+ADD_DEVICE_SELECT Select Device
+ADD_DEVICE_DRIVER Braille Driver
+ADD_DEVICE_DONE Done
+ADD_DEVICE_UNSELECTED_METHOD communication method not selected
+ADD_DEVICE_UNSELECTED_DEVICE device not selected
+ADD_DEVICE_UNSELECTED_DRIVER braille driver not selected
+ADD_DEVICE_NO_DEVICES no devices
+ADD_DEVICE_DUPLICATE_NAME device name already in use
+ADD_DEVICE_NO_PERMISSION access to device not granted
+
+REMOVE_DEVICE_PROMPT Would you really like to remove this device?
+
+SET_SELECTION_NONE none selected
+
+NAVIGATION_MODE_LABEL_List Vertical List
+NAVIGATION_MODE_LABEL_Grid Two Dimensional Grid
+
+SPEECH_SUPPORT_LABEL_native Native
+
+COMMUNICATION_METHOD_LABEL_Bluetooth Bluetooth
+COMMUNICATION_METHOD_LABEL_Usb USB
+COMMUNICATION_METHOD_LABEL_Serial Serial
+
+LOG_LEVEL_LABEL_debug Debug
+LOG_LEVEL_LABEL_information Information
+LOG_LEVEL_LABEL_notice Notice
+LOG_LEVEL_LABEL_warning Warning
+LOG_LEVEL_LABEL_error Error
+LOG_LEVEL_LABEL_critical Critical
+LOG_LEVEL_LABEL_alert Alert
+LOG_LEVEL_LABEL_emergency Emergency
+
+LOG_CATEGORY_LABEL_inpkts Input Packets
+LOG_CATEGORY_LABEL_outpkts Output Packets
+LOG_CATEGORY_LABEL_brlkeys Braille Device Key Events
+LOG_CATEGORY_LABEL_kbdkeys Keyboard Key Events
+LOG_CATEGORY_LABEL_csrtrk Cursor Tracking
+LOG_CATEGORY_LABEL_csrrtg Cursor Routing
+LOG_CATEGORY_LABEL_update Update Events
+LOG_CATEGORY_LABEL_speech Speech Events
+LOG_CATEGORY_LABEL_async Async Events
+LOG_CATEGORY_LABEL_server Server Events
+LOG_CATEGORY_LABEL_gio Generic I/O
+LOG_CATEGORY_LABEL_serial Serial I/O
+LOG_CATEGORY_LABEL_usb USB I/O
+LOG_CATEGORY_LABEL_bt Bluetooth I/O
+LOG_CATEGORY_LABEL_hid Human Interface I/O
+LOG_CATEGORY_LABEL_brldrv Braille Driver Events
+LOG_CATEGORY_LABEL_spkdrv Speech Driver Events
+LOG_CATEGORY_LABEL_scrdrv Screen Driver Events
+
+BRAILLE_DRIVER_LABEL_auto autodetect
+
+TEXT_TABLE_LABEL_auto locale-based autoselection
+TEXT_TABLE_LABEL_ar Arabic (generic)
+TEXT_TABLE_LABEL_as Assamese
+TEXT_TABLE_LABEL_awa Awadhi
+TEXT_TABLE_LABEL_bg Bulgarian
+TEXT_TABLE_LABEL_bh Bihari
+TEXT_TABLE_LABEL_bn Bengali
+TEXT_TABLE_LABEL_bo Tibetan
+TEXT_TABLE_LABEL_bra Braj
+TEXT_TABLE_LABEL_brf Braille Ready Format (for viewing .brf files within an editor or pager)
+TEXT_TABLE_LABEL_cs Czech
+TEXT_TABLE_LABEL_cy Welsh
+TEXT_TABLE_LABEL_da Danish
+TEXT_TABLE_LABEL_da_1252 Danish (Svend Thougaard, 2002–11–18)
+TEXT_TABLE_LABEL_da_lt Danish (LogText)
+TEXT_TABLE_LABEL_de German
+TEXT_TABLE_LABEL_dra Dravidian
+TEXT_TABLE_LABEL_el Greek
+TEXT_TABLE_LABEL_en English
+TEXT_TABLE_LABEL_en_CA English (Canada)
+TEXT_TABLE_LABEL_en_GB English (United Kingdom)
+TEXT_TABLE_LABEL_en_nabcc English (North American Braille Computer Code)
+TEXT_TABLE_LABEL_en_US English (United States)
+TEXT_TABLE_LABEL_eo Esperanto
+TEXT_TABLE_LABEL_es Spanish
+TEXT_TABLE_LABEL_et Estonian
+TEXT_TABLE_LABEL_fi Finnish
+TEXT_TABLE_LABEL_fr French
+TEXT_TABLE_LABEL_fr_2007 French (unified 2007)
+TEXT_TABLE_LABEL_fr_CA French (Canada)
+TEXT_TABLE_LABEL_fr_cbifs French (Code Braille Informatique Français Standard)
+TEXT_TABLE_LABEL_fr_FR French (France)
+TEXT_TABLE_LABEL_fr_vs French (VisioBraille)
+TEXT_TABLE_LABEL_ga Irish
+TEXT_TABLE_LABEL_gd Gaelic
+TEXT_TABLE_LABEL_gon Gondi
+TEXT_TABLE_LABEL_gu Gujarati
+TEXT_TABLE_LABEL_he Hebrew
+TEXT_TABLE_LABEL_hi Hindi
+TEXT_TABLE_LABEL_hr Croatian
+TEXT_TABLE_LABEL_hu Hungarian
+TEXT_TABLE_LABEL_hy Armenian
+TEXT_TABLE_LABEL_is Icelandic
+TEXT_TABLE_LABEL_it Italian
+TEXT_TABLE_LABEL_kha Khasi
+TEXT_TABLE_LABEL_kn Kannada
+TEXT_TABLE_LABEL_kok Konkani
+TEXT_TABLE_LABEL_kru Kurukh
+TEXT_TABLE_LABEL_lt Lituanian
+TEXT_TABLE_LABEL_lv Latvian
+TEXT_TABLE_LABEL_mg Malagasy
+TEXT_TABLE_LABEL_mi Maori
+TEXT_TABLE_LABEL_ml Malayalam
+TEXT_TABLE_LABEL_mni Manipuri
+TEXT_TABLE_LABEL_mr Marathi
+TEXT_TABLE_LABEL_mt Maltese
+TEXT_TABLE_LABEL_mun Munda
+TEXT_TABLE_LABEL_mwr Marwari
+TEXT_TABLE_LABEL_ne Nepali
+TEXT_TABLE_LABEL_new Newari
+TEXT_TABLE_LABEL_nl Dutch
+TEXT_TABLE_LABEL_nl_BE Dutch (Belgium)
+TEXT_TABLE_LABEL_nl_NL Dutch (Netherlands)
+TEXT_TABLE_LABEL_no Norwegian
+TEXT_TABLE_LABEL_no_generic Norwegian (with support for other languages)
+TEXT_TABLE_LABEL_no_oup Norwegian (Offentlig utvalg for punktskrift)
+TEXT_TABLE_LABEL_nwc Newari (old)
+TEXT_TABLE_LABEL_or Oriya
+TEXT_TABLE_LABEL_pa Panjabi
+TEXT_TABLE_LABEL_pi Pali
+TEXT_TABLE_LABEL_pl Polish
+TEXT_TABLE_LABEL_pt Portuguese
+TEXT_TABLE_LABEL_ro Romanian
+TEXT_TABLE_LABEL_ru Russian
+TEXT_TABLE_LABEL_sa Sanskrit
+TEXT_TABLE_LABEL_sat Santali
+TEXT_TABLE_LABEL_sd Sindhi
+TEXT_TABLE_LABEL_se Sami (Northern)
+TEXT_TABLE_LABEL_sk Slovak
+TEXT_TABLE_LABEL_sl Slovenian
+TEXT_TABLE_LABEL_sv Swedish
+TEXT_TABLE_LABEL_sv_1989 Swedish (1989 standard)
+TEXT_TABLE_LABEL_sv_1996 Swedish (1996 standard)
+TEXT_TABLE_LABEL_sw Swahili
+TEXT_TABLE_LABEL_ta Tamil
+TEXT_TABLE_LABEL_te Telugu
+TEXT_TABLE_LABEL_tr Turkish
+TEXT_TABLE_LABEL_uk Ukrainian
+TEXT_TABLE_LABEL_vi Vietnamese
+
+ATTRIBUTES_TABLE_LABEL_invleft_right inverse foreground colour in the left column and background colour in the right column
+ATTRIBUTES_TABLE_LABEL_left_right foreground colour in the left column and background colour in the right column
+ATTRIBUTES_TABLE_LABEL_upper_lower foreground colour in the upper square and background colour in the lower square
+
+CONTRACTION_TABLE_LABEL_auto locale-based autoselection
+CONTRACTION_TABLE_LABEL_af Afrikaans (contracted)
+CONTRACTION_TABLE_LABEL_am Amharic (uncontracted)
+CONTRACTION_TABLE_LABEL_de German
+CONTRACTION_TABLE_LABEL_de_g0 German (uncontracted)
+CONTRACTION_TABLE_LABEL_de_g1 German (basic contractions)
+CONTRACTION_TABLE_LABEL_de_g2 German (contracted)
+CONTRACTION_TABLE_LABEL_de_1998 German (contracted - 1998 standard)
+CONTRACTION_TABLE_LABEL_de_2015 German (contracted - 2015 standard)
+CONTRACTION_TABLE_LABEL_en English
+CONTRACTION_TABLE_LABEL_en_US English (United States)
+CONTRACTION_TABLE_LABEL_en_ueb_g1 English (Unified, uncontracted)
+CONTRACTION_TABLE_LABEL_en_ueb_g2 English (Unified, contracted)
+CONTRACTION_TABLE_LABEL_en_us_g2 English (United States, contracted)
+CONTRACTION_TABLE_LABEL_es Spanish (contracted)
+CONTRACTION_TABLE_LABEL_fr French
+CONTRACTION_TABLE_LABEL_fr_g1 French (uncontracted)
+CONTRACTION_TABLE_LABEL_fr_g2 French (contracted)
+CONTRACTION_TABLE_LABEL_ha Hausa (contracted)
+CONTRACTION_TABLE_LABEL_id Indonesian (contracted)
+CONTRACTION_TABLE_LABEL_ipa International Phonetic Alphabet
+CONTRACTION_TABLE_LABEL_ja Japanese (uncontracted)
+CONTRACTION_TABLE_LABEL_ko Korean
+CONTRACTION_TABLE_LABEL_ko_g0 Korean (uncontracted)
+CONTRACTION_TABLE_LABEL_ko_g1 Korean (partially contracted)
+CONTRACTION_TABLE_LABEL_ko_g2 Korean (contracted)
+CONTRACTION_TABLE_LABEL_lt Lithuanian (uncontracted)
+CONTRACTION_TABLE_LABEL_mg Malagasy (contracted)
+CONTRACTION_TABLE_LABEL_mun Munda (contracted)
+CONTRACTION_TABLE_LABEL_nl Dutch (contracted)
+CONTRACTION_TABLE_LABEL_ny Chichewa (contracted)
+CONTRACTION_TABLE_LABEL_pt Portuguese (contracted)
+CONTRACTION_TABLE_LABEL_ru Russian (contracted)
+CONTRACTION_TABLE_LABEL_si Sinhalese (uncontracted)
+CONTRACTION_TABLE_LABEL_sw Swahili (contracted)
+CONTRACTION_TABLE_LABEL_th Thai (contracted)
+CONTRACTION_TABLE_LABEL_zh_TW Chinese (Taiwan, uncontracted)
+CONTRACTION_TABLE_LABEL_zu Zulu (contracted)
+
+KEYBOARD_TABLE_LABEL_braille bindings for braille keyboards
+KEYBOARD_TABLE_LABEL_desktop bindings for full keyboards
+KEYBOARD_TABLE_LABEL_keypad bindings for keypad-based navigation
+KEYBOARD_TABLE_LABEL_laptop bindings for keyboards without a keypad
+KEYBOARD_TABLE_LABEL_sun_type6 bindings for Sun Type 6 keyboards
diff --git a/Android/TRANSLATORS b/Android/TRANSLATORS
new file mode 100644
index 0000000..eeb6218
--- /dev/null
+++ b/Android/TRANSLATORS
@@ -0,0 +1,18 @@
+ar Ikrami Ahmad <ikrami@blind.gov.qa>
+cs Petr Řehák <Rehak@adaptech.cz>
+da Ole Guldberg <Ole@omgwtf.dk>
+de Dietmar Segbert <DietmarSegbert@GMX.de>
+de Mario Lang <MLang@blind.guru>
+de Nermin Hasic <nermin-hasic@gmx.de>
+en Dave Mielke <Dave@Mielke.cc>
+es Tony Hernandez <TonyHSpeaks@GMail.com>
+fa Reza Imany <imany.reza1@gmail.com>
+fi Jani Kinnunen <Jani.Kinnunen@wippies.fi>
+fr Jean-Philippe Mengual <jpmengual@debian.org>
+it Ollie Mallard <Mallard@kimabe.eu>
+nb Helge Havnegjerde <Helge.Havnegjerde@getmail.no>
+nl Miranda Woudstra <Miran.Woudstra@gmail.com>
+ru Nikita Tseykovets <Tseikovets@rambler.ru>
+sl J.G <Jozko.Gregorc@guest.arnes.si>
+tr Erişilebilir Android Ekibi <bilgi@erisilebilirandroid.com>
+zh 高生旺 <Coscell@molerat.net>
diff --git a/Android/Tools/fixstrings b/Android/Tools/fixstrings
new file mode 100755
index 0000000..093f530
--- /dev/null
+++ b/Android/Tools/fixstrings
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+sed -e '
+s/$/<\/string>/
+s/ /">/
+s/^/  <string name="/
+' -i "${@}"
+
+exit 0
diff --git a/Android/Tools/mkicons b/Android/Tools/mkicons
new file mode 100755
index 0000000..4eabbbd
--- /dev/null
+++ b/Android/Tools/mkicons
@@ -0,0 +1,49 @@
+#!/bin/sh
+set -e
+
+readonly programName="${0##*/}"
+
+programMessage() {
+  local message="${1}"
+
+  echo "${programName}: ${message}"
+}
+
+syntaxError() {
+  local message="${1}"
+
+  programMessage "${message}"
+  exit 2
+}
+
+semanticError() {
+  local message="${1}"
+
+  programMessage "${message}"
+  exit 3
+}
+
+makeIcon() {
+  local name="${1}"
+  local size="${2}"
+
+  convert "${imageFile}" -resize "${size}" "res/drawable-${name}/${imageName}.png"
+}
+
+[ "${#}" -ge 1 ] || syntaxError "missing image file"
+imageFile="${1}"
+shift 1
+
+[ "${#}" -eq 0 ] || syntaxError "too many parameters"
+
+[ -f "${imageFile}" ] || semanticError "image file not found: ${imageFile}"
+[ -r "${imageFile}" ] || semanticError "image file not readable: ${imageFile}"
+
+imageName="${imageFile%.*}"
+
+makeIcon ldpi 36x36
+makeIcon mdpi 48x48
+makeIcon hdpi 72x72
+makeIcon xhdpi 96x96
+
+exit 0
diff --git a/Android/Tools/mksettings b/Android/Tools/mksettings
new file mode 100755
index 0000000..37775a3
--- /dev/null
+++ b/Android/Tools/mksettings
@@ -0,0 +1,170 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] "prologue.tcl"]
+
+proc loadProperties {file names} {
+   set properties [dict create]
+
+   set comment #
+   set pattern {^}
+   append pattern "${comment}([join $names |])"
+   append pattern "\\s+(\[^\\s${comment}\]+)"
+   append pattern "\\s*(?:${comment}\\s*(.*?))?"
+   append pattern {\s*$}
+
+   forEachLine record $file {
+      if {[regexp $pattern $record x name code label]} {
+         if {[string length $label] == 0} {
+            set label $code
+         } elseif {[set index [string first ";" $label]] >= 0} {
+            set label [string replace $label $index end]
+         }
+
+         dict set properties $name $code $label
+      }
+   }
+
+   return $properties
+}
+
+proc compareKeys {dictionary key1 key2} {
+   set text1 [dict get $dictionary $key1]
+   set text2 [dict get $dictionary $key2]
+
+   set capitalized1 [string is upper -strict [string index $text1 0]]
+   set capitalized2 [string is upper -strict [string index $text2 0]]
+
+   if {!$capitalized1 && $capitalized2} {
+      return -1
+   }
+
+   if {$capitalized1 && !$capitalized2} {
+      return 1
+   }
+
+   return [string compare $key1 $key2]
+}
+
+proc makeStringArray {name items} {
+   set lines [list]
+   lappend lines "  <string-array name=\"$name\">"
+
+   foreach item $items {
+      lappend lines "    <item>$item</item>"
+   }
+
+   lappend lines "  </string-array>"
+   return $lines
+}
+
+proc makeResourceLines {properties name} {
+   set property [dict get $properties $name]
+   set prefix "[string toupper [string map {- _} $name]]_"
+
+   set lines [list]
+   lappend lines {<?xml version="1.0" encoding="utf-8"?>}
+
+   lappend lines ""
+   lappend lines "<!-- generated from $::inputFile by [file tail [info script]] -->"
+   lappend lines "<!-- changes made here will be overwritten -->"
+
+   lappend lines ""
+   lappend lines "<resources>"
+
+   set codes [list]
+   set labels [list]
+
+   foreach code [lsort -command [list compareKeys $property] [dict keys $property]] {
+      set translatable "true"
+
+      if {![string equal $code "auto"]} {
+         if {[string equal $name "braille-driver"]} {
+            set translatable "false"
+         }
+      }
+
+      if {[string equal $code off]} {
+         lvarpush codes $code
+         lvarpush labels "@string/setting_state_$code"
+         continue
+      }
+
+      set label "${prefix}LABEL_[string map {- _} $code]"
+      lappend labels "@string/$label"
+      lappend codes $code
+
+      lappend lines "  <string name=\"$label\" translatable=\"$translatable\">[dict get $property $code]</string>"
+   }
+
+   lappend lines ""
+   lvarcat lines [makeStringArray "${prefix}LABELS" $labels]
+
+   lappend lines ""
+   lvarcat lines [makeStringArray "${prefix}VALUES" $codes]
+
+   lappend lines "</resources>"
+   return $lines
+}
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions positionalArguments "\[property ...\]"
+set propertyNames $positionalArguments
+
+if {[llength $propertyNames] == 0} {
+   lappend propertyNames "braille-driver"
+   lappend propertyNames "text-table"
+   lappend propertyNames "attributes-table"
+   lappend propertyNames "contraction-table"
+   lappend propertyNames "keyboard-table"
+}
+
+if {[llength $propertyNames] == 0} {
+   syntaxError "property names not specified"
+}
+
+set inputFile  "brltty.conf.in"
+set inputPath [file join $sourceRoot Documents $inputFile]
+logNote "loading properties: $inputPath"
+set properties [loadProperties $inputPath $propertyNames]
+
+foreach name $propertyNames {
+   if {![dict exists $properties $name]} {
+      syntaxError "unknown property name: $name"
+   }
+}
+
+set updated 0
+foreach propertyName $propertyNames {
+   set resourceFile "[string tolower $propertyName].xml"
+   set resourcePath [file join $applicationDirectory res values $resourceFile]
+   logNote "writing resource file: $resourcePath"
+
+   if {[updateFile $resourcePath "[join [makeResourceLines $properties $propertyName] \n]\n"]} {
+      set updated 1
+   }
+}
+
+if {!$updated} {
+   logMessage task "no files updated"
+}
+
+exit 0
diff --git a/Android/Tools/newstrings b/Android/Tools/newstrings
new file mode 100755
index 0000000..e5d040f
--- /dev/null
+++ b/Android/Tools/newstrings
@@ -0,0 +1,187 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] "prologue.tcl"]
+
+proc splitNames {names oldNamesVariable sameNamesVariable newNamesVariable} {
+   global baseStrings ignorableNames
+
+   upvar 1 $oldNamesVariable oldNames
+   upvar 1 $sameNamesVariable sameNames
+   upvar 1 $newNamesVariable newNames
+
+   lassign [intersect3 $names [dict keys $baseStrings]] oldNames sameNames newNames
+   lassign [intersect3 $newNames $ignorableNames] newNames x x
+}
+
+proc writeNames {path names} {
+   global baseStrings
+   file delete $path
+
+   if {[llength $names] > 0} {
+      if {[catch [list open $path {WRONLY TRUNC CREAT}] channel] == 0} {
+         foreach name $names {
+            set string [dict get $baseStrings $name]
+            puts $channel "$name [dict get $string text]"
+         }
+
+         close $channel; unset channel
+      } else {
+         semanticError $channel
+      }
+   }
+}
+
+proc loadStringList {} {
+   global androidDirectory
+
+   set strings [dict create]
+   set path [file join $androidDirectory STRINGS]
+   logMessage detail "loading string list: $path"
+
+   set order 0
+   set pattern {^\s*(\S+)\s+(.*?)\s*$}
+
+   if {[catch [list open $path {RDONLY}] channel] == 0} {
+      while {[gets $channel line] >= 0} {
+         if {[regexp $pattern $line x name text]} {
+            set string [dict create text $text order [incr order]]
+            dict set strings $name $string
+         }
+      }
+
+      close $channel; unset channel
+   } else {
+      semanticError $channel
+   }
+
+   return $strings
+}
+
+proc auditStringList {} {
+   global baseStrings listedStrings
+
+   logNote "auditing string list"
+   splitNames [dict keys $listedStrings] oldNames sameNames newNames
+   writeNames "[getProgramName].txt" $newNames
+
+   foreach name $oldNames {
+      writeProgramMessage "old listed string: $name"
+   }
+
+   foreach name $sameNames {
+      set oldText [dict get $listedStrings $name text]
+      set newText [dict get $baseStrings $name text]
+
+      if {![string equal $newText $oldText]} {
+         writeProgramMessage "base string changed: $name: $oldText -> $newText"
+      }
+   }
+}
+
+proc loadStringResources {directory} {
+   logMessage detail "loading resource directory: $directory"
+   set strings [dict create]
+
+   set pattern {^\s*<\s*string}
+   append pattern {\s+name\s*=\s*"([^"]*)"}
+   append pattern {(?:\s+translatable\s*=\s*"true")?}
+   append pattern {(?:\s+tools:ignore\s*=\s*"(MissingTranslation)")?}
+   append pattern {\s*>\s*(.*?)\s*<\s*/string\s*>\s*$}
+
+   foreach path [glob "$directory/*.xml"] {
+      logMessage detail "loading string resources: $path"
+
+      if {[catch [list open $path {RDONLY}] channel] == 0} {
+         set order 0
+
+         while {[gets $channel line] >= 0} {
+            if {[regexp $pattern $line x name ignorable text]} {
+               set ignorable [expr {[string length $ignorable] > 0}]
+               set string [dict create text $text ignorable $ignorable order [incr order]]
+               dict set strings $name $string
+            }
+         }
+
+         close $channel; unset channel
+      } else {
+         semanticError $channel
+      }
+   }
+
+   return $strings
+}
+
+proc getIgnorableNames {strings} {
+   set names [list]
+
+   foreach name [dict keys $strings] {
+      if {[dict get $strings $name ignorable]} {
+         lappend names $name
+      }
+   }
+
+   return $names
+}
+
+proc auditLanguage {code directory} {
+   logNote "auditing language: $code"
+   set languageStrings [loadStringResources $directory]
+   splitNames [dict keys $languageStrings] oldNames sameNames newNames
+   writeNames "[getProgramName]-$code.txt" $newNames
+
+   foreach name $oldNames {
+      writeProgramMessage "old language string: $code: $name"
+   }
+
+   if {([string length $oldNames] + [string length $newNames]) == 0} {
+      global listedStrings
+
+      foreach name [dict keys $listedStrings] {
+         if {[dict get $listedStrings $name order] != [dict get $languageStrings $name order]} {
+            writeProgramMessage "language string out of order: $code: $name"
+         }
+      }
+   }
+}
+
+proc auditAllLanguages {} {
+   global baseDirectory
+   set languageDelimiter -
+
+   foreach directory [glob -nocomplain -path "$baseDirectory$languageDelimiter" "?*"] {
+      set code [lindex [split [file tail $directory] $languageDelimiter] 1]
+      auditLanguage $code $directory
+   }
+}
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions
+
+set baseDirectory [file join $applicationDirectory res values]
+set baseStrings [loadStringResources $baseDirectory]
+set ignorableNames [getIgnorableNames $baseStrings]
+
+set listedStrings [loadStringList]
+auditStringList
+
+auditAllLanguages
+exit 0
diff --git a/Android/Tools/prologue.tcl b/Android/Tools/prologue.tcl
new file mode 100644
index 0000000..436cc48
--- /dev/null
+++ b/Android/Tools/prologue.tcl
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. .. "prologue.tcl"]
+set androidDirectory [file join $sourceRoot Android]
+set gradleDirectory [file join $androidDirectory Gradle]
+set applicationDirectory [file join $gradleDirectory app src main]
diff --git a/Android/Tools/rebuild b/Android/Tools/rebuild
new file mode 100755
index 0000000..33fb28d
--- /dev/null
+++ b/Android/Tools/rebuild
@@ -0,0 +1,73 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] "prologue.tcl"]
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions
+
+proc executeCommand {args} {
+   if {[catch $args response] == 0} {
+      set response ""
+   } elseif {[string equal [lindex $::errorCode 0] CHILDSTATUS]} {
+      set response "host command failed: exit status [lindex $::errorCode 2]"
+   }
+
+   return $response
+}
+
+proc setWorkingDirectory {path} {
+   logDetail "working directory: $path"
+   set problem [executeCommand cd $path]
+
+   if {[string length $problem] > 0} {
+      semanticError "$problem"
+   }
+}
+
+proc executeHostCommand {args} {
+   set command [join $args " "]
+   logDetail "executing host command: $command"
+
+   set args [linsert $args 0 exec <@ stdin]
+   lappend args >@ stdout 2>@ stderr
+   set problem [eval executeCommand $args]
+
+   if {[string length $problem] > 0} {
+      semanticError "$problem: $command"
+   }
+}
+
+proc executeGradleTask {task} {
+   executeHostCommand ./gradlew --quiet --console plain -- $task
+}
+
+setWorkingDirectory $sourceRoot
+executeHostCommand ./autogen
+executeHostCommand ./cfg-android
+
+setWorkingDirectory $gradleDirectory
+executeGradleTask clean
+executeGradleTask :app:assembleDebug
+executeGradleTask :app:assembleRelease
+
+logMessage task "done"
+exit 0
diff --git a/Authorization/Polkit/Makefile.in b/Authorization/Polkit/Makefile.in
new file mode 100644
index 0000000..24e961e
--- /dev/null
+++ b/Authorization/Polkit/Makefile.in
@@ -0,0 +1,44 @@
+# Authorization/Polkit/Makefile.  Generated from Makefile.in by configure.
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+POLKIT_PACKAGE_NAME = org.a11y.brlapi
+POLKIT_POLICIES_EXTENSION = policy
+POLKIT_RULES_EXTENSION = rules
+
+POLKIT_POLICIES_DIRECTORY = $(INSTALL_ROOT)$(POLKIT_POLICY_DIR)
+POLKIT_RULES_DIRECTORY = $(INSTALL_ROOT)$(POLKIT_RULE_DIR)
+
+install: install-policies install-rules
+
+install-policies-directory:
+	$(INSTALL_DIRECTORY) $(POLKIT_POLICIES_DIRECTORY)
+
+install-policies: install-policies-directory
+	$(INSTALL_DATA) $(SRC_DIR)/policies $(POLKIT_POLICIES_DIRECTORY)/$(POLKIT_PACKAGE_NAME).$(POLKIT_POLICIES_EXTENSION)
+
+install-rules-directory:
+	$(INSTALL_DIRECTORY) $(POLKIT_RULES_DIRECTORY)
+
+install-rules: install-rules-directory
+	$(INSTALL_DATA) $(SRC_DIR)/rules $(POLKIT_RULES_DIRECTORY)/$(POLKIT_PACKAGE_NAME).$(POLKIT_RULES_EXTENSION)
+
+uninstall::
+	-rm $(POLKIT_POLICIES_DIRECTORY)/$(POLKIT_PACKAGE_NAME).*
+	-rm $(POLKIT_RULES_DIRECTORY)/$(POLKIT_PACKAGE_NAME).*
+
diff --git a/Authorization/Polkit/policies b/Authorization/Polkit/policies
new file mode 100644
index 0000000..17750ca
--- /dev/null
+++ b/Authorization/Polkit/policies
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"
+>
+
+<policyconfig>
+  <vendor>The BRLTTY Developers</vendor>
+  <vendor_url>http://brltty.app/</vendor_url>
+
+  <action id="org.a11y.brlapi.write-display">
+    <description>Access to the braille device via BrlAPI</description>
+    <message>Privileges are required to access the braille device via BrlAPI</message>
+
+    <defaults>
+      <allow_active>yes</allow_active>
+      <allow_inactive>no</allow_inactive>
+      <allow_any>no</allow_any>
+    </defaults>
+
+    <annotate key="org.freedesktop.policykit.owner">unix-user:brltty</annotate>
+  </action>
+</policyconfig>
diff --git a/Authorization/Polkit/rules b/Authorization/Polkit/rules
new file mode 100644
index 0000000..c2aef84
--- /dev/null
+++ b/Authorization/Polkit/rules
@@ -0,0 +1,7 @@
+polkit.addRule(function(action, subject) {
+  if (action.id == "org.a11y.brlapi.write-display") {
+    if (subject.isInGroup("brlapi")) {
+      return polkit.Result.YES;
+    }
+  }
+});
diff --git a/Autostart/AppStream/Makefile.in b/Autostart/AppStream/Makefile.in
new file mode 100644
index 0000000..ad3e70d
--- /dev/null
+++ b/Autostart/AppStream/Makefile.in
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+METAINFO_DIRECTORY = $(INSTALL_ROOT)$(datadir)/metainfo
+METAINFO_FILE = org.a11y.brltty.metainfo.xml
+
+install-metainfo-directory:
+	$(INSTALL_DIRECTORY) $(METAINFO_DIRECTORY)
+
+install: install-metainfo-directory
+	$(INSTALL_DATA) $(SRC_DIR)/$(METAINFO_FILE) $(METAINFO_DIRECTORY)
+
+uninstall:
+	-rm -f $(METAINFO_DIRECTORY)/$(METAINFO_FILE)
+
diff --git a/Autostart/AppStream/org.a11y.brltty.metainfo.xml b/Autostart/AppStream/org.a11y.brltty.metainfo.xml
new file mode 100644
index 0000000..23f7412
--- /dev/null
+++ b/Autostart/AppStream/org.a11y.brltty.metainfo.xml
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Copyright (C) 1995-2023 by The BRLTTY Developers.
+ 
+  This file is provided, free of charge and as-is, under the terms of the
+  All Permissive License as published by the Free Software Foundation.
+  Please see: https://spdx.org/licenses/FSFAP.html
+
+  Copying and distribution of this file, with or without modification,
+  are permitted in any medium without royalty provided the copyright
+  notice and this notice are preserved. This file is offered as-is,
+  without any warranty.
+-->
+
+<!--
+  While the license does permit modification, the copyright holder will
+  appreciate it if modifications were restricted to the modalias list,
+  and that the rest of the metadata remain as it was originally written.
+-->
+
+<component>
+  <id>org.a11y.brltty</id>
+  <name>BRLTTY</name>
+  <summary>Support for braille devices</summary>
+
+  <description>
+    A background process providing access to the console screen (when in
+    text mode) for a blind person using a refreshable braille display.
+  </description>
+
+  <developer_name>The BRLTTY Developers</developer_name>
+  <url type="homepage">http://brltty.app/</url>
+
+  <metadata_license>FSFAP</metadata_license>
+  <project_license>GPL-2.0</project_license>
+
+  <provides>
+    <!-- BEGIN_USB_BRAILLE_DEVICES -->
+
+    <!-- Device: 0403:6001 -->
+    <!-- Generic Identifier -->
+    <!-- Vendor: Future Technology Devices International, Ltd. -->
+    <!-- Product: FT232 USB-Serial (UART) IC -->
+    <!-- Albatross [all models] -->
+    <!-- Cebra [all models] -->
+    <!-- HIMS [Sync Braille] -->
+    <!-- HandyTech [FTDI chip] -->
+    <!-- Hedo [MobilLine] -->
+    <!-- MDV [all models] -->
+    <modalias>usb:v0403p6001*</modalias>
+
+    <!-- Device: 0403:6010 -->
+    <!-- Generic Identifier -->
+    <!-- Vendor: Future Technology Devices International, Ltd -->
+    <!-- Product: FT2232C/D/H Dual UART/FIFO IC -->
+    <!-- DotPad [all models] -->
+    <modalias>usb:v0403p6010*</modalias>
+
+    <!-- Device: 0403:DE58 -->
+    <!-- Hedo [MobilLine] -->
+    <modalias>usb:v0403pDE58*</modalias>
+
+    <!-- Device: 0403:DE59 -->
+    <!-- Hedo [ProfiLine] -->
+    <modalias>usb:v0403pDE59*</modalias>
+
+    <!-- Device: 0403:F208 -->
+    <!-- Papenmeier [all models] -->
+    <modalias>usb:v0403pF208*</modalias>
+
+    <!-- Device: 0403:FE70 -->
+    <!-- Baum [Vario 40 (40 cells)] -->
+    <modalias>usb:v0403pFE70*</modalias>
+
+    <!-- Device: 0403:FE71 -->
+    <!-- Baum [PocketVario (24 cells)] -->
+    <modalias>usb:v0403pFE71*</modalias>
+
+    <!-- Device: 0403:FE72 -->
+    <!-- Baum [SuperVario 40 (40 cells)] -->
+    <modalias>usb:v0403pFE72*</modalias>
+
+    <!-- Device: 0403:FE73 -->
+    <!-- Baum [SuperVario 32 (32 cells)] -->
+    <modalias>usb:v0403pFE73*</modalias>
+
+    <!-- Device: 0403:FE74 -->
+    <!-- Baum [SuperVario 64 (64 cells)] -->
+    <modalias>usb:v0403pFE74*</modalias>
+
+    <!-- Device: 0403:FE75 -->
+    <!-- Baum [SuperVario 80 (80 cells)] -->
+    <modalias>usb:v0403pFE75*</modalias>
+
+    <!-- Device: 0403:FE76 -->
+    <!-- Baum [VarioPro 80 (80 cells)] -->
+    <modalias>usb:v0403pFE76*</modalias>
+
+    <!-- Device: 0403:FE77 -->
+    <!-- Baum [VarioPro 64 (64 cells)] -->
+    <modalias>usb:v0403pFE77*</modalias>
+
+    <!-- Device: 0452:0100 -->
+    <!-- Metec [all models] -->
+    <modalias>usb:v0452p0100*</modalias>
+
+    <!-- Device: 045E:930A -->
+    <!-- HIMS [Braille Sense (USB 1.1)] -->
+    <!-- HIMS [Braille Sense (USB 2.0)] -->
+    <!-- HIMS [Braille Sense U2 (USB 2.0)] -->
+    <modalias>usb:v045Ep930A*</modalias>
+
+    <!-- Device: 045E:930B -->
+    <!-- HIMS [Braille Edge and QBrailleXL] -->
+    <modalias>usb:v045Ep930B*</modalias>
+
+    <!-- Device: 0483:A1D3 -->
+    <!-- Baum [Orbit Reader 20 (20 cells)] -->
+    <modalias>usb:v0483pA1D3*</modalias>
+
+    <!-- Device: 0483:A366 -->
+    <!-- Baum [Orbit Reader 40 (40 cells)] -->
+    <modalias>usb:v0483pA366*</modalias>
+
+    <!-- Device: 06B0:0001 -->
+    <!-- Alva [Satellite (5nn)] -->
+    <modalias>usb:v06B0p0001*</modalias>
+
+    <!-- Device: 0798:0001 -->
+    <!-- Voyager [all models] -->
+    <modalias>usb:v0798p0001*</modalias>
+
+    <!-- Device: 0798:0600 -->
+    <!-- Alva [Voyager Protocol Converter] -->
+    <modalias>usb:v0798p0600*</modalias>
+
+    <!-- Device: 0798:0624 -->
+    <!-- Alva [BC624] -->
+    <modalias>usb:v0798p0624*</modalias>
+
+    <!-- Device: 0798:0640 -->
+    <!-- Alva [BC640] -->
+    <modalias>usb:v0798p0640*</modalias>
+
+    <!-- Device: 0798:0680 -->
+    <!-- Alva [BC680] -->
+    <modalias>usb:v0798p0680*</modalias>
+
+    <!-- Device: 0904:1016 -->
+    <!-- FrankAudiodata [B2K84 (before firmware installation)] -->
+    <modalias>usb:v0904p1016*</modalias>
+
+    <!-- Device: 0904:1017 -->
+    <!-- FrankAudiodata [B2K84 (after firmware installation)] -->
+    <modalias>usb:v0904p1017*</modalias>
+
+    <!-- Device: 0904:2000 -->
+    <!-- Baum [VarioPro 40 (40 cells)] -->
+    <modalias>usb:v0904p2000*</modalias>
+
+    <!-- Device: 0904:2001 -->
+    <!-- Baum [EcoVario 24 (24 cells)] -->
+    <modalias>usb:v0904p2001*</modalias>
+
+    <!-- Device: 0904:2002 -->
+    <!-- Baum [EcoVario 40 (40 cells)] -->
+    <modalias>usb:v0904p2002*</modalias>
+
+    <!-- Device: 0904:2007 -->
+    <!-- Baum [VarioConnect 40 (40 cells)] -->
+    <modalias>usb:v0904p2007*</modalias>
+
+    <!-- Device: 0904:2008 -->
+    <!-- Baum [VarioConnect 32 (32 cells)] -->
+    <modalias>usb:v0904p2008*</modalias>
+
+    <!-- Device: 0904:2009 -->
+    <!-- Baum [VarioConnect 24 (24 cells)] -->
+    <modalias>usb:v0904p2009*</modalias>
+
+    <!-- Device: 0904:2010 -->
+    <!-- Baum [VarioConnect 64 (64 cells)] -->
+    <modalias>usb:v0904p2010*</modalias>
+
+    <!-- Device: 0904:2011 -->
+    <!-- Baum [VarioConnect 80 (80 cells)] -->
+    <modalias>usb:v0904p2011*</modalias>
+
+    <!-- Device: 0904:2014 -->
+    <!-- Baum [EcoVario 32 (32 cells)] -->
+    <modalias>usb:v0904p2014*</modalias>
+
+    <!-- Device: 0904:2015 -->
+    <!-- Baum [EcoVario 64 (64 cells)] -->
+    <modalias>usb:v0904p2015*</modalias>
+
+    <!-- Device: 0904:2016 -->
+    <!-- Baum [EcoVario 80 (80 cells)] -->
+    <modalias>usb:v0904p2016*</modalias>
+
+    <!-- Device: 0904:3000 -->
+    <!-- Baum [Refreshabraille 18 (18 cells)] -->
+    <modalias>usb:v0904p3000*</modalias>
+
+    <!-- Device: 0904:3001 -->
+    <!-- Baum [Orbit in Refreshabraille Emulation Mode (18 cells)] -->
+    <!-- Baum [Refreshabraille 18 (18 cells)] -->
+    <modalias>usb:v0904p3001*</modalias>
+
+    <!-- Device: 0904:4004 -->
+    <!-- Baum [Pronto! V3 18 (18 cells)] -->
+    <modalias>usb:v0904p4004*</modalias>
+
+    <!-- Device: 0904:4005 -->
+    <!-- Baum [Pronto! V3 40 (40 cells)] -->
+    <modalias>usb:v0904p4005*</modalias>
+
+    <!-- Device: 0904:4007 -->
+    <!-- Baum [Pronto! V4 18 (18 cells)] -->
+    <modalias>usb:v0904p4007*</modalias>
+
+    <!-- Device: 0904:4008 -->
+    <!-- Baum [Pronto! V4 40 (40 cells)] -->
+    <modalias>usb:v0904p4008*</modalias>
+
+    <!-- Device: 0904:6001 -->
+    <!-- Baum [SuperVario2 40 (40 cells)] -->
+    <modalias>usb:v0904p6001*</modalias>
+
+    <!-- Device: 0904:6002 -->
+    <!-- Baum [PocketVario2 (24 cells)] -->
+    <modalias>usb:v0904p6002*</modalias>
+
+    <!-- Device: 0904:6003 -->
+    <!-- Baum [SuperVario2 32 (32 cells)] -->
+    <modalias>usb:v0904p6003*</modalias>
+
+    <!-- Device: 0904:6004 -->
+    <!-- Baum [SuperVario2 64 (64 cells)] -->
+    <modalias>usb:v0904p6004*</modalias>
+
+    <!-- Device: 0904:6005 -->
+    <!-- Baum [SuperVario2 80 (80 cells)] -->
+    <modalias>usb:v0904p6005*</modalias>
+
+    <!-- Device: 0904:6006 -->
+    <!-- Baum [Brailliant2 40 (40 cells)] -->
+    <modalias>usb:v0904p6006*</modalias>
+
+    <!-- Device: 0904:6007 -->
+    <!-- Baum [Brailliant2 24 (24 cells)] -->
+    <modalias>usb:v0904p6007*</modalias>
+
+    <!-- Device: 0904:6008 -->
+    <!-- Baum [Brailliant2 32 (32 cells)] -->
+    <modalias>usb:v0904p6008*</modalias>
+
+    <!-- Device: 0904:6009 -->
+    <!-- Baum [Brailliant2 64 (64 cells)] -->
+    <modalias>usb:v0904p6009*</modalias>
+
+    <!-- Device: 0904:600A -->
+    <!-- Baum [Brailliant2 80 (80 cells)] -->
+    <modalias>usb:v0904p600A*</modalias>
+
+    <!-- Device: 0904:6011 -->
+    <!-- Baum [VarioConnect 24 (24 cells)] -->
+    <modalias>usb:v0904p6011*</modalias>
+
+    <!-- Device: 0904:6012 -->
+    <!-- Baum [VarioConnect 32 (32 cells)] -->
+    <modalias>usb:v0904p6012*</modalias>
+
+    <!-- Device: 0904:6013 -->
+    <!-- Baum [VarioConnect 40 (40 cells)] -->
+    <modalias>usb:v0904p6013*</modalias>
+
+    <!-- Device: 0904:6101 -->
+    <!-- Baum [VarioUltra 20 (20 cells)] -->
+    <modalias>usb:v0904p6101*</modalias>
+
+    <!-- Device: 0904:6102 -->
+    <!-- Baum [VarioUltra 40 (40 cells)] -->
+    <modalias>usb:v0904p6102*</modalias>
+
+    <!-- Device: 0904:6103 -->
+    <!-- Baum [VarioUltra 32 (32 cells)] -->
+    <modalias>usb:v0904p6103*</modalias>
+
+    <!-- Device: 0921:1200 -->
+    <!-- HandyTech [GoHubs chip] -->
+    <modalias>usb:v0921p1200*</modalias>
+
+    <!-- Device: 0F4E:0100 -->
+    <!-- FreedomScientific [Focus 1] -->
+    <modalias>usb:v0F4Ep0100*</modalias>
+
+    <!-- Device: 0F4E:0111 -->
+    <!-- FreedomScientific [PAC Mate] -->
+    <modalias>usb:v0F4Ep0111*</modalias>
+
+    <!-- Device: 0F4E:0112 -->
+    <!-- FreedomScientific [Focus 2] -->
+    <modalias>usb:v0F4Ep0112*</modalias>
+
+    <!-- Device: 0F4E:0114 -->
+    <!-- FreedomScientific [Focus 3+] -->
+    <modalias>usb:v0F4Ep0114*</modalias>
+
+    <!-- Device: 10C4:EA60 -->
+    <!-- Generic Identifier -->
+    <!-- Vendor: Cygnal Integrated Products, Inc. -->
+    <!-- Product: CP210x UART Bridge / myAVR mySmartUSB light -->
+    <!-- BrailleMemo [Pocket] -->
+    <!-- Seika [Braille Display] -->
+    <modalias>usb:v10C4pEA60*</modalias>
+
+    <!-- Device: 10C4:EA80 -->
+    <!-- Generic Identifier -->
+    <!-- Vendor: Cygnal Integrated Products, Inc. -->
+    <!-- Product: CP210x UART Bridge -->
+    <!-- Seika [Note Taker] -->
+    <modalias>usb:v10C4pEA80*</modalias>
+
+    <!-- Device: 1148:0301 -->
+    <!-- BrailleMemo [Smart] -->
+    <modalias>usb:v1148p0301*</modalias>
+
+    <!-- Device: 1209:ABC0 -->
+    <!-- Inceptor [all models] -->
+    <modalias>usb:v1209pABC0*</modalias>
+
+    <!-- Device: 16C0:05E1 -->
+    <!-- Canute [all models] -->
+    <modalias>usb:v16C0p05E1*</modalias>
+
+    <!-- Device: 1A86:7523 -->
+    <!-- Generic Identifier -->
+    <!-- Vendor: Jiangsu QinHeng, Ltd. -->
+    <!-- Product: CH341 USB Bridge Controller -->
+    <!-- Baum [NLS eReader Zoomax (20 cells)] -->
+    <modalias>usb:v1A86p7523*</modalias>
+
+    <!-- Device: 1C71:C004 -->
+    <!-- BrailleNote [HumanWare APEX] -->
+    <modalias>usb:v1C71pC004*</modalias>
+
+    <!-- Device: 1C71:C005 -->
+    <!-- HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)] -->
+    <modalias>usb:v1C71pC005*</modalias>
+
+    <!-- Device: 1C71:C006 -->
+    <!-- HumanWare [non-Touch models (HID protocol)] -->
+    <modalias>usb:v1C71pC006*</modalias>
+
+    <!-- Device: 1C71:C00A -->
+    <!-- HumanWare [BrailleNote Touch (HID protocol)] -->
+    <modalias>usb:v1C71pC00A*</modalias>
+
+    <!-- Device: 1C71:C021 -->
+    <!-- HumanWare [Brailliant BI 14 (serial protocol)] -->
+    <modalias>usb:v1C71pC021*</modalias>
+
+    <!-- Device: 1C71:C101 -->
+    <!-- HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pC101*</modalias>
+
+    <!-- Device: 1C71:C104 -->
+    <!-- HumanWare [APH Chameleon 20 (serial protocol)] -->
+    <modalias>usb:v1C71pC104*</modalias>
+
+    <!-- Device: 1C71:C111 -->
+    <!-- HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pC111*</modalias>
+
+    <!-- Device: 1C71:C114 -->
+    <!-- HumanWare [APH Mantis Q40 (serial protocol)] -->
+    <modalias>usb:v1C71pC114*</modalias>
+
+    <!-- Device: 1C71:C121 -->
+    <!-- HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pC121*</modalias>
+
+    <!-- Device: 1C71:C124 -->
+    <!-- HumanWare [Humanware BrailleOne (serial protocol)] -->
+    <modalias>usb:v1C71pC124*</modalias>
+
+    <!-- Device: 1C71:C131 -->
+    <!-- HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pC131*</modalias>
+
+    <!-- Device: 1C71:C141 -->
+    <!-- HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pC141*</modalias>
+
+    <!-- Device: 1C71:CE01 -->
+    <!-- HumanWare [NLS eReader (HID protocol, firmware 1.0)] -->
+    <!-- HumanWare [NLS eReader (HID protocol, firmware 1.1)] -->
+    <modalias>usb:v1C71pCE01*</modalias>
+
+    <!-- Device: 1C71:CE04 -->
+    <!-- HumanWare [NLS eReader (serial protocol)] -->
+    <modalias>usb:v1C71pCE04*</modalias>
+
+    <!-- Device: 1FE4:0003 -->
+    <!-- HandyTech [USB-HID adapter] -->
+    <modalias>usb:v1FE4p0003*</modalias>
+
+    <!-- Device: 1FE4:0044 -->
+    <!-- HandyTech [Easy Braille (HID)] -->
+    <modalias>usb:v1FE4p0044*</modalias>
+
+    <!-- Device: 1FE4:0054 -->
+    <!-- HandyTech [Active Braille] -->
+    <modalias>usb:v1FE4p0054*</modalias>
+
+    <!-- Device: 1FE4:0055 -->
+    <!-- HandyTech [Connect Braille 40] -->
+    <modalias>usb:v1FE4p0055*</modalias>
+
+    <!-- Device: 1FE4:0061 -->
+    <!-- HandyTech [Actilino] -->
+    <modalias>usb:v1FE4p0061*</modalias>
+
+    <!-- Device: 1FE4:0064 -->
+    <!-- HandyTech [Active Star 40] -->
+    <modalias>usb:v1FE4p0064*</modalias>
+
+    <!-- Device: 1FE4:0074 -->
+    <!-- HandyTech [Braille Star 40 (HID)] -->
+    <modalias>usb:v1FE4p0074*</modalias>
+
+    <!-- Device: 1FE4:0081 -->
+    <!-- HandyTech [Basic Braille 16] -->
+    <modalias>usb:v1FE4p0081*</modalias>
+
+    <!-- Device: 1FE4:0082 -->
+    <!-- HandyTech [Basic Braille 20] -->
+    <modalias>usb:v1FE4p0082*</modalias>
+
+    <!-- Device: 1FE4:0083 -->
+    <!-- HandyTech [Basic Braille 32] -->
+    <modalias>usb:v1FE4p0083*</modalias>
+
+    <!-- Device: 1FE4:0084 -->
+    <!-- HandyTech [Basic Braille 40] -->
+    <modalias>usb:v1FE4p0084*</modalias>
+
+    <!-- Device: 1FE4:0086 -->
+    <!-- HandyTech [Basic Braille 64] -->
+    <modalias>usb:v1FE4p0086*</modalias>
+
+    <!-- Device: 1FE4:0087 -->
+    <!-- HandyTech [Basic Braille 80] -->
+    <modalias>usb:v1FE4p0087*</modalias>
+
+    <!-- Device: 1FE4:008A -->
+    <!-- HandyTech [Basic Braille 48] -->
+    <modalias>usb:v1FE4p008A*</modalias>
+
+    <!-- Device: 1FE4:008B -->
+    <!-- HandyTech [Basic Braille 160] -->
+    <modalias>usb:v1FE4p008B*</modalias>
+
+    <!-- Device: 1FE4:00A4 -->
+    <!-- HandyTech [Activator] -->
+    <modalias>usb:v1FE4p00A4*</modalias>
+
+    <!-- Device: 4242:0001 -->
+    <!-- Pegasus [all models] -->
+    <modalias>usb:v4242p0001*</modalias>
+
+    <!-- Device: C251:1122 -->
+    <!-- EuroBraille [Esys (version < 3.0, no SD card)] -->
+    <modalias>usb:vC251p1122*</modalias>
+
+    <!-- Device: C251:1123 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1123*</modalias>
+
+    <!-- Device: C251:1124 -->
+    <!-- EuroBraille [Esys (version < 3.0, with SD card)] -->
+    <modalias>usb:vC251p1124*</modalias>
+
+    <!-- Device: C251:1125 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1125*</modalias>
+
+    <!-- Device: C251:1126 -->
+    <!-- EuroBraille [Esys (version >= 3.0, no SD card)] -->
+    <modalias>usb:vC251p1126*</modalias>
+
+    <!-- Device: C251:1127 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1127*</modalias>
+
+    <!-- Device: C251:1128 -->
+    <!-- EuroBraille [Esys (version >= 3.0, with SD card)] -->
+    <modalias>usb:vC251p1128*</modalias>
+
+    <!-- Device: C251:1129 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1129*</modalias>
+
+    <!-- Device: C251:112A -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112A*</modalias>
+
+    <!-- Device: C251:112B -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112B*</modalias>
+
+    <!-- Device: C251:112C -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112C*</modalias>
+
+    <!-- Device: C251:112D -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112D*</modalias>
+
+    <!-- Device: C251:112E -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112E*</modalias>
+
+    <!-- Device: C251:112F -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p112F*</modalias>
+
+    <!-- Device: C251:1130 -->
+    <!-- EuroBraille [Esytime (firmware 1.03, 2014-03-31)] -->
+    <!-- EuroBraille [Esytime] -->
+    <modalias>usb:vC251p1130*</modalias>
+
+    <!-- Device: C251:1131 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1131*</modalias>
+
+    <!-- Device: C251:1132 -->
+    <!-- EuroBraille [reserved] -->
+    <modalias>usb:vC251p1132*</modalias>
+
+    <!-- END_USB_BRAILLE_DEVICES -->
+  </provides>
+</component>
diff --git a/Autostart/Hotplug/brltty b/Autostart/Hotplug/brltty
new file mode 100755
index 0000000..771100e
--- /dev/null
+++ b/Autostart/Hotplug/brltty
@@ -0,0 +1,49 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+programName="${0##*/}"
+
+programMessage() {
+   echo >&2 "${programName}: ${1}"
+}
+
+programError() {
+   [ -n "${1}" ] && programMessage "${1}"
+   exit "${2:-2}"
+}
+
+[ -n "${ACTION}" ] || programError "action not specified."
+case "${ACTION}"
+in
+   add)
+      driver="auto"
+      device="usb:"
+
+      brltty -b"${driver}" -d"${device}" && {
+         [ -n "${REMOVER}" ] && ln -s -f -- "${0}" "${REMOVER}"
+      }
+      ;;
+
+   remove)
+      ;;
+
+   *) programError "action not supported: ${ACTION}"
+esac
+
+exit 0
diff --git a/Autostart/Hotplug/brltty.usermap b/Autostart/Hotplug/brltty.usermap
new file mode 100644
index 0000000..6155cac
--- /dev/null
+++ b/Autostart/Hotplug/brltty.usermap
@@ -0,0 +1,528 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BEGIN_USB_BRAILLE_DEVICES
+
+# Device: 0403:6001
+# Generic Identifier
+# Vendor: Future Technology Devices International, Ltd.
+# Product: FT232 USB-Serial (UART) IC
+# Albatross [all models]
+# Cebra [all models]
+# HIMS [Sync Braille]
+# HandyTech [FTDI chip]
+# Hedo [MobilLine]
+# MDV [all models]
+brltty 0x0003 0x0403 0x6001
+
+# Device: 0403:6010
+# Generic Identifier
+# Vendor: Future Technology Devices International, Ltd
+# Product: FT2232C/D/H Dual UART/FIFO IC
+# DotPad [all models]
+brltty 0x0003 0x0403 0x6010
+
+# Device: 0403:DE58
+# Hedo [MobilLine]
+brltty 0x0003 0x0403 0xde58
+
+# Device: 0403:DE59
+# Hedo [ProfiLine]
+brltty 0x0003 0x0403 0xde59
+
+# Device: 0403:F208
+# Papenmeier [all models]
+brltty 0x0003 0x0403 0xf208
+
+# Device: 0403:FE70
+# Baum [Vario 40 (40 cells)]
+brltty 0x0003 0x0403 0xfe70
+
+# Device: 0403:FE71
+# Baum [PocketVario (24 cells)]
+brltty 0x0003 0x0403 0xfe71
+
+# Device: 0403:FE72
+# Baum [SuperVario 40 (40 cells)]
+brltty 0x0003 0x0403 0xfe72
+
+# Device: 0403:FE73
+# Baum [SuperVario 32 (32 cells)]
+brltty 0x0003 0x0403 0xfe73
+
+# Device: 0403:FE74
+# Baum [SuperVario 64 (64 cells)]
+brltty 0x0003 0x0403 0xfe74
+
+# Device: 0403:FE75
+# Baum [SuperVario 80 (80 cells)]
+brltty 0x0003 0x0403 0xfe75
+
+# Device: 0403:FE76
+# Baum [VarioPro 80 (80 cells)]
+brltty 0x0003 0x0403 0xfe76
+
+# Device: 0403:FE77
+# Baum [VarioPro 64 (64 cells)]
+brltty 0x0003 0x0403 0xfe77
+
+# Device: 0452:0100
+# Metec [all models]
+brltty 0x0003 0x0452 0x0100
+
+# Device: 045E:930A
+# HIMS [Braille Sense (USB 1.1)]
+# HIMS [Braille Sense (USB 2.0)]
+# HIMS [Braille Sense U2 (USB 2.0)]
+brltty 0x0003 0x045e 0x930a
+
+# Device: 045E:930B
+# HIMS [Braille Edge and QBrailleXL]
+brltty 0x0003 0x045e 0x930b
+
+# Device: 0483:A1D3
+# Baum [Orbit Reader 20 (20 cells)]
+brltty 0x0003 0x0483 0xa1d3
+
+# Device: 0483:A366
+# Baum [Orbit Reader 40 (40 cells)]
+brltty 0x0003 0x0483 0xa366
+
+# Device: 06B0:0001
+# Alva [Satellite (5nn)]
+brltty 0x0003 0x06b0 0x0001
+
+# Device: 0798:0001
+# Voyager [all models]
+brltty 0x0003 0x0798 0x0001
+
+# Device: 0798:0600
+# Alva [Voyager Protocol Converter]
+brltty 0x0003 0x0798 0x0600
+
+# Device: 0798:0624
+# Alva [BC624]
+brltty 0x0003 0x0798 0x0624
+
+# Device: 0798:0640
+# Alva [BC640]
+brltty 0x0003 0x0798 0x0640
+
+# Device: 0798:0680
+# Alva [BC680]
+brltty 0x0003 0x0798 0x0680
+
+# Device: 0904:1016
+# FrankAudiodata [B2K84 (before firmware installation)]
+brltty 0x0003 0x0904 0x1016
+
+# Device: 0904:1017
+# FrankAudiodata [B2K84 (after firmware installation)]
+brltty 0x0003 0x0904 0x1017
+
+# Device: 0904:2000
+# Baum [VarioPro 40 (40 cells)]
+brltty 0x0003 0x0904 0x2000
+
+# Device: 0904:2001
+# Baum [EcoVario 24 (24 cells)]
+brltty 0x0003 0x0904 0x2001
+
+# Device: 0904:2002
+# Baum [EcoVario 40 (40 cells)]
+brltty 0x0003 0x0904 0x2002
+
+# Device: 0904:2007
+# Baum [VarioConnect 40 (40 cells)]
+brltty 0x0003 0x0904 0x2007
+
+# Device: 0904:2008
+# Baum [VarioConnect 32 (32 cells)]
+brltty 0x0003 0x0904 0x2008
+
+# Device: 0904:2009
+# Baum [VarioConnect 24 (24 cells)]
+brltty 0x0003 0x0904 0x2009
+
+# Device: 0904:2010
+# Baum [VarioConnect 64 (64 cells)]
+brltty 0x0003 0x0904 0x2010
+
+# Device: 0904:2011
+# Baum [VarioConnect 80 (80 cells)]
+brltty 0x0003 0x0904 0x2011
+
+# Device: 0904:2014
+# Baum [EcoVario 32 (32 cells)]
+brltty 0x0003 0x0904 0x2014
+
+# Device: 0904:2015
+# Baum [EcoVario 64 (64 cells)]
+brltty 0x0003 0x0904 0x2015
+
+# Device: 0904:2016
+# Baum [EcoVario 80 (80 cells)]
+brltty 0x0003 0x0904 0x2016
+
+# Device: 0904:3000
+# Baum [Refreshabraille 18 (18 cells)]
+brltty 0x0003 0x0904 0x3000
+
+# Device: 0904:3001
+# Baum [Orbit in Refreshabraille Emulation Mode (18 cells)]
+# Baum [Refreshabraille 18 (18 cells)]
+brltty 0x0003 0x0904 0x3001
+
+# Device: 0904:4004
+# Baum [Pronto! V3 18 (18 cells)]
+brltty 0x0003 0x0904 0x4004
+
+# Device: 0904:4005
+# Baum [Pronto! V3 40 (40 cells)]
+brltty 0x0003 0x0904 0x4005
+
+# Device: 0904:4007
+# Baum [Pronto! V4 18 (18 cells)]
+brltty 0x0003 0x0904 0x4007
+
+# Device: 0904:4008
+# Baum [Pronto! V4 40 (40 cells)]
+brltty 0x0003 0x0904 0x4008
+
+# Device: 0904:6001
+# Baum [SuperVario2 40 (40 cells)]
+brltty 0x0003 0x0904 0x6001
+
+# Device: 0904:6002
+# Baum [PocketVario2 (24 cells)]
+brltty 0x0003 0x0904 0x6002
+
+# Device: 0904:6003
+# Baum [SuperVario2 32 (32 cells)]
+brltty 0x0003 0x0904 0x6003
+
+# Device: 0904:6004
+# Baum [SuperVario2 64 (64 cells)]
+brltty 0x0003 0x0904 0x6004
+
+# Device: 0904:6005
+# Baum [SuperVario2 80 (80 cells)]
+brltty 0x0003 0x0904 0x6005
+
+# Device: 0904:6006
+# Baum [Brailliant2 40 (40 cells)]
+brltty 0x0003 0x0904 0x6006
+
+# Device: 0904:6007
+# Baum [Brailliant2 24 (24 cells)]
+brltty 0x0003 0x0904 0x6007
+
+# Device: 0904:6008
+# Baum [Brailliant2 32 (32 cells)]
+brltty 0x0003 0x0904 0x6008
+
+# Device: 0904:6009
+# Baum [Brailliant2 64 (64 cells)]
+brltty 0x0003 0x0904 0x6009
+
+# Device: 0904:600A
+# Baum [Brailliant2 80 (80 cells)]
+brltty 0x0003 0x0904 0x600a
+
+# Device: 0904:6011
+# Baum [VarioConnect 24 (24 cells)]
+brltty 0x0003 0x0904 0x6011
+
+# Device: 0904:6012
+# Baum [VarioConnect 32 (32 cells)]
+brltty 0x0003 0x0904 0x6012
+
+# Device: 0904:6013
+# Baum [VarioConnect 40 (40 cells)]
+brltty 0x0003 0x0904 0x6013
+
+# Device: 0904:6101
+# Baum [VarioUltra 20 (20 cells)]
+brltty 0x0003 0x0904 0x6101
+
+# Device: 0904:6102
+# Baum [VarioUltra 40 (40 cells)]
+brltty 0x0003 0x0904 0x6102
+
+# Device: 0904:6103
+# Baum [VarioUltra 32 (32 cells)]
+brltty 0x0003 0x0904 0x6103
+
+# Device: 0921:1200
+# HandyTech [GoHubs chip]
+brltty 0x0003 0x0921 0x1200
+
+# Device: 0F4E:0100
+# FreedomScientific [Focus 1]
+brltty 0x0003 0x0f4e 0x0100
+
+# Device: 0F4E:0111
+# FreedomScientific [PAC Mate]
+brltty 0x0003 0x0f4e 0x0111
+
+# Device: 0F4E:0112
+# FreedomScientific [Focus 2]
+brltty 0x0003 0x0f4e 0x0112
+
+# Device: 0F4E:0114
+# FreedomScientific [Focus 3+]
+brltty 0x0003 0x0f4e 0x0114
+
+# Device: 10C4:EA60
+# Generic Identifier
+# Vendor: Cygnal Integrated Products, Inc.
+# Product: CP210x UART Bridge / myAVR mySmartUSB light
+# BrailleMemo [Pocket]
+# Seika [Braille Display]
+brltty 0x0003 0x10c4 0xea60
+
+# Device: 10C4:EA80
+# Generic Identifier
+# Vendor: Cygnal Integrated Products, Inc.
+# Product: CP210x UART Bridge
+# Seika [Note Taker]
+brltty 0x0003 0x10c4 0xea80
+
+# Device: 1148:0301
+# BrailleMemo [Smart]
+brltty 0x0003 0x1148 0x0301
+
+# Device: 1209:ABC0
+# Inceptor [all models]
+brltty 0x0003 0x1209 0xabc0
+
+# Device: 16C0:05E1
+# Canute [all models]
+brltty 0x0003 0x16c0 0x05e1
+
+# Device: 1A86:7523
+# Generic Identifier
+# Vendor: Jiangsu QinHeng, Ltd.
+# Product: CH341 USB Bridge Controller
+# Baum [NLS eReader Zoomax (20 cells)]
+brltty 0x0003 0x1a86 0x7523
+
+# Device: 1C71:C004
+# BrailleNote [HumanWare APEX]
+brltty 0x0003 0x1c71 0xc004
+
+# Device: 1C71:C005
+# HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]
+brltty 0x0003 0x1c71 0xc005
+
+# Device: 1C71:C006
+# HumanWare [non-Touch models (HID protocol)]
+brltty 0x0003 0x1c71 0xc006
+
+# Device: 1C71:C00A
+# HumanWare [BrailleNote Touch (HID protocol)]
+brltty 0x0003 0x1c71 0xc00a
+
+# Device: 1C71:C021
+# HumanWare [Brailliant BI 14 (serial protocol)]
+brltty 0x0003 0x1c71 0xc021
+
+# Device: 1C71:C101
+# HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)]
+# HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xc101
+
+# Device: 1C71:C104
+# HumanWare [APH Chameleon 20 (serial protocol)]
+brltty 0x0003 0x1c71 0xc104
+
+# Device: 1C71:C111
+# HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)]
+# HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xc111
+
+# Device: 1C71:C114
+# HumanWare [APH Mantis Q40 (serial protocol)]
+brltty 0x0003 0x1c71 0xc114
+
+# Device: 1C71:C121
+# HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)]
+# HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xc121
+
+# Device: 1C71:C124
+# HumanWare [Humanware BrailleOne (serial protocol)]
+brltty 0x0003 0x1c71 0xc124
+
+# Device: 1C71:C131
+# HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)]
+# HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xc131
+
+# Device: 1C71:C141
+# HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)]
+# HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xc141
+
+# Device: 1C71:CE01
+# HumanWare [NLS eReader (HID protocol, firmware 1.0)]
+# HumanWare [NLS eReader (HID protocol, firmware 1.1)]
+brltty 0x0003 0x1c71 0xce01
+
+# Device: 1C71:CE04
+# HumanWare [NLS eReader (serial protocol)]
+brltty 0x0003 0x1c71 0xce04
+
+# Device: 1FE4:0003
+# HandyTech [USB-HID adapter]
+brltty 0x0003 0x1fe4 0x0003
+
+# Device: 1FE4:0044
+# HandyTech [Easy Braille (HID)]
+brltty 0x0003 0x1fe4 0x0044
+
+# Device: 1FE4:0054
+# HandyTech [Active Braille]
+brltty 0x0003 0x1fe4 0x0054
+
+# Device: 1FE4:0055
+# HandyTech [Connect Braille 40]
+brltty 0x0003 0x1fe4 0x0055
+
+# Device: 1FE4:0061
+# HandyTech [Actilino]
+brltty 0x0003 0x1fe4 0x0061
+
+# Device: 1FE4:0064
+# HandyTech [Active Star 40]
+brltty 0x0003 0x1fe4 0x0064
+
+# Device: 1FE4:0074
+# HandyTech [Braille Star 40 (HID)]
+brltty 0x0003 0x1fe4 0x0074
+
+# Device: 1FE4:0081
+# HandyTech [Basic Braille 16]
+brltty 0x0003 0x1fe4 0x0081
+
+# Device: 1FE4:0082
+# HandyTech [Basic Braille 20]
+brltty 0x0003 0x1fe4 0x0082
+
+# Device: 1FE4:0083
+# HandyTech [Basic Braille 32]
+brltty 0x0003 0x1fe4 0x0083
+
+# Device: 1FE4:0084
+# HandyTech [Basic Braille 40]
+brltty 0x0003 0x1fe4 0x0084
+
+# Device: 1FE4:0086
+# HandyTech [Basic Braille 64]
+brltty 0x0003 0x1fe4 0x0086
+
+# Device: 1FE4:0087
+# HandyTech [Basic Braille 80]
+brltty 0x0003 0x1fe4 0x0087
+
+# Device: 1FE4:008A
+# HandyTech [Basic Braille 48]
+brltty 0x0003 0x1fe4 0x008a
+
+# Device: 1FE4:008B
+# HandyTech [Basic Braille 160]
+brltty 0x0003 0x1fe4 0x008b
+
+# Device: 1FE4:00A4
+# HandyTech [Activator]
+brltty 0x0003 0x1fe4 0x00a4
+
+# Device: 4242:0001
+# Pegasus [all models]
+brltty 0x0003 0x4242 0x0001
+
+# Device: C251:1122
+# EuroBraille [Esys (version < 3.0, no SD card)]
+brltty 0x0003 0xc251 0x1122
+
+# Device: C251:1123
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1123
+
+# Device: C251:1124
+# EuroBraille [Esys (version < 3.0, with SD card)]
+brltty 0x0003 0xc251 0x1124
+
+# Device: C251:1125
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1125
+
+# Device: C251:1126
+# EuroBraille [Esys (version >= 3.0, no SD card)]
+brltty 0x0003 0xc251 0x1126
+
+# Device: C251:1127
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1127
+
+# Device: C251:1128
+# EuroBraille [Esys (version >= 3.0, with SD card)]
+brltty 0x0003 0xc251 0x1128
+
+# Device: C251:1129
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1129
+
+# Device: C251:112A
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112a
+
+# Device: C251:112B
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112b
+
+# Device: C251:112C
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112c
+
+# Device: C251:112D
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112d
+
+# Device: C251:112E
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112e
+
+# Device: C251:112F
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x112f
+
+# Device: C251:1130
+# EuroBraille [Esytime (firmware 1.03, 2014-03-31)]
+# EuroBraille [Esytime]
+brltty 0x0003 0xc251 0x1130
+
+# Device: C251:1131
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1131
+
+# Device: C251:1132
+# EuroBraille [reserved]
+brltty 0x0003 0xc251 0x1132
+
+# END_USB_BRAILLE_DEVICES
diff --git a/Autostart/Systemd/Makefile.in b/Autostart/Systemd/Makefile.in
new file mode 100644
index 0000000..361c246
--- /dev/null
+++ b/Autostart/Systemd/Makefile.in
@@ -0,0 +1,74 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+SYSTEMD_UNITS_DIRECTORY = $(INSTALL_ROOT)$(SYSTEMD_UNIT_DIR)
+SYSTEMD_USERS_DIRECTORY = $(INSTALL_ROOT)$(SYSTEMD_SYSUSERS_DIR)
+SYSTEMD_USERS_FILE = $(SYSTEMD_USERS_DIRECTORY)/$(PACKAGE_TARNAME).conf
+
+SYSTEMD_FILES_DIRECTORY = $(INSTALL_ROOT)$(SYSTEMD_TMPFILES_DIR)
+SYSTEMD_FILES_FILE = $(SYSTEMD_FILES_DIRECTORY)/$(PACKAGE_TARNAME).conf
+
+install: install-units install-wrapper install-users install-files
+
+install-units-directory:
+	$(INSTALL_DIRECTORY) $(SYSTEMD_UNITS_DIRECTORY)
+
+install-units: install-units-directory
+	$(INSTALL_DATA) $(BLD_DIR)/brltty@.service $(SYSTEMD_UNITS_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brltty-device@.service $(SYSTEMD_UNITS_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brltty@.path $(SYSTEMD_UNITS_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brltty.path $(SYSTEMD_UNITS_DIRECTORY)
+
+install-wrapper: install-commands-directory
+	$(INSTALL_SCRIPT) $(SRC_DIR)/systemd-wrapper $(INSTALL_COMMANDS_DIRECTORY)
+
+install-users-directory:
+	$(INSTALL_DIRECTORY) $(SYSTEMD_USERS_DIRECTORY)
+
+install-users: install-users-directory
+	$(INSTALL_DATA) $(SRC_DIR)/sysusers $(SYSTEMD_USERS_FILE)
+
+install-files-directory:
+	$(INSTALL_DIRECTORY) $(SYSTEMD_FILES_DIRECTORY)
+
+install-files: install-files-directory
+	$(INSTALL_DATA) $(BLD_DIR)/tmpfiles $(SYSTEMD_FILES_FILE)
+
+uninstall:
+	-rm -f $(SYSTEMD_UNITS_DIRECTORY)/$(PACKAGE_TARNAME).*
+	-rm -f $(SYSTEMD_UNITS_DIRECTORY)/$(PACKAGE_TARNAME)@.*
+	-rm -f $(SYSTEMD_UNITS_DIRECTORY)/$(PACKAGE_TARNAME)-*
+	-rm -f $(INSTALL_COMMANDS_DIRECTORY)/systemd-*
+	-rm -f $(SYSTEMD_USERS_FILE)
+	-rm -f $(SYSTEMD_FILES_FILE)
+
+distclean::
+	-rm brltty@.service
+	-rm tmpfiles
+
+reload: reload-units reload-users reload-files
+
+reload-units:
+	systemctl daemon-reload
+
+reload-users:
+	systemd-sysusers $(SYSTEMD_USERS_FILE)
+
+reload-files:
+	systemd-tmpfiles --create $(SYSTEMD_FILES_FILE)
+
diff --git a/Autostart/Systemd/brltty-device@.service b/Autostart/Systemd/brltty-device@.service
new file mode 100644
index 0000000..0151348
--- /dev/null
+++ b/Autostart/Systemd/brltty-device@.service
@@ -0,0 +1,33 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+[Unit]
+Description=Braille Device: %I
+Documentation=man:brltty(1)
+
+BindsTo=%i.device
+Requires=brltty@-%i.service
+
+RefuseManualStart=true
+RefuseManualStop=true
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/true
+RemainAfterExit=true
+
diff --git a/Autostart/Systemd/brltty.path b/Autostart/Systemd/brltty.path
new file mode 100644
index 0000000..6b180c0
--- /dev/null
+++ b/Autostart/Systemd/brltty.path
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+[Unit]
+Description=Default BRLTTY Instance
+Documentation=man:brltty(1)
+
+DefaultDependencies=no
+After=local-fs.target
+Requires=brltty@-etc-brltty.conf.service
+
+
+[Path]
+PathExists=/etc/brltty.conf
+Unit=brltty@-etc-brltty.conf.service
+
+
+[Install]
+WantedBy=default.target multi-user.target rescue.target emergency.target
diff --git a/Autostart/Systemd/brltty@.path b/Autostart/Systemd/brltty@.path
new file mode 100644
index 0000000..b681a44
--- /dev/null
+++ b/Autostart/Systemd/brltty@.path
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+[Unit]
+Description=BRLTTY (using configuration file /etc/brltty_%I.conf)
+Documentation=man:brltty(1)
+
+DefaultDependencies=no
+After=local-fs.target
+Requires=brltty@-etc-brltty_%i.conf.service
+
+
+[Path]
+PathExists=/etc/brltty_%I.conf
+Unit=brltty@-etc-brltty_%i.conf.service
+
+
+[Install]
+WantedBy=default.target multi-user.target rescue.target emergency.target
diff --git a/Autostart/Systemd/brltty@.service.in b/Autostart/Systemd/brltty@.service.in
new file mode 100644
index 0000000..4a011cc
--- /dev/null
+++ b/Autostart/Systemd/brltty@.service.in
@@ -0,0 +1,108 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+[Unit]
+Description=BRLTTY Instance: %I
+Documentation=man:brltty(1)
+Documentation=@PACKAGE_URL@
+
+RequiresMountsFor=@WRITABLE_DIRECTORY@
+RequiresMountsFor=@UPDATABLE_DIRECTORY@
+RequiresMountsFor=@BRLAPI_SOCKETPATH@
+
+Wants=sound.target
+After=sound.target
+
+After=local-fs.target
+ConditionPathExists=%I
+
+RefuseManualStart=true
+StopWhenUnneeded=true
+
+
+[Service]
+
+SyslogIdentifier=brltty
+Environment="BRLTTY_SYSTEMD_INSTANCE=%I"
+
+StandardInput=null
+StandardOutput=null
+StandardError=inherit
+
+PIDFile=%t/brltty/brltty-%i.pid
+Environment="BRLTTY_PID_FILE=%t/brltty/brltty-%i.pid"
+
+Type=@SYSTEMD_SERVICE_TYPE@
+Environment="BRLTTY_EXECUTABLE_ARGUMENTS=@SYSTEMD_SERVICE_ARGUMENTS@"
+ExecStart=@COMMANDS_DIRECTORY@/systemd-wrapper
+
+TimeoutStartSec=5
+TimeoutStopSec=10
+
+Restart=on-failure
+RestartSec=30
+
+Nice=-10
+OOMScoreAdjust=-900
+
+# for playing alert tunes via the built-in PC speaker (KDMKTONE, KIOCSOUND)
+ExecStartPre=-+/sbin/modprobe pcspkr
+AmbientCapabilities=cap_sys_tty_config
+
+# for creating virtual devices
+ExecStartPre=-+/sbin/modprobe uinput
+
+# for injecting input characters typed on a braille device (TIOCSTI)
+AmbientCapabilities=cap_sys_admin
+
+# for creating needed but missing special device files
+AmbientCapabilities=cap_mknod
+
+#######################################################
+# keep these synchronized with sysusers.d/brltty.conf #
+#######################################################
+
+User=brltty
+
+# for reading screen content (/dev/vcs*)
+# for virtual console monitoring and control (/dev/tty<n>)
+SupplementaryGroups=tty
+
+# for serial I/O (/dev/ttyS<n>)
+# probably only one of these should be uncommented
+SupplementaryGroups=dialout
+#SupplementaryGroups=uucp
+
+# for USB I/o via USBFS (/dev/bus/usb/*/*)
+SupplementaryGroups=root
+
+# for playing sound via the ALSA framework
+SupplementaryGroups=audio
+
+# for playing sound via the Pulse Audio daemon
+SupplementaryGroups=pulse-access
+
+# for monitoring keyboard input (/dev/input/*)
+SupplementaryGroups=input
+
+# for creating virtual devices (/dev/uinput or /dev/input/uinput)
+SupplementaryGroups=root
+
+# for reading the BrlAPI authorization key file
+SupplementaryGroups=brlapi
+
diff --git a/Autostart/Systemd/systemd-wrapper b/Autostart/Systemd/systemd-wrapper
new file mode 100755
index 0000000..02f5d01
--- /dev/null
+++ b/Autostart/Systemd/systemd-wrapper
@@ -0,0 +1,108 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+readonly defaultPath="/etc/brltty.conf"
+
+readonly programPrefix="brltty-"
+programName="${0##*/}"
+[ "${programName#${programPrefix}}" = "${programName}" ] && programName="${programPrefix}${programName}"
+readonly programName
+
+programMessage() {
+  local message="${1}"
+  local level="${2}"
+
+  logger -p "daemon.${level:-info}" -t "${programName}" -- "${1}" &&
+  [ ! -t 2 ] ||
+  echo >&2 "${programName}: ${message}"
+}
+
+errorMessage() {
+  local message="${1}"
+  local code="${2}"
+
+  programMessage "${message}" err
+  exit "${code:-1}"
+}
+
+syntaxError() {
+  local message="${1}"
+
+  errorMessage "${message}" 2
+}
+
+semanticError() {
+  local message="${1}"
+
+  errorMessage "${message}" 3
+}
+
+instancePath="${BRLTTY_SYSTEMD_INSTANCE:-${defaultPath}}"
+[ "${instancePath#/}" = "${instancePath}" ] && instancePath="/${instancePath}"
+readonly instancePath
+
+if [ "${instancePath#/sys/}" != "${instancePath}" ]
+then
+  isDeviceInstance=true
+  udevOption="path"
+elif [ -c "${instancePath}" ]
+then
+  isDeviceInstance=true
+  udevOption="name"
+elif [ -f "${instancePath}" ]
+then
+  isDeviceInstance=false
+elif [ -e "${instancePath}" ]
+then
+  semanticError "unsupported instance path: ${instancePath}"
+else
+  semanticError "instance path not found: ${instancePath}"
+fi
+
+if "${isDeviceInstance}"
+then
+  isUdevManaged=false
+
+  while read line
+  do
+    if [[ "${line}" =~ ^'N: ' ]]
+    then
+      isUdevManaged=true
+    elif [[ "${line}" =~ ^'E: '([^ =]+)=(.*) ]]
+    then
+      name="${BASH_REMATCH[1]}"
+      value="${BASH_REMATCH[2]}"
+
+      [[ "${name}" =~ ^'BRLTTY_' ]] || continue
+      [ -z "${!name}" ] || continue
+
+      export "${name}=${value}"
+    fi
+  done < <((udevadm info --"${udevOption}"="${instancePath}" --export 2>/dev/null))
+
+  "${isUdevManaged}" || semanticError "instance path not managed by udev: ${instancePath}"
+else
+  export BRLTTY_CONFIGURATION_FILE="${instancePath}"
+fi
+
+set -- "${BRLTTY_EXECUTABLE_PATH:-brltty}" -E ${BRLTTY_EXECUTABLE_ARGUMENTS} "${@}"
+programMessage "executing command: ${*}" info
+exec "${@}"
+exit "${?}"
diff --git a/Autostart/Systemd/sysusers b/Autostart/Systemd/sysusers
new file mode 100644
index 0000000..b3c1361
--- /dev/null
+++ b/Autostart/Systemd/sysusers
@@ -0,0 +1,52 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+################################################
+# keep these synchronized with brltty@.service #
+################################################
+
+u brltty - "Braille Device Daemon" /var/lib/brltty
+
+# for reading screen content (/dev/vcs*)
+# for virtual console monitoring and control (/dev/tty<n>)
+m brltty tty
+
+# for serial I/O (/dev/ttyS<n>)
+# probably only one of these should be uncommented
+m brltty dialout
+#m brltty uucp
+
+# for USB I/o (/dev/bus/usb/*/*)
+m brltty root
+
+# for playing sound via the ALSA framework
+m brltty audio
+
+# for playing sound via the Pulse Audio daemon
+m brltty pulse-access
+
+# for monitoring keyboard input (/dev/input/*)
+m brltty input
+
+# for creating virtual devices (/dev/uinput)
+m brltty root
+
+# for reading the BrlAPI authorization key file
+g brlapi
+m brltty brlapi
+
diff --git a/Autostart/Systemd/tmpfiles.in b/Autostart/Systemd/tmpfiles.in
new file mode 100644
index 0000000..150e0b5
--- /dev/null
+++ b/Autostart/Systemd/tmpfiles.in
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY's runtime directory
+d @WRITABLE_DIRECTORY@ 0750 brltty brltty
+
+# BRLTTY's local state directory
+d @UPDATABLE_DIRECTORY@ 2770 brltty brltty
+
+# BrlAPI's sockets directory
+d @BRLAPI_SOCKETPATH@ 3777 brltty brltty
+
+# BrlAPI's authorization key file
+z @CONFIGURATION_DIRECTORY@/@api_authkeyfile@ 0640 root brlapi
+
diff --git a/Autostart/Udev/Makefile.in b/Autostart/Udev/Makefile.in
new file mode 100644
index 0000000..fa1767b
--- /dev/null
+++ b/Autostart/Udev/Makefile.in
@@ -0,0 +1,85 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+UDEV_RULES_SEQUENCE = 90
+UDEV_PARENT_LOCATION = $(libdir)
+#UDEV_PARENT_LOCATION = $(sysconfdir)
+
+UDEV_RULES_LOCATION = $(UDEV_PARENT_LOCATION)/udev/rules.d
+UDEV_RULES_DIRECTORY = $(INSTALL_ROOT)$(UDEV_RULES_LOCATION)
+
+UDEV_PATH_PREFIX = $(UDEV_RULES_DIRECTORY)/$(UDEV_RULES_SEQUENCE)-$(PACKAGE_TARNAME)-
+UDEV_RULES_EXTENSION = rules
+
+UDEV_NAME_HID = hid.$(UDEV_RULES_EXTENSION)
+UDEV_NAME_UINPUT = uinput.$(UDEV_RULES_EXTENSION)
+
+UPDUSBDEVS = $(SRC_TOP)Tools/updusbdevs -quiet -quiet
+UDEV_NAME_USB_TEMPLATE = usb-template.$(UDEV_RULES_EXTENSION)
+UDEV_NAME_USB_CUSTOMIZED = usb-customized.$(UDEV_RULES_EXTENSION)
+UDEV_NAME_USB_GENERIC = usb-generic.$(UDEV_RULES_EXTENSION)
+
+all: usb-customized usb-generic
+
+usb-customized:
+	cp $(UDEV_NAME_USB_TEMPLATE) $(UDEV_NAME_USB_CUSTOMIZED)
+	$(UPDUSBDEVS) -nogeneric udev:$(UDEV_NAME_USB_CUSTOMIZED)
+
+usb-generic:
+	cp $(UDEV_NAME_USB_TEMPLATE) $(UDEV_NAME_USB_GENERIC)
+	$(UPDUSBDEVS) -onlygeneric udev:$(UDEV_NAME_USB_GENERIC)
+
+install-wrapper: install-commands-directory
+	$(INSTALL_SCRIPT) $(SRC_DIR)/udev-wrapper $(INSTALL_COMMANDS_DIRECTORY)
+
+install-rules-directory:
+	$(INSTALL_DIRECTORY) $(UDEV_RULES_DIRECTORY)
+
+install-rules: install-rules-hid install-rules-uinput install-rules-usb
+
+install-rules-hid: install-rules-directory
+	$(INSTALL_DATA) $(SRC_DIR)/$(UDEV_NAME_HID) $(UDEV_PATH_PREFIX)$(UDEV_NAME_HID)
+
+install-rules-uinput: install-rules-directory
+	$(INSTALL_DATA) $(SRC_DIR)/$(UDEV_NAME_UINPUT) $(UDEV_PATH_PREFIX)$(UDEV_NAME_UINPUT)
+
+install-rules-usb-customized: usb-customized install-rules-directory
+	$(INSTALL_DATA) $(BLD_DIR)/$(UDEV_NAME_USB_CUSTOMIZED) $(UDEV_PATH_PREFIX)$(UDEV_NAME_USB_CUSTOMIZED)
+
+install-rules-usb-generic: usb-generic install-rules-directory
+	$(INSTALL_DATA) $(BLD_DIR)/$(UDEV_NAME_USB_GENERIC) $(UDEV_PATH_PREFIX)$(UDEV_NAME_USB_GENERIC)
+
+install-rules-usb: install-rules-usb-customized install-rules-usb-generic
+
+install: install-wrapper install-rules
+
+uninstall:
+	-rm -f $(UDEV_RULES_DIRECTORY)/?*-$(PACKAGE_TARNAME).$(UDEV_RULES_EXTENSION)
+	-rm -f $(UDEV_RULES_DIRECTORY)/?*-$(PACKAGE_TARNAME)-*.$(UDEV_RULES_EXTENSION)
+	-rm -f $(INSTALL_COMMANDS_DIRECTORY)/udev-*
+
+clean::
+	-rm -f $(UDEV_NAME_USB_CUSTOMIZED)
+	-rm -f $(UDEV_NAME_USB_GENERIC)
+
+distclean:: clean
+	-rm -f $(UDEV_NAME_USB_TEMPLATE)
+
+reload:
+	udevadm control --reload
+
diff --git a/Autostart/Udev/hid.rules b/Autostart/Udev/hid.rules
new file mode 100644
index 0000000..ef26d83
--- /dev/null
+++ b/Autostart/Udev/hid.rules
@@ -0,0 +1,51 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+##############################################################
+# Udev rules for giving BRLTTY access to HID braille devices #
+##############################################################
+
+#############################################################################
+# BRLTTY is now able to switch to running as an unprivileged user, using    #
+# only a few capabilities), and we don't want to be using CAP_DAC_OVERRIDE. #
+#############################################################################
+
+SUBSYSTEM!="hidraw", GOTO="brltty_hid_end"
+ACTION!="add", GOTO="brltty_hid_end"
+
+ATTRS{bInterfaceNumber}=="?*", GOTO="brltty_hid_usb"
+GOTO="brltty_hid_end"
+
+LABEL="brltty_hid_usb"
+ENV{.BRLTTY_HID_NAME}="$attr{../product}-Intf:$attr{bInterfaceNumber}"
+GOTO="brltty_hid_filter"
+
+LABEL="brltty_hid_filter"
+ENV{.BRLTTY_HID_NAME}=="NLS eReader *", GOTO="brltty_hid_add"
+GOTO="brltty_hid_end"
+
+LABEL="brltty_hid_add"
+SYMLINK+="brltty/HID-$env{.BRLTTY_HID_NAME}"
+TEST=="/usr/bin/setfacl", GOTO="brltty_hid_setfacl"
+GOTO="brltty_hid_end"
+
+LABEL="brltty_hid_setfacl"
+RUN+="/usr/bin/setfacl -m u:brltty:rw /dev/$name"
+GOTO="brltty_hid_end"
+
+LABEL="brltty_hid_end"
diff --git a/Autostart/Udev/udev-wrapper b/Autostart/Udev/udev-wrapper
new file mode 100755
index 0000000..da6ebcb
--- /dev/null
+++ b/Autostart/Udev/udev-wrapper
@@ -0,0 +1,32 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This script must be installed within brltty's library directory: /usr/lib/brltty/
+
+set -e
+readonly rootDirectory="/sys/fs/cgroup/systemd"
+
+[ ! -d "${rootDirectory}" ] || {
+  groupDirectory="${rootDirectory}/brltty"
+  [ -d "${groupDirectory}" ] || mkdir "${groupDirectory}"
+  echo $$ >"${groupDirectory}/tasks"
+}
+
+exec "${BRLTTY_EXECUTABLE_PATH:-brltty}" -E "${@}"
+exit "${?}"
diff --git a/Autostart/Udev/uinput.rules b/Autostart/Udev/uinput.rules
new file mode 100644
index 0000000..101eda3
--- /dev/null
+++ b/Autostart/Udev/uinput.rules
@@ -0,0 +1,42 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+######################################################
+# Udev rules for giving BRLTTY access to /dev/uinput #
+######################################################
+
+#############################################################################
+# BRLTTY is now able to switch to running as an unprivileged user, using    #
+# only a few capabilities), and we don't want to be using CAP_DAC_OVERRIDE. #
+# It uses uinput in order to support some important features, including:    #
+# *  Keyboard Key Tables                                                    #
+# *  Injecting arbitrary key combinations typed on braille devices.         #
+#############################################################################
+
+KERNEL=="uinput", ACTION=="add", GOTO="brltty_uinput_add"
+GOTO="brltty_uinput_end"
+
+LABEL="brltty_uinput_add"
+TEST=="/usr/bin/setfacl", GOTO="brltty_uinput_setfacl"
+GOTO="brltty_uinput_end"
+
+LABEL="brltty_uinput_setfacl"
+RUN+="/usr/bin/setfacl -m u:brltty:rw /dev/$name"
+GOTO="brltty_uinput_end"
+
+LABEL="brltty_uinput_end"
diff --git a/Autostart/Udev/usb-template.rules.in b/Autostart/Udev/usb-template.rules.in
new file mode 100644
index 0000000..f48a2e5
--- /dev/null
+++ b/Autostart/Udev/usb-template.rules.in
@@ -0,0 +1,84 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+#########################################################################
+# Udev rules for starting BRLTTY when a USB braille device is connected #
+#########################################################################
+
+SUBSYSTEM=="usb_device", GOTO="brltty_usb_begin"
+SUBSYSTEM=="usb", KERNEL!="*:*", GOTO="brltty_usb_begin"
+GOTO="brltty_device_end"
+
+LABEL="brltty_usb_begin"
+# BEGIN_USB_BRAILLE_DEVICES
+# END_USB_BRAILLE_DEVICES
+GOTO="brltty_device_end"
+
+LABEL="brltty_usb_run"
+SYMLINK+="brltty/USB-Mfr:$attr{manufacturer}-Prd:$attr{product}-Ser:$attr{serial}"
+ENV{BRLTTY_BRAILLE_DEVICE}="usb:vendor=0X$sysfs{idVendor}+product=0X$sysfs{idProduct}+serial=$sysfs{serial}"
+GOTO="brltty_device_run"
+
+LABEL="brltty_device_run"
+TEST=="/sys/fs/cgroup/systemd", GOTO="brltty_systemd"
+TEST=="/run/systemd/system", GOTO="brltty_systemd"
+ENV{BRLTTY_PID_FILE}="/run/brltty/brltty.$kernel.pid"
+
+TEST=="/etc/init", GOTO="brltty_upstart"
+TEST=="/etc/event.d", GOTO="brltty_upstart"
+
+ACTION=="add", GOTO="brltty_udev_add"
+ACTION=="remove", GOTO="brltty_udev_remove"
+GOTO="brltty_device_end"
+
+LABEL="brltty_udev_add"
+RUN+="@COMMANDS_DIRECTORY@/udev-wrapper"
+GOTO="brltty_device_end"
+
+LABEL="brltty_udev_remove"
+RUN+="@COMMANDS_DIRECTORY@/udev-wrapper -C"
+GOTO="brltty_device_end"
+
+LABEL="brltty_upstart"
+ACTION=="add", GOTO="brltty_upstart_start"
+ACTION=="remove", GOTO="brltty_upstart_stop"
+GOTO="brltty_device_end"
+
+LABEL="brltty_upstart_start"
+PROGRAM="/sbin/initctl status brltty", RESULT=="*running*", GOTO="brltty_upstart_restart"
+RUN+="/sbin/initctl start --quiet --no-wait brltty 'BRLTTY_BRAILLE_DEVICE=$env{BRLTTY_BRAILLE_DEVICE}' 'BRLTTY_BRAILLE_DRIVER=$env{BRLTTY_BRAILLE_DRIVER}'"
+GOTO="brltty_device_end"
+
+LABEL="brltty_upstart_restart"
+RUN+="/sbin/initctl restart --quiet --no-wait brltty 'BRLTTY_BRAILLE_DEVICE=$env{BRLTTY_BRAILLE_DEVICE}' 'BRLTTY_BRAILLE_DRIVER=$env{BRLTTY_BRAILLE_DRIVER}'"
+GOTO="brltty_device_end"
+
+LABEL="brltty_upstart_stop"
+RUN+="/sbin/initctl stop --quiet --no-wait brltty"
+GOTO="brltty_device_end"
+
+LABEL="brltty_systemd"
+TAG+="systemd"
+ACTION=="add", GOTO="brltty_systemd_add"
+GOTO="brltty_device_end"
+
+LABEL="brltty_systemd_add"
+ENV{SYSTEMD_WANTS}+="brltty-device@.service"
+GOTO="brltty_device_end"
+
+LABEL="brltty_device_end"
diff --git a/Autostart/Upstart/job b/Autostart/Upstart/job
new file mode 100644
index 0000000..eb4304f
--- /dev/null
+++ b/Autostart/Upstart/job
@@ -0,0 +1,19 @@
+# BRLTTY is a background process (daemon) providing access to the Linux/Unix
+# console (when in text mode) for a blind person using a refreshable braille
+# display.
+
+# This is a simple Upstart job. Copy it into your distribution's Upstart
+# jobs directory and modify as appropriate for your needs. Possible
+# locations for this directory include:
+# * /etc/init/jobs.d/
+# * /etc/event.d/
+# * /etc/init/
+# Set its permissions, ownership, and name as per the other Upstart jobs.
+
+console output
+respawn
+
+# This service is started automatically by init so that braille output is
+# available as soon as possible on system startup.
+start on startup
+exec /bin/brltty -n
diff --git a/Autostart/X11/90xbrlapi.in b/Autostart/X11/90xbrlapi.in
new file mode 100644
index 0000000..cb3e3e5
--- /dev/null
+++ b/Autostart/X11/90xbrlapi.in
@@ -0,0 +1,19 @@
+# startup script for /etc/X11/Xsession.d
+
+prefix="@prefix@"
+exec_prefix="@exec_prefix@"
+drivers_directory="@drivers_directory@"
+program_directory="@program_directory@"
+xbrlapi="$program_directory/xbrlapi"
+brltty="$program_directory/brltty"
+
+if [ -x "${xbrlapi}" ]; then
+  if "${xbrlapi}" 2>/dev/null ; then
+    # xbrlapi could connect to BrlAPI, try to start brltty with AtSpi2 driver.
+    if [ -x "${brltty}" -a \
+         -e "$drivers_directory/libbrlttybba.so" -a \
+         -e "$drivers_directory/libbrlttyxa2.so" ]; then
+      "${brltty}" -b ba -s no -x a2 -N 2>/dev/null
+    fi
+  fi
+fi
diff --git a/Autostart/X11/Makefile.in b/Autostart/X11/Makefile.in
new file mode 100644
index 0000000..ad7d57b
--- /dev/null
+++ b/Autostart/X11/Makefile.in
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+distclean::
+	-rm 90xbrlapi
+
diff --git a/Autostart/gdm/brltty-gdm.conf b/Autostart/gdm/brltty-gdm.conf
new file mode 100644
index 0000000..57cbda5
--- /dev/null
+++ b/Autostart/gdm/brltty-gdm.conf
@@ -0,0 +1,7 @@
+braille-driver ba
+speech-driver no
+screen-driver a2
+screen-parameters release=no
+
+no-api on
+preferences-file brltty-gdm.prefs
diff --git a/Autostart/gdm/brltty.desktop b/Autostart/gdm/brltty.desktop
new file mode 100644
index 0000000..35bf907
--- /dev/null
+++ b/Autostart/gdm/brltty.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=Application
+Name=BRLTTY Screen Reader
+Exec=brltty -n -f brltty-gdm.conf 
+NoDisplay=true
+AutostartCondition=GSettings org.gnome.desktop.a11y.applications screen-reader-enabled
+X-GNOME-AutoRestart=true
diff --git a/Autostart/gdm/xbrlapi.desktop b/Autostart/gdm/xbrlapi.desktop
new file mode 100644
index 0000000..100762f
--- /dev/null
+++ b/Autostart/gdm/xbrlapi.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Type=Application
+Name=xbrlapi
+Exec=xbrlapi -q
+NoDisplay=true
+X-GNOME-AutoRestart=true
diff --git a/Bindings/Emacs/Makefile.in b/Bindings/Emacs/Makefile.in
new file mode 100644
index 0000000..6b0488e
--- /dev/null
+++ b/Bindings/Emacs/Makefile.in
@@ -0,0 +1,54 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+EMACS_OK = @EMACS_OK@
+EMACS = @EMACS@
+
+EMACS_API_NAME = $(API_NAME)
+EMACS_API_OBJECT = bindings.$O
+EMACS_API_LIBRARY = $(EMACS_API_NAME).$(LIB_EXT)
+
+EMACS_API_SUBDIRECTORY = @EMACS_SUBDIRECTORY@
+EMACS_API_LIBDIR = $(INSTALL_ROOT)$(DRIVERS_DIRECTORY)/$(EMACS_API_SUBDIRECTORY)
+EMACS_API_ELDIR = $(INSTALL_ROOT)$(TABLES_DIRECTORY)/$(EMACS_API_SUBDIRECTORY)
+
+all: $(EMACS_API_LIBRARY)
+
+$(EMACS_API_LIBRARY): $(EMACS_API_OBJECT)
+	$(MKLIB:<name>=$(EMACS_API_LIBRARY).$(API_VERSION)) $@ $(EMACS_API_OBJECT) $(API_LDFLAGS)
+
+$(EMACS_API_OBJECT): | $(API_NAME)
+	$(CC) $(LIBCFLAGS) -o $@ -c $(SRC_DIR)/bindings.c
+
+clean::
+	-rm -f -- $(EMACS_API_LIBRARY)
+
+distclean::
+	-rm -f -- add_directory.el
+
+install: all
+	$(INSTALL_DIRECTORY) $(EMACS_API_LIBDIR)
+	$(INSTALL_PROGRAM) $(EMACS_API_LIBRARY) $(EMACS_API_LIBDIR)
+	$(INSTALL_DIRECTORY) $(EMACS_API_ELDIR)
+	$(INSTALL_SCRIPT) add_directory.el $(EMACS_API_ELDIR)
+
+uninstall:
+	-rm -f -r -- $(EMACS_API_LIBDIR)
+	-rm -f -r -- $(EMACS_API_ELDIR)
+
diff --git a/Bindings/Emacs/add_directory.el.in b/Bindings/Emacs/add_directory.el.in
new file mode 100644
index 0000000..4b7dcd8
--- /dev/null
+++ b/Bindings/Emacs/add_directory.el.in
@@ -0,0 +1 @@
+(add-to-list 'load-path "@DRIVERS_DIRECTORY@/@EMACS_SUBDIRECTORY@")
diff --git a/Bindings/Emacs/apitest b/Bindings/Emacs/apitest
new file mode 100755
index 0000000..7bef17c
--- /dev/null
+++ b/Bindings/Emacs/apitest
@@ -0,0 +1,21 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec emacs --batch --no-site-file -L . -l apitest.el "${@}"
+exit "${?}"
diff --git a/Bindings/Emacs/apitest.el b/Bindings/Emacs/apitest.el
new file mode 100644
index 0000000..382078f
--- /dev/null
+++ b/Bindings/Emacs/apitest.el
@@ -0,0 +1,27 @@
+(load "brlapi")
+
+(defvar brl (apply #'brlapi-open-connection command-line-args-left))
+
+(let ((version (brlapi-get-library-version)))
+  (message "Library Version: %s.%s.%s"
+            (plist-get version :major)
+            (plist-get version :minor)
+            (plist-get version :revision)))
+(message "Driver Name: %s" (brlapi-get-driver-name brl))
+(message "Model Identifier: %s" (brlapi-get-model-identifier brl))
+(let ((size (brlapi-get-display-size brl)))
+  (message "Display Size: %dx%d" (car size) (cdr size)))
+
+(message "Computer Braille Table: %s" (brlapi-get-computer-braille-table brl))
+(message "Literary Braille Table: %s" (brlapi-get-literary-braille-table brl))
+(message "Message Locale: %s" (brlapi-get-message-locale brl))
+
+(message "Bound Command Keycode Names: %S"
+         (mapcar (lambda (code) (brlapi-get-command-keycode-name brl code))
+                 (brlapi-get-bound-command-keycodes brl)))
+
+(message "Defined Driver Keycode Names: %S"
+         (mapcar (lambda (code) (brlapi-get-driver-keycode-name brl code))
+                 (brlapi-get-defined-driver-keycodes brl)))
+
+(brlapi-close-connection brl)
diff --git a/Bindings/Emacs/bindings.c b/Bindings/Emacs/bindings.c
new file mode 100644
index 0000000..7e84a59
--- /dev/null
+++ b/Bindings/Emacs/bindings.c
@@ -0,0 +1,891 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define BRLAPI_NO_SINGLE_SESSION
+#include "prologue.h"
+#include <brlapi.h>
+#include <emacs-module.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+int plugin_is_GPL_compatible;
+
+static const char *error_name = "brlapi-error";
+static const char *error_message = "BrlAPI Error";
+
+static inline emacs_value
+cons(emacs_env *env, emacs_value car, emacs_value cdr) {
+  emacs_value args[] = { car, cdr };
+
+  return env->funcall(env, env->intern(env, "cons"), ARRAY_COUNT(args), args);
+}
+
+static inline emacs_value
+list(emacs_env *env, ptrdiff_t nargs, emacs_value *args) {
+  return env->funcall(env, env->intern(env, "list"), nargs, args);
+}
+
+static void
+error(emacs_env *env) {
+  const char *const str = brlapi_strerror(&brlapi_error);
+  emacs_value error_symbol = env->intern(env, error_name);
+  emacs_value data[] = {
+    env->make_string(env, str, strlen(str)),
+    env->make_integer(env, brlapi_errno),
+    env->make_integer(env, brlapi_libcerrno),
+    env->make_integer(env, brlapi_gaierrno)
+  };
+
+  env->non_local_exit_signal(env,
+    error_symbol, list(env, ARRAY_COUNT(data), data)
+  );
+}
+
+static emacs_value
+getLibraryVersion(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  int major, minor, revision;
+
+  brlapi_getLibraryVersion(&major, &minor, &revision);
+
+  {
+    emacs_value data[] = {
+      env->intern(env, ":major"), env->make_integer(env, major),
+      env->intern(env, ":minor"), env->make_integer(env, minor),
+      env->intern(env, ":revision"), env->make_integer(env, revision)
+    };
+
+    return list(env, ARRAY_COUNT(data), data);
+  }
+}
+
+static char *
+extract_string(emacs_env *env, ptrdiff_t nargs, ptrdiff_t arg, emacs_value *args) {
+  if (arg < nargs) {
+    if (env->is_not_nil(env, args[arg])) {
+      ptrdiff_t size;
+      if (env->copy_string_contents(env, args[arg], NULL, &size)) {
+        char *string = malloc(size);
+
+        if (env->copy_string_contents(env, args[arg], string, &size)) {
+          return string;
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static emacs_value
+openConnection(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = malloc(brlapi_getHandleSize());
+  char *host = extract_string(env, nargs, 0, args);
+  char *auth = extract_string(env, nargs, 1, args);
+  const brlapi_connectionSettings_t desiredSettings = {
+    .host = host, .auth = auth
+  };
+  brlapi_connectionSettings_t actualSettings;
+  int result;
+
+  if (env->non_local_exit_check(env) != emacs_funcall_exit_return) {
+    if (handle) free(handle);
+    if (host) free(host);
+    if (auth) free(auth);
+    return NULL;
+  }
+
+  result = brlapi__openConnection(handle, &desiredSettings, &actualSettings);
+
+  if (host) free(host);
+  if (auth) free(auth);
+  
+  if (result == -1) {
+    error(env);
+    free(handle);
+    return NULL;
+  }
+
+  return env->make_user_ptr(env, free, handle);
+}
+
+static emacs_value
+getString(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  int BRLAPI_STDCALL (*get) (brlapi_handle_t *, char *, size_t)
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    char name[BRLAPI_MAXNAMELENGTH + 1];
+    size_t length = get(handle, name, sizeof(name));
+
+    if (length != -1) {
+      if (length > 0) length--;
+
+      return env->make_string(env, name, length);
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static emacs_value
+getDriverName(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return getString(env, nargs, args, data, brlapi__getDriverName);
+}
+
+static emacs_value
+getModelIdentifier(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return getString(env, nargs, args, data, brlapi__getModelIdentifier);
+}
+
+static emacs_value
+getDisplaySize(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    unsigned int x, y;
+
+    if (brlapi__getDisplaySize(handle, &x, &y) != -1) {
+      return cons(env, env->make_integer(env, x), env->make_integer(env, y));
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static char *
+extract_driver(emacs_env *env, emacs_value arg) {
+  ptrdiff_t size;
+
+  if (env->copy_string_contents(env, arg, NULL, &size)) {
+    char *driver = malloc(size);
+
+    if (env->copy_string_contents(env, arg, driver, &size)) {
+      return driver;
+    }
+  }
+
+  return NULL;
+}
+
+static emacs_value
+enterTtyMode(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+  emacs_value result = NULL;
+
+  if (handle) {
+    int tty = nargs > 1 && env->is_not_nil(env, args[1])?
+              env->extract_integer(env, args[1]): BRLAPI_TTY_DEFAULT;
+    char *driver = nargs > 2? extract_driver(env, args[2]): NULL;
+
+    if (env->non_local_exit_check(env) == emacs_funcall_exit_return) {
+      tty = brlapi__enterTtyMode(handle, tty, driver);
+    
+      if (tty != -1) {
+        result = env->make_integer(env, tty);
+      } else {
+        error(env);
+      }
+    }
+
+    if (driver) free(driver);
+  }
+
+  return result;
+}
+
+static emacs_value
+leaveTtyMode(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    if (brlapi__leaveTtyMode(handle) != -1) {
+      return env->intern(env, "nil");
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static inline int
+extract_cursor(emacs_env *env, emacs_value arg) {
+  if (!env->is_not_nil(env, arg)) return BRLAPI_CURSOR_OFF;
+  if (env->eq(env, arg, env->intern(env, "leave"))) return BRLAPI_CURSOR_LEAVE;
+  return env->extract_integer(env, arg);
+}
+
+static emacs_value
+writeText(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    ptrdiff_t size;
+
+    if (env->copy_string_contents(env, args[1], NULL, &size)) {
+      char text[size];
+
+      if (env->copy_string_contents(env, args[1], text, &size)) {
+        int cursor = nargs > 2? extract_cursor(env, args[2]): BRLAPI_CURSOR_OFF;
+
+        if (env->non_local_exit_check(env) == emacs_funcall_exit_return) {
+          if (brlapi__writeText(handle, cursor, text) != -1) {
+            return env->intern(env, "nil");
+          }
+
+          error(env);
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static emacs_value
+readKey(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+  const int wait = nargs > 1? env->is_not_nil(env, args[1]): 0;
+  brlapi_keyCode_t keyCode;
+  int result;
+
+  if (handle == NULL) return NULL;
+
+  do {
+    if (env->process_input(env) != emacs_process_input_continue)
+      return NULL;
+
+    result = brlapi__readKey(handle, wait, &keyCode);
+  } while (result == -1 &&
+           brlapi_errno == BRLAPI_ERROR_LIBCERR &&
+           brlapi_libcerrno == EINTR);
+  
+  if (result == -1) {
+    error(env);
+    return NULL;
+  }
+
+  if (result == 0) return env->intern(env, "nil");
+
+  return env->make_integer(env, (intmax_t)keyCode);
+}
+
+static emacs_value
+readKeyWithTimeout(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+  const int timeout_ms = env->extract_integer(env, args[1]);
+  brlapi_keyCode_t keyCode;
+  int result;
+
+  do {
+    if (env->process_input(env) != emacs_process_input_continue)
+      return NULL;
+
+    result = brlapi__readKeyWithTimeout(handle, timeout_ms, &keyCode);
+  } while (result == -1 &&
+           timeout_ms < 0 &&
+           brlapi_errno == BRLAPI_ERROR_LIBCERR &&
+           brlapi_libcerrno == EINTR);
+  
+  if (result == -1) {
+    error(env);
+    return NULL;
+  }
+
+  if (result == 0) return env->intern(env, "nil");
+
+  return env->make_integer(env, (intmax_t)keyCode);
+}
+
+static inline brlapi_keyCode_t
+extract_keyCode(emacs_env *env, emacs_value value) {
+  return (brlapi_keyCode_t)env->extract_integer(env, value);
+}
+
+static const ptrdiff_t changeKeysMinArity = 2;
+
+static emacs_value
+changeKeys (
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  int BRLAPI_STDCALL (*change) (
+    brlapi_handle_t *, brlapi_rangeType_t, const brlapi_keyCode_t[], unsigned int
+  )
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    static const char *rangeNames[] = {
+      "all", "code", "command", "key", "type", NULL
+    };
+    static const brlapi_rangeType_t rangeTypes[] = {
+      brlapi_rangeType_all,
+      brlapi_rangeType_code,
+      brlapi_rangeType_command,
+      brlapi_rangeType_key,
+      brlapi_rangeType_type
+    };
+    size_t rangeIndex = 0;
+
+    while (rangeNames[rangeIndex]) {
+      if (env->eq(env, args[1], env->intern(env, rangeNames[rangeIndex]))) {
+        break;
+      }
+      rangeIndex++;
+    }
+
+    if (rangeNames[rangeIndex]) {
+      const brlapi_rangeType_t range = rangeTypes[rangeIndex];
+
+      if (range == brlapi_rangeType_all) {
+        if (change(handle, range, NULL, 0) != -1) {
+          return env->intern(env, "nil");
+        }
+
+        error(env);
+      } else {
+        brlapi_keyCode_t keys[nargs - changeKeysMinArity];
+
+        for (ptrdiff_t index = 0; index < ARRAY_COUNT(keys); index++) {
+          keys[index] = extract_keyCode(env, args[changeKeysMinArity + index]);
+
+          if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
+            return NULL;
+        }
+
+        if (change(handle, range, keys, ARRAY_COUNT(keys)) != -1) {
+          return env->intern(env, "nil");
+        }
+
+        error(env);
+      }
+
+      return NULL;
+    }
+
+    { /* (signal 'wrong-type-argument (list 'symbolp arg)) */
+      emacs_value data[] = {
+        env->intern(env, "symbolp"), args[1]
+      };
+
+      env->non_local_exit_signal(env,
+        env->intern(env, "wrong-type-argument"),
+        list(env, ARRAY_COUNT(data), data)
+      );
+    }
+  }
+
+  return NULL;
+}
+
+static emacs_value
+acceptKeys(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return changeKeys(env, nargs, args, data, brlapi__acceptKeys);
+}
+
+static emacs_value
+ignoreKeys(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return changeKeys(env, nargs, args, data, brlapi__ignoreKeys);
+}
+
+static emacs_value
+describeKeyCode(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  const brlapi_keyCode_t keyCode = extract_keyCode(env, args[0]);
+
+  if (env->non_local_exit_check(env) == emacs_funcall_exit_return) {
+    brlapi_describedKeyCode_t description;
+
+    if (brlapi_describeKeyCode(keyCode, &description) != -1) {
+      static const size_t min_len = 3;
+      emacs_value data[min_len + description.flags];
+      
+      data[0] = env->intern(env, description.type);
+      data[1] = env->intern(env, description.command);
+      data[2] = env->make_integer(env, description.argument);
+      for (size_t index = 0; index < description.flags; index += 1) {
+        data[min_len+index] = env->intern(env, description.flag[index]);
+      }
+
+      return list(env, ARRAY_COUNT(data), data);
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static emacs_value
+getStringParameter(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  brlapi_param_t param
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    static const brlapi_param_flags_t flags = BRLAPI_PARAMF_GLOBAL;
+    size_t count;
+    char *string = brlapi__getParameterAlloc(handle, param, 0, flags, &count);
+
+    if (string) {
+      emacs_value result = env->make_string(env, string, count);
+
+      free(string);
+
+      return result;
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static emacs_value
+setStringParameter(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  brlapi_param_t param
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    ptrdiff_t length;
+    
+    if (env->copy_string_contents(env, args[1], NULL, &length)) {
+      char string[length];
+      
+      if (env->copy_string_contents(env, args[1], string, &length)) {
+        static const brlapi_param_flags_t flags = BRLAPI_PARAMF_GLOBAL;
+
+        if (brlapi__setParameter(handle, param, 0, flags, string, length) != -1) {
+          return env->intern(env, "nil");
+        }
+
+        error(env);
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static emacs_value
+getDriverCode(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_DRIVER_CODE);
+}
+
+static emacs_value
+getDriverVersion(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_DRIVER_VERSION);
+}
+
+static emacs_value
+getClipboardContent(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_CLIPBOARD_CONTENT);
+}
+
+static emacs_value
+setClipboardContent(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return setStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_CLIPBOARD_CONTENT);
+}
+
+static emacs_value
+getComputerBrailleTable(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE);
+}
+
+static emacs_value
+setComputerBrailleTable(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return setStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE);
+}
+
+static emacs_value
+getLiteraryBrailleTable(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_LITERARY_BRAILLE_TABLE);
+}
+
+static emacs_value
+setLiteraryBrailleTable(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return setStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_LITERARY_BRAILLE_TABLE);
+}
+
+static emacs_value
+getMessageLocae(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_MESSAGE_LOCALE);
+}
+
+static emacs_value
+setMessageLocale(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return setStringParameter(env, nargs, args, data,
+                            BRLAPI_PARAM_MESSAGE_LOCALE);
+}
+
+static emacs_value
+getKeycodes(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  brlapi_param_t param
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    static const brlapi_param_flags_t flags = BRLAPI_PARAMF_GLOBAL;
+    size_t count;
+    brlapi_keyCode_t *codes = brlapi__getParameterAlloc(
+      handle, param, 0, flags, &count
+    );
+
+    if (codes != NULL) {
+      count /= sizeof(brlapi_keyCode_t);
+
+      {
+        emacs_value data[count];
+        for (size_t i = 0; i < count; i += 1) {
+          data[i] = env->make_integer(env, (intmax_t)codes[i]);
+        }
+        free(codes);
+
+        return list(env, ARRAY_COUNT(data), data);
+      }
+    }
+
+    error(env);
+  }
+
+  return NULL;
+}
+
+static emacs_value
+getBoundCommandKeycodes(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodes(env, nargs, args, data,
+    BRLAPI_PARAM_BOUND_COMMAND_KEYCODES
+  );
+}
+
+static emacs_value
+getDefinedDriverKeycodes(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodes(env, nargs, args, data,
+    BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES
+  );
+}
+
+static emacs_value
+getKeycodeName(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  brlapi_param_t param
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    brlapi_keyCode_t keyCode = extract_keyCode(env, args[1]);
+
+    if (env->non_local_exit_check(env) == emacs_funcall_exit_return) {
+      char *name = brlapi__getParameterAlloc(handle,
+        param, keyCode, BRLAPI_PARAMF_GLOBAL, NULL
+      );
+      
+      if (name) {
+        emacs_value result = env->intern(env, name);
+
+        free(name);
+        
+        return result;
+      }
+
+      error(env);
+    }
+  }
+  
+  return NULL;
+}
+
+static emacs_value
+getCommandKeycodeName(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodeName(env, nargs, args, data,
+    BRLAPI_PARAM_COMMAND_KEYCODE_NAME
+  );
+}
+
+static emacs_value
+getDriverKeycodeName(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodeName(env, nargs, args, data,
+    BRLAPI_PARAM_DRIVER_KEYCODE_NAME
+  );
+}
+
+static emacs_value
+getKeycodeSummary(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data,
+  brlapi_param_t param
+) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    brlapi_keyCode_t keyCode = extract_keyCode(env, args[1]);
+
+    if (env->non_local_exit_check(env) == emacs_funcall_exit_return) {
+      size_t count;
+      char *summary = brlapi__getParameterAlloc(handle,
+        param, keyCode, BRLAPI_PARAMF_GLOBAL, &count
+      );
+      
+      if (summary) {
+        emacs_value result = env->make_string(env, summary, count);
+
+        free(summary);
+        
+        return result;
+      }
+
+      error(env);
+    }
+  }
+  
+  return NULL;
+}
+
+static emacs_value
+getCommandKeycodeSummary(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodeSummary(env, nargs, args, data,
+    BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY
+  );
+}
+
+static emacs_value
+getDriverKeycodeSummary(
+  emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data
+) {
+  return getKeycodeSummary(env, nargs, args, data,
+    BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY
+  );
+}
+
+static emacs_value
+closeConnection(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
+  brlapi_handle_t *const handle = env->get_user_ptr(env, args[0]);
+
+  if (handle) {
+    brlapi__closeConnection(handle);
+
+    return env->intern(env, "nil");
+  }
+
+  return NULL;
+}
+
+int
+emacs_module_init(struct emacs_runtime *runtime) {
+  emacs_env *const env = runtime->get_environment(runtime);
+
+  if (runtime->size < sizeof(*runtime)) return 1;
+  if (env->size < sizeof(*env)) return 2;
+
+  { /* (define-error 'brlapi-error "BrlAPI Error") */
+    emacs_value args[] = {
+      env->intern(env, error_name),
+      env->make_string(env, error_message, strlen(error_message))
+    };
+
+    env->funcall(env, env->intern(env, "define-error"), ARRAY_COUNT(args), args);
+  }
+
+#define register_function(func, minargs, maxargs, name, doc)                  \
+  {                                                                           \
+    emacs_value args[] = {                                                    \
+      env->intern(env, "brlapi-" name),                                       \
+      env->make_function(env, minargs, maxargs, func, doc, NULL)              \
+    };                                                                        \
+    env->funcall(env, env->intern(env, "defalias"), ARRAY_COUNT(args), args); \
+  }
+
+  register_function(getLibraryVersion, 0, 0, "get-library-version",
+    "Get the version of BrlAPI."
+  )
+  register_function(openConnection, 0, 2, "open-connection",
+    "Open a new connection to BRLTTY."
+    "\n\nHOST is a string of the form \"host:0\" where the number after the colon"
+    "\nspecifies the instance of BRLTTY on the given host."
+    "\n\nIf AUTH is non-nil, specifies the path to the secret key."
+    "\n\nAlso see `brlapi-close-connection'."
+    "\n\n(fn &optional HOST AUTH)"
+  )
+  register_function(getDriverName, 1, 1, "get-driver-name",
+    "Get the complete name of the driver used by BRLTTY."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getModelIdentifier, 1, 1, "get-model-identifier",
+    "Get model identifier."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getDisplaySize, 1, 1, "get-display-size",
+    "Get the size of the braille display reachable via CONNECTION."
+    "\n\nReturns a `cons' where `car' is the x dimension and `cdr' the y dimension."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(enterTtyMode, 1, 3, "enter-tty-mode",
+    "Enter TTY mode."
+    "\n\n(fn CONNECTION &optional TTY DRIVER)"
+  )
+  register_function(leaveTtyMode, 1, 1, "leave-tty-mode",
+    "Leave TTY mode."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(writeText, 2, 3, "write-text",
+    "Write STRING to the braille display."
+    "\n\nCURSOR is either an integer or the symbol `leave'."
+    "\n\n(fn CONNECTION STRING CURSOR)"
+  )
+  register_function(readKey, 1, 2, "read-key",
+    "Read a keypress from CONNECTION."
+    "\n\nIf WAIT is non-nil, wait until a keypress actually arrives."
+    "\n\n(fn CONNECTION WAIT)"
+  )
+  register_function(readKeyWithTimeout, 2, 2, "read-key-with-timeout",
+    "Read a keypress from CONNECTION waiting MILISECONDS."
+    "\n\n(fn CONNECTION MILISECONDS)"
+  )
+  register_function(acceptKeys, changeKeysMinArity, emacs_variadic_function, "accept-keys",
+    "Ask the server to give KEY-CODES to the application."
+    "\n\nTYPE should be one of the following symbols:"
+    "\n  'all' for all keys"
+    "\n  'type' for keys of a given type"
+    "\n  'command' for keys of a given command block, i.e. matching the key type and command parts"
+    "\n  'key' for a given key with any flags"
+    "\n  'code' for a given key code"
+    "\n\n(fn CONNECTION TYPE &rest KEY-CODES)"
+  )
+  register_function(ignoreKeys, changeKeysMinArity, emacs_variadic_function, "ignore-keys",
+    "Ask the server to handle KEY-CODES by brltty."
+    "\n\nSee `brlapi-accept-keys' for a description of the TYPE argument."
+    "\n\n(fn CONNECTION TYPE &rest KEY-CODES)"
+  )
+  register_function(describeKeyCode, 1, 1, "describe-key-code",
+    "Decode KEY-CODE into its individual symbolic components."
+    "\n\n(fn KEY-CODE)"
+  )
+  register_function(getDriverCode, 1, 1, "get-driver-code",
+    "Get the driver code (a 2 letter string) of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getDriverVersion, 1, 1, "get-driver-version",
+    "Get the driver version(a string) of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getClipboardContent, 1, 1, "get-clipboard-content",
+    "Get the content of the clipboard of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(setClipboardContent, 2, 2, "set-clipboard-content",
+    "Set the content of the clipboard of CONNECTION to STRING."
+    "\n\n(fn CONNECTION STRING)"
+  )
+  register_function(getComputerBrailleTable, 1, 1, "get-computer-braille-table",
+    "Get the computer braille table of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(setComputerBrailleTable, 2, 2, "set-computer-braille-table",
+    "Set the computer braille table of CONNECTION to STRING."
+    "\n\n(fn CONNECTION STRING)"
+  )
+  register_function(getLiteraryBrailleTable, 1, 1, "get-literary-braille-table",
+    "Get the literary braille table of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(setLiteraryBrailleTable, 2, 2, "set-literary-braille-table",
+    "Set the literary braille table of CONNECTION to STRING."
+    "\n\n(fn CONNECTION STRING)"
+  )
+  register_function(getMessageLocae, 1, 1, "get-message-locale",
+    "Get the message locale of CONNECTION."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(setMessageLocale, 2, 2, "set-message-locale",
+    "Set the message locale of CONNECTION to STRING."
+    "\n\n(fn CONNECTION STRING)"
+  )
+  register_function(getBoundCommandKeycodes, 1, 1, "get-bound-command-keycodes",
+    "Get commands bound by the driver."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getCommandKeycodeName, 2, 2, "get-command-keycode-name",
+    "Get the name (a symbol) of the given command KEYCODE."
+    "\n\n(fn KEYCODE)"
+  )
+  register_function(getCommandKeycodeSummary, 2, 2, "get-command-keycode-summary",
+    "Get a description for a command KEYCODE."
+    "\n\n(fn KEYCODE)"
+  )
+  register_function(getDefinedDriverKeycodes, 1, 1, "get-defined-driver-keycodes",
+    "Get a list of defined driver specific keycodes."
+    "\n\n(fn CONNECTION)"
+  )
+  register_function(getDriverKeycodeName, 2, 2, "get-driver-keycode-name",
+    "Get the name (a symbol) of the driver specific KEYCODE."
+    "\n\n(fn KEYCODE)"
+  )
+  register_function(getDriverKeycodeSummary, 2, 2, "get-driver-keycode-summary",
+    "Get a description for a driver specific KEYCODE."
+    "\n\n(fn KEYCODE)"
+  )
+  register_function(closeConnection, 1, 1, "close-connection",
+    "Close the connection."
+    "\n\n(fn CONNECTION)"
+  )
+#undef register_function
+
+  return 0;
+}
diff --git a/Bindings/Emacs/bindings.m4 b/Bindings/Emacs/bindings.m4
new file mode 100644
index 0000000..aa93cd2
--- /dev/null
+++ b/Bindings/Emacs/bindings.m4
@@ -0,0 +1,38 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_EMACS_BINDINGS], [dnl
+EMACS_OK=false
+EMACS=""
+
+AC_CHECK_HEADER([emacs-module.h], [dnl
+   AC_PATH_PROG([EMACS], [emacs])
+
+   if test -n "${EMACS}"
+   then
+      AC_MSG_NOTICE([emacs editor: ${EMACS}])
+      EMACS_OK=true
+   else
+      AC_MSG_WARN([emacs editor not found])
+      EMACS="EMACS_EDITOR_NOT_FOUND_BY_CONFIGURE"
+   fi
+])
+
+AC_SUBST([EMACS_OK])
+AC_SUBST([EMACS])
+AC_SUBST([EMACS_SUBDIRECTORY], [Emacs])
+])
diff --git a/Bindings/Java/APIError.java b/Bindings/Java/APIError.java
new file mode 100644
index 0000000..7bb2215
--- /dev/null
+++ b/Bindings/Java/APIError.java
@@ -0,0 +1,71 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class APIError extends Error {
+  @Override
+  public native String toString ();
+
+  private final int apiError;
+  private final int osError;
+  private final int gaiError;
+  private final String functionName;
+
+  public APIError (int api, int os, int gai, String function) {
+    apiError = api;
+    osError = os;
+    gaiError = gai;
+    functionName = function;
+  }
+
+  public final int getApiError () {
+    return apiError;
+  }
+
+  public final int getOsError () {
+    return osError;
+  }
+
+  public final int getGaiError () {
+    return gaiError;
+  }
+
+  public final String getFunctionName () {
+    return functionName;
+  }
+
+  public final static int SUCCESS             =  0; /* Success */
+  public final static int NOMEM               =  1; /* Not enough memory */
+  public final static int TTYBUSY             =  2; /* Already a connection running in this tty */
+  public final static int DEVICEBUSY          =  3; /* Already a connection using RAW mode */
+  public final static int UNKNOWN_INSTRUCTION =  4; /* Not implemented in protocol */
+  public final static int ILLEGAL_INSTRUCTION =  5; /* Forbiden in current mode */
+  public final static int INVALID_PARAMETER   =  6; /* Out of range or have no sense */
+  public final static int INVALID_PACKET      =  7; /* Invalid size */
+  public final static int CONNREFUSED         =  8; /* Connection refused */
+  public final static int OPNOTSUPP           =  9; /* Operation not supported */
+  public final static int GAIERR              = 10; /* Getaddrinfo error */
+  public final static int LIBCERR             = 11; /* Libc error */
+  public final static int UNKNOWNTTY          = 12; /* Couldn't find out the tty number */
+  public final static int PROTOCOL_VERSION    = 13; /* Bad protocol version */
+  public final static int EOF                 = 14; /* Unexpected end of file */
+  public final static int EMPTYKEY            = 15; /* Too many levels of recursion */
+  public final static int DRIVERERROR         = 16; /* Packet returned by driver too large */
+}
diff --git a/Bindings/Java/APIException.java b/Bindings/Java/APIException.java
new file mode 100644
index 0000000..69b8d12
--- /dev/null
+++ b/Bindings/Java/APIException.java
@@ -0,0 +1,58 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class APIException extends RuntimeException {
+  @Override
+  public native String toString ();
+
+  private final long connectionHandle;
+  private final ConnectionBase connection;
+
+  private final int errorNumber;
+  private final int packetType;
+  private final byte[] failedPacket;
+
+  public APIException (long handle, int error, int type, byte[] packet) {
+    super();
+
+    Connection.setUnusable(handle);
+    connectionHandle = handle;
+    connection = ConnectionBase.getConnection(handle);
+
+    errorNumber = error;
+    packetType = type;
+    failedPacket = packet;
+  }
+
+  public final int getErrorNumber () {
+    return errorNumber;
+  }
+
+  public final int getPacketType () {
+    return packetType;
+  }
+
+  public final byte[] getFailedPacket () {
+    return failedPacket;
+  }
+
+  public native static String getPacketTypeName (int type);
+}
diff --git a/Bindings/Java/APIVersion.java b/Bindings/Java/APIVersion.java
new file mode 100644
index 0000000..ee11b10
--- /dev/null
+++ b/Bindings/Java/APIVersion.java
@@ -0,0 +1,35 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class APIVersion extends NativeComponent {
+  private APIVersion () {
+    super();
+  }
+
+  private native static int getMajor ();
+  public final static int MAJOR = getMajor();
+
+  private native static int getMinor ();
+  public final static int MINOR = getMinor();
+
+  private native static int getRevision ();
+  public final static int REVISION = getRevision();
+}
diff --git a/Bindings/Java/AuthenticationException.java b/Bindings/Java/AuthenticationException.java
new file mode 100644
index 0000000..8756984
--- /dev/null
+++ b/Bindings/Java/AuthenticationException.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class AuthenticationException extends ConnectException {
+  private final String authenticationScheme;
+
+  public AuthenticationException (String scheme) {
+    super("authentication failed: %s", scheme);
+    authenticationScheme = scheme;
+  }
+
+  public final String getScheme () {
+    return authenticationScheme;
+  }
+}
diff --git a/Bindings/Java/BitMask.java b/Bindings/Java/BitMask.java
new file mode 100644
index 0000000..290c349
--- /dev/null
+++ b/Bindings/Java/BitMask.java
@@ -0,0 +1,90 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.Arrays;
+
+public class BitMask extends Component {
+  private final static int BYTE_SIZE = Byte.SIZE;
+
+  private final byte[] maskBytes;
+  private final int maskSize;
+
+  public BitMask (byte[] bytes) {
+    maskBytes = bytes;
+    maskSize = maskBytes.length * BYTE_SIZE;
+  }
+
+  public int getSize () {
+    return maskSize;
+  }
+
+  public boolean isSet (int index) {
+    if (index < 0) return false;
+    if (index >= maskSize) return false;
+
+    int bit = 1 << (index % BYTE_SIZE);
+    index /= BYTE_SIZE;
+    return (maskBytes[index] & bit) != 0;
+  }
+
+  private int[] bitNumbers = null;
+
+  private final int[] newBitNumbers () {
+    int size = getSize();
+    int[] buffer = new int[size];
+    int count = 0;
+    int start = 0;
+
+    for (int bits : maskBytes) {
+      if ((bits &= BYTE_MASK) != 0) {
+        int bit = start;
+
+        while (true) {
+          if ((bits & 1) != 0) buffer[count++] = bit;
+          if ((bits >>= 1) == 0) break;
+          bit += 1;
+        }
+      }
+
+      start += BYTE_SIZE;
+    }
+
+    int[] result = new int[count];
+    System.arraycopy(buffer, 0, result, 0, count);
+    return result;
+  }
+
+  public final int[] getBitNumbers () {
+    synchronized (this) {
+      if (bitNumbers == null) bitNumbers = newBitNumbers();
+    }
+
+    int count = bitNumbers.length;
+    int[] result = new int[count];
+    System.arraycopy(bitNumbers, 0, result, 0, count);
+    return result;
+  }
+
+  @Override
+  public String toString () {
+    return Arrays.toString(getBitNumbers());
+  }
+}
diff --git a/Bindings/Java/Client.java b/Bindings/Java/Client.java
new file mode 100644
index 0000000..1204bfc
--- /dev/null
+++ b/Bindings/Java/Client.java
@@ -0,0 +1,226 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.io.InterruptedIOException;
+
+public abstract class Client extends Program {
+  protected abstract void runClient (Connection connection)
+            throws ProgramException;
+
+  private final ConnectionSettings connectionSettings = new ConnectionSettings();
+
+  public final Client setServerHost (String host) throws SyntaxException {
+    try {
+      connectionSettings.setServerHost(host);
+    } catch (IllegalArgumentException exception) {
+      throw new SyntaxException(exception.getMessage());
+    }
+
+    return this;
+  }
+
+  public final Client setAuthenticationScheme (String scheme) throws SyntaxException {
+    try {
+      connectionSettings.setAuthenticationScheme(scheme);
+    } catch (IllegalArgumentException exception) {
+      throw new SyntaxException(exception.getMessage());
+    }
+
+    return this;
+  }
+
+  protected Client (String... arguments) {
+    super(arguments);
+
+    addOption("server",
+      (operands) -> {
+        setServerHost(operands[0]);
+      },
+      "host specification"
+    );
+
+    addOption("authentication",
+      (operands) -> {
+        setAuthenticationScheme(operands[0]);
+      },
+      "scheme"
+    );
+  }
+
+  @Override
+  protected void extendUsageSummary (StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    usage.append('\n')
+         .append("The default server host is ")
+         .append(ConnectionSettings.DEFAULT_SERVER_HOST)
+         .append(". ")
+
+         .append("The format of a host specification is ")
+         .append(USAGE_OPTIONAL_BEGIN)
+         .append("host")
+         .append(USAGE_OPTIONAL_END)
+         .append(USAGE_OPTIONAL_BEGIN)
+         .append(ConnectionSettings.HOST_PORT_SEPARATOR).append("port")
+         .append(USAGE_OPTIONAL_END)
+         .append(". ")
+
+         .append("The host component may be either a host name or an IPV4 address")
+         .append(" - if not specified, a local socket is used. ")
+
+         .append("The port component must be an integer within the range ")
+         .append(ConnectionSettings.MINIMUM_PORT_NUMBER)
+         .append(" through ")
+         .append(ConnectionSettings.MAXIMUM_PORT_NUMBER)
+         .append(" - if not specified, ")
+         .append(ConnectionSettings.DEFAULT_PORT_NUMBER)
+         .append(" is assumed. ")
+         ;
+
+    usage.append('\n')
+         .append("The default authentication scheme is ")
+         .append(ConnectionSettings.DEFAULT_AUTHENTICATION_SCHEME)
+         .append(". ")
+
+         .append("The format of a scheme specification is name")
+         .append(USAGE_OPTIONAL_BEGIN)
+         .append(ConnectionSettings.AUTHENTICATION_OPERAND_PREFIX).append("operand")
+         .append(USAGE_OPTIONAL_END)
+         .append(". ")
+
+         .append("More than one scheme, separated by ")
+         .append(ConnectionSettings.AUTHENTICATION_SCHEME_SEPARATOR)
+         .append(", may be specified. ")
+
+         .append("The following schemes are supported:")
+         .append("\n  ").append(ConnectionSettings.AUTHENTICATION_SCHEME_KEYFILE)
+         .append(ConnectionSettings.AUTHENTICATION_OPERAND_PREFIX).append("path")
+         .append("\n  ").append(ConnectionSettings.AUTHENTICATION_SCHEME_NONE)
+         ;
+  }
+
+  protected interface ClientTask {
+    public void run (Connection connection) throws ProgramException;
+  }
+
+  private final Client connect (ClientTask task) throws ProgramException {
+    try {
+      Connection connection = new Connection(connectionSettings);
+
+      try {
+        task.run(connection);
+      } finally {
+        connection.close();
+        connection = null;
+      }
+    } catch (LostConnectionException exception) {
+      throw new ExternalException("connection lost");
+    } catch (APIError error) {
+      throw new ExternalException(("API error: " + error));
+    } catch (APIException exception) {
+      throw new ExternalException(("API exception: " + exception));
+    }
+
+    return this;
+  }
+
+  public final boolean pause (Connection connection, int milliseconds) {
+    try {
+      connection.pause(milliseconds);
+      return true;
+    } catch (InterruptedIOException exception) {
+      return false;
+    }
+  }
+
+  @Override
+  protected final void runProgram () throws ProgramException {
+    connect(
+      (connection) -> {
+        runClient(connection);
+      }
+    );
+  }
+
+  protected final Parameter getParameter (Connection connection, String name)
+            throws SemanticException
+  {
+    Parameter parameter = connection.getParameters().get(name);
+    if (parameter != null) return parameter;
+    throw new SemanticException("unknown parameter: %s", name);
+  }
+
+  protected interface TtyModeTask {
+    public void run (Connection connection) throws ProgramException;
+  }
+
+  protected final Client ttyMode (Connection connection, String driver, TtyModeTask task, int... path)
+            throws ProgramException
+  {
+    try {
+      connection.enterTtyModeWithPath(driver, path);
+
+      try {
+        task.run(connection);
+      } finally {
+        if (!connection.isUnusable()) connection.leaveTtyMode();
+      }
+    } catch (APIError error) {
+      throw new ProgramException(("tty mode error: " + error));
+    }
+
+    return this;
+  }
+
+  protected final Client ttyMode (Connection connection, boolean keys, TtyModeTask task, int... path)
+            throws ProgramException
+  {
+    return ttyMode(connection, (keys? connection.getDriverName(): null), task, path);
+  }
+
+  protected interface RawModeTask {
+    public void run (Connection connection) throws ProgramException;
+  }
+
+  protected final Client rawMode (Connection connection, String driver, RawModeTask task)
+            throws ProgramException
+  {
+    try {
+      connection.enterRawMode(driver);
+
+      try {
+        task.run(connection);
+      } finally {
+        if (!connection.isUnusable()) connection.leaveRawMode();
+      }
+    } catch (APIError error) {
+      throw new ProgramException(("raw mode error: " + error));
+    }
+
+    return this;
+  }
+
+  protected final Client rawMode (Connection connection, RawModeTask task)
+            throws ProgramException
+  {
+    return rawMode(connection, connection.getDriverName(), task);
+  }
+}
diff --git a/Bindings/Java/CommandKeycode.java b/Bindings/Java/CommandKeycode.java
new file mode 100644
index 0000000..3c02998
--- /dev/null
+++ b/Bindings/Java/CommandKeycode.java
@@ -0,0 +1,83 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class CommandKeycode extends Keycode {
+  private final native void expandKeycode (long code);
+  private int typeValue;
+  private int commandValue;
+  private int argumentValue;
+  private int flagsValue;
+
+  public CommandKeycode (long code) {
+    super(code);
+    expandKeycode(code);
+  }
+
+  public final int getType () {
+    return typeValue;
+  }
+
+  public final int getCommand () {
+    return commandValue;
+  }
+
+  public final int getArgument () {
+    return argumentValue;
+  }
+
+  public final int getFlags () {
+    return flagsValue;
+  }
+
+  private final native void describeKeycode (long code);
+  private String typeName = null;
+  private String commandName = null;
+  private String[] flagNames = null;
+
+  private final void describeKeycode () {
+    synchronized (this) {
+      if (flagNames == null) describeKeycode(getCode());
+    }
+  }
+
+  public final String getTypeName () {
+    describeKeycode();
+    return typeName;
+  }
+
+  public final String getCommandName () {
+    describeKeycode();
+    return commandName;
+  }
+
+  public final String[] getFlagNames () {
+    describeKeycode();
+    return flagNames;
+  }
+
+  @Override
+  public String toString () {
+    return String.format(
+      "%s Type:%08X Cmd:%04X Arg:%04X Flg:%04X",
+      Keycode.toString(getCode()), getType(), getCommand(), getArgument(), getFlags()
+    );
+  }
+}
diff --git a/Bindings/Java/Component.java b/Bindings/Java/Component.java
new file mode 100644
index 0000000..1d6cedb
--- /dev/null
+++ b/Bindings/Java/Component.java
@@ -0,0 +1,57 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class Component {
+  protected Component () {
+  }
+
+  public final static int BYTE_MASK = (1 << Byte.SIZE) - 1;
+  public final static char UNICODE_BRAILLE_ROW = 0X2800;
+
+  public static char toUnicodeBraille (byte dots) {
+    char character = UNICODE_BRAILLE_ROW;
+    character |= dots & BYTE_MASK;
+    return character;
+  }
+
+  public static String toUnicodeBraille (byte[] dots) {
+    int count = dots.length;
+    char[] characters = new char[count];
+    for (int i=0; i<count; i+=1) characters[i] = toUnicodeBraille(dots[i]);
+    return new String(characters);
+  }
+
+  public static void printf (String format, Object... arguments) {
+    System.out.printf(format, arguments);
+  }
+
+  public static String toOperandName (String string) {
+    return string.replace(' ', '-').toLowerCase();
+  }
+
+  public static String getObjectName(Class<? extends Object> type) {
+    return type.getSimpleName();
+  }
+
+  public final String getObjectName() {
+    return getObjectName(getClass());
+  }
+}
diff --git a/Bindings/Java/ConnectException.java b/Bindings/Java/ConnectException.java
new file mode 100644
index 0000000..5aacb2f
--- /dev/null
+++ b/Bindings/Java/ConnectException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class ConnectException extends ExternalException {
+  public ConnectException (String format, Object... arguments) {
+    super(format, arguments);
+  }
+}
diff --git a/Bindings/Java/Connection.java b/Bindings/Java/Connection.java
new file mode 100644
index 0000000..7811632
--- /dev/null
+++ b/Bindings/Java/Connection.java
@@ -0,0 +1,109 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.io.InterruptedIOException;
+
+public class Connection extends ConnectionBase {
+  public Connection (ConnectionSettings settings) throws ConnectException {
+    super(settings);
+  }
+
+  public int getCellCount () {
+    DisplaySize size = getDisplaySize();
+    return size.getWidth() * size.getHeight();
+  }
+
+  public int enterTtyMode (int tty) {
+    return enterTtyMode(tty, null);
+  }
+
+  public int enterTtyMode (String driver) {
+    return enterTtyMode(Constants.TTY_DEFAULT, driver);
+  }
+
+  public int enterTtyMode () {
+    return enterTtyMode(null);
+  }
+
+  public void enterTtyModeWithPath (int[] ttys, String driver) {
+    enterTtyModeWithPath(driver, ttys);
+  }
+
+  public void enterTtyModeWithPath (int... ttys) {
+    enterTtyModeWithPath(null, ttys);
+  }
+
+  public final long readKey () throws InterruptedIOException {
+    return readKey(true);
+  }
+
+  public void write (byte[] dots) {
+    int count = getCellCount();
+
+    if (dots.length != count) {
+      byte[] newDots = new byte[count];
+      while (count > dots.length) newDots[--count] = 0;
+      System.arraycopy(dots, 0, newDots, 0, count);
+      dots = newDots;
+    }
+
+    writeDots(dots);
+  }
+
+  public void write (int cursor, String text) {
+    if (text != null) {
+      int count = getCellCount();
+
+      {
+        StringBuilder newText = new StringBuilder(text);
+        while (newText.length() < count) newText.append(' ');
+        text = newText.toString();
+      }
+
+      if (text.length() > count) text = text.substring(0, count);
+    }
+
+    writeText(cursor, text);
+  }
+
+  public void write (String text, int cursor) {
+    write(cursor, text);
+  }
+
+  public void write (int cursor) {
+    write(cursor, null);
+  }
+
+  public void write (String text) {
+    write(Constants.CURSOR_LEAVE, text);
+  }
+
+  private Parameters connectionParameters = null;
+  public Parameters getParameters () {
+    synchronized (this) {
+      if (connectionParameters == null) {
+        connectionParameters = new Parameters(this);
+      }
+    }
+
+    return connectionParameters;
+  }
+}
diff --git a/Bindings/Java/ConnectionBase.java b/Bindings/Java/ConnectionBase.java
new file mode 100644
index 0000000..8178dcc
--- /dev/null
+++ b/Bindings/Java/ConnectionBase.java
@@ -0,0 +1,137 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.io.InterruptedIOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+public class ConnectionBase extends NativeComponent implements AutoCloseable {
+  private long connectionHandle;
+  private final static Map<Long, ConnectionBase> connections = new HashMap<>();
+  private boolean hasBecomeUnusable = false;
+
+  private native int openConnection (
+    ConnectionSettings desiredSettings, ConnectionSettings actualSettings
+  ) throws ConnectException;
+
+  private final ConnectionSettings connectionSettings;
+  private final int fileDescriptor;
+
+  public ConnectionBase (ConnectionSettings settings) throws ConnectException {
+    super();
+    connectionSettings = new ConnectionSettings();
+    fileDescriptor = openConnection(settings, connectionSettings);
+
+    synchronized (connections) {
+      if (connections.putIfAbsent(connectionHandle, this) != null) {
+        throw new IllegalStateException("connection handle already mapped");
+      }
+    }
+  }
+
+  public final String getServerHost () {
+    return connectionSettings.getServerHost();
+  }
+
+  public final String getAuthenticationScheme () {
+    return connectionSettings.getAuthenticationScheme();
+  }
+
+  public final int getFileDescriptor () {
+    return fileDescriptor;
+  }
+
+  private native void closeConnection ();
+  private boolean hasBeenClosed = false;
+
+  @Override
+  public void close () {
+    synchronized (this) {
+      if (!hasBeenClosed) {
+        synchronized (connections) {
+          connections.remove(connectionHandle);
+        }
+
+        closeConnection();
+        hasBeenClosed = true;
+      }
+    }
+  }
+
+  public static ConnectionBase getConnection (long handle) {
+    synchronized (connections) {
+      return connections.get(handle);
+    }
+  }
+
+  public static void setUnusable (long handle) {
+    // this needs to be public but it mustn't be easy
+    ConnectionBase connection = getConnection(handle);
+    connection.hasBecomeUnusable = true;
+  }
+
+  public final boolean isUnusable () {
+    return hasBecomeUnusable;
+  }
+
+  public native String getDriverName ();
+  public native String getModelIdentifier ();
+  public native DisplaySize getDisplaySize ();
+  
+  public native void pause (int milliseconds)
+         throws InterruptedIOException;
+
+  public native int enterTtyMode (int tty, String driver);
+  public native void enterTtyModeWithPath (String driver, int... ttys);
+  public native void leaveTtyMode ();
+  public native void setFocus (int tty);
+
+  protected native void writeText (int cursor, String text);
+  protected native void writeDots (byte[] dots);
+  public native void write (WriteArguments arguments);
+
+  public native Long readKey (boolean wait) throws InterruptedIOException;
+
+  public native long readKeyWithTimeout (int milliseconds)
+         throws InterruptedIOException, TimeoutException;
+
+  public native void ignoreKeys (long type, long[] keys);
+  public native void acceptKeys (long type, long[] keys);
+
+  public native void ignoreAllKeys ();
+  public native void acceptAllKeys ();
+
+  public native void ignoreKeyRanges (long[][] ranges);
+  public native void acceptKeyRanges (long[][] ranges);
+
+  public native void enterRawMode (String driver);
+  public native void leaveRawMode ();
+  public native int sendRaw (byte[] buffer);
+
+  public native int recvRaw (byte[] buffer)
+         throws InterruptedIOException;
+
+  public native Object getParameter (int parameter, long subparam, boolean global);
+  public native void setParameter (int parameter, long subparam, boolean global, Object value);
+  public native long watchParameter (int parameter, long subparam, boolean global, ParameterWatcher watcher);
+  public native static void unwatchParameter (long identifier);
+}
diff --git a/Bindings/Java/ConnectionSettings.java b/Bindings/Java/ConnectionSettings.java
new file mode 100644
index 0000000..5318d49
--- /dev/null
+++ b/Bindings/Java/ConnectionSettings.java
@@ -0,0 +1,137 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.io.File;
+
+public class ConnectionSettings extends NativeComponent {
+  public final static char HOST_PORT_SEPARATOR = ':';
+  public final static String DEFAULT_HOST_NAME = "";
+
+  public final static int MINIMUM_PORT_NUMBER =  0;
+  public final static int MAXIMUM_PORT_NUMBER = 99;
+  public final static int DEFAULT_PORT_NUMBER = MINIMUM_PORT_NUMBER;
+
+  public final static String DEFAULT_SERVER_HOST =
+    DEFAULT_HOST_NAME + HOST_PORT_SEPARATOR + DEFAULT_PORT_NUMBER;
+
+  private String serverHost = null;
+
+  public static void checkHostSpecification (String specification) {
+    int index = specification.indexOf(HOST_PORT_SEPARATOR);
+
+    if (index == 0) {
+      return;
+    }
+
+    if (index < 0) {
+      specification = String.format(
+        "%s%c%s", specification, HOST_PORT_SEPARATOR, DEFAULT_PORT_NUMBER
+      );
+    } else {
+      String number = specification.substring(index+1);
+
+      if (number.isEmpty()) {
+        throw new IllegalArgumentException("missing port number");
+      }
+
+      try {
+        Parse.asInt(
+          "port number", number,
+          MINIMUM_PORT_NUMBER, MAXIMUM_PORT_NUMBER
+        );
+      } catch (SyntaxException exception) {
+        throw new IllegalArgumentException(exception.getMessage());
+      }
+    }
+  }
+
+  public final ConnectionSettings setServerHost (String specification) {
+    checkHostSpecification(specification);
+    serverHost = specification;
+    return this;
+  }
+
+  public final String getServerHost () {
+    return serverHost;
+  }
+
+  public final static char AUTHENTICATION_SCHEME_SEPARATOR = '+';
+  public final static char AUTHENTICATION_OPERAND_PREFIX = ':';
+
+  public final static String AUTHENTICATION_SCHEME_KEYFILE = "keyfile";
+  public final static String AUTHENTICATION_SCHEME_NONE = "none";
+
+  private native static String getKeyfileDirectory ();
+  public final static String AUTHENTICATION_KEYFILE_DIRECTORY = getKeyfileDirectory();
+
+  private native static String getKeyfileName ();
+  public final static String AUTHENTICATION_KEYFILE_NAME = getKeyfileName();
+
+  public final static String DEFAULT_AUTHENTICATION_SCHEME;
+  static {
+    StringBuilder builder = new StringBuilder();
+
+    {
+      File file = new File(
+        AUTHENTICATION_KEYFILE_DIRECTORY,
+        AUTHENTICATION_KEYFILE_NAME
+      );
+
+      if (file.isFile() && file.canRead()) {
+        builder.append(AUTHENTICATION_SCHEME_KEYFILE)
+               .append(AUTHENTICATION_OPERAND_PREFIX)
+               .append(file.getAbsolutePath());
+      }
+    }
+
+    if (builder.length() > 0) builder.append(AUTHENTICATION_SCHEME_SEPARATOR);
+    builder.append(AUTHENTICATION_SCHEME_NONE);
+
+    DEFAULT_AUTHENTICATION_SCHEME = builder.toString();
+  }
+
+  private String authenticationScheme = null;
+
+  public static void checkAuthenticationScheme (String scheme) {
+  }
+
+  public final ConnectionSettings setAuthenticationScheme (String scheme) {
+    checkAuthenticationScheme(scheme);
+    authenticationScheme = scheme;
+    return this;
+  }
+
+  public final String getAuthenticationScheme () {
+    return authenticationScheme;
+  }
+
+  public ConnectionSettings () {
+    setServerHost(DEFAULT_SERVER_HOST);
+    setAuthenticationScheme(DEFAULT_AUTHENTICATION_SCHEME);
+  }
+
+  @Override
+  public String toString () {
+    return String.format(
+      "Server:%s Scheme:%s", serverHost, authenticationScheme
+    );
+  }
+}
diff --git a/Bindings/Java/CursorPositionUsage.java b/Bindings/Java/CursorPositionUsage.java
new file mode 100644
index 0000000..7d75745
--- /dev/null
+++ b/Bindings/Java/CursorPositionUsage.java
@@ -0,0 +1,30 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class CursorPositionUsage extends OperandUsage {
+  public CursorPositionUsage (int defaultValue) {
+    super(WriteArguments.CURSOR_POSITION);
+    setDefault(defaultValue);
+    setRangeMinimum(Parse.MINIMUM_CURSOR_POSITION);
+    addWord(Parse.NO_CURSOR, Constants.CURSOR_OFF);
+    addWord(Parse.LEAVE_CURSOR, Constants.CURSOR_LEAVE);
+  }
+}
diff --git a/Bindings/Java/DisplayNumberUsage.java b/Bindings/Java/DisplayNumberUsage.java
new file mode 100644
index 0000000..306407c
--- /dev/null
+++ b/Bindings/Java/DisplayNumberUsage.java
@@ -0,0 +1,29 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class DisplayNumberUsage extends OperandUsage {
+  public DisplayNumberUsage (int defaultValue) {
+    super(WriteArguments.DISPLAY_NUMBER);
+    setDefault(defaultValue);
+    setRangeMinimum(Parse.MINIMUM_DISPLAY_NUMBER);
+    addWord(Parse.DEFAULT_DISPLAY, Constants.DISPLAY_DEFAULT);
+  }
+}
diff --git a/Bindings/Java/DisplaySize.java b/Bindings/Java/DisplaySize.java
new file mode 100644
index 0000000..7b3423a
--- /dev/null
+++ b/Bindings/Java/DisplaySize.java
@@ -0,0 +1,47 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class DisplaySize {
+  private final int displayWidth;
+  private final int displayHeight;
+
+  public DisplaySize (int width, int height) {
+    displayWidth = width;
+    displayHeight = height; 
+  }
+
+  public DisplaySize (int[] dimensions) {
+    this(dimensions[0], dimensions[1]);
+  }
+
+  public int getWidth () {
+    return displayWidth;
+  }
+
+  public int getHeight () {
+    return displayHeight;
+  }
+
+  @Override
+  public String toString () {
+    return String.format("%dx%d", getWidth(), getHeight());
+  }
+}
diff --git a/Bindings/Java/DriverKeycode.java b/Bindings/Java/DriverKeycode.java
new file mode 100644
index 0000000..b50606b
--- /dev/null
+++ b/Bindings/Java/DriverKeycode.java
@@ -0,0 +1,58 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class DriverKeycode extends Keycode {
+  public native static boolean isPress (long code);
+  public native static long getValue (long code);
+  public native static int getGroup (long code);
+  public native static int getNumber (long code);
+
+  private native static int getNumberAny ();
+  public final static int KEY_NUMBER_ANY = getNumberAny();
+
+  public DriverKeycode (long code) {
+    super(code);
+  }
+
+  public final boolean isPress () {
+    return isPress(getCode());
+  }
+
+  public final long getValue () {
+    return getValue(getCode());
+  }
+
+  public final int getGroup () {
+    return getGroup(getCode());
+  }
+
+  public final int getNumber () {
+    return getNumber(getCode());
+  }
+
+  @Override
+  public String toString () {
+    return String.format(
+      "%s %s Grp:%d Num:%d",
+      Keycode.toString(getCode()), (isPress()? "press": "release"), getGroup(), getNumber()
+    );
+  }
+}
diff --git a/Bindings/Java/ExitException.java b/Bindings/Java/ExitException.java
new file mode 100644
index 0000000..95e7fa1
--- /dev/null
+++ b/Bindings/Java/ExitException.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class ExitException extends RuntimeException {
+  private final int exitCode;
+
+  public ExitException (int code) {
+    super(Integer.toString(code));
+    exitCode = code;
+  }
+
+  public final int getExitCode () {
+    return exitCode;
+  }
+}
diff --git a/Bindings/Java/ExternalException.java b/Bindings/Java/ExternalException.java
new file mode 100644
index 0000000..a0e6c6f
--- /dev/null
+++ b/Bindings/Java/ExternalException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class ExternalException extends ProgramException {
+  public ExternalException (String format, Object... arguments) {
+    super(format, arguments);
+  }
+}
diff --git a/Bindings/Java/GlobalParameter.java b/Bindings/Java/GlobalParameter.java
new file mode 100644
index 0000000..29deb91
--- /dev/null
+++ b/Bindings/Java/GlobalParameter.java
@@ -0,0 +1,31 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class GlobalParameter extends Parameter {
+  protected GlobalParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final boolean isGlobal () {
+    return true;
+  }
+}
diff --git a/Bindings/Java/HostException.java b/Bindings/Java/HostException.java
new file mode 100644
index 0000000..6af0ed3
--- /dev/null
+++ b/Bindings/Java/HostException.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class HostException extends ConnectException {
+  private final String serverHost;
+
+  public HostException (String host, String cause) {
+    super("%s: %s", cause, host);
+    serverHost = host;
+  }
+
+  public final String getHost () {
+    return serverHost;
+  }
+}
diff --git a/Bindings/Java/Keycode.java b/Bindings/Java/Keycode.java
new file mode 100644
index 0000000..ab2b5af
--- /dev/null
+++ b/Bindings/Java/Keycode.java
@@ -0,0 +1,42 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class Keycode extends NativeComponent {
+  private final long codeValue;
+
+  public Keycode (long code) {
+    super();
+    codeValue = code;
+  }
+
+  public final long getCode () {
+    return codeValue;
+  }
+
+  public static String toString (long code) {
+    return String.format("Code:%016X", code);
+  }
+
+  @Override
+  public String toString () {
+    return toString(getCode());
+  }
+}
diff --git a/Bindings/Java/KeywordMap.java b/Bindings/Java/KeywordMap.java
new file mode 100644
index 0000000..eb4485d
--- /dev/null
+++ b/Bindings/Java/KeywordMap.java
@@ -0,0 +1,83 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class KeywordMap<V> {
+  private final Map<String, V> valueMap = new LinkedHashMap<>();
+  private final Map<String, String> aliasMap = new HashMap<>();
+  private final static String AMBIGUOUS = "";
+
+  private static String normalizeKeyword (String keyword) {
+    return keyword.toLowerCase();
+  }
+
+  private final void addAliases (String keyword) {
+    String alias = keyword;
+
+    while (true) {
+      alias = alias.substring(0, alias.length()-1);
+      if (alias.isEmpty()) break;
+
+      String value = aliasMap.get(alias);
+      if (value == null) {
+        aliasMap.put(alias, keyword);
+      } else if (value == AMBIGUOUS) {
+        break;
+      } else {
+        aliasMap.put(alias, AMBIGUOUS);
+      }
+    }
+  }
+
+  public KeywordMap<V> put (String keyword, V value) {
+    keyword = normalizeKeyword(keyword);
+    valueMap.put(keyword, value);
+    addAliases(keyword);
+    return this;
+  }
+
+  public final V get (String keyword) {
+    keyword = normalizeKeyword(keyword);
+    V value = valueMap.get(keyword);
+    if (value != null) return value;
+
+    keyword = aliasMap.get(keyword);
+    if (keyword == null) return null;
+    if (keyword == AMBIGUOUS) return null;
+    return valueMap.get(keyword);
+  }
+
+  public final String[] getKeywords () {
+    Set<String> keywords = valueMap.keySet();
+    return keywords.toArray(new String[keywords.size()]);
+  }
+
+  public final boolean isEmpty () {
+    return valueMap.isEmpty();
+  }
+
+  public KeywordMap () {
+  }
+}
diff --git a/Bindings/Java/LocalParameter.java b/Bindings/Java/LocalParameter.java
new file mode 100644
index 0000000..1328c71
--- /dev/null
+++ b/Bindings/Java/LocalParameter.java
@@ -0,0 +1,31 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class LocalParameter extends Parameter {
+  protected LocalParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final boolean isGlobal () {
+    return false;
+  }
+}
diff --git a/Bindings/Java/LostConnectionException.java b/Bindings/Java/LostConnectionException.java
new file mode 100644
index 0000000..621313b
--- /dev/null
+++ b/Bindings/Java/LostConnectionException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class LostConnectionException extends RuntimeException {
+  public LostConnectionException () {
+    super();
+  }
+}
diff --git a/Bindings/Java/Makefile.in b/Bindings/Java/Makefile.in
new file mode 100644
index 0000000..4fa8195
--- /dev/null
+++ b/Bindings/Java/Makefile.in
@@ -0,0 +1,115 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+JAVA_OK = @JAVA_OK@
+JAVAC = @JAVAC@
+JAVADOC = @JAVADOC@
+JAR = @JAR@
+
+JAVA_JAR = @JAVA_JAR@
+JAVA_JAR_DIR = @JAVA_JAR_DIR@
+JAVA_JAR_DESTDIR = $(INSTALL_ROOT)
+
+JAVA_JNI = @JAVA_JNI@
+JAVA_JNI_DIR = @JAVA_JNI_DIR@
+JAVA_JNI_HDR = @JAVA_JNI_HDR@
+JAVA_JNI_INC = @JAVA_JNI_INC@
+JAVA_JNI_FLAGS = @JAVA_JNI_FLAGS@
+JAVA_JNI_DESTDIR = $(INSTALL_ROOT)
+
+all: jar jni doc
+
+JAVA_SOURCES = Constants.java $(SRC_DIR)/*.java $(SRC_DIR)/*/*.java
+
+JAVA_JNI_FILE = $(LIB_PFX)$(API_NAME)_java.$(LIB_EXT)
+jni: $(JAVA_JNI_FILE)
+
+$(JAVA_JNI_FILE): bindings.$O | $(API_NAME)
+	$(MKLIB:<name>=$(JAVA_JNI_FILE).$(API_VERSION)) $@ bindings.$O $(API_LDFLAGS)
+
+bindings.$O:
+	$(CC) $(LIBCFLAGS) $(JAVA_JNI_FLAGS) -o $@ -c $(SRC_DIR)/bindings.c
+
+JAVA_JAR_FILE = $(API_NAME).jar
+jar: $(JAVA_JAR_FILE)
+
+$(JAVA_JAR_FILE): classes.made
+	$(JAR) cfm $@ $(SRC_DIR)/manifest -C classes .
+
+JAVAC_LINT_OPTIONS = cast,deprecation,fallthrough,finally,overrides,rawtypes,unchecked
+
+classes.made: $(JAVA_SOURCES)
+	$(INSTALL_DIRECTORY) classes
+	$(JAVAC) -Xlint:$(JAVAC_LINT_OPTIONS) -d classes -classpath . $^
+	touch $@
+
+Constants.java: $(CONSTANTS_DEPENDENCIES)
+	$(AWK) $(CONSTANTS_ARGUMENTS) >$@
+
+doc: doc.made
+
+doc.made: $(JAVA_SOURCES)
+	$(INSTALL_DIRECTORY) doc
+	$(JAVADOC) -quiet -d doc -author -notimestamp -version -use $^
+	touch $@
+
+clean::
+	-rm -f Constants.java classes.made $(JAVA_JAR_FILE) $(JAVA_JNI_FILE)
+	-rm -f -r classes
+	-rm -f doc.made
+	-rm -f -r doc
+
+install: install-jar install-jni
+
+install-jar: install-jar-$(JAVA_JAR)
+
+install-jar-yes: jar
+	$(INSTALL_DIRECTORY) $(JAVA_JAR_DESTDIR)$(JAVA_JAR_DIR)
+	$(INSTALL) $(JAVA_JAR_FILE) $(JAVA_JAR_DESTDIR)$(JAVA_JAR_DIR)
+
+install-jar-no:
+	@echo jar not installed
+
+install-jni: install-jni-$(JAVA_JNI)
+
+install-jni-yes: jni
+	$(INSTALL_DIRECTORY) $(JAVA_JNI_DESTDIR)$(JAVA_JNI_DIR)
+	$(INSTALL) $(JAVA_JNI_FILE) $(JAVA_JNI_DESTDIR)$(JAVA_JNI_DIR)
+
+install-jni-no:
+	@echo jni not installed
+
+uninstall: uninstall-jar uninstall-jni
+
+uninstall-jar: uninstall-jar-$(JAVA_JAR)
+
+uninstall-jar-yes:
+	-rm -f -- $(JAVA_JAR_DESTDIR)$(JAVA_JAR_DIR)/$(JAVA_JAR_FILE)
+
+uninstall-jar-no:
+
+uninstall-jni: uninstall-jni-$(JAVA_JNI)
+
+uninstall-jni-yes:
+	-rm -f -- $(JAVA_JNI_DESTDIR)$(JAVA_JNI_DIR)/$(JAVA_JNI_FILE)
+
+uninstall-jni-no:
+
diff --git a/Bindings/Java/NativeComponent.java b/Bindings/Java/NativeComponent.java
new file mode 100644
index 0000000..98923fe
--- /dev/null
+++ b/Bindings/Java/NativeComponent.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class NativeComponent extends Component {
+  private native static void initializeNativeData ();
+
+  static {
+    System.loadLibrary("brlapi_java");
+    initializeNativeData();
+  }
+
+  protected NativeComponent () {
+    super();
+  }
+}
diff --git a/Bindings/Java/OperandException.java b/Bindings/Java/OperandException.java
new file mode 100644
index 0000000..e0268f3
--- /dev/null
+++ b/Bindings/Java/OperandException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class OperandException extends ProgramException {
+  public OperandException (String format, Object... arguments) {
+    super(format, arguments);
+  }
+}
diff --git a/Bindings/Java/OperandUsage.java b/Bindings/Java/OperandUsage.java
new file mode 100644
index 0000000..055e3a6
--- /dev/null
+++ b/Bindings/Java/OperandUsage.java
@@ -0,0 +1,216 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class OperandUsage {
+  private final String operandDescription;
+
+  public OperandUsage (String description) {
+    operandDescription = description;
+  }
+
+  public final String getOperandDescription () {
+    return operandDescription;
+  }
+
+  private Integer rangeMinimum = null;
+  private Integer rangeMaximum = null;
+  private String rangeUnits = null;
+  private String rangeComment = null;
+
+  public final OperandUsage setRangeMinimum (int minimum) {
+    rangeMinimum = minimum;
+    return this;
+  }
+
+  public final OperandUsage setRangeMaximum (int maximum) {
+    rangeMaximum = maximum;
+    return this;
+  }
+
+  public final OperandUsage setRange (int minimum, int maximum) {
+    return setRangeMinimum(minimum).setRangeMaximum(maximum);
+  }
+
+  public final OperandUsage setRangeUnits (String units) {
+    rangeUnits = units;
+    return this;
+  }
+
+  public final OperandUsage setRangeComment (String text) {
+    rangeComment = text;
+    return this;
+  }
+
+  private static class WordEntry {
+    public final String word;
+    public final Integer value;
+    public final String comment;
+
+    public WordEntry (String word, Integer value, String comment) {
+      this.word = word;
+      this.value = value;
+      this.comment = comment;
+    }
+  }
+
+  private final List<WordEntry> wordList = new LinkedList<>();
+
+  public final OperandUsage addWord (String word, Integer value, String comment) {
+    wordList.add(new WordEntry(word, value, comment));
+    return this;
+  }
+
+  public final OperandUsage addWord (String word, String comment) {
+    return addWord(word, null, comment);
+  }
+
+  public final OperandUsage addWord (String word, int value) {
+    return addWord(word, value, null);
+  }
+
+  public final OperandUsage addWord (String word) {
+    return addWord(word, null, null);
+  }
+
+  private String defaultWord = null;
+  private Integer defaultValue = null;
+
+  public final OperandUsage setDefault (String word) {
+    defaultWord = word;
+    defaultValue = null;
+    return this;
+  }
+
+  public final OperandUsage setDefault (int value) {
+    defaultWord = null;
+    defaultValue = value;
+    return this;
+  }
+
+  public final String getDefaultWord () {
+    if (defaultWord != null) return defaultWord;
+    if (defaultValue == null) return null;
+
+    for (WordEntry entry : wordList) {
+      if (entry.value == defaultValue) {
+        return (defaultWord = entry.word);
+      }
+    }
+
+    return defaultValue.toString();
+  }
+
+  public final Integer getDefaultValue () {
+    if (defaultValue != null) return defaultValue;
+    if (defaultWord == null) return null;
+
+    for (WordEntry entry : wordList) {
+      if (entry.word.equals(defaultWord)) {
+        return (defaultValue = entry.value);
+      }
+    }
+
+    return null;
+  }
+
+  public final StringBuilder appendTo (StringBuilder usage) {
+    usage.append('\n')
+         .append("The ").append(operandDescription).append(" operand must be");
+
+    boolean haveRangeMinimum = rangeMinimum != null;
+    boolean haveRangeMaximum = rangeMaximum != null;
+    boolean haveRange = haveRangeMinimum || haveRangeMaximum;
+
+    if (haveRange) {
+      if (rangeUnits != null) {
+        usage.append(" a number of ").append(rangeUnits);
+      } else {
+        usage.append(" an integer");
+      }
+
+      if (haveRangeMinimum) {
+        String phrase =
+          haveRangeMaximum?
+          "within the range":
+          "greater than or equal to";
+
+        usage.append(' ').append(phrase).append(' ').append(rangeMinimum);
+      }
+
+      if (haveRangeMaximum) {
+        String phrase =
+          haveRangeMinimum?
+          "through":
+          "less than or equal to";
+
+        usage.append(' ').append(phrase).append(' ').append(rangeMaximum);
+      }
+
+      if (rangeComment != null) {
+        usage.append(" (").append(rangeComment).append(')');
+      }
+    }
+
+    if (!wordList.isEmpty()) {
+      if (haveRange) usage.append(", or");
+
+      int count = wordList.size();
+      if (count > 1) usage.append(" any of");
+      int number = 0;
+
+      for (WordEntry entry : wordList) {
+        number += 1;
+
+        if (number > 1) {
+          if (count > 2) usage.append(',');
+          if (number == count) usage.append(" or");
+        }
+
+        usage.append(' ').append(entry.word);
+        String comment = entry.comment;
+
+        if (comment != null) {
+          usage.append(" (").append(comment).append(')');
+        }
+      }
+    }
+
+    usage.append(". ");
+
+    {
+      String word = getDefaultWord();
+
+      if (word != null) {
+        usage.append("If not specified, ").append(word).append(" is assumed. ");
+      }
+    }
+
+    return usage;
+  }
+
+  @Override
+  public String toString () {
+    return appendTo(new StringBuilder()).toString();
+  }
+}
diff --git a/Bindings/Java/Parameter.java b/Bindings/Java/Parameter.java
new file mode 100644
index 0000000..9afe4b5
--- /dev/null
+++ b/Bindings/Java/Parameter.java
@@ -0,0 +1,249 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class Parameter extends ParameterComponent {
+  private final ConnectionBase clientConnection;
+
+  protected Parameter (ConnectionBase connection) {
+    super();
+    clientConnection = connection;
+  }
+
+  private String parameterLabel = null;
+  public String getLabel () {
+    synchronized (this) {
+      if (parameterLabel == null) {
+        parameterLabel = Strings.wordify(
+          Strings.replaceAll(getClass().getSimpleName(), "Parameter$", "")
+        );
+      }
+    }
+
+    return parameterLabel;
+  }
+
+  private String parameterName = null;
+  public String getName () {
+    synchronized (this) {
+      if (parameterName == null) {
+        parameterName = toOperandName(getLabel());
+      }
+    }
+
+    return parameterName;
+  }
+
+  public abstract int getParameter ();
+  public abstract boolean isGlobal ();
+
+  public boolean isHidable () {
+    return false;
+  }
+
+  protected final Object getValue (long subparam) {
+    return clientConnection.getParameter(getParameter(), subparam, isGlobal());
+  }
+
+  protected final Object getValue () {
+    return getValue(0);
+  }
+
+  protected final void setValue (long subparam, Object value) {
+    clientConnection.setParameter(getParameter(), subparam, isGlobal(), value);
+  }
+
+  protected final void setValue (Object value) {
+    setValue(0, value);
+  }
+
+  public Object get (long subparam) {
+    return null;
+  }
+
+  public String toString (long subparam) {
+    return toString(get(subparam));
+  }
+
+  public Object get () {
+    return null;
+  }
+
+  @Override
+  public String toString () {
+    return toString(get());
+  }
+
+  public interface Settable {
+  }
+
+  public final boolean isSettable () {
+    return this instanceof Settable;
+  }
+
+  public interface StringSettable extends Settable {
+    public void set (String value) throws SyntaxException;
+  }
+
+  public interface BooleanSettable extends Settable {
+    public void set (boolean value);
+  }
+
+  public interface ByteSettable extends Settable {
+    public void set (byte value);
+
+    public default byte getMinimum () {
+      return 0;
+    }
+
+    public default byte getMaximum () {
+      return Byte.MAX_VALUE;
+    }
+  }
+
+  public interface ShortSettable extends Settable {
+    public void set (short value);
+
+    public default short getMinimum () {
+      return 0;
+    }
+
+    public default short getMaximum () {
+      return Short.MAX_VALUE;
+    }
+  }
+
+  public interface IntSettable extends Settable {
+    public void set (int value);
+
+    public default int getMinimum () {
+      return 0;
+    }
+
+    public default int getMaximum () {
+      return Integer.MAX_VALUE;
+    }
+  }
+
+  public interface LongSettable extends Settable {
+    public void set (long value);
+
+    public default long getMinimum () {
+      return 0;
+    }
+
+    public default long getMaximum () {
+      return Long.MAX_VALUE;
+    }
+  }
+
+  protected final String getParseDescription () {
+    return getName() + " value";
+  }
+
+  public void set (String value) throws OperandException {
+    if (!isSettable()) {
+      throw new SemanticException("parameter is not settable: %s", getName());
+    }
+
+    if (this instanceof StringSettable) {
+      StringSettable settable = (StringSettable)this;
+      settable.set(value);
+      return;
+    }
+
+    if (this instanceof BooleanSettable) {
+      BooleanSettable settable = (BooleanSettable)this;
+      settable.set(Parse.asBoolean(getParseDescription(), value));
+      return;
+    }
+
+    if (this instanceof ByteSettable) {
+      ByteSettable settable = (ByteSettable)this;
+      settable.set(Parse.asByte(getParseDescription(), value, settable.getMinimum(), settable.getMaximum()));
+      return;
+    }
+
+    if (this instanceof ShortSettable) {
+      ShortSettable settable = (ShortSettable)this;
+      settable.set(Parse.asShort(getParseDescription(), value, settable.getMinimum(), settable.getMaximum()));
+      return;
+    }
+
+    if (this instanceof IntSettable) {
+      IntSettable settable = (IntSettable)this;
+      settable.set(Parse.asInt(getParseDescription(), value, settable.getMinimum(), settable.getMaximum()));
+      return;
+    }
+
+    if (this instanceof LongSettable) {
+      LongSettable settable = (LongSettable)this;
+      settable.set(Parse.asLong(getParseDescription(), value, settable.getMinimum(), settable.getMaximum()));
+      return;
+    }
+
+    {
+      Class<?> type = Settable.class;
+
+      for (Class<?> i : getClass().getInterfaces()) {
+        if (type.isAssignableFrom(i)) {
+          type = i;
+          break;
+        }
+      }
+
+      throw new UnsupportedOperationException(
+        String.format(
+          "set not supported: %s: %s", getName(), type.getSimpleName()
+        )
+      );
+    }
+  }
+
+  public final static class WatcherHandle implements AutoCloseable {
+    private long watchIdentifier;
+
+    private WatcherHandle (long identifier) {
+      watchIdentifier = identifier;
+    }
+
+    @Override
+    public void close () {
+      synchronized (this) {
+        if (watchIdentifier != 0) {
+          ConnectionBase.unwatchParameter(watchIdentifier);
+          watchIdentifier = 0;
+        }
+      }
+    }
+  }
+
+  public final WatcherHandle watch (long subparam, ParameterWatcher watcher) {
+    return new WatcherHandle(
+      clientConnection.watchParameter(
+        getParameter(), subparam, isGlobal(), watcher
+      )
+    );
+  }
+
+  public final WatcherHandle watch (ParameterWatcher watcher) {
+    return watch(0, watcher);
+  }
+}
diff --git a/Bindings/Java/ParameterComponent.java b/Bindings/Java/ParameterComponent.java
new file mode 100644
index 0000000..6f75619
--- /dev/null
+++ b/Bindings/Java/ParameterComponent.java
@@ -0,0 +1,139 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.Arrays;
+
+public abstract class ParameterComponent extends Component {
+  protected ParameterComponent () {
+    super();
+  }
+
+  public static String asString (Object value) {
+    return (String)value;
+  }
+
+  public static boolean[] asBooleanArray (Object value) {
+    return (boolean[])value;
+  }
+
+  public static byte[] asByteArray (Object value) {
+    return (byte[])value;
+  }
+
+  public static short[] asShortArray (Object value) {
+    return (short[])value;
+  }
+
+  public static int[] asIntArray (Object value) {
+    return (int[])value;
+  }
+
+  public static long[] asLongArray (Object value) {
+    return (long[])value;
+  }
+
+  public static boolean asBoolean (Object value) {
+    return asBooleanArray(value)[0];
+  }
+
+  public static byte asByte (Object value) {
+    return asByteArray(value)[0];
+  }
+
+  public static short asShort (Object value) {
+    return asShortArray(value)[0];
+  }
+
+  public static int asInt (Object value) {
+    return asIntArray(value)[0];
+  }
+
+  public static long asLong (Object value) {
+    return asLongArray(value)[0];
+  }
+
+  public static DisplaySize asDisplaySize (Object value) {
+    return new DisplaySize(asIntArray(value));
+  }
+
+  public static BitMask asBitMask (Object value) {
+    return new BitMask(asByteArray(value));
+  }
+
+  public static RowCells asRowCells (Object value) {
+    return new RowCells(asByteArray(value));
+  }
+
+  public static String asDots (byte cell) {
+    if (cell == 0) return "0";
+
+    StringBuilder numbers = new StringBuilder();
+    int dots = cell & BYTE_MASK;
+    int dot = 1;
+
+    while (true) {
+      if ((dots & 1) != 0) numbers.append(dot);
+      if ((dots >>= 1) == 0) break;
+      dot += 1;
+    }
+
+    return numbers.toString();
+  }
+
+  public static String asDots (byte[] cells) {
+    int count = cells.length;
+    String[] dots = new String[count];
+
+    for (int i=0; i<count; i+=1) {
+      dots[i] = asDots(cells[i]);
+    }
+
+    return Arrays.toString(dots);
+  }
+
+  public static String toString (Object value) {
+    if (value == null) return null;
+
+    if (value.getClass().isArray()) {
+      if (value instanceof boolean[]) {
+        return Arrays.toString((boolean[])value);
+      }
+
+      if (value instanceof byte[]) {
+        return Arrays.toString((byte[])value);
+      }
+
+      if (value instanceof short[]) {
+        return Arrays.toString((short[])value);
+      }
+
+      if (value instanceof int[]) {
+        return Arrays.toString((int[])value);
+      }
+
+      if (value instanceof long[]) {
+        return Arrays.toString((long[])value);
+      }
+    }
+
+    return value.toString();
+  }
+}
diff --git a/Bindings/Java/ParameterWatcher.java b/Bindings/Java/ParameterWatcher.java
new file mode 100644
index 0000000..a206136
--- /dev/null
+++ b/Bindings/Java/ParameterWatcher.java
@@ -0,0 +1,28 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class ParameterWatcher extends ParameterComponent {
+  public abstract void onParameterUpdated (
+    int parameter,
+    long subparam,
+    Object value
+  );
+}
diff --git a/Bindings/Java/Parameters.java b/Bindings/Java/Parameters.java
new file mode 100644
index 0000000..556a438
--- /dev/null
+++ b/Bindings/Java/Parameters.java
@@ -0,0 +1,168 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import org.a11y.brlapi.parameters.*;
+
+public class Parameters extends ParameterComponent {
+  public final ServerVersionParameter serverVersion;
+  public final ClientPriorityParameter clientPriority;
+  public final DriverNameParameter driverName;
+  public final DriverCodeParameter driverCode;
+  public final DriverVersionParameter driverVersion;
+  public final DeviceModelParameter deviceModel;
+  public final DeviceCellSizeParameter deviceCellSize;
+  public final DisplaySizeParameter displaySize;
+  public final DeviceIdentifierParameter deviceIdentifier;
+  public final DeviceSpeedParameter deviceSpeed;
+  public final DeviceOnlineParameter deviceOnline;
+  public final RetainDotsParameter retainDots;
+  public final ComputerBrailleCellSizeParameter computerBrailleCellSize;
+  public final LiteraryBrailleParameter literaryBraille;
+  public final CursorDotsParameter cursorDots;
+  public final CursorBlinkPeriodParameter cursorBlinkPeriod;
+  public final CursorBlinkPercentageParameter cursorBlinkPercentage;
+  public final RenderedCellsParameter renderedCells;
+  public final SkipIdenticalLinesParameter skipIdenticalLines;
+  public final AudibleAlertsParameter audibleAlerts;
+  public final ClipboardContentParameter clipboardContent;
+  public final BoundCommandKeycodesParameter boundCommandKeycodes;
+  public final CommandKeycodeNameParameter commandKeycodeName;
+  public final CommandKeycodeSummaryParameter commandKeycodeSummary;
+  public final DefinedDriverKeycodesParameter definedDriverKeycodes;
+  public final DriverKeycodeNameParameter driverKeycodeName;
+  public final DriverKeycodeSummaryParameter driverKeycodeSummary;
+  public final ComputerBrailleRowsMaskParameter computerBrailleRowsMask;
+  public final ComputerBrailleRowCellsParameter computerBrailleRowCells;
+  public final ComputerBrailleTableParameter computerBrailleTable;
+  public final LiteraryBrailleTableParameter literaryBrailleTable;
+  public final MessageLocaleParameter messageLocale;
+
+  public Parameters (ConnectionBase connection) {
+    super();
+
+    serverVersion = new ServerVersionParameter(connection);
+    clientPriority = new ClientPriorityParameter(connection);
+    driverName = new DriverNameParameter(connection);
+    driverCode = new DriverCodeParameter(connection);
+    driverVersion = new DriverVersionParameter(connection);
+    deviceModel = new DeviceModelParameter(connection);
+    deviceCellSize = new DeviceCellSizeParameter(connection);
+    displaySize = new DisplaySizeParameter(connection);
+    deviceIdentifier = new DeviceIdentifierParameter(connection);
+    deviceSpeed = new DeviceSpeedParameter(connection);
+    deviceOnline = new DeviceOnlineParameter(connection);
+    retainDots = new RetainDotsParameter(connection);
+    computerBrailleCellSize = new ComputerBrailleCellSizeParameter(connection);
+    literaryBraille = new LiteraryBrailleParameter(connection);
+    cursorDots = new CursorDotsParameter(connection);
+    cursorBlinkPeriod = new CursorBlinkPeriodParameter(connection);
+    cursorBlinkPercentage = new CursorBlinkPercentageParameter(connection);
+    renderedCells = new RenderedCellsParameter(connection);
+    skipIdenticalLines = new SkipIdenticalLinesParameter(connection);
+    audibleAlerts = new AudibleAlertsParameter(connection);
+    clipboardContent = new ClipboardContentParameter(connection);
+    boundCommandKeycodes = new BoundCommandKeycodesParameter(connection);
+    commandKeycodeName = new CommandKeycodeNameParameter(connection);
+    commandKeycodeSummary = new CommandKeycodeSummaryParameter(connection);
+    definedDriverKeycodes = new DefinedDriverKeycodesParameter(connection);
+    driverKeycodeName = new DriverKeycodeNameParameter(connection);
+    driverKeycodeSummary = new DriverKeycodeSummaryParameter(connection);
+    computerBrailleRowsMask = new ComputerBrailleRowsMaskParameter(connection);
+    computerBrailleRowCells = new ComputerBrailleRowCellsParameter(connection);
+    computerBrailleTable = new ComputerBrailleTableParameter(connection);
+    literaryBrailleTable = new LiteraryBrailleTableParameter(connection);
+    messageLocale = new MessageLocaleParameter(connection);
+  }
+
+  private final Parameter[] newParameterArray () {
+    Class<Parameters> type = Parameters.class;
+
+    Field[] fields = type.getFields();
+    int fieldCount = fields.length;
+
+    Parameter[] parameters = new Parameter[fieldCount];
+    int parameterCount = 0;
+
+    for (Field field : fields) {
+      int modifiers = field.getModifiers();
+      if ((modifiers & Modifier.PUBLIC) == 0) continue;
+      if ((modifiers & Modifier.FINAL) == 0) continue;
+      if (!type.equals(field.getDeclaringClass())) continue;
+
+      try {
+        parameters[parameterCount] = (Parameter)field.get(this);
+        parameterCount += 1;
+      } catch (IllegalAccessException exception) {
+        continue;
+      }
+    }
+
+    Parameter[] result = new Parameter[parameterCount];
+    System.arraycopy(parameters, 0, result, 0, parameterCount);
+    return result;
+  }
+
+  private Parameter[] parameterArray = null;
+  public final Parameter[] get () {
+    synchronized (this) {
+      if (parameterArray == null) {
+        parameterArray = newParameterArray();
+      }
+    }
+
+    int count = parameterArray.length;
+    Parameter[] result = new Parameter[count];
+    System.arraycopy(parameterArray, 0, result, 0, count);
+    return result;
+  }
+
+  private final KeywordMap<Parameter> newParameterNameMap () {
+    KeywordMap<Parameter> map = new KeywordMap<>();
+
+    for (Parameter parameter : get()) {
+      map.put(parameter.getName(), parameter);
+    }
+
+    return map;
+  }
+
+  private KeywordMap<Parameter> parameterNameMap = null;
+  public final Parameter get (String name) {
+    synchronized (this) {
+      if (parameterNameMap == null) {
+        parameterNameMap = newParameterNameMap();
+      }
+    }
+
+    return parameterNameMap.get(name);
+  }
+
+  public static void sortByName (Parameter[] parameters) {
+    Arrays.sort(parameters,
+      (parameter1, parameter2) -> {
+        return parameter1.getName().compareTo(parameter2.getName());
+      }
+    );
+  }
+}
diff --git a/Bindings/Java/Parse.java b/Bindings/Java/Parse.java
new file mode 100644
index 0000000..979e466
--- /dev/null
+++ b/Bindings/Java/Parse.java
@@ -0,0 +1,231 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class Parse {
+  private Parse () {
+    super();
+  }
+
+  private final static KeywordMap<Boolean> booleanKeywords = new KeywordMap<>();
+  static {
+    Boolean FALSE = false;
+    Boolean TRUE = true;
+
+    booleanKeywords.put("false", FALSE);
+    booleanKeywords.put("true", TRUE);
+
+    booleanKeywords.put("off", FALSE);
+    booleanKeywords.put("on", TRUE);
+
+    booleanKeywords.put("no", FALSE);
+    booleanKeywords.put("yes", TRUE);
+
+    booleanKeywords.put("0", FALSE);
+    booleanKeywords.put("1", TRUE);
+  }
+
+  public static Boolean asBoolean (String description, String operand)
+         throws SyntaxException
+  {
+    Boolean value = booleanKeywords.get(operand);
+    if (value != null) return value;
+    throw new SyntaxException("%s is not a boolean: %s", description, operand);
+  }
+
+  public static void checkMinimum (String description, long value, long minimum)
+         throws SyntaxException
+  {
+    if (value < minimum) {
+      throw new SyntaxException(
+        "%s is less than %d: %d", description, minimum, value
+      );
+    }
+  }
+
+  public static void checkMaximum (String description, long value, long maximum)
+         throws SyntaxException
+  {
+    if (value > maximum) {
+      throw new SyntaxException(
+        "%s is greater than %d: %d", description, maximum, value
+      );
+    }
+  }
+
+  public static void checkRange (String description, long value, long minimum, long maximum)
+         throws SyntaxException
+  {
+    checkMinimum(description, value, minimum);
+    checkMaximum(description, value, maximum);
+  }
+
+  private static Number asNumber (String description, String operand, long minimum, long maximum)
+          throws SyntaxException
+  {
+    long value;
+
+    try {
+      value = Long.valueOf(operand);
+      checkRange(description, value, minimum, maximum);
+      return Long.valueOf(value);
+    } catch (NumberFormatException exception) {
+      throw new SyntaxException(
+        "%s is not an integer: %s", description, operand
+      );
+    }
+  }
+
+  public final static byte DEFAULT_RANGE_MINIMUM = 0;
+
+  public static long asLong (String description, String operand, long minimum, long maximum)
+         throws SyntaxException
+  {
+    return asNumber(description, operand, minimum, maximum).longValue();
+  }
+
+  public static long asLong (String description, String operand, long minimum)
+         throws SyntaxException
+  {
+    return asLong(description, operand, minimum, Long.MAX_VALUE);
+  }
+
+  public static long asLong (String description, String operand)
+         throws SyntaxException
+  {
+    return asLong(description, operand, DEFAULT_RANGE_MINIMUM);
+  }
+
+  public static int asInt (String description, String operand, int minimum, int maximum)
+         throws SyntaxException
+  {
+    return asNumber(description, operand, minimum, maximum).intValue();
+  }
+
+  public static int asInt (String description, String operand, int minimum)
+         throws SyntaxException
+  {
+    return asInt(description, operand, minimum, Integer.MAX_VALUE);
+  }
+
+  public static int asInt (String description, String operand)
+         throws SyntaxException
+  {
+    return asInt(description, operand, DEFAULT_RANGE_MINIMUM);
+  }
+
+  public static short asShort (String description, String operand, short minimum, short maximum)
+         throws SyntaxException
+  {
+    return asNumber(description, operand, minimum, maximum).shortValue();
+  }
+
+  public static short asShort (String description, String operand, short minimum)
+         throws SyntaxException
+  {
+    return asShort(description, operand, minimum, Short.MAX_VALUE);
+  }
+
+  public static short asShort (String description, String operand)
+         throws SyntaxException
+  {
+    return asShort(description, operand, DEFAULT_RANGE_MINIMUM);
+  }
+
+  public static byte asByte (String description, String operand, byte minimum, byte maximum)
+         throws SyntaxException
+  {
+    return asNumber(description, operand, minimum, maximum).byteValue();
+  }
+
+  public static byte asByte (String description, String operand, byte minimum)
+         throws SyntaxException
+  {
+    return asByte(description, operand, minimum, Byte.MAX_VALUE);
+  }
+
+  public static byte asByte (String description, String operand)
+         throws SyntaxException
+  {
+    return asByte(description, operand, DEFAULT_RANGE_MINIMUM);
+  }
+
+  public static byte asDots (String description, String operand)
+         throws SyntaxException
+  {
+    if (operand.equals("0")) return 0;
+
+    byte dots = 0;
+    String numbers = "12345678";
+
+    for (char number : operand.toCharArray()) {
+      int index = numbers.indexOf(number);
+      if (index < 0) {
+        throw new SyntaxException(
+          "%s contains an invalid dot number: %s (%c)", description, operand, number
+        );
+      }
+
+      int dot = 1 << index;
+      if ((dots & dot) != 0) {
+        throw new SyntaxException(
+          "%s contains a duplicate dot number: %s (%c)", description, operand, number
+        );
+      }
+
+      dots |= dot;
+    }
+
+    return dots;
+  }
+
+  public final static int MINIMUM_CURSOR_POSITION =  1;
+  public final static String NO_CURSOR = "no";
+  public final static String LEAVE_CURSOR = "leave";
+
+  public static int asCursorPosition (String operand) throws SyntaxException {
+    if (Strings.isAbbreviation(NO_CURSOR, operand)) {
+      return Constants.CURSOR_OFF;
+    }
+
+    if (Strings.isAbbreviation(LEAVE_CURSOR, operand)) {
+      return Constants.CURSOR_LEAVE;
+    }
+
+    return asInt(
+      WriteArguments.CURSOR_POSITION,
+      operand, MINIMUM_CURSOR_POSITION
+    );
+  }
+
+  public final static int MINIMUM_DISPLAY_NUMBER = 1;
+  public final static String DEFAULT_DISPLAY = "default";
+
+  public static int asDisplayNumber (String operand) throws SyntaxException {
+    if (Strings.isAbbreviation(DEFAULT_DISPLAY, operand)) {
+      return Constants.DISPLAY_DEFAULT;
+    }
+
+    return asInt(
+      WriteArguments.DISPLAY_NUMBER,
+      operand, MINIMUM_DISPLAY_NUMBER
+    );
+  }
+}
diff --git a/Bindings/Java/Program.java b/Bindings/Java/Program.java
new file mode 100644
index 0000000..c264b49
--- /dev/null
+++ b/Bindings/Java/Program.java
@@ -0,0 +1,321 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+
+public abstract class Program extends ProgramComponent implements Runnable {
+  protected abstract void runProgram () throws ProgramException;
+
+  private String programName = null;
+
+  public final boolean isClient () {
+    return isClient(this);
+  }
+
+  public final String getProgramName() {
+    if (programName == null) return getObjectName();
+    return programName;
+  }
+
+  public final Program setProgramName(String name) {
+    programName = name;
+    return this;
+  }
+
+  protected final void writeProgramMessage (String format, Object... arguments) {
+    System.err.println((getObjectName() + ": " + String.format(format, arguments)));
+  }
+
+  protected static class Option {
+    public final static char PREFIX_CHARACTER = '-';
+
+    public interface Handler {
+      public void handleOption (String[] operands) throws SyntaxException;
+    }
+
+    private final String optionName;
+    private final Handler optionHandler;
+    private final String[] operandDescriptions;
+
+    public Option (String name, Handler handler, String... operands) {
+      optionName = name;
+      optionHandler = handler;
+      operandDescriptions = operands;
+    }
+
+    public final String getName () {
+      return optionName;
+    }
+
+    public final Handler getHandler () {
+      return optionHandler;
+    }
+
+    public final String[] getOperands () {
+      int count = operandDescriptions.length;
+      String[] result = new String[count];
+      System.arraycopy(operandDescriptions, 0, result, 0, count);
+      return result;
+    }
+  }
+
+  private final String[] programArguments;
+  private final KeywordMap<Option> programOptions = new KeywordMap<>();
+
+  protected final void addOption (String name, Option.Handler handler, String... operands) {
+    programOptions.put(name, new Option(name, handler, operands));
+  }
+
+  private List<String> requiredParameters = null;
+  private List<String> optionalParameters = null;
+  private boolean haveRepeatingParameter = false;
+
+  protected final void addRequiredParameters (String... parameters) {
+    if (optionalParameters != null) {
+      throw new IllegalStateException("optional parameters already added");
+    }
+
+    if (requiredParameters == null) {
+      requiredParameters = new ArrayList<>();
+    }
+
+    for (String parameter : parameters) {
+      requiredParameters.add(parameter);
+    }
+  }
+
+  protected final void addOptionalParameters (String... parameters) {
+    if (haveRepeatingParameter) {
+      throw new IllegalStateException("repeating parameter already added");
+    }
+
+    if (optionalParameters == null) {
+      optionalParameters = new ArrayList<>();
+    }
+
+    for (String parameter : parameters) {
+      optionalParameters.add(parameter);
+    }
+  }
+
+  protected final void addRepeatingParameter (String parameter) {
+    addOptionalParameters(parameter);
+    haveRepeatingParameter = true;
+  }
+
+  public String getPurpose() {
+    return null;
+  }
+
+  protected void extendUsageSummary(StringBuilder usage) {}
+
+  public final static char USAGE_OPTIONAL_BEGIN = '[';
+  public final static char USAGE_OPTIONAL_END = ']';
+  public final static String USAGE_REPEATING_INDICATOR = "...";
+
+  public final String getUsageSummary () {
+    StringBuilder usage = new StringBuilder();
+    boolean haveOptions = !programOptions.isEmpty();
+
+    {
+      String pattern = "^(.*)(\\p{Upper}.*)$";
+      String name = getObjectName();
+      Matcher matcher = Strings.getMatcher(pattern, name);
+      String phrase;
+
+      if (matcher.matches()) {
+        name = matcher.group(1);
+        phrase = "the " + name + ' ' + matcher.group(2);
+      } else {
+        phrase = name;
+      }
+
+      usage.append("Usage Summary for ").append(phrase);
+    }
+
+    {
+      usage.append("\nSyntax: ").append(getProgramName());
+      int start = usage.length();
+
+      if (haveOptions) {
+        usage.append(' ').append(USAGE_OPTIONAL_BEGIN)
+             .append(Option.PREFIX_CHARACTER).append("option")
+             .append(' ').append(USAGE_REPEATING_INDICATOR)
+             .append(USAGE_OPTIONAL_END);
+      }
+
+      if (requiredParameters != null) {
+        for (String parameter : requiredParameters) {
+          usage.append(' ').append(toOperandName(parameter));
+        }
+      }
+
+      if (optionalParameters != null) {
+        for (String parameter : optionalParameters) {
+          usage.append(' ').append(USAGE_OPTIONAL_BEGIN)
+               .append(toOperandName(parameter));
+        }
+
+        if (haveRepeatingParameter) {
+          usage.append(' ').append(USAGE_REPEATING_INDICATOR);
+        }
+
+        for (int i=optionalParameters.size(); i>0; i-=1) {
+          usage.append(USAGE_OPTIONAL_END);
+        }
+      }
+
+      if (usage.length() == start) usage.append(" (no arguments)");
+    }
+
+    {
+      String purpose = getPurpose();
+
+      if ((purpose != null) && !purpose.isEmpty()) {
+        usage.append("\n\n").append(purpose);
+      }
+    }
+
+    if (haveOptions) {
+      usage.append("\n\nThese options may be specified:");
+
+      for (String name : programOptions.getKeywords()) {
+        Option option = programOptions.get(name);
+        usage.append("\n  ").append(Option.PREFIX_CHARACTER).append(name);
+
+        for (String operand : option.getOperands()) {
+          usage.append(' ').append(toOperandName(operand));
+        }
+      }
+    }
+
+    {
+      StringBuilder extension = new StringBuilder();
+      extendUsageSummary(extension);
+
+      String text = Strings.formatParagraphs(extension.toString());
+      if (!text.isEmpty()) usage.append("\n\n").append(text);
+    }
+
+    return usage.toString();
+  }
+
+  protected Program (String... arguments) {
+    super();
+    programArguments = arguments;
+
+    addOption("help",
+      (operands) -> {
+        printf("%s\n", getUsageSummary());
+        throw new ExitException(EXIT_CODE_SUCCESS);
+      }
+    );
+  }
+
+  protected void processParameters (String[] parameters)
+            throws SyntaxException
+  {
+    if (parameters.length > 0) {
+      throw new TooManyParametersException(parameters);
+    }
+  }
+
+  private final void processArguments (String[] arguments)
+          throws SyntaxException
+  {
+    int argumentCount = arguments.length;
+    int argumentIndex = 0;
+
+    while (argumentIndex < argumentCount) {
+      String argument = arguments[argumentIndex];
+      if (argument.isEmpty()) break;
+      if (argument.charAt(0) != Option.PREFIX_CHARACTER) break;
+
+      if (argument.length() == 1) {
+        argumentIndex += 1;
+        break;
+      }
+
+      String name = argument.substring(1).toLowerCase();
+      Option option = programOptions.get(name);
+
+      if (option == null) {
+        throw new SyntaxException("unknown option: %s", argument);
+      }
+
+      String[] operandDescriptions = option.getOperands();
+      int operandCount = operandDescriptions.length;
+
+      {
+        int index = argumentCount - argumentIndex - 1;
+
+        if (index < operandCount) {
+          throw new SyntaxException(
+            "missing %s: %c%s",
+            operandDescriptions[index],
+            Option.PREFIX_CHARACTER,
+            option.getName()
+          );
+        }
+      }
+
+      String[] operands = new String[operandCount];
+      System.arraycopy(arguments, argumentIndex+1, operands, 0, operandCount);
+      option.getHandler().handleOption(operands);
+
+      argumentIndex += 1 + operandCount;
+    }
+
+    int parameterCount = argumentCount - argumentIndex;
+    String[] parameters = new String[parameterCount];
+    System.arraycopy(arguments, argumentIndex, parameters, 0, parameterCount);
+    processParameters(parameters);
+  }
+
+  protected void onProgramException (ProgramException exception) {
+    writeProgramMessage("%s", exception.getMessage());
+    int exitCode;
+
+    if (exception instanceof SyntaxException) {
+      exitCode = EXIT_CODE_SYNTAX;
+    } else if (exception instanceof SemanticException) {
+      exitCode = EXIT_CODE_SEMANTIC;
+    } else if (exception instanceof ExternalException) {
+      exitCode = EXIT_CODE_EXTERNAL;
+    } else {
+      exitCode = EXIT_CODE_INTERNAL;
+    }
+
+    throw new ExitException(exitCode);
+  }
+
+  @Override
+  public final void run () {
+    try {
+      processArguments(programArguments);
+      runProgram();
+    } catch (ProgramException exception) {
+      onProgramException(exception);
+    }
+  }
+}
diff --git a/Bindings/Java/ProgramComponent.java b/Bindings/Java/ProgramComponent.java
new file mode 100644
index 0000000..7448b25
--- /dev/null
+++ b/Bindings/Java/ProgramComponent.java
@@ -0,0 +1,40 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public abstract class ProgramComponent extends Component {
+  protected ProgramComponent () {
+    super();
+  }
+
+  public static boolean isClient (Class <? extends Program> type) {
+    return Client.class.isAssignableFrom(type);
+  }
+
+  public static boolean isClient (Program program) {
+    return isClient(program.getClass());
+  }
+
+  public final static int EXIT_CODE_SUCCESS  = 0;
+  public final static int EXIT_CODE_SYNTAX   = 2;
+  public final static int EXIT_CODE_SEMANTIC = 3;
+  public final static int EXIT_CODE_EXTERNAL = 8;
+  public final static int EXIT_CODE_INTERNAL = 9;
+}
diff --git a/Bindings/Java/ProgramException.java b/Bindings/Java/ProgramException.java
new file mode 100644
index 0000000..6335010
--- /dev/null
+++ b/Bindings/Java/ProgramException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class ProgramException extends Exception {
+  public ProgramException (String format, Object... arguments) {
+    super(String.format(format, arguments));
+  }
+}
diff --git a/Bindings/Java/README b/Bindings/Java/README
new file mode 100644
index 0000000..ce9ddc8
--- /dev/null
+++ b/Bindings/Java/README
@@ -0,0 +1,50 @@
+apitest {program | client} [argument ...]
+
+Programs (see the programs/ subdirectory):
+  -help
+  version
+
+Clients (see the clients/ subdirectory):
+  -help
+  -server host[:port] (default is localhost:0)
+  -authorization scheme+...
+  api-error
+  api-exception
+    -wait seconds (default is 5)
+  bound-commands
+  computer-braille
+  driver-keys
+  echo [tty ...]
+    -commands | -keys
+    -values | -names
+    -reads count (default is 10)
+    -timeout seconds (default is 10)
+  get-driver
+  get-model
+  get-size
+  list-parameters [parameter [subparam]]
+  pause
+    -wait seconds (default is 5)
+  set-parameter parameter value
+  write-arguments
+    -wait seconds (default is 5)
+    -text text
+    -begin cell# (default is 1)
+    -length #cells (default is largest size of: text, and-mask, or-mask)
+    -cursor {leave | no | cell#}
+    -display {default | display#}
+    -fix | -nofix
+    -render
+  write-dots [dots ...]
+    -wait seconds (default is 5)
+  write-text [text ...]
+    -wait seconds (default is 5)
+    -cursor {no | leave | cell#}
+
+These aren't case sensitive and allow unambiguous truncations:
+  program names (e.g. v for version)
+  client names (e.g. l for list-parameters)
+  option names (e.g. -h for -help)
+  parameter names (e.g. clip for clipboard-content)
+  boolean operands (false/true, off/on, no/yes, 0/1)
+
diff --git a/Bindings/Java/RowCells.java b/Bindings/Java/RowCells.java
new file mode 100644
index 0000000..0bd5794
--- /dev/null
+++ b/Bindings/Java/RowCells.java
@@ -0,0 +1,85 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class RowCells extends ParameterComponent {
+  private final static int BYTE_SIZE = Byte.SIZE;
+
+  private final byte[] cellArray;
+  private final BitMask cellMask;
+
+  private final int copyBytes (byte[] to, byte[] from, int index) {
+    int count = to.length;
+    System.arraycopy(from, index, to, 0, count);
+    return index + count;
+  }
+
+  public RowCells (byte[] bytes) {
+    if (bytes.length == 0) {
+      cellArray = null;
+      cellMask = new BitMask(new byte[0]);
+    } else {
+      cellArray = new byte[0X100];
+      int index = copyBytes(cellArray, bytes, 0);
+
+      {
+        byte[] mask = new byte[cellArray.length / BYTE_SIZE];
+        index = copyBytes(mask, bytes, index);
+        cellMask = new BitMask(mask);
+      }
+    }
+  }
+
+  public int getSize () {
+    return cellArray.length;
+  }
+
+  public boolean isDefined (int index) {
+    return cellMask.isSet(index);
+  }
+
+  public byte getCell (int index) {
+    return isDefined(index)? cellArray[index]: 0;
+  }
+
+  @Override
+  public String toString () {
+    StringBuilder sb = new StringBuilder();
+    sb.append('{');
+    boolean first = true;
+
+    for (int index : cellMask.getBitNumbers()) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(", ");
+      }
+
+      sb.append(
+        String.format(
+          "%02X:%s", index, asDots(cellArray[index])
+        )
+      );
+    }
+
+    sb.append('}');
+    return sb.toString();
+  }
+}
diff --git a/Bindings/Java/SemanticException.java b/Bindings/Java/SemanticException.java
new file mode 100644
index 0000000..5b821f2
--- /dev/null
+++ b/Bindings/Java/SemanticException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class SemanticException extends OperandException {
+  public SemanticException (String format, Object... arguments) {
+    super(format, arguments);
+  }
+}
diff --git a/Bindings/Java/Strings.java b/Bindings/Java/Strings.java
new file mode 100644
index 0000000..f04093a
--- /dev/null
+++ b/Bindings/Java/Strings.java
@@ -0,0 +1,150 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class Strings {
+  private Strings () {
+  }
+
+  public static boolean isAbbreviation (String actual, String supplied) {
+    int count = supplied.length();
+    if (count == 0) return false;
+    if (count > actual.length()) return false;
+    return actual.regionMatches(true, 0, supplied, 0, count);
+  }
+
+  private final static Map<String, Pattern> patternCache = new HashMap<>();
+
+  public static Pattern getPattern (String expression) {
+    synchronized (patternCache) {
+      return patternCache.computeIfAbsent(expression, Pattern::compile);
+    }
+  }
+
+  public static Matcher getMatcher (String expression, String text) {
+    return getPattern(expression).matcher(text);
+  }
+
+  public static String replaceAll (String text, String expression, String replacement) {
+    return getMatcher(expression, text).replaceAll(replacement);
+  }
+
+  public static String wordify (String string) {
+    string = replaceAll(string, "^\\s*(.*?)\\s*$", "$1");
+
+    if (!string.isEmpty()) {
+      string = replaceAll(string, "(?<=.)(?=\\p{Upper})", " ");
+      string = replaceAll(string, "\\s+", " ");
+    }
+
+    return string;
+  }
+  
+  public static int findNonemptyLine (CharSequence text) {
+    int length = text.length();
+    int at = 0;
+
+    for (int index=0; index<length; index+=1) {
+      char character = text.charAt(index);
+
+      if (character == '\n') {
+        at = index + 1;
+      } else if (!Character.isWhitespace(character)) {
+        break;
+      }
+    }
+
+    return at;
+  }
+
+  public static int findTrailingWhitespace (CharSequence text) {
+    int index = text.length();
+
+    while (--index >= 0) {
+      if (!Character.isWhitespace(text.charAt(index))) break;
+    }
+
+    return index + 1;
+  }
+
+  public static String removeTrailingWhitespace (String text) {
+    return text.substring(0, findTrailingWhitespace(text));
+  }
+
+  public static String compressWhitespace (String text) {
+    text = removeTrailingWhitespace(text);
+    if (!text.isEmpty()) text = replaceAll(text, "(?<=\\S)\\s+", " ");
+    return text;
+  }
+
+  public static String compressEmptyLines (String text) {
+    return replaceAll(text, "\n{2,}", "\n\n");
+  }
+
+  public static String formatParagraphs (String text, int width) {
+    StringBuilder result = new StringBuilder();
+
+    int length = text.length();
+    int from = 0;
+
+    while (from < length) {
+      int to = text.indexOf('\n', from);
+      if (to < 0) to = length;
+      String line = compressWhitespace(text.substring(from, to));
+
+      if (!line.isEmpty()) {
+        if (!Character.isWhitespace(line.charAt(0))) {
+          if (result.length() > 0) result.append('\n');
+
+          while (line.length() > width) {
+            int end = line.lastIndexOf(' ', width);
+
+            if (end < 0) {
+              end = line.indexOf(' ', width);
+              if (end < 0) break;
+            }
+
+            result.append(line.substring(0, end)).append('\n');
+            line = line.substring(end+1);
+          }
+        }
+
+        result.append(line);
+      }
+
+      result.append('\n');
+      from = to + 1;
+    }
+
+    result.setLength(findTrailingWhitespace(result));
+    result.delete(0, findNonemptyLine(result));
+
+    return compressEmptyLines(result.toString());
+  }
+
+  public static String formatParagraphs (String text) {
+    return formatParagraphs(text, 72);
+  }
+}
diff --git a/Bindings/Java/SyntaxException.java b/Bindings/Java/SyntaxException.java
new file mode 100644
index 0000000..93c5406
--- /dev/null
+++ b/Bindings/Java/SyntaxException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class SyntaxException extends OperandException {
+  public SyntaxException (String format, Object... arguments) {
+    super(format, arguments);
+  }
+}
diff --git a/Bindings/Java/TooManyParametersException.java b/Bindings/Java/TooManyParametersException.java
new file mode 100644
index 0000000..c91f606
--- /dev/null
+++ b/Bindings/Java/TooManyParametersException.java
@@ -0,0 +1,30 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class TooManyParametersException extends SyntaxException {
+  public TooManyParametersException (String[] parameters, int at) {
+    super("too many parameters");
+  }
+
+  public TooManyParametersException (String[] parameters) {
+    this(parameters, 0);
+  }
+}
diff --git a/Bindings/Java/UnavailableServiceException.java b/Bindings/Java/UnavailableServiceException.java
new file mode 100644
index 0000000..c612ecc
--- /dev/null
+++ b/Bindings/Java/UnavailableServiceException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class UnavailableServiceException extends HostException {
+  public UnavailableServiceException (String host) {
+    super(host, "service not available");
+  }
+}
diff --git a/Bindings/Java/UnknownHostException.java b/Bindings/Java/UnknownHostException.java
new file mode 100644
index 0000000..2e451c3
--- /dev/null
+++ b/Bindings/Java/UnknownHostException.java
@@ -0,0 +1,26 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+public class UnknownHostException extends HostException {
+  public UnknownHostException (String host) {
+    super(host, "unknown host");
+  }
+}
diff --git a/Bindings/Java/WriteArguments.java b/Bindings/Java/WriteArguments.java
new file mode 100644
index 0000000..7b66b4c
--- /dev/null
+++ b/Bindings/Java/WriteArguments.java
@@ -0,0 +1,293 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi;
+
+import java.util.Arrays;
+
+public class WriteArguments extends Component {
+  private String text = null;
+  private byte andMask[] = null;
+  private byte orMask[] = null;
+  private int regionBegin = 0;
+  private int regionSize = 0;
+  private int cursorPosition = Constants.CURSOR_LEAVE;
+  private int displayNumber = Constants.DISPLAY_DEFAULT;
+
+  public WriteArguments () {
+  }
+
+  public String getText () {
+    return text;
+  }
+
+  public WriteArguments setText (String text) {
+    this.text = text;
+    return this;
+  }
+
+  public byte[] getAndMask () {
+    return andMask;
+  }
+
+  public WriteArguments setAndMask (byte[] mask) {
+    andMask = mask;
+    return this;
+  }
+
+  public byte[] getOrMask () {
+    return orMask;
+  }
+
+  public WriteArguments setOrMask (byte[] mask) {
+    orMask = mask;
+    return this;
+  }
+
+  public int getRegionBegin () {
+    return regionBegin;
+  }
+
+  public WriteArguments setRegionBegin (int begin) {
+    regionBegin = begin;
+    return this;
+  }
+
+  public int getRegionSize () {
+    return regionSize;
+  }
+
+  public WriteArguments setRegionSize (int size) {
+    regionSize = size;
+    return this;
+  }
+
+  public WriteArguments setRegion (int begin, int size) {
+    setRegionBegin(begin);
+    setRegionSize(size);
+    return this;
+  }
+
+  public int getCursorPosition () {
+    return cursorPosition;
+  }
+
+  public WriteArguments setCursorPosition (int position) {
+    cursorPosition = position;
+    return this;
+  }
+
+  public int getDisplayNumber () {
+    return displayNumber;
+  }
+
+  public WriteArguments setDisplayNumber (int number) {
+    displayNumber = number;
+    return this;
+  }
+
+  public final static String TEXT = "text";
+  public final static String AND_MASK = "and-mask";
+  public final static String OR_MASK = "or-mask";
+  public final static String REGION_BEGIN = "region begin";
+  public final static String REGION_SIZE = "region size";
+  public final static String CURSOR_POSITION = "cursor position";
+  public final static String DISPLAY_NUMBER = "display number";
+
+  private static void checkRange (String description, int value, Integer minimum, Integer maximum) {
+    try {
+      if (minimum != null) Parse.checkMinimum(description, value, minimum);
+      if (maximum != null) Parse.checkMaximum(description, value, maximum);
+    } catch (SyntaxException exception) {
+      throw new IllegalStateException(exception.getMessage());
+    }
+  }
+
+  public final void check (int cellCount, boolean fix) {
+    boolean haveRegionBegin = regionBegin != 0;
+    boolean haveRegionSize = regionSize != 0;
+    boolean haveRegion = haveRegionBegin || haveRegionSize;
+
+    boolean haveText = text != null;
+    boolean haveAndMask = andMask != null;
+    boolean haveOrMask = orMask != null;
+    boolean haveContent = haveText || haveAndMask || haveOrMask;
+
+    if (haveRegion || haveContent) {
+      if (!haveContent) {
+        throw new IllegalStateException(
+          String.format(
+            "region content (%s, %s, and/or %s) not specified",
+            TEXT, AND_MASK, OR_MASK
+          )
+        );
+      }
+
+      if (!haveRegionBegin) {
+        if (!fix) {
+          throw new IllegalStateException(
+            String.format(
+              "%s not set", REGION_BEGIN
+            )
+          );
+        }
+
+        regionBegin = 1;
+        haveRegion = haveRegionBegin = true;
+      }
+
+      if (!haveRegionSize) {
+        if (!fix) {
+          throw new IllegalStateException(
+            String.format(
+              "%s not set", REGION_SIZE
+            )
+          );
+        }
+
+        regionSize = 0;
+        if (haveText) regionSize = Math.max(regionSize, text.length());
+        if (haveAndMask) regionSize = Math.max(regionSize, andMask.length);
+        if (haveOrMask) regionSize = Math.max(regionSize, orMask.length);
+        haveRegion = haveRegionSize = true;
+      }
+
+      checkRange(REGION_BEGIN, regionBegin, 1, cellCount);
+      checkRange(REGION_SIZE, regionSize, 1, null);
+
+      {
+        int maximum = cellCount + 1 - regionBegin;
+
+        if (!fix) {
+          checkRange(REGION_SIZE, regionSize, null, maximum);
+        } else if (regionSize > maximum) {
+          regionSize = maximum;
+        }
+      }
+
+      if (haveText) {
+        int textLength = text.length();
+
+        if (textLength > regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s length is greater than %s: %d > %d",
+                TEXT, REGION_SIZE,
+                textLength, regionSize
+              )
+            );
+          }
+
+          text = text.substring(0, regionSize);
+        } else if (textLength < regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s length is less than %s: %d < %d",
+                TEXT, REGION_SIZE,
+                textLength, regionSize
+              )
+            );
+          }
+
+          StringBuilder newText = new StringBuilder(text);
+          while (newText.length() < regionSize) newText.append(' ');
+          text = newText.toString();
+        }
+      }
+
+      if (haveAndMask) {
+        int andSize = andMask.length;
+
+        if (andSize > regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s size is greater than %s: %d > %d",
+                AND_MASK, REGION_SIZE,
+                andSize, regionSize
+              )
+            );
+          }
+
+          byte[] newMask = new byte[regionSize];
+          System.arraycopy(andMask, 0, newMask, 0, regionSize);
+          andMask = newMask;
+        } else if (andSize < regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s size is less than %s: %d < %d",
+                AND_MASK, REGION_SIZE,
+                andSize, regionSize
+              )
+            );
+          }
+
+          byte[] newMask = new byte[regionSize];
+          System.arraycopy(andMask, 0, newMask, 0, andSize);
+          Arrays.fill(newMask, andSize, (regionSize - andSize), (byte)BYTE_MASK);
+          andMask = newMask;
+        }
+      }
+
+      if (haveOrMask) {
+        int orSize = orMask.length;
+
+        if (orSize > regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s size is greater than %s: %d > %d",
+                OR_MASK, REGION_SIZE,
+                orSize, regionSize
+              )
+            );
+          }
+
+          byte[] newMask = new byte[regionSize];
+          System.arraycopy(orMask, 0, newMask, 0, regionSize);
+          orMask = newMask;
+        } else if (orSize < regionSize) {
+          if (!fix) {
+            throw new IllegalStateException(
+              String.format(
+                "%s size is less than %s: %d < %d",
+                OR_MASK, REGION_SIZE,
+                orSize, regionSize
+              )
+            );
+          }
+
+          byte[] newMask = new byte[regionSize];
+          System.arraycopy(orMask, 0, newMask, 0, orSize);
+          Arrays.fill(newMask, orSize, (regionSize - orSize), (byte)0);
+          orMask = newMask;
+        }
+      }
+    }
+
+    if (cursorPosition != Constants.CURSOR_OFF) {
+      if (cursorPosition != Constants.CURSOR_LEAVE) {
+        checkRange(CURSOR_POSITION, cursorPosition, 1, cellCount);
+      }
+    }
+  }
+}
diff --git a/Bindings/Java/apitool b/Bindings/Java/apitool
new file mode 100755
index 0000000..b18a1a9
--- /dev/null
+++ b/Bindings/Java/apitool
@@ -0,0 +1,23 @@
+#!/bin/sh
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec java -classpath "${programDirectory}/classes" org.a11y.brlapi.commands.ApiToolCommand "${@}"
+exit "${?}"
diff --git a/Bindings/Java/bindings.c b/Bindings/Java/bindings.c
new file mode 100644
index 0000000..ada35db
--- /dev/null
+++ b/Bindings/Java/bindings.c
@@ -0,0 +1,1676 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <pthread.h>
+
+#include "bindings.h"
+
+#define NEW_PRIMITIVE_WRAPPER(name, type, sig) \
+static jobject \
+new##name (JNIEnv *env, type value) { \
+  static JAVA_CLASS_VARIABLE(class); \
+  static JAVA_METHOD_VARIABLE(constructor); \
+  \
+  if (!javaFindClassAndMethod( \
+    env, \
+    &class, JAVA_OBJ_LANG(#name), \
+    &constructor, JAVA_CONSTRUCTOR_NAME, \
+    JAVA_SIG_CONSTRUCTOR(JAVA_SIG_##sig) \
+  )) return NULL; \
+  \
+  return (*env)->NewObject(env, class, constructor, value); \
+}
+
+NEW_PRIMITIVE_WRAPPER(Long, jlong, LONG)
+
+#define BRLAPI_NO_DEPRECATED
+#define BRLAPI_NO_SINGLE_SESSION
+#include "brlapi.h"
+#define BRLAPI_OBJECT(name) "org/a11y/brlapi/" name
+
+static jint jniVersion = 0;
+static int libraryVersion_major = 0;
+static int libraryVersion_minor = 0;
+static int libraryVersion_revision = 0;
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_NativeComponent, initializeNativeData, void
+) {
+  jniVersion = (*env)->GetVersion(env);
+
+  brlapi_getLibraryVersion(
+    &libraryVersion_major,
+    &libraryVersion_minor,
+    &libraryVersion_revision
+  );
+}
+
+static void
+logJavaVirtualMachineError (jint error, const char *method) {
+  const char *message;
+
+  switch (error) {
+    case JNI_OK:
+      message = "success";
+      break;
+
+    default:
+#ifdef JNI_ERR
+    case JNI_ERR:
+#endif /* JNI_ERR */
+      message = "unknown error";
+      break;
+
+#ifdef JNI_EDETACHED
+    case JNI_EDETACHED:
+      message = "thread not attached to virtual machine";
+      break;
+#endif /* JNI_EDETACHED */
+
+#ifdef JNI_EVERSION
+    case JNI_EVERSION:
+      message = "version error";
+      break;
+#endif /* JNI_EVERSION */
+
+#ifdef JNI_ENOMEM
+    case JNI_ENOMEM:
+      message = "not enough memory";
+      break;
+#endif /* JNI_ENOMEM */
+
+#ifdef JNI_EEXIST
+    case JNI_EEXIST:
+      message = "virtual machine already created";
+      break;
+#endif /* JNI_EEXIST */
+
+#ifdef JNI_EINVAL
+    case JNI_EINVAL:
+      message = "invalid argument";
+      break;
+#endif /* JNI_EINVAL */
+  }
+
+  fprintf(stderr, "Java virtual machine error %d in %s: %s\n", error, method, message);
+}
+
+static pthread_key_t threadKey_vm;
+
+static void
+destroyThreadKey_vm (void *value) {
+  JavaVM *vm = value;
+  (*vm)->DetachCurrentThread(vm);
+}
+
+static void
+createThreadKey_vm (void) {
+  pthread_key_create(&threadKey_vm, destroyThreadKey_vm);
+}
+
+static void
+setThreadExitHandler (JavaVM *vm) {
+  static pthread_once_t once = PTHREAD_ONCE_INIT;
+  pthread_once(&once, createThreadKey_vm);
+  pthread_setspecific(threadKey_vm, vm);
+}
+
+static JNIEnv *
+getJavaEnvironment (brlapi_handle_t *handle) {
+  JavaVM *vm = brlapi__getClientData(handle);
+  void *env = NULL;
+
+  if (vm) {
+    jint result = (*vm)->GetEnv(vm, &env, jniVersion);
+
+    if (result != JNI_OK) {
+      if (result == JNI_EDETACHED) {
+        JavaVMAttachArgs args = {
+          .version = jniVersion,
+          .name = NULL,
+          .group = NULL
+        };
+
+        #ifdef __ANDROID__
+          JNIEnv *e = env;
+          result = (*vm)->AttachCurrentThread(vm, &e, &args);
+          env = e;
+        #else /* __ANDROID__ */
+          result = (*vm)->AttachCurrentThread(vm, &env, &args);
+        #endif /* __ANDROID__ */
+
+        if (result == JNI_OK) {
+          setThreadExitHandler(env);
+        } else {
+          logJavaVirtualMachineError(result, "AttachCurrentThread");
+        }
+      } else {
+        logJavaVirtualMachineError(result, "GetEnv");
+      }
+    }
+  }
+
+  return env;
+}
+
+static void
+throwJavaError (JNIEnv *env, const char *object, const char *message) {
+  if ((*env)->ExceptionCheck(env)) return;
+  jclass class = (*env)->FindClass(env, object);
+  if (class) (*env)->ThrowNew(env, class, message);
+}
+
+static void
+logBrlapiError (const char *label) {
+  size_t size = brlapi_strerror_r(&brlapi_error, NULL, 0);
+  char msg[size+1];
+  brlapi_strerror_r(&brlapi_error, msg, sizeof(msg));
+  fprintf(stderr,
+    "%s: API=%d Libc=%d GAI=%d: %s\n",
+    label, brlapi_errno, brlapi_libcerrno, brlapi_gaierrno, msg
+  );
+}
+
+static void
+throwAPIError (JNIEnv *env) {
+  if (0) logBrlapiError("API Error");
+  if ((*env)->ExceptionCheck(env)) return;
+
+  {
+    const char *object = NULL;
+
+    switch (brlapi_errno) {
+      case BRLAPI_ERROR_SUCCESS:
+        break;
+
+      case BRLAPI_ERROR_NOMEM:
+        object = JAVA_OBJ_OUT_OF_MEMORY_ERROR;
+        break;
+
+      case BRLAPI_ERROR_EOF:
+        object = BRLAPI_OBJECT("LostConnectionException");
+        break;
+
+      case BRLAPI_ERROR_LIBCERR: {
+        switch (brlapi_libcerrno) {
+          case EINTR:
+            object = JAVA_OBJ_INTERRUPTED_IO_EXCEPTION;
+            break;
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+
+    if (object) {
+      throwJavaError(env, object, brlapi_errfun);
+      return;
+    }
+  }
+
+  static JAVA_CLASS_VARIABLE(class);
+  if (!javaFindClass(env, &class, BRLAPI_OBJECT("APIError"))) return;
+
+  static JAVA_METHOD_VARIABLE(constructor);
+  if (!JAVA_FIND_CONSTRUCTOR(env, &constructor, class,
+    JAVA_SIG_INT // api error
+    JAVA_SIG_INT // os error
+    JAVA_SIG_INT // gai error
+    JAVA_SIG_STRING // function name
+  )) return;
+
+  jstring jFunction;
+
+  if (!brlapi_errfun) {
+    jFunction = NULL;
+  } else if (!(jFunction = (*env)->NewStringUTF(env, brlapi_errfun))) {
+    return;
+  }
+
+  jobject object = (*env)->NewObject(
+    env, class, constructor,
+    brlapi_errno, brlapi_libcerrno, brlapi_gaierrno, jFunction
+  );
+
+  if (object) {
+    (*env)->Throw(env, object);
+  } else if (jFunction) {
+    (*env)->ReleaseStringUTFChars(env, jFunction, brlapi_errfun);
+  }
+}
+
+static void
+throwConnectError (JNIEnv *env, const brlapi_connectionSettings_t *settings) {
+  if (0) logBrlapiError("Connect Error");
+
+  const char *object = NULL;
+  const char *message = NULL;
+
+  const char *host = NULL;
+  const char *auth = NULL;
+
+  if (settings) {
+    host = settings->host;
+    auth = settings->auth;
+  }
+
+  switch (brlapi_errno) {
+    case BRLAPI_ERROR_SUCCESS:
+      break;
+
+    case BRLAPI_ERROR_CONNREFUSED:
+      object = BRLAPI_OBJECT("UnavailableServiceException");
+      message = host;
+      break;
+
+    case BRLAPI_ERROR_AUTHENTICATION:
+      object = BRLAPI_OBJECT("AuthenticationException");
+      message = auth;
+      break;
+
+    case BRLAPI_ERROR_GAIERR: {
+      switch (brlapi_gaierrno) {
+        case EAI_SYSTEM:
+          goto SYSTEM_ERROR;
+
+        case EAI_MEMORY:
+          object = JAVA_OBJ_OUT_OF_MEMORY_ERROR;
+          break;
+
+        #ifdef EAI_NODATA
+        case EAI_NODATA: // obsoleted on RFC 2553bis-02
+        #endif /* EAI_NODATA */
+
+        case EAI_NONAME:
+          object = BRLAPI_OBJECT("UnknownHostException");
+          message = host;
+          break;
+      }
+
+      break;
+    }
+
+    SYSTEM_ERROR:
+    case BRLAPI_ERROR_LIBCERR: {
+      switch (brlapi_libcerrno) {
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  if (object) {
+    if (!message) message = "";
+    throwJavaError(env, object, message);
+  } else {
+    throwAPIError(env);
+  }
+}
+
+static void BRLAPI_STDCALL
+handleAPIException (brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size) {
+  JNIEnv *env = getJavaEnvironment(handle);
+  if ((*env)->ExceptionCheck(env)) return;
+
+  jbyteArray jPacket = (*env)->NewByteArray(env, size);
+  if (!jPacket) return;
+  (*env)->SetByteArrayRegion(env, jPacket, 0, size, (jbyte *) packet);
+
+  static JAVA_CLASS_VARIABLE(class);
+  if (!javaFindClass(env, &class, BRLAPI_OBJECT("APIException"))) return;
+
+  static JAVA_METHOD_VARIABLE(constructor);
+  if (!JAVA_FIND_CONSTRUCTOR(env, &constructor, class,
+    JAVA_SIG_LONG // handle
+    JAVA_SIG_INT // error
+    JAVA_SIG_INT // type
+    JAVA_SIG_ARRAY(JAVA_SIG_BYTE) // packet
+  )) return;
+
+  jclass object = (*env)->NewObject(
+    env, class, constructor,
+    (jlong) (intptr_t) handle, error, type, jPacket
+  );
+  if (!object) return;
+
+  (*env)->Throw(env, object);
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_APIVersion, getMajor, jint
+) {
+  return libraryVersion_major;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_APIVersion, getMinor, jint
+) {
+  return libraryVersion_minor;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_APIVersion, getRevision, jint
+) {
+  return libraryVersion_revision;
+}
+
+#define GET_CLASS(env, class, object, ret) \
+  jclass class; \
+  do { \
+    if (!((class) = (*(env))->GetObjectClass((env), (object)))) return ret; \
+  } while (0)
+
+#define FIND_FIELD(env, field, class, name, signature, ret) \
+  jfieldID field; \
+  do { \
+    if (!(field = (*(env))->GetFieldID((env), (class), (name), (signature)))) return ret; \
+  } while (0)
+
+#define FIND_CONNECTION_HANDLE(env, object, ret) \
+  GET_CLASS((env), class, (object), ret); \
+  FIND_FIELD((env), field, class, "connectionHandle", JAVA_SIG_LONG, ret);
+
+#define GET_CONNECTION_HANDLE(env, object, ret) \
+  brlapi_handle_t *handle; \
+  do { \
+    FIND_CONNECTION_HANDLE((env), (object), ret); \
+    handle = (void*) (intptr_t) JAVA_GET_FIELD((env), Long, (object), field); \
+    if (!handle) { \
+      throwJavaError((env), JAVA_OBJ_ILLEGAL_STATE_EXCEPTION, "connection has been closed"); \
+      return ret; \
+    } \
+  } while (0)
+
+#define SET_CONNECTION_HANDLE(env, object, value, ret) \
+  do { \
+    FIND_CONNECTION_HANDLE((env), (object), ret); \
+    JAVA_SET_FIELD((env), Long, (object), field, (jlong) (intptr_t) (value)); \
+  } while (0)
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_ConnectionSettings, getKeyfileDirectory, jstring
+) {
+  return (*env)->NewStringUTF(env, BRLAPI_ETCDIR);
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_ConnectionSettings, getKeyfileName, jstring
+) {
+  return (*env)->NewStringUTF(env, BRLAPI_AUTHKEYFILE);
+}
+
+static int
+openConnection (
+  JNIEnv *env, jobject connection,
+  jobject jRequestedSettings, brlapi_connectionSettings_t *cRequestedSettings,
+  jobject jActualSettings, brlapi_connectionSettings_t *cActualSettings,
+  brlapi_handle_t **handle, int *fileDescriptor,
+  jobject *jRequestedHost, jobject *jRequestedAuth
+) {
+  if (jRequestedSettings) {
+    GET_CLASS(env, class, jRequestedSettings, 0);
+
+    {
+      FIND_FIELD(env, field, class, "serverHost", JAVA_SIG_STRING, 0);
+      *jRequestedHost = JAVA_GET_FIELD(env, Object, jRequestedSettings, field);
+
+      if (*jRequestedHost) {
+        if (!(cRequestedSettings->host = (*env)->GetStringUTFChars(env, *jRequestedHost, NULL))) {
+          return 0;
+        }
+      }
+    }
+
+    {
+      FIND_FIELD(env, field, class, "authenticationScheme", JAVA_SIG_STRING, 0);
+      *jRequestedAuth = JAVA_GET_FIELD(env, Object, jRequestedSettings, field);
+
+      if (*jRequestedAuth) {
+        if (!(cRequestedSettings->auth = (*env)->GetStringUTFChars(env, *jRequestedAuth, NULL))) {
+          return 0;
+        }
+      }
+    }
+  }
+
+  if (!(*handle = malloc(brlapi_getHandleSize()))) {
+    throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    return 0;
+  }
+
+  *fileDescriptor = brlapi__openConnection(
+    *handle, cRequestedSettings, cActualSettings
+  );
+
+  if (*fileDescriptor < 0) {
+    throwConnectError(env, cRequestedSettings);
+    return 0;
+  }
+
+  if (cActualSettings) {
+    GET_CLASS(env, class, jActualSettings, 0);
+
+    if (cActualSettings->host) {
+      jstring host = (*env)->NewStringUTF(env, cActualSettings->host);
+      if (!host) return 0;
+
+      FIND_FIELD(env, field, class, "serverHost", JAVA_SIG_STRING, 0);
+      JAVA_SET_FIELD(env, Object, jActualSettings, field, host);
+      if ((*env)->ExceptionCheck(env)) return 0;
+    }
+
+    if (cActualSettings->auth) {
+      jstring auth = (*env)->NewStringUTF(env, cActualSettings->auth);
+      if (!auth) return 0;
+
+      FIND_FIELD(env, field, class, "authenticationScheme", JAVA_SIG_STRING, 0);
+      JAVA_SET_FIELD(env, Object, jActualSettings, field, auth);
+      if ((*env)->ExceptionCheck(env)) return 0;
+    }
+  }
+
+  {
+    JavaVM *vm;
+    (*env)->GetJavaVM(env, &vm);
+    brlapi__setClientData(*handle, vm);
+  }
+
+  brlapi__setExceptionHandler(*handle, handleAPIException);
+  SET_CONNECTION_HANDLE(env, connection, *handle, 0);
+  return 1;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, openConnection, jint,
+  jobject jRequestedSettings, jobject jActualSettings
+) {
+  brlapi_connectionSettings_t cRequestedSettings, *pRequestedSettings;
+  memset(&cRequestedSettings, 0, sizeof(cRequestedSettings));
+  pRequestedSettings = jRequestedSettings? &cRequestedSettings: NULL;
+
+  brlapi_connectionSettings_t cActualSettings, *pActualSettings;
+  memset(&cActualSettings, 0, sizeof(cActualSettings));
+  pActualSettings = jActualSettings? &cActualSettings: NULL;
+
+  brlapi_handle_t *handle = NULL;
+  int fileDescriptor = -1;
+
+  jobject jRequestedHost = NULL;
+  jobject jRequestedAuth = NULL;
+
+  int opened = openConnection(
+    env, this,
+    jRequestedSettings, pRequestedSettings,
+    jActualSettings, pActualSettings,
+    &handle, &fileDescriptor,
+    &jRequestedHost, &jRequestedAuth
+  );
+
+  if (cRequestedSettings.host) {
+    (*env)->ReleaseStringUTFChars(env, jRequestedHost, cRequestedSettings.host); 
+  }
+
+  if (cRequestedSettings.auth) {
+    (*env)->ReleaseStringUTFChars(env, jRequestedAuth,  cRequestedSettings.auth); 
+  }
+
+  if (opened) return fileDescriptor;
+  if (fileDescriptor >= 0) brlapi__closeConnection(handle);
+  if (handle) free(handle);
+  return -1;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, closeConnection, void
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+  brlapi__closeConnection(handle);
+  free(handle);
+  SET_CONNECTION_HANDLE(env, this, NULL, );
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, getDriverName, jstring
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+  char name[0X20];
+
+  if (brlapi__getDriverName(handle, name, sizeof(name)) < 0) {
+    throwAPIError(env);
+    return NULL;
+  }
+
+  name[sizeof(name)-1] = 0;
+  return (*env)->NewStringUTF(env, name);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, getModelIdentifier, jstring
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+  char identifier[0X20];
+
+  if (brlapi__getModelIdentifier(handle, identifier, sizeof(identifier)) < 0) {
+    throwAPIError(env);
+    return NULL;
+  }
+
+  identifier[sizeof(identifier)-1] = 0;
+  return (*env)->NewStringUTF(env, identifier);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, getDisplaySize, jobject
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+
+  unsigned int width, height;
+  if (brlapi__getDisplaySize(handle, &width, &height) < 0) {
+    throwAPIError(env);
+    return NULL;
+  }
+
+  jclass class = (*env)->FindClass(env, BRLAPI_OBJECT("DisplaySize"));
+  if (!class) return NULL;
+
+  jmethodID constructor = JAVA_GET_CONSTRUCTOR(env, class,
+    JAVA_SIG_INT // width
+    JAVA_SIG_INT // height
+  );
+  if (!constructor) return NULL;
+
+  jobject object = (*env)->NewObject(env, class, constructor, width, height);
+  if (!object) return NULL;
+  return object;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, pause, void,
+  jint milliseconds
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+  int result = brlapi__pause(handle, milliseconds);
+
+  if (result < 0) {
+    throwAPIError(env);
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, enterTtyMode, jint,
+  jint jtty, jstring jdriver
+) {
+  int tty ;
+  char *driver;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, -1);
+  
+  tty = (int)jtty; 
+  if (!jdriver)
+    driver = NULL;
+  else
+    if (!(driver = (char *)(*env)->GetStringUTFChars(env, jdriver, NULL))) {
+      throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+      return -1;
+    }
+
+  result = brlapi__enterTtyMode(handle, tty,driver);
+  if (result < 0) {
+    throwAPIError(env);
+    return -1;
+  }
+
+  return (jint) result;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, enterTtyModeWithPath, void,
+  jstring jdriver, jintArray jttys
+) {
+  jint *ttys ;
+  char *driver;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, );
+  
+  if (!jttys) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+  if (!(ttys = (*env)->GetIntArrayElements(env, jttys, NULL))) {
+    throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    return;
+  }
+
+  if (!jdriver) {
+    driver = NULL;
+  } else if (!(driver = (char *)(*env)->GetStringUTFChars(env, jdriver, NULL))) {
+    throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    return;
+  }
+
+  result = brlapi__enterTtyModeWithPath(handle, ttys,(*env)->GetArrayLength(env,jttys),driver);
+  (*env)->ReleaseIntArrayElements(env, jttys, ttys, JNI_ABORT);
+  if (result < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, leaveTtyMode, void
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (brlapi__leaveTtyMode(handle) < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, setFocus, void,
+  jint tty
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+  if (brlapi__setFocus(handle, tty) < 0) throwAPIError(env);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, writeText, void,
+  jint cursor, jstring jText
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+  
+  const char *cText;
+  if (!jText) {
+    cText = NULL;
+  } else if (!(cText = (*env)->GetStringUTFChars(env, jText, NULL))) {
+    throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    return;
+  }
+
+  int result = brlapi__writeText(handle, cursor, cText);
+  if (jText) (*env)->ReleaseStringUTFChars(env, jText, cText); 
+  if (result < 0) throwAPIError(env);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, writeDots, void,
+  jbyteArray jDots
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+  
+  if (!jDots) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  jbyte *cDots = (*env)->GetByteArrayElements(env, jDots, NULL);
+  if (!cDots) return;
+
+  int result = brlapi__writeDots(handle, (const unsigned char *)cDots);
+  (*env)->ReleaseByteArrayElements(env, jDots, cDots, JNI_ABORT); 
+  
+  if (result < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, write, void,
+  jobject jArguments
+) {
+  if (!jArguments) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  GET_CONNECTION_HANDLE(env, this, );
+  GET_CLASS(env, class, jArguments, );
+  brlapi_writeArguments_t cArguments = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+
+  {
+    FIND_FIELD(env, field, class, "displayNumber", JAVA_SIG_INT, );
+    cArguments.displayNumber = JAVA_GET_FIELD(env, Int, jArguments, field);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "regionBegin", JAVA_SIG_INT, );
+    cArguments.regionBegin = JAVA_GET_FIELD(env, Int, jArguments, field);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "regionSize", JAVA_SIG_INT, );
+    cArguments.regionSize = JAVA_GET_FIELD(env, Int, jArguments, field);
+  }
+
+  jstring jText;
+  {
+    FIND_FIELD(env, field, class, "text", JAVA_SIG_STRING, );
+
+    if ((jText = JAVA_GET_FIELD(env, Object, jArguments, field))) {
+      cArguments.text = (char *) (*env)->GetStringUTFChars(env, jText, NULL);
+      cArguments.charset = "UTF-8";
+    } else {
+      cArguments.text = NULL;
+    }
+  }
+
+  jbyteArray jAndMask;
+  {
+    FIND_FIELD(env, field, class, "andMask", JAVA_SIG_ARRAY(JAVA_SIG_BYTE), );
+
+    if ((jAndMask = JAVA_GET_FIELD(env, Object, jArguments, field))) {
+      cArguments.andMask = (unsigned char *) (*env)->GetByteArrayElements(env, jAndMask, NULL);
+    } else {
+      cArguments.andMask = NULL;
+    }
+  }
+
+  jbyteArray jOrMask;
+  {
+    FIND_FIELD(env, field, class, "orMask", JAVA_SIG_ARRAY(JAVA_SIG_BYTE), );
+
+    if ((jOrMask = JAVA_GET_FIELD(env, Object, jArguments, field))) {
+      cArguments.orMask = (unsigned char *) (*env)->GetByteArrayElements(env, jOrMask, NULL);
+    } else {
+      cArguments.orMask = NULL;
+    }
+  }
+
+  {
+    FIND_FIELD(env, field, class, "cursorPosition", JAVA_SIG_INT, );
+    cArguments.cursor = JAVA_GET_FIELD(env, Int, jArguments, field);
+  }
+
+  int result = brlapi__write(handle, &cArguments);
+  if (jText) (*env)->ReleaseStringUTFChars(env, jText, cArguments.text); 
+  if (jAndMask) (*env)->ReleaseByteArrayElements(env, jAndMask, (jbyte*) cArguments.andMask, JNI_ABORT); 
+  if (jOrMask) (*env)->ReleaseByteArrayElements(env, jOrMask, (jbyte*) cArguments.orMask, JNI_ABORT); 
+
+  if (result < 0) throwAPIError(env);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, readKey, jobject,
+  jboolean jWait
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+
+  int cWait = jWait != JNI_FALSE;
+  brlapi_keyCode_t code;
+
+  int result = brlapi__readKey(handle, cWait, &code);
+  if (result < 0) throwAPIError(env);
+  if (!result) return NULL;
+  return newLong(env, code);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, readKeyWithTimeout, jlong,
+  jint milliseconds
+) {
+  GET_CONNECTION_HANDLE(env, this, -1);
+
+  brlapi_keyCode_t code;
+  int result = brlapi__readKeyWithTimeout(handle, milliseconds, &code);
+
+  if (result < 0) {
+    throwAPIError(env);
+  } else if (!result) {
+    throwJavaError(env, JAVA_OBJ_TIMEOUT_EXCEPTION, __func__);
+  }
+
+  return (jlong)code;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, ignoreKeys, void,
+  jlong jrange, jlongArray js
+) {
+  jlong *s;
+  unsigned int n;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (!js) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, js);
+  s = (*env)->GetLongArrayElements(env, js, NULL);
+
+  // XXX jlong != brlapi_keyCode_t probably
+  result = brlapi__ignoreKeys(handle, jrange, (const brlapi_keyCode_t *)s, n);
+  (*env)->ReleaseLongArrayElements(env, js, s, JNI_ABORT);
+  
+  if (result < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, acceptKeys, void,
+  jlong jrange, jlongArray js
+) {
+  jlong *s;
+  unsigned int n;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (!js) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, js);
+  s = (*env)->GetLongArrayElements(env, js, NULL);
+
+  // XXX jlong != brlapi_keyCode_t probably
+  result = brlapi__acceptKeys(handle, jrange, (const brlapi_keyCode_t *)s, n);
+  (*env)->ReleaseLongArrayElements(env, js, s, JNI_ABORT);
+
+  if (result < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, ignoreAllKeys, void
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (brlapi__ignoreAllKeys(handle) < 0)
+    throwAPIError(env);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, acceptAllKeys, void
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (brlapi__acceptAllKeys(handle) < 0)
+    throwAPIError(env);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, ignoreKeyRanges, void,
+  jobjectArray js
+) {
+  unsigned int n;
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (!js) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, js);
+
+  {
+    unsigned int i;
+    brlapi_range_t s[n];
+
+    for (i=0; i<n; i++) {
+      jlongArray jl = (*env)->GetObjectArrayElement(env, js, i);
+      jlong *l = (*env)->GetLongArrayElements(env, jl, NULL);
+      s[i].first = l[0];
+      s[i].last = l[1];
+      (*env)->ReleaseLongArrayElements(env, jl, l, JNI_ABORT);
+    }
+    if (brlapi__ignoreKeyRanges(handle, s, n)) {
+      throwAPIError(env);
+      return;
+    }
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, acceptKeyRanges, void,
+  jobjectArray js
+) {
+  unsigned int n;
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (!js) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, js);
+
+  {
+    unsigned int i;
+    brlapi_range_t s[n];
+
+    for (i=0; i<n; i++) {
+      jlongArray jl = (*env)->GetObjectArrayElement(env, js, i);
+      jlong *l = (*env)->GetLongArrayElements(env, jl, NULL);
+      s[i].first = l[0];
+      s[i].last = l[1];
+      (*env)->ReleaseLongArrayElements(env, jl, l, JNI_ABORT);
+    }
+    if (brlapi__acceptKeyRanges(handle, s, n)) {
+      throwAPIError(env);
+      return;
+    }
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, enterRawMode, void,
+  jstring jdriver
+) {
+  char *driver;
+  int res;
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (!jdriver) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  } else if (!(driver = (char *)(*env)->GetStringUTFChars(env, jdriver, NULL))) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return;
+  }
+  res = brlapi__enterRawMode(handle, driver);
+  if (jdriver) (*env)->ReleaseStringUTFChars(env, jdriver, driver);
+  if (res < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, leaveRawMode, void
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+
+  if (brlapi__leaveRawMode(handle) < 0) {
+    throwAPIError(env);
+    return;
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, sendRaw, jint,
+  jbyteArray jbuf
+) {
+  jbyte *buf;
+  unsigned int n;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, -1);
+
+  if (!jbuf) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return -1;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, jbuf);
+  buf = (*env)->GetByteArrayElements(env, jbuf, NULL);
+
+  result = brlapi__sendRaw(handle, (const unsigned char *)buf, n);
+  (*env)->ReleaseByteArrayElements(env, jbuf, buf, JNI_ABORT);
+
+  if (result < 0) {
+    throwAPIError(env);
+    return -1;
+  }
+
+  return (jint) result;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, recvRaw, jint,
+  jbyteArray jbuf
+) {
+  jbyte *buf;
+  unsigned int n;
+  int result;
+  GET_CONNECTION_HANDLE(env, this, -1);
+
+  if (!jbuf) {
+    throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+    return -1;
+  }
+
+  n = (unsigned int) (*env)->GetArrayLength(env, jbuf);
+  buf = (*env)->GetByteArrayElements(env, jbuf, NULL);
+
+  result = brlapi__recvRaw(handle, (unsigned char *)buf, n);
+
+  if (result < 0) {
+    (*env)->ReleaseByteArrayElements(env, jbuf, buf, JNI_ABORT);
+    throwAPIError(env);
+    return -1;
+  }
+
+  (*env)->ReleaseByteArrayElements(env, jbuf, buf, 0);
+  return (jint) result;
+}
+
+static int
+checkParameter (
+  JNIEnv *env,
+  jint parameter, jlong subparam, jboolean global,
+  const brlapi_param_properties_t **properties,
+  brlapi_param_flags_t *flags
+) {
+  if (!(*properties = brlapi_getParameterProperties(parameter))) {
+    throwJavaError(env, JAVA_OBJ_ILLEGAL_ARGUMENT_EXCEPTION, "parameter out of range");
+    return 0;
+  }
+
+  if (!(*properties)->hasSubparam && (subparam != 0)) {
+    throwJavaError(env, JAVA_OBJ_ILLEGAL_ARGUMENT_EXCEPTION, "nonzero subparam");
+    return 0;
+  }
+
+  *flags = 0;
+  if (global == JNI_TRUE) {
+    *flags |= BRLAPI_PARAMF_GLOBAL;
+  } else if (global == JNI_FALSE) {
+    *flags |= BRLAPI_PARAMF_LOCAL;
+  }
+
+  return 1;
+}
+
+static jobject
+newParameterValueObject (
+  JNIEnv *env, const brlapi_param_properties_t *properties,
+  const void *value, size_t size
+) {
+  jobject result = NULL;
+  size_t count = size;
+
+  switch (properties->type) {
+    case BRLAPI_PARAM_TYPE_STRING: {
+      result = (*env)->NewStringUTF(env, value);
+      break;
+    }
+
+    case BRLAPI_PARAM_TYPE_BOOLEAN: {
+      const brlapi_param_bool_t *cBooleans = value;
+      count /= sizeof(*cBooleans);
+      result = (*env)->NewBooleanArray(env, count);
+
+      if (result && count) {
+        jboolean jBooleans[count];
+
+        for (jsize i=0; i<count; i+=1) {
+          jBooleans[i] = cBooleans[i]? JNI_TRUE: JNI_FALSE;
+        }
+
+        (*env)->SetBooleanArrayRegion(env, result, 0, count, jBooleans);
+      }
+
+      break;
+    }
+
+    case BRLAPI_PARAM_TYPE_UINT8: {
+      result = (*env)->NewByteArray(env, count);
+
+      if (result && count) {
+        (*env)->SetByteArrayRegion(env, result, 0, count, value);
+      }
+
+      break;
+    }
+
+    case BRLAPI_PARAM_TYPE_UINT16: {
+      count /= 2;
+      result = (*env)->NewShortArray(env, count);
+
+      if (result && count) {
+        (*env)->SetShortArrayRegion(env, result, 0, count, value);
+      }
+
+      break;
+    }
+
+    case BRLAPI_PARAM_TYPE_UINT32: {
+      count /= 4;
+      result = (*env)->NewIntArray(env, count);
+
+      if (result && count) {
+        (*env)->SetIntArrayRegion(env, result, 0, count, value);
+      }
+
+      break;
+    }
+
+    case BRLAPI_PARAM_TYPE_UINT64: {
+      count /= 8;
+      result = (*env)->NewLongArray(env, count);
+
+      if (result && count) {
+        (*env)->SetLongArrayRegion(env, result, 0, count, value);
+      }
+
+      break;
+    }
+  }
+
+  return result;
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, getParameter, jobject,
+  jint parameter, jlong subparam, jboolean global
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+
+  jobject result = NULL;
+  const brlapi_param_properties_t *properties;
+  brlapi_param_flags_t flags;
+
+  if (checkParameter(env, parameter, subparam, global, &properties, &flags)) {
+    void *value;
+    size_t size;
+
+    if ((value = brlapi__getParameterAlloc(handle, parameter, subparam, flags, &size))) {
+      result = newParameterValueObject(env, properties, value, size);
+      free(value);
+    } else {
+      throwAPIError(env);
+    }
+  }
+
+  return result;
+}
+
+static void
+setParameter (
+  JNIEnv *env, brlapi_handle_t *handle,
+  jint parameter, jlong subparam, brlapi_param_flags_t flags,
+  const void *data, size_t size
+) {
+  if (brlapi__setParameter(handle, parameter, subparam, flags, data, size) < 0) {
+    throwAPIError(env);
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, setParameter, void,
+  jint parameter, jlong subparam, jboolean global, jobject value
+) {
+  GET_CONNECTION_HANDLE(env, this, );
+
+  const brlapi_param_properties_t *properties;
+  brlapi_param_flags_t flags;
+
+  if (checkParameter(env, parameter, subparam, global, &properties, &flags)) {
+    switch (properties->type) {
+      case BRLAPI_PARAM_TYPE_STRING: {
+        jboolean isCopy;
+        const char *string = (*env)->GetStringUTFChars(env, value, &isCopy);
+
+        if (string) {
+          setParameter(
+            env, handle,
+            parameter, subparam, flags,
+            string, strlen(string)
+          );
+
+          (*env)->ReleaseStringUTFChars(env, value, string);
+        }
+
+        break;
+      }
+
+      case BRLAPI_PARAM_TYPE_BOOLEAN: {
+        jsize count = (*env)->GetArrayLength(env, value);
+
+        if (!javaHasExceptionOccurred(env)) {
+          jboolean values[count + 1];
+          (*env)->GetBooleanArrayRegion(env, value, 0, count, values);
+
+          if (!javaHasExceptionOccurred(env)) {
+            brlapi_param_bool_t booleans[count + 1];
+
+            for (unsigned int i=0; i<count; i+=1) {
+              booleans[i] = values[i] != JNI_FALSE;
+            }
+
+            setParameter(
+              env, handle,
+              parameter, subparam, flags,
+              booleans, count
+            );
+          }
+        }
+
+        break;
+      }
+
+      case BRLAPI_PARAM_TYPE_UINT8: {
+        jsize count = (*env)->GetArrayLength(env, value);
+
+        if (!javaHasExceptionOccurred(env)) {
+          jbyte values[count + 1];
+          (*env)->GetByteArrayRegion(env, value, 0, count, values);
+
+          if (!javaHasExceptionOccurred(env)) {
+            setParameter(
+              env, handle,
+              parameter, subparam, flags,
+              values, count
+            );
+          }
+        }
+
+        break;
+      }
+
+      case BRLAPI_PARAM_TYPE_UINT16: {
+        jsize count = (*env)->GetArrayLength(env, value);
+
+        if (!javaHasExceptionOccurred(env)) {
+          jshort values[count + 1];
+          (*env)->GetShortArrayRegion(env, value, 0, count, values);
+
+          if (!javaHasExceptionOccurred(env)) {
+            setParameter(
+              env, handle,
+              parameter, subparam, flags,
+              values, (count * 2)
+            );
+          }
+        }
+
+        break;
+      }
+
+      case BRLAPI_PARAM_TYPE_UINT32: {
+        jsize count = (*env)->GetArrayLength(env, value);
+
+        if (!javaHasExceptionOccurred(env)) {
+          jint values[count + 1];
+          (*env)->GetIntArrayRegion(env, value, 0, count, values);
+
+          if (!javaHasExceptionOccurred(env)) {
+            setParameter(
+              env, handle,
+              parameter, subparam, flags,
+              values, (count * 4)
+            );
+          }
+        }
+
+        break;
+      }
+
+      case BRLAPI_PARAM_TYPE_UINT64: {
+        jsize count = (*env)->GetArrayLength(env, value);
+
+        if (!javaHasExceptionOccurred(env)) {
+          jlong values[count + 1];
+          (*env)->GetLongArrayRegion(env, value, 0, count, values);
+
+          if (!javaHasExceptionOccurred(env)) {
+            setParameter(
+              env, handle,
+              parameter, subparam, flags,
+              values, (count * 8)
+            );
+          }
+        }
+
+        break;
+      }
+    }
+  }
+}
+
+typedef struct {
+  brlapi_handle_t *handle;
+  brlapi_paramCallbackDescriptor_t descriptor;
+
+  struct {
+    jobject object;
+    jclass class;
+    jmethodID method;
+  } watcher;
+} WatchedParameterData;
+
+static void
+handleWatchedParameter (
+  brlapi_param_t parameter,
+  brlapi_param_subparam_t subparam,
+  brlapi_param_flags_t flags,
+  void *identifier, const void *data, size_t length
+) {
+  WatchedParameterData *wpd = (WatchedParameterData *)identifier;
+  brlapi_handle_t *handle = wpd->handle;
+  JNIEnv *env = getJavaEnvironment(handle);
+  if (!env) return;
+
+  jobject value = newParameterValueObject(
+    env, brlapi_getParameterProperties(parameter),
+    data, length
+  );
+
+  if (value) {
+    (*env)->CallVoidMethod(
+      env, wpd->watcher.object, wpd->watcher.method,
+      parameter, subparam, value
+    );
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_ConnectionBase, watchParameter, jlong,
+  jint parameter, jlong subparam, jboolean global, jobject watcher
+) {
+  GET_CONNECTION_HANDLE(env, this, 0);
+
+  const brlapi_param_properties_t *properties;
+  brlapi_param_flags_t flags;
+
+  if (checkParameter(env, parameter, subparam, global, &properties, &flags)) {
+    WatchedParameterData *wpd;
+
+    if ((wpd = malloc(sizeof(*wpd)))) {
+      memset(wpd, 0, sizeof(*wpd));
+      wpd->handle = handle;
+
+      if ((wpd->watcher.object = (*env)->NewGlobalRef(env, watcher))) {
+        wpd->watcher.class = (*env)->FindClass(
+          env, BRLAPI_OBJECT("ParameterWatcher")
+        );
+
+        if (wpd->watcher.class) {
+          wpd->watcher.method = (*env)->GetMethodID(
+            env, wpd->watcher.class, "onParameterUpdated",
+            JAVA_SIG_METHOD(JAVA_SIG_VOID,
+              JAVA_SIG_INT // parameter
+              JAVA_SIG_LONG // subparam
+              JAVA_SIG_OBJECT(JAVA_OBJ_OBJECT) // value
+            )
+          );
+
+          if (wpd->watcher.method) {
+            wpd->descriptor = brlapi__watchParameter(
+              handle, parameter, subparam, flags,
+              handleWatchedParameter, wpd, NULL, 0
+            );
+
+            if (wpd->descriptor) return (intptr_t)wpd;
+            throwAPIError(env);
+          }
+        }
+
+        (*env)->DeleteGlobalRef(env, wpd->watcher.object);
+      }
+
+      free(wpd);
+    } else {
+      throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    }
+  }
+
+  return 0;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_ConnectionBase, unwatchParameter, void,
+  jlong identifier
+) {
+  WatchedParameterData *wpd = (WatchedParameterData *)(intptr_t)identifier;
+
+  if (brlapi__unwatchParameter(wpd->handle, wpd->descriptor) < 0) {
+    throwAPIError(env);
+  }
+
+  (*env)->DeleteGlobalRef(env, wpd->watcher.object);
+  free(wpd);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_APIError, toString, jstring
+) {
+  GET_CLASS(env, class, this, NULL);
+  brlapi_error_t error;
+  memset(&error, 0, sizeof(error));
+
+  {
+    FIND_FIELD(env, field, class, "apiError", JAVA_SIG_INT, NULL);
+    error.brlerrno = JAVA_GET_FIELD(env, Int, this, field);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "osError", JAVA_SIG_INT, NULL);
+    error.libcerrno = JAVA_GET_FIELD(env, Int, this, field);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "gaiError", JAVA_SIG_INT, NULL);
+    error.gaierrno = JAVA_GET_FIELD(env, Int, this, field);
+  }
+
+  jstring jFunction;
+  {
+    FIND_FIELD(env, field, class, "functionName", JAVA_SIG_STRING, NULL);
+
+    if ((jFunction = JAVA_GET_FIELD(env, Object, this, field))) {
+      const char *cFunction = (*env)->GetStringUTFChars(env, jFunction, NULL);
+      if (!cFunction) return NULL;
+      error.errfun = cFunction;
+    } else {
+      error.errfun = NULL;
+    }
+  }
+
+  size_t size = brlapi_strerror_r(&error, NULL, 0);
+  char cMessage[size+1];
+
+  brlapi_strerror_r(&error, cMessage, sizeof(cMessage));
+  if (jFunction) (*env)->ReleaseStringUTFChars(env, jFunction, error.errfun);
+
+  return (*env)->NewStringUTF(env, cMessage);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_APIException, toString, jstring
+) {
+  GET_CONNECTION_HANDLE(env, this, NULL);
+  GET_CLASS(env, class, this, NULL);
+
+  jint error;
+  {
+    FIND_FIELD(env, field, class, "errorNumber", JAVA_SIG_INT, NULL);
+    error = JAVA_GET_FIELD(env, Int, this, field);
+  }
+
+  jint type;
+  {
+    FIND_FIELD(env, field, class, "packetType", JAVA_SIG_INT, NULL);
+    type = JAVA_GET_FIELD(env, Int, this, field);
+  }
+
+  jbyteArray jPacket;
+  {
+    FIND_FIELD(env, field, class, "failedPacket", JAVA_SIG_ARRAY(JAVA_SIG_BYTE), NULL);
+
+    if (!(jPacket = JAVA_GET_FIELD(env, Object, this, field))) {
+      throwJavaError(env, JAVA_OBJ_NULL_POINTER_EXCEPTION, __func__);
+      return NULL;
+    }
+  }
+
+  jint size = (*env)->GetArrayLength(env, jPacket);
+  jbyte *cPacket = (*env)->GetByteArrayElements(env, jPacket, NULL);
+
+  if (!cPacket) {
+    throwJavaError(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, __func__);
+    return NULL;
+  }
+
+  char buffer[0X100];
+  brlapi__strexception(handle, buffer, sizeof(buffer), error, type, cPacket, size); 
+  jstring message = (*env)->NewStringUTF(env, buffer);
+  (*env)->ReleaseByteArrayElements(env, jPacket, cPacket, JNI_ABORT);
+
+  return message;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_APIException, getPacketTypeName, jstring,
+  jint type
+) {
+  const char *name = brlapi_getPacketTypeName((brlapi_packetType_t) type);
+  if (!name) return NULL;
+  return (*env)->NewStringUTF(env, name);
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_CommandKeycode, expandKeycode, void,
+  jlong code
+) {
+  brlapi_expandedKeyCode_t ekc;
+  GET_CLASS(env, class, this, );
+
+  if (brlapi_expandKeyCode((brlapi_keyCode_t)code, &ekc) < 0) {
+    memset(&ekc, 0, sizeof(ekc));
+    ekc.type = code & BRLAPI_KEY_TYPE_MASK;
+    ekc.command = code & BRLAPI_KEY_CODE_MASK;
+    ekc.flags = (code & BRLAPI_KEY_FLAGS_MASK) >> BRLAPI_KEY_FLAGS_SHIFT;
+  }
+
+  {
+    FIND_FIELD(env, field, class, "typeValue", JAVA_SIG_INT, );
+    JAVA_SET_FIELD(env, Int, this, field, ekc.type);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "commandValue", JAVA_SIG_INT, );
+    JAVA_SET_FIELD(env, Int, this, field, ekc.command);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "argumentValue", JAVA_SIG_INT, );
+    JAVA_SET_FIELD(env, Int, this, field, ekc.argument);
+  }
+
+  {
+    FIND_FIELD(env, field, class, "flagsValue", JAVA_SIG_INT, );
+    JAVA_SET_FIELD(env, Int, this, field, ekc.flags);
+  }
+}
+
+JAVA_INSTANCE_METHOD(
+  org_a11y_brlapi_CommandKeycode, describeKeycode, void,
+  jlong code
+) {
+  brlapi_describedKeyCode_t dkc;
+  GET_CLASS(env, class, this, );
+
+  if (brlapi_describeKeyCode((brlapi_keyCode_t)code, &dkc) < 0) {
+    memset(&dkc, 0, sizeof(dkc));
+    dkc.type = "UNSUPPORTED";
+  }
+
+  {
+    jstring name = (*env)->NewStringUTF(env, dkc.type);
+    if (!name) return;
+
+    FIND_FIELD(env, field, class, "typeName", JAVA_SIG_STRING, );
+    JAVA_SET_FIELD(env, Object, this, field, name);
+  }
+
+  {
+    jstring name = (*env)->NewStringUTF(env, dkc.command);
+    if (!name) return;
+
+    FIND_FIELD(env, field, class, "commandName", JAVA_SIG_STRING, );
+    JAVA_SET_FIELD(env, Object, this, field, name);
+  }
+
+  {
+    jclass stringClass = (*env)->FindClass(env, JAVA_OBJ_STRING);
+    if (!stringClass) return;
+
+    jsize count = dkc.flags;
+    jobjectArray names = (*env)->NewObjectArray(env, count, stringClass, NULL);
+    if (!names) return;
+
+    for (unsigned int index=0; index<count; index+=1) {
+      jstring name = (*env)->NewStringUTF(env, dkc.flag[index]);
+      if (!name) return;
+
+      (*env)->SetObjectArrayElement(env, names, index, name);
+      if ((*env)->ExceptionCheck(env)) return;
+    }
+
+    {
+      FIND_FIELD(env, field, class, "flagNames", JAVA_SIG_ARRAY(JAVA_SIG_STRING), );
+      JAVA_SET_FIELD(env, Object, this, field, names);
+    }
+  }
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_DriverKeycode, isPress, jboolean,
+  jlong code
+) {
+  return (code & BRLAPI_DRV_KEY_PRESS)? JNI_TRUE: JNI_FALSE;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_DriverKeycode, getValue, jlong,
+  jlong code
+) {
+  return code & BRLAPI_DRV_KEY_VALUE_MASK;
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_DriverKeycode, getGroup, jint,
+  jlong code
+) {
+  return BRLAPI_DRV_KEY_GROUP(code);
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_DriverKeycode, getNumber, jint,
+  jlong code
+) {
+  return BRLAPI_DRV_KEY_NUMBER(code);
+}
+
+JAVA_STATIC_METHOD(
+  org_a11y_brlapi_DriverKeycode, getNumberAny, jint
+) {
+  return BRLAPI_DRV_KEY_NUMBER_ANY;
+}
diff --git a/Bindings/Java/bindings.h b/Bindings/Java/bindings.h
new file mode 100644
index 0000000..844e361
--- /dev/null
+++ b/Bindings/Java/bindings.h
@@ -0,0 +1,42 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_JAVA_BINDINGS
+#define BRLAPI_INCLUDED_JAVA_BINDINGS
+
+#include "common_java.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define JAVA_GET_CONSTRUCTOR(env,class,arguments) \
+  ((*(env))->GetMethodID((env), (class), JAVA_CONSTRUCTOR_NAME, JAVA_SIG_CONSTRUCTOR(arguments)))
+
+#define JAVA_GET_FIELD(env,type,object,field) \
+  ((*(env))->Get ## type ## Field((env), (object), (field)))
+
+#define JAVA_SET_FIELD(env,type,object,field,value) \
+   ((*(env))->Set ## type ## Field((env), (object), (field), (value)))
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_JAVA_BINDINGS */
diff --git a/Bindings/Java/bindings.m4 b/Bindings/Java/bindings.m4
new file mode 100644
index 0000000..d0367da
--- /dev/null
+++ b/Bindings/Java/bindings.m4
@@ -0,0 +1,177 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_JAVA_BINDINGS], [dnl
+AC_SUBST([JAVA_OK], [false])
+AC_SUBST([JAVAC], [JAVA_COMPILER_NOT_FOUND_BY_CONFIGURE])
+AC_SUBST([JAVADOC], [JAVA_DOCUMENT_COMMAND_NOT_FOUND_BY_CONFIGURE])
+AC_SUBST([JAR], [JAVA_ARCHIVE_COMMAND_NOT_FOUND_BY_CONFIGURE])
+
+java_compiler_name="javac"
+java_compiler_path=""
+
+if test -n "${JAVA_HOME}"
+then
+   BRLTTY_CHECK_JAVA_HOME(["${JAVA_HOME}"])
+else
+   BRLTTY_CHECK_JAVA_COMPILER([${java_compiler_name}])
+   BRLTTY_CHECK_JAVA_HOME(["/usr/java"])
+   BRLTTY_CHECK_JAVA_HOME(["/usr/lib/jvm/default-java"])
+   BRLTTY_CHECK_JAVA_HOME(["/usr/lib/jvm/java"])
+   BRLTTY_CHECK_JAVA_COMPILER([gcj])
+fi
+
+test -n "${java_compiler_path}" && {
+   brltty_path=`realpath -- "${java_compiler_path}"` && java_compiler_path="${brltty_path}"
+   test "${java_compiler_path##*/}" = "javavm" && java_compiler_path=""
+}
+
+BRLTTY_CHECK_JAVA_PATH([dnl
+   test -n "${JAVA_VERSION}" || export JAVA_VERSION=11+
+   AC_MSG_NOTICE([Java version: ${JAVA_VERSION}])
+
+   AC_MSG_CHECKING([JVM path])
+   BRLTTY_JAVA_QUERY([brltty_path], [jvmpath])
+
+   if test -n "${brltty_path}"
+   then
+      AC_MSG_RESULT([${brltty_path}])
+      java_compiler_path="${brltty_path%/*}/${java_compiler_name}"
+   else
+      AC_MSG_RESULT([not found])
+   fi
+])
+
+if test -n "${java_compiler_path}"
+then
+   JAVA_OK=true
+   AC_MSG_NOTICE([Java compiler: ${java_compiler_path}])
+
+   java_compiler_name="${java_compiler_path##*/}"
+   java_source_encoding="UTF-8"
+
+   case "${java_compiler_name}"
+   in
+      javac) java_compiler_options="-encoding ${java_source_encoding}";;
+      gcj)   java_compiler_options="-C --encoding=${java_source_encoding}";;
+
+      *)     java_compiler_options=""
+             AC_MSG_WARN([Java compiler name not handled: ${java_compiler_name}])
+             ;;
+   esac
+
+   JAVAC="'${java_compiler_path}' ${java_compiler_options}"
+   java_commands_directory=`AS_DIRNAME("${java_compiler_path}")`
+   java_home_directory=`AS_DIRNAME("${java_commands_directory}")`
+
+   BRLTTY_CHECK_JAVA_COMMAND([java_document_command], [javadoc gjdoc])
+   test -n "${java_document_command}" || java_document_command=":"
+   JAVADOC="'${java_document_command}' -encoding ${java_source_encoding}"
+
+   BRLTTY_CHECK_JAVA_COMMAND([java_archive_command], [jar gjar])
+   JAR="'${java_archive_command}'"
+
+   BRLTTY_JAVA_DIRECTORY([JAR], [/usr/share/java /mingw])
+   BRLTTY_JAVA_DIRECTORY([JNI], [/usr/lib*/java /usr/lib*/jni /usr/lib/*/jni /mingw])
+
+   JAVA_JNI_INC="${java_home_directory}/include"
+   JAVA_JNI_HDR="jni.h"
+   JAVA_JNI_FLAGS=""
+   AC_CHECK_HEADER([${JAVA_JNI_HDR}], [], [AC_CHECK_FILE(["${JAVA_JNI_INC}/${JAVA_JNI_HDR}"], [JAVA_JNI_FLAGS="-I${JAVA_JNI_INC}"], [JAVA_OK=false])])
+
+   "${JAVA_OK}" && {
+      set -- "${JAVA_JNI_INC}"/*/jni_md.h
+
+      if test ${#} -eq 1
+      then
+         brltty_directory=`AS_DIRNAME("${1}")`
+         JAVA_JNI_FLAGS="${JAVA_JNI_FLAGS} -I${brltty_directory}"
+      elif test ${#} -gt 0
+      then
+         AC_MSG_WARN([more than one machine-dependent Java header found: ${*}])
+         JAVA_OK=false
+      fi
+   }
+
+   AC_SUBST([JAVA_JNI_HDR])
+   AC_SUBST([JAVA_JNI_INC], ["'${JAVA_JNI_INC}'"])
+   AC_SUBST([JAVA_JNI_FLAGS])
+else
+   AC_MSG_WARN([Java compiler not found])
+fi
+])
+
+AC_DEFUN([BRLTTY_CHECK_JAVA_PATH], [dnl
+test -n "${java_compiler_path}" || {
+   $1
+}
+])
+
+AC_DEFUN([BRLTTY_CHECK_JAVA_COMPILER], [dnl
+   BRLTTY_CHECK_JAVA_PATH([dnl
+      AC_PATH_PROG([java_compiler_path], [$1], [])
+   ])
+])
+
+AC_DEFUN([BRLTTY_CHECK_JAVA_HOME], [dnl
+test -n "${java_compiler_path}" || {
+   AC_MSG_CHECKING([if $1 is a JDK])
+   brltty_path="$1/bin/${java_compiler_name}"
+
+   if test -f "${brltty_path}" -a -x "${brltty_path}"
+   then
+      java_compiler_path="${brltty_path}"
+      AC_MSG_RESULT([yes])
+   else
+      AC_MSG_RESULT([no])
+   fi
+}
+])
+
+AC_DEFUN([BRLTTY_CHECK_JAVA_COMMAND], [dnl
+   AC_PATH_PROGS([$1], [$2], [], ["${java_commands_directory}"])
+])
+
+AC_DEFUN([BRLTTY_JAVA_DIRECTORY], [dnl
+test -n "${JAVA_$1_DIR}" || {
+   for directory in $2
+   do
+      test -d "${directory}" && {
+	 JAVA_$1_DIR="${directory}"
+	 break
+      }
+   done
+}
+
+if test -n "${JAVA_$1_DIR}"
+then
+   JAVA_$1="yes"
+   AC_MSG_NOTICE([Java] m4_tolower([$1]) [installation directory: ${JAVA_$1_DIR}])
+else
+   JAVA_$1="no"
+   AC_MSG_WARN([no commonly used] m4_tolower([$1]) [installation directory])
+fi
+
+AC_SUBST([JAVA_$1])
+AC_SUBST([JAVA_$1_DIR])
+])
+
+AC_DEFUN([BRLTTY_JAVA_QUERY], [dnl
+   $1=`"${srcdir}/Tools/javacmd" $2`
+])
diff --git a/Bindings/Java/clients/ApiErrorClient.java b/Bindings/Java/clients/ApiErrorClient.java
new file mode 100644
index 0000000..ac50660
--- /dev/null
+++ b/Bindings/Java/clients/ApiErrorClient.java
@@ -0,0 +1,45 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class ApiErrorClient extends Client {
+  public ApiErrorClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    String result = null;
+
+    try {
+      connection.setParameter(
+        Constants.PARAM_SERVER_VERSION, 0, true, new int[] {0}
+      );
+
+      result = "not thrown";
+    } catch (APIError error) {
+      result = "test succeeded";
+    }
+
+    printf("API Error: %s\n", result);
+  }
+}
diff --git a/Bindings/Java/clients/ApiExceptionClient.java b/Bindings/Java/clients/ApiExceptionClient.java
new file mode 100644
index 0000000..3174102
--- /dev/null
+++ b/Bindings/Java/clients/ApiExceptionClient.java
@@ -0,0 +1,57 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class ApiExceptionClient extends PauseClient {
+  public ApiExceptionClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection)
+            throws ProgramException
+  {
+    ttyMode(
+      connection, false,
+      (con) -> {
+        WriteArguments arguments = new WriteArguments()
+          .setRegion(1, (con.getCellCount() + 1))
+          .setText("This should fail because the region size is too big.");
+
+        con.write(arguments);
+        String result = null;
+
+        try {
+          if (pause(con)) {
+            result = "wait timed out";
+          } else {
+            result = "wait was interrupted";
+          }
+        } catch (APIException exception) {
+          result = "test succeeded";
+        }
+
+        printf("API Exception: %s\n", result);
+      }
+    );
+  }
+}
diff --git a/Bindings/Java/clients/BoundCommandsClient.java b/Bindings/Java/clients/BoundCommandsClient.java
new file mode 100644
index 0000000..7c09fe3
--- /dev/null
+++ b/Bindings/Java/clients/BoundCommandsClient.java
@@ -0,0 +1,41 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class BoundCommandsClient extends Client {
+  public BoundCommandsClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    Parameters parameters = connection.getParameters();
+
+    for (long code : parameters.boundCommandKeycodes.get()) {
+      printf(
+        "%s: %s\n",
+        parameters.commandKeycodeName.get(code),
+        parameters.commandKeycodeSummary.get(code)
+      );
+    }
+  }
+}
diff --git a/Bindings/Java/clients/ComputerBrailleClient.java b/Bindings/Java/clients/ComputerBrailleClient.java
new file mode 100644
index 0000000..a6d4dd8
--- /dev/null
+++ b/Bindings/Java/clients/ComputerBrailleClient.java
@@ -0,0 +1,41 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class ComputerBrailleClient extends Client {
+  public ComputerBrailleClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    Parameters parameters = connection.getParameters();
+
+    for (int row : parameters.computerBrailleRowsMask.get().getBitNumbers()) {
+      printf(
+        "U+%04X: %s\n",
+        (row << 8),
+        parameters.computerBrailleRowCells.get(row).toString()
+      );
+    }
+  }
+}
diff --git a/Bindings/Java/clients/DriverKeysClient.java b/Bindings/Java/clients/DriverKeysClient.java
new file mode 100644
index 0000000..e3ca6aa
--- /dev/null
+++ b/Bindings/Java/clients/DriverKeysClient.java
@@ -0,0 +1,39 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class DriverKeysClient extends Client {
+  public DriverKeysClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    Parameters parameters = connection.getParameters();
+
+    for (long code : parameters.definedDriverKeycodes.get()) {
+      printf(
+        "%s\n", parameters.driverKeycodeName.get(code)
+      );
+    }
+  }
+}
diff --git a/Bindings/Java/clients/EchoClient.java b/Bindings/Java/clients/EchoClient.java
new file mode 100644
index 0000000..ac48687
--- /dev/null
+++ b/Bindings/Java/clients/EchoClient.java
@@ -0,0 +1,238 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import java.io.InterruptedIOException;
+import java.util.concurrent.TimeoutException;
+import org.a11y.brlapi.*;
+
+public class EchoClient extends Client {
+  private boolean echoDriverKeys = false;
+  private boolean echoNames = false;
+
+  public final static int MINIMUM_READ_COUNT =  1;
+  public final static int DEFAULT_READ_COUNT = 10;
+
+  public final static String NO_TIMEOUT = "none";
+  public final static int FOREVER_READ_TIMEOUT =  0;
+  public final static int MINIMUM_READ_TIMEOUT =  1;
+  public final static int DEFAULT_READ_TIMEOUT = 10;
+
+  private int readCount = DEFAULT_READ_COUNT;
+  private final OperandUsage readCountUsage = new OperandUsage("read count")
+    .setDefault(readCount)
+    .setRangeMinimum(MINIMUM_READ_COUNT)
+    ;
+
+  private int readTimeout = DEFAULT_READ_TIMEOUT;
+  private final OperandUsage readTimeoutUsage = new OperandUsage("read timeout")
+    .setDefault(readTimeout)
+    .setRangeMinimum(MINIMUM_READ_TIMEOUT)
+    .setRangeUnits("seconds")
+    .setRangeComment("readKeyWithTimeout is used")
+    .addWord(NO_TIMEOUT, FOREVER_READ_TIMEOUT, "readKey is used")
+    ;
+
+  public EchoClient (String... arguments) {
+    super(arguments);
+    addRepeatingParameter("tty");
+
+    addOption("commands",
+      (operands) -> {
+        echoDriverKeys = false;
+      }
+    );
+
+    addOption("keys",
+      (operands) -> {
+        echoDriverKeys = true;
+      }
+    );
+
+    addOption("values",
+      (operands) -> {
+        echoNames = false;
+      }
+    );
+
+    addOption("names",
+      (operands) -> {
+        echoNames = true;
+      }
+    );
+
+    addOption("reads",
+      (operands) -> {
+        readCount = Parse.asInt(
+          readCountUsage.getOperandDescription(),
+          operands[0], MINIMUM_READ_COUNT
+        );
+      },
+      "count"
+    );
+
+    addOption("timeout",
+      (operands) -> {
+        String operand = operands[0];
+
+        if (Strings.isAbbreviation(NO_TIMEOUT, operand)) {
+          readTimeout = FOREVER_READ_TIMEOUT;
+        } else {
+          readTimeout = Parse.asInt(
+            readTimeoutUsage.getOperandDescription(),
+            operand, MINIMUM_READ_TIMEOUT
+          );
+        }
+      },
+      "seconds"
+    );
+  }
+
+  @Override
+  protected final void extendUsageSummary (StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    readCountUsage.appendTo(usage);
+    readTimeoutUsage.appendTo(usage);
+  }
+
+  private int[] ttyPath = null;
+
+  @Override
+  protected void processParameters (String[] parameters)
+            throws SyntaxException
+  {
+    int count = parameters.length;
+    ttyPath = new int[count];
+
+    for (int i=0; i<count; i+=1) {
+      ttyPath[i] = Parse.asInt("tty path component", parameters[i]);
+    }
+  }
+
+  private final void show (Connection connection, String text) {
+    printf("%s\n", text);
+    connection.write(text, Constants.CURSOR_OFF);
+  }
+
+  private final String toString (Connection connection, long code) {
+    if (echoDriverKeys) {
+      DriverKeycode keycode = new DriverKeycode(code);
+      String string = keycode.toString();
+
+      if (echoNames) {
+        String name = connection.getParameters().driverKeycodeName.get(keycode.getValue());
+
+        if ((name != null) && !name.isEmpty()) {
+          string += " Name:" + name;
+        }
+      }
+
+      return string;
+    } else {
+      CommandKeycode keycode = new CommandKeycode(code);
+      if (!echoNames) return keycode.toString();
+
+      StringBuilder builder = new StringBuilder();
+      builder.append(Keycode.toString(code));
+
+      {
+        String type = keycode.getTypeName();
+        if (type != null) builder.append(" Type:").append(type);
+      }
+
+      {
+        String command = keycode.getCommandName();
+        if (command != null) builder.append(" Cmd:").append(command);
+      }
+
+      {
+        int argument = keycode.getArgument();
+
+        if (argument > 0) {
+          builder.append(
+            String.format(
+              " Arg:%04X(%d)", argument, argument
+            )
+          );
+        }
+      }
+
+      {
+        String[] flags = keycode.getFlagNames();
+
+        if (flags != null) {
+          int count = flags.length;
+          boolean first = true;
+
+          for (int index=0; index<count; index+=1) {
+            String flag = flags[index];
+            if (flag == null) continue;
+            if (flag.isEmpty()) continue;
+
+            if (first) {
+              first = false;
+              builder.append(" Flg:");
+            } else {
+              builder.append(',');
+            }
+
+            builder.append(flag);
+          }
+        }
+      }
+
+      return builder.toString();
+    }
+  }
+
+  @Override
+  protected final void runClient (Connection connection) throws ProgramException {
+    ttyMode(
+      connection, echoDriverKeys,
+      (con) -> {
+        show(con,
+          String.format(
+            "press keys (timeout is %d seconds)", readTimeout
+          )
+        );
+
+        while (readCount-- > 0) {
+          long code;
+
+          try {
+            if (readTimeout == FOREVER_READ_TIMEOUT) {
+              code = con.readKey();
+            } else {
+              code = con.readKeyWithTimeout((readTimeout * 1000));
+            }
+          } catch (InterruptedIOException exception) {
+            continue;
+          } catch (TimeoutException exception) {
+            break;
+          }
+
+          show(con, toString(con, code));
+        }
+      },
+      ttyPath
+    );
+  }
+}
diff --git a/Bindings/Java/clients/GetDriverClient.java b/Bindings/Java/clients/GetDriverClient.java
new file mode 100644
index 0000000..8f2c42d
--- /dev/null
+++ b/Bindings/Java/clients/GetDriverClient.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class GetDriverClient extends Client {
+  public GetDriverClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    printf("%s\n", connection.getDriverName());
+  }
+}
diff --git a/Bindings/Java/clients/GetModelClient.java b/Bindings/Java/clients/GetModelClient.java
new file mode 100644
index 0000000..d8721c7
--- /dev/null
+++ b/Bindings/Java/clients/GetModelClient.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class GetModelClient extends Client {
+  public GetModelClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    printf("%s\n", connection.getModelIdentifier());
+  }
+}
diff --git a/Bindings/Java/clients/GetSizeClient.java b/Bindings/Java/clients/GetSizeClient.java
new file mode 100644
index 0000000..715c58e
--- /dev/null
+++ b/Bindings/Java/clients/GetSizeClient.java
@@ -0,0 +1,33 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class GetSizeClient extends Client {
+  public GetSizeClient (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) {
+    printf("%s\n", connection.getDisplaySize());
+  }
+}
diff --git a/Bindings/Java/clients/ListParametersClient.java b/Bindings/Java/clients/ListParametersClient.java
new file mode 100644
index 0000000..35597ff
--- /dev/null
+++ b/Bindings/Java/clients/ListParametersClient.java
@@ -0,0 +1,90 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class ListParametersClient extends Client {
+  public ListParametersClient (String... arguments) {
+    super(arguments);
+    addOptionalParameters("parameter", "subparam");
+  }
+
+  private String parameterName = null;
+  private Long subparamValue = null;
+
+  @Override
+  protected void processParameters (String[] parameters)
+            throws SyntaxException
+  {
+    int count = parameters.length;
+    int index = 0;
+
+    if (index == count) return;
+    parameterName = parameters[index++];
+
+    if (index == count) return;
+    subparamValue = Parse.asLong("subparam", parameters[index++]);
+
+    if (index < count) throw new TooManyParametersException(parameters, index);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) throws SemanticException {
+    if (parameterName == null) {
+      Parameter[] parameterArray = connection.getParameters().get();
+      Parameters.sortByName(parameterArray);
+
+      for (Parameter parameter : parameterArray) {
+        if (parameter.isHidable()) continue;
+        String value = parameter.toString();
+        if (value != null) printf("%s: %s\n", parameter.getLabel(), value);
+      }
+    } else {
+      Parameter parameter = getParameter(connection, parameterName);
+      String value;
+
+      if (subparamValue == null) {
+        value = parameter.toString();
+      } else {
+        long subparam = subparamValue;
+        value = parameter.toString(subparam);
+      }
+
+      if (value == null) {
+        StringBuilder message = new StringBuilder();
+        message.append("parameter has no value");
+
+        message.append(": ");
+        message.append(parameter.getName());
+
+        if (subparamValue != null) {
+          message.append('[');
+          message.append(subparamValue);
+          message.append(']');
+        }
+
+        throw new SemanticException("%s", message);
+      }
+
+      printf("%s\n", value);
+    }
+  }
+}
diff --git a/Bindings/Java/clients/PauseClient.java b/Bindings/Java/clients/PauseClient.java
new file mode 100644
index 0000000..3a37865
--- /dev/null
+++ b/Bindings/Java/clients/PauseClient.java
@@ -0,0 +1,80 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class PauseClient extends Client {
+  public final static byte MINIMUM_WAIT_TIME = 1;
+  public final static byte DEFAULT_WAIT_TIME = 5;
+
+  private byte waitTime = DEFAULT_WAIT_TIME;
+  private final OperandUsage waitTimeUsage = new OperandUsage("wait time")
+    .setDefault(waitTime)
+    .setRangeMinimum(MINIMUM_WAIT_TIME)
+    .setRangeUnits("seconds")
+    ;
+
+  protected final int getWaitTime () {
+    return waitTime * 1000;
+  }
+
+  protected final boolean pause (Connection connection) {
+    return pause(connection, getWaitTime());
+  }
+
+  public PauseClient (String... arguments) {
+    super(arguments);
+    // don't add parameters to this client because some other clients extend it
+
+    addOption("wait",
+      (operands) -> {
+        waitTime = Parse.asByte(
+          waitTimeUsage.getOperandDescription(),
+          operands[0], MINIMUM_WAIT_TIME
+        );
+      },
+      "seconds"
+    );
+  }
+
+  @Override
+  protected void extendUsageSummary (StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    waitTimeUsage.appendTo(usage);
+  }
+
+  @Override
+  protected void runClient (Connection connection) 
+            throws ProgramException
+  {
+    printf("Pausing for %d seconds...", waitTime);
+    String result;
+
+    if (pause(connection)) {
+      result = "pause completed";
+    } else {
+      result = "pause interrupted";
+    }
+
+    printf(" %s\n", result);
+  }
+}
diff --git a/Bindings/Java/clients/SetParameterClient.java b/Bindings/Java/clients/SetParameterClient.java
new file mode 100644
index 0000000..311bd3d
--- /dev/null
+++ b/Bindings/Java/clients/SetParameterClient.java
@@ -0,0 +1,56 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class SetParameterClient extends Client {
+  public SetParameterClient (String... arguments) {
+    super(arguments);
+    addRequiredParameters("parameter", "value");
+  }
+
+  private String parameterName = null;
+  private String parameterValue = null;
+
+  @Override
+  protected void processParameters (String[] parameters)
+            throws SyntaxException
+  {
+    int count = parameters.length;
+    int index = 0;
+
+    if (index == count) throw new SyntaxException("missing parameter name");
+    parameterName = parameters[index++];
+
+    if (index == count) throw new SyntaxException("missing larameter value");
+    parameterValue = parameters[index++];
+
+    if (index < count) throw new TooManyParametersException(parameters, index);
+  }
+
+  @Override
+  protected final void runClient (Connection connection)
+            throws OperandException
+  {
+    Parameter parameter = getParameter(connection, parameterName);
+    parameter.set(parameterValue);
+  }
+}
diff --git a/Bindings/Java/clients/WriteArgumentsClient.java b/Bindings/Java/clients/WriteArgumentsClient.java
new file mode 100644
index 0000000..81edafe
--- /dev/null
+++ b/Bindings/Java/clients/WriteArgumentsClient.java
@@ -0,0 +1,159 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.a11y.brlapi.*;
+
+public class WriteArgumentsClient extends PauseClient {
+  private final List<WriteArguments> writeArgumentsList = new LinkedList<>();
+  private WriteArguments writeArguments = new WriteArguments();
+  private boolean fixWriteArguments = true;
+
+  private final OperandUsage cursorPositionUsage =
+     new CursorPositionUsage(writeArguments.getCursorPosition());
+
+  private final OperandUsage displayNumberUsage =
+      new DisplayNumberUsage(writeArguments.getDisplayNumber());
+
+  public final static int MINIMUM_REGION_BEGIN = 1;
+  public final static int MINIMUM_REGION_SIZE = 1;
+
+  public WriteArgumentsClient (String... arguments) {
+    super(arguments);
+    writeArgumentsList.add(writeArguments);
+
+    addOption("text",
+      (operands) -> {
+        writeArguments.setText(operands[0]);
+      },
+      "text"
+    );
+
+    addOption("begin",
+      (operands) -> {
+        writeArguments.setRegionBegin(
+          Parse.asInt(
+            WriteArguments.REGION_BEGIN,
+            operands[0], MINIMUM_REGION_BEGIN
+          )
+        );
+      },
+      "region begin"
+    );
+
+    addOption("length",
+      (operands) -> {
+        writeArguments.setRegionSize(
+          Parse.asInt(
+            WriteArguments.REGION_SIZE,
+            operands[0], MINIMUM_REGION_SIZE
+          )
+        );
+      },
+      "region size"
+    );
+
+    addOption("cursor",
+      (operands) -> {
+        writeArguments.setCursorPosition(Parse.asCursorPosition(operands[0]));
+      },
+      "position"
+    );
+
+    addOption("display",
+      (operands) -> {
+        writeArguments.setDisplayNumber(Parse.asDisplayNumber(operands[0]));
+      },
+      "number"
+    );
+
+    addOption("fix",
+      (operands) -> {
+        fixWriteArguments = true;
+      }
+    );
+
+    addOption("nofix",
+      (operands) -> {
+        fixWriteArguments = false;
+      }
+    );
+
+    addOption("render",
+      (operands) -> {
+        writeArguments = new WriteArguments();
+        writeArgumentsList.add(writeArguments);
+      }
+    );
+  }
+
+  @Override
+  protected final void extendUsageSummary (StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    new OperandUsage(WriteArguments.REGION_BEGIN)
+      .setDefault(MINIMUM_REGION_BEGIN)
+      .setRangeMinimum(MINIMUM_REGION_BEGIN)
+      .appendTo(usage);
+
+    new OperandUsage(WriteArguments.REGION_SIZE)
+      .setDefault(
+        String.format(
+          "the size of the largest content property (%s, %s, %s)",
+          WriteArguments.TEXT, WriteArguments.AND_MASK, WriteArguments.OR_MASK
+        )
+       )
+      .setRangeMinimum(MINIMUM_REGION_SIZE)
+      .setRangeUnits("cells")
+      .appendTo(usage);
+
+    cursorPositionUsage.appendTo(usage);
+    displayNumberUsage.appendTo(usage);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) 
+            throws ProgramException
+  {
+    ttyMode(
+      connection, false,
+      (con) -> {
+        int cellCount = con.getCellCount();
+
+        for (WriteArguments arguments : writeArgumentsList) {
+          try {
+            arguments.check(cellCount, fixWriteArguments);
+          } catch (IllegalStateException exception) {
+            throw new SyntaxException(exception.getMessage());
+          }
+
+          con.write(arguments);
+        }
+
+        byte[] dots = con.getParameters().renderedCells.get();
+        printf("%s\n", toUnicodeBraille(dots));
+
+        pause(con);
+      }
+    );
+  }
+}
diff --git a/Bindings/Java/clients/WriteDotsClient.java b/Bindings/Java/clients/WriteDotsClient.java
new file mode 100644
index 0000000..c1b9d3b
--- /dev/null
+++ b/Bindings/Java/clients/WriteDotsClient.java
@@ -0,0 +1,60 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class WriteDotsClient extends PauseClient {
+  public WriteDotsClient (String... arguments) {
+    super(arguments);
+    addRepeatingParameter("dots");
+  }
+
+  private byte[] dots = null;
+
+  @Override
+  protected final void processParameters (String[] parameters)
+            throws SyntaxException
+  {
+    int count = parameters.length;
+    dots = new byte[count];
+
+    for (int index=0; index<count; index+=1) {
+      dots[index] = Parse.asDots("dots parameter", parameters[index]);
+    }
+  }
+
+  @Override
+  protected final void runClient (Connection connection) 
+            throws ProgramException
+  {
+    ttyMode(
+      connection, false,
+      (con) -> {
+        con.write(dots);
+
+        byte[] dots = con.getParameters().renderedCells.get();
+        printf("%s\n", toUnicodeBraille(dots));
+
+        pause(con);
+      }
+    );
+  }
+}
diff --git a/Bindings/Java/clients/WriteTextClient.java b/Bindings/Java/clients/WriteTextClient.java
new file mode 100644
index 0000000..bf16f40
--- /dev/null
+++ b/Bindings/Java/clients/WriteTextClient.java
@@ -0,0 +1,84 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.clients;
+
+import org.a11y.brlapi.*;
+
+public class WriteTextClient extends PauseClient {
+  private int cursorPosition = Constants.CURSOR_OFF;
+  private final OperandUsage cursorPositionUsage =
+     new CursorPositionUsage(cursorPosition);
+
+  public WriteTextClient (String... arguments) {
+    super(arguments);
+    addRepeatingParameter("text");
+
+    addOption("cursor",
+      (operands) -> {
+        cursorPosition = Parse.asCursorPosition(operands[0]);
+      },
+      "position"
+    );
+  }
+
+  private String text = null;
+
+  @Override
+  protected final void processParameters (String[] parameters) {
+    StringBuilder builder = new StringBuilder();
+
+    for (String parameter : parameters) {
+      if (builder.length() > 0) builder.append(' ');
+      builder.append(parameter);
+    }
+
+    text = builder.toString();
+  }
+
+  @Override
+  protected final void extendUsageSummary (StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    cursorPositionUsage.appendTo(usage);
+  }
+
+  @Override
+  protected final void runClient (Connection connection) 
+            throws ProgramException
+  {
+    Parse.checkMaximum(
+      cursorPositionUsage.getOperandDescription(),
+      cursorPosition, connection.getCellCount()
+    );
+
+    ttyMode(
+      connection, false,
+      (con) -> {
+        printf("%s\n", text);
+        con.write(text, cursorPosition);
+
+        byte[] dots = con.getParameters().renderedCells.get();
+        printf("%s\n", toUnicodeBraille(dots));
+
+        pause(con);
+      }
+    );
+  }
+}
diff --git a/Bindings/Java/commands/ApiToolCommand.java b/Bindings/Java/commands/ApiToolCommand.java
new file mode 100644
index 0000000..2383b74
--- /dev/null
+++ b/Bindings/Java/commands/ApiToolCommand.java
@@ -0,0 +1,157 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.commands;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import org.a11y.brlapi.*;
+import org.a11y.brlapi.clients.*;
+import org.a11y.brlapi.programs.*;
+
+public class ApiToolCommand extends Program {
+  private static final KeywordMap<Class<? extends Program>> knownPrograms = new KeywordMap<>();
+
+  private static void addProgram(Class<? extends Program> type) {
+    String name = type.getSimpleName();
+
+    if (isClient(type)) {
+      name = Strings.replaceAll(name, "Client$", "");
+    } else {
+      name = Strings.replaceAll(name, "Program$", "");
+    }
+
+    name = toOperandName(Strings.wordify(name));
+    knownPrograms.put(name, type);
+  }
+
+  static {
+    addProgram(ApiErrorClient.class);
+    addProgram(ApiExceptionClient.class);
+    addProgram(BoundCommandsClient.class);
+    addProgram(ComputerBrailleClient.class);
+    addProgram(DriverKeysClient.class);
+    addProgram(EchoClient.class);
+    addProgram(GetDriverClient.class);
+    addProgram(GetModelClient.class);
+    addProgram(GetSizeClient.class);
+    addProgram(ListParametersClient.class);
+    addProgram(PauseClient.class);
+    addProgram(SetParameterClient.class);
+    addProgram(VersionProgram.class);
+    addProgram(WriteArgumentsClient.class);
+    addProgram(WriteDotsClient.class);
+    addProgram(WriteTextClient.class);
+  }
+
+  public ApiToolCommand(String... arguments) {
+    super(arguments);
+    addRequiredParameters("program/client");
+    addRepeatingParameter("argument");
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Command line access to the functionality provided by the BrlAPI interface.";
+  }
+
+  @Override
+  protected final void extendUsageSummary(StringBuilder usage) {
+    super.extendUsageSummary(usage);
+
+    if (knownPrograms.isEmpty()) {
+      usage.append("\nNo programs or clients have been defined.");
+    } else {
+      usage
+          .append("\nThese programs and clients have been defined")
+          .append(" (each has its own -help option):");
+
+      for (String name : knownPrograms.getKeywords()) {
+        usage.append("\n  ").append(name);
+      }
+    }
+  }
+
+  private String programName = null;
+  private Class<? extends Program> programType = null;
+  private String[] programArguments = null;
+
+  @Override
+  protected final void processParameters(String[] parameters) throws SyntaxException {
+    int count = parameters.length;
+
+    if (count == 0) {
+      throw new SyntaxException("missing program/client name");
+    }
+
+    programName = parameters[0];
+    programType = knownPrograms.get(programName);
+
+    if (programType == null) {
+      throw new SyntaxException("unknown program/client: %s", programName);
+    }
+
+    count -= 1;
+    programArguments = new String[count];
+    System.arraycopy(parameters, 1, programArguments, 0, count);
+  }
+
+  @Override
+  protected final void runProgram() throws ProgramException {
+    String objectName = getObjectName();
+    Program program = null;
+
+    try {
+      Constructor<? extends Program> constructor =
+          programType.getConstructor(programArguments.getClass());
+
+      program = (Program) constructor.newInstance((Object) programArguments);
+    } catch (NoSuchMethodException exception) {
+      throw new ProgramException("constructor not found: %s", objectName);
+    } catch (InstantiationException exception) {
+      throw new ProgramException(
+          "instantiation failed: %s: %s", objectName, exception.getMessage());
+    } catch (IllegalAccessException exception) {
+      throw new ProgramException("access denied: %s: %s", objectName, exception.getMessage());
+    } catch (InvocationTargetException exception) {
+      Throwable cause = exception.getCause();
+
+      String message =
+          String.format(
+              "construction failed: %s: %s", objectName, cause.getClass().getSimpleName());
+
+      {
+        String problem = cause.getMessage();
+        if (problem != null) message += ": " + problem;
+      }
+
+      throw new ProgramException(message);
+    }
+
+    program.setProgramName(programName).run();
+  }
+
+  public static void main(String... arguments) {
+    try {
+      new ApiToolCommand(arguments).setProgramName("apitool").run();
+    } catch (ExitException exception) {
+      System.exit(exception.getExitCode());
+    }
+  }
+}
diff --git a/Bindings/Java/constants.awk b/Bindings/Java/constants.awk
new file mode 100644
index 0000000..5f8e687
--- /dev/null
+++ b/Bindings/Java/constants.awk
@@ -0,0 +1,119 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  print "package org.a11y.brlapi;"
+  print ""
+  print "public interface Constants {"
+}
+
+END {
+  print "}"
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeCommandDefinition(name, value, help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  if (name == "PASSCHAR") return
+  if (name == "PASSKEY") return
+
+  writeCommandDefinition(name, "(" value " << KEY_CMD_BLK_SHIFT)", help)
+}
+
+function brlKey(name, symbol, value, help) {
+}
+
+function brlFlag(name, symbol, value, help) {
+  if (value ~ /^0[xX][0-9a-fA-F]+0000$/) {
+    value = hexadecimalValue(substr(value, 1, length(value)-4))
+    if (name ~ /^CHAR_/) {
+      name = substr(name, 6)
+    } else {
+      value = value "00"
+    }
+  } else if (value ~ /^\(/) {
+    gsub("BRL_FLG_", "KEY_FLG_", value)
+  } else {
+    return
+  }
+  writeJavaConstant("int", "KEY_FLG_" name, value, help)
+}
+
+function brlDot(number, symbol, value, help) {
+  writeJavaConstant("int", "DOT" number, value, help)
+}
+
+function apiConstant(name, symbol, value, help) {
+  writeJavaConstant("int", name, value, help);
+}
+
+function apiMask(name, symbol, value, help) {
+  writeJavaConstant("long", "KEY_" name, hexadecimalValue(value) "L");
+}
+
+function apiShift(name, symbol, value, help) {
+  writeJavaConstant("int", "KEY_" name, hexadecimalValue(value));
+}
+
+function apiType(name, symbol, value, help) {
+  writeJavaConstant("int", "KEY_TYPE_" name, hexadecimalValue(value));
+}
+
+function apiKey(name, symbol, value, help) {
+  value = hexadecimalValue(value)
+
+  if (name == "FUNCTION") {
+    for (i=0; i<35; ++i) {
+      writeKeyDefinition("F" (i+1), "(" value " + " i ")")
+    }
+  } else {
+    writeKeyDefinition(name, value)
+  }
+}
+
+function apiRangeType(name, symbol, value, help) {
+  writeJavaConstant("int", "rangeType_" name, value, help)
+}
+
+function writeCommandDefinition(name, value, help) {
+  writeJavaConstant("int", "KEY_CMD_" name, value, help)
+}
+
+function writeKeyDefinition(name, value) {
+  writeJavaConstant("int", "KEY_SYM_" name, value)
+}
+
+function writeJavaConstant(type, name, value, help) {
+  writeJavadocComment(help)
+  print "  " type " " name " = " value ";"
+}
+
+function writeJavadocComment(text) {
+  if (length(text) > 0) print "  /** " text " */"
+}
+
+function hexadecimalValue(value) {
+  value = tolower(value)
+  gsub("x0+", "x", value)
+  gsub("x$", "x0", value)
+  return value
+}
+
diff --git a/Bindings/Java/manifest b/Bindings/Java/manifest
new file mode 100644
index 0000000..bf773ae
--- /dev/null
+++ b/Bindings/Java/manifest
@@ -0,0 +1 @@
+Main-Class: org.a11y.brlapi.commands.ApiToolCommand
diff --git a/Bindings/Java/parameters/AudibleAlertsParameter.java b/Bindings/Java/parameters/AudibleAlertsParameter.java
new file mode 100644
index 0000000..0611bf6
--- /dev/null
+++ b/Bindings/Java/parameters/AudibleAlertsParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class AudibleAlertsParameter extends GlobalParameter implements Parameter.BooleanSettable {
+  public AudibleAlertsParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_AUDIBLE_ALERTS;
+  }
+
+  @Override
+  public final Boolean get () {
+    return asBoolean(getValue());
+  }
+
+  @Override
+  public final void set (boolean yes) {
+    setValue(new boolean[] {yes});
+  }
+}
diff --git a/Bindings/Java/parameters/BoundCommandKeycodesParameter.java b/Bindings/Java/parameters/BoundCommandKeycodesParameter.java
new file mode 100644
index 0000000..d981914
--- /dev/null
+++ b/Bindings/Java/parameters/BoundCommandKeycodesParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class BoundCommandKeycodesParameter extends GlobalParameter {
+  public BoundCommandKeycodesParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_BOUND_COMMAND_KEYCODES;
+  }
+
+  @Override
+  public boolean isHidable () {
+    return true;
+  }
+
+  @Override
+  public final long[] get () {
+    return asLongArray(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/ClientPriorityParameter.java b/Bindings/Java/parameters/ClientPriorityParameter.java
new file mode 100644
index 0000000..4378750
--- /dev/null
+++ b/Bindings/Java/parameters/ClientPriorityParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ClientPriorityParameter extends LocalParameter implements Parameter.IntSettable {
+  public ClientPriorityParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_CLIENT_PRIORITY;
+  }
+
+  @Override
+  public final Integer get () {
+    return asInt(getValue());
+  }
+
+  @Override
+  public final void set (int priority) {
+    setValue(new int[] {priority});
+  }
+}
diff --git a/Bindings/Java/parameters/ClipboardContentParameter.java b/Bindings/Java/parameters/ClipboardContentParameter.java
new file mode 100644
index 0000000..43d83f5
--- /dev/null
+++ b/Bindings/Java/parameters/ClipboardContentParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ClipboardContentParameter extends GlobalParameter implements Parameter.StringSettable {
+  public ClipboardContentParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_CLIPBOARD_CONTENT;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+
+  @Override
+  public final void set (String text) {
+    setValue(text);
+  }
+}
diff --git a/Bindings/Java/parameters/CommandKeycodeNameParameter.java b/Bindings/Java/parameters/CommandKeycodeNameParameter.java
new file mode 100644
index 0000000..6808f06
--- /dev/null
+++ b/Bindings/Java/parameters/CommandKeycodeNameParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class CommandKeycodeNameParameter extends GlobalParameter {
+  public CommandKeycodeNameParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMMAND_KEYCODE_NAME;
+  }
+
+  @Override
+  public final String get (long code) {
+    return asString(getValue(code));
+  }
+}
diff --git a/Bindings/Java/parameters/CommandKeycodeSummaryParameter.java b/Bindings/Java/parameters/CommandKeycodeSummaryParameter.java
new file mode 100644
index 0000000..069c517
--- /dev/null
+++ b/Bindings/Java/parameters/CommandKeycodeSummaryParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class CommandKeycodeSummaryParameter extends GlobalParameter {
+  public CommandKeycodeSummaryParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMMAND_KEYCODE_SUMMARY;
+  }
+
+  @Override
+  public final String get (long code) {
+    return asString(getValue(code));
+  }
+}
diff --git a/Bindings/Java/parameters/ComputerBrailleCellSizeParameter.java b/Bindings/Java/parameters/ComputerBrailleCellSizeParameter.java
new file mode 100644
index 0000000..3dd1c05
--- /dev/null
+++ b/Bindings/Java/parameters/ComputerBrailleCellSizeParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ComputerBrailleCellSizeParameter extends GlobalParameter implements Parameter.ByteSettable {
+  public ComputerBrailleCellSizeParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMPUTER_BRAILLE_CELL_SIZE;
+  }
+
+  @Override
+  public final Byte get () {
+    return asByte(getValue());
+  }
+
+  @Override
+  public final void set (byte size) {
+    setValue(new byte[] {size});
+  }
+}
diff --git a/Bindings/Java/parameters/ComputerBrailleRowCellsParameter.java b/Bindings/Java/parameters/ComputerBrailleRowCellsParameter.java
new file mode 100644
index 0000000..d0564b3
--- /dev/null
+++ b/Bindings/Java/parameters/ComputerBrailleRowCellsParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ComputerBrailleRowCellsParameter extends GlobalParameter {
+  public ComputerBrailleRowCellsParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMPUTER_BRAILLE_ROW_CELLS;
+  }
+
+  @Override
+  public final RowCells get (long row) {
+    return asRowCells(getValue(row));
+  }
+}
diff --git a/Bindings/Java/parameters/ComputerBrailleRowsMaskParameter.java b/Bindings/Java/parameters/ComputerBrailleRowsMaskParameter.java
new file mode 100644
index 0000000..30d8cc0
--- /dev/null
+++ b/Bindings/Java/parameters/ComputerBrailleRowsMaskParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ComputerBrailleRowsMaskParameter extends GlobalParameter {
+  public ComputerBrailleRowsMaskParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMPUTER_BRAILLE_ROWS_MASK;
+  }
+
+  @Override
+  public boolean isHidable () {
+    return true;
+  }
+
+  @Override
+  public final BitMask get () {
+    return asBitMask(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/ComputerBrailleTableParameter.java b/Bindings/Java/parameters/ComputerBrailleTableParameter.java
new file mode 100644
index 0000000..3f18e14
--- /dev/null
+++ b/Bindings/Java/parameters/ComputerBrailleTableParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ComputerBrailleTableParameter extends GlobalParameter implements Parameter.StringSettable {
+  public ComputerBrailleTableParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_COMPUTER_BRAILLE_TABLE;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+
+  @Override
+  public final void set (String name) {
+    setValue(name);
+  }
+}
diff --git a/Bindings/Java/parameters/CursorBlinkPercentageParameter.java b/Bindings/Java/parameters/CursorBlinkPercentageParameter.java
new file mode 100644
index 0000000..54371d2
--- /dev/null
+++ b/Bindings/Java/parameters/CursorBlinkPercentageParameter.java
@@ -0,0 +1,48 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class CursorBlinkPercentageParameter extends GlobalParameter implements Parameter.ByteSettable {
+  public CursorBlinkPercentageParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_CURSOR_BLINK_PERCENTAGE;
+  }
+
+  @Override
+  public final Byte get () {
+    return asByte(getValue());
+  }
+
+  @Override
+  public final void set (byte percentage) {
+    setValue(new byte[] {percentage});
+  }
+
+  @Override
+  public final byte getMaximum () {
+    return 100;
+  }
+}
diff --git a/Bindings/Java/parameters/CursorBlinkPeriodParameter.java b/Bindings/Java/parameters/CursorBlinkPeriodParameter.java
new file mode 100644
index 0000000..a4d6ac4
--- /dev/null
+++ b/Bindings/Java/parameters/CursorBlinkPeriodParameter.java
@@ -0,0 +1,53 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class CursorBlinkPeriodParameter extends GlobalParameter implements Parameter.IntSettable {
+  public CursorBlinkPeriodParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_CURSOR_BLINK_PERIOD;
+  }
+
+  @Override
+  public final Integer get () {
+    return asInt(getValue());
+  }
+
+  @Override
+  public final void set (int period) {
+    setValue(new int[] {period});
+  }
+
+  @Override
+  public final int getMinimum () {
+    return 100;
+  }
+
+  @Override
+  public final int getMaximum () {
+    return 2500;
+  }
+}
diff --git a/Bindings/Java/parameters/CursorDotsParameter.java b/Bindings/Java/parameters/CursorDotsParameter.java
new file mode 100644
index 0000000..dad5016
--- /dev/null
+++ b/Bindings/Java/parameters/CursorDotsParameter.java
@@ -0,0 +1,52 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class CursorDotsParameter extends GlobalParameter implements Parameter.StringSettable {
+  public CursorDotsParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_CURSOR_DOTS;
+  }
+
+  @Override
+  public final Byte get () {
+    return asByte(getValue());
+  }
+
+  public final void set (byte dots) {
+    setValue(new byte[] {dots});
+  }
+
+  @Override
+  public final void set (String numbers) throws SyntaxException {
+    set(Parse.asDots(getParseDescription(), numbers));
+  }
+
+  @Override
+  public String toString () {
+    return asDots(get());
+  }
+}
diff --git a/Bindings/Java/parameters/DefinedDriverKeycodesParameter.java b/Bindings/Java/parameters/DefinedDriverKeycodesParameter.java
new file mode 100644
index 0000000..1f4b4c9
--- /dev/null
+++ b/Bindings/Java/parameters/DefinedDriverKeycodesParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DefinedDriverKeycodesParameter extends GlobalParameter {
+  public DefinedDriverKeycodesParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEFINED_DRIVER_KEYCODES;
+  }
+
+  @Override
+  public boolean isHidable () {
+    return true;
+  }
+
+  @Override
+  public final long[] get () {
+    return asLongArray(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DeviceCellSizeParameter.java b/Bindings/Java/parameters/DeviceCellSizeParameter.java
new file mode 100644
index 0000000..3589096
--- /dev/null
+++ b/Bindings/Java/parameters/DeviceCellSizeParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DeviceCellSizeParameter extends GlobalParameter {
+  public DeviceCellSizeParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEVICE_CELL_SIZE;
+  }
+
+  @Override
+  public final Byte get () {
+    return asByte(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DeviceIdentifierParameter.java b/Bindings/Java/parameters/DeviceIdentifierParameter.java
new file mode 100644
index 0000000..377061b
--- /dev/null
+++ b/Bindings/Java/parameters/DeviceIdentifierParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DeviceIdentifierParameter extends GlobalParameter {
+  public DeviceIdentifierParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEVICE_IDENTIFIER;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DeviceModelParameter.java b/Bindings/Java/parameters/DeviceModelParameter.java
new file mode 100644
index 0000000..99c148a
--- /dev/null
+++ b/Bindings/Java/parameters/DeviceModelParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DeviceModelParameter extends GlobalParameter {
+  public DeviceModelParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEVICE_MODEL;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DeviceOnlineParameter.java b/Bindings/Java/parameters/DeviceOnlineParameter.java
new file mode 100644
index 0000000..44a1d94
--- /dev/null
+++ b/Bindings/Java/parameters/DeviceOnlineParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DeviceOnlineParameter extends GlobalParameter {
+  public DeviceOnlineParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEVICE_ONLINE;
+  }
+
+  @Override
+  public final Boolean get () {
+    return asBoolean(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DeviceSpeedParameter.java b/Bindings/Java/parameters/DeviceSpeedParameter.java
new file mode 100644
index 0000000..8cfa094
--- /dev/null
+++ b/Bindings/Java/parameters/DeviceSpeedParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DeviceSpeedParameter extends GlobalParameter {
+  public DeviceSpeedParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DEVICE_SPEED;
+  }
+
+  @Override
+  public final Integer get () {
+    return asInt(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DisplaySizeParameter.java b/Bindings/Java/parameters/DisplaySizeParameter.java
new file mode 100644
index 0000000..4da5243
--- /dev/null
+++ b/Bindings/Java/parameters/DisplaySizeParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DisplaySizeParameter extends GlobalParameter {
+  public DisplaySizeParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DISPLAY_SIZE;
+  }
+
+  @Override
+  public final DisplaySize get () {
+    return asDisplaySize(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DriverCodeParameter.java b/Bindings/Java/parameters/DriverCodeParameter.java
new file mode 100644
index 0000000..f6b333d
--- /dev/null
+++ b/Bindings/Java/parameters/DriverCodeParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DriverCodeParameter extends GlobalParameter {
+  public DriverCodeParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DRIVER_CODE;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DriverKeycodeNameParameter.java b/Bindings/Java/parameters/DriverKeycodeNameParameter.java
new file mode 100644
index 0000000..796a2de
--- /dev/null
+++ b/Bindings/Java/parameters/DriverKeycodeNameParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DriverKeycodeNameParameter extends GlobalParameter {
+  public DriverKeycodeNameParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DRIVER_KEYCODE_NAME;
+  }
+
+  @Override
+  public final String get (long code) {
+    return asString(getValue(code));
+  }
+}
diff --git a/Bindings/Java/parameters/DriverKeycodeSummaryParameter.java b/Bindings/Java/parameters/DriverKeycodeSummaryParameter.java
new file mode 100644
index 0000000..ca8cd9f
--- /dev/null
+++ b/Bindings/Java/parameters/DriverKeycodeSummaryParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DriverKeycodeSummaryParameter extends GlobalParameter {
+  public DriverKeycodeSummaryParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DRIVER_KEYCODE_SUMMARY;
+  }
+
+  @Override
+  public final String get (long code) {
+    return asString(getValue(code));
+  }
+}
diff --git a/Bindings/Java/parameters/DriverNameParameter.java b/Bindings/Java/parameters/DriverNameParameter.java
new file mode 100644
index 0000000..2998f3c
--- /dev/null
+++ b/Bindings/Java/parameters/DriverNameParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DriverNameParameter extends GlobalParameter {
+  public DriverNameParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DRIVER_NAME;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/DriverVersionParameter.java b/Bindings/Java/parameters/DriverVersionParameter.java
new file mode 100644
index 0000000..37f5fa9
--- /dev/null
+++ b/Bindings/Java/parameters/DriverVersionParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class DriverVersionParameter extends GlobalParameter {
+  public DriverVersionParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_DRIVER_VERSION;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/LiteraryBrailleParameter.java b/Bindings/Java/parameters/LiteraryBrailleParameter.java
new file mode 100644
index 0000000..3bf8377
--- /dev/null
+++ b/Bindings/Java/parameters/LiteraryBrailleParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class LiteraryBrailleParameter extends GlobalParameter implements Parameter.BooleanSettable {
+  public LiteraryBrailleParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_LITERARY_BRAILLE;
+  }
+
+  @Override
+  public final Boolean get () {
+    return asBoolean(getValue());
+  }
+
+  @Override
+  public final void set (boolean yes) {
+    setValue(new boolean[] {yes});
+  }
+}
diff --git a/Bindings/Java/parameters/LiteraryBrailleTableParameter.java b/Bindings/Java/parameters/LiteraryBrailleTableParameter.java
new file mode 100644
index 0000000..2c1b58e
--- /dev/null
+++ b/Bindings/Java/parameters/LiteraryBrailleTableParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class LiteraryBrailleTableParameter extends GlobalParameter implements Parameter.StringSettable {
+  public LiteraryBrailleTableParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_LITERARY_BRAILLE_TABLE;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+
+  @Override
+  public final void set (String name) {
+    setValue(name);
+  }
+}
diff --git a/Bindings/Java/parameters/MessageLocaleParameter.java b/Bindings/Java/parameters/MessageLocaleParameter.java
new file mode 100644
index 0000000..879b79f
--- /dev/null
+++ b/Bindings/Java/parameters/MessageLocaleParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class MessageLocaleParameter extends GlobalParameter implements Parameter.StringSettable {
+  public MessageLocaleParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_MESSAGE_LOCALE;
+  }
+
+  @Override
+  public final String get () {
+    return asString(getValue());
+  }
+
+  @Override
+  public final void set (String locale) {
+    setValue(locale);
+  }
+}
diff --git a/Bindings/Java/parameters/RenderedCellsParameter.java b/Bindings/Java/parameters/RenderedCellsParameter.java
new file mode 100644
index 0000000..2477675
--- /dev/null
+++ b/Bindings/Java/parameters/RenderedCellsParameter.java
@@ -0,0 +1,48 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class RenderedCellsParameter extends LocalParameter {
+  public RenderedCellsParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_RENDERED_CELLS;
+  }
+
+  @Override
+  public boolean isHidable () {
+    return true;
+  }
+
+  @Override
+  public final byte[] get () {
+    return asByteArray(getValue());
+  }
+
+  @Override
+  public String toString () {
+    return asDots(get());
+  }
+}
diff --git a/Bindings/Java/parameters/RetainDotsParameter.java b/Bindings/Java/parameters/RetainDotsParameter.java
new file mode 100644
index 0000000..e44caa7
--- /dev/null
+++ b/Bindings/Java/parameters/RetainDotsParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class RetainDotsParameter extends LocalParameter implements Parameter.BooleanSettable {
+  public RetainDotsParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_RETAIN_DOTS;
+  }
+
+  @Override
+  public final Boolean get () {
+    return asBoolean(getValue());
+  }
+
+  @Override
+  public final void set (boolean yes) {
+    setValue(new boolean[] {yes});
+  }
+}
diff --git a/Bindings/Java/parameters/ServerVersionParameter.java b/Bindings/Java/parameters/ServerVersionParameter.java
new file mode 100644
index 0000000..304b479
--- /dev/null
+++ b/Bindings/Java/parameters/ServerVersionParameter.java
@@ -0,0 +1,38 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class ServerVersionParameter extends GlobalParameter {
+  public ServerVersionParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_SERVER_VERSION;
+  }
+
+  @Override
+  public final Integer get () {
+    return asInt(getValue());
+  }
+}
diff --git a/Bindings/Java/parameters/SkipIdenticalLinesParameter.java b/Bindings/Java/parameters/SkipIdenticalLinesParameter.java
new file mode 100644
index 0000000..5b2801e
--- /dev/null
+++ b/Bindings/Java/parameters/SkipIdenticalLinesParameter.java
@@ -0,0 +1,43 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.parameters;
+
+import org.a11y.brlapi.*;
+
+public class SkipIdenticalLinesParameter extends GlobalParameter implements Parameter.BooleanSettable {
+  public SkipIdenticalLinesParameter (ConnectionBase connection) {
+    super(connection);
+  }
+
+  @Override
+  public final int getParameter () {
+    return Constants.PARAM_SKIP_IDENTICAL_LINES;
+  }
+
+  @Override
+  public final Boolean get () {
+    return asBoolean(getValue());
+  }
+
+  @Override
+  public final void set (boolean yes) {
+    setValue(new boolean[] {yes});
+  }
+}
diff --git a/Bindings/Java/programs/VersionProgram.java b/Bindings/Java/programs/VersionProgram.java
new file mode 100644
index 0000000..812a0a4
--- /dev/null
+++ b/Bindings/Java/programs/VersionProgram.java
@@ -0,0 +1,35 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+package org.a11y.brlapi.programs;
+
+import org.a11y.brlapi.*;
+
+public class VersionProgram extends Program {
+  public VersionProgram (String... arguments) {
+    super(arguments);
+  }
+
+  @Override
+  protected final void runProgram () {
+    printf(
+      "%d.%d.%d\n", APIVersion.MAJOR, APIVersion.MINOR, APIVersion.REVISION
+    );
+  }
+}
diff --git a/Bindings/Lisp/Makefile.in b/Bindings/Lisp/Makefile.in
new file mode 100644
index 0000000..21b1b7f
--- /dev/null
+++ b/Bindings/Lisp/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+all:
+
+install: all
+
+uninstall:
+
+distclean::
+	-rm -f brlapi.asd
+	-rm -f brlapi_config.lisp
+
diff --git a/Bindings/Lisp/apisetup.lisp b/Bindings/Lisp/apisetup.lisp
new file mode 100644
index 0000000..bcaa3ea
--- /dev/null
+++ b/Bindings/Lisp/apisetup.lisp
@@ -0,0 +1,38 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; libbrlapi - A library providing access to braille terminals for applications.
+;
+; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+;
+; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;
+; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;
+; Web Page: http://brltty.app/
+;
+; This software is maintained by Dave Mielke <dave@mielke.cc>.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(require 'asdf)
+(require 'sb-posix)
+
+(defun current-directory ()
+  "The current working directory as a directory path."
+  (concatenate 'string (sb-posix:getcwd) "/")
+)
+
+(defun add-package-directory (path)
+  "Add a directory from which packages can be loaded."
+  (pushnew path asdf:*central-registry* :test #'equal)
+)
+
+(defun load-package (name)
+  "Load a package."
+  (asdf:operate 'asdf:load-op name)
+)
+
+(add-package-directory (current-directory))
+(load-package 'brlapi)
+
diff --git a/Bindings/Lisp/apitest b/Bindings/Lisp/apitest
new file mode 100755
index 0000000..b5a0243
--- /dev/null
+++ b/Bindings/Lisp/apitest
@@ -0,0 +1,21 @@
+#!/bin/sh
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec sbcl --script "${programDirectory}/apitest.lisp" "${@}"
+exit "${?}"
diff --git a/Bindings/Lisp/apitest.lisp b/Bindings/Lisp/apitest.lisp
new file mode 100644
index 0000000..4fe587d
--- /dev/null
+++ b/Bindings/Lisp/apitest.lisp
@@ -0,0 +1,90 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; libbrlapi - A library providing access to braille terminals for applications.
+;
+; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+;
+; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;
+; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;
+; Web Page: http://brltty.app/
+;
+; This software is maintained by Dave Mielke <dave@mielke.cc>.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(load "apisetup.lisp")
+
+(defun brlapi-test-tty (output session &key tty timeout)
+  "Perform a test of BrlAPI's input functions."
+
+  (setf tty (if tty  (parse-integer tty) -1))
+  (setf timeout (if timeout  (parse-integer timeout) 10))
+
+  (brlapi:enter-tty-mode session tty)
+  (brlapi:write-text session (format nil "press keys (timeout is ~D seconds)" timeout))
+
+  (loop
+    with code
+    with text
+    while (setf code (brlapi:read-key-with-timeout session timeout))
+    do (setf text
+         (apply #'format nil
+           "code=0X~X type=~A cmd=~A arg=~D flg=~{~A~^,~}"
+           (list* code (brlapi:describe-key-code code))))
+       (format output "Key: ~A~%" text)
+       (brlapi:write-text session text)
+  )
+  (brlapi:leave-tty-mode session)
+)
+
+(defun brlapi-test (&key host auth tty timeout)
+  "Perform a test of various BrlAPI functiins."
+
+  (let (
+      (output *standard-output*)
+      (session (brlapi:open-connection :auth auth :host host))
+    )
+
+    (loop
+      for (name value format) in (brlapi:property-list session) by #'cdr
+      do (format output "~A: ~@?~%" name format value)
+    )
+
+    (if (brlapi:is-connected session)
+      (progn
+        (if (or tty timeout)
+          (brlapi-test-tty output session :tty tty :timeout timeout))
+      )
+      (format output "connection failure: ~A~%" (brlapi:error-message))
+    )
+
+    (brlapi:close-connection session)
+  )
+)
+
+(defun program-arguments ()
+  "The command-line arguments."
+
+  sb-ext:*posix-argv*
+)
+
+(defun string->keyword (string)
+  "Convert a string to a keyword."
+
+  (values (intern string "KEYWORD"))
+)
+
+(defun arguments->keyword-options (arguments)
+  "Convert a list of argument strings into a list of keyword options."
+
+  (loop
+    for (option value) on arguments by #'cddr
+    append (list (string->keyword (string-upcase option)) value)
+  )
+)
+
+(apply #'brlapi-test (arguments->keyword-options (cdr (program-arguments))))
+(exit)
diff --git a/Bindings/Lisp/bindings.m4 b/Bindings/Lisp/bindings.m4
new file mode 100644
index 0000000..a8cc255
--- /dev/null
+++ b/Bindings/Lisp/bindings.m4
@@ -0,0 +1,21 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_LISP_BINDINGS], [dnl
+LISP_OK=true
+AC_SUBST([LISP_OK])
+])
diff --git a/Bindings/Lisp/brlapi.asd.in b/Bindings/Lisp/brlapi.asd.in
new file mode 100644
index 0000000..b24fe47
--- /dev/null
+++ b/Bindings/Lisp/brlapi.asd.in
@@ -0,0 +1,33 @@
+;;;; @configure_input@
+
+;;;; libbrlapi - A library providing access to braille terminals for applications.
+;;;;
+;;;; Copyright (C) 2006-2023 by Mario Lang <mlang@blind.guru>
+;;;;
+;;;; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;;;;
+;;;; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;;;;
+;;;; Web Page: http://brltty.app/
+;;;;
+;;;; This software is maintained by Dave Mielke <dave@mielke.cc>.
+
+;;;; * BrlAPI System Definition
+
+(asdf:defsystem :brlapi
+  :name "brlapi"
+  :version "@api_release@"
+  :description "BrlAPI client bindings for Common Lisp."
+  :homepage "@PACKAGE_URL@"
+  :mailto "@PACKAGE_BUGREPORT@"
+  :author "Mario Lang <MLang@blind.guru>"
+  :depends-on (:cffi)
+  :serial t
+  :components ((:file "brlapi_prologue")
+               (:file "brlapi_config" :depends-on ("brlapi_prologue"))
+               (:file "brlapi" :depends-on ("brlapi_config"))))
+
+;;;;@include "brlapi.lisp"
diff --git a/Bindings/Lisp/brlapi.lisp b/Bindings/Lisp/brlapi.lisp
new file mode 100644
index 0000000..b22006a
--- /dev/null
+++ b/Bindings/Lisp/brlapi.lisp
@@ -0,0 +1,325 @@
+;;;; libbrlapi - A library providing access to braille terminals for applications.
+;;;;
+;;;; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+;;;;
+;;;; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;;;;
+;;;; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;;;;
+;;;; Web Page: http://brltty.app/
+;;;;
+;;;; This software is maintained by Dave Mielke <dave@mielke.cc>.
+
+(eval-when (:compile-toplevel)
+  (declaim (optimize (safety 3) (debug 3))))
+
+(in-package :brlapi)
+
+;;;; * The DISPLAY class
+
+(defclass display ()
+  ((handle :initarg :handle :reader connection-handle)
+   (fd :initarg :fd :reader file-descriptor)
+   (auth :initarg :auth :reader server-auth)
+   (host :initarg :host :reader server-host)
+   (tty :initform nil :reader entered-tty)))
+
+(defmethod property-list ((obj display))
+  "Return various properties as a lsit of three-element lists.
+The sublist for each returned property contains, in order, its name, its value, and its format string."
+  (list*
+    (list "version" (multiple-value-list (library-version)) "~{~D~^.~}")
+    (if (is-connected obj)
+      (multiple-value-bind (width height) (display-size obj)
+        (list
+          (list "host" (server-host obj) "~A")
+          (list "auth" (server-auth obj) "~A")
+          (list "fd" (file-descriptor obj) "~D")
+          (list "driver" (driver-name obj) "~A")
+          (list "model" (model-identifier obj) "~A")
+          (list "width" width "~D")
+          (list "height" height "~D")
+        )
+      )
+    )
+  )
+)
+
+(defmethod print-object ((obj display) stream)
+  (print-unreadable-object (obj stream :type t)
+    (format stream "~{~A~^ ~}"
+      (loop
+        for (name value format) in (property-list obj) by #'cdr
+        append (list (format nil "~A=~@?" name format value))
+      )
+    )
+  )
+)
+
+
+;;;; * Error handling
+
+(defctype brlapi-code :int)
+(define-condition brlapi-error (error)
+  ((text :initarg :text :reader brlapi-error-text))
+  (:report (lambda (c stream)
+             (format stream "libbrlapi function returned ~A"
+                     (brlapi-error-text c))))
+  (:documentation "Signalled when a libbrlapi function answers with -1."))
+
+(defmethod error-message ()
+  "The message for the most recent error."
+  (foreign-funcall "brlapi_strerror"
+    :pointer (foreign-funcall "brlapi_error_location" :pointer)
+    :string)
+)
+
+(defmethod translate-from-foreign (value (name (eql 'brlapi-code)))
+  "Raise a BRLAPI-ERROR if VALUE, a brlapi-code, is -1."
+  (declare (integer value))
+  (if (eql value -1)
+      (error 'brlapi-error :text (error-message))
+      value))
+
+
+;;;; * Connection management
+
+(defmethod is-connected ((obj display))
+  (and
+    obj
+    (let
+      ((fd (file-descriptor obj)))
+      (and fd (numberp fd) (not (< fd 0)))
+    )
+  )
+)
+
+(defcstruct settings
+  "Connection setting structure."
+  (auth :string)
+  (host :string))
+
+(defun open-connection (&key auth host)
+  "Open a new connection to BRLTTY on HOST usng AUTH for authorization.
+Return a DISPLAY object which can further be used to interact with BRLTTY."
+  (with-foreign-object (settings 'settings)
+    (setf (foreign-slot-value settings 'settings 'auth)
+          (if (stringp auth) auth (null-pointer))
+          (foreign-slot-value settings 'settings 'host)
+          (if (stringp host) host (null-pointer)))
+    (let* ((handle (foreign-alloc :char :count (foreign-funcall "brlapi_getHandleSize" :int)))
+           (fd (foreign-funcall "brlapi__openConnection"
+                                :pointer handle
+                                :pointer settings
+                                :pointer settings
+                                brlapi-code))
+           (display (make-instance 'display :handle handle :fd fd
+                                   :auth (foreign-slot-value settings 'settings 'auth)
+                                   :host (foreign-slot-value settings 'settings 'host))))
+      #+sbcl (sb-ext:finalize display (lambda ()
+                                        (foreign-funcall "brlapi__closeConnection" :pointer handle :void)
+                                        (foreign-free handle)))
+      display)))
+
+(defmethod close-connection ((obj display))
+  (foreign-funcall "brlapi__closeConnection" :pointer (connection-handle obj) :void)
+  (setf (slot-value obj 'fd) nil)
+  (foreign-free (connection-handle obj))
+  (setf (slot-value obj 'handle) nil))
+
+;;;; * Querying the display
+
+(defmethod library-version ()
+  "Return the version of the API as multiple values (major, minor, revision)."
+  (with-foreign-objects ((major :int) (minor :int) (revision :int))
+    (foreign-funcall "brlapi_getLibraryVersion"
+      :pointer major :pointer minor :pointer revision
+      :void
+    )
+    (values (mem-ref major :int) (mem-ref minor :int) (mem-ref revision :int))
+  )
+)
+
+(defmethod driver-name ((obj display))
+  "Return the currently used driver name."
+  (with-foreign-pointer-as-string ((str str-size) 64)
+    (foreign-funcall "brlapi__getDriverName" :pointer (connection-handle obj)
+                     :string str :int str-size brlapi-code)))
+
+(defmethod model-identifier ((obj display))
+  "Return the currently used display model."
+  (with-foreign-pointer-as-string ((str str-size) 64)
+    (foreign-funcall "brlapi__getModelIdentifier" :pointer (connection-handle obj)
+                     :string str :int str-size brlapi-code)))
+
+(defmethod display-size ((obj display))
+  "Return the dimensions of DISPLAY as multiple values.
+The first value represents the x dimension and the second the y dimension."
+  (with-foreign-objects ((x :int) (y :int))
+    (foreign-funcall "brlapi__getDisplaySize" :pointer (connection-handle obj) :pointer x :pointer y brlapi-code)
+    (values (mem-ref x :int) (mem-ref y :int))))
+
+;;;; * TTY mode
+
+(defmethod enter-tty-mode ((obj display) tty &optional (driver ""))
+  (declare (integer tty))
+  (declare (string driver))
+  (setf (slot-value obj 'tty) (foreign-funcall "brlapi__enterTtyMode" :pointer (connection-handle obj) :int tty :string driver brlapi-code)))
+
+(defmethod leave-tty-mode ((obj display))
+  (foreign-funcall "brlapi__leaveTtyMode" :pointer (connection-handle obj) brlapi-code)
+  (setf (slot-value obj 'tty) nil))
+
+
+;;;; * Output
+
+(defmethod write-text ((obj display) text &key (cursor -1))
+  "Write TEXT (a string) to the braille display."
+  (declare (string text))
+  (declare (integer cursor))
+  (if (eql (foreign-funcall "brlapi__writeText"
+                            :pointer (connection-handle obj)
+                            :int cursor :string text
+                            brlapi-code)
+           0)
+      text))
+
+(defbitfield (dots :uint8)
+  (:dot1 #x01) :dot2 :dot3 :dot4 :dot5 :dot6 :dot7 :dot8)
+(defmethod write-dots ((obj display) &rest dots-list)
+  "Write the given dots list to the display."
+  (with-foreign-object (dots 'dots (display-size obj))
+    (loop for i below (min (display-size obj) (length dots-list))
+          do (setf (mem-aref dots 'dots i)
+                   (foreign-bitfield-value 'dots (nth i dots-list))))
+    (loop for i from (length dots-list) below (display-size obj)
+          do (setf (mem-aref dots 'dots i) 0))
+    (foreign-funcall "brlapi__writeDots" :pointer (connection-handle obj) :pointer dots brlapi-code)))
+
+(defcstruct write-struct
+  (display-number :int)
+  (region-begin :int)
+  (region-size :int)
+  (text :pointer)
+  (attr-and :pointer)
+  (attr-or :pointer)
+  (cursor :int)
+  (charset :string))
+
+(defmethod write-region ((obj display) text &key (begin 1) size (cursor -1) (display-number -1)
+                         (charset "") attr-and attr-or)
+  "Update a specific region of the braille display and apply and/or masks."
+  (let ((size (or size (min (display-size obj)
+                            (max (length text)
+                                 (length attr-and)
+                                 (length attr-or))))))    
+    (with-foreign-objects ((ws 'write-struct)
+                           (txt :string (1+ size))
+                           (attra 'dots size)
+                           (attro 'dots size))
+      (loop for i below size
+            do (setf (mem-aref txt :uint8 i) (char-code #\SPACE)))
+      (setf (mem-aref txt :uint8 size) 0)
+      (loop for i below (min size (length text))
+            do (setf (mem-aref txt :uint8 i) (char-code (aref text i))))
+      (loop for i below size
+            do (setf (mem-aref attra :uint8 i) #XFF (mem-aref attro :uint8 i) 0))
+      (loop for i below (min size (length attr-and))
+            do (setf (mem-aref attra :uint8 i) (foreign-bitfield-value 'dots (nth i attr-and))))
+      (loop for i below (min size (length attr-or))
+            do (setf (mem-aref attro :uint8 i) (foreign-bitfield-value 'dots (nth i attr-or))))
+      (setf (foreign-slot-value ws 'write-struct 'display-number) display-number
+            (foreign-slot-value ws 'write-struct 'cursor) cursor
+            (foreign-slot-value ws 'write-struct 'region-begin) begin
+            (foreign-slot-value ws 'write-struct 'region-size) size
+            (foreign-slot-value ws 'write-struct 'charset) charset
+            (foreign-slot-value ws 'write-struct 'text) txt
+            (foreign-slot-value ws 'write-struct 'attr-or) attro
+            (foreign-slot-value ws 'write-struct 'attr-and) attra)
+      (eql (foreign-funcall "brlapi__write" :pointer (connection-handle obj) :pointer ws brlapi-code) 0))))
+
+
+;;;; * Input
+
+(defctype key-code :uint64)
+(defmethod read-key ((obj display) &optional block)
+  (with-foreign-object (key 'key-code)
+    (case (foreign-funcall "brlapi__readKey" :pointer (connection-handle obj) :boolean block :pointer key brlapi-code)
+      (0 nil)
+      (1 (mem-ref key 'key-code)))))
+
+(defmethod read-key-with-timeout ((obj display) &optional (timeout -1))
+  (with-foreign-object (key 'key-code)
+    (case (foreign-funcall "brlapi__readKeyWithTimeout" :pointer (connection-handle obj) :int (* timeout 1000) :pointer key brlapi-code)
+      (0 nil)
+      (1 (mem-ref key 'key-code)))))
+
+(defcstruct expanded-key-code
+  "A key code broken down into its individual fields."
+  (type :int)
+  (command :int)
+  (argument :int)
+  (flags :int)
+)
+
+(defcstruct key-code-description
+  "The description of a key code."
+  (type-name :string)
+  (command-name :string)
+  (argument-value :int)
+  (flag-count :int)
+  (flag-names :string :count 32)
+  (field-values expanded-key-code)
+)
+
+(defun expand-key-code (code)
+  "Return the individual fields of a key code as a list of numeric values.
+The list contains, in order, the type, command, argument, and flags."
+
+  (with-foreign-object (expansion 'expanded-key-code)
+    (foreign-funcall "brlapi_expandKeyCode" key-code code :pointer expansion :int)
+    (with-foreign-slots
+      ((type command argument flags) expansion expanded-key-code)
+      (list type command argument flags)
+    )
+  )
+)
+
+(defun describe-key-code (code)
+  "Return the individual fields of the key code as a list.
+The list contains:
+  0: type (a string)
+  1: command (a string)
+  2: argument (a non-negative integer)
+  3: flags (a list of strings)"
+
+  (with-foreign-object (description 'key-code-description)
+    (foreign-funcall "brlapi_describeKeyCode" key-code code :pointer description :int)
+    (with-foreign-slots
+      ((type-name command-name argument-value flag-count flag-names) description key-code-description)
+
+      (list 
+        type-name command-name argument-value
+        (loop
+          for index below flag-count
+          collect (mem-aref (foreign-slot-pointer description 'key-code-description 'flag-names) :string index))
+      )
+    )
+  )
+)
+
+;;;; * Example usage
+
+(defun example (&optional (tty -1))
+  "A basic example."
+  (let ((display (open-connection)))
+    (enter-tty-mode display tty)
+    (write-text display "Press any key to continue...")
+    (apply #'format t "; Command: ~A, argument: ~D, flags: ~D"
+           (multiple-value-list (expand-key-code (read-key display t))))
+    (leave-tty-mode display)
+    (close-connection display)))
+
diff --git a/Bindings/Lisp/brlapi_config.lisp.in b/Bindings/Lisp/brlapi_config.lisp.in
new file mode 100644
index 0000000..9e9e568
--- /dev/null
+++ b/Bindings/Lisp/brlapi_config.lisp.in
@@ -0,0 +1,29 @@
+;;;; @configure_input@
+
+;;;; libbrlapi - A library providing access to braille terminals for applications.
+;;;;
+;;;; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+;;;;
+;;;; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;;;;
+;;;; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;;;;
+;;;; Web Page: http://brltty.app/
+;;;;
+;;;; This software is maintained by Dave Mielke <dave@mielke.cc>.
+
+(eval-when (:compile-toplevel)
+  (declaim (optimize (safety 3) (debug 3))))
+
+(in-package :brlapi)
+
+;;;; * C BrlAPI Library loading
+
+(define-foreign-library libbrlapi
+  (:unix (:or "libbrlapi.@library_extension@.@api_release@" "libbrlapi.@library_extension@.@api_version@"))
+  (t (:default "libbrlapi")))
+(use-foreign-library libbrlapi)
+
diff --git a/Bindings/Lisp/brlapi_prologue.lisp b/Bindings/Lisp/brlapi_prologue.lisp
new file mode 100644
index 0000000..f99322f
--- /dev/null
+++ b/Bindings/Lisp/brlapi_prologue.lisp
@@ -0,0 +1,34 @@
+;;;; libbrlapi - A library providing access to braille terminals for applications.
+;;;;
+;;;; Copyright (C) 2006-2023 by Mario Lang <mlang@delysid.org>
+;;;;
+;;;; libbrlapi comes with ABSOLUTELY NO WARRANTY.
+;;;;
+;;;; This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+;;;;
+;;;; Web Page: http://brltty.app/
+;;;;
+;;;; This software is maintained by Dave Mielke <dave@mielke.cc>.
+
+(eval-when (:compile-toplevel)
+  (declaim (optimize (safety 3) (debug 3))))
+
+;;;; * Package definition
+
+(defpackage :brlapi
+  (:use :common-lisp :cffi)
+  (:export #:open-connection #:close-connection #:is-connected
+           #:property-list #:print-object
+           #:library-version
+	   #:driver-name #:model-identifier #:display-size
+           #:enter-tty-mode #:leave-tty-mode
+           #:write-text #:write-dots #:write-region
+           #:read-key read-key-with-timeout
+           #:expand-key-code #:describe-key-code
+           #:error-message))
+
+(in-package :brlapi)
+
diff --git a/Bindings/Lua/Makefile.in b/Bindings/Lua/Makefile.in
new file mode 100644
index 0000000..3346e53
--- /dev/null
+++ b/Bindings/Lua/Makefile.in
@@ -0,0 +1,51 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+LUA_OK = @LUA_OK@
+LUA = @LUA@
+
+LUA_INCLUDES = @lua_includes@
+LUA_API_DIRECTORY = $(INSTALL_ROOT)@LUA_LIBRARY_DIRECTORY@
+
+LUA_API_NAME = $(API_NAME)
+LUA_API_OBJECT = bindings.$O
+LUA_API_LIBRARY = $(LUA_API_NAME).$(LIB_EXT)
+LUA_COMMANDS_HEADER=cmd.auto.h
+
+all: $(LUA_API_LIBRARY)
+
+$(LUA_API_LIBRARY): $(LUA_API_OBJECT)
+	$(MKLIB:<name>=$(LUA_API_LIBRARY).$(API_VERSION)) $@ $(LUA_API_OBJECT) $(API_LDFLAGS)
+
+$(LUA_API_OBJECT): $(LUA_COMMANDS_HEADER) | $(API_NAME)
+	$(CC) $(LIBCFLAGS) $(LUA_INCLUDES) -o $@ -c $(SRC_DIR)/bindings.c
+
+$(LUA_COMMANDS_HEADER): $(CONSTANTS_DEPENDENCIES)
+	$(AWK) >$@ $(CONSTANTS_ARGUMENTS)
+
+clean::
+	-rm -f -- $(LUA_API_LIBRARY)
+
+install: all
+	$(INSTALL_DIRECTORY) $(LUA_API_DIRECTORY)
+	$(INSTALL_PROGRAM) $(LUA_API_LIBRARY) $(LUA_API_DIRECTORY)
+
+uninstall:
+	-rm -f $(LUA_API_DIRECTORY)/$(LUA_API_LIBRARY)
+
diff --git a/Bindings/Lua/apitest b/Bindings/Lua/apitest
new file mode 100755
index 0000000..c79e0a1
--- /dev/null
+++ b/Bindings/Lua/apitest
@@ -0,0 +1,22 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+export LUA_PATH="${programDirectory}/../../?.lua;;"
+exec lua "${programDirectory}/${programName}.lua" -- "${@}"
+exit "${?}"
diff --git a/Bindings/Lua/apitest.lua b/Bindings/Lua/apitest.lua
new file mode 100644
index 0000000..107ee69
--- /dev/null
+++ b/Bindings/Lua/apitest.lua
@@ -0,0 +1,82 @@
+--[[
+  libbrlapi - A library providing access to braille terminals for applications.
+ 
+  Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ 
+  libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ 
+  This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ 
+  Web Page: http://brltty.app/
+ 
+  This software is maintained by Dave Mielke <dave@mielke.cc>.
+]]
+
+local brlapi = require("brlapi")
+require("../../brltty-prologue")
+
+local function showProperty (name, value)
+  io.stdout:write(string.format("%s: %s\n", name, tostring(value)))
+end
+
+local function performTests ()
+  showProperty("BrlAPI Version", brlapi.version)
+  if brlapi.version < {major=0, minor=8, revision=3} then
+    error "BrlAPI is too old"
+  end
+
+  local brl <close> = brlapi.openConnection(arg[2], arg[3])
+
+  showProperty("File Descriptor", brl:getFileDescriptor())
+  showProperty("Driver Name", brl:getDriverName())
+  showProperty("Driver Code", brl:getDriverCode())
+  showProperty("Driver Version", brl:getDriverVersion())
+  showProperty("Model Identifier", brl:getModelIdentifier())
+
+  do
+    local columns, rows = brl:getDisplaySize()
+    showProperty("Display Size", string.format("%dx%d", columns, rows))
+  end
+
+  showProperty("Device Online", brl:getDeviceOnline())
+  showProperty("Audible Alerts", brl:getAudibleAlerts())
+  showProperty("Computer Braille Table", brl:getComputerBrailleTable())
+  showProperty("Literary Braille Table", brl:getLiteraryBrailleTable())
+  showProperty("Message Locale", brl:getMessageLocale())
+
+  do
+    local commandKeycodes = brl:getBoundCommandKeycodes();
+    local string = ""
+    for i = 1, #commandKeycodes, 1 do
+      string = string .. brl:getCommandKeycodeName(commandKeycodes[i])
+      if i < #commandKeycodes then
+        string = string .. ", "
+      end
+    end
+    showProperty("Bound Command Keycode Names", string)
+  end
+
+  do
+    local driverKeycodes = brl:getDefinedDriverKeycodes();
+    local string = ""
+    for i = 1, #driverKeycodes, 1 do
+      string = string .. brl:getDriverKeycodeName(driverKeycodes[i])
+      if i < #driverKeycodes then string = string .. ", " end
+    end
+    showProperty("Defined Driver Keycode Names", string)
+  end
+end
+
+do
+  local ok, error = pcall(performTests)
+
+  if not ok then
+    writeProgramMessage(error)
+    os.exit(9)
+  end
+end
+
+os.exit()
diff --git a/Bindings/Lua/bindings.c b/Bindings/Lua/bindings.c
new file mode 100644
index 0000000..c294db6
--- /dev/null
+++ b/Bindings/Lua/bindings.c
@@ -0,0 +1,660 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define BRLAPI_NO_SINGLE_SESSION 1
+#include "prologue.h"
+#include <brlapi.h>
+#include <brl_cmds.h>
+#include <errno.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+static const char *handle_t = "brlapi";
+#define checkhandle(L, arg) (brlapi_handle_t *)luaL_checkudata(L, (arg), handle_t)
+
+static void error(lua_State *L) {
+  lua_pushstring(L, brlapi_strerror(&brlapi_error));
+  lua_error(L);
+}
+
+static int openConnection(lua_State *L) {
+  const brlapi_connectionSettings_t desiredSettings = {
+    .host = luaL_optstring(L, 1, NULL), .auth = luaL_optstring(L, 2, NULL)
+  };
+  brlapi_connectionSettings_t actualSettings;
+  brlapi_handle_t *const handle = (brlapi_handle_t *)lua_newuserdatauv(
+    L, brlapi_getHandleSize(), 0
+  );
+  luaL_setmetatable(L, handle_t);
+
+  if (brlapi__openConnection(handle, &desiredSettings, &actualSettings) == -1)
+    error(L);
+
+  lua_pushstring(L, actualSettings.host);
+  lua_pushstring(L, actualSettings.auth);    
+
+  return 3;
+}
+
+static int getFileDescriptor(lua_State *L) {
+  const int fileDescriptor = brlapi__getFileDescriptor(checkhandle(L, 1));
+
+  if (fileDescriptor == BRLAPI_INVALID_FILE_DESCRIPTOR) error(L);
+
+  lua_pushinteger(L, fileDescriptor);
+
+  return 1;
+}
+
+static int closeConnection(lua_State *L) {
+  brlapi__closeConnection(checkhandle(L, 1));
+
+  return 0;
+}
+
+static int getDriverName(lua_State *L) {
+  char name[BRLAPI_MAXNAMELENGTH + 1];
+
+  if (brlapi__getDriverName(checkhandle(L, 1), name, sizeof(name)) == -1)
+    error(L);
+
+  lua_pushstring(L, name);
+
+  return 1;
+}
+
+static int getModelIdentifier(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  char ident[BRLAPI_MAXNAMELENGTH + 1];
+
+  if (brlapi__getModelIdentifier(handle, ident, sizeof(ident)) == -1) error(L);
+
+  lua_pushstring(L, ident);
+
+  return 1;
+}
+
+static int getDisplaySize(lua_State *L) {
+  unsigned int x, y;
+
+  if (brlapi__getDisplaySize(checkhandle(L, 1), &x, &y) == -1) error(L);
+
+  lua_pushinteger(L, x), lua_pushinteger(L, y);
+
+  return 2;
+}
+
+static int enterTtyMode(lua_State *L) {
+  const int result = brlapi__enterTtyMode(checkhandle(L, 1),
+    luaL_optinteger(L, 2, BRLAPI_TTY_DEFAULT), luaL_optstring(L, 3, NULL)
+  );
+
+  if (result == -1) error(L);
+
+  lua_pushinteger(L, result);
+
+  return 1;
+}
+
+static int leaveTtyMode(lua_State *L) {
+  if (brlapi__leaveTtyMode(checkhandle(L, 1)) == -1) error(L);
+
+  return 0;
+}
+
+static int setFocus(lua_State *L) {
+  if (brlapi__setFocus(checkhandle(L, 1), luaL_checkinteger(L, 2)) == -1)
+    error(L);
+
+  return 0;
+}
+
+static int writeText(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const int cursor = luaL_checkinteger(L, 2);
+  const char *const text = luaL_checkstring(L, 3);
+
+  if (brlapi__writeText(handle, cursor, text) == -1) error(L);
+
+  return 0;
+}
+
+static int writeDots(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const unsigned char *const dots = (const unsigned char *)luaL_checkstring(L, 2);
+
+  if (brlapi__writeDots(handle, dots) == -1) error(L);
+
+  return 0;
+}
+
+static int readKey(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const int wait = lua_toboolean(L, 2);
+  brlapi_keyCode_t keyCode;
+  int result;
+
+  do {
+    result = brlapi__readKey(handle, wait, &keyCode);
+  } while (result == -1 &&
+           brlapi_errno == BRLAPI_ERROR_LIBCERR && brlapi_libcerrno == EINTR);
+
+  switch (result) {
+  case -1:
+    error(L);
+    break; /* never reached */
+  case 0:
+    break;
+  case 1:
+    lua_pushinteger(L, (lua_Integer)keyCode);
+   
+    return 1;
+  }
+
+  return 0;
+}
+
+static int readKeyWithTimeout(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const int timeout_ms = luaL_checkinteger(L, 2);
+  brlapi_keyCode_t keyCode;
+  int result;
+
+  do {
+    result = brlapi__readKeyWithTimeout(handle, timeout_ms, &keyCode);
+  } while (result == -1 &&
+           brlapi_errno == BRLAPI_ERROR_LIBCERR && brlapi_libcerrno == EINTR);
+
+  switch (result) {
+  case -1:
+    error(L);
+    break; /* never reached */
+  case 0:
+    break;
+  case 1:
+    lua_pushinteger(L, (lua_Integer)keyCode);
+ 
+    return 1;
+  }
+
+  return 0;
+}
+
+static int expandKeyCode(lua_State *L) {
+  brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 1);
+  brlapi_expandedKeyCode_t expansion;
+
+  brlapi_expandKeyCode(keyCode, &expansion);
+  lua_pushinteger(L, expansion.type),
+  lua_pushinteger(L, expansion.command),
+  lua_pushinteger(L, expansion.argument),
+  lua_pushinteger(L, expansion.flags);
+
+  return 4;
+}
+
+static int describeKeyCode(lua_State *L) {
+  brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 1);
+  brlapi_describedKeyCode_t description;
+
+  brlapi_describeKeyCode(keyCode, &description);
+
+  lua_pushstring(L, description.type),
+  lua_pushstring(L, description.command),
+  lua_pushinteger(L, description.argument);
+  lua_createtable(L, description.flags, 0);
+  for (size_t i = 0; i < description.flags; i += 1) {
+    lua_pushstring(L, description.flag[i]);
+    lua_rawseti(L, -2, i + 1);
+  }
+
+  return 4;
+}
+
+static int enterRawMode(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const char *const driverName = luaL_checkstring(L, 2);
+
+  if (brlapi__enterRawMode(handle, driverName) == -1) error(L);
+
+  return 0;
+}
+
+static int leaveRawMode(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+
+  if (brlapi__leaveRawMode(handle) == -1) error(L);
+
+  return 0;
+}
+
+static int changeKeys(
+  lua_State *L,
+  int BRLAPI_STDCALL (*change) (brlapi_handle_t *, brlapi_rangeType_t, const brlapi_keyCode_t[], unsigned int)
+) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  static const char *rangeNames[] = {
+    "all", "code", "command", "key", "type", NULL
+  };
+  static const brlapi_rangeType_t rangeTypes[] = {
+    brlapi_rangeType_all,
+    brlapi_rangeType_code,
+    brlapi_rangeType_command,
+    brlapi_rangeType_key,
+    brlapi_rangeType_type
+  };
+  const brlapi_rangeType_t range = rangeTypes[
+    luaL_checkoption(L, 2, NULL, rangeNames)
+  ];
+
+  if (range == brlapi_rangeType_all) {
+    if (change(handle, range, NULL, 0) == -1) error(L);
+
+    return 0;
+  }
+
+  luaL_checktype(L, 3, LUA_TTABLE);
+  lua_len(L, 3);
+
+  {
+    const size_t count = lua_tointeger(L, -1);
+    brlapi_keyCode_t keys[count];
+    lua_pop(L, 1);
+
+    for (size_t i = 0; i < count; i += 1) {
+      lua_geti(L, 3, i + 1);
+      keys[i] = (brlapi_keyCode_t)luaL_checkinteger(L, -1);
+      lua_pop(L, 1);
+    }
+
+    if (change(handle, range, keys, count) == -1) error(L);
+  }
+ 
+  return 0;
+}
+
+static int acceptKeys(lua_State *L) {
+  return changeKeys(L, brlapi__acceptKeys);
+}
+
+static int ignoreKeys(lua_State *L) {
+  return changeKeys(L, brlapi__ignoreKeys);
+}
+
+static int acceptAllKeys(lua_State *L) {
+  if (brlapi__acceptAllKeys(checkhandle(L, 1)) == -1) error(L);
+
+  return 0;
+}
+
+static int ignoreAllKeys(lua_State *L) {
+  if (brlapi__ignoreAllKeys(checkhandle(L, 1)) == -1) error(L);
+
+  return 0;
+}
+
+static int suspendDriver(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const char *const driverName = luaL_checkstring(L, 2);
+
+  if (brlapi__suspendDriver(handle, driverName) == -1) error(L);
+
+  return 0;
+}
+
+static int resumeDriver(lua_State *L) {
+  if (brlapi__resumeDriver(checkhandle(L, 1)) == -1) error(L);
+
+  return 0;
+}
+
+static int pause_(lua_State *L) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const int timeout_ms = luaL_checkinteger(L, 2);
+  int result;
+
+  do {
+    result = brlapi__pause(handle, timeout_ms);
+  } while (timeout_ms == -1 && result == -1 &&
+           brlapi_errno == BRLAPI_ERROR_LIBCERR && brlapi_libcerrno == EINTR);
+
+  if (result == -1) error(L);
+
+  return 0;
+}
+
+static int getBooleanParameter(lua_State *L, brlapi_param_t param) {
+  brlapi_param_bool_t value;
+  const int result = brlapi__getParameter(checkhandle(L, 1),
+    param, 0, BRLAPI_PARAMF_GLOBAL, &value, sizeof(value)
+  );
+
+  if (result == -1) error(L);
+
+  lua_pushboolean(L, value);
+
+  return 1;
+}
+
+static int setBooleanParameter(lua_State *L, brlapi_param_t param) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  const brlapi_param_flags_t flags = BRLAPI_PARAMF_GLOBAL;
+  const brlapi_param_bool_t value = lua_toboolean(L, 2);
+
+  if (brlapi__setParameter(handle, param, 0, flags, &value, sizeof(value)) == -1)
+    error(L);
+
+  return 0;
+}
+
+static int getStringParameter(
+  lua_State *L,
+  brlapi_param_t param, brlapi_param_subparam_t subparam
+) {
+  size_t count;
+  char *string = brlapi__getParameterAlloc(checkhandle(L, 1),
+    param, subparam, BRLAPI_PARAMF_GLOBAL, &count
+  );
+
+  if (string == NULL) error(L);
+
+  lua_pushlstring(L, string, count);
+  free(string);
+
+  return 1;
+}
+
+static int setStringParameter(
+  lua_State *L,
+  brlapi_param_t param, brlapi_param_subparam_t subparam
+) {
+  brlapi_handle_t *const handle = checkhandle(L, 1);
+  size_t length;
+  const brlapi_param_flags_t flags = BRLAPI_PARAMF_GLOBAL;
+  const char *string = luaL_checklstring(L, 2, &length);
+
+  if (brlapi__setParameter(handle, param, subparam, flags, string, length) == -1)
+    error(L);
+
+  return 0;
+}
+
+static int getDriverCode(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_DRIVER_CODE, 0);
+}
+
+static int getDriverVersion(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_DRIVER_VERSION, 0);
+}
+
+static int getDeviceOnline(lua_State *L) {
+  return getBooleanParameter(L, BRLAPI_PARAM_DEVICE_ONLINE);
+}
+
+static int getAudibleAlerts(lua_State *L) {
+  return getBooleanParameter(L, BRLAPI_PARAM_AUDIBLE_ALERTS);
+}
+
+static int setAudibleAlerts(lua_State *L) {
+  return setBooleanParameter(L, BRLAPI_PARAM_AUDIBLE_ALERTS);
+}
+
+static int getClipboardContent(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_CLIPBOARD_CONTENT, 0);
+}
+
+static int setClipboardContent(lua_State *L) {
+  return setStringParameter(L, BRLAPI_PARAM_CLIPBOARD_CONTENT, 0);
+}
+
+static int getComputerBrailleTable(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE, 0);
+}
+
+static int setComputerBrailleTable(lua_State *L) {
+  return setStringParameter(L, BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE, 0);
+}
+
+static int getLiteraryBrailleTable(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_LITERARY_BRAILLE_TABLE, 0);
+}
+
+static int setLiteraryBrailleTable(lua_State *L) {
+  return setStringParameter(L, BRLAPI_PARAM_LITERARY_BRAILLE_TABLE, 0);
+}
+
+static int getMessageLocale(lua_State *L) {
+  return getStringParameter(L, BRLAPI_PARAM_MESSAGE_LOCALE, 0);
+}
+
+static int setMessageLocale(lua_State *L) {
+  return setStringParameter(L, BRLAPI_PARAM_MESSAGE_LOCALE, 0);
+}
+
+static int getBoundCommandKeycodes(lua_State *L) {
+  size_t count;
+  brlapi_keyCode_t *codes = brlapi__getParameterAlloc(checkhandle(L, 1),
+    BRLAPI_PARAM_BOUND_COMMAND_KEYCODES, 0, BRLAPI_PARAMF_GLOBAL, &count
+  );
+
+  if (codes == NULL) error(L);
+
+  count /= sizeof(brlapi_keyCode_t);
+
+  lua_newtable(L);
+  for (size_t i = 0; i < count; i += 1) {
+    lua_pushinteger(L, (lua_Integer)codes[i]);
+    lua_rawseti(L, -2, i + 1);
+  }
+
+  free(codes);
+
+  return 1;
+}
+
+static int getCommandKeycodeName(lua_State *L) {
+  const brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 2);
+  return getStringParameter(L, BRLAPI_PARAM_COMMAND_KEYCODE_NAME, keyCode);
+}
+
+static int getCommandKeycodeSummary(lua_State *L) {
+  const brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 2);
+  return getStringParameter(L, BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY, keyCode);
+}
+
+static int getDefinedDriverKeycodes(lua_State *L) {
+  size_t count;
+  brlapi_keyCode_t *codes = brlapi__getParameterAlloc(checkhandle(L, 1),
+    BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES, 0, BRLAPI_PARAMF_GLOBAL, &count
+  );
+
+  if (codes == NULL) error(L);
+
+  count /= sizeof(brlapi_keyCode_t);
+
+  lua_newtable(L);
+  for (size_t i = 0; i < count; i += 1) {
+    lua_pushinteger(L, (lua_Integer)codes[i]);
+    lua_rawseti(L, -2, i + 1);
+  }
+
+  free(codes);
+
+  return 1;
+}
+
+static int getDriverKeycodeName(lua_State *L) {
+  const brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 2);
+  return getStringParameter(L, BRLAPI_PARAM_DRIVER_KEYCODE_NAME, keyCode);
+}
+
+static int getDriverKeycodeSummary(lua_State *L) {
+  const brlapi_keyCode_t keyCode = (brlapi_keyCode_t)luaL_checkinteger(L, 2);
+  return getStringParameter(L, BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY, keyCode);
+}
+
+static inline int versionGetField(lua_State *L, const char *name) {
+  if (lua_getfield(L, 1, name) == LUA_TNUMBER) {
+    if (lua_getfield(L, 2, name) == LUA_TNUMBER) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int versionCompare(lua_State *L, int op) {
+  int result = 0;
+
+  if (versionGetField(L, "major")) {
+    if (lua_compare(L, -2, -1, LUA_OPLT)) {
+      result = 1;
+    } else if (lua_compare(L, -2, -1, LUA_OPEQ)) {
+      if (versionGetField(L, "minor")) {
+	if (lua_compare(L, -2, -1, LUA_OPLT)) {
+	  result = 1;
+	} else if (lua_compare(L, -2, -1, LUA_OPEQ)) {
+	  if (versionGetField(L, "revision")) {
+	    if (lua_compare(L, -2, -1, op)) {
+	      result = 1;
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  lua_pushboolean(L, result);
+  return 1;
+}
+
+static int versionLT(lua_State *L) {
+  return versionCompare(L, LUA_OPLT);
+}
+
+static int versionLE(lua_State *L) {
+  return versionCompare(L, LUA_OPLE);
+}
+
+static int versionString(lua_State *L) {
+  static const char *separator = ".";
+
+  lua_getfield(L, 1, "major");
+  lua_pushstring(L, separator);
+  lua_getfield(L, 1, "minor");
+  lua_pushstring(L, separator);
+  lua_getfield(L, 1, "revision");
+
+  lua_concat(L, 5);
+
+  return 1;
+}
+
+static const luaL_Reg version_meta[] = {
+  { "__lt", versionLT },
+  { "__le", versionLE },
+  { "__tostring", versionString },
+  { NULL, NULL }
+};
+
+static const luaL_Reg meta[] = {
+  { "__close", closeConnection },
+  { NULL, NULL }
+};
+
+static const luaL_Reg funcs[] = {
+  { "openConnection", openConnection },
+  { "getFileDescriptor", getFileDescriptor },
+  { "closeConnection", closeConnection },
+  { "getDriverName", getDriverName },
+  { "getModelIdentifier", getModelIdentifier },
+  { "getDisplaySize", getDisplaySize },
+  { "enterTtyMode", enterTtyMode },
+  { "leaveTtyMode", leaveTtyMode },
+  { "setFocus", setFocus },
+  { "writeText", writeText },
+  { "writeDots", writeDots },
+  { "readKey", readKey },
+  { "readKeyWithTimeout", readKeyWithTimeout },
+  { "expandKeyCode", expandKeyCode },
+  { "describeKeyCode", describeKeyCode },
+  { "enterRawMode", enterRawMode },
+  { "leaveRawMode", leaveRawMode },
+  { "acceptKeys", acceptKeys },
+  { "ignoreKeys", ignoreKeys },
+  { "acceptAllKeys", acceptAllKeys },
+  { "ignoreAllKeys", ignoreAllKeys },
+  { "suspendDriver", suspendDriver },
+  { "resumeDriver", resumeDriver },
+  { "pause", pause_ },
+  { "getDriverCode", getDriverCode },
+  { "getDriverVersion", getDriverVersion },
+  { "getDeviceOnline", getDeviceOnline },
+  { "getAudibleAlerts", getAudibleAlerts },
+  { "setAudibleAlerts", setAudibleAlerts },
+  { "getClipboardContent", getClipboardContent },
+  { "setClipboardContent", setClipboardContent },
+  { "getComputerBrailleTable", getComputerBrailleTable },
+  { "setComputerBrailleTable", setComputerBrailleTable },
+  { "getLiteraryBrailleTable", getLiteraryBrailleTable },
+  { "setLiteraryBrailleTable", setLiteraryBrailleTable },
+  { "getMessageLocale", getMessageLocale },
+  { "setMessageLocale", setMessageLocale },
+  { "getBoundCommandKeycodes", getBoundCommandKeycodes },
+  { "getCommandKeycodeName", getCommandKeycodeName },
+  { "getCommandKeycodeSummary", getCommandKeycodeSummary },
+  { "getDefinedDriverKeycodes", getDefinedDriverKeycodes },
+  { "getDriverKeycodeName", getDriverKeycodeName },
+  { "getDriverKeycodeSummary", getDriverKeycodeSummary },
+  { NULL, NULL }
+};
+
+static void createcmdtable(lua_State *L) {
+  lua_newtable(L);
+#define CMD(name, value) lua_pushinteger(L, (lua_Integer)value), lua_setfield(L, -2, STRINGIFY(name));
+#include "cmd.auto.h"
+#undef CMD
+}
+
+int luaopen_brlapi(lua_State *L) {
+  luaL_newmetatable(L, handle_t);
+  luaL_setfuncs(L, meta, 0);
+  luaL_newlib(L, funcs);
+
+  {
+    int major, minor, revision;
+
+    brlapi_getLibraryVersion(&major, &minor, &revision);
+
+    lua_createtable(L, 0, 3);
+    lua_pushinteger(L, major), lua_setfield(L, -2, "major");
+    lua_pushinteger(L, minor), lua_setfield(L, -2, "minor");
+    lua_pushinteger(L, revision), lua_setfield(L, -2, "revision");
+    luaL_newmetatable(L, "version");
+    luaL_setfuncs(L, version_meta, 0);
+    lua_setmetatable(L, -2);
+    lua_setfield(L, -2, "version");
+  }
+
+  createcmdtable(L);
+  lua_setfield(L, -2, "CMD");
+
+  /* Set function table as metatable __index */
+  lua_pushvalue(L, -1), lua_setfield(L, -3, "__index");
+
+  return 1;
+}
diff --git a/Bindings/Lua/bindings.m4 b/Bindings/Lua/bindings.m4
new file mode 100644
index 0000000..320765a
--- /dev/null
+++ b/Bindings/Lua/bindings.m4
@@ -0,0 +1,51 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_LUA_BINDINGS], [dnl
+LUA_OK=false
+LUA=""
+LUA_LIBRARY_DIRECTORY=""
+
+BRLTTY_HAVE_PACKAGE([lua], [lua5.4 lua], [dnl
+   AC_PATH_PROGS([LUA], [lua5.4 lua])
+
+   if test -n "${LUA}"
+   then
+      AC_MSG_NOTICE([Lua shell: ${LUA}])
+      BRLTTY_LUA_QUERY([LUA_LIBRARY_DIRECTORY], [libdir])
+
+      if test -n "${LUA_LIBRARY_DIRECTORY}"
+      then
+         AC_MSG_NOTICE([Lua library directory: ${LUA_LIBRARY_DIRECTORY}])
+         LUA_OK=true
+      else
+         AC_MSG_WARN([Lua library directory not found])
+      fi
+   else
+      AC_MSG_WARN([Lua shell not found])
+      LUA="LUA_SHELL_NOT_FOUND_BY_CONFIGURE"
+   fi
+])
+
+AC_SUBST([LUA_OK])
+AC_SUBST([LUA])
+AC_SUBST([LUA_LIBRARY_DIRECTORY])
+])
+
+AC_DEFUN([BRLTTY_LUA_QUERY], [dnl
+   $1=`"${srcdir}/Tools/luacmd" $2`
+])
diff --git a/Bindings/Lua/constants.awk b/Bindings/Lua/constants.awk
new file mode 100644
index 0000000..1dbddaa
--- /dev/null
+++ b/Bindings/Lua/constants.awk
@@ -0,0 +1,53 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+function brlCommand(name, symbol, value, help) {
+  print "  CMD(" name ", " symbol ")"
+}
+
+function brlBlock(name, symbol, value, help) {
+}
+
+function brlKey(name, symbol, value, help) {
+}
+
+function brlFlag(name, symbol, value, help) {
+}
+
+function brlDot(number, symbol, value, help) {
+}
+
+function apiConstant(name, symbol, value, help) {
+}
+
+function apiMask(name, symbol, value, help) {
+}
+
+function apiShift(name, symbol, value, help) {
+}
+
+function apiType(name, symbol, value, help) {
+}
+
+function apiKey(name, symbol, value, help) {
+}
+
+function apiRangeType(name, symbol, value, help) {
+}
+
diff --git a/Bindings/OCaml/META b/Bindings/OCaml/META
new file mode 100644
index 0000000..c4c3ac0
--- /dev/null
+++ b/Bindings/OCaml/META
@@ -0,0 +1,5 @@
+requires = "unix"
+description = "Bindings for the libbrlapi braille enabling library"
+archive(byte) = "brlapi.cma"
+archive(native) = "brlapi.cmxa"
+version = "0.1"
diff --git a/Bindings/OCaml/Makefile.in b/Bindings/OCaml/Makefile.in
new file mode 100644
index 0000000..5503fa4
--- /dev/null
+++ b/Bindings/OCaml/Makefile.in
@@ -0,0 +1,120 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+OCAML_OK = @OCAML_OK@
+OCAMLC = @OCAMLC@
+OCAMLOPT = @OCAMLOPT@
+OCAMLMKLIB = @OCAMLMKLIB@
+OCAMLMKLIB_FLAGS = -dllib dll$(API_NAME)_stubs.so -cclib -l$(API_NAME)_stubs -cclib -l$(API_NAME)
+OCAMLMKLIBOPT_FLAGS = -cclib -l$(API_NAME)_stubs -cclib -l$(API_NAME)
+OCAMLBEST = @OCAMLBEST@
+OCAMLVERSION = @OCAMLVERSION@
+OCAMLLIB = @OCAMLLIB@
+OCAMLWIN32 = @OCAMLWIN32@
+OCAMLFIND = @OCAMLFIND@
+OCAML_INSTALL_TARGET = @OCAML_INSTALL_TARGET@
+OCAML_UNINSTALL_TARGET = @OCAML_UNINSTALL_TARGET@
+
+OCAML_LIB = $(API_NAME)
+OCAML_BCLIB = $(OCAML_LIB).cma
+OCAML_NCLIB = @OCAML_NCLIB@
+
+OCAML_MISC = $(SRC_DIR)/META brlapi.mli brlapi.cmi
+
+OCAML_CLIBS = @OCAML_CLIBS@
+
+OCAML_DESTDIR = $(INSTALL_ROOT)$(OCAMLLIB)
+OCAML_PKGDIR = $(OCAML_DESTDIR)/$(OCAML_LIB)
+OCAML_STUBDIR = $(OCAML_DESTDIR)/stublibs
+OCAML_STUB = dll$(OCAML_LIB)_stubs.so
+OCAML_OWNER = $(OCAML_STUB).owner
+
+OCAML_FILES = $(OCAML_MISC) $(OCAML_CLIBS) $(OCAML_BCLIB) $(OCAML_NCLIB)
+
+.PHONY: all clean
+.PHONY: install install-with-findlib install-without-findlib
+.PHONY: uninstall uninstall-with-findlib uninstall-without-findlib
+
+all: $(OCAML_FILES)
+
+$(OCAML_CLIBS): brlapi_stubs.$O | brlapi
+	$(OCAMLMKLIB) -ldopt "$(LDFLAGS)" $(API_LDFLAGS) -oc $(OCAML_LIB)_stubs brlapi_stubs.$O
+
+$(OCAML_BCLIB): $(OCAML_CLIBS) brlapi.cmo
+	$(OCAMLC) -a $(OCAMLMKLIB_FLAGS) -o $(OCAML_LIB).cma brlapi.cmo
+
+$(OCAML_NCLIB): $(OCAML_CLIBS) brlapi.cmx
+	$(OCAMLMKLIB) -ldopt "$(LDFLAGS)" $(OCAMLMKLIBOPT_FLAGS) -o $(OCAML_LIB) brlapi.cmx
+
+brlapi.cmi: brlapi.mli
+	$(OCAMLC) -cc $(CC) -ccopt "$(LIBCFLAGS)" -o $@ -c brlapi.mli
+
+brlapi.cmo: brlapi.ml brlapi.cmi
+	$(OCAMLC) -cc $(CC) -ccopt "$(LIBCFLAGS)" -o $@ -c brlapi.ml
+
+brlapi.cmx: brlapi.ml brlapi.cmi
+	$(OCAMLOPT) -cc $(CC) -ccopt "$(LIBCFLAGS)" -o $@ -c brlapi.cmx brlapi.ml
+
+brlapi_stubs.$O: $(SRC_DIR)/brlapi_stubs.c
+	$(OCAMLC) -cc $(CC) -ccopt "$(LIBCFLAGS)" -I $(BLD_TOP)$(PGM_DIR) -I $(SRC_TOP)$(PGM_DIR) -c $(SRC_DIR)/brlapi_stubs.c
+
+brlapi.mli: $(SRC_DIR)/main.mli constants.mli
+	cat $(SRC_DIR)/main.mli constants.mli > $@
+
+brlapi.ml: $(SRC_DIR)/main.ml constants.ml
+	cat $(SRC_DIR)/main.ml constants.ml > $@
+
+constants.mli: constants.ml
+	$(OCAMLC) -o /dev/null -c -i constants.ml > $@
+
+constants.ml: $(CONSTANTS_DEPENDENCIES)
+	$(AWK) $(CONSTANTS_ARGUMENTS) >$@
+
+clean::
+	-rm -f -- *.cm* *.a *.so constants.ml* brlapi.ml*
+
+install: all $(OCAML_INSTALL_TARGET)
+
+install-without-findlib:
+	$(INSTALL_DIRECTORY) $(OCAML_PKGDIR)
+	for file in $(OCAML_FILES); do $(INSTALL) $$file $(OCAML_PKGDIR); done
+	$(INSTALL_DIRECTORY) $(OCAML_STUBDIR)
+	[ ! -f "$(OCAML_PKGDIR)/$(OCAML_STUB)" ] || mv $(OCAML_PKGDIR)/$(OCAML_STUB) $(OCAML_STUBDIR)
+	echo >$(OCAML_STUBDIR)/$(OCAML_OWNER) $(OCAML_LIB)
+
+OCAML_LDCONF = $(OCAML_DESTDIR)/ld.conf
+install-with-findlib:
+	$(INSTALL_DIRECTORY) $(OCAML_DESTDIR)
+	[ ! -d "$(OCAML_DESTDIR)/$(OCAML_LIB)" ] || \
+	$(OCAMLFIND) remove -destdir "$(OCAML_DESTDIR)" $(OCAML_LIB)
+	set -- -destdir "$(OCAML_DESTDIR)"; \
+	[ -f "$(OCAML_LDCONF)" ] && set -- "$${@}" -ldconf "$(OCAML_LDCONF)" || set -- "$${@}" -ldconf ignore; \
+	$(OCAMLFIND) install "$${@}" $(OCAML_LIB) $(OCAML_FILES) 
+
+uninstall: $(OCAML_UNINSTALL_TARGET)
+
+uninstall-without-findlib:
+	-rm -f -r -- $(OCAML_PKGDIR)
+	-rm -f -- $(OCAML_STUBDIR)/$(OCAML_STUB)
+	-rm -f -- $(OCAML_STUBDIR)/$(OCAML_OWNER)
+
+uninstall-with-findlib:
+	-$(OCAMLFIND) remove -destdir "$(OCAML_DESTDIR)" $(OCAML_LIB)
+
diff --git a/Bindings/OCaml/bindings.m4 b/Bindings/OCaml/bindings.m4
new file mode 100644
index 0000000..b204bec
--- /dev/null
+++ b/Bindings/OCaml/bindings.m4
@@ -0,0 +1,156 @@
+# autoconf input for Objective Caml programs
+# Copyright (C) 2001 Jean-Christophe Filliâtre
+#   from a first script by Georges Mariano 
+# 
+# Modified to be an autoconf m4 function in 2006
+# for BRLTTY [http://brltty.app/]
+# by Dave Mielke <dave@mielke.cc>
+# 
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License version 2, as published by the Free Software Foundation.
+# 
+# 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 Library General Public License version 2 for more details
+# (enclosed in the file LGPL).
+
+# the script generated by autoconf from this input will set the following
+# variables:
+#   OCAMLC        "ocamlc" if present in the path, or a failure
+#                 or "ocamlc.opt" if present with same version number as ocamlc
+#   OCAMLOPT      "ocamlopt" (or "ocamlopt.opt" if present), or "no"
+#   OCAMLMKLIB      "ocamlkmlib", or "no"
+#   OCAMLBEST     either "byte" if no native compiler was found, 
+#                 or "opt" otherwise
+#   OCAMLDEP      "ocamldep"
+#   OCAMLLEX      "ocamllex" (or "ocamllex.opt" if present)
+#   OCAMLYACC     "ocamlyac"
+#   OCAMLLIB      the path to the ocaml standard library
+#   OCAMLVERSION  the ocaml version number
+#   OCAMLWEB      "ocamlweb" (not mandatory)
+#   OCAMLWIN32    "yes"/"no" depending on Sys.os_type = "Win32"
+
+AC_DEFUN([BRLTTY_OCAML_BINDINGS], [dnl
+# Check for Ocaml compilers
+
+# we first look for ocamlc in the path; if not present, we fail
+AC_CHECK_PROG(OCAMLC,ocamlc,ocamlc,no)
+if test "$OCAMLC" = no ; then
+    AC_MSG_WARN([Cannot find ocamlc.])
+    OCAML_OK=false
+else
+    # checking for ocamlmklib
+    AC_CHECK_PROG(OCAMLMKLIB,ocamlmklib,ocamlmklib,no)
+    if test "$OCAMLMKLIB" = no ; then
+        AC_MSG_WARN([Cannot find ocamlmklib.])
+        OCAML_OK=false
+    else    
+        OCAML_OK=true
+        # we extract Ocaml version number and library path
+        OCAMLVERSION=`$OCAMLC -version`
+        AC_MSG_NOTICE([OCaml version is $OCAMLVERSION])
+
+        OCAMLLIB=`$OCAMLC -where`
+        AC_MSG_NOTICE([OCaml library path is $OCAMLLIB])
+
+        # then we look for ocamlopt; if not present, we issue a warning
+        # if the version is not the same, we also discard it
+        # we set OCAMLBEST to "opt" or "byte", whether ocamlopt is available or not
+        AC_CHECK_PROG(OCAMLOPT,ocamlopt,ocamlopt,no)
+        OCAMLBEST=byte
+        OCAML_NCLIB=
+        if test "$OCAMLOPT" = no ; then
+            AC_MSG_WARN([Cannot find ocamlopt; bytecode compilation only.])
+        else
+            AC_MSG_CHECKING(ocamlopt version)
+            TMPVERSION=`$OCAMLOPT -v | sed -n -e 's|.*version *\(.*\)$|\1|p' `
+            if test "$TMPVERSION" != "$OCAMLVERSION" ; then
+                AC_MSG_RESULT(differs from ocamlc; ocamlopt discarded.)
+                OCAMLOPT=no
+            else
+                AC_MSG_RESULT(ok)
+                OCAMLBEST=opt
+                OCAML_NCLIB="\$(OCAML_LIB).cmxa"
+            fi
+        fi
+
+        # checking for ocamlc.opt
+        AC_CHECK_PROG(OCAMLCDOTOPT,ocamlc.opt,ocamlc.opt,no)
+        if test "$OCAMLCDOTOPT" != no ; then
+            AC_MSG_CHECKING(ocamlc.opt version)
+            TMPVERSION=`$OCAMLCDOTOPT -v | sed -n -e 's|.*version *\(.*\)$|\1|p' `
+            if test "$TMPVERSION" != "$OCAMLVERSION" ; then
+                AC_MSG_RESULT(differs from ocamlc; ocamlc.opt discarded.)
+            else
+                AC_MSG_RESULT(ok)
+                OCAMLC=$OCAMLCDOTOPT
+            fi
+        fi
+
+        # checking for ocamlopt.opt
+        if test "$OCAMLOPT" != no ; then
+            AC_CHECK_PROG(OCAMLOPTDOTOPT,ocamlopt.opt,ocamlopt.opt,no)
+            if test "$OCAMLOPTDOTOPT" != no ; then
+                AC_MSG_CHECKING(ocamlc.opt version)
+                TMPVER=`$OCAMLOPTDOTOPT -v | sed -n -e 's|.*version *\(.*\)$|\1|p' `
+                if test "$TMPVER" != "$OCAMLVERSION" ; then
+                    AC_MSG_RESULT(differs from ocamlc; ocamlopt.opt discarded.)
+                else
+                    AC_MSG_RESULT(ok)
+                    OCAMLOPT=$OCAMLOPTDOTOPT
+                fi
+            fi
+        fi
+
+        # platform
+        AC_MSG_CHECKING(platform)
+        if echo "let _ = Sys.os_type;;" | ocaml | grep -q Win32; then
+            AC_MSG_RESULT(Win32)
+            OCAMLWIN32=yes
+            OCAML_CLIBS=libbrlapi_stubs.a
+        elif echo "let _ = Sys.os_type;;" | ocaml | grep -q Cygwin; then
+            AC_MSG_RESULT(Cygwin)
+            OCAMLWIN32=yes
+            OCAML_CLIBS=libbrlapi_stubs.a
+        else
+            AC_MSG_RESULT(Unix)
+            OCAMLWIN32=no
+            OCAML_CLIBS="libbrlapi_stubs.a dllbrlapi_stubs.so"
+        fi
+    
+        # checking for ocamlfindlib
+        AC_CHECK_PROG(OCAMLFIND,ocamlfind,ocamlfind,no)
+        if test "$OCAMLFIND" = ocamlfind; then
+            OCAMLC='ocamlfind ocamlc'
+            if test "$OCAMLOPT" = ocamlopt; then
+                OCAMLOPT='ocamlfind ocamlopt'
+            fi
+            OCAML_INSTALL_TARGET=install-with-findlib
+            OCAML_UNINSTALL_TARGET=uninstall-without-findlib
+        else
+            OCAML_INSTALL_TARGET=install-without-findlib
+            OCAML_UNINSTALL_TARGET=uninstall-without-findlib
+            AC_MSG_WARN([Cannot find ocamlfind.])
+        fi
+    fi
+fi
+
+# substitutions to perform
+AC_SUBST(OCAMLC)
+AC_SUBST(OCAMLOPT)
+AC_SUBST(OCAMLMKLIB)
+AC_SUBST(OCAMLBEST)
+AC_SUBST(OCAMLVERSION)
+AC_SUBST(OCAMLLIB)
+AC_SUBST(OCAMLWIN32)
+AC_SUBST(OCAML_CLIBS)
+AC_SUBST(OCAML_NCLIB)
+AC_SUBST(OCAMLFIND)
+AC_SUBST(OCAML_INSTALL_TARGET)
+AC_SUBST(OCAML_UNINSTALL_TARGET)
+
+AC_SUBST([OCAML_OK])
+])
diff --git a/Bindings/OCaml/brlapi_stubs.c b/Bindings/OCaml/brlapi_stubs.c
new file mode 100644
index 0000000..594b826
--- /dev/null
+++ b/Bindings/OCaml/brlapi_stubs.c
@@ -0,0 +1,510 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2005-2023 by
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ * All rights reserved.
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define CAML_NAME_SPACE /* Don't import old names */
+#include <caml/mlvalues.h> /* definition of the value type, and conversion macros */
+#include <caml/memory.h> /* miscellaneous memory-related functions and macros (for GC interface, in-place modification of structures, etc). */
+#include <caml/alloc.h> /* allocation functions (to create structured Caml objects) */
+#include <caml/fail.h> /* functions for raising exceptions */
+#include <caml/callback.h> /* callback from C to Caml */
+#include <caml/custom.h> /* operations on custom blocks */
+#include <caml/intext.h> /* operations for writing user-defined serialization and deserialization functions for custom blocks */
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+#include "brlapi_protocol.h"
+
+#ifndef MIN
+#define MIN(x, y) (x<y)?(x):(y)
+#endif /* MIN */
+
+extern value unix_error_of_code (int errcode); /* TO BE REMOVED */
+
+/* The following macros call a BrlAPI function */
+/* The first one just calls the function, whereas */
+/* the second one also checks the function's return code and raises */
+/* an exception if this code is -1 */
+/* The macros decide which version of a brlapi function should be called */
+/* depending on whether the handle value is None or Some realHandle */
+
+#define brlapi(function, ...) \
+do { \
+  if (Is_long(handle)) brlapi_ ## function (__VA_ARGS__); \
+  else brlapi__ ## function ((brlapi_handle_t *) Data_custom_val(Field(handle, 0)), ## __VA_ARGS__); \
+} while (0)
+
+#define brlapiCheckError(function, ...) \
+do { \
+  int res_; \
+  if (Is_long(handle)) res_ = brlapi_ ##function (__VA_ARGS__); \
+  else res_ = brlapi__ ##function ((brlapi_handle_t *) Data_custom_val(Field(handle, 0)), ## __VA_ARGS__); \
+  if (res_==-1) raise_brlapi_error(); \
+} while (0)
+
+#define brlapiCheckErrorWithCode(function, ret, ...) \
+do { \
+  int res_; \
+  if (Is_long(handle)) res_ = brlapi_ ##function (__VA_ARGS__); \
+  else res_ = brlapi__ ##function ((brlapi_handle_t *) Data_custom_val(Field(handle, 0)), ## __VA_ARGS__); \
+  if (res_==-1) raise_brlapi_error(); \
+  (*(int *)ret) = res_; \
+} while (0)
+
+static int compareHandle(value h1, value h2)
+{
+  CAMLparam2(h1, h2);
+  CAMLreturn(memcmp(Data_custom_val(h1), Data_custom_val(h2), brlapi_getHandleSize()));
+}
+
+static struct custom_operations customOperations = {
+  .identifier = "BrlAPI handle",
+  .finalize = custom_finalize_default,
+  .compare = compareHandle,
+  .hash = custom_hash_default, /* FIXME: provide a genuine hashing function */
+  .serialize = custom_serialize_default,
+  .deserialize = custom_deserialize_default,
+};
+
+/* Function : constrCamlError */
+/* Converts a brlapi_error_t into its Caml representation */
+static value constrCamlError(const brlapi_error_t *err)
+{
+  value camlError;
+  camlError = caml_alloc_tuple(4);
+  Store_field(camlError, 0, Val_int(err->brlerrno));
+  Store_field(camlError, 1, Val_int(err->libcerrno));
+  Store_field(camlError, 2, Val_int(err->gaierrno));
+  if (err->errfun!=NULL)
+    Store_field(camlError, 3, caml_copy_string(err->errfun));
+  else
+    Store_field(camlError, 3, caml_copy_string(""));
+  return camlError;
+}
+
+CAMLprim value brlapiml_errorCode_of_error(value camlError)
+{
+  CAMLparam1(camlError);
+  CAMLlocal1(result);
+  switch (Int_val(Field(camlError, 0))) {
+    case BRLAPI_ERROR_NOMEM: result = Val_int(0); break;
+    case BRLAPI_ERROR_TTYBUSY: result = Val_int(1); break;
+    case BRLAPI_ERROR_DEVICEBUSY: result = Val_int(2); break;
+    case BRLAPI_ERROR_UNKNOWN_INSTRUCTION: result = Val_int(3); break;
+    case BRLAPI_ERROR_ILLEGAL_INSTRUCTION: result = Val_int(4); break;
+    case BRLAPI_ERROR_INVALID_PARAMETER: result = Val_int(5); break;
+    case BRLAPI_ERROR_INVALID_PACKET: result = Val_int(6); break;
+    case BRLAPI_ERROR_CONNREFUSED: result = Val_int(7); break;
+    case BRLAPI_ERROR_OPNOTSUPP: result = Val_int(8); break;
+    case BRLAPI_ERROR_GAIERR: {
+      result = caml_alloc(1, 0);
+      Store_field(result, 0, Val_int(Field(camlError, 2)));
+    }; break;
+    case BRLAPI_ERROR_LIBCERR: {
+      result = caml_alloc(1, 1);
+      Store_field(result, 0, unix_error_of_code(Int_val(Field(camlError, 1))));
+    }; break;
+    case BRLAPI_ERROR_UNKNOWNTTY: result = Val_int(9); break;
+    case BRLAPI_ERROR_PROTOCOL_VERSION: result = Val_int(10); break;
+    case BRLAPI_ERROR_EOF: result = Val_int(11); break;
+    case BRLAPI_ERROR_EMPTYKEY: result = Val_int(12); break; 
+    case BRLAPI_ERROR_DRIVERERROR: result = Val_int(13); break;
+    case BRLAPI_ERROR_AUTHENTICATION: result = Val_int(14); break;
+    default: {
+      result = caml_alloc(1, 2);
+      Store_field(result, 0, Val_int(Field(camlError, 0)));
+    }
+  }
+  CAMLreturn(result);
+}
+
+/* Function : raise_brlapi_error */
+/* Raises the Brlapi_error exception */
+static void raise_brlapi_error(void)
+{
+  static const value *exception = NULL;
+  CAMLparam0();
+  CAMLlocal1(res);
+  if (exception==NULL) exception = caml_named_value("Brlapi_error");
+  res = caml_alloc(2,0);
+  Store_field(res, 0, *exception);
+  Store_field(res, 1, constrCamlError(&brlapi_error));
+  caml_raise(res);
+  CAMLreturn0;
+}
+
+/* Function : raise_brlapi_exception */
+/* Raises Brlapi_exception */
+static void BRLAPI_STDCALL raise_brlapi_exception(int err, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  static const value *exception = NULL;
+  int i;
+  CAMLparam0();
+  CAMLlocal2(str, res);
+  str = caml_alloc_string(size);
+  for (i=0; i<size; i++) Byte(str, i) = ((char *) packet)[i];
+  if (exception==NULL) exception = caml_named_value("Brlapi_exception");
+  res = caml_alloc (4, 0);
+  Store_field(res, 0, *exception);
+  Store_field(res, 1, Val_int(err));
+  Store_field(res, 2, caml_copy_int32(type));
+  Store_field(res, 3, str);
+  caml_raise(res);
+  CAMLreturn0;
+}
+
+/* function packDots */
+/* Converts Caml dots in brltty dots */
+static inline void packDots(value camlDots, unsigned char *dots, int size)
+{
+  unsigned int i;
+  for (i=0; i<size; i++)
+    dots[i] = Int_val(Field(camlDots, i));
+}
+
+CAMLprim value brlapiml_openConnection(value settings)
+{
+  CAMLparam1(settings);
+  CAMLlocal2(s, pair);
+  int res;
+  brlapi_connectionSettings_t brlapiSettings;
+  brlapiSettings.auth = String_val(Field(settings, 0));
+  brlapiSettings.host = String_val(Field(settings, 1));
+  res = brlapi_openConnection(&brlapiSettings, &brlapiSettings);
+  if (res<0) raise_brlapi_error();
+  s = caml_alloc_tuple(2);
+  Store_field(s, 0, caml_copy_string(brlapiSettings.auth));
+  Store_field(s, 1, caml_copy_string(brlapiSettings.host));
+  pair = caml_alloc_tuple(2);
+  Store_field(pair, 0, Val_int(res));
+  Store_field(pair, 1, s);
+  CAMLreturn(pair);
+}
+
+CAMLprim value brlapiml_openConnectionWithHandle(value settings)
+{
+  CAMLparam1(settings);
+  CAMLlocal1(handle);
+  brlapi_connectionSettings_t brlapiSettings;
+  brlapiSettings.auth = String_val(Field(settings, 0));
+  brlapiSettings.host = String_val(Field(settings, 1));
+  handle = caml_alloc_custom(&customOperations, brlapi_getHandleSize(), 0, 1);
+  if (brlapi__openConnection(Data_custom_val(handle), &brlapiSettings, &brlapiSettings)<0) raise_brlapi_error();
+  CAMLreturn(handle);
+}
+
+CAMLprim value brlapiml_closeConnection(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapi(closeConnection);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_getDriverName(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  char name[BRLAPI_MAXNAMELENGTH];
+  brlapiCheckError(getDriverName, name, sizeof(name));
+  CAMLreturn(caml_copy_string(name));
+}
+
+CAMLprim value brlapiml_getModelIdentifier(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  char identifier[BRLAPI_MAXNAMELENGTH];
+  brlapiCheckError(getModelIdentifier, identifier, sizeof(identifier));
+  CAMLreturn(caml_copy_string(identifier));
+}
+
+CAMLprim value brlapiml_getDisplaySize(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  CAMLlocal1(size);
+  unsigned int x, y;
+  brlapiCheckError(getDisplaySize, &x, &y);
+  size = caml_alloc_tuple(2);
+  Store_field(size, 0, Val_int(x));
+  Store_field(size, 1, Val_int(y));
+  CAMLreturn(size);
+}
+
+CAMLprim value brlapiml_enterTtyMode(value handle, value tty, value driverName)
+{
+  CAMLparam3(handle, tty, driverName);
+  int res;
+  brlapiCheckErrorWithCode(enterTtyMode, &res, Int_val(tty), String_val(driverName));
+  CAMLreturn(Val_int(res));
+}
+
+CAMLprim value brlapiml_enterTtyModeWithPath(value handle, value ttyPathCaml, value driverName)
+{
+  CAMLparam3(handle, ttyPathCaml, driverName);
+  int i, size = Wosize_val(ttyPathCaml);
+  int ttyPath[size];
+  for (i=0; i<size; i++) ttyPath[i] = Int_val(Field(ttyPathCaml, i));
+  brlapiCheckError(enterTtyModeWithPath, ttyPath, size, String_val(driverName));
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_leaveTtyMode(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapi(leaveTtyMode);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_setFocus(value handle, value tty)
+{
+  CAMLparam2(handle, tty);
+  brlapiCheckError(setFocus, Int_val(tty));
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_writeText(value handle, value cursor, value text)
+{
+  CAMLparam3(handle, cursor, text);
+  brlapiCheckError(writeText, Int_val(cursor), String_val(text));
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_writeDots(value handle, value camlDots)
+{
+  CAMLparam2(handle, camlDots);
+  int size = Wosize_val(camlDots);
+  unsigned char dots[size];
+  packDots(camlDots, dots, size);
+  brlapiCheckError(writeDots, dots);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_write(value handle, value writeArguments)
+{
+  CAMLparam2(handle, writeArguments);
+  int andSize = Wosize_val(Field(writeArguments, 4));
+  int orSize = Wosize_val(Field(writeArguments, 5));
+  unsigned char andMask[andSize], orMask[orSize];
+  brlapi_writeArguments_t wa;
+  wa.displayNumber = Val_int(Field(writeArguments, 0));
+  wa.regionBegin = Val_int(Field(writeArguments, 1));
+  wa.regionSize = Val_int(Field(writeArguments, 2));
+  wa.text = &Byte(String_val(Field(writeArguments, 3)), 0);
+  packDots(Field(writeArguments, 4), andMask, andSize);
+  wa.andMask = andMask;
+  packDots(Field(writeArguments, 5), orMask, orSize);
+  wa.orMask = orMask;
+  wa.cursor = Val_int(Field(writeArguments, 6));
+  wa.charset = &Byte(String_val(Field(writeArguments, 7)), 0);
+  brlapiCheckError(write, &wa);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_readKey(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  int res;
+  brlapi_keyCode_t keyCode;
+  CAMLlocal1(retVal);
+  brlapiCheckErrorWithCode(readKey, &res, 0, &keyCode);
+  if (res==0) CAMLreturn(Val_int(0));
+  retVal = caml_alloc(1, 1);
+  Store_field(retVal, 0, caml_copy_int64(keyCode));
+  CAMLreturn(retVal);
+}
+
+CAMLprim value brlapiml_readKeyWithTimeout(value handle, value timeout_ms)
+{
+  CAMLparam2(handle, timeout_ms);
+  int res;
+  brlapi_keyCode_t keyCode;
+  CAMLlocal1(retVal);
+  brlapiCheckErrorWithCode(readKeyWithTimeout, &res, Int_val(timeout_ms), &keyCode);
+  if (res==0) CAMLreturn(Val_int(0));
+  retVal = caml_alloc(1, 1);
+  Store_field(retVal, 0, caml_copy_int64(keyCode));
+  CAMLreturn(retVal);
+}
+
+CAMLprim value brlapiml_waitKey(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapi_keyCode_t keyCode;
+  brlapiCheckError(readKey, 1, &keyCode);
+  CAMLreturn(caml_copy_int64(keyCode));
+}
+
+#define brlapi__expandKeyCode(h,x,y) brlapi_expandKeyCode(x,y)
+
+CAMLprim value brlapiml_expandKeyCode(value handle, value camlKeyCode)
+{
+  CAMLparam2(handle, camlKeyCode);
+  CAMLlocal1(result);
+  brlapi_expandedKeyCode_t ekc;
+  brlapiCheckError(expandKeyCode, Int64_val(camlKeyCode), &ekc);
+  result = caml_alloc_tuple(4);
+  Store_field(result, 0, caml_copy_int32(ekc.type));
+  Store_field(result, 1, caml_copy_int32(ekc.command));
+  Store_field(result, 2, caml_copy_int32(ekc.argument));
+  Store_field(result, 2, caml_copy_int32(ekc.flags));
+  CAMLreturn(result);
+}
+
+CAMLprim value brlapiml_ignoreKeys(value handle, value rt, value camlKeys)
+{
+  CAMLparam3(handle, rt, camlKeys);
+  unsigned int i, size = Wosize_val(camlKeys);
+  brlapi_keyCode_t keys[size];
+  for (i=0; i<size; i++) keys[i] = Int64_val(Field(camlKeys, i)); 
+  brlapiCheckError(ignoreKeys, Int_val(rt), keys, size);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_acceptKeys(value handle, value rt, value camlKeys)
+{
+  CAMLparam3(handle, rt, camlKeys);
+  unsigned int i, size = Wosize_val(camlKeys);
+  brlapi_keyCode_t keys[size];
+  for (i=0; i<size; i++) keys[i] = Int64_val(Field(camlKeys, i)); 
+  brlapiCheckError(acceptKeys, Int_val(rt), keys, size);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_ignoreAllKeys(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapiCheckError(ignoreAllKeys);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_acceptAllKeys(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapiCheckError(acceptAllKeys);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_ignoreKeyRanges(value handle, value camlRanges)
+{
+  CAMLparam2(handle, camlRanges);
+  CAMLlocal1(r);
+  unsigned int i, size = Wosize_val(camlRanges);
+  brlapi_range_t ranges[size];
+  for (i=0; i<size; i++) {
+    r = Field(camlRanges, i);
+    ranges[i].first = Int64_val(Field(r, 0));
+    ranges[i].last = Int64_val(Field(r, 1));
+  }
+  brlapiCheckError(ignoreKeyRanges, ranges, size);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_acceptKeyRanges(value handle, value camlRanges)
+{
+  CAMLparam2(handle, camlRanges);
+  CAMLlocal1(r);
+  unsigned int i, size = Wosize_val(camlRanges);
+  brlapi_range_t ranges[size];
+  for (i=0; i<size; i++) {
+    r = Field(camlRanges, i);
+    ranges[i].first = Int64_val(Field(r, 0));
+    ranges[i].last = Int64_val(Field(r, 1));
+  }
+  brlapiCheckError(acceptKeyRanges, ranges, size);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_enterRawMode(value handle, value driverName)
+{
+  CAMLparam2(handle, driverName);
+  brlapiCheckError(enterRawMode, String_val(driverName));
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_leaveRawMode(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapi(leaveRawMode);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_sendRaw(value handle, value str)
+{
+  CAMLparam2(handle, str);
+  int res;
+  unsigned char packet[BRLAPI_MAXPACKETSIZE];
+  ssize_t i, size = MIN(sizeof(packet), caml_string_length(str));
+  for (i=0; i<size; i++) packet[i] = Byte(str, i);
+  brlapiCheckErrorWithCode(sendRaw, &res, packet, size);
+  CAMLreturn(Val_int(res));
+}
+
+CAMLprim value brlapiml_recvRaw(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  unsigned char packet[BRLAPI_MAXPACKETSIZE];
+  int i, size;
+  CAMLlocal1(str);
+  brlapiCheckErrorWithCode(recvRaw, &size, packet, sizeof(packet));
+  str = caml_alloc_string(size);
+  for (i=0; i<size; i++) Byte(str, i) = packet[i];
+  CAMLreturn(str);
+}
+
+CAMLprim value brlapiml_suspendDriver(value handle, value driverName)
+{
+  CAMLparam2(handle, driverName);
+  brlapiCheckError(suspendDriver, String_val(driverName));
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_resumeDriver(value handle, value unit)
+{
+  CAMLparam2(handle, unit);
+  brlapi(resumeDriver);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value brlapiml_strerror(value camlError)
+{
+  CAMLparam1(camlError);
+  brlapi_error_t error;
+  error.brlerrno = Int_val(Field(camlError,0));
+  error.libcerrno = Int_val(Field(camlError,1));
+  error.gaierrno = Int_val(Field(camlError,2));
+  error.errfun = String_val(Field(camlError,3));
+  size_t size = brlapi_strerror_r(&error, NULL, 0);
+  char buf[size+1];
+  brlapi_strerror_r(&error, buf, sizeof(buf));
+  CAMLreturn(caml_copy_string(buf));
+}
+
+/* Function : setExceptionHandler */
+/* Sets a handler that raises a Caml exception each time a brlapi */
+/* exception occurs */
+CAMLprim value brlapiml_setExceptionHandler(value unit)
+{
+  CAMLparam1(unit);
+  brlapi_setExceptionHandler(raise_brlapi_exception);
+  CAMLreturn(Val_unit);
+}
diff --git a/Bindings/OCaml/constants.awk b/Bindings/OCaml/constants.awk
new file mode 100644
index 0000000..f8d2b21
--- /dev/null
+++ b/Bindings/OCaml/constants.awk
@@ -0,0 +1,114 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+}
+
+END {
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeCommandDefinition("cmd_" name, "Int32.of_int " value, help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  writeCommandDefinition("blk_" name, "Int32.of_int (" value " lsl key_cmd_blk_shift)", help)
+}
+
+function brlKey(name, symbol, value, help) {
+# not implemented for other bindings either
+#  print "braille key:"
+#  print name symbol value
+#  print "(** " help " *)"
+}
+
+function brlFlag(name, symbol, value, help) {
+  if (value ~ /^0[xX][0-9a-fA-F]+0000$/) {
+    value = hexadecimalValue(substr(value, 1, length(value)-4))
+    if (name ~ /^CHAR_/) {
+      name = substr(name, 6)
+    } else {
+      value = value "00"
+    }
+  } else if (value ~ /^\(/) {
+    return
+  } else {
+    return
+  }
+  writeCamlConstant("int32", "KEY_FLG_" name, "Int32.of_int " tolower(value), help)
+}
+
+function brlDot(number, symbol, value, help) {
+  writeCamlConstant("int", "dot" number, value, help)
+}
+
+function apiConstant(name, symbol, value, help) {
+  writeCamlConstant("int", name, value, help);
+}
+
+function apiMask(name, symbol, value, help) {
+  writeCamlConstant("int64", "key_" tolower(name), hexadecimalValue(value) "L");
+}
+
+function apiShift(name, symbol, value, help) {
+  writeCamlConstant("int", "key_" tolower(name), hexadecimalValue(value));
+}
+
+function apiType(name, symbol, value, help) {
+  writeCamlConstant("int", "key_type_" tolower(name), hexadecimalValue(value));
+}
+
+function apiKey(name, symbol, value, help) {
+  value = hexadecimalValue(value)
+
+  if (name == "FUNCTION") {
+    for (i=0; i<35; ++i) {
+      writeKeyDefinition("F" (i+1), "(" value " + " i ")")
+    }
+  } else {
+    writeKeyDefinition(name, value)
+  }
+}
+
+function apiRangeType(name, symbol, value, help) {
+  writeCamlConstant("int", "rangeType_" name, value, help)
+}
+
+function writeCommandDefinition(name, value, help) {
+  writeCamlConstant("int32", name, value, help)
+}
+
+function writeKeyDefinition(name, value) {
+  writeCamlConstant("int32", "KEY_SYM_" name, "Int32.of_int " value)
+}
+
+function writeCamlConstant(type, name, value, help) {
+  print "let " tolower(name) " : " type " = " value " " camldocComment(help)
+}
+
+function camldocComment(text) {
+  if (length(text) > 0) text = "(** " text " *)";
+  return text;
+}
+
+function hexadecimalValue(value) {
+  value = tolower(value)
+  gsub("x0+", "x", value)
+  gsub("x$", "x0", value)
+  return value
+}
diff --git a/Bindings/OCaml/main.ml b/Bindings/OCaml/main.ml
new file mode 100644
index 0000000..879bbf1
--- /dev/null
+++ b/Bindings/OCaml/main.ml
@@ -0,0 +1,217 @@
+(*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ * All rights reserved.
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ *)
+
+(* BrlAPI Interface for the OCaml language *)
+
+let dot1 = 1 lsl 0
+let dot2 = 1 lsl 1
+let dot3 = 1 lsl 2
+let dot4 = 1 lsl 3
+let dot5 = 1 lsl 4
+let dot6 = 1 lsl 5
+let dot7 = 1 lsl 6
+let dot8 = 1 lsl 7
+
+type settings = {
+  auth : string;
+  host : string
+}
+
+let settings_initializer = {
+  auth = "";
+  host = ""
+}
+
+type writeArguments = {
+  mutable displayNumber : int;
+  mutable regionBegin : int;
+  mutable regionSize : int;
+  text : string;
+  attrAnd : int array;
+  attrOr : int array;
+  mutable cursor : int;
+  mutable charset : string
+}
+
+let writeArguments_initializer = {
+  displayNumber = -1;
+  regionBegin = 0;
+  regionSize = 0;
+  text = "";
+  attrAnd = [| |];
+  attrOr = [| |];
+  cursor = -1;
+  charset = ""
+}
+
+type handle
+
+let max_packet_size = 512
+
+type errorCode =
+  | SUCCESS
+  | NOMEM
+  | TTYBUSY
+  | DEVICEBUSY
+  | UNKNOWN_INSTRUCTION
+  | ILLEGAL_INSTRUCTION
+  | INVALID_PARAMETER
+  | INVALID_PACKET
+  | CONNREFUSED
+  | OPNOTSUPP
+  | GAIERR of int
+  | LIBCERR of Unix.error
+  | UNKNOWNTTY
+  | PROTOCOL_VERSION
+  | EOF
+  | EMPTYKEY
+  | DRIVERERROR
+  | AUTHENTICATION
+  | Unknown of int
+
+type error = {
+  brlerrno : int;
+  libcerrno : int;
+  gaierrno : int;
+  errfun : string;
+}
+
+external errorCode_of_error :
+  error -> errorCode = "brlapiml_errorCode_of_error"
+
+external strerror :
+  error -> string = "brlapiml_strerror"
+
+exception Brlapi_error of error
+exception Brlapi_exception of errorCode * int32 * string
+
+external openConnection :
+  settings -> Unix.file_descr * settings = "brlapiml_openConnection"
+external openConnectionWithHandle :
+  settings -> handle = "brlapiml_openConnectionWithHandle"
+external closeConnection :
+  ?h:handle -> unit -> unit = "brlapiml_closeConnection"
+external getDriverName :
+  ?h:handle -> unit -> string = "brlapiml_getDriverName"
+external getModelIdentifier :
+  ?h:handle -> unit -> string = "brlapiml_getModelIdentifier"
+external getDisplaySize :
+  ?h:handle -> unit -> int * int = "brlapiml_getDisplaySize"
+
+external enterTtyMode :
+  ?h:handle -> int -> string -> int = "brlapiml_enterTtyMode"
+external enterTtyModeWithPath :
+  ?h:handle -> int array -> string -> int = "brlapiml_enterTtyModeWithPath"
+external leaveTtyMode :
+  ?h:handle -> unit -> unit = "brlapiml_leaveTtyMode"
+external setFocus :
+  ?h:handle -> int -> unit = "brlapiml_setFocus"
+
+external writeText :
+  ?h:handle -> int -> string -> unit = "brlapiml_writeText"
+external writeDots :
+  ?h:handle -> int array -> unit = "brlapiml_writeDots"
+external write :
+  ?h:handle -> writeArguments -> unit = "brlapiml_write"
+
+external readKey :
+  ?h:handle -> unit -> int64 option = "brlapiml_readKey"
+external waitKey :
+  ?h:handle -> unit -> int64 = "brlapiml_waitKey"
+external readKeyWithTimeout :
+  ?h:handle -> int -> int64 option = "brlapiml_readKeyWithTimeout"
+
+type expandedKeyCode = {
+  type_ : int32;
+  command : int32;
+  argument : int32;
+  flags : int32
+}
+
+external expandKeyCode :
+  ?h:handle -> int64 -> expandedKeyCode = "brlapiml_expandKeyCode"
+
+type rangeType =
+  | RT_all
+  | RT_type
+  | RT_command
+  | RT_key
+  | RT_code
+
+external ignoreKeys :
+  ?h:handle -> rangeType -> int64 array -> unit = "brlapiml_ignoreKeys"
+external acceptKeys :
+  ?h:handle -> rangeType -> int64 array -> unit = "brlapiml_acceptKeys"
+external ignoreAllKeys :
+  ?h:handle -> unit = "brlapiml_ignoreAllKeys"
+external acceptAllKeys :
+  ?h:handle -> unit = "brlapiml_acceptAllKeys"  
+external ignoreKeyRanges :
+  ?h:handle -> (int64 * int64) array -> unit = "brlapiml_ignoreKeyRanges"
+external acceptKeyRanges :
+  ?h:handle -> (int64 * int64) array -> unit = "brlapiml_acceptKeyRanges"
+
+external enterRawMode :
+  ?h:handle -> string -> unit = "brlapiml_enterRawMode"
+external leaveRawMode :
+  ?h:handle -> unit -> unit = "brlapiml_leaveRawMode"
+external sendRaw :
+  ?h:handle -> string -> int = "brlapiml_sendRaw"
+external recvRaw :
+  ?h:handle -> unit -> string = "brlapiml_recvRaw"
+  
+external suspendDriver :
+  ?h:handle -> string -> unit = "brlapiml_suspendDriver"
+external resumeDriver :
+  ?h:handle -> unit -> unit = "brlapiml_resumeDriver"
+
+module type KEY = sig
+  type key
+  val key_of_int64 : int64 -> key
+  val int64_of_key : key -> int64
+end
+
+module MakeKeyHandlers (M1 : KEY) = struct
+  type key = M1.key
+  let readKey ?h () = match readKey ?h () with
+    | None -> None
+    | Some x -> Some (M1.key_of_int64 x)
+
+  let waitKey ?h () = M1.key_of_int64 (waitKey ?h ()) 
+  let ignoreKeys ?h t a = ignoreKeys ?h t (Array.map M1.int64_of_key a)
+  let acceptKeys ?h t a = acceptKeys ?h t (Array.map M1.int64_of_key a)
+  let f (x,y) = (M1.int64_of_key x, M1.int64_of_key y)
+  let g a = Array.map f a
+  let ignoreKeyRanges ?h a = ignoreKeyRanges ?h (g a)
+  let acceptKeyRanges ?h a = acceptKeyRanges ?h (g a)
+end
+
+  
+
+external setExceptionHandler :
+  unit -> unit = "brlapiml_setExceptionHandler"
+
+let _ =
+  let x = { brlerrno = 0; libcerrno = 0; gaierrno = 0; errfun = "" } in
+  Callback.register_exception "Brlapi_error"
+    (Brlapi_error x);
+  Callback.register_exception "Brlapi_exception"
+    (Brlapi_exception (SUCCESS, 0l, ""));
+  setExceptionHandler()
diff --git a/Bindings/OCaml/main.mli b/Bindings/OCaml/main.mli
new file mode 100644
index 0000000..feeb05b
--- /dev/null
+++ b/Bindings/OCaml/main.mli
@@ -0,0 +1,226 @@
+(*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ * All rights reserved.
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ *)
+
+(* BrlAPI Interface for the OCaml language *)
+(*
+Ajouter des fichiers .in
+*)
+
+(*
+BRLAPI_RELEASE
+BRLAPI_MAJOR
+BRLAPI_MINOR
+BRLAPI_REVISION
+Faire un fichier .in: @VAR@ est remplacé par la valeur de VAR
+telle que définie dans configure.ac.
+*)
+
+(*
+BRLAPI_SOCKETPORTNUM
+BRLAPI_SOCKETPORT (string)
+BRLAPI_MAXPACKETSIZE
+BRLAPI_MAXNAMELENGTH
+BRLAPI_SOCKETPATH
+BRLAPI_ETCDIR
+BRLAPI_AUTHKEYFILE
+BRLAPI_DEFAUTH
+Idem que précédemmment
+C'est du ni<eau proto. Faut-il binder ?
+*)
+
+type settings = {
+  auth : string;
+  host : string
+}
+
+val settings_initializer : settings
+
+type writeArguments = {
+  mutable displayNumber : int;
+  mutable regionBegin : int;
+  mutable regionSize : int;
+  text : string;
+  attrAnd : int array;
+  attrOr : int array;
+  mutable cursor : int;
+  mutable charset : string
+}
+
+val writeArguments_initializer : writeArguments
+
+type handle
+
+val max_packet_size : int
+
+type errorCode =
+  | SUCCESS
+  | NOMEM
+  | TTYBUSY
+  | DEVICEBUSY
+  | UNKNOWN_INSTRUCTION
+  | ILLEGAL_INSTRUCTION
+  | INVALID_PARAMETER
+  | INVALID_PACKET
+  | CONNREFUSED
+  | OPNOTSUPP
+  | GAIERR of int
+  | LIBCERR of Unix.error
+  | UNKNOWNTTY
+  | PROTOCOL_VERSION
+  | EOF
+  | EMPTYKEY
+  | DRIVERERROR
+  | AUTHENTICATION
+  | Unknown of int
+
+type error = {
+  brlerrno : int;
+  libcerrno : int;
+  gaierrno : int;
+  errfun : string;
+}
+
+external errorCode_of_error :
+  error -> errorCode = "brlapiml_errorCode_of_error"
+
+external strerror :
+  error -> string = "brlapiml_strerror"
+
+exception Brlapi_error of error
+exception Brlapi_exception of errorCode * int32 * string
+
+external openConnection :
+  settings -> Unix.file_descr * settings = "brlapiml_openConnection"
+external openConnectionWithHandle :
+  settings -> handle = "brlapiml_openConnectionWithHandle"
+external closeConnection :
+  ?h:handle -> unit -> unit = "brlapiml_closeConnection"
+(*
+val expandHost : string -> string * string
+Idem: proto
+*)
+external getDriverName :
+  ?h:handle -> unit -> string = "brlapiml_getDriverName"
+external getModelIdentifier :
+  ?h:handle -> unit -> string = "brlapiml_getModelIdentifier"
+external getDisplaySize :
+  ?h:handle -> unit -> int * int = "brlapiml_getDisplaySize"
+
+external enterTtyMode :
+  ?h:handle -> int -> string -> int = "brlapiml_enterTtyMode"
+val enterTtyModeWithPath :
+  ?h:handle -> int array -> string -> int
+external leaveTtyMode :
+  ?h:handle -> unit -> unit = "brlapiml_leaveTtyMode"
+external setFocus :
+  ?h:handle -> int -> unit = "brlapiml_setFocus"
+
+external writeText :
+  ?h:handle -> int -> string -> unit = "brlapiml_writeText"
+(* Arg optionnel pour curseur ? *)
+external writeDots :
+  ?h:handle -> int array -> unit = "brlapiml_writeDots"
+external write :
+  ?h:handle -> writeArguments -> unit = "brlapiml_write"
+
+(*
+BRLAPI_KEYCODE_MAX
+Les constantes pour travailler sur les flags de touches ?
+Les KEY_SYM ?
+Oui: pour être cohérent avec le fait de fournir le int64
+Flags: entiers.
+*)
+
+(* val getKeyName : expandedKeyCode -> string *)
+
+(*
+brlapi_dotNumberToBit
+brlapi_dotBitToNumber
+Unicode ?
+Oui, utile pour les key_sym
+*)
+
+external readKey :
+  ?h:handle -> unit -> int64 option = "brlapiml_readKey"
+external waitKey :
+  ?h:handle -> unit -> int64 = "brlapiml_waitKey"
+external readKeyWithTimeout :
+  ?h:handle -> int -> int64 option = "brlapiml_readKeyWithTimeout"
+
+type expandedKeyCode = {
+  type_ : int32;
+  command : int32;
+  argument : int32;
+  flags : int32
+}
+
+external expandKeyCode :
+  ?h:handle -> int64 -> expandedKeyCode = "brlapiml_expandKeyCode"
+
+type rangeType =
+  | RT_all
+  | RT_type
+  | RT_command
+  | RT_key
+  | RT_code
+
+external ignoreKeys :
+  ?h:handle -> rangeType -> int64 array -> unit = "brlapiml_ignoreKeys"
+external acceptKeys :
+  ?h:handle -> rangeType -> int64 array -> unit = "brlapiml_acceptKeys"
+external ignoreAllKeys :
+  ?h:handle -> unit = "brlapiml_ignoreAllKeys"
+external acceptAllKeys :
+  ?h:handle -> unit = "brlapiml_acceptAllKeys"  
+external ignoreKeyRanges :
+  ?h:handle -> (int64 * int64) array -> unit = "brlapiml_ignoreKeyRanges"
+external acceptKeyRanges :
+  ?h:handle -> (int64 * int64) array -> unit = "brlapiml_acceptKeyRanges"
+
+external enterRawMode :
+  ?h:handle -> string -> unit = "brlapiml_enterRawMode"
+external leaveRawMode :
+  ?h:handle -> unit -> unit = "brlapiml_leaveRawMode"
+external sendRaw :
+  ?h:handle -> string -> int = "brlapiml_sendRaw"
+external recvRaw :
+  ?h:handle -> unit -> string = "brlapiml_recvRaw"
+
+external suspendDriver :
+  ?h:handle -> string -> unit = "brlapiml_suspendDriver"
+external resumeDriver :
+  ?h:handle -> unit -> unit = "brlapiml_resumeDriver"
+
+module type KEY = sig
+  type key
+  val key_of_int64 : int64 -> key
+  val int64_of_key : key -> int64
+end
+
+module MakeKeyHandlers (M1 : KEY) : sig
+  type key = M1.key
+  val readKey : ?h:handle -> unit -> key option
+  val waitKey : ?h:handle -> unit -> key
+
+  val ignoreKeys : ?h:handle -> rangeType -> key array -> unit
+  val acceptKeys : ?h:handle -> rangeType -> key array -> unit
+  val ignoreKeyRanges : ?h:handle -> (key * key) array -> unit
+  val acceptKeyRanges : ?h:handle -> (key * key) array -> unit
+end
diff --git a/Bindings/Python/Makefile.in b/Bindings/Python/Makefile.in
new file mode 100644
index 0000000..c5bcf62
--- /dev/null
+++ b/Bindings/Python/Makefile.in
@@ -0,0 +1,84 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+PYTHON_OK = @PYTHON_OK@
+PYTHON = @PYTHON@
+PYTHON_VERSION = @PYTHON_VERSION@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+
+CYTHON = @CYTHON@
+CYTHON_CFLAGS = @CYTHON_CFLAGS@
+
+PYTHON_DESTDIR = $(INSTALL_ROOT)
+PYTHON_PREFIX =
+
+PYTHON_MODULE = $(API_NAME)
+PYTHON_API = $(PYTHON_MODULE).$(LIB_EXT)
+
+V_setup_  = $(V_setup_0)
+V_setup_0 = --quiet
+V_setup_1 = --verbose
+V_setup   = $(V_setup_$(V))
+
+INSTALLED_FILES = installed-files
+
+all: $(PYTHON_API)
+
+$(PYTHON_API): brlapi.auto.c $(API_HDRS) | $(INSTALLED_FILES) brlapi
+	set -- $(V_setup) build --build-temp .; \
+	[ "@host_os@" != "mingw32" ] || set -- "$${@}" --compiler mingw32; \
+	"$(PYTHON)" ./setup.py "$${@}"
+	[ "@host_os@" != "mingw32" ] || "$(PYTHON)" ./setup.py $(V_setup) bdist_wininst --skip-build
+
+$(INSTALLED_FILES):
+	touch $(INSTALLED_FILES)
+
+brlapi.auto.c: $(SRC_DIR)/brlapi.pyx $(SRC_DIR)/c_brlapi.pxd constants.auto.pyx
+	"$(CYTHON)" -$(PYTHON_VERSION) -I. -X embedsignature=True -o $@ $(SRC_DIR)/brlapi.pyx
+
+constants.auto.pyx: $(CONSTANTS_DEPENDENCIES)
+	$(AWK) $(CONSTANTS_ARGUMENTS) >$@
+
+doc: $(PYTHON_API)
+	LD_PRELOAD=$(API_LIB) "$(PYTHON)" $(SRC_DIR)/mkdoc.py $(PYTHON_MODULE)
+
+install: all
+	set -- $(V_setup) install --skip-build --record "$(INSTALLED_FILES)"; \
+	[ -z "$(PYTHON_DESTDIR)" ] || set -- "$${@}" --root "$(PYTHON_DESTDIR)"; \
+	[ -z "$(PYTHON_PREFIX)" ] || set -- "$${@}" --prefix "$(PYTHON_PREFIX)"; \
+	"$(PYTHON)" ./setup.py "$${@}"
+	-rm -f -r -- Brlapi.egg-info
+
+uninstall:
+	[ ! -f "$(INSTALLED_FILES)" ] || rm -f -- `$(AWK) '{printf "%s%s ", "$(PYTHON_DESTDIR)", $$0}' "$(INSTALLED_FILES)"`
+
+clean::
+	-rm -f -- $(PYTHON_API) *.auto.* *.html "$(INSTALLED_FILES)"
+	-rm -f -r -- build
+	-rm -f -r -- dist
+	-rm -f -r -- Release
+
+distclean::
+	-rm -f -- setup.py
diff --git a/Bindings/Python/apitest b/Bindings/Python/apitest
new file mode 100755
index 0000000..d1848e1
--- /dev/null
+++ b/Bindings/Python/apitest
@@ -0,0 +1,21 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec python "${programDirectory}/${programName}.py" "${@}"
+exit "${?}"
diff --git a/Bindings/Python/apitest.py b/Bindings/Python/apitest.py
new file mode 100755
index 0000000..1c8d87c
--- /dev/null
+++ b/Bindings/Python/apitest.py
@@ -0,0 +1,135 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+import ctypes
+import os
+import sys
+import sysconfig
+import time
+
+def getProgramName ():
+  return os.path.basename(sys.argv[0])
+
+def logMessage (message, file=sys.stderr, label=getProgramName()):
+  file.write((label + ": " + message + "\n"))
+  file.flush()
+
+def getBuildDirectory (directory):
+  name = "{prefix}.{platform}-{version[0]}.{version[1]}".format(
+    prefix = directory,
+    platform = sysconfig.get_platform(),
+    version = sys.version_info
+  )
+
+  return os.path.join(os.getcwd(), "build", name)
+
+def getLibraryDirectory ():
+  return os.path.join(os.getcwd(), "..", "..", "Programs")
+
+def loadLibrary (directory, name):
+  platform = sysconfig.get_platform()
+
+  if platform == "win32":
+    pattern = name + "-*.dll"
+
+    import fnmatch
+    names = [name
+      for name in os.listdir(directory)
+      if fnmatch.fnmatch(name, pattern)
+    ]
+
+    return ctypes.WinDLL(os.path.join(directory, names[0]))
+
+  return ctypes.CDLL(os.path.join(directory, ("lib" + name + ".so")))
+
+sys.path.insert(0, getBuildDirectory("lib"))
+library = loadLibrary(getLibraryDirectory(), "brlapi")
+import brlapi
+
+if __name__ == "__main__":
+  import errno
+
+  def writeProperty (name, value):
+    try:
+      value = value.decode("utf-8")
+    except AttributeError:
+      pass
+
+    sys.stdout.write(name + ": " + value + "\n")
+
+  writeProperty("BrlAPI Version", ".".join(map(str, brlapi.getLibraryVersion())))
+
+  try:
+    brl = brlapi.Connection()
+
+    try:
+      writeProperty("File Descriptor", str(brl.fileDescriptor))
+      writeProperty("Server Host", brl.host)
+      writeProperty("Authorization Schemes", brl.auth)
+      writeProperty("Driver Name", brl.driverName)
+      writeProperty("Model Identifier", brl.modelIdentifier)
+      writeProperty("Display Width", str(brl.displaySize[0]))
+      writeProperty("Display Height", str(brl.displaySize[1]))
+
+      brl.enterTtyMode()
+      try:
+        timeout = 10
+        brl.writeText("press keys (timeout is %d seconds)" % (timeout, ))
+
+        while True:
+          code = brl.readKeyWithTimeout(timeout * 1000)
+          if not code: break
+
+          properties = brlapi.describeKeyCode(code)
+          properties["code"] = "0X%X" % code
+
+          for name in ("flags", ):
+            properties[name] = ",".join(properties[name])
+
+          for property in (
+            ("command" , "cmd"),
+            ("argument", "arg"),
+            ("flags"   , "flg"),
+          ):
+            (oldName, newName) = property
+            properties[newName] = properties[oldName]
+            del properties[oldName]
+
+          names = ("code", "type", "cmd", "arg", "flg")
+          values = map(lambda name: ("%s=%s" % (name, str(properties[name]))), names)
+
+          text = " ".join(values)
+          brl.writeText(text)
+          writeProperty("Key", text);
+
+      finally:
+        brl.leaveTtyMode()
+    finally:
+      brl.closeConnection()
+  except brlapi.ConnectionError as e:
+    if e.brlerrno == brlapi.ERROR_CONNREFUSED:
+      logMessage("Connection to %s refused. BRLTTY is too busy..." % e.host)
+    elif e.brlerrno == brlapi.ERROR_AUTHENTICATION:
+      logMessage("Authentication with %s failed. Please check the permissions of %s" % (e.host, e.auth))
+    elif e.brlerrno == brlapi.ERROR_LIBCERR and (e.libcerrno == errno.ECONNREFUSED or e.libcerrno == errno.ENOENT):
+      logMessage("Connection to %s failed. Is BRLTTY really running?" % (e.host))
+    else:
+      logMessage("Connection to BRLTTY at %s failed: " % (e.host))
+    print(e)
+    print(e.brlerrno)
+    print(e.libcerrno)
diff --git a/Bindings/Python/bindings.c b/Bindings/Python/bindings.c
new file mode 100644
index 0000000..55f2304
--- /dev/null
+++ b/Bindings/Python/bindings.c
@@ -0,0 +1,170 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2005-2023 by
+ *   Alexis Robert <alexissoft@free.fr>
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* bindings.c provides initialized variables and brlapi exception handler to
+ * the Python bindings */
+
+
+/* include Python.h first in order to prevent a redefine of _POSIX_C_SOURCE */
+#include <Python.h>
+
+#include "brlapi.h"
+
+/* a kludge to get around a broken MinGW <pthread.h> header */
+#ifdef __MINGW32__
+#ifdef __struct_timespec_defined
+#ifndef _TIMESPEC_DEFINED
+#define _TIMESPEC_DEFINED
+#endif /* _TIMESPEC_DEFINED */
+#endif /* __struct_timespec_defined */
+#endif /* __MINGW32__ */
+
+#include <pthread.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "bindings.h"
+
+const brlapi_writeArguments_t brlapi_writeArguments_initialized = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+
+static pthread_once_t brlapi_protocolExceptionOnce = PTHREAD_ONCE_INIT;
+static pthread_key_t brlapi_protocolExceptionKey;
+static char *brlapi_protocolExceptionSingleThread;
+
+#if defined(WINDOWS)
+
+#elif defined(__GNUC__) || defined(__sun__)
+#pragma weak pthread_once
+#pragma weak pthread_key_create
+#pragma weak pthread_getspecific
+#pragma weak pthread_setspecific
+#endif /* weak external references */
+
+static void BRLAPI_STDCALL brlapi_pythonExceptionHandler(brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  char str[128];
+
+  brlapi__strexception(handle, str, sizeof(str), error, type, packet, size);
+#ifndef WINDOWS
+  if (!(pthread_once && pthread_key_create))
+    brlapi_protocolExceptionSingleThread = strdup(str);
+  else
+#endif
+    pthread_setspecific(brlapi_protocolExceptionKey, strdup(str));
+}
+
+char *brlapi_protocolException(void)
+{
+  char *exception;
+#ifndef WINDOWS
+  if (!(pthread_once && pthread_key_create)) {
+    exception = brlapi_protocolExceptionSingleThread;
+    brlapi_protocolExceptionSingleThread = NULL;
+    return exception;
+  } else
+#endif
+  {
+    exception = pthread_getspecific(brlapi_protocolExceptionKey);
+    pthread_setspecific(brlapi_protocolExceptionKey, NULL);
+    return exception;
+  }
+}
+
+static void do_brlapi_protocolExceptionInit(void)
+{
+  pthread_key_create(&brlapi_protocolExceptionKey, free);
+}
+
+void brlapi_protocolExceptionInit(brlapi_handle_t *handle) {
+#ifndef WINDOWS
+  if (pthread_once && pthread_key_create)
+#endif /* WINDOWS */
+    pthread_once(&brlapi_protocolExceptionOnce, do_brlapi_protocolExceptionInit);
+
+  brlapi__setExceptionHandler(handle, &brlapi_pythonExceptionHandler);
+}
+
+/* brlapi_python_parameter_callback */
+/* This is called from brlapi functions called by Python. We pass the callback
+ * data as such for brlapi.pyx' callback to translate them into python objects
+ */
+static void brlapi_python_parameter_callback(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len)
+{
+  brlapi_python_paramCallbackDescriptor_t *descr = priv;
+  PyObject *arglist, *result;
+  PyGILState_STATE gstate;
+  brlapi_python_callbackData_t callbackData = {
+    .parameter = parameter,
+    .subparam = subparam,
+    .flags = flags,
+    .data = data,
+    .len = len,
+  };
+
+  gstate = PyGILState_Ensure();
+
+  arglist = Py_BuildValue("(L)", (long long) (uintptr_t) &callbackData);
+
+  result = PyObject_CallObject(descr->callback, arglist);
+  if (result != NULL)
+    Py_DECREF(result);
+
+  PyGILState_Release(gstate);
+}
+
+brlapi_python_paramCallbackDescriptor_t *brlapi_python_watchParameter(brlapi_handle_t *handle, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, PyObject *func)
+{
+  brlapi_python_paramCallbackDescriptor_t *descr;
+  brlapi_paramCallbackDescriptor_t *brlapi_descr;
+  PyObject *result;
+
+  if (!PyCallable_Check(func)) {
+    PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+    return NULL;
+  }
+
+  Py_INCREF(func);
+
+  descr = malloc(sizeof(*descr));
+  descr->callback = func;
+
+  brlapi_descr = brlapi__watchParameter(handle, param, subparam, flags, brlapi_python_parameter_callback, descr, NULL, 0);
+
+  if (!brlapi_descr) {
+    free(descr);
+    PyErr_SetString(PyExc_ValueError, "watching parameter failed");
+    return NULL;
+  }
+
+  descr->brlapi_descr = brlapi_descr;
+
+  return descr;
+}
+
+int brlapi_python_unwatchParameter(brlapi_handle_t *handle, brlapi_python_paramCallbackDescriptor_t *descr)
+{
+  int ret;
+
+  ret = brlapi__unwatchParameter(handle, descr->brlapi_descr);
+  Py_DECREF(descr->callback);
+  free(descr);
+
+  return ret;
+}
diff --git a/Bindings/Python/bindings.h b/Bindings/Python/bindings.h
new file mode 100644
index 0000000..e8a5a95
--- /dev/null
+++ b/Bindings/Python/bindings.h
@@ -0,0 +1,42 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2005-2023 by
+ *   Alexis Robert <alexissoft@free.fr>
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* bindings.h provides initialized variables to the Python bindings */
+
+#include "brlapi.h"
+
+typedef struct {
+  brlapi_paramCallbackDescriptor_t brlapi_descr;
+  PyObject *callback;
+} brlapi_python_paramCallbackDescriptor_t;
+
+typedef struct {
+  brlapi_param_t parameter;
+  brlapi_param_subparam_t subparam;
+  brlapi_param_flags_t flags;
+  const void *data;
+  size_t len;
+} brlapi_python_callbackData_t;
+
+extern const brlapi_writeArguments_t brlapi_writeArguments_initialized;
+extern char *brlapi_protocolException(void);
+extern void brlapi_protocolExceptionInit(brlapi_handle_t *handle);
+
+extern brlapi_python_paramCallbackDescriptor_t *brlapi_python_watchParameter(brlapi_handle_t *handle, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, PyObject *args);
+extern int brlapi_python_unwatchParameter(brlapi_handle_t *handle, brlapi_python_paramCallbackDescriptor_t *descr);
diff --git a/Bindings/Python/bindings.m4 b/Bindings/Python/bindings.m4
new file mode 100644
index 0000000..c9d37e2
--- /dev/null
+++ b/Bindings/Python/bindings.m4
@@ -0,0 +1,126 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_PYTHON_BINDINGS], [dnl
+PYTHON_OK=true
+
+# Suppress a new warning introduced in Python 3.6:
+#
+#    Python runtime initialized with LC_CTYPE=C
+#    (a locale with default ASCII encoding),
+#    which may cause Unicode compatibility problems.
+#    Using C.UTF-8, C.utf8, or UTF-8 (if available)
+#    as alternative Unicode-compatible locales is recommended.
+#
+export PYTHONCOERCECLOCALE=0
+
+AC_PATH_PROGS([PYTHON], [python3 python python2], [python])
+if test -n "${PYTHON}"
+then
+   test -n "${PYTHON_VERSION}" || {
+      BRLTTY_PYTHON_QUERY([PYTHON_VERSION], [version])
+
+      test -n "${PYTHON_VERSION}" || {
+         AC_MSG_WARN([Python version not defined])
+         PYTHON_OK=false
+      }
+   }
+   AC_SUBST([PYTHON_VERSION])
+
+   test -n "${PYTHON_CPPFLAGS}" || {
+      BRLTTY_PYTHON_QUERY([python_include_directory], [incdir])
+
+      if test -n "${python_include_directory}"
+      then
+         PYTHON_CPPFLAGS="-I${python_include_directory}"
+
+         test -f "${python_include_directory}/Python.h" || {
+            AC_MSG_WARN([Python developer environment not installed])
+            PYTHON_OK=false
+         }
+      else
+         AC_MSG_WARN([Python include directory not found])
+         PYTHON_OK=false
+      fi
+   }
+   AC_SUBST([PYTHON_CPPFLAGS])
+
+   test -n "${PYTHON_LIBS}" || {
+      PYTHON_LIBS="-lpython${PYTHON_VERSION}"
+      BRLTTY_PYTHON_QUERY([python_library_directory], [libdir])
+
+      if test -n "${python_library_directory}"
+      then
+         PYTHON_LIBS="-L${python_library_directory}/config ${PYTHON_LIBS}"
+      else
+         AC_MSG_WARN([Python library directory not found])
+         PYTHON_OK=false
+      fi
+   }
+   AC_SUBST([PYTHON_LIBS])
+
+   test -n "${PYTHON_EXTRA_LIBS}" || {
+      BRLTTY_PYTHON_QUERY([PYTHON_EXTRA_LIBS], [libopts])
+   }
+   AC_SUBST([PYTHON_EXTRA_LIBS])
+
+   test -n "${PYTHON_EXTRA_LDFLAGS}" || {
+      BRLTTY_PYTHON_QUERY([PYTHON_EXTRA_LDFLAGS], [linkopts])
+   }
+   AC_SUBST([PYTHON_EXTRA_LDFLAGS])
+
+   test -n "${PYTHON_SITE_PKG}" || {
+      BRLTTY_PYTHON_QUERY([PYTHON_SITE_PKG], [pkgdir])
+
+      test -n "${PYTHON_SITE_PKG}" || {
+         AC_MSG_WARN([Python packages directory not found])
+         PYTHON_OK=false
+      }
+   }
+   AC_SUBST([PYTHON_SITE_PKG])
+else
+   AC_MSG_WARN([Python interpreter not found])
+   PYTHON_OK=false
+fi
+
+AC_PATH_PROGS([CYTHON], [cython3 cython])
+test -n "${CYTHON}" || {
+   AC_MSG_WARN([Cython compiler not found])
+   PYTHON_OK=false
+}
+
+if test "${GCC}" = "yes"
+then
+   CYTHON_CFLAGS="-Wno-parentheses -Wno-unused -fno-strict-aliasing -U_POSIX_C_SOURCE -U_XOPEN_SOURCE"
+else
+   case "${host_os}"
+   in
+      *)
+         CYTHON_CFLAGS=""
+         ;;
+   esac
+fi
+AC_SUBST([CYTHON_CFLAGS])
+
+AC_SUBST([PYTHON_OK])
+])
+
+AC_DEFUN([BRLTTY_PYTHON_QUERY], [dnl
+   $1=`"${PYTHON}" "${srcdir}/Tools/pythoncmd" $2`
+])
diff --git a/Bindings/Python/brlapi.pyx b/Bindings/Python/brlapi.pyx
new file mode 100644
index 0000000..0136895
--- /dev/null
+++ b/Bindings/Python/brlapi.pyx
@@ -0,0 +1,1109 @@
+"""
+This module implements a set of bindings for BrlAPI, a braille bridge for applications.
+
+The reference C API documentation is available online http://brltty.app/doc/BrlAPIref, as well as in manual pages.
+
+This documentation is only a python helper, you should also read C manual pages.
+
+Example : 
+import brlapi
+import errno
+import Xlib.keysymdef.miscellany
+try:
+  b = brlapi.Connection()
+  print("Server version " + str(b.getParameter(brlapi.PARAM_SERVER_VERSION, 0, brlapi.PARAMF_GLOBAL)))
+  print("Display size " + str(b.getParameter(brlapi.PARAM_DISPLAY_SIZE, 0, brlapi.PARAMF_GLOBAL)))
+  print("Driver " + b.getParameter(brlapi.PARAM_DRIVER_NAME, 0, brlapi.PARAMF_GLOBAL))
+  print("Model " + b.getParameter(brlapi.PARAM_DEVICE_MODEL, 0, brlapi.PARAMF_GLOBAL))
+
+  for cmd in b.getParameter(brlapi.PARAM_BOUND_COMMAND_KEYCODES, 0, brlapi.PARAMF_GLOBAL):
+    print("Command %x short name: %s" % (cmd, b.getParameter(brlapi.PARAM_COMMAND_KEYCODE_NAME, cmd, brlapi.PARAMF_GLOBAL)))
+
+  for key in b.getParameter(brlapi.PARAM_DEFINED_DRIVER_KEYCODES, 0, brlapi.PARAMF_GLOBAL):
+    print("Key %x short name: %s" % (key, b.getParameter(brlapi.PARAM_DRIVER_KEYCODE_NAME, key, brlapi.PARAMF_GLOBAL)))
+
+  # Make our output more prioritized
+  b.setParameter(brlapi.PARAM_CLIENT_PRIORITY, 0, False, 70)
+
+  def update_callback(param, subparam, flags, value):
+    s = ""
+    for i in value:
+      s += unichr(0x2800 + ord(i))
+    print("Got output update %s" % s)
+
+  p = b.watchParameter(brlapi.PARAM_RENDERED_CELLS, 0, False, update_callback)
+
+  b.enterTtyMode()
+  b.ignoreKeys(brlapi.rangeType_all,[0])
+
+  # Accept the home, window up and window down braille commands
+  b.acceptKeys(brlapi.rangeType_command,[brlapi.KEY_TYPE_CMD|brlapi.KEY_CMD_HOME, brlapi.KEY_TYPE_CMD|brlapi.KEY_CMD_WINUP, brlapi.KEY_TYPE_CMD|brlapi.KEY_CMD_WINDN])
+
+  # Accept the tab key
+  b.acceptKeys(brlapi.rangeType_key,[brlapi.KEY_TYPE_SYM|Xlib.keysymdef.miscellany.XK_Tab])
+
+  b.writeText("Trying to get a key within one second")
+  key = b.readKeyWithTimeout(1000)
+  print("got " + str(key))
+
+  b.writeText("Press home, winup/dn or tab to continue ... ¤")
+  key = b.readKey()
+
+  k = brlapi.expandKeyCode(key)
+  b.writeText("Key %ld (%x %x %x %x) !" % (key, k["type"], k["command"], k["argument"], k["flags"]))
+  b.writeText(None,1)
+  b.acceptAllKeys()
+  b.readKey()
+
+  underline = chr(brlapi.DOT7 + brlapi.DOT8)
+  # Note: center() can take two arguments only starting from python 2.4
+  b.write(
+      regionBegin = 1,
+      regionSize = 40,
+      text = "Press any key to continue               ",
+      orMask = 25*underline + 15*chr(0))
+  b.readKey()
+
+  b.acceptAllKeys()
+  b.writeText("Press any key")
+  k = b.readKey()
+  k = brlapi.expandKeyCode(key)
+  b.writeText("Key %ld (%x %x %x %x) !" % (key, k["type"], k["command"], k["argument"], k["flags"]))
+  b.readKey()
+
+  b.ignoreAllKeys()
+  b.acceptKeyRanges([(brlapi.KEY_TYPE_CMD|brlapi.KEY_CMD_PASSDOTS, brlapi.KEY_TYPE_CMD|brlapi.KEY_CMD_PASSDOTS|brlapi.KEY_CMD_ARG_MASK)])
+  b.writeText("Press a dot key")
+  key = b.readKey()
+  k = brlapi.expandKeyCode(key)
+  b.writeText("Key %ld (%x %x %x %x) !" % (key, k["type"], k["command"], k["argument"], k["flags"]))
+  b.acceptAllKeys()
+  b.readKey()
+
+  b.unwatchParameter(p)
+  b.leaveTtyMode()
+  b.closeConnection()
+
+except brlapi.ConnectionError as e:
+  if e.brlerrno == brlapi.ERROR_CONNREFUSED:
+    print("Connection to %s refused. BRLTTY is too busy..." % e.host)
+  elif e.brlerrno == brlapi.ERROR_AUTHENTICATION:
+    print("Authentication with %s failed. Please check the permissions of %s" % (e.host,e.auth))
+  elif e.brlerrno == brlapi.ERROR_LIBCERR and (e.libcerrno == errno.ECONNREFUSED or e.libcerrno == errno.ENOENT):
+    print("Connection to %s failed. Is BRLTTY really running?" % (e.host))
+  else:
+    print("Connection to BRLTTY at %s failed: " % (e.host))
+  print(e)
+  print(e.brlerrno)
+  print(e.libcerrno)
+"""
+
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+cimport c_brlapi
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t
+import errno
+
+include "constants.auto.pyx"
+
+def getLibraryVersion():
+	"""Get the BrlAPI version as a three-element list (major, minor, revision).
+	See brlapi_getLibraryVersion(3)."""
+	cdef int major
+	cdef int minor
+	cdef int revision
+	with nogil:
+		c_brlapi.brlapi_getLibraryVersion(&major, &minor, &revision)
+	return (major, minor, revision)
+
+def expandKeyCode(code):
+	"""Expand a keycode into its individual components.
+	See brlapi_expandKeyCode(3)."""
+	cdef c_brlapi.brlapi_expandedKeyCode_t ekc
+	cdef int retval
+	retval = c_brlapi.brlapi_expandKeyCode(code, &ekc)
+	if retval == -1:
+		raise OperationError()
+	else:
+		return {
+			"type": ekc.type,
+			"command": ekc.command,
+			"argument": ekc.argument,
+			"flags": ekc.flags
+		}
+
+def describeKeyCode(code):
+	"""Describe the individual components of a keycode symbolically.
+	See brlapi_describeKeyCode(3)."""
+	cdef c_brlapi.brlapi_describedKeyCode_t dkc
+	cdef int retval
+	retval = c_brlapi.brlapi_describeKeyCode(code, &dkc)
+	if retval == -1:
+		raise OperationError()
+	else:
+		flags = ()
+		for flag in range(dkc.flags):
+			flags += (dkc.flag[flag], )
+		return {
+			"type": dkc.type,
+			"command": dkc.command,
+			"argument": dkc.argument,
+			"flags": flags
+		}
+
+cdef object _parameterToPython(c_brlapi.brlapi_param_t c_param, const void *c_value, size_t size):
+	cdef const c_brlapi.brlapi_param_properties_t *props
+
+	with nogil:
+		props = c_brlapi.brlapi_getParameterProperties(c_param)
+
+	if props == NULL:
+		raise OperationError()
+
+	if props.type == PARAM_TYPE_STRING:
+		string = <char *>c_value
+		s = string[:size]
+		ret = s.decode("UTF-8")
+	elif props.type == PARAM_TYPE_BOOLEAN:
+		values8 = <uint8_t *>c_value
+		ret = values8[0] != 0
+	elif props.type == PARAM_TYPE_UINT8:
+		if props.isArray:
+			values8 = <uint8_t *>c_value
+			ret = values8[:size]
+		else:
+			values8 = <uint8_t *>c_value
+			ret = values8[0]
+	elif props.type == PARAM_TYPE_UINT16:
+		if props.isArray:
+			values16 = <uint16_t *>c_value
+			ret = [values16[i] for i in range(size//2)]
+		else:
+			values16 = <uint16_t *>c_value
+			ret = values16[0]
+	elif props.type == PARAM_TYPE_UINT32:
+		if props.isArray:
+			values32 = <uint32_t *>c_value
+			ret = [values32[i] for i in range(size//4)]
+		else:
+			values32 = <uint32_t *>c_value
+			ret = values32[0]
+	elif props.type == PARAM_TYPE_UINT64:
+		if props.isArray:
+			values64 = <uint64_t *>c_value
+			ret = [values64[i] for i in range(size//8)]
+		else:
+			values64 = <uint64_t *>c_value
+			ret = values64[0]
+	else:
+		raise ValueError("Unsupported parameter type")
+
+	return ret
+
+class OperationError(Exception):
+	"""Error while performing some operation"""
+	def __init__(self):
+		cdef char *exception
+		exception = c_brlapi.brlapi_protocolException()
+		if (exception):
+			self.exception = exception
+			c_brlapi.free(exception)
+		else:
+			self.exception = None
+			self.brlerrno = c_brlapi.brlapi_error.brlerrno
+			self.libcerrno = c_brlapi.brlapi_error.libcerrno
+			self.gaierrno = c_brlapi.brlapi_error.gaierrno
+			if (c_brlapi.brlapi_error.errfun):
+				self.errfun = c_brlapi.brlapi_error.errfun
+			else:
+				self.errfun = b""
+
+	def __str__(self):
+		cdef c_brlapi.brlapi_error_t error
+		cdef c_brlapi.size_t size
+		cdef char *buf
+		if self.exception:
+			return self.exception
+		error.brlerrno = self.brlerrno
+		error.libcerrno = self.libcerrno
+		error.gaierrno = self.gaierrno
+		str = self.errfun
+		error.errfun = str
+		size = c_brlapi.brlapi_strerror_r(&error, NULL, 0)
+		buf = <char*>c_brlapi.malloc(size+1)
+		c_brlapi.brlapi_strerror_r(&error, buf, size+1)
+		ret = buf.decode("ASCII", errors="replace")
+		c_brlapi.free(buf)
+		return ret
+
+class ConnectionError(OperationError):
+	"""Error while connecting to BrlTTY"""
+
+	def __init__(self, host, auth):
+		OperationError.__init__(self)
+		self.host = host
+		self.auth = auth
+
+	def __str__(self):
+		msg = "couldn't connect to %s with key %s: %s" % (self.host,self.auth,OperationError.__str__(self))
+		msg = msg + "\n(brlerrno %d, libcerrno %d, gaierrno %d)" % (self.brlerrno, self.libcerrno, self.gaierrno)
+		if self.brlerrno == ERROR_CONNREFUSED:
+			msg = msg + "\nBRLTTY is too busy..."
+		elif self.brlerrno == ERROR_AUTHENTICATION:
+			msg = msg + "\nAuthentication failed. Please check you can read %s and it is not empty." % self.auth
+		elif self.brlerrno == ERROR_LIBCERR and (self.libcerrno == errno.ECONNREFUSED or self.libcerrno == errno.ENOENT):
+			msg = msg + "\nIs BRLTTY really running?"
+		return msg
+
+	def host(self):
+		"""Host of BRLTTY server"""
+		return self.settings.host
+
+	def auth(self):
+		"""Authentication method used"""
+		return self.settings.auth
+
+cdef class WriteStruct:
+	"""Structure containing arguments to be given to Connection.write()
+	See brlapi_writeArguments_t(3).
+	
+	This is DEPRECATED. Use the named parameters of write() instead."""
+	cdef c_brlapi.brlapi_writeArguments_t props
+
+	def __init__(self):
+		self.props = c_brlapi.brlapi_writeArguments_initialized
+
+	property displayNumber:
+		"""Display number DISPLAY_DEFAULT == unspecified"""
+		def __get__(self):
+			return self.props.displayNumber
+		def __set__(self, val):
+			self.props.displayNumber = val
+
+	property regionBegin:
+		"""Region of display to update, 1st character of display is 1"""
+		def __get__(self):
+			return self.props.regionBegin
+		def __set__(self, val):
+			self.props.regionBegin = val
+
+	property regionSize:
+		"""Number of characters held in text, attrAnd and attrOr. For multibytes text, this is the number of multibyte characters. Combining and double-width characters count for 1"""
+		def __get__(self):
+			return self.props.regionSize
+		def __set__(self, val):
+			self.props.regionSize = val
+
+	property text:
+		"""Text to display"""
+		def __get__(self):
+			if (not self.props.text):
+				return None
+			else:
+				return self.props.text
+		def __set__(self, val):
+			cdef c_brlapi.size_t size
+			cdef char *c_val
+			cdef char *text
+			if (type(val) == unicode):
+				val = val.encode('UTF-8')
+				self.charset = 'UTF-8'.encode("ASCII")
+			if (self.props.text):
+				c_brlapi.free(<char*>self.props.text)
+			if (val):
+				size = len(val)
+				c_val = val
+				text = <char*>c_brlapi.malloc(size+1)
+				c_brlapi.memcpy(<void*>text,<void*>c_val,size)
+				text[size] = 0
+				self.props.text = text
+				self.props.textSize = size
+			else:
+				self.props.text = NULL
+
+	property cursor:
+		"""CURSOR_LEAVE == don't touch, CURSOR_OFF == turn off, 1 = 1st char of display, ..."""
+		def __get__(self):
+			return self.props.cursor
+		def __set__(self, val):
+			self.props.cursor = val
+
+	property charset:
+		"""Character set of the text"""
+		def __get__(self):
+			if (not self.props.charset):
+				return None
+			else:
+				return self.props.charset
+		def __set__(self, val):
+			cdef c_brlapi.size_t size
+			cdef char *c_val
+			cdef char *charset
+			if (self.props.charset):
+				c_brlapi.free(<char*>self.props.charset)
+			if (val):
+				if (type(val) == unicode):
+					val = val.encode('ASCII')
+				size = len(val)
+				c_val = val
+				charset = <char*>c_brlapi.malloc(size+1)
+				c_brlapi.memcpy(<void*>charset,<void*>c_val,size)
+				charset[size] = 0
+				self.props.charset = charset
+			else:
+				self.props.charset = NULL
+
+	property attrAnd:
+		"""And attributes; applied first"""
+		def __get__(self):
+			cdef char *c_val
+			if (not self.props.andMask):
+				return None
+			else:
+				c_val = <char*>self.props.andMask
+				return c_val[:self.regionSize]
+		def __set__(self, val):
+			cdef c_brlapi.size_t size
+			cdef char *c_val
+			if (self.props.andMask):
+				c_brlapi.free(<char*>self.props.andMask)
+			if (val):
+				if (type(val) == unicode):
+					val = val.encode('latin1')
+				size = len(val)
+				c_val = val
+				self.props.andMask = <unsigned char*>c_brlapi.malloc(size)
+				c_brlapi.memcpy(<void*>self.props.andMask,<void*>c_val,size)
+			else:
+				self.props.andMask = NULL
+
+	property attrOr:
+		"""Or attributes; applied after ANDing"""
+		def __get__(self):
+			cdef char *c_val
+			if (not self.props.orMask):
+				return None
+			else:
+				c_val = <char*>self.props.orMask
+				return c_val[:self.regionSize]
+		def __set__(self, val):
+			cdef c_brlapi.size_t size
+			cdef char *c_val
+			if (self.props.orMask):
+				c_brlapi.free(<char*>self.props.orMask)
+			if (val):
+				if (type(val) == unicode):
+					val = val.encode('latin1')
+				size = len(val)
+				c_val = val
+				self.props.orMask = <unsigned char*>c_brlapi.malloc(size)
+				c_brlapi.memcpy(<void*>self.props.orMask,<void*>c_val,size)
+			else:
+				self.props.orMask = NULL
+
+cdef class Connection:
+	"""Class which manages the bridge between your program and BrlAPI"""
+
+	cdef c_brlapi.brlapi_handle_t *h
+	cdef c_brlapi.brlapi_connectionSettings_t settings
+	cdef int fd
+
+	def __init__(self, host = None, auth = None):
+		"""Connect your program to BrlTTY using settings
+
+		See brlapi_openConnection(3)
+		
+		Setting host to None defaults it to localhost, using the local installation's default TCP port, or to the content of the BRLAPI_HOST environment variable, if it exists.
+		Note: Please check that resolving this name works before complaining.
+
+		Setting auth to None defaults it to local installation setup or to the content of the BRLAPI_AUTH environment variable, if it exists."""
+		cdef c_brlapi.brlapi_connectionSettings_t client
+
+		if auth:
+			client.auth = auth
+		else:
+			client.auth = NULL
+
+		if host:
+			client.host = host
+		else:
+			client.host = NULL
+
+		self.h = <c_brlapi.brlapi_handle_t*> c_brlapi.malloc(c_brlapi.brlapi_getHandleSize())
+
+		with nogil:
+			self.fd = <int>c_brlapi.brlapi__openConnection(self.h, &client, &self.settings)
+		c_brlapi.brlapi_protocolExceptionInit(self.h)
+		if self.fd == -1:
+			c_brlapi.free(self.h)
+			raise ConnectionError(self.settings.host, self.settings.auth)
+
+	def closeConnection(self):
+		"""Close the BrlAPI connection"""
+		if self.fd != -1:
+			c_brlapi.brlapi__closeConnection(self.h)
+			self.fd = -1
+
+	def __del__(self):
+		"""Release resources used by the connection"""
+		if self.fd != -1:
+			c_brlapi.brlapi__closeConnection(self.h)
+		c_brlapi.free(self.h)
+
+	property host:
+		"""To get authorized to connect, libbrlapi has to tell the BrlAPI server a secret key, for security reasons. This is the path to the file which holds it; it will hence have to be readable by the application."""
+		def __get__(self):
+			return self.settings.host
+
+	property auth:
+		"""This tells where the BrlAPI server resides : it might be listening on another computer, on any TCP port. It should look like "foo:1", which means TCP port number BRLAPI_SOCKETPORTNUM+1 on computer called "foo"."""
+		def __get__(self):
+			return self.settings.auth
+
+	property fileDescriptor:
+		"""Returns the Unix file descriptor that the connection uses"""
+		def __get__(self):
+			return self.fd
+
+	property displaySize:
+		"""Get the size of the braille display
+		See brlapi_getDisplaySize(3)."""
+		def __get__(self):
+			cdef unsigned int x
+			cdef unsigned int y
+			cdef int retval
+			with nogil:
+				retval = c_brlapi.brlapi__getDisplaySize(self.h, &x, &y)
+			if retval == -1:
+				raise OperationError()
+			else:
+				return (x, y)
+	
+	property driverName:
+		"""Get the complete name of the driver used by BrlTTY
+		See brlapi_getDriverName(3)."""
+		def __get__(self):
+			cdef char name[21]
+			cdef int retval
+			with nogil:
+				retval = c_brlapi.brlapi__getDriverName(self.h, name, sizeof(name))
+			if retval == -1:
+				raise OperationError()
+			else:
+				return name
+
+	property modelIdentifier:
+		"""Get the identifier for the model of the braille display
+		See brlapi_getModelIdentifier(3)."""
+		def __get__(self):
+			cdef char identifier[21]
+			cdef int retval
+			with nogil:
+				retval = c_brlapi.brlapi__getModelIdentifier(self.h, identifier, sizeof(identifier))
+			if retval == -1:
+				raise OperationError()
+			else:
+				return identifier
+
+	def enterTtyMode(self, tty = TTY_DEFAULT, driver = None):
+		"""Ask for some tty, with some key mechanism
+
+		See brlapi_enterTtyMode(3).
+
+		* tty : If tty >= 0, application takes control of the specified tty
+			If tty == TTY_DEFAULT, the library first tries to get the tty number from the WINDOWID environment variable (form xterm case), then the CONTROLVT variable, and at last reads /proc/self/stat (on linux)
+		* driver : Tells how the application wants readKey() to return key presses. None or "" means BrlTTY commands are required, whereas a driver name means that raw key codes returned by this driver are expected."""
+		cdef int retval
+		cdef int c_tty
+		cdef char *c_driver
+		c_tty = tty
+		if not driver:
+			c_driver = NULL
+		else:
+			if (type(driver) == unicode):
+				driver = driver.encode('ASCII')
+			c_driver = driver
+		with nogil:
+			retval = c_brlapi.brlapi__enterTtyMode(self.h, c_tty, c_driver)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def enterTtyModeWithPath(self, path = [], driver = None):
+		"""Ask for some tty, with some key mechanism
+
+		See brlapi_enterTtyModeWithPath(3).
+
+		* tty is an array of ttys representing the tty path to be got. Can be None.
+		* driver : has the same meaning as in enterTtyMode.
+		
+		Providing an empty array or None means to get the root."""
+		cdef int retval
+		cdef int *c_ttys
+		cdef int c_nttys
+		cdef char *c_driver
+		if not path:
+			c_ttys = NULL
+			c_nttys = 0
+		else:
+			c_nttys = len(path)
+			c_ttys = <int*>c_brlapi.malloc(c_nttys * sizeof(int))
+			for i from 0 <= i < c_nttys:
+				c_ttys[i] = path[i]
+		if not driver:
+			c_driver = NULL
+		else:
+			if (type(driver) == unicode):
+				driver = driver.encode('ASCII')
+			c_driver = driver
+		with nogil:
+			retval = c_brlapi.brlapi__enterTtyModeWithPath(self.h, c_ttys, c_nttys, c_driver)
+		if (c_ttys):
+			c_brlapi.free(c_ttys)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def leaveTtyMode(self):
+		"""Stop controlling the tty
+		See brlapi_leaveTtyMode(3)."""
+		cdef int retval
+		with nogil:
+			retval = c_brlapi.brlapi__leaveTtyMode(self.h)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def setFocus(self, tty):
+		"""Tell the current tty to brltty.
+		See brlapi_setFocus(3).
+		This is intended for focus tellers, such as brltty, xbrlapi, screen, ... enterTtyMode() must have been called before hand to tell where this focus applies in the tty tree."""
+		cdef int retval
+		cdef int c_tty
+		c_tty = tty
+		with nogil:
+			retval = c_brlapi.brlapi__setFocus(self.h, c_tty)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	# The writeArguments parameter must remain first (after self) in order
+	# to maintain backward compatibility with old code which passes it
+	# by position. New code should not use it since the plan is to remove
+	# it once it's no longer being used. New code should supply attributes
+	# by specifying the remaining parameters, as needed, by name.
+	def write(self, WriteStruct writeArguments = None,
+			displayNumber = None,
+			regionBegin = None,
+			regionSize = None,
+			text = None,
+			andMask = None,
+			orMask = None,
+			cursor = None,
+			charset = None):
+		"""Update a specific region of the braille display and apply and/or masks.
+		See brlapi_write(3).
+		* s : gives information necessary for the update"""
+		cdef int retval
+		if not writeArguments:
+			writeArguments = WriteStruct()
+		if displayNumber != None:
+			writeArguments.displayNumber = displayNumber
+		if regionBegin != None:
+			writeArguments.regionBegin = regionBegin
+		if regionSize != None:
+			writeArguments.regionSize = regionSize
+		if text:
+			writeArguments.text = text
+		if andMask:
+			writeArguments.attrAnd = andMask
+		if orMask:
+			writeArguments.attrOr = orMask
+		if cursor != None:
+			writeArguments.cursor = cursor
+		if charset:
+			writeArguments.charset = charset
+		with nogil:
+			retval = c_brlapi.brlapi__write(self.h, &writeArguments.props)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def writeDots(self, dots):
+		"""Write the given dots array to the display.
+		See brlapi_writeDots(3).
+		* dots : points on an array of dot information, one per character. Its size must hence be the same as what displaysize provides."""
+		cdef int retval
+		cdef char *c_dots
+		cdef unsigned char *c_udots
+		(x, y) = self.displaySize
+		dispSize = x * y
+		if (type(dots) == unicode):
+			dots = dots.encode('latin1')
+		if (len(dots) < dispSize):
+			dots = dots + b"".center(dispSize - len(dots), b'\0')
+		c_dots = dots
+		c_udots = <unsigned char *>c_dots
+		with nogil:
+			retval = c_brlapi.brlapi__writeDots(self.h, c_udots)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def writeText(self, text, cursor = CURSOR_OFF):
+		"""Write the given \0-terminated string to the braille display.
+		See brlapi_writeText(3).
+		If the string is too long, it is cut. If it's too short, spaces are appended. The current LC_CTYPE locale is considered, unless it is left as default "C", in which case the charset is assumed to be 8bits, and the same as the server's.
+
+		* cursor : gives the cursor position; if equal to CURSOR_OFF, no cursor is shown at all; if cursor == CURSOR_LEAVE, the cursor is left where it is
+		* text : points to the string to be displayed"""
+		w = WriteStruct()
+		w.cursor = cursor
+		if text is not None:
+			(x, y) = self.displaySize
+			dispSize = x * y
+			if (len(text) < dispSize):
+				text = text + "".center(dispSize - len(text))
+			w.regionBegin = 1
+			w.regionSize = dispSize
+			w.text = text[0 : dispSize]
+		return self.write(w)
+
+	def readKey(self, wait = True):
+		"""Read a key from the braille keyboard.
+		See brlapi_readKey(3).
+
+		This function returns one key press's code.
+
+		If None or "" was given to enterTtyPath(), a brltty command is returned. It is hence pretty driver-independent, and should be used by default when no other option is possible.
+
+		By default, all commands but those which restart drivers and switch virtual are returned to the application and not to brltty. If the application doesn't want to see some command events, it should call either ignoreKeys() or ignoreKeyRanges().
+
+		If some driver name was given to enterTtyMode(), a raw keycode is returned, as specified by the terminal driver. It generally corresponds to the very code that the terminal tells to the driver. This should only be used by applications which are dedicated to a particular braille terminal. Hence, checking the terminal type thanks to a call to drivername before getting tty control is a pretty good idea.
+
+		By default, all the keypresses will be passed to the client, none will go through brltty, so the application will have to handle console switching itself for instance."""
+		cdef c_brlapi.brlapi_keyCode_t code
+		cdef int retval
+		cdef int c_wait
+		c_wait = wait
+
+		while True:
+			with nogil:
+				retval = c_brlapi.brlapi__readKey(self.h, c_wait, <c_brlapi.brlapi_keyCode_t*>&code)
+			if retval == -1 and not (c_brlapi.brlapi_error.brlerrno == ERROR_LIBCERR and c_brlapi.brlapi_error.libcerrno == errno.EINTR):
+				raise OperationError()
+			elif retval <= 0:
+				if not wait:
+					return None
+			else:
+				return code
+
+	def readKeyWithTimeout(self, timeout_ms = -1):
+		"""Read a key from the braille keyboard.
+		See brlapi_readKeyWithtimeout(3).
+
+                This function works like brlapi_readKey, except that parameter wait is replaced by a timeout_ms parameter."""
+		cdef c_brlapi.brlapi_keyCode_t code
+		cdef int retval
+		cdef int c_timeout_ms
+		c_timeout_ms = timeout_ms
+
+		while True:
+			with nogil:
+				retval = c_brlapi.brlapi__readKeyWithTimeout(self.h, c_timeout_ms, <c_brlapi.brlapi_keyCode_t*>&code)
+			if retval == -1 and not (c_brlapi.brlapi_error.brlerrno == ERROR_LIBCERR and c_brlapi.brlapi_error.libcerrno == errno.EINTR):
+				raise OperationError()
+			elif retval <= 0:
+				if timeout_ms >= 0:
+					return None
+			else:
+				return code
+
+	def expandKeyCode(self, code):
+		"""Expand a keycode into its individual components.
+		This is a stub to maintain backward compatibility.
+		Call brlapi.expandKeyCode(code) instead."""
+		return expandKeyCode(code)
+
+	def ignoreKeys(self, key_type, set):
+		"""Ignore some key presses from the braille keyboard.
+		See brlapi_ignoreKeys(3).
+		
+		This function asks the server to give the provided keys to brltty, rather than returning them to the application via brlapi_readKey().
+		
+		The given codes should be brltty commands (nul or "" was given to brlapi_enterTtyMode())"""
+		cdef int retval
+		cdef c_brlapi.brlapi_rangeType_t c_type
+		cdef c_brlapi.brlapi_keyCode_t *c_set
+		cdef long c_n
+		c_type = key_type
+		c_n = len(set)
+		c_set = <c_brlapi.brlapi_keyCode_t*>c_brlapi.malloc(c_n * sizeof(c_brlapi.brlapi_keyCode_t))
+		for i from 0 <= i < c_n:
+			c_set[i] = set[i]
+		with nogil:
+			retval = c_brlapi.brlapi__ignoreKeys(self.h, c_type, c_set, c_n)
+		c_brlapi.free(c_set)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def acceptKeys(self, key_type, set):
+		"""Accept some key presses from the braille keyboard.
+		See brlapi_ignoreKeys(3).
+		
+		This function asks the server to give the provided keys to the application, and not give them to brltty.
+
+		The given codes should be brltty commands (nul or "" was given to brlapi_enterTtyMode())"""
+		cdef int retval
+		cdef c_brlapi.brlapi_rangeType_t c_type
+		cdef c_brlapi.brlapi_keyCode_t *c_set
+		cdef long c_n
+		c_type = key_type
+		c_n = len(set)
+		c_set = <c_brlapi.brlapi_keyCode_t*>c_brlapi.malloc(c_n * sizeof(c_brlapi.brlapi_keyCode_t))
+		for i from 0 <= i < c_n:
+			c_set[i] = set[i]
+		with nogil:
+			retval = c_brlapi.brlapi__acceptKeys(self.h, c_type, c_set, c_n)
+		c_brlapi.free(c_set)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+	
+	def ignoreAllKeys(self):
+		"""Ignore all key presses from the braille keyboard.
+		See brlapi_ignoreAllKeys(3).
+		
+		This function asks the server to give all keys to brltty, rather than returning them to the application via brlapi_readKey()."""
+		cdef int retval
+		with nogil:
+			retval = c_brlapi.brlapi__ignoreAllKeys(self.h)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+	
+	def acceptAllKeys(self):
+		"""Accept all key presses from the braille keyboard.
+		See brlapi_acceptAllKeys(3).
+		
+		This function asks the server to give all keys to the application, and not give them to brltty.
+
+		Warning: after calling this function, make sure to call brlapi_ignoreKeys() for ignoring important keys like BRL_CMD_SWITCHVT_PREV/NEXT and such."""
+		cdef int retval
+		with nogil:
+			retval = c_brlapi.brlapi__acceptAllKeys(self.h)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def ignoreKeyRanges(self, keys):
+		"""Ignore some key presses from the braille keyboard.
+		See brlapi_ignoreKeyRanges(3).
+		
+		This function asks the server to give the provided key ranges to brltty, rather than returning them to the application via brlapi_readKey().
+		
+		The given codes should be raw keycodes (i.e. some driver name was given to brlapi_enterTtyMode()) """
+		cdef int retval
+		cdef c_brlapi.brlapi_range_t *c_keys
+		cdef long c_n
+		c_n = len(keys)
+		c_keys = <c_brlapi.brlapi_range_t*>c_brlapi.malloc(c_n * sizeof(c_brlapi.brlapi_range_t))
+		for i from 0 <= i < c_n:
+			c_keys[i].first = keys[i][0]
+			c_keys[i].last = keys[i][1]
+		with nogil:
+			retval = c_brlapi.brlapi__ignoreKeyRanges(self.h, c_keys, c_n)
+		c_brlapi.free(c_keys)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def acceptKeyRanges(self, keys):
+		"""Accept some key presses from the braille keyboard.
+		See brlapi_acceptKeyRanges(3).
+		
+		This function asks the server to return the provided key ranges (inclusive) to the application, and not give them to brltty.
+		
+		The given codes should be raw keycodes (i.e. some driver name was given to brlapi_enterTtyMode()) """
+		cdef int retval
+		cdef c_brlapi.brlapi_range_t *c_keys
+		cdef long c_n
+		c_n = len(keys)
+		c_keys = <c_brlapi.brlapi_range_t*>c_brlapi.malloc(c_n * sizeof(c_brlapi.brlapi_range_t))
+		for i from 0 <= i < c_n:
+			c_keys[i].first = keys[i][0]
+			c_keys[i].last = keys[i][1]
+		with nogil:
+			retval = c_brlapi.brlapi__acceptKeyRanges(self.h, c_keys, c_n)
+		c_brlapi.free(c_keys)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def enterRawMode(self, driver):
+		"""Switch to Raw mode
+		See brlapi_enterRawMode(3).
+		
+		* driver : Specifies the name of the driver for which the raw communication will be established"""
+		cdef int retval
+		cdef char *c_driver
+		if (type(driver) == unicode):
+			driver = driver.encode('ASCII')
+		c_driver = driver
+		with nogil:
+			retval = c_brlapi.brlapi__enterRawMode(self.h, c_driver)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def leaveRawMode(self):
+		"""leave Raw mode
+		See brlapi_leaveRawMode(3)."""
+		cdef int retval
+		with nogil:
+			retval = c_brlapi.brlapi__leaveRawMode(self.h)
+		if retval == -1:
+			raise OperationError()
+		else:
+			return retval
+
+	def getParameter(self, param, subparam = 0, flags = 0):
+		"""Get the value of a parameter.
+		See brlapi_getParameter(3).
+
+		This gets the current content of a parameter"""
+		cdef c_brlapi.brlapi_param_t c_param
+		cdef c_brlapi.brlapi_param_subparam_t c_subparam
+		cdef c_brlapi.brlapi_param_flags_t c_flags
+		cdef void *c_value
+		cdef size_t size
+		cdef ssize_t retval
+		cdef uint64_t *values64
+		cdef uint32_t *values32
+		cdef uint16_t *values16
+		cdef uint8_t *values8
+		cdef char *string
+
+		c_param = param
+		c_subparam = subparam
+		c_flags = flags
+
+		with nogil:
+			c_value = c_brlapi.brlapi__getParameterAlloc(self.h, c_param, c_subparam, c_flags, &size)
+		if c_value == NULL:
+			raise OperationError()
+
+		try:
+			ret = _parameterToPython(c_param, c_value, size)
+		except Exception as e:
+			c_brlapi.free(c_value)
+			raise(e)
+
+		c_brlapi.free(c_value)
+		return ret
+
+
+	def setParameter(self, param, subparam, flags, value):
+		"""Set the value of a parameter.
+		See brlapi_setParameter(3).
+
+		This sets the content of a parameter"""
+		cdef c_brlapi.brlapi_param_t c_param
+		cdef c_brlapi.brlapi_param_subparam_t c_subparam
+		cdef c_brlapi.brlapi_param_flags_t c_flags
+		cdef void *c_value
+		cdef uint64_t *values64
+		cdef uint32_t *values32
+		cdef uint16_t *values16
+		cdef uint8_t *values8
+		cdef char *string
+		cdef const c_brlapi.brlapi_param_properties_t *props
+
+		c_param = param
+		c_subparam = subparam
+		c_flags = flags
+
+		with nogil:
+			props = c_brlapi.brlapi_getParameterProperties(c_param)
+
+		if props == NULL:
+			raise OperationError()
+
+		if props.type == PARAM_TYPE_STRING:
+			if type(value) != unicode and type(value) != str:
+				raise ValueError("String value expected")
+
+		if props.type == PARAM_TYPE_BOOLEAN:
+			if props.isArray:
+				if type(value[0]) != bool:
+					raise ValueError("Boolean values expected")
+			else:
+				if type(value) != bool:
+					raise ValueError("Boolean value expected")
+
+		if props.type == PARAM_TYPE_UINT8 or \
+		   props.type == PARAM_TYPE_UINT16 or \
+		   props.type == PARAM_TYPE_UINT32:
+			if props.isArray:
+				if type(value[0]) != int:
+					raise ValueError("Integer values expected")
+			else:
+				if type(value) != int:
+					raise ValueError("Integer value expected")
+
+		if props.type == PARAM_TYPE_STRING:
+			if type(value) == unicode:
+				value = value.encode('UTF-8')
+			size = len(value)
+			c_value = <void*>c_brlapi.malloc(size)
+			values8 = <uint8_t *>c_value
+			string = value
+			c_brlapi.memcpy(<void*>values8,<void*>string,size)
+		elif props.type == PARAM_TYPE_BOOLEAN or props.type == PARAM_TYPE_UINT8:
+			if props.isArray:
+				size = 1 * len(value)
+				c_value = <void*>c_brlapi.malloc(size)
+				values8 = <uint8_t *>c_value
+				for i in len(value):
+					values8[i] = value[i]
+			else:
+				size = 1
+				c_value = <void*>c_brlapi.malloc(size)
+				values8 = <uint8_t *>c_value
+				values8[0] = value
+		elif props.type == PARAM_TYPE_UINT16:
+			if props.isArray:
+				size = 2 * len(value)
+				c_value = <void*>c_brlapi.malloc(size)
+				values16 = <uint16_t *>c_value
+				for i in len(value):
+					values16[i] = value[i]
+			else:
+				size = 2
+				c_value = <void*>c_brlapi.malloc(size)
+				values16 = <uint16_t *>c_value
+				values16[0] = value
+		elif props.type == PARAM_TYPE_UINT32:
+			if props.isArray:
+				size = 4 * len(value)
+				c_value = <void*>c_brlapi.malloc(size)
+				values32 = <uint32_t *>c_value
+				for i in len(value):
+					values32[i] = value[i]
+			else:
+				size = 4
+				c_value = <void*>c_brlapi.malloc(size)
+				values32 = <uint32_t *>c_value
+				values32[0] = value
+		elif props.type == PARAM_TYPE_UINT64:
+			if props.isArray:
+				size = 4 * len(value)
+				c_value = <void*>c_brlapi.malloc(size)
+				values64 = <uint64_t *>c_value
+				for i in len(value):
+					values64[i] = value[i]
+			else:
+				size = 8
+				c_value = <void*>c_brlapi.malloc(size)
+				values64 = <uint64_t *>c_value
+				values64[0] = value
+		else:
+			raise ValueError("Unsupported parameter type")
+
+		with nogil:
+			retval = c_brlapi.brlapi__setParameter(self.h, c_param, c_subparam, c_flags, c_value, size)
+		if retval == -1:
+			c_brlapi.free(c_value)
+			raise OperationError()
+		c_brlapi.free(c_value)
+
+	def watchParameter(self, param, subparam, flags, func):
+		"""Set a parameter change callback.
+		See brlapi_watchParameter(3).
+
+		This registers a parameter change callback: whenever the given
+		parameter changes, the given function is called.
+
+		This returns an entry object, to be passed to unwatchParameter."""
+		cdef c_brlapi.brlapi_param_t c_param
+		cdef c_brlapi.brlapi_param_subparam_t c_subparam
+		cdef c_brlapi.brlapi_param_flags_t c_flags
+		cdef c_brlapi.brlapi_python_paramCallbackDescriptor_t *descr
+
+		c_param = param
+		c_subparam = subparam
+		c_flags = flags
+
+		def cfunc(param):
+			cdef c_brlapi.brlapi_python_callbackData_t *callbackData
+			callbackData = <c_brlapi.brlapi_python_callbackData_t*> <uintptr_t> param
+
+			parameter = callbackData.parameter
+			subparam = callbackData.subparam
+			flags = callbackData.flags
+			data = _parameterToPython(callbackData.parameter, callbackData.data, callbackData.len)
+
+			func(parameter, subparam, flags, data)
+
+		descr = c_brlapi.brlapi_python_watchParameter(self.h, c_param, c_subparam, c_flags, cfunc)
+		return <uintptr_t>descr
+
+	def unwatchParameter(self, entry):
+		"""Clear a parameter change callback.
+		See brlapi_unwatchParameter(3).
+
+		This unregisters a parameter change callback: the callback
+		function previously registered with brlapi_watchParameter
+		will not be called any more."""
+		cdef uintptr_t descr
+
+		descr = entry
+		c_brlapi.brlapi_python_unwatchParameter(self.h, <c_brlapi.brlapi_python_paramCallbackDescriptor_t *>descr)
+
+	def pause(self, timeout_ms):
+		"""Wait until an event is received from the BrlAPI server.
+		See brlapi_pause(3).
+		"""
+		c_brlapi.brlapi__pause(self.h, timeout_ms)
+
+	def sync(self):
+		"""Synchronize against any pending exception, and raise it.
+		See brlapi_sync(3).
+		"""
+		cdef int retval
+
+		with nogil:
+			retval = c_brlapi.brlapi__sync(self.h)
+		if retval == -1:
+			raise OperationError()
diff --git a/Bindings/Python/c_brlapi.pxd b/Bindings/Python/c_brlapi.pxd
new file mode 100644
index 0000000..14d1994
--- /dev/null
+++ b/Bindings/Python/c_brlapi.pxd
@@ -0,0 +1,175 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# File binding C functions
+
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t
+
+from cpython.ref cimport PyObject
+
+cdef extern from "sys/types.h":
+	ctypedef Py_ssize_t size_t
+	ctypedef Py_ssize_t ssize_t
+
+cdef extern from "Programs/brlapi_protocol.h":
+	int BRLAPI_MAXPACKETSIZE
+
+cdef extern from "Programs/brlapi.h":
+	ctypedef struct brlapi_connectionSettings_t:
+		char *auth
+		char *host
+
+	ctypedef struct brlapi_writeArguments_t:
+		int displayNumber
+		unsigned int regionBegin
+		int regionSize
+		char *text
+		int textSize
+		unsigned char *andMask
+		unsigned char *orMask
+		int cursor
+		char *charset
+
+	ctypedef struct brlapi_expandedKeyCode_t:
+		unsigned int type
+		unsigned int command
+		unsigned int argument
+		unsigned int flags
+
+	ctypedef struct brlapi_describedKeyCode_t:
+		char *type
+		char *command
+		unsigned int argument
+		unsigned int flags
+		char *flag[32]
+		brlapi_expandedKeyCode_t values
+
+	ctypedef struct brlapi_error_t:
+		int brlerrno
+		int libcerrno
+		int gaierrno
+		char *errfun
+
+	brlapi_error_t brlapi_error
+
+	ctypedef struct brlapi_handle_t
+
+	ctypedef unsigned long long brlapi_keyCode_t
+
+	void brlapi_getLibraryVersion(int *major, int *minor, int *revision) nogil
+	size_t brlapi_getHandleSize()
+	void brlapi__closeConnection(brlapi_handle_t *)
+	int brlapi__openConnection(brlapi_handle_t *, brlapi_connectionSettings_t*, brlapi_connectionSettings_t*) nogil
+
+	int brlapi__getDisplaySize(brlapi_handle_t *, unsigned int*x, unsigned int *y) nogil
+	int brlapi__getDriverName(brlapi_handle_t *, char*, int) nogil
+	int brlapi__getModelIdentifier(brlapi_handle_t *, char*, int) nogil
+
+	int brlapi__enterTtyMode(brlapi_handle_t *, int, char*) nogil
+	int brlapi__enterTtyModeWithPath(brlapi_handle_t *, int *, int, char*) nogil
+	int brlapi__leaveTtyMode(brlapi_handle_t *) nogil
+	int brlapi__setFocus(brlapi_handle_t *, int) nogil
+
+	int brlapi__write(brlapi_handle_t *, brlapi_writeArguments_t*) nogil
+	int brlapi__writeDots(brlapi_handle_t *, unsigned char*) nogil
+	int brlapi__writeText(brlapi_handle_t *, int, char*) nogil
+
+	ctypedef enum brlapi_rangeType_t:
+		brlapi_rangeType_all
+	
+	ctypedef struct brlapi_range_t:
+		brlapi_keyCode_t first
+		brlapi_keyCode_t last
+
+	int brlapi__ignoreKeys(brlapi_handle_t *, brlapi_rangeType_t, brlapi_keyCode_t *, unsigned int) nogil
+	int brlapi__acceptKeys(brlapi_handle_t *, brlapi_rangeType_t, brlapi_keyCode_t *, unsigned int) nogil
+	int brlapi__ignoreAllKeys(brlapi_handle_t *) nogil
+	int brlapi__acceptAllKeys(brlapi_handle_t *) nogil
+	int brlapi__ignoreKeyRanges(brlapi_handle_t *, brlapi_range_t *, unsigned int) nogil
+	int brlapi__acceptKeyRanges(brlapi_handle_t *, brlapi_range_t *, unsigned int) nogil
+	int brlapi__readKey(brlapi_handle_t *, int, brlapi_keyCode_t*) nogil
+	int brlapi__readKeyWithTimeout(brlapi_handle_t *, int, brlapi_keyCode_t*) nogil
+	int brlapi_expandKeyCode(brlapi_keyCode_t, brlapi_expandedKeyCode_t *)
+	int brlapi_describeKeyCode(brlapi_keyCode_t, brlapi_describedKeyCode_t *)
+
+	int brlapi__enterRawMode(brlapi_handle_t *, char*) nogil
+	int brlapi__leaveRawMode(brlapi_handle_t *) nogil
+	int brlapi__recvRaw(brlapi_handle_t *, void*, int)
+	int brlapi__sendRaw(brlapi_handle_t *, void*, int)
+
+	ctypedef int brlapi_param_t
+	ctypedef uint64_t brlapi_param_subparam_t
+	ctypedef uint32_t brlapi_param_flags_t
+	ctypedef void *brlapi_paramCallbackDescriptor_t
+
+	ctypedef int brlapi_param_type_t
+	ctypedef struct brlapi_param_properties_t:
+		brlapi_param_type_t type
+		uint16_t arraySize
+		uint16_t isArray
+		uint16_t canRead
+		uint16_t canWrite
+		uint16_t canWatch
+		uint16_t abiPadding1
+		uint16_t hasSubparam
+
+	void *brlapi__getParameterAlloc(brlapi_handle_t *, brlapi_param_t, unsigned long long, brlapi_param_flags_t, size_t *) nogil
+	int brlapi__setParameter(brlapi_handle_t *, brlapi_param_t, unsigned long long, brlapi_param_flags_t, void*, size_t) nogil
+	const brlapi_param_properties_t *brlapi_getParameterProperties(brlapi_param_t parameter) nogil
+	brlapi_paramCallbackDescriptor_t brlapi__watchParameter(brlapi_handle_t *, brlapi_param_t, uint64_t, brlapi_param_flags_t, brlapi_paramCallback_t, void *, void*, size_t)
+	int brlapi__unwatchParameter(brlapi_handle_t *, brlapi_paramCallbackDescriptor_t)
+
+	int brlapi__pause(brlapi_handle_t *handle, int timeout_ms) nogil
+	int brlapi__sync(brlapi_handle_t *handle) nogil
+
+	brlapi_error_t* brlapi_error_location()
+	size_t brlapi_strerror_r(brlapi_error_t*, char *buf, size_t buflen)
+	brlapi_keyCode_t BRLAPI_KEY_MAX
+	brlapi_keyCode_t BRLAPI_KEY_FLAGS_MASK
+	brlapi_keyCode_t BRLAPI_KEY_TYPE_MASK
+	brlapi_keyCode_t BRLAPI_KEY_CODE_MASK
+	brlapi_keyCode_t BRLAPI_KEY_CMD_BLK_MASK
+	brlapi_keyCode_t BRLAPI_KEY_CMD_ARG_MASK
+
+cdef extern from "bindings.h":
+	brlapi_writeArguments_t brlapi_writeArguments_initialized
+	char *brlapi_protocolException()
+	void brlapi_protocolExceptionInit(brlapi_handle_t *)
+
+	ctypedef struct brlapi_python_paramCallbackDescriptor_t:
+		brlapi_paramCallbackDescriptor_t brlapi_descr
+		PyObject *callback
+
+	ctypedef struct brlapi_python_callbackData_t:
+		brlapi_param_t parameter
+		brlapi_param_subparam_t subparam
+		brlapi_param_flags_t flags
+		const void *data
+		size_t len
+
+	brlapi_python_paramCallbackDescriptor_t *brlapi_python_watchParameter(brlapi_handle_t *, brlapi_param_t, uint64_t, int, object) except NULL
+	int brlapi_python_unwatchParameter(brlapi_handle_t *, brlapi_python_paramCallbackDescriptor_t *)
+
+cdef extern from "stdlib.h":
+	void *malloc(size_t)
+	void free(void*)
+	char *strdup(char *)
+
+cdef extern from "string.h":
+	void *memcpy(void *, void *, size_t)
diff --git a/Bindings/Python/constants.awk b/Bindings/Python/constants.awk
new file mode 100644
index 0000000..2b65edc
--- /dev/null
+++ b/Bindings/Python/constants.awk
@@ -0,0 +1,106 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+function brlCommand(name, symbol, value, help) {
+  writeCommandDefinition(name, value, help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  if (name == "PASSCHAR") return
+  if (name == "PASSKEY") return
+
+  writeCommandDefinition(name, "(" value" << KEY_CMD_BLK_SHIFT)", help)
+}
+
+function brlKey(name, symbol, value, help) {
+}
+
+function brlFlag(name, symbol, value, help) {
+  if (value ~ /^0[xX][0-9a-fA-F]+0000$/) {
+    value = tolower(substr(value, 1, length(value)-4))
+    if (name ~ /^CHAR_/) {
+      name = substr(name, 6)
+    } else {
+      value = value "00"
+    }
+  } else if (value ~ /^\(/) {
+    gsub("BRL_FLG_", "KEY_FLG_", value)
+  } else {
+    return
+  }
+  writePythonAssignment("KEY_FLG_" name, value, help)
+}
+
+function brlDot(number, symbol, value, help) {
+  writePythonAssignment("DOT" number, value, help)
+}
+
+function apiConstant(name, symbol, value, help) {
+  writePythonAssignment(name, value, help)
+}
+
+function apiMask(name, symbol, value, help) {
+  writePythonAssignment("KEY_" name, "c_brlapi." symbol, help)
+}
+
+function apiShift(name, symbol, value, help) {
+  value = hexadecimalValue(value)
+  writePythonAssignment("KEY_" name, value, help)
+}
+
+function apiType(name, symbol, value, help) {
+  value = hexadecimalValue(value)
+  writePythonAssignment("KEY_TYPE_" name, value, help)
+}
+
+function apiKey(name, symbol, value, help) {
+  value = hexadecimalValue(value)
+
+  if (name == "FUNCTION") {
+    for (i=0; i<35; ++i) {
+      key = "F" (i+1)
+      writeKeyDefinition(key, "(" value " + " i ")", "the " key " key")
+    }
+  } else {
+    writeKeyDefinition(name, value, help)
+  }
+}
+
+function apiRangeType(name, symbol, value, help) {
+  writePythonAssignment("rangeType_" name, value, help)
+}
+
+function writeCommandDefinition(name, value, help) {
+  writePythonAssignment("KEY_CMD_" name, value, help)
+}
+
+function writeKeyDefinition(name, value, help) {
+  writePythonAssignment("KEY_SYM_" name, value, help)
+}
+
+function writePythonAssignment(name, value, help) {
+  if (length(help) > 0) print "## " help
+  print name " = " value
+}
+
+function hexadecimalValue(value) {
+  value = tolower(value)
+  return value
+}
+
diff --git a/Bindings/Python/mkdoc.py b/Bindings/Python/mkdoc.py
new file mode 100644
index 0000000..602b7d4
--- /dev/null
+++ b/Bindings/Python/mkdoc.py
@@ -0,0 +1,26 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+from distutils.util import get_platform
+import pydoc
+import sys
+
+if __name__ == "__main__":
+    sys.path.insert(1, 'build/lib.' + get_platform() + '-' + sys.version[0:3])
+    pydoc.writedoc('brlapi')
diff --git a/Bindings/Python/setup.py.in b/Bindings/Python/setup.py.in
new file mode 100644
index 0000000..4860385
--- /dev/null
+++ b/Bindings/Python/setup.py.in
@@ -0,0 +1,73 @@
+# @configure_input@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+from setuptools import setup, Extension
+
+if __name__ == "__main__":
+    brlapi = Extension("brlapi",
+        language = "c",
+
+        sources = [
+            "brlapi.auto.c",
+            "@srcdir@/bindings.c"
+        ],
+
+        include_dirs = [
+            "@builddir@",
+            "@srcdir@",
+            "@top_builddir@/Programs",
+            "@top_srcdir@/Programs",
+            "@top_builddir@/",
+            "@top_srcdir@/"
+        ],
+
+        library_dirs = [
+            "./@top_builddir@/Programs"
+        ],
+
+        libraries = [
+            "brlapi",
+            "pthread"
+        ],
+
+        define_macros = [
+        ],
+
+        extra_compile_args = "@CYTHON_CFLAGS@".split()
+    )
+    
+    setup(
+        zip_safe = False,
+
+        name = "Brlapi",
+        version = "@api_release@",
+        description = "BrlAPI Client Bindings for Python",
+
+        author = "Alexis Robert",
+        author_email = "alexissoft@free.fr",
+
+        maintainer = "The BRLTTY Developers",
+        maintainer_email = "@PACKAGE_BUGREPORT@",
+
+        license = "LGPL 2.1",
+        url = "@PACKAGE_URL@",
+
+        ext_modules = [brlapi]
+    )
diff --git a/Bindings/Tcl/Makefile.in b/Bindings/Tcl/Makefile.in
new file mode 100644
index 0000000..7236573
--- /dev/null
+++ b/Bindings/Tcl/Makefile.in
@@ -0,0 +1,60 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include $(SRC_TOP)bindings.mk
+
+TCLSH = @TCLSH@
+TCL_OK = @TCL_OK@
+TCL_CPPFLAGS = @TCL_CPPFLAGS@
+TCL_LIBS = @TCL_LIBS@
+TCL_DIR = @TCL_DIR@
+
+TCL_API_DIR = $(INSTALL_ROOT)$(TCL_DIR)/$(API_NAME)-$(API_RELEASE)
+TCL_API_LIB = $(LIB_PFX)$(API_NAME)_tcl.$(LIB_EXT)
+TCL_INDEX_SCRIPT = mkindex
+
+all: $(TCL_API_LIB) | make-index-script
+
+$(TCL_API_LIB): bindings.$O | $(API_NAME)
+	$(MKLIB:<name>=$(TCL_API_LIB).$(API_VERSION)) $@ bindings.$O $(TCL_LIBS) $(API_LDFLAGS)
+
+PARAMETERS_HEADER = parameters.auto.h
+CONSTANTS_HEADERS = $(PARAMETERS_HEADER)
+
+$(CONSTANTS_HEADERS): $(CONSTANTS_DEPENDENCIES)
+	$(AWK) -v parametersHeader=$(PARAMETERS_HEADER) $(CONSTANTS_ARGUMENTS)
+
+bindings.$O:
+	$(CC) $(TCL_CPPFLAGS) $(LIBCFLAGS) -o $@ -c $(SRC_DIR)/bindings.c
+
+make-index-script:
+	echo >$(TCL_INDEX_SCRIPT) 'pkg_mkIndex $(TCL_API_DIR) $(TCL_API_LIB)'
+
+clean::
+	-rm -f -- $(TCL_API_LIB) $(TCL_INDEX_SCRIPT)
+	-rm -f -- $(CONSTANTS_HEADERS)
+
+install: all
+	$(INSTALL_DIRECTORY) $(TCL_API_DIR)
+	$(INSTALL_PROGRAM) $(TCL_API_LIB) $(TCL_API_DIR)
+	$(TCLSH) ./$(TCL_INDEX_SCRIPT)
+
+uninstall:
+	-rm -f $(TCL_API_DIR)/$(TCL_API_LIB)
+	-rm -f $(TCL_API_DIR)/pkgIndex.tcl
+	-[ -d $(TCL_API_DIR) ] && rmdir $(TCL_API_DIR)
+
diff --git a/Bindings/Tcl/apishell b/Bindings/Tcl/apishell
new file mode 100755
index 0000000..c8dd822
--- /dev/null
+++ b/Bindings/Tcl/apishell
@@ -0,0 +1,21 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec tclsh "${@}"
+exit "${?}"
diff --git a/Bindings/Tcl/apitest b/Bindings/Tcl/apitest
new file mode 100755
index 0000000..fe21021
--- /dev/null
+++ b/Bindings/Tcl/apitest
@@ -0,0 +1,21 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../../apitest.sh"
+exec tclsh "${programDirectory}/${programName}.tcl" "${@}"
+exit "${?}"
diff --git a/Bindings/Tcl/apitest.tcl b/Bindings/Tcl/apitest.tcl
new file mode 100755
index 0000000..b7c0f85
--- /dev/null
+++ b/Bindings/Tcl/apitest.tcl
@@ -0,0 +1,126 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname $argv0] prologue.tcl]
+
+proc putProperties {label properties} {
+   puts stdout "$label: $properties"
+}
+
+proc formatProperty {name value} {
+   return "$name=$value"
+}
+
+proc formatValues {values args} {
+   set result ""
+   set delimiter ""
+
+   foreach name $args value $values {
+      append result "$delimiter[formatProperty $name $value]"
+      set delimiter " "
+   }
+
+   return $result
+}
+
+proc ttyShowKeyCode {session code} {
+   set properties [list]
+
+   brlapi describeKeyCode $code description
+   set description(code) [format "0X%X" $code]
+
+   foreach property {flags} {
+      set description($property) [join $description($property) ","]
+   }
+
+   foreach {property name} {
+      code     code
+      type     type
+      command  cmd
+      argument arg
+      flags    flg
+   } {
+      lappend properties [formatProperty $name $description($property)]
+      unset description($property)
+   }
+
+   foreach property [lsort [array names description]] {
+      lappend properties [formatProperty $property $description($property)]
+   }
+
+   set text [join $properties " "]
+   $session write -text $text
+   putProperties Key $text
+}
+
+proc ttyShowKeys {session timeout} {
+   $session write -text "press keys (timeout is $timeout seconds)"
+
+   while {[string length [set code [$session readKeyWithTimeout $timeout]]] > 0} {
+      ttyShowKeyCode $session $code
+   }
+}
+
+proc mainProgram {} {
+   set optionDefinitions {
+      {host untyped.server  "which server to connect to"}
+      {auth untyped.schemes "which authorization schemes to use"}
+      {tty  untyped.number  "which virtual terminal to claim"}
+   }
+
+   processProgramArguments optionValues $optionDefinitions
+
+   putProperties "BrlAPI Version" [brlapi getVersionString]
+
+   set connectionSettings [list]
+   if {[info exists optionValues(host)]} {
+      lappend connectionSettings -host $optionValues(host)
+   }
+   if {[info exists optionValues(auth)]} {
+      lappend connectionSettings -auth $optionValues(auth)
+   }
+
+   if {[catch [list eval brlapi openConnection $connectionSettings] session] == 0} {
+      putProperties "Session Command" $session
+      putProperties "Server Host" [$session getHost]
+      putProperties "Authorization Schemes" [$session getAuth]
+      putProperties "File Descriptor" [$session getFileDescriptor]
+      putProperties "Driver Name" [$session getDriverName]
+      putProperties "Model Identifier" [$session getModelIdentifier]
+      putProperties "Display Size" [formatValues [$session getDisplaySize] width height]
+
+      if {[info exists optionValues(tty)]} {
+         if {[catch [list $session enterTtyMode -tty $optionValues(tty)] tty] == 0} {
+            putProperties "TTY Number" $tty
+            ttyShowKeys $session 10
+            $session leaveTtyMode
+         } else {
+            writeProgramMessage "invalid tty: $tty"
+         }
+         unset tty
+      }
+
+      $session closeConnection
+   } else {
+      writeProgramMessage "connection failure: $session"
+   }
+   unset session
+}
+
+mainProgram
+exit 0
diff --git a/Bindings/Tcl/bindings.c b/Bindings/Tcl/bindings.c
new file mode 100644
index 0000000..a85280a
--- /dev/null
+++ b/Bindings/Tcl/bindings.c
@@ -0,0 +1,1671 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define BRLAPI_NO_DEPRECATED
+#define BRLAPI_NO_SINGLE_SESSION
+#include "brlapi.h"
+
+#include <tcl.h>
+#include "brl_dots.h"
+
+#define allocateMemory(size) ((void *)ckalloc((size)))
+#define deallocateMemory(address) ckfree((void *)(address))
+
+#define TEST_TCL_OK(expression) \
+do { \
+  int tclResult = (expression); \
+  if (tclResult != TCL_OK) return tclResult; \
+} while (0)
+
+static int
+setArrayElement (Tcl_Interp *interp, const char *array, const char *element, Tcl_Obj *value) {
+  if (!value) return TCL_ERROR;
+  Tcl_IncrRefCount(value);
+  Tcl_Obj *result = Tcl_SetVar2Ex(interp, array, element, value, TCL_LEAVE_ERR_MSG);
+  Tcl_DecrRefCount(value);
+  return result? TCL_OK: TCL_ERROR;
+}
+
+#define SET_ARRAY_ELEMENT(element, object) \
+  TEST_TCL_OK(setArrayElement(interp, array, element, object))
+
+typedef struct {
+  brlapi_connectionSettings_t settings;
+  brlapi_handle_t *handle;
+  brlapi_fileDescriptor fileDescriptor;
+
+  unsigned int displayWidth;
+  unsigned int displayHeight;
+
+  Tcl_Interp *tclInterpreter;
+  Tcl_Command tclCommand;
+} BrlapiSession;
+
+static void
+setIntResult (Tcl_Interp *interp, int value) {
+  Tcl_SetIntObj(Tcl_GetObjResult(interp), value);
+}
+
+static void
+setWideIntResult (Tcl_Interp *interp, Tcl_WideInt value) {
+  Tcl_SetWideIntObj(Tcl_GetObjResult(interp), value);
+}
+
+static void
+setStringResult (Tcl_Interp *interp, const char *string, int length) {
+  Tcl_SetStringObj(Tcl_GetObjResult(interp), string, length);
+}
+
+static void
+setStringsResult (Tcl_Interp *interp, ...) {
+  Tcl_ResetResult(interp);
+
+  va_list arguments;
+  va_start(arguments, interp);
+  Tcl_AppendStringsToObjVA(Tcl_GetObjResult(interp), arguments);
+  va_end(arguments);
+}
+
+static void
+setByteArrayResult (Tcl_Interp *interp, const unsigned char *bytes, int count) {
+  Tcl_SetByteArrayObj(Tcl_GetObjResult(interp), bytes, count);
+}
+
+static void
+setBrlapiError (Tcl_Interp *interp) {
+  size_t size = brlapi_strerror_r(&brlapi_error, NULL, 0);
+  char text[size+1];
+  const char *name;
+  int number;
+
+  brlapi_strerror_r(&brlapi_error, text, sizeof(text));
+
+  switch (brlapi_error.brlerrno) {
+    case BRLAPI_ERROR_LIBCERR:
+      name = "LIBC";
+      number = brlapi_error.libcerrno;
+      break;
+
+    case BRLAPI_ERROR_GAIERR:
+      name = "GAI";
+      number = brlapi_error.gaierrno;
+      break;
+
+    default:
+      name = "BRL";
+      number = brlapi_error.brlerrno;
+      break;
+  }
+
+  {
+    Tcl_Obj *const elements[] = {
+      Tcl_NewStringObj("BrlAPI", -1),
+      Tcl_NewStringObj(name, -1),
+      Tcl_NewIntObj(number),
+      Tcl_NewStringObj(text, -1)
+    };
+
+    Tcl_SetObjErrorCode(interp, Tcl_NewListObj(4, elements));
+  }
+
+  Tcl_Obj *result = Tcl_GetObjResult(interp);
+  Tcl_SetStringObj(result, "BrlAPI error: ", -1);
+  Tcl_AppendToObj(result, text, -1);
+}
+
+#define TEST_BRLAPI_OK(expression) \
+do { \
+  if ((expression) == -1) { \
+    setBrlapiError(interp); \
+    return TCL_ERROR; \
+  } \
+} while (0)
+
+static int
+getDisplaySize (Tcl_Interp *interp, BrlapiSession *session) {
+  TEST_BRLAPI_OK(brlapi__getDisplaySize(session->handle, &session->displayWidth, &session->displayHeight));
+  return TCL_OK;
+}
+
+static int
+getCellCount (
+  Tcl_Interp *interp, BrlapiSession *session, unsigned int *count
+) {
+  if (!(session->displayWidth && session->displayHeight)) {
+    TEST_TCL_OK(getDisplaySize(interp, session));
+  }
+
+  *count = session->displayWidth * session->displayHeight;
+  return TCL_OK;
+}
+
+#define FUNCTION_HANDLER_RETURN int
+#define FUNCTION_HANDLER_PARAMETERS (Tcl_Interp *interp, Tcl_Obj *const objv[], int objc, void *data)
+#define FUNCTION_HANDLER_NAME(command,function) functionHandler_##command##_##function
+#define FUNCTION_HANDLER(command,function) \
+  static FUNCTION_HANDLER_RETURN \
+  FUNCTION_HANDLER_NAME(command, function) \
+  FUNCTION_HANDLER_PARAMETERS
+typedef FUNCTION_HANDLER_RETURN (*FunctionHandler) FUNCTION_HANDLER_PARAMETERS;
+
+typedef struct {
+  const char *name;
+  FunctionHandler handler;
+} FunctionEntry;
+
+#define FUNCTION(command,function) \
+  { .name=#function, .handler = FUNCTION_HANDLER_NAME(command, function) }
+
+#define BEGIN_FUNCTIONS static const FunctionEntry functions[] = {
+#define END_FUNCTIONS { .name=NULL }};
+
+static int
+testArgumentCount (
+  Tcl_Interp *interp, Tcl_Obj *const objv[], int objc,
+  int start, int required, int optional,
+  const char *syntax
+) {
+  int minimum = start + required;
+  int maximum = (optional < 0)? objc: (minimum + optional);
+
+  if ((objc >= minimum) && (objc <= maximum)) return TCL_OK;
+  Tcl_WrongNumArgs(interp, start, objv, syntax);
+  return TCL_ERROR;
+}
+
+#define TEST_ARGUMENT_COUNT(start,required,optional,syntax) \
+  TEST_TCL_OK(testArgumentCount(interp, objv, objc, (start), (required), (optional), (syntax)))
+
+#define TEST_FUNCTION_ARGUMENTS(required,optional,syntax) \
+  TEST_ARGUMENT_COUNT(2, (required), (optional), (syntax))
+
+#define TEST_FUNCTION_NO_ARGUMENTS() TEST_FUNCTION_ARGUMENTS(0, 0, NULL)
+
+static int
+invokeFunction (Tcl_Interp *interp, Tcl_Obj *const objv[], int objc, const FunctionEntry *functions, void *data) {
+  TEST_ARGUMENT_COUNT(1, 1, -1, "<function> [<arg> ...]");
+  const FunctionEntry *function;
+
+  {
+    int index;
+    TEST_TCL_OK(Tcl_GetIndexFromObjStruct(interp, objv[1], functions, sizeof(*functions), "function", 0, &index));
+    function = functions + index;
+  }
+
+  return function->handler(interp, objv, objc, data);
+}
+
+#define OPTION_HANDLER_RETURN int
+#define OPTION_HANDLER_PARAMETERS (Tcl_Interp *interp, Tcl_Obj *const objv[], void *data)
+#define OPTION_HANDLER_NAME(command,function,option) optionHandler_##command##_##function##_##option
+#define OPTION_HANDLER(command,function,option) \
+  static OPTION_HANDLER_RETURN \
+  OPTION_HANDLER_NAME(command, function, option) \
+  OPTION_HANDLER_PARAMETERS
+typedef OPTION_HANDLER_RETURN (*OptionHandler) OPTION_HANDLER_PARAMETERS;
+
+typedef struct {
+  const char *name;
+  OptionHandler handler;
+  int operands;
+  const char *syntax;
+} OptionEntry;
+
+static int
+processOptions (
+  Tcl_Interp *interp, void *data,
+  Tcl_Obj *const objv[], int objc,
+  int start, int *consumed,
+  const OptionEntry *options
+) {
+  *consumed = objc;
+  objv += start;
+  objc -= start;
+
+  while (objc > 0) {
+    Tcl_Obj *name = objv[0];
+
+    {
+      const char *string = Tcl_GetString(name);
+      if (!string) return TCL_ERROR;
+      if (*string != '-') break;
+    }
+
+    int index;
+    TEST_TCL_OK(Tcl_GetIndexFromObjStruct(interp, name, options, sizeof(*options), "option", 0, &index));
+    const OptionEntry *option = &options[index];
+
+    int count = option->operands;
+    TEST_ARGUMENT_COUNT(1, count, -1, option->syntax);
+    TEST_TCL_OK(option->handler(interp, objv, data));
+
+    count += 1;
+    objv += count;
+    objc -= count;
+  }
+
+  *consumed -= objc;
+  return TCL_OK;
+}
+
+#define BEGIN_OPTIONS { static const OptionEntry optionTable[] = {
+#define END_OPTIONS(start,required,optional,syntax) \
+  , {.name = NULL} \
+  }; \
+  int consumed; \
+  TEST_TCL_OK(processOptions(interp, &options, objv, objc, (start), &consumed, optionTable)); \
+  objv += consumed; \
+  objc -= consumed; \
+  TEST_ARGUMENT_COUNT(0, (required), (optional), (syntax)); \
+}
+#define OPTION(command,function,option) \
+  .name = "-" #option, .handler = OPTION_HANDLER_NAME(command, function, option)
+#define OPERANDS(count,text) \
+  .operands = (count), .syntax = ((count)? (text): NULL)
+
+static int
+getSessionStringProperty (
+  Tcl_Interp *interp, BrlapiSession *session,
+  int BRLAPI_STDCALL (*getProperty) (brlapi_handle_t *handle, char *buffer, size_t size)
+) {
+  size_t size = 0X10;
+
+  while (1) {
+    char buffer[size];
+    int result = getProperty(session->handle, buffer, size);
+    TEST_BRLAPI_OK(result);
+
+    if (result <= size) {
+      setStringResult(interp, buffer, result-1);
+      return TCL_OK;
+    }
+
+    size = result;
+  }
+}
+
+FUNCTION_HANDLER(session, closeConnection) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+  Tcl_DeleteCommandFromToken(interp, session->tclCommand);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, enterRawMode) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<driver>");
+
+  const char *driver = Tcl_GetString(objv[2]);
+  if (!driver) return TCL_ERROR;
+
+  TEST_BRLAPI_OK(brlapi__enterRawMode(session->handle, driver));
+  return TCL_OK;
+}
+
+typedef struct {
+  int tty;
+  const char *driver;
+} EnterTtyModeOptions;
+
+OPTION_HANDLER(session, enterTtyMode, commands) {
+  EnterTtyModeOptions *options = data;
+  options->driver = NULL;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, enterTtyMode, keys) {
+  EnterTtyModeOptions *options = data;
+  return (options->driver = Tcl_GetString(objv[1]))? TCL_OK: TCL_ERROR;
+}
+
+OPTION_HANDLER(session, enterTtyMode, tty) {
+  EnterTtyModeOptions *options = data;
+  Tcl_Obj *obj = objv[1];
+
+  const char *string = Tcl_GetString(obj);
+  if (!string) return TCL_ERROR;
+
+  if (strcmp(string, "default") == 0) {
+    options->tty = BRLAPI_TTY_DEFAULT;
+  } else {
+    TEST_TCL_OK(Tcl_GetIntFromObj(interp, obj, &options->tty));
+  }
+
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, enterTtyMode) {
+  BrlapiSession *session = data;
+
+  EnterTtyModeOptions options = {
+    .tty = BRLAPI_TTY_DEFAULT,
+    .driver = NULL
+  };
+
+  BEGIN_OPTIONS
+    { OPTION(session, enterTtyMode, commands),
+      OPERANDS(0, "")
+    },
+
+    { OPTION(session, enterTtyMode, keys),
+      OPERANDS(1, "<driver>")
+    },
+
+    { OPTION(session, enterTtyMode, tty),
+      OPERANDS(1, "{default | <number>}")
+    }
+  END_OPTIONS(2, 0, 0, "")
+
+  {
+    int result = brlapi__enterTtyMode(session->handle, options.tty, options.driver);
+    TEST_BRLAPI_OK(result);
+
+    setIntResult(interp, result);
+    return TCL_OK;
+  }
+}
+
+typedef struct {
+  Tcl_Obj *path;
+  const char *driver;
+} EnterTtyModeWithPathOptions;
+
+OPTION_HANDLER(session, enterTtyModeWithPath, commands) {
+  EnterTtyModeWithPathOptions *options = data;
+  options->driver = NULL;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, enterTtyModeWithPath, keys) {
+  EnterTtyModeWithPathOptions *options = data;
+  return (options->driver = Tcl_GetString(objv[1]))? TCL_OK: TCL_ERROR;
+}
+
+OPTION_HANDLER(session, enterTtyModeWithPath, path) {
+  EnterTtyModeWithPathOptions *options = data;
+  options->path = objv[1];
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, enterTtyModeWithPath) {
+  BrlapiSession *session = data;
+
+  EnterTtyModeWithPathOptions options = {
+    .path = NULL,
+    .driver = NULL
+  };
+
+  BEGIN_OPTIONS
+    { OPTION(session, enterTtyModeWithPath, commands),
+      OPERANDS(0, "")
+    },
+
+    { OPTION(session, enterTtyModeWithPath, keys),
+      OPERANDS(1, "<driver>")
+    },
+
+    { OPTION(session, enterTtyModeWithPath, path),
+      OPERANDS(1, "<list>")
+    }
+  END_OPTIONS(2, 0, 0, "")
+
+  Tcl_Obj **elements;
+  int count;
+
+  if (options.path) {
+    TEST_TCL_OK(Tcl_ListObjGetElements(interp, options.path, &count, &elements));
+  } else {
+    elements = NULL;
+    count = 0;
+  }
+
+  if (count) {
+    int path[count];
+
+    for (int index=0; index<count; index+=1) {
+      TEST_TCL_OK(Tcl_GetIntFromObj(interp, elements[index], &path[index]));
+    }
+
+    TEST_BRLAPI_OK(brlapi__enterTtyModeWithPath(session->handle, path, count, options.driver));
+  } else {
+    TEST_BRLAPI_OK(brlapi__enterTtyModeWithPath(session->handle, NULL, 0, options.driver));
+  }
+
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, getAuth) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  setStringResult(interp, session->settings.auth, -1);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, getDisplaySize) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+  TEST_TCL_OK(getDisplaySize(interp, session));
+
+  Tcl_Obj *width = Tcl_NewIntObj(session->displayWidth);
+  if (!width) return TCL_ERROR;
+
+  Tcl_Obj *height = Tcl_NewIntObj(session->displayHeight);
+  if (!height) return TCL_ERROR;
+
+  Tcl_Obj *const elements[] = {width, height};
+  Tcl_Obj *list = Tcl_NewListObj(2, elements);
+  if (!list) return TCL_ERROR;
+
+  Tcl_SetObjResult(interp, list);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, getDriverName) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+  return getSessionStringProperty(interp, session, brlapi__getDriverName);
+}
+
+FUNCTION_HANDLER(session, getFileDescriptor) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  setIntResult(interp, session->fileDescriptor);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, getHost) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  setStringResult(interp, session->settings.host, -1);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, getModelIdentifier) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+  return getSessionStringProperty(interp, session, brlapi__getModelIdentifier);
+}
+
+FUNCTION_HANDLER(session, leaveRawMode) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  TEST_BRLAPI_OK(brlapi__leaveRawMode(session->handle));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, leaveTtyMode) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  TEST_BRLAPI_OK(brlapi__leaveTtyMode(session->handle));
+  return TCL_OK;
+}
+
+typedef struct {
+  Tcl_Obj *value;
+  brlapi_param_flags_t flags;
+} ParameterOptions;
+
+OPTION_HANDLER(session, parameter, echo) {
+  ParameterOptions *options = data;
+  brlapi_param_flags_t flag = BRLAPI_PARAMF_SELF;
+
+  int echo;
+  TEST_TCL_OK(Tcl_GetBooleanFromObj(interp, objv[1], &echo));
+
+  if (echo) {
+    options->flags |= flag;
+  } else {
+    options->flags &= ~flag;
+  }
+
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, parameter, global) {
+  ParameterOptions *options = data;
+  brlapi_param_flags_t flag = BRLAPI_PARAMF_GLOBAL;
+
+  int global;
+  TEST_TCL_OK(Tcl_GetBooleanFromObj(interp, objv[1], &global));
+
+  if (global) {
+    options->flags |= flag;
+  } else {
+    options->flags &= ~flag;
+  }
+
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, parameter, set) {
+  ParameterOptions *options = data;
+  options->value = objv[1];
+  return TCL_OK;
+}
+
+static int
+parseParameterName (Tcl_Interp *interp, Tcl_Obj *name, brlapi_param_t *parameter) {
+  typedef struct {
+    const char *name;
+    brlapi_param_t value;
+  } ParameterEntry;
+
+  static const ParameterEntry parameters[] = {
+    #include "parameters.auto.h"
+    { .name=NULL }
+  };
+
+  int index;
+  TEST_TCL_OK(Tcl_GetIndexFromObjStruct(interp, name, parameters, sizeof(*parameters), "parameter name", 0, &index));
+
+  *parameter = parameters[index].value;
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, parameter) {
+  BrlapiSession *session = data;
+
+  ParameterOptions options = {
+    .value = NULL,
+    .flags = 0
+  };
+
+  BEGIN_OPTIONS
+    { OPTION(session, parameter, echo),
+      OPERANDS(1, "<boolean>")
+    },
+
+    { OPTION(session, parameter, global),
+      OPERANDS(1, "<boolean>")
+    },
+
+    { OPTION(session, parameter, set),
+      OPERANDS(1, "<value>")
+    }
+  END_OPTIONS(2, 1, 1, "<name> [<subparam>]")
+
+  Tcl_Obj *name = objv[0];
+  brlapi_param_t parameter;
+  TEST_TCL_OK(parseParameterName(interp, name, &parameter));
+
+  const brlapi_param_properties_t *properties = brlapi_getParameterProperties(parameter);
+  if (!properties) {
+    setStringsResult(interp, "bad parameter name", Tcl_GetString(name), NULL);
+    return TCL_ERROR;
+  }
+
+  int haveSubparam = objc > 1;
+  brlapi_param_subparam_t subparam;
+
+  if (properties->hasSubparam) {
+    if (!haveSubparam) {
+      setStringsResult(interp, "subparam not specified for \"", Tcl_GetString(name), "\"", NULL);
+      return TCL_ERROR;
+    }
+
+    Tcl_WideInt value;
+    TEST_TCL_OK(Tcl_GetWideIntFromObj(interp, objv[1], &value));
+    subparam = value;
+  } else {
+    if (haveSubparam) {
+      setStringsResult(interp, "subparam specified for \"", Tcl_GetString(name), "\"", NULL);
+      return TCL_ERROR;
+    }
+
+    subparam = 0;
+  }
+
+  if (options.value) {
+    Tcl_Obj *value = options.value;
+
+    switch (properties->type) {
+      case BRLAPI_PARAM_TYPE_STRING: {
+        const char *string = Tcl_GetString(value);
+	TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, string, strlen(string)));
+        break;
+      }
+
+      default: {
+        Tcl_Obj **elements;
+        int count;
+        TEST_TCL_OK(Tcl_ListObjGetElements(interp, value, &count, &elements));
+
+        if (count) {
+          switch (properties->type) {
+            case BRLAPI_PARAM_TYPE_BOOLEAN: {
+              brlapi_param_bool_t buffer[count];
+
+              for (int index=0; index<count; index+=1) {
+                int boolean;
+                TEST_TCL_OK(Tcl_GetBooleanFromObj(interp, elements[index], &boolean));
+                buffer[index] = !!boolean;
+              }
+
+	      TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, buffer, sizeof(buffer)));
+              break;
+            }
+
+            case BRLAPI_PARAM_TYPE_UINT8: {
+              uint8_t buffer[count];
+
+              for (int index=0; index<count; index+=1) {
+                int integer;
+                TEST_TCL_OK(Tcl_GetIntFromObj(interp, elements[index], &integer));
+                buffer[index] = integer;
+              }
+
+	      TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, buffer, sizeof(buffer)));
+              break;
+            }
+
+            case BRLAPI_PARAM_TYPE_UINT16: {
+              uint16_t buffer[count];
+
+              for (int index=0; index<count; index+=1) {
+                int integer;
+                TEST_TCL_OK(Tcl_GetIntFromObj(interp, elements[index], &integer));
+                buffer[index] = integer;
+              }
+
+	      TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, buffer, sizeof(buffer)));
+              break;
+            }
+
+            case BRLAPI_PARAM_TYPE_UINT32: {
+              uint32_t buffer[count];
+
+              for (int index=0; index<count; index+=1) {
+                int integer;
+                TEST_TCL_OK(Tcl_GetIntFromObj(interp, elements[index], &integer));
+                buffer[index] = integer;
+              }
+
+	      TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, buffer, sizeof(buffer)));
+              break;
+            }
+
+            case BRLAPI_PARAM_TYPE_UINT64: {
+              uint64_t buffer[count];
+
+              for (int index=0; index<count; index+=1) {
+                long integer;
+                TEST_TCL_OK(Tcl_GetLongFromObj(interp, elements[index], &integer));
+                buffer[index] = integer;
+              }
+
+	      TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, buffer, sizeof(buffer)));
+              break;
+            }
+
+            default:
+              break;
+          }
+        } else {
+	  TEST_BRLAPI_OK(brlapi__setParameter(session->handle, parameter, subparam, options.flags, NULL, 0));
+        }
+
+        break;
+      }
+    }
+  } else {
+    size_t length;
+    void *value = brlapi__getParameterAlloc(session->handle, parameter, subparam, options.flags, &length);
+
+    if (!value) {
+      setBrlapiError(interp);
+      return TCL_ERROR;
+    }
+
+    Tcl_Obj *result = Tcl_GetObjResult(interp);
+
+    switch (properties->type) {
+      case BRLAPI_PARAM_TYPE_STRING: {
+        Tcl_SetStringObj(result, value, -1);
+        break;
+      }
+
+      default: {
+        Tcl_SetListObj(result, 0, NULL);
+        const void *end = value + length;
+
+        switch (properties->type) {
+          case BRLAPI_PARAM_TYPE_BOOLEAN: {
+            const brlapi_param_bool_t *boolean = value;
+
+            while (boolean < (const brlapi_param_bool_t *)end) {
+              Tcl_Obj *element = Tcl_NewBooleanObj(*boolean);
+              if (!element) return TCL_ERROR;
+              TEST_TCL_OK(Tcl_ListObjAppendElement(interp, result, element));
+              boolean += 1;
+            }
+
+            break;
+          }
+
+          case BRLAPI_PARAM_TYPE_UINT8: {
+            const uint8_t *integer = value;
+
+            while (integer < (const uint8_t *)end) {
+              Tcl_Obj *element = Tcl_NewIntObj(*integer);
+              if (!element) return TCL_ERROR;
+              TEST_TCL_OK(Tcl_ListObjAppendElement(interp, result, element));
+              integer += 1;
+            }
+
+            break;
+          }
+
+          case BRLAPI_PARAM_TYPE_UINT16: {
+            const uint16_t *integer = value;
+
+            while (integer < (const uint16_t *)end) {
+              Tcl_Obj *element = Tcl_NewIntObj(*integer);
+              if (!element) return TCL_ERROR;
+              TEST_TCL_OK(Tcl_ListObjAppendElement(interp, result, element));
+              integer += 1;
+            }
+
+            break;
+          }
+
+          case BRLAPI_PARAM_TYPE_UINT32: {
+            const uint32_t *integer = value;
+
+            while (integer < (const uint32_t *)end) {
+              Tcl_Obj *element = Tcl_NewIntObj(*integer);
+              if (!element) return TCL_ERROR;
+              TEST_TCL_OK(Tcl_ListObjAppendElement(interp, result, element));
+              integer += 1;
+            }
+
+            break;
+          }
+
+          case BRLAPI_PARAM_TYPE_UINT64: {
+            const uint64_t *integer = value;
+
+            while (integer < (const uint64_t *)end) {
+              Tcl_Obj *element = Tcl_NewWideIntObj(*integer);
+              if (!element) return TCL_ERROR;
+              TEST_TCL_OK(Tcl_ListObjAppendElement(interp, result, element));
+              integer += 1;
+            }
+
+            break;
+          }
+
+          default:
+            break;
+        }
+
+        break;
+      }
+    }
+
+    free(value);
+  }
+
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, readKey) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<wait>");
+
+  int length;
+  const char *operand = Tcl_GetStringFromObj(objv[2], &length);
+  if (!operand) return TCL_ERROR;
+
+  int wait;
+  TEST_TCL_OK(Tcl_GetBoolean(interp, operand, &wait));
+
+  brlapi_keyCode_t key;
+  int result = brlapi__readKey(session->handle, wait, &key);
+  TEST_BRLAPI_OK(result);
+
+  if (result == 1) setWideIntResult(interp, key);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, readKeyWithTimeout) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "{infinite | <seconds>}");
+
+  int length;
+  const char *operand = Tcl_GetStringFromObj(objv[2], &length);
+  if (!operand) return TCL_ERROR;
+  int timeout;
+
+  if (strcmp(operand, "infinite") == 0) {
+    timeout = -1;
+  } else {
+    int seconds;
+    TEST_TCL_OK(Tcl_GetInt(interp, operand, &seconds));
+
+    if (seconds < 0) {
+      setStringsResult(interp, "negative timeout ", operand, NULL);
+      return TCL_ERROR;
+    }
+
+    timeout = seconds * 1000;
+  }
+
+  brlapi_keyCode_t code;
+  int result = brlapi__readKeyWithTimeout(session->handle, timeout, &code);
+  TEST_BRLAPI_OK(result);
+
+  if (result == 1) setWideIntResult(interp, code);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, recvRaw) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<maximumLength>");
+
+  int size;
+  TEST_TCL_OK(Tcl_GetIntFromObj(interp, objv[2], &size));
+
+  unsigned char buffer[size];
+  int result = brlapi__recvRaw(session->handle, buffer, size);
+  TEST_BRLAPI_OK(result);
+
+  setByteArrayResult(interp, buffer, result);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, resumeDriver) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  TEST_BRLAPI_OK(brlapi__resumeDriver(session->handle));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, sendRaw) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<packet>");
+
+  int count;
+  const unsigned char *bytes = Tcl_GetByteArrayFromObj(objv[2], &count);
+
+  TEST_BRLAPI_OK(brlapi__sendRaw(session->handle, bytes, count));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, setFocus) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<ttyNumber>");
+
+  int tty;
+  TEST_TCL_OK(Tcl_GetIntFromObj(interp, objv[2], &tty));
+
+  TEST_BRLAPI_OK(brlapi__setFocus(session->handle, tty));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, suspendDriver) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<driver>");
+
+  const char *driver = Tcl_GetString(objv[2]);
+  if (!driver) return TCL_ERROR;
+
+  TEST_BRLAPI_OK(brlapi__suspendDriver(session->handle, driver));
+  return TCL_OK;
+}
+
+typedef struct {
+  brlapi_writeArguments_t arguments;
+
+  Tcl_Obj *textObject;
+  int textLength;
+
+  int andLength;
+  int orLength;
+
+  unsigned numericCursor:1;
+  unsigned numericDisplay:1;
+  unsigned regionSpecified:1;
+} WriteOptions;
+
+OPTION_HANDLER(session, write, and) {
+  WriteOptions *options = data;
+  options->arguments.andMask = Tcl_GetByteArrayFromObj(objv[1], &options->andLength);
+  if (!options->andLength) options->arguments.andMask = NULL;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, write, cursor) {
+  WriteOptions *options = data;
+  int isNumeric = 0;
+
+  Tcl_Obj *object = objv[1];
+  const char *operand = Tcl_GetString(object);
+  if (!operand) return TCL_ERROR;
+
+  if (strcmp(operand, "off") == 0) {
+    options->arguments.cursor = BRLAPI_CURSOR_OFF;
+  } else if (strcmp(operand, "leave") == 0) {
+    options->arguments.cursor = BRLAPI_CURSOR_LEAVE;
+  } else {
+    int position;
+    TEST_TCL_OK(Tcl_GetIntFromObj(interp, object, &position));
+
+    options->arguments.cursor = position + 1;
+    isNumeric = 1;
+  }
+
+  options->numericCursor = isNumeric;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, write, display) {
+  WriteOptions *options = data;
+  int isNumeric = 0;
+
+  Tcl_Obj *object = objv[1];
+  const char *operand = Tcl_GetString(object);
+  if (!operand) return TCL_ERROR;
+
+  if (strcmp(operand, "default") == 0) {
+    options->arguments.displayNumber = BRLAPI_DISPLAY_DEFAULT;
+  } else {
+    int number;
+    TEST_TCL_OK(Tcl_GetIntFromObj(interp, object, &number));
+
+    options->arguments.displayNumber = number;
+    isNumeric = 1;
+  }
+
+  options->numericDisplay = isNumeric;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, write, or) {
+  WriteOptions *options = data;
+  options->arguments.orMask = Tcl_GetByteArrayFromObj(objv[1], &options->orLength);
+  if (!options->orLength) options->arguments.orMask = NULL;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, write, region) {
+  WriteOptions *options = data;
+
+  int start;
+  TEST_TCL_OK(Tcl_GetIntFromObj(interp, objv[1], &start));
+  options->arguments.regionBegin = start + 1;
+
+  int size;
+  TEST_TCL_OK(Tcl_GetIntFromObj(interp, objv[2], &size));
+  options->arguments.regionSize = size;
+
+  options->regionSpecified = 1;
+  return TCL_OK;
+}
+
+OPTION_HANDLER(session, write, text) {
+  WriteOptions *options = data;
+  options->textObject = objv[1];
+  options->textLength = Tcl_GetCharLength(options->textObject);
+  if (!options->textLength) options->textObject = NULL;
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, write) {
+  BrlapiSession *session = data;
+
+  WriteOptions options = {
+    .arguments = BRLAPI_WRITEARGUMENTS_INITIALIZER
+  };
+
+  BEGIN_OPTIONS
+    { OPTION(session, write, and),
+      OPERANDS(1, "<dots>")
+    },
+
+    { OPTION(session, write, cursor),
+      OPERANDS(1, "{leave | off | <offset>}")
+    },
+
+    { OPTION(session, write, display),
+      OPERANDS(1, "{default | <number>}")
+    },
+
+    { OPTION(session, write, or),
+      OPERANDS(1, "<dots>")
+    },
+
+    { OPTION(session, write, region),
+      OPERANDS(2, "<start> <size>")
+    },
+
+    { OPTION(session, write, text),
+      OPERANDS(1, "<string>")
+    }
+  END_OPTIONS(2, 0, 0, "")
+
+  if (options.numericDisplay) {
+    if (options.arguments.displayNumber < 0) {
+      setStringResult(interp, "display number out of range", -1);
+      return TCL_ERROR;
+    }
+  }
+
+  int characterCount = 0;
+  {
+    typedef struct {
+      const char *name;
+      const void *address;
+      int length;
+    } LengthEntry;
+
+    const LengthEntry lengths[] = {
+      { .name = "text",
+        .address = options.textObject,
+        .length = options.textLength
+      },
+
+      { .name = "andMask",
+        .address = options.arguments.andMask,
+        .length = options.andLength
+      },
+
+      { .name = "orMask",
+        .address = options.arguments.orMask,
+        .length = options.orLength
+      },
+
+      { .name = NULL }
+    };
+
+    const LengthEntry *current = lengths;
+    const LengthEntry *first = NULL;
+
+    while (current->name) {
+      if (current->address) {
+        if (!first) {
+          first = current;
+          characterCount = current->length;
+        } else if (current->length != first->length) {
+          setStringsResult(interp, first->name, "/", current->name, " length mismatch", NULL);
+          return TCL_ERROR;
+        }
+      }
+
+      current += 1;
+    }
+  }
+
+  unsigned int cellCount;
+  TEST_TCL_OK(getCellCount(interp, session, &cellCount));
+
+  if (options.numericCursor) {
+    int position = options.arguments.cursor - 1;
+
+    if ((position < 0) || (position >= cellCount)) {
+      setStringResult(interp, "cursor position out of range", -1);
+      return TCL_ERROR;
+    }
+  }
+
+  if (options.regionSpecified) {
+    int begin = options.arguments.regionBegin;
+    int size = options.arguments.regionSize;
+
+    if ((begin < 1) || (begin >= cellCount)) {
+      setStringResult(interp, "beginning of region out of range", -1);
+      return TCL_ERROR;
+    }
+
+    if ((size < 1) || (((begin - 1) + size) > cellCount)) {
+      setStringResult(interp, "size of region out of range", -1);
+      return TCL_ERROR;
+    }
+
+    cellCount = size;
+  } else {
+    options.arguments.regionBegin = 1;
+    options.arguments.regionSize = cellCount;
+  }
+
+  unsigned char andMask[cellCount];
+  unsigned char orMask[cellCount];
+
+  if (characterCount < cellCount) {
+    if (options.arguments.andMask) {
+      memset(andMask, 0XFF, cellCount);
+      memcpy(andMask, options.arguments.andMask, characterCount);
+      options.arguments.andMask = andMask;
+    }
+
+    if (options.arguments.orMask) {
+      memset(orMask, 0X00, cellCount);
+      memcpy(orMask, options.arguments.orMask, characterCount);
+      options.arguments.orMask = orMask;
+    }
+
+    if (options.textObject) {
+      if (Tcl_IsShared(options.textObject)) {
+        if (!(options.textObject = Tcl_DuplicateObj(options.textObject))) {
+          return TCL_ERROR;
+        }
+      }
+
+      do {
+        Tcl_AppendToObj(options.textObject, " ", -1);
+      } while (Tcl_GetCharLength(options.textObject) < cellCount);
+    }
+  } else if (characterCount > cellCount) {
+    if (options.textObject) {
+      if (!(options.textObject = Tcl_GetRange(options.textObject, 0, cellCount-1))) {
+        return TCL_ERROR;
+      }
+    }
+  }
+
+  if (options.textObject) {
+    options.arguments.charset = "UTF-8";
+    options.textLength = Tcl_GetCharLength(options.textObject);
+
+    options.arguments.text = Tcl_GetString(options.textObject);
+    if (!options.arguments.text) return TCL_ERROR;
+  }
+
+  // TCL uses C0,80 as the UTF-8 representation for NUL.
+  // This causes problems for BrlAPI.
+  {
+    const char *text = options.arguments.text;
+    size_t length = text? strlen(text): 0;
+    char buffer[length? length: 1];
+
+    if (text) {
+      const char *from = text;
+      char *to = buffer;
+
+      while (1) {
+        const char *nul = strstr(from, "\xC0\x80");
+
+        if (!nul) {
+          if (to != buffer) {
+            strcpy(to, from);
+            options.arguments.text = buffer;
+          }
+
+          break;
+        }
+
+        size_t count = nul - from;
+        memcpy(to, from, count);
+        to += count;
+        *to++ = 0;
+        from += count + 2;
+        length -= 1;
+      }
+    }
+
+    options.arguments.textSize = length;
+    TEST_BRLAPI_OK(brlapi__write(session->handle, &options.arguments));
+    return TCL_OK;
+  }
+}
+
+FUNCTION_HANDLER(session, writeDots) {
+  BrlapiSession *session = data;
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<dots>");
+
+  unsigned int size;
+  TEST_TCL_OK(getCellCount(interp, session, &size));
+
+  unsigned char buffer[size];
+  int count;
+  const unsigned char *cells = Tcl_GetByteArrayFromObj(objv[2], &count);
+
+  if (count < size) {
+    memcpy(buffer, cells, count);
+    memset(&buffer[count], 0, size-count);
+    cells = buffer;
+  }
+
+  TEST_BRLAPI_OK(brlapi__writeDots(session->handle, cells));
+  return TCL_OK;
+}
+
+static int
+changeKeys (
+  Tcl_Interp *interp, Tcl_Obj *const objv[], int objc, BrlapiSession *session,
+  int BRLAPI_STDCALL (*change) (brlapi_handle_t *handle, brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count)
+) {
+  TEST_FUNCTION_ARGUMENTS(1, 1, "<rangeType> [<keyCodeList>]");
+
+  brlapi_rangeType_t rangeType;
+  {
+    static const char *rangeNames[] = {
+      "all",
+      "code",
+      "command",
+      "key",
+      "type",
+      NULL
+    };
+
+    static const brlapi_rangeType_t rangeTypes[] = {
+      brlapi_rangeType_all,
+      brlapi_rangeType_code,
+      brlapi_rangeType_command,
+      brlapi_rangeType_key,
+      brlapi_rangeType_type
+    };
+
+    int rangeIndex;
+    TEST_TCL_OK(Tcl_GetIndexFromObj(interp, objv[2], rangeNames, "range type", 0, &rangeIndex));
+    rangeType = rangeTypes[rangeIndex];
+  }
+
+  Tcl_Obj *codeList = (objc < 4)? NULL: objv[3];
+
+  if (rangeType != brlapi_rangeType_all) {
+    if (!codeList) {
+      setStringResult(interp, "no key code list", -1);
+      return TCL_ERROR;
+    }
+
+    Tcl_Obj **codeElements;
+    int codeCount;
+    TEST_TCL_OK(Tcl_ListObjGetElements(interp, codeList, &codeCount, &codeElements));
+
+    if (codeCount) {
+      brlapi_keyCode_t codes[codeCount];
+
+      for (int codeIndex=0; codeIndex<codeCount; codeIndex+=1) {
+        Tcl_WideInt code;
+        TEST_TCL_OK(Tcl_GetWideIntFromObj(interp, codeElements[codeIndex], &code));
+        codes[codeIndex] = code;
+      }
+
+      TEST_BRLAPI_OK(change(session->handle, rangeType, codes, codeCount));
+      return TCL_OK;
+    }
+  } else if (codeList) {
+    setStringResult(interp, "unexpected key code list", -1);
+    return TCL_ERROR;
+  }
+
+  TEST_BRLAPI_OK(change(session->handle, rangeType, NULL, 0));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, acceptKeys) {
+  BrlapiSession *session = data;
+  return changeKeys(interp, objv, objc, session, brlapi__acceptKeys);
+}
+
+FUNCTION_HANDLER(session, ignoreKeys) {
+  BrlapiSession *session = data;
+  return changeKeys(interp, objv, objc, session, brlapi__ignoreKeys);
+}
+
+static int
+changeKeyRanges (
+  Tcl_Interp *interp, Tcl_Obj *const objv[], int objc, BrlapiSession *session,
+  int BRLAPI_STDCALL (*change) (brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int count)
+) {
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<keyRangeList>");
+
+  Tcl_Obj **rangeElements;
+  int rangeCount;
+  TEST_TCL_OK(Tcl_ListObjGetElements(interp, objv[2], &rangeCount, &rangeElements));
+
+  if (rangeCount) {
+    brlapi_range_t ranges[rangeCount];
+
+    for (int rangeIndex=0; rangeIndex<rangeCount; rangeIndex+=1) {
+      brlapi_range_t *range = &ranges[rangeIndex];
+      Tcl_Obj **codeElements;
+      int codeCount;
+      TEST_TCL_OK(Tcl_ListObjGetElements(interp, rangeElements[rangeIndex], &codeCount, &codeElements));
+
+      if (codeCount != 2) {
+        setStringResult(interp, "key range element is not a two-element list", -1);
+        return TCL_ERROR;
+      }
+
+      {
+        Tcl_WideInt codes[codeCount];
+
+        for (int codeIndex=0; codeIndex<codeCount; codeIndex+=1) {
+          TEST_TCL_OK(Tcl_GetWideIntFromObj(interp, codeElements[codeIndex], &codes[codeIndex]));
+        }
+
+        range->first = codes[0];
+        range->last = codes[1];
+      }
+    }
+
+    TEST_BRLAPI_OK(change(session->handle, ranges, rangeCount));
+    return TCL_OK;
+  }
+
+  TEST_BRLAPI_OK(change(session->handle, NULL, 0));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(session, acceptKeyRanges) {
+  BrlapiSession *session = data;
+  return changeKeyRanges(interp, objv, objc, session, brlapi__acceptKeyRanges);
+}
+
+FUNCTION_HANDLER(session, ignoreKeyRanges) {
+  BrlapiSession *session = data;
+  return changeKeyRanges(interp, objv, objc, session, brlapi__ignoreKeyRanges);
+}
+
+static void
+endSession (ClientData data) {
+  BrlapiSession *session = data;
+  brlapi__closeConnection(session->handle);
+  deallocateMemory(session->handle);
+  deallocateMemory(session);
+}
+
+static void
+handleBrlapiException (
+  brlapi_handle_t *handle,
+  int error, brlapi_packetType_t type,
+  const void *packet, size_t size
+) {
+  BrlapiSession *session = brlapi__getClientData(handle);
+  Tcl_Interp *interp = session->tclInterpreter;
+  char message[0X100];
+
+  brlapi__strexception(
+    handle, message, sizeof(message), error, type, packet, size
+  );
+
+  const char *name = Tcl_GetCommandName(interp, session->tclCommand);
+  fprintf(stderr, "BrlAPI session failure: %s: %s\n", name, message);
+  exit(1);
+}
+
+static int
+brlapiSessionCommand (ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
+  BEGIN_FUNCTIONS
+    FUNCTION(session, acceptKeyRanges),
+    FUNCTION(session, acceptKeys),
+    FUNCTION(session, closeConnection),
+    FUNCTION(session, enterRawMode),
+    FUNCTION(session, enterTtyMode),
+    FUNCTION(session, enterTtyModeWithPath),
+    FUNCTION(session, getAuth),
+    FUNCTION(session, getDisplaySize),
+    FUNCTION(session, getDriverName),
+    FUNCTION(session, getFileDescriptor),
+    FUNCTION(session, getHost),
+    FUNCTION(session, getModelIdentifier),
+    FUNCTION(session, ignoreKeyRanges),
+    FUNCTION(session, ignoreKeys),
+    FUNCTION(session, leaveRawMode),
+    FUNCTION(session, leaveTtyMode),
+    FUNCTION(session, parameter),
+    FUNCTION(session, readKey),
+    FUNCTION(session, readKeyWithTimeout),
+    FUNCTION(session, recvRaw),
+    FUNCTION(session, resumeDriver),
+    FUNCTION(session, sendRaw),
+    FUNCTION(session, setFocus),
+    FUNCTION(session, suspendDriver),
+    FUNCTION(session, write),
+    FUNCTION(session, writeDots),
+  END_FUNCTIONS
+
+  return invokeFunction(interp, objv, objc, functions, data);
+}
+
+FUNCTION_HANDLER(general, describeKeyCode) {
+  TEST_FUNCTION_ARGUMENTS(2, 0, "<keyCode> <arrayName>");
+
+  Tcl_WideInt keyCode;
+  TEST_TCL_OK(Tcl_GetWideIntFromObj(interp, objv[2], &keyCode));
+
+  const char *array = Tcl_GetString(objv[3]);
+  if (!array) return TCL_ERROR;
+
+  brlapi_describedKeyCode_t dkc;
+  TEST_BRLAPI_OK(brlapi_describeKeyCode(keyCode, &dkc));
+
+  SET_ARRAY_ELEMENT("type", Tcl_NewStringObj(dkc.type, -1));
+  SET_ARRAY_ELEMENT("command", Tcl_NewStringObj(dkc.command, -1));
+  SET_ARRAY_ELEMENT("argument", Tcl_NewIntObj(dkc.argument));
+
+  {
+    Tcl_Obj *flags = Tcl_NewListObj(0, NULL);
+    SET_ARRAY_ELEMENT("flags", flags);
+
+    for (int index=0; index<dkc.flags; index+=1) {
+      Tcl_Obj *flag = Tcl_NewStringObj(dkc.flag[index], -1);
+      if (!flag) return TCL_ERROR;
+      TEST_TCL_OK(Tcl_ListObjAppendElement(interp, flags, flag));
+    }
+  }
+
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(general, expandKeyCode) {
+  TEST_FUNCTION_ARGUMENTS(2, 0, "<keyCode> <arrayName>");
+
+  Tcl_WideInt keyCode;
+  TEST_TCL_OK(Tcl_GetWideIntFromObj(interp, objv[2], &keyCode));
+
+  const char *array = Tcl_GetString(objv[3]);
+  if (!array) return TCL_ERROR;
+
+  brlapi_expandedKeyCode_t ekc;
+  TEST_BRLAPI_OK(brlapi_expandKeyCode(keyCode, &ekc));
+
+  SET_ARRAY_ELEMENT("type", Tcl_NewIntObj(ekc.type));
+  SET_ARRAY_ELEMENT("command", Tcl_NewIntObj(ekc.command));
+  SET_ARRAY_ELEMENT("argument", Tcl_NewIntObj(ekc.argument));
+  SET_ARRAY_ELEMENT("flags", Tcl_NewIntObj(ekc.flags));
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(general, getHandleSize) {
+  TEST_FUNCTION_NO_ARGUMENTS();
+  setIntResult(interp, brlapi_getHandleSize());
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(general, getVersionNumbers) {
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  int majorNumber, minorNumber, revisionNumber;
+  brlapi_getLibraryVersion(&majorNumber, &minorNumber, &revisionNumber);
+
+  Tcl_Obj *majorObject = Tcl_NewIntObj(majorNumber);
+  if (!majorObject) return TCL_ERROR;
+
+  Tcl_Obj *minorObject = Tcl_NewIntObj(minorNumber);
+  if (!minorObject) return TCL_ERROR;
+
+  Tcl_Obj *revisionObject = Tcl_NewIntObj(revisionNumber);
+  if (!revisionObject) return TCL_ERROR;
+
+  Tcl_Obj *const elements[] = {majorObject, minorObject, revisionObject};
+  Tcl_Obj *list = Tcl_NewListObj(3, elements);
+  if (!list) return TCL_ERROR;
+
+  Tcl_SetObjResult(interp, list);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(general, getVersionString) {
+  TEST_FUNCTION_NO_ARGUMENTS();
+
+  setStringResult(interp, BRLAPI_RELEASE, -1);
+  return TCL_OK;
+}
+
+FUNCTION_HANDLER(general, makeDots) {
+  TEST_FUNCTION_ARGUMENTS(1, 0, "<dotNumbersList>");
+
+  Tcl_Obj **elements;
+  int elementCount;
+  TEST_TCL_OK(Tcl_ListObjGetElements(interp, objv[2], &elementCount, &elements));
+
+  if (elementCount) {
+    BrlDots cells[elementCount];
+
+    for (int elementIndex=0; elementIndex<elementCount; elementIndex+=1) {
+      Tcl_Obj *element = elements[elementIndex];
+      BrlDots *cell = &cells[elementIndex];
+      *cell = 0;
+
+      int numberCount;
+      const char *numbers = Tcl_GetStringFromObj(element, &numberCount);
+      if (!numbers) return TCL_ERROR;
+
+      if ((numberCount != 1) || (numbers[0] != '0')) {
+        for (int numberIndex=0; numberIndex<numberCount; numberIndex+=1) {
+          char number = numbers[numberIndex];
+          BrlDots dot = brlNumberToDot(number);
+
+          if (!dot) {
+            setStringResult(interp, "invalid dot number", -1);
+            return TCL_ERROR;
+          }
+
+          if (*cell & dot) {
+            setStringResult(interp, "duplicate dot number", -1);
+            return TCL_ERROR;
+          }
+
+          *cell |= dot;
+        }
+      }
+    }
+
+    setByteArrayResult(interp, cells, elementCount);
+  } else {
+    setByteArrayResult(interp, NULL, elementCount);
+  }
+
+  return TCL_OK;
+}
+
+typedef struct {
+  brlapi_connectionSettings_t settings;
+} OpenConnectionOptions;
+
+OPTION_HANDLER(general, openConnection, auth) {
+  OpenConnectionOptions *options = data;
+  return (options->settings.auth = Tcl_GetString(objv[1]))? TCL_OK: TCL_ERROR;
+}
+
+OPTION_HANDLER(general, openConnection, host) {
+  OpenConnectionOptions *options = data;
+  return (options->settings.host = Tcl_GetString(objv[1]))? TCL_OK: TCL_ERROR;
+}
+
+FUNCTION_HANDLER(general, openConnection) {
+  OpenConnectionOptions options = {
+    .settings = BRLAPI_SETTINGS_INITIALIZER
+  };
+
+  BEGIN_OPTIONS
+    { OPTION(general, openConnection, auth),
+      OPERANDS(1, "{none | <scheme>,...}")
+    },
+
+    { OPTION(general, openConnection, host),
+      OPERANDS(1, "[<host>][:<port>]")
+    }
+  END_OPTIONS(2, 0, 0, "")
+
+  BrlapiSession *session = allocateMemory(sizeof(*session));
+  if (session) {
+    memset(session, 0, sizeof(*session));
+    session->tclInterpreter = interp;
+
+    if ((session->handle = allocateMemory(brlapi_getHandleSize()))) {
+      int result = brlapi__openConnection(session->handle, &options.settings, &session->settings);
+
+      if (result != -1) {
+        session->fileDescriptor = result;
+
+        brlapi__setClientData(session->handle, session);
+        brlapi__setExceptionHandler(session->handle, handleBrlapiException);
+
+        char name[0X20];
+        {
+          static unsigned int suffix = 0;
+          snprintf(name, sizeof(name), "brlapi%u", suffix++);
+          session->tclCommand = Tcl_CreateObjCommand(interp, name, brlapiSessionCommand, session, endSession);
+        }
+
+        if (session->tclCommand) {
+          setStringResult(interp, name, -1);
+          return TCL_OK;
+        }
+      } else {
+        setBrlapiError(interp);
+      }
+
+      deallocateMemory(session->handle);
+    }
+
+    deallocateMemory(session);
+  }
+
+  return TCL_ERROR;
+}
+
+static int
+brlapiGeneralCommand (ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
+  BEGIN_FUNCTIONS
+    FUNCTION(general, describeKeyCode),
+    FUNCTION(general, expandKeyCode),
+    FUNCTION(general, getHandleSize),
+    FUNCTION(general, getVersionNumbers),
+    FUNCTION(general, getVersionString),
+    FUNCTION(general, makeDots),
+    FUNCTION(general, openConnection),
+  END_FUNCTIONS
+
+  return invokeFunction(interp, objv, objc, functions, data);
+}
+
+int
+Brlapi_tcl_Init (Tcl_Interp *interp) {
+  int result = TCL_ERROR;
+  Tcl_Command command = Tcl_CreateObjCommand(interp, "brlapi", brlapiGeneralCommand, NULL, NULL);
+
+  if (command) {
+    result = Tcl_PkgProvide(interp, "Brlapi", BRLAPI_RELEASE);
+    if (result != TCL_OK) Tcl_DeleteCommandFromToken(interp, command);
+  }
+
+  return result;
+}
diff --git a/Bindings/Tcl/bindings.m4 b/Bindings/Tcl/bindings.m4
new file mode 100644
index 0000000..758f0db
--- /dev/null
+++ b/Bindings/Tcl/bindings.m4
@@ -0,0 +1,80 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_TCL_BINDINGS], [dnl
+AC_PATH_PROGS([TCLSH], [tclsh tclsh8.5 tclsh8.6 tclsh8.7], [])
+
+TCL_OK=false
+TCL_CPPFLAGS=""
+TCL_LIBS=""
+TCL_DIR=""
+
+if test -n "${TCLSH}"
+then
+   AC_MSG_NOTICE([Tcl shell: ${TCLSH}])
+   BRLTTY_TCL_QUERY([tcl_configuration_script], [config])
+
+   if test -n "${tcl_configuration_script}"
+   then
+      AC_MSG_NOTICE([Tcl configuration script: ${tcl_configuration_script}])
+
+      if test ! -r "${tcl_configuration_script}"
+      then
+         AC_MSG_WARN([Tcl configuration script not readable: ${tcl_configuration_script}])
+      elif . "${tcl_configuration_script}"
+      then
+         TCL_OK=true
+         TCL_CPPFLAGS="${TCL_INCLUDE_SPEC}"
+         TCL_LIBS="${TCL_LIB_SPEC}"
+      fi
+   else
+      AC_MSG_WARN([Tcl configuration script not found])
+   fi
+else
+   AC_MSG_WARN([Tcl shell not found])
+   TCLSH="TCL_SHELL_NOT_FOUND_BY_CONFIGURE"
+fi
+
+${TCL_OK} && {
+   test -n "${TCL_PACKAGE_PATH}" && {
+      for directory in ${TCL_PACKAGE_PATH}
+      do
+         test `expr "${directory}" : '.*/lib'` -eq 0 || {
+            TCL_DIR="${directory}"
+            break
+         }
+      done
+   }
+
+   if test -n "${TCL_DIR}"
+   then
+      AC_MSG_NOTICE([Tcl packages directory: ${TCL_DIR}])
+   else
+      AC_MSG_WARN([Tcl packages directory not found])
+      TCL_DIR="TCL_PACKAGES_DIRECTORY_NOT_FOUND_BY_CONFIGURE"
+   fi
+}
+
+AC_SUBST([TCL_OK])
+AC_SUBST([TCL_CPPFLAGS])
+AC_SUBST([TCL_LIBS])
+AC_SUBST([TCL_DIR])
+])
+
+AC_DEFUN([BRLTTY_TCL_QUERY], [dnl
+   $1=`"${TCLSH}" "${srcdir}/Tools/tclcmd" $2`
+])
diff --git a/Bindings/Tcl/brlapi.txt b/Bindings/Tcl/brlapi.txt
new file mode 100644
index 0000000..74ccd03
--- /dev/null
+++ b/Bindings/Tcl/brlapi.txt
@@ -0,0 +1,58 @@
+brlapi <function> [<arg> ...]
+   getVersionString
+   getVersionNumbers
+   openConnection [<option> ...]
+      -host [<host>][:<port>]
+      -auth {none | <scheme>,...}
+         keyfile:<file>
+         user:<name>
+         group:<name>
+         polkit
+   expandKeyCode <keyCode> <arrayName>
+   describeKeyCode <keyCode> <arrayName>
+   makeDots <dotNumbersList>
+   getHandleSize
+
+brlapi<sessionNumber> function [arg ...]
+   getHost
+   getAuth
+   getFileDescriptor
+   getDriverName
+   getModelIdentifier
+   getDisplaySize
+   parameter [<option> ...] <name> [<subparam>]
+      -echo <boolean>
+      -global <boolean>
+      -set <value>
+   enterTtyMode [<option> ...]
+      -tty {default | <number>}
+      -commands
+      -keys <driver>
+   enterTtyModeWithPath [<option> ...]
+      -path <list>
+      -commands
+      -keys <driver>
+   leaveTtyMode
+   setFocus <ttyNumber>
+   readKey <wait>
+   readKeyWithTimeout {infinite | <seconds>}
+   acceptKeys <rangeType> [<keyCodeList>]
+   ignoreKeys <rangeType> [<keyCodeList>]
+   acceptKeyRanges <keyRangeList>
+   ignoreKeyRanges <keyRangeList>
+   write [<option> ...]
+      -text <string>
+      -and <dots>
+      -or <dots>
+      -region <start> <size>
+      -cursor {leave | off | <offset>}
+      -display {default | <number>}
+   writeDots <dots>
+   enterRawMode <driver>
+   leaveRawMode
+   recvRaw <maximumLength>
+   sendRaw <packet>
+   suspendDriver <driver>
+   resumeDriver
+   closeConnection
+
diff --git a/Bindings/Tcl/constants.awk b/Bindings/Tcl/constants.awk
new file mode 100644
index 0000000..96b18be
--- /dev/null
+++ b/Bindings/Tcl/constants.awk
@@ -0,0 +1,66 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+}
+
+END {
+}
+
+function brlCommand(name, symbol, value, help) {
+}
+
+function brlBlock(name, symbol, value, help) {
+}
+
+function brlKey(name, symbol, value, help) {
+}
+
+function brlFlag(name, symbol, value, help) {
+}
+
+function brlDot(number, symbol, value, help) {
+}
+
+function apiConstant(name, symbol, value, help) {
+  if (gsub("^PARAM_", "", name)) {
+    if (name == "COUNT") return
+    if (gsub("^TYPE_", "", name)) return
+
+    gsub("_", "-", name)
+    name = tolower(name)
+    print "{ \"" name "\", " symbol " }," >parametersHeader
+  }
+}
+
+function apiMask(name, symbol, value, help) {
+}
+
+function apiShift(name, symbol, value, help) {
+}
+
+function apiType(name, symbol, value, help) {
+}
+
+function apiKey(name, symbol, value, help) {
+}
+
+function apiRangeType(name, symbol, value, help) {
+}
+
diff --git a/Bindings/Tcl/prologue.tcl b/Bindings/Tcl/prologue.tcl
new file mode 100644
index 0000000..e2b61d5
--- /dev/null
+++ b/Bindings/Tcl/prologue.tcl
@@ -0,0 +1,19 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [eval file join [lreplace [file split [info script]] end-2 end-1]]
+load libbrlapi_tcl.so
diff --git a/Bootdisks/README.Debian b/Bootdisks/README.Debian
new file mode 100644
index 0000000..506a276
--- /dev/null
+++ b/Bootdisks/README.Debian
@@ -0,0 +1,35 @@
+Debian Installer feat. BRLTTY
+-----------------------------
+
+Debian Installer natively supports BRLTTY since its beta 3 for Debian
+Etch, see official announcement at [1].  If your Braille display is
+connected by USB, simply press ENTER at syslinux prompt (at the very
+beginning of the installation process), your display should be
+autodetected.  Otherwise, if either your display is connected by
+serial port or you want to set a particular Braille table, give the
+following line at syslinux prompt before pressing ENTER (you may be
+careful as the keyboard layout is qwerty at this step):
+
+install brltty=drv,dev,tbl
+
+"drv" stands for the BRLTTY code of your Braille display, "dev" stands
+for the device your Braille display is connected to (e.g. ttyS0, usb:,
+...)  and "tbl" stands for the code of the Braille table to be set.
+For example, this is the line that enables BRLTTY for a BAUM
+SuperVario 40 Braille display connected by USB with default Braille
+table:
+
+install brltty=bm,usb:
+
+Supported Braille displays and Braille tables are listed along with
+corresponding codes in the BRLTTY manual, see [2].  Details about
+installing Debian are available at [3].  Please also consider joining
+Debian Accessibility mailing list at [4].
+
+Footnotes:
+[1]  http://www.debian.org/devel/debian-installer/News/2006/20060811
+[2]  http://brltty.app/doc/Manual-BRLTTY/English/BRLTTY.html
+[3]  http://www.debian.org/releases/stable/installmanual
+[4]  http://lists.debian.org/debian-accessibility/
+
+ -- Boris Daix <Boris.Daix@alysse.org>  Sun, 27 Aug 2006 23:10:15 +0200
diff --git a/Bootdisks/README.RedHat.cd b/Bootdisks/README.RedHat.cd
new file mode 100644
index 0000000..19cd46a
--- /dev/null
+++ b/Bootdisks/README.RedHat.cd
@@ -0,0 +1,159 @@
+Adding BRLTTY to a Red Hat Linux Install CD
+===========================================
+
+Written during April, 2004, by Dave Mielke <dave@mielke.cc>.
+
+
+Quick Start
+-----------
+
+The script "rhmkiso" can be used to add BRLTTY to a RedHat or Fedora install
+CD. Here's how to use it.
+
+First: Log in as (or su to) root. This is because the script uses a loopback
+mount. You'll probably also need to be root in order to write the resultant
+image to a CD as well.
+
+Second: Create an empty directory and change to it.
+
+   mkdir work
+   cd work
+
+Third: Copy or download the image of the installer CD ("boot.iso") for the
+desired RedHat or Fedora release, or the image for the first CD of the full
+installation set ("disc1.iso"), into the current directory.
+
+   cp /path/to/boot.iso .
+
+Fourth: Copy or download the tarball for the desired BRLTTY release into the
+current directory.
+
+   cp /path/to/brltty-3.5.tar.gz .
+
+Fifth: Run the "rhmkiso" script with no options or parameters.
+
+   rhmkiso
+
+Sixth: You should now have a new file, "brltty.iso", in the current directory. 
+Write this modified image to a CD.
+
+   cdrecord -v dev=0,0,0 -data brltty.iso
+
+If you're using a rewritable CD then you should erase it first.
+
+   cdrecord -v dev=0,0,0 blank=fast
+
+For more information on writing to a CD, please check the CD-Writing HOWTO at:
+
+   http://tldp.org/HOWTO/CD-Writing-HOWTO.html
+
+Seventh: Boot from the CD you've just written, and wait a few seconds after it
+begins to spin in order to give the boot loader enough time to get started and
+to present its initial prompt. Then enter:
+
+   text brltty=<driver>,<device>,<table>
+e.g.
+   text brltty=ht,ttyS1,de
+
+You can omit any operand, allowing it to default, simply by not specifying it. 
+You also needn't specify any trailing commas resulting from omitted operands.
+
+   text brltty=ht,ttyS1,
+   text brltty=ht,ttyS1
+   text brltty=ht,
+   text brltty=ht
+   text brltty=ht,,de
+   text brltty=ht,,
+
+The <driver> operand is the two-letter identifier of the driver for your
+braille display. It's the only required operand. See the -b option (below) for
+further details.
+
+The <device> operand is the device to which your braille display is connected. 
+It's optional, and defaults to the primary serial port ("/dev/ttyS0"). If your
+display is connected to a serial port then specify either the name (e.g.
+"ttyS1") of or the path (e.g. "/dev/ttyS1") to the corresponding device. If
+it's connected to the USB then specify "usb:" (the trailing colon is
+important).
+
+The <table> operand is the text translation table you wish to use. It's
+optional, and defaults to "nabcc" (North American Braille Computer Code). See
+the "text.*.tbl" files in the "BrailleTables" subdirectory of BRLTTY's source
+tree for alternatives. See the -t option (below) for further details.
+
+
+The Details
+-----------
+
+The "rhmkiso" script can add BRLTTY to either a RedHat or a Fedora install CD. 
+It can be applied either to the basic installer CD or to the first CD of the
+full installation set, and makes the following modifications to the image:
+*  BRLTTY is added (all drivers, all tables).
+*  The timeout for the initial boot loader prompt is disabled.
+*  The default kernel configuration to be booted is changed to "text".
+*  The use of frame buffers for the "text" consoles is disabled.
+
+For rescue images, the following additional modifications are made:
+*  Text consoles are used when "rescue" is selected.
+*  The use of frame buffers for the "rescue" consoles is disabled.
+
+Although its defaults are what usually needs to be done, the "rhmkiso" script
+offers a number of options so that you can customize what it does. It's general
+invocation syntax is:
+
+   rhmkiso [-option ...]
+
+The following options are provided:
+   -i file         The original (input) image (default: boot.iso).
+   -o file         The modified (output) image (default: brltty.iso).
+   -a file         The BRLTTY archive (default: check current directory).
+   -b driver,...   The braille driver(s) to include (default: all).
+   -t file         The text translation table to build in (default: nabcc).
+   -s              Invoke an interactive shell to inspect the modified image.
+   -h              Display usage information and exit.
+
+The -i option specifies the location of the distribution-supplied original
+(input) image which is to be modified. Either a relative or an absolute path
+may be supplied. It defaults to "boot.iso" (the usual name for the basic
+installer CD image).
+
+The -o option specifies the location of the file which is to contain the
+modified (output) image which the script will create. Either a relative or an
+absolute path may be supplied. It defaults to "brltty.iso".
+
+The -b option specifies which braille display drivers will be linked into the
+brltty executable as opposed to being built as dynamically loadable shared
+objects. If more than one driver is specified then their specifiers must be
+separated from one another by a comma. Either the two-letter identifier or the
+proper name (full or abbreviated) for each driver must be supplied. If a driver
+specifier is prefixed by a minus sign (-) then that driver is excluded from the
+build. The first non-excluded driver in the list is the default driver. The
+word "all", if used instead of a driver specifier as the last item in the list,
+causes all the remaining drivers to be implicitly appended to the list in no
+specific order (or, if preceded by a minus sign, to be excluded from the
+build). The default is "all", i.e. all of the drivers (in no particular order
+so the default is unpredictable) are linked in.
+
+The -t option specifies which text translation table is to be built into the
+brltty executable. If a relative path is supplied then it's anchored at the
+"BrailleTables" subdirectory within BRLTTY's source tree. Since any of BRLTTY's
+own tables can also be specified when the image is booted, the primary use of
+this option is to specify the absolute path to a user-defined table outside
+BRLTTY's source tree. The ".tbl" extension is optional. If a simple file name,
+i.e. one of BRLTTY's own tables, is being supplied then the "text." prefix is
+also optional. The default is "nabcc" (North American Braille Computer Code).
+
+The -s option causes an interactive shell to be invoked after all of the
+modifications to the files within the image have been made but before the CD
+image is created. This gives you an opportunity to inspect what the script has
+done. Your login shell, i.e. the one pointed to by the $SHELL environment
+variable, is used; if it's not set then "/bin/sh" is assumed. 
+
+The script uses a lot of temporary file space to do its job, especially if the
+first CD of the full installation set is being used. This is because it needs
+to work with a complete extracted copy of the files within the image. If
+there's not enough space in "/tmp" then you can direct the script to use
+another directory via the $TMPDIR environment variable.
+
+The script uses a loopback mount to access the files within the original CD
+image. This means that it must be run as root.
diff --git a/Bootdisks/README.RedHat.fd b/Bootdisks/README.RedHat.fd
new file mode 100644
index 0000000..10a7066
--- /dev/null
+++ b/Bootdisks/README.RedHat.fd
@@ -0,0 +1,209 @@
+Adding BRLTTY to a Red Hat Linux Install Floppy
+===============================================
+
+Written on April 7, 2002
+
+Here is an outline of a procedure for adapting the RedHat (or similar)
+installation boot disk so that BRLTTY can be used during the installation
+process.
+
+History:
+-Initial draft from Stéphane Doyon <s.doyon@videotron.ca>
+-Additions from Nicolas Pitre <nico@fluxnic.net>
+-April 7, 2002: Mostly rewritten by Stéphane Doyon to take into account
+   the INIT_PATH hack
+-September 26, 2002: Amended by Dave Mielke <dave@mielke.cc> due to change from
+   make file editing to autoconf in 3.1.
+
+TODO:
+-Figure out how to use shared libraries from a statically linked
+   executable, when the lib references symbols from the main
+   program. With that we could do generic bootdisk with dynamic selection
+   of braille driver.
+-Give working instructions on how to make higher sized floppies for a
+   one-floppy solution.
+
+You need an already working Linux station to perform this. A certain
+level of Linux/Unix expertise is also required. Please read to the end
+before doing anything. You'll probably end up asking for sighted help at
+some point (such as to know why it won't boot, or to help install BRLTTY
+on the new machine), but if you can get this to work then you'll be free
+to spend an hour on your own choosing which packages you want installed.
+
+This was last tried with RedHat 7.2.
+
+First step is compiling BRLTTY. The bootdisk will use one and only one
+BRLTTY driver, so you must choose wich. Do:
+
+make distclean
+./configure --enable-standalone-programs --with-init-path
+            --with-braille-driver=<driver> --with-text-table=<table> 
+make
+where <driver> is the driver to build into BRLTTY (one of the possible values
+listed under --with-braille-driver in the output from ./configure --help) and
+<table> is the text translation table to use (such as text.french.tbl,
+text.german.tbl, ...) (see the the BrailleTables/ subdirectory).
+
+This will build BRLTTY with the chosen display driver built-in, and will
+link the BRLTTY executable statically so that it doesn't depend on any
+shared libraries.
+
+If your display is on the second serial port instead of the first,
+you will need to add "--with-braille-device=/dev/ttyS1".
+
+You should get an executable called "brltty" (in the "Programs/"  subdirectory)
+whose size is around 570K, depending on the driver you chose. If you do:
+
+	file Programs/brltty
+
+it should say, among other things, "statically linked".
+
+The Red Hat installation boot disk is a DOS disk that contains a boot
+loader (called syslinux), a kernel (called vmlinuz), and an initial ramdisk
+(called initrd.img). This initial ramdisk is the initial root filesystem
+containing a bootstrap installation program: /sbin/init. We'll just put
+brltty on that ramdisk image and make sure it is started before anything
+else. However, because the statically linked brltty executable is so
+large, we will have to put the ramdisk image on a second floppy, because
+it won't fit on the original single boot floppy (with the kernel).
+
+So now we must hack floppies. Make a temporary work directory. We will
+call it ".../work", could be "~/work".
+
+mkdir .../work
+mkdir .../work/etcbrltty
+
+Now copy relevant files from your BRLTTY build. Copy the executable, the
+translation table, the contraction table (if you want it), and the help file(s)
+to your work directory.
+
+cp Programs/brltty .../work
+cp BrailleTables/*.tbl .../work/etcbrltty
+cp ContractionTables/*.ctb .../work/etcbrltty
+cp help/* .../work/etcbrltty
+
+(You can also copy alternative translation tables from BrailleTables/
+to .../work/etcbrltty. These CAN be loaded dynamically.)
+
+Now change to the work directory:
+
+cd .../work
+
+The executable is way too large for a boot disk because it contains lots of
+unneeded symbol table and debugging information.  Remove this excess data in
+order to make the executable as small as possible.
+
+strip brltty
+
+Next step is to select an installation boot floppy image from your
+distribution. This could be images/boot.img or images/bootnet.img from an
+RH CDROM. Consider checking the updates site for your distribution in
+case they have updated installation disk images. If what you have is an
+actual floppy, use "dd if=/dev/fd0 of=boot.img bs=512" to make an image
+file from it.
+
+Copy the chosen image to your work directory and if necessary rename it
+to boot.img.
+
+So now the current directory should contain boot.img, brltty, and
+etcbrltty/.
+
+Now run the rhmkbot script (which is located in the same directory as this
+README). It will copy boot.img to myboot.img, and it will modify the
+syslinux.cfg: it will add a new boot entry called "brltty". That entry will
+have options to cause syslinux to load a ramdisk from a second floppy, instead
+of using the initrd.img on the first floppy. The brltty entry has the "text"
+keyword parameter to trigger the text-mode install. rhmkbot also makes this new
+entry the default boot entry.
+
+Now run the rhmkroot script. It will get initrd.img (the initial ramdisk)
+from boot.img and call it myroot.img. It will modify that image:
+-Rename /sbin/init to /sbin/init_real,
+-Copy brltty to /sbin/brltty,
+-Link /sbin/init to /sbin/brltty
+-Copy files from etcbrltty to /etc/brltty,
+-Try to make sure necessary devices such as vcsa, serial ports, and
+   console device exist in /dev. (vcsa is usually missing.)
+
+If it turns out that the brltty executable is too large, do "./configure
+--help" and check out the various "--disable-..." options. Each removes a
+specific feature from BRLTTY, and results in a corresponding decrease in the
+size of the executable. Recommended possibilities include:
+
+   --disable-table-selection --disable-learn-mode --disable-contracted-braille
+   --disable-pcm-support --disable-midi-support --disable-fm-support
+   --disable-api --disable-gpm
+   --disable-speech-support --without-x
+
+If building the Papenmeier driver, then also consider:
+
+   --disable-pm-configfile
+
+Remember to do "make distclean" if you need to reconfigure BRLTTY.
+
+Assuming there are no errors, the next step is to actually write the
+floppies.
+
+So get two formatted floppies. One will be the boot floppy, the other the
+ramdisk floppy. To write the boot floppy do:
+
+dd if=myboot.img of=/dev/fd0 bs=512
+
+To write the ramdisk floppy do:
+
+dd if=myroot.img of=/dev/fd0 bs=512
+
+That's it!
+
+So now boot the boot floppy on the machine you want to install. At the
+prompt you can press ENTER to choose the brltty entry. 
+
+If you want to specify boot parameters, remember to use the label
+"brltty" instead of "linux".
+
+You can pass options to BRLTTY from the boot prompt by using option names
+similar to those in the brltty.conf file: use only uppercase, use underscores
+instead of minus signs, and prefix with BRLTTY_. For example:
+
+brltty BRLTTY_BRAILLE_DEVICE=/dev/ttyS1
+
+When you press ENTER, syslinux will load the kernel. When the floppy
+stops spinning the kernel will start to run and will prompt you for the
+ramdisk floppy. Put the second floppy in and press ENTER. The kernel will
+load the ramdisk. When the floppy stops spinning, BRLTTY SHOULD
+(hopefullY) come on and the installation program should start.
+
+One of the problems you may have during the installation process is that
+some autoconfiguration programs may play around with the serial ports to
+try to autodetect hardware, thereby upsetting them and causing BRLTTY to
+stop working. This can also occur when the system itself boots. More
+recent RedHat versions are less aggressive though. Two tricks if you have
+problems:
+-If kudzu runs when your system boots and upsets BRLTTY, either disable
+that service (using chkconfig or ntsysv) or modify /etc/sysconfig/kudzu
+or the script that uses kudzu to make it use the --safe option.
+-To get the installation program to leave a serial port alone, you might
+try to modify the ramdisk to change /dev/ttyS0 to say /dev/ttySmyown, and
+tell BRLTTY to use that as it's serial port. This might have the effect
+of hiding the serial port from bad applications.
+
+Note that you must also install BRLTTY on your new machine. When it will boot
+Linux after the installation, BRLTTY will not be installed and the machine will
+be inaccessible. One way to simplify things is to use the statically linked
+brltty you compiled for the installation disk, as it is fairly self-contained.
+Once it's running, then you can build yourself a shared version and modify your
+boot scripts or inittab to have BRLTTY start automatically. A nice way to get
+brltty onto your new machine is this: during the installation process, after
+your target partition has been formatted, it is (at least with the current RH
+installation program) mounted under /mnt/sysimage. You can temporarily put
+aside the installation program and get a shell by going to the second virtual
+console (alt-F2). There you can do:
+
+cp /sbin/brltty /mnt/sysimage/bin/
+
+I am not sure if at that point it is possible to edit inittab or startup
+scripts. Otherwise on your first boot, you just have to log in as root
+(without braille support) and type "/bin/brltty" and if all went well
+BRLTTY should come on.
+
+Good luck!
diff --git a/Bootdisks/bp2cf b/Bootdisks/bp2cf
new file mode 100755
index 0000000..19266e7
--- /dev/null
+++ b/Bootdisks/bp2cf
@@ -0,0 +1,262 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Convert the boot parameter to configuration file directives.
+# If /proc is mounted then use the brltty= boot parameter in /proc/cmdline.
+# If /proc is not mounted then use the brltty environment variable.
+# Invoke with -h for usage information.
+
+programName="${0##*/}"
+programMessage()
+{
+   echo 2>&1 "${programName}: ${1}"
+}
+syntaxError()
+{
+   programMessage "${1}"
+   exit 2
+}
+internalError()
+{
+   programMessage "${1}"
+   exit 3
+}
+
+configurationFile=""
+requestedAction=create
+deviceTranslation=none
+requestedParameter=""
+OPTIND=1
+while getopts ":f:cundop:h" option
+do
+   case "${option}"
+   in
+      f) configurationFile="${OPTARG}";;
+      c) requestedAction=create;;
+      u) requestedAction=update;;
+      n) deviceTranslation=none;;
+      d) deviceTranslation=devfs;;
+      o) deviceTranslation=old;;
+      p) requestedParameter="${OPTARG}";;
+      h)
+         cat <<EOF
+Usage: ${programName} [option ...]
+-f file  The configuration file to create/update.
+-c       Create the configuration file (write to stdout if no -f).
+-u       Update the configuration file (copying from stdin to stdout if no -f).
+-n       Do not translate device paths.
+-d       Do old-style to devfs device path translation.
+-o       Do devfs to old-style device path translation.
+-p [driver][,[device][,[table]]]
+         Explicitly specify the boot parameter.
+-h       Display this usage summary.
+EOF
+         exit 0
+         ;;
+     \?) syntaxError "unknown option: -${OPTARG}";;
+      :) syntaxError "missing value: -${OPTARG}";;
+      *) internalError "unimplemented option: -${option}";;
+   esac
+done
+shift "`expr $OPTIND - 1`"
+[ "${#}" -eq 0 ] || syntaxError "too many parameters."
+
+case "${requestedAction}"
+in
+   create)
+      putConfigurationLine()
+      {
+         echo "${1}" || exit 4
+      }
+      startConfigurationFile()
+      {
+         [ -n "${configurationFile}" ] && exec >"${configurationFile}"
+         putConfigurationLine "`makeHeaderLine Created`"
+         putConfigurationLine "`makeParameterLine`"
+         putConfigurationLine ""
+      }
+      putConfigurationDirective()
+      {
+         putConfigurationLine "${1} ${2}"
+      }
+      finalizeConfigurationFile()
+      {
+         :
+      }
+      ;;
+   update)
+      putSedCommand()
+      {
+         sedScript="${sedScript}
+${1}"
+      }
+      startConfigurationFile()
+      {
+         if [ -n "${configurationFile}" ]
+         then
+            [ -e "${configurationFile}" ] || syntaxError "file not found: ${configurationFile}"
+            [ -f "${configurationFile}" ] || syntaxError "not a file: ${configurationFile}"
+            [ -r "${configurationFile}" ] || syntaxError "file not readable: ${configurationFile}"
+            [ -w "${configurationFile}" ] || syntaxError "file not writable: ${configurationFile}"
+            outputFile="${configurationFile}.new"
+            exec <"${configurationFile}" >"${outputFile}"
+         fi
+         sedScript=""
+         putSedCommand "\$a\\
+\\
+`makeHeaderLine Updated`\\
+`makeParameterLine`\\
+"
+      }
+      putConfigurationDirective()
+      {
+         value="`echo "${2}" | sed -e 's%\\([/\\]\\)%\\\\\\1%g'`"
+         putSedCommand "/^[[:space:]]*${1}[[:space:]]/s/^/#/"
+         putSedCommand "\$a${1} ${value}"
+      }
+      finalizeConfigurationFile()
+      {
+         sed -e "${sedScript}"
+         [ -n "${outputFile}" ] && mv -f "${outputFile}" "${configurationFile}"
+      }
+      ;;
+   *) internalError "unimplemented action: ${requestedAction}";;
+esac
+
+translateDevice_none()
+{
+   :
+}
+translateDevice_devfs()
+{
+   minor="${device#ttyS}"
+   if [ "${minor}" != "${device}" ]
+   then
+      device="tts/${minor}"
+      return 0
+   fi
+   minor="${device#lp}"
+   if [ "${minor}" != "${device}" ]
+   then
+      device="printers/${minor}"
+      return 0
+   fi
+   programMessage "unsupported old-style device: ${device}"
+}
+translateDevice_old()
+{
+   major="${device%%/*}" 
+   if [ "${major}" != "${device}" ]
+   then
+      minor="${device#*/}"
+      case "${major}"
+      in
+         tts) devfs="ttyS${minor}";;
+         printers) devfs="lp${minor}";;
+      esac
+   fi
+   if [ -n "${devfs}" ]
+   then
+      device="${devfs}"
+   else
+      programMessage "unsupported devfs device: ${device}"
+   fi
+}
+
+makeHeaderLine()
+{
+   echo "# ${1} by brltty-bp2cf`date +' on %Y-%m-%d at %H:%M:%S %Z (UTC%z)'`."
+}
+makeParameterLine()
+{
+   echo "# Boot Parameter:${bootParameter}"
+}
+putConfigurationFile()
+{
+   startConfigurationFile
+   [ -n "${brailleDriver}" ] && putConfigurationDirective "braille-driver" "${brailleDriver}"
+   [ -n "${brailleDevice}" ] && {
+      device="`echo "${brailleDevice}" | sed -e 's%//*%/%g' -e 's%^/dev/%%'`"
+      if [ "${device#/}" = "${device}" ]
+      then
+         translateDevice_${deviceTranslation}
+      fi
+      putConfigurationDirective "braille-device" "${device}"
+   }
+   [ -n "${textTable}" ] && putConfigurationDirective "text-table" "${textTable}"
+   finalizeConfigurationFile
+}
+parseBootParameter()
+{
+   bootParameter="${bootParameter} ${1}"
+   number=1
+   while [ "${number}" -le 3 ]
+   do
+      cut="cut -d, -f${number}"
+      [ "${number}" -gt 1 ] && cut="${cut} -s"
+      operand="`echo ${1} | ${cut}`"
+      if [ -n "${operand}" ]
+      then
+         case "${number}"
+         in
+            1) brailleDriver="${operand}";;
+            2) brailleDevice="${operand}";;
+            3) textTable="${operand}";;
+         esac
+      fi
+      number="`expr ${number} + 1`"
+   done
+}
+putBootParameter()
+{
+   parseBootParameter "${1}"
+   putConfigurationFile
+}
+parseBootCommand()
+{
+   found=false
+   while [ "${#}" -gt 0 ]
+   do
+      case "${1}"
+      in
+         "brltty="*)
+            found=true
+            parseBootParameter "${1#*=}"
+            ;;
+      esac
+      shift
+   done
+   "${found}" && putConfigurationFile
+}
+
+brailleDriver=""
+brailleDevice=""
+textTable=""
+bootCommandFile="/proc/cmdline"
+if [ -n "${requestedParameter}" ]
+then
+   putBootParameter "${requestedParameter}"
+elif [ -f "${bootCommandFile}" ]
+then
+   parseBootCommand `cat "${bootCommandFile}"`
+elif [ -n "${brltty}" ]
+then
+   putBootParameter "${brltty}"
+fi
+exit 0
diff --git a/Bootdisks/rhmkboot b/Bootdisks/rhmkboot
new file mode 100755
index 0000000..20e90de
--- /dev/null
+++ b/Bootdisks/rhmkboot
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -e
+
+# Make local mount points
+mkdir -p mnt1 mnt2
+
+# Work on a copy of bootdisk image
+cp -f boot.img myboot.img
+mount myboot.img mnt1 -o loop -t msdos
+
+# Modify syslinux.cfg
+cat >cfg <<EOF
+label brltty
+  kernel vmlinuz
+  append root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 lang= text devfs=nomount ramdisk_size=9216
+EOF
+cat mnt1/syslinux.cfg |sed 's/default linux/default brltty/' >>cfg
+cp cfg mnt1/syslinux.cfg
+umount mnt1
diff --git a/Bootdisks/rhmkiso b/Bootdisks/rhmkiso
new file mode 100755
index 0000000..8052462
--- /dev/null
+++ b/Bootdisks/rhmkiso
@@ -0,0 +1,333 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+programName="${0##*/}"
+programMessage() {
+   echo >&2 "${programName}: ${1}"
+}
+programError() {
+   [ -n "${1}" ] && programMessage "${1}"
+   exit "${2:-3}"
+}
+syntaxError() {
+   programError "${1}" 2
+}
+
+mount_ext2() {
+   mount -o loop -t ext2 "${2}" "${1}"
+}
+unmount_ext2() {
+   umount "${1}"
+}
+check_ext2() {
+   set -- $(stat -c '%a %s' -f -- "${initrdRoot}")
+   initrdAvailableBlocks="${1}"
+   initrdBlockSize="${2}"
+   set -- $(du --block-size="${initrdBlockSize}" -s "${brlttyInstallRoot}")
+   brlttyInstallBlocks="${1}"
+   set -- $(stat -c '%s' -- "${initrdImage}")
+   initrdImageBytes="${1}"
+   initrdImageBlocks=$(( (initrdImageBytes + initrdBlockSize - 1) / initrdBlockSize ))
+   initrdPadBlocks=$(( brlttyInstallBlocks - initrdAvailableBlocks ))
+   (( (initrdPadBlocks += 200) > 0 )) && {
+      initrdMounted=false
+      "unmount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+
+      dd bs="${initrdBlockSize}" count="${initrdPadBlocks}" if="/dev/zero" of="${initrdImage}" seek="${initrdImageBlocks}"
+      e2fsck -y -f "${initrdImage}" && resize2fs "${initrdImage}" || {
+         newImage="${temporaryDirectory}/new.${initrdType}"
+         cp -RpPd -- "${initrdImage}" "${newImage}"
+         mke2fs -q -b "${initrdBlockSize}" -F "${newImage}"
+
+         newRoot="${temporaryDirectory}/new"
+         mkdir "${newRoot}"
+
+         "mount_${initrdType}" "${newRoot}" "${newImage}"
+         newMounted=true
+
+         "mount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+         initrdMounted=true
+         cp -RpPd -- "${initrdRoot}/." "${newRoot}"
+         initrdMounted=false
+         "unmount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+
+         newMounted=false
+         "unmount_${initrdType}" "${newRoot}" "${newImage}"
+
+         cp -- "${newImage}" "${initrdImage}"
+      }
+
+      "mount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+      initrdMounted=true
+   }
+}
+
+mount_cpio() {
+   cd "${1}"
+   cpio --quiet -i -I "${2}"
+   cd "${workingDirectory}"
+}
+unmount_cpio() {
+   cd "${1}"
+   [ -n "${2}" ] && {
+      find . -print |
+      sed -e '/^\.$/d' -e 's/^\.\///' |
+      cpio --quiet -o -H newc -O "${2}"
+   }
+   rm -f -r -- $(ls -1 -A)
+   cd "${workingDirectory}"
+}
+check_cpio() {
+   :
+}
+
+originalImage="boot.iso"
+modifiedImage="brltty.iso"
+brlttyPattern="brltty-*"
+brailleDrivers="all"
+textTable="en-nabcc"
+invokeShell=false
+
+showUsage() {
+   cat <<EOF
+Usage: ${programName} [-option ...]
+The following options are supported:
+-i file         The original (input) image (default: ${originalImage}).
+-o file         The modified (output) image (default: ${modifiedImage}).
+-a file         The BRLTTY archive (default: ${brlttyPattern}).
+-b driver,...   The braille driver(s) to include (default: ${brailleDrivers}).
+-t file         The text table to build in (default: ${textTable}).
+-s              Invoke an interactive shell to inspect the modified image.
+-h              Display usage information and exit.
+EOF
+   exit 0
+}
+
+while getopts ":i:o:a:b:t:sh" option
+do
+   case "${option}"
+   in
+      i) originalImage="${OPTARG}";;
+      o) modifiedImage="${OPTARG}";;
+      a) brlttyPattern="${OPTARG}";;
+      b) brailleDrivers="${OPTARG}";;
+      t) textTable="${OPTARG}";;
+      s) invokeShell=true;;
+      h) showUsage;;
+     \?) syntaxError "unknown option: -${OPTARG}";;
+      :) syntaxError "missing operand: -${OPTARG}";;
+      *) syntaxError "unimplemented option: -${option}";;
+   esac
+done
+shift $((OPTIND - 1))
+[ "${#}" -gt 0 ] && syntaxError "too many parameters."
+
+[ -f "${originalImage}" ] || programError "original image not found: ${originalImage}"
+
+shopt -s nullglob extglob
+set -- $brlttyPattern
+[ "${#}" -eq 0 ] && programError "brltty archive not found: ${brlttyPattern}"
+[ "${#}" -gt 1 ] && {
+   brlttyArchives="$(echo "${*}" | sed -e 's% %,&%g')"
+   programError "more than one brltty archive: ${brlttyArchives}"
+}
+brlttyArchive="${1}"
+
+cleanup() {
+   set +e
+   cd /
+   "${newMounted}" && "unmount_${initrdType}" "${newRoot}"
+   "${initrdMounted}" && "unmount_${initrdType}" "${initrdRoot}"
+   "${originalMounted}" && umount "${originalRoot}"
+   [ -n "${temporaryDirectory}" ] && rm -r "${temporaryDirectory}"
+}
+set -e
+originalMounted=false
+initrdMounted=false
+newMounted=false
+trap "cleanup" 0
+
+workingDirectory="${PWD}"
+temporaryDirectory="$(mktemp -d "${TMPDIR:-/tmp}/${programName}.XXXXXX")"
+
+originalRoot="${temporaryDirectory}/original"
+mkdir "${originalRoot}"
+mount -o loop -t iso9660 "${originalImage}" "${originalRoot}"
+originalMounted=true
+
+modifiedRoot="${temporaryDirectory}/modified"
+mkdir "${modifiedRoot}"
+cp -RpPd -- "${originalRoot}/." "${modifiedRoot}"
+
+loaderDirectory=""
+for directory in boot/isolinux isolinux
+do
+   [ -d "${originalRoot}/${directory}" ] && {
+      loaderDirectory="${directory}"
+      break
+   }
+done
+
+configurationFile="isolinux.cfg"
+sed -n -e '
+1i\
+prompt 1\
+timeout 0\
+default text\
+
+/^ *prompt /d
+/^ *timeout /d
+/^ *default /d
+/^ *menu  *default *$/d
+/^ *append.* text/s/$/ nofb/
+/^ *append.* rescue/s/$/ text nofb/
+p
+' <"${originalRoot}/${loaderDirectory}/${configurationFile}" >"${modifiedRoot}/${loaderDirectory}/${configurationFile}"
+
+# bootMessage="boot.msg"
+# echo -n -e '\a' >"${modifiedRoot}/${loaderDirectory}/${bootMessage}"
+# cat "${originalRoot}/${loaderDirectory}/${bootMessage}" >>"${modifiedRoot}/${loaderDirectory}/${bootMessage}"
+
+initrdImage="${temporaryDirectory}/initrd.img"
+initrdPath="${modifiedRoot}/${loaderDirectory}/initrd.img"
+gunzip -c "${initrdPath}" >"${initrdImage}"
+
+initrdDescription="`file -b "${initrdImage}"`"
+case "${initrdDescription}"
+in
+   *" ext2 "*) initrdType=ext2;;
+   *" cpio "*) initrdType=cpio;;
+   *) programError "unsupported initrd image format: ${initrdDescription}"
+esac
+
+initrdRoot="${temporaryDirectory}/initrd"
+mkdir "${initrdRoot}"
+"mount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+initrdMounted=true
+
+found=false
+for name in linuxrc init
+do
+   linuxrcPath="${initrdRoot}/${name}"
+   initPath="$(readlink "${linuxrcPath}")" && {
+      found=true
+      break
+   }
+done
+"${found}" || programError "linuxrc not found."
+rm "${linuxrcPath}"
+ln -s /bin/brltty "${linuxrcPath}"
+
+deviceDirectory="${initrdRoot}/dev"
+ensureDevices() {
+   typeset type="${1}"
+   typeset major="${2}"
+   typeset minor="${3}"
+   typeset prefix="${4}"
+   typeset last="${5}"
+   typeset suffix="${6}"
+   typeset number=0
+   while ((number <= last))
+   do
+      typeset path="${deviceDirectory}/${prefix}${suffix}"
+      [ -e "${path}" ] || mknod -m 600 "${path}" "${type}" "${major}" "$((minor++))"
+      suffix=$((++number))
+   done
+}
+ensureDevices c 7 0 vcs 12
+ensureDevices c 7 128 vcsa 12
+ensureDevices c 5 1 console 0
+ensureDevices c 4 0 tty 12 0
+ensureDevices c 4 64 ttyS 3 0
+ensureDevices c 188 0 ttyUSB 1 0
+
+case "${brlttyArchive}"
+in
+   *.tar.gz|*.tgz)
+      tar x -z -C "${temporaryDirectory}" -f "${brlttyArchive}"
+      ;;
+
+   *.tar)
+      tar x -C "${temporaryDirectory}" -f "${brlttyArchive}"
+      ;;
+
+   *)
+      programError "unsupported brltty archive name: ${brlttyArchive}"
+      ;;
+esac
+
+shopt -s nullglob
+set -- ${temporaryDirectory}/brltty*
+[ "${#}" -eq 0 ] && programError "brltty build directory not found."
+brlttyBuildRoot="${1}"
+
+brlttyInstallRoot="${temporaryDirectory}/install"
+(
+   set -e
+   trap "" 0
+   cd "${brlttyBuildRoot}"
+
+   shopt -s nullglob
+   set -- ./autogen*
+   for autogen
+   do
+      [ -f "${autogen}" -a -r "${autogen}"  -a -x "${autogen}" ]  && "${autogen}"
+   done
+
+   ./configure --quiet --with-install-root="${brlttyInstallRoot}" \
+	       --with-init-path="${initPath}" --with-stderr-path="/dev/tty5" \
+               --with-writable-directory="/tmp" \
+               --with-screen-driver="lx,-all" \
+	       --with-braille-driver="-tt,-vr,${brailleDrivers}" \
+	       --with-text-table="${textTable}" \
+	       --disable-speech-support --without-libbraille --without-curses --disable-x \
+	       --disable-pcm-support --disable-fm-support --disable-midi-support \
+               --enable-standalone-programs --disable-api --disable-gpm --disable-icu
+   make -s -C Programs all
+   strip Programs/brltty
+   make -s -C Programs install-programs install-data-files install-tables
+) || exit "${?}"
+
+"check_${initrdType}"
+(
+   set -e
+   cd "${brlttyInstallRoot}"
+   cp --parents -- $(find . -type f -printf '%P ') "${initrdRoot}"
+) || exit 1
+
+"${invokeShell}" && (
+   set +e
+   trap "" 0
+
+   cd "${temporaryDirectory}"
+   "${SHELL:-/bin/sh}"
+   exit 0
+)
+
+initrdMounted=false
+"unmount_${initrdType}" "${initrdRoot}" "${initrdImage}"
+gzip -c -9 "${initrdImage}" >"${initrdPath}"
+
+isoVolumeIdentifier="$(isoinfo -d -i "${originalImage}" | grep '^Volume id:' | cut -d' ' -f3-)"
+isoApplicationIdentifier="$(isoinfo -d -i "${originalImage}" | grep '^Application id:' | cut -d' ' -f3-)"
+mkisofs -quiet -J -R -T -boot-load-size 4 -boot-info-table -no-emul-boot \
+	-A "${isoApplicationIdentifier}" -V "${isoVolumeIdentifier}" \
+	-b "${loaderDirectory}/isolinux.bin" -c "${loaderDirectory}/boot.cat" \
+	-o "${modifiedImage}" "${modifiedRoot}"
+exit 0
diff --git a/Bootdisks/rhmkroot b/Bootdisks/rhmkroot
new file mode 100755
index 0000000..68f40f6
--- /dev/null
+++ b/Bootdisks/rhmkroot
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+set -e
+
+# Make local mount points
+mkdir -p mnt1 mnt2
+# Mount bootdisk image read-only
+mount boot.img mnt1 -o loop,ro -t msdos
+# Get the initial ramdisk it contains
+cp mnt1/initrd.img .
+# Finished with boot image for now.
+umount mnt1
+# Unzip and mount it
+mv initrd.img initrd.gz
+gunzip initrd.gz
+# Prevent warning about having to check the filesystem
+tune2fs -c 0 -i 0 initrd >/dev/null
+mount initrd mnt2 -o loop
+
+# Work in initial ramdisk
+# Rename it's init
+mv mnt2/sbin/init mnt2/sbin/init_real
+# Put BRLTTY on it
+cp brltty mnt2/sbin
+# New init is a link to BRLTTY
+ln -s brltty mnt2/sbin/init
+# Help files and tables (if the person isn't familiar...)
+if [ -d etcbrltty ]; then
+  mkdir -p mnt2/etc/brltty
+  install etcbrltty/* mnt2/etc/brltty
+fi
+# Make sure some needed device files are present
+# ttyS0 ttyS1 tty0
+for i in ttyS0 ttyS1 tty0; do
+  if [ ! -e mnt2/dev/$i ]; then
+    cp -a /dev/$i mnt2/dev/$i
+    chown root:root mnt2/dev/$i
+  fi
+done
+# vcsa or vcsa0
+# NB this doesn't handle devfs...
+if [ ! -e mnt2/dev/vcsa -a ! -e mnt2/dev/vcsa0 ]; then
+  for i in vcsa vcsa0; do
+    if [ -c /dev/$i ]; then
+      if [ -h /dev/$i ]; then
+        tgt="`readlink /dev/$i`"
+        # This assumes the link is relative...
+        if [ -c /dev/$tgt ]; then
+          cp -a /dev/$tgt mnt2/dev
+        else
+          echo "Failed to get link target of /dev/$i"
+          echo "Failed!!"
+          exit 1
+        fi
+      fi
+      cp -a /dev/$i mnt2/dev
+    fi
+  done
+fi
+if [ ! -e mnt2/dev/vcsa -a ! -e mnt2/dev/vcsa0 ]; then
+  echo "Failed to create mnt2/dev/vcsa"
+  echo "Failed!"
+  exit 1
+fi
+
+# Done with initial ramdisk. wrap it up.
+umount mnt2
+gzip -9 initrd
+mv initrd.gz myroot.img
diff --git a/Build/Logs/Android.log b/Build/Logs/Android.log
new file mode 100644
index 0000000..aceb022
--- /dev/null
+++ b/Build/Logs/Android.log
@@ -0,0 +1,45 @@
+cfg-android: preparing ABI build trees
+cfg-android: configuring top-level for armeabi-v7a
+cfg-android-abi: installing tool chain for ABI: armeabi-v7a
+Toolchain installed to .../brltty/Android/ABI/armeabi-v7a/ToolChain.
+cfg-android-abi: configuring build: .../brltty
+configure: WARNING: using cross tools not prefixed with host triplet
+cfg-android-abi: installing tool chain for ABI: arm64-v8a
+Toolchain installed to .../brltty/Android/ABI/arm64-v8a/ToolChain.
+cfg-android-abi: configuring build: .../brltty/Android/ABI/arm64-v8a
+configure: WARNING: using cross tools not prefixed with host triplet
+cfg-android-abi: configuring build: .../brltty/Android/ABI/armeabi-v7a
+configure: WARNING: using cross tools not prefixed with host triplet
+cfg-android-abi: installing tool chain for ABI: x86
+Toolchain installed to .../brltty/Android/ABI/x86/ToolChain.
+cfg-android-abi: configuring build: .../brltty/Android/ABI/x86
+configure: WARNING: using cross tools not prefixed with host triplet
+cfg-android-abi: installing tool chain for ABI: x86_64
+Toolchain installed to .../brltty/Android/ABI/x86_64/ToolChain.
+cfg-android-abi: configuring build: .../brltty/Android/ABI/x86_64
+configure: WARNING: using cross tools not prefixed with host triplet
+.../brltty/Programs/menu.c:42:2: warning: file globbing support not available on this platform [-W#warnings]
+#warning file globbing support not available on this platform
+ ^
+1 warning generated.
+.../brltty/Programs/menu.c:42:2: warning: file globbing support not available on this platform [-W#warnings]
+#warning file globbing support not available on this platform
+ ^
+1 warning generated.
+.../brltty/Programs/menu.c:42:2: warning: file globbing support not available on this platform [-W#warnings]
+#warning file globbing support not available on this platform
+ ^
+1 warning generated.
+.../brltty/Programs/menu.c:42:2: warning: file globbing support not available on this platform [-W#warnings]
+#warning file globbing support not available on this platform
+ ^
+1 warning generated.
+Note: Some input files use or override a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+Note: Some input files use unchecked or unsafe operations.
+Note: Recompile with -Xlint:unchecked for details.
+Note: Some input files use or override a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+Note: Some input files use unchecked or unsafe operations.
+Note: Recompile with -Xlint:unchecked for details.
+rebuild: done
diff --git a/Build/Logs/DOS.log b/Build/Logs/DOS.log
new file mode 100644
index 0000000..a62db4a
--- /dev/null
+++ b/Build/Logs/DOS.log
@@ -0,0 +1,23 @@
+mkdosarc: configuring build
+configure: WARNING: using cross tools not prefixed with host triplet
+configure: WARNING: function not available: systemd:sd_session_get_vt()
+mkdosarc: building programs
+.../brltty/Programs/lock.c:213:2: warning: #warning thread lock support not available on this platform
+In file included from .../brltty/Drivers/Braille/TTY/braille.c:40:
+.../brltty/Headers/get_curses.h:54:2: warning: #warning curses package either unspecified or unsupported
+.../brltty/Programs/io_misc.c:452:2: warning: #warning sockets not supported on this platform
+In file included from .../brltty/Programs/brltty-ttb.c:1294:
+.../brltty/Headers/get_curses.h:54:2: warning: #warning curses package either unspecified or unsupported
+mkdosarc: building documentation
+Processing file .../brltty/Documents/Manual-BRLTTY/English/BRLTTY.sgml
+Processing file .../brltty/Documents/Manual-BRLTTY/English/BRLTTY.sgml
+Processing file .../brltty/Documents/Manual-BRLTTY/French/BRLTTY.sgml
+Processing file .../brltty/Documents/Manual-BRLTTY/French/BRLTTY.sgml
+Processing file .../brltty/Documents/Manual-BrlAPI/English/BrlAPI.sgml
+Processing file .../brltty/Documents/Manual-BrlAPI/English/BrlAPI.sgml
+mkdosarc: installing files
+mkdosarc: updating files
+mkdosarc: converting text files
+mkdosarc: checking for name collisions
+mkdosarc: creating archive
+mkdosarc: done
diff --git a/Build/Logs/Windows-libusb-1.0.log b/Build/Logs/Windows-libusb-1.0.log
new file mode 100644
index 0000000..c144225
--- /dev/null
+++ b/Build/Logs/Windows-libusb-1.0.log
@@ -0,0 +1,69 @@
+mkwin: revision identifier: BRLTTY-6.4-290-gb713062eM
+mkwin: MinGW command not found: linuxdoc
+mkwin: copying source tree
+mkwin: applying patches
+patching file Programs/usb_libusb-1.0.c
+patching file Programs/usb_libusb.c
+mkwin: configuring build
+mkwin: building programs
+In file included from c:\Program Files (x86)\Python38-32\include/Python.h:85,
+                 from brlapi.auto.c:4:
+c:\Program Files (x86)\Python38-32\include/pytime.h:123:59: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  123 | PyAPI_FUNC(int) _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv);
+      |                                                           ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:130:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  130 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:135:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  135 |     struct timeval *tv,
+      |            ^~~~~~~
+brlapi.auto.c: In function '__Pyx_ImportType':
+brlapi.auto.c:24151:13: warning: unknown conversion type character 'z' in format [-Wformat=]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+brlapi.auto.c:24152:24: note: format string is defined here
+24152 |             "Expected %zd from C header, got %zd from PyObject",
+      |                        ^
+brlapi.auto.c:24151:13: warning: unknown conversion type character 'z' in format [-Wformat=]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+brlapi.auto.c:24152:47: note: format string is defined here
+24152 |             "Expected %zd from C header, got %zd from PyObject",
+      |                                               ^
+brlapi.auto.c:24151:13: warning: too many arguments for format [-Wformat-extra-args]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In file included from c:\Program Files (x86)\Python38-32\include/Python.h:85,
+                 from ../../../source/Bindings/Python/bindings.c:25:
+c:\Program Files (x86)\Python38-32\include/pytime.h:123:59: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  123 | PyAPI_FUNC(int) _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv);
+      |                                                           ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:130:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  130 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:135:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  135 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+mkwin: building documents
+linuxdoc is not installed - document will not be made
+linuxdoc is not installed - document will not be made
+linuxdoc is not installed - document will not be made
+mkwin: installing files
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+mkwin: updating files
+mkwin: USB package: libusb-1.0
+mkwin: installing package files: LibUSB-1.0 (aka LibUSBx)
+mkwin: installing package files: LibUSB-1.0 WinUSB Coinstallers
+   Creating library brlapi.lib and object brlapi.exp
+mkwin: creating configurator
+Successfully compiled: C:\Users\dave\AppData\Local\Temp\brltty-mkwin\install\brltty-win-6.4-290-libusb-1.0\brlttycnf.exe
+mkwin: converting text files
+mkwin: creating archive
+mkwin: creating installer
+mkwin: cleaning up
+mkwin: done
diff --git a/Build/Logs/Windows-libusb.log b/Build/Logs/Windows-libusb.log
new file mode 100644
index 0000000..d98aaf1
--- /dev/null
+++ b/Build/Logs/Windows-libusb.log
@@ -0,0 +1,68 @@
+mkwin: revision identifier: BRLTTY-6.4-290-gb713062eM
+mkwin: MinGW command not found: linuxdoc
+mkwin: copying source tree
+mkwin: applying patches
+patching file Programs/usb_libusb-1.0.c
+patching file Programs/usb_libusb.c
+mkwin: configuring build
+mkwin: building programs
+In file included from c:\Program Files (x86)\Python38-32\include/Python.h:85,
+                 from brlapi.auto.c:4:
+c:\Program Files (x86)\Python38-32\include/pytime.h:123:59: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  123 | PyAPI_FUNC(int) _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv);
+      |                                                           ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:130:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  130 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:135:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  135 |     struct timeval *tv,
+      |            ^~~~~~~
+brlapi.auto.c: In function '__Pyx_ImportType':
+brlapi.auto.c:24151:13: warning: unknown conversion type character 'z' in format [-Wformat=]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+brlapi.auto.c:24152:24: note: format string is defined here
+24152 |             "Expected %zd from C header, got %zd from PyObject",
+      |                        ^
+brlapi.auto.c:24151:13: warning: unknown conversion type character 'z' in format [-Wformat=]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+brlapi.auto.c:24152:47: note: format string is defined here
+24152 |             "Expected %zd from C header, got %zd from PyObject",
+      |                                               ^
+brlapi.auto.c:24151:13: warning: too many arguments for format [-Wformat-extra-args]
+24151 |             "%s.%s size changed, may indicate binary incompatibility. "
+      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In file included from c:\Program Files (x86)\Python38-32\include/Python.h:85,
+                 from ../../../source/Bindings/Python/bindings.c:25:
+c:\Program Files (x86)\Python38-32\include/pytime.h:123:59: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  123 | PyAPI_FUNC(int) _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv);
+      |                                                           ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:130:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  130 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\include/pytime.h:135:12: warning: 'struct timeval' declared inside parameter list will not be visible outside of this definition or declaration
+  135 |     struct timeval *tv,
+      |            ^~~~~~~
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+mkwin: building documents
+linuxdoc is not installed - document will not be made
+linuxdoc is not installed - document will not be made
+linuxdoc is not installed - document will not be made
+mkwin: installing files
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+c:\Program Files (x86)\Python38-32\lib\site-packages\setuptools\command\bdist_wininst.py:20: SetuptoolsDeprecationWarning: bdist_wininst is deprecated and will be removed in a future version. Use bdist_wheel (wheel packages) instead.
+  warnings.warn(
+mkwin: updating files
+mkwin: USB package: libusb
+mkwin: installing package files: LibUSB (aka LibUSB-Win32)
+   Creating library brlapi.lib and object brlapi.exp
+mkwin: creating configurator
+Successfully compiled: C:\Users\dave\AppData\Local\Temp\brltty-mkwin\install\brltty-win-6.4-290-libusb\brlttycnf.exe
+mkwin: converting text files
+mkwin: creating archive
+mkwin: creating installer
+mkwin: cleaning up
+mkwin: done
diff --git a/Build/mk-android b/Build/mk-android
new file mode 100755
index 0000000..bc52d48
--- /dev/null
+++ b/Build/mk-android
@@ -0,0 +1,28 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/mk-prologue.sh"
+parseProgramArguments "${@}"
+
+mkPlatformName="Android"
+mkOldLogName="Android"
+mkNewLogName="${programName}"
+
+mkBuild "${sourceRoot}/Android/Tools/rebuild"
+exit 0
diff --git a/Build/mk-dos b/Build/mk-dos
new file mode 100755
index 0000000..6585627
--- /dev/null
+++ b/Build/mk-dos
@@ -0,0 +1,29 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/mk-prologue.sh"
+parseProgramArguments "${@}"
+
+mkPlatformName="DOS"
+mkOldLogName="DOS"
+mkNewLogName="${programName}"
+
+dosDirectory="${sourceRoot}/DOS"
+mkBuild "${dosDirectory}/mkdosarc" -T "${dosDirectory}/ToolChain" -o -- "${sourceRoot}"
+exit 0
diff --git a/Build/mk-prologue.sh b/Build/mk-prologue.sh
new file mode 100644
index 0000000..d5fe589
--- /dev/null
+++ b/Build/mk-prologue.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${BASH_SOURCE[0]}")/../prologue.sh"
+setSourceRoot
+
+readonly mkLogFileExtension="log"
+readonly mkLogsDirectory="${sourceRoot}/Build/Logs"
+mkDiffOptions=()
+
+mkIgnoreMismatch() {
+   local pattern="${1}"
+   mkDiffOptions+=( -I "${pattern}" )
+}
+
+mkBuild() {
+   [ -n "${mkPlatformName}" ] || internalError "platform name not defined"
+   [ -n "${mkOldLogName}" ] || internalError "old log name not defined"
+   [ -n "${mkNewLogName}" ] || internalError "new log name not defined"
+
+   logMessage task "building for ${mkPlatformName}"
+   local newLogFile="${mkNewLogName}.${mkLogFileExtension}"
+   local oldLogFile="${mkLogsDirectory}/${mkOldLogName}.${mkLogFileExtension}"
+
+   "${@}" &>"${newLogFile}" || {
+      local exitStatus="${?}"
+      logError "build failed with exit status ${exitStatus} - see ${newLogFile}"
+      exit 10
+   }
+
+   logMessage task "build completed successfully"
+   sed -e "s%${sourceRoot}%.../brltty%g" -i "${newLogFile}"
+   diff "${mkDiffOptions[@]}" -- "${oldLogFile}" "${newLogFile}"
+}
diff --git a/Build/mk-windows b/Build/mk-windows
new file mode 100755
index 0000000..e8e97b3
--- /dev/null
+++ b/Build/mk-windows
@@ -0,0 +1,60 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/mk-prologue.sh"
+windowsDirectory="${sourceRoot}/Windows"
+. "${windowsDirectory}/mingw.sh"
+
+readonly defaultUsbPackageNames=( libusb libusb-1.0 )
+
+showProgramUsageNotes() {
+cat <<END_OF_PROGRAM_USAGE_NOTES
+The positional parameters are the names of USB packages. If none are
+specified, the default is: ${defaultUsbPackageNames[*]}
+END_OF_PROGRAM_USAGE_NOTES
+}
+
+processExtraProgramParameters() {
+   [ "${#}" -gt 0 ] || set -- "${defaultUsbPackageNames[@]}"
+   usbPackageNames=( "${@}" )
+}
+
+optionalProgramParameters
+parseProgramArguments "${@}"
+
+mkIgnoreMismatch "^mkwin: revision identifier: "
+mkIgnoreMismatch "^ *Creating library "
+mkIgnoreMismatch "^Successfully compiled: "
+
+mkIgnoreMismatch "^declare: usage: "
+mkIgnoreMismatch "^.*: declare: -[nA]: "
+
+read <"${windowsDirectory}/python-location" pythonLocation || exit "${?}"
+[ -e "${pythonLocation}" ] || logError "Python not found: ${pythonLocation}"
+[ -d "${pythonLocation}" ] || logError "not a directory: ${pythonLocation}"
+
+for usbPackage in "${usbPackageNames[@]}"
+do
+   mkPlatformName="Windows[${usbPackage}]"
+   mkOldLogName="Windows-${usbPackage}"
+   mkNewLogName="${programName}-${usbPackage}"
+   mkBuild "${windowsDirectory}/mkwin" -P "${pythonLocation}" -u "${usbPackage}" -- "${sourceRoot}"
+done
+
+exit 0
diff --git a/DOS/cwsdpmi.exe b/DOS/cwsdpmi.exe
new file mode 100755
index 0000000..17e3220
--- /dev/null
+++ b/DOS/cwsdpmi.exe
Binary files differ
diff --git a/DOS/mkdosarc b/DOS/mkdosarc
new file mode 100755
index 0000000..bb7c249
--- /dev/null
+++ b/DOS/mkdosarc
@@ -0,0 +1,288 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+shopt -s nullglob
+
+defaultArchiveName="brltty-dos"
+defaultInstallDirectory="BRLTTY"
+
+. "`dirname "${0}"`/../prologue.sh"
+addProgramOption a string.name archiveName "specify archive name" "${defaultArchiveName}"
+addProgramOption i string.directory installDirectory "specify installation directory on target system" "${defaultInstallDirectory}"
+addProgramOption o flag overwriteArchive "overwrite existing archive"
+addProgramOption s flag invokeShell "invoke interactive shell to inspect/modify result"
+addProgramOption T string.directory toolsRoot "specify tool chain root directory"
+addProgramParameter source sourceDirectory "top-level directory of source tree"
+parseProgramArguments "${@}"
+
+[ -n "${archiveName}" ] || archiveName="${defaultArchiveName}"
+[ -n "${installDirectory}" ] || installDirectory="${defaultInstallDirectory}"
+[ -n "${toolsRoot}" ] && export DOS_TOOLS_ROOT="${toolsRoot}"
+
+[ -n "${sourceDirectory}" ] || sourceDirectory="."
+verifyInputDirectory "${sourceDirectory}" || semanticError
+sourceDirectory="$(resolveDirectory "${sourceDirectory}")"
+
+archivePath="${initialDirectory}/${archiveName}.zip"
+[ ! -e "${archivePath}" ] || {
+   "${overwriteArchive}" ] || semanticError "archive already exists: ${archivePath}"
+   rm -f -r "${archivePath}"
+}
+
+function convertTextFile {
+   local file="${1}"
+
+   unix2dos -q -o "${file}"
+}
+
+function installFile {
+   local sourceFile="${1}"
+   local targetFile="${2}"
+
+   targetFile="${installDirectory}/${targetFile}"
+   local targetDirectory="${targetFile%/*}"
+   mkdir -p "${targetDirectory}"
+   cp "${sourceFile}" "${targetFile}"
+}
+
+function installCSV {
+   local oldName="${1}"
+   local newName="${2}"
+
+   local extension="csv"
+   installFile "${sourceDirectory}/${documentsSubdirectory}/${oldName}.${extension}" "${documentSubdirectory}/${newName}.${extension}"
+}
+
+function installManualFiles {
+   local directory="${1}"
+
+   cd "${directory}/Documents/Manual-BRLTTY"
+
+   local oldFile
+   for oldFile in $(find . -type f)
+   do
+      local newFile="${oldFile}"
+
+      local subdirectory="${oldFile%/*}"
+      local name="${oldFile##*/}"
+
+      local extension="${name##*.}"
+      name="${name%.*}"
+
+      case "${extension}"
+      in
+         html)
+            sed -r -e 's/"([[:alpha:]]+)(-([[:digit:]]+))?\.html(["#])/"\1\3.htm\4/g' -i "${oldFile}"
+            newFile="${subdirectory}/${name/-/}.${extension:0:3}"
+            ;;
+
+         txt | htm | doc | pdf);;
+         *) continue;;
+      esac
+
+      installFile "${oldFile}" "${manualSubdirectory}/${newFile}"
+   done
+}
+
+needTemporaryDirectory
+
+buildDirectory="${temporaryDirectory}/build"
+mkdir -p "${buildDirectory}"
+
+installDirectory="${temporaryDirectory}/intall/${installDirectory}"
+mkdir -p "${installDirectory}"
+documentSubdirectory="doc"
+manualSubdirectory="${documentSubdirectory}/Manual"
+
+cd "${buildDirectory}"
+logMessage task "configuring build"
+"${sourceDirectory}/cfg-dos" --prefix="${installDirectory}"
+
+logMessage task "building programs"
+make -s -C Programs all-commands
+
+logMessage task "building documentation"
+make -s -C Documents all
+
+logMessage task "installing files"
+make -s -C Programs install-commands install-tables
+
+logMessage task "updating files"
+
+cd "${installDirectory}/bin"
+mv brltty-lscmds.exe brllscmd.exe
+mv brltty-trtxt.exe brltrtxt.exe
+mv brltty-ttb.exe brlttb.exe
+mv brltty-ctb.exe brlctb.exe
+mv brltty-atb.exe brlatb.exe
+mv brltty-ktb.exe brlktb.exe
+mv brltty-tune.exe brltune.exe
+mv brltty-morse.exe brlmorse.exe
+
+cd "${sourceDirectory}/DOS"
+installFile cwsdpmi.exe "bin/cwsdpmi.exe"
+
+cd "${sourceDirectory}/Drivers"
+name=README
+for driverType in Braille Speech
+do
+   cd "${driverType}"
+
+   for driverName in *
+   do
+      cd "${driverName}"
+      typeset -u driverCode="$(sed -n -e '/^ *DRIVER_CODE *= */s/^.*= *//p' "Makefile.in")"
+
+      for file in "${name}"*
+      do
+         document="${file/#${name}/${driverCode}}"
+         document="${document//./-}"
+         installFile "${file}" "${documentSubdirectory}/Drivers/${driverType}/${document}.txt"
+      done
+
+      cd ..
+   done
+
+   cd ..
+done
+
+cd "${sourceDirectory}"
+installFile "LICENSE-LGPL" "LIC-LGPL.txt"
+installFile "README" "README.txt"
+
+cd "${documentsSubdirectory}"
+installFile "ChangeLog" "${documentSubdirectory}/CHG-LOG.txt"
+
+for file in DOS
+do
+   installFile "README.${file}" "${documentSubdirectory}/${file}.txt"
+done
+
+for file in BUGS CONTRIBUTORS HISTORY TODO
+do
+   installFile "${file}" "${documentSubdirectory}/${file}.txt"
+done
+
+installCSV braille-driver BRL-DRV
+installCSV speech-driver SPK-DRV
+installCSV screen-driver SCR-DRV
+
+installCSV text-table TEXT-TBL
+installCSV contraction-table CONT-TBL
+installCSV attributes-table ATTR-TBL
+installCSV keyboard-table KBD-TBL
+
+installManualFiles "${sourceDirectory}"
+installManualFiles "${buildDirectory}"
+
+cd "${buildDirectory}"
+installFile "Documents/brltty.conf" "etc/brltty.cfg"
+installFile "Programs/revision_identifier.auto.h" "REVISION.txt"
+sed -e 's/"//g' -i "${installDirectory}/REVISION.txt"
+
+logMessage task "converting text files"
+cd "${installDirectory}"
+find . -print |
+   while read path
+   do
+      handle="${path#.}"
+      [ -n "${handle}" ] || continue
+
+      name="${path##*/}"
+      extension="${name##*.}"
+
+      if [ "${name#*.*.}" != "${name}" ]
+      then
+         logWarning "name contains more than one dot: ${handle}"
+      elif [ "${name#.}" != "${name}" ]
+      then
+         logWarning "null name: ${handle}"
+      elif [ "${name%.}" != "${name}" ]
+      then
+         logWarning "null extension: ${handle}"
+      elif [ -f "${path}" ]
+      then
+         if [ "${extension}" = "${name}" ]
+         then
+            logWarning "file without an extension: ${handle}"
+         else
+            case "${extension}"
+            in
+               txt | html | htm | csv | cfg | [tcak]t[bi] | h) convertTextFile "${path}";;
+               exe | pdf | doc);;
+               *) logWarning "unexpected file extension: ${handle}";;
+            esac
+         fi
+      elif [ -d "${path}" ]
+      then
+         if [ "${extension}" != "${name}" ]
+         then
+            logWarning "directory with an extension: ${handle}"
+         fi
+      else
+         logWarning "unsupported special file: ${handle}"
+      fi
+   done
+
+logMessage task "checking for name collisions"
+cd "${installDirectory}"
+find . -type d -print |
+   while read directory
+   do
+      declare -A shortNames=()
+
+      for longName in $(ls -1 --quote-name --quoting-style=shell "${directory}")
+      do
+         delimiter="."
+         prefix="${longName%${delimiter}*}"
+
+         if [ "${prefix}" = "${longName}" ]
+         then
+            suffix=""
+            delimiter=""
+         else
+            suffix="${longName:${#prefix}+${#delimiter}}"
+         fi
+
+         prefix="${prefix:0:8}"
+         suffix="${suffix:0:3}"
+         declare -u shortName="${prefix}${delimiter}${suffix}"
+
+         if [ "${#shortNames["${shortName}"]}" -eq 0 ]
+         then
+            shortNames["${shortName}"]="${longName}"
+         else
+            logWarning "name collision: ${directory#.}: ${shortNames["${shortName}"]} & ${longName}"
+         fi
+      done
+   done
+
+! "${invokeShell}" || {
+   logMessage task "invoking shell"
+   cd "${installDirectory}"
+   "${SHELL:-/bin/sh}" || :
+}
+
+logMessage task "creating archive"
+cd "${installDirectory}/.."
+zip -q -r "${archivePath}" "${installDirectory##*/}"
+
+logMessage task "done"
+exit 0
diff --git a/DOS/mkdostools b/DOS/mkdostools
new file mode 100755
index 0000000..573da7f
--- /dev/null
+++ b/DOS/mkdostools
@@ -0,0 +1,532 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+umask 022
+shopt -s nullglob
+
+readonly autoconfOldVersion=2.13
+readonly autoconfNewVersion=2.59
+readonly automakeVersion=1.9.6
+readonly crxVersion=2.04
+
+readonly defaultArchivesSubdirectory="Archives"
+readonly defaultBuildSubdirectory="Build"
+readonly defaultInstallSubdirectory="Tools"
+readonly defaultTargetSystem="i586-pc-msdosdjgpp"
+
+function setVariable {
+   local variable="${1}"
+   local value="${2}"
+
+   eval "${variable}"'="${value}"'
+}
+
+function setArrayElement {
+   local array="${1}"
+   local index="${2}"
+   local value="${3}"
+
+   setVariable "${array}[${index}]" "${value}"
+}
+
+function defineEnumeration {
+   local array="${1}"
+   shift 1
+
+   declare -g -i -A "${array}"
+   local value=0
+
+   while [ "${#}" -gt 0 ]
+   do
+      setArrayElement "${array}" "${1}" $((value++))
+      shift 1
+   done
+
+   readonly "${array}"
+}
+
+readonly programName="${0##*/}"
+readonly programDirectory="$(realpath "$(dirname "${0}")")"
+
+function programMessage {
+   local message="${1}"
+
+   echo >&2 "${programName}: ${message}"
+}
+
+defineEnumeration logLevels error warning task step detail
+declare -i logLevel="${logLevels[task]}"
+
+function logMore {
+   ((logLevel += 1)) || :
+}
+
+function logLess {
+   ((logLevel -= 1)) || :
+}
+
+function logMessage {
+   local level="${1}"
+   local message="${2}"
+
+   local value="${logLevels[$level]}"
+
+   [ -n "${value}" ] || {
+      logWarning "undefined log level: ${level}"
+      value=0
+   }
+
+   [ "${value}" -gt "${logLevel}" ] || programMessage "${message}"
+}
+
+function syntaxError {
+   local message="${1}"
+
+   logError "${message}"
+   exit 2
+}
+
+function semanticError {
+   local message="${1}"
+
+   logError "${message}"
+   exit 3
+}
+
+function externalError {
+   local message="${1}"
+
+   logError "${message}"
+   exit 4
+}
+
+function showUsage {
+   cat <<-END-OF-USAGE
+	usage: ${programName} [-option ...]
+	-h            display a usage summary (this text), and then exit
+	-q            decrease output verbosity (may be specified more than once)
+	-v            increase output verbosity (may be specified more than once)
+	-d directory  specify the ROOT directory (default is the current directory)
+	-a directory  specify the archives directory (default is ROOT/${defaultArchivesSubdirectory})
+	-b directory  specify the build directory (default is ROOT/${defaultBuildSubdirectory})
+	-i directory  specify the install directory (default is ROOT/${defaultInstallSubdirectory})
+	-t system     specify the target system (default is ${defaultTargetSystem})
+	-g version    the gcc version to build (defaults if only one is archived)
+	END-OF-USAGE
+
+   exit 0
+}
+
+function handleDirectoryOption {
+   local variable="${1}"
+   local default="${2}"
+   local name="${3}"
+
+   [ -n "${!variable}" ] || setVariable "${variable}" "${default}"
+   setVariable "${variable}" "$(realpath "${!variable}")"
+   readonly "${variable}"
+
+   [ -z "${name}" ] || logMessage detail "${name} directory: ${!variable}"
+}
+
+function logArrayElements {
+   local array="${1}"
+   local name="${2}"
+
+   message="${name} properties:"
+
+   eval local 'indeces="${!'"${array}"'[*]}"'
+   local index
+
+   for index in ${indeces}
+   do
+      local variable="${array}[${index}]"
+      message+=" ${index}=${!variable}"
+   done
+
+   logMessage detail "${message}"
+}
+
+function findHostCommand {
+   local variable="${1}"
+   shift 1
+
+   local command
+   for command
+   do
+      local path="$(type -p "${command}")"
+
+      [ -z "${path}" ] || {
+         setVariable "${variable}" "${path}"
+         export "${variable}"
+         logMessage detail "host command location: ${variable} -> ${!variable}"
+         return 0
+      }
+   done
+
+   semanticError "host command not found: ${variable} (${*})"
+}
+
+function pathChange {
+   local newPath="${1}"
+
+   export PATH="${newPath}"
+   logMessage detail "host command search path: ${PATH}"
+}
+
+function pathPrepend {
+   local directory="${1}"
+
+   pathChange "${directory}:${PATH}"
+}
+
+function makeDirectory {
+   local path="${1}"
+
+   mkdir -p "${path}"
+}
+
+function emptyDirectory {
+   local path="${1}"
+
+   rm -f -r "${path}/"*
+}
+
+function initializeDirectory {
+   local path="${1}"
+
+   makeDirectory "${path}"
+   emptyDirectory "${path}"
+}
+
+function verifyArchive {
+   local array="${1}"
+   local type="${2}"
+   local prefix="${3}"
+   local suffix="${4}"
+   local version="${5}"
+
+   declare -g -A "${array}"
+   setArrayElement "${array}" type "${type}"
+   setArrayElement "${array}" prefix "${prefix}"
+   setArrayElement "${array}" suffix "${suffix}"
+
+   local name="${prefix%-}"
+   setArrayElement "${array}" name "${name}"
+
+   local originalDirectory="${PWD}"
+   cd "${archivesDirectory}"
+   if [ -n "${version}" ]
+   then
+      local file="${prefix}${version}${suffix}"
+      [ -f "${file}" ] || semanticError "${type} archive not found: ${file}"
+   else
+      local files=("${prefix}"*"${suffix}")
+      local count="${#files[*]}"
+      ((count > 0)) || semanticError "${type} archive not found: ${name}"
+      ((count == 1)) || semanticError "${type} package with multiple archives: ${files[*]}"
+
+      local file="${files[0]}"
+      version="${file%${suffix}}"
+      version="${version:${#prefix}}"
+   fi
+   cd "${originalDirectory}"
+
+   [[ "${version}" =~ ^[0-9]{3}$ ]] && version="${version:0:1}.${version:1}"
+   setArrayElement "${array}" version "${version}" 
+
+   setArrayElement "${array}" file "${file}"
+   setArrayElement "${array}" path "${archivesDirectory}/${file}"
+
+   local source="${name}-${version}"
+   setArrayElement "${array}" source "${source}"
+
+   readonly "${array}"
+   logArrayElements "${array}" "archive"
+}
+
+function gnuVerifyArchive {
+   local array="${1}"
+   local name="${2}"
+   local version="${3}"
+
+   verifyArchive "${array}" Gnu "${name}-" ".tar.gz" "${version}"
+}
+
+function djgppVerifyArchive {
+   local array="${1}"
+   local name="${2}"
+   local type="${3}"
+   local version="${4}"
+
+   verifyArchive "${array}" DJGPP "${name}" "${type}.zip" "${version//./}"
+}
+
+function logPackageTask {
+   local array="${1}"
+   local task="${2}"
+
+   local nameVariable="${array}[name]"
+   local versionVariable="${array}[version]"
+   logMessage task "${task}: ${!nameVariable}-${!versionVariable}"
+}
+
+function unpackArchive {
+   local array="${1}"
+
+   local typeVariable="${array}[type]"
+   local pathVariable="${array}[path]"
+
+   logPackageTask "${array}" "unpacking ${!typeVariable} archive"
+   "unpackArchive_${!typeVariable}" "${!pathVariable}"
+}
+
+function unpackArchive_Gnu {
+   local path="${1}"
+
+   tar xfz "${path}"
+}
+
+function unpackArchive_DJGPP {
+   local path="${1}"
+
+   unzip -q -a "${path}"
+}
+
+function changeScriptVariable {
+   local script="${1}"
+   local variable="${2}"
+   local value="${3}"
+
+   sed -e "/^ *${variable} *=/s%=.*%='${value}'%" -i "${script}"
+}
+
+function logBuildDirectory {
+   logNote "build directory: ${PWD}"
+}
+
+function runBuildCommand {
+   local logFile="${1}"
+   shift 1
+
+   logNote "build command: ${*}"
+   "${@}" >&"${logFile}" || externalError "build error: for details, see ${PWD}/${logFile}"
+}
+
+function configurePackage {
+   local source="${1}"
+   shift 1
+
+   runBuildCommand configure.log "${source}/configure" "${@}"
+}
+
+function makePackage {
+   runBuildCommand make.log make "${@}"
+}
+
+function installPackage {
+   runBuildCommand install.log make install "${@}"
+}
+
+function buildHostPackage {
+   local array="${1}"
+   shift 1
+
+   logPackageTask "${array}" "building host package"
+
+   local sourceVariable="${array}[source]"
+   local build="${!sourceVariable}-host"
+   local prefix="$(realpath "${!sourceVariable}-install")"
+
+   makeDirectory "${build}"
+   cd "${build}"
+   logBuildDirectory
+   configurePackage "../${!sourceVariable}" --prefix="${prefix}"
+   makePackage
+   installPackage
+   cd ..
+
+   local bin="${prefix}/bin"
+   pathPrepend "${bin}"
+
+   while [ "${#}" -gt 0 ]
+   do
+      local command="${1}"
+      local variable="${2}"
+      shift 2
+
+      changeScriptVariable "${gccUnpackScript}" "${variable}" "${bin}/${command}"
+   done
+}
+
+function buildHostArchive {
+   local array="${1}"
+   shift 1
+
+   unpackArchive "${array}"
+   buildHostPackage "${array}" "${@}"
+}
+
+function buildHostAutoconf {
+   local array="${1}"
+   local autoconfVariable="${2}"
+   local autoheaderVariable="${3}"
+
+   buildHostArchive "${array}" autoconf "${autoconfVariable}" autoheader "${autoheaderVariable}"
+}
+
+function configureTargetPackage {
+   local array="${1}"
+   shift 1
+
+   local sourceVariable="${array}[source]"
+   configurePackage "../${!sourceVariable}" \
+      "--prefix=${installDirectory}" \
+      "--target=${targetSystem}" \
+      "${@}"
+}
+
+function buildTargetPackage {
+   local array="${1}"
+   shift 1
+
+   logPackageTask "${array}" "building target package"
+
+   cd gnu
+   local sourceVariable="${array}[source]"
+   local build="${!sourceVariable}-target"
+   makeDirectory "${build}"
+   cd "${build}"
+   logBuildDirectory
+   configureTargetPackage "${array}" "${@}"
+   makePackage
+   installPackage
+   cd ../..
+}
+
+rootDirectory=""
+archivesDirectory=""
+buildDirectory=""
+installDirectory=""
+
+targetSystem=""
+gccVersion=""
+
+while getopts ":hqvd:a:b:i:t:g:" option
+do
+   case "${option}"
+   in
+      h) showUsage;;
+
+      q) logLess;;
+      v) logMore;;
+
+      d) rootDirectory="${OPTARG}";;
+      a) archivesDirectory="${OPTARG}";;
+      b) buildDirectory="${OPTARG}";;
+      i) installDirectory="${OPTARG}";;
+
+      t) targetSystem="${OPTARG}";;
+      g) gccVersion="${OPTARG}";;
+
+      :) syntaxError "missing ooperand: -${OPTARG}";;
+     \?) syntaxError "unknown option: -${OPTARG}";;
+      *) syntaxError "unimplemented option: -${option}";;
+   esac
+done
+
+shift $((OPTIND - 1))
+[ "${#}" -eq 0 ] || syntaxError "too many parameters"
+
+handleDirectoryOption rootDirectory "${PWD}" "root"
+handleDirectoryOption archivesDirectory "${rootDirectory}/${defaultArchivesSubdirectory}" "archives"
+handleDirectoryOption buildDirectory "${rootDirectory}/${defaultBuildSubdirectory}" "build"
+handleDirectoryOption installDirectory "${rootDirectory}/${defaultInstallSubdirectory}" "install"
+
+[ -n "${targetSystem}" ] || targetSystem="${defaultTargetSystem}"
+readonly targetSystem
+
+pathChange "$(getconf PATH)"
+unset MAKEFLAGS
+
+logMessage task "finding host commands"
+findHostCommand CC cc gcc
+findHostCommand CXX c++ g++ cxx gxx
+findHostCommand LIBTOOL libtool
+
+logMessage task "verifying archives"
+gnuVerifyArchive gnuAutoconfOld autoconf "${autoconfOldVersion}"
+gnuVerifyArchive gnuAutoconfNew autoconf "${autoconfNewVersion}"
+gnuVerifyArchive gnuAutomake automake "${automakeVersion}"
+gnuVerifyArchive gnuBinutils binutils
+gnuVerifyArchive gnuGcc gcc "${gccVersion}"
+djgppVerifyArchive djgppGcc gcc s2 "${gnuGcc[version]}"
+djgppVerifyArchive djgppCrx djcrx "" "${crxVersion}"
+
+logMessage task "preparing build directory"
+initializeDirectory "${buildDirectory}"
+cd "${buildDirectory}"
+
+unpackArchive djgppCrx
+unpackArchive djgppGcc
+
+gccUnpackScript="unpack-gcc.sh"
+buildHostAutoconf gnuAutoconfOld AUTOCONF_OLD AUTOHEADER_OLD
+buildHostAutoconf gnuAutoconfNew AUTOCONF AUTOHEADER
+buildHostArchive gnuAutomake
+
+logMessage task "patching gcc source"
+logBuildDirectory
+chmod u=rwx,go=r "${gccUnpackScript}"
+runBuildCommand unpack-gcc.log "./${gccUnpackScript}" "$(realpath --relative-to=. "${gnuGcc["path"]}")"
+
+cd gnu
+unpackArchive gnuBinutils
+cd ..
+
+logMessage task "preparing install directory"
+initializeDirectory "${installDirectory}"
+pathPrepend "$${installDirectory}/bin"
+readonly targetDirectory="${installDirectory}/${targetSystem}"
+makeDirectory "${targetDirectory}"
+makeDirectory "${targetDirectory}/bin"
+cp -r lib "${targetDirectory}"
+cp -r include "${targetDirectory}"
+
+logMessage task "building stubify"
+cd src/stub
+logBuildDirectory
+runBuildCommand compile.log "${CC}" -O2 stubify.c -o "${targetDirectory}/bin/stubify"
+cd ../..
+
+buildTargetPackage gnuBinutils
+buildTargetPackage djgppGcc --with-headers="${targetDirectory}/include"
+
+logMessage task "creating symbolic links"
+cd "${targetDirectory}/lib"
+ln -s libstdc++.a libstdcxx.a
+ln -s libsupc++.a libsupcxx.a
+
+logMessage task "cleaning up"
+cd /
+emptyDirectory "${buildDirectory}"
+
+logMessage task "done"
+exit 0
diff --git a/Documents/BUGS b/Documents/BUGS
new file mode 100644
index 0000000..074b1af
--- /dev/null
+++ b/Documents/BUGS
@@ -0,0 +1,2 @@
+systemd restart of a path unit doesn't work
+SELECTVT doesn't redirect input
diff --git a/Documents/BrlAPIref.doxy.in b/Documents/BrlAPIref.doxy.in
new file mode 100644
index 0000000..3f2e805
--- /dev/null
+++ b/Documents/BrlAPIref.doxy.in
@@ -0,0 +1,1098 @@
+# Doxyfile 1.3.9.1
+# @configure_input@
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = BrlAPI
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @api_version@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @builddir@
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of source 
+# files, where putting all generated files in the same directory would otherwise 
+# cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = NO
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is used 
+# as the annotated text. Otherwise, the brief description is used as-is. If left 
+# blank, the following values are used ("$name" is automatically replaced with the 
+# name of the entity): "The $name class" "The $name widget" "The $name file" 
+# "is" "provides" "specifies" "contains" "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = YES
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited 
+# members of a class in the documentation of that class as if those members were 
+# ordinary class members. Constructors, destructors and assignment operators of 
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources 
+# only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES 
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = NO
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_builddir@/Programs/brlapi.h \
+                         @top_srcdir@/Programs/brlapi_keycodes.h \
+                         @top_srcdir@/Programs/brlapi_param.h \
+                         @top_builddir@/Programs/brlapi_constants.h \
+                         @top_srcdir@/Programs/brlapi_protocol.h
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp 
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = 
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories 
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = BrlAPIref/html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, letter, legal and 
+# executive. If left blank a4 will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = BrlAPIref/man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse the 
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or 
+# super classes. Setting the tag to NO turns the diagrams off. Note that this 
+# option is superseded by the HAVE_DOT option below. This is only a fallback. It is 
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes that 
+# lay further from the root node will be omitted. Note that setting this option to 
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also 
+# note that a graph may be further truncated if the graph's image dimensions are 
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). 
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/Documents/CONTRIBUTORS b/Documents/CONTRIBUTORS
new file mode 100644
index 0000000..6f996fc
--- /dev/null
+++ b/Documents/CONTRIBUTORS
@@ -0,0 +1,98 @@
+Adi Dascal <ad@baum.ro>
+Adi Kushnir <adikushnir@gmail.com>
+Alan Coopersmith <alan.coopersmith@oracle.com>
+Alex Bernier <alex.bernier@free.fr>
+Andreas Gross <andi@andi-bika.de>
+Angela Engel <angela.engel@gmx.at>
+Arthur Breuneval <arthur.breuneval@yahoo.fr>
+Attila Hammer <hammera@pickup.hu>
+August Hörandl <august.hoerandl@gmx.at>
+Aura Kelloniemi <kaura.dev@sange.fi>
+Boris Daix <Boris.Daix@insa-lyon.fr>
+Cheryl Homiak <cah4110@icloud.com>
+Chris Brannon <chris@the-brannons.com>
+Christian Comaschi <christian_comaschi@libero.it>
+Christoph-Simon Senjak <christoph.senjak@googlemail.com>
+Coscell Kao <coscell@molerat.net>
+Daniel Dalton <d.dalton@iinet.net.au>
+Dave Mielke <dave@mielke.cc>
+David Sauer <sauer@brailcom.cz>
+Didier Poitou <d.poitou@eurobraille.fr>
+Dietmar Segbert <dietmarsegbert@gmx.de>
+Dominic Mazzoni <dmazzoni@google.com>
+Emilio Pozuelo Monfort <emilio.pozuelo@um.es>
+Étienne Carrier <etienne.carrier@humanware.com>
+Hans Schou <chlor@schou.dk>
+Helge Havnegjerde <helge.havnegjerde@getmail.no>
+Hermann Bender <meinelisten@onlinehome.de>
+James Teh <jamie@jantrid.net>
+Jan Buchal <buchal@brailcom.org>
+Jani Kinnunen <jani.kinnunen@wippies.fi>
+Jaroslav Skarvada <jskarvad@redhat.com>
+Jean-Philippe Mengual <jpmengual@debian.org>
+Joanmarie Diggs <jdiggs@igalia.com>
+John J. Boyer <john.boyer@jjb-software.com>
+Jozko Gregorc <jozko.gregorc@guest.arnes.si>
+Julian Sackmann <masackma@microsoft.com>
+Kazunori Minatani <99112004@gakushuin.ac.jp>
+Keith Wessel <keith@wessel.com>
+Lars Bjørndal <lars.bjorndal@broadpark.no>
+Lee Maschmeyer <leemer1@comcast.net>
+Luke Yelavich <themuso@ubuntu.com>
+Mario Lang <mlang@delysid.org>
+Mark Mielke <mark@mielke.cc>
+Marko Lalic <markola@microsoft.com>
+Matthew Miller <mattdm@mattdm.org>
+Michael Curran <mick@kulgan.net>
+Michel Such <michel.such@free.fr>
+Mike Gorse <mgorse@alum.wpi.edu>
+Mike Pedersen <mpedersen@mindspring.com>
+Miranda Woudstra <Miran.Woudstra@gmail.com>
+Neal Gompa (ニール・ゴンパ) <NGompa13@gmail.com>
+Nicolas Pitre <nico@fluxnic.net>
+Nikita Tseykovets <tseikovets@rambler.ru>
+Olaf Hering <olaf@aepfle.de>
+Ole Guldberg <ole@omgwtf.dk>
+Olivier Bert <obert01@mistigri.org>
+Ollie Mallard <mallard@ilgerone.net>
+Ondřej Lysoněk <OLysonek@RedHat.com>
+Oscar Fernandez <ofa@once.es>
+Pete De Vasto <pdevasto@incyte.com>
+Peter Lundblad <plundblad@google.com>
+Petr Řehák <rehak@adaptech.cz>
+Raoul Megelas <rmgls@free.fr>
+Regiane Mendonça Villela <regianevillela@gmail.com>
+Rich Burridge <Rich.Burridge@Sun.COM>
+Rimas Kudelis <rq@akl.lt>
+Robert Pösel <robyer@seznam.cz>
+Rudolf Weeber <Rudolf.Weeber@gmx.de>
+Samuel Thibault <samuel.thibault@ens-lyon.org>
+Samuel Yang <mison@bbs.ee.ntu.edu.tw>
+Sebastiano Pistore <SebastianoPistore.info@protonmail.ch>
+Sébastien Hinderer <sebastien.hinderer@ens-lyon.org>
+Sebastian Krahmer <krahmer@suse.com>
+Sérgio Neves <sergionevess@gmail.com>
+Simon Kainz <simon@familiekainz.at>
+Simon Meers <drmeers@gmail.com>
+Stepan Kasal <kasal@ucw.cz>
+Stefan Moisei <vortex37@gmail.com>
+Stephane Dalton <sdalton@videotron.ca>
+Stéphane Doyon <s.doyon@videotron.ca>
+Tapio Kelloniemi <tapio.kelloniemi@thack.org>
+Thu Trang <phamtrang2002@softhome.net>
+Timothy Spaulding <spaulding@icanbrew.com>
+Timo Matsinen <matsinen@jyu.fi>
+Tom spot Callaway <tcallawa@redhat.com>
+Tomáš Janoušek <tomi@nomi.cz>
+Tomas Mårdsjö <tomas.mardsjo@icap.nu>
+Tomoyuki Kudou <kudou@cypac.co.jp>
+Tony Hernandez <tonyhspeaks@gmail.com>
+Ulf Beckmann <beckmann@flusoft.de>
+Victor Montalvao <vicmont@microsoft.com>
+Victor Tsaran <vtsaran@yahoo.com>
+Vipul Kute <vipul.kute@gmail.com>
+Willi Lutzenberger <willutz@gmx.net>
+William Hubbs <w.d.hubbs@gmail.com>
+Wolfgang Astleitner <wolfgang.astleitner@liwest.at>
+Yair K. <caesium@hotpop.com>
+Yannick Plassiard <yan@mistigri.org>
diff --git a/Documents/ChangeLog b/Documents/ChangeLog
new file mode 100644
index 0000000..adfa8c6
--- /dev/null
+++ b/Documents/ChangeLog
@@ -0,0 +1,3874 @@
+Note: this file is in reversed chronological order (bottom to top).
+
+
+July 22, 2023:
+ - BRLTTY 6.6 released:
+   General changes:
+      The -H (uppercase) option and the concept of hidden options have been removed.
+      For consistency (with -A, -B, -S, -X), the -z and -Z short options have been swapped:
+	 -z is now --stay-privileged
+	 -Z is now --privilege-parameters
+      Multi-line contracted braille is now supported.
+      The cursor style can now be set to no dots.
+      A rectangular append to the clipboard now ensures a leading newline.
+      README.CommandReference has been added.
+   Speech changes:
+      Speech can now be interrupted when writing new text to the input FIFO.
+      Autospeak now provides correct character highlighting to the speech driver.
+      A word is now a whitespace-delimited (rather than an alphanumeric) sequence of characters.
+      Word completion for single-character words now works correctly.
+      The ROUTE_SPEECH command (go to the speech cursor) has been added.
+      The DESC_CURR_CHAR command now speaks phonetically.
+   Preferences Menu changes:
+      The PREFRESET command has been added.
+      Space + p + Dots 7-8 is bound to the new PREFRESET command.
+      If the preferences file can't be found then try loading /etc/brltty/default.prefs.
+      Save on Exit now also saves the preferences on program termination.
+      The Speak Key Context preference has been added.
+      The Speak Modifier Key preference has been added.
+   Baum braille driver changes:
+      More recent Orbit Reader models have routing keys.
+   BrlAPI braille driver changes:
+      The speechChanges (no, yes) parameter has been added.
+   CombiBraille braille driver changes:
+      Upgraded to use generic (rather than direct serial) I/O.
+      The routing keys are now supported.
+   DotPad braille driver:
+      New - its two-letter driver code is dp.
+      A multi-line text display is supported within its graphic area.
+   Freedom Scientific braille driver changes:
+      Minor Focus key binding changes:
+	 LeftShift + RoutingKey -> SETLEFT
+	 RightShift + RoutingKey -> SWITCHVT
+	 LeftSelector + NavrowKey -> PRDIFCHAR
+	 RightSelector + NavrowKey -> NXDIFCHAR
+   HandyTech braille driver changes:
+      Bluetooth service discovery now needs to be performed (for newer models).
+   External Speech speech driver changes:
+      The volume and pitch can now be set.
+      Now disconnects from the server when a write error or timeout occurs.
+      Now tries to reconnect to the server if disconnected.
+      Now correctly handles the receipt of tracking data.
+   AtSpi2 screen driver changes:
+      A potential crash on receipt of a cursor routing request has been resolved.
+   File Viewer screen driver:
+      New. Its two-letter driver code is fv.
+      A virtual screen presents the specified file (via its file parameter).
+      The height of the screen is the number of lines within the file.
+      The width of the screen is the length of the file's longest line.
+   Linux screen driver changes:
+      A message is shown on the braille display if character injection (TIOCSTI) is disabled.
+   Terminal Emulator screen driver:
+      New. Its two-letter driver code is em.
+      It monitors a shared memory segment maintained by a terminal emulator
+      for screen content and size, cursor position, etc.
+      It uses a message queue to know when the segment has been updated
+      and to send input typed on the braille device to the terminal emulator.
+      BRLTTY's provided terminal emulator is the new brltty-pty command (in libexec/brltty/).
+      The new brltty-term command runs a shell or terminal manager via brltty-pty,
+      and also runs BRLTTY with its Terminal Emulator screen driver to interact with it.
+   BrlAPI changes:
+      The version is now 0.8.5 (it was 0.8.4).
+      Fuzzing support has been added to the server (see README.APIFuzzing).
+      Various fuzzer-detected issues have been resolved.
+   Contraction tables:
+      The en-ueb-g1 (English, Unified, Grade 1) contraction table has been added.
+   Key tables:
+      A possible stack overflow during table compilation has been resolved.
+      The run directive has been added.
+      The macro directive has been added.
+      The isolated directive has been added.
+      The ktbcheck tool has been added.
+   Windows changes:
+      The batch scripts have been improved.
+      The tasks-brltty.bat script has been added.
+      The regquery-brlapi.bat script has been added.
+      The build now includes the Debug\ directory, which contains:
+         The build's config.h, config.mk, and config.log files.
+         Unstripped executables (.exe) and libraries (.dll).
+
+June 9, 2022:
+ - BRLTTY 6.5 released:
+   General changes:
+      Help (-h) output lines are now wrapped at the screen width (not at 79).
+      Parameter errors no longer prevent a driver from starting.
+      Word Wrap when using contracted braille has been fixed.
+      The Quiet setting can now be specified within brltty.conf.
+      In data files, \R now represents the Unicode replacement character.
+      Chord-6/8 now toggle between 6- and 8-dot computer braille.
+      Bindings for the Emacs editor have been added.
+      Bindings for the Lua language have been added.
+      An Arabic message catalog has been added.
+   Logging changes:
+      The ingio log category has been renamed to gio.
+      The bluetooth log category has been renamed to bt.
+      The hid log category has been added.
+   Baum braille driver changes:
+      The Orbit Reader 40 is now supported.
+   Canute braille driver changes:
+      Driver startup is now much faster.
+      Lines that haven't effectively changed are no longer rewritten.
+   HumanWare braille driver changes:
+      An easy way to revert to the legacy thumb key bindings has been added.
+      The newer models can now be used via Bluetooth on Linux.
+   Linux screen driver changes:
+      The WidecharPadding= parameter has been added.
+      The FixUnicodeSpaces= parameter has been renamed to RpiSpacesBug=.
+   AtSpi2 screen driver changes:
+      Control characters are now mapped to their corresponding special keys.
+      Brltty is now shut down if the driver fails to start.
+      The driver no longer crashes when the X server is shut down.
+   BrlAPI changes:
+      The version is now 0.8.4 (it was 0.8.3).
+      The permissions on local server sockets are now set correctly.
+      The cursor is now represented correctly when using six-dot cells.
+      Some locking issues have been resolved.
+      A text console can now create its own client session.
+   Text table changes:
+      The Russian (ru) table has been significantly updated.
+      The Arabic (ar) table has been significantly updated.
+      Typing when using the Arabic (ar) table now yields Arabic (not English) letters.
+      The input directive has been added.
+   Key table changes:
+      The +escaped character modifier has been added.
+   Udev changes:
+      The rules for customized and generic devices can now be installed separately.
+      The device and uinput rules can now be installed separately.
+      Filtering is now also done on the manufacturer and product strings.
+      Filtering on a parent hub's vendor/product identifiers is now supported.
+      A HID rules file has been added.
+      Symlinks for USB-connected devices are now in /dev/brltty/.
+   Linux changes:
+      Bluetooth HID is now supported.
+   Windows changes:
+      Brltty no longer crashes when started as a service.
+   Android changes:
+      Now targeting API level 30 and setting isAccessibilityTool.
+      The Actions screen now scrolls vertically.
+      Settings -> Accessibility -> BRLTTY -> Settings now goes to the Actions screen.
+      Arabic string translations have been added.
+   Tool changes:
+      brltty-clip now has better long options.
+      brltty-hid has been added.
+      brltty-ttysize has been added.
+      xbrlapi now ignores keyboard remapping errors from the X server.
+   Build changes:
+      The LD, STRIP, and RANLIB commands are no longer hard-coded.
+
+September 24, 2021:
+ - BRLTTY 6.4 released:
+   General changes:
+      Dynamically changing the text table to auto now works.
+      The keyboard table can now be explicitly set to off.
+      Vertical scrolling for multi-line displays has been improved.
+      Trailing whitespace (except for the first one) is now trimmed when pasting.
+      The internationalization of date phrases has been improved.
+      Emoji using characters like the hash (#) or asterisk (*) are now ignored.
+   Sliding braille window changes:
+      Cursor tracking always moves the window all the way to the left
+      if the whole line is short enough to fit on it.
+      Sliding the window triggers when the screen cursor moves to
+      (rather than beyond) the first/last cell of the text portion of the display.
+   Contracted braille changes:
+      Contraction table support can no longer be excluded.
+      An internal contraction table can now be configured.
+      Contracted braille is automatically enabled on startup if:
+         The contraction table has been either specified or automatically selected.
+         The text table hasn't been either specified or automatically selected.
+      Cursor tracking has been improved.
+      Panning left when there are wide characters has been fixed.
+      A buffer overrun that occasionally caused a crash has been fixed.
+   Preferences menu changes:
+      The autospeak settings have been moved into their own submenu.
+      Contracted Braille and 6-dot Computer Braille are now configured separately.
+      Blinking properties are now configured by period and percent visible
+      rather than by visible and invisible times.
+      The Space status field type has been added.
+      Three-digit status field types have been added.
+   New navigation commands:
+      SAY_ALL: Speak the entire screen (SAY_ABOVE + SAY_BELOW).
+      LOWER and HIGHER: Adjust the speech pitch.
+      CONTRACTED: Toggle between contracted (on) and computer (off) braille.
+      COMPBRL6: Toggle between six- and eight-dot computer braille.
+      Chord-G (Space + dots 1245) is bound to the CONTRACTED toggle.
+   Status summary (info line) changes:
+      Indicator character changes:
+         c: The screen cursor is being rendered.
+         s: The speech cursor is being rendered.
+         t: Cursor tracking is on.
+         u: The attributes underline is being rendered.
+	 Braille display content:
+	    6: six-dot computer braille
+	    8: eight-dot computer braille
+	    c: contracted braille
+	    a: character attributes
+	 Braille keyboard mode:
+	    space: typing in text mode
+	    b: typing in Unicode braille patterns mode
+	    d: keyboard is disabled
+      Dot indicator changes:
+         Dot5: rendering the attributes underline (was cursor style)
+         Dot6: typing Unicode braille patterns (was blinking cursor)
+         Dot8: the keyboard is enabled (was sliding window)
+      Screen number when on a special screen:
+         f: frozen host screen
+         h: braille device or keyboard help screen
+         m: preferences menu
+   New braille devices:
+      The NLS eReader from Zoomax.
+      The Brailliant BI 20X and 40X from Humanware.
+      The Active Braille S from Help Tech.
+   FreedomScientific braille driver changes:
+      Focus models:
+         Many bindings for the front keys have been added.
+         Bindings for speech navigation have been added.
+   HandyTech braille driver changes:
+      Version 1.3 of the Easy Braille's USB-HID firmware is now supported.
+   HumanWare braille driver changes:
+      The outer thumb keys now navigate up/down (used to be left/right).
+      The inner thumb keys now navigate left/right (used to be up/down).
+      Support for firmware version 1.1 of the newer models has been added.
+   BrlAPI changes:
+      The version is now 0.8.3 (it was 0.8.2).
+      The current braille typing mode is now honoured.
+      Typing Unicode braille patterns has been fixed.
+      Server termination during initialization no longer hangs.
+      New client features:
+         Writes can be automatically padded/truncated (use negative region size).
+         The thread-safe brlapi_strerror_r() function.
+         The brlapi_sync() function.
+         The BRLAPI_PARAM_CLIENT_PRIORITY_DISABLE constant.
+      Security fixes:
+         Use fchmod (not chmod) to adjust local socket permissions.
+         The sticky bit is now set on the local sockets directory.
+   Contraction table changes:
+      Locale-based autoselection (set to auto) is now supported.
+      A table for Russian (set to ru) has been added.
+      UEB (Unified English Braille) changes:
+         The Greek letters have been defined.
+         Some math symbols have been added.
+         Common accents are now supported - they are the
+         acute, cedilla, circumflex, diaeresis, grave, and ring.
+   Android changes:
+      The four main architectures (arm/x86, 32/64 bits) are now supported.
+      BRLTTY is now an app on Google Play.
+      The Java BrlAPI client objects are now available via a Maven repository.
+      A customized brltty.conf can now be placed in the primary storage area.
+      The default contraction table has been changed to auto (from en-us-g2).
+      Rules specifying which data files should be backed up have been added.
+      Web page rendering changes:
+         Widget types are no longer unnecessarily shown.
+         Annotations for lists and list items have been added.
+         Link annotations no longer contain the link's URL.
+      The about application screen now contains:
+         BRLTTY's app version.
+         The version of the BrlAPI server.
+         The date and time of the build.
+         The repository revision of the source code.
+         A link to BRLTTY's privacy policy.
+         The link to BRLTTY's app page on Google Play.
+      Emulating F16 now logs the screen to a local file for developer debugging.
+   Windows changes:
+      The .csv files have been added to the etc/ directory of the archive.
+   New command line options:
+      --autospeak-threshold=<screen-content-quality>
+   Configure changes:
+      The --disable-contracted-braille option has been removed.
+      The --with-contraction-table option has been added.
+      The --without-contraction-table option has been added.
+
+January 28, 2021:
+ - BRLTTY 6.3 released:
+   Core changes:
+      The CLIP_NEW command when performed via a keyboard table no longer crashes.
+      Message localization now works on Windows and Android.
+      Non-UTF8 consoles on Linux and Windows are now supported.
+   Command line option changes:
+      --start-message no longer has a short option (was -Y).
+      --stop-message no longer has a short option (was -Z).
+      --prompt-patterns no longer has a short option (was -z).
+      The short option for --privilege-parameters is now -z (was -y).
+      The --stay-privileged [-Z] option has been added.
+      Setting the unprivileged user to :STAY-PRIVILEGED: is no longer supported.
+      The override-preference option has been renamed to override-preferences.
+      The --locale-directory option has been added.
+   Build changes:
+      The Systemd and Udev wrapper scripts are now in libexec/ (not lib/brltty/).
+      BRLTTY now has a .pc (pkgconfig) file.
+      The revision identifier for the build is now stored within the tarball.
+   Systemd changes:
+      The brlapi group is created during boot if it doesn't already exist.
+   FreedomScientific braille driver changes:
+      Focus models: The SETMARK an GOTOMARK commands have been bound.
+   XWindow braille driver changes:
+      A backspace key has been added.
+   SpeechDispatcher speech driver changes:
+      The name= parameter has been added.
+   BrlAPI changes:
+      The version is now 0.8.2 (it was 0.8.1).
+      Some locking issues have been resolved.
+   Contraction table changes:
+      The cldr directive has been renamed to emoji.
+      Emoji translation is no longer applied to (some) non-emoji characters.
+      A (rare) problem that could cause an infinite loop has been resolved.
+   Developer changes:
+      The versioned symbolic link for libbrlapi.so is now created by make api.
+      The run-* scripts now work when the build and source trees aren't the same.
+      The run-brltty script now uses the message catalogs within the build tree.
+      The msgtest command has been added.
+
+December 23, 2020:
+ - BRLTTY 6.2 released:
+   General changes:
+      Problems related to the system waking up from suspend have been resolved.
+      The format of the DESCCHAR command's output has been improved.
+      The representation of any Unicode braille pattern can now be overridden.
+      Prompt patterns no longer fall back to the default algorithm.
+      The +route modifier now works for block commands.
+      The Scroll-aware Cursor Navigation preference has been added.
+      The Start Selection with Routing Key preference has been added.
+      The Frank Audiodata braille device is now supported.
+      The Java bindings have been significantly enhanced.
+   Alva braille driver changes:
+      Satellite models:
+         The Left/Right pads have been respectively renamed to Speech/Nav.
+         The CLIP_COPY and CLIP_APPEND commands have been bound.
+   Hedo braille driver changes:
+      Detection of The MobilLine model via USB has been improved.
+   Seika braille driver changes:
+      The left space bar has been remapped to being the Meta (left alt) key.
+   XWindow braille driver changes:
+      X clipboard support has been added.
+   AtSpi2 screen driver changes:
+      Cursor routing problems have been resolved.
+   BrlAPI changes:
+      The version is now 0.8.1 (it was 0.8.0).
+      A problem with reading raw keycodes has been fixed.
+      The server now also listens for localhost connections via IPV6.
+      Byte reordering is now done for watched parameter values.
+      Setting the computer braille cell size is no longer strict.
+      Reading the RENDERED_CELLS parameter now works.
+      These have been added for getting the underlying file descriptor:
+         brlapi_getFileDescriptor()
+         brlapi__getFileDescriptor(handle)
+         BRLAPI_INVALID_FILE_DESCRIPTOR
+      These parameters have been renamed:
+         BRLAPI_PARAM_BOUND_COMMAND_CODES -> BRLAPI_PARAM_BOUND_COMMAND_KEYCODES
+         BRLAPI_PARAM_COMMAND_SHORT_NAME  -> BRLAPI_PARAM_COMMAND_KEYCODE_NAME
+         BRLAPI_PARAM_COMMAND_LONG_NAME   -> BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY
+         BRLAPI_PARAM_DEVICE_KEY_CODES    -> BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES
+         BRLAPI_PARAM_KEY_SHORT_NAME      -> BRLAPI_PARAM_DRIVER_KEYCODE_NAME
+         BRLAPI_PARAM_KEY_LONG_NAME       -> BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY
+      And their corresponding parameter types have also been renamed:
+         brlapi_param_commandCode_t      -> brlapi_param_commandKeycode_t
+         brlapi_param_commandShortName_t -> brlapi_param_commandKeycodeName_t
+         brlapi_param_commandLongName_t  -> brlapi_param_commandKeycodeSummary_t
+         brlapi_param_keyCode_t          -> brlapi_param_driverKeycode_t
+         brlapi_param_keyShortName_t     -> brlapi_param_driverKeycodeName_t
+         brlapi_param_keyLongName_t      -> brlapi_param_driverKeycodeSummary_t
+   Linux security enhancements:
+      BRLTTY can now run as an unprivileged user.
+      Some namespaces are now being isolated.
+      A system call filter can be enabled.
+      The --privilege-parameters command line option has been added.
+      The privilege-parameters configuration file directive has been added.
+      The --with-privilege-parameters configure option has been added.
+      Full details in README.Linux (online at http://brltty.app/doc/Linux.html).
+   Android changes:
+      Finding user customization files in /sdcard/brltty/ has bee fixed.
+      Text selection and host clipboard interactions have been implemented.
+      The screen element ordering algorithm has been improved.
+      Speech tracking has been implemented.
+      Commands for each of the global actions have been added.
+      Global actions can now also be performed via chords.
+      Global actions for moving to the first/last screen element have been added.
+      Navigation among the visible screen windows is now supported.
+      A global action to show the window title has been added.
+      A global action to show various device status indicators has been added.
+      Support for structural web page navigation has been added.
+   Systemd management changes:
+      The brltty-device@.service instance unit has been added.
+      BRLTTY is now run as an unprivileged user with perks.
+      The BRLTTY instance is now stopped correctly on USB disconnect.
+      A USB device management problem introduced in Systemd 247 has been fixed.
+      BRLTTY's needed directories are now created (if necessary).
+      The brlapi group is now assumed (e.g. for /etc/brlapi.key ownership).
+   Upstart udev rules changes:
+      The initctl command (rather than start and stop) is now used.
+      The --quiet and --no-wait options are now used.
+      The udev rules now restart the job on connect if it's already running.
+   Polkit changes:
+      Members of the brlapi group are now authorized.
+      The rules/actions can now be (un)installed via the Polkit make file.
+   Command installation changes:
+      The brltty-config script has been renamed to brltty-config.sh.
+      The brltty-prologue.sh script is now installed.
+      The brltty-genkey script is now installed.
+
+April 6, 2020:
+ - BRLTTY 6.1 released:
+   New commands:
+      REFRESH: rewrite the whole braille display (some drivers).
+      REFRESH_LINE: rewrite a specific braille line (some drivers).
+      ROUTE_LINE: bring the cursor to a specific braille line.
+   New braille drivers:
+      Canute: A 40 column, 9 line display from Bristol Braille.
+   BrlAPI braille driver changes:
+      The version is now 0.8.0 (it was 0.7.0).
+      Increase/decrease the client priority based on the screen content quality.
+      A quality of "good" overrides Orca rendering - lower ones don't.
+   HandyTech braille driver changes:
+      Basic Braille Plus models are supported.
+      The Easy Braille can be autodetected when using Bluetooth.
+   HumanWare braille driver changes:
+      The Brailliant BI 14 can be autodetected when using Bluetooth.
+      The routing keys of the Brailliant BI 14 work.
+      Four new models are supported:
+         HumanWare BrailleOne
+         APH Chameleon 20
+         APH Mantis Q40
+         NLS eReader
+   Inceptor (BrailleMe) braille driver changes:
+      Cursor positioning has been fixed.
+      USB (NVDA mode) autodetection works.
+      USB (NVDA mode) key bindings have been rationalized.
+      Bluetooth (BrailleBack mode) key bindings have been rationalized.
+   AtSpi2 screen driver changes:
+      The BRLTTY and X clipboards are synchronized.
+      Highlighted screen regions are supported.
+      When a widget doesn't have text then its accessibility name is rendered.
+      Set the screen content quality (for the BrlAPI braille driver) as follows:
+         good: terminal widgets
+         fair: text widgets (e.g. labels)
+         poor: widgets that have descriptive text
+         none: widgets that don't have a text interface
+      The type= parameter now specifies which widgets override Orca rendering.
+      type=default has been added (and is the new default).
+      The default is to defer to Orca except for terminal widgets.
+   Linux screen driver changes:
+      The fallbackText= parameter has been added.
+      Cursor routing when there are wide characters has been fixed.
+      Cursor positioning when there are wide characters has been fixed.
+   BrlAPI changes:
+      UTF-8 character encoding is handled internally (no longer needs iconv).
+      Large file descriptor numbers no longer cause a client to crash.
+      The client and server protocol versions can be increased independently.
+      Packets can contain up to 4096 (rather than 512) bytes.
+      The brlapi__pause() function can be used to wait for an incoming event.
+      A framework for setting, getting, and watching parameters has been added:
+         brlapi_getParameterProperties()
+         brlapi__getParameter()
+         brlapi__getParameterAlloc()
+         brlapi__setParameter()
+         brlapi__watchParameter()
+         brlapi__unwatchParameter()
+      The brltty-clip command can be used to get/set the clipboard content.
+   xbrlapi changes:
+      Now backgrounds after successfully establishing the BrlAPI connection.
+      The -n (--no-daemon) option disables backgrounding.
+      The BRLTTY and X clipboards are synchronized.
+   Text table changes:
+      Defining the Unicode replacement character (U+FFFD) works properly.
+      The fr-vs table supports the middle dot character.
+   Contraction table changes:
+      Undefined characters now fall back to their text table representations.
+      The en-ueb-g2 table has been updated.
+      The zh-tw-ucb table has been removed.
+      Renamed French tables:
+         fr-integral -> fr-g1
+         fr-abrege -> fr-g2
+      Renamed German tables:
+         de-basis -> de-g0
+         de-vollschrift -> de-g1
+         de-kurzschrift -> de-g2
+         de-kurzschrift-1998 -> de-1998
+         de-kurzschrift-2015 -> de-2015
+   Linux changes:
+      The systemd and udev wrappers are now in /usr/lib/brltty/.
+   Android changes:
+      The Update Application button on the Actions screen works.
+      Routing key #7 presents the Accessibility Actions for a screen element.
+   Windows changes:
+      A potential BrlAPI client/server communication deadlock has been fixed.
+      Some .bat scripts have been renamed:
+         install-brltty -> enable-brlapi
+         uninstall-brltty -> disable-brlapi
+         run-debug -> debug-brltty
+
+February 22, 2019:
+ - BRLTTY 6.0 released:
+   General changes:
+      Chords Dot4 + Dot6 + Dot8/Dot7 turn on/off typing Unicode braille patterns.
+      Word wrap when panning to the left now works properly.
+      Cursor tracking while word wrap is on has been fixed.
+      Support for speaking the current line's indent has been added.
+      Vertical screen scroll tracking is now supported.
+      Command execution can now be delayed till all the keys have been released.
+      Resources are no longer wasted checking non-braille USB devices.
+      The eSpeak-NG speech driver has been added.
+      BRLTTY can now run within an InitRamFS as a dracut module.
+      The -Y [--start-message=] and -Z [--stop-message=] options have been added.
+      The prompt-pattern configuration file directive has been added.
+   Baum braille driver changes:
+      The B2G key bindings have been significantly improved.
+   HandyTech braille driver changes:
+      Support for the Activator has been added.
+      Key bindings now work correctly after returning from the internal mode.
+   Hedo braille driver changes:
+      Newer MobilLine models are now detected.
+   HIMS braille driver changes:
+      Shifted qwerty keyboard letters are now interpreted correctly.
+   HumanWare braille driver changes:
+      Bluetooth connection problems have been resolved.
+      USB session initialization problems have been resolved.
+      The BrailleNote Touch routing keys now work.
+      The BI 32 joystick now works.
+      BI 14 USB detection has been fixed.
+   MDV braille driver changes:
+      Binding changes have been made so that F10 now does exit the menu.
+   ViaVoice speech driver changes:
+      The driver has finally been revived.
+      Speech tracking has been implemented.
+      The configure option --with-viavoice has been removed.
+   Linux screen driver changes:
+      Full Unicode support is finally available (no more 512 character limit).
+      Among many other benefits, Unicode braille patterns now automatically render.
+      This capability only works if a 4.19 (or later) kernel is being used.
+      The unicode=no driver parameter can be used to revert to the older behaviour.
+   AtSpi2 screen driver changes:
+      The default is now to only read terminal screens.
+      Determining which widget is currently active has been fixed.
+      BRLTTY is now automatically stopped when the session is closed.
+   BrlAPI changes:
+      The version is now 0.7.0 (it was 0.6.7).
+      The server now starts immediately rather than when the braille driver starts.
+      The following new client functions have been added:
+         void brlapi_getLibraryVersion(int *major, int *minor, int *revision)
+         void brlapi__setClientData(brlapi_handle_t *handle, void *data)
+         void *brlapi__getClientData(brlapi_handle_t *handle)
+         int brlapi__readKeyWithTimeout(brlapi_handle_t *handle,
+                                        int milliseconds,
+                                        brlapi_keyCode_t *code)
+      xbrlapi now maps unmapped keysyms to temporary keycodes.
+   Text table changes:
+      The se (Northern Sami) table has been added.
+      New directives: ifGlyph, ifNotGlyph, ifCell, ifNotCell
+   Contraction table changes:
+      A table for the German 2015 standard [de-kurzschrift-2015] has been added
+      (the de-kurzschrift-1998 table should now be used for the 1998 standard).
+      New directives: cldr, replace
+      The cldr directive has been added to tables for languages that have defined
+      annotations: af, de, en, es, fr, ko, nl, pt, sw, th, zu
+      (you also need to install your distribution's CLDR annotations package).
+   Android changes:
+      The Android device is now kept awake while navigating internal screens
+      (help, the preferences menu, a frozen screen, etc).
+      New braille-friendly widget representations:
+         Check Boxes: ⣏ ⣹ (not checked), ⣏⠶⣹ (checked)
+         Radio Buttons: ⢎ ⡱ (not selected), ⢎⠶⡱ (selected)
+         Switches: ⢸⣭⡇ (off), ⢸⣛⡇ (on)
+      A notification showing the braille device and its state has been added.
+      Support for the Accessibility button (Android 8 or later) has been added.
+      The Actions screen has been added - you can get to it by:
+         Global Action #5 (emulate the F5 keyboard key)
+         Tapping the (new) notification.
+         Clicking the (new) Accessibility button.
+      Routing keys beyond an element's text are now associated with that element.
+      Routing key #6 now does a context click (for elements that support it).
+      The algorithm that decides which elements to render has been much improved.
+      The list renderer now orders elements much more sensibly.
+      The accuracy of left, right, up, and down navigation has been improved.
+      Support for range controls has been added (adjust with the scroll actions).
+      Bluetooth connections are no longer tried:
+         If the Android device doesn't have a Bluetooth adapter.
+         If the Android device's Bluetooth adapter is disabled.
+         While the Android device is searching for new Bluetooth devices.
+
+February 5, 2018:
+ - BRLTTY 5.6 released:
+   General updates:
+      BRLTTY has been relicensed to the LGPL (from the GPL).
+      Better cursor routing handling of status line updates and vertical scrolling.
+      The Track Screen Scroll setting has been added.
+      The Word Wrap setting has been added.
+      Panning left when using a contraction table now word wraps.
+      Typing dismisses an alert message.
+      The test for when INFO does text maximization instead has been fixed.
+      Overrides are now applied when the preferences haven't yet been saved.
+      The Inceptor braille driver (from Innovision) has been added.
+   The Baum braille driver:
+      On models with B9 and B10 by the joystick, both are interpreted as Space.
+      Input packet handling for Pronto! V3 models has been fixed.
+   The EuroBraille braille driver:
+      Improved autodetection when using Bluetooth.
+      The Esytime braille keyboard can be used over Bluetooth and USB.
+      For Esytime models, B5 has been renamed to B8.
+   The HIMS braille driver:
+      The Smart Beetle is now recognized when using Bluetooth.
+   The HumanWare braille driver:
+      The BrailleNote Touch is recognized when using USB.
+   BrlAPI changes:
+      The version is now 0.6.7 (it was 0.6.6).
+      The brlapi_getModelIdentifier() function has been added to the API.
+      The default character set is now based on the user's locale.
+      Sessions would occasionally hang when connecting.
+      PolKit authentication is no longer attempted when it isn't available.
+      Reading raw key codes no longer uses key ranges.
+   Text tables:
+      The lt (Lithuanian) table has been updated.
+      The uk (Ukrainian) table has been added.
+   Contraction tables:
+      The fr-abrege (French) table has been updated.
+      The lt (Lithuanian) table has been added.
+      Support has been added for LibLouis tables.
+      brltty-ctb's output is now UTF-8 (rather than ASCII).
+   Key tables:
+      The ifPlatform and ifNotPlatform directives have been added.
+   Systemd changes:
+      Path (rather than target) units are now used to manage BRLTTY instances.
+   Android changes:
+      Danish translations have been added.
+   Build features:
+      Add the reload target to the make files for Systemd and Udev.
+      A make file for install/uninstall of the AppStream rules has been added.
+      Python 3.6 is supported.
+
+April 18, 2017:
+ - BRLTTY 5.5 released:
+   General changes:
+      The braille and speech banners no longer include the revision identifier.
+      The -o (--override-preference=) option has been added.
+      quiet-if-no-braille can be specified via the configuration file.
+      Cursor routing hangs have been resolved.
+      The Log Messages submenu (for warnings and errors) has been added.
+      Support for panning within a long alert message has been added.
+      Alert beeps work on a Linux serial console.
+      The Greek text table has been improved.
+   Braille device changes:
+      The default is to check both USB and Bluetooth (not just USB).
+      Specifying the Bluetooth address is no longer mandatory.
+      A prefix of a Bluetooth device's name can be specified.
+      A generic USB serial adapter is probed only if it's been customized.
+   Baum braille driver changes:
+      Support for the Vario Ultra has been improved.
+      Support for the Orbit 20 has been added.
+   EuroBraille braille driver changes:
+      More Esytime bindings have been added.
+      Support has been added for the EsysLight 80 and the Esytime Evolution.
+   HandyTech braille driver changes:
+      Support has been added for the Actilino.
+      The rocker keys can be used for efficient navigation of the menu.
+      B4+B5 has been bound to Command Learn Mode.
+      The (now redundant) keypad binding for Command Learn Mode has been removed.
+      The Bluetooth names of the Active Star and the Braillino are recognized.
+   HIMS braille driver changes:
+      Function key bindings have been added for models with four scroll keys.
+   TTY braille driver changes:
+      The braille cells are shown (in addition to the text).
+   HumanWare braille driver changes:
+      The Braille Note is autodetected when using USB.
+      Support has been added for the Braille Note Touch.
+   Polkit changes:
+      The policy file is installed.
+      The policy has been renamed to org.a11y.brlapi (from org.brltty).
+      Initialization doesn't delay BRLTTY startup.
+   Systemd changes:
+      The default instance is now brltty.target (not brltty.service).
+      Multiple instances can be managed (via brltty@.target instances).
+      A make file has been added to simplify unit and wrapper installation.
+      BRLTTY isn't started until Udev has finished initializing.
+      BRLTTY isn't automatically restarted if it terminates cleanly.
+   Udev changes:
+      A make file has been added to simplify rules and wrapper installation.
+   BrlAPI changes:
+      The version is now 0.6.6 (it was 0.6.5).
+      The server isn't started until the braille driver has started.
+      Several small fixes.
+   xbrlapi changes:
+      The X11 and gdm autostart files are installed.
+      Constantly trying to connect when BRLTTY isn't available has been fixed.
+      Reconnection on every window change has been fixed.
+   Key table changes:
+      Common chords for all of the sticky modifier keys have been added.
+      Listings combine dot keys, e.g. Dots12 instead of Dot1+Dot2.
+   Android changes:
+      The cursor no longer jumps back to the start while spaces are being typed.
+      Customization files can be in internal storage and/or on the SD card.
+   Windows changes:
+      Interaction with NVDA has been revived.
+      LibUSB can be used on a 64-bit system.
+      Builds can be done on MinGW64.
+   Build changes:
+      AppStream metainfo is maintained and installed.
+      The new Speech Dispatcher installation layout is supported.
+      Fixes to support building on Solaris 10 and 11 with C99.
+
+June 28, 2016:
+ - BRLTTY 5.4 released:
+   Core changes:
+      The Delayed Cursor Tracking feature (and preference) has been added.
+      Stuck keys are now automatically released after a specifiable timeout.
+      Touch events no longer interfere with Learn Mode.
+      Support for the Polkit authorization manager has been added.
+      A couple of memory corruption issues have been resolved.
+   USB fixes:
+      Device disconnect detection has been improved.
+      Excessive CPU consumption for some older serial adapters has been resolved.
+   Baum braille driver changes:
+      More bindings have been defined for models that have the B11 key.
+      False reporting of input timeouts has been resolved.
+      Handling of unknown packet types has been improved.
+   HandyTech braille driver changes:
+      Model detection on Windows has been fixed.
+      Output errors now restart the driver.
+   HIMS braille driver changes:
+      Support for the Smart Beetle has been added.
+      Newer firmware resolution of qwerty key code conflicts is supported.
+   HumanWare braille driver changes:
+      Several USB connection problems have been resolved.
+      The newer HID-based USB protocol is supported.
+      The Brailliant B 80's Bluetooth device name is recognized.
+   Papenmeier braille driver changes:
+      Clipboard bindings have been added that work on EL-C models.
+      Bar bindings for the menu now work on EL models that don't have switches.
+      Bindings may now be defined that use a routing key and an EL key.
+   XWindow braille driver changes:
+      Fixed-size font restrictions have been removed.
+   xbrlapi changes:
+      Brltty restarts are handled.
+      Logs written to .xsession have been improved.
+   brltty-trtxt changes:
+      The -b [--no-base-characters] option has been added.
+   Systemd/Udev changes:
+      Multiple USB braille devices are handled - each starts its own brltty.
+      Disconnecting a USB braille device stops the associated brltty.
+   Updated Tables:
+      The en_CA (English Canada) text table.
+      The zh_tw (Chinese, Taiwan) contraction table.
+
+December 22, 2015:
+ - BRLTTY 5.3.1 released:
+   Linux screen driver fix:
+      Some consoles couldn't be accessed due to an I/O error.
+   Android fix:
+      The preference settings can be saved.
+   HIMS braille driver changes:
+      Autodetecting the braille device's model has been improved.
+      Keys that a model doesn't have are no longer listed within its help text.
+      Support for qwerty keyboards has been improved.
+      The scroll key bindings have been harmonized across all models.
+   Key table changes:
+      The -a (--audit) option has been added to brltty-ktb.
+      Duplicate key bindings have been removed.
+      The "uppercase" command modifier has been renamed to "upper".
+      The following directives have been added:
+         beginVariables, endVariables, ignore, listVariables
+
+December 15, 2015:
+ - BRLTTY 5.3 released:
+   General fixes:
+      The missing key event (stuck key) problem has been resolved.
+      The braille display is now initialized when the -q (--quiet) option is used.
+      Playing alert tunes has been moved to a dedicated thread.
+      PCM (sound card) tone generation is now way more efficient.
+      Program-relative paths are no longer used for relative paths specified via
+         command line options or configuration file directives.
+      Explicit paths for tables no longer consider the tables directory path.
+      The -U (--updatable-directory) option has been added (it used to be
+         hard-coded to /var/lib/brltty/).
+      Braille driver parameters may be specified via the fourth boot parameter
+         (use + to separate them).
+      The speech FIFO on Linux has been moved to the writable directory (see -W -
+         the default is /var/run/brltty/).
+      The Console Bell Alert preference has been added - when enabled, and with
+         the tune device set to PCM, the console bell can be heard even if the PC
+         speaker either doesn't exist or isn't supported.
+   New and changed commands:
+      The toLeft modifier can be applied to any vertical motion command.
+      The AltGr (Right Alt) and GUI (Windows) keys are supported.
+      The PRPGRPH (go to previous paragraph) command navigates to the first line
+         of the current paragraph (or, if already on it, to the first line of the
+         previous paragraph) rather than to the last line of the previous
+         paragraph.
+      The space bar is a real space (rather than a braille character with no dots)
+         when the keyboard is in Unicode braille input mode.
+      Disabling the braille keyboard now disables all braille input commands.
+      New commands that always skip forward/backward to a nonblank braille window:
+         NXNBWIN, PRNBWIN
+      Sticky modifier command improvements:
+         Cycle through next,on,off (rather than just on,off) - next means just for
+         the next typed character, and times out after five seconds.
+      New commands to start/stop the braille,speech,screen drivers:
+         BRL_START, BRL_STOP, SPK_START, SPK_STOP, SCR_START, SCR_STOP
+      New commands for attaching to a specific (not necessarily current) console:
+         SELECT, SELECT_NEXT, SELECT_PREV
+   Albatross braille driver changes:
+      Detection of no pending input has been improved.
+      Recognizing a failed autodetection probe has been improved.
+   Alva braille driver changes:
+      Establishing a Bluetooth connection is now supported for more models.
+   Baum braille driver changes:
+      The standard braille keyboard chords are supported.
+      The Refreshabraille has an improved key table.
+      The Conny has an improved key table.
+      The 12-cell Vario/BrailleConnect now uses the Conny key table.
+      The protocols= parameter has been renamed to protocol= and accepts a
+         specific protocol name (default, escape, hid1, hid2, ht, pb).
+      Support for the following models has been added:
+         VarioUltra, Pronto!, Brailliant2, SuperVario2, HID Refreshabraille 18
+   BrailleMemo braille driver changes:
+      Model autodetection has been improved.
+   FreedomScientific braille driver changes:
+      The key tables for newer models use the newer key names:
+         The wheels are called Nav keys.
+         The GDF keys are called Selector keys.
+   HandyTech braille driver changes:
+      Touch-based navigation has been implemented for the following models:
+         Modular Evolution, Active Braille, Active Star
+   HIMS braille driver changes:
+      Function keys can be emulated by pressing space with a routing key.
+      The Braille Sense U2 is recognized.
+      The Braille Edge scroll keys are now mapped correctly.
+   HumanWare braille driver changes:
+      Bindings for adding Alt, Control, or both to a function key have been added.
+   MDV braille driver changes:
+      The bindings are now defined within key tables.
+      Support for the following models has been added:
+         Lilli Blu, MB248, MB408+
+   Papenmeier braille driver changes:
+      The status key bindings have been standardized across the various models.
+      Minor changes to the front key bindings (for models with 13).
+      XT keyboard support has been fixed.
+   XWindow braille driver changes:
+      Support for the Unicode braille characters has been added.
+   AtSpi2 screen driver changes:
+      The widget with focus on startup is determined correctly.
+      The currently focused widget is cached for efficiency.
+      The screen size is implicitly increased based on the cursor's location.
+      The release= (yes, no) parameter has been added.
+   Linux screen driver changes:
+      VGA foreground and background colours are now reported correctly.
+      A font change now triggers a braille display update.
+      The VT= (number) parameter has been added - 0 means the current console.
+      Systemd is no longer blocked from starting a login session on an unused tty.
+   Screen screen driver changes:
+      ASCII control characters can now be entered.
+   BrlAPI changes:
+      The version is now 0.6.4 (it was 0.6.3).
+      Cursor blinking finally works again.
+      The braille display is refreshed whenever it comes back online if the client
+         has focus.
+   Language profile support fixes:
+      The language profile can now be changed when there's no braille device.
+      The braille device and keyboard key table help texts are regenerated
+         according to the new localization when the language profile is changed.
+   Text table changes:
+      The en_CA (English Canada) text table has been updated.
+      The fr-vs (VisioBraille) text table has been updated.
+      The alias directive has been added.
+      Support for the variable and conditional directives has been added.
+      The -u (--undefined) option has been added to brltty-ttb - it reports
+         the characters in the current screen font that aren't yet defined in the
+         text table.
+      brltty-ttb can write C preprocessor format (cpp) tables.
+   Contraction table changes:
+      The Unicode braille characters are now handled internally.
+      The en-us-g2 (grade 2 US English) contraction table has been updated.
+      The zh-tw (Taiwan Chinese) contraction table has been updated.
+   Key table changes:
+      Major chords key subtable changes:
+         Tab is now Space+Dots45 (it was Space+Dots2356).
+         Backspace is now just Dot7, and Enter is now just Dot8.
+         Add Space to get just dot 7 or just dot 8.
+         Unchorded bindings have been added for navigating the preferences menu:
+            Dot1: go to the previous item
+            Dot4: go to the next item
+            Dot2: go to the first item
+            Dot5: go to the last item
+            Dot3: select the previous setting for the current item
+            Dot6: select the next setting for the current item
+            Dot7: go back one level
+            Dot8: exit the menu
+      Minor changes to the keypad keyboard table.
+      The assignDefault directive has been added.
+      The -r (--reStructuredText) option has been added to brltty-ktb - it causes
+         the help text to be formatted as reStructuredText.
+      The help text has been improved:
+         Related bindings have been organized into functional groups.
+         Each of these groups has a descriptive header.
+         Variants of the same binding are listed together.
+         The bindings for Help and Learn are intentionally listed at the top.
+   Java bindings fixes:
+      The JDK hierarchy is located even when symbolic links point to it.
+      The subdirectory containing machine-dependent JNI definitions is included.
+   Android updates:
+      F9 goes to the Global Actions (Power Off) dialog (Lollipop and later).
+      F10 is the Menu key.
+      The braille display stays in the active window (new problem as of Lollipop).
+      The order in which screen elements are rendered is more intuitive.
+      Czech translations have been added.
+      Finnish translations have been added.
+   Windows updates:
+      Support for the Unicode braille characters has been added.
+   brltty-tune (formerly tunetest) changes:
+      The tune syntax has been completely changed.
+      Tunes can be read from files.
+   Build changes:
+      The autoconf dependency has been updated to 2.64 (from 2.53).
+      The ktbtest command has been renamed to brltty-ktb.
+      The tunetest command has been renamed to brltty-tune.
+      The brltty-atb, brltty-ktb, and brltty-tune commands are installed.
+      Key table help text is rendered in HTML (as well as in plain text).
+      Systemd service ready notification (via type=notify) is supported.
+      mkwin command updates:
+         The -C (Cygwin root directory) option has been added.
+         A safe, relative path to the configure script can now be used.
+
+November 6, 2014:
+ - BRLTTY 5.2 released:
+   General Fixes:
+      Unexpected cursor tracking when switching virtual consoles has been resolved.
+      The PASSPS2 command has (finally) been implemented.
+      An infrastructure for defining custom commands has been added.
+      A braille update is now forced when the braille device comes back online.
+      All speech-related delays have been removed.
+      Language profiles have been implemented (see README.Profiles).
+      The following log categories have been added:
+         brldrv  Braille Driver Events
+         spkdrv  Speech Driver Events
+         scrdrv  Screen Driver Events
+         all     a pseudo-category that enables all categories
+         -name   disable a log category (especially useful after specifying all)
+   New Commands:
+      BRLKBD                 Set the braille keyboard enabled/disabled.
+      PASTE_HISTORY          paste from the clipboard's history
+      SET_TEXT_TABLE         directly select a text table
+      SET_ATTRIBUTES_TABLE   directly select an attributes table
+      SET_CONTRACTION_TABLE  directly select a contraction table
+      SET_KEYBOARD_TABLE     directly select a keyboard table
+      SET_LANGUAGE_PROFILE   directly select a language profile
+      ALERT                  play a specific alert tune (used internally)
+   New Braille Drivers:
+      The Hedo [hd] driver has been added (MobilLine and ProfilLine models).
+   Alva Braille Driver:
+      The BC's Windows key has been bound to the Escape key.
+      Key Repeat and Secondary Cursor Row Emulation are now automatically disabled.
+      The driver has been updated to use newer core facilities.
+   BrailleMemo Braille Driver:
+      The key bindings have been changed.
+      The BM Smart can now connect via USB.
+   BrailleNote Braille Driver:
+      The APEX is now recognized when connected via USB.
+      The APEX routing keys are now handled.
+      Text input in Input Mode is now supported.
+   EuroBraille Braille Driver:
+      The Protocol= parameter now recognizes "esysiris".
+      A number of USB connectivity issues have been resolved.
+   FreedomScientific Braille Driver:
+      A number of USB connectivity issues have been resolved.
+   HandyTech Braille Driver:
+      The key combination LeftSpace+Dot7 has been bound to the Backspace key.
+      Support for the following new models has been added:
+         Active Star 40, Modular Connect 88, Connect Braille 40
+   Iris Braille Driver:
+      A possible crash when woken up in packet forward mode has been resolved.
+      Key bindings have been added for the (new) PASTE_HISTORY command.
+      The LatchDelay= parameter has been added.
+      The driver has been updated to use newer core facilities.
+   Metec Braille Driver:
+      Support for status cells has been added.
+      The driver has been updated to use newer core facilities.
+   Papenmeier Braille Driver:
+      Support for the Live and Live+ models has been added.
+      Support for keyboards in Windows mode has been added.
+      The two braille keyboard thumb keys have been remapped:
+         LeftThumb now functions as the control key.
+         RightThumb now functions as the left alt key.
+   TSI Braille Driver:
+      Key tables (rather than hard-coded bindings) are now used.
+      The repeat delay and rate can now be controlled from the preferences menu.
+      The HighBaud=yes/no parameter has been added.
+      The SetBaud= parameter has been added.
+      The driver has been updated to use newer core facilities.
+   BrlAPI Updates:
+      Now at release 0.6.3.
+      Support for the XDG_VTNR environment variable has been added.
+   USB Updates:
+      All endpoint I/O is now deferred until after the strings have been read.
+      The strings are now encoded in UTF-8 (rather than in Latin1).
+      Asynchronous endpoint input errors are now detected.
+      Detaching the kernel's driver via libusb is now supported.
+      Detaching the kernel's driver via libusb-1.0 is now supported.
+      Reading from an endpoint via libusb-1.0 now works again.
+      Native Linux support now works on big endian platforms.
+      The braille driver is restarted on Linux if the serial adapter is unplugged.
+   Preferences Menu Changes:
+      The "Save on Exit" selector has been moved back to the top of the root menu.
+      The MENU_PREV_LEVEL command when in the root menu now exits the menu.
+      File extensions are no longer shown in the table selectors.
+      The Keyboard Table selector has been added.
+      The Profiles submenu has been added.
+      The Build Information advanced submenu has been added.
+      The Tools advanced submenu has been added.
+   Text Tables:
+      no-oub (Norwegian) has been renamed to no-oup.
+      no-oup is now the default Norwegian table.
+   Contraction Tables:
+      Unicode combining characters are now handled.
+      Best fit (rather than explicit) character matching is now done.
+      The "contraction" opcode no longer applies if even one non-punctuation
+         character either precedes or follows the sequence.
+      Updated Tables:
+         de-kurzschrift (German)
+         en-us-g2 (English US Grade 2)
+   Key Tables:
+      Contexts can now have meaningful names.
+      Including a subtable can no longer change the context of the including table.
+   Android Changes:
+      The version code and version name are now set.
+      The occasional crash on service startup has been resolved.
+      Bluetooth devices that don't implement service discovery are now supported.
+   DOS Changes:
+      The configuration file has been renamed to brltty.cfg (from brltty.con).
+      The log file is now created in BRLTTY's top-level directory.
+      The log file is now written cleanly.
+      The "page fault on program termination" problem has been resolved.
+      The mkdosarc and mkdostools scripts have been added.
+   Windows Changes:
+      The mkwin script has been made much more flexible.
+      brltty.inf has been renamed to brltty-libusb.inf.
+      brltty.nsi has been renamed to libusb.nsi.
+   Build Changes:
+      The --with-keyboard-package option has been renamed to --with-kbd-package.
+      The PKG_CONFIG environment variable (pkg-config command path) is honoured.
+      A patch for screen-4.2.1 has been added.
+      The tables have been moved into separate subdirectories of /etc/brltty/:
+         Attributes/   attributes tables
+         Contraction/  contraction tables
+         Input/        braille driver tables
+         Keyboard/     keyboard tables
+         Text/         text tables
+
+March 27, 2014:
+ - BRLTTY 5.1 released:
+   General Changes:
+      An alert (2 beeps every 30 seconds) is now played when the screen is frozen.
+      Saving the preferences file now adds comments showing the possible values
+      (and the default value) for each property.
+      USB device identifiers now support the genericDevices= (yes, no) parameter.
+   Command Changes:
+      Unicode braille dots input is now supported:
+         The BRLUCDOTS toggle command has been added.
+         The "Braille Input Mode" preference has been added to "Input Options".
+      Skip Blank Windows now works like Skip Identical Lines:
+         FWINLT/FWINRT are unchanged:
+            If SBW is off then don't skip blank windows.
+            If SBW is on then honour Skip Blank Windows Mode.
+         FWINLTSKIP/FWINRTSKIP now work as follows:
+            If SBW is off then honour Skip Blank Windows Mode.
+            If SBW is on then don't skip blank windows.
+   The FreedomScientific Braille Driver:
+      Various problems with detecting a connection error have been resolved.
+      A problem causing a braille display freeze has been resolved.
+   The Papenmeier Braille Driver:
+      The following bindings have been added for models with 22 status cells:
+         Binding        Command
+         Status20       BRLUCDOTS
+         Dot7+Status20  BRLUCDOTS+off
+         Dot8+Status20  BRLUCDOTS+on
+   The Application Programming Interface (BrlAPI):
+      The version has been increased to 0.6.2 (from 0.6.1).
+      The retainDots= parameter has been removed.
+      The PASSDOTS command is no longer implicitly accepted by default.
+      Explicitly accepting PASSDOTS bypasses the conversion to PASSCHAR.
+   Environment Variables:
+      BRLTTY_NO_API (yes, no) is now supported.
+   The Configuration File (brltty.conf):
+      no-api (yes, no) can now be specified.
+      The following directives have been added:
+         include <file>
+         ifSet <property> [<statement>]
+         ifNotSet <property> [<statement>]
+         assign <variable> <value>
+         ifVar <variable> [<statement>]
+         ifNotVar <variable> [<statement>]
+         else
+         endIf
+      The special syntax \{variable} is now supported.
+   Text Tables:
+      The Hebrew text table (he) has been modernized.
+   Contraction Tables:
+      The US English Grade 2 (en-us-g2) contraction table has been updated.
+   Key Tables:
+      The following binding changes have been made to the "keypad" key table:
+         Binding           Old      New
+         KP0+KPMinus       SIXDOTS  SIXDOTS+off
+         KP0+KPPlus        -        SIXDOTS+on
+         KPPeriod+KPMinus  FREEZE   BRLUCDOTS+off
+         KPPeriod+KPPlus   -        BRLUCDOTS+on
+         KPMinus+KP3       -        FREEZE
+      The <statement> operand of the ifKey directive is now optional.
+      The following directives have been added:
+         ifNotKey <key> [<statement>]
+         ifVar <variable> [<statement>]
+         ifNotVar <variable> [<statement>]
+         else
+         endIf
+   Data File Customization:
+      The XDG configuration file paradigm is now supported.
+      A local version of any data file can be placed in "/etc/xdg/brltty/".
+      The XDG_CONFIG_HOME and XDG_CONFIG_DIRS environment variables are supported.
+      See "README.Customize" for full details.
+   Build Changes:
+      The source has been moved from an svn repository to a git repository at:
+         "https://github.com/brltty/brltty.git".
+      The URL of the web site has been changed to "http://brltty.com".
+      Many of the core headers are now installed (in "/usr/include/brltty/").
+      The brltty-devel rpm is now created.
+      The --with-api-socket-path= configure option has been added.
+
+January 27, 2014:
+ - BRLTTY 5.0 released:
+   Major Changes:
+      Conversion from frequent regular polling to event-based scheduling.
+      The speech driver now runs in its own thread.
+   General Changes:
+      The --message-delay brltty option has been renamed to --message-timeout.
+      The -Q [--quiet-if-no-braille] brltty option has been added.
+      The -U [--update-interval=] brltty option has been removed.
+      The CLIP_SAVE and CLIP_RESTORE commands have been added.
+      The PWGEN command has been removed.
+      The FREEZE toggle can now be explicitly set to on or off.
+      The confirmation tune is played twice if an explicit toggle is already set.
+      Braille driver selection using the Bluetooth device name is now supported.
+      A Bluetooth device address may now contain dashes (-) instead of colons (:).
+      The "bth:" and "bluez:" device qualifier aliases are no longer supported.
+      Braille device identifiers may now contain parameters (see README.Devices).
+      Lots of improvements to Android support have been made.
+   Preferences Menu Changes:
+      Input Options Submenu:
+         The Autorepeat Delay setting has been renamed to Long Press Time.
+         The Braille Orientation setting has been added.
+      Internal Parameters Submenu:
+         The log categories have been moved down into their own submenu.
+   The following braille drivers have been added:
+      bg: B2G models (from NBP).
+      mm: Braille Memo models (from KGS).
+   Significant upgrades have been made to the following braille drivers:
+      Alva, Baum,  Cebra, FreedomScientific, HandyTech, HumanWare,
+      Papenmeier, Pegasus, Seika.
+   Alva Braille Driver Changes:
+      Support has been added for the newer Braille Pen models (from Harpo).
+      Support has been added for the Easy Link 12 Touch (from Optelec).
+   FreedomScientific Braille Driver Changes:
+      All of the bumper keys now have bindings.
+      The rocker keys of the Focus 80 Blue now have bindings.
+      For Focus models:
+         The left shift key now functions as a control modifier key.
+         The right shift key now functions as a meta (left alt) modifier key.
+         The left GDF key plus the left bumper keys goes to the top/bottom line.
+         The right GDF key plus the right bumper keys goes to the top/bottom line.
+   HandyTech Braille Driver Changes:
+      Support has been added for the Basic Braille models.
+   HIMS Braille Driver Changes:
+      Support has been added for the Braille Sense U2.
+      Support has been added for using Braille Edge models via Bluetooth.
+      Support for models which have a qwerty keyboard has been improved.
+   Papenmeier Braille Driver Changes:
+      The status key bindings for models with 22 status keys have been revised.
+   Seika Braille Driver Changes:
+      The K2, K3, and K4 keys of the braille display models now work.
+   Voyager Braille Driver Changes:
+      Support has been added for the routing keys on newer Braille Pen models.
+   XWindow Braille Driver Changes:
+      It is now usable when only the default fonts are available.
+   BrlAPI Changes:
+      Version updated to 0.6.1.
+      API parameter syntax problems are now errors (rather than warnings).
+      Autorepeat key events are now being delivered to clients.
+      A crash when visual text wasn't being supplied by the core has been fixed.
+      Orca braille output is no longer being hidden by xbrlapi output.
+      gdm should now automatically start xbrlapi.
+   Text Table Changes:
+      The Slovenian table (sl) has been added.
+      The full-width Latin letters now have braille representations.
+      The full-width Latin digits now have braille representations.
+      brltty-trtxt now preserves space characters.
+   Attributes Table Changes:
+      The existing tables have been given more descriptive names:
+         attrib -> invleft_right
+         attributes -> left_right
+      The upper_lower table has been added.
+   Contraction Table Changes:
+      The US English Grade 2 table has been improved.
+      The German table has been improved.
+      The -v (--verification-table=) option has been added to brltty-ctb.
+   Key Table Changes:
+      A binding for TIME (KP0+T) has been added to the desktop key table.
+      A table for Sun type 6 keyboards (sun_type6) has been added.
+      Support has been added for long key press bindings.
+      The value of an assign statement may now contain variable expansions.
+   Build Changes:
+      The libusb-1.0 package is now preferred over the libusb package.
+      The message catalogs are now installed if the needed tools are available.
+      pyrexc is no longer an alternate compiler for the Python bindings.
+      Paths to Java build commands which contain blanks are now supported.
+      Many fixes for building on Mac OS X (Darwin) have been made.
+      The following wrapper scripts for a number of platforms have been added:
+         cfg-android, cfg-darwin.
+      The eutp command (for EuroBraille file transfer) has been added.
+
+April 1, 2013:
+ - BRLTTY 4.5 released:
+   General Fixes:
+      The cursor routing algorithm is now usually almost as fast as it used to be.
+      Speech navigation now works when braille is not being used.
+      Autospeak is now forced on if speech is being used without braille.
+      Autospeak no longer considers a single character to be a completed word.
+      The input modifier commands now function as regular toggles.
+      Hangs can no longer occur when the system clock is adjusted backward.
+      Occasional delayed input with some braille devices has been resolved.
+      Poor response when using USB with some braille devices has been resolved.
+      Bogus Bluetooth connect errors (resource busy) have been resolved.
+   Alva Braille Driver:
+      The key bindings for the BC models have been reworked.
+      Secondary routing keys are now interpreted as though they were primary keys.
+      The SecondaryRoutingKeyEmulation= parameter has been added.
+   Baum Braille Driver:
+      The RefreshaBraille key bindings have been improved.
+   BrailleNote Braille Driver:
+      Key bindings are now defined within key tables rather than being hard-coded.
+   Cebra Braille Driver:
+      Bluetooth support has been added.
+      Support for the Novem braille keyboard has been added.
+   FreedomScientific Braille Driver:
+      Support for the Focus 14 has been added.
+      A key binding for CLIP_COPY has been added.
+      The definitions for the two Focus 40 rocker keys have been swapped.
+   HandyTech Braille Driver:
+      Poor performance when using an Active Braille has been resolved.
+   HIMS Braille Driver:
+      USB support for the Braille Edge has been added.
+      Newer Braille Sense models, when connected via USB, are now recognized.
+      Many key bindings for the Braille Sense and Braille Edge have been added.
+   HumanWare Braille Driver:
+      USB autodetection is now supported.
+   IrisLinux Braille Driver:
+      Removed - superceded by the Iris braille driver.
+   NinePoint Braille Driver:
+      Cebra devices are now supported by the new Cebra braille driver.
+      This driver now supports the NinePoint device.
+   Papenmeier Braille Driver:
+      Bluetooth is now supported.
+      Models EL40c, EL60c, and EL80c are now supported.
+   Seika Braille Driver:
+      Support for the Seika 80 has been added.
+   Voyager Braille Driver:
+      The key bindings have been reworked.
+   eSpeak Speech Driver:
+      Now runs in a separate thread in order to improve core performance.
+   BrlAPI Server:
+      The RetainDots= parameter has been added.
+   BrlAPI Client:
+      Version updated to 0.6.0.
+      The brlapi_writeWText() function has been added.
+      The Python bindings are now compiled by Cython (rather than with Pyrex).
+      Python 3 is now supported.
+      A file descriptor leak has been resolved.
+   Text Tables:
+      The fr-vs table has been updated.
+   Contraction Tables:
+      The en-us-g2 table has been updated.
+   Keyboard Key Tables:
+      Many bindings have been added to the laptop table.
+   Android:
+      An initial implementation is now available.
+      Runs poorly on 4.0 (ICS) but quite well on 4.1 (JB).
+      Screen navigation is supported.
+      See Documents/README.Android for details.
+
+June 7, 2012:
+ - BRLTTY 4.4 released:
+   Changes:
+      DOS option syntax (/option[:value]) can only be used on DOS or Windows.
+      The clipboard functions have been renamed to accurately reflect what they do:
+         CUTBEGIN  -> CLIP_NEW
+         CUTAPPEND -> CLIP_ADD
+         CUTLINE   -> COPY_LINE
+         CUTRECT   -> COPY_RECT
+         COPYCHARS -> CLIP_COPY
+         APNDCHARS -> CLIP_APPEND
+      The Caml bindings for BrlAPI have been renamed to Ocaml.
+      The tbltest program has been renamed to brltty-ttb and is installed.
+      The ctbtest program has been renamed to brltty-ctb and is installed.
+      The --with-compiler-prefix= configure option has been removed.
+   Fixes:
+      Brltty keyboard key bindings no longer interfere with Orca key bindings.
+      The updating of status lines no longer confuses the cursor routing algorithm.
+      The same USB device can no longer be used by more than one brltty at a time.
+   New Features:
+      The help function cycles through all relevant pages.
+         For example, help for the current keyboard key table is viewable.
+      The info-mode line contains a clock.
+      The preferences menu has been divided into a set of category-based submenus.
+         Open a submenu with any key that would normally alter a setting.
+         Close a submenu either by scrolling to its Close item and activating it
+         or via the new MENU_PREV_LEVEL function.
+      The -l [--log-level=] option of brltty accepts log category names.
+         The names are: ingio, inpkts, outpkts, brlkeys, kbdkeys, csrtrk, csrrtg.
+         Specify the log level and/or category names as a comma-delimited list.
+      The -C [--cancel-execution] option has been added to brltty.
+         The same pid file must be specified by both the cancelling and the
+         already-running brltty.
+      The pid file may be specified within the configuration file (brltty.conf).
+      The brltty-trtxt program has been added and is installed.
+   New functions:
+      MENU_PREV_LEVEL: Close the current submenu.
+      TIME: Display the current time (format configurable via the menu).
+      PWGEN: Set the clipboard to a random password (length set by routing key).
+      Sticky input modifier functions:
+         SHIFT: Add the shift modifier to the next typed character.
+         UPPER: Add the uppercase modifier to the next typed character.
+         CONTROL: Add the control modifier to the next typed character.
+         META: Add the meta (left alt) modifier to the next typed character.
+      Autospeak suboption toggles:
+         ASPK_SEL_LINE: Set autospeak selected line on/off.
+         ASPK_SEL_CHAR: Set autospeak selected character on/off.
+         ASPK_INS_CHARS: Set autospeak inserted characters on/off.
+         ASPK_DEL_CHARS: Set autospeak deleted characters on/off.
+         ASPK_REP_CHARS: Set autospeak replaced characters on/off.
+         ASPK_CMP_WORDS: Set autospeak completed words on/off.
+      Many functions specifically for speech navigation:
+         SPEAK_CURR_CHAR: Speak current character.
+         SPEAK_PREV_CHAR: Go to and speak previous character.
+         SPEAK_NEXT_CHAR: Go to and speak next character.
+         SPEAK_CURR_WORD: Speak current word.
+         SPEAK_PREV_WORD: Go to and speak previous word.
+         SPEAK_NEXT_WORD: Go to and speak next word.
+         SPEAK_CURR_LINE: Speak current line.
+         SPEAK_PREV_LINE: Go to and speak previous line.
+         SPEAK_NEXT_LINE: Go to and speak next line.
+         SPEAK_FRST_CHAR: Go to and speak first non-blank character on line.
+         SPEAK_LAST_CHAR: Go to and speak last non-blank character on line.
+         SPEAK_FRST_LINE: Go to and speak first non-blank line on screen.
+         SPEAK_LAST_LINE: Go to and speak last non-blank line on screen.
+         DESC_CURR_CHAR: Describe current character.
+         SPELL_CURR_WORD: Spell current word.
+         ROUTE_CURR_LOCN: Bring cursor to speech location.
+         SPEAK_CURR_LOCN: Speak speech location.
+         SHOW_CURR_LOCN: Set speech location visibility on/off.
+   New braille drivers:
+      hw: HumanWare
+      ir: Iris
+      np: NinePoint (Cebra)
+   Baum braille driver changes:
+      The Inka is fully supported.
+      New key bindings for models with six display keys:
+         Display1+Display5 displays the current time.
+   EuroBraille braille driver changes:
+      The latest models are supported.
+      Key tables, rather than hard-coded bindings, are used.
+      The display is updated when returning from the internal menu.
+      The LCD (visual display) is supported.
+      Large displays work reliably when connected via USB.
+      The protocol= parameter takes a model (rather than a protocol) name.
+   FreedomScientific braille driver changes:
+      New key bindings:
+         LeftRockerUp goes up one line.
+         LeftRockerDown goes down one line.
+   HandyTech braille driver changes:
+      New key bindings:
+         B2+B3+B5+B6 displays the current time.
+         B1+B2+!RoutingKey sets the clipboard to a random password.
+   Papenmeier braille driver changes:
+      A serious USB problem has been fixed.
+      New key bindings for models with 13 front keys:
+         Dot3, when in the menu, closes the current submenu.
+         Dot1+Dot2+Dot3+Dot6 routes the cursor vertically.
+         Dot1+Dot2+Dot3+Dot6+Dot7 switches to the previous virtual console.
+         Dot1+Dot2+Dot3+Dot6+Dot8 switches to the next virtual console.
+         Dot1+Cot2+Dot3+Dot7 displays the current time.
+         Dot1+Dot2+Dot3+Dot7+!RoutingKey1 sets the clipboard to a random password.
+   Seika braille driver changes:
+      The notetaker models are supported.
+   eSpeak speech driver changes:
+      The maxrate= parameter has been added.
+   Text tables:
+      The de (German) table has been updated.
+      The hu (Hungarian) table has been updated.
+      Support for writing JAWS (.jbt) text tables has been added to brltty-ttb.
+   Contraction tables:
+      the en-us-g2 (US English - Grade 2) table has been updated.
+      A table may be implemented as an executable program.
+      The latex-access (braille math) executable table has been added.
+      The -r [--reformat-input] option of brltty-ctb handles paragraphs better.
+   Keyboard key tables:
+      Many bindings have been added to the keypad and laptop tables.
+   Documentation:
+      A portuguese translation of the manual has been added.
+
+October 10, 2011:
+ - BRLTTY 4.3 released:
+   New options:
+      The -F [--speech-fifo=] option has been renamed to -i [--speech-input=].
+      The -F [--preferences-file=] option sets the path to the preferences file.
+      The -L [--log-file=] brltty option writes timestamped logs to a file.
+      The -q [--quiet] xbrlapi option suppresses the displaying of window titles.
+      The -r [--reformat-text] ctbtest option joins unindented input lines.
+      The log level can now be specified within brltty.conf.
+   New device support:
+      A braille driver for BrailComm displays has been added.
+      The HandyTech braille driver now supports the Active Braille.
+      The Voyager braille driver now supports the part 232 serial adapter.
+      The Voyager braille driver now supports the Braille Pen (aka Easy Link).
+   Preferences menu changes:
+      The preference selections now apply to the preferences menu itself, too.
+      Keyboard key bindings now work correctly within the preferences menu.
+      The "Text Style" preference has new settings:
+         8-Dot Computer Braille (formerly 8-dot)
+         Contracted Braille (formerly 6-dot)
+         6-Dot Computer Braille
+      The "Expand Current Word" preference has been added.
+         It's only visible if "Text Style" is set to "Contracted Braille".
+         It can be set to:
+            Yes (don't contract the word the cursor is on)
+            No (contract the whole line)
+      The "Capitalization Mode" preference has been added.
+         It's only visible if "Text Style" is set to "Contracted Braille".
+         It can be set to:
+            No Capitalization
+            Use Capital Sign
+            Superimpose Dot 7
+   General changes:
+      Internationalization support has been improved.
+      French and German translations have been added.
+      Support for multi-byte local character sets has been improved.
+      Horizontal window motions now work correctly with contracted braille.
+      The half window left motion no longer can move to a bad location.
+      Alert messages no longer disappear unexpectedly.
+      The speech FIFO is now created relative to the current working directory.
+      Serial flow control is now enabled before device probing.
+      The following global variables for use within tables are now predefined:
+         tablesDirectory
+         tableExtension
+         subtableExtension
+   Alva braille driver changes:
+      The BC key bindings have been changed significantly in order to add
+      more functions and to support the USB640 (which has no smartpad).
+      The braille keyboard of the BC Feature Pack is now supported.
+   Baum braille driver changes:
+      Updating the text and status cells of the Vario 80 has been fixed.
+      The front and command keys of the Vario 80 are now supported.
+   EuroBraille braille driver changes:
+      Reading keys is faster and no longer generates spurious input errors.
+   HandyTech braille driver changes:
+      Support for the Active Braille has been added.
+      The help screen now describes how the keypad keys are named.
+   Papenmeier braille driver changes:
+      The initial state of the switches and keys of EL models is detected.
+   Seika braille driver changes:
+      The help screen now describes the key layout.
+   TTY braille driver changes:
+      A few more key bindings have been defined.
+   Voyager braille driver changes:
+      Serial support has been added.
+      Bluetooth support has been added.
+      Support for the part 232 serial adapter has been added.
+      Support for the Braille Pen (aka Easy Link) has been added.
+   XWindow braille driver changes:
+      More and less restrictive fonts are supported.
+      Hard program exits have been removed.
+   eSpeak speech driver changes:
+      The full advertized speech rate range can now be used.
+   FestivalLite speech driver changes:
+      The default voice has been changed to kal (from kal16).
+   AtSpi screen driver changes:
+      No more annoying warnings when not on an AtSpi widget.
+   BrlAPI fixes:
+      ISO-8859-1 is now accepted as a character set when iconv isn't available.
+      The autorepeat flags are now handled on a per session basis.
+      Commands are now processed when the device is released.
+   WindowEyes BrlAPI driver changes:
+      Testing for BrlAPI open errors has been improved.
+   Text table changes:
+      A common subtable for the block characters has been added.
+      The glyph directive has been added.
+      Several alternate fonts for the Latin letters are now defined.
+      If a character isn't defined then check for an alternative which is:
+         (its Unicode base character, its iconv-defined ASCII equivalent, etc).
+      The en_UK text table has been renamed to en_GB (to comply with ISO 3166).
+      Updated text tables:
+         bo (Tibetan)
+         brf: dot 7 is no longer presented
+         fr_FR (French France)
+         fr-vs (French table used by VisioBraille devices)
+         is (Icelandic): updated to the newly adopted standard
+   Contraction table changes:
+      If a zero-width character isn't defined then don't show it.
+      Updated tables:
+         de-kurzschrift (German)
+         en-us-g2 (American English)
+         es (Spanish)
+         fr-abrege (French)
+         zh-tw (Taiwanese Chinese)
+   Key table changes:
+      A specific key within a set can now be specified by number.
+      A key combination can now include specific keys which aren't in set 0.
+      Keys which aren't in set 0 can now be mapped to keyboard functions.
+      A key set name can now be used to define a hotkey.
+      The help text no longer includes hidden hotkeys.
+      The help text now handles context-specific hotkeys correctly.
+      The note directive now supports the use of variables.
+   Windows changes:
+      Serial devices beyond COM9 may now be specified.
+      The WindowEyes driver is now copied into the correct installation directory.
+   Build changes:
+      Building an exported (not checked out) copy now "knows" its revision number.
+      Make support for install/uninstall of /usr/share/doc/brltty has been added.
+      The make targets for source archives are now prefixed with "src-".
+      The bin-tar, bin-tar-gzip, and bin-tar-bzip2 make targets have been added.
+      Support for xz compression of binary and source archives has been added.
+      The preferences file has been moved to /var/lib/brltty/brltty.prefs.
+      There's now only one preferences file (instead of one per braille driver).
+      The preferences file is now text-based (instead of binary).
+      The "writable" directory has been moved to /var/run/brltty.
+      The "library" directory is now known as the "drivers" directory.
+      The "data" directory no longer has a use and has been removed.
+      Support for the "gjar" command has been added.
+
+May 10, 2010:
+ - BRLTTY 4.2 released:
+   Ending a cut operation beyond the right edge of the screen has been fixed.
+   Key/character insertion works with newer versions of Xorg.
+   The braille driver help files have been converted to plain text.
+   Multiple instances of brltty may not use the same PID file.
+   The keypad keyboard key table provides a braille input mode.
+   The working directory is no longer set to the data directory.
+   The writable directory is created if it doesn't already exist.
+   USB I/O via libusb1 is supported.
+   The Metec braille driver has been added.
+   The eSpeak speech driver has been added.
+   The AtSpi2 screen driver has been added.
+   A BrlAPI client for Window-Eyes has been developed.
+   Albatross braille driver changes:
+      Converted to use key tables.
+      USB support has been added.
+   Alva braille driver changes:
+      Converted to use key tables.
+   Baum braille driver changes:
+      Converted to use key tables.
+      Vario Pro wheels are supported.
+      For Vario Pro models, only update the text cells which have changed.
+   CombiBraille braille driver changes:
+      Converted to use key tables.
+   FreedomScientific braille driver changes:
+      Converted to use key tables.
+      Bluetooth support has been added.
+      The Focus Blue is supported.
+      Rockers and bumper bars are recognized as distinct keys.
+   HandyTech braille driver changes:
+      Converted to use key tables.
+      Pressing two routing keys simultaneously immediately cuts the selected text.
+      Newer models which use the USB HID interface are supported.
+      The InputMode= parameter has been removed.
+   HIMS braille driver changes:
+      Converted to use key tables.
+   Papenmeier braille driver changes:
+      Converted to use key tables.
+      The DebugReads= and DebugWrites= parameters have been removed.
+      Support for the configuration file has been removed (now uses key tables).
+   Pegasus braille driver changes:
+      Converted to use key tables.
+   Seika braille driver changes:
+      Converted to use key tables.
+   Voyager braille driver changes:
+      Converted to use key tables.
+   BrlAPI changes:
+      Key event support has been added.
+      Various client fixes for the Windows environment.
+      Various fixes for when threads aren't available.
+      No more extraneous connection to localhost when using a TCP/IP host name.
+      A fix to exception handling within the Python bindings.
+   ExternalSpeech speech driver changes:
+      Two-letter driver identification code changed to "xs" (from "es").
+   AtSpi screen driver changes:
+      Static linking is supported.
+      A problem causing crashes on 64-bit platforms has been resolved.
+   Windows screen driver changes:
+      Significant reductions in memory usage.
+      Better handling of unreadable screens.
+      Better handling of the Alt+Tab window.
+   DOS changes:
+      Log records are written to the file "brltty.log".
+      The TSI braille driver stays at 9600 baud.
+   Key table changes:
+      Add the following directives:
+         assign, context, hide, hotkey, ifkey, map, note, superimpose, title.
+      Add the \{name} variable name expansion syntax.
+      Add the ! immediate key syntax.
+      A command may have more than one modifier.
+      The line command modifiers are supported.
+      Keyboard key table names are now prefixed with "kbd-".
+      The ktbtest tool has been added.
+   Linux changes:
+      USB device inspection is more efficient.
+      Fixes to keyboard connect/disconnect monitoring.
+      Fixes to key event handling.
+      PCM and MIDI support defaults to ALSA (instead of to OSS).
+   Windows changes:
+      Improved conversion of Windows errors to system errors (errno values).
+      Bluetooth support has been added.
+      Text table autoselection has been fixed.
+      Text table editing (ttbtest -e) is supported.
+   Build changes:
+      --enable-usb-support changed to --with-usb-package[=package,...].
+      --disable-usb-support changed to --without-usb-package.
+      --enable-bluetooth-support changed to --with-bluetooth-package[=package,...].
+      --disable-bluetooth-support changed to --without-bluetooth-package.
+   Text tables updated:
+      hu, sk.
+   Contraction tables updated:
+      de-kurzschrift, en-us-g2, zh-tw.
+October 8, 2009:
+ - BRLTTY 4.1 released:
+   General Changes:
+      BRL_CMD_WINUP can go above the top row of the screen.
+      Protect against NULL being given to snprintf() for a string.
+      Change the "braille display offline/online" logs to DEBUG (from NOTICE).
+      Fixes to status field initialization during a preferences file upgrade.
+      Default status field selection should take into account 66-cell displays.
+      Remove usbResetEndpoint().
+      Resolve gcc-4.4 warnings.
+      Resolve 64-bit warnings.
+      C compilers needn't deallocate variable-size arrays until function return.
+      Updates to the English Grade 2 contraction table.
+      Updates to the German contraction table.
+      Change ctbtest to expect input encoded in UTF-8.
+      liblouis tables use y for 5-digit and z for 8-digit characters.
+   Baum Braille Driver Changes:
+      Fix the support for display key #6 in PB2 mode.
+   EuroBraille Braille Driver Changes:
+      Re-add some bindings forgotten during the driver rewrite for 3.10.
+      Rework the bindings a bit for greater consistency and easier learning.
+      Add new bindings to support as many commands as possible.
+   Papenmeier Braille Driver Changes:
+      Interpret the EL66S Easy Access Bar correctly.
+   TSI Braille Driver Changes:
+      Increase the write delay a bit.
+   Linux Screen Driver Changes:
+      Give KD_FONT_OP_GET maximum (rather than reasonably high) font dimensions.
+   Java Bindings Changes:
+      Define the DOTn constants as bytes (rather than as chars).
+      JNI fixes for Sun-based JVMs.
+      Load the JNI part automatically at startup.
+   Lisp Bindings Changes:
+      Reference version 0.5.3 of BrlAPI.
+   Python Bindings Changes:
+      Pyrex doesn't automatically convert C's NULL to Python's None.
+      Translate BrlAPI exceptions into Python exceptions.
+   BrlAPI Changes:
+      Remove all mention of key sets.
+      Handle unbound key ranges.
+      Fix an issue with TCP/IP socket connection on Windows.
+      Handle the possibility of a different braille device after resuming.
+   Windows Changes:
+      Fix a compile problem.
+      The screen driver should refer to "consoles" rather than to "terminals".
+   Documentation Updates:
+      Update the French manual from 3.10 to 4.1.
+      Document that the EuroBraille Esys is supported.
+      Update the supported FreedomScientific model lists.
+      Clarify that --with-*-table sets the fallback (not the default) table.
+      Change log fixes.
+      Update the TSI braille driver's README.
+      Add README.Bluetooth.
+      The README for how to use a Seika with the TSI driver is no longer needed.
+      E-mail address change for Nicolas Pitre.
+      E-mail address change for Jean-Philippe Mengual.
+   Build Changes:
+      The API socket directory should be created under the install root.
+      Add the --disable-stripping configure option.
+      Remove the --disable-preferences-menu configure option.
+      Remove the --disable-table-selection configure option.
+      Fedora no longer creates "/usr/lib/ocaml/stublibs/dllbrlapi_stubs.*".
+
+May 15, 2009:
+ - BRLTTY 4.0 released:
+   General Changes:
+      All screen reading, character handling, and tables are now Unicode-based.
+      The "<n> startup problems" message has been removed.
+      Boot operands may contain multiple items separated by plus signs.
+      USB enhancements and fixes.
+      Windows serial I/O fixes.
+      A UDEV rules file for brltty is provided.
+      An upstart job for brltty is provided.
+   Status Cells:
+      Hard-coded styles have been replaced by user-configurable fields.
+      If there are no status cells then a text region may be reserved for them.
+      The INFO command toggles text maximization if a text region is being used.
+   Braille Drivers:
+      The Pegasus and Seika braille drivers have been added.
+      The BrailleSense braille driver has been renamed to HIMS.
+   Alva Braille Driver:
+      The BC6nn series are supported.
+   Baum Braille Driver:
+      The VarioConnect, EcoVario, VarioPro, and Refreshabraille are supported.
+   BrailleNote Braille Driver:
+      The statuscells= parameter has been removed.
+   EuroBraille Braille Driver:
+      USB support has been added.
+      Robustness has been improved.
+      Bindings for the Esys have been added.
+   FreedomScientific Braille Driver:
+      The statuscells= parameter has been removed.
+   HandyTech Braille Driver:
+      The Evolution models have been redefined to only have text cells.
+   HIMS Braille Driver:
+      The Braille Sense key bindings have been reworked.
+      SyncBraille support has been added.
+      Bluetooth support has been fixed.
+   TSI Braille Driver:
+      The way Seika devices implement PowerBraille emulation is supported.
+      The PowerBraille 81 has been redefined to only have text cells.
+      The core's flow control mechanism is used (rather than hard-coded delays).
+   Voyager Braille Driver:
+      The statuscells= parameter has been removed.
+      Key binding changes:
+         CUTAPPEND/CUTLINE moved to CRt# + B/C
+         PRINDENT/NXINDENT moved to CRt# + A+B/C+D
+   XWindow Braille Driver:
+      The font= parameter has been added.
+   SpeechDispatcher Speech Driver:
+      Single characters are now spoken correctly.
+   BrlAPI Server:
+      The version has been changed from 0.5.2 to 0.5.3.
+      enterTtyMode() now works correctly on Windows.
+      Problems when no braille display is connected at startup have been resolved.
+      The core's output is automatically restored when the last client disconnects.
+      Writes to the driver are no longer buffered.
+      Braille keyboard input is now converted to characters.
+      Output to the braille display is now drained the same way the core does.
+   Lisp Bindings:
+      Now compatible with CFFI 0.10.0.
+   Text Tables:
+      The license has been changed from GPL to LGPL.
+      File names have been changed from "text.*.tbl" to "*.ttb".
+      The file extension ".tti" is used for text subtables.
+      Tables must be encoded in UTF-8 (rather than in a local character set).
+      The "byte" keyword is no longer optional.
+      The "dot" directive is no longer supported.
+      The "char" and "include" directives have been added.
+      Language-based tables include common representations for box characters.
+      Tables for several languages have been added.
+      The default is now to select the table based on the locale's name.
+      The tbltest utility has been renamed to ttbtest.
+      The compress, fi2, identity, and simple tables have been removed.
+      The following tables have been renamed (mostly for ISO 639 compliance):
+         cz     -> cs
+         fi1    -> fi
+         nabcc  -> en-nabcc
+         se     -> sv
+         se-old -> sv-1989
+         visiob -> fr-vs
+         vni    -> vi
+   Attributes Tables:
+      The license has been changed from GPL to LGPL.
+      File names have been changed from "attr*.tbl" to "*.atb".
+      The file extension ".ati" is used for attributes subtables.
+      Tables must be encoded in UTF-8 (rather than in Latin1).
+      The "byte" directive is no longer supported.
+      The "include" directive has been added.
+   Contraction Tables:
+      The license has been changed from GPL to LGPL.
+      Tables for several languages have been added.
+      The following tables have been renamed:
+         en-uebc-g2 -> en-ueb-g2
+      The en-ueb-g2, en-us-g2, and zh-tw tables have been updated.
+   Key tables (for binding keyboard keys to brltty commands) have been added:
+      The file extension ".ktb" is used for key tables.
+      The file extension ".kti" is used for key subtables.
+      The desktop, keypad, and laptop key tables are provided.
+      The -k [--key-table=] option has been added.
+      The -K [--keyboard-properties=] option has been added.
+   Source Tree Changes:
+      The text tables have been moved from BrailleTables/ to Tables/.
+      The attributes tables have been moved from BrailleTables/ to Tables/.
+      The contraction tables have been moved from ContractionTables/ to Tables/.
+      The braille drivers have been moved from BrailleDrivers/ to Drivers/Braille/.
+      The speech drivers have been moved from SpeechDrivers/ to Drivers/Speech/.
+      The screen drivers have been moved from ScreenDrivers/ to Drivers/Screen/.
+      Source for the Windows installer is provided.
+
+July 16, 2008:
+ - BRLTTY 3.10 released:
+   The core and drivers (excluding text tables) are now Unicode-based.
+   The DESCCHAR function now shows (if possible) the character's name.
+   Linear cuts no longer include newline characters.
+   Specifying the braille device as "serial:" now defaults to the first serial device.
+   Add a driver for the Braille Sense.
+   Bring the French manual fully up-to-date (was at 3.6).
+   Linux-specific changes:
+      Don't unnecessarily wake up USB devices when searching.
+   Albatross braille driver changes:
+      Correctly handle keys which send their code on both press and release.
+   Baum braille driver changes:
+      Bind TL1+TR3 to the BACK funct5ion.
+   EuroBraille braille driver changes:
+      Rewritten.
+      Add support for the Iris PC keyboard.
+      Add key binding to do Shift+Tab directly (dots 235 + space).
+      New EsysIris key bindings:
+         VK_L34 -> FREEZE
+         VK_L67 -> HOME
+   FreedomScientific braille driver changes:
+      New PAC Mate key bindings:
+         routing key + left wheel press -> SETLEFT
+         routing key + right wheel press -> DESCCHAR
+   General speech support changes:
+      Add a preference for setting the pitch.
+      Add a preference for setting the punctuation level.
+   SpeechDispatcher speech driver changes:
+      Change the connection name to "main" (from "driver").
+      Single character entry/deletion now always speaks.
+      Try to reopen the connection if an error occurs.
+      Support setting pitch and punctuation level.
+   Theta speech driver changes:
+      It wouldn't compile with newer gccs.
+   Linux screen driver changes:
+      Now works on big endian architectures.
+      Add support for multi-byte character sets.
+      Add the Charset= parameter.
+      Add support for the right Alt key (AltGr).
+   PcBios screen driver changes:
+      Add AT/XT scancode support.
+   Windows screen driver changes:
+      Add the FollowFocus= parameter.
+      Add AT/XT scancode support.
+   Text table changes:
+      Rename no-h to no-generic.
+      Remove no-p.
+      Add no-oub [Offentlig Utvalg for Blindeskrift].
+   Contraction table changes:
+      Unicode characters can be specified by value [\uxxxx].
+      Unicode characters can be specified by name [\<name>].
+      When the cursor is:
+      *  On a word - it is uncontracted (not rendered in computer braille).
+      *  Between words - the space is expanded.
+      Rename the opcode "repeated" to "repeatable".
+      Fixes to the English Grade 2 [en-us-g2] table.
+      Fix the UEB [Unified English Braille] percent sign.
+      Fixes to the Chinese [zh-...] tables.
+      Rename zh-tw-big5 to zh-tw.
+      Remove zh-tw-gb2312.
+      Remove zh-tw-utf8.
+   BrlAPI changes:
+      Now at release 0.5.2.
+      Present a blinking cursor if that's what the user has configured.
+      Install/uninstall "/var/lib/BrlAPI/".
+   Caml bindings changes:
+      Install and uninstall now work properly.
+   Java bindings changes:
+      Package the classes into org.a11y.BrlAPI.
+      Rename the classes to remove the (now redundant) Brlapi prefix.
+   Python bindings changes:
+      Improved error handling facilities.
+   tbltest changes:
+      Added the -e [--edit] option for editing a text table.
+
+October 17, 2007:
+ - BRLTTY 3.9 released:
+   Core changes:
+      The startup log and initial braille display message contain the build revision.
+      The -v [--verify] option identifies the screen driver.
+      Failure of the braille and/or screen driver to start isn't fatal.
+      Repeated retries are scheduled for each driver until it starts.
+      Attempts to connect to gpm are only made when it's actually needed.
+      Standalone programs no longer include support for shared objects.
+      The MSDOS operating system is supported.
+      MSDOS option syntax (/flag and /name:value) is supported.
+      The syntax +x to clear a short option flag is supported.
+      Help output groups options by function rather than lists them alphabetically.
+      Needed but missing resources are created (by default) in /lib/brltty/rw/:
+         command line option: -W [--writable-directory=]
+         configuration file directive: writable-directory
+         environment variable: BRLTTY_WRITABLE_DIRECTORY
+         configure script option: --with-writable-directory=
+   BrlAPI changes:
+      Version updated to 0.5.1.
+      TCP sockets are more responsive.
+      The -h [--host] option of xbrlapi has been changed to -b [--brlapi].
+   Baum braille driver changes:
+      The autosuspend feature of Linux USB devices is disabled.
+      Don't probe for emulation modes if a serial device is being used.
+      Add the protocol= (all, native) parameter.
+   HandyTech braille driver changes:
+      The Easy Braille is supported.
+   Papenmeier braille driver changes:
+      The Trio models are supported.
+      The second set of vertical status keys does vertical routing.
+   Linux changes:
+      USB devices maintained by udev under /dev/bus/usb/ are supported.
+   Cygwin changes:
+      The emulation layer (rather than Windows functions) is used.
+      Serial devices named ttySn (rather than COMn) are used.
+   Windows changes:
+      Write to the event log.
+      The -I [--install-service] and -R [--remove-service] options have been added.
+      The -r [--release-device] option defaults to "on".
+   Table changes:
+      Corrections to Finnish braille [text.fi1.tbl, text.fi2.tbl].
+      Corrections to Portuguese braille [text.pt.tbl].
+      Corrections to American English grade 2 braille [en-us-g2.ctb].
+      Unified English Braille table rename: uebc-g2 -> en-uebc-g2
+      Chinese table renames: big5 -> zh-tw-big5, gb2312 -> zh-tw-gb2312
+      New Chinese tables: zh-tw-utf8, zh-tw-ucb
+
+June 4, 2007:
+ - BRLTTY 3.8 released:
+   General updates:
+      Add the -r [--release-device] command line option.
+      Add the release-device configuration directive.
+      Look in $HOME/.brltty/ for brltty.conf, translation tables, contraction tables, and preferences files.
+      Add screen scroll detection to the cursor routing algorithm.
+      Fix left-panning when presenting contracted braille.
+      Change the default braille and speech drivers to "auto".
+      Change the default braille device to "usb:".
+      Improved AT keyboard scancode support.
+      Added XT keyboard scancode support.
+      Verify that a driver's version matches that of the core when loading it.
+      If any drivers are built-in then only autodetect for them.
+      Log unexpected data from braille displays as warnings.
+      New braille drivers: IrixLinux
+      New speech drivers: SpeechDispatcher
+      New screen drivers: PCBIOS
+      New supported operating systems: kFreeBSD, MSDOS (almost)
+   Startup updates:
+      Only count command line option and configuration file errors as problems.
+      Periodically retry starting the speech driver.
+      Periodically retry creating the PID file.
+   BrlAPI updates:
+      Version changed to 0.5.0.
+      Base TCP/IP port changed from 35751 to the newly IANA-assigned 4101 [brlp-0] (4 ports).
+      Move headers from "/usr/include/brltty/*.h" to "/usr/include/brlapi_*.h".
+      Lots of function and constant renames (see man brlapi_deprecated).
+      Remove brlapi_getDriverId().
+      Add brlapi_suspendDriver() and brlapi_resumeDriver().
+      Redesign the paradigm for ignoring and accepting key codes.
+      Add support for multiple simultaneous sessions.
+      Add support for the application-definable BRLAPI_NO_DEPRECATED and BRLAPI_NO_SINGLE_SESSION macros.
+      Improved Unicode support.
+      Rename the WINDOWSPATH environment variable to WINDOWPATH.
+      Rename the BRLAPI_KEYFILE environment variable to BRLAPI_AUTH.
+      Rename the KeyFile= parameter to Auth=.
+      Add support for authentication by user and/or group.
+      Implement bindings for Caml, Java, Lisp, Python, and Tcl.
+   Preferences menu updates:
+      Change the default for "Autorepeat" from "No" to "Yes".
+      Add "Autorepeat Panning" [No, Yes].
+      Add "Braille Sensitivity" [Minimum, Low, Medium, High, Maximum].
+      Rename "Pointer Follows Window" to "Highlight Window".
+      The "Highlight Window" preference works even if gpm isn't available.
+      Only highlight the upper-left character of the window if attribute underlining is on.
+   Alva braille driver updates:
+      Add support for the Braille System 40.
+   Baum braille driver updates:
+      Some key binding changes.
+      Add keyboard input.
+      Add the VarioKeys parameter for those who prefer the old Vario driver bindings.
+   MiniBraille braille driver updates:
+      Add flow control so that it works on fast CPUs.
+      Some key binding changes.
+   HandyTech braille driver updates:
+      Support the Modular Evolution models (64 and 88).
+      Add one-hand convenience bindings for BrailleStar navigation functions.
+   Papenmeier braille driver updates:
+      Add support for the latest BrailleX II models (except for the Trio line).
+      Add support for setting braille firmness.
+   VisioBraille braille driver updates:
+      Support the non-standard serial flow control used by older models.
+      Distribute the vstp (VisioBraille file transfer) application.
+   Linux screen driver updates:
+      Add support for frame buffer consoles.
+      Fix the emulation of keys F1-F5.
+   Screen screen driver and screen patch updates:
+      Fixed cursor routing.
+      Add support for keyboard emulation.
+      Fix support for the status line.
+      Add support for the input line.
+      Show the current window number (rather than 0).
+      Show correct character highlighting.
+   Windows support updates:
+      Recognize the CR-LF sequence as a text file input line terminator.
+      Don't misinterpret/corrupt preferences files by treating them as text files.
+      Present the AltTabInfo window.
+      Support keyboard emulation when in a DOS box or a non-console application.
+      Determine which facilities are available at run-time rather than at build-time.
+      Remove support for Windows 95.
+   Text translation table updates:
+      Renames: uk->en_UK, us->en_US
+      Add a table for Swedish 1996.
+      Add a table for Unified French 2007.
+      Add a table for Portuguese.
+      Fix the Czech table to support German symbols in a uniform way.
+      Support tables in the Gnome Braille format.
+      Support tables in the sbl (SuSE Blinux) format.
+   Contraction table updates:
+      Several fixes to en-us-g2 (Grade 2 American English).
+      Updates to big5.
+      Add a table for GB2312 (Simplified Chinese Braille)
+   Build updates:
+      The autogen.sh script has been renamed to autogen.
+      Add support for a relocatable install (useful on Windows).
+      Change the default for gpm support to enabled.
+      Define general macros for braille applications in "/usr/include/iso_11548-1.h".
+      Add the --with-stderr-path configure script option.
+      Braillified installer images log errors to tty5.
+
+December 26, 2005:
+ - BRLTTY 3.7.2 released:
+   The AtSpi screen driver doesn't load due to a missing Xt dependency.
+   Check for X11/extensions/XKB.h.
+   Swedish text translation table changes:
+      Add text.se.tbl (1996 standard).
+      Rename text.se2.tbl to text.se-old.tbl (1989 standard).
+      Remove text.se1.tbl (modified english table).
+   English grade 2 contraction table changes:
+      The "m-st" contraction should always be used when "must" is the root word.
+
+December 12, 2005:
+ - BRLTTY 3.7.1 released:
+   (is|validate)(Integer|Float) should only modify *value when returning true.
+   usbWriteEndpoint() in usb_libusb.c called usbGetInputEndpoint().
+   KDGETMODE, KDGKBMETA and KDGKBMODE need an int (not a long) operand.
+   Add the BRL_BLK_(PR|NX)DIFCHAR functions.
+   Allow the end of the cut region to be corrected.
+   Fix a call to brlapi_perror() within xbrlapi.
+   Waiting for output to unblock wasn't being done correctly.
+   English grade 2 contraction table changes:
+      Don't use a letter sign when a letter is followed by a period.
+      Handle "by-and-by" as a special case.
+      Don't use a contraction if a capitalization state change within it needs clarification.
+      Use "ed" for "redolent".
+   Baum braille driver changes:
+      A Vario 40 can return device identity but not cell count.
+      Add a few more "words of wisdom" to README.Inka.
+      Binding changes:
+         Function  Old         New
+         SETMARK   TL1+TL2+CRn TL1+TL3+CRn
+         GOTOMARK  TL2+TL3+CRn TR1+TR3+CRn
+         PRINDENT  TR2+TR1+CRn TL2+TL1+CRn
+         NXINDENT  TR2+TR3+CRn TL2+TL3+CRn
+         PRDIFCHAR -           TR2+TR1+CRn
+         NXDIFCHAR -           TR2+TR3+CRn
+   API changes:
+      Correct the documentation for the BRLPACKET_WRITE packet.
+      Remove the prototype of brlapi_getDriverInfo() from api.h.
+      An API client should forget the authorization key as soon as it's been sent to the server.
+      Ensure that the Unix-domain socket is removed before binding to it.
+      Fix the behaviour of the "and" dot mask.
+   Build changes:
+      Check for -lXtst and -lXext.
+      Move the X dependencies from the executables to the components which need them.
+      Move the curses dependency from the executables to the TTY braille driver.
+      Some shells don't like the semicolon in "for i; do".
+
+September 17, 2005:
+ - BRLTTY 3.7 released:
+   Change the dot-to-bit mapping to comply with ISO/TR 11548-1.
+   New cursor routing algorithm (works with editors with wrapped lines).
+   Replace the -N [--no-speech] option with -N [--no-api].
+   Get the fully qualified path to the executable.
+   getWorkingDirectory() should use reallocWrapper().
+   hasTimedOut(0) should return true in order to handle a timeout of 0.
+   Create the rescaleInteger() function from in-line preferences menu code.
+   Define and implement BRL_BLK_GOTOLINE.
+   NULs are written to the end of the visual display if it's off the end of the screen.
+   Termination signals should just set a flag and let brltty terminate safely.
+   Change all "bluez" uses to "bluetooth".
+   Split the autorepeat code out into a separate function.
+   Autospeak sometimes decides that characters have been deleted when, in fact, they've been replaced.
+   Enable autorepeat and autospeak for the preferences menu.
+   Write debug logs for braille and speech setting changes.
+   The coordinates on the status line should start from 1 rather than from 0.
+   Generate lower status digits symbolically rather than mathematically.
+   Operate serial devices in true nonblocking mode.
+   Give better names to serialTestLines()'s arguments.
+   Text table renames: fr -> fr_CA, fr-iso01 -> fr_FR
+   Force OSS PCM output to be blocking.
+      There appears to be a bug in ALSA OSS simulation.
+      Output is nonblocking if snd_seq_oss is loaded with nonblock_open=1.
+      In this case, write() always returns the full count even though
+      large chunks of sound are missing.
+   Ensure that the PCM ALSA support uses a sensible sample rate.
+   By default, use the PCM ALSA device "default".
+   Fix ALSA MIDI event timing and output flushing.
+   Alva braille driver changes:
+      Add input/output packet logging.
+      Remove the autodetect loop.
+      Remove some hard-coded delays during open.
+      Ensure that all packet buffers are as large as the largest possible packet.
+      Wait during open to see if the status cells have been turned off.
+   BrailleLite braille driver changes:
+      Redefine the key-to-dots table in terms of braille dot constants.
+      Remove other hard-coded dot values.
+      Redo the layout of the bindings table to make it easier to edit.
+   BrailleNote braille driver changes:
+      Reindent the source to 2 (form 3).
+      Remove the probe loop from brl_open().
+      Add packet I/O.
+      Add output flow control.
+      Define a structure for input packets.
+   EuroBraille braille driver changes:
+      Use symbolic, rather than hard-coded, dot-to-bit mapping.
+   FreedomScientific braille driver changes:
+      Reduce the USB retry count from 5 to 3.
+      Remove the "no response from display" warning.
+      Fix a typo in the definition of the Focus 40 braille display.
+   HandyTech braille driver changes:
+      Reduce the USB retry count from 5 to 3.
+      Remove the "no response from display" warning.
+   LogText braille driver changes:
+      Redefine the output and input tables in terms of braille dot constants.
+      Don't prepare the output and input tables more than once.
+   MDV braille driver changes:
+      Remove the autodetect loop.
+   Papenmeier braille driver changes:
+      Reduce the USB retry count from 5 to 3.
+      Remove the "no response from display" warning.
+      Add automatic cursor routing to horizontal combined mode bindings.
+      read_config:
+         Include prologue.h.
+         Link with $(LDLIBS).
+   TTY braille driver changes:
+      Position the cursor correctly.
+      Build even if no curses package is available.
+   Virtual braille driver changes:
+      Use PF_LOCAL/AF_LOCAL instead of PF_UNIX/AF_UNIX.
+      Don't index the names array beyond its size.
+   VisioBraille braille driver changes:
+      Redefine the output table in terms of braille dot constants.
+   Voyager braille driver changes:
+      Reformat the definition of the dots table.
+   XWindow braille driver changes:
+      Reindent source.
+      Rename the xtparms parameter to tkparms.
+      Add the input= parameter.
+      Add a more advanced model (based on the VisioBraille mapping).
+      Xm isn't initernationalized.
+      Add cursor routing and displaying of dots.
+      Show the cursor position within the visual display.
+      Right-clicking on the window pops up a menu in which one can
+      change the size and model of the emulated braille device.
+      Keep the window on top.
+      Add a setup menu.
+      Add support for command autorepeating.
+      Include X11/Intrinsic.h in more places to keep Solaris happy.
+   ExternalSpeech speech driver changes:
+      Allow names, rather than just numbers, to be passed to the uid/gid parameters.
+   Swift speech driver changes:
+      Add debug logs for the setting of parameters.
+   API changes:
+      Change BrlAPI's release to 0.4.0.
+      Add void to argumentless routine prototypes where missing.
+      Move brlapi_fd_mutex from api.h to api_protocol.h.
+      Move BRL_KEYBUF_SIZE from api.h to api_client.c.
+      Define the BRLAPI_MAXKEYSETSIZE constant and use it in api_client.c.
+      brlapi_readFile() and brlapi_writeFile() are only used within api_common.c.
+      Clean up the API TTY list in a safe way.
+      The API should automatically clean up its TTY tree.
+      Remove the ancestor loop from brlapi_getControllingTty().
+      Require clients to be more precise when claiming a tty or entering raw mode:
+         getRaw() has an argument to specify the driver one wants to exchange packets with.
+         The integer argument of getTty() used to specify either BRL_KEYCODES or BRL_COMMANCS
+         has been replaced by a string argument.
+         If either NULL or "" then keypresses are delivered as BRLTTY commands.
+         If a driver name then the server verifies that that driver is being used 
+         and that it actually supports raw key codes.
+         brlapi_getRaw() should return an error rather than an exception
+         when the required driver is not the one used by BRLTTY.
+      Define BRLCOMMANDS (in api.h) in a backward compatible way.
+      Verify the result of gethostbyname().
+      Add conditional code for PF_LOCAL.
+      Use sockaddr_storage rather than sockaddr.
+      Split brlapi_readPacket() into brlapi_readPacketHeader() and brlapi_readPacketContent().
+      Split processRequest() into several smaller functions.
+      Check cursor value more carefully in brlapi_writeText().
+      Only real commands (neither NOOP nor EOF) should be sent to clients.
+      The server side of the API should do non-blocked reads.
+      Report packet read errors via errno rather than via brlapi_libcerrno.
+      Rename errorHandler to exceptionHandler.
+      Add the brlapi_strexception() function.
+      More clearly distinguish between errors and exceptions.
+      Problems caused by getTty and (un)ignoreKeySet should be reported as errors rather than as exceptions.
+      brlapi_packetType() should return a convenient string for exception packets.
+      Clean up error handling by putting all necessary information for printing errors into a struct.
+      Add the infrastructure for handling display size changes.
+      Handle client's charset and convert as needed in the server.
+      Recognize -1 (rather than 0) as the "no screen" vt.
+      Several (rare) race condition fixes.
+      Add support for the Unicode braille row.
+      If supported, reduce the thread stack size to 64K (from 8M).
+      Add the API parameter stacksize=.
+      Remove the TOORECURSE error code.
+      Replace both RAWNOTSUPP and KEYSNOTSUMP errors by OPNOTSUPP.
+      Return OPNOTSUPP (rather than INVALID_PARAMETER) when a charset is specified and iconv is absent.
+      Simplify the returning of errors.
+      Fix tty table extension.
+      brlapi_write() should use the specified length rather than call strlen since the data could contain NUL bytes.
+      Add the brlapi_getTtyPath() function.
+      Use recursive locking so that brl_readCommand() can call message().
+      Change the prototypes for brlapi_(send|recv)Raw() to use "void *" instead of "unsigned char *".
+      Allow the KeyFile= parameter to accept the special string "none".
+      Reject empty key files.
+      Accept keys larger than the allowed key size.
+      By default, the client should also check for the IP socket.
+      Check for an already-running BrlAPI server.
+      Provide public access to brlapi_splitHost().
+      Dot masks should be unsigned.
+      Remove the trailing newline from the "BrlAPI resize" log.
+      "code" has already been converted to host byte order by the caller.
+      Add some synchronisation cleanups.
+      Allow users to call brlapi_write(NULL).
+      api.h should internally include what it depends on.
+      Preprocess the autorepeat flags.
+      Let the clients see BRL_CMD_NOOP.
+   apitest changes:
+      Add the -d [--dots] option.
+      Exit show dots mode when a braille display key (rather than a keyboard key) is pressed.
+      Learn Mode:
+         Announce entry.
+         Write command descriptions to standard error.
+         Eliminate bogus error message when leaving.
+   xbrlapi changes:
+      Add key event support.
+      No longer use the deprecated BRLCOMMANDS constant.
+      Close the connection via a signal-safe method.
+   tbltest changes:
+      Add support for writing tables.
+      Add support for .a2b (Gnopernicus) braille tables.
+      Interpret - as standard input.
+   en-us-g2 contraction table fixes:
+      [had]rian, hid[ea]way, r[er]egister, un[con]genial.
+      Don't remove space after "a" if before "and", "for", "of", "the", or "with".
+      Don't contract the word "in" after a dash.
+   USB changes:
+      Remove the assumption that a USB device maps to a file system object.
+      A device should point to the proper descriptor for the current interface alternative.
+      Use a common algorithm for iterating through the configuration descriptor block.
+      Only reread the configuration descriptor block if the first buffer is too small.
+      Publicize the functions for getting the configuration descriptor and its components.
+      When setting the configuration, deallocate the cached copy of the old descriptor.
+      Add a function for reading the device descriptor from the device itself.
+      Automatically close an open interface when changing the configuration.
+      Add an endpoint parameter to usbReapResponse().
+      Improve the timeout used for in-line endpoint reading when awaiting input.
+      Calculate timeouts more accurately.
+      Make device closing more robust.
+      Check for a serial adapter even if configuration data for it isn't supplied.
+      Fix the bulk input loop.
+      Asynchronous errors should always be passed back to the caller.
+      Code restructuring:
+         Reorder the platform-dependent function definitions.
+         Group the global internal function definitions by where they need to be implemented.
+         Rename the constants to avoid conflicts with libusb.
+      Linux changes:
+         Allow reaping the URB for a specific endpoint.
+         Reap a URB after discarding it.
+         Add support for inline reading of an interrupt endpoint.
+         Add support for inline writing to an interrupt endpoint.
+      BSD changes:
+         Add more robustness to deallocating device and endpoint extensions.
+      Darwin changes:
+         Add more robustness to deallocating an endpoint extension.
+      Add support for libusb.
+   General code changes:
+      Update the copyright prologues from 2004 to 2005.
+      Change "Linux console" to "console screen" in the copyright prologues.
+      Change the nested inclusion protection convention from _NAME_H to BRLTTY_INCLUDED_NAME.
+      <sys/types.h> needs to be included before almost any other header  on some BSD systems.
+      gcc-3.3 (at least on cygwin) doesn't like apostrophes in #error directives.
+      gcc-4 does a lot more checking for matching signedness.
+      gcc-4 doesn't allow a variable to be declared global and then static.
+      Remove the casts from the results of malloc() calls.
+      Clean up the passing of TranslationTable and DotsTable parameters.
+      Logs to standard error should be prefixed with the program's name.
+      Use LogPrint() rather than fprintf(stderr) for option processing errors.
+      Rename ctb_definitions.h to ctb_internal.h.
+      Remove dummy timezone parameters to gettimeofday().
+      Rename infmode to infoMode.
+      Remove tests for and calls to endhostent() and endservent().
+      Ensure that all I/O headers are prefixed with "io_" (primarily because of <usb.h>.
+   General driver changes:
+      Define opt_pcmDevice as external for those speech drivers which need it.
+      Change the speech rate,volume handlers to take a floating-point multiplier.
+      Prefix all driver identification #defines with the driver code.
+      Uniformly refer to driver codes as "codes" rather than as "identifiers".
+      Add the driver comment string to the driver structure.
+      Split the braille and speech drivers into separate subdirectories.
+      Don't create the /lib/brltty/brltty-???.lst files.
+      Make the screen drivers dyamically loadable.
+      Add skeleton braille,speech,screen driver source.
+   Screen driver changes:
+      Define screen driver parameters in a more standard way.
+      Allow screen driver codes to be used as parameter qualifiers.
+      Make --without-screen-driver work.
+      Add the screen-driver configuration directive and -x command line option to BRLTTY.
+      Remove hard-coded limitations and assumptions from the screen state table.
+      Don't keep two copies of the current screen number.
+      All screen drivers should determine their vt# in only one place.
+      Clean up the code which switches between the live,frozen,help screens.
+      The read() method should return a boolean rather than the buffer.
+      Add the uservt() method to the MainScreen object.
+      Add the uservt() method to the Screen screen driver.
+      Add the currentvt() method to the FrozenScreen object.
+      Correctly detect when the Linux console switches between text and graphics mode.
+   Solaris changes:
+      Define AF_LOCAL to be AF_INET.
+      Define _XOPEN_SOURCE=500 and __EXTENSIONS__.
+      Add native USB support.
+      Remove the use of "[from ... to] =" initializers.
+      Remove the use of type-casted tuples.
+      Remove the use of __label__ declarations.
+      "#pragma weak", rather than "__attributes__((weak))", is needed on Solaris.
+   Build changes:
+      gendeps changes:
+         Remove the need for TclX.
+         Search for tclsh rather than hard-code its path.
+      Rename the "aux" subdirectory to "acdir" (for Cygwin).
+      Don't build spktest automatically.
+      Build/install/uninstall xbrlapi.
+      Pass the list of needed libraries to the linking of the API server library.
+      Link the API client library with only those dependencies which it actually needs.
+      Add build-time detection for: pwd.h, grp.h, regex.h, gai_strerror(), hstrerror().
+      Add compile-time detection for: ETIMEDOUT, EAFNOSUPORT.
+      Explicitly test for libX11, libXt, and the xt toolkit library.
+      Separate the concepts of dynamic library and loadable module.
+      Use <NAME> rather than <SONAME> in the MKLIB macro.
+      Use libusb, if available, if we don't provie native USB support.
+      Upgrade the BrlAPIref doxygen configuration file to 1.3.9.1.
+      Clean up the top-level <driver-type>.mk files.
+      Correct the documentation regarding default installed package locations.
+      Rename the brltty_setup_files macro to brltty_make_files.
+      Rename setup.mk.in to prologue.mk.in.
+      "make clean/distclean" should process the directories for internal drivers.
+      "make all" in the "Documents/" subdirectory should make the man3 files.
+      Add the make file targets tar,gz,bz2,rpm.
+      Fix the hard-coded dependencies which allow the VideoBraille braille driver to be built-in.
+      Split the non-config parts of config.h into prologue.h and include the former from the latter.
+      Remove the leading ./ from config.h in make file dependency lists.
+      Some compilers don't like trailing slashes on directory paths.
+      Use the shell variable $RANDOM as a last resort when generating the API key.
+   rhmkiso chagnes:
+      Build in the Linux screen driver, and don't build any other screen drivers.
+      Add support for making a braillified Fedora Core 4 installer image.
+   RPM changes:
+      The spec file referenced an old substitution for the API version.
+      Don't include the TTY braille driver.
+   Gnopernicus changes:
+      ttybrl.c:
+         Improve the BRLTTY command to Gnopernicus key mappings.
+         Use Gnopernicus layer-key codes rather than Baum key codes.
+         Move all key code generation into a common routine.
+         Rename key to blk and keypress to key.
+         Ignore BRL_CMD_NOOP.
+         Handle the autorepeat flags.
+         Check for errors in the command read loop.
+         ignore_input() is no longer needed because flags aren't taken into account when ignoring commands.
+         Adapt to BrlAPI 0.4.0.
+      README.Gnopernicus:
+         Add a note about using BRLTTY's Patches/ttybrl.c.
+         Document the latest BRLTTY command to Gnopernicus key mappings.
+         Document how to get the routing keys to work.
+   Initial support for the Hurd platform.
+   Initial support for the Mac OS X (Darwin) platform.
+   Initial support for the Cygwin platform.
+   Initial support for the MinGW platform.
+   Remove the Vario braille driver.
+   Remove the VarioHT braille driver.
+   Add the Baum braille driver.
+   Add the Braudi braille driver.
+   Add the BrlAPI braille driver.
+   Add the Libbraille braille driver.
+   Add the TechniBraille braille driver.
+   Add the AtSpi screen driver.
+   Add the Hurd screen driver.
+   Add the Windows screen driver.
+   Rename the shm screen driver to Screen.
+   Add al,bm,bn,ht,md to the braille driver serial autodetect list.
+   Add bm to the braille driver USB autodetect list.
+   Add bm to the braille driver Bluetooth autodetect list.
+
+May 5, 2005:
+ - BRLTTY 3.6.2 released:
+   French braille uses dots 348 for the inverted question mark.
+   Don't build the TTY and XWindow drivers when braillifying a boot image.
+   If mcookie isn't available then use /dev/urandom or /dev/random to generate the API key.
+   The shared memory screen driver should primarily use a per user segment.
+   Update the screen patch to generate a per-user shared memory key.
+   Some platforms don't define PF_LOCAL.
+   BRL_CMD_LNEND is broken when the display sticks out to the right of the screen.
+   Updated big5 contraction table.
+   The feature list now states that French contracted braille is provided.
+   Add the Swift speech synthesizer driver.
+   BrailleLite braille driver changes:
+      Clarify the help for advance bar combinations and whiz wheels.
+   EuroBraille braille driver changes:
+      Corrected several bugs in write/read operations.
+      Corrected several key bugs on Iris models.
+   FreedomScientific braille driver changes:
+      Add support for the Focus 2 series (40 and 80).
+      Add support for the PacMate 80.
+   Papenmeier braille driver changes:
+      Add support for the EL+ 80.
+      Add support for the second row of routing keys.
+      Give the four protocol II models we support their proper names.
+   VisioBraille braille driver changes:
+      Return NOOP rather than EOF when CTRL or ALT is pressed.
+      Bind a key to BRL_CMD_LEARN.
+      Don't log timeouts when the display isn't connected.
+   Mikropuhe speech driver changes:
+      Lock the mutex while calling pthread_cond_signal().
+      Check for already enqueued speech before the first wait.
+      The mute method can hang brltty.
+   API changes:
+      The flags and protocol version fields weren't being maintained in network byte order.
+      brlapi_writeFile() and brlapi_readFile() are only for use within api_common.c.
+   Linux screen driver changes:
+      If KD_FONT_OP_GET fails or returns a character count of 0
+      then calculate the VGA character count from the screen font map.
+      Only reevaluate the VGA character count when necessary.
+   Queue facility changes:
+      Don't deallocate discarded elements when a queue is being deallocated.
+      Initialize queue data to NULL.
+      Deallocate an item beore discarding its element.
+
+December 22, 2004:
+ - BRLTTY 3.6.1 released:
+   Remove the %pre and %post sections from the rpm for the time being.
+   Remove use of PATH_MAX (the HURD doesn't have it).
+   getWorkingDirectory() can gobble memory on failures.
+   If autogen.sh  is given arguments then run configure with them.
+   Add the -D [data-directory=] option to tbltest and ctbtest.
+   tbltest should report unused dot combinations.
+   Updates to the fr-iso01 text translation table.
+   Freedom Scientific driver changes:
+      Assume that the serial cable is disconnected if no ACK after 2.5 seconds.
+   TSI driver changes:
+      Fix memory corruption if the display is off or disconnected for an extended period of time.
+   API changes:
+      In one place select() is called with the wrong mask size.
+      Display the packet correctly in the default error handler.
+      Log the reason that the authentication key can't be loaded.
+      Check that the writeVisual() handler exists before calling it.
+      Rename mutex variables to suit core conventions.
+      Introduce new error code BRLERR_DRIVERERROR for braille driver problems.
+   apitest changes:
+      Report key read errors.
+      Report the file descriptor of the connection.
+   rhmkiso changes:
+      Automatically increase the size of the initrd as needed.
+      Remove needless manipulations of set -e.
+      Old versions of tar don't preserve bin as a symbolic link.
+   Curses package support changes:
+      Use ncursesw if available.
+      Ensure that the curses run-time library can be found.
+      Only use plain old curses as a last resort.
+   OpenBSD operating system support changes:
+      All special serial control characters must be properly disabled.
+      host_sys.o doesn't make.
+
+October 11, 2004:
+ - BRLTTY 3.6 released:
+   Add support for old VisioBraille input flow control (PROM version < 4).
+   Add rate setting to the ExternalSpeech driver.
+   Remove type-punning warnings from the Mikropuhe driver.
+   Add support for multi-byte character sets to the TTY driver.
+   Remove the need for -t identity from the TTY and XWindow drivers.
+   Correctly determine the optionality of the configuration file.
+   Always change the menu setting even if a routing key doesn't evaluate to a new one.
+   PCM ALSA fixes.
+   Add xbrlapi (not installed yet).
+   Update our copy of Gnopernicus's ttybrl.c.
+   Alva workaround for bug exhibited by at least one ABT340:
+      A bogus routing key press event is generated for routing keys 25-29
+      when at least two front keys are already pressed.
+   Papenmeier driver updates:
+      The "query switches" command should show "both" rather than "?".
+      Add bindings for bar motion with the right front key pressed.
+      Resynchronize the switches and keys whenever the bar is moved.
+   API updates:
+      Add proper support for brl_writeVisual().
+      Correct a bug which occurs when the display is only partially updated.
+      Add a missing brlapi_libcerrno assignment to the client code.
+      Add support for Unix domain sockets.
+   Build updates:
+      Make clean shouldn't remove the text copy of the BrailleLite help files.
+      The text copy of the Voyager help files should be created in the source tree.
+   Documentation updates:
+      Bring the documentation up-to-date.
+      Merge the documentation of reciprocal commands, e.g. LNUP/LNDN.
+      Fix e-mail addresses and some examples in BrlAPI's manual.
+      Remove old screen patches.
+   Code updates:
+      Move a few routines to more logical places.
+      Share the common code for moving up/down to a line with different content.
+      Reindent the rest of config.c and main.c according to conventions.
+      Use our own delay mechanism rather than sleep().
+      Redo splitString() to allow for the easy adding of options.
+      Split command line option processing into a separate routine.
+   Fixes to the handling of counter (extendable flag) options:
+      Warn if the same option is specified more than once in the configuration file.
+      Allow a non-negative integer for non-command-line specifications.
+
+September 4, 2004:
+ - BRLTTY 3.6pre3 released:
+   Restarting drivers caused strange corruptions.
+   Eliminate some compile warnings when many features are disabled.
+   Make the entire options mechanism be wholly table driven.
+   Specifying an invalid option yields a bogus excess parameter error.
+   Automatically generate long options to set flags to 0.
+   Abbreviation tests via strncasecmp() don't require an extra length check.
+   findSharedSymbol() needs to accept the address of any kind of pointer.
+   Go back to OSS being the default on Linux even if ALSA is available.
+   Remove the link dependency on libasound from the PCM/MIDI ALSA code.
+   Fix an Alva driver buffer indexing overflow.
+   Prevent the ClioNoteBraille (EuroBraille driver) from reidentifying after each frame is sent.
+   Add a HOWTO for reading Chinese big5.
+   More Nemeth Code changes to the big5 contraction table.
+   Move the preferences files from the data directory to the configuration directory to prevent loss.
+   Implicitly disable autospeak during speech tracking.
+   Protect serial port draining from system call interruptions.
+   Initial changes to support QNX.
+
+August 21, 2004:
+ - BRLTTY 3.6pre2 released:
+   Default PCM and/or MIDI support on Linux to ALSA if that seems to make sense.
+   Add the -p [--pcm-device=] and the -m [--midi-device=] options.
+   splitString() can now optionally return the count.
+   Move the serial parity/flow constants to a common place (iodefs.h).
+   Move low-level I/O routines from misc.[ch] to iomisc.[ch].
+   Convert serial devices from file descriptors (ints) to an opaque type.
+   drainBrailleOutput() should allow for the driver to have rounded down.
+   Wait for all output to the braille display to be drained before closing the driver.
+   Replace the Display= and Term= parameters of the XWindow driver with XtParms=.
+   EuroBraille driver fix for entering/leaving PC mode.
+   HandyTech driver changes:
+      Simplify the bluetooth read routine.
+      Add the InputMode= parameter.
+   General driver changes:
+      Clean up include lsits.
+      Give standardized names to external symbols.
+   Autodetection changes:
+      Change the short form of the --version option to -V.
+      Change the -v option to --verify.
+      -b and -s both accept a comma-delimited list of drivers.
+      -s accepts "auto".
+      Only autodetect if more than one driver or device is specified.
+      Restarting a braille/speech driver re-autodetects.
+      Don't enable flow control until after successfully detecting the display.
+      Improve the order of the logs.
+      Remove the "driver giving up" warnings.
+   MIDI ALSA changes:
+      Ensure that "Midi" occurs in every routine name.
+      Simplify duration scheduling.
+      Support pauses between notes.
+   API changes:
+      Move the multiple settings of the root tty into setCurrentRootTty().
+      Fix problems which occur when the null screen driver is being used.
+
+August 12, 2004:
+ - BRLTTY 3.6pre1 released:
+   Clarify the copyright statement in the top-level README.
+   Reorganize the startup logs.
+   Add support for braille display autodetection (-b auto).
+   Allow more than one braille device to be specified.
+   Access the driver directly if the API fails to start.
+   Convert PCM and MIDI devices from file descriptors into opaque types.
+   Add ALSa support for PCM and MIDI devices.
+   Introduce common serial device access routines.
+   A va_list (stdarg) must be reset before being reused on some platforms.
+   Use the type ScreenKey rather than unsigned short for screen key insertion.
+   Fix the handling of control characters when using scan codes.
+   Move the handling of meta character input entirely into the screen driver.
+   Implement Linux-specific AT2 support.
+   Failure to load the specified text table results in an uninitialized reverse text table.
+   Add an initial timeout parameter to readChunk().
+   Implement changeOpenFlags(), setOpenFlags(), and setBlockingIo().
+   Convert learn mode to use a better timeout mechanism.
+   Split describeCommand() out of learnMode().
+   The vertical window shift should be the number of lines on the display if greater than 1.
+   Add the TTY braille driver.
+   Add the XWindow braille driver.
+   Add ASCII characters 0X60,0X7B-0X7F to text.brf.tbl.
+   Support \s (space) for translation tables.
+   Update the Spanish text table to the O.N.C.E. standard.
+   Updated big5 contraction table.
+   Support FreeBSD installations which don't have speaker support.
+   Add support for NetBSD.
+   Support compilation on OSF.
+   Add compile-time warnings for features which aren't available.
+   Save our latest copy of Gnopernicus's brltty driver (ttybrl.c).
+   API changes:
+      Update version to 0.3.0.
+      Increment protocol number.
+      Change the license for all client-side files to the LGPL.
+      Prefix definitions in brldefs.h with BRL_.
+      Remove from brldefs.h what shouldn't be in there.
+      Split protocol-specific definitions from api.h out into api_protocol.h.
+      Added two packet types for (un)ignoring key sets.
+      Renamed brlapi_(un)ignoreKeys to brlapi_(un)ignoreKeyRange.
+      Added the brlapi_(un)ifnoreKeySet functions.
+      For (un)ignore, only consider the blk and arg fields of a command.
+      Protocol enhancements (some packets removed, certain operations faster).
+      Error management is now partly asynchronous.
+      Server partially rewritten to use select() system call.
+      Now only two threads: one for brltty, one for incoming connections.
+      Default client error handler with capability to define another one.
+      Some functions renamed.
+      apitest added.
+      Don't look at $WINDOW to determine the tty (yet).
+      Add support for drivers with brl_writeVisual().
+      Implement recursive windows.
+      Deprecate brlapi_readCommand() and replace it with brlapi_readKey().
+      Replace uses of uint32_t with unsigned int where possible.
+      Rename BRLAPI_EXTWRITESTRUCT_INITIALIZER to BRLAPI_WRITESTRUCT_INITIALIZER.
+   USB fixes:
+      Allow usbBeginInput() to be called more than once on the same endpoint.
+      Set the configuration before claiming the interface.
+      Configuration descriptors must be read by number rather than by value.
+      usbCancelRequest() shouldn't deallocate a completed request.
+      BSD systems only allow short transfer to be set for input endpoints.
+      OpenBSD endpoint read timeouts should be silent and non-fatal.
+   HandyTech driver changes:
+      Remove the no longer needed (read|write)Bytes() routines.
+      Turn brl_(read|write)Packet() into standalone routines for later enhancement.
+      Add bluetooth support.
+      Add support for keyboard input.
+   Papenmeier driver changes:
+      Change some BrailleX 2D Screen key bindings.
+      Implement simulated EL switches for those models which don't have them.
+      Simplify the EL command generation macros.
+      Reduce the volume of output logging.
+      Log the baud at which the driver is probing.
+      Make protocol I input packet logging more standard.
+      Fix the count for copying generic status cell data from the core-supplied buffer.
+      Remove unused definitions from the header.
+      Move protocol I definitions from the header into the protocol I section of the implementation.
+      Complete the implementation of protocol II.
+      Support the EL 40/66/80 Slimline models.
+      Remove out-of-date debugging utilities.
+   TSI driver changes:
+      Remove CTS checking.
+      Remove the wait-for-connect loop.
+   VisioBraille driver changes:
+      Prefix public constants with BRL_VS.
+      Reindent the source to core conventions.
+      Don't remember display content until successfully written.
+   Voyager driver changes:
+      Fix an upper bound error in brl_writeWindow().
+      Remove the write retry loops.
+      Simplify the control request retry algorithm.
+      Act on first key release rather than after all keys are released.
+      Add support for the part232 serial interface.
+      Remove knowledge of USB string encoding.
+      Rename the BrlInput= parameter to InputMode=.
+      Change the default for InputMode= to no.
+      Change InputMode=no to mean don't require chording.
+      Add the StatusCells= parameter.
+      Simplify the help generation macros.
+      Reorganize the help template.
+   Build changes:
+      Rename --disable-usb to --disable-usb-support.
+      Add --disable-bluetooth-support.
+      Rename --disable-...-tunes to --disable-...-support.
+      Add --with-curses=.
+      Add --with-xt-toolkit=.
+      Reorder the arguments to BRLTTY_ARG_ENABLE/DISABLE.
+      Detect if Linux-specific AT2 support can be used.
+      Check for iconv.h.
+      Check for <sys/select.h>.
+      Detect if __attribute__((format(printf))) is supported.
+      Detect if __attribute__((packed)) is supported.
+      Detect if __attribute__((unused)) is supported.
+      Detect if the __alignof__ operator is supported.
+      Abort the configure if ld flags aren't known.
+      Completely rewrite install-sh to support more than one source operand.
+      Hard-code "ar" in the make file since HP-UX doesn't set "$(AR)".
+      The FestivalLite driver needs -lm.
+      Don't build the API or the Mikropuhe driver if pthreads isn't available.
+
+June 12, 2004:
+ - BRLTTY 3.5 released:
+   Don't include the Mikropuhe speech driver in the RPM. (dm)
+   Fix up the lists of supported Papenmeier models. (dm)
+   Update brltty's LSM (Linux Software Map) for 3.5. (dm)
+   Documentation updates: (dm)
+      Move the last section ("Epilogue") to the beginning (as "Formalities").
+      Letter (rather than number) the appendices.
+      Convert some descriptive lists into tables.
+      Add some more alert tune events.
+      Describe the Braille Firmness feature.
+      Remove the mention of USB from the Voyager driver.
+      Routing keys now function as a scroll bar for numeric preferences.
+   Document the speech FIFO: (dm)
+      -F (--speech-fifo=) command line option
+      speech-fifo configuration file directive
+      BRLTTY_SPEECH_FIFO environment variable
+   Document the fact that identification by USB serial number may not work. (dm)
+   Restore the default status cell style for the HandyTech driver to "Alva". (dm)
+   Bring the change log up-to-date (3.4.1 to 3.5). (dm)
+   Only refer to libpthread if necessary. (dm)
+
+June 8, 2004:
+ - BRLTTY 3.5pre4 released:
+   Increase the Voyager driver's STALL_TRIES setting from 3 to 7. (sd)
+   Split usbControlTransfer() into usbControlRead() and usbControlWrite(). (dm)
+   Add retry logic to the Voyager driver's call to usbBeginInput(). (dm)
+   The USB serial adapter handlers should return a boolean success value. (ml,dm)
+   gcc 3.3 reports "-lgcc_s not found" when linking statically. (dm)
+   Remove the BrailleLite driver's detection loop since the core now loops. (dm)
+   Disabling speech support breaks the compile. (ml)
+   Log the USB language. (dm)
+   Move the USB device open algorithm into the core. (dm)
+   Introcuce USB_IS_PRODUCT(). (ml,dm)
+   Remove unreachable code. (ml)
+   Repack the Freedom Scientific model entry structure. (ml)
+   Improve the way the null braille and speech drivers work. (dm)
+   Fix a pointer error. (st).
+   Most of the previous/next setting code is common. (dm)
+   Fix the comments for and adjust the names of the fields of the MenuItem structure. (dm)
+   Change the routing keys in the preferences menu from a set of descrete values to a scroll bar. (dm,np)
+   Fix a buffer overflow problem in updatePreferences(). (np,dm)
+   Building the BrlAPI reference manual fails when not building in the source tree. (ml)
+   Add the firmness handler to the Alva driver for the Satellite displays. (dm)
+   Prepare for reading USB input on systems which don't support URBs. (dm)
+   Get rid of the illegal type-punning warning. (dm)
+   Prepare the Papenmeier driver for protocol-specific handlers. (dm)
+   Support the HandyTech FTDI USB<->serial adapter. (ml)
+   Add a USB input packet filtering mechanism and use it for FTDI USB<->serial adapters. (dm)
+   Add the base for Papenmeier USB support. (ml,dm)
+   Parse contraction table input with unsignee chars. (dm)
+   Parse translation tables with unsigned chars. (dm)
+   Fix some problems with Solaris and HP-UX builds. (dm)
+   --with-screen-driver=no needs route.o. (dm)
+   Solaris and OpenBSD share the same PCM code. (dm)
+   Include sys_..._none.h files where appropriate. (dm)
+   Solaris requires -lsocket. (dm)
+   Linux, Solaris, OpenBSD, and FreeBSD all use <dlfcn.h>. (dm)
+   Linux and FreeBSD use the same PCM code. (dm)
+   Make BRLTTY buildable on FreeBSD. (dm)
+   Rename usbBulk(Read|Write)() to usb(Read|Write)Endpoint(). (dm)
+   Add USB support for OpenBSD (incomplete). (dm)
+   The Mikropuhe driver's deallocateSpeechItem() routine needs the new data argument. (dm)
+   EuroBraille update: (yp)
+      More BrlAPI compliant.
+      Update logged version number.
+   Include <unistd.h> within each header file which uses size_t. (sd)
+   Use message() to report TSI low battery condition. (sd)
+   Use a queue to link the pending USB input requests. (dm)
+   Some more tidying up of the USB declarations. (dm)
+   HandyTech display identification via USB was unreliable on a fast machine. (dm)
+   Maintain endpoint-specific data. (dm)
+   The preferences menu crashes if the contraction tables directory is bad. (dm)
+   The "er" in "surgery" doesn't get contracted (en-us-g2). (jb)
+   brl_readPacket() and brl_writePacket() now use the size_t and ssize_t types. (st)
+   Correct the BrlAPI client run-time discovery of libpthread use. (st)
+   BrlAPI fixes: (ml)
+      Remove duplicate declarations already in pthread.h.
+      strndup() is a glibc specific GNU extension which isn't available on some platforms, e.g. OpenBSD.
+      "int vt" was declared outside the Linux specific block, causing a compiler warning about an unused variable.
+   brltty-install and brldefs.h didn't install correctly when not building in the source tree. (ml)
+   The Voyager driver should no longer be Linux-dependent. (ml)
+   Occasionally the rest of the line, rather than just the current word, becomes uncontracted. (dm)
+   Rearrange the public USB constants a bit. (dm)
+   Add the FTDI chip used by HandyTech. (ml)
+   Remove the reference to -lm. (dm)
+   Remove inclusion of <math.h>. (dm)
+   Change all doubles to floats. (np,dm)
+   Generate triangle (rather than sine) waves when playing tunes via the pcm. (np)
+   Implement the "Braille Firmness" preference. (dm)
+   Add "Braille Firmness" capability to the Voyager and FreedomScientific drivers. (dm)
+   Move usbBulk(Read|Write) out of the portable part of the USB core. (dm)
+   OpenBSD needs misc.c to include <sys/stat.yh>. (ml)
+   Ensure that all declarations are at the beginnings of their blocks. (ml)(
+   Remove double semicolons. (dm)
+   Add a "routing started" tune, change the "routing succeeded" tune. (sd)
+   Don't write the initial "application character map changed" log. (dm)
+
+April 13, 2004:
+ - BRLTTY 3.5pre3 released:
+   Rename the key/cell offset/address variables in the Papenmeier driver. (dm)
+   Remove adlib debug logs. (dm)
+   Updates to the Papenmeier initialization debug logs. (dm)
+   Voyager driver changes: (dm)
+      Remove failure on error for non-critical operations )the plus-box doesn't support them all).
+      Call usbControlTranfer() with proper constants.
+   Rearrange parameters to usbControlTransfer(). (dm)
+   Add the $X suffix to the executables in the Papenmeier make file: (dm)
+   Papenmeier configuration file processing changes: (dm)
+      Make parser errors more informative.
+      Add more error checking.
+   Add support for rescue images to rhmkiso. (dm)
+   Preferences menu binding changes: (dm)
+      The home/end keys are discard/exit.
+      The escape/return keys are exit/save.
+      The back command is previous setting.
+   Speed up the Papenmeier command resolution algorithm. (dm)
+   Papenmeier configuration file processing changes: (dm)
+      Remove command and modifier table size limits.
+      Allocate tables only as big as they need to be for each model.
+      Allow processing to continue when an error is encountered.
+   Improve the Papenmeier help descriptions. (dm)
+   Tidy up the Papenmeier configuration file parser declarations. (dm)
+   Fix descrepancies in the Papenmeier status key toggle commands. (dm)
+   Remove the padding of help page widths to multiples of 40. (dm)
+   Allow a help page to have more than 255 lines. (dm)
+   Papenmeier driver changes: (dm)
+      Remove the open retry loop.
+      Improve some configuration file error messagds.
+      Allow the key to be between or after the modifiers.
+      List the key after, rather than before, the modifiers.
+      Check for duplicate modifiers.
+   Report the use of a non-option argument when invoking brltty. (dm)
+   Log (debug) keys and commands in command learn mode. (dm)
+   Fix the learning of non-initial repeating commands. (dm)
+   Don't log Papenmeier non-modifier key releases. (dm)
+   Remove some double spaces within the Papenmeier configuration file. (dm)
+   Add a Papenmeier EL mode for the preferences menu. (dm)
+   Support the Papenmeier EL40 better as it has no right key. (dm)
+   Add the -a option to rhmkiso. (dm)
+   Change the rhmkiso image parameters into the -i and -o options. (dm)
+   Fix problems with HandyTech initialization after the loop was removed. (dm)
+   rhmkiso should look for the directory the same way isolinux does. (dm)
+   Add a README (README.RedHat.cd) for rhmkiso. (dm)
+   Rework the Papenmeier switch/key bindings. (dm)
+   Name the switches and keys correctly in the Papenmeier configuration file. (dm)
+   The short info display contains stray dots. (dm)
+   Remove the Televox driver. (np)
+   HandyTech driver changes: (dm)
+      Check for errors after awaitInput().
+      Only check for the display once when opening it.
+   awaitInput() should set EAGAIN on a timeout. (dm)
+   For BrlAPI: Remove the Vario driver's check that its internal buffer is being used. (mm)
+   Add forcePcmOutput() and awaitPcmOutput(). (dm)
+   Generate the dependencies before doing "make distclean". (dm)
+   Tidy up the Mikropuhe sample width setting code. (dm)
+   The Mikropuhe driver should close the sound device after a few seconds of inactivity. (dm)
+   The Mikropuhe mute function should stop the audio device immediately. (dm)
+   Mikropuhe driver enhancements: (dm)
+      Synthesize and speak in a separate thread.
+      Implement the mute function.
+   Core autorepeat support foiled leaving command learn mode for some displays. (dm)
+   Look for external packages in a few more places. (dm)
+   Rearrange code/declarations pertaining to command line options. (dm)
+   Add the speech pass-through FIFO. (np)
+   Don't call gettimeofday() if there's no rewrite interval. (np)
+   Use mallocWrapper() in the CombiBraille driver. (ml)
+   Fix the way in which the Alva driver calculates its rewrite interval. (dm)
+   Change the way the HandyTech driver detects timeouts. (dm)
+   Rewritten VisioBraille README. (sh)
+   Add the -t (built-in text table) option to rhmkiso. (np)
+   CMD_RETURN should behave just like CMD_HOME in the references menu. (np)
+   Add the displaysize= parameter to the VisioBraille driver. (sh)
+   big5 updates. (ck)
+   The English word "I" shouldn't have a letter sign in front of it. (dm)
+   Moved API authentication before any allocation or thread creation. (st)
+   Don't log an Alva display parameters packet as unexpected. (dm)
+   Set errno to EAGAIN if readChunk() times out. (dm)
+   Add usbAwaitInput(). (dm)
+   USB_DIR_(IN|OUT) have been renamed to USB_DIRECTION_(IN|OUT)PUT. (dm)
+   Make the settable ViaVoice rates and volumes more sensible. (dm)
+   Add serial adapter support to the USB core. (dm)
+   Allow --with-(text|attributes)-table= to take an absolute path. (dm)
+   Add USB support to the HandyTech driver. (ml)
+   Add the -s (start shell for inspection) option to rhmkiso. (dm)
+   Add rhmkiso command line argument parsing: (dm)
+      A parameter to specify the original (input) image.
+      A parameter to specify the modified (output) image.
+      An option to specify the braille driver(s).
+      An option to display command usage.
+   rhmkiso enhancements: (np)
+      Copy the entire file structure rather than just the isolinux directory.
+      Copy the volume and application identifiers.
+      Create the destination image with Joliet support.
+      Make it easier to configure a specific braille driver.
+   BrlAPI fixes: (stet
+      Some blanks removed.
+      brlapi_splitHost() now shared between server and client.
+      Server now defaults to listening on local interface only for security.
+      Read and write functions now return size_t.
+      Read and write functions now define brlapi_errno themselves on error.
+      brlapi_getTty() now returns the number of the tty it got.
+   Reverse the functions of the BrailleLite Millennium whiz wheels. (dm)
+   Rename mkrhiso to rhmkiso: (dm)
+      Use nofb rather than video=vc:13.
+      Strip the brltty executable before installing it.
+      Don't invoke the interactive shell.
+   Implement the FreedomScientific (fs) braille driver. (dm)
+   Allow the USB input transfer mode to be specified. (dm)
+
+March 11, 2004:
+ - BRLTTY 3.5pre2 released:
+   Bring device examples in brltty.conf template up-to-date. (dm)
+   Rework key bindings a bit for Alva 3nn and 4nn models. (np)
+   Add the RETURN command. (dm)
+   Add the prfword contraction table opcode. (dm)
+   BRLTTY Manual update: (np,dm)
+      Remove documentation for tbl2hex and txt2hlp.
+      Move "Contracted Braille" to its own top-level section.
+      Some minor corrections.
+   More fixes to fr-abrege.ctb. (np)
+   Fix the "er" contraction in the French table. (np)
+   Add a script (mkrhiso) to make a RedHat/Fedora boot CD. (dm)
+   Single character contraction table matches should be case insensitive. (dm)
+   Give the "always" contraction table opcode a lower priority for matching. (dm)
+   Allow a contracted braille match string to contain spaces. (dm)
+   Fix the dependencies for the host_... objects. (np)
+   Add entries for the French words a, à, and y. (np)
+
+March 8, 2004:
+ - BRLTTY 3.5pre1 released:
+   tbl2hex, tbltest and ctbtest should check for missing parameters. (np,dm)
+   Add text.brf.tbl. (np)
+   Fix the install-api-manpages make target to detect installation errors. (dm)
+   Add the fr-abrege contraction table. (np)
+   Add ctbtest. (dm)
+   Installing BRLTTY shouldn't require that BrlAPI's man pages be built. (dm)
+   Voyager driver enhancements: (sd)
+      Remove internal support for autorepeat and use the core's facility.
+      Add the brlinput= driver parameter.
+   Add the class, after and before contraction table opcodes. (dm)
+   Add the begmidword contraction table opcode. (dm)
+   Hard-code and logically group the contraction table opcode names. (dm)
+   Allow braille/speech driver parameters to be qualified with a driver identifier. (dm)
+   Add --with-(braille|speech|screen|api)-parameters=. (dm)
+   Add comment describing reason for selected dot-to-bit mapping. (np)
+   BrlAPI enhancements: (sh)
+      brlapi_getDriverId and brlapi_getDriverName now use snprintf().
+      Remove BRAPI_PACKETWRITE.
+      Rewrite brlapi_writeBrl() to use the new extended write packet.
+      Protocol version is now 2.
+   Enhance the description of the standard dot numbering convention. (dm)
+   Rename brlapi.h to api.h. (dm)
+   Document the --with-mikropuhe build option. (dm)
+   Correct the descriptions of the two supplied attributes translation tables. (dm)
+   Remove the documentation for tbl2tbl, tbl2txt, and txt2tbl. (dm)
+   BrlAPI enhancements: (sh)
+      Protocol:
+         Add protocol version to the authentication packet.
+         Add extra server-side verifications to recognize a packet containing a
+            valid authentication key but no protocol version (an old-style packet),
+            and to report an accurate error message in this situation.
+      api_server.c:
+         Lower-case first letter of function names to be closer to core's coding style.
+         Make all private fuoctions static.
+   VisioBraille driver enhancements: (sh)
+      Add support for typing upper-case c with cedilla and upper-case a with grave.
+      Key combinations of the form C-M-char can now be given from the braille keyboard.
+      Code clean-up and instruction reordering.
+      Add debug facilities for dealing with unknown PROMs.
+   Add tbltest. (dm)
+   BrlAPI enhancements: (sh)
+      Add more explanation to comments for fields of brlapi_extWriteStruct.
+      Check buffer size in brlapi_getDriverId() andbrlapi_getDriverName().
+   Add the fr-iso01 text translation table. (yp)
+   BrlAPI enhancements: (sh,st)
+      brlapi_extWrite() now takes only one argument (of type brlapi_exjWriteStruct).
+      BRLAPI_EXTWRITESTRUCT_INITIALIZER macro added (to initialize brlapi_extWriteStruct).
+      Fields in packet and corresponding bits in flags have been reordered.
+      Static variables inside client functions have been removed.
+      brlapi_writeDots() now accepts "unsigned char"s (rather than "char"s).
+      Return 0 and do nothing if region's begin exceeds its end.
+      Server refuses invalid cursor position.
+   EuroBraille driver version 1.3.2. (yp)
+   Add a description to the VisioBraille text translation table. (sh)
+   Add Finnish text translation tables. (tm,dm)
+   Remove personal text translation tables. (dm)
+   The text/attributes translation tables are now editable text rather than binary. (np,dm)
+   ViaVoice driver enhancements: (dm)
+      The speed= and volume= driver parameters are now handled by the preferences menu.
+   Add a skeleton setPcmAmplitudeFormat to the support for each non-Linux platform. (dm)
+   Add a table for uncontracted unified French braille. (np)
+   Introduce the user-space Voyager driver. (sd)
+   Map the Alva Satellite's tumbler keys. (dm)
+   Add the cursorDots() function. (sh,dm)
+   Add the extended write feature to BrlAPI. (sh)
+   Automatically turn off the Alva Satellite's PROG key simulation. (dm)
+   Add a routing succeeded tune. (sd)
+   Implement the Mikropuhe speech driver. (dm)
+   Normalize the tune names. (dm)
+   Fix the cursor routing timeout logic. (dm)
+   Play a tune when routing fails. (dm)
+   brlapi.h documentation enhancements: (sh)
+      Correct the example for brlapi_initializeConnection().
+      Illustrate the use of brlapi_strerror().
+   Tidy up the cursor routing code. (dm)
+   Clear all pending pids when a single SIGCHLD is received. (dm)
+   Modularize the status cell setting code. (dm)
+   Always recheck the VGA character count. (dm)
+   Fix ifdef placement errors for disabling speech support. (dm)
+   Don't install the API's man pages when API is disabled. (dm)
+   Configure's option summary output should be subject to the --quiet flag. (dm)
+   Include inttypes rather than stdint. (dm)
+   Add core-supported output overflow protection to Alva serial output. (dm)
+   Add support for the BrailleLite M20. (dm)
+   Increment BrlAPI's version number from 0.1.2 to 0.2.0. (dm)
+   Add -pipe to the gcc/g++ compile options. (dm)
+   BrlAPI fixes: (st)
+      Get rid of bindings, 4k freed from the library.
+      Remove brlapi_getControllingTty availability (getTty does it).
+      Add support for WINDOWID (for multiplexer when an application is running in an xterm).
+      "uint32_t" -> "int" for cursor (to allow -1 special value).
+      "unsigned char"s for writeBrlDots().
+      Add brlapi_strerror().
+      A small but important fix in brlapi_errno_location().
+      Rename BRLAPI_AUTHNAME to BRLAPI_AUTHPATH.
+   Document the BrailleNote key combination for command learn mode. (dm)
+   Add autorepeat to the Papenmeier driver. (dm)
+   Group the Papenmeier status keys more uniformly and conveniently. (dm)
+   Add the CMD_AUTOREPEAT command. (dm)
+   Don't include savePreferences if --disable-preferences-menu is specified. (dm)
+   When in learn mode write "lrn" to the status cells. (dm)
+   When in the preferences menu write "prf" rather than "prefs" to the status cells. (dm)
+   Enhance autospeak to respond to user input and cursor motion. (dm)
+   The mute functions of the FestivalLite and Theta drivers should wait till resources are freed. (dm)
+   Say speech rate/volume changes as they are made. (dm)
+   FestivalLite driver: replace wpm= with spk_rate(). (dm)
+   Theta driver: replace rate= with spk_rate(). (dm)
+   Add the Speech Volume preference. (dm)
+   Add the CMD_SAY_SOFTER and CMD_SAY_LOUDER commands. (dm)
+   Add spk_volume() to the Theta driver. (dm)
+   Fix key bindings for speech commands for PB80. (sd)
+   Add spk_rate() to the Festival driver. (dm)
+   Add the CMD_SAY_SLOWER and CMD_SAY_FASTER commands. (dm)
+   Add "Speech Rate" to the preferences menu. (dm)
+   BrlAPI fixes: (sh)
+      Add braces to clarify an ambiguous else clause.
+      Call the driver's reset() handler when raw mode is left improperly.
+   Correct the representation of 0XC7 (uppercase C with cedilla). (sh)
+   Remove the spurious #define of PREFSTYLE for the null braille driver. (dm)
+   brl_reset() should return a boolean. (dm)
+   EuroBraille driver version 1.3.2: (yp)
+      Remade brl_ReadPacket and brl_WritePacket to allow writting of low-level packets and error code retrieving.
+      Full Iris support.
+      Implement brl_reset.
+   Move the autorepeat settings from command line options to the preferences menu. (dm)
+   Convert blinking settings from update intervals to elapsed time. (dm)
+   Command learn mode shouldn't announce CMD_NOOP. (dm)
+   Centralize the knowledge of which commands can autorepeat. (dm)
+   Clean up the toggle code. (dm)
+   big5 enhancements (including Nemeth Codes). (ck)
+   The mode pointer in the virtual driver shouldn't be externally visible. (dm)
+   Log each command as soon as it's received from the driver. (dm)
+   Log unrecognized and truncated Alva input packets. (dm)
+   Ensure that all toggle descriptions are formatted correctly for learn mode. (dm)
+   Rename the "VAL_SWITCH..." constants to "VAL_TOGGLE_...". (dm)
+   Change the HandyTech driver to use the core's autorepeat facility. (dm)
+   Log the compilation date and time for each driver. (dm)
+   Change all drivers to recognize qualified devices (serial:, usb:, client:, server:). (dm)
+   BRLTTY command-line options changes: (dm)
+      Rename -R (--refresh-interval=) to -U (--update-interval=).
+      Add -R (--autorepeat-delay=).
+      Add -r (--autorepeat-interval=).
+   Change the Alva driver to use the core's autorepeat support. (dm)
+   Add autorepeat suport to the core. (dm)
+   Alvas automatically send an identification packet when DTR is ascerted. (dm)
+   Arbitrarily rewrite the Alva display whenever a configuration change occurs. (dm)
+   Log unexpected Alva input packets. (dm)
+   Alva Satellites can change dimensions on-the-fly. (dm)
+   Force the default choice when an invalid choice is made. (dm)
+   Reidentify an Alva display without restarting the driver. (dm)
+   Define front and top key bindings for the Alva Satellite models. (dm)
+   Define the alternate Alva routing and status keys. (dm)
+   Add USB support to the Alva driver. (dm)
+   Add USB support to the core. (dm)
+   Implement the Albatross driver. (dm)
+   Change the copyright end year from 2003 to 2004. (dm)
+
+January 15, 2004:
+ - BRLTTY 3.4.1 released:
+   Fix an invalid address conversion. (dm)
+   accept() can return 0. (st)
+   Reset the address length before calling accept(). (st)
+   Pasting accented characters doesn't work. (dm)
+   Only remove brltty-config from Programs during distclean. (ah)
+
+December 31, 2003:
+ - BRLTTY 3.4 released:
+   Allow --with-...-driver= identifiers to be prefixed with a minus sign. (dm)
+   Remove duplicate files from screen-3.9.11 patch. (TK)
+   Add more build options to specify directories. (dm)
+      --with-program-directory
+      --with-library-directory
+      --with-data-directory
+      --with-manpage-directory
+      --with-include-directory
+   Swap HandyTech's "freeze screen" [B7] and "six-dot braille" [B6 B8] bindings. (dm)
+   Support HandyTech Modular and Braille Star 80 keypad. (ml)
+   Optional packages are also looked for in /opt/<package-name>. (dm)
+   Configure shouldn't list external drivers when linking statically. (dm)
+   Standardize the software speech driver identification notices. (dm)
+   Support dynamic external, dynamic internal, and static linking for drivers with external dependencies. (dm)
+   spk_open() should return a boolean indicating success or failure. (dm)
+   Add spktest. (dm)
+   Support the Theta text to speech engine. (dm)
+   Add the 4(hundred) to the names of the Alva Delphi models. (dm)
+   Get rid of --disable-tainted-components build option. (dm)
+   Add the --with[out]-viavoice build options. (dm)
+   Remove several unsigned/signed char conflicts. (dm)
+   C90 doesn't permit C++-style // comments. (ml,dm)
+   Support the Festival Lite text to speech engine. (ml)
+   Allow the --with-...-driver= options to take "all" or a comma-delimited list. (dm)
+   The Alva driver shouldn't set CRTSCTS as it prevents some models from displaying output. (dm)
+   Split out HandyTech model identification in preparation for buffer resize support. (dm)
+   Braille drivers providing packet I/O must now implement brl_reset(). (dm)
+   Allow the .tbl extension for attributes translation tables to be optional. (dm)
+   Allow the .ctb extension for contraction tables to be optional. (dm)
+   Support default table extensions and prefixes when configuring. (dm)
+   Disabling contracted braille doesn't fully disable it. (sd,dm)
+   No longer support library names and paths for drivers. (dm)
+   Make private globals static (MiniBraille, Papenmeier, VisioBraille). (dm)
+   Reduce size of BRLPARAMS structure in Avla driver to save space. (ml)
+   Ignore EINTR interruption in Alva's SendToAlva() routine. (ml)
+   Make more private globals static in MultiBraille driver. (ml)
+   Use the TranslationTable typedef in the LogText driver. (ml)
+   EuroBraille driver 1.3.1:
+      Corrected AzerBraille 40 Programming keys bugs.
+      Added a LOG_IO define to log in/out packets to a file.
+      Started to debug Iris identification failures (still not working perfectly).
+   Make private globals static (CombiBraille, MultiBraille). (ml)
+   Make private globals static (EuroBraille, MultiBraille, VideoBraille). (ml)
+   Append the driver code to the driver symbol name. (dm)
+   Convert the VideoBraille driver to use makeOutputTable. (dm)
+   Revive the VarioHT driver. (dm)
+   Fix hanging of Vario driver when display is turned off and on again. (ml)
+   Fix hanging of TSI driver when falling back to 9600 baud. (dm)
+   Convert the EcoBraille driver to use makeOutputTable, openSerialDevice and resetSerialDevice. (dm)
+   Rearrange the t_key structure in the EuroBraille driver to save space. (ml)
+   Convert the MiniBraille driver to use makeOutputTable and openSerialDevice. (dm)
+   Convert the BralleNote driver to use makeOutputTable. (dm)
+   Convert MultiBraille driver to use makeOutputTable, openSerialDevice and resetSerialDevice. (ml,dm)
+   Starting the HandyTech driver gobbles a file descriptor if:
+      model detection fails (ml)
+      resetting the device fails (dm)
+   Convert the MDV driver to use openSerialDevice and resetSerialDevice. (ml,dm)
+   Convert the VisioBraille driver to use openSerialDevice and resetSerialDevice. (ml,dm)
+   The O_NOCTTY flag shouldn't be used when opening the Voyager device. (dm)
+   Convert the EuroBraille driver to use openSerialDevice and resetSerialDevice. (ml)
+   Convert the Alva driver to use openSerialDevice and resetSerialDevice. (ml,dm)
+   Convert the Vario driver to use openSerialDevice and resetSerialDevice. (ml,dm)
+   Convert the BrailleLite driver to use openSerialDevice and resetSerialDevice. (dm)
+   Replace internal TSI driver SetSpeed() routine with resetSerialDevice(). (ml)
+   Opening a serial device shouldn't hang if CLOCAL isn't set. (dm)
+   Convert the TSI driver to use openSerialDevice(). (dm)
+   Update big5.ctb. (ck)
+   Add the patch for screen-4.0.1. (rb)
+   Add packet I/O to the HandyTech driver. (dm)
+   Changes to support OpenBSD. (ml,dm)
+   Add support for HandyTech's Braillino. (ml,dm)
+   Add a full list of country codes to en-us-g2.ctb as literal. (ml)
+   Implement contraction table opcode 26 (locale). (dm)
+   Document how to use stow to maintain multiple releases of BRLTTY. (bd)
+   brlapi_getControllingTty() now checks back through parent processes until a real tty is found. (ml,dm)
+   Add targets for compiling and uncompiling text translation tables. (ml)
+   Assume a direct-through mapping if there is no screen font map. (ml,dm)
+   Enhance the INIT_PATH mechanism: (np)
+      Handle being invoked as linuxrc.
+      Handle other commands, e.g. telinit, being ways to invoke init.
+   EuroBraille driver 1.3: (yp)
+      Reduced code size.
+      BrlAPI compatible.
+      Transfer routines moved to separate program.
+      Corrected bugs when using an Iris-40 (still not perfect).
+   Change the configure default for BrlAPI to enabled. (dm)
+   Put %config and %verify for brltty.conf on the same line in the rpm spec file. (dm)
+   Delay after receiving a Papenmeier identity packet to allow the display time to become fully ready. (dm)
+   Add the virtual braille display driver. (ml)
+   Introduce brltty-config and use it from brltty-install. (dm)
+   Add CMD_LEARN to the Papenmeier driver. (dm)
+   Allow PRDIFLN/NXDIFLN to move up/down in the preferences menu. (dm)
+   Don't report non-existent directories when uninstalling. (dm)
+   Reduce the log for failure to set the working directory to a warning. (dm)
+   Add / as an additional backup working directory. (dm)
+   Make the directory for driver help, driver configuration, and driver lists specifiable. (dm)
+      the -D (--data-directory=) command line option
+      the data-directory configuration file directive
+      the BRLTTY_DATA_DIRECTORY environment variable
+   Make the directory for text and attributes tables specifiable. (dm)
+      the -T (--tables-directory=) command line option
+      the tables-directory configuration file directive
+      the BRLTTY_TABLES_DIRECTORY environment variable
+   Make the directory for contraction tables specifiable. (dm)
+      the -C (--contractions-directory=) command line option
+      the contractions-directory configuration file directive
+      the BRLTTY_CONTRACTIONS_DIRECTORY environment variable
+   The "braille driver initialization failed" log fills /var when the display isn't connected. (dm)
+
+September 1, 2003:
+ - BRLTTY 3.3.1 released:
+   Correct the description of which commands are unmasked by default. (st)
+   Build fails with standalone-programs and braille-driver=vd,vh. (dm)
+   Log "braille driver initialization failed" fills /var when display not connected. (dm)
+
+August 4, 2003:
+ - BRLTTY 3.3 released:
+   Let rpm determine requirements. (dm)
+   Papenmeier driver enhancements: (dm,bd)
+      Write packet as single operation rather than in 3 parts (header, data, trailer).
+      Log output packets.
+      Log full identity packet rather than just first 4 bytes.
+   Configuring with --without-screen-driver now uses scr_null. (dm)
+   Remove the use of sched_yield() to restore tone length accuracy. (np)
+   Add the "Say-Line Mode" (Immediate, Enqueue) preference. (np,dm)
+   Rename the default for the Linux screen driver option acm from default to auto. (dm)
+   No longer strip executables in the build tree. (dm)
+   Compile all drivers even if built-in ones are configured. (dm)
+   Add the -L (--library-directory=) option to brltty and brltest. (dm)
+   Wrap long output lines from configure --help. (dm)
+   Enhancements to en-us-g2 contraction table. (jb)
+   Braille Lite driver enhancements: (dm)
+      Some initialization failures gobble resources.
+      Add Millennium support (whiz wheels, advance bar combinations).
+      Change keyboard emulation mode default from off to on.
+      Allow chorded keyboard input when not in keyboard emulation mode.
+   Check for the pow() function during autoconf. (dm)
+   The global variable languageCount in the ViaVoice speech driver isn't used. (dm)
+   Add basic support for Gnopernicus. (ml)
+   autogen.sh invokes autoconf (overridable by $BRLTTY_AUTOCONF) instead of autoconf-2.53. (dm)
+   Clean up the make file in the Documents subdirectory. (dm)
+   Remove the German BRLTTY manual since it's so out-of-date. (dm)
+   Start implementing the Autospeak preference. (dm)
+   README.Debian still had references to /sbin/brltty (now in /bin). (sh)
+   Make the timing routines more robust. (dm)
+   Stop console tones automatically if the system is too slow. (dm)
+   Convert the screen drivers from C++ to C. (dm)
+   Add the -n, -d and -o options to bp2cf for device path translation. (dm)
+   A few changes to text.da-1252.tbl by Svend and his group. (hs)
+   Reduce HandyTech driver data segment size. (ml)
+   Reduce Papenmeier driver data segment size when configuration file support not included. (ml)
+   bp2cf now records the boot parameter in the configuration file header. (dm)
+   Installing the rpm now updates brltty.conf according to the boot parameter. (dm)
+   bp2cf interpreted the boot parameter incorrectly if it contained no commas. (dm)
+   Add the -f, -c, -u, -p, and -h options to bp2cf. (dm)
+   Move the default install location for the API library from /lib to /usr/lib. (dm)
+   Improve brltty-install's progress messages. (dm)
+   brltty-install now copies brltty.conf if it exists. (dm)
+   Clean up the rpm spec file. (dm)
+   Move default install directory for man pages from /man to /usr/share/man. (dm)
+   Rename install-brltty to brltty-install. (dm)
+   EuroBraille driver enhancements: Yannick Plassiard <plassi_y@epita.fr>
+      Document cut&paste in README.
+      Initial support for file transfer.
+      Several bug fixes.
+   Implement brlapi. Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.fr> + Samuel Thibault <Samuel.Thibault@ens-lyon.fr>
+   Core changes to support big5.ctb: (dm)
+      Change ctb opcode "ignore" to "replace".
+   Add table for Chinese big5 character set (big5.ctb). Samuel Yang <mison@bbs.ee.ntu.edu.tw>
+   Add Czech text table (text.cz.tbl). Jan Buchal <buchal@brailcom.org>
+   Add alert messages preference. (dm)
+   VisioBraille driver enhancements. Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.fr>
+   IA64 sizeof returns a long and needs a long printf format. (dm)
+   Make install no longer installs unneeded tables. (dm)
+      (--without-text-table, --without-attributes-table, --disable-contracted-braille)
+   Change text-table default from us to nabcc in brltty.conf. (dm)
+
+February 3, 2003:
+ - BRLTTY 3.2 released:
+   Russian text table (text.ru.tbl) based on koi8-r. Hans Schou <chlor@schou.dk>
+   Man page (brltty.1). (dm)
+   Fixed Papenmeier easy access bar support. (dm)
+   Speech changes: (dm)
+      CMD_SAY renamed to CMD_SAY_LINE.
+      CMCD_SAYALL renamed to CMD_SAY_BELOW.
+      CMD_SAY_ABOVE added.
+   Papenmeier driver robustness enhancements. (dm)
+   Type char is unsigned on arm, powerpc and s390. (dm)
+      HandyTech/braille.c (keys.column, keys.status)
+      VideoBraille/vblow.c (BrButtons)
+   Added --disable-speech-support. (dm)
+   Added --disable-tainted-components. (dm)
+   Updated config.guess and config.sub (from automake-1.5). (dm)
+   Updated VisioBraille driver. Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.fr>
+   Support building in another directory. (dm)
+   Fix Linux MIDI device initialization. (dm)
+   Add core support for visual displays. (dm)
+   Renamed help source from brlttyh*.txt to help*.txt. (dm)
+   Added --disable-pm-configfile. (dm)
+   Added braille display resize mechanism. (dm)
+   Moved brl->buffer allocation from driver to core. (dm,sd)
+      (all but: LogText, Vario)
+   Braille driver changes. (dm)
+      Renamed type brldim to BrailleDisplay.
+      Renamed field brl->disp to brl->buffer.
+      Renamed routine brl_initialize to brl_open.
+      Renamed routine brl_read to brl_readCommand.
+      brl_open success now determined by returned boolean.
+      Reordered brl_open parameters to put BrailleDisplay first.
+      Use brl->helpPage instead of setHelpPageNumber().
+      Pass BrailleDisplay to readCommand and writeStatus.
+   Support boot parameters (brltty=driver,device,table).
+   Allow option operand shortcuts. (dm)
+      Relative device paths are anchored at /dev.
+      The .tbl table extension is optional.
+      The text. text table prefix is optional with no directory.
+   Normalize translation table names. (dm)
+      french->fr german->de pl-iso02->pl sweden=se1 swedish=se2
+   Added LogText driver help file. Hans Schou <chlor@schou.dk>
+   Fixed LogText input/output translation for blank, NUL, and 0XFF. Hans Schou <chlor@schou.dk>
+   Better Danish translation tables. Hans Schou <chlor@schou.dk>
+   RPM now correctly builds by non-root user. Matthew Miller <mattdm@mattdm.org>
+   Define more Alva models. (dm)
+      Delphi 420, 440 Desktop; Satellite 584 Pro, 544 Traveller
+   Move device creation from the make file to the Linux screen driver. (dm)
+   Remove the --with-screen-device build option. (dm)
+   With devfs, /dev/tty0 is /dev/vc/0. (dm) + Tapio Kelloniemi
+   Verify that /dev/vcsa (or equivalent) is character special. (dm)
+   Version 1.2 of the EuroBraille driver. Yannick PLASSIARD <plassi_y@epitech.net>
+      (code cleanup, new models, LCD display)
+   Add cbifs text table. Yannick PLASSIARD <plassi_y@epitech.net>
+   Add a settings summary to the end of the ./configure output. (dm,sd)
+   Implement a separate settable volume level for each tune device. (dm)
+   Log tune device open errors with LOG_DEBUG. (dm,sd)
+   In the preferences menu, show each --disable-ed tune device as "unsupported". (dm,sd)
+   Split scr_base.h/cc into a separate header and source for each class. (dm)
+   Complete the conversion from tones to notes. (dm)
+   Rationalize/normalize the tune device names. (dm,sd)
+      speaker->beeper dac->pcm adlib->fm
+   Enhancements to en-us-g2 contraction table. John Boyer <director@chpi.org>
+
+October 30, 2002:
+ - BRLTTY 3.1 released (note move from /sbin to /bin):
+   Significantly reduced screen-3.9.11 patch size. Rich Burridge <Rich.Burridge@Sun.COM>
+   Use (int) instead of rint() to eliminate the need for DO_C99_MATH
+      when building against uClibc. (np) + (dm)
+   Horizontal window placement during cursor tracking with sliding window off
+      should be relative rather than absolute. (dm)
+   Add text translation table for vni (Vietnamese). Thu Trang <phamtrang2002@softhome.net>
+   Implement attribute viewing and attribute underlining for contracted braille. (dm)
+   Load drivers from background process. (dm)
+      (ViaVoice wouldn't run when loaded in foreground but started in backgroun)
+   Support 512-character fonts. (dm) + Tapio Kelloniemi
+   Fix colour array for CR_DESCCHAR. Tapio Kelloniemi
+   Experimental gpm (mouse) support. (dm)
+   HandyTech driver improvements. (dm) + Mario Lang <mlang@delysid.org>
+      Merge Bookworm's status maintenance into main read loop.
+      Paste changed from B4+B5 to UP+DN (now accessible from input mode).
+      Control and meta modifiers added to input mode.
+      BrailleStar command enhancements.
+      BrailleStar USB IDs added into Linux kernel (2.4.20).
+   Conversion of Alva and HandyTech drivers from C++ to C. (dm)
+   Removal of closed source library for Alva parallel port support. (dm,np)
+   Introduce --disable-... options to reduce the size of the executable. (dm)
+      preferences-menu learn-mode contracted-braille
+      speaker-tunes dac-tunes midi-tunes adlib-tunes
+   Yield during busy loops. (dm)
+   Help file code made endianism-independent. (dm)
+   All core header files made C++ includable. (dm)
+   Install programs in /bin instead of in /sbin. (dm)
+   Solaris support. (dm) + Rich Burridge <Rich.Burridge@Sun.COM>
+   Changes to scrtest syntax. (dm)
+   Help option (-h, --help) added to test and utility programs. (dm)
+   Vario-HT renamed to VarioHT. (dm)
+   Autoconf support and portabilization. (dm)
+   Driver loading no longer relies on static data. (dm)
+   Use cp rather than mv in mkbd so as to not alter the file table layout. (dm)
+   Debian botdisk notes. Boris Daix <Boris.Daix@insa-lyon.fr>
+   Changes to support gcc 3. (dm) + Mark Mielke <mark@mielke.cc> + Mario Lang <mlang@delysid.org>
+      Use -lsupc++ when linking with gcc3.
+      Use the C++ linker if the C linker can't do the job
+         (brltty,brltest,scrtest gain a run-time dependency on libstdc++).
+      Remove inline attribute from all .cc implementations (scr_base, scr_shm).
+      Extern const (brl.h[noBraille], spk.h[noSpeech]) followed by non-const
+         declaration (brl_driver, spk_driver) no longer allowed.
+   Enhancements to en-us-g2 contraction table. John Boyer <director@chpi.org>
+
+July 13, 2002:
+ - BRLTTY 3.0 released:
+   New Bootdisk Hacking Mini-HowTo. (sd)
+   Updated screen patch (for 3.9.11) provided. Rudolf Weeber <Rudolf.Weeber@gmx.de>
+   Implement in-line contracted braille facility. John Boyer <director@chpi.org>
+   Develop contraction table for grade 2 US English braille. John Boyer <director@chpi.org>
+   Implement generic driver parameters facility. (dm,sd)
+   Resolve conflict between .dat and .conf both being called configuration files. (dm)
+      Default file extension .dat becomes .prefs.
+      CMD_CONFMENU -> CMD_PREFMENU
+      CMD_SAVECONF -> CMD_PREFSAVE
+      CMD_RESET -> CMD_PREFLOAD
+   Normalize short options. (dm)
+      -p becomes -S
+      -c becomes -p
+   Normalize long options. (dm)
+      --braille becomes --braille-driver
+      --config becomes --preferences-file
+      --device becomes --braille-device
+      --log becomes --log-level
+      --speech becomes --speech-driver
+      --speechparm becomes --speech-parameters
+      --table becomes --text-table
+   normalize brltty.conf directives. (dm)
+      braille-configuration becomes preferences-file
+      dot-translation becomes text-table
+      speech-driverparm becomes speech-parameters
+   Add -c (--contraction-table=) option to specify six-dot braille translation. (dm)
+   Add -e (--standard-error) option to log to standard error rather than via syslog. (dm,sd)
+   Add -f (--configuration-file=) option to specify default parameters file (brltty.conf). (dm)
+   Recognize level names (and abbreviations thereof) for -l (--log-level=) option. (dm)
+      (emergency, alert, critical, error, warning, notice, information, debug)
+   Add -n (--no-daemon) option to make debugging easier and to permit invocation from inittab. (dm,sd)
+   Add -B (--braille-parameters=) option, and the braille-parameters brltty.conf directive. (dm)
+   Add -E (--environment-variables) option to allow boot parameters. (dm,sd)
+   Implement user-settable attributes translation table. (dm)
+      Introduce short option -a.
+      Introduce long option --attributes-table=.
+      Introduce brltty.conf directive attributes-table.
+   Add -M (--message-delay=) option. (dm)
+   Add -N (--no-speech) option. (dm)
+   Add -P (--pid-file=) option. (dm)
+   Add -R (--refresh-interval=) option. (dm)
+   Add -X (--screen-parameters=) option. (dm)
+   Add the acm= (default, iso01, vt100, cp437, user) screen parameter. (dm)
+   Implement BrailleNote driver. (dm) with help from Mike Pedersen <mpedersen@mindspring.com>
+   Implement HandyTech driver. Andreas Gross <andi@andi-bika.de>
+   Add Bookworm support. (dm)
+   Implement LogText driver. (dm) with help from Hans Schou <chlor@schou.dk>
+   Implement Voyager driver. (sd) with help from Stephane Dalton <sdalton@videotron.ca>
+   BrailleLite driver enhancements. (sd)
+      Auto-detect 18 verses 40.
+      Add DETECT_FOREVER option.
+      Add USE_TEXTTRANS option.
+      Add bindings for search backward/forward.
+      Add bindings for move to previous/next paragraph.
+      Add bindings for switch to previous/next virtual terminal.
+      Change binding for restart driver to a safer combination.
+      Add shortcut for one-digit repeat count.
+      Fix bug causing internal cursor to go off display during g-chord with advance bar left.
+      Fix k-chord to toggle off keyboard emulation.
+      Fix day-one startup segfault bug.
+      Add baudrate parameter.
+      Add kbemu parameter.
+      Portability fixes.
+   Papenmeier driver enhancements:
+      Remove special-case code for routing keys. (ah)
+      Recognize key "routing" in configuration file. (ah)
+      Allow routing keys to be bound to modifier keys. (ah)
+      Recognize toggle modifiers "on" and "off" in configuration file. (ah)
+      Add on/off capability to existing toggle key definitions. (ah)
+      Allow command to be bound to just modifier keys. (ah)
+      Implement input mode and special key support. (ah,dm)
+      Remove yacc noise from make. (dm,ah)
+      Clean up log messages. (dm)
+      Log display type even if unknown. (dm)
+      For displays with an easy access bar: (dm)
+         Change left2 from HOME to BACK.
+      For displays with 9 front keys: (dm)
+         Change 1+3 from TOP to TOP_LEFT.
+         Change 1+7 from BOT to BOT_LEFT.
+         Move cut begin/end from 2/8 to 1/9.
+         Add paste as 1+9 (for displays with less than 13 status cells).
+         Move half window left/right from 1+5/9+5 to 2/8.
+         Switch lnup/lndn to 3/7 and winup/windn to 4/6.
+         Add more horizontal motion commands.
+      For displays with 13 front keys: (dm)
+         Define keys 1-4 and 10-13 as modifiers.
+         Define 1+13 as paste.
+         Add many more commands.
+      For displays with 22 status keys: (dm)
+         Fix key assignments: 5-8 -> 6-9.
+         Define 4 as back.
+         Move info from 8 to 5.
+         Define 8 as input mode toggle.
+         Display current virtual screen number in 11.
+      Define status key commands for displays with 4 status cells. (dm)
+      Rename environment variable BRLTTY_CONF to BRLTTY_PM_CONF. (dm)
+      Key releases no longer interrupt message waits. (dm)
+      Remove conditional compiling for debugging. (dm)
+      Add driver parameters: configfile, debugreads, debugwrites, debugkeys. (dm)
+      Wait indefinitely for braille display to be connected. (dm)
+   TSI driver enhancements. (sd)
+      Add bindings for go to beginning/end of current line.
+   Clean up Alva parallel port build. (dm) + Andreas Gross <andi@andi-bika.de>
+   Version 1.0 of the EuroBraille driver. Yannick Plassiard <Yannick.Plassiard@free.fr>
+   Fix MiniBraille delay after write. (dm)
+   Vario driver can now be statically linked. (dm)
+   Add cut-and-paste for VideoBraille. Christian Comaschi <christian_comaschi@libero.it>
+   Add identification output to VisioBraille driver. (dm)
+   Add support for 5.0 PROM family to VisioBraille driver. Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.fr>
+   Add program, uid, and gid parameters to ExternalSpeech driver. (sd)
+   Stop GenericSay from reannouncing BRLTTY version during cursor routing. (dm)
+   Add command parameter to GenericSay driver. (dm)
+   Implement ViaVoice driver. (dm)
+   List translation tables in make file. (dm)
+   Add cross-compile support to make file. (sd,dm)
+   Add INSTALL_ROOT support to make file. (dm)
+   Add Danish text translation table. Hans Schou <chlor@schou.dk>
+   Add -c (--code-page=) option to tbl2txt. (dm)
+      Initial code and tables supplied by Hans Schou <chlor@schou.dk>.
+   Clean up screen device creation in make file. (dm)
+   Make preferences menu more user-friendly. (dm,sd)
+      Choices are words rather than numbers.
+      Only present relevant items.
+   Change left/right arrow in prefs menu from prev/next item to prev/next choice. (dm)
+   Add support for tunes via sound card (/dev/dsp). (dm,np)
+   Add support for tunes via MIDI (/dev/sequencer). (dm)
+   Add support for tunes via AdLib/OPL3/SB-FM. (dm)
+   Sliding window enhancements. (sd)
+   Replace skip blank end-of-lines with configurable skip blank windows mode. (dm)
+   Implement horizontal window scroll overlap. (dm)
+      Full window right no longer does partial move to pick up end of line.
+      Single character right lets left end of window move to last column of line.
+      Wrap up at left of line goes to multiple of window width at right of previous line.
+   Display message when sound is off for critical operations like freeze/unfreeze. (sd)
+   Allow braille display to stick out to the right of the screen. (sd)
+   Add commands to switch to the next and previous virtual terminals. (sd)
+   Implement CMD_BACK (to go back after a cursor tracking motion). (dm)
+      Add to Alva driver. (np)
+      Add to BrailleLite driver. (sd)
+      Add to BrailleNote driver. (dm)
+      Add to Papenmeier driver. (dm)
+      Add to TSI driver. (sd)
+   Implement CR_DESCCHAR (describe character at routing key). (dm)
+   Implement CR_SETLEFT (left of window to routing key). (dm)
+   Rename CR_ROUTEOFFSET to CR_ROUTE. (dm)
+   Rename CR_BEGBLKOFFSET to CR_CUTBEGIN. (dm)
+   Rename CR_ENDBLKOFFSET to CR_CUTRECT. (dm)
+   Implement CR_CUTLINE (lineear cut to character). (dm)
+   Implement CR_CUTAPPEND (start cut area without clearing cut buffer). (dm)
+   Implement CR_SETMARK (remember window position). (dm)
+   Implement CR_GOTOMARK (return to remembered window position). (dm)
+   Implement CMD_{PR,NX}PROMPT (find previous/next command prompt). (dm)
+   Implement CMD_LEARN (command learn mode). (dm)
+   Remove special commands for displays without routing keys: (dm,sd)
+      Use CR_ROUTE instead of CMD_CSRJMP.
+      Use CR_CUTBEGIN instead of CMD_CUT_BEG.
+      Use CR_CUTRECT instead of CMD_CUT_END.
+   Add reverse text translation table for input from braille display. (dm,sd)
+   Get font data from kernel rather than guess at it by probing. (sd)
+   Handle font change and screen size change. (sd)
+   Make cursor routing work when keyboard is in raw mode. (dm)
+   Search list of known screen device names (/dev/vcsa0 /dev/vcsa /dev/vcc/a)
+      rather than just check for /dev/vcsa0. (dm)
+   Move key insertion, cursor routing, and virtual terminal switching into screen class. (dm)
+   Add cursor routing, cursor motion, and page switching to help screen. (dm)
+   Allow a screen driver to selectively override commands. (dm)
+   Fix holes in signal blocking during cursor routing. (dm)
+   Don't use LOG_CONS option of syslog uhtil in the background. (dm)
+   Convert CMD_ from defines to enum. (dm)
+      CombiBraille: tables.h converted to cmdtrans.h, argtrans removed.
+   Convert CMD_KEY_... commands to VAL_PASSKEY+VPK_... mechanism. (dm)
+      BrailleLite cmdtrans[] and blkey.cmd changed from unsigned char to int.
+   Rename VAL_PASSTHRU to VAL_PASSCHAR. (dm)
+   Rename VAL_BRLKEY to VAL_PASSDOTS. (dm)
+   Add the following flags for VAL_PASSCHAR and VAL_PASSDOTS: (dm)
+      VPC_CONTROL, VPC_META, VPC_SHIFT, VPC_UPPER
+   Inform readbrl of the current command context. (dm)
+   Remove TBL_CMD and TBL_ARG. (dm)
+   Log command line syntax errors to syslog. (dm)
+   Make message delay by default; add MSG_NODELAY flag. (sd)
+   Fix problems with the skipping of blank windows and ends of liens. (sd)
+   Change directory to right place if PREFIX is set. (dm)
+   Disable speech tracking when CMD_SAY is used. (np)
+   Merge LogAndStderr into LohPrint. (dm)
+   Cleanly wrap long help (-h) output lines. (dm)
+   Increase default message delay from 2 to 4 seconds. (dm)
+   Message delay is now interruptable by pressing key on braille display. (dm)
+   Startup and exiting messages now fit within 18 characters. (dm,sd)
+   Add message to alert user to presence of startup errors. (dm)
+   Warn if a configuration file directive is specified more than once. (dm)
+   Add more memory allocation and I/O error checks. (dm)
+   Control-C now usable during startup and driver restart. (dm,sd)
+   Audit all braille and speech drivers. (dm)
+      Remove printing via stdio.
+      Ensure that first line of identification output uses LOG_NOTICE.
+      Ensure that subsequent lines of identification output use LOG_INFO.
+      Remove new-lines from logged messages.
+      Ensure that all output to braille display is drained before closing. 
+   Updated manual. (dm) with help from Cheryl Homiak <chomiak@shellworld.net>
+
+June 26, 2001:
+ - BRLTTY 2.98 released:
+ - fixes to the TSI driver for Navigator 80, by Stéphane Doyon
+ - added driver for MiniBraille displays, by David Sauer <sauer@brailcom.cz>
+ - added driver for Videobraille displays, by Christian Comaschi
+   <christian_comaschi@libero.it>
+ - added driver for VisioBraille displays, by Sébastien Hinderer
+   <jrf3@wanadoo.fr>
+ - autodetection of BrailleLite 18 vs 40, by Willi Lutzenberger 
+   <willutz@gmx.net>
+ - support for Alva AS570 from Willi Lutzenberger <willutz@gmx.net>
+
+March 2001:
+ - version 2.97 (beta):
+ - Code cleanup i.e. the big mess that brltty.c was is now broken into
+   multiple files.
+ - Command and parameter to skip blank windows.
+ - Command to switch virtual terminal.
+ - Command to find the next/prev line indented not more than a certain point.
+ - Command to find next/prev line following blank lines (next/prev paragraph).
+ - Command to search for some text on the screen. Text is taken from
+   cut_buffer.
+ - Fixes to ExternalSpeech driver. Can now pass uid through speech driver
+   parameter.
+
+26 Nov 2000:
+ - version 2.96 (beta):
+ - Vario support updates from Mario Lang <mlang@home.delysid.org>
+ - ALVA Satellite 544 support from Kazunori MINATANI <99112004@gakushuin.ac.jp>
+ - Various pieces from Stéphane Doyon, most notably:
+   - cursor routing race fix that showed as a BRLTTY freeze with
+     latest 2.4.0-test* Linux kernels;
+   - BRLTTY now doesn't exit on startup when minor i.e. non-fatal
+     errors occur;
+   - loglevel lowered on less important messages to reduce noise;
+   - fixes to the module for external speech program support;
+   - TSI driver updates
+   - multiple new command bindings for speech;
+   - speech tracking;
+   - various cleanup.
+
+16 Sep 2000:
+ - version 2.95 (beta):
+ - EcoBraille driver update from Oscar Fernandez <ofa@once.es>
+ - Attribute tracking tuning by Dave Mielke <dave@mielke.cc>
+ - EuroBraille driver update from Yannick Plassiard <Yannick.Plassiard@free.fr>
+ - Added ExternalSpeech speech driver: for sending speech to an external
+   program with our custom protocol. Intended for a bit more sophisticated
+   speech features: speech tracking, and passing video attributes. 
+   First intended for use with IBM's Viavoice TTS AKA Viavoice OutLoud.
+   The external speech program will be released separately.
+ - Added support for speech tracking (for synths that provide word indexing).
+ - Added option to skip directly to next/prev line on FWINRT/LT when the
+   rest of the line is blank.
+ - MDV braille driver update.
+
+23 May 2000:
+ - version 2.90 (beta):
+ - Tieman MultiBraille driver, contributed by Wolfgang Astleitner
+   <wolfgang.astleitner@liwest.at>
+ - Put startbrl() back after daemonized because some drivers expect to have
+   all life time to probe for a terminal.  Created inithlpscr() instead.
+ - Replaced 'g++' by 'gcc' in the Makefile eliminating dependencies 
+   with libstdc++
+
+24 April 2000:
+ - version 2.51 (beta):
+ - added new driver for all Papenmeier terminals (AH)
+    - autodetection
+    - configuration via config file
+
+ - changed the startup order (AH) - new order:
+     loadconfig()
+     startbrl()     -- pm: autodetection, changes name of help file
+     intscr(helpfile)
+
+08 April 2000:
+  - Released BRLTTY 2.50 (beta)
+  - Added Installation Bootdisk Hacking Mini-HowTo (SD, NP)
+  - Fixed BrailleLite speech loading (added RTLD_GLOBAL to dlopen() flags) (NP)
+  - TSI driver updates (SD)
+  - BrailleLite advance bar binding improvements from Pete De Vasto 
+    <pdevasto@incyte.com>
+  - Apply the -q option to all log messages. Never print debug messages.
+      If -q is specified then don't print information messages and
+      notices. (DM)
+  - Change the default of the -l option from 4 (LOG_WARNING) to 5 (LOG_NOTICE). (DM)
+  - Standardized the way in which drivers identify themselves:
+      LOG_NOTICE for configuration information, LOG_INFO for copyright.
+      First line no indent, subsequent lines indented 3 spaces. (DM)
+  - Added Papenmeier functions: CMD_SAY to status key 5, CMD_MUTE to
+      status key 4. (DM)
+  - Papenmeier status cells did not say "Info " when in info mode. Fixed. (DM)
+  - The default name of the configuration save/restore file is now
+      driver-dependent. Renamed from "/etc/brltty/brlttyconf.dat" to
+      "/etc/brltty/brltty-XX.dat", where "XX" is the two-letter
+      identifier of the braille driver. To preserve current settings
+      when upgrading, preserve and rename old file. (DM)
+  - Use getopt_long, if the installation has it, to give every option a
+      long (--keyword or --keyword=value) name. (DM)
+  - Add support for "/etc/brltty.conf" (with sample file in DOCS
+      subdirectory). (DM)
+  - Papenmeier driver now uses safe read and write routines. (DM)
+  - Safe read and write routines now available for drivers so that
+      signals won't interfere with their I/O operations. (DM)
+  - Full list of speech drivers now displayed if bad driver specified. (DM)
+  - Add more options to the GenericSay AccentSA helper script. (DM)
+  - Cursor routing has been broken -- fixed  (NP)
+  - Better fix for cursor routing which happened to re-open and redetect 
+    screen font, and incidentally writing to stderr.  Fixed  (NP)
+  - Fixed compilation warnings  (NP)
+
+01 April 2000:
+  - Released BRLTTY 2.40 (beta)
+  - Work from Dave Mielke <dave@mielke.cc>:
+    - Added helper script for Accent/SA to GenericSay.
+    - GenericSay now closes pipe to signal mute function.
+    - GenericSay command changed to /usr/local/bin/say from /usr/bin/say.
+    - Generic-say renamed to GenericSay.
+    - Speech driver are dynamically loadable.
+    - BRLTTY now has the ability to determine the character set used by 
+      the screen fonts.
+  - Credits owner for the previous item corrected (they actually should go to
+    August Hörandl).
+  - All possible dynamic loadable braille drivers are listed if there is no
+    valid selection provided.  (NP)
+
+28 March 2000:
+  - Released BRLTTY 2.30 (beta)
+  - Braille terminal drivers are now dynamically loadable.  This is a big
+    change. Credits go to:
+      August Hörandl <august.hoerandl@gmx.at>
+        Original work
+      Dave Mielke <dave@mielke.cc>
+        Resynching with current BRLTTY version, cleanup, etc.
+
+01 January 2000:
+  - Released BRLTTY 2.21 (beta)
+
+December 1999:
+  - Contribution of a driver for BAUM Vario (emulation mode 2 / HT-Protocol)
+    + a Swedish braille table from Per Lejontand <per@acc.umu.se> (with
+    assistance from Niklas Edmundsson <nikke@acc.umu.se>). BRLTTY beta
+    version 2.20 will be circulated so that this driver can be tested.
+
+October 1999:
+  - TSI driver: Workaround for quirks of TSI emulators such as Vario.
+
+June 1999:
+  - Completed first stable beta version of MDV driver.
+
+21 June 1999:
+  - Released BRLTTY 2.11 (beta)
+  - Added support for BrailleLite 40 (NP).
+  - Added autodetection of some extra CombiBraille models.  Thanks to
+    William Blokland <William_Blokland@tieman.nl>.
+  - Added Alva Delphi 80 support.  Thanks to ??? <cstrobel@crosslink.net>.
+
+23 March 1999:
+  - BRLTTY 2.1 released.
+  - Completed updates to the manual (english version only) for 2.1.
+  - missing errno.h for some drivers when compiling on my RedHat (glibc),
+    I added the include to misc.h.
+  - Work-around for bug in Linux kernel 2.0.35: beeps wouldn't stop...
+  - writebrl() and closebrl() now receive a pointer on the brldim structure,
+    like inibrl().
+  - Removed syslog warning when the default config file doesn't exist.
+  - Added EcoBraille support.  Thanks to Oscar Fernandez <ofa@once.es>.
+  - Minor update to TSI driver (2.1).
+  - Added some bindings for switching settings in config menu. Quickly
+    fixed the manual. Regenerated the german manual using latin-1 source
+    though not with all suggested options.
+  - Some tweaks to the BrailleLite driver (NP)
+  - Rework of the config menu so small braille displays can use it.
+  - Alva Delphi support added (thanks to Terry Barnaby 
+    <terry@beam.demon.co.uk>).
+    Now Alva_ABT3 is called Alva.
+  - Alva parallel port support added using J. lemmens 
+    <jlemmens@inter.nl.net> 's library.
+
+22 December 1998:
+  - BrailleLite 18 and Festival support from Nikhil added.
+    Also eliminated the need for a fixed menu item length
+    and it's now possible to use a display smaller than any menu item i.e.
+    the BrailleLite 18.
+  - Contribution of Norwegian braille tables and ibm850toiso from 
+    Helge Havnegjerde <helge.havnegjerde@c2i.net>
+
+22 July 1998: BRLTTY 2.0 released!
+  - Another fix to the cursor routing algorithm
+  - Web pages online at http://www.cam.org/~nico/brltty
+  - Fixes to compile with egcs and glibc (RH5.1)
+  - Fixed Combibraille speech support (broken when BRLTTY moved to 
+    ISO-8859-1 base charset while the Combibraille expects an IBM CP437
+    charset).
+  - Updated manual, FAQ and TODO.
+  - Fixes (hopefully) to the cursor routing algorithm in csrjmp_sub.
+  - Added syslog support functions, and messages in brltty.c.
+  - Added CMD_RESTARTBRL command to reinitialize braille driver when
+    the braille display is turned off.
+  -  Additions to TSI driver: support for PB65/80, support for all PB models
+     at 19200baud, detects when display is turned off and reinitializes itself.
+  -  Papenmeier driver update.
+  -  EuroBraille driver fixes.
+  - Added CMD_CSRJMP_VERT command which routes the cursor to the current line
+    but does not try to change the horizontal position. Hopefully will be
+    useful in lynx!
+  -  Added attribute underlining/blinking functions (experimental).
+  -  Incorporated Papenmeier braille terminal driver + German manual.
+     Thanks to August Hörandl <hoerandl@elina.htlw1.ac.at> and his team!
+  -  Alva driver fixes.
+
+
+07 April 1998: BRLTTY 1.9.0 pre-release.
+  -  Added absolute toggles (ON/OFF) flags to function defines.
+  -  Moved braille tables and tools to the BrailleTables directory.
+  -  Added Eurobraille terminal series driver.
+  -  Now BRLTTY knows which virtual console is active and preserves separate
+     information structures for each of them.  This means preserving 
+     current window position, tracking and display modes as you switch
+     from a virtual console to another. 
+  -  Nicolas Pitre now assumes maintenance of BRLTTY since Nikhil Nair
+     seems to be unreachable for quite a long time.
+  -  Modularized screen drivers, added experimental screen driver to get
+     screen image from the "screen" program.  A patch for "screen" is provided
+     in the Patches directory.
+  -  Made all different methods of handling status cells available
+     within the configuration menu.  The default is determined by
+     the compiled in braille driver.
+  -  Incorporated patch from Joerg Korinek <korinek@ira.uka.de> which lets
+     the main BRLTTY process continue while cursor routing is in progress.
+     Thanks!
+  -  Added some new functions to scr.cc, which add a new `thread' of
+     screen reading.  This is used by csrjmp_sub(), to avoid race conditions.
+     [The problem was that a forked subprocess shares filedescriptors with
+     the parent, and `clone (0, SIGCHLD | COPYVM | COPYFD)' (as documented
+     in clone(2)) wouldn't compile.]
+  -  CURSOR ROUTING NOW APPEARS TO BE STABLE!!!
+  -  BRLTTY placed under CVS revision control - Id headers added to many
+     files.
+  -  Cursor routing timeout made an idle timeout of 2 seconds: routing
+     continues until the cursor is idle for this length of time.
+  -  Added CMD_SKPIDLNS.
+  -  Speech support modularised.  Added speech.h, as well as SPK_TARGET in
+     the Makefile and corresponding directories.
+  -  Added very simple speech interface and few speech drivers.
+  -  Status cells standardised: two types (Alva/Tieman) or none, switch in
+     configuration menu.  Hence another new config file format :-(.
+  -  A few bugfixes.
+  -  Some job control finetuning in csrjmp() and csrjmp_sub() - now use
+     SIGUSR1 to kill child process before another child is forked; child
+     blocks this signal for most of the time.
+  -  Added support for (optional) Makefiles in driver subdirectories.
+
+
+17 September 1996: BRLTTY 1.0.2 released.
+  -  Incorporated skipping of identical lines.  Adapted to make a line
+     containing the cursor different to any other.
+  -  Config file format updated, so old config files won't work any more.
+  -  Some tidying up of the archive and of the documentation.
+
+
+13 September 1996: BRLTTY 1.0.1 released.
+  -  Speech support added for CombiBraille.
+  -  Documentation brought up to date.
+
+
+3 September 1996: prerelease of BRLTTY 1.0.1.
+  -  Screen reading library rewritten from scratch, in C++ (but without
+     using any class libraries, so no extra shared libraries are needed).
+     In particular, the dependence on the Linux vcsa format is gone: there
+     is a new help file format which is far more compact than the old one
+     (txt2hlp replaces txt2scrn, brlttydev.hlp replaces brlttyhelp.scr),
+     and freeze mode works properly, by using a dynamically allocated
+     memory buffer instead of the old /tmp/vcsa.frz hack.
+  -  BRLTTY can now survive as a stand-alone executable, with absolutely no
+     data files - even if /etc/brltty/ is not found.  This is done by
+     compiling in the default text translation table (now selected in the
+     Makefile) and the attribute translation table.  The compilation is done
+     by using the new filter comptable to generate text.auto.h and
+     attrib.auto.h.  A text translation table is now only loaded if the
+     -t option is specified.
+  -  -q|--quiet now surpresses the start-up message on the Braille display.
+
+  (Documentation still to be updated ...)
+     
+
+28 July 1996: BRLTTY 1.0 released.
diff --git a/Documents/HISTORY b/Documents/HISTORY
new file mode 100644
index 0000000..786a262
--- /dev/null
+++ b/Documents/HISTORY
@@ -0,0 +1,36 @@
+The BRLTTY project started in July, 1995. The initial team consisted of Nikhil
+Nair and James Bowden.
+
+The first version ran with Blazie Engineering's Braille Lite. Since, at that
+time, the Braille Lite wasn't designed to be a dedicated refreshable braille
+display for a computer, its response time was far too slow. This situation has
+now been corrected.
+
+The second version, BRLTTY-0.22-BETA, was released in September of 1995. It was
+the first to be released publicly. As well as the Braille Lite, it also
+supported Tieman B.V.'s CombiBraille series.
+
+James Bowden stopped being an active developer, although his continued help in
+other areas (including documentation) was much appreciated. Two new members,
+Nicolas Pitre and Stéphane Doyon, joined the team. They added support for Alva
+and Telesensory Systems Inc. displays, as well as many excellent features for
+the system as a whole.
+
+A stable version (1.0) was released sometime around the end of 1996.
+
+Support for Papenmeier displays was contributed by a team from The Technical
+High School, Department of Electrical Engineering, Vienna, Austria. Support for
+the TSI displays was completed, and support for the EuroBraille brand was
+added. New features were also continually being added to the system.
+
+Regretably, Nikhil Nair stopped working on the BRLTTY project. Nicolas Pitre
+assumed the job of maintainer.
+
+Version 2.0 was released during the summer of 1998, and version 2.1 was
+released in March of 1999. It added support for EcoBraille (thanks to Oscar
+Fernandez), Alva Delphi (thanks to Terry Barnaby), and Braille Lite 18 (from
+Nikhil Nair), as well as Some small improvements and fixes.
+
+Dave Mielke began to submit fixes and enhancements during 2000,
+and joined the team as the next maintainer in June of 2001.
+
diff --git a/Documents/Makefile.in b/Documents/Makefile.in
new file mode 100644
index 0000000..e82b92d
--- /dev/null
+++ b/Documents/Makefile.in
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+clean distclean::
+	for directory in $(MANUAL_DIRECTORIES); do (cd $(BLD_TOP)$$directory && $(MAKE) $@) || exit 1; done
+
+all:: $(ALL_API)
+	for directory in $(MANUAL_DIRECTORIES); do (cd $(BLD_TOP)$$directory && $(MAKE) $@-@can_make_manual@) || exit 1; done
+
+all-api man3: BrlAPIref-@can_make_BrlAPIref@
+
+BrlAPIref-no:
+	@echo doxygen is not installed - BrlAPI reference documentation will not be made
+
+BrlAPIref-yes: BrlAPIref.made
+BrlAPIref.made: BrlAPIref $(BLD_TOP)$(PGM_DIR)/brlapi.h $(BLD_TOP)$(PGM_DIR)/brlapi_constants.h $(SRC_TOP)$(PGM_DIR)/brlapi_keycodes.h $(SRC_TOP)$(PGM_DIR)/brlapi_param.h $(SRC_TOP)$(PGM_DIR)/brlapi_protocol.h BrlAPIref.doxy
+	$(DOXYGEN) BrlAPIref.doxy
+	touch $@
+
+BrlAPIref:
+	mkdir BrlAPIref
+
+%.h:
+	cd $(@D) && $(MAKE) $(@F)
+
+%.html: $(SRC_DIR)/README.%
+	rst2html --config "$(SRC_TOP)docutils.conf" $< $@
+
+%.txt: $(SRC_DIR)/README.%
+	rst2txt --config "$(SRC_TOP)docutils.conf" $< $@
+
+clean::
+	-rm -f *.made *.html
+	-rm -f -r BrlAPIref
+
+distclean::
+	-rm -f brltty.conf *.1 BrlAPIref.doxy
+
diff --git a/Documents/Manual-BRLTTY/English/BRLTTY.sgml b/Documents/Manual-BRLTTY/English/BRLTTY.sgml
new file mode 100644
index 0000000..2352d89
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/BRLTTY.sgml
@@ -0,0 +1,5899 @@
+<!doctype linuxdoc system [
+
+<!ENTITY DriverCodes SYSTEM "driver-codes.sgml">
+<!ENTITY BrailleDrivers SYSTEM "braille-drivers.sgml">
+<!ENTITY SpeechDrivers SYSTEM "speech-drivers.sgml">
+<!ENTITY TextTables SYSTEM "text-tables.sgml">
+<!ENTITY ContractionTables SYSTEM "contraction-tables.sgml">
+
+]>
+<article>
+
+<titlepag>
+  <title>BRLTTY Reference Manual
+    <subtitle>Access to the Console Screen for Blind Persons using Refreshable Braille Displays
+  <author>
+    <name>Nikhil Nair <tt><htmlurl url="mailto:nn201@cus.cam.ac.uk" name="&lt;nn201@cus.cam.ac.uk&gt;"></tt>
+  <and>
+    <name>Nicolas Pitre <tt><htmlurl url="mailto:nico@fluxnic.net" name="&lt;nico@fluxnic.net&gt;"></tt>
+  <and>
+    <name>Stéphane Doyon <tt><htmlurl url="mailto:s.doyon@videotron.ca" name="&lt;s.doyon@videotron.ca&gt;"></tt>
+  <and>
+    <name>Dave Mielke <tt><htmlurl url="mailto:dave@mielke.cc" name="&lt;dave@mielke.cc&gt;"></tt>
+  <date>Version 6.6, July 2023
+  <abstract>
+    Copyright &copy; 1995-2023 by The BRLTTY Developers.
+    BRLTTY is free software,
+    and comes with ABSOLUTELY NO WARRANTY.
+    It is placed under the terms of version 2 or later of
+    <bf/The GNU General Public License/
+    as published by
+    <bf/The Free Software Foundation/.
+  </abstract>
+</titlepag>
+
+<toc>
+<lof>
+<lot>
+
+<sect>Formalities<p>
+
+<sect1>License<p>
+This program is free software.
+You may redistribute it and/or modify it under the terms of
+<htmlurl url="http://www.gnu.org/licenses/licenses.html#LGPL" name="The GNU Lesser General Public License">
+as published by <htmlurl url="http://www.gnu.org/fsf/fsf.html" name="The Free Software Foundation">.
+Version 2.1 (or any later version) of the license may be used.
+
+You should have received a copy of the license along with this program.
+It should be in the file <tt/LICENSE-LGPL/ in the top-level directory.
+If not, write to the Free Software Foundation Inc.,
+675 Mass Ave, Cambridge, MA 02139, USA.
+
+<sect1>Disclaimer<p>
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY - not even
+the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the <bf/GNU General Public License/ for more details.
+
+<sect1>Contact Information<label id="contact"><p>
+BRLTTY represents the work of a team.
+For up-to-date information, see BRLTTY's web page at
+[<htmlurl url="http://brltty.app/" name="http://brltty.app/">].
+As of this writing, the team includes:
+<itemize>
+  <item>
+    Dave Mielke (maintainer, active)
+    <descrip>
+      <tag/Web/<htmlurl url="http://mielke.cc/" name="http://mielke.cc/">
+      <tag/E-Mail/<htmlurl url="mailto:dave@mielke.cc" name="&lt;dave@mielke.cc&gt;">
+    </descrip>
+  <item>
+    Samuel Thibault (active)
+    <descrip>
+      <tag/Web/<htmlurl url="http://dept-info.labri.fr/~thibault/" name="http://dept-info.labri.fr/~thibault/">
+      <tag/E-Mail/<htmlurl url="mailto:samuel.thibault@ens-lyon.org" name="&lt;samuel.thibault@ens-lyon.org&gt;">
+    </descrip>
+  <item>
+    Mario Lang (active)
+    <descrip>
+      <tag/Web/<htmlurl url="http://delysid.org/" name="http://delysid.org/">
+      <tag/E-Mail/<htmlurl url="mailto:mlang@delysid.org" name="&lt;mlang@delysid.org&gt;">
+    </descrip>
+  <item>
+    Nicolas Pitre
+    <descrip>
+      <tag/Web/<htmlurl url="http://www.fluxnic.net/" name="http://www.fluxnic.net/">
+      <tag/E-Mail/<htmlurl url="mailto:nico@fluxnic.net" name="&lt;nico@fluxnic.net&gt;">
+    </descrip>
+  <item>
+    Stéphane Doyon
+    <descrip>
+      <tag/Web/<htmlurl url="http://pages.infinit.net/sdoyon/" name="http://pages.infinit.net/sdoyon/">
+      <tag/E-Mail/<htmlurl url="mailto:s.doyon@videotron.ca" name="&lt;s.doyon@videotron.ca&gt;">
+    </descrip>
+  <item>
+    Nikhil Nair  (author)
+    <descrip>
+      <tag/E-Mail/<htmlurl url="mailto:nn201@cus.cam.ac.uk" name="&lt;nn201@cus.cam.ac.uk&gt;">
+    </descrip>
+</itemize>
+
+Questions, comments, suggestions, criticisms, and contributions are all welcome.
+Even though our e-mail addresses are listed above,
+the best way to contact us is via BRLTTY's mailing list.
+You can post to the list by sending e-mail to
+<htmlurl url="mailto:BRLTTY@mielke.cc" name="&lt;BRLTTY@mielke.cc&gt;">.
+If you aren't subscribed to the list
+then your posts will be held for moderator approval.
+To subscribe, unsubscribe, change settings, view archives, etc,
+go to the list's information page at
+<htmlurl url="http://mielke.cc/mailman/listinfo/brltty" name="http://mielke.cc/mailman/listinfo/brltty">.
+
+<sect>Introduction<p>
+BRLTTY gives a braille user access to the text consoles of a Linux/Unix system.
+It runs as a background process (daemon)
+which operates a refreshable braille display,
+and can be started very early in the system boot sequence.
+It enables a braille user, therefore, to easily independently handle
+aspects of system administration such as
+single user mode entry,
+file system recovery,
+and boot problem analysis.
+It even greatly eases such routine tasks as logging in.
+
+BRLTTY reproduces a rectangular portion of the screen
+(referred to within this document as `the window')
+as braille text on the display.
+Controls on the display can be used to
+move the window around on the screen,
+to enable and disable various viewing options,
+and to perform special functions.
+
+<sect1>Feature Summary<p>
+BRLTTY provides the following capabilities:
+<itemize>
+  <item>
+    Full implementation of the usual screen review facilities.
+  <item>
+    Choice between <tt/block/, <tt/underline/, or <tt/no/ cursor.
+  <item>
+    Optional <tt/underline/ to indicate specially highlighted text.
+  <item>
+    Optional use of <tt/blinking/ (rates individually settable) for
+    cursor, special highlighting underline, and/or capital letters.
+  <item>
+    Screen freezing for leisurely review.
+  <item>
+    Intelligent cursor routing, allowing easy fetching of cursor within
+    text editors, web browsers, etc.,
+    without moving ones hands from the braille display.
+  <item>
+    A cut-and-paste function (linear or rectangular) which is particularly useful for
+    copying long file names,
+    copying text between virtual terminals,
+    entering complicated commands,
+    etc.
+  <item>
+    Table driven in-line contracted braille (English and French provided).
+  <item>
+    Support for multiple braille codes.
+  <item>
+    Ability to identify an unknown character.
+  <item>
+    Ability to inspect character highlighting.
+  <item>
+    An on-line help facility for braille display commands.
+  <item>
+    A preferences menu.
+  <item>
+    Basic speech support.
+  <item>
+    Modular design allowing relatively easy addition of
+    drivers for other braille displays and speech synthesizers.
+  <item>
+    An Application Programming Interface.
+</itemize>
+
+<sect1>System Requirements<p>
+To date, BRLTTY runs under Linux, Solaris, OpenBSD, FreeBSD, NetBSD, and Windows.
+While ports to other Unix-like operating systems aren't currently planned,
+we do welcome any interest in such projects.
+
+<descrip>
+  <tag/Linux/
+    This software has been tested on a variety of Linux systems:
+    <itemize>
+      <item>
+        Desktops, laptops, and some PDAs.
+      <item>
+        Processors from a 386SX20 to a Pentium.
+      <item>
+        A huge range of memory sizes.
+      <item>
+        Several distributions including Debian, Red Hat, Slackware, and SuSE.
+      <item>
+        Many kernels, including 1.2.13, 2.0, 2.2, and 2.4.
+    </itemize>
+  <tag/Solaris/
+    This software has been tested on the following Solaris systems:
+    <itemize>
+      <item>
+        The Sparc architecture (releases 7, 8, and 9).
+      <item>
+        The Intel architecture (release 9).
+    </itemize>
+  <tag/OpenBSD/
+    This software has been tested on the following OpenBSD systems:
+    <itemize>
+      <item>
+        The Intel architecture (release 3.4).
+    </itemize>
+  <tag/FreeBSD/
+    This software has been tested on the following FreeBSD systems:
+    <itemize>
+      <item>
+        The Intel architecture (release 5.1).
+    </itemize>
+  <tag/NetBSD/
+    This software has been tested on the following NetBSD systems:
+    <itemize>
+      <item>
+        The Intel architecture (release 1.6).
+    </itemize>
+  <tag/Windows/
+    This software has been tested on Windows 95, 98, and XP.
+</descrip>
+
+On Linux, BRLTTY can inspect the content of the screen
+completely independently of any logged in user.
+It does this by using a special device
+which provides easy access to the contents of the current virtual console.  
+This device was introduced in version 1.1.92 of the Linux kernel,
+and is normally called either <tt>/dev/vcsa</> or <tt>/dev/vcsa0</>
+(on systems with <tt/devfs/ it's called <tt>/dev/vcc/a</>).
+For this reason, Linux kernel 1.1.92 or later is required
+if BRLTTY is to be used in this way.
+This capability:
+<itemize>
+  <item>
+    Allows BRLTTY to be started very early in the system boot sequence.
+  <item>
+    Enables the braille display to be fully operational during the login prompt.
+  <item>
+    Makes it much easier for a braille user to perform boot-time system administration tasks.
+</itemize>
+
+A patch for the <tt/screen/ program is provided
+(see the <tt/Patches/ subdirectory).
+It allows BRLTTY to access <tt/screen/'s screen image via shared memory,
+and, therefore, allows BRLTTY to be used quite effectively on platforms
+which don't have their own screen content inspection facility.
+The main weakness of the <tt/screen/ approach is that 
+BRLTTY can't be started until the user has logged in.
+
+BRLTTY only works with text-based consoles and applications.
+It can be used with <tt/curses/-based applications,
+but not with any application which
+either uses special VGA features
+or requires a graphics console (like the X Window system).
+
+You must also, of course, possess a supported refreshable braille display
+(see section <ref id="displays" name="Supported Braille Displays"> for the complete list).
+We hope that additional displays will be supported in the future, so,
+if you have any vaguely technical programming information
+for a device which you'd like to see supported,
+then please let us know (see section <ref id="contact" name="Contact Information">).
+
+Finally, you need tools to build the executable from its source:
+<tt/make/, <tt/C/ and <tt/C++/ compilers, <tt/yacc/, <tt/awk/, etc.
+The development tools provided with standard Unix distributions should suffice.
+If you have problems,
+then contact us and we'll compile a binary for you.
+
+<sect>The Build Procedure<p>
+BRLTTY can be downloaded from its web site
+(see section <ref id="contact" name="Contact Information"> for its location).
+All releases are provided as compressed <ref id="tar" name="tar balls">.
+Newer releases are also provided as <ref id="rpm" name="RPM"> (RedHat Package Manager) files.
+
+That tidbit of information has probably peaked your curiosity,
+and now you just can't wait to get started.
+It's a good idea, though,
+to first become familiar with the files which will ultimately be installed.
+
+<sect1>Installed File Hierarchy<label id="hierarchy"><p>
+The build procedure should result in the installation of the following files:
+<descrip>
+  <tag>/bin/</>
+    <descrip>
+      <tag>brltty</tag>
+        The BRLTTY program.
+      <tag><ref id="utility-brltty-install" name="brltty-install"></tag>
+        A utility for copying BRLTTY's
+        <ref id="hierarchy" name="installed file hierarchy">
+        from one location to another.
+      <tag><ref id="utility-brltty-config" name="brltty-config"></tag>
+        A utility which sets a number of environment variables to values
+        which reflect the current installation of BRLTTY.
+    </descrip>
+  <tag>/lib/</>
+    <descrip>
+      <tag/libbrlapi.a/
+        Static archive of the Application Programming Interface.
+      <tag/libbrlapi.so/
+        Dynamically loadable object for the Application Programming Interface.
+    </descrip>
+  <tag>/lib/brltty/</>
+    Your installation of BRLTTY may not have all of the following types of files.
+    They're only created as needed based on the build options you select
+    (see <ref id="build" name="Build Options">).
+    <descrip>
+      <tag/brltty-brl.lst/
+        A list of the braille display drivers which have been built
+        as dynamically loadable shared objects,
+        and, therefore, which can be selected at run-time.
+        Each line consists of
+        the two-letter identification code for a driver,
+        a tab character,
+        and a description of the braille display which that driver is for.
+      <tag>libbrlttyb<em/driver/.so.1</tag>
+        The dynamically loadable driver for a braille display,
+        where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">.
+      <tag/brltty-spk.lst/
+        A list of the speech synthesizer drivers which have been built
+        as dynamically loadable shared objects,
+        and, therefore, which can be selected at run-time.
+        Each line consists of
+        the two-letter identification code for a driver,
+        a tab character,
+        and a description of the speech synthesizer which that driver is for.
+      <tag>libbrlttys<em/driver/.so.1</tag>
+        The dynamically loadable driver for a speech synthesizer,
+        where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">.
+    </descrip>
+  <tag>/lib/brltty/rw/</>
+    Files created at run-time, e.g. needed but missing system resources.
+  <tag>/etc/</>
+    <descrip>
+      <tag/brltty.conf/
+        System defaults for BRLTTY.
+      <tag/brlapi.key/
+        The access key for BrlAPI.
+    </descrip>
+  <tag>/etc/brltty/</>
+    Your installation of BRLTTY may not have all of the following types of files.
+    They're only created as needed based on the build options you select
+    (see <ref id="build" name="Build Options">).
+    <descrip>
+      <tag/*.conf/
+        Driver-specific configuration data.
+        Their names look more or less like <tt/brltty-/<em/driver/<tt/.conf/,
+        where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">.
+      <tag/*.atb/
+        Attributes tables
+        (see section <ref id="table-attributes" name="Attributes Tables"> for details).
+        Their names look like <em/name/<tt/.atb/.
+      <tag/*.ati/
+        Include files for attributes tables.
+      <tag/*.ctb/
+        Contraction tables
+        (see section <ref id="table-contraction" name="Contraction Tables"> for details).
+        Their names look like <em/language/<tt/-/<em/country/<tt/-/<em/level/<tt/.ctb/.
+      <tag/*.cti/
+        Include files for contraction tables.
+      <tag/*.ktb/
+        Key tables
+        (see section <ref id="table-key" name="Key Tables"> for details).
+        Their names look like <em/name/<tt/.ktb/.
+      <tag/*.kti/
+        Include files for key tables.
+      <tag/*.ttb/
+        Text tables
+        (see section <ref id="table-text" name="Text Tables"> for details).
+        Their names look like <em/language/<tt/.ttb/.
+      <tag/*.tti/
+        Include files for text tables.
+      <tag/*.hlp/
+        Driver-specific help pages.
+        Their names look more or less like <tt/brltty-/<em/driver/<tt/.hlp/,
+        where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">.
+    </descrip>
+  <tag>/var/lib/BrlAPI/</>
+    Local sockets for connecting to the Application Programming Interface.
+  <tag>/include/</>
+    C header files for the Application Programming Interface.
+    Their names look like <tt/brlapi-/<em/function/<tt/.h/.
+    The main header is <tt/brlapi.h/.
+  <tag>/include/brltty/</>
+    C header files for accessing braille hardware.
+    Their names look like <tt/brldefs-/<em/driver/<tt/.h/
+    (where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">).
+    The headers <tt/brldefs.h/ and <tt/api.h/ are provided
+    for backward compatibility and shouldn't be used.
+  <tag>/man/</>
+    Man pages.
+    <descrip>
+      <tag>man1/<em/name/.1</tag>
+        Man pages for BRLTTY-related user commands.
+      <tag>man3/<em/name/.3</tag>
+        Man pages for Application Programming Interface library routines.
+    </descrip>
+</descrip>
+
+Some optional files which you should be aware of,
+although they aren't part of the installed file hierarchy, are:
+<descrip>
+  <tag>/etc/brltty.conf</tag>
+    The system defaults configuration file.
+    It's created by the system administrator.
+    See <ref id="configure" name="The Configuration File"> for details.
+  <tag>/etc/brltty-<em/driver/.prefs</tag>
+    The saved preferences settings file
+    (<em/driver/ is a two-letter <ref id="drivers" name="driver identification code">).
+    It's created by the <ref id="command-PREFSAVE" name="PREFSAVE"> command.
+    See <ref id="preferences" name="Preferences Settings"> for details.
+</descrip>
+
+<sect1>Installing from a TAR Ball<label id="tar"><p>
+Here's what to do if you just want to install BRLTTY as quickly as possible,
+trusting that all of our defaults are correct.
+<enum>
+  <item>
+    Download the source.
+    It'll be a file named
+    <tt/brltty-/<em/release/<tt/.tar.gz/,
+    e.g. <tt/brltty-3.0.tar.gz/.
+  <item>
+    Unpack the source into its native hierarchical structure.
+    <tscreen>tar xzf brltty-<em/release/.tar.gz</tscreen>
+    This should create the directory <tt/brltty-/<em/release/.
+  <item>
+    Change to the source directory, configure, compile, and install BRLTTY.
+    <tscreen>
+      cd brltty-<em/release/
+      <newline>
+      ./configure
+      <newline>
+      make install
+    </tscreen>
+    This should be done as <bf/root/.
+</enum>
+
+To uninstall BRLTTY, do:
+<tscreen>
+  cd brltty-<em/release/
+  <newline>
+  make uninstall
+</tscreen>
+
+That's all there's to it.
+Now, for those who really want to know what's going on, here are the details.
+
+<sect2>Build Options<label id="build"><p>
+The first step in building BRLTTY is to configure it
+for your system and/or for your personal needs.
+This is done by running the <tt/configure/ script in BRLTTY's top-level directory.
+We've tried to make the defaults fit the most common case, so,
+assuming that you're not attempting to do anything out of the ordinary,
+you may not need to do anything more complicated than
+invoke this script without specifying any options at all.
+<tscreen>./configure</tscreen>
+If, however, you have some special requirements,
+or even if you're just adventurous,
+you should find out what your choices are.
+<tscreen>./configure --help</tscreen>
+You should also check out the <tt/README/ file in the subdirectory
+containing the driver for your braille display
+for any additional display-specific instructions.
+
+<sect3>System Defaults<label id="build-defaults"><p>
+<descrip>
+  <tag><tt/--with-braille-driver=/<em/driver/<label id="build-braille-driver"></tag>
+    Specify the braille display drivers
+    which are to be linked into the BRLTTY binary.
+    Those drivers which aren't listed via this option
+    are built as dynamically loadable shared objects
+    and can still be selected at run-time.
+    Each driver must be identified
+    either by its two-letter <ref id="drivers" name="driver identification code">
+    or by its proper name (full or abbreviated).
+    The driver identifiers must be
+    separated from one another by a single comma.
+    If a driver identifier is prefixed by a minus sign (<tt/-/),
+    then that driver is excluded from the build.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/all/
+        Link all of the drivers into the binary.
+        Don't build any of them as dynamically loadable shared objects.
+        This word may also be specified as the final element of a driver list.
+        This is how to specify the default driver when all the drivers are to be linked in.
+      <tag/-all/
+        Only build those drivers which have been explicitly included via this option.
+      <tag/no/
+        Don't build any drivers at all.
+        This is equivalent to specifying <tt/--without-braille-driver/.
+      <tag/yes/
+        Build all of the drivers as dynamically loadable shared objects.
+        Don't link any of them into the binary.
+        This is equivalent to specifying <tt/--with-braille-driver/.
+    </descrip>
+    See the <ref id="configure-braille-driver" name="braille-driver"> configuration file directive
+    and the <ref id="options-braille-driver" name="-b"> command line option
+    for run-time selection.
+  <tag><tt/--with-braille-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="build-braille-parameters"></tag>
+    Specify the default parameter settings for the braille display drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-braille-parameters" name="braille-parameters"> configuration file directive
+    and the <ref id="options-braille-parameters" name="-B"> command line option
+    for run-time selection.
+  <tag><tt/--with-braille-device=/<em/device/<tt/,/...<label id="build-braille-device"></tag>
+    Specify the default device to which the braille display is connected
+    (see section <ref id="operand-braille-device" name="Braille Device Specification">).
+    If this option isn't specified then
+    <tt/usb:/ is assumed if USB support is available,
+    <tt/bluetooth:/ is assumed if Bluetooth support is available,
+    and <tt/usb:,bluetooth:/ is assumed if both are available.
+    If neither USB nor Bluetooth support is available then
+    an operating system appropriate path
+    for the primary (first) serial port (device) is assumed.
+    See the <ref id="configure-braille-device" name="braille-device"> configuration file directive
+    and the <ref id="options-braille-device" name="-d"> command line option
+    for run-time selection.
+  <tag><tt/--with-libbraille=/<em/directory/<label id="build-libbraille"></tag>
+    Specify the installed location of the Libbraille package,
+    and build the Libbraille braille display driver
+    (see <ref id="restrictions-libbraille" name="Build Restrictions">).
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-libbraille/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/Libbraille</>,
+        <tt>/usr/local/libbraille</>,
+        <tt>/opt/Libbraille</>,
+        or <tt>/opt/libbraille</>.
+        This is equivalent to specifying <tt/--with-libbraille/.
+    </descrip>
+  <tag><tt/--with-text-table=/<em/file/<label id="build-text-table"></tag>
+    Specify the built-in (fallback) text table
+    (see section <ref id="table-text" name="Text Tables"> for details).
+    The specified table is linked into the BRLTTY binary, and is used
+    either if locale-based autoselection fails
+    or if the requested table can't be loaded.
+    The absolute path to a table outside the source tree may be specified.
+    The <tt/.ttb/ extension is optional.
+    If this option isn't specified,
+    then <tt/en-nabcc/,
+    a commonly (in North America) used 8-dot variant of the
+    <ref id="nabcc" name="North American Braille Computer Code">,
+    is assumed.
+    See the <ref id="configure-text-table" name="text-table"> configuration file directive
+    and the <ref id="options-text-table" name="-t"> command line option
+    for run-time selection.
+    This setting can be changed with the
+    <ref id="preference-text-table" name="Text Table"> preference.
+  <tag><tt/--with-attributes-table=/<em/file/<label id="build-attributes-table"></tag>
+    Specify the built-in (fallback) attributes table
+    (see section <ref id="table-attributes" name="Attributes Translation"> for details).
+    The specified table is linked into the BRLTTY binary, and is used
+    if the requested table can't be loaded.
+    The absolute path to a table outside the source tree may be specified.
+    The <tt/.atb/ extension is optional.
+    If this option isn't specified,
+    then <tt/left_right/ is assumed.
+    Change it to <tt/invleft_right/ if you'd like it done the old way.
+    See the <ref id="configure-attributes-table" name="attributes-table"> configuration file directive
+    and the <ref id="options-attributes-table" name="-a"> command line option
+    for run-time selection.
+    This setting can be changed with the
+    <ref id="preference-attributes-table" name="Attributes Table"> preference.
+  <tag><tt/--with-speech-driver=/<em/driver/<label id="build-speech-driver"></tag>
+    Specify the speech synthesizer drivers
+    which are to be linked into the BRLTTY binary.
+    Those drivers which aren't listed via this option
+    are built as dynamically loadable shared objects
+    and can still be selected at run-time.
+    Each driver must be identified
+    either by its two-letter <ref id="drivers" name="driver identification code">
+    or by its proper name (full or abbreviated).
+    The driver identifiers must be
+    separated from one another by a single comma.
+    If a driver identifier is prefixed by a minus sign (<tt/-/),
+    then that driver is excluded from the build.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/all/
+        Link all of the drivers into the binary.
+        Don't build any of them as dynamically loadable shared objects.
+        This word may also be specified as the final element of a driver list.
+        This is how to specify the default driver when all the drivers are to be linked in.
+      <tag/-all/
+        Only build those drivers which have been explicitly included via this option.
+      <tag/no/
+        Don't build any drivers at all.
+        This is equivalent to specifying <tt/--without-speech-driver/.
+      <tag/yes/
+        Build all of the drivers as dynamically loadable shared objects.
+        Don't link any of them into the binary.
+        This is equivalent to specifying <tt/--with-speech-driver/.
+    </descrip>
+    See the <ref id="configure-speech-driver" name="speech-driver"> configuration file directive
+    and the <ref id="options-speech-driver" name="-s"> command line option
+    for run-time selection.
+  <tag><tt/--with-speech-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="build-speech-parameters"></tag>
+    Specify the default parameter settings for the speech synthesizer drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-speech-parameters" name="speech-parameters"> configuration file directive
+    and the <ref id="options-speech-parameters" name="-S"> command line option
+    for run-time selection.
+  <tag><tt/--with-flite=/<em/directory/<label id="build-flite"></tag>
+    Specify the installed location of the FestivalLite text-to-speech package,
+    and build the FestivalLite speech synthesizer driver
+    (see <ref id="restrictions-flite" name="Build Restrictions">).
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-flite/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/FestivalLite</>,
+        <tt>/usr/local/flite</>,
+        <tt>/opt/FestivalLite</>,
+        or <tt>/opt/flite</>.
+        This is equivalent to specifying <tt/--with-flite/.
+    </descrip>
+  <tag><tt/--with-flite-language=/<em/language/<label id="build-flite-language"></tag>
+    Specify the language which the FestivalLite text to speech engine is to use.
+    The default language is <tt/usenglish/.
+  <tag><tt/--with-flite-lexicon=/<em/lexicon/<label id="build-flite-lexicon"></tag>
+    Specify the lexicon which the FestivalLite text to speech engine is to use.
+    The default lexicon is <tt/cmulex/.
+  <tag><tt/--with-flite-voice=/<em/voice/<label id="build-flite-voice"></tag>
+    Specify the voice which the FestivalLite text to speech engine is to use.
+    The default voice is <tt/cmu_us_kal16/.
+  <tag><tt/--with-mikropuhe=/<em/directory/<label id="build-mikropuhe"></tag>
+    Specify the installed location of the Mikropuhe text-to-speech package,
+    and build the Mikropuhe speech synthesizer driver
+    (see <ref id="restrictions-mikropuhe" name="Build Restrictions">).
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-mikropuhe/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/Mikropuhe</>,
+        <tt>/usr/local/mikropuhe</>,
+        <tt>/opt/Mikropuhe</>,
+        or <tt>/opt/mikropuhe</>.
+        This is equivalent to specifying <tt/--with-mikropuhe/.
+    </descrip>
+  <tag><tt/--with-speechd=/<em/directory/<label id="build-speechd"></tag>
+    Specify the installed location of the speech-dispatcher text-to-speech package,
+    and build the speech-dispatcher speech synthesizer driver.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-speechd/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/speech-dispatcher</>,
+        <tt>/usr/local/speechd</>,
+        <tt>/opt/speech-dispatcher</>,
+        or <tt>/opt/speechd</>.
+        This is equivalent to specifying <tt/--with-speechd/.
+    </descrip>
+  <tag><tt/--with-swift=/<em/directory/<label id="build-swift"></tag>
+    Specify the installed location of the Swift text-to-speech package,
+    and build the Swift speech synthesizer driver.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-swift/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/Swift</>,
+        <tt>/usr/local/swift</>,
+        <tt>/opt/Swift</>,
+        or <tt>/opt/swift</>.
+        This is equivalent to specifying <tt/--with-swift/.
+    </descrip>
+  <tag><tt/--with-theta=/<em/directory/<label id="build-theta"></tag>
+    Specify the installed location of the Theta text-to-speech package,
+    and build the Theta speech synthesizer driver
+    (see <ref id="restrictions-theta" name="Build Restrictions">).
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't build the driver.
+        This is equivalent to specifying <tt/--without-theta/.
+      <tag/yes/
+        Build the driver if the package can be found in
+        <tt>/usr</>,
+        <tt>/usr/local</>,
+        <tt>/usr/local/Theta</>,
+        <tt>/usr/local/theta</>,
+        <tt>/opt/Theta</>,
+        or <tt>/opt/theta</>.
+        This is equivalent to specifying <tt/--with-theta/.
+    </descrip>
+  <tag><tt/--with-screen-driver=/<em/driver/<label id="build-screen-driver"></tag>
+    Specify the screen drivers
+    which are to be linked into the BRLTTY binary.
+    Those drivers which aren't listed via this option
+    are built as dynamically loadable shared objects
+    and can still be selected at run-time.
+    Each driver must be identified
+    either by its two-letter driver identification code
+    (see section <ref id="screen" name="Supported Screen Drivers">)
+    or by its proper name (full or abbreviated).
+    The driver identifiers must be
+    separated from one another by a single comma.
+    If a driver identifier is prefixed by a minus sign (<tt/-/),
+    then that driver is excluded from the build.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/all/
+        Link all of the drivers into the binary.
+        Don't build any of them as dynamically loadable shared objects.
+        This word may also be specified as the final element of a driver list.
+        This is how to specify the default driver when all the drivers are to be linked in.
+      <tag/-all/
+        Only build those drivers which have been explicitly included via this option.
+      <tag/no/
+        Don't build any drivers at all.
+        This is equivalent to specifying <tt/--without-screen-driver/.
+      <tag/yes/
+        Build all of the drivers as dynamically loadable shared objects.
+        Don't link any of them into the binary.
+        This is equivalent to specifying <tt/--with-screen-driver/.
+    </descrip>
+    The first non-excluded driver becomes the default driver.
+    If this option isn't specified,
+    or if no driver is specifically included,
+    then an operating system appropriate default is selected.
+    If a native driver for the current operating system is available,
+    then that driver is selected;
+    if not, then <tt/sc/ is selected.
+    See the <ref id="configure-screen-driver" name="screen-driver"> configuration file directive
+    and the <ref id="options-screen-driver" name="-x"> command line option
+    for run-time selection.
+  <tag><tt/--with-screen-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="build-screen-parameters"></tag>
+    Specify the default parameter settings for the screen drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="screen" name="Supported Screen Drivers">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-screen-parameters" name="screen-parameters"> configuration file directive
+    and the <ref id="options-screen-parameters" name="-X"> command line option
+    for run-time selection.
+  <tag><tt/--with-usb-package=/<em/package/<tt/,/...<label id="build-usb-package"></tag>
+    Specify the package which is to be used for USB I/O.
+    The package names must be separated from one another by a single comma,
+    and are processed from left to right.
+    The first one which is installed on the system is selected.
+    The following packages are supported:
+    <enum>
+      <item>libusb
+      <item>libusb-1.0
+    </enum>
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't support USB I/O.
+        This is equivalent to specifying <tt/--without-usb-package/.
+      <tag/yes/
+        Use native support for USB I/O.
+        If native support isn't available for the current platform
+        then use the first available supported package
+        (as per the order specified above).
+        This is equivalent to specifying <tt/--with-usb-package/.
+    </descrip>
+  <tag><tt/--with-bluetooth-package=/<em/package/<tt/,/...<label id="build-bluetooth-package"></tag>
+    Specify the package which is to be used for Bluetooth I/O.
+    The package names must be separated from one another by a single comma,
+    and are processed from left to right.
+    The first one which is installed on the system is selected.
+    The following packages are supported:
+    <enum>
+      <item>(no packages are currently supported)
+    </enum>
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't support Bluetooth I/O.
+        This is equivalent to specifying <tt/--without-bluetooth-package/.
+      <tag/yes/
+        Use native support for Bluetooth I/O.
+        If native support isn't available for the current platform
+        then use the first available supported package
+        (as per the order specified above).
+        This is equivalent to specifying <tt/--with-bluetooth-package/.
+    </descrip>
+</descrip>
+
+<sect3>Directory Specification<label id="build-directoreis"><p>
+<descrip>
+  <tag><tt/--with-execute-root=/<em/directory/<label id="build-execute-root"></tag>
+    Specify the directory at which the
+    <ref id="hierarchy" name="installed file hierarchy">
+    is to be rooted at run-time.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the system's root directory is assumed.
+    Use this option if you need to install
+    BRLTTY's run-time files in a non-standard location.
+    You need to use this feature, for example, if you'd like to have
+    more than one version of BRLTTY installed at the same time
+    (see section <ref id="multiple" name="Installing Multiple Versions">
+    for an example of how to do this).
+  <tag><tt/--with-install-root=/<em/directory/<label id="build-install-root"></tag>
+    Specify the directory beneath which the
+    <ref id="hierarchy" name="installed file hierarchy">
+    is to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the run-time package root
+    (see the <ref id="build-execute-root" name="--with-execute-root"> build option)
+    is assumed.
+    This directory is only used by
+    <ref id="make-install" name="make install">
+    and <ref id="make-uninstall" name="make uninstall">.
+    Use this option if you need to install BRLTTY in a different location
+    than the one from which it'll ultimately be executed.
+    You need to use this feature, for example,
+    if you're building BRLTTY on one system for use on another.
+  <tag><tt/--prefix=/<em/directory/<label id="build-portable-root"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the default directories for the architecture-independent files
+    are to be rooted.
+    These directories include:
+    <itemize>
+      <item>the <ref id="build-writable-directory" name="writable directory">
+      <item>the <ref id="build-data-directory" name="data directory">
+      <item>the <ref id="build-configuration-directory" name="configuration directory">
+      <item>the <ref id="build-manpage-directory" name="manpage directory">
+      <item>the <ref id="build-include-directory" name="include directory">
+    </itemize>
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the system's root directory is assumed.
+    This directory is rooted at the directory specified by the
+    <ref id="build-execute-root" name="--with-execute-root"> build option.
+  <tag><tt/--exec-prefix=/<em/directory/<label id="build-architecture-root"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the default directories for the architecture-dependent files
+    are to be rooted.
+    These directories include:
+    <itemize>
+      <item>the <ref id="build-program-directory" name="program directory">
+      <item>the <ref id="build-library-directory" name="library directory">
+      <item>the <ref id="build-api-directory" name="API directory">
+    </itemize>
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the directory specified via the
+    <ref id="build-portable-root" name="--prefix">
+    build option is assumed.
+    This directory is rooted at the directory specified by the
+    <ref id="build-execute-root" name="--with-execute-root"> build option.
+  <tag><tt/--libdir=/<em/directory/<label id="build-api-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the static archive and the dynamically loadable object
+    for the Application Programming Interface are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the directory
+    specified via the standard configure option <tt/--libdir/
+    (which defaults to <tt>/lib</> rooted at the directory specified by the
+    <ref id="build-architecture-root" name="--exec-prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--sysconfdir=/<em/directory/<label id="build-configuration-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the configuration files are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the directory
+    specified via the standard configure option <tt/--sysconfdir/
+    (which defaults to <tt>/etc</> rooted at the directory specified by the
+    <ref id="build-portable-root" name="--prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-program-directory=/<em/directory/<label id="build-program-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the runnable programs (binaries, executables) are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the directory
+    specified via the standard configure option <tt/--bindir/
+    (which defaults to <tt>/bin</> rooted at the directory specified by the
+    <ref id="build-architecture-root" name="--exec-prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-library-directory=/<em/directory/<label id="build-library-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the drivers and other architecture-dependent files are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the <tt/brltty/ subdirectory of the directory
+    specified via the standard configure option <tt/--libdir/
+    (which defaults to <tt>/lib</> rooted at the directory specified by the
+    <ref id="build-architecture-root" name="--exec-prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-writable-directory=/<em/directory/<label id="build-writable-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    which may be written to.
+    The absolute path should be supplied.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Don't define a writable directory.
+        This is equivalent to specifying <tt/--without-writable-directory/.
+      <tag/yes/
+        Use the default location.
+        This is equivalent to specifying <tt/--with-writable-directory/.
+    </descrip>
+    If this option isn't specified,
+    then the <tt>rw</> subdirectory of the directory specified via the
+    <ref id="build-library-directory" name="--with-library-directory"> build option
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-data-directory=/<em/directory/<label id="build-data-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the tables, help pages,
+    and other architecture-independent files are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the <tt/brltty/ subdirectory of the directory
+    specified via the standard configure option <tt/--sysconfdir/
+    (which defaults to <tt>/etc</> rooted at the directory specified by the
+    <ref id="build-portable-root" name="--prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-manpage-directory=/<em/directory/<label id="build-manpage-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the man pages are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the directory
+    specified via the standard configure option <tt/--mandir/
+    (which defaults to <tt>/man</> rooted at the directory specified by the
+    <ref id="build-portable-root" name="--prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+  <tag><tt/--with-include-directory=/<em/directory/<label id="build-include-directory"></tag>
+    Specify the directory within the
+    <ref id="hierarchy" name="installed file hierarchy">
+    where the C header files for the Application Programming Interface
+    are to be installed.
+    The absolute path should be supplied.
+    If this option isn't specified,
+    then the <tt/brltty/ subdirectory of the directory
+    specified via the standard configure option <tt/--includedir/
+    (which defaults to <tt>/include</> rooted at the directory specified by the
+    <ref id="build-portable-root" name="--prefix"> build option)
+    is assumed.
+    The directory is created if it doesn't exist.
+</descrip>
+
+<sect3>Build Features<label id="build-features"><p>
+These options are primarily useful when building BRLTTY for use on a boot disk.
+<descrip>
+  <tag><tt/--enable-standalone-programs/<label id="build-standalone-programs"></tag>
+    Create statically linked, rather than dynamically linked, programs.
+    This option removes all dependencies on shared objects at run-time.
+    Only the default drivers (see the
+    <ref id="build-braille-driver" name="--with-braille-driver">,
+    <ref id="build-speech-driver" name="--with-speech-driver">,
+    and <ref id="build-screen-driver" name="--with-screen-driver">
+    build options) are compiled.
+  <tag><tt/--enable-relocatable-install/<label id="build-relocatable-install"></tag>
+    If this feature is enabled then all internal paths
+    are recalculated to be relative to the program directory.
+    If it's disabled then all internal paths are absolute.
+    This feature allows the entire installed file hierarchy
+    to be copied or moved, in tact, from one place to another,
+    and is primarily intended for use on Windows platforms.
+  <tag><tt/--disable-stripping/<label id="build-stripping"></tag>
+    Don't remove the symbol tables from executables and shared objects when installing them.
+  <tag><tt/--disable-learn-mode/<label id="build-learn-mode"></tag>
+    Reduce program size by excluding command learn mode
+    (see section <ref id="learn" name="Command Learn Mode">).
+  <tag><tt/--disable-contracted-braille/<label id="build-contracted-braille"></tag>
+    Reduce program size by excluding support for contracted braille
+    (see section <ref id="table-contraction" name="Contraction Tables">).
+  <tag><tt/--disable-speech-support/<label id="build-speech-support"></tag>
+    Reduce program size by excluding support for speech synthesizers.
+  <tag><tt/--disable-iconv/<label id="build-iconv"></tag>
+    Reduce program size by excluding support for
+    character set conversion.
+  <tag><tt/--disable-icu/<label id="build-icu"></tag>
+    Reduce program size by excluding support for
+    Unicode-based internationalization.
+  <tag><tt/--disable-x/<label id="build-x"></tag>
+    Reduce program size by excluding support for
+    X11.
+  <tag><tt/--disable-beeper-support/<label id="build-beeper-support"></tag>
+    Reduce program size by excluding support for
+    the console tone generator.
+  <tag><tt/--disable-pcm-support/<label id="build-pcm-support"></tag>
+    Reduce program size by excluding support for
+    the digital audio interface on the sound card.
+  <tag><tt/--enable-pcm-support=/<em/interface/</tag>
+    If a platform provides more than one digital audio interface
+    then the one which is to be used may be specified.
+    <table loc="h">
+      <tabular ca="lll">
+        Platform|Interface|Description@<hline>
+        Linux
+          |oss|Open Sound System@
+          |alsa|Advanced Linux Sound Architecture@
+      </tabular>
+    </table>
+  <tag><tt/--disable-midi-support/<label id="build-midi-support"></tag>
+    Reduce program size by excluding support for
+    the Musical Instrument Digital Interface of the sound card.
+  <tag><tt/--enable-midi-support=/<em/interface/</tag>
+    If a platform provides more than one Musical Instrument Digital Interface
+    then the one which is to be used may be specified.
+    <table loc="h">
+      <tabular ca="lll">
+        Platform|Interface|Description@<hline>
+        Linux
+          |oss|Open Sound System@
+          |alsa|Advanced Linux Sound Architecture@
+      </tabular>
+    </table>
+  <tag><tt/--disable-fm-support/<label id="build-fm-support"></tag>
+    Reduce program size by excluding support for
+    the FM synthesizer on an AdLib, OPL3, Sound Blaster, or equivalent sound card.
+  <tag><tt/--disable-pm-configfile/<label id="build-pm-configfile"></tag>
+    Reduce program size by excluding support for
+    the Papenmeier driver's configuration file.
+  <tag><tt/--disable-gpm/<label id="build-gpm"></tag>
+    Reduce program size by excluding
+    the interface to the <tt/gpm/ application
+    which allows BRLTTY to interact with the pointer (mouse) device
+    (see section <ref id="gpm" name="Pointer (Mouse) Support via GPM">).
+  <tag><tt/--disable-api/<label id="build-api"></tag>
+    Reduce program size by excluding
+    the Application Programming Interface.
+  <tag><tt/--with-api-parameters=/<em/name/<tt/=/<em/value/<tt/,/...<label id="build-api-parameters"></tag>
+    Specify the default parameter settings for the Application Programming Interface.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    For a description of the parameters accepted by the interface,
+    please see the <bf/BrlAPI/ reference manual.
+    See the <ref id="configure-api-parameters" name="api-parameters"> configuration file directive
+    and the <ref id="options-api-parameters" name="-A"> command line option
+    for run-time selection.
+  <tag><tt/--disable-caml-bindings/<label id="build-caml-bindings"></tag>
+    Don't build the Caml bindings for the Application Programming Interface.
+  <tag><tt/--disable-java-bindings/<label id="build-java-bindings"></tag>
+    Don't build the Java bindings for the Application Programming Interface.
+  <tag><tt/--disable-lisp-bindings/<label id="build-lisp-bindings"></tag>
+    Don't build the Lisp bindings for the Application Programming Interface.
+  <tag><tt/--disable-python-bindings/<label id="build-python-bindings"></tag>
+    Don't build the Python bindings for the Application Programming Interface.
+  <tag><tt/--disable-tcl-bindings/<label id="build-tcl-bindings"></tag>
+    Don't build the Tcl bindings for the Application Programming Interface.
+  <tag><tt/--with-tcl-config=/<em/path/<label id="build-tcl-config"></tag>
+    Specify the location of the Tcl configuration script (<tt/tclConfig.sh/).
+    Either the path to the script itself
+    or to the directory containing it
+    may be supplied.
+    Any of the following words can also be used as the operand of this option:
+    <descrip>
+      <tag/no/
+        Use other means to guess if Tcl is available, and, if so, where it has been installed.
+        This is equivalent to specifying <tt/--without-tcl-config/.
+      <tag/yes/
+        Search for the script in a few commonly used directories.
+        This is equivalent to specifying <tt/--with-tcl-config/.
+    </descrip>
+</descrip>
+
+<sect3>Miscellaneous Options<label id="build-miscellaneous"><p>
+<descrip>
+  <tag><tt/--with-init-path=/<em/path/<label id="build-init-path"></tag>
+    Specify the path to the real <tt/init/ program for the system.
+    The absolute path should be supplied.
+    If this option is specified, then:
+    <enum>
+      <item>
+        The <tt/init/ program should be moved to a new location.
+      <item>
+        <tt/brltty/ should be moved to the <tt/init/ program's original location.
+      <item>
+        When the system runs <tt/init/ at startup,
+        <tt/brltty/ is actually run.
+        It puts itself into the background,
+        and runs the real <tt/init/ in the foreground.
+        This is one (somewhat sneaky) way to have braille right at the outset.
+        It's especially useful for some install/rescue disks.
+    </enum>
+    If this option isn't specified, then this feature isn't activated.
+    This option is primarily intended for building a braillified installer image.
+  <tag><tt/--with-stderr-path=/<em/path/<label id="build-stderr-path"></tag>
+    Specify the path to the file or device where standard error output is to be written.
+    The absolute path should be supplied.
+    If this option isn't specified, then this feature isn't activated.
+    This option is primarily intended for building a braillified installer image.
+</descrip>
+
+<sect2>Make File Targets<label id="make"><p>
+Once BRLTTY has been configured,
+the next steps are to compile and to install it.
+These are done by applying the system's <tt/make/ command
+to BRLTTY's main make file (<tt/Makefile/ in the top-level directory).
+BRLTTY's make file supports most of the common application maintenance targets.
+They include:
+<descrip>
+  <tag/make/
+    A shortcut for <tt/make all/.
+  <tag>make all<label id="make-all"></tag>
+    Compile and link the BRLTTY executable, its drivers and their help pages,
+    its test programs, and a few other small utilities.
+  <tag>make install<label id="make-install"></tag>
+    Complete the compile and link phase
+    (see <ref id="make-all" name="make all">),
+    and then install the BRLTTY executable, its data files, drivers, and help pages,
+    in the correct places and with the correct permissions.
+  <tag>make uninstall<label id="make-uninstall"></tag>
+    Remove the BRLTTY executable, its data files, drivers, and help pages,
+    from the system.
+  <tag>make clean<label id="make-clean"></tag>
+    Ensure that the next compile and link
+    (see <ref id="make-all" name="make all">)
+    will be done from scratch by removing the results of
+    compiling, linking, and testing from the source directory structure.
+    This includes the removal of object files, executables,
+    dynamically loadable shared objects, driver lists, help pages,
+    temporary header files, and core files.
+  <tag>make distclean<label id="make-distclean"></tag>
+    In addition to removing the results of compiling and linking
+    (see <ref id="make-clean" name="make clean">):
+    <itemize>
+      <item>
+        Remove the results of BRLTTY configuration
+        (see <ref id="build" name="Build Options">).
+        This includes the removal of
+        <tt/config.mk/, <tt/config.h/,
+        <tt/config.cache/, <tt/config.status/, and <tt/config.log/.
+      <item>
+        Remove other files from the source directory structure
+        which tend to accumulate over time but which don't belong there.
+        This includes the removal of
+        editor backup files, test case results,
+        rejected patch hunks, and copies of original source files.
+    </itemize>
+</descrip>
+
+<sect1>Testing BRLTTY<p>
+After compiling, linking, and installing BRLTTY,
+it's a good idea to give it a quick test before activating it permanently.
+To do so, invoke it with the command:
+<tscreen>brltty -b<em/driver/ -d<em/device/</tscreen>
+For <em/driver/, specify the two-letter
+<ref id="drivers" name="driver identification code">
+corresponding to your braille display.
+For <em/device/, specify the full path
+for the device to which your braille display is connected.
+
+If you don't want to explicitly identify the driver and device
+each time you start BRLTTY, then you can take two approaches.
+You can establish system defaults
+via the <ref id="configure-braille-driver" name="braille-driver">
+and the <ref id="configure-braille-device" name="braille-device">
+configuration file directives,
+and/or compile your needs right into BRLTTY
+via the <ref id="build-braille-driver" name="--with-braille-driver">
+and the <ref id="build-braille-device" name="--with-braille-device">
+build options.
+
+If all is well, BRLTTY's version identification message
+should appear on the braille display for a few seconds
+(see the <ref id="options-message-timeout" name="-M"> command line option).
+After it goes away (which you can hasten by pressing any key on the display),
+the area of the screen where the cursor is should appear.
+This means that you should expect to see your shell's command prompt.
+Then, as you enter your next command,
+each character should appear on the display as it's typed on the keyboard.
+
+If this is your experience, then leave BRLTTY running, and enjoy it.
+If this isn't your experience, then it may be necessary
+to test each driver separately in order to isolate the source of the problem.
+The screen driver can be tested with <ref id="utility-scrtest" name="scrtest">,
+and the braille display driver can be tested with <ref id="utility-brltest" name="brltest">.
+
+If you experience a problem which requires a lot of digging,
+then you may wish to use the following <tt/brltty/ command line options:
+<itemize>
+  <item>
+    <ref id="options-log-level" name="-ldebug">
+    to log lots of diagnostic messages.
+  <item>
+    <ref id="options-no-daemon" name="-n">
+    to keep BRLTTY in the foreground.
+  <item>
+    <ref id="options-standard-error" name="-e">
+    to direct diagnostic messages to standard error
+    rather than to the system log.
+</itemize>
+
+<sect1>Starting BRLTTY<p>
+BRLTTY, when properly installed, is invoked with the single command <tt/brltty/.
+A configuration file
+(see section <ref id="configure" name="The Configuration File"> for details)
+can be created in order to establish system defaults for such things as
+the location of the preferences file,
+the braille display driver to be used,
+the device to which the braille display is connected,
+and the text table to be used.
+Many options
+(see section <ref id="options" name="Command Line Options"> for details)
+allow explicit run-time specification of such things as
+the location of the configuration file,
+any defaults established within the configuration file,
+and some characteristics which have reasonable defaults
+but which those who think they know what they're doing may wish to play with.
+The <ref id="options-help" name="-h"> option
+displays a summary of all the options.
+The <ref id="options-version" name="-V"> option
+displays the current version of the program, the API, and the selected drivers.
+The <ref id="options-verify" name="-v"> option
+displays the values of the options after all sources have been considered.
+
+It's probably best to have the system automatically start BRLTTY
+as part of the boot sequence
+so that the braille display is already up and running when the login prompt appears.
+Most (probably all) distributions provide a script wherein
+user-supplied applications can be safely started near the end of the boot sequence.
+The name of this script is distribution-dependent.
+Here are the ones we know about so far:
+<descrip>
+  <tag/Red Hat/
+    <tt>/etc/rc.d/rc.local</>
+</descrip>
+
+Starting BRLTTY from this script is a good approach (especially for new users).
+Just add a set of lines like these:
+<tscreen><verb>
+if [ -x /bin/brltty -a -f /etc/brltty.conf ]
+then
+   /bin/brltty
+fi
+</verb></tscreen>
+This can usually be abbreviated to the somewhat less readable form:
+<tscreen><verb>
+[ -x /bin/brltty -a -f /etc/brltty.conf ] && /bin/brltty
+</verb></tscreen>
+Don't add these lines before the first line
+(which usually looks like <tt>&num;!/bin/sh</>).
+
+If the braille display is to be used by a system administrator,
+then it should probably be started as early as possible during the boot sequence
+(like before the file systems are checked)
+so that the display is usable
+in the event that something goes wrong during these checks
+and the system drops into single user mode.
+Again, exactly where it's best to do this is distribution-dependent.
+Here are the places we know about so far:
+<descrip>
+  <tag>Debian</tag>
+    <tt>/etc/init.d/boot</> (for older releases)
+    <newline>
+    <tt>/etc/init.d/</> (for newer releases)
+    <newline>
+    A <tt/brltty/ package is provided
+    (see [<htmlurl url="http://packages.debian.org/brltty" name="http://packages.debian.org/brltty">])
+    as of release <tt/3.0/ (<tt/Woody/).
+    Since this package takes care of starting BRLTTY,
+    there's no need for user-supplied code to do so if it's installed.
+    If you need the daemon to run with some command-line options,
+    you can change the contents between quotes
+    on the directive <tt/ARGUMENTS/
+    in the <tt>/etc/default/brltty</> file.
+  <tag>RedHat</tag>
+    <tt>/etc/rc.d/rc.sysinit</>
+    <newline>
+    Beware that later releases,
+    in order to support a more user-oriented system initialization procedure,
+    have this script reinvoke itself such that
+    it's under the control of <tt/initlog/.
+    Look, probably right up near the top, for a set of lines like these:
+    <tscreen><verb>
+    # Rerun ourselves through initlog
+    if [ -z "$IN_INITLOG" ]; then
+      [ -f /sbin/initlog ] && exec /sbin/initlog $INITLOG_ARGS -r /etc/rc.sysinit
+    fi
+    </verb></tscreen>
+    Starting BRLTTY before this reinvocation
+    results in two BRLTTY processes running at the same time,
+    and that'll give you no end of problems.
+    If your version of this script has this feature,
+    then make sure you start BRLTTY after the lines which implement it.
+  <tag>Slackware</tag>
+    <tt>/etc/rc.d/rc.S</>
+  <tag>SuSE</tag>
+    <tt>/sbin/init.d/boot</>
+</descrip>
+
+An alternative is to start BRLTTY from <tt>/etc/inittab</>.
+You have two choices if you choose this route.
+<itemize>
+  <item>
+    If you want it to be started really early
+    but don't need it to be automatically restarted if it dies,
+    then add a line like this before the first <tt/:sysinit:/ line which is already in there.
+    <tscreen>brl::sysinit:/bin/brltty</tscreen>
+  <item>
+    If you don't mind it being started later
+    but do want it to be automatically restarted if it dies,
+    then add a line like this anywhere within the file.
+    <tscreen>brl:12345:respawn:/bin/brltty -n</tscreen>
+    The <ref id="options-no-daemon" name="-n"> (<tt/--nodaemon/) option is very
+    important when running BRLTTY with <bf/init/'s <tt/respawn/ facility.
+    You'll end up with hundreds of BRLTTY processes all running at the same time
+    if you forget to specify it.
+</itemize>
+Check that the identifier (<tt/brl/ in these examples)
+isn't already being used by another entry,
+and, if it is, choose a different one which isn't.
+
+Note that a command like <tt/kill -TERM/
+is sufficient to stop BRLTTY in its tracks.
+If it dies during entry into single user mode, for example,
+it may well be due to a problem of this nature.
+
+Some systems, as part of the boot sequence, probe the serial ports
+(usually in order to automatically find the mouse and deduce its type).
+If your braille display is using a serial port,
+this kind of probing may be enough to get it confused.
+If this happens to you, then try restarting the braille driver
+(see the <ref id="command-RESTARTBRL" name="RESTARTBRL"> command).
+Better yet, turn off the serial port probing.
+Here's what we know so far about how to do this:
+<descrip>
+  <tag/Red Hat/
+    The probing is done by a service named <tt/kudzu/.
+    Use the command <tscreen>chkconfig --list kudzu</tscreen> to see if it's been enabled.
+    Use the command <tscreen>chkconfig kudzu off</tscreen> to disable it.
+    Later releases allow you to let <tt/kudzu/ run
+    without probing the serial ports.
+    To do this, edit the file <tt>/etc/sysconfig/kudzu</>,
+    and set <tt/SAFE/ to <tt/yes/.
+</descrip>
+
+If you want to start BRLTTY before any file systems are mounted, then
+ensure that all of its components are installed within the root file system.
+See the <ref id="build-execute-root" name="--with-execute-root">,
+<ref id="build-program-directory" name="--bindir">,
+<ref id="build-library-directory" name="--libdir">,
+<ref id="build-writable-directory" name="--with-writable-directory">,
+and <ref id="build-data-directory" name="--with-data-directory">
+build options.
+
+<sect1>Security Considerations<p>
+BRLTTY needs to run with root privileges because it needs
+read and write access for the port to which the braille display is connected,
+read access to <tt>/dev/vcsa</> or equivalent
+(to query the screen dimensions and the cursor position,
+and to review the current screen content and highlighting),
+and read and write access to the system console
+(for arrow key entry during cursor routing,
+for input character insertion during paste,
+for special key simulation using keys on the braille display,
+for retrieving output character translation and screen font mapping tables,
+and for activation of the internal beeper).
+Access to the needed devices can, of course, be granted to a non-root user
+by changing the file permissions associated with the devices.
+Merely having access to the console, however, isn't enough because
+activating the internal beeper and simulating key strokes still require root privilege.
+So, if you're willing to give up cursor routing, cut&amp;paste, beeps,
+and all that, you can run BRLTTY without root priviledge.
+
+<sect1>Build and Run-Time Restrictions<p>
+<descrip>
+  <tag/Alert Tunes/<label id="restrictions-tunes">
+    Some platforms don't support all of the tune devices.
+    See the <ref id="preference-tune-device" name="Tune Device">
+    preference for details.
+  <tag/FestivalLite Speech Synthesizer Driver/<label id="restrictions-flite">
+    The driver for the FestivalLite text to speech engine
+    is only built if that package has been installed.
+
+    This driver and the driver for the Theta text to speech engine
+    (see the <ref id="build-theta" name="--with-theta"> build option)
+    cannot be simultaneously linked into the BRLTTY binary
+    (see the <ref id="build-speech-driver" name="--with-speech-driver"> build option)
+    because their run-time libraries contain conflicting symbols.
+  <tag/Libbraille Braille Display Driver/<label id="restrictions-libbraille">
+    The driver for the Libbraille package
+    is only built if that package has been installed.
+  <tag/Mikropuhe Speech Synthesizer Driver/<label id="restrictions-mikropuhe">
+    The driver for the Mikropuhe text to speech engine
+    is only built if that package has been installed.
+
+    This driver cannot be included if the BRLTTY binary is statically linked
+    (see the <ref id="build-standalone-programs" name="--enable-standalone-programs"> build option)
+    because a static archive isn't included with the package.
+  <tag/Theta Speech Synthesizer Driver/<label id="restrictions-theta">
+    The driver for the Theta text to speech engine
+    is only built if that package has been installed.
+
+    This driver and the driver for the FestivalLite text to speech engine
+    (see the <ref id="build-flite" name="--with-flite"> build option)
+    cannot be simultaneously linked into the BRLTTY binary
+    (see the <ref id="build-speech-driver" name="--with-speech-driver"> build option)
+    because their run-time libraries contain conflicting symbols.
+
+    If this driver is built as a dynamically loadable shared object
+    then <tt>$THETA_HOME/lib</> must be added
+    to the <tt/LD_LIBRARY_PATH/ environment variable
+    before BRLTTY is invoked
+    because the shared objects within the package don't contain
+    run-time search paths for their dependencies.
+  <tag/ViaVoice Speech Synthesizer Driver/<label id="restrictions-viavoice">
+    The driver for the ViaVoice text to speech engine
+    is only built if that package has been installed.
+
+    This driver cannot be included if the BRLTTY binary is statically linked
+    (see the <ref id="build-standalone-programs" name="--enable-standalone-programs"> build option)
+    because a static archive isn't included with the package.
+  <tag/VideoBraille Braille Display Driver/<label id="restrictions-videobraille">
+    The driver for the VideoBraille braille display is built on all systems,
+    but only works on Linux.
+</descrip>
+
+<sect1>Installing from an RPM File<label id="rpm"><p>
+To install BRLTTY from an RPM (RedHat Package Manager) file, do the following:
+<enum>
+  <item>
+    Download the binary package which corresponds to your hardware.
+    It'll be a file named
+    <tt/brltty-/<em/release/<tt/-/<em/version/<tt/./<em/architecture/<tt/.rpm/,
+    e.g. <tt/brltty-3.0-1.i386.rpm/.
+  <item>
+    Install the package.
+    <tscreen>rpm -Uvh brltty-<em/release/-<em/version/.<em/architecture/.rpm</tscreen>
+    This should be done as <bf/root/.
+    Strictly speaking, the <tt/-U/ (update) option is the only one which is necessary.
+    The <tt/-v/ (verbose) option displays the name of the package as it's being installed.
+    The <tt/-h/ (hashes) option displays a progress meter (using hash signs).
+</enum>
+For the brave,
+we also provide the source RPM (<tt/.src.rpm/) file,
+but that's beyond the scope of this document.
+
+To uninstall BRLTTY, do:
+<tscreen>rpm -e brltty</tscreen>
+
+<sect1>Other Utilities<p>
+Building BRLTTY also results in the building of
+a few small helper and diagnostic utilities.
+
+<sect2>brltty-config<label id="utility-brltty-config"><p>
+This utility sets a number of environment variables to values
+which reflect the current installation of BRLTTY
+(see <ref id="build" name="Build Options">).
+It should be executed within an existing shell environment,
+i.e. not as a command in its own right,
+and can only be used by scripts which support <bf/Bourne Shell/ syntax.
+<tscreen>. brltty-config</tscreen>
+
+The following environment variables are set:
+<descrip>
+  <tag/BRLTTY_VERSION/
+    The version number of the BRLTTY package.
+  <tag/BRLTTY_EXECUTE_ROOT/
+    Run-time root for the installed package.
+    Configured via the <ref id="build-execute-root" name="--with-execute-root"> build option.
+  <tag/BRLTTY_PROGRAM_DIRECTORY/
+    Directory for runnable programs (binaries, executables).
+    Configured via the <ref id="build-program-directory" name="--with-program-directory"> build option.
+  <tag/BRLTTY_LIBRARY_DIRECTORY/
+    Directory for drivers.
+    Configured via the <ref id="build-library-directory" name="--with-library-directory"> build option.
+  <tag/BRLTTY_WRITABLE_DIRECTORY/
+    Directory which can be written to.
+    Configured via the <ref id="build-writable-directory" name="--with-writable-directory"> build option.
+  <tag/BRLTTY_DATA_DIRECTORY/
+    Directory for tables and help pages.
+    Configured via the <ref id="build-data-directory" name="--with-data-directory"> build option.
+  <tag/BRLTTY_MANPAGE_DIRECTORY/
+    Directory for manual pages.
+    Configured via the <ref id="build-manpage-directory" name="--with-manpage-directory"> build option.
+  <tag/BRLTTY_INCLUDE_DIRECTORY/
+    Directory for BrlAPI's C header files.
+    Configured via the <ref id="build-include-directory" name="--with-include-directory"> build option.
+  <tag/BRLAPI_VERSION/
+    The version number of BrlAPI (BRLTTY's Application Programming Interface).
+  <tag/BRLAPI_RELEASE/
+    The full release number of BrlAPI.
+  <tag/BRLAPI_AUTH/
+    The name of BrlAPI's key file.
+</descrip>
+
+In addition, the following standard <bf/autoconf/ environment variables are also set:
+<descrip>
+  <tag/prefix/
+    Subroot for architecture-independent files.
+    Configured via the <ref id="build-portable-root" name="--prefix"> build option.
+  <tag/exec_prefix/
+    Subroot for architecture-dependent files.
+    Configured via the <ref id="build-architecture-root" name="--exec-prefix"> build option.
+  <tag/bindir/
+    Default location for <ref id="build-program-directory" name="program directory">.
+    Configured via the <tt/--bindir/ build option.
+  <tag/libdir/
+    Directory for BrlAPI's static archive and dynamically loadable object.
+    Default anchor for <ref id="build-library-directory" name="library directory">.
+    Configured via the <ref id="build-api-directory" name="--libdir"> build option.
+  <tag/sysconfdir/
+    Directory for configuration files.
+    Default anchor for <ref id="build-data-directory" name="data directory">.
+    Configured via the <ref id="build-configuration-directory" name="--sysconfdir"> build option.
+  <tag/mandir/
+    Default location for <ref id="build-manpage-directory" name="manual pages directory">.
+    Configured via the <tt/--mandir/ build option.
+  <tag/includedir/
+    Default anchor for <ref id="build-include-directory" name="header files directory">.
+    Configured via the <tt/--includedir/ build option.
+</descrip>
+
+<sect2>brltty-install<label id="utility-brltty-install"><p>
+This utility copies BRLTTY's
+<ref id="hierarchy" name="installed file hierarchy">
+from one location to another.
+<tscreen>brltty-install <em/to/ &lsqb;<em/from/&rsqb;</tscreen>
+<descrip>
+  <tag><em/to/</tag>
+    The location to which the
+    <ref id="hierarchy" name="installed file hierarchy">
+    is to be copied.
+    It must be an existing directory.
+  <tag><em/from/</tag>
+    The location from which the
+    <ref id="hierarchy" name="installed file hierarchy">
+    is to be taken.
+    If it's specified, then it must be an existing directory.
+    If it's not specified, then the location used for the build is assumed.
+</descrip>
+
+This utility can be used, for example, to copy BRLTTY to a root disk.
+If a root floppy is mounted as <tt>/mnt</>,
+and BRLTTY is installed on the main system,
+then typing <tscreen>brltty-install /mnt</tscreen>
+copies BRLTTY, along with all of its data and library files,
+to the root floppy.
+
+Some problems have been experienced when copying BRLTTY
+between systems with different versions of the shared C library.
+This is worth investigating if you have difficulties.
+
+<sect2>brltest<label id="utility-brltest"><p>
+This utility tests a braille display driver,
+and also provides an interactive way to learn
+what the keys on the braille display do.
+It should be run as root.
+<tscreen>brltest -<em/option/ ... [<em/driver/ [<em/name/=<em/value/ ...]]</tscreen>
+<descrip>
+  <tag><em/driver/</tag>
+    The driver for the braille display.
+    It must be a two-letter <ref id="drivers" name="driver identification code">.
+    If it's not specified, then the first driver configured via the
+    <ref id="build-braille-driver" name="--with-braille-driver"> build option
+    is assumed.
+  <tag><em/name/<tt/=/<em/value/</tag>
+    Set a braille display driver parameter.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+  <tag><tt/-d/<em/device/ <tt/--device=/<em/device/</tag>
+    The absolute path for the device to which the braille display is connected.
+    If it's not specified, then the device configured via the
+    <ref id="build-braille-device" name="--with-braille-device"> build option
+    is assumed.
+  <tag><tt/-D/<em/directory/ <tt/--data-directory=/<em/directory/</tag>
+    The absolute path for the directory wherein the driver data files reside.
+    If it's not specified, then the directory configured via the
+    <ref id="build-data-directory" name="--with-data-directory"> build option
+    is assumed.
+  <tag><tt/-L/<em/directory/ <tt/--library-directory=/<em/directory/</tag>
+    The absolute path for the directory wherein the drivers reside.
+    If it's not specified, then the directory configured via the
+    <ref id="build-library-directory" name="--libdir"> build option
+    is assumed.
+  <tag><tt/-W/<em/directory/ <tt/--writable-directory=/<em/directory/</tag>
+    The absolute path for a directory which can be written to.
+    If it's not specified, then the directory configured via the
+    <ref id="build-writable-directory" name="--with-writable-directory"> build option
+    is assumed.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+This utility uses BRLTTY's <ref id="learn" name="Command Learn Mode">.
+The key press timeout
+(after which this utility exits)
+is <tt/10/ seconds.
+The message hold time
+(used for non-final segments of long messages)
+is <tt/4/ seconds.
+
+<sect2>spktest<label id="utility-spktest"><p>
+This utility tests a speech synthesizer driver.
+It may need to be run as root.
+<tscreen>spktest -<em/option/ ... [<em/driver/ [<em/name/=<em/value/ ...]]</tscreen>
+<descrip>
+  <tag><em/driver/</tag>
+    The driver for the speech synthesizer.
+    It must be a two-letter <ref id="drivers" name="driver identification code">.
+    If it's not specified, then the first driver configured via the
+    <ref id="build-speech-driver" name="--with-speech-driver"> build option
+    is assumed.
+  <tag><em/name/<tt/=/<em/value/</tag>
+    Set a speech synthesizer driver parameter.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+  <tag><tt/-t/<em/string/ <tt/--text-string=/<em/string/</tag>
+    The text to be spoken.
+    If it's not specified, then standard input is read.
+  <tag><tt/-D/<em/directory/ <tt/--data-directory=/<em/directory/</tag>
+    The absolute path for the directory wherein the driver data files reside.
+    If it's not specified, then the directory configured via the
+    <ref id="build-data-directory" name="--with-data-directory"> build option
+    is assumed.
+  <tag><tt/-L/<em/directory/ <tt/--library-directory=/<em/directory/</tag>
+    The absolute path for the directory wherein the drivers reside.
+    If it's not specified, then the directory configured via the
+    <ref id="build-library-directory" name="--libdir"> build option
+    is assumed.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+<sect2>scrtest<label id="utility-scrtest"><p>
+This utility tests the screen driver.
+It must be run as root.
+<tscreen>scrtest -<em/option/ ... [<em/name/=<em/value/ ...]</tscreen>
+<descrip>
+  <tag><em/name/<tt/=/<em/value/</tag>
+    Set a screen driver parameter.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+  <tag><tt/-l/<em/column/ <tt/--left=/<em/column/</tag>
+    Specify the starting (left) column (zero-origin) of the region.
+    If this value isn't supplied,
+    then a default value, based on the specified width,
+    is selected such that the region is horizontally centred.
+  <tag><tt/-c/<em/count/ <tt/--columns=/<em/count/</tag>
+    Specify the width of the region (in columns).
+    If this value isn't supplied,
+    then a default value, based on the specified starting column,
+    is selected such that the region is horizontally centred.
+  <tag><tt/-t/<em/row/ <tt/--top=/<em/row/</tag>
+    Specify the starting (top) row (zero-origin) of the region.
+    If this value isn't supplied,
+    then a default value, based on the specified height,
+    is selected such that the region is vertically centred.
+  <tag><tt/-r/<em/count/ <tt/--rows=/<em/count/</tag>
+    Specify the height of the region (in rows).
+    If this value isn't supplied,
+    then a default value, based on the specified starting row,
+    is selected such that the region is vertically centred.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+Notes:
+<itemize>
+  <item>
+    If neither a starting column nor a region width is specified,
+    then the region is horizontally-centred and starts at column 5.
+  <item>
+    If neither a starting row nor a region height is specified,
+    then the region is vertically-centred and starts at row 5.
+</itemize>
+
+The following is written to standard output:
+<enum>
+  <item>
+    A line detailing the dimensions of the screen.
+    <tscreen>Screen: <em/width/x<em/height/</tscreen>
+  <item>
+    A line detailing the position (zero-origin) of the cursor.
+    <tscreen>Cursor: &lsqb;<em/column/,<em/row/&rsqb;</tscreen>
+  <item>
+    A line detailing the size of the selected screen region,
+    and the position (zero-origin) of its top-left corner.
+    <tscreen>Region: <em/width/x<em/height/@&lsqb;<em/column/,<em/row/&rsqb;</tscreen>
+  <item>
+    The contents of the selected screen region.
+    Unprintable characters are written as blanks.
+</enum>
+
+<sect2>ttbtest<label id="utility-ttbtest"><p>
+This utility tests a text table
+(see section <ref id="table-text" name="Text Tables">).
+<tscreen>ttbtest -<em/option/ ... <em/input-table/ <em/output-table/</tscreen>
+<descrip>
+  <tag><em/input-table/</tag>
+    The file system path to the input text table.
+    If it's relative then it's anchored at the directory configured via the
+    <ref id="build-data-directory" name="--with-data-directory"> build option.
+  <tag><em/output-table/</tag>
+    The file system path to the output text table.
+    If it's relative then it's anchored at the current working directory.
+    If this parameter isn't supplied then no output table is written.
+  <tag><tt/-i/<em/format/ <tt/--input-format=/<em/format/</tag>
+    Specify the format of the input table.
+    If this option isn't supplied then the format of the input table
+    is deduced from the extension of the input table's file name.
+  <tag><tt/-o/<em/format/ <tt/--output-format=/<em/format/</tag>
+    Specify the format of the output table.
+    If this option isn't supplied then the format of the output table
+    is deduced from the extension of the output table's file name.
+  <tag><tt/-c/<em/charset/ <tt/--charset=/<em/charset/</tag>
+    Specify the name of the 8-bit character set to use when interpreting the tables.
+    If this option isn't supplied then the host's character set is used.
+  <tag><tt/-e/ <tt/--edit/</tag>
+    Invoke the text table editor.
+    If the output table is specified then changes are written to it.
+    If not then the input table is rewritten.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+If no special action is requested then the output table is optional.
+If it is not specified then the input table is checked.
+If it is specified then the input table is converted.
+
+The following table formats are supported:
+<descrip>
+  <tag/ttb/BRLTTY
+  <tag/sbl/SuSE Blinux
+  <tag/a2b/Gnopernicus
+  <tag/gnb/Gnome Braille
+</descrip>
+
+<sect2>ctbtest<label id="utility-ctbtest"><p>
+This utility tests a contraction table
+(see section <ref id="table-contraction" name="Contraction Tables">).
+The text read from the input files (or standard input)
+is rewritten to standard output as contracted braille.
+<tscreen>ctbtest -<em/option/ ... <em/input-file/ ...</tscreen>
+<descrip>
+  <tag><em/input-file/</tag>
+    The list of files to be processed.
+    Any number of files may be specified.
+    They're processed from left to right.
+    The special file name <tt/-/ is interpreted to mean standard input.
+    If no files are specified then standard input is processed.
+  <tag><tt/-c/<em/file/ <tt/--contraction-table=/<em/file/</tag>
+    The file system path to the contraction table.
+    If it's relative then it's anchored at the directory configured via the
+    <ref id="build-data-directory" name="--with-data-directory"> build option.
+    The <tt/.ctb/ extension is optional.
+    If this option isn't supplied then <tt/en-us-g2/ is assumed.
+  <tag><tt/-t/<em/file/|<tt/auto/ <tt/--text-table=/<em/file/|<tt/auto/</tag>
+    Specify the text table
+    (see section <ref id="table-text" name="Text Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ttb/ extension is optional.
+    See the <ref id="configure-text-table" name="text-table">
+    configuration file directive for the default run-time setting.
+    This setting can be changed with the
+    <ref id="preference-text-table" name="Text Table"> preference.
+  <tag><tt/-w/<em/columns/ <tt/--output-width=/<em/columns/</tag>
+    The maximum length of an output line.
+    Each contracted input line is wrapped into as many output lines as necessary.
+    If this option isn't specified then there's no limit,
+    and there's a one-to-one correspondence between input and output lines.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+The text table is used:
+<itemize>
+  <item>
+    To define the output character set so that
+    the contracted braille will be displayed correctly.
+    The same table as will be used by BRLTTY when the output is read should be specified.
+  <item>
+    To define the braille representations of those characters
+    defined within the contraction table as <tt/=/
+    (see section <ref id="contraction-opcodes-translation" name="Character Translation">).
+</itemize>
+
+The <tt/brf.ttb/ text table is provided for use with this utility.
+It defines the format used within <tt/.brf/ files. 
+This is also the preferred format used
+by most braille printers
+and within electronically distributed braille documents.
+This table effectively allows this utility to be used as a text to braille translator.
+
+<sect2>tunetest<label id="utility-tunetest"><p>
+This utility tests the alert tunes facility,
+and also provides an easy way to compose new tunes.
+It may need to be run as root.
+<tscreen>tunetest -<em/option/ ... {<em/note/ <em/duration/} ...</tscreen>
+<descrip>
+  <tag><em/note/</tag>
+    A standard <tt/MIDI/ note number.
+    It must be an integer from <tt/1/ through <tt/127/,
+    with <tt/60/ representing <tt/Middle C/.
+    Each value represents a standard chromatic semi-tone,
+    with the next lower and higher values
+    representing, respectively, the next lower and higher notes.
+    The lowest value (<tt/1/) represents
+    the fifth <tt/C-Sharp/ below <tt/Middle C/,
+    and the highest value (<tt/127/) represents
+    the sixth <tt/G/ above <tt/Middle C/.
+  <tag><em/duration/</tag>
+    The duration of the note in milliseconds.
+    It must be an integer from <tt/1/ through <tt/255/.
+  <tag><tt/-d/<em/device/ <tt/--device=/<em/device/</tag>
+    The device on which to play the tune.
+    <descrip>
+      <tag/beeper/
+        The internal beeper (console tone generator).
+      <tag/pcm/
+        The digital audio interface on the sound card.
+      <tag/midi/
+        The Musical Instrument Digital Interface on the sound card.
+      <tag/fm/
+        The FM synthesizer on an
+        AdLib, OPL3, Sound Blaster, or equivalent sound card.
+    </descrip>
+    The device name may be abbreviated.
+    See the <ref id="preference-tune-device" name="Tune Device"> preference
+    for details regarding the default device and platform restrictions.
+  <tag><tt/-v/<em/loudness/ <tt/--volume=/<em/loudness/</tag>
+    Specify the output volume (loudness) as a percentage of the maximum.
+    The default output volume is <tt/50/.
+  <tag><tt/-p/<em/device/ <tt/--pcm-device=/<em/device/</tag>
+    Specify the device to use for digital audio
+    (see section <ref id="operand-pcm-device" name="PCM Device Specification">).
+    This option isn't available if the
+    <ref id="build-pcm-support" name="--disable-pcm-support"> build option was specified.
+  <tag><tt/-m/<em/device/ <tt/--midi-device=/<em/device/</tag>
+    Specify the device to use for the Musical Instrument Digital Interface
+    (see section <ref id="operand-midi-device" name="MIDI Device Specification">).
+    This option isn't available if the
+    <ref id="build-midi-support" name="--disable-midi-support"> build option was specified.
+  <tag><tt/-i/<em/instrument/ <tt/--instrument=/<em/instrument/</tag>
+    The instrument to use if the selected device is <tt/midi/.
+    For the complete list of instruments,
+    see the <ref id="midi" name="MIDI Instrument Table">.
+    The default instrument is an <tt/acoustic grand piano/.
+    The words comprising the instrument name must be separated from one another
+    by a single minus sign rather than by spaces,
+    and any of the words may be abbreviated.
+    An <tt/acoustic grand piano/, for example, may be specified as <tt/a-gra-pi/.
+  <tag><tt/-h/ <tt/--help/</tag>
+    Display a summary of the command line options, and then exit.
+</descrip>
+
+<sect>Using BRLTTY<p>
+Before starting BRLTTY, you need to set up your braille display.
+In most cases this is done simply
+by connecting it to an available serial port,
+and then turning it on.
+After your display has been set up,
+run BRLTTY simply by typing the command <tt/brltty/ at a shell prompt
+(this must be done as root).
+Check the
+<ref id="options-braille-device" name="-d"> command line option,
+the <ref id="configure-braille-device" name="braille-device"> configuration file directive,
+and the <ref id="build-braille-device" name="--with-braille-device"> build option
+for alternatives regarding how to tell BRLTTY which device your display is connected to.
+Check the
+<ref id="options-braille-driver" name="-b"> command line option,
+the <ref id="configure-braille-driver" name="braille-driver"> configuration file directive,
+and the <ref id="build-braille-driver" name="--with-braille-driver"> build option
+for alternatives regarding how to tell BRLTTY which kind of braille display you have.
+Check the
+<ref id="options-braille-parameters" name="-B"> command line option,
+and the <ref id="configure-braille-parameters" name="braille-parameters"> configuration file directive
+for alternatives regarding how to pass parameters to the driver for your braille display.
+
+A message giving the program name (BRLTTY) and its version number
+will appear briefly
+(see the <ref id="options-message-timeout" name="-M"> command line option)
+on the braille display.
+The display will then show a small area of the screen including the cursor.
+By default, the cursor is represented as
+dots 7 and 8 superimposed on the character it is on.
+
+Any screen activity will be reflected on the braille display.
+The display will also follow the progress of the cursor on the screen.  
+This feature is known as <bf/cursor tracking/.
+
+Just typing on the keyboard and reading the display, however, isn't enough.
+Try entering a command which will cause an error, and pressing <bf/enter/.  
+The error appears on the screen, but, unless you have a multi-line display,
+the chances are that it isn't visible on the braille display.
+All you see thereon is another shell prompt.
+What's needed, then, is some way to move
+the braille <em/window/ around the screen.
+The keys on the braille display itself can be used to send commands to BRLTTY
+which, in addition to a lot of other things, can also do exactly that.
+
+<sect1>Commands<label id="commands"><p>
+Unfortunately, the various braille displays don't offer a standard set of controls.
+Some have the six standard dot keys, some have eight, and others have none.
+Some have thumb keys, but there's no standard number of them.
+Some have a button above each braille cell.
+Some have rocker switches.
+Some have an easy-to-reach bar which works much like a joystick.
+Most have varying combinations of the above.
+Because the nature and layout of each display is so different,
+please refer to the documentation for your particular display
+in order to find out exactly what its keys do.
+
+BRLTTY commands are referred to by name within this manual.
+If you forget which key(s) on your braille display to use
+for a particular command, then refer to its driver's help page.
+The main key you should immediately commit to memory, therefore, is the one
+for the <ref id="command-HELP" name="HELP"> command.
+Use the regular motion keys (as described below) to navigate the help page,
+and press the <tt/help/ key again to quit.
+
+<sect2>Vertical Motion<label id="vertical-motion"><p>
+See also
+the <ref id="command-PRINDENT-NXINDENT" name="PRINDENT/NXINDENT">,
+and the <ref id="command-PRDIFCHAR-NXDIFCHAR" name="PRDIFCHAR/NXDIFCHAR">
+routing key commands.
+<descrip>
+  <tag>LNUP/LNDN<label id="command-LNUP-LNDN"></tag>
+    Go up/down one line.
+    If identical line skipping has been activated
+    (see the <ref id="command-SKPIDLNS" name="SKPIDLNS"> command),
+    then these commands, rather than moving exactly one line,
+    are aliases for the <ref id="command-PRDIFLN-NXDIFLN" name="PRDIFLN/NXDIFLN"> commands.
+  <tag>WINUP/WINDN<label id="command-WINUP-WINDN"></tag>
+    Go up/down one window.
+    If the window is only 1 line high then move 5 lines.
+  <tag>PRDIFLN/NXDIFLN<label id="command-PRDIFLN-NXDIFLN"></tag>
+    Go up/down to the nearest line with different content.
+    If identical line skipping has been activated
+    (see the <ref id="command-SKPIDLNS" name="SKPIDLNS"> command),
+    then these commands, rather than skipping identical lines,
+    are aliases for the <ref id="command-LNUP-LNDN" name="LNUP/LNDN"> commands.
+  <tag>ATTRUP/ATTRDN<label id="command-ATTRUP-ATTRDN"></tag>
+    Go up/down to the nearest line with different attributes (character highlighting).
+  <tag>TOP/BOT<label id="command-TOP-BOT"></tag>
+    Go to the top/bottom line.
+  <tag>TOP_LEFT/BOT_LEFT<label id="command-TOP_LEFT-BOT_LEFT"></tag>
+    Go to the top-left/bottom-left corner.
+  <tag>PRPGRPH/NXPGRPH<label id="command-PRPGRPH-NXPGRPH"></tag>
+    Go to the nearest line of the previous/next paragraph
+    (the first non-blank line beyond the nearest blank line).
+    The current line is included when searching for the inter-paragraph space.
+  <tag>PRPROMPT/NXPROMPT<label id="command-PRPROMPT-NXPROMPT"></tag>
+    Go to the previous/next command prompt.
+  <tag>PRSEARCH/NXSEARCH<label id="command-PRSEARCH-NXSEARCH"></tag>
+    Search backward/forward for the nearest occurrence
+    of the character string within the cut buffer
+    (see <ref id="cut" name="Cut and Paste">)
+    which isn't within the braille window.
+    The search proceeds to the left/right,
+    starting at the character immediately to the left/right of the window,
+    and wrapping at the edge of the screen.
+    The search isn't case sensitive.
+</descrip>
+
+<sect2>Horizontal Motion<label id="horizontal-motion"><p>
+See also the <ref id="command-SETLEFT" name="SETLEFT"> routing key command.
+<descrip>
+  <tag>CHRLT/CHRRT<label id="command-CHRLT-CHRRT"></tag>
+    Go left/right one character.
+  <tag>HWINLT/HWINRT<label id="command-HWINLT-HWINRT"></tag>
+    Go left/right half a window.
+  <tag>FWINLT/FWINRT<label id="command-FWINLT-FWINRT"></tag>
+    Go left/right one window.
+    These commands are particularly useful
+    because they automatically wrap when they reach the edge of the screen.
+    Other features, like their ability to skip blank windows
+    (see the <ref id="command-SKPBLNKWINS" name="SKPBLNKWINS"> command),
+    further enhance their usefulness.
+  <tag>FWINLTSKIP/FWINRTSKIP<label id="command-FWINLTSKIP-FWINRTSKIP"></tag>
+    Go left/right to the nearest non-blank window.
+  <tag>LNBEG/LNEND<label id="command-LNBEG-LNEND"></tag>
+    Go to the beginning/end of the line.
+</descrip>
+
+<sect2>Implicit Motion<label id="implicit-motion"><p>
+See also the <ref id="command-GOTOMARK" name="GOTOMARK"> routing key command.
+<descrip>
+  <tag>HOME<label id="command-HOME"></tag>
+    Go to where the cursor is.
+  <tag>BACK<label id="command-BACK"></tag>
+    Go back to where the most recent motion command put the braille window.
+    This is an easy way to get right back to where you were reading
+    after an unexpected event (like cursor tracking)
+    moves the braille window at an inopportune moment.
+  <tag>RETURN<label id="command-RETURN"></tag>
+    <itemize>
+      <item>
+        If the most recent motion of the braille window was automatic,
+        e.g. as a result of cursor tracking,
+        then go back to where the most recent motion command put it
+        (see the <ref id="command-BACK" name="BACK"> command).
+      <item>
+        If the cursor isn't within the braille window
+        then go to where the cursor is
+        (see the <ref id="command-HOME" name="HOME"> command).
+    </itemize>
+</descrip>
+
+<sect2>Feature Activation<label id="feature-activation"><p>
+Each of these commands has three forms:
+<bf/activate/ (turn the feature on),
+<bf/deactivate/ (turn the feature off),
+and <bf/toggle/ (if it's off then turn it on, and if it's on then turn it off).
+Unless specifically noted, each of these features
+is initially <bf/off/, and, when <bf/on/, affects BRLTTY's operation as a whole.
+The initial setting of some of these features can be changed
+via the <ref id="preferences-menu" name="preferences menu">.
+<descrip>
+  <tag>FREEZE<label id="command-FREEZE"></tag>
+    Freeze the screen image.
+    BRLTTY makes a copy of the screen (content and attributes)
+    as of the moment when the screen image is frozen,
+    and then ignores all updating of the screen until it's unfrozen.
+    This feature makes it easy, for example,
+    to sample the output of an application which writes too much too quickly.
+  <tag>DISPMD<label id="command-DISPMD"></tag>
+    Show the highlighting (the attributes)
+    of each character within the braille window,
+    rather than the characters themselves (the content).
+    This feature is useful, for example,
+    when you need to locate a highlighted item.
+    When showing screen content, the text table is used
+    (see the <ref id="options-text-table" name="-t"> command line option,
+    the <ref id="configure-text-table" name="text-table"> configuration file directive,
+    and the <ref id="build-text-table" name="--with-text-table"> build option).
+    When showing screen attributes, the attributes table is used
+    (see the <ref id="options-attributes-table" name="-a"> command line option,
+    the <ref id="configure-attributes-table" name="attributes-table"> configuration file directive,
+    and the <ref id="build-attributes-table" name="--with-attributes-table"> build option).
+    This feature only affects the current virtual terminal.
+  <tag>SIXDOTS<label id="command-SIXDOTS"></tag>
+    Show characters using 6-dot, rather than 8-dot, braille.
+    Dots 7 and 8 are still used by other features
+    like cursor representation and highlighted character underlining.
+    If a contraction table has been selected
+    (see the <ref id="options-contraction-table" name="-c"> command line option
+    and the <ref id="configure-contraction-table" name="contraction-table"> configuration file directive),
+    then it is used.
+    This setting can also be changed with the
+    <ref id="preference-text-style" name="Text Style"> preference.
+  <tag>SLIDEWIN<label id="command-SLIDEWIN"></tag>
+    If cursor tracking
+    (see the <ref id="command-CSRTRK" name="CSRTRK"> command)
+    is <bf/on/, then,
+    whenever the cursor moves too close to (or beyond)
+    either end of the braille window,
+    horizontally reposition the window such that the cursor,
+    while remaining on that side, is nearer the centre.
+    If this feature is <bf/off/,
+    then the braille window is always positioned such that
+    its left end is a multiple of its width from the left edge of the screen.
+    This setting can also be changed with the
+    <ref id="preference-sliding-window" name="Sliding Window"> preference.
+  <tag>SKPIDLNS<label id="command-SKPIDLNS"></tag>
+    Rather than explicitly moving exactly one line either up or down,
+    skip past lines which have the same content as the current line.
+    This feature affects
+    the <ref id="command-LNUP-LNDN" name="LNUP/LNDN"> commands,
+    as well as the line wrapping feature of
+    the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT">
+    and <ref id="command-FWINLTSKIP-FWINRTSKIP" name="FWINLTSKIP/FWINRTSKIP"> commands.
+    This setting can also be changed with the
+    <ref id="preference-skip-identical-lines" name="Skip Identical Lines"> preference.
+  <tag>SKPBLNKWINS<label id="command-SKPBLNKWINS"></tag>
+    Skip past blank windows when reading either forward or backward.
+    This feature affects the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> commands.
+    This setting can also be changed with the
+    <ref id="preference-skip-blank-windows" name="Skip Blank Windows"> preference.
+  <tag>CSRVIS<label id="command-CSRVIS"></tag>
+    Show the cursor by superimposing a dot pattern
+    (see the <ref id="command-CSRSIZE" name="CSRSIZE"> command)
+    on top of the character where it is.
+    This feature is initially <bf/on/.
+    This setting can also be changed with the
+    <ref id="preference-show-cursor" name="Show Cursor"> preference.
+  <tag>CSRHIDE<label id="command-CSRHIDE"></tag>
+    Hide the cursor (see the <ref id="command-CSRVIS" name="CSRVIS"> command)
+    in order to accurately read the character beneath it.
+    This feature only affects the current virtual terminal.
+  <tag>CSRTRK<label id="command-CSRTRK"></tag>
+    Track (follow) the cursor.
+    If the cursor moves to a location which isn't within the braille window,
+    then automatically move the braille window to the cursor's new location.
+    You'll usually want this feature turned on
+    since it minimizes the effects of screen scrolling,
+    and since, during input, the region wherein you're currently typing is always visible.
+    If this feature causes the braille window to jump at an inopportune moment,
+    then use the <ref id="command-BACK" name="BACK"> command
+    to get back to where you were reading.
+   You may need to turn this feature off when using an application which
+   continually updates the screen while maintaining a fixed data layout.
+    This feature is initially <bf/on/.
+    This feature only affects the current virtual terminal.
+  <tag>CSRSIZE<label id="command-CSRSIZE"></tag>
+    Represent the cursor with all eight dots (a solid block),
+    rather than with just dots 7 and 8 (an underline).
+    This setting can also be changed with the
+    <ref id="preference-cursor-style" name="Cursor Style"> preference.
+  <tag>CSRBLINK<label id="command-CSRBLINK"></tag>
+    Blink (turn on and off according to a predefined interval)
+    the symbol representing the cursor
+    (see the <ref id="command-CSRVIS" name="CSRVIS"> command).
+    This setting can also be changed with the
+    <ref id="preference-blinking-cursor" name="Blinking Cursor"> preference.
+  <tag>ATTRVIS<label id="command-ATTRVIS"></tag>
+    Underline (with combinations of dots 7 and 8) highlighted characters.
+    <descrip>
+      <tag/no underline/
+        White on black (normal),
+        gray on black,
+        white on blue,
+        black on cyan.
+      <tag/dots 7 and 8/
+        Black on white (reverse video).
+      <tag/dot 8/
+        Everything else.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="preference-show-attributes" name="Show Attributes"> preference.
+  <tag>ATTRBLINK<label id="command-ATTRBLINK"></tag>
+    Blink (turn on and off according to a predefined interval)
+    the attribute underline
+    (see the <ref id="command-ATTRVIS" name="ATTRVIS"> command).
+    This feature is initially <bf/on/.
+    This setting can also be changed with the
+    <ref id="preference-blinking-attributes" name="Blinking Attributes"> preference.
+  <tag>CAPBLINK<label id="command-CAPBLINK"></tag>
+    Blink (turn on and off according to a predefined interval)
+    capital (uppercase) letters.
+    This setting can also be changed with the
+    <ref id="preference-blinking-capitals" name="Blinking Capitals"> preference.
+  <tag>TUNES<label id="command-TUNES"></tag>
+    Play a short predefined tune (see <ref id="tunes" name="Alert Tunes">)
+    whenever a significant event occurs.
+    This feature is initially <bf/on/.
+    This setting can also be changed with the
+    <ref id="preference-alert-tunes" name="Alert Tunes"> preference.
+  <tag>AUTOREPEAT<label id="command-AUTOREPEAT"></tag>
+    Automatically repeat a command at a regular interval
+    after an initial delay
+    while its key (combination) remains pressed.
+    Only some drivers support this functionality,
+    the primary limitation being that many braille displays
+    don't signal key presses and key releases as distinctly separate events.
+    This feature is initially <bf/on/.
+    This setting can also be changed with the
+    <ref id="preference-autorepeat" name="Autorepeat"> preference.
+  <tag>AUTOSPEAK<label id="command-AUTOSPEAK"></tag>
+    Automatically speak:
+    <itemize>
+      <item>the new line when the braille window is moved vertically.
+      <item>characters which are entered or deleted.
+      <item>the character to which the cursor is moved.
+    </itemize>
+    This feature is initially <bf/off/.
+    This setting can also be changed with the
+    <ref id="preference-autospeak" name="Autospeak"> preference.
+</descrip>
+
+<sect2>Mode Selection<label id="mode-selection"><p>
+<descrip>
+  <tag>HELP<label id="command-HELP"></tag>
+    Switch to the braille display driver's help page.
+    This is where you can find an on-line summary of things like
+    what your braille display's keys do,
+    and how to interpret its status cells.
+    Use the regular <ref id="vertical-motion" name="vertical">
+    and <ref id="horizontal-motion" name="horizontal"> motion commands
+    to navigate the help page.
+    Invoke the <tt/help/ command again to return to the screen.
+  <tag>INFO<label id="command-INFO"></tag>
+    Switch to the status display
+    (see section <ref id="status" name="The Status Display"> for full details).
+    It presents a summary including
+    the position of the cursor,
+    the position of the braille window,
+    and the states of a number of BRLTTY's features.
+    Invoke this command again to return to the screen.
+  <tag>LEARN<label id="command-LEARN"></tag>
+    Switch to command learn mode
+    (see section <ref id="learn" name="Command Learn Mode"> for full details).
+    This is how you can interactively learn what your braille display's keys do.
+    Invoke this command again to return to the screen.
+    This command isn't available if the
+    <ref id="build-learn-mode" name="--disable-learn-mode"> build option was specified.
+</descrip>
+
+<sect2>Preference Maintenance<label id="preference-maintenance"><p>
+<descrip>
+  <tag>PREFMENU<label id="command-PREFMENU"></tag>
+    Switch to the preferences menu
+    (see <ref id="preferences-menu" name="The Preferences Menu"> for full details).
+    Invoke this command again to return to normal operation.
+  <tag>PREFSAVE<label id="command-PREFSAVE"></tag>
+    Save the current preferences settings
+    (see <ref id="preferences" name="Preferences"> for full details).
+  <tag>PREFLOAD<label id="command-PREFLOAD"></tag>
+    Restore the most recently saved preferences settings
+    (see <ref id="preferences" name="Preferences"> for full details).
+</descrip>
+
+<sect2>Menu Navigation<label id="menu-navigation"><p>
+<descrip>
+  <tag>MENU_FIRST_ITEM/MENU_LAST_ITEM<label id="command-MENU_FIRST_ITEM-MENU_LAST_ITEM"></tag>
+    Go to the first/last item in the menu.
+  <tag>MENU_PREV_ITEMMENU_NEXT_ITEM/<label id="command-MENU_PREV_ITEM-MENU_NEXT_ITEM"></tag>
+    Go to the previous/next item in the menu.
+  <tag>MENU_PREV_SETTING/MENU_NEXT_SETTING<label id="command-MENU_PREV_SETTING-MENU_NEXT_SETTING"></tag>
+    Decrement/increment the current menu item's setting.
+</descrip>
+
+<sect2>Speech Controls<label id="speech-controls"><p>
+<descrip>
+  <tag>SAY_LINE<label id="command-SAY_LINE"></tag>
+    Speak the current line.
+    The <ref id="preference-sayline-mode" name="Say-Line Mode"> preference
+    determines if pending speech is discarded first.
+  <tag>SAY_ABOVE<label id="command-SAY_ABOVE"></tag>
+    Speak the top portion of the screen (ending with the current line).
+  <tag>SAY_BELOW<label id="command-SAY_BELOW"></tag>
+    Speak the bottom portion of the screen (starting with the current line).
+  <tag>MUTE<label id="command-MUTE"></tag>
+    Stop speaking immediately.
+  <tag>SPKHOME<label id="command-SPKHOME"></tag>
+    Go to where the speech cursor is.
+  <tag>SAY_SLOWER/SAY_FASTER<label id="command-SAY_SLOWER-SAY_FASTER"></tag>
+    Decrease/increase the speech rate
+    (see also the <ref id="preference-speech-rate" name="Speech Rate"> preference).
+    This command is only available if a driver which supports it is being used.
+  <tag>SAY_SOFTER/SAY_LOUDER<label id="command-SAY_SOFTER-SAY_LOUDER"></tag>
+    Decrease/increase the speech volume
+    (see also the <ref id="preference-speech-volume" name="Speech Volume"> preference).
+    This command is only available if a driver which supports it is being used.
+</descrip>
+
+<sect2>Speech Navigation<label id="speech-navigation"><p>
+<descrip>
+  <tag/SPEAK_CURR_CHAR/a
+</descrip>
+
+<sect2>Virtual Terminal Switching<p>
+See also the <ref id="command-SWITCHVT" name="SWITCHVT"> routing key command.
+<descrip>
+  <tag>SWITCHVT_PREV/SWITCHVT_NEXT<label id="command-SWITCHVT_PREV-SWITCHVT_NEXT"></tag>
+    Switch to the previous/next virtual terminal.
+</descrip>
+
+<sect2>Other Commands<p>
+<descrip>
+  <tag>CSRJMP_VERT<label id="command-CSRJMP_VERT"></tag>
+    Route (bring) the cursor to anywhere on the top line of the braille window
+    (see <ref id="routing" name="Cursor Routing"> for full details).
+    The cursor is moved by simulating vertical arrow-key presses.
+    This command doesn't always work because some applications
+    either move the cursor somewhat unpredictably
+    or use the arrow keys for purposes other than cursor motion.
+    It's somewhat safer than the other cursor routing commands, though,
+    because it makes no attempt to simulate the left- and right-arrows.
+  <tag>PASTE<label id="command-PASTE"></tag>
+    Insert the characters within the cut buffer at the current cursor location
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+  <tag>RESTARTBRL<label id="command-RESTARTBRL"></tag>
+    Stop, and then restart the braille display driver.
+  <tag>RESTARTSPEECH<label id="command-RESTARTSPEECH"></tag>
+    Stop, and then restart the speech synthesizer driver.
+</descrip>
+
+<sect2>Character Commands<label id="commands-characters"><p>
+<descrip>
+  <tag>ROUTE<label id="command-ROUTE"></tag>
+    Route (bring) the cursor to the character
+    associated with the routing key
+    (see <ref id="routing" name="Cursor Routing"> for full details).
+    The cursor is moved by simulating arrow-key presses.
+    This command doesn't always work because some applications
+    either move the cursor somewhat unpredictably
+    or use the arrow keys for purposes other than cursor motion.
+  <tag>CUTBEGIN<label id="command-CUTBEGIN"></tag>
+    Anchor the start of the cut block at the character
+    associated with the routing key
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+    This command clears the cut buffer.
+  <tag>CUTAPPEND<label id="command-CUTAPPEND"></tag>
+    Anchor the start of the cut block at the character
+    associated with the routing key
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+    This command doesn't clear the cut buffer.
+  <tag>CUTRECT<label id="command-CUTRECT"></tag>
+    Anchor the end of the cut block at the character
+    associated with the routing key,
+    and append the rectangular region to the cut buffer
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+  <tag>CUTLINE<label id="command-CUTLINE"></tag>
+    Anchor the end of the cut block at the character
+    associated with the routing key,
+    and append the linear region to the cut buffer
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+  <tag>COPYCHARS<label id="command-COPYCHARS"></tag>
+    Copy the character block
+    anchored by the two routing keys
+    to the cut buffer
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+  <tag>APNDCHARS<label id="command-APNDCHARS"></tag>
+    Append the character block
+    anchored by the two routing keys
+    to the cut buffer
+    (see <ref id="cut" name="Cut and Paste"> for full details).
+  <tag>PRINDENT/NXINDENT<label id="command-PRINDENT-NXINDENT"></tag>
+    Go up/down to the nearest line which isn't indented
+    more than the column associated with the routing key.
+  <tag>DESCCHAR<label id="command-DESCCHAR"></tag>
+    Momentarily (see the <ref id="options-message-timeout" name="-M"> command line option)
+    display a message describing the character associated with the routing key.
+    It reveals
+    the decimal and hexadecimal values of the character,
+    the foreground and background colours,
+    and, when present, special attributes (<tt/bright/ and <tt/blink/).
+    The message looks like this:
+    <tscreen>char 65 (0x41): white on black bright blink</tscreen>
+  <tag>SETLEFT<label id="command-SETLEFT"></tag>
+    Horizontally reposition the braille window so that
+    its left edge is at the column associated with the routing key.
+    This feature makes it very easy to put the window exactly where it's needed,
+    and, therefore, for displays which have routing keys,
+    almost eliminates the need for a lot of elementary window motion
+    (like
+    the <ref id="command-CHRLT-CHRRT" name="CHRLT/CHRRT">
+    and <ref id="command-HWINLT-HWINRT" name="HWINLT/HWINRT">
+    commands).
+  <tag>PRDIFCHAR/NXDIFCHAR<label id="command-PRDIFCHAR-NXDIFCHAR"></tag>
+    Go up/down to the nearest line which has a different character
+    in the column associated with the routing key.
+</descrip>
+
+<sect2>Base Commands<label id="commands-base"><p>
+<descrip>
+  <tag>SWITCHVT<label id="command-SWITCHVT"></tag>
+    Switch to the virtual terminal whose number
+    (counting from 1) matches that of the routing key.
+    See also the <ref id="command-SWITCHVT_PREV-SWITCHVT_NEXT" name="SWITCHVT_PREV/SWITCHVT_NEXT"> virtual terminal switching commands.
+  <tag>SETMARK<label id="command-SETMARK"></tag>
+    Mark (remember) the current position of the braille window
+    in a register associated with the routing key.
+    See the <ref id="command-GOTOMARK" name="GOTOMARK"> command.
+    This feature only affects the current virtual terminal.
+  <tag>GOTOMARK<label id="command-GOTOMARK"></tag>
+    Move the braille window to the position formerly marked
+    (see the <ref id="command-SETMARK" name="SETMARK"> command)
+    with the same routing key.
+    This feature only affects the current virtual terminal.
+</descrip>
+
+<sect1>The Configuration File<label id="configure"><p>
+System defaults for many settings may be established within a configuration file.
+The default name for this file is <tt>/etc/brltty.conf</tt>,
+although it may be overridden with the
+<ref id="options-configuration-file" name="-f"> command line option.
+It doesn't need to exist.
+A template for it can be found within the <tt/Documents/ subdirectory.
+
+Blank lines are ignored.
+A comment begin with a number sign (<tt/&num;/),
+and continues to the end of the line.
+The following directives are recognized:
+<descrip>
+  <tag><tt/api-parameters/ <em/name/<tt/=/<em/value/<tt/,/...<label id="configure-api-parameters"></tag>
+    Specify parameters for the Application Programming Interface.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    For a description of the parameters accepted by the interface,
+    please see the <bf/BrlAPI/ reference manual.
+    See the <ref id="build-api-parameters" name="--with-api-parameters">
+    build option for the defaults established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-api-parameters" name="-A"> command line option.
+  <tag><tt/attributes-table/ <em/file/<label id="configure-attributes-table"></tag>
+    Specify the attributes table
+    (see section <ref id="table-attributes" name="Attributes Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.atb/ extension is optional.
+    The default is to use the built-in table
+    (see the <ref id="build-attributes-table" name="--with-attributes-table"> build option).
+    This directive can be overridden with the
+    <ref id="options-attributes-table" name="-a"> command line option.
+  <tag><tt/braille-device/ <em/device/<tt/,/...<label id="configure-braille-device"></tag>
+    Specify the device to which the braille display is connected
+    (see section <ref id="operand-braille-device" name="Braille Device Specification">).
+    See the <ref id="build-braille-device" name="--with-braille-device">
+    build option for the default established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-braille-device" name="-d"> command line option.
+  <tag><tt/braille-driver/ <em/driver/<tt/,/...|<tt/auto/<label id="configure-braille-driver"></tag>
+    Specify the braille display driver
+    (see section <ref id="operand-driver" name="Driver Specification">).
+    The default is to perform autodetection.
+    This directive can be overridden with the
+    <ref id="options-braille-driver" name="-b"> command line option.
+  <tag><tt/braille-parameters/ [<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="configure-braille-parameters"></tag>
+    Specify parameters for the braille display drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="build-braille-parameters" name="--with-braille-parameters">
+    build option for the defaults established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-braille-parameters" name="-B"> command line option.
+  <tag><tt/contraction-table/ <em/file/<label id="configure-contraction-table"></tag>
+    Specify the contraction table
+    (see section <ref id="table-contraction" name="Contraction Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ctb/ extension is optional.
+    The contraction table is used when the 6-dot braille feature is activated
+    (see the <ref id="command-SIXDOTS" name="SIXDOTS"> command
+    and the <ref id="preference-text-style" name="Text Style"> preference).
+    The default is to display uncontracted 6-dot braille.
+    This directive can be overridden with the
+    <ref id="options-contraction-table" name="-c"> command line option.
+    It isn't available if the
+    <ref id="build-contracted-braille" name="--disable-contracted-braille"> build option was specified.
+  <tag><tt/keyboard-table/ <em/file/|<tt/auto/<label id="configure-keyboard-table"></tag>
+    Specify the keyboard table
+    (see section <ref id="table-key" name="Key Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ktb/ extension is optional.
+    The default is not to use a keyboard table.
+    This directive can be overridden with the
+    <ref id="options-keyboard-table" name="-k"> command line option.
+  <tag><tt/keyboard-properties/ <em/name/<tt/=/<em/value/<tt/,/...<label id="configure-keyboard-properties"></tag>
+    Specify the properties of the keyboard(s) to be monitored.
+    If the same property is specified more than once,
+    then its rightmost assignment is used.
+    See section <ref id="keyboard-properties" name="Keyboard Properties">
+    for a list of the properties which may be specified.
+    The default is to monitor all keyboards.
+    This directive can be overridden with the
+    <ref id="options-keyboard-properties" name="-K"> command line option.
+  <tag><tt/midi-device/ <em/device/<label id="configure-midi-device"></tag>
+    Specify the device to use for the Musical Instrument Digital Interface
+    (see section <ref id="operand-midi-device" name="MIDI Device Specification">).
+    This directive can be overridden with the
+    <ref id="options-midi-device" name="-m"> command line option.
+    It isn't available if the
+    <ref id="build-midi-support" name="--disable-midi-support"> build option was specified.
+  <tag><tt/pcm-device/ <em/device/<label id="configure-pcm-device"></tag>
+    Specify the device to use for digital audio
+    (see section <ref id="operand-pcm-device" name="PCM Device Specification">).
+    This directive can be overridden with the
+    <ref id="options-pcm-device" name="-p"> command line option.
+    It isn't available if the
+    <ref id="build-pcm-support" name="--disable-pcm-support"> build option was specified.
+  <tag><tt/preferences-file/ <em/file/<label id="configure-preferences-file"></tag>
+    Specify the location of the file which is to be used
+    for the saving and loading of user preferences.
+    If a relative path is supplied, then it's anchored at <tt>/var/lib/brltty</>.
+    The default is to use <tt/brltty.prefs/.
+    This directive can be overridden with the
+    <ref id="options-preferences-file" name="-F"> command line option.
+  <tag><tt/release-device/ <em/boolean/<label id="configure-release-device"></tag>
+    Whether or not to release the device to which the braille display is connected
+    when the current screen or window can't be read.
+    <descrip>
+      <tag/on/Release the device.
+      <tag/off/Don't release the device.
+    </descrip>
+    The default setting is <tt/on/ on Windows platforms
+    and <tt/off/ on all other platforms.
+    This directive can be overridden with the
+    <ref id="options-release-device" name="-r"> command line option.
+  <tag><tt/screen-driver/ <em/driver/<label id="configure-screen-driver"></tag>
+    Specify the screen driver
+    (see section <ref id="screen" name="Supported Screen Drivers">).
+    See the <ref id="build-screen-driver" name="--with-screen-driver">
+    build option for the default established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-screen-driver" name="-x"> command line option.
+  <tag><tt/screen-parameters/ [<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="configure-screen-parameters"></tag>
+    Specify parameters for the screen drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="screen" name="Supported Screen Drivers">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="build-screen-parameters" name="--with-screen-parameters">
+    build option for the defaults established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-screen-parameters" name="-X"> command line option.
+  <tag><tt/speech-driver/ <em/driver/<tt/,/...|<tt/auto/<label id="configure-speech-driver"></tag>
+    Specify the speech synthesizer driver
+    (see section <ref id="operand-driver" name="Driver Specification">).
+    The default is to perform autodetection.
+    This directive can be overridden with the
+    <ref id="options-speech-driver" name="-s"> command line option.
+    It isn't available if the
+    <ref id="build-speech-support" name="--disable-speech-support"> build option was specified.
+  <tag><tt/speech-input/ <em/name/<label id="configure-speech-input"></tag>
+    Specify the name of the file system object
+    (FIFO, named pipe, named socket, etc)
+    which can be used by other applications
+    for text-to-speech conversion via BRLTTY's speech driver.
+    This directive can be overridden with the
+    <ref id="options-speech-input" name="-i"> command line option.
+    It isn't available if the
+    <ref id="build-speech-support" name="--disable-speech-support"> build option was specified.
+  <tag><tt/speech-parameters/ [<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="configure-speech-parameters"></tag>
+    Specify parameters for the speech synthesizer drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="build-speech-parameters" name="--with-speech-parameters">
+    build option for the defaults established during the build procedure.
+    This directive can be overridden with the
+    <ref id="options-speech-parameters" name="-S"> command line option.
+  <tag><tt/text-table/ <em/file/|<tt/auto/<label id="configure-text-table"></tag>
+    Specify the text table
+    (see section <ref id="table-text" name="Text Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ttb/ extension is optional.
+    The default is to perform locale-based autoselection,
+    with fallback to the built-in table
+    (see the <ref id="build-text-table" name="--with-text-table"> build option).
+    This directive can be overridden with the
+    <ref id="options-text-table" name="-t"> command line option.
+</descrip>
+
+<sect1>Command Line Options<label id="options"><p>
+Many settings can be explicitly specified when invoking BRLTTY.
+The <tt/brltty/ command accepts the following options:
+<descrip>
+  <tag><tt/-a/<em/file/ <tt/--attributes-table=/<em/file/<label id="options-attributes-table"></tag>
+    Specify the attributes table
+    (see section <ref id="table-attributes" name="Attributes Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.atb/ extension is optional.
+    See the <ref id="configure-attributes-table" name="attributes-table">
+    configuration file directive for the default run-time setting.
+    This setting can be changed with the
+    <ref id="preference-attributes-table" name="Attributes Table"> preference.
+  <tag><tt/-b/<em/driver/<tt/,/...|<tt/auto/ <tt/--braille-driver=/<em/driver/<tt/,/...|<tt/auto/<label id="options-braille-driver"></tag>
+    Specify the braille display driver
+    (see section <ref id="operand-driver" name="Driver Specification">).
+    See the <ref id="configure-braille-driver" name="braille-driver">
+    configuration file directive for the default run-time setting.
+  <tag><tt/-c/<em/file/ <tt/--contraction-table=/<em/file/<label id="options-contraction-table"></tag>
+    Specify the contraction table
+    (see section <ref id="table-contraction" name="Contraction Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ctb/ extension is optional.
+    The contraction table is used when the 6-dot braille feature is activated
+    (see the <ref id="command-SIXDOTS" name="SIXDOTS"> command
+    and the <ref id="preference-text-style" name="Text Style"> preference).
+    See the <ref id="configure-contraction-table" name="contraction-table">
+    configureation file directive for the default run-time setting.
+    This setting can be changed with the
+    <ref id="preference-contraction-table" name="Contraction Table"> preference.
+    This option isn't available if the
+    <ref id="build-contracted-braille" name="--disable-contracted-braille"> build option was specified.
+  <tag><tt/-d/<em/device/<tt/,/... <tt/--braille-device=/<em/device/<tt/,/...<label id="options-braille-device"></tag>
+    Specify the device to which the braille display is connected
+    (see section <ref id="operand-braille-device" name="Braille Device Specification">).
+    See the <ref id="configure-braille-device" name="braille-device">
+    configuration file directive for the default run-time setting.
+  <tag><tt/-e/ <tt/--standard-error/<label id="options-standard-error"></tag>
+    Write diagnostic messages to standard error.
+    The default is to record them via <tt/syslog/.
+  <tag><tt/-f/<em/file/ <tt/--configuration-file=/<em/file/<label id="options-configuration-file"></tag>
+    Specify the location of the <ref id="configure" name="configuration file">
+    which is to be used for the establishing of default run-time settings.
+  <tag><tt/-h/ <tt/--help/<label id="options-help"></tag>
+    Display a summary of the command line options accepted by BRLTTY,
+    and then exit.
+  <tag><tt/-i/<em/name/ <tt/--speech-input=/<em/name/<label id="options-speech-input"></tag>
+    Specify the name of the file system object
+    (FIFO, named pipe, named socket, etc)
+    which can be used by other applications
+    for text-to-speech conversion via BRLTTY's speech driver.
+    If not specified, the file system object is not created.
+    See the <ref id="configure-speech-input" name="speech-input">
+    configuration file directive for the default run-time setting.
+    This option isn't available if the
+    <ref id="build-speech-support" name="--disable-speech-support"> build option was specified.
+  <tag><tt/-k/<em/file/ <tt/--keyboard-table=/<em/file/<label id="options-keyboard-table"></tag>
+    Specify the keyboard table
+    (see section <ref id="table-key" name="Key Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ktb/ extension is optional.
+    See the <ref id="configure-keyboard-table" name="keyboard-table">
+    configureation file directive for the default run-time setting.
+    This setting can be changed with the
+    <ref id="preference-keyboard-table" name="Keyboard Table"> preference.
+  <tag><tt/-l/<em/level/ <tt/--log-level=/<em/level/<label id="options-log-level"></tag>
+    Specify the severity threshold for diagnostic message generation.
+    The following levels are recognized.
+    <descrip>
+      <tag/0/emergency
+      <tag/1/alert
+      <tag/2/critical
+      <tag/3/error
+      <tag/4/warning
+      <tag/5/notice
+      <tag/6/information
+      <tag/7/debug
+    </descrip>
+    Either the number or the name may be supplied,
+    and the name may be abbreviated.
+    If not specified,
+    then <tt/information/ is assumed
+    (see the <ref id="options-quiet" name="-q"> option for more details).
+  <tag><tt/-m/<em/device/ <tt/--midi-device=/<em/device/<label id="options-midi-device"></tag>
+    Specify the device to use for the Musical Instrument Digital Interface
+    (see section <ref id="operand-midi-device" name="MIDI Device Specification">).
+    See the <ref id="configure-midi-device" name="midi-device">
+    configuration file directive for the default run-time setting.
+    This option isn't available if the
+    <ref id="build-midi-support" name="--disable-midi-support"> build option was specified.
+  <tag><tt/-n/ <tt/--no-daemon/<label id="options-no-daemon"></tag>
+    Specify that BRLTTY is to remain in the foreground.
+    If not specified, then BRLTTY becomes a background process (daemon)
+    after initializing itself but before starting any of the selected drivers.
+  <tag><tt/-p/<em/device/ <tt/--pcm-device=/<em/device/<label id="options-pcm-device"></tag>
+    Specify the device to use for digital audio
+    (see section <ref id="operand-pcm-device" name="PCM Device Specification">).
+    See the <ref id="configure-pcm-device" name="pcm-device">
+    configuration file directive for the default run-time setting.
+    This option isn't available if the
+    <ref id="build-pcm-support" name="--disable-pcm-support"> build option was specified.
+  <tag><tt/-q/ <tt/--quiet/<label id="options-quiet"></tag>
+    Log less information.
+    This option changes the default log level
+    (see the <ref id="options-log-level" name="-l"> option)
+    to <tt/notice/ if
+    either the <ref id="options-verify" name="-v">
+    or the <ref id="options-version" name="-V">
+    option is specified, and to <tt/warning/ otherwise.
+  <tag><tt/-r/ <tt/--release-device/<label id="options-release-device"></tag>
+    Release the device to which the braille display is connected
+    when the current screen or window can't be read.
+    See the <ref id="configure-release-device" name="release-device">
+    configuration file directive for the default run-time setting.
+  <tag><tt/-s/<em/driver/<tt/,/...|<tt/auto/ <tt/--speech-driver=/<em/driver/<tt/,/...|<tt/auto/<label id="options-speech-driver"></tag>
+    Specify the speech synthesizer driver
+    (see section <ref id="operand-driver" name="Driver Specification">).
+    See the <ref id="configure-speech-driver" name="speech-driver">
+    configuration file directive for the default run-time setting.
+    This option isn't available if the
+    <ref id="build-speech-support" name="--disable-speech-support"> build option was specified.
+  <tag><tt/-t/<em/file/ <tt/--text-table=/<em/file/<label id="options-text-table"></tag>
+    Specify the text table
+    (see section <ref id="table-text" name="Text Tables"> for details).
+    If a relative path is supplied, then it's anchored at <tt>/etc/brltty</>
+    (see the <ref id="build-data-directory" name="--with-data-directory">
+    and the <ref id="build-execute-root" name="--with-execute-root">
+    build options for more details).
+    The <tt/.ttb/ extension is optional.
+    See the <ref id="configure-text-table" name="text-table">
+    configureation file directive for the default run-time setting.
+    This setting can be changed with the
+    <ref id="preference-text-table" name="Text Table"> preference.
+  <tag><tt/-v/ <tt/--verify/<label id="options-verify"></tag>
+    Display the current versions
+    of BRLTTY itself,
+    of the server side of its application programming interface,
+    and of the selected braille and speech drivers,
+    and then exit.
+    If the <ref id="options-quiet" name="-q"> option isn't specified,
+    then also display the values of the options after all sources have been considered.
+    If more than one braille driver
+    (see the <ref id="options-braille-driver" name="-b"> command line option)
+    and/or more than one braille device
+    (see the <ref id="options-braille-device" name="-d"> command line option)
+    has been specified then braille display autodetection is performed.
+    If more than one speech driver
+    (see the <ref id="options-speech-driver" name="-s"> command line option)
+    has been specified then speech synthesizer autodetection is performed.
+  <tag><tt/-x/<em/driver/ <tt/--screen-driver=/<em/driver/<label id="options-screen-driver"></tag>
+    Specify the screen driver
+    (see section <ref id="screen" name="Supported Screen Drivers">).
+    See the <ref id="configure-screen-driver" name="screen-driver">
+    configuration file directive for the default run-time setting.
+  <tag><tt/-A/<em/name/<tt/=/<em/value/<tt/,/... <tt/--api-parameters=/<em/name/<tt/=/<em/value/<tt/,/...<label id="options-api-parameters"></tag>
+    Specify parameters for the Application Programming Interface.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    For a description of the parameters accepted by the interface,
+    please see the <bf/BrlAPI/ reference manual.
+    See the <ref id="configure-api-parameters" name="api-parameters">
+    configuration file directive for the default run-time settings.
+  <tag><tt/-B/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/... <tt/--braille-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="options-braille-parameters"></tag>
+    Specify parameters for the braille display drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-braille-parameters" name="braille-parameters">
+    configuration file directive for the default run-time settings.
+  <tag><tt/-E/ <tt/--environment-variables/<label id="options-environment-variables"></tag>
+    Recognize environment variables when determining
+    the default settings for unspecified command line options
+    (see section <ref id="options" name="Command Line Options">).
+    If this option is specified, 
+    and if the environment variable associated with an unspecified option is defined,
+    then the value of that environment variable is used.
+    The names of these environment variables are based on
+    the long names of the options they correspond to:
+    <itemize>   
+      <item>All letters are in upper case.
+      <item>Underscores (<tt/_/) are used instead of minus signs (<tt/-/).
+      <item>The prefix <tt/BRLTTY_/ is added.
+    </itemize>   
+    This option is particularly useful on the Linux operating system
+    as it allows default settings to be passed to BRLTTY via boot parameters.
+    The following environment variables are supported:
+    <descrip>
+      <tag><tt/BRLTTY_API_PARAMETERS/</tag>
+        Parameters for the Application Programming Interface
+        (see the <ref id="options-api-parameters" name="-A"> command line option).
+      <tag><tt/BRLTTY_ATTRIBUTES_TABLE/</tag>
+        The attributes table
+        (see the <ref id="options-attributes-table" name="-a"> command line option).
+      <tag><tt/BRLTTY_BRAILLE_DEVICE/</tag>
+        The braille display device
+        (see the <ref id="options-braille-device" name="-d"> command line option).
+      <tag><tt/BRLTTY_BRAILLE_DRIVER/</tag>
+        The braille display driver
+        (see the <ref id="options-braille-driver" name="-b"> command line option).
+      <tag><tt/BRLTTY_BRAILLE_PARAMETERS/</tag>
+        Parameters for the braille display driver
+        (see the <ref id="options-braille-parameters" name="-B"> command line option).
+      <tag><tt/BRLTTY_CONFIGURATION_FILE/</tag>
+        The configuration file
+        (see the <ref id="options-configuration-file" name="-f"> command line option).
+      <tag><tt/BRLTTY_CONTRACTION_TABLE/</tag>
+        The contraction table
+        (see the <ref id="options-contraction-table" name="-c"> command line option).
+      <tag><tt/BRLTTY_KEYBOARD_TABLE/</tag>
+        The keyboard table
+        (see the <ref id="options-keyboard-table" name="-k"> command line option).
+      <tag><tt/BRLTTY_KEYBOARD_PROPERTIES/</tag>
+        The keyboard properties
+        (see the <ref id="options-keyboard-properties" name="-K"> command line option).
+      <tag><tt/BRLTTY_MIDI_DEVICE/</tag>
+        The Musical Instrument Digital Interface device
+        (see the <ref id="options-midi-device" name="-m"> command line option).
+      <tag><tt/BRLTTY_PCM_DEVICE/</tag>
+        The digital audio device
+        (see the <ref id="options-pcm-device" name="-p"> command line option).
+      <tag><tt/BRLTTY_PREFERENCES_FILE/</tag>
+        The location of the file which is to be used
+        for the saving and loading of user preferences
+        (see the <ref id="options-preferences-file" name="-F"> command line option).
+      <tag><tt/BRLTTY_RELEASE_DEVICE/</tag>
+        Whether or not to release the device to which the braille display is connected
+        when the current screen or window can't be read
+        (see the <ref id="options-release-device" name="-r"> command line option).
+      <tag><tt/BRLTTY_SCREEN_PARAMETERS/</tag>
+        Parameters for the screen driver
+        (see the <ref id="options-screen-parameters" name="-X"> command line option).
+      <tag><tt/BRLTTY_SPEECH_DRIVER/</tag>
+        The speech synthesizer driver
+        (see the <ref id="options-speech-driver" name="-s"> command line option).
+      <tag><tt/BRLTTY_SPEECH_INPUT/</tag>
+        The name of the file system object
+        which can be used by other applications
+        for text-to-speech conversion via BRLTTY's speech driver
+        (see the <ref id="options-speech-input" name="-i"> command line option).
+      <tag><tt/BRLTTY_SPEECH_PARAMETERS/</tag>
+        Parameters for the speech synthesizer driver
+        (see the <ref id="options-speech-parameters" name="-S"> command line option).
+      <tag><tt/BRLTTY_TEXT_TABLE/</tag>
+        The text table
+        (see the <ref id="options-text-table" name="-t"> command line option).
+    </descrip>
+  <tag><tt/-F/<em/file/ <tt/--preferences-file=/<em/file/<label id="options-preferences-file"></tag>
+    Specify the location of the file which is to be used
+    for the saving and loading of user preferences.
+    If a relative path is supplied, then it's anchored at <tt>/var/lib/brltty</>.
+    See the <ref id="configure-preferences-file" name="preferences-file">
+    configuration file directive for the default run-time setting.
+  <tag><tt/-I/ <tt/--install-service/<label id="options-install-service"></tag>
+    Install BRLTTY as the BrlAPI service.
+    This means that:
+    <itemize>
+      <item>BRLTTY will be automatically started when the system is booted.
+      <item>Applications can know that a BrlAPI server is running.
+    </itemize>
+    This option is only supported on the Windows platform.
+  <tag><tt/-K/<em/name/<tt/=/<em/value/<tt/,/... <tt/--keyboard-properties=/<em/name/<tt/=/<em/value/<tt/,/...<label id="options-keyboard-properties"></tag>
+    Specify the properties of the keyboard(s) to be monitored.
+    If the same property is specified more than once,
+    then its rightmost assignment is used.
+    See section <ref id="keyboard-properties" name="Keyboard Properties">
+    for a list of the properties which may be specified.
+    See the <ref id="configure-keyboard-properties" name="keyboard-properties">
+    configuration file directive for the default run-time settings.
+  <tag><tt/-M/<em/csecs/ <tt/--message-timeout=/<em/csecs/<label id="options-message-timeout"></tag>
+    Specify the amount of time (in hundredths of a second)
+    that BRLTTY keeps its own internally generated messages on the braille display.
+    If not specified, then <tt/400/ (4 seconds) is assumed.
+  <tag><tt/-N/ <tt/--no-api/<label id="options-no-api"></tag>
+    Disable the application programming interface.
+  <tag><tt/-P/<em/file/ <tt/--pid-file=/<em/file/<label id="options-pid-file"></tag>
+    Specify the file wherein BRLTTY is to write its process identifier (pid).
+    If not specified, BRLTTY doesn't write its process identifier anywhere.
+  <tag><tt/-R/ <tt/--remove-service/<label id="options-remove-service"></tag>
+    Remove the BrlAPI service.
+    This means that:
+    <itemize>
+      <item>BRLTTY will not be automatically started when the system is booted.
+      <item>Applications can know that no BrlAPI server is running.
+    </itemize>
+    This option is only supported on the Windows platform.
+  <tag><tt/-S/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/... <tt/--speech-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="options-speech-parameters"></tag>
+    Specify parameters for the speech synthesizer drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="drivers" name="Driver Identification Codes">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-speech-parameters" name="speech-parameters">
+    configuration file directive for the default run-time settings.
+  <tag><tt/-V/ <tt/--version/<label id="options-version"></tag>
+    Display the current versions
+    of BRLTTY itself,
+    of the server side of its application programming interface,
+    and of those drivers which have been linked into its binary,
+    and then exit.
+    If the <ref id="options-quiet" name="-q"> option isn't specified,
+    then also display copyright information.
+  <tag><tt/-X/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/... <tt/--screen-parameters=/[<em/driver/<tt/:/]<em/name/<tt/=/<em/value/<tt/,/...<label id="options-screen-parameters"></tag>
+    Specify parameters for the screen drivers.
+    If the same parameter is specified more than once,
+    then its rightmost assignment is used.
+    If a parameter name is qualified by a driver
+    (see section <ref id="screen" name="Supported Screen Drivers">)
+    then that setting only applies to that driver;
+    if it isn't then it applies to all drivers.
+    For a description of the parameters accepted by a specific driver,
+    please see the documentation for that driver.
+    See the <ref id="configure-screen-parameters" name="screen-parameters">
+    configuration file directive for the default run-time settings.
+</descrip>
+
+<sect>Feature Descriptions<p>
+
+<sect1>Cursor Routing<label id="routing"><p>
+When moving the braille window around the screen
+while examining the text, say, in an editor,
+you often need to bring the cursor
+to a specific character within the braille window.
+You'll probably find this to be a rather difficult task for a number of reasons.
+One is that you may not know where the cursor is,
+and that you may lose your place while trying to find it.
+Another is that the cursor may move unpredictably as the arrow keys are pressed
+(some editors, for example, don't allow the cursor to be
+more to the right than the end of the line it's on).
+Cursor routing provides just such a capability
+by knowing where the cursor is,
+by simulating the same arrow-key presses which you'd have to enter manually,
+and by monitoring the progress of the cursor as it moves.
+
+Some braille displays have a button, known as a routing key, above each cell.
+These keys use the <ref id="command-ROUTE" name="ROUTE"> command
+to route the cursor right to the desired location.
+
+Cursor routing, while very convenient and effective,
+is, strictly speaking, not completely reliable.
+One reason for this is that its current implementation assumes
+VT100 cursor key escape sequences.
+Another is that some applications do non-standard things
+in response to detecting that a cursor key has been pressed.
+A minor problem found within some editors (like <tt/vi/),
+as already mentioned above,
+is that they throw in some unpredictable horizontal motion
+when vertical motion is requested
+because they don't allow the cursor to be to the right of the end of a line.
+A major problem found within some web browsers (like <tt/lynx/)
+is that the up- and down-arrow keys are used to move among the links
+(which may skip lines and/or move the cursor horizontally,
+but which rarely just moves the cursor one line in the desired direction),
+and that the left- and right-arrow keys are used to select links
+(which has absolutely nothing to do with any form of cursor motion whatsoever,
+and which even totally changes the screen content).
+
+Cursor routing may not work very well on a heavily loaded system,
+and definitely doesn't work very well when working on a remote system over a slow link.
+This is so because of all of the checks which must be made along the way
+in order to deal with unpredictable cursor motion
+and in order to ensure that any mistake has at least a fighting chance to be undone.
+Even  though BRLTTY tries to be fairly clever,
+it must still essentially wait to see what happens after each simulated arrow-key press.
+
+Once a cursor routing request has been made,
+BRLTTY keeps trying to route the cursor to the desired location until
+a timeout expires before the cursor reaches that location,
+the cursor seems to be moving in the wrong direction,
+or you switch to a different virtual terminal.
+An attempt is first made to use vertical motion
+to bring the cursor to the right line,
+and, only if that succeeds, an attempt is then made
+to use horizontal motion to bring the cursor to the right column.
+If another request is made while one is still in progress,
+then the first one is aborted and the second one is initiated.
+
+A safer but less powerful cursor routing command,
+<ref id="command-CSRJMP_VERT" name="CSRJMP_VERT">,
+uses just vertical motion to bring the cursor
+to anywhere on the top line of the braille window.
+It's especially useful in conjunction with applications (like <tt/lynx/)
+wherein horizontal cursor motion must never be attempted.
+
+<sect1>Cut and Paste<label id="cut"><p>
+This feature enables you to grab some text which is already on the screen 
+and re-enter it at the current cursor position.
+Using it saves time and avoids errors
+when a long and/or complicated piece of text needs to be copied,
+and even when the same short and simple piece of text needs to be copied many times.
+It's particularly useful for things like
+long file names,
+complicated command lines,
+E-mail addresses,
+and URLs. Cutting and pasting text involves three simple steps:
+<enum>
+  <item>
+    Mark either the top-left corner of the rectangular area
+    or the beginning of the linear area
+    on the screen which is to be grabbed (cut).
+    If your display has routing keys,
+    then move the braille window so that
+    the first character to be cut appears anywhere within it, and then:
+    <itemize>
+      <item>
+        invoke the <ref id="command-CUTBEGIN" name="CUTBEGIN"> command
+        to start a new cut buffer
+      <item>
+        invoke the <ref id="command-CUTAPPEND" name="CUTAPPEND"> command
+        to append to the existing cut buffer
+    </itemize>
+    by pressing the key(s) associated with it
+    and then pressing the routing key associated with the character.
+  <item>
+    Mark either the bottom-right corner of the rectangular area
+    or the end of the linear area
+    on the screen which is to be grabbed (cut).
+    If your display has routing keys,
+    then move the braille window so that
+    the last character to be cut appears anywhere within it, and then
+    <itemize>
+      <item>
+        invoke the <ref id="command-CUTRECT" name="CUTRECT"> command
+        to cut a rectangular area
+      <item>
+        invoke the <ref id="command-CUTLINE" name="CUTLINE"> command
+        to cut a linear area
+    </itemize>
+    by pressing the key(s) associated with it
+    and then pressing the routing key associated with the character.
+    Marking the end of the cut area
+    appends the selected screen content to the cut buffer.
+    Excess white-space is removed from the end of each line in the cut buffer
+    so that unwanted trailing spaces won't be pasted back in.
+    Control characters are replaced with blanks.
+  <item>
+    Insert (paste) the text where it's needed.
+    Place the cursor over the character where the text is to be pasted,
+    and invoke the <ref id="command-PASTE" name="PASTE"> command.
+    You can paste the same text any number of times without recutting it.
+    This description assumes that you're already in some sort of input mode.
+    If you paste when you're in some other kind of mode
+    (like <tt/vi/'s command mode),
+    then you'd better be aware of what the characters in the cut buffer will do.
+</enum>
+
+The cut buffer is also used by
+the <ref id="command-PRSEARCH-NXSEARCH" name="PRSEARCH/NXSEARCH"> commands.
+
+<sect1>Pointer (Mouse) Support via GPM<label id="gpm"><p>
+If BRLTTY is configured with the <ref id="build-gpm" name="--enable-gpm"> build option
+on a system where the <tt/gpm/ application has been installed,
+then it'll interact with the pointer (mouse).
+
+Moving the pointer drags the braille window
+(see the <ref id="preference-window-follows-pointer" name="Window Follows Pointer"> preference).
+Whenever the pointer is moved beyond the edge of the braille window,
+the braille window is dragged along (one character at a time).
+This gives the braille user another two-dimensional way
+to inspect the screen content
+or to quickly move the braille window to a desired location.
+It also gives a sighted observer an easy way to move the braille window
+to something he'd like the braille user to read.
+
+<tt/gpm/ uses reverse video to show where the pointer is.
+Underlining of highlighted characters
+(see the <ref id="command-ATTRVIS" name="ATTRVIS"> command for details)
+should be turned on, therefore, when the braille user wishes to use the pointer.
+
+This feature also gives the braille user access to <tt/gpm/'s cut-and-paste capability.
+Although you should read <tt/gpm/'s own documentation,
+here are some notes on how it works.
+<itemize>
+  <item>
+    Copy the current character to the cut buffer
+    by single-clicking the left button.
+  <item>
+    Copy the current word (space-delimited) to the cut buffer
+    by double-clicking the left button.
+  <item>
+    Copy the current line to the cut buffer
+    by tripple-clicking the left button.
+  <item>
+    Copy a linear region to the cut buffer as follows:
+    <enum>
+      <item>
+        Place the pointer on the first character of the region.
+      <item>
+        Press (and hold) the left button.
+      <item>
+        Move the pointer to the last character of the region
+        (all currently selected characters are highlighted).
+      <item>
+        Release the left button.
+    </enum>
+  <item>
+    Paste (input) the current contents of the cut buffer
+    by clicking the middle button of a three-button mouse
+    or by clicking the right button of a two-button mouse.
+  <item>
+    Append to the cut buffer
+    by using the right button of a three-button mouse.
+</itemize>
+
+<sect1>Alert Tunes<label id="tunes"><p>
+BRLTTY alerts you to the occurrence of significant events
+by playing short predefined tunes.
+This feature can be activated and deactivated with
+either the <ref id="command-TUNES" name="TUNES"> command
+or the <ref id="preference-alert-tunes" name="Alert Tunes"> preference.
+The tunes are played via the internal beeper by default,
+but other alternatives can be selected
+with the <ref id="preference-tune-device" name="Tune Device"> preference.
+
+Each significant event is associated, from highest to lowest priority,
+with one or more of the following:
+<descrip>
+  <tag/a tune/
+    If a tune has been associated with the event,
+    if the <ref id="preference-alert-tunes" name="Alert Tunes"> preference
+    (see also the <ref id="command-TUNES" name="TUNES"> command)
+    is active,
+    and if the selected tune device
+    (see the <ref id="preference-tune-device" name="Tune Device"> preference)
+    can be opened,
+    then the tune is played.
+  <tag/a dot pattern/
+    If a dot pattern has been associated with the event,
+    and if the <ref id="preference-alert-dots" name="Alert Dots"> preference is active,
+    then the dot pattern is briefly displayed on every braille cell.
+    Some braille displays don't respond quickly enough
+    for this mechanism to work effectively.
+  <tag/a message/
+    If a message has been associated with the event,
+    and if the <ref id="preference-alert-messages" name="Alert Messages"> preference is active,
+    then it is displayed for a few seconds
+    (see the <ref id="options-message-timeout" name="-M"> command line option).
+</descrip>
+
+These events include:
+<itemize>
+  <item>
+    When the braille display driver starts or stops.
+  <item>
+    When a lengthy command completes.
+  <item>
+    When a command cannot be executed.
+  <item>
+    When a mark is set.
+  <item>
+    When the start or end of the cut block is set.
+  <item>
+    When a feature is activated or deactivated.
+  <item>
+    When cursor tracking is turned on or off.
+  <item>
+    When the screen image is frozen or unfrozen.
+  <item>
+    When the braille window wraps
+    either down to the beginning of the next line
+    or up to the end of the previous line.
+  <item>
+    When identical lines are skipped.
+  <item>
+    When a requested motion cannot be performed.
+  <item>
+    When cursor routing starts, finishes, or fails.
+</itemize>
+
+<sect1>Preferences Settings<label id="preferences"><p>
+When BRLTTY starts, it loads a file which contains your preferences settings.
+The file doesn't need to exist,
+and is created the first time the settings are saved
+with the <ref id="command-PREFSAVE" name="PREFSAVE"> command.
+The most recently saved settings can be restored at any time
+with the <ref id="command-PREFLOAD" name="PREFLOAD"> command.
+
+The name for this file is <tt>/etc/brltty-</tt><em/driver/<tt/.prefs/.
+where <em/driver/ is the two-letter <ref id="drivers" name="driver identification code">.
+
+<sect2>The Preferences Menu<label id="preferences-menu"><p>
+The preferences settings are saved as binary data
+which, therefore, can't be edited by hand.
+BRLTTY, however, has a simple menu from which you can easily change them.
+
+The menu is activated by the <ref id="command-PREFMENU" name="PREFMENU"> command.
+The braille display briefly
+(see the <ref id="options-message-timeout" name="-M"> command line option)
+shows the menu title,
+and then presents the current item and its current setting.
+
+<Sect3>Navigating the Menu<p>
+See <ref id="menu-navigation" name="Menu Navigation Commands"> for the full list of
+commands which enable you to select items and change settings within the menu.
+For backward compatibility with old drivers, the window motion commands,
+which have modified meanings in this context, can also be used.
+<descrip>
+  <tag><tt/TOP//<tt/BOT/, <tt/TOP_LEFT//<tt/BOT_LEFT/, <tt/PAGE_UP//<tt/PAGE_DOWN/</tag>
+    Go to the first/last item in the menu
+    (same as <ref id="command-MENU_FIRST_ITEM-MENU_LAST_ITEM" name="MENU_FIRST_ITEM/MENU_LAST_ITEM">).
+  <tag><tt/LNUP//<tt/LNDN/, <tt/PRDIFLN//<tt/NXDIFLN/, <tt/CURSOR_UP//<tt/CURSOR_DOWN/</tag>
+    Go to the previous/next item in the menu
+    (same as <ref id="command-MENU_PREV_ITEM-MENU_NEXT_ITEM" name="MENU_PREV_ITEM/MENU_NEXT_ITEM">).
+  <tag><tt/WINUP//<tt/WINDN/, <tt/CHRLT//<tt/CHRRT/, <tt/CURSOR_LEFT//<tt/CURSOR_RIGHT/, <tt/BACK//<tt/HOME/</tag>
+    Decrement/increment the current menu item's setting
+    (same as <ref id="command-MENU_PREV_SETTING-MENU_NEXT_SETTING" name="MENU_PREV_SETTING/MENU_NEXT_SETTING">).
+</descrip>
+
+Notes:
+<itemize>
+  <item>
+    The routing keys can also be used to select a setting for the current item.
+    If the item has numeric settings,
+    then the entire row of routing keys acts as a scroll bar
+    which covers the full range of valid values.
+    If the item has named settings,
+    then the routing keys correspond ordinally with the settings.
+  <item>
+    Use the <tt/PREFLOAD/ command to undo all of the changes
+    which were made since entering the menu.
+  <item>
+    Use the <tt/PREFMENU/ command (again) to leave the new settings in effect,
+    exit the menu, and resume normal operation.
+    If the <sq/Save Settings on Exit/ item is set, then, in addition,
+    the new settings are written to the preferences settings file.
+    Any command not recognized by the menu system also does these same things.
+</itemize>
+
+<sect3>The Menu Items<p>
+<descrip>
+  <tag>Save on Exit<label id="preference-save-on-exit"></tag>
+    When exiting the preferences menu:
+    <descrip>
+      <tag/No/
+        Don't automatically save the preferences settings.
+      <tag/Yes/
+        Automatically save the preferences settings.
+    </descrip>
+    The initial setting is <tt/No/.
+  <tag>Text Style<label id="preference-text-style"></tag>
+    When displaying screen content
+    (see the <ref id="command-DISPMD" name="DISPMD"> command),
+    show characters:
+    <descrip>
+      <tag/8-dot/
+        With all eight dots.
+      <tag/6-dot/
+        With only dots 1 through 6.
+        If a contraction table has been selected
+        (see the <ref id="options-contraction-table" name="-c"> command line option
+        and the <ref id="configure-contraction-table" name="contraction-table"> configuration file directive),
+        then it is used.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-SIXDOTS" name="SIXDOTS"> command.
+  <tag>Skip Identical Lines<label id="preference-skip-identical-lines"></tag>
+    When moving either up or down exactly one line with
+    the <ref id="command-LNUP-LNDN" name="LNUP/LNDN"> commands,
+    as well as the line wrapping feature of
+    the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT">
+    and <ref id="command-FWINLTSKIP-FWINRTSKIP" name="FWINLTSKIP/FWINRTSKIP"> commands:
+    <descrip>
+      <tag/No/
+        Don't skip past lines which have the same content as the current line.
+      <tag/Yes/
+        Skip past lines which have the same content as the current line.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-SKPIDLNS" name="SKPIDLNS"> command.
+  <tag>Skip Blank Windows<label id="preference-skip-blank-windows"></tag>
+    When moving either left or right with
+    the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> commands:
+    <descrip>
+      <tag/No/
+        Don't skip past blank windows.
+      <tag/Yes/
+        Skip past blank windows.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-SKPBLNKWINS" name="SKPBLNKWINS"> command.
+  <tag>Which Blank Windows<label id="preference-which-blank-windows"></tag>
+    If blank windows are to be skipped:
+    <descrip>
+      <tag/All/
+        Skip all of them.
+      <tag/End of Line/
+        Only skip those which are at the end (on the right side) of a line.
+      <tag/Rest of Line/
+        Only skip those which are
+        at the end (on the right side) of a line when reading forward,
+        and at the beginning (on the left side) of a line when reading backward.
+    </descrip>
+  <tag>Sliding Window<label id="preference-sliding-window"></tag>
+    If the cursor is being tracked
+    (see the <ref id="command-CSRTRK" name="CSRTRK"> command),
+    and the cursor moves too close to (or beyond)
+    either end of the braille window:
+    <descrip>
+      <tag/No/
+        Horizontally reposition the window such that its left end
+        is a multiple of its width from the left edge of the screen.
+      <tag/Yes/
+        Horizontally reposition the window such that the cursor,
+        while remaining on that side of the window, is nearer the centre.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-SLIDEWIN" name="SLIDEWIN"> command.
+  <tag>Eager Sliding Window<label id="preference-eager-sliding-window"></tag>
+    If the braille window is to slide:
+    <descrip>
+      <tag/No/
+        Reposition it whenever the cursor moves beyond either end.
+      <tag/Yes/
+        Reposition it whenever the cursor moves too close to either end.
+    </descrip>
+    The initial setting is <tt/No/.
+  <tag>Window Overlap<label id="preference-window-overlap"></tag>
+    When moving either left or right with
+    the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> commands,
+    this setting specifies how many characters
+    horizontally adjacent braille windows should overlap each other by.
+    The initial setting is <tt/0/.
+  <tag>Autorepeat<label id="preference-autorepeat"></tag>
+    While the key (combination) for a command remains pressed:
+    <descrip>
+      <tag/No/
+        Don't automatically repeat the command.
+      <tag/Yes/
+        Automatically repeat the command at a regular interval after an initial delay.
+    </descrip>
+    The following commands are eligible for autorepeating:
+    <itemize>
+      <item>
+        The <ref id="command-LNUP-LNDN" name="LNUP/LNDN"> commands.
+      <item>
+        The <ref id="command-PRDIFLN-NXDIFLN" name="PRDIFLN/NXDIFLN"> commands.
+      <item>
+        The <ref id="command-CHRLT-CHRRT" name="CHRLT/CHRRT"> commands.
+      <item>
+        Braille window panning operations
+        (see the <ref id="preference-autorepeat-panning" name="Autorepeat Panning"> preference).
+      <item>
+        The Page-Up and Page-Down operations.
+      <item>
+        The Cursor-Up and Cursor-Down operations.
+      <item>
+        The Cursor-Left and Cursor-Right operations.
+      <item>
+        The Backspace and Delete operations.
+      <item>
+        Character entry.
+    </itemize>
+    Only some drivers support this functionality,
+    the primary limitation being that many braille displays
+    don't signal both key presses and key releases as distinctly separate events.
+    This setting can also be changed with the
+    <ref id="command-AUTOREPEAT" name="AUTOREPEAT"> command.
+    The initial setting is <tt/Yes/.
+  <tag>Autorepeat Panning<label id="preference-autorepeat-panning"></tag>
+    When the <ref id="preference-autorepeat" name="Autorepeat"> preference is enabled:
+    <descrip>
+      <tag/No/
+        Don't autorepeat braille window panning operations.
+      <tag/Yes/
+        Autorepeat braille window panning operations.
+    </descrip>
+    This preference affects the <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> commands.
+    The initial setting is <tt/No/.
+  <tag>Autorepeat Delay<label id="preference-autorepeat-delay"></tag>
+    When a character is to be autorepeated,
+    this setting specifies the amount of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    which must pass before autorepeating begins.
+    The initial setting is <tt/50/.
+  <tag>Autorepeat Interval<label id="preference-autorepeat-interval"></tag>
+    When a character is being autorepeated,
+    this setting specifies the amount of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    between each reexecution.
+    The initial setting is <tt/10/.
+  <tag>Show Cursor<label id="preference-show-cursor"></tag>
+    When displaying screen content
+    (see the <ref id="command-DISPMD" name="DISPMD"> command):
+    <descrip>
+      <tag/No/
+        Don't show the cursor.
+      <tag/Yes/
+        Show the cursor.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-CSRVIS" name="CSRVIS"> command.
+    The initial setting is <tt/Yes/.
+  <tag>Cursor Style<label id="preference-cursor-style"></tag>
+    When showing the cursor, represent it:
+    <descrip>
+      <tag/Underline/
+        With dots 7 and 8.
+      <tag/Block/
+        With all eight dots.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-CSRSIZE" name="CSRSIZE"> command.
+  <tag>Blinking Cursor<label id="preference-blinking-cursor"></tag>
+    When the cursor is to be shown:
+    <descrip>
+      <tag/No/
+        Leave it visible all the time.
+      <tag/Yes/
+        Make it alternately visible and invisible
+        according to a predefined interval.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-CSRBLINK" name="CSRBLINK"> command.
+  <tag>Cursor Visible Time<label id="preference-cursor-visible-time"></tag>
+    When the cursor is to be blinked, this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that it is to be visible.
+    The initial setting is <tt/40/.
+  <tag>Cursor Invisible Time<label id="preference-cursor-invisible-time"></tag>
+    When the cursor is to be blinked, this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that it is to be invisible.
+    The initial setting is <tt/40/.
+  <tag>Show Attributes<label id="preference-show-attributes"></tag>
+    When displaying screen content
+    (see the <ref id="command-DISPMD" name="DISPMD"> command):
+    <descrip>
+      <tag/No/
+        Don't underline highlighted characters.
+      <tag/Yes/
+        Underline highlighted characters.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-ATTRVIS" name="ATTRVIS"> command.
+  <tag>Blinking Attributes<label id="preference-blinking-attributes"></tag>
+    When highlighted characters are to be underlined:
+    <descrip>
+      <tag/No/
+        Leave the indicator visible all the time.
+      <tag/Yes/
+        Make the indicator alternately visible and invisible
+        according to a predefined interval.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-ATTRBLINK" name="ATTRBLINK"> command.
+  <tag>Attributes Visible Time<label id="preference-attributes-visible-time"></tag>
+    When the highlighted character underline is to be blinked,
+    this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that it is to be visible.
+    The initial setting is <tt/20/.
+  <tag>Attributes Invisible Time<label id="preference-attributes-invisible-time"></tag>
+    When the highlighted character underline is to be blinked,
+    this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that it is to be invisible.
+    The initial setting is <tt/60/.
+  <tag>Blinking Capitals<label id="preference-blinking-capitals"></tag>
+    When displaying screen content
+    (see the <ref id="command-DISPMD" name="DISPMD"> command):
+    <descrip>
+      <tag/No/
+        Leave capital letters visible all the time.
+      <tag/Yes/
+        Make capital letters alternately visible and invisible
+        according to a predefined interval.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-CAPBLINK" name="CAPBLINK"> command.
+  <tag>Capitals Visible Time<label id="preference-capitals-visible-time"></tag>
+    When capital letters are to be blinked,
+    this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that they're to be visible.
+    The initial setting is <tt/60/.
+  <tag>Capitals Invisible Time<label id="preference-capitals-invisible-time"></tag>
+    When capital letters are to be blinked,
+    this setting specifies the length of time
+    (see the note on <ref id="time-settings" name="time settings"> below)
+    during each cycle that they're to be invisible.
+    The initial setting is <tt/20/.
+  <tag>Braille Firmness<label id="preference-braille-firmness"></tag>
+    Adjust the firmness (or stiffness) of the braille dots.
+    It can be set to:
+    <itemize>
+      <item>Maximum
+      <item>High
+      <item>Medium
+      <item>Low
+      <item>Minimum
+    </itemize>
+    This preference is only available if a driver which supports it is being used.
+    The initial setting is <tt/Medium/.
+  <tag>Braille Sensitivity<label id="preference-braille-sensitivity"></tag>
+    Adjust the sensitivity of the braille dots to touch.
+    It can be set to:
+    <itemize>
+      <item>Maximum
+      <item>High
+      <item>Medium
+      <item>Low
+      <item>Minimum
+    </itemize>
+    This preference is only available if a driver which supports it is being used.
+    The initial setting is <tt/Medium/.
+  <tag>Window Follows Pointer<label id="preference-window-follows-pointer"></tag>
+    When moving the pointer device (mouse):
+    <descrip>
+      <tag/No/
+        Don't drag the braille window.
+      <tag/Yes/
+        Drag the braille window.
+    </descrip>
+    This preference is only presented if the
+    <ref id="build-gpm" name="--enable-gpm"> build option was specified.
+  <tag>Highlight Window<label id="preference-highlight-window"></tag>
+    When moving the braille window:
+    <descrip>
+      <tag/No/
+        Don't highlight the new screen area.
+      <tag/Yes/
+        Highlight the new screen area.
+    </descrip>
+    This feature enables a sighted observer to see where the braille window is,
+    and, therefore, to know what the braille user is reading.
+    Any motion of the braille window (manual, cursor tracking, etc.),
+    other than when it moves in response to pointer (mouse) motion
+    (see the <ref id="preference-window-follows-pointer" name="Window Follows Pointer"> preference),
+    causes the area of the screen corresponding to
+    the new location of the braille window to be highlighted.
+    If the <ref id="preference-show-attributes" name="Show Attributes"> preference is enabled
+    then only the character corresponding to the upper-left corner of the braille window is highlighted.
+  <tag>Alert Tunes<label id="preference-alert-tunes"></tag>
+    Whenever a significant event with an associated tune occurs
+    (see <ref id="tunes" name="Alert Tunes">):
+    <descrip>
+      <tag/No/
+        Don't play the tune.
+      <tag/Yes/
+        Play the tune.
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-TUNES" name="TUNES"> command.
+    The initial setting is <tt/Yes/.
+  <tag>Tune Device<label id="preference-tune-device"></tag>
+    Play alert tunes via:
+    <descrip>
+      <tag/Beeper/
+        The internal beeper (console tone generator).
+        This setting is supported
+        on Linux,
+        on OpenBSD,
+        on FreeBSD,
+        and on NetBSD.
+        It's always safe to use,
+        although it may be somewhat soft.
+        This device isn't available if the
+        <ref id="build-beeper-support" name="--disable-beeper-support"> build option was specified.
+      <tag/PCM/
+        The digital audio interface on the sound card.
+        This setting is supported
+        on Linux (via <tt>/dev/dsp</>),
+        on Solaris (via <tt>/dev/audio</>),
+        on OpenBSD (via <tt>/dev/audio0</>),
+        on FreeBSD (via <tt>/dev/dsp</>),
+        and on NetBSD (via <tt>/dev/audio0</>).
+        It doesn't work when this device is already being used
+        by another application.
+        This device isn't available if the
+        <ref id="build-pcm-support" name="--disable-pcm-support"> build option was specified.
+      <tag/MIDI/
+        The Musical Instrument Digital Interface on the sound card
+        This setting is supported
+        on Linux (via <tt>/dev/sequencer</>).
+        It doesn't work when this device is already being used
+        by another application.
+        This device isn't available if the
+        <ref id="build-midi-support" name="--disable-midi-support"> build option was specified.
+      <tag/FM/
+        The FM synthesizer on an
+        AdLib, OPL3, Sound Blaster, or equivalent sound card.
+        This setting is supported
+        on Linux.
+        It works even if the FM synthesizer
+        is already being used by another application.
+        The results are unpredictable, and potentially not very good,
+        if it's used with a sound card which doesn't support this feature.
+        This device isn't available if the
+        <ref id="build-fm-support" name="--disable-fm-support"> build option was specified.
+    </descrip>
+    The initial setting is <tt/Beeper/ on those platforms which support it,
+    and <tt/PCM/ on those platforms which don't.
+  <tag>PCM Volume<label id="preference-pcm-volume"></tag>
+    If the digital audio interface of the sound card
+    is being used to play the alert tunes,
+    this setting specifies the loudness
+    (as a percentage of the maximum)
+    at which they are to be played.
+    The initial setting is <tt/70/.
+  <tag>MIDI Volume<label id="preference-midi-volume"></tag>
+    If the Musical Instrument Digital Interface (MIDI) of the sound card
+    is being used to play the alert tunes,
+    this setting specifies the loudness
+    (as a percentage of the maximum)
+    at which they are to be played.
+    The initial setting is <tt/70/.
+  <tag>MIDI Instrument<label id="preference-midi-instrument"></tag>
+    If the Musical Instrument Digital Interface (MIDI) of the sound card
+    is being used to play the alert tunes,
+    this setting specifies which instrument is to be used
+    (see the <ref id="midi" name="MIDI Instrument Table">).
+    The initial setting is <tt/Acoustic Grand Piano/.
+  <tag>FM Volume<label id="preference-fm-volume"></tag>
+    If the FM synthesizer of the sound card
+    is being used to play the alert tunes,
+    this setting specifies the loudness
+    (as a percentage of the maximum)
+    at which they are to be played.
+    The initial setting is <tt/70/.
+  <tag>Alert Dots<label id="preference-alert-dots"></tag>
+    Whenever a significant event with an associated dot pattern occurs
+    (see <ref id="tunes" name="Alert Tunes">):
+    <descrip>
+      <tag/No/
+        Don't display the dot pattern.
+      <tag/Yes/
+        Briefly display the dot pattern.
+    </descrip>
+    If alert tunes are to be played
+    (see the <ref id="command-TUNES" name="TUNES"> command
+    and the <ref id="preference-alert-tunes" name="Alert Tunes"> preference),
+    if a tune has been associated with the event,
+    and if the selected tune device can be opened,
+    then, regardless of the setting of this preference, the dot pattern isn't displayed.
+  <tag>Alert Messages<label id="preference-alert-messages"></tag>
+    Whenever a significant event with an associated message occurs
+    (see <ref id="tunes" name="Alert Tunes">):
+    <descrip>
+      <tag/No/
+        Don't display the message.
+      <tag/Yes/
+        Display the message.
+    </descrip>
+    If alert tunes are to be played
+    (see the <ref id="command-TUNES" name="TUNES"> command
+    and the <ref id="preference-alert-tunes" name="Alert Tunes"> preference),
+    if a tune has been associated with the event,
+    and if the selected tune device can be opened,
+    or if alert dot patterns are to be displayed
+    (see the <ref id="preference-alert-dots" name="Alert Dots"> preference)
+    and if a dot pattern has been associated with the event,
+    then, regardless of the setting of this preference, the message isn't displayed.
+  <tag>Say-Line Mode<label id="preference-sayline-mode"></tag>
+    When using the <ref id="command-SAY_LINE" name="SAY_LINE"> command:
+    <descrip>
+      <tag/Immediate/
+        Discard pending speech.
+      <tag/Enqueue/
+        Don't discard pending speech.
+    </descrip>
+    The initial setting is <tt/Immediate/.
+  <tag>Autospeak<label id="preference-autospeak"></tag>
+    <descrip>
+      <tag/No/
+        Only speak when explicitly requested to do so.
+      <tag/Yes/
+        Automatically speak:
+        <itemize>
+          <item>the new line when the braille window is moved vertically.
+          <item>characters which are entered or deleted.
+          <item>the character to which the cursor is moved.
+        </itemize>
+    </descrip>
+    This setting can also be changed with the
+    <ref id="command-AUTOSPEAK" name="AUTOSPEAK"> command.
+    The initial setting is <tt/No/.
+  <tag>Speech Rate<label id="preference-speech-rate"></tag>
+    Adjust the speech rate
+    (<tt/0/ is the slowest, <tt/20/ is the fastest).
+    This preference is only available if a driver which supports it is being used.
+    This setting can also be changed with
+    the <ref id="command-SAY_SLOWER-SAY_FASTER" name="SAY_SLOWER/SAY_FASTER"> commands.
+    The initial setting is <tt/10/.
+  <tag>Speech Volume<label id="preference-speech-volume"></tag>
+    Adjust the speech volume
+    (<tt/0/ is the softest, <tt/20/ is the loudest).
+    This preference is only available if a driver which supports it is being used.
+    This setting can also be changed with
+    the <ref id="command-SAY_SOFTER-SAY_LOUDER" name="SAY_SOFTER/SAY_LOUDER"> commands.
+    The initial setting is <tt/10/.
+  <tag>Speech Pitch<label id="preference-speech-pitch"></tag>
+    Adjust the speech pitch
+    (<tt/0/ is the lowest, <tt/20/ is the highest).
+    This preference is only available if a driver which supports it is being used.
+    The initial setting is <tt/10/.
+  <tag>Speech Punctuation<label id="preference-speech-punctuation"></tag>
+    Adjust the amount of punctuation which is spoken.
+    It can be set to:
+    <itemize>
+      <item>None
+      <item>Some
+      <item>All
+    </itemize>
+    This preference is only available if a driver which supports it is being used.
+    The initial setting is <tt/Some/.
+  <tag>Status Style<label id="preference-status-style"></tag>
+    This setting specifies the way that the status cells are to be used.
+    You shouldn't normally need to play with it.
+    It enables BRLTTY's developers to test status cell configurations
+    for braille displays which they don't actually have.
+    <descrip>
+      <tag/None/
+        Don't use the status cells.
+        This setting is always safe, but it's also quite useless.
+      <tag/Alva/
+        The status cells contain:
+        <descrip>
+          <tag/1/
+            The location of the cursor (see below).
+          <tag/2/
+            The location of the top-left corner of the braille window (see below).
+          <tag/3/
+            A letter indicating BRLTTY's state.
+            In order of precedence:
+            <descrip>
+              <tag/a/
+                Screen attributes are being shown
+                (see the <ref id="command-DISPMD" name="DISPMD"> command).
+              <tag/f/
+                The screen image is frozen
+                (see the <ref id="command-FREEZE" name="FREEZE"> command).
+              <tag/f/
+                The cursor is being tracked
+                (see the <ref id="command-CSRTRK" name="CSRTRK"> command).
+              <tag><em/blank/</tag>
+                Nothing special.
+            </descrip>
+        </descrip>
+        The locations of the cursor and the braille window
+        are presented in an interesting way.
+        Dots 1 through 6 represent the line number with a letter
+        from <tt/a/ (for 1) through <tt/y/ (for 25).
+        Dots 7 and 8 (the extra two at the bottom)
+        represent the horizontal braille window number as follows:
+        <descrip>
+          <tag/No Dots/The first (leftmost) window.
+          <tag/Dot 7/The second window.
+          <tag/Dot 8/The third window.
+          <tag/Dots 7 and 8/The fourth window.
+        </descrip>
+        In both cases, the indicators wrap:
+        line 26 is represented by the letter <tt/a/, and the fifth horizontal
+        braille window is represented by no dots at the bottom.
+      <tag/Tieman/
+        The status cells contain:
+        <descrip>
+          <tag/1-2/
+            The columns (counting from 1) of the cursor
+            (shown in the top half of the cells)
+            and the top-left corner of the braille window
+            (shown in the bottom half of the cells).
+          <tag/3-4/
+            The rows (counting from 1) of the cursor
+            (shown in the top half of the cells)
+            and the top-left corner of the braille window
+            (shown in the bottom half of the cells).
+          <tag/5/
+            Each dot indicates if a feature is turned on as follows:
+            <descrip>
+              <tag/Dot 1/
+                The screen image is frozen
+                (see the <ref id="command-FREEZE" name="FREEZE"> command).
+              <tag/Dot 2/
+                Screen attributes are being displayed
+                (see the <ref id="command-DISPMD" name="DISPMD"> command).
+              <tag/Dot 3/
+                Alert tunes are being played
+                (see the <ref id="command-TUNES" name="TUNES"> command).
+              <tag/Dot 4/
+                The cursor is being shown
+                (see the <ref id="command-CSRVIS" name="CSRVIS"> command).
+              <tag/Dot 5/
+                The cursor is a solid block
+                (see the <ref id="command-CSRSIZE" name="CSRSIZE"> command).
+              <tag/Dot 6/
+                The cursor is blinking
+                (see the <ref id="command-CSRBLINK" name="CSRBLINK"> command).
+              <tag/Dot 7/
+                The cursor is being tracked
+                (see the <ref id="command-CSRTRK" name="CSRTRK"> command).
+              <tag/Dot 8/
+                The braille window will slide
+                (see the <ref id="command-SLIDEWIN" name="SLIDEWIN"> command).
+            </descrip>
+        </descrip>
+      <tag/PowerBraille 80/
+        The status cells contain:
+        <descrip>
+          <tag/1/
+            The row (counting from 1) corresponding
+            to the top of the braille window.
+            The tens digit is shown in the top half of the cell,
+            and the units digit is shown in the bottom half of the cell.
+        </descrip>
+      <tag/Generic/
+        This setting passes a lot of information to the braille driver,
+        and the driver itself decides how to present it.
+      <tag/MDV/
+        The status cells contain:
+        <descrip>
+          <tag/1-2/
+            The location of the top-left corner of the braille window.
+            The row (counting from 1) is shown in the top half of the cells,
+            and the column (counting from 1) is shown in the bottom half of the cells.
+        </descrip>
+      <tag/Voyager/
+        The status cells contain:
+        <descrip>
+          <tag/1/
+            The row (counting from 1) corresponding to
+            the top of the braille window (see below).
+          <tag/2/
+            The row (counting from 1) whereon the cursor is (see below).
+          <tag/3/
+            If the screen is frozen
+            (see the <ref id="command-FREEZE" name="FREEZE"> command),
+            then the letter <tt/F/.
+            If it isn't, then
+            the column (counting from 1) wherein the cursor is (see below).
+        </descrip>
+        Row and column numbers are shown as two digits within a single cell.
+        The tens digit is shown in the top half of the cell,
+        and the units digit is shown in the bottom half of the cell.
+    </descrip>
+    The initial setting is braille display driver dependent.
+  <tag>Text Table<label id="preference-text-table"></tag>
+    Select the text table.
+    See section <ref id="table-text" name="Text Tables"> for details.
+    See the <ref id="options-text-table" name="-t"> command line option for the initial setting.
+    This preference isn't saved.
+  <tag>Attributes Table<label id="preference-attributes-table"></tag>
+    Select the attributes table.
+    See section <ref id="table-attributes" name="Attributes Tables"> for details.
+    See the <ref id="options-attributes-table" name="-a"> command line option for the initial setting.
+    This preference isn't saved.
+  <tag>Contraction Table<label id="preference-contraction-table"></tag>
+    Select the contraction table.
+    See section <ref id="table-contraction" name="Contraction Tables"> for details.
+    See the <ref id="options-contraction-table" name="-c"> command line option for the initial setting.
+    This preference isn't saved.
+  <tag>Keyboard Table<label id="preference-keyboard-table"></tag>
+    Select the keyboard table.
+    See section <ref id="table-key" name="Key Tables"> for details.
+    See the <ref id="options-keyboard-table" name="-k"> command line option for the initial setting.
+    This preference isn't saved.
+</descrip>
+
+Notes:
+<itemize>
+  <item><label id="time-settings">
+    All time settings are in hundredths of a second.
+    They are multiples of 4 within the range 1 through 100.
+</itemize>
+
+<sect1>The Status Display<label id="status"><p>
+The status display is a summary of BRLTTY's current state
+which fits completely within the braille window.
+Some braille displays have a set of status cells
+which are used to permanently display some of this information as well
+(see the documentation for your display's driver).
+The data presented by this display isn't static, and may change at any time
+in response to screen updates and/or BRLTTY commands.
+
+Use the <ref id="command-INFO" name="INFO"> command
+to switch to the status display,
+and use it again to return to the screen.
+The layout of the information contained therein
+is dependent on the size of the braille window.
+
+<sect2>Displays with 21 Cells or More<p>
+Short pneumonics have been used, even though they're rather cryptic,
+in order to show the precise column layout.
+<tscreen><em/wx/:<em/wy/ <em/cx/:<em/cy/ <em/vt/ <em/tcmfdu/</tscreen>
+<descrip>
+  <tag><em/wx/<tt/:/<em/wy/</tag>
+    The column and row (counting from 1) on the screen corresponding
+    to the top-left corner of the braille window.
+  <tag><em/cx/<tt/:/<em/cy/</tag>
+    The column and row (counting from 1) on the screen corresponding
+    to the position of the cursor.
+  <tag><em/vt/</tag>
+    The number (counting from 1) of the current virtual terminal.
+  <tag><em/t/</tag>
+    The state of the cursor tracking feature
+    (see the <ref id="command-CSRTRK" name="CSRTRK"> command).
+    <descrip>
+      <tag>blank</tag>Cursor tracking is off.
+      <tag><tt/t/</tag>Cursor tracking is on.
+    </descrip>
+  <tag><em/c/</tag>
+    The state of the cursor visibility features
+    (see the <ref id="command-CSRVIS" name="CSRVIS">
+    and <ref id="command-CSRBLINK" name="CSRBLINK"> commands).
+    <descrip>
+      <tag>blank</tag>The cursor isn't visible, and won't blink when made visible.
+      <tag><tt/b/</tag>The cursor isn't visible, and will blink when made visible.
+      <tag><tt/v/</tag>The cursor is visible, and isn't blinking.
+      <tag><tt/B/</tag>The cursor is visible, and is blinking.
+    </descrip>
+  <tag><em/m/</tag>
+    The current display mode
+    (see the <ref id="command-DISPMD" name="DISPMD"> command).
+    <descrip>
+      <tag><tt/t/</tag>Screen content (text) is being displayed.
+      <tag><tt/a/</tag>Screen highlighting (attributes) is being displayed.
+    </descrip>
+  <tag><em/f/</tag>
+    The state of the frozen screen feature
+    (see the <ref id="command-FREEZE" name="FREEZE"> command).
+    <descrip>
+      <tag>blank</tag>The screen isn't frozen.
+      <tag><tt/f/</tag>The screen is frozen.
+    </descrip>
+  <tag><em/d/</tag>
+    The number of braille dots being used to display each character
+    (see the <ref id="command-SIXDOTS" name="SIXDOTS"> command).
+    <descrip>
+      <tag><tt/8/</tag>All eight dots are being used.
+      <tag><tt/6/</tag>Only dots 1 through 6 are being used.
+    </descrip>
+  <tag><em/u/</tag>
+    The state of the uppercase (capital letter) display features
+    (see the <ref id="command-CAPBLINK" name="CAPBLINK"> command).
+    <descrip>
+      <tag>blank</tag>Uppercase letters don't blink.
+      <tag><tt/B/</tag>Uppercase letters blink.
+    </descrip>
+</descrip>
+
+<sect2>Displays with 20 Cells or Less<p>
+Short pneumonics have been used, even though they're rather cryptic,
+in order to show the precise column layout.
+<tscreen><em/xx/<em/yy/<em/s/ <em/vt/ <em/tcmfdu/</tscreen>
+<descrip>
+  <tag><em/xx/</tag>
+    The columns (counting from 1) on the screen corresponding
+    to the position of the cursor (shown in the top half of the cells)
+    and to the top-left corner of the braille window (shown in the bottom half of the cells).
+  <tag><em/yy/</tag>
+    The rows (counting from 1) on the screen corresponding
+    to the position of the cursor (shown in the top half of the cells)
+    and to the top-left corner of the braille window (shown in the bottom half of the cells).
+  <tag><em/s/</tag>
+    The settings of some of BRLTTY's features.
+    A feature is turned on if its corresponding dot is raised.
+    <descrip>
+      <tag/Dot 1/
+        Frozen screen image
+        (see the <ref id="command-FREEZE" name="FREEZE"> command).
+      <tag/Dot 2/
+        Display attributes
+        (see the <ref id="command-DISPMD" name="DISPMD"> command).
+      <tag/Dot 3/
+        Alert tunes
+        (see the <ref id="command-TUNES" name="TUNES"> command).
+      <tag/Dot 4/
+        Visible cursor
+        (see the <ref id="command-CSRVIS" name="CSRVIS"> command).
+      <tag/Dot 5/
+        Block cursor
+        (see the <ref id="command-CSRSIZE" name="CSRSIZE"> command).
+      <tag/Dot 6/
+        Blinking cursor
+        (see the <ref id="command-CSRBLINK" name="CSRBLINK"> command).
+      <tag/Dot 7/
+        Cursor tracking
+        (see the <ref id="command-CSRTRK" name="CSRTRK"> command).
+      <tag/Dot 8/
+        Sliding window
+        (see the <ref id="command-SLIDEWIN" name="SLIDEWIN"> command).
+    </descrip>
+  <tag><em/vt/</tag>
+    The number (counting from 1) of the current virtual terminal.
+  <tag><em/t/</tag>
+    The state of the cursor tracking feature
+    (see the <ref id="command-CSRTRK" name="CSRTRK"> command).
+    <descrip>
+      <tag>blank</tag>Cursor tracking is off.
+      <tag><tt/t/</tag>Cursor tracking is on.
+    </descrip>
+  <tag><em/c/</tag>
+    The state of the cursor visibility features
+    (see the <ref id="command-CSRVIS" name="CSRVIS">
+    and <ref id="command-CSRBLINK" name="CSRBLINK"> commands).
+    <descrip>
+      <tag>blank</tag>The cursor isn't visible, and won't blink when made visible.
+      <tag><tt/b/</tag>The cursor isn't visible, and will blink when made visible.
+      <tag><tt/v/</tag>The cursor is visible, and isn't blinking.
+      <tag><tt/B/</tag>The cursor is visible, and is blinking.
+    </descrip>
+  <tag><em/m/</tag>
+    The current display mode
+    (see the <ref id="command-DISPMD" name="DISPMD"> command).
+    <descrip>
+      <tag><tt/t/</tag>Screen content (text) is being displayed.
+      <tag><tt/a/</tag>Screen highlighting (attributes) is being displayed.
+    </descrip>
+  <tag><em/f/</tag>
+    The state of the frozen screen feature
+    (see the <ref id="command-FREEZE" name="FREEZE"> command).
+    <descrip>
+      <tag>blank</tag>The screen isn't frozen.
+      <tag><tt/f/</tag>The screen is frozen.
+    </descrip>
+  <tag><em/d/</tag>
+    The number of braille dots being used to display each character
+    (see the <ref id="command-SIXDOTS" name="SIXDOTS"> command).
+    <descrip>
+      <tag><tt/8/</tag>All eight dots are being used.
+      <tag><tt/6/</tag>Only dots 1 through 6 are being used.
+    </descrip>
+  <tag><em/u/</tag>
+    The state of the uppercase (capital letter) display features
+    (see the <ref id="command-CAPBLINK" name="CAPBLINK"> command).
+    <descrip>
+      <tag>blank</tag>Uppercase letters don't blink.
+      <tag><tt/B/</tag>Uppercase letters blink.
+    </descrip>
+</descrip>
+
+<sect1>Command Learn Mode<label id="learn"><p>
+Command learn mode is an interactive way to learn
+what the keys on the braille display do.
+It can be accessed
+either by the <ref id="command-LEARN" name="LEARN"> command
+or via the <ref id="utility-brltest" name="brltest"> utility.
+This feature isn't available if the
+<ref id="build-learn-mode" name="--disable-learn-mode"> build option was specified.
+
+When this mode is entered,
+the message <tt/command learn mode/ is written to the braille display.
+Then, as each key (or key combination) on the display is pressed,
+a short message describing its BRLTTY function is written.
+This mode exits immediately if the key (or key combination)
+for the <ref id="command-LEARN" name="LEARN"> command is pressed.
+It exits automatically, and the message <tt/done/ is written,
+if ten seconds elapse without any key on the display being pressed.
+Note that some displays don't signal the driver
+and/or some drivers don't signal BRLTTY
+until all the keys are released.
+
+If a message is longer than the braille display is wide,
+then it's displayed in segments.
+The length of each segment but the last is one less than the display's width,
+with the rightmost character on the display being set to a minus sign.
+Each such segment remains on the display
+either for a few seconds
+(see the <ref id="options-message-timeout" name="-M"> command line option)
+or until any key on the display is pressed.
+
+<sect>Tables<label id="tables"><p>
+
+<sect1>Text Tables<label id="table-text"><p>
+Files with names of the form <tt/*.ttb/ are text tables,
+and with names of the form <tt/*.tti/ are text subtables.
+They are used by BRLTTY to translate the characters on the screen
+into their corresponding 8-dot computer braille representations.
+
+BRLTTY is initially configured to use the
+<ref id="nabcc" name="North American Braille Computer Code">
+(NABCC) text table.
+In addition to this default,
+the following alternatives are provided:
+<table loc="h">
+  <tabular ca="ll">
+    Name|Language@
+    &TextTables
+  </tabular>
+</table>
+See the <ref id="options-text-table" name="-t"> command line option,
+the <ref id="configure-text-table" name="text-table"> configuration file directive,
+and the <ref id="build-text-table" name="--with-text-table"> build option
+for details regarding how to use an alternate text table.
+
+<sect2>Text Table Format<p>
+A text table consists of a sequence of directives, one per line,
+which define how each character is to be represented in braille.
+<tt/UTF-8/ character encoding must be used.
+White-space (blanks, tabs) at the beginning of a line, 
+as well as before and/or after any operand of any directive,
+is ignored.
+Lines containing only white-space are ignored.
+If the first non-white-space character of a line is "&num;"
+then that line is a comment and is ignored.
+
+<sect2>Text Table Directives<p>
+The following directives are provided:
+<descrip>
+  <tag><tt/char/ <em/character/ <em/dots/ &num; <em/comment/</tag>
+    Use the <tt/char/ directive to specify
+    how a Unicode character is to be represented in braille.
+    Characters defined with this directive
+    can also be entered from a braille keyboard.
+    If several characters have the same braille representation
+    then only one of them should be defined with the <tt/char/ directive -
+    the others should be defined with the <tt/glyph/ directive
+    (which has the same syntax).
+    If more than one character with the same braille representation
+    is defined with the <tt/char/ directive
+    (which is currently allowed for backward compatibility)
+    then the first one is selected.
+
+    <descrip>
+      <tag><em/character/</tag>
+        The Unicode character being defined.
+        It may be:
+        <itemize>
+          <item>
+            Any single character other than a backslash or a white-space character.
+          <item>
+            A backslash-prefixed special character.
+            These are:
+            <descrip>
+            - <tag/\b/The backspace character.
+            - <tag/\f/The formfeed character.
+            - <tag/\n/The newline character.
+            - <tag/\o&num;&num;&num;/The three-digit octal representation of a character.
+            - <tag/\r/The carriage return character.
+            - <tag/\s/The space character.
+            - <tag/\t/The horizontal tab character.
+            - <tag/\u&num;&num;&num;&num;/The four-digit hexadecimal representation of a character.
+            - <tag/\U&num;&num;&num;&num;&num;&num;&num;&num;/The eight-digit hexadecimal representation of a character.
+            - <tag/\v/The vertical tab character.
+            - <tag/\x&num;&num;/The two-digit hexadecimal representation of a character.
+            - <tag/\X&num;&num;/... (the case of the X and of the digits isn't significant)
+            - <tag/\&num;/A literal number sign.
+            - <tag/\&lt;name&gt;/The Unicode name of a character (use _ for space).
+            - <tag/\\/A literal backslash.
+            </descrip>
+        </itemize>
+      <tag><em/dots/</tag>
+        The braille representation of the Unicode character.
+        It is a sequence of one to eight dot numbers.
+        If the dot number sequence is enclosed within parentheses
+        then the dot numbers may be separated from one another by white-space.
+        A dot number is a digit within the range <tt/1/-<tt/8/ as defined by the
+        <ref id="dots" name="Standard Braille Dot Numbering Convention">.
+        The special dot number <tt/0/ is recognized
+        when not enclosed within parentheses,
+        and means no dots;
+        it may not be used in conjunction with any other dot number.
+    </descrip>
+
+    Examples:
+    <itemize>
+      <item><tt/char a 1/
+      <item><tt/char b (12)/
+      <item><tt/char c ( 4  1   )/
+      <item><tt/char \\ 12567/
+      <item><tt/char \s 0/
+      <item><tt/char \x20 ()/
+      <item><tt/char \&lt;LATIN_SMALL_LETTER_D&gt; 145/
+    </itemize>
+  <tag><tt/glyph/ <em/character/ <em/dots/ &num; <em/comment/</tag>
+    Use the <tt/glyph/ directive to specify
+    how a Unicode character is to be represented in braille.
+    Characters defined with this directive are output-only. 
+    They cannot be entered from a braille keyboard.
+
+    See the <tt/char/ directive for syntax details and for examples.
+  <tag><tt/byte/ <em/byte/ <em/dots/ &num; <em/comment/</tag>
+    Use the <tt/byte/ directive to specify how
+    a character in the local character set 
+    is to be represented in braille.
+    It has been retained for backward compatibility but should not be used.
+    Unicode characters should be defined
+    (via the <tt/char/ directive)
+    so that the text table remains valid
+    regardless of what the local character set is.
+
+    <descrip>
+      <tag><em/byte/</tag>
+        The local character being defined.
+        It may be specified in the same ways
+        as the <em/character/ operand of the <tt/char/ directive 
+        except that the Unicode-specific forms
+        (\u, \U, \&lt;)
+        may not be used.
+      <tag><em/dots/</tag>
+        The braille representation of the local character.
+        It may be specified in the same ways
+        as the <em/dots/ operand of the <tt/char/ directive.
+    </descrip>
+  <tag><tt/include/ <em/file/ &num; <em/comment/</tag>
+    Use the <tt/include/ directive to include the content of a text subtable.
+    It is recursive, which means that
+    any text subtable can itself include yet another text subtable.
+    Care must be taken to ensure that an "include loop" is not created.
+
+    <descrip>
+      <tag><em/file/</tag>
+        The file to be included.
+        It may be either a relative or an absolute path.
+        If relative, it is anchored at the directory containing the including file.
+    </descrip>
+</descrip>
+
+<sect1>Attributes Tables<label id="table-attributes"><p>
+Files with names of the form *.atb are attributes tables,
+and with names of the form *.ati are attributes subtables.
+They are used when BRLTTY
+is displaying screen attributes rather than screen content
+(see the <ref id="command-DISPMD" name="DISPMD"> command).
+Each of the eight braille dots represents
+one of the eight <tt/VGA/ attribute bits.
+
+The following attributes tables are provided:
+<descrip>
+  <tag/left_right/
+    The lefthand column represents the foreground colours:
+    <descrip>
+      <tag/Dot 1/Blue
+      <tag/Dot 2/Green
+      <tag/Dot 3/Red
+      <tag/Dot 7/Bright
+    </descrip>
+    The righthand column represents the background colours:
+    <descrip>
+      <tag/Dot 4/Blue
+      <tag/Dot 5/Green
+      <tag/Dot 6/Red
+      <tag/Dot 8/Blink
+    </descrip>
+    A dot is raised when its corresponding attribute bit is on.
+    This is the default attributes table
+    because it's the most intuitive.
+    One of its problems, though, is that it's difficult to discern
+    the difference between normal (white on black)
+    and reverse (black on white) video.
+  <tag/invleft_right/
+    The lefthand column represents the foreground colours:
+    <descrip>
+      <tag/Dot 1/Blue
+      <tag/Dot 2/Green
+      <tag/Dot 3/Red
+      <tag/Dot 7/Bright
+    </descrip>
+    The righthand column represents the background colours:
+    <descrip>
+      <tag/Dot 4/Blue
+      <tag/Dot 5/Green
+      <tag/Dot 6/Red
+      <tag/Dot 8/Blink
+    </descrip>
+    A background bit being on triggers its corresponding dot,
+    whereas a foreground bit being off triggers its corresponding dot.
+    This unintuitive logic actually makes it easier
+    to read the most commonly used attribute combinations.
+  <tag/upper_lower/
+    The upper square represents the foreground colours:
+    <descrip>
+      <tag/Dot 1/Red
+      <tag/Dot 4/Green
+      <tag/Dot 2/Blue
+      <tag/Dot 5/Bright
+    </descrip>
+    The lower square represents the background colours:
+    <descrip>
+      <tag/Dot 3/Red
+      <tag/Dot 6/Green
+      <tag/Dot 7/Blue
+      <tag/Dot 8/Blink
+    </descrip>
+    A dot is raised when its corresponding attribute bit is on.
+</descrip>
+See the <ref id="options-attributes-table" name="-a"> command line option,
+the <ref id="configure-attributes-table" name="attributes-table"> configuration file directive,
+and the <ref id="build-attributes-table" name="--with-attributes-table"> build option
+for details regarding how to use an alternate attributes table.
+
+<sect2>Attributes Table Format<p>
+An attributes table consists of a sequence of directives, one per line,
+which define how combinations of <tt/VGA/ attributes
+are to be represented in braille. 
+<tt/UTF-8/ character encoding must be used.
+White-space (blanks, tabs) at the beginning of a line,
+as well as before and/or after any operand of any directive,
+is ignored.
+Lines containing only white-space are ignored.
+If the first non-white-space character of a line is "&num;"
+then that line is a comment and is ignored.
+
+<sect2>Attributes Table Directives<p>
+The following directives are provided:
+<descrip>
+  <tag><tt/dot/ <em/dot/ <em/state/ &num; <em/comment/</tag>
+    Use the <tt/dot/ directive to specify
+    what a specific dot represents.
+
+    <descrip>
+      <tag><em/dot/</tag>
+        The dot being defined.
+        It is a single digit within the range <tt/1/-<tt/8/ as defined by the
+        <ref id="dots" name="Standard Braille Dot Numbering Convention">.
+      <tag><em/state/</tag>
+        What the dot represents.
+        It may be:
+        <descrip>
+          <tag><tt/=/<em/attribute/</tag>The dot is raised if the named attribute is on.
+          <tag><tt/&tilde;/<em/attribute/</tag>The dot is raised if the named attribute is off.
+        </descrip>
+
+        The names of the attribute bits are:
+        <descrip>
+          <tag/0X01/<tt/fg-blue/
+          <tag/0X02/<tt/fg-green/
+          <tag/0X04/<tt/fg-red/
+          <tag/0X08/<tt/fg-bright/
+          <tag/0X10/<tt/bg-blue/
+          <tag/0X20/<tt/bg-green/
+          <tag/0X40/<tt/bg-red/
+          <tag/0X80/<tt/blink/
+        </descrip>
+    </descrip>
+
+    Examples:
+    <itemize>
+      <item><tt/dot 1 =fg-red/
+      <item><tt/dot 2 &tilde;bg-blue/
+    </itemize>
+  <tag><tt/include/ <em/file/ &num; <em/comment/</tag>
+    Use the <tt/include/ directive to include the content of an attributes subtable.
+    It is recursive, which means that
+    any attributes subtable can itself include yet another attributes subtable.
+    Care must be taken to ensure that an "include loop" is not created.
+
+    <descrip>
+      <tag><em/file/</tag>
+        The file to be included.
+        It may be either a relative or an absolute path.
+        If relative, it is anchored at the directory containing the including file.
+    </descrip>
+</descrip>
+
+<sect1>Contraction Tables<label id="table-contraction"><p>
+Files with names of the form <tt/*.ctb/ are contraction tables,
+and with names of the form <tt/*.cti/ are contraction subtables.
+They are used by BRLTTY to translate character sequences on the screen
+into their corresponding contracted braille representations.
+
+BRLTTY presents contracted braille if:
+<itemize>
+  <item>
+    A contraction table has been selected.
+    See the <ref id="options-contraction-table" name="-c"> command line option
+    and the <ref id="configure-contraction-table" name="contraction-table"> configuration file directive
+    for details.
+  <item>
+    The 6-dot braille feature has been activated.
+    See the <ref id="command-SIXDOTS" name="SIXDOTS"> command
+    and the <ref id="preference-text-style" name="Text Style"> preference
+    for details.
+</itemize>
+This feature isn't available if the
+<ref id="build-contracted-braille" name="--disable-contracted-braille"> build option was specified.
+
+The following contraction tables are provided:
+<table loc="h">
+  <tabular ca="ll">
+    Name|Language@
+    &ContractionTables
+  </tabular>
+</table>
+See the <ref id="options-contraction-table" name="-c"> command line option,
+and the <ref id="configure-contraction-table" name="contraction-table"> configuration file directive
+for details regarding how to use a contraction table.
+
+<sect2>Contraction Table Format<p>
+A contraction table consists of a sequence of entries, one per line,
+which define how character sequences are to be represented in braille.
+<tt/UTF-8/ character encoding must be used.
+White-space (blanks, tabs) at the beginning of a line,
+as well as before and/or after any operand,
+is ignored.
+Lines containing only white-space are ignored.
+If the first non-white-space character of a line is "&num;"
+then that line is a comment and is ignored.
+
+The format of a contraction table entry is:
+<tscreen><em/directive/ <em/operand/ ... [<em/comment/]</tscreen>
+Each directive has a specific number of operands.
+Any text beyond the last operand of a directive is interpreted as a comment.
+The order of the entries within a contraction table is, in general,
+anything that is convenient for its maintainer(s).
+An entry which defines an entity, e.g. <tt/class/,
+must precede all references to that entity.
+
+Entries which match character sequences
+are automatically rearranged from longest to shortest
+so that longer matches are always preferred.
+If more than one entry matches the same character sequence
+then their original table ordering is maintained.
+Thus, the same sequence may be translated differently under different circumstances.
+
+<sect2>Contraction Table Operands<p>
+<descrip>
+  <tag><em/characters/</tag>
+    The first operand of a character sequence matching directive
+    is the character sequence to be matched.
+    Each character within the sequence may be:
+    <itemize>
+      <item>
+        Any single character other than a backslash or a white-space character.
+      <item>
+        A backslash-prefixed special character.
+        These are:
+        <descrip>
+        - <tag/\b/The backspace character.
+        - <tag/\f/The formfeed character.
+        - <tag/\n/The newline character.
+        - <tag/\o&num;&num;&num;/The three-digit octal representation of a character.
+        - <tag/\r/The carriage return character.
+        - <tag/\s/The space character.
+        - <tag/\t/The horizontal tab character.
+        - <tag/\u&num;&num;&num;&num;/The four-digit hexadecimal representation of a character.
+        - <tag/\U&num;&num;&num;&num;&num;&num;&num;&num;/The eight-digit hexadecimal representation of a character.
+        - <tag/\v/The vertical tab character.
+        - <tag/\x&num;&num;/The two-digit hexadecimal representation of a character.
+        - <tag/\X&num;&num;/... (the case of the X and of the digits isn't significant)
+        - <tag/\&num;/A literal number sign.
+        - <tag/\&lt;name&gt;/The Unicode name of a character (use _ for space).
+        - <tag/\\/A literal backslash.
+        </descrip>
+    </itemize>
+  <tag><em/representation/</tag>
+    The second operand of those character sequence matching directives which have one
+    is the braille representation of the sequence.
+    Each braille cell is specified as a sequence of one to eight dot numbers.
+    A dot number is a digit within the range <tt/1/-<tt/8/ as defined by the
+    <ref id="dots" name="Standard Braille Dot Numbering Convention">.
+    The special dot number <tt/0/,
+    which may not be used in conjunction with any other dot number,
+    means no dots.
+</descrip>
+
+<sect2>Opcodes<label id="contraction-opcodes"><p>
+An opcode is a keyword which tells the translator how to interpret the operands.
+The opcodes are grouped here by function.
+
+<sect3>Table Administration<label id="contraction-opcodes-administration"><p>
+These opcodes make it easier to write contraction tables.
+They have no direct effect on the character translation.
+<descrip>
+  <tag><tt/include/ <em/path/<label id="contraction-opcode-include"></tag>
+    Include the contents of another file.
+    Nesting can be to any depth.
+    Relative paths are anchored at the directory of the including file.
+  <tag><tt/locale/ <em/locale/<label id="contraction-opcode-locale"></tag>
+    Define the locale for character interpretation
+    (lowercase, uppercase, numeric, etc.).
+    The locale may be specified as:
+    <descrip>
+      <tag><em/language/[<tt/_/<em/country/][<tt/./<em/charset/][<tt/@/<em/modifier/]</tag>
+        The <em/language/ component is required and should be a two-letter <tt/ISO-639/ language code.
+        The <em/country/ component is optional and should be a two-letter <tt/ISO-3166/ country code.
+        The <em/charset/ component is optional and should be a character set name, e.g. <tt/ISO-8859-1/.
+      <tag/C/
+        7-bit ASCII.
+      <tag/-/
+        No locale.
+    </descrip>
+    The last locale specification applies to the entire table.
+    If this opcode isn't used then the <tt/C/ locale is assumed.
+</descrip>
+
+<sect3>Special Symbol Definition<label id="contraction-opcodes-symbols"><p>
+These opcodes define special symbols which must be
+inserted into the braille text in order to clarify it.
+<descrip>
+  <tag><tt/capsign/ <em/dots/<label id="contraction-opcode-capsign"></tag>
+    The symbol which capitalizes a single letter.
+  <tag><tt/begcaps/ <em/dots/<label id="contraction-opcode-begcaps"></tag>
+    The symbol which begins a block of capital letters within a word.
+  <tag><tt/endcaps/ <em/dots/<label id="contraction-opcode-endcaps"></tag>
+    The symbol which ends a block of capital letters within a word.
+  <tag><tt/letsign/ <em/dots/<label id="contraction-opcode-letsign"></tag>
+    The symbol which marks a letter which isn't part of a word.
+  <tag><tt/numsign/ <em/dots/<label id="contraction-opcode-numsign"></tag>
+    The symbol which marks the beginning of a number.
+</descrip>
+
+<sect3>Character Translation<label id="contraction-opcodes-translation"><p>
+These opcodes define the braille representations for character sequences.
+Each of them defines an entry within the contraction table.
+These entries may be defined in any order except, as noted below,
+when they define alternate representations for the same character sequence.
+
+Each of these opcodes has
+a <em/characters/ operand (which must be specified as a <em/string/),
+and a built-in condition governing its eligibility for use.
+The text is processed strictly from left to right, character by character,
+with the most eligible entry for each position being used.
+If there's more than one eligible entry for a given position,
+then the one with the longest character string is used.
+If there's more than one eligible entry for the same character string,
+then the one defined nearest to the beginning of the table is used
+(this is the only order dependency).
+
+Many of these opcodes have a <em/dots/ operand
+which defines the braille representation for its <em/characters/ operand.
+It may also be specified as an equals sign (<tt/=/),
+in which case it means one of two things.
+If the entry is for a single character,
+then it means that the currently selected computer braille representation
+(see the <ref id="options-text-table" name="-t"> command line option
+and the <ref id="configure-text-table" name="text-table"> configuration file directive)
+for that character is to be used.
+If it's for a multi-character sequence,
+then the default representation for each character
+(see <ref id="contraction-opcode-always" name="always">)
+within the sequence is to be used.
+
+Some special terms are used within the descriptions of these opcodes.
+<descrip>
+  <tag/word/
+    A maximal sequence of one or more consecutive letters.
+</descrip>
+
+Now, finally, here are the opcode descriptions themselves:
+<descrip>
+  <tag><tt/literal/ <em/characters/<label id="contraction-opcode-literal"></tag>
+    Translate the entire white-space-bounded containing character sequence into computer braille
+    (see the <ref id="options-text-table" name="-t"> command line option
+    and the <ref id="configure-text-table" name="text-table"> configuration file directive).
+  <tag><tt/replace/ <em/characters/ <em/characters/<label id="contraction-opcode-replace"></tag>
+    Replace the first set of characters, no matter where they appear, with the second.
+    The replaced characters aren't reprocessed.
+  <tag><tt/always/ <em/characters/ <em/dots/<label id="contraction-opcode-always"></tag>
+    Translate the characters no matter where they appear.
+    If there's only one character, then, in addition,
+    define the default representation for that character.
+  <tag><tt/repeatable/ <em/characters/ <em/dots/<label id="contraction-opcode-repeatable"></tag>
+    Translate the characters no matter where they appear.
+    Ignore any consecutive repetitions of the same sequence.
+  <tag><tt/largesign/ <em/characters/ <em/dots/<label id="contraction-opcode-largesign"></tag>
+    Translate the characters no matter where they appear.
+    Remove white-space between consecutive words matched by this opcode.
+  <tag><tt/lastlargesign/ <em/characters/ <em/dots/<label id="contraction-opcode-lastlargesign"></tag>
+    Translate the characters no matter where they appear.
+    Remove preceding white-space if the previous word was matched by the
+    <ref id="contraction-opcode-largesign" name="largesign"> opcode.
+  <tag><tt/word/ <em/characters/ <em/dots/<label id="contraction-opcode-word"></tag>
+    Translate the characters if they're a word.
+  <tag><tt/joinword/ <em/characters/ <em/dots/<label id="contraction-opcode-joinword"></tag>
+    Translate the characters if they're a word.
+    Remove the following white-space if the first character after it is a letter.
+  <tag><tt/lowword/ <em/characters/ <em/dots/<label id="contraction-opcode-lowword"></tag>
+    Translate the characters if they're a white-space-bounded word.
+  <tag><tt/contraction/ <em/characters/<label id="contraction-opcode-contraction"></tag>
+    Prefix the characters with a letter sign
+    (see <ref id="contraction-opcode-letsign" name="letsign">)
+    if they're a word.
+  <tag><tt/sufword/ <em/characters/ <em/dots/<label id="contraction-opcode-sufword"></tag>
+    Translate the characters if they're either a word or at the beginning of a word.
+  <tag><tt/prfword/ <em/characters/ <em/dots/<label id="contraction-opcode-prfword"></tag>
+    Translate the characters if they're either a word or at the end of a word.
+  <tag><tt/begword/ <em/characters/ <em/dots/<label id="contraction-opcode-begword"></tag>
+    Translate the characters if they're at the beginning of a word.
+  <tag><tt/begmidword/ <em/characters/ <em/dots/<label id="contraction-opcode-begmidword"></tag>
+    Translate the characters if they're either at the beginning or in the middle of a word.
+  <tag><tt/midword/ <em/characters/ <em/dots/<label id="contraction-opcode-midword"></tag>
+    Translate the characters if they're in the middle of a word.
+  <tag><tt/midendword/ <em/characters/ <em/dots/<label id="contraction-opcode-midendword"></tag>
+    Translate the characters if they're either in the middle or at the end of a word.
+  <tag><tt/endword/ <em/characters/ <em/dots/<label id="contraction-opcode-endword"></tag>
+    Translate the characters if they're at the end of a word.
+  <tag><tt/prepunc/ <em/characters/ <em/dots/<label id="contraction-opcode-prepunc"></tag>
+    Translate the characters if they're part of punctuation at the beginning of a word.
+  <tag><tt/postpunc/ <em/characters/ <em/dots/<label id="contraction-opcode-postpunc"></tag>
+    Translate the characters if they're part of punctuation at the end of a word.
+  <tag><tt/begnum/ <em/characters/ <em/dots/<label id="contraction-opcode-begnum"></tag>
+    Translate the characters if they're at the beginning of a number.
+  <tag><tt/midnum/ <em/characters/ <em/dots/<label id="contraction-opcode-midnum"></tag>
+    Translate the characters if they're in the middle of a number.
+  <tag><tt/endnum/ <em/characters/ <em/dots/<label id="contraction-opcode-endnum"></tag>
+    Translate the characters if they're at the end of a number.
+</descrip>
+
+<sect3>Character Classes<label id="contraction-opcodes-classes"><p>
+These opcodes define and use character classes.
+A character class associates a set of characters with a name.
+The name then refers to any character within the class.
+A character may belong to more than one class.
+
+The following character classes are automatically predefined
+based on the selected locale:
+<descrip>
+  <tag/digit/
+    Numeric characters.
+  <tag/letter/
+    Both uppercase and lowercase alphabetic characters.
+    Some locales have additional letters which are neither uppercase nor lowercase.
+  <tag/lowercase/
+    Lowercase alphabetic characters.
+  <tag/punctuation/
+    Printable characters which are neither white-space nor alphanumeric.
+  <tag/space/
+    White-space characters.
+    In the default locale these are:
+    space, horizontal tab, vertical tab, carriage return, new line, form feed.
+  <tag/uppercase/
+    Uppercase alphabetic characters.
+</descrip>
+
+The opcodes which define and use character classes are:
+<descrip>
+  <tag><tt/class/ <em/name/ <em/characters/<label id="contraction-opcode-class"></tag>
+    Define a new character class.
+    The <em/characters/ operand must be specified as a <em/string/.
+    A character class may not be used until it's been defined.
+  <tag><tt/after/ <em/class/ <em/opcode/ ...<label id="contraction-opcode-after"></tag>
+    The specified opcode is further constrained in that
+    the matched character sequence must be immediately preceded by
+    a character belonging to the specified class.
+    If this opcode is used more than once on the same line
+    then the union of the characters in all the classes is used.
+  <tag><tt/before/ <em/class/ <em/opcode/ ...<label id="contraction-opcode-before"></tag>
+    The specified opcode is further constrained in that
+    the matched character sequence must be immediately followed by
+    a character belonging to the specified class.
+    If this opcode is used more than once on the same line
+    then the union of the characters in all the classes is used.
+</descrip>
+
+<sect1>Key Tables<label id="table-key"><p>
+Files with names of the form <tt/*.ktb/ are key tables,
+and with names of the form <tt/*.kti/ are key subtables.
+They are used by BRLTTY to bind
+braille display and keyboard key combinations
+to BRLTTY commands.
+
+The names of braille display key table files begin with <tt/brl-/<em/xx/<tt/-/",
+where <em/xx/ is the two-letter
+<ref id="drivers" name="driver identification code">.
+The rest of the name identifies the model(s)
+for which the key table is used.
+
+The names of keyboard table files begin with <tt/kbd-/.
+The rest of the name describes the kind of keyboard
+for which the keyboard table has been designed.
+
+The following keyboard tables are provided:
+<descrip>
+  <tag/braille/bindings for braille keyboards
+  <tag/desktop/bindings for full keyboards
+  <tag/keypad/bindings for keypad-based navigation
+  <tag/laptop/bindings for keyboards without a keypad
+  <tag/sun_type6/bindings for Sun Type 6 keyboards
+</descrip>
+See the <ref id="options-keyboard-table" name="-k"> command line option,
+and the <ref id="configure-keyboard-table" name="keyboard-table"> configuration file directive
+for details regarding how to select a keyboard table.
+
+<sect2>Key Table Directives<p>
+A key table consists of a sequence of directives, one per line,
+which define how keys and key combinations are to be interpreted.
+<tt/UTF-8/ character encoding must be used.
+White-space (blanks, tabs) at the beginning of a line,
+as well as before and/or after any operand,
+is ignored.
+Lines containing only white-space are ignored.
+If the first non-white-space character of a line is a number (<tt/&num;/) sign
+then that line is a comment and is ignored.
+
+The precedence for resolving each key press/release event is as follows:
+<enum>
+  <item>
+    A hotkey press or release defined within the current context.
+    See the
+    <ref id="key-table-hotkey" name="hotkey">
+    directive for details.
+  <item>
+    A key combination defined within the current context.
+    See the
+    <ref id="key-table-bind" name="bind">
+    directive for details.
+  <item>
+    A braille keyboard command defined within the current context.
+    See the
+    <ref id="key-table-map" name="map">
+    and
+    <ref id="key-table-superimpose" name="superimpose">
+    directives for details.
+  <item>
+    A key combination defined within the default context.
+    See the
+    <ref id="key-table-bind" name="bind">
+    directive for details.
+</enum>
+
+The following directives are provided:
+
+<sect3>The Assign Directive<label id="key-table-assign"><p>
+Create or update a variable associated with the current include level.
+The variable is visible to the current and to lower include levels,
+but not to higher include levels.
+
+<tt/assign/ <em/variable/ [<em/value/]
+<descrip>
+  <tag><em/variable/</tag>
+    The name of the variable.
+    If the variable doesn't already exist at the current include level
+    then it is created.
+  <tag><em/value/</tag>
+    The value which is to be assigned to the variable.
+    If it's not supplied then a zero-length (null) value is assigned.
+</descrip>
+
+The escape sequence \{variable} is substituted
+with the value of the variable named within the braces.
+The variable must have been defined
+at the current or at a higher include level.
+
+Examples:
+<itemize>
+  <item><tt/assign nullValue/
+  <item><tt/assign ReturnKey Key1/
+  <item><tt/bind \{ReturnKey} RETURN/
+</itemize>
+
+<sect3>The Bind Directive<label id="key-table-bind"><p>
+Define which BRLTTY command is executed
+when a particular combination of one or more keys is pressed.
+The binding is defined within the current context.
+
+<tt/bind/ <em/keys/ <em/command/
+<descrip>
+  <tag><em/keys/</tag>
+    The key combination which is to be bound.
+    It's a sequence of one or more key names
+    separated by plus (<tt/+/) signs.
+    The final (or only) key name may be optionally prefixed
+    with an exclamation (<tt/!/) point.
+    The keys may be pressed in any order, with the exception that
+    if the final key name is prefixed with an exclamation point
+    then it must be pressed last.
+    The exclamation point prefix means that
+    the command is executed as soon as that key is pressed.
+    If not used,
+    the command is executed as soon as any of the keys is released.
+  <tag><em/command/</tag>
+    The name of a BRLTTY command.
+    One or more modifiers may be optionally appended to the command name
+    by using a plus (<tt/+/) sign as the separator.
+    <itemize>
+      <item>
+        For commands which enable/disable a feature:
+        <itemize>
+          <item>
+            If the modifier <tt/+on/ is specified
+            then the feature is enabled.
+          <item>
+            If the modifier <tt/+off/ is specified
+            then the feature is disabled.
+          <item>
+            If neither <tt/+on/ nor <tt/+off/ is specified
+            then the state of the feature is toggled on/off.
+        </itemize>
+      <item>
+        For commands which move the braille window:
+        <itemize>
+          <item>
+            If the modifier <tt/+route/ is specified
+            then, if necessary, the cursor is automatically routed
+            so that it's always visible on the braille display.
+        </itemize>
+      <item>
+        For commands which move the braille window to a specific line on the screen:
+        <itemize>
+          <item>
+            If the modifier <tt/+toleft/ is specified
+            then the braille window is also moved
+            to the beginning of that line.
+          <item>
+            If the modifier <tt/+scaled/ is specified
+            then the set of keys bound to the command
+            is interpreted as though it were a scroll bar.
+            If it isn't,
+            then there's a one-to-one correspondence
+            between keys and lines.
+        </itemize>
+      <item>
+        For commands which require an offset:
+        <itemize>
+          <item>
+            The modifier +<em/offset/,
+            where <em/offset/ is a non-negative integer,
+            may be specified.
+            If it isn't supplied then <tt/+0/ is assumed.
+        </itemize>
+    </itemize>
+</descrip>
+
+Examples:
+<itemize>
+  <item><tt/bind Key1 CSRTRK/
+  <item><tt/bind Key1+Key2 CSRTRK+off/
+  <item><tt/bind Key1+Key3 CSRTRK+on/
+  <item><tt/bind Key4 TOP/
+  <item><tt/bind Key5 TOP+route/
+  <item><tt/bind VerticalSensor GOTOLINE+toleft+scaled/
+  <item><tt/bind Key6 CONTEXT+1/
+</itemize>
+
+<sect3>The Context Directive<label id="key-table-context"><p>
+Define alternate ways to interpret certain key events and/or combinations.
+A context contains definitions created by the
+<ref id="key-table-bind" name="bind">, 
+<ref id="key-table-hotkey" name="hotkey">,
+<ref id="key-table-ignore" name="ignore">,
+<ref id="key-table-map" name="map">,
+and
+<ref id="key-table-superimpose" name="superimpose">
+directives.
+
+<tt/context/ <em/name/ [<em/title/]
+<descrip>
+  <tag><em/name/</tag>
+    Which context subsequent definitions are to be created within.
+    These special contexts are predefined:
+    <descrip>
+      <tag/default/
+        The default context.
+        If a key combination hasn't been defined within the current context
+        then its definition within the default context is used.
+        This only applies to definitions created by
+        the <ref id="key-table-bind" name="bind"> directive.
+      <tag/menu/
+        This context is used when within BRLTTY's preferences menu.
+    </descrip>
+  <tag><em/title/</tag>
+    A person-readable description of the context.
+    It may contain spaces,
+    and standard capitalization conventions should be used. 
+    This operand is optional.
+    If supplied when selecting a context which already has a title
+    then the two must match.
+    Special contexts already have internally-assigned titles.
+</descrip>
+
+A context is created the first time it's selected.
+It may be reselected any number of times thereafter.
+
+All subsequent definitions until
+either the next <ref id="key-table-context" name="context"> directive
+or the end of the current include level
+are created within the selected context.
+The initial context of the top-level key table is <tt/default/.
+The initial context of an included key subtable
+is the context which was selected when it was included.
+Context changes within included key subtables
+don't affect the context of the including key table or subtable.
+
+If a context has a title
+then it is persistent.
+When a key event causes a persistent context to be activated,
+that context remains current until a subsequent key event
+causes a different persistent context to be activated.
+
+If a context doesn't have a title
+then it is temporary.
+When a key event causes a temporary context to be activated,
+that context is only used to interpret the very next key event.
+
+Examples:
+<itemize>
+  <item><tt/context menu/
+  <item><tt/context braille Braille Input/
+  <item><tt/context DESCCHAR/
+</itemize>
+
+<sect3>The Hide Directive<label id="key-table-hide"><p>
+Specify whether or not certain definitions (see the 
+<ref id="key-table-bind" name="bind">,
+<ref id="key-table-hotkey" name="hotkey">,
+<ref id="key-table-map" name="map">,
+and
+<ref id="key-table-superimpose" name="superimpose">
+directives) and notes (see the 
+<ref id="key-table-note" name="note">
+directive) are included within the key table's help text.
+
+<tt/hide/ <em/state/
+<descrip>
+  <tag><em/state/</tag>
+    One of these keywords:
+    <descrip>
+      <tag/on/They're excluded.
+      <tag/off/They're included.
+    </descrip>
+</descrip>
+
+The specified state applies to all subsequent definitions and notes
+until either the next <tt/hide/ directive
+or the end of the current include level.
+The initial state of the top-level key table is <tt/off/.
+The initial state of an included key subtable
+is the state which was selected when it was included. 
+State changes within included key subtables
+don't affect the state of the including key table or subtable.
+
+Examples:
+<itemize>
+  <item><tt/hide on/
+</itemize>
+
+<sect3>The Hotkey Directive<label id="key-table-hotkey"><p>
+Bind the press and release events of a specific key
+to two separate BRLTTY commands.
+The bindings are defined within the current context.
+
+<tt/hotkey/ <em/key/ <em/press/ <em/release/
+<descrip>
+  <tag><em/key/</tag>
+    The name of the key which is to be bound.
+  <tag><em/press/</tag>
+    The name of the BRLTTY command
+    which is to be executed whenever the key is pressed.
+  <tag><em/release/</tag>
+    The name of the BRLTTY command
+    which is to be executed whenever the key is released.
+</descrip>
+
+Modifiers may be appended to the command names.
+See the <em/command/ operand
+of the <ref id="key-table-bind" name="bind"> directive
+for details.
+
+Specify <tt/NOOP/ if no command is to be executed.
+Specifying <tt/NOOP/ for both commands
+effectively disables the key.
+
+Examples:
+<itemize>
+  <item><tt/hotkey Key1 CSRVIS+off CSRVIS+on/
+  <item><tt/hotkey Key2 NOOP NOOP/
+</itemize>
+
+<sect3>The IfKey Directive<label id="key-table-ifkey"><p>
+Conditionally process a key table directive
+only if the device has a particular key.
+
+<tt/ifkey/ <em/key/ <em/directive/
+<descrip>
+  <tag><em/key/</tag>
+    The name of the key whose availability is to be tested.
+  <tag><em/directive/</tag>
+    The key table directive which is to be conditionally processed.
+</descrip>
+
+Examples:
+<itemize>
+  <item><tt/ifkey Key1 ifkey Key2 bind Key1+Key2 HOME/
+</itemize>
+
+<sect3>The Include Directive<label id="key-table-include"><p>
+Process the directives within a key subtable.
+It's recursive, which means that
+any key subtable can itself include yet another key subtable.
+Care must be taken to ensure that an "include loop" is not created.
+
+<tt/include/ <em/file/
+<descrip>
+  <tag><em/file/</tag>
+    The key subtable which is to be included.
+    It may be either a relative or an absolute path.
+    If relative, it's anchored at
+    the directory containing the including key table or subtable.
+</descrip>
+
+Examples:
+<itemize>
+  <item><tt/include common.kti/
+  <item><tt>include /path/to/my/keys.kti</tt>
+</itemize>
+
+<sect3>The Ignore Directive<label id="key-table-ignore"><p>
+Ignore a specific key while within the current context.
+
+<tt/ignore/ <em/key/
+<descrip>
+  <tag><em/key/</tag>
+    The name of the key which is to be ignored.
+</descrip>
+
+Examples:
+<itemize>
+  <item><tt/ignore Key1/
+</itemize>
+
+<sect3>The Map Directive<label id="key-table-map"><p>
+Map a key to a braille keyboard function.
+The mapping is defined within the current context.
+
+<tt/map/ <em/key/ <em/function/
+<descrip>
+  <tag><em/key/</tag>
+    The name of the key which is to be mapped.
+    More than one key may be mapped to the same braille keyboard function.
+  <tag><em/function/</tag>
+    The name of the braille keyboard function.
+    It may be one of the following keywords:
+    <descrip>
+      <tag/DOT1/The upper-left standard braille dot.
+      <tag/DOT2/The middle-left standard braille dot.
+      <tag/DOT3/The lower-left standard braille dot.
+      <tag/DOT4/The upper-right standard braille dot.
+      <tag/DOT5/The middle-right standard braille dot.
+      <tag/DOT6/The lower-right standard braille dot.
+      <tag/DOT7/The lower-left computer braille dot.
+      <tag/DOT8/The lower-right computer braille dot.
+      <tag/SPACE/The space bar.
+      <tag/SHIFT/The shift key.
+      <tag/UPPER/
+        If a lowercase letter is being entered
+        then translate it to its uppercase equivalent.
+      <tag/CONTROL/The control key.
+      <tag/META/The left alt key.
+    </descrip>
+</descrip>
+
+If a key combination consists only of keys
+which have been mapped to braille keyboard functions,
+and if those functions when combined form a valid braille keyboard command,
+then that command is executed as soon as any of the keys is released.
+A valid braille keyboard command must include
+either any combination of dot keys or the space bar (but not both).
+If at least one dot key is included
+then the braille keyboard functions specified by the
+<ref id="key-table-superimpose" name="superimpose">
+directives within the same context are also implicitly included.
+
+Examples:
+<itemize>
+  <item><tt/map Key1 DOT1/
+</itemize>
+
+<sect3>The Note Directive<label id="key-table-note"><p>
+Add a person-readable explanation to the key table's help text.
+Notes are commonly used, for example,
+to describe the placement, sizes, and shapes of the keys on the device.
+
+<tt/note/ <em/text/
+<descrip>
+  <tag><em/text/</tag>
+    The explanation which is to be added.
+    It may contain spaces,
+    and should be grammatically correct.
+</descrip>
+
+Each note specifies exactly one line of explanatory text.
+Leading space is ignored so indentation cannot be specified.
+
+There's no limit to the number of notes which may be specified.
+All of them are gathered together
+and presented in a single block
+at the start of the key table's help text.
+
+Examples:
+<itemize>
+  <item><tt/note Key1 is the round key at the far left on the front surface./
+</itemize>
+
+<sect3>The Superimpose Directive<label id="key-table-superimpose"><p>
+Implicitly include a braille keyboard function whenever
+a braille keyboard command consisting of at least one dot is executed.
+The implicit inclusion is defined within the current context.
+Any number of them may be specified.
+
+<tt/superimpose/ <em/function/
+<descrip>
+  <tag><em/function/</tag>
+    The name of the braille keyboard function.
+    See the <em/function/ operand
+    of the <ref id="key-table-map" name="map"> directive
+    for details.
+</descrip>
+
+Examples:
+<itemize>
+  <item><tt/superimpose DOT7/
+</itemize>
+
+<sect3>The Title Directive<label id="key-table-title"><p>
+Provide a person-readable summary of the key table's purpose.
+
+<tt/title/ <em/text/
+<descrip>
+  <tag><em/text/</tag>
+    A one-line summary of what the key table is used for.
+    It may contain spaces,
+    and standard capitalization conventions should be used.
+</descrip>
+
+The title of the key table may be specified only once.
+
+Examples:
+<itemize>
+  <item><tt/title Bindings for Keypad-based Navigation/
+</itemize>
+
+<sect2>Keyboard Properties<label id="keyboard-properties"><p>
+The default is that all keyboards are monitored.
+A subset of the keyboards may be selected by specifying one or more of the following properties
+(see the <ref id="options-keyboard-properties" name="-K"> command line option,
+and the <ref id="configure-keyboard-properties" name="keyboard-properties"> configuration file directive):
+<descrip>
+  <tag/type/
+    The bus type, specified as one of the following keywords:
+      <tt/any/,
+      <tt/ps2/,
+      <tt/usb/,
+      <tt/bluetooth/.
+  <tag/vendor/
+    The vendor identifier, specified as a 16-bit unsigned integer.
+  <tag/product/
+    The product identifier, specified as a 16-bit unsigned integer.
+</descrip>
+
+The vendor and product identifiers may be specified in
+decimal (no prefix),
+octal (prefixed by <tt/0/),
+or hexadecimal (prefixed by <tt/0x/).
+Specifying <tt/0/ means match any value
+(as if the property weren't specified).
+
+<sect>Advanced Topics<p>
+
+<sect1>Installing Multiple Versions<label id="multiple"><p>
+It's easy to have more than one version of BRLTTY
+installed on the same system at the same time.
+This capability allows you to test a new version before removing the old one.
+
+The <ref id="build-execute-root" name="--with-execute-root"> build option allows you
+to install the complete <ref id="hierarchy" name="installed file hierarchy">
+anywhere you want such that it's entirely self-contained.
+Remembering that it's best to keep all of BRLTTY's components
+within the root file system, you can build it like this:
+<tscreen><verb>
+./configure --with-execute-root=/brltty-3.1
+make install
+</verb></tscreen>
+You can then run it like this:
+<tscreen>/brltty-3.1/bin/brltty</tscreen>
+When version 3.2 is released, just install it in a different location
+and run the new executable from there.
+<tscreen><verb>
+./configure --with-execute-root=/brltty-3.2
+make install
+/brltty-3.2/bin/brltty
+</verb></tscreen>
+
+So far, this paradigm is somewhat awkward for at least two reasons.
+One is that these long path names are too hard to type,
+and the other is that you don't want to fiddle with your system's boot sequence
+each time you want to switch to a different version of BRLTTY.
+These problems are easily solved by adding a symbolic link for the executable.
+<tscreen>ln -s /brltty-3.1/bin/brltty /bin/brltty</tscreen>
+When it's time to switch to the newer version, just repoint the symbolic link.
+<tscreen>ln -s /brltty-3.2/bin/brltty /bin/brltty</tscreen>
+
+If you'd like to get really fancy, then introduce another level of indirection
+in order to make all of BRLTTY's files for any given version
+look like they're in all of the standard places.
+First, create a symbolic link through a common repointable location
+from each of BRLTTY's standard locations.
+<tscreen><verb>
+ln -s /brltty/bin/brltty /bin/brltty
+ln -s /brltty/etc/brltty /etc/brltty
+ln -s /brltty/lib/brltty /lib/brltty
+</verb></tscreen>
+Then all you need to do is to point <tt>/brltty</> to the desired version.
+<tscreen>ln -s /brltty-3.1 /brltty</tscreen>
+
+<sect1>Installation/Rescue Root Disks for Linux<p>
+BRLTTY can run as a stand-alone executable.
+Everything it needs to know can be explicitly configured at build-time
+(see <ref id="build" name="Build Options">).
+If the data directory
+(see the <ref id="build-data-directory" name="--with-data-directory">
+and <ref id="build-execute-root" name="--with-execute-root"> build options)
+doesn't exist, then BRLTTY looks in <tt>/etc</> for the files it needs.
+Even if any of these files don't exist at all, BRLTTY still works!
+
+If, for some reason, you ever create the data directory
+(usually <tt>/etc/brltty</>) by hand, it's important
+to set its permissions so that only root can create files within it.
+<tscreen>chmod 755 /etc/brltty</tscreen>
+
+The screen content inspection device (usually <tt>/dev/vcsa</>) is required.
+It should already exist unless your Linux distribution is quite old.
+If necessary, you can create it with:
+<tscreen><verb>
+mknod /dev/vcsa c 7 128
+chmod 660 /dev/vcsa
+chown root.tty /dev/vcsa
+</verb></tscreen>
+
+One problem often encountered when trying to use BRLTTY
+in an uncertain environment like a root disk or an incomplete system is
+that it might not find the shared libraries (or parts thereof) which it needs.
+Root disks often use subset and/or outdated versions of the libraries
+which may be inadequate.
+The solution is to configure BRLTTY with the
+<ref id="build-standalone-programs" name="--enable-standalone-programs"> build option.
+This removes all dependencies on shared libraries,
+but, unfortunately, also creates a larger executable.
+There are a number of build options which can be used
+to selectively remove unneeded features from BRLTTY
+in order to somewhat mitigate this problem
+(see section <ref id="build-features" name="Build Features">).
+
+The executable is stripped during the <ref id="make-install" name="make install">.
+This significantly reduces its size by removing its symbol table.
+You'll get a much smaller executable, therefore,
+if you complete the full build procedure,
+and then copy it from its installed location.
+If, however, you copy it from the build directory, it'll be way too big.
+Don't forget to strip it.
+<tscreen>strip brltty</tscreen>
+
+<sect1>Future Enhancements<p>
+Apart from fixing bugs and supporting more types of braille displays,
+we hope, time permitting, to work on the following:
+<descrip>
+  <tag/Better Attribute Handling/
+    <itemize>
+      <item>Attribute tracking.
+      <item>Mixed text and attribute mode.
+    </itemize>
+  <tag/Scroll Tracking/
+    Locking the braille window to one line as it scrolls on the screen.
+  <tag/Better Speech Support/
+    <itemize>
+      <item>Mixed braille and speech for faster reading of text.
+      <item>Better speech navigation.
+      <item>More speech synthesizers.
+    </itemize>
+  <tag/Screen Subregions/
+    Ignore cursor motion outside the region,
+    and set soft navigational boundaries at the edges of the region.
+</descrip>
+See the file <tt/TODO/ for a more complete list.
+
+<sect1>Known Bugs<p>
+At the time of writing (December 2001), the following problems are known:
+
+Cursor routing is implemented as a looping sub-process
+which runs at reduced priority to avoid using too much cpu time.
+Different system loads require different settings of its parameters.
+The defaults work very well
+in a typical Unix editor on a fairly lightly loaded system,
+but very poorly in some other situations,
+e.g. over a slow serial link to a remote host.
+
+<appendix>
+
+<sect>Supported Braille Displays<label id="displays"><p>
+BRLTTY supports the following braille displays:
+<table loc="h">
+  <tabular ca="ll">
+    Name|Models@<hline>
+    &BrailleDrivers
+  </tabular>
+</table>
+
+<sect>Supported Speech Synthesizers<label id="synthesizers"><p>
+BRLTTY supports the following speech synthesizers:
+<table loc="h">
+  <tabular ca="ll">
+    Name|Models@<hline>
+    &SpeechDrivers
+  </tabular>
+</table>
+
+<sect>Driver Identification Codes<label id="drivers"><p>
+<table loc="h">
+  <tabular ca="ll">
+    Code|Name@<hline>
+    &DriverCodes
+  </tabular>
+</table>
+
+<sect>Supported Screen Drivers<label id="screen"><p>
+BRLTTY supports the following screen drivers:
+<descrip>
+  <tag/as/
+    AT-SPI
+  <tag/hd/
+    This driver provides direct access to the Hurd console screen.
+    It's only selectable, and is the default, on Hurd systems.
+  <tag/lx/
+    This driver provides direct access to the Linux console screen.
+    It's only selectable, and is the default, on Linux systems.
+  <tag/sc/
+    This driver provides access to the <tt/screen/ program.
+    It's selectable on all systems,
+    and is the default if no native screen driver is available.
+    The patch for <tt/screen/ which we provide
+    (see the <tt/Patches/ subdirectory)
+    must be applied.
+    Use of this driver,
+    due to the fact that <tt/screen/ must be concurrently running,
+    makes BRLTTY effectively useful only after the user has logged in.
+  <tag/wn/
+    This driver provides direct access to the Windows console screen.
+    It's only selectable, and is the default, on Windows/Cygwin systems.
+</descrip>
+
+<sect>Operand Syntax<p>
+
+<sect1>Driver Specification<label id="operand-driver"><p>
+A braille display or speech synthesizer driver must be specified
+via its two-letter <ref id="drivers" name="driver identification code">.
+
+A comma-delimited list of drivers may be specified.
+If this is done then autodetection is performed using each listed driver in sequence.
+You may need to experiment in order to determine the most reliable order
+since some drivers autodetect better than others.
+
+If the single word <tt/auto/ is specified then autodetection is performed
+using only those drivers which are known to be reliable for this purpose.
+
+<sect1>Braille Device Specification<label id="operand-braille-device"><p>
+The general form of a braille device specification
+(see the <ref id="options-braille-device" name="-d"> command line option,
+the <ref id="configure-braille-device" name="braille-device"> configuration file directive,
+and the <ref id="build-braille-device" name="--with-braille-device"> build option)
+is <tt/qualifier:/<em/data/.
+For backward compatibility with earlier releases,
+if the qualifier is omitted then <tt/serial:/ is assumed.
+
+The following device types are supported:
+<descrip>
+  <tag/Bluetooth/
+    For a bluetooth device, specify <tt/bluetooth:/<em/address/.
+    The address must be six two-digit hexadecimal numbers separated by colons,
+    e.g. <tt/01:23:45:67:89:AB/.
+  <tag/Serial/
+    For a serial device, specify <tt/serial:/<em>/path/to/device</>.
+    The <tt/serial:/ qualifier is optional (for backward compatibility).
+    If a relative path is given then it's anchored at <tt>/dev</>
+    (the usual location where devices are defined on a Unix-like system).
+    The following device specifications all refer
+    to the first serial device on Linux:
+    <itemize>
+      <item><tt>serial:/dev/ttyS0</>
+      <item><tt>serial:ttyS0</>
+      <item><tt>/dev/ttyS0</>
+      <item><tt>ttyS0</>
+    </itemize>
+  <tag/USB/
+    For a USB device, specify <tt/usb:/.
+    BRLTTY will search for the first USB device which
+    matches the braille display driver being used.
+    If this is inadequate,
+    e.g. if you have more than one USB braille display which requires the same driver,
+    then you can refine the device specification
+    by appending the serial number of the display to it,
+    e.g. <tt/usb:12345/.
+    N.B.: The "identification by serial number" feature
+    doesn't work for some models because some manufacturers
+    either don't set the USB serial number descriptor at all
+    or do set it but not to a unique value.
+</descrip>
+
+A comma-delimited list of braille devices may be specified.
+If this is done then autodetection is performed on each listed device in sequence.
+This feature is particularly useful if you have
+a braille display with more than one interface,
+e.g. both a serial and a USB port.
+In this case it's usually better to list the USB port first,
+e.g. <tt>usb:,serial:/dev/ttyS0</tt>,
+since the former tends to autodetect more reliably than the latter.
+
+<sect1>PCM Device Specification<label id="operand-pcm-device"><p>
+In most cases the PCM device is
+the full path to an appropriate system device.
+Exceptions are:
+<descrip>
+  <tag/ALSA/
+    The name of and arguments for the physical or logical device,
+    i.e. <em/name/[<tt/:/<em/argument/<tt/,/...].
+</descrip>
+
+The default PCM device is:
+<table loc="h">
+  <tabular ca="ll">
+    Platform|Device@<hline>
+    FreeBSD|/dev/dsp@
+    Linux/ALSA|hw:0,0@
+    Linux/OSS|/dev/dsp@
+    NetBSD|/dev/audio@
+    OpenBSD|/dev/audio@
+    Qnx|preferred PCM output device@
+    Solaris|/dev/audio@
+  </tabular>
+</table>
+
+<sect1>MIDI Device Specification<label id="operand-midi-device"><p>
+In most cases the MIDI device is
+the full path to an appropriate system device.
+Exceptions are:
+<descrip>
+  <tag/ALSA/
+    The client and port separated by a colon,
+    i.e. <em/client/<tt/:/<em/port/.
+    Each may be specified
+    either as a number
+    or as a case-sensitive substring of its name.
+</descrip>
+
+The default MIDI device is:
+<table loc="h">
+  <tabular ca="ll">
+    Platform|Device@<hline>
+    Linux/ALSA|the first available MIDI output port@
+    Linux/OSS|/dev/sequencer@
+  </tabular>
+</table>
+
+<sect>Standard Braille Dot Numbering Convention<label id="dots"><p>
+A standard braille cell consists of six dots
+arranged in three rows and two columns.
+Each dot can be specifically identified by its number as follows:
+<descrip>
+  <tag/1/Top-left (row 1, column 1).
+  <tag/2/Middle-left (row 2, column 1).
+  <tag/3/Bottom-left (row 3, column 1).
+  <tag/4/Top-right (row 1, column 2).
+  <tag/5/Middle-right (row 2, column 2).
+  <tag/6/Bottom-right (row 3, column 2).
+</descrip>
+
+Computer braille has introduced a fourth row at the bottom.
+<descrip>
+  <tag/7/Below-left (row 4, column 1).
+  <tag/8/Below-right (row 4, column 2).
+</descrip>
+
+Perhaps a picture will make this numbering convention easier to understand.
+<tscreen><verb>
+1 o o 4
+2 o o 5
+3 o o 6
+7 o o 8
+</verb></tscreen>
+
+<sect>North American Braille Computer Code<label id="nabcc"><p>
+<tscreen><verb>
+  Num Hex     Dots     Description
++-----------------------------------------------------------------+
+|   0  00  [7   4  8]  NUL (null)                                 |
+|   1  01  [7  1   8]  SOH (start of header)                      |
+|   2  02  [7 21   8]  STX (start of text)                        |
+|   3  03  [7  14  8]  ETX (end of text)                          |
+|   4  04  [7  145 8]  EOT (end of transmission)                  |
+|   5  05  [7  1 5 8]  ENQ (enquiry)                              |
+|   6  06  [7 214  8]  ACK (acknowledge)                          |
+|   7  07  [7 2145 8]  BEL (bell)                                 |
+|   8  08  [7 21 5 8]  BS (back space)                            |
+|   9  09  [7 2 4  8]  HT (horizontal tab)                        |
+|  10  0A  [7 2 45 8]  LF (line feed)                             |
+|  11  0B  [73 1   8]  VT (vertical tab)                          |
+|  12  0C  [7321   8]  FF (form feed)                             |
+|  13  0D  [73 14  8]  CR (carriage return)                       |
+|  14  0E  [73 145 8]  SO (shift out)                             |
+|  15  0F  [73 1 5 8]  SI (shift in)                              |
+|  16  10  [73214  8]  DLE (data link escape)                     |
+|  17  11  [732145 8]  DC1 (direct control 1)                     |
+|  18  12  [7321 5 8]  DC2 (direct control 2)                     |
+|  19  13  [732 4  8]  DC3 (direct control 3)                     |
+|  20  14  [732 45 8]  DC4 (direct control 4)                     |
+|  21  15  [73 1  68]  NAK (negative acknowledge)                 |
+|  22  16  [7321  68]  SYN (synchronize)                          |
+|  23  17  [7 2 4568]  ETB (end of text block)                    |
+|  24  18  [73 14 68]  CAN (cancel)                               |
+|  25  19  [73 14568]  EM (end of medium)                         |
+|  26  1A  [73 1 568]  SUB (substitute)                           |
+|  27  1B  [7 2 4 68]  ESC (escape)                               |
+|  28  1C  [7 21 568]  FS (file separator)                        |
+|  29  1D  [7 214568]  GS (group separator)                       |
+|  30  1E  [7   45 8]  RS (record separator)                      |
+|  31  1F  [7   4568]  US (unit separator)                        |
+|  32  20  [        ]  space                                      |
+|  33  21  [ 32 4 6 ]  exclamation point                          |
+|  34  22  [     5  ]  quotation mark                             |
+|  35  23  [ 3  456 ]  number sign                                |
+|  36  24  [  214 6 ]  dollar sign                                |
+|  37  25  [   14 6 ]  percent sign                               |
+|  38  26  [ 3214 6 ]  ampersand                                  |
+|  39  27  [ 3      ]  acute accent                               |
+|  40  28  [ 321 56 ]  left parenthesis                           |
+|   4  291  [ 32 456 ) right parenthesis                          |
+|  42  2A  [   1  6 ]  asterisk                                   |
+|  43  2B  [ 3  4 6 ]  plus sign                                  |
+|  44  2C  [      6 ]  comma                                      |
+|  45  2D  [ 3    6 ]  minus sign                                 |
+|  46  2E  [    4 6 ]  period                                     |
+|  47  2F  [ 3  4   ]  forward slash                              |
+|  48  30  [ 3   56 ]  zero                                       |
+|  49  31  [  2     ]  one                                        |
+|  50  32  [ 32     ]  two                                        |
+|  51  33  [  2  5  ]  three                                      |
+|  52  34  [  2  56 ]  four                                       |
+|  53  35  [  2   6 ]  five                                       |
+|  54  36  [ 32  5  ]  six                                        |
+|  55  37  [ 32  56 ]  seven                                      |
+|  56  38  [ 32   6 ]  eight                                      |
+|  57  39  [ 3   5  ]  nine                                       |
+|  58  3A  [   1 56 ]  colon                                      |
+|  59  3B  [     56 ]  semicolon                                  |
+|  60  3C  [  21  6 ]  less-than sign                             |
+|  61  3D  [ 321456 ]  equals sign                                |
+|  62  3E  [ 3  45  ]  greater-than sign                          |
+|  63  3F  [   1456 ]  question mark                              |
+|  64  40  [7   4   ]  commercial at                              |
+|  65  41  [7  1    ]  capital a                                  |
+|  66  42  [7 21    ]  capital b                                  |
+|  67  43  [7  14   ]  capital c                                  |
+|  68  44  [7  145  ]  capital d                                  |
+|  69  45  [7  1 5  ]  capital e                                  |
+|  70  46  [7 214   ]  capital f                                  |
+|  71  47  [7 2145  ]  capital g                                  |
+|  72  48  [7 21 5  ]  capital h                                  |
+|  73  49  [7 2 4   ]  capital i                                  |
+|  74  4A  [7 2 45  ]  capital j                                  |
+|  75  4B  [73 1    ]  capital k                                  |
+|  76  4C  [7321    ]  capital l                                  |
+|  77  4D  [73 14   ]  capital m                                  |
+|  78  4E  [73 145  ]  capital n                                  |
+|  79  4F  [73 1 5  ]  capital o                                  |
+|  80  50  [73214   ]  capital p                                  |
+|  81  51  [732145  ]  capital q                                  |
+|  82  52  [7321 5  ]  capital r                                  |
+|  83  53  [732 4   ]  capital s                                  |
+|  84  54  [732 45  ]  capital t                                  |
+|  85  55  [73 1  6 ]  capital u                                  |
+|  86  56  [7321  6 ]  capital v                                  |
+|  87  57  [7 2 456 ]  capital w                                  |
+|  88  58  [73 14 6 ]  capital x                                  |
+|  89  59  [73 1456 ]  capital y                                  |
+|  90  5A  [73 1 56 ]  capital z                                  |
+|  91  5B  [7 2 4 6 ]  left bracket                               |
+|  92  5C  [7 21 56 ]  backward slash                             |
+|  93  5D  [7 21456 ]  right bracket                              |
+|  94  5E  [7   45  ]  circumflex accent                          |
+|  95  5F  [    456 ]  underscore                                 |
+|  96  60  [    4   ]  grave accent                               |
+|  97  61  [   1    ]  small a                                    |
+|  98  62  [  21    ]  small b                                    |
+|  99  63  [   14   ]  small c                                    |
+| 100  64  [   145  ]  small d                                    |
+| 101  65  [   1 5  ]  small e                                    |
+| 102  66  [  214   ]  small f                                    |
+| 103  67  [  2145  ]  small g                                    |
+| 104  68  [  21 5  ]  small h                                    |
+| 105  69  [  2 4   ]  small i                                    |
+| 106  6A  [  2 45  ]  small j                                    |
+| 107  6B  [ 3 1    ]  small k                                    |
+| 108  6C  [ 321    ]  small l                                    |
+| 109  6D  [ 3 14   ]  small m                                    |
+| 110  6E  [ 3 145  ]  small n                                    |
+| 111  6F  [ 3 1 5  ]  small o                                    |
+| 112  70  [ 3214   ]  small p                                    |
+| 113  71  [ 32145  ]  small q                                    |
+| 114  72  [ 321 5  ]  small r                                    |
+| 115  73  [ 32 4   ]  small s                                    |
+| 116  74  [ 32 45  ]  small t                                    |
+| 117  75  [ 3 1  6 ]  small u                                    |
+| 118  76  [ 321  6 ]  small v                                    |
+| 119  77  [  2 456 ]  small w                                    |
+| 120  78  [ 3 14 6 ]  small x                                    |
+| 121  79  [ 3 1456 ]  small y                                    |
+| 122  7A  [ 3 1 56 ]  small z                                    |
+| 123  7B  [  2 4 6 ]  left brace                                 |
+| 124  7C  [  21 56 ]  vertical bar                               |
+| 125  7D  [  21456 ]  right brace                                |
+| 126  7E  [    45  ]  tilde accent                               |
+| 127  7F  [7   456 ]  DEL (delete)                               |
+| 128  80  [    4  8]  <control>                                  |
+| 129  81  [   1   8]  <control>                                  |
+| 130  82  [  21   8]  BPH (break permitted here)                 |
+| 131  83  [   14  8]  NBH (no break here)                        |
+| 132  84  [   145 8]  <control>                                  |
+| 133  85  [   1 5 8]  NL (next line)                             |
+| 134  86  [  214  8]  SSA (start of selected area)               |
+| 135  87  [  2145 8]  ESA (end of selected area)                 |
+| 136  88  [  21 5 8]  CTS (character tabulation set)             |
+| 137  89  [  2 4  8]  CTJ (character tabulation justification)   |
+| 138  8A  [  2 45 8]  LTS (line tabulation set)                  |
+| 139  8B  [ 3 1   8]  PLD (partial line down)                    |
+| 140  8C  [ 321   8]  PLU (partial line up)                      |
+| 141  8D  [ 3 14  8]  RLF (reverse line feed)                    |
+| 142  8E  [ 3 145 8]  SS2 (single shift two)                     |
+| 143  8F  [ 3 1 5 8]  SS3 (single shift three)                   |
+| 144  90  [ 3214  8]  DCS (device control string)                |
+| 145  91  [ 32145 8]  PU1 (private use one)                      |
+| 146  92  [ 321 5 8]  PU2 (private use two)                      |
+| 147  93  [ 32 4  8]  STS (set transmit state)                   |
+| 148  94  [ 32 45 8]  CC (cancel character)                      |
+| 149  95  [ 3 1  68]  MW (message waiting)                       |
+| 150  96  [ 321  68]  SGA (start of guarded area)                |
+| 151  97  [  2 4568]  EGA (end of guarded area)                  |
+| 152  98  [ 3 14 68]  SS (start of string)                       |
+| 153  99  [ 3 14568]  <control>                                  |
+| 154  9A  [ 3 1 568]  SCI (single character introducer)          |
+| 155  9B  [  2 4 68]  CSI (control sequence introducer)          |
+| 156  9C  [  21 568]  ST (string terminator)                     |
+| 157  9D  [  214568]  OSC (operating system command)             |
+| 158  9E  [    45 8]  PM (privacy message)                       |
+| 159  9F  [    4568]  APC (application program command)          |
+| 160  A0  [7      8]  no-break space                             |
+| 161  A1  [732 4 6 ]  inverted exclamation mark                  |
+| 162  A2  [7 214 6 ]  cent sign                                  |
+| 163  A3  [73  456 ]  pound sign                                 |
+| 164  A4  [7  14 6 ]  currency sign                              |
+| 165  A5  [73214 6 ]  yen sign                                   |
+| 166  A6  [7  1 56 ]  broken bar                                 |
+| 167  A7  [73   5  ]  section sign                               |
+| 168  A8  [7    5  ]  diaeresis                                  |
+| 169  A9  [732  56 ]  copyright sign                             |
+| 170  AA  [       8]  feminine ordinal indicator                 |
+| 171  AB  [7 21  6 ]  left-pointing double angle quotation mark  |
+| 172  AC  [7 2  56 ]  not sign                                   |
+| 173  AD  [73    6 ]  soft hyphen                                |
+| 174  AE  [732   6 ]  registered sign                            |
+| 175  AF  [7 2   6 ]  macron                                     |
+| 176  B0  [73   56 ]  degree sign                                |
+| 177  B1  [73  4 6 ]  plus-minus sign                            |
+| 178  B2  [732     ]  superscript two                            |
+| 179  B3  [7 2  5  ]  superscript three                          |
+| 180  B4  [73      ]  acute accent                               |
+| 181  B5  [7    56 ]  micro sign                                 |
+| 182  B6  [732  5  ]  pilcrow sign                               |
+| 183  B7  [7   4 6 ]  middle dot                                 |
+| 184  B8  [7     6 ]  cedilla                                    |
+| 185  B9  [7 2     ]  superscript one                            |
+| 186  BA  [7       ]  masculine ordinal indicator                |
+| 187  BB  [73  45  ]  right-pointing double angle quotation mark |
+| 188  BC  [7321 56 ]  vulgar fraction one quarter                |
+| 189  BD  [7321456 ]  vulgar fraction one half                   |
+| 190  BE  [732 456 ]  vulgar fraction three quarters             |
+| 191  BF  [7  1456 ]  inverted question mark                     |
+| 192  C0  [732  5 8]  capital a grave                            |
+| 193  C1  [7  1  68]  capital a acute                            |
+| 194  C2  [7 2    8]  capital a circumflex                       |
+| 195  C3  [7    5 8]  capital a tilde                            |
+| 196  C4  [73214 68]  capital a diaeresis                        |
+| 197  C5  [73  45 8]  capital a ring above                       |
+| 198  C6  [73     8]  capital ae                                 |
+| 199  C7  [73  4 68]  capital c cedilla                          |
+| 200  C8  [732  568]  capital e grave                            |
+| 201  C9  [7 21  68]  capital e acute                            |
+| 202  CA  [732    8]  capital e circumflex                       |
+| 203  CB  [73214568]  capital e diaeresis                        |
+| 204  CC  [732   68]  capital i grave                            |
+| 205  CD  [7  14 68]  capital i acute                            |
+| 206  CE  [7 2  5 8]  capital i circumflex                       |
+| 207  CF  [7321 568]  capital i diaeresis                        |
+| 208  D0  [7     68]  capital eth                                |
+| 209  D1  [7   4 68]  capital n tilde                            |
+| 210  D2  [73   5 8]  capital o grave                            |
+| 211  D3  [7  14568]  capital o acute                            |
+| 212  D4  [7 2  568]  capital o circumflex                       |
+| 213  D5  [7    568]  capital o tilde                            |
+| 214  D6  [732 4 68]  capital o diaeresis                        |
+| 215  D7  [7  1  6 ]  multiplication sign                        |
+| 216  D8  [73  4  8]  capital o stroke                           |
+| 217  D9  [73   568]  capital u grave                            |
+| 218  DA  [7  1 568]  capital u acute                            |
+| 219  DB  [7 2   68]  capital u circumflex                       |
+| 220  DC  [732 4568]  capital u diaeresis                        |
+| 221  DD  [7 214 68]  capital y acute                            |
+| 222  DE  [73    68]  capital thorn                              |
+| 223  DF  [73  4568]  small sharp s                              |
+| 224  E0  [ 32  5 8]  small a grave                              |
+| 225  E1  [   1  68]  small a acute                              |
+| 226  E2  [  2    8]  small a circumflex                         |
+| 227  E3  [     5 8]  small a tilde                              |
+| 228  E4  [ 3214 68]  small a diaeresis                          |
+| 229  E5  [ 3  45 8]  small a ring above                         |
+| 230  E6  [ 3     8]  small ae                                   |
+| 231  E7  [ 3  4 68]  small c cedilla                            |
+| 232  E8  [ 32  568]  small e grave                              |
+| 233  E9  [  21  68]  small e acute                              |
+| 234  EA  [ 32    8]  small e circumflex                         |
+| 235  EB  [ 3214568]  small e diaeresis                          |
+| 236  EC  [ 32   68]  small i grave                              |
+| 237  ED  [   14 68]  small i acute                              |
+| 238  EE  [  2  5 8]  small i circumflex                         |
+| 239  EF  [ 321 568]  small i diaeresis                          |
+| 240  F0  [      68]  small eth                                  |
+| 241  F1  [    4 68]  small n tilde                              |
+| 242  F2  [ 3   5 8]  small o grave                              |
+| 243  F3  [   14568]  small o acute                              |
+| 244  F4  [  2  568]  small o circumflex                         |
+| 245  F5  [     568]  small o tilde                              |
+| 246  F6  [ 32 4 68]  small o diaeresis                          |
+| 247  F7  [73  4   ]  division sign                              |
+| 248  F8  [ 3  4  8]  small o stroke                             |
+| 249  F9  [ 3   568]  small u grave                              |
+| 250  FA  [   1 568]  small u acute                              |
+| 251  FB  [  2   68]  small u circumflex                         |
+| 252  FC  [ 32 4568]  small u diaeresis                          |
+| 253  FD  [  214 68]  small y acute                              |
+| 254  FE  [ 3    68]  small thorn                                |
+| 255  FF  [ 3  4568]  small y diaeresis                          |
++-----------------------------------------------------------------+
+</verb></tscreen>
+
+<sect>MIDI Instrument Table<label id="midi"><p>
+<table loc="h">
+  <tabular ca="lrl">
+    <hline>Piano
+      |0|Acoustic Grand Piano@
+      |1|Bright Acoustic Piano@
+      |2|Electric Grand Piano@
+      |3|Honkytonk Piano@
+      |4|Electric Piano 1@
+      |5|Electric Piano 2@
+      |6|Harpsichord@
+      |7|Clavi@
+    <hline>Chromatic Percussion
+      |8|Celesta@
+      |9|Glockenspiel@
+      |10|Music Box@
+      |11|Vibraphone@
+      |12|Marimba@
+      |13|Xylophone@
+      |14|Tubular Bells@
+      |15|Dulcimer@
+    <hline>Organ
+      |16|Drawbar Organ@
+      |17|Percussive Organ@
+      |18|Rock Organ@
+      |19|Church Organ@
+      |20|Reed Organ@
+      |21|Accordion@
+      |22|Harmonica@
+      |23|Tango Accordion@
+    <hline>Guitar
+      |24|Nylon Acoustic Guitar@
+      |25|Steel Acoustic Guitar@
+      |26|Jazz Electric Guitar@
+      |27|Clean Electric Guitar@
+      |28|Muted Electric Guitar@
+      |29|Overdriven Guitar@
+      |30|Distortion Guitar@
+      |31|Guitar Harmonics@
+    <hline>Bass
+      |32|Acoustic Bass@
+      |33|Finger Electric Bass@
+      |34|Pick Electric Bass@
+      |35|Fretless Bass@
+      |36|Slap Bass 1@
+      |37|Slap Bass 2@
+      |38|Synth Bass 1@
+      |39|Synth Bass 2@
+    <hline>Strings
+      |40|Violin@
+      |41|Viola@
+      |42|Cello@
+      |43|Contrabass@
+      |44|Tremolo Strings@
+      |45|Pizzicato Strings@
+      |46|Orchestral Harp@
+      |47|Timpani@
+    <hline>Ensemble
+      |48|String Ensemble 1@
+      |49|String Ensemble 2@
+      |50|Synth Strings 1@
+      |51|Synth Strings 2@
+      |52|Choir Aahs@
+      |53|Voice Oohs@
+      |54|Synth Voice@
+      |55|Orchestra Hit@
+    <hline>Brass
+      |56|Trumpet@
+      |57|Trombone@
+      |58|Tuba@
+      |59|Muted Trumpet@
+      |60|French Horn@
+      |61|Brass Section@
+      |62|Synth Brass 1@
+      |63|Synth Brass 2@
+    <hline>Reed
+      |64|Soprano Saxophone@
+      |65|Alto Saxophone@
+      |66|Tenor Saxophone@
+      |67|Baritone Saxophone@
+      |68|Oboe@
+      |69|English Horn@
+      |70|Bassoon@
+      |71|Clarinet@
+    <hline>Pipe
+      |72|Piccolo@
+      |73|Flute@
+      |74|Recorder@
+      |75|Pan Flute@
+      |76|Blown Bottle@
+      |77|Shakuhachi@
+      |78|Whistle@
+      |79|Ocarina@
+    <hline>Synth Lead
+      |80|Lead 1 (square)@
+      |81|Lead 2 (sawtooth)@
+      |82|Lead 3 (calliope)@
+      |83|Lead 4 (chiff)@
+      |84|Lead 5 (charang)@
+      |85|Lead 6 (voice)@
+      |86|Lead 7 (fifths)@
+      |87|Lead 8 (bass + lead)@
+    <hline>Synth Pad
+      |88|Pad 1 (new age)@
+      |89|Pad 2 (warm)@
+      |90|Pad 3 (polysynth)@
+      |91|Pad 4 (choir)@
+      |92|Pad 5 (bowed)@
+      |93|Pad 6 (metallic)@
+      |94|Pad 7 (halo)@
+      |95|Pad 8 (sweep)@
+    <hline>Synth FM
+      |96|FX 1 (rain)@
+      |97|FX 2 (soundtrack)@
+      |98|FX 3 (crystal)@
+      |99|FX 4 (atmosphere)@
+      |100|FX 5 (brightness)@
+      |101|FX 6 (goblins)@
+      |102|FX 7 (echoes)@
+      |103|FX 8 (science-fiction)@
+    <hline>Ethnic
+      |104|Sitar@
+      |105|Banjo@
+      |106|Shamisen@
+      |107|Koto@
+      |108|Kalimba@
+      |109|Bag Pipe@
+      |110|Fiddle@
+      |111|Shanai@
+    <hline>Percussive
+      |112|Tinkle Bell@
+      |113|Agogo@
+      |114|Steel Drum@
+      |115|Wooden Block@
+      |116|Taiko Drum@
+      |117|Melodic Tom@
+      |118|Synth Drum@
+      |119|Reverse Cymbal@
+    <hline>Sound Effects
+      |120|Guitar Fret Noise@
+      |121|Breath Noise@
+      |122|Seashore@
+      |123|Bird Tweet@
+      |124|Telephone Ring@
+      |125|Helicopter@
+      |126|Applause@
+      |127|Gunshot@
+  </tabular>
+</table>
+
+</article>
diff --git a/Documents/Manual-BRLTTY/English/Makefile.in b/Documents/Manual-BRLTTY/English/Makefile.in
new file mode 100644
index 0000000..4f11575
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/Makefile.in
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DOCUMENT_NAME = BRLTTY
+DOCUMENT_LANGUAGE = english
+include $(SRC_TOP)document.mk
diff --git a/Documents/Manual-BRLTTY/English/braille-drivers.sgml b/Documents/Manual-BRLTTY/English/braille-drivers.sgml
new file mode 100644
index 0000000..9daafb2
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/braille-drivers.sgml
@@ -0,0 +1,155 @@
+Albatross
+  |46/80@
+Alva
+  |ABT (3nn)@
+  |Delphi (4nn)@
+  |Satellite (5nn)@
+  |Braille System 40@
+  |Braille Controller 640/680@
+  |Easy Link 12@
+Baum
+  |BrailleConnect 12/24/32/40/64/80@
+  |Brailliant 24/32/40/64/80@
+  |Conny 12@
+  |DM80 Plus@
+  |EcoVario 24/32/40/64/80@
+  |Inka@
+  |NLS eReader Zoomax@
+  |Orbit Reader 20/40@
+  |PocketVario 24@
+  |Pronto! V3 18/40@
+  |Pronto! V4 18/40@
+  |RBT 40/80@
+  |Refreshabraille 18@
+  |SuperVario 32/40/64/80@
+  |Vario 40/80@
+  |VarioConnect 12/24/32/40/64/80@
+  |VarioPro 40/64/80@
+  |VarioUltra 20/32/40@
+BrailComm
+  |III@
+BrailleLite
+  |18/40/M20/M40@
+BrailleMemo
+  |Pocket (16)@
+  |Smart (16)@
+  |32@
+  |40@
+BrailleNote
+  |18/32@
+  |Apex@
+Braudi@
+BrlAPI@
+Canute
+  |360 (40x9)@
+Cebra
+  |20/40/60/80/100/120/140@
+CombiBraille
+  |25/45/85@
+DotPad@
+EcoBraille
+  |20/40/80@
+EuroBraille
+  |AzerBraille@
+  |Clio@
+  |Esys@
+  |Iris@
+  |NoteBraille@
+  |Scriba@
+FrankAudiodata
+  |B2K84@
+FreedomScientific
+  |Focus 1 44/70/84@
+  |Focus 2 40/80@
+  |Focus Blue 14/40/80@
+  |PAC Mate 20/40@
+HandyTech
+  |Modular 20/40/80@
+  |Modular Evolution 64/88@
+  |Modular Connect 88@
+  |Active Braille@
+  |Active Braille S@
+  |Active Star 40@
+  |Actilino@
+  |Activator@
+  |Basic Braille 16/20/32/40/48/64/80@
+  |Braillino@
+  |Braille Wave@
+  |Easy Braille@
+  |Braille Star 40/80@
+  |Connect Braille 40@
+  |Bookworm@
+Hedo
+  |ProfiLine@
+  |MobilLine@
+HIMS
+  |Braille Sense@
+  |SyncBraille@
+  |Braille Edge@
+  |Smart Beetle@
+  |QBrailleXL@
+HumanWare
+  |Brailliant BI 14/32/40@
+  |Brailliant BI 20X/40X@
+  |Brailliant B 80@
+  |BrailleNote Touch@
+  |BrailleOne@
+  |APH Chameleon 20@
+  |APH Mantis Q40@
+  |NLS eReader@
+Inceptor
+  |BrailleMe (20)@
+Iris@
+Libbraille@
+LogText
+  |32@
+MDV
+  |MB208@
+  |MB248@
+  |MB408L@
+  |MB408L+@
+  |Lilli Blu@
+Metec
+  |BD-40@
+MiniBraille
+  |20@
+MultiBraille
+  |MB125CR@
+  |MB145CR@
+  |MB185CR@
+NinePoint
+  |8@
+Papenmeier
+  |Compact 486@
+  |Compact/Tiny@
+  |IB 80 CR Soft@
+  |2D Lite (plus)@
+  |2D Screen Soft@
+  |EL 80@
+  |EL 2D 40/66/80@
+  |EL 40/66/70/80 S@
+  |EL 40/60/80 C@
+  |EL 2D 80 S@
+  |EL 40 P@
+  |EL 80 II@
+  |Elba 20/32@
+  |Trio 40/Elba20/Elba32@
+  |Live 20/40@
+Pegasus
+  |20/27/40/80@
+Seika
+  |3/4/5 (40)@
+  |80@
+  |Mini (16)@
+TechniBraille@
+TSI
+  |Navigator 20/40/80@
+  |PowerBraille 40/65/80@
+VideoBraille
+  |40@
+VisioBraille
+  |20/40@
+Voyager
+  |44/70@
+  |Part232 (serial adapter)@
+  |BraillePen/EasyLink@
diff --git a/Documents/Manual-BRLTTY/English/contraction-tables.sgml b/Documents/Manual-BRLTTY/English/contraction-tables.sgml
new file mode 100644
index 0000000..82a22bc
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/contraction-tables.sgml
@@ -0,0 +1,38 @@
+auto|locale-based autoselection@
+af|Afrikaans (contracted)@
+am|Amharic (uncontracted)@
+de|German@
+de-g0|German (uncontracted)@
+de-g1|German (basic contractions)@
+de-g2|German (contracted)@
+de-1998|German (contracted - 1998 standard)@
+de-2015|German (contracted - 2015 standard)@
+en|English@
+en&lowbar;US|English (United States)@
+en-ueb-g1|English (Unified English Braille, grade 1)@
+en-ueb-g2|English (Unified English Braille, grade 2)@
+en-us-g2|English (US, grade 2)@
+es|Spanish (grade 2)@
+fr|French@
+fr-g1|French (uncontracted)@
+fr-g2|French (contracted)@
+ha|Hausa (contracted)@
+id|Indonesian (contracted)@
+ipa|International Phonetic Alphabet@
+ja|Japanese (uncontracted)@
+ko|Korean@
+ko-g0|Korean (uncontracted)@
+ko-g1|Korean (grade 1)@
+ko-g2|Korean (grade 2)@
+lt|Lituanian@
+mg|Malagasy (contracted)@
+mun|Munda (contracted)@
+nl|Dutch (contracted)@
+ny|Chichewa (contracted)@
+pt|Portuguese (grade 2)@
+ru|Russian (grade 1)@
+si|Sinhalese (uncontracted)@
+sw|Swahili (contracted)@
+th|Thai (contracted)@
+zh&lowbar;TW|Chinese (Taiwan, uncontracted)@
+zu|Zulu (contracted)@
diff --git a/Documents/Manual-BRLTTY/English/driver-codes.sgml b/Documents/Manual-BRLTTY/English/driver-codes.sgml
new file mode 100644
index 0000000..0e84ac5
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/driver-codes.sgml
@@ -0,0 +1,55 @@
+al|Alva@
+an|Android@
+at|Albatross@
+ba|BrlAPI@
+bc|BrailComm@
+bd|Braudi@
+bg|B2G@
+bl|BrailleLite@
+bm|Baum (Native, HT, PB1, PB2)@
+bn|BrailleNote@
+cb|CombiBraille@
+ce|Cebra@
+cn|Canute@
+dp|DotPad@
+ec|EcoBraille@
+en|eSpeak-NG@
+es|eSpeak@
+eu|EuroBraille@
+fa|FrankAudiodata@
+fl|FestivalLite@
+fs|FreedomScientific@
+fv|Festival@
+gs|GenericSay@
+hd|Hedo@
+hm|HIMS@
+ht|HandyTech@
+hw|HumanWare@
+ic|Inceptor@
+ir|Iris@
+lb|Libbraille@
+lt|LogText@
+mb|MultiBraille@
+md|MDV@
+mm|BrailleMemo@
+mn|MiniBraille@
+mp|Mikropuhe@
+mt|Metec@
+no|no driver@
+np|NinePoint@
+pg|Pegasus@
+pm|Papenmeier@
+sd|SpeechDispatcher@
+sk|Seika@
+sw|Swift@
+th|Theta@
+tn|TechniBraille@
+ts|Telesensory Systems Inc.@
+tt|TTY@
+vd|VideoBraille@
+vo|Voyager@
+vr|Virtual@
+vs|VisioBraille@
+vv|ViaVoice@
+xs|ExternalSpeech@
+xw|XWindow@
diff --git a/Documents/Manual-BRLTTY/English/speech-drivers.sgml b/Documents/Manual-BRLTTY/English/speech-drivers.sgml
new file mode 100644
index 0000000..66c66e8
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/speech-drivers.sgml
@@ -0,0 +1,28 @@
+Alva
+  |Delphi (4nn)@
+Android
+  |text to speech engine@
+BrailleLite
+  |@
+CombiBraille
+  |@
+eSpeak
+  |text to speech engine@
+eSpeak-NG
+  |text to speech engine@
+ExternalSpeech
+  |runs /usr/local/bin/externalspeech@
+Festival
+  |text to speech engine@
+FestivalLite
+  |text to speech engine@
+GenericSay
+  |pipes to /usr/local/bin/say@
+Mikropuhe
+  |text to speech engine@
+Swift
+  |text to speech engine@
+Theta
+  |text to speech engine@
+ViaVoice
+  |text to speech engine@
diff --git a/Documents/Manual-BRLTTY/English/text-tables.sgml b/Documents/Manual-BRLTTY/English/text-tables.sgml
new file mode 100644
index 0000000..bc9e375
--- /dev/null
+++ b/Documents/Manual-BRLTTY/English/text-tables.sgml
@@ -0,0 +1,90 @@
+auto|locale-based autoselection@
+ar|Arabic (generic)@
+as|Assamese@
+awa|Awadhi@
+bg|Bulgarian@
+bh|Bihari@
+bn|Bengali@
+bo|Tibetan@
+bra|Braj@
+brf|Braille Ready Format@
+|(for viewing .brf files within an editor or pager)@
+cs|Czech@
+cy|Welsh@
+da|Danish@
+da-1252|Danish (Svend Thougaard, 2002-11-18)@
+da-lt|Danish (LogText)@
+de|German@
+dra|Dravidian@
+el|Greek@
+en|English@
+en&lowbar;CA|English (Canada)@
+en&lowbar;GB|English (United Kingdom)@
+en&lowbar;US|English (United States)@
+en-nabcc|English (North American Braille Computer Code)@
+eo|Esperanto@
+es|Spanish@
+et|Estonian@
+fi|Finnish@
+fr|French@
+fr&lowbar;CA|French (Canada)@
+fr&lowbar;FR|French (France)@
+fr-2007|French (unified 2007)@
+fr-cbifs|French (Code Braille Informatique Français Standard)@
+fr-vs|French (VisioBraille)@
+ga|Irish@
+gd|Gaelic@
+gon|Gondi@
+gu|Gujarati@
+he|Hebrew@
+hi|Hindi@
+hr|Croatian@
+hu|Hungarian@
+hy|Armenian@
+is|Icelandic@
+it|Italian@
+kha|Khasi@
+kn|Kannada@
+kok|Konkani@
+kru|Kurukh@
+lt|Lituanian@
+lv|Latvian@
+mg|Malagasy@
+mi|Maori@
+ml|Malayalam@
+mni|Manipuri@
+mr|Marathi@
+mt|Maltese@
+mun|Munda@
+mwr|Marwari@
+ne|Nepali@
+new|Newari@
+nl|Dutch@
+nl&lowbar;BE|Dutch (Belgium)@
+nl&lowbar;NL|Dutch (Netherlands)@
+no|Norwegian@
+no-generic|Norwegian (with support for other languages)@
+no-oup|Norwegian (Offentlig utvalg for punktskrift)@
+nwc|Newari (old)@
+or|Oriya@
+pa|Panjabi@
+pi|Pali@
+pl|Polish@
+pt|Portuguese@
+ro|Romanian@
+ru|Russian@
+sa|Sanskrit@
+sat|Santali@
+sd|Sindhi@
+se|Sami (Northern)@
+sk|Slovak@
+sl|Slovenian@
+sv|Swedish@
+sv-1989|Swedish (1989 standard)@
+sv-1996|Swedish (1996 standard)@
+sw|Swahili@
+ta|Tamil@
+te|Telugu@
+tr|Turkish@
+uk|Ukrainian@
+vi|Vietnamese@
diff --git a/Documents/Manual-BRLTTY/French/Advanced.sgml b/Documents/Manual-BRLTTY/French/Advanced.sgml
new file mode 100644
index 0000000..bf6b250
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Advanced.sgml
@@ -0,0 +1,135 @@
+<sect>Sujets avancés<p>
+
+<sect1>Installation de plusieurs versions<label id="multiple"><p>
+Il est facile d'avoir plus d'une version de BRLTTY installée sur  le même
+système en même temps. Cette possibilité vous permet de tester une nouvelle
+version avant de supprimer l'ancienne.
+
+L'option de compilation <ref id="build-execute-root" name="--with-execute-root">
+vous permet d'installer la <ref id="hierarchy" name="hiérarchie des fichiers installés"> complète là où
+vous le voulez de sorte qu'elle soit totalement autonome dans son contenu.
+Tout en vous rappelant qu'il vaut mieux prendre tous les composants de
+BRLTTY dans le système de fichier racine, vous pouvez le compiler ainsi:
+<tscreen><verb>
+./configure --with-execute-root=/brltty-3.1
+make install
+</verb></tscreen>
+Vous pouvez alors l'exécuter ainsi:
+<tscreen>/brltty-3.1/bin/brltty</tscreen>
+Quand vous avez fait la version 3.2, installez-la simplement dans un
+emplacement différent et exécutez le nouvel exécutable à partir de là.
+<tscreen><verb>
+./configure --with-execute-root=/brltty-3.2
+make install
+/brltty-3.2/bin/brltty
+</verb></tscreen>
+
+Jusque-là, tout cela est quelque peu maladroit pour au moins deux raisons.
+La première est que ces longs chemins sont trop difficiles à taper, et l'autre
+est que vous ne voulez pas bricoler avec
+la séquence de démarrage de votre système chaque fois que vous souhaitez aller
+à une version différente de BRLTTY.
+Ces problèmes se résolvent facilement en ajoutant un lien symbolique pour
+l'exécutable.
+<tscreen>ln -s /brltty-3.1/bin/brltty /bin/brltty</tscreen>
+Quand il est temps d'utiliser la version la plus récente, pointez de nouveau
+simplement le lien symbolique.
+<tscreen>ln -s /brltty-3.2/bin/brltty /bin/brltty</tscreen>
+
+Si vous aimez vraiment faire de la fantaisie, introduisez un autre niveau
+de redirection de façon à rendre tous les fichiers de chaque version de BRLTTY
+tels qu'ils sont dans tous les emplacements standards.
+D'abord, créez un lien symbolique à travers un emplacement où on peut
+repointer aisément à partir de chacun des emplacements standards de BRLTTY.
+<tscreen><verb>
+ln -s /brltty/bin/brltty /bin/brltty
+ln -s /brltty/etc/brltty /etc/brltty
+ln -s /brltty/lib/brltty /lib/brltty
+</verb></tscreen>
+Ensuite, tout ce que vous devez faire est de pointer <tt>/brltty</tt> vers la
+version désirée.
+<tscreen>ln -s /brltty-3.1 /brltty</tscreen>
+
+<sect1>Disques racines d'Installation/Secours pour Linux<p>
+BRLTTY peut s'exécuter comme un seul exécutable. Tout ce qu'il a besoin de
+savoir peut être configuré explicitement lors de la compilation (voir
+<ref id="build" name="Options de compilation">).
+Si le répertoire de données (voir les options de compilation
+<ref id="build-data-directory" name="--with-data-directory"> et
+<ref id="build-execute-root" name="--with-execute-root">) n'existe pas, BRLTTY
+cherche dans <tt>/etc</tt> les fichiers dont il a besoin.
+Même si un de ces fichiers n'existe pas, BRLTTY fonctionne encore!
+
+Si, pour une raison quelconque, vous avez déjà créé le répertoire de données
+(habituellement, <tt>/etc/brltty</tt>) à la main, il est important de régler
+ses permissions de telle sorte que seul le super-utilisateur puisse y créer des
+fichiers.
+<tscreen>chmod 755 /etc/brltty</tscreen>
+
+Le périphérique d'inspection du contenu de l'écran (habituellement
+<tt>/dev/vcsa</tt>) est nécessaire. Il devrait déjà exister, à moins que votre
+distribution de Linux ne soit trop vieille. Si nécessaire, vous pouvez le créer
+avec:
+<tscreen><verb>
+mknod /dev/vcsa c 7 128
+chmod 660 /dev/vcsa
+chown root.tty /dev/vcsa
+</verb></tscreen>
+
+Un problème souvent rencontré lorsqu'on essaie d'utiliser BRLTTY dans un
+environnement instable, comme un disque racine ou un système incomplet, est
+qu'il pourrait ne pas trouver les bibliothèques partagées (ou des parties
+de celles-ci) dont il a besoin.
+Les disques racines utilisent souvent des versions des bibliothèques
+sous-paramétrées et/ou non à jour qui peuvent être inadéquates. La solution est de configurer BRLTTY avec l'optfon de
+compilation <ref id="build-standalone-programs" name="--enable-standalone-programs">.
+Cela supprime toutes les dépendances des bibliothèques partagées, mais
+malheureusement, crée aussi un exécutable plus gros.
+Il y a un certain nombre d'options de compilation que vous pouvez utiliser
+pour supprimer de façon sélective les possibilités de BRLTTY dont vous n'avez pas
+besoin de façon à atténuer ce problème (voir la section
+<ref id="build-features" name="possibilitéés de la compilation">).
+
+L'exécutable est nettoyé pendant le
+<ref id="make-install" name="make install">. Cela réduit de façon
+significative sa taille en supprimant sa table de symboles.
+Vous obtiendrez un exécutable beaucoup plus petit, puis copiez-le de son emplacement
+d'installation.
+Cependant, si vous le copiez depuis le répertoire de compilation, n'oubliez pas de le nettoyer.
+<tscreen>strip brltty</tscreen>
+
+<sect1>Avancées futures<p>
+Outre la réparation de bugs et le support de plus de types d'afficheurs
+brailles, nous espérons, si le temps nous le permet, travailler sur:
+<descrip>
+  <tag>Meilleure prise en charge des attributs</tag>
+    <itemize>
+      <item>Poursuite des attributs.
+      <item>Mode mixte texte et attributs.
+    </itemize>
+  <tag>Poursuite du défilement</tag>
+     Alignement de la plage braille sur une ligne alors qu'elle défile à
+     l'écran.
+  <tag>Meilleur support de voix</tag>
+    <itemize>
+      <item>Braille et voix mélangés pour une lecture plus rapide du texte.
+      <item>Meilleur navigation vocale.
+      <item>Plus de synthèses vocales.
+    </itemize>
+  <tag>Sous-régions sur l'écran</tag>
+    Ignorer le déplacement du curseur hors de la région, et rendre fluide les
+    limites de navigation aux bords de la région.
+</descrip>
+Voir le fichier <tt>TODO</tt> pour une liste plus complète.
+
+<sect1>Bogues connus<p>
+A l'heure où nous écrivons (décembre 2001), les problèmes suivants sont
+connus.
+
+La routine-curseur est considérée comme un sous-processus en boucle qui s'exécute
+avec une priorité réduite pour éviter de trop utiliser le temps du processeur.
+Les chargements de systèmes différents nécessitent des réglages différents de
+ses paramètres. Ceux par défaut fonctionnent très bien dans un éditeur Unix
+classique sur un système chargé normalement, mais très mal dans d'autres
+situations, par exemple sur une liaison série lente vers un nom de machine
+supprimé.
diff --git a/Documents/Manual-BRLTTY/French/BRLTTY.sgml b/Documents/Manual-BRLTTY/French/BRLTTY.sgml
new file mode 100644
index 0000000..62352bc
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/BRLTTY.sgml
@@ -0,0 +1,150 @@
+<!doctype linuxdoc system[
+
+<!ENTITY introduction 	SYSTEM "Introduction.sgml">
+<!ENTITY compilation 	SYSTEM "Compilation.sgml">
+<!ENTITY utilisation 	SYSTEM "Utilisation.sgml">
+<!ENTITY features 	SYSTEM "Features.sgml">
+<!ENTITY translation 	SYSTEM "Translation.sgml">
+<!ENTITY advanced 	SYSTEM "Advanced.sgml">
+<!ENTITY displays 	SYSTEM "Displays.sgml">
+<!ENTITY synthesizers 	SYSTEM "Synthesizers.sgml">
+<!ENTITY drivers 	SYSTEM "Drivers.sgml">
+<!ENTITY screen         SYSTEM "Screen.sgml">
+<!ENTITY syntaxe 	SYSTEM "Syntax.sgml">
+<!ENTITY dots 		SYSTEM "Dots.sgml">
+<!ENTITY nabcc          SYSTEM "nabcc.sgml">
+<!ENTITY fr2007          SYSTEM "fr-2007.sgml">
+<!ENTITY midi 		SYSTEM "Midi.sgml">
+
+<!ENTITY DriverCodes SYSTEM "driver-codes.sgml">
+<!ENTITY BrailleDrivers SYSTEM "braille-drivers.sgml">
+<!ENTITY SpeechDrivers SYSTEM "speech-drivers.sgml">
+<!ENTITY TextTables SYSTEM "text-tables.sgml">
+<!ENTITY ContractionTables SYSTEM "contraction-tables.sgml">
+
+]>
+<article>
+
+<titlepag>
+  <title>Manuel de référence de BRLTTY
+    <subtitle>Accès à l'écran d'une console pour les personnes non-voyantes utilisant des afficheurs braille
+  <author>
+    <name>Nikhil Nair <tt><htmlurl url="mailto:nn201@cus.cam.ac.uk" name="&lt;nn201@cus.cam.ac.uk&gt;"></tt>
+  <and>
+    <name>Nicolas Pitre <tt><htmlurl url="mailto:nico@fluxnic.net" name="&lt;nico@fluxnic.net&gt;"></tt>
+  <and>
+    <name>Stéphane Doyon <tt><htmlurl url="mailto:s.doyon@videotron.ca" name="&lt;s.doyon@videotron.ca&gt;"></tt>
+  <and>
+    <name>Dave Mielke <tt><htmlurl url="mailto:dave@mielke.cc" name="&lt;dave@mielke.cc&gt;"></tt>
+  <and>
+    <name>Traduction française
+  <and>
+    <name>
+      Jean-Philippe Mengual
+        <tt><htmlurl url="mailto:texou@accelibreinfo.eu" name="&lt;texou@accelibreinfo.eu&gt;"></tt>
+      pour
+        <tt><htmlurl url="http://www.traduc.org/" name="Traduc.org"></tt>
+  <date>Version 6.6, juillet 2023
+  <abstract>
+    Copyright &copy; 1995-2023 by Les Développeurs de BRLTTY.
+    BRLTTY est un logiciel libre, et n'est fourni avec AUCUNE
+    GARANTIE. Il est placé sous les termes de la version 2 ou
+    ultérieure de la <bf>GNU General Public License</bf> publiée par
+    <bf>The Free Software Foundation</bf>.
+  </abstract>
+</titlepag>
+
+<toc>
+<lof>
+<lot>
+
+<sect>Préambule<p>
+
+<sect1>Licence<p>
+Ce programme est un logiciel libre. Vous pouvez le redistribuer et/ou
+le modifier sous les termes de la <htmlurl url="http://www.gnu.org/licenses/licenses.html#LGPL" name="GNU Lesser General Public License">
+stipulée par la <htmlurl url="http://www.gnu.org/fsf/fsf.html" name="Free Software Foundation">. Vous pouvez utiliser une version 2,1 (ou supérieure) de la licence.
+
+Vous devriez avoir reçu une copie de la licence avec le
+programme. Elle devrait être dans le fichier <tt/LICENSE-LGPL/ du
+répertoire principal. Si ce n'est pas le cas, écrivez à la Free Software
+Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+<sect1>Mise en garde<p>
+Ce programme est distribué avec l'espoir d'être utile, mais SANS
+AUCUNE GARANTIE, même pas celle d'être COMMERCIALISABLE ou
+de CONVENIR À UNE FINALITÉ EN PARTICULIER. Voir la <bf>GNU General
+Public License</bf> pour plus de détails.
+
+<sect1>Contacts<label id="contact"><p>
+BRLTTY est le travail d'une équipe. Pour des informations actualisées,
+voyez la page Web de BRLTTY sur [<htmlurl url="http://brltty.app/" name="http://brltty.app/">]. Ont
+participé à ce document et au logiciel:
+
+<itemize>
+  <item>
+    Dave Mielke (responsable, actif)
+    <descrip>
+      <tag/Web/<htmlurl url="http://mielke.cc/" name="http://mielke.cc/">
+      <tag/E-Mail/<htmlurl url="mailto:dave@mielke.cc" name="&lt;dave@mielke.cc&gt;">
+    </descrip>
+  <item>
+    Samuel Thibault (actif)
+    <descrip>
+      <tag/Web/<htmlurl url="http://dept-info.labri.fr/~thibault/" name="http://dept-info.labri.fr/~thibault/">
+      <tag/E-Mail/<htmlurl url="mailto:samuel.thibault@ens-lyon.org" name="&lt;samuel.thibault@ens-lyon.org&gt;">
+    </descrip>
+  <item>
+    Mario Lang (actif)
+    <descrip>
+      <tag/Web/<htmlurl url="http://delysid.org/" name="http://delysid.org/">
+      <tag/E-Mail/<htmlurl url="mailto:mlang@delysid.org" name="&lt;mlang@delysid.org&gt;">
+    </descrip>
+  <item>
+    Nicolas Pitre
+    <descrip>
+      <tag/Web/<htmlurl url="http://www.fluxnic.net/" name="http://www.fluxnic.net/">
+      <tag/E-Mail/<htmlurl url="mailto:nico@fluxnic.net" name="&lt;nico@fluxnic.net&gt;">
+    </descrip>
+  <item>
+    Stéphane Doyon
+    <descrip>
+      <tag/Web/<htmlurl url="http://pages.infinit.net/sdoyon/" name="http://pages.infinit.net/sdoyon/">
+      <tag/E-Mail/<htmlurl url="mailto:s.doyon@videotron.ca" name="&lt;s.doyon@videotron.ca&gt;">
+    </descrip>
+  <item>
+    Nikhil Nair  (auteur)
+    <descrip>
+      <tag/E-Mail/<htmlurl url="mailto:nn201@cus.cam.ac.uk" name="&lt;nn201@cus.cam.ac.uk&gt;">
+    </descrip>
+
+</itemize>
+
+Toute question, tout commentaire, toute suggestions, critiques et contributions 
+sont les bienvenus. Même si nos adresses de messagerie sont listées ci-dessus,
+le meilleur moyen de nous contacter est la liste de diffusion BRLTTY. Vous
+pouvez poster sur la liste en envoyant votre message à
+<htmlurl url="mailto:BRLTTY@mielke.cc" name="&lt;BRLTTY@mielke.cc&gt;">. Si
+vous n'êtes pas abonné à la liste, vos messages seront soumis à l'approbation
+du modérateur. Pour vous abonner, vous désabonner, modifier les paramètres de
+voir les archives, etc, rendez-vous sur la page d'information de la liste, sur
+<htmlurl url="http://mielke.cc/mailman/listinfo/brltty" name="http://mielke.cc/mailman/listinfo/brltty">.
+
+&introduction
+&compilation
+&utilisation
+&features
+&translation
+&advanced
+
+&displays
+&synthesizers
+&drivers
+&screen
+&syntaxe
+&dots
+&nabcc
+&fr2007
+&midi
+
+</article>
diff --git a/Documents/Manual-BRLTTY/French/Compilation.sgml b/Documents/Manual-BRLTTY/French/Compilation.sgml
new file mode 100644
index 0000000..7e6f467
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Compilation.sgml
@@ -0,0 +1,1648 @@
+<sect>La procédure de compilation<p>
+On peut télécharger BRLTTY depuis son site Web (voir la section <ref
+id="contact" name="Contacts"> pour son adresse).  Toutes les versions
+sont fournies en <ref id="tar" name="archives tar"> compressés. Les
+versions récentes sont aussi fournies en fichiers <ref id="rpm"
+name="RPM"> (RedHat paquet Manager).
+
+Ces information éparses ont probablement piqué votre
+curiosité et vous êtes impatient de démarrer.
+Cependant, nous vous recommandons de vous familiariser d'abord
+avec les fichiers qui seront finalement installés.
+
+<sect1>Hiérarchie des fichiers installés<label id="hierachy"><p>
+La procédure de compilation devrait aboutir à l'installation des
+fichiers suivants:
+<descrip>
+  <tag>/bin/</tag>
+    <descrip>
+      <tag>brltty</tag>
+        Le programme BRLTTY.
+      <tag><ref id="utility-brltty-install" name="brltty-install"></tag>
+        Un outil pour la copie de la <ref id="hierarchy"
+        name="hiérarchie des fichiers installés"> de BRLTTY d'un emplacement à
+        un autre...
+      <tag><ref id="utility-brltty-config" name="brltty-config"></tag>
+        Un outil qui assigne un certain nombre de variables
+        d'environnement à des valeurs reflétant l'installation
+        courante de BRLTTY.
+    </descrip>
+  <tag>/lib/brltty/rw/</tag>
+    Fichiers créés lors de l'exécution, comme ceux nécessaires mais absents
+    du système.
+  <tag>/etc/</tag>
+    <descrip>
+      <tag>brltty.conf</tag>
+        Paramètres système par défaut pour BRLTTY.
+      <tag>brlapi.key</tag>
+        Clés d'accès pour BrlAPI.
+    </descrip>
+  <tag>/etc/brltty/</tag>
+    Il se peut que votre installation de BRLTTY n'ait pas tous les
+    types de fichiers suivants. Ils ne sont créés qu'en fonction des
+    besoins déduits des options de compilation que vous sélectionnez
+    (voir <ref id="build" name="Options de compilation">).
+    <descrip>
+      <tag>*.conf</tag>
+        Base de configuration spécifique au pilote. Leur nom ressemble
+        plus ou moins à <tt>brltty-</tt><em>pilote</em><tt>.conf</tt>, où
+        <em>pilote</em> correspond aux deux lettres du <ref id="drivers"
+        name="code d'identification du pilote">.
+      <tag/*.atb/
+        Tables d'attributs
+        (voir la section <ref id="table-attributes" name="Tables d'attributs"> pour plus de détails).
+        Leur nom ressemble à <em/name/<tt/.atb/.
+      <tag/*.ati/
+        Fichiers include pour les tables d'attributs.
+
+      <tag>*.ctb</tag>
+        Tables de braille abrégé (voir <ref id=table-contraction name="Tables
+        de braille abrégé"> pour plus de détails).  Leur nom ressemble plus ou
+        moins à
+        <em>langue</em><tt>-</tt><em>pays</em><tt>-</tt><em>niveau</em><tt>.ctb</tt>.
+      <tag>*.cti</tag>
+        Fichiers include pour les tables de traduction.
+      <tag/*.ktb/
+        Tables de touches
+        (voir la section <ref id="table-key" name="Tables de touches"> pour plus de détails).
+        Leurs noms ressemble à <em/name/<tt/.ktb/.
+      <tag/*.kti/
+        Fichiers Include pour les tables de touches.
+      <tag/*.ttb/
+        Tables de texte
+        (voir la section <ref id="table-text" name="Tables de texte"> pour plus de détails).
+        Leurs noms ressemble à <em/language/<tt/.ttb/.
+      <tag/*.tti/
+        Fichiers Include pour les tables de texte.
+      <tag>*.hlp</tag>
+        Pages d'aide spécifiques à chaque pilote. Leur nom ressemble
+        plus ou moins à <tt>brltty-</tt><em>pilote</em><tt>.hlp</tt>, où
+        <em>pilote</em> correspond aux deux lettres du <ref id="drivers"
+        name="code d'identification du pilote">.
+    </descrip>
+  <tag>/var/lib/</tag>
+    Sockets locaux pour la connexion à l'interface de programmation de
+    l'application.
+  <tag>/include/</tag>
+    Les fichiers d'en-tête C pour l'interface de programmation de
+    l'application. Leurs noms ressemblent à <em>brlapi-fonction</em><tt>.h</tt>.
+    L'en-tête principale est brlapi.h.
+  <tag>/include/brltty/</tag>
+    Les fichiers d'en-tête C pour l'interface de programmation de
+    l'application. Leurs noms ressemblent à <em>brldefs-pilote</em><tt>.h</tt> où
+    <em>pilote</em><tt>-</tt><em>pilote</em><tt>.h</tt> (où <em>pilote</em> correspond
+    aux deux lettres du <ref id="drivers" name="Code d'identification
+     du pilote">). Les en-têtes brldefs.h et api.h sont fournies pour une
+     compatibilité avec des version précédentes et ne devraient pas être
+     utilisées.
+     
+  <tag>/lib/</tag>
+    <descrip>
+      <tag>libbrlapi.a</tag>
+        bibliothèque statique de l'interface de programmation de
+        l'application (Application Programming Interface).
+      <tag>libbrlapi.so</tag>
+        bibliothèque dynamique pour l'interface de programmation de
+        l'application.
+    </descrip>
+  <tag>/lib/brltty/</tag>
+    Il se peut que votre installation de BRLTTY n'ait pas tous les
+    types de fichiers suivants. Ils ne sont créés qu'en fonction des
+    besoins déduits des options de compilation que vous sélectionnez
+    (voir <ref id="build" name="Options de compilation">).
+    <descrip>
+      <tag>brltty-brl.lst</tag>
+        Une liste des pilotes des afficheurs braille qui ont été
+        compilés en tant qu'objets dynamiques et, ainsi, qui peuvent
+        être utilisés. Chaque ligne contient les deux lettres
+        du code d'identification pour un pilote, une tabulation, et
+        une description de l'afficheur braille pour lequel ce pilote
+        existe.
+      <tag>libbrlttyb<em>pilote</em>.so.1</tag>
+        Le pilote dynamique pour un afficheur braille, où <em>pilote</em>
+        correspond aux deux lettres du <ref id="drivers" name="code
+        d'identification du pilote">.
+      <tag>brltty-spk.lst</tag>
+        Une liste des pilotes de synthèse vocale qui ont été
+        compilés en tant qu'objets dynamiques, et, ainsi, qui peuvent
+        être utilisés. Chaque ligne contient les deux lettres
+        du code d'identification pour un pilote, une tabulation, et
+        une description de la synthèse vocale pour laquelle ce pilote
+        existe.
+      <tag>libbrlttys<em>pilote</em>.so.1</tag>
+        Le pilote dynamique pour une synthèse vocale, où <em>pilote</em>
+        correspond aux deux lettres du <ref id="drivers" name="Code
+        d'identification du pilote">.
+    </descrip>
+
+  <tag>/man/</tag>
+    Pages de manuel.
+    <descrip>
+      <tag>man1/<em>nom</em>.1</tag>
+        Pages de manuel pour les commandes utilisateur liées à
+        BRLTTY.
+      <tag>man3/<em>nom</em>.3</tag>
+        Pages de manuel pour la bibliothèque de
+        l'interface de programmation de l'application.
+    </descrip>
+</descrip>
+
+D'autres fichiers optionnels que vous devriez connaître, bien qu'ils ne
+fassent pas partie de la hiérarchie des fichiers installés, sont :
+<descrip>
+  <tag>/etc/brltty.conf</tag>
+    Le fichier des paramètres système par défaut. Il est créé par
+    l'administrateur système. Voir <ref id="configure" name="Le
+    fichier de configuration"> pour plus de détails.
+  <tag>/etc/brltty-<em>pilote</em>.prefs</tag>
+    Le fichier de sauvegarde des paramètres de préférence (<em>pilote</em>
+    correspond aux deux lettres du <ref id="drivers" name="code
+    d'identification de pilote">). Il est créé par la commande <ref
+    id="command-PREFSAVE" name="PREFSAVE">.  Voir <ref
+    id="preferences" name="Paramètres de Préférences"> pour plus de
+    détails.
+</descrip>
+
+<sect1>Installation à partir d'un TAR Ball<label id="tar"><p>
+Voici ce que vous avez à faire si vous voulez seulement installer
+BRLTTY à toute vitesse, en espérant que tous
+nos paramètres par défaut soient corrects.
+<enum>
+  <item>
+    Téléchargez les sources. Il s'agira d'un fichier nommé
+    <tt>brltty-</tt><em>version</em><tt>.tar.gz</tt>, par exemple
+    <tt>brltty-3.0.tar.gz</tt>.
+  <item>
+    Décompressez les sources dans une structure hiérarchique native.
+    <tscreen>tar xzf brltty-<em>version</em>.tar.gz</tscreen>
+    Cela devrait créer le répertoire <tt>brltty-</tt><em>version</em>.
+  <item>
+    Allez dans le répertoire des sources, configurez, compilez et installez BRLTTY.
+    <tscreen>
+      cd brltty-<em>version</em>
+      <newline>
+      ./configure
+      <newline>
+      make
+      <newline>
+      make install
+    </tscreen>
+    Vous devez effectuer cette dernière commande en tant que <bf/root/.
+</enum>
+
+Pour désinstaller BRLTTY, faites :
+<tscreen>
+  cd brltty-<em>version</em>
+  <newline>
+  make uninstall
+</tscreen>
+
+C'est tout ce que vous avez à faire.
+Pour ceux qui veulent vraiment savoir ce qui se passe, voici
+maintenant les détails.
+
+<sect2>Options de compilation<label id="build"><p>
+La première étape dans la compilation de BRLTTY est de le configurer
+en fonction de votre système et/ou de vos besoins personnels. Cela se
+fait par l'exécution du script de <tt>configuration</tt> dans le
+répertoire racine de BRLTTY. Nous avons tenté de faire correspondre les paramètres
+par défaut suffisants à la plupart des cas communs, donc, en
+supposant que vous ne tentez pas de faire quelque chose sortant de
+l'ordinaire, il se peut que vous n'ayez rien besoin d'autre
+que d'appeler ce script sans spécifier d'options.
+<tscreen>./configure</tscreen>
+Si toutefois vous avez des besoins particuliers, ou si vous
+êtes simplement un aventurier, vous devriez trouver plus loin quels sont
+vos choix.
+<tscreen>./configure --help</tscreen>
+Vous devriez aussi lire le fichier README dans le sous-répertoire
+contenant le pilote pour votre afficheur braille pour toutes les
+instructions supplémentaires spécifiques.
+
+<sect3>Paramètres système par défaut<label id="build-defaults"><p>
+<descrip>
+  <tag><tt>--with-braille-driver=</tt><em>pilote</em><label id="build-braille-driver"></tag>
+    Spécifie les pilotes des terminaux braille que vous voulez lier à
+    l'exécutable de BRLTTY. Les pilotes qui ne figureront pas dans la
+    liste de cette option seront compilés en tant qu'objets dynamiques
+    et pourront toujours être sélectionnés au moment de l'exécution. Chaque
+    pilote doit être identifié soit par les 2 lettres de son <ref id="drivers" name="code
+    d'identification de pilote"> ou par son vrai nom (complet ou
+    abrégé). Ces noms et/ou identifiants de pilotes doivent être séparés les uns des
+    autres par une simple virgule. Si un identifiant de pilote est
+    précédé du signe moins (<tt>-</tt>), ce pilote est alors exclu de la
+    compilation. Le premier pilote non-exclu devient le pilote par
+    défaut. Il n'y a pas de pilote par défaut si vous n'utilisez pas
+    cette option. Un des mots suivants peut être aussi utilisé comme
+    opérateur pour l'option:
+
+(<tt>-</tt>),
+    <descrip>
+      <tag>all</tag>
+        Lie tous les pilotes à l'exécutable. Ne les compile pas en
+        tant qu'objets dynamiques. Vous pouvez spécifier ce mot comme
+        élément terminant une liste de pilotes. C'est une façon de
+        spécifier un pilote par défaut quand tous les pilotes seront
+        compilés.
+      <tag>-all</tag>
+        Ne compile que les pilotes qui ont été explicitement inclus
+        par cette option.
+      <tag>no</tag>
+        Ne compile aucun pilote. Cela revient à spécifier
+        <tt>--without-braille-driver</tt>.
+      <tag>yes</tag>
+        Compile tous les pilotes en tant qu'objets dynamiques. Ne les
+        lie pas à l'exécutable. Cela revient à spécifier
+        <tt>--with-braille-driver</tt>.
+    </descrip>
+    Voir la ligne <ref id="configure-braille-driver" name="braille-driver"> du fichier de configuration
+    et l'option <ref id="options-braille-driver" name="-b"> de la ligne de commande pour une sélection à l'exécution.
+  <tag><tt>--with-braille-parameters=</tt>[<em>pilote</em><tt>:</tt>]<em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-braille-parameters"></tag>
+    Spécifie les paramètres par défaut pour les pilotes des afficheurs
+    braille. Si un même paramètre est spécifié plus d'une fois, sa
+    valeur la plus à droite est prise en compte. Si le nom d'un
+    paramètre est spécifique à un pilote, (voir la section
+    <ref id="drivers" name="Codes d'identification de pilotes">) cette
+    valeur ne s'applique qu'à ce pilote; sinon, il
+    s'applique à tous les pilotes. Pour une description des paramètres
+    acceptés par un pilote particulier, reportez-vous à la
+    documentation de ce pilote. Voir la ligne <ref
+    id="configure-braille-parameters" name="braille-parameters"> du
+    fichier de configuration et l'option <ref
+    id="options-braille-parameters" name="-B"> de la ligne de commande
+    pour la sélection à l'exécution.
+  <tag><tt>--with-braille-device=</tt><em>pilote</em><tt>,</tt>...<label id="build-braille-device"></tag>
+    Spécifie le port par défaut auquel l'afficheur braille est relié
+    (voir la section <ref id="operand-braille-device"
+    name="Spécification du port braille">). Si vous ne spécifiez pas
+    cette option, usb: est supposé si le support USB est disponible,
+    bluetooth: est supposé si le support Bluetooth est disponible, et
+    usb:,bluetooth: est supposé si les deux sont disponibles. Si ni le support
+    USB ni le support Bluetooth ne sont disponibles, un
+    chemin approprié pour le périphérique du port série primaire correspondant au système
+    d'exploitation est supposé. Voir la ligne
+    <ref id="configure-braille-device" name="braille-device"> du
+    fichier de configuration et l'option <ref
+    id="options-braille-device" name="-d"> de la ligne de commande
+    pour la sélection à l'exécution.
+  <tag><tt>--with-libbraille=</tt><em>répertoire</em>
+    Spécifie l'endroit où se trouve le paquet libbraille, et compile le pilote
+    pour l'afficheur braille libbraille (voir
+    <ref id="restrictions-flite" name="Restrictions dans la compilation">).
+    Un des mots suivants peut être aussi utilisé comme
+    opérateur pour l'option:
+
+(<tt>-</tt>),
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote. Cela revient à spécifier
+        <tt>--without-libbraille</tt>.
+        
+      <tag>yes</tag>
+        Compile le pilote si le paquet est présent dans <tt>/usr</tt>, 
+        <tt>/usr/local</tt>, <tt>/usr/local/Libbraille</tt>,
+        <tt>/usr/local/libbraille</tt>, <tt>/opt/Libbraille</tt>, ou
+        <tt>/opt/libbraille</tt>. Cela revient à à spécifier <tt>--with-libbraille</tt>.
+    </descrip>
+  <tag><tt>--with-text-table=</tt><em>fichier</em><label id="build-text-table"></tag>
+    Spécifie la table de traduction de texte compilée en dur (celle par défaut) (voir la section <ref
+    id="table-text" name="Tables de texte"> pour plus de
+    détails). La table spécifiée est liée au binaire BRLTTY et elle est utilisée
+    au cas où la sélection automatique à partir de la locale échoue ou si la table
+    demandée ne peut pas être chargée.
+    Vous pouvez fournir le chemin absolu d'une table hors de
+    l'arborescence source. L'extension <tt/.ttb/ est facultative. Pour un
+    nom de fichier simple, le préfixe <tt>text.</tt> est facultatif. Si
+    vous ne spécifiez pas cette option, <tt/en-nabcc/, une table commune (en
+    Amérique du Nord) utilisant la variante 8 points du 
+    <ref id="nabcc" name="North American Braille Computer Code">, est
+    utilisée. Voir la ligne <ref id="configure-text-table"
+    name="text-table"> du fichier de configuration et l'option <ref
+    id="options-text-table" name="-t"> en ligne de commande pour la
+    sélection à l'exécution. Ce paramètre peut être modifié par la
+    préférence <ref id="preference-text-table" name="Text Table">.
+  <tag><tt>--with-attributes-table=</tt><em>fichier</em><label id="build-attributes-table"></tag>
+    Spécifie la table des attributs compilée en dur (celle par défaut) (voir la section
+    <ref id="table-attributes" name="Tables d'attributs">
+    pour plus de détails). La table spécifiée est liée au binaire BRLTTY et elle est utilisée
+    au cas où la sélection automatique à partir de la locale échoue ou si la table
+    demandée ne peut pas être chargée.
+    Vous pouvez fournir le chemin absolu d'une
+    table hors de l'arborescence source.  L'extension <tt>.atb</tt> est
+    facultative. Si vous ne spécifiez pas cette option,
+    <tt>attributes</tt> est alors utilisé. Changez cette option en
+    <tt>attrib</tt> pour maintenir l'ancienne forme. Voir la ligne
+    <ref id="configure-attributes-table" name="attributes-table"> du
+    fichier de configuration et l'option <ref
+    id="options-attributes-table" name="-a"> en ligne de commande pour
+    la sélection à l'exécution. Ce paramètre peut être modifié par la
+    préférence <ref id="preference-attributes-table" name="Attributes
+    Table">.
+  <tag><tt>--with-speech-driver=</tt><em>pilote</em><label id="build-speech-driver"></tag>
+    Spécifie les pilotes de synthèse vocale que vous voulez lier à
+    l'exécutable de BRLTTY. Les pilotes qui ne figureront pas dans la
+    liste de cette option seront compilés en tant qu'objets dynamiques
+    et pourront toujours être sélectionnés à l'exécution. Chaque
+    pilote doit être identifié soit par les 2 lettres de son <ref
+    id="drivers" name="code d'identification de pilote"> ou par son
+    vrai nom (complet ou abrégé). Ces noms et/ou les identifiants de pilotes doivent être
+    séparés les uns des autres par une simple virgule. Si un
+    identifiant de pilote est précédé du signe moins (<tt>-</tt>), ce
+    pilote est alors exclu de la compilation. Un des mots
+    suivants peut aussi être utilisé comme opérateur pour l'option:
+    <descrip>
+      <tag>all</tag>
+        Lie tous les pilotes à l'exécutable. Ne les compile pas en
+        tant qu'objets dynamiques. Vous pouvez spécifier ce mot comme
+        élément terminant une liste de pilotes. C'est une façon de
+        spécifier un pilote par défaut quand tous les pilotes seront
+        compilés.
+      <tag>-all</tag>
+        Ne compile que les pilotes qui ont été explicitement inclus
+        par cette option.
+      <tag>no</tag>
+        Ne compile aucun pilote. Cela revient à spécifier
+        <tt>--without-speech-driver</tt>.
+      <tag>yes</tag>
+        Compile tous les pilotes en tant qu'objets dynamiques. Ne les
+        lie pas à l'exécutable. Cela revient à spécifier
+        <tt>--with-speech-driver</tt>.
+    </descrip>
+    Voir la ligne <ref id="configure-speech-driver" name="speech-driver"> du fichier de configuration et
+    l'option <ref id="options-speech-driver" name="-s"> de la ligne de commande pour une sélection à l'exécution.
+  <tag><tt>--with-speech-parameters=</tt>[<em>pilote</em><tt>:</tt>]<em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-speech-parameters"></tag>
+    Spécifie les paramètres par défaut pour les pilotes de synthèse
+    vocale. Si un même paramètre est spécifié plus d'une fois, sa
+    valeur la plus à droite est prise en compte. Si le nom d'un
+    paramètre est spécifique à un pilote, (voir la section
+    <ref id="drivers" name="Codes d'identification de pilotes">) cette valeur ne s'applique
+    qu'à ce pilote; sinon, il s'applique à tous les
+    pilotes. Pour une description des paramètres acceptés par un
+    pilote spécifique, reportez-vous à la documentation de ce
+    pilote. Voir la ligne <ref id="configure-speech-parameters" name="speech-parameters"> du fichier de
+    configuration et l'option <ref id="options-speech-parameters" name="-S"> de la ligne de commande pour la
+    sélection à l'exécution.
+  <tag><tt>--with-flite=</tt><em>directory</em><label id="build-flite"></tag>
+    Spécifie l'emplacement d'installation de FestivalLite
+    et compile le pilote pour cette synthèse vocale
+    (voir <ref id="restrictions-flite" name="Restrictions dans la compilation"> L'un des
+    mots suivants peut aussi être utilisé comme opérateur de cette
+    option:
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote.
+        Cela revient à spécifier <tt>--without-flite</tt>.
+      <tag>yes</tag>
+        Compile le pilote si le paquet existe dans <tt>/usr</tt>,
+        <tt>/usr/local</tt>, <tt>/usr/local/FestivalLite</tt>,
+        <tt>/usr/local/flite</tt>, <tt>/opt/FestivalLite</tt> ou
+        <tt>/opt/flite</tt>. Cela revient à
+        spécifier<tt>--with-flite</tt>.
+    </descrip>
+
+  <tag><tt>--with-flite-language=</tt><em>language</em><label id="build-flite-language"></tag>
+    Spécifie la langue qu'utilisera le moteur de FestivalLite.
+    La langue utilisée par défaut est <tt>l'anglais</tt>.
+  <tag><tt>--with-flite-lexicon=</tt><em>lexicon</em><label id="build-flite-lexicon"></tag>
+    Spécifie le dictionnaire que le moteur de FestivalLite
+    utilisera. Le dictionnaire par défaut est <tt>cmulex</tt>.
+  <tag><tt>--with-flite-voice=</tt><em>voice</em><label id="build-flite-voice"></tag>
+    Spécifie la personne que le moteur de FestivalLite doit utiliser.
+    La voix par défaut est <tt>cmu_us_kal16</tt>.
+  <tag><tt>--with-mikropuhe=</tt><em>directory</em><label id="build-mikropuhe"></tag>
+    Spécifie l'emplacement d'installation du paquet
+    Mickropuhe et compile le pilote pour la synthèse
+    vocale Mikropuhe (voir <ref id="restrictions-mikropuhe" name="Restrictions dans la compilation">). L'un
+    des mots suivants peut aussi être utilisé comme opérateur de cette
+    option:
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote.
+        Cela revient à spécifier <tt>--without-mikropuhe</tt>.
+      <tag>yes</tag>
+        Compile le pilote si le paquet existe dans <tt>/usr</tt>, 
+        <tt>/usr/local</tt>, <tt>/usr/local</tt>, <tt>/usr/local/Mikropuhe</tt>,
+        <tt>/usr/local/mikropuhe</tt>, <tt>/opt/Mikropuhe</tt> 
+        ou <tt>/opt/mikropuhe</tt>. Cela revient à spécifier 
+        <tt>--with-mikropuhe</tt>.
+    </descrip>
+  <tag><tt>--with-speechd=</tt><em>repertoire</em><label id="build-theta"></tag>
+    Spécifie l'endroit où est installé le paquet de la synthèse vocale speech-dispatcher
+    (multiplexeur de synthèse vocale) et compile le pilote pour speech-dispatcher.
+    L'un des mots suivants peut aussi être utilisé comme opérateur de cette 
+    option:
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote.
+        Cela revient à spécifier <tt>--without-speechd</tt>.
+      <tag>yes</tag>
+        Compile le pilote si le paquet existe dans <tt>/usr</tt>, <tt>/usr/local</tt>,
+        <tt>/usr/local/speech-dispatcher</tt>, <tt>/usr/local/speechd</tt>,
+        <tt>/opt/speech-dispatcher</tt>, ou <tt>/opt/speechd</tt>. Cela revient
+        à spécifier <tt>--with-speechd</tt>.
+    </descrip>    
+  <tag><tt>--with-swift=</tt><em>repertoire</em><label id="build-theta"></tag>
+    Spécifie l'endroit où est installé le paquet de la synthèse vocale, et
+    compile le pilote pour la synthèse vocale Swift.
+    L'un des mots suivants peut aussi être utilisé comme opérateur de cette 
+    option:
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote.
+        Cela revient à spécifier <tt>--without-swift</tt>.
+      <tag>yes</tag>
+        Compile le pilote si le paquet existe dans <tt>/usr</tt>, <tt>/usr/local</tt>,
+        <tt>/usr/local/Swift</tt>, <tt>/usr/local/swift</tt>,
+        <tt>/opt/Swift</tt>, ou <tt>/opt/swift</tt>. Cela revient
+        à spécifier <tt>--with-swift</tt>.
+    </descrip>    
+  <tag><tt>--with-theta=</tt><em>repertoire</em><label id="build-theta"></tag>
+    Spécifie l'emplacement d'installation du paquet Theta
+    et compile le pilote pour la synthèse vocale Theta
+    (voir <ref id="restrictions-theta" name="Restrictions dans la compilation">). L'un des mots
+    suivants peut aussi être utilisé comme opérateur de cette option:
+    <descrip>
+      <tag>no</tag>
+        Ne compile pas le pilote.
+        Cela revient à spécifier <tt>--without-theta</tt>.
+      <tag>yes</tag>
+        Compile le pilote si le paquet existe dans <tt>/usr</tt>, <tt>/usr/local</tt>,
+        <tt>/usr/local/Theta</tt>, <tt>/usr/local/theta</tt>,
+        <tt>/opt/Theta</tt>, ou <tt>/opt/theta</tt>.
+        Cela revient à spécifier <tt>--with-theta</tt>.
+    </descrip>
+  <tag><tt>--with-screen-driver=</tt><em>pilote</em><label id="build-screen-driver"></tag>
+    Spécifie les pilotes pour l'écran qui seront liés à l'exécutable BRLTTY. 
+    Ceux qui ne sont pas mentionnés dans cette option sont compilés en tant
+    qu'objets dynamiques et peuvent être choisis lors de
+    l'exécution. Chaque pilote doit être identifié soit par les deux lettres de
+    son code d'identification (voir la section Ecrans supportés) soit par
+    son nom  (complet ou abrégé). Vous devez séparer les identifiants des
+    pilotes les uns des autres par une virgule. Si l'identifiant d'un pilote
+    est précédé du signe moins (<tt>-</tt>), il est exclu de la compilation.
+    Un des mots suivants peut être aussi utilisé comme
+    opérateur pour l'option:
+
+(<tt>-</tt>),
+    <descrip>
+      <tag>all</tag>
+        Lie tous les pilotes à l'exécutable. Ne les compile pas en
+        tant qu'objets dynamiques. Vous pouvez spécifier ce mot comme
+        élément terminant une liste de pilotes. C'est la façon de
+        spécifier un pilote par défaut quand tous les pilotes seront
+        compilés.
+      <tag>-all</tag>
+        Ne compile que les pilotes qui ont été explicitement inclus
+        par cette option.
+      <tag>no</tag>
+        Ne compile aucun pilote. Cela revient à spécifier
+        <tt>--without-screen-driver</tt>.
+      <tag>yes</tag>
+        Compile tous les pilotes en tant qu'objets dynamiques. Ne les
+        lie pas à l'exécutable. Cela revient à spécifier
+        <tt>--with-braille-driver</tt>.
+    </descrip>
+
+    Le premier pilote non exclu devient celui par défaut. 
+    Si cette option n'est pas spécifiée, une sélection adaptée au
+    système d'exploitation est faite. Si un pilote spécifique au
+    système d'exploitation en cours est fiable, il est utilisé. Sinon,
+    <tt>sc</tt> est utilisé.
+    
+    Voir la ligne <ref id="configure-screen-driver" name="screen-driver"> du fichier de configuration
+    et l'option <ref id="options-screen-driver" name="-x"> de la ligne de commande pour une sélection à l'exécution.
+  <tag><tt>--with-screen-parameters=</tt><em>name</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-screen-parameters"></tag>
+    Spécifie les paramètres par défaut pour le pilote d'écran. Si le
+    même paramètre est spécifié plus d'une fois, c'est sa valeur la
+    plus à droite qui est retenue. Si le nom d'un paramètre est affecté à un
+    pilote (voir section Pilotes d'écran supportés) alors ce paramètre ne
+    s'applique qu'au pilote; sinon il s'applique à tous les pilotes.
+    Pour une description des paramètres
+    acceptés par un pilote spécifique, reportez-vous à la
+    documentation de celui-ci. Voir la ligne <ref id="configure-screen-parameters" name="screen-parameters"> du
+    fichier de configuration et l'option <ref id="options-screen-parameters" name="-X"> en ligne de commande
+    pour la sélection à l'exécution.
+
+  <tag><tt/--with-usb-paquet=/<em/paquet/<tt/,/...<label id="build-usb-paquet"></tag>
+    Spécifie le paquet qui sera utilisé pour l'E/S USB.
+    Vous devez spéarer les noms de paquets les uns des
+    autres par une virgule, ils sont traités de la gauche vers la droite.
+    Le premier installé sur le système est sélectionné.
+    Les paquets suivants sont supportés:
+    <enum>
+      <item>libusb
+      <item>libusb-1.0
+    </enum>
+    Un des mots suivants peut être aussi utilisé comme opérande pour cette
+    option:
+    <descrip>
+      <tag/no/
+        Ne supporte pas l'E/S USB.
+        Cela revient à spécifier <tt/--without-usb-paquet/.
+      <tag/yes/
+        Utiliser le support natif pour l'E/S USB.
+        Si un support natif n'est pas disponible pour la
+        plateforme actuelle, utiliser alors le premier paquet supporté
+        disponible (comme pour l'ordre spécifié ci-dessus).
+        Cela revient à spécifier <tt/--with-usb-paquet/.
+    </descrip>
+  <tag><tt/--with-bluetooth-paquet=/<em/paquet/<tt/,/...<label id="build-bluetooth-paquet"></tag>
+    Spécifie le paquet qui sera utilisé pour l'E/S Bluetooth.
+    Vous devez spéarer les noms de paquets les uns des
+    autres par une virgule, ils sont traités de la gauche vers la droite.
+    Le premier installé sur le système est sélectionné.
+    Les paquets suivants sont supportés:
+    <enum>
+      <item>(aucun paquet n'est actuellement supporté)
+    </enum>
+    Un des mots suivants peut être aussi utilisé comme opérande pour cette
+    option:
+    <descrip>
+      <tag/no/
+        Ne supporte pas l'E/S Bluetooth.
+        Cela revient à spécifier <tt/--without-bluetooth-paquet/.
+      <tag/yes/
+        Utiliser le support natif pour l'E/S Bluetooth.
+        Si un support natif n'est pas disponible pour la
+        plateforme actuelle, utiliser alors le premier paquet supporté
+        disponible (comme pour l'ordre spécifié ci-dessus).
+        Cela revient à spécifier <tt/--with-bluetooth-paquet/.
+    </descrip>
+
+</descrip>
+
+<sect3>Spécification du répertoire<label id="hierarchy"><p>
+<descrip>
+  <tag><tt>--with-execute-root=</tt><em>repertoire</em><label id="build-execute-root"></tag>
+    Spécifie le répertoire dans lequel la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> aura sa racine lors de
+    l'exécution. Vous devriez fournir un chemin absolu. Si cette
+    option n'est pas spécifiée, le répertoire racine du système est
+    utilisé. Utilisez cette option si vous avez besoin d'installer les
+    exécutables de BRLTTY dans un emplacement non standard. Par
+    exemple, vous devez utiliser cette option si vous voulez avoir
+    plusieurs versions de BRLTTY installées en même temps (voir la
+    section <ref id="multiple" name="Installiation de plusieurs
+    versions"> pour un exemple de cette opération).
+  <tag><tt>--with-install-root=</tt><em>repertoire</em><label id="build-install-root"></tag>
+    Spécifie le répertoire où la <ref id="hierarchy" name="hiérarchie
+    des fichiers installés"> sera installée. Vous devriez
+    fournir un chemin absolu. Si cette option n'est pas spécifiée, la racine
+    d'exécution du paquet (voir l'option de compilation <ref
+    id="build-execute-root" name="--with-execute-root">) est
+    utilisée. Ce répertoire n'est utilisé que par <ref id="make-install"
+    name="make-install"> et <ref id="make-uninstall" name="make-uninstall">.
+    Utilisez cette option si vous avez besoin d'installer
+    BRLTTY dans un répertoire différent de celui où il sera exécuté en
+    définitive. Par exemple, vous avez besoin d'utiliser cette
+    caractéristique si vous compilez BRLTTY sur un système pour
+    l'utiliser sur un autre.
+  <tag><tt>--prefix=</tt><em>repertoire</em><label id="build-portable-root"></tag>
+    Indique le répertoire dans lequel la <ref id="hierarchy" name="hiérarchie des fichiers
+    installés"> constitue le répertoire par défaut, indépendant de
+    l'architecture, et qui sera utilisé comme répertoire
+    racine. Ces sous-répertoires comprennent les dossiers:
+    <itemize>
+      <item>le <ref id="build-data-directory" name="répertoire writable">
+      <item>le <ref id="build-data-directory" name="répertoire data">
+      <item>le <ref id="build-configuration-directory" name="répertoire configuration">
+      <item>le <ref id="build-manpage-directory" name="répertoire manpage">
+      <item>le <ref id="build-include-directory" name="répertoire include">
+    </itemize>
+    Vous devriez fournir un chemin absolu. Si vous ne spécifiez pas
+    cette option, c'est le répertoire racine du système qui est
+    utilisé. Ce répertoire est lié à celui précisé par l'option de
+    compilation <ref id="build-execute-root" name="--with-execute-root">.
+  <tag><tt>--with-writable-directory=</tt><em>répertoire</em><label id="build-architecture-root"></tag>
+    Spécifie le répertoire dans lequel la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    pourra être écrite. Vous devriez fournir un chemin absolu. Un des mots
+    suivants peut être utilisé comme opérateur pour cette option.
+
+(<tt>-</tt>),
+    <descrip>
+      <tag>no</tag>
+        Ne définit pas de répertoire inscriptible (writable). Cela revient à
+        spécifier
+        <tt>--without-writable-directory</tt>.
+      <tag>yes</tag>
+        Utilise l'endroit par défaut. Cela revient à spécifier
+        <tt>--with-writable-directory</tt>.
+    </descrip>
+    Si vous ne spécifiez pas cette option, le sous-répertoire autorisé en rw
+    (lecture/écriture) dans le dossier spécifié par l'option --with-library-directory
+    est supposé. Le répertoire est créé s'il n'existe pas.
+    
+  <tag><tt>--exec-prefix=</tt><em>répertoire</em><label id="build-architecture-root"></tag>
+    Spécifie le répertoire de la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    où les répertoires par défaut pour les
+    fichiers dépendant de l'architecture doivent être placés.
+    Ces répertoires incluent:
+    <itemize>
+      <item>le <ref id="build-program-directory" name="répertoire programme">
+      <item>le <ref id="build-library-directory" name="répertoire des bibliothèques compilées">
+      <item>le <ref id="build-api-directory" name="répertoire d'API compilé">
+    </itemize>
+    Vous devriez fournir un chemin absolu. Si vous ne spécifiez pas
+    cette option, c'est le répertoire spécifié par l'option de
+    compilation <ref id="build-portable-root" name="--prefix"> qui est utilisé.
+    Ce répertoire est lié à celui
+    spécifié par l'option de compilation <ref id="build-execute-root" name="--with-execute-root">.
+  <tag><tt>--libdir=</tt><em>repertoire</em><label id="build-api-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    où les modules statiques et les objets chargeables
+    dynamiquement pour l'Interface Programmatique de l'Application sont à copier. Le chemin absolu doit être
+    indiqué. Si cette option est absente le répertoire indiqué dans
+    l'option <tt>--libdir</tt> (qui, par défaut, est le répertoire racine <tt>/lib</tt>
+    du répertoire spécifié par l'option de construction
+    <ref id="build-architecture-root" name="--exec-prefix">) est utilisé. Le répertoire est créé s'il n'existe
+    pas.
+  <tag><tt>--sysconfdir=</tt><em>directory</em><label id="build-configuration-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    où les fichiers de configuration sont à installer. Vous devez
+    fournir un chemin absolu. Si vous ne spécifiez pas cette option,
+    c'est le répertoire spécifié par l'option de configuration standard <tt>--sysconfdir</tt>
+    (qui, par défaut, est le répertoire racine de <tt>/etc</tt> spécifié
+    par l'option de compilation  <ref id="build-portable-root"
+    name="--prefix">) qui est utilisé.
+    Le répertoire est créé s'il n'existe pas.
+  <tag><tt>--with-program-directory=</tt><em>directory</em><label id="build-program-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> où les exécutables
+    (binaires, exécutables), doivent être installés. Vous devez
+    fournir un chemin absolu. Si cette option n'est pas utilisée,
+    c'est le répertoire spécifié par l'option de configuration
+    standard <tt>--bindir</tt> (qui, par défaut, est le répertoire-racine
+    spécifié par l'option de compilation <ref
+    id="build-architecture-root" name="--exec-prefix">) qui est utilisé.
+    Le répertoire est créé s'il n'existe pas.
+  <tag><tt>--with-library-directory=</tt><em>directory</em><label id="build-library-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> où les pilotes et
+    d'autres fichiers indépendants de l'architecture doivent être installés. Vous devez
+    fournir un chemin absolu. Si cette option n'est pas spécifiée,
+    c'est le sous-répertoire du répertoire <tt>brltty</tt>
+    spécifié par l'option de configuration standard <tt>--libdir</tt>
+    (qui par défaut est le répertoire racine spécifié par l'option de
+    compilation <ref id="build-architecture-root" name="--exec-prefix">) qui est utilisé.
+    Le répertoire est créé s'il n'existe pas.
+  <tag><tt>--with-data-directory=</tt><em>directory</em><label id="build-data-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> où les tables, les pages
+    d'aide et d'autres fichiers indépendants de l'architecture doivent être
+    installés. Vous devez fournir un chemin absolu. Si cette option
+    n'est pas spécifiée, c'est le sous-répertoire du
+    répertoire <tt>brltty</tt> spécifié par l'option de configuration
+    standard <tt>--sysconfdir</tt> (qui par défaut est le répertoire
+    racine <tt>/etc</tt> spécifié par
+    l'option de compilation <ref id="build-portable-root"
+    name="--prefix">) qui est utilisé.  Le répertoire est créé
+    s'il n'existe pas.
+  <tag><tt>--with-manpage-directory=</tt><em>directory</em><label id="build-manpage-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> où les tables et les
+    pages de manuel doivent être installées. Vous devez fournir un
+    chemin absolu. Si cette option n'est pas spécifiée, c'est le
+    sous-répertoire du répertoire <tt>brltty</tt> spécifié
+    par l'option de configuration standard <tt>--mandir</tt> (qui par
+    défaut est le répertoire racine <tt>/man</tt> spécifié par l'option
+    de compilation <ref id="build-portable-root" name="--prefix">) qui
+    est utilisé. Le répertoire est créé s'il n'existe pas.
+  <tag><tt>--with-include-directory=</tt><em>directory</em><label id="build-include-directory"></tag>
+    Spécifie le répertoire situé dans la <ref id="hierarchy"
+    name="hiérarchie des fichiers installés"> où les fichiers d'en-tête
+    C pour l'Interface Programmatique de l'Application doivent être
+    installés. Vous devez fournir un chemin absolu. Si cette option
+    n'est pas spécifiée, c'est le sous-répertoire du répertoire <tt>brltty</tt> spécifié par
+    l'option de configuration standard <tt>--includedir</tt> (qui par
+    défaut est le répertoire racine <tt>/include</tt> spécifié par l'option
+    de compilation <ref id="build-portable-root" name="--prefix"> qui
+    est utilisé. Le répertoire est créé s'il n'existe pas.
+</descrip>
+
+<sect3>Caractéristiques de la compilation<label id="build-features"><p>
+Ces options sont surtout utiles quand vous compilez BRLTTY pour une
+disquette de démarrage. Elles visent à réduire la taille des programmes en supprimant
+les fonctions inutiles.
+<descrip>
+  <tag><tt>--enable-standalone-programs</tt><label id="build-standalone-programs"></tag>
+    Crée des programmes liés de façon statique plutôt que de façon
+    dynamique. Cette option supprime toutes les dépendances sur les
+    objets partagés à l'exécution. Seuls les pilotes par défaut (voir
+    les options de compilation <ref id="build-braille-driver" name="--with-braille-driver">,
+    <ref id="build-speech-driver" name="--with-speech-driver"> et
+    <ref id="build-screen-driver" name="--with-screen-driver">) sont compilés.
+  <tag><tt/--disable-stripping/<label id="build-stripping"></tag>
+    Ne supprime pas les symboles des tables des exécutables et des objets
+    partagés lors de leur installation.
+    
+  <tag><tt>--enable-relocatable-install</tt><label id="build-table-selection"></tag>
+    Si cette fonctionnalité est activée, tous les chemins internes sont
+    recalculés afin d'englober le répertoire du programme. Si elle n'est pas
+    utilisée, tous les chemins internes sont absolus. Cette fonctionnalité
+    vise à ce que toute la
+    <ref id="hierarchy" name="hiérarchie des fichiers installés"> soit
+    copiée ou déplacée proprement d'un endroit à l'autre, tout particulièrement
+    pour un usage sur des plateformes Windows.
+  <tag><tt/--disable-strippingl/<label id="build-strippingl"></tag>
+    Ne supprime pas les tables de symboles des exécutables et des objets
+    partagés lors de leur installation.
+  <tag><tt>--disable-learn-mode</tt><label id="build-learn-mode"></tag>
+    Réduit la taille du programme en excluant le mode apprentissage
+    des commandes (voir la section <ref id="learn" name="Mode apprentissage
+    des commandes">).
+  <tag><tt>--disable-contracted-braille</tt><label id="build-contracted-braille"></tag>
+    Réduit la taille du programme en excluant le support pour le
+    braille abrégé (voir la section <ref id=table-contraction
+    name="Braille abrégé">).
+  <tag><tt>--disable-speech-support</tt><label id="build-speech-support"></tag>
+    Réduit la taille du programme en excluant le support des synthëses vocales.
+  <tag><tt>--disable-iconv</tt><label id="build-table-selection"></tag>
+    Réduit la taille du programme en excluant le support pour la conversion
+    des codages.
+  <tag><tt/--disable-icu/<label id="build-icu"></tag>
+    Réduit la taille du programme en excluant le support pour 
+    l'internationalisation basée sur l'Unicode.
+  <tag><tt/--disable-x/<label id="build-x"></tag>
+    Réduit la taille du programme en excluant le support pour X11.
+  <tag><tt>--disable-beeper-support</tt><label id="build-beeper-support"></tag>
+    Réduit la taille du programme en excluant le support pour le
+    générateur des "none" en console.
+  <tag><tt>--disable-pcm-support</tt><label id="build-pcm-support"></tag>
+    Réduit la taille du programme en excluant le support pour
+    l'interface Digital Audio sur la carte son.
+  <tag><tt>--enable-pcm-support=</tt><em>interface</em></tag>
+    Si une plateforme propose plus d'une interface Digital Audio,
+    celle qui sera utilisée peut être spécifiée.
+    <table loc="h">
+      <tabular ca="lll">
+        Plateforme|Interface|Description@<hline>
+        Linux
+          |oss|Open Sound System@
+          |alsa|Advanced Linux Sound Architecture@
+      </tabular>
+    </table>
+  <tag><tt>--disable-midi-support</tt><label id="build-midi-support"></tag>
+    Réduit la taille du programme en excluant le support pour
+    l'interface numérique d'instruments de musique sur la carte son.
+  <tag><tt>--enable-midi-support=</tt><em>interface</em></tag>
+    Si une plateforme propose plus d'une interface numérique d'instrument de 
+    musique, vous pouvez spécifier celle qui sera utilisée.
+    <table loc="h">
+      <tabular ca="lll">
+        Platte-forme|Interface|Description@<hline>
+        Linux
+          |oss|Open Sound System@
+          |alsa|Advanced Linux Sound Architecture@
+      </tabular>
+    </table>
+  <tag><tt>--disable-fm-support</tt><label id="build-fm-support"></tag>
+    Réduit la taille du programme en excluant le support pour le
+    synthétiseur FM sur une carte son AdLib, OPL3, Sound Blaster, ou
+    équivalent.
+  <tag><tt>--disable-pm-configfile</tt><label id="build-pm-configfile"></tag>
+    Inclut une interface avec l'application <tt>gpm</tt> de telle sorte que,
+        sur les systèmes qui le supportent, BRLTTY interagisse avec le
+        pilote du pointeur (souris) (voir la section <ref id="gpm"
+    name="Support du pointeur (souris) via GPM">).
+  <tag><tt>--disable-gpm</tt><label id="build-api"></tag>
+    Réduit la taille du programme en excluant l'interface avec l'application gpm
+    qui permet à BRLTTY d'interagir avec le périphérique du pointeur (la souris)
+    (voir Support du pointeur (souris)  par GPM).
+  <tag><tt>--disable-api</tt><label id="build-api"></tag>
+    Réduit la taille du programme en excluant l'interface de programmation de l'application.
+  <tag><tt>--with-api-parameters=</tt><em>name</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Spécifie les paramètres par défaut pour l'interface de programmation
+    de l'application. Si le même paramètre est spécifié plus d'une
+    fois, sa valeur la plus à droite est utilisée. Pour une
+    description des paramètres acceptés par l'interface, voir le
+    manuel de référence de <bf>BrlAPI</bf>. Voir la ligne
+    <ref id="configure-api-parameters" name="api-parameters"> du
+    fichier de configuration et l'option <ref
+    id="options-api-parameters" name="-A">
+    en ligne de commande pour la sélection à l'exécution.
+  <tag><tt>--disable-caml-bindings=</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Ne compile pas les bindings Caml (interfaces de programmation) pour l'interface de programmation de l'application.
+  <tag><tt>--disable-java-bindings=</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Ne compile pas les bindings Java (interfaces de programmation)  pour l'interface de programmation de l'application.
+  <tag><tt>--disable-lisp-bindings=</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Ne compile pas les bindings Lisp (interfaces de programmation) pour l'interface de programmation de l'application.
+  <tag><tt>--disable-python-bindings=</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Ne compile pas les bindings Python (interfaces de programmation) pour l'interface de programmation de l'application.
+  <tag><tt>--disable-tcl-bindings=</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="build-api-parameters"></tag>
+    Ne compile pas les bindings Tcl (interfaces de programmation) pour l'interface de programmation de l'application.
+  <tag><tt>--with-tcl-config=</tt><em>chemin</em>
+    Spécifie l'endroit où se trouve le script de configuration Tcl 
+    (tclConfig.sh). Vous pouvez fournir le chemin, soit vers le script lui-même,
+    soit vers le répertoire où il se trouve. Un des mots suivants peut être 
+    aussi utilisé comme opérateur pour l'option:
+
+(<tt>-</tt>),
+    <descrip>
+      <tag>no</tag>
+        Utilise d'autres moyens pour savoir si Tcl est disponible et, s'il
+        l'est, où il a été installé. Cela revient à spécifier
+        <tt>--without-tcl-config.</tt>.
+      <tag>yes</tag>
+        Cherche le script dans quelques répertoires couramment utilisés. 
+        Cela revient à spécifier
+        <tt>--with-tcl-config</tt>.
+    </descrip>
+    
+</descrip>
+
+<sect3>Options diverses<label id="build-miscellaneous"><p>
+<descrip>
+  <tag><tt>--with-init-path=</tt><em>path</em><label id="build-init-path"></tag>
+    Spécifie le chemin du programme réel de démarrage pour le
+    système. Vous devriez fournir le chemin absolu. Si vous ne
+    spécifiez pas cette option:
+    <enum>
+      <item>
+        Vous devriez déplacer le programme <tt>init</tt> dans un nouvel
+        emplacement.
+      <item>
+        Vous devriez déplacer <tt>brltty</tt> dans l'emplacement original
+        du programme.
+      <item>
+        Finalement, quand le système exécute <tt/init/ au démarrage,
+        <tt>brltty</tt> est exécuté. Il se met automatiquement en
+        arrière-plan
+        et exécute le <tt>init</tt> réel au premier plan. C'est
+        l'une des manières quelque peu tordue d'avoir droit au braille dès le
+        début. C'est surtout utile pour certains disques
+        d'installation/sauvegarde.
+    </enum>
+    Si vous ne spécifiez pas cette option, cette possibilité n'est pas
+    activée. Elle vise tout particulièrement la compilation d'une image pour
+    un installeur en braille.
+  <tag><tt>--with-stderr-path==</tt><em>chemin</em><label id="build-init-path"></tag>
+    Spécifie le chemin du fichier ou du périphérique où la sortie standard
+    des erreurs sera écrite. Vous devriez fournir un chemin absolu. Si vous
+    ne spécifiez pas cette option, cette possibilité n'est pas activée.
+    Cette option vise tout particulièrement la compilation d'une image pour
+un installeur en braille.
+</descrip>
+
+<sect2>Préparer les cibles de fichier<label id="make"><p>
+Une fois que BRLTTY a été configuré, les étapes suivantes consistent
+en la compilation et l'installation de ce dernier. Elles sont
+effectuées en entrant la commande make du système dans le fichier make
+principal de BRLTTY (<tt>Makefile</tt> dans le répertoire principal). Le
+fichier make de BRLTTY supporte la plupart des cibles de maintenance
+d'application courants. Ils incluent:
+<descrip>
+  <tag>make</tag>
+    Un raccourci pour tout préparer.
+  <tag>make all<label id="make-all"></tag>
+    Compile et fait l'édition de liens pour l'exécutable BRLTTY, ses pilotes et leurs pages
+    de manuel, ses programmes de texte, et quelques autres petits
+    outils.
+  <tag>make install<label id="make-install"></tag>
+    Complète la compilation et la phase d'édition de liens (voir <ref id="make-all" name="make all">), et
+    installe alors l'exécutable BRLTTY, ses fichiers de données
+    (data), pilotes et pages d'aide, aux emplacements corrects et avec
+    les bonnes permissions.
+  <tag>make uninstall<label id="make-uninstall"></tag>
+    Enlève du système l'exécutable BRLTTY, ses fichiers de données,
+    pilotes et pages de manuel.
+  <tag>make clean<label id="make-clean"></tag>
+    Garantit que la compilation à venir et l'édition de liens (voir see <ref
+    id="make-all" name="make all">)
+    se feront à vide en enlevant les résultats de la
+    compilation précédente, en liant et en testant depuis la structure du
+    répertoire source. Cela comprend la suppression des fichiers
+    objets, des exécutables, des objets dynamiques, des listes de
+    pilote, des pages de manuel, des fichiers d'en-tête temporaires,
+    et des fichiers liés.
+  <tag>make distclean<label id="make-distclean"></tag>
+    Au-delà de la suppression des résultats de la compilation précédente et de
+    l'edition de liens (voir <ref id="make-clean" name="make clean">):
+    <itemize>
+      <item>
+
+        Supprime les résultats de la configuration de BRLTTY (voir
+        <ref id="build" name="Options de compilation">).  Cela inclut la suppression de
+        <tt>config.mk</tt>, <tt>config.h</tt>,
+        <tt>config.cache</tt>, <tt>config.status</tt>, et <tt>config.log</tt>.
+      <item>
+        Supprime les autres fichiers de la structure du répertoire
+        source, qui prennent beaucoup de place mais qui ne leur
+        appartient pas. Cela inclut la suppression de fichiers éditeur de sauvegarde (backup), résultats de test, les reliquats du patch, et copies de fichiers source originaux.
+    </itemize>
+</descrip>
+
+<sect1>Tester BRLTTY<p>
+Après la compilation, l'édition de liens, et l'installation de BRLTTY,
+c'est peut-être une bonne idée de faire un petit test avant de
+l'activer en permanence. Pour cela, appelez-le avec:
+
+<tscreen>brltty -b<em>pilote</em> -d<em>périphérique</em></tscreen>
+Pour <em>pilote</em>, spécifiez les deux lettres du <ref id="drivers"
+name="code d'identification de pilote"> correspondant à votre
+afficheur braille. Pour <em>périphérique</em>, spécifiez le chemin complet
+du périphérique auquel votre afficheur braille est connecté.
+
+Si vous ne voulez pas identifier explicitement le pilote et le
+périphérique à chaque fois que vous démarrez BRLTTY, vous pouvez
+procéder de deux façons. Vous pouvez établir des paramètres système
+par défaut via les lignes <ref id="configure-braille-driver" name="braille-driver"> et <ref id="configure-braille-device" name="braille-device"> du
+fichier de configuration, et/ou compiler tout ce dont vous avez besoin
+dans BRLTTY via les options de compilation <ref id="build-braille-driver" name="--with-braille-driver">
+et <ref id="build-braille-device" name="--with-braille-device">.
+
+Si tout va bien, le message d'identification de BRLTTY devrait
+apparaître sur l'afficheur braille pendant quelques secondes (voir
+l'option <ref id="options-message-timeout" name="-M"> de la ligne de commande). Après qu'il ait disparu, (ce
+que vous pouvez accélérer en appuyant sur n'importe quelle touche de
+l'afficheur), la zone de l'écran où le curseur se situe devrait
+apparaître. Cela signifie que vous devriez vous attendre à voir
+s'afficher les commande du shell. Alors, comme vous avez entré votre
+commande précédente, chaque caractère devrait apparaître sur
+l'afficheur dès qu'il est tapé sur le clavier.
+
+Si les choses se passent ainsi, arrêtez l'exécution de BRLTTY, et
+réjouissez-vous. Sinon, il peut être nécessaire de tester chaque pilote
+séparément de façon à isoler la source du problème. Vous pouvez tester
+le pilote de l'écran par <ref id="utility-scrtest" name="scrtest">, et celui de l'afficheur braille
+par <ref id="utility-brltest" name="brltest">.
+
+Si vous rencontrez un problème qui nécessite de farfouiller,
+vous voudrez peut-être exécuter les options suivantes de la ligne de commande de
+<tt>brltty</tt>:
+<itemize>
+  <item>
+    ...<ref id="options-log-level" name="-ldebug">
+    pour mettre dans un journal beaucoup de messages de diagnostique.
+  <item>
+    <ref id="options-no-daemon" name="-n">
+    pour mettre BRLTTY au premier plan.
+  <item>
+    <ref id="options-standard-error" name="-e">
+    pour diriger les messages de diagnostique vers l'erreur standard et
+    non dans le journal système.
+</itemize>
+
+<sect1>Démarrer BRLTTY<p>
+
+Quand BRLTTY est correctement installé, on l'appelle par la simple
+commande <tt>brltty</tt>. Vous pouvez créer un fichier de configuration
+(voir la section <ref id="configure" name="le fichier de configuration"> pour plus de détails)
+afin de d'établir des paramètres système par défaut pour des choses
+telles que l'emplacement du fichier de préférences, le pilote
+d'afficheur braille à utiliser, le périphérique auquel l'afficheur
+braille est connecté, et la table de texte à utiliser.
+Beaucoup d'options (voir la section <ref id="options" name="Options en ligne de commande">
+pour plus de détails) permettent de spécifier lors de l'exécution des
+choses telles que le fichier de configuration, les paramètres par
+défaut fixés dans le fichier de configuration, et quelques
+caractéristiques qui ont des paramètres par défaut raisonnables mais
+avec lesquelles seuls ceux qui pensent savoir ce qu'ils font peuvent
+souhaiter modifier. L'option <ref id="options-help" name="-h"> affiche un résumé de toutes les
+options. L'option <ref id="options-version" name="-V"> affiche la version courante du programme,
+de l'API et des pilotes sélectionnés. L'option ``-v'' affiche les valeurs
+des options après que toutes les sources aient été examinées.
+
+C'est probablement mieux d'avoir BRLTTY démarré automatiquement par le
+système, dès la séquence d'amorçage, de façon à ce
+que l'afficheur braille soit déjà prêt à fonctionner quand l'invite du
+logging apparaît. La plupart (probablement toutes) des distributions
+fournissent un script dans lequel les applications fournies par
+l'utilisateur peuvent être démarrées en sécurité, presqu'à la fin de
+la séquence de boot. Le nom de ce script dépend de votre
+distribution. Voici celles que nous connaissons:
+<descrip>
+  <tag>Red Hat</tag>
+    <tt>/etc/rc.d/rc.local</tt>
+</descrip>
+
+C'est une bonne idée que de démarrer BRLTTY depuis ce script (surtout
+pour les nouveaux utilisateurs). Ajustez simplement des lignes du
+type:
+<tscreen><verb>
+if [ -x /bin/brltty -a -f /etc/brltty.conf ]
+then
+   /bin/brltty
+fi
+</verb></tscreen>
+Normalement, cela peut s'abréger en une forme un peu moins
+lisible du type:
+<tscreen><verb> 
+[ -x /bin/brltty -a -f /etc/brltty.conf ] && /bin/brltty
+</verb></tscreen>
+N'ajoutez pas ces lignes avant la première ligne (qui ressemble
+généralement à <tt>&num;!/bin/sh</tt>).
+
+Si l'afficheur braille doit être utilisé par un administrateur système, il
+devrait probablement être démarré le plus tôt possible, pendant la
+séquence de boot (comme par exemple avant que les systèmes de fichier
+ne soient vérifiés) afin que l'afficheur soit utilisable si quelque
+chose ne va pas dans ces tests et que le système bascule en mode
+mono-utilisateur. De nouveau, là où il est l'idéal de faire cela
+dépend de la distribution. Voici les emplacements que nous connaissons:
+<descrip>
+  <tag>Debian</tag>
+    <tt>/etc/init.d/boot</tt> (pour les vieilles versions)
+    <newline>
+    <tt>/etc/init.d/</tt> (pour les versions récentes)
+    <newline>
+    Un paquet <tt>brltty</tt> est fourni
+    (voir [<htmlurl url="http://paquets.debian.org/brltty" name="http://paquets.debian.org/brltty">])
+    en tant que version <tt>3.0</tt> (<tt>Woody</tt>).
+    Comme ce paquet prend soin du démarrage de BRLTTY, il n'y a pas
+    besoin pour l'utilisateur de fournir quoique ce soit, s'il est
+    installé. Si vous avez besoin que le démon se lance avec des options en ligne
+    de commande, vous pouvez modifier le contenu entre guillemets du fichier
+    <tt>/etc/default/brltty</>. Si vous avez besoin que le démon se lance avec 
+    des options en ligne de commande, vous pouvez modifier le contenu entre 
+    guillemets de la ligne <tt/ARGUMENTS/ du fichier <tt>/etc/default/brltty</>.
+  <tag>RedHat</tag>
+    <tt>/etc/rc.d/rc.sysinit</tt>
+    <newline>
+    Sachez que les versions récentes, afin de supporter une procédure
+    d'initialisation du système plus orientée vers les utilisateurs,
+    font rappeler ce script par lui-même de telle sorte qu'il soit
+    sous le contrôle de <tt>initlog</tt>. Recherchez des lignes comme celles-ci:
+    <tscreen><verb>
+    # Rerun ourselves through initlog
+    if [ -z "$IN_INITLOG" ]; then
+      [ -f /sbin/initlog ] && exec /sbin/initlog $INITLOG_ARGS -r /etc/rc.sysinit
+    fi
+    </verb></tscreen>
+    Démarrer BRLTTY avant ce rappel donne deux processus BRLTTY en
+    même temps, et cela vous causera des foules de problèmes. Si votre
+    version de ce script a cette caractéristique, assurez-vous de
+    démarrer BRLTTY après les lignes qui le mettent en action.
+  <tag>Slackware</tag>
+    <tt>/etc/rc.d/rc.S</tt>
+  <tag>SuSE</tag>
+    <tt>/sbin/init.d/boot</tt>
+</descrip>
+
+Une autre solution est de démarrer BRLTTY depuis /etc/inittab. Si vous
+choisissez cette solution, vous avez deux possibilités.
+<itemize>
+  <item>
+    Si vous voulez qu'il démarre vraiment très tôt, mais que vous
+    n'avez pas besoin qu'il soit redémarré automatiquement s'il
+    échoue, ajoutez une ligne comme la suivante avant la première
+    ligne <tt>:sysinit:</tt> qui est déjà dans le fichier.
+  <item>
+    Une autre solution est de démarrer BRLTTY depuis /etc/inittab. Si
+    vous choisissez cette solution, vous avez deux possibilités. Si
+    vous voulez qu'il démarre vraiment très tôt, mais que vous n'avez
+    pas besoin qu'il soit redémarré automatiquement en cas d'échec,
+    ajoutez une ligne comme la suivante avant la première ligne
+    :sysinit: qui est déjà dans le fichier.
+    <tscreen>brl:12345:respawn:/bin/brltty -n</tscreen>
+
+    L'option <ref id="options-no-daemon" name="-n"> (--nodaemon) quand BRLTTY est exécuté avec la
+    facilité respawn de l'inittab. Si vous oubliez de la
+    spécifier, vous allez vous retrouver avec des centaines de
+    processus BRLTTY, tous exécutés en même temps.
+</itemize>
+Vérifiez que l'identificateur (<tt/brl/ dans ces exemples) n'est pas déjà
+utilisé par une autre entrée, et, si c'est le cas, choisissez-en un
+autre.
+
+Remarquez qu'une commande telle que <tt>kill -TERM</tt> suffit pour arrêter
+BRLTTY dans ses opérations. Si, par exemple, il meurt lors de
+l'entrée en mode mono-utilisateur, il se pourrait bien que cela vienne
+d'un problème de cette nature.
+
+Certains systèmes rencontrent des problèmes si une application tente
+d'utiliser le sous-système son du noyau avant qu'il ait été
+initialisé. Si votre système en fait partie, vous aurez peut-être
+besoin de désactiver le démarrage automatique du pilote de la synthèse
+vocale avec l'option <ref id="options-no-speech" name="-N">.
+
+Certains systèmes, dans une étape de leur séquence de boot, testent
+(probe) les ports série (normalement afin de détecter automatiquement
+la souris et déduire son type). Si votre afficheur braille utilise un
+port série, ce genre de détection peut suffir à le gêner. Si cela vous
+arrive, essayez de redémarrer le pilote braille (voir la commande
+<ref id="command-RESTARTBRL" name="RESTARTBRL">). Ou mieux, désactiver le test du port série. Voici ce
+que nous savons sur la manière de réaliser cela:
+<descrip>
+  <tag>Red Hat</tag>
+    Le test se fait par un service appelé <tt>kudzu</tt>. Utilisez la commande
+    <tscreen>chkconfig --list kudzu</tscreen> pour voir s'il a été activé.
+    Utilisez la commande <tscreen>chkconfig kudzu off</tscreen> pour le désactiver.
+    Les dernières versions permettent de laisser <tt>kudzu</tt> s'exécuter sans
+    tester les ports série.
+    Pour cela, éditez le fichier <tt>/etc/sysconfig/kudzu</tt>,, et réglez <tt>SAFE</tt> à <tt>yes</tt>.
+</descrip>
+
+Si vous voulez démarrer BRLTTY avant que les systèmes de fichiers ne
+soient montés, assurez-vous que tous leurs composants sont installés
+dans le fichier racine du système. Voir les options de compilation
+<ref id="build-execute-root" name="--with-execute-root">,
+<ref id="build-program-directory" name="--bindir">,
+<ref id="build-data-directory" name="--with-data-directory">,
+et <ref id="build-library-directory" name="--libdir"> ref id="build-execute-root" name="--with-writable-directory"> et ref id="build-execute-root" name="--with-data-directory'' build">.
+
+<sect1>Considérations sur la sécurité<p>
+L'exécution de BRLTTY nécessite les privilèges root parce que le
+programme a besoin, lorsqu'il s'exécute, des droits d'accès de lecture
+et d'écriture pour le port auquel l'afficheur braille est connecté,
+les droits d'accès en lecture à /dev/vcsa ou équivalent (pour avoir
+les dimensions de l'écran et la position du curseur, et pour revoir le
+contenu et les choses mises en valeur sur l'écran courant), ainsi que
+les accès en lecture et écriture à la console système (pour l'entrée
+des flèches de direction pendant le déplacement du curseur) pour
+l'insertion de caractères lors du Coller, pour la simulation de
+touches spéciales en utilisant celles de l'afficheur braille, pour la
+recherche de traduction des caractères en sortie et les tables faisant
+la correspondance des polices de l'écran, et pour l'activation
+du beeper interne). Vous pouvez, bien entendu, autoriser les
+utilisateurs ordinaires à accéder aux périphériques nécessaires, en
+changeant les permissions du fichier associé au
+périphérique. Toutefois, le simple accès à la console ne suffit pas
+car l'activation du beeper interne et des fonctions de
+simulation du clavier requiert les privilèges root. Ainsi, si vous
+voulez arrêter le déplacement du curseur, utiliser Copier/Coller les
+beep et tout cela, vous pouvez exécuter BRLTTY sans les privilèges
+root.
+
+<sect1>Restrictions applicables à la compilation et à l'exécution<p>
+<descrip>
+  <tag>les beeps d'Alerte</tag><label id="restriction-tunes">
+    Certaines plateformes ne supportent pas tous les périphériques
+    sonores. Voir <ref id="preference-tune-device" name="Périphérique pour le son"> pour plus de détails.
+  <tag>Pilote pour la synthèse FestivalLite</tag><label id="restrictions-flite">
+    Le pilote pour le moteur FestivalLite n'est compilé
+    que si ce paquet a été installé.
+
+    Ce pilote et celui pour le moteur Theta (voir
+    l'option de compilation ``--with-theta'') ne peuvent être liés
+    tous les deux à l'exécutable BRLTTY (voir l'option de compilation
+    <ref id="build-theta" name="--with-speech-driver">) car les bibliothèques nécessaires à leur
+    exécution contiennent des éléments conflictuels.
+  <tag>Pilote pour l'afficheur braille Libbraille</tag><label id="restrictions-mikropuhe">
+    Le pilote pour le paquet libbraille n'est compilé que si le paquet a
+    été installé.   
+  
+  <tag>Pilote pour la synthèse Mikropuhe</tag><label id="restrictions-mikropuhe">
+    Le pilote pour le moteur Mikropuhe n'est compilé
+    que si ce paquet a été installé.
+
+    Vous ne pouvez inclure ce pilote si l'exécutable BRLTTY est lié
+    statiquement (voir l'option de compilation
+    <ref id="build-standalone-programs" name="--enable-standalone-programs">) car le paquet n'inclut pas
+    de bibliothèque statique.
+  <tag>Le pilote pour la synthèse Theta</tag><label id="restrictions-theta">
+    Le pilote pour le moteur Theta n'est compilé que si
+    ce paquet a été installé.
+
+    Ce pilote et celui pour le moteur FestivalLite
+    (voir l'option de compilation <ref id="build-flite" name="--with-flite">) ne peuvent être
+    liés tous les deux à l'exécutable BRLTTY (voir l'option de
+    compilation <ref id="build-speech-driver" name="--with-speech-driver">) car les bibliothèques
+    nécessaires à leur exécution contiennent des éléments conflictuels.
+
+    Si ce pilote est compilé comme objet dynamique, vous devez
+    rajouter <tt>$THETA_HOME/lib</tt> à la variable d'environnement
+    <tt>LD_LIBRARY_PATH</tt> avant que BRLTTY ne soit appelé, car les objets à
+    l'intérieur du paquet ne contiennent pas les chemins de recherche
+    pour l'exécution et leurs dépendances.
+  <tag>ViaVoice Speech Synthesizer Driver</tag><label id="restrictions-viavoice">
+    Le pilote pour le moteur ViaVoice n'est compilé que
+    si ce paquet a été installé.
+
+    Vous ne pouvez inclure ce pilote si l'exécutable BRLTTY est lié
+    statiquement (voir l'option de compilation
+    the <ref id="build-standalone-programs" name="--enable-standalone-programs">) car le paquet n'inclut pas
+    de bibliothèque statique.
+  <tag>Pilote pour l'afficheur braille VideoBraille</tag><label id="restrictions-videobraille">
+    Le pilote pour l'afficheur braille VideoBraille est compilé sur
+    tous les systèmes, mais ne fonctionne que sur Linux.
+</descrip>
+
+<sect1>Installation à partir d'un fichier RPM<label id="rpm"><p>
+Pour installer BRLTTY à partir d'un fichier RPM (RedHat paquet
+Manager), procéder comme suit:
+<enum>
+  <item>
+    Téléchargez le paquet exécutable correspondant à votre
+    matériel. Ce sera un fichier nommé
+    <tt>brltty-</tt><em>version</em><tt>-</tt><em>version</em><tt>.</tt><em>architecture</em><tt>.rpm</tt>,
+    par exemple, <tt>brltty-3.0-1.i386.rpm</tt>.
+  <item>
+    Installez le paquet.
+    <tscreen>rpm -Uvh brltty-<em>version</em>-<em>version</em>.<em>architecture</em>.rpm</tscreen>
+    Vous devez faire cela en tant que <bf>root</bf>.
+
+    A proprement parler, l'option <tt>-U</tt> (Update) est la seule
+    nécessaire. L'option <tt>-v</tt> (verbose) affiche le nom du paquet
+    lorsqu'il va être installé. L'option <tt>-h</tt> (hashes) affiche une barre
+    de progression (utilisant des hachures).
+</enum>
+Pour les gens courageux, nous fournissons le fichier RPM source
+(<tt>.src.rpm</tt>) mais cela dépasse l'objectif de ce document.
+
+Pour désinstaller BRLTTY, faites:
+<tscreen>rpm -e brltty</tscreen>
+
+<sect1>Autres outils<p>
+La compilation de BRLTTY donne aussi celle de quelques petits
+outils d'aide et de diagnostic.
+
+<sect2>brltty-config<label id="utility-brltty-config"><p>
+Cet outil affecte un certain nombre de variables d'environnement
+à des valeurs reflétant l'installation courante de BRLTTY (voir les
+<ref id="build" name="options de compilation">). Vous devriez l'exécuter dans un
+environnement shell existant, donc ce n'est pas une véritable commande,
+et seuls les scripts supportant la syntaxe <tt>Bourne Shell</tt>
+peuvent l'utiliser.
+<tscreen>. brltty-config</tscreen>
+
+Les variables d'environnement suivantes sont affectées:
+<descrip>
+  <tag>BRLTTY_VERSION</tag>
+    Le numéro de version du paquet BRLTTY.
+  <tag>BRLTTY_EXECUTE_ROOT</tag>
+    La racine de l'exécution du paquet installé. Configurée par
+    l'option de compilation <ref id="build-execute-root" name="--with-execute-root">.
+  <tag>BRLTTY_PROGRAM_DIRECTORY</tag>
+    Répertoire des programmes exécutables (binaires
+    exécutables). Configuré par l'option de compilation <ref id="build-program-directory" name="--with-program-directory">.
+  <tag>BRLTTY_LIBRARY_DIRECTORY</tag>
+    Répertoire des pilotes. Configuré par l'option de compilation <ref id="build-library-directory" name="--with-library-directory">.
+  <tag>BRLTTY_WRITABLE_DIRECTORY</tag>    
+    Répertoire dans lequel il est possible d'écrire. Configuré par l'option
+    de compilation <ref id="build-program-directory" name="--with-writable-directory">.
+  <tag>BRLTTY_DATA_DIRECTORY</tag>
+    Répertoire des tables et des pages de manuel. Configuré par
+    l'option de compilation <ref id="build-data-directory" name="--with-data-directory">.
+  <tag>BRLTTY_MANPAGE_DIRECTORY</tag>
+    Répertoire des pages de manuel. Configuré par l'option de
+    compilation <ref id="build-manpage-directory" name="--with-manpage-directory">.
+  <tag>BRLTTY_INCLUDE_DIRECTORY</tag>
+    Répertoire pour les fichiers d'en-tête C de BrlAPI. Configuré
+    par l'option de compilation <ref id="build-include-directory" name="--with-include-directory">.
+  <tag>BRLAPI_VERSION</tag>
+    Le numéro de version de BrlAPI (BRLTTY's Application
+    Programming Interface).
+  <tag>BRLAPI_RELEASE</tag>
+    Le numéro de version complet de BrlAPI.
+  <tag>BRLAPI_AUTH</tag>
+    Le nom du fichier de clés de BrlAPI.
+</descrip>
+
+En plus, les variables d'environnement standard <tt>autoconf</tt>
+suivantes sont aussi assignées:
+<descrip>
+  <tag>prefix</tag>
+    Sous-répertoire pour les fichiers indépendants de l'architecture.
+    Configuré par l'option de compilation <ref id="build-portable-root" name="--prefix">.
+  <tag>exec_prefix</tag>
+    Sous-répertoire pour les fichiers dépendants de l'architecture.
+    Configuré par l'option de compilation <ref id="build-architecture-root" name="--exec-prefix">.
+  <tag>bindir</tag>
+    Emplacement par défaut du <ref id="build-program-directory" name="répertoire du programme">.
+    Configuré par l'option de compilation <tt>--bindir</tt>.
+  <tag>libdir</tag>
+    Répertoire pour les objets dynamiques et statiques de
+    BrlAPI, la localisation par défaut pour le <ref
+    id="build-library-directory" name="répertoire des
+    bibliothèques">. Configuré par l'option de compilation <ref
+    id="build-api-directory" name="--libdir">.
+  <tag>sysconfdir</tag>
+    Répertoire des fichiers de configuration, emplacement par défaut du <ref
+    id="build-data-directory" name="répertoire de données">. Configuré par
+    l'option de compilation <ref id="build-configuration-directory"
+    name="--sysconfdir">.
+  <tag>mandir</tag>
+    Emplacement par défaut pour le <ref id="build-manpage-directory" name="répertoire des pages de manuel">.
+    Configuré par l'option de compilation <tt>--mandir</tt>.
+  <tag>includedir</tag>
+    Emplacement par défaut du <ref id="build-include-directory" name="répertoire des fichiers d'en-tête">.
+    Configuré par l'option de compilation <tt>--includedir</tt>.
+</descrip>
+
+<sect2>brltty-install<label id="utility-brltty-install"><p>
+Cet outil copie la <ref id="hierarchy" name="hiérarchie des fichiers installés"> de
+BRLTTY d'un emplacement à un autre.
+<tscreen>brltty-install <em>destination</em> &lsqb;<em>origine</em>&rsqb;</tscreen>
+<descrip>
+  <tag><em>destination</em></tag>
+    L'emplacement où la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    sera copiée. Cela doit être un répertoire existant.
+  <tag><em>from</em></tag>
+    L'emplacement à partir duquel la <ref id="hierarchy" name="hiérarchie des fichiers installés">
+    sera copiée. S'il est spécifié, le répertoire doit
+    exister. S'il n'est pas spécifié, l'emplacement utilisé pour la
+    compilation est utilisé.
+</descrip>
+Par exemple, vous pouvez utiliser cette outil pour copier BRLTTY
+à partir d'un disque racine. Si une disquette racine est montée dans
+<tt>/mnt</tt>, et que BRLTTY est installé sur le système principal, taper <tscreen>brltty-install /mnt</tscreen>
+copie BRLTTY, en entier avec ses fichiers de données et ses bibliothèques, sur
+la disquette racine.
+
+Quelques problèmes ont été rencontrés en copiant BRLTTY entre des
+systèmes avec différentes versions de la bibliothèque C. Si vous avez
+des difficultés, cela vaut la peine d'enquêter.
+
+<sect2>brltest<label id="utility-brltest"><p>
+Cet outil teste un pilote d'afficheur braille, et fournit presque
+une façon interactive d'apprendre ce que font les touches de
+l'afficheur braille. Vous devriez l'exécuter en tant que root.
+<tscreen>brltest -<em>option</em> ... [<em>pilote</em> [<em>nom</em>=<em>valeur</em> ...]]</tscreen>
+<descrip>
+  <tag><em>pilote</em></tag>
+    Le pilote de l'afficheur braille. Doit être les deux lettres du
+    <ref id="drivers" name="code d'identification du pilote">. S'il n'est pas spécifié, le
+    premier pilote configuré par l'option de compilation
+    <ref id="build-braille-driver" name="--with-braille-driver"> est utilisé.
+  <tag><em>nom</em><tt>=</tt><em>valeur</em></tag>
+    Affecte un paramètre d'afficheur braille. Pour une description des
+    paramètres acceptés par un pilote spécifique, voir la
+    documentation de ce pilote.
+  <tag><tt>-d</tt><em>device</em> <tt>--device=</tt><em>périphérique</em></tag>
+    Le chemin absolu pour le périphérique auquel l'afficheur braille
+    est connecté. S'il n'est pas spécifié, c'est le périphérique
+    configuré par l'option de compilation <ref
+    id="build-braille-device" name="--with-braille-device"> qui est
+    utilisé.
+  <tag><tt>-D</tt><em>directory</em> <tt>--data-directory=</tt><em>répertoire</em></tag>
+    Vous devriez fournir un chemin absolu pour le répertoire où les
+    fichiers de données des pilotes sont placés. S'il n'est pas spécifié,
+    c'est le répertoire configuré par l'option de compilation
+    <ref id="build-data-directory" name="--with-data-directory"> qui est utilisé.
+  <tag><tt>-L</tt><em>directory</em> <tt>--library-directory=</tt><em>repertoire</em></tag>
+    Le chemin absolu pour le répertoire dans lequel sont situés les
+    pilotes. S'il n'est pas spécifié, c'est le répertoire configuré par
+    l'option de compilation <ref id="build-library-directory" name="--libdir"> qui est utilisé.
+  <tag><tt>-W</tt><em>répertoire</em> <tt>--writable-directory=</tt><em>repertoire</em></tag>
+    Le chemin absolu vers un répertoire où il est possible d'écrire. S'il
+    n'est pas spécifié, le répertoire configuré avec l'option >e compilation --with-writable-directory
+    est utilisé.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options de la ligne de commande, puis quitte.
+</descrip>
+
+Cet outil utilise le ``mode apprentissage des commandes'' de
+BRLTTY. Le délai d'appui sur une touche (après lequel cet outil
+quitte) est de 10 secondes. Le temps d'affichage du message (utilisé
+pour les segments non-finaux de longs messages) est de <tt>4</tt> secondes.
+
+<sect2>spktest<label id="utility-spktest"><p>
+Cet outil teste un pilote de synthèse vocale. Il se peut qu'il
+doive être exécuté en tant que root.
+<tscreen>spktest -<em>option</em> ... [<em>pilote</em> [<em>nom</em>=<em>value</em> ...]]</tscreen>
+<descrip>
+  <tag><em>pilote</em></tag>
+
+    Le pilote pour la synthèse vocale. Doit être les deux lettres
+    du <ref id="drivers" name="code d'identification de pilote">. S'il n'est pas spécifié,
+    c'est le premier pilote spécifié par l'option de compilation
+    <ref id="build-speech-driver" name="--with-speech-driver"> qui est utilisé.
+  <tag><em>nom</em><tt>=</tt><em>valeur</em></tag>
+    Règle le paramètre du pilote de la synthèse vocale. Pour une
+    description des paramètres acceptés par un pilote spécifique,
+    voir la documentation de ce pilote.
+  <tag><tt>-t</tt><em>string</em> <tt>--text-string=</tt><em>string</em></tag>
+    Le texte qui sera dit. S'il n'est pas spécifié, c'est l'entrée
+    standard (stdin) qui est lue.
+  <tag><tt>-D</tt><em>repertoire</em> <tt>--data-directory=</tt><em>repertoire</em></tag>
+    Le chemin absolu pour le répertoire dans lequel se situent les
+    fichiers de données du pilote. S'il n'est pas spécifié, c'est le
+    répertoire configuré par l'option de compilation
+    <ref id="build-data-directory" name="--with-data-directory"> qui est utilisé.
+  <tag><tt>-L</tt><em>repertoire</em> <tt>--library-directory=</tt><em>repertoire</em></tag>
+    Le chemin absolu du répertoire où se situe les pilotes. S'il n'est
+    pas spécifié, c'est le répertoire configuré par l'option de
+    compilation <ref id="build-library-directory" name="--libdir"> qui est utilisé.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options de la ligne de commande, puis quitte.
+</descrip>
+
+<sect2>scrtest<label id="utility-scrtest"><p>
+Cet outil teste le pilote d'écran. Il doit être exécuté en tant
+que root.
+<tscreen>scrtest -<em>option</em> ... [<em>nom</em>=<em>valeur</em> ...]</tscreen>
+<descrip>
+  <tag><em>nom</em><tt>=</tt><em>valeur</em></tag>
+    Règle le paramètre du pilote de l'écran. Pour une
+    description des paramètres acceptés par un pilote spécifique
+    voir la documentation de ce pilote.
+  <tag><tt>-l</tt><em>colonne</em> <tt>--left=</tt><em>colonne</em></tag>
+    Spécifie la colonne du début (à gauche) de l'écran (origine à
+    zéro). Si vous ne fournissez pas cette valeur, une valeur par
+    défaut, basée sur la largeur spécifiée, est sélectionnée, de telle
+    sorte que la fenêtre soit centrée à l'horizontal, est utilisée.
+  <tag><tt>-c</tt><em>coompte</em> <tt>--columns=</tt><em>compte</em></tag>
+    Spécifie la largeur de la fenêtre (en colonnes). Si vous ne
+    fournissez pas cette valeur, une valeur par défaut, basée sur la
+    colonne de début, est sélectionnée, de telle sorte que la fenêtre
+    soit centrée à l'horrizontal.
+  <tag><tt>-t</tt><em>ligne</em> <tt>--top=</tt><em>ligne</em></tag>
+    Spécifie la ligne de début (en haut) de l'écran (origine à
+    zéro). Si vous ne fournissez pas cette valeur, une valeur par
+    défaut, basée sur la hauteur spécifiée, est sélectionnée, de telle
+    sorte que la fenêtre soit centrée à la verticale.
+  <tag><tt>-r</tt><em>compte</em> <tt>--rows=</tt><em>compte</em></tag>
+    Spécifie la hauteur de la fenêtre (en lignes). Si vous ne
+    fournissez pas cette valeur, une valeur par défaut, basée sur la
+    rangée de début spécifiée, est sélectionnée, de telle sorte que la
+    fenêtre soit centrée à la verticale.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options de la ligne de commande, puis
+        quitte.
+</descrip>
+
+Remarques:
+<itemize>
+  <item>
+    Si vous ne spécifiez ni la colonne de début, ni la largeur de la
+    fenêtre, la région est centrée horizontalement et commence à la
+    colonne 5.
+  <item>
+    Si vous ne spécifiez ni la rangée de début, ni la hauteur de la
+    fenêtre, la région est centrée verticalement et commence à la ligne
+    5.
+</itemize>
+
+Les éléments suivants sont écrits sur la sortie standard (stdout):
+<enum>
+  <item>
+    Une ligne détaillant les dimensions de l'écran.
+    <tscreen>Screen: <em>largeur</em>x<em>hauteur</em></tscreen>
+  <item>
+    Une ligne détaillant la position (à l'origine zéro) du curseur.
+    <tscreen>Cursor: &lsqb;<em>colonne</em>,<em>ligne</em>&rsqb;</tscreen>
+  <item>
+    Une ligne détaillant les dimensions de la zone d'écran
+    sélectionnée, et la position (à l'origine zéro) de son coin en
+    haut à gauche.
+    <tscreen>Region: <em>largeur</em>x<em>hauteur</em>@&lsqb;<em>colonne</em>,<em>ligne</em>&rsqb;</tscreen>
+  <item>
+    Le contenu de la région d'écran sélectionnée. Les caractères
+    non-imprimables sont représentés par des espaces.
+</enum>
+
+<sect2>ttbtest<label id="utility-ttbtest"><p>
+Cet outil teste une table de texte (section <ref id="table-text" name="Tables de
+texte">).
+<tscreen>ttbtest -<em/option/ ... <em/input-table/ <em/output-table/</tscreen>
+<descrip>
+  <tag><em>table-en-entrée</em></tag>
+    Le chemin du fichier vers la table de texte en entrée du test. S'il
+    est relatif, il est ancré au répertoire configuré avec l'option de
+    compilation --with-data-directory.
+    
+  <tag><em>table-en-sortie</em></tag>  
+    Le chemin du fichier vers la table de texte en sortie du test. S'il
+    est relatif, il est ancré au répertoire de travail  courant. Si vous ne
+    fournissez pas ce paramètre, aucune table en sortie ne  sera écrite.
+  <tag><tt>-i</tt> <tt>--input-format=</tt><em>format</em></tag>
+    Spécifie le format  de la table d'entrée. S'il vous ne fournissez pas cette
+    option, le  format de la table en entrée est déduit de l'extension de son
+    fichier.
+  <tag><tt>-o</tt> <tt>--output-format=</tt><em>format</em></tag>
+    Spécifie le format  de la table en sortie. S'il vous ne fournissez pas cette
+    option, le  format de la table en entrée est déduit de l'extension de son
+    fichier.
+  <tag><tt/-c/<em/charset/ <tt/--charset=/<em/charset/</tag>
+    Spécifie le nom de l'encodage 8-bit à utiliser lors de l'interprétation des 
+    tables. Si vous ne fournissez pas cette option, le codage de l'hôte est
+    utilisé.
+  <tag><tt/-e/ <tt/--edit/</tag>
+    Appelle l'éditeur de tables de texte. Si vous spécifiez table de sortie,
+    les changements seront écrits dessus. Sinon, la table d'entrée et réécrite.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options en ligne de commande, et quitte.
+</descrip>
+
+Si vous ne demandez aucune action en particulier, la table de sortie est 
+facultative. Si vous ne la spécifiez pas, la table d'entrée est vérifiée.
+Si vous la spécifiez, la table d'entrée est convertie.
+Les formats de table suivants sont supportés:
+<descrip>
+  <tag/ttb/BRLTTY
+  <tag/sbl/SuSE Blinux
+  <tag/a2b/Gnopernicus
+  <tag/gnb/Braille Gnome
+</descrip>
+
+
+<sect2>ctbtest<label id="utility-ctbtest"><p>
+Cet outil teste une table de braille abrégé (section <ref id="table-contraction" name="Tables
+de braille abrégé">). Le texte lu à partir de l'entrée standard (stdin) est réécrit sur
+la sortie standard (stdout) en braille abrégé.
+<tscreen>ctbtest -<em>fichier-en-entrée</em></tscreen>
+<descrip>
+  <tag><em>fichier-en-entrée</em></tag>
+    La liste des fichiers à traiter. Vous pouvez spécifier n'importe quel
+    nombre de fichiers. Ils sont traités de la gauche vers la droite. Le  nom
+    de fichier spécial - est interprété comme l'entrée standard (stdin). Si
+    vous ne spécifiez aucun fichier, l'entrée standard est traitée.
+  <tag><tt>-c</tt><em>fichier</em> <tt>--contraction-table=</tt><em>fichier</em></tag>
+    Le chemin vers le fichier de la table de braille abrégé. S'il est relatif, il
+    est ancré au répertoire configuré par l'option de compilation
+    <ref id="build-data-directory" name="--with-data-directory">.
+    L'extension .ctb est facultative. Si vous ne fournissez pas cette option,
+    en-us-g2 est supposé.
+  <tag><tt/-t/<em/file/|<tt/auto/ <tt/--text-table=/<em/file/|<tt/auto/</tag>
+    Spécifie la table de texte (voir
+    la section <ref id="table-text" name="Tables de texte"> pour les détails).
+    Si vous fournissez un chemin relatif, il est ancré à
+    <tt>/etc/brltty</> (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory"> et
+    <ref id="build-execute-root" name="--with-execute-root"> pour plus de détails).
+    L'extension <tt/.ttb/ est facultative.
+    Voir la ligne <ref id="configure-text-table" name="text-table">
+    du fichier de configuration pour le paramétrage par défaut au moment de l'exécution. Vous pouvez modifier
+    ce paramètre avec la préférence 
+    <ref id="preference-text-table" name="Text Table">.
+  <tag><tt>-w</tt><em>colonnes</em> <tt>--output-width==</tt><em>colonnes</em></tag>
+    La longueur  maximale d'une ligne en sortie. Chaque ligne d'entrée en
+    braille abrégé est développée sur  autant de lignes que nécessaire. Si
+    vous ne spécifiez  pas cette option,  il n'y a  pas de limites et s'opère
+    une correspondance  ligne par  ligne entre les  lignes en entrée et celles  en
+    sortie.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options en ligne de commande, et quitte.
+</descrip>
+
+La table de texte est utilisée:
+<itemize>
+  <item>
+    Pour définir l'encodage de sortie de manière à ce que le
+    braille abrégé soit correctement affiché. Vous devriez spécifier
+    la même table devant être utilisée par BRLTTY lorsque la sortie sera lue.
+  <item>
+    Pour définir les représentations braille de ces caractères
+    définies dans le braille abrégé (voir la section
+    <ref id="contraction-opcodes-translation" name="Traduction de caractère">).
+</itemize>
+
+C'est la table de traduction de texte <tt>text.brf.ttb</tt> qui est
+fournie pour l'utilisation de cet outil. Il définit le format
+utilisé dans les fichiers .brf. C'est aussi le format que préfèrent
+utiliser les imprimantes braille et les documents braille
+électroniques. Cette table permet à cet outil d'être réellement
+utilisé en tant que traducteur de braille en texte.
+
+<sect2>tunetest<label id="utility-tunetest"><p>
+Cet outil teste la facilité des sons d'avertissement, et fournit aussi
+un moyen facile de créer de nouveaux sons. Il se peut que vous
+soyez obligés de l'exécuter en tant que root.
+<tscreen>tunetest -<em>option</em> ... {<em>note</em> <em>durée</em>} ...</tscreen>
+<descrip>
+  <tag><em>note</em></tag>
+    Un numéro de note MIDI standard. Il doit être un entier de <tt>1</tt> à <tt>127</tt>,
+    avec 60 représentant la valeur moyenne. Chaque valeur représente un
+    demi-ton chromatique standard, donc des notes les
+    plus basses aux notes les plus hautes. La valeur la plus faible (<tt>1</tt>)
+    représente le cinquième sous Middle C, et la valeur la plus haute
+    (<tt>127</tt>) représente le sixième G au-dessus de Middle C (notation anglosaxonne).
+  <tag><em>durée</em></tag>
+    La durée de la note est en millisecondes. Elle doit être un
+    entier de <tt>1</tt> à <tt>255</tt>..
+  <tag><tt>-d</tt><em>périphérique</em> <tt>--device=</tt><em>périphérique</em></tag>
+    Le périphérique sur lequel jouer le son.
+    <descrip>
+      <tag>beeper</tag>
+        Le beeper interne (générateur de sons en console).
+      <tag>pcm</tag>
+        L'interface digital audio sur la carte son.
+      <tag>midi</tag>
+        L'interface numérique d'instrument de musique sur la carte
+        son.
+      <tag>fm</tag>
+        Le synthétiseur FM sur une carte son AdLib, OPL3, Sound
+        Blaster, ou équivalente.
+    </descrip>
+    Vous pouvez abréger le nom du périphérique. Voir la préférence
+    <ref id="preference-tune-device" name="Tune Device"> pour plus de détails concernant le périphérique
+    par défaut et les restrictions de la plateforme.
+  <tag><tt>-v</tt><em>volume</em> <tt>--volume=</tt><em>volume</em></tag>
+    Spécifie le volume à la sortie (intensité) sous la forme d'un
+    pourcentage du maximum. Le volume de sortie par défaut est de <tt>50</tt>.
+  <tag><tt>-p</tt><em>device</em> <tt>--pcm-device=</tt><em>device</em></tag>
+    Spécifie le périphérique à utiliser pour le son (voir la
+    section <ref id="operand-pcm-device" name="spécification du périphérique PCM">). Cette option ne
+    fonctionne pas si vous avez spécifié l'option de compilation 
+    <ref id="build-pcm-support" name="--disable-pcm-support">.
+  <tag><tt/preferences-file/ <em/file/<label id="configure-preferences-file"></tag>
+    Spécifie l'emplacement du fichier qui doit être utilisé pour sauvegarder
+    et charger les préférences de l'utilisateur. Si vous fournissez un chemin
+    relatif, il est ancré sur <tt>/var/lib/brltty</>. Le réglage par défaut
+    consiste à utiliser <tt/brltty.prefs/. Vous pouvez outre-passer cette
+    ligne avec l'option <ref id="options-preferences-file" name="-F"> en
+    ligne de commande.
+  <tag><tt>-m</tt><em>peripherique</em> <tt>--midi-device=</tt><em>peripherique</em></tag>
+    Spécifie le périphérique à utiliser pour l'interface numérique d'instrument
+    de musique (voir la section <ref id="operand-midi-device" name="spécification du
+    périphérique MIDI">). Cette option ne fonctionne pas si vous avez
+    spécifié l'option de compilation <ref id="build-midi-support" name="--disable-midi-support">.
+  <tag><tt>-i</tt><em>instrument</em> <tt>--instrument=</tt><em>instrument</em></tag>
+    L'instrument à utiliser si le périphérique sélectionné est
+    midi. Pour la liste complète des instruments, voir la <ref id="operand-midi-device" name="Table des
+    instrument MIDI">. L'instrument par défaut est un <tt>piano grand
+    accoustique</tt>. Les mots comportant le nom de l'instrument doivent
+    être séparés les uns des autres par un simple signe moins plutôt
+    que par des espaces, et chacun des mots peut être abrégé.
+  <tag><tt>-h</tt> <tt>--help</tt></tag>
+    Affiche un résumé des options de la ligne de commande.
+</descrip>
diff --git a/Documents/Manual-BRLTTY/French/Displays.sgml b/Documents/Manual-BRLTTY/French/Displays.sgml
new file mode 100644
index 0000000..24b27f5
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Displays.sgml
@@ -0,0 +1,8 @@
+<sect>Afficheurs braille supportés<label id="displays"><p>
+BRLTTY supporte les afficheurs braille suivants:
+<table loc="h">
+  <tabular ca="ll">
+    Nom|Modèles@<hline>
+    &BrailleDrivers
+  </tabular>
+</table>
diff --git a/Documents/Manual-BRLTTY/French/Dots.sgml b/Documents/Manual-BRLTTY/French/Dots.sgml
new file mode 100644
index 0000000..e06ac53
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Dots.sgml
@@ -0,0 +1,27 @@
+<sect>Convention standard du nombre de points brailles<label id="dots"><p>
+Une cellule braille standard consiste en six points répartis en trois lignes
+et deux colonnes.
+Chaque point peut être identifié par son numéro, comme suit:
+<descrip>
+  <tag>1</tag>En haut à gauche (ligne 1, colonne 1).
+  <tag>2</tag>Milieu gauche (ligne 2, colonne 1).
+  <tag>3</tag>Bas gauche (ligne 3, colonne 1).
+  <tag>4</tag>En haut droite (ligne 1, colonne 2).
+  <tag>5</tag>Milieu droite (ligne 2, colonne 2).
+  <tag>6</tag>Bas droite (ligne 3, colonne 2).
+</descrip>
+
+Le braille informatique a introduit une quatrième rangée en bas.
+<descrip>
+  <tag>7</tag>Dessous gauche (ligne 4, colonne 1).
+  <tag>8</tag>Dessous droite (ligne 4, colonne 2).
+</descrip>
+
+Peut-être qu'une image rendra cette convention numérique plus facile à
+comprendre.
+<tscreen><verb>
+1 o o 4
+2 o o 5
+3 o o 6
+7 o o 8
+</verb></tscreen>
diff --git a/Documents/Manual-BRLTTY/French/Drivers.sgml b/Documents/Manual-BRLTTY/French/Drivers.sgml
new file mode 100644
index 0000000..9ed74a3
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Drivers.sgml
@@ -0,0 +1,7 @@
+<sect>Codes d'identification de pilote<label id="drivers"><p>
+<table loc="h">
+  <tabular ca="ll">
+    Code|Name@<hline>
+    &DriverCodes
+  </tabular>
+</table>
diff --git a/Documents/Manual-BRLTTY/French/Features.sgml b/Documents/Manual-BRLTTY/French/Features.sgml
new file mode 100644
index 0000000..c92ae8b
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Features.sgml
@@ -0,0 +1,1148 @@
+<sect>Description des possibilités<p>
+
+<sect1>Routine curseur<label id="routing"><p>
+Lorsque vous déplacez la plage braille sur l'écran en examinant le texte dans 
+un éditeur, vous avez souvent besoin d'amener le curseur à un caractère en 
+particulier à l'intérieur de la plage braille.
+Vous trouverez que c'est une tâche plutôt difficile pour un certain nombre de
+raisons. L'une d'entre elles est qu'il se peut que vous ne sachiez pas où se
+trouve le curseur, et vous avez peut-être perdu votre position en essayant de le
+trouver. 
+Une autre est que le curseur peut se déplacer de façon imprévisible lorsque
+vous appuyez sur les flèches de direction (certains éditeurs, par exemple,
+n'autorisent pas le curseur à aller plus à droite que la fin de la ligne où il
+se trouve).
+La routine-curseur fournit cette possibilité en sachant où se trouve le curseur,
+en simulant les appuis sur les flèches de direction que vous devriez
+entrer manuellement, et en affichant l'évolution du curseur pendant qu'il se
+déplace.
+
+Certains afficheurs braille ont un bouton, appelé routine-curseur, au-dessus de
+chaque cellule. Ces touches utilisent la commande
+<ref id="command-ROUTE" name="ROUTE"> pour amener le curseur précisément à 
+l'emplacement désiré.
+
+La routine-curseur, bien que très pratique et très efficace, n'est pas,
+à proprement parler, totalement fiable. Une des raisons de cela est que sa
+conception actuelle utilise des séquences d'échappement de touches du curseur
+VT100.
+
+Une autre est que certaines applications font des choses non standards pour
+réagir lorsqu'elles détectent l'appui sur les touches du curseur.
+Un problème mineur trouvé à l'intérieur de certains éditeurs (comme <tt>vi</tt>),
+comme mentionné ci-dessus, est qu'ils se précipitent dans un déplacement
+vertical imprévisible lorsque vous demandez un déplacement vertical, car elles
+n'autorisent pas le curseur à se placer à droite de la fin d'une ligne.
+Un problème majeur trouvé dans certains navigateurs web (comme
+<tt>lynx</tt>), est que les flèches haut et bas sont utilisées pour se déplacer
+parmi les liens (ce qui peut sauter des lignes et/ou déplacer horizontalement
+le curseur mais rarement déplacer le curseur d'une ligne dans la direction
+désirée), et que les flèches gauche et droite sont utilisées pour sélectionner
+les liens (ce qui n'a absolument rien à voir avec une quelconque forme de
+déplacement de curseur, et qui change même totalement le contenu de l'écran).
+
+Il se peut que la routine curseur ne fonctionne pas très bien sur les systèmes
+lourds à se charger, et elle ne fonctionnera certainement pas très bien
+lorsqu'elle tournera sous un vieux système ayant des liaisons lentes. Il en est
+ainsi car tous les tests qui doivent être faits pendant le processus de façon à
+traiter les mouvements imprévisibles du curseur et afin de s'assurer
+qu'une erreur a au moins une chance d'être corrigée.
+Bien que BRLTTY s'efforce d'être assez intelligent, il doit attendre encore
+de voir ce qu'il se passe après chaque appui simulé sur une flèche de direction. 
+
+Une fois qu'une demande de routine-curseur a été faite, BRLTTY essaie d'amener
+le curseur à la position désirée durant un certain délai avant
+que le curseur n'atteigne cette position, le curseur semblant se déplacer dans la
+mauvaise direction, ou vous passez à un terminal différent.
+En premier, un effort est fait pour utiliser le déplacement vertical pour
+amener le curseur à la bonne ligne, puis, uniquement si cela réussit, un effort
+est fait pour utiliser le déplacement horizontal pour amener le curseur à la
+bonne colonne. Si vous faites une autre demande alors qu'une routine est en train de
+s'effectuer, la première est annulée et la deuxième est démarrée. 
+
+Une commande de routine curseur plus sûre mais moins puissante, 
+<ref id="command-CSRJMP_VERT" name="CSRJMP_VERT">, utilise le déplacement
+vertical pour amener le curseur n'importe où sur la première ligne de la
+plage braille. Elle est surtout utile, jointe à certaines applications
+(comme <tt>lynx</tt>), dans lesquelles le déplacement du curseur à l'horizontal
+ne doit jamais être tenté.
+
+<sect1>Copier-coller<label id="cut"><p>
+Cette possibilité vous permet d'extraire un texte qui est déjà sur l'écran
+et de le réentrer à la position courante du curseur. Son utilisation vous fait
+gagner du temps et permet d'éviter les erreurs lorsque vous avez besoin de copier un
+texte long et/ou compliqué, et même quand vous avez besoin de copier plusieurs
+fois le même texte court et simple. Elle est particulièrement utile pour des
+choses telles que les noms de fichier longs, les lignes de commande compliquées,
+les adresses mail, et les URLS. Copier et coller du texte passe par trois
+étapes simples:
+<enum>
+  <item>
+    Marquer le coin en haut à gauche de la zone rectangulaire ou le début de la
+    zone linéaire sur l'écran, qui sera extraite (copiée). Si votre afficheur a
+    des routines-curseur, déplacez la plage braille de façon à ce que le
+    premier caractère à copier apparaisse quelque part à l'intérieur, puis:
+    <itemize>
+      <item>
+        appelez la commande <ref id="command-CUTBEGIN" name="CUTBEGIN"> pour
+	démarrer un nouveau tampon
+      <item>
+        appelez la commande <ref id="command-CUTAPPEND" name="CUTAPPEND"> pour
+	marquer le tampon copié existant.
+    </itemize>
+    en pressant la/les touche(s) qui y sont associée(s), puis en appuyant sur la
+    routine-curseur associée au caractère
+  <item>
+    Marquer le coin en bas à droite de la zone rectangulaire ou la fin de
+    la zone linéaire sur l'écran qui doit être extraite (copiée).
+    Si votre afficheur a des routines-curseur, déplacez la plage braille
+    de telle sorte que le dernier caractère à copier apparaisse quelque part
+    à l'intérieur, puis
+    <itemize>
+      <item>
+        appelez la commande <ref id="command-CUTRECT" name="CUTRECT"> pour
+	copier une zone rectangulaire.
+      <item>
+        appelez la commande <ref id="command-CUTLINE" name="CUTLINE"> pour
+	copier une zone linéaire.
+    </itemize>
+    en pressant les/la touche(s) associée(s) puis en pressant la routine-curseur
+    associée au caractère. Le marquage
+    de la fin de la région copiée dépose le contenu d'un écran copié dans le
+    presse-papier.
+    Les espaces superflus sont supprimés en fin de chaque ligne dans le
+    presse-papier de façon à ce que les espaces non désirés qui en découlent ne
+    soient pas collés.
+    Les caractères de contrôle sont remplacés par des espaces.
+  <item>
+    Insérez (coller) le texte là où vous avez besoin. Placez le curseur après le
+    caractère où le texte doit être inséré, et appelez la commande
+    <ref id="command-PASTE" name="PASTE">. Vous pouvez copier le même texte
+    autant de fois que vous le voulez sans le copier à nouveau.
+    Cette description suppose que vous êtes déjà dans un mode d'entrée. Si vous
+    collez alors que vous êtes dans un autre type de mode (comme le
+    mode commande de <tt>vi</tt>), vous devez alors bien être conscient de ce que
+    les caractères du presse-papier pourront faire.
+    </enum>
+
+Le tampon copié est utilisé aussi par les commandes
+<ref id="command-PRSEARCH-NXSEARCH" name="PRSEARCH/NXSEARCH">.
+
+<sect1>Support du pointeur (souris) via GPM<label id="gpm"><p>
+Si BRLTTY est configuré avec l'option de compilation
+<ref id="gpm" name="--enable-gpm"> sur un système où l'application
+<tt>gpm</tt> a été installée, il réagira au pointeur (souris).
+
+Le fait de bouger la plage braille déplace le pointeur (voir la préférence
+<ref id="preference-pointer-follows-window" name="Le pointeur suit la fenêtre">).
+Le déplacement de la plage braille (manuel, par recherche du curseur etc.),
+est appliqué non seulement quand il se déplace en réponse à un mouvement du pointeur, mais il
+déplace aussi le pointeur au caractère à l'écran qui correspond au coin en haut à
+gauche de la plage braille. Cela permet à une personne voyante de voir où se
+trouve la plage braille et donc, de savoir ce que l'utilisateur brailliste
+est en train de lire. Cela récupère aussi le pointeur dans la plage
+braille de sorte que vous pouvez le
+trouver facilement et que le périphérique du pointeur peut toujours être
+utilisé comme un autre moyen de déplacer la plage braille. 
+
+Le déplacement du pointeur emmène la plage braille (voir la préférence
+<ref id="preference-window-follows-pointer" name="La fenêtre suit le pointeur">).
+Chaque fois que vous déplacez le pointeur au-delà du bord de la plage
+braille, la plage braille est emmenée tout au long du déplacement (un
+caractère à la fois).
+Cela donne à l'utilisateur braille une autre manière en deux dimensions
+d'inspecter le contenu de l'écran ou de déplacer rapidement la plage braille
+à un endroit désiré. Cela donne aussi à l'utilisateur voyant une façon
+simple de déplacer la plage braille sur quelque chose qu'il aimerait que
+l'utilisateur brailliste lise.
+
+<tt>gpm</tt> utilise la vidéo inversée pour montrer où se trouve le pointeur.
+Le soulignement des caractères en surbrillance devrait donc être activé (voir
+la commande <ref id="command-ATTRVIS" name="ATTRVIS"> pour des  détails) que 
+quand l'utilisateur brailliste veut utiliser le pointeur.
+
+Cette fonctionnalité donne aussi à l'utilisateur brailliste accès à la
+fonction copier-coller de <tt>gpm</tt>. Bien que vous devriez lire la documentation
+spécifique de <tt>gpm</tt>, voici quelques remarques sur son fonctionnement.
+<itemize>
+  <item>
+    Copiez le caractère courant dans le presse-papier par un simple clic sur le
+    bouton gauche.
+  <item>
+    Copiez le mot courant (délimité par un espace) dans le presse-papier en
+    cliquant deux fois sur le bouton gauche.
+  <item>
+    Copiez la ligne courante dans le presse papier en cliquant trois fois sur
+    le bouton gauche.
+  <item>
+    Copiez une région linéaire vers le presse-papier comme suit:
+    <enum>
+      <item>
+        Placez le curseur sur le premier caractère de la région.
+      <item>
+        Pressez (et maintenez) le bouton gauche.
+      <item>
+        Déplacez le curseur au dernier caractère de la région (tous les
+	caractères sélectionnés sont maintenant en surbrillance).
+      <item>
+        Lâchez le bouton gauche.
+    </enum>
+  <item>
+    Collez (insérez) le contenu courant du presse papier en cliquant sur le
+    bouton central d'une souris à trois boutons, ou en cliquant sur le bouton
+    droit d'une souris à deux boutons.
+  <item>
+    Marquez le presse-papier en utilisant le bouton droit d'une souris à trois
+    boutons.
+</itemize>
+
+<sect1>Sons d'avertissement<label id="tunes"><p>
+BRLTTY vous avertit de l'exécution d'événements significatifs en jouant un son
+bref prédéfini. Cette fonctionalité peut être activée et désactivée avec la
+commande
+<ref id="command-TUNES" name="TUNES"> ou la préférence
+<ref id="preference-alert-tunes" name="Sons d'avertissement">.
+Les sons sont joués par le synthétiseur interne par défaut, mais vous pouvez
+sélectionner d'autres alternatives avec la préférence
+<ref id="preference-tune-device" name="Périphérique pour les sons">.
+
+Chaque événement significatif est associé, de la priorité la plus haute à la
+plus faible, à un ou plusieurs des éléments suivants:
+<descrip>
+  <tag>un son</tag>
+    Si vous avez associé un nom à l'événement, si la préférence
+    <ref id="preference-alert-tunes" name="Sons d'avertissement"> (voir aussi la
+    commande <ref id="command-TUNES" name="TUNES">) est active, et si le
+    périphérique de son sélectionné (voir la préférence <ref id="preference-tune-device" name="Périphérique de son">)
+    peut être ouvert, le son est joué.
+  <tag>un schéma de points</tag>
+    Si un type de signe a été associé à un événement, et si la préférence
+    <ref id="preference-alert-dots" name="Points d'avertissement"> est active, le signe
+    est brièvement affiché sur chaque cellule braille.
+    Certains afficheurs braille ne réagissent pas assez vite pour que ce
+    système fonctionne efficacement.
+  <tag>un message</tag>
+    Si un message a été associé à l'événement, et si la préférence
+    <ref id="preference-alert-messages" name="Messages d'avertissement"> est active,
+    il est affiché pendant quelques secondes (voir l'option <ref id="options-message-timeout" name="-M">
+    en ligne de commande.
+</descrip>
+
+Ces événements incluent:
+<itemize>
+  <item>
+    Le moment où le pilote de l'afficheur braille démarre ou s'arrête.
+  <item>
+    Le moment où une commande longue est terminée.
+  <item>
+    Le moment où une commande peut être exécutée.
+  <item>
+    Le moment où un repère est posé.
+  <item>
+    Le moment où le début ou la fin du presse-papier est posé.
+  <item>
+    Le moment où une caractéristique est activée ou désactivée.
+  <item>
+    Le moment où la poursuite du curseur est activé ou désactivé.
+  <item>
+    Le moment où l'image de l'écran est gelée ou rafraîchie.
+  <item>
+    Le moment où la plage braille entraîne la plage braille en bas au début de la
+    ligne suivante, ou en haut à la fin de la ligne précédente.
+  <item>
+    Le moment où des lignes identiques sont sautées.
+  <item>
+    Le moment où un déplacement demandé ne peut pas être effectué.
+  <item>
+    Le moment où la routine-curseur commence, s'achève ou échoue.
+</itemize>
+
+<sect1>Paramètres de préférence<label id="preferences"><p>
+Quand BRLTTY démarre, il charge un fichier qui contient vos paramètres de
+préférence. Il n'est pas indispensable que le fichier existe, et il est créé la
+première fois que les paramètres sont sauvegardés avec la commande
+<ref id="command-PREFSAVE" name="PREFSAVE">.
+Les paramètres sauvegardés le plus récemment peuvent être restaurés n'importe
+quand par la commande <ref id="command-PREFLOAD" name="PREFLOAD">.
+
+Le nom de ce fichier est <tt>/etc/brltty-</tt><em>pilote</em><tt>.prefs</tt>.
+où <em>pilote</em> correspond aux deux lettres du
+<ref id="drivers" name="code d'identification de pilote">.
+
+<sect2>Le menu préférences<label id="preferences-menu"><p>
+Les paramètres de préférence sont sauvegardés sous forme de données binaires
+que vous ne pouvez donc pas éditer à la main.
+Cependant, BRLTTY a un menu simple à partir duquel vous pouvez facilement les
+changer.
+
+le menu est activé par la commande <ref id="command-PREFMENU" name="PREFMENU">.
+L'afficheur braille affiche brièvement (voir l'option
+<ref id="options-message-timeout" name="-M"> en ligne de commande) le titre du
+menu, puis présente l'item du paramètre actuel.
+
+<Sect3>Navigation dans le menu<p>
+Voir <ref id="menu-navigation" name="Commandes de navigation dans le menu"> pour la
+liste des commandes qui vous permettent de sélectionner l'élément, et de changer
+la valeur dans le menu.
+Par souci de compatibilité avec les vieux pilotes, les commandes
+de déplacement dans la fenêtre, qui ont changé de sens dans ce contexte,
+peuvent être aussi utilisées.
+<descrip>
+  <tag><tt>Début</tt>/<tt>Fin</tt>, <tt>Haut-Gauche</tt>/<tt>Bas-Gauche</tt>, <tt>PAGE_PRECEDENTE</tt>/<tt>PAGE_SUIVANTE</tt></tag>
+    Va au premier/dernier élément du menu (comme 
+    <ref id="command-MENU_FIRST_ITEM-MENU_LAST_ITEM" name="MENU_FIRST_ITEM/MENU_LAST_ITEM">).
+  <tag><tt>FH</tt>/<tt>FB</tt>, <tt>LIGNPRECEDENTE</tt>/<tt>LNSUIV</tt>, <tt>CURSEUR_HAUT</tt>/<tt>CURSEUR_BAS</tt></tag>
+    Va à l'élément précédent/suivant du menu (comme
+    <ref id="command-MENU_PREV_ITEM-MENU_NEXT_ITEM" name="MENU_PREV_ITEM/MENU_NEXT_ITEM">).
+  <tag><tt>FENETRE_PRECEDENTE</tt>/<tt>FENSUIV</tt>, <tt>CARGAUCH</tt>/<tt>CADROITE</tt>, <tt>CURSEUR_GAUCHE</tt>/<tt>CURSEUR_DROITE</tt>, <tt>DEBUT</tt>/<tt>HOME</tt></tag>
+    Déroule ou "enroule" la valeur de l'élément actuel dans le menu (comme
+    <ref id="command-MENU_PREV_SETTING-MENU_NEXT_SETTING" name="MENU_PREV_SETTING/MENU_NEXT_SETTING">).
+</descrip>
+
+Remarques:
+<itemize>
+  <item>
+    Vous pouvez aussi utiliser les routine-curseur pour sélectionner une valeur
+    pour l'élément actuel. Si un élément a des valeurs numériques, toute la ligne
+    de routines-curseur agit comme une barre de défilement qui couvre toute la
+    gamme des valeurs valides. Si un élément a une valeur nominale,
+    les routines-curseur correspondent normalement aux valeurs.
+  <item>
+    Utilisez la commande <tt>PREFLOAD</tt> pour annuler tous les changements
+    qui ont été faits depuis que vous êtes dans le menu.
+  <item>
+    Utilisez la commande <tt>PREFMENU</tt> (encore) pour donner effet
+    aux nouvelles valeurs, sortir du menu et faire des
+    opérations normales. De plus, si vous avez sélectionné l'option <tt>Enregistrer en quittant</tt>, les
+    nouvelles valeurs sont sauvegardées dans le fichier des préférences.
+    Toute commande non reconnue par le système du menu fait la même chose.
+</itemize>
+
+<sect3>Les éléments du menu<p>
+<descrip>
+  <tag>Enregistrer en quittant<label id="preference-save-on-exit"></tag>
+      Lors de la sortie du menu de préférences:
+    <descrip>
+      <tag>Non</tag>
+        Ne sauvegarde pas automatiquement les paramètres de préférence.
+      <tag>Oui</tag>
+        Sauvegarde automatiquement les paramètres de préférence.
+    </descrip>
+    Le paramètre par défaut est <tt>Non</tt>.
+  <tag>Apparence du texte<label id="preference-text-style"></tag>
+    Lors de l'affichage du contenu de l'écran (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">), montre les caractères:
+    <descrip>
+      <tag>8 points</tag>
+        Avec les huit points.
+      <tag>6 points</tag>
+        Avec 6 points seulement.
+	Si vous avez sélectionné une table de braille abrégé (voir l'option
+	<ref id="options-contraction-table" name="-c"> en ligne de commande et
+	la ligne <ref id="configure-contraction-table" name="contraction-table">
+	du fichier de configuration), elle est utilisée.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre par la commande
+    <ref id="command-SIXDOTS" name="SIXDOTS">.
+  <tag>Passer les lignes identiques<label id="preference-skip-identical-lines"></tag>
+    Quand vous vous déplacez de ligne en ligne en haut ou en bas avec les
+    commandes <ref id="command-LNUP-LNDN" name="LNUP/LNDN">,
+    et lors de la fonction de défilement de lignes des commandes 
+    <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> et
+    <ref id="command-FWINLTSKIP-FWINRTSKIP" name="FWINLTSKIP/FWINRTSKIP">:
+    <descrip>
+      <tag>Non</tag>
+        Ne passe pas les lignes qui ont le même contenu que la ligne actuelle.
+      <tag>Oui</tag>
+        Passe les lignes déjà vues qui ont le même contenu que la ligne actuelle.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-SKPIDLNS" name="SKPIDLNS">.
+  <tag>Passer les lignes vierges<label id="preference-skip-blank-windows"></tag>
+    Lors d'un déplacement à gauche ou à droite avec les commandes
+    <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT">:
+    <descrip>
+      <tag>Non</tag>
+        Ne passe pas les fenêtres vides déjà lues.
+      <tag>Oui</tag>
+        Passe les fenêtres vides.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-SKPBLNKWINS" name="SKPBLNKWINS">.
+  <tag>Quelles fenêtres vierges<label id="preference-which-blank-windows"></tag>
+    Si les fenêtres vides doivent être sautées:
+    <descrip>
+      <tag>Toutes</tag>
+        Les passe toutes.
+      <tag>Fin de ligne</tag>
+        Ne passe que celles qui sont à la fin (sur le côté droit) de l'écran.
+      <tag>Reste de la ligne</tag>
+        Ne passe que celles qui sont à la fin (sur le côté droit) d'une ligne lors d'une lecture
+	vers l'avant, et au début (sur le côté gauche) d'une ligne lors d'une
+	lecture à reculons.
+    </descrip>
+  <tag>Faire défiler la fenêtre<label id="preference-sliding-window"></tag>
+    Si le curseur est poursuivi (voir la commande
+    <ref id="command-CSRTRK" name="CSRTRK">)
+    et que le curseur est trop enfermé (ou trop à l'extérieur) à la fin d'une plage braille:
+    <descrip>
+      <tag>Non</tag>
+        Repositionne la plage horizontalement de sorte que son bord gauche
+	soit un multiple de sa largeur à partir du bord gauche de l'écran.
+      <tag>Oui</tag>
+        Repositionne la plage horizontalement de façon à ce que le curseur,
+	tout en restant sur ce côté de la plage, soit plus proche du centre.	
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-SLIDEWIN" name="SLIDEWIN">.
+  <tag>Eager Sliding Window<label id="preference-eager-sliding-window"></tag>
+    Si la plage braille doit glisser:
+    <descrip>
+      <tag>Non</tag>
+        La repositionne chaque fois que le curseur va au-delà de la fin.
+      <tag>Oui</tag>
+        La repositionne chaque fois que le curseur va trop à l'intérieur près
+	de la fin.
+    </descrip>
+    Le paramètre initial est à <tt>non</tt>.
+  <tag>Chevauchement de fenêtre<label id="preference-window-overlap"></tag>
+    Lors d'un déplacement à gauche ou à droite avec les commandes
+    <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT">, ce paramètre
+    spécifie de combien de caractères adjacents horizontalement doit
+    se couvrir la plage braille.
+    Le paramètre initial est <tt>0</tt>.
+  <tag>Répétition automatique<label id="preference-autorepeat"></tag>
+    Tandis que la touche (la combinaison) d'une commande reste appuyée:
+    <descrip>
+      <tag>Non</tag>
+        Ne répète pas automatiquement la commande.
+      <tag>Oui</tag>
+        Répète automatiquement la commande selon un intervalle régulier après
+	un délai initial.
+    </descrip>
+    Les commandes suivantes peuvent être répétées automatiquement:
+    <itemize>
+      <item>
+        Les commandes <ref id="command-LNUP-LNDN" name="LNUP/LNDN">.
+      <item>
+        Les commandes <ref id="command-PRDIFLN-NXDIFLN" name="PRDIFLN/NXDIFLN">.
+      <item>
+        Les commandes <ref id="command-CHRLT-CHRRT" name="CHRLT/CHRRT">.
+      <item>
+        Opérations de défilement automatique de la plage braille (voir
+        la préférence "Répétition automatique du défilement").
+      <item>
+        Les opérations Page Précédente Page suivante.
+      <item>
+        Les opérations curseur sur ligne précédente et ligne suivante.
+      <item>
+        Les opérations curseur à gauche et curseur à droite.
+      <item>
+        Les opérations Effacer et Supprimer.
+      <item>
+        L'entrée d'un caractère.
+    </itemize>
+    Seuls certains pilotes supportent cette fonctionnalité, la première
+    limite étant que beaucoup d'afficheurs braille ne signalent pas
+    les appuis sur une touche et les fonctions d'une touche comme des
+    événements distinctement séparés.
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-AUTOREPEAT" name="AUTOREPEAT">.
+    Le paramètre initial est <tt>oui</tt>.
+  <tag>Répétition automatique du défilement<label id="preference-autorepeat-delay"></tag>
+    Quand la préférence "Répétition automatique" est activée:
+    
+    <descrip>
+      <tag>Non</tag>
+        Ne répète pas automatiquement les opérations de défilement  de la
+        plage braille.
+      <tag>Oui</tag>
+        Répète automatiquement les opérations de défilement de la plage 
+        braille.
+    </descrip>
+    
+    Ce paramètre modifie le comportement des commandes "FWINLT/FWINRT". Le
+    paramètre initial est <tt>non</tt>.
+
+  <tag>Délai de la répétition automatique<label id="preference-autorepeat-delay"></tag>
+    Lorsqu'un caractère doit être répété automatiquement, ce paramètre spécifie la valeur
+    de le délai (voir la remarque sur <ref id="time-settings" name="paramètres de temps"> ci-dessous)
+    qui doit s'écouler avant de commencer la répétition automatique.
+    Le paramètre initial est <tt>50</tt>.
+  <tag>Intervalle de la répétition automatique<label id="preference-autorepeat-interval"></tag>
+    Lorsqu'un caractère doit être répété automatiquement, ce paramètre spécifie la valeur
+    de temps (voir la remarque à propos de <ref id="time-settings" name="paramètres de temps"> ci-dessous)
+    entre chaque réexécution.
+    La valeur initiale est <tt>10</tt>.
+  <tag>Afficher le curseur<label id="preference-show-cursor"></tag>
+    Lors de l'affichage du contenu de l'écran (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">):
+    <descrip>
+      <tag>Non</tag>
+        N'affiche pas le curseur.
+      <tag>Oui</tag>
+        Affiche le curseur.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-CSRVIS" name="CSRVIS">.
+    La valeur initiale est <tt>Oui</tt>.
+  <tag>Apparence du curseur<label id="preference-cursor-style"></tag>
+    Lorsque le curseur est affiché, il faut le représenter:
+    <descrip>
+      <tag>Souligné</tag>
+        (souligné) Avec les points 7 et 8.
+      <tag>Pavé</tag>
+      (un pavé) Avec les huit points.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-CSRSIZE" name="CSRSIZE">.
+  <tag>Clignotement du curseur<label id="preference-blinking-cursor"></tag>
+    Lorsque le curseur doit être affiché:
+    <descrip>
+      <tag>Non</tag>
+        Le rend visible tout le temps.
+      <tag>YOui</tag>
+        Le rend alternativement visible et invisible selon un intervalle
+	prédéfini.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-CSRBLINK" name="CSRBLINK">.
+  <tag>Durée de visibilité du curseur<label id="preference-cursor-visible-time"></tag>
+    Quand le curseur doit clignoter, ce paramètre spécifie la durée (voir
+    la remarque à propos des <ref id="time-settings" name="paramètres de temps">
+    ci-dessous) pendant laquelle il est visibile pendant chaque cycle.
+    La valeur par défaut est <tt>40</tt>.
+  <tag>Durée d'invisibilité du curseur<label id="preference-cursor-invisible-time"></tag>
+    Lorsque le curseur doit clignoter, ce paramètre spécifie la durée (voir
+    la remarque à propos des <ref id="time-settings" name="paramètres de temps">
+    ci-dessous) pendant laquelle il est invisibile pendant chaque cycle.
+    La valeur initiale est <tt>40</tt>.
+  <tag>Afficher les attributs<label id="preference-show-attributes"></tag>
+    Lors de l'affichage du contenu de l'écran (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">):
+    <descrip>
+      <tag>Non</tag>
+        Ne souligne pas les caractères en surbrillance.
+      <tag>Oui</tag>
+        Souligne les caractères en surbrillance.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-ATTRVIS" name="ATTRVIS">.
+  <tag>Clignottement des attributs<label id="preference-blinking-attributes"></tag>
+    Lorsque les caractères en surbrillance doivent clignoter:
+    <descrip>
+      <tag>Non</tag>
+        Laisse l'indicateur visible tout le temps.
+      <tag>Oui</tag>
+        Rend l'indicateur alternativement visible et invisible selon un
+	intervalle prédéfini.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-ATTRBLINK" name="ATTRBLINK">.
+  <tag>Durée de visibilité des attributs<label id="preference-attributes-visible-time"></tag>
+    Quand le soulignement des caractères en surbrillance doit clignoter,
+    ce paramètre spécifie la durée (voir la remarque à propos des
+    <ref id="time-settings" name="paramètres de temps"> ci-dessous) pendant 
+    laquelle il est visibile pendant chaque cycle.
+    La valeur initiale est <tt>20</tt>.
+  <tag>Durée d'invisibilité des attributs<label id="preference-attributes-invisible-time"></tag>
+    Quand le soulignement des caractères en surbrillance doit clignoter,
+    ce paramètre spécifie la durée (voir la remarque à propos des
+    <ref id="time-settings" name="paramètres de temps"> ci-dessous) pendant 
+    laquelle il est invisibile pendant chaque cycle.
+    La valeur initiale est <tt>60</tt>.
+  <tag>Clignottement des majuscules<label id="preference-blinking-capitals"></tag>
+    Lors de l'affichage du contenu de l'écran (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">):
+    <descrip>
+      <tag>Non</tag>
+        Laisse les lettres en majuscule visibles tout le temps.
+      <tag>Oui</tag>
+        Rend les lettres en majuscule alternativement visibles et invisibles
+	selon un intervalle prédéfini.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-CAPBLINK" name="CAPBLINK">.
+  <tag>Durée de visibilité des majuscules<label id="preference-capitals-visible-time"></tag>
+    Lorsque les lettres en majuscule doivent clignoter, ce paramètre
+    spécifie la durée (voir la remarque à propos des
+    <ref id="time-settings" name="paramètres de temps"> ci-dessous) pendant 
+    laquelle elles sont visibile pendant chaque cycle.
+    La valeur par défaut est <tt>60</tt>.
+  <tag>Durée d'invisibilité des majuscules<label id="preference-capitals-invisible-time"></tag>
+    Lorsque les lettres en majuscule doivent clignoter, ce paramètre
+    spécifie la durée (voir la remarque à propos des 
+    <ref id="time-settings" name="paramètres de temps"> ci-dessous) pendant 
+    laquelle elles sont invisibiles pendant chaque cycle.
+    La valeur par défaut est <tt>20</tt>.
+  <tag>Rugosité du braille<label id="preference-braille-firmness"></tag>
+    Règle la rugosité (ou la rigidité) des points braille.
+    Elle peut être réglée à:
+    <itemize>
+      <item>Maximum
+      <item>Forte
+      <item>Moyenne
+      <item>Faible
+      <item>Minimum
+    </itemize>
+    Cette préférence n'est disponible que si vous utilisez un pilote qui
+    la supporte. La valeur initiale est <tt>Moyenne</tt>.
+  <tag>Sensibilité du braille<label id="preference-braille-firmness"></tag>
+    Règle la sensibilité des points braille au teucher.
+    Elle peut être réglée à:
+    <itemize>
+      <item>Maximum
+      <item>Haute
+      <item>Moyenne
+      <item>Faible
+      <item>Minimum
+    </itemize>
+    Cette préférence n'est disponible que si vous utilisez un pilote qui
+    la supporte. La valeur initiale est <tt>Moyenne</tt>.
+
+  <tag>La fenêtre suit le pointeur<label id="preference-window-follows-pointer"></tag>
+    Lors du déplacement du pointeur (souris):
+    <descrip>
+      <tag>Non</tag>
+        N'emmène pas la plage braille.
+      <tag>Oui</tag>
+        Emmène la plage braille.
+    </descrip>
+    Cette préférence n'est présentée que si l'option de compilation
+    <ref id="gpm" name="--enable-gpm"> a été spécifiée.
+  <tag>Surlignement de la fenêtre<label id="preference-pointer-follows-window"></tag>
+    Lors du déplacement de la plage braille:
+    <descrip>
+      <tag>Non</tag>
+        Ne met pas en surbrillance la nouvelle zone de l'ùcran.
+      <tag>Oui</tag>
+        Cette option active un marqueur visible montrant où se situe la
+        plage braille, et, par conséquent, permettant de savoir ce que
+        l'utilisateur brailliste est en train de lire. Tout mouvement de la
+        plage braille (manuel, poursuite du curseur, etc.) autre que ceux
+        répondant au mouvement du pointeur (voir la préférence 
+        <ref id="preference-window-follows-pointer" name="La fenêtre suit le pointeur">)
+        a pour conséquence que la zone de l'écran correspondant
+        au nouvel endroit où se trouve la  plage braille est mise en
+        surbrillance. Si la préférence "Afficher les attributs" est activée, seul le
+        caractère correspondant au coin en haut à gauche de la plage braille
+        est mis en surbrillance.
+    </descrip>
+    Cette préférence n'est présentée que si l'option de compilation 
+    <ref id="gpm" name="--enable-gpm"> a été spécifiée.
+  <tag>Sons d'avertissement<label id="preference-alert-tunes"></tag>
+    Chaque fois qu'un événement significatif avec un son associé se produit,
+    (voir <ref id="tunes" name="Sons d'avertissement">):
+    <descrip>
+      <tag>Non</tag>
+        Ne joue pas le son.
+      <tag>Oui</tag>
+        Joue le son.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-TUNES" name="TUNES">.
+    La valeur initiale est <tt>oui</tt>.
+  <tag>Périphérique de son<label id="preference-tune-device"></tag>
+    Joue les son d'avertissement via:
+    <descrip>
+      <tag>Beeper</tag>
+        Le beeper interne (générateur de sons de la console).
+	Cette valeur est supportée sur Linux, OpenBSD, FreeBSD, et
+	NetBSD. Elle est toujours sûre dans son utilisation, bien qu'elle
+	soit peut-être quelque peu rustique. Ce périphérique n'est pas
+	disponible si vous avez spécifié l'option de compilation
+        <ref id="build-beeper-support" name="--disable-beeper-support">.
+      <tag>PCM</tag>
+        L'interface audio de la carte son.
+        Cette valeur est supportée sous Linux (via  <tt>/dev/dsp</tt>),
+	Solaris (via <tt>/dev/audio</tt>), OpenBSD (via <tt>/dev/audio0</tt>),
+        FreeBSD (via <tt>/dev/dsp</tt>), et NetBSD
+	(via <tt>/dev/audio0</tt>).
+	Ne fonctionne pas quand ce périphérique doit déjà être utilisé par
+	une autre application.
+	Ce périphérique n'est pas disponible si vous avez spécifié l'option de
+	compilation <ref id="build-pcm-support" name="--disable-pcm-support">.
+      <tag>MIDI</tag>
+        L'interface MIDI de la carte son. Cette valeur
+	est supportée sous Linux (via <tt>/dev/sequencer</tt>).
+	Ne fonctionne pas quand ce périphérique est déjà utilisé par une
+	autre application.
+        Ce périphérique n'est pas disponible si vous avez spécifié l'option de
+	compilation <ref id="build-midi-support" name="--disable-midi-support">.
+      <tag>FM</tag>
+        La synthèse FM sur une carte son AdLib, OPL3, Sound Blaster, ou
+	équivalente. Cette valeur est supportée sous Linux. Elle fonctionne
+	même si une synthèse FM est déjà utilisée par une autre application.
+	Les résultats sont imprévisibles, et peuvent ne pas être bons, si elle
+	est utilisée avec une carte son ne supportant pas cette caractéristique.
+        Ce périphérique n'est pas disponible si vous avez spécifié l'option de
+	compilation <ref id="build-fm-support" name="--disable-fm-support">.
+    </descrip>
+    La valeur initiale est <tt>Beeper</tt> sur les plateformes supportant cela,
+    et <tt>PCM</tt> sur les autres.
+  <tag>Volume PCM<label id="preference-pcm-instrument"></tag>
+    Si vous utilisez l'interface audio numérique de votre carte son pour
+    jouer les sons d'avertissement, ce paramètre spécifie le volume (sous la forme d'un
+    pourcentage du maximum) auquel ils sont joués.
+  <tag>Volume MIDI<label id="preference-pcm-instrument"></tag>
+    Si vous utilisez la Musical Instrument Digital Interface (MIDI, interface
+    numérique d'instruments de musique) de votre carte son 
+    pour jouer les sons d'avertissement, ce paramètre spécifie le volume (sous la
+    forme d'un pourcentage du maximum) auquel ils sont joués.
+    Le paramètre initial est 70.
+  <tag>Instrument MIDI<label id="preference-midi-instrument"></tag>
+    Si l'interface MIDI de la carte son est
+    utilisée pour jouer les sons d'avertissement, ce paramètre spécifie l'instrument 
+    qui doit être utilisé (voir <ref id="operand-midi-device" name="Table d'instruments MIDI">).
+    La valeur initiale est <tt>Grand piano acoustique</tt>.
+  <tag>Volume FM<label id="preference-alert-dots"></tag>
+    Si vous utilisez le synthétiseur FM de votre carte son 
+    pour jouer les sons d'avertissement, ce paramètre spécifie le volume (sous la
+    forme d'un pourcentage du maximum) auquel ils sont joués.
+  <tag>Points d'avertissement<label id="preference-alert-dots"></tag>
+    Chaque fois qu'un événement avec un type de point associé se produit (voir
+    <ref id="tunes" name="Sons d'avertissement">):
+    <descrip>
+      <tag>Non</tag>
+        N'affiche pas les points.
+      <tag>Oui</tag>
+        Affiche brièvement les points.
+    </descrip>
+    Si les sons d'avertissement doivent être joués (voir la commande
+    <ref id="command-TUNES" name="TUNES"> et la préférence
+    <ref id="preference-alert-tunes" name="Sons d'avertissement">), si un son a été
+    associé à l'événement, et si le périphérique de son sélectionné peut être
+    ouvert, alors sans s'occuper de la valeur de cette préférence, les points ne sont pas affichés.
+  <tag>Messages d'avertissement<label id="preference-alert-messages"></tag>
+    Chaque fois qu'un événement significatif avec un message associé se produit
+    (voir <ref id="tunes" name="Sons d'avertissement">):
+    <descrip>
+      <tag>Non</tag>
+        N'affiche pas le message.
+      <tag>Oui</tag>
+        Affiche le message.
+    </descrip>
+    Si des sons d'avertissement doivent être joués (voir la commande
+    <ref id="command-TUNES" name="TUNES">, et la préférence
+    <ref id="preference-alert-tunes" name="Sons d'avertissement">), si un son a été
+    associé à l'événement, et si le périphérique de son sélectionné peut être
+    ouvert, ou si des points d'avertissement doivent être affichés (voir la préférence
+    <ref id="preference-alert-dots" name="Points d'avertissement">) et si des points ont
+    été associés à l'événement, sans se soucier de la valeur de cette préférence,
+    le message n'est pas affiché.
+  <tag>Mode Dire la ligne<label id="preference-sayline-mode"></tag>
+    Lors de l'utilisation de la commande 
+    <ref id="command-SAY_LINE" name="SAY_LINE">:
+    <descrip>
+      <tag>Immédiat</tag>
+        Suspend la parole.
+      <tag>En file</tag>
+        Ne suspend pas la parole.
+    </descrip>
+    La valeur initiale est <tt>Immédiat</tt>.
+  <tag>Parole automatique<label id="preference-autospeak"></tag>
+    <descrip>
+      <tag>Inactif</tag>
+        Ne parle que quand vous le demandez explicitement.
+      <tag>Actif</tag>
+        Dit automatiquement:
+        <itemize>
+          <item>la nouvelle ligne lorsque vous déplacez la plage braille
+	  verticalement.
+          <item>les caractères qui sont entrés ou effacés.
+          <item>le caractère sur lequel vous déplacez le curseur.
+        </itemize>
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-AUTOSPEAK" name="AUTOSPEAK">.
+    La valeur initiale est <tt>inactif</tt>.
+  <tag>Vitesse de la synthèse<label id="preference-speech-rate"></tag>
+    Ajuste le débit de parole (<tt>0</tt> est le plus lent, <tt>20</tt> est le plus
+    rapide). Cette préférence n'est disponible que si vous utilisez un pilote
+    qui la supporte.
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-SAY_SLOWER-SAY_FASTER" name="SAY_SLOWER/SAY_FASTER">.
+    La valeur initiale est <tt>10</tt>.
+  <tag>Volume de la synthèse<label id="preference-speech-volume"></tag>
+    Ajuste le volume de la synthèse (<tt>0</tt> est le plus bas, <tt>20</tt> le plus
+    fort). Cette préférence n'est disponible que si vous utilisez un pilote
+    qui la supporte.
+    Vous pouvez aussi changer ce paramètre avec la commande
+    <ref id="command-SAY_SOFTER-SAY_LOUDER" name="SAY_SOFTER/SAY_LOUDER">.
+    La valeur initiale est <tt>10</tt>.
+  <tag>Ton de la voix<label id="preference-speech-pitch"></tag>
+    Ajuste le volume de la synthèse ((<tt/0/ est le plus bas, <tt/20/ est
+    le plus élevé). Cette préférence n'est disponible que si vous utilisez
+    un pilote qui la supporte. Le réglage initial est <tt/10/.
+  <tag>Ponctuation pour la synthèse<label id="preference-speech-punctuation"></tag>
+    Ajuste la quantité de ponctuation parlée. Elle peut être initialisée à:
+    <itemize>
+      <item>Aucune
+      <item>Quelques
+      <item>Toutes
+    </itemize>
+    
+    Cette préférence n'est disponible que si vous utilisez
+    un pilote qui la supporte. Le réglage initial est <tt/Quelques/.
+  <tag>Apparence de l'état<label id="preference-status-style"></tag>
+    Ce paramètre spécifie la façon dont les cellules d'état doivent être
+    utilisées. Normalement, vous ne devriez pas avoir besoin de jouer avec ça.
+    Cela permet aux développeurs de BRLTTY de tester les configurations des
+    cellules d'état pour les afficheurs braille qu'ils n'ont pas avec eux.
+    <descrip>
+      <tag>Aucune</tag>
+        N'utilise pas les cellules de statut.
+	Cette valeur est toujours sûre, mais elle est aussi totalement inutile.
+      <tag>Alva</tag>
+        Les cellules d'état contiennent:
+        <descrip>
+          <tag>1</tag>
+            La place du curseur (voir ci-dessous).
+          <tag>2</tag>
+            La place du coin en haut à gauche de la plage braille (voir
+	    ci-dessous).
+          <tag>3</tag>
+            Une lettre indiquant l'état de BRLTTY.
+            Dans l'ordre de rangement:
+            <descrip>
+              <tag>a</tag>
+	        Les attributs de l'écran sont affichés (voir la commande
+		<ref id="command-DISPMD" name="DISPMD">).
+              <tag>f</tag>
+	        L'image de l'écran est gelée (voir la commande
+		<ref id="command-FREEZE" name="FREEZE">).
+              <tag>f</tag>
+	        Le curseur est poursuivi (voir la commande
+		<ref id="command-CSRTRK" name="CSRTRK">).
+              <tag><em>vierge</em></tag>
+                Rien de spécial.
+            </descrip>
+        </descrip>
+	Les emplacements du curseur et de la plage braille sont présentés de
+	façon intéressante. Les points 1 à 6 représentent le numéro de la ligne
+	avec une lettre de <tt>a</tt> (pour 1) à <tt>y</tt> (pour 25).
+	Les points 7 et 8 (les deux points supplémentaires tout en bas)
+	représentent le numéro de la plage braille horrizontale comme suit:
+	<descrip>
+          <tag>Aucun points</tag>La première fenêtre (la plus à gauche).
+          <tag>Point 7</tag>La seconde fenêtre.
+          <tag>Point 8</tag>La troisième fenêtre.
+          <tag>Points 7 et 8</tag>La quatrième fenêtre.
+        </descrip>
+	Dans les deux cas, les indicateurs incluent:
+	la ligne 26 est représentée par la lettre <tt>a</tt>, et la cinquième
+	plage braille horizontale est représentée avec aucun point tout en bas.
+      <tag>Tieman</tag>
+        Les cellules d'état contiennent:
+        <descrip>
+          <tag>1-2</tag>
+	    Les colonnes (en partant de un) du curseur (montrées dans la
+	    moitié supérieure des cellules et du coin haut à gauche de la
+	    plage braille (affiché dans la partie en bas des cellules).
+          <tag>3-4</tag>
+	    Les lignes (en partant de un) du curseur affichées dans la
+	    moitié supérieure des cellules et du coin haut à gauche de la
+	    plage braille (affiché dans la partie basse des cellules).
+          <tag>5</tag>
+	    Chaque point indique si une caractéristique est active comme suit:
+            <descrip>
+              <tag>point 1</tag>
+                L'image de l'écran est gelée (voir la commande
+		<ref id="command-FREEZE" name="FREEZE">).
+              <tag>Point 2</tag>
+                Les attributs de l'écran sont affichés (voir la commande
+                <ref id="command-DISPMD" name="DISPMD">).
+              <tag>Point 3</tag>
+                Les sons d'avertissement sont joués (voir la commande
+                <ref id="command-TUNES" name="TUNES">).
+              <tag>Point 4</tag>
+                Le curseur est affiché (voir la commande
+                <ref id="command-CSRVIS" name="CSRVIS">).
+              <tag>Point 5</tag>
+                Le curseur est un pavé (voir la commande
+                <ref id="command-CSRSIZE" name="CSRSIZE">).
+              <tag>Point 6</tag>
+                Le curseur est masqué (voir la commande
+                <ref id="command-CSRBLINK" name="CSRBLINK">).
+              <tag>Point 7</tag>
+                Le curseur est poursuivi (voir la commande
+                <ref id="command-CSRTRK" name="CSRTRK">).
+              <tag>Point 8</tag>
+                La plage braille défilera (voir la commande
+                <ref id="command-SLIDEWIN" name="SLIDEWIN">).
+            </descrip>
+        </descrip>
+      <tag>PowerBraille 80</tag>
+        Les cellules d'état contiennent:
+        <descrip>
+          <tag>1</tag>
+	    La ligne (en partant de 1) correspondant au haut de la plage
+	    braille. La partie des dizaines est montrée dans la moitié supérieure
+	    de la cellule, et celle des unités est montrée dans la moitié
+	    inférieure de la cellule.
+        </descrip>
+      <tag>Générique</tag>
+        Ce paramètre transmet beaucoup d'informations au pilote braille, et le
+	pilote lui-même décide comment les présenter.
+      <tag>MDV</tag>
+        Les cellules de statut contiennent:
+        <descrip>
+          <tag>1-2</tag>
+	    L'emplacement du coin en haut à gauche de la plage braille.
+	    La ligne (en partant de 1) est affichée dans la moitié supérieure
+	    des cellules, et la colonne (en partant de 1) est montrée dans la
+    moitié inférieure des cellules.
+        </descrip>
+      <tag>Voyager</tag>
+        Les cellules d'état contiennent:
+        <descrip>
+          <tag>1</tag>
+	    La ligne (en partant de 0) correspondant au haut de la plage
+	    braille (voir ci-dessous).
+          <tag>2</tag>
+            La ligne (en partant de 1) sur laquelle se trouve le curseur (voir
+	    ci-dessous).
+          <tag>3</tag>
+	    Si l'écran est gelé (voir la commande
+	    <ref id="command-FREEZE" name="FREEZE">), la lettre <tt>F</tt>.
+            Sinon, la colonne (en partant de 1) dans laquelle se trouve le
+	    curseur (voir ci-dessous).
+        </descrip>
+	Les numéros de ligne et de colonne sont montrés comme deux cases dans
+	une seule cellule. Les dizaine sont affichées dans la moitié haute de la
+	cellule, et les unités sont affichées dans la moitié inférieure de la
+	cellule.
+    </descrip>
+    La valeur initiale dépend du pilote de l'afficheur braille.
+  <tag>Table de texte<label id="preference-text-table"></tag>
+    Sélectionne la table de texte. Voir la section
+    <ref id="table-text" name="Table de texte"> pour des détails.
+    Voir l'option <ref id="options-text-table" name="-t"> en ligne de
+    commande pour la valeur initiale.
+    Cette préférence n'est pas sauvegardée.
+  <tag>Table d'attributs<label id="preference-attributes-table"></tag>
+    Sélectionne la table d'attributs. Voir la section
+    <ref id="table-attributes" name="Tables d'attributs"> pour
+    des détails. Voir l'option <ref id="options-attributes-table" name="-a">
+    en ligne de commande pour la valeur initiale.
+    Cette préférence n'est pas sauvegardée.
+  <tag>Table de braille abrégé<label id="preference-contraction-table"></tag>
+    Sélectionne la table de braille abrégé. Voir la section
+    <ref id="table-contraction" name="Tables d'abrégé"> pour des détails. Voir l'option
+    <ref id="options-contraction-table" name="-c"> en ligne de commande
+    pour la valeur initiale.
+    Cette préférence n'est pas sauvegardée.
+  <tag> Tabe de touches<label id="preference-key-table"></tag>
+    Sélection de la table de touches.
+    Voir la section <ref id="table-key" name="Tables de touches"> pour plus de détails.
+    Voir l'option <ref id="options-key-table" name="-k"> en ligne de commande 
+    pour le réglage initial.
+    Cette préférence n'est pas sauvegardée.
+</descrip>
+
+Remarques:
+<itemize>
+  <item><label id="time-settings">
+    Tous les paramètres de temps sont en centièmes de seconde. Ce sont des
+    multiples de 4 compris entre 1 et 100.
+</itemize>
+
+<sect1>L'affichage des états<label id="status"><p>
+L'affichage des états est un résumé de l'état courant de BRLTTY qui s'adapte
+totalement à l'intérieur de la plage braille. Certains afficheurs braille ont
+un type de cellules d'état qui sont utilisées pour afficher en permanence
+certaines de ces informations de la même façon (voir la documentation du
+pilote de votre afficheur).
+Les données présentées par cet affichage ne sont pas statiques et peuvent changer
+à n'importe quel moment, en réaction aux mises à jour de l'écran et/ou aux
+commandes BRLTTY.
+
+Utilisez la commande <ref id="command-INFO" name="INFO"> pour aller à
+l'affichage des états, et utilisez-la de nouveau pour revenir à l'écran.
+La présentation des informations qu'il contient dépend de la taille de
+la plage braille.
+
+<sect2>Afficheurs de 21 cellules ou plus<p>
+De courtes symboliques ont été utilisées, bien qu'elles s'apparentent à un code
+chiffré, de façon à afficher la présentation en colonne précise.
+<tscreen><em>wx</em>:<em>wy</em> <em>cx</em>:<em>cy</em> <em>vt</em> <em>tcmfdu</em></tscreen>
+<descrip>
+  <tag><em>wx</em><tt>:</tt><em>wy</em></tag>
+    La colonne et la ligne (en partant de 1) sur l'écran correspondant au
+    coin en haut à gauche de la plage braille.
+  <tag><em>cx</em><tt>:</tt><em>cy</em></tag>
+    La colonne et la ligne (en partant de 1) sur l'écran correspondant à la
+    position du curseur.
+  <tag><em>vt</em></tag>
+    Le numéro (en partant de 1) de la console virtuelle courante.
+  <tag><em>t</em></tag>
+    L'état de la fonction de poursuite du curseur (voir la commande
+    <ref id="command-CSRTRK" name="CSRTRK"> command).
+    <descrip>
+      <tag>vide</tag>Le suivi du curseur est inactif.
+      <tag><tt>t</tt></tag>Le suivi du curseur est actif.
+    </descrip>
+  <tag><em>c</em></tag>
+    L'état des caractéristiques de visibilité du curseur (voir les commandes
+    <ref id="command-CSRVIS" name="CSRVIS"> et
+    <ref id="command-CSRBLINK" name="CSRBLINK">).
+    <descrip>
+      <tag>vide</tag>Le curseur n'est pas visible et ne clignotera pas quand il
+      sera visible.
+      <tag><tt>b</tt></tag>Le curseur n'est pas visible, et clignotera lorsqu'il
+      sera visible.
+      <tag><tt>v</tt></tag>Le curseur est visible et non clignotant.
+      <tag><tt>B</tt></tag>Le curseur est visible et clignotant.
+    </descrip>
+  <tag><em>m</em></tag>
+    Le mode d'affichage actuel (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">).
+    <descrip>
+      <tag><tt>t</tt></tag>Le contenu de l'écran (texte) est affiché.
+      <tag><tt>a</tt></tag>La surbrillance à l'écran (les attributs) est affichée.
+    </descrip>
+  <tag><em>f</em></tag>
+    L'état de la fonction de gel de l'écran (voir la commande
+    <ref id="command-FREEZE" name="FREEZE">).
+    <descrip>
+      <tag>vide</tag>L'écran n'est pas gelé.
+      <tag><tt>f</tt></tag>L'écran est gelé.
+    </descrip>
+  <tag><em>d</em></tag>
+    Le nombre de points braille utilisés pour afficher chaque caractère (voir
+    la commande <ref id="command-SIXDOTS" name="SIXDOTS">).
+    <descrip>
+      <tag><tt>8</tt></tag>Les huit points sont utilisés.
+      <tag><tt>6</tt></tag>Seuls 6 points sont utilisés.
+    </descrip>
+  <tag><em>u</em></tag>
+    L'état des fonctions d'affichage des lettres majuscules (voir la
+    commande <ref id="command-CAPBLINK" name="CAPBLINK">).
+    <descrip>
+      <tag>vide</tag>Les lettres en majuscule ne clignotent pas.
+      <tag><tt>B</tt></tag>Les lettres en majuscule clignotent.
+    </descrip>
+</descrip>
+
+<sect2>Afficheurs à 20 cellules ou moins<p>
+De courtes symboliques ont été utilisées, bien qu'elles s'apparentent à un code
+chiffré, de façon à montrer la présentation en colonne précise.
+<tscreen><em>xx</em><em>yy</em><em>s</em> <em>vt</em> <em>tcmfdu</em></tscreen>
+<descrip>
+  <tag><em>xx</em></tag>
+    Les colonnes (en partant de 1) sur l'écran correspondant à la position du
+    curseur (affiché dans la moitié supérieure des cellules) et au coin en
+    haut à gauche de l'afficheur braille (affiché dans la moitié inférieure des
+    cellules).
+  <tag><em>yy</em></tag>
+    Les lignes (en partant de 1) sur l'écran correspondant à la position du
+    curseur (affichée dans la moitié supérieure des cellules) et au coin en    haut à gauche de l'afficheur braille (montré dans la moitié inférieure des
+    cellules).
+  <tag><em>s</em></tag>
+    Les valeurs de certaines fonctions de BRLTTY.
+    Une fonctionalité est active si le point lui correspondant est élevé.
+    <descrip>
+      <tag>Point 1</tag>
+        L'image de l'écran gelée (voir la commande
+	<ref id="command-FREEZE" name="FREEZE">).
+      <tag>Point 2</tag>
+        Affichage des attributs (voir la commande
+	<ref id="command-DISPMD" name="DISPMD">).
+      <tag>Point 3</tag>
+        Les sons d'avertissement (voir la commande
+	<ref id="command-TUNES" name="TUNES">).
+      <tag>Point 4</tag>
+        Curseur visible (voir la commande
+	<ref id="command-CSRVIS" name="CSRVIS">).
+      <tag>Point 5</tag>
+        Curseur en pavé (voir la commande
+	<ref id="command-CSRSIZE" name="CSRSIZE">).
+      <tag>Point 6</tag>
+        Clignotement du curseur (voir la commande
+	<ref id="command-CSRBLINK" name="CSRBLINK">).
+      <tag>Point 7</tag>
+        Poursuite du curseur (voir la commande
+	<ref id="command-CSRTRK" name="CSRTRK">).
+      <tag>Point 8</tag>
+        Glissement de la plage (voir la commande
+	<ref id="command-SLIDEWIN" name="SLIDEWIN">).
+    </descrip>
+  <tag><em>vt</em></tag>
+    Le numéro (en partant de 1) de la console virtuelle actuelle.
+  <tag><em>t</em></tag>
+    L'état de la fonction de poursuite du curseur (voir la commande
+    <ref id="command-CSRTRK" name="CSRTRK"> command).
+    <descrip>
+      <tag>vide</tag>Le suivi du curseur est inactif.
+      <tag><tt>t</tt></tag>Le suivi du curseur est actif.
+    </descrip>
+  <tag><em>c</em></tag>
+    L'état des fonctions de visibilité du curseur (voir les commandes
+    <ref id="command-CSRVIS" name="CSRVIS"> et
+    <ref id="command-CSRBLINK" name="CSRBLINK">).
+    <descrip>
+      <tag>vide</tag>Le curseur n'est pas visible et ne cligontera pas quand il
+      sera visible.
+      <tag><tt>b</tt></tag>Le curseur n'est pas visible, et clignotera lorsqu'il
+      sera visible.
+      <tag><tt>v</tt></tag>Le curseur est visible et non clignotant.
+      <tag><tt>B</tt></tag>Le curseur est visible et clignotant.
+    </descrip>
+  <tag><em>m</em></tag>
+    Le mode d'affichage actuel (voir la commande
+    <ref id="command-DISPMD" name="DISPMD">).
+    <descrip>
+      <tag><tt>t</tt></tag>Le contenu de l'écran (texte) est affiché.
+      <tag><tt>a</tt></tag>La surbrillance à l'écran (les attributs) est affichée.
+    </descrip>
+  <tag><em>f</em></tag>
+    L'état de la fonction de gel de l'écran (voir la commande
+    <ref id="command-FREEZE" name="FREEZE">).
+    <descrip>
+      <tag>vide</tag>L'écran n'est pas gelé.
+      <tag><tt>f</tt></tag>L'écran est gelé.
+    </descrip>
+  <tag><em>d</em></tag>
+    Le nombre de points braille utilisés pour afficher chaque caractère (voir
+    la commande <ref id="command-SIXDOTS" name="SIXDOTS">).
+    <descrip>
+      <tag><tt>8</tt></tag>Les huit points sont utilisés.
+      <tag><tt>6</tt></tag>Seuls 6 points sont utilisés.
+    </descrip>
+  <tag><em>u</em></tag>
+    L'état des fonctions d'affichage des lettres majuscules (voir la
+    commande <ref id="command-CAPBLINK" name="CAPBLINK">).
+    <descrip>
+      <tag>vide</tag>Les lettres en majuscule ne clignotent pas.
+      <tag><tt>B</tt></tag>Les lettres en majuscule clignotent.
+    </descrip>
+</descrip>
+
+<sect1>Mode Apprentissage des commandes<label id="learn"><p>
+Le Mode Apprentissage des commandes est une façon interactive d'apprendre
+ce que les touches de l'afficheur braille font. Vous pouvez y accéder soit
+par la commande <ref id="command-LEARN" name="LEARN"> ou via l'utilitaire
+<ref id="utility-brltest" name="brltest">.
+Cette caractéristique n'est pas disponible si vous avez spécifié l'option de
+compilation <ref id="build-learn-mode" name="--disable-learn-mode">.
+
+Lorsque vous êtes entré dans ce mode, le message <tt>Mode apprentissage des commandes</tt>
+est écrit sur l'afficheur braille.
+Alors, dès que vous pressez une touche (ou une combinaison de touches) de
+l'afficheur, un court message décrivant sa fonction dans BRLTTY est écrit.
+Vous quitterez immédiatement ce mode si vous pressez la touche (ou la
+combinaison de touches) pour la commande <ref id="command-LEARN" name="LEARN">.
+Vous sortez automatiquement, et le message <tt>done</tt> s'inscrit, si un
+de temps de 10 secondes s'écoule sans qu'une touche de l'afficheur ne soit
+pressée.
+Remarquez que certains afficheurs ne se signalent pas au pilote et/ou certains
+afficheurs ne se signalent pas à BRLTTY jusqu'à ce que toutes les touches soient
+faites.
+
+Si un message est plus long que la largeur de l'afficheur braille, il est
+affiché en segments. La longueur d'un segment est de un soustrait à
+la largeur de l'afficheur braillle, avec le caractère le plus à droite sur
+l'afficheur qui affiche le signe moins.
+Chaque segment reste sur l'afficheur, soit pendant quelques secondes (voir
+l'option <ref id="options-message-timeout" name="-M"> en ligne de commande)
+soit jusqu'à ce qu'une touche de l'afficheur soit pressée.
diff --git a/Documents/Manual-BRLTTY/French/Introduction.sgml b/Documents/Manual-BRLTTY/French/Introduction.sgml
new file mode 100644
index 0000000..32a4b49
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Introduction.sgml
@@ -0,0 +1,166 @@
+<sect>Introduction<p>
+BRLTTY donne à un utilisateur brailliste un accès aux consoles texte
+d'un système Linux/Unix. Il exécute un processus en arrière-plan
+(démon) qui fait fonctionner l'afficheur braille, et
+peut être démarré très tôt dans la séquence de démarrage du
+système. Ainsi, il permet à un utilisateur brailliste, de prendre en main
+facilement et de façon indépendante des aspects de l'administration du
+système, comme l'entrée en mode mono-utilisateur, la restauration de systèmes de fichiers, et
+l'analyse de problèmes de démarrage. Il facilite aussi beaucoup des
+opérations de bases telles que l'identification.
+
+BRLTTY reproduit une portion rectangulaire de l'écran (appelée
+dans ce document `la fenêtre') sous forme de texte braille sur l'afficheur.
+Vous pouvez utiliser des contrôles de l'affichage pour déplacer la fenêtre sur
+l'écran, pour activer et désactiver des options de revue
+variées, et pour exécuter des fonctions spéciales.
+
+<sect1>Résumé des possibilités<p>
+BRLTTY donne les possibilités suivantes:
+<itemize>
+  <item>
+    Totale mise en oeuvre des facilités de revue d'écran habituelles.
+  <item>
+    Choix d'un curseur en forme de <tt>bloc</tt>, <tt>souligné</tt>, ou
+<tt>aucun</tt>.
+  <item>
+    <tt>Soulignement</tt> possible pour indiquer un texte particulièrement
+    en surbrillance.
+  <item>
+    Utilisation possible du <tt>clignotement</tt> (fréquences réglables
+    individuellement) pour le curseur, le soulignement des caractères en
+    surbrillance, et/ou les lettres en majuscule.
+  <item>
+    Gel de l'écran pour en faire une relecture plus lente.
+  <item>
+    Routine-curseur intelligente, permettant un rapide
+    rapatriment du curseur dans les éditeurs de texte, les navigateurs
+    web, etc., sans bouger les mains de l'afficheur braille.
+  <item>
+    Une fonction copier-coller (linéaire ou rectangulaire) qui est
+    particulièrement utile pour la copie de longs noms de fichier, de
+    texte entre des terminaux virtuels, la saisie de commandes
+    compliquées, etc.
+  <item>
+    Gestion des tables de braille abrégé (fournie en anglais et en français).
+  <item>
+    Support pour de multiples codes braille.
+  <item>
+    Possibilité d'identifier un caractère inconnu.
+  <item>
+    Possibilité d'inspecter un caractère en surbrillance.
+  <item>
+    Une facilité dans l'aide en ligne pour les commandes de
+    l'afficheur braille.
+  <item>
+    Un menu préférences.
+  <item>
+    Support de synthèses basiques.
+  <item>
+    Une conception en modules permettant d'ajouter relativement
+    facilement d'autres afficheurs braille et d'autres synthèses
+    vocales.
+  <item>
+    Une Interface de programmation de l'Application.
+</itemize>
+
+<sect1>Système requis<p>
+Actuellement, BRLTTY fonctionne sur Linux, Solaris, OpenBSD, FreeBSD,
+NetBSD et Windows. Les portages sur d'autres systèmes d'exploitation
+dérivés de Unix ne sont pas encore prévues, nous apprécierions vraiment
+tout intérêt pour de tels projets.
+
+<descrip>
+  <tag>Linux</tag>
+    Ce logiciel a été testé sur un grand nombre de systèmes Linux:
+    <itemize>
+      <item>
+        Ordinateurs de bureau, portables, et quelques PDAs.
+      <item>
+        Des processeurs 386SX20 à Pentium.
+      <item>
+        Une large échelle de capacité de mémoire.
+      <item>
+        Plusieurs distributions dont Debian, Red Hat, Slackware et SuSE.
+      <item>
+        La plupart des noyaux, dont les 1.2.13, 2.0, 2.2, et 2.4.
+    </itemize>
+  <tag>Solaris</tag>
+    Ce logiciel a été testé sur les systèmes Solaris suivants:
+    <itemize>
+      <item>
+        L'architecture Sparc (versions 7, 8, et 9).
+      <item>
+        L'architecture Intel (version 9).
+    </itemize>
+  <tag>OpenBSD</tag>
+    Ce logiciel a été testé sur les systèmes OpenBSD suivants:
+    <itemize>
+      <item>
+        L'architecture Intel (version 3.4).
+    </itemize>
+  <tag>FreeBSD</tag>
+    Ce logiciel a été testé sur les systèmes FreeBSD suivants:
+    <itemize>
+      <item>
+        L'architecture Intel (version 5.1).
+    </itemize>
+  <tag>NetBSD</tag>
+    Ce logiciel a été testé sur les systèmes NetBSD suivants:
+    <itemize>
+      <item>
+        L'architecture Intel (version 1.6).
+    </itemize>
+  <tag>Windows</tag>
+    Ce logiciel a été testé sur Windows 95, 98 et XP.
+
+</descrip>
+
+Sur Linux, BRLTTY peut inspecter le contenu de l'écran de façon
+totalement indépendante de l'utilisateur. Cela est possible
+grâce à l'utilisation d'un périphérique spécial offrant un accès
+facile aux contenus de la console virtuelle courante. Ce périphérique
+a été ajouté à la version 1.1.92 du noyau Linux, et s'appelle
+normalement <tt>/dev/vcsa</tt> ou <tt>/dev/vcsa0</tt> (sur les systèmes
+avec <tt>devfs</tt>, il s'appelle <tt>/dev/vcc/a</tt>). C'est pourquoi le noyau
+Linux 1.1.92 ou supérieur est nécessaire si BRLTTY est utilisé de
+cette façon. Cette possibilité:
+<itemize>
+  <item>
+    Permet à BRLTTY d'être démarré très tôt dans la séquence de
+    démarrage du système.
+  <item>
+    Active l'afficheur braille pour qu'il soit totalement opérationnel
+    à l'invite de logging.
+  <item>
+    Facilite fortement pour un utilisateur brailliste
+    des tâches d'administration lors du démarrage.
+</itemize>
+
+Un correctif pour le programme <tt>d'écran</tt> est fourni (voir le sous-répertoire
+<tt>Patches</tt>). Il permet à BRLTTY d'accéder à l'image d'un écran
+via une mémoire partagée, et, ainsi, permet à BRLTTY d'être utilisé
+beaucoup plus efficacement sur des plateformes qui n'ont pas leurs
+propres facilité d'inspection du contenu de leur écran. La faiblesse
+principale de cette approche de l'écran est que BRLTTY ne peut être
+démarré tant que l'utilisateur n'est pas connecté.
+
+BRLTTY ne fonctionne qu'avec des consoles et des applications basées
+sur du texte. Il peut être utilisé avec les applications basées sur
+<tt>curses</tt>, mais pas avec une application utilisant des caractéristiques
+spéciales VGA ou qui requièrent une console graphique (comme le système X
+Window).
+
+Bien entendu, vous devez aussi posséder un afficheur braille supporté
+(voir la section <ref id="displays" name="Afficheurs braille
+supportés"> pour la liste complète). Nous espérons que des afficheurs
+supplémentaires seront supportés dans le futur, donc si vous disposez de quelques
+vagues informations de programmation techniques pour un pilote que
+vous aimeriez voir supporté, faites-le nous savoir (voir la section
+<ref id="contact" name="Contacts">).
+
+Enfin, vous avez besoin d'outils pour compiler l'exécutable depuis le
+source, <tt>make</tt>, les compilateurs <tt>C</tt> et <tt>C++</tt>, <tt>yacc</tt>,
+<tt>awk</tt>, etc. Les outils de développement fournis avec les
+distributions Unix standards devraient suffire. Si vous rencontrez des
+problèmes, contactez-nous et nous vous compilerons un exécutable.
diff --git a/Documents/Manual-BRLTTY/French/Makefile.in b/Documents/Manual-BRLTTY/French/Makefile.in
new file mode 100644
index 0000000..6a77a56
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Makefile.in
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DOCUMENT_NAME = BRLTTY
+DOCUMENT_LANGUAGE = french
+include $(SRC_TOP)document.mk
diff --git a/Documents/Manual-BRLTTY/French/Midi.sgml b/Documents/Manual-BRLTTY/French/Midi.sgml
new file mode 100644
index 0000000..0726eec
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Midi.sgml
@@ -0,0 +1,149 @@
+<sect>Table d'instruments MIDI<label id="midi"><p>
+<table loc="h">
+  <tabular ca="lrl">
+    <hline>Piano
+      |0|Piano à queue accoustique@
+      |1|Piano accoustique brillant@
+      |2|Piano à queue électrique@
+      |3|Piano bastringue@
+      |4|Piano 1@
+      |5|Piano électrique 2@
+      |6|clavecin@
+      |7|Claviers@
+    <hline>Percussions chromatiques
+      |8|Célesta@
+      |9|Glockenspiel@
+      |10|Boîte à rythme@
+      |11|Vibraphone@
+      |12|Marimba@
+      |13|Xylophone@
+      |14|Tubular Bells@
+      |15|Dulcimer@
+    <hline>Orgue
+      |16|Orgue de barbarie@
+      |17|Orgue percussive@
+      |18|Orgue rock@
+      |19|Orgue d'église@
+      |20|Orgue de hanche@
+      |21|Accordéon@
+      |22|Harmonica@
+      |23|Accordéon de tango@
+    <hline>Guitare
+      |24|Guitare accoustique Nylon@
+      |25|Guitare accoustique métal@
+      |26|Guitare électrique Jazz@
+      |27|Guitare électrique propre@
+      |28|Guitare électrique bouchée@
+      |29|Guitare overdriven@
+      |30|Distortion Guitar@
+      |31|Harmoniques de guitare@
+    <hline>Basse
+      |32|Basse accoustique@
+      |33|Basse électrique au doigt@
+      |34|Pick Electric Bass@
+      |35|Fretless Bass@
+      |36|Slap Bass 1@
+      |37|Slap Bass 2@
+      |38|Synth Bass 1@
+      |39|Synth Bass 2@
+    <hline>Pistons
+      |40|Violon@
+      |41|Alto@
+      |42|Violoncelle@
+      |43|Contrebasse@
+      |44|Pistons Tremolo@
+      |45|Pisstons pizzicato@
+      |46|Harpe Orchestrale@
+      |47|timbales@
+    <hline>Ensemble
+      |48|String Ensemble 1@
+      |49|String Ensemble 2@
+      |50|Synth Strings 1@
+      |51|Synth Strings 2@
+      |52|Aahs choeur@
+      |53|Oohs voix@
+      |54|Voice synthétique@
+      |55|Orchestra Hit@
+    <hline>Cuivre
+      |56|Trompette@
+      |57|Trombonne@
+      |58|Tuba@
+      |59|Trompette bouchée@
+      |60|Cor français@
+      |61|Section cuivre@
+      |62|Cuivre synthétique 1@
+      |63|Cuivre synthétique 2@
+    <hline>Anche
+      |64|Saxophone soprano@
+      |65|Saxophone Alto@
+      |66|Saxophone Tenor@
+      |67|Saxophone Bariton@
+      |68|Hautbois@
+      |69|Cor anglais@
+      |70|Basson@
+      |71|Clarinette@
+    <hline>Tuyau
+      |72|Piccolo@
+      |73|Flûte@
+      |74|Flûte à bec@
+      |75|Flûte de pan@
+      |76|Blown Bottle@
+      |77|Shakuhachi@
+      |78|Sifflet@
+      |79|Ocarina@
+    <hline>Synth Lead
+      |80|Lead 1 (square)@
+      |81|Lead 2 (dent de scie)@
+      |82|Lead 3 (calliope)@
+      |83|Lead 4 (chiff)@
+      |84|Lead 5 (charang)@
+      |85|Lead 6 (voix)@
+      |86|Lead 7 (cinquièmes)@
+      |87|Lead 8 (basse + lead)@
+    <hline>Synth Pad
+      |88|Pad 1 (nOuvel âge)@
+      |89|Pad 2 (chaud)@
+      |90|Pad 3 (polysynth)@
+      |91|Pad 4 (choeur)@
+      |92|Pad 5 (bowed)@
+      |93|Pad 6 (métalique)@
+      |94|Pad 7 (halo)@
+      |95|Pad 8 (sweep)@
+    <hline>Synth FM
+      |96|FX 1 (pluie)@
+      |97|FX 2 (soundtrack)@
+      |98|FX 3 (cristal)@
+      |99|FX 4 (atmosphère)@
+      |100|FX 5 (brightness)@
+      |101|FX 6 (goblins)@
+      |102|FX 7 (échos)@
+      |103|FX 8 (science-fiction)@
+    <hline>Ethnic
+      |104|Sitar@
+      |105|Banjo@
+      |106|Shamisen@
+      |107|Koto@
+      |108|Kalimba@
+      |109|Bag Pipe@
+      |110|Fiddle@
+      |111|Shanai@
+    <hline>Percussive
+      |112|Tinkle Bell@
+      |113|Agogo@
+      |114|Steel Drum@
+      |115|Wooden Block@
+      |116|Taiko Drum@
+      |117|Melodic Tom@
+      |118|Synth Drum@
+      |119|Reverse Cymbal@
+    <hline>Effets sonores
+      |120|Bruit de touche de guitare@
+      |121|Bruit de souffle@
+      |122|Bord de mer@
+      |123|Bruit d'oiseau@
+      |124|Sonnerie de télphone@
+      |125|Helicoptère@
+      |126|Applaudissements@
+      |127|Gunshot@
+  </tabular>
+</table>
diff --git a/Documents/Manual-BRLTTY/French/Screen.sgml b/Documents/Manual-BRLTTY/French/Screen.sgml
new file mode 100644
index 0000000..9307114
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Screen.sgml
@@ -0,0 +1,23 @@
+<sect>Pilotes d'écran supportés <label id="screen"><p>
+BRLTTY supporte les pilotes d'écran suivants:
+<descrip>
+  <tag/as/
+    AT-SPI
+  <tag/hd/
+    Ce pilote fournit un accès direct à l'écran d'une console Hurd.
+    Il n'est  sélectionnable et par défaut oque sur les systèmes Hurd.
+  <tag/lx/
+    Ce pilote fournit un accès direct à l'écran d'une console Linux
+    Il n'est  sélectionnable et par défaut oque sur les systèmes Linux.
+  <tag/sc/
+    Ce pilote fournit un accès direct au programme <tt/screen/.
+    Vous pouvez le sélectionner sur tous les systèmes, et il l'est par défaut
+    si aucun pilote d'écran d'origine n'est disponible. Vous devez appliquer le
+    correctif de <tt/screen/ que nous fournissons (voir le sous-répertoire
+    <tt/Patches/ ). Du fait que screen doit être exécuté simultanément,
+    l'utilisation de ce pilote rend BRLTTY opérationnel uniquement après que
+    l'utilisateur s'est identifié.
+  <tag/wn/
+    Ce pilote fournit un accès direct à l'écran d'une console Windows.
+    Il n'est  sélectionnable et par défaut que sur les systèmes Windows/Cygwin.
+</descrip>
diff --git a/Documents/Manual-BRLTTY/French/Syntax.sgml b/Documents/Manual-BRLTTY/French/Syntax.sgml
new file mode 100644
index 0000000..36f3686
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Syntax.sgml
@@ -0,0 +1,109 @@
+<sect>Syntaxe des opérateurs<p>
+
+<sect1>Spécification de pilote<label id="operand-driver"><p>
+Vous devez spécifier un pilote pour un afficheur braille ou une synthèse vocale
+via les deux lettres de son
+<ref id="drivers" name="Code d'identification de pilote">.
+
+Vous pouvez spécifier une liste de pilotes délimités par des virgules. Dans
+ce cas, une détection automatique s'effectue en utilisant chaque pilote listé dans la
+séquence.
+Il se peut que vous soyez obligé de faire des essais afin de déterminer l'ordre le plus
+fiable, étant donné que certains pilotes se détectent mieux automatiquement que d'autres.
+
+Si vous ne spécifiez que le mot <tt>auto</tt>, la détection automatique s'effectue
+en n'utilisant que les pilotes connus pour leur fiabilité dans le but
+recherché.
+
+<sect1>Spécification du périphérique braille<label id="operand-braille-device"><p>
+La forme générale de la spécification d'un périphérique braille (voir l'option
+<ref id="options-braille-device" name="-d"> en ligne de commande, la ligne
+<ref id="configure-braille-device" name="braille-device"> du fichier de
+configuration, et l'option de compilation
+<ref id="build-braille-device" name="--with-braille-device">) est
+<tt>qualificateur:</tt><em>donnée</em>.
+Par compatibilité entre d'anciennes versions et les plus récentes, si vous ommettez
+le qualificateur c'est <tt>serial:</tt> qui est utilisé.
+
+Les types de périphérique suivants sont supportés:
+<descrip>
+  <tag>Bluetooth</tag>
+    Pour un périphérique bluetooth, spécifiez <tt>bluetooth:</tt><em>addresse</em>.
+    L'adresse doit se composer de six nombres hexadécimaux à deux chiffres
+    séparés par des "deux-points", par exemple <tt>01:23:45:67:89:AB</tt>.
+  <tag>Série</tag>
+    Pour un périphérique en port série, spécifiez
+    <tt>serial:</tt><em>/chemin/vers/peripherique</em>. Le qualificateur <tt>serial:</tt>
+    est facultatif (pour compatibilité). Si vous donnez un chemin
+    relatif, il est déterminé par rapport à <tt>/dev</tt> (l'emplacement habituel
+    où les périphériques sont définis sur un système de type Unix).
+    Les spécifications de périphérique suivantes se réfèrent toutes au port
+    série 1 sur Linux:
+    <itemize>
+      <item><tt>serial:/dev/ttyS0</tt>
+      <item><tt>serial:ttyS0</tt>
+      <item><tt>/dev/ttyS0</tt>
+      <item><tt>ttyS0</tt>
+    </itemize>
+  <tag>USB</tag>
+    Pour un périphérique USB, spécifiez <tt>usb:</tt>. BRLTTY cherchera le premier
+    périphérique USB qui entraîne l'utilisation du pilote d'afficheur braille.
+    Par exemple, si vous avez plus d'un afficheur braille USB nécessitant le
+    même pilote, vous pouvez affiner la spécification de pilote en y affectant
+    le numéro de série de l'afficheur, comme par exemple <tt>usb:12345</tt>.
+    N.B.: La possibilité "identification par le numéro de série" ne
+    fonctionne pas avec certains modèles car certains fabricants, soit
+    n'indiquent pas la description du numéro de série, soit l'indiquent
+    mais pas en une valeur unique.
+</descrip>
+
+Vous pouvez spécifier une liste de pilotes délimités par des virgules. Dans
+ce cas, une détection automatique s'effectue en utilisant chaque pilote listé dans la
+séquence. Cette possibilité est particulièrement utile si vous avez un
+afficheur braille à plusieurs interfaces, par exemple un port série et un USB.
+Dans ce cas, il est en général préférable de lister d'abord le port USB, comme
+par exemple <tt>usb:,serial:/dev/ttyS0</tt>, étant donné que l'ancien a
+tendance à être mieux détecté que le plus récent.
+
+<sect1>Spécification d'un périphérique PCM<label id="operand-pcm-device"><p>
+Dans la plupart des cas, le périphériqve PCM est le chemin complet vers un
+périphérique du système approprié. Les exceptions sont:
+<descrip>
+  <tag>ALSA</tag>
+    Le nom et ses arguments pour le périphérique logique ou physique, comme
+    <em>nom</em>[<tt>:</tt><em>argument</em><tt>,</tt>...].
+</descrip>
+
+Le périphérique PCM par défaut est:
+<table loc="h">
+  <tabular ca="ll">
+    Plateforme|Périphérique@<hline>
+    FreeBSD|/dev/dsp@
+    Linux/ALSA|hw:0,0@
+    Linux/OSS|/dev/dsp@
+    NetBSD|/dev/audio@
+    OpenBSD|/dev/audio@
+    Qnx|le périphérique de sortie PCM préféré@
+    Solaris|/dev/audio@
+  </tabular>
+</table>
+
+<sect1>Spécification de périphérique MIDI<label id="operand-midi-device"><p>
+Dans la plupart des cas, le périphérique MIDI est le chemin complet vers un
+périphérique du système approprié. Les exceptions sont:
+<descrip>
+  <tag>ALSA</tag>
+    Le client et le port séparés par "deux-points" tel que
+    <em>client</em><tt>:</tt><em>port</em>.
+    Vous pouvez spécifier chacun soit comme un nombre soit comme une
+    sous-chaîne sensible à la casse de son nom.
+</descrip>
+
+Le périphérique MIDI par défaut est:
+<table loc="h">
+  <tabular ca="ll">
+    Plateforme|Périphérique@<hline>
+    Linux/ALSA|le premier port de sortie MIDI disponible@
+    Linux/OSS|/dev/sequencer@
+  </tabular>
+</table>
diff --git a/Documents/Manual-BRLTTY/French/Synthesizers.sgml b/Documents/Manual-BRLTTY/French/Synthesizers.sgml
new file mode 100644
index 0000000..ad71c79
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Synthesizers.sgml
@@ -0,0 +1,8 @@
+<sect>Synthèses vocales supportées<label id="synthesizers"><p>
+BRLTTY supporte les synthèses vocales suivantes:
+<table loc="h">
+  <tabular ca="ll">
+    Nom|Modèle@<hline>
+    &SpeechDrivers
+  </tabular>
+</table>
diff --git a/Documents/Manual-BRLTTY/French/Translation.sgml b/Documents/Manual-BRLTTY/French/Translation.sgml
new file mode 100644
index 0000000..1a99765
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Translation.sgml
@@ -0,0 +1,1086 @@
+<sect>Tables<label id="tables"><p>
+
+<sect1>Tables de texte<label id="table-text"><p>
+Les fichiers ayant un nom sous la forme <tt/*.ttb/ sont des tables de texte,
+et ceux avec des noms de la forme <tt/*.tti/ sont des sous-tables de texte.
+Elles sont utilisées par BRLTTY pour traduire les caractères à l'écran
+dans les représentations brailles qui correspondent à l'informatique 8 points.
+
+Au départ, BRLTTY est configuré pour utiliser la table de texte
+<ref id="nabcc" name="North American Braille Computer Code">
+(NABCC) (code informatique braille nord-américain).
+En plus de celle-ci par défaut, les alternatives suivantes sont fournies:
+
+<table loc="h">
+  <tabular ca="ll">
+    Nom|Langue@
+    &TextTables
+  </tabular>
+</table>
+
+Voir l'option <ref id="options-text-table" name="-t"> en ligne de commande,
+la ligne <ref id="configure-text-table" name="text-table"> du fichier de
+configuration, et l'option de compilation
+<ref id="build-text-table" name="--with-text-table"> pour des détails
+concernant la façon d'utiliser et de changer de table de texte.
+
+<sect2>Format des tables de texte<p>
+Une table de texte consiste en une séquence d'instructions, une par ligne,
+qui définit comment chaque caractère doit être représenté en braille 
+Vous devez utiliser l'encodage <tt/UTF-8/.
+Un blanc (espaces, tabs) tant au début de la ligne qu'aavant et/ou après 
+l'opérateur d'une instruction, est ignoré.
+
+Les lignes ne contenant que des blancs sont ignorées.
+Si le premier caractère non blanc d'une ligne est "&num;", cette ligne est
+un commentaire et est ignorée.
+
+<sect2>Instructions des tables de texte<p>
+Les instructions suivantes sont fournies:
+<descrip>
+  <tag><tt/char/ <em/caractère/ <em/points/ &num; <em/commentaire/</tag>
+    Utilise l'instruction <tt/char/ pour spécifier la façon dont un caractère 
+    Unicode sera représenté en braille. Les caractères définis par cette
+    instruction peuvent également être saisies au clavier braille. Si plusieurs
+    caractères ont la même représentation braille, vous ne devriez en définir
+    qu'un avec la ligne <tt/char/ - vous devriez définir l'autre avec la ligne
+    <tt/glyph/ (dont la syntaxe est identique). Si plus d'un caractère ayant la
+    même représentation braille est défini avec l'instruction <tt/char/
+    (ce qui est actuellement possible pour des questions de rétrocompatibilité),
+    c'est la première qui est sélectionnée. 
+
+    <descrip>
+      <tag><em/caractère/</tag>
+        Le caractère Unicode qui sera défini. Cela peut être:
+        <itemize>
+          <item>
+            Tout caractère différent d'un antislash ou d'un caractère blanc.
+          <item>
+            Un caractère spécial précédé d'un anti-slash.
+            Ce sont:
+            <descrip>
+            - <tag/\b/Le caractère Effacement
+            - <tag/\f/Le caractère formfeed
+            - <tag/\n/Le caractère Nouvelle ligne.
+            - <tag/\o&num;&num;&num;/La représentation octale à 3 chiffres d'un caractère.
+            - <tag/\r/Le caractère retour chariot
+            - <tag/\s/Le caractère Espace
+            - <tag/\t/Le caractère Tab horizontal
+            - <tag/\u&num;&num;&num;&num;/La représentation hexadécimale à quatre chiffres d'un caractère.
+            - <tag/\U&num;&num;&num;&num;&num;&num;&num;&num;/La représentation hexadécimale à huit chiffres d'un caractère.
+            - <tag/\v/Le caractère tab vertical
+            - <tag/\x&num;&num;/La représentation hexadécimale à deux chiffres d'un caractère.
+            - <tag/\X&num;&num;/... (la casse du X et des chiffres n'a pas de signification)
+            - <tag/\&num;/Signe d'un nombre littéral.
+            - <tag/\&lt;name&gt;/Le nom Unicode d'un caractère (utilisez _ pour l'espace).
+            - <tag/\\/Un antislash littéral.
+            </descrip>
+        </itemize>
+      <tag><em/dots/</tag>
+        La représentation braille du caractère Unicode. C'est une séquence d'un
+        à huit nombres de points. Si la séquence du nombre de points est entourée
+        de parenthèses, vous pouvez séparer les numéros des points l'un de l'autre
+        par des blancs. Un numéro de point est un chiffre compris entre <tt/1/-<tt/8/ 
+        tels que définis par la
+        <ref id="dots" name="Standard Braille Dot Numbering Convention"> (convention
+        standard de numérotation de points brailles). Le numéro de point spécial <tt/0/
+        est reconnu quand il n'est pas entouré de parenthèses, et il signifie aucun
+        point; il ne peut être utilisé parallèlement à un autre numéro de point.
+    </descrip>
+
+    Exemples:
+    <itemize>
+      <item><tt/char a 1/
+      <item><tt/char b (12)/
+      <item><tt/char c ( 4  1   )/
+      <item><tt/char \\ 12567/
+      <item><tt/char \s 0/
+      <item><tt/char \x20 ()/
+      <item><tt/char \&lt;LATIN_SMALL_LETTER_D&gt; 145/
+    </itemize>
+  <tag><tt/glyph/ <em/caractère/ <em/dots/ &num; <em/comment/</tag>
+    Utilisez l'instruction <tt/glyph/ pour spécifier la façon dont doit être
+    représenté en braille un caractère Unicode. Les caractères définis avec
+    cette instruction peuvent uniquement être affichés. On ne peut pas les
+    saisir au clavier braille. Voir la ligne <tt/char/ pour les détails sur
+    la syntaxe et pour des exemples.
+  <tag><tt/byte/ <em/byte/ <em/points/ &num; <em/commentaire/</tag>
+    Utilisez l'instruction <tt/byte/ pour spécifier comment un caractère en encodage local
+    doit être représenté en braille. Il a été retenu pour des raisons de
+    compatibilité mais ne devrait pas être utilisé.
+
+    Les caractères Unicode devraient être définis
+    (via l'instruction <tt/char/) de telle sorte que la table de texte demeure
+    valide par rapport à l'encodage local.
+
+    <descrip>
+      <tag><em/byte/</tag>
+        Le caractère local défini. Il peut être spécifié de la même manière que
+        l'opérateur <em/caractère/ de l'instruction <tt/char/ sauf que les formes
+        spécifiques à l'Unicode (\u, \U, \&lt;) ne peuvent pas être utilisées.
+      <tag><em/points/</tag>
+        La représentation braille du caractère local.
+        Il peut être spécifié de la même manière que
+        l'opérateur <em/points/ de l'instruction <tt/char/.
+    </descrip>
+  <tag><tt/include/ <em/fichier/ &num; <em/commentaire/</tag>
+    Utilisez l'instruction <tt/include/ pour inclure le contenu d'une sous-table de
+    texte. Elle est récursive, ce qui signifie que toute sous-table de texte peut
+    inclure elle-même une autre sous-table de texte. Prenez soin de vous
+    assurer de ne pas créer une "inclusion en boucle".
+
+    <descrip>
+      <tag><em/fichier/</tag>
+        Le fichier à inclure. Cela peut être un chemin relatif ou absolu. Si c'est
+        relatif, il est ancré au répertoire contenant le fichier qui inclut.
+    </descrip>
+</descrip>
+
+<sect1>Tables d'attributs<label id="table-attributes"><p>
+Les fichiers aux noms sous la forme *.atb sont des tables d'attributs et ceux
+aux noms sous la forme *.ati sont des sous-tables d'attributs. Ils sont
+utilisés quand BRLTTY affiche les
+attributs de l'écran au lieu du contenu de l'écran (voir la commande
+<ref id="command-DISPMD" name="DISPMD">).
+Chacun des huit points braille représente l'un des huit bits d'attributs
+<tt>VGA</tt>.
+Les tables d'attributs suivantes sont fournies:
+<descrip>
+  <tag/left_right/
+    La colonne à gauche représente les couleurs de premier plan:
+    <descrip>
+      <tag/Point 1/Bleu
+      <tag/Point 2/Vert
+      <tag/Point 3/Rouge
+      <tag/Point 7/Brillant
+    </descrip>
+    La colonne à droite représente les couleurs de fond:
+    <descrip>
+      <tag/Point 4/Bleu
+      <tag/Point 5/Vert
+      <tag/Point 6/Rouge
+      <tag/Point 8/Clignotant
+    </descrip>
+    Un point est affiché quand son bit d'attribut correspondant est actif.
+    C'est la table d'attributs par défaut car c'est la plus
+    intuitive. Cependant, l'un de ses problèmes est qu'il est difficile de
+    distinguer la différence entre la vidéo normale (noir sur blanc) et inversée
+    (blanc sur noir).
+  <tag/invleft_right/
+    La colonne à gauche représente les couleurs de premier plan: 
+    <descrip>
+      <tag/Point 1/Bleu
+      <tag/Point 2/Vert
+      <tag/Point 3/Rouge
+      <tag/Point 7/Brillant
+    </descrip>
+    La colonne à droite représente les couleurs de fond:
+    <descrip>
+      <tag/Point 4/Bleu
+      <tag/Point 5/Vert
+      <tag/Point 6/Rouge
+      <tag/Point 8/Clignotant
+    </descrip>
+    Un bit de fond est actif pour générer ses points correspondant, tandis
+    qu'un bit de premier plan est inactif pour générer son point correspondant.
+    Cette logique non intuitive facilite en fait la lecture de la plupart des
+    combinaisons d'attributs communément utilisées.
+  <tag/upper_lower/
+    Le carré supérieur représente les couleurs de premier plan:
+    <descrip>
+      <tag/Point 1/Rouge
+      <tag/Point 4/Vert
+      <tag/Point 2/Bleu
+      <tag/Point 5/Brillant
+    </descrip>
+    Le carré inférieur représente les couleurs d'arrière-plan:
+    <descrip>
+      <tag/Point 3/Rouge
+      <tag/Point 6/Vert
+      <tag/Point 7/Bleu
+      <tag/Point 8/Clignotant
+    </descrip>
+    Un point s'affiche quand le bit de l'attribut qui y correspond est actif.
+</descrip>
+Voir l'option <ref id="options-attributes-table" name="-a"> en ligne de
+commande, la ligne <ref id="configure-attributes-table" name="attributes-table">
+du fichier de configuration, et l'option de compilation
+<ref id="build-attributes-table" name="--with-attributes-table"> pour des
+détails concernant l'utilisation et le changement de table
+d'attributs.
+
+<sect2>Format des tables d'attributs<p>
+Une table d'attributs est une séquence de lignes de commande, avec une
+commande par ligne, qui définit comment doit être représenté en braille les
+combinaisons des attributs <tt/VGA/. Vous devez utiliser un encodage de caractères
+<tt/UTF-8/.
+Les espaces blancs (les vides, tabulations) au début d'une ligne, ou avant
+et/ou après l'opérateur d'une ligne de commande, sont ignorés.
+Les lignes ne contenant que des espaces sont ignorées.
+Si le premier caractère non-blanc d'une ligne est "&num;" cette ligne est un
+commentaire et est ignorée.
+
+<sect2>Lignes de commande des tables d'attributs<p>
+Les lignes de commande suivantes sont fournies:
+<descrip>
+  <tag><tt>dot</tt> <em>point</em> <em>etat</em> &num; <em>commentaire</em></tag>
+    Utilisez l'instruction <tt>dot</tt> pour spécifier ce que représente un point 
+    particulier.
+
+    <descrip>
+      <tag><em>point</em></tag>
+        Le point qui est défini. C'est une seule case allant de 
+        <tt>1</tt> à <tt>8</tt> comme défini par la
+	<ref id="dots" name="Convention standard du nombre de points brailles">.
+      <tag><em>état</em></tag>
+        Ce que représente le point. Il peut s'agir:
+        <descrip>
+          <tag><tt>on</tt></tag>D'un point élevé si l'attribut nommé est actif.
+          <tag><tt>off</tt></tag>D'un point enfoncé si l'attribut nommé est inactif.
+        </descrip>
+
+        Les noms des bits des attributs sont:
+        <descrip>
+          <tag/0X01/<tt/bleu premier plan/
+          <tag/0X02/<tt/vert premier plan//
+          <tag/0X04/<tt/rouge premier plan//
+          <tag/0X08/<tt/brillant premier plan//
+          <tag/0X10/<tt/fond bleu/
+          <tag/0X20/<tt/fond vert/
+          <tag/0X40/<tt/fond rouge/
+          <tag/0X80/<tt/fond clignotant/
+        </descrip>
+    </descrip>
+
+    Exemples:
+    <itemize>
+      <item><tt/dot 1 =fg-red/
+      <item><tt/dot 2 &tilde;bg-blue/
+    </itemize>
+
+  <tag><tt/include/ <em/fichier/ &num; <em/commentaire/</tag>
+    Utilisez la ligne  <tt/include/ pour inclure le contenu d'une sous-table d'attributs. 
+    Il est récursif, ce qui signifie que toute sous-table d'attributs peut
+    inclure elle-même une autre sous-table d'attributs. Prenez soin de vous
+    assurer de ne pas créer une "inclusion en boucle".
+
+    <descrip>
+      <tag><em/fichier/</tag>
+        Le fichier à inclure. Cela peut être un chemin relatif ou absolu. Si c'est
+        relatif, il est ancré au répertoire contenant le fichier qui inclut.
+    </descrip>
+</descrip>
+
+<sect1>Tables de braille abrégé<label id="table-contraction"><p>
+Les fichiers aux noms sous la forme <tt/*.ctb/ sont des tables de braille abrégé 
+et ceux aux noms sous la forme <tt/*.cti/ sont des sous-tables de braille abrégé. 
+Ils sont utilisés par BRLTTY pour traduire des séquences de caractères à l'écran 
+en leurs représentations correspondantes en braille abrégé. 
+
+BRLTTY présente du braille abrégé si:
+<itemize>
+  <item>
+    Une table de braille abrégé a été sélectionnée.
+    Voir l'option <ref id="options-contraction-table" name="-c"> en ligne
+    de commande et la ligne
+    <ref id="configure-contraction-table" name="contraction-table"> du
+    fichier de configuration pour des détails.
+  <item>
+    La fonction braille 6 points a été activée. Voir la commande
+    <ref id="command-SIXDOTS" name="SIXDOTS"> et la préférence
+    <ref id="preference-text-style" name="Text Style"> pour des détails.
+</itemize>
+Cette possibilité n'est pas disponible si vous avez spécifié l'option de
+compilation
+<ref id="build-contracted-braille" name="--disable-contracted-braille">.
+
+Les tables d'abrégé suivantes sont fournies:
+
+<table loc="h">
+  <tabular ca="ll">
+    Nom|Langue@
+    &ContractionTables
+ </tabular>
+</table>
+Voir l'option <ref id="options-contraction-table" name="-c"> en ligne de commande
+et la ligne <ref id="configure-contraction-table" name="contraction-table"> du fichier de 
+configuration pour des détails sur la façon d'utiliser une table d'abrégé.
+
+<sect2>Format des tables de braille abrégé<p>
+Une table de braille abrégé est une séquence de lignes de commande, avec une
+commande par ligne, qui définit comment les séquences de caractères vont être
+représentées en braille. Vous devez utiliser un encodage de caractères
+<tt/UTF-8/.
+
+Les espaces blancs (les vides, tabulations) au début d'une ligne, ou avant
+et/ou après l'opérateur d'une ligne de commande, sont ignorés.
+Les lignes ne contenant qu'à des espaces sont ignorées.
+Si le premier caractère non-blanc d'une ligne est "&num;" cette ligne est un
+commentaire et est ignorée.
+
+Le format d'une entrée de table de braille abrégé est:
+<tscreen><em/directive/ <em/opérateur/ ... [<em/commentaire/]</tscreen>
+Chaque ligne a un nombre d'opérateurs spécifique.
+Tout texte au-delà du dernier opérateur d'une ligne est interprété comme un commentaire.
+L'ordre des entrées à l'intérieur de la table de braille abrégé est, en général, selon la
+convenance de son/ses mainteneur(s).
+Une entrée qui définit une entité, comme <tt/class/,
+doit précéder toutes les références de cette entité.
+
+Les entrées qui correspondent à des séquences de caractères 
+sont automatiquement réorganisées de la plus longue à la plus courte
+afin que des correspondances plus longues soient toujours préférées.
+Si plus d'une entrée correspond à la même séquence de caractères, 
+leur organisation d'origine dans la table est maintenue. 
+Ainsi, la même séquence peut être traduite différemment dans des circonstances
+différentes. 
+
+<sect2>Opérateurs des tables de braille abrégé<p>
+<descrip>
+  <tag><em/characters/</tag>
+    Le premier opérateur d'une séquence de caractères correspondant à une ligne
+    est la séquence de caractères à laquelle elle doit correspondre.
+    Chaque caractère dans la séquence peut être:
+    <itemize>
+      <item>
+            Tout caractère différent d'un antislash (barre oblique inversée) ou d'un caractère blanc.
+          <item>
+            Un caractère spécial précédé d'une barre oblique inversée.
+            Ce sont:
+            <descrip>
+            - <tag/\b/Le caractère Effacement
+            - <tag/\f/Le caractère formfeed
+            - <tag/\n/Le caractère Nouvelle ligne.
+            - <tag/\o&num;&num;&num;/La représentation octale à 3 chiffres d'un caractère.
+            - <tag/\r/Le caractère retour chariot
+            - <tag/\s/Le caractère Espace
+            - <tag/\t/Le caractère Tab horizontale
+            - <tag/\u&num;&num;&num;&num;/La représentation hexadécimale à quatre chiffres d'un caractère.
+            - <tag/\U&num;&num;&num;&num;&num;&num;&num;&num;/La représentation hexadécimale à huit chiffres d'un caractère.
+            - <tag/\v/Le caractère tab verticale
+            - <tag/\x&num;&num;/La représentation hexadécimale à deux chiffres d'un caractère.
+            - <tag/\X&num;&num;/... (la casse du X et des chiffres n'a pas de signification)
+            - <tag/\&num;/Signe d'un nombre littéral.
+            - <tag/\&lt;name&gt;/Le nom Unicode d'un caractère (utilisez _ pour l'espace).
+            - <tag/\\/Un antislash littéral.
+            </descrip>
+        </itemize>
+  <tag><em/representation/</tag>
+    Le second opérateur de ces lignes correspondant à la séquence de caractères
+    qui en a une est la représentation braille de la séquence.
+    Chaque cellule braille est spécifiée comme une séquence d'un à huit numéros
+    de points. Un numéro de point est un chiffre compris entre <tt/1/-<tt/8/ 
+    tels que définis par la
+    <ref id="dots" name="Standard Braille Dot Numbering Convention"> (convention standard
+    de numérotation de points brailles). Le numéro de point spécial <tt/0/
+    est reconnu quand il n'est pas entouré de parenthèses, et il signifie aucun
+    point; il ne peut pas être utilisé parallèlement à un autre numéro de point.
+</descrip>
+
+<sect2>Opcodes<label id="contraction-opcodes"><p>
+Un opcode est un mot-clé qui dit au traducteur comment interpréter les
+opérateurs. Les opcodes sont groupés ici par leur fonction.
+
+<sect3>Administration de la table<label id="contraction-opcodes-administration"><p>
+Ces opérateurs facilitent l'écriture des tables de braille abrégé.
+Ils n'ont pas d'effet direct sur la traduction de caractère.
+<descrip>
+  <tag><tt>include</tt> <em>chemin</em><label id="contraction-opcode-include"></tag>
+    Inclut le contenu d'un autre fichier.
+    L'inclusion peut se faire à n'importe quel niveau.
+    Les chemins relatifs sont déterminés par rapport au répertoire du fichier inclu.
+  <tag><tt>locale</tt> <em>locale</em><label id="contraction-opcode-locale"></tag>
+    Définit la locale pour l'interprétation d'un caractère (minuscule,
+    majuscule, numérique, etc). La locale peut être définie comme:
+    <descrip>
+      <tag><em>langue</em>[<tt>_</tt><em>pays</em>][<tt>.</tt><em>charset</em>][<tt>@</tt><em>modifier</em>]</tag>
+        La composante <em>langue</em> est requise et devrait être un code de
+	langue à deux lettres <tt>ISO-639</tt>.
+        La composante <em>pays</em> est facultative et devrait être un code de
+	pays à deux lettres <tt>ISO-3166</tt>.
+        La composante <em>charset</em> est optionnelle et devrait être le nom
+	d'une table de caractères, comme <tt>ISO-8859-1</tt>.
+      <tag>C</tag>
+        7-bit ASCII.
+      <tag>-</tag>
+        Aucune locale.
+    </descrip>
+    La dernière spécification de locale s'applique à toute la table. Si
+    vous n'utilisez pas cet opcode, la locale <tt>C</tt> est utilisée.
+</descrip>
+
+<sect3>Définition d'un symbole spécial<label id="contraction-opcodes-symbols"><p>
+Ces opcodes définissent les caractères spéciaux qui doivent être insérés dans
+le texte braille afin de le rendre plus clair.
+<descrip>
+  <tag><tt>capsign</tt> <em>points</em><label id="contraction-opcode-capsign"></tag>
+    Le symbole qui met en majuscule une seule lettre.
+  <tag><tt>begcaps</tt> <em>points</em><label id="contraction-opcode-begcaps"></tag>
+    Le symbole qui commence un bloc de lettres en majuscule à l'intérieur d'un
+    mot.
+  <tag><tt>endcaps</tt> <em>points</em><label id="contraction-opcode-endcaps"></tag>
+    Le symbole qui termine un bloc de lettres en majuscules à l'intérieur
+    d'un mot.
+  <tag><tt>letsign</tt> <em>points</em><label id="contraction-opcode-letsign"></tag>
+    Le symbole qui désigne une lettre ne faisant pas partie du mot.
+  
+  <tag><tt/numsign/ <em/points/<label id="contraction-opcode-numsign"></tag>  
+    Le symbole marquant le début d'un nombre.
+  <tag><tt>lastlargesign</tt> <em>points</em><label id="contraction-opcode-numsign"></tag>
+    Traduit les caractères quel que soit l'endroit où ils apparaissent.
+    Supprime les espaces qui les précède si le mot précédent a été marqué
+    par le code "largesign".
+</descrip>
+
+<sect3>Traduction de caractère<label id="contraction-opcodes-translation"><p>
+Ces opcodes définissent les représentations braille des séquences de caractères.
+Chacun d'eux définit une entrée à l'intérieur de la table de braille abrégé.
+Ces entrées peuvent être définies dans n'importe quel ordre, sauf, comme
+remarqué ci-dessous, lorsqu'elles définissent des représentations
+alternatives de la même séquence de caractères.
+
+Chacun de ces opcodes a un opérateurs <em>caractères</em> (qui doit être
+spécifié comme une <em>chaîne</em>), et une condition de configuration dirigeant
+son utilisation.
+Le texte est traduit strictement de la gauche vers la droite, caractère par
+caractère, avec l'entrée la plus acceptable pour chaque position utilisée.
+S'il y a plus d'une entrée acceptable pour une position donnée, celle ayant la
+chaîne de caractères la plus longue est utilisée. S'il y a plus d'une
+entrée acceptable pour la même chaîne de caractères, celle définie le plus
+au début de la table est utilisée (c'est la seule dépendance de l'ordre).
+
+Beaucoup de ces opcodes ont un opérateur <em>points</em> qui définit la
+représentation braille de son opérateur <em>caractères</em>. Il peut
+être aussi spécifié comme un signe égal (<tt>=</tt>), au quel cas il
+signifie l'une des deux choses. Si l'entrée est pour un seul caractère,
+cela signifie que la représentation du braille informatique sélectionnée
+(voir l'option <ref id="options-text-table" name="-t"> en ligne de
+commande et la ligne <ref id="configure-text-table" name="text-table">
+du fichier de configuration) de ce caractère doit être utilisée. Si
+c'est pour une séquence multi-caractères, la représentation par défaut
+de chaque caractère (voir <ref id="contraction-opcode-always"
+name="always">) dans une séquence doit être utilisée.
+
+Certains termes spéciaux sont utilisés à l'intérieur des descriptions de ces
+opcodes.
+<descrip>
+  <tag>word</tag>
+    Une séquence maximale d'une ou plusieurs lettres à la suite.
+</descrip>
+
+Enfin, voici maintenant la description des opcodes eux-mêmes:
+<descrip>
+  <tag><tt>literal</tt> <em>caractères</em><label id="contraction-opcode-literal"></tag>
+    Traduit ce qui est lié à l'espace et qui contient une séquence de
+    caractères en braille informatique (voir l'option
+    <ref id="options-text-table" name="-t"> en ligne de commande et la ligne
+    <ref id="configure-text-table" name="text-table"> du fichier de
+     configuration).
+  <tag><tt>replace</tt> <em>caractères</em> <em>caractères</em><label id="contraction-opcode-replace"></tag>
+    Remplace la première valeur des caractères, quel que soit l'endroit où ils
+    apparaissent, par la seconde. Les caractères remplacés ne sont pas
+    réinsérés.
+  <tag><tt>always</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-always"></tag>
+    Traduit les caractères quel que soit l'endroit où ils apparaissent.
+    S'il n'y a qu'un caractère, alors, en plus, définit la représentation par
+    défaut de ce caractère.
+  <tag><tt>repeatable</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-repeated"></tag>
+    Traduit les caractères quel que soit l'endroit où ils apparaissent. Ignore toute
+    répétition immédiate de la même séquence.
+  <tag><tt>largesign</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-largesign"></tag>
+    Traduit les caractères quel que soit l'endroit où ils apparaissent. Supprime les
+    espaces entre les mots qui se suivent et qui sont gérés par cet opcode.
+  <tag><tt>word</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-word"></tag>
+    Traduit les caractères s'ils forment un mot.
+  <tag><tt>joinword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-joinword"></tag>
+    Traduit les caractères s'ils forment un mot. Supprime l'espace suivant si
+    le premier caractère qui le suit est une lettre.
+  <tag><tt>lowword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-lowword"></tag>
+    Traduit les caractères s'ils forment un mot lié à un espace. 
+  <tag><tt>contraction</tt> <em>caractères</em><label id="contraction-opcode-contraction"></tag>
+    Fait précéder les caractères d'un signe-lettre (voir
+    <ref id="contraction-opcode-letsign" name="letsign">) s'ils forment un mot.
+  <tag><tt>sufword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-sufword"></tag>
+    Traduit les caractères s'ils forment soit un mot, soit s'ils sont au
+    début d'un mot.
+  <tag><tt>prfword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-prfword"></tag>
+    Traduit les caractères s'ils forment soit un mot, soit s'ils sont à la
+        fin d'un mot.
+  <tag><tt>begword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-begword"></tag>
+    Traduit les caractères s'ils sont au début d'un mot.
+  <tag><tt>begmidword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-begmidword"></tag>
+    Traduit les caractères s'ils sont au début ou au milieu d'un mot.
+  <tag><tt>midword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-midword"></tag>
+    Traduit les caractères s'ils sont au milieu d'un mot.
+  <tag><tt>midendword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-midendword"></tag>
+    Traduit les caractères s'ils sont au milieu ou à la fin d'un mot.
+  <tag><tt>endword</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-endword"></tag>
+    Traduit les caractères s'ils sont à la fin d'un mot.
+  <tag><tt>prepunc</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-prepunc"></tag>
+    Traduit les caractères s'ils font partie de la ponctuation au début d'un
+    mot.
+  <tag><tt>postpunc</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-postpunc"></tag>
+    Traduit les caractères s'ils font partie de la ponctuation à la fin d'un
+    mot.
+  <tag><tt>begnum</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-begnum"></tag>
+    Traduit les caractères s'ils sont au début d'un nombre.
+  <tag><tt>midnum</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-midnum"></tag>
+    Traduit les caractères s'ils sont au milieu d'un nombre.
+  <tag><tt>endnum</tt> <em>caractères</em> <em>points</em><label id="contraction-opcode-endnum"></tag>
+    Traduit les caractères s'ils sont à la fin d'un nombre.
+</descrip>
+
+<sect3>Classes de caractère<label id="contraction-opcodes-classes"><p>
+Ces opcodes définissent et utilisent des classes de caractères. Une classe de
+caractères associe un type de caractère à un nom. Le nom se réfère alors à
+n'importe quel caractère à l'intérieur de la classe. Un caractère peut
+appartenir à plus d'une classe.
+
+Les classes de caractère suivantes sont automatiquement prédéfinies, basées sur
+la locale sélectionnée.
+<descrip>
+  <tag>digit</tag>
+    Caractères numériques.
+  <tag>letter</tag>
+    Les caractères alphabétiques majuscule ou minuscule.
+    Certaines locales ont des lettres supplémentaires qui ne sont ni en majuscule
+    ni en minuscule.
+  <tag>lowercase</tag>
+    Les caractères alphabétiques minuscules.
+  <tag>punctuation</tag>
+    Caractères imprimables qui ne sont ni des espaces ni alphanumériques.
+  <tag>space</tag>
+    Caractères d'espacement.
+    Dans la locale par défaut, il s'agit de: espace, tabulation horizontale, tabulation
+    verticale, retour chariot, nouvelle ligne, saut de page.
+  <tag>uppercase</tag>
+    Caractères alphabétiques en majuscules.
+</descrip>
+
+Les opcodes qui définissent et utilisent des classes de caractères sont:
+<descrip>
+  <tag><tt>class</tt> <em>nom</em> <em>caractères</em><label id="contraction-opcode-class"></tag>
+    Définit une nouvelle classe de caractère. L'opérateur <em>caractères</em>
+    doit être spécifié comme une <em>chaîne</em>. Une classe de caractère ne peut
+    pas être utilisée tant qu'elle n'est pas définie.
+  <tag><tt>after</tt> <em>class</em> <em>opcode</em> ...<label id="contraction-opcode-after"></tag>
+    L'opcode spécifié est assez contraint dans le sens où la séquence de
+    caractères adéquat doit être immédiatement précédée par un caractère
+    appartenant à la classe spécifiée.
+    Si vous utilisez plus d'une fois cet opcode sur la même ligne, l'union des
+    caractères de toute la classe est utilisée.
+  <tag><tt>before</tt> <em>class</em> <em>opcode</em> ...<label id="contraction-opcode-before"></tag>
+    L'opcode spécifié est assez contraint dans le sens où la séquence de
+    caractères adéquat doit être immédiatement suivie par un caractère
+    appartenant à la classe spécifiée.
+    Si vous utilisez plus d'une fois cet opcode sur la même ligne, l'union des
+    caractères de toute la classe est utilisée.
+</descrip>
+
+<sect1>Tables de touches<label id="table-key"><p>
+Les fichiers aux noms ayant la forme <tt/*.ktb/ sont des tables de touches, et ceux
+aux noms ayant la forme <tt/*.kti/ sont des sous-tables de touches.
+Ils sont utilisés par BRLTTY pour associer des combinaisons de touches
+de l'afficheur braille et du clavier à des commandes BRLTTY.
+
+Les noms de fichier de table de touches de l'afficheur braille commencent par
+<tt/brl-/<em/xx/<tt/-/", où <em/xx/ représente le
+<ref id="drivers" name="code d'identification de pilote"> à deux lettres. Le reste
+du nom identifie le(s) modèle(s) pour le(s)quel(s) la table de touches est
+utilisée.
+
+Les noms de fichier de table de touches du clavier commencent par <tt/kbd-/.
+Le reste du nom décrit le type de clavier pour lequel a été conçue la table
+de touches.
+
+Les tables de touches suivantes sont fournies:
+<descrip>
+  <tag/braille/associations pour les claviers braille
+  <tag/desktop/associations pour les claviers complets
+  <tag/keypad/associations pour la navigation à partir du pavé numérique
+  <tag/laptop/associations pour les claviers sans pavé numérique
+  <tag/sun_type6/associations pour les claviers Sun Type 6
+</descrip>
+      Voir l'option <ref id="options-key-table" name="-k"> en ligne de commande
+      et la ligne 
+      <ref id="configure-key-table" name="key-table"> du fichier de
+      configuration pour plus de détails concernant la manière de sélectionner
+      une table de touches de clavier.
+      
+      <sect2>Format des tables de touches<p>
+Une table de touches consiste en une séquence d'instructions, une par ligne,
+qui définit comment les touches et les combinaisons de touches seront interprétées.
+Vous devez utiliser l'encodage <tt/UTF-8/.
+Un blanc (espaces, tabs) tant au début de la ligne qu'aavant et/ou après n'importe quel opérateur,
+est ignoré.
+
+Les lignes ne contenant que des blancs sont ignorées.
+Si le premier caractère non blanc d'une ligne est un nombre ("&num;"), cette ligne est
+un commentaire et est ignorée.
+
+L'ordre de résolution de chaque appui de touche/événement qui se produit
+est le suivant:
+<enum>
+  <item>
+    Un appui sur une touche de raccourci ou une action définie dans le contexte actuel.
+    Voir la ligne 
+    <ref id="key-table-hotkey" name="hotkey">
+    pour des détails.
+  <item>
+    Une combinaison de touches définie dans le contexte actuel.
+    Voir la ligne
+    <ref id="key-table-bind" name="bind">
+    pour des détails.
+  <item>
+    Une commande du clavier braille définie dans le contexte actuel.
+    Voir les lignes
+    <ref id="key-table-map" name="map">
+    et
+    <ref id="key-table-superimpose" name="superimpose">d
+    pour des détails.
+  <item>
+    Une combinaison de touches définie dans le contexte par défaut.
+    Voir la ligne
+    <ref id="key-table-bind" name="bind">
+    pour des détails.
+</enum>
+
+      Les lignes suivantes sont fournies:
+
+<sect3>La ligne Assign<label id="key-table-assign"><p>
+Crée ou met à jour une variable associée au niveau include actuel.
+La variable est visible aux niveaux include actuel et inférieur, mais pas aux
+niveaux include supérieurs.
+
+<tt/assign/ <em/variable/ [<em/valeur/]
+      <descrip>
+ <tag><em/variable/</tag>
+    Le nom de la variable.
+    Si la variable n'existe pas déjà au niveau include actuel, elle sera créée.
+  <tag><em/valeur/</tag>
+    La valeur qui sera associée à la variable.
+    Si on ne la fournit pas, une valeur zéro (null) est affectée.
+</descrip>
+
+La séquence d'échappement \{variable} est remplacée par la valeur de la
+variable nommée dans les accolades. La variable doit être définie au niveau 
+include actuel ou supérieur.
+
+Exemples:
+<itemize>
+  <item><tt/assign nullValue/
+  <item><tt/assign ReturnKey Key1/
+  <item><tt/bind \{ReturnKey} RETURN/
+</itemize>
+
+<sect3>La ligne Bind<label id="key-table-bind"><p>
+Définit la commande qui est BRLTTY est exécutée quand on appuie sur une ou plusieurs
+combinaisons de touches particulières.
+L'association est définie dans le contexte actuel.
+
+        <tt/bind/ <em/touches/ <em/commande/
+            <descrip>
+              <tag><em/touches/</tag>
+                La combinaison de touches à associer.
+               C'est une séquence d'un ou plusieurs noms de touches séparés par 
+               des signes plus (<tt/+/). Le nom de touche à la fin (ou seulement)
+               peut être éventuellement précédé d'un point d'exclamation
+               (<tt/!/). Vous pouvez appuyer sur les touches dans
+               n'importe quel ordre sauf si le nom de la touche à la fin est
+               précédé d'un point d'exclamation, alors on doit appuyer dessus
+               en dernier.
+               Le préfixe du point d'exclamation signifie que la commande est
+               exécutée dès qu'on appuie sur cette touche. S'il n'est pas
+               utilisé, la commande est exécutée dès qu'on effectue
+               une des touches.
+              <tag><em/commande/</tag>
+                Le nom d'une commande BRLTTY. Un ou plusieurs modificateurs
+                peuvent éventuellement être associé au nom de la commande en 
+                utilisant un signe plus (<tt/+/) comme séparateur.
+                <itemize>
+      <item>
+        Pour les commandes qui activent/désactivent une fonctionnalité:
+         <itemize>
+           <item>
+            Si vous spécifiez le modificateur <tt/+on/, la fonctionnalité est
+            alors activée.
+          <item>
+            Si vous spécifiez le modificateur <tt/+off/, la fonctionnalité est
+            alors désactivée.
+          <item>
+            Si vous ne spécifiez ni <tt/+on/ ni <tt/+off/, l'état de la fonctionnalité
+            est activable/désactivable en bascule.
+</itemize>
+      <item>
+        Pour les commandes qui déplacent la plage braille:
+        <itemize>+          <item>
+            Si vous spécifiez le modificateur <tt/+route/, si nécessaire,
+            le curseur est automatiquement routé afin d'être toujours visible
+            sur l'afficheur braille.
+        </itemize>
+      <item>
+        Pour les commandes qui déplacent la plage braille vers une ligne
+        spécifique de l'écran:
+        <itemize>
+          <item>
+            Si vous spécifiez le modificateur <tt/+toleft/,
+            la plage braille est alors également déplacée au début de cette
+            ligne.
+          <item>
+            Si vous spécifiez le modificateur <tt/+scaled/, l'ensemble de
+            touches associé à la commande est interprété comme si c'était une
+            barre de défilement. Si vous ne le spécifiez pas, il y a une
+            correspondance une à une entre les touches et les lignes.
+        </itemize>
+      <item>
+        Pour les commandes qui demandent un complément (offset):
+        <itemize>
+          <item>
+            Vous pouvez spécifier le modificateur +<em/offset/, où
+            <em/offset/ est un entier non négatif.
+            Si vous ne le fournissez pas, <tt/+0/ est supposé.
+        </itemize>
+    </itemize>
+</descrip>
+
+Exemples:
+<itemize>
+  <item><tt/bind Key1 CSRTRK/
+  <item><tt/bind Key1+Key2 CSRTRK+off/
+  <item><tt/bind Key1+Key3 CSRTRK+on/
+  <item><tt/bind Key4 TOP/
+  <item><tt/bind Key5 TOP+route/
+  <item><tt/bind VerticalSensor GOTOLINE+toleft+scaled/
+  <item><tt/bind Key6 CONTEXT+1/
+</itemize>
+
+<sect3>La ligne Context<label id="key-table-context"><p>
+Définit des façons alternatives d'interpréter certains événements et/ou
+combinaisons de touches. Un contexte contient des définitions créées avec les
+lignes 
+<ref id="key-table-bind" name="bind">, 
+<ref id="key-table-hotkey" name="hotkey">,
+<ref id="key-table-map" name="map">,
+et
+<ref id="key-table-superimpose" name="superimpose">.
+
+<tt/context/ <em/identificateur/ [<em/titre/]
+<descrip>
+  <tag><em/identificateur/</tag>
+    À l'intérieur du contexte sous-jacent dans lequel doivent être créées les définitions.
+    Cela peut être:
+
+<itemize>
+      <item>
+        Un de ces noms spéciaux:
+        <descrip>
+          <tag/default/
+            Le contexte par défaut. Si une combinaison de touches n'a pas été définie
+            dans le contexte actuel, c'est alors sa définition dans le contexte
+            par défaut qui sera utilisée. Cela ne s'applique qu'aux définitions
+            créées par la ligne
+            <ref id="key-table-bind" name="bind">.
+          <tag/menu/
+            Ce contexte est utilisé quand on est à l'intérieur du menu des
+            préférences de BRLTTY.
+        </descrip>
+      <item>
+        Un entier compris entre <tt/0/ et <tt/252/.
+        Le contexte <tt/0/ est une façon alternative de se référer au contexte
+        par défaut. Vous devriez éviter les numéros de contexte supérieurs car 
+        le numéro le plus élevé autorisé est susceptible de changer sans signalement,
+        si, par exemple, on ajoute davantage de contextes nommés.
+
+    </itemize>
+
+  <tag><em/titre/</tag>
+    Une description lisible par un humain du  contexte.
+    Il peut contenir des espaces et vous devriez utiliser les conventions de
+    mise en majuscules standards. 
+    Cet opérande est facultatif. Si on le fournit lors de la sélection d'un
+    contexte ayant déjà un titre, les deux doivent correspondre.
+    Les contextes nommés ont déjà des titres internes attribués.
+    
+    Les contextes numériques sont créés au départ sans titres.
+</descrip>
+
+Un contexte est créé la première fois qu'il est sélectionné.
+
+Il peut être ensuite sélectionné autant de fois que nécessaire.
+
+Toutes les définitions sous-jacentes jusqu'à la prochaine ligne
+<ref id="key-table-context" name="context"> ou à la fin du niveau include
+actuel sont créées à l'intérieur du contexte sélectionné.
+
+Le contexte initial du premier niveau de la table de touches est <tt/default/.
+Le contexte initial d'une sous-table de touches incluse est le contexte qui
+a été sélectionné lorsqu'il a été inclu.
+
+Les changements de contexte dans les sous-tables de touches incluses n'affectent
+pas le contexte de la table de touches qui inclut ou de la sous-table.
+
+Si un contexte a un titre (tous les contextes nommés et les contextes numériques 
+pour lesquels on a fourni l'opérande <em/title/), il demeure en place.
+
+Quand un événement de touches entraîne l'activation d'un contexte permanent,
+ce contexte reste actuel jusqu'à ce qu'un événement de touches subséquent
+entraîne l'activation d'un contexte permanent différent. 
+
+Si un contexte n'a pas de titre (les contextes numériques pour lesquels on n'a
+pas fourni l'opérande <em/title/), il est temporaire.
+
+Quand un événement de touche provoque l'activation d'un contexte temporaire,
+ce contexte n'est utilisé que pour interpréter le tout prochain événement de
+touche.
+
+Exemples:
+<itemize>
+  <item><tt/context menu/
+  <item><tt/context 1 Braille Input/
+  <item><tt/context 2/
+</itemize>
+
+<sect3>La ligne Hide<label id="key-table-hide"><p>
+Spécifie si des définitions (voir les lignes
+<ref id="key-table-bind" name="bind">,
+<ref id="key-table-hotkey" name="hotkey">,
+<ref id="key-table-map" name="map">,
+et
+<ref id="key-table-superimpose" name="superimpose">) et les remarques (voir
+la ligne <ref id="key-table-note" name="note">) sont incluses ou pas 
+dans le texte d'aide de la table de touches.
+ 
+<tt/hide/ <em/state/
+<descrip>
+  <tag><em/state/</tag>
+    Un de ces mots-clés:
+     <descrip>
+      <tag/on/Elles sont exclues.
+      <tag/off/Elles sont incluses.
+     </descrip>
+</descrip>
+
+L'état spécifié s'applique à toutes les définitions et les notes qui en découlent
+jusqu'à la prochaine ligne <tt/hide/ ou jusqu'à la fin du niveau include actuel.
+L'état initial du premier niveau de la table de touches est <tt/off/.
+L'état initial d'une sous-table de touches incluse est l'état qui a été
+sélectionné quand elle a été incluse. 
+
+Les changements d'état à l'intérieur des sous-tables de touches incluses
+n'affectent pas l'état de la table de touche ou de la sous-table qui inclut.
+
+Exemples:
+<itemize>
+  <item><tt/hide on/
+</itemize>
+
+<sect3>La ligne Hotkey<label id="key-table-hotkey"><p>
+Associe l'appui ou la survenance d'un événement d'une touche spécifique
+à deux commandes BRLTTY distinctes.
+
+Les associations sont définies dans le contexte actuel.
+
+<tt/hotkey/ <em/touche/ <em/appui/ <em/effectuer/
+<descrip>
+  <tag><em/touche/</tag>
+    Le nom de la touche qui sera associée.
+  <tag><em/appui/</tag>
+    Le nom de la commande BRLTTY qui sera exécutée à chaque fois qu'on appuiera
+    sur la touche. 
+
+  <tag><em/Effectuer/</tag>
+    Le nom de la commande BRLTTY qui sera exécutée à chaque fois qu'on effectuera
+    la touche. 
+</descrip>
+
+On peut coller des modificateurs aux noms de commande.
+
+Voir l'opérande <em/command/ de la ligne 
+<ref id="key-table-bind" name="bind"> pour des
+détails.
+
+Spécifiez <tt/NOOP/ si aucune commande ne sera exécutée.
+Spécifier <tt/NOOP/ pour deux commandes désactive la touche dans les faits.
+
+Exemples:
+<itemize>
+  <item><tt/hotkey Key1 CSRVIS+off CSRVIS+on/
+  <item><tt/hotkey Key2 NOOP NOOP/
+</itemize>
+
+<sect3>La ligne IfKey<label id="key-table-ifkey"><p>
+Applique une ligne de la table de touches à la condition que
+le périphérique ait une touche particulière.
+
+<tt/ifkey/ <em/touche/ <em/ligne/
+<descrip>
+  <tag><em/touche/</tag>
+    Le nom de la touche dont la disponibilité doit être testée.
+  <tag><em/ligne/</tag>
+    La ligne de la table de touches qui doit être appliquée sous condition.
+</descrip>
+
+Exemples:
+<itemize>
+  <item><tt/ifkey Key1 ifkey Key2 bind Key1+Key2 HOME/
+</itemize>
+
+<sect3>La ligne Include<label id="key-table-include"><p>
+Exécute les lignes à l'intérieur d'une sous-table de touches.
+
+Cela est récursif, ce qui signifie que n'importe quelle table de touches
+peut s'inclure elle-même dans une autre sous-table.
+
+Il faut faire attention à bien s'assurer qu'une "boucle d'inclusion" ne soit
+pas créée.
+
+<tt/include/ <em/fichier/
+<descrip>
+  <tag><em/file/</tag>
+    La sous-table de touche qui doit être incluse.
+    Il peut s'agir d'un chemin soit relatif soit absolu.
+    S'il est relatif, il est ancré au répertoire contenant la table de touches
+    qui inclut ou la sous-table.
+</descrip>
+
+Exemples:
+<itemize>
+  <item><tt/include common.kti/
+  <item><tt>include /chemin/vers/mes/touches.kti</tt>
+</itemize>
+
+<sect3>La ligne Map<label id="key-table-map"><p>
+Fait correspondre une touche à une fonction de clavier braille.
+La correspondance est définie à l'intérieur du contexte actuel.
+
+<tt/map/ <em/touche/ <em/fonction/
+<descrip>
+  <tag><em/touche/</tag>
+    Le nom de la touche qui doit être associée. Vous pouvez associer plus d'une
+    touche à la même fonction de clavier braille.
+  <tag><em/fonction/</tag>
+    Le nom de la fonction de clavier braille. Cela peut être un des
+    mots-clés suivants:
+    <descrip>
+      <tag/DOT1/Le point braille standard en haut à gauche.
+      <tag/DOT2/Le point braille standard au milieu à gauche
+      <tag/DOT3/Le point braille standard en bas à gauche.
+      <tag/DOT4/Le point braille standard en haut à droite.
+      <tag/DOT5/Le point braille standard au milieu à droite.
+      <tag/DOT6/Le point braille standard en bas à droite.
+      <tag/DOT7/Le point braille informatique en bas à gauche.
+      <tag/DOT8/Le point braille informatique en bas à droite.
+      <tag/SPACE/La barre d'espace.
+      <tag/SHIFT/La touche shift.
+      <tag/UPPERCASE/
+        Si on doit entrer une lettre minuscule, la traduit alors dans son
+        équivalent en majuscule.
+      <tag/CONTROL/La touche contrôle.
+      <tag/META/La touche alt gauche.
+    </descrip>
+</descrip>
+
+Si une combinaison de touches ne consiste qu'en des touches qui ont été
+associées à des fonctions du clavier braille, et si ces fonctions, lorsqu'elles
+sont combinées, constituent une commande de clavier braille valide, la commande
+est alors exécutée dès que les touches sont utilisées.
+Une commande de clavier braille valide doit inclure soit n'importe quelle
+combinaison de points, soit la barre d'espace (mais pas les deux).
+Si on inclut au moins un point braille, les fonctions du clavier braille
+spécifiée par les lignes
+<ref id="key-table-superimpose" name="superimpose">
+dans le même contexte sont aussi incluses implicitement.
+
+Exemples:
+<itemize>
+  <item><tt/map Key1 DOT1/
+</itemize>
+
+<sect3>La ligne Note<label id="key-table-note"><p>
+
+Ajoute une explication lisible par un humain au texte d'aide de la table de
+touches. 
+
+Les remarques sont utilisées en général, par exemple, pour décrire la place,
+les tailles et les formes des touches d'un périphérique.
+
+<tt/note/ <em/texte/
+<descrip>
+  <tag><em/texte/</tag>
+    L'explication qui doit être ajoutée.
+    Elle peut contenir des espaces et devrait être grammaticalement correcte.
+</descrip>
+
+Chaque remarque comporte exactement une ligne de texte d'explication.
+Le grand espace est ignoré donc on ne peut spécifier l'indentation.
+
+Il n'y a pas de limite au nombre de remarques que vous pouvez spécifier.
+Toutes sont regroupées et présentées dans un seul bloc au début du texte d'aide
+de la table de touches.
+
+Exemples:
+<itemize>
+  <item><tt/note Key1 est la touche ronde tout à gauche de la partie frontale./
+</itemize>
+
+<sect3>La ligne Superimpose<label id="key-table-superimpose"><p>
+Inclut implicitement une fonction de clavier braille à chaque fois qu'une
+commande de clavier braille d'au moins un point esst exécutée.
+
+L'inclusion implicite est définie dans le contexte actuel.
+
+Vous pouvez spécifier n'importe quel numéro parmi elles.
+
+<tt/superimpose/ <em/fonction/
+<descrip>
+  <tag><em/fonction/</tag>
+    Le nom de la fonction de clavier braille. Voir l'opérande <em/function/
+    de la ligne <ref id="key-table-map" name="map"> pour des détails.
+</descrip>
+
+Exemples:
+<itemize>
+  <item><tt/superimpose DOT7/
+</itemize>
+
+<sect3>La ligne Title<label id="key-table-title"><p>
+Fournit un résumé lisible par un humain de l'objectif de la table de touches.
+
+<tt/title/ <em/texte/
+<descrip>
+  <tag><em/texte/</tag>
+   Un résumé d'une ligne de la raison pour laquelle est utilisée la table de 
+   touches. Il peut contenir des espaces et vous devriez utiliser les
+   conventions de mise en majuscules standards.
+
+</descrip>
+
+Le titre d'une table de touches ne peut être spécifié qu'une fois.
+
+Exemples:
+<itemize>
+  <item><tt/title Bindings for Keypad-based Navigation/
+</itemize>
+
+<sect2>Propriétés du clavier<label id="keyboard-properties"><p>
+Par défaut, tous les claviers sont pris en charge. 
+Un sous-paramètre des claviers peut être sélectionné en spécifiant une ou plusieurs des
+propriétés suivantes (voir l'option <ref id="options-keyboard-properties" name="-K">
+en ligne de commande et la ligne <ref id="configure-keyboard-properties" name="keyboard-properties"> du fichier de configuration):
+<descrip>
+<tag/type/
+Le type de bus, spécifié en tant que mots-clés  parmi ceux suivants:
+<tt/any/,
+<tt/ps2/,
+<tt/usb/,
+<tt/bluetooth/.
+<tag/vendor/
+L'identificateur du vendeur, spécifié comme une entier non-signé 16-bit.
+<tag/product/
+L'identificateur du produit, spécifié comme une entier non signé 16 bits.
+</descrip>
+
+Les identificateurs du vendeur et du produit peuvent être spécifiés en décimal
+(pas de préfixe), octal (préfixé par <tt/0/), ou hexacécimal (préfixé par <tt/0x/).
+La spécification de <tt/0/ signifie que cela correspond à toute valeur (comme si
+la propriété n'était pas spécifiée).
diff --git a/Documents/Manual-BRLTTY/French/Utilisation.sgml b/Documents/Manual-BRLTTY/French/Utilisation.sgml
new file mode 100644
index 0000000..2ac6768
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/Utilisation.sgml
@@ -0,0 +1,1026 @@
+<sect>Utilisation de BRLTTY<p>
+Avant de démarrer BRLTTY, vous avez besoin d'installer votre afficheur
+braille. Dans la plupart des cas, cela se fait simplement en le
+connectant sur un port série disponible, puis en le mettant sous
+tension. Après avoir installé votre afficheur, exécutez simplement
+BRLTTY en tapant la commande BRLTTY sur une invite du shell (vous
+devez faire cela en tant que root). Intéressez-vous à l'option <ref id="options-braille-device" name="-d">
+en ligne de commande, à la ligne <ref id="configure-braille-device" name="braille-device"> du fichier de
+configuration, et à l'option de compilation <ref id="build-braille-device" name="--with-braille-device">
+pour d'autres moyens de dire à BRLTTY à quel périphérique votre
+afficheur est connecté. Regardez l'option <ref id="options-braille-driver" name="-b"> en ligne de commande,
+la ligne <ref id="configure-braille-driver" name="braille-driver"> du fichier de configuration, et l'option
+de compilation <ref id="configure-braille-driver" name="--with-braille-driver"> pour d'autres possibilités
+concernant la manière d'indiquer à BRLTTY quel type d'afficheur braille
+vous avez. Observez l'option <ref id="options-braille-parameters" name="-B"> en ligne de commande, et la ligne
+<ref id="configure-braille-parameters" name="braille-parameters"> du fichier de configuration pour
+d'autres possibilités concernant la manière de passer les paramètres
+au pilote de votre afficheur braille. Un message donnant le nom du
+programme (BRLTTY) et son numéro de version apparaîtra brièvement
+(voir l'option <ref id="options-message-timeout" name="-M"> en ligne de commande) sur l'afficheur braille.
+L'afficheur montrera alors une petite zone de l'écran contenant le
+curseur. Par défaut, le curseur est représenté par les points 7 et 8
+superposés sur le caractère où il est.
+
+Toute activité de l'écran sera affichée sur l'afficheur
+braille. L'afficheur suivra aussi la progression du curseur sur
+l'écran. Cette caractéristique est connue en tant que <bf>poursuite du curseur</bf>.
+
+Toutefois, le fait de simplement taper sur le clavier et lire
+l'afficheur ne suffit pas. Essayez d'entrer une commande qui
+provoquera une erreur, et de taper <tt>entrée</tt>. L'erreur
+apparaît sur l'écran mais, sauf si vous avez un afficheur à plusieurs
+lignes, il y a des chances qu'elle ne soit pas visible sur l'afficheur
+braille. Vous n'y voyez que l'invite du shell. Vous avez donc besoin,
+à présent, d'un moyen de déplacer la <tt>plage</tt> braille à
+travers l'écran. Les touches de l'afficheur braille lui-même peuvent
+être utilisées pour envoyer les commandes à BRLTTY qui, entre autres
+choses, peut s'en charger.
+
+<sect1>Commandes<label id="commands"><p>
+Malheureusement, les différents afficheurs braille n'offrent pas des
+touches de contrôle standard. Certains ont des touches standard six
+points, certains en ont huit, et d'autres n'en ont pas. Certains ont
+des touches au niveau des pouces, mais il n'y en a pas beaucoup en
+standard. Certains ont un bouton au-dessus de chaque cellule
+braille. Certains ont des boutons qu'il faut pousser. D'autres ont une
+barre facile d'accès fonctionnant beaucoup comme une manette de
+jeu. La plupart ont des combinaisons variées des éléments ci-dessus. A
+cause de la nature et de la disposition si différente entre chaque
+afficheur, reportez-vous à la documentation de votre afficheur
+personnel pour trouver ce que fait exactement chaque touche.
+
+Les commandes de BRLTTY sont identifiées par leur nom dans ce
+manuel. Si vous oubliez la/les touche(s) de votre afficheur braille à
+utiliser pour une commande particulière, reportez-vous à la page
+d'aide de son pilote. Par conséquent, la touche que vous devriez 
+mémoriser immédiatement est celle de la commande
+<ref id="command-HELP" name="HELP">. Utilisez les routines habituelles (comme
+décrit ci-dessous) pour naviguer dans la page d'aide, puis pressez de
+nouveau la touche d'aide pour quitter.
+
+<sect2>Déplacement vertical<label id="vertical-motion"><p>
+Voir aussi les commandes de routines <ref id="command-PRINDENT-NXINDENT" name="PRINDENT/NXINDENT"> et <ref id="command-PRDIFCHAR-NXDIFCHAR" name="PRDIFCHAR/NXDIFCHAR">.
+<descrip>
+  <tag>LNUP/LNDN<label id="command-LNUP-LNDN"></tag>
+    Monte/descend d'une ligne. Si le saut des lignes identiques a été
+    activé (voir la commande <ref id="command-SKPIDLNS" name="SKPIDLNS">), ces commandes, au lieu de
+    se déplacer exactement d'une ligne à l'autre, sont des alias pour
+    les commandes <ref id="command-PRDIFLN-NXDIFLN" name="PRDIFLN/NXDIFLN">.
+  <tag>WINUP/WINDN<label id="command-WINUP-WINDN"></tag>
+    Monte/descend d'un écran. Si l'écran n'est pas plus haut qu'une ligne,
+    elle déplace alors de 5 lignes.
+  <tag>PRDIFLN/NXDIFLN<label id="command-PRDIFLN-NXDIFLN"></tag>
+    Monte/descend à la ligne la plus proche ayant un contenu
+    différent. Si le saut de lignes identiques a été activé (voir les
+    commandes <ref id="command-SKPIDLNS" name="SKPIDLNS">), ces commandes, plutôt que de sauter des
+    lignes identiques, sont des alias pour les commandes <ref id="command-LNUP-LNDN" name="LNUP/LNDN"> commands.
+  <tag>ATTRUP/ATTRDN<label id="command-ATTRUP-ATTRDN"></tag>
+    Monte/descend à la ligne la plus proche avec des attributs
+    différents (mise en valeur de caractère).
+  <tag>TOP/BOT<label id="command-TOP-BOT"></tag>
+    Va au début ou à la fin de la ligne.
+  <tag>TOP_LEFT/BOT_LEFT<label id="command-TOP_LEFT-BOT_LEFT"></tag>
+    Va au coin en haut à gauche ou en bas à gauche.
+  <tag>PRPGRPH/NXPGRPH<label id="command-PRPGRPH-NXPGRPH"></tag>
+    Va à la ligne la plus proche du paragraphe précédent/suivant (la
+    première ligne non vide après la ligne vide la plus proche). La
+    ligne courante est incluse dans la recherche de l'espace entre les
+    paragraphes.
+  <tag>PRPROMPT/NXPROMPT<label id="command-PRPROMPT-NXPROMPT"></tag>
+    Va à l'invite de commande suivant/précédent.
+  <tag>PRSEARCH/NXSEARCH<label id="command-PRSEARCH-NXSEARCH"></tag>
+    Recherche avant/arrière de l'occurrence la plus proche de la
+    chaîne de caractères du presse-papier collé (voir <ref id="cut" name="Copier-coller">) qui
+    n'est pas dans la plage braille. La recherche se fait à
+    gauche/droite, et commence au caractère immédiatement à
+    gauche/droite de la plage, et en englobant le bord de
+    l'écran. La recherche ne tient pas compte de la casse.
+</descrip>
+
+<sect2>Déplacement horizontal<label id="horizontal-motion"><p>
+Voir aussi la commande de la routine <ref id="command-SETLEFT" name="SETLEFT">.
+<descrip>
+  <tag>CHRLT/CHRRT<label id="command-CHRLT-CHRRT"></tag>
+    Va à gauche/droite d'un caractère.
+  <tag>HWINLT/HWINRT<label id="command-HWINLT-HWINRT"></tag>
+    Va d'une demi-fenêtre à gauche/droite.
+  <tag>FWINLT/FWINRT<label id="command-FWINLT-FWINRT"></tag>
+    Va d'une fenêtre à gauche/droite. Ces commandes sont
+    particulièrement utiles parce qu'elles débordent automatiquement
+    quand elles atteignent le bord de l'écran. Les autres
+    caractéristiques, comme celles de sauter les fenêtres vides (voir
+    la commande <ref id="command-SKPBLNKWINS" name="SKPBLNKWINS">), accroissent
+    leur maniabilité.
+  <tag>FWINLTSKIP/FWINRTSKIP<label id="command-FWINLTSKIP-FWINRTSKIP"></tag>
+    Va à la fenêtre non-vide la plus proche à gauche/droite.
+  <tag>LNBEG/LNEND<label id="command-LNBEG-LNEND"></tag>
+    Va au début/fin de la ligne.
+</descrip>
+
+<sect2>Déplacement implicite<label id="implicit-motion"><p>
+Voir aussi la commande de routine <ref id="command-GOTOMARK" name="GOTOMARK">.
+<descrip>
+  <tag>HOME<label id="command-HOME"></tag>
+    Va à l'endroit où se trouve le curseur d'écran.
+  <tag>BACK<label id="command-BACK"></tag>
+    Revient là où la commande de déplacement la plus récente a mis la
+    plage braille. C'est une façon facile de retourner à droite de là
+    où vous lisiez, après un événement imprévu (comme une poursuite du
+    curseur) qui déplace la plage braille à un moment inapproprié.
+  <tag>RETURN<label id="command-RETURN"></tag>
+    <itemize>
+      <item>
+        Si le déplacement le plus récent de la plage braille
+        était automatique, comme le résultat d'une poursuite du
+        curseur, revient là où la commande de déplacement la plus
+        récente l'a mise (voir la commande <ref id="command-BACK" name="BACK">).
+      <item>
+        Si le curseur n'est pas dans la plage braille, va où se
+        trouve le curseur (voir la commande <ref id="command-HOME" name="HOME">).
+    </itemize>
+</descrip>
+
+<sect2>Activation de fonctionnalités<label id="feature-activation"><p>
+Chacune de ces commandes a trois modes: <bf>activée</bf>
+(déclenche la fonction), <bf>désactivée</bf> (annule la fonction),
+et <bf>à bascule</bf> (si la fonction est inhibée, la déclenche, et
+vis versa). Sauf si cela est explicitement dit, chacune de ces
+fonctions est initialement à <tt>inactif</tt>, et, quand elle est
+<bf>activée</bf>, cela agit sur l'exécution de BRLTTY tout entier. Le
+paramètre initial de certaines des fonctionnalités peut être changée
+par le <ref id="preferences-menu" name="menu des préférences">.
+<descrip>
+  <tag>FREEZE<label id="command-FREEZE"></tag>
+    Gèle l'image de l'écran. BRLTTY fait une copie de l'écran (contenu
+    et attributs) au moment où l'image de l'écran est
+    raffraîchie, et ignore dès lors toute mise à jour de l'écran
+    jusqu'à ce qu'il soit réactualisé. Par exemple, cette
+    fonctionnalité facilite la lecture de la sortie d'une application
+    qui écrit beaucoup trop rapidement.
+  <tag>DISPMD<label id="command-DISPMD"></tag>
+    Montre les mises en relief (attributs) de chaque caractère dans la
+    plage braille, plutôt que les caractères eux-mêmes (le
+    contenu). Par exemple, cette fonctionnalité est utile si vous
+    devez mettre en valeur un item. Quand elle montre le contenu de
+    l'écran, la table de texte est utilisée (voir
+    l'option <ref id="options-text-table" name="-t"> en ligne de commande, la ligne <ref id="configure-text-table" name="text-table"> du
+    fichier de configuration et l'option de compilation
+    <ref id="build-text-table" name="--with-text-table">).  Quand elle montre les attributs de
+    l'écran, la table d'attributs est utilisée (voir
+    l'option ``-a'' en ligne de commande, la ligne
+    <ref id="build-attributes-table" name="attributes-table"> du fichier de configuration, et l'option de
+    compilation ``--with-attributes-table'').  Cette fonctionnalité ne
+    s'applique qu'au terminal virtuel courant.
+  <tag>SIXDOTS<label id="command-SIXDOTS"></tag>
+    Montre les caractères brailles sous la forme 6-points et non
+    8-points. Les points 7 et 8 sont encore utilisés par d'autres
+    fonctionnalités telles que la représentation du curseur et le
+    soulignement des caractères mis en relief. Si une table de braille
+    abrégé a été sélectionnée (voir l'option <ref id="options-contraction-table" name="-c"> en ligne de
+    commande, et la ligne <ref id="configure-contraction-table" name="contraction-table"> du fichier de
+    configuration), elle est utilisée. Vous pouvez aussi changer ce
+    paramètre avec la préférence <ref id="preference-text-style" name="Apparence
+    du texte">.
+  <tag>SLIDEWIN<label id="command-SLIDEWIN"></tag>
+    Si la poursuite du curseur (voir la commande <ref
+    id="command-CSRTRK" name="CSRTRK">) est  <bf>on</bf>, chaque fois que
+    le curseur se déplace de façon trop limitée (ou pas assez) au-delà
+    de la fin de la plage braille, elle repositionne
+    horizontalement la plage braille, de sorte que le curseur,
+    tant qu'il reste de ce côté, soit plus proche du centre. Si cette
+    caractéristique est <bf>inactive</bf>, la plage braille est toujours
+    positionnée de façon à ce que sont côté gauche soit un multiple de
+    sa largeur depuis le bord gauche de l'écran.
+
+    Vous pouvez aussi modifier ce paramètre avec la préférence 
+    <ref id="preference-sliding-window" name="Faire défiler la fenêtre">.
+  <tag>SKPIDLNS<label id="command-SKPIDLNS"></tag>
+    Au lieu de déplacer exactement d'une ligne en haut ou en bas,
+    saute les lignes déjà lues qui ont le même contenu que la ligne
+    courante. Cette caractéristiques influence les commandes <ref id="command-LNUP-LNDN" name="LNUP/LNDN">,
+    ainsi que le défilement de la ligne exécutée par les commandes
+  <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT"> et <ref id="command-FWINLTSKIP-FWINRTSKIP" name="FWINLTSKIP/FWINRTSKIP">.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-skip-identical-lines" name="Sauter les lignes identiques">.
+  <tag>SKPBLNKWINS<label id="command-SKPBLNKWINS"></tag>
+    Saute les fenêtres vides déjà passées lors d'une lecture en avant ou
+    en arrière. Cette caractéristique influence les commandes <ref id="command-FWINLT-FWINRT" name="FWINLT/FWINRT">.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+<ref id="preference-skip-blank-windows" name="Sauter les fenêtres vierges">.
+  <tag>CSRVIS<label id="command-CSRVIS"></tag>
+    Montre le curseur en superposant un modèle de point (voir la
+    commande <ref id="command-CSRSIZE" name="CSRSIZE">) au-dessus du
+    caractère où il se trouve. A l'origine, cette caractéristique est <bf>activée</bf>.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-show-cursor" name="Afficher le curseur">.
+  <tag>CSRHIDE<label id="command-CSRHIDE"></tag>
+    Rend le curseur invisible (voir la commande <ref
+    id="command-CSRVIS" name="CSRVIS"> ) de façon à lire précisément le
+    caractère sous lui. Cette caractéristique n'affecte que la console
+  virtuelle courante.
+  <tag>CSRTRK<label id="command-CSRTRK"></tag>
+    Poursuite du curseur. Si le curseur se déplace à un
+    endroit qui n'est pas à l'intérieur de la plage braille, il
+    déplace automatiquement la plage braille vers la nouvelle
+    position du curseur. Vous voudrez en principe que cette
+    caractéristique soit active, puisque cela minimise les effets du défilement
+    de l'écran, et puisque, lorsque vous entrez quelque chose, la
+    région à l'intérieur de laquelle vous écrivez actuellement est
+    toujours visible. 
+    Si cette option fait sauter la plage braille à un moment
+    inadéquat, utilisez la commande <ref id="command-BACK" name="BACK">
+    pour revenir là où vous étiez en train de lire.
+    Il se peut que vous deviez
+    désactiver cette option lorsque vous utilisez une application qui
+    raffraîchit en permanence l'écran tout en maintenant une présentation fixe 
+    des données. A l'origine, cette caractéristique est <bf>active</bf>.
+  <tag>CSRSIZE<label id="command-CSRSIZE"></tag>
+    Représente le curseur avec les huit points (un bloc complet), au
+    lieu de n'afficher que les points 7 et 8 (un soulignement). Vous
+  pouvez aussi changer ce paramètre avec la préférence 
+  <ref id="preference-cursor-style" name="Apparence du curseur">.
+  <tag>CSRBLINK<label id="command-CSRBLINK"></tag>
+    Cache (active et désactive en fonction d'un intervalle
+    prédéfini) le symbole représentant le curseur (voir la commande <ref id="command-CSRVIS" name="CSRVIS">).
+    Vous pouvez changer ce paramètre avec la préférence 
+    <ref id="preference-blinking-cursor" name="Curseur clignotant">.
+  <tag>ATTRVIS<label id="command-ATTRVIS"></tag>
+    Souligne (avec la combinaison des points 7 et 8) les caractères à
+    mettre en évidence.
+    <descrip>
+      <tag>Non souligné</tag>
+        Blanc sur noir (normal),
+        Gris sur noir,
+        Blanc sur bleu,
+        Noir sur cyan.
+      <tag>points 7 et 8</tag>
+        Noir sur blanc (vidéo inversée).
+      <tag>point 8</tag>
+        Tout le reste.
+    </descrip>
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-show-attributes" name="Afficher les attributes">.
+  <tag>ATTRBLINK<label id="command-ATTRBLINK"></tag>
+    Masque (active ou désactive selon un intervalle prédéfini)
+    l'attribut souligné (voir la commande <ref id="command-ATTRVIS" name="ATTRVIS"> command).
+    A l'origine, cette caractéristique est <bf>activée</bf>.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-blinking-attributes" name="Attributs clignotants">.
+  <tag>CAPBLINK<label id="command-CAPBLINK"></tag>
+    Masque (active ou désactive selon un intervalle prédéfini) les
+    lettres en majuscule. Vous pouvez aussi changer ce paramètre avec
+    la préférence <ref id="preference-blinking-capitals" name="Majuscules
+    clignotantes">.
+  <tag>TUNES<label id="command-TUNES"></tag>
+    Joue un son court prédéfini (voir <ref id="tunes" name="Sons d'avertissement">)
+    chaque fois qu'un événement significatif se produit. A
+    l'origine, cette caractéristique est <bf>activée</bf>.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-alert-tunes" name="Sons d'avertissement">.
+  <tag>AUTOREPEAT<label id="command-AUTOREPEAT"></tag>
+    Répète automatiquement une commande à un intervalle régulier après
+    un délai initial tant que sa touche (combinaison) reste
+    appuyée. Seuls certains pilotes supportent cette fonctionnalité,
+    la principale limite étant que la plupart des afficheurs brailles
+    ne signale pas en même temps les touches pressées et les touches
+    exécutées comme des événements distinctement séparés. A l'origine,
+    cette caractéristique est <bf>activée</bf>.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-autorepeat" name="Répétition automatique">.
+  <tag>AUTOSPEAK<label id="command-AUTOSPEAK"></tag>
+    Dit automatiquement:
+    <itemize>
+      <item>la nouvelle ligne quand la plage braille est déplacée verticalement
+      <item>les caractères que vous entrez ou que vous effacez.
+      <item>le caractère vers lequel vous déplacez le curseur.
+    </itemize>
+    A l'origine, cette caractéristique est <bf>inactive</bf>.
+    Vous pouvez aussi changer ce paramètre avec la préférence 
+    <ref id="preference-autospeak" name="Parole automatique">.
+</descrip>
+
+<sect2>Sélection de mode<label id="mode-selection"><p>
+<descrip>
+  <tag>HELP<label id="command-HELP"></tag>
+    Se déplace à la page d'aide du pilote d'un afficheur braille. C'est
+    là que vous pouvez trouver un résumé en ligne de choses telles que
+    ce que font les touches de votre afficheur braille, et comment
+    interpréter ses cellules d'état.
+    Utilisez les commandes <ref id="vertical-motion" name="vertical">
+    et <ref id="horizontal-motion" name="horizontal"> pour naviguer
+    dans la page d'aide. Appelez la commande <tt>help</tt> de nouveau pour
+    revenir à l'écran.
+  <tag>INFO<label id="command-INFO"></tag>
+    Va à l'affichage des états (voir la section <ref id="status"
+    name="Affichage de l'état"> pour des détails complets). Cela
+    présente un résumé comprenant la position du curseur, la position
+    de la plage braille, et l'état d'un certain nombre de
+    caractéristiques de BRLTTY. Appelez de nouveau
+    cette commande pour revenir à l'écran.
+  <tag>LEARN<label id="command-LEARN"></tag>
+    Entre dans le mode d'apprentissage de commande (voir la section 
+    <ref id="learn"
+    name="Mode Apprentissage des commandes"> pour des détails complets). C'est ainsi
+    que vous pouvez apprendre de façon interactive ce que font les
+    touches de votre afficheur braille. Rappelez cette commande pour
+    revenir à l'écran. Cette commande n'est pas disponible si l'option
+    de compilation <ref id="build-learn-mode"
+    name="--disable-learn-mode"> a été spécifiée.
+</descrip>
+
+<sect2>Maintenance des préférences<label id="preference-maintenance"><p>
+<descrip>
+  <tag>PREFMENU<label id="command-PREFMENU"></tag>
+    Accès au menu des préférences (voir <ref id="preferences-menu" name="Le
+    Menu des préférences"> pour des détails complets). Rappelez cette
+    commande pour revenir à l'écran.
+  <tag>PREFSAVE<label id="command-PREFSAVE"></tag>
+    Enregistre les paramètres de préférence courants (voir <ref
+    id="preferences" name="Preferences"> pour des détails complets).
+  <tag>PREFLOAD<label id="command-PREFLOAD"></tag>
+    Recharge les derniers paramètres de préférence sauvegardés
+    (voir <ref id="preferences" name="Preferences"> pour des
+    détails complets).
+</descrip>
+
+<sect2>Navigation dans le menu<label id="menu-navigation"><p>
+<descrip>
+  <tag>MENU_FIRST_ITEM/MENU_LAST_ITEM<label id="command-MENU_FIRST_ITEM-MENU_LAST_ITEM"></tag>
+    Va au premier/dernier élément du menu.
+  <tag>MENU_PREV_ITEMMENU_NEXT_ITEM/<label id="command-MENU_PREV_ITEM-MENU_NEXT_ITEM"></tag>
+    Va à l'élément précédent/suivant du menu.
+  <tag>MENU_PREV_SETTING/MENU_NEXT_SETTING<label id="command-MENU_PREV_SETTING-MENU_NEXT_SETTING"></tag>
+    Remonte/descend le paramètre des éléments du menu courant.
+</descrip>
+
+<sect2>Contrôles de la verbosité<label id="speech-controls"><p>
+<descrip>
+  <tag>SAY_LINE<label id="command-SAY_LINE"></tag>
+    Dit la ligne courante. La préférence <ref
+    id="preference-sayline-mode" name="Mode Dire la ligne"> détermine si le
+    message en attente est proposé en premier.
+  <tag>SAY_ABOVE<label id="command-SAY_ABOVE"></tag>
+    Dit la portion supérieure de l'écran (qui s'arrête à la ligne actuelle).
+  <tag>SAY_BELOW<label id="command-SAY_BELOW"></tag>
+    Dit la portion inférieure de l'écran (qui commence à la ligne courante).
+  <tag>MUTE<label id="command-MUTE"></tag>
+    Arrête immédiatement la parole.
+  <tag>SPKHOME<label id="command-SPKHOME"></tag>
+    Va où se trouve le curseur parlant.
+  <tag>SAY_SLOWER/SAY_FASTER<label id="command-SAY_SLOWER-SAY_FASTER"></tag>
+    Diminue/accélère le débit de parole (voir aussi la préférence 
+    <ref id="preference-speech-rate" name="Vitesse de la synthèse">).
+    Cette commande n'est disponible que si un pilote qui la supporte est utilisé.
+  <tag>SAY_SOFTER/SAY_LOUDER<label id="command-SAY_SOFTER-SAY_LOUDER"></tag>
+    Diminue/augmente le volume de la parole (voir aussi la préférence 
+    <ref id="preference-speech-volume" name="Volume de la synthèse">).
+    Cette commande n'est disponible que si un pilote qui la supporte est utilisé.
+</descrip>
+
+<sect2>Verbosigé dans la navigation<label id="speech-navigation"><p>
+<descrip>
+ <tag/SPEAK_CURR_CHAR/a
+</descrip>
+<sect2>Aller à un terminal virtuel<p>
+Voir aussi la commande de touche de chemin <ref id="command-SWITCHVT" name="SWITCHVT">.
+<descrip>
+  <tag>SWITCHVT_PREV/SWITCHVT_NEXT<label id="command-SWITCHVT_PREV-SWITCHVT_NEXT"></tag>
+    Va à la console précédente/suivante.
+</descrip>
+
+<sect2>Autres Commandes<p>
+<descrip>
+  <tag>CSRJMP_VERT<label id="command-CSRJMP_VERT"></tag>
+    Amène le curseur n'importe où sur la première ligne de la plage
+    braille (voir <ref id="routing" name="Déplacement du Curseur"> pour des détails complets).
+    Le curseur se déplace grâce à la simulation de l'appui sur les touches de direction verticales.
+    Cette commande ne fonctionne pas toujours car certaines applications,
+    soit déplacent le curseur sans vraiment avertir, soit utilisent
+    les flèches de direction à d'autres fins que le déplacement du curseur.
+    C'est toutefois un peu plus sûr que les autres commandes de déplacement du
+    curseur car cette commande ne tente pas de simuler les flèches gauche et
+    droite.
+  <tag>PASTE<label id="command-PASTE"></tag>
+    Insère les caractères du presse-papier à l'emplacement actuel du curseur.
+    (voir <ref id="cut" name="Copier-coller"> pour des détails complets).
+  <tag>RESTARTBRL<label id="command-RESTARTBRL"></tag>
+    Arrête puis relance le pilote de l'afficheur braille.
+  <tag>RESTARTSPEECH<label id="command-RESTARTSPEECH"></tag>
+    Arrête puis relance le pilote de la synthèse vocale.
+</descrip>
+
+<sect2>Commandes de caractères<label id="commands-characters"><p>
+<descrip>
+  <tag>ROUTE<label id="command-ROUTE"></tag>
+    Déplace le curseur au caractère associé à la routine
+    (voir <ref id="routing" name="Déplacement du Curseur"> pour des
+    détails complets).
+    Le curseur se déplace grâce à la simulation de l'appui sur les touches de direction verticales.
+    Cette commande ne fonctionne pas toujours car certaines applications,
+    soit déplacent le curseur sans vraiment avertir, soit utilisent
+    les flèches de direction à d'autres fins que le déplacement du curseur.
+  <tag>CUTBEGIN<label id="command-CUTBEGIN"></tag>
+    Positionne le début du bloc à copier au caractère associé à la routine-curseur.
+    (voir <ref id="cut" name="Copier-coller"> pour des détails complets).
+    Cette commande vide le presse-papier.
+  <tag>CUTAPPEND<label id="command-CUTAPPEND"></tag>
+    Positionne le début du bloc à copier au caractère associé à la routine
+    (voir <ref id="cut" name="Copier-coller"> pour des détails complets).
+    Cette commande ne vide pas le presse-papier.
+  <tag>CUTRECT<label id="command-CUTRECT"></tag>
+    Fixe la fin du bloc à copier au caractère associé à la touche de direction,
+    et met la fenêtre rectangulaire dans le presse-papier (voir
+    <ref id="cut" name="Copier/Coller"> pour des détails complets).
+  <tag>CUTLINE<label id="command-CUTLINE"></tag>
+    Fixe la fin du bloc à copier au caractère associé à la touche de
+    direction, et met la fenêtre linéaire dans le presse-papier (voir
+    <ref id="cut" name="Cut and Paste"> pour des détails complets).
+  <tag>COPYCHARS<label id="command-COPYCHARS"></tag>
+    Copie le bloc de caractères marqué par les deux touches de routine
+    dans le presse-papier 
+    (voir <ref id="cut" name="Copier et coller"> pour tous les détails).
+  <tag>APNDCHARS<label id="command-APNDCHARS"></tag>
+    Insère le bloc de caractères marqués par les deux touches de routine
+    dans le presse-papier
+    (voir <ref id="cut" name="Copier et coller"> pour tous les détails).
+  <tag>PRINDENT/NXINDENT<label id="command-PRINDENT-NXINDENT"></tag>
+    Monte/descend à la ligne la plus proche qui n'est pas indentée plus
+    que la colonne associée à la routine.
+  <tag>DESCCHAR<label id="command-DESCCHAR"></tag>
+    Affiche momentanément (voir l'option <ref
+    id="options-message-timeout" name="-M">
+    en ligne de commande) un message décrivant le caractère associé à
+    la routine. Cela montre les valeurs décimales et
+    hexadécimales du caractère, les couleurs de fond et de premier
+    plan, et, lorsqu'ils sont présents, les attributs spéciaux
+    (<tt>bright</tt> et <tt>blink</tt>). Le message ressemble à cela:
+    <tscreen>char 65 (0x41): white on black bright blink</tscreen>
+  <tag>SETLEFT<label id="command-SETLEFT"></tag>
+    Replace horrizontalement la plage braille de façon à ce que son
+    côté gauche soit à la colonne associée à la touche de
+    déplacement. Cette caractéristique rend très facile l'action de
+    mettre la plage précisément là où il faut et, donc, pour les
+    afficheurs qui ont des touches routines, élimine presque la
+    nécessité pour beaucoup de déplacements élémentaires de la plage
+    (comme les commandes <ref id="command-CHRLT-CHRRT"
+    name="CHRLT/CHRRT"> et <ref id="command-HWINLT-HWINRT" name="HWINLT/HWINRT">).
+  <tag>PRDIFCHAR/NXDIFCHAR<label id="command-PRDIFCHAR-NXDIFCHAR"></tag>
+    Monte/descend à la ligne la plus proche contenant un caractère différent sur la
+    colonne associée à la routine curseur.
+</descrip>
+
+<sect2>Commandes de base<label id="commands-base"><p>
+<descrip>
+  <tag>SWITCHVT<label id="command-SWITCHVT"></tag>
+    Bascule vers le terminal virtuel dont le numéro (en commençant à 1)
+    correspond à la routine curseur.
+    Voir aussi les commandes de basculement entre terminaux virtuels
+    <ref id="command-SWITCHVT_PREV-SWITCHVT_NEXT" name="SWITCHVT_PREV/SWITCHVT_NEXT">.
+  <tag>SETMARK<label id="command-SETMARK"></tag>
+    Marque la position courante de la plage braille dans
+    une mémoire associée à la routine. Voir la commande <ref id="command-GOTOMARK" name="GOTOMARK">.
+    Cette caractéristique n'affecte que le terminal virtuel courant.
+  <tag>GOTOMARK<label id="command-GOTOMARK"></tag>
+    Déplace la plage braille à la position précédemment marquée
+    (voir la commande <ref id="command-SETMARK" name="SETMARK">) avec
+    la même routine.
+    Cette fonction n'affecte que le terminal virtuel courant.
+</descrip>
+
+<sect1>Le fichier de configuration<label id="configure"><p>
+
+Des valeurs système par défaut pour certains paramètres peuvent être
+établies dans un fichier de configuration. Le nom par défaut de ce
+fichier est <tt>/etc/brltty.conf</tt>, même s'il peut être contourné
+par l'option en ligne de commande <ref id="options-configuration-file" name="-f">.
+Son existence n'est pas indispensable.
+Vous pouvez trouver un fichier type dans le sous-répertoire
+<tt>Documents</tt>
+
+Les lignes vides sont ignorées.
+Un commentaire commence par un signe nombre (<tt>&num;</tt>), et continue jusqu'à
+la fin de la ligne.
+Les lignes de commande suivantes sont reconnues:
+<descrip>
+  <tag><tt>api-parameters</tt> <em>name</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="configure-api-parameters"></tag>
+    Spécifie les paramètres pour l'API.
+    Si vous spécifiez le même paramètre plus d'une fois, sa
+    valeur située le plus à droite est utilisée. Pour une description
+    des paramètres acceptés par l'interface, voyez le manuel de
+    référence <bf>BrlAPI</bf>. Voir l'option de compilation <ref id="build-api-parameters" name="--with-api-parameters">
+    pour les paramètres par défaut établis lors de la procédure de
+    compilation. Cette ligne de commande peut être évitée avec
+    l'option en ligne de commande <ref id="options-api-parameters" name="-A">.
+<tag><tt/attributes-table/ <em/file/<label id="configure-attributes-table"></tag>
+    Spécifie la table d'attributs (voir la section
+    <ref id="table-attributes" name="Tables d'attributs"> pour des détails).
+    Si vous fournissez un chemin relatif, il est déterminé par rapport à <tt>/etc/brltty</tt>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root">
+    pour plus de détails).
+    L'extension <tt>.atb</tt> est facultative.
+    Le comportement par défaut est d'utiliser la table compilée en dur (voir l'option de
+    compilation <ref id="build-attributes-table" name="--with-attributes-table">).
+    Vous pouvez vous passer de cette ligne de commande avec l'option
+    en ligne de commande <ref id="options-attributes-table" name="-a">.
+  <tag><tt>braille-device</tt> <em>device</em><tt>,</tt>...<label id="configure-braille-device"></tag>
+    Spécifie le périphérique auquel l'afficheur braille est connecté
+    (voir la section <ref id="operand-braille-device" name="Spécification du périphérique braille">).
+    Voir l'option de compilation <ref id="build-braille-device" name="--with-braille-device">
+    pour les paramètres par défaut établis durant la procédure de compilation.
+    Vous pouvez vous passer de cette ligne de commande avec
+    l'option <ref id="options-braille-device" name="-d">.
+  <tag><tt>braille-driver</tt> <em>pilote</em><tt>,</tt>...|<tt>auto</tt><label id="configure-braille-driver"></tag>
+    Spécifie le pilote de l'afficheur braille (voir la section
+    <ref id="operand-driver" name="Spécification du pilote">). Par défaut, c'est
+    une  autodétection  qui est effectuée.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande 
+    <ref id="options-braille-driver" name="-b">.
+  <tag><tt>braille-parameters</tt> [<em>pilote</em><tt>:</tt>]<em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="configure-braille-parameters"></tag>
+    Spécifie les paramètres pour les pilotes de l'afficheur
+    braille. Si vous spécifiez le même paramètre plus d'une fois,
+    c'est sa valeur placée le plus à droite qui est utilisée. Si le
+    nom d'un paramètre est qualifié par celui d'un pilote (voir la
+    section <ref id="drivers" name="Codes d'identification de pilote">)
+    ce paramètre ne s'applique qu'à ce pilote; sinon il s'applique à
+    tous les pilotes. Par défaut, c'est une  autodétection  qui est effectuée.
+    Pour une description des paramètres acceptés par un pilote en
+    particulier, voyez la documentation de ce pilote.
+    Voir l'option de compilation <ref id="build-braille-parameters" name="--with-braille-parameters">
+    pour les paramètres par défaut établis lors de la procédure de
+    compilation.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-braille-parameters" name="-B">.
+  <tag><tt>contraction-table</tt> <em>fichier</em><label id="configure-contraction-table"></tag>
+    Spécifie la table de braille abrégé (voir la section <ref id="table-contraction"
+    name="Braille abrégé"> pour des détails).
+    Si vous fournissez un chemin relatif, il est déterminé par rapport à <tt>/etc/brltty</tt>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root"> pour
+    plus de détails.
+    L'extension <tt>.ctb</tt> est facultative.
+    La table de braille abrégé est utilisée quand la fonction de braille
+    6 points est activée (voir la commande <ref id="command-SIXDOTS" name="SIXDOTS">
+    et la préférence <ref id="preference-text-style" name="Apparence du texte">).
+    Par défaut, l'affichage est en braille 6 points intégral.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-contraction-table" name="-c">.
+    Elle n'est pas disponible si l'option de compilation <ref
+    id="build-contracted-braille" name="--disable-contracted-braille">
+    a été spécifiée.
+  <tag><tt/key-table/ <em/file/|<tt/auto/<label id="configure-key-table"></tag>
+    Spécifie la table de touches
+    (voir la section <ref id="table-key" name="Tables de touches"> pour plus de détails).
+    Si vous fournissez un chemin relatif, il est ancré sur <tt>/etc/brltty</>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root"> pour plus de 
+    détails).
+    L'extension <tt/.ktb/ est facultative.
+    Par défaut, aucune table de touches n'est utilisée.
+    L'effet de cette instruction peut être annulé par l'option en ligne de
+    commande
+    <ref id="options-key-table" name="-k">.
+  <tag><tt/keyboard-properties/ <em/name/<tt/=/<em/value/<tt/,/...<label id="configure-keyboard-properties"></tag>
+    Spécifie les propriétés du/des claviers(s) qui vont être surveillés (monitored).
+    Si vous spécifiez plus d'une fois la même propriété, sa valeur la plus à 
+    droite est utilisée. 
+    Voir la section <ref id="keyboard-properties" name="Propriétés du clavier">
+    pour une liste des propriétés que vous pouvez spécifier. Par défaut, on gère
+    tous les claviers. Vous pouvez annuler l'effet de cette instruction avec 
+    l'option en ligne de commande 
+    <ref id="options-keyboard-properties" name="-K">.
+
+  <tag><tt>midi-device</tt> <em>device</em><label id="configure-midi-device"></tag>
+    Spécifie le périphérique à utiliser pour l'interface MIDI
+    (voir la section <ref id="operand-midi-device"
+    name="Spécification du périphérique MIDI">).
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-midi-device" name="-m">.
+    Elle n'est pas disponible si vous avez spécifié l'option de
+    compilation <ref id="build-midi-support" name="--disable-midi-support">.
+  <tag><tt>pcm-device</tt> <em>device</em><label id="configure-pcm-device"></tag>
+    Spécifie le périphérique à utiliser pour le son (voir la
+    section <ref id="operand-pcm-device" name="Spécification du
+    périphérique PCM">).
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-pcm-device" name="-p">.
+    Elle n'est pas disponible si vous avez spécifié l'option de
+    compilation <ref id="build-pcm-support" name="--disable-pcm-support">.
+  <tag><tt>screen-parameters</tt> <em>name</em><tt>=</tt><em>value</em><tt>,</tt>...<label id="configure-screen-parameters"></tag>
+    Spécifie les paramètres pour le pilote d'écran.
+    Si le même paramètre est spécifié plus d'une fois, sa valeur la
+    plus à droite est utilisée. Pour une description des paramètres
+    acceptés par un pilote en particulier, voyez la documentation de
+    ce pilote. Voir l'option de compilation <ref id="build-screen-parameters" name="--with-screen-parameters">
+    pour les paramètres par défaut établis pendant la procédure de
+    compilation.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-screen-parameters" name="-X">.
+  <tag><tt/release-device/ <em/boolean/<label id="configure-release-device"></tag>
+    Crée ou non le périphérique auquel l'afficheur braille est connecté lorsque
+    l'écran ou la fenêtre courant ne peuvent pas être lus.
+    <descrip>
+      <tag/on/Créer le périphérique.
+      <tag/off/Ne pas créer le périphérique.
+    </descrip>
+    Par défaut, le réglage est <tt/on/ sur les plateformes Windows et
+    <tt/off/ on sur les autres plateformes. Vous pouvez annuler l'effet de
+    cette instruction avec l'option en ligne de commande
+    <ref id="options-release-device" name="-r">.
+   <tag><tt/screen-driver/ <em/driver/<label id="configure-screen-driver"></tag>
+    Voir l'option de compilation <ref id="build-braille-device" name="--with-screen-driver">.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-screen-driver" name="-x">.
+  <tag><tt>screen-parameters</tt> <em>pilote</em><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="configure-screen-parameters"></tag>
+    Spécifie les paramètres pour les pilotes d'écran.
+    Si le même paramètre est spéciié plus d'une fois, sa valeur la
+    plus à droite est utilisée. Si un nom de paramètre est affecté à un pilote
+    (voir section Pilotes d'écran supportés), ce paramètre ne s'applique qu'au 
+    pilote; sinon il s'applique à tous les pilotes. Pour une description des paramètres
+    acceptés par un pilote en particulier, voir la documentation de
+    ce pilote. Voir l'option de compilation <ref id="build-screen-parameters" name="--with-screen-parameters">
+    pour les paramètres par défaut établis pendant la procédure de
+    compilation.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-screen-parameters" name="-X">.
+
+  <tag><tt>speech-driver</tt> <em>driver</em><tt>,</tt>...|<tt>auto</tt><label id="configure-speech-driver"></tag>
+    Spécifie le pilote de synthèse vocales (voir la section 
+    <ref id="operand-driver" name="Spécification du pilote de synthèse">). Par
+    défaut, une autodétection est effectuée.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-speech-driver" name="-s">.
+    Elle n'est pas disponible si vous avez spécifié l'option de
+    compilation <ref id="build-speech-support" name="--disable-speech-support">.
+  <tag><tt>speech-input</tt> <em>file</em><label id="configure-speech-input"></tag>
+    Spécifie le nom de l'objet du système de fichiers (FIFO, tuyau (pipe)
+    nommé, socket nommée, etc) que d'autres applications peuvent utiliser pour
+    convertir du texte en parole avec le pilote de synthèse de BRLTTY.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-speech-input" name="-F">.
+    Elle n'est pas disponible si vous avez spécifié <ref id="build-speech-support" name="--disable-speech-support">.
+  <tag><tt>speech-parameters</tt> [<em>driver</em><tt>:</tt>]<em>name</em><tt>=</tt><em>value</em><tt>,</tt>...<label id="configure-speech-parameters"></tag>
+    Spécifie les paramètres pour les pilotes de synthèse vocale. Si
+    vous spécifiez plus d'une fois le paramètre, c'est sa valeur le
+    plus à droite qui est utilisée. 
+    Si le nom d'un paramètre est qualifié par le nom d'un pilote (voir
+    la section <ref id="drivers" name="Codes d'identification de pilote">)
+    ce paramètre s'applique seulement à ce pilote; sinon il s'applique
+    pour tous les pilotes.
+    Pour une description des paramètres acceptés par un pilote en
+    particulier, voyez la documentation de ce pilote. Voir l'option de
+    compilation <ref id="build-speech-parameters" name="--with-speech-parameters">
+    pour les paramètres par défaut établis durant la procédure de compilation.
+    Vous pouvez éviter cette ligne avec l'option en ligne de commande
+    <ref id="options-speech-parameters" name="-S">.
+  <tag><tt/text-table/ <em/file/|<tt/auto/<label id="configure-text-table"></tag>
+    Spécifie la table de texte (voir la section <ref
+    id="table-text" name="Tables de texte"> pour les détails).
+    Si vous fournissez un chemin relatif, il est déterminé par rapport à <tt>/etc/brltty</tt>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root">.
+    pour plus de détails).
+    L'extension <tt>.ttb</tt> est facultative.
+    Pour un nom de fichier simple, le préfixe <tt>text.</tt> est facultatif.
+    Par défaut, on réalise une auto-sélection à partir de la locale
+    avec utilisation de la table compilée en dur en cas de problème
+    (voir l'option de compilation <ref id="build-text-table" name="--with-text-table">).
+    L'effet de cette instruction peut être annulé avec l'option en ligne de commande
+    <ref id="options-text-table" name="-t">.
+</descrip>
+
+<sect1>Options en ligne de commande<label id="options"><p>
+Un grand nombre de paramètres peuvent être spécifiés explicitement
+lorsque vous appelez BRLTTY. La commande <tt>brltty</tt> accepte les options
+suivantes:
+<descrip>
+   <tag><tt/-a/<em/file/ <tt/--attributes-table=/<em/file/<label id="options-attributes-table"></tag>
+    Spécifie la table d'attributs (voir la section
+    <ref id="table-attributes" name="Tables d'attributs"> pour les détails).
+    Si vous fournissez un chemin relatif, il est déterminé par rapport à <tt>/etc/brltty</tt>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root">
+    pour plus de détails).
+    L'extension <tt>.atb</tt> est facultative. Si vous ne spécifiez pas cette
+    option, on suppose que c'est <tt/left_right/. Passez-la à <tt/invleft_right/
+    si vous souhaitez l'ancien comportement.
+    Voir la ligne <ref id="configure-attributes-table" name="attributes-table">
+    du fichier de configuration pour la sélection des paramètres par défaut
+    lors de l'exécution.
+    Vous pouvez changer ce paramètre avec la préférence <ref id="preference-attributes-table" name="Table d'attributs">.
+  <tag><tt>-b</tt><em>driver</em><tt>,</tt>...|<tt>auto</tt> <tt>--braille-driver=</tt><em>driver</em><tt>,</tt>...|<tt>auto</tt><label id="options-braille-driver"></tag>
+    Spécifie le pilote de l'afficheur braille (voir la section
+    <ref id="operand-driver" name="Spécification du pilote">).
+    Voir la ligne <ref id="configure-braille-driver" name="braille-driver">
+    du fichier de configuration pour les paramètres par défaut
+    à l'exécution.
+  <tag><tt>-c</tt><em>fichier</em> <tt>--contraction-table=</tt><em>fichier</em><label id="options-contraction-table"></tag>
+    Spécifie la table de braille abrégé (voir la section <ref id="table-contraction" name="Braille abrégé">
+    pour les détails).
+    Si vous fournissez un chemin relatif, il est rattaché à <tt>/etc/brltty</tt>.
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root"> pour plus de
+    détails).
+    L'extension <tt>.ctb</tt> est facultative.
+    La table de braille abrégé est utilisée lorsque la possibilité braille 6
+    points est activée (voir la commande <ref id="command-SIXDOTS" name="SIXDOTS">
+    et la préférence <ref id="preference-text-style" name="Apparence du texte">).
+    Voir la ligne <ref id="configure-contraction-table" name="contraction-table">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+    Vous pouvez changer ce paramètre avec
+    la préférence <ref id="preference-contraction-table" name="Table de braille abrégé">.
+    Cette option n'est pas disponible si l'option de compilation 
+    <ref id="build-contracted-braille" name="--disable-contracted-braille">
+    a été spécifiée.
+  <tag><tt>-d</tt><em>peripherique</em><tt>,</tt>... <tt>--braille-device=</tt><em>device</em><tt>,</tt>...<label id="options-braille-device"></tag>
+    Spécifie le périphérique auquel l'afficheur braille est connecté (voir la
+    section <ref id="operand-braille-device" name="Spécification du périphérique braille">).
+    Voir la ligne <ref id="configure-braille-device" name="braille-device">
+    du fichier de configuration pour les paramètres par défaut à
+    l'exécution.
+  <tag><tt>-e</tt> <tt>--standard-error</tt><label id="options-standard-error"></tag>
+    Ecrit les messages de diagnostic sur la console d'erreur standard (stderr).
+    Par défaut, ils s'enregistrent dans <tt>syslog</tt>.
+  <tag><tt>-f</tt><em>fichier</em> <tt>--configuration-file=</tt><em>file</em><label id="options-configuration-file"></tag>
+    Spécifie l'emplacement du <ref id="configure" name="fichier de configuration">
+    qui doit être utilisé pour l'établissement des paramètres par défaut à l'exécution.
+   <tag><tt/-h/ <tt/--help/<label id="options-help"></tag>
+    Affiche un résumé des options en ligne de commande acceptées par BRLTTY,
+    puis quitte.
+  <tag><tt/-i/<em/nom/ <tt/--speech-input=/<em/nom/<label id="options-speech-input"></tag>
+    Spécifie le nom de l'objet du système de fichiers (FIFO, tuyau (pipe) nommé,
+    socket nommée, etc) que les applications peuvent utiliser pour convertir du
+    texte en parole avec le pilote de synthèse de BRLTTY. Si elle n'est pas
+    spécifiée, l'objet du système de fichiers n'est pas créé.
+    Voir la ligne <ref id="configure-speech-input" name="speech-input"> du
+    fichier de configuration pour le réglage par défaut au moment de l'exécution.
+    Cette option n'est pas disponible si vous avez spécifié l'option de
+    compilation <ref id="build-speech-support" name="--disable-speech-support">.
+  <tag><tt/-k/<em/file/ <tt/--key-table=/<em/file/<label id="options-key-table"></tag>
+    Spécifie la table de touches
+    (voir la section <ref id="table-key" name="Key Tables"> pour plus de détails).
+    Si vous fournissez un chemin relatif, il est ancré à <tt>/etc/brltty</>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root"> pour plus de détails).
+    L'extension <tt/.ktb/ est facultative. Voir l'instruction
+    <ref id="configure-key-table" name="key-table"> du fichier de
+    configuration pour le paramètre d'exécution par défaut. Vous pouvez modifier
+    ce paramètre avec la préférence 
+    <ref id="preference-key-table" name="Table de touches">.
+
+  <tag><tt>-l</tt><em>niveau</em> <tt>--log-level=</tt><em>level</em><label id="options-log-level"></tag>
+    Spécifie le niveau de sécurité pour l'émission des messages de diagnostique.
+    Les niveaux suivants sont reconnus.
+    <descrip>
+      <tag>0</tag>emergency (urgence)
+      <tag>1</tag>alert (avertissement)
+      <tag>2</tag>critical (critique)
+      <tag>3</tag>error (erreur)
+      <tag>4</tag>warning (attention)
+      <tag>5</tag>notice (note)
+      <tag>6</tag>information
+      <tag>7</tag>debug (débogage)
+    </descrip>
+    Vous pouvez fournir soit le numéro soit le nom, et vous pouvez abréger le nom.
+    Si vous ne spécifiez pas cela, c'est <tt>information</tt> qui est utilisé
+    (voir l'option <ref id="options-quiet" name="-q"> pour plus de détails).
+  <tag><tt>-m</tt><em>périphérique</em> <tt>--midi-device=</tt><em>device</em><label id="options-midi-device"></tag>
+    Spécifie le pilote à utiliser pour l'interface MIDI.
+    (voir la section <ref id="operand-midi-device" name="Spécification du périphérique MIDI">).
+    Voir la ligne <ref id="configure-midi-device" name="midi-device">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+    Cette option n'est pas disponible si vous avez spécifié l'option de
+    compilation <ref id="build-midi-support" name="--disable-midi-support">.
+  <tag><tt>-n</tt> <tt>--no-daemon</tt><label id="options-no-daemon"></tag>
+    Indique à BRLTTY qu'il doit rester au premier plan.
+    Si cette option n'est pas spécifiée, BRLTTY devient un processus en
+    tâche de fond (démon) après s'être initialisé mais avant de démarrer tous
+    les pilotes sélectionnés.
+  <tag><tt>-p</tt><em>périphérique</em> <tt>--pcm-device=</tt><em>device</em><label id="options-pcm-device"></tag>
+    Spécifie le périphérique à utiliser pour le son (voir la section
+    (<ref id="operand-pcm-device" name="Spécification du périphérique PCM">).
+    Voir la ligne <ref id="configure-pcm-device" name="pcm-device">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+    Cette option n'est pas disponible si vous avez spécifié l'option de 
+    compilation <ref id="build-pcm-support" name="--disable-pcm-support">.
+  <tag><tt>-q</tt> <tt>--quiet</tt><label id="options-quiet"></tag>
+    Met moins d'informations dans le journal. Cette option passe le niveau de
+    journalisation (voir l'option <ref id="options-log-level" name="-l">
+    à <tt>notice</tt> si vous avez spécifié l'option <ref id="options-verify" name="-v">
+    ou <ref id="options-version" name="-V">,
+    et, sinon, à <tt>warning</tt>.
+  <tag><tt>-r</tt> <tt>--release-device=</tt>...|<tt>auto</tt><label id="options-release-device"></tag>
+    Prend en compte le périphérique auquel est connecté l'afficheur braille 
+    lorsque  l'écran courant ou la fenêtre  ne peuvent pas être lus.
+    Voir la ligne <ref id="configure-speech-driver" name="release-device">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+  <tag><tt>-s</tt><em>périphérique</em><tt>,</tt>...|<tt>auto</tt> <tt>--speech-driver=</tt><em>pilote</em><tt>,</tt>...|<tt>auto</tt><label id="options-speech-driver"></tag>
+    Spécifie le pilote de la synthèse vocale (voir la section 
+    <ref id="operand-driver" name="Spécification de pilote">).
+    Voir la ligne <ref id="configure-speech-driver" name="speech-driver">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+    Cette option n'est pas disponible si vous avez spécifié l'option de compilation
+    <ref id="build-speech-support" name="--disable-speech-support">.
+   <tag><tt/-t/<em/file/ <tt/--text-table=/<em/file/<label id="options-text-table"></tag>
+    Spécifie la table de texte (voir la section
+    <ref id="table-text" name="Tables de texte"> pour les détails).
+    Si vous fournissez un chemin relatif, il est rattaché à <tt>/etc/brltty</tt>
+    (voir les options de compilation <ref id="build-data-directory" name="--with-data-directory">
+    et <ref id="build-execute-root" name="--with-execute-root"> pour plus de
+    détails).
+    L'extension <tt>.ttb</tt> est facultative.
+    Pour un nom de fichier simple, le péfixe <tt>text.</tt> est facultatif.
+    Voir la ligne <ref id="configure-text-table" name="text-table">
+    du fichier de configureation pour les paramètres par défaut à l'exécution.
+    Vous pouvez changer ce paramètre avec la préférence <ref id="preference-text-table" name="Table de texte">.
+  <tag><tt>-v</tt> <tt>--verify</tt><label id="options-verify"></tag>
+    Affiche les versions courantes de BRLTTY, du côté serveur de son
+    API, et des pilotes de synthèse
+    et de braille sélectionnés, puis quitte.
+    Si l'option <ref id="options-quiet" name="-q"> n'est pas spéciifiée,
+    affiche aussi les valeurs des
+    options après que toutes les sources aient été examinées.
+    Si vous avez spécifié plus d'un pilote braille (voir l'option en ligne de
+    commande <ref id="options-braille-driver" name="-b">) et/ou plus d'un
+    périphérique braille (voir l'option en ligne de commande <ref id="options-braille-device" name="-d">),
+    une autodétection de l'afficheur braille est entreprise.
+    Si vous avez spécifié plus d'un pilote de synthèse (voir l'option en ligne
+    de commande <ref id="options-speech-driver" name="-s">), une autodétection
+    de la synthèse vocale est entreprise.
+  <tag><tt>-x</tt><em>pilote</em> <tt>--screen-driver=</tt><em>pilote</em>...<label id="options-screen-driver"></tag>
+    Spécifie le pilote d'écran (voir la section Pilotes d'écran supportés).
+    Voir la ligne <ref id="configure-braille-device" name="screen-driver">
+    du fichier de configuration pour les paramètres par défaut à
+    l'exécution.
+  <tag><tt>-A</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>... <tt>--api-parameters=</tt><em>name</em><tt>=</tt><em>value</em><tt>,</tt>...<label id="options-api-parameters"></tag>
+    Spécifie les paramètres pour l'API.
+    Si vous spécifiez le même paramètre plus d'une fois, c'est sa valeur la
+    plus à droite qui est utilisée. Pour une description des paramètres acceptés
+    par l'interface, reportez-vous au manuel de référence <bf>BrlAPI</bf>.
+    Voir la ligne <ref id="configure-api-parameters" name="api-parameters">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+  <tag><tt>-B</tt>[<em>pilote</em><tt>:</tt>]<em>nom</em><tt>=</tt><em>valeur</em><tt>...</tt> <tt>--braille-parameters=</tt>[<em>pilote</em><tt>:</tt>]<em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<label id="options-braille-parameters"></tag>
+    Spécifie les paramètres pour les pilotes des afficheurs braille. 
+    Si vous spécifiez le même paramètre plus d'une fois, c'est sa valeur le
+    plus à droite qui est utilisée. Si vous qualifiez le nom d'un paramètre par un pilote, (voir
+    la section <ref id="drivers" name="Codes d'identification des pilotes">) ce paramètre
+    ne s'applique qu'à ce pilote; sinon il s'applique à tous les pilotes.
+    Pour une description des paramètres acceptés par un pilote en particulier,
+    reportez-vous à la documentation de ce pilote.
+    Voir la ligne <ref id="configure-braille-parameters" name="braille-parameters">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+  <tag><tt>-E</tt> <tt>--environment-variables</tt><label id="options-environment-variables"></tag>
+    Reconnaît les variables d'environnement quand les paramètres par défaut
+    pour des options en ligne de commandes non spécifiées sont déterminées
+    (voir la section <ref id="options" name="Options en ligne de commande">).
+    Si vous spécifiez cette option, et si vous définissez une variable
+    d'environnement associée à une option non spécifiée, c'est la valeur de cette variable d'environnement qui est
+    utilisée. Les noms de ces variables d'environnement sont basés sur les noms
+    longs des options auxquelles elles correspondent:
+    <itemize>   
+      <item>Toutes les lettres sont en majuscule.
+      <item>Les soulignements (<tt>_</tt>) sont utilisés au lieu du signe moins
+      (<tt>-</tt>).
+      <item>Le préfixe <tt>BRLTTY_</tt> est ajouté.
+    </itemize>   
+    Cette option est particulièrement utile sur les systèmes d'exploitation
+    Linux dans le sens où elle permet de passer des paramètres par défaut à BRLTTY
+    via les paramètres du boot. Les variables d'environnement suivantes sont
+    supportées:
+    <descrip>
+      <tag><tt>BRLTTY_API_PARAMETERS</tt></tag>
+        Paramètres pour l'API (voir l'option
+        en ligne de commande <ref id="options-api-parameters" name="-A">).
+      <tag><tt>BRLTTY_ATTRIBUTES_TABLE</tt></tag>
+        La table d'attributs (voir l'option en ligne de
+        commande <ref id="options-attributes-table" name="-a">).
+      <tag><tt>BRLTTY_BRAILLE_DEVICE</tt></tag>
+        Le périphérique de l'afficheur braille (voir l'option en ligne de commande
+        <ref id="options-braille-device" name="-d">).
+      <tag><tt>BRLTTY_BRAILLE_DRIVER</tt></tag>
+        Le pilote de l'afficheur braille (voir l'option en ligne de commande
+        <ref id="options-braille-driver" name="-b">).
+      <tag><tt>BRLTTY_BRAILLE_PARAMETERS</tt></tag>
+        Paramètres pour le pilote de l'afficheur braille (voir l'option en ligne
+        de commande <ref id="options-braille-parameters" name="-B">).
+      <tag><tt>BRLTTY_CONFIGURATION_FILE</tt></tag>
+        Le fichier de configuration (voir l'option 
+        <ref id="options-configuration-file" name="-f"> de la ligne de commande).
+      <tag><tt/BRLTTY_KEY_TABLE/</tag>
+        La table de touches
+        (voir l'option en ligne de commande <ref id="options-key-table" name="-k">).
+      <tag><tt/BRLTTY_KEYBOARD_PROPERTIES/</tag>
+        Les propriétés du clavier
+        (voir l'option en ligne de commande <ref id="options-keyboard-properties" name="-K">).
+      <tag><tt>BRLTTY_CONTRACTION_TABLE</tt></tag>
+        La table de braille abrégé (voir l'option <ref id="options-contraction-table" name="-c">
+       en ligne de commande).
+      <tag><tt>BRLTTY_MIDI_DEVICE</tt></tag>
+        Le périphérique pour l'interface MIDI (voir 
+        l'option <ref id="options-midi-device" name="-m"> en ligne de commande).
+      <tag><tt>BRLTTY_PCM_DEVICE</tt></tag>
+        Le périphérique audio (voir l'option <ref id="options-pcm-device" name="-p">
+        en ligne de commande).
+      <tag><tt/BRLTTY_PREFERENCES_FILE/</tag>
+        L'emplacement du fichier qui doit être utilisé pour sauvegarder et charger
+        les préférences de l'utilisateur.(voir l'option
+        <ref id="options-preferences-file" name="-F"> en ligne de commande).
+      <tag><tt/BRLTTY_RELEASE_DEVICE/</tag>
+        Crée ou non le périphérique auquel l'afficheur est connecté quand 
+        l'écran ou la fenêtre suivant ne peuvent pas être lus 
+        (voir l'option <ref id="options-release-device" name="-r"> de la ligne de commande).
+      <tag><tt>BRLTTY_SCREEN_PARAMETERS</tt></tag>
+        Paramètres pour le pilote d'écran (voir l'option <ref id="options-screen-parameters" name="-X">
+        en ligne de commande).
+      <tag><tt>BRLTTY_SPEECH_DRIVER</tt></tag>
+        Le pilote de la synthèse vocale (voir l'option <ref id="options-speech-driver" name="-s">
+        en ligne de commande).
+      <tag><tt>BRLTTY_SPEECH_INPUT</tt></tag>
+        Le nom de l'objet du système de fichiers que d'autres applications
+        peuvent utiliser pour convertir du texte en parole avec le pilote de
+        synthèse de BRLTTY (voir l'option <ref id="options-speech-input" name="-i">
+        en ligne de commande).
+      <tag><tt>BRLTTY_SPEECH_PARAMETERS</tt></tag>
+        Paramètres pour le pilote de la synthèse vocale (voir l'option 
+        <ref id="options-speech-parameters" name="-S"> en ligne de commande).
+      <tag><tt/BRLTTY_TEXT_TABLE/</tag>
+        La table de texte (voir l'option
+        <ref id="options-text-table" name="-a"> en ligne de commande).
+    </descrip>
+  <tag><tt/-F/<em/file/ <tt/--preferences-file=/<em/file/<label id="options-preferences-file"></tag>
+    Spécifie l'emplacement du fichier qui doit être utilisé pour sauvegarder et
+    charger les les préférences de l'utilisateur. Si vous spécifiez un chemin
+    relatif, il est encré sur <tt>/var/lib/brltty</>. Voir la ligne
+    <ref id="configure-preferences-file" name="preferences-file"> du fichier de
+    configuration pour le réglage par défaut au moment de l'exécution.
+    qui doit être utilisé par les autres applications souhaitant avoir
+    accès au pilote de synthèse de BRLTTY. Si ce n'est pas spécifié,
+    aucun FIFO n'est créé. Voir la ligne 
+    <ref id="configure-speech-input" name="speech-fifo"> du fichier de
+    configuration pour les paramètres par défaut à l'exécution.
+    Cette option n'est pas disponible si vous avez spécifié l'option
+    de compilation 
+    <ref id="build-speech-support" name="--disable-speech-support">.
+  <tag><tt/-I/ <tt/--install-service/<label id="options-install-service"></tag>
+    Installe BRLTTY et le service BrlAPI.
+    Cela signifie que:
+    <itemize>
+      <item>BRLTTY sera lancé automatiquement quand le système démarrera.
+      <item>Les applications peuvent savoir qu'un serveur BrlAPI est lancé.
+    </itemize>
+    Cette option n'est supportée que sur la plateforme Windows.
+  <tag><tt/-K/<em/name/<tt/=/<em/value/<tt/,/... <tt/--keyboard-properties=/<em/name/<tt/=/<em/value/<tt/,/...<label id="options-keyboard-properties"></tag>
+    Spécifie les propriétés du/des clavier(s) à prendre en charge. Si vous spécifiez
+    la même propriété plus d'une fois, la valeur la plus à droite est utilisée.
+    Voir la section <ref id="keyboard-properties" name="Propriétés du clavier">
+    pour une liste des propriétés que vous pouvez spécifier.
+    Voir l'instruction <ref id="configure-keyboard-properties" name="keyboard-properties">
+    du fichier de configuration pour les paramètres d'exécution par défaut.
+  <tag><tt>-M</tt><em>csecs</em> <tt>--message-timeout=</tt><em>csecs</em><label id="options-message-timeout"></tag>
+    Spécifie le temps (en centièmes de seconde) que prend BRLTTY pour
+    afficher ses propres messages internes sur l'afficheur braille. Si
+    ce n'est pas spécifié, c'est <tt>400</tt> (4 secondes) qui est utlisé.
+  <tag><tt>-N</tt> <tt>--no-api</tt><label id="options-no-speech"></tag>
+    Désactive l'interface de programmation de l'application.
+   <tag><tt/-P/<em/file/ <tt/--pid-file=/<em/file/<label id="options-pid-file"></tag>
+    Spécifie le fichier à l'intérieur duquel BRLTTY doit écrire ses
+    identifiants de processus (pid). Si cela n'est pas spécifié,
+    BRLTTY écrit ses identifiants de processus nulle part.
+  <tag><tt/-R/ <tt/--remove-service/<label id="options-remove-service"></tag>
+    Supprime le service BrlAPI.
+    Cela signifie que:
+    <itemize>
+      <item>BRLTTY ne sera pas lancé automatiquement quand le système démarrera.
+      <item>Les applications peuvent savoir qu'aucun serveur BrlAPI n'est lancé.
+    </itemize>
+    Cette option n'est supportée que sur la plateforme Windows.
+  <tag><tt>-S</tt>[<em>driver</em><tt>:</tt>]<em>name</em><tt>=</tt><em>value</em><tt>,</tt>...
+   <tt>--speech-parameters=</tt>[<em>driver</em><tt>:</tt>]<em>name</em><tt>=</tt><em>value</em><tt>,</tt>...<label id="options-speech-parameters"></tag>
+    Spécifie les paramètres pour les pilotes de synthèse vocale.
+    Si vous spécifiez plus d'une fois le même paramètre, c'est sa
+    valeur la plus à droite qui est utilisée. Si un nom de paramètre
+    est qualifié par un pilote (voir la section
+    <ref id="drivers" name="Code d'identification de pilote">)
+    ce paramètre ne s'applique qu'à ce pilote; sinon il s'applique à
+    tous les pilotes. Pour une description des paramètres acceptés par
+    un pilote en particulier, voyez la documentation de ce pilote.
+    Voir la ligne <ref id="configure-speech-parameters" name="speech-parameters">
+    du fichier de configuration pour les paramètres par défaut à l'exécution.
+  <tag><tt>-V</tt> <tt>--version</tt><label id="options-version"></tag>
+    Affiche les versions courantes de BRLTTY lui-même, de la partie
+    serveur de son API, et des
+    pilotes qui ont été liés à son binaire, puis quitte. Si vous
+    ne spécifiez pas l'option <ref id="options-quiet" name="-q">,
+    affiche aussi les informations légales.
+  <tag><tt>-X</tt><em>nom</em><tt>=</tt><em>valeur</em><tt>,</tt>...<tt>--screen-parameters=</tt><em>pilote</em><tt>=</tt><em>nom</em><tt>valeur</tt>...<label id="options-screen-parameters"></tag>
+    Spécifie les paramètres pour les pilotes d'écran. 
+    Si vous spécifiez plus d'une fois le même paramètre, c'est sa
+    valeur la plus à droite qui est utilisée. Si un paramètre est réservé
+    à un pilote (voir la section Pilotes d'écran supportés), ce paramètre
+    ne s'applique qu'à ce pilote; sinon il s'applique à tous les pilotes. 
+    Pour une description des paramètres acceptés par un pilote en particulier, 
+    reportez-vous à sa documentation. Voir la ligne <ref id="configure-screen-parameters" name="screen-parameters">
+    du fichier de configuration pour la sélection des paramètres par
+    défaut à l'exécution.
+</descrip>
diff --git a/Documents/Manual-BRLTTY/French/braille-drivers.sgml b/Documents/Manual-BRLTTY/French/braille-drivers.sgml
new file mode 100644
index 0000000..9daafb2
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/braille-drivers.sgml
@@ -0,0 +1,155 @@
+Albatross
+  |46/80@
+Alva
+  |ABT (3nn)@
+  |Delphi (4nn)@
+  |Satellite (5nn)@
+  |Braille System 40@
+  |Braille Controller 640/680@
+  |Easy Link 12@
+Baum
+  |BrailleConnect 12/24/32/40/64/80@
+  |Brailliant 24/32/40/64/80@
+  |Conny 12@
+  |DM80 Plus@
+  |EcoVario 24/32/40/64/80@
+  |Inka@
+  |NLS eReader Zoomax@
+  |Orbit Reader 20/40@
+  |PocketVario 24@
+  |Pronto! V3 18/40@
+  |Pronto! V4 18/40@
+  |RBT 40/80@
+  |Refreshabraille 18@
+  |SuperVario 32/40/64/80@
+  |Vario 40/80@
+  |VarioConnect 12/24/32/40/64/80@
+  |VarioPro 40/64/80@
+  |VarioUltra 20/32/40@
+BrailComm
+  |III@
+BrailleLite
+  |18/40/M20/M40@
+BrailleMemo
+  |Pocket (16)@
+  |Smart (16)@
+  |32@
+  |40@
+BrailleNote
+  |18/32@
+  |Apex@
+Braudi@
+BrlAPI@
+Canute
+  |360 (40x9)@
+Cebra
+  |20/40/60/80/100/120/140@
+CombiBraille
+  |25/45/85@
+DotPad@
+EcoBraille
+  |20/40/80@
+EuroBraille
+  |AzerBraille@
+  |Clio@
+  |Esys@
+  |Iris@
+  |NoteBraille@
+  |Scriba@
+FrankAudiodata
+  |B2K84@
+FreedomScientific
+  |Focus 1 44/70/84@
+  |Focus 2 40/80@
+  |Focus Blue 14/40/80@
+  |PAC Mate 20/40@
+HandyTech
+  |Modular 20/40/80@
+  |Modular Evolution 64/88@
+  |Modular Connect 88@
+  |Active Braille@
+  |Active Braille S@
+  |Active Star 40@
+  |Actilino@
+  |Activator@
+  |Basic Braille 16/20/32/40/48/64/80@
+  |Braillino@
+  |Braille Wave@
+  |Easy Braille@
+  |Braille Star 40/80@
+  |Connect Braille 40@
+  |Bookworm@
+Hedo
+  |ProfiLine@
+  |MobilLine@
+HIMS
+  |Braille Sense@
+  |SyncBraille@
+  |Braille Edge@
+  |Smart Beetle@
+  |QBrailleXL@
+HumanWare
+  |Brailliant BI 14/32/40@
+  |Brailliant BI 20X/40X@
+  |Brailliant B 80@
+  |BrailleNote Touch@
+  |BrailleOne@
+  |APH Chameleon 20@
+  |APH Mantis Q40@
+  |NLS eReader@
+Inceptor
+  |BrailleMe (20)@
+Iris@
+Libbraille@
+LogText
+  |32@
+MDV
+  |MB208@
+  |MB248@
+  |MB408L@
+  |MB408L+@
+  |Lilli Blu@
+Metec
+  |BD-40@
+MiniBraille
+  |20@
+MultiBraille
+  |MB125CR@
+  |MB145CR@
+  |MB185CR@
+NinePoint
+  |8@
+Papenmeier
+  |Compact 486@
+  |Compact/Tiny@
+  |IB 80 CR Soft@
+  |2D Lite (plus)@
+  |2D Screen Soft@
+  |EL 80@
+  |EL 2D 40/66/80@
+  |EL 40/66/70/80 S@
+  |EL 40/60/80 C@
+  |EL 2D 80 S@
+  |EL 40 P@
+  |EL 80 II@
+  |Elba 20/32@
+  |Trio 40/Elba20/Elba32@
+  |Live 20/40@
+Pegasus
+  |20/27/40/80@
+Seika
+  |3/4/5 (40)@
+  |80@
+  |Mini (16)@
+TechniBraille@
+TSI
+  |Navigator 20/40/80@
+  |PowerBraille 40/65/80@
+VideoBraille
+  |40@
+VisioBraille
+  |20/40@
+Voyager
+  |44/70@
+  |Part232 (serial adapter)@
+  |BraillePen/EasyLink@
diff --git a/Documents/Manual-BRLTTY/French/contraction-tables.sgml b/Documents/Manual-BRLTTY/French/contraction-tables.sgml
new file mode 100644
index 0000000..15151e4
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/contraction-tables.sgml
@@ -0,0 +1,38 @@
+auto|sélection automatique basée sur la locale@
+af|Afrikaans (abrégé)@
+am|Amharic (désabrégé)@
+de|Allemand@
+de-g0|Allemand (désabrégé)@
+de-g1|Allemand (abréviations de base)@
+de-g2|Allemand (abrégé)@
+de-1998|Allemand (abrégé - standard 1998)@
+de-2015|Allemand (abrégé - standard 2015)@
+en|Anglais@
+en&lowbar;US|Anglais (USA)@
+en-ueb-g1|Anglais (Braille anglais unifié, 1er degré)@
+en-ueb-g2|Anglais (Braille anglais unifié, 2ème degré)@
+en-us-g2|Anglais (USA, 2ème degré)@
+es|Espagnol (2ème degré)@
+fr|Français@
+fr-g1|Français (désabrégé)@
+fr-g2|Français (abrégé)@
+ha|Hausa (abrégé)@
+id|Indonésien (abrégé)@
+ipa|Alphabet phonétique international@
+ja|Japoneais (désabrégé)@
+ko|Coréen@
+ko-g0|Coréen (désabrégé)@
+ko-g1|Coréen (1er degré)@
+ko-g2|Coréen (2ème degré)@
+lt|Lituanien@
+mg|Malagasky (abrégé)@
+mun|Munda (abrégé)@
+nl|Autrichien (abrégé)@
+ny|Chichewa (abrégé)@
+pt|Portuguais (2ème degré)@
+ru|Russe (1er degré)@
+si|Cingalais (désabrégé)@
+sw|Swahili (abrégé)@
+th|Thaïlandais (abrégé)@
+zh&lowbar;TW|Chinois (Taiwan, désabrégé)@
+zu|Zoulou (abrégé)@
diff --git a/Documents/Manual-BRLTTY/French/driver-codes.sgml b/Documents/Manual-BRLTTY/French/driver-codes.sgml
new file mode 100644
index 0000000..0e84ac5
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/driver-codes.sgml
@@ -0,0 +1,55 @@
+al|Alva@
+an|Android@
+at|Albatross@
+ba|BrlAPI@
+bc|BrailComm@
+bd|Braudi@
+bg|B2G@
+bl|BrailleLite@
+bm|Baum (Native, HT, PB1, PB2)@
+bn|BrailleNote@
+cb|CombiBraille@
+ce|Cebra@
+cn|Canute@
+dp|DotPad@
+ec|EcoBraille@
+en|eSpeak-NG@
+es|eSpeak@
+eu|EuroBraille@
+fa|FrankAudiodata@
+fl|FestivalLite@
+fs|FreedomScientific@
+fv|Festival@
+gs|GenericSay@
+hd|Hedo@
+hm|HIMS@
+ht|HandyTech@
+hw|HumanWare@
+ic|Inceptor@
+ir|Iris@
+lb|Libbraille@
+lt|LogText@
+mb|MultiBraille@
+md|MDV@
+mm|BrailleMemo@
+mn|MiniBraille@
+mp|Mikropuhe@
+mt|Metec@
+no|no driver@
+np|NinePoint@
+pg|Pegasus@
+pm|Papenmeier@
+sd|SpeechDispatcher@
+sk|Seika@
+sw|Swift@
+th|Theta@
+tn|TechniBraille@
+ts|Telesensory Systems Inc.@
+tt|TTY@
+vd|VideoBraille@
+vo|Voyager@
+vr|Virtual@
+vs|VisioBraille@
+vv|ViaVoice@
+xs|ExternalSpeech@
+xw|XWindow@
diff --git a/Documents/Manual-BRLTTY/French/fr-2007.sgml b/Documents/Manual-BRLTTY/French/fr-2007.sgml
new file mode 100644
index 0000000..d3d72fb
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/fr-2007.sgml
@@ -0,0 +1,257 @@
+<sect>Table française unifiée<label id="nabcc"><p>
+<tscreen><verb>
+  Num Hex  Points     Description
++-------------------------------------------------------------------------+
+|  0  00   [ 32145 8] code nul                                            |
+|  1  01   [73214 68] début de lecture                                    |
+|  2  02   [  21 5 8] début de texte                                      |
+|  3  03   [ 3214 68] fin de texte                                        |
+|  4  04   [7  145 8] fin de transmission                                 |
+|  5  05   [   1 5 8] ENQUIRY                                             |
+|  6  06   [7 214  8] Connaissance                                        |
+|  7  07   [  2145 8] son (bell)                                          |
+|  8  08   [7 21 5 8] effacement                                          |
+|  9  09   [7 214 68] tabulation                                          |
+|  10 0A   [  2 4568] LINE FEED (LF)                                      |
+|  11 0B   [ 3 1   8] tabulation                                          |
+|  12 0C   [7321   8] pied FORM FEED (FF)                                 |
+|  13 0D   [73 14  8] retour chariot                                      |
+|  14 0E   [ 3 145 8] SHIFT OUT                                           |
+|  15 0F   [ 32   68] SHIFT IN                                            |
+|  16 10   [73214  8] échapper lien noir                                  |
+|  17 11   [732145 8] contrôle de périphérique 1                          |
+|  18 12   [7321 5 8] contrôle de périphérique 2                          |
+|  19 13   [732 4  8] contrôle de périphérique 3                          |
+|  20 14   [732 45 8] contrôle de périphérique 4                          |
+|  21 15   [73 1 6 8] Connaissance négative                               |
+|  22 16   [7321  68] [SYNCHRONOUS IDLE]                                  |
+|  23 17   [7 2 4568] fin du bloc de transmission                         |
+|  24 18   [73 14 68] annule                                              |                                             |
+|  25 19   [ 321  68] fin du milieu                                       |
+|  26 1A   [7  1 568] remplacer                                           |
+|  27 1B   [7 21 568] échappement                                         |
+|  28 1C   [7  14 68] séparateur 4                                        |   
+|  29 1D   [7 21  68] séparateur 3                                        |
+|  30 1E   [732  5  ] séparateur 2                                        |
+|  31 1F   [732   6 ] flèche droite                                       |
+|  32 20   [        ] espace                                              |
+|  33 21   [ 32  5  ] point d'exclamation                                 |
+|  34 22   [ 32  56 ] guillemet                                           |
+|  35 23   [ 3  4568] dièse                                               |
+|  36 24   [73   5  ] dollar                                              |
+|  37 25   [ 3  4 68] pour cent                                           |
+|  38 26   [ 3214568] e commercial                                        |
+|  39 27   [ 3      ] apostrophe                                          |
+|  40 28   [ 32   6 ] parenthèse ouvrant                                  |
+|  41 29   [ 3   56 ] parenthèse fermant                                  |
+|  42 2A   [ 3   5  ] astérisque (étoile)                                 |
+|  43 2B   [732  5 8] plus                                                |
+|  44 2C   [  2     ] virgule                                             |                                       |
+|  45 2D   [ 3    6 ] tiret                                               |
+|  46 2E   [  2  56 ] point                                               |
+|  47 2F   [ 3  4   ] barre oblique                                       |
+|  48 30   [ 3  456 ] zéro                                                |
+|  49 31   [   1  6 ] un                                                  |
+|  50 32   [  21  6 ] deux                                                |
+|  51 33   [   14 6 ] trois                                               |
+|  52 34   [   1456 ] quatre                                              |
+|  53 35   [   1 56 ] cinq                                                |
+|  54 36   [  214 6 ] six                                                 |
+|  55 37   [  21456 ] sept                                                |
+|  56 38   [  21 56 ] huit                                                |
+|  57 39   [  2 4 6 ] neuf                                                |
+|  58 3A   [  2  5  ] deux-points                                         |
+|  59 3B   [ 32     ] inférieur                                           |
+|  60 3C   [ 32    8] inférieur                                           |
+|  61 3D   [732  568] égal                                                |
+|  62 3E   [7    56 ] supérieur                                           |
+|  63 3F   [  2   6 ] point d'interrogation                               |
+|  64 40   [ 3  45  ] arrobas                                             |
+|  65 41   [7  1    ] a majuscule                                         |
+|  66 42   [7 21    ] b majuscule                                         |
+|  67 43   [7  14   ] c majuscule                                         |
+|  68 44   [7  145  ] d majuscule                                         |
+|  69 45   [7  1 5  ] e majuscule                                         |
+|  70 46   [7 214   ] f majuscule                                         |
+|  71 47   [7 2145  ] g majuscule                                         |
+|  72 48   [7 21 5  ] h majuscule                                         |
+|  73 49   [7 2 4   ] i majuscule                                         |
+|  74 4A   [7 2 45  ] j majuscule                                         |
+|  75 4B   [73 1    ] k majuscule                                         |
+|  76 4C   [7321    ] l majuscule                                         |
+|  77 4D   [73 14   ] m majuscule                                         |
+|  78 4E   [73 145  ] n majuscule                                         |
+|  79 4F   [73 1 5  ] o majuscule                                         |
+|  80 50   [73214   ] p majuscule                                         |
+|  81 51   [732145  ] q majuscule                                         |
+|  82 52   [7321 5  ] r majuscule                                         |
+|  83 53   [732 4   ] s majuscule                                         |
+|  84 54   [732 45  ] t majuscule                                         |
+|  85 55   [73 1  6 ] u majuscule                                         |
+|  86 56   [7321  6 ] v majuscule                                         |
+|  87 57   [7 2 456 ] w majuscule                                         |
+|  88 58   [73 14 6 ] x majuscule                                         |
+|  89 59   [73 1456 ] y majuscule                                         |
+|  90 5A   [73 1 56 ] z majuscule                                         |
+|  91 5B   [732   68] crochet ouvrant                                     |
+|  92 5C   [ 3  4  8] barre oblique inversée                              |
+|  93 5D   [73   568] crochet fermant                                     |
+|  94 5E   [    4   ] accent circonflexe                                  |
+|  95 5F   [7    5 8] souligné                                            |
+|  96 60   [      6 ] accent grave                                        |
+|  97 61   [   1    ] a minuscule                                         |
+|  98 62   [  21    ] b minuscule                                         |
+|  99 63   [   14   ] c minuscule                                         |
+| 100 64   [   145  ] d minuscule                                         |
+| 101 65   [   1 5  ] e minuscule                                         |
+| 102 66   [  214   ] f minuscule                                         |
+| 103 67   [  2145  ] g minuscule                                         |
+| 104 68   [  21 5  ] h minuscule                                         |
+| 105 69   [  2 4   ] i minuscule                                         |
+| 106 6A   [  2 45  ] j minuscule                                         |
+| 107 6B   [ 3 1    ] k minuscule                                         |
+| 108 6C   [ 321    ] l minuscule                                         |
+| 109 6D   [ 3 14   ] m minuscule                                         |
+| 110 6E   [ 3 145  ] n minuscule                                         |
+| 111 6F   [ 3 1 5  ] o minuscule                                         |
+| 112 70   [ 3214   ] p minuscule                                         |
+| 113 71   [ 32145  ] q minuscule                                         |
+| 114 72   [ 321 5  ] r minuscule                                         |
+| 115 73   [ 32 4   ] s minuscule                                         |
+| 116 74   [ 32 45  ] t minuscule                                         |
+| 117 75   [ 3 1  6 ] u minuscule                                         |
+| 118 76   [ 321  6 ] v minuscule                                         |  
+| 119 77   [  2 456 ] w minuscule                                         |
+| 120 78   [ 3 14 6 ] x minuscule                                         |
+| 121 79   [ 3 1456 ] y minuscule                                         |
+| 122 7A   [ 3 1 56 ] z minuscule                                         |
+| 123 7B   [732    8] ouvre accolade                                      |
+| 124 7C   [    4568] barre verticale                                     |
+| 125 7D   [7    568] ferme accolade                                      |
+| 126 7E   [ 3     8] tilde minuscule                                     |
+| 127 7F   [ 321   8] effacement                                          |
+| 128 20AC [7  1 5 8] euro                                                |
+| 129 201A [7     6 ] guillemet bas simple                                |
+| 130 0192 [  214  8] florin                                              |
+| 131 201E [     56 ] guillemet bas double                                |
+| 132 2026 [ 3    68] points de suspension                                |
+| 133 2020 [ 3   568] obel                                                |
+| 134 2021 [73   56 ] double obel                                         | 
+| 135 2C6  [    4  8] circonflexe minuscule                               |
+| 136 2030 [73  4 68] pour mille                                          |
+| 137 0160 [732 4 68] s carron majuscule                                  |
+| 138 2039 [7    5  ] guillemet fermant                                   |
+| 139 0152 [7 2 4 6 ] e dans o majuscule                                  |
+| 140 017D [73 1 568] z carron majuscule                                  |
+| 141 2018 [      68] guillemet simple ouvrant                            |
+| 142 2019 [73      ] guillemet simple fermant                            |
+| 143 201C [73     8] guillemet double ouvrant                            |
+| 144 201D [7     68] guillemett double fermant                           |
+| 145 2022 [7 2145 8] puce                                                |
+| 146 2013 [7   4  8] tiret cadre atteint                                 |
+| 147 2014 [7   45 8] tiret EM                                            |
+| 148 02DC [7   4 6 ] tilde minuscule                                     |
+| 149 2122 [ 32 45 8] marque déposée                                      |
+| 150 0161 [ 32 4 68] s avec carron minuscule                             |
+| 151 203A [     5 8] guillemet ouvrant                                   |
+| 152 0153 [  2 4 68] e dans o minuscule                                  |
+| 153 017E [73 1 568] z carron minuscule                                  |
+| 154 0178 [7  14568] y tréma majuscule                                   |
+| 160 A0   [7       ] espace insécable (forcé)                            |
+| 161 A1   [ 32  5 8] point d'exclamation inversé                         |
+| 162 A2   [7  14  8] cent                                                |
+| 163 A3   [732     ] livre                                               |
+| 164 A4   [    45  ] devise                                              |
+| 165 A5   [7 2  568] yen                                                 |
+| 166 A6   [    45 8] trait coupé vertical                                |
+| 167 A7   [ 3214  8] paragraphe                                          |
+| 168 A8   [    4 6 ] tréma                                               |
+| 169 A9   [   14  8] copyright                                           |
+| 170 AA   [7  1  68] ordinal féminin                                     |
+| 171 AB   [ 32  568] guillemet français ouvrant                          |
+| 172 AC   [7 2  56 ] négation logique                                    |
+| 173 AD   [7      8] trait d'union conditionne                           |
+| 174 AE   [ 321 5 8] marque déposée                                      |
+| 175 AF   [ 3 14  8] macron                                              |
+| 176 B0   [7 2   6 ] degré                                               |
+| 177 B1   [73    68] plus ou moins                                       |
+| 178 B2   [7   45  ] carré (puissance 2)                                 |
+| 179 B3   [7   456 ] cube (puissance 3)                                  |
+| 180 B4   [     5  ] accent aigu                                         |
+| 181 B5   [7 2  5  ] micro                                               |
+| 182 B6   [7   4568] pied de mouche                                      |
+| 183 B7   [       8] point médiant                                       |
+| 184 B8   [    456 ] cédille                                             |
+| 185 B9   [7   4   ] exposant                                            |
+| 186 BA   [7 2   68] ordinal masculin                                    |
+| 187 BB   [732  56 ] guillemetts français fermant                        |
+| 188 BC   [ 3 1  68] un quart                                            |
+| 189 BD   [    4 68] un demi                                             |
+| 190 BE   [ 3 14 68] trois quart                                         |
+| 191 BF   [  2   68] point d'interrogation inversé                       |
+| 192 C0   [7321 56 ] a grave majuscule                                   |
+| 193 C1   [7321 568] a aigu majuscule                                    |
+| 194 C2   [7  1  6 ] a circonflexe majuscule                             |
+| 195 C3   [7  1   8] a tilde majuscule                                   |
+| 196 C4   [73  456 ] a tréma majuscule                                   |
+| 197 C5   [7 2     ] a avec rond au-dessus majuscule                     |
+| 198 C6   [73  45  ] a e majuscule                                       |
+| 199 C7   [73214 6 ] c cédille majuscule                                 |
+| 200 C8   [732 4 6 ] e grave majuscule                                   |
+| 201 C9   [7321456 ] e aigu majuscule                                    |
+| 202 CA   [7 21  6 ] e circonflexe majuscule                             |
+| 203 CB   [7 214 6 ] e tréma majuscule                                   |
+| 204 CC   [7 2 4  8] i grave majuscule                                   |
+| 205 CD   [73  4   ] i aigu majuscule                                    |
+| 206 CE   [7  14 6 ] i circonflexe majuscule                             |
+| 207 CF   [7 21456 ] i tréma majuscule                                   |
+| 208 D0   [7 21   8] Eth majuscule                                       |
+| 209 D1   [73 145 8] n tilde majuscule                                   |
+| 210 D2   [73 1 5 8] o grave majuscule                                   |
+| 211 D3   [73  4 6 ] o aigu majuscule                                    |
+| 212 D4   [73 1456 ] o circonflexe majuscule                             |
+| 213 D5   [73 1   8] o tilde majuscule                                   |
+| 214 D6   [7 2 4 68] o tréma majuscule                                   |
+| 215 D7   [73   5 8] multipilé par                                       |
+| 216 D8   [73  4568] o barré majuscule                                   |
+| 217 D9   [732 456 ] u grave majuscule                                   |
+| 218 DA   [732 4568] u aigu majuscule                                    |
+| 219 DB   [7  1 56 ] u circonflexe majuscule                             |
+| 220 DC   [7 21 56 ] u tréma majuscule                                   |
+| 221 DD   [73 14568] y aigu majuscule                                    |
+| 222 DE   [  2 45 8] Thorn majuscule                                     |
+| 223 DF   [ 32 4  8] szeth                                               |
+| 224 E0   [ 321 56 ] a grave minuscule                                   |
+| 225 E1   [ 321 568] a aigu minuscule                                    |
+| 226 E2   [   1  68] a circonflexe minuscule                             |
+| 227 E3   [   1   8] a qilde minuscule                                   |
+| 228 E4   [73  45 8] a tréma minuscule                                   |
+| 229 E5   [  2    8] a avec rond en tête minuscule                       |
+| 230 E6   [ 3  45 8] a e minuscule                                       |
+| 231 E7   [ 3214 6 ] c cédille minuscule                                 |
+| 232 E8   [ 32 4 6 ] e grave minuscule                                   |
+| 233 E9   [ 321456 ] e aigu minuscule                                    |
+| 234 EA   [  21  68] e circonflexe minuscule                             |
+| 235 EB   [  214 68] e tréma minuscule                                   | 
+| 236 EC   [  2 4  8] i grave minuscule                                   |
+| 237 ED   [73  4  8] i aigu minuscule                                    |
+| 238 EE   [   14 68] i circonflexe minuscule                             |
+| 239 EF   [  214568] i tréma minuscule                                   |
+| 240 F0   [  21   8] Eth minuscule                                       |
+| 241 F1   [7 214568] n tilde minuscule                                   |
+| 242 F2   [ 3 1 5 8] o grave minuscule                                   |
+| 243 F3   [ 3  4 6 ] o aigu minuscule                                    |
+| 244 F4   [   14568] o circonflexe minuscule                             |
+| 245 F5   [7   4 68] o tilde minuscule                                   |
+| 246 F6   [ 3   5 8] o tréma minuscule                                   |
+| 247 F7   [7 2  5 8] divisé par                                          |
+| 248 F8   [     568] o barré minuscule                                   |
+| 249 F9   [ 32 456 ] u grave minuscule                                   |
+| 250 FA   [ 32 4568] u aigu minuscule                                    |
+| 251 FB   [   1 568] u circonflexe minuscule                             |
+| 252 FC   [  21 568] u tréma minuscule                                   |
+| 253 FD   [ 3 14568] y aigu minuscule                                    |
+| 254 FE   [  2 45 8] thorn minuscule                                     |
+| 255 FF   [  2  568] y tréma minuscule                                   |
++-------------------------------------------------------------------------+
+</verb></tscreen>
diff --git a/Documents/Manual-BRLTTY/French/nabcc.sgml b/Documents/Manual-BRLTTY/French/nabcc.sgml
new file mode 100644
index 0000000..f44c2bd
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/nabcc.sgml
@@ -0,0 +1,262 @@
+<sect>Code informatique braille Nord Américain<label id="nabcc"><p>
+<tscreen><verb>
+  Num Hex     Points     Description
++-----------------------------------------------------------------+
+|   0  00  [7   4  8]  NUL (null)                                 |
+|   1  01  [7  1   8]  SOH (start of header)                      |
+|   2  02  [7 21   8]  STX (start of text)                        |
+|   3  03  [7  14  8]  ETX (end of text)                          |
+|   4  04  [7  145 8]  EOT (end of transmission)                  |
+|   5  05  [7  1 5 8]  ENQ (enquiry)                              |
+|   6  06  [7 214  8]  ACK (acknowledge)                          |
+|   7  07  [7 2145 8]  BEL (bell)                                 |
+|   8  08  [7 21 5 8]  BS (back space)                            |
+|   9  09  [7 2 4  8]  HT (horizontal tab)                        |
+|  10  0A  [7 2 45 8]  LF (line feed)                             |
+|  11  0B  [73 1   8]  VT (vertical tab)                          |
+|  12  0C  [7321   8]  FF (form feed)                             |
+|  13  0D  [73 14  8]  CR (carriage return)                       |
+|  14  0E  [73 145 8]  SO (shift out)                             |
+|  15  0F  [73 1 5 8]  SI (shift in)                              |
+|  16  10  [73214  8]  DLE (data link escape)                     |
+|  17  11  [732145 8]  DC1 (direct control 1)                     |
+|  18  12  [7321 5 8]  DC2 (direct control 2)                     |
+|  19  13  [732 4  8]  DC3 (direct control 3)                     |
+|  20  14  [732 45 8]  DC4 (direct control 4)                     |
+|  21  15  [73 1  68]  NAK (negative acknowledge)                 |
+|  22  16  [7321  68]  SYN (synchronize)                          |
+|  23  17  [7 2 4568]  ETB (end of text block)                    |
+|  24  18  [73 14 68]  CAN (cancel)                               |
+|  25  19  [73 14568]  EM (end of medium)                         |
+|  26  1A  [73 1 568]  SUB (substitute)                           |
+|  27  1B  [7 2 4 68]  ESC (escape)                               |
+|  28  1C  [7 21 568]  FS (file separator)                        |
+|  29  1D  [7 214568]  GS (group separator)                       |
+|  30  1E  [7   45 8]  RS (record separator)                      |
+|  31  1F  [7   4568]  US (unit separator)                        |
+|  32  20  [        ]  space                                      |
+|  33  21  [ 32 4 6 ]  exclamation point                          |
+|  34  22  [     5  ]  quotation mark                             |
+|  35  23  [ 3  456 ]  number sign                                |
+|  36  24  [  214 6 ]  signe dollar                                |
+|  37  25  [   14 6 ]  signe pour cent                               |
+|  38  26  [ 3214 6 ]  ampersand                                  |
+|  39  27  [ 3      ]  accent aigu                               |
+|  40  28  [ 321 56 ]  left parenthesis                           |
+|   4  291  [ 32 456 ) right parenthesis                          |
+|  42  2A  [   1  6 ]  asterisk                                   |
+|  43  2B  [ 3  4 6 ]  plus sign                                  |
+|  44  2C  [      6 ]  comma                                      |
+|  45  2D  [ 3    6 ]  minus sign                                 |
+|  46  2E  [    4 6 ]  period                                     |
+|  47  2F  [ 3  4   ]  forward slash                              |
+|  48  30  [ 3   56 ]  zero                                       |
+|  49  31  [  2     ]  one                                        |
+|  50  32  [ 32     ]  two                                        |
+|  51  33  [  2  5  ]  three                                      |
+|  52  34  [  2  56 ]  four                                       |
+|  53  35  [  2   6 ]  five                                       |
+|  54  36  [ 32  5  ]  six                                        |
+|  55  37  [ 32  56 ]  seven                                      |
+|  56  38  [ 32   6 ]  eight                                      |
+|  57  39  [ 3   5  ]  nine                                       |
+|  58  3A  [   1 56 ]  colon                                      |
+|  59  3B  [     56 ]  semicolon                                  |
+|  60  3C  [  21  6 ]  less-than sign                             |
+|  61  3D  [ 321456 ]  equals sign                                |
+|  62  3E  [ 3  45  ]  greater-than sign                          |
+|  63  3F  [   1456 ]  question mark                              |
+|  64  40  [7   4   ]  commercial at                              |
+|  65  41  [7  1    ]  capital a                                  |
+|  66  42  [7 21    ]  capital b                                  |
+|  67  43  [7  14   ]  capital c                                  |
+|  68  44  [7  145  ]  capital d                                  |
+|  69  45  [7  1 5  ]  capital e                                  |
+|  70  46  [7 214   ]  capital f                                  |
+|  71  47  [7 2145  ]  capital g                                  |
+|  72  48  [7 21 5  ]  capital h                                  |
+|  73  49  [7 2 4   ]  capital i                                  |
+|  74  4A  [7 2 45  ]  capital j                                  |
+|  75  4B  [73 1    ]  capital k                                  |
+|  76  4C  [7321    ]  capital l                                  |
+|  77  4D  [73 14   ]  capital m                                  |
+|  78  4E  [73 145  ]  capital n                                  |
+|  79  4F  [73 1 5  ]  capital o                                  |
+|  80  50  [73214   ]  capital p                                  |
+|  81  51  [732145  ]  capital q                                  |
+|  82  52  [7321 5  ]  capital r                                  |
+|  83  53  [732 4   ]  capital s                                  |
+|  84  54  [732 45  ]  capital t                                  |
+|  85  55  [73 1  6 ]  capital u                                  |
+|  86  56  [7321  6 ]  capital v                                  |
+|  87  57  [7 2 456 ]  capital w                                  |
+|  88  58  [73 14 6 ]  capital x                                  |
+|  89  59  [73 1456 ]  capital y                                  |
+|  90  5A  [73 1 56 ]  capital z                                  |
+|  91  5B  [7 2 4 6 ]  left bracket                               |
+|  92  5C  [7 21 56 ]  backward slash                             |
+|  93  5D  [7 21456 ]  right bracket                              |
+|  94  5E  [7   45  ]  circumflex accent                          |
+|  95  5F  [    456 ]  underscore                                 |
+|  96  60  [    4   ]  grave accent                               |
+|  97  61  [   1    ]  small a                                    |
+|  98  62  [  21    ]  small b                                    |
+|  99  63  [   14   ]  small c                                    |
+| 100  64  [   145  ]  small d                                    |
+| 101  65  [   1 5  ]  small e                                    |
+| 102  66  [  214   ]  small f                                    |
+| 103  67  [  2145  ]  small g                                    |
+| 104  68  [  21 5  ]  small h                                    |
+| 105  69  [  2 4   ]  small i                                    |
+| 106  6A  [  2 45  ]  small j                                    |
+| 107  6B  [ 3 1    ]  small k                                    |
+| 108  6C  [ 321    ]  small l                                    |
+| 109  6D  [ 3 14   ]  small m                                    |
+| 110  6E  [ 3 145  ]  small n                                    |
+| 111  6F  [ 3 1 5  ]  small o                                    |
+| 112  70  [ 3214   ]  small p                                    |
+| 113  71  [ 32145  ]  small q                                    |
+| 114  72  [ 321 5  ]  small r                                    |
+| 115  73  [ 32 4   ]  small s                                    |
+| 116  74  [ 32 45  ]  small t                                    |
+| 117  75  [ 3 1  6 ]  small u                                    |
+| 118  76  [ 321  6 ]  small v                                    |
+| 119  77  [  2 456 ]  small w                                    |
+| 120  78  [ 3 14 6 ]  small x                                    |
+| 121  79  [ 3 1456 ]  small y                                    |
+| 122  7A  [ 3 1 56 ]  small z                                    |
+| 123  7B  [  2 4 6 ]  left brace                                 |
+| 124  7C  [  21 56 ]  vertical bar                               |
+| 125  7D  [  21456 ]  right brace                                |
+| 126  7E  [    45  ]  tilde accent                               |
+| 127  7F  [7   456 ]  DEL (delete)                               |
+| 128  80  [    4  8]  <control>                                  |
+| 129  81  [   1   8]  <control>                                  |
+| 130  82  [  21   8]  BPH (break permitted here)                 |
+| 131  83  [   14  8]  NBH (no break here)                        |
+| 132  84  [   145 8]  <control>                                  |
+| 133  85  [   1 5 8]  NL (next line)                             |
+| 134  86  [  214  8]  SSA (start of selected area)               |
+| 135  87  [  2145 8]  ESA (end of selected area)                 |
+| 136  88  [  21 5 8]  CTS (character tabulation set)             |
+| 137  89  [  2 4  8]  CTJ (character tabulation justification)   |
+| 138  8A  [  2 45 8]  LTS (line tabulation set)                  |
+| 139  8B  [ 3 1   8]  PLD (partial line down)                    |
+| 140  8C  [ 321   8]  PLU (partial line up)                      |
+| 141  8D  [ 3 14  8]  RLF (reverse line feed)                    |
+| 142  8E  [ 3 145 8]  SS2 (single shift two)                     |
+| 143  8F  [ 3 1 5 8]  SS3 (single shift three)                   |
+| 144  90  [ 3214  8]  DCS (device control string)                |
+| 145  91  [ 32145 8]  PU1 (private use one)                      |
+| 146  92  [ 321 5 8]  PU2 (private use two)                      |
+| 147  93  [ 32 4  8]  STS (set transmit state)                   |
+| 148  94  [ 32 45 8]  CC (cancel character)                      |
+| 149  95  [ 3 1  68]  MW (message waiting)                       |
+| 150  96  [ 321  68]  SGA (start of guarded area)                |
+| 151  97  [  2 4568]  EGA (end of guarded area)                  |
+| 152  98  [ 3 14 68]  SS (start of string)                       |
+| 153  99  [ 3 14568]  <control>                                  |
+| 154  9A  [ 3 1 568]  SCI (single character introducer)          |
+| 155  9B  [  2 4 68]  CSI (control sequence introducer)          |
+| 156  9C  [  21 568]  ST (string terminator)                     |
+| 157  9D  [  214568]  OSC (operating system command)             |
+| 158  9E  [    45 8]  PM (privacy message)                       |
+| 159  9F  [    4568]  APC (application program command)          |
+| 160  A0  [7      8]  no-break space                             |
+| 161  A1  [732 4 6 ]  inverted exclamation mark                  |
+| 162  A2  [7 214 6 ]  cent sign                                  |
+| 163  A3  [73  456 ]  pound sign                                 |
+| 164  A4  [7  14 6 ]  currency sign                              |
+| 165  A5  [73214 6 ]  yen sign                                   |
+| 166  A6  [7  1 56 ]  broken bar                                 |
+| 167  A7  [73   5  ]  section sign                               |
+| 168  A8  [7    5  ]  diaeresis                                  |
+| 169  A9  [732  56 ]  copyright sign                             |
+| 170  AA  [       8]  feminine ordinal indicator                 |
+| 171  AB  [7 21  6 ]  left-pointing double angle quotation mark  |
+| 172  AC  [7 2  56 ]  not sign                                   |
+| 173  AD  [73    6 ]  soft hyphen                                |
+| 174  AE  [732   6 ]  registered sign                            |
+| 175  AF  [7 2   6 ]  macron                                     |
+| 176  B0  [73   56 ]  degree sign                                |
+| 177  B1  [73  4 6 ]  plus-minus sign                            |
+| 178  B2  [732     ]  superscript two                            |
+| 179  B3  [7 2  5  ]  superscript three                          |
+| 180  B4  [73      ]  acute accent                               |
+| 181  B5  [7    56 ]  micro sign                                 |
+| 182  B6  [732  5  ]  pilcrow sign                               |
+| 183  B7  [7   4 6 ]  middle dot                                 |
+| 184  B8  [7     6 ]  cedilla                                    |
+| 185  B9  [7 2     ]  superscript one                            |
+| 186  BA  [7       ]  masculine ordinal indicator                |
+| 187  BB  [73  45  ]  right-pointing double angle quotation mark |
+| 188  BC  [7321 56 ]  vulgar fraction one quarter                |
+| 189  BD  [7321456 ]  vulgar fraction one half                   |
+| 190  BE  [732 456 ]  vulgar fraction three quarters             |
+| 191  BF  [7  1456 ]  inverted question mark                     |
+| 192  C0  [732  5 8]  capital a grave                            |
+| 193  C1  [7  1  68]  capital a acute                            |
+| 194  C2  [7 2    8]  capital a circumflex                       |
+| 195  C3  [7    5 8]  capital a tilde                            |
+| 196  C4  [73214 68]  capital a diaeresis                        |
+| 197  C5  [73  45 8]  capital a ring above                       |
+| 198  C6  [73     8]  capital ae                                 |
+| 199  C7  [73  4 68]  capital c cedilla                          |
+| 200  C8  [732  568]  capital e grave                            |
+| 201  C9  [7 21  68]  capital e acute                            |
+| 202  CA  [732    8]  capital e circumflex                       |
+| 203  CB  [73214568]  capital e diaeresis                        |
+| 204  CC  [732   68]  capital i grave                            |
+| 205  CD  [7  14 68]  capital i acute                            |
+| 206  CE  [7 2  5 8]  capital i circumflex                       |
+| 207  CF  [7321 568]  capital i diaeresis                        |
+| 208  D0  [7     68]  capital eth                                |
+| 209  D1  [7   4 68]  capital n tilde                            |
+| 210  D2  [73   5 8]  capital o grave                            |
+| 211  D3  [7  14568]  capital o acute                            |
+| 212  D4  [7 2  568]  capital o circumflex                       |
+| 213  D5  [7    568]  capital o tilde                            |
+| 214  D6  [732 4 68]  capital o diaeresis                        |
+| 215  D7  [7  1  6 ]  multiplication sign                        |
+| 216  D8  [73  4  8]  capital o stroke                           |
+| 217  D9  [73   568]  capital u grave                            |
+| 218  DA  [7  1 568]  capital u acute                            |
+| 219  DB  [7 2   68]  capital u circumflex                       |
+| 220  DC  [732 4568]  capital u diaeresis                        |
+| 221  DD  [7 214 68]  capital y acute                            |
+| 222  DE  [73    68]  capital thorn                              |
+| 223  DF  [73  4568]  small sharp s                              |
+| 224  E0  [ 32  5 8]  small a grave                              |
+| 225  E1  [   1  68]  small a acute                              |
+| 226  E2  [  2    8]  small a circumflex                         |
+| 227  E3  [     5 8]  small a tilde                              |
+| 228  E4  [ 3214 68]  small a diaeresis                          |
+| 229  E5  [ 3  45 8]  small a ring above                         |
+| 230  E6  [ 3     8]  small ae                                   |
+| 231  E7  [ 3  4 68]  small c cedilla                            |
+| 232  E8  [ 32  568]  small e grave                              |
+| 233  E9  [  21  68]  small e acute                              |
+| 234  EA  [ 32    8]  small e circumflex                         |
+| 235  EB  [ 3214568]  small e diaeresis                          |
+| 236  EC  [ 32   68]  small i grave                              |
+| 237  ED  [   14 68]  small i acute                              |
+| 238  EE  [  2  5 8]  small i circumflex                         |
+| 239  EF  [ 321 568]  small i diaeresis                          |
+| 240  F0  [      68]  small eth                                  |
+| 241  F1  [    4 68]  small n tilde                              |
+| 242  F2  [ 3   5 8]  small o grave                              |
+| 243  F3  [   14568]  small o acute                              |
+| 244  F4  [  2  568]  small o circumflex                         |
+| 245  F5  [     568]  small o tilde                              |
+| 246  F6  [ 32 4 68]  small o diaeresis                          |
+| 247  F7  [73  4   ]  division sign                              |
+| 248  F8  [ 3  4  8]  small o stroke                             |
+| 249  F9  [ 3   568]  small u grave                              |
+| 250  FA  [   1 568]  small u acute                              |
+| 251  FB  [  2   68]  small u circumflex                         |
+| 252  FC  [ 32 4568]  small u diaeresis                          |
+| 253  FD  [  214 68]  small y acute                              |
+| 254  FE  [ 3    68]  small thorn                                |
+| 255  FF  [ 3  4568]  small y diaeresis                          |
++-----------------------------------------------------------------+
+</verb></tscreen>
diff --git a/Documents/Manual-BRLTTY/French/speech-drivers.sgml b/Documents/Manual-BRLTTY/French/speech-drivers.sgml
new file mode 100644
index 0000000..ede82d3
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/speech-drivers.sgml
@@ -0,0 +1,26 @@
+Alva
+  |Delphi (4nn)@
+BrailleLite
+  |@
+CombiBraille
+  |@
+eSpeak
+  |text to speech engine@
+eSpeak-NG
+  |text to speech engine@
+ExternalSpeech
+  |runs /usr/local/bin/externalspeech@
+Festival
+  |text to speech engine@
+FestivalLite
+  |text to speech engine@
+GenericSay
+  |pipes to /usr/local/bin/say@
+Mikropuhe
+  |text to speech engine@
+Swift
+  |text to speech engine@
+Theta
+  |text to speech engine@
+ViaVoice
+  |text to speech engine@
diff --git a/Documents/Manual-BRLTTY/French/text-tables.sgml b/Documents/Manual-BRLTTY/French/text-tables.sgml
new file mode 100644
index 0000000..d632cf8
--- /dev/null
+++ b/Documents/Manual-BRLTTY/French/text-tables.sgml
@@ -0,0 +1,90 @@
+auto|sélection automatique basée sur la locale@
+ar|Arabe (générique)@
+as|Assamese@
+awa|Awadhi@
+bg|Bulgare@
+bh|Bihari@
+bn|Bengali@
+bo|Tibétin@
+bra|Braj@
+brf|Format prêt-à-embosser@
+|(pour voir des fichiers .brf dans un éditeur ou un pager)@
+cs|Tchèque@
+cy|Gallois@
+da|Danois@
+da-1252|Danois (Svend Thougaard, 18-11-2002)@
+da-lt|Danois (LogText)@
+de|Allemand@
+dra|Dravidian@
+el|Grec@
+en|Anglais@
+en&lowbar;CA|Anglais (Canada)@
+en&lowbar;GB|Anglais (Royaume-Uni)@
+en&lowbar;US|Anglais (États-Unis)@
+en-nabcc|Anglais (code informatique braille nord-amçricain)@
+eo|Esperanto@
+es|Espagnol@
+et|Estonien@
+fi|Finnois@
+fr|Français@
+fr&lowbar;CA|Français (Canada)@
+fr&lowbar;FR|Français (France)@
+fr-2007|Français (unifié 2007)@
+fr-cbifs|Français (Code Braille Informatique Français Standard)@
+fr-vs|Français (VisioBraille)@
+ga|Irlandais@
+gd|Gaélique@
+gon|Gondi@
+gu|Gujrati@
+he|Hébreux@
+hi|Hindi@
+hr|Croate@
+hu|Hongrois@
+hy|Arménien@
+is|Islandais@
+it|Italien@
+kha|Khasi@
+kn|Kannada@
+kok|Konkani@
+kru|Kurukh@
+lt|Lituanien@
+lv|Léton@
+mg|Malagasy@
+mi|Maori@
+ml|Malayalam@
+mni|Manipuri@
+mr|Marathi@
+mt|Maltaiss@
+mun|Munda@
+mwr|Marwari@
+ne|Népalais@
+new|Newari@
+nl|Autrichien@
+nl&lowbar;BE|Autrichien (Belge)@
+nl&lowbar;NL|Autrichien (Pays-Bas)@
+no|Norvégien@
+no-generic|Norvégien (avec le support des autres langues)@
+no-oup|Norvégien (Offentlig Utvalg for punktskrift)@
+nwc|Newari (ancien)@
+or|Oriya@
+pa|Panjabi@
+pi|Pali@
+pl|Polonais@
+pt|Portuguais@
+ro|Roumain@
+ru|Russe@
+sa|Sanscrit@
+sat|Santali@
+sd|Sindhi@
+se|Sami (du Nord)@
+sk|Slovaque@
+sl|Slovène@
+sv|Suédois@
+sv-1989|Suédois (standard 1989)@
+sv-1996|Suédois (standard 1996)@
+sw|Swahili@
+ta|Tamoul@
+te|Telugu@
+tr|Turc@
+uk|Ukrainien@
+vi|Vietnamien@
diff --git a/Documents/Manual-BRLTTY/Portuguese/BRLTTY.doc b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.doc
new file mode 100644
index 0000000..728928b
--- /dev/null
+++ b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.doc
Binary files differ
diff --git a/Documents/Manual-BRLTTY/Portuguese/BRLTTY.htm b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.htm
new file mode 100644
index 0000000..8a49a27
--- /dev/null
+++ b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.htm
@@ -0,0 +1,18195 @@
+<html xmlns:o="urn:schemas-microsoft-com:office:office"

+xmlns:w="urn:schemas-microsoft-com:office:word"

+xmlns:st1="urn:schemas-microsoft-com:office:smarttags"

+xmlns="http://www.w3.org/TR/REC-html40">

+

+<head>

+<meta http-equiv=Content-Type content="text/html; charset=windows-1252">

+<meta name=ProgId content=Word.Document>

+<meta name=Generator content="Microsoft Word 11">

+<meta name=Originator content="Microsoft Word 11">

+<link rel=File-List href="BRLTTY%20Reference%20Manual_arquivos/filelist.xml">

+<title>  BRLTTY Reference Manual</title>

+<o:SmartTagType namespaceuri="urn:schemas-microsoft-com:office:smarttags"

+ name="PersonName"/>

+<o:SmartTagType namespaceuri="urn:schemas-microsoft-com:office:smarttags"

+ name="metricconverter"/>

+<!--[if gte mso 9]><xml>

+ <o:DocumentProperties>

+  <o:Author>Win</o:Author>

+  <o:LastAuthor>Win</o:LastAuthor>

+  <o:Revision>2</o:Revision>

+  <o:TotalTime>1296</o:TotalTime>

+  <o:LastPrinted>2011-12-14T16:14:00Z</o:LastPrinted>

+  <o:Created>2012-01-04T16:47:00Z</o:Created>

+  <o:LastSaved>2012-01-04T16:47:00Z</o:LastSaved>

+  <o:Pages>1</o:Pages>

+  <o:Words>34801</o:Words>

+  <o:Characters>187931</o:Characters>

+  <o:Company>Particular</o:Company>

+  <o:Lines>1566</o:Lines>

+  <o:Paragraphs>444</o:Paragraphs>

+  <o:CharactersWithSpaces>222288</o:CharactersWithSpaces>

+  <o:Version>11.5606</o:Version>

+ </o:DocumentProperties>

+</xml><![endif]--><!--[if gte mso 9]><xml>

+ <w:WordDocument>

+  <w:HideSpellingErrors/>

+  <w:HyphenationZone>21</w:HyphenationZone>

+  <w:PunctuationKerning/>

+  <w:ValidateAgainstSchemas/>

+  <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>

+  <w:IgnoreMixedContent>false</w:IgnoreMixedContent>

+  <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>

+  <w:Compatibility>

+   <w:BreakWrappedTables/>

+   <w:SnapToGridInCell/>

+   <w:WrapTextWithPunct/>

+   <w:UseAsianBreakRules/>

+   <w:DontGrowAutofit/>

+  </w:Compatibility>

+  <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel>

+ </w:WordDocument>

+</xml><![endif]--><!--[if gte mso 9]><xml>

+ <w:LatentStyles DefLockedState="false" LatentStyleCount="156">

+ </w:LatentStyles>

+</xml><![endif]--><!--[if !mso]><object

+ classid="clsid:38481807-CA0E-42D2-BF39-B33AF135CC4D" id=ieooui></object>

+<style>

+st1\:*{behavior:url(#ieooui) }

+</style>

+<![endif]-->

+<style>

+<!--

+ /* Font Definitions */

+ @font-face

+	{font-family:Helvetica;

+	panose-1:2 11 6 4 2 2 2 2 2 4;

+	mso-font-charset:0;

+	mso-generic-font-family:swiss;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:Courier;

+	panose-1:2 7 4 9 2 2 5 2 4 4;

+	mso-font-charset:0;

+	mso-generic-font-family:modern;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:"Tms Rmn";

+	panose-1:2 2 6 3 4 5 5 2 3 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:Helv;

+	panose-1:2 11 6 4 2 2 2 3 2 4;

+	mso-font-charset:0;

+	mso-generic-font-family:swiss;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:"New York";

+	panose-1:2 4 5 3 6 5 6 2 3 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:System;

+	panose-1:0 0 0 0 0 0 0 0 0 0;

+	mso-font-charset:0;

+	mso-generic-font-family:swiss;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:Wingdings;

+	panose-1:5 0 0 0 0 0 0 0 0 0;

+	mso-font-charset:2;

+	mso-generic-font-family:auto;

+	mso-font-pitch:variable;

+	mso-font-signature:0 268435456 0 0 -2147483648 0;}

+@font-face

+	{font-family:"MS Mincho";

+	panose-1:2 2 6 9 4 2 5 8 3 4;

+	mso-font-alt:"\FF2D\FF33 \660E\671D";

+	mso-font-charset:128;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 134676480 16 0 131072 0;}

+@font-face

+	{font-family:Batang;

+	panose-1:2 3 6 0 0 1 1 1 1 1;

+	mso-font-alt:\BC14\D0D5;

+	mso-font-charset:129;

+	mso-generic-font-family:auto;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 151388160 16 0 524288 0;}

+@font-face

+	{font-family:SimSun;

+	panose-1:2 1 6 0 3 1 1 1 1 1;

+	mso-font-alt:\5B8B\4F53;

+	mso-font-charset:134;

+	mso-generic-font-family:auto;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:1 135135232 16 0 262144 0;}

+@font-face

+	{font-family:PMingLiU;

+	panose-1:2 1 6 1 0 1 1 1 1 1;

+	mso-font-alt:\65B0\7D30\660E\9AD4;

+	mso-font-charset:136;

+	mso-generic-font-family:auto;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:1 134742016 16 0 1048576 0;}

+@font-face

+	{font-family:"MS Gothic";

+	panose-1:2 11 6 9 7 2 5 8 2 4;

+	mso-font-alt:"\FF2D\FF33 \30B4\30B7\30C3\30AF";

+	mso-font-charset:128;

+	mso-generic-font-family:modern;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 134676480 16 0 131072 0;}

+@font-face

+	{font-family:Dotum;

+	panose-1:2 11 6 0 0 1 1 1 1 1;

+	mso-font-alt:\B3CB\C6C0;

+	mso-font-charset:129;

+	mso-generic-font-family:modern;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 151388160 16 0 524288 0;}

+@font-face

+	{font-family:SimHei;

+	panose-1:2 1 6 0 3 1 1 1 1 1;

+	mso-font-alt:\9ED1\4F53;

+	mso-font-charset:134;

+	mso-generic-font-family:modern;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 135135232 16 0 262144 0;}

+@font-face

+	{font-family:MingLiU;

+	panose-1:2 1 6 9 0 1 1 1 1 1;

+	mso-font-alt:\7D30\660E\9AD4;

+	mso-font-charset:136;

+	mso-generic-font-family:modern;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 134742016 16 0 1048576 0;}

+@font-face

+	{font-family:Mincho;

+	panose-1:2 2 6 9 4 3 5 8 3 5;

+	mso-font-alt:\660E\671D;

+	mso-font-charset:128;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 134676480 16 0 131072 0;}

+@font-face

+	{font-family:Gulim;

+	panose-1:2 11 6 0 0 1 1 1 1 1;

+	mso-font-alt:\AD74\B9BC;

+	mso-font-charset:129;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:fixed;

+	mso-font-signature:1 151388160 16 0 524288 0;}

+@font-face

+	{font-family:Century;

+	panose-1:2 4 6 4 5 5 5 2 3 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:"Angsana New";

+	panose-1:2 2 6 3 5 4 5 2 3 4;

+	mso-font-charset:222;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:16777217 0 0 0 65536 0;}

+@font-face

+	{font-family:"Cordia New";

+	panose-1:2 11 3 4 2 2 2 2 2 4;

+	mso-font-charset:222;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:16777217 0 0 0 65536 0;}

+@font-face

+	{font-family:Mangal;

+	panose-1:0 0 4 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:32768 0 0 0 0 0;}

+@font-face

+	{font-family:Latha;

+	panose-1:2 0 4 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:1048576 0 0 0 0 0;}

+@font-face

+	{font-family:Sylfaen;

+	panose-1:1 10 5 2 5 3 6 3 3 3;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:16778883 0 512 0 13 0;}

+@font-face

+	{font-family:Vrinda;

+	panose-1:0 0 4 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:Raavi;

+	panose-1:2 0 5 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:Shruti;

+	panose-1:2 0 5 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:Sendnya;

+	panose-1:0 0 4 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:Gautami;

+	panose-1:2 0 5 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:Tunga;

+	panose-1:0 0 4 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:"Estrangelo Edessa";

+	panose-1:0 0 0 0 0 0 0 0 0 0;

+	mso-font-charset:1;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:0 0 0 0 0 0;}

+@font-face

+	{font-family:"Arial Unicode MS";

+	panose-1:2 11 6 4 2 2 2 2 2 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:Tahoma;

+	panose-1:2 11 6 4 3 5 4 4 2 4;

+	mso-font-charset:0;

+	mso-generic-font-family:swiss;

+	mso-font-format:other;

+	mso-font-pitch:variable;

+	mso-font-signature:3 0 0 0 1 0;}

+@font-face

+	{font-family:Times;

+	panose-1:2 2 6 3 5 4 5 2 3 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-pitch:variable;

+	mso-font-signature:536902279 -2147483648 8 0 511 0;}

+@font-face

+	{font-family:Cambria;

+	panose-1:2 4 5 3 5 4 6 3 2 4;

+	mso-font-charset:0;

+	mso-generic-font-family:roman;

+	mso-font-pitch:variable;

+	mso-font-signature:-1610611985 1073741899 0 0 159 0;}

+ /* Style Definitions */

+ p.MsoNormal, li.MsoNormal, div.MsoNormal

+	{mso-style-parent:"";

+	margin:0cm;

+	margin-bottom:.0001pt;

+	mso-pagination:widow-orphan;

+	font-size:12.0pt;

+	font-family:"Times New Roman";

+	mso-fareast-font-family:"Times New Roman";}

+h1

+	{mso-style-link:" Char Char1";

+	margin:0cm;

+	margin-bottom:.0001pt;

+	mso-para-margin-top:.01gd;

+	mso-para-margin-right:0cm;

+	mso-para-margin-bottom:.01gd;

+	mso-para-margin-left:0cm;

+	mso-para-margin-bottom:.0001pt;

+	mso-pagination:widow-orphan;

+	mso-outline-level:1;

+	font-size:24.0pt;

+	mso-bidi-font-size:10.0pt;

+	font-family:Times;

+	mso-fareast-font-family:Cambria;

+	mso-bidi-font-family:"Times New Roman";

+	mso-fareast-language:EN-US;

+	font-weight:bold;

+	mso-bidi-font-weight:normal;}

+a:link, span.MsoHyperlink

+	{color:blue;

+	text-decoration:underline;

+	text-underline:single;}

+a:visited, span.MsoHyperlinkFollowed

+	{color:purple;

+	text-decoration:underline;

+	text-underline:single;}

+p.MsoPlainText, li.MsoPlainText, div.MsoPlainText

+	{margin:0cm;

+	margin-bottom:.0001pt;

+	mso-pagination:widow-orphan;

+	font-size:10.0pt;

+	font-family:"Courier New";

+	mso-fareast-font-family:"Times New Roman";}

+p

+	{mso-margin-top-alt:auto;

+	margin-right:0cm;

+	mso-margin-bottom-alt:auto;

+	margin-left:0cm;

+	mso-pagination:widow-orphan;

+	font-size:12.0pt;

+	font-family:"Times New Roman";

+	mso-fareast-font-family:"Times New Roman";}

+span.shorttext

+	{mso-style-name:short_text;}

+span.hps

+	{mso-style-name:hps;}

+span.hpsatn

+	{mso-style-name:"hps atn";}

+span.CharChar1

+	{mso-style-name:" Char Char1";

+	mso-style-locked:yes;

+	mso-style-link:"Título 1";

+	mso-ansi-font-size:24.0pt;

+	font-family:Times;

+	mso-ascii-font-family:Times;

+	mso-fareast-font-family:Cambria;

+	mso-hansi-font-family:Times;

+	mso-font-kerning:18.0pt;

+	mso-ansi-language:PT-BR;

+	mso-fareast-language:EN-US;

+	mso-bidi-language:AR-SA;

+	font-weight:bold;

+	mso-bidi-font-weight:normal;}

+ins

+	{mso-style-type:export-only;

+	text-decoration:none;}

+span.msoIns

+	{mso-style-type:export-only;

+	mso-style-name:"";

+	text-decoration:underline;

+	text-underline:single;}

+span.msoDel

+	{mso-style-type:export-only;

+	mso-style-name:"";

+	text-decoration:line-through;

+	color:red;}

+@page Section1

+	{size:595.3pt 841.9pt;

+	margin:70.85pt 57.6pt 70.85pt 57.6pt;

+	mso-header-margin:35.4pt;

+	mso-footer-margin:35.4pt;

+	mso-paper-source:0;}

+div.Section1

+	{page:Section1;}

+ /* List Definitions */

+ @list l0

+	{mso-list-id:212469199;

+	mso-list-type:hybrid;

+	mso-list-template-ids:1293964110 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l0:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l1

+	{mso-list-id:349841323;

+	mso-list-type:hybrid;

+	mso-list-template-ids:276315108 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l1:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l2

+	{mso-list-id:513375672;

+	mso-list-type:hybrid;

+	mso-list-template-ids:702059810 -893195066 68550681 68550683 68550671 68550681 68550683 68550671 68550681 68550683;}

+@list l2:level1

+	{mso-level-tab-stop:46.5pt;

+	mso-level-number-position:left;

+	margin-left:46.5pt;

+	text-indent:-19.5pt;}

+@list l3

+	{mso-list-id:643848053;

+	mso-list-template-ids:-142190118;}

+@list l3:level1

+	{mso-level-start-at:4;

+	mso-level-text:%1;

+	mso-level-tab-stop:33.75pt;

+	mso-level-number-position:left;

+	margin-left:33.75pt;

+	text-indent:-33.75pt;}

+@list l3:level2

+	{mso-level-text:"%1\.%2";

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	margin-left:36.0pt;

+	text-indent:-36.0pt;}

+@list l3:level3

+	{mso-level-start-at:6;

+	mso-level-text:"%1\.%2\.%3";

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	margin-left:36.0pt;

+	text-indent:-36.0pt;}

+@list l3:level4

+	{mso-level-text:"%1\.%2\.%3\.%4";

+	mso-level-tab-stop:54.0pt;

+	mso-level-number-position:left;

+	margin-left:54.0pt;

+	text-indent:-54.0pt;}

+@list l3:level5

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5";

+	mso-level-tab-stop:72.0pt;

+	mso-level-number-position:left;

+	margin-left:72.0pt;

+	text-indent:-72.0pt;}

+@list l3:level6

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6";

+	mso-level-tab-stop:90.0pt;

+	mso-level-number-position:left;

+	margin-left:90.0pt;

+	text-indent:-90.0pt;}

+@list l3:level7

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7";

+	mso-level-tab-stop:90.0pt;

+	mso-level-number-position:left;

+	margin-left:90.0pt;

+	text-indent:-90.0pt;}

+@list l3:level8

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7\.%8";

+	mso-level-tab-stop:108.0pt;

+	mso-level-number-position:left;

+	margin-left:108.0pt;

+	text-indent:-108.0pt;}

+@list l3:level9

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7\.%8\.%9";

+	mso-level-tab-stop:126.0pt;

+	mso-level-number-position:left;

+	margin-left:126.0pt;

+	text-indent:-126.0pt;}

+@list l4

+	{mso-list-id:795178198;

+	mso-list-type:hybrid;

+	mso-list-template-ids:761433356 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l4:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l5

+	{mso-list-id:869344372;

+	mso-list-type:hybrid;

+	mso-list-template-ids:2120496906 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l5:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l6

+	{mso-list-id:880635059;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-1455003424 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l6:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:71.4pt;

+	mso-level-number-position:left;

+	margin-left:71.4pt;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l7

+	{mso-list-id:1205756943;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-1676400484 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l7:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l8

+	{mso-list-id:1313018946;

+	mso-list-template-ids:-947899814;}

+@list l8:level1

+	{mso-level-start-at:4;

+	mso-level-text:%1;

+	mso-level-tab-stop:33.75pt;

+	mso-level-number-position:left;

+	margin-left:33.75pt;

+	text-indent:-33.75pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level2

+	{mso-level-text:"%1\.%2";

+	mso-level-tab-stop:33.75pt;

+	mso-level-number-position:left;

+	margin-left:33.75pt;

+	text-indent:-33.75pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level3

+	{mso-level-start-at:4;

+	mso-level-text:"%1\.%2\.%3";

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	margin-left:36.0pt;

+	text-indent:-36.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level4

+	{mso-level-text:"%1\.%2\.%3\.%4";

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	margin-left:36.0pt;

+	text-indent:-36.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level5

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5";

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	margin-left:36.0pt;

+	text-indent:-36.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level6

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6";

+	mso-level-tab-stop:54.0pt;

+	mso-level-number-position:left;

+	margin-left:54.0pt;

+	text-indent:-54.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level7

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7";

+	mso-level-tab-stop:54.0pt;

+	mso-level-number-position:left;

+	margin-left:54.0pt;

+	text-indent:-54.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level8

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7\.%8";

+	mso-level-tab-stop:72.0pt;

+	mso-level-number-position:left;

+	margin-left:72.0pt;

+	text-indent:-72.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l8:level9

+	{mso-level-text:"%1\.%2\.%3\.%4\.%5\.%6\.%7\.%8\.%9";

+	mso-level-tab-stop:72.0pt;

+	mso-level-number-position:left;

+	margin-left:72.0pt;

+	text-indent:-72.0pt;

+	mso-ansi-font-size:11.0pt;

+	font-family:"Courier New";

+	mso-bidi-font-family:"Times New Roman";}

+@list l9

+	{mso-list-id:1550798714;

+	mso-list-type:hybrid;

+	mso-list-template-ids:564694874 -762517710 68550681 68550683 68550671 68550681 68550683 68550671 68550681 68550683;}

+@list l9:level1

+	{mso-level-tab-stop:33.75pt;

+	mso-level-number-position:left;

+	margin-left:33.75pt;

+	text-indent:-20.25pt;}

+@list l10

+	{mso-list-id:1555777252;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-916146944 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l10:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:48.9pt;

+	mso-level-number-position:left;

+	margin-left:48.9pt;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l11

+	{mso-list-id:1561013156;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-883007664 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l11:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l12

+	{mso-list-id:1938520230;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-1376909192 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l12:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+@list l13

+	{mso-list-id:2038115422;

+	mso-list-type:hybrid;

+	mso-list-template-ids:-1350387946 68550661 68550659 68550661 68550657 68550659 68550661 68550657 68550659 68550661;}

+@list l13:level1

+	{mso-level-number-format:bullet;

+	mso-level-text:\F0A7;

+	mso-level-tab-stop:36.0pt;

+	mso-level-number-position:left;

+	text-indent:-18.0pt;

+	font-family:Wingdings;}

+ol

+	{margin-bottom:0cm;}

+ul

+	{margin-bottom:0cm;}

+-->

+</style>

+<!--[if gte mso 10]>

+<style>

+ /* Style Definitions */

+ table.MsoNormalTable

+	{mso-style-name:"Tabela normal";

+	mso-tstyle-rowband-size:0;

+	mso-tstyle-colband-size:0;

+	mso-style-noshow:yes;

+	mso-style-parent:"";

+	mso-padding-alt:0cm 5.4pt 0cm 5.4pt;

+	mso-para-margin:0cm;

+	mso-para-margin-bottom:.0001pt;

+	mso-pagination:widow-orphan;

+	font-size:10.0pt;

+	font-family:"Times New Roman";

+	mso-ansi-language:#0400;

+	mso-fareast-language:#0400;

+	mso-bidi-language:#0400;}

+</style>

+<![endif]-->

+</head>

+

+<body lang=PT-BR link=blue vlink=purple style='tab-interval:35.4pt'>

+

+<div class=Section1>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Manual de Referência do BRLTTY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Controle de

+Acesso da Tela para pessoas cegas que usam Display Braille<span

+style='mso-tab-count:1'>    </span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Nikhil Nair &lt;nn201@cus.cam.ac.uk&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Nicolas Pitre &lt;nico@fluxnic.net&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Stéphane Doyon &lt;s.doyon@videotron.ca&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Dave Mielke &lt;dave@mielke.cc&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Tradução para a

+lingua Portuguesa</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Regiane Mendonça

+Villela &lt;regianevillela@gmail.com&gt;</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Filipe Oliveira

+Bernardes &lt;filipeobernardes@gmail.com&gt;</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>Version 4.3, oct 2011<o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Copyright ©

+1995-2011 by Desenvolvedores do BRLTTY. BRLTTY é um software</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>livre, e <span

+class=hps><span lang=PT style='mso-ansi-language:PT'>vem com</span></span><span

+class=shorttext><span lang=PT style='mso-ansi-language:PT'> </span></span><span

+class=hps><span lang=PT style='mso-ansi-language:PT'>ABSOLUTAMENTE SEM NENHUMA

+GARANTIA</span></span>. Ele é postado sob os</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>termos da versão

+2 ou posterior da GNU e publicada pela Fundação de Software</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Livre.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> 

+</span>______________________________________________________________________</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Tabela de Conteúdo</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'> </span>1. Formalidades</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>1.1 Licença</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>1.2 Renúncia</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>1.3 Contato</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>2. Introdução</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>2.1 Sumário </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>2.2 Requisitos

+do Sistema</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>3. Processo de

+compilação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.1 Instalação

+da herança de arquivos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.2 Instalação

+a partir do Tar Ball</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>3.2.1

+Opções de compilação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>         </span>3.2.1.1 Padrões do Sistema</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>3.2.1.2

+Especificações de Diretório</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>3.2.1.3

+Caracteristicas de Compilação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>3.2.1.4

+Opções diversas</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>3.2.2

+Atingir metas de arquivos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.3 Testando o

+BRLTTY</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.4 Iniciando

+o BRLTTY</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.5 Considerações

+de Segurança</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.6 Restrições

+de Compilação e Tempo de Execução</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>3.7 Instalação

+a partir de um arquivo RPM </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>3.8 Outras Utilidades<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.1 brltty-config<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.2 brltty-install<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.3 brltest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.4 spktest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.5 scrtest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.6 ttbtest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.7 ctbtest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>3.8.8 tunetest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>4. Usando BRLTTY</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>4.1 Comandos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.1 Deslocamento

+Vertical </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.2

+Deslocamento Horizontal </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.3

+Deslocamento Implicito </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.4 Caracteristica

+de Ativação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.5 Modo

+de Seleção</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.6

+Preferências de Manutenção</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.7 Menu de

+Navegação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.8

+Controles de Fala</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.9 Comutação

+do Terminal Virtual</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>      </span><span

+style='mso-spacerun:yes'>  </span>4.1.10 Outros Comandos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.11 Caracteres

+de Comandos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>4.1.12 Base

+Commandos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>4.2 Arquivos

+de configuração</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>4.3 Opções de

+linha de Comando</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>5. Descrições de

+caracteristicas</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.1 Roteamento

+de Cursor</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.2 Copiar e Colar</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>   </span><span

+style='mso-spacerun:yes'>  </span>5.3 Suporte de Ponteiro (Mouse) via GPM</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.4 Alerta de Tunes</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.5 Configurações

+de Preferências </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>5.5.1 Menu

+de Preferências</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>5.5.1.1 Menu

+de Navegação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>5.5.1.2 Itens

+de Menu</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.6 Status do Display</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>5.6.1 Displays

+com 21 Células ou Mais</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>5.6.2

+Displays com 20 Células ou Menos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>5.7 Comando de

+Modo de Aprendizagem</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>6. Tabelas</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>6.1 Tabelas de

+Texto</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.1.1 Tabela

+de Formato de Texto</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.1.2 Tabela

+de Diretivas de Texto</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>6.2 Tabela de

+Atributos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.2.1 Tabela

+de Formatos dos Atributos</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.2.2 Tabela

+de Diretórios dos Atributos </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>6.3 Tabela de Contração</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.3.1 Tabela

+de Formato de Contração</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.3.2 Tabela

+de Operandos de Contração</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>6.3.3 Opcodes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>         </span>6.3.3.1 Administração de Tabela</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.3.3.2 Definição

+de Símbolo Especial</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.3.3.3 Tradução

+de Caracter </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.3.3.4 Classes

+de Caracter</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>6.4 Tabelas de

+Teclas</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.4.1 Tabela

+de Diretivas de Teclas</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.1 Diretiva

+de atribuição</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.2 Diretiva

+de vínculo</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.3 Diretiva

+de contexto</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.4 Diretiva

+de ocultar</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.5 Diretiva

+de Hotkey </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>6.4.1.6 Diretiva de IfKey <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.7 Diretiva

+de inclusão</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.8 Diretiva

+de mapa</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.9 Diretiva

+de observação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.10

+Diretiva de sobreposição</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           </span>6.4.1.11

+Diretiva de título</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>6.4.2 Propriedades

+do Teclado</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>7. Tópicos Avançados</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>7.1 Instalação

+de Multiplas Versões</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>7.2 Instalação/Ajuda

+dos Discos de root para Linux</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>7.3 Herança Futura</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>7.4 Bugs

+Conhecidos</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>A. Display de

+Braille Suportados</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>B. Sintetizadores

+de Fala Suportados</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>C. Identificação

+dos Códigos do Driver </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>D. Drivers de Tela

+Suportados</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>E. Sintaxe de

+Operação</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>E.1 Especificação

+do Driver </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>E.2 Especificação

+do Dispositivo Braille </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>E.3 Especificação

+do Dispositivo PCM </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>E.4 Especificação

+do Dispositivo MIDI</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>F. Convenção do

+Padrão Braille para numeração</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>G. Código

+Norte-Americano do Padrão Braille</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>H. Tabela de

+Instrumento MIDI </p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>______________________________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>1. Formalidades</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>1.1 Licença</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>Este programa é um software

+livre. Você poderá redistribuir e/ou modificar sobre os termos do GNU General

+Public Lincense como publicado pela Fundação do Software Livre. Versão 2 (ou

+outra versão posterior) da licença poderá ser usada.</p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>Você deverá receber uma cópia

+da licença junto com este programa. <span class=hps><span lang=PT

+style='mso-ansi-language:PT'>Deve-se</span></span><span lang=PT

+style='mso-ansi-language:PT'> ter <span class=hps>no arquivo de licença</span>-GPL

+<span class=hps>no</span> diretório raiz<span class=hps>.</span> </span><span

+class=hps>Se não estiver, escreva</span> <span class=hps>para a Free</span> <span

+class=hps>Software</span> <span class=hps>Foundation</span> <span class=hps>Inc.</span>,

+675 <span class=hps>Mass</span> <span class=hps>Ave,</span> <span class=hps>Cambridge,</span>

+<span class=hps>MA 02139</span>, EUA.</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>1.2 Renúncia</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>Este programa é distribuído na

+esperança de ser usual, mais <span class=hps><span lang=PT style='mso-ansi-language:

+PT'>vem com</span></span><span class=shorttext><span lang=PT style='mso-ansi-language:

+PT'> </span></span><span class=hps><span lang=PT style='mso-ansi-language:PT'>ABSOLUTAMENTE

+SEM NENHUMA GARANTIA - nem mesmo</span></span><span lang=PT style='mso-ansi-language:

+PT'> <span class=hps>a garantia implícita de</span><br>

+<span class=hps>COMERCIALIZAÇÃO ou</span> <span class=hps>ADEQUAÇÃO</span> <span

+class=hps>PARA UM PROPÓSITO</span> <span class=hps>PARTICULAR. Ver na licença

+GNU para mais detalhes.</span></span><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>1.3 Contato</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>BRLTTY é representado por uma

+equipe de trabalho. Para atualizar a informação dia a dia, no web site BRLTTY

+[http://mielke.cc/brltty]. Composto por:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>·<span style='mso-spacerun:yes'>  </span>Dave

+Mielke (mantenedor, ativo)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span>Web<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span></span>http://mielke.cc/</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>&lt;dave@mielke.cc&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>·<span style='mso-spacerun:yes'> 

+</span>Samuel Thibault (ativo)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span>Web<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>http://dept-info.labri.fr/~thibault/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span></span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>       

+</span>&lt;samuel.thibault@ens-lyon.org&gt;</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>·<span style='mso-spacerun:yes'>  </span>Mario

+Lang (ativo)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span>Web<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span></span>http://delysid.org/</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>       

+</span>&lt;mlang@delysid.org&gt;</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>·<span

+style='mso-spacerun:yes'>  </span>Nicolas Pitre</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>Web<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>http://www.fluxnic.net/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span></span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>&lt;nico@fluxnic.net&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>·<span style='mso-spacerun:yes'>  </span>Stéphane

+Doyon<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span>Web<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>        </span>http://pages.infinit.net/sdoyon/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span></span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>&lt;s.doyon@videotron.ca&gt;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>·<span style='mso-spacerun:yes'> 

+</span>Nikhil Nair (autor)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span></span>Email</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>       

+</span>&lt;nn201@cus.cam.ac.uk&gt;</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt;text-align:justify'>Perguntas,

+comentários, sugestões, criticas, e contribuições são todas bem-<span

+style='mso-spacerun:yes'>    </span><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>    </span><span style='mso-spacerun:yes'>     </span>vindas.

+Usando os endereços de email listados acima, e a melhor maneira de <span

+style='mso-spacerun:yes'> </span>entrar em contato com a lista do BRLTTY. Você

+pode postar na lista enviando um email para <a href="mailto:brltty@mielke.cc"><span

+style='color:windowtext'>brltty@mielke.cc</span></a>. Se você não inscrever na

+lista então seus posts serão mantidos para a aprovação do moderador. Para

+inscrever, remover assinatura, configurações de mudanças, arquivos para

+visualização, etc, vá na <span style='mso-spacerun:yes'>  </span>página <span

+lang=EN-US style='mso-ansi-language:EN-US'><a

+href="http://mielke.cc/mailman/listinfo/brltty"><span lang=PT-BR

+style='color:windowtext;mso-ansi-language:PT-BR'>http://mielke.cc/mailman/listinfo/brltty</span></a></span>.</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'>   </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>2. Introdução</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>BRLTTY dá ao usuário braille acesso

+para o terminal de texto de um sistema Unix/Linux. Ele é executado como um

+processo em segundo plano (daemon) que opera um display Braille atualizável, e

+pode ser iniciado antes da seqüência do sistema de boot. Ele permite que um

+usuário braille trabalhe de forma independente com a administração do sistema,

+tais como modo de entrada única de usuário, recuperação do sistema de arquivos

+e análise de problemas de inicialização. Ele ainda facilita muito as tarefas de

+rotina como ‘logging in’.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'><span class=hps><span lang=PT

+style='mso-ansi-language:PT'>BRLTTY</span></span><span lang=PT

+style='mso-ansi-language:PT'> <span class=hps>reproduz</span> <span class=hps>uma

+parte retangular</span> <span class=hpsatn>da tela (</span>que se refere <span

+class=hps>neste documento</span> <span class=hps>como ‘a</span> <span

+class=hps>janela’)</span> <span class=hps>como texto</span> <span class=hps>braille</span>

+<span class=hps>na tela.</span> <span class=hps>Controles</span> <span

+class=hps>na tela</span> <span class=hps>pode ser usados</span> <span

+class=hps>para mover a janela</span> <span class=hps>em torno da</span> <span

+class=hps>tela,</span> <span class=hps>para ativar e desativar</span> <span

+class=hps>várias opções de visualização</span>, e para realizar <span

+class=hps>funções especiais.<o:p></o:p></span></span></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>2.1 Sumário </p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>BRLTTY oferece os

+seguintes recursos:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Completa

+implementacão das facilidades normais de revisão de tela.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Escolha entre

+block, underline, ou no cursor.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Sublinhado

+Opcional para indicar destaque de texto especial.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>· Uso opcional de alerta

+(velocidade ajustáveis individualmente) para cursor, destaque sublinhado

+especial, e/ou letras maiúsculas.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Congelamento de

+tela para revisão.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>· Cursor inteligente de

+roteamento, permitindo que seja fácil buscar do cursor dentro de editores de

+texto, navegadores web, etc, sem mover as mãos da linha Braille.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>· A função de copia-e-cola que

+é particularmente útil para copiar nomes de arquivos, copiar o texto entre os

+terminais virtuais, entrada de comandos complicados, etc.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Tabela de

+contração Braille (Inglês e Francês).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Suporte para

+vários códigos em braille.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Capacidade para

+identificar um caracter desconhecido.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Capacidade para

+verificar um caracter sublinhado.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Ajuda on-line

+para comandos da linha Braille.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>· Menu</span> de preferências.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Suporte básico

+de fala.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'>· Projeto modular que permite

+a adição relativamente fácil de drivers para outros display braille e sintetizadores

+de voz.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Uma Interface

+de Programação de Aplicativos.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>2.2 Requisitos do

+Sistema</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>Até essa data, BRLTTY roda no Linux, Solaris, OpenBSD,

+FreeBSD,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>NetBSD, e Windows. Enquanto que outros

+sistemas operacionais não estão<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>atualmente planejados, alguma sugestão de

+outros projetos será bem<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>vinda. </span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Linux</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado por diversos sistemas:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Desktops,

+laptops, e alguns PDAs.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Processadores

+de 386SX20 até Pentium.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Diversas séries

+de tamanhos de mémoria.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Diversos

+distribuidores incluindo Debian, Red Hat, Slackware, e SuSE.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>· Vários kernels, incluindo 1.2.13, 2.0, 2.2,

+and 2.4.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'>     </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Solaris<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado nos sistemas abaixo:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span>·

+Arquitetura Sparc (versões 7, 8, e 9).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'>   </span>· Arquitetura Intel (versão 9).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>OpenBSD</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado no sistema OpenBSD abaixo:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· Arquitetura Intel

+(versão 3.4).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>FreeBSD</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado no sistema FreeBSD abaixo:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>· Arquitetura Intel (versão 5.1).<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> </span><span style='mso-spacerun:yes'> </span>NetBSD<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado no sistema NetBSD abaixo:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>· Arquitetura Intel (versão 1.6).<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Windows<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Este software tem

+sido testado no Windows 95, 98 e XP.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>No Linux, BRLTTY pode inspecionar o conteúdo da tela

+de forma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>completamente independente de qualquer

+usuário conectado. Ele faz isso<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>usando um dispositivo especial que fornece

+fácil acesso ao conteúdo do <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>atual console. Este dispositivo foi

+introduzido na versão 1.1.92 do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>kernel do Linux, e é normalmente chamado

+também de /dev/vcsa ou<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/dev/vcsa0 (em sistemas com devfs é chamado

+/dev/vcc/a). Por esta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>razão, o kernel do Linux 1.1.92 ou superior é

+necessário se o BRLTTY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span></span><span style='font-size:

+11.0pt'>for utilizado desta forma. Com a capacidade de:</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· <span

+style='font-size:11.0pt'>Permite BRLTTY a ser iniciado muito antes da seqüência

+de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>inicialização do sistema</span>.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· <span

+style='font-size:11.0pt'>Permite a visualização em Braille para estar

+totalmente operacional<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>durante o login do prompt</span>.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>· <span

+style='font-size:11.0pt'>Faz muito mais fácil para um usuário em Braille para

+realizar a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>inicialização de tarefas de administração do

+sistema</span>.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span class=hps><span

+lang=PT style='mso-ansi-language:PT'>Um patch</span></span><span lang=PT

+style='mso-ansi-language:PT'> <span class=hps>para o programa de</span> <span

+class=hps>tela é</span> <span class=hpsatn>fornecido (</span>ver os <span

+class=hps>subdiretórios dos<o:p></o:p></span></span></p>

+

+<p class=MsoPlainText><span class=hps><span lang=PT style='mso-ansi-language:

+PT'><span style='mso-spacerun:yes'>  </span>Patches</span></span><span lang=PT

+style='mso-ansi-language:PT'>). <span class=hps>Ele permite</span> que <span

+class=hps>BRLTTY</span> <span class=hps>acesse imagem da tela</span> <span

+class=hps>da tela</span> <span class=hps>via</span><br>

+&nbsp;&nbsp;<span class=hps>memória compartilhada</span>, <span class=hps>e,

+portanto,</span> <span class=hps>permite</span> <span class=hps>BRLTTY</span> <span

+class=hps>a ser</span> <span class=hps>utilizados com<o:p></o:p></span></span></p>

+

+<p class=MsoPlainText><span class=hps><span lang=PT style='mso-ansi-language:

+PT'><span style='mso-spacerun:yes'>  </span>bastante</span></span><span

+lang=PT style='mso-ansi-language:PT'> <span class=hps>efetivamente</span> <span

+class=hps>em plataformas</span> <span class=hps>que não</span> <span class=hps>têm

+a sua</span> <span class=hps>própria tela</span> <span class=hps>de<o:p></o:p></span></span></p>

+

+<p class=MsoPlainText><span class=hps><span lang=PT style='mso-ansi-language:

+PT'><span style='mso-spacerun:yes'>  </span>conteúdo</span></span><span

+lang=PT style='mso-ansi-language:PT'> <span class=hps>instalações de inspecção</span>.

+<span class=hps>A principal fraqueza</span> <span class=hps>da abordagem</span>

+<span class=hps>de tela</span> <span class=hps>é<o:p></o:p></span></span></p>

+

+<p class=MsoPlainText><span class=hps><span lang=PT style='mso-ansi-language:

+PT'><span style='mso-spacerun:yes'>  </span>que</span></span><span lang=PT

+style='mso-ansi-language:PT'> <span class=hps>BRLTTY</span> <span class=hps>não

+pode ser iniciado</span> <span class=hps>até que o usuário</span> <span

+class=hps>esteja logado.</span></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>BRLTTY apenas

+trabalha com consoles e aplicações. Pode ser usados em</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>aplicações shell,

+mais não com aplicações que usam caracteristicas VGA ou que</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>requer console

+gráfico (como o sistema X Window).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Você deverá

+também, claro, possuir um display Braille(ver seção “Display de</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Braille

+Suportado” para completer a lista). Nós esperamos que outros displays</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>sejam suportados

+no futuro, então, se você tem alguma informação técnica de um</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>dispositivo que

+você gostaria de ser visto com suportado, por favor entre em</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>contato (ver

+seção “Informação de contato”).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>Finalmente, você precisa de ferramentas para construir

+o executável a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>partir de sua fonte: make, compiladores C e C

++ +, yacc, awk, etc. As<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ferramentas de desenvolvimento fornecidas com

+a distribuição padrão do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Unix deveria ser suficiente. <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span></span><span style='mso-bidi-font-size:18.0pt'>3

+- Processo de compilação</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>BRLTTY pode ser baixado a partir do seu site (consulte

+a secção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Informações de contato para a sua

+localização). Todas as releases são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fornecidas compactadas “tar balls”. Releases

+mais recentes também são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fornecidas como arquivos RPM (RedHat Package

+Manager).</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Essa

+informação, provavelmente atingiu a sua curiosidade, e agora você<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não pode esperar para começar. É uma boa

+idéia, porém, que primeiro se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>familiarizar com os arquivos que serão

+finalmente instalados.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='mso-bidi-font-size:18.0pt'>3.1 - Instalação da herança de arquivos</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Processo de compilação deve resultar na

+instalação dos seguintes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquivos:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/bin/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty: O programa BRLTTY.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty-install: Um utilitário para copiar

+BRLTTY hierarquia de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquivos instalado a partir de um local para

+outro.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty-config: Um utilitário que define um

+número de variáveis de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ambiente para valores que refletem a

+instalação atual do BRLTTY.</span><span style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>/lib/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>libbrlapi.a: arquivo estático da Interface de

+Programação de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Aplicativos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>libbrlapi.so: objeto dinâmico carregável para

+o Interface de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Programação de Aplicativos.</span><span

+style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/lib/brltty/<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>A instalação do BRLTTY não pode ter todos os

+seguintes tipos de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquivos. Eles são criados apenas quando

+necessário com base nas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opções de compilação que você selecionar (ver

+seção Opções de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilação).<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty-brl.lst: Uma lista dos drivers do

+display braille, que foram<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>construídas como objetos compartilhados

+dinamicamente carregáveis, e,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>portanto, que pode ser selecionada em tempo

+de execução. Cada linha<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>consiste do código de duas identificações no

+codigo do driver, um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caracter de tabulação.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>libbrlttybdriver.so.1: O driver é carregável

+e &nbsp;dinamico para uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>linha Braille, onde o driver é duas letras do

+código de identificação<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>do driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty-spk.lst: A lista de drivers de

+sintetizador de voz que foram<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>construídos como objetos compartilhados

+dinamicamente carregáveis, e,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>portanto, que pode ser selecionada em tempo

+de execução. Cada linha<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>consiste do código de identificação de duas letras

+para um driver, um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caracter de tabulação, e uma descrição do

+sintetizador de voz que esse<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>libbrlttysdriver.so.1: O driver carregável

+dinamicamente por um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>sintetizador de voz, onde o driver é o de

+duas letras do código de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificação do driver.</span><span

+style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>/lib/brltty/rw/</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Os arquivos criados em tempo de execução, por

+exemplo, é necessário,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>mas falta recursos do sistema.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/etc/</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty.conf: padrões do sistema para BRLTTY.</span><span

+style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brlapi.key: A chave de acesso para BrlAPI.</span><span

+style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/etc/brltty/</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>A instalação do BRLTTY não pode ter todos os

+seguintes tipos de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquivos. Eles são criados apenas quando

+necessário com base nas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opções de compilação que você selecionar (ver

+seção “Opções de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilação”).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='mso-tab-count:1'>    </span><span style='font-size:11.0pt'>*.conf:

+Driver específico para configuração de dados. Seus nomes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>      </span>são mais ou menos como

+brltty-driver.conf, onde driver é o de duas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>      </span>letras do código de identificação no

+driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>*.atb:

+tabelas de atributos.(ver seção “Tabelas de Atributos”) Seus nomes parecem <i>name</i>.atb.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>*.ati:

+Incluir arquivos de tabelas de atributos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>*.ctb:

+Contração tabelas. (Ver seção “Tabela de Contração”). Seus nomes parecem idioma

+<i>language-country-level</i>.ctb.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>*.cti:

+Incluir arquivos para tabelas contração.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>*.ktb:

+Tabelas de Teclas.(Ver seção “Tabelas de Teclas”). Seus nomes parecem <i>name</i>.ktb.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>*.kti:

+Incluir arquivos para tabelas de teclas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>*.ttb:

+tabelas de texto. (Ver seção Tabelas de Texto). Seus nomes parecem <i>language</i>.ttb.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>*.tti:

+Incluir arquivos de texto para tabelas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>*.hlp:

+Driver específico páginas de ajuda. Seus nomes mais ou menos como <i>brttty-driver</i>.hlp,

+onde driver é o de duas letras do código de identificação do driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/var/lib/BrlAPI/</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>São usados soquetes locais para conexão com a

+Interface de Programação<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de Aplicativos.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/include/</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Arquivos de cabeçalho C para a Interface de

+Programação de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Aplicativos. Seus nomes parecem brlapi-<i>function</i>.h.

+O cabeçalho<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>principal é brlapi.h.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/include/brltty/</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Arquivos de cabeçalho C para acessar hardware

+braille. Seus nomes são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brldefs-<i>driver</i>.h (onde <i>driver</i> é

+o de duas letras do código de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificação do driver). O cabeçalho

+brldefs.h e api.h são fornecidos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>para compatibilidade com versões anteriores e

+não deve ser usado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/man/</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Páginas man.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>man1/<i>name</i>.1: páginas man para comandos

+do usuário BRLTTY-relacionados.</span><span style='mso-bidi-font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>man3/<i>name</i>.3: páginas man para rotinas

+da biblioteca de Interface de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Programação de Aplicativos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Alguns arquivos opcionais que você deve estar

+ciente de, apesar de não<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>serem parte da hierarquia de arquivos

+instalados, são:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='mso-bidi-font-size:

+11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>/etc/brltty.conf</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>O sistema de arquivo de configuração padrão.

+É criado pelo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>administrador do sistema. Ver seção “Arquivo

+de Configuração”.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/etc/brltty-driver.prefs</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>O arquivo de configurações foram salvos as

+preferências (<i>driver</i> é um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>código de duas letras de identificação do

+driver). É criado pelo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>comando PREFSAVE. Ver seção “Preferência de

+Configuração”.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><span

+style='mso-spacerun:yes'>  </span>3.2 - Instalação a partir do Tar Ball</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Este

+passo é apenas quando se quer instalar apenas o BRLTTY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>rapidamente, é acreditar que os padrões

+estejam corretos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>1.

+Baixe o código. Um arquivo chamado brltty-<i>release</i>.tar.gz, por<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>exemplo, brltty-3.0.tar.gz.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>2.

+Descompacte o código fonte em sua estrutura hierárquica nativa.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'> </span></span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>Ex.: tar-zxvf

+brltty-release.tar.gz. <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Isso deve criar o diretório brltty-<i>release</i>.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>3. Mude para o diretório fonte, configure,

+compile e instale BRLTTY.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'>    </span><span

+style='font-size:11.0pt'>cd brltty-release</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;<span

+style='mso-tab-count:1'>   </span>./configure</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;<span style='mso-tab-count:

+1'>   </span>make install<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;Isto deve ser feito como

+root.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Para

+desinstalar BRLTTY, faça:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span><span style='mso-tab-count:1'>   </span>cd

+brltty-release</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>make

+uninstall<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>3.2.1 Opções de Compilação</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>O

+primeiro passo na construção BRLTTY é configurá-lo para o seu<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>sistema e/ou para suas necessidades pessoais.

+Isto é feito através da<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>execução do script no diretório BRLTTY de

+nível superior. Nós tentamos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fazer o ajuste padrão o caso mais comum,

+então, supondo que você não<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>está tentando fazer alguma coisa fora do

+comum, você pode não precisar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fazer nada mais complicado do que invocar o

+script sem especificar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>todas opções.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>./configure<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>Se, no entanto, você tem algumas necessidades

+especiais, ou mesmo se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>você está apenas se aventurando, você deve

+descobrir quais são suas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>escolhas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>./configure --help</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Você

+também deve verificar se o arquivo README no subdiretório que<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>contém o driver para o seu display Braille

+para algumas instruções<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>adicionais de exibição específico.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>3.2.1.1

+Padrões do Sistema</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-driver-braille

+= driver</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especificar

+os drivers do display braille, que devem estar ligados ao<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>binário BRLTTY. Os drivers que não estão

+listados por esta opção são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>construídas como objetos compartilhados

+dinamicamente carregáveis e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ainda pode ser selecionada em tempo de

+execução. Cada driver deve ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificado pelo seu código de duas letras

+de identificação do driver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ou pelo seu nome próprio (completo ou

+abreviado). Os identificadores<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>do driver deve ser separada da outra por uma

+única vírgula. Se um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificador do driver é precedido por um

+sinal de menos (-), então<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>esse driver é excluído da compilação.

+Qualquer uma das seguintes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>palavras também pode ser usado como o

+operando esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>all <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Link de todos os drivers para o binário. Não

+construa qualquer um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>deles como objetos compartilhados

+dinamicamente carregáveis. Esta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>palavra também pode ser especificado como o

+último elemento de uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>lista de driver. Isto é como especificar o

+driver default quando todos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>os drivers devem ser ligados dentro.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>-all: Apenas construir os condutores que

+tenham sido incluídos <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>explicitamente através desta opção.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no: Não construa qualquer driver <st1:PersonName

+ProductID="em tudo. Isso" w:st="on">em tudo. Isso</st1:PersonName> é

+equivalente a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificar --without-braille-driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>yes: Construir todos os drivers como objetos

+compartilhados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>dinamicamente carregáveis. Não ligar qualquer

+um deles dentro do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>binário. Isso é equivalente a especificar

+--with-braille-driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Ver arquivo

+configuração e a linha de opção de commando –b para a seleção de</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>tempo de

+execução.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>--with-braille-parameters=

+[driver:]name = value, ...</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Especifique as configurações padrão de

+parâmetro para os drivers do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>display braille. Se o mesmo parâmetro é

+especificado mais de uma vez,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>a sua atribuição é usada. Se um nome de

+parâmetro é qualificada por um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver, (ver seção “Códigos de Identificação

+de Drivers”) então essa<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração só se aplica ao driver, senão é,

+então, que se aplica a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>todos os drivers. Para essa descrição dos

+parâmetros aceitos por a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificação de driver, por favor ver na

+documentação do driver. Ver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração de arquivo “braille-parameters”

+ou linha de comando –b. </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-braille-device

+= <i>device</i>, ...</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifica

+o dispositivo padrão para o qual o display braille está<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ligado. (Ver seção Especificação de

+Dispositivo Braille). Se essa<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opção não for especificada, então usb: é

+assumido se o suporte USB<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>está disponível, e um sistema operacional tem

+um caminho apropriado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>para a porta primária serial (dispositivo) é

+assumido se não.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-libbraille

+= <i>directory</i></span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local da instalação do pacote Libbraille, e compilar o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver display braille Libbraille (Ver seção

+“Restrições de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilação”). Qualquer uma das seguintes

+palavras também pode ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>usado como o operado por essa opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Não compilar o driver. Isso é equivalente a

+especificar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--without-libbraille.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilar o driver se o pacote pode ser encontrado

+em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>/usr/local/Libbraille,

+/usr/local/libbraille, /opt/Libbraille, ou<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>/opt/libbraille. </span><span

+style='font-size:11.0pt'>Isso é equivalente a especificar --with-libbraille.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-text-table=<i>file</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+tabela texto build-in (retorno)(Ver seção “Tabelas de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Texto”). A tabela especificada é ligada ao

+binário BRLTTY, e é usado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>tanto em falhas de autoseleção quanto na

+solicitação de tabela que não<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>pode ser carregado. O caminho absoluto para

+uma tabela fora do código<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fonte pode ser especificado. A extensão

+“.ttb” é opcional. Esta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração pode ser alterada com a

+preferência Tabela de texto. Se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>esta opção não for especificada, então en-nabcc,

+normalmente(América<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>do Norte) usa 8 pontos ver “Código Braille

+Norte-Americano”. Ver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração de “Tabela de Texto” ou comando

+–t. Esta configuração<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>pode ser mudada com a preferência “Tabela de

+Texto”.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-attributes-table=<i>file</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+a tabela texto de atributos built-in (retorno)(Ver seção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>“Atributos de Tradução”). A tabela especificada

+é ligada ao binário<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>BRLTTY, e é usado quando a tabela solicitada

+não pode ser carregada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caminho absoluto para uma tabela fora do

+código fonte pode ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificado. A extensão do “.atb” é

+opcional. Se essa opção não for<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificada, então os atributos são

+assumidos. Mude o atributo se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>você gostaria que ele fosse feito à moda

+antiga. Veja a tabela<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>atributos de arquivo de configuração e uma

+opção de linha de comando<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>para seleção de tempo de execução -a. Esta

+configuração pode ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>alterada com a preferência atributos da

+tabela.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-speech-driver = driver</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especificar

+os drivers de sintetizador de voz que são ligados ao<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>binário BRLTTY. Esses drivers não estão

+listados nesta opção são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>construídas como objetos compartilhados

+dinamicamente carregáveis e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ainda pode ser selecionado em tempo de

+execução. Cada driver deve ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificado pelo seu código de duas letras

+de identificação do driver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ou pelo seu nome próprio (completo ou

+abreviado). Os identificadores<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>do driver deve ser separados por uma única

+vírgula. Se identificador<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>do driver é precedido por um sinal de menos

+(-), então esse driver é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>excluído da compilação. Qualquer uma das

+seguintes palavras também<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>pode ser usadas como operadores esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>all<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>link de todos os drivers para o binário. Não

+compila qualquer um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>deles como objetos compartilhados

+dinamicamente carregáveis. Esta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>palavra também pode ser especificada como o

+último elemento de uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>lista de driver. Isto é como especificar o

+driver default quando todos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>os drivers devem ser ligados.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='mso-spacerun:yes'>              </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>-all<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>apenas construir os drivers que tenham sido

+incluídos explicitamente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>através desta opção.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não construa qualquer driver. </span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Isso é equivalente

+a especificar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>without-speech-driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar todos os drivers como objetos

+compartilhados dinamicamente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>carregáveis. Não ligar qualquer um deles

+dentro do binário. Isso é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>equivalente a especificar --with-speech-driver.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Veja

+a configuração do arquivo “speech-driver” e a opção -s de linha<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de comando para seleção de tempo de execução.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>--with-speech-parameters = [<i>driver</i>:]<i>name

+= value,</i> ...</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+os parâmetros das configurações padrão para os drivers de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>sintetizador de voz. Se o mesmo parâmetro é

+especificado mais de uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>vez. Se um nome de parâmetro é qualificado

+por um driver (Ver Código<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de Identificação de Drivers), então essa

+configuração só se aplica<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>nesse driver. Para obter uma descrição dos

+parâmetros aceitos por um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver específico, consulte a documentação do

+driver. Ver configuração<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>“speech-parameters” ou comando “-s”.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-flite=<i>directory</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local instalação do FestivalLite no pacote text-to<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>speech e compila o driver sintetizador de voz

+FestivalLite (Ver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>“Restrições de Compilação”). Qualquer uma das

+seguintes palavras<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>também pode ser usado como o operadores esta

+opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar –without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>flite.</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>y<span

+style='font-size:11.0pt'>es<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver se o pacote pode ser encontrado

+em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/usr/local/FestivalLite, /usr/local/flite,

+/opt/FestivalLite, ou<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/opt/flite. Isso é equivalente a especificar

+--with-flite.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-flite-language=<i>language</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o idioma usado que o text-to-speech do FestivalLite. O<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>idioma padrão é USENGLISH.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-flite-lexicon=<i>lexicon</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o léxico que o text-to-speech FestivalLite usa. O léxico<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>padrão é cmulex.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-flite-voice=<i>voice</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+a voz que o text-to-speech FestivalLite usa. A voz padrão<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>é cmu_us_kal16.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br style='mso-special-character:

+line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>-- with-mikropuhe=<i>directory</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local instalação do pacote text-to-speech da Mikropuhe,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>e compilar o driver sintetizador de voz

+Mikropuhe (Ver Restrições de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilação). Qualquer uma das seguintes

+palavras também pode ser usado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>como o operando esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar –without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>mikropuhe.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver se o pacote pode ser encontrado

+em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/usr/local/Mikropuhe, /usr/local/mikropuhe,

+/opt/Mikropuhe, ou<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/opt/mikropuhe. Isso é equivalente a

+especificar --with-mikropuhe.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-speechd=<i>directory</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local da instalação do pacote text-to-speech do speech<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>dispatcher, e compilar o driver sintetizador

+de voz do speech<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>dispatcher. Qualquer uma das seguintes

+palavras também pode ser usado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>como o operando esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar –without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>speechd.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>compilar o driver se o pacote pode ser

+encontrado em /usr,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>   </span>/usr/local,

+/usr/local/speech-dispatcher, /usr/local/speechd,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>   </span>/opt/speech-dispatcher, ou

+/opt/speechd. Isso é equivalente a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>   </span>especificar --with-speechd.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>  </span><span style='mso-spacerun:yes'>   </span></span><span

+style='font-size:11.0pt'>--with-swift =<i> directory</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local da instalação do pacote de text-to-speech Swift, e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver sintetizador de voz Swift.

+Qualquer uma das<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>seguintes palavras também pode ser usado como

+o operando esta opção:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'><span style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar -–without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>swift.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver se o pacote pode ser

+encontrado em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>/usr/local/Swift,

+/usr/local/swift, /opt/Swift, ou /opt/swift. </span><span style='font-size:

+11.0pt'>Isso é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>equivalente a especificar --with-swift.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-theta

+= <i>directory</i> </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local da instalação do pacote text-to-speech do Theta, e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver sintetizador de voz Theta (Ver

+“Restrições de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Compilação”). Qualquer uma das seguintes

+palavras também pode ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>usado como o operando esta opção:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar -–without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>theta.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'><span style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver se o pacote pode ser

+encontrado em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/usr/local/Theta, /usr/theta/local, /opt/Theta,

+ou /opt/theta. Isso é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>equivalente a especificar --with-theta.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-viavoice=<i>directory</i>:

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local da instalação do pacote text-to-speech do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ViaVoice, e compilar o driver sintetizador de

+voz do ViaVoice (Ver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Restrições de Compilação). Qualquer uma das

+seguintes palavras também<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>pode ser usado como o operando esta opção:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não compilar o driver. Isso é equivalente a

+especificar –-without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>viavoice.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br style='mso-special-character:

+line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar o driver se o pacote pode ser encontrado

+em /usr, /usr/local,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/usr/local/ViaVoice, /usr/local/ViaVoice,

+/opt/ViaVoice, ou<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/opt/ViaVoice. Isso é equivalente a

+especificar --with-viavoice.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-screen-driver=<i>driver</i>:

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especificar

+os drivers da tela que devem ser ligados ao binário<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>BRLTTY. Os drivers que não estão listados por

+esta opção são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>construídas como objetos compartilhados

+dinamicamente carregáveis e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ainda pode ser selecionada em tempo de

+execução. Cada driver deve ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>identificado pelo seu código de duas letras

+de identificação do driver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>(Ver seção Drivers de Tela Suportados) ou

+pelo seu nome próprio<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>(completo ou abreviado). Os identificadores

+de driver devem ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>separados da outra por uma única vírgula. Se

+um identificador de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver é precedido por um sinal de menos (-),

+então esse driver é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>excluído da compilação. Qualquer uma das

+seguintes palavras também<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>pode ser usado como o operando esta opção:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>all<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>liga todos os drivers dentro do binário. Não

+construa qualquer um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>deles como objetos compartilhados dinamicamente

+carregáveis. Esta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>palavra também pode ser especificado como o

+último elemento de uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>lista de driver. Isto é como especificar o

+driver default quando todos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>os drivers devem ser ligados.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>-all<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>apenas compila os drivers que tenham sido

+incluídos explicitamente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>através desta opção.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não construa qualquer driver. Isso é

+equivalente a especificar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>without-screen-driver.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'><span style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar todos os drivers como objetos compartilhados

+dinamicamente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>carregáveis. Não ligar qualquer um deles

+dentro do binário. Isso é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>equivalente a especificar

+--with-screen-driver.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>O

+primeiro driver não-excluído começa sendo driver padrão. Se essa<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opção não for especificada, ou se não

+especificado o driver, então um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>padrão do sistema operacional apropriado é

+selecionado. Se um driver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>nativo para o sistema operacional atual está

+disponível, então esse<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>driver é selecionado, senão, o sc é

+selecionado. Veja o arquivo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>screen-driver de configuração e a opção de

+linha de comando para<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>seleção -x tempo de execução.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-screen-parameters=[<i>driver:]name=value</i>,...:

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+as configurações padrão de parâmetro para os screen<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>drivers. Se um nome de parâmetro é

+qualificada por um driver (consulte<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>a seção Supported Screen Drivers), em

+seguida, a configuração só<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>aplica ao driver, senão é, aceitável por

+drivers específicos. Para<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>obter uma descrição dos parâmetros aceitos

+por um driver específico,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>consulte a documentação do driver. Veja no

+arquivo de configuração de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>parâmetros e o comando –X no tempo de execução.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-usb-package

+= <i>package</i>, ...: </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especificar

+o pacote que está sendo utilizado pela I/O da USB. O nome<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>dos pacotes devem ser separados um do outro

+por uma única vírgula, e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>são processados da esquerda para a direita. O

+primeiro, que é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalado no sistema é selecionada. Os

+pacotes a seguir são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>suportados:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>1. libusb</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>2. libusb-1.0 <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Qualquer uma das seguintes palavras também

+pode ser usado como o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>operadores esta opção:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não suporta I/O da USB. Isso é equivalente a

+especificar -–without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>usb-package.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'> </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>use o suporte nativo para I/O da USB. Se o

+suporte nativo não está<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>disponível para a plataforma atual, em

+seguida, usar o pacote<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>disponível de suporte (como por ordem

+especificada acima). Isso é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>equivalente a especificar --with-usb-package.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-bluetooth-package

+= <i>package</i>, ... </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especificar

+o pacote que está sendo utilizado para I/O de Bluetooth.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Os nomes dos pacotes devem ser separados um

+do outro por uma única<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>vírgula, e são processados da esquerda para a

+direita. O primeiro, que<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>é instalado no sistema é selecionada. Os

+pacotes a seguir são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>suportados:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:33.75pt;text-indent:-20.25pt;

+mso-list:l9 level1 lfo1;tab-stops:list 33.75pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>1.<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>(Sem os pacotes

+são suportados).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:13.5pt'><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Qualquer uma das seguintes palavras também

+pode ser usado como o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>operador esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>não

+suporte da I/O do Bluetooth. Isso é equivalente a<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>especificar

+--with-bluetooth-package.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>use

+o suporte nativo para I/O do Bluetooth. Se o suporte nativo<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>não

+está disponível para a plataforma atual, em seguida, usar o<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>primeiro

+pacote disponível suporte (como por ordem especificada<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:13.5pt'><span style='font-size:11.0pt'>acima).

+Isso é equivalente a especificar --with-bluetooth-package.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>3.2.1.2

+Especificações de Diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-execute-root=<i>directory</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório em que a hierarquia de arquivos foram<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalados no tempo de execução. O caminho

+absoluto deve ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fornecido. Se essa opção não for

+especificada, então diretório raiz do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>sistema é assumido. Você precisa usar essa

+característica, por<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>exemplo, se você tiver mais de uma versão

+instalada do BRLTTY precisar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalar arquivos BRLTTY no mesmo tempo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-install-root=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório sob o qual a hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>foi instalado. O caminho absoluto deve ser

+fornecido. Se esta opção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não for especificada, então o pacote root no

+tempo de execução (veja a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>opção --with-execute-root build

+option) é assumida. </span><span style='font-size:11.0pt'>Este diretório é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>apenas usado pelo make install e make

+uninstall. Use esta opção se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>você precisa instalar BRLTTY em um local

+diferente daquele a partir do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>qual foi finalmente foi executado. Você

+precisa usar este recurso, por<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>exemplo, se você está construindo BRLTTY em

+um sistema para uso em<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>outro.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--prefix

+= <i>directory</i></span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os diretórios padrão para os arquivos

+independentes de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquitetura são enraizadas. Esses diretórios

+são:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>·

+Diretório de Programa</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Biblioteca</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de API</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>·

+Diretório de Escrita</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Dados</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Configuração</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Manpage </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Inclusão<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>O caminho absoluto deve ser fornecido. Se

+essa opção não for<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificada, então diretório raiz do sistema

+é assumido. Este<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>diretório é enraizada no diretório

+especificado pela opção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilação --with-execute-root.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--exec-prefix

+= <i>directory</i></span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da “hierarquia de arquivos instalados”<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os diretórios padrões para os arquivos

+dependentes de arquitetura<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>devem ser enraizados. Esses diretórios são:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Programa</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de Biblioteca</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Diretório de API</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>O caminho absoluto deve ser fornecido. Se

+essa opção não for<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificada, então o diretório especificado

+através da opção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilação --prefix é assumida. Este

+diretório é enraizado no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>diretório especificado pelo

+--with-execute-root.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--libdir=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde o arquivo estático e o objeto carregado

+dinamicamente para a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>API ser instalada. O caminho absoluto deve

+ser fornecido. Se essa<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opção não for especificada, então o diretório

+especificado através da <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>opção de configuração padrão –-libdir (cujo

+padrão é /lib enraizado no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>diretório especificado pelo –-exec-prefix) é

+assumida. O diretório é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>criado senão existe.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--sysconfdir=<i>directory</i>

+<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Especifique o diretório dentro da hierarquia

+de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os arquivos de configuração do sistema

+serão instalados. O<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caminho absoluto deve ser fornecido. Se essa

+opção não for<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificada, então o diretório especificado

+através da opção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração padrão --sysconfdir (cujo padrão

+é /etc enraizado no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>diretório especificado por --prefix) é

+assumida. O diretório é criado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>se ele não existe.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-program-directory=<i>directory</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os programas executáveis (binários,

+executáveis) são instalados.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>O caminho absoluto deve ser fornecido. Se

+essa opção não for<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificada, então o diretório especificado

+através da opção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração padrão --bindir (cujo padrão

+/bin enraizada no diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificado pelo --exec-prefix) é assumida.

+O diretório é criado se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ele não existe.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-library-directory=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os drivers e outros arquivos dependentes

+de arquitetura são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalados. O caminho absoluto deve ser

+fornecido. Se essa opção não<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>for especificada, então o brltty subdiretório

+do diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificado através da opção de configuração

+padrão --libdir (cujo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>padrão é /lib enraizada no diretório

+especificado pelo --exec-prefix)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>é assumida. O diretório é criado se ele não

+existe.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-writable-directory=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>que podem ser gravados. O caminho absoluto

+deve ser fornecido.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Qualquer uma das seguintes palavras também

+pode ser usado como o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>operando esta opção:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não defina um diretório gravável. Isso é

+equivalente a especificar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>without-writable-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Use o local padrão. Isso é equivalente a

+especificar --with-writable<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>directory.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+essa opção não for especificada, então o subdiretório do diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>rw especificado com --with-library-directory

+é assumido. O diretório é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>criado se ele não existe.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>--with-data-directory=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde as paginas das tabelas de ajuda, e

+outros arquivos de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquiteturas independentes foram instaladas.

+O caminho absoluto deve<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ser fornecido. Se essa opção não for

+especificada, então o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>subdiretório do diretório do brltty

+especificado através da opção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração padrão --sysconfdir (cujo padrão

+é /etc enraizado no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>diretório especificado pela opção --prefix) é

+assumido. O diretório é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>criado se ele não existe.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-manpage-directory=<i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde as páginas do man serão instalados. O

+caminho absoluto deve ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fornecido. Se essa opção não for

+especificada, então o diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especificado através da opção de configuração

+padrão --mandir (cujo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>padrão é /man enraizado no diretório

+especificado por --prefix) é<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>assumida. O diretório é criado se ele não

+existe.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-include-directory=<i>directory</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o diretório dentro da hierarquia de arquivos instalados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde os arquivos de cabeçalho C para o

+Application Programming<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Interface serão instalados. O caminho

+absoluto deve ser fornecido. Se<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>essa opção não for especificada, então o

+subdiretório do diretório<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>brltty é especificado através da opção de

+configuração padrão<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>includedir (cujo padrão é /include enraizado

+no diretório especificado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>por --prefix) é assumida. O diretório é

+criado se ele não existe.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>3.2.1.3

+Caracteristicas de Compilação</span><span style='font-size:14.0pt;mso-bidi-font-size:

+10.0pt;font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Estas

+opções são úteis principalmente quando a construção do BRLTTY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>for usado em um disco de inicialização.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--enable-standalone-programs</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>   </span><span

+style='mso-spacerun:yes'>  </span></span><span style='font-size:11.0pt'>Criar

+links estaticamente, em vez de links dinamicos. Esta opção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>remove todas as dependências de objetos

+compartilhados no tempo de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>execução. Apenas os drivers padrão (veja a

+opção --with-braille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>driver, --with-speech-driver, e

+--with-screen-driver) são compilados.</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span lang=EN-US style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>--enable-relocatable-install</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+este recurso estiver ativado, todos os caminhos internos são<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>recalculados a ser relativo ao diretório do

+programa. Se desativado,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>então todos os caminhos internos são

+absolutos. Este recurso permite<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>que toda a hierarquia de arquivos instalados

+ser copiados ou movidos,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de um lugar para outro, e é destinado

+principalmente para uso em<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>plataformas Windows.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-strippingl

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Não

+remova as tabelas de símbolos a partir de arquivos executáveis e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>objetos compartilhados quando instalá-los.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-learn-mode</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o modo de comando (Ver seção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de Modo de Comando de Aprendizagem).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-contracted-braille</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte para braille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>contratados (Ver seção Tabela de Contração).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-speech-support</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte dos sintetizadores<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>de voz.</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:12.0pt'><span style='font-size:11.0pt'>--disable-iconv</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Reduzir o tamanho do programa, com

+exclusão do suporte para a conversão de conjunto de caracteres.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--disable-icu</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>internacionalização Unicode-based.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-x

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte para X11.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-beeper-support</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, com exclusão do suporte para o console<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>tone generator.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-pcm-support</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte para a interface<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>digital de áudio na placa de som.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--enable-pcm-support

+= interface</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+a plataforma oferece mais de uma interface digital de áudio para<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ser usado pode ser especificado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-midi-support</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, com exclusão do suporte para o Musical<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Instrument Digital Interface da placa de som.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--enable-midi-support

+= <i>interface</i> </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+uma plataforma fornece mais de uma Interface Musical Instrument<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Digital então o que está a ser utilizado pode

+ser especificado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Platform<span style='mso-spacerun:yes'>  

+</span>Interface<span style='mso-spacerun:yes'>   </span>Description<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> 

+</span>_______________________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>Linux<span style='mso-spacerun:yes'>     

+</span>oss<span style='mso-spacerun:yes'>         </span>Open Sound System<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>             </span>alsa<span

+style='mso-spacerun:yes'>        </span>Advanced Linux Sound Architecture<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-fm-support

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o apoio do sintetizador de FM<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>em um cartão de AdLib, OPL3, Sound Blaster,

+ou o equivalente de som.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-pm-configfile</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo o suporte para o arquivo de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração do driver Papenmeier.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--disable-gpm</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo a interface da aplicação gpm<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>que permite interagir BRLTTY com o

+dispositivo pointer (mouse)(ver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>seção Ponteiro (Mouse) Support via GPM).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-api

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Reduzir

+o tamanho do programa, excluindo a Application Programming<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>Interface.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>--with-api-parameters=<i>name</i>

+= <i>value</i>, ...</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+as configurações padrão do parâmetro para o Application<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Programming Interface. Se o mesmo parâmetro é

+especificado mais de uma<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>vez, a sua atribuição é usada. Para obter uma

+descrição dos parâmetros<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>aceitos pela interface, consulte o manual de

+referência BrlAPI. Veja<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>os arquivos de configuração api-parameters e

+o comando –a.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>--disable-caml-bindings</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Não construir bindings caml

+para o Application Programming Interface.</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--disable-java-bindings</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Não

+construir as bindings para a Interface Java Application<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>Programming.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>--disable-lisp-bindings</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Não construir as bindings de

+Lisp para o Application Programming<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>Interface.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>--disable-python-bindings </span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Não construir bindings Python

+para o Application Programming<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>Interface.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>--disable-tcl-bindings </span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Não construir bindings Tcl

+para a Application Programming Interface.</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-tcl-config=<i>path</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o local do script de configuração Tcl (tclConfig.sh). Ou o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caminho para o script em si ou o diretório

+que contém pode ser<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fornecido. Qualquer uma das seguintes

+palavras também pode ser usado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>como o operando esta opção:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>no<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>utilizar outros meios de adivinhar se Tcl

+está disponível, se sim,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>onde ele foi instalado. Isso é equivalente a

+especificar –without<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>tcl-config.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>procure o script em alguns diretórios

+frequentemente utilizados. Isso<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>é equivalente a especificar --with-tcl-config.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>3.2.1.4 <span

+style='font-size:11.0pt'>Opções Diversas</span><span style='font-size:14.0pt;

+mso-bidi-font-size:10.0pt;font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-compiler-prefix

+= <i>prefix</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o prefixo (caminho e início do nome do programa) para o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>conjunto de ferramentas de compilar e ligar

+possam ser utilizadas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Pode ser necessário usar esta opção se, por

+exemplo, você está a<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilando cruzado para uma arquitetura

+diferente.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp; </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>--with-init-path = <i>path</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o caminho para o programa real de init do sistema. O<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>caminho absoluto deve ser fornecido. Se esta

+opção for especificada,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>então:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:46.5pt;text-indent:-19.5pt;mso-list:

+l2 level1 lfo2;tab-stops:list 46.5pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>1.<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>O programa de

+init deve ser movido para um novo local.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:46.5pt;text-indent:-19.5pt;mso-list:

+l2 level1 lfo2;tab-stops:list 46.5pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>2.<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>O brltty deve ser

+transferido para local original do programa de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>       </span>init.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:46.5pt;text-indent:-19.5pt;mso-list:

+l2 level1 lfo2;tab-stops:list 46.5pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>3.<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Quando o sistema

+de init é executado no inicio, brltty é<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>realmente executado.</span> <span

+style='font-size:11.0pt'>Ele se coloca em segundo plano, e executa a<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>inicialização real no primeiro plano. Este é

+uma maneira de ter<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>braille desde o início. É especialmente útil

+para alguns discos<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>de instalação/recuperação.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+essa opção não for especificada, então este recurso não está<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>ativado. Esta opção é destinada

+principalmente para a construção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>uma imagem do instalador braillificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>--with-stderr-path

+= <i>path</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Especifique

+o caminho para o arquivo ou dispositivo onde o padrão<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>error é escrito. O caminho absoluto deve ser

+fornecido. Se essa opção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>não for especificada, então este recurso não

+está ativado. Esta opção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>é destinada principalmente para a construção

+de uma imagem do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>instalador braillified.</span><span

+lang=EN-US style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>3.2.2 <span

+style='font-size:11.0pt'>Atingir metas de arquivos</span><span

+style='font-size:11.0pt;font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt;font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Depois

+BRLTTY ter sido configurado, o próximo passo é compilar e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalá-lo. Isto é feito através da aplicação

+de comando make que o<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>sistema de arquivo principal BRLTTY fazer

+(Makefile no diretório de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>nível superior). O arquivo BRLTTY é

+compatível com a maioria dos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>objetivos comuns de manutenção da aplicação.

+Eles incluem:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>make<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>um atalho para make all.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>make all<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>compilar e vincular o executável do BRLTTY,

+seus drivers e suas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>páginas de ajuda, seus programas de teste, e

+alguns outros pequenos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>utilitários.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>make install<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>complete a fase de compilação e ligação (ver “make

+all”), e depois<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>instalar os executáveis do BRLTTY, arquivos

+de dados, drivers e<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>páginas de ajuda, nos lugares corretos e com

+as permissões corretas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'> </span>&nbsp;make uninstall<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>remova o executável BRLTTY, seus arquivos de

+dados, drivers e páginas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>de ajuda, a partir do sistema.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>        </span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>          </span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span

+style='font-size:11.0pt'>make clean<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>certifique-se que a próxima compilação e

+ligação (ver make all) será<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>feito a partir do zero, removendo os

+resultados da compilação, ligação<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>e testes da estrutura de diretório de origem.

+Isso inclui a remoção de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>arquivos de objeto, executáveis, objetos

+compartilhados dinamicamente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>carregáveis, listas de drivers, páginas de

+ajuda, arquivos de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>cabeçalho temporária, e arquivos principais.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp; make distclean<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>além de remover os resultados da compilação e

+vinculação (ver make<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>clean):</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:48.9pt;text-indent:-18.0pt;mso-list:

+l10 level1 lfo3;tab-stops:list 48.9pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Remova os

+resultados da configuração BRLTTY (ver Build Options). Isso inclui a remoção de

+config.mk, config.h, config.cache, config.status, config.log.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:48.9pt;text-indent:-18.0pt;mso-list:

+l10 level1 lfo3;tab-stops:list 48.9pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Remover os outros

+arquivos da estrutura de diretório de origem que se acumulam ao longo do tempo,

+mas que não pertencem ali. Isso inclui a remoção de arquivos de editor de

+backup, os resultados do caso de teste, rejeitou blocos de patch, e as cópias

+dos arquivos originais.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><span

+style='mso-spacerun:yes'>  </span>3.3 Testando o BRLTTY:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Após

+a compilação, ligação e instalação BRLTTY, é uma boa idéia para<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>fazer um teste rápido antes de ativá-lo

+permanentemente. Para fazer<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>isso, chame com o comando:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>brltty

+-bdriver -ddevice</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>   </span></span><span style='font-size:11.0pt'>Para <i>driver</i>,

+especificar o código de identificação do driver de duas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>letras correspondente ao seu display Braille.

+Para o dispositivo,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>especifique o caminho completo do dispositivo

+que o seu display está<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>conectado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+você não quiser explicitamente identificar o driver e um<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>dispositivo de cada vez que você começar o

+BRLTTY, então você pode ter<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>duas abordagens. Você pode estabelecer

+padrões do sistema através do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>braille-driver e do dispositivo

+braille-device do arquivo de<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>configuração, e/ou compilar as suas

+necessidades para a direita em<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span></span><span lang=EN-US style='font-size:

+11.0pt;mso-ansi-language:EN-US'>BRLTTY atraves --with-braille-driver e

+--with-braille-device.</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><br>

+<span style='mso-spacerun:yes'>     </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>A

+mensagem BRLTTY de identificação de versão deve aparecer no display<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>braille por alguns segundos (veja na linha de

+comando -m). Depois ele<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>vai embora (que você pode acelerar premindo

+qualquer tecla do<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>display), a área da tela onde está o cursor

+deve aparecer. Isso<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>significa que você deve esperar para ver o

+seu comando shell prompt.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Então, quando você entra em seu próximo

+comando, cada carácter deve<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>aparecer no visor, como é digitado no

+teclado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Se sua

+experiência, é deixar o executar o BRLTTY então divirta-se, <span

+style='font-family:Wingdings;mso-ascii-font-family:"Courier New";mso-hansi-font-family:

+"Courier New";mso-char-type:symbol;mso-symbol-font-family:Wingdings'><span

+style='mso-char-type:symbol;mso-symbol-font-family:Wingdings'>J</span></span>. Se

+você</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>não tem

+experiência, precisará testar cada driver separadamente na ordem</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>isolada do

+problema. O driver de tela pode ser testado com “scrtest”, e o</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>driver do display

+braille pode ser testado com “brltest”.<span style='font-size:11.0pt'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Se

+você tiver um problema que exige uma grande quantidade de garimpo,<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>então você pode querer usar a seguinte linha

+de comando brltty:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· -ldebug para registrar muitas mensagens de

+diagnóstico.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· -n para manter BRLTTY em primeiro plano.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· -e para direcionar as mensagens de

+diagnóstico de erro padrão em vez<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>    </span>de log do sistema.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'> </span><span style='mso-spacerun:yes'> </span></span><span

+style='mso-bidi-font-size:18.0pt'>3.4 Iniciando o BRLTTY</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>BRLTTY, quando devidamente instalado, é

+invocada com o único comando brltty. Um arquivo de configuração (ver Arquivos

+de Configuração) pode ser criado a fim de estabelecer padrões do sistema como a

+localização do arquivo de preferências, o driver display braille a ser

+utilizada, o dispositivo para o qual o display braille estiver ligado, e a

+tabela de texto para ser usado. Muitas opções (ver Opções de Linha de Comando)

+permitem a especificação de tempo de execução explícita como a localização do

+arquivo de configuração, os padrões estabelecidos no arquivo de configuração, e

+algumas características que têm padrões razoáveis. A opção –h exibe um resumo

+de todas as opções. A opção -v mostra a versão atual do programa, a API, e os

+drivers selecionados. A opção-v exibe os valores das opções depois de todas as

+fontes têm sido considerados.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>É provavelmente o melhor para que o

+sistema iniciar automaticamente BRLTTY como parte da seqüência de inicialização

+para que o display braille já está instalado e funcionando, quando o login do

+prompt aparecer. A maioria (provavelmente todas) das distribuições fornecem um

+script de aplicações onde fornecida pelo usuário pode ser seguramente começou

+perto do final da seqüência de inicialização. O nome do script a distribuição é

+dependente. Aqui estão os que conhecem até agora:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br style='mso-special-character:

+line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span lang=EN-US style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span><span lang=EN-US style='font-size:

+11.0pt;mso-ansi-language:EN-US'>Red Hat</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>/etc/rc.d/rc.local</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>     </span></span><span style='font-size:11.0pt'>Começando

+BRLTTY desse script é uma boa aproximação (especialmente<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>para novos usuários). Basta adicionar um

+conjunto de linhas como<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>estas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>if [-x /bin/brltty-a-f

+/etc/brltty.conf]</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>then</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;/bin/brltty</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>fi</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Isso geralmente pode ser abreviado para a

+forma um pouco menos legível:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>[-x /bin/brltty –a -f

+/etc/brltty.conf] &amp;&amp; /bin/brltty</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><br>

+</span><span style='font-size:11.0pt'>Não adicione estas linhas antes da

+primeira linha (que geralmente parece com #!/bin/sh).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se o display Braille está sendo utilizado

+pelo administrador do sistema, então provavelmente deve ser iniciado o mais

+cedo possível durante a seqüência de boot (como antes os sistemas de arquivos

+são verificados), de modo que o display utilizado no caso de algo der errado

+durante estas verificações e o sistema cai no modo de usuário único. Mais uma

+vez, exatamente onde é melhor de fazer isso é distribuição de dependentes. Aqui

+estão os lugares que conhecemos até agora:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Debian</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>&nbsp;&nbsp;/etc/init.d/boot

+(para versões mais antigas)</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>&nbsp;&nbsp;/etc/init.d/rcS

+(para versões mais recentes)</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Um

+pacote brltty é fornecido (ver [http://packages.debian.org/brltty]) a partir de

+versão 3.0 (Woody). Uma vez que este pacote cuida da partida BRLTTY, não há

+necessidade de código fornecido pelo usuário estiver instalado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>RedHat</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>/etc/rc.d/rc.sysinit</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Cuidado que versões posteriores, a fim de

+apoiar um processo de inicialização mais orientada para o utilizador do

+sistema, tem esse script reinvoke si só, que está sob o controle de initlog.

+Olha, provavelmente, até perto do topo, para um conjunto de linhas como estas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>#

+Execute novamente a nós mesmos através initlog</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>if [-z &quot;$

+IN_INITLOG&quot;]; then</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>[-f /sbin/initlog] &amp;&amp;

+exec /sbin/initlog $ INITLOG_ARGS –r /etc/rc.sysinit</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>fi</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Inicializando

+o BRLTTY partir do resultado em duas reinvocações do BRLTTY processos em

+execução ao mesmo tempo. Se a sua versão do script tem esse recurso,

+certifique-se de começar BRLTTY após as linhas que implementá-lo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Slackware</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>&nbsp;&nbsp;/etc/rc.d/rc.S</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>SuSE</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>&nbsp;&nbsp;/sbin/init.d/boot</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Uma alternativa é começar BRLTTY a partir

+de /etc/inittab. Você tem duas opções, se você escolher esta rota.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Se

+você quer que ela seja iniciada muito cedo, não precisa ser reiniciado

+automaticamente para encerrar, em seguida, adicione uma linha como esta antes

+do primeiro: sysinit: a linha que já está lá dentro.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>brl::sysinit:/bin/brltty</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Se

+você não se importa que seja iniciado mais tarde, mas quero que ele seja

+reiniciado automaticamente para encerrar, adicione uma linha como esta em

+qualquer lugar dentro do arquivo.</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><span style='mso-spacerun:yes'>  </span>brl:12345:respawn:/bin/brltty –n</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'> </span></span><span style='font-size:11.0pt'>A

+opção -n (--nodaemon) é muito importante quando executando BRLTTY com

+facilidade init respawn. Você vai acabar com centenas de processos BRLTTY todos

+rodando ao mesmo tempo, se você esquecer de especificar.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Verifique se o identificador (bt nestes

+exemplos) já não está sendo usado por outra entrada, e, se for, escolher uma

+opção diferente.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Note-se que um comando como kill -TERM é

+suficiente para parar BRLTTY em suas trilhas. Se ele morre durante a entrada em

+modo de usuário único, por exemplo, pode muito bem ser devido a um problema

+desta natureza.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Alguns sistemas, como parte da seqüência

+de boot, sonda as portas seriais (normalmente, a fim de localizar

+automaticamente o mouse e deduzir seu tipo). Se o seu display braille estiver

+usando uma porta serial, este tipo de sondagem pode ser o suficiente para

+obtê-lo confuso. Se isso acontecer com você, então tente reiniciar o driver

+braille (veja o comando RESTARTBRL). Melhor ainda, desligar a porta serial.

+sobre como fazer isso:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Red Hat</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A sondagem é feita por um serviço chamado

+kudzu. Use o comando:</span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>chkconfig

+--list kudzu, </span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>para ver se ele foi ativado. Use o

+comando: </span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>chkconfig

+kudzu off</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Para desativá-lo. Posteriormente, os

+lançamentos permitem que você execute kudzu, sem deixar de sondagem das portas

+seriais. Para fazer isso, editar o arquivo /etc/sysconfig/kudzu/ e definir SAFE

+para yes.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se você deseja iniciar BRLTTY antes de

+qualquer sistema de arquivos são montados, em seguida, garantir que todos os

+seus componentes estão instalados no sistema de arquivos raiz. </span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Veja as opções

+--with-execute-root, --bindir, --libdir, --with-writable-directory, e

+–with-data-directory.</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='mso-bidi-font-size:18.0pt;mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='mso-bidi-font-size:

+18.0pt'>3.5 Considerações de segurança</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>BRLTTY precisa ser executado com

+privilégios de root, porque ele precisa ler e escrever de acesso para a porta

+para o qual o display braille é conectado, acesso de leitura /dev/vcsa ou

+equivalente (para consultar as dimensões e posição do cursor, e rever o

+conteúdo da tela em destaque), e ler e gravar o acesso ao console do sistema (para

+a entrada de seta chave durante cursor encaminhamento, para a inserção de

+caracteres de entrada durante a pasta, para simulação de chave utilizando as

+teclas no display braille, para recuperar tradução saída de caractere e fonte

+de tela tabelas de mapeamento, e para a ativação do apito interno). O acesso

+aos dispositivos necessários podem, evidentemente, ser concedido a um usuário

+não-root alterando as permissões de arquivo associado com os dispositivos.

+Meramente ter acesso ao console, no entanto, não é suficiente porque ativar o

+aviso sonoro interno e simulando golpes de tecla ainda requerem privilégios de

+root. Então, se você estiver disposto a desistir de cursor de roteamento,

+cortar e colar, bips, e tudo isso, você pode executar BRLTTY sem privilégios de

+root.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='mso-bidi-font-size:

+18.0pt'>3.6 Restrições de Compilação e Tempo de Execução</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Alert Tunes</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Algumas plataformas não suportam todos os

+dispositivos de música. Veja Tune Device.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Driver sintetizador de voz FestivalLite</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o text-to-speech

+FestivalLite é construído somente se o pacote foi instalado. Este driver e o

+driver para o text-to-speech do Theta (veja --with-theta) não podem ser

+simultaneamente ligados ao binário BRLTTY (veja --with-speech-driver).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Driver Libbraille Display Braille</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o pacote Libbraille é

+construído somente se o pacote foi instalado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Driver sintetizador de voz Mikropuhe </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o text-to-speech Mikropuhe

+é construído somente se o pacote foi instalado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'> </span><span style='font-size:11.0pt'>Este

+driver não pode ser incluído se o binário BRLTTY é estaticamente ligados (ver a

+opção --enable-standalone-programs), porque um arquivo estático não está

+incluído no pacote.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Driver do sintetizador de voz Theta</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o text-to-speech Theta é

+construído somente se o pacote foi instalado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este driver e o driver para o

+text-to-speech FestivalLite (veja --with-flite) não podem ser simultaneamente

+ligados ao binário BRLTTY (veja --with-speech-driver).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'> </span><span

+style='font-size:11.0pt'>Se esse driver é construído como um objeto carregado

+dinamicamente compartilhada então $ THETA_HOME/lib deve ser adicionado à

+variável de ambiente LD_LIBRARY_PATH antes BRLTTY é invocado porque os objetos

+compartilhados dentro do pacote não contém caminhos de pesquisa em tempo de

+execução de suas dependências.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>Driver

+sintetizador de voz ViaVoice</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o text-to-speech ViaVoice é

+construído somente se o pacote foi instalado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este driver não pode ser incluído se o

+binário BRLTTY é estaticamente ligados (ver --enable-standalone-programs),

+porque um arquivo estático não está incluído no pacote.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Driver VideoBraille Display Braille</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O driver para o display Braille

+VideoBraille é construído em todos os sistemas, mas só funciona em Linux.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='mso-bidi-font-size:

+18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>3.7

+Instalação a partir de um arquivo RPM</span><span style='font-size:11.0pt;

+font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Para instalar BRLTTY de um RPM (RedHat

+Package Manager) do arquivo, faça o seguinte:<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-size:11.0pt'>1.

+Baixe o pacote binário que corresponde ao seu hardware. Vai ser um arquivo

+chamado brltty-<i>release-version.architecture</i>.rpm, por exemplo,

+brltty-3.0-1.i386.rpm.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:9.0pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>2. Instale o pacote.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>rpm -Uvh

+brltty-release-version.architecture.rpm</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span style='font-size:11.0pt'>Isto deve ser feito como root.

+Estritamente falando, a opção –u (update) é a única que é necessário. A opção

+-v (verbose) exibe o nome do pacote como ele está sendo instalado. A opção -h

+(hashes) exibe uma barra de progresso (usando sinais de hash).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Para os corajosos, nós também fornecemos

+a fonte do arquivo RPM (.src.rpm), mas isso está fora do escopo deste

+documento.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Para desinstalar BRLTTY,

+faça:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>rpm-e

+brltty</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>3.8 Outros Utilidades</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Construindo BRLTTY também resulta na

+construção de ajudas e pequenos utilitários de diagnóstico.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.1 <span style='font-size:11.0pt'>brltty-config</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário cria uma série de

+variáveis de ambiente para valores que refletem a instalação atual do BRLTTY

+(ver Build Options). Deve ser executado dentro de um ambiente shell existente,

+isto é, não como um comando em seu próprio direito, e só pode ser usado por

+scripts que suportam a sintaxe Bourne Shell.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As seguintes variáveis de

+ambiente são definidas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_VERSION<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O número de versão do

+pacote BRLTTY.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_EXECUTE_ROOT<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>root em tempo de execução

+para o pacote instalado. Configurado com --with-execute-root.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_PROGRAM_DIRECTORY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Diretorio de programas

+executáveis (binários, executáveis). Configurada através

+--with-program-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>BRLTTY_LIBRARY_DIRECTORY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>Diretório de drivers. </span><span style='font-size:11.0pt'>Configurada

+através de --with-library-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_WRITABLE_DIRECTORY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Diretório que pode ser

+gravado. Configurado através do --with-writable-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_DATA_DIRECTORY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Diretorio de tabelas e

+páginas de ajuda. Configurada via --with-data-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_MANPAGE_DIRECTORY:

+Diretorio de páginas de manual. Configurada via --with-manpage-directory.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY_INCLUDE_DIRECTORY:

+Diretório para arquivos de cabeçalho C BrlAPI. Configurado através

+--with-include-directory.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLAPI_VERSION<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O número da versão BrlAPI

+(Application Programming Interface do BRLTTY).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLAPI_RELEASE<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O número da versão

+completa de BrlAPI.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLAPI_AUTH<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O nome do arquivo de chave

+de BrlAPI.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Além disso, as seguintes variáveis de

+ambiente padrão autoconf também são definidos:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>prefix </span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Subroot para arquivos independentes de

+arquitetura. Configurado através do --prefix.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>exec_prefix</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Subroot para arquivos dependentes de

+arquitetura. Configurada via --exec-prefix.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>bindir</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Local padrão para o diretório do

+programa. Configurada via --bindir.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>libdir</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Diretório estático BrlAPI de arquivo e

+objetos carregáveis dinamicamente. Suporte padrão o diretório da biblioteca.

+Configurada via --libdir.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>sysconfdir</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Diretório para arquivos de configuração.

+Suporte padrão para o diretório de dados. Configurada via --sysconfdir.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>mandir</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Localização padrão para o diretório

+páginas de manual. Configurada via --mandir.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>includedir</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Suporte padrão para o diretório arquivos

+de cabeçalho. Configurada via --includedir.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.2 <span style='font-size:11.0pt'>brltty-install<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este utilitário copia BRLTTY hierarquia

+de arquivos instalado a partir de um local para outro.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>brltty-install to [from]</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>to</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A localização para o qual a hierarquia de

+arquivos instalados foi copiada. Deve ser um diretório existente.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>from</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A localização do qual a hierarquia de

+arquivos instalados é para ser tomado. Se for especificado, então ele deve ser

+um diretório existente. Se não for especificado, então o local utilizado para a

+construção é assumida.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário pode ser usado, por

+exemplo, para copiar BRLTTY para um disco de raiz. Se um disquete de root é

+montada como /mnt e BRLTTY está instalado no sistema principal, em seguida,

+digitando </span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>brltty-install/mnt

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>cria cópias do BRLTTY, juntamente com

+todos os seus dados e arquivos da biblioteca, para a raiz de disquete.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Alguns problemas foram sentidos quando

+copia o BRLTTY entre sistemas com diferentes versões da biblioteca C

+compartilhada. Este vale a pena investigar se você tem dificuldades.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.3 brltest</p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este utilitário testa um driver do

+display braile, e também fornece uma forma interativa de aprender quais as

+teclas do display braille. Ele deve ser executado como root.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;brltest

+-option ... [driver [name= value ...]]</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+_</span><i><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>driver

+</span></i><span lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>O

+driver para o display braille. </span><span style='font-size:11.0pt'>Ela deve

+ser uma de duas letras do código de identificação do driver. Se não for

+especificado, então o primeiro driver configurado com -- with-braille-driver é

+assumida.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+_</span><i><span style='font-size:11.0pt'>name = value</span></i><span

+style='font-size:11.0pt'> </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Definir um parâmetro de driver display

+Braille. Para obter uma descrição dos parâmetros aceitos por um driver

+específico, consulte a documentação do driver.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-d<i>device</i> --device = <i>device</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para o dispositivo

+para o qual o display braille está ligado. Se não for especificado, o

+dispositivo configurado por --with-braille-device é assumida.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-D<i>directory</i>--data-directory = <i>directory</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para o diretório onde

+os drivers dos arquivos de dados reside. Se não for especificado, então o

+diretório configurado através do --with-data-directory é assumida.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-L<i>directory</i>--library-directory =

+&nbsp;<i>directory</i></span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para o diretório onde

+residem os drivers. Se não for especificado, então o diretório configurado

+através do --libdir é assumida.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-w<i>directory </i>--writable-directory=<i>directory</i>:

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para um diretório que

+pode ser gravado. Se não for especificado, então o diretório configurado com

+--with-writable-directory é assumida.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-h --help</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Mostra um resumo das opções de linha de

+comando, e depois sair. </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário usa o comando do BRLTTY

+Command Learn Mode. Quando pressionar a tecla do tempo limite &nbsp;é de 10

+segundos. O tempo de espera de mensagens é de 4 segundos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.4 <i><span style='font-size:11.0pt'>spktest</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário testa um driver

+sintetizador de voz. Pode precisar de ser executado como root.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>spktest

+-option ... [driver [name=value ...]]</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+_</span><i><span style='font-size:11.0pt'>driver</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O driver para sintetizador de voz. Ela

+deve ter uma de duas letras do código de identificação do driver. Se não for

+especificado, então o primeiro driver configurado via --with-speech-driver é

+assumida.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+_</span><i><span style='font-size:11.0pt'>name = value </span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Definir o parâmetro do driver do

+sintetizador de voz. Para obter uma descrição dos parâmetros aceitos por um

+driver específico, consulte a documentação do driver.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-t<i>string </i>--text-string = <i>string</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O texto a ser falado. Se não for

+especificado, entrada padrão é a leitura.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-D<i>directory </i>--data-directory=<i>directory</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para o diretório onde

+os arquivos de dados do driver reside. Se não for especificado, então o

+diretório configurado através do --with-data-directory é assumida.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-L<i>directory </i>--library-directory = <i>directory</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho absoluto para o diretório onde

+residem os drivers. Se não for especificado, então o diretório configurado

+através do --libdir é assumida.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-h--help: mostrar um resumo das opções de

+linha de comando, e depois sair.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.5 <span style='font-size:11.0pt'>scrtest<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este utilitário testa o driver da tela.

+Ele deve ser executado como root.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>scrtest

+-option ... [name = value ...]</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+_</span><i><span style='font-size:11.0pt'>name = value</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Definir um parâmetro de driver tela. Para

+obter uma descrição dos parâmetros aceitos por um driver específico, consulte a

+documentação do driver.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-l<i>column</i>--lest = <i>column</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique o ponto de partida da coluna

+(à esquerda)(origem zero) da região. Se esse valor não for fornecido, então um

+valor padrão, com base na largura especificada, é selecionado de modo que a

+região será centralizada horizontalmente.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-c<i>count</i> --columns = <i>count</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique a largura da região (em

+colunas). Se esse valor não for fornecido, então um valor padrão, com base na

+coluna inicial especificado, é selecionado de modo que a região será

+centralizada horizontalmente.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-<i>trow </i>--top = <i>row</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique a linha (de cima) partida (origem

+zero) da região. Se esse valor não for fornecido, então um valor padrão, com

+base na altura especificada, é selecionado de modo que a região está centrada

+verticalmente.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-r<i>count</i> --rows = <i>count</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique a altura da região (em

+linhas). Se esse valor não for fornecido, então um valor padrão, com base na

+linha especificada de partida, é selecionado de modo que a região está centrada

+verticalmente.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-h –help<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>mostrar um resumo das

+opções de linha de comando, e depois sair.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Notas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· Se nenhuma coluna de

+partida, nem uma largura região é especificado, então a região é centrada na

+horizontal e começa na coluna 5.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· Se nenhuma linha de

+partida, nem a altura da região é especificado, então a região é verticalmente

+centrado e começa na linha 5.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A seguir o padrão de saída:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;1. Uma linha

+detalhando as dimensões da tela.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Screen:

+widthxheight</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;2. Uma linha que

+detalha a posição (origem zero) do cursor.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cursor:

+[column, row]</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;3. Uma linha que

+detalha o tamanho da região da tela selecionada, e a posição (origem zero), do

+seu canto superior esquerdo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Region: widthxheight@[column, row]</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;4. O conteúdo da

+região da tela selecionada. Não são impressos os caracteres com espaços em

+branco.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>3.8.6 ttbtest</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário testa uma tabela de texto

+(ver Tabela de Texto).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>ttbtest -option ...

+input-table output-table</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><i><span style='font-size:11.0pt'>input-table </span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho do sistema de arquivo com a

+tabela de entrada de texto. Se isso é relativo, então é suportado no diretório

+configurado através --with-data-directory.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>output-table </span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O caminho do sistema de arquivos para a

+tabela de texto de saída. Se isso é relativo, então ele está apoiado no

+diretório de trabalho atual. Se este parâmetro não é fornecido, em seguida,

+tabela de saída está escrito.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-i<i>format </i>--input-format = <i>format</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique o formato da tabela de

+entrada. Se esta opção não é fornecida, em seguida, o formato da tabela de

+entrada é deduzida a partir da extensão do nome da tabela de entrada do

+arquivo.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-o<i>format</i> --output-format = <i>format</i>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique o formato da tabela de saída.

+Se esta opção não é fornecida, o formato da tabela de saída, que decorre da

+extensão do nome da tabela de saída do arquivo.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-c<i>charset</i> --charset = <i>charset</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Especifique o nome do caractere de 8 bits

+para usar quando interpretar as tabelas. Se esta opção não é fornecida, definir

+o caractere de host que está sendo usado.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-e --edit </span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Chamar o editor de tabela de texto. Se a

+tabela de saída for especificado, as alterações são escritas para ele. Se não,

+então a tabela de entrada é reescrita.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-h --help </span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Mostrar um resumo das opções de linha de

+comando, e depois sair.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se nenhuma ação especial é requerida, a

+tabela de saída é facultativa. Se não for especificada, a tabela de entrada

+está marcada. Se for especificado, a tabela de entrada é convertido.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Os formatos de tabela a seguir são

+suportados:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>ttb</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>&nbsp;&nbsp;&nbsp; BRLTTY</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>sbl</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>&nbsp;&nbsp;&nbsp; SuSE blinux</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>a2b</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;

+Gnopernicus</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>gnb</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp; Gnome

+Braille</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span>3.8.7 <span style='font-size:11.0pt'>ctbtest</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário testa uma tabela de

+contração (ver Tabela de Contração). O texto lido a partir dos arquivos de entrada

+(ou Standard input) é reescrita para a saída padrão como contratada braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ctbtest

+-option ... input-file ...</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>input-file </span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A lista de arquivos a serem processados.

+Qualquer número de arquivos pode ser especificado. Eles são processados da esquerda

+para a direita. O nome de arquivo especial - é interpretada para significar a

+entrada padrão. Se nenhum arquivo for especificado, entrada padrão é

+processado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-c<i>file</i> --contraction-table = <i>file<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O caminho do sistema de

+arquivos para o quadro de contração. Se isso é relativo, então é suportado no

+diretório configurado --with-data-directory. A extensão do .ctb é opcional. Se

+esta opção não é fornecida, em seguida, en-us-g2 é assumido.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-t<i>file</i> |auto –text-table = <i>file</i>|auto<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especificar a tabela de

+texto (ver Text Tables). Se um caminho relativo for fornecido, então ele está

+apoiado em /etc/brltty (ver --with-data- directory e o --with-execute-root). A

+extensão .ttb é opcional. Veja a tabela de texto do arquivo de configuração

+para a configuração de tempo de execução padrão. Esta configuração pode ser

+alterada com a preferência Text Table.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-w<i>columns</i> --output-width =<i>columns

+<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O comprimento máximo de

+uma linha de saída. Cada linha de entrada contratada está envolvida dentro de

+linhas de saida quantas forem necessárias. Se essa opção não for especificada,

+não há limite, e há uma correspondência um-para-um entre as linhas de entrada e

+saída.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-h -–help<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostrar um resumo das

+opções de linha de comando, e depois sair.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A tabela de texto é usado:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l7 level1 lfo4;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-size:11.0pt;font-family:Wingdings;mso-fareast-font-family:Wingdings;

+mso-bidi-font-family:Wingdings'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Para definir o

+caractere de saída de forma que o braille contratado será exibida corretamente.

+A mesma tabela que será usada pelo BRLTTY quando a saída é lido deve ser

+especificado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l7 level1 lfo4;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-size:11.0pt;font-family:Wingdings;mso-fareast-font-family:Wingdings;

+mso-bidi-font-family:Wingdings'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Para definir as

+representações braille dos caracteres definidos na tabela de contração como = (ver

+secção Tradução de Caracteres).<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A tabela brf.ttb texto é fornecido para

+uso com este utilitário. Ele define o formato usado nos arquivos .brf. Este

+também é o formato preferido usado pela maioria das impressoras em braille e

+dentro distribuída eletronicamente documentos <st1:PersonName

+ProductID="em braille. Esta" w:st="on">em braille. Esta</st1:PersonName> tabela

+efetiva permite que este utilitário para ser usado como um tradutor de texto

+braille.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>3.8.8 <span style='font-size:11.0pt'>tunetest</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Este utilitário testa músicas de alerta,

+e também fornece uma maneira fácil de compor novas melodias. Pode precisar de

+ser executado como root.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>tunetest

+-option ... {note duration} ...</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>note </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Um padrão MIDI de número de nota. Deve

+ser um número inteiro de <st1:metricconverter ProductID="1 a" w:st="on">1 a</st1:metricconverter>

+127, com 60 representantes Middle C. Cada valor representa um padrão cromático

+semi-tom, com os próximos valores inferiores e superiores que representam,

+respectivamente, as notas imediatamente inferior e superior.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>duration</span></i><span

+style='font-size:11.0pt'> </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A duração da nota <st1:PersonName

+ProductID="em milissegundos. Deve" w:st="on">em milissegundos. Deve</st1:PersonName>

+ser um número inteiro de <st1:metricconverter ProductID="1 a" w:st="on">1 a</st1:metricconverter>

+255.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>-d<i>device</i> --device = <i>device</i> </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O dispositivo no qual a tocar a música.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;Beeper<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>o apito interno (console gerador de tom).<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;Pcm<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>a interface digital de áudio na placa de som.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Midi<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>o Musical Instrument Digital Interface na

+placa de som.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Fm<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>o sintetizador de FM em um cartão de AdLib,

+OPL3, Sound Blaster, ou o &nbsp;&nbsp;equivalente de som.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><br>

+<span style='font-size:11.0pt'>O nome do dispositivo pode ser abreviada. Veja

+Dispositivo de Tune para obter detalhes sobre o dispositivo padrão e as

+restrições da plataforma.</span><br>

+<br>

+<span style='font-size:11.0pt'>-v<i>loudness</i> --volume=<i>loudness</i></span><br>

+<span style='font-size:11.0pt'>Especificar o volume da produção (volume) como

+uma porcentagem do máximo. O volume de saída padrão é 50.</span><br>

+<br>

+<span style='font-size:11.0pt'>-p<i>device</i> --pcm-device = <i>device</i> </span><br>

+<span style='font-size:11.0pt'>Especifique o dispositivo a ser usado para o

+áudio digital (ver Especificação do Dispositivo PCM). Esta opção não estará

+disponível se a opção --disable-pcm-support for especificada.</span><br>

+<br>

+<span style='font-size:11.0pt'>-m<i>device</i> --midi-device = <i>device<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifique o dispositivo

+a ser usado para o Musical Instrument Digital Interface (ver Especificação do

+Dispositivo MIDI). Esta opção não estará disponível se a opção

+--disable-midi-support build option foi especificado.</span><br>

+<br>

+<span style='font-size:11.0pt'>-i<i>instrument </i>--instrument = <i>instrument</i>

+</span><br>

+<span style='font-size:11.0pt'>O instrumento a ser usado se o dispositivo

+selecionado para midi. O instrumento padrão é um acoustic grand piano. As

+palavras que compõem o nome do instrumento deve ser separados um do outro por

+um único sinal de menos em vez de espaços, e qualquer uma das palavras pode ser

+abreviada. Um piano acústico, por exemplo, pode ser especificado como a-gra-pi.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-h –help </span><br>

+<span style='font-size:11.0pt'>Mostrar um resumo das opções de linha de

+comando, e depois sair.</span><br>

+<br>

+4 - Usando o BRLTTY</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Antes de iniciar o BRLTTY,

+você precisa configurar a sua linha Braille. Na maioria dos casos isso é feito

+simplesmente conectando-o a uma porta serial disponível, e depois ligá-lo.

+Depois de configurado o display execute BRLTTY simplesmente digitando o comando

+brltty no shell prompt (isto deve ser feito como root). Verifique a opção -d de

+linha de comando, as diretivas de arquivo de configuração do &nbsp;dispositivo

+braille, e a opção de construção --with-braille-device&nbsp;para alternativas

+sobre como dizer ao &nbsp;BRLTTY qual dispositivo que o display está conectado.

+Verifique a opção -b de linha de comando, as diretivas de arquivo de

+configuração do dispositivo braille, e a opção de construção

+--with-braille-device &nbsp;para alternativas sobre como dizer ao &nbsp;BRLTTY

+que tipo de display braille você tem. Verifique a opção -B de linha de comando,

+e o braille-parameters &nbsp;das diretiva de arquivo de configuração sobre como

+passar parâmetros para o driver do seu display braille.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Uma mensagem indicando o

+nome do programa (BRLTTY) e seu número de versão aparecerá brevemente (ver a

+opção -M de linha de comando) no display braille. A display mostrará então uma

+pequena área da tela, incluindo o cursor. Por padrão o cursor é representado

+com os pontos 7 e 8 sobreposto ao caracter que ele está.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Qualquer atividade de tela

+será refletida na tela <st1:PersonName ProductID="em Braille. O" w:st="on">em

+ Braille. O</st1:PersonName> display também irá acompanhar o andamento do

+cursor na tela. Esse recurso é conhecido como o cursor tracking.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Basta digitar no teclado e

+ler o display, no entanto, não é suficiente. Tente digitar um comando que

+causará um erro, e pressionar enter. O erro aparece na tela, mas, a menos que

+tenha um display multi-linha, as chances são de que não é visível no display

+braille. Tudo que você vê nela é outra janela de comandos. O que é necessário,

+então, é uma maneira de mover a janela braille em torno da tela. As teclas no

+display braille em si pode ser usado para enviar comandos para BRLTTY que, além

+de um monte de outras coisas, também pode fazer exatamente isso.</span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>4.1 – Comandos</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Infelizmente, os vários

+display braille não oferecem um conjunto de controles. Alguns têm o padrão de

+seis pontos chaves, alguns têm oito, e outros não têm nenhum. Alguns têm as

+teclas do polegar, mas não há um número padrão deles. Alguns possuem um botão

+acima de cada célula braille. Alguns têm interruptores liga/desliga(rocker

+switches). Alguns têm uma barra fácil de alcançar, que funciona como um

+joystick. Alguns têm as combinações dos anteriores. Porque a natureza e a

+disposição de cada display é tão diferente, consulte o documentação para o

+display específico, a fim de descobrir exatamente o que suas teclas fazem.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Comandos BRLTTY são

+referidos pelo nome neste manual. Se você esquecer qual é a tecla(s) no seu

+display braille para usar um comando particular, em seguida, consulte a sua

+página de ajuda dos driver’s. A tecla principal você deve imediatamente guardar

+na memória, portanto, é único para o comando HELP. Use as teclas de movimento

+regular (conforme descrito abaixo) para navegar na página de ajuda, e pressione

+a help novamente para sair.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.1 Deslocamento Vertical<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja também as

+PRINDENT/NXINDENT e o PRDIFCHAR/NXDIFCHAR teclas &nbsp;de roteamento de

+comandos.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+LNUP/LNDN</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo em uma

+linha. Se o comando pular linha idêntica foi ativado (veja o comando SKPIDLNS),

+então esses comandos, ao invés de mover exatamente uma linha, serão associados

+os comandos PRDIFLN/NXDIFLN.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+WINUP/WINDN</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo em uma

+janela. Se a janela é apenas uma linha alta move 5 linhas.<br>

+<br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>PRDIFLN/NXDIFLN</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo para a

+linha mais próxima com conteúdo diferente. Se o comando pular linha idêntica

+foi ativado (veja o comando SKPIDLNS), então esses comandos, ao invés de pular

+linhas idênticas, serão associados os comandos LNUP/LNDN.<br>

+<br>

+ATTRUP/ATTRDN</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo para a

+linha mais próxima com diferentes atributos (caracter em destaque).<br>

+<br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>TOP/BOT</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>Vá para a linha superior/inferior.<br>

+<br>

+TOP_LEFT/BOT_LEFT</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>Vá para o canto top-left/bottom-left.<br>

+<br>

+</span><span style='font-size:11.0pt'>PRPGRPH/NXPGRPH<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para a próxima linha do

+parágrafo anterior/seguinte (a primeira linha não-branco para além do próximo

+linha em branco). A linha atual é incluída na pesquisa para o espaço

+inter-paragráfo.<br>

+<br>

+PRPROMPT/NXPROMPT<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para o prompet comando

+anterior/seguinte.<br>

+<br>

+PRSEARCH/NXSEARCH<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Procura para trás/frente

+para a próxima ocorrência da cadeia de caracteres no buffer de corte (ver

+Recortar e Colar) que não estiver dentro da janela braille. Os recursos de

+pesquisa para a esquerda/direita, começando imediatamente no caracter à

+direita/esquerda da janela, e envolvendo a borda da tela. A pesquisa não

+diferencia maiúsculas de minúsculas.<br>

+<br>

+4.1.2 Deslocamento Horizontal<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja também as SETLEFT

+teclas de encaminhamento de comando.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CHRLT/CHRRT</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vá para a esquerda/direita

+de um caracter.<br>

+<br>

+HWINLT/HWINRT</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vá para a esquerda/direita

+da metade de uma janela.<br>

+<br>

+FWINLT/FWINRT</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vá para a esquerda/direita

+de uma janela. Estes comandos são particularmente úteis porque quebram

+automaticamente quando atingir a borda da tela. Outros recursos, como a sua

+capacidade de pular janelas em branco (veja o comando SKPBLNKWINS), reforçar a

+sua utilidade.<br>

+<br>

+FWINLTSKIP/FWINRTSKIP</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vá para a esquerda/direita

+para a janela mais próxima não-branca.<br>

+<br>

+LNBEG/LNEND</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para o início/fim da

+linha.<br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>4.1.3 Deslocamento Implícito<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja também GOTOMARK

+teclas de encaminhamento de comando.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+HOME</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vai para onde está o

+cursor.<br>

+<br>

+BACK</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Volte para onde o comando

+de movimento mais recente colocado no display braille. Esta é uma maneira fácil

+de voltar para onde você estava lendo, após um evento inesperado (como

+acompanhamento do cursor) movendo a janela braille em um momento inoportuno.<br>

+<br>

+RETURN</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l4 level1 lfo5;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o movimento

+mais recente da janela do braille foi automático, por exemplo, como resultado

+do monitoramento de cursor, então volta para onde o comando de movimento mais

+recente colocá-lo (veja o comando BACK).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l4 level1 lfo5;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o cursor não

+estiver dentro da janela do braille, em seguida, vai para onde está o cursor

+(veja o comando HOME).<br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-36.0pt;mso-list:

+l8 level3 lfo6;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-bidi-font-size:10.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>4.1.4<span style='font:7.0pt "Times New Roman"'>&nbsp; </span></span></span><![endif]><span

+style='font-size:11.0pt'><span style='mso-spacerun:yes'> </span>Caracteristica

+de Ativação</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Cada um desses comandos

+tem três formas: activate (ativar o recurso), desactivate (desativar o recurso),

+e toggle (se estiver desligado então irá ligar, e se estiver ligado irá

+desligar). Exceto conforme indicado, cada um desses recursos está inicialmente

+off, e, quando on, afeta a operação BRLTTY como um todo. A configuração inicial

+de algumas dessas características podem ser alteradas através do menu de

+preferências.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+FREEZE</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Congela a imagem da tela.

+BRLTTY faz uma cópia da tela (conteúdo e atributos) a partir do momento em que

+a imagem da tela é congelada e em seguida, ignora todas as atualização da tela

+até que ela seja descongelada. Esta característica torna mais fácil, por

+exemplo, uma amostra da saída de uma aplicação que escreve muito, muito

+rapidamente.<br>

+<br>

+DISPMD</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostrar o destaque (os

+atributos) de cada caracter dentro da display braille, em vez dos próprios

+caracteres(o conteúdo). Esse recurso é útil, por exemplo, quando você precisa

+localizar um item <st1:PersonName ProductID="em destaque. Ao" w:st="on">em

+ destaque. Ao</st1:PersonName> mostrar o conteúdo da tela, o quadro de texto é

+usado (veja a opção de linha de comando -t, o quadro de texto do arquivo de

+diretiva de configuração, e --with-text-table como opção de construção). Ao

+mostrar os atributos da tela, a tabela de atributos é usado (veja -a, os

+attributes-table diretiva de arquivo de configuração, e --with-attributes-table

+opção de construção). Este recurso só afeta o terminal virtual atual.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>SIXDOTS</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostrar caracteres usando

+6 pontos, ao invés de 8 pontos, <st1:PersonName ProductID="em braille. Os"

+w:st="on">em braille. Os</st1:PersonName> pontos 7 e 8 ainda são usados por

+outras características como a representação do cursor e enfatizando o carácter

+realçado. Se uma tabela de contração foi selecionada (veja a opção -c e da

+tabela de contração das diretivas de configuração de arquivo), então ele é

+usado. Esta configuração também pode ser alterada com a preferência do estilo

+de texto.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SLIDEWIN</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se o controle de cursor

+(veja o comando CSRTRK) está on, então, sempre que o cursor move-se muito perto

+(ou mais) ou outra extremidade da janela do braille, reposicionar

+horizontalmente a janela de tal forma que o cursor, mantendo-se nesse lado,

+está mais próximo da centro. Se este recurso está off a janela braille está

+sempre posicionado de modo que sua extremidade esquerda é um múltiplo de sua

+largura da borda esquerda da tela. Esta configuração também pode ser alterada

+com a preferência Sliding Window.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SKPIDLNS</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ao invés de mover exatamente

+uma linha para cima ou para baixo, salta linhas anteriores, que têm o mesmo

+conteúdo da linha atual. Esta característica afeta o comandos LNUP/LNDN, bem

+como o recurso de linha de embalagem de FWINRT/FWINLT e o comando

+FWINLTSKIP/FWINRTSKIP. Esta configuração também pode ser alterada com a

+preferência Skip Identical Lines.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SKPBLNKWINS</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Pular janelas em branco

+durante a leitura para frente ou para trás. Este recurso afeta os comandos

+FWINLT/FWINRT. Esta configuração também pode ser alterada com a preferência

+Skip Blank Windows.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CSRVIS</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostrar o cursor pela

+superposição de um ponto padrão (veja o comando CSRSIZE) em cima do caracter

+onde ela está. Este recurso está inicialmente on. Esta configuração também pode

+ser alterada com a preferência do Mostrar Cursor.<br>

+<br>

+CSRHIDE<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ocultar o cursor (veja o

+comando CSRVIS), a fim de ler exatamente o caracter abaixo dela. Este recurso

+só afeta o terminal atual vi<br>

+<br>

+CSRTRK</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vestígio(seguir) do

+cursor. Se o cursor se move para um local que não seja dentro da janela do braille,

+então automaticamente move a janela de braille para a nova localização do

+cursor. Você geralmente quer este recurso ativado, uma vez que minimiza os

+efeitos de rolagem da tela, e uma vez que, durante a entrada, a região onde

+você está atualmente escrevendo sempre é visível. Se este recurso faz com que a

+janela de braille salte em um momento inoportuno, use o comando BACK para

+voltar para onde você estava lendo. Talvez seja necessário desativar esse

+recurso ao usar um aplicativo que atualiza continuamente a tela, mantendo um

+layout de dados fixo. Este recurso está inicialmente ligado. Este recurso só

+afeta o terminal virtual atual.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CSRSIZE</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Representa o curso com os

+oito ponto(um bloco sólido), e não apenas com os pontos 7 e 8 (um sublinhado).

+Esta configuração também pode ser mudada com a preferência do Curso Style.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CSRBLINK</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Pisca(liga e desliga de

+acordo com um intervalo pré-definido) o símbolo que representa o cursor(veja o

+comando CSRVIS). Esta configuração também pode ser mudada com a preferência do

+Blinking Cursor.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+ATTRVIS</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Sublinhado(com

+combinações dos pontos 7 e 8) o caracter destacado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Sem

+sublinhado</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Branco

+sobre preto(normal),cinza sobre preto, branco sobre azul, preto sobre cinza.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Pontos

+7 ou 8</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Preto

+sobre branco (vídeo reverso).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Pino

+8</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Todo

+o resto.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'>Esta

+configuração também pode ser mudada com a preferência do Mostrar Atributos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>ATTRBLINK</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Pisca(muda de ligado e

+desligado de acordo com o intervalo predefinido) o atributo sublinhado(veja o

+comando ATTRVIS). Este recurso esta inicialmente ligado. Esta configuração

+também pode ser mudada com a preferência do Blinking Attributes.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>CAPBLINK</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Pisca(liga e desliga de

+acordo com um intervalo pré-definido) a letra maiúscula. Esta configuração

+também pode ser mudada com a preferência do Blinking Capitals.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:27.0pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>TUNES</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Tocar uma melodia curta

+pré-definidas (ver Alert Tunes) sempre que ocorra um evento significativo. Este

+recurso esta inicialmente ligado. Esta configuração também pode ser alterada

+com a preferência Alerta de Tunes.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>AutoRepeat</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Automaticamente repete um

+comando em um intervalo regular após um atraso inicial enquanto a sua chave

+(combinação) continua pressionada. Apenas alguns drivers suportam esta

+funcionalidade, a principal limitação é que muitos display braille não

+sinalizam teclas apertadas e teclas liberadas como eventos separados

+distintamente. Este recurso esta inicialmente ligado. Esta configuração também

+pode ser alterada com a preferência de Autorepetição.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>AUTOSPEAK</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Automaticamente falar:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l13 level1 lfo7;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>A nova linha

+quando a janela braille é movida verticalmente.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l13 level1 lfo7;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>caracteres que

+são inseridos ou excluídos.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l13 level1 lfo7;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>O caracter ao

+qual o cursor é movido.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Este recurso esta

+inicialmente desligado. Esta configuração também pode ser alterada com a

+preferência de Auto-Fala.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.5 Modo de Seleção<br>

+<br>

+HELP</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alterne para a página de

+ajuda do display braille. Isto é onde você pode encontrar um resumo on-line das

+coisas que as teclas do seu display braille fazem, e como interpretar o status

+das células. Use os comandos regular de movimento vertical e horizontal para

+navegar pela página de ajuda. Chame o comando de ajuda novamente para voltar

+para a tela.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+INFO</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alterne para a exibição de

+status (ver seção The Status Display para mais detalhes). Ele apresenta um

+resumo com a posição do cursor, a posição da janela braille, e os estados de

+uma série de características do BRLTTY. Chame este comando novamente para

+voltar para a tela.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>LEARN</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alternar para o modo de

+comando aprender (ver seção Command Learn Mode para mais detalhes). Isto é como

+você pode aprender de forma interativa o que as teclas do seu display braille

+fazem. Chamar este comando novamente para voltar para a tela. Este comando não

+estará disponível se a opção --disable-learn-mode foi especificada na opção de

+construção.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-36.0pt;mso-list:

+l3 level3 lfo8;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-size:11.0pt;mso-fareast-font-family:"Courier New"'><span

+style='mso-list:Ignore'>4.1.6<span style='font:7.0pt "Times New Roman"'>&nbsp; </span></span></span><![endif]><span

+style='font-size:11.0pt'>Preferencias de Manutenção<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>PREFMENU<o:p></o:p></span></p>

+

+<p class=MsoPlainText>Escolha do menu de preferências (Ver Menu Preferências).

+Chama esse comando de novo para retornar as operações normais.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>PREFSAVE</p>

+

+<p class=MsoPlainText>Salva as configurações de preferências atuais (Ver

+Preferências).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>PREFLOAD</p>

+

+<p class=MsoPlainText>Armazena as configurações de preferências mais recentes

+(Ver Preferencias).</p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.7 Menu de Navegação<br>

+<br>

+MENU_FIRST_ITEM/MENU_LAST_ITEM</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para o primeiro/último

+item no menu.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+MENU_PREV_ITEM/MENU_NEXT_ITEM</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para o

+anterior/seguinte item no menu.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+MENU_PREV_SETTING/MENU_NEXT_SETTING</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Retroceder/Expandir o

+atual item do menu.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.8 Controles de Fala<br>

+<br>

+SAY_LINE</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Fala a linha atual. A

+preferência do Say-Line Mode determina se fala pendente é descartada em

+primeiro lugar.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SAY_ABOVE</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Fale a parte superior da

+tela (termina com a linha atual).</span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>SAY_BELOW</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Fale a parte inferior da

+tela (começando com a linha atual).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+MUTE</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Pare de falar

+imediatamente.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SPKHOME</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vá para onde o cursor

+fala.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SAY_SLOWER/SAY_FASTER</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Aumentar/diminuir a

+velocidade de leitura (ver também a preferência de Speech Rate). Este comando

+só está disponível se um driver que suporta ele está sendo usado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SAY_SOFTER/SAY_LOUDER</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Aumentar/diminuir o volume

+da voz (ver também a preferência de Speech Volume). Este comando só está

+disponível se um driver que suporta ele está sendo usado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.9 Comutação do Terminal Virtual<br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja também SWITCHVT

+encaminhamento de teclas de comando.<br>

+<br>

+SWITCHVT_PREV/SWITCHVT_NEXT</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mude para o

+anterior/próximo terminal virtual.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.10 Outros Comandos<br>

+<br>

+CSRJMP_VERT<br>

+&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; </span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Encaminha (trazer) o cursor para qualquer

+lugar na linha de cima da janela do braille (ver Roteamento de Cursor). O

+cursor é movido por uma simulação vertical das teclas de seta pressionadas.

+Este comando nem sempre funciona, porque alguns aplicativos ou move o cursor um

+pouco imprevisível ou use as teclas de seta para outros fins que não o

+movimento do cursor. É um pouco mais seguro do que outros comandos de

+roteamento do cursor, porém, porque não faz nenhuma tentativa de simular as

+setas da esquerda e direita.<br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>PASTE<br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Insira os caracteres no buffer de corte

+na posição atual do cursor (ver Copiar e Colar).<br>

+<br>

+RESTARTBRL<br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Pare e reinicie o controlador de

+visualização <st1:PersonName ProductID="em Braille.&#65532;&#65532;RESTARTSPEECH&#65532;&#65532;Pare"

+w:st="on">em Braille.<br>

+ <br>

+ RESTARTSPEECH<br>

+ <span style='font-size:10.0pt;font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+ </span>Pare</st1:PersonName> e reinicie o controlador de sintetizador de voz.<br>

+<br>

+4.1.11 Comandos de Carácter<br>

+<br>

+ROUTE<br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Encaminha (trazer) o cursor para o

+caractere associado a tecla de roteamento (ver Roteamento de Cursor). O cursor

+é movido por uma simulação das teclas de seta pressionadas. Este comando nem

+sempre funciona, porque alguns aplicativos ou move o cursor um pouco

+imprevisível ou use as teclas de seta para outros fins que não o movimento do

+cursor.<br>

+<br>

+CUTBEGIN<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Apoia-se no início do

+bloco de corte para o caracter associado com a tecla de roteamento (ver Copiar

+e Colar). Este comando limpa o buffer de corte.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CUTAPPEND</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Apoia-se no início do

+bloco de corte para o caráter associado com a tecla de roteamento (ver Copiar e

+Colar). Este comando não limpar o buffer de corte.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+CUTRECT</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Apoia-se no fim do bloco

+de corte para o caráter associado com a tecla de roteamento, e anexar a região

+retangular para o buffer de corte (ver Copiar e Colar).</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>CUTLINE</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Apoia-se no fim do bloco

+de corte para o caráter associado com a tecla de roteamento, e anexar a região

+linear para o buffer de corte (ver Copiar e Colar).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+COPYCHARS</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Copie o bloco de caráter

+apoiado-se por duas tecla de roteamento para o buffer de corte (ver Copiar e

+Colar).</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+APNDCHARS<br>

+&nbsp;&nbsp;&nbsp; </span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Acrescente o bloco de

+caráter apoiand-se por duas teclas de roteamento para o buffer de corte (ver

+Copiar e Colar).<br>

+<br>

+PRINDENT/NXINDENT</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo para a

+próxima linha que não está mais avançado do que a coluna associada à tecla de

+roteamento.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+DESCCHAR</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Momentaneamente (veja a

+opção -M de linha de comando) exibi uma mensagem descrevendo o caráter

+associado com a tecla de roteamento. Ela revela os valores decimais e

+hexadecimais do caracter, as cores de primeiro plano e fundo, e, quando

+presentes, atributos especiais (bright e brink). </span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>A mensagem é assim:</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>char 65 (0x41): white on black bright blink</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+SETLEFT</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Reposiciona

+horizontalmente a janela braille de modo que sua borda esquerda é a coluna

+associada à tecla de roteamento. Esta característica torna muito fácil colocar

+a janela exatamente onde ela é necessária, e, por conseguinte, para mostra o

+que tem as teclas de roteamento, quase elimina a necessidade de muito movimento

+fundamenta dajanela (como os comandos CHRLT/CHRRT e HWINLT/HWINRT).</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>PRDIFCHAR/NXDIFCHAR</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ir para cima/baixo para a

+próxima da linha que tem um caráter diferente na coluna associada à tecla de

+roteamento.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+4.1.12 Comandos de Suporte<br>

+<br>

+SWITCHVT</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Muda para o terminal virtual

+cujo número (contagem a partir de 1) corresponde ao da tecla de roteamento.

+Veja também os comandos de troca de terminal virtual

+SWITCHVT_PREV/SWITCHVT_NEXT.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+SETMARK</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Marcar (lembre-se), a

+posição atual da janela braille num registo associado com a telca de

+roteamento. Veja o comando GOTOMARK. Este recurso só afeta o terminal virtual

+atual.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+GOTOMARK</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Move a janela braille para

+a posição anteriormente marcada (veja o comando SETMARK) com a mesma tecla de

+roteamento. Este recurso só afeta o terminal virtual atual.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>4.2 - Arquivos de

+configuração</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Padrões do sistema para

+diversas configurações podem ser estabelecidas dentro de um arquivo de

+configuração. O nome padrão para este arquivo é /brltty.conf /etc, embora possa

+ser substituído com a opção de linha de comando -f. Ele não precisa existir. Um

+modelo para ele pode ser encontrado dentro do subdiretório DOCS.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As linhas em branco são

+ignoradas. Um comentário começa com um sinal numérico (#), e continua até o fim

+da linha. As seguintes diretivas são reconhecidas:</span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>api-parameters

+name=value,...</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para a Application Programming Interrface. Se o mesmo parâmetro for

+especificado mais de uma vez, a sua atribuição mais a direita é usada. Para

+obter uma descrição dos parâmetros aceitos pela interface, consulte o manual de

+referência BrlAPI. Veja as opções de construção --with-api-parameters para os

+padrões estabelecidos durante o processo de construção. Esta diretiva pode ser

+substituído com a opção de linha de comando -A.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+attributes-table file</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+atributos (ver Attributes Tables para obter detalhes). Se um caminho relativo

+for fornecido, então ele está ancorado em /etc /brltty (ver as opções de

+compilação --with-data-directory e --with-execute-root para mais detalhes). A

+extensão .atb é opcional. O padrão é usar a tabela built-in (veja a opção de

+construção --with-attributes-table). Esta diretiva pode ser substituída com a

+opção de linha de comando -a.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+braille-device device,...</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo

+para o qual o display braille está ligado (consulte a seção Braille Device

+Specification). Veja a opção construção --with-braille-device para o padrão

+estabelecido durante o processo de construção. Esta diretiva pode ser

+substituída com a opção de linha de comando-d.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+braille-driver driver,...|auto<br>

+Especifica o driver do display braille (ver seção Driver Specification). </span><span

+style='font-size:11.0pt'>O padrão é a realização de auto-detecção. Esta

+diretiva pode ser substituída com a opção de linha de comando -b.<br>

+<br>

+braille-parameters [driver:]name=value,...</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drivers do display braille. Se o mesmo parâmetro for especificado mais

+de uma vez, a sua atribuição mais a direita é usada. Se um nome de parâmetro é

+qualificado por um driver (consulte a seção Driver Identification Codes), então

+essa configuração só se aplica ao driver, se não é, então se aplica a todos os

+drivers. Para obter uma descrição dos parâmetros aceitos por um driver

+específico, consulte a documentação desse drive. Veja a de opção construção

+--with-braille-parameters para os padrões estabelecidos durante o processo de

+construção. Esta diretiva pode ser substituída com a opção de linha de comando

+-B.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>contraction-table file</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+contração (ver seção Contraction Tables para mais detalhes). Se um caminho

+relativo for fornecido, então ele está ancorado em /etc/brltty (ver as opções

+de construção --with-data-directory e --with-execute-root para mais detalhes).

+A extensão .ctb. é opcional. A tabela de contração é usado quando o recurso de

+6-pontos braille é ativado (veja o comando SIXDOTS e a preferência Text Style).

+O padrão é mostrar descontracionada 6-pontos braille. Esta diretiva pode ser

+substituída com a opção de linha de comando -c. Não está disponível se a opção

+de construção --disable-contracted-braille foi especificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+key-table file|auto</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+tecla (ver Key Tables para detalhes). Se um caminho relativo for fornecido,

+então ele está ancorado em /etc/brltty (ver as opções de compilação

+--with-data-directory e --with-execute-root para mais detalhes). A extensão

+.ktb é opcional. O padrão é não usar uma tabela de teclas. Esta diretiva pode

+ser substituída com a opção de linha de comando -k.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+keyboard-properties name=value,...</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica as propriedades

+dos teclado(s) a serem monitorados. Se a mesma propriedade for especificada

+mais de uma vez, a sua atribuição mais a direita é usada. Consulte a seção

+Keyboard Properties para obter uma lista das propriedades que podem ser

+especificadas. O padrão é monitorar todos os teclados. Esta diretiva pode ser

+substituída com a opção de linha de comando -K.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+midi-device device</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo a

+ser usado para o Musical Instrument Digital Interface (ver seção MIDI Device

+Specification). Esta diretiva pode ser substituída com a opção de linha de

+comando -m. Não está disponível se a opção de construção --disable-midi-support

+foi especificada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+pcm-device device</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo a

+ser usado para o áudio digital (ver seção PCM Device Specification). Esta

+diretiva pode ser substituída com a opção linha de comando -p. Não está

+disponível se a opção de construção --disable-pcm-support foi especificada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>preferences-file file</p>

+

+<p class=MsoPlainText>Especifica a localização do arquivo que sera usada para

+salvar e carregar as preferencias de usuario. Se um caminho relativo é

+fornecido, será anexado em /var/lib/brltty. O padrão é usado <st1:PersonName

+ProductID="em brltty.prefs. O" w:st="on">em brltty.prefs. O</st1:PersonName>

+diretório pode ser reescrit com o comando -F.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>release-device boolean</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Querendo ou não, para

+liberar o dispositivo ao qual o display braille está ligado quando a tela ou

+janela atual não pode ser lido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'><br>

+On(on)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Libera

+o dispositivo.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Off(off)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Não

+liberar o dispositivo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A configuração padrão é

+ligado <st1:PersonName ProductID="em plataformas Windows" w:st="on">em

+ plataformas Windows</st1:PersonName> e desligado em todas as outras

+plataformas. Esta diretiva pode ser substituída com a opção de linha de comando

+-r.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+screen-driver driver</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o driver da

+tela (ver seção Drivers de Tela Suportados). Veja a opção de construção

+--with-screen-driver para o padrão estabelecido durante o processo de

+compilação. Esta diretiva pode ser substituída com a opção de linha de comando

+-x.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+screen-parameters [driver:]name=value,...</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drivers de tela. Se o mesmo parâmetro for especificado mais de uma vez,

+a sua atribuição mais a direita é usada. Se um nome de parâmetro é qualificado

+por um driver (consulte a seção Drivers de Tela Suportados), em seguida, a

+definição só se aplica ao drive, se não é, então, se aplica a todos os drivers.

+Para obter uma descrição dos parâmetros aceitos por um driver específico,

+consulte a documentação do drive. Veja a opção de construção

+--with-screen-parameters para os padrões estabelecidos durante o processo de

+construção. Esta diretiva pode ser substituída com a opção de linha de comando

+-X.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+speech-driver driver,...|auto</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o driver do

+sintetizador de voz (ver seção Especificação de Driver). O padrão é a

+realização de auto-detecção. Esta diretiva pode ser substituída com a opção de

+linha de comando -s. Não está disponível se a opção de construção

+--disable-speech-support foi especificada.</span> </p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>speech-fifo file</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a FIFO (um

+arquivo especial que funciona como um tubo) que é usado por outras aplicações

+que desejam ter acesso ao driver de fala BRLTTY. Esta diretiva pode ser

+substituída com a opção de linha de comando -F. Não está disponível se a opção

+de construção --disable-speech-support &nbsp;foi especificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+speech-parameters [driver:]name=value,...</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drives de sintetizador de voz. Se o mesmo parâmetro é especificado mais

+de uma vez, a sua atribuição mais a direita é usada. Se um nome de parâmetro é

+qualificado por um driver (consulte a seção Código de Identificação de Driver),

+então essa configuração só se aplica ao drive, senão se aplica a todos os

+drivers. Para obter uma descrição dos parâmetros aceitos por um driver

+específico, consulte a documentação do drive. Veja a opção de construção

+--with-speech-parameters para os padrões estabelecidos durante o processo de

+construção. Esta diretiva pode ser substituída com o opção de linha de comando

+-S.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+text-table file|auto</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+texto(veja a seção Tabelas de Texto para detalhes). Se um caminho relativo for

+fornecido, então ele está ancorado em /etc/brltty (veja as opções de compilação

+–with-data-directory e –with-execute-root para mais detalhes). A extensão .ttb

+é opcional. O padrão é executar auto-seleção do locale-based, com alternativa

+para a tabela build-in (veja a opção compilação --with-text-table). Esta

+diretiva pode ser subtituída com a opção de linha de comando –t.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>speech-input name</p>

+

+<p class=MsoPlainText>Especifica o nome do arquivo do objeto do sistema (FIFO,

+pipe name, socket name, etc) que pode ser usado com outras aplicações para

+conversão text-to-speech via driver de fala BRLTTY. Este diretório pode ser

+reescrito com o comando -i.</p>

+

+<p class=MsoPlainText>Não está disponivel se a opção na compilação não for

+especificada --disable-speech-support.</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>4.3 - Opções de

+Linha de Comando</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Muitas configurações podem

+ser explicitamente especificadas ao chamar BRLTTY. Os comandos brltty aceitam

+as seguintes opções:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-afile --attributes-table=file</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+atributos (veja seção Tabelas de Atributos). Se um caminho relativo for

+fornecido, então ele está ancorado em /etc/brltty (veja as opções de compilação

+–with-data-directory e –with-execute-root para mais detalhes). A extensão .atb

+não é necessária.Veja as configurações do arquivo attributes-table para a

+configuração padrão de run-time. Esta configuração pode ser alterada com a

+preferência Tabela de Atributos.</span><span style='mso-spacerun:yes'>   

+</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>-bdriver,...|auto --braille-driver=driver,...|auto</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o drive do

+display braille (veja seção Especificação de Driver). Veja as configurações do

+arquivo braille-drive para a configuração padrão de run-time.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-cfile --contraction-table=file</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+contração (veja seção Tabela de Contração). Se um caminho relativo for

+fornecido, então ele está ancorado em /etc/brltty (veja as opções de compilação

+--with-data-directory e --with-execute-root para mais detalhes). A extensão

+.ctb é opcional. A tabela de contração é usado quando o recurso de 6-pontos braille

+é ativado (veja o comando SIXDOTS e a preferência Estilo de Texto). Veja as

+configurações do arquivo tabela de contração para a configuração padrão de

+run-time. Esta configuração pode ser alterada com a preferência Tabela de

+Contração. Esta opção não está disponível se a opção de construção

+--disable-contracted-braille foi especificada.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-ddevice,... --braille-device=device,...</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo

+para o qual o display braille está ligado (ver seção Especificação Dispositivo

+Braille). Veja as configurações do arquivo braille-device para a configuração

+padrão de run-time.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-e --standard-error</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Escreve mensagens de

+disgnóstico de erro padrão. O padrão é gravá-los via syslog</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-ffile --configuration-file=file</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o local do

+arquivo de configuração que será utilizado para a criação das configurações

+padrões de run-time.</span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-h -–help</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostra um resumo das

+opções de linha de comando aceitas pelo BRLTTY, e depois sai.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-kfile --key-table=file</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+teclas (veja seção Key Tables pra detalhes). Se um caminho relativo for

+fornecido, então ele está ancorado em /etc/brltty (veja as opções de compilação

+--with-data-directory e --with-execute-root para mais detalhes). A extensão

+.ktb é opcional. Esta configuração pode ser alterada com a preferência Tabela

+de Telas.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-llevel --log-level=level</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifique o limite de

+gravidade para geração de mensagens de diagnóstico. Os seguintes níveis são

+reconhecidos.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>0</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>emergência</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>1</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>alerta</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>2</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>crítico</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>3</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>erro</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>4</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>aviso</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>5</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>advertência</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>6</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>informações</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>7</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>debug</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Ou o número ou o nome pode

+ser fornecido, bem como o nome pode ser abreviado. Se não for especificado,

+então o nível informação é assumido (veja a opção -q para mais detalhes).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-mdevice --midi-device=device</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo a

+ser usado para o Music Instrument Digital Interface (ver seção Especificação de

+Dispositivos MIDI). Veja as configurações do arquivo midi-device para a

+configuração padrão de run-time. Esta opção não está disponível se a opção de

+construção --disable-midi-support foi especificada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-n --no-daemon</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica que BRLTTY irá

+ficar em primeiro plano. Se não for especificado, então BRLTTY torna-se um

+processo em background (daemon) após se inicialização, mas antes de iniciar

+qualquer um dos drivers selecionados.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-pdevice --pcm-device=device</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o dispositivo a

+ser usado para o áudio digital (ver seção Especificação do Dispositivo PCM).

+Veja as configurações do arquivo pcm-device para a configuração padrão de

+run-time. Esta opção não estará disponível se a opção a opção de compilação --disable-pcm-support

+foi especificado.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-q –quiet</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Log menos informação. Esta

+opção altera o nível de log (veja a opção -l) para notice se qualquer das opção

+-v ou -V seja especificado, e para warnig caso contrário.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-r --release-device</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Libera o dispositivo para

+o qual o display braille está ligado quando a tela ou janela atual não pode ser

+lido. Veja as configurações do arquivo release-device para a configuração

+padrão de run-time.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-sdriver,...|auto --speech-driver=driver,...|auto</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o driver do sintetizador

+de voz (ver seção Especificação de Driver). Veja as configurações do arquivo

+speech-driver para a configuração padrão de run-time. Esta opção não estará

+disponível se a opção de construção --disable-speech-support foi especificado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-tfile --text-table=file</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a tabela de

+texto (ver seção Tabela de Texto). Se um caminho relativo for fornecido, então

+ele está ancorado em /etc/brltty (ver as opções de construção

+--with-data-directory e --with-execute-root para mais detalhes). A extensão

+.ttb é opcional. Veja as configurações do arquivo text-table para a

+configuração padrão de run-time. Esta configuração pode ser alterada com a

+preferência Tabela Texto.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-v –verify</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostrar a versão atual do

+BRLTTY, do lado do servidor de sua interface de programação de aplicativo, do

+braille selecionado e drivers de fala, e depois sai. Se a opção-q não for

+especificado, então também exibir os valores das opções depois de todas as

+fontes serem consideradas. Se mais de um driver braille (veja a opção de linha

+de comando -b) e/ou mais de um dispositivo braile (veja a opção linha de

+comando -d) tenha sido especificado, então a autodetecção do display Braille é

+realizado. Se mais de um driver de fala(veja a opção de linha de comando -s)

+tenha sido especificado, então a autodetecção do sintetizador de voz é

+realizada.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-xdriver --screen-driver=driver</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o driver da

+tela (ver seção Drivers Suportados de Tela). Veja as configurações do arquivo

+screen-driver para a configuração padrão de run-time.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-Aname=value,... --api-parameters=name=value,...</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para a Application Programming Interface. Se o mesmo parâmetro é especificado

+mais de uma vez, a sua atribuição mais a direita é usada. Para obter uma

+descrição dos parâmetros aceitos pela interface, consulte o manual de

+referência BrlAPI. Veja as configurações do arquivo api-parameters para a

+configuração padrão de run-time.</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>-B[driver:]name=value,... --braille-parameters=[driver:]name=value,...</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drivers do display braille. Se o mesmo parâmetro é especificado mais de

+uma vez, a sua atribuição mais a direita é usada. Se um nome de parâmetro é

+qualificado por um driver (consulte a seção Códigos de Identificação de

+Drivers), então essa configuração só se aplica ao drive, se não é, então se

+aplica a todos os drivers. Para obter uma descrição dos parâmetros aceitos por

+um driver específico, consulte a documentação do driver. Veja as configurações

+do arquivo braille-parameters para a configuração padrão de run-time.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-E --environment-variables</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Reconhece as variáveis de

+ambiente para determinar as configurações padrão para as opções de linha de

+comando não especificada (ver seção Opções de Linha de Comando). Se esta opção

+for especificada, e se a variável de ambiente associados a uma opção não

+especificada é definida, então o valor da variável de ambiente é utilizado. Os

+nomes dessas variáveis de ambiente são baseados em nomes grandes das opções que

+correspondem a:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l12 level1 lfo9;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Todas as letras

+são em maiúsculas.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l12 level1 lfo9;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Os underscores

+(_) são utilizados em vez de sinais de menos (-).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l12 level1 lfo9;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>O prefixo BRLTTY_

+é adicionado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta opção é

+particularmente útil no sistema operacional Linux, pois permite configuração

+padrão a ser passado para BRLTTY através de parâmetros de inicialização. As

+seguintes variáveis de ambiente são suportadas:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_API_PARAMETERS</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Parâmetros para a

+Application Programming Interface (veja a opção de linha de comando -A).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_ATTRIBUTES_TABLE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A tabela de atributos

+(veja a opção de linha de comando -a).</span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='font-size:11.0pt'><br>

+BRLTTY_BRAILLE_DEVICE</span><span lang=EN-US style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O dispositivo do display

+braille (veja a opção de linha de comando -d).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_BRAILLE_DRIVER</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O driver do display

+braille (veja a opção de linha de comando -b).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_BRAILLE_PARAMETERS</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Parâmetros para o driver

+do display braille (veja a opção de linha de comando -B).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_CONFIGURATION_FILE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O arquivo de configuração

+(veja a opção de linha de comando -f).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_CONTRACTION_TABLE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A tabela de contração

+(veja a opção de linha de comando -c).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_KEY_TABLE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A tabela de teclas (veja a

+opção de linha de comando -k).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_KEYBOARD_PROPERTIES</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As propriedades do teclado

+(veja a opção de linha de comando -K).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_MIDI_DEVICE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O dispositivo Musical

+Instrument Digital Interface (veja a opção de linha de comando -m).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_PCM_DEVICE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O dispositivo de audio

+digital (veja a opção de linha de comando -p).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_RELEASE_DEVICE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Querendo ou não libera o

+dispositivo para o qual o display braille está ligado quando a tela ou janela

+atual não pode ser lido (veja a opção de linha de comando -r).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_SCREEN_PARAMETERS</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Parâmentros para o driver

+da tela (veja a opção de linha de comando -X).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_SPEECH_DRIVER</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O driver do sintetizador

+de voz (veja a opção de linha de comando -s).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_SPEECH_FIFO</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A passagem FIFO da voz (

+veja a opção de linha de comando -F).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_SPEECH_PARAMETERS</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Parâmetros para o driver

+do sintetizado de voz ( veja a opção de linha de comando -S).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+BRLTTY_TEXT_TABLE</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A tabela de texto ( veja a

+opção de linha de comando -t).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-Ffile --speech-fifo=file<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a FIFO (um

+arquivo especial que funciona como um tubo) que é usado por outras aplicações

+que desejam ter acesso ao driver BRLTTY da fala. Se não é especificado, FIFO

+não é criado. Veja as configurações do arquivo speech-fifo para a configuração

+padrão de run-time. Esta opção não estará disponível se a opção de construção

+--disable-speech-support &nbsp;foi especificada.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-i --install-service</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Instala o BRLTTY como o

+serviço BrlAPI. Isso significa que:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+BRLTTY será iniciado automaticamente quando o sistema é inicializado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Aplicações podem saber que um servidor BrlAPI está sendo executado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta opção só é suportado

+na plataforma Windows.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-Kname=value,... --keyboard-properties=name=value,...</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica as propriedades

+do teclado(s) a ser monitorado. Se a mesma propriedade for especificada mais de

+uma vez, a sua atribuição mais a direita é usada. Consulte a seção Keyboard

+Prperties para obter uma lista das propriedades que podem ser especificadas.

+Veja as configurações do arquivo keyboard-properties para a configuração padrão

+de run-time.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-Mcsecs --message-delay=csecs</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica a quantidade de

+tempo (em centésimos de segundo) que o BRLTTY mantém seu próprio gerador de

+mensagens internamente no display braille. Se não for especificado, então 400

+(4 segundos) é assumida.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-N --no-api</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Desativa a interface de

+programação de aplicativo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-Pfile --pid-file=file</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o arquivo onde

+o BRLTTY escreve o seu identificador de processo (PID). Se não especificado,

+BRLTTY não escreve o seu processo de identificação em qualquer lugar.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-R --remove-service</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Remove o serviço BrlAPI.

+Isso significa que:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· BRLTTY não será iniciado

+automaticamente quando o sistema é inicializado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· Aplicações podem saber

+que nenhum servidor BrlAPI está sendo executado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta opção só é suportado

+na plataforma Windows.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-S[driver:]name=value,... --speech-parameters=[driver:]name=value,...</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drivers do sintetizador de voz. Se o mesmo parâmetro é especificado

+mais de uma vez, a sua atribuição mais a direita é usada. Se um nome de

+parâmetro é qualificado por um driver (consulte a seção Código de Identificação

+de Drivers), então essa configuração só se aplica ao driver, se não se aplica a

+todos os drivers. Para obter uma descrição dos parâmetros aceitos por um driver

+específico, consulte a documentação do driver. Veja as configurações do arquivo

+speech-parameters para a configuração padrão de run-time.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-Ucsecs

+--update-interval=csecs</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica o intervalo (em

+centésimos de segundo) em que a janela do braille é atualizada com o novo

+conteúdo da tela. Se não for especificado, então, 4 (40 milisegundos) é

+assumido.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><br>

+-V --version</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mostra a versão atual do

+BRLTTY, do lado do servidor de sua interface de programação de aplicativo, e

+dos drivers que foram ligados ao seu binário, e depois sai. Se a opção-q não

+for especificado, então também exibir informações de direitos autorais.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><br>

+-X[driver:]name=value,... --screen-parameters=[driver:]name=value,...</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica os parâmetros

+para os drivers da tela. Se o mesmo parâmetro é especificado mais de uma vez, a

+sua atribuição mais a direita é usada. Se um nome de parâmetro é qualificado

+por um driver (consulte a seção Drivers Suportados de Tela), então essa

+configuração só se aplica a esse driver, se não é, então se aplica a todos os

+drivers. Para obter uma descrição dos parâmetros aceitos por um driver

+específico, consulte a documentação do driver. Veja as configurações do arquivo

+screen-parameters para a configuração padrão de run-time.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5. Descrições das

+Características<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.1 Cursor de

+roteamento</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ao mover a janela de Braille em torno da

+tela, para análise do texto, por exemplo, em um editor, muitas vezes você

+precisa levar o cursor até um carácter especifico dentro da janela do braille.

+Você provavelmente vai achar que isso uma tarefa bastante difícil para um número

+de razões. Uma delas é que você pode não saber onde está o cursor, e que você

+pode perder seu lugar ao tentar encontrá-lo. Outra é que o cursor pode mover

+imprevisivelmente quando as setas forem pressionadas (alguns editores, por

+exemplo, não permitem que o cursor seja mais direito ao fim da linha). Cursor

+roteamento fornece apenas essa capacidade de saber onde está o cursor, através

+da simulação das prensas mesma seta-chave que você tem que digitar manualmente,

+e pelo acompanhamento do progresso do cursor que se move.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alguns displays braille

+têm um botão, conhecido como uma tecla de cursor, acima de cada célula. Essas

+chaves usam o comando ROUTE para encaminhar o cursor para o local desejado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor de roteamento é muito conveniente

+e eficaz, porém não totalmente confiável. Uma razão para isso é que a sua

+implementação atual assume VT100 cursor seqüências de escape chave. Outra é que

+algumas aplicações fazem coisas fora do padrão em resposta ao detectar que uma

+tecla do cursor foi pressionado. Um pequeno problema encontrado dentro de

+alguns editores (como vi), como já mencionado acima, é que eles jogam em algum

+movimento imprevisível horizontal quando o movimento vertical é solicitado

+porque eles não permitem que o cursor fiquem à direita do final de uma linha .

+Um dos principais problemas encontrados dentro de alguns navegadores web (como

+o lynx) é que as teclas de seta para baixo/cima são usadas para mover entre os

+links (que podem saltar linhas e / ou mover o cursor horizontalmente, mas que

+raramente move o cursor uma linha na direção desejada), e que a esquerda e

+teclas de seta para a direita são usados para selecionar as ligações (que não

+tem absolutamente nada a ver com qualquer forma de movimento do cursor que

+seja, e que até muda totalmente o conteúdo da tela).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor de roteamento não funciona muito

+bem em um sistema muito carregado, e definitivamente não funciona muito bem ao

+trabalhar em um sistema remoto através de um link lento. Isto é assim porque de

+todas as verificações que devem ser feitos ao longo do caminho, a fim de lidar

+com o movimento do cursor imprevisível, a fim de assegurar que qualquer erro

+tem pelo menos uma chance de lutar para ser desfeito. Mesmo BRLTTY tenta ser

+bastante inteligente, ainda deve essencialmente esperar para ver o que acontece

+após cada pressionamento da seta-chave.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Uma vez que uma solicitação de cursor de

+roteamento foi feita, BRLTTY continua tentando encaminhar o cursor para o local

+pretendido, até um tempo limite de expiração antes que o cursor alcança essa localização,

+o cursor parece estar se movendo na direção errada, ou se mudar para um

+terminal virtual diferente. Uma tentativa é feita para usar o movimento

+vertical para trazer o cursor para a linha direita, e, se essa for bem

+sucedida, essa tentativa é feita, então usa o movimento horizontal para trazer

+o cursor para a coluna da direita. Se outra solicitação é feita enquanto ainda

+se está em andamento, em seguida, a primeira é anulada e o segundo é iniciada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Um cursor mais seguro, porém menos

+potente de roteamento, CSRJMP_VERT, usa apenas o movimento vertical para trazer

+o cursor para qualquer lugar no top da linha da janela braille. É especialmente

+útil em um conjunto com as aplicações (como lynx) onde o movimento do cursor

+horizontal nunca deve ser tentado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.2 Copiar e

+Colar</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este recurso permite que você pegue algum

+texto que já está na tela e digitá-la na posição atual do cursor. Usando ele

+economiza tempo e evita erros quando uma peça longa e/ou complicadas do texto

+precisa ser copiado, e mesmo quando a mesma peça curta e simples do texto

+precisa ser copiado muitas vezes. É particularmente útil para coisas como nomes

+de arquivo longos, linhas de comandos complicados, os endereços de e-mail e

+URL. Cortar e colar texto envolve três passos simples:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;1. Marque o canto superior

+esquerdo da área retangular ou no início da área linear na tela, que deve ser

+agarrado (corte). Se o display tem chaves de roteamento, em seguida, move a

+janela braille, para que o primeiro carácter a ser cortado aparecer em qualquer

+lugar dentro dela e, em seguida:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Invocar o comando CUTBEGIN para iniciar um

+novo buffer de corte.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Invocar o comando CUTAPPEND para anexar ao

+buffer de corte existentes, pressionando as chave associadas a ele e depois

+pressionar a tecla de roteamento associado com o personagem.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>2. Marque o canto inferior

+direito da área retangular ou o fim da área linear na tela, que deve ser

+agarrado (corte). Se o display tem chaves de roteamento, move a janela de

+braille para que o último caracter a ser cortado aparece em qualquer lugar

+dentro dela e, em seguida:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>· Invocar o comando CUTRECT para cortar uma

+área retangular.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;· Invocar o

+comando CUTLINE para cortar uma área linear pressionando as chave associadas a

+ele e depois pressionar a tecla de roteamento associado com o carácter.

+Marcando o fim da área de corte acrescenta o conteúdo da tela selecionada com o

+buffer de corte. O excesso de espaço em branco é removido no final de cada

+linha no buffer de corte de modo que os espaços indesejados no final não vão

+ser colado para trás do carácter de controle que serão substituídos por espaços

+em branco.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>3. Inserir (colar) o texto onde ele é

+necessário. Coloque o cursor sobre o carácter onde o texto deve ser colado, e

+invocar o comando PASTE. Você pode colar o mesmo texto varias vezes sem

+recortar. Esta descrição assume que você já está em algum tipo de modo de

+entrada. Se você colar quando você estiver em algum outro tipo de modalidade

+(como comando vi), então é melhor você estar ciente de que os caracteres no buffer

+de corte vão fazer.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O buffer de corte também é usado pelos

+comandos PRSEARCH/NXSEARCH.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.3 Suporte do

+Ponteiro (Mouse) via GPM</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se BRLTTY é configurado com a opção de

+compilação --enable-gpm no sistema onde a aplicação gpm foi instalada, em

+seguida, ele vai interagir com o cursor (mouse).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Mover o ponteiro e arrastar a janela

+braille (veja Window Follows Pointer). Sempre que o ponteiro é movido para além

+da borda da janela do braille, a janela do braille é arrastada (um carácter por

+vez). Isto dá ao usuário braille outra forma bidimensional para inspecionar o

+conteúdo da tela ou mover-se rapidamente a janela braille para o local

+desejado. Ele também dá uma visão de observador de maneira fácil para mover a

+janela de braille para algo que ele gostaria que o usuário Braille possa ler.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O gpm usa vídeo reverso para mostrar onde

+o cursor está. Sublinhando os caracteres em destaque (veja o comando ATTRVIS)

+deve ser ativado, portanto, quando o usuário braille pretende usar o ponteiro.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esse recurso também permite o acesso do

+usuário em Braille para gpm é cortado e colado. Embora você deve ler a

+documentação própria gpm, aqui estão algumas notas sobre a forma como ele

+funciona.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Cópia do carácter atual para o buffer

+cortado por um único clique com o botão esquerdo.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Cópia da palavra atual (espaço delimitado)

+para o buffer de corte com um duplo clique com o botão esquerdo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Cópia da linha atual para o buffer de

+corte de triplo-clique com o botão esquerdo.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Cópia de uma região linear para o buffer

+de corte da seguinte forma:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.

+Coloque o ponteiro do mouse sobre o primeiro carácter da região.</span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.

+Pressione (e mantenha pressionado) o botão esquerdo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3.

+Mova o ponteiro para o último carácter da região (todos os caracteres

+selecionados são destacados).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4.

+Solte o botão esquerdo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Cole (entrada) o conteúdo atual do buffer

+de corte clicando o botão do meio do mouse de três botões, ou clicando com o

+botão direito do mouse de dois botões.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>· Aparece para o buffer de corte usando o

+botão direito do mouse de três botões.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.4 Alerta de

+Tunes</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>BRLTTY alerta para a ocorrência de

+eventos significativos, tocando melodias curtas predefinidas. Este recurso pode

+ser ativado e desativado com um comando TUNES ou o Alert Tunes. As músicas são

+tocadas através do apito interno, por padrão, mas outras alternativas podem ser

+selecionadas com a preferência de Tune Device.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cada evento significativo está associado,

+do maior para a menor prioridade, com um ou mais dos seguintes procedimentos:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>a tune</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se uma música tem sido

+associado com o evento, se o Alert Tunes (veja também o comando TUNES) está

+ativo e se o dispositivo de ajuste for selecionado (veja a preferência de

+Dispositivos Tune) pode ser aberto, então a música é tocada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>a dot pattern</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se um padrão de pontos tem

+sido associado com o evento, e se a preferência do Alert Dots está ativo, então

+o padrão de pontos é exibido rapidamente em cada célula braille. Alguns

+displays braille não respondem com rapidez suficiente para que este mecanismo

+funcione de forma eficaz.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>a message</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se a mensagem tem sido

+associada com o evento, e se a preferência Alert Messages estiver ativa, ela é

+exibido por alguns segundos (veja a opção -m de linha de comando).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Estes eventos incluem:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando o driver do display Braille começa ou pára.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando um comando demorado concluí.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando um comando não pode ser executado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando uma marca é definida.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando o início ou o fim do bloco de corte é definida.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando um recurso é ativado ou desativado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando o controle de cursor é ligado ou desligado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando a imagem da tela é congelada ou descongelada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>·

+Quando a janela braille envolvida ou até o início da próxima <span

+style='mso-spacerun:yes'>   </span>linha ou até o fim da linha anterior.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando as linhas idênticas são ignorados.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando uma solicitação não pode ser executada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Quando o cursor começa o roteamento, termina, ou falha.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.5 Configurações

+de Preferências</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando BRLTTY é iniciado, ele carrega um

+arquivo que contém as definições de preferências. O arquivo não precisa existir,

+ele é criado pela primeira vez, as configurações são salvas com o comando

+PREFSAVE. As configurações mais recentemente salvas podem ser restauradas a

+qualquer momento com o comando PREFLOAD.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O nome para este arquivo é

+/etc/brltty-driver.prefs. Onde driver tem duas letras do código de

+identificação de driver.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>5.5.1 Menu de Preferências</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>As configurações de preferências são

+salvos como dados binários que, portanto, não pode ser editado manualmente.

+BRLTTY, no entanto, tem um menu simples do que você pode facilmente mudá-los.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O menu é ativado pelo comando PREFMENU. O

+display braille (ver -m) mostra o título do menu, e em seguida, apresenta o

+item atual e sua configuração atual.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span>5.5.1.1 Menu de <span style='font-size:11.0pt'>Navegação</span><span

+style='font-size:11.0pt;font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Consulte o Comandos do Menu de Navegação

+para a lista completa dos comandos que permitem que você selecione os itens e

+altere as configurações dentro do menu. Para compatibilidade com drivers

+antigos, os comandos de janela do movimento, que alteraram os significados,

+neste contexto, também pode ser usado.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'>               </span></span><span

+style='font-size:11.0pt'>TOP/ BOT, TOP_LEFT/BOT_LEFT, PAGE_UP/PAGE_DOWN <o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Ir

+para o primeiro item / último no menu (o mesmo como<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>MENU_FIRST_ITEM/MENU_LAST_ITEM).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>LNUP/LNDN,

+PRDIFLN/NXDIFLN, CURSOR_UP/CURSOR_DOWN<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Ir

+para o item anterior / seguinte no menu (o mesmo que MENU_PREV_ITEM/MENU_NEXT_ITEM).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>WINUP/WINDN, CHRLT/CHRRT,

+CURSOR_LEFT/CURSOR_RIGHT, BACK/HOME Diminuir aumentar / definir o item de menu

+atual (o mesmo que MENU_PREV_SETTING/MENU_NEXT_SETTING).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Notas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· As chaves de roteamento

+também pode ser usado para selecionar uma opção para o item atual. Se o item

+tem ajustes numéricos, em seguida, toda a linha de chaves de roteamento atua

+como uma barra de rolagem, que abrange toda a gama de valores válidos. Se o

+item tem chamado configurações, então as chaves de roteamento correspondem com

+as configurações.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· Use o comando PREFLOAD

+desfazer todas as alterações que foram feitas desde a sua entrada no menu.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>· Use o comando PREFMENU

+(novamente) para deixar as novas configurações de fato, sair do menu, e retomar

+a operação normal. Se a opção &quot;Save Settings on Exit&quot; é definido,

+então, além disso, as novas configurações são gravadas no arquivo de

+configurações de preferências. Qualquer comando não é reconhecido pelo sistema

+de menu também faz essas coisas mesmo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>5.5.1.2 Itens de Menu</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Salvar ao sair</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Ao sair do menu de

+preferências:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não salvar automaticamente as configurações

+de preferências.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Salvar automaticamente as configurações de

+preferências.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;A configuração inicial

+é No.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Text Style<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Ao exibir

+o conteúdo da tela (veja o comando DISPMD), os caracteres mostram:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;8-dot<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com os oito pontos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;6-dot<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com apenas pontos de <st1:metricconverter

+ProductID="1 a" w:st="on">1 a</st1:metricconverter> 6. Se uma tabela de

+contração foi selecionada(veja a opção -c da linha de comando e da tabela de

+contração do arquivo de</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'> </span><span style='font-size:11.0pt'>configuração), então ele

+é usado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta configuração também

+pode ser alterado com o comando SIXDOTS.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Skip Identical Lines<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Quando se deslocam para

+cima ou para baixo exatamente uma linha com os comandos LNUP/LNDN, bem como o

+recurso de FWINRT/FWINLT e comandos FWINLTSKIP/FWINRTSKIP:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não pule linhas passadas, que têm o mesmo

+conteúdo da linha atual.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Pula linhas passadas, que têm o mesmo

+conteúdo da linha atual.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Esta configuração

+também pode ser alterado com o comando SKPIDLNS.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Skip Blank Windows</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando se deslocam para a esquerda ou

+direita, com os comandos FWINLT/FWINRT:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não pular janelas em branco.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Pular janelas em branco.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando SKPBLNKWINS.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Which Blank Windows</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se as janelas em branco devem ser

+ignorados:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>All<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>passar todos eles.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Fim da Linha<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>apenas ignorar aqueles que estão no final

+(do lado direito) de uma linha.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Resto da Linha<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>apenas ignorar aqueles que estão no final

+(do lado direito) de uma linha durante a leitura para a frente, e no início (do

+lado esquerdo) de uma linha durante a leitura para trás.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Sliding Window</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se o cursor está a ser controlado (veja o

+comando CSRTRK), e o cursor move-se demasiado perto (ou mais) uma das janela do

+Braille:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Reposicionar horizontalmente a janela de

+modo que sua extremidade esquerda é um múltiplo de sua largura da borda

+esquerda da tela.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Horizontalmente reposicionar a janela de tal

+forma que o cursor, mantendo-se nesse lado da janela, está mais próximo do

+centro.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando SLIDEWIN.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Eager Sliding Window</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se a janela de braille é slide:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Reposicioná-lo sempre que o cursor se move

+para além de uma ou outra extremidade.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Reposicioná-lo sempre que o cursor move-se

+muito perto de uma das extremidades.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A configuração inicial é No.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Window Overlap</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando se deslocam para a esquerda ou

+direita com os comandos FWINLT/FWINRT, esta configuração especifica quantos

+caracteres horizontalmente adjacente janelas braille devem sobrepor-se uns aos

+outros por. O ajuste inicial é 0.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>AutoRepeat</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Enquanto a chave (combinação) para um

+comando permanece pressionado:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não repita automaticamente o comando.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Automaticamente repetir o comando em um

+intervalo regular, após um atraso inicial.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os seguintes comandos são

+elegíveis para auto reply:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+O comando LNUP/LNDN.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+O comando PRDIFLN/NXDIFLN.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+O comando CHRLT/CHRRT.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Operações panorâmica da janela Braille (ver AutoRepeat Panning).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Operações de Page-Up e Page-Down.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Operações de cursor-up e cursor-down.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Operações de cursor esquerda e cursor direita.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Operações de Backspace e Delete.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·&nbsp;Carácter

+de entrada.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Apenas alguns drivers suportam esta

+funcionalidade, a principal limitação é que muitos display braille não

+sinalizam tanto teclas pressionadas e chaves &nbsp;como eventos separados

+distintamente. Esta configuração também pode ser alterado com o comando

+AutoRepeat. A configuração inicial é YES.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>AutoRepeat Panning</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando a preferência Autorepeat está

+habilitada:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não há operações de autorepeat da janela

+panorâmica em braille.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>       </span></span><span style='font-size:11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Operações de AutoRepeat para janela

+panorâmica em braille.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esta preferência atinge os comandos

+FWINLT/FWINRT. A configuração inicial é No.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>AutoRepeat Delay</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando um carácter é a auto repetitivo,

+esta configuração especifica a quantidade de tempo (veja Configurações de Tempo),

+que deve passar antes auto repetição começa. A configuração inicial é 50.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>AutoRepeat Interval<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando um carácter está sendo auto

+repetitivo, esta configuração especifica a quantidade de tempo (veja Configurações

+de Tempo) entre cada reexecução. A configuração inicial é 10.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Show Cursor<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ao exibir o conteúdo da tela (veja o

+comando DISPMD):</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não mostrar o cursor.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Mostrar o cursor.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando CSRVIS. A configuração inicial é Yes.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Style</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao mostrar o cursor, representá-lo:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Underline<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com pontos 7 e 8.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Block<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com os oito pontos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando CSRSIZE.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Blinking Cursor</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser mostrado:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Deixar-se visível o tempo todo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Torná-lo alternadamente, visíveis e

+invisíveis de acordo com um &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;intervalo

+predefinido.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta configuração também

+pode ser alterado com o comando CSRBLINK.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Visible Time</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser piscou, esta

+configuração especifica o período de tempo (veja time settings) durante cada

+ciclo que está a ser visível. A configuração inicial é de 40.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Invisible Time<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser piscou, esta

+configuração especifica o período de tempo (veja time settings) durante cada

+ciclo que é ser invisível. A configuração inicial é de 40.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Show Cursor<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ao exibir o conteúdo da tela (veja o

+comando DISPMD):</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não mostrar o cursor.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-spacerun:yes'>       </span></span><span style='font-size:11.0pt'>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Mostrar o cursor.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando CSRVIS. A configuração inicial é Yes.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Style</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao mostrar o cursor, representá-lo:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Underline<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com pontos 7 e 8.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Block<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Com os oito pontos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando CSRSIZE.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Blinking Cursor</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser mostrado:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Deixar-se visível o tempo todo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Torná-lo alternadamente, visíveis e

+invisíveis de acordo com um &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;intervalo

+predefinido.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esta configuração também

+pode ser alterado com o comando CSRBLINK.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Visible Time</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser piscou, esta

+configuração especifica o período de tempo (veja time settings) durante cada

+ciclo que está a ser visível. A configuração inicial é de 40.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor Invisible Time</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando o cursor está a ser piscou, esta

+configuração especifica o período de tempo (veja time settings) durante cada

+ciclo que é ser invisível. A configuração inicial é de 40.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Show Attributes</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao exibir o conteúdo da tela (veja o

+comando DISPMD):</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não sublinhar os caracteres em destaque.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Sublinhar caracteres em destaque.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando ATTRVIS.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Blinking Attributes</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando os caracteres em destaque são

+sublinhados:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Deixe o indicador visível o tempo todo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Faça o indicador alternadamente visíveis e

+invisíveis de acordo com um intervalo predefinido.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando ATTRBLINK.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Attributes Visible Time<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando o carácter sublinhado em destaque

+piscar, esta configuração especifica o período de tempo (veja time settings)

+durante cada ciclo que está a ser visível. A configuração inicial é de 20.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Attributes Invisible Time<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando o carácter sublinhado em destaque

+piscar, esta configuração especifica o período de tempo (veja time settings)

+durante cada ciclo que é ser invisível. A configuração inicial é de 60.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Blinking Capitals</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao exibir o conteúdo da tela (veja o

+comando DISPMD):<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Deixar maiúsculas visível o tempo todo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Faça letras maiúsculas alternadamente

+visíveis e invisíveis de acordo com um intervalo predefinido.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando CAPBLINK.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Capitals Visible Time</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando as letras maiúsculas são

+destacadas, esta configuração especifica o período de tempo (veja time

+settings) durante cada ciclo que estão a ser visíveis. A configuração inicial é

+de 60.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Capitals Invisible Time </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Quando as letras maiúsculas são

+destacadas, esta configuração especifica o período de tempo (veja time

+settings) durante cada ciclo que estão a ser invisível. A configuração inicial

+é de 20.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Braille Firmness</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ajuste a consistência (ou rigidez) dos

+pontos braille. Pode ser definido como:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Máximo</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Alta</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Média</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Baixo</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Mínimo<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Essa preferência está

+disponível somente se um driver que suporta ele está sendo usado. A

+configuração inicial é média.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Window Follows Pointer</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ajuste a sensibilidade dos pontos braille

+para tocar. Pode ser definido como:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Máximo</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Alta</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Média</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Baixo</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Mínimo<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Essa preferência está

+disponível somente se um driver que suporta ele está sendo usado. A

+configuração inicial é média.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Window Follows Pointer</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao mover o dispositivo de cursor (mouse):<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não arrastar a janela braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Arraste a janela braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta preferência é apenas apresentada se

+a opção de compilação --enable-gpm foi especificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Highlight Window</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao mover a janela do Braille:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não dar destaque a área da tela nova.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Destaque da área nova tela.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Este recurso permite que um observador

+com visão para ver onde está a janela braille e, portanto, saber o que o

+usuário está lendo <st1:PersonName ProductID="em braille. Qualquer" w:st="on">em

+ braille. Qualquer</st1:PersonName> movimento da janela em braille (manual,

+controle do cursor, etc), exceto quando se move em resposta ao ponteiro (mouse)

+movimento (veja Windows Follows Pointer), faz com que a área da tela

+correspondente ao novo local da janela Braille ser destacada. Se a preferência

+Show Attributes está habilitada, somente o caractere correspondente ao canto

+superior esquerdo da janela do braille é destacado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Alert Tunes</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Sempre que um evento significativo, com

+uma melodia associada ocorre (ver Alerta de Tunes):<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Não tocar a música.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>  </span>Tocar a música.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando TUNES. A configuração inicial é Yes.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Tune Device</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Tocar músicas de alerta através de:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Beeper</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O apito interno (console

+gerador de tom). Esta configuração é suportada em Linux, no OpenBSD, no FreeBSD

+e no NetBSD. É sempre seguro para usar, embora possa ser um pouco fraco. Este

+dispositivo não está disponível se a opção de compilação --disable-bipper-suport

+foi especificada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>PCM</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A interface digital de

+áudio na placa de som. Esta configuração é suportada no Linux (via /dev/dsp),

+no Solaris (via /dev/audio), no OpenBSD (via /dev/audio0), sobre FreeBSD (via

+/dev/dsp), e no NetBSD (via /dev/audio0). Ele não funciona quando este

+dispositivo já está sendo usado por outro aplicativo. Este dispositivo não está

+disponível se a opção de compilação --disable-pcm-support foi especificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>MIDI</span><span

+lang=EN-US style='mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O Musical Instrument

+Digital Interface na placa de som que está definida é compatível com Linux

+(através do /dev/sequencer). Ele não funciona quando este dispositivo já está

+sendo usado por outro aplicativo. Este dispositivo não está disponível se a

+opção de compilação --disable-midi-support foi especificada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>FM<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O sintetizador de FM em um

+cartão de AdLib, OPL3, Sound Blaster, ou o equivalente de som. Esta

+configuração é suportada <st1:PersonName ProductID="em Linux. Os" w:st="on">em

+ Linux. Os</st1:PersonName> resultados são imprevisíveis e potencialmente não

+muito bons, se for usado com uma placa de som que não suporta esse recurso.

+Este dispositivo não está disponível se a opção de compilação

+--disable-fm-support foi especificada.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A configuração inicial é

+Beeper sobre as plataformas que suportam, e PCM sobre as plataformas que não o

+fazem.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>PCM Volume<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se a interface de áudio digital da placa

+de som está sendo usado para tocar as músicas alerta, esta configuração

+especifica o volume (como uma porcentagem do máximo) em que estão a ser tocado.

+A configuração inicial é de 70.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>MIDI Volume<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se o Musical Instrument Digital Interface

+(MIDI) da placa de som está sendo usado para tocar as músicas alerta, esta

+configuração especifica o volume (como uma porcentagem do máximo) em que estão

+a ser tocado. A configuração inicial é de 70.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>MIDI Instrument<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se o Musical Instrument Digital Interface

+(MIDI) da placa de som está sendo usada para tocar as músicas alerta, esta

+configuração especifica qual o instrumento a ser utilizado (ver MIDI Table

+Instrument). A configuração inicial é Acoustic Grand Piano.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>FM Volume<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se o sintetizador FM da placa de som está

+sendo usado para tocar as músicas alerta, esta configuração especifica o volume

+(como uma porcentagem do máximo) em que estão a ser tocado. A configuração

+inicial é de 70.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alert Dots<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Sempre que um evento significativo

+associado com um padrão de pontos ocorre (ver Alerta de Tunes):<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não exibir o padrão de pontos.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Resumidamente exibir o padrão de pontos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se melodias de alerta são para ser jogado

+(veja o comando TUNES e o Alerta de Tunes), se uma música tem sido associado

+com o evento, e se o dispositivo da canção selecionado pode ser aberto, então,

+independentemente da configuração dessa preferência, o padrão de pontos não

+será exibido.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Alert Messages</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Sempre que um evento significativo, com

+uma mensagem associada ocorre (ver Alerta de Tunes):<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não mostrar a mensagem.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Mostrar a mensagem.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se as mensagens de alerta são tocadas

+(veja o comando TUNES e a preferência Alerta de Tunes), se uma música tem sido

+associado com o evento, e se o dispositivo de ajuste selecionado pode ser

+aberto, ou se o alerta padrões de pontos que devem ser exibidas (consulte Alert

+Dots) e se um padrão de pontos tem sido associado com o evento, então,

+independentemente da configuração dessa preferência, a mensagem não é exibida.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Say-Line Mode</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ao usar o comando SAY_LINE:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Immediate<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Descartar discurso pendentes.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Enqueue<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Não descartar pendentes discurso.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A configuração inicial é immediate.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Autospeak</span><span

+lang=EN-US style='mso-ansi-language:EN-US'> <o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;No<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Só falar quando for solicitado a fazê-lo.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;Yes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><span

+style='mso-spacerun:yes'>   </span>Automaticamente falar:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+A nova linha quando a janela é movida verticalmente braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Caracteres que são inseridos ou excluídos.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+O carácter ao qual o cursor é movido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração também pode ser

+alterado com o comando AUTOSPEAK. A configuração inicial é No.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Speech Rate<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ajustar a velocidade de fala (0 é o mais

+lento, 20 é o mais rápido). Essa preferência está disponível somente se um

+driver que suporta ele está sendo usado. Esta configuração também pode ser

+alterado pelos comandos SAY_SLOWER/SAY_FASTER. A configuração inicial é 10.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Speech Pitch<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ajuste o volume da voz (0 é o mais baixo,

+20 é o mais alto). Essa preferência está disponível somente se um driver que

+suporta ele está sendo usado. Esta configuração também pode ser alterado com os

+comandos SAY_SOFTER/SAY_LOUDER. A configuração inicial é 10.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Speech Pitch<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ajustar o tom de voz (0 é o menor e o 20

+é o mais alto). Essa preferência está disponível somente se um driver que

+suporta ele está sendo usado. A configuração inicial é 10.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Speech Punctuation</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Ajuste a quantidade de pontuação que é

+falado. Pode ser definido como:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· All</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· Some</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· All</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Essa preferência está

+disponível somente se um driver que suporta ele está sendo usado. A

+configuração inicial é some.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Status Style<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta configuração especifica o caminho

+que as células de estado estão a ser utilizados. Você não deveria precisam de

+tocar com nelas. Isso permite que os desenvolvedores BRLTTY para testar as

+configurações de status da pilha para display braille que eles realmente não têm.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>None<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Não use as células de status. Essa

+configuração é sempre seguro, mas também é completamente inútil.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Alva<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As células status conter:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>1

+- A localização do cursor (veja abaixo).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>2

+- A localização do canto superior esquerdo da janela do braille (ver abaixo).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>3

+- Uma carta indicando o estado de BRLTTY. </span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Na ordem de precedência:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>a<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Tela

+atributos estão a ser mostrados (veja o comando DISPMD).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>f<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+imagem da tela é congelada (veja o comando FREEZE).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>f<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor está sendo controlado (veja o comando CSRTRK).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Nada

+de especial.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A localização do cursor e a janela

+braille são apresentados de uma maneira interessante. Os pontos <st1:metricconverter

+ProductID="1 a" w:st="on">1 a</st1:metricconverter> 6 representam o número da

+linha com uma carta de a (para 1) por y (para 25). Os pontos 7 e 8 (os dois

+extra na parte inferior) representam o número janela horizontal braille como

+segue:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>No

+Dots<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>A

+primeira janela (à esquerda).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+7<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+segunda janela.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+8<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+terceira janela.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dots

+7 e 8<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+quarta janela.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Em ambos os casos, os

+indicadores guardados: a linha 26 é representado pela letra A, e a quinta

+janela horizontal braille é representado por nenhum ponto, na parte inferior. </span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Tieman</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>As células de status devem conter:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>1-2:

+As colunas (contando a partir 1) do cursor (como mostrado na metade superior

+das células) e no canto superior esquerdo da janela do braille (mostrado na

+metade inferior das células).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>3-4:

+As linhas (contando a partir 1) do cursor (como mostrado na metade superior das

+células) e no canto superior esquerdo da janela do braille (mostrado na metade

+inferior das células).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>5:

+Cada ponto indica se um recurso é ativado como se segue:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+1: A imagem da tela é congelada (veja o comando FREEZE).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+2: atributos da tela estão sendo exibidos (veja o comando DISPMD).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+3: Alerta músicas estão sendo tocadas (veja o comando TUNES).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+4: O cursor está sendo mostrado (veja o comando CSRVIS).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+5: O cursor é um bloco sólido (veja o comando CSRSIZE).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+6: O cursor está piscando (veja o comando CSRBLINK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+7: O cursor está a ser controlado (veja o comando CSRTRK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+8: A janela vai deslizar braille (veja o comando SLIDEWIN).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>PowerBraille

+80</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><br>

+<br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>As

+células status conter:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>1:

+A linha (contando a partir de 1) correspondente à parte superior da janela <st1:PersonName

+ProductID="em braille. Os" w:st="on">em braille. Os</st1:PersonName> dígitos

+das dezenas é mostrado na metade superior da pilha, e os dígitos das unidades é

+apresentada na metade inferior da célula.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Generic<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Esta definição passa um monte de

+informações para o driver braille, e o driver decide forma de apresentá-lo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>MDV</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>As células de status devem conter:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>1-2:

+A localização do canto superior esquerdo da janela do braille.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+linha (contando a partir de 1) é mostrado na metade superior das<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>células,

+e na coluna (contando a partir de 1) é mostrado na metade<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>inferior

+das células.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Voyager<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>As células de status devem conter:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>1:

+A linha (contando a partir de 1) correspondente à parte superior da janela

+braille (ver below).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>2:

+A linha (contando a partir de 1) onde o cursor está (veja below).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>3:

+Se a tela está congelada (veja o comando FREEZE), depois a letra F. Se não for,

+então a coluna (contando a partir de 1) onde está o cursor (veja below).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os números de linhas e

+colunas são mostrados os números de dois dígitos em uma única célula. Os

+dígitos das dezenas são mostrados na metade superior da pilha, e os dígitos das

+unidades são apresentadas na metade inferior da célula.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A configuração inicial do

+driver de display Braille é dependente.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Text Table<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Selecione a tabela de texto. Ver seção de

+Tabelas de Texto para mais detalhes. Veja a opção -t para a configuração inicial.

+Esta preferência não é salvo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Attributes Table<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Selecione a tabela de atributos. Ver

+Tabela de Atributos. Veja a opção de linha de comando -t para a configuração

+inicial. Esta preferência não é salvo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Contraction Table<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Selecione a tabela de contração. Ver

+Tabela de Contração. Consulte a linha de comando -c para a configuração

+inicial. Esta preferência não é salvo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Key Table</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Selecione a tabela chave. Ver Tabelas de

+Teclas. Consulte a linha de comando -k para a configuração inicial. Esta

+preferência não é salvo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Notas:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Todos os parâmetros de tempo em centésimos de segundo. Eles são<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>múltiplos

+do intervalo da janela de atualização braille (veja a<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>linha

+de comando -u) dentro da faixa de <st1:metricconverter ProductID="1 a" w:st="on">1

+ a</st1:metricconverter> 100.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.6 Status do

+Display</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O status do display é um resumo do estado

+atual BRLTTY, que se encaixa totalmente dentro da janela do braille. Alguns

+displays braille têm um conjunto de células de status que são usados para

+exibir permanentemente algumas dessas informações, bem como (consulte a

+documentação para o driver do display). Os dados apresentados por este display

+não são estáticos, e podem ser alteradas a qualquer momento, em resposta a tela

+de atualizações e/ou comandos BRLTTY.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Use o comando INFO para alternar para a

+exibição de status, e usá-lo novamente para retornar para a tela. O layout das

+informações contidas está dependente do tamanho da janela do braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>5.6.1 Display com 21 células ou mais</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Pneumáticos curta têm sido utilizados,

+apesar de serem um tanto enigmática, a fim de mostrar o layout da coluna

+precisa.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>wx:wy cx:cy vt tcmfdu</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><i><span style='font-size:

+11.0pt'>wx:wy</span></i><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A coluna e linha (contando a partir de 1)

+na tela correspondente ao canto superior esquerdo da janela do braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>cx:cy</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>A coluna e linha (contando a partir de 1)

+sobre a tela correspondente à posição do cursor.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>vt</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O número (contando a partir 1) do

+terminal virtual atual.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>t</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado do recurso de controle de cursor

+(veja o comando CSRTRK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><i><span style='font-size:

+11.0pt'>blank</span></i><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>controle

+de cursor está off.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>controle

+de cursor está on.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>c</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado dos recursos visibilidade do

+cursor (ver o CSRVIS e CSRBLINK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor não estiver visível, e não piscar quando se faz visível.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>b</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor não é visível, e começa a piscar quando se faz visível.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>v</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor é visível, e não está piscando.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>B</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor é visível e está piscando.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>m</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O modo de exibição atual (veja o comando

+DISPMD).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Tela

+de conteúdo (texto) está sendo exibido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>a</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Tela

+de realce (atributos) está sendo exibido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>f</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado do recurso de tela congelada

+(veja o comando FREEZE).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+tela não está congelada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><i><span style='font-size:

+11.0pt'>d</span></i><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O número de pontos braille sendo usado

+para exibir cada caracter (veja o comando SIXDOTS).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>8</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Todos

+os oito pontos estão sendo usados.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>6</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Somente

+pontos <st1:metricconverter ProductID="1 a" w:st="on">1 a</st1:metricconverter>

+6 estão sendo usados.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>u</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado da maiúscula (letra maiúscula)

+apresentam características (veja o comando CAPBLINK).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Letras

+maiúsculas não piscar.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>B</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Piscar

+letras maiúsculas.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>5.6.2 <span style='font-size:11.0pt'>Display com 20

+células ou menos</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Pneumático curto têm sido utilizados,

+apesar de serem um tanto enigmática, a fim de mostrar o layout da coluna

+precisa.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>xxyys

+vt tcmfdu</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>xx</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>As colunas (contando a partir de 1) sobre

+a tela correspondente à posição do cursor (como mostrado na metade superior das

+células) e para o canto superior esquerdo da janela do braille (mostrado na

+metade inferior das células).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>yy</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>As linhas (contando a partir de 1) sobre

+a tela correspondente à posição do cursor (como mostrado na metade superior das

+células) e para o canto superior esquerdo da janela do braille (mostrado na

+metade inferior das células).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>s</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>As definições de algumas das

+características do BRLTTY. Um recurso é ativado se o ponto correspondente é

+gerado.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+1: Congela a imagem da tela (veja o comando FREEZE).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+2: Mostrar atributos (veja o comando DISPMD).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+3: Tunes de alerta (veja o comando TUNES).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+4: Cursor visível (veja o comando CSRVIS).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+5: Bloco de cursor (veja o comando CSRSIZE).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+6: Cursor piscando (veja o comando CSRBLINK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+7: Cursor de monitoramento (veja o comando CSRTRK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+8: Janela deslizante (veja o comando SLIDEWIN).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>vt</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O número (contando a partir 1) do

+terminal virtual atual.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><i><span style='font-size:11.0pt'>t</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado do recurso de controle de cursor

+(veja o comando CSRTRK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Controle

+de cursor está off.</span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Controle

+cursor está on.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>c</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+estado dos recursos visibilidade do cursor (ver o CSRVIS e CSRBLINK).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor não estiver visível, e não piscar quando se faz visível.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>b</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor não é visível, e começa a piscar quando se faz visível.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>v</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor é visível, e não está piscando.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>B</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+cursor é visível e está piscando.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>m</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O modo de exibição atual (veja o comando

+DISPMD).</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Tela

+de conteúdo (texto) está sendo exibido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>a</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Tela

+de realce (atributos) está sendo exibido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>f</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado do recurso de tela congelada

+(veja o comando FREEZE).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+tela não está congelada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>f</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+tela está congelada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>d</span></i><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O número de pontos braille sendo usado

+para exibir cada caracter (veja o comando SIXDOTS).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>8</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Todos

+os oito pontos estão sendo usados.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>6</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Somente

+pontos <st1:metricconverter ProductID="1 a" w:st="on">1 a</st1:metricconverter>

+6 estão sendo usados.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>u</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>O estado da maiúscula (letra maiúscula)

+apresentam características (veja o comando CAPBLINK).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>blank</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Letras

+maiúsculas não piscar.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>B</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Piscar

+letras maiúsculas.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>5.7 Comando de Modo

+de Aprendizagem</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Comando modo de aprendizagem é uma forma

+interativa de aprender o que as teclas no visor do Braille faz. Pode ser

+acessado tanto pelo comando LEARN ou através do utilitário brltest. Esse

+recurso não estará disponível se a opção de compilação --disable-learn-mode foi

+especificado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando este modo está inscrito, a

+mensagem command learn mode é escrita no display braille. Então, cada tecla (ou

+combinação de teclas) sobre a tela é pressionada, uma mensagem curta que

+descreva a sua função BRLTTY está escrito. Este modo sai imediatamente se a

+tecla (ou combinação de teclas) para o comando LEARN é pressionado. Ele sai

+automaticamente, e a mensagem done é escrita, se dez segundos transcorrer sem

+qualquer tecla no display sendo pressionado. Observe que alguns displays não

+têm sinal ao display e/ou alguns drivers não sinalizam BRLTTY até que todas as

+teclas são liberadas.</span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Se uma mensagem for maior que o display

+Braille, então é exibida <st1:PersonName ProductID="em segmentos. O" w:st="on">em

+ segmentos. O</st1:PersonName> comprimento de cada segmento, mas o último é um

+a menos do que a largura do display, com o caracter mais à direita no visor a

+ser definida como um sinal de menos. Cada segmento se mantenha no visor seja

+por alguns segundos (veja a opção-M) ou até que qualquer tecla na tela é

+pressionada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>6 – Tabelas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>6.1 - Tabelas de Texto</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os arquivos com nomes no

+formato *. ttb são tabelas de texto, e com nomes no formato *.tti são textos

+subtabelados. Eles são usados pelo BRLTTY para traduzir os caracteres na tela

+em suas respectivas representações de 8 pontos no computador braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY é inicialmente

+configurado para usar a tabela de texto North American Braille Computer

+Code(NABCC). Além desse padrão, as alternativas a seguir são fornecidas:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Auto<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>auto-seleção

+da localidade-base<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ar</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Árabe

+(genérico)<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>as</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Assamês<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>awa</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>       </span></span><span style='font-size:11.0pt'>Awadhi<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>bg</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Búlgaro</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>bh</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Bihari</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>bn</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Bengali</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>bo</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Tibetano</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>bra</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>       </span></span><span style='font-size:11.0pt'>Braj

+(?Braj Bhasha )</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>brf</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>       </span></span><span style='font-size:11.0pt'>para

+visualização de arquivos. brf dentro de um editor ou pager</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>cs</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Checo<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>cy</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Galês</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>da</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Dinamarquês</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>de</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Alemão</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>dra</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>       </span></span><span style='font-size:11.0pt'>Dravidianas</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>el</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Grego</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>em</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Inglês</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>en_CA</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'> </span></span><span style='font-size:11.0pt'>Inglês

+(Canadá)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>en_UK</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'> </span></span><span style='font-size:11.0pt'>Inglês

+(Reino Unido)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>en_US</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'> </span></span><span style='font-size:11.0pt'>Inglês

+(Estados Unidos)</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>en-nabcc</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><span style='mso-tab-count:1'>       </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Inglês (North

+American Braille Computer Code)</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>eo</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Esperanto</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>es</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Espanhol</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>et</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Estoniano</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fi</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Finlandes</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Francês</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr_CA</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'> </span></span><span style='font-size:11.0pt'>Francês

+(Canadá)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr_FR</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'> </span></span><span style='font-size:11.0pt'>Francês

+(França)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr_2007</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Francês

+(unificado)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr-cbifs</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>       </span></span><span style='font-size:11.0pt'>Francês

+(Code Braille Informatique Français Standard)</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ga</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Irlandês</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>gd</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:1'>          </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Gaélico</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>gon</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:1'>       </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Gondi</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>gu</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:1'>          </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Guzerate</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>he</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:1'>          </span></span><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>Hebraico</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>hi</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Hindi</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>hr</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Croata</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>hu<span

+style='mso-tab-count:1'>   </span>Húngaro</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>hy<span

+style='mso-tab-count:1'>   </span>Armênio</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>is</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Islandes</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>it</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Italiano</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kha<span

+style='mso-tab-count:1'>  </span>Khasi</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kn<span

+style='mso-tab-count:1'>   </span>Canará</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kok<span

+style='mso-tab-count:1'>  </span>Concanis</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kru<span

+style='mso-tab-count:1'>  </span>Kurukh</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>lt<span

+style='mso-tab-count:1'>   </span>Lituana</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>lv<span

+style='mso-tab-count:1'>   </span>Letã</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mg<span

+style='mso-tab-count:1'>   </span>Malgaxe</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mi<span

+style='mso-tab-count:1'>   </span>Maori</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ml<span

+style='mso-tab-count:1'>   </span>Malaiala</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mni<span

+style='mso-tab-count:1'>  </span>Manipuri</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mr<span

+style='mso-tab-count:1'>   </span>Marati</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mt<span

+style='mso-tab-count:1'>   </span>Maltês</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mun<span

+style='mso-tab-count:1'>  </span>Mundari</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mwr<span

+style='mso-tab-count:1'>  </span>Marwari</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ne<span

+style='mso-tab-count:1'>   </span>Nepali</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>new<span

+style='mso-tab-count:1'>  </span>Newari</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>nl<span

+style='mso-tab-count:1'>   </span>Holandês</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>nl_BE<span

+style='mso-tab-count:1'> </span>Holandês (Bélgica)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>nl_NL<span

+style='mso-tab-count:1'> </span>Holandês (Países Baixos)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>no<span

+style='mso-tab-count:1'>   </span>Norueguês</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>no-generic<span

+style='mso-tab-count:1'> </span>Norueguês (com suporte para outros idiomas)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>no-oub<span

+style='mso-tab-count:1'>    </span>Norueguês (Utvalg Offentlig para

+Blindeskrift)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>nwc<span

+style='mso-tab-count:1'>  </span>Antigo Newari</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>or<span

+style='mso-tab-count:1'>   </span>Oriá</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>pa<span

+style='mso-tab-count:1'>   </span>Panjabi</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>pi<span

+style='mso-tab-count:1'>   </span>Páli</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>pl<span

+style='mso-tab-count:1'>   </span>Polonês</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>pt<span

+style='mso-tab-count:1'>   </span>Português</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ro<span

+style='mso-tab-count:1'>   </span>Romeno</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ru<span

+style='mso-tab-count:1'>   </span>Russo</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sa<span

+style='mso-tab-count:1'>   </span>Sânscrito</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sat<span

+style='mso-tab-count:1'>  </span>Santali</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sd<span

+style='mso-tab-count:1'>   </span>Sindi</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sk<span

+style='mso-tab-count:1'>   </span>Eslovaco</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sv<span

+style='mso-tab-count:1'>   </span>Sueco</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sw<span

+style='mso-tab-count:1'>   </span>Suaíli</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ta<span

+style='mso-tab-count:1'>   </span>Tâmil</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>te<span

+style='mso-tab-count:1'>   </span>Telugu</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>tr<span

+style='mso-tab-count:1'>   </span>Turco</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>vi<span

+style='mso-tab-count:1'>   </span>Vietnamita</span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja a opção de linha de

+comando -t, a configuração text-table do arquivo de directiva, e a opção de

+construção --with-text-table para obter detalhes sobre como usar uma tabela de

+texto alternativo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.1.1<span style='mso-tab-count:1'> </span>Tabela

+de Formato de Texto<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A tabela de texto consiste

+em uma seqüência de directivas, um por linha, que definem como cada character é

+representado <st1:PersonName ProductID="em braille. UTF-8" w:st="on">em

+ braille. UTF-8</st1:PersonName> caracter encoding deve ser usado. O espaço em

+branco (espaços, tabs) no início de uma linha, assim como antes e/ou após

+qualquer operando de qualquer directiva, é ignorado. Linhas que contêm apenas

+espaço em branco são ignoradas. Se o primeiro caráter não-espaço em branco

+antes de uma linha é &quot;#&quot; então essa linha é um comentário e é

+ignorado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.1.2<span style='mso-tab-count:1'> </span>Tabela

+de Diretivas de Texto<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As seguintes diretivas são

+fornecidas:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>char <i>character dots # comentários<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Use a diretiva char para

+especificar como um caracter Unicode será representado em braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>character<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os caracteres Unicode a

+serem definidos. Podem ser:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Qualquer caracter único que não seja uma barra-invertida ou um carácter de

+espaço em branco.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Um caracter especial prefixado de barra-invertida. São eles:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\b</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de retrocesso.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\f</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+formfeed (quebra de página).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\n</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de nova linha.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\o###</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter de três dígitos octal.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\r</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de retorno de transporte.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\s</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de espaço.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de tab horizontal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\u####</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de quatro dígito.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\U########</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de oito dígito.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\v</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de tab horizontal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\x##</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de dois dígitos.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\X##</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>(o

+caso do X e dos dígitos não é significativa).<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>f<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+tela está congelada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\#</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Um

+sinal de número literal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\&lt;name&gt;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+nome Unicode de um carácter(use _ para espaço).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\\</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Uma

+barra-invertida literal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>dots<o:p></o:p></span></i></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A representação braille

+dos caracteres Unicode. É uma seqüência de 1-8 números dos pontos. Se o número

+do ponto de seqüência é colocado entre parênteses, em seguida, os números dos

+pontos podem ser separados um do outro por espaço <st1:PersonName

+ProductID="em branco. Um" w:st="on">em branco. Um</st1:PersonName> número de

+ponto é um dígito dentro do intervalo de 1-8, tal como definido pela Standard

+Braille Dot Numbering Convention. O ponto especial de número 0 não é

+reconhecido quando entre parênteses, e significa que não há pontos, não podendo

+ser usado em conjunto com qualquer outro número de pontos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Exemplos</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char a 1</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char b (12)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char c ( 4 1 )</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char \\ 12567</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char \s 0</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+char \x20 ()</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· char

+\&lt;LATIN_SMALL_LETTER_D&gt; 145</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span style='font-size:11.0pt'>byte <i>byte dots # comment<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Use a diretiva de byte

+para especificar como um carácter no conjunto de caracteres local está sendo

+representado <st1:PersonName ProductID="em braille. Foi" w:st="on">em braille.

+ Foi</st1:PersonName> mantido para compatibilidade com versões anteriores, mas

+não deve ser usado. Carácteres Unicode devem ser definidos (através da diretiva

+char) para que a tabela de texto permanece válida, independentemente do qual

+conjunto de caracteres local usado.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>byte<o:p></o:p></span></i></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O caráter local a ser

+definido. Pode ser especificado da mesma forma como um operador character da

+diretiva char exceto que as formas específicas de Unicode (\u,\U,\&lt;) não

+podem ser utilizados.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>dots<o:p></o:p></span></i></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A representação braille do

+carácter local. Pode ser especificado da mesma forma como operando dots da

+directiva char.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>include <i>file # comment<o:p></o:p></i></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Use a diretiva include

+para incluir o conteúdo de um texto subtabelado. Ele é recursivo, o que

+significa que qualquer texto subtabelado pode-se incluir ainda em outro texto

+subtabela. Cuidados devem ser tomados para garantir que &quot;include

+loop&quot; não seja criado.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><i><span style='font-size:11.0pt'>file<o:p></o:p></span></i></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O arquivo a ser incluído.

+Pode ser um relativo ou um caminho absoluto. Se relativo, ele está ancorada no

+diretório contendo o arquivo incluido.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>6.2 Tabela de

+Atributos</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os arquivos com nomes no

+formato *.atb são tabelas de atributos, e com nomes no formato *.ati são

+atributos subtabelas. Eles são usados quando BRLTTY é mostrado na tela de

+atributos e não o conteúdo da tela (veja o comando DISPMD). Cada um dos oito

+pontos braille representa um dos oito bits de atributo VGA.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os seguintes atributos são

+fornecidas:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>attributes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A coluna da esquerda

+representa as cores de primeiro plano:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+1</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Red</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+2</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Green</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+3</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Blue</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+7<span style='mso-tab-count:2'>     </span>Bright</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A coluna do lado direito

+representa as cores de fundo:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+4<span style='mso-tab-count:2'>     </span>Red</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+5<span style='mso-tab-count:2'>     </span>Green</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+6<span style='mso-tab-count:2'>     </span>Blue</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+8<span style='mso-tab-count:2'>     </span>Blink</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Um ponto é levantado

+quando o bit de atributo correspondente está ligado. Este é o padrão da tabela

+de atributos, pois é a mais intuitiva. Um dos seus problemas, porém, é que é

+difícil discernir a diferença entre normal (branco sobre fundo preto) e inversa

+(preto sobre fundo branco) vídeo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>attrib</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A coluna da esquerda

+representa as cores de primeiro plano:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+1</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Red</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+2<span style='mso-tab-count:2'>     </span>Green</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+3</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Blue</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+7</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Bright</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A coluna do lado direito

+representa as cores de fundo:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+4</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Red</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+5</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Green</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+6</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Blue</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Dot

+8</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:2'>                </span></span><span style='font-size:

+11.0pt'>Blink</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Um bit de fundo

+desencadeará seus pontos correspondentes, enquanto que um bit de primeiro plano

+desencadeará seus correspondentes pontos. Essa lógica intuitiva realmente torna

+mais fácil de ler as combinações de atributos mais comumente usados.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja a opção de linha de

+comando -a, a diretiva de arquivo de configuração attributes-tables, e a opção

+de criação --with-attributes-table para obter detalhes sobre como usar uma tabela

+de atributos alternativos.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.2.1<span style='mso-tab-count:1'> </span>Tabela

+de Formatos dos Atributos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Uma tabela de atributos

+consiste em uma seqüência de diretivas, um por linha, que definem como as

+combinações de atributos VGA estão sendo representados <st1:PersonName

+ProductID="em braile. Caracteres UTF-8" w:st="on">em braile. Caracteres UTF-8</st1:PersonName>

+devem ser utilizados. O espaço em branco (espaços, tabs) no início de uma

+linha, assim como antes e/ou após qualquer operando de qualquer diretiva, é

+ignorado. Linhas que contêm apenas espaço em branco são ignoradas. Se o caráter

+não-espaço em branco antes de uma linha for &quot;#&quot; então essa linha é um

+comentário e é ignorado.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.2.2<span style='mso-tab-count:1'> </span>Tabela

+de Diretórios dos Atributos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As seguintes diretivas são

+fornecidas:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>dot <i>dot state # comment</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Use a diretiva dot para

+especificar o que um determinado ponto representa.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>dot</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O ponto a ser definido. É

+um único dígito no intervalo 1-8, tal como definido pela Standard Braille Dot

+Numbering Convention.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>state</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O que o ponto representa.

+Pode ser:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><i><span style='font-size:

+11.0pt'>=attribute</span></i><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+ponto é levantado se o atributo nome está on.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><i><span style='font-size:

+11.0pt'>~attribute</span></i><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+ponto é levantado se o atributo nome está off.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os nomes dos bits de

+atributo são:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0X01</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>fg-blue</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0X02</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>fg-green</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0x04</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>fg-red</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0x08</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>fg-bright</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0X10</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>bg-blue</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0x20</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>bg-green</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0x40</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>bg-red</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>0x80</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><span style='mso-tab-count:2'>                   </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>blink</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Exemplos:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· dot 1 =fg-red</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>· dot 2 ~bg-blue</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>include<i>

+file # comment</i></span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Use a diretiva include

+para incluir o conteúdo de um atributos subtabela. Ele é recursivo, o que

+significa que os atributos subtabela pode-se incluir ainda em outros atributos

+subtabela. Cuidados devem ser tomados para garantir que &quot;include loop&quot;

+não seja criado.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>file</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O arquivo a ser incluído.

+Pode ser um relativo ou um caminho absoluto. Se relativo, ela está ancorada no

+diretório contendo o arquivo incluindo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>6.3 Tabela de

+Contração</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os arquivos com nomes no

+formato *.ctb são tabelas de contração, e com nomes no formato *.cti são

+subtabelas de contração. Eles são usados pelo BRLTTY para traduzir as

+seqüências de caracteres na tela em suas respectivas representações contração

+em braille.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>BRLTTY apresenta contração

+em braille se:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+A tabela de contração foi selecionada. Consulte a opção de linha de comando -c

+e as diretivas do arquivo de configuração contraction-table para obter mais

+detalhes.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+O recurso 6-dot braille foi ativado. Veja o comando SIXDOTS e o Text Style para

+mais detalhes.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esse recurso não estará

+disponível se a opção de construção --disable-contracted-braille foi

+especificada.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As seguintes tabelas de

+contração são fornecidas:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>af</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Afrikaans

+(contracionado)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>am</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>          </span></span><span style='font-size:11.0pt'>Amharic

+(não-contracionado)</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>de-basis<span

+style='mso-tab-count:3'>             </span>German (não-contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>de-kurzschrift&nbsp;&nbsp;&nbsp;

+<span style='mso-tab-count:1'>   </span>German (contracionado - padrão1998)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>de-vollschrift&nbsp;&nbsp;&nbsp;

+<span style='mso-tab-count:1'>   </span>German (contrações básicas)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>en-ueb-g2<span

+style='mso-tab-count:3'>            </span>Unified English Braille (grau 2)</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>en-us-g2<span

+style='mso-tab-count:3'>             </span>American English (grau 2)</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>es<span

+style='mso-tab-count:4'>                   </span>Spanish (grau 2)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>fr-abrege<span

+style='mso-tab-count:3'>            </span>French (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>fr-integral<span

+style='mso-tab-count:2'>          </span>French (não-contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>ha</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:4'>                                                         </span></span><span

+style='font-size:11.0pt'>Hausa (contracionado)</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>id</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:4'>                                                         </span></span><span

+style='font-size:11.0pt'>Indonesian (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>já<span

+style='mso-tab-count:3'>              </span>&nbsp;&nbsp;&nbsp;<span

+style='mso-spacerun:yes'>  </span>Japanese (não-contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ko-g1<span

+style='mso-tab-count:4'>                </span>Korean (grau 1)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ko-g2<span

+style='mso-tab-count:4'>                </span>Korean (grau 2)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ko<span

+style='mso-tab-count:4'>                   </span>Korean (não-contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mg<span

+style='mso-tab-count:4'>                   </span>Malagasy (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>mun<span

+style='mso-tab-count:4'>                  </span>Munda (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>nl<span

+style='mso-tab-count:4'>                   </span>Dutch (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ny<span

+style='mso-tab-count:4'>                   </span>Chichewa (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ipa<span

+style='mso-tab-count:4'>                  </span>International Phonetic

+Alphabet</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>pt<span

+style='mso-tab-count:4'>                   </span>Portuguese (grau 2)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>si<span

+style='mso-tab-count:4'>                   </span>Sinhalese (não-contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>sw<span

+style='mso-tab-count:4'>                   </span>Swahili (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>th<span

+style='mso-tab-count:4'>                   </span>Thai (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>zh-tw<span

+style='mso-tab-count:4'>                </span>Taiwanese Chinese

+(não-contracionado)</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>zh-tw-ucb</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><span style='mso-tab-count:3'>                                    </span></span><span

+lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Taiwanese Chinese

+(Unique Chinese Braille)</span><span lang=EN-US style='font-family:Times;

+mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>zu<span

+style='mso-tab-count:4'>                   </span>Zulu (contracionado)</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Veja a opção de linha de

+comando -c, e as diretivas do arquivo de configuração contraction-table para

+obter detalhes sobre como usar uma tabela de contração.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span>6.3.1 <span style='font-size:11.0pt'>Tabela de Formato de Contração</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Uma tabela de contração

+consiste de uma seqüência de entradas, uma por linha, que define como as

+seqüências de caracteres devem ser representadas <st1:PersonName

+ProductID="em Braille. Caracteres UTF-8" w:st="on">em Braille. Caracteres UTF-8</st1:PersonName>

+devem ser usados. O espaço em branco (espaços, tabs) no início de uma linha,

+assim como antes e/ou depois de qualquer operando, é ignorado. Linhas que

+contêm apenas espaço em branco são ignoradas. Se o caráter não-espaço em branco

+antes de uma linha é &quot;#&quot; então essa linha é um comentário e é

+ignorado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O formato de uma entrada

+na tabela de contração é:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>directive

+operand ... [comment]</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Cada diretiva tem um

+número específico de operandos. Qualquer texto além do último operando de uma

+diretiva é interpretado como um comentário. A ordem das entradas dentro de uma

+tabela de contração é, em geral, tudo o que é conveniente para seu mantenedor

+(s). Uma entrada que define uma entidade, e.g. class, deve preceder todas as

+referências a essa entidade.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Entradas que combinam

+sequências de caracteres são automaticamente rearranjadas do maior para o

+menor, de modo que o maior sempre é preferível. Se mais uma entrada corresponde

+a seqüência de caracteres, então a sua tabela original de ordenação é mantida.

+Assim, a mesma seqüência pode ser traduzida de forma diferente em

+circunstâncias diferentes.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.2<span style='mso-tab-count:1'> </span>Tabela

+de Operandos de Contração</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>characters</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O primeiro operando de uma

+seqüência de caracteres correspondente as diretivas e a seqüência de caracteres

+a ser correspondida. Cada carácter dentro da seqüência pode ser:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Qualquer caracter único que não seja uma barra-invertida ou um carácter de

+espaço em branco.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Um caracter especial prefixado de barra-invertida. São eles:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\b</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de retrocesso.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\f</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+formfeed (quebra de página).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\n</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de nova linha.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\o###</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter de três dígitos octal.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\r</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de retorno de transporte.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\s</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de espaço.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\t</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de tab horizontal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\u####</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de quatro dígito.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\U########</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de oito dígito.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\v</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácter

+de tab horizontal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\x##</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Representação

+de um carácter hexadecimal de dois dígitos.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\X##</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>(o

+caso do X e dos dígitos não é significativa).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\#</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Um

+sinal de número literal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\&lt;name&gt;</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+nome Unicode de um carácter(use _ para espaço).</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>\\</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Uma

+barra-invertida literal.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>representation</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O segundo operando dessa

+seqüência de caracteres correspondentes a diretivas que tem uma representação

+braille da seqüência. Cada célula braille é especificada como uma seqüência de

+1-8 números de pontos. Um número de ponto é um dígito dentro do intervalo 1-8,

+tal como definido pela Standard Braille Dot Numbering Convention. O número de

+ponto especial 0, que não pode ser usado em conjunto com qualquer outro número

+de pontos, significa que não há pontos.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.3 Opcodes<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Um opcode é uma

+palavra-chave que diz o tradutor como interpretar os operandos. Os opcodes são

+agrupados aqui por função.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.3.1 Administração de Tabela<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Estes opcodes torna mais

+fácil de escrever as tabelas contração. Eles não têm nenhum efeito direto sobre

+a tradução de caracteres.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>include<i> path</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Incluir o conteúdo de

+outro arquivo. Codificação pode ser em qualquer profundidade. Os caminhos

+relativos são ancorados no diretório do arquivo incluído.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>locale <i>locale</i></span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Define a localidade para a

+interpretação de carácteres (letras minúsculas, maiúsculas, números, etc.) O

+local pode ser especificado como:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><i><span style='font-size:11.0pt'>language[_country][.charset][@modifier]</span></i><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O componente <i>language</i>

+é necessário e deverá ser um código de duas letras para línguas no padrão

+ISO-639. O componente <i>country</i> é opcional e deverá ser um código de duas

+letras no padrão ISO-3166. O componente <i>charset</i> é opcional e deve ser um

+nome de conjunto de caracteres, e.g. ISO-8859-1.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>C</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>7-bit ASCII.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>-</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Sem localidade.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A última especificação de

+localidade se aplica à tabela inteira. Se este opcode não for usado, a

+localidade C é assumida.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.3.2 Definição de símbolo especial<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Estes opcodes definem

+símbolos especiais que devem ser inseridos no texto em braile, a fim de

+esclarecê-lo.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>capsign

+<i>dots</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+símbolo que capitaliza uma única letra.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>begcaps

+<i>dots</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+símbolo que se inicia um bloco de letras maiúsculas em uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>endcaps<i>

+dots</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+símbolo que termina um bloco de letras maiúsculas em uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>letsign<i>

+dots</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+símbolo que marca uma carta que não faz parte de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>numsign

+<i>dots</i></span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+símbolo que marca o início de um número.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.3.3 Tradução de Carácter<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Estes opcodes definem as

+representações braille para sequências de carácteres. Cada um deles define uma

+entrada na tabela de contração. Essas entradas podem ser definidas em qualquer

+ordem, exceto, como indicado abaixo, quando definem representações alternativas

+para a seqüência da mesma natureza.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Cada um desses opcodes

+possui um carácter operando (que deve ser especificado como uma string), e uma

+condição embutida que regem a sua elegibilidade para o uso. O texto é

+processado estritamente de esquerda para a direita, caractere por caractere,

+com a entrada mais elegível para cada posição a ser utilizada. Se houver mais

+de uma entrada elegível para uma determinada posição, então aquele com a maior

+cadeia de caractere é usado. Se houver mais de uma entrada elegível para a

+mesma string de caracteres, então a uma definição mais próxima do início da

+tabela é usada (isso é a dependência de ordem única).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Muitos desses opcodes têm

+um dot operando que define a representação braille para seu operando

+characters. Também pode ser especificado como um sinal de igual (=), caso em

+que significa uma de duas coisas. Se a entrada é para um único carácter, então

+isso significa que a atual representação braille selecionada no computador (ver

+a opção de linha de comando -t e a diretiva text-table do arquivo de

+configuração) para que o caráter é para ser usado. Se é para uma seqüência de

+multi-caráteres, então a representação padrão para cada caracter (veja always)

+dentro da seqüência deve ser usada.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Alguns termos especiais

+são usados nas descrições desses opcodes.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>word</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Uma

+seqüência máxima de uma ou mais letras consecutivas.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Agora, finalmente, aqui

+está a descrição opcode:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>literal

+characters</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+toda a seqüência de caracteres delimitada por espaço em branco no braille do

+computador (ver a opção de linha de comando -t e a diretiva text-table do

+arquivo de configuração).</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>replace

+characters characters</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Substitui

+o primeiro conjunto de caracteres, não importa onde eles aparecem, com a

+segunda. Os carácteres substituídos não são reprocessado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>always

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres, não importa onde eles aparecem. Se há apenas um carácter, então,

+além disso, defini a representação padrão para esse carácter.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>repeatable

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres, não importa onde eles aparecem. Ignora quaisquer repetições

+consecutivas da mesma sequência.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>largesign

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os personagens, não importa onde eles aparecem. Retira os espaço em branco

+entre as palavras consecutivas acompanhado por este opcode.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>lastlargesign

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres, não importa onde eles aparecem. Remove o espaço em branco anterior

+se a palavra anterior foi acompanhado pelo opcode largesign.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>word

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são uma palavra.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>joinword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são uma palavra. Remove o seguinte espaço em branco se o

+primeiro caractere depois é uma letra.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>lowword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são uma palavra delimitada branco-espaço.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>contraction

+characters</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Prefixa

+os carácteres com um sinal de letra (veja letsign) se eles são uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>sufword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são uma palavra ou no início de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>prfword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são uma palavra ou no final de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>begword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>Traduz os carácteres se eles estão no início de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>begmidword characters dots</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles estão ou no início ou no meio de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>midword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles estão no meio de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>midendword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles estão ou no meio ou no final de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>endword

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles estão no final de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>prepunc

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são parte da pontuação no início de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>postpunc

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Traduz

+os carácteres se eles são parte da pontuação no final de uma palavra.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>begnum

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>Traduz os carácteres se eles estão no início de um número.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>midnum

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>Traduz os carácteres se eles estão no meio de um número.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>endnum

+characters dots</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>Traduz os carácteres se eles estão no final de um número.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.3.3.4 Classes de Caracteres<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Estes opcodes definem e

+usam classes de carácteres. Uma classe de caracteres associa um conjunto de

+carácteres com um nome. O nome, em seguida, refere-se a qualquer carácter

+dentro da classe. Um carácter pode pertencer a mais de uma classe.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As classes de carácteres a

+seguir são automaticamente pré-definicadas com base na localidade selecionada:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>digit</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácteres

+numéricos.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>letter</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Ambas

+os caracteres maiúsculos e minúsculos do alfabeto. Algumas localidades têm

+letras adicionais que não são nem maiúsculas nem minúsculas.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>lowercase</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>Carácteres do alfabeto em minúsculo.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>punctuation</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Carácteres

+impresso que não são nem espaço em branco e nem alfanuméricos.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>space</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Caracteres

+espaço <st1:PersonName ProductID="em branco. Na" w:st="on">em branco. Na</st1:PersonName>

+localidade padrão, esses são: espaço, tabulações horizontal e vertical, de

+retorno de carro, a nova linha, formfeed(quebra de página).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>uppercase</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>Carácteres

+do alfabeto em maiúsculo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os opcodes que definem e

+usam as classes de carácteres são:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>class

+name characters</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Defini

+uma nova classe de carácter. Os carácteres operando devem ser especificados

+como uma string. Uma classe de caracter não pode ser utilizada até que ela seja

+definida.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>after

+class opcode ...</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+opcode especificado é mais restrito na seqüência de caracteres correspondente e

+deve ser imediatamente precedido por um carácter que pertence à classe

+especificada. Se este opcode é usado mais de uma vez na mesma linha, então a

+união dos caracteres em todas as classes é usada.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>before

+class opcode ...</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+opcode especificado é mais restrito na seqüência de caracteres correspondente e

+deve ser imediatamente seguido por um carácter que pertence à classe

+especificada. Se este opcode é usado mais de uma vez na mesma linha, então a

+união dos caracteres em todas as classes é usada.<o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='mso-bidi-font-size:

+18.0pt'>6.4 Tabelas de Teclas</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os arquivos com nomes no

+formato *. ktb são tabelas chaves, e com nomes no formato *. kti são subtabelas

+chaves. Eles são usados por BRLTTY para vincular ao display braille e ao

+teclado combinações de teclas para comandos do BRLTTY.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os nomes dos display

+braille nos arquivos de tabela chaves começam com brl-xx-, onde xx é o código

+de duas letras de identificação do driver(driver identification code). O resto

+do nome identifica o modelo (s) para os quais a tabela chave é usada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os nomes dos teclados nos

+arquivos de tabela chaves começam com kbd-. O resto do nome descreve o tipo de

+teclado para qual a tabela chaves foi concebido.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os seguintes teclados de

+tabela chaves são fornecidos:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kbd-desktop</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ligações

+para teclado completo</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kbd-keypad</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ligações

+para navegação baseado em teclado numérico</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>kbd-laptop</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>ligações

+para teclados sem um teclado númerico</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Consulte a opção de linha

+de comando -k e a directiva do arquivo de configuração key-table para obter

+detalhes sobre como selecionar uma tabela chaves do teclado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1 Tabela de Diretivas de Teclas<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Uma tabela chaves consiste

+em uma seqüência de diretivas, uma por linha, que definem como as teclas e

+combinações de teclas devem ser interpretadas. UTF-8 caracter encoding deve ser

+usado. O espaço em branco (espaços, tabs) no início de uma linha, assim como

+antes e/ou depois de qualquer operando, é ignorado. Linhas que contêm apenas

+espaço em branco são ignoradas. Se o caráter não-espaço em branco antes de uma

+linha é um sinal de número (#) então essa linha é um comentário e é ignorada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A prioridade para a

+resolução de cada evento da tecla pressionada/liberada é a seguinte:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>1.

+Uma hotkey pressionada ou liberação definida dentro do contexto atual. Veja a

+diretiva hotkey para mais detalhes.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><st1:metricconverter

+ProductID="2. A" w:st="on"><span style='font-size:11.0pt'>2. A</span></st1:metricconverter><span

+style='font-size:11.0pt'> combinação de teclas definida dentro do contexto

+atual. Consulte a diretiva bind para detalhes.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>3.

+Um comando do teclado braille definido dentro do contexto atual. Veja as

+directivas map e superimpose para mais detalhes.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><st1:metricconverter

+ProductID="4. A" w:st="on"><span style='font-size:11.0pt'>4. A</span></st1:metricconverter><span

+style='font-size:11.0pt'> combinação de teclas definida dentro do contexto

+padrão. Consulte a diretiva bind para detalhes.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>As diretrizes a seguir são

+fornecidas:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>6.4.1.1 Atribuir Diretiva<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Criar ou atualizar uma

+variável associada com o atual nível de incluir. A variável é visível para o

+atual e incluem os níveis mais baixos, mas não incluem os níveis mais elevados.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>assign variable [value]</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>variable</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+nome da variável. Se a variável não existir no atual nível de incluir então ela

+é criada.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>value</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+valor que será atribuído à variável. Se não for fornecido, então um valor de

+comprimento zero (nulo) é atribuído.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A seqüência de escape

+\{variable} é substituída com o valor da variável chamada dentro das chaves. A

+variável deve ter sido definida no atual ou um nível de incluir superior.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>Exemplos:</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>assign nullValue</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>assign ReturnKey Key1</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind \{ReturnKey} RETURN</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>6.4.1.2

+</span><span style='font-size:11.0pt'>Diretiva de vínculo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Define quais comandos

+BRLTTY é executado quando uma combinação específica de uma ou mais teclas é

+pressionada. A ligação é definida dentro do contexto atual.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>bind keys command</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>keys</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A combinação de teclas que

+será vinculada. É uma seqüência de um ou mais nomes de chaves separadas pelo

+sinal de mais (+). O nome da chave final(ou única) pode ser opcionalmente prefixado

+com um ponto de exclamação (!). As chaves podem ser pressionadas em qualquer

+ordem, com a ressalva de que se o nome da chave final é prefixado com um ponto

+de exclamação em seguida, ele deve ser pressionado por último. O prefixo do

+ponto de exclamação significa que o comando é executado assim que a tecla é

+pressionada. Se não for utilizado, o comando é executado assim que qualquer uma

+das teclas é liberada.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>command</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O nome de um comando

+BRLTTY. Um ou mais modificadores podem ser opcionalmente anexado ao nome do

+comando, usando um sinal positivo (+) como separador.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Para comandos que

+ativa/desativa o recurso:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l11 level1 lfo10;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o modificador

++on for especificado, o recurso está habilitado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l11 level1 lfo10;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o modificador

++off é especificada, o recurso está desativado.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l11 level1 lfo10;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se nem +on ou

++off é especificado, o estado do recurso é ativado/desativado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Para comandos que mover a

+janela braille:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l0 level1 lfo11;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o modificador

++route é especificado, então, se necessário, o cursor é automaticamente

+encaminhado para que fique sempre visível no display braille.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Para comandos que move a

+janela braille para uma linha específica na tela:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l0 level1 lfo11;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o modificador

++toleft for especificado, a janela de braile também é movida para o início

+dessa linha.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l0 level1 lfo11;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>Se o modificador

++scaled é especificado, o conjunto de chaves vinculadas ao comando é

+interpretado como se fosse uma barra de rolagem. Se não for, então não há uma

+correspondência de um-para-um entre as teclas e linhas.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Para comandos que exigem

+um deslocamento:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O modificador +offset,

+onde offset é um número inteiro não negativo, pode ser especificado. Se não for

+fornecido, então +0 é assumido.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Exemplos:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span style='font-size:11.0pt'>bind Key1 CSRTRK</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:18.0pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind Key1+Key2 CSRTRK+off</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind Key1+Key3 CSRTRK+on</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind Key4 TOP</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind Key5 TOP+route</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind VerticalSensor GOTOLINE+toleft+scaled</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l1 level1 lfo12;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>bind Key6 CONTEXT+1</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.3 Diretiva de Contexto<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Definir formas

+alternativas de interpretar certos acontecimentos-chave e/ou combinações. Um

+contexto contém as definições criadas pelas directivas bind, hotkey, map, e

+superimpose.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>context identifier [title]</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>identifier</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Qual a definição posterior

+do contexto deve ser criada dentro. Podem ser:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·

+Um desses nomes especiais:</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>default</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O contexto padrão. Se a

+combinação de teclas não foi definida dentro do contexto atual, então a sua

+definição no contexto padrão é usado. Isso só se aplica as definições criadas

+pela diretiva de vinculo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>menu</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Esse contexto é utilizado

+quando dentro do menu de preferências do BRLTTY.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>·</span><span

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New"'>

+</span><span style='font-size:11.0pt'>Um número inteiro no intervalo de <st1:metricconverter

+ProductID="0 a" w:st="on">0 a</st1:metricconverter> 252. Contexto 0 é uma

+maneira alternativa para se referir ao contexto padrão. Os números mais altos

+de contexto devem ser evitados porque o maior número permitido está sujeito a

+alteração sem aviso prévio, se, por exemplo, os contextos mais nomeados são

+adicionados.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>title</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Uma descrição

+person-readable do contexto. Ele pode conter espaços, e as convenções de

+capitalização padrão deve ser usado. Este operando é opcional. Se for fornecido

+ao selecionar um contexto que já tem um título, então os dois devem

+coincidir.Contextos nomeado já tem títulos internamente atribuídos. Contextos numéricos

+são inicialmente criados sem títulos.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Um contexto é criado a

+primeira vez que ele é selecionado. Pode ser re-selecionado qualquer número de

+vezes depois disso.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Todas as definições

+subseqüentes até a próxima diretiva context ou no final do corrente nível de

+incluir são criadas dentro do contexto selecionado. O contexto inicial de nível

+superior da tabela chave é padrão. O contexto inicial incluído em uma subtabela

+chave é o contexto que foi selecionado quando ele foi incluído. Contextos de

+mudanças incluídos dentro de subtabelas chaves não afetam o contexto da mesa,

+incluindo tabela chave ou subtabela.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se um contexto tem um

+título (todos os contextos nomeados e os contextos numérico para o qual o

+operando title foi fornecido), então é persistente*. Quando um evento-chave faz

+com um contexto de persistência ser ativado, esse contexto atual permanece até

+que um evento posterior chave faz um contexto diferente persistente ser

+ativado.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se um contexto não tem um

+título (os contextos numéricos para os quais o operando title não foi

+fornecido), então ele é temporário. Quando um evento chave provoca um contexto

+temporário a ser ativado, esse contexto só é usado para interpretar o evento

+chave mais próximo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Exemplos:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>context menu</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>context 1 Braille Input</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>context 2</span><span lang=EN-US style='font-family:

+Times;mso-bidi-font-family:"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>6.4.1.4

+</span><span style='font-size:11.0pt'>Directiva de Ocultar<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifica se deseja ou

+não certas definições (ver as diretivas, hotkey, map, e superimpose) e notas

+(ver a diretiva note) serão incluídas no texto da tabela de chave de ajuda.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>hide

+state</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>state</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='font-size:11.0pt'>Uma dessas palavras-chaves:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:70.8pt'><span style='font-size:11.0pt'>on</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='font-size:11.0pt'>Estão excluídos.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='font-size:11.0pt'>off</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='font-size:11.0pt'>Eles estão incluídos.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O estado especificado se

+aplica as todas as definições e notas subseqüentes até a próxima diretiva hide

+ou o fim do atual nível de incluir. O estado inicial da tabela chave de nível

+superior é off. O estado inicial incluído em uma subtabela chave é o estado que

+foi selecionado quando ele foi incluído. Mudanças de estado incluídos dentro de

+uma subtabelas chave não afetam o estado incluindo na tabela chave ou

+subtabela.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Exemplo:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·</span><span

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New"'>

+&nbsp;</span><span style='font-size:11.0pt'>hide on</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.5 Diretiva de Hotkey<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Vincula os eventos press e

+release de uma tecla específica para dois comandos separados BRLTTY. Os

+vinculos são definidas dentro do contexto atual.</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>hotkey key press release</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>key</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+nome da tecla que será limitada.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>press</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+nome do comando BRLTTY que deve ser executado sempre que a tecla é pressionada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>release</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+nome do comando BRLTTY que deve ser executado sempre que a tecla é liberada.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Modificadores podem ser

+acrescentados aos nomes de comando. Veja o operando command da diretiva bind

+para mais detalhes.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Especifique NOOP se nenhum

+comando será executado. Especificando NOOP para ambos os comandos efetivamente

+desativa a chave.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Exemplos:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>hotkey Key1 CSRVIS+off CSRVIS+on</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·</span><span

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New"'>

+</span><span style='font-size:11.0pt'>hotkey Key2 NOOP NOOP</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.6 Diretiva IfKey<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Condicionalmente processo

+uma diretiva da tabela chave somente se o aparelho tem uma chave particular.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>ifkey key directive</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>key</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>O

+nome da chave, cuja disponibilidade está a ser testado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>directive</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>A diretiva da tabela chave

+que deve ser condicionalmente processado.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Exemplos:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='font-size:11.0pt;mso-ansi-language:EN-US'>·</span><span lang=EN-US

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'> </span><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>ifkey Key1 ifkey Key2 bind Key1+Key2 HOME</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span style='font-size:11.0pt'>6.4.1.7 Diretiva Include<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Processa as diretivas

+dentro de uma subtabela chave. É recursiva, o que significa que qualquer

+subtabela chave pode ser incluir ainda em outra subtabela chave. Cuidados devem

+ser tomados para garantir que &quot;incluem loop&quot; não seja criado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>include

+file</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>file</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>A

+subtabela chave que será incluída. Pode ser um caminho relativo ou absoluto. Se

+relativo está ancorado no diretório que contém a incluindo a tabela chave ou

+subtabela.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>Exemplos:</span><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l5 level1 lfo13;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>include common.kti</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:18.0pt'><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:36.0pt;text-indent:-18.0pt;mso-list:

+l5 level1 lfo13;tab-stops:list 36.0pt'><![if !supportLists]><span lang=EN-US

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings;mso-ansi-language:EN-US'><span style='mso-list:Ignore'>§<span

+style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='font-size:11.0pt;

+mso-ansi-language:EN-US'>include /path/to/my/keys.kti</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.8 Diretiva de Mapa<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Mapeia uma tecla para uma

+função do teclado <st1:PersonName ProductID="em Braille. O" w:st="on">em

+ braille. O</st1:PersonName> mapeamento é definido dentro do contexto atual.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>map key function</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>key</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+nome da tecla que será mapeada. Mais de uma tecla pode ser mapeada para a mesma

+função teclado em braille.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>function</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>O

+nome da função do teclado <st1:PersonName ProductID="em braille. Pode" w:st="on">em

+ braille. Pode</st1:PersonName> ser uma das seguintes palavras:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>DOT1</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>O

+padrão braille do ponto superior esquerdo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>DOT2</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>O

+padrão braille do ponto do meio esquerdo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>DOT3</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>O

+padrão braille do ponto inferior esquerdo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.25pt'><span style='font-size:11.0pt'>DOT4</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>O padrão braille do ponto superior direito.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>DOT5</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>O padrão braille do ponto do meio direito.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>DOT6</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>O padrão braille do ponto inferior direito.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>DOT7</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>O ponto braille do computador inferior esquerdo.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>DOT8</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>O ponto braille do computador inferior direito.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>SPACE</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>A barra de espaço.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>SHIFT</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>A

+tecla shift.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>UPPERCASE</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>Se

+uma letra minúscula está sendo inserido então traduzi para seu equivalente em

+maiúsculas.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>CONTROLE</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>A chave de controle.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>META</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><span

+style='mso-tab-count:1'>                </span></span><span style='font-size:

+11.0pt'>A tecla Alt esquerda.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Se a combinação de teclas

+consiste apenas de chaves que foram mapeadas para funções do teclado em braile,

+e se essas funções quando combinadas formam um comando de teclado braille

+válido, então o comando é executado logo que qualquer uma das teclas é

+liberado. Um comando de teclado braille válido deve incluir ou qualquer

+combinação dos pontos chaves ou a barra de espaço (mas não ambos). Se pelo

+menos um ponto chave é incluído, então as funções do teclado em braille

+especificados pelas diretivas superimpose dentro do mesmo contexto também estão

+implicitamente incluídos.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Exemplos:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>·</span><span

+style='font-size:7.0pt;font-family:"Times New Roman";mso-bidi-font-family:"Courier New"'>

+</span><span style='font-size:11.0pt'>map Key1 DOT1</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.9 Diretiva Note</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Adiciona uma explicação

+person-readable para o texto de ajuda da tabela chaves. As notas são usadas,

+por exemplo, para descrever o posicionamento, tamanhos e formas das teclas do

+dispositivo.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>note

+text</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>text</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>A

+explicação que será adicionada. Ele pode conter espaços, e deve ser

+gramaticalmente correta.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Cada nota especifica

+exatamente uma linha de texto explicativo. espaço à esquerda é tão ignorado

+recuo não pode ser especificado.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Não há limite para o

+número de notas que podem ser especificadas. Todos eles estão reunidos e

+apresentados em um único bloco no início do texto de ajuda da tabela chaves.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Exemplo:</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>note Key1 is the round key at the far left on the front surface.</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.10 Diretiva de sobreposição<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Inclui implicitamente uma

+função do teclado sempre que um comando de teclado braille consiste em pelo

+menos um dot é executado. A inclusão implícita é definida dentro do contexto

+atual. Qualquer número deles pode ser especificado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>superimpose function</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>function</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-size:11.0pt'>O

+nome da função do teclado braille. Veja o operando function da diretiva map

+para detalhes.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Exemplo:</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>superimpose DOT7</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.1.11 Diretiva Title</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Apresenta um resumo

+person-readable do propósito das tabelas chaves</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>title text</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span style='font-size:11.0pt'>text</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.25pt'><span style='font-size:11.0pt'>Um

+resumo de uma linha de como a tabela chave é usado. Ele pode conter espaço, e

+as convenções de capitalização padrão deve ser usado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O título da tabela chave

+pode ser especificado apenas uma vez.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><br>

+</span><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:EN-US'>Exemplo:</span><span

+lang=EN-US style='font-family:Times;mso-bidi-font-family:"Courier New";

+mso-ansi-language:EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-size:11.0pt;mso-ansi-language:

+EN-US'>title Bindings for Keypad-based Navigation</span><span lang=EN-US

+style='font-family:Times;mso-bidi-font-family:"Courier New";mso-ansi-language:

+EN-US'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>6.4.2 Propriedades do Teclado<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O padrão é que todos os

+teclados são monitorados. Um subconjunto de teclados pode ser selecionado por

+uma ou mais propriedades seguintes (veja a opção de linha de comando –K, e a

+diretiva do arquivo de configuração keyboard-properties).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>type</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>O tipo de barramento,

+especificado com uma das seguintes palavras-chaves: any, ps2, usb, Bluetooth</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>vendor</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Identificação do vendedor,

+especificado com um inteiro de 16-bits não sinalizado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>product</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Identificação do produto,

+especificado com um inteiro de 16-bits não sinalizado.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>Os identificadores vendor

+e product podem ser especificados em notação decimal (sem prefixo), octal(

+prefixado por 0), ou hexadecimal (prefixado por 0x). Especificando 0 significa

+corresponder a qualquer valor (como se a propriedade não foi especificada).</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>7. Tópicos

+Avançados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>7.1 Instalação de

+múltiplas versões</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>É fácil ter mais de uma versão do BRLTTY

+instalado no mesmo sistema ao mesmo tempo. Esse recurso permite que você teste

+uma nova versão antes de remover o antigo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A opção de compilação --with-execute-root

+permite que você instale a hierarquia de arquivo em qualquer lugar que você

+deseja. Lembrando que é melhor para manter todos os componentes BRLTTY dentro

+do sistema de arquivos root, você pode compila-los assim:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>. /configure --with-execute-root

+=/brltty-3.1</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>make install</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Você pode executá-lo assim:</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;/brltty-3.1/bin/brltty</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando a versão 3.2 é liberado, basta

+instalá-lo em um local diferente e executar o novo arquivo executável a partir

+daí.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;. /configure

+--with-execute-root =/brltty-3.2</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;make install</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;/brltty-3.2/bin/brltty</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Até agora, este paradigma é um pouco

+estranho, pelo menos, duas razões. Uma desses nomes é muito difícil de escrever,

+e o outro é que você não quer mexer com a seqüência de inicialização do sistema

+cada vez que você quiser mudar para uma versão diferente do BRLTTY. Estes

+problemas são facilmente resolvidos adicionando um link simbólico para o

+executável.</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;ln-s /brltty-3.1/bin/brltty

+/bin/brltty</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Quando é hora de mudar para a nova

+versão, apenas o link repoint symbolc.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;ln-s

+/brltty-3.2/bin/brltty /bin/brltty</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se você quiser ficar realmente, então

+inicie um outro nível de indireção para fazer todos os arquivos BRLTTY para

+qualquer versão que eles estão em todos os locais padrão. Primeiro, crie um

+link simbólico através de um local comum repointable de cada uma das

+localizações padrão de BRLTTY.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>ln -s /brltty/bin/brltty /bin/brltty</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>ln -s /brltty/etc/brltty /etc/brltty</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>ln -s brltty /lib/brltty /lib/brltty</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Então, tudo que você precisa fazer é

+apontar /brltty a versão desejada.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>ln -s /brltty-3.1 /brltty<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>7.2

+Instalação/Ajuda dos Discos de root para o Linux</span><span style='font-family:

+Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>BRLTTY pode funcionar como um executável

+autônomo. Tudo o que ele precisa saber pode ser explicitamente configurado em

+tempo de compilação (veja Opções de Compilação). Se o diretório de dados (ver

+na opção de compilação --with-data-directory e --with-execute-root) não

+existir, então BRLTTY olha em /etc para os arquivos que ele necessita. Mesmo

+que nenhum destes arquivos não existem, BRLTTY ainda funciona!</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Se, por alguma razão, você já criou o

+diretório de dados (normalmente /etc/brltty), é importante definir suas

+permissões para que somente o root possa criar arquivos dentro dela.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>chmod 755 /etc/brltty</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>A tela do aparelho de inspeção de

+conteúdo (normalmente /dev/vcsa) é necessária. Ela já deve existir a menos que

+sua distribuição do Linux é bastante antiga. Se necessário, você pode criá-lo

+com:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>mknod /dev/vcsa c 7 128</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>chmod 660 /dev/vcsa</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<span style='mso-spacerun:yes'>       </span></span><span style='font-size:

+11.0pt'>chown root.tty /dev/vcsa</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Um problema freqüentemente encontrado ao

+tentar usar BRLTTY em um ambiente incerto como um disco de root ou de um

+sistema incompleto é que ela não pôde encontrar as bibliotecas compartilhadas

+(ou partes) de que necessita. Discos de root costumam usar subconjunto e/ou

+versões desatualizadas das bibliotecas que podem ser inadequados. A solução é

+configurar BRLTTY com a opção --enable-standalone-programs. Isso remove todas

+as dependências de bibliotecas compartilhadas, mas, infelizmente, também cria

+um arquivo executável maior. Há uma série de opções de compilação que pode ser

+usado para remover de recursos desnecessários do BRLTTY, a fim de atenuar um

+pouco esse problema (ver Build Features).</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>O executável é eliminado durante o make

+install. Isto reduz significativamente o seu tamanho retirando sua tabela de

+símbolo. Você receberá um executável muito menor, portanto, se você concluir o

+processo de compilação completo, e depois copiá-lo de seu local instalado. Se,

+no entanto, copiá-lo do diretório de compilação, vai ser muito grande. Não se

+esqueça de tira-lo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+<span style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'>               </span></span><span

+style='font-size:11.0pt'>strip brltty<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>7.3 - Melhorias

+futuras</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Além de corrigir os erros e dar suporte a

+mais tipos de displays braille, esperamos, se o tempo permitir, trabalhar sobre

+o seguinte:</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Better Attribute Handling</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*

+Acompanhamento de Atributo.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*

+Misto de texto e modo de atributo.</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Scroll Tracking</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Bloquear a janela de uma linha braille já

+que rola na tela.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Better Speech Support<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*

+Braille mistos e de fala para leitura mais rápida do texto.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*

+Melhor navegação de speech.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+</span><span style='font-size:11.0pt'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*

+Mais sintetizadores de voz.</span><span style='font-family:Times;mso-bidi-font-family:

+"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Screen Subregions</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>Ignorar o movimento do cursor fora da

+região, e definir limites de navegação suave nas bordas da região.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Veja o arquivo TODO para uma lista mais

+completa.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>7.4 Bugs

+conhecidos</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><br>

+</span><span style='font-size:11.0pt'>No momento da escrita (dezembro 2001), os

+seguintes problemas são conhecidos:</span><span style='font-family:Times;

+mso-bidi-font-family:"Courier New"'><br>

+<br>

+</span><span style='font-size:11.0pt'>Cursor de roteamento é implementado como

+um looping do sub-processo que corre na prioridade reduzida para evitar o uso

+de muito tempo de CPU. Os diferentes sistemas que é carregado requer diferentes

+configurações de seus parâmetros. Os padrões funcionam muito bem em um editor

+de Unix típico de um sistema bastante leve, mas muito pobre em algumas outras

+situações, por exemplo, em um link lento de série para um host remoto.</span><span

+style='font-family:Times;mso-bidi-font-family:"Courier New"'><br

+style='mso-special-character:line-break'>

+<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>

+<![endif]><o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>Apêndice:<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>A. Display

+Braille Suportados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'>BRLTTY suporta os seguintes

+displays braille:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>B. Sintetizadores de Fala Suportados:</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'>BRLTTY suporta os seguintes

+sintetizadores de fala:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Nome<span

+style='mso-spacerun:yes'>             </span>Modelos<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> 

+</span>____________________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Alva<span

+style='mso-spacerun:yes'>             </span>Delphi (4nn)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>BrailleLite<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>CombiBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>eSpeak<span

+style='mso-spacerun:yes'>           </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'> </span>ExternalSpeech<span

+style='mso-spacerun:yes'>   </span>runs /usr/local/bin/externalspeech<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Festival<span

+style='mso-spacerun:yes'>         </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>FestivalLite<span

+style='mso-spacerun:yes'>     </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>GenericSay<span

+style='mso-spacerun:yes'>       </span>pipes to /usr/local/bin/say<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Mikropuhe<span

+style='mso-spacerun:yes'>        </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Swift<span style='mso-spacerun:yes'>     

+</span><span style='mso-spacerun:yes'>      </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Theta<span

+style='mso-spacerun:yes'>            </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>ViaVoice<span

+style='mso-spacerun:yes'>         </span>text to speech engine<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='font-family:Times;mso-bidi-font-family:

+"Courier New";mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-bidi-font-size:18.0pt'>C – Código de Identificação

+de Driver</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+

+<table class=MsoNormalTable border=0 cellspacing=0 cellpadding=0

+ style='border-collapse:collapse'>

+ <tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Código </span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-left:none;mso-border-left-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Nome</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:1'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>al</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Alva</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:2'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>at</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Albatross</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:3'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ba</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>BrlAPI</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:4'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>bl</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>BrailleLite</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:5'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>bm</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Baum (Native, HT, PB1,

+  PB2)</span><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:6'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>bn</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>BrailleNote</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:7'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>cb</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>CombiBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:8'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ec</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>EcoBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:9'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>es</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>eSpeak</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:10'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>eu</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>EuroBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:11'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>fl</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>FestivalLite</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:12'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>fs</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>FreedomScientific</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:13'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>fv</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Festival</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:14'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>gs</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>GenericSay</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:15'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>hm</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>HIMS</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:16'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ht</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>HandyTech</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:17'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>il</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>IrisLinux</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:18'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>lb</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Libbraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:19'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>lt</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>LogText</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:20'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>mb</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>MultiBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:21'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>md</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>MDV</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:22'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>mn</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>MiniBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:23'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>mp</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Mikropuhe</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:24'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>mt</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Metec</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:25'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>no</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>no driver</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:26'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>pg</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Pegasus</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:27'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>pm</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Papenmeier</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:28'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>sd</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>SpeechDispatcher</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:29'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>sk</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Seika</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:30'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>sw</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Swift</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:31'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>th</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Theta</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:32'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ts</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Telesensory Systems Inc.</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:33'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>tt</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>TTY</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:34'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>vd</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>VideoBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:35'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>vo</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Voyager</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:36'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>vr</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>Virtual</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:37'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>vs</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>VisioBraille</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:38'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>vv</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ViaVoice</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:39'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>xs</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>ExternalSpeech</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:40'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>xw</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt'>

+  <p class=MsoPlainText><span style='font-size:11.0pt'>XWindow</span><span

+  style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:41;mso-yfti-lastrow:yes;height:29.6pt'>

+  <td valign=top style='border:solid #AAAAAA 1.0pt;border-top:none;mso-border-top-alt:

+  solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;padding:5.6pt 5.6pt 5.6pt 5.6pt;

+  height:29.6pt'>

+  <p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:

+  "Courier New"'><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td valign=top style='border-top:none;border-left:none;border-bottom:solid #AAAAAA 1.0pt;

+  border-right:solid #AAAAAA 1.0pt;mso-border-top-alt:solid #AAAAAA .75pt;

+  mso-border-left-alt:solid #AAAAAA .75pt;mso-border-alt:solid #AAAAAA .75pt;

+  padding:5.6pt 5.6pt 5.6pt 5.6pt;height:29.6pt'>

+  <p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:

+  "Courier New"'><o:p>&nbsp;</o:p></span></p>

+  </td>

+ </tr>

+</table>

+

+<p class=MsoPlainText><span style='font-family:Times;mso-bidi-font-family:"Courier New"'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'>D.

+Drivers de Tela Suportados<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'>BRLTTY suporta os seguintes

+drivers de tela:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'><span lang=EN-US

+style='mso-ansi-language:EN-US'>as AT-SPI<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>     </span><span style='mso-tab-count:1'> </span>hd <o:p></o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'>Este driver fornece um acesso

+direto para a tela do console Hurd. É só selecionar a opção padrão do sistema

+Hurd.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>      </span>Name<span

+style='mso-spacerun:yes'>                </span>Models</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>    </span>_____________________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Albatross<span

+style='mso-spacerun:yes'>           </span>46/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Alva<span

+style='mso-spacerun:yes'>                </span>ABT (3nn)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Delphi (4nn)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Satellite (5nn)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span><span

+style='mso-spacerun:yes'>                    </span>Braille System 40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                </span><span

+style='mso-spacerun:yes'>          </span>Braille Controller 640/680<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Baum <span

+style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'>          </span>Inka<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Vario/RBT<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>SuperVario/Brailliant<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>PocketVario<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>VarioPro<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>EcoVario<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                         

+</span>VarioConnect/BrailleConnect<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Refreshabraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>BrailComm<span

+style='mso-spacerun:yes'>           </span>III<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>BrailleLite<span

+style='mso-spacerun:yes'>         </span>18/40/M20/M40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>BrailleNote<span

+style='mso-spacerun:yes'>         </span>18/32<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>BrlAPI<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>CombiBraille<span

+style='mso-spacerun:yes'>        </span>25/45/85<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>EcoBraille<span

+style='mso-spacerun:yes'>          </span>20/40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>EuroBraille<span

+style='mso-spacerun:yes'>         </span>AzerBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Clio<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Iris<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>NoteBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Scriba<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Esys 12/40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>FreedomScientific<span

+style='mso-spacerun:yes'>   </span>Focus 1 44/70/84<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Focus 2 40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Focus Blue 40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>PAC Mate 20/40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>HandyTech<span

+style='mso-spacerun:yes'>           </span>Modular 20/40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Modular Evolution

+64/88<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                  </span><span

+style='mso-spacerun:yes'>        </span>Active Braille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Braille Wave<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Easy Braille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Braille Star 40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Bookworm<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Braillino<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>HIMS<span

+style='mso-spacerun:yes'>                </span>Braille Sense<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>SyncBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Libbraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>LogText<span

+style='mso-spacerun:yes'>             </span>32<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>MDV<span

+style='mso-spacerun:yes'>                 </span>MB208/MB408L/MB408S (protocol

+5)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Metec<span

+style='mso-spacerun:yes'>               </span>BD-40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>MiniBraille<span

+style='mso-spacerun:yes'>         </span>20<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>MultiBraille<span

+style='mso-spacerun:yes'>        </span>MB125CR/MB145CR/MB185CR<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> </span><span style='mso-spacerun:yes'>    

+</span>Papenmeier<span style='mso-spacerun:yes'>          </span>Compact 486<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Compact/Tiny<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>IB 80 CR Soft<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>2D Lite (plus)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>2D Screen Soft<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 80</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 2D 40/66/80</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 40/66/70/80 S</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 2D 80 S</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 40 P</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>EL 80 II</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>Elba 20/32</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>                         

+</span>Trio 40/Elba20/Elba32</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Pegasus<span

+style='mso-spacerun:yes'>             </span>20/27/40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Seika<span

+style='mso-spacerun:yes'>               </span>40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>TSI<span

+style='mso-spacerun:yes'>                 </span>Navigator 20/40/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>PowerBraille 40/65/80<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>TTY<span

+style='mso-spacerun:yes'>                 </span>terminfo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>VideoBraille<span

+style='mso-spacerun:yes'>        </span>40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Virtual<span

+style='mso-spacerun:yes'>             </span>TCP/Unix, client/server<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>VisioBraille<span

+style='mso-spacerun:yes'>        </span>20/40<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Voyager<span

+style='mso-spacerun:yes'>             </span>44/70<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Part232 (serial

+adapter)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>BraillePen/EasyLink<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>XWindow<span

+style='mso-spacerun:yes'>             </span>X11<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>                          </span>Windows<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Code<span style='mso-spacerun:yes'>  

+</span>Name<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>    </span>____________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>al<span style='mso-spacerun:yes'>    

+</span>Alva<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>at<span style='mso-spacerun:yes'>    

+</span>Albatross<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>ba<span style='mso-spacerun:yes'>    

+</span>BrlAPI<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>bc<span style='mso-spacerun:yes'>    

+</span>BrailComm<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>bl<span style='mso-spacerun:yes'>    

+</span>BrailleLite<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>bm<span style='mso-spacerun:yes'>    

+</span>Baum (Native, HT, PB1, PB2)<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>bn<span style='mso-spacerun:yes'>    

+</span>BrailleNote<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>cb<span style='mso-spacerun:yes'>    

+</span>CombiBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>ec<span style='mso-spacerun:yes'>    

+</span>EcoBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>es<span style='mso-spacerun:yes'>    

+</span>eSpeak<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>eu<span style='mso-spacerun:yes'>    

+</span>EuroBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>fl<span style='mso-spacerun:yes'>    

+</span>FestivalLite<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>fs<span style='mso-spacerun:yes'>    

+</span>FreedomScientific<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>fv<span style='mso-spacerun:yes'>    

+</span>Festival<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>gs<span style='mso-spacerun:yes'>    

+</span>GenericSay<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>hm<span style='mso-spacerun:yes'>    

+</span>HIMS<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>ht<span style='mso-spacerun:yes'>    

+</span>HandyTech<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>il<span style='mso-spacerun:yes'>    

+</span>IrisLinux<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>lb<span style='mso-spacerun:yes'>    

+</span>Libbraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>lt<span style='mso-spacerun:yes'>    

+</span>LogText<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>mb<span style='mso-spacerun:yes'>    

+</span>MultiBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>md<span style='mso-spacerun:yes'>    

+</span>MDV<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>mn<span style='mso-spacerun:yes'>    

+</span>MiniBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>mp<span style='mso-spacerun:yes'>    

+</span>Mikropuhe<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>mt<span style='mso-spacerun:yes'>    

+</span>Metec<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>no<span style='mso-spacerun:yes'>    

+</span>no driver<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>pg<span style='mso-spacerun:yes'>    

+</span>Pegasus<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>pm<span style='mso-spacerun:yes'>    

+</span>Papenmeier<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>sd<span style='mso-spacerun:yes'>    

+</span>SpeechDispatcher<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>sk<span style='mso-spacerun:yes'>    

+</span>Seika<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>    </span>sw<span style='mso-spacerun:yes'>    

+</span>Swift<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>th<span style='mso-spacerun:yes'>    

+</span>Theta<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>ts<span style='mso-spacerun:yes'>    

+</span>Telesensory Systems Inc.<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>tt<span style='mso-spacerun:yes'>    

+</span>TTY<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>vd<span style='mso-spacerun:yes'>    

+</span>VideoBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>vo<span style='mso-spacerun:yes'>    

+</span>Voyager<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>vr<span style='mso-spacerun:yes'>    

+</span>Virtual<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>vs<span style='mso-spacerun:yes'>    

+</span>VisioBraille<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>vv<span style='mso-spacerun:yes'>    

+</span>ViaVoice<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>xs<span style='mso-spacerun:yes'>    

+</span>ExternalSpeech<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>      </span>xw<span

+style='mso-spacerun:yes'>     </span>XWindow</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:70.8pt;text-indent:-35.55pt'>lx <span

+style='mso-tab-count:1'>   </span><span style='mso-spacerun:yes'> </span>Este driver

+fornece um acesso direto no console do Linux. É só</p>

+

+<p class=MsoPlainText style='margin-left:70.8pt;text-indent:-35.55pt'><span

+style='mso-spacerun:yes'>       </span>selecionar a opção padrão do sistema

+Linux.</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'> </span>sc <span

+style='mso-spacerun:yes'>    </span>Este driver fornece um acesso direto na

+tela do programa. Selecione</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='mso-spacerun:yes'> </span>todos os sistemas, na opção padrão se o driver

+de tela nativo é</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='mso-spacerun:yes'> </span>disponível. O caminho da tela que nós

+fornecemos (ver Subdiretório</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='mso-spacerun:yes'> </span>de Caminhos) deverá ser aplicado. Usado neste

+driver junto com o</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='mso-spacerun:yes'> </span>fato da tela deverá ser rodada concorrentemente,

+o BRLTTY faz o uso</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt;text-indent:35.4pt'><span

+style='mso-spacerun:yes'> </span>efetivo apenas depois do usuário ter logado.</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>      </span>wn<span

+style='mso-tab-count:1'>    </span>Este driver fornece um acesso direto com o

+console do Windows. É só</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>           

+</span>selecionado a opção padrão em Windows/Cygwin systems.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>E. Sintaxe de Operação</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>E.1. Especificação do Driver</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Um display braille ou um driver sintetizador de fala deverá

+ser especificado com duas letras “código de identificação de driver”.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Uma virgula delimita a lista de drivers especificada. Se

+isto está feito então a autodetecção é feita usando cada lista de drivers <st1:PersonName

+ProductID="em seq&#65532;&#65514;ncia. Voc&#65514;" w:st="on">em seqüência.

+ Você</st1:PersonName> deverá precisar experimentar na ordem determinada como

+mais confiável quando alguns drivers autodetectados forem melhores que os

+outros.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Se uma única palavra auto é especificada quando a

+autodetecção é fornecida usando apenas aqueles drivers que são conhecidos é

+confiável nesse propósito.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>E.2. Especificação do Dispositivo Braille</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Em geral, a especificação do dispositivo Braille (ver

+linha de comando –d, o arquivo de configuração “braille-device”, e na

+compilação a opção “—with-braille-device”) é qualificada: dado. Para ser

+compatível com versões anteriores, se a serial é assumida.</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>A seguir os tipos de dispositivos são suportados: </p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-tab-count:1'>      </span>Bluetooth</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'>Para o dispositivo bluetooth,

+especifica bluetooth:address (bt : pode ser usado). O endereço deverá ser seis

+pares de dígitos de hexadecimal separados por dois pontos, e.g.

+01:23:45:67:89:AB.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-tab-count:1'>      </span>Serial</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'>Para o dispositivo serial,

+especificado: /path/to/device. A serial é opcional (para compatibilidade

+anterior). Se um caminho relativo é dado enquanto o caminho é anexado em /dev

+(a localização onde os dispositivos são definidos no sistema unix). As

+especificações do dispositivo a seguir todas se referem para o primeiro

+dispositivo serial no Linux:</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='margin-left:71.4pt;text-indent:-18.0pt;mso-list:

+l6 level1 lfo14;tab-stops:list 71.4pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='mso-ansi-language:EN-US'>serial:/dev/ttyS0</span></p>

+

+<p class=MsoPlainText style='margin-left:71.4pt;text-indent:-18.0pt;mso-list:

+l6 level1 lfo14;tab-stops:list 71.4pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='mso-ansi-language:EN-US'>serial:ttyS0</span></p>

+

+<p class=MsoPlainText style='margin-left:71.4pt;text-indent:-18.0pt;mso-list:

+l6 level1 lfo14;tab-stops:list 71.4pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='mso-ansi-language:EN-US'>/dev/ttyS0</span></p>

+

+<p class=MsoPlainText style='margin-left:71.4pt;text-indent:-18.0pt;mso-list:

+l6 level1 lfo14;tab-stops:list 71.4pt'><![if !supportLists]><span

+style='font-family:Wingdings;mso-fareast-font-family:Wingdings;mso-bidi-font-family:

+Wingdings'><span style='mso-list:Ignore'>§<span style='font:7.0pt "Times New Roman"'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

+</span></span></span><![endif]><span lang=EN-US style='mso-ansi-language:EN-US'>ttyS0</span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'>USB</p>

+

+<p class=MsoPlainText style='margin-left:35.4pt'>Para o dispositivo USB,

+especificado USB: o BRLTTY será procurado pelo primeiro dispositivo USB que foi

+marcado pelo driver do display braille que está sendo usado. Se isto é

+inadequado, e.g. se você tem mais de um display braille USB que requer um mesmo

+driver, então você pode refinar a especificação do dispositivo por numero de

+serial visível no display, e.g. usb:12345. N.B.: A característica de

+identificação do numero serial” não funciona em alguns modelos por causa de

+alguns fabricantes não setarem o numero serial USB em todos ou setam mais não

+com um único valor.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>A virgula delimita a lista de dispositivos braille

+especificados. Se isso é feito enquanto a autodeteção é feita em cada lista de

+dispositivo em seqüência.</p>

+

+<p class=MsoPlainText>Este característica é particularmente usual se você tem

+um display braille com mais de uma interface, e.g. ambos uma serial e uma porta

+USB. No caso é usualmente melhor a lista de portas USB primeiro, e.g.

+usb:serial:/dev/ttyS0, de </p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>port.<span style='mso-spacerun:yes'>  </span>In this case

+it's usually better to list the USB port first, desde que a autodeteção tende

+ser confiavel do que o esperado.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>E.3. Especificação do Dispositivo PCM</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Na maioria dos casos os dispositivos PCM é um caminho

+cheio e apropriado para o dispositivo do sistema. Exceções são: </p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>ALSA</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>O nome dos argumentos são dispositivos físicos e lógicos

+i.e. name[:argument,...].</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>O dispositivo PCM padrão é:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>      </span>Plataforma<span

+style='mso-spacerun:yes'>     </span>Dispositivo</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>    </span>_______________________________________________________</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>FreeBSD<span

+style='mso-spacerun:yes'>      </span><span style='mso-spacerun:yes'>  </span>/dev/dsp<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Linux/ALSA<span

+style='mso-spacerun:yes'>   </span><span style='mso-spacerun:yes'>  </span>hw:0,0<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Linux/OSS<span

+style='mso-spacerun:yes'>    </span><span style='mso-spacerun:yes'>  </span>/dev/dsp<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>NetBSD<span

+style='mso-spacerun:yes'>       </span><span style='mso-spacerun:yes'>  </span>/dev/audio<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>OpenBSD<span

+style='mso-spacerun:yes'>      </span><span style='mso-spacerun:yes'>  </span>/dev/audio<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Qnx<span

+style='mso-spacerun:yes'>          </span><span

+style='mso-spacerun:yes'>  </span>preferred PCM output device<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Solaris<span

+style='mso-spacerun:yes'>      </span><span style='mso-spacerun:yes'>  </span>/dev/audio<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'>E.4.

+Especificação do Dispositivo MIDI<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>Na maioria dos casos de dispositivos MIDI é um caminho

+completo e apropriado do para o dispositivo do sistema. Exceções são:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>ALSA</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>O cliente e porta são separadas por uma virgula, i.e.

+client:port. Cada um deve ser especificado como um numero ou como um caso

+especial de substring do nome.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>O dispositivo padrão MIDI é:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Plataforma<span

+style='mso-spacerun:yes'>     </span>Dispositivo<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>    </span>____________________________________________________________<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Linux/ALSA<span

+style='mso-spacerun:yes'>   </span><span style='mso-spacerun:yes'>  </span>the

+first available MIDI output port<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>      </span>Linux/OSS<span

+style='mso-spacerun:yes'>    </span><span style='mso-spacerun:yes'>  </span>/dev/sequencer<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText>F. Convenção do Padrão Braille para numeração</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>Um padrão braille de celulas consiste em 6 pontos para

+alinhados em 3 linhas e 2 colunas. Cada ponto pode ser identificado por os

+números abaixo:</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText style='text-indent:35.4pt'>1 Topo-esquerda (linha 1,

+coluna 1).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-tab-count:1'> </span>2 Meio-esquerda (linha 2, coluna 1).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-tab-count:1'> </span>3 Fim-esquerda (linha 3, coluna 1).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-tab-count:1'> </span>4 Topo-direita (linha 1, coluna 2).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'> </span>5 Meio-direita (linha 2, coluna 2).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'> </span>6 Fim-direita (linha 3, coluna 2).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Display braille

+tem introduzido 40 linhas no fim.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-tab-count:1'> </span>7 Abaixo-esquerda (linha 4, coluna 1).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>     </span><span

+style='mso-tab-count:1'> </span>8 Abaixo-direita (linha 4, coluna 2).</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>Talvez a imagem

+ficará mais facil de entender.</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>       </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>1 o o 4<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>       </span>2 o o 5<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>       </span>3 o o </span>6</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>       </span>7 o o 8</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText>G. Código Norte-Americano do Padrão Braille</p>

+

+<p class=MsoPlainText><o:p>&nbsp;</o:p></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>    </span><span

+lang=EN-US style='mso-ansi-language:EN-US'>Num Hex<span

+style='mso-spacerun:yes'>     </span>Pontos<span style='mso-spacerun:yes'>    

+</span>Descrição<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> 

+</span>+-----------------------------------------------------------------+<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>0<span style='mso-spacerun:yes'>  </span>00<span

+style='mso-spacerun:yes'>  </span>[7<span style='mso-spacerun:yes'>  

+</span>4<span style='mso-spacerun:yes'>  </span>8]<span

+style='mso-spacerun:yes'>  </span>NUL (vazio)<span

+style='mso-spacerun:yes'>           </span><span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>1<span style='mso-spacerun:yes'> 

+</span>01<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1<span style='mso-spacerun:yes'>   </span>8]<span

+style='mso-spacerun:yes'>  </span>SOH (inicio do cabeçalho)<span

+style='mso-spacerun:yes'>                  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>2<span style='mso-spacerun:yes'> 

+</span>02<span style='mso-spacerun:yes'>  </span>[7 21<span

+style='mso-spacerun:yes'>   </span>8]<span style='mso-spacerun:yes'>  </span>STX

+(inicio do texto)<span style='mso-spacerun:yes'>                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| <span

+style='mso-spacerun:yes'>  </span>3<span style='mso-spacerun:yes'> 

+</span>03<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>14<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>ETX (fim do texto)<span

+style='mso-spacerun:yes'>             </span><span

+style='mso-spacerun:yes'>            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>4<span style='mso-spacerun:yes'> 

+</span>04<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>145 8]<span style='mso-spacerun:yes'> 

+</span>EOT (fim da transmissão) <span

+style='mso-spacerun:yes'>                  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span><span style='mso-spacerun:yes'> </span>5<span

+style='mso-spacerun:yes'>  </span>05<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>  </span>1 5 8]<span

+style='mso-spacerun:yes'>  </span>ENQ (pesquisa)<span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>6<span style='mso-spacerun:yes'>  </span>06<span

+style='mso-spacerun:yes'>  </span>[7 214<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>ACK (reconhecimento)<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>7<span style='mso-spacerun:yes'> 

+</span>07<span style='mso-spacerun:yes'>  </span>[7 2145 8]<span

+style='mso-spacerun:yes'>  </span>BEL (campainha)<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'>  

+</span>8<span style='mso-spacerun:yes'>  </span>08<span

+style='mso-spacerun:yes'>  </span>[7 21 5 8]<span style='mso-spacerun:yes'> 

+</span>BS (back space)<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>9<span style='mso-spacerun:yes'> 

+</span>09<span style='mso-spacerun:yes'>  </span>[7 2 4<span

+style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'>  </span>HT

+(tab horizontal)<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>10<span style='mso-spacerun:yes'> 

+</span>0A<span style='mso-spacerun:yes'>  </span>[7 2 45 8]<span

+style='mso-spacerun:yes'>  </span>LF (linha de avanço)<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>11 <span style='mso-spacerun:yes'> </span>0B<span

+style='mso-spacerun:yes'>  </span>[73 1<span style='mso-spacerun:yes'>  

+</span>8]<span style='mso-spacerun:yes'>  </span>VT (tab vertical)<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>12<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="0C" w:st="on">0C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7321<span style='mso-spacerun:yes'>  

+</span>8]<span style='mso-spacerun:yes'>  </span>FF (forma de avançar)<span

+style='mso-spacerun:yes'>                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'> 

+</span>13<span style='mso-spacerun:yes'>  </span>0D <span

+style='mso-spacerun:yes'> </span>[73 14<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>CR (retorno)<span

+style='mso-spacerun:yes'>         </span><span

+style='mso-spacerun:yes'>                      </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>14<span style='mso-spacerun:yes'>  </span>0E<span

+style='mso-spacerun:yes'>  </span>[73 145 8]<span style='mso-spacerun:yes'> 

+</span>SO (shift out)<span style='mso-spacerun:yes'>               </span><span

+style='mso-spacerun:yes'>              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>15<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="0F" w:st="on">0F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[73 1 5 8]<span style='mso-spacerun:yes'> 

+</span>SI (shift in)<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>16<span style='mso-spacerun:yes'>  </span>10<span

+style='mso-spacerun:yes'>  </span>[73214<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>DLE (data link escape)<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>17<span style='mso-spacerun:yes'>  </span>11<span

+style='mso-spacerun:yes'>  </span>[732145 8]<span style='mso-spacerun:yes'> 

+</span>DC1 (direct control 1)<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>18<span style='mso-spacerun:yes'>  </span>12<span

+style='mso-spacerun:yes'>  </span>[7321 5 8]<span style='mso-spacerun:yes'> 

+</span>DC2 (direct control 2)<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>19<span style='mso-spacerun:yes'>  </span>13<span

+style='mso-spacerun:yes'>  </span>[732 4<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>DC3 (direct control 3)<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>20<span style='mso-spacerun:yes'>  </span>14<span

+style='mso-spacerun:yes'>  </span>[732 45 8]<span style='mso-spacerun:yes'> 

+</span>DC4 (direct control 4)<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>21<span style='mso-spacerun:yes'> 

+</span>15<span style='mso-spacerun:yes'>  </span>[73 1<span

+style='mso-spacerun:yes'>  </span>68]<span style='mso-spacerun:yes'>  </span>NAK

+(não reconhecimento)<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>                 </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>22<span style='mso-spacerun:yes'>  </span>16<span

+style='mso-spacerun:yes'>  </span>[7321<span style='mso-spacerun:yes'> 

+</span>68]<span style='mso-spacerun:yes'>  </span>SYN (sincronização)<span

+style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>23<span style='mso-spacerun:yes'> 

+</span>17<span style='mso-spacerun:yes'>  </span>[7 2 4568]<span

+style='mso-spacerun:yes'>  </span>ETB (fim do bloco de texto)<span

+style='mso-spacerun:yes'>                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>24<span style='mso-spacerun:yes'> 

+</span>18<span style='mso-spacerun:yes'>  </span>[73 14 68]<span

+style='mso-spacerun:yes'>  </span>CAN (cancela)<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>25<span style='mso-spacerun:yes'> 

+</span>19<span style='mso-spacerun:yes'>  </span>[73 14568]<span

+style='mso-spacerun:yes'>  </span>EM (fim do meio)<span

+style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>         </span><span

+style='mso-spacerun:yes'>                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>26<span style='mso-spacerun:yes'> 

+</span>1A<span style='mso-spacerun:yes'>  </span>[73 1 568]<span

+style='mso-spacerun:yes'>  </span>SUB (substituto)<span

+style='mso-spacerun:yes'>                           </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'> 

+</span>27<span style='mso-spacerun:yes'>  </span>1B<span

+style='mso-spacerun:yes'>  </span>[7 2 4 68]<span style='mso-spacerun:yes'> 

+</span>ESC (escape)<span

+style='mso-spacerun:yes'>                               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>28<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="1C" w:st="on">1C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7 21 568]<span style='mso-spacerun:yes'> 

+</span>FS (arquivo separador)<span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>29<span style='mso-spacerun:yes'> 

+</span>1D<span style='mso-spacerun:yes'>  </span>[7 214568]<span

+style='mso-spacerun:yes'>  </span>GS (grupo separador)<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>30<span style='mso-spacerun:yes'> 

+</span>1E<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>   </span>45 8]<span style='mso-spacerun:yes'> 

+</span>RS (gravador separador)<span

+style='mso-spacerun:yes'>                    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>31<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="1F" w:st="on">1F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7<span style='mso-spacerun:yes'>  

+</span>4568]<span style='mso-spacerun:yes'>  </span>US (unidade separador)<span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>32<span style='mso-spacerun:yes'> 

+</span>20<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>        </span>]<span style='mso-spacerun:yes'> 

+</span>espaço<span

+style='mso-spacerun:yes'>                                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| <span

+style='mso-spacerun:yes'> </span>33<span style='mso-spacerun:yes'> 

+</span>21<span style='mso-spacerun:yes'>  </span>[ 32 4 6 ]<span

+style='mso-spacerun:yes'>  </span>ponto de exclamação<span

+style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>34<span style='mso-spacerun:yes'> 

+</span>22<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>     </span>5<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>marcação de texto<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| <span

+style='mso-spacerun:yes'> </span>35<span style='mso-spacerun:yes'> 

+</span>23<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>456 ]<span style='mso-spacerun:yes'> 

+</span>simbolo do numero<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>36<span style='mso-spacerun:yes'> 

+</span>24<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>214 6 ]<span style='mso-spacerun:yes'> 

+</span>simbolo de dolar<span style='mso-spacerun:yes'>         </span><span

+style='mso-spacerun:yes'>                  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'> 

+</span>37<span style='mso-spacerun:yes'>  </span>25<span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'> </span>14 6 ]<span style='mso-spacerun:yes'> 

+</span>simbolo de porcento<span

+style='mso-spacerun:yes'>                        </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>38<span style='mso-spacerun:yes'>  </span>26<span

+style='mso-spacerun:yes'>  </span>[ 3214 6 ]<span style='mso-spacerun:yes'> 

+</span>ampersand<span

+style='mso-spacerun:yes'>                                  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>39<span style='mso-spacerun:yes'> 

+</span>27<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>      </span>]<span style='mso-spacerun:yes'> 

+</span>acento agudo<span

+style='mso-spacerun:yes'>                               </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>40<span style='mso-spacerun:yes'> 

+</span>28<span style='mso-spacerun:yes'>  </span>[ 321 56 ]<span

+style='mso-spacerun:yes'>  </span>parenteses esquerdo<span

+style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>   </span>4 <span

+style='mso-spacerun:yes'> </span>291<span style='mso-spacerun:yes'>  </span>[

+32 456 ) parenteses direito<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>42<span style='mso-spacerun:yes'> 

+</span>2A<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1<span style='mso-spacerun:yes'>  </span>6

+]<span style='mso-spacerun:yes'>  </span>asterisco<span

+style='mso-spacerun:yes'>                                  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>43<span style='mso-spacerun:yes'> 

+</span>2B<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>4 6 ]<span style='mso-spacerun:yes'> 

+</span>sinal de mais<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>44<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="2C" w:st="on">2C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>     

+</span>6 ]<span style='mso-spacerun:yes'>  </span>virgula<span

+style='mso-spacerun:yes'>                                    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>45<span style='mso-spacerun:yes'> 

+</span>2D<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>    </span>6 ]<span style='mso-spacerun:yes'> 

+</span>sinal de menos<span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>46<span style='mso-spacerun:yes'> 

+</span>2E<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>    </span>4 6 ]<span style='mso-spacerun:yes'> 

+</span>ponto <span

+style='mso-spacerun:yes'>                                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>47<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="2F" w:st="on">2F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[ 3<span style='mso-spacerun:yes'> 

+</span>4<span style='mso-spacerun:yes'>   </span>]<span

+style='mso-spacerun:yes'>  </span>contra barra <span

+style='mso-spacerun:yes'>          </span><span

+style='mso-spacerun:yes'>                    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'> 

+</span>48<span style='mso-spacerun:yes'>  </span>30<span

+style='mso-spacerun:yes'>  </span>[ 3<span style='mso-spacerun:yes'>  

+</span>56 ]<span style='mso-spacerun:yes'>  </span>zero<span

+style='mso-spacerun:yes'>                                       </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>49<span style='mso-spacerun:yes'> 

+</span>31<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>     </span>]<span

+style='mso-spacerun:yes'>  </span>um <span

+style='mso-spacerun:yes'>                                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>50<span style='mso-spacerun:yes'> 

+</span>32<span style='mso-spacerun:yes'>  </span>[ 32<span

+style='mso-spacerun:yes'>     </span>]<span style='mso-spacerun:yes'>  </span>dois<span

+style='mso-spacerun:yes'>                                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>51<span style='mso-spacerun:yes'> 

+</span>33<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>  </span>5<span

+style='mso-spacerun:yes'>  </span>] <span style='mso-spacerun:yes'> </span>três

+<span style='mso-spacerun:yes'>                                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>52<span style='mso-spacerun:yes'> 

+</span>34<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>  </span>56 ]<span

+style='mso-spacerun:yes'>  </span>quatro<span

+style='mso-spacerun:yes'>                                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>53<span style='mso-spacerun:yes'> 

+</span>35<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>   </span>6 ]<span

+style='mso-spacerun:yes'>  </span>cinco<span

+style='mso-spacerun:yes'>                                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>54<span style='mso-spacerun:yes'> 

+</span>36<span style='mso-spacerun:yes'>  </span>[ 32<span

+style='mso-spacerun:yes'>  </span>5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>seis<span

+style='mso-spacerun:yes'>                                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>55<span style='mso-spacerun:yes'> 

+</span>37<span style='mso-spacerun:yes'>  </span>[ 32<span

+style='mso-spacerun:yes'>  </span>56 ]<span style='mso-spacerun:yes'>  </span>sete

+<span style='mso-spacerun:yes'>                                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>56<span style='mso-spacerun:yes'> 

+</span>38<span style='mso-spacerun:yes'>  </span>[ 32<span

+style='mso-spacerun:yes'>   </span>6 ]<span style='mso-spacerun:yes'> 

+</span>oito <span

+style='mso-spacerun:yes'>                                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>57<span style='mso-spacerun:yes'> 

+</span>39<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>   </span>5<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>nove<span

+style='mso-spacerun:yes'>                                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>58<span style='mso-spacerun:yes'> 

+</span>3A<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1 56 ]<span style='mso-spacerun:yes'> 

+</span>ponto e virgula<span style='mso-spacerun:yes'>      </span><span

+style='mso-spacerun:yes'>                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>59<span style='mso-spacerun:yes'> 

+</span>3B<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>     </span>56 ]<span style='mso-spacerun:yes'> 

+</span>dois pontos<span style='mso-spacerun:yes'>    </span><span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>60<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="3C" w:st="on">3C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'> 

+</span>21<span style='mso-spacerun:yes'>  </span>6 ]<span

+style='mso-spacerun:yes'>  </span>jacaré menos<span

+style='mso-spacerun:yes'>        </span><span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>61<span style='mso-spacerun:yes'> 

+</span>3D<span style='mso-spacerun:yes'>  </span>[ 321456 ]<span

+style='mso-spacerun:yes'>  </span>igual<span style='mso-spacerun:yes'>        

+</span><span style='mso-spacerun:yes'>      </span><span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>62<span style='mso-spacerun:yes'> 

+</span>3E<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>45<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>jacaré mais<span style='mso-spacerun:yes'> 

+</span><span style='mso-spacerun:yes'>       </span><span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>63<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="3F" w:st="on">3F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>  

+</span>1456 ]<span style='mso-spacerun:yes'>  </span>interrogação<span

+style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>64<span style='mso-spacerun:yes'> 

+</span>40<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>   </span>4<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'> </span>]<span style='mso-spacerun:yes'>  </span>e

+comercial<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>65<span style='mso-spacerun:yes'> 

+</span>41<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1<span style='mso-spacerun:yes'>    </span>]<span

+style='mso-spacerun:yes'>  </span>maiusculo a <span

+style='mso-spacerun:yes'>                               </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>66<span style='mso-spacerun:yes'> 

+</span>42<span style='mso-spacerun:yes'>  </span>[7 21<span

+style='mso-spacerun:yes'>    </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+b<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>67<span style='mso-spacerun:yes'> 

+</span>43<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>14<span style='mso-spacerun:yes'>   </span>]<span

+style='mso-spacerun:yes'>  </span>maiusculo c<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>68<span style='mso-spacerun:yes'> 

+</span>44<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>145<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>maiusculo d<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>69<span style='mso-spacerun:yes'> 

+</span>45<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1 5<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>maiusculo e<span

+style='mso-spacerun:yes'>        </span><span

+style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>70<span style='mso-spacerun:yes'> 

+</span>46<span style='mso-spacerun:yes'>  </span>[7 214<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+f<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>71<span style='mso-spacerun:yes'> 

+</span>47<span style='mso-spacerun:yes'>  </span>[7 2145<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+g<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>72<span style='mso-spacerun:yes'> 

+</span>48<span style='mso-spacerun:yes'>  </span>[7 21 5<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+h<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>73<span style='mso-spacerun:yes'> 

+</span>49<span style='mso-spacerun:yes'>  </span>[7 2 4 <span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+i<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>74<span style='mso-spacerun:yes'> 

+</span>4A<span style='mso-spacerun:yes'>  </span>[7 2 45<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+j<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>75<span style='mso-spacerun:yes'> 

+</span>4B<span style='mso-spacerun:yes'>  </span>[73 1<span

+style='mso-spacerun:yes'>    </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+k<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>76<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="4C" w:st="on">4C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7321<span style='mso-spacerun:yes'>   

+</span>]<span style='mso-spacerun:yes'>  </span>maiusculo l<span

+style='mso-spacerun:yes'>                              </span><span

+style='mso-spacerun:yes'>  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>77<span style='mso-spacerun:yes'> 

+</span>4D<span style='mso-spacerun:yes'>  </span>[73 14<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+m<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>78<span style='mso-spacerun:yes'> 

+</span>4E<span style='mso-spacerun:yes'>  </span>[73 145<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+n<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>79<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="4F" w:st="on">4F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[73 1 5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>maiusculo o<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>80<span style='mso-spacerun:yes'> 

+</span>50<span style='mso-spacerun:yes'>  </span>[73214<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+p<span style='mso-spacerun:yes'>      </span><span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>81<span style='mso-spacerun:yes'> 

+</span>51<span style='mso-spacerun:yes'>  </span>[732145<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+q<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>82<span style='mso-spacerun:yes'> 

+</span>52<span style='mso-spacerun:yes'>  </span>[7321 5<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+r<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>83<span style='mso-spacerun:yes'> 

+</span>53<span style='mso-spacerun:yes'>  </span>[732 4<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+s<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>84<span style='mso-spacerun:yes'> 

+</span>54<span style='mso-spacerun:yes'>  </span>[732 45<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>maiusculo

+t<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>85<span style='mso-spacerun:yes'> 

+</span>55<span style='mso-spacerun:yes'>  </span>[73 1<span

+style='mso-spacerun:yes'>  </span>6 ]<span style='mso-spacerun:yes'>  </span>maiusculo

+u<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>86<span style='mso-spacerun:yes'> 

+</span>56<span style='mso-spacerun:yes'>  </span>[7321<span

+style='mso-spacerun:yes'>  </span>6 ]<span style='mso-spacerun:yes'>  </span>maiusculo

+v<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>87<span style='mso-spacerun:yes'> 

+</span>57<span style='mso-spacerun:yes'>  </span>[7 2 456 ]<span

+style='mso-spacerun:yes'>  </span>maiusculo w<span

+style='mso-spacerun:yes'>                            </span><span

+style='mso-spacerun:yes'>    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>88<span style='mso-spacerun:yes'> 

+</span>58<span style='mso-spacerun:yes'>  </span>[73 14 6 ]<span

+style='mso-spacerun:yes'>  </span>maiusculo x<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>89<span style='mso-spacerun:yes'> 

+</span>59<span style='mso-spacerun:yes'>  </span>[73 1456 ]<span

+style='mso-spacerun:yes'>  </span>maiusculo y<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>90<span style='mso-spacerun:yes'>  </span>5A<span

+style='mso-spacerun:yes'>  </span>[73 1 56 ]<span style='mso-spacerun:yes'> 

+</span>maiusculo z<span

+style='mso-spacerun:yes'>                                </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>91<span style='mso-spacerun:yes'> 

+</span>5B<span style='mso-spacerun:yes'>  </span>[7 2 4 6 ]<span

+style='mso-spacerun:yes'>  </span>colchete esquerdo<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>92<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="5C" w:st="on">5C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7 21 56 ]<span style='mso-spacerun:yes'> 

+</span>barra invertida<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>93<span style='mso-spacerun:yes'> 

+</span>5D<span style='mso-spacerun:yes'>  </span>[7 21456 ]<span

+style='mso-spacerun:yes'>  </span>colchete direito<span

+style='mso-spacerun:yes'>                           </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>94<span style='mso-spacerun:yes'>  </span>5E<span

+style='mso-spacerun:yes'>  </span>[7<span style='mso-spacerun:yes'>  

+</span>45<span style='mso-spacerun:yes'>  </span>]<span

+style='mso-spacerun:yes'>  </span>acento circunflexo<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>|<span style='mso-spacerun:yes'> 

+</span>95<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="5F" w:st="on">5F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>  </span>456 ]<span style='mso-spacerun:yes'> 

+</span>sobretraço<span

+style='mso-spacerun:yes'>                                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>|<span style='mso-spacerun:yes'> 

+</span>96<span style='mso-spacerun:yes'>  </span>60<span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>   

+</span>4<span style='mso-spacerun:yes'>   </span>]<span

+style='mso-spacerun:yes'>  </span>acento grave<span

+style='mso-spacerun:yes'>                               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>97<span style='mso-spacerun:yes'> 

+</span>61<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1<span style='mso-spacerun:yes'>    </span>]<span

+style='mso-spacerun:yes'>  </span>minusculo a<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>98<span style='mso-spacerun:yes'> 

+</span>62<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>21<span style='mso-spacerun:yes'>    </span>]<span

+style='mso-spacerun:yes'>  </span>minusculo b<span

+style='mso-spacerun:yes'>                          </span><span

+style='mso-spacerun:yes'>      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>|<span

+style='mso-spacerun:yes'>  </span>99<span style='mso-spacerun:yes'> 

+</span>63<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>14<span style='mso-spacerun:yes'>   </span>]<span

+style='mso-spacerun:yes'>  </span>minusculo c<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 100<span

+style='mso-spacerun:yes'>  </span>64<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>   </span>145<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+d<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 101<span

+style='mso-spacerun:yes'>  </span>65<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>   </span>1 5<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+e<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 102<span

+style='mso-spacerun:yes'>  </span>66<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>214<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+f<span style='mso-spacerun:yes'>  </span><span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 103<span

+style='mso-spacerun:yes'>  </span>67<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2145<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+g<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 104<span

+style='mso-spacerun:yes'>  </span>68<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>21 5<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+h<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 105<span

+style='mso-spacerun:yes'>  </span>69<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 4<span

+style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'>  </span>minusculo

+i<span style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 106<span

+style='mso-spacerun:yes'>  </span>6A<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2 45<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>minusculo j<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 107<span

+style='mso-spacerun:yes'>  </span>6B<span style='mso-spacerun:yes'>  </span>[ 3

+1<span style='mso-spacerun:yes'>    </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo k<span style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 108<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="6C" w:st="on">6C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[ 321<span style='mso-spacerun:yes'>   

+</span>]<span style='mso-spacerun:yes'>  </span>minusculo l<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 109<span

+style='mso-spacerun:yes'>  </span>6D<span style='mso-spacerun:yes'>  </span>[ 3

+14<span style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo m<span style='mso-spacerun:yes'>    </span><span

+style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>                   </span><span

+style='mso-spacerun:yes'>        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 110<span

+style='mso-spacerun:yes'>  </span>6E<span style='mso-spacerun:yes'>  </span>[ 3

+145<span style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo n<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 111<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="6F" w:st="on">6F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[ 3 1 5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>minusculo o<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 112<span

+style='mso-spacerun:yes'>  </span>70<span style='mso-spacerun:yes'>  </span>[

+3214<span style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo p<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 113<span

+style='mso-spacerun:yes'>  </span>71<span style='mso-spacerun:yes'>  </span>[

+32145<span style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo q<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 114<span

+style='mso-spacerun:yes'>  </span>72<span style='mso-spacerun:yes'>  </span>[

+321 5<span style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo r<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 115<span

+style='mso-spacerun:yes'>  </span>73<span style='mso-spacerun:yes'>  </span>[

+32 4<span style='mso-spacerun:yes'>   </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo s<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 116<span

+style='mso-spacerun:yes'>  </span>74<span style='mso-spacerun:yes'>  </span>[

+32 45<span style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'> 

+</span>minusculo t<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 117<span

+style='mso-spacerun:yes'>  </span>75 <span style='mso-spacerun:yes'> </span>[ 3

+1<span style='mso-spacerun:yes'>  </span>6 ]<span style='mso-spacerun:yes'> 

+</span>minusculo u<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 118<span

+style='mso-spacerun:yes'>  </span>76<span style='mso-spacerun:yes'>  </span>[

+321<span style='mso-spacerun:yes'>  </span>6 ]<span style='mso-spacerun:yes'> 

+</span>minusculo v<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 119<span

+style='mso-spacerun:yes'>  </span>77<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 456 ]<span

+style='mso-spacerun:yes'>  </span>minusculo w<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 120<span

+style='mso-spacerun:yes'>  </span>78<span style='mso-spacerun:yes'>  </span>[ 3

+14 6 ]<span style='mso-spacerun:yes'>  </span>minusculo x<span

+style='mso-spacerun:yes'>                      </span><span

+style='mso-spacerun:yes'>          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 121<span

+style='mso-spacerun:yes'>  </span>79<span style='mso-spacerun:yes'>  </span>[ 3

+1456 ]<span style='mso-spacerun:yes'>  </span>minusculo y<span

+style='mso-spacerun:yes'>   </span><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 122<span

+style='mso-spacerun:yes'>  </span>7A<span style='mso-spacerun:yes'>  </span>[ 3

+1 56 ]<span style='mso-spacerun:yes'>  </span>minusculo z<span

+style='mso-spacerun:yes'>                                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 123<span

+style='mso-spacerun:yes'>  </span>7B<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 4 6 ]<span

+style='mso-spacerun:yes'>  </span>chave esquerda<span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 124<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="7C" w:st="on">7C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>  </span>21

+56 ]<span style='mso-spacerun:yes'>  </span>barra vertical<span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 125<span

+style='mso-spacerun:yes'>  </span>7D<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>21456 ]<span

+style='mso-spacerun:yes'>  </span>chave direita<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 126<span

+style='mso-spacerun:yes'>  </span>7E<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>    </span>45<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'> 

+</span>acento til<span style='mso-spacerun:yes'>   </span><span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 127<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="7F" w:st="on">7F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[7<span style='mso-spacerun:yes'>  

+</span>456 ]<span style='mso-spacerun:yes'>  </span>DEL (delete)<span

+style='mso-spacerun:yes'>                               </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 128<span style='mso-spacerun:yes'>  </span>80<span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>   

+</span>4<span style='mso-spacerun:yes'>  </span>8]<span

+style='mso-spacerun:yes'>  </span>&lt;control&gt;<span

+style='mso-spacerun:yes'>                                  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 129<span style='mso-spacerun:yes'> 

+</span>81<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1<span style='mso-spacerun:yes'>  

+</span>8]<span style='mso-spacerun:yes'>  </span>&lt;control&gt;<span

+style='mso-spacerun:yes'>                                  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 130<span style='mso-spacerun:yes'> 

+</span>82<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>21<span style='mso-spacerun:yes'>  

+</span>8]<span style='mso-spacerun:yes'>  </span>BPH (break permitted

+here)<span style='mso-spacerun:yes'>                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 131<span style='mso-spacerun:yes'> 

+</span>83<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>14<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>NBH (no break here)<span

+style='mso-spacerun:yes'>            </span><span

+style='mso-spacerun:yes'>            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 132<span style='mso-spacerun:yes'> 

+</span>84<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>145 8]<span style='mso-spacerun:yes'> 

+</span>&lt;control&gt;<span

+style='mso-spacerun:yes'>                                  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 133<span

+style='mso-spacerun:yes'>  </span>85<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>   </span>1 5 8]<span

+style='mso-spacerun:yes'>  </span>NL (proxima linha)<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 134<span

+style='mso-spacerun:yes'>  </span>86<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>214<span

+style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'>  </span>SSA

+(começa a seleção de area)<span style='mso-spacerun:yes'>             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 135<span

+style='mso-spacerun:yes'>  </span>87<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2145 8]<span

+style='mso-spacerun:yes'>  </span>ESA (fim de seleção de area)<span

+style='mso-spacerun:yes'>               </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 136<span

+style='mso-spacerun:yes'>  </span>88<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>21 5 8]<span

+style='mso-spacerun:yes'>  </span>CTS (seta tabulação de caracter)<span

+style='mso-spacerun:yes'>           </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 137<span

+style='mso-spacerun:yes'>  </span>89<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 4<span

+style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'>  </span>CTJ

+(justifica tabulação de caracter)<span style='mso-spacerun:yes'>   </span><span

+style='mso-spacerun:yes'>   </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 138<span

+style='mso-spacerun:yes'>  </span>8A<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 45 8]<span

+style='mso-spacerun:yes'>  </span>LTS (seta a tabulação de linha)<span

+style='mso-spacerun:yes'>            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 139 <span

+style='mso-spacerun:yes'> </span>8B<span style='mso-spacerun:yes'>  </span>[ 3

+1<span style='mso-spacerun:yes'>   </span>8]<span style='mso-spacerun:yes'> 

+</span>PLD (linha parcial para baixo)<span

+style='mso-spacerun:yes'>             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 140<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="8C" w:st="on">8C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[ 321<span style='mso-spacerun:yes'>  

+</span>8]<span style='mso-spacerun:yes'>  </span>PLU (linha parcial para cima)<span

+style='mso-spacerun:yes'>              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 141<span style='mso-spacerun:yes'> 

+</span>8D<span style='mso-spacerun:yes'>  </span>[ 3 14<span

+style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'>  </span>RLF

+(reverse line feed)<span style='mso-spacerun:yes'>                    </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 142<span style='mso-spacerun:yes'> 

+</span>8E<span style='mso-spacerun:yes'>  </span>[ 3 145 8]<span

+style='mso-spacerun:yes'>  </span>SS2 (single shift two)<span

+style='mso-spacerun:yes'>       </span><span

+style='mso-spacerun:yes'>              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 143<span style='mso-spacerun:yes'>  </span><st1:metricconverter

+ProductID="8F" w:st="on">8F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[ 3 1 5 8]<span style='mso-spacerun:yes'> 

+</span>SS3 (single shift three)<span

+style='mso-spacerun:yes'>                   </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 144<span

+style='mso-spacerun:yes'>  </span>90<span style='mso-spacerun:yes'>  </span>[

+3214<span style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'> 

+</span>DCS (controle de dispositivo de string)<span

+style='mso-spacerun:yes'>    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 145<span

+style='mso-spacerun:yes'>  </span>91<span style='mso-spacerun:yes'>  </span>[

+32145 8]<span style='mso-spacerun:yes'>  </span>PU1 (uso privado um)<span

+style='mso-spacerun:yes'>  </span><span style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>                    </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 146<span

+style='mso-spacerun:yes'>  </span>92<span style='mso-spacerun:yes'>  </span>[

+321 5 8]<span style='mso-spacerun:yes'>  </span>PU2 (uso privado dois)<span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 147<span

+style='mso-spacerun:yes'>  </span>93<span style='mso-spacerun:yes'>  </span>[

+32 4<span style='mso-spacerun:yes'>  </span>8]<span style='mso-spacerun:yes'> 

+</span>STS (seta estado de transmissão)<span

+style='mso-spacerun:yes'>           </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 148<span style='mso-spacerun:yes'> 

+</span>94<span style='mso-spacerun:yes'>  </span>[ 32 45 8]<span

+style='mso-spacerun:yes'>  </span>CC (cancela caracter)<span

+style='mso-spacerun:yes'>                      </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 149<span style='mso-spacerun:yes'> 

+</span>95<span style='mso-spacerun:yes'>  </span>[ 3 1<span

+style='mso-spacerun:yes'>  </span>68]<span style='mso-spacerun:yes'>  </span>MW

+(message waiting)<span style='mso-spacerun:yes'>                       </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 150<span

+style='mso-spacerun:yes'>  </span>96<span style='mso-spacerun:yes'>  </span>[

+321<span style='mso-spacerun:yes'>  </span>68]<span style='mso-spacerun:yes'> 

+</span>SGA (inicio da area protegida)<span

+style='mso-spacerun:yes'>             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 151<span

+style='mso-spacerun:yes'>  </span>97<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 4568]<span

+style='mso-spacerun:yes'>  </span>EGA (fim da area protegida)<span

+style='mso-spacerun:yes'>                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 152<span

+style='mso-spacerun:yes'>  </span>98<span style='mso-spacerun:yes'>  </span>[ 3

+14 68]<span style='mso-spacerun:yes'>  </span>SS (inicio da string)<span

+style='mso-spacerun:yes'>                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 153<span

+style='mso-spacerun:yes'>  </span>99<span style='mso-spacerun:yes'>  </span>[ 3

+14568]<span style='mso-spacerun:yes'>  </span>&lt;controle&gt;<span

+style='mso-spacerun:yes'>                 </span><span

+style='mso-spacerun:yes'>                </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 154<span

+style='mso-spacerun:yes'>  </span>9A<span style='mso-spacerun:yes'>  </span>[ 3

+1 568]<span style='mso-spacerun:yes'>  </span>SCI (introdução do caracter unico)<span

+style='mso-spacerun:yes'>         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 155<span

+style='mso-spacerun:yes'>  </span>9B<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>2 4 68]<span

+style='mso-spacerun:yes'>  </span>CSI (introdução de controle de sequencia)<span

+style='mso-spacerun:yes'>  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 156<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="9C" w:st="on">9C</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>  </span>21

+568]<span style='mso-spacerun:yes'>  </span>ST (terminador de string)<span

+style='mso-spacerun:yes'>                  </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 157<span

+style='mso-spacerun:yes'>  </span>9D<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>214568]<span

+style='mso-spacerun:yes'>  </span>OSC (comando de operação de sistema)<span

+style='mso-spacerun:yes'>       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 158<span

+style='mso-spacerun:yes'>  </span>9E<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>    </span>45 8]<span

+style='mso-spacerun:yes'>  </span>PM (mensagem privada)<span

+style='mso-spacerun:yes'>                      </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 159<span

+style='mso-spacerun:yes'>  </span><st1:metricconverter ProductID="9F" w:st="on">9F</st1:metricconverter><span

+style='mso-spacerun:yes'>  </span>[<span style='mso-spacerun:yes'>   

+</span>4568]<span style='mso-spacerun:yes'>  </span>APC (comando de programa de

+aplicação)<span style='mso-spacerun:yes'>     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 160<span

+style='mso-spacerun:yes'>  </span>A0<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>      </span>8]<span

+style='mso-spacerun:yes'>  </span>sem quebra de espaço<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 161<span

+style='mso-spacerun:yes'>  </span>A1<span style='mso-spacerun:yes'> 

+</span>[732 4 6 ]<span style='mso-spacerun:yes'>  </span>ponto de exclamação

+invertido<span style='mso-tab-count:2'>           </span><span

+style='mso-spacerun:yes'>   </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 162<span

+style='mso-spacerun:yes'>  </span>A2<span style='mso-spacerun:yes'>  </span>[7

+214 6 ]<span style='mso-spacerun:yes'>  </span>simbolo do centavo<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 163<span

+style='mso-spacerun:yes'>  </span>A3<span style='mso-spacerun:yes'> 

+</span>[73<span style='mso-spacerun:yes'>  </span>456 ]<span

+style='mso-spacerun:yes'>  </span>simbolo da libra<span

+style='mso-spacerun:yes'>                           </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 164<span

+style='mso-spacerun:yes'>  </span>A4<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>  </span>14 6 ]<span

+style='mso-spacerun:yes'>  </span>símbolo da moeda<span

+style='mso-spacerun:yes'>                  </span><span

+style='mso-spacerun:yes'>         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 165<span

+style='mso-spacerun:yes'>  </span>A5<span style='mso-spacerun:yes'> 

+</span>[73214 6 ]<span style='mso-spacerun:yes'>  </span>simbolo do yen<span

+style='mso-spacerun:yes'>                             </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 166<span style='mso-spacerun:yes'> 

+</span>A6<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1 56 ]<span style='mso-spacerun:yes'> 

+</span>broken bar<span

+style='mso-spacerun:yes'>                                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 167<span style='mso-spacerun:yes'> 

+</span>A7<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>   </span>5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>section sign<span

+style='mso-spacerun:yes'>                               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 168<span

+style='mso-spacerun:yes'>  </span>A8<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>    </span>5<span

+style='mso-spacerun:yes'>  </span>]<span style='mso-spacerun:yes'>  </span>trema<span

+style='mso-spacerun:yes'>     </span><span

+style='mso-spacerun:yes'>            </span><span

+style='mso-spacerun:yes'>    </span><span

+style='mso-spacerun:yes'>                 </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 169<span

+style='mso-spacerun:yes'>  </span>A9<span style='mso-spacerun:yes'> 

+</span>[732<span style='mso-spacerun:yes'>  </span>56 ]<span

+style='mso-spacerun:yes'>  </span>simbolo de copyright <span

+style='mso-spacerun:yes'> </span><span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 170<span style='mso-spacerun:yes'> 

+</span>AA<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>       </span>8]<span style='mso-spacerun:yes'> 

+</span>feminine ordinal indicator<span

+style='mso-spacerun:yes'>                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 171<span style='mso-spacerun:yes'> 

+</span>AB<span style='mso-spacerun:yes'>  </span>[7 21<span

+style='mso-spacerun:yes'>  </span>6 ]<span style='mso-spacerun:yes'> 

+</span>left-pointing double angle quotation mark<span

+style='mso-spacerun:yes'>  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 172<span style='mso-spacerun:yes'> 

+</span>AC<span style='mso-spacerun:yes'>  </span>[7 2<span

+style='mso-spacerun:yes'>  </span>56 ]<span style='mso-spacerun:yes'> 

+</span>not sign<span

+style='mso-spacerun:yes'>                                   </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 173<span style='mso-spacerun:yes'> 

+</span>AD<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>    </span>6 ]<span style='mso-spacerun:yes'> 

+</span>soft hyphen<span

+style='mso-spacerun:yes'>                                </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 174<span style='mso-spacerun:yes'> 

+</span>AE<span style='mso-spacerun:yes'>  </span>[732<span

+style='mso-spacerun:yes'>   </span>6 ]<span style='mso-spacerun:yes'> 

+</span>registered sign<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 175<span style='mso-spacerun:yes'> 

+</span>AF<span style='mso-spacerun:yes'>  </span>[7 2<span

+style='mso-spacerun:yes'>   </span>6 ]<span style='mso-spacerun:yes'> 

+</span>macron<span style='mso-spacerun:yes'>                          </span><span

+style='mso-spacerun:yes'>           </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 176<span style='mso-spacerun:yes'> 

+</span>B0<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>   </span>56 ]<span style='mso-spacerun:yes'> 

+</span>degree sign<span

+style='mso-spacerun:yes'>                                </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 177<span style='mso-spacerun:yes'> 

+</span>B1<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>4 6 ]<span style='mso-spacerun:yes'> 

+</span>plus-minus sign<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 178<span style='mso-spacerun:yes'> 

+</span>B2<span style='mso-spacerun:yes'>  </span>[732<span

+style='mso-spacerun:yes'>     </span>]<span style='mso-spacerun:yes'> 

+</span>superscript two<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 179<span style='mso-spacerun:yes'> 

+</span>B3<span style='mso-spacerun:yes'>  </span>[7 2<span

+style='mso-spacerun:yes'>  </span>5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>superscript three<span

+style='mso-spacerun:yes'>                          </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 180<span style='mso-spacerun:yes'> 

+</span>B4<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>      </span>]<span style='mso-spacerun:yes'> 

+</span>acute accent<span

+style='mso-spacerun:yes'>                               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 181<span style='mso-spacerun:yes'> 

+</span>B5<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>    </span>56 ]<span style='mso-spacerun:yes'> 

+</span>micro sign<span

+style='mso-spacerun:yes'>                                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 182<span style='mso-spacerun:yes'> 

+</span>B6<span style='mso-spacerun:yes'>  </span>[732<span

+style='mso-spacerun:yes'>  </span>5<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>pilcrow sign<span

+style='mso-spacerun:yes'>                               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 183<span style='mso-spacerun:yes'>  </span>B7<span

+style='mso-spacerun:yes'>  </span>[7<span style='mso-spacerun:yes'>   </span>4

+6 ]<span style='mso-spacerun:yes'>  </span>middle dot<span

+style='mso-spacerun:yes'>                                 </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 184<span style='mso-spacerun:yes'> 

+</span>B8<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>     </span>6 ]<span style='mso-spacerun:yes'> 

+</span>cedilha<span

+style='mso-spacerun:yes'>                                    </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 185<span style='mso-spacerun:yes'> 

+</span>B9<span style='mso-spacerun:yes'>  </span>[7 2<span

+style='mso-spacerun:yes'>     </span>]<span style='mso-spacerun:yes'> 

+</span>superscript one<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 186<span style='mso-spacerun:yes'> 

+</span>BA<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>       </span>]<span style='mso-spacerun:yes'> 

+</span>masculine ordinal indicator<span style='mso-spacerun:yes'>   </span><span

+style='mso-spacerun:yes'>             </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 187<span style='mso-spacerun:yes'> 

+</span>BB<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>45<span style='mso-spacerun:yes'> 

+</span>]<span style='mso-spacerun:yes'>  </span>right-pointing double angle

+quotation mark |<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 188<span style='mso-spacerun:yes'> 

+</span>BC<span style='mso-spacerun:yes'>  </span>[7321 56 ]<span

+style='mso-spacerun:yes'>  </span>vulgar fraction one quarter<span

+style='mso-spacerun:yes'>                </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 189<span style='mso-spacerun:yes'> 

+</span>BD<span style='mso-spacerun:yes'>  </span>[7321456 ]<span

+style='mso-spacerun:yes'>  </span>vulgar fraction one half<span

+style='mso-spacerun:yes'>                   </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 190<span style='mso-spacerun:yes'> 

+</span>BE<span style='mso-spacerun:yes'>  </span>[732 456 ]<span

+style='mso-spacerun:yes'>  </span>vulgar fraction three quarters<span

+style='mso-spacerun:yes'>             </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 191<span style='mso-spacerun:yes'> 

+</span>BF<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1456 ]<span style='mso-spacerun:yes'> 

+</span>interrogação invertida<span

+style='mso-spacerun:yes'>                     </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 192<span style='mso-spacerun:yes'> 

+</span>C0<span style='mso-spacerun:yes'>  </span>[732<span

+style='mso-spacerun:yes'>  </span>5 8]<span style='mso-spacerun:yes'> 

+</span>capital a grave<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>| 193<span style='mso-spacerun:yes'> 

+</span>C1<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1<span style='mso-spacerun:yes'> 

+</span>68]<span style='mso-spacerun:yes'>  </span>capital a acute<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 194<span

+style='mso-spacerun:yes'>  </span>C2<span style='mso-spacerun:yes'>  </span>[7

+2<span style='mso-spacerun:yes'>    </span>8]<span style='mso-spacerun:yes'> 

+</span>capital a circumflex<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 195<span

+style='mso-spacerun:yes'>  </span>C3<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>    </span>5 8]<span

+style='mso-spacerun:yes'>  </span>capital a tilde<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 196<span

+style='mso-spacerun:yes'>  </span>C4<span style='mso-spacerun:yes'> 

+</span>[73214 68]<span style='mso-spacerun:yes'>  </span>capital a

+diaeresis<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 197<span style='mso-spacerun:yes'> 

+</span>C5<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>45 8]<span style='mso-spacerun:yes'> 

+</span>capital a ring above<span style='mso-spacerun:yes'>        </span><span

+style='mso-spacerun:yes'>               </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>| 198<span style='mso-spacerun:yes'> 

+</span>C6<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>     </span>8]<span style='mso-spacerun:yes'> 

+</span>capital ae<span

+style='mso-spacerun:yes'>                                 </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 199<span

+style='mso-spacerun:yes'>  </span>C7<span style='mso-spacerun:yes'> 

+</span>[73<span style='mso-spacerun:yes'>  </span>4 68]<span

+style='mso-spacerun:yes'>  </span>capital c cedilla<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 200<span

+style='mso-spacerun:yes'>  </span>C8<span style='mso-spacerun:yes'> 

+</span>[732<span style='mso-spacerun:yes'>  </span>568]<span

+style='mso-spacerun:yes'>  </span>capital e grave<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 201<span

+style='mso-spacerun:yes'>  </span>C9<span style='mso-spacerun:yes'>  </span>[7

+21<span style='mso-spacerun:yes'>  </span>68]<span style='mso-spacerun:yes'> 

+</span>capital e acute<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 202<span

+style='mso-spacerun:yes'>  </span>CA<span style='mso-spacerun:yes'> 

+</span>[732<span style='mso-spacerun:yes'>    </span>8]<span

+style='mso-spacerun:yes'>  </span>capital e circumflex<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 203<span

+style='mso-spacerun:yes'>  </span>CB<span style='mso-spacerun:yes'> 

+</span>[73214568]<span style='mso-spacerun:yes'>  </span>capital e

+diaeresis<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 204<span

+style='mso-spacerun:yes'>  </span>CC<span style='mso-spacerun:yes'> 

+</span>[732<span style='mso-spacerun:yes'>   </span>68]<span

+style='mso-spacerun:yes'>  </span>capital i grave<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 205<span

+style='mso-spacerun:yes'>  </span>CD<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>  </span>14 68]<span

+style='mso-spacerun:yes'>  </span>capital i acute<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 206<span

+style='mso-spacerun:yes'>  </span>CE<span style='mso-spacerun:yes'>  </span>[7

+2<span style='mso-spacerun:yes'>  </span>5 8]<span style='mso-spacerun:yes'> 

+</span>capital i circumflex<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 207<span

+style='mso-spacerun:yes'>  </span>CF<span style='mso-spacerun:yes'> 

+</span>[7321 568]<span style='mso-spacerun:yes'>  </span>capital i

+diaeresis<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 208<span

+style='mso-spacerun:yes'>  </span>D0<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>     </span>68]<span

+style='mso-spacerun:yes'>  </span>capital eth<span

+style='mso-spacerun:yes'>               </span><span

+style='mso-spacerun:yes'>                 </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 209<span

+style='mso-spacerun:yes'>  </span>D1<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>   </span>4 68]<span

+style='mso-spacerun:yes'>  </span>capital n tilde<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 210<span

+style='mso-spacerun:yes'>  </span>D2<span style='mso-spacerun:yes'> 

+</span>[73<span style='mso-spacerun:yes'>   </span>5 8]<span

+style='mso-spacerun:yes'>  </span>capital o grave<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 211<span

+style='mso-spacerun:yes'>  </span>D3<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>  </span>14568]<span

+style='mso-spacerun:yes'>  </span>capital o acute<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 212<span

+style='mso-spacerun:yes'>  </span>D4<span style='mso-spacerun:yes'>  </span>[7

+2<span style='mso-spacerun:yes'>  </span>568]<span style='mso-spacerun:yes'> 

+</span>capital o circumflex<span

+style='mso-spacerun:yes'>                       </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 213<span

+style='mso-spacerun:yes'>  </span>D5<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>    </span>568]<span

+style='mso-spacerun:yes'>  </span>capital o tilde<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 214<span

+style='mso-spacerun:yes'>  </span>D6<span style='mso-spacerun:yes'> 

+</span>[732 4 68]<span style='mso-spacerun:yes'>  </span>capital o

+diaeresis<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 215<span style='mso-spacerun:yes'> 

+</span>D7<span style='mso-spacerun:yes'>  </span>[7<span

+style='mso-spacerun:yes'>  </span>1<span style='mso-spacerun:yes'>  </span>6

+]<span style='mso-spacerun:yes'>  </span>simbolo de multiplicação<span

+style='mso-spacerun:yes'>                   </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 216<span style='mso-spacerun:yes'> 

+</span>D8<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>4<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>capital o stroke<span

+style='mso-spacerun:yes'>                           </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>| 217<span style='mso-spacerun:yes'> 

+</span>D9<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>   </span>568]<span style='mso-spacerun:yes'> 

+</span>capital u grave<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 218<span

+style='mso-spacerun:yes'>  </span>DA<span style='mso-spacerun:yes'> 

+</span>[7<span style='mso-spacerun:yes'>  </span>1 568]<span

+style='mso-spacerun:yes'>  </span>capital u acute<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 219<span

+style='mso-spacerun:yes'>  </span>DB<span style='mso-spacerun:yes'>  </span>[7

+2<span style='mso-spacerun:yes'>   </span>68]<span style='mso-spacerun:yes'> 

+</span>capital u circumflex<span style='mso-spacerun:yes'>    </span><span

+style='mso-spacerun:yes'>                   </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 220<span

+style='mso-spacerun:yes'>  </span>DC<span style='mso-spacerun:yes'> 

+</span>[732 4568]<span style='mso-spacerun:yes'>  </span>capital u

+diaeresis<span style='mso-spacerun:yes'>                        </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 221<span style='mso-spacerun:yes'> 

+</span>DD<span style='mso-spacerun:yes'>  </span>[7 214 68]<span

+style='mso-spacerun:yes'>  </span>capital y acute<span

+style='mso-spacerun:yes'>                            </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 222<span style='mso-spacerun:yes'> 

+</span>DE<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>    </span>68]<span style='mso-spacerun:yes'> 

+</span>capital thorn<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 223<span style='mso-spacerun:yes'> 

+</span>DF<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>4568]<span style='mso-spacerun:yes'>  </span>small

+sharp s<span style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 224<span style='mso-spacerun:yes'> 

+</span>E0<span style='mso-spacerun:yes'>  </span>[ 32<span

+style='mso-spacerun:yes'>  </span>5 8]<span style='mso-spacerun:yes'> 

+</span>small a grave<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 225<span style='mso-spacerun:yes'> 

+</span>E1<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1<span style='mso-spacerun:yes'> 

+</span>68]<span style='mso-spacerun:yes'>  </span>small a acute<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 226<span style='mso-spacerun:yes'> 

+</span>E2<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>   

+</span>8]<span style='mso-spacerun:yes'>  </span>small a circumflex<span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'> </span><span style='mso-spacerun:yes'> </span>|

+227<span style='mso-spacerun:yes'>  </span>E3<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>     </span>5 8]<span

+style='mso-spacerun:yes'>  </span>small a tilde<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 228<span style='mso-spacerun:yes'> 

+</span>E4<span style='mso-spacerun:yes'>  </span>[ 3214 68]<span

+style='mso-spacerun:yes'>  </span>small a diaeresis<span

+style='mso-spacerun:yes'>                          </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 229<span style='mso-spacerun:yes'> 

+</span>E5<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>45 8]<span style='mso-spacerun:yes'> 

+</span>small a ring above<span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>| 230<span style='mso-spacerun:yes'> 

+</span>E6<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>     </span>8]<span style='mso-spacerun:yes'> 

+</span>small ae<span style='mso-spacerun:yes'>              </span><span

+style='mso-spacerun:yes'>                     </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 231<span

+style='mso-spacerun:yes'>  </span>E7<span style='mso-spacerun:yes'>  </span>[

+3<span style='mso-spacerun:yes'>  </span>4 68]<span style='mso-spacerun:yes'> 

+</span>small c cedilla<span

+style='mso-spacerun:yes'>                            </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 232<span

+style='mso-spacerun:yes'>  </span>E8<span style='mso-spacerun:yes'>  </span>[

+32<span style='mso-spacerun:yes'>  </span>568]<span style='mso-spacerun:yes'> 

+</span>small e grave<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 233<span

+style='mso-spacerun:yes'>  </span>E9<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>  </span>21<span

+style='mso-spacerun:yes'>  </span>68]<span style='mso-spacerun:yes'>  </span>small

+e acute<span style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 234<span

+style='mso-spacerun:yes'>  </span>EA<span style='mso-spacerun:yes'>  </span>[

+32<span style='mso-spacerun:yes'>    </span>8]<span style='mso-spacerun:yes'> 

+</span>small e circumflex<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 235<span

+style='mso-spacerun:yes'>  </span>EB<span style='mso-spacerun:yes'>  </span>[

+3214568]<span style='mso-spacerun:yes'>  </span>small e diaeresis<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 236<span

+style='mso-spacerun:yes'>  </span>EC<span style='mso-spacerun:yes'>  </span>[

+32<span style='mso-spacerun:yes'>   </span>68]<span style='mso-spacerun:yes'> 

+</span>small i grave<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 237<span style='mso-spacerun:yes'> 

+</span>ED<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>14 68]<span style='mso-spacerun:yes'> 

+</span>small i acute<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 238<span style='mso-spacerun:yes'> 

+</span>EE<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>  </span>5

+8]<span style='mso-spacerun:yes'>  </span>small i circumflex<span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 239<span style='mso-spacerun:yes'> 

+</span>EF<span style='mso-spacerun:yes'>  </span>[ 321 568]<span

+style='mso-spacerun:yes'>  </span>small i diaeresis<span

+style='mso-spacerun:yes'>                          </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 240<span style='mso-spacerun:yes'> 

+</span>F0<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>      </span>68]<span style='mso-spacerun:yes'> 

+</span>small eth<span

+style='mso-spacerun:yes'>                                  </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 241<span style='mso-spacerun:yes'> 

+</span>F1<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>    </span>4 68]<span style='mso-spacerun:yes'> 

+</span>small n tilde<span style='mso-spacerun:yes'>       </span><span

+style='mso-spacerun:yes'>                       </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 242<span style='mso-spacerun:yes'> 

+</span>F2<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>   </span>5 8]<span style='mso-spacerun:yes'> 

+</span>small o grave<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 243<span style='mso-spacerun:yes'> 

+</span>F3<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>14568]<span style='mso-spacerun:yes'> 

+</span>small o acute<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span></span>| 244<span style='mso-spacerun:yes'> 

+</span>F4<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'> 

+</span>568]<span style='mso-spacerun:yes'>  </span>small o circumflex<span

+style='mso-spacerun:yes'>                         </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 245<span

+style='mso-spacerun:yes'>  </span>F5<span style='mso-spacerun:yes'> 

+</span>[<span style='mso-spacerun:yes'>     </span>568]<span

+style='mso-spacerun:yes'>  </span>small o tilde<span

+style='mso-spacerun:yes'>                              </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span>| 246<span

+style='mso-spacerun:yes'>  </span>F6<span style='mso-spacerun:yes'>  </span>[

+32 4 68]<span style='mso-spacerun:yes'>  </span>small o diaeresis<span

+style='mso-spacerun:yes'>                          </span>|</p>

+

+<p class=MsoPlainText><span style='mso-spacerun:yes'>  </span><span lang=EN-US

+style='mso-ansi-language:EN-US'>| 247<span style='mso-spacerun:yes'> 

+</span>F7<span style='mso-spacerun:yes'>  </span>[73<span

+style='mso-spacerun:yes'>  </span>4<span style='mso-spacerun:yes'>  

+</span>]<span style='mso-spacerun:yes'>  </span>simbolo de divisão<span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 248<span style='mso-spacerun:yes'> 

+</span>F8<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>4<span style='mso-spacerun:yes'> 

+</span>8]<span style='mso-spacerun:yes'>  </span>small o stroke<span

+style='mso-spacerun:yes'>                            </span><span

+style='mso-spacerun:yes'> </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 249<span style='mso-spacerun:yes'> 

+</span>F9<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>   </span>568]<span style='mso-spacerun:yes'> 

+</span>small u grave<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 250<span style='mso-spacerun:yes'> 

+</span>FA<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>   </span>1 568]<span style='mso-spacerun:yes'> 

+</span>small u acute<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 251<span style='mso-spacerun:yes'> 

+</span>FB<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>2<span style='mso-spacerun:yes'>  

+</span>68]<span style='mso-spacerun:yes'>  </span>small u circumflex<span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 252<span style='mso-spacerun:yes'> 

+</span>FC<span style='mso-spacerun:yes'>  </span>[ 32 4568]<span

+style='mso-spacerun:yes'>  </span>small u diaeresis <span

+style='mso-spacerun:yes'>                         </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 253<span style='mso-spacerun:yes'> 

+</span>FD<span style='mso-spacerun:yes'>  </span>[<span

+style='mso-spacerun:yes'>  </span>214 68]<span style='mso-spacerun:yes'> 

+</span>small y acute<span

+style='mso-spacerun:yes'>                              </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 254<span style='mso-spacerun:yes'> 

+</span>FE<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>    </span>68]<span style='mso-spacerun:yes'> 

+</span>small thorn<span

+style='mso-spacerun:yes'>                                </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>| 255<span style='mso-spacerun:yes'> 

+</span>FF<span style='mso-spacerun:yes'>  </span>[ 3<span

+style='mso-spacerun:yes'>  </span>4568]<span style='mso-spacerun:yes'> 

+</span>small y diaeresis<span

+style='mso-spacerun:yes'>                          </span>|<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><span

+style='mso-spacerun:yes'>  </span>+-----------------------------------------------------------------+<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'>H.

+Tabela de Instrumento MIDI<o:p></o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<div align=center>

+

+<table class=MsoNormalTable border=1 cellpadding=0 style='mso-cellspacing:1.5pt;

+ border:solid windowtext 1.0pt'>

+ <tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Piano<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>0<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Acoustic Grand Piano<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:1'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>1<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Bright Acoustic Piano<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:2'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>2<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Electric Grand Piano<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:3'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>3<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Honkytonk Piano<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:4'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>4<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Electric Piano 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:5'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>5<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Electric Piano 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:6'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>6<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Harpsichord<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:7'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>7<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Clavi<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:8'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Chromatic Percussion<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>8<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Celesta<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:9'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>9<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Glockenspiel<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:10'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>10<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Music Box<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:11'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>11<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Vibraphone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:12'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>12<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Marimba<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:13'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>13<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Xylophone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:14'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>14<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tubular Bells<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:15'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>15<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Dulcimer<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:16'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Organ<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>16<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Drawbar Organ<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:17'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>17<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Percussive Organ<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:18'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>18<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Rock Organ<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:19'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>19<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Church Organ<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:20'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>20<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Reed Organ<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:21'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>21<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Accordion<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:22'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>22<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Harmonica<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:23'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>23<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tango Accordion<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:24'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Guitar<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>24<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Nylon Acoustic Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:25'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>25<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Steel Acoustic Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:26'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>26<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Jazz Electric Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:27'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>27<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Clean Electric Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:28'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>28<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Muted Electric Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:29'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>29<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Overdriven Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:30'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>30<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Distortion Guitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:31'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>31<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Guitar Harmonics<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:32'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Bass<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>32<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Acoustic Bass<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:33'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>33<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Finger Electric Bass<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:34'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>34<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pick Electric Bass<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:35'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>35<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Fretless Bass<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:36'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>36<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Slap Bass 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:37'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>37<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Slap Bass 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:38'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>38<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Bass 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:39'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>39<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Bass 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:40'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Strings<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>40<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Violin<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:41'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>41<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Viola<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:42'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>42<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Cello<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:43'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>43<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Contrabass<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:44'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>44<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tremolo Strings<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:45'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>45<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pizzicato Strings<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:46'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>46<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Orchestral Harp<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:47'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>47<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Timpani<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:48'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Ensemble<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>48<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>String Ensemble 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:49'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>49<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>String Ensemble 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:50'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>50<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Strings 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:51'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>51<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Strings 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:52'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>52<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Choir Aahs<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:53'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>53<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Voice Oohs<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:54'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>54<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Voice<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:55'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>55<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Orchestra Hit<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:56'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Brass<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>56<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Trumpet<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:57'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>57<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Trombone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:58'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>58<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tuba<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:59'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>59<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Muted Trumpet<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:60'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>60<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>French Horn<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:61'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>61<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Brass Section<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:62'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>62<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Brass 1<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:63'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>63<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Brass 2<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:64'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Reed<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>64<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Soprano Saxophone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:65'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>65<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Alto Saxophone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:66'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>66<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tenor Saxophone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:67'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>67<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Baritone Saxophone<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:68'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>68<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Oboe<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:69'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>69<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>English Horn<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:70'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>70<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Bassoon<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:71'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>71<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Clarinet<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:72'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pipe<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>72<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Piccolo<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:73'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>73<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Flute<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:74'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>74<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Recorder<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:75'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>75<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pan Flute<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:76'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>76<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Blown Bottle<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:77'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>77<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Shakuhachi<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:78'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>78<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Whistle<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:79'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>79<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Ocarina<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:80'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Lead<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>80<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 1 (square)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:81'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>81<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 2 (sawtooth)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:82'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>82<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 3 (calliope)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:83'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>83<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 4 (chiff)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:84'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>84<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 5 (charang)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:85'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>85<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 6 (voice)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:86'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>86<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 7 (fifths)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:87'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>87<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Lead 8 (bass + lead)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:88'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Pad<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>88<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 1 (new age)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:89'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>89<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 2 (warm)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:90'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>90<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 3 (polysynth)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:91'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>91<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 4 (choir)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:92'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>92<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 5 (bowed)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:93'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>93<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 6 (metallic)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:94'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>94<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 7 (halo)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:95'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>95<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Pad 8 (sweep)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:96'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth FM<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>96<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 1 (rain)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:97'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>97<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 2 (soundtrack)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:98'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>98<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 3 (crystal)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:99'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>99<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 4 (atmosphere)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:100'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>100<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 5 (brightness)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:101'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>101<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 6 (goblins)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:102'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>102<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 7 (echoes)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:103'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>103<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>FX 8 (science-fiction)<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:104'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Ethnic<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>104<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Sitar<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:105'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>105<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Banjo<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:106'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>106<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Shamisen<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:107'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>107<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Koto<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:108'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>108<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Kalimba<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:109'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>109<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Bag Pipe<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:110'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>110<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Fiddle<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:111'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>111<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Shanai<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:112'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Percussive<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>112<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Tinkle Bell<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:113'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>113<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Agogo<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:114'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>114<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Steel Drum<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:115'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>115<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Wooden Block<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:116'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>116<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Taiko Drum<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:117'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>117<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Melodic Tom<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:118'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>118<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Synth Drum<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:119'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>119<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Reverse Cymbal<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:120'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Sound Effects<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>120<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Guitar Fret Noise<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:121'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>121<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Breath Noise<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:122'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>122<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Seashore<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:123'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>123<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Bird Tweet<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:124'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>124<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Telephone Ring<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:125'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>125<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Helicopter<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:126'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>126<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Applause<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:127'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>127<o:p></o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext>Gunshot<o:p></o:p></span></p>

+  </td>

+ </tr>

+ <tr style='mso-yfti-irow:128;mso-yfti-lastrow:yes'>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+  <td style='border:none;padding:.75pt .75pt .75pt .75pt'>

+  <p class=MsoPlainText><span class=shorttext><o:p>&nbsp;</o:p></span></p>

+  </td>

+ </tr>

+</table>

+

+</div>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoPlainText><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+<p class=MsoNormal><span lang=EN-US style='mso-ansi-language:EN-US'><o:p>&nbsp;</o:p></span></p>

+

+</div>

+

+</body>

+

+</html>

diff --git a/Documents/Manual-BRLTTY/Portuguese/BRLTTY.pdf b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.pdf
new file mode 100644
index 0000000..f4bbc40
--- /dev/null
+++ b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.pdf
Binary files differ
diff --git a/Documents/Manual-BRLTTY/Portuguese/BRLTTY.txt b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.txt
new file mode 100644
index 0000000..08722bf
--- /dev/null
+++ b/Documents/Manual-BRLTTY/Portuguese/BRLTTY.txt
@@ -0,0 +1,6602 @@
+  Manual de Referência do BRLTTY

+  Controle de Acesso da Tela para pessoas cegas que usam Display Braille	

+

+  Nikhil Nair <nn201@cus.cam.ac.uk>

+

+  Nicolas Pitre <nico@fluxnic.net>

+

+  Stéphane Doyon <s.doyon@videotron.ca>

+

+  Dave Mielke <dave@mielke.cc>

+

+  Tradução para a lingua Portuguesa

+

+  Regiane Mendonça Villela <regianevillela@gmail.com>

+

+  Filipe Oliveira Bernardes <filipeobernardes@gmail.com>

+

+  Version 4.3, oct 2011

+

+

+  Copyright © 1995-2011 by Desenvolvedores do BRLTTY. BRLTTY é um software

+  livre, e vem com ABSOLUTAMENTE SEM NENHUMA GARANTIA. Ele é postado sob os

+  termos da versão 2 ou posterior da GNU e publicada pela Fundação de Software

+  Livre.

+

+  ______________________________________________________________________

+

+  Tabela de Conteúdo

+

+

+  1. Formalidades

+     1.1 Licença

+     1.2 Renúncia

+     1.3 Contato

+

+  2. Introdução

+     2.1 Sumário 

+     2.2 Requisitos do Sistema

+

+  3. Processo de compilação

+     3.1 Instalação da herança de arquivos

+     3.2 Instalação a partir do Tar Ball

+        3.2.1 Opções de compilação

+           3.2.1.1 Padrões do Sistema

+           3.2.1.2 Especificações de Diretório

+           3.2.1.3 Caracteristicas de Compilação

+           3.2.1.4 Opções diversas

+        3.2.2 Atingir metas de arquivos

+     3.3 Testando o BRLTTY

+     3.4 Iniciando o BRLTTY

+     3.5 Considerações de Segurança

+     3.6 Restrições de Compilação e Tempo de Execução

+     3.7 Instalação a partir de um arquivo RPM 

+     3.8 Outras Utilidades

+        3.8.1 brltty-config

+        3.8.2 brltty-install

+        3.8.3 brltest

+        3.8.4 spktest

+        3.8.5 scrtest

+        3.8.6 ttbtest

+        3.8.7 ctbtest

+        3.8.8 tunetest

+

+  4. Usando BRLTTY

+     4.1 Comandos

+        4.1.1 Deslocamento Vertical 

+        4.1.2 Deslocamento Horizontal 

+        4.1.3 Deslocamento Implicito 

+        4.1.4 Caracteristica de Ativação

+        4.1.5 Modo de Seleção

+        4.1.6 Preferências de Manutenção

+        4.1.7 Menu de Navegação

+        4.1.8 Controles de Fala

+        4.1.9 Comutação do Terminal Virtual

+        4.1.10 Outros Comandos

+        4.1.11 Caracteres de Comandos

+        4.1.12 Base Commandos

+     4.2 Arquivos de configuração

+     4.3 Opções de linha de Comando

+

+  5. Descrições de caracteristicas

+     5.1 Roteamento de Cursor

+     5.2 Copiar e Colar

+     5.3 Suporte de Ponteiro (Mouse) via GPM

+     5.4 Alerta de Tunes

+     5.5 Configurações de Preferências 

+        5.5.1 Menu de Preferências

+           5.5.1.1 Menu de Navegação

+           5.5.1.2 Itens de Menu

+     5.6 Status do Display

+        5.6.1 Displays com 21 Células ou Mais

+        5.6.2 Displays com 20 Células ou Menos

+     5.7 Comando de Modo de Aprendizagem

+

+  6. Tabelas

+     6.1 Tabelas de Texto

+        6.1.1 Tabela de Formato de Texto

+        6.1.2 Tabela de Diretivas de Texto

+     6.2 Tabela de Atributos

+        6.2.1 Tabela de Formatos dos Atributos

+        6.2.2 Tabela de Diretórios dos Atributos 

+     6.3 Tabela de Contração

+        6.3.1 Tabela de Formato de Contração

+        6.3.2 Tabela de Operandos de Contração

+        6.3.3 Opcodes

+           6.3.3.1 Administração de Tabela

+           6.3.3.2 Definição de Símbolo Especial

+           6.3.3.3 Tradução de Caracter 

+           6.3.3.4 Classes de Caracter

+     6.4 Tabelas de Teclas

+        6.4.1 Tabela de Diretivas de Teclas

+           6.4.1.1 Diretiva de atribuição

+           6.4.1.2 Diretiva de vínculo

+           6.4.1.3 Diretiva de contexto

+           6.4.1.4 Diretiva de ocultar

+           6.4.1.5 Diretiva de Hotkey 

+           6.4.1.6 Diretiva de IfKey 

+           6.4.1.7 Diretiva de inclusão

+           6.4.1.8 Diretiva de mapa

+           6.4.1.9 Diretiva de observação

+           6.4.1.10 Diretiva de sobreposição

+           6.4.1.11 Diretiva de título

+        6.4.2 Propriedades do Teclado

+

+  7. Tópicos Avançados

+     7.1 Instalação de Multiplas Versões

+     7.2 Instalação/Ajuda dos Discos de root para Linux

+     7.3 Herança Futura

+     7.4 Bugs Conhecidos

+

+  A. Display de Braille Suportados

+  B. Sintetizadores de Fala Suportados

+  C. Identificação dos Códigos do Driver 

+  D. Drivers de Tela Suportados

+  E. Sintaxe de Operação

+     E.1 Especificação do Driver 

+     E.2 Especificação do Dispositivo Braille 

+     E.3 Especificação do Dispositivo PCM 

+     E.4 Especificação do Dispositivo MIDI

+

+  F. Convenção do Padrão Braille para numeração

+  G. Código Norte-Americano do Padrão Braille

+  H. Tabela de Instrumento MIDI 

+

+

+  ______________________________________________________________________

+

+  1. Formalidades

+  

+

+  1.1 Licença

+

+Este programa é um software livre. Você poderá redistribuir e/ou modificar 

+sobre os termos do GNU General Public Lincense como publicado pela Fundação do 

+Software Livre. Versão 2 (ou outra versão posterior) da licença poderá ser 

+usada.

+Você deverá receber uma cópia da licença junto com este programa. Deve-se ter 

+no arquivo de licença-GPL no diretório raiz. Se não estiver, escreva para a 

+Free Software Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, EUA.

+   

+  1.2 Renúncia

+

+Este programa é distribuído na esperança de ser usual, mais vem com 

+ABSOLUTAMENTE SEM NENHUMA GARANTIA - nem mesmo a garantia implícita de 

+COMERCIALIZAÇÃO ou ADEQUAÇÃO PARA UM PROPÓSITO PARTICULAR. Ver na licença GNU 

+para mais detalhes.  

+

+  1.3 Contato

+

+BRLTTY é representado por uma equipe de trabalho. Para atualizar a informação 

+dia a dia, no web site BRLTTY [http://mielke.cc/brltty]. Composto por:

+

+  ·  Dave Mielke (mantenedor, ativo)

+

+     Web

+        http://mielke.cc/

+

+     Email

+        <dave@mielke.cc>

+

+  ·  Samuel Thibault (ativo)

+

+     Web

+        http://dept-info.labri.fr/~thibault/

+

+     Email

+        <samuel.thibault@ens-lyon.org>

+

+  ·  Mario Lang (ativo)

+

+     Web

+        http://delysid.org/

+

+     Email

+        <mlang@delysid.org>

+

+  ·  Nicolas Pitre

+

+     Web

+        http://www.fluxnic.net/

+

+     Email

+        <nico@fluxnic.net>

+

+  ·  Stéphane Doyon

+

+     Web

+        http://pages.infinit.net/sdoyon/

+

+     Email

+        <s.doyon@videotron.ca>

+

+  ·  Nikhil Nair (autor)

+

+     Email

+        <nn201@cus.cam.ac.uk>

+

+Perguntas, comentários, sugestões, criticas, e contribuições são todas bem-              

+vindas. Usando os endereços de email listados acima, e a melhor maneira de  

+entrar em contato com a lista do BRLTTY. Você pode postar na lista enviando um 

+email para brltty@mielke.cc. Se você não inscrever na lista então seus posts 

+serão mantidos para a aprovação do moderador. Para inscrever, remover 

+assinatura, configurações de mudanças, arquivos para visualização, etc, vá na   

+página http://mielke.cc/mailman/listinfo/brltty.

+   	

+  2. Introdução

+  

+BRLTTY dá ao usuário braille acesso para o terminal de texto de um sistema 

+Unix/Linux. Ele é executado como um processo em segundo plano (daemon) que 

+opera um display Braille atualizável, e pode ser iniciado antes da seqüência 

+do sistema de boot. Ele permite que um usuário braille trabalhe de forma 

+independente com a administração do sistema, tais como modo de entrada única 

+de usuário, recuperação do sistema de arquivos e análise de problemas de 

+inicialização. Ele ainda facilita muito as tarefas de rotina como ‘logging 

+in’.

+

+BRLTTY reproduz uma parte retangular da tela (que se refere neste documento 

+como ‘a janela’) como texto braille na tela. Controles na tela pode ser usados 

+para mover a janela em torno da tela, para ativar e desativar várias opções de 

+visualização, e para realizar funções especiais.

+

+  2.1 Sumário 

+  

+  BRLTTY oferece os seguintes recursos:

+

+  · Completa implementacão das facilidades normais de revisão de tela.

+

+  · Escolha entre block, underline, ou no cursor.

+

+  · Sublinhado Opcional para indicar destaque de texto especial.

+

+· Uso opcional de alerta (velocidade ajustáveis individualmente) para cursor, 

+destaque sublinhado especial, e/ou letras maiúsculas.

+

+  · Congelamento de tela para revisão.

+

+· Cursor inteligente de roteamento, permitindo que seja fácil buscar do cursor 

+dentro de editores de texto, navegadores web, etc, sem mover as mãos da linha 

+Braille.

+

+· A função de copia-e-cola que é particularmente útil para copiar nomes de 

+arquivos, copiar o texto entre os terminais virtuais, entrada de comandos 

+complicados, etc.

+

+  · Tabela de contração Braille (Inglês e Francês).

+

+  · Suporte para vários códigos em braille.

+

+  · Capacidade para identificar um caracter desconhecido.

+

+  · Capacidade para verificar um caracter sublinhado.

+

+  · Ajuda on-line para comandos da linha Braille.

+

+  · Menu de preferências.

+

+  · Suporte básico de fala.

+

+· Projeto modular que permite a adição relativamente fácil de drivers para 

+outros display braille e sintetizadores de voz.

+

+  · Uma Interface de Programação de Aplicativos.

+

+  2.2 Requisitos do Sistema

+  

+  Até essa data, BRLTTY roda no Linux, Solaris, OpenBSD, FreeBSD,

+  NetBSD, e Windows. Enquanto que outros sistemas operacionais não estão

+  atualmente planejados, alguma sugestão de outros projetos será bem

+  vinda. 

+

+  Linux

+

+  Este software tem sido testado por diversos sistemas:

+

+  · Desktops, laptops, e alguns PDAs.

+

+  · Processadores de 386SX20 até Pentium.

+

+  · Diversas séries de tamanhos de mémoria.

+

+  · Diversos distribuidores incluindo Debian, Red Hat, Slackware, e SuSE.

+

+  · Vários kernels, incluindo 1.2.13, 2.0, 2.2, and 2.4.

+ 	

+  Solaris

+  

+  Este software tem sido testado nos sistemas abaixo:

+

+        · Arquitetura Sparc (versões 7, 8, e 9).

+

+        · Arquitetura Intel (versão 9).

+

+  OpenBSD

+

+  Este software tem sido testado no sistema OpenBSD abaixo:

+

+  · Arquitetura Intel (versão 3.4).

+

+  FreeBSD

+ 

+  Este software tem sido testado no sistema FreeBSD abaixo:

+

+  · Arquitetura Intel (versão 5.1).

+

+  NetBSD

+

+  Este software tem sido testado no sistema NetBSD abaixo:

+

+  · Arquitetura Intel (versão 1.6).

+

+  Windows

+

+  Este software tem sido testado no Windows 95, 98 e XP.

+

+  No Linux, BRLTTY pode inspecionar o conteúdo da tela de forma

+  completamente independente de qualquer usuário conectado. Ele faz isso

+  usando um dispositivo especial que fornece fácil acesso ao conteúdo do 

+  atual console. Este dispositivo foi introduzido na versão 1.1.92 do

+  kernel do Linux, e é normalmente chamado também de /dev/vcsa ou

+  /dev/vcsa0 (em sistemas com devfs é chamado /dev/vcc/a). Por esta

+  razão, o kernel do Linux 1.1.92 ou superior é necessário se o BRLTTY

+  for utilizado desta forma. Com a capacidade de:

+

+  · Permite BRLTTY a ser iniciado muito antes da seqüência de

+  inicialização do sistema.

+

+  · Permite a visualização em Braille para estar totalmente operacional

+  durante o login do prompt.

+

+  · Faz muito mais fácil para um usuário em Braille para realizar a

+  inicialização de tarefas de administração do sistema.

+

+  Um patch para o programa de tela é fornecido (ver os subdiretórios dos

+  Patches). Ele permite que BRLTTY acesse imagem da tela da tela via 

+  memória compartilhada, e, portanto, permite BRLTTY a ser utilizados com

+  bastante efetivamente em plataformas que não têm a sua própria tela de

+  conteúdo instalações de inspecção. A principal fraqueza da abordagem de tela é

+  que BRLTTY não pode ser iniciado até que o usuário esteja logado.

+

+  BRLTTY apenas trabalha com consoles e aplicações. Pode ser usados em

+  aplicações shell, mais não com aplicações que usam caracteristicas VGA ou que

+  requer console gráfico (como o sistema X Window).

+

+  Você deverá também, claro, possuir um display Braille(ver seção “Display de

+  Braille Suportado” para completer a lista). Nós esperamos que outros displays

+  sejam suportados no futuro, então, se você tem alguma informação técnica de um

+  dispositivo que você gostaria de ser visto com suportado, por favor entre em

+  contato (ver seção “Informação de contato”).

+

+  Finalmente, você precisa de ferramentas para construir o executável a

+  partir de sua fonte: make, compiladores C e C + +, yacc, awk, etc. As

+  ferramentas de desenvolvimento fornecidas com a distribuição padrão do

+  Unix deveria ser suficiente. 

+

+  3 - Processo de compilação

+  

+  BRLTTY pode ser baixado a partir do seu site (consulte a secção

+  Informações de contato para a sua localização). Todas as releases são

+  fornecidas compactadas “tar balls”. Releases mais recentes também são

+  fornecidas como arquivos RPM (RedHat Package Manager). 

+

+     Essa informação, provavelmente atingiu a sua curiosidade, e agora você

+  não pode esperar para começar. É uma boa idéia, porém, que primeiro se

+  familiarizar com os arquivos que serão finalmente instalados.

+

+  3.1 - Instalação da herança de arquivos

+

+  Processo de compilação deve resultar na instalação dos seguintes

+  arquivos:

+

+  /bin/

+

+  brltty: O programa BRLTTY.

+

+  brltty-install: Um utilitário para copiar BRLTTY hierarquia de

+  arquivos instalado a partir de um local para outro.

+

+  brltty-config: Um utilitário que define um número de variáveis de

+  ambiente para valores que refletem a instalação atual do BRLTTY.

+  

+  /lib/

+

+  libbrlapi.a: arquivo estático da Interface de Programação de

+  Aplicativos.

+

+  libbrlapi.so: objeto dinâmico carregável para o Interface de

+  Programação de Aplicativos.

+

+  /lib/brltty/

+

+  A instalação do BRLTTY não pode ter todos os seguintes tipos de

+  arquivos. Eles são criados apenas quando necessário com base nas

+  opções de compilação que você selecionar (ver seção Opções de

+  Compilação).

+

+  brltty-brl.lst: Uma lista dos drivers do display braille, que foram

+  construídas como objetos compartilhados dinamicamente carregáveis, e,

+  portanto, que pode ser selecionada em tempo de execução. Cada linha

+  consiste do código de duas identificações no codigo do driver, um

+  caracter de tabulação.

+

+  libbrlttybdriver.so.1: O driver é carregável e  dinamico para uma

+  linha Braille, onde o driver é duas letras do código de identificação

+  do driver.

+

+  brltty-spk.lst: A lista de drivers de sintetizador de voz que foram

+  construídos como objetos compartilhados dinamicamente carregáveis, e,

+  portanto, que pode ser selecionada em tempo de execução. Cada linha

+  consiste do código de identificação de duas letras para um driver, um

+  caracter de tabulação, e uma descrição do sintetizador de voz que esse

+  driver.

+

+  libbrlttysdriver.so.1: O driver carregável dinamicamente por um

+  sintetizador de voz, onde o driver é o de duas letras do código de

+  identificação do driver.

+

+  /lib/brltty/rw/

+  Os arquivos criados em tempo de execução, por exemplo, é necessário,

+  mas falta recursos do sistema.

+

+  /etc/

+  brltty.conf: padrões do sistema para BRLTTY.

+  brlapi.key: A chave de acesso para BrlAPI.

+

+  /etc/brltty/

+  A instalação do BRLTTY não pode ter todos os seguintes tipos de

+  arquivos. Eles são criados apenas quando necessário com base nas

+  opções de compilação que você selecionar (ver seção “Opções de

+  Compilação”).

+

+  	*.conf: Driver específico para configuração de dados. Seus nomes

+      são mais ou menos como brltty-driver.conf, onde driver é o de duas

+      letras do código de identificação no driver.

+

+*.atb: tabelas de atributos.(ver seção “Tabelas de Atributos”) Seus 

+nomes parecem name.atb.

+       

+      *.ati: Incluir arquivos de tabelas de atributos.

+       

+*.ctb: Contração tabelas. (Ver seção “Tabela de Contração”). Seus 

+nomes parecem idioma language-country-level.ctb.

+

+      *.cti: Incluir arquivos para tabelas contração.

+       

+*.ktb: Tabelas de Teclas.(Ver seção “Tabelas de Teclas”). Seus 

+nomes parecem name.ktb.

+       

+      *.kti: Incluir arquivos para tabelas de teclas.

+       

+*.ttb: tabelas de texto. (Ver seção Tabelas de Texto). Seus nomes 

+parecem language.ttb.

+       

+      *.tti: Incluir arquivos de texto para tabelas.

+       

+*.hlp: Driver específico páginas de ajuda. Seus nomes mais ou menos 

+como brttty-driver.hlp, onde driver é o de duas letras do código de 

+identificação do driver.

+

+  /var/lib/BrlAPI/

+  São usados soquetes locais para conexão com a Interface de Programação

+  de Aplicativos.

+

+  /include/

+  Arquivos de cabeçalho C para a Interface de Programação de

+  Aplicativos. Seus nomes parecem brlapi-function.h. O cabeçalho

+  principal é brlapi.h.

+

+  /include/brltty/

+  Arquivos de cabeçalho C para acessar hardware braille. Seus nomes são

+  brldefs-driver.h (onde driver é o de duas letras do código de

+  identificação do driver). O cabeçalho brldefs.h e api.h são fornecidos

+  para compatibilidade com versões anteriores e não deve ser usado.

+

+  /man/

+  Páginas man.

+

+  man1/name.1: páginas man para comandos do usuário BRLTTY-relacionados.

+  man3/name.3: páginas man para rotinas da biblioteca de Interface de

+  Programação de Aplicativos.

+

+  Alguns arquivos opcionais que você deve estar ciente de, apesar de não

+  serem parte da hierarquia de arquivos instalados, são:

+

+  /etc/brltty.conf

+  O sistema de arquivo de configuração padrão. É criado pelo

+  administrador do sistema. Ver seção “Arquivo de Configuração”.

+

+  /etc/brltty-driver.prefs

+  O arquivo de configurações foram salvos as preferências (driver é um

+  código de duas letras de identificação do driver). É criado pelo

+  comando PREFSAVE. Ver seção “Preferência de Configuração”.

+

+  3.2 - Instalação a partir do Tar Ball

+

+     Este passo é apenas quando se quer instalar apenas o BRLTTY

+  rapidamente, é acreditar que os padrões estejam corretos.

+ 

+     1. Baixe o código. Um arquivo chamado brltty-release.tar.gz, por

+  exemplo, brltty-3.0.tar.gz.

+

+     2. Descompacte o código fonte em sua estrutura hierárquica nativa.

+ 

+  Ex.: tar-zxvf brltty-release.tar.gz. 

+

+  Isso deve criar o diretório brltty-release.

+

+  3. Mude para o diretório fonte, configure, compile e instale BRLTTY.

+ 

+  	cd brltty-release

+  	./configure 

+  	make install

+ 

+  Isto deve ser feito como root. 

+ 

+     Para desinstalar BRLTTY, faça:

+

+  	cd brltty-release

+      make uninstall

+

+  3.2.1 Opções de Compilação

+

+     O primeiro passo na construção BRLTTY é configurá-lo para o seu

+  sistema e/ou para suas necessidades pessoais. Isto é feito através da

+  execução do script no diretório BRLTTY de nível superior. Nós tentamos

+  fazer o ajuste padrão o caso mais comum, então, supondo que você não

+  está tentando fazer alguma coisa fora do comum, você pode não precisar

+  fazer nada mais complicado do que invocar o script sem especificar

+  todas opções.

+

+  ./configure

+

+  Se, no entanto, você tem algumas necessidades especiais, ou mesmo se

+  você está apenas se aventurando, você deve descobrir quais são suas

+  escolhas.

+

+  ./configure --help

+ 

+     Você também deve verificar se o arquivo README no subdiretório que

+  contém o driver para o seu display Braille para algumas instruções

+  adicionais de exibição específico. 

+ 

+     3.2.1.1 Padrões do Sistema 

+ 

+     --with-driver-braille = driver 

+     Especificar os drivers do display braille, que devem estar ligados ao

+  binário BRLTTY. Os drivers que não estão listados por esta opção são

+  construídas como objetos compartilhados dinamicamente carregáveis e

+  ainda pode ser selecionada em tempo de execução. Cada driver deve ser

+  identificado pelo seu código de duas letras de identificação do driver

+  ou pelo seu nome próprio (completo ou abreviado). Os identificadores

+  do driver deve ser separada da outra por uma única vírgula. Se um

+  identificador do driver é precedido por um sinal de menos (-), então

+  esse driver é excluído da compilação. Qualquer uma das seguintes

+  palavras também pode ser usado como o operando esta opção:

+

+  all 

+  Link de todos os drivers para o binário. Não construa qualquer um

+  deles como objetos compartilhados dinamicamente carregáveis. Esta

+  palavra também pode ser especificado como o último elemento de uma

+  lista de driver. Isto é como especificar o driver default quando todos

+  os drivers devem ser ligados dentro.

+

+  -all: Apenas construir os condutores que tenham sido incluídos 

+  explicitamente através desta opção.

+

+  no: Não construa qualquer driver em tudo. Isso é equivalente a

+  especificar --without-braille-driver.

+

+  yes: Construir todos os drivers como objetos compartilhados

+  dinamicamente carregáveis. Não ligar qualquer um deles dentro do

+  binário. Isso é equivalente a especificar --with-braille-driver.

+

+  Ver arquivo configuração e a linha de opção de commando –b para a seleção de

+  tempo de execução.

+

+  --with-braille-parameters= [driver:]name = value, ...

+  Especifique as configurações padrão de parâmetro para os drivers do

+  display braille. Se o mesmo parâmetro é especificado mais de uma vez,

+  a sua atribuição é usada. Se um nome de parâmetro é qualificada por um

+  driver, (ver seção “Códigos de Identificação de Drivers”) então essa

+  configuração só se aplica ao driver, senão é, então, que se aplica a

+  todos os drivers. Para essa descrição dos parâmetros aceitos por a

+  especificação de driver, por favor ver na documentação do driver. Ver

+  configuração de arquivo “braille-parameters” ou linha de comando –b.  

+ 

+     --with-braille-device = device, ... 

+     Especifica o dispositivo padrão para o qual o display braille está

+  ligado. (Ver seção Especificação de Dispositivo Braille). Se essa

+  opção não for especificada, então usb: é assumido se o suporte USB

+  está disponível, e um sistema operacional tem um caminho apropriado

+  para a porta primária serial (dispositivo) é assumido se não. 

+ 

+     --with-libbraille = directory 

+     Especifique o local da instalação do pacote Libbraille, e compilar o

+  driver display braille Libbraille (Ver seção “Restrições de

+  Compilação”). Qualquer uma das seguintes palavras também pode ser

+  usado como o operado por essa opção:

+

+  no

+  Não compilar o driver. Isso é equivalente a especificar

+  --without-libbraille.

+

+  Yes

+  Compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/Libbraille, /usr/local/libbraille, /opt/Libbraille, ou

+  /opt/libbraille. Isso é equivalente a especificar --with-libbraille.

+

+  --with-text-table=file

+     Especifique tabela texto build-in (retorno)(Ver seção “Tabelas de

+  Texto”). A tabela especificada é ligada ao binário BRLTTY, e é usado

+  tanto em falhas de autoseleção quanto na solicitação de tabela que não

+  pode ser carregado. O caminho absoluto para uma tabela fora do código

+  fonte pode ser especificado. A extensão “.ttb” é opcional. Esta

+  configuração pode ser alterada com a preferência Tabela de texto. Se

+  esta opção não for especificada, então en-nabcc, normalmente(América

+  do Norte) usa 8 pontos ver “Código Braille Norte-Americano”. Ver

+  configuração de “Tabela de Texto” ou comando –t. Esta configuração

+  pode ser mudada com a preferência “Tabela de Texto”. 

+ 

+     --with-attributes-table=file 

+     Especifique a tabela texto de atributos built-in (retorno)(Ver seção

+  “Atributos de Tradução”). A tabela especificada é ligada ao binário

+  BRLTTY, e é usado quando a tabela solicitada não pode ser carregada.

+  caminho absoluto para uma tabela fora do código fonte pode ser

+  especificado. A extensão do “.atb” é opcional. Se essa opção não for

+  especificada, então os atributos são assumidos. Mude o atributo se

+  você gostaria que ele fosse feito à moda antiga. Veja a tabela

+  atributos de arquivo de configuração e uma opção de linha de comando

+  para seleção de tempo de execução -a. Esta configuração pode ser

+  alterada com a preferência atributos da tabela.

+

+  --with-speech-driver = driver 

+     Especificar os drivers de sintetizador de voz que são ligados ao

+  binário BRLTTY. Esses drivers não estão listados nesta opção são

+  construídas como objetos compartilhados dinamicamente carregáveis e

+  ainda pode ser selecionado em tempo de execução. Cada driver deve ser

+  identificado pelo seu código de duas letras de identificação do driver

+  ou pelo seu nome próprio (completo ou abreviado). Os identificadores

+  do driver deve ser separados por uma única vírgula. Se identificador

+  do driver é precedido por um sinal de menos (-), então esse driver é

+  excluído da compilação. Qualquer uma das seguintes palavras também

+  pode ser usadas como operadores esta opção:

+

+  all

+  link de todos os drivers para o binário. Não compila qualquer um

+  deles como objetos compartilhados dinamicamente carregáveis. Esta

+  palavra também pode ser especificada como o último elemento de uma

+  lista de driver. Isto é como especificar o driver default quando todos

+  os drivers devem ser ligados. 

+              

+  -all

+  apenas construir os drivers que tenham sido incluídos explicitamente

+  através desta opção.

+

+  no

+  não construa qualquer driver. Isso é equivalente a especificar

+  without-speech-driver.

+

+  yes

+  compilar todos os drivers como objetos compartilhados dinamicamente

+  carregáveis. Não ligar qualquer um deles dentro do binário. Isso é

+  equivalente a especificar --with-speech-driver.

+

+     Veja a configuração do arquivo “speech-driver” e a opção -s de linha

+  de comando para seleção de tempo de execução. 

+

+  --with-speech-parameters = [driver:]name = value, ... 

+     Especifique os parâmetros das configurações padrão para os drivers de

+  sintetizador de voz. Se o mesmo parâmetro é especificado mais de uma

+  vez. Se um nome de parâmetro é qualificado por um driver (Ver Código

+  de Identificação de Drivers), então essa configuração só se aplica

+  nesse driver. Para obter uma descrição dos parâmetros aceitos por um

+  driver específico, consulte a documentação do driver. Ver configuração

+  “speech-parameters” ou comando “-s”. 

+ 

+     --with-flite=directory  

+     Especifique o local instalação do FestivalLite no pacote text-to

+  speech e compila o driver sintetizador de voz FestivalLite (Ver

+  “Restrições de Compilação”). Qualquer uma das seguintes palavras

+  também pode ser usado como o operadores esta opção:

+

+  no

+  não compilar o driver. Isso é equivalente a especificar –without

+  flite.

+

+  yes

+  compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/FestivalLite, /usr/local/flite, /opt/FestivalLite, ou

+  /opt/flite. Isso é equivalente a especificar --with-flite.

+ 

+     --with-flite-language=language 

+     Especifique o idioma usado que o text-to-speech do FestivalLite. O

+  idioma padrão é USENGLISH. 

+ 

+     --with-flite-lexicon=lexicon  

+     Especifique o léxico que o text-to-speech FestivalLite usa. O léxico

+  padrão é cmulex. 

+ 

+     --with-flite-voice=voice  

+     Especifique a voz que o text-to-speech FestivalLite usa. A voz padrão

+  é cmu_us_kal16. 

+

+  -- with-mikropuhe=directory  

+     Especifique o local instalação do pacote text-to-speech da Mikropuhe,

+  e compilar o driver sintetizador de voz Mikropuhe (Ver Restrições de

+  Compilação). Qualquer uma das seguintes palavras também pode ser usado

+  como o operando esta opção:

+ 

+     no

+  não compilar o driver. Isso é equivalente a especificar –without

+  mikropuhe.

+

+  yes

+  compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/Mikropuhe, /usr/local/mikropuhe, /opt/Mikropuhe, ou

+  /opt/mikropuhe. Isso é equivalente a especificar --with-mikropuhe.

+

+  --with-speechd=directory  

+     Especifique o local da instalação do pacote text-to-speech do speech

+  dispatcher, e compilar o driver sintetizador de voz do speech

+  dispatcher. Qualquer uma das seguintes palavras também pode ser usado

+  como o operando esta opção:

+ 

+     no

+  não compilar o driver. Isso é equivalente a especificar –without

+  speechd.

+ 

+     yes

+   compilar o driver se o pacote pode ser encontrado em /usr,

+   /usr/local, /usr/local/speech-dispatcher, /usr/local/speechd,

+   /opt/speech-dispatcher, ou /opt/speechd. Isso é equivalente a

+   especificar --with-speechd. 

+

+     --with-swift = directory  

+     Especifique o local da instalação do pacote de text-to-speech Swift, e

+  compilar o driver sintetizador de voz Swift. Qualquer uma das

+  seguintes palavras também pode ser usado como o operando esta opção: 

+ 

+  no

+  não compilar o driver. Isso é equivalente a especificar -–without

+  swift. 

+

+  yes

+  compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/Swift, /usr/local/swift, /opt/Swift, ou /opt/swift. Isso é

+  equivalente a especificar --with-swift. 

+ 

+     --with-theta = directory  

+     Especifique o local da instalação do pacote text-to-speech do Theta, e

+  compilar o driver sintetizador de voz Theta (Ver “Restrições de

+  Compilação”). Qualquer uma das seguintes palavras também pode ser

+  usado como o operando esta opção: 

+

+  no

+  não compilar o driver. Isso é equivalente a especificar -–without

+  theta. 

+ 

+  Yes

+  compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/Theta, /usr/theta/local, /opt/Theta, ou /opt/theta. Isso é

+  equivalente a especificar --with-theta. 

+ 

+     --with-viavoice=directory:  

+     Especifique o local da instalação do pacote text-to-speech do

+  ViaVoice, e compilar o driver sintetizador de voz do ViaVoice (Ver

+  Restrições de Compilação). Qualquer uma das seguintes palavras também

+  pode ser usado como o operando esta opção: 

+

+     no

+  não compilar o driver. Isso é equivalente a especificar –-without

+  viavoice. 

+

+  Yes

+  compilar o driver se o pacote pode ser encontrado em /usr, /usr/local,

+  /usr/local/ViaVoice, /usr/local/ViaVoice, /opt/ViaVoice, ou

+  /opt/ViaVoice. Isso é equivalente a especificar --with-viavoice. 

+ 

+     --with-screen-driver=driver:  

+     Especificar os drivers da tela que devem ser ligados ao binário

+  BRLTTY. Os drivers que não estão listados por esta opção são

+  construídas como objetos compartilhados dinamicamente carregáveis e

+  ainda pode ser selecionada em tempo de execução. Cada driver deve ser

+  identificado pelo seu código de duas letras de identificação do driver

+  (Ver seção Drivers de Tela Suportados) ou pelo seu nome próprio

+  (completo ou abreviado). Os identificadores de driver devem ser

+  separados da outra por uma única vírgula. Se um identificador de

+  driver é precedido por um sinal de menos (-), então esse driver é

+  excluído da compilação. Qualquer uma das seguintes palavras também

+  pode ser usado como o operando esta opção: 

+ 

+     all

+  liga todos os drivers dentro do binário. Não construa qualquer um

+  deles como objetos compartilhados dinamicamente carregáveis. Esta

+  palavra também pode ser especificado como o último elemento de uma

+  lista de driver. Isto é como especificar o driver default quando todos

+  os drivers devem ser ligados.

+ 

+     -all

+  apenas compila os drivers que tenham sido incluídos explicitamente

+  através desta opção.

+ 

+     no

+  não construa qualquer driver. Isso é equivalente a especificar

+  without-screen-driver. 

+ 

+  Yes

+  compilar todos os drivers como objetos compartilhados dinamicamente

+  carregáveis. Não ligar qualquer um deles dentro do binário. Isso é

+  equivalente a especificar --with-screen-driver. 

+

+     O primeiro driver não-excluído começa sendo driver padrão. Se essa

+  opção não for especificada, ou se não especificado o driver, então um

+  padrão do sistema operacional apropriado é selecionado. Se um driver

+  nativo para o sistema operacional atual está disponível, então esse

+  driver é selecionado, senão, o sc é selecionado. Veja o arquivo

+  screen-driver de configuração e a opção de linha de comando para

+  seleção -x tempo de execução. 

+ 

+     --with-screen-parameters=[driver:]name=value,...:  

+     Especifique as configurações padrão de parâmetro para os screen

+  drivers. Se um nome de parâmetro é qualificada por um driver (consulte

+  a seção Supported Screen Drivers), em seguida, a configuração só

+  aplica ao driver, senão é, aceitável por drivers específicos. Para

+  obter uma descrição dos parâmetros aceitos por um driver específico,

+  consulte a documentação do driver. Veja no arquivo de configuração de

+  parâmetros e o comando –X no tempo de execução. 

+ 

+     --with-usb-package = package, ...:  

+     Especificar o pacote que está sendo utilizado pela I/O da USB. O nome

+  dos pacotes devem ser separados um do outro por uma única vírgula, e

+  são processados da esquerda para a direita. O primeiro, que é

+  instalado no sistema é selecionada. Os pacotes a seguir são

+  suportados:

+

+  1. libusb

+  2. libusb-1.0 

+

+  Qualquer uma das seguintes palavras também pode ser usado como o

+  operadores esta opção:

+ 

+  no

+  não suporta I/O da USB. Isso é equivalente a especificar -–without

+  usb-package.

+ 

+  yes

+  use o suporte nativo para I/O da USB. Se o suporte nativo não está

+  disponível para a plataforma atual, em seguida, usar o pacote

+  disponível de suporte (como por ordem especificada acima). Isso é

+  equivalente a especificar --with-usb-package.

+ 

+     --with-bluetooth-package = package, ...  

+     Especificar o pacote que está sendo utilizado para I/O de Bluetooth.

+  Os nomes dos pacotes devem ser separados um do outro por uma única

+  vírgula, e são processados da esquerda para a direita. O primeiro, que

+  é instalado no sistema é selecionada. Os pacotes a seguir são

+  suportados: 

+  

+1.	(Sem os pacotes são suportados).

+  Qualquer uma das seguintes palavras também pode ser usado como o

+  operador esta opção:

+

+  no

+  não suporte da I/O do Bluetooth. Isso é equivalente a

+  especificar --with-bluetooth-package.

+

+  Yes

+  use o suporte nativo para I/O do Bluetooth. Se o suporte nativo

+  não está disponível para a plataforma atual, em seguida, usar o

+  primeiro pacote disponível suporte (como por ordem especificada

+  acima). Isso é equivalente a especificar --with-bluetooth-package.

+

+     3.2.1.2 Especificações de Diretório

+  

+  --with-execute-root=directory  

+     Especifique o diretório em que a hierarquia de arquivos foram

+  instalados no tempo de execução. O caminho absoluto deve ser

+  fornecido. Se essa opção não for especificada, então diretório raiz do

+  sistema é assumido. Você precisa usar essa característica, por

+  exemplo, se você tiver mais de uma versão instalada do BRLTTY precisar

+  instalar arquivos BRLTTY no mesmo tempo. 

+ 

+     --with-install-root=directory 

+     Especifique o diretório sob o qual a hierarquia de arquivos instalados

+  foi instalado. O caminho absoluto deve ser fornecido. Se esta opção

+  não for especificada, então o pacote root no tempo de execução (veja a

+  opção --with-execute-root build option) é assumida. Este diretório é

+  apenas usado pelo make install e make uninstall. Use esta opção se

+  você precisa instalar BRLTTY em um local diferente daquele a partir do

+  qual foi finalmente foi executado. Você precisa usar este recurso, por

+  exemplo, se você está construindo BRLTTY em um sistema para uso em

+  outro. 

+ 

+     --prefix = directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde os diretórios padrão para os arquivos independentes de

+  arquitetura são enraizadas. Esses diretórios são:

+     

+     · Diretório de Programa

+  · Diretório de Biblioteca

+  · Diretório de API

+     · Diretório de Escrita

+  · Diretório de Dados

+  · Diretório de Configuração

+  · Diretório de Manpage 

+  · Diretório de Inclusão

+

+  O caminho absoluto deve ser fornecido. Se essa opção não for

+  especificada, então diretório raiz do sistema é assumido. Este

+  diretório é enraizada no diretório especificado pela opção de

+  compilação --with-execute-root. 

+ 

+     --exec-prefix = directory 

+     Especifique o diretório dentro da “hierarquia de arquivos instalados”

+  onde os diretórios padrões para os arquivos dependentes de arquitetura

+  devem ser enraizados. Esses diretórios são:

+  · Diretório de Programa

+  · Diretório de Biblioteca

+  · Diretório de API

+

+  O caminho absoluto deve ser fornecido. Se essa opção não for

+  especificada, então o diretório especificado através da opção de

+  compilação --prefix é assumida. Este diretório é enraizado no

+  diretório especificado pelo --with-execute-root. 

+ 

+     --libdir=directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde o arquivo estático e o objeto carregado dinamicamente para a

+  API ser instalada. O caminho absoluto deve ser fornecido. Se essa

+  opção não for especificada, então o diretório especificado através da 

+  opção de configuração padrão –-libdir (cujo padrão é /lib enraizado no

+  diretório especificado pelo –-exec-prefix) é assumida. O diretório é

+  criado senão existe. 

+ 

+     --sysconfdir=directory 

+  Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde os arquivos de configuração do sistema serão instalados. O

+  caminho absoluto deve ser fornecido. Se essa opção não for

+  especificada, então o diretório especificado através da opção de

+  configuração padrão --sysconfdir (cujo padrão é /etc enraizado no

+  diretório especificado por --prefix) é assumida. O diretório é criado

+  se ele não existe. 

+

+  --with-program-directory=directory  

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde os programas executáveis (binários, executáveis) são instalados.

+  O caminho absoluto deve ser fornecido. Se essa opção não for

+  especificada, então o diretório especificado através da opção de

+  configuração padrão --bindir (cujo padrão /bin enraizada no diretório

+  especificado pelo --exec-prefix) é assumida. O diretório é criado se

+  ele não existe. 

+ 

+     --with-library-directory=directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde os drivers e outros arquivos dependentes de arquitetura são

+  instalados. O caminho absoluto deve ser fornecido. Se essa opção não

+  for especificada, então o brltty subdiretório do diretório

+  especificado através da opção de configuração padrão --libdir (cujo

+  padrão é /lib enraizada no diretório especificado pelo --exec-prefix)

+  é assumida. O diretório é criado se ele não existe. 

+

+     --with-writable-directory=directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  que podem ser gravados. O caminho absoluto deve ser fornecido.

+  Qualquer uma das seguintes palavras também pode ser usado como o

+  operando esta opção:

+

+  no

+  não defina um diretório gravável. Isso é equivalente a especificar

+  without-writable-directory.

+

+  Yes

+  Use o local padrão. Isso é equivalente a especificar --with-writable

+  directory.

+     Se essa opção não for especificada, então o subdiretório do diretório

+  rw especificado com --with-library-directory é assumido. O diretório é

+  criado se ele não existe.

+        

+  --with-data-directory=directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde as paginas das tabelas de ajuda, e outros arquivos de

+  arquiteturas independentes foram instaladas. O caminho absoluto deve

+  ser fornecido. Se essa opção não for especificada, então o

+  subdiretório do diretório do brltty especificado através da opção de

+  configuração padrão --sysconfdir (cujo padrão é /etc enraizado no

+  diretório especificado pela opção --prefix) é assumido. O diretório é

+  criado se ele não existe. 

+ 

+     --with-manpage-directory=directory 

+     Especifique o diretório dentro da hierarquia de arquivos instalados,

+  onde as páginas do man serão instalados. O caminho absoluto deve ser

+  fornecido. Se essa opção não for especificada, então o diretório

+  especificado através da opção de configuração padrão --mandir (cujo

+  padrão é /man enraizado no diretório especificado por --prefix) é

+  assumida. O diretório é criado se ele não existe. 

+ 

+     --with-include-directory=directory  

+     Especifique o diretório dentro da hierarquia de arquivos instalados

+  onde os arquivos de cabeçalho C para o Application Programming

+  Interface serão instalados. O caminho absoluto deve ser fornecido. Se

+  essa opção não for especificada, então o subdiretório do diretório

+  brltty é especificado através da opção de configuração padrão

+  includedir (cujo padrão é /include enraizado no diretório especificado

+  por --prefix) é assumida. O diretório é criado se ele não existe. 

+

+     3.2.1.3 Caracteristicas de Compilação 

+ 

+     Estas opções são úteis principalmente quando a construção do BRLTTY

+  for usado em um disco de inicialização. 

+ 

+     --enable-standalone-programs 

+     Criar links estaticamente, em vez de links dinamicos. Esta opção

+  remove todas as dependências de objetos compartilhados no tempo de

+  execução. Apenas os drivers padrão (veja a opção --with-braille

+  driver, --with-speech-driver, e --with-screen-driver) são compilados. 

+

+  --enable-relocatable-install 

+     Se este recurso estiver ativado, todos os caminhos internos são

+  recalculados a ser relativo ao diretório do programa. Se desativado,

+  então todos os caminhos internos são absolutos. Este recurso permite

+  que toda a hierarquia de arquivos instalados ser copiados ou movidos,

+  de um lugar para outro, e é destinado principalmente para uso em

+  plataformas Windows. 

+ 

+     --disable-strippingl 

+     Não remova as tabelas de símbolos a partir de arquivos executáveis e

+  objetos compartilhados quando instalá-los. 

+ 

+     --disable-learn-mode 

+     Reduzir o tamanho do programa, excluindo o modo de comando (Ver seção

+  de Modo de Comando de Aprendizagem). 

+ 

+     --disable-contracted-braille 

+     Reduzir o tamanho do programa, excluindo o suporte para braille

+  contratados (Ver seção Tabela de Contração). 

+ 

+     --disable-speech-support 

+     Reduzir o tamanho do programa, excluindo o suporte dos sintetizadores

+  de voz.

+

+--disable-iconv 

+Reduzir o tamanho do programa, com exclusão do suporte para a 

+conversão de conjunto de caracteres.

+          

+  --disable-icu 

+     Reduzir o tamanho do programa, excluindo o suporte de

+  internacionalização Unicode-based. 

+ 

+     --disable-x  

+     Reduzir o tamanho do programa, excluindo o suporte para X11. 

+ 

+     --disable-beeper-support 

+     Reduzir o tamanho do programa, com exclusão do suporte para o console

+  tone generator. 

+ 

+     --disable-pcm-support 

+     Reduzir o tamanho do programa, excluindo o suporte para a interface

+  digital de áudio na placa de som. 

+

+     --enable-pcm-support = interface 

+     Se a plataforma oferece mais de uma interface digital de áudio para

+  ser usado pode ser especificado. 

+ 

+     --disable-midi-support 

+     Reduzir o tamanho do programa, com exclusão do suporte para o Musical

+  Instrument Digital Interface da placa de som. 

+ 

+     --enable-midi-support = interface  

+     Se uma plataforma fornece mais de uma Interface Musical Instrument

+  Digital então o que está a ser utilizado pode ser especificado.

+

+  Platform   Interface   Description

+  _______________________________________________________________

+  Linux      oss         Open Sound System

+             alsa        Advanced Linux Sound Architecture

+

+     --disable-fm-support  

+     Reduzir o tamanho do programa, excluindo o apoio do sintetizador de FM

+  em um cartão de AdLib, OPL3, Sound Blaster, ou o equivalente de som. 

+ 

+     --disable-pm-configfile

+     Reduzir o tamanho do programa, excluindo o suporte para o arquivo de

+  configuração do driver Papenmeier.

+

+  --disable-gpm 

+     Reduzir o tamanho do programa, excluindo a interface da aplicação gpm

+  que permite interagir BRLTTY com o dispositivo pointer (mouse)(ver

+  seção Ponteiro (Mouse) Support via GPM). 

+ 

+     --disable-api  

+     Reduzir o tamanho do programa, excluindo a Application Programming

+  Interface. 

+ 

+     --with-api-parameters=name = value, ... 

+     Especifique as configurações padrão do parâmetro para o Application

+  Programming Interface. Se o mesmo parâmetro é especificado mais de uma

+  vez, a sua atribuição é usada. Para obter uma descrição dos parâmetros

+  aceitos pela interface, consulte o manual de referência BrlAPI. Veja

+  os arquivos de configuração api-parameters e o comando –a. 

+ 

+     --disable-caml-bindings 

+     Não construir bindings caml para o Application Programming Interface. 

+ 

+     --disable-java-bindings 

+     Não construir as bindings para a Interface Java Application

+  Programming. 

+ 

+     --disable-lisp-bindings 

+     Não construir as bindings de Lisp para o Application Programming

+  Interface. 

+ 

+     --disable-python-bindings  

+     Não construir bindings Python para o Application Programming

+  Interface. 

+ 

+     --disable-tcl-bindings  

+     Não construir bindings Tcl para a Application Programming Interface. 

+

+     --with-tcl-config=path  

+     Especifique o local do script de configuração Tcl (tclConfig.sh). Ou o

+  caminho para o script em si ou o diretório que contém pode ser

+  fornecido. Qualquer uma das seguintes palavras também pode ser usado

+  como o operando esta opção:

+

+  no

+  utilizar outros meios de adivinhar se Tcl está disponível, se sim,

+  onde ele foi instalado. Isso é equivalente a especificar –without

+  tcl-config.

+

+  yes

+  procure o script em alguns diretórios frequentemente utilizados. Isso

+  é equivalente a especificar --with-tcl-config. 

+

+  3.2.1.4 Opções Diversas 

+ 

+     --with-compiler-prefix = prefix 

+     Especifique o prefixo (caminho e início do nome do programa) para o

+  conjunto de ferramentas de compilar e ligar possam ser utilizadas.

+  Pode ser necessário usar esta opção se, por exemplo, você está a

+  compilando cruzado para uma arquitetura diferente.

+    

+  --with-init-path = path 

+     Especifique o caminho para o programa real de init do sistema. O

+  caminho absoluto deve ser fornecido. Se esta opção for especificada,

+  então:

+

+1.	O programa de init deve ser movido para um novo local.

+

+2.	O brltty deve ser transferido para local original do programa de

+       init.

+

+3.	Quando o sistema de init é executado no inicio, brltty é

+   realmente executado. Ele se coloca em segundo plano, e executa a

+   inicialização real no primeiro plano. Este é uma maneira de ter

+   braille desde o início. É especialmente útil para alguns discos

+   de instalação/recuperação.

+

+     Se essa opção não for especificada, então este recurso não está

+  ativado. Esta opção é destinada principalmente para a construção de

+  uma imagem do instalador braillificada. 

+ 

+     --with-stderr-path = path 

+     Especifique o caminho para o arquivo ou dispositivo onde o padrão

+  error é escrito. O caminho absoluto deve ser fornecido. Se essa opção

+  não for especificada, então este recurso não está ativado. Esta opção

+  é destinada principalmente para a construção de uma imagem do

+  instalador braillified.

+

+  3.2.2 Atingir metas de arquivos

+ 

+     Depois BRLTTY ter sido configurado, o próximo passo é compilar e

+  instalá-lo. Isto é feito através da aplicação de comando make que o

+  sistema de arquivo principal BRLTTY fazer (Makefile no diretório de

+  nível superior). O arquivo BRLTTY é compatível com a maioria dos

+  objetivos comuns de manutenção da aplicação. Eles incluem:

+

+  make

+  um atalho para make all.

+

+  make all

+  compilar e vincular o executável do BRLTTY, seus drivers e suas

+  páginas de ajuda, seus programas de teste, e alguns outros pequenos

+  utilitários.

+

+  make install

+  complete a fase de compilação e ligação (ver “make all”), e depois

+  instalar os executáveis do BRLTTY, arquivos de dados, drivers e

+  páginas de ajuda, nos lugares corretos e com as permissões corretas.

+

+  make uninstall

+  remova o executável BRLTTY, seus arquivos de dados, drivers e páginas

+  de ajuda, a partir do sistema.

+

+        

+

+

+

+

+          

+

+  make clean

+  certifique-se que a próxima compilação e ligação (ver make all) será

+  feito a partir do zero, removendo os resultados da compilação, ligação

+  e testes da estrutura de diretório de origem. Isso inclui a remoção de

+  arquivos de objeto, executáveis, objetos compartilhados dinamicamente

+  carregáveis, listas de drivers, páginas de ajuda, arquivos de

+  cabeçalho temporária, e arquivos principais.

+

+  make distclean

+  além de remover os resultados da compilação e vinculação (ver make

+  clean):

+?	Remova os resultados da configuração BRLTTY (ver Build Options). 

+Isso inclui a remoção de config.mk, config.h, config.cache, 

+config.status, config.log.

+?	Remover os outros arquivos da estrutura de diretório de origem 

+que se acumulam ao longo do tempo, mas que não pertencem ali. 

+Isso inclui a remoção de arquivos de editor de backup, os 

+resultados do caso de teste, rejeitou blocos de patch, e as 

+cópias dos arquivos originais.

+

+  3.3 Testando o BRLTTY:

+ 

+     Após a compilação, ligação e instalação BRLTTY, é uma boa idéia para

+  fazer um teste rápido antes de ativá-lo permanentemente. Para fazer

+  isso, chame com o comando: 

+ 

+     brltty -bdriver -ddevice 

+ 

+     Para driver, especificar o código de identificação do driver de duas

+  letras correspondente ao seu display Braille. Para o dispositivo,

+  especifique o caminho completo do dispositivo que o seu display está

+  conectado. 

+ 

+     Se você não quiser explicitamente identificar o driver e um

+  dispositivo de cada vez que você começar o BRLTTY, então você pode ter

+  duas abordagens. Você pode estabelecer padrões do sistema através do

+  braille-driver e do dispositivo braille-device do arquivo de

+  configuração, e/ou compilar as suas necessidades para a direita em

+  BRLTTY atraves --with-braille-driver e --with-braille-device. 

+     

+     A mensagem BRLTTY de identificação de versão deve aparecer no display

+  braille por alguns segundos (veja na linha de comando -m). Depois ele

+  vai embora (que você pode acelerar premindo qualquer tecla do

+  display), a área da tela onde está o cursor deve aparecer. Isso

+  significa que você deve esperar para ver o seu comando shell prompt.

+  Então, quando você entra em seu próximo comando, cada carácter deve

+  aparecer no visor, como é digitado no teclado.

+

+  Se sua experiência, é deixar o executar o BRLTTY então divirta-se, :). Se você

+  não tem experiência, precisará testar cada driver separadamente na ordem

+  isolada do problema. O driver de tela pode ser testado com “scrtest”, e o

+  driver do display braille pode ser testado com “brltest”.

+

+     Se você tiver um problema que exige uma grande quantidade de garimpo,

+  então você pode querer usar a seguinte linha de comando brltty:

+  · -ldebug para registrar muitas mensagens de diagnóstico.

+  · -n para manter BRLTTY em primeiro plano.

+  · -e para direcionar as mensagens de diagnóstico de erro padrão em vez

+    de log do sistema.

+

+  3.4 Iniciando o BRLTTY

+ 

+BRLTTY, quando devidamente instalado, é invocada com o único comando 

+brltty. Um arquivo de configuração (ver Arquivos de Configuração) pode 

+ser criado a fim de estabelecer padrões do sistema como a localização 

+do arquivo de preferências, o driver display braille a ser utilizada, o 

+dispositivo para o qual o display braille estiver ligado, e a tabela de 

+texto para ser usado. Muitas opções (ver Opções de Linha de Comando) 

+permitem a especificação de tempo de execução explícita como a 

+localização do arquivo de configuração, os padrões estabelecidos no 

+arquivo de configuração, e algumas características que têm padrões 

+razoáveis. A opção –h exibe um resumo de todas as opções. A opção -v 

+mostra a versão atual do programa, a API, e os drivers selecionados. A 

+opção-v exibe os valores das opções depois de todas as fontes têm sido 

+considerados. 

+ 

+É provavelmente o melhor para que o sistema iniciar automaticamente 

+BRLTTY como parte da seqüência de inicialização para que o display 

+braille já está instalado e funcionando, quando o login do prompt 

+aparecer. A maioria (provavelmente todas) das distribuições fornecem um 

+script de aplicações onde fornecida pelo usuário pode ser seguramente 

+começou perto do final da seqüência de inicialização. O nome do script 

+a distribuição é dependente. Aqui estão os que conhecem até agora: 

+

+  Red Hat

+  /etc/rc.d/rc.local

+ 

+     Começando BRLTTY desse script é uma boa aproximação (especialmente

+  para novos usuários). Basta adicionar um conjunto de linhas como

+  estas:

+

+if [-x /bin/brltty-a-f /etc/brltty.conf]

+then

+   /bin/brltty

+fi

+ 

+Isso geralmente pode ser abreviado para a forma um pouco menos legível:

+[-x /bin/brltty –a -f /etc/brltty.conf] && /bin/brltty

+ 

+Não adicione estas linhas antes da primeira linha (que geralmente 

+parece com #!/bin/sh). 

+ 

+Se o display Braille está sendo utilizado pelo administrador do 

+sistema, então provavelmente deve ser iniciado o mais cedo possível 

+durante a seqüência de boot (como antes os sistemas de arquivos são 

+verificados), de modo que o display utilizado no caso de algo der 

+errado durante estas verificações e o sistema cai no modo de usuário 

+único. Mais uma vez, exatamente onde é melhor de fazer isso é 

+distribuição de dependentes. Aqui estão os lugares que conhecemos até 

+agora: 

+

+Debian

+  /etc/init.d/boot (para versões mais antigas)

+  /etc/init.d/rcS (para versões mais recentes)

+  

+Um pacote brltty é fornecido (ver [http://packages.debian.org/brltty]) 

+a partir de versão 3.0 (Woody). Uma vez que este pacote cuida da 

+partida BRLTTY, não há necessidade de código fornecido pelo usuário 

+estiver instalado. 

+ 

+RedHat

+/etc/rc.d/rc.sysinit

+ 

+Cuidado que versões posteriores, a fim de apoiar um processo de 

+inicialização mais orientada para o utilizador do sistema, tem esse 

+script reinvoke si só, que está sob o controle de initlog. Olha, 

+provavelmente, até perto do topo, para um conjunto de linhas como 

+estas:

+

+# Execute novamente a nós mesmos através initlog

+if [-z "$ IN_INITLOG"]; then

+[-f /sbin/initlog] && exec /sbin/initlog $ INITLOG_ARGS –r 

+/etc/rc.sysinit

+fi 

+

+Inicializando o BRLTTY partir do resultado em duas reinvocações do 

+BRLTTY processos em execução ao mesmo tempo. Se a sua versão do script 

+tem esse recurso, certifique-se de começar BRLTTY após as linhas que 

+implementá-lo. 

+ 

+Slackware

+  /etc/rc.d/rc.S

+ 

+SuSE

+  /sbin/init.d/boot

+ 

+Uma alternativa é começar BRLTTY a partir de /etc/inittab. Você tem 

+duas opções, se você escolher esta rota.

+Se você quer que ela seja iniciada muito cedo, não precisa ser 

+reiniciado automaticamente para encerrar, em seguida, adicione uma 

+linha como esta antes do primeiro: sysinit: a linha que já está lá 

+dentro.

+

+brl::sysinit:/bin/brltty

+Se você não se importa que seja iniciado mais tarde, mas quero que ele 

+seja reiniciado automaticamente para encerrar, adicione uma linha como 

+esta em qualquer lugar dentro do arquivo.

+

+  brl:12345:respawn:/bin/brltty –n

+ 

+ A opção -n (--nodaemon) é muito importante quando executando BRLTTY 

+com facilidade init respawn. Você vai acabar com centenas de processos 

+BRLTTY todos rodando ao mesmo tempo, se você esquecer de especificar. 

+ 

+Verifique se o identificador (bt nestes exemplos) já não está sendo 

+usado por outra entrada, e, se for, escolher uma opção diferente. 

+ 

+Note-se que um comando como kill -TERM é suficiente para parar BRLTTY 

+em suas trilhas. Se ele morre durante a entrada em modo de usuário 

+único, por exemplo, pode muito bem ser devido a um problema desta 

+natureza. 

+ 

+Alguns sistemas, como parte da seqüência de boot, sonda as portas 

+seriais (normalmente, a fim de localizar automaticamente o mouse e 

+deduzir seu tipo). Se o seu display braille estiver usando uma porta 

+serial, este tipo de sondagem pode ser o suficiente para obtê-lo 

+confuso. Se isso acontecer com você, então tente reiniciar o driver 

+braille (veja o comando RESTARTBRL). Melhor ainda, desligar a porta 

+serial. sobre como fazer isso: 

+ 

+Red Hat 

+ 

+A sondagem é feita por um serviço chamado kudzu. Use o comando:

+

+chkconfig --list kudzu, 

+ 

+para ver se ele foi ativado. Use o comando: 

+

+chkconfig kudzu off

+ 

+Para desativá-lo. Posteriormente, os lançamentos permitem que você 

+execute kudzu, sem deixar de sondagem das portas seriais. Para fazer 

+isso, editar o arquivo /etc/sysconfig/kudzu/ e definir SAFE para yes. 

+ 

+Se você deseja iniciar BRLTTY antes de qualquer sistema de arquivos são 

+montados, em seguida, garantir que todos os seus componentes estão 

+instalados no sistema de arquivos raiz. Veja as opções --with-execute-

+root, --bindir, --libdir, --with-writable-directory, e –with-data-

+directory.

+

+3.5 Considerações de segurança

+ 

+BRLTTY precisa ser executado com privilégios de root, porque ele 

+precisa ler e escrever de acesso para a porta para o qual o display 

+braille é conectado, acesso de leitura /dev/vcsa ou equivalente (para 

+consultar as dimensões e posição do cursor, e rever o conteúdo da tela 

+em destaque), e ler e gravar o acesso ao console do sistema (para a 

+entrada de seta chave durante cursor encaminhamento, para a inserção de 

+caracteres de entrada durante a pasta, para simulação de chave 

+utilizando as teclas no display braille, para recuperar tradução saída 

+de caractere e fonte de tela tabelas de mapeamento, e para a ativação 

+do apito interno). O acesso aos dispositivos necessários podem, 

+evidentemente, ser concedido a um usuário não-root alterando as 

+permissões de arquivo associado com os dispositivos. Meramente ter 

+acesso ao console, no entanto, não é suficiente porque ativar o aviso 

+sonoro interno e simulando golpes de tecla ainda requerem privilégios 

+de root. Então, se você estiver disposto a desistir de cursor de 

+roteamento, cortar e colar, bips, e tudo isso, você pode executar 

+BRLTTY sem privilégios de root.

+

+3.6 Restrições de Compilação e Tempo de Execução

+ 

+Alert Tunes 

+ 

+Algumas plataformas não suportam todos os dispositivos de música. Veja 

+Tune Device. 

+ 

+Driver sintetizador de voz FestivalLite 

+ 

+O driver para o text-to-speech FestivalLite é construído somente se o 

+pacote foi instalado. Este driver e o driver para o text-to-speech do 

+Theta (veja --with-theta) não podem ser simultaneamente ligados ao 

+binário BRLTTY (veja --with-speech-driver). 

+ 

+Driver Libbraille Display Braille 

+ 

+O driver para o pacote Libbraille é construído somente se o pacote foi 

+instalado. 

+ 

+Driver sintetizador de voz Mikropuhe  

+ 

+O driver para o text-to-speech Mikropuhe é construído somente se o 

+pacote foi instalado. Este driver não pode ser incluído se o binário 

+BRLTTY é estaticamente ligados (ver a opção --enable-standalone-

+programs), porque um arquivo estático não está incluído no pacote. 

+ 

+Driver do sintetizador de voz Theta 

+ 

+O driver para o text-to-speech Theta é construído somente se o pacote 

+foi instalado. 

+ 

+Este driver e o driver para o text-to-speech FestivalLite (veja --with-

+flite) não podem ser simultaneamente ligados ao binário BRLTTY (veja --

+with-speech-driver). Se esse driver é construído como um objeto 

+carregado dinamicamente compartilhada então $ THETA_HOME/lib deve ser 

+adicionado à variável de ambiente LD_LIBRARY_PATH antes BRLTTY é 

+invocado porque os objetos compartilhados dentro do pacote não contém 

+caminhos de pesquisa em tempo de execução de suas dependências. 

+

+Driver sintetizador de voz ViaVoice 

+ 

+O driver para o text-to-speech ViaVoice é construído somente se o 

+pacote foi instalado. 

+Este driver não pode ser incluído se o binário BRLTTY é estaticamente 

+ligados (ver --enable-standalone-programs), porque um arquivo estático 

+não está incluído no pacote. 

+ 

+Driver VideoBraille Display Braille 

+ 

+O driver para o display Braille VideoBraille é construído em todos os 

+sistemas, mas só funciona em Linux.

+

+3.7 Instalação a partir de um arquivo RPM

+ 

+Para instalar BRLTTY de um RPM (RedHat Package Manager) do arquivo, 

+faça o seguinte:

+

+1. Baixe o pacote binário que corresponde ao seu hardware. Vai ser um 

+arquivo chamado brltty-release-version.architecture.rpm, por exemplo, 

+brltty-3.0-1.i386.rpm.

+

+2. Instale o pacote.

+      rpm -Uvh brltty-release-version.architecture.rpm

+ 

+Isto deve ser feito como root. Estritamente falando, a opção –u (update) 

+é a única que é necessário. A opção -v (verbose) exibe o nome do pacote 

+como ele está sendo instalado. A opção -h (hashes) exibe uma barra de 

+progresso (usando sinais de hash). 

+ 

+Para os corajosos, nós também fornecemos a fonte do arquivo RPM 

+(.src.rpm), mas isso está fora do escopo deste documento. 

+

+Para desinstalar BRLTTY, faça:

+          

+      rpm-e brltty

+

+3.8 Outros Utilidades

+ 

+Construindo BRLTTY também resulta na construção de ajudas e pequenos 

+utilitários de diagnóstico. 

+ 

+3.8.1 brltty-config 

+ 

+Este utilitário cria uma série de variáveis de ambiente para valores que 

+refletem a instalação atual do BRLTTY (ver Build Options). Deve ser 

+executado dentro de um ambiente shell existente, isto é, não como um 

+comando em seu próprio direito, e só pode ser usado por scripts que 

+suportam a sintaxe Bourne Shell.

+

+As seguintes variáveis de ambiente são definidas:

+

+BRLTTY_VERSION

+O número de versão do pacote BRLTTY.

+

+BRLTTY_EXECUTE_ROOT

+root em tempo de execução para o pacote instalado. Configurado com --

+with-execute-root.

+

+BRLTTY_PROGRAM_DIRECTORY

+Diretorio de programas executáveis (binários, executáveis). Configurada 

+através --with-program-directory.

+

+BRLTTY_LIBRARY_DIRECTORY

+Diretório de drivers. Configurada através de --with-library-directory.

+

+BRLTTY_WRITABLE_DIRECTORY

+Diretório que pode ser gravado. Configurado através do --with-writable-

+directory.

+

+BRLTTY_DATA_DIRECTORY

+Diretorio de tabelas e páginas de ajuda. Configurada via --with-data-

+directory.

+

+BRLTTY_MANPAGE_DIRECTORY: Diretorio de páginas de manual. Configurada 

+via --with-manpage-directory.

+

+BRLTTY_INCLUDE_DIRECTORY: Diretório para arquivos de cabeçalho C BrlAPI. 

+Configurado através --with-include-directory. 

+

+BRLAPI_VERSION

+O número da versão BrlAPI (Application Programming Interface do BRLTTY).

+

+BRLAPI_RELEASE

+O número da versão completa de BrlAPI.

+

+BRLAPI_AUTH

+O nome do arquivo de chave de BrlAPI.

+ 

+Além disso, as seguintes variáveis de ambiente padrão autoconf também 

+são definidos: 

+ 

+prefix  

+Subroot para arquivos independentes de arquitetura. Configurado através 

+do --prefix. 

+ 

+exec_prefix 

+Subroot para arquivos dependentes de arquitetura. Configurada via --

+exec-prefix. 

+ 

+bindir 

+Local padrão para o diretório do programa. Configurada via --bindir. 

+ 

+libdir 

+Diretório estático BrlAPI de arquivo e objetos carregáveis 

+dinamicamente. Suporte padrão o diretório da biblioteca. Configurada via 

+--libdir. 

+ 

+sysconfdir 

+Diretório para arquivos de configuração. Suporte padrão para o diretório 

+de dados. Configurada via --sysconfdir. 

+ 

+mandir 

+Localização padrão para o diretório páginas de manual. Configurada via -

+-mandir. 

+ 

+includedir 

+Suporte padrão para o diretório arquivos de cabeçalho. Configurada via -

+-includedir. 

+ 

+3.8.2 brltty-install

+ 

+Este utilitário copia BRLTTY hierarquia de arquivos instalado a partir 

+de um local para outro.

+

+brltty-install to [from]

+ 

+to 

+A localização para o qual a hierarquia de arquivos instalados foi 

+copiada. Deve ser um diretório existente. 

+ 

+from 

+A localização do qual a hierarquia de arquivos instalados é para ser 

+tomado. Se for especificado, então ele deve ser um diretório existente. 

+Se não for especificado, então o local utilizado para a construção é 

+assumida. 

+ 

+Este utilitário pode ser usado, por exemplo, para copiar BRLTTY para um 

+disco de raiz. Se um disquete de root é montada como /mnt e BRLTTY está 

+instalado no sistema principal, em seguida, digitando 

+

+      brltty-install/mnt 

+ 

+cria cópias do BRLTTY, juntamente com todos os seus dados e arquivos da 

+biblioteca, para a raiz de disquete. 

+Alguns problemas foram sentidos quando copia o BRLTTY entre sistemas com 

+diferentes versões da biblioteca C compartilhada. Este vale a pena 

+investigar se você tem dificuldades. 

+ 

+3.8.3 brltest

+ 

+Este utilitário testa um driver do display braile, e também fornece uma 

+forma interativa de aprender quais as teclas do display braille. Ele 

+deve ser executado como root.

+

+      brltest -option ... [driver [name= value ...]]

+ 

+_driver  

+O driver para o display braille. Ela deve ser uma de duas letras do 

+código de identificação do driver. Se não for especificado, então o 

+primeiro driver configurado com -- with-braille-driver é assumida. 

+ 

+_name = value  

+Definir um parâmetro de driver display Braille. Para obter uma descrição 

+dos parâmetros aceitos por um driver específico, consulte a documentação 

+do driver. 

+ 

+-ddevice --device = device 

+O caminho absoluto para o dispositivo para o qual o display braille está 

+ligado. Se não for especificado, o dispositivo configurado por --with-

+braille-device é assumida. 

+ 

+-Ddirectory--data-directory = directory 

+O caminho absoluto para o diretório onde os drivers dos arquivos de 

+dados reside. Se não for especificado, então o diretório configurado 

+através do --with-data-directory é assumida. 

+ 

+-Ldirectory--library-directory =  directory 

+O caminho absoluto para o diretório onde residem os drivers. Se não for 

+especificado, então o diretório configurado através do --libdir é 

+assumida. 

+ 

+-wdirectory --writable-directory=directory:  

+O caminho absoluto para um diretório que pode ser gravado. Se não for 

+especificado, então o diretório configurado com --with-writable-

+directory é assumida. 

+ 

+-h --help 

+Mostra um resumo das opções de linha de comando, e depois sair.  

+ 

+Este utilitário usa o comando do BRLTTY Command Learn Mode. Quando 

+pressionar a tecla do tempo limite  é de 10 segundos. O tempo de espera 

+de mensagens é de 4 segundos. 

+ 

+3.8.4 spktest 

+ 

+Este utilitário testa um driver sintetizador de voz. Pode precisar de 

+ser executado como root.

+

+      spktest -option ... [driver [name=value ...]]

+ 

+_driver 

+O driver para sintetizador de voz. Ela deve ter uma de duas letras do 

+código de identificação do driver. Se não for especificado, então o 

+primeiro driver configurado via --with-speech-driver é assumida. 

+ 

+_name = value  

+Definir o parâmetro do driver do sintetizador de voz. Para obter uma 

+descrição dos parâmetros aceitos por um driver específico, consulte a 

+documentação do driver. 

+ 

+-tstring --text-string = string  

+O texto a ser falado. Se não for especificado, entrada padrão é a 

+leitura. 

+ 

+-Ddirectory --data-directory=directory  

+O caminho absoluto para o diretório onde os arquivos de dados do driver 

+reside. Se não for especificado, então o diretório configurado através 

+do --with-data-directory é assumida. 

+ 

+-Ldirectory --library-directory = directory  

+O caminho absoluto para o diretório onde residem os drivers. Se não for 

+especificado, então o diretório configurado através do --libdir é 

+assumida. 

+ 

+-h--help: mostrar um resumo das opções de linha de comando, e depois 

+sair. 

+ 

+3.8.5 scrtest

+ 

+Este utilitário testa o driver da tela. Ele deve ser executado como 

+root.

+

+      scrtest -option ... [name = value ...]

+ 

+_name = value 

+Definir um parâmetro de driver tela. Para obter uma descrição dos 

+parâmetros aceitos por um driver específico, consulte a documentação do 

+driver. 

+ 

+-lcolumn--lest = column 

+Especifique o ponto de partida da coluna (à esquerda)(origem zero) da 

+região. Se esse valor não for fornecido, então um valor padrão, com base 

+na largura especificada, é selecionado de modo que a região será 

+centralizada horizontalmente. 

+ 

+-ccount --columns = count 

+Especifique a largura da região (em colunas). Se esse valor não for 

+fornecido, então um valor padrão, com base na coluna inicial 

+especificado, é selecionado de modo que a região será centralizada 

+horizontalmente.

+

+-trow --top = row  

+Especifique a linha (de cima) partida (origem zero) da região. Se esse 

+valor não for fornecido, então um valor padrão, com base na altura 

+especificada, é selecionado de modo que a região está centrada 

+verticalmente. 

+ 

+-rcount --rows = count 

+Especifique a altura da região (em linhas). Se esse valor não for 

+fornecido, então um valor padrão, com base na linha especificada de 

+partida, é selecionado de modo que a região está centrada verticalmente. 

+ 

+-h –help

+mostrar um resumo das opções de linha de comando, e depois sair. 

+ 

+Notas:

+

+· Se nenhuma coluna de partida, nem uma largura região é especificado, 

+então a região é centrada na horizontal e começa na coluna 5.

+

+· Se nenhuma linha de partida, nem a altura da região é especificado, 

+então a região é verticalmente centrado e começa na linha 5.

+ 

+A seguir o padrão de saída:

+

+ 1. Uma linha detalhando as dimensões da tela.

+

+        Screen: widthxheight

+

+ 2. Uma linha que detalha a posição (origem zero) do cursor.

+

+        Cursor: [column, row]

+

+ 3. Uma linha que detalha o tamanho da região da tela selecionada, e a 

+posição (origem zero), do seu canto superior esquerdo.

+ 

+       Region: widthxheight@[column, row]

+

+ 4. O conteúdo da região da tela selecionada. Não são impressos os 

+caracteres com espaços em branco.

+ 

+3.8.6 ttbtest 

+ 

+Este utilitário testa uma tabela de texto (ver Tabela de Texto).

+

+      ttbtest -option ... input-table output-table

+ 

+input-table  

+O caminho do sistema de arquivo com a tabela de entrada de texto. Se 

+isso é relativo, então é suportado no diretório configurado através --

+with-data-directory. 

+ 

+output-table  

+O caminho do sistema de arquivos para a tabela de texto de saída. Se 

+isso é relativo, então ele está apoiado no diretório de trabalho atual. 

+Se este parâmetro não é fornecido, em seguida, tabela de saída está 

+escrito. 

+ 

+-iformat --input-format = format 

+Especifique o formato da tabela de entrada. Se esta opção não é 

+fornecida, em seguida, o formato da tabela de entrada é deduzida a 

+partir da extensão do nome da tabela de entrada do arquivo. 

+ 

+-oformat --output-format = format  

+Especifique o formato da tabela de saída. Se esta opção não é fornecida, 

+o formato da tabela de saída, que decorre da extensão do nome da tabela 

+de saída do arquivo. 

+ 

+-ccharset --charset = charset 

+Especifique o nome do caractere de 8 bits para usar quando interpretar 

+as tabelas. Se esta opção não é fornecida, definir o caractere de host 

+que está sendo usado. 

+ 

+-e --edit  

+Chamar o editor de tabela de texto. Se a tabela de saída for 

+especificado, as alterações são escritas para ele. Se não, então a 

+tabela de entrada é reescrita. 

+ 

+-h --help  

+Mostrar um resumo das opções de linha de comando, e depois sair. 

+ 

+Se nenhuma ação especial é requerida, a tabela de saída é facultativa. 

+Se não for especificada, a tabela de entrada está marcada. Se for 

+especificado, a tabela de entrada é convertido. 

+ 

+Os formatos de tabela a seguir são suportados:

+

+ttb

+    BRLTTY

+sbl

+    SuSE blinux

+a2b

+    Gnopernicus

+gnb

+    Gnome Braille

+ 

+3.8.7 ctbtest 

+ 

+Este utilitário testa uma tabela de contração (ver Tabela de Contração). 

+O texto lido a partir dos arquivos de entrada (ou Standard input) é 

+reescrita para a saída padrão como contratada braille.

+  

+      ctbtest -option ... input-file ...

+ 

+input-file  

+A lista de arquivos a serem processados. Qualquer número de arquivos 

+pode ser especificado. Eles são processados da esquerda para a direita. 

+O nome de arquivo especial - é interpretada para significar a entrada 

+padrão. Se nenhum arquivo for especificado, entrada padrão é processado. 

+ 

+-cfile --contraction-table = file

+O caminho do sistema de arquivos para o quadro de contração. Se isso é 

+relativo, então é suportado no diretório configurado --with-data-

+directory. A extensão do .ctb é opcional. Se esta opção não é fornecida, 

+em seguida, en-us-g2 é assumido. 

+ 

+-tfile |auto –text-table = file|auto

+Especificar a tabela de texto (ver Text Tables). Se um caminho relativo 

+for fornecido, então ele está apoiado em /etc/brltty (ver --with-data- 

+directory e o --with-execute-root). A extensão .ttb é opcional. Veja a 

+tabela de texto do arquivo de configuração para a configuração de tempo 

+de execução padrão. Esta configuração pode ser alterada com a 

+preferência Text Table. 

+ 

+-wcolumns --output-width =columns 

+O comprimento máximo de uma linha de saída. Cada linha de entrada 

+contratada está envolvida dentro de linhas de saida quantas forem 

+necessárias. Se essa opção não for especificada, não há limite, e há uma 

+correspondência um-para-um entre as linhas de entrada e saída. 

+ 

+-h -–help

+Mostrar um resumo das opções de linha de comando, e depois sair. 

+ 

+A tabela de texto é usado:

+

+?	Para definir o caractere de saída de forma que o braille contratado 

+será exibida corretamente. A mesma tabela que será usada pelo 

+BRLTTY quando a saída é lido deve ser especificado.

+?	Para definir as representações braille dos caracteres definidos na 

+tabela de contração como = (ver secção Tradução de Caracteres).

+ 

+ 

+A tabela brf.ttb texto é fornecido para uso com este utilitário. Ele 

+define o formato usado nos arquivos .brf. Este também é o formato 

+preferido usado pela maioria das impressoras em braille e dentro 

+distribuída eletronicamente documentos em braille. Esta tabela efetiva 

+permite que este utilitário para ser usado como um tradutor de texto 

+braille. 

+ 

+3.8.8 tunetest 

+ 

+Este utilitário testa músicas de alerta, e também fornece uma maneira 

+fácil de compor novas melodias. Pode precisar de ser executado como 

+root.

+

+      tunetest -option ... {note duration} ...

+

+note  

+Um padrão MIDI de número de nota. Deve ser um número inteiro de 1 a 127, 

+com 60 representantes Middle C. Cada valor representa um padrão 

+cromático semi-tom, com os próximos valores inferiores e superiores que 

+representam, respectivamente, as notas imediatamente inferior e 

+superior. 

+ 

+duration  

+A duração da nota em milissegundos. Deve ser um número inteiro de 1 a 

+255. 

+ 

+-ddevice --device = device  

+O dispositivo no qual a tocar a música.

+

+  Beeper

+  o apito interno (console gerador de tom).

+

+  Pcm

+  a interface digital de áudio na placa de som.

+  

+  Midi

+  o Musical Instrument Digital Interface na placa de som.

+  

+  Fm

+  o sintetizador de FM em um cartão de AdLib, OPL3, Sound Blaster, ou o 

+  equivalente de som.

+ 

+O nome do dispositivo pode ser abreviada. Veja Dispositivo de Tune para 

+obter detalhes sobre o dispositivo padrão e as restrições da plataforma. 

+ 

+-vloudness --volume=loudness 

+Especificar o volume da produção (volume) como uma porcentagem do 

+máximo. O volume de saída padrão é 50. 

+ 

+-pdevice --pcm-device = device  

+Especifique o dispositivo a ser usado para o áudio digital (ver 

+Especificação do Dispositivo PCM). Esta opção não estará disponível se a 

+opção --disable-pcm-support for especificada. 

+ 

+-mdevice --midi-device = device

+Especifique o dispositivo a ser usado para o Musical Instrument Digital 

+Interface (ver Especificação do Dispositivo MIDI). Esta opção não estará 

+disponível se a opção --disable-midi-support build option foi 

+especificado. 

+ 

+-iinstrument --instrument = instrument  

+O instrumento a ser usado se o dispositivo selecionado para midi. O 

+instrumento padrão é um acoustic grand piano. As palavras que compõem o 

+nome do instrumento deve ser separados um do outro por um único sinal de 

+menos em vez de espaços, e qualquer uma das palavras pode ser abreviada. 

+Um piano acústico, por exemplo, pode ser especificado como a-gra-pi.

+

+-h –help  

+Mostrar um resumo das opções de linha de comando, e depois sair. 

+ 

+4 - Usando o BRLTTY

+

+Antes de iniciar o BRLTTY, você precisa configurar a sua linha Braille. 

+Na maioria dos casos isso é feito simplesmente conectando-o a uma porta 

+serial disponível, e depois ligá-lo. Depois de configurado o display 

+execute BRLTTY simplesmente digitando o comando brltty no shell prompt 

+(isto deve ser feito como root). Verifique a opção -d de linha de 

+comando, as diretivas de arquivo de configuração do  dispositivo 

+braille, e a opção de construção --with-braille-device para alternativas 

+sobre como dizer ao  BRLTTY qual dispositivo que o display está 

+conectado. Verifique a opção -b de linha de comando, as diretivas de 

+arquivo de configuração do dispositivo braille, e a opção de construção 

+--with-braille-device  para alternativas sobre como dizer ao  BRLTTY que 

+tipo de display braille você tem. Verifique a opção -B de linha de 

+comando, e o braille-parameters  das diretiva de arquivo de configuração 

+sobre como passar parâmetros para o driver do seu display braille.

+

+Uma mensagem indicando o nome do programa (BRLTTY) e seu número de 

+versão aparecerá brevemente (ver a opção -M de linha de comando) no 

+display braille. A display mostrará então uma pequena área da tela, 

+incluindo o cursor. Por padrão o cursor é representado com os pontos 7 e 

+8 sobreposto ao caracter que ele está.

+

+Qualquer atividade de tela será refletida na tela em Braille. O display 

+também irá acompanhar o andamento do cursor na tela. Esse recurso é 

+conhecido como o cursor tracking.

+

+Basta digitar no teclado e ler o display, no entanto, não é suficiente. 

+Tente digitar um comando que causará um erro, e pressionar enter. O erro 

+aparece na tela, mas, a menos que tenha um display multi-linha, as 

+chances são de que não é visível no display braille. Tudo que você vê 

+nela é outra janela de comandos. O que é necessário, então, é uma 

+maneira de mover a janela braille em torno da tela. As teclas no display 

+braille em si pode ser usado para enviar comandos para BRLTTY que, além 

+de um monte de outras coisas, também pode fazer exatamente isso.

+  

+

+4.1 – Comandos

+

+Infelizmente, os vários display braille não oferecem um conjunto de 

+controles. Alguns têm o padrão de seis pontos chaves, alguns têm oito, e 

+outros não têm nenhum. Alguns têm as teclas do polegar, mas não há um 

+número padrão deles. Alguns possuem um botão acima de cada célula 

+braille. Alguns têm interruptores liga/desliga(rocker switches). Alguns 

+têm uma barra fácil de alcançar, que funciona como um joystick. Alguns 

+têm as combinações dos anteriores. Porque a natureza e a disposição de 

+cada display é tão diferente, consulte o documentação para o display 

+específico, a fim de descobrir exatamente o que suas teclas fazem.

+

+Comandos BRLTTY são referidos pelo nome neste manual. Se você esquecer 

+qual é a tecla(s) no seu display braille para usar um comando 

+particular, em seguida, consulte a sua página de ajuda dos driver’s. A 

+tecla principal você deve imediatamente guardar na memória, portanto, é 

+único para o comando HELP. Use as teclas de movimento regular (conforme 

+descrito abaixo) para navegar na página de ajuda, e pressione a help 

+novamente para sair.

+ 

+4.1.1 Deslocamento Vertical

+

+Veja também as PRINDENT/NXINDENT e o PRDIFCHAR/NXDIFCHAR teclas  de 

+roteamento de comandos.

+ 

+LNUP/LNDN

+Ir para cima/baixo em uma linha. Se o comando pular linha idêntica foi 

+ativado (veja o comando SKPIDLNS), então esses comandos, ao invés de 

+mover exatamente uma linha, serão associados os comandos 

+PRDIFLN/NXDIFLN.

+ 

+WINUP/WINDN

+Ir para cima/baixo em uma janela. Se a janela é apenas uma linha alta 

+move 5 linhas. 

+ 

+PRDIFLN/NXDIFLN

+Ir para cima/baixo para a linha mais próxima com conteúdo diferente. Se 

+o comando pular linha idêntica foi ativado (veja o comando SKPIDLNS), 

+então esses comandos, ao invés de pular linhas idênticas, serão 

+associados os comandos LNUP/LNDN. 

+ 

+ATTRUP/ATTRDN

+Ir para cima/baixo para a linha mais próxima com diferentes atributos 

+(caracter em destaque). 

+ 

+TOP/BOT

+Vá para a linha superior/inferior. 

+ 

+TOP_LEFT/BOT_LEFT

+Vá para o canto top-left/bottom-left. 

+ 

+PRPGRPH/NXPGRPH

+Ir para a próxima linha do parágrafo anterior/seguinte (a primeira linha 

+não-branco para além do próximo linha em branco). A linha atual é 

+incluída na pesquisa para o espaço inter-paragráfo. 

+ 

+PRPROMPT/NXPROMPT

+Ir para o prompet comando anterior/seguinte. 

+ 

+PRSEARCH/NXSEARCH

+Procura para trás/frente para a próxima ocorrência da cadeia de 

+caracteres no buffer de corte (ver Recortar e Colar) que não estiver 

+dentro da janela braille. Os recursos de pesquisa para a 

+esquerda/direita, começando imediatamente no caracter à direita/esquerda 

+da janela, e envolvendo a borda da tela. A pesquisa não diferencia 

+maiúsculas de minúsculas. 

+ 

+4.1.2 Deslocamento Horizontal

+

+Veja também as SETLEFT teclas de encaminhamento de comando.

+ 

+CHRLT/CHRRT

+Vá para a esquerda/direita de um caracter. 

+ 

+HWINLT/HWINRT

+Vá para a esquerda/direita da metade de uma janela. 

+ 

+FWINLT/FWINRT

+Vá para a esquerda/direita de uma janela. Estes comandos são 

+particularmente úteis porque quebram automaticamente quando atingir a 

+borda da tela. Outros recursos, como a sua capacidade de pular janelas 

+em branco (veja o comando SKPBLNKWINS), reforçar a sua utilidade. 

+ 

+FWINLTSKIP/FWINRTSKIP

+Vá para a esquerda/direita para a janela mais próxima não-branca. 

+ 

+LNBEG/LNEND

+Ir para o início/fim da linha. 

+

+4.1.3 Deslocamento Implícito

+

+Veja também GOTOMARK teclas de encaminhamento de comando.

+ 

+HOME

+Vai para onde está o cursor. 

+ 

+BACK

+Volte para onde o comando de movimento mais recente colocado no display 

+braille. Esta é uma maneira fácil de voltar para onde você estava lendo, 

+após um evento inesperado (como acompanhamento do cursor) movendo a 

+janela braille em um momento inoportuno. 

+ 

+RETURN

+?	Se o movimento mais recente da janela do braille foi automático, 

+por exemplo, como resultado do monitoramento de cursor, então volta 

+para onde o comando de movimento mais recente colocá-lo (veja o 

+comando BACK).

+?	Se o cursor não estiver dentro da janela do braille, em seguida, 

+vai para onde está o cursor (veja o comando HOME). 

+

+4.1.4	 Caracteristica de Ativação

+

+Cada um desses comandos tem três formas: activate (ativar o recurso), 

+desactivate (desativar o recurso), e toggle (se estiver desligado então 

+irá ligar, e se estiver ligado irá desligar). Exceto conforme indicado, 

+cada um desses recursos está inicialmente off, e, quando on, afeta a 

+operação BRLTTY como um todo. A configuração inicial de algumas dessas 

+características podem ser alteradas através do menu de preferências.

+ 

+FREEZE

+Congela a imagem da tela. BRLTTY faz uma cópia da tela (conteúdo e 

+atributos) a partir do momento em que a imagem da tela é congelada e em 

+seguida, ignora todas as atualização da tela até que ela seja 

+descongelada. Esta característica torna mais fácil, por exemplo, uma 

+amostra da saída de uma aplicação que escreve muito, muito rapidamente. 

+ 

+DISPMD

+Mostrar o destaque (os atributos) de cada caracter dentro da display 

+braille, em vez dos próprios caracteres(o conteúdo). Esse recurso é 

+útil, por exemplo, quando você precisa localizar um item em destaque. Ao 

+mostrar o conteúdo da tela, o quadro de texto é usado (veja a opção de 

+linha de comando -t, o quadro de texto do arquivo de diretiva de 

+configuração, e --with-text-table como opção de construção). Ao mostrar 

+os atributos da tela, a tabela de atributos é usado (veja -a, os 

+attributes-table diretiva de arquivo de configuração, e --with-

+attributes-table opção de construção). Este recurso só afeta o terminal 

+virtual atual.

+

+SIXDOTS

+Mostrar caracteres usando 6 pontos, ao invés de 8 pontos, em braille. Os 

+pontos 7 e 8 ainda são usados por outras características como a 

+representação do cursor e enfatizando o carácter realçado. Se uma tabela 

+de contração foi selecionada (veja a opção -c e da tabela de contração 

+das diretivas de configuração de arquivo), então ele é usado. Esta 

+configuração também pode ser alterada com a preferência do estilo de 

+texto.

+ 

+SLIDEWIN

+Se o controle de cursor (veja o comando CSRTRK) está on, então, sempre 

+que o cursor move-se muito perto (ou mais) ou outra extremidade da 

+janela do braille, reposicionar horizontalmente a janela de tal forma 

+que o cursor, mantendo-se nesse lado, está mais próximo da centro. Se 

+este recurso está off a janela braille está sempre posicionado de modo 

+que sua extremidade esquerda é um múltiplo de sua largura da borda 

+esquerda da tela. Esta configuração também pode ser alterada com a 

+preferência Sliding Window.

+ 

+SKPIDLNS

+Ao invés de mover exatamente uma linha para cima ou para baixo, salta 

+linhas anteriores, que têm o mesmo conteúdo da linha atual. Esta 

+característica afeta o comandos LNUP/LNDN, bem como o recurso de linha 

+de embalagem de FWINRT/FWINLT e o comando FWINLTSKIP/FWINRTSKIP. Esta 

+configuração também pode ser alterada com a preferência Skip Identical 

+Lines.

+ 

+SKPBLNKWINS

+Pular janelas em branco durante a leitura para frente ou para trás. Este 

+recurso afeta os comandos FWINLT/FWINRT. Esta configuração também pode 

+ser alterada com a preferência Skip Blank Windows.

+ 

+CSRVIS

+Mostrar o cursor pela superposição de um ponto padrão (veja o comando 

+CSRSIZE) em cima do caracter onde ela está. Este recurso está 

+inicialmente on. Esta configuração também pode ser alterada com a 

+preferência do Mostrar Cursor. 

+ 

+CSRHIDE

+Ocultar o cursor (veja o comando CSRVIS), a fim de ler exatamente o 

+caracter abaixo dela. Este recurso só afeta o terminal atual vi 

+ 

+CSRTRK

+Vestígio(seguir) do cursor. Se o cursor se move para um local que não 

+seja dentro da janela do braille, então automaticamente move a janela de 

+braille para a nova localização do cursor. Você geralmente quer este 

+recurso ativado, uma vez que minimiza os efeitos de rolagem da tela, e 

+uma vez que, durante a entrada, a região onde você está atualmente 

+escrevendo sempre é visível. Se este recurso faz com que a janela de 

+braille salte em um momento inoportuno, use o comando BACK para voltar 

+para onde você estava lendo. Talvez seja necessário desativar esse 

+recurso ao usar um aplicativo que atualiza continuamente a tela, 

+mantendo um layout de dados fixo. Este recurso está inicialmente ligado. 

+Este recurso só afeta o terminal virtual atual.

+ 

+CSRSIZE

+Representa o curso com os oito ponto(um bloco sólido), e não apenas com 

+os pontos 7 e 8 (um sublinhado). Esta configuração também pode ser 

+mudada com a preferência do Curso Style.

+ 

+CSRBLINK

+Pisca(liga e desliga de acordo com um intervalo pré-definido) o símbolo 

+que representa o cursor(veja o comando CSRVIS). Esta configuração também 

+pode ser mudada com a preferência do Blinking Cursor.

+ 

+ATTRVIS

+      Sublinhado(com combinações dos pontos 7 e 8) o caracter destacado.

+      

+Sem sublinhado

+Branco sobre preto(normal),cinza sobre preto, branco sobre azul, 

+preto sobre cinza.

+

+      Pontos 7 ou 8

+      Preto sobre branco (vídeo reverso).

+

+      Pino 8

+      Todo o resto.

+

+Esta configuração também pode ser mudada com a preferência do 

+Mostrar Atributos.

+

+ATTRBLINK

+Pisca(muda de ligado e desligado de acordo com o intervalo predefinido) 

+o atributo sublinhado(veja o comando ATTRVIS). Este recurso esta 

+inicialmente ligado. Esta configuração também pode ser mudada com a 

+preferência do Blinking Attributes.

+

+CAPBLINK

+Pisca(liga e desliga de acordo com um intervalo pré-definido) a letra 

+maiúscula. Esta configuração também pode ser mudada com a preferência do 

+Blinking Capitals.

+

+TUNES

+Tocar uma melodia curta pré-definidas (ver Alert Tunes) sempre que 

+ocorra um evento significativo. Este recurso esta inicialmente ligado. 

+Esta configuração também pode ser alterada com a preferência Alerta de 

+Tunes.

+

+AutoRepeat

+Automaticamente repete um comando em um intervalo regular após um atraso 

+inicial enquanto a sua chave (combinação) continua pressionada. Apenas 

+alguns drivers suportam esta funcionalidade, a principal limitação é que 

+muitos display braille não sinalizam teclas apertadas e teclas liberadas 

+como eventos separados distintamente. Este recurso esta inicialmente 

+ligado. Esta configuração também pode ser alterada com a preferência de 

+Autorepetição.

+

+AUTOSPEAK

+Automaticamente falar:

+

+?	A nova linha quando a janela braille é movida verticalmente.

+?	caracteres que são inseridos ou excluídos.

+?	O caracter ao qual o cursor é movido.

+

+Este recurso esta inicialmente desligado. Esta configuração também pode 

+ser alterada com a preferência de Auto-Fala.

+ 

+4.1.5 Modo de Seleção 

+ 

+HELP

+Alterne para a página de ajuda do display braille. Isto é onde você pode 

+encontrar um resumo on-line das coisas que as teclas do seu display 

+braille fazem, e como interpretar o status das células. Use os comandos 

+regular de movimento vertical e horizontal para navegar pela página de 

+ajuda. Chame o comando de ajuda novamente para voltar para a tela.

+ 

+INFO

+Alterne para a exibição de status (ver seção The Status Display para 

+mais detalhes). Ele apresenta um resumo com a posição do cursor, a 

+posição da janela braille, e os estados de uma série de características 

+do BRLTTY. Chame este comando novamente para voltar para a tela.

+

+LEARN

+Alternar para o modo de comando aprender (ver seção Command Learn Mode 

+para mais detalhes). Isto é como você pode aprender de forma interativa 

+o que as teclas do seu display braille fazem. Chamar este comando 

+novamente para voltar para a tela. Este comando não estará disponível se 

+a opção --disable-learn-mode foi especificada na opção de construção.

+

+4.1.6	Preferencias de Manutenção

+

+PREFMENU

+Escolha do menu de preferências (Ver Menu Preferências). Chama esse comando de 

+novo para retornar as operações normais.

+

+PREFSAVE

+Salva as configurações de preferências atuais (Ver Preferências).

+

+PREFLOAD

+Armazena as configurações de preferências mais recentes (Ver Preferencias).

+ 

+4.1.7 Menu de Navegação 

+ 

+MENU_FIRST_ITEM/MENU_LAST_ITEM

+Ir para o primeiro/último item no menu.

+ 

+MENU_PREV_ITEM/MENU_NEXT_ITEM

+Ir para o anterior/seguinte item no menu.

+ 

+MENU_PREV_SETTING/MENU_NEXT_SETTING

+Retroceder/Expandir o atual item do menu.

+ 

+4.1.8 Controles de Fala 

+ 

+SAY_LINE

+Fala a linha atual. A preferência do Say-Line Mode determina se fala 

+pendente é descartada em primeiro lugar.

+ 

+SAY_ABOVE

+Fale a parte superior da tela (termina com a linha atual).

+

+SAY_BELOW

+Fale a parte inferior da tela (começando com a linha atual).

+ 

+MUTE

+Pare de falar imediatamente.

+ 

+SPKHOME

+Vá para onde o cursor fala.

+ 

+SAY_SLOWER/SAY_FASTER

+Aumentar/diminuir a velocidade de leitura (ver também a preferência de 

+Speech Rate). Este comando só está disponível se um driver que suporta 

+ele está sendo usado.

+ 

+SAY_SOFTER/SAY_LOUDER

+Aumentar/diminuir o volume da voz (ver também a preferência de Speech 

+Volume). Este comando só está disponível se um driver que suporta ele 

+está sendo usado.

+ 

+4.1.9 Comutação do Terminal Virtual 

+

+Veja também SWITCHVT encaminhamento de teclas de comando. 

+ 

+SWITCHVT_PREV/SWITCHVT_NEXT

+Mude para o anterior/próximo terminal virtual.

+ 

+4.1.10 Outros Comandos 

+ 

+CSRJMP_VERT 

+         

+Encaminha (trazer) o cursor para qualquer lugar na linha de cima da 

+janela do braille (ver Roteamento de Cursor). O cursor é movido por uma 

+simulação vertical das teclas de seta pressionadas. Este comando nem 

+sempre funciona, porque alguns aplicativos ou move o cursor um pouco 

+imprevisível ou use as teclas de seta para outros fins que não o 

+movimento do cursor. É um pouco mais seguro do que outros comandos de 

+roteamento do cursor, porém, porque não faz nenhuma tentativa de simular 

+as setas da esquerda e direita. 

+

+PASTE 

+ 

+Insira os caracteres no buffer de corte na posição atual do cursor (ver 

+Copiar e Colar). 

+ 

+RESTARTBRL 

+ 

+Pare e reinicie o controlador de visualização em Braille. 

+ 

+RESTARTSPEECH 

+ 

+Pare e reinicie o controlador de sintetizador de voz. 

+ 

+4.1.11 Comandos de Carácter 

+ 

+ROUTE 

+ 

+Encaminha (trazer) o cursor para o caractere associado a tecla de 

+roteamento (ver Roteamento de Cursor). O cursor é movido por uma 

+simulação das teclas de seta pressionadas. Este comando nem sempre 

+funciona, porque alguns aplicativos ou move o cursor um pouco 

+imprevisível ou use as teclas de seta para outros fins que não o 

+movimento do cursor. 

+ 

+CUTBEGIN

+Apoia-se no início do bloco de corte para o caracter associado com a 

+tecla de roteamento (ver Copiar e Colar). Este comando limpa o buffer de 

+corte.

+ 

+CUTAPPEND

+Apoia-se no início do bloco de corte para o caráter associado com a 

+tecla de roteamento (ver Copiar e Colar). Este comando não limpar o 

+buffer de corte.

+ 

+CUTRECT

+Apoia-se no fim do bloco de corte para o caráter associado com a tecla 

+de roteamento, e anexar a região retangular para o buffer de corte (ver 

+Copiar e Colar).

+

+CUTLINE

+Apoia-se no fim do bloco de corte para o caráter associado com a tecla 

+de roteamento, e anexar a região linear para o buffer de corte (ver 

+Copiar e Colar).

+ 

+COPYCHARS

+Copie o bloco de caráter apoiado-se por duas tecla de roteamento para o 

+buffer de corte (ver Copiar e Colar).

+ 

+APNDCHARS 

+    

+Acrescente o bloco de caráter apoiand-se por duas teclas de roteamento 

+para o buffer de corte (ver Copiar e Colar). 

+ 

+PRINDENT/NXINDENT

+

+Ir para cima/baixo para a próxima linha que não está mais avançado do 

+que a coluna associada à tecla de roteamento.

+ 

+DESCCHAR

+Momentaneamente (veja a opção -M de linha de comando) exibi uma mensagem 

+descrevendo o caráter associado com a tecla de roteamento. Ela revela os 

+valores decimais e hexadecimais do caracter, as cores de primeiro plano 

+e fundo, e, quando presentes, atributos especiais (bright e brink). A 

+mensagem é assim:

+

+char 65 (0x41): white on black bright blink

+ 

+SETLEFT

+Reposiciona horizontalmente a janela braille de modo que sua borda 

+esquerda é a coluna associada à tecla de roteamento. Esta característica 

+torna muito fácil colocar a janela exatamente onde ela é necessária, e, 

+por conseguinte, para mostra o que tem as teclas de roteamento, quase 

+elimina a necessidade de muito movimento fundamenta dajanela (como os 

+comandos CHRLT/CHRRT e HWINLT/HWINRT).

+

+PRDIFCHAR/NXDIFCHAR

+Ir para cima/baixo para a próxima da linha que tem um caráter diferente 

+na coluna associada à tecla de roteamento.

+ 

+4.1.12 Comandos de Suporte 

+ 

+SWITCHVT

+Muda para o terminal virtual cujo número (contagem a partir de 1) 

+corresponde ao da tecla de roteamento. Veja também os comandos de troca 

+de terminal virtual SWITCHVT_PREV/SWITCHVT_NEXT.

+ 

+SETMARK

+Marcar (lembre-se), a posição atual da janela braille num registo 

+associado com a telca de roteamento. Veja o comando GOTOMARK. Este 

+recurso só afeta o terminal virtual atual.

+ 

+GOTOMARK

+Move a janela braille para a posição anteriormente marcada (veja o 

+comando SETMARK) com a mesma tecla de roteamento. Este recurso só afeta 

+o terminal virtual atual.

+

+4.2 - Arquivos de configuração

+

+Padrões do sistema para diversas configurações podem ser estabelecidas 

+dentro de um arquivo de configuração. O nome padrão para este arquivo é 

+/brltty.conf /etc, embora possa ser substituído com a opção de linha de 

+comando -f. Ele não precisa existir. Um modelo para ele pode ser 

+encontrado dentro do subdiretório DOCS.

+

+As linhas em branco são ignoradas. Um comentário começa com um sinal 

+numérico (#), e continua até o fim da linha. As seguintes diretivas são 

+reconhecidas:

+

+api-parameters name=value,...

+Especifica os parâmetros para a Application Programming Interrface. Se o 

+mesmo parâmetro for especificado mais de uma vez, a sua atribuição mais 

+a direita é usada. Para obter uma descrição dos parâmetros aceitos pela 

+interface, consulte o manual de referência BrlAPI. Veja as opções de 

+construção --with-api-parameters para os padrões estabelecidos durante o 

+processo de construção. Esta diretiva pode ser substituído com a opção 

+de linha de comando -A.

+ 

+attributes-table file

+Especifica a tabela de atributos (ver Attributes Tables para obter 

+detalhes). Se um caminho relativo for fornecido, então ele está ancorado 

+em /etc /brltty (ver as opções de compilação --with-data-directory e --

+with-execute-root para mais detalhes). A extensão .atb é opcional. O 

+padrão é usar a tabela built-in (veja a opção de construção --with-

+attributes-table). Esta diretiva pode ser substituída com a opção de 

+linha de comando -a.

+ 

+braille-device device,...

+Especifica o dispositivo para o qual o display braille está ligado 

+(consulte a seção Braille Device Specification). Veja a opção construção 

+--with-braille-device para o padrão estabelecido durante o processo de 

+construção. Esta diretiva pode ser substituída com a opção de linha de 

+comando-d.

+ 

+braille-driver driver,...|auto 

+Especifica o driver do display braille (ver seção Driver Specification). 

+O padrão é a realização de auto-detecção. Esta diretiva pode ser 

+substituída com a opção de linha de comando -b. 

+ 

+braille-parameters [driver:]name=value,...

+Especifica os parâmetros para os drivers do display braille. Se o mesmo 

+parâmetro for especificado mais de uma vez, a sua atribuição mais a 

+direita é usada. Se um nome de parâmetro é qualificado por um driver 

+(consulte a seção Driver Identification Codes), então essa configuração 

+só se aplica ao driver, se não é, então se aplica a todos os drivers. 

+Para obter uma descrição dos parâmetros aceitos por um driver 

+específico, consulte a documentação desse drive. Veja a de opção 

+construção --with-braille-parameters para os padrões estabelecidos 

+durante o processo de construção. Esta diretiva pode ser substituída com 

+a opção de linha de comando -B.

+

+contraction-table file

+Especifica a tabela de contração (ver seção Contraction Tables para mais 

+detalhes). Se um caminho relativo for fornecido, então ele está ancorado 

+em /etc/brltty (ver as opções de construção --with-data-directory e --

+with-execute-root para mais detalhes). A extensão .ctb. é opcional. A 

+tabela de contração é usado quando o recurso de 6-pontos braille é 

+ativado (veja o comando SIXDOTS e a preferência Text Style). O padrão é 

+mostrar descontracionada 6-pontos braille. Esta diretiva pode ser 

+substituída com a opção de linha de comando -c. Não está disponível se a 

+opção de construção --disable-contracted-braille foi especificada.

+ 

+key-table file|auto

+Especifica a tabela de tecla (ver Key Tables para detalhes). Se um 

+caminho relativo for fornecido, então ele está ancorado em /etc/brltty 

+(ver as opções de compilação --with-data-directory e --with-execute-root 

+para mais detalhes). A extensão .ktb é opcional. O padrão é não usar uma 

+tabela de teclas. Esta diretiva pode ser substituída com a opção de 

+linha de comando -k.

+ 

+keyboard-properties name=value,...

+Especifica as propriedades dos teclado(s) a serem monitorados. Se a 

+mesma propriedade for especificada mais de uma vez, a sua atribuição 

+mais a direita é usada. Consulte a seção Keyboard Properties para obter 

+uma lista das propriedades que podem ser especificadas. O padrão é 

+monitorar todos os teclados. Esta diretiva pode ser substituída com a 

+opção de linha de comando -K.

+ 

+midi-device device

+Especifica o dispositivo a ser usado para o Musical Instrument Digital 

+Interface (ver seção MIDI Device Specification). Esta diretiva pode ser 

+substituída com a opção de linha de comando -m. Não está disponível se a 

+opção de construção --disable-midi-support foi especificada.

+ 

+pcm-device device

+Especifica o dispositivo a ser usado para o áudio digital (ver seção PCM 

+Device Specification). Esta diretiva pode ser substituída com a opção 

+linha de comando -p. Não está disponível se a opção de construção --

+disable-pcm-support foi especificada.

+

+preferences-file file

+Especifica a localização do arquivo que sera usada para salvar e carregar as 

+preferencias de usuario. Se um caminho relativo é fornecido, será anexado em 

+/var/lib/brltty. O padrão é usado em brltty.prefs. O diretório pode ser reescrit 

+com o comando -F.

+

+release-device boolean

+Querendo ou não, para liberar o dispositivo ao qual o display braille 

+está ligado quando a tela ou janela atual não pode ser lido.

+ 

+On(on)

+      Libera o dispositivo.

+       

+      Off(off)

+      Não liberar o dispositivo.

+

+A configuração padrão é ligado em plataformas Windows e desligado em 

+todas as outras plataformas. Esta diretiva pode ser substituída com a 

+opção de linha de comando -r.

+ 

+screen-driver driver

+Especifica o driver da tela (ver seção Drivers de Tela Suportados). Veja 

+a opção de construção --with-screen-driver para o padrão estabelecido 

+durante o processo de compilação. Esta diretiva pode ser substituída com 

+a opção de linha de comando -x.

+ 

+screen-parameters [driver:]name=value,...

+Especifica os parâmetros para os drivers de tela. Se o mesmo parâmetro 

+for especificado mais de uma vez, a sua atribuição mais a direita é 

+usada. Se um nome de parâmetro é qualificado por um driver (consulte a 

+seção Drivers de Tela Suportados), em seguida, a definição só se aplica 

+ao drive, se não é, então, se aplica a todos os drivers. Para obter uma 

+descrição dos parâmetros aceitos por um driver específico, consulte a 

+documentação do drive. Veja a opção de construção --with-screen-

+parameters para os padrões estabelecidos durante o processo de 

+construção. Esta diretiva pode ser substituída com a opção de linha de 

+comando -X.

+ 

+speech-driver driver,...|auto

+Especifica o driver do sintetizador de voz (ver seção Especificação de 

+Driver). O padrão é a realização de auto-detecção. Esta diretiva pode 

+ser substituída com a opção de linha de comando -s. Não está disponível 

+se a opção de construção --disable-speech-support foi especificada. 

+

+speech-fifo file

+Especifica a FIFO (um arquivo especial que funciona como um tubo) que é 

+usado por outras aplicações que desejam ter acesso ao driver de fala 

+BRLTTY. Esta diretiva pode ser substituída com a opção de linha de 

+comando -F. Não está disponível se a opção de construção --disable-

+speech-support  foi especificada.

+ 

+speech-parameters [driver:]name=value,...

+Especifica os parâmetros para os drives de sintetizador de voz. Se o 

+mesmo parâmetro é especificado mais de uma vez, a sua atribuição mais a 

+direita é usada. Se um nome de parâmetro é qualificado por um driver 

+(consulte a seção Código de Identificação de Driver), então essa 

+configuração só se aplica ao drive, senão se aplica a todos os drivers. 

+Para obter uma descrição dos parâmetros aceitos por um driver 

+específico, consulte a documentação do drive. Veja a opção de construção 

+--with-speech-parameters para os padrões estabelecidos durante o 

+processo de construção. Esta diretiva pode ser substituída com o opção 

+de linha de comando -S.

+ 

+text-table file|auto

+Especifica a tabela de texto(veja a seção Tabelas de Texto para 

+detalhes). Se um caminho relativo for fornecido, então ele está ancorado 

+em /etc/brltty (veja as opções de compilação –with-data-directory e –

+with-execute-root para mais detalhes). A extensão .ttb é opcional. O 

+padrão é executar auto-seleção do locale-based, com alternativa para a 

+tabela build-in (veja a opção compilação --with-text-table). Esta 

+diretiva pode ser subtituída com a opção de linha de comando –t.

+

+speech-input name

+Especifica o nome do arquivo do objeto do sistema (FIFO, pipe name, socket name, 

+etc) que pode ser usado com outras aplicações para conversão text-to-speech via 

+driver de fala BRLTTY. Este diretório pode ser reescrito com o comando -i.

+Não está disponivel se a opção na compilação não for especificada --disable-

+speech-support.

+     

+4.3 - Opções de Linha de Comando

+

+Muitas configurações podem ser explicitamente especificadas ao chamar 

+BRLTTY. Os comandos brltty aceitam as seguintes opções:

+ 

+-afile --attributes-table=file

+Especifica a tabela de atributos (veja seção Tabelas de Atributos). Se 

+um caminho relativo for fornecido, então ele está ancorado em 

+/etc/brltty (veja as opções de compilação –with-data-directory e –with-

+execute-root para mais detalhes). A extensão .atb não é necessária.Veja 

+as configurações do arquivo attributes-table para a configuração padrão 

+de run-time. Esta configuração pode ser alterada com a preferência 

+Tabela de Atributos.    

+

+-bdriver,...|auto --braille-driver=driver,...|auto

+Especifica o drive do display braille (veja seção Especificação de 

+Driver). Veja as configurações do arquivo braille-drive para a 

+configuração padrão de run-time.

+ 

+-cfile --contraction-table=file

+Especifica a tabela de contração (veja seção Tabela de Contração). Se um 

+caminho relativo for fornecido, então ele está ancorado em /etc/brltty 

+(veja as opções de compilação --with-data-directory e --with-execute-

+root para mais detalhes). A extensão .ctb é opcional. A tabela de 

+contração é usado quando o recurso de 6-pontos braille é ativado (veja o 

+comando SIXDOTS e a preferência Estilo de Texto). Veja as configurações 

+do arquivo tabela de contração para a configuração padrão de run-time. 

+Esta configuração pode ser alterada com a preferência Tabela de 

+Contração. Esta opção não está disponível se a opção de construção --

+disable-contracted-braille foi especificada.

+ 

+-ddevice,... --braille-device=device,...

+Especifica o dispositivo para o qual o display braille está ligado (ver 

+seção Especificação Dispositivo Braille). Veja as configurações do 

+arquivo braille-device para a configuração padrão de run-time.

+ 

+-e --standard-error

+Escreve mensagens de disgnóstico de erro padrão. O padrão é gravá-los 

+via syslog

+ 

+-ffile --configuration-file=file

+Especifica o local do arquivo de configuração que será utilizado para a 

+criação das configurações padrões de run-time.

+

+-h -–help

+Mostra um resumo das opções de linha de comando aceitas pelo BRLTTY, e 

+depois sai.

+ 

+-kfile --key-table=file

+Especifica a tabela de teclas (veja seção Key Tables pra detalhes). Se 

+um caminho relativo for fornecido, então ele está ancorado em 

+/etc/brltty (veja as opções de compilação --with-data-directory e --

+with-execute-root para mais detalhes). A extensão .ktb é opcional. Esta 

+configuração pode ser alterada com a preferência Tabela de Telas.

+ 

+-llevel --log-level=level

+Especifique o limite de gravidade para geração de mensagens de 

+diagnóstico. Os seguintes níveis são reconhecidos.

+

+0

+      emergência

+1

+      alerta

+2

+      crítico

+3

+      erro

+4

+      aviso

+5

+      advertência

+6

+      informações

+7

+      debug

+

+Ou o número ou o nome pode ser fornecido, bem como o nome pode ser 

+abreviado. Se não for especificado, então o nível informação é assumido 

+(veja a opção -q para mais detalhes).

+ 

+-mdevice --midi-device=device

+Especifica o dispositivo a ser usado para o Music Instrument Digital 

+Interface (ver seção Especificação de Dispositivos MIDI). Veja as 

+configurações do arquivo midi-device para a configuração padrão de run-

+time. Esta opção não está disponível se a opção de construção --disable-

+midi-support foi especificada.

+

+-n --no-daemon

+Especifica que BRLTTY irá ficar em primeiro plano. Se não for 

+especificado, então BRLTTY torna-se um processo em background (daemon) 

+após se inicialização, mas antes de iniciar qualquer um dos drivers 

+selecionados.

+ 

+-pdevice --pcm-device=device

+Especifica o dispositivo a ser usado para o áudio digital (ver seção 

+Especificação do Dispositivo PCM). Veja as configurações do arquivo pcm-

+device para a configuração padrão de run-time. Esta opção não estará 

+disponível se a opção a opção de compilação --disable-pcm-support foi 

+especificado.

+ 

+-q –quiet

+Log menos informação. Esta opção altera o nível de log (veja a opção -l) 

+para notice se qualquer das opção -v ou -V seja especificado, e para 

+warnig caso contrário.

+ 

+-r --release-device

+Libera o dispositivo para o qual o display braille está ligado quando a 

+tela ou janela atual não pode ser lido. Veja as configurações do arquivo 

+release-device para a configuração padrão de run-time.

+ 

+-sdriver,...|auto --speech-driver=driver,...|auto

+Especifica o driver do sintetizador de voz (ver seção Especificação de 

+Driver). Veja as configurações do arquivo speech-driver para a 

+configuração padrão de run-time. Esta opção não estará disponível se a 

+opção de construção --disable-speech-support foi especificado.

+ 

+-tfile --text-table=file

+Especifica a tabela de texto (ver seção Tabela de Texto). Se um caminho 

+relativo for fornecido, então ele está ancorado em /etc/brltty (ver as 

+opções de construção --with-data-directory e --with-execute-root para 

+mais detalhes). A extensão .ttb é opcional. Veja as configurações do 

+arquivo text-table para a configuração padrão de run-time. Esta 

+configuração pode ser alterada com a preferência Tabela Texto.

+ 

+-v –verify

+Mostrar a versão atual do BRLTTY, do lado do servidor de sua interface 

+de programação de aplicativo, do braille selecionado e drivers de fala, 

+e depois sai. Se a opção-q não for especificado, então também exibir os 

+valores das opções depois de todas as fontes serem consideradas. Se mais 

+de um driver braille (veja a opção de linha de comando -b) e/ou mais de 

+um dispositivo braile (veja a opção linha de comando -d) tenha sido 

+especificado, então a autodetecção do display Braille é realizado. Se 

+mais de um driver de fala(veja a opção de linha de comando -s) tenha 

+sido especificado, então a autodetecção do sintetizador de voz é 

+realizada.

+ 

+-xdriver --screen-driver=driver

+Especifica o driver da tela (ver seção Drivers Suportados de Tela). Veja 

+as configurações do arquivo screen-driver para a configuração padrão de 

+run-time.

+ 

+-Aname=value,... --api-parameters=name=value,...

+Especifica os parâmetros para a Application Programming Interface. Se o 

+mesmo parâmetro é especificado mais de uma vez, a sua atribuição mais a 

+direita é usada. Para obter uma descrição dos parâmetros aceitos pela 

+interface, consulte o manual de referência BrlAPI. Veja as configurações 

+do arquivo api-parameters para a configuração padrão de run-time.

+

+-B[driver:]name=value,... --braille-parameters=[driver:]name=value,...

+Especifica os parâmetros para os drivers do display braille. Se o mesmo 

+parâmetro é especificado mais de uma vez, a sua atribuição mais a 

+direita é usada. Se um nome de parâmetro é qualificado por um driver 

+(consulte a seção Códigos de Identificação de Drivers), então essa 

+configuração só se aplica ao drive, se não é, então se aplica a todos os 

+drivers. Para obter uma descrição dos parâmetros aceitos por um driver 

+específico, consulte a documentação do driver. Veja as configurações do 

+arquivo braille-parameters para a configuração padrão de run-time.

+ 

+-E --environment-variables

+Reconhece as variáveis de ambiente para determinar as configurações 

+padrão para as opções de linha de comando não especificada (ver seção 

+Opções de Linha de Comando). Se esta opção for especificada, e se a 

+variável de ambiente associados a uma opção não especificada é definida, 

+então o valor da variável de ambiente é utilizado. Os nomes dessas 

+variáveis de ambiente são baseados em nomes grandes das opções que 

+correspondem a:

+

+?	Todas as letras são em maiúsculas.

+?	Os underscores (_) são utilizados em vez de sinais de menos (-).

+?	O prefixo BRLTTY_ é adicionado.

+

+Esta opção é particularmente útil no sistema operacional Linux, pois 

+permite configuração padrão a ser passado para BRLTTY através de 

+parâmetros de inicialização. As seguintes variáveis de ambiente são 

+suportadas:

+ 

+BRLTTY_API_PARAMETERS

+Parâmetros para a Application Programming Interface (veja a opção de 

+linha de comando -A).

+ 

+BRLTTY_ATTRIBUTES_TABLE

+A tabela de atributos (veja a opção de linha de comando -a).

+      

+BRLTTY_BRAILLE_DEVICE

+O dispositivo do display braille (veja a opção de linha de comando -d).

+ 

+BRLTTY_BRAILLE_DRIVER

+O driver do display braille (veja a opção de linha de comando -b).

+ 

+BRLTTY_BRAILLE_PARAMETERS

+Parâmetros para o driver do display braille (veja a opção de linha de 

+comando -B).

+ 

+BRLTTY_CONFIGURATION_FILE

+O arquivo de configuração (veja a opção de linha de comando -f).

+ 

+BRLTTY_CONTRACTION_TABLE

+A tabela de contração (veja a opção de linha de comando -c).

+ 

+BRLTTY_KEY_TABLE

+A tabela de teclas (veja a opção de linha de comando -k).

+ 

+BRLTTY_KEYBOARD_PROPERTIES

+As propriedades do teclado (veja a opção de linha de comando -K).

+ 

+BRLTTY_MIDI_DEVICE

+O dispositivo Musical Instrument Digital Interface (veja a opção de 

+linha de comando -m).

+ 

+BRLTTY_PCM_DEVICE

+O dispositivo de audio digital (veja a opção de linha de comando -p).

+ 

+BRLTTY_RELEASE_DEVICE

+Querendo ou não libera o dispositivo para o qual o display braille está 

+ligado quando a tela ou janela atual não pode ser lido (veja a opção de 

+linha de comando -r).

+ 

+BRLTTY_SCREEN_PARAMETERS

+Parâmentros para o driver da tela (veja a opção de linha de comando -X).

+ 

+BRLTTY_SPEECH_DRIVER

+O driver do sintetizador de voz (veja a opção de linha de comando -s).

+ 

+BRLTTY_SPEECH_FIFO

+A passagem FIFO da voz ( veja a opção de linha de comando -F).

+ 

+BRLTTY_SPEECH_PARAMETERS

+Parâmetros para o driver do sintetizado de voz ( veja a opção de linha 

+de comando -S).

+ 

+BRLTTY_TEXT_TABLE

+A tabela de texto ( veja a opção de linha de comando -t).

+ 

+-Ffile --speech-fifo=file

+Especifica a FIFO (um arquivo especial que funciona como um tubo) que é 

+usado por outras aplicações que desejam ter acesso ao driver BRLTTY da 

+fala. Se não é especificado, FIFO não é criado. Veja as configurações do 

+arquivo speech-fifo para a configuração padrão de run-time. Esta opção 

+não estará disponível se a opção de construção --disable-speech-support 

+ foi especificada.

+ 

+-i --install-service

+Instala o BRLTTY como o serviço BrlAPI. Isso significa que:

+

+      · BRLTTY será iniciado automaticamente quando o sistema é 

+inicializado.

+

+      · Aplicações podem saber que um servidor BrlAPI está sendo 

+executado.

+

+Esta opção só é suportado na plataforma Windows.

+ 

+-Kname=value,... --keyboard-properties=name=value,...

+Especifica as propriedades do teclado(s) a ser monitorado. Se a mesma 

+propriedade for especificada mais de uma vez, a sua atribuição mais a 

+direita é usada. Consulte a seção Keyboard Prperties para obter uma 

+lista das propriedades que podem ser especificadas. Veja as 

+configurações do arquivo keyboard-properties para a configuração padrão 

+de run-time.

+ 

+-Mcsecs --message-delay=csecs

+Especifica a quantidade de tempo (em centésimos de segundo) que o BRLTTY 

+mantém seu próprio gerador de mensagens internamente no display braille. 

+Se não for especificado, então 400 (4 segundos) é assumida.

+

+-N --no-api

+Desativa a interface de programação de aplicativo.

+ 

+-Pfile --pid-file=file

+Especifica o arquivo onde o BRLTTY escreve o seu identificador de 

+processo (PID). Se não especificado, BRLTTY não escreve o seu processo 

+de identificação em qualquer lugar.

+ 

+-R --remove-service

+Remove o serviço BrlAPI. Isso significa que:

+

+· BRLTTY não será iniciado automaticamente quando o sistema é 

+inicializado.

+· Aplicações podem saber que nenhum servidor BrlAPI está sendo 

+executado.

+

+Esta opção só é suportado na plataforma Windows.

+ 

+-S[driver:]name=value,... --speech-parameters=[driver:]name=value,...

+Especifica os parâmetros para os drivers do sintetizador de voz. Se o 

+mesmo parâmetro é especificado mais de uma vez, a sua atribuição mais a 

+direita é usada. Se um nome de parâmetro é qualificado por um driver 

+(consulte a seção Código de Identificação de Drivers), então essa 

+configuração só se aplica ao driver, se não se aplica a todos os 

+drivers. Para obter uma descrição dos parâmetros aceitos por um driver 

+específico, consulte a documentação do driver. Veja as configurações do 

+arquivo speech-parameters para a configuração padrão de run-time.

+

+-Ucsecs --update-interval=csecs

+Especifica o intervalo (em centésimos de segundo) em que a janela do 

+braille é atualizada com o novo conteúdo da tela. Se não for 

+especificado, então, 4 (40 milisegundos) é assumido.

+ 

+-V --version

+Mostra a versão atual do BRLTTY, do lado do servidor de sua interface de 

+programação de aplicativo, e dos drivers que foram ligados ao seu 

+binário, e depois sai. Se a opção-q não for especificado, então também 

+exibir informações de direitos autorais.

+ 

+-X[driver:]name=value,... --screen-parameters=[driver:]name=value,...

+Especifica os parâmetros para os drivers da tela. Se o mesmo parâmetro é 

+especificado mais de uma vez, a sua atribuição mais a direita é usada. 

+Se um nome de parâmetro é qualificado por um driver (consulte a seção 

+Drivers Suportados de Tela), então essa configuração só se aplica a esse 

+driver, se não é, então se aplica a todos os drivers. Para obter uma 

+descrição dos parâmetros aceitos por um driver específico, consulte a 

+documentação do driver. Veja as configurações do arquivo screen-

+parameters para a configuração padrão de run-time.

+

+5. Descrições das Características

+

+5.1 Cursor de roteamento

+ 

+Ao mover a janela de Braille em torno da tela, para análise do texto, 

+por exemplo, em um editor, muitas vezes você precisa levar o cursor até 

+um carácter especifico dentro da janela do braille. Você provavelmente 

+vai achar que isso uma tarefa bastante difícil para um número de razões. 

+Uma delas é que você pode não saber onde está o cursor, e que você pode 

+perder seu lugar ao tentar encontrá-lo. Outra é que o cursor pode mover 

+imprevisivelmente quando as setas forem pressionadas (alguns editores, 

+por exemplo, não permitem que o cursor seja mais direito ao fim da 

+linha). Cursor roteamento fornece apenas essa capacidade de saber onde 

+está o cursor, através da simulação das prensas mesma seta-chave que 

+você tem que digitar manualmente, e pelo acompanhamento do progresso do 

+cursor que se move. 

+

+Alguns displays braille têm um botão, conhecido como uma tecla de 

+cursor, acima de cada célula. Essas chaves usam o comando ROUTE para 

+encaminhar o cursor para o local desejado. 

+ 

+Cursor de roteamento é muito conveniente e eficaz, porém não totalmente 

+confiável. Uma razão para isso é que a sua implementação atual assume 

+VT100 cursor seqüências de escape chave. Outra é que algumas aplicações 

+fazem coisas fora do padrão em resposta ao detectar que uma tecla do 

+cursor foi pressionado. Um pequeno problema encontrado dentro de alguns 

+editores (como vi), como já mencionado acima, é que eles jogam em algum 

+movimento imprevisível horizontal quando o movimento vertical é 

+solicitado porque eles não permitem que o cursor fiquem à direita do 

+final de uma linha . Um dos principais problemas encontrados dentro de 

+alguns navegadores web (como o lynx) é que as teclas de seta para 

+baixo/cima são usadas para mover entre os links (que podem saltar linhas 

+e / ou mover o cursor horizontalmente, mas que raramente move o cursor 

+uma linha na direção desejada), e que a esquerda e teclas de seta para a 

+direita são usados para selecionar as ligações (que não tem 

+absolutamente nada a ver com qualquer forma de movimento do cursor que 

+seja, e que até muda totalmente o conteúdo da tela). 

+ 

+Cursor de roteamento não funciona muito bem em um sistema muito 

+carregado, e definitivamente não funciona muito bem ao trabalhar em um 

+sistema remoto através de um link lento. Isto é assim porque de todas as 

+verificações que devem ser feitos ao longo do caminho, a fim de lidar 

+com o movimento do cursor imprevisível, a fim de assegurar que qualquer 

+erro tem pelo menos uma chance de lutar para ser desfeito. Mesmo BRLTTY 

+tenta ser bastante inteligente, ainda deve essencialmente esperar para 

+ver o que acontece após cada pressionamento da seta-chave. 

+ 

+Uma vez que uma solicitação de cursor de roteamento foi feita, BRLTTY 

+continua tentando encaminhar o cursor para o local pretendido, até um 

+tempo limite de expiração antes que o cursor alcança essa localização, o 

+cursor parece estar se movendo na direção errada, ou se mudar para um 

+terminal virtual diferente. Uma tentativa é feita para usar o movimento 

+vertical para trazer o cursor para a linha direita, e, se essa for bem 

+sucedida, essa tentativa é feita, então usa o movimento horizontal para 

+trazer o cursor para a coluna da direita. Se outra solicitação é feita 

+enquanto ainda se está em andamento, em seguida, a primeira é anulada e 

+o segundo é iniciada. 

+ 

+Um cursor mais seguro, porém menos potente de roteamento, CSRJMP_VERT, 

+usa apenas o movimento vertical para trazer o cursor para qualquer lugar 

+no top da linha da janela braille. É especialmente útil em um conjunto 

+com as aplicações (como lynx) onde o movimento do cursor horizontal 

+nunca deve ser tentado.

+

+5.2 Copiar e Colar

+ 

+Este recurso permite que você pegue algum texto que já está na tela e 

+digitá-la na posição atual do cursor. Usando ele economiza tempo e evita 

+erros quando uma peça longa e/ou complicadas do texto precisa ser 

+copiado, e mesmo quando a mesma peça curta e simples do texto precisa 

+ser copiado muitas vezes. É particularmente útil para coisas como nomes 

+de arquivo longos, linhas de comandos complicados, os endereços de e-

+mail e URL. Cortar e colar texto envolve três passos simples: 

+ 

+  1. Marque o canto superior esquerdo da área retangular ou no início da 

+área linear na tela, que deve ser agarrado (corte). Se o display tem 

+chaves de roteamento, em seguida, move a janela braille, para que o 

+primeiro carácter a ser cortado aparecer em qualquer lugar dentro dela 

+e, em seguida:

+

+  · Invocar o comando CUTBEGIN para iniciar um novo buffer de corte.

+  · Invocar o comando CUTAPPEND para anexar ao buffer de corte 

+existentes, pressionando as chave associadas a ele e depois pressionar a 

+tecla de roteamento associado com o personagem.

+

+2. Marque o canto inferior direito da área retangular ou o fim da área 

+linear na tela, que deve ser agarrado (corte). Se o display tem chaves 

+de roteamento, move a janela de braille para que o último caracter a ser 

+cortado aparece em qualquer lugar dentro dela e, em seguida:

+

+  · Invocar o comando CUTRECT para cortar uma área retangular.

+  · Invocar o comando CUTLINE para cortar uma área linear pressionando 

+as chave associadas a ele e depois pressionar a tecla de roteamento 

+associado com o carácter. Marcando o fim da área de corte acrescenta o 

+conteúdo da tela selecionada com o buffer de corte. O excesso de espaço 

+em branco é removido no final de cada linha no buffer de corte de modo 

+que os espaços indesejados no final não vão ser colado para trás do 

+carácter de controle que serão substituídos por espaços em branco.

+ 

+3. Inserir (colar) o texto onde ele é necessário. Coloque o cursor sobre 

+o carácter onde o texto deve ser colado, e invocar o comando PASTE. Você 

+pode colar o mesmo texto varias vezes sem recortar. Esta descrição 

+assume que você já está em algum tipo de modo de entrada. Se você colar 

+quando você estiver em algum outro tipo de modalidade (como comando vi), 

+então é melhor você estar ciente de que os caracteres no buffer de corte 

+vão fazer. 

+ 

+O buffer de corte também é usado pelos comandos PRSEARCH/NXSEARCH.

+

+5.3 Suporte do Ponteiro (Mouse) via GPM

+ 

+Se BRLTTY é configurado com a opção de compilação --enable-gpm no 

+sistema onde a aplicação gpm foi instalada, em seguida, ele vai 

+interagir com o cursor (mouse). 

+ 

+Mover o ponteiro e arrastar a janela braille (veja Window Follows 

+Pointer). Sempre que o ponteiro é movido para além da borda da janela do 

+braille, a janela do braille é arrastada (um carácter por vez). Isto dá 

+ao usuário braille outra forma bidimensional para inspecionar o conteúdo 

+da tela ou mover-se rapidamente a janela braille para o local desejado. 

+Ele também dá uma visão de observador de maneira fácil para mover a 

+janela de braille para algo que ele gostaria que o usuário Braille possa 

+ler. 

+ 

+O gpm usa vídeo reverso para mostrar onde o cursor está. Sublinhando os 

+caracteres em destaque (veja o comando ATTRVIS) deve ser ativado, 

+portanto, quando o usuário braille pretende usar o ponteiro. 

+ 

+Esse recurso também permite o acesso do usuário em Braille para gpm é 

+cortado e colado. Embora você deve ler a documentação própria gpm, aqui 

+estão algumas notas sobre a forma como ele funciona.

+

+   · Cópia do carácter atual para o buffer cortado por um único clique 

+com o botão esquerdo.

+   · Cópia da palavra atual (espaço delimitado) para o buffer de corte 

+com um duplo clique com o botão esquerdo.

+   · Cópia da linha atual para o buffer de corte de triplo-clique com o 

+botão esquerdo.

+   · Cópia de uma região linear para o buffer de corte da seguinte 

+forma:

+

+     1. Coloque o ponteiro do mouse sobre o primeiro carácter da região.

+     2. Pressione (e mantenha pressionado) o botão esquerdo.

+     3. Mova o ponteiro para o último carácter da região (todos os 

+caracteres selecionados são destacados).

+     4. Solte o botão esquerdo.

+

+   · Cole (entrada) o conteúdo atual do buffer de corte clicando o botão 

+do meio do mouse de três botões, ou clicando com o botão direito do 

+mouse de dois botões.

+   · Aparece para o buffer de corte usando o botão direito do mouse de 

+três botões.

+

+5.4 Alerta de Tunes

+ 

+BRLTTY alerta para a ocorrência de eventos significativos, tocando 

+melodias curtas predefinidas. Este recurso pode ser ativado e desativado 

+com um comando TUNES ou o Alert Tunes. As músicas são tocadas através do 

+apito interno, por padrão, mas outras alternativas podem ser 

+selecionadas com a preferência de Tune Device. 

+ 

+Cada evento significativo está associado, do maior para a menor 

+prioridade, com um ou mais dos seguintes procedimentos: 

+ 

+a tune

+Se uma música tem sido associado com o evento, se o Alert Tunes (veja 

+também o comando TUNES) está ativo e se o dispositivo de ajuste for 

+selecionado (veja a preferência de Dispositivos Tune) pode ser aberto, 

+então a música é tocada.

+ 

+a dot pattern

+Se um padrão de pontos tem sido associado com o evento, e se a 

+preferência do Alert Dots está ativo, então o padrão de pontos é exibido 

+rapidamente em cada célula braille. Alguns displays braille não 

+respondem com rapidez suficiente para que este mecanismo funcione de 

+forma eficaz.

+ 

+a message

+Se a mensagem tem sido associada com o evento, e se a preferência Alert 

+Messages estiver ativa, ela é exibido por alguns segundos (veja a opção 

+-m de linha de comando).

+ 

+Estes eventos incluem:

+

+      · Quando o driver do display Braille começa ou pára.

+       

+      · Quando um comando demorado concluí.

+       

+      · Quando um comando não pode ser executado.

+       

+      · Quando uma marca é definida.

+       

+      · Quando o início ou o fim do bloco de corte é definida.

+       

+      · Quando um recurso é ativado ou desativado.

+       

+      · Quando o controle de cursor é ligado ou desligado.

+       

+      · Quando a imagem da tela é congelada ou descongelada.

+       

+· Quando a janela braille envolvida ou até o início da próxima    

+linha ou até o fim da linha anterior.

+       

+      · Quando as linhas idênticas são ignorados.

+       

+      · Quando uma solicitação não pode ser executada.

+       

+      · Quando o cursor começa o roteamento, termina, ou falha.

+       

+5.5 Configurações de Preferências

+ 

+Quando BRLTTY é iniciado, ele carrega um arquivo que contém as 

+definições de preferências. O arquivo não precisa existir, ele é criado 

+pela primeira vez, as configurações são salvas com o comando PREFSAVE. 

+As configurações mais recentemente salvas podem ser restauradas a 

+qualquer momento com o comando PREFLOAD. 

+ 

+O nome para este arquivo é /etc/brltty-driver.prefs. Onde driver tem 

+duas letras do código de identificação de driver. 

+ 

+5.5.1 Menu de Preferências 

+ 

+As configurações de preferências são salvos como dados binários que, 

+portanto, não pode ser editado manualmente. BRLTTY, no entanto, tem um 

+menu simples do que você pode facilmente mudá-los. 

+ 

+O menu é ativado pelo comando PREFMENU. O display braille (ver -m) 

+mostra o título do menu, e em seguida, apresenta o item atual e sua 

+configuração atual. 

+ 

+5.5.1.1 Menu de Navegação 

+ 

+Consulte o Comandos do Menu de Navegação para a lista completa dos 

+comandos que permitem que você selecione os itens e altere as 

+configurações dentro do menu. Para compatibilidade com drivers antigos, 

+os comandos de janela do movimento, que alteraram os significados, neste 

+contexto, também pode ser usado. 

+ 

+ 	TOP/ BOT, TOP_LEFT/BOT_LEFT, PAGE_UP/PAGE_DOWN 

+      Ir para o primeiro item / último no menu (o mesmo como

+      MENU_FIRST_ITEM/MENU_LAST_ITEM).

+ 

+LNUP/LNDN, PRDIFLN/NXDIFLN, CURSOR_UP/CURSOR_DOWN

+Ir para o item anterior / seguinte no menu (o mesmo que 

+MENU_PREV_ITEM/MENU_NEXT_ITEM). 

+ 

+WINUP/WINDN, CHRLT/CHRRT, CURSOR_LEFT/CURSOR_RIGHT, BACK/HOME 

+Diminuir aumentar / definir o item de menu atual (o mesmo que 

+MENU_PREV_SETTING/MENU_NEXT_SETTING). 

+

+Notas:

+

+· As chaves de roteamento também pode ser usado para selecionar uma 

+opção para o item atual. Se o item tem ajustes numéricos, em seguida, 

+toda a linha de chaves de roteamento atua como uma barra de rolagem, que 

+abrange toda a gama de valores válidos. Se o item tem chamado 

+configurações, então as chaves de roteamento correspondem com as 

+configurações.

+

+· Use o comando PREFLOAD desfazer todas as alterações que foram feitas 

+desde a sua entrada no menu.

+

+· Use o comando PREFMENU (novamente) para deixar as novas configurações 

+de fato, sair do menu, e retomar a operação normal. Se a opção "Save 

+Settings on Exit" é definido, então, além disso, as novas configurações 

+são gravadas no arquivo de configurações de preferências. Qualquer 

+comando não é reconhecido pelo sistema de menu também faz essas coisas 

+mesmo. 

+ 

+5.5.1.2 Itens de Menu 

+ 

+Salvar ao sair 

+ 

+   Ao sair do menu de preferências: 

+ 

+   No

+   Não salvar automaticamente as configurações de preferências.

+ 

+   Yes

+   Salvar automaticamente as configurações de preferências. 

+ 

+   A configuração inicial é No. 

+ 

+Text Style

+

+   Ao exibir o conteúdo da tela (veja o comando DISPMD), os caracteres 

+mostram:

+

+   8-dot

+   Com os oito pontos.

+

+   6-dot

+   Com apenas pontos de 1 a 6. Se uma tabela de contração foi 

+selecionada(veja a opção -c da linha de comando e da tabela de contração 

+do arquivo de configuração), então ele é usado.

+

+Esta configuração também pode ser alterado com o comando SIXDOTS.

+ 

+Skip Identical Lines

+

+Quando se deslocam para cima ou para baixo exatamente uma linha com os 

+comandos LNUP/LNDN, bem como o recurso de FWINRT/FWINLT e comandos 

+FWINLTSKIP/FWINRTSKIP:

+

+   No

+   Não pule linhas passadas, que têm o mesmo conteúdo da linha atual.

+

+   Yes

+   Pula linhas passadas, que têm o mesmo conteúdo da linha atual.

+ 

+   Esta configuração também pode ser alterado com o comando SKPIDLNS. 

+ 

+Skip Blank Windows 

+ 

+Quando se deslocam para a esquerda ou direita, com os comandos 

+FWINLT/FWINRT:

+ 

+       No

+   Não pular janelas em branco.

+ 

+       Yes

+   Pular janelas em branco. 

+ 

+Esta configuração também pode ser alterado com o comando SKPBLNKWINS. 

+ 

+Which Blank Windows 

+ 

+Se as janelas em branco devem ser ignorados: 

+ 

+       All

+   passar todos eles.

+   

+   Fim da Linha

+   apenas ignorar aqueles que estão no final (do lado direito) de uma 

+linha.

+

+   Resto da Linha

+   apenas ignorar aqueles que estão no final (do lado direito) de uma 

+linha durante a leitura para a frente, e no início (do lado esquerdo) de 

+uma linha durante a leitura para trás. 

+ 

+Sliding Window 

+ 

+Se o cursor está a ser controlado (veja o comando CSRTRK), e o cursor 

+move-se demasiado perto (ou mais) uma das janela do Braille: 

+ 

+       No

+   Reposicionar horizontalmente a janela de modo que sua extremidade 

+esquerda é um múltiplo de sua largura da borda esquerda da tela.

+ 

+       Yes

+   Horizontalmente reposicionar a janela de tal forma que o cursor, 

+mantendo-se nesse lado da janela, está mais próximo do centro. 

+ 

+Esta configuração também pode ser alterado com o comando SLIDEWIN. 

+ 

+Eager Sliding Window 

+ 

+Se a janela de braille é slide: 

+ 

+       No

+   Reposicioná-lo sempre que o cursor se move para além de uma ou outra 

+extremidade.

+ 

+       Yes

+   Reposicioná-lo sempre que o cursor move-se muito perto de uma das 

+extremidades. 

+ 

+A configuração inicial é No. 

+ 

+Window Overlap 

+ 

+Quando se deslocam para a esquerda ou direita com os comandos 

+FWINLT/FWINRT, esta configuração especifica quantos caracteres 

+horizontalmente adjacente janelas braille devem sobrepor-se uns aos 

+outros por. O ajuste inicial é 0. 

+

+AutoRepeat 

+ 

+Enquanto a chave (combinação) para um comando permanece pressionado: 

+ 

+       No

+   Não repita automaticamente o comando.

+ 

+       Yes

+   Automaticamente repetir o comando em um intervalo regular, após um 

+atraso inicial.

+

+Os seguintes comandos são elegíveis para auto reply:

+      · O comando LNUP/LNDN.

+      · O comando PRDIFLN/NXDIFLN.

+      · O comando CHRLT/CHRRT.

+      · Operações panorâmica da janela Braille (ver AutoRepeat Panning).

+      · Operações de Page-Up e Page-Down.

+      · Operações de cursor-up e cursor-down.

+      · Operações de cursor esquerda e cursor direita.

+      · Operações de Backspace e Delete.

+      · Carácter de entrada.

+ 

+Apenas alguns drivers suportam esta funcionalidade, a principal 

+limitação é que muitos display braille não sinalizam tanto teclas 

+pressionadas e chaves  como eventos separados distintamente. Esta 

+configuração também pode ser alterado com o comando AutoRepeat. A 

+configuração inicial é YES. 

+ 

+AutoRepeat Panning 

+ 

+Quando a preferência Autorepeat está habilitada: 

+ 

+       No

+   Não há operações de autorepeat da janela panorâmica em braille. 

+

+       Yes

+   Operações de AutoRepeat para janela panorâmica em braille. 

+ 

+Esta preferência atinge os comandos FWINLT/FWINRT. A configuração 

+inicial é No. 

+ 

+AutoRepeat Delay 

+ 

+Quando um carácter é a auto repetitivo, esta configuração especifica a 

+quantidade de tempo (veja Configurações de Tempo), que deve passar antes 

+auto repetição começa. A configuração inicial é 50. 

+ 

+AutoRepeat Interval

+ 

+Quando um carácter está sendo auto repetitivo, esta configuração 

+especifica a quantidade de tempo (veja Configurações de Tempo) entre 

+cada reexecução. A configuração inicial é 10. 

+

+Show Cursor

+ 

+Ao exibir o conteúdo da tela (veja o comando DISPMD): 

+ 

+       No

+   Não mostrar o cursor.

+ 

+       Yes

+   Mostrar o cursor. 

+ 

+Esta configuração também pode ser alterado com o comando CSRVIS. A 

+configuração inicial é Yes. 

+ 

+Cursor Style 

+ 

+Ao mostrar o cursor, representá-lo:

+

+   Underline

+   Com pontos 7 e 8.

+   

+   Block

+   Com os oito pontos.

+ 

+Esta configuração também pode ser alterado com o comando CSRSIZE. 

+ 

+Blinking Cursor 

+ 

+Quando o cursor está a ser mostrado:

+   

+   No

+   Deixar-se visível o tempo todo.

+   

+   Yes

+   Torná-lo alternadamente, visíveis e invisíveis de acordo com um     

+   intervalo predefinido.

+

+Esta configuração também pode ser alterado com o comando CSRBLINK. 

+ 

+Cursor Visible Time 

+ 

+Quando o cursor está a ser piscou, esta configuração especifica o 

+período de tempo (veja time settings) durante cada ciclo que está a ser 

+visível. A configuração inicial é de 40. 

+ 

+Cursor Invisible Time

+ 

+Quando o cursor está a ser piscou, esta configuração especifica o 

+período de tempo (veja time settings) durante cada ciclo que é ser 

+invisível. A configuração inicial é de 40. 

+

+Show Cursor

+ 

+Ao exibir o conteúdo da tela (veja o comando DISPMD): 

+ 

+       No

+   Não mostrar o cursor. 

+

+       Yes

+   Mostrar o cursor. 

+ 

+Esta configuração também pode ser alterado com o comando CSRVIS. A 

+configuração inicial é Yes. 

+ 

+Cursor Style 

+ 

+Ao mostrar o cursor, representá-lo:

+

+   Underline

+   Com pontos 7 e 8.

+   

+   Block

+   Com os oito pontos.

+ 

+Esta configuração também pode ser alterado com o comando CSRSIZE. 

+ 

+Blinking Cursor 

+ 

+Quando o cursor está a ser mostrado:

+   No

+   Deixar-se visível o tempo todo.

+   

+   Yes

+   Torná-lo alternadamente, visíveis e invisíveis de acordo com um     

+   intervalo predefinido.

+

+Esta configuração também pode ser alterado com o comando CSRBLINK. 

+ 

+Cursor Visible Time 

+ 

+Quando o cursor está a ser piscou, esta configuração especifica o 

+período de tempo (veja time settings) durante cada ciclo que está a ser 

+visível. A configuração inicial é de 40. 

+ 

+Cursor Invisible Time 

+Quando o cursor está a ser piscou, esta configuração especifica o 

+período de tempo (veja time settings) durante cada ciclo que é ser 

+invisível. A configuração inicial é de 40. 

+

+Show Attributes 

+ 

+Ao exibir o conteúdo da tela (veja o comando DISPMD):

+   No

+   Não sublinhar os caracteres em destaque.

+   

+   Yes

+   Sublinhar caracteres em destaque.

+ 

+Esta configuração também pode ser alterado com o comando ATTRVIS. 

+ 

+Blinking Attributes 

+ 

+Quando os caracteres em destaque são sublinhados:

+   

+   No

+   Deixe o indicador visível o tempo todo.

+   

+   Yes

+   Faça o indicador alternadamente visíveis e invisíveis de acordo com 

+um intervalo predefinido.

+ 

+Esta configuração também pode ser alterado com o comando ATTRBLINK. 

+ 

+Attributes Visible Time

+ 

+Quando o carácter sublinhado em destaque piscar, esta configuração 

+especifica o período de tempo (veja time settings) durante cada ciclo 

+que está a ser visível. A configuração inicial é de 20. 

+ 

+Attributes Invisible Time

+ 

+Quando o carácter sublinhado em destaque piscar, esta configuração 

+especifica o período de tempo (veja time settings) durante cada ciclo 

+que é ser invisível. A configuração inicial é de 60. 

+ 

+Blinking Capitals 

+ 

+Ao exibir o conteúdo da tela (veja o comando DISPMD):

+

+   No

+   Deixar maiúsculas visível o tempo todo.

+   

+   Yes

+   Faça letras maiúsculas alternadamente visíveis e invisíveis de acordo 

+com um intervalo predefinido.

+ 

+Esta configuração também pode ser alterado com o comando CAPBLINK. 

+ 

+Capitals Visible Time 

+ 

+Quando as letras maiúsculas são destacadas, esta configuração especifica 

+o período de tempo (veja time settings) durante cada ciclo que estão a 

+ser visíveis. A configuração inicial é de 60. 

+ 

+Capitals Invisible Time  

+Quando as letras maiúsculas são destacadas, esta configuração especifica 

+o período de tempo (veja time settings) durante cada ciclo que estão a 

+ser invisível. A configuração inicial é de 20. 

+ 

+Braille Firmness 

+ 

+Ajuste a consistência (ou rigidez) dos pontos braille. Pode ser definido 

+como:

+

+      · Máximo

+      · Alta

+      · Média

+      · Baixo

+      · Mínimo

+       

+Essa preferência está disponível somente se um driver que suporta ele 

+está sendo usado. A configuração inicial é média. 

+ 

+Window Follows Pointer 

+ 

+Ajuste a sensibilidade dos pontos braille para tocar. Pode ser definido 

+como:

+      · Máximo

+      · Alta

+      · Média

+      · Baixo

+      · Mínimo

+       

+Essa preferência está disponível somente se um driver que suporta ele 

+está sendo usado. A configuração inicial é média. 

+ 

+Window Follows Pointer 

+ 

+Ao mover o dispositivo de cursor (mouse):

+

+   No

+   Não arrastar a janela braille.

+   

+   Yes

+   Arraste a janela braille.

+ 

+Esta preferência é apenas apresentada se a opção de compilação --enable-

+gpm foi especificada. 

+ 

+Highlight Window 

+ 

+Ao mover a janela do Braille:

+

+   No

+   Não dar destaque a área da tela nova.

+

+   Yes

+   Destaque da área nova tela.

+ 

+Este recurso permite que um observador com visão para ver onde está a 

+janela braille e, portanto, saber o que o usuário está lendo em braille. 

+Qualquer movimento da janela em braille (manual, controle do cursor, 

+etc), exceto quando se move em resposta ao ponteiro (mouse) movimento 

+(veja Windows Follows Pointer), faz com que a área da tela 

+correspondente ao novo local da janela Braille ser destacada. Se a 

+preferência Show Attributes está habilitada, somente o caractere 

+correspondente ao canto superior esquerdo da janela do braille é 

+destacado. 

+ 

+Alert Tunes 

+ 

+Sempre que um evento significativo, com uma melodia associada ocorre 

+(ver Alerta de Tunes):

+

+  No

+  Não tocar a música.

+

+  Yes

+  Tocar a música.

+ 

+Esta configuração também pode ser alterado com o comando TUNES. A 

+configuração inicial é Yes. 

+ 

+Tune Device 

+ 

+Tocar músicas de alerta através de:

+

+Beeper

+

+O apito interno (console gerador de tom). Esta configuração é suportada 

+em Linux, no OpenBSD, no FreeBSD e no NetBSD. É sempre seguro para usar, 

+embora possa ser um pouco fraco. Este dispositivo não está disponível se 

+a opção de compilação --disable-bipper-suport foi especificada.

+  

+PCM

+

+A interface digital de áudio na placa de som. Esta configuração é 

+suportada no Linux (via /dev/dsp), no Solaris (via /dev/audio), no 

+OpenBSD (via /dev/audio0), sobre FreeBSD (via /dev/dsp), e no NetBSD 

+(via /dev/audio0). Ele não funciona quando este dispositivo já está 

+sendo usado por outro aplicativo. Este dispositivo não está disponível 

+se a opção de compilação --disable-pcm-support foi especificada.

+   

+MIDI

+

+O Musical Instrument Digital Interface na placa de som que está definida 

+é compatível com Linux (através do /dev/sequencer). Ele não funciona 

+quando este dispositivo já está sendo usado por outro aplicativo. Este 

+dispositivo não está disponível se a opção de compilação --disable-midi-

+support foi especificada.

+   

+FM

+

+O sintetizador de FM em um cartão de AdLib, OPL3, Sound Blaster, ou o 

+equivalente de som. Esta configuração é suportada em Linux. Os 

+resultados são imprevisíveis e potencialmente não muito bons, se for 

+usado com uma placa de som que não suporta esse recurso. Este 

+dispositivo não está disponível se a opção de compilação --disable-fm-

+support foi especificada.

+

+A configuração inicial é Beeper sobre as plataformas que suportam, e PCM 

+sobre as plataformas que não o fazem.

+ 

+PCM Volume

+ 

+Se a interface de áudio digital da placa de som está sendo usado para 

+tocar as músicas alerta, esta configuração especifica o volume (como uma 

+porcentagem do máximo) em que estão a ser tocado. A configuração inicial 

+é de 70.

+

+MIDI Volume

+ 

+Se o Musical Instrument Digital Interface (MIDI) da placa de som está 

+sendo usado para tocar as músicas alerta, esta configuração especifica o 

+volume (como uma porcentagem do máximo) em que estão a ser tocado. A 

+configuração inicial é de 70. 

+ 

+MIDI Instrument

+ 

+Se o Musical Instrument Digital Interface (MIDI) da placa de som está 

+sendo usada para tocar as músicas alerta, esta configuração especifica 

+qual o instrumento a ser utilizado (ver MIDI Table Instrument). A 

+configuração inicial é Acoustic Grand Piano. 

+ 

+FM Volume

+ 

+Se o sintetizador FM da placa de som está sendo usado para tocar as 

+músicas alerta, esta configuração especifica o volume (como uma 

+porcentagem do máximo) em que estão a ser tocado. A configuração inicial 

+é de 70. 

+

+Alert Dots

+ 

+Sempre que um evento significativo associado com um padrão de pontos 

+ocorre (ver Alerta de Tunes):

+

+   No

+   Não exibir o padrão de pontos.

+

+   Yes

+   Resumidamente exibir o padrão de pontos.

+ 

+Se melodias de alerta são para ser jogado (veja o comando TUNES e o 

+Alerta de Tunes), se uma música tem sido associado com o evento, e se o 

+dispositivo da canção selecionado pode ser aberto, então, 

+independentemente da configuração dessa preferência, o padrão de pontos 

+não será exibido. 

+ 

+Alert Messages 

+ 

+Sempre que um evento significativo, com uma mensagem associada ocorre 

+(ver Alerta de Tunes):

+

+   No

+   Não mostrar a mensagem.

+

+   Yes

+   Mostrar a mensagem.

+ 

+Se as mensagens de alerta são tocadas (veja o comando TUNES e a 

+preferência Alerta de Tunes), se uma música tem sido associado com o 

+evento, e se o dispositivo de ajuste selecionado pode ser aberto, ou se 

+o alerta padrões de pontos que devem ser exibidas (consulte Alert Dots) 

+e se um padrão de pontos tem sido associado com o evento, então, 

+independentemente da configuração dessa preferência, a mensagem não é 

+exibida. 

+ 

+Say-Line Mode 

+ 

+Ao usar o comando SAY_LINE:

+

+   Immediate

+   Descartar discurso pendentes.

+

+   Enqueue

+   Não descartar pendentes discurso.

+ 

+A configuração inicial é immediate. 

+ 

+Autospeak 

+

+   No

+   Só falar quando for solicitado a fazê-lo.

+

+   Yes

+   Automaticamente falar:

+

+      · A nova linha quando a janela é movida verticalmente braille.

+      · Caracteres que são inseridos ou excluídos.

+      · O carácter ao qual o cursor é movido.

+ 

+Esta configuração também pode ser alterado com o comando AUTOSPEAK. A 

+configuração inicial é No. 

+ 

+Speech Rate

+ 

+Ajustar a velocidade de fala (0 é o mais lento, 20 é o mais rápido). 

+Essa preferência está disponível somente se um driver que suporta ele 

+está sendo usado. Esta configuração também pode ser alterado pelos 

+comandos SAY_SLOWER/SAY_FASTER. A configuração inicial é 10. 

+ 

+Speech Pitch

+ 

+Ajuste o volume da voz (0 é o mais baixo, 20 é o mais alto). Essa 

+preferência está disponível somente se um driver que suporta ele está 

+sendo usado. Esta configuração também pode ser alterado com os comandos 

+SAY_SOFTER/SAY_LOUDER. A configuração inicial é 10. 

+ 

+Speech Pitch

+ 

+Ajustar o tom de voz (0 é o menor e o 20 é o mais alto). Essa 

+preferência está disponível somente se um driver que suporta ele está 

+sendo usado. A configuração inicial é 10. 

+ 

+Speech Punctuation 

+ 

+Ajuste a quantidade de pontuação que é falado. Pode ser definido como:

+

+      · All

+      · Some

+      · All

+

+Essa preferência está disponível somente se um driver que suporta ele 

+está sendo usado. A configuração inicial é some. 

+ 

+Status Style

+ 

+Esta configuração especifica o caminho que as células de estado estão a 

+ser utilizados. Você não deveria precisam de tocar com nelas. Isso 

+permite que os desenvolvedores BRLTTY para testar as configurações de 

+status da pilha para display braille que eles realmente não têm. 

+ 

+None

+ 

+Não use as células de status. Essa configuração é sempre seguro, mas 

+também é completamente inútil. 

+ 

+Alva

+

+As células status conter:

+

+      1 - A localização do cursor (veja abaixo).

+      2 - A localização do canto superior esquerdo da janela do braille 

+(ver abaixo).

+      3 - Uma carta indicando o estado de BRLTTY. 

+

+Na ordem de precedência:

+

+      a

+      Tela atributos estão a ser mostrados (veja o comando DISPMD).

+      

+      f

+      A imagem da tela é congelada (veja o comando FREEZE).

+      

+      f

+      O cursor está sendo controlado (veja o comando CSRTRK).

+      

+      blank

+      Nada de especial.

+ 

+A localização do cursor e a janela braille são apresentados de uma 

+maneira interessante. Os pontos 1 a 6 representam o número da linha com 

+uma carta de a (para 1) por y (para 25). Os pontos 7 e 8 (os dois extra 

+na parte inferior) representam o número janela horizontal braille como 

+segue:

+

+      No Dots

+A primeira janela (à esquerda).

+      

+      Dot 7

+      A segunda janela.

+      

+      Dot 8

+      A terceira janela.

+      

+      Dots 7 e 8

+      A quarta janela.

+

+Em ambos os casos, os indicadores guardados: a linha 26 é representado 

+pela letra A, e a quinta janela horizontal braille é representado por 

+nenhum ponto, na parte inferior.  

+ 

+Tieman 

+ 

+As células de status devem conter:

+

+      1-2: As colunas (contando a partir 1) do cursor (como mostrado na 

+metade superior das células) e no canto superior esquerdo da janela do 

+braille (mostrado na metade inferior das células).

+       

+      3-4: As linhas (contando a partir 1) do cursor (como mostrado na 

+metade superior das células) e no canto superior esquerdo da janela do 

+braille (mostrado na metade inferior das células).

+       

+      5: Cada ponto indica se um recurso é ativado como se segue:

+

+      Dot 1: A imagem da tela é congelada (veja o comando FREEZE).

+       

+      Dot 2: atributos da tela estão sendo exibidos (veja o comando 

+DISPMD).

+       

+      Dot 3: Alerta músicas estão sendo tocadas (veja o comando TUNES).

+       

+      Dot 4: O cursor está sendo mostrado (veja o comando CSRVIS).

+       

+      Dot 5: O cursor é um bloco sólido (veja o comando CSRSIZE).

+       

+      Dot 6: O cursor está piscando (veja o comando CSRBLINK).

+       

+      Dot 7: O cursor está a ser controlado (veja o comando CSRTRK).

+       

+      Dot 8: A janela vai deslizar braille (veja o comando SLIDEWIN).

+ 

+PowerBraille 80 

+ 

+As células status conter:

+

+1: A linha (contando a partir de 1) correspondente à parte superior 

+da janela em braille. Os dígitos das dezenas é mostrado na metade 

+superior da pilha, e os dígitos das unidades é apresentada na 

+metade inferior da célula.

+ 

+Generic

+ 

+Esta definição passa um monte de informações para o driver braille, e o 

+driver decide forma de apresentá-lo.

+   

+MDV 

+ 

+As células de status devem conter:

+

+      1-2: A localização do canto superior esquerdo da janela do braille.

+      A linha (contando a partir de 1) é mostrado na metade superior das

+      células, e na coluna (contando a partir de 1) é mostrado na metade

+      inferior das células.

+ 

+Voyager

+ 

+As células de status devem conter:

+

+1: A linha (contando a partir de 1) correspondente à parte superior 

+da janela braille (ver below).

+

+2: A linha (contando a partir de 1) onde o cursor está (veja 

+below).

+3: Se a tela está congelada (veja o comando FREEZE), depois a letra 

+F. Se não for, então a coluna (contando a partir de 1) onde está o 

+cursor (veja below).

+

+Os números de linhas e colunas são mostrados os números de dois dígitos 

+em uma única célula. Os dígitos das dezenas são mostrados na metade 

+superior da pilha, e os dígitos das unidades são apresentadas na metade 

+inferior da célula.

+

+A configuração inicial do driver de display Braille é dependente.

+ 

+Text Table

+ 

+Selecione a tabela de texto. Ver seção de Tabelas de Texto para mais 

+detalhes. Veja a opção -t para a configuração inicial. Esta preferência 

+não é salvo. 

+ 

+Attributes Table

+ 

+Selecione a tabela de atributos. Ver Tabela de Atributos. Veja a opção 

+de linha de comando -t para a configuração inicial. Esta preferência não 

+é salvo. 

+ 

+Contraction Table

+ 

+Selecione a tabela de contração. Ver Tabela de Contração. Consulte a 

+linha de comando -c para a configuração inicial. Esta preferência não é 

+salvo. 

+ 

+Key Table 

+Selecione a tabela chave. Ver Tabelas de Teclas. Consulte a linha de 

+comando -k para a configuração inicial. Esta preferência não é salvo. 

+ 

+Notas:

+

+      · Todos os parâmetros de tempo em centésimos de segundo. Eles são

+      múltiplos do intervalo da janela de atualização braille (veja a

+      linha de comando -u) dentro da faixa de 1 a 100.

+       

+5.6 Status do Display

+ 

+O status do display é um resumo do estado atual BRLTTY, que se encaixa 

+totalmente dentro da janela do braille. Alguns displays braille têm um 

+conjunto de células de status que são usados para exibir permanentemente 

+algumas dessas informações, bem como (consulte a documentação para o 

+driver do display). Os dados apresentados por este display não são 

+estáticos, e podem ser alteradas a qualquer momento, em resposta a tela 

+de atualizações e/ou comandos BRLTTY. 

+ 

+Use o comando INFO para alternar para a exibição de status, e usá-lo 

+novamente para retornar para a tela. O layout das informações contidas 

+está dependente do tamanho da janela do braille. 

+ 

+5.6.1 Display com 21 células ou mais 

+ 

+Pneumáticos curta têm sido utilizados, apesar de serem um tanto 

+enigmática, a fim de mostrar o layout da coluna precisa.

+      wx:wy cx:cy vt tcmfdu

+

+wx:wy 

+A coluna e linha (contando a partir de 1) na tela correspondente ao 

+canto superior esquerdo da janela do braille. 

+ 

+cx:cy 

+A coluna e linha (contando a partir de 1) sobre a tela 

+correspondente à posição do cursor. 

+ 

+vt 

+O número (contando a partir 1) do terminal virtual atual. 

+ 

+t 

+O estado do recurso de controle de cursor (veja o comando CSRTRK).

+

+      blank

+      controle de cursor está off.

+       

+      t

+      controle de cursor está on.

+ 

+c 

+O estado dos recursos visibilidade do cursor (ver o CSRVIS e 

+CSRBLINK).

+

+      blank

+      O cursor não estiver visível, e não piscar quando se faz visível.

+   

+      b

+      O cursor não é visível, e começa a piscar quando se faz visível.

+   

+      v

+      O cursor é visível, e não está piscando.

+   

+      B

+      O cursor é visível e está piscando.

+ 

+m 

+O modo de exibição atual (veja o comando DISPMD).

+

+      t

+      Tela de conteúdo (texto) está sendo exibido.

+  

+      a

+      Tela de realce (atributos) está sendo exibido.

+ 

+f 

+O estado do recurso de tela congelada (veja o comando FREEZE).

+

+      blank

+      A tela não está congelada.

+

+d 

+O número de pontos braille sendo usado para exibir cada caracter 

+(veja o comando SIXDOTS).

+

+      8

+      Todos os oito pontos estão sendo usados.

+       

+      6

+      Somente pontos 1 a 6 estão sendo usados.

+ 

+u 

+O estado da maiúscula (letra maiúscula) apresentam características 

+(veja o comando CAPBLINK).

+

+      blank

+      Letras maiúsculas não piscar.

+

+      B

+      Piscar letras maiúsculas.

+

+5.6.2 Display com 20 células ou menos 

+ 

+Pneumático curto têm sido utilizados, apesar de serem um tanto 

+enigmática, a fim de mostrar o layout da coluna precisa.

+

+      xxyys vt tcmfdu

+ 

+xx 

+As colunas (contando a partir de 1) sobre a tela correspondente à 

+posição do cursor (como mostrado na metade superior das células) e 

+para o canto superior esquerdo da janela do braille (mostrado na 

+metade inferior das células). 

+ 

+yy 

+As linhas (contando a partir de 1) sobre a tela correspondente à 

+posição do cursor (como mostrado na metade superior das células) e 

+para o canto superior esquerdo da janela do braille (mostrado na 

+metade inferior das células). 

+ 

+s 

+As definições de algumas das características do BRLTTY. Um recurso 

+é ativado se o ponto correspondente é gerado.

+

+      Dot 1: Congela a imagem da tela (veja o comando FREEZE).

+       

+      Dot 2: Mostrar atributos (veja o comando DISPMD).

+       

+      Dot 3: Tunes de alerta (veja o comando TUNES).

+       

+      Dot 4: Cursor visível (veja o comando CSRVIS).

+       

+      Dot 5: Bloco de cursor (veja o comando CSRSIZE).

+       

+      Dot 6: Cursor piscando (veja o comando CSRBLINK).

+      

+      Dot 7: Cursor de monitoramento (veja o comando CSRTRK).

+       

+      Dot 8: Janela deslizante (veja o comando SLIDEWIN).

+ 

+vt 

+O número (contando a partir 1) do terminal virtual atual. 

+ 

+t 

+O estado do recurso de controle de cursor (veja o comando CSRTRK).

+

+      blank

+      Controle de cursor está off.

+

+      t

+      Controle cursor está on.

+ 

+c

+O estado dos recursos visibilidade do cursor (ver o CSRVIS e 

+CSRBLINK).

+

+      blank

+      O cursor não estiver visível, e não piscar quando se faz visível.

+	

+      b

+      O cursor não é visível, e começa a piscar quando se faz visível.

+   

+      v

+      O cursor é visível, e não está piscando.

+   

+      B

+      O cursor é visível e está piscando.

+ 

+m 

+O modo de exibição atual (veja o comando DISPMD).

+

+      t

+      Tela de conteúdo (texto) está sendo exibido.

+   

+      a

+      Tela de realce (atributos) está sendo exibido.

+ 

+f 

+O estado do recurso de tela congelada (veja o comando FREEZE).

+

+      blank

+      A tela não está congelada.

+

+      f

+      A tela está congelada.

+ 

+d 

+O número de pontos braille sendo usado para exibir cada caracter 

+(veja o comando SIXDOTS).

+

+      8

+      Todos os oito pontos estão sendo usados.

+

+      6

+      Somente pontos 1 a 6 estão sendo usados.

+ 

+u 

+O estado da maiúscula (letra maiúscula) apresentam características 

+(veja o comando CAPBLINK).

+

+      blank

+      Letras maiúsculas não piscar.

+   

+      B

+      Piscar letras maiúsculas.

+       

+5.7 Comando de Modo de Aprendizagem

+ 

+Comando modo de aprendizagem é uma forma interativa de aprender o que as 

+teclas no visor do Braille faz. Pode ser acessado tanto pelo comando 

+LEARN ou através do utilitário brltest. Esse recurso não estará 

+disponível se a opção de compilação --disable-learn-mode foi 

+especificado. 

+ 

+Quando este modo está inscrito, a mensagem command learn mode é escrita 

+no display braille. Então, cada tecla (ou combinação de teclas) sobre a 

+tela é pressionada, uma mensagem curta que descreva a sua função BRLTTY 

+está escrito. Este modo sai imediatamente se a tecla (ou combinação de 

+teclas) para o comando LEARN é pressionado. Ele sai automaticamente, e a 

+mensagem done é escrita, se dez segundos transcorrer sem qualquer tecla 

+no display sendo pressionado. Observe que alguns displays não têm sinal 

+ao display e/ou alguns drivers não sinalizam BRLTTY até que todas as 

+teclas são liberadas.

+ 

+Se uma mensagem for maior que o display Braille, então é exibida em 

+segmentos. O comprimento de cada segmento, mas o último é um a menos do 

+que a largura do display, com o caracter mais à direita no visor a ser 

+definida como um sinal de menos. Cada segmento se mantenha no visor seja 

+por alguns segundos (veja a opção-M) ou até que qualquer tecla na tela é 

+pressionada.

+

+6 – Tabelas

+

+6.1 - Tabelas de Texto

+

+Os arquivos com nomes no formato *. ttb são tabelas de texto, e com 

+nomes no formato *.tti são textos subtabelados. Eles são usados pelo 

+BRLTTY para traduzir os caracteres na tela em suas respectivas 

+representações de 8 pontos no computador braille.

+

+BRLTTY é inicialmente configurado para usar a tabela de texto North 

+American Braille Computer Code(NABCC). Além desse padrão, as 

+alternativas a seguir são fornecidas:

+

+      Auto

+      auto-seleção da localidade-base

+       

+      ar	Árabe (genérico)

+       

+      as	Assamês

+       

+      awa	Awadhi

+       

+      bg	Búlgaro

+

+      bh	Bihari

+

+      bn	Bengali

+

+      bo	Tibetano

+

+      bra	Braj (?Braj Bhasha )

+

+brf	para visualização de arquivos. brf dentro de um editor ou 

+pager

+

+      cs	Checo

+       

+      cy	Galês

+      

+      da	Dinamarquês

+      

+      de	Alemão

+      

+      dra	Dravidianas

+      

+      el	Grego

+      

+      em	Inglês

+      

+      en_CA	Inglês (Canadá)

+

+      en_UK	Inglês (Reino Unido)

+

+      en_US	Inglês (Estados Unidos)

+

+      en-nabcc	Inglês (North American Braille Computer Code)

+

+      eo	Esperanto

+

+      es	Espanhol

+      

+      et	Estoniano

+

+      fi	Finlandes

+      

+      fr	Francês

+

+      fr_CA	Francês (Canadá)

+

+      fr_FR	Francês (França)

+

+      fr_2007	Francês (unificado)

+

+      fr-cbifs	Francês (Code Braille Informatique Français Standard)

+      ga	Irlandês

+

+      gd	Gaélico

+

+      gon	Gondi

+

+      gu	Guzerate

+

+      he	Hebraico

+

+      hi	Hindi

+

+      hr	Croata

+

+      hu	Húngaro

+      

+      hy	Armênio

+

+      is	Islandes

+

+      it	Italiano

+

+      kha	Khasi

+

+      kn	Canará

+

+      kok	Concanis

+

+      kru	Kurukh

+

+      lt	Lituana

+

+      lv	Letã

+

+      mg	Malgaxe

+

+      mi	Maori

+

+      ml	Malaiala

+

+      mni	Manipuri

+      

+      mr	Marati

+

+      mt	Maltês

+

+      mun	Mundari

+

+      mwr	Marwari

+

+      ne	Nepali

+

+      new	Newari

+

+      nl	Holandês

+

+      nl_BE	Holandês (Bélgica)

+

+      nl_NL	Holandês (Países Baixos)

+

+      no	Norueguês

+

+      no-generic	Norueguês (com suporte para outros idiomas)

+

+      no-oub	Norueguês (Utvalg Offentlig para Blindeskrift)

+

+      nwc	Antigo Newari

+

+      or	Oriá

+

+      pa	Panjabi

+

+      pi	Páli

+

+      pl	Polonês

+

+      pt	Português

+

+      ro	Romeno

+

+      ru	Russo

+

+      sa	Sânscrito

+

+      sat	Santali

+

+      sd	Sindi

+

+      sk	Eslovaco

+

+      sv	Sueco

+

+      sw	Suaíli

+

+      ta	Tâmil

+

+      te	Telugu

+

+      tr	Turco

+

+      vi	Vietnamita

+

+Veja a opção de linha de comando -t, a configuração text-table do 

+arquivo de directiva, e a opção de construção --with-text-table para 

+obter detalhes sobre como usar uma tabela de texto alternativo.

+ 

+6.1.1	Tabela de Formato de Texto

+

+A tabela de texto consiste em uma seqüência de directivas, um por linha, 

+que definem como cada character é representado em braille. UTF-8 

+caracter encoding deve ser usado. O espaço em branco (espaços, tabs) no 

+início de uma linha, assim como antes e/ou após qualquer operando de 

+qualquer directiva, é ignorado. Linhas que contêm apenas espaço em 

+branco são ignoradas. Se o primeiro caráter não-espaço em branco antes 

+de uma linha é "#" então essa linha é um comentário e é ignorado.

+ 

+6.1.2	Tabela de Diretivas de Texto

+

+As seguintes diretivas são fornecidas:

+ 

+char character dots # comentários

+

+Use a diretiva char para especificar como um caracter Unicode será 

+representado em braille.

+ 

+character

+

+Os caracteres Unicode a serem definidos. Podem ser:

+

+      · Qualquer caracter único que não seja uma barra-invertida ou um 

+carácter de espaço em branco.

+

+      · Um caracter especial prefixado de barra-invertida. São eles:

+

+      \b

+      Carácter de retrocesso.

+

+      \f

+      Carácter formfeed (quebra de página).

+

+      \n

+      Carácter de nova linha.

+

+      \o###

+      Representação de um carácter de três dígitos octal.

+

+      \r

+      Carácter de retorno de transporte.

+

+      \s

+      Carácter de espaço.

+

+      \t

+      Carácter de tab horizontal.

+

+      \u####

+      Representação de um carácter hexadecimal de quatro dígito.

+

+      \U########

+      Representação de um carácter hexadecimal de oito dígito.

+

+      \v

+      Carácter de tab horizontal.

+

+      \x##

+      Representação de um carácter hexadecimal de dois dígitos.

+

+      \X##

+      (o caso do X e dos dígitos não é significativa).

+      

+      f

+      A tela está congelada.

+

+      \#

+      Um sinal de número literal.

+

+      \<name>

+      O nome Unicode de um carácter(use _ para espaço).

+

+      \\

+      Uma barra-invertida literal.

+ 

+dots

+A representação braille dos caracteres Unicode. É uma seqüência de 1-8 

+números dos pontos. Se o número do ponto de seqüência é colocado entre 

+parênteses, em seguida, os números dos pontos podem ser separados um do 

+outro por espaço em branco. Um número de ponto é um dígito dentro do 

+intervalo de 1-8, tal como definido pela Standard Braille Dot Numbering 

+Convention. O ponto especial de número 0 não é reconhecido quando entre 

+parênteses, e significa que não há pontos, não podendo ser usado em 

+conjunto com qualquer outro número de pontos.

+ 

+Exemplos

+      · char a 1

+

+      · char b (12)

+

+      · char c ( 4 1 )

+

+      · char \\ 12567

+

+      · char \s 0

+

+      · char \x20 ()

+

+      · char \<LATIN_SMALL_LETTER_D> 145

+ 

+byte byte dots # comment

+Use a diretiva de byte para especificar como um carácter no conjunto de 

+caracteres local está sendo representado em braille. Foi mantido para 

+compatibilidade com versões anteriores, mas não deve ser usado. 

+Carácteres Unicode devem ser definidos (através da diretiva char) para 

+que a tabela de texto permanece válida, independentemente do qual 

+conjunto de caracteres local usado.

+ 

+byte

+O caráter local a ser definido. Pode ser especificado da mesma forma 

+como um operador character da diretiva char exceto que as formas 

+específicas de Unicode (\u,\U,\<) não podem ser utilizados.

+ 

+dots

+A representação braille do carácter local. Pode ser especificado da 

+mesma forma como operando dots da directiva char.

+ 

+include file # comment

+Use a diretiva include para incluir o conteúdo de um texto subtabelado. 

+Ele é recursivo, o que significa que qualquer texto subtabelado pode-se 

+incluir ainda em outro texto subtabela. Cuidados devem ser tomados para 

+garantir que "include loop" não seja criado.

+ 

+file

+O arquivo a ser incluído. Pode ser um relativo ou um caminho absoluto. 

+Se relativo, ele está ancorada no diretório contendo o arquivo incluido.

+

+6.2 Tabela de Atributos

+

+Os arquivos com nomes no formato *.atb são tabelas de atributos, e com 

+nomes no formato *.ati são atributos subtabelas. Eles são usados quando 

+BRLTTY é mostrado na tela de atributos e não o conteúdo da tela (veja o 

+comando DISPMD). Cada um dos oito pontos braille representa um dos oito 

+bits de atributo VGA.

+

+Os seguintes atributos são fornecidas: 

+ 

+attributes

+

+A coluna da esquerda representa as cores de primeiro plano:

+

+      Dot 1		Red

+

+      Dot 2		Green

+

+      Dot 3		Blue

+

+      Dot 7		Bright

+

+A coluna do lado direito representa as cores de fundo:

+

+      Dot 4		Red

+

+      Dot 5		Green

+

+      Dot 6		Blue

+

+      Dot 8		Blink

+

+Um ponto é levantado quando o bit de atributo correspondente está 

+ligado. Este é o padrão da tabela de atributos, pois é a mais intuitiva. 

+Um dos seus problemas, porém, é que é difícil discernir a diferença 

+entre normal (branco sobre fundo preto) e inversa (preto sobre fundo 

+branco) vídeo.

+ 

+attrib

+A coluna da esquerda representa as cores de primeiro plano:

+

+      Dot 1		Red

+

+      Dot 2		Green

+      Dot 3		Blue

+

+      Dot 7		Bright

+

+A coluna do lado direito representa as cores de fundo:

+

+      Dot 4		Red

+

+      Dot 5		Green

+

+      Dot 6		Blue

+

+      Dot 8		Blink

+

+Um bit de fundo desencadeará seus pontos correspondentes, enquanto que 

+um bit de primeiro plano desencadeará seus correspondentes pontos. Essa 

+lógica intuitiva realmente torna mais fácil de ler as combinações de 

+atributos mais comumente usados.

+

+Veja a opção de linha de comando -a, a diretiva de arquivo de 

+configuração attributes-tables, e a opção de criação --with-attributes-

+table para obter detalhes sobre como usar uma tabela de atributos 

+alternativos.

+ 

+6.2.1	Tabela de Formatos dos Atributos

+

+Uma tabela de atributos consiste em uma seqüência de diretivas, um por 

+linha, que definem como as combinações de atributos VGA estão sendo 

+representados em braile. Caracteres UTF-8 devem ser utilizados. O espaço 

+em branco (espaços, tabs) no início de uma linha, assim como antes e/ou 

+após qualquer operando de qualquer diretiva, é ignorado. Linhas que 

+contêm apenas espaço em branco são ignoradas. Se o caráter não-espaço em 

+branco antes de uma linha for "#" então essa linha é um comentário e é 

+ignorado.

+ 

+6.2.2	Tabela de Diretórios dos Atributos

+

+As seguintes diretivas são fornecidas:

+ 

+dot dot state # comment

+Use a diretiva dot para especificar o que um determinado ponto 

+representa.

+

+dot

+O ponto a ser definido. É um único dígito no intervalo 1-8, tal como 

+definido pela Standard Braille Dot Numbering Convention.

+

+state

+O que o ponto representa. Pode ser:

+

+      =attribute

+      O ponto é levantado se o atributo nome está on.

+

+      ~attribute

+      O ponto é levantado se o atributo nome está off.

+

+Os nomes dos bits de atributo são:

+

+      0X01		fg-blue

+

+      0X02		fg-green

+

+      0x04		fg-red

+

+      0x08		fg-bright

+

+      0X10		bg-blue

+

+      0x20		bg-green

+

+      0x40		bg-red

+

+      0x80		blink

+ 

+Exemplos:

+

+      · dot 1 =fg-red

+      · dot 2 ~bg-blue

+ 

+include file # comment

+Use a diretiva include para incluir o conteúdo de um atributos 

+subtabela. Ele é recursivo, o que significa que os atributos subtabela 

+pode-se incluir ainda em outros atributos subtabela. Cuidados devem ser 

+tomados para garantir que "include loop" não seja criado.

+

+file

+O arquivo a ser incluído. Pode ser um relativo ou um caminho absoluto. 

+Se relativo, ela está ancorada no diretório contendo o arquivo 

+incluindo.

+

+6.3 Tabela de Contração

+

+Os arquivos com nomes no formato *.ctb são tabelas de contração, e com 

+nomes no formato *.cti são subtabelas de contração. Eles são usados pelo 

+BRLTTY para traduzir as seqüências de caracteres na tela em suas 

+respectivas representações contração em braille.

+

+BRLTTY apresenta contração em braille se:

+

+      · A tabela de contração foi selecionada. Consulte a opção de linha 

+de comando -c e as diretivas do arquivo de configuração contraction-

+table para obter mais detalhes.

+      · O recurso 6-dot braille foi ativado. Veja o comando SIXDOTS e o 

+Text Style para mais detalhes.

+

+Esse recurso não estará disponível se a opção de construção --disable-

+contracted-braille foi especificada.

+

+As seguintes tabelas de contração são fornecidas:

+

+      af	Afrikaans (contracionado)

+

+      am	Amharic (não-contracionado)

+

+      de-basis			German (não-contracionado)

+

+      de-kurzschrift    	German (contracionado - padrão1998)

+

+      de-vollschrift    	German (contrações básicas)

+

+      en-ueb-g2			Unified English Braille (grau 2)

+

+      en-us-g2			American English (grau 2)

+

+      es				Spanish (grau 2)

+

+      fr-abrege			French (contracionado)

+

+fr-integral		French (não-contracionado) 

+

+ha				Hausa (contracionado)

+

+      id				Indonesian (contracionado)

+

+      já			     Japanese (não-contracionado)

+

+      ko-g1				Korean (grau 1)

+

+      ko-g2				Korean (grau 2)

+

+      ko				Korean (não-contracionado)

+

+      mg				Malagasy (contracionado)

+

+      mun				Munda (contracionado)

+

+      nl				Dutch (contracionado)

+

+      ny				Chichewa (contracionado)

+

+      ipa				International Phonetic Alphabet

+

+      pt				Portuguese (grau 2)

+

+      si				Sinhalese (não-contracionado)

+

+      sw				Swahili (contracionado)

+

+      th				Thai (contracionado)

+

+      zh-tw				Taiwanese Chinese (não-contracionado)

+

+      zh-tw-ucb			Taiwanese Chinese (Unique Chinese Braille)

+

+      zu				Zulu (contracionado)

+

+Veja a opção de linha de comando -c, e as diretivas do arquivo de 

+configuração contraction-table para obter detalhes sobre como usar uma 

+tabela de contração.

+ 

+6.3.1 Tabela de Formato de Contração

+

+Uma tabela de contração consiste de uma seqüência de entradas, uma por 

+linha, que define como as seqüências de caracteres devem ser 

+representadas em Braille. Caracteres UTF-8 devem ser usados. O espaço em 

+branco (espaços, tabs) no início de uma linha, assim como antes e/ou 

+depois de qualquer operando, é ignorado. Linhas que contêm apenas espaço 

+em branco são ignoradas. Se o caráter não-espaço em branco antes de uma 

+linha é "#" então essa linha é um comentário e é ignorado.

+

+O formato de uma entrada na tabela de contração é:

+

+      directive operand ... [comment]

+

+Cada diretiva tem um número específico de operandos. Qualquer texto além 

+do último operando de uma diretiva é interpretado como um comentário. A 

+ordem das entradas dentro de uma tabela de contração é, em geral, tudo o 

+que é conveniente para seu mantenedor (s). Uma entrada que define uma 

+entidade, e.g. class, deve preceder todas as referências a essa 

+entidade.

+

+Entradas que combinam sequências de caracteres são automaticamente 

+rearranjadas do maior para o menor, de modo que o maior sempre é 

+preferível. Se mais uma entrada corresponde a seqüência de caracteres, 

+então a sua tabela original de ordenação é mantida. Assim, a mesma 

+seqüência pode ser traduzida de forma diferente em circunstâncias 

+diferentes.

+ 

+6.3.2	Tabela de Operandos de Contração 

+ 

+

+characters

+O primeiro operando de uma seqüência de caracteres correspondente as 

+diretivas e a seqüência de caracteres a ser correspondida. Cada carácter 

+dentro da seqüência pode ser:

+

+      · Qualquer caracter único que não seja uma barra-invertida ou um 

+carácter de espaço em branco.

+

+      · Um caracter especial prefixado de barra-invertida. São eles:

+

+      \b

+      Carácter de retrocesso.

+

+      \f

+      Carácter formfeed (quebra de página).

+

+      \n

+      Carácter de nova linha.

+

+      \o###

+      Representação de um carácter de três dígitos octal.

+

+      \r

+      Carácter de retorno de transporte.

+

+      \s

+      Carácter de espaço.

+

+      \t

+      Carácter de tab horizontal.

+

+      \u####

+      Representação de um carácter hexadecimal de quatro dígito.

+

+      \U########

+      Representação de um carácter hexadecimal de oito dígito.

+

+      \v

+      Carácter de tab horizontal.

+

+      \x##

+      Representação de um carácter hexadecimal de dois dígitos.

+

+      \X##

+      (o caso do X e dos dígitos não é significativa).

+

+      \#

+      Um sinal de número literal.

+

+      \<name>

+      O nome Unicode de um carácter(use _ para espaço).

+

+      \\

+      Uma barra-invertida literal.

+ 

+

+

+representation

+O segundo operando dessa seqüência de caracteres correspondentes a 

+diretivas que tem uma representação braille da seqüência. Cada célula 

+braille é especificada como uma seqüência de 1-8 números de pontos. Um 

+número de ponto é um dígito dentro do intervalo 1-8, tal como definido 

+pela Standard Braille Dot Numbering Convention. O número de ponto 

+especial 0, que não pode ser usado em conjunto com qualquer outro número 

+de pontos, significa que não há pontos.

+ 

+6.3.3 Opcodes

+

+Um opcode é uma palavra-chave que diz o tradutor como interpretar os 

+operandos. Os opcodes são agrupados aqui por função.

+ 

+6.3.3.1 Administração de Tabela

+

+Estes opcodes torna mais fácil de escrever as tabelas contração. Eles 

+não têm nenhum efeito direto sobre a tradução de caracteres.

+

+include path

+

+Incluir o conteúdo de outro arquivo. Codificação pode ser em qualquer 

+profundidade. Os caminhos relativos são ancorados no diretório do 

+arquivo incluído.

+

+locale locale

+

+Define a localidade para a interpretação de carácteres (letras 

+minúsculas, maiúsculas, números, etc.) O local pode ser especificado 

+como:

+

+language[_country][.charset][@modifier]

+

+O componente language é necessário e deverá ser um código de duas letras 

+para línguas no padrão ISO-639. O componente country é opcional e deverá 

+ser um código de duas letras no padrão ISO-3166. O componente charset é 

+opcional e deve ser um nome de conjunto de caracteres, e.g. ISO-8859-1.

+

+C

+7-bit ASCII.

+

+-

+Sem localidade.

+

+A última especificação de localidade se aplica à tabela inteira. Se este 

+opcode não for usado, a localidade C é assumida.

+ 

+6.3.3.2 Definição de símbolo especial

+

+Estes opcodes definem símbolos especiais que devem ser inseridos no 

+texto em braile, a fim de esclarecê-lo.

+

+      capsign dots

+      O símbolo que capitaliza uma única letra.

+

+      begcaps dots

+O símbolo que se inicia um bloco de letras maiúsculas em uma 

+palavra.

+

+      endcaps dots

+      O símbolo que termina um bloco de letras maiúsculas em uma palavra.

+

+      letsign dots

+      O símbolo que marca uma carta que não faz parte de uma palavra.

+

+      numsign dots

+      O símbolo que marca o início de um número.

+ 

+6.3.3.3 Tradução de Carácter

+

+Estes opcodes definem as representações braille para sequências de 

+carácteres. Cada um deles define uma entrada na tabela de contração. 

+Essas entradas podem ser definidas em qualquer ordem, exceto, como 

+indicado abaixo, quando definem representações alternativas para a 

+seqüência da mesma natureza.

+

+Cada um desses opcodes possui um carácter operando (que deve ser 

+especificado como uma string), e uma condição embutida que regem a sua 

+elegibilidade para o uso. O texto é processado estritamente de esquerda 

+para a direita, caractere por caractere, com a entrada mais elegível 

+para cada posição a ser utilizada. Se houver mais de uma entrada 

+elegível para uma determinada posição, então aquele com a maior cadeia 

+de caractere é usado. Se houver mais de uma entrada elegível para a 

+mesma string de caracteres, então a uma definição mais próxima do início 

+da tabela é usada (isso é a dependência de ordem única).

+

+Muitos desses opcodes têm um dot operando que define a representação 

+braille para seu operando characters. Também pode ser especificado como 

+um sinal de igual (=), caso em que significa uma de duas coisas. Se a 

+entrada é para um único carácter, então isso significa que a atual 

+representação braille selecionada no computador (ver a opção de linha de 

+comando -t e a diretiva text-table do arquivo de configuração) para que 

+o caráter é para ser usado. Se é para uma seqüência de multi-caráteres, 

+então a representação padrão para cada caracter (veja always) dentro da 

+seqüência deve ser usada.

+

+Alguns termos especiais são usados nas descrições desses opcodes.

+

+      word

+      Uma seqüência máxima de uma ou mais letras consecutivas.

+

+Agora, finalmente, aqui está a descrição opcode:

+

+      literal characters

+Traduz toda a seqüência de caracteres delimitada por espaço em 

+branco no braille do computador (ver a opção de linha de comando -t 

+e a diretiva text-table do arquivo de configuração).

+

+      replace characters characters

+Substitui o primeiro conjunto de caracteres, não importa onde eles 

+aparecem, com a segunda. Os carácteres substituídos não são 

+reprocessado.

+

+      always characters dots

+Traduz os carácteres, não importa onde eles aparecem. Se há apenas 

+um carácter, então, além disso, defini a representação padrão para 

+esse carácter.

+

+      repeatable characters dots

+Traduz os carácteres, não importa onde eles aparecem. Ignora 

+quaisquer repetições consecutivas da mesma sequência.

+

+      largesign characters dots

+Traduz os personagens, não importa onde eles aparecem. Retira os 

+espaço em branco entre as palavras consecutivas acompanhado por 

+este opcode.

+

+      lastlargesign characters dots

+Traduz os carácteres, não importa onde eles aparecem. Remove o 

+espaço em branco anterior se a palavra anterior foi acompanhado 

+pelo opcode largesign.

+

+      word characters dots

+      Traduz os carácteres se eles são uma palavra.

+

+      joinword characters dots

+Traduz os carácteres se eles são uma palavra. Remove o seguinte 

+espaço em branco se o primeiro caractere depois é uma letra.

+

+      lowword characters dots

+Traduz os carácteres se eles são uma palavra delimitada branco-

+espaço.

+

+      contraction characters

+Prefixa os carácteres com um sinal de letra (veja letsign) se eles 

+são uma palavra.

+

+      sufword characters dots

+Traduz os carácteres se eles são uma palavra ou no início de uma 

+palavra.

+

+      prfword characters dots

+Traduz os carácteres se eles são uma palavra ou no final de uma 

+palavra.

+

+      begword characters dots

+	Traduz os carácteres se eles estão no início de uma palavra.

+

+	begmidword characters dots

+Traduz os carácteres se eles estão ou no início ou no meio de uma 

+palavra.

+

+      midword characters dots

+      Traduz os carácteres se eles estão no meio de uma palavra.

+

+      midendword characters dots

+Traduz os carácteres se eles estão ou no meio ou no final de uma 

+palavra.

+

+      endword characters dots

+      Traduz os carácteres se eles estão no final de uma palavra.

+

+      prepunc characters dots

+Traduz os carácteres se eles são parte da pontuação no início de 

+uma palavra.

+

+      postpunc characters dots

+Traduz os carácteres se eles são parte da pontuação no final de uma 

+palavra.

+

+      begnum characters dots

+	Traduz os carácteres se eles estão no início de um número.

+

+      midnum characters dots

+	Traduz os carácteres se eles estão no meio de um número.

+

+      endnum characters dots

+	Traduz os carácteres se eles estão no final de um número.

+ 

+6.3.3.4 Classes de Caracteres

+

+Estes opcodes definem e usam classes de carácteres. Uma classe de 

+caracteres associa um conjunto de carácteres com um nome. O nome, em 

+seguida, refere-se a qualquer carácter dentro da classe. Um carácter 

+pode pertencer a mais de uma classe.

+

+As classes de carácteres a seguir são automaticamente pré-definicadas 

+com base na localidade selecionada:

+

+      digit

+      Carácteres numéricos.

+

+      letter

+Ambas os caracteres maiúsculos e minúsculos do alfabeto. Algumas 

+localidades têm letras adicionais que não são nem maiúsculas nem 

+minúsculas.

+

+      lowercase

+	Carácteres do alfabeto em minúsculo.

+

+      punctuation

+Carácteres impresso que não são nem espaço em branco e nem 

+alfanuméricos.

+

+      space

+Caracteres espaço em branco. Na localidade padrão, esses são: 

+espaço, tabulações horizontal e vertical, de retorno de carro, a 

+nova linha, formfeed(quebra de página).

+

+      uppercase

+      Carácteres do alfabeto em maiúsculo.

+

+Os opcodes que definem e usam as classes de carácteres são:

+

+      class name characters

+

+Defini uma nova classe de carácter. Os carácteres operando devem 

+ser especificados como uma string. Uma classe de caracter não pode 

+ser utilizada até que ela seja definida.

+

+      after class opcode ...

+O opcode especificado é mais restrito na seqüência de caracteres 

+correspondente e deve ser imediatamente precedido por um carácter 

+que pertence à classe especificada. Se este opcode é usado mais de 

+uma vez na mesma linha, então a união dos caracteres em todas as 

+classes é usada.

+

+      before class opcode ...

+O opcode especificado é mais restrito na seqüência de caracteres 

+correspondente e deve ser imediatamente seguido por um carácter que 

+pertence à classe especificada. Se este opcode é usado mais de uma 

+vez na mesma linha, então a união dos caracteres em todas as 

+classes é usada.

+

+       6.4 Tabelas de Teclas

+

+Os arquivos com nomes no formato *. ktb são tabelas chaves, e com nomes 

+no formato *. kti são subtabelas chaves. Eles são usados por BRLTTY para 

+vincular ao display braille e ao teclado combinações de teclas para 

+comandos do BRLTTY.

+

+Os nomes dos display braille nos arquivos de tabela chaves começam com 

+brl-xx-, onde xx é o código de duas letras de identificação do 

+driver(driver identification code). O resto do nome identifica o modelo 

+(s) para os quais a tabela chave é usada.

+

+Os nomes dos teclados nos arquivos de tabela chaves começam com kbd-. O 

+resto do nome descreve o tipo de teclado para qual a tabela chaves foi 

+concebido.

+

+Os seguintes teclados de tabela chaves são fornecidos:

+

+      kbd-desktop

+      ligações para teclado completo

+

+      kbd-keypad

+      ligações para navegação baseado em teclado numérico

+

+      kbd-laptop

+      ligações para teclados sem um teclado númerico

+

+Consulte a opção de linha de comando -k e a directiva do arquivo de 

+configuração key-table para obter detalhes sobre como selecionar uma 

+tabela chaves do teclado.

+ 

+6.4.1 Tabela de Diretivas de Teclas

+

+Uma tabela chaves consiste em uma seqüência de diretivas, uma por linha, 

+que definem como as teclas e combinações de teclas devem ser 

+interpretadas. UTF-8 caracter encoding deve ser usado. O espaço em 

+branco (espaços, tabs) no início de uma linha, assim como antes e/ou 

+depois de qualquer operando, é ignorado. Linhas que contêm apenas espaço 

+em branco são ignoradas. Se o caráter não-espaço em branco antes de uma 

+linha é um sinal de número (#) então essa linha é um comentário e é 

+ignorada.

+

+A prioridade para a resolução de cada evento da tecla 

+pressionada/liberada é a seguinte:

+

+1. Uma hotkey pressionada ou liberação definida dentro do contexto 

+atual. Veja a diretiva hotkey para mais detalhes.

+

+2. A combinação de teclas definida dentro do contexto atual. 

+Consulte a diretiva bind para detalhes.

+

+3. Um comando do teclado braille definido dentro do contexto atual. 

+Veja as directivas map e superimpose para mais detalhes.

+

+4. A combinação de teclas definida dentro do contexto padrão. 

+Consulte a diretiva bind para detalhes.

+

+As diretrizes a seguir são fornecidas:

+

+6.4.1.1 Atribuir Diretiva

+

+Criar ou atualizar uma variável associada com o atual nível de incluir. 

+A variável é visível para o atual e incluem os níveis mais baixos, mas 

+não incluem os níveis mais elevados.

+

+assign variable [value]

+

+      variable

+O nome da variável. Se a variável não existir no atual nível de 

+incluir então ela é criada.

+

+      value

+O valor que será atribuído à variável. Se não for fornecido, então 

+um valor de comprimento zero (nulo) é atribuído.

+

+A seqüência de escape \{variable} é substituída com o valor da variável 

+chamada dentro das chaves. A variável deve ter sido definida no atual ou 

+um nível de incluir superior.

+

+Exemplos:

+

+      · assign nullValue

+

+      · assign ReturnKey Key1

+

+      · bind \{ReturnKey} RETURN

+ 

+6.4.1.2 Diretiva de vínculo

+

+Define quais comandos BRLTTY é executado quando uma combinação 

+específica de uma ou mais teclas é pressionada. A ligação é definida 

+dentro do contexto atual.

+

+bind keys command

+

+

+keys

+A combinação de teclas que será vinculada. É uma seqüência de um ou mais 

+nomes de chaves separadas pelo sinal de mais (+). O nome da chave 

+final(ou única) pode ser opcionalmente prefixado com um ponto de 

+exclamação (!). As chaves podem ser pressionadas em qualquer ordem, com 

+a ressalva de que se o nome da chave final é prefixado com um ponto de 

+exclamação em seguida, ele deve ser pressionado por último. O prefixo do 

+ponto de exclamação significa que o comando é executado assim que a 

+tecla é pressionada. Se não for utilizado, o comando é executado assim 

+que qualquer uma das teclas é liberada.

+

+command

+O nome de um comando BRLTTY. Um ou mais modificadores podem ser 

+opcionalmente anexado ao nome do comando, usando um sinal positivo (+) 

+como separador.

+

+Para comandos que ativa/desativa o recurso:

+

+?	Se o modificador +on for especificado, o recurso está habilitado.

+?	Se o modificador +off é especificada, o recurso está desativado.

+?	Se nem +on ou +off é especificado, o estado do recurso é 

+ativado/desativado.

+

+Para comandos que mover a janela braille:

+

+?	Se o modificador +route é especificado, então, se necessário, o 

+cursor é automaticamente encaminhado para que fique sempre visível 

+no display braille.

+

+Para comandos que move a janela braille para uma linha específica na 

+tela:

+

+?	Se o modificador +toleft for especificado, a janela de braile 

+também é movida para o início dessa linha.

+?	Se o modificador +scaled é especificado, o conjunto de chaves 

+vinculadas ao comando é interpretado como se fosse uma barra de 

+rolagem. Se não for, então não há uma correspondência de um-para-um 

+entre as teclas e linhas.

+

+Para comandos que exigem um deslocamento:

+

+O modificador +offset, onde offset é um número inteiro não negativo, 

+pode ser especificado. Se não for fornecido, então +0 é assumido.

+

+Exemplos:

+

+?	bind Key1 CSRTRK

+

+?	bind Key1+Key2 CSRTRK+off

+

+?	bind Key1+Key3 CSRTRK+on

+

+?	bind Key4 TOP

+

+?	bind Key5 TOP+route

+

+?	bind VerticalSensor GOTOLINE+toleft+scaled

+

+?	bind Key6 CONTEXT+1

+ 

+6.4.1.3 Diretiva de Contexto

+

+Definir formas alternativas de interpretar certos acontecimentos-chave 

+e/ou combinações. Um contexto contém as definições criadas pelas 

+directivas bind, hotkey, map, e superimpose.

+ 

+context identifier [title]

+      identifier

+

+Qual a definição posterior do contexto deve ser criada dentro. Podem 

+ser:

+

+      · Um desses nomes especiais:

+

+default

+O contexto padrão. Se a combinação de teclas não foi definida dentro do 

+contexto atual, então a sua definição no contexto padrão é usado. Isso 

+só se aplica as definições criadas pela diretiva de vinculo.

+

+menu

+Esse contexto é utilizado quando dentro do menu de preferências do 

+BRLTTY.

+

+· Um número inteiro no intervalo de 0 a 252. Contexto 0 é uma 

+maneira alternativa para se referir ao contexto padrão. Os números 

+mais altos de contexto devem ser evitados porque o maior número 

+permitido está sujeito a alteração sem aviso prévio, se, por 

+exemplo, os contextos mais nomeados são adicionados.

+

+title

+Uma descrição person-readable do contexto. Ele pode conter espaços, e as 

+convenções de capitalização padrão deve ser usado. Este operando é 

+opcional. Se for fornecido ao selecionar um contexto que já tem um 

+título, então os dois devem coincidir.Contextos nomeado já tem títulos 

+internamente atribuídos. Contextos numéricos são inicialmente criados 

+sem títulos.

+

+Um contexto é criado a primeira vez que ele é selecionado. Pode ser re-

+selecionado qualquer número de vezes depois disso.

+

+Todas as definições subseqüentes até a próxima diretiva context ou no 

+final do corrente nível de incluir são criadas dentro do contexto 

+selecionado. O contexto inicial de nível superior da tabela chave é 

+padrão. O contexto inicial incluído em uma subtabela chave é o contexto 

+que foi selecionado quando ele foi incluído. Contextos de mudanças 

+incluídos dentro de subtabelas chaves não afetam o contexto da mesa, 

+incluindo tabela chave ou subtabela.

+

+Se um contexto tem um título (todos os contextos nomeados e os contextos 

+numérico para o qual o operando title foi fornecido), então é 

+persistente*. Quando um evento-chave faz com um contexto de persistência 

+ser ativado, esse contexto atual permanece até que um evento posterior 

+chave faz um contexto diferente persistente ser ativado.

+

+Se um contexto não tem um título (os contextos numéricos para os quais o 

+operando title não foi fornecido), então ele é temporário. Quando um 

+evento chave provoca um contexto temporário a ser ativado, esse contexto 

+só é usado para interpretar o evento chave mais próximo.

+

+Exemplos:

+

+      · context menu

+

+      · context 1 Braille Input

+

+      · context 2

+ 

+6.4.1.4 Directiva de Ocultar

+

+Especifica se deseja ou não certas definições (ver as diretivas, hotkey, 

+map, e superimpose) e notas (ver a diretiva note) serão incluídas no 

+texto da tabela de chave de ajuda.

+

+      hide state

+

+      state

+      Uma dessas palavras-chaves:

+       

+on

+      Estão excluídos.

+

+      off

+      Eles estão incluídos.

+

+O estado especificado se aplica as todas as definições e notas 

+subseqüentes até a próxima diretiva hide ou o fim do atual nível de 

+incluir. O estado inicial da tabela chave de nível superior é off. O 

+estado inicial incluído em uma subtabela chave é o estado que foi 

+selecionado quando ele foi incluído. Mudanças de estado incluídos dentro 

+de uma subtabelas chave não afetam o estado incluindo na tabela chave ou 

+subtabela.

+

+Exemplo:

+

+      ·  hide on

+ 

+6.4.1.5 Diretiva de Hotkey

+

+Vincula os eventos press e release de uma tecla específica para dois 

+comandos separados BRLTTY. Os vinculos são definidas dentro do contexto 

+atual.

+ 

+hotkey key press release

+

+      key

+      O nome da tecla que será limitada.

+

+      press

+O nome do comando BRLTTY que deve ser executado sempre que a tecla 

+é pressionada.

+

+      release

+O nome do comando BRLTTY que deve ser executado sempre que a tecla 

+é liberada.

+

+Modificadores podem ser acrescentados aos nomes de comando. Veja o 

+operando command da diretiva bind para mais detalhes.

+

+Especifique NOOP se nenhum comando será executado. Especificando NOOP 

+para ambos os comandos efetivamente desativa a chave.

+ 

+Exemplos:

+

+      · hotkey Key1 CSRVIS+off CSRVIS+on

+

+      · hotkey Key2 NOOP NOOP

+ 

+6.4.1.6 Diretiva IfKey

+

+Condicionalmente processo uma diretiva da tabela chave somente se o 

+aparelho tem uma chave particular.

+ 

+ifkey key directive

+      

+key

+      O nome da chave, cuja disponibilidade está a ser testado.

+      directive

+

+A diretiva da tabela chave que deve ser condicionalmente processado.

+ 

+Exemplos:

+

+      · ifkey Key1 ifkey Key2 bind Key1+Key2 HOME

+ 

+6.4.1.7 Diretiva Include

+

+Processa as diretivas dentro de uma subtabela chave. É recursiva, o que 

+significa que qualquer subtabela chave pode ser incluir ainda em outra 

+subtabela chave. Cuidados devem ser tomados para garantir que "incluem 

+loop" não seja criado.

+

+      include file

+

+      file

+A subtabela chave que será incluída. Pode ser um caminho relativo 

+ou absoluto. Se relativo está ancorado no diretório que contém a 

+incluindo a tabela chave ou subtabela.

+

+Exemplos:

+

+?	include common.kti

+

+?	include /path/to/my/keys.kti

+ 

+6.4.1.8 Diretiva de Mapa

+

+Mapeia uma tecla para uma função do teclado em braille. O mapeamento é 

+definido dentro do contexto atual.

+ 

+map key function

+      

+      key

+O nome da tecla que será mapeada. Mais de uma tecla pode ser 

+mapeada para a mesma função teclado em braille.

+

+      function

+O nome da função do teclado em braille. Pode ser uma das seguintes 

+palavras:

+

+      DOT1

+      O padrão braille do ponto superior esquerdo.

+

+      DOT2

+      O padrão braille do ponto do meio esquerdo.

+

+      DOT3

+      O padrão braille do ponto inferior esquerdo.

+

+      DOT4

+	O padrão braille do ponto superior direito.

+

+      DOT5

+	O padrão braille do ponto do meio direito.

+

+      DOT6

+	O padrão braille do ponto inferior direito.

+

+      DOT7

+	O ponto braille do computador inferior esquerdo.

+

+      DOT8

+	O ponto braille do computador inferior direito.

+

+      SPACE

+	A barra de espaço.

+

+      SHIFT

+      A tecla shift.

+

+      UPPERCASE

+Se uma letra minúscula está sendo inserido então traduzi para seu 

+equivalente em maiúsculas.

+

+      CONTROLE

+	A chave de controle.

+

+      META

+	A tecla Alt esquerda.

+

+Se a combinação de teclas consiste apenas de chaves que foram mapeadas 

+para funções do teclado em braile, e se essas funções quando combinadas 

+formam um comando de teclado braille válido, então o comando é executado 

+logo que qualquer uma das teclas é liberado. Um comando de teclado 

+braille válido deve incluir ou qualquer combinação dos pontos chaves ou 

+a barra de espaço (mas não ambos). Se pelo menos um ponto chave é 

+incluído, então as funções do teclado em braille especificados pelas 

+diretivas superimpose dentro do mesmo contexto também estão 

+implicitamente incluídos.

+

+Exemplos:

+

+      · map Key1 DOT1

+ 

+6.4.1.9 Diretiva Note

+

+Adiciona uma explicação person-readable para o texto de ajuda da tabela 

+chaves. As notas são usadas, por exemplo, para descrever o 

+posicionamento, tamanhos e formas das teclas do dispositivo.

+

+      note text

+      text

+A explicação que será adicionada. Ele pode conter espaços, e deve 

+ser gramaticalmente correta.

+

+Cada nota especifica exatamente uma linha de texto explicativo. espaço à 

+esquerda é tão ignorado recuo não pode ser especificado.

+

+Não há limite para o número de notas que podem ser especificadas. Todos 

+eles estão reunidos e apresentados em um único bloco no início do texto 

+de ajuda da tabela chaves.

+ 

+Exemplo:

+note Key1 is the round key at the far left on the front surface.

+ 

+6.4.1.10 Diretiva de sobreposição

+

+Inclui implicitamente uma função do teclado sempre que um comando de 

+teclado braille consiste em pelo menos um dot é executado. A inclusão 

+implícita é definida dentro do contexto atual. Qualquer número deles 

+pode ser especificado.

+ 

+superimpose function

+      function

+O nome da função do teclado braille. Veja o operando function da 

+diretiva map para detalhes.

+ 

+Exemplo:

+superimpose DOT7

+ 

+6.4.1.11 Diretiva Title

+

+Apresenta um resumo person-readable do propósito das tabelas chaves

+ 

+title text

+      text

+Um resumo de uma linha de como a tabela chave é usado. Ele pode 

+conter espaço, e as convenções de capitalização padrão deve ser 

+usado.

+

+O título da tabela chave pode ser especificado apenas uma vez.

+ 

+Exemplo:

+title Bindings for Keypad-based Navigation

+ 

+6.4.2 Propriedades do Teclado

+

+O padrão é que todos os teclados são monitorados. Um subconjunto de 

+teclados pode ser selecionado por uma ou mais propriedades seguintes 

+(veja a opção de linha de comando –K, e a diretiva do arquivo de 

+configuração keyboard-properties).

+

+type

+O tipo de barramento, especificado com uma das seguintes palavras-

+chaves: any, ps2, usb, Bluetooth

+

+vendor

+Identificação do vendedor, especificado com um inteiro de 16-bits não 

+sinalizado.

+

+product

+Identificação do produto, especificado com um inteiro de 16-bits não 

+sinalizado.

+

+Os identificadores vendor e product podem ser especificados em notação 

+decimal (sem prefixo), octal( prefixado por 0), ou hexadecimal 

+(prefixado por 0x). Especificando 0 significa corresponder a qualquer 

+valor (como se a propriedade não foi especificada). 

+

+7. Tópicos Avançados

+

+7.1 Instalação de múltiplas versões

+ 

+É fácil ter mais de uma versão do BRLTTY instalado no mesmo sistema ao 

+mesmo tempo. Esse recurso permite que você teste uma nova versão antes 

+de remover o antigo. 

+ 

+A opção de compilação --with-execute-root permite que você instale a 

+hierarquia de arquivo em qualquer lugar que você deseja. Lembrando que é 

+melhor para manter todos os componentes BRLTTY dentro do sistema de 

+arquivos root, você pode compila-los assim: 

+ 

+. /configure --with-execute-root =/brltty-3.1 

+make install 

+ 

+Você pode executá-lo assim: 

+ 

+   /brltty-3.1/bin/brltty 

+ 

+Quando a versão 3.2 é liberado, basta instalá-lo em um local diferente e 

+executar o novo arquivo executável a partir daí. 

+ 

+  . /configure --with-execute-root =/brltty-3.2 

+  make install 

+  /brltty-3.2/bin/brltty 

+ 

+Até agora, este paradigma é um pouco estranho, pelo menos, duas razões. 

+Uma desses nomes é muito difícil de escrever, e o outro é que você não 

+quer mexer com a seqüência de inicialização do sistema cada vez que você 

+quiser mudar para uma versão diferente do BRLTTY. Estes problemas são 

+facilmente resolvidos adicionando um link simbólico para o executável. 

+ 

+   ln-s /brltty-3.1/bin/brltty /bin/brltty 

+ 

+Quando é hora de mudar para a nova versão, apenas o link repoint 

+symbolc. 

+ 

+   ln-s /brltty-3.2/bin/brltty /bin/brltty 

+ 

+Se você quiser ficar realmente, então inicie um outro nível de indireção 

+para fazer todos os arquivos BRLTTY para qualquer versão que eles estão 

+em todos os locais padrão. Primeiro, crie um link simbólico através de 

+um local comum repointable de cada uma das localizações padrão de 

+BRLTTY. 

+ 

+       ln -s /brltty/bin/brltty /bin/brltty 

+       ln -s /brltty/etc/brltty /etc/brltty 

+       ln -s brltty /lib/brltty /lib/brltty 

+ 

+Então, tudo que você precisa fazer é apontar /brltty a versão desejada. 

+ 

+       ln -s /brltty-3.1 /brltty

+

+7.2 Instalação/Ajuda dos Discos de root para o Linux

+ 

+BRLTTY pode funcionar como um executável autônomo. Tudo o que ele 

+precisa saber pode ser explicitamente configurado em tempo de compilação 

+(veja Opções de Compilação). Se o diretório de dados (ver na opção de 

+compilação --with-data-directory e --with-execute-root) não existir, 

+então BRLTTY olha em /etc para os arquivos que ele necessita. Mesmo que 

+nenhum destes arquivos não existem, BRLTTY ainda funciona! 

+ 

+Se, por alguma razão, você já criou o diretório de dados (normalmente 

+/etc/brltty), é importante definir suas permissões para que somente o 

+root possa criar arquivos dentro dela. 

+ 

+       chmod 755 /etc/brltty 

+ 

+A tela do aparelho de inspeção de conteúdo (normalmente /dev/vcsa) é 

+necessária. Ela já deve existir a menos que sua distribuição do Linux é 

+bastante antiga. Se necessário, você pode criá-lo com: 

+ 

+       mknod /dev/vcsa c 7 128 

+       chmod 660 /dev/vcsa 

+       chown root.tty /dev/vcsa 

+ 

+Um problema freqüentemente encontrado ao tentar usar BRLTTY em um 

+ambiente incerto como um disco de root ou de um sistema incompleto é que 

+ela não pôde encontrar as bibliotecas compartilhadas (ou partes) de que 

+necessita. Discos de root costumam usar subconjunto e/ou versões 

+desatualizadas das bibliotecas que podem ser inadequados. A solução é 

+configurar BRLTTY com a opção --enable-standalone-programs. Isso remove 

+todas as dependências de bibliotecas compartilhadas, mas, infelizmente, 

+também cria um arquivo executável maior. Há uma série de opções de 

+compilação que pode ser usado para remover de recursos desnecessários do 

+BRLTTY, a fim de atenuar um pouco esse problema (ver Build Features). 

+ 

+O executável é eliminado durante o make install. Isto reduz 

+significativamente o seu tamanho retirando sua tabela de símbolo. Você 

+receberá um executável muito menor, portanto, se você concluir o 

+processo de compilação completo, e depois copiá-lo de seu local 

+instalado. Se, no entanto, copiá-lo do diretório de compilação, vai ser 

+muito grande. Não se esqueça de tira-lo. 

+ 

+ 	strip brltty

+

+7.3 - Melhorias futuras

+ 

+Além de corrigir os erros e dar suporte a mais tipos de displays 

+braille, esperamos, se o tempo permitir, trabalhar sobre o seguinte: 

+ 

+Better Attribute Handling 

+

+       * Acompanhamento de Atributo. 

+       * Misto de texto e modo de atributo. 

+ 

+Scroll Tracking 

+Bloquear a janela de uma linha braille já que rola na tela. 

+ 

+Better Speech Support

+ 

+       * Braille mistos e de fala para leitura mais rápida do texto. 

+       * Melhor navegação de speech. 

+       * Mais sintetizadores de voz. 

+ 

+Screen Subregions 

+Ignorar o movimento do cursor fora da região, e definir limites de 

+navegação suave nas bordas da região. 

+ 

+Veja o arquivo TODO para uma lista mais completa.

+

+7.4 Bugs conhecidos

+ 

+No momento da escrita (dezembro 2001), os seguintes problemas são 

+conhecidos: 

+ 

+Cursor de roteamento é implementado como um looping do sub-processo que 

+corre na prioridade reduzida para evitar o uso de muito tempo de CPU. Os 

+diferentes sistemas que é carregado requer diferentes configurações de 

+seus parâmetros. Os padrões funcionam muito bem em um editor de Unix 

+típico de um sistema bastante leve, mas muito pobre em algumas outras 

+situações, por exemplo, em um link lento de série para um host remoto. 

+

+Apêndice:

+

+A. Display Braille Suportados

+

+       BRLTTY suporta os seguintes displays braille:

+

+B. Sintetizadores de Fala Suportados:

+

+       BRLTTY suporta os seguintes sintetizadores de fala:

+

+      Nome             Modelos

+  ____________________________________________________________

+      Alva             Delphi (4nn)

+      BrailleLite

+      CombiBraille

+      eSpeak           text to speech engine

+      ExternalSpeech   runs /usr/local/bin/externalspeech

+      Festival         text to speech engine

+      FestivalLite     text to speech engine

+      GenericSay       pipes to /usr/local/bin/say

+      Mikropuhe        text to speech engine

+      Swift            text to speech engine

+      Theta            text to speech engine

+      ViaVoice         text to speech engine

+

+C – Código de Identificação de Driver

+Código 

+Nome

+al

+Alva

+at

+Albatross

+ba

+BrlAPI

+bl

+BrailleLite

+bm

+Baum (Native, HT, PB1, PB2)

+bn

+BrailleNote

+cb

+CombiBraille

+ec

+EcoBraille

+es

+eSpeak

+eu

+EuroBraille

+fl

+FestivalLite

+fs

+FreedomScientific

+fv

+Festival

+gs

+GenericSay

+hm

+HIMS

+ht

+HandyTech

+il

+IrisLinux

+lb

+Libbraille

+lt

+LogText

+mb

+MultiBraille

+md

+MDV

+mn

+MiniBraille

+mp

+Mikropuhe

+mt

+Metec

+no

+no driver

+pg

+Pegasus

+pm

+Papenmeier

+sd

+SpeechDispatcher

+sk

+Seika

+sw

+Swift

+th

+Theta

+ts

+Telesensory Systems Inc.

+tt

+TTY

+vd

+VideoBraille

+vo

+Voyager

+vr

+Virtual

+vs

+VisioBraille

+vv

+ViaVoice

+xs

+ExternalSpeech

+xw

+XWindow

+

+

+

+D. Drivers de Tela Suportados

+

+       BRLTTY suporta os seguintes drivers de tela:

+

+       as AT-SPI

+

+     	hd 

+Este driver fornece um acesso direto para a tela do console Hurd. É só 

+selecionar a opção padrão do sistema Hurd.

+

+      Name                Models

+    _____________________________________________________________

+      Albatross           46/80

+      Alva                ABT (3nn)

+                          Delphi (4nn)

+                          Satellite (5nn)

+                          Braille System 40

+                          Braille Controller 640/680

+      Baum                Inka

+                          Vario/RBT

+                          SuperVario/Brailliant

+                          PocketVario

+                          VarioPro

+                          EcoVario

+                          VarioConnect/BrailleConnect

+                          Refreshabraille

+      BrailComm           III

+      BrailleLite         18/40/M20/M40

+      BrailleNote         18/32

+      BrlAPI

+      CombiBraille        25/45/85

+      EcoBraille          20/40/80

+      EuroBraille         AzerBraille

+                          Clio

+                          Iris

+                          NoteBraille

+                          Scriba

+                          Esys 12/40

+      FreedomScientific   Focus 1 44/70/84

+                          Focus 2 40/80

+                          Focus Blue 40

+                          PAC Mate 20/40

+      HandyTech           Modular 20/40/80

+                          Modular Evolution 64/88

+                          Active Braille

+                          Braille Wave

+                          Easy Braille

+                          Braille Star 40/80

+                          Bookworm

+                          Braillino

+      HIMS                Braille Sense

+                          SyncBraille

+      Libbraille

+      LogText             32

+      MDV                 MB208/MB408L/MB408S (protocol 5)

+      Metec               BD-40

+      MiniBraille         20

+      MultiBraille        MB125CR/MB145CR/MB185CR

+      Papenmeier          Compact 486

+                          Compact/Tiny

+                          IB 80 CR Soft

+                          2D Lite (plus)

+                          2D Screen Soft

+                          EL 80

+                          EL 2D 40/66/80

+                          EL 40/66/70/80 S

+                          EL 2D 80 S

+                          EL 40 P

+                          EL 80 II

+                          Elba 20/32

+                          Trio 40/Elba20/Elba32

+      Pegasus             20/27/40/80

+      Seika               40

+      TSI                 Navigator 20/40/80

+                          PowerBraille 40/65/80

+      TTY                 terminfo

+      VideoBraille        40

+      Virtual             TCP/Unix, client/server

+      VisioBraille        20/40

+      Voyager             44/70

+                          Part232 (serial adapter)

+                          BraillePen/EasyLink

+      XWindow             X11

+                          Windows

+

+

+      Code   Name

+    ____________________________________________________

+      al     Alva

+      at     Albatross

+      ba     BrlAPI

+      bc     BrailComm

+      bl     BrailleLite

+      bm     Baum (Native, HT, PB1, PB2)

+      bn     BrailleNote

+      cb     CombiBraille

+      ec     EcoBraille

+      es     eSpeak

+      eu     EuroBraille

+      fl     FestivalLite

+      fs     FreedomScientific

+      fv     Festival

+      gs     GenericSay

+      hm     HIMS

+      ht     HandyTech

+      il     IrisLinux

+      lb     Libbraille

+      lt     LogText

+      mb     MultiBraille

+      md     MDV

+      mn     MiniBraille

+      mp     Mikropuhe

+      mt     Metec

+      no     no driver

+      pg     Pegasus

+      pm     Papenmeier

+      sd     SpeechDispatcher

+      sk     Seika

+      sw     Swift

+      th     Theta

+      ts     Telesensory Systems Inc.

+      tt     TTY

+      vd     VideoBraille

+      vo     Voyager

+      vr     Virtual

+      vs     VisioBraille

+      vv     ViaVoice

+      xs     ExternalSpeech

+      xw     XWindow

+

+lx 	 Este driver fornece um acesso direto no console do Linux. É só

+       selecionar a opção padrão do sistema Linux.

+      sc     Este driver fornece um acesso direto na tela do programa. Selecione

+        todos os sistemas, na opção padrão se o driver de tela nativo é

+        disponível. O caminho da tela que nós fornecemos (ver Subdiretório

+        de Caminhos) deverá ser aplicado. Usado neste driver junto com o

+        fato da tela deverá ser rodada concorrentemente, o BRLTTY faz o uso

+        efetivo apenas depois do usuário ter logado.

+      wn	Este driver fornece um acesso direto com o console do Windows. É só

+            selecionado a opção padrão em Windows/Cygwin systems.

+

+E. Sintaxe de Operação

+

+E.1. Especificação do Driver

+

+Um display braille ou um driver sintetizador de fala deverá ser especificado com 

+duas letras “código de identificação de driver”.

+

+Uma virgula delimita a lista de drivers especificada. Se isto está feito então a 

+autodetecção é feita usando cada lista de drivers em seqüência. Você deverá 

+precisar experimentar na ordem determinada como mais confiável quando alguns 

+drivers autodetectados forem melhores que os outros.

+

+Se uma única palavra auto é especificada quando a autodetecção é fornecida 

+usando apenas aqueles drivers que são conhecidos é confiável nesse propósito.

+

+E.2. Especificação do Dispositivo Braille

+

+Em geral, a especificação do dispositivo Braille (ver linha de comando –d, o 

+arquivo de configuração “braille-device”, e na compilação a opção “—with-

+braille-device”) é qualificada: dado. Para ser compatível com versões 

+anteriores, se a serial é assumida.

+

+A seguir os tipos de dispositivos são suportados: 

+

+	Bluetooth

+Para o dispositivo bluetooth, especifica bluetooth:address (bt : pode ser 

+usado). O endereço deverá ser seis pares de dígitos de hexadecimal 

+separados por dois pontos, e.g. 01:23:45:67:89:AB.

+

+	Serial

+Para o dispositivo serial, especificado: /path/to/device. A serial é 

+opcional (para compatibilidade anterior). Se um caminho relativo é dado 

+enquanto o caminho é anexado em /dev (a localização onde os dispositivos 

+são definidos no sistema unix). As especificações do dispositivo a seguir 

+todas se referem para o primeiro dispositivo serial no Linux:

+

+?	serial:/dev/ttyS0

+?	serial:ttyS0

+?	/dev/ttyS0

+?	ttyS0

+

+USB

+Para o dispositivo USB, especificado USB: o BRLTTY será procurado pelo 

+primeiro dispositivo USB que foi marcado pelo driver do display braille 

+que está sendo usado. Se isto é inadequado, e.g. se você tem mais de um 

+display braille USB que requer um mesmo driver, então você pode refinar a 

+especificação do dispositivo por numero de serial visível no display, e.g. 

+usb:12345. N.B.: A característica de identificação do numero serial” não 

+funciona em alguns modelos por causa de alguns fabricantes não setarem o 

+numero serial USB em todos ou setam mais não com um único valor.

+

+A virgula delimita a lista de dispositivos braille especificados. Se isso é 

+feito enquanto a autodeteção é feita em cada lista de dispositivo em seqüência.

+Este característica é particularmente usual se você tem um display braille com 

+mais de uma interface, e.g. ambos uma serial e uma porta USB. No caso é 

+usualmente melhor a lista de portas USB primeiro, e.g. usb:serial:/dev/ttyS0, de 

+

+port.  In this case it's usually better to list the USB port first, desde que a 

+autodeteção tende ser confiavel do que o esperado.

+

+E.3. Especificação do Dispositivo PCM

+

+Na maioria dos casos os dispositivos PCM é um caminho cheio e apropriado para o 

+dispositivo do sistema. Exceções são: 

+

+ALSA

+

+O nome dos argumentos são dispositivos físicos e lógicos i.e. 

+name[:argument,...].

+

+O dispositivo PCM padrão é:

+

+      Plataforma     Dispositivo

+    _______________________________________________________

+      FreeBSD        /dev/dsp

+      Linux/ALSA     hw:0,0

+      Linux/OSS      /dev/dsp

+      NetBSD         /dev/audio

+      OpenBSD        /dev/audio

+      Qnx            preferred PCM output device

+      Solaris        /dev/audio

+

+E.4. Especificação do Dispositivo MIDI

+

+Na maioria dos casos de dispositivos MIDI é um caminho completo e apropriado do 

+para o dispositivo do sistema. Exceções são:

+

+ALSA

+

+O cliente e porta são separadas por uma virgula, i.e. client:port. Cada um deve 

+ser especificado como um numero ou como um caso especial de substring do nome.

+

+O dispositivo padrão MIDI é:

+

+      Plataforma     Dispositivo

+    ____________________________________________________________

+      Linux/ALSA     the first available MIDI output port

+      Linux/OSS      /dev/sequencer

+

+F. Convenção do Padrão Braille para numeração

+

+Um padrão braille de celulas consiste em 6 pontos para alinhados em 3 linhas e 2 

+colunas. Cada ponto pode ser identificado por os números abaixo:

+

+       1 Topo-esquerda (linha 1, coluna 1).

+

+     	2 Meio-esquerda (linha 2, coluna 1).

+

+     	3 Fim-esquerda (linha 3, coluna 1).

+

+     	4 Topo-direita (linha 1, coluna 2).

+

+      5 Meio-direita (linha 2, coluna 2).

+

+      6 Fim-direita (linha 3, coluna 2).

+

+  Display braille tem introduzido 40 linhas no fim.

+

+     	7 Abaixo-esquerda (linha 4, coluna 1).

+

+     	8 Abaixo-direita (linha 4, coluna 2).

+

+  Talvez a imagem ficará mais facil de entender.

+

+       1 o o 4

+       2 o o 5

+       3 o o 6

+       7 o o 8

+

+G. Código Norte-Americano do Padrão Braille

+

+    Num Hex     Pontos     Descrição

+  +-----------------------------------------------------------------+

+  |   0  00  [7   4  8]  NUL (vazio)                                |

+  |   1  01  [7  1   8]  SOH (inicio do cabeçalho)                  |

+  |   2  02  [7 21   8]  STX (inicio do texto)                      |

+  |   3  03  [7  14  8]  ETX (fim do texto)                         |

+  |   4  04  [7  145 8]  EOT (fim da transmissão)                   |

+  |   5  05  [7  1 5 8]  ENQ (pesquisa)                             |

+  |   6  06  [7 214  8]  ACK (reconhecimento)                       |

+  |   7  07  [7 2145 8]  BEL (campainha)                            |

+  |   8  08  [7 21 5 8]  BS (back space)                            |

+  |   9  09  [7 2 4  8]  HT (tab horizontal)                        |

+  |  10  0A  [7 2 45 8]  LF (linha de avanço)                       |

+  |  11  0B  [73 1   8]  VT (tab vertical)                          |

+  |  12  0C  [7321   8]  FF (forma de avançar)                      |

+  |  13  0D  [73 14  8]  CR (retorno)                               |

+  |  14  0E  [73 145 8]  SO (shift out)                             |

+  |  15  0F  [73 1 5 8]  SI (shift in)                              |

+  |  16  10  [73214  8]  DLE (data link escape)                     |

+  |  17  11  [732145 8]  DC1 (direct control 1)                     |

+  |  18  12  [7321 5 8]  DC2 (direct control 2)                     |

+  |  19  13  [732 4  8]  DC3 (direct control 3)                     |

+  |  20  14  [732 45 8]  DC4 (direct control 4)                     |

+  |  21  15  [73 1  68]  NAK (não reconhecimento)                   |

+  |  22  16  [7321  68]  SYN (sincronização)                        |

+  |  23  17  [7 2 4568]  ETB (fim do bloco de texto)                |

+  |  24  18  [73 14 68]  CAN (cancela)                              |

+  |  25  19  [73 14568]  EM (fim do meio)                           |

+  |  26  1A  [73 1 568]  SUB (substituto)                           |

+  |  27  1B  [7 2 4 68]  ESC (escape)                               |

+  |  28  1C  [7 21 568]  FS (arquivo separador)                     |

+  |  29  1D  [7 214568]  GS (grupo separador)                       |

+  |  30  1E  [7   45 8]  RS (gravador separador)                    |

+  |  31  1F  [7   4568]  US (unidade separador)                     |

+  |  32  20  [        ]  espaço                                     |

+  |  33  21  [ 32 4 6 ]  ponto de exclamação                        |

+  |  34  22  [     5  ]  marcação de texto                          |

+  |  35  23  [ 3  456 ]  simbolo do numero                          |

+  |  36  24  [  214 6 ]  simbolo de dolar                           |

+  |  37  25  [   14 6 ]  simbolo de porcento                        |

+  |  38  26  [ 3214 6 ]  ampersand                                  |

+  |  39  27  [ 3      ]  acento agudo                               |

+  |  40  28  [ 321 56 ]  parenteses esquerdo                        |

+  |   4  291  [ 32 456 ) parenteses direito                         |

+  |  42  2A  [   1  6 ]  asterisco                                  |

+  |  43  2B  [ 3  4 6 ]  sinal de mais                              |

+  |  44  2C  [      6 ]  virgula                                    |

+  |  45  2D  [ 3    6 ]  sinal de menos                             |

+  |  46  2E  [    4 6 ]  ponto                                      |

+  |  47  2F  [ 3  4   ]  contra barra                               |

+  |  48  30  [ 3   56 ]  zero                                       |

+  |  49  31  [  2     ]  um                                         |

+  |  50  32  [ 32     ]  dois                                       |

+  |  51  33  [  2  5  ]  três                                       |

+  |  52  34  [  2  56 ]  quatro                                     |

+  |  53  35  [  2   6 ]  cinco                                      |

+  |  54  36  [ 32  5  ]  seis                                       |

+  |  55  37  [ 32  56 ]  sete                                       |

+  |  56  38  [ 32   6 ]  oito                                       |

+  |  57  39  [ 3   5  ]  nove                                       |

+  |  58  3A  [   1 56 ]  ponto e virgula                            |

+  |  59  3B  [     56 ]  dois pontos                                |

+  |  60  3C  [  21  6 ]  jacaré menos                               |

+  |  61  3D  [ 321456 ]  igual                                      |

+  |  62  3E  [ 3  45  ]  jacaré mais                                |

+  |  63  3F  [   1456 ]  interrogação                               |

+  |  64  40  [7   4   ]  e comercial                                |

+  |  65  41  [7  1    ]  maiusculo a                                |

+  |  66  42  [7 21    ]  maiusculo b                                |

+  |  67  43  [7  14   ]  maiusculo c                                |

+  |  68  44  [7  145  ]  maiusculo d                                |

+  |  69  45  [7  1 5  ]  maiusculo e                                |

+  |  70  46  [7 214   ]  maiusculo f                                |

+  |  71  47  [7 2145  ]  maiusculo g                                |

+  |  72  48  [7 21 5  ]  maiusculo h                                |

+  |  73  49  [7 2 4   ]  maiusculo i                                |

+  |  74  4A  [7 2 45  ]  maiusculo j                                |

+  |  75  4B  [73 1    ]  maiusculo k                                |

+  |  76  4C  [7321    ]  maiusculo l                                |

+  |  77  4D  [73 14   ]  maiusculo m                                |

+  |  78  4E  [73 145  ]  maiusculo n                                |

+  |  79  4F  [73 1 5  ]  maiusculo o                                |

+  |  80  50  [73214   ]  maiusculo p                                |

+  |  81  51  [732145  ]  maiusculo q                                |

+  |  82  52  [7321 5  ]  maiusculo r                                |

+  |  83  53  [732 4   ]  maiusculo s                                |

+  |  84  54  [732 45  ]  maiusculo t                                |

+  |  85  55  [73 1  6 ]  maiusculo u                                |

+  |  86  56  [7321  6 ]  maiusculo v                                |

+  |  87  57  [7 2 456 ]  maiusculo w                                |

+  |  88  58  [73 14 6 ]  maiusculo x                                |

+  |  89  59  [73 1456 ]  maiusculo y                                |

+  |  90  5A  [73 1 56 ]  maiusculo z                                |

+  |  91  5B  [7 2 4 6 ]  colchete esquerdo                          |

+  |  92  5C  [7 21 56 ]  barra invertida                            |

+  |  93  5D  [7 21456 ]  colchete direito                           |

+  |  94  5E  [7   45  ]  acento circunflexo                         |

+  |  95  5F  [    456 ]  sobretraço                                 |

+  |  96  60  [    4   ]  acento grave                               |

+  |  97  61  [   1    ]  minusculo a                                |

+  |  98  62  [  21    ]  minusculo b                                |

+  |  99  63  [   14   ]  minusculo c                                |

+  | 100  64  [   145  ]  minusculo d                                |

+  | 101  65  [   1 5  ]  minusculo e                                |

+  | 102  66  [  214   ]  minusculo f                                |

+  | 103  67  [  2145  ]  minusculo g                                |

+  | 104  68  [  21 5  ]  minusculo h                                |

+  | 105  69  [  2 4   ]  minusculo i                                |

+  | 106  6A  [  2 45  ]  minusculo j                                |

+  | 107  6B  [ 3 1    ]  minusculo k                                |

+  | 108  6C  [ 321    ]  minusculo l                                |

+  | 109  6D  [ 3 14   ]  minusculo m                                |

+  | 110  6E  [ 3 145  ]  minusculo n                                |

+  | 111  6F  [ 3 1 5  ]  minusculo o                                |

+  | 112  70  [ 3214   ]  minusculo p                                |

+  | 113  71  [ 32145  ]  minusculo q                                |

+  | 114  72  [ 321 5  ]  minusculo r                                |

+  | 115  73  [ 32 4   ]  minusculo s                                |

+  | 116  74  [ 32 45  ]  minusculo t                                |

+  | 117  75  [ 3 1  6 ]  minusculo u                                |

+  | 118  76  [ 321  6 ]  minusculo v                                |

+  | 119  77  [  2 456 ]  minusculo w                                |

+  | 120  78  [ 3 14 6 ]  minusculo x                                |

+  | 121  79  [ 3 1456 ]  minusculo y                                |

+  | 122  7A  [ 3 1 56 ]  minusculo z                                |

+  | 123  7B  [  2 4 6 ]  chave esquerda                             |

+  | 124  7C  [  21 56 ]  barra vertical                             |

+  | 125  7D  [  21456 ]  chave direita                              |

+  | 126  7E  [    45  ]  acento til                                 |

+  | 127  7F  [7   456 ]  DEL (delete)                               |

+  | 128  80  [    4  8]  <control>                                  |

+  | 129  81  [   1   8]  <control>                                  |

+  | 130  82  [  21   8]  BPH (break permitted here)                 |

+  | 131  83  [   14  8]  NBH (no break here)                        |

+  | 132  84  [   145 8]  <control>                                  |

+  | 133  85  [   1 5 8]  NL (proxima linha)                         |

+  | 134  86  [  214  8]  SSA (começa a seleção de area)             |

+  | 135  87  [  2145 8]  ESA (fim de seleção de area)               |

+  | 136  88  [  21 5 8]  CTS (seta tabulação de caracter)           |

+  | 137  89  [  2 4  8]  CTJ (justifica tabulação de caracter)      |

+  | 138  8A  [  2 45 8]  LTS (seta a tabulação de linha)            |

+  | 139  8B  [ 3 1   8]  PLD (linha parcial para baixo)             |

+  | 140  8C  [ 321   8]  PLU (linha parcial para cima)              |

+  | 141  8D  [ 3 14  8]  RLF (reverse line feed)                    |

+  | 142  8E  [ 3 145 8]  SS2 (single shift two)                     |

+  | 143  8F  [ 3 1 5 8]  SS3 (single shift three)                   |

+  | 144  90  [ 3214  8]  DCS (controle de dispositivo de string)    |

+  | 145  91  [ 32145 8]  PU1 (uso privado um)                       |

+  | 146  92  [ 321 5 8]  PU2 (uso privado dois)                     |

+  | 147  93  [ 32 4  8]  STS (seta estado de transmissão)           |

+  | 148  94  [ 32 45 8]  CC (cancela caracter)                      |

+  | 149  95  [ 3 1  68]  MW (message waiting)                       |

+  | 150  96  [ 321  68]  SGA (inicio da area protegida)             |

+  | 151  97  [  2 4568]  EGA (fim da area protegida)                |

+  | 152  98  [ 3 14 68]  SS (inicio da string)                      |

+  | 153  99  [ 3 14568]  <controle>                                 |

+  | 154  9A  [ 3 1 568]  SCI (introdução do caracter unico)         |

+  | 155  9B  [  2 4 68]  CSI (introdução de controle de sequencia)  |

+  | 156  9C  [  21 568]  ST (terminador de string)                  |

+  | 157  9D  [  214568]  OSC (comando de operação de sistema)       |

+  | 158  9E  [    45 8]  PM (mensagem privada)                      |

+  | 159  9F  [    4568]  APC (comando de programa de aplicação)     |

+  | 160  A0  [7      8]  sem quebra de espaço                       |

+  | 161  A1  [732 4 6 ]  ponto de exclamação invertido		   |

+  | 162  A2  [7 214 6 ]  simbolo do centavo                         |

+  | 163  A3  [73  456 ]  simbolo da libra                           |

+  | 164  A4  [7  14 6 ]  símbolo da moeda                           |

+  | 165  A5  [73214 6 ]  simbolo do yen                             |

+  | 166  A6  [7  1 56 ]  broken bar                                 |

+  | 167  A7  [73   5  ]  section sign                               |

+  | 168  A8  [7    5  ]  trema                                      |

+  | 169  A9  [732  56 ]  simbolo de copyright                       |

+  | 170  AA  [       8]  feminine ordinal indicator                 |

+  | 171  AB  [7 21  6 ]  left-pointing double angle quotation mark  |

+  | 172  AC  [7 2  56 ]  not sign                                   |

+  | 173  AD  [73    6 ]  soft hyphen                                |

+  | 174  AE  [732   6 ]  registered sign                            |

+  | 175  AF  [7 2   6 ]  macron                                     |

+  | 176  B0  [73   56 ]  degree sign                                |

+  | 177  B1  [73  4 6 ]  plus-minus sign                            |

+  | 178  B2  [732     ]  superscript two                            |

+  | 179  B3  [7 2  5  ]  superscript three                          |

+  | 180  B4  [73      ]  acute accent                               |

+  | 181  B5  [7    56 ]  micro sign                                 |

+  | 182  B6  [732  5  ]  pilcrow sign                               |

+  | 183  B7  [7   4 6 ]  middle dot                                 |

+  | 184  B8  [7     6 ]  cedilha                                    |

+  | 185  B9  [7 2     ]  superscript one                            |

+  | 186  BA  [7       ]  masculine ordinal indicator                |

+  | 187  BB  [73  45  ]  right-pointing double angle quotation mark |

+  | 188  BC  [7321 56 ]  vulgar fraction one quarter                |

+  | 189  BD  [7321456 ]  vulgar fraction one half                   |

+  | 190  BE  [732 456 ]  vulgar fraction three quarters             |

+  | 191  BF  [7  1456 ]  interrogação invertida                     |

+  | 192  C0  [732  5 8]  capital a grave                            |

+  | 193  C1  [7  1  68]  capital a acute                            |

+  | 194  C2  [7 2    8]  capital a circumflex                       |

+  | 195  C3  [7    5 8]  capital a tilde                            |

+  | 196  C4  [73214 68]  capital a diaeresis                        |

+  | 197  C5  [73  45 8]  capital a ring above                       |

+  | 198  C6  [73     8]  capital ae                                 |

+  | 199  C7  [73  4 68]  capital c cedilla                          |

+  | 200  C8  [732  568]  capital e grave                            |

+  | 201  C9  [7 21  68]  capital e acute                            |

+  | 202  CA  [732    8]  capital e circumflex                       |

+  | 203  CB  [73214568]  capital e diaeresis                        |

+  | 204  CC  [732   68]  capital i grave                            |

+  | 205  CD  [7  14 68]  capital i acute                            |

+  | 206  CE  [7 2  5 8]  capital i circumflex                       |

+  | 207  CF  [7321 568]  capital i diaeresis                        |

+  | 208  D0  [7     68]  capital eth                                |

+  | 209  D1  [7   4 68]  capital n tilde                            |

+  | 210  D2  [73   5 8]  capital o grave                            |

+  | 211  D3  [7  14568]  capital o acute                            |

+  | 212  D4  [7 2  568]  capital o circumflex                       |

+  | 213  D5  [7    568]  capital o tilde                            |

+  | 214  D6  [732 4 68]  capital o diaeresis                        |

+  | 215  D7  [7  1  6 ]  simbolo de multiplicação                   |

+  | 216  D8  [73  4  8]  capital o stroke                           |

+  | 217  D9  [73   568]  capital u grave                            |

+  | 218  DA  [7  1 568]  capital u acute                            |

+  | 219  DB  [7 2   68]  capital u circumflex                       |

+  | 220  DC  [732 4568]  capital u diaeresis                        |

+  | 221  DD  [7 214 68]  capital y acute                            |

+  | 222  DE  [73    68]  capital thorn                              |

+  | 223  DF  [73  4568]  small sharp s                              |

+  | 224  E0  [ 32  5 8]  small a grave                              |

+  | 225  E1  [   1  68]  small a acute                              |

+  | 226  E2  [  2    8]  small a circumflex                         |

+  | 227  E3  [     5 8]  small a tilde                              |

+  | 228  E4  [ 3214 68]  small a diaeresis                          |

+  | 229  E5  [ 3  45 8]  small a ring above                         |

+  | 230  E6  [ 3     8]  small ae                                   |

+  | 231  E7  [ 3  4 68]  small c cedilla                            |

+  | 232  E8  [ 32  568]  small e grave                              |

+  | 233  E9  [  21  68]  small e acute                              |

+  | 234  EA  [ 32    8]  small e circumflex                         |

+  | 235  EB  [ 3214568]  small e diaeresis                          |

+  | 236  EC  [ 32   68]  small i grave                              |

+  | 237  ED  [   14 68]  small i acute                              |

+  | 238  EE  [  2  5 8]  small i circumflex                         |

+  | 239  EF  [ 321 568]  small i diaeresis                          |

+  | 240  F0  [      68]  small eth                                  |

+  | 241  F1  [    4 68]  small n tilde                              |

+  | 242  F2  [ 3   5 8]  small o grave                              |

+  | 243  F3  [   14568]  small o acute                              |

+  | 244  F4  [  2  568]  small o circumflex                         |

+  | 245  F5  [     568]  small o tilde                              |

+  | 246  F6  [ 32 4 68]  small o diaeresis                          |

+  | 247  F7  [73  4   ]  simbolo de divisão                         |

+  | 248  F8  [ 3  4  8]  small o stroke                             |

+  | 249  F9  [ 3   568]  small u grave                              |

+  | 250  FA  [   1 568]  small u acute                              |

+  | 251  FB  [  2   68]  small u circumflex                         |

+  | 252  FC  [ 32 4568]  small u diaeresis                          |

+  | 253  FD  [  214 68]  small y acute                              |

+  | 254  FE  [ 3    68]  small thorn                                |

+  | 255  FF  [ 3  4568]  small y diaeresis                          |

+  +-----------------------------------------------------------------+

+

+H. Tabela de Instrumento MIDI

+

+Piano

+0

+Acoustic Grand Piano

+

+1

+Bright Acoustic Piano

+

+2

+Electric Grand Piano

+

+3

+Honkytonk Piano

+

+4

+Electric Piano 1

+

+5

+Electric Piano 2

+

+6

+Harpsichord

+

+7

+Clavi

+Chromatic Percussion

+8

+Celesta

+

+9

+Glockenspiel

+

+10

+Music Box

+

+11

+Vibraphone

+

+12

+Marimba

+

+13

+Xylophone

+

+14

+Tubular Bells

+

+15

+Dulcimer

+Organ

+16

+Drawbar Organ

+

+17

+Percussive Organ

+

+18

+Rock Organ

+

+19

+Church Organ

+

+20

+Reed Organ

+

+21

+Accordion

+

+22

+Harmonica

+

+23

+Tango Accordion

+Guitar

+24

+Nylon Acoustic Guitar

+

+25

+Steel Acoustic Guitar

+

+26

+Jazz Electric Guitar

+

+27

+Clean Electric Guitar

+

+28

+Muted Electric Guitar

+

+29

+Overdriven Guitar

+

+30

+Distortion Guitar

+

+31

+Guitar Harmonics

+Bass

+32

+Acoustic Bass

+

+33

+Finger Electric Bass

+

+34

+Pick Electric Bass

+

+35

+Fretless Bass

+

+36

+Slap Bass 1

+

+37

+Slap Bass 2

+

+38

+Synth Bass 1

+

+39

+Synth Bass 2

+Strings

+40

+Violin

+

+41

+Viola

+

+42

+Cello

+

+43

+Contrabass

+

+44

+Tremolo Strings

+

+45

+Pizzicato Strings

+

+46

+Orchestral Harp

+

+47

+Timpani

+Ensemble

+48

+String Ensemble 1

+

+49

+String Ensemble 2

+

+50

+Synth Strings 1

+

+51

+Synth Strings 2

+

+52

+Choir Aahs

+

+53

+Voice Oohs

+

+54

+Synth Voice

+

+55

+Orchestra Hit

+Brass

+56

+Trumpet

+

+57

+Trombone

+

+58

+Tuba

+

+59

+Muted Trumpet

+

+60

+French Horn

+

+61

+Brass Section

+

+62

+Synth Brass 1

+

+63

+Synth Brass 2

+Reed

+64

+Soprano Saxophone

+

+65

+Alto Saxophone

+

+66

+Tenor Saxophone

+

+67

+Baritone Saxophone

+

+68

+Oboe

+

+69

+English Horn

+

+70

+Bassoon

+

+71

+Clarinet

+Pipe

+72

+Piccolo

+

+73

+Flute

+

+74

+Recorder

+

+75

+Pan Flute

+

+76

+Blown Bottle

+

+77

+Shakuhachi

+

+78

+Whistle

+

+79

+Ocarina

+Synth Lead

+80

+Lead 1 (square)

+

+81

+Lead 2 (sawtooth)

+

+82

+Lead 3 (calliope)

+

+83

+Lead 4 (chiff)

+

+84

+Lead 5 (charang)

+

+85

+Lead 6 (voice)

+

+86

+Lead 7 (fifths)

+

+87

+Lead 8 (bass + lead)

+Synth Pad

+88

+Pad 1 (new age)

+

+89

+Pad 2 (warm)

+

+90

+Pad 3 (polysynth)

+

+91

+Pad 4 (choir)

+

+92

+Pad 5 (bowed)

+

+93

+Pad 6 (metallic)

+

+94

+Pad 7 (halo)

+

+95

+Pad 8 (sweep)

+Synth FM

+96

+FX 1 (rain)

+

+97

+FX 2 (soundtrack)

+

+98

+FX 3 (crystal)

+

+99

+FX 4 (atmosphere)

+

+100

+FX 5 (brightness)

+

+101

+FX 6 (goblins)

+

+102

+FX 7 (echoes)

+

+103

+FX 8 (science-fiction)

+Ethnic

+104

+Sitar

+

+105

+Banjo

+

+106

+Shamisen

+

+107

+Koto

+

+108

+Kalimba

+

+109

+Bag Pipe

+

+110

+Fiddle

+

+111

+Shanai

+Percussive

+112

+Tinkle Bell

+

+113

+Agogo

+

+114

+Steel Drum

+

+115

+Wooden Block

+

+116

+Taiko Drum

+

+117

+Melodic Tom

+

+118

+Synth Drum

+

+119

+Reverse Cymbal

+Sound Effects

+120

+Guitar Fret Noise

+

+121

+Breath Noise

+

+122

+Seashore

+

+123

+Bird Tweet

+

+124

+Telephone Ring

+

+125

+Helicopter

+

+126

+Applause

+

+127

+Gunshot

+

+

+

+

+

+

+

+

+

diff --git a/Documents/Manual-BrlAPI/English/BrlAPI.sgml b/Documents/Manual-BrlAPI/English/BrlAPI.sgml
new file mode 100644
index 0000000..28af572
--- /dev/null
+++ b/Documents/Manual-BrlAPI/English/BrlAPI.sgml
@@ -0,0 +1,1608 @@
+<!doctype linuxdoc system>
+<article>
+<title>BrlAPI Reference manual
+<author>
+<name>Sébastien Hinderer <tt><htmlurl
+url="mailto:Sebastien.Hinderer@ens-lyon.org"
+name="&lt;Sebastien.Hinderer@ens-lyon.org&gt;"></tt><newline></name>
+<and>
+<name>Samuel Thibault <tt><htmlurl url="mailto:Samuel.Thibault@ens-lyon.org"
+name="&lt;Samuel.Thibault@ens-lyon.org&gt;"></tt><newline></name>
+</author>
+<date>V1.5, November 2019
+<abstract>
+This document describes <tt>BrlAPI</tt>.
+</abstract>
+<toc>
+
+<!-- rappel des attributs:
+<bf/gras/ <em/mis en valeur/ <sf/sans serif/ <sl/slanted/ <tt/machine à écrire
+<it/italique
+-->
+
+<!---->
+<sect>Introduction<label id="sec-intro">
+<!---->
+<!-- Seb -->
+<!-- an introduction explaining very briefly what BrlAPI does, the
+vocabulary it uses, and how to read this documentation -->
+<p><em/BrlAPI/ is a service provided by the <em/brltty/ daemon.
+
+Its purpose is to allow programmers to write applications that take advantage
+of a braille terminal in order to deliver a blind user suitable information
+for his/her specific needs.
+
+While an application communicates with the braille terminal, everything
+<em/brltty/ sends to the braille terminal in the application's console is
+ignored, whereas each piece of data coming from the braille terminal is sent to
+the application, rather than to <em/brltty/.
+
+<sect1>Concepts
+
+<p> All throughout this manual, a few terms will be used which are either
+specific to braille terminals, or introduced because of <em/BrlAPI/. They are defined
+below. Taking a few minutes to go through this glossary will save a lot
+of time and questions later.
+
+<descrip>
+
+<tag/Authorization key/
+A file containing arbitrary data, that has to be sent to the server by the
+client, to prove it is allowed to establish a connection and then control
+the braille terminal.
+
+<tag/Braille display/
+The small screen on the braille terminal that is able to display braille text.
+
+<tag/Braille keyboard/
+The keyboard of the braille terminal.
+
+<tag/Braille terminal/
+A computer designed to display text in braille. In this case, the text is
+supposed to come from another computer running Linux or any other Unix system.
+
+<tag/Brltty/
+The background process that gives a blind person access to the console screen
+thanks to a braille terminal or speech synthetizer.
+
+<tag/Client/
+An application designed to handle a braille terminal thanks to <em/BrlAPI/.
+
+<tag/Command/
+A code returned by the driver, indicating an action to do, for instance
+"go to previous line", "go to next line", etc.
+
+<tag/Driver/
+A library that has functions to communicate with a braille terminal.
+Basically, a driver has functions to open communication with the
+braille terminal, close the communication, write on the braille
+display, and read keypresses from the braille keyboard, plus some special
+functions that will be described in detail in this manual.
+
+<tag/Key/
+A code that is returned by the driver when a key is pressed. This is
+different from a command, because the command concept is driver-independent
+(all drivers use the same command codes - those defined by <em/brltty/), whereas
+codes used for returning keypresses may vary between drivers.
+
+<tag/BrlAPI's Library/
+This library helps clients to connect and use <em/BrlAPI/'s
+server thanks to a series of <tt/brlapi_/-prefixed functions.
+
+<tag/Packet/
+A sequence of bytes making up the atomic unit in communications, either between
+braille drivers and braille terminals or between the server and clients.
+
+<tag/Raw mode/
+Mode in which the client application exchanges packets with the driver.
+Normal operations like sending text for display or reading keypresses are
+not available in this mode. It lets applications take advantage of advanced
+functionalities of the driver's communication protocol.
+
+<tag/Server/
+The part of <em/brltty/ that controls incoming connections and communication
+between clients and braille drivers.
+
+<tag/Suspend mode/
+Mode in which the server keeps the device driver closed, so that the client
+can connect directly to the device.
+
+<tag/Tty/
+Synonym for console, terminal, ...  Linux' console consist of several Virtual
+Ttys (VTs).  The screen program's windows also are Ttys.  X-window system's
+xterms emulate Ttys as well.
+
+</descrip>
+
+<sect1>How to read this manual
+
+<p>This manual is split in five parts.
+
+<descrip>
+
+<tag><ref id="sec-general" name="General description"></tag>
+Describes more precisely what <em/BrlAPI/
+is and how it works in collaboration with <em/brltty/'s core, the braille driver
+and clients. In this part, a "connection-use-disconnection" scenario
+will be described step by step, explaining for each step what <em/BrlAPI/ does in
+reaction to client instructions. These explanations will take place at a
+user level.
+
+<tag><ref id="sec-concurrency" name="Concurrency management"></tag>
+This part explains how concurrency between <em/BrlAPI/ clients is handled
+thanks to focus tellers.
+
+<tag><ref id="sec-install" name="Installation and configuration"></tag>
+This part explains in detail how to install and configure the API. For
+instructions on how to install and configure <em/brltty/, please report to
+the <em/brltty/ documentation.
+
+<tag><ref id="sec-library" name="Library description"></tag>
+This part describes how client applications
+can communicate with the server using the <em/BrlAPI/ library that
+comes with <em/brltty/. Each function will be briefly described,
+classified by categories. More exhaustive descriptions of every
+function are available in the corresponding online manual pages.
+
+<tag><ref id="sec-drivers" name="Writing braille drivers"></tag>
+This part describes how the braille drivers included in <em/brltty/ should be
+written in order to take advantage of <em/BrlAPI/'s services.
+
+<tag><ref id="sec-protocol" name="Protocol reference"></tag>
+This part describes in detail the communication
+protocol that is used to communicate between server and clients.
+
+</descrip>
+
+What should be read probably depends on what should be done by applications with
+<em/BrlAPI/.
+
+Reading chapters <ref id="sec-general" name="General description">, <ref
+id="sec-concurrency" name="Concurrency management"> and <ref id="sec-install"
+name="Installation and configuration"> is recommended, since they provide
+useful information and (hopefully) lead to a good understanding of <em/BrlAPI/,
+for an efficient use.
+
+Chapter <ref id="sec-library" Name="Library description"> concerns writing
+applications that take advantage of braille terminals so as to bring specific
+(and more useful) information to blind people.
+
+Chapter <ref id="sec-drivers" Name="Drivers"> is for braille driver implementation: either adding a braille driver
+to <em/brltty/ or modifying an existing one so that it can benefit from
+<em/BrlAPI/'s features, this chapter will be of interest, since it describes
+exactly what is needed to write a driver for <em/brltty/: the core
+of drivers interface for instance.
+
+Finally, chapter <ref id="sec-protocol" Name="Protocol reference"> is for <em/not using/ the library, but using the <em/BrlAPI/
+server directly, when the library might not be sufficient: it describes the
+underlying protocol that will have to be used to do so.
+
+<!---->
+<sect>General description of <em/BrlAPI/<label id="sec-general">
+<!---->
+<!-- a general documentation, which explains the issue and the design we
+chose to handle it -->
+
+<p>Here is explained what <em/BrlAPI/ is, and what it precisely does.
+These explanations should be simple enough to be accessible to every user.
+For a more technical review of <em/BrlAPI/'s functionalities, please see chapter
+<ref id="sec-library" name="Libary description">.
+
+<sect1>Historical notes.
+
+<p>Originally, <em/brltty/ was designed to give access to the Linux
+console to visually impaired people, through a braille terminal
+or a speech synthetizer. At that time, applications running in the
+console were not taking care of the presence of a braille
+terminal (most applications didn't even know what a braille
+terminal was).
+
+This situation where applications are not aware of the presence of a
+special device is elegant of course, since it lets use an
+unlimited number of applications which don't need to be specially
+designed for visually impaired people.
+
+However, it appeared that applications specially designed to take
+advantage of a braille terminal could be wanted, to
+provide the suitable information to blind users, for instance.
+The idea of <em/BrlAPI/ is to propose an efficient communication
+mechanism, to control the braille display, read keys from the
+braille keyboard, or to exchange data with the braille terminal at
+a lower level (e.g. to write file transfer protocols between
+braille terminals and Linux systems).
+
+<sect1>Why <em/BrlAPI/ is part of <em/brltty/.
+
+<p>Instead of rewriting a whole communication program from
+scratch, we chose to add communication
+mechanisms to <em/brltty/. This choice has two main justifications.
+
+On the one hand, integration to <em/brltty/ allows us to use the
+increasing number of drivers written for <em/brltty/, thus handling
+a large number of braille terminals without having to rewrite any
+piece of existing code.
+
+On the other hand, if an application chooses to send its own
+information to the braille display, and to process braille keys,
+<em/brltty/ has to be warned, so that it won't try to communicate
+with the braille terminal while the application already does.
+To make this synchronzation between <em/brltty/
+and client applications possible, it seemed easier to add the
+communication mechanisms to <em/brltty/'s core, instead of writing an
+external program providing them.
+
+<sect1>How it works.
+
+<p>We are now going to describe the steps an application should
+go through to get control of the braille terminal, and what
+happens on <em/brltty/'s side at each step. This step-by-step
+description will let us introduce more precisely some
+concepts that are useful for every <em/BrlAPI/ user.
+
+<sect2>Connection.
+
+<p>The first thing any client application has to do is to
+connect (in the Unix sense of the word) to <em/BrlAPI/ which is
+an mere application server. If this is not
+clear, the only thing to be remembered is that this
+step allows the client application to let the server know about its
+presence. At this stage, nothing special is done on <em/brltty/'s
+side.
+
+<sect2>Authorization.
+
+<p>Since Unix is designed to allow many users to work on the
+same machine, it's quite possible that there are more than one
+user accounts on the system. Most probably, one doesn't want
+any user with an account on the machine to be able to communicate
+with the braille terminal (just imagine what would happen if,
+while somebody was working with the braille terminal, another user
+connected to the system began to communicate with it,
+preventing the first one from doing his job...). That's why <em/BrlAPI/ has to
+provide a way to determine whether a user who established a
+connection is really allowed to communicate with the braille
+terminal. To achieve this, <em/BrlAPI/ requires that each
+application that wants to control a braille terminal sends an
+authorization key before doing anything else. The control of
+the braille terminal will only be possible for the client once
+it has sent the proper authorization key. What is called
+authorization key is in fact a Unix file containing data (it
+must be non-empty) on your system. All the things you have to do is to give
+read permissions on this file to users that are allowed to
+communicate with the braille terminal, and only to them. This
+way, only authorized users will have access to the
+authorization key and then be able to send it to <em/BrlAPI/.
+To see how to do that, please see chapter <ref id="sec-install" name="Installation and configuration">.
+
+At the end of this step, the user is authorized to take
+control of the braille terminal. On <em/brltty/'s side, some data
+structures are allocated to store information on the client,
+but this has no user-level side-effect.
+
+<sect2>Real use of the braille terminal.
+
+<p>Once the client is properly connected and authorized,
+there are two possible types of communication with the braille
+terminal. The chosen type of communication depends on what the
+client plans to do. If its purpose is to display information on
+the braille display or to process braille keys, it will have to
+take control of the Linux tty on which it is running. If its
+purpose is to exchange data with the braille terminal (e.g. for
+file transfer), it will enter what is called "raw mode".
+
+<sect3>Braille display and braille key presses processing.
+
+<p>If the client wants to display something on the braille
+display or to process braille keys itself, rather than letting
+<em/brltty/ process them, it has to take control of the Linux
+terminal it is running on.
+
+Once a client has obtained the control of his tty, <em/BrlAPI/
+will completely discard <em/brltty/'s display on this tty (and only
+this one), leaving the braille display free for the client.
+
+At the same time, if a key is pressed on the braille
+keyboard, <em/BrlAPI/ checks whether the client application is
+interested in this key or not. If it is, the key is passed to
+it, either as a key code or as a <em/brltty/ command. If it is not, the
+key code is converted into a <em/brltty/ command and returned to
+<em/brltty/.
+
+Once the client is not interested in displaying text
+or reading braille keys any more, it has to leave the tty, so
+that either <em/brltty/ can continue its job, or another client can
+take control of it.
+
+<sect3>Parameter handling
+
+The server exposes some parameters to the client. Some parameters are global to
+all clients (e.g. the braille display size), while others are local per client
+(e.g. retaindots, i.e. whether to send Perkins presses as dot patterns or as
+letters). Some parameters are read-only (e.g. the braille display size), while
+others are read-write (e.g. retaindots). Some parameters may change during
+execution, while others change only when a client set it.
+
+Clients can either request the current value of a parameter, or set its value
+(if it is read-write), or request the server to notify on value change.
+
+<sect3>Raw mode.
+
+<p>Only one client can be in raw mode at the same time. In
+this mode, data coming from the braille terminal are checked
+by the driver (to ensure they are valid), but instead of being processed,
+they are delivered "as-is" to the client that is in raw mode.
+
+In the other direction, packets sent to <em/BrlAPI/ by the
+client that is in raw mode are passed to the driver which is
+expected to deliver them to the braille terminal without any
+modification.
+
+<sect3>Suspend Mode.
+
+<p>Only one client can be in suspend mode at the same time. This mode is also
+exclusive with raw mode. In this mode, the server keeps the device driver
+closed, and thus the client can open the device directly by itself.
+
+<p><bf/This mode is not recommended/, since the client will then have to
+reimplement device access. Raw mode should really be preferred, since it lets
+the client take advantage of server's ability to talk with the device
+(USB/bluetooth support for instance).
+
+<sect3>Remarks.
+
+<p>
+
+<itemize>
+
+<item>The operations described in the three previous
+subsections are not completely mutually exclusive. An
+application that controls its current tty can enter raw or suspend
+mode, provided that no other application already is in this
+mode.
+
+<item>Not every braille driver supports raw mode. It has
+to be specially (re)written to support it, since it has
+to provide special functions to process incoming and outgoing
+packets. The same restriction is true (but less strong)
+concerning the ability to deliver/convert keycodes into
+commands: not every driver has this ability, it has to be
+modified to get it.
+
+<item>Operations previously described can be repeated.
+You can, for instance, use raw mode to transfer data onto
+your braille terminal, display text in braille, return to raw
+mode..., all that without having to reconnect to <em/BrlAPI/ before
+each operation.
+
+</itemize>
+
+<sect2>Disconnection.
+
+<p>Once the client has finished using the braille terminal, it
+has to disconnect from the API, so that the memory structures
+allocated for the connection can be freed and eventually used by
+another client. This step is transparent for the user, in the
+sense that it involves no change on the braille display.
+
+
+<!---->
+<sect>Concurrency management between <em/BrlAPI/ clients<label id="sec-concurrency">
+<!---->
+<!-- Sam -->
+<!-- explain focus tellers, tty tree, & co -->
+
+<p>
+An essential purpose of <em/BrlAPI/ is to manage concurrent access to the
+braille display between the <em/brltty/ daemon and applications. This
+concurrency is managed "per Tty". We first describe this with a flat view, and
+then consider Tty hierarchy.
+
+<sect1>VT switching
+
+<p>
+Let's first describe how things work with the simple case of a single series of
+Virtual Ttys (VTs), the linux console for instance.
+
+<p>
+As described in <ref id="sec-general" name="General Description">, before being able
+to write output, a <em/BrlAPI/ client has to "get" a tty, i.e. it sends to the
+<em/BrlAPI/ server the number of the linux' Virtual Tty on which it is running.
+The <em/BrlAPI/ server uses this information so as to know which client's output
+should be shown on the braille display, according to the focus teller's
+information.
+
+<p>
+Let's say some client <em/A/ is running on VT 2.  It "got" VT 2 and wrote some
+output on its <em/BrlAPI/ connection.  The focus teller is <em/brltty/ here: it
+always tells to the <em/BrlAPI/ server which VT is currently shown on the screen
+and gets usual keyboard presses (it is "active").
+
+<p>
+Let's say VT 1 is active, then the <em/BrlAPI/ server shows <em/brltty/'s output
+on the braille display.  I.e. the usual <em/brltty/ screen reading appears.
+Moreover, when braille keys are pressed, they are passed to <em/brltty/, so that
+usual screen reading can be performed.  When the user switches to VT 2,
+<em/brltty/ (as focus teller) tells it to the <em/BrlAPI/ server, which then
+remembers that client <em/A/ has got it and has produced some output.  The
+server then displays this output on the braille display.  Note that <em/A/
+doesn't need to re-submit its output: the server had recorded it so as to be
+able to show it as soon as the focus switches to VT 2.  Whenever some key of the
+braille device is pressed, <em/BrlAPI/ looks whether it is in the list of keys
+that client <em/A/ said to be of his interest.  If it is, it is passed to <em/A/
+(and not to <em/brltty/). If it isn't, it is passed to <em/brltty/ (and not to
+<em/A/).
+
+<p>
+As a consequence, whenever clients get and release Ttys and the user switches
+between Ttys, either the <em/brltty/ screen reading or the client's output is
+automatically shown according to rather natural rules.
+
+<sect1>A pile of "paper sheets"
+
+<p>
+Let's look at VT 2 by itself. What is shown on the braille display can be seen
+as the result of a pile of two paper sheets.  <em/brltty/ is represented by the
+bottom sheet on which its screen reading is written, and client <em/A/ by the
+top sheet on which its output is written. <em/A/'s sheet hence "covers"
+<em/brltty/'s sheet: <em/A/'s output "mask" <em/brltty/'s screen reading.
+
+<p>
+<em/A/ may yet want to temporarily let <em/brltty/'s screen reading appear on VT
+2, while still receiving some key presses, for instance.  For this, it sends a
+"void" write.  The server then clears the recorded output for this connection
+(in the sheet representation, the sheet becomes "transparent").  As a
+consequence, <em/brltty/'s output is automatically shown (by transparency in the
+sheet representation), just like if <em/A/ had released the Tty.
+
+<p>
+Keypresses are handled in a similar way: <em/A/'s desire to get key presses is
+satisfied first before <em/brltty/.
+
+<p>
+Let's say some other client <em/B/ (probably launched by <em/A/) also gets VT 2
+and outputs some text on its <em/BrlAPI/ connection.  This adds a third sheet,
+on top of the two previous ones.  It means that the <em/BrlAPI/ server will show
+<em/B/'s output on the braille device.  If <em/A/ then outputs some text, the
+server will record it (on <em/A/'s sheet which hence becomes opaque again), but
+it won't be displayed on the braille device, since <em/B/'s sheet is still at
+the top and opaque (i.e. with some text on it).  But if <em/B/ issues a void
+write, the server clears its ouput buffer (i.e. <em/B/'s sheet becomes
+transparent), and as a result <em/A/'s output appear on the braille display (by
+transparency through <em/B/'s sheet).
+
+<p>
+The sheet order is by default determined by the Tty "get"ting order. Clients
+can however change their priority (which by default is 50) to a higher value in
+order to show up higher in the pile, or to a lower value in order to hide lower
+in the pile.
+
+<sect1>Hierarchy
+
+<p>
+Now, what happens when running some <em/screen/ program on, say, VT 3?  It
+emulates a series of Ttys, whose output actually appear on the same VT 3.
+That's where a hierarchy level appears: the focus information is not only the VT
+number but also, in the case of VT 3, which <em/screen/ window is active.  This
+hence forms a <em/tree/ of Ttys: the "root" being the vga driver's output, whose
+sons are VTs, and VT 3 has the <em/screen/ windows as sons.  <em/Brltty/ is a
+focus teller for the root, <em/screen/ will have to be a focus teller for VT 3.
+<em/Screen/ should then get VT 3, not display anything (so that the usual
+<em/brltty/ screen reading will be shown by transparency), and tell the
+<em/BrlAPI/ server which <em/screen/ window is active (at startup and at each
+window switch).  This is not implemented directly in <em/screen/ yet, but this
+may be achieved via a second <em/brltty/ daemon running the Screen driver (but
+it isn't yet able to get the current window number though) and the <em/BrlAPI/
+driver.
+
+<p>
+A <em/BrlAPI/ client <em/C/ running in some <em/screen/ window number 1 would
+then have to get the Tty path "VT 3 then window 1", which is merely expressed
+as "3 1".  The window number is available in the <tt/WINDOW/ environment
+variable, set by <em/screen/. The VT number, which actually represents the "path
+to screen's output" should be available in the <tt/WINDOWPATH/ environment
+variable, also set by <em/screen/.  The client can thus merely concatenate the
+content of <tt/WINDOWPATH/ (which could hold many levels of window numbers) and
+of <tt/WINDOW/ and give the result as tty path to the <em/BrlAPI/ server, which
+then knows precisely where the client's usual output resides.  In practice,
+applications just need to call <tt/brlapi_enterTtyMode(BRLAPI_TTY_DEFAULT)/, and
+the the <em/BrlAPI/ client library will automatically perform all that.
+
+<p>
+Whenever the user switches to VT 3, the <em/BrlAPI/ server remembers the window
+that <em/screen/ told to be active.  If it was window 1, it then displays
+<em/C/'s output (if any).  Else <em/brltty/'s usual screen reading is shown.
+Of course, several clients may be run in window 1 as well, and the "sheet pile"
+mecanism applies: <em/brltty/'s sheet first (at the root of the Ttys tree), then
+<em/screen/'s sheet (which is transparent, on VT 3), then <em/C/'s sheet (on
+window 1 of VT 3), then other clients' sheets (on the same window).
+
+<p>
+Ttys are hence organized in a tree, each client adding its sheet at some tty in
+the tree.
+
+<sect1>The X-window case
+
+<p>
+Let's say some X server is running on VT 7 of a Linux system. Xorg's <em/xinit/
+and <em/xdm/ commands automatically set the X session's <tt/WINDOWPATH/
+environment variable to "7", so that X11 <em/BrlAPI/ clients started from
+the session just need to call <em/brlapi_enterTtyMode(xid)/ where <em/xid/
+is the X-window ID of the window of the client. The <em/BrlAPI/ library
+will automatically prepend the content of <tt/WINDOWPATH/ to it.
+
+<p>
+For text-based <em/BrlAPI/ clients running in an xterm (which should just call
+<tt/brlapi_enterTtyMode(BRLAPI_TTY_DEFAULT)/ as explained in the previous
+section), <em/BrlAPI/ detects the window id thanks to the <tt/WINDOWID/ variable
+set by xterm.
+
+<p>
+Screen readers are not bound to a particular window, so they should call
+<em/brlapi_enterTtyModeWithPath(NULL, 0)/ to let the <em/BrlAPI/ library only
+send the content of <tt/WINDOWPATH/, expressing that screen readers take the
+whole tty.  The user should notably launch <em/xbrlapi/, which is a focus
+teller for X-window as well as a keyboard simulator (<em/brltty/ can't reliably
+simulate them at the kernel level in such situation).  For accessing AT-SPI
+contents (like gnome or kde applications), Orca should also be launched.  For
+accessing AT-SPI terminals (like gnome-terminal) in the same way as in the
+console, a second <em/brltty/ daemon running the at-spi screen driver and the
+<em/BrlAPI/ driver can also be launched.  All three would get the VT of the
+X session, in that order (for now): <em/xbrlapi/ first, then <em/orca/ and
+<em/brltty/ at last.  When the X focus is on an AT-SPI terminal, <em/brltty/
+will hence be able to grab the braille display and key presses.  Else <em/orca/
+would get them.  And <em/xbrlapi/ would finally get remaining key presses and
+simulate them.
+
+<p>
+Note: old versions of <tt/xinit/, <tt/xdm/, <tt/kdm/ or <tt/gdm/ do not
+automatically set the <tt/WINDOWPATH/ variable. The user can set it by hand in
+his <tt>~/.xsession</tt>, <tt>~/.xinitrc</tt>, <tt>~/.gdmrc</tt>... to "7"
+
+<p>
+Note: some Operating Systems like Solaris do not have VTs. In that case
+<tt/WINDOWPATH/ is empty or not even set.  Everything explained above still
+work fine.
+
+<sect1>Detaching
+
+<p>
+Several programs allow detaching: <em/screen/ and <em/VNC/ for instance. In such
+situation, an intermediate <em/BrlAPI/ server should be run for each such
+session. Clients would connect to it, and it would prepend the "current tty"
+path on the fly while forwarding things to the root <em/BrlAPI/ server. This
+intermediate server is yet to be written (but it is actually relatively close to
+be).
+
+
+<!---->
+<sect>Installation and configuration of <em/BrlAPI/<label id="sec-install">
+<!---->
+<!-- Seb -->
+<!-- an installation and configuration documentation -->
+<p>
+<tt/make install/ will install libbrlapi.so in /lib, and include files in
+/usr/include/brltty. An authorization key will also typically be set in
+/etc/brlapi.key (if it is not, just create it and put arbitrary data in it), but
+it won't be readable by anybody else than root. It is up
+to you to define a group of users who will have the right to read it and hence
+be able to connect to the server. For instance, you may want to do:
+
+<tscreen><code>
+# addgroup brlapi
+# chgrp brlapi /etc/brlapi.key
+# chmod g+r /etc/brlapi.key
+# addgroup user1 brlapi
+# addgroup user2 brlapi
+...
+</code></tscreen>
+
+
+<!---->
+<sect>Library description<label id="sec-library">
+<!---->
+<!-- the library documentation, for those who want to write
+applications which use it, it should be split in: -->
+
+<p>
+Let's now see how one can write dedicated applications. Basic notions will be
+seen, along with a very simple client. Greater details are given as online
+manual pages.
+
+<!-- a basic documentation, which briefly shows
+how one is supposed to use our library, with trivial examples -->
+
+<p>
+The historical test program for <em/BrlAPI/ was something like:
+<itemize>
+<item>connect to <em/BrlAPI/
+<item>get driver id
+<item>get driver name
+<item>get display size
+<item>try entering raw mode, immediately leave raw mode.
+<item>get tty control
+<item>write something on the display
+<item>wait for a key press
+<item>leave tty control
+<item>disconnect from <em/BrlAPI/
+</itemize>
+
+It is here rewritten, its working briefly explained.
+
+<sect1>Connecting to <em/BrlAPI/
+
+<p>Connection to <em/BrlAPI/ is needed first, thanks to the
+<tt>brlapi_openConnection</tt> call. For this, a
+<tt>brlapi_connectionSettings_t</tt> variable must be filled which will hold the
+settings the library needs to connect to the server. Just giving <tt/NULL/
+will work for local use. The other parameter lets you get back the parameters
+which were actually used to initialize connection. <tt/NULL/ will also be nice
+for now.
+
+<tscreen><code>
+  if (brlapi_openConnection(NULL, NULL)<0) {
+    brlapi_perror("brlapi_openConnection");
+    exit(1);
+  }
+</code></tscreen>
+
+The connection might fail, so testing is needed.
+
+<sect1>Getting driver name
+
+<p>Knowing the type of the braille device might be useful:
+
+<tscreen><code>
+  char name[BRLAPI_MAXNAMELENGTH+1];
+  if (brlapi_getDriverName(name, sizeof(name)) < 0)
+    brlapi_perror("brlapi_getDriverName");
+  else
+    fprintf(stderr, "Driver name: %s\n", name);
+</code></tscreen>
+
+This is particularly useful before entering raw mode to achieve file
+transfers for instance, just to check that the device is really the one
+expected.
+
+<sect1>Getting display size
+
+<p>Before writing on the braille display, the size should be always first
+checked to be sure everything will hold on it:
+
+<tscreen><code>
+  if (brlapi_getDisplaySize(&amp;x, &amp;y) < 0)
+    brlapi_perror("brlapi_getDisplaySize");
+  else
+    fprintf(stderr, "Braille display has %d line%s of %d column%s\n",
+      y, y>1?"s":"", x, x>1?"s":"");
+</code></tscreen>
+
+<sect1>Entering raw mode, immediately leaving raw mode.
+
+<p>Entering raw mode is very simple:
+
+<tscreen><code>
+  fprintf(stderr, "Trying to enter in raw mode... ");
+  if (brlapi_enterRawMode(name) < 0)
+    brlapi_perror("brlapi_enterRawMode");
+  else {
+    fprintf(stderr, "Ok, leaving raw mode immediately\n");
+    brlapi_leaveRawMode();
+  }
+</code></tscreen>
+
+Not every driver supports raw mode, so testing is needed.
+
+While in raw mode, <tt>brlapi_sendRaw</tt> and <tt>brlapi_recvRaw</tt>
+can be used to send and get data directly to and from the device.
+It should be used with care, improper use might completely thrash the device!
+
+<sect1>Getting tty control
+
+<p>Let's now display something on the device. control of the tty must be get
+first:
+
+<tscreen><code>
+  fprintf(stderr, "Taking control of the tty... ");
+  if (brlapi_enterTtyMode(BRLAPI_TTY_DEFAULT, NULL) >= 0)
+  {
+    fprintf(stderr, "Ok\n");
+</code></tscreen>
+
+The first parameter tells the server the number of the tty to take
+control of. Setting BRLAPI_TTY_DEFAULT lets the library determine it for us.
+
+<p>The server is asked to send <em/brltty/ commands, which are device-independent.
+
+<p>Getting control might fail if, for instance, another application already took
+control of this tty, so testing is needed.
+
+<p>From now on, the braille display is detached from the screen.
+
+<sect1>Writing something on the display
+
+<p>The application can now write things on the braille display without
+altering the screen display:
+
+<tscreen><code>
+    fprintf(stderr, "Writing to braille display... ");
+    if (brlapi_writeText(0, "Press a braille key to continue...") >= 0)
+    {
+      fprintf(stderr, "Ok\n");
+</code></tscreen>
+
+The cursor is also asked <em/not/ to be shown: its position is set to 0.
+
+<p>"Writing to braille display... Ok" is now displayed on the screen, and
+"Press a braille key to continue..." on the braille display.
+
+<sect1>Waiting for a key press
+
+<p>To have a break for the user to be able to read these messages,
+a key press (a command here, which is driver-independent) may be waited for:
+
+<tscreen><code>
+      fprintf(stderr, "Waiting until a braille key is pressed to continue... ");
+      if (brlapi_readKey(1, &amp;key) > 0)
+        fprintf(stderr, "got it! (code=%"BRLAPI_PRIxKEYCODE")\n", key);
+</code></tscreen>
+
+The command is returned, as described in <tt>&lt;brlapi_constants.h></tt>
+and <tt>&lt;brlapi_keycodes.h></tt>.
+It is not transmitted to <em/brltty/: it is up to the application to define
+the behavior, here cleanly exitting, as described below.
+
+The first parameter tells the lib to block until a key press is indeed read.
+
+<sect1>Understanding commands
+
+<p>There are two kinds of commands: braille commands (line up/down, top/bottom,
+etc.) and X Keysyms (i.e. regular keyboard keys). One way to discover which key
+was pressed is to just use a switch statement: 
+
+<tscreen><code>
+        switch(key) {
+	  case BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_LNUP:
+	    fprintf(stderr, "line up\n");
+	    break;
+	  case BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_LNDN:
+	    fprintf(stderr, "line down\n");
+	    break;
+	  case BRLAPI_KEY_TYPE_SYM|XK_Tab:
+	    fprintf(stderr, "tab\n");
+	    break;
+	  default:
+	    fprintf(stderr, "unknown key\n");
+	    break;
+	}
+</code></tscreen>
+
+Another way is to ask BrlAPI to expand the keycode into separate information
+parts:
+
+<tscreen><code>
+        brlapi_expandedKeyCode_t ekey;
+	brlapi_expandKeyCode(key, &amp;ekey);
+	fprintf(stderr, "type %u, command %u, argument %u, flags %u\n",
+	  ekey.type, ekey.command, ekey.argument, ekey.flags);
+</code></tscreen>
+
+Eventually, named equivalents are provided:
+
+<tscreen><code>
+        brlapi_describedKeyCode_t dkey;
+	int i;
+
+	brlapi_describeKeyCode(key, &amp;dkey);
+	fprintf(stderr, "type %s, command %s, argument %u, flags",
+	  dkey.type, dkey.command, dkey.argument);
+	for (i = 0; i < dkey.flags; i++)
+	  fprintf(stderr, " %s", dkey.flag[i]);
+	fprintf(stderr, "\n");
+</code></tscreen>
+
+
+
+
+<sect1>Leaving tty control
+
+<p>Let's now leave the tty:
+
+<tscreen><code>
+    fprintf(stderr, "Leaving tty... ");
+    if (brlapi_leaveTtyMode() >= 0)
+      fprintf(stderr, "Ok\n");
+</code></tscreen>
+
+But control of another tty can still be get for instance, by calling
+<tt>brlapi_enterTtyMode()</tt> again...
+
+<sect1>Disconnecting from <em/BrlAPI/
+
+<p>Let's disconnect from <em/BrlAPI/:
+
+<tscreen><code>
+  brlapi_closeConnection();
+</code></tscreen>
+
+The application can as well still need to connect to another server on another
+computer for instance, by calling <tt>brlapi_openConnection()</tt>
+again...
+
+<sect1>Putting everything together...
+
+<p>
+<tscreen><code>
+#include <stdio.h>
+#include <stdlib.h>
+#include <brlapi.h>
+
+int main()
+{
+  brlapi_keyCode_t key;
+  char name[BRLAPI_MAXNAMELENGTH+1];
+  unsigned int x, y;
+
+/* Connect to BrlAPI */
+  if (brlapi_openConnection(NULL, NULL)<0)
+  {
+    brlapi_perror("brlapi_openConnection");
+    exit(1);
+  }
+
+/* Get driver name */
+  if (brlapi_getDriverName(name, sizeof(name)) < 0)
+    brlapi_perror("brlapi_getDriverName");
+  else
+    fprintf(stderr, "Driver name: %s\n", name);
+
+/* Get display size */
+  if (brlapi_getDisplaySize(&amp;x, &amp;y) < 0)
+    brlapi_perror("brlapi_getDisplaySize");
+  else
+    fprintf(stderr, "Braille display has %d line%s of %d column%s\n",
+      y, y>1?"s":"", x, x>1?"s":"");
+
+/* Try entering raw mode, immediately go out from raw mode */
+  fprintf(stderr, "Trying to enter in raw mode... ");
+  if (brlapi_enterRawMode(name) < 0)
+    brlapi_perror("brlapi_enterRawMode");
+  else {
+    fprintf(stderr, "Ok, leaving raw mode immediately\n");
+    brlapi_leaveRawMode();
+  }
+
+/* Get tty control */
+  fprintf(stderr, "Taking control of the tty... ");
+  if (brlapi_enterTtyMode(BRLAPI_TTY_DEFAULT, NULL) >= 0)
+  {
+    fprintf(stderr, "Ok\n");
+
+/* Write something on the display */
+    fprintf(stderr, "Writing to braille display... ");
+    if (brlapi_writeText(0, "Press a braille key to continue...") >= 0)
+    {
+      fprintf(stderr, "Ok\n");
+
+/* Wait for a key press */
+      fprintf(stderr, "Waiting until a braille key is pressed to continue... ");
+      if (brlapi_readKey(1, &amp;key) > 0) {
+        brlapi_expandedKeyCode_t ekey;
+        brlapi_describedKeyCode_t dkey;
+	int i;
+
+        fprintf(stderr, "got it! (code=%"BRLAPI_PRIxKEYCODE")\n", key);
+
+	brlapi_expandKeyCode(key, &amp;ekey);
+	fprintf(stderr, "type %u, command %u, argument %u, flags %u\n",
+	  ekey.type, ekey.command, ekey.argument, ekey.flags);
+
+	brlapi_describeKeyCode(key, &amp;dkey);
+	fprintf(stderr, "type %s, command %s, argument %u, flags",
+	  dkey.type, dkey.command, dkey.argument);
+	for (i = 0; i < dkey.flags; i++)
+	  fprintf(stderr, " %s", dkey.flag[i]);
+	fprintf(stderr, "\n");
+      } else brlapi_perror("brlapi_readKey");
+
+    } else brlapi_perror("brlapi_writeText");
+
+/* Leave tty control */
+    fprintf(stderr, "Leaving tty... ");
+    if (brlapi_leaveTtyMode() >= 0)
+      fprintf(stderr, "Ok\n");
+    else brlapi_perror("brlapi_leaveTtyMode");
+
+  } else brlapi_perror("brlapi_enterTtyMode");
+
+/* Disconnect from BrlAPI */
+  brlapi_closeConnection();
+  return 0;
+}
+</code></tscreen>
+
+This should compile well thanks to
+<tt>gcc apiclient.c -o apiclient -lbrlapi</tt>
+
+<!---->
+<sect>Writing (<em/BrlAPI/-compliant) drivers for <em/brltty/<label id="sec-drivers">
+<!---->
+<!-- Seb -->
+
+<p>In this chapter, we will describe in details how to write a
+driver for <em/brltty/. We begin with a general description of the
+structure the driver should have, before explaining more precisely
+what each function is supposed to do.
+
+<sect1>Overview of the driver's structure
+
+<p>A braille driver is in fact a library that is either
+dynamically loaded by <em/brltty/ at startup, or statically linked to
+it during the compilation, depending on the options given to the
+<tt>./configure</tt> script.
+
+This library has to provide every function needed by the core,
+plus some additional functions, that are not mandatory, but which
+improve communication with <em/BrlAPI/ and the service level provided
+to client applications.
+
+Basically, a driver library needs to provide a function to open
+the communication with the braille terminal, one to close this
+communication, one to read key codes from the braille keyboard, and
+one to write text on the braille display. As we will see in a
+moment, other functions are required.
+
+Moreover, a driver can provide additional functionalities, by
+defining some macros asserting that it has these functionalities,
+and by defining associated functions.
+
+<sect1>Basic driver structure
+
+<p><em>Every</em> <em/brltty/ driver <em>must</em> consist in at least
+a file called braille.c, located in an appropriate subdirectory of
+the BrailleDrivers subdirectory. This braille.c file must have the
+following layout
+
+<verb>
+    #include "prologue.h"
+    /* Include standard C headers */
+    #include "Programs/brl.h"
+    #include "Programs/misc.h"
+    #include "Programs/scr.h"
+    #include "Programs/message.h"
+    /* Include other files */
+
+    static void brl_identify() { }
+
+    static int brl_open(BrailleDisplay *brl, char **parameters, const char *tty) { ... }
+
+    static void brl_close(BrailleDisplay *brl) { ... }
+
+    static void brl_writeWindow(BrailleDisplay *brl) { ... }
+
+    static void brl_writeStatus(BrailleDisplay *brl) { ... }
+
+    static int brl_readCommand(BrailleDisplay *brl, DriverCommandContext context) { ... }
+</verb>
+
+Before giving a detailed description of what each function is
+supposed to do, we define the <tt>BrailleDisplay</tt> structure,
+since each function has an argument of type <tt>BrailleDisplay
+*</tt>. The <tt>BrailleDisplay</tt> structure is defined like this:
+
+<verb>
+    typedef struct {
+
+      int x, y; /* The dimensions of the display */
+
+      int helpPage; /* The page number within the help file */
+
+      unsigned char *buffer; /* The contents of the display */
+
+      unsigned isCoreBuffer:1; /* The core allocated the buffer */
+
+      unsigned resizeRequired:1; /* The display size has changed */
+
+      unsigned int writeDelay;
+
+      void (*bufferResized)(int rows, int columns);
+
+    } BrailleDisplay;
+</verb>
+
+We now describe each function's semantics and calling
+convention.
+
+The <tt/brl_identify()/ function takes no argument and returns
+nothing. It is called as soon as the driver is loaded, and its
+purpose is to print some information about the driver in the system
+log. To achieve this, the only thing this function has to do is to
+call LOG_PRINT with appropriate arguments (log level and string to
+put in the syslog).
+
+The <tt/brl_open()/ function takes 3 arguments and returns an int. Its
+purpose is to initialize the communication with the braille
+terminal. Generally, this function has to open the file referred to by
+the <tt/tty/ argument, and to configure the associated communication
+port. The <tt/parameters/ argument contains parameters passed to the
+driver with the -B command-line option. It's up to the driver's
+author to decide wether or not he/she wants to use this argument,
+and what for. The function can perform some additional tasks such
+as trying to identify precisely which braille terminal model is
+connected to the computer, by sending it a request and analyzing its
+answer. The value that is finally returned depends on the success of
+the initialization process. If it fails, th function has to return
+-1. The function returns 0 on success.
+
+The <tt/brl_close()/ function takes just one argument, and returns
+nothing. The name of this function should be self-explanatory; it's
+goal is to close (finish) the communication between the computer and
+the braille terminal. In general, the only thing this function has
+to do is to close the file descriptor associated to the braille
+terminal's communication port.
+
+The <tt/brl_writeWindow()/ function takes just one argument of type
+BrailleDisplay, and returns nothing. This function displays the
+specified text on the braille window. This routine is the right
+place to check if the text that has to be displayed is not already
+on the braille display, to send it only if necessary. More
+generally, if the braille terminal supports partial refresh of the
+display, the calculus of what exactly has to be sent to the braille
+display to have a proper display, according to what was previously
+displayed should be done in this function.
+
+The <tt/brl_writeStatus()/ function is very similar to <tt/brl_writeWindow()/.
+The only difference is that whereas <tt/brl_writeWindow()/ writes on the
+main braille display, <tt/brl_writeStatus()/ writes on an auxiliary braille
+display, which occasionaly appears on some braille terminals. The
+remarks that have been done concerning optimizations for refreshing
+the display still apply here.
+
+The <tt/brl_readCommand()/ function takes two arguments, and returns an
+integer. Its purpose is to read commands from the braille keyboard
+and to pass them to <em/brltty/'s core, which in turn will process them.
+The first argument, of type <tt/BrailleDisplay/, is for future use, and
+can safely be ignored for the moment. The second argument indicates
+in which context (state) <em/brltty/ is. For instance, it specifies if
+<em/brltty/ is in a menu, displays a help screen, etc. This information
+can indeed be of some interest when translating a key into a
+command, especially if the keys can have different meanings,
+depending on the context. So, this function has to read keypresses
+from the braille keyboard, and to convert them into commands,
+according to the given context, these commands then being returned
+to <em/brltty/. For a complete list of available command codes, please
+have a look at <tt/brl.h/ in the Programs subdirectory. Two codes have special
+meanings:
+
+<descrip>
+
+<tag/eof/ specifies that no command is available now, and that
+no key is waiting to be converted into command in a near future.
+
+<tag/CMD_NOOP/ specifies that no command is available, but
+that one will be, soon. As a consequence, brl_readCommand will be
+called again immediately. Returning CMD_NOOP is appropriate for
+instance when a key is composed of two consecutive data packets.
+When the first of them is received, one can expect that the second
+will arrive quickly, so that trying to read it as soon as possible
+is a good idea.
+
+</descrip>
+
+<sect1>Enhancements for <em/BrlAPI/
+
+<p>To improve the level of service provided to client
+applications communicating with braille drivers through <em/BrlAPI/, the
+drivers should declare some additional functions that will then be
+called by the API when needed.
+
+For each additional feature that has to be implemented in a
+driver, a specific macro must be defined, in addition to the
+functions implementing that feature. For the moment, two features
+are supported by <em/BrlAPI/:
+
+<itemize>
+<item>reading braille terminal specific key codes,
+
+<item>exchanging raw data packets between the braille
+terminal and a client application running on the PC.
+</itemize>
+
+For each feature presented below, only a short description of each
+concerned macro and function will be given. For a more complete description
+of concepts used here, please refer to chapters <ref id="sec-intro" name="Introduction"> and <ref id="sec-general" name="General description">.
+
+<sect2>Exchanging raw data packets
+
+<p>Under some circumstances, an application running on the PC
+can be interested in a raw level communication with the braille
+terminal. For instance, to implement a file transfer protocol,
+commands to display braille or to read keys are not enough. In
+such a case, one must have a way to send raw data to the
+terminal, and to receive them from it.
+
+A driver that wants to provide such a mechanism has to define
+three functions: one to send packets, another one to receive them,
+and the last one to reset the communication when problems occur.
+
+The macro that declares that a driver is able to transmit packets
+is:
+
+<verb>
+#define BRL_HAVE_PACKET_IO
+</verb>
+
+The prototypes of the functions the driver should define are:
+
+<verb>
+static int brl_writePacket(BrailleDisplay *brl, const unsigned char *packet, int size);
+static int brl_readPacket(BrailleDisplay *brl, unsigned char *p, int size);
+static void brl_rescue(BrailleDisplay *brl)
+</verb>
+
+<tt>brl_writePacket()</tt> sends a packet of <tt/size/ bytes, stored
+at <tt/packet/, to the braille terminal. If the communication protocol
+allows to determined if a packet has been send properly (e.g. the
+terminal sends back an acknowledgement for each packet he
+receives), then this function should wait the acknowledgement,
+and, if it is not received, retransmission of the packet should take
+place.
+
+<tt>brl_readPacket()</tt> reads a packet of at most <tt/size/ bytes, and
+stores it at the specified address. The read must not block. I.e.,
+if no packet is available, the function should return immediately,
+returning 0.
+
+<tt>brl_rescue()</tt> is called by <em/BrlAPI/ when a client
+application terminates without properly leaving the raw mode. This
+function should restore the terminal's state, so that it is
+able to display text in braille again.
+
+<sect3>Remarks.
+
+<p>
+<itemize>
+<item> If the driver provides such functions, every other
+functions should use them, instead of trying to communicate
+directly with the braille terminal. For instance, <tt/readCommand()/
+should call <tt/readPacket()/, and then extract a key from the packet,
+rather than reading directly from the communication port's file
+descriptor. The same applies for <tt/brl_writeWindow()/, which should
+use <tt/brl_writePacket()/, rather than writing on the communication
+port's file descriptor.
+
+<item> For the moment, the argument of type BrailleDisplay
+can safely be ignored by the functions described here.
+
+</itemize>
+
+<!---->
+<sect>Protocol reference<label id="sec-protocol">
+<!---->
+<!-- a boring documentation, explaining the underlying protocol of the api
+in detail -->
+<p>
+Under some circumstances, it may be preferable to communicate directly with
+<em/BrlAPI/'s server rather than using <em/BrlAPI/'s
+library. Here are the needed details to be able
+to do this. This chapter is also of interest if a precise understanding of
+how the communication stuff works is desired, to be sure to understand how
+to write multithreaded clients, for instance.
+
+<p>
+In all the following, <em/integer/ will mean an unsigned 32 bits integer in
+network byte order (ie most significant bytes first).
+
+<sect1>Reliable packet transmission channel
+
+<p>
+The protocol between <em/BrlAPI/'s server and clients is based on exchanges
+of packets. So as to avoid locks due to packet loss, these exchanges are
+supposed reliable, and ordering must be preserved, thus <em/BrlAPI/ needs
+a reliable packet transmission channel.
+
+<p>
+To achieve this, <em/BrlAPI/ uses a TCP-based connection, on which packets
+are transmitted this way:
+
+<itemize>
+<item>the size in bytes of the packet is transmitted first as an integer,
+<item>then the type of the packet, as an integer,
+<item>and finally the packet data.
+</itemize>
+
+<p>
+The size does not include the { size, type } header, so that packets which
+don't need any data have a size of 0 byte. The type of the packet can be
+either of <tt/BRLAPI_PACKET_*/ constants defined in <tt/api_protocol.h/. Each type of
+packet will be further discussed below.
+
+<p>
+<em/BrlAPI/'s library ships two functions to achieve packets sending and receiving
+using this protocol: <tt/brlapi_writePacket/ and <tt/brlapi_readPacket/. It
+is a good idea to use these functions rather than rewriting them, since this protocol
+might change one day in favor of a real reliable packet transmission protocol
+such as the experimental RDP.
+
+<sect1>Responses from the server
+
+<p>
+As described below, many packets are `acknowledged'. It means that upon
+reception, the server sends either:
+
+<itemize>
+<item>a <tt/BRLAPI_PACKET_ACK/ packet, with no data, which means the operation
+corresponding to the received packet was successful,
+<item>or a <tt/BRLAPI_PACKET_ERROR/ packet, the data being an integer
+which should be one of <tt/BRLAPI_ERROR_*/ constants. This
+means the operation corresponding to the received packet failed.
+</itemize>
+
+<p>
+Some other packets need some information as a response.
+Upon reception, the server will send either:
+
+<itemize>
+<item>a packet of the same type, its data being the response,
+<item>or a <tt/BRLAPI_PACKET_ERROR/ packet.
+</itemize>
+
+<p>
+If at some point an ill-formed or non-sense packet is received by the server,
+and <tt/BRLAPI_PACKET_EXCEPTION/ is returned, holding the guilty packet for
+further analysis.
+
+<sect1>Operating modes
+<p>
+The connection between the client and the server can be in either of the 
+four following modes:
+
+<itemize>
+<item>authorization mode: this is the initial mode, when the client hasn't
+got the authorization to use the server yet. The server first sends a
+<tt/BRLAPI_PACKET_VERSION/ packet that announces the server version. The client
+must send back a <tt/BRLAPI_PACKET_VERSION/ for announcing its own version too.
+The server then sends a <tt/BRLAPI_PACKET_AUTH/ packet that announces
+which authorization methods are allowed. The client can then send
+<tt/BRLAPI_PACKET_AUTH/ packets, which makes the connection enter normal mode.
+If no authorization is needed, the server can announce the <tt/NONE/ method, the
+client then doesn't need to send a <tt/BRLAPI_PACKET_AUTH/ packet.
+
+
+
+<item>normal mode: the client is authorized to use the server, but didn't ask for a tty
+or raw mode. The client can send either of these types of packet:
+  <itemize>
+  <item><tt/BRLAPI_PACKET_GETDRIVERNAME/
+  or <tt/BRLAPI_PACKET_GETDISPLAYSIZE/ to get pieces of information from the server,
+  <item><tt/BRLAPI_PACKET_ENTERTTYMODE/ to enter tty handling mode,
+  <item><tt/BRLAPI_PACKET_ENTERRAWMODE/ to enter raw mode,
+  </itemize>
+
+
+<item>tty handling mode: the client holds the control of a tty: <em/brltty/ has
+no power on it any more, masked keys excepted. It's up to the client to manage
+display and keypresses. For this, it can send either of these types of packet:
+  <itemize>
+  <item><tt/BRLAPI_PACKET_LEAVETTYMODE/ to leave tty handling mode and go back to
+  normal mode,
+  <item><tt/BRLAPI_PACKET_IGNOREKEYRANGE/ and <tt/BRLAPI_PACKET_ACCEPTKEYRANGE/ to mask and unmask keys,
+  <item><tt/BRLAPI_PACKET_WRITE/ to display text on this tty,
+  <item><tt/BRLAPI_PACKET_ENTERRAWMODE/ to enter raw mode,
+  <item><tt/BRLAPI_PACKET_GETDRIVERNAME/
+  or <tt/BRLAPI_PACKET_GETDISPLAYSIZE/ to get pieces of information from the server,
+  </itemize>
+And the server might send <tt/BRLAPI_PACKET_KEY/ packets to signal key presses.
+
+
+<item>raw mode: the client wants to exchange packets directly with the braille
+terminal. Only these types of packet will be accepted.
+  <itemize>
+  <item><tt/BRLAPI_PACKET_LEAVERAWMODE/ to get back to previous mode, either normal or
+  tty handling mode.
+  <item><tt/BRLAPI_PACKET_PACKET/ to send a packet to the braille terminal.
+  </itemize>
+And the server might send <tt/BRLAPI_PACKET_PACKET/ packets to give received packets
+from the terminal to the client.
+
+<item>suspend mode: the client wants to completely drive the braille terminal.
+The device driver is hence kept closed. No type of packet is allowed except
+<tt/BRLAPI_PACKET_RESUME/
+</itemize>
+
+Termination of the connection is initiated by the client in normal mode by
+simply closing its side of the socket. The server will then close the
+connection.
+
+
+<sect1>Details for each type of packet
+
+<p>
+Here is described the semantics of each type of packet. Most of them are
+directly linked to some of <em/BrlAPI/'s library's functions. Reading their
+online manual page as well will hence be of good help for understanding.
+
+<sect2><tt/BRLAPI_PACKET_VERSION/
+This must be the first packet ever transmitted from the server to the client and
+from the client to the server. The server sends one first for letting the client
+know its protocol version. Data is an integer indicating the protocol version.
+
+Then client must then respond the same way for giving its
+version.  If the protocol version can't be handled by the server, a
+<tt/BRLAPI_ERROR_PROTOCOL_VERSION/ error packet is returned and the connection
+is closed.
+
+<sect2><tt/BRLAPI_PACKET_AUTH/
+<p>
+This must be the second packet ever transmitted from the server to the client
+and from the client to the server. The server sends one first for letting the
+client know which authorization methods are available.  Data is the allowed
+authorization types, as integers.
+
+If the <tt/NONE/ method is not announced by the server, the client can then try
+to get authorized by sending packets whose data is the type of authorization
+that is tried (as an integer), and eventually some data (if the authorization
+type needs it).
+
+If the authorization is successful, the server acknowledges the packet, and
+other types of packets might be used, other <tt/BRLAPI_PACKET_AUTH/ shouldn't be
+sent by the client.
+
+If the authorization is not successful, the server sends a
+<tt/BRLAPI_ERROR_AUTHENTICATION/ error, and the client can try another
+authorization method.
+
+Authorization methods are as follow:
+
+<itemize>
+  <item><tt/NONE/: the client doesn't need to send an authorization packet.
+  <item><tt/KEY/: data holds a secret key, the authorization is successful only
+if the key matches the server secret key.
+  <item><tt/CREDENTIALS/: Operating-System-specific credentials are explicitely
+sent over the socket, the authorization is successful if the server considers
+the credentials sufficient.
+</itemize>
+
+Note: when the Operating system permits it, the server may use implicit
+credential check, and then advertise the <tt/none/ method.
+
+<sect2><tt/BRLAPI_PACKET_GETDRIVERNAME/ (see <em/brlapi_getDriverName()/)
+<p>
+This should be sent by the client when it needs the full name of
+the current <tt/brltty/ driver. The returned string is \0 terminated.
+
+<sect2><tt/BRLAPI_PACKET_GETMODELID/ (see <em/brlapi_getModelIdentifier()/)
+<p>
+This should be sent by the client when it needs to identify
+which model of braille display is currently used by <tt/brltty/.
+The returned string is \0 terminated.
+
+<sect2><tt/BRLAPI_PACKET_GETDISPLAYSIZE/ (see <em/brlapi_getDisplaySize()/)
+<p>
+This should be sent by the client when it needs to know the braille display
+size. The returned data are two integers: width and then height.
+
+<sect2><tt/BRLAPI_PACKET_ENTERTTYMODE/ (see <em/brlapi_enterTtyMode()/ and
+<em/brlapi_enterTtyModeWithPath()/)
+<p>
+This should be sent by the client to get control of a tty. Sent data are
+first a series of integers: the first one gives the number of following
+integers, which are the numbers of ttys that leads to the tty that
+the application wants to take control of (it can be empty if the tty is 
+one of the machine's VT). The last integer of this series tells the number of
+the tty to get control of. Finaly, how key presses should be reported is sent:
+either a driver name or "", preceded by the number of caracters in the driver
+name (0 in the case of ""), as an unsigned byte. This packet is then
+acknowledged by the server.
+
+<sect2><tt/BRLAPI_PACKET_KEY/ (see <em/brlapi_readKey()/)
+<p>
+As soon as the client gets a tty, it must be prepared to handle
+<tt/BRLAPI_PACKET_KEY/ incoming packets
+at any time (as soon as the key
+was pressed on the braille terminal, hopefuly).
+The data holds a key code as 2 integers, or
+the key flags then the command code
+as 2 integers, depending on what has been request in the
+<tt/BRLAPI_PACKET_ENTERTTYMODE/ packet.
+
+<sect2><tt/BRLAPI_PACKET_SETFOCUS/ (see <em/brlapi_setFocus()/)
+
+<p>
+For the server to know which tty is active, one particular client is responsible
+for sending <tt/BRLAPI_PACKET_SETFOCUS/ packets. They hold a single integer telling
+the new current tty. For instance, when running an X server on VT 7, the
+<tt/xbrlapi/ client would have sent a <tt/BRLAPI_PACKET_ENTERTTYMODE(7)/ and will send
+window IDs whenever X focus changes, allowing display and keypresses switching
+between xterms.
+
+<sect2><tt/BRLAPI_PACKET_LEAVETTYMODE/ (see <em/brlapi_leaveTtyMode()/)
+<p>
+This should be sent to free the tty and masked keys lists.
+This is acknowledged by the server.
+
+<sect2><tt/BRLAPI_PACKET_IGNOREKEYRANGE/ and <tt/BRLAPI_PACKET_ACCEPTKEYRANGE/
+(see <em/brlapi_ignoreKeyRange()/ and <em/brlapi_acceptKeyRange()/)
+<p>
+If the client doesn't want every key press to be signaled to it, but some of
+them to be given to <tt/brltty/ for normal processing, it can send
+<tt/BRLAPI_PACKET_IGNOREKEYRANGE/ packets to
+tell ranges of key codes which shouldn't be
+sent to it, but given to <tt/brltty/, and <tt/BRLAPI_PACKET_ACCEPTKEYRANGE/
+packets to tell ranges
+of key codes which should be sent to it, and not given to
+<tt/brltty/. The server keeps a dynamic list of ranges, so that arbitrary
+sequences of such packets can be sent.
+A range is composed of 2 keycodes: the "first" and the "last" boundaries.
+Each keycode is composed of 2 integers: the key flags then the command code.
+The range expressed by these two keycodes is the set of keycodes whose command
+codes are between the command code of the "first" keycode and the "last" keycode
+(inclusive), and whose flags contain at least the flags of the "first" keycode
+and at most the flags of the "last" keycode. Setting the "first" and "last"
+keycode to the same value express only one keycode, for instance. Setting the
+first and last keycode to the same command code but setting no flags in the
+"first" keycode and setting one flag in the "last" keycode expresses only two
+keycode, with the same command code and no flags set except possibly the flag
+that is set in the "last" keycode. Setting one flag <em/i/ in the "first"
+keycode and setting the same flag plus another flag <em/j/ in the "last" keycode
+expresses that the keycodes in the range have flag <em/i/ set and possibly flag
+<em/j/ set, but no other flag. Several such ranges can be provided one after the
+other.
+
+<sect2><tt/BRLAPI_PACKET_WRITE/ (see <em/brlapi_write()/)
+<p>
+To display text on the braille terminal and set the position of the cursor,
+the client can send a <tt/BRLAPI_PACKET_WRITE/ packet. The packet begins
+with an integer holding flags (see <tt/BRLAPI_WF_*/). These flags indicate
+which data will then be available, in the following order (corresponding to
+flag weight):
+
+<itemize>
+<item> A display number can be given as a integer, in case the braille
+display has several. If not given, usual display is used.
+<item> A region must be given as two integers indicating the beginning and the
+number of characters of the part of the braille display which is to be updated,
+the first cell of the display being numbered 1. For braille displays that have
+several lines, the first cell of the second line of the display is numbered the
+length of lines plus one, etc., in other words the display is handled as the
+concatenation of the lines of the display.
+If the number is negative, its absolute value is taken into account, and the
+update is padded or truncated to fill the rest of the display.
+<item> The text to display can then be given, preceded by its size in bytes
+expressed as an integer. It will erase the corresponding region in the AND and
+OR fields. If the region size is positive, the text's length in characters must exactly match the region
+size. For multibyte text, this is the number of wide characters. Notably,
+combining and double-width characters count for 1.
+<item> Then an AND field can be given, one byte per character: the 8-dot
+representation of the above text will be AND-ed with this field, hence allowing
+to erase some unwanted parts of characters. Dots are coded as described in
+ISO/TR 11548-1: dot 1 is set iff bit 0 is set, dot 2 is set iff bit 1 is set,
+...  dot <em/i+1/ is set if bit <em/i/ is set. This also corresponds to the
+low-order byte of the coding of unicode's braille row <tt/U+2800/.
+<item> As well, an OR field may be given, one byte per character: the 8-dot
+result of the AND operation above (or the 8-dot representation of the text if
+no AND operation was performed) is OR-ed with this field, hence allowing
+to set some dots, to underline characters for instance.
+<item> A cursor position can be specified. 1 representing
+the first character of the display, 0 turning the cursor off. If not given,
+the cursor (if any) is left unmodified.
+<item> Last but not least, the charset of the text can be specified: the length
+of the name first in one byte, then the name itself in ASCII characters. If the
+charset is not specified, an 8-bit charset is assumed, and it is assumed to be
+the same as the server's. Multibyte charsets may be used, AND and OR fields'
+bytes will correspond to each text's wide <em/character/, be it a combining or a
+double-width character.
+</itemize>
+
+A <tt/BRLAPI_PACKET_WRITE/ packet without any flag (and hence no data) means a
+"void" WRITE: the server clears the output buffer for this connection.
+
+<sect2><tt/BRLAPI_PACKET_ENTERRAWMODE/ (see <em/brlapi_enterRawMode()/)
+<p>
+To enter raw mode, the client must send a <tt/BRLAPI_PACKET_ENTERRAWMODE/ packet,
+which is acknowledged. Once in raw mode, no other packet than
+<tt/BRLAPI_PACKET_LEAVERAWMODE/ or <tt/BRLAPI_PACKET_PACKET/ will be accepted.
+The data must hold the special value <tt/BRLAPI_DEVICE_MAGIC/: <tt/0xdeadbeef/, then
+the name of the driver (one byte for the length, then the name) to avoid
+erroneous raw mode activating.
+
+<sect2><tt/BRLAPI_PACKET_LEAVERAWMODE/ (see <em/brlapi_leaveRawMode()/)
+<p>
+To leave raw mode, the client must send a <tt/BRLAPI_PACKET_LEAVERAWMODE/ packet, which
+is acknowledged.
+
+<sect2><tt/BRLAPI_PACKET_PACKET/ (see <em/brlapi_sendRaw()/ and
+<em/brlapi_recvRaw()/)
+<p>
+While in raw mode, only <tt/BRLAPI_PACKET_PACKET/ packets can be exchanged between
+the client and the server: to send a packet to the braille terminal, the
+client merely sends a <tt/BRLAPI_PACKET_PACKET/ packet, its data being the packet to
+send to the terminal. Whenever its receives a packet from the terminal, the
+server does exactly the same, so that packet exchanges between the terminal and
+the server are exactly reproduced between the server and the client.
+
+<sect2><tt/BRLAPI_PACKET_SUSPENDDRIVER/ (see <em/brlapi_suspendDriver()/)
+<p>
+To enter suspend mode, the client must send a <tt/BRLAPI_PACKET_SUSPEND/ packet,
+which is acknowledge. Once in suspend mode, no other packet than
+<tt/BRLAPI_PACKET_RESUME/ will be accepted.
+The data must hold the special value <tt/BRLAPI_DEVICE_MAGIC/: <tt/0xdeadbeef/,
+then the name of the driver (one byte for the length, then the name) to avoid
+erroneous raw mode activating.
+
+<sect2><tt/BRLAPI_PACKET_PARAM_REQUEST/
+
+<p>
+
+This packet is sent by the client to request values of parameters. The packet
+begins with an integer which holds flags (see <tt/BRLAPI_PARAMF_*/) which
+describe which, how, and when the value should be returned by the server:
+<itemize>
+<item> When the <tt/BRLAPI_PARAMF_GLOBAL/ flag is set, the server will
+return/subscribe the global value instead of the local value.
+<item> When the <tt/BRLAPI_PARAMF_GET/ flag is set, the server acknowledges the
+request by returning the latest value with a <tt/BRLAPI_PACKET_PARAM_VALUE/
+packet. Otherwise the server acknowledges the request with a
+<tt/BRLAPI_PACKET_ACK/ packet, without providing the value.
+<item> When the <tt/BRLAPI_PARAMF_SUBSCRIBE/ flag is set, the server will keep
+sending asynchronously the value of the parameter whenever it changes, with
+<tt/BRLAPI_PACKET_PARAM_UPDATE/ packets, until
+another request packet has the <tt/BRLAPI_PARAMF_UNSUBSCRIBE/ flag set for this
+parameter.
+<item> When the <tt/BRLAPI_PARAMF_SELF/ flag is set along
+<tt/BRLAPI_PARAMF_SUBSCRIBE/, the server will send the value of the parameter
+when it is changed even by the client itself.
+<item> When the <tt/BRLAPI_PARAMF_UNSUBSCRIBE/ flag is set, the server
+will stop sending asynchronously the value of the parameter with
+<tt/BRLAPI_PACKET_PARAM_UPDATE/ packets.
+</itemize>
+
+It does not make sense to set both the <tt/BRLAPI_PARAMF_SUBSCRIBE/ and
+<tt/BRLAPI_PARAMF_UNSUBSCRIBE/ flags.
+
+Then an integer representing the parameter to be requested.
+Then two integers that form (in big-endian order) a 64bit value used to
+subspecify the precise parameter to be requested (e.g. a keycode number).
+
+If several <tt/BRLAPI_PARAMF_SUBSCRIBE/ packets are sent by the client, as
+many <tt/BRLAPI_PARAMF_UNSUBSCRIBE/ packets have to be sent by the client before
+the server stops sending <tt/BRLAPI_PACKET_PARAM_UPDATE/ packets.
+
+<sect2><tt/BRLAPI_PACKET_PARAM_VALUE/
+
+<p>
+This packet is sent by the client or the server to provide the value of a
+parameter. The packet begins with an integer which holds flags (see
+<tt/BRLAPI_PVF_*/) which describe which value is being transmitted:
+
+<itemize>
+<item> When the <tt/BRLAPI_PVF_GLOBAL/ flag is set, the value is the global value
+instead of the local value.
+</itemize>
+
+Then an integer representing the parameter being transmitted. Then two integers
+that form (in big-endian order) a 64bit value used to subspecify the precise
+parameter being transmitted (e.g. a keycode number).  Eventually, the packet
+contains the value.
+
+When the packet is sent by the client, it defines the new value of the
+parameter, and if it is a global value, the server broadcasts the new value to
+all clients which have subscribed to updates. The packet is then acknowledged by
+the server on success. If the value can not be changed, the server returns an
+error (e.g. <tt/BRLAPI_ERROR_READONLY_PARAMETER/).
+
+<sect2><tt/BRLAPI_PACKET_PARAM_VALUE/
+
+This packet is sent asynchronously by the server to provide an update of a
+value of a parameter. This is sent only if the client has previously sent a
+<tt/BRLAPI_PACKET_PARAM_REQUEST/ packet with the <tt/BRLAPI_PARAMF_SUBSCRIBE/
+for the corresponding parameter.
+
+It is structured exactly like a <tt/BRLAPI_PACKET_PARAM_VALUE/ packet.
+
+<sect2><tt/BRLAPI_PACKET_SYNCHRONIZE/
+
+This packet is sent by the client and just acknowledged by the server. This
+allows the client to perform a round-try with the server, thus collecting any
+pending exception notification.
+
+</article>
diff --git a/Documents/Manual-BrlAPI/English/Makefile.in b/Documents/Manual-BrlAPI/English/Makefile.in
new file mode 100644
index 0000000..c72fdd0
--- /dev/null
+++ b/Documents/Manual-BrlAPI/English/Makefile.in
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DOCUMENT_NAME = BrlAPI
+DOCUMENT_LANGUAGE = english
+include $(SRC_TOP)document.mk
diff --git a/Documents/README.APIFuzzing b/Documents/README.APIFuzzing
new file mode 100644
index 0000000..a5caab4
--- /dev/null
+++ b/Documents/README.APIFuzzing
@@ -0,0 +1,60 @@
+~~~~~~~
+Fuzzing
+~~~~~~~
+
+.. include:: prologue.rst
+
+Principle
+=========
+
+BRLTTY has some support for testing it through fuzzing, i.e. feeding it with
+more or less random content, to check that it does not fall into pieces.
+
+Building
+--------
+
+Building with fuzzing support is
+
+   CC=clang ./configure --enable-api-fuzzing
+
+(Fuzzing is only supported with the clang compiler)
+
+This will also enable the address and undefined-behavior sanitizers.
+
+Running
+-------
+
+To start a fuzzing session, one has to set the fuzz BrlAPI parameter, for
+instance to start 1000 fuzzing loops:
+
+   ./run-brltty -b xw -x no -A fuzz=1000 -n -e
+
+It can be useful to try different fuzzing seeds with:
+
+   ./run-brltty -b xw -x no -A fuzz=1000,fuzzseed=1234 -n -e
+
+The initial steps of BrlAPI connections are not fuzzed by default, so the fuzzer
+does not have to get it right before fuzzing the rest of the protocol. To fuzz
+these initial steps, one can use:
+
+   ./run-brltty -b xw -x no -A fuzz=1000,fuzzhead=on -n -e
+
+Conversely, one can concentrate the fuzzing on the writeText operation only (in
+latin1) with:
+
+   ./run-brltty -b xw -x no -A fuzz=1000,fuzzwrite=on -n -e
+
+And in utf8:
+
+   ./run-brltty -b xw -x no -A fuzz=1000,fuzzwriteutf8=on -n -e
+
+Reproducing
+-----------
+
+Once a bug is detected by the fuzzer, it emits a crash file. One can reproduce
+the behavior with:
+
+   ./run-brltty -b xw -x no -A crash=crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 -n -e
+
+and use gdb etc. at will to understand the bug, and eventually check when it is
+fixed.
diff --git a/Documents/README.Android b/Documents/README.Android
new file mode 100644
index 0000000..5e7c9f6
--- /dev/null
+++ b/Documents/README.Android
@@ -0,0 +1,960 @@
+~~~~~~~~~~~~~~~~~
+BRLTTY on Android
+~~~~~~~~~~~~~~~~~
+
+.. |SDK build tools version| replace:: 29.0.3
+.. |NDK version| replace:: r21e (21.4.7075529)
+.. |JDK version| replace:: 1.7
+
+.. _BRLTTY on Google Play: https://play.google.com/store/apps/details?id=org.a11y.brltty.android
+
+.. include:: prologue.rst
+
+Using BRLTTY
+============
+
+Activation and Configuration
+----------------------------
+
+At this point, BRLTTY has been installed. Next, you'll need to go into
+``Settings`` -> ``Accessibility`` -> ``BRLTTY`` in order to start the ``BRLTTY``
+accessibility service, adjust its settings, and select your braille device.
+
+If you'll be connecting to your braille device via Bluetooth,
+see `Connecting Via Bluetooth`_.
+
+If you'll be connecting to your braille device via USB,
+see `Connecting Via USB`_.
+
+If your braille device has a braille keyboard,
+see `Using a Braille Keyboard`_.
+
+Starting and Stopping BRLTTY
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY isn't a regular Android application - it's an accessibility service. As
+such, it can't be started and stopped in the usual way, i.e. from the launcher.
+In fact, it can't even be found within the applications list.
+
+BRLTTY must be started and stopped from the ``Accessibility Settings`` screen.
+To get there, launch the ``Settings`` application, and then tap on
+``Accessibility`` (near the bottom). This screen contains a "Services" section
+that lists all of the accessibility services that are currently installed on
+the device. For each installed accessibility service, there's an associated
+indicator that says ``On`` if that service is currently running, and ``Off`` if
+it isn't.
+
+Find ``BRLTTY`` and tap on it. This brings up a window with two items in it.
+One is a "switch" for turning BRLTTY on and off. The other is a button that
+takes you to BRLTTY's ``Settings`` screen. You can go through BRLTTY's
+settings, making changes as desired, as well as define your braille device(s),
+either before starting BRLTTY or while it's running.
+
+Connecting Your Braille Device
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Connecting Via Bluetooth
+````````````````````````
+
+In order to use a Bluetooth braille device, you'll need to first "pair" it with
+your Android device. Go into ``Settings`` -> ``Bluetooth``. If your braille
+device is already listed within the ``Paired Devices`` section of that screen
+then it has already been paired. If you still need to pair it then tap
+``Search for Devices``. This will add an ``Available Devices`` section to the
+screen. If your braille device isn't listed then you'll probably need to
+perform a model-specific action on it in order to make it visible
+(also known as discoverable) - see its manual for details. After doing that,
+tap ``Search for Devices`` again. Tap on your braille device to begin the
+Bluetooth Pairing Request, enter its PIN (see its manual for details), and tap
+``OK``.
+
+Connecting Via USB
+``````````````````
+
+In order to use a USB braille device, you'll need a special cable known as a
+"Micro USB Host Adapter". The reason for this is that the USB port on an
+Android device usually acts as a "device" (rather than as a "host") port. This
+is so that, for example, you can control your Android device from your
+computer. The Micro USB Host Adapter has a special plug, known as an OTG
+(on-the-go) connector, that, when inserted into the Android device's USB port,
+instructs Android to act as the USB host.
+
+The Micro USB Host Adapter also allows you to connect any other USB 
+device (keyboard, mouse, printer, hub, etc) to your Android device. Be aware,
+though, that if any such device, including your braille device, draws power via
+its USB port then your Android device's battery will become the source of that
+power. If portability isn't an issue, you may wish to consider using your Micro
+USB Host Adapter to connect your Android device to a powered hub so that your
+USB devices will draw power from the hub rather than from your Android device's
+battery. You may also wish to consider disabling USB charging on any devices
+that offer this capability.
+
+Defining Your Braille Device
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You don't actually need to define your braille device, but BRLTTY will connect
+to it much faster if you do. If you don't, BRLTTY will search through all of
+the devices that have been connected via either Bluetooth or USB
+(see `Connecting Your Braille Device`_) for one that it recognizes. If there's
+more than one, it'll select the first one that it finds.
+
+To define your braille device, go to BRLTTY's ``Settings`` screen, tap on
+``Manage Devices``, and then on ``Add Device``. From there, find your braille
+device, and then tap ``Add``. To find your braille device:
+
+1) Select its communication method (Bluetooth, USB).
+
+2) Select your device from the list that's presented.
+
+3) Select the correct braille driver.
+   This step is optional, i.e. you can usually leave it set to ``autodetect``.
+   Going through the effort of selecting the correct driver, however,
+   ensures a fast and reliable connection.
+
+After you've added your braille device to BRLTTY, tap on ``Selected Device``
+and select it from the list of devices that BRLTTY knows about.
+
+Using a Braille Keyboard
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Braille device keyboard input is supported, but, like all Android input
+methods, it must be explicitly enabled, and then explicitly selected. Android
+doesn't permit BRLTTY to do either of these automatically on your behalf.
+Although it's inconvenient, Android imposes this manual process so that you're
+very consciously aware of which input methods can process, and which input
+method is currently processing, whatever you're typing. Such applications,
+after all, handle extremely sensitive personal data (such as passwords, credit
+card numbers, etc), so it's crucial that you make your own decisions regarding
+which of them you're willing to trust.
+
+If you type on your braille device's keyboard when BRLTTY's input method is
+either disabled or enabled but not selected, then BRLTTY will alert you to this
+fact via a message on your braille display. You may wish to enable BRLTTY's
+keyboard support ahead of time, but you probably don't want to select it ahead
+of time. The reason for this is that Android only allows exactly one input
+method to be in use at a time. When you explicitly select BRLTTY's input
+method, therefore, you're also implicitly deselecting the on-screen keyboard.
+
+You can enable BRLTTY's keyboard support in one of the following ways:
+
+* Launch Android's ``Settings`` application and tap on ``Language and Input``.
+  The ``Keyboard and Input Methods`` section of this screen shows the
+  ``Default`` (currently selected) input method, and contains a check box for
+  each installed input method. An input method is enabled if its check box is
+  checked, so, to enable BRLTTY's keyboard support, check the box labelled
+  ``BRLTTY Input Service``. Once it's been enabled, you can select it at any
+  time by adjusting the ``Default`` setting.
+
+* If BRLTTY is running then switching between input methods is much easier.
+  Go to BRLTTY's `Actions screen`_ and tap ``Switch Input Method``. This
+  brings up Android's Input Method Picker, which presents a set of radio
+  buttons - one for each enabled input method. If there's no radio button for
+  BRLTTY's input method then it hasn't been enabled yet. To enable it, tap the
+  button labelled ``Set up input methods``. This screen contains a check box for
+  each installed input method. Check the box labelled ``BRLTTY Input Service``.
+  Then tap the ``Back`` button to return to the ``Language and Input`` screen,
+  find the ``Keyboard and Input Methods`` section, and set the ``Default``
+  input method to BRLTTY's input method.
+
+Actions Screen
+~~~~~~~~~~~~~~
+
+BRLTTY's Actions screen presents several common actions that you may wish to perform:
+
+* Switch Input Method
+* BRLTTY Settings
+* View User Guide
+* Browse Web Site
+* Browse Community Messages
+* Post Community Message
+* Manage Community Membership
+* Update Application
+* About Application
+
+You can get to this screen using any of the following methods:
+
+* From your braille device via global action #5.
+  See `Global Actions`_ for details.
+
+* From your braille device via Space + Dots12345678.
+
+* Via ``Settings`` -> ``Accessibility`` -> ``BRLTTY`` -> ``Settings``.
+
+* From the notifications shade.
+  Open it by dragging the status bar downward:
+
+  + With two fingers (if Explore by Touch is active).
+  + With one finger (if Explore by Touch isn't active).
+
+  Then find BRLTTY's service notification and tap it.
+
+* Via the Accessibility button on the system navigation bar.
+  This capability was introduced in Android 8.0 (Oreo).
+  The button may not be visible for a number of reasons, for example:
+
+  + The device's system navigation bar isn't rendered via software.
+  + An application has chosen to hide the system navigation bar.
+
+Customized Data Files
+~~~~~~~~~~~~~~~~~~~~~
+
+You can customize any of BRLTTY's data files,
+e.g. a text, contraction, or key table or subtable.
+To do this, add a file with the same name directly into a folder named ``brltty``
+at the top-level of your Android device's primary shared/external storage area.
+This area might be internal (on the device itself)
+or external (on a removal storage device, e.g. an SD card).
+Normally, it's the area that ``/sdcard`` is symbolically linked to.
+
+BRLTTY won't be aware of your customized data files
+if this area has been mounted by a computer.
+
+It's safe to include the original data file from your customized copy.
+If you're only adding lines, therefore, then your customized copy
+need only contain those additions and the include statement.
+
+Navigating the Screen
+---------------------
+
+Using Multiple Hosts
+~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY only remains connected to your braille device while your Android device
+is unlocked or while its screen is on. If your Android device is locked and its
+screen is off then BRLTTY automatically disconnects from your braille device.
+This is so that you can easily share your braille device amongst multiple
+hosts.
+
+Pressing your Android device's power button (or similar action) to wake it up,
+even though it may still be locked, is sufficient to cause BRLTTY to
+automatically reconnect to your braille device. This allows you to enter your
+password or PIN via your braille keyboard.
+
+You can continue using your braille device even though your Android device's
+screen may have turned off, as long as its lock timer hasn't yet expired.
+Pressing keys on your braille device resets your Android device's lock timer in
+the same way that pressing its keys, touching its screen, etc does. This means
+that your Android device will stay awake and unlocked even though you're only
+controlling it from your braille device, and that it'll also still
+automatically lock once you're no longer using it.
+
+Accessibility Focus
+~~~~~~~~~~~~~~~~~~~
+
+The "accessibility focus" feature of Android is used for cursor tracking and
+routing. It's a soft cursor, not visible on the screen, that can be 
+programmatically associated with any screen element. All screen readers that
+use it to define the current element for actions (like tapping) will implicitly
+cooperate reasonably seamlessly with one another.
+
+The cursor is usually placed on the first character of the screen element that
+currently has accessibility focus. The one exception to this is within an
+input area. If that area has input focus then the cursor is placed at
+the location within it where input will be inserted.
+
+When a home screen folder is opened, BRLTTY automatically sets accessibility
+focus to that folder's first entry. This eliminates the need to search for it.
+
+The Cursor Routing Keys
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The cursor routing keys of your braille device perform their usual function when
+within an input area if it has input focus - the key above a given
+character brings the cursor to that character. In any other context, however, a
+cursor routing key performs an action on the screen element under it. Starting
+with the leftmost routing key over a screen element, which we'll call key #1,
+these actions are as follows:
+
+1) Bring accessibility focus (cursor)
+2) tap (click)
+3) hold (long click)
+4) scroll backward (up or left)
+5) scroll forward (down or right)
+6) context click
+7) accessibility actions
+
+A range control (progress bar, volume slider, etc) can be adjusted up/down
+via the scroll forward/backward actions.
+
+Input Areas
+~~~~~~~~~~~
+
+When an input area has input focus, BRLTTY's attribute underlining
+feature is used to highlight the selected text region.
+
+Widget Representations
+~~~~~~~~~~~~~~~~~~~~~~
+
+Check Boxes
+```````````
+
+A check box is rendered as a three-cell symbol:
+
+1) dots 123478 (the left side of the box)
+2) dots 2356 (the check mark)
+3) dots 145678 (the right side of the box)
+
+The check mark is present if the box is checked and absent if it isn't.
+If the check box has a label then it appears to the right of the symbol.
+The braille representations are:
+
+* ⣏⠶⣹ checked
+* ⣏ ⣹ not checked
+
+Radio Buttons
+`````````````
+
+A radio button is rendered as a three-cell symbol:
+
+1) dots 2348 (the left side of the button)
+2) dots 2356 (the check mark)
+3) dots 1567 (the right side of the button)
+
+The check mark shows which of the radio buttons is currently selected.
+The label for each radio button appears to the right of its symbol.
+The braille representations are:
+
+* ⢎⠶⡱ selected
+* ⢎ ⡱ not selected
+
+Switches
+````````
+
+A (two-position) switch is rendered as a three-cell symbol:
+
+1) dots 4568 (the left side of the switch)
+2) dots 1478 (the top and bottom of the switch)
+3) dots 1237 (the right side of the switch)
+
+Dots 25 are added to the middle cell if the switch is on,
+and dots 36 are added to the middle cell if the switch is off.
+In other words, the switch is up when on and down when off.
+The label for the switch's current state appears to the right of the symbol.
+The braille representations are:
+
+* ⢸⣛⡇ on
+* ⢸⣭⡇ off
+
+Range Controls
+``````````````
+
+A range control is one which can be adjusted (rather than set or edited).
+They include widgets like progress bars, volume sliders, etc.
+They're rendered as a three-value summary::
+
+* An at sign followed by the current setting.
+* The minimum and maximum settings, separated by a dash, within parentheses.
+
+The developer of an application can choose which value range a given control uses.
+For example, a 16-position volume control currently set to 75% might look like this::
+
+  @11 (0 - 15)
+
+It could, however, also look like this::
+
+  @75% (0% - 100%)
+
+Disabled Controls
+`````````````````
+
+If a control is currently disabled then the word ``disabled``,
+enclosed within parentheses, appears to the right of its label.
+For example::
+
+  Connect (disabled)
+
+When There's No Text
+````````````````````
+
+A screen element that has no text of its own,
+and that BRLTTY doesn't explicitly support,
+is normally not rendered.
+Examples of these include:
+
+* A graphic (e.g. an image view).
+* A container used to construct the screen's layout (e.g. a frame layout).
+
+It's still necessary to render it, however, if it implements
+an action (e.g. a tap) which the user needs to be able to perform.
+
+If the application's developer has provided descriptive text
+then that text is rendered.
+If not, then BRLTTY renders a generic description within (parentheses).
+It contains the widget's type, and, if available, it's source code identifier.
+
+Global Actions
+~~~~~~~~~~~~~~
+
+Android supports a number of global actions that can be performed by pressing
+special hardware buttons and/or by touching reserved areas on the screen.
+BRLTTY also offers a way to perform these actions from your braille device.
+While a better way may be developed in the future, this is how it can be done
+right now.
+
+* Since Android doesn't use the keyboard function keys
+  (commonly named ``F1`` through ``F12``),
+  BRLTTY uses them to perform the global actions.
+  The way a braille device emulates keyboard function keys
+  differs from model to model,
+  so you should check the BRLTTY documentation for your braille device.
+  The most common way is to press the corresponding cursor routing key
+  along with some other key or key combination.
+  For braille devices that have a braille keyboard,
+  the most common key to be used in conjunction with a cursor routing key
+  in order to emulate a keyboard function key is the space bar.
+
+* If your braille device has a braille keyboard
+  then you can perform the global actions
+  via chords that also include dots 7 and 8.
+  For example, Space + Dots78 + Dots125 (h) goes to the home screen.
+
+.. table:: Global Android Actions
+
+  =======  =========  =====================================  =====================
+  FN-Key   Chord78+   Action                                 As of Android Release
+  -------  ---------  -------------------------------------  ---------------------
+  ``F1``   125 (h)    go to the Home screen                  4.1 (Jelly Bean)
+  ``F2``   12 (b)     tap the Back button                    4.1 (Jelly Bean)
+  ``F3``   1345 (n)   go to the Notifications screen         4.1 (Jelly Bean)
+  ``F4``   1235 (r)   tap the Recent Apps (Overview) button  4.1 (Jelly Bean)
+  ``F5``   123456     go to BRLTTY's `Actions screen`_       \*
+  ``F6``   23         move to the first item                 \*
+  ``F7``   2          move to the previous item              \*
+  ``F8``   5          move to the next item                  \*
+  ``F9``   56         move to the last item                  \*
+  ``F10``  134 (m)    tap the Menu button                    \*
+  ``F11``  36         return to the active window            4.0 (Kitkat)
+  ``F12``  3          switch to the previous window          4.0 (Kitkat)
+  ``F13``  6          switch to the next window              4.0 (Kitkat)
+  ``F14``  2345 (t)   show the window title                  6.0 (Nougat)
+  ``F15``  24 (i)     show various device status indicators           \*
+  ``F16``  234 (s)    go to the Quick Settings screen        4.2 (Jelly Bean MR1)
+  ``F17``  135 (o)    go to the Device Options screen        5.0 (Lollipop)
+  =======  =========  =====================================  =====================
+
+Text Selection and the Clipboard
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As with the `global actions`_,
+if your braille device has a braille keyboard
+then you can perform text selection actions
+via chords that also include dots 7 and 8.
+For example, Space + Dots78 + Dots14 (c)
+copies the selected text to Adroid's clipboard.
+This works as of Android release 4.3 (Jelly Bean MR2).
+
+.. table:: Text Selection and Clipboard Actions
+
+  ========  =======================================
+  Chord78+  Action
+  --------  ---------------------------------------
+  1 (a)     select all of the text
+  4         clear the text selection
+  14 (c)    copy the selected text to the clipboard
+  1236 (v)  paste the text on the clipboard
+  1346 (x)  cut the selected text to the clipboard
+  ========  =======================================
+
+The text selection extends inclusively
+from its first endpoint through its second one.
+The attribute underlining feature (see the Show Attributes preference)
+is used to show which text has currently been selected.
+It may be helpful to set the attribute underline to blink
+if the cursor isn't set to blink, and vice versa.
+
+In addition to being able to select all of the text,
+there are ways to select a specific portion of the text.
+Exactly how this is done depends on which braille device you're using.
+There is, however, one way to do it that works
+on any braille device that has cursor routing keys.
+
+The cursor routing key where the cursor is
+normally doesn't serve any useful purpose.
+If, however, you enable the Start Text Selection preference
+(which can be found within the Navigation Options submenu),
+then it enters `text selection mode`_
+with that character being both the first and second endpoints.
+This method has the constraint that the first endpoint of the text selection
+must be a character that the cursor can get to.
+
+.. _Text Selection Mode:
+
+When in text selection mode,
+pressing a cursor routing key changes the second endpoint of the selection.
+This may be done any number of times.
+The second endpoint may be either after or before the first one.
+
+Commands
+````````
+
+HOST_COPY
+  Copy the selected text to the host clipboard
+  and then deselect it.
+
+HOST_CUT
+  Copy the selected text to the host clipboard
+  and then delete it.
+
+HOST_PASTE
+  Insert the text on the host clipboard after the screen cursor.
+
+TXTSEL_ALL
+  Select all of the text.
+
+TXTSEL_CLEAR
+  Clear (deselect) the current text selection.
+
+TXTSEL_SET
+  Select a specific portion of the text.
+  This command requires that both endpoints of the text
+  are within the current braille window.
+  The key combination needs two cursor routing (or equivalent) keys.
+  We recommend a long press of two cursor routing keys
+  (if the braille device supports it).
+
+TXTSEL_START
+  Enter `text selection mode`_ with that character being both endpoints.
+  With this method, the first endpoint of the text selection can be anywehre,
+  i.e. it isn't constrained to be a character that the cursor can get to.
+  The key combination needs a cursor routing (or equivalent) key.
+  We recommend a long press of a cursor routing key
+  (if the braille device supports it).
+
+Web Pages
+~~~~~~~~~
+
+Element Annotations
+```````````````````
+
+A number of elements within a web page are annotated
+in order to help distinguish them from the surrounding text.
+Each annotation is placed immediately before
+the text of the element that it describes.
+It consists of a three-letter tag,
+optionally followed by descriptivE data,
+all enclosed within (parentheses).
+Here are some examples:
+
+.. table:: Web Page Element Annotation Examples
+
+  =================  ====================================================
+  Example            Meaning
+  -----------------  ----------------------------------------------------
+  (btn) Submit       a Submit button
+  (lnk https://...)  the target URL of a link
+  (tbl 4x9)          the start of a table with four columns and nine rows
+  (col 2@3)          the second column of the third row of a table
+  =================  ====================================================
+
+The following table lists all of the annotations:
+
+.. table:: Web Page Element Annotations
+
+  ==========  =====================================  ===========
+  Annotation  Meaning                                Data
+  ----------  -------------------------------------  -----------
+  btn         button
+  cap         table caption
+  col         column in table row                    coordinates
+  frm         start of form
+  hdg         heading (unspecified level)
+  hd1         level one heading
+  hd2         level two heading
+  hd3         level three heading
+  hd4         level four heading
+  hd5         level five heading
+  hd6         level six heading
+  hdr         header of table column                 coordinates
+  lnk         target of link                         URL
+  lsi         list item                              coordinates
+  lsm         list marker
+  lst         start of list                          dimensions
+  pop         combo box (single or Multiple choice)
+  pwd         password field
+  row         start of table row
+  tbl         start of table                         dimensions
+  txt         text field or area
+  \--------   horizontal divider
+  ==========  =====================================  ===========
+
+Structural Navigation
+`````````````````````
+
+Pressing Dots2578 enters Structural navigation mode.
+When in this mode,
+a web page, when being browsed with Chrome, can be structurally navigated
+by pressing dot combinations on your braille device.
+Press Dots78 (without Space) to return to the default key bindings.
+
+When in this mode,
+combinations of dots 1 through 6 specify the type of element
+that you'd like to navigate to.
+Adding dot 7 means move to the previous element of that type,
+and adding dot 8 means move to the next element of that type.
+If neither dot 7 nor dot 8 is added then the current direction is used.
+
+The current direction defaults to (starts out as) ``next``.
+Pressing dot 7 by itself sets it to ``previous``,
+and pressing dot 8 by itself sets it to ``next``.
+
+If the key table for your braille device defines
+a key to be the Left Alt (meta) modifier
+then you don't need to switch to/from structural navigation mode.
+Just hold that key while pressing
+the desired structural navigation dot combination.
+
+The following element types are supported
+(where possible, a hopefully easy-to-remember character has been chosen):
+
+.. table:: Structurally Navigable Web Page Elements
+
+  ======  ====  =====================================
+  Dots    Char  Go to the Previous/Next
+  ------  ----  -------------------------------------
+  1       a     article
+  12      b     button
+  14      c     control
+  145     d     ARIA landmark
+  15      e     editable text
+  124     f     focusable item
+  1245    g     graphic
+  125     h     heading (any level)
+  24      i     list item
+  123     l     link (visited or unvisited)
+  134     m     media
+  135     o     list (ordered or unordered)
+  1235    r     radio button
+  234     s     section
+  2345    t     table
+  136     u     unvisited link
+  1236    v     visited link
+  1346    x     check box
+  1356    z     combo box (single or multiple choice)
+  2       1     level one heading
+  23      2     level two heading
+  25      3     level three heading
+  256     4     level four heading
+  26      5     level five heading
+  235     6     level six heading
+  ======  ====  =====================================
+
+When BRLTTY Crashes
+-------------------
+
+We hope, of course, that BRLTTY won't crash. If it does, though, we want to
+know about it.
+
+If BRLTTY does crash, you'll get a dialog with a message like this::
+
+   Unfortunately, BRLTTY has stopped.
+
+This dialog will stay on the screen until you dismiss it by tapping its ``OK``
+button. Android will then try to automatically restart BRLTTY, so don't be
+overly concerned if this dialog comes up again. Android will eventually give up
+if, after a few automatic restart attempts, it decides that BRLTTY simply won't
+stay running.
+
+If this ever happens, then, if you can, connect your device to your host via
+USB as soon as possible in order to capture a debug log. To capture a debug
+log, use this command::
+
+   adb logcat -v time -d >/path/to/logfile
+
+The ``-v time`` option means to add a timestamp to each log record. The ``-d``
+option means to dump the current Android system log. The ``adb logcat`` command
+writes the log to its standard output, so you need to redirect its standard
+output (with ``>``) to wherever you'd like the log to be written.
+
+The reason for capturing the log as soon as possible after a problem is that
+Android imposes limits on its log storage so that the log can't consume too
+much of your device's resources. If the log becomes too large, Android
+automatically removes older entries from it. If you wait too long, therefore,
+the part of it that shows how BRLTTY crashed may already have been
+automatically removed.
+
+Known Issues
+------------
+
+Serial devices aren't supported. Even though Android devices don't have serial
+ports, serial devices still can be connected via a USB to Serial adapter. Users
+who have older, serial-only braille devices should still be able to use them
+with their Android devices.
+
+Installing BRLTTY
+=================
+
+BRLTTY has been designed to run on at least Android 4.1 (Jelly Bean).
+While it does run on Android 4.0 (Ice Cream Sandwich),
+many of its highly desirable features won't work.
+
+BRLTTY can be installed via Google Play.
+You can either search for it by name
+or go directly to `BRLTTY on Google Play`_.
+
+Required Permissions
+--------------------
+
+BRLTTY requires access to a number of privileged
+Android operating system capabilities.
+The required permissions are as follows:
+
+.. include:: android-permissions.rst
+
+The Old Way
+-----------
+
+Before it was on Google Play, BRLTTY had to be installed and updated manually.
+For now, this way still works.
+
+These instructions are from the perspective of a Firefox user on Windows,
+but the process should be much the same when using a different web browser and/or operating system.
+
+On Your Computer
+~~~~~~~~~~~~~~~~
+
+1) Go to `BRLTTY's web site`_.
+
+2) Find the ``Download`` link and press Enter on it.
+
+3) Go to the ``Android`` section, down-arrow from there to the link that says
+   ``Latest APK``, and press Enter on it.
+
+4) You'll be prompted to open or save the file at this point. Save it.
+
+5) Go to your ``Downloads`` folder (or wherever you save downloads), and
+   find the ``brltty-latest.apk`` file.
+
+6) If the file has been saved on your computer as ``brltty-latest.zip``,
+   then press the ``Context`` key, arrow to and press Enter on ``Rename``,
+   and change the file extension from ``zip`` to ``apk``. Don't worry if you
+   get a warning about the possibility of rendering the file unusable. Go
+   ahead with the rename.
+
+On Your Android Device
+~~~~~~~~~~~~~~~~~~~~~~
+
+1) Go into ``Settings`` -> ``Security``, and ensure that ``Unknown Sources``
+   is enabled. This option says something like:
+   
+      Allow the installation of apps from unknown sources
+      
+   This is a one-time step. Once the box has been checked, it stays checked.
+
+2) Copy the ``apk`` file to your device. There are a number of ways to do this:
+
+   * The easiest way may be to email it to yourself as a file attachment so
+     that it will go to the email on your Android device.
+
+   * Another option is to save the file in Dropbox on your computer, and
+     then wait for it to show up in Dropbox on your Android device.
+
+   * Another option is to connect your Android device to your computer via
+     a USB cable, and then to copy the file to it in the same way that
+     you'd copy a file to a thumb drive.
+
+3) Tap the ``brltty-latest.apk`` file to start its installation, and answer any
+   prompts. If you use the Dropbox method, you might need to tap on the file
+   twice - once to download it, and a second time to install it.
+
+4) Tap ``OK`` when installation is complete.
+
+Building BRLTTY
+===============
+
+Preparing Your Host Environment
+-------------------------------
+
+BRLTTY is currently being built using:
+
+* Version |SDK build tools version| of the Android SDK build tools.
+* Version |NDK version| of the Android NDK.
+* Version |JDK version| of OpenJDK.
+
+You need the Android SDK (Software Development Kit) for:
+
+* installing an application onto your device
+* removing an application from your device
+
+You can get it from `The Android SDK Web Page`_.
+
+You need the Android NDK (Native Development Kit) if you want to do your own
+builds. You can get it from `The Android NDK Web Page`_.
+
+The SDK initially only includes support for the current Android API
+(Application Programming Interface) level. BRLTTY, however, needs to support
+earlier API levels so that it can run on older releases of Android. Support for
+any missing API levels is added whenever the SDK is updated. To do this, use
+the following command::
+
+  android update sdk -u
+
+The ``-u`` option, which is the short form of the ``--no-ui`` option, means to
+bypass the graphical interface.
+
+There may be password prompts for installing packages that are provided by
+various vendours. Any of these can be easily skipped.
+
+The 64-bit versions of the SDK and NDK depend on 32-bit system libraries. If
+you're using a 64-bit version then you need to first ensure that these are
+installed on your system. This at least includes:
+
+* libc6 (or glibc)
+* libz (or zlib)
+* libstdc++6 (or libstdc++)
+* libncurses
+
+If you're using a modern Debian GNU/Linux system (``Wheezy`` or later), you can
+install these packages for a foreign architecture (in this case, i386) with the
+following commands (as root)::
+
+   dpkg --add-architecture i386
+   apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386 libc6:i386
+
+Installing and Preparing the BRLTTY Source Tree
+-----------------------------------------------
+
+Choose the directory that should contain BRLTTY's source tree (which needn't
+yet exist). Then extract the latest BRLTTY source into it with the following
+command::
+
+   git clone https://github.com/brltty/brltty.git /path/to/brltty
+
+The directory operand (of ``git clone``) is optional. If you don't specify it
+then the directory named ``brltty`` within the current working directory is
+assumed.
+
+Next, you need to prepare the source tree. This is done as follows::
+
+   cd /path/to/brltty
+   ./autogen
+
+At this point, the source tree is essentially just like what you'd get were you
+to unpack an officially released BRLTTY archive. It doesn't yet know anything
+about the specifics of your system. It also doesn't yet know anything about the
+platform you intend to build BRLTTY for.
+
+Adding information to BRLTTY's source tree regarding the specifics of your
+system, as well as of your intent to build BRLTTY for Android, is done as
+follows::
+
+   export ANDROID_NDK=/path/to/Android/NDK
+   ./cfg-android -q
+
+The ``-q`` option, which is the short form of the ``configure`` command's 
+``--quiet`` option, means to not display any progress information (there's 
+usually quite a lot of it) - only warnings and errors are displayed.
+
+All of the options you give to the ``cfg-android`` command are passed directly
+through to the ``configure`` command. So, while ``cfg-android`` supplies a
+default set of options to ``configure``, it's easy for you to do your own
+customization.
+
+Building BRLTTY for Android
+---------------------------
+
+In order to be able to build an Android application, a number of Android build
+tools need to be added to your command search path. This is done via the
+following command::
+
+   export PATH="/path/to/Android/SDK/tools:/path/to/Android/SDK/platform-tools:$PATH"
+
+The final step is to build the BRLTTY service for Android. This is done as
+follows::
+
+   cd /path/to/brltty/Android/Application
+   make -s
+
+The ``-s`` option of the ``make`` command, which is short for its ``--silent``
+option, means to not display any progress information (there's usually quite a
+lot of it) - only warnings and errors are displayed.
+
+The result of the build is the file ``BRLTTY_App-debug.apk``. It will be in the
+``bin/`` subdirectory of BRLTTY's Android Application directory::
+
+   /path/to/brltty/Android/Application/bin/BRLTTY_App-debug.apk
+
+``apk`` is the file extension used for an installable Android package.
+
+Preparing Your Android Device
+-----------------------------
+
+You need ``USB Debugging`` to be enabled. This is done from the ``Developer
+Options`` screen. You can get to it from the ``Settings`` screen.
+
+Launch the ``Settings`` application, and look, near the bottom, for ``Developer
+Options``. If you can't find it, the most likely cause is a new feature that
+was introduced in Android 4.2 (Jelly Bean). If you need to enable it, tap on
+``About Phone``, which, again, can be found near the bottom of the ``Settings``
+screen. Then, on the ``About Phone`` screen, look for the ``Build Number``
+line. Tap on ``Build Number`` seven times and your device will officially
+declare you to be a developer. You should then be able to find ``Developer
+Options`` on the ``Settings`` screen.
+
+There's a check box at the top-right of the ``Developer Options`` screen. It
+needs to be checked so that all of the other controls on that screen will be
+enabled. After doing that, check the ``USB Debugging`` check box (which can be
+found within the ``Debugging`` section). This enables the ``adb`` (Android
+Debug Bridge) tool to perform functions on your Android device.
+
+Installing BRLTTY on Your Android Device
+----------------------------------------
+
+In order to install BRLTTY onto your device, or to remove it from your device,
+you need to be in BRLTTY's Android Application directory::
+
+   cd /path/to/brltty/Android/Application
+
+You also need to connect your device to your host via USB.
+
+To install BRLTTY, use this command::
+
+   make -s install
+
+To remove BRLTTY, use this command::
+
+   make -s uninstall
+
+The ``make install`` command will fail if BRLTTY is already installed. If
+you're wanting to upgrade BRLTTY, however, then removing it first is probably
+what you don't want to be doing. This is because removing BRLTTY also causes
+its settings to be lost. What you should do instead is reinstall it. You can do
+this with the following command::
+
+   make -s reinstall
+
+If you've obtained your Android package file (``apk``) for BRLTTY from some
+other source (than building it for yourself), then it may have a different name
+than the make file is expecting. It's useful, therefore, to know what the
+actual host commands are for installing and removing Android applications.
+
+The host command for installing an Android application is::
+
+   adb install /path/to/file
+
+The host command for reinstalling an Android application is::
+
+   adb install -r /path/to/file
+
+The host command for removing an Android application is::
+
+   adb uninstall application.package.name
+
+So, to remove BRLTTY, the host command is::
+
+   adb uninstall org.a11y.brltty.android
+
+If any of these ``make`` or ``adb`` commands fails with an error like ``device 
+not found``, it's probably because your host's USB device permissions are 
+requiring root access. The solution to this problem is to restart the ``adb``
+server such that it is running as root. With this done, you yourself will still
+be able to use ``adb`` as a regular user.
+
+The commands to restart the ``adb`` server such that it's running as root are
+as follows::
+
+   su
+   cd /path/to/Android/SDK/platform-tools
+   ./adb kill-server
+   ./adb start-server
+   exit
+
diff --git a/Documents/README.AttributesTables b/Documents/README.AttributesTables
new file mode 100644
index 0000000..c731125
--- /dev/null
+++ b/Documents/README.AttributesTables
@@ -0,0 +1,80 @@
+~~~~~~~~~~~~~~~~~
+Attributes Tables
+~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+Files with names of the form ``*.atb`` are attributes tables, and with names of
+the form ``*.ati`` are attributes subtables. They are used when BRLTTY is
+displaying screen attributes rather than screen content. Each of the eight
+braille dots represents one of the eight VGA attribute bits.
+
+Attributes tables can usually be found in the ``/etc/brltty/Attributes/``
+directory (see |README.Customize| for more details). See
+`Attributes Table List`_ for a list of BRLTTY's attributes tables.
+
+An attributes table consists of a sequence of directives, one per line, which 
+define how combinations of VGA attributes are to be represented in braille. 
+UTF-8 character encoding must be used. Whitespace (blanks, tabs) at the 
+beginning of a line, as well as before and/or after any operand of any 
+directive, is ignored. Lines containing only whitespace are ignored. If the 
+first non-whitespace character of a line is ``#`` then that line is a comment 
+and is ignored.
+
+Directives
+==========
+
+The Dot Directive
+-----------------
+
+.. parsed-literal:: dot *dot* *state* # *comment*
+
+Use this directive to specify what a specific dot represents. The default is
+that all dots are down and not used to represent anything.
+
+*dot*
+   The dot being defined. It is a single digit within the range ``1``-``8`` as
+   defined by the standard braille dot numbering convention (see 
+   |README.BrailleDots| for details).
+
+*state*
+   What the dot being defined represents. It may be:
+
+   =attribute
+      The dot is raised if the named attribute is on.
+
+   ~attribute
+      The dot is raised if the named attribute is off.
+
+The attributes are:
+
+=========  ===  ========
+Name       Hex  binary
+---------  ---  --------
+fg-blue    01   00000001
+fg-green   02   00000010
+fg-red     04   00000100
+fg-bright  08   00001000
+bg-blue    10   00010000
+bg-green   20   00100000
+bg-red     40   01000000
+blink      80   10000000
+=========  ===  ========
+
+Examples::
+
+   dot 1 =fg-red
+   dot 2 ~bg-blue
+
+.. include:: nesting-directives.rst
+
+Attributes Table List
+=====================
+
+.. csv-table::
+   :header-rows: 1
+   :file: attributes-table.csv
+
diff --git a/Documents/README.Bluetooth b/Documents/README.Bluetooth
new file mode 100644
index 0000000..096bbed
--- /dev/null
+++ b/Documents/README.Bluetooth
@@ -0,0 +1,207 @@
+~~~~~~~~~~~~~~~~~~~~~
+Bluetooth Connections
+~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Pairing a Bluetooth Device
+==========================
+
+You need to "pair" your device with the host on which you wish to use it. You 
+can pair a device with more than one host, but you can usually only use it with 
+one host at a time.
+
+On Linux
+--------
+
+Pairing a Bluetooth device on Linux is relatively easy if you're comfortable 
+working within the graphical (X) environment. All you need to do is to run 
+``bluetooth-applet`` and follow the steps it presents to you. It's a little 
+trickier to pair a Bluetooth device via the command line, but it can be done.
+How to do it depends on the version of the Bluetooth software stack that you're
+using.
+
+For Bluetooth Version 5
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As of Bluetooth version 5, all required actions for pairing a device via the
+command-line can be performed with a single tool called ``bluetoothctl``.
+
+First, in case you have several Bluetooth controllers in use (uncommon, but
+possible), you need to make sure that the correct one is currently selected.
+Use the ``list`` command to show all available controllers, and the
+``select <host-address>`` command to select one:
+
+.. code-block:: console
+
+   # bluetoothctl
+   [bluetooth]# list
+   Controller 01:23:45:67:89:AB fzidpc73
+   [bluetooth]# select 01:23:45:67:89:AB
+
+It can happen that the selected controller is not powered on at the moment.
+Make sure it is, and, if it isn't, use the ``power on`` command to enable the
+controller:
+
+.. code-block:: console
+
+   [bluetooth]# show
+   Controller 01:23:45:67:89:AB
+           Name: fzidpc73
+           Alias: fzidpc73-0
+           Class: 0x000000
+           Powered: no
+           Discoverable: no
+           Pairable: yes
+           UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
+           UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
+           UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
+           UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
+           UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
+           Modalias: usb:v1D6Bp0246d0517
+           Discovering: no
+   [bluetooth]# power on
+   [CHG] Controller 01:23:45:67:89:AB Class: 0x000104
+   Changing power on succeeded
+   [CHG] Controller 01:23:45:67:89:AB Powered: yes
+
+To obtain the Bluetooth device address of the device you want to pair with,
+enable scan mode:
+
+.. code-block:: console
+
+   [bluetooth]# scan on
+   Discovery started
+   [CHG] Controller 01:23:45:67:89:AB Discovering: yes
+   [NEW] Device 12:34:56:78:9A:BC braillex live 0139
+   [bluetooth]# scan off
+   Discovery stopped
+   [CHG] Controller 01:23:45:67:89:AB Discovering: no
+
+To be able to receive PIN code requests directly on the console, you need to
+enable the *agent*:
+
+.. code-block:: console
+
+   [bluetooth]# agent on
+   Agent registered
+
+Now you are finally ready to initiate the pairing:
+
+.. code-block:: console
+
+   [bluetooth]# pair 12:34:56:78:9A:BC
+   Attempting to pair with 12:34:56:78:9A:BC
+   [CHG] Device 12:34:56:78:9A:BC Connected: yes
+   Request PIN code
+   [agent] Enter PIN code: 1234
+   [CHG] Device 12:34:56:78:9A:BC UUIDs:
+           00001101-0000-1000-8000-00805f9b34fb
+   [CHG] Device 12:34:56:78:9A:BC Paired: yes
+   Pairing successful
+   [CHG] Device 12:34:56:78:9A:BC Connected: no
+
+For Bluetooth Version 4
+~~~~~~~~~~~~~~~~~~~~~~~
+
+You need to find out your host's Bluetooth Device Address (referred to later in 
+this document as *host-address*). To do this, run the command::
+
+   hciconfig hci0
+
+Look for the (indented) line which begins with ``BD Address:``. The very next 
+"word" on that line is your host's Bluetooth Device Address. It'll be six 
+two-digit hexadecimal numbers separated by colons (``:``). Examples in this 
+document use the value ``01:23:45:67:89:AB``.
+
+You need to find out your device's Bluetooth Device Address (referred to later 
+in this document as *device-address*). To do this, run the command::
+
+   hcitool scan
+
+This command can take a while to complete as it gives devices a fair bit of
+time to respond. Each device which responds creates one two-column output line.
+The first column is its Bluetooth Device Address (examples in this document use
+the value ``12:34:56:78:9A:BC``), and the second column is its 
+current name. Manufacturers usually set a device's initial name to its product 
+name and model number so that it's easy to spot within the scan output. If your 
+device allows you to change its name, it's okay to do so even after you've 
+paired it.
+
+A device needs to be "visible" in order for the scan to find it. Most devices 
+are "invisible" by default, but have a way to be made temporarily visible. You 
+should check your device's manual to find out how to do this. In many cases,
+there's either a button which needs to be pressed or a menu item which needs to 
+be selected. Since both the device's temporary visibility and the host's scan 
+time out, make your device visible just before you initiate the scan.
+
+You need to know what PIN (password) your device is expecting. Your device's 
+documentation should contain this information. Many devices allow you to set 
+the PIN, in which case they'll have a menu which allows you to do this. 
+Examples in this document use the value ``1234``.
+
+Go into the directory ``/var/lib/bluetooth/<host-address>``. In there (create
+if necessary) is the file ``pincodes``. Each line in this file associates a
+device with its PIN. It has two fields separated by space. The first field is
+the device's Bluetooth Device Address and the second field is its PIN. The 
+Bluetooth Device Address must be in uppercase. For example::
+
+   cat /var/lib/bluetooth/01:23:45:67:89:AB/pincodes
+   12:34:56:78:9A:BC 1234
+
+
+Using a Bluetooth Device with BRLTTY
+====================================
+
+After your device has been paired with your host, it's ready to be used by 
+BRLTTY.
+
+Identifying the Device
+----------------------
+
+You can tell BRLTTY to use your Bluetooth device either via the ``-d``
+(or ``--braille-device=``) command line option, or via the ``braille-device``
+line in the file ``/etc/brltty.conf``. The device should be specified as the
+word ``bluetooth``, a colon (``:``), and the device's Bluetooth Device Address.
+For example::
+
+   brltty -d bluetooth:12:34:56:78:9A:BC
+
+or::
+
+   cat /etc/brltty.conf
+   braille-device bluetooth:12:34:56:78:9A:BC
+
+On some platforms,
+BRLTTY supports the detection of a Bluetooth device based on its name.
+These platforms include:
+
+* Android
+* Linux (if the Bluetooth version is at least 5)
+* Windows
+
+The address of the device needn't be specified on these platforms -
+specifying just ``bluetooth:`` is sufficient.
+BRLTTY will find the braille device based on its name.
+It'll only consider devices that have already been paired with the host.
+
+If two (or more) braille devices are paired with your host,
+and if both of them are near by, turned on, and configured for Bluetooth access,
+then, of course, BRLTTY might choose the wrong one.
+As long as both of them don't have the same Bluetooth name,
+you can still specify the intended device without knowing its address.
+You can limit the devices that BRLTTY will consider
+by specifying the beginning of its Bluetooth name::
+
+   bluetooth:name=prefix
+
+If you need to specify a space within the prefix, then:
+
+* On the command line, use quotes::
+
+    -d "bluetooth:name=braille device"
+
+* In ``/etc/brltty.conf``, use \\s::
+
+    braille-device bluetooth:name=braille\sdevice
+
diff --git a/Documents/README.BrailleDots b/Documents/README.BrailleDots
new file mode 100644
index 0000000..ff40ed8
--- /dev/null
+++ b/Documents/README.BrailleDots
@@ -0,0 +1,35 @@
+~~~~~~~~~~~~
+Braille Dots
+~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+A standard braille cell consists of six dots arranged in three rows and two
+columns. Each dot can be specifically identified by its number as follows:
+
+===  ============  ===  ======
+Dot  Position      Row  Column
+---  ------------  ---  ------
+1    top-left      1    1
+2    middle-left   2    1
+3    bottom-left   3    1
+4    top-right     1    2
+5    middle-right  2    2
+6    bottom-right  3    2
+===  ============  ===  ======
+
+Computer braille has introduced a fourth row at the bottom.
+
+===  ============  ===  ======
+Dot  Position      Row  Column
+---  ------------  ---  ------
+7    below-left    4    1
+8    below-right   4    2
+===  ============  ===  ======
+
+A picture may make this numbering convention easier to understand::
+
+   1 o o 4
+   2 o o 5
+   3 o o 6
+   7 o o 8
diff --git a/Documents/README.CommandReference b/Documents/README.CommandReference
new file mode 100644
index 0000000..515f103
--- /dev/null
+++ b/Documents/README.CommandReference
@@ -0,0 +1,1887 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The BRLTTY Command Reference
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. contents::
+
+Alphabetical Command Index
+==========================
+
+* `ALERT`_
+* `ALTGR`_
+* `ASPK_CMP_WORDS`_
+* `ASPK_DEL_CHARS`_
+* `ASPK_INDENT`_
+* `ASPK_INS_CHARS`_
+* `ASPK_REP_CHARS`_
+* `ASPK_SEL_CHAR`_
+* `ASPK_SEL_LINE`_
+* `ATTRBLINK`_
+* `ATTRDN`_
+* `ATTRUP`_
+* `ATTRVIS`_
+* `AUTOREPEAT`_
+* `AUTOSPEAK`_
+* `BACK`_
+* `BOT`_
+* `BOT_LEFT`_
+* `BRLKBD`_
+* `BRLUCDOTS`_
+* `BRL_START`_
+* `BRL_STOP`_
+* `CAPBLINK`_
+* `CHRLT`_
+* `CHRRT`_
+* `CLIP_ADD`_
+* `CLIP_APPEND`_
+* `CLIP_COPY`_
+* `CLIP_NEW`_
+* `CLIP_RESTORE`_
+* `CLIP_SAVE`_
+* `COMPBRL6`_
+* `CONTEXT`_
+* `CONTRACTED`_
+* `CONTROL`_
+* `COPY_LINE`_
+* `COPY_RECT`_
+* `CSRBLINK`_
+* `CSRHIDE`_
+* `CSRJMP_VERT`_
+* `CSRSIZE`_
+* `CSRTRK`_
+* `CSRVIS`_
+* `DESCCHAR`_
+* `DESC_CURR_CHAR`_
+* `DISPMD`_
+* `FREEZE`_
+* `FWINLT`_
+* `FWINLTSKIP`_
+* `FWINRT`_
+* `FWINRTSKIP`_
+* `GOTOLINE`_
+* `GOTOMARK`_
+* `GUI`_
+* `GUI_APP_ALERTS`_
+* `GUI_APP_LIST`_
+* `GUI_APP_MENU`_
+* `GUI_AREA_ACTV`_
+* `GUI_AREA_NEXT`_
+* `GUI_AREA_PREV`_
+* `GUI_BACK`_
+* `GUI_BRL_ACTIONS`_
+* `GUI_DEV_OPTIONS`_
+* `GUI_DEV_SETTINGS`_
+* `GUI_HOME`_
+* `GUI_ITEM_FRST`_
+* `GUI_ITEM_LAST`_
+* `GUI_ITEM_NEXT`_
+* `GUI_ITEM_PREV`_
+* `GUI_TITLE`_
+* `HELP`_
+* `HOME`_
+* `HOSTCMD`_
+* `HOST_COPY`_
+* `HOST_CUT`_
+* `HOST_PASTE`_
+* `HWINLT`_
+* `HWINRT`_
+* `INDICATORS`_
+* `INFO`_
+* `KEY_BACKSPACE`_
+* `KEY_CURSOR_DOWN`_
+* `KEY_CURSOR_LEFT`_
+* `KEY_CURSOR_RIGHT`_
+* `KEY_CURSOR_UP`_
+* `KEY_DELETE`_
+* `KEY_END`_
+* `KEY_ENTER`_
+* `KEY_ESCAPE`_
+* `KEY_FUNCTION`_
+* `KEY_HOME`_
+* `KEY_INSERT`_
+* `KEY_PAGE_DOWN`_
+* `KEY_PAGE_UP`_
+* `KEY_TAB`_
+* `LEARN`_
+* `LNBEG`_
+* `LNDN`_
+* `LNEND`_
+* `LNUP`_
+* `MACRO`_
+* `MENU_FIRST_ITEM`_
+* `MENU_LAST_ITEM`_
+* `MENU_NEXT_ITEM`_
+* `MENU_NEXT_SETTING`_
+* `MENU_PREV_ITEM`_
+* `MENU_PREV_LEVEL`_
+* `MENU_PREV_SETTING`_
+* `META`_
+* `MUTE`_
+* `NOOP`_
+* `NXDIFCHAR`_
+* `NXDIFLN`_
+* `NXINDENT`_
+* `NXNBWIN`_
+* `NXPGRPH`_
+* `NXPROMPT`_
+* `NXSEARCH`_
+* `OFFLINE`_
+* `PASSAT`_
+* `PASSCHAR`_
+* `PASSDOTS`_
+* `PASSPS2`_
+* `PASSXT`_
+* `PASTE`_
+* `PASTE_HISTORY`_
+* `PRDIFCHAR`_
+* `PRDIFLN`_
+* `PREFLOAD`_
+* `PREFMENU`_
+* `PREFRESET`_
+* `PREFSAVE`_
+* `PRINDENT`_
+* `PRNBWIN`_
+* `PRPGRPH`_
+* `PRPROMPT`_
+* `PRSEARCH`_
+* `REFRESH`_
+* `REFRESH_LINE`_
+* `RESTARTBRL`_
+* `RESTARTSPEECH`_
+* `RETURN`_
+* `ROUTE`_
+* `ROUTE_CURR_LOCN`_
+* `ROUTE_LINE`_
+* `ROUTE_SPEECH`_
+* `SAY_ABOVE`_
+* `SAY_ALL`_
+* `SAY_BELOW`_
+* `SAY_FASTER`_
+* `SAY_HIGHER`_
+* `SAY_LINE`_
+* `SAY_LOUDER`_
+* `SAY_LOWER`_
+* `SAY_SLOWER`_
+* `SAY_SOFTER`_
+* `SCR_START`_
+* `SCR_STOP`_
+* `SELECTVT`_
+* `SELECTVT_NEXT`_
+* `SELECTVT_PREV`_
+* `SETLEFT`_
+* `SETMARK`_
+* `SET_ATTRIBUTES_TABLE`_
+* `SET_CONTRACTION_TABLE`_
+* `SET_KEYBOARD_TABLE`_
+* `SET_LANGUAGE_PROFILE`_
+* `SET_TEXT_TABLE`_
+* `SHIFT`_
+* `SHOW_CURR_LOCN`_
+* `SIXDOTS`_
+* `SKPBLNKWINS`_
+* `SKPIDLNS`_
+* `SLIDEWIN`_
+* `SPEAK_CURR_CHAR`_
+* `SPEAK_CURR_LINE`_
+* `SPEAK_CURR_LOCN`_
+* `SPEAK_CURR_WORD`_
+* `SPEAK_FRST_CHAR`_
+* `SPEAK_FRST_LINE`_
+* `SPEAK_INDENT`_
+* `SPEAK_LAST_CHAR`_
+* `SPEAK_LAST_LINE`_
+* `SPEAK_NEXT_CHAR`_
+* `SPEAK_NEXT_LINE`_
+* `SPEAK_NEXT_WORD`_
+* `SPEAK_PREV_CHAR`_
+* `SPEAK_PREV_LINE`_
+* `SPEAK_PREV_WORD`_
+* `SPELL_CURR_WORD`_
+* `SPKHOME`_
+* `SPK_START`_
+* `SPK_STOP`_
+* `SWITCHVT`_
+* `SWITCHVT_NEXT`_
+* `SWITCHVT_PREV`_
+* `TIME`_
+* `TOP`_
+* `TOP_LEFT`_
+* `TOUCH_AT`_
+* `TOUCH_NAV`_
+* `TUNES`_
+* `TXTSEL_ALL`_
+* `TXTSEL_CLEAR`_
+* `TXTSEL_SET`_
+* `TXTSEL_START`_
+* `UNSTICK`_
+* `UPPER`_
+* `WINDN`_
+* `WINUP`_
+
+Special Modes
+=============
+
+* `HELP`_
+* `LEARN`_
+* `PREFMENU`_
+* `INFO`_
+* `DISPMD`_
+* `FREEZE`_
+* `DESCCHAR`_
+* `TIME`_
+* `INDICATORS`_
+* `CONTEXT`_
+
+.. _HELP:
+
+**HELP** - Enter/leave help display.
+
+.. _LEARN:
+
+**LEARN** - Enter/leave command learn mode.
+
+.. _PREFMENU:
+
+**PREFMENU** - Enter/leave preferences menu.
+
+.. _INFO:
+
+**INFO** - Enter/leave status display.
+
+.. _DISPMD:
+
+**DISPMD** - Set display mode attributes/text.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _FREEZE:
+
+**FREEZE** - Set screen image frozen/unfrozen.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _DESCCHAR:
+
+**DESCCHAR** - Describe character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _TIME:
+
+**TIME** - Show current date and time.
+
+.. _INDICATORS:
+
+**INDICATORS** - Show various device status indicators.
+
+.. _CONTEXT:
+
+**CONTEXT** - Switch to command context.
+
+The following modifiers may be specified:
+
+* an offset
+
+Cursor Functions
+================
+
+* `HOME`_
+* `BACK`_
+* `RETURN`_
+* `ROUTE`_
+* `ROUTE_LINE`_
+* `CSRJMP_VERT`_
+* `ROUTE_CURR_LOCN`_
+
+.. _HOME:
+
+**HOME** - Go to screen cursor.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _BACK:
+
+**BACK** - Go back after cursor tracking.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _RETURN:
+
+**RETURN** - Go to screen cursor or go back after cursor tracking.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _ROUTE:
+
+**ROUTE** - Bring screen cursor to character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _ROUTE_LINE:
+
+**ROUTE_LINE** - Bring screen cursor to line.
+
+The following modifiers may be specified:
+
+* Row: scaled
+* Vertical: toleft
+
+.. _CSRJMP_VERT:
+
+**CSRJMP_VERT** - Bring screen cursor to current line.
+
+.. _ROUTE_CURR_LOCN:
+
+**ROUTE_CURR_LOCN** - Bring screen cursor to speech cursor.
+
+Vertical Navigation
+===================
+
+* `LNUP`_
+* `LNDN`_
+* `TOP`_
+* `BOT`_
+* `TOP_LEFT`_
+* `BOT_LEFT`_
+* `PRDIFLN`_
+* `NXDIFLN`_
+* `ATTRUP`_
+* `ATTRDN`_
+* `PRPGRPH`_
+* `NXPGRPH`_
+* `PRPROMPT`_
+* `NXPROMPT`_
+* `WINUP`_
+* `WINDN`_
+* `PRINDENT`_
+* `NXINDENT`_
+* `PRDIFCHAR`_
+* `NXDIFCHAR`_
+* `GOTOLINE`_
+
+.. _LNUP:
+
+**LNUP** - Go up one line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _LNDN:
+
+**LNDN** - Go down one line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _TOP:
+
+**TOP** - Go to top line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _BOT:
+
+**BOT** - Go to bottom line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _TOP_LEFT:
+
+**TOP_LEFT** - Go to beginning of top line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _BOT_LEFT:
+
+**BOT_LEFT** - Go to beginning of bottom line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _PRDIFLN:
+
+**PRDIFLN** - Go up to nearest line with different content.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _NXDIFLN:
+
+**NXDIFLN** - Go down to nearest line with different content.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _ATTRUP:
+
+**ATTRUP** - Go up to nearest line with different highlighting.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _ATTRDN:
+
+**ATTRDN** - Go down to nearest line with different highlighting.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _PRPGRPH:
+
+**PRPGRPH** - Go up to first line of paragraph.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _NXPGRPH:
+
+**NXPGRPH** - Go down to first line of next paragraph.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _PRPROMPT:
+
+**PRPROMPT** - Go up to previous command prompt.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _NXPROMPT:
+
+**NXPROMPT** - Go down to next command prompt.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _WINUP:
+
+**WINUP** - Go up several lines.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _WINDN:
+
+**WINDN** - Go down several lines.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _PRINDENT:
+
+**PRINDENT** - Go up to nearest line with less indent than character.
+
+The following modifiers may be specified:
+
+* a column number
+* Motion: route
+* Vertical: toleft
+
+.. _NXINDENT:
+
+**NXINDENT** - Go down to nearest line with less indent than character.
+
+The following modifiers may be specified:
+
+* a column number
+* Motion: route
+* Vertical: toleft
+
+.. _PRDIFCHAR:
+
+**PRDIFCHAR** - Go up to nearest line with different character.
+
+The following modifiers may be specified:
+
+* a column number
+* Motion: route
+* Vertical: toleft
+
+.. _NXDIFCHAR:
+
+**NXDIFCHAR** - Go down to nearest line with different character.
+
+The following modifiers may be specified:
+
+* a column number
+* Motion: route
+* Vertical: toleft
+
+.. _GOTOLINE:
+
+**GOTOLINE** - Go to selected line.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Row: scaled
+* Vertical: toleft
+
+Horizontal Navigation
+=====================
+
+* `FWINLT`_
+* `FWINRT`_
+* `FWINLTSKIP`_
+* `FWINRTSKIP`_
+* `PRNBWIN`_
+* `NXNBWIN`_
+* `LNBEG`_
+* `LNEND`_
+* `CHRLT`_
+* `CHRRT`_
+* `HWINLT`_
+* `HWINRT`_
+* `SETLEFT`_
+
+.. _FWINLT:
+
+**FWINLT** - Go backward one braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _FWINRT:
+
+**FWINRT** - Go forward one braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _FWINLTSKIP:
+
+**FWINLTSKIP** - Go backward skipping blank braille windows.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _FWINRTSKIP:
+
+**FWINRTSKIP** - Go forward skipping blank braille windows.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _PRNBWIN:
+
+**PRNBWIN** - Go backward to nearest non-blank braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _NXNBWIN:
+
+**NXNBWIN** - Go forward to nearest non-blank braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _LNBEG:
+
+**LNBEG** - Go to beginning of line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _LNEND:
+
+**LNEND** - Go to end of line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _CHRLT:
+
+**CHRLT** - Go left one character.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _CHRRT:
+
+**CHRRT** - Go right one character.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _HWINLT:
+
+**HWINLT** - Go left half a braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _HWINRT:
+
+**HWINRT** - Go right half a braille window.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SETLEFT:
+
+**SETLEFT** - Place left end of braille window at character.
+
+The following modifiers may be specified:
+
+* a column number
+
+Window Navigation
+=================
+
+* `GUI_TITLE`_
+* `GUI_BRL_ACTIONS`_
+* `GUI_HOME`_
+* `GUI_BACK`_
+* `GUI_DEV_SETTINGS`_
+* `GUI_DEV_OPTIONS`_
+* `GUI_APP_LIST`_
+* `GUI_APP_MENU`_
+* `GUI_APP_ALERTS`_
+* `GUI_AREA_ACTV`_
+* `GUI_AREA_PREV`_
+* `GUI_AREA_NEXT`_
+* `GUI_ITEM_FRST`_
+* `GUI_ITEM_PREV`_
+* `GUI_ITEM_NEXT`_
+* `GUI_ITEM_LAST`_
+
+.. _GUI_TITLE:
+
+**GUI_TITLE** - Show the window title.
+
+.. _GUI_BRL_ACTIONS:
+
+**GUI_BRL_ACTIONS** - Open the braille actions window.
+
+.. _GUI_HOME:
+
+**GUI_HOME** - Go to the home screen.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _GUI_BACK:
+
+**GUI_BACK** - Go back to the previous screen.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _GUI_DEV_SETTINGS:
+
+**GUI_DEV_SETTINGS** - Open the device settings window.
+
+.. _GUI_DEV_OPTIONS:
+
+**GUI_DEV_OPTIONS** - Open the device options window.
+
+.. _GUI_APP_LIST:
+
+**GUI_APP_LIST** - Open the application list window.
+
+.. _GUI_APP_MENU:
+
+**GUI_APP_MENU** - Open the application-specific menu.
+
+.. _GUI_APP_ALERTS:
+
+**GUI_APP_ALERTS** - Open the application alerts window.
+
+.. _GUI_AREA_ACTV:
+
+**GUI_AREA_ACTV** - Return to the active screen area.
+
+.. _GUI_AREA_PREV:
+
+**GUI_AREA_PREV** - Switch to the previous screen area.
+
+.. _GUI_AREA_NEXT:
+
+**GUI_AREA_NEXT** - Switch to the next screen area.
+
+.. _GUI_ITEM_FRST:
+
+**GUI_ITEM_FRST** - Move to the first item in the screen area.
+
+.. _GUI_ITEM_PREV:
+
+**GUI_ITEM_PREV** - Move to the previous item in the screen area.
+
+.. _GUI_ITEM_NEXT:
+
+**GUI_ITEM_NEXT** - Move to the next item in the screen area.
+
+.. _GUI_ITEM_LAST:
+
+**GUI_ITEM_LAST** - Move to the last item in the screen area.
+
+Clipboard Functions
+===================
+
+* `CLIP_NEW`_
+* `CLIP_ADD`_
+* `COPY_LINE`_
+* `COPY_RECT`_
+* `CLIP_COPY`_
+* `CLIP_APPEND`_
+* `PASTE`_
+* `PASTE_HISTORY`_
+* `PRSEARCH`_
+* `NXSEARCH`_
+* `CLIP_SAVE`_
+* `CLIP_RESTORE`_
+
+.. _CLIP_NEW:
+
+**CLIP_NEW** - Start new clipboard at character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _CLIP_ADD:
+
+**CLIP_ADD** - Append to clipboard from character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _COPY_LINE:
+
+**COPY_LINE** - Linear copy to character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _COPY_RECT:
+
+**COPY_RECT** - Rectangular copy to character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _CLIP_COPY:
+
+**CLIP_COPY** - Copy characters to clipboard.
+
+.. _CLIP_APPEND:
+
+**CLIP_APPEND** - Append characters to clipboard.
+
+.. _PASTE:
+
+**PASTE** - Insert clipboard text after screen cursor.
+
+.. _PASTE_HISTORY:
+
+**PASTE_HISTORY** - Insert clipboard history entry after screen cursor.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _PRSEARCH:
+
+**PRSEARCH** - Search backward for clipboard text.
+
+.. _NXSEARCH:
+
+**NXSEARCH** - Search forward for clipboard text.
+
+.. _CLIP_SAVE:
+
+**CLIP_SAVE** - Save clipboard to disk.
+
+.. _CLIP_RESTORE:
+
+**CLIP_RESTORE** - Restore clipboard from disk.
+
+Text Selection and the Host Clipboard
+=====================================
+
+* `TXTSEL_CLEAR`_
+* `TXTSEL_SET`_
+* `TXTSEL_START`_
+* `TXTSEL_ALL`_
+* `HOST_COPY`_
+* `HOST_CUT`_
+* `HOST_PASTE`_
+
+.. _TXTSEL_CLEAR:
+
+**TXTSEL_CLEAR** - Clear the text selection.
+
+.. _TXTSEL_SET:
+
+**TXTSEL_SET** - Set text selection.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _TXTSEL_START:
+
+**TXTSEL_START** - Start text selection.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _TXTSEL_ALL:
+
+**TXTSEL_ALL** - Select all of the text.
+
+.. _HOST_COPY:
+
+**HOST_COPY** - Copy selected text to host clipboard.
+
+.. _HOST_CUT:
+
+**HOST_CUT** - Cut selected text to host clipboard.
+
+.. _HOST_PASTE:
+
+**HOST_PASTE** - Insert host clipboard text after screen cursor.
+
+Configuration Functions
+=======================
+
+* `TOUCH_NAV`_
+* `AUTOREPEAT`_
+* `SIXDOTS`_
+* `CONTRACTED`_
+* `COMPBRL6`_
+* `SKPIDLNS`_
+* `SKPBLNKWINS`_
+* `SLIDEWIN`_
+* `CSRTRK`_
+* `CSRSIZE`_
+* `CSRVIS`_
+* `CSRHIDE`_
+* `CSRBLINK`_
+* `ATTRVIS`_
+* `ATTRBLINK`_
+* `CAPBLINK`_
+* `TUNES`_
+* `SET_TEXT_TABLE`_
+* `SET_ATTRIBUTES_TABLE`_
+* `SET_CONTRACTION_TABLE`_
+* `SET_KEYBOARD_TABLE`_
+* `SET_LANGUAGE_PROFILE`_
+
+.. _TOUCH_NAV:
+
+**TOUCH_NAV** - Set touch navigation on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _AUTOREPEAT:
+
+**AUTOREPEAT** - Set autorepeat on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _SIXDOTS:
+
+**SIXDOTS** - Set text style 6-dot/8-dot.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CONTRACTED:
+
+**CONTRACTED** - Set contracted/computer braille.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _COMPBRL6:
+
+**COMPBRL6** - Set six/eight dot computer braille.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _SKPIDLNS:
+
+**SKPIDLNS** - Set skipping of lines with identical content on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _SKPBLNKWINS:
+
+**SKPBLNKWINS** - Set skipping of blank braille windows on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _SLIDEWIN:
+
+**SLIDEWIN** - Set sliding braille window on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CSRTRK:
+
+**CSRTRK** - Set track screen cursor on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CSRSIZE:
+
+**CSRSIZE** - Set screen cursor style block/underline.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CSRVIS:
+
+**CSRVIS** - Set screen cursor visibility on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CSRHIDE:
+
+**CSRHIDE** - Set hidden screen cursor on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CSRBLINK:
+
+**CSRBLINK** - Set screen cursor blinking on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ATTRVIS:
+
+**ATTRVIS** - Set attribute underlining on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ATTRBLINK:
+
+**ATTRBLINK** - Set attribute blinking on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _CAPBLINK:
+
+**CAPBLINK** - Set capital letter blinking on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _TUNES:
+
+**TUNES** - Set alert tunes on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _SET_TEXT_TABLE:
+
+**SET_TEXT_TABLE** - Set text table.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SET_ATTRIBUTES_TABLE:
+
+**SET_ATTRIBUTES_TABLE** - Set attributes table.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SET_CONTRACTION_TABLE:
+
+**SET_CONTRACTION_TABLE** - Set contraction table.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SET_KEYBOARD_TABLE:
+
+**SET_KEYBOARD_TABLE** - Set keyboard table.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SET_LANGUAGE_PROFILE:
+
+**SET_LANGUAGE_PROFILE** - Set language profile.
+
+The following modifiers may be specified:
+
+* an offset
+
+Menu Operations
+===============
+
+* `MENU_PREV_ITEM`_
+* `MENU_NEXT_ITEM`_
+* `MENU_FIRST_ITEM`_
+* `MENU_LAST_ITEM`_
+* `MENU_PREV_SETTING`_
+* `MENU_NEXT_SETTING`_
+* `MENU_PREV_LEVEL`_
+* `PREFSAVE`_
+* `PREFLOAD`_
+* `PREFRESET`_
+
+.. _MENU_PREV_ITEM:
+
+**MENU_PREV_ITEM** - Go up to previous item.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _MENU_NEXT_ITEM:
+
+**MENU_NEXT_ITEM** - Go down to next item.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _MENU_FIRST_ITEM:
+
+**MENU_FIRST_ITEM** - Go up to first item.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _MENU_LAST_ITEM:
+
+**MENU_LAST_ITEM** - Go down to last item.
+
+The following modifiers may be specified:
+
+* Motion: route
+* Vertical: toleft
+
+.. _MENU_PREV_SETTING:
+
+**MENU_PREV_SETTING** - Select previous choice.
+
+.. _MENU_NEXT_SETTING:
+
+**MENU_NEXT_SETTING** - Select next choice.
+
+.. _MENU_PREV_LEVEL:
+
+**MENU_PREV_LEVEL** - Go to previous menu level.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _PREFSAVE:
+
+**PREFSAVE** - Save preferences to disk.
+
+.. _PREFLOAD:
+
+**PREFLOAD** - Restore preferences from disk.
+
+.. _PREFRESET:
+
+**PREFRESET** - Reset preferences to defaults.
+
+Speech Functions
+================
+
+* `MUTE`_
+* `SAY_LINE`_
+* `SAY_ALL`_
+* `SAY_ABOVE`_
+* `SAY_BELOW`_
+* `SPKHOME`_
+* `SAY_SOFTER`_
+* `SAY_LOUDER`_
+* `SAY_SLOWER`_
+* `SAY_FASTER`_
+* `SAY_LOWER`_
+* `SAY_HIGHER`_
+* `AUTOSPEAK`_
+* `ASPK_SEL_LINE`_
+* `ASPK_SEL_CHAR`_
+* `ASPK_INS_CHARS`_
+* `ASPK_DEL_CHARS`_
+* `ASPK_REP_CHARS`_
+* `ASPK_CMP_WORDS`_
+* `ASPK_INDENT`_
+
+.. _MUTE:
+
+**MUTE** - Stop speaking.
+
+.. _SAY_LINE:
+
+**SAY_LINE** - Speak current line.
+
+.. _SAY_ALL:
+
+**SAY_ALL** - Speak from top of screen through bottom of screen.
+
+.. _SAY_ABOVE:
+
+**SAY_ABOVE** - Speak from top of screen through current line.
+
+.. _SAY_BELOW:
+
+**SAY_BELOW** - Speak from current line through bottom of screen.
+
+.. _SPKHOME:
+
+**SPKHOME** - Go to current speaking position.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SAY_SOFTER:
+
+**SAY_SOFTER** - Decrease speaking volume.
+
+.. _SAY_LOUDER:
+
+**SAY_LOUDER** - Increase speaking volume.
+
+.. _SAY_SLOWER:
+
+**SAY_SLOWER** - Decrease speaking rate.
+
+.. _SAY_FASTER:
+
+**SAY_FASTER** - Increase speaking rate.
+
+.. _SAY_LOWER:
+
+**SAY_LOWER** - Decrease speaking pitch.
+
+.. _SAY_HIGHER:
+
+**SAY_HIGHER** - Increase speaking pitch.
+
+.. _AUTOSPEAK:
+
+**AUTOSPEAK** - Set autospeak on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_SEL_LINE:
+
+**ASPK_SEL_LINE** - Set autospeak selected line on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_SEL_CHAR:
+
+**ASPK_SEL_CHAR** - Set autospeak selected character on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_INS_CHARS:
+
+**ASPK_INS_CHARS** - Set autospeak inserted characters on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_DEL_CHARS:
+
+**ASPK_DEL_CHARS** - Set autospeak deleted characters on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_REP_CHARS:
+
+**ASPK_REP_CHARS** - Set autospeak replaced characters on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_CMP_WORDS:
+
+**ASPK_CMP_WORDS** - Set autospeak completed words on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _ASPK_INDENT:
+
+**ASPK_INDENT** - Set autospeak indent of current line on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+Speech Navigation
+=================
+
+* `ROUTE_SPEECH`_
+* `SPEAK_CURR_CHAR`_
+* `DESC_CURR_CHAR`_
+* `SPEAK_PREV_CHAR`_
+* `SPEAK_NEXT_CHAR`_
+* `SPEAK_FRST_CHAR`_
+* `SPEAK_LAST_CHAR`_
+* `SPEAK_CURR_WORD`_
+* `SPELL_CURR_WORD`_
+* `SPEAK_PREV_WORD`_
+* `SPEAK_NEXT_WORD`_
+* `SPEAK_CURR_LINE`_
+* `SPEAK_PREV_LINE`_
+* `SPEAK_NEXT_LINE`_
+* `SPEAK_FRST_LINE`_
+* `SPEAK_LAST_LINE`_
+* `SPEAK_INDENT`_
+* `SPEAK_CURR_LOCN`_
+* `SHOW_CURR_LOCN`_
+
+.. _ROUTE_SPEECH:
+
+**ROUTE_SPEECH** - Bring speech cursor to character.
+
+The following modifiers may be specified:
+
+* a column number
+
+.. _SPEAK_CURR_CHAR:
+
+**SPEAK_CURR_CHAR** - Speak current character.
+
+.. _DESC_CURR_CHAR:
+
+**DESC_CURR_CHAR** - Describe current character.
+
+.. _SPEAK_PREV_CHAR:
+
+**SPEAK_PREV_CHAR** - Go to and speak previous character.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_NEXT_CHAR:
+
+**SPEAK_NEXT_CHAR** - Go to and speak next character.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_FRST_CHAR:
+
+**SPEAK_FRST_CHAR** - Go to and speak first non-blank character on line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_LAST_CHAR:
+
+**SPEAK_LAST_CHAR** - Go to and speak last non-blank character on line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_CURR_WORD:
+
+**SPEAK_CURR_WORD** - Speak current word.
+
+.. _SPELL_CURR_WORD:
+
+**SPELL_CURR_WORD** - Spell current word.
+
+.. _SPEAK_PREV_WORD:
+
+**SPEAK_PREV_WORD** - Go to and speak previous word.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_NEXT_WORD:
+
+**SPEAK_NEXT_WORD** - Go to and speak next word.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_CURR_LINE:
+
+**SPEAK_CURR_LINE** - Speak current line.
+
+.. _SPEAK_PREV_LINE:
+
+**SPEAK_PREV_LINE** - Go to and speak previous line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_NEXT_LINE:
+
+**SPEAK_NEXT_LINE** - Go to and speak next line.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_FRST_LINE:
+
+**SPEAK_FRST_LINE** - Go to and speak first non-blank line on screen.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_LAST_LINE:
+
+**SPEAK_LAST_LINE** - Go to and speak last non-blank line on screen.
+
+The following modifiers may be specified:
+
+* Motion: route
+
+.. _SPEAK_INDENT:
+
+**SPEAK_INDENT** - Speak indent of current line.
+
+.. _SPEAK_CURR_LOCN:
+
+**SPEAK_CURR_LOCN** - Speak speech cursor location.
+
+.. _SHOW_CURR_LOCN:
+
+**SHOW_CURR_LOCN** - Set speech cursor visibility on/off.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+Keyboard Input
+==============
+
+* `PASSDOTS`_
+* `PASSCHAR`_
+* `KEY_BACKSPACE`_
+* `KEY_ENTER`_
+* `KEY_TAB`_
+* `KEY_CURSOR_LEFT`_
+* `KEY_CURSOR_RIGHT`_
+* `KEY_CURSOR_UP`_
+* `KEY_CURSOR_DOWN`_
+* `KEY_PAGE_UP`_
+* `KEY_PAGE_DOWN`_
+* `KEY_HOME`_
+* `KEY_END`_
+* `KEY_INSERT`_
+* `KEY_DELETE`_
+* `UNSTICK`_
+* `UPPER`_
+* `SHIFT`_
+* `CONTROL`_
+* `META`_
+* `ALTGR`_
+* `GUI`_
+* `KEY_ESCAPE`_
+* `KEY_FUNCTION`_
+* `SWITCHVT`_
+* `SWITCHVT_PREV`_
+* `SWITCHVT_NEXT`_
+* `SELECTVT`_
+* `SELECTVT_PREV`_
+* `SELECTVT_NEXT`_
+* `BRLKBD`_
+* `BRLUCDOTS`_
+
+.. _PASSDOTS:
+
+**PASSDOTS** - Type braille dots.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+* Character: upper, escaped
+* Braille: dot1, dot2, dot3, dot4, dot5, dot6, dot7, dot8, space
+
+.. _PASSCHAR:
+
+**PASSCHAR** - Type unicode character.
+
+The following modifiers may be specified:
+
+* a single character
+* Input: shift, control, meta, altgr, gui
+* Character: upper, escaped
+
+.. _KEY_BACKSPACE:
+
+**KEY_BACKSPACE** - Backspace key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_ENTER:
+
+**KEY_ENTER** - Enter key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_TAB:
+
+**KEY_TAB** - Tab key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_CURSOR_LEFT:
+
+**KEY_CURSOR_LEFT** - Cursor-left key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_CURSOR_RIGHT:
+
+**KEY_CURSOR_RIGHT** - Cursor-right key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_CURSOR_UP:
+
+**KEY_CURSOR_UP** - Cursor-up key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_CURSOR_DOWN:
+
+**KEY_CURSOR_DOWN** - Cursor-down key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_PAGE_UP:
+
+**KEY_PAGE_UP** - Page-up key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_PAGE_DOWN:
+
+**KEY_PAGE_DOWN** - Page-down key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_HOME:
+
+**KEY_HOME** - Home key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_END:
+
+**KEY_END** - End key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_INSERT:
+
+**KEY_INSERT** - Insert key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_DELETE:
+
+**KEY_DELETE** - Delete key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _UNSTICK:
+
+**UNSTICK** - Clear all sticky input modifiers.
+
+.. _UPPER:
+
+**UPPER** - Cycle the Upper sticky input modifier (next, on, off).
+
+.. _SHIFT:
+
+**SHIFT** - Cycle the Shift sticky input modifier (next, on, off).
+
+.. _CONTROL:
+
+**CONTROL** - Cycle the Control sticky input modifier (next, on, off).
+
+.. _META:
+
+**META** - Cycle the Meta (Left Alt) sticky input modifier (next, on, off).
+
+.. _ALTGR:
+
+**ALTGR** - Cycle the AltGr (Right Alt) sticky input modifier (next, on, off).
+
+.. _GUI:
+
+**GUI** - Cycle the GUI (Windows) sticky input modifier (next, on, off).
+
+.. _KEY_ESCAPE:
+
+**KEY_ESCAPE** - Escape key.
+
+The following modifiers may be specified:
+
+* Input: shift, control, meta, altgr, gui
+
+.. _KEY_FUNCTION:
+
+**KEY_FUNCTION** - Function key.
+
+The following modifiers may be specified:
+
+* an offset
+* Input: shift, control, meta, altgr, gui
+
+.. _SWITCHVT:
+
+**SWITCHVT** - Switch to specific virtual terminal.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SWITCHVT_PREV:
+
+**SWITCHVT_PREV** - Switch to the previous virtual terminal.
+
+.. _SWITCHVT_NEXT:
+
+**SWITCHVT_NEXT** - Switch to the next virtual terminal.
+
+.. _SELECTVT:
+
+**SELECTVT** - Bind to specific virtual terminal.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _SELECTVT_PREV:
+
+**SELECTVT_PREV** - Bind to the previous virtual terminal.
+
+.. _SELECTVT_NEXT:
+
+**SELECTVT_NEXT** - Bind to the next virtual terminal.
+
+.. _BRLKBD:
+
+**BRLKBD** - Set braille keyboard enabled/disabled.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+.. _BRLUCDOTS:
+
+**BRLUCDOTS** - Set braille typing mode dots/text.
+
+The following modifiers may be specified:
+
+* Toggle: on, off
+
+Special Functions
+=================
+
+* `SETMARK`_
+* `GOTOMARK`_
+* `REFRESH`_
+* `REFRESH_LINE`_
+* `RESTARTBRL`_
+* `BRL_STOP`_
+* `BRL_START`_
+* `RESTARTSPEECH`_
+* `SPK_STOP`_
+* `SPK_START`_
+* `SCR_STOP`_
+* `SCR_START`_
+
+.. _SETMARK:
+
+**SETMARK** - Remember current braille window position.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _GOTOMARK:
+
+**GOTOMARK** - Go to remembered braille window position.
+
+The following modifiers may be specified:
+
+* an offset
+* Motion: route
+
+.. _REFRESH:
+
+**REFRESH** - Refresh braille display.
+
+.. _REFRESH_LINE:
+
+**REFRESH_LINE** - Refresh braille line.
+
+The following modifiers may be specified:
+
+* Row: scaled
+* Vertical: toleft
+
+.. _RESTARTBRL:
+
+**RESTARTBRL** - Restart braille driver.
+
+.. _BRL_STOP:
+
+**BRL_STOP** - Stop the braille driver.
+
+.. _BRL_START:
+
+**BRL_START** - Start the braille driver.
+
+.. _RESTARTSPEECH:
+
+**RESTARTSPEECH** - Restart speech driver.
+
+.. _SPK_STOP:
+
+**SPK_STOP** - Stop the speech driver.
+
+.. _SPK_START:
+
+**SPK_START** - Start the speech driver.
+
+.. _SCR_STOP:
+
+**SCR_STOP** - Stop the screen driver.
+
+.. _SCR_START:
+
+**SCR_START** - Start the screen driver.
+
+Internal Functions
+==================
+
+* `NOOP`_
+* `OFFLINE`_
+* `ALERT`_
+* `PASSXT`_
+* `PASSAT`_
+* `PASSPS2`_
+* `TOUCH_AT`_
+* `MACRO`_
+* `HOSTCMD`_
+
+.. _NOOP:
+
+**NOOP** - Do nothing.
+
+.. _OFFLINE:
+
+**OFFLINE** - Braille display temporarily unavailable.
+
+.. _ALERT:
+
+**ALERT** - Render an alert.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _PASSXT:
+
+**PASSXT** - XT (set 1) keyboard scan code.
+
+The following modifiers may be specified:
+
+* Keyboard: release, emul0, emul1
+
+.. _PASSAT:
+
+**PASSAT** - AT (set 2) keyboard scan code.
+
+The following modifiers may be specified:
+
+* Keyboard: release, emul0, emul1
+
+.. _PASSPS2:
+
+**PASSPS2** - PS/2 (set 3) keyboard scan code.
+
+The following modifiers may be specified:
+
+* Keyboard: release, emul0, emul1
+
+.. _TOUCH_AT:
+
+**TOUCH_AT** - Current reading location.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _MACRO:
+
+**MACRO** - Execute command macro.
+
+The following modifiers may be specified:
+
+* an offset
+
+.. _HOSTCMD:
+
+**HOSTCMD** - Run host command.
+
+The following modifiers may be specified:
+
+* an offset
diff --git a/Documents/README.ContractionTables b/Documents/README.ContractionTables
new file mode 100644
index 0000000..c36eedd
--- /dev/null
+++ b/Documents/README.ContractionTables
@@ -0,0 +1,342 @@
+~~~~~~~~~~~~~~~~~~
+Contraction Tables
+~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+Files with names of the form ``*.ctb`` are contraction tables, and with names
+of the form ``*.cti`` are contraction subtables. They are used by BRLTTY to
+translate character sequences on the screen into their corresponding contracted
+braille representations.
+
+Contraction tables can usually be found in the ``/etc/brltty/Contraction/``
+directory (see |README.Customize| for more details). SEe
+`Contraction Table list`_ for a list of BRLTTY's contraction tables.
+
+A contraction table consists of a sequence of directives, one per line, that 
+define how character sequences are to be represented in contracted braille.
+UTF-8 character encoding must be used. Whitespace (blanks, tabs) at the
+beginning of a line, as well as before and/or after any operand, is ignored.
+Lines containing only whitespace are ignored. If the first non-whitespace
+character of a line is ``#`` then that line is a comment and is ignored.
+
+Contraction Directives
+======================
+
+The format of a contraction directive is:
+
+.. parsed-literal:: *directive* *operand* ... *comment*
+
+Each directive has a specific number of operands. Any text beyond the last 
+operand of a directive is interpreted as a comment. This commenting capability
+is often used to list some of the words that make the directive necessary.
+
+The order of the directives within a contraction table is, in general, anything
+that is convenient for its maintainer(s). A directive that defines an entity,
+e.g. `The Class Directive`_, must precede all references to that entity.
+
+Directives that match character sequences are automatically rearranged such
+that longer sequences are matched first. If more than one directive matches the
+same character sequence then their original table ordering is maintained.
+
+Whenever a character needs to be written, its representation as defined via
+`The Always Directive`_ is used. In principle, this means that every character
+within the *representation* operand of every contraction directive should be
+explicitly defined via `The Always Directive`_. If a character's representation
+hasn't been defined then the Unicode Replacement Character (U+FFFD) is used -
+if it's representation hasn't been defined then all eight dots are used.
+
+The Literal Directive
+---------------------
+
+.. parsed-literal:: literal *characters*
+
+Translate the entire whitespace-bounded text containing the character sequence
+into computer braille.
+
+The Always Directive
+--------------------
+
+.. parsed-literal:: always *characters* *representation*
+
+Unconditionally translate the characters no matter where they appear. If
+there's only one character, then, in addition, define the default
+representation for that character.
+
+The Repeatable Directive
+------------------------
+
+.. parsed-literal:: repeatable *characters* *representation*
+
+Unconditionally translate the characters no matter where they appear. Ignore
+any consecutive repetitions of the same sequence.
+
+The LargeSign Directive
+-----------------------
+
+.. parsed-literal:: largeSign *characters* *representation*
+
+Unconditionally translate the characters no matter where they appear. Remove
+whitespace between consecutive words matched by this directive.
+
+The LastLargeSign Directive
+---------------------------
+
+.. parsed-literal:: lastLargeSign *characters* *representation*
+
+Unconditionally translate the characters no matter where they appear. Remove
+preceding whitespace if the previous word was matched by
+`The LargeSign Directive`_.
+
+The Word Directive
+------------------
+
+.. parsed-literal:: word *characters* *representation*
+
+Translate the characters if they're a word.
+
+The JoinWord Directive
+----------------------
+
+.. parsed-literal:: joinWord *characters* *representation*
+
+Translate the characters if they're a word. Remove the following whitespace if
+the first character after it is a letter.
+
+The LowWord Directive
+---------------------
+
+.. parsed-literal:: lowWord *characters* *representation*
+
+Translate the characters if they're a whitespace-bounded word.
+
+The Contraction Directive
+-------------------------
+
+.. parsed-literal:: contraction *characters*
+
+Prefix the characters with a letter sign (see
+`The LetSign Directive`_) if they're a word.
+
+The SufWord Directive
+---------------------
+
+.. parsed-literal:: sufWord *characters* *representation*
+
+Translate the characters if they're either a word or at the beginning of a
+word.
+
+The PrfWord Directive
+---------------------
+
+.. parsed-literal:: prfWord *characters* *representation*
+
+Translate the characters if they're either a word or at the end of a word.
+
+The BegWord Directive
+---------------------
+
+.. parsed-literal:: begWord *characters* *representation*
+
+Translate the characters if they're at the beginning of a word.
+
+The BegMidWord Directive
+------------------------
+
+.. parsed-literal:: begMidWord *characters* *representation*
+
+Translate the characters if they're either at the beginning or in the middle of
+a word.
+
+The MidWord Directive
+---------------------
+
+.. parsed-literal:: midWord *characters* *representation*
+
+Translate the characters if they're in the middle of a word.
+
+The MidEndWord Directive
+------------------------
+
+.. parsed-literal:: midEndWord *characters* *representation*
+
+Translate the characters if they're either in the middle or at the end of a
+word.
+
+The EndWord Directive
+---------------------
+
+.. parsed-literal:: endWord *characters* *representation*
+
+Translate the characters if they're at the end of a word.
+
+The PrePunc Directive
+---------------------
+
+.. parsed-literal:: prePunc *characters* *representation*
+
+Translate the characters if they're part of punctuation at the beginning of a
+word.
+
+The PostPunc Directive
+----------------------
+
+.. parsed-literal:: postPunc *characters* *representation*
+
+Translate the characters if they're part of punctuation at the end of a word.
+
+The BegNum Directive
+--------------------
+
+.. parsed-literal:: begNum *characters* *representation*
+
+Translate the characters if they're at the beginning of a number.
+
+The MidNum Directive
+--------------------
+
+.. parsed-literal:: midNum *characters* *representation*
+
+Translate the characters if they're in the middle of a number.
+
+The EndNum Directive
+--------------------
+
+.. parsed-literal:: endNum *characters* *representation*
+
+Translate the characters if they're at the end of a number.
+
+Character Classes
+=================
+
+The Class Directive
+-------------------
+
+.. parsed-literal:: class *name* *characters*
+
+Define a new character class. A character class may not be used until it has
+been defined.
+
+The After Directive
+-------------------
+
+.. parsed-literal:: after *class* *directive*
+
+The specified directive is further constrained in that the matched character
+sequence must be immediately preceded by a character belonging to the specified
+class. If this directive is used more than once on the same line then the union
+of the characters in all the classes is used.
+
+The Before Directive
+--------------------
+
+.. parsed-literal:: before *class* *directive*
+
+The specified directive is further constrained in that the matched character
+sequence must be immediately followed by a character belonging to the specified
+class. If this directive is used more than once on the same line then the union
+of the characters in all the classes is used.
+
+Indicator Specification
+=======================
+
+The CapSign Directive
+---------------------
+
+.. parsed-literal:: capSign *representation*
+
+Define the symbol which capitalizes a single letter.
+
+The BegCaps Directive
+---------------------
+
+.. parsed-literal:: begCaps *representation*
+
+Define the symbol which begins a block of capital letters within a word.
+
+The EndCaps Directive
+---------------------
+
+.. parsed-literal:: endCaps *representation*
+
+Define the symbol which ends a block of capital letters within a word.
+
+The LetSign Directive
+---------------------
+
+.. parsed-literal:: letSign *representation*
+
+Define the symbol which marks a letter which isn't part of a word.
+
+The NumSign Directive
+---------------------
+
+.. parsed-literal:: numSign *representation*
+
+Define the symbol which marks the beginning of a number.
+
+Special Directives
+==================
+
+The Replace Directive
+---------------------
+
+.. parsed-literal:: replace *characters* *characters*
+
+Translate the first character sequence into the second character sequence.
+The replacement characters are then recontracted.
+
+The Emoji Directive
+-------------------
+
+.. parsed-literal:: emoji *language*
+
+Translate an emoji character sequence into its description in the specified
+language. The language operand is an ISO 639 two-letter language code.
+
+This directive only works if at least ICU version 57 is installed.
+
+This directive relies on data provided by the CLDR package.
+CLDR stands for the Common Locale Data Repository project.
+Knoqwn names for this package include:
+
+* cldr-emoji-annotation
+* unicode-cldr
+
+At the time of this writing, this project is maintained at
+`<https://github.com/unicode-org/cldr>`_.
+
+Standard Directives
+===================
+
+.. include:: nesting-directives.rst
+
+Operands
+========
+
+.. include:: string-operand.rst
+
+The Representation Operand
+--------------------------
+
+The contracted braille representation of a character sequence. Braille cells
+are separated from one another by a minus (``-``) sign. Each braille cell is
+specified as a sequence of one to eight dot numbers. A dot number is a digit
+within the range ``1``-``8`` as defined by the standard braille dot numbering
+convention (see |README.BrailleDots| for details). The special dot number
+``0`` means no dots, and may not be used in conjunction with any other dot
+numbers.
+
+The equals (``=``) sign , when used all by itself, means that the *characters*
+operand of the contraction directive is to be written without any translation.
+
+Contraction Table List
+======================
+
+.. csv-table::
+   :header-rows: 1
+   :file: contraction-table.csv
+
diff --git a/Documents/README.Customize b/Documents/README.Customize
new file mode 100644
index 0000000..9e2d42e
--- /dev/null
+++ b/Documents/README.Customize
@@ -0,0 +1,65 @@
+~~~~~~~~~~~~~~~~~~~
+Local Customization
+~~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+BRLTTY supports the XDG configuration file paradigm. The most important
+capability this offers is an easy way to customize your BRLTTY configuration
+that won't be overwritten during an upgrade, reinstall, etc. The XDG paradigm
+defines standard locations for your own data files that will override BRLTTY's
+provided data files. In other words, you no longer need to modify BRLTTY's
+provided data files in order to make your own local customizations.
+
+The following data files are covered by this feature:
+
+*  The configuration file (``brltty.conf``).
+*  Text tables (``*.ttb``) and subtables (``*.tti``).
+   See |README.TextTables| for details.
+*  Attributes tables (``*.atb``) and subtables (``*.ati``).
+   See |README.AttributesTables| for details.
+*  Contraction tables (``*.ctb``) and subtables (``*.cti``).
+   See |README.ContractionTables| for details.
+*  Key tables (``*.ktb``) and subtables (``*.kti``).
+   See |README.KeyTables| for details.
+
+The standard directory where you should put your customized files is
+``/etc/xdg/brltty/``.
+
+The simplest way to customize a provided data file is to copy it to this
+directory, and then to modify your copy as needed. You can, however, just place
+your changes in your version of the file, and then ``include`` the provided
+file in order to automatically always pick up the latest versions of all the
+unchanged lines.
+
+For example: Let's say that you'd like to add your own key bindings to the 
+``laptop`` keyboard table. To do so, create ``/etc/xdg/brltty/laptop.ktb``, 
+and place lines like these into it::
+
+   bind Key1 COMMAND1
+   bind Key2 COMMAND2
+   ...
+   include laptop.ktb
+
+The ``XDG_CONFIG_HOME`` environment variable defines the base directory in
+which to check before the standard directory is checked. If it's set and not
+empty then the primary override directory is ``$XDG_CONFIG_HOME/brltty/``. If
+it either isn't set or is empty, but the ``HOME`` environment variable is set
+and not empty (the usual case when you're logged in) then the primary override
+directory is ``$HOME/.config/brltty/``. If both the ``XDG_CONFIG_HOME`` and
+``HOME`` environment variables are either not set or empty (the usual case
+during the system boot sequence) then the primary override directory isn't
+defined.
+
+The ``XDG_CONFIG_DIRS`` environment variable defines a colon-separated list of 
+secondary base directories. The ``brltty/`` subdirectory of each of these 
+secondary base directories is BRLTTY's corresponding secondary override 
+directory. So, for example, if ``XDG_CONFIG_DIRS`` is set to ``/a:/b`` then
+BRLTTY will check ``/a/brltty/`` and ``/b/brltty/`` (in that order). If
+``XDG_CONFIG_DIRS`` is set and not empty then ``/etc/xdg/brltty/`` is no longer
+implicitly checked.
+
+For backward compatibility, the original data file override paradigm is also 
+supported. If the ``HOME`` environment variable is set and not empty then the 
+tertiary override directory is ``$HOME/.brltty/``. If it isn't set, or if it's 
+empty, then the tertiary override directory is ``./.brltty/``.
diff --git a/Documents/README.DOS b/Documents/README.DOS
new file mode 100644
index 0000000..5b5de15
--- /dev/null
+++ b/Documents/README.DOS
@@ -0,0 +1,367 @@
+~~~~~~~~~~~~~
+BRLTTY on DOS
+~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Using BRLTTY
+============
+
+Serial Support
+--------------
+
+The default DOS serial driver does not support speeds above 9600 baud. For 
+higher speeds, a more capable serial driver (like ADF) must be used.
+
+USB Support
+-----------
+
+USB isn't supported.
+
+Bluetooth Support
+-----------------
+
+Bluetooth isn't supported.
+
+Unpacking the Archive
+---------------------
+
+If you'd like to unpack the archive on your DOS system but don't have an 
+unzip command on it, you can get one from
+`<ftp://ftp.delorie.com/pub/djgpp/current/v2/unzip32.exe>`_.
+
+If you'd like to unpack the archive into your DOS file system from your Linux
+system then you need to be able to mount your DOS partition on your Linux
+system. If you do this then you must be careful about two things. The first is
+that you must ensure that your DOS system is shut down first so that two
+systems won't be accessing the same partition at the same time. The second is
+that you must mount the partition in a way that prevents the long file names 
+that BRLTTY uses from being converted into those cryptic Windows short file 
+names - the ones that look like ``longna~1.ext``. One way to do this is to use 
+the ``-o nonumtail`` option when mounting your DOS partition. For example::
+
+   mkdir -p /mnt/dos
+   mount -o nonumtail /dev/sda1 /mnt/dos
+
+What you most likely have is an image of the whole hard disk, rather than an 
+image of just the DOS partition. There are a number of ways to mount the DOS 
+partition within the hard disk image. We'll describe some of them here. For our 
+examples, we'll assume that the DOS partition is the first primary partition of 
+a hard disk whose image is in the file ``disk.img``.
+
+A Simple but Dangerous Way
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The simplest, but also the most dangerous, way is to do some simple math and to 
+use an obscure mount option. First, use the ``fdisk`` command to find out where
+the DOS partition starts within the hard disk image. To do this, use the
+command::
+
+   fdisk -l disk.img
+
+You should see output that looks like this::
+
+   Disk disk.img: 1073 MB, 1073741824 bytes, 2097152 sectors
+   Units = sectors of 1 * 512 = 512 bytes
+   Sector size (logical/physical): 512 bytes / 512 bytes
+   I/O size (minimum/optimal): 512 bytes / 512 bytes
+   Disk identifier: 0x00000000
+
+      Device Boot      Start         End      Blocks   Id  System
+   disk.img1   *          63     2062367     1031152+   6  FAT16
+
+From the ``Start`` column, you can see that the DOS partition starts in sector
+63 of the hard disk. Since a sector is a 512-byte block, you need to multiply
+63 by 512 in order to calculate the partition's byte offset::
+
+   63 x 512 = 32256
+
+Now you can mount the DOS partition using that obscure option::
+
+   mount -o offset=32256 disk.img /mnt/dos
+
+But don't forget the afore-mentioned Windows cryptic short file name problem. 
+The command you should really use, therefore, is::
+
+   mount -o offset=32256,nonumtail disk.img /mnt/dos
+
+Before you restart your DOS system, don't forget to first unmount the DOS 
+partition from your Linux system::
+
+   umount /mnt/dos
+
+Using ``kpatrx``
+~~~~~~~~~~~~~~~~
+
+Another way to mount the DOS partition, which is much safer, is to use the 
+``kpartx`` command to create a loop device for the desired partition within the 
+hard disk image. Use a command like this::
+
+   kpartx -v -s -a disk.img
+
+*  The ``-v`` (verbose) option tells ``kpartx`` to be informative, which is the
+   easiest way to find out the name of the loop device that it will create.
+
+*  The ``-s`` (synchronous) option tells ``kpartx`` to wait until the loop
+   device has been created by ``udev`` before returning.
+
+*  The ``-a`` (add) option tells ``kpartx`` to create the loop device.
+
+You should see output like this::
+
+   add map loop0p1 (253:11): 0 2062305 linear /dev/loop0 63
+
+So you now know that the loop device you need to mount is
+``/dev/mapper/loop0p1``::
+
+   mount -o nonumtail /dev/mapper/loop0p1 /mnt/dos
+
+When you're finished, you need to unmount the partition and to remove the loop 
+device::
+
+   umount /mnt/dos
+   kpartx -d disk.img
+
+``DOSIDLE``
+-----------
+
+``DOSIDLE`` is a DOS application that many users run in order to stop their 
+computer or virtual machine from needlessly consuming CPU time when DOS has 
+nothing meaningful to do. If you use it, don't use a "cooling strategy" more
+invasive than ``weak``. In other words, don't specify more than ``-fm1``. Using
+a "cooling strategy" that is too aggressive will cause ``DOSIDLE`` to correctly
+deduce that the system is idle when BRLTTY has nothing to do, but that will
+cause BRLTTY to stop running, too, which isn't what you want.
+
+Using ``dosemu`` on Linux
+-------------------------
+
+If you're using ``dosemu`` on Linux, and the Linux kernel is 3.15, then you'll 
+need to explicitly enable 16-bit segment support. This can be done with the 
+following command::
+
+   echo 1 >/proc/sys/abi/ldt16
+
+If you'd like this setting to take effect automatically each time you reboot,
+then create the file ``ldt16.conf`` in the directory ``/etc/sysctl.d/``, 
+and place the following line in it::
+   
+   abi.ldt16 = 1
+
+Making the Cross Compiler
+=========================
+
+The DOS version of BRLTTY can be compiled on Linux by using a cross-compiler. 
+To create it, get the following archives:
+
+*  `<ftp://ftp.delorie.com/pub/djgpp/current/v2gnu/gcc410s2.zip>`_
+*  `<ftp://ftp.delorie.com/pub/djgpp/current/v2/djcrx203.zip>`_
+
+The ``djcrx203.zip`` archive contains a file named ``cross/howto.32`` that
+explains how to build a cross-compiler for gcc 3.2. You can build one for gcc
+4.1 by applying the same instructions to the ``gcc410s2.zip`` archive (listed
+above) instead of to the ``gcc32s2.zip`` archive (mentioned in the "howto").
+You can also use a more recent ``binutils`` package (2.2.24 works fine).
+
+If, when building the cross-compiler, you encounter an error that complains 
+about the ``writev`` function and the ``iovec`` structure not being defined,
+then you need to upgrade to a newer version of ``djcrx``. Upgrading to
+``djcrx204`` is sufficient. It can be downloaded from
+`<ftp://ftp.delorie.com/pub/djgpp/beta/v2/djcrx204.zip>`_.
+
+Another complexity when building the cross-compiler is that some parts of the 
+process need an older version of ``autoconf`` (2.13) whereas other parts of it
+need a newer version (2.57). Also, the build works best when a specific version
+of ``automake`` (1.9.6) is used.
+
+We've written a script that builds the cross-compiler, and that also takes care
+of all of the idiosyncracies that we encountered when doing so.
+
+The ``mkdostools`` Script
+-------------------------
+
+The ``mkdostools`` script builds the cross-compiler that's needed in order to 
+build BRLTTY for DOS on Linux. It resides in the ``DOS/`` subdirectory of
+BRLTTY's source tree.
+
+Basic Options
+~~~~~~~~~~~~~
+
+.. include:: common-options.rst
+
+Build Directories
+~~~~~~~~~~~~~~~~~
+
+A number of directories are used during the build process. Each of them can be
+explicitly specified via an option, and also has a default subdirectory within
+the directory that contains the script itself. Each of the default
+subdirectories may be a symbolic link that points somewhere else. These
+directories are:
+
+   ========  ======  =========
+   Purpose   Option  Default
+   --------  ------  ---------
+   archives  ``-a``  Archives/
+   build     ``-b``  Build/
+   install   ``-i``  Tools/
+   ========  ======  =========
+
+The **archives** directory is the only one that you need to prepare. It must
+contain all of the archives that are needed in order to build the
+cross-compiler. DJGPP archives have the ``.zip`` extension, and Gnu archives
+have the ``.tar.gz`` extension. See `Required Archives`_ for the list.
+
+The **build** directory will contain all of the files that are intirimly needed
+during the build process. It's created if it doesn't already exist. If it does
+already exist then it's emptied at the start of the build process. It's also
+emptied upon the completion of a successful build. This directory needs to be
+on a volume that has at least 1.5GB of free file space.
+
+The **install** directory is where the cross-compiler tools are to be
+installed. It's created if it doesn't already exist. If it does already exist
+then it's emptied at the start of the build process.
+
+Required Archives
+~~~~~~~~~~~~~~~~~
+
+You'll need the following DJGPP archives:
+
+   ================  ==================================================
+   Archive Name      Download From
+   ----------------  --------------------------------------------------
+   ``djcrx204.zip``  `<ftp://ftp.delorie.com/pub/djgpp/beta/v2>`_
+   ``gcc432s2.zip``  `<ftp://ftp.delorie.com/pub/djgpp/current/v2gnu>`_
+   ================  ==================================================
+
+You'll need the following Gnu archives:
+
+   =========================  ====================================
+   Archive Name               Download From
+   -------------------------  ------------------------------------
+   ``autoconf-2.13.tar.gz``   `<http://ftp.gnu.org/gnu/autoconf>`_
+   ``autoconf-2.57.tar.gz``   `<http://ftp.gnu.org/gnu/autoconf>`_
+   ``automake-1.9.6.tar.gz``  `<http://ftp.gnu.org/gnu/automake>`_
+   ``binutils-2.24.tar.gz``   `<http://ftp.gnu.org/gnu/binutils>`_
+   ``gcc-4.3.2.tar.gz``       `<http://gcc.gnu.org/mirrors.html>`_
+   =========================  ====================================
+
+If you'd prefer to build a different version of gcc, there are two important
+things to know. One is that you need the ``gcc*s2.zip`` archives from 
+DJGPP. The other is that the versions of the Gnu and DJGPP gcc archives must 
+match. If, for example, you'd like to build gcc-4.1.2, then you'll need 
+both ``gcc-4.1.2.tar.gz`` and ``gcc412s2.zip``. The reason we use gcc-4.3.2 in
+our examples here is because it's the highest version of gcc for which we could
+find an ``s2.zip`` DJGPP archive.
+
+If you only have one Gnu archive for gcc in your **archives** directory then
+that version will be built. If you have more than one then you'll need to use
+the ``-g`` (gcc) option (e.g. ``-g 4.3.2``) to explicitly specify the version
+that is to be built.
+
+Building BRLTTY
+===============
+
+Configuring the Build
+---------------------
+
+Before configuring BRLTTY, you must add the ``bin/`` subdirectory of the 
+cross-compiler tools to your command search path. If, for example, the
+cross-compiler is installed in ``/usr/local/dostools``, then add its tools to
+your command search path with a command like this::
+
+   export PATH="/usr/local/dostools/bin:$PATH"
+
+You'll also need to ensure that gcc's ``-fgnu89-inline`` option is used. This
+is done by setting the ``CFLAGS`` environment variable before configuring. For 
+example::
+
+   export CFLAGS="-fgnu89-inline"
+
+You should be able to use a configure command like this one::
+
+   ./configure \
+      --prefix=/brltty-dos --host=i586-pc-msdosdjgpp \
+      --enable-relocatable-install \
+      --disable-api --disable-icu --disable-x \
+      --without-usb-package --without-bluetooth-package \
+      --without-libbraille --with-braille-driver=-vr,all \
+      --without-espeak --without-espeak-ng \
+      --without-flite --without-speechd \
+      --with-speech-driver=all \
+      --with-screen-driver=pb,-all
+
+The ``cfg-dos`` Script
+~~~~~~~~~~~~~~~~~~~~~~
+
+We provide a script named ``cfg-dos``, which resides in the top-level directory
+of BRLTTY's source tree, that should make configuring BRLTTY for DOS a bit
+easier. It runs the ``configure`` script for you, giving it all of the options
+that are required for a successful DOS build. You can specify additional
+``configure`` options (although that shouldn't be necessary) simply by giving
+them to ``cfg-dos``. The only thing you do need to do is to point the
+``DOS_TOOLS_ROOT`` environment variable to the top-level directory of your 
+cross-compiler tools. For example::
+
+   export DOS_TOOLS_ROOT=/usr/local/dostools
+
+The ``mkdosarc`` Script
+-----------------------
+
+The ``mkdosarc`` script, which resides in the ``DOS/`` subdirectory of BRLTTY's
+source tree, creates a DOS archive (a ``.zip`` file) of BRLTTY. It does
+everything (``configure``, ``make``, ..., ``zip``) except for building the
+cross-compiler. If you don't already have a cross-compiler for DOS then see
+`Making the Cross Compiler`_ for instructions on how to build one.
+
+Before running this script, ensure that the following commands have been 
+installed on your system:
+
+*  linuxdoc
+*  unix2dos
+*  zip
+
+You'll also need to point the ``DOS_TOOLS_ROOT`` environment variable to the 
+top-level directory of the cross-compiler tools. For example::
+
+   export DOS_TOOLS_ROOT=/usr/local/dostools
+
+Parameters
+~~~~~~~~~~
+
+The script requires only one parameter - the path to the top-level directory of
+BRLTTY's source tree. If, for example, you're in the ``DOS/`` subdirectory of 
+BRLTTY's source tree (where the script resides), then invoke it like this::
+
+   ./mkdosarc ..
+
+If you're in the top-level directory of BRLTTY's soruce tree then invoke it
+like this::
+
+   DOS/mkdosarc .
+
+Options
+~~~~~~~
+
+``-a`` (archive)
+   Specify the name of the archive (``.zip``) file that will be created. The
+   default archive name is ``brltty-dos``.
+
+``-o`` (overwrite)
+   Allow an already-existing archive to be overwritten. The default is that an
+   already-existing archive won't be overwritten.
+
+``-i`` (install)
+   Specify the name of the subdirectory on the target DOS system into which the
+   archive will be unpacked. Its default name is ``BRLTTY``. It will be an
+   immediate subdirectory of the current working directory when the archive is
+   being unpacked. It will be created if it doesn't already exist.
+
+``-s`` (shell)
+   Invoke an interactive shell just before the archive is created so that you
+   can inspect and/or modify what will be in the archive. The current working
+   directory is set to the top-level directory of the content.
+   The shell specified by the ``SHELL`` environment variable is used. If it
+   isn't set then ``/bin/sh`` is assumed.
+
+.. include:: common-options.rst
+
diff --git a/Documents/README.Devices b/Documents/README.Devices
new file mode 100644
index 0000000..282a549
--- /dev/null
+++ b/Documents/README.Devices
@@ -0,0 +1,290 @@
+~~~~~~~~~~~~~~~~~~
+Device Identifiers
+~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+The general syntax of a device identifier is:
+
+.. parsed-literal:: qualifier:parameter=value+...
+
+More specifically, with [brackets] meaning optional, the syntax is:
+
+.. parsed-literal:: [qualifier:][parameter=]value[+parameter=value]...
+
+The qualifier specifies which class of device is being identified.
+It is separated from the rest of the device identifier by a colon (``:``).
+It may be one of:
+
+   ==========  =======================
+   Qualifier   Assumed First Parameter
+   ----------  -----------------------
+   serial:     name=
+   usb:        serialNumber=
+   bluetooth:  address=
+   hid:        address=
+   null:
+   ==========  =======================
+
+The qualifier is case insensitive, and any abbreviation may be used.
+For backward compatibility (to when a device identifier was
+strictly interpreted as the absolute path to a serial device),
+the qualifier (along with its colon delimiter) is optional,
+and, if it isn't supplied, ``serial`` is assumed.
+
+One or more parameters may be supplied after the qualifier.
+Each parameter is specified as its name and its value,
+in that order, separated by an equals (``=``) sign.
+Parameters are separated from one another by a plus (``+``) sign.
+Parameter names are case insensitive, and any abbreviation may be used.
+Supplying a zero-length value means to use the parameter's default value.
+
+For backward compatibility
+(to when device identifier parameters weren't supported),
+the first supplied parameter is special
+in that its name (along with its equals sign delimiter) is optional.
+If the name of the first parameter isn't supplied then the
+class-dependent parameter that most specifically identifies the device is assumed.
+See the table above for which parameter is assumed for each class.
+
+It isn't possible to supply no parameters at all.
+Specifying only the qualifier is equivalent to also specifying
+a zero-length value for the assumed first parameter for that class.
+For example, specifying ``usb:`` is equivalent to specifying ``usb:serialNumber=``.
+
+An example to illustrate the optionality of both the qualifier
+and the name of the first supplied parameter:
+
+* On Linux, the name of the first serial device is ``ttyS0``.
+  All of the following device identifiers refer to the same serial device::
+
+    serial:name=ttyS0
+    serial:ttyS0
+    ttyS0
+    name=ttyS0
+
+Serial Device Identifiers
+-------------------------
+
+Serial device identifiers support the following parameters:
+
+   ===============  =========================================================
+   Name             Value
+   ---------------  ---------------------------------------------------------
+   ``name``         name of, relative path to, or absolute path to the device
+   ``baud``         the data transmission/reception rate (bits per second)
+   ``dataBits``     the number of data bits per character
+   ``stopBits``     the number of stop bits per character
+   ``parity``       ``none``, ``odd``, ``even``, ``space``, ``mark``
+   ``flowControl``  ``none``, ``hardware``
+   ===============  =========================================================
+
+All of the parameters are optional,
+although the ``name=`` parameter should be supplied.
+It shouldn't normally be necessary to supply any of the others,
+especially since, in most cases, the braille driver overrides them.
+
+``name=``
+   Specify the host device to use. It must be the name of the device, the
+   relative path to the device, or the absolute path to the device. If it's either
+   a name or a relative path then the host's device directory (usually ``/dev``
+   on Unix-based platforms) is used. If this parameter isn't supplied then the
+   primary (first) serial device is assumed.
+
+``baud=``
+   Specify the speed, in bits per second, at which to communicate with the
+   device. It must be a positive integer. Typical values are: ``9600``, ``19200``,
+   ``38400``, ``57600``, ``115200``. If this parameter isn't supplied then
+   ``9600`` is assumed.
+
+``dataBits=``
+   Specify the number of bits to use to represent a character, not including
+   any metadata (start bit, stop bit(s), parity, etc). It must be a positive integer,
+   and is usually within the range ``5``-``8``. If this parameter isn't
+   supplied then ``8`` is assumed.
+
+``stopBits=``
+   Specify the number of stop bits to inject after a character has been
+   transmitted and to expect after a character has been received. It must be
+   a positive integer, and is usually either ``1`` (for higher speeds) or ``2`` (for
+   lower speeds). If this parameter isn't supplied then ``1`` is assumed.
+
+``parity=``
+   Specify the type of error detection to use. It must be one of: ``none``,
+   ``odd``, ``even``, ``space``, ``mark``. If this parameter isn't supplied
+   then ``none`` is assumed.
+
+``flowControl=``
+   Specify the kind of flow control to use. It must be one of: ``none``,
+   ``hardware``. If this parameter isn't supplied then ``none`` is assumed.
+
+USB Device Identifiers
+----------------------
+
+USB device identifiers support the following parameters:
+
+   ======================  ===================================================
+   Name                    Value
+   ----------------------  ---------------------------------------------------
+   ``serialNumber=``       one or more arbitrary characters
+   ``vendorIdentifier=``   an integer within the range 1-65535 (0X0001-0XFFFF)
+   ``productIdentifier=``  an integer within the range 1-65535 (0X0001-0XFFFF)
+   ``genericDevices=``     ``yes``, ``no``
+   ======================  ===================================================
+
+All of the parameters are optional. It shouldn't normally be necessary to
+supply any of them.
+
+``serialNumber=`` 
+   Specify the serial number that the device must have. The match is case
+   sensitive and must be exact. If this parameter isn't supplied then the
+   serial number of the device isn't verified.
+
+``vendorIdentifier=``
+   Specify the vendor identifier that the device must have. It must be an
+   integer within the range ``1``-``65535`` (or, in hexadecimal,
+   ``0X0001``-``0XFFFF``). If this parameter isn't supplied then the vendor
+   identifier of the device isn't verified.
+
+``productIdentifier=``
+   Specify the product identifier that the device must have. It must be an
+   integer within the range ``1``-``65535`` (or, in hexadecimal,
+   ``0X0001``-``0XFFFF``). If this parameter isn't supplied then the product
+   identifier of the device isn't verified.
+``genericDevices=``
+   Specify whether or not a device that has a generic vendor/product identifier
+   pair may be used. It's value must be either ``yes`` or ``no``. The check is
+   case insensitive, and any abbreviation may be used. If this parameter isn't
+   supplied then ``yes`` is assumed.
+
+Bluetooth Device Identifiers
+----------------------------
+
+Bluetooth device identifiers support the following parameters:
+
+   =============  =============================================================
+   Name           Value
+   -------------  -------------------------------------------------------------
+   ``address=``   six two-digit hexadecimal numbers separated by ``:`` or ``-``
+   ``name=``      one or more arbitrary characters
+   ``channel=``   an integer within the range ``1``-``30``
+   ``discover=``  ``yes``, ``no``
+   ``timeout=``   an integer within the range ``1``-``59``
+   =============  =============================================================
+
+All of the parameters are optional except that
+the ``address=`` parameter is required on platforms where
+BRLTTY can't yet detect a Bluetooth device based on its name.
+It shouldn't normally be necessary to supply any of the others.
+
+BRLTTY can detect a Bluetooth device by name on the following platforms:
+
+* Android
+* Linux (if the Bluetooth version is at least 5)
+* Windows
+
+``address=``
+   Specify the MAC address of the device. It must be six two-digit
+   hexadecimal numbers (the "letter" digits may be in either case) separated
+   from one another by either a colon (``:``) or a minus (``-``) sign.
+
+``name=``
+   Specify the name of the device. It must be one or more
+   arbitrary characters. They need only match the beginning (so not
+   necessarily all) of the name. Case is significant.
+   Only the names of paired devices are considered.
+   This parameter is ignored if the ``address=`` parameter has been specified.
+
+``channel=``
+   Specify the RFCOMM channel to use. It must be an integer within the range
+   ``1``-``30``. If this parameter isn't supplied then either a driver-supplied
+   default channel number is assumed or service discovery is performed.
+
+``discover=``
+   Specify whether or not service discovery is to be performed. In other words,
+   this parameter specifies whether or not the device is to be "asked" for the
+   RFCOMM channel number. It must be either ``yes`` or ``no``. The check is
+   case insensitive, and any abbreviation may be used. If this parameter isn't
+   supplied then whether or not service discovery is performed is decided
+   according to the following sequence of tests:
+
+   1) If the ``channel=`` parameter has been supplied, then **no**.
+   2) If the driver has requested service discovery, then **yes**.
+   3) If the driver has specified a default channel number, then **no**.
+   4) Otherwise, **no**.
+
+``timeout=``
+   Specify the number of seconds to wait for a connection to the device to be
+   acquired. It must be an integer within the range ``1``-``59``. If this
+   parameter isn't supplied then a reasonable default is assumed.
+
+HID Device Identifiers
+----------------------
+
+HID device identifiers support the following parameters:
+
+   =================  ============================
+   Name               Value
+   -----------------  ----------------------------
+   ``address=``       Bluetooth MAC address
+   ``name=``          Bluetooth device name
+   ``manufacturer=``  USB device manufacturer
+   ``description=``   USB product description
+   ``serialNumber=``  USB serial number
+   ``vendor=``        four hexadecimal digits
+   ``product=``       four hexadecimal digits
+   =================  ============================
+
+All of the parameters are optional, although, in most cases,
+either the ``serialNumber=`` parameter (for a USB device)
+or the ``address=`` parameter (for a Bluetooth device)
+should be supplied.
+USB-specific and Bluetooth-specific parameters may not be combined.
+
+``address=``
+   The MAC address of a Bluetooth device.
+   It must be six two-digit hexadecimal numbers
+   separated from one another by a colon (``:``).
+   The letter digits may be in either case.
+
+``name=``
+   The name of a Bluetooth device.
+   Only the start of the name is matched.
+   Matching is case insensitive.
+
+``manufacturer=``
+   The name of the manufacturer of a USB device.
+   Only the start of the name is matched.
+   Matching is case insensitive.
+
+``description=``
+   The product description of a USB device.
+   Only the start of the description is matched.
+   Matching is case insensitive.
+
+``serialNumber=``
+   The serial number of a USB device.
+   Only the start of the serial number is matched.
+   Matching is case insensitive.
+
+``vendor=``
+   The vendor identifier of the device.
+   It must be four hexadecimal digits.
+   The letter digits may be in either case.
+
+``product=``
+   The product identifier of the device.
+   It must be four hexadecimal digits.
+   The letter digits may be in either case.
+
+Null Device Identifiers
+-----------------------
+A ``null`` endpoint has the following properties:
+
+* It always indicates that input is available.
+* Reading from it always yields end-of-file.
+* Writing to it is always successful but does nothing.
+
diff --git a/Documents/README.Introduction b/Documents/README.Introduction
new file mode 100644
index 0000000..19e1874
--- /dev/null
+++ b/Documents/README.Introduction
@@ -0,0 +1,351 @@
+~~~~~~~~~~~~~~~~~~~~~~
+Introduction to BRLTTY   
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+You can look up a full listing of all of your braille device's key bindings at:
+`<https://brltty.app/doc/KeyBindings/index.html>`_
+
+With one exception, this document applies to using BRLTTY with any braille device.
+The exception is that, when key bindings are given,
+it assumes that the braille device has a braille keyboard.
+If it doesn't, then, as mentioned above,
+you can look up your device's corresponding key bindings online.
+
+Many *chords* (combining a dot key combination with the Space key) are toggles.
+Repeatedly pressing any of these key bindings alternately turns the corresponding feature on and off.
+Adding the Dot7 key forces the feature to be turned off,
+and adding the Dot8 key forces the feature to be turned on.
+
+Useful Key Bindings
+===================
+
+Space + h (dots 125)
+  Go to the help screen.
+  It's a toggle, so pressing it again returns to the actual screen.
+  This screen is only shown on the braille display.
+  It contains a full listing of all of your braille device's key bindings.
+
+Space + l (dots 123)
+  Enter learn mode.
+  It's a toggle, so pressing it again leaves learn mode.
+  Additionally, if you don't do anything for 10 seconds then learn mode is automatically terminated.
+  When in learn mode, pressing any key combination shows, on the braille display, what it'll do without actually doing it.
+
+Space + p (dots 1234)
+  Go to `the preferences menu`_.
+  It's a toggle, so pressing it again returns to the actual screen.
+  Adding the Dot8 key saves the current settings.
+  Adding the Dot7 key restores the settings as of the most recent time that they were saved.
+
+Space + g (dots 1245)
+  Toggle between computer braille and contracted braille.
+  Adding the Dot7 key forces computer braille,
+  and adding the Dot8 key forces contracted braille.
+
+Space + t (dots 2345)
+  Toggle cursor tracking on/off.
+  When cursor tracking is on, any motion of the cursor drags the braille display to the new screen location.
+
+Space + i (dots 24)
+  Toggle the skipping of identical lines on/off.
+  This feature is especially useful for compressing multiple contiguous blank lines into a single one.
+
+Space + b (dots 12)
+  Toggle the skipping of blank braille windows on/off.
+  This feature is especially useful for skipping over the blank ends of short lines when panning.
+
+The Preferences Menu
+====================
+
+The preferences menu contains a lot of settings that let you customize how BRLTTY behaves.
+It's internal to BRLTTY so it doesn't show up on the actual screen -
+it's only presented on the braille display.
+
+The Dot Keys
+------------
+
+The dot keys can be used to navigate the preferences menu:
+
+Dots 1 and 4
+  In the same way as they (when combined with the Space key) move the cursor up/down when using a conventional screen reader,
+  these dot keys move up/down within the current submenu.
+  In other words, Dot1 moves to the previous item and Dot4 moves to the next item.
+
+Dots 3 and 6
+  In a similar way to how they (when combined with the Space key) move the cursor left/right when using a conventional screen reader,
+  these dot keys select the previous/next possible setting for the current item.
+  The setting choices wrap,
+  so Dot3, when the first choice is selected, wraps to the last one,
+  and Dot6, when the last choice is selected, wraps to the first one.
+
+Dot 2
+  Move to the first item.
+
+Dot 5
+  Move to the last item.
+
+Dot 7
+  Go up one submenu level.
+  If in the main menu then exit and return to the actual screen.
+
+Dot 8
+  Exit and return to the actual screen.
+
+The Routing Keys
+----------------
+
+The routing keys can be used to change the setting for the current item:
+
+* If the setting is a discrete set of choices, e.g. ``Yes`` or ``No``,
+  then each successive group of routing keys selects those choices.
+  For example:
+
+  * If there are two choices
+    then keys 1,3,5,... select the first one
+    and keys 2,4,6,... select the second one.
+
+  * If there are three choices
+    then keys 1,4,7,... select the first one,
+    keys 2,5,8,... select the second one,
+    and keys 3,6,9,... select the third one.
+
+* If the setting is a number then the routing keys behave like a scroll bar.
+
+  * The leftmost key selects the lowest possible value.
+  * The rightmost key selects the highest possible value.
+  * The inner keys select an evenly distributed value within the range.
+
+Exiting the Preferences Menu
+----------------------------
+
+These are the ways to exit the preferences menu:
+
+Dot7
+  Go up one level when in the main menu.
+
+Dot8
+  Exit the preferences menu.
+
+Space + p  (dots 1234)
+  Preferences Menu enter/exit toggle.
+
+Space + p (dots 1234) + Dot8
+  Save the current settings.
+
+None of these methods close `Submenus`_.
+This means that going back to the preferences menu later resumes at the same place.
+
+The ``Save on Exit`` item is at the top of the main menu.
+It specifies whether or not (``Yes`` or ``No``)
+the current settings are to be saved when exiting the preferences menu.
+If it's set to ``No`` then you can still manually save the current settings with Space + p (dots 1234) + Dot8.
+
+Submenus
+--------
+
+The preferences menu is a tree structure of menus.
+The one at the root (or top) is known as the main menu.
+
+If an item ends with an ASCII right arrow (``-->``)
+then it's the name of a submenu.
+For example::
+
+  Braille Presentation -->
+
+Pressing Dot3, Dot6, or any routing key on this kind of item opens the submenu.
+If it's closed then the braille display is placed on the first item.
+If it's open then the braille display is placed on the current item.
+
+The first item of each submenu always is
+an ASCII left arrow (``<--``),
+followed by the word ``Close``,
+followed by the name of the submenu.
+For example::
+
+  <-- Close Braille Presentation
+
+Pressing Dot3, Dot6, or any routing key on this kind of item closes the submenu.
+
+Another way to leave a submenu is to press Dot7.
+Leaving a submenu this way leaves it open.
+
+Additional Notes
+----------------
+
+The default is that only relevant items are shown.
+In the Braille Presentation submenu, for example,
+Computer Braille Cell Type is only shown when Braille Variant is set to Computer,
+and Expand Current Word is only shown when Braille Variant is set to Contracted.
+This can be changed by setting Show All Items (in the Menu Options submenu) to Yes.
+
+Table selection changes are currently not saved along with the other settings.
+These include:
+
+* The current text (computer braille) table.
+* The current attributes table.
+* The current contraction table.
+* The current keyboard table.
+
+The Braille Keyboard
+====================
+
+Dots 7 and 8
+------------
+
+Dots 7 and 8 are special because
+the Dot7 key is Backspace and the Dot8 key is Enter.
+If either of these keys is pressed together with other dot keys then it functions as itself.
+If you need to type either just dot 7 or just dot 8
+then combine the corresponding key with the Space key.
+So:
+
+Dot7
+  Press the Backspace key.
+
+Dot8
+  Press the Enter key.
+
+Space + Dot7
+  Type dot 7.
+
+Space + Dot8
+  Type dot 8.
+
+Typing
+------
+
+Characters are typed by pressing either the Space key or any combination of the dot keys.
+This can be disabled in order to avoid inadvertently typing a character by accidentally pressing keyboard keys.
+
+Space + Dots13 + Dot7
+  Disable typing.
+
+Space + Dots13 + Dot8
+  Enable typing.
+
+The following typing modes are supported:
+
+Space + Dots46 + Dot7
+  Switch to interpreting the characters being typed as regular text.
+  The dot combination is back-translated through the currently selected text (computer braille) table.
+  This is the default.
+
+Space + Dots46 + Dot8
+  Switch to interpreting the characters being typed as Unicode braille patterns.
+
+Special Keys
+------------
+
+The special keys on a normal keyboard can be individually pressed as follows:
+
+Space + Dots45 (conventional screen reader binding)
+  The Forward Tab key.
+
+Space + Dot3 (conventional screen reader binding)
+  The Cursor Left key.
+
+Space + Dot6 (conventional screen reader binding)
+  The Cursor Right key.
+
+Space + Dot1 (conventional screen reader binding)
+  The Cursor Up key.
+
+Space + Dot4 (conventional screen reader binding)
+  The Cursor Down key.
+
+Space + Dots23 (conventional screen reader binding)
+  The Page Up key.
+
+Space + Dots56 (conventional screen reader binding)
+  The Page Down key.
+
+Space + Dot2
+  The Home key.
+
+Space + Dot5
+  The End key.
+
+Space + Dots35 (low i)
+  The Insert key.
+
+Space + Dots256 (low d)
+  The Delete key.
+
+Space + Dots26 (low e)
+  The Escape key.
+
+Space + a routing key
+  In most cases, a function key can be pressed by pressing the corresponding routing key along with the Space key.
+  For example, the ``F1`` key can be pressed by pressing Space + the leftmost routing key.
+
+Modifier Keys
+-------------
+
+A subset of the modifier keys, the most common being Control and Left Alt, have been defined for most braille devices.
+Especially for those braille devices for which no modifier keys have been defined,
+but also for those for which only some of them have been defined,
+a general scheme for supporting sticky modifiers is provided.
+
+The key combinations for these modifiers all consist of Space + Dot8 + one of the other dot keys.
+They are as follows:
+
+Space + Dot8 + Dot1
+  The GUI (Windows, Command) key.
+
+Space + Dot8 + Dot4
+  The Shift key.
+
+Space + Dot8 + Dot2
+  The Left Alt (Meta) key.
+
+Space + Dot8 + Dot5
+  The Right Alt (AltGr) key.
+
+Space + Dot8 + Dot3
+  The Control key.
+
+Space + Dot8 + Dot6
+  The Uppercase key.
+
+Space + Dot8 + Dot7
+  Unlock and clear all of the modifiers.
+  This is especially useful when you forget how the various modifiers have been set.
+
+Each modifier can be cycled through these three states:
+
+First Press
+  Temporarily set the modifier.
+  It'll only apply to the next typed character.
+
+Second Press
+  Lock the modifier.
+  It'll apply to all typed characters until it's cleared.
+
+Third Press
+  Unlock and clear the modifier.
+
+What does BRLTTY mean?
+======================
+
+Since people are asking, let's answer it here.
+
+The ``BRL`` part means **braille**.
+The ``TTY`` part actually does mean **tty**, which needs a bit of an explanation.
+The most important thing to know, though, is that it has nothing at all to do with the ``tty`` devices that deaf people use to communicate over phone lines.
+
+BRLTTY began its life on the Linux platform.
+That platform has virtual consoles, which, among other things,
+allow multiple, simultaneous login sessions.
+These virtual consoles, way back in the early days, emulated serially-connected terminals.
+They can still do that, of course, but, these days, they can do a whole lot more.
+
+Serially-connected terminals, especially those that used paper (as opposed to a screen), were commonly known as teletypes (or teletypewriters).
+That's where the term ``tty`` comes from.
+This may well also be the reason that the devices that deaf people use to communicate over hone lines are called ``ttys``,
+i.e. they, too, behave very much like serially-connected terminals.
+
+For a historical perspective regarding the term ``tty``, you might read:
+`<https://en.wikipedia.org/wiki/Teleprinter>`_
+
diff --git a/Documents/README.KeyTables b/Documents/README.KeyTables
new file mode 100644
index 0000000..c41a6de
--- /dev/null
+++ b/Documents/README.KeyTables
@@ -0,0 +1,614 @@
+~~~~~~~~~~
+Key Tables
+~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+Files with names of the form ``*.ktb`` are key tables, and with names of the
+form ``*.kti`` are key subtables. They are used to bind braille device and
+computer keyboard key combinations to BRLTTY commands.
+
+*  Braille device key tables can usually be found in the
+   ``/etc/brltty/Input/xx/`` directory, where ``xx`` is the two-letter braille
+   driver identification code (see `Braille Driver List`_). The name of a
+   braille device key table identifies the model(s) for which it is used. The
+   driver selects which key table is to be used.
+
+*  Computer keyboard key tables can usually be found in the
+   ``/etc/brltty/Keyboard/`` directory. The name of a keyboard key table
+   describes the kind of keyboard for which it has been designed. See
+   `Keyboard Table List`_ for a list of BRLTTY's keyboard tables.
+
+*  See |README.Customize| for more details regarding key table locations.
+
+A key table consists of a sequence of directives, one per line, which define 
+how keys and key combinations are to be interpreted. UTF-8 character encoding 
+must be used. Whitespace (blanks, tabs) at the beginning of a line, as well as 
+before and/or after any operand, is ignored. Lines containing only whitespace 
+are ignored. If the first non-whitespace character of a line is ``#`` then
+that line is a comment and is ignored.
+
+The precedence for resolving each key press/release event is as follows:
+
+1) A hotkey press or release defined within the current context.
+2) A key combination defined within the current context.
+3) A braille keyboard command defined within the current context.
+4) A key combination defined within the default context.
+
+Directives
+==========
+
+.. |key operand| replace::
+   The name of the key whose availability is to be tested.
+
+.. |platform operand| replace::
+   The name of a host platform.
+   The following platform names are recognized:
+   android, apple, cygwin, dos, grub, linux,
+   mingw32, mingw64, openbsd, sun, windows.
+
+The Bind Directive
+------------------
+
+.. parsed-literal:: bind *keys* *commands*
+
+Use this directive to define which BRLTTY command is executed when a particular
+combination of keys is pressed. The binding is defined within the current
+context.
+
+*keys*
+   The key combination which is to be bound. It's a sequence of one or more key
+   and/or key group names separated by plus (``+``) signs. The final (or only)
+   key name may be optionally prefixed with an exclamation (``!``) point. For
+   example::
+
+      key1
+      group1
+      key1+key2
+      key1+key2+group1
+      !group1
+      key1+!key2
+
+   A key group name refers to any key within the named group. A specific key
+   within the group may be identified by appending a dot (``.``) and a number
+   to the group's name. The first key within a group is key #1 (e.g.
+   ``keyGroupName.1``).
+
+   The keys may be pressed in any order, with the exception that if the final
+   key name is prefixed with an exclamation point then it must be pressed last.
+   The exclamation point prefix means that the primary command (see below) is
+   executed as soon as that key is pressed. If not used, the primary command is
+   executed as soon as any of the keys is released.
+
+*commands*
+   The primary and secondary BRLTTY commands that are to be bound to the key
+   combination. The two commands (primary first and secondary last) are
+   separated from one another by a colon (``:``). Both commands are optional.
+   If just a primary command is being bound then the colon needn't be supplied.
+   All of these are valid:
+
+   *  bind key primaryCommand
+   *  bind key primaryCommand:
+   *  bind key primaryCommand:secondaryCommand
+   *  bind key :secondaryCommand
+   *  bind key :
+
+   The primary command is executed as described above. The secondary command is
+   executed if the key combination is held for a while (i.e. when a long key
+   press is detected). If the **autorepeat** feature is enabled then the
+   secondary command is autorepeated if it has been defined, else the primary
+   command is autorepeated.
+
+   For a list of commands, see |README.CommandReference|.
+
+.. _Bind Command Modifiers:
+
+One or more modifiers may be optionally appended to a command name by using a 
+plus (``+``) sign as the separator. For example::
+
+   bind key command
+   bind key command+modifier1
+   bind key command+modifier1+modifier2
+
+For commands which enable/disable a feature:
+
+*  If the modifier ``+on`` is specified then the feature is enabled.
+*  If the modifier ``+off`` is specified then the feature is disabled.
+*  If neither ``+on`` nor ``+off`` is specified then the state of the feature
+   is toggled on/off.
+
+For commands which move the braille window:
+
+*  If the modifier ``+route`` is specified then, if necessary, the screen
+   cursor is automatically routed so that it's always visible on the braille
+   display.
+
+For commands which move the braille window to a specific line on the screen:
+
+*  If the modifier ``+toleft`` is specified then the braille window is also
+   moved to the beginning of that line.
+*  If the modifier ``+scaled`` is specified then the key group bound to the 
+   command is interpreted as though it were a scroll bar. If it isn't
+   specified then there's a one-to-one correspondence between keys and lines.
+
+For commands which require an offset:
+
+*  The modifier ``+offset``, where ``offset`` is a non-negative integer, may be
+   specified. If it isn't specified then ``+0`` is assumed.
+
+For commands which input any keyboard key (print or braille): 
+
+*  The ``+shift`` modifier adds the shift key.
+*  The ``+control`` modifier adds the control key.
+*  The ``+meta`` modifier adds the left alt (or meta) key.
+*  The ``+altgr`` modifier adds the right alt (or altgr) key.
+*  The ``+gui`` modifier adds the gui (or windows) key.
+
+For commands which input characters (print or braille):
+
+*  The modifier ``+upper`` converts a lowercase character to uppercase.
+
+For commands which input braille characters:
+
+*  The modifiers ``+dot1`` through ``+dot8`` add those dots to the character.
+*  The modifier ``+space`` adds the space bar (or "chord" key) to the
+   character.
+
+For commands which input keyboard scancodes:
+
+*  The ``+release`` modifier means that it's a key release scancode. If it
+   isn't specified then it's a key press scancode (unless, of course, the
+   scancode itself indicates something else).
+*  The ``+emul0`` modifier means that it's an emulation mode 0 scancode.
+*  The ``+emul1`` modifier means that it's an emulation mode 1 scancode.
+
+Examples::
+
+   bind Key1 CSRTRK
+   bind Key1+Key2 CSRTRK+off
+   bind Key1+Key3 CSRTRK+on
+   bind Key4 TOP
+   bind Key5 TOP+route
+   bind VerticalSensor GOTOLINE+toleft+scaled
+   bind Key6 CONTEXT+context1
+
+The Context Directive
+---------------------
+
+.. parsed-literal:: context *name* *title*
+
+Use this directive to define alternate ways to interpret certain key events
+and/or combinations.
+Switching to another context is done via the BRLTTY command CONTEXT+name.
+
+A context contains definitions created by any of:
+
+*  `The Bind Directive`_
+*  `The Hotkey Directive`_
+*  `The Ignore Directive`_
+*  `The Macro Directive`_
+*  `The Map Directive`_
+*  `The Run Directive`_
+*  `The Superimpose Directive`_
+
+*name*
+   Which context subsequent definitions are to be created within.
+
+*title*
+   A person-readable description of the context. It may contain spaces.
+   Standard capitalization conventions should be used. This operand is
+   optional. If supplied when selecting a context which already has a title
+   then the two must match. Special contexts already have internally-assigned
+   titles.
+
+A context is created the first time it's selected. It may be reselected any
+number of times thereafter. These special contexts are predefined:
+
+``default``
+   The default context. If a definition can't be found within the current
+   context then it's looked up within the default context. This only applies
+   to definitions created by:
+
+   *  `The Bind Directive`_.
+   *  `The Macro Directive`_.
+   *  `The Run Directive`_.
+
+``menu``
+   This context is used when within BRLTTY's Preferences Menu.
+
+All subsequent definitions until either the next ``context`` directive or the
+end of the current include level are created within the selected context. The 
+initial context of the top-level key table is ``default``. The initial context
+of an included key subtable is the context which was selected when it was 
+included. Context changes within included key subtables don't affect the 
+context of the including key table or subtable.
+
+If a context has a title then it is persistent. When a key event causes a
+persistent context to be activated, that context remains current until a
+subsequent key event causes a different persistent context to be activated.
+
+If a context doesn't have a title then it is temporary. When a key event causes
+a temporary context to be activated, that context is only used to interpret the 
+very next key event.
+
+Examples::
+
+   context menu
+   context braille Braille Input
+   context DESCCHAR
+
+The Hide Directive
+------------------
+
+.. parsed-literal:: hide *state*
+
+Use this directive to specify whether or not definitions created by:
+
+*  `The Bind Directive`_
+*  `The Hotkey Directive`_
+*  `The Map Directive`_
+*  `The Superimpose Directive`_
+
+and text added by:
+
+*  `The Note Directive`_
+
+are to be included within the key table's help text.
+
+*state*
+   One of these keywords:
+
+   ``on``
+      They're excluded.
+   ``off``
+      They're included.
+
+The specified hide state applies to all subsequent definitions and notes until 
+either the next ``hide`` directive or the end of the current include level. The
+initial hide state of the top-level key table is ``off``. The initial hide
+state of an included key subtable is the hide state that was in effect when it
+was included. Hide State changes within included key subtables don't affect the
+hide state of the including key table or subtable.
+
+Examples::
+
+   hide on
+
+The Hotkey Directive
+--------------------
+
+.. parsed-literal:: hotkey *key* *press* *release*
+
+Use this directive to bind the press and release events of a specific key to
+two separate BRLTTY commands. The bindings are defined within the current
+context.
+
+*key*
+   The name of the key which is to be bound.
+
+*press*
+   The name of the BRLTTY command which is to be executed whenever the key is
+   pressed.
+
+*release*
+   The name of the BRLTTY command which is to be executed whenever the key is
+   released.
+
+Modifiers may be appended to the command names. See
+`Bind Command Modifiers`_ for details.
+
+Specify the ``NOOP`` command if no command is to be executed. Specifying the
+``NOOP`` command for both events effectively disables the key.
+
+Examples::
+
+   hotkey Key1 CSRVIS+off CSRVIS+on
+   hotkey Key2 NOOP NOOP
+
+The IfKey Directive
+-------------------
+
+.. parsed-literal:: ifKey *key* *directive*
+
+Use this directive to only process one or more directives if the device has a
+particular key.
+
+*key*
+   |key operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifKey Key1 ifkey Key2 bind Key1+Key2 HOME
+
+The IfNotKey Directive
+----------------------
+
+.. parsed-literal:: ifNotKey *key* *directive*
+
+Use this directive to only process one or more directives if the device doesn't
+have a particular key.
+
+*key*
+   |key operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifNotKey Key2 bind Key1 HOME
+
+The IfPlatform Directive
+------------------------
+
+.. parsed-literal:: ifPlatform *platform* *directive*
+
+Use this directive to only process one or more directives if running on the
+named host platform.
+
+*platform*
+   |platform operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifPlatform android include android.kti
+
+The IfNotPlatform Directive
+---------------------------
+
+.. parsed-literal:: ifNotPlatform *platform* *directive*
+
+Use this directive to only process one or more directives if not running on
+the named host platform.
+
+*platform*
+   |platform operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifNotPlatform grub include advanced.kti
+
+The Ignore Directive
+--------------------
+
+.. parsed-literal:: ignore *key*
+
+Use this directive to ignore a specific key while within the current context.
+
+*key*
+   The name of the key which is to be ignored.
+
+Examples::
+
+   ignore Key1
+
+The Isolated Directive
+----------------------
+
+.. parsed-literal:: isolated
+
+Use this directive to isolate the current context.
+If a key binding can't be found within an isolated context
+then it won't be looked up within the default context.
+
+Examples::
+
+   isolated
+
+The Macro Directive
+-------------------
+
+.. parsed-literal:: macro *keys* *command* ...
+
+Use this directive to bind a sequence of BRLTY commands to a key combination.
+
+*keys*
+   The key combination being defined.
+
+*command* ...
+   The BRLTTY commands that are to be executed.
+
+Examples::
+
+   macro Key1+Key2 TOP LNEND
+
+The Map Directive
+-----------------
+
+.. parsed-literal:: map *key* *function*
+
+Use this directive to map a key to a braille keyboard function. The mapping is
+defined within the current context.
+
+*key*
+   The name of the key that is to be mapped. More than one key may be mapped to
+   the same braille keyboard function.
+
+.. _The Map Function Operand:
+
+*function*
+   The name of the braille keyboard function. It may be one of the following
+   keywords:
+
+   ``DOT1``
+      The upper-left standard braille dot key.
+   ``DOT2``
+      The middle-left standard braille dot key.
+   ``DOT3``
+      The lower-left standard braille dot key.
+   ``DOT4``
+      The upper-right standard braille dot key.
+   ``DOT5``
+      The middle-right standard braille dot key.
+   ``DOT6``
+      The lower-right standard braille dot key.
+   ``DOT7``
+      The lower-left computer braille dot key.
+   ``DOT8``
+      The lower-right computer braille dot key.
+   ``SPACE``
+      The space bar.
+   ``SHIFT``
+      The shift key.
+   ``UPPER``
+      If a lowercase letter is being entered then translate it to its
+      uppercase equivalent.
+   ``CONTROL``
+      The control key.
+   ``META``
+      The left alt (or meta) key.
+   ``ALTGR``
+      The right alt (or altgr) key.
+   ``GUI``
+      The gui (or windows) key.
+
+If a key combination consists only of keys which have been mapped to braille 
+keyboard functions, and if those functions, when combined, form a valid braille 
+keyboard command, then that command is executed as soon as any of the keys is 
+released. A valid braille keyboard command must include either any combination 
+of dot keys or the space bar (but not both). If at least one dot key is 
+included then the braille keyboard functions specified by
+`The Superimpose Directive`_ within the same context are also implicitly
+included.
+
+Examples::
+
+   map Key1 DOT1
+
+The Note Directive
+------------------
+
+.. parsed-literal:: note *text*
+
+Use this directive to add a person-readable explanation to the key table's help
+text. Notes are commonly used, for example, to describe the placement, sizes,
+and shapes of the keys on the device.
+
+*text*
+   The explanation that is to be added. It may contain spaces, and should be
+   grammatically correct.
+
+Each note specifies exactly one line of explanatory text. Leading space is 
+ignored so indentation cannot be specified.
+
+There's no limit to the number of notes that may be added. All of them are 
+gathered together and presented in a single block at the start of the key 
+table's help text.
+
+*  A note that doesn't have a special prefix begins a new outer bulleted list
+   element.
+
+*  A note that is prefixed with an asterisk (``*``) continues the current outer
+   bulleted list element.
+
+*  A note that is prefixed with a plus (``+``) sign is an inner bulletged list
+   element.
+
+Examples::
+
+   note This is the first outer list element.
+   note This is the second outer list element.
+   note * Continue the second outer list element.
+   note + The first element of an inner list.
+   note + The second element of an inner list.
+   note * The third line of the second outer list element.
+
+The above example would be rendered as:
+
+*  This is the first outer list element.
+
+*  This is the second outer list element.
+   Continue the second outer list element.
+
+   +  The first element of an inner list.
+
+   +  The second element of an inner list.
+
+   The third line of the second outer list element.
+
+The Run Directive
+-----------------
+
+.. parsed-literal:: run *keys* *name* [*argument* ...]
+
+Use this directive to bind a host command to a key combination.
+
+*keys*
+   The key combination being defined.
+
+<name*
+   The name of the host command. It may also be the path.
+
+*argument* ...
+   The arguments to be passed to the host command.
+
+Examples::
+
+   run Key1+Key2 pkill --exact brltty
+
+The Superimpose Directive
+-------------------------
+
+.. parsed-literal:: superimpose *function*
+
+Use this directive to implicitly include a braille keyboard function whenever a
+braille keyboard command consisting of at least one dot is executed. Only
+implicit inclusions defined within the current context are performed. Any
+number of these directives may be specified.
+
+*function*
+   The name of the braille keyboard function that is to be implicitly included.
+   See `The Map Function Operand`_ for details.
+
+Examples::
+
+   superimpose DOT7
+
+The Title Directive
+-------------------
+
+.. parsed-literal:: title <text>
+
+Use this directive to specify a person-readable, one-line summary of the key
+table's purpose.
+
+*text*
+   A brief summary of what the key table is used for. It may contain spaces,
+   and standard capitalization conventions should be used.
+
+The title of the key table may be specified only once.
+
+Examples::
+
+   title Bindings for Keypad-based Navigation
+
+.. include:: nesting-directives.rst
+.. include:: variable-directives.rst
+.. include:: condition-directives.rst
+
+Keyboard Table List
+===================
+
+.. csv-table::
+   :header-rows: 1
+   :file: keyboard-table.csv
+
+Braille Driver List
+===================
+
+.. csv-table::
+   :header-rows: 1
+   :file: braille-driver.csv
+
diff --git a/Documents/README.Linux b/Documents/README.Linux
new file mode 100644
index 0000000..3dd02f7
--- /dev/null
+++ b/Documents/README.Linux
@@ -0,0 +1,1010 @@
+~~~~~~~~~~~~~~~
+BRLTTY on Linux
+~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+.. |super user| replace:: ``root``
+.. |recommended user| replace:: ``brltty``
+.. |user information text| replace:: Braille Device Daemon
+.. |nologin shell path| replace:: ``/sbin/nologin``
+.. |safe shell path| replace:: ``/bin/sh``
+.. |safe command search path| replace:: ``/usr/sbin:/sbin:/usr/bin:/bin``
+
+.. |sockets directory name| replace:: ``BrlAPI``
+.. |updatable directory name| replace:: ``brltty``
+.. |writable directory name| replace:: ``brltty``
+
+.. |configuration file path| replace:: ``/etc/brltty.conf``
+.. |key file path| replace:: ``/etc/brlapi.key``
+
+.. |sockets directory path| replace:: ``/var/lib/BrlAPI/``
+.. |updatable directory path| replace:: ``/var/lib/brltty/``
+.. |writable directory path| replace:: ``/var/run/brltty/``
+
+.. |the super user| replace:: the super user (|super user|)
+.. |user information field| replace:: user information (gecos) field
+.. |configuration directive| replace:: configuration directive (in |configuration file path|)
+.. |environment variable| replace:: environment variable (if the ``-E`` command line option has been specified)
+.. |configure option| replace:: configure option (at build time)
+.. |speaker| replace:: built-in PC speaker
+.. |uinput device| replace:: ``uinput`` device (``/dev/uinput`` or ``/dev/input/uinput``)
+.. |key file| replace:: authorization key file (usually |key file path|)
+.. |sockets directory| replace:: sockets directory (usually |sockets directory path|)
+.. |updatable directory| replace:: updatable directory (usually |updatable directory path|)
+.. |writable directory| replace:: writable directory (usually |writable directory path|)
+
+.. |keyboard key tables| replace::
+  keyboard key tables
+  (which allow keyboard key combinations to be bound
+  to BRLTTY's navigation and configuration commands)
+
+.. |cap_setgid| replace:: *cap_setgid*
+.. |cap_chown| replace:: *cap_chown*
+.. |cap_fowner| replace:: *cap_fowner*
+.. |cap_dac_override| replace:: *cap_dac_override*
+.. |cap_sys_module| replace:: *cap_sys_module*
+.. |cap_sys_admin| replace:: **cap_sys_admin**
+.. |cap_sys_tty_config| replace:: **cap_sys_tty_config**
+.. |cap_mknod| replace:: **cap_mknod**
+
+.. |cap_setgid reason| replace:: switching to the primary group of `the unprivileged user`_ and establishing the `needed group memberships`_
+.. |cap_chown reason| replace:: claiming ownership of the `state directories`_
+.. |cap_fowner reason| replace:: adding group permissions to the `state directories`_
+.. |cap_dac_override reason| replace:: creating missing `state directories`_
+.. |cap_sys_module reason| replace:: installing the needed `kernel modules`_
+.. |cap_sys_admin reason| replace:: injecting input characters
+.. |cap_sys_tty_config reason| replace:: using the |speaker|
+.. |cap_mknod reason| replace:: creating needed but missing device files
+
+Least Privilege
+===============
+
+When BRLTTY starts executing as |the super user|,
+i.e. when its initial effective user identifier is 0,
+it has unrestricted access to all of the privileged operations
+that the host has to offer.
+While we always endeavour to ensure that BRLTTY isn't abusing this freedom,
+we also don't feel that it's worth risking the possibility
+that our code might be hacked.
+To reduce this possibility, therefore, BRLTTY first
+establishes a `safer execution environment`_ within which It only has
+access to those `privileged host operations`_ that it actually needs.
+This is a best security practice known as **least privilege**.
+
+Safer Execution Environment
+---------------------------
+
+When Started by the Super User
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY takes a number of steps to establish a safer execution environment:
+
+* Set a `safe command search path`_.
+* Set a `safe default shell`_.
+* `Namespace isolation`_.
+* Switch to executing as `the unprivileged user`_.
+* Install the needed `kernel modules`_.
+* Establish the `needed group memberships`_.
+* Set its `required capabilities`_.
+* Relinquish all non-required capabilities.
+* Install the `system call filter`_.
+
+The Unprivileged User
+`````````````````````
+
+When BRLTTY starts executing as |the super user|,
+one of the first things it does is switch
+to executing as an unprivileged user.
+The user is selected as follows:
+
+Explicit Specification
+  This method is only allowed when BRLTTY was started as |the super user|.
+  It may be specified via the ``user`` `privilege parameter`_.
+  If it isn't explicitly specified,
+  or if there's a problem:
+
+  * The user doesn't exist.
+
+  * The specification is ignored because
+    BRLTTY wasn't started as |the super user|.
+
+  then the default user is selected.
+
+The Default
+  The default unprivileged user is the one specified at build time
+  via the ``lx:user=`` parameter
+  of the ``--with-privilege-parameters`` option
+  of BRLTTY's ``configure`` command.
+  If there's a problem:
+
+  * The user doesn't exist.
+  * BRLTTY wasn't configured to have a default unprivileged user.
+
+  then BRLTTY continues to execute as |the super user|.
+
+Continuing to Run as the Super User
+  This is the last resort!
+  In this case:
+
+  * BRLTTY still establishes the rest of its `safer execution environment`_.
+
+  * Establishing the `needed group memberships`_ replaces
+    (rather than extends) the supplementary group list.
+
+After having successfully switched to executing as an unprivileged user,
+the following is done:
+
+* Ensure that all of the user identifiers
+  (real, effective, saved, filesystem)
+  are set to the selected user.
+
+* Ensure that all of the group identifiers
+  (real, effective, saved, filesystem)
+  are set to the primary group of the selected user.
+
+* If any of the following environment variables is set
+  then reset it to the name of the selected user:
+
+  + LOGNAME
+  + USER
+
+* Ensure that the following environment variables aren't set:
+
+  + XDG_CONFIG_HOME
+  + XDG_DATA_DIRS
+
+* Change the XDG runtime directory
+  (set the ``XDG_RUNTIME_DIR`` environment variable)
+  to that of the selected user.
+
+* Extend the supplementary group list with the `needed group memberships`_.
+
+* If the selected user's home directory is defined then switch to it by:
+
+  * Changing the working directory.
+  * Setting the ``HOME`` environment variable.
+
+  The |updatable directory| is used instead if:
+
+  * The user's home directory isn't defined.
+  * BRLTTY is unable to switch to the user's home directory.
+  * BRLTTY had to continue executing as |the super user|.
+
+State Directories
+`````````````````
+
+A state directory is one which a program needs to be able to write data to.
+BRLTTY's state directories are:
+
+|sockets directory|
+  This directory is where BRLTTY creates BrlAPI's
+  local (UNIX domain) server sockets.
+  It needs to be world writable, and, as such,
+  should also have its sticky bit set.
+  So:
+
+  .. parsed-literal:: chmod ugo=rwx,o+t *path*
+
+|updatable directory|
+  This directory is where BRLTTY saves user data.
+  This includes:
+
+  * preferences files
+  * clipboard content
+
+|writable directory|
+  This directory is where BRLTTY creates private copies of
+  file system objects that it needs but
+  that don't already exist (or, at least, that it can't find),
+  or that are inaccessible (can't be opened).
+  It's also where, if requested, BRLTTY creates its input FIFO
+  (which allows users to take advantage of its text-to-speech capability).
+
+The actual locations of these directories can be specified in a number of ways.
+From highest to lowest precedence, they are:
+
+* command line option
+* |configuration directive|
+* |environment variable|
+* default location (can be changed with ``configure`` at build time)
+
+.. table:: State Directory Location Specification
+
+  =========  ======  ===================  ==========================  ==========================
+  Directory  Option  Config Directive     Environment Variable        Default Location
+  ---------  ------  -------------------  --------------------------  --------------------------
+  Sockets                                                             |sockets directory path|
+  Updatable  -U      updatable-directory  BRLTTY_UPDATABLE_DIRECTORY  |updatable directory path|
+  Writable   -W      writable-directory   BRLTTY_WRITABLE_DIRECTORY   |writable directory path|
+  =========  ======  ===================  ==========================  ==========================
+
+After having successfully switched to executing as `the unprivileged user`_,
+BRLTTY attempts to gain full access to its state directories.
+The most common case where this is necessary is
+to automate the transition from an older release of BRLTTY
+from the days when it had to execute as |the super user|.
+Another (much rarer) case would be
+when transitioning from one unprivileged user to another.
+
+The main reason that BRLTTY does this job is
+that it's extremely difficult for a blind user to figure out what's wrong
+before his/her braille device is up and running.
+Put another way, it can be near impossible for a braille user to figure out
+why BRLTTY is having problems while BRLTTY is having problems.
+
+The following actions are taken for each of the state directories:
+
+* If it doesn't exist then it is created.
+  This usually requires the |cap_dac_override| (temporary) capability
+  because it's usually a subdirectory of a directory
+  that can only be written to by |the super user|,
+  e.g. ``/var/lib/``.
+
+* Its owning user and group are changed to be
+  the user and the primary group of the user
+  that BRLTTY Is executing as.
+  The same change is also made to whatever the directory contains.
+  This requires the |cap_chown| (temporary) capability
+  for a directory or file that's owned by a different user.
+
+* Group read and write permissions are added to it
+  and to whatever it contains.
+  For directories, group search permission is also added
+  and the set-group-ID bit is set.
+  This requires the |cap_fowner| (temporary) capability
+  for a directory or file that's owned by a different user.
+
+Gaining full access to a state directory is only attempted
+if the last component of its path is its expected name:
+
+.. table:: Expected State Directory Names
+
+  =========  ==========================
+  Directory  Expected Name
+  ---------  --------------------------
+  Sockets    |sockets directory name|
+  Updatable  |updatable directory name|
+  Writable   |writable directory name|
+  =========  ==========================
+
+This is a protective measure, given that command line options, etc
+can be used to change the location of a state directory
+to a non-BRLTTY-specific location.
+It prevents BRLTTY from Attempting to gain full access
+to another program's data, or, even worse, to a public directory.
+If it's really necessary to use such a directory
+then it's far better to let a human being take care of it.
+
+When Started by an Unprivileged User
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY, as of release 6.2, can be started
+by an unprivileged user (not |super user|).
+Use cases for this include system administrators or users
+who'd like BRLTTY to be startable
+by any user, by users who belong to a specific group, etc.
+
+In order for an unprivileged user to successfully start BRLTTY,
+the environment needs to be prepared as follows:
+
+* Assign the `required capabilities`_ to BRLTTY's executable.
+
+* Give the user that's to be able to start BRLTTY
+  the `needed group memberships`_.
+  This isn't necessary, i.e. BRLTTY will join them by itself,
+  if the |cap_setgid| (temporary) capability
+  has also been assigned to its executable.
+  In this case, BRLTTY will extend the supplementary group list
+  that it inherited from the user with any needed groups that are missing.
+
+* Install the needed `kernel modules`_.
+  This isn't necessary, i.e. BRLTTY will install them by itself,
+  if the |cap_sys_module| (temporary) capability
+  has also been assigned to its executable.
+
+* Ensure that BRLTTY's `state directories`_
+  have been created,
+  have the correct ownership,
+  and have the correct permissions.
+  Each of these prerequisites isn't necessary,
+  i.e. BRLTTY will take care of it by itself,
+  if the associated (temporary) capability
+  has also been assigned to its executable.
+  They are:
+
+  .. table:: Temporary Capabilities for State Directory Prerequisites
+
+    ============  ====================
+    Prerequisite  Temporary Capability
+    ------------  --------------------
+    creation      |cap_dac_override|
+    ownership     |cap_chown|
+    permissions   |cap_fowner|
+    ============  ====================
+
+.. _privilege parameter:
+
+Privilege Parameters
+~~~~~~~~~~~~~~~~~~~~
+
+Privilege parameters control how BRLTTY establishes
+its `safer execution environment`_.
+From highest to lowest precedence, they can be specified via:
+
+* The ``--privilege-parameters`` (or ``-z``) command line option.
+* The ``privilege-parameters`` |configuration directive|.
+* The ``BRLTTY_PRIVILEGE_PARAMETERS`` |environment variable|.
+* The ``--with-privilege-parameters`` |configure option|.
+
+Each of these takes a comma-separated list of parameters in the form::
+
+  platform:name=value
+
+The ``platform:`` part is optional -
+if it's omitted then the parameter setting applies on all platforms.
+It's best, therefore, to always include it.
+The platform code for Linux is ``lx``.
+
+The command line option can be specified any number of times.
+Likewise, the configuration directive can be specified any number of times.
+Additional specifications extend, rather than replace, the parameter list.
+The same parameter specified later at the same level of precedence,
+or at a higher level of precedence, overrides its earlier setting.
+
+The following privilege parameters are supported for Linux:
+
+``path``
+  This parameter sets the `safe command search path`_.
+  No attempt is made to validate it.
+
+``scfmode``
+  This parameter sets the mode of the `system call filter`_.
+  The supported modes are:
+
+  ``no``
+    Don't install the filter.
+    This is the default.
+
+  ``log``
+    Log each unapproved system call to ``syslog``.
+    It's still executed.
+
+  ``fail``
+    Each unapproved system call fails with ``errno`` set to ``EPERM``
+    (operation not permitted).
+
+  ``kill``
+    An attempt to execute an unapproved system call
+    causes the entire BRLTTY process to be killed.
+
+``shell``
+  This parameter sets the path to the `safe default shell`_.
+  No attempt is made to validate it.
+
+``user``
+  This parameter sets `the unprivileged user`_.
+
+Staying Privileged
+~~~~~~~~~~~~~~~~~~
+
+If the ``--stay-privileged`` (or ``-Z``) command line option is specified
+then BRLTTY will retain all of the privileges
+that it had when it was invoked.
+It won't:
+
+* Switch to an unprivileged user
+  (i.e. it'll continue to execute as the invoking user).
+
+* Relinquish any group memberships.
+  Missing `needed group memberships`_ are, if possible, still established.
+
+* Relinquish any capabilities.
+  Missing `required capabilities`_ are, if possible, still acquired.
+
+Safe Command Search Path
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+A safe command search path is established
+by setting the ``PATH`` environment variable
+to a system-configured set of safe directories.
+You can find out which ones they are by running this command::
+
+  getconf PATH
+
+If, for some reason, the system-configured path isn't available
+then |safe command search path| is used.
+
+The path can be explicitly set
+via the ``path`` `privilege parameter`_.
+
+Safe Default Shell
+~~~~~~~~~~~~~~~~~~
+
+A safe default shell for external software to assume
+is established by setting the ``SHELL`` environment variable
+to |safe shell path|.
+On many systems, this is the Bourne Shell.
+On others, it's a symbolic link to some other shell.
+To find out what it is on your system, run this command:
+
+.. parsed-literal:: ls -l |safe shell path|
+
+Note that scripts usually internally specify which shell is to be used.
+
+The default shell can be explicitly set
+via the ``shell`` `privilege parameter`_.
+
+Namespace Isolation
+~~~~~~~~~~~~~~~~~~~
+
+BRLTTY isolates some of the kernel namespaces
+that are associated with its process.
+The namespaces that it currently isolates are:
+
+cgroup
+  This namespace is used to manage control groups.
+
+mount
+  This namespace is used to manage mount points.
+
+UTS
+  This namespace is used to manage the host name and the NIS domain name.
+
+System Call Filter
+~~~~~~~~~~~~~~~~~~
+
+The kernel provides a system call filter,
+known as ``seccomp`` (secure computing),
+that verifies that only an approved set of system calls is being used.
+The default is that BRLTTY doesn't actually use it
+because, by nature, using a system call filter
+makes a program somewhat fragile.
+Reasons for this include:
+
+* The various object libraries that BRLTTY relies on, e.g. ``libc``,
+  might change which system calls they use from one release to the next.
+
+* The filter is also applied to any external software,
+  e.g. text-to-speech engines,
+  that BRLTTY uses.
+
+Even though the default is that BRLTTY doesn't use the filter,
+access to it is still provided for those users or administrators who prefer
+to avail themselves of the additional protection that it offers.
+Use the ``scfmode`` `privilege parameter`_ to specify how BRLTTY uses it.
+
+* Specify ``log`` if you'd like BRLTTY to continue executing normally
+  but to also record any unapproved system calls in the system log.
+
+* Specify ``fail`` if you'd like unapproved system calls to fail,
+  with BRLTTY attempting to handle those failures.
+  Note that, while BRLTTY endeavours to handle such failures well,
+  external software that it uses might not.
+
+* Specify ``kill`` if you'd like an unapproved system call
+  to cause the entire BRLTTY process to be summarily killed.
+
+Installing the filter also includes configuring BRLTTY's process
+so that no external command that it subsequently invokes
+will be able to acquire any additional privileges.
+See the ``PR_SET_NO_NEW_PRIVS`` section of the man page for ``prctl``
+for details.
+The quick summary is that, when executing any host command:
+
+* It's set-user-ID bit will be ignored.
+* It's set-group-ID bit will be ignored.
+* It's file capabilities will be ignored.
+
+The current list of approved system calls
+is in the ``syscalls_linux.h`` header,
+which is in the ``Programs/`` subdirectory of BRLTTY's source tree.
+BRLTTY must be rebuilt in order for changes to the list to become effective.
+Please let us know if you discover any system calls
+that are missing and should be added.
+
+Preparing the Environment
+-------------------------
+
+Creating the Unprivileged User
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We recommend that `the unprivileged user`_ be named |recommended user|,
+and that it be a system user.
+
+The user's primary group should be user-specific.
+Ideally, it should have the same name as the user.
+
+The user's supplementary group list should include
+all of the groups that own the various system resources
+that BRLTTY needs access to in order for it to do its job properly.
+See `Needed Group Memberships`_ for details.
+
+The user should have a home directory.
+It should be on a local file system
+so that BRLTTY can start properly when there are network problems.
+
+There's normally no need for anyone to log into the user.
+This can be enforced by setting its login shell to |nologin shell path|.
+
+The |user information field| should be set
+to a user-friendly description of why it exists.
+We recommend something like:
+
+.. parsed-literal:: |user information text|
+
+The ``useradd`` Command
+```````````````````````
+
+The host command to create a user is:
+
+.. parsed-literal:: useradd *option* ... *name*
+
+It must be run as |the super user|.
+
+It accepts a lot of options.
+For all of the details, run this command::
+
+  man useradd
+
+Unless there are special and/or unusual considerations,
+its most important options are:
+
+.. table:: ``useradd`` Options
+
+  ========================  ========================================
+  Option                    Action
+  ------------------------  ----------------------------------------
+  ``--system``              create a system user
+  ``--user-group``          create a user-specific primary group
+  ``--gid`` *group*         set the primary group
+  ``--groups`` *group*,...  set the supplementary group list
+  ``--no-create-home``      don't create the home directory
+  ``--create-home``         create the home directory
+  ``--home`` *path*         the absolute path for the home directory
+  ``--comment`` *text*      set the |user information field|
+  ``--shell`` *path*        set the login shell
+  ========================  ========================================
+
+The ``brltty-mkuser`` Script
+````````````````````````````
+
+The top-level directory of BRLTTY's source tree contains
+a script named ``brltty-mkuser`` that simplifies the job of
+Creating and making changes to `the unprivileged user`_.
+For details, run this command::
+
+  brltty-mkuser -h
+
+The following options require special mention:
+
+``-U`` *name*
+  This option specifies the name of the user
+  that's to be created or changed.
+  If the default unprivileged user was configured at build time
+  then It defaults to that user.
+
+``-N``
+  This option allows the creation of a new user.
+
+``-E``
+  This option allows changes to be made to an existing user.
+
+``-G``
+  This option suppresses setting the user's supplementary group list.
+  A new user won't belong to any supplementary groups,
+  and an existing user will retain its current supplementary group memberships.
+
+Note that this script is safe to accidentally invoke because
+both creating a new user (``-N``)
+and making changes to an existing user (``-E``)
+must be explicitly allowed.
+These options aren't mutually exclusive - both may be specified.
+
+The user's supplementary group list is used
+to establish the `needed group memberships`_.
+Each of the following options removes a group from the full list,
+and, therefore, also removes that group's associated functionality from BRLTTY.
+If a group's name is shown in *italics* then it's only our recommendation
+as its actual name isn't defined by any standard or convention.
+
+.. table:: ``brltty-mkuser`` Options for Excluding Supplementary Groups
+
+  ======  ================  ========================================
+  Option  Removes Group     Lost Functionality
+  ------  ----------------  ----------------------------------------
+  ``-a``  ``audio``         playing sound via the ALSA framework
+  ``-b``  *brlapi*          reading BrlAPI's |key file|
+  ``-c``  ``tty``           access to the virtual consoles
+  ``-k``  ``input``         keyboard monitoring
+  ``-p``  ``pulse-access``  playing sound via the Pulse Audio server
+  ``-s``  *dialout*         access to serial devices
+  ``-u``  usually ``root``  access to USB devices
+  ======  ================  ========================================
+
+The following options are for configuring
+the basic (password file) fields of the user:
+
+.. table:: ``brltty-mkuser`` Options for New / Existing User Configuration
+
+  ======  =======  ============================
+  Option  Operand  Sets / Changes
+  ------  -------  ----------------------------
+  ``-d``  *path*   the home directory
+  ``-g``  *group*  the primary group
+  ``-i``  *text*   the |user information field|
+  ``-l``  *path*   the login shell
+  ======  =======  ============================
+
+If any of these options isn't specified, then:
+
+* When making changes to an existing user, it has no effect.
+  Its associated field isn't changed.
+
+* When creating a new user, the defaults are:
+
+  .. table:: ``brltty-mkuser`` Defaults for New User Configuration
+
+    ======  ====================================================
+    Option  Default When creating a New User
+    ------  ----------------------------------------------------
+    ``-d``  the updatable directory (|updatable directory path|)
+    ``-g``  a new group with the same name as the user
+    ``-i``  |user information text|
+    ``-l``  |nologin shell path|
+    ======  ====================================================
+
+The following options are primarily for developers:
+
+.. table:: ``brltty-mkuser`` Options for Developers
+
+  ======  ====================================================
+  Option  Description
+  ------  ----------------------------------------------------
+  ``-S``  use ``sudo`` to execute the commands as root
+  ``-T``  test mode - show the commands that would be executed
+  ======  ====================================================
+
+Assigning Capabilities to the Executable
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Don't do this if you only want BRLTTY to execute successfully
+`when started as the super user`_.**
+
+First, here's a summary of all of the capabilities that BRLTTY needs
+`when started by an unprivileged user`_.
+
+* The `required capabilities`_ are highlighted **this way**.
+  They're needed throughout BRLTTY's execution.
+
+* The `temporary capabilities`_ are highlighted *this way*.
+  They're only needed when BRLTTY starts,
+  and are relinquished when they're no longer needed.
+
+.. table:: Capability Summary
+
+  ====================  ===========================
+  Capability            Reason
+  --------------------  ---------------------------
+  |cap_chown|           |cap_chown reason|
+  |cap_dac_override|    |cap_dac_override reason|
+  |cap_fowner|          |cap_fowner reason|
+  |cap_mknod|           |cap_mknod reason|
+  |cap_setgid|          |cap_setgid reason|
+  |cap_sys_admin|       |cap_sys_admin reason|
+  |cap_sys_module|      |cap_sys_module reason|
+  |cap_sys_tty_config|  |cap_sys_tty_config reason|
+  ====================  ===========================
+
+The ``setcap`` Command
+``````````````````````
+
+The host command to assign all of the capabilities,
+i.e. both the `required capabilities`_ and the `temporary capabilities`_,
+to BRLTTY's executable
+so that it'll be fully functional `when started by an unprivileged user`_,
+without requiring any additional administrator configuration,
+is::
+
+  setcap cap_setgid,cap_chown,cap_fowner,cap_dac_override,cap_sys_module,cap_sys_admin,cap_sys_tty_config,cap_mknod+p /path/to/brltty
+
+It must be run as |the super user|.
+
+The ``brltty-setcaps`` Script
+`````````````````````````````
+
+The top-level directory of BRLTTY's source tree contains
+a script named ``brltty-setcaps`` that simplifies the job of
+`assigning capabilities to the executable`_.
+Its default is to assign all of them.
+For details, run this command::
+
+  brltty-setcaps -h
+
+It requires one positional argument - the path to BRLTTY's executable.
+It also accepts a number of options (which must precede that path).
+In particular, each of the following options
+removes a capability from the full set,
+and, therefore, also removes the functionality
+that that capability grants from BRLTTY.
+
+.. table:: ``brltty-setcaps`` Options for Excluding Capabilities
+
+  ======  ====================  ===========================
+  Option  Removes Capability    Lost Functionality
+  ------  --------------------  ---------------------------
+  ``-c``  |cap_dac_override|    |cap_dac_override reason|
+  ``-d``  |cap_mknod|           |cap_mknod reason|
+  ``-g``  |cap_setgid|          |cap_setgid reason|
+  ``-i``  |cap_sys_admin|       |cap_sys_admin reason|
+  ``-m``  |cap_sys_module|      |cap_sys_module reason|
+  ``-o``  |cap_chown|           |cap_chown reason|
+  ``-p``  |cap_fowner|          |cap_fowner reason|
+  ``-s``  |cap_sys_tty_config|  |cap_sys_tty_config reason|
+  ======  ====================  ===========================
+
+The following options are primarily for developers:
+
+.. table:: ``brltty-setcaps`` Options for Developers
+
+  ======  ====================================================
+  Option  Description
+  ------  ----------------------------------------------------
+  ``-C``  don't set the capabilities
+  ``-G``  set group root execution
+  ``-S``  use ``sudo`` to execute the commands as root
+  ``-T``  test mode - show the commands that would be executed
+  ``-U``  set user root execution
+  ======  ====================================================
+
+Privileged Host Operations
+--------------------------
+
+The privileged host operations that BRLTTY needs to be able to perform
+in order to be fully functional are:
+
+Kernel Modules
+~~~~~~~~~~~~~~
+
+BRLTTY relies on functionality provided by these kernel modules:
+
+**pcspkr**
+  For playing alert tunes via the |speaker|.
+
+**uinput**
+  For creating virtual devices via the |uinput device|.
+
+Needed Group Memberships
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY relies on file system and server (daemon) access
+granted via membership in the following owning user groups.
+
+* A group name shown in **bold** is its actual name.
+
+* A group name shown in *italics* is our recommended name for it
+  as its actual name isn't defined by any standard or convention.
+
+**audio**
+  For playing sound via the ALSA framework.
+
+*brlapi*
+  For reading BrlAPI's |key file|.
+  In other words, the group that owns that file.
+
+**dialout** (some distributions use **uucp**)
+  For serial I/O via these ``/dev/`` devices:
+
+  **ttyS<n>**
+    Actual serial ports.
+
+  **ttyACM<n>**
+    USB to serial adapters that implement the CDC ACM standard.
+
+  **ttyUSB<n>**
+    Other USB to serial adapters.
+
+**input**
+  For monitoring keyboard input via the devices in ``/dev/input/``.
+  This capability is used to support |keyboard key tables|.
+
+**pulse-access**
+  For playing sound via the Pulse Audio daemon.
+
+**root**
+  For:
+
+  * USB I/O via ``USBFS`` (using the devices in ``/dev/bus/usb/``).
+  * Creating virtual devices via the |uinput device|.
+
+  Note that this is the ``root`` group - not the ``root`` user.
+
+**tty**
+  For:
+
+  * Reading screen content via the ``/dev/vcs`` devices.
+  * Virtual console monitoring and control via the ``/dev/tty<n>`` devices.
+
+* `When started by the super user`_,
+  existing group memberships are relinquished.
+
+* `When started by an unprivileged user`_,
+  existing group memberships are retained.
+
+Required Capabilities
+~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY uses the privileged kernel operations
+that are granted via these capabilities
+throughout its execution.
+They are retained within its ``permitted`` and ``effective`` sets,
+but not within its ``inheritable`` and ``ambient`` sets.
+In other words, they're for BRLTTY itself
+and aren't passed along to any host command that it runs.
+
+|cap_mknod|
+  For creating needed but missing device files.
+
+|cap_sys_admin|
+  For using ``TIOCSTI`` to inject input characters typed on a braille device.
+
+|cap_sys_tty_config|
+  For using ``KDMKTONE`` and ``KIOCSOUND`` to play alert tunes via the |speaker|.
+
+Temporary Capabilities
+~~~~~~~~~~~~~~~~~~~~~~
+
+BRLTTY only uses the privileged kernel operations
+that are granted via these capabilities
+`when started by an unprivileged user`_.
+They allow it to configure itself,
+thus not relying so heavily on explicit administrator configuration,
+and are relinquished right after this has been done.
+
+|cap_chown|
+  If this capability has also been assigned to BRLTTY's executable
+  then it can claim ownership of its `state directories`_
+  and their contents
+  after having successfully switched to executing as `the unprivileged user`_.
+  The primary group of that user is used.
+
+|cap_dac_override|
+  If this capability has also been assigned to BRLTTY's executable
+  then it can create missing `state directories`_
+  after having successfully switched to executing as `the unprivileged user`_.
+
+|cap_fowner|
+  If this capability has also been assigned to BRLTTY's executable
+  then it can add group permissions to its `state directories`_
+  and to their contents
+  after having successfully switched to executing as `the unprivileged user`_.
+  Both read and write group permissions are added to all files and directories.
+  In addition, for all directories,
+  group search permission is added
+  and the set-group-ID bit is set.
+
+|cap_setgid|
+  If this capability has also been assigned to BRLTTY's executable
+  then it internally extends its supplementary group list
+  with any `needed group memberships`_ that are missing.
+
+|cap_sys_admin|
+  While this is one of the `required capabilities`_,
+  it's also needed as a temporary capability for `namespace isolation`_.
+
+|cap_sys_module|
+  If this capability has also been assigned to BRLTTY's executable
+  then the needed `kernel modules`_ needn't have been already installed
+  because they can be internally installed.
+
+Known Problems
+--------------
+
+Writing to SYSFS Files
+~~~~~~~~~~~~~~~~~~~~~~
+
+The ``SYSFS`` virtual file system (usually mounted at ``/sys/``)
+contains some files that BRLTTY occasionally needs to write to.
+While they can be read by anyone,
+they can only be written to by |the super user|.
+BRLTTY needs to be able to write to these files
+for (at least) the following reasons:
+
+* Disabling USB autosuspend.
+  Some USB-connected braille devices don't respond very well
+  to being automatically suspended by the kernel.
+  In these cases, BRLTTY disables the feature
+  by writing to the ``power/autosuspend`` file
+  of the associated PCI device.
+
+A possible approach might be to add Udev rules.
+
+Creating Virtual Devices via Uinput
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The |uinput device| is usually only readable and writable
+by |the super user|.
+Without any group and/or others access,
+it's impossible for BRLTTY to access it
+after having switched to `the unprivileged user`_.
+
+This situation could be easily resolved
+by granting group read and write permissions to the device.
+This shouldn't be problematic because its owned by the ``root`` group.
+Even better, of course, would be to give the device its own group
+(e.g. named ``uinput``).
+
+BRLTTY currently gets around this problem by including the following
+Udev rule to add an ACL (access control list) entry to the device::
+
+  KERNEL=="uinput", ACTION=="add", TEST=="/usr/bin/setfacl",
+  RUN+="/usr/bin/setfacl -m u:brltty:rw /dev/$name"
+
+If BRLTTY finds that it isn't permitted to open the device
+(perhaps because the Udev rule isn't present)
+then its fallback method Is to create a private copy of the device file
+within its |writable directory|.
+This requires the |cap_mknod| capability.
+
+Other alternatives include:
+
+* Adding the ``cap_dac_override`` (permanent) capability
+  so that BRLTTY isn't subject to file ownership restrictions.
+  This is a very, very bad idea
+  but, for completeness, it's on the table.
+
+* Adding the ``cap_fowner`` (temporary) capability
+  so that BRLTTY can add an acl (access control list) entry
+  that grants itself access to the device.
+
+* Adding the ``cap_fowner`` (temporary) capability
+  so that BRLTTY can add group read and write permissions to the device.
+
+Being able to create virtual devices
+is a very important ability for BRLTTY to have.
+It's used for:
+
+* Creating a virtual keyboard in order to
+  forward those keyboard events (key presses and releases)
+  that haven't been claimed by bindings within |keyboard key tables|
+  back to the system.
+
+* Creating a virtual keyboard in order to inject simulated typing. 
+  This is done to support the typing of
+  arbitrary combinations of modifier keys (shift, control, alt, etc),
+  in combination (or not) with any character(s) and/or special key(s),
+  on the keyboard of a braille device.
+  The fallback interface - ``TIOCSTI`` - only provides character injection,
+  which means that it can only support the typing of individual characters
+  and the pressing of those special keys
+  that can be emulated by well-known escape sequences.
+
+* Creating a virtual sound device in order to
+  watch for tones sent to the |speaker|.
+  This is done to support the redirection of these tones
+  to the PCM interface of a sound card.
+  While sighted users seem to be content with not hearing these tones
+  on a computer that doesn't have a |speaker|,
+  most blind users rely on being able to hear them.
+  For example, a blind person can't see a visual bell.
+
+* Creating a virtual LED device in order to
+  monitor what the keyboard LEDs are showing.
+  This is done to support the generation of
+  An audio-based rendering of LED state changes.
+
+Creating Private Copies of Device Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The default location of the |writable directory| on a modern system
+is usually on a memory-resident file system
+that has been mounted with the ``nodev`` option.
+This prevents a device file within that file system from being opened,
+even if its permissions (mode, acl, etc) allow it.
+The good news is that this isn't usually a problem
+on a well-configured system
+because it's extremely rare for a needed device file to be missing.
+If it is, then the best solution is
+to change the writable directory to a better location
+(see `State Directories`_ for how to do that).
+
diff --git a/Documents/README.OpenBSD b/Documents/README.OpenBSD
new file mode 100644
index 0000000..db294b7
--- /dev/null
+++ b/Documents/README.OpenBSD
@@ -0,0 +1,91 @@
+~~~~~~~~~~~~~~~~~
+BRLTTY on OpenBSD
+~~~~~~~~~~~~~~~~~
+:Author: Mario Lang <mlang@delysid.org>
+
+.. include:: prologue.rst
+
+This document describes how to get BRLTTY working on a recent OpenBSD system.
+It assumes that you're working with a recent OpenBSD installation (e.g. 3.4).
+
+Setting up BRLTTY
+=================
+
+Patching ``screen``
+-------------------
+
+Since OpenBSD doesn't have a way for user-space applications to access the
+contents of the virtual consoles, we have to use ``screen`` to make this
+information available to BRLTTY. This also means that you can't use BRLTTY with
+the OpenBSD virtual consoles directly - you must start ``screen`` first on some
+virtual terminal in order to be able to use BRLTTY.
+
+You need to patch ``screen`` to enable the ``IPC_SHARED_IMAGE`` functionality
+that BRLTTY relies on. Assuming your ports tree is in ``/usr/ports``, you'll
+need to copy the appropriate patch from ``brltty/Patches`` into
+``/usr/ports/misc/screen/patches``.
+
+OpenBSD 3.4 comes with screen-4.0.1. You can check the ``screen`` version by
+looking in ``/usr/ports/misc/screen/Makefile``.
+
+The patch file should be named with the prefix ``patch-`` so that the
+``screen`` build process will find it.
+
+Now change into ``/usr/ports/misc/screen`` and run::
+
+   make
+   sudo make install
+
+If you've successfully managed to compile and install the ``screen`` package,
+you can proceed to building BRLTTY.
+
+There's a simple test you can use to verify if your ``screen`` installation
+does support the ``IPC_SHARED_IMAGE`` feature. Start ``screen``, and then
+execute the command::
+
+   ipcs -m
+
+You should see output that looks roughly like this::
+
+   Shared Memory:
+   T       ID     KEY        MODE       OWNER    GROUP
+   m    65536 -1160968401 --rwa------     root     root
+
+If you do, then ``screen`` seems to have been compiled with the
+``IPC_SHARED_IMAGE`` feature.
+
+Note that proper integration of the ``IPC_SHARED_IMAGE`` functionality via an
+additional "flavor" for the ``screen`` port has recently been submitted to the
+OpenBSD ports mailing list. If you're working with the CVS ports tree, it might
+be worthwhile to check if the ``shm`` flavor has already been checked in by
+invoking the following command when in ``/usr/ports/misc/screen``::
+
+   make show=FLAVORS
+
+If you see ``shm`` listed as a choosable flavor, simply build ``screen`` with
+the ``FLAVOR`` environment variable set to ``shm`` - don't bother copying the
+patch from BRLTTY's source tree.
+
+Compiling BRLTTY
+================
+
+To compile BRLTTY, do the following::
+
+   ./configure
+   gmake
+   sudo gmake install
+
+Note that ``gmake`` should be used in order to avoid a problem related to BSD
+and GNU Make incompatibilities.
+
+Running BRLTTY
+==============
+
+You need to have ``screen`` started before you try to run BRLTTY. Inside
+``screen``, simply invoke BRLTTY as you would on other systems. Note that the
+first serial port is ``/dev/cua00``.
+
+A typical invocation could look like this::
+
+   brltty -b ts -d serial:cua00
+
diff --git a/Documents/README.Polling b/Documents/README.Polling
new file mode 100644
index 0000000..0be100c
--- /dev/null
+++ b/Documents/README.Polling
@@ -0,0 +1,93 @@
+~~~~~~~
+Polling
+~~~~~~~
+
+.. include:: prologue.rst
+
+As of release 5.0, BRLTTY has been converted from being polling-based to being 
+event-based. This means, among other things, that it's now able to become 
+completely idle whenever it doesn't have anything useful to do. For comparison, 
+it formerly used a huge, central, main loop which was run frequently (about 25 
+times per second), and which was aware of, and had to check for, every single
+task that might need to be performed.
+
+In practical terms, this change, when compared with how BRLTTY used to work, 
+delivers at least the following significant benefits:
+
+*  Noticeably improved response time. Near instant reaction to significant user
+   and system events rather than waiting for the next polling cycle to check
+   for them.
+
+*  Much less system load. Just a few, short-lived bursts of activity when
+   actually necessary rather than frequent, much longer-lived checking that,
+   far more often than not, resulted in nothing needing to be done.
+
+*  Way less battery drain. The system is no longer forced either to remain
+   awake or to continually wake up after a rather brief nap.
+
+There are cases in which polling is still being used. Some of these will
+eventually be resolved by future code changes, while others, unfortunately, are
+required due to host platform limitations.
+
+.. topic:: Monitoring for Screen Updates
+
+   This task involves checking for size changes, content changes, highlighting
+   changes, cursor movement, etc. It's event-based on:
+
+   *  Android
+
+   *  Linux text consoles if the kernel is at least 2.6.37 (released on January 4,
+      2011)
+
+   *  Linux graphics consoles monitored via AT-SPI2.
+
+   On other platforms, |frequent polling| is used.
+
+.. topic:: Monitoring for Serial Input
+
+   This task is event-based on:
+
+   *  Linux
+
+   On other platforms, |frequent polling| is used.
+
+.. topic:: Monitoring for USB Input
+
+   This task is event-based on:
+
+   *  Linux
+
+   On other platforms, |frequent polling| is used.
+
+.. topic:: Monitoring for Bluetooth Input
+
+   Tghis task is event-based on:
+
+   *  Android
+
+   *  Linux
+
+   On other platforms, |frequent polling| is used.
+
+.. topic:: Starting the Braille Driver
+
+   On all platforms, |periodic retrying| is used.
+
+.. topic:: Starting the Speech Driver
+
+   On all platforms, |periodic retrying| is used.
+
+.. topic:: Starting the Screen Driver
+
+   On all platforms, |periodic retrying| is used.
+
+.. topic:: Starting the Keyboard Monitor
+
+   On all platforms, |periodic retrying| is used.
+
+.. topic:: Creating the PID File
+
+   On all platforms, |periodic retrying| is used.
+
+.. |frequent polling| replace:: frequent polling (about every 40 milliseconds)
+.. |periodic retrying| replace:: periodic retrying (about every five seconds)
diff --git a/Documents/README.PrivacyPolicy b/Documents/README.PrivacyPolicy
new file mode 100644
index 0000000..ca1e802
--- /dev/null
+++ b/Documents/README.PrivacyPolicy
@@ -0,0 +1,81 @@
+~~~~~~~~~~~~~~
+Privacy Policy
+~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+General Statements
+==================
+
+BRLTTY is a screen reader for braille users.
+It doesn't introduce any content of its own
+- it just renders, in braille, whatever is on the user's screen.
+It's just as safe to use by anyone of any age, therefore,
+as the host (computer, phone, tablet, etc) itself is.
+
+We're unaware of any law anywhere which would consider it inappropriate for a  child to be using BRLTTY.
+In fact, we believe that blind children should be encouraged to use it
+because we believe that literacy, i.e. the ability to read and write,
+is just as important for them as speech is.
+
+BRLTTY doesn't filter the screen's content.
+Any content that can be read on the screen by a sighted child
+is presented in braille and, therefore, is just as readable by a blind child.
+It's the responsibility of the developers of any given program, app, website, etc
+to control access to any questionable or reprehensible content
+that they feel the need to present.
+Likewise, if a parent has any concerns in this area
+then he/she should make use of any applicable parental controls
+that the host platform offers.
+
+If the braille device has a keyboard then it can be used for typing.
+This necessarily means that BRLTTY is transiently aware of
+any information that's being typed,
+including sensitive information such as passwords.
+This information is only retained long enough for,
+and exclusively for the purpose of,
+forwarding it through to the host as typed input.
+
+In addition to primarily being a screen reader for braille users,
+BRLTTY also contains support for reviewing the screen's content via speech.
+Unlike braille, where it's in direct control of the rendering,
+a third-party TTS (Text to Speech) engine is used to translate screen content into speech.
+We have no control over the privacy policies that apply to any of the third-party TTS engines.
+If a user wishes to use the speech capabilities of BRLTTY
+then he/she should become familiar with the privacy policy of the TTS being used.
+
+BRLTTY is an open source project.
+Suppliers (manufacturers, vendours, distributors, etc)
+should be aware that any code in any of its drivers
+that's necessary for the successful and effective operation of a braille device
+has necessarily become public.
+All other information that we (BRLTTY's developers) learn or become aware of
+(future plans, proprietary features, etc)
+while in discussions with or by reading documentation provided by any supplier
+isn't shared with the public.
+
+BRLTTY neither requests nor in any other way tries to obtain
+information about its users.
+Also, no information about its users, their host devices, etc
+that BRLTTY is either actively or passively aware of
+is shared with others (yes, this includes not even with us).
+
+While BRLTTY doesn't transmit any data,
+it does preserve the following as local data:
+
+* User configuration (also known as settings or preferences).
+* Operational logs that, by default, don't contain sensitive data like screen content, typed input, etc.
+
+If we, BRLTTY's developers, would like to have a look at
+debugging information in order to find the cause of a problem
+then we explain to the user which of BRLTTY's log categories would need to be enabled
+and what sensitive information would, therefore, become visible to us.
+It's still his/her own decision to enable the logging and to send the log to us.
+
+Android
+=======
+
+BRLTTY requests the following Android permissions:
+
+.. include:: android-permissions.rst
+
diff --git a/Documents/README.Profiles b/Documents/README.Profiles
new file mode 100644
index 0000000..b5b466c
--- /dev/null
+++ b/Documents/README.Profiles
@@ -0,0 +1,145 @@
+~~~~~~~~
+Profiles
+~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+A profile is an activatable group of settings which explicitly override the 
+ones that were established via command line options, the configuration file 
+(``brltty.conf``), the preferences file (``brltty.prefs``), etc when BRLTTY
+was invoked. Profiles must be placed in the ``/etc/brltty/Profiles/``
+directory (see |README.Customize| for more details).
+
+A profile must be encoded in UTF-8. Blank lines are ignored. If the first
+non-whitespace character of a line is ``#`` then that line is a comment and is
+ignored. The `Standard Directives`_ are supported. All other lines specify the
+settings that are to be overridden, and must be in the form:
+
+.. parsed-literal:: *name* *value*
+
+Each type of profile has the following properties:
+
+*  A specific group of settings that it can override.
+*  Its own file extension.
+*  Its own selector within BRLTTY's Profiles submenu.
+*  Its own SET_*_PROFILE command for direct selection (see
+   `Profile Selection`_).
+
+Supported Profile Types
+-----------------------
+
+Language Profiles
+~~~~~~~~~~~~~~~~~
+
+A language profile must have the ``.lpf`` file extension. It can override the 
+following settings:
+
+*  locale
+*  speech-driver
+*  speech-parameters
+*  text-table 
+*  contraction-table
+
+For example, a profile for the German language might look like this::
+
+   locale de_DE.UTF-8
+   speech-driver es
+   speech-parameters voice=de,maxrate=300
+   text-table de
+   contraction-table de-g2
+
+All of the speech driver parameters must be specified on a single line, and be
+separated from one another by a comma (``,``).
+
+Profile Selection
+-----------------
+
+Each type of profile has a selector within BRLTTY's Profiles submenu. The
+selector for a given profile type allows for setting that type of profile
+either to ``off`` (which means "no profile") or to any of the files in the 
+``/etc/brltty/Profiles/`` directory which have the file extension for that type
+of profile. The file names are sorted alphabetically.
+
+If you'd like to select a profile directly (rather than use the Profiles 
+submenu) then you can define key bindings for the appropriate ``SET_*_PROFILE`` 
+command. For example, if you'd like to directly select your language profile 
+then you'd define bindings for the ``SET_LANGUAGE_PROFILE`` command. You may
+add your bindings either to the keyboard table that you're using or to the key 
+table for your braille device. Except for a few examples below, exactly how to 
+do this is beyond the scope of this document (see |README.KeyTables| for
+details).
+
+One approach is to define a single binding which involves a routing key. For 
+example::
+
+   bind Key1+Key2+RoutingKey SET_LANGUAGE_PROFILE
+
+Using this paradigm, the leftmost routing key means to use no language profile, 
+and the next few routing keys select each of your language profiles in the same 
+order as they appear within the Language selector of the Profiles submenu.
+
+Another approach is to define a specific binding for each language profile. For 
+example::
+
+   bind Key1+Key2 SET_LANGUAGE_PROFILE+0
+   bind Key1+Key3 SET_LANGUAGE_PROFILE+1
+   bind Key2+Key3 SET_LANGUAGE_PROFILE+2
+   bind Key1+Key2+Key3 SET_LANGUAGE_PROFILE+3
+
+Using this paradigm, the binding for ``+0`` (Key1+Key2) means to use no
+language profile, and the bindings for ``+1`` (Key1+Key3), ``+2`` (Key2+Key3),
+etc select each of your language profiles in the same order as they appear
+within the Language selector of the Profiles submenu.
+
+A practical approach, which would make it easy to remember the bindings, might 
+be to use a keyboard table, and to use a simple key combination plus a 
+meaningful letter for each language name. Let's say that your primary language 
+is English, and that you also use French and German. In this case, your default 
+settings (no profile) would be for English, and you'd create french.lpf for 
+French and german.lpf for German. You could then define a set of bindings which 
+use ``e`` for English, ``f`` for French, and ``g`` for German. For example::
+
+   bind ShiftLeft+ShiftRight+!e SET_LANGUAGE_PROFILE+0
+   bind ShiftLeft+ShiftRight+!f SET_LANGUAGE_PROFILE+1
+   bind ShiftLeft+ShiftRight+!g SET_LANGUAGE_PROFILE+2
+
+The ``+1`` binding would activate ``french.lpf`` and the ``+2`` binding would
+activate ``german.lpf`` because that's how the profile name list would be
+sorted. If, however, you choose to use the native language names then you'd
+have ``deutsch.lpf`` for German and ``français.lpf`` for French. In this case,
+you'd need a different set of bindings because ``deutsch.lpf`` (for German)
+sorts before ``français.lpf`` (for French)::
+
+   bind ShiftLeft+ShiftRight+!e SET_LANGUAGE_PROFILE+0
+   bind ShiftLeft+ShiftRight+!d SET_LANGUAGE_PROFILE+1
+   bind ShiftLeft+ShiftRight+!f SET_LANGUAGE_PROFILE+2
+
+When adding the bindings to a key table, they should most likely be defined 
+within the default context. The best way to ensure this is to add them at the 
+very end of the appropriate key table or subtable, and to add the ``context 
+default`` statement just before them. For example::
+
+   context default
+   bind Key1+RoutingKey SET_LANGUAGE_PROFILE
+
+If you'd like your additional binding definitions to survive a BRLTTY reinstall
+then you should use a customized key table or subtable (see |README.Customize|)
+that includes the provided one, and then defines your additiional bindings.
+Let's say that you'd like to add your bindings to the ``laptop`` keyboard table
+(``laptop.ktb``). Create a file with the same name - ``laptop.ktb`` - in the
+``/etc/xdg/brltty/`` directory, and, with an editor, make it look like this::
+
+   include laptop.ktb
+   context default
+   bind Key1+Key2+RoutingKey SET_LANGUAGE_PROFILE
+
+Standard Directives
+===================
+
+.. include:: nesting-directives.rst
+.. include:: variable-directives.rst
+.. include:: condition-directives.rst
+
diff --git a/Documents/README.Stow b/Documents/README.Stow
new file mode 100644
index 0000000..0db26ca
--- /dev/null
+++ b/Documents/README.Stow
@@ -0,0 +1,127 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Managing BRLTTY with Stow
+~~~~~~~~~~~~~~~~~~~~~~~~~
+:Author: B Daix
+:Date: 2003-09-10
+
+.. include:: prologue.rst
+
+Purpose
+=======
+
+BRLTTY is a critical software for those who rely on it to access their box.
+Upgrading such a tool can't be done without paying some special attention.
+
+What will be explained here is how to keep ``/usr/local/bin/brltty`` up to date
+without removing old versions.  We decide to put BRLTTY in ``/usr/local`` as
+we don't want to break filesystem root distribution (i.e. ``/bin/brltty``
+should only come from a packaged version of BRLTTY for your distribution. If
+you install software in respect with this rule, you'd install it in
+``/usr/local``). Also, we want not to have to keep source files to be able to
+remove things cleanly. After installing a software, you shouldn't have to rely
+on a by-software uninstallation suite.
+
+You should install Stow to be able to run it and to find more details about it.
+It's a common utility:
+
+On Debian:
+   ``apt-get install stow``
+
+On Fedora:
+   ``yum install stow``
+
+Why Stow can help
+=================
+
+Stow allows to manage files like if they were packaged, by creating symlinks
+from by-directory grouped sets of files instead of copying them directly.
+
+We recommend to attentively read Stow Info files, to realize what it can do,
+how, and specially why it is so useful when you have to manage personally
+compiled software you rely on.
+
+The procedure
+=============
+
+So we want to run a BRLTTY version, taken from a set of personally compiled
+ones, and safely keep all of them around. Current working release will be run
+from ``/usr/local`` root, while all releases files will be kept in the "safe
+place", the **Stow directory** ``/usr/local/stow/``. For example, releases
+(bins/configs/libs/mans, not source trees) will be stored there like this:
+
+*  ``/usr/local/stow/brltty-3.2/``
+*  ``/usr/local/stow/brltty-3.3-with-api/``
+*  ``/usr/local/stow/brltty-3.3/``
+*  and so on
+
+Compiling/installing BRLTTY for Stow file management
+----------------------------------------------------
+
+We have to use ``configure`` flags to indicate we want BRLTTY to be run
+From ``/usr/local`` root, and installed in ``/usr/local/stow/brltty-3.2/``
+root. Stow will do symlinks for us then. The distinction between execution root
+and installation root is critical (c.f. Stow Info)::
+
+   ./configure --with-execute-root=/usr/local \
+               --with-install-root=/usr/local/stow/brltty-3.2
+
+You may add whatever flags then, except those which change directories as we
+already managed this.
+
+Then, simply::
+
+   make
+   make install
+
+The command::
+
+   ls /usr/local/stow/brltty-3.2/
+
+should show you all what would have been put in ``/usr/local`` if we haven't
+managed files with Stow.
+
+Reproduce this for each version you want to install, for example, we built a
+brltty-3.3-with-api directory by applying above principles and adding dedicated
+flag for enabling API to ``configure`` command line. We did the same for a
+standard brltty-3.3 too, and so on, all stored in ``/usr/local/stow/``. As you
+see, we cleanly separate things - one directory per version on one hand,
+building vs. using on the other. Testing is now far lighter.
+
+Managing safely kept versions with Stow
+---------------------------------------
+
+Before asking for Stow to create symlinks, we have to be sure we won't get any
+conflicts, i.e. we haven't already asked for Stow to create symlinks on the
+same software without removing them first, or we don't forget we have already
+installed BRLTTY directly::
+
+   ls /usr/local/{bin,sbin}
+
+should be enough to see if we have to clean things up before going any further.
+Note we'll see how to use Stow to remove created symlinks just after explaining
+how to ask for it to create them (we hope you read from up to below...)::
+
+   cd /usr/local/stow
+   stow brltty-3.2
+
+will create the symlinks, in parent directory. For example:
+
+*  ``ln -s brltty-3.2/bin ../bin`` (if ``../bin`` didn't exist)
+*  ``ln -s brltty-3.2/bin/brltty ../bin/brltty`` (otherwise)
+*  and so on
+
+Now, running ``/usr/local/bin/brltty`` will work perfectly, looking for its
+config files in ``/usr/local/etc/brltty/`` and so on, even if it's all
+symlinks - as if BRLTTY-3.2 was installed there.
+
+When you want to switch to another version, you simply have to ask for Stow to
+remove symlinks it created::
+
+   cd /usr/local/stow/
+   stow -D brltty-3.2 # if current release points to this version
+
+and then::
+
+   stow brltty-3.3-with-api
+
+Doesn't it look powerful?!
diff --git a/Documents/README.Systemd b/Documents/README.Systemd
new file mode 100644
index 0000000..8d1dd07
--- /dev/null
+++ b/Documents/README.Systemd
@@ -0,0 +1,116 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Using Systemd Service Management
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+.. |configuration directory| replace:: /usr/lib
+.. |unit directory| replace:: |configuration directory|/systemd/system
+.. |users directory| replace:: |configuration directory|/sysusers.d
+.. |files directory| replace:: |configuration directory|/tmpfiles.d
+.. |commands directory| replace:: /usr/libexec/brltty
+.. |build subdirectory| replace:: Autostart/Systemd
+.. |default configuration file| replace:: ``/etc/brltty.conf``
+
+Initial Setup
+=============
+
+The following steps need to be performed:
+
+* Systemd service and path units need to be installed
+  into the |unit directory| directory:
+
+  + brltty@.service
+  + brltty-device@.service
+  + brltty.path
+  + brltty@.path
+
+* A wrapper script needs to be installed
+  into the |commands directory| directory:
+
+  + systemd-wrapper
+
+* A file defining the non-root user that BRLTTY is to run as,
+  including the supplementary groups that it needs access to,
+  needs to be installed into the |users directory| directory:
+
+  + sysusers -> brltty.conf
+
+* A file defining the files and directories that should exist
+  needs to be installed into the |files directory| directory:
+
+  + tmpfiles -> brltty.conf
+
+A make file has been provided in order to make this easy to do.
+It can be found within the |build subdirectory| subdirectory
+of BRLTTY's build tree.
+To install all of these files, change to this directory and run the command::
+
+  make install
+
+You then need to tell Systemd that the files have been installed.
+To do this, run the command::
+
+  systemctl daemon-reload
+
+That's all you need to do.
+Your system is now able to manage BRLTTY instances via Systemd.
+
+Managing BRLTTY Instances
+=========================
+
+Systemd manages BRLTTY instances on your system in a number of ways.
+
+USB Braille Devices
+-------------------
+
+If BRLTTY's Udev rules have also been installed
+then a BRLTTY instance will be automatically started
+when a USB braille device is connected,
+and automatically stopped when it's disconnected.
+Several braille devices can be managed in this way at the same time.
+They can be connected and disconnected at any time and in any order.
+
+The default configuration file - |default configuration file| - is used
+except that any ``braille-device`` and ``braille-driver`` directives
+that it specifies are ignored because they're overridden by the Udev rules.
+It's safe, therefore, to specify a default non-USB braille device
+within |default configuration file|.
+
+The Default Instance
+--------------------
+
+The default BRLTTY instance is the one that's configured
+via the file |default configuration file|.
+It's managed by applying standard Systemd commands to BRLTTY's path unit.
+For example::
+
+  systemctl enable brltty.path
+  systemctl start brltty.path
+  systemctl stop brltty.path
+  systemctl disable brltty.path
+
+Additional Instances
+--------------------
+
+Additional BRLTTY instances can be managed via path instance references.
+Each of them has its own configuration file.
+
+If, for example, the name of an instance is ``iname``, then
+its Systemd path name would be ``brltty@iname``,
+its configuration file would be ``/etc/brltty_iname.conf``,
+and it'd be managed via Systemd commands like these::
+
+  systemctl enable brltty@iname.path
+  systemctl start brltty@iname.path
+  systemctl stop brltty@iname.path
+  systemctl disable brltty@iname.path
+
+Any number of instances may be managed in this way.
+These instances don't implicitly also read the default configuration file
+(|default configuration file|).
+You can, however, explicitly include it
+from within any instance-specific configuration file:
+
+.. parsed-literal:: include |default configuration file|
+
diff --git a/Documents/README.TextTables b/Documents/README.TextTables
new file mode 100644
index 0000000..dde711d
--- /dev/null
+++ b/Documents/README.TextTables
@@ -0,0 +1,276 @@
+~~~~~~~~~~~
+Text Tables
+~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+Description
+===========
+
+Files with names of the form ``*.ttb`` are text tables, and with names of the
+form ``*.tti`` are text subtables. They are used by BRLTTY to translate the
+characters on the screen into their corresponding 8-dot computer braille
+representations (see |README.BrailleDots| for details).
+
+Text tables can usually be found in the ``/etc/brltty/Text/`` directory (see
+|README.Customize| for more details). See `Text Table List`_ for a list of
+BRLTTY's text tables.
+
+A text table consists of a sequence of directives, one per line, which define 
+how each character is to be represented in braille. UTF-8 character encoding 
+must be used. Whitespace (blanks, tabs) at the beginning of a line, as well as 
+before and/or after any operand of any directive, is ignored. Lines containing 
+only whitespace are ignored. If the first non-whitespace character of a line 
+is ``#`` then that line is a comment and is ignored.
+
+The precedence for determining the braille representation for a character is as
+follows:
+
+1) If the character is within the Unicode braille Row (U+2800 through U+28FF)
+   then its low-order eight bits are used as follows:
+
+   ===  ========  ===
+   Hex  Binary    Dot
+   ---  --------  ---
+   01   00000001  1
+   02   00000010  2
+   04   00000100  3
+   08   00001000  4
+   10   00010000  5
+   20   00100000  6
+   40   01000000  7
+   80   10000000  8
+   ===  ========  ===
+
+2) If the character is within the Unicode private use row U+F000 through U+F0FF
+   then its low-order byte is:
+
+   A) interpreted as a single-byte character within the locAL character set
+   B) translated to Unicode
+   C) reprocessed
+
+3) An explicit representation (defined via `The Char Directive`_,
+   `The Glyph Directive`_, or `The Byte Directive`_.
+
+4) An equivalent representation (defined via `The Alias Directive`_).
+
+5) The explicit representation of the Unicode base character.
+
+6) The explicit representation of the transliterated ASCII character.
+
+7) The explicit representation of the Unicode Replacement Character (U+FFFD).
+
+8) The explicit representation of a question mark (``?``).
+
+9) All eight braille dots.
+
+Directives
+==========
+
+.. |character operand| replace::
+   The character being defined.
+   See `The String Operand`_ for details.
+
+.. |byte operand| replace::
+   The character being defined.
+   It may be specified in any of the ways supported by `The String Operand`_
+   except that the Unicode-specific forms (``\u``, ``\U``, ``\<``) may not be used.
+
+.. |dots operand| replace::
+   The braille representation for the character being defined.
+   See `The Dots Operand`_ for details.
+
+.. |cell operand| replace::
+   The braille representation for the character being tested.
+   See `The Cell Operand`_ for details.
+
+The Char Directive
+------------------
+
+.. parsed-literal:: char *character* *dots* # *comment*
+
+Use this directive to specify how a Unicode character is to be represented in
+braille. A character defined via this directive can also be entered from a
+braille keyboard. If several characters have the same braille representation
+then only one of them should be defined via this directive - the others should
+be defined via `The Glyph Directive`_ (which has the same syntax). If more than
+one character with the same braille representation is defined via this
+directive (which is allowed for backward compatibility) then the first one is
+used when entered from a braille keyboard.
+
+*character*
+   |character operand|
+
+*dots*
+   |dots operand|
+
+Examples::
+
+   char a 1
+   char b (12)
+   char c ( 4  1   )
+   char \\ 12567
+   char \s 0
+   char \x20 ()
+   char \<LATIN_SMALL_LETTER_D> 145
+
+The Glyph Directive
+-------------------
+
+.. parsed-literal:: glyph *character* *dots* # *comment*
+
+Use this directive to specify how a Unicode character is to be represented in
+braille. A character defined via this directive is output-only - it can't
+be entered from a braille keyboard.
+
+*character*
+   |character operand|
+
+*dots*
+   |dots operand|
+
+See `The Char Directive`_ for examples.
+
+The Input Directive
+-------------------
+
+.. parsed-literal:: input *character* *dots* # *comment*
+
+Use this directive to specify how a Unicode character is to be entered from a
+braille keyboard. A character defined via this directive is input-only - its
+actual braille representation isn't defined.
+
+*character*
+   |character operand|
+
+*dots*
+   |dots operand|
+
+See `The Char Directive`_ for examples.
+
+The Alias Directive
+-------------------
+
+.. parsed-literal:: alias *from* *to* # *comment*
+
+Use this directive to define the *from* Unicode character such that it has the
+same braille representation as the *to* Unicode character. See
+`The String Operand`_ for details on how to specify both operands.
+
+The Byte Directive
+------------------
+
+.. parsed-literal:: byte *byte* *dots* # *comment*
+
+Use this directive to specify how a character in the local character set is to
+be represented in braille. It has been retained for backward compatibility but
+should not be used. Unicode characters should be defined (via either
+`The Char Directive`_ or `The Glyph Directive`_) so that the text table remains
+valid regardless of what the local character set is.
+
+*byte*
+   |byte operand|
+
+*dots*
+   |dots operand|
+
+The IfGlyph Directive
+---------------------
+
+.. parsed-literal:: ifGlyph *character* *directive*
+
+Use this directive to only process one or more directives if a character has a
+braille representation.
+
+*character*
+   |character operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifGlyph C alias \u2103 C # degree Celsius
+
+The IfNotGlyph Directive
+------------------------
+
+.. parsed-literal:: ifNotGlyph *character* *directive*
+
+Use this directive to only process one or more directives if a character
+doesn't have a braille representation.
+
+*character*
+   |character operand|
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifNotGlyph \s glyph \s 0
+
+The IfInput Directive
+---------------------
+
+.. parsed-literal:: ifInput *cell* *directive*
+
+Use this directive to only process one or more directives if a character can be
+entered from a braille keyboard.
+
+*cell*
+   |cell operand|
+
+*directive*
+   |directive operand|
+
+The IfNotInput Directive
+------------------------
+
+.. parsed-literal:: ifNotInput *cell* *directive*
+
+Use this directive to only process one or more directives if a character can't
+be entered from a braille keyboard.
+
+*cell*
+   |cell operand|
+
+*directive*
+   |directive operand|
+
+.. include:: nesting-directives.rst
+.. include:: variable-directives.rst
+.. include:: condition-directives.rst
+
+Operands
+========
+
+.. include:: string-operand.rst
+
+The Dots Operand
+----------------
+
+A *dots* operand is a sequence of one to eight dot numbers. If the dot numbers
+are enclosed within (parentheses) then they may be separated from one another
+by whitespace. A dot number is a digit within the range ``1``-``8``
+as defined by the standard braille dot numbering convention (see
+|README.BrailleDots| for details). The special dot number ``0`` is recognized
+when not enclosed within (parentheses), and means no dots - it may not be used
+in conjunction with any other dot number.
+
+The Cell Operand
+----------------
+
+A *cell* operand is a sequence of one to eight dot numbers. A dot number is
+a digit within the range ``1``-``8`` as defined by the standard braille dot
+numbering convention (see |README.BrailleDots| for details). The special dot
+number ``0`` is recognized, and means no dots - it may not be used in
+conjunction with any other dot number.
+
+Text Table List
+===============
+
+.. csv-table::
+   :header-rows: 1
+   :file: text-table.csv
+
diff --git a/Documents/README.Upstart b/Documents/README.Upstart
new file mode 100644
index 0000000..6299dec
--- /dev/null
+++ b/Documents/README.Upstart
@@ -0,0 +1,41 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Using Upstart Service Management
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+
+An Upstart job for BRLTTY can be found within the ``Autostart/Upstart/`` 
+subdirectory of BRLTTY's source tree. It's called ``brltty.conf``. All you need
+to do is to copy this file into Upstart's jobs directory.
+
+Upstart's jobs directory has changed over time. Older Upstart releases use
+``/etc/event.d/`` whereas newer ones use ``/etc/init/``. You'll need to check
+your system to see which of these directories it's using. Don't get confused by
+the directory ``/etc/init.d/``, which your system probably also has, and which
+has a similar name. It's used for something entirely different - SysV init
+scripts.
+
+The file also needs to have the correct name. Older Upstart releases just use 
+the job name itself, e.g. ``brltty``, whereas newer ones append the ``.conf`` 
+extension, e.g. ``brltty.conf``.
+
+So, to define BRLTTY's Upstart job, you'll need to do something like this:
+
+*  For an older Upstart release::
+
+      cp brltty.conf /etc/event.d/brltty
+
+*  For a newer Upstart release::
+
+      cp brltty.conf /etc/init/brltty.conf
+
+Once the job file is in the right place, BRLTTY will automatically start when 
+the system is rebooted. You'll also be able to use standard Upstart commands to 
+manage the ``brltty`` job. The most common ones are::
+
+   status brltty
+   start brltty
+   stop brltty
+
+For example: You don't need to reboot the system in order to start BRLTTY.
+Just use Upstart's ``start`` command to start it right away.
diff --git a/Documents/README.Windows b/Documents/README.Windows
new file mode 100644
index 0000000..5fcecfe
--- /dev/null
+++ b/Documents/README.Windows
@@ -0,0 +1,745 @@
+~~~~~~~~~~~~~~~~~
+BRLTTY on Windows
+~~~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+.. _Cygwin's Web Site: http://www.cygwin.com/
+.. _MinGW's Web Site: http://mingw.sourceforge.net/
+.. _LibUSB-Win32's Web Site: http://libusb-win32.sourceforge.net/
+.. _LibUSB-1.0's Web Site: http://www.libusb.org/wiki/windows_backend
+.. _Cygwin Ports: http://cygwinports.org/
+.. _Cygwin java-ecj jar: ftp://sourceware.org/pub/java/ecj-4.5.jar
+
+Building BRLTTY
+===============
+
+System Requirements
+-------------------
+
+The earliest release of Windows that is supported by BRLTTY is Windows 98.
+
+Pre-built versions of BRLTTY are available for download on
+`BRLTTY's Web Site`_. Their names begin with ``brltty-win-``. Those with the
+``.zip`` extension are archives, and those with the ``.exe`` extension are
+installers.
+
+In order to build BRLTTY yourself from a prepared source tarball, you'll need
+Cygwin (see `Cygwin's Web Site`_) and/or MinGW (see `MinGW's Web Site`_).
+You'll also need the following additional packages:
+
+*  gcc
+*  make
+*  w32api (version 3.6 or later)
+
+If you'd like to build BRLTTY from its source repository (rather than from one
+of the prepared source tarballs) then you'll also need these packages:
+
+*  autoconf
+*  tcltk
+
+If you'd like to prepare the documentation (by doing ``make`` within the
+``Documents/`` subdirectory) then you'll need these packages:
+
+*  linuxdoc-tools (not pre-packaged in Cygwin)
+*  doxygen
+
+If you're using MSYS (the MinGW command shell) for running ``configure`` and
+``make``, you should always use Windows-like paths (e.g. ``c:/brltty``) rather
+than MSYS paths (e.g. ``/c/brltty``) because BRLTTY does **not** understand
+MSYS paths.
+
+Windows 98 and ME Limitation
+----------------------------
+
+On Windows versions 2000, XP, 2003, and later, BRLTTY automatically accesses
+the Windows console that you're currently using as you switch between them.
+This isn't possible on earlier versions. One way to still achieve this
+functionality, though, is to run one BRLTTY on the root window that directly
+accesses your braille device, and another one on each console that indirectly
+accesses your braille device via BrlAPI. This scheme may sound complicated,
+but it can be set up to run automatically.
+
+The first (or root) BRLTTY should be run as part of Windows startup. It must be
+run with the options that are necessary for it to access your braille device,
+e.g. ``-b`` (to specify the driver), ``-d`` (to specify the device), and
+(maybe) ``-B`` (to specify driver-sspecific parameters). It must also be given
+the ``-Xroot=yes`` option (which attaches it to the root window).
+
+An additional BRLTTY must then be run for each new console. It should be
+invoked like this::
+
+   brltty -bba -N
+
+The ``-bba`` option tells it to access the root BRLTTY via BrlAPI, and the
+``-N`` option tells it not to start a BrlAPI server of its own.
+
+These (non-root) BRLTTYs can be started automatically by, for example, invoking
+them from your ``.bashrc`` script. Each of these BRLTTYs only reviews the
+console it's running within, and connects, via BrlAPI, to the root BRLTTY in
+order to access your braille device.
+
+If you're not concerned with security, and would rather not fiddle with the
+``brlapi.key`` file, then add the ``-Aauth=none`` option to the command line
+that starts the root BRLTTY. You don't need to worry about unauthorized access
+over the network since the default is that only locally running programs can
+connect to BrlAPI.
+
+Package Management via the Command Line
+---------------------------------------
+
+Managing Cygwin Packages
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+A convenient way to manage Cygwin packages from the command line
+is to use the ``apt-cyg`` command.
+It's similar to the ``apt-get`` command which some Linux distributions use.
+
+As of the time of this writing, this is how you can install it::
+
+   svn --force export http://apt-cyg.googlecode.com/svn/trunk/ /usr/local/bin/
+   chmod +x /usr/local/bin/apt-cyg
+
+If you're using the 64-bit Cygwin platform, then, after installing apt-cyg,
+you'll need to make a couple of simple modifications to it. Using your
+favourite text editor, and remembering that the actual line numbers within your
+version may differ from those shown below, edit ``/usr/local/bin/apt-cyg`` and
+make the following changes:
+
+1) On line 98, change ``$mirror/setup.bz2`` to ``$mirror/x86_64/setup.bz2``.
+
+2) On line 105, change ``$mirror/setup.ini`` to ``$mirror/x86_64/setup.ini``.
+
+To search for a package, you can do:
+
+.. parsed-literal:: apt-cyg find *pattern*
+
+To install a package, you can do:
+
+.. parsed-literal:: apt-cyg install *name*
+
+For full details, you can do:
+
+.. parsed-literal:: apt-cyg help
+
+Managing MinGW Packages
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A convenient way to manage MinGW packages from the command line
+is to use the ``mingw-get`` command.
+Be sure to invoke it with arguments because
+when invoked with no arguments its graphical interface is used.
+
+To list all of the packages, you can do:
+
+.. parsed-literal:: mingw-get list
+
+To install a package, you can do:
+
+.. parsed-literal:: mingw-get install *name*
+
+For full details, you can do:
+
+.. parsed-literal:: mingw-get -h
+
+USB Support
+-----------
+
+USB devices are supported thanks to the ``LibUSB-Win32`` and the ``LibUSB-1.0``
+packages. If both are installed, preferrence is given to ``LibUSB-1.0``.
+
+Pre-built versions of BRLTTY have USB support compiled in, and the required
+Windows drivers are also included, so you just need to let the installer set
+them up.
+
+In order to build BRLTTY yourself with USB support enabled, you'll need to
+**first** install at least one of LibUSB-Win32 or LibUSB-1.0.
+
+LibUSB-Win32
+~~~~~~~~~~~~
+
+At the time of this writing, LibUSB-Win32 binaries can be found on
+`LibUSB-Win32's Web Site`_. They'll be named something like
+``libusb-win32-bin-<version>.exe``, and should be available on
+``http://sourceforge.net/project/showfiles.php?group_id=78138``.
+
+*  On Cygwin:
+
+   1) Install the ``libusb-win32`` package.
+
+*  On MinGW:
+
+   1) Unpack the archive somewhere.
+
+   2) Symlink the header and library files into your MinGW
+      installation::
+
+         ln -s LibUSB-Win32/include/lusb0_usb.h /mingw/include/usb.h
+         ln -s LibUSB-Win32/lib/gcc/libusb.a /mingw/lib/
+         ln -s LibUSB-Win32/bin/x86/libusb0_x86.dll /mingw/bin/libusb0.dll
+
+In order to be able to use the LibUSB-Win32 driver, you'll need to copy its
+run-time files into BRLTTY's source tree::
+
+   cp LibUSB-Win32/bin/x86/libusb0.sys brltty/hotplug/libusb0.sys
+   cp LibUSB-Win32/bin/x86/libusb0_x86.dll brltty/hotplug/libusb0.dll
+   cp LibUSB-Win32/bin/amd64/libusb0.sys brltty/hotplug/libusb0_x64.sys
+   cp LibUSB-Win32/bin/amd64/libusb0.dll brltty/hotplug/libusb0_x64.dll
+
+Then, either right-click on ``brltty/hotplug/brltty.inf`` and select
+``install``, or, on braille device plug, point at the
+``brltty/hotplug/brltty.inf`` file.
+
+LibUSB-1.0
+~~~~~~~~~~
+
+As of the time of this writing, LibUSB-1.0 binary snapshots can be found on
+`LibUSB-1.0's Web Site`_. They'll be named something like
+``libusb_<date>.7z``.
+
+*  On Cygwin:
+
+   1) Install the ``libusb1.0-devel`` package.
+
+*  On MinGW:
+
+   1) Unpack the archive somewhere.
+
+   2) Symlink the header and library files into your MinGW
+      installation::
+
+         ln -s LibUSB-1.0/include/libusbx-1.0 /mingw/include/libusb-1.0
+         ln -s LibUSB-1.0/MinGW32/dll/libusb-1.0.dll.a /mingw/lib/
+         ln -s LibUSB-1.0/MinGW32/dll/libusb-1.0.dll /mingw/bin/
+
+   3) Copy the file ``libusb-1.0.pc`` in the ``Windows/`` subdirectory of
+      BRLTTY's source tree into MinGW's ``/mingw/lib/pkgconfig/`` directory. If
+      the ``pkgconfig/`` subdirectory doesn't already exist then create it.
+
+In order to be able to use the LibUSB-1.0 driver, you'll need to either
+right-click on ``brltty/hotplug/brltty-libusb-1.0.inf`` and select ``install``,
+or, on braille device plug, point at the
+``brltty/hotplug/brltty-libusb-1.0.inf`` file.
+
+Configuring a BRLTTY Build
+--------------------------
+
+Some of BRLTTY's configure options are of particular interest to users of the 
+Windows platform:
+
+--enable-relocatable-install
+   The default is for BRLTTY to refer to its components via absolute paths. On
+   the Windows platform, however, the convention is for a package to use
+   relative paths so that it's entirely self-contained. This enables it to be
+   installed into an arbitrary directory, and to be moved around thereafter at
+   well. This option builds BRLTTY such that relative paths are used.
+
+Missing Java Class Definitions on Cygwin
+----------------------------------------
+
+You may get a Java failure that looks something like this::
+
+   Exception in thread "main" java.lang.NoClassDefFoundError:
+   org.eclipse.jdt.internal.compiler.batch.GCCMain
+      at gnu.java.lang.MainThread.run(Unknown Source)
+   Caused by: java.lang.ClassNotFoundException:
+   org.eclipse.jdt.internal.compiler.batch.GCCMain
+   not found in gnu.gcj.runtime.SystemClassLoader
+   {urls=[], parent=gnu.gcj.runtime.ExtensionClassLoader{urls=[], parent=null}}
+
+This problem occurs when the ``java-ecj`` package isn't installed. It can be
+obtained from `Cygwin Ports`_. You can also resolve the probem by downloading
+the `Cygwin java-ecj jar`_, and installing it into ``/usr/share/java/ecj.jar``.
+
+Using BRLTTY
+============
+
+Windows Scripts for Managing BRLTTY
+-----------------------------------
+
+These ``.bat`` scripts are in the top-level folder where BRLTTY has been installed.
+They should be run by a user that has administrative privileges.
+
+``run-brltty.bat``
+~~~~~~~~~~~~~~~~~~
+
+Manually run BRLTTY with a log level of ``info``.
+Logs are written to the file ``brltty.log``
+in the top-level folder where BRLTTY has been installed.
+All arguments are passed through to BRLTTY.
+
+``debug-brltty.bat``
+~~~~~~~~~~~~~~~~~~~~
+
+Manually run BRLTTY, via the ``run-brltty.bat`` script, with options that
+set the log level to ``debug`` and enable several useful log categories.
+All arguments are passed through to the ``run-brltty.bat`` script.
+
+``kill-brltty.bat``
+~~~~~~~~~~~~~~~~~~~
+
+Forceably terminate the manually-run BRLTTY process.
+
+``enable-brlapi.bat``
+~~~~~~~~~~~~~~~~~~~~~~
+
+Install the BrlAPI service and start it.
+
+``disable-brlapi.bat``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Stop the BrlAPI service and uninstall it.
+
+Sticking to a Console
+---------------------
+
+It may be useful to have BRLTTY only review the console that it's started in,
+i.e.  for it not to follow the keyboard focus. To achieve this, set the
+``FollowFocus`` parameter of BRLTTY's ``Windows`` screen driver to ``no``. This
+can be done:
+
+On the Command Line:
+   ``-Xwn:FollowFocus=no``
+
+In ``brltty.conf``:
+   ``screen-parameter wn:FollowFocus=no``
+
+Sharing the Braille Device with Other Screen Readers
+----------------------------------------------------
+
+When you're not on a window that BRLTTY can handle, its default action is to
+retain control of the braille device and to present a short message explaining
+the problem. If you have another braille-capable screen reader and would like
+it to take over instead, then both BRLTTY and that other screen reader must be
+instructed to share the braille device.
+
+BRLTTY can be instructed to share the braille device via its
+``--release-device`` option (the short form of this option is ``-r``). When
+this option is in effect, BRLTTY releases the braille device when you move onto
+a window that it can't handle, and then tries to regain control of the braille
+device when you move onto a window that it can handle. Note that these actions
+take a noticeable amount of time so you should only use this option if it's
+actually needed.
+
+Sharing with JAWS
+~~~~~~~~~~~~~~~~~
+
+A common case wherein a JAWS user might want BRLTTY to be in control of the
+braille device when on a console window is when using Cygwin.
+
+There are two phases to configuring JAWS to run in the background while BRLTTY
+controls the braille device. First, a usable window title must be established
+and stable. Second, JAWS braille must be put to sleep.
+
+.. topic:: What is a Window Title?
+
+   Every window in Windows has a title bar that contains the name of the
+   application that's running in it, as well as some controls to do things like
+   move and resize the window. BRLTTY doesn't show this title bar.
+
+   For a program window, JAWS uses the name of the program's executable as the
+   name of the configuration files to load when that program gains focus. For a
+   console application such as Cygwin, however, it uses the title of the window
+   instead. You must, therefore, tell JAWS within the window title that this is
+   a Cygwin window.
+
+.. topic:: Setting the Window Title
+
+   JAWS uses one of the words in the window title as the name of the
+   configuration file set to load. This file set is what tells JAWS the
+   specifics of how to handle the application. It is, therefore, where JAWS
+   must be instructed to put its braille component to sleep.
+
+   As of this writing, it appears that JAWS uses the following algorithm for
+   choosing which word in the title to use as the name of the file set:
+
+   1) If there are no slashes (``/``) or backslashes (``\``) in the title then
+      JAWS uses the first word. Thus, if the title is ``Cygwin Bash Shell``
+      then JAWS will load the ``Cygwin`` configuration file set.
+
+   2) If there's at least one slash (``/``) or backslash (``\``) in the title
+      then JAWS uses the last word. Thus, if the title is ``$PWD - Cygwin``
+      then JAWS will similarly load the ``Cygwin`` configuration file set.
+
+.. topic:: Setting Cygwin's Window Title
+
+   First, it is imperative that you replace, or at least modify, the default
+   ``PS1`` (primary shell prompt) setting. The default for this setting, as
+   distributed by Cygwin, places ``$PWD`` (the path to the current working
+   directory) in the window title, thus requiring you to have a separate JAWS
+   configuration for every directory on the system! One possible way to resolve
+   this "problem" is to uncomment the ``settitle`` function (which can be found
+   near the end of ``.bashrc``). This function allows you to place a string of
+   your own choice in the title. You can use this function, therefore, as
+   follows::
+
+      export PROMPT_COMMAND='settitle "$PWD - Cygwin"'
+      export PS1='$ '
+
+   The first of these lines causes the window title to be set just before each
+   shell prompt. Since ``$PWD`` always contains at least one slash (``/``), the
+   operative word in the title will be ``Cygwin`` (the last word), and JAWS,
+   therefore, will load any ``Cygwin`` configuration files that it finds.
+
+   The second of these lines sets the primary shell prompt to the customary
+   dollar (``$``) sign. More importantly, though, it replaces Cygwin's default
+   ``PS1`` setting which, because it contains escape sequences that overwrite
+   the title with ``$PWD``, renders the window unrecognizable by JAWS.
+
+.. topic:: Putting JAWS Braille to Sleep
+
+   Now that a stable window title has been established, JAWS braille can
+   finally be put to sleep. While in Cygwin, and with BRLTTY not running, do
+   the following:
+
+   1) Press Insert+F2 to bring up the ``Run JAWS Manager`` dialog.
+
+   2) Down-Arrow to ``Configuration Manager``, and press Enter. Verify that the
+      title on the top line contains ``Cygwin.jcf``.
+
+   3) Press Alt+S for ``Set Options``.
+
+   4) Press B for ``Braille``.
+
+   5) Press S for ``Braille Sleep Mode``.
+
+   6) Verify that the box is checked. It should look like ``<x>`` (rather than
+      like ``< >``). Press Space to toggle the setting if the ``x`` isn't there.
+
+   7) Press Enter to leave the menu.
+
+   8) Press Control+S to save the file.
+
+   9) Press Alt+F4 to exit the configuration manager.
+
+.. topic:: Putting JAWS Speech to Sleep
+
+   If you'd like to use JAWS speech in Windows, but not in Cygwin, you can do
+   the following:
+
+   1) Press Insert+F2 to bring up the ``Run JAWS Manager`` dialog.
+
+   2) Down-Arrow to ``Configuration Manager`` and press Enter. Verify that the
+      title on the top line contains ``Cygwin.jcf``.
+
+   3) Press Alt+S for ``Set Options``.
+
+   4) Press A for ``Advanced``.
+
+   5) You should be on an item labelled ``Sleep Mode Enable`` with an empty
+      check box (``< >``). Press Space to check it (``<x>``). 
+
+   6) Press Enter to leave the menu.
+
+   7) Press Control+S to save the file.
+
+   8) Press Alt+F4 to exit the configuration manager.
+
+.. topic:: Known Problem
+
+   You'll always be able to switch to Cygwin, and BRLTTY (if it's running) will
+   take control of the braille device automatically. Switching back to Windows
+   and JAWS, however, may be problematic. The degree of success seems to depend
+   on the type of braille device being used. It usually works properly.
+   Sometimes, however, when using a USB device, the cable needs to be
+   unplugged/replugged to allow JAWS to regain control. In extreme cases, you
+   may need to exit BRLTTY before going to Windows.
+
+MinGW Scripts for Building BRLTTY
+=================================
+
+The ``mkwin`` Script
+--------------------
+
+The ``mkwin`` script,
+which can be found in the ``Windows/`` subdirectory of BRLTTY's source tree,
+builds the Windows archive and installer for BRLTTY.
+
+Software Components Required by ``mkwin``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Links to the pages where we found these
+installers, components, and files are listed.
+In case any of these links becomes out-of-date, however,
+copies of some of them have been preserived at
+`<http://BRLTTY.app/archive/Windows/>`_.
+
+The following software components should already be installed:
+
+*  ``MinGW`` [32-bit] (from `<http://mingw.org/>`_)
+
+The following ``MinGW`` packages may still need to be installed:
+
+*  ``msys-bison``
+*  ``msys-dos2unix``
+*  ``msys-groff``
+*  ``msys-m4``
+*  ``msys-tar``
+*  ``msys-unzip``
+*  ``msys-wget``
+*  ``msys-zip``
+*  ``mingw32-libpdcurses``
+*  ``mingw32-pthreads-w32``
+*  ``mingw32-tcl``
+
+The following packages should also be installed:
+
+*  ``AutoHotkey`` (from `<http://www.autohotkey.com/>`_)
+
+   The ``mkwin`` script assumes that it has been installed in
+   ``C:\Program Files (x86)\AutoHotkey``.
+   The ``-A`` (AutoHotkey) option can be used to specify another location.
+   The installer currently being used is named ``AutoHotkey104805_Install.exe``.
+
+*  ``NSIS`` (from `<http://nsis.sourceforge.net/>`_)
+
+   The ``mkwin`` script assumes that it has been installed in
+   ``C:\Program Files (x86)\NSIS``.
+   The ``-N`` (NSIS) option can be used to specify another location.
+   The installer currently being used is named ``nsis-3.0b0-setup.exe``.
+
+*  ``Python`` (from `<http://www.python.org/>`_)
+
+   The 32-bit variants are required. Version 2 or 3 may be used. If the
+   ``python`` command isn't in your command search path, or if you'd like to
+   use a different version of ``Python``, then use the ``-P`` (Python) option
+   to specify the top-level directory of the desired ``Python`` version.
+
+*  ``Cython`` (from `<http://cython.org/>`_)
+
+   A corresponding version of ``Cython`` must be installed for each version of
+   ``Python``. Each ``Cython`` version is installed within the corresponding
+   ``Python`` version's hierarchy. Install ``Cython`` with the command::
+
+      pip install cython
+
+   Some systems, either in addition to or in place of the ``pip`` command,
+   have the ``pip2`` and/or the ``pip3`` command. As you might have guessed,
+   ``pip2`` corresponds to ``Python2`` and ``pip3`` corresponds to ``Python3``.
+   It's best to use the one with the most explicit version suffix.
+
+   If the ``pip`` command isn't already included within  the version of
+   ``Python`` that you're using (usually within its ``scripts`` subdirectory)
+   then you can install it from `<https://pip.pypa.io/en/stable/installing/>`_.
+
+   Copies of the files mentioned below are at
+   `<http://BRLTTY.app/archive/Windows/Cython/>`_:
+
+   +  To force use of MinGW's (rather than Microsoft's) C compiler,
+      add ``distutils.cfg`` to the ``Python/lib/distutils/`` directory.
+      It should contain these lines::
+
+         [build]
+         compiler = mingw32
+
+   +  If you get a message like "Unknown MS Compiler version 1900"
+      then you'll probably need to apply the patch ``patch1``
+      (from `<https://bugs.python.org/file40608/patch.diff>`_)
+      when within the ``Python/lib/distutils/`` directory::
+
+         cd Python/lib/distutils
+         patch patch1
+
+   +  If you get a message like "cannot find -lvcruntime140"
+      then you'll need to add the 32-bit version of ``vcruntime140.dll``
+      (from `<http://www.dll-files.com/>`_)
+      to the ``Python/libs/`` directory.
+      You can verify that you have the 32-bit version with the ``file`` command::
+
+         file vcruntime140.dll
+         vcruntime140.dll: PE32 executable (DLL) (console) Intel 80386, for MS Windows
+
+      A message like "vcruntime140.dll: file not recognized: File format not recognized"
+      probably means that you don't have the 32-bit version of the DLL.
+
+*  ``LibUSB-Win32`` (from `<http://libusb-win32.sourceforge.net/>`_)
+
+   The ``mkwin`` script assumes that it has been installed in
+   ``C:\LibUSB-Win32``.
+   The ``-U`` (LibUSB-Win32) option can be used to specify another location.
+   The archive currently being used is named ``libusb-win32-bin-1.2.6.0.zip``.
+   Our copy of this archive has a top-level directory named ``libusb-win32-bin-1.2.6.0``.
+
+*  ``LibUSB-1.0`` (from `<http://www.libusb.org/wiki/windows_backend>`_)
+
+   The ``mkwin`` script assumes that it has been installed in
+   ``C:\LibUSB-1.0``.
+   The ``-X`` (libusbx) option can be used to specify another location.
+   The archive currently being used is named ``libusbx-1.0.18-win.7z``.
+   Our copy of this archive doesn't have a top-level directory.
+   We have an alternate copy of this archive named ``libusbx-1.0.18-win.tar.gz``.
+
+*  ``WinUSB`` (from `<http://www.libusb.org/wiki/windows_backend>`_)
+
+   The ``mkwin`` script assumes that it has been installed in
+   ``C:\WinUSB``.
+   The ``-W`` (WinUSB) option can be used to specify another location.
+   The archive currently being used is named ``winusb.zip``.
+   Our copy of this archive doesn't have a top-level directory.
+
+*  ``ICU`` (from `<http://icu-project.org/>`_)
+
+   The ``mkwin`` script assumes that it has been installed in ``C:\ICU``.
+   The ``-I`` (ICU) option can be used to specify another location.
+   The archive currently being used is named ``icu4c-53_1-Win32-msvc10.zip``.
+   Our copy of this archive has a top-level directory named ``icu``.
+   Since Windows file systems don't have case sensitive names,
+   renaming it from ``icu`` to ``ICU`` is optional.
+
+*  ``pkg-config`` (from `<http://gtk.org/download/win32.php>`_)
+
+   The archive currently being used is named ``pkg-config_0.28-1_win32.zip``.
+   Our copy of this archive doesn't have a top-level directory.
+   It should be unpacked when in MinGW's ``/mingw/`` directory.
+
+*  ``Glib`` (from `<http://gtk.org/download/win32.php>`_)
+
+   The archive currently being used is named ``glib_2.34.3-1_win32.zip``.
+   Our copy of this archive doesn't have a top-level directory.
+   It should be unpacked when in MinGW's ``/mingw/`` directory.
+
+*  ``gettext-runtime`` (from `<http://gtk.org/download/win32.php>`_)
+
+   Only install it if your MinGW installation doesn't already have it.
+   Check for the presence of a ``libintl`` DLL in MinGW's ``/mingw/bin/`` directory.
+
+The ``mkwin`` script uses the ``lib`` command,
+which belongs to MSVC (Microsoft Visual C++).
+On newer Windows systems, MSVC has become
+a component of Microsoft Visual Studio.
+If you don't have it then you'll need to get
+the following files from someone who does.
+Copies of them are at `<http://BRLTTY.app/archive/Windows/MSVC/>`_.
+
+*  lib.exe
+*  link.exe
+*  mspdb100.dll
+*  msvcr100.dll
+
+All of them should be placed within the same directory
+(MinGW's ``/usr/local/bin/`` directory may be a good place).
+Either add this directory to your command search path
+or use the ``-M`` (MSVC) option to specify its location.
+
+If you'd like to have USB support then create the symbolic links as described
+within the MinGW instructions for using `LibUSB-Win32`_ and/or `LibUSB-1.0`_.
+You'll also need to create ``.pc`` (pkg-config) files for them within MinGW's
+``/mingw/lib/pkgconfig>`` directory. The ones we've created are at
+`<http://BRLTTY.app/archive/Windows/pkgconfig/>`_.
+
+If you'd like to have Bluetooth support then you'll need to add
+the files listed below to MinGW's ``/mingw/include/`` directory.
+Copies of them are at `<http://BRLTTY.app/archive/Windows/Bluetooth/>`_.
+
+*  ws2bth.h
+*  bthdef.h
+*  bthsdpdef.h
+
+More ``mkwin`` Options
+~~~~~~~~~~~~~~~~~~~~~~
+
+``-u`` (USB)
+   Specify which USB package is to be used. The following USB packages are
+   supported:
+
+   *  libusb (for LibUSB-Win32)
+   *  libusb-1.0 (for LibUSB-1.0 + WinUSB)
+
+``-C`` (Cygwin)
+   Specify the root (top-level) directory of a Cygwin installation.
+   If it isn't specified then ``/cygwin`` (if it exists) is assumed.
+   Cygwin's ``bin/`` directory is added to the end of the command search path
+   as a last resort for finding needed commands that MinGW may not have.
+
+``-s`` (shell)
+   Invoke an interactive shell just before the archive is created so that you
+   can have a look at and/or modify what it will contain. If the ``SHELL``
+   environment variable is set then that shell is used. If not, ``/bin/sh`` is
+   assumed.
+
+``-t`` (temporary directory)
+   Specify the temporary directory that is to be used for the build.
+   It must not already exist.
+   If it isn't specified then an internally generated path is used.
+
+``-k`` (keep)
+   Don't remove the temporary directory
+   at the end of a successful build.
+   It isn't removed if the build fails
+   so that you can have a look at what went wrong.
+
+.. include:: common-options.rst
+
+``mkwin`` Parameters
+~~~~~~~~~~~~~~~~~~~~
+
+The ``mkwin`` script accepts two parameters:
+
+1) The path to the top-level directory of BRLTTY's source tree.
+   It may be either relative or absolute.
+   This parameter is required.
+
+2) The build revision.
+   It may be any character sequence that doesn't include whitespace.
+   It is appended, with a prepended dash (``-``), to
+   BRLTTY's version number in order to construct the build version.
+   This parameter is optional - if it isn't specified then
+   the revision number part of the build identifier is used.
+
+The build name is used as:
+
+*  the name of the top-level directory within the archive
+*  the file name of the archive (which has the ``.zip`` extension)
+*  the file name of the installer (which has the ``.exe`` extension)
+
+It has the following form::
+
+   brltty-win-buildVersion-buildRevision-usbPackage
+
+For example:
+
+*  if your current working directory is the ``Windows/`` subdirectory of
+   BRLTTY's source tree,
+
+*  if the current BRLTTY version is 5.2,
+
+*  and remembering that the default USB package is ``libusb``,
+
+then invoking the following command::
+
+   ./mkwin .. 4
+
+will create these files within the current working directory:
+
+*  brltty-win-5.2-4-libusb.zip (the archive)
+*  brltty-win-5.2-4-libusb.exe (the installer)
+
+The ``winsetup`` Script
+-----------------------
+
+The ``winsetup`` script,
+which can be found in the ``Windows/`` subdirectory of BRLTTY's source tree,
+ensures that all needed components are installed on your system
+so that `the mkwin script`_ can successfully build BRLTTY.
+
+``winsetup`` Options
+~~~~~~~~~~~~~~~~~~~~
+
+``-a`` (archive URL)
+   Specify the location for BRLTTY's Windows archive.
+
+``-d`` (dry run)
+  Don't actually install anything.
+
+``-k`` (keep)
+   Don't remove the temporary directory.
+
+``-p`` (Python directory)
+   Specify the location where Python has been installed.
+
+``-t`` (temporary directory)
+   Specify the temporary directory that is to be used for the installs.
+   It must not already exist.
+   If it isn't specified then an internally generated path is used.
+
+.. include:: common-options.rst
+
diff --git a/Documents/README.X11 b/Documents/README.X11
new file mode 100644
index 0000000..a75f025
--- /dev/null
+++ b/Documents/README.X11
@@ -0,0 +1,94 @@
+~~~~~~~~~~~~~~~
+BRLTTY with X11
+~~~~~~~~~~~~~~~
+
+.. include:: prologue.rst
+.. _Orca: https://help.gnome.org/users/orca/stable/
+
+BRLTTY provides some support for the X11 graphical environment.
+
+Braille Input
+=============
+
+From the Braille Device
+-----------------------
+
+Some braille devices have an integrated braille keyboard. BRLTTY alone can't
+properly simulate keypresses for the X11 server because it doesn't know which
+keyboard layout the latter is using.  In order to achieve proper keyboard
+simulation, ``xbrlapi`` should be started in the background during ``X``
+session startup.
+
+By default, ``xbrlapi`` also writes the title of the current window to the
+braille device. To avoid this (since it could interfere with output from screen
+readers like `Orca`_), give the ``-q`` (quiet) option to ``xbrlapi``.
+
+From the PC Keyboard
+--------------------
+
+The PC keyboard can be used, when in X11, as a braille keyboard. This
+capability can be enabled by using the ``brai`` layout. For example::
+
+   setxkbmap -layout "us,brai" -option "grp:shift_caps_toggle,grp_led:scroll"
+
+enables both the ``US`` and the ``braille`` keyboard layouts in two different
+XKB groups), and Shift+CapsLock will switch between them. The scroll LED will
+be lit while in ``braille`` mode so that sighted users will be alerted as to
+why they may be getting unexpected output if they switch groups by accident.
+Other shortcuts and LEDs can also be used (see the XKB documentation).
+
+The default keyboard mapping for braille dots is::
+
+   f j
+   d k
+   s l
+   a ;
+
+but other layouts are also available (see the variants in XKB).
+
+Applications then need to be "told" to use the standard X11 Input Module. This
+can be done by setting::
+
+   export GTK_IM_MODULE=xim
+   export QT_IM_MODULE=xim
+   export XMODIFIERS=@im=xim
+
+This is, however, not enough for recent gnome desktops, which also need to
+run::
+
+   gsettings set org.gnome.settings-daemon.plugins.keyboard active false
+
+The ``setxkbmap`` command (above) only enables the ``braille`` layout for the
+current session. For persistent configuration, the two groups must be
+configured properly in the system. On Debian, for example, this can be achieved
+by setting the following in ``/etc/default/keyboard``::
+
+   XKBLAYOUT="us,brai"
+   XKBOPTIONS="grp:shift_caps_toggle,grp_led:scroll"
+
+By default, the result is Unicode braille patterns because X11 doesn't know
+which braille table it should be using. To tell X11 the conversion that should
+be performed, use the ``brltty-ttb`` tool to convert your BRLTTY text table
+into an XCompose table. For example::
+
+   brltty-ttb -i ttb -o XCompose en_US /tmp/my.XCompose
+
+The ``XCompose`` table can then be enabled by::
+
+   cat /tmp/my.XCompose >> ~/.XCompose
+
+Reading the Screen
+==================
+
+BRLTTY isn't meant to be able to read a whole graphical environment as it
+doesn't have the notion of menus, buttons, etc. The `Orca`_ screen reader
+should be used instead. BRLTTY, however, has a driver that can at least review
+the textual elements (edit boxes, terminals). This is particularly useful for
+reading terminals since that's what BRLTTY is good at. This can be achieved by
+running a second BRLTTY during X11 session startup::
+
+   brltty -b ba -x as
+
+This second BRLTTY connects to the first one via BrlAPI (thanks to the ``ba``
+braille driver), and reads the screen via AtSpi (thanks to the ``as`` screen
+driver). An ``a2`` screen driver is also provided for AtSpi2 support.
diff --git a/Documents/TODO b/Documents/TODO
new file mode 100644
index 0000000..fe48394
--- /dev/null
+++ b/Documents/TODO
@@ -0,0 +1,28 @@
+BRLTTY TODO - March 30, 2000
+============================
+
+(This concerns the main module of BRLTTY.  Individual Braille terminal
+drivers may have their own TODO lists.)
+
+Your help is welcome.
+
+- Defining smaller regions of the screen to be concentrated on.  Cursor
+  movement outside the current region would be ignored.  This would be
+  useful if the screen was split into multiple `windows' (e.g. in ytalk).
+
+- Attribute tracking (scroll bars, etc.)
+
+- Revision of beeps.  Too many events have the same beeps, so we might want
+  to add new ones. See if we could optimize the use of the console resource.
+  Implement .au and .wav sounds (through a soundcard).
+
+- Better speech support.
+
+- Support for more Braille displays.
+
+- Port to other Unix platforms.  Since all Linux dependent functions are now
+  modularized in separate source files, it would be easy to write other 
+  modules to make BRLTTY usable with the screen package or such and then 
+  become available for any other Unix platform.  An experimental patch is 
+  available in the Patches/ directory.
+
diff --git a/Documents/android-permissions.rst b/Documents/android-permissions.rst
new file mode 100644
index 0000000..313fc2b
--- /dev/null
+++ b/Documents/android-permissions.rst
@@ -0,0 +1,45 @@
+``BIND_ACCESSIBILITY_SERVICE``
+  * For inspecting the layout and content of the screen.
+
+``BIND_INPUT_METHOD``
+  * For Android to accept input via BRLTTY from your braille device's keyboard.
+
+``FOREGROUND_SERVICE``
+  * For creating a foreground notification.
+
+``WAKE_LOCK``
+  * For resetting the Android device's lock timer
+    each time you interact with a control on your braille device.
+
+``BLUETOOTH``
+  * For communicating with a braille device via Bluetooth.
+
+``INTERNET``
+  * For listening on a TCP/IP port for BrlAPI client connection requests.
+
+``READ_EXTERNAL_STORAGE``
+  * For reading customized data files
+    from your Android device's primary shared/external storage area.
+
+``SYSTEM_ALERT_WINDOW``
+  * For presenting the Accessibility Actions chooser.
+
+``RECEIVE_BOOT_COMPLETED``
+  * For knowing when locked storage can be accessed after a reboot.
+
+``REQUEST_INSTALL_PACKAGES``
+  * For upgrading to a newer release.
+
+``ACCESS_WIFI_STATE``
+  * For getting Wi-Fi status values (for the INDICATORS command).
+
+``ACCESS_FINE_LOCATION``
+  * For getting the Wi-Fi SSID (for the INDICATORS command).
+  * For getting cell information (for the INDICATORS command).
+
+``ACCESS_COARSE_UPDATES``
+  * For getting the cell signal strength (for the INDICATORS command).
+
+``READ_PHONE_STATE``
+  * For getting the cell data network type (for the INDICATORS command).
+
diff --git a/Documents/attributes-table.csv b/Documents/attributes-table.csv
new file mode 100644
index 0000000..a4f5092
--- /dev/null
+++ b/Documents/attributes-table.csv
@@ -0,0 +1,4 @@
+"name","description"
+"left_right","foreground colour in the left column and background colour in the right column"
+"invleft_right","inverse foreground colour in the left column and background colour in the right column"
+"upper_lower","foreground colour in the upper square and background colour in the lower square"
diff --git a/Documents/braille-driver.csv b/Documents/braille-driver.csv
new file mode 100644
index 0000000..b0d5310
--- /dev/null
+++ b/Documents/braille-driver.csv
@@ -0,0 +1,40 @@
+"code","name","aliases","others","usage"
+"auto","autodetect","","",""
+"al","Alva","","","ABT(3nn), Delphi(4nn), Satellite(5nn), Braille System 40, Braille Controller 640/680, Easy Link 12"
+"at","Albatross","","","46/80"
+"ba","BrlAPI","","","BrlAPI client"
+"bc","BrailComm","","","III"
+"bd","Braudi","","","Pro"
+"bl","BrailleLite","","","18/40/M20/M40"
+"bm","Baum","","Refreshabraille, Orbit, NLS eReader Zoomax, NBP B2G","BrailleConnect 12/24/32/40/64/80, Brailliant 24/32/40/64/80, Conny 12, DM80 Plus, EcoVario 24/32/40/64/80, Inka, NLS eReader Zoomax, Orbit Reader 20/40, PocketVario 24, Pronto! V3 18/40, Pronto! V4 18/40, RBT 40/80, Refreshabraille 18, SuperVario 32/40/64/80, Vario 40/80, VarioConnect 12/24/32/40/64/80, VarioPro 40/64/80, VarioUltra 20/32/40"
+"bn","BrailleNote","","","18/32, Apex"
+"cb","CombiBraille","","","25/45/85"
+"ce","Cebra","","","20/40/60/80/100/120/140"
+"cn","Canute","","","360 (40x9)"
+"dp","DotPad","","",""
+"ec","EcoBraille","","","20/40/80"
+"eu","EuroBraille","","","AzerBraille, Clio, Esys, Iris, NoteBraille, Scriba"
+"fa","FrankAudiodata","","","B2K84"
+"fs","FreedomScientific","VFO, Vispero","","Focus 1 44/70/84, Focus 2 40/80, Focus 3+ (Blue) 14/40/80, PAC Mate 20/40"
+"hd","Hedo","","","ProfiLine, MobilLine"
+"hm","HIMS","","","Braille Sense, SyncBraille, Braille Edge, Smart Beetle, QBrailleXL"
+"ht","HandyTech","HelpTech","","Modular 20/40/80, Modular Evolution 64/88, Modular Connect 88, Active Braille, Active Braille S, Active Star 40, Actilino, Activator, Basic Braille 16/20/32/40/48/64/80, Braillino, Braille Wave, Easy Braille, Braille Star 40/80, Connect Braille 40, Bookworm"
+"hw","HumanWare","","APH Chameleon, APH Mantis, NLS eReader","Brailliant BI 14/32/40, Brailliant BI 20X/40X, Brailliant B 80, BrailleNote Touch, BrailleOne, APH Chameleon 20, APH Mantis Q40, NLS eReader"
+"ic","Inceptor","Innovision","","BrailleMe (20)"
+"ir","Iris","","","KB"
+"lb","Libbraille","","","Libbraille"
+"lt","LogText","","","32"
+"mb","MultiBraille","","","MB125CR, MB145CR, MB185CR"
+"md","MDV","","","MB208, MB248, MB408L, MB408L+, Lilli Blu"
+"mm","BrailleMemo","","","Pocket (16), Smart (16), 32, 40"
+"mn","MiniBraille","","","20"
+"mt","Metec","","","BD-40"
+"np","NinePoint","","","8"
+"pg","Pegasus","","","20/27/40/80"
+"pm","Papenmeier","","","Compact 486, Compact/Tiny, IB 80 CR Soft, 2D Lite (plus), 2D Screen Soft, EL 80, EL 2D 40/66/80, EL 40/66/70/80 S, EL 40/60/80 C, EL 2D 80 S, EL 40 P, EL 80 II, Elba 20/32, Trio 40/Elba20/Elba32, Live 20/40"
+"sk","Seika","","","3/4/5 (40), 80, Mini (16)"
+"tn","TechniBraille","","","Manager 40"
+"ts","TSI","","","Navigator 20/40/80, PowerBraille 40/65/80"
+"vd","VideoBraille","","","40"
+"vo","Voyager","","Braille Pen, Easy Link","44/70, Part232 (serial adapter), BraillePen/EasyLink"
+"vs","VisioBraille","","","20/40"
diff --git a/Documents/brltty.1.in b/Documents/brltty.1.in
new file mode 100644
index 0000000..f91ff00
--- /dev/null
+++ b/Documents/brltty.1.in
@@ -0,0 +1,866 @@
+.\" @configure_input@
+.\"
+.\" BRLTTY - A background process providing access to the console screen (when in
+.\"          text mode) for a blind person using a refreshable braille display.
+.\"
+.\" Copyright (C) 1995-2023 by The BRLTTY Developers.
+.\"
+.\" BRLTTY comes with ABSOLUTELY NO WARRANTY.
+.\"
+.\" This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+.\"
+.\" Web Page: http://brltty.app/
+.\"
+.\" This software is maintained by Dave Mielke <dave@mielke.cc>.
+.\"
+.TH "BRLTTY" "1" "July 2023" "@PACKAGE_TARNAME@ @PACKAGE_VERSION@" "@PACKAGE_NAME@ User's Manual"
+.SH NAME
+brltty \- refreshable braille display driver for Linux/Unix
+.SH SYNOPSIS
+\fBbrltty \fR[\fIoption\fR ...]
+.SH DESCRIPTION
+.B brltty
+is a background process (daemon)
+which provides access to the console screen (when in text mode)
+for a blind person using a refreshable braille display.
+It drives the braille display,
+and provides complete screen review functionality.
+Some speech capability has also been incorporated.
+.SH OPTIONS
+Options can be passed to
+.B brltty
+in a number of ways.
+From most to least influential, these are:
+.IP 1. 4
+Command Line Options
+.IP 2. 4
+Boot Parameters
+.IP 3. 4
+Environment Variables
+(if the
+.B \-E
+.RB "(" "\-\-environment\-variables" ")"
+option is in effect)
+.IP 4. 4
+The Configuration File
+.IP 5. 4
+Built-in Defaults
+.SS "Command Line Options"
+The options are processed sequentially from left to right.
+If an option is specified more than once,
+or in case of a conflict,
+the rightmost specification takes precedence.
+.PP
+The following options are supported:
+.TP
+\fB\-a \fItable\fR (\fB\-\-attributes\-table=\fR)
+The path to the attributes table.
+Relative paths are anchored at
+.BR "@TABLES_DIRECTORY@/@ATTRIBUTES_TABLES_SUBDIRECTORY@" "."
+The
+.B ".atb"
+extension is optional.
+The built-in default is
+.BR "@attributes_table@.atb" "."
+.TP
+\fB\-b \fIdriver\fB,\fR...|\fBauto\fR (\fB\-\-braille\-driver=\fR)
+The driver for the braille display
+(see Driver Specification).
+The built-in default is \fBauto\fR.
+.TP
+\fB\-c \fItable\fR (\fB\-\-contraction\-table=\fR)
+The path to the contraction table.
+Relative paths are anchored at
+.BR "@TABLES_DIRECTORY@/@CONTRACTION_TABLES_SUBDIRECTORY@" "."
+The
+.B ".ctb"
+extension is optional.
+.TP
+\fB\-d \fIdevice\fB,\fR... (\fB\-\-braille\-device=\fR)
+The device to which the braille display is connected.
+The built-in default is \fB@braille_device@\fR.
+.RS
+.PP
+The general form of a braille device specification is
+\fIqualifier\fB:\fIdata\fR.
+For backward compatibility with earlier releases,
+if the qualifier is omitted then
+.B serial:
+is assumed.
+The following device types are supported:
+.TP
+.B Bluetooth
+For a bluetooth device, specify \fBbluetooth:\fIaddress\fR.
+The address must be six two-digit hexadecimal numbers separated by colons, e.g.
+.BR "01:23:45:67:89:AB" "."
+.TP
+.B Serial
+For a serial device, specify \fBserial:\fIdevice\fR.
+The
+.B serial:
+qualifier is optional (for backward compatibility).
+If a relative path is given then it's anchored at
+.B /dev/
+(the usual location where devices are defined on a Unix-like system).
+The following device specifications all refer
+to the primary serial device on Linux:
+.BR "serial:@serial_first_device@" ","
+.BR "serial:/dev/@serial_first_device@" ","
+.BR "@serial_first_device@" ","
+.BR "/dev/@serial_first_device@" "."
+.TP
+.B USB
+For a USB device, specify \fBusb:\fR.
+.B brltty
+will search for the first USB device which
+matches the braille display driver being used.
+If this is inadequate,
+e.g. if you have more than one USB braille display which requires the same driver,
+then you can refine the device specification
+by appending the serial number of the display to it, e.g.
+.BR "usb:12345" "."
+N.B.:
+The "identification by serial number" feature doesn't work for some models
+because some manufacturers
+either don't set the USB serial number descriptor at all
+or do set it but not to a unique value.
+.PP
+A comma-delimited list of braille devices may be specified.
+If this is done then autodetection is performed on each listed device in sequence.
+This feature is particularly useful if you have
+a braille display with more than one interface,
+e.g. both a serial and a USB port.
+.RE
+.TP
+\fB\-e\fR (\fB\-\-standard\-error\fR)
+Write logs to standard error rather than to the system log (useful for debugging).
+.TP
+\fB\-f \fIfile\fR (\fB\-\-configuration\-file=\fR)
+The path to the configuration file.
+Relative paths are anchored at the current working directory.
+The built-in default is
+.BR "@CONFIGURATION_DIRECTORY@/@CONFIGURATION_FILE@" "."
+.TP
+\fB\-h\fR (\fB\-\-help\fR)
+Print a command line usage summary (commonly used options only),
+and then exit.
+.TP
+\fB\-i \fIname\fR (\fB\-\-speech\-input=\fR)
+The file system object
+(FIFO, named pipe, named socket, etc)
+which gives other applications access to
+.BR brltty 's
+speech driver for text-to-speech conversion.
+It's created at start-up and removed at termination.
+Relative paths are anchored at the current working directory.
+The built-in default is
+that the file system object is not created.
+.TP
+\fB\-k \fItable\fR (\fB\-\-keyboard\-table=\fR)
+The path to the keyboard table.
+Relative paths are anchored at
+.BR "@TABLES_DIRECTORY@/@KEYBOARD_TABLES_SUBDIRECTORY@" "."
+The
+.B ".ktb"
+extension is optional.
+.TP
+\fB\-l \fIlevel\fR (\fB\-\-log\-level=\fR)
+The minimum severity level for messages written to the log.
+Any of the following numbers,
+or any abbreviation of their corresponding names,
+may be specified:
+.RS
+.TP 4
+.B 0
+.B emergency
+.TP 4
+.B 1
+.B alert
+.TP 4
+.B 2
+.B critical
+.TP 4
+.B 3
+.B error
+.TP 4
+.B 4
+.B warning
+.TP 4
+.B 5
+.B notice
+.TP 4
+.B 6
+.B information
+.TP 4
+.B 7
+.B debug
+.RE
+.PP
+The built-in default is
+.BR "notice" "."
+.TP
+\fB\-m \fIdevice\fR (\fB\-\-midi\-device=\fR)
+The device to use for the Musical Instrument Digital Interface.
+For ALSA it's \fIclient\fB:\fIport\fR,
+where each may be either a number or a case-sensitive substring of its name.
+For other interfaces it's the full path to an appropriate system device.
+The built-in default is:
+.RS
+.IP Linux/ALSA 12
+the first available MIDI output port
+.IP Linux/OSS 12
+.B /dev/sequencer
+.RE
+.TP
+\fB\-n\fR (\fB\-\-no\-daemon\fR)
+Remain in the foreground (useful for debugging).
+.TP
+\fB\-o \fIname\fB=\fIvalue\fB,\fR... (\fB\-\-override\-preference=\fR)
+Override a preference setting.
+For the location of the preferences file, see the
+.B \-F
+.RB "(" "\-\-preferences\-file" ")"
+option.
+.TP
+\fB\-p \fIdevice\fR (\fB\-\-pcm\-device=\fR)
+The device to use for digital audio.
+For ALSA it's \fIname\fR[\fB:\fIargument\fB,\fR...].
+For other interfaces it's the full path to an appropriate system device.
+The built-in default is:
+.RS
+.IP FreeBSD 12
+.B /dev/dsp
+.IP Linux/ALSA 12
+.B hw:0,0
+.IP Linux/OSS 12
+.B /dev/dsp
+.IP NetBSD 12
+.B /dev/audio
+.IP OpenBSD 12
+.B /dev/audio
+.IP Qnx 12
+the preferred PCM output device
+.IP Solaris 12
+.B /dev/audio
+.RE
+.TP
+\fB\-q\fR (\fB\-\-quiet\fR)
+Suppress the start-up messages.
+This is done by reducing the default log level
+(see the
+.B \-l
+.RB "(" "\-\-log\-level=" ")"
+option)
+to
+.B warning
+.RB "(" "information"
+if either
+.B \-v
+.RB "(" "\-\-verify" ")"
+or
+.B \-V
+.RB "(" "\-\-version" ")"
+is also specified).
+.TP
+\fB\-r\fR (\fB\-\-release\-device\fR)
+Release the device to which the braille display is connected
+when the current screen or window can't be read.
+.TP
+\fB\-s \fIdriver\fB,\fR...|\fBauto\fR (\fB\-\-speech\-driver=\fR)
+The driver for the speech synthesizer
+(see Driver Specification).
+The built-in default is \fBauto\fR.
+.TP
+\fB\-t \fItable\fR (\fB\-\-text\-table=\fR)
+The path to the text table.
+Relative paths are anchored at
+.BR "@TABLES_DIRECTORY@/@TEXT_TABLES_SUBDIRECTORY@" "."
+The
+.B ".ttb"
+extension is optional.
+The built-in default is
+.B "@text_table@.ttb"
+(the North American Braille Computer Code).
+.TP
+\fB\-v\fR (\fB\-\-verify\fR)
+Print the start-up messages and then exit.
+This always includes the versions of
+.B brltty
+itself,
+the server side of its application programming interface,
+and each of the selected braille and speech drivers.
+If the
+.B \-q
+.RB "(" "\-\-quiet" ")"
+option isn't also specified
+then it also includes the values of the options after all sources have been considered.
+If more than one braille driver and/or more than one braille device
+has been specified then braille display autodetection is performed.
+If more than one speech driver
+has been specified then speech synthesizer autodetection is performed.
+.TP
+\fB\-x \fIdriver\fR (\fB\-\-screen\-driver=\fR)
+The screen driver.
+The built-in default is operating system appropriate.
+.TP
+\fB\-A \fIname\fB=\fIvalue\fB,\fR... (\fB\-\-api\-parameters=\fR)
+Parameters for the application programming interface.
+If the same parameter is specified more than once
+then the rightmost specification is used.
+Parameter names may be abbreviated.
+.TP
+\fB\-B \fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR... (\fB\-\-braille\-parameters=\fR)
+Parameters for the braille display driver.
+If the same parameter is specified more than once
+then the rightmost specification is used.
+Parameter names may be abbreviated.
+If a parameter assignment is qualified with a driver identification code
+then it's only processed if that braille display driver is being used.
+.TP
+\fB\-D \fIdirectory\fR (\fB\-\-drivers\-directory=\fR)
+The path to the directory which contains
+the dynamically loadable driver objects.
+The built-in default is
+.RB "@DRIVERS_DIRECTORY@" "."
+.TP
+\fB\-E\fR (\fB\-\-environment\-variables\fR)
+Recognize environment variables.
+.TP
+\fB\-F \fIfile\fR (\fB\-\-preferences\-file=\fR)
+The path to the preferences file.
+Relative paths are anchored at
+.BR "@UPDATABLE_DIRECTORY@" "."
+The built-in default is
+.BR "@PREFERENCES_FILE@" "."
+.TP
+\fB\-H\fR (\fB\-\-full\-help\fR)
+Print a command line usage summary (all options),
+and then exit.
+.TP
+\fB\-I\fR (\fB\-\-install\-service\fR)
+(Windows only)
+Install
+.B brltty
+as the
+.B BrlAPI
+service so that it will be automatically started when the system is booted,
+and so that applications can know that a
+.B BrlAPI
+server is running.
+.TP
+\fB\-K \fIarg\fR (\fB\-\-keyboard\-properties=\fR)
+Properties of the keyboard.
+.TP
+\fB\-L \fIfile\fR (\fB\-\-log\-file=\fR)
+The file to which log messages are written.
+Relative paths are anchored at the current working directory.
+The default is to send log messages to the system log.
+.TP
+\fB\-M \fIcsecs\fR (\fB\-\-message\-delay=\fR)
+The message hold time in hundredths of a second.
+The built-in default is
+.B 400
+(4 seconds).
+.TP
+\fB\-N\fR (\fB\-\-no\-api\fR)
+Don't start the application programming interface.
+.TP
+\fB\-P \fIfile\fR (\fB\-\-pid\-file=\fR)
+The full path to the process identifier file.
+If this option is supplied,
+.B brltty
+writes its process identifier (pid) into the specified file at start-up.
+The file is removed when
+.B brltty
+terminates.
+.TP
+\fB\-R\fR (\fB\-\-remove\-service\fR)
+(Windows only)
+Remove the
+.B BrlAPI
+service so that
+.B brltty
+will not be automatically started when the system is booted,
+and so that applications can know that no
+.B BrlAPI
+server is running.
+.TP
+\fB\-S \fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR... (\fB\-\-speech\-parameters=\fR)
+Parameters for the speech synthesizer driver.
+If the same parameter is specified more than once
+then the rightmost specification is used.
+Parameter names may be abbreviated.
+If a parameter assignment is qualified with a driver identification code
+then it's only processed if that speech synthesizer driver is being used.
+.TP
+\fB\-T \fIdirectory\fR (\fB\-\-tables\-directory=\fR)
+The path to the directory which contains
+the text, contraction, attributes, keyboard, and input tables.
+The built-in default is
+.RB "@TABLES_DIRECTORY@" "."
+.TP
+\fB\-U \fIdirectory\fR (\fB\-\-updatable\-directory=\fR)
+The path to a directory which contains files that can be updated.
+The built-in default is
+.RB "@UPDATABLE_DIRECTORY@" "."
+.TP
+\fB\-V\fR (\fB\-\-version\fR)
+Print the versions of
+.B brltty
+itself,
+the server side of its application programming interface,
+and those drivers which were configured in at build-time,
+and then exit.
+If the
+.B \-q
+.RB "(" "\-\-quiet" ")"
+option isn't also specified
+then also print copyright information.
+.TP
+\fB\-W \fIdirectory\fR (\fB\-\-writable\-directory=\fR)
+The path to a directory which can be written to.
+The built-in default is
+.RB "@WRITABLE_DIRECTORY@" "."
+.TP
+\fB\-X \fIname\fB=\fIvalue\fB,\fR... (\fB\-\-screen\-parameters=\fR)
+Parameters for the screen driver.
+If the same parameter is specified more than once
+then the rightmost specification is used.
+Parameter names may be abbreviated.
+.TP
+\fB\-Y \fItext\fR (\fB\-\-start\-message=\fR)
+The text to be shown when the braille driver starts
+and to be spoken when the speech driver starts.
+The built-in default is
+.RB "@PACKAGE_NAME@ @PACKAGE_VERSION@" "."
+.TP
+\fB\-Z \fItext\fR (\fB\-\-stop\-message=\fR)
+The text to be shown when the braille driver stops.
+The built-in default is
+.RB "@PACKAGE_NAME@ stopped" "."
+.SS "Environment Variables"
+The following environment variables are recognized if the
+.B \-E
+.RB "(" "\-\-environment\-variables" ")"
+option is specified:
+.TP
+\fBBRLTTY_API_PARAMETERS=\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the application programming interface.
+See the
+.B \-A
+.RB "(" "\-\-api\-parameters=" ")"
+option for details.
+.TP
+\fBBRLTTY_ATTRIBUTES_TABLE=\fItable\fR
+The attributes table.
+See the
+.B \-a
+.RB "(" "\-\-attributes\-table=" ")"
+option for details.
+.TP
+\fBBRLTTY_BRAILLE_DEVICE=\fIdevice\fB,\fR...
+The device to which the braille display is connected.
+See the
+.B \-d
+.RB "(" "\-\-braille\-device=" ")"
+option for details.
+.TP
+\fBBRLTTY_BRAILLE_DRIVER=\fIdriver\fB,\fR...|\fBauto\fR
+The driver for the braille display.
+See the
+.B \-b
+.RB "(" "\-\-braille\-driver=" ")"
+option for details.
+.TP
+\fBBRLTTY_BRAILLE_PARAMETERS=\fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the braille display driver.
+See the
+.B \-B
+.RB "(" "\-\-braille\-parameters=" ")"
+option for details.
+.TP
+\fBBRLTTY_CONFIGURATION_FILE=\fIfile\fR
+The configuration file.
+See the
+.B \-f
+.RB "(" "\-\-configuration\-file=" ")"
+option for details.
+.TP
+\fBBRLTTY_CONTRACTION_TABLE=\fItable\fR
+The contraction table.
+See the
+.B \-c
+.RB "(" "\-\-contraction\-table=" ")"
+option for details.
+.TP
+\fBBRLTTY_MIDI_DEVICE=\fIdevice\fR
+The device to use for the Musical Instrument Digital Interface.
+See the
+.B \-m
+.RB "(" "\-\-midi\-device=" ")"
+option for details.
+.TP
+\fBBRLTTY_PCM_DEVICE=\fIdevice\fR
+The device to use for digital audio.
+See the
+.B \-p
+.RB "(" "\-\-pcm\-device=" ")"
+option for details.
+.TP
+\fBBRLTTY_PREFERENCES_FILE=\fIfile\fR
+The preferences file.
+See the
+.B \-F
+.RB "(" "\-\-preferences\-file=" ")"
+option for details.
+.TP
+\fBBRLTTY_RELEASE_DEVICE=on\fR|\fBoff\fR
+Release the device to which the braille display is connected
+when the current screen or window can't be read.
+See the
+.B \-r
+.RB "(" "\-\-release\-device" ")"
+option for details.
+.TP
+\fBBRLTTY_SCREEN_DRIVER=\fIdriver\fR
+The screen driver.
+See the
+.B \-x
+.RB "(" "\-\-screen\-driver=" ")"
+option for details.
+.TP
+\fBBRLTTY_SCREEN_PARAMETERS=\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the screen driver.
+See the
+.B \-X
+.RB "(" "\-\-screen\-parameters=" ")"
+option for details.
+.TP
+\fBBRLTTY_SPEECH_DRIVER=\fIdriver\fB,\fR...|\fBauto\fR
+The driver for the speech synthesizer.
+See the
+.B \-s
+.RB "(" "\-\-speech\-driver=" ")"
+option for details.
+.TP
+\fBBRLTTY_SPEECH_INPUT=\fIname\fR
+The file system object which gives other applications access to
+.BR brltty 's
+speech driver for text-to-speech conversion.
+See the
+.B \-i
+.RB "(" "\-\-speech\-input=" ")"
+option for details.
+.TP
+\fBBRLTTY_SPEECH_PARAMETERS=\fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the speech synthesizer driver.
+See the
+.B \-S
+.RB "(" "\-\-speech\-parameters=" ")"
+option for details.
+.TP
+\fBBRLTTY_TEXT_TABLE=\fItable\fR
+The text table.
+See the
+.B \-t
+.RB "(" "\-\-text\-table=" ")"
+option for details.
+.SS "The Configuration File"
+Blank lines are ignored.
+If the character
+.B "#"
+occurs on any line then
+all characters from it to the end of that line are treated as a comment.
+.PP
+The following configuration directives are supported:
+.TP
+\fBapi\-parameters \fIname\fB=\fIvalue\fB,\fR...
+Parameters for the application programming interface.
+See the
+.B \-A
+.RB "(" "\-\-api\-parameters=" ")"
+option for details.
+.TP
+\fBattributes\-table \fItable\fR
+The attributes table.
+See the
+.B \-a
+.RB "(" "\-\-attributes\-table=" ")"
+option for details.
+.TP
+\fBbraille\-device \fIdevice\fB,\fR...
+The device to which the braille display is connected.
+See the
+.B \-d
+.RB "(" "\-\-braille\-device=" ")"
+option for details.
+.TP
+\fBbraille\-driver \fIdriver\fB,\fR...|\fBauto\fR
+The driver for the braille display.
+See the
+.B \-b
+.RB "(" "\-\-braille\-driver=" ")"
+option for details.
+.TP
+\fBbraille\-parameters \fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the braille display driver.
+See the
+.B \-B
+.RB "(" "\-\-braille\-parameters=" ")"
+option for details.
+.TP
+\fBcontraction\-table \fItable\fR
+The contraction table.
+See the
+.B \-c
+.RB "(" "\-\-contraction\-table=" ")"
+option for details.
+.TP
+\fBmidi\-device \fIdevice\fR
+The device to use for the Musical Instrument Digital Interface.
+See the
+.B \-m
+.RB "(" "\-\-midi\-device=" ")"
+option for details.
+.TP
+\fBpcm\-device \fIdevice\fR
+The device to use for digital audio.
+See the
+.B \-p
+.RB "(" "\-\-pcm\-device=" ")"
+option for details.
+.TP
+\fBpreferences\-file \fIfile\fR
+The preferences file.
+See the
+.B \-F
+.RB "(" "\-\-preferences\-file=" ")"
+option for details.
+.TP
+\fBrelease\-device on\fR|\fBoff\fR
+Release the device to which the braille display is connected
+when the current screen or window can't be read.
+See the
+.B \-r
+.RB "(" "\-\-release\-device" ")"
+option for details.
+.TP
+\fBscreen\-driver \fIdriver\fR
+The screen driver.
+See the
+.B \-x
+.RB "(" "\-\-screen\-driver=" ")"
+option for details.
+.TP
+\fBscreen\-parameters \fIname\fB=\fIvalue\fB,\fR...
+Parameters for the screen driver.
+See the
+.B \-X
+.RB "(" "\-\-screen\-parameters=" ")"
+option for details.
+.TP
+\fBspeech\-driver \fIdriver\fB,\fR...|\fBauto\fR
+The driver for the speech synthesizer.
+See the
+.B \-s
+.RB "(" "\-\-speech\-driver=" ")"
+option for details.
+.TP
+\fBspeech\-input \name\fR
+The file system object which gives other applications access to
+.BR brltty 's
+speech driver for text\-to\-speech conversion.
+See the
+.B \-i
+.RB "(" "\-\-speech\-input=" ")"
+option for details.
+.TP
+\fBspeech\-parameters \fR[\fIdriver\fB:\fR]\fIname\fB=\fIvalue\fB,\fR...
+Parameters for the speech synthesizer driver.
+See the
+.B \-S
+.RB "(" "\-\-speech\-parameters=" ")"
+option for details.
+.TP
+\fBtext\-table \fItable\fR
+The text table.
+See the
+.B \-t
+.RB "(" "\-\-text\-table=" ")"
+option for details.
+.SS "Driver Specification"
+A braille display or speech synthesizer driver
+must be specified via its identification code:
+.RS
+.TP 4
+.B al
+Alva
+.TP 4
+.B an
+Android
+.TP 4
+.B at
+Albatross
+.TP 4
+.B ba
+BrlAPI
+.TP 4
+.B bc
+BrailComm
+.TP 4
+.B bd
+Braudi
+.TP 4
+.B bl
+BrailleLite
+.TP 4
+.B bm
+Baum
+.TP 4
+.B bn
+BrailleNote
+.TP 4
+.B cb
+CombiBraille
+.TP 4
+.B ce
+Cebra
+.TP 4
+.B cn
+Canute
+.TP 4
+.B dp
+DotPad
+.TP 4
+.B ec
+EcoBraille
+.TP 4
+.B en
+eSpeak-NG
+.TP 4
+.B es
+eSpeak
+.TP 4
+.B eu
+EuroBraille
+.TP 4
+.B fa
+FrankAudiodata
+.TP 4
+.B fl
+FestivalLite
+.TP 4
+.B fs
+FreedomScientific
+.TP 4
+.B fv
+Festival
+.TP 4
+.B gs
+GenericSay
+.TP 4
+.B hd
+Hedo
+.TP 4
+.B hm
+HIMS
+.TP 4
+.B ht
+HandyTech
+.TP 4
+.B hw
+HumanWare
+.TP 4
+.B ir
+Iris
+.TP 4
+.B ic
+Inceptor
+.TP 4
+.B lb
+Libbraille
+.TP 4
+.B lt
+LogText
+.TP 4
+.B mb
+MultiBraille
+.TP 4
+.B md
+MDV
+.TP 4
+.B mm
+BrailleMemo
+.TP 4
+.B mn
+MiniBraille
+.TP 4
+.B mp
+Mikropuhe
+.TP 4
+.B mt
+Metec
+.TP 4
+.B no
+no driver
+.TP 4
+.B np
+NinePoint
+.TP 4
+.B pg
+Pegasus
+.TP 4
+.B pm
+Papenmeier
+.TP 4
+.B sd
+SpeechDispatcher
+.TP 4
+.B sk
+Seika
+.TP 4
+.B sw
+Swift
+.TP 4
+.B th
+Theta
+.TP 4
+.B tn
+TechniBraille Systems Inc.
+.TP 4
+.B ts
+Telesensory Systems Inc.
+.TP 4
+.B vd
+VideoBraille
+.TP 4
+.B vo
+Voyager, Part232 (serial adapter), BraillePen/EasyLink
+.TP 4
+.B vs
+VisioBraille
+.TP 4
+.B vv
+ViaVoice
+.TP 4
+.B xs
+ExternalSpeech
+.PP
+A comma-delimited list of drivers may be specified.
+If this is done then autodetection is performed using each listed driver in sequence.
+You may need to experiment in order to determine the most reliable order
+since some drivers autodetect better than others.
+.PP
+If the single word
+.B auto
+is specified then autodetection is performed
+using only those drivers which are known to be reliable for this purpose.
+.RE
+.SH "SEE ALSO"
+For full documentation, see
+.BR brltty 's
+on-line manual at
+.RB "[" "http://brltty.app/documentation.html" "]."
diff --git a/Documents/brltty.conf.in b/Documents/brltty.conf.in
new file mode 100644
index 0000000..5e8e483
--- /dev/null
+++ b/Documents/brltty.conf.in
@@ -0,0 +1,759 @@
+# @configure_input@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://BRLTTY.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This is a configuration file template for the BRLTTY application. 
+# Uncomment those entries which apply to your personal needs and system
+# requirements.
+
+# BRLTTY expects to find its configuration file in "@CONFIGURATION_DIRECTORY@/@CONFIGURATION_FILE@"
+# (can be overridden with the --configuration-file= [-f] option).
+# If it doesn't exist, then BRLTTY silently continues but may require
+# that certain command line options be explicitly supplied.
+
+# Blank lines are ignored. The character '#', anywhere on a line,
+# initiates a comment; all characters from it to the end of that line
+# are ignored.
+
+# Each configuration entry consists of a keyword followed by its operand.
+# An arbitrary amount of white space, (blanks and/or tabs), may occur
+# before the keyword, as well as before and after the operand. Keyword
+# processing isn't case sensitive. Examples of valid entries are:
+#
+#	Braille-Driver	pm	# Papenmeier braille displays.
+#	braille-device	serial:@serial_first_device@	# The first serial device.
+#	SPEECH-DRIVER	fv	# The Festival Text to Speech System.
+
+# The default settings given within the following descriptions assume no
+# special build options (see "./configure --help" in the top-level
+# directory of the source tree.
+
+
+####################
+# Braille Settings #
+####################
+
+# The braille-driver directive specifies the two-letter driver
+# identification code of the driver for the braille display.
+# If not specified, autodetection will be performed.
+# If more than one driver, separated by commas, is specified,
+# then autodetection will be performed amongst them.
+# (can be overridden with the --braille-driver= [-b] option)
+#braille-driver	auto	# autodetect
+#braille-driver	al	# Alva
+#braille-driver	at	# Albatross
+#braille-driver	ba	# BrlAPI
+#braille-driver	bc	# BrailComm
+#braille-driver	bd	# Braudi
+#braille-driver	bl	# BrailleLite
+#braille-driver	bm	# Baum;; Refreshabraille, Orbit, NLS eReader Zoomax, NBP B2G
+#braille-driver	bn	# BrailleNote
+#braille-driver	cb	# CombiBraille
+#braille-driver	ce	# Cebra
+#braille-driver	cn	# Canute
+#braille-driver	dp	# DotPad
+#braille-driver	ec	# EcoBraille
+#braille-driver	eu	# EuroBraille
+#braille-driver	fa	# FrankAudiodata
+#braille-driver	fs	# FreedomScientific; VFO, Vispero
+#braille-driver	hd	# Hedo
+#braille-driver	hm	# HIMS
+#braille-driver	ht	# HandyTech; HelpTech
+#braille-driver	hw	# HumanWare;; APH Chameleon, APH Mantis, NLS eReader
+#braille-driver	ic	# Inceptor; Innovision
+#braille-driver	ir	# Iris
+#braille-driver	lb	# Libbraille
+#braille-driver	lt	# LogText
+#braille-driver	mb	# MultiBraille
+#braille-driver	md	# MDV
+#braille-driver	mm	# BrailleMemo
+#braille-driver	mn	# MiniBraille
+#braille-driver	mt	# Metec
+#braille-driver	np	# NinePoint
+#braille-driver	pg	# Pegasus
+#braille-driver	pm	# Papenmeier
+#braille-driver	sk	# Seika
+#braille-driver	tn	# TechniBraille
+#braille-driver	ts	# TSI
+#braille-driver	vd	# VideoBraille
+#braille-driver	vo	# Voyager;; Braille Pen, Easy Link
+#braille-driver	vs	# VisioBraille
+
+# The braille-device directive specifies the device to which the braille
+# display is connected. The generic syntax is type:device. The device
+# type is optional, and, for backward compatibility, defaults to serial.
+# The following devices are supported (brackets indicate optionality):
+#    serial:path (relative paths are anchored at "/dev")
+#    usb:[serial-number]
+#    bluetooth:address
+# If not specified, "@braille_device@" will be used.
+# If more than one device, separated by commas, is specified,
+# then each of them will be probed in turn.
+# (can be overridden with the --braille-device= [-d] option)
+#braille-device	serial:@serial_first_device@	# First serial device.
+#braille-device	usb:		# First USB device matching braille driver.
+#braille-device	usb:nnnnn	# Specific USB device by serial number.
+#braille-device	bluetooth:		# First paired Bluetooth device matching braille driver.
+#braille-device	bluetooth:xx:xx:xx:xx:xx:xx	# Specific bluetooth device by address.
+
+# NOTE: If the device is connected via a serial-to-USB adapter then setting
+# braille-device to usb: will not work. In this case, you needs to identify the
+# virtual serial device which the kernel has created for the adapter, i.e. to
+# something like serial:ttyUSB0 or ttyACM0 (see the kernel messages on device
+# plug to get the actual device name).
+
+# The release-device directive specifies whether or not the device to which the
+# braille display is connected is to be released when the current screen or
+# window can't be read by BRLTTY. If not specified, "on" will be used on Windows
+# platforms and "off" will be used on all other platforms.
+# (can be overridden with the --release-device [-r] option)
+#release-device	on	# Release the device.
+#release-device	off	# Don't release the device.
+
+# The text-table directive specifies which text table to use. Relative paths
+# are anchored at "@TABLES_DIRECTORY@/@TEXT_TABLES_SUBDIRECTORY@". If not specified, locale-based
+# autoselection with fallback to "@text_table@" will be performed.
+# (can be overridden with the --text-table= [-t] option)
+#text-table	auto	# locale-based autoselection
+#text-table	ar	# Arabic (generic)
+#text-table	as	# Assamese
+#text-table	awa	# Awadhi
+#text-table	bg	# Bulgarian
+#text-table	bh	# Bihari
+#text-table	bn	# Bengali
+#text-table	bo	# Tibetan
+#text-table	bra	# Braj
+#text-table	brf	# Braille Ready Format (for viewing .brf files within an editor or pager)
+#text-table	cs	# Czech
+#text-table	cy	# Welsh
+#text-table	da	# Danish
+#text-table	da-1252	# Danish (Svend Thougaard, 2002–11–18)
+#text-table	da-lt	# Danish (LogText)
+#text-table	de	# German
+#text-table	dra	# Dravidian
+#text-table	el	# Greek
+#text-table	en	# English
+#text-table	en_CA	# English (Canada)
+#text-table	en_GB	# English (United Kingdom)
+#text-table	en_US	# English (United States)
+#text-table	en-nabcc	# English (North American Braille Computer Code)
+#text-table	eo	# Esperanto
+#text-table	es	# Spanish
+#text-table	et	# Estonian
+#text-table	fi	# Finnish
+#text-table	fr	# French
+#text-table	fr_CA	# French (Canada)
+#text-table	fr_FR	# French (France)
+#text-table	fr-2007	# French (unified 2007)
+#text-table	fr-cbifs	# French (Code Braille Informatique Français Standard)
+#text-table	fr-vs	# French (VisioBraille)
+#text-table	ga	# Irish
+#text-table	gd	# Gaelic
+#text-table	gon	# Gondi
+#text-table	gu	# Gujarati
+#text-table	he	# Hebrew
+#text-table	hi	# Hindi
+#text-table	hr	# Croatian
+#text-table	hu	# Hungarian
+#text-table	hy	# Armenian
+#text-table	is	# Icelandic
+#text-table	it	# Italian
+#text-table	kha	# Khasi
+#text-table	kn	# Kannada
+#text-table	kok	# Konkani
+#text-table	kru	# Kurukh
+#text-table	lt	# Lituanian
+#text-table	lv	# Latvian
+#text-table	mg	# Malagasy
+#text-table	mi	# Maori
+#text-table	ml	# Malayalam
+#text-table	mni	# Manipuri
+#text-table	mr	# Marathi
+#text-table	mt	# Maltese
+#text-table	mun	# Munda
+#text-table	mwr	# Marwari
+#text-table	ne	# Nepali
+#text-table	new	# Newari
+#text-table	nl	# Dutch
+#text-table	nl_BE	# Dutch (Belgium)
+#text-table	nl_NL	# Dutch (Netherlands)
+#text-table	no	# Norwegian
+#text-table	no-generic	# Norwegian (with support for other languages)
+#text-table	no-oup	# Norwegian (Offentlig utvalg for punktskrift)
+#text-table	nwc	# Newari (old)
+#text-table	or	# Oriya
+#text-table	pa	# Panjabi
+#text-table	pi	# Pali
+#text-table	pl	# Polish
+#text-table	pt	# Portuguese
+#text-table	ro	# Romanian
+#text-table	ru	# Russian
+#text-table	se	# Sami (Northern)
+#text-table	sa	# Sanskrit
+#text-table	sat	# Santali
+#text-table	sd	# Sindhi
+#text-table	sk	# Slovak
+#text-table	sl	# Slovenian
+#text-table	sv	# Swedish
+#text-table	sv-1989	# Swedish (1989 standard)
+#text-table	sv-1996	# Swedish (1996 standard)
+#text-table	sw	# Swahili
+#text-table	ta	# Tamil
+#text-table	te	# Telugu
+#text-table	tr	# Turkish
+#text-table	uk	# Ukrainian
+#text-table	vi	# Vietnamese
+
+# The contraction-table directive specifies which contraction table to use.
+# Relative paths are anchored at "@TABLES_DIRECTORY@/@CONTRACTION_TABLES_SUBDIRECTORY@". If not specified, no
+# contraction table will be available.
+# (can be overridden with the --contraction-table= [-c] option)
+#contraction-table	auto	# locale-based autoselection
+#contraction-table	af	# Afrikaans (contracted)
+#contraction-table	am	# Amharic (uncontracted)
+#contraction-table	de	# German
+#contraction-table	de-g0	# German (uncontracted)
+#contraction-table	de-g1	# German (basic contractions)
+#contraction-table	de-g2	# German (contracted)
+#contraction-table	de-1998	# German (contracted - 1998 standard)
+#contraction-table	de-2015	# German (contracted - 2015 standard)
+#contraction-table	en	# English
+#contraction-table	en_US	# English (United States)
+#contraction-table	en-ueb-g1	# English (Unified, uncontracted)
+#contraction-table	en-ueb-g2	# English (Unified, contracted)
+#contraction-table	en-us-g2	# English (United States, contracted)
+#contraction-table	es	# Spanish (contracted)
+#contraction-table	fr	# French
+#contraction-table	fr-g1	# French (uncontracted)
+#contraction-table	fr-g2	# French (contracted)
+#contraction-table	ha	# Hausa (contracted)
+#contraction-table	id	# Indonesian (contracted)
+#contraction-table	ipa	# International Phonetic Alphabet
+#contraction-table	ja	# Japanese (uncontracted)
+#contraction-table	ko	# Korean
+#contraction-table	ko-g0	# Korean (uncontracted)
+#contraction-table	ko-g1	# Korean (partially contracted)
+#contraction-table	ko-g2	# Korean (contracted)
+#contraction-table	lt	# Lithuanian (uncontracted)
+#contraction-table	mg	# Malagasy (contracted)
+#contraction-table	mun	# Munda (contracted)
+#contraction-table	nl	# Dutch (contracted)
+#contraction-table	ny	# Chichewa (contracted)
+#contraction-table	pt	# Portuguese (contracted)
+#contraction-table	ru	# Russian (contracted)
+#contraction-table	si	# Sinhalese (uncontracted)
+#contraction-table	sw	# Swahili (contracted)
+#contraction-table	th	# Thai (contracted)
+#contraction-table	zh_TW	# Chinese (Taiwan, uncontracted)
+#contraction-table	zu	# Zulu (contracted)
+
+# The attributes-table directive specifies which attributes table to use.
+# Relative paths are anchored at "@TABLES_DIRECTORY@/@ATTRIBUTES_TABLES_SUBDIRECTORY@". If not specified,
+# "@attributes_table@" will be used.
+# (can be overridden with the --attributes-table= [-a] option)
+#attributes-table	left_right	# foreground colour in the left column and background colour in the right column
+#attributes-table	invleft_right	# inverse foreground colour in the left column and background colour in the right column
+#attributes-table	upper_lower	# foreground colour in the upper square and background colour in the lower square
+
+
+#############################
+# Braille Driver Parameters #
+#############################
+
+# The braille-parameters directive passes driver-specific parameters
+# through to the braille driver.
+# (can be specified more than once)
+# (can be overridden with the --braille-parameters= [-B] option)
+#braille-parameters driver:name=value,...
+
+# Alva Braille Driver Parameters
+#braille-parameters al:RotatedCells=no # [no,yes]
+#braille-parameters al:SecondaryRoutingKeyEmulation=no # [no,yes]
+
+# BrlAPI Braille Driver Parameters
+#braille-parameters ba:Auth=@CONFIGURATION_DIRECTORY@/@api_authkeyfile@
+#braille-parameters ba:Host=:0
+#braille-parameters ba:SpeechChanges=yes # [yes,no]
+
+# BrailleLite Braille Driver Parameters
+#braille-parameters bl:BaudRate=9600 # [300,600,1200,2400,4800,9600,19200,38400]
+#braille-parameters bl:KbEmu=yes # [yes,no]
+
+# Baum Braille Driver Parameters
+#braille-parameters bm:Protocol=default # [default,escape,hid1,hid2,ht,pb]
+#braille-parameters bm:VarioKeys=no # [no,yes]
+
+# DotPad Braille Driver Parameters
+#brailledd-parameters dp:Display=default # [default,text,graphic]
+
+# EuroBraille Braille Driver Parameters
+#braille-parameters eu:Protocol= # [auto,azerbraille,clio,eurobraille,notebraille,pupibraille,scriba,esys,esytime,iris,esysiris]
+
+# HandyTech Braille Driver Parameters
+#braille-parameters ht:SetTime=no # [no,yes]
+
+# Iris Braille Driver Parameters
+#braille-parameters ir:Embedded= # [no,yes]
+#braille-parameters ir:LatchDelay=10 # [0-100] (tenths of a second)
+#braille-parameters ir:Protocol= # [eurobraille,native]
+
+# Libbraille Braille Driver Parameters
+#braille-parameters lb:Device=/dev/ttyS0 # 
+#braille-parameters lb:Driver=auto # 
+#braille-parameters lb:Table=us.tbl # 
+
+# TSI Braille Driver Parameters
+#braille-parameters ts:HighBaud=yes # [yes,no]
+#braille-parameters ts:SetBaud=9600 # [4800,9600,19200]
+
+# TTY Braille Driver Parameters
+#braille-parameters tt:Baud=9600 # 
+#braille-parameters tt:CharSet=ISO8859-1 # 
+#braille-parameters tt:Columns=40 # [1-80]
+#braille-parameters tt:Lines=1 # [1-3]
+#braille-parameters tt:Locale= # 
+#braille-parameters tt:Term=vt100 # [terminfo]
+
+# VisioBraille Braille Driver Parameters
+#braille-parameters vs:Baud=57600 # 
+#braille-parameters vs:DisplaySize=40 # [20-40]
+#braille-parameters vs:PromVersion=4 # [3-6]
+
+# XWindow Braille Driver Parameters
+#braille-parameters xw:Columns=40 # [1-80]
+#braille-parameters xw:Font=name # []
+#braille-parameters xw:Input=off # [off,on]
+#braille-parameters xw:Lines=1 # [1-3]
+#braille-parameters xw:Model=bare # [bare]
+#braille-parameters xw:TkParms= # 
+
+
+###################
+# Speech Settings #
+###################
+
+# The speech-driver directive specifies the two-letter driver
+# identification code of the driver for the speech synthesizer.
+# If not specified, autodetection will be performed.
+# If more than one driver, separated by commas, is specified,
+# then autodetection will be performed amongst them.
+# (can be overridden with the --speech-driver= [-s] option)
+#speech-driver	auto	# autodetect
+#speech-driver	al	# Alva
+#speech-driver	an	# Android
+#speech-driver	bl	# BrailleLite
+#speech-driver	cb	# CombiBraille
+#speech-driver	en	# eSpeak-NG
+#speech-driver	es	# eSpeak
+#speech-driver	fl	# FestivalLite
+#speech-driver	fv	# Festival
+#speech-driver	gs	# GenericSay
+#speech-driver	mp	# Mikropuhe
+#speech-driver	sd	# SpeechDispatcher
+#speech-driver	sw	# Swift
+#speech-driver	th	# Theta
+#speech-driver	vv	# ViaVoice; Voxin
+#speech-driver	xs	# ExternalSpeech
+
+# The speech-input directive specifies the name of the file system object
+# (FIFO, named pipe, named socket, etc) which can be used by external
+# applications for text-to-speech conversion via BRLTTY's speech driver.
+# Relative paths are anchored at the current working directory. If not
+# specified, the file system object isn't created.
+# (can be overridden with the --speech-input= [-i] option)
+#speech-input	/path/to/file-system-object
+
+# The quiet-if-no-braille directive specifies that the autospeak feature
+# isn't to be implicitly enabled when the braille driver is not running.
+# (can be overridden with the --quiet-if-no-braille= [-Q] option)
+#quiet-if-no-braille	off	# [off,on]
+
+# The autospeak-threshold directive specifies the minimum screen content
+# quality that will trigger the autospeak feature.
+# (can be overridden with the --autospeak-threshold= option)
+#autospeak-threshold	none
+
+
+############################
+# Speech Driver Parameters #
+############################
+
+# The speech-parameters directive passes driver-specific parameters through
+# to the speech driver.
+# (can be specified more than once)
+# (can be overridden with the --speech-parameters= [-S] option)
+#speech-parameters driver:name=value,...
+
+# eSpeak Speech Driver Parameters
+#speech-parameters es:MaxRate=450 # [80-]
+#speech-parameters es:Path=
+#speech-parameters es:PunctList=
+#speech-parameters es:Voice=default
+
+# eSpeak-NG Speech Driver Parameters
+#speech-parameters en:MaxRate=450 # [80-]
+#speech-parameters en:Path=
+#speech-parameters en:PunctList=
+#speech-parameters en:Voice=en
+
+# ExternalSpeech Speech Driver Parameters
+#speech-parameters xs:Socket_Path=/tmp/exs-data
+
+# Festival Speech Driver Parameters
+#speech-parameters fv:Command=festival # [/path/to/command]
+#speech-parameters fv:Name= # [kevin,kal]
+
+# FestivalLite Speech Driver Parameters
+#speech-parameters fl:Pitch=100 # [50-200]
+
+# GenericSay Speech Driver Parameters
+#speech-parameters gs:Command=/usr/local/bin/say
+
+# Mikropuhe Speech Driver Parameters
+#speech-parameters mp:Name= # [/path/to/mikropuhe/name.pu5]
+#speech-parameters mp:Pitch=0 # [-10-10]
+
+# SpeechDispatcher Speech Driver Parameters
+#speech-parameters sd:Language= # [two-letter language code]
+#speech-parameters sd:Module= # [flite,festival,epos-generic,dtk-generic,...]
+#speech-parameters sd:Name= # 
+#speech-parameters sd:Port=6560 # [1-65535]
+#speech-parameters sd:Voice= # [male1,female1,male2,female2,male3,female3,child_male,child_female]
+
+# Swift Speech Driver Parameters
+#speech-parameters sw:Name= # [voice,/path/to/voice]
+
+# Theta Speech Driver Parameters
+#speech-parameters th:Age= # [1-99,-1--99]
+#speech-parameters th:Gender= # [male,female,neuter]
+#speech-parameters th:Language= # [two-letter language code]
+#speech-parameters th:Name= # [voice,/path/to/voice]
+#speech-parameters th:Pitch=0.0 # [-2.0-2.0]
+
+# ViaVoice Speech Driver Parameters
+#speech-parameters vv:Quality= # [fair,poor,good]
+#speech-parameters vv:Mode= # [words,letters,punctuation,phonetic]
+#speech-parameters vv:Synthesize= # [sentences,all]
+#speech-parameters vv:Abbreviations= # [on,off]
+#speech-parameters vv:Years= # [on,off]
+#speech-parameters vv:Language= # [American-English, British-English, Castilian-Spanish, Mexican-Spanish, Standard-French, Canadian-French, Standard-German, Standard-Italian, Standard-Mandarin-GBK, Standard-Mandarin-PinYin, Standard-Mandarin-UCS2, Taiwanese-Mandarin-Big5, Taiwanese-Mandarin-ZhuYin, Taiwanese-Mandarin-PinYin, Taiwanese-Mandarin-UCS2, Brazilian-Portuguese, Standard-Japanese-SJIS, Standard-Japanese-UCS2, Standard-Finnish, Standard-Korean-UHC, Standard-Korean-UCS2, Standard-Cantonese-GBK, Standard-Cantonese-UCS2, HongKong-Cantonese-Big5, HongKong-Cantonese-UCS2, Standard-Dutch, Standard-Norwegian, Standard-Swedish, Standard-Danish, Standard-Thai-TIS620]
+#speech-parameters vv:Voice= # [man,woman,child,patriarch,matriarch]
+#speech-parameters vv:Gender= # [male,female]
+#speech-parameters vv:HeadSize= # [0-100]
+#speech-parameters vv:PitchBaseline= # [40-422 Hz]
+#speech-parameters vv:Expressiveness= # [0-100] (pitch fluctuation)
+#speech-parameters vv:Roughness= # [0-100]
+#speech-parameters vv:Breathiness= # [0-100]
+#speech-parameters vv:Volume= # [0-100 %]
+#speech-parameters vv:Speed= # [70-1297 wpm]
+
+
+#####################
+# Keyboard Settings #
+#####################
+
+# The keyboard-table directive specifies which keyboard table to use. Relative paths are
+# anchored at "@TABLES_DIRECTORY@/@KEYBOARD_TABLES_SUBDIRECTORY@". If not specified, no keyboard table is used.
+# (can be overridden with the --keyboard-table= [-k] option)
+#keyboard-table	off	# no keyboard table
+#keyboard-table	braille	# bindings for braille keyboards
+#keyboard-table	desktop	# bindings for full keyboards
+#keyboard-table	keypad	# bindings for keypad-based navigation
+#keyboard-table	laptop	# bindings for keyboards without a keypad
+#keyboard-table	sun_type6	# bindings for Sun Type 6 keyboards
+
+# The keyboard-properties directive specifies which keyboards to monitor.
+# If not specified, all keyboards are monitored.
+# (can be specified more than once)
+# (can be overridden with the --keyboard-properties= [-K] option)
+#keyboard-properties	Type=any	# [any,ps2,usb,bluetooth,internal]
+#keyboard-properties	Vendor=0X0000	# [0X0000-0XFFFF]
+#keyboard-properties	Product=0X0000	# [0X0000-0XFFFF]
+
+
+##################
+# Sound Settings #
+##################
+
+# The pcm-device directive specifies the device to use for soundcard
+# digital audio. If not specified, a method- and system-dependent
+# default will be used.
+# (can be overridden with the --pcm-device= [-p] option)
+#pcm-device	/path/to/device	# most methods
+#pcm-device	pcm-handle-id	# ALSA (see second parameter of snd_pcm_open)
+
+# The midi-device directive specifies the device to use for the Musical
+# Instrument Digital Interface. If not specified, a method- and
+# system-dependent default will be used.
+# (can be overridden with the --midi-device= [-m] option)
+#midi-device	/path/to/device	# most methods
+#midi-device	client:port	# ALSA (may use number or case-sensitive substring of name)
+
+
+###################
+# Logging Options #
+###################
+
+# The quiet directive reduces the logging level to standard error by one
+# level and also suppresses the braille and speech driver banners.
+# (can be overridden with the --quiet [-q] option)
+#quiet off	# [off,on]
+
+# The log-file directive specifies the file to which diagnostics are written.
+# Relative paths are anchored at the current working directory. If not
+# specified, diagnostics are written to the system log.
+# (can be overridden with the --log-file= [-L] option)
+#log-file	/tmp/brltty.log
+
+# The log-level directive specifies which event categories are to be
+# logged as well as the severity threshold for uncategorized events.
+# The category names and severity threshold are separated by commas.
+# (can be overridden with the --log-level= [-l] option)
+#log-level	notice
+
+# Only one severity threshold may be specified. Less severe uncategorized
+# events will not be logged. If not specified, "notice" will be assumed.
+# The severity thresholds are:
+#log-level	emergency
+#log-level	alert
+#log-level	critical
+#log-level	error
+#log-level	warning
+#log-level	notice
+#log-level	information
+#log-level	debug
+
+# Any number of event categories may be enabled. Prefixing a category name
+# with a minus sign [-] disables it (particularly useful if "all" is used).
+# The categories are:
+#log-level	all	# enable all of the categories
+#log-level	inpkts	# input packets
+#log-level	outpkts	# output packets
+#log-level	brlkeys	# braille device key events
+#log-level	kbdkeys	# keyboard key events
+#log-level	csrtrk	# cursor tracking
+#log-level	csrrtg	# cursor routing
+#log-level	update	# update events
+#log-level	speech	# speech events
+#log-level	async	# asynchronous event scheduling
+#log-level	server	# BrlAPI server events
+#log-level	gio	# generic I/O
+#log-level	serial	# serial I/O
+#log-level	usb	# USB I/O
+#log-level	bt	# Bluetooth I/O
+#log-level	hid	# Human Interface I/O
+#log-level	brldrv	# braille driver events
+#log-level	spkdrv	# speech driver events
+#log-level	scrdrv	# screen driver events
+
+
+#######################
+# Preference Settings #
+#######################
+
+# The preferences-file directive specifies the file in which to save the user's
+# preferences. Relative paths are usually anchored at "@UPDATABLE_DIRECTORY@"
+# (can be changed via the updatable-directory directive).
+# If not specified, "@PREFERENCES_FILE@" will be used.
+# (can be overridden with the --preferences-file= [-F] option)
+#preferences-file	@PREFERENCES_FILE@
+
+# The override-preferences directive can be used to explicitly specify the
+# initial setting of any preference. More than one preference may be overridden
+# by separating them by commas and/or by using this directive multiple times.
+# (can be specified more than once)
+# (can be overridden with the --override-preferences= [-o] option)
+#override-preferences preference=setting,...
+
+
+##########################
+# Miscellaneous Settings #
+##########################
+
+# The pid-file directive specifies the absolute path to a file that will
+# contain BRLTTY's process identifier. It is removed when BRLTTY terminates.
+# (can be overridden with the --pid-file= [-P] option)
+#pid-file @WRITABLE_DIRECTORY@/brltty.pid
+
+# The locale-directory directive specifies the absolute path to the directory
+# which contains the compiled message translations for other languages. If not
+# specified, "@LOCALE_DIRECTORY@" will be used.
+# (can be overridden with the --locale-directory= option)
+#locale-directory @LOCALE_DIRECTORY@
+
+# The updatable-directory directive specifies the absolute path to a directory
+# which contains files that can be updated (preferences, saved clipboard, etc).
+# If not specified, "@UPDATABLE_DIRECTORY@" will be used.
+# (can be overridden with the --updatable-directory= [-U] option)
+#updatable-directory @UPDATABLE_DIRECTORY@
+
+# The writable-directory directive specifies the absolute path to a directory
+# which can be written to (creation of missing but needed resources, etc). If
+# not specified, "@WRITABLE_DIRECTORY@" will be used.
+# (can be overridden with the --writable-directory= [-W] option)
+#writable-directory @WRITABLE_DIRECTORY@
+
+# The drivers-directory directive specifies the absolute path to the
+# directory which contains the dynamically loadable drivers. If not
+# specified, @DRIVERS_DIRECTORY@ will be used.
+# (can be overridden with the --drivers-directory= [-D] option)
+#drivers-directory @DRIVERS_DIRECTORY@
+
+# The tables-directory directive specifies the absolute path to the directory
+# which contains the text, contraction, attributes, keyboard, and input tables.
+# If not specified, "@TABLES_DIRECTORY@" will be used.
+# (can be overridden with the --tables-directory= [-T] option)
+#tables-directory @TABLES_DIRECTORY@
+
+# The start-message directive specifies the text to be shown when the braille
+# driver starts and to be spoken when the speech driver starts. If not
+# specified, "@PACKAGE_NAME@ @PACKAGE_VERSION@" will be used.
+# (can be overridden with the --start-message= option)
+#start-message @PACKAGE_NAME@\s@PACKAGE_VERSION@
+
+# The stop-message directive specifies the text to be shown when the braille
+# driver stops. If not specified, "@PACKAGE_NAME@ stopped" will be used.
+# (can be overridden with the --stop-message= option)
+#stop-message @PACKAGE_NAME@\sstopped
+
+# The prompt-patterns directive specifies a list of regular expressions
+# that are to be used by the NXPROMPT and PRPROMPT navigation commands.
+# If not specified, then an exact match of the content of the current line,
+# up to and including the first space, will be used.
+# (can be specified more than once)
+# (can be overridden with the --prompt-patterns= option)
+#prompt-patterns	regexp,...
+
+# The message-time directive specifies how long a message is to be displayed
+# before it's automatically cleared. If not specified, four seconds will be
+# used.
+# (can be overridden with the --message-time= [-M] option)
+#message-time	400	# hundredths of a second
+
+
+########################
+# Privilege Parameters #
+########################
+
+# The stay-privileged directive disables switching to an unprivilegdd user
+# as well as the relinquishing of any privileges (group memberships,
+# capabilities, etc).
+# (can be overridden with the --stay-privileged= [-z] option)
+#stay-privileged	off	# [off,on]
+
+# The privilege-parameters directive specifies the default privilege
+# establishment parameters to use when BRLTTY is started by a privileged
+# user (e.g. by root on a Linux/Unix system). The configured defaults are:
+# "@privilege_parameters@".
+# (can be specified more than once)
+# (can be overridden with the --privilege-parameters= [-Z] option)
+#privilege-parameters platform:name=value,...
+
+# Linux Privilege Parameters
+#privilege-parameters lx:path=/usr/sbin:/sbin:/usr/bin:/bin
+#privilege-parameters lx:scfmode=no # [no,log,fail,kill]
+#privilege-parameters lx:shell=/bin/sh
+#privilege-parameters lx:user=brltty
+
+
+################################################
+# Application Programming Interface Parameters #
+################################################
+
+# The no-api directive disables the Application Programming Interface (API).
+# (can be overridden with the --no-api= [-N] option)
+#no-api	off	# [off,on]
+
+# The api-parameters directive passes parameters to the Application
+# Programming Interface.
+# (can be specified more than once)
+# (can be overridden with the --api-parameters= [-A] option)
+#api-parameters name=value,...
+
+#api-parameters Auth=keyfile:@CONFIGURATION_DIRECTORY@/@api_authkeyfile@	# Require authentication key
+#api-parameters Auth=user:joe		# Allow some local user
+#api-parameters Auth=group:brl		# Allow some local group
+#api-parameters Auth=polkit		# authenticate via Polkit
+#api-parameters Host=:0			# Accept only local Unix connections
+#api-parameters Host=0.0.0.0:0		# Accept any internet connection.
+#api-parameters StackSize=65536
+
+
+###################
+# Screen Settings #
+###################
+
+# The screen-driver directive specifies the two-letter driver
+# identification code of the driver for the console screen.
+# (can be overridden with the --screen-driver= [-x] option)
+#screen-driver	an	# Android
+#screen-driver	as	# AtSpi
+#screen-driver	a2	# AtSpi2
+#screen-driver	em	# Terminal Emulator
+#screen-driver	fv	# File Viewer
+#screen-driver	hd	# Hurd
+#screen-driver	lx	# Linux
+#screen-driver	pb	# PcBios
+#screen-driver	sc	# Screen
+#screen-driver	wn	# Windows
+
+
+############################
+# Screen Driver Parameters #
+############################
+
+# The screen-parameters directive passes driver-specific parameters through
+# to the screen driver.
+# (can be specified more than once)
+# (can be overridden with the --screen-parameters= [-X] option)
+#screen-parameters driver:name=value,...
+
+# AtSpi Screen Driver Parameters
+#screen-parameters as:Type=text+terminal # [text,terminal,all]+...
+
+# AtSpi2 Screen Driver Parameters
+#screen-parameters a2:Release=yes # [yes,no]
+#screen-parameters a2:Type=default # [default,all,{terminal,text}+...]
+
+# File Viewer Screen Driver Parameters
+#screen-parameters fv:File=path #
+
+# Linux Screen Driver Parameters
+#screen-parameters lx:Charset=name+... # []
+#screen-parameters lx:FallbackText=text # ""
+#screen-parameters lx:HFB=auto # [auto,vga,fb,0-7]
+#screen-parameters lx:LogSFM=no # [no,yes]
+#screen-parameters lx:RpiSpacesBug=no # [no,yes]
+#screen-parameters lx:Unicode=yes # [yes,no]
+#screen-parameters lx:VT=0 # [0-63]
+#screen-parameters lx:WidecharPadding=no # [no,yes]
+
+# Terminal Emulator Screen Driver Parameters
+#screen-parameters em:Directory=path # []
+#screen-parameters em:Emulator=command # [brltty-pty]
+#screen-parameters em:Group=name/number # []
+#screen-parameters em:Home=path # []
+#screen-parameters em:Path=path # []
+#screen-parameters em:Shell=command # [$SHELL]
+#screen-parameters em:User=name/number # []
+
+# Windows Screen Driver Parameters
+#screen-parameters wn:Root=no # [no,yes]
+#screen-parameters wn:FollowFocus=yes # [yes,no]
+
+
diff --git a/Documents/common-options.rst b/Documents/common-options.rst
new file mode 100644
index 0000000..e337ae2
--- /dev/null
+++ b/Documents/common-options.rst
@@ -0,0 +1,11 @@
+``-v`` (verbose)
+   Increase output verbosity. This option complements the ``-q`` option. It may
+   be specified multiple times.
+
+``-q`` (quiet)
+   Decrease output verbosity. This option complements the ``-v`` option. It may
+   be specified multiple times.
+
+``-h`` (help)
+   Write a brief command line usage summary on standard output, and then exit.
+
diff --git a/Documents/condition-directives.rst b/Documents/condition-directives.rst
new file mode 100644
index 0000000..8423bab
--- /dev/null
+++ b/Documents/condition-directives.rst
@@ -0,0 +1,38 @@
+.. |directive operand| replace::
+   The directive that is to be conditionally processed.
+   It may contain spaces.
+   This operand is optional.
+   If it isn't supplied then this directive applies to all subsequent lines
+   until `The EndIf Directive`_ or `The Else Directive`_
+   that is at the same conditional nesting level.
+
+The EndIf Directive
+-------------------
+
+.. parsed-literal:: endIf
+
+Use this directive to terminate the current conditional nesting level.
+
+Examples::
+
+   ifVar x
+      These lines will be processed if a variable named x exists.
+   endIf
+
+The Else Directive
+------------------
+
+.. parsed-literal:: else
+
+Use this directive to negate the test associated with the current conditional
+nesting level.
+
+Examples::
+
+   assign x some\svalue
+   ifVar x
+      These lines will be processed.
+   else
+      These lines won't be processed.
+   endIf
+
diff --git a/Documents/contraction-table.csv b/Documents/contraction-table.csv
new file mode 100644
index 0000000..927e829
--- /dev/null
+++ b/Documents/contraction-table.csv
@@ -0,0 +1,39 @@
+"name","description"
+"auto","locale-based autoselection"
+"af","Afrikaans (contracted)"
+"am","Amharic (uncontracted)"
+"de","German"
+"de-g0","German (uncontracted)"
+"de-g1","German (basic contractions)"
+"de-g2","German (contracted)"
+"de-1998","German (contracted - 1998 standard)"
+"de-2015","German (contracted - 2015 standard)"
+"en","English"
+"en_US","English (United States)"
+"en-ueb-g1","English (Unified, uncontracted)"
+"en-ueb-g2","English (Unified, contracted)"
+"en-us-g2","English (United States, contracted)"
+"es","Spanish (contracted)"
+"fr","French"
+"fr-g1","French (uncontracted)"
+"fr-g2","French (contracted)"
+"ha","Hausa (contracted)"
+"id","Indonesian (contracted)"
+"ipa","International Phonetic Alphabet"
+"ja","Japanese (uncontracted)"
+"ko","Korean"
+"ko-g0","Korean (uncontracted)"
+"ko-g1","Korean (partially contracted)"
+"ko-g2","Korean (contracted)"
+"lt","Lithuanian (uncontracted)"
+"mg","Malagasy (contracted)"
+"mun","Munda (contracted)"
+"nl","Dutch (contracted)"
+"ny","Chichewa (contracted)"
+"pt","Portuguese (contracted)"
+"ru","Russian (contracted)"
+"si","Sinhalese (uncontracted)"
+"sw","Swahili (contracted)"
+"th","Thai (contracted)"
+"zh_TW","Chinese (Taiwan, uncontracted)"
+"zu","Zulu (contracted)"
diff --git a/Documents/keyboard-table.csv b/Documents/keyboard-table.csv
new file mode 100644
index 0000000..65fd739
--- /dev/null
+++ b/Documents/keyboard-table.csv
@@ -0,0 +1,7 @@
+"name","description"
+"off","no keyboard table"
+"braille","bindings for braille keyboards"
+"desktop","bindings for full keyboards"
+"keypad","bindings for keypad-based navigation"
+"laptop","bindings for keyboards without a keypad"
+"sun_type6","bindings for Sun Type 6 keyboards"
diff --git a/Documents/nesting-directives.rst b/Documents/nesting-directives.rst
new file mode 100644
index 0000000..4d471a9
--- /dev/null
+++ b/Documents/nesting-directives.rst
@@ -0,0 +1,13 @@
+The Include Directive
+---------------------
+
+.. parsed-literal:: include *file* # *comment*
+
+Use this directive to include the content of another file. It is recursive,
+which means that an included file can itself include yet another file.
+Care must be taken to ensure that an "include loop" is not created.
+
+*file*
+   The file to be included. It may be either a relative or an absolute path. If
+   relative, it is anchored at the directory containing the including file.
+
diff --git a/Documents/prologue.rst b/Documents/prologue.rst
new file mode 100644
index 0000000..cc757fa
--- /dev/null
+++ b/Documents/prologue.rst
@@ -0,0 +1,3 @@
+.. include:: references.rst
+.. include:: substitutions.rst
+.. contents::
diff --git a/Documents/references.rst b/Documents/references.rst
new file mode 100644
index 0000000..161c31e
--- /dev/null
+++ b/Documents/references.rst
@@ -0,0 +1,26 @@
+.. _BRLTTY on Android: Android.html
+.. _Attributes Tables: AttributesTables.html
+.. _Bluetooth Connections: Bluetooth.html
+.. _Braille Dots: BrailleDots.html
+.. _The Command Reference: CommandReference.html
+.. _Contraction Tables: ContractionTables.html
+.. _Local Customization: Customize.html
+.. _Device Identifiers: Devices.html
+.. _BRLTTY on DOS: DOS.html
+.. _Introduction to BRLTTY: Introduction.html
+.. _Key Tables: KeyTables.html
+.. _BRLTTY on Linux: Linux.html
+.. _BRLTTY on OpenBSD: OpenBSD.html
+.. _Polling: Polling.html
+.. _Privacy Policy: PrivacyPolicy.html
+.. _Profiles: Profiles.html
+.. _Using the Stow Build Manager: Stow.html
+.. _Using Systemd Service Management: Systemd.html
+.. _Text Tables: TextTables.html
+.. _Using Upstart Service Management: Upstart.html
+.. _BRLTTY on Windows: Windows.html
+.. _BRLTTY with X11: X11.html
+
+.. _BRLTTY's Web Site: https://brltty.app/
+.. _The Android SDK Web Page: https://developer.android.com/sdk/index.html
+.. _The Android NDK Web Page: https://developer.android.com/tools/sdk/ndk/index.html
diff --git a/Documents/screen-driver.csv b/Documents/screen-driver.csv
new file mode 100644
index 0000000..6218db1
--- /dev/null
+++ b/Documents/screen-driver.csv
@@ -0,0 +1,11 @@
+"code","name","usage"
+"an","Android","Android"
+"as","AtSpi","X11 desktop via AT-SPI 1"
+"a2","AtSpi2","X11 desktop via AT-SPI 2"
+"em","Terminal Emulator",""
+"fv","File Viewer",""
+"hd","Hurd","The Hurd"
+"lx","Linux","Linux"
+"pb","PcBios","MS-DOS"
+"sc","Screen","patched screen program"
+"wn","Windows","Microsoft Windows"
diff --git a/Documents/speech-driver.csv b/Documents/speech-driver.csv
new file mode 100644
index 0000000..153d529
--- /dev/null
+++ b/Documents/speech-driver.csv
@@ -0,0 +1,17 @@
+"code","name","aliases","usage"
+"auto","autodetect","",""
+"al","Alva","","braille device"
+"an","Android","","OS-specific"
+"bl","BrailleLite","","braille device"
+"cb","CombiBraille","","braille device"
+"en","eSpeak-NG","","software engine"
+"es","eSpeak","","software engine"
+"fl","FestivalLite","","software engine"
+"fv","Festival","","software engine"
+"gs","GenericSay","","pipes to /usr/local/bin/say"
+"mp","Mikropuhe","","software engine"
+"sd","SpeechDispatcher","","multi-engine server"
+"sw","Swift","","software engine"
+"th","Theta","","software engine"
+"vv","ViaVoice","Voxin","software engine"
+"xs","ExternalSpeech","","sends speech to external program and receives spoken position indexes"
diff --git a/Documents/string-operand.rst b/Documents/string-operand.rst
new file mode 100644
index 0000000..ad041d6
--- /dev/null
+++ b/Documents/string-operand.rst
@@ -0,0 +1,32 @@
+The String Operand
+------------------
+
+A *string* operand may be specified as a non-whitespace sequence of:
+
+*  Any single character other than a backslash (\\\\) or a white-space
+   character.
+
+*  A backslash-prefixed special character. These are:
+
+   ===============  ==========================================================
+   Sequence         Meaning
+   ---------------  ----------------------------------------------------------
+   ``\b``           The backspace character.
+   ``\f``           The formfeed character.
+   ``\n``           The newline character.
+   ``\o###``        The three-digit octal representation of a character.
+   ``\r``           The carriage return character.
+   ``\R``           The Unicode replacement character.
+   ``\s``           The space character.
+   ``\t``           The horizontal tab character.
+   ``\u####``       The four-digit hexadecimal representation of a character.
+   ``\U########``   The eight-digit hexadecimal representation of a character.
+   ``\v``           The vertical tab character.
+   ``\x##``         The two-digit hexadecimal representation of a character.
+   ``\X##``         (the case of the X and of the digits isn't significant)
+   ``\<name>``      The Unicode name of a character (use _ for space).
+   ``\{variable}``  The value of a variable.
+   ``\\``           A literal backslash.
+   ``\#``           A literal number sign.
+   ===============  ==========================================================
+
diff --git a/Documents/substitutions.rst b/Documents/substitutions.rst
new file mode 100644
index 0000000..a76dff3
--- /dev/null
+++ b/Documents/substitutions.rst
@@ -0,0 +1,22 @@
+.. |README.Android| replace:: `BRLTTY on Android`_
+.. |README.AttributesTables| replace:: `Attributes Tables`_
+.. |README.Bluetooth| replace:: `Bluetooth Connections`_
+.. |README.BrailleDots| replace:: `Braille Dots`_
+.. |README.CommandReference| replace:: `The Command Reference`_
+.. |README.ContractionTables| replace:: `Contraction Tables`_
+.. |README.Customize| replace:: `Local Customization`_
+.. |README.Devices| replace:: `Device Identifiers`_
+.. |README.DOS| replace:: `BRLTTY on DOS`_
+.. |README.Introduction| replace:: `Introduction to BRLTTY`_
+.. |README.KeyTables| replace:: `Key Tables`_
+.. |README.Linux| replace:: `BRLTTY on Linux`_
+.. |README.OpenBSD| replace:: `BRLTTY on OpenBSD`_
+.. |README.Polling| replace:: `Polling`_
+.. |README.PrivacyPolicy| replace:: `Privacy Policy`_
+.. |README.Profiles| replace:: `Profiles`_
+.. |README.Stow| replace:: `Using the Stow Build Manager`_
+.. |README.Systemd| replace:: `Using Systemd Service Management`_
+.. |README.TextTables| replace:: `Text Tables`_
+.. |README.Upstart| replace:: `Using Upstart Service Management`_
+.. |README.Windows| replace:: `BRLTTY on Windows`_
+.. |README.X11| replace:: `BRLTTY with X11`_
diff --git a/Documents/text-table.csv b/Documents/text-table.csv
new file mode 100644
index 0000000..8498ec2
--- /dev/null
+++ b/Documents/text-table.csv
@@ -0,0 +1,90 @@
+"name","description"
+"auto","locale-based autoselection"
+"ar","Arabic (generic)"
+"as","Assamese"
+"awa","Awadhi"
+"bg","Bulgarian"
+"bh","Bihari"
+"bn","Bengali"
+"bo","Tibetan"
+"bra","Braj"
+"brf","Braille Ready Format (for viewing .brf files within an editor or pager)"
+"cs","Czech"
+"cy","Welsh"
+"da","Danish"
+"da-1252","Danish (Svend Thougaard, 2002–11–18)"
+"da-lt","Danish (LogText)"
+"de","German"
+"dra","Dravidian"
+"el","Greek"
+"en","English"
+"en_CA","English (Canada)"
+"en_GB","English (United Kingdom)"
+"en_US","English (United States)"
+"en-nabcc","English (North American Braille Computer Code)"
+"eo","Esperanto"
+"es","Spanish"
+"et","Estonian"
+"fi","Finnish"
+"fr","French"
+"fr_CA","French (Canada)"
+"fr_FR","French (France)"
+"fr-2007","French (unified 2007)"
+"fr-cbifs","French (Code Braille Informatique Français Standard)"
+"fr-vs","French (VisioBraille)"
+"ga","Irish"
+"gd","Gaelic"
+"gon","Gondi"
+"gu","Gujarati"
+"he","Hebrew"
+"hi","Hindi"
+"hr","Croatian"
+"hu","Hungarian"
+"hy","Armenian"
+"is","Icelandic"
+"it","Italian"
+"kha","Khasi"
+"kn","Kannada"
+"kok","Konkani"
+"kru","Kurukh"
+"lt","Lituanian"
+"lv","Latvian"
+"mg","Malagasy"
+"mi","Maori"
+"ml","Malayalam"
+"mni","Manipuri"
+"mr","Marathi"
+"mt","Maltese"
+"mun","Munda"
+"mwr","Marwari"
+"ne","Nepali"
+"new","Newari"
+"nl","Dutch"
+"nl_BE","Dutch (Belgium)"
+"nl_NL","Dutch (Netherlands)"
+"no","Norwegian"
+"no-generic","Norwegian (with support for other languages)"
+"no-oup","Norwegian (Offentlig utvalg for punktskrift)"
+"nwc","Newari (old)"
+"or","Oriya"
+"pa","Panjabi"
+"pi","Pali"
+"pl","Polish"
+"pt","Portuguese"
+"ro","Romanian"
+"ru","Russian"
+"se","Sami (Northern)"
+"sa","Sanskrit"
+"sat","Santali"
+"sd","Sindhi"
+"sk","Slovak"
+"sl","Slovenian"
+"sv","Swedish"
+"sv-1989","Swedish (1989 standard)"
+"sv-1996","Swedish (1996 standard)"
+"sw","Swahili"
+"ta","Tamil"
+"te","Telugu"
+"tr","Turkish"
+"uk","Ukrainian"
+"vi","Vietnamese"
diff --git a/Documents/variable-directives.rst b/Documents/variable-directives.rst
new file mode 100644
index 0000000..97f3269
--- /dev/null
+++ b/Documents/variable-directives.rst
@@ -0,0 +1,126 @@
+The Assign Directive
+--------------------
+
+.. parsed-literal:: assign *variable* *value*
+
+Use this directive to create or update a variable associated with
+the current nesting level (see `The BeginVariables Directive`_)
+of the current include level (see `The Include Directive`_).
+The variable is visible to the current and to lower include levels,
+but not to higher include levels.
+
+*variable*
+   The name of the variable. If the variable doesn't already exist at the
+   current include level then it is created.
+
+*value*
+   The value that is to be assigned to the variable. If it's not supplied then
+   a zero-length (null) value is assigned.
+
+Examples::
+
+   assign nullValue
+   assign shortValue word
+   assign longValue a\svalue\swith\sspaces
+   assign IndirectValue \{variableName}
+
+The AssignDefault Directive
+---------------------------
+
+.. parsed-literal:: assignDefault *variable* *value*
+
+Use this directive to assign a default value to a variable associated with
+the current nesting level (see `The BeginVariables Directive`_)
+of the current include level (see `The Include Directive`_).
+It's functionally equivalent to:
+
+.. parsed-literal:: ifNotVar *variable* assign *variable* *value*
+
+See `The Assign Directive`_ and `The IfNotVar Directive`_ for more details.
+
+*variable*
+   The name of the variable. If the variable doesn't already exist at the
+   current include level then it is created. If it does already exist then it
+   is **not** modified.
+
+*value*
+   The value that is to be assigned to the variable if it doesn't already
+   exist. If it's not supplied then a zero-length (null) value is assigned.
+
+Examples::
+
+   assignDefault format plain\stext
+
+The IfVar Directive
+-------------------
+
+.. parsed-literal:: ifVar *variable* *directive*
+
+Use this directive to only process one or more directives if a variable exists.
+
+*variable*
+   The name of the variable whose existence is to be tested.
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifVar var1 ifVar var2 assign concatenation \{var1}\{var2}
+
+The IfNotVar Directive
+----------------------
+
+.. parsed-literal:: ifNotVar *variable* *directive*
+
+Use this directive to only process one or more directives if a variable doesn't
+exist.
+
+*variable*
+   The name of the variable whose existence is to be tested.
+
+*directive*
+   |directive operand|
+
+Examples::
+
+   ifNotVar var1 assign var1 default\svalue
+
+The BeginVariables Directive
+----------------------------
+
+.. parsed-literal:: beginVariables
+
+Use this directive to open a new variable nesting level.
+`The Assign Directive`_) will define variables at this new nesting level,
+and will hide variables with the same names in any previous nesting level.
+These variables will remain defined until `The EndVariables Directive`_
+that is at the same variable nesting level.
+
+Examples::
+
+   assign x 1
+   # \{x} evaluates to 1
+   beginVariables
+   # \{x} still evaluates to 1
+   assign x 2
+   # \{x} now evaluates to 2
+   endVariables
+   # \{x} evaluates to 1 again
+
+The EndVariables Directive
+--------------------------
+
+.. parsed-literal:: endVariables
+
+Use this directive to close the current variable nesting level.
+See `The BeginVariables Directive`_ for details.
+
+The ListVariables Directive
+---------------------------
+
+.. parsed-literal:: listVariables
+
+Use this directive to list all of the currently defiined variables.
+It can be helpful when debugging.
+
diff --git a/Documents/xbrlapi.1.in b/Documents/xbrlapi.1.in
new file mode 100644
index 0000000..1a08e80
--- /dev/null
+++ b/Documents/xbrlapi.1.in
@@ -0,0 +1,62 @@
+.TH "XBRLAPI" "1" "July 2023" "@api_name@ @api_release@" "@api_name@ User's Manual"
+.SH NAME
+xbrlapi \- X11 BrlAPI helper for Linux/Unix
+.SH SYNOPSIS
+\fBxbrlapi\fR [\fIoption\fR ...]
+.SH DESCRIPTION
+.B xbrlapi
+connects to a
+.B BrlAPI
+server in order to provide it with the ID of the
+.B X11
+window which currently has focus.
+It also simulates
+.B X
+keysyms from braille key presses (else brltty would simulate them using a qwerty
+keyboard). By default, it puts itself in the background, unless option
+.B -n
+is given.
+.SH OPTIONS
+Options are processed sequentially from left to right.
+If an option is specified more than once,
+or in case of a conflict,
+the rightmost specification takes precedence.
+.PP
+The following options are supported:
+.TP
+\fB-b\fR [\fIhost\fR][:\fIport\fR] (\fB--brlapi=\fR)
+The
+.B BrlAPI
+server to connect to.
+.TP
+\fB-a\fR \fIstring\fR (\fB--auth=\fR)
+The
+.B BrlAPI
+authorization/authentication string.
+.TP
+\fB-d\fR \fIdisplay\fR (\fB--display=\fR)
+The
+.B X
+display to connect to.
+If not specified, the content of the
+.B DISPLAY
+environment variable is used.
+.TP
+\fB-n\fR (\fB--no-daemon\fR)
+Keep xbrlapi in the foreground.
+.TP
+\fB-q\fR (\fB--quiet\fR)
+Do not write any text to the braille device.
+.TP
+\fB-h\fR (\fB--help\fR)
+Print a command line usage summary and then exit.
+.TP
+\fB-H\fR (\fB--full-help\fR)
+Print a thorough command line usage summary and then exit.
+.SH "SEE ALSO"
+For more details on the purpose of
+.BR xbrlapi ,
+see
+.BR BrlAPI 's
+on-line manual at
+.RB "[" "http://brltty.app/doc/Manual-BrlAPI/English/BrlAPI-3.html" "]."
diff --git a/Drivers/Braille/Albatross/Makefile.in b/Drivers/Braille/Albatross/Makefile.in
new file mode 100644
index 0000000..e6b1939
--- /dev/null
+++ b/Drivers/Braille/Albatross/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = at
+DRIVER_NAME = Albatross
+DRIVER_USAGE = 46/80
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Albatross/README b/Drivers/Braille/Albatross/README
new file mode 100644
index 0000000..9ab9b1a
--- /dev/null
+++ b/Drivers/Braille/Albatross/README
@@ -0,0 +1,30 @@
+This directory contains the BRLTTY driver for the Albatross
+[http://www.tivomatic.fi/html/albatross.html] braille display from Tivomatic
+[http://www.tivomatic.fi/]. It has been implemented, and is being maintained,
+by Dave Mielke <dave@mielke.cc> and Sami Haahtinen <ressu@ressukka.net>.
+
+
+The keys on the front of the display are named as depicted below within the
+documentation for this driver. See the help file for what they do.
+
+   +---+ +---+ +------+ +-----------+      +-----------+ +------+ +---+ +---+
+   |Top| |Bak| |  Up  | |           |      |           | |  Up  | |Bak| |Top|
+   +---+ +---+ +------+ |           |      |           | +------+ +---+ +---+
+                        |   Left    |      |   Right   |
+   +---+ +---+ +------+ |           |      |           | +------+ +---+ +---+
+   |Bot| |Csr| | Down | |           |      |           | | Down | |Csr| |Bot|
+   +---+ +---+ +------+ +-----------+      +-----------+ +------+ +---+ +---+
+
+
+The keys on the top of the display are named as depicted below within the
+documentation for this driver. See the help file for what they do.
+
+   +---+       +-------+       +---+        +---+       +-------+       +---+
+   |Hlp| +---+ | Paste | +---+ |Prf|        |PrU| +---+ | DifUp | +---+ |PgU|
+   +---+ |mwp| +-------+ |gwp| +---+        +---+ |iup| +-------+ |idn| +---+
+         |RF1|           |RF2|                    |RF3|           |RF4|
+   +---+ |ctn| +-------+ |cta| +---+        +---+ |ctl| +-------+ |ctr| +---+
+   |Lrn| +---+ | Track | +---+ |Stt|        |PrD| +---+ | DifDn | +---+ |PgD|
+   +---+       +-------+       +---+        +---+       +-------+       +---+
+
+
diff --git a/Drivers/Braille/Albatross/braille.c b/Drivers/Braille/Albatross/braille.c
new file mode 100644
index 0000000..2ac7ee7
--- /dev/null
+++ b/Drivers/Braille/Albatross/braille.c
@@ -0,0 +1,627 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Albatross/braille.c - Braille display library
+ * Tivomatic's Albatross series
+ * Author: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+#include "timing.h"
+#include "async_wait.h"
+
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-at.h"
+
+BEGIN_KEY_NAME_TABLE(all)
+  /* front left keys */
+  KEY_NAME_ENTRY(AT_KEY_Home1, "Home1"),
+  KEY_NAME_ENTRY(AT_KEY_End1, "End1"),
+  KEY_NAME_ENTRY(AT_KEY_ExtraCursor1, "ExtraCursor1"),
+  KEY_NAME_ENTRY(AT_KEY_Cursor1, "Cursor1"),
+  KEY_NAME_ENTRY(AT_KEY_Up1, "Up1"),
+  KEY_NAME_ENTRY(AT_KEY_Down1, "Down1"),
+  KEY_NAME_ENTRY(AT_KEY_Left, "Left"),
+
+  /* front right keys */
+  KEY_NAME_ENTRY(AT_KEY_Home2, "Home2"),
+  KEY_NAME_ENTRY(AT_KEY_End2, "End2"),
+  KEY_NAME_ENTRY(AT_KEY_ExtraCursor2, "ExtraCursor2"),
+  KEY_NAME_ENTRY(AT_KEY_Cursor2, "Cursor2"),
+  KEY_NAME_ENTRY(AT_KEY_Up2, "Up2"),
+  KEY_NAME_ENTRY(AT_KEY_Down2, "Down2"),
+  KEY_NAME_ENTRY(AT_KEY_Right, "Right"),
+
+  /* front middle keys */
+  KEY_NAME_ENTRY(AT_KEY_Up3, "Up3"),
+  KEY_NAME_ENTRY(AT_KEY_Down3, "Down3"),
+
+  /* top left keys */
+  KEY_NAME_ENTRY(AT_KEY_F1, "F1"),
+  KEY_NAME_ENTRY(AT_KEY_F2, "F2"),
+  KEY_NAME_ENTRY(AT_KEY_F3, "F3"),
+  KEY_NAME_ENTRY(AT_KEY_F4, "F4"),
+  KEY_NAME_ENTRY(AT_KEY_F5, "F5"),
+  KEY_NAME_ENTRY(AT_KEY_F6, "F6"),
+  KEY_NAME_ENTRY(AT_KEY_F7, "F7"),
+  KEY_NAME_ENTRY(AT_KEY_F8, "F8"),
+
+  /* top right keys */
+  KEY_NAME_ENTRY(AT_KEY_F9, "F9"),
+  KEY_NAME_ENTRY(AT_KEY_F10, "F10"),
+  KEY_NAME_ENTRY(AT_KEY_F11, "F11"),
+  KEY_NAME_ENTRY(AT_KEY_F12, "F12"),
+  KEY_NAME_ENTRY(AT_KEY_F13, "F13"),
+  KEY_NAME_ENTRY(AT_KEY_F14, "F14"),
+  KEY_NAME_ENTRY(AT_KEY_F15, "F15"),
+  KEY_NAME_ENTRY(AT_KEY_F16, "F16"),
+
+  /* attribute keys */
+  KEY_NAME_ENTRY(AT_KEY_Attribute1, "Attribute1"),
+  KEY_NAME_ENTRY(AT_KEY_Attribute2, "Attribute2"),
+  KEY_NAME_ENTRY(AT_KEY_Attribute3, "Attribute3"),
+  KEY_NAME_ENTRY(AT_KEY_Attribute4, "Attribute4"),
+
+  /* wheels */
+  KEY_NAME_ENTRY(AT_KEY_LeftWheelRight, "LeftWheelRight"),
+  KEY_NAME_ENTRY(AT_KEY_LeftWheelLeft, "LeftWheelLeft"),
+  KEY_NAME_ENTRY(AT_KEY_LeftWheelUp, "LeftWheelUp"),
+  KEY_NAME_ENTRY(AT_KEY_LeftWheelDown, "LeftWheelDown"),
+  KEY_NAME_ENTRY(AT_KEY_RightWheelRight, "RightWheelRight"),
+  KEY_NAME_ENTRY(AT_KEY_RightWheelLeft, "RightWheelLeft"),
+  KEY_NAME_ENTRY(AT_KEY_RightWheelUp, "RightWheelUp"),
+  KEY_NAME_ENTRY(AT_KEY_RightWheelDown, "RightWheelDown"),
+
+  /* routing keys */
+  KEY_GROUP_ENTRY(AT_GRP_RoutingKeys1, "RoutingKey1"),
+  KEY_GROUP_ENTRY(AT_GRP_RoutingKeys2, "RoutingKey2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(all),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  int (*openPort) (const char *device);
+  int (*configurePort) (unsigned int baud);
+  void (*closePort) (void);
+  int (*awaitInput) (int milliseconds);
+  int (*readBytes) (unsigned char *buffer, size_t size, int wait);
+  int (*writeBytes) (const unsigned char *buffer, size_t size);
+} InputOutputOperations;
+
+static const InputOutputOperations *io;
+static unsigned int charactersPerSecond;
+
+#include "io_serial.h"
+
+static SerialDevice *serialDevice = NULL;
+
+static int
+openSerialPort (const char *device) {
+  if ((serialDevice = serialOpenDevice(device))) return 1;
+  return 0;
+}
+
+static int
+configureSerialPort (unsigned int baud) {
+  return serialRestartDevice(serialDevice, baud);
+}
+
+static void
+closeSerialPort (void) {
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+awaitSerialInput (int milliseconds) {
+  return serialAwaitInput(serialDevice, milliseconds);
+}
+
+static int
+readSerialBytes (unsigned char *buffer, size_t size, int wait) {
+  const int timeout = 100;
+  return serialReadData(serialDevice, buffer, size,
+                        (wait? timeout: 0), timeout);
+}
+
+static int
+writeSerialBytes (const unsigned char *buffer, size_t size) {
+  return serialWriteData(serialDevice, buffer, size);
+}
+
+static const InputOutputOperations serialOperations = {
+  openSerialPort, configureSerialPort, closeSerialPort,
+  awaitSerialInput, readSerialBytes, writeSerialBytes
+};
+
+#include "io_usb.h"
+
+static UsbChannel *usbChannel = NULL;
+
+static int
+openUsbPort (const char *device) {
+  BEGIN_USB_STRING_LIST(usbManufacturers_0403_6001)
+    "Tivomatic Oy",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0403, .product=0X6001,
+      .manufacturers = usbManufacturers_0403_6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  if ((usbChannel = usbOpenChannel(usbChannelDefinitions, (void *)device))) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+configureUsbPort (unsigned int baud) {
+  const SerialParameters parameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = baud
+  };
+
+  return usbSetSerialParameters(usbChannel->device, &parameters);
+}
+
+static void
+closeUsbPort (void) {
+  if (usbChannel) {
+    usbCloseChannel(usbChannel);
+    usbChannel = NULL;
+  }
+}
+
+static int
+awaitUsbInput (int milliseconds) {
+  return usbAwaitInput(usbChannel->device, usbChannel->definition->inputEndpoint, milliseconds);
+}
+
+static int
+readUsbBytes (unsigned char *buffer, size_t size, int wait) {
+  const int timeout = 100;
+  int count = usbReadData(usbChannel->device,
+                          usbChannel->definition->inputEndpoint,
+                          buffer, size,
+                          (wait? timeout: 0), timeout);
+
+  if (count != -1) return count;
+  if (errno == EAGAIN) return 0;
+  return -1;
+}
+
+static int
+writeUsbBytes (const unsigned char *buffer, size_t size) {
+  return usbWriteEndpoint(usbChannel->device, usbChannel->definition->outputEndpoint, buffer, size, 1000);
+}
+
+static const InputOutputOperations usbOperations = {
+  openUsbPort, configureUsbPort, closeUsbPort,
+  awaitUsbInput, readUsbBytes, writeUsbBytes
+};
+
+static TranslationTable inputMap;
+static const unsigned char topLeftKeys[]  = {
+  AT_KEY_F1, AT_KEY_F2, AT_KEY_F3, AT_KEY_F4,
+  AT_KEY_F5, AT_KEY_F6, AT_KEY_F7, AT_KEY_F8
+};
+static const unsigned char topRightKeys[] = {
+  AT_KEY_F9, AT_KEY_F10, AT_KEY_F11, AT_KEY_F12,
+  AT_KEY_F13, AT_KEY_F14, AT_KEY_F15, AT_KEY_F16
+};
+
+static unsigned char controlKey;
+#define NO_CONTROL_KEY 0XFF
+
+static unsigned char displayContent[80];
+static int displaySize;
+static int windowWidth;
+static int windowStart;
+static int statusCount;
+static int statusStart;
+
+static int
+readByte (unsigned char *byte) {
+  int result = io->readBytes(byte, 1, 0);
+
+  if (result > 0) {
+    logInputPacket(byte, result);
+  } else if (result == -1) {
+    logSystemError("Albatross read");
+  } else if (!result) {
+    errno = EAGAIN;
+  }
+
+  return result == 1;
+}
+
+static void
+discardInput (void) {
+  unsigned char byte;
+  while (readByte(&byte));
+}
+
+static int
+awaitByte (unsigned char *byte) {
+  if (readByte(byte)) return 1;
+
+  if (io->awaitInput(1000))
+    if (readByte(byte))
+      return 1;
+
+  return 0;
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, int count) {
+  brl->writeDelay += (count * 1000 / charactersPerSecond) + 1;
+  logOutputPacket(bytes, count);
+  if (io->writeBytes(bytes, count) != -1) return 1;
+  logSystemError("Albatross write");
+  return 0;
+}
+
+static int
+acknowledgeDisplay (BrailleDisplay *brl) {
+  unsigned char description;
+  if (!awaitByte(&description)) return 0;
+  if (description == 0XFF) return 0;
+
+  {
+    unsigned char byte;
+
+    if (!awaitByte(&byte)) return 0;
+    if (byte != 0XFF) return 0;
+
+    if (!awaitByte(&byte)) return 0;
+    if (byte != description) return 0;
+  }
+
+  {
+    static const unsigned char acknowledgement[] = {0XFE, 0XFF, 0XFE, 0XFF};
+    if (!writeBytes(brl, acknowledgement, sizeof(acknowledgement))) return 0;
+
+    discardInput();
+    asyncWait(100);
+    discardInput();
+  }
+  logMessage(LOG_DEBUG, "Albatross description byte: %02X", description);
+
+  windowStart = statusStart = 0;
+  displaySize = (description & 0X80)? 80: 46;
+  if ((statusCount = description & 0X0F)) {
+    windowWidth = displaySize - statusCount - 1;
+    if (description & 0X20) {
+      statusStart = windowWidth + 1;
+      displayContent[statusStart - 1] = 0;
+    } else {
+      windowStart = statusCount + 1;
+      displayContent[windowStart - 1] = 0;
+    }
+  } else {
+    windowWidth = displaySize;
+  }
+
+  {
+    int i;
+    for (i=0; i<sizeof(inputMap); ++i) inputMap[i] = i;
+
+    /* top keypad remapping */
+    {
+      const unsigned char *left = NULL;
+      const unsigned char *right = NULL;
+
+      switch (description & 0X50) {
+        case 0X00: /* left right */
+          break;
+
+        case 0X10: /* right right */
+          left = topRightKeys;
+          break;
+
+        case 0X50: /* left left */
+          right = topLeftKeys;
+          break;
+
+        case 0X40: /* right left */
+          left = topRightKeys;
+          right = topLeftKeys;
+          break;
+      }
+
+      if (left)
+        for (i=0; i<8; ++i)
+          inputMap[topLeftKeys[i]] = left[i];
+
+      if (right)
+        for (i=0; i<8; ++i)
+          inputMap[topRightKeys[i]] = right[i];
+    }
+  }
+
+  logMessage(LOG_INFO, "Albatross: %d cells (%d text, %d%s status), top keypads [%s,%s].",
+             displaySize, windowWidth, statusCount,
+             !statusCount? "": statusStart? " right": " left",
+             (inputMap[topLeftKeys[0]] == topLeftKeys[0])? "left": "right",
+             (inputMap[topRightKeys[0]] == topRightKeys[0])? "right": "left");
+  return 1;
+}
+
+static int
+clearDisplay (BrailleDisplay *brl) {
+  unsigned char bytes[] = {0XFA};
+  int cleared = writeBytes(brl, bytes, sizeof(bytes));
+  if (cleared) memset(displayContent, 0, displaySize);
+  return cleared;
+}
+
+static int
+updateDisplay (BrailleDisplay *brl, const unsigned char *cells, unsigned int count, unsigned int start) {
+  static time_t lastUpdate = 0;
+  unsigned char bytes[count * 2 + 2];
+  unsigned char *byte = bytes;
+  unsigned int index;
+  *byte++ = 0XFB;
+  for (index=0; index<count; ++index) {
+    unsigned char cell;
+    if (!cells) {
+      cell = displayContent[start+index];
+    } else if ((cell = translateOutputCell(cells[index])) != displayContent[start+index]) {
+      displayContent[start+index] = cell;
+    } else {
+      continue;
+    }
+    *byte++ = start + index + 1;
+    *byte++ = cell;
+  }
+
+  if (((byte - bytes) > 1) || (time(NULL) != lastUpdate)) {
+    *byte++ = 0XFC;
+    if (!writeBytes(brl, bytes, byte-bytes)) return 0;
+    lastUpdate = time(NULL);
+  }
+  return 1;
+}
+
+static int
+updateWindow (BrailleDisplay *brl, const unsigned char *cells) {
+  return updateDisplay(brl, cells, windowWidth, windowStart);
+}
+
+static int
+updateStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  return updateDisplay(brl, cells, statusCount, statusStart);
+}
+
+static int
+refreshDisplay (BrailleDisplay *brl) {
+  return updateDisplay(brl, NULL, displaySize, 0);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (isSerialDeviceIdentifier(&device)) {
+    io = &serialOperations;
+  } else if (isUsbDeviceIdentifier(&device)) {
+    io = &usbOperations;
+  } else {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if (io->openPort(device)) {
+    unsigned int baudTable[] = {19200, 9600, 0};
+    const unsigned int *baud = baudTable;
+
+    while (io->configurePort(*baud)) {
+      TimePeriod period;
+      int count = 0;
+      unsigned char byte;
+
+      startTimePeriod(&period, 1000);
+      charactersPerSecond = *baud / 10;
+      controlKey = NO_CONTROL_KEY;
+
+      logMessage(LOG_DEBUG, "trying Albatross at %u baud", *baud);
+
+      while (awaitByte(&byte)) {
+        if (byte == 0XFF) {
+          if (!acknowledgeDisplay(brl)) break;
+
+          brl->textColumns = windowWidth;
+          brl->textRows = 1;
+
+          setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+          MAKE_OUTPUT_TABLE(0X80, 0X40, 0X20, 0X10, 0X08, 0X04, 0X02, 0X01);
+          clearDisplay(brl);
+
+          return 1;
+        }
+
+        if (++count == 100) break;
+        if (afterTimePeriod(&period, NULL)) break;
+      }
+
+      if (!*++baud) break;
+    }
+
+    io->closePort();
+  }
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  io->closePort();
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  updateWindow(brl, brl->buffer);
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+  updateStatus(brl, status);
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char byte;
+
+  while (readByte(&byte)) {
+    if (byte == 0XFF) {
+      if (acknowledgeDisplay(brl)) {
+        refreshDisplay(brl);
+        brl->textColumns = windowWidth;
+        brl->textRows = 1;
+        brl->resizeRequired = 1;
+      }
+      continue;
+    }
+
+    byte = inputMap[byte];
+    {
+      KeyGroup group;
+      KeyNumber number;
+
+      if ((byte >= 2) && (byte <= 41)) {
+        group = AT_GRP_RoutingKeys1;
+        number = byte - 2;
+      } else if ((byte >= 111) && (byte <= 150)) {
+        group = AT_GRP_RoutingKeys1;
+        number = byte - 71;
+      } else if ((byte >= 43) && (byte <= 82)) {
+        group = AT_GRP_RoutingKeys2;
+        number = byte - 43;
+      } else if ((byte >= 152) && (byte <= 191)) {
+        group = AT_GRP_RoutingKeys2;
+        number = byte - 112;
+      } else {
+        goto notRouting;
+      }
+
+      if ((number >= windowStart) &&
+          (number < (windowStart + windowWidth))) {
+        number -= windowStart;
+        enqueueKey(brl, group, number);
+        continue;
+      }
+    }
+  notRouting:
+
+    switch (byte) {
+      default:
+        break;
+
+      case 0XFB:
+        refreshDisplay(brl);
+        continue;
+
+      case AT_KEY_Attribute1:
+      case AT_KEY_Attribute2:
+      case AT_KEY_Attribute3:
+      case AT_KEY_Attribute4:
+      case AT_KEY_F1:
+      case AT_KEY_F2:
+      case AT_KEY_F7:
+      case AT_KEY_F8:
+      case AT_KEY_F9:
+      case AT_KEY_F10:
+      case AT_KEY_F15:
+      case AT_KEY_F16:
+      case AT_KEY_Home1:
+      case AT_KEY_Home2:
+      case AT_KEY_End1:
+      case AT_KEY_End2:
+      case AT_KEY_ExtraCursor1:
+      case AT_KEY_ExtraCursor2:
+      case AT_KEY_Cursor1:
+      case AT_KEY_Cursor2:
+        if (byte == controlKey) {
+          controlKey = NO_CONTROL_KEY;
+          enqueueKeyEvent(brl, AT_GRP_NavigationKeys, byte, 0);
+          continue;
+        }
+
+        if (controlKey == NO_CONTROL_KEY) {
+          controlKey = byte;
+          enqueueKeyEvent(brl, AT_GRP_NavigationKeys, byte, 1);
+          continue;
+        }
+
+      case AT_KEY_Up1:
+      case AT_KEY_Down1:
+      case AT_KEY_Left:
+      case AT_KEY_Up2:
+      case AT_KEY_Down2:
+      case AT_KEY_Right:
+      case AT_KEY_Up3:
+      case AT_KEY_Down3:
+      case AT_KEY_F3:
+      case AT_KEY_F4:
+      case AT_KEY_F5:
+      case AT_KEY_F6:
+      case AT_KEY_F11:
+      case AT_KEY_F12:
+      case AT_KEY_F13:
+      case AT_KEY_F14:
+      case AT_KEY_LeftWheelRight:
+      case AT_KEY_LeftWheelLeft:
+      case AT_KEY_LeftWheelUp:
+      case AT_KEY_LeftWheelDown:
+      case AT_KEY_RightWheelRight:
+      case AT_KEY_RightWheelLeft:
+      case AT_KEY_RightWheelUp:
+      case AT_KEY_RightWheelDown:
+        enqueueKey(brl, AT_GRP_NavigationKeys, byte);
+        continue;
+    }
+
+    logUnexpectedPacket(&byte, 1);
+  }
+
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+  return EOF;
+}
diff --git a/Drivers/Braille/Albatross/brldefs-at.h b/Drivers/Braille/Albatross/brldefs-at.h
new file mode 100644
index 0000000..0ce2662
--- /dev/null
+++ b/Drivers/Braille/Albatross/brldefs-at.h
@@ -0,0 +1,88 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_AT_BRLDEFS
+#define BRLTTY_INCLUDED_AT_BRLDEFS
+
+typedef enum {
+  /* front left keys */
+  AT_KEY_Home1        =  91, /* front left first upper */
+  AT_KEY_End1         =  92, /* front left first lower */
+  AT_KEY_ExtraCursor1 =  93, /* front left second upper */
+  AT_KEY_Cursor1      =  94, /* front left second lower */
+  AT_KEY_Up1          =  95, /* front left third upper */
+  AT_KEY_Down1        =  96, /* front left third lower */
+  AT_KEY_Left         =  97, /* front left fourth */
+
+  /* front right keys */
+  AT_KEY_Home2        = 201, /* front right first upper */
+  AT_KEY_End2         = 202, /* front right first lower */
+  AT_KEY_ExtraCursor2 = 203, /* front right second upper */
+  AT_KEY_Cursor2      = 204, /* front right second lower */
+  AT_KEY_Up2          = 205, /* front right third upper */
+  AT_KEY_Down2        = 206, /* front right third lower */
+  AT_KEY_Right        = 207, /* front right fourth */
+
+  /* front middle keys */
+  AT_KEY_Up3          =  98, /* front middle upper */
+  AT_KEY_Down3        = 208, /* front middle lower */
+
+  /* top left keys */
+  AT_KEY_F1           =  83, /* top left first front */
+  AT_KEY_F2           =  84, /* top left first rear */
+  AT_KEY_F3           =  85, /* top left third rear */
+  AT_KEY_F4           =  86, /* top left third front */
+  AT_KEY_F5           =  87, /* top left second */
+  AT_KEY_F6           =  88, /* top left fourth */
+  AT_KEY_F7           =  89, /* top left fifth rear */
+  AT_KEY_F8           =  90, /* top left fifth front */
+
+  /* top right keys */
+  AT_KEY_F9           = 193, /* top right first front */
+  AT_KEY_F10          = 194, /* top right first rear */
+  AT_KEY_F11          = 195, /* top right third rear */
+  AT_KEY_F12          = 196, /* top right third front */
+  AT_KEY_F13          = 197, /* top right second */
+  AT_KEY_F14          = 198, /* top right fourth */
+  AT_KEY_F15          = 199, /* top right fifth rear */
+  AT_KEY_F16          = 200, /* top right fifth front */
+
+  /* attribute keys */
+  AT_KEY_Attribute1   =   1, /* attribute left front */
+  AT_KEY_Attribute2   =  42, /* attribute left rear */
+  AT_KEY_Attribute3   = 151, /* attribute right front */
+  AT_KEY_Attribute4   = 192, /* attribute right rear */
+
+  /* wheels */
+  AT_KEY_LeftWheelRight  = 103, /* wheel left horizontal right */
+  AT_KEY_LeftWheelLeft   = 104, /* wheel left horizontal left */
+  AT_KEY_LeftWheelUp     = 105, /* wheel left vertical up */
+  AT_KEY_LeftWheelDown   = 106, /* wheel left vertical down */
+  AT_KEY_RightWheelRight = 213, /* wheel right horizontal right */
+  AT_KEY_RightWheelLeft  = 214, /* wheel right horizontal left */
+  AT_KEY_RightWheelUp    = 215, /* wheel right vertical up */
+  AT_KEY_RightWheelDown  = 216, /* wheel right vertical down */
+} AT_NavigationKey;
+
+typedef enum {
+  AT_GRP_NavigationKeys = 0,
+  AT_GRP_RoutingKeys1,
+  AT_GRP_RoutingKeys2
+} AT_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_AT_BRLDEFS */ 
diff --git a/Drivers/Braille/Alva/Makefile.in b/Drivers/Braille/Alva/Makefile.in
new file mode 100644
index 0000000..e2de4f7
--- /dev/null
+++ b/Drivers/Braille/Alva/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = al
+DRIVER_NAME = Alva
+DRIVER_USAGE = ABT(3nn), Delphi(4nn), Satellite(5nn), Braille System 40, Braille Controller 640/680, Easy Link 12
+DRIVER_VERSION = 2.2
+DRIVER_DEVELOPERS = Nicolas Pitre <nico@fluxnic.net>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Alva/README b/Drivers/Braille/Alva/README
new file mode 100644
index 0000000..f461a90
--- /dev/null
+++ b/Drivers/Braille/Alva/README
@@ -0,0 +1,155 @@
+README for the Alva Braille Driver
+
+
+Supported Hardware
+==================
+
+This driver is intended to work with any Alva braille display from the ABT3 
+(3nn), Delphi (4nn), Satellite (5nn), and Braille Controller (6nn) series. It 
+recognizes the 320, 340, 34d, 380, 382, 38D, 420, 440, 44d, 480, 544, 544t, 
+570p, 584p, 620, 632, 640 and 680; other models can be easily added. The 
+serial, USB, and Bluetooth protocols are supported; the parallel port is not. 
+
+If you want any information about Alva products, you should look at this
+web site: http://www.optelec.com
+
+
+Key Bindings
+============
+
+All the key definitions are listed in the key bindings configuration files 
+(usually found in /etc/brltty/brl-al-*). Each file corresponds to an Alva 
+model. For more information on the functions listed, see the BRLTTY manual. 
+
+For a quick start, enter the help screen by:
+
+   3nn and 4nn models: press the PROG key.
+   5nn models: press the left outer key of the right satellite keypad.
+   6nn models: press both rear ETouch keys together.
+
+Enter interactive command learn mode by:
+
+   5nn models: press the right outer key of the right satellite keypad.
+   6nn models: press the  left rear and right front ETouch keys together.
+
+Some keys found on larger display models might not be bound to any functions
+at the moment. However, it is pretty easy to add new bindings. 
+
+
+Special Considerations for the BC6xx models
+===========================================
+
+The BC6xx models have an internal menu with many configuration options.
+Unfortunately the default value for a few options are suboptimal with BRLTTY
+and you might want to consider changing those settings for a better user
+experience.  To enter the local menu press SmartPad keys 2, 3 and Down
+simultaneously.  The SmartPad direction keys are then used to select a
+setting, the center key to allow changing a setting.
+
+Repeat Keys: This determines if the braille terminal should send repeated
+key events when a key is held down. BRLTTY can already manage key repetitions
+by itself in a better way and this setting prevents BRLTTY's support from
+working properly. Therefore you should change this setting to "off".
+
+2nd Cursor Routing row: This setting allows for simulating a second row of
+cursor routing keys as found on some earlier Alva models by holding down a
+cursor routing key during a short period. This causes problems with BRLTTY's
+multi-key bindings if keys are not pressed in a particular order. To mitigate
+the problem slightly, BRLTTY considers the first and second routing key rows
+as being the same on the BC6xx, those models actually having only one such
+row of keys anyway.  It is best to use BRLTTY's ability to bind functions
+on held-down keys instead if one wishes to preserve the additional
+possibilities this setting offers. Therefore you should change this setting
+to "off".
+
+
+Port Specification
+==================
+
+This driver supports serial, USB, and Bluetooth communication only. Some 
+parallel port communication for ABT models was previously supported, but
+since that support was based on a non-GPL compatible and binary only
+library, it has been removed. When/if someone can help with providing us
+with the proper protocol information, we'll be pleased to write an open
+source parallel port driver in conformance with BRLTTY's license.
+
+Brltty's default port is usb:. If you need to specify something else please 
+follow the suggestions below.
+
+There are a number of ways to specify the port on the PC to which the display 
+is connected. Here's a summary, although you should check BRLTTY's manual for 
+all of the details.
+
+The default protocol is USB. If you'd like your brltty executable to have its 
+own unique default port then use the --with-braille-device= option of the 
+configure script in BRLTTY's top-level directory at build-time. If you'd like 
+your system to have its own unique default port regardless of which brltty 
+executable is being used then use the braille-device directive of BRLTTY's 
+configuration file (usually "/etc/brltty.conf"). If you'd like to specify a 
+specific port when invoking brltty then use the -d (or --braille-device=) 
+command line option.
+
+For a serial port, specify "serial:/path/to/device". The "serial:" qualifier is 
+optional (for backward compatibility). If a relative path is given then it's 
+anchored at "/dev" (the usual place that devices are found on a Unix-based 
+system). The following device specifications all refer to the primary serial 
+port on Linux:
+
+   serial:/dev/ttyS0
+   serial:ttyS0
+   /dev/ttyS0
+   ttyS0
+
+For a USB port, specify "usb:". BRLTTY will search for the first USB device 
+which matches the braille display driver being used. If this is inadequate, 
+e.g. if you have more than one USB braille display which require the same 
+driver, then you can refine the device specification by appending the serial 
+number of the display to it, e.g. "usb:12345".
+
+
+Serial Port Communication
+=========================
+
+By default, the ABT communicates at 9600 BPS. Although higher baud rates 
+can be used, the communication becomes unreliable creating occasional errors
+in the displayed braille. Therefore the baud rate in the local menu of your
+ABT should remain set to 9600.
+
+With serial communication, the ABT serial port must be set to alva-mode (the 
+default); see the description of 'Local Mode' in the ABT manual. The ABT serial 
+port has the same connections as the standard 9-pin serial port of an IBM PC. 
+Therefore, a cable connecting the ABT with a PC must have the following wires:
+
+9 pin (ABT)        9 pin (PC)   or    25 pin (PC)
+
+1                  1                  8 (CD)
+2                  3                  2 (TD)
+3                  2                  3 (RD)
+4                  6                  6 (DSR)
+5                  5                  7 (GND)
+6                  4                  20 (DTR)
+7                  8                  5 (CTS)
+8                  7                  4 (RTS)
+9                  9                  22 (RI)
+
+This kind of cable is called a "null modem" (or "cross over") cable. All 
+connectors are female D connectors.
+
+NOTE: Problems using the serial port may occur in some circumstances if
+the parallel port is also connected. If BRLTTY seems to work but exhibits 
+strange behaviour, then trying disconnecting the parallel port cable
+may help.
+
+
+ABT3xx Firmware Version 
+=======================
+
+BRLTTY no longer supports ABT3xx firmware versions older than 010495.
+To get the firmware version, simply power up your ABT while not connected
+to a computer. The last digits that will appear on the braille display
+correspond to the firmware version. If the firmware is too old then it
+needs to be upgraded to at least version 010495.
+
+
+Nicolas Pitre <nico@fluxnic.net>
+January 14, 2014
diff --git a/Drivers/Braille/Alva/braille.c b/Drivers/Braille/Alva/braille.c
new file mode 100644
index 0000000..d7fb457
--- /dev/null
+++ b/Drivers/Braille/Alva/braille.c
@@ -0,0 +1,2032 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Alva/braille.cc - Braille display library for Alva braille displays
+ * Copyright (C) 1995-2002 by Nicolas Pitre <nico@fluxnic.net>
+ * See the GNU Lesser General Public License for details in the LICENSE-LGPL file
+ *
+ */
+
+/* Changes:
+ *    january 2004:
+ *              - Added USB support.
+ *              - Improved key bindings for Satellite models.
+ *              - Moved autorepeat (typematic) support to the core.
+ *    september 2002:
+ *		- This pesky binary only parallel port library is just
+ *		  causing trouble (not compatible with new compilers, etc).
+ *		  It is also unclear if distribution of such closed source
+ *		  library is allowed within a GPL'ed program archive.
+ *		  Let's just nuke it until we can write an open source one.
+ *		- Converted this file back to pure C source.
+ *    may 21, 1999:
+ *		- Added Alva Delphi 80 support.  Thanks to ???
+*		  <cstrobel@crosslink.net>.
+ *    mar 14, 1999:
+ *		- Added LogPrint's (which is a good thing...)
+ *		- Ugly ugly hack for parallel port support:  seems there
+ *		  is a bug in the parallel port library so that the display
+ *		  completely hang after an arbitrary period of time.
+ *		  J. Lemmens didn't respond to my query yet... and since
+ *		  the F***ing library isn't Open Source, I can't fix it.
+ *    feb 05, 1999:
+ *		- Added Alva Delphi support  (thanks to Terry Barnaby 
+ *		  <terry@beam.demon.co.uk>).
+ *		- Renamed Alva_ABT3 to Alva.
+ *		- Some improvements to the autodetection stuff.
+ *    dec 06, 1998:
+ *		- added parallel port communication support using
+ *		  J. lemmens <jlemmens@inter.nl.net> 's library.
+ *		  This required brl.o to be sourced with C++ for the parallel 
+ *		  stuff to link.  Now brl.o is a partial link of brlmain.o 
+ *		  and the above library.
+ *    jun 21, 1998:
+ *		- replaced CMD_WINUP/DN with CMD_ATTRUP/DN wich seems
+ *		  to be a more useful binding.  Modified help files 
+ *		  acordingly.
+ *    apr 23, 1998:
+ *		- I finally had the chance to test with an ABT380... and
+ *		  corrected the ABT380 model ID for autodetection.
+ *		- Added a refresh delay to force redrawing the whole display
+ *		  in order to minimize garbage due to noise on the 
+ *		  serial line
+ *    oct 02, 1996:
+ *		- bound CMD_SAY_LINE and CMD_MUTE
+ *    sep 22, 1996:
+ *		- bound CMD_PRDIFLN and CMD_NXDIFLN.
+ *    aug 15, 1996:
+ *              - adeded automatic model detection for new firmware.
+ *              - support for selectable help screen.
+ *    feb 19, 1996: 
+ *              - added small hack for automatic rewrite of display when
+ *                the terminal is turned off and back on, replugged, etc.
+ *      feb 15, 1996:
+ *              - Modified writebrl() for lower bandwith
+ *              - Joined the forced ReWrite function to the CURSOR key
+ *      jan 31, 1996:
+ *              - moved user configurable parameters into brlconf.h
+ *              - added identbrl()
+ *              - added overide parameter for serial device
+ *              - added keybindings for BRLTTY preferences menu
+ *      jan 23, 1996:
+ *              - modifications to be compatible with the BRLTTY braille
+ *                mapping standard.
+ *      dec 27, 1995:
+ *              - Added conditions to support all ABT3xx series
+ *              - changed directory Alva_ABT40 to Alva_ABT3
+ *      dec 02, 1995:
+ *              - made changes to support latest Alva ABT3 firmware (new
+ *                serial protocol).
+ *      nov 05, 1995:
+ *              - added typematic facility
+ *              - added key bindings for Stephane Doyon's cut'n paste.
+ *              - added cursor routing key block marking
+ *              - fixed a bug in readbrl() about released keys
+ *      sep 30' 1995:
+ *              - initial Alva driver code, inspired from the
+ *                (old) BrailleLite code.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "parse.h"
+#include "bitfield.h"
+#include "timing.h"
+#include "ascii.h"
+#include "hidkeys.h"
+#include "io_generic.h"
+#include "io_usb.h"
+#include "usb_hid.h"
+
+typedef enum {
+  PARM_ROTATED_CELLS,
+  PARM_SECONDARY_ROUTING_KEY_EMULATION
+} DriverParameter;
+#define BRLPARMS "rotatedcells", "secondaryroutingkeyemulation"
+
+#define BRL_STATUS_FIELDS sfAlphabeticCursorCoordinates, sfAlphabeticWindowCoordinates, sfStateLetter
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-al.h"
+#include "braille.h"
+
+BEGIN_KEY_NAME_TABLE(routing1)
+  KEY_GROUP_ENTRY(AL_GRP_RoutingKeys1, "RoutingKey1"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing2)
+  KEY_GROUP_ENTRY(AL_GRP_RoutingKeys2, "RoutingKey2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status1)
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+0, "Status1A"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+1, "Status1B"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+2, "Status1C"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+3, "Status1D"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+4, "Status1E"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS1+5, "Status1F"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status2)
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+0, "Status2A"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+1, "Status2B"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+2, "Status2C"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+3, "Status2D"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+4, "Status2E"),
+  KEY_NAME_ENTRY(AL_KEY_STATUS2+5, "Status2F"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(abt_basic)
+  KEY_NAME_ENTRY(AL_KEY_Prog, "Prog"),
+  KEY_NAME_ENTRY(AL_KEY_Home, "Home"),
+  KEY_NAME_ENTRY(AL_KEY_Cursor, "Cursor"),
+
+  KEY_NAME_ENTRY(AL_KEY_Up, "Up"),
+  KEY_NAME_ENTRY(AL_KEY_Left, "Left"),
+  KEY_NAME_ENTRY(AL_KEY_Right, "Right"),
+  KEY_NAME_ENTRY(AL_KEY_Down, "Down"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(abt_extra)
+  KEY_NAME_ENTRY(AL_KEY_Cursor2, "Cursor2"),
+  KEY_NAME_ENTRY(AL_KEY_Home2, "Home2"),
+  KEY_NAME_ENTRY(AL_KEY_Prog2, "Prog2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(sat_basic)
+  KEY_NAME_ENTRY(AL_KEY_Home, "Home"),
+  KEY_NAME_ENTRY(AL_KEY_Cursor, "Cursor"),
+
+  KEY_NAME_ENTRY(AL_KEY_Up, "Up"),
+  KEY_NAME_ENTRY(AL_KEY_Left, "Left"),
+  KEY_NAME_ENTRY(AL_KEY_Right, "Right"),
+  KEY_NAME_ENTRY(AL_KEY_Down, "Down"),
+
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadF1, "SpeechPadF1"),
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadUp, "SpeechPadUp"),
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadLeft, "SpeechPadLeft"),
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadDown, "SpeechPadDown"),
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadRight, "SpeechPadRight"),
+  KEY_NAME_ENTRY(AL_KEY_SpeechPadF2, "SpeechPadF2"),
+
+  KEY_NAME_ENTRY(AL_KEY_NavPadF1, "NavPadF1"),
+  KEY_NAME_ENTRY(AL_KEY_NavPadUp, "NavPadUp"),
+  KEY_NAME_ENTRY(AL_KEY_NavPadLeft, "NavPadLeft"),
+  KEY_NAME_ENTRY(AL_KEY_NavPadDown, "NavPadDown"),
+  KEY_NAME_ENTRY(AL_KEY_NavPadRight, "NavPadRight"),
+  KEY_NAME_ENTRY(AL_KEY_NavPadF2, "NavPadF2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(sat_extra)
+  KEY_NAME_ENTRY(AL_KEY_LeftTumblerLeft, "LeftTumblerLeft"),
+  KEY_NAME_ENTRY(AL_KEY_LeftTumblerRight, "LeftTumblerRight"),
+  KEY_NAME_ENTRY(AL_KEY_RightTumblerLeft, "RightTumblerLeft"),
+  KEY_NAME_ENTRY(AL_KEY_RightTumblerRight, "RightTumblerRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(etouch)
+  KEY_NAME_ENTRY(AL_KEY_ETouchLeftRear, "ETouchLeftRear"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchLeftFront, "ETouchLeftFront"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchRightRear, "ETouchRightRear"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchRightFront, "ETouchRightFront"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(smartpad)
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF1, "SmartpadF1"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF2, "SmartpadF2"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadLeft, "SmartpadLeft"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadEnter, "SmartpadEnter"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadUp, "SmartpadUp"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadDown, "SmartpadDown"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadRight, "SmartpadRight"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF3, "SmartpadF3"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF4, "SmartpadF4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(thumb)
+  KEY_NAME_ENTRY(AL_KEY_THUMB+0, "ThumbLeft"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+1, "ThumbUp"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+2, "ThumbHome"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+3, "ThumbDown"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+4, "ThumbRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(featurepack)
+  KEY_NAME_ENTRY(AL_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(AL_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(AL_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(AL_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(AL_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(AL_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(AL_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(AL_KEY_Dot8, "Dot8"),
+  KEY_NAME_ENTRY(AL_KEY_Control, "Control"),
+  KEY_NAME_ENTRY(AL_KEY_Windows, "Windows"),
+  KEY_NAME_ENTRY(AL_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(AL_KEY_Alt, "Alt"),
+  KEY_NAME_ENTRY(AL_KEY_Enter, "Enter"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(el)
+  KEY_NAME_ENTRY(AL_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(AL_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(AL_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(AL_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(AL_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(AL_KEY_Dot6, "Dot6"),
+
+  KEY_NAME_ENTRY(AL_KEY_Dot7, "Shift"),
+  KEY_NAME_ENTRY(AL_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(AL_KEY_Dot8, "Control"),
+
+  KEY_NAME_ENTRY(AL_KEY_SmartpadEnter, "JoystickEnter"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadLeft, "JoystickLeft"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadRight, "JoystickRight"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadUp, "JoystickUp"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadDown, "JoystickDown"),
+
+  KEY_NAME_ENTRY(AL_KEY_THUMB+0, "ScrollLeft"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+4, "ScrollRight"),
+
+  KEY_GROUP_ENTRY(AL_GRP_RoutingKeys1, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(voyager)
+  KEY_NAME_ENTRY(AL_KEY_THUMB+0, "Thumb1"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+1, "Thumb2"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+3, "Thumb3"),
+  KEY_NAME_ENTRY(AL_KEY_THUMB+4, "Thumb4"),
+
+  KEY_NAME_ENTRY(AL_KEY_SmartpadLeft, "Left"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadUp, "Up"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadDown, "Down"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadRight, "Right"),
+
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF1, "Dot1"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF2, "Dot2"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF3, "Dot3"),
+  KEY_NAME_ENTRY(AL_KEY_SmartpadF4, "Dot4"),
+
+  KEY_NAME_ENTRY(AL_KEY_ETouchLeftRear, "Dot5"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchLeftFront, "Dot6"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchRightRear, "Dot7"),
+  KEY_NAME_ENTRY(AL_KEY_ETouchRightFront, "Dot8"),
+
+  KEY_GROUP_ENTRY(AL_GRP_RoutingKeys1, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(abt_small)
+  KEY_NAME_TABLE(abt_basic),
+  KEY_NAME_TABLE(status1),
+  KEY_NAME_TABLE(routing1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(abt_large)
+  KEY_NAME_TABLE(abt_basic),
+  KEY_NAME_TABLE(abt_extra),
+  KEY_NAME_TABLE(status1),
+  KEY_NAME_TABLE(routing1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(sat_small)
+  KEY_NAME_TABLE(sat_basic),
+  KEY_NAME_TABLE(status1),
+  KEY_NAME_TABLE(status2),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(routing2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(sat_large)
+  KEY_NAME_TABLE(sat_basic),
+  KEY_NAME_TABLE(sat_extra),
+  KEY_NAME_TABLE(status1),
+  KEY_NAME_TABLE(status2),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(routing2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bc640)
+  KEY_NAME_TABLE(etouch),
+  KEY_NAME_TABLE(smartpad),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(featurepack),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(routing2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bc680)
+  KEY_NAME_TABLE(etouch),
+  KEY_NAME_TABLE(smartpad),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(featurepack),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(routing2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el)
+  KEY_NAME_TABLE(el),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(voyager)
+  KEY_NAME_TABLE(voyager),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(abt_small)
+DEFINE_KEY_TABLE(abt_large)
+DEFINE_KEY_TABLE(sat_small)
+DEFINE_KEY_TABLE(sat_large)
+DEFINE_KEY_TABLE(bc640)
+DEFINE_KEY_TABLE(bc680)
+DEFINE_KEY_TABLE(el)
+DEFINE_KEY_TABLE(voyager)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(abt_small),
+  &KEY_TABLE_DEFINITION(abt_large),
+  &KEY_TABLE_DEFINITION(sat_small),
+  &KEY_TABLE_DEFINITION(sat_large),
+  &KEY_TABLE_DEFINITION(bc640),
+  &KEY_TABLE_DEFINITION(bc680),
+  &KEY_TABLE_DEFINITION(el),
+  &KEY_TABLE_DEFINITION(voyager),
+END_KEY_TABLE_LIST
+
+struct BrailleDataStruct {
+  unsigned int rotatedCells;
+
+  struct {
+    unsigned char buffer[0X20];
+    unsigned char *end;
+  } restore;
+
+  union {
+    struct {
+      unsigned int secondaryRoutingKeyEmulation;
+
+      unsigned char splitOffset;
+      HidKeyboardPacket hidKeyboardPacket;
+
+      struct {
+        uint32_t hardware;
+        uint32_t firmware;
+        uint32_t btBase;
+        uint32_t btFP;
+      } version;
+
+      struct {
+        uint64_t base;
+        uint64_t featurePack;
+      } macAddress;
+    } bc;
+  } protocol;
+};
+
+typedef struct {
+  const char *name;
+  const KeyTableDefinition *keyTableDefinition;
+  unsigned char identifier;
+  unsigned char columns;
+  unsigned char statusCells;
+  unsigned char flags;
+} ModelEntry;
+static const ModelEntry *model;		/* points to terminal model config struct */
+
+#define MOD_FLAG_CAN_CONFIGURE   0X01
+#define MOD_FLAG_FORCE_FROM_0    0X02
+
+static const ModelEntry modelTable[] = {
+  { .identifier = 0X00,
+    .name = "ABT 320",
+    .columns = 20,
+    .statusCells = 3,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X01,
+    .name = "ABT 340",
+    .columns = 40,
+    .statusCells = 3,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X02,
+    .name = "ABT 340 Desktop",
+    .columns = 40,
+    .statusCells = 5,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X03,
+    .name = "ABT 380",
+    .columns = 80,
+    .statusCells = 5,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_large)
+  }
+  ,
+  { .identifier = 0X04,
+    .name = "ABT 382 Twin Space",
+    .columns = 80,
+    .statusCells = 5,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_large)
+  }
+  ,
+  { .identifier = 0X0A,
+    .name = "Delphi 420",
+    .columns = 20,
+    .statusCells = 3,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X0B,
+    .name = "Delphi 440",
+    .columns = 40,
+    .statusCells = 3,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X0C,
+    .name = "Delphi 440 Desktop",
+    .columns = 40,
+    .statusCells = 5,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_small)
+  }
+  ,
+  { .identifier = 0X0D,
+    .name = "Delphi 480",
+    .columns = 80,
+    .statusCells = 5,
+    .flags = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(abt_large)
+  }
+  ,
+  { .identifier = 0X0E,
+    .name = "Satellite 544",
+    .columns = 40,
+    .statusCells = 3,
+    .flags = MOD_FLAG_CAN_CONFIGURE,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sat_small)
+  }
+  ,
+  { .identifier = 0X0F,
+    .name = "Satellite 570 Pro",
+    .columns = 66,
+    .statusCells = 3,
+    .flags = MOD_FLAG_CAN_CONFIGURE,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sat_large)
+  }
+  ,
+  { .identifier = 0X10,
+    .name = "Satellite 584 Pro",
+    .columns = 80,
+    .statusCells = 3,
+    .flags = MOD_FLAG_CAN_CONFIGURE,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sat_large)
+  }
+  ,
+  { .identifier = 0X11,
+    .name = "Satellite 544 Traveller",
+    .columns = 40,
+    .statusCells = 3,
+    .flags = MOD_FLAG_CAN_CONFIGURE,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sat_small)
+  }
+  ,
+  { .identifier = 0X13,
+    .name = "Braille System 40",
+    .columns = 40,
+    .statusCells = 0,
+    .flags = MOD_FLAG_CAN_CONFIGURE,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sat_small)
+  }
+  ,
+  { .name = NULL }
+};
+
+static const ModelEntry modelBC624 = {
+  .identifier = 0X24,
+  .name = "BC624",
+  .columns = 24,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bc640)
+};
+
+static const ModelEntry modelBC640 = {
+  .identifier = 0X40,
+  .name = "BC640",
+  .columns = 40,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bc640)
+};
+
+static const ModelEntry modelBC680 = {
+  .identifier = 0X80,
+  .name = "BC680",
+  .columns = 80,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bc680)
+};
+
+static const ModelEntry modelEL12 = {
+  .identifier = 0X40,
+  .name = "EasyLink 12 Touch",
+  .columns = 12,
+  .flags = MOD_FLAG_FORCE_FROM_0,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(el)
+};
+
+static const ModelEntry modelVoyager = {
+  .identifier = 0X00,
+  .name = "Voyager Protocol Converter",
+  .columns = 70,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(voyager)
+};
+
+typedef struct {
+  int (*test) (BrailleDisplay *brl);
+  unsigned char feature;
+  unsigned char offset;
+  unsigned char disable;
+  unsigned char enable;
+} SettingsUpdateEntry;
+
+typedef struct {
+  void (*initializeVariables) (BrailleDisplay *brl, char **parameters);
+
+  BraillePacketVerifier *verifyPacket;
+  int (*readPacket) (BrailleDisplay *brl, unsigned char *packet, int size);
+
+  const SettingsUpdateEntry *requiredSettings;
+  int (*setFeature) (BrailleDisplay *brl, const unsigned char *data, size_t size);
+  size_t (*getFeature) (BrailleDisplay *brl, unsigned char feature, unsigned char *buffer, size_t size);
+
+  int (*updateConfiguration) (BrailleDisplay *brl, int autodetecting, const unsigned char *packet);
+  int (*detectModel) (BrailleDisplay *brl);
+
+  int (*readCommand) (BrailleDisplay *brl);
+  int (*writeBraille) (BrailleDisplay *brl, const unsigned char *cells, int start, int count);
+} ProtocolOperations;
+static const ProtocolOperations *protocol;
+
+typedef enum {
+  STATUS_FIRST,
+  STATUS_LEFT,
+  STATUS_RIGHT
+} StatusType;
+
+static unsigned char *previousText = NULL;
+static unsigned char *previousStatus = NULL;
+
+static unsigned char actualColumns;
+static unsigned char textOffset;
+static unsigned char statusOffset;
+
+static unsigned char textRewriteRequired = 0;
+static unsigned char statusRewriteRequired;
+
+typedef unsigned char FieldByteConverter (unsigned char byte);
+
+static uint64_t
+parseNumericField (
+  const unsigned char **bytes, size_t *count,
+  size_t size, size_t width,
+  FieldByteConverter *convertByte
+) {
+  uint64_t result = 0;
+
+  while (width > 0) {
+    result <<= 8;
+
+    if (size > 0) {
+      if (*count > 0) {
+        result |= convertByte(*(*bytes)++);
+        *count -= 1;
+      }
+
+      size -= 1;
+    }
+
+    width -= 1;
+  }
+
+  return result;
+}
+
+static unsigned char
+convertHexadecimalByte (unsigned char byte) {
+  return byte;
+}
+
+static uint64_t
+parseHexadecimalField (
+  const unsigned char **bytes, size_t *count,
+  size_t size, size_t width
+) {
+  return parseNumericField(bytes, count, size, width, convertHexadecimalByte);
+}
+
+static unsigned char
+convertDecimalByte (unsigned char byte) {
+  return byte - '0';
+}
+
+static uint64_t
+parseDecimalField (
+  const unsigned char **bytes, size_t *count,
+  size_t size, size_t width
+) {
+  return parseNumericField(bytes, count, size, width, convertDecimalByte);
+}
+
+static int
+readPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  return readBraillePacket(brl, NULL, packet, size, protocol->verifyPacket, NULL);
+}
+
+static int
+flushSettingsUpdate (
+  BrailleDisplay *brl, size_t length,
+  const unsigned char *old, const unsigned char *new
+) {
+  if (length) {
+    if (memcmp(old, new, length) != 0) {
+      if (!protocol->setFeature(brl, new, length)) return 0;
+
+      {
+        unsigned char **const end = &brl->data->restore.end;
+
+        if (length > UINT8_MAX) {
+          logBytes(LOG_WARNING, "settings update too long", new, length);
+        } else if ((*end + length + 1) > (brl->data->restore.buffer + sizeof(brl->data->restore.buffer))) {
+          logBytes(LOG_WARNING, "settings update not saved", new, length);
+        } else {
+          *end = mempcpy(*end, old, length);
+          *(*end)++ = length;
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+updateSettings (BrailleDisplay *brl) {
+  size_t length = 0;
+  const size_t size = 0X20;
+  unsigned char old[size];
+  unsigned char new[size];
+  const SettingsUpdateEntry *settings = protocol->requiredSettings;
+
+  if (settings) {
+    unsigned char previous = 0;
+
+    while (settings->feature) {
+      if (!settings->test || settings->test(brl)) {
+        if (settings->feature != previous) {
+          if (!flushSettingsUpdate(brl, length, old, new)) return 0;
+
+          if (!(length = protocol->getFeature(brl, settings->feature, old, size))) {
+            if (errno == EAGAIN) goto next;
+
+#ifdef ETIMEDOUT
+            if (errno == ETIMEDOUT) goto next;
+#endif /* ETIMEDOUT */
+
+            return 0;
+          }
+
+          memcpy(new, old, length);
+          previous = settings->feature;
+        }
+
+        {
+          unsigned char *byte = &new[settings->offset];
+
+          *byte &= ~settings->disable;
+          *byte |= settings->enable;
+        }
+      }
+
+    next:
+      settings += 1;
+    }
+  }
+
+  return flushSettingsUpdate(brl, length, old, new);
+}
+
+static int
+restoreSettings (BrailleDisplay *brl) {
+  const unsigned char *request = brl->data->restore.end;
+
+  while (request > brl->data->restore.buffer) {
+    unsigned char length = *--request;
+
+    request -= length;
+    if (!protocol->setFeature(brl, request, length)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+reallocateBuffer (unsigned char **buffer, int size) {
+  void *address = realloc(*buffer, size);
+  if (size && !address) return 0;
+  *buffer = address;
+  return 1;
+}
+
+static int
+reallocateBuffers (BrailleDisplay *brl) {
+  if (reallocateBuffer(&previousText, brl->textColumns*brl->textRows))
+    if (reallocateBuffer(&previousStatus, brl->statusColumns*brl->statusRows))
+      return 1;
+
+  logMessage(LOG_ERR, "cannot allocate braille buffers");
+  return 0;
+}
+
+static int
+setDefaultConfiguration (BrailleDisplay *brl) {
+  logMessage(LOG_INFO, "detected Alva %s: %d columns, %d status cells",
+             model->name, model->columns, model->statusCells);
+
+  brl->textColumns = model->columns;
+  brl->textRows = 1;
+  brl->statusColumns = model->statusCells;
+  brl->statusRows = 1;
+
+  actualColumns = model->columns;
+  statusOffset = 0;
+  textOffset = statusOffset + model->statusCells;
+  textRewriteRequired = 1;			/* To write whole display at first time */
+  statusRewriteRequired = 1;
+  return reallocateBuffers(brl);
+}
+
+static int
+updateConfiguration (BrailleDisplay *brl, int autodetecting, int textColumns, int statusColumns, StatusType statusType) {
+  int changed = 0;
+  int separator = 0;
+
+  actualColumns = textColumns;
+  if (statusType == STATUS_FIRST) {
+    statusOffset = 0;
+    textOffset = statusOffset + statusColumns;
+  } else if ((statusColumns = MIN(statusColumns, (actualColumns-1)/2))) {
+    separator = 1;
+    textColumns -= statusColumns + separator;
+
+    switch (statusType) {
+      case STATUS_LEFT:
+        statusOffset = 0;
+        textOffset = statusOffset + statusColumns + separator;
+        break;
+
+      case STATUS_RIGHT:
+        textOffset = 0;
+        statusOffset = textOffset + textColumns + separator;
+        break;
+
+      default:
+        break;
+    }
+  } else {
+    statusOffset = 0;
+    textOffset = 0;
+  }
+
+  if (statusColumns != brl->statusColumns) {
+    logMessage(LOG_INFO, "status cell count changed to %d", statusColumns);
+    brl->statusColumns = statusColumns;
+    changed = 1;
+  }
+
+  if (textColumns != brl->textColumns) {
+    logMessage(LOG_INFO, "text column count changed to %d", textColumns);
+    brl->textColumns = textColumns;
+    if (!autodetecting) brl->resizeRequired = 1;
+    changed = 1;
+  }
+
+  if (changed)
+    if (!reallocateBuffers(brl))
+      return 0;
+
+  if (separator) {
+    unsigned char cell = 0;
+    if (!protocol->writeBraille(brl, &cell, MAX(textOffset, statusOffset)-1, 1)) return 0;
+  }
+
+  textRewriteRequired = 1;
+  statusRewriteRequired = 1;
+  return 1;
+}
+
+#define PACKET_SIZE(count) (((count) * 2) + 4)
+#define MAXIMUM_PACKET_SIZE PACKET_SIZE(0XFF)
+#define PACKET_BYTE(packet, index) ((packet)[PACKET_SIZE((index)) - 1])
+
+static const unsigned char BRL_ID[] = {ASCII_ESC, 'I', 'D', '='};
+#define BRL_ID_LENGTH (sizeof(BRL_ID))
+#define BRL_ID_SIZE (BRL_ID_LENGTH + 1)
+
+static int
+writeFunction1 (BrailleDisplay *brl, unsigned char code) {
+  unsigned char bytes[] = {ASCII_ESC, 'F', 'U', 'N', code, ASCII_CR};
+  return writeBraillePacket(brl, NULL, bytes, sizeof(bytes));
+}
+
+static int
+writeParameter1 (BrailleDisplay *brl, unsigned char parameter, unsigned char setting) {
+  unsigned char bytes[] = {ASCII_ESC, 'P', 'A', 3, 0, parameter, setting, ASCII_CR};
+  return writeBraillePacket(brl, NULL, bytes, sizeof(bytes));
+}
+
+static int
+updateConfiguration1 (BrailleDisplay *brl, int autodetecting, const unsigned char *packet) {
+  int textColumns = brl->textColumns;
+  int statusColumns = brl->statusColumns;
+  int count = PACKET_BYTE(packet, 0);
+
+  if (count >= 3) statusColumns = PACKET_BYTE(packet, 3);
+  if (count >= 4) textColumns = PACKET_BYTE(packet, 4);
+  return updateConfiguration(brl, autodetecting, textColumns, statusColumns, STATUS_FIRST);
+}
+
+static int
+setBrailleFirmness1 (BrailleDisplay *brl, BrailleFirmness setting) {
+  return writeParameter1(brl, 3,
+                         setting * 4 / BRL_FIRMNESS_MAXIMUM);
+}
+
+static int
+identifyModel1 (BrailleDisplay *brl, unsigned char identifier) {
+  /* Find out which model we are connected to... */
+  for (
+    model = modelTable;
+    model->name && (model->identifier != identifier);
+    model += 1
+  );
+
+  if (model->name) {
+    if (setDefaultConfiguration(brl)) {
+      if (model->flags & MOD_FLAG_CAN_CONFIGURE) {
+        brl->setBrailleFirmness = setBrailleFirmness1;
+
+        if (!writeFunction1(brl, 0X07)) return 0;
+
+        while (awaitBrailleInput(brl, 200)) {
+          unsigned char packet[MAXIMUM_PACKET_SIZE];
+          int count = protocol->readPacket(brl, packet, sizeof(packet));
+
+          if (count == -1) break;
+          if (count == 0) continue;
+
+          if ((packet[0] == 0X7F) && (packet[1] == 0X07)) {
+            updateConfiguration1(brl, 1, packet);
+            break;
+          }
+        }
+
+        if (!writeFunction1(brl, 0X0B)) return 0;
+      }
+
+      return 1;
+    }
+  } else {
+    logMessage(LOG_ERR, "detected unknown Alva model with ID %02X (hex)", identifier);
+  }
+
+  return 0;
+}
+
+static void
+initializeVariables1 (BrailleDisplay *brl, char **parameters) {
+}
+
+static int
+readPacket1 (BrailleDisplay *brl, unsigned char *packet, int size) {
+  int offset = 0;
+  int length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    {
+      int started = offset > 0;
+
+      if (!gioReadByte(brl->gioEndpoint, &byte, started)) {
+        int result = (errno == EAGAIN)? 0: -1;
+        if (started) logPartialPacket(packet, offset);
+        return result;
+      }
+    }
+
+  gotByte:
+    if (offset == 0) {
+      if (byte == 0X7F) {
+        length = PACKET_SIZE(0);
+      } else if ((byte & 0XF0) == 0X70) {
+        length = 2;
+      } else if (byte == BRL_ID[0]) {
+        length = BRL_ID_SIZE;
+      } else if (!byte) {
+        length = 2;
+      } else {
+        logIgnoredByte(byte);
+        continue;
+      }
+    } else {
+      int unexpected = 0;
+
+      unsigned char type = packet[0];
+
+      if (type == 0X7F) {
+        if (offset == 3) length = PACKET_SIZE(byte);
+        if (((offset % 2) == 0) && (byte != 0X7E)) unexpected = 1;
+      } else if (type == BRL_ID[0]) {
+        if ((offset < BRL_ID_LENGTH) && (byte != BRL_ID[offset])) unexpected = 1;
+      } else if (!type) {
+        if (byte) unexpected = 1;
+      }
+
+      if (unexpected) {
+        logShortPacket(packet, offset);
+        offset = 0;
+        length = 0;
+        goto gotByte;
+      }
+    }
+
+    if (offset < size) {
+      packet[offset] = byte;
+    } else {
+      if (offset == size) logTruncatedPacket(packet, offset);
+      logDiscardedByte(byte);
+    }
+
+    if (++offset == length) {
+      if ((offset > size) || !packet[0]) {
+        offset = 0;
+        length = 0;
+        continue;
+      }
+
+      logInputPacket(packet, offset);
+      return length;
+    }
+  }
+}
+
+static int
+detectModel1 (BrailleDisplay *brl) {
+  int probes = 0;
+
+  while (writeFunction1(brl, 0X06)) {
+    while (awaitBrailleInput(brl, 200)) {
+      unsigned char packet[MAXIMUM_PACKET_SIZE];
+
+      if (protocol->readPacket(brl, packet, sizeof(packet)) > 0) {
+        if (memcmp(packet, BRL_ID, BRL_ID_LENGTH) == 0) {
+          if (identifyModel1(brl, packet[BRL_ID_LENGTH])) {
+            return 1;
+          }
+        }
+      }
+    }
+
+    if (errno != EAGAIN) break;
+    if (++probes == 3) break;
+  }
+
+  return 0;
+}
+
+static int
+readCommand1 (BrailleDisplay *brl) {
+  unsigned char packet[MAXIMUM_PACKET_SIZE];
+  int length;
+
+  while ((length = protocol->readPacket(brl, packet, sizeof(packet))) > 0) {
+    unsigned char group = packet[0];
+    unsigned char key = packet[1];
+    int press = !(key & AL_KEY_RELEASE);
+    key &= ~AL_KEY_RELEASE;
+
+    switch (group) {
+      case 0X71: /* operating keys and status keys */
+        if (key <= 0X0D) {
+          enqueueKeyEvent(brl, AL_GRP_NavigationKeys, key+AL_KEY_OPERATION, press);
+          continue;
+        }
+
+        if ((key >= 0X20) && (key <= 0X25)) {
+          enqueueKeyEvent(brl, AL_GRP_NavigationKeys, key-0X20+AL_KEY_STATUS1, press);
+          continue;
+        }
+
+        if ((key >= 0X30) && (key <= 0X35)) {
+          enqueueKeyEvent(brl, AL_GRP_NavigationKeys, key-0X30+AL_KEY_STATUS2, press);
+          continue;
+        }
+
+        break;
+
+      case 0X72: /* primary (lower) routing keys */
+        if (key <= 0X5F) {			/* make */
+          enqueueKeyEvent(brl, AL_GRP_RoutingKeys1, key, press);
+          continue;
+        }
+
+        break;
+
+      case 0X75: /* secondary (upper) routing keys */
+        if (key <= 0X5F) {			/* make */
+          enqueueKeyEvent(brl, AL_GRP_RoutingKeys2, key, press);
+          continue;
+        }
+
+        break;
+
+      case 0X77: /* satellite keypads */
+        if (key <= 0X05) {
+          enqueueKeyEvent(brl, AL_GRP_NavigationKeys, key+AL_KEY_SPEECH_PAD, press);
+          continue;
+        }
+
+        if ((key >= 0X20) && (key <= 0X25)) {
+          enqueueKeyEvent(brl, AL_GRP_NavigationKeys, key-0X20+AL_KEY_NAV_PAD, press);
+          continue;
+        }
+
+        continue;
+
+      case 0X7F:
+        switch (packet[1]) {
+          case 0X07: /* text/status cells reconfigured */
+            if (!updateConfiguration1(brl, 0, packet)) return BRL_CMD_RESTARTBRL;
+            continue;
+
+          case 0X0B: { /* display parameters reconfigured */
+            int count = PACKET_BYTE(packet, 0);
+
+            if (count >= 8) {
+              unsigned char frontKeys = PACKET_BYTE(packet, 8);
+              const unsigned char progKey = 0X02;
+
+              if (frontKeys & progKey) {
+                unsigned char newSetting = frontKeys & ~progKey;
+
+                logMessage(LOG_DEBUG, "Reconfiguring front keys: %02X -> %02X",
+                           frontKeys, newSetting);
+                writeParameter1(brl, 6, newSetting);
+              }
+            }
+
+            continue;
+          }
+        }
+
+        break;
+
+      default:
+        if (length >= BRL_ID_SIZE) {
+          if (memcmp(packet, BRL_ID, BRL_ID_LENGTH) == 0) {
+            /* The terminal has been turned off and back on. */
+            if (!identifyModel1(brl, packet[BRL_ID_LENGTH])) return BRL_CMD_RESTARTBRL;
+            brl->resizeRequired = 1;
+            continue;
+          }
+        }
+
+        break;
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+
+  return (length < 0)? BRL_CMD_RESTARTBRL: EOF;
+}
+
+static int
+writeBraille1 (BrailleDisplay *brl, const unsigned char *cells, int start, int count) {
+  static const unsigned char header[] = {ASCII_CR, ASCII_ESC, 'B'};	/* escape code to display braille */
+  static const unsigned char trailer[] = {ASCII_CR};		/* to send after the braille sequence */
+
+  unsigned char packet[sizeof(header) + 2 + count + sizeof(trailer)];
+  unsigned char *byte = packet;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  *byte++ = start;
+  *byte++ = count;
+  byte = mempcpy(byte, cells, count);
+  byte = mempcpy(byte, trailer, sizeof(trailer));
+
+  return writeBraillePacket(brl, NULL, packet, byte-packet);
+}
+
+static const ProtocolOperations protocol1Operations = {
+  .initializeVariables = initializeVariables1,
+
+  .readPacket = readPacket1,
+
+  .updateConfiguration = updateConfiguration1,
+  .detectModel = detectModel1,
+
+  .readCommand = readCommand1,
+  .writeBraille = writeBraille1
+};
+
+static void
+initializeVariables2 (BrailleDisplay *brl, char **parameters) {
+  brl->data->protocol.bc.secondaryRoutingKeyEmulation = 0;
+  if (*parameters[PARM_SECONDARY_ROUTING_KEY_EMULATION]) {
+    if (!validateYesNo(&brl->data->protocol.bc.secondaryRoutingKeyEmulation, parameters[PARM_SECONDARY_ROUTING_KEY_EMULATION])) {
+      logMessage(LOG_WARNING, "%s: %s", "invalid secondary routing key emulation setting",
+                 parameters[PARM_SECONDARY_ROUTING_KEY_EMULATION]);
+    }
+  }
+
+  initializeHidKeyboardPacket(&brl->data->protocol.bc.hidKeyboardPacket);
+
+  brl->data->protocol.bc.version.hardware = 0;
+  brl->data->protocol.bc.version.firmware = 0;
+  brl->data->protocol.bc.version.btBase = 0;
+  brl->data->protocol.bc.version.btFP = 0;
+
+  brl->data->protocol.bc.macAddress.base = 0;
+  brl->data->protocol.bc.macAddress.featurePack = 0;
+}
+
+static int
+testHaveFeaturePack2 (BrailleDisplay *brl) {
+  return brl->data->protocol.bc.macAddress.featurePack != 0;
+}
+
+static int
+testHaveRawKeyboard2 (BrailleDisplay *brl) {
+  return testHaveFeaturePack2(brl) &&
+         (brl->data->protocol.bc.version.firmware >= 0X020801);
+}
+
+static void
+logVersion2 (uint32_t version, const char *label) {
+  BytesOverlay overlay;
+
+  unsigned char *byte = &overlay.bytes[2];
+  char string[0X40];
+
+  putLittleEndian32(&overlay.u32, version);
+  STR_BEGIN(string, sizeof(string));
+
+  while (1) {
+    STR_PRINTF("%u", *byte);
+    if (byte == overlay.bytes) break;
+
+    *byte = 0;
+    if (!overlay.u32) break;
+
+    STR_PRINTF(".");
+    byte -= 1;
+  }
+
+  STR_END;
+  logMessage(LOG_DEBUG, "%s: %s", label, string);
+}
+
+static uint64_t
+parseHardwareVersion2 (
+  const unsigned char **bytes, size_t *count
+) {
+  return parseDecimalField(bytes, count, 2, 3);
+}
+
+static uint64_t
+parseFirmwareVersion2 (
+  const unsigned char **bytes, size_t *count
+) {
+  return parseHexadecimalField(bytes, count, 3, 3);
+}
+
+static void
+setVersions2 (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  brl->data->protocol.bc.version.hardware = parseHardwareVersion2(&bytes, &count);
+  logVersion2(brl->data->protocol.bc.version.hardware, "Hardware Version");
+
+  brl->data->protocol.bc.version.firmware = parseFirmwareVersion2(&bytes, &count);
+  logVersion2(brl->data->protocol.bc.version.firmware, "Firmware Version");
+
+  brl->data->protocol.bc.version.btBase = parseFirmwareVersion2(&bytes, &count);
+  logVersion2(brl->data->protocol.bc.version.btBase, "Base Bluetooth Module Version");
+
+  brl->data->protocol.bc.version.btFP = parseFirmwareVersion2(&bytes, &count);
+  logVersion2(brl->data->protocol.bc.version.btFP, "Feature Pack Bluetooth Module Version");
+}
+
+static void
+logMacAddress2 (uint64_t address, const char *label) {
+  BytesOverlay overlay;
+
+  const unsigned char *byte = &overlay.bytes[5];
+  char string[0X20];
+
+  putLittleEndian64(&overlay.u64, address);
+  STR_BEGIN(string, sizeof(string));
+
+  while (1) {
+    STR_PRINTF("%02X", *byte);
+    if (byte == overlay.bytes) break;
+    byte -= 1;
+    STR_PRINTF("%c", ':');
+  }
+
+  STR_END;
+  logMessage(LOG_DEBUG, "%s: %s", label, string);
+}
+
+static uint64_t
+parseMacAddress2 (
+  const unsigned char **bytes, size_t *count
+) {
+  BytesOverlay overlay;
+
+  putLittleEndian64(&overlay.u64, parseHexadecimalField(bytes, count, 6, 6));
+  swapBytes(&overlay.bytes[5], &overlay.bytes[4]);
+  swapBytes(&overlay.bytes[2], &overlay.bytes[0]);
+  return getLittleEndian64(overlay.u64);
+}
+
+static void
+setMacAddresses2 (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  brl->data->protocol.bc.macAddress.base = parseMacAddress2(&bytes, &count);
+  logMacAddress2(brl->data->protocol.bc.macAddress.base, "Base Mac Address");
+
+  brl->data->protocol.bc.macAddress.featurePack = parseMacAddress2(&bytes, &count);
+  logMacAddress2(brl->data->protocol.bc.macAddress.featurePack, "Feature Pack Mac Address");
+}
+
+static int
+interpretKeyboardEvent2 (BrailleDisplay *brl, const unsigned char *packet) {
+  const void *newPacket = packet;
+  processHidKeyboardPacket(&brl->data->protocol.bc.hidKeyboardPacket, newPacket);
+  return EOF;
+}
+
+static int
+interpretKeyEvent2 (BrailleDisplay *brl, unsigned char group, unsigned char key) {
+  unsigned char release = group & 0X80;
+  int press = !release;
+  group &= ~release;
+
+  switch (group) {
+    case 0X01:
+      switch (key) {
+        case 0X01:
+          if (!protocol->updateConfiguration(brl, 0, NULL)) return BRL_CMD_RESTARTBRL;
+          return EOF;
+
+        default:
+          break;
+      }
+      break;
+
+    {
+      unsigned int base;
+      unsigned int count;
+      int secondary;
+
+    case 0X71: /* thumb key */
+      base = AL_KEY_THUMB;
+      count = AL_KEYS_THUMB;
+      secondary = 1;
+      goto doKey;
+
+    case 0X72: /* etouch key */
+      base = AL_KEY_ETOUCH;
+      count = AL_KEYS_ETOUCH;
+      secondary = 0;
+      goto doKey;
+
+    case 0X73: /* smartpad key */
+      base = AL_KEY_SMARTPAD;
+      count = AL_KEYS_SMARTPAD;
+      secondary = 1;
+      goto doKey;
+
+    case 0X78: /* feature pack key */
+      base = AL_KEY_FEATUREPACK;
+      count = AL_KEYS_FEATUREPACK;
+      secondary = 0;
+      goto doKey;
+
+    doKey:
+      if (secondary) {
+        if ((key / count) == 1) {
+          key -= count;
+        }
+      }
+
+      if (key < count) {
+        enqueueKeyEvent(brl, AL_GRP_NavigationKeys, base+key, press);
+        return EOF;
+      }
+      break;
+    }
+
+    case 0X74: { /* routing key */
+      unsigned char secondary = key & 0X80;
+      key &= ~secondary;
+
+      /* 
+       * The 6xx series don't have a second row of routing keys but
+       * emulate them (in order to aid compatibility with the 5xx series)
+       * using an annoying press delay.  It is adviseable to turn this
+       * functionality off in the device's menu, but, in case it's left
+       * on, we just interpret these keys as primary routing keys by
+       * default, unless overriden by a driver parameter.
+       */
+      if (!brl->data->protocol.bc.secondaryRoutingKeyEmulation) secondary = 0;
+
+      if (brl->data->protocol.bc.version.firmware < 0X011102) {
+        if (key >= brl->data->protocol.bc.splitOffset) {
+          key -= brl->data->protocol.bc.splitOffset;
+        }
+      }
+
+      if (key >= textOffset) {
+        if ((key -= textOffset) < brl->textColumns) {
+          KeyGroup group = secondary? AL_GRP_RoutingKeys2: AL_GRP_RoutingKeys1;
+
+          enqueueKeyEvent(brl, group, key, press);
+          return EOF;
+        }
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  logMessage(LOG_WARNING, "unknown key: group=%02X key=%02X", group, key);
+  return EOF;
+}
+
+static BraillePacketVerifierResult
+verifyPacket2s (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case ASCII_ESC:
+          *length = 2;
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    case 2:
+      switch (byte) {
+        case 0X32: /* 2 */ *length =  5; break;
+        case 0X3F: /* ? */ *length =  3; break;
+        case 0X45: /* E */ *length =  3; break;
+        case 0X4B: /* K */ *length =  4; break;
+        case 0X4E: /* N */ *length = 14; break;
+        case 0X50: /* P */ *length =  3; break;
+        case 0X54: /* T */ *length =  4; break;
+        case 0X56: /* V */ *length = 13; break;
+        case 0X68: /* h */ *length = 10; break;
+        case 0X72: /* r */ *length = 3; break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+setFeature2s (BrailleDisplay *brl, const unsigned char *request, size_t size) {
+  return writeBraillePacket(brl, NULL, request, size);
+}
+
+static size_t
+getFeature2s (BrailleDisplay *brl, unsigned char feature, unsigned char *response, size_t size) {
+  const unsigned char request[] = {ASCII_ESC, feature, 0X3F};
+
+  if (protocol->setFeature(brl, request, sizeof(request))) {
+    while (awaitBrailleInput(brl, 1000)) {
+      int length = protocol->readPacket(brl, response, size);
+
+      if (length <= 0) break;
+      if ((response[0] == ASCII_ESC) && (response[1] == feature)) return length;
+      logUnexpectedPacket(response, length);
+    }
+  }
+
+  return 0;
+}
+
+static int
+updateConfiguration2s (BrailleDisplay *brl, int autodetecting, const unsigned char *packet) {
+  unsigned char response[0X20];
+
+  if (protocol->getFeature(brl, 0X45, response, sizeof(response))) {
+    unsigned char textColumns = response[2];
+
+    if (autodetecting) {
+      if (brl->data->protocol.bc.version.firmware < 0X010A00) {
+        switch (textColumns) {
+          case 12:
+            if (model == &modelBC640) {
+              model = &modelEL12;
+              logMessage(LOG_INFO, "switched to model %s", model->name);
+            }
+            break;
+
+          default:
+            break;
+        }
+      }
+    }
+
+    if (protocol->getFeature(brl, 0X54, response, sizeof(response))) {
+      unsigned char statusColumns = response[2];
+      unsigned char statusSide = response[3];
+
+      if (updateConfiguration(brl, autodetecting, textColumns, statusColumns,
+                              (statusSide == 'R')? STATUS_RIGHT: STATUS_LEFT)) {
+        brl->data->protocol.bc.splitOffset = (model->columns == actualColumns)? 0: actualColumns+1;
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+identifyModel2s (BrailleDisplay *brl, unsigned char identifier) {
+  static const ModelEntry *const models[] = {
+    &modelBC624, &modelBC640, &modelBC680,
+    NULL
+  };
+
+  unsigned char response[0X20];
+  const ModelEntry *const *modelEntry = models;
+
+  while ((model = *modelEntry++)) {
+    if (model->identifier == identifier) {
+      if (protocol->getFeature(brl, 0X56, response, sizeof(response))) {
+        setVersions2(brl, &response[2], sizeof(response)-2);
+
+        if (protocol->getFeature(brl, 0X4E, response, sizeof(response))) {
+          setMacAddresses2(brl, &response[2], sizeof(response)-2);
+
+          if (setDefaultConfiguration(brl)) {
+            if (updateConfiguration2s(brl, 1, NULL)) {
+              return 1;
+            }
+          }
+        }
+      }
+
+      return 0;
+    }
+  }
+
+  logMessage(LOG_ERR, "detected unknown Alva model with ID %02X (hex)", identifier);
+  return 0;
+}
+
+static int
+detectModel2s (BrailleDisplay *brl) {
+  int probes = 0;
+
+  do {
+    unsigned char response[0X20];
+
+    if (protocol->getFeature(brl, 0X3F, response, sizeof(response))) {
+      if (identifyModel2s(brl, response[2])) {
+        return 1;
+      }
+    } else if (errno != EAGAIN) {
+      break;
+    }
+  } while (++probes < 3);
+
+  return 0;
+}
+
+static int
+readCommand2s (BrailleDisplay *brl) {
+  while (1) {
+    unsigned char packet[MAXIMUM_PACKET_SIZE];
+    int length = protocol->readPacket(brl, packet, sizeof(packet));
+
+    if (!length) return EOF;
+    if (length < 0) return BRL_CMD_RESTARTBRL;
+
+    switch (packet[0]) {
+      case ASCII_ESC:
+        switch (packet[1]) {
+          case 0X4B: /* K */ {
+            int command = interpretKeyEvent2(brl, packet[2], packet[3]);
+            if (command != EOF) return command;
+            continue;
+          }
+
+          case 0X68: /* h */ {
+            int command = interpretKeyboardEvent2(brl, &packet[2]);
+            if (command != EOF) return command;
+            continue;
+          }
+
+          default:
+            break;
+        }
+        break;
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+}
+
+static int
+writeBraille2s (BrailleDisplay *brl, const unsigned char *cells, int start, int count) {
+  unsigned char packet[4 + count];
+  unsigned char *byte = packet;
+
+  *byte++ = ASCII_ESC;
+  *byte++ = 0X42;
+  *byte++ = start;
+  *byte++ = count;
+  byte = mempcpy(byte, cells, count);
+
+  return writeBraillePacket(brl, NULL, packet, byte-packet);
+}
+
+static const SettingsUpdateEntry requiredSettings2s[] = {
+  { /* enable raw feature pack keys */
+    .feature = 0X72 /* r */,
+    .test = testHaveRawKeyboard2,
+    .offset = 2,
+    .disable = 0XFF,
+    .enable = 0X01
+  },
+
+  { /* disable key repeat */
+    .feature = 0X50 /* P */,
+    .offset = 2,
+    .disable = 0XFF
+  },
+
+  { /* disable second routing key row emulation */
+    .feature = 0X32 /* 2 */,
+    .offset = 2,
+    .disable = 0XFF
+  },
+
+  { .feature = 0 }
+};
+
+static const ProtocolOperations protocol2sOperations = {
+  .initializeVariables = initializeVariables2,
+
+  .verifyPacket = verifyPacket2s,
+  .readPacket = readPacket,
+
+  .requiredSettings = requiredSettings2s,
+  .setFeature = setFeature2s,
+  .getFeature = getFeature2s,
+
+  .updateConfiguration = updateConfiguration2s,
+  .detectModel = detectModel2s,
+
+  .readCommand = readCommand2s,
+  .writeBraille = writeBraille2s
+};
+
+static BraillePacketVerifierResult
+verifyPacket2u (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case 0X01: *length = 9; break;
+        case 0X04: *length = 3; break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+setFeature2u (BrailleDisplay *brl, const unsigned char *request, size_t size) {
+  logOutputPacket(request, size);
+  return gioWriteHidFeature(brl->gioEndpoint, request, size) != -1;
+}
+
+static size_t
+getFeature2u (BrailleDisplay *brl, HidReportIdentifier identifier, unsigned char *response, size_t size) {
+  ssize_t length = gioGetHidFeature(brl->gioEndpoint, identifier, response, size);
+
+  if (length > 0) {
+    logInputPacket(response, length);
+    return length;
+  }
+
+  return 0;
+}
+
+static int
+updateConfiguration2u (BrailleDisplay *brl, int autodetecting, const unsigned char *packet) {
+  unsigned char buffer[0X20];
+  size_t length = protocol->getFeature(brl, 0X05, buffer, sizeof(buffer));
+
+  if (length > 0) {
+    int textColumns = brl->textColumns;
+    int statusColumns = brl->statusColumns;
+    int statusSide = 0;
+
+    if (length >= 2) statusColumns = buffer[1];
+    if (length >= 3) statusSide = buffer[2];
+    if (length >= 7) textColumns = buffer[6];
+
+    if (updateConfiguration(brl, autodetecting, textColumns, statusColumns,
+                            statusSide? STATUS_RIGHT: STATUS_LEFT)) {
+      brl->data->protocol.bc.splitOffset = model->columns - actualColumns;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+detectModel2u (BrailleDisplay *brl) {
+  {
+    unsigned char buffer[0X20];
+    size_t length = protocol->getFeature(brl, 0X09, buffer, sizeof(buffer));
+
+    if (length > 3) setVersions2(brl, &buffer[3], length-3);
+  }
+
+  {
+    unsigned char buffer[0X20];
+    size_t length = protocol->getFeature(brl, 0X0D, buffer, sizeof(buffer));
+
+    if (length > 1) setMacAddresses2(brl, &buffer[1], length-1);
+  }
+
+  if (setDefaultConfiguration(brl))
+    if (updateConfiguration2u(brl, 1, NULL))
+      return 1;
+
+  return 0;
+}
+
+static int
+readCommand2u (BrailleDisplay *brl) {
+  while (1) {
+    unsigned char packet[MAXIMUM_PACKET_SIZE];
+    int length = protocol->readPacket(brl, packet, sizeof(packet));
+
+    if (!length) return EOF;
+    if (length < 0) return BRL_CMD_RESTARTBRL;
+
+    switch (packet[0]) {
+      case 0X01: {
+        int command = interpretKeyboardEvent2(brl, &packet[1]);
+        if (command != EOF) return command;
+        continue;
+      }
+
+      case 0X04: {
+        int command = interpretKeyEvent2(brl, packet[2], packet[1]);
+        if (command != EOF) return command;
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+}
+
+static int
+writeBraille2u (BrailleDisplay *brl, const unsigned char *cells, int start, int count) {
+  while (count > 0) {
+    int length = MIN(count, 40);
+    unsigned char packet[3 + length];
+    unsigned char *byte = packet;
+
+    *byte++ = 0X02;
+    *byte++ = start;
+    *byte++ = length;
+    byte = mempcpy(byte, cells, length);
+
+    if (!writeBraillePacket(brl, NULL, packet, byte-packet)) return 0;
+    cells += length;
+    start += length;
+    count -= length;
+  }
+
+  return 1;
+}
+
+static ssize_t
+writeData2u (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  const void *data, size_t size, int timeout
+) {
+  const unsigned char *bytes = data;
+
+  return usbHidSetReport(device, definition->interface,
+                         bytes[0], bytes, size, timeout);
+}
+
+static const SettingsUpdateEntry requiredSettings2u[] = {
+  { /* enable raw feature pack keys */
+    .feature = 6 /* Key Settings Report */,
+    .test = testHaveRawKeyboard2,
+    .offset = 1,
+    .enable = 0X20
+  },
+
+  { /* disable key repeat */
+    .feature = 6 /* Key Settings Report */,
+    .offset = 1,
+    .disable = 0X08
+  },
+
+  { /* disable second routing key row emulation */
+    .feature = 7 /* CR Key Settings Report */,
+    .offset = 1,
+    .disable = 0X02
+  },
+
+  { .feature = 0 }
+};
+
+static const ProtocolOperations protocol2uOperations = {
+  .initializeVariables = initializeVariables2,
+
+  .verifyPacket = verifyPacket2u,
+  .readPacket = readPacket,
+
+  .requiredSettings = requiredSettings2u,
+  .setFeature = setFeature2u,
+  .getFeature = getFeature2u,
+
+  .updateConfiguration = updateConfiguration2u,
+  .detectModel = detectModel2u,
+
+  .readCommand = readCommand2u,
+  .writeBraille = writeBraille2u
+};
+
+static BrailleDisplay *brailleDisplay = NULL;
+
+int
+AL_writeData (unsigned char *data, int len ) {
+  return writeBraillePacket(brailleDisplay, NULL, data, len);
+}
+
+static void
+setUsbConnectionProperties (
+  GioUsbConnectionProperties *properties,
+  const UsbChannelDefinition *definition
+) {
+  model = properties->applicationData;
+
+  if (definition->outputEndpoint) {
+    properties->applicationData = &protocol1Operations;
+  } else {
+    properties->applicationData = &protocol2uOperations;
+    properties->writeData = writeData2u;
+  }
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Satellite (5nn) */
+      .vendor=0X06B0, .product=0X0001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2
+    },
+
+    { /* Voyager Protocol Converter */
+      .vendor=0X0798, .product=0X0600,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .data=&modelVoyager
+    },
+
+    { /* BC624 */
+      .vendor=0X0798, .product=0X0624,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .data=&modelBC624
+    },
+
+    { /* BC640 */
+      .vendor=0X0798, .product=0X0640,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .data=&modelBC640
+    },
+
+    { /* BC680 */
+      .vendor=0X0798, .product=0X0680,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .data=&modelBC680
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &protocol1Operations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.setConnectionProperties = setUsbConnectionProperties;
+  descriptor.usb.options.inputTimeout = 100;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.discoverChannel = 1;
+  descriptor.bluetooth.options.applicationData = &protocol2sOperations;
+  descriptor.bluetooth.options.inputTimeout = 200;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    protocol = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->restore.end = brl->data->restore.buffer;
+
+    if (connectResource(brl, device)) {
+      protocol->initializeVariables(brl, parameters);
+
+      brl->data->rotatedCells = 0;
+      if (*parameters[PARM_ROTATED_CELLS]) {
+        if (!validateYesNo(&brl->data->rotatedCells, parameters[PARM_ROTATED_CELLS])) {
+          logMessage(LOG_WARNING, "%s: %s", "invalid rotated cells setting",
+                     parameters[PARM_ROTATED_CELLS]);
+        }
+      }
+
+      if (protocol->detectModel(brl)) {
+        if (updateSettings(brl)) {
+          setBrailleKeyTable(brl, model->keyTableDefinition);
+
+          if (brl->data->rotatedCells) {
+            makeOutputTable(dotsTable_rotated);
+          } else {
+            makeOutputTable(dotsTable_ISO11548_1);
+          }
+
+          brailleDisplay = brl;
+          return 1;
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  brailleDisplay = NULL;
+  restoreSettings(brl);
+  disconnectBrailleResource(brl, NULL);
+  free(brl->data);
+
+  if (previousText) {
+    free(previousText);
+    previousText = NULL;
+  }
+
+  if (previousStatus) {
+    free(previousStatus);
+    previousStatus = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned int from;
+  unsigned int to;
+
+  if (cellsHaveChanged(previousText, brl->buffer, brl->textColumns, &from, &to, &textRewriteRequired)) {
+    if (model->flags & MOD_FLAG_FORCE_FROM_0) from = 0;
+
+    {
+      size_t count = to - from;
+      unsigned char cells[count];
+
+      translateOutputCells(cells, &brl->buffer[from], count);
+      if (!protocol->writeBraille(brl, cells, textOffset+from, count)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+  size_t cellCount = brl->statusColumns;
+
+  if (cellsHaveChanged(previousStatus, status, cellCount, NULL, NULL, &statusRewriteRequired)) {
+    unsigned char cells[cellCount];
+
+    translateOutputCells(cells, status, cellCount);
+    if (!protocol->writeBraille(brl, cells, statusOffset, cellCount)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  return protocol->readCommand(brl);
+}
diff --git a/Drivers/Braille/Alva/braille.h b/Drivers/Braille/Alva/braille.h
new file mode 100644
index 0000000..4c4fa0d
--- /dev/null
+++ b/Drivers/Braille/Alva/braille.h
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Alva/braille.h - Configurable definitions for the Alva driver
+ * Copyright (C) 1995-1998 by Nicolas Pitre <nico@fluxnic.net>
+ *
+ */
+
+/* used by speech.c */
+extern int AL_writeData (unsigned char *data, int len );
diff --git a/Drivers/Braille/Alva/brldefs-al.h b/Drivers/Braille/Alva/brldefs-al.h
new file mode 100644
index 0000000..adee9c4
--- /dev/null
+++ b/Drivers/Braille/Alva/brldefs-al.h
@@ -0,0 +1,108 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_AL_BRLDEFS
+#define BRLTTY_INCLUDED_AL_BRLDEFS
+
+#define AL_KEYS_OPERATION 14
+#define AL_KEYS_STATUS 6
+#define AL_KEYS_SATELLITE 6
+#define AL_KEYS_ETOUCH 4
+#define AL_KEYS_SMARTPAD 9
+#define AL_KEYS_THUMB 5
+#define AL_KEYS_FEATUREPACK 14
+
+typedef enum {
+  AL_KEY_OPERATION = 1,
+  AL_KEY_STATUS1 = AL_KEY_OPERATION + AL_KEYS_OPERATION,
+  AL_KEY_STATUS2 = AL_KEY_STATUS1 + AL_KEYS_STATUS,
+  AL_KEY_SPEECH_PAD = AL_KEY_STATUS2 + AL_KEYS_STATUS,
+  AL_KEY_NAV_PAD = AL_KEY_SPEECH_PAD + AL_KEYS_SATELLITE,
+  AL_KEY_ETOUCH = AL_KEY_NAV_PAD + AL_KEYS_SATELLITE,
+  AL_KEY_SMARTPAD = AL_KEY_ETOUCH + AL_KEYS_ETOUCH,
+  AL_KEY_THUMB = AL_KEY_SMARTPAD + AL_KEYS_SMARTPAD,
+  AL_KEY_FEATUREPACK = AL_KEY_THUMB + AL_KEYS_THUMB,
+
+  AL_KEY_Prog = AL_KEY_OPERATION,
+  AL_KEY_Home,
+  AL_KEY_Cursor,
+  AL_KEY_Up,
+  AL_KEY_Left,
+  AL_KEY_Right,
+  AL_KEY_Down,
+  AL_KEY_Cursor2,
+  AL_KEY_Home2,
+  AL_KEY_Prog2,
+  AL_KEY_LeftTumblerLeft,
+  AL_KEY_LeftTumblerRight,
+  AL_KEY_RightTumblerLeft,
+  AL_KEY_RightTumblerRight,
+
+  AL_KEY_SpeechPadF1 = AL_KEY_SPEECH_PAD,
+  AL_KEY_SpeechPadUp,
+  AL_KEY_SpeechPadLeft,
+  AL_KEY_SpeechPadDown,
+  AL_KEY_SpeechPadRight,
+  AL_KEY_SpeechPadF2,
+
+  AL_KEY_NavPadF1 = AL_KEY_NAV_PAD,
+  AL_KEY_NavPadUp,
+  AL_KEY_NavPadLeft,
+  AL_KEY_NavPadDown,
+  AL_KEY_NavPadRight,
+  AL_KEY_NavPadF2,
+
+  AL_KEY_ETouchLeftRear = AL_KEY_ETOUCH,
+  AL_KEY_ETouchLeftFront,
+  AL_KEY_ETouchRightRear,
+  AL_KEY_ETouchRightFront,
+
+  AL_KEY_SmartpadF1 = AL_KEY_SMARTPAD,
+  AL_KEY_SmartpadF2,
+  AL_KEY_SmartpadLeft,
+  AL_KEY_SmartpadEnter,
+  AL_KEY_SmartpadUp,
+  AL_KEY_SmartpadDown,
+  AL_KEY_SmartpadRight,
+  AL_KEY_SmartpadF3,
+  AL_KEY_SmartpadF4,
+
+  AL_KEY_Dot1 = AL_KEY_FEATUREPACK + 1,
+  AL_KEY_Dot2,
+  AL_KEY_Dot3,
+  AL_KEY_Dot4,
+  AL_KEY_Dot5,
+  AL_KEY_Dot6,
+  AL_KEY_Dot7,
+  AL_KEY_Dot8,
+  AL_KEY_Control,
+  AL_KEY_Windows,
+  AL_KEY_Space,
+  AL_KEY_Alt,
+  AL_KEY_Enter,
+
+  AL_KEY_RELEASE = 0X80
+} AL_NavigationKey;
+
+typedef enum {
+  AL_GRP_NavigationKeys = 0,
+  AL_GRP_RoutingKeys1,
+  AL_GRP_RoutingKeys2
+} AL_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_AL_BRLDEFS */ 
diff --git a/Drivers/Braille/B2G/Makefile.in b/Drivers/Braille/B2G/Makefile.in
new file mode 100644
index 0000000..8adfeac
--- /dev/null
+++ b/Drivers/Braille/B2G/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://mielke.cc/brltty/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bg
+DRIVER_NAME = B2G
+DRIVER_USAGE = NBP B2G recovery mode
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/B2G/braille.c b/Drivers/Braille/B2G/braille.c
new file mode 100644
index 0000000..638651c
--- /dev/null
+++ b/Drivers/Braille/B2G/braille.c
@@ -0,0 +1,384 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://mielke.cc/brltty/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "async_handle.h"
+#include "async_io.h"
+
+#include "brl_driver.h"
+#include "brldefs-bg.h"
+
+#define KEYBOARD_DEVICE_NAME "cp430_keypad"
+#define BRAILLE_DEVICE_PATH "/dev/braille0"
+#define TEXT_CELL_COUNT 20
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  KEY_NAME_ENTRY(BG_NAV_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(BG_NAV_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(BG_NAV_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(BG_NAV_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(BG_NAV_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(BG_NAV_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(BG_NAV_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(BG_NAV_Dot8, "Dot8"),
+
+  KEY_NAME_ENTRY(BG_NAV_Space, "Space"),
+  KEY_NAME_ENTRY(BG_NAV_Backward, "Backward"),
+  KEY_NAME_ENTRY(BG_NAV_Forward, "Forward"),
+
+  KEY_NAME_ENTRY(BG_NAV_Center, "Center"),
+  KEY_NAME_ENTRY(BG_NAV_Left, "Left"),
+  KEY_NAME_ENTRY(BG_NAV_Right, "Right"),
+  KEY_NAME_ENTRY(BG_NAV_Up, "Up"),
+  KEY_NAME_ENTRY(BG_NAV_Down, "Down"),
+
+  KEY_NAME_ENTRY(BG_NAV_Louder, "Louder"),
+  KEY_NAME_ENTRY(BG_NAV_Softer, "Softer"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(BG_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+struct BrailleDataStruct {
+  struct {
+    int fileDescriptor;
+    AsyncHandle inputHandler;
+  } keyboard;
+
+  struct {
+    int fileDescriptor;
+  } braille;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[TEXT_CELL_COUNT];
+  } text;
+};
+
+#ifdef HAVE_LINUX_INPUT_H
+#include <linux/input.h>
+
+#ifndef KEY_BRL_DOT9
+#define KEY_BRL_DOT9 0X1F9
+#endif /* KEY_BRL_DOT9 */
+
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include "metec_flat20_ioctl.h"
+
+static int
+handleKeyEvent (BrailleDisplay *brl, int code, int press) {
+  KeyNumber number;
+
+  switch(code) {
+#define NAV(CODE,KEY) case KEY_##CODE: number = BG_NAV_##KEY; break;
+    NAV(UP, Up)
+    NAV(LEFT, Left)
+    NAV(RIGHT, Right)
+    NAV(DOWN, Down)
+    NAV(OK, Center)
+
+    NAV(NEXT, Forward)
+    NAV(PREVIOUS, Backward)
+
+    NAV(VOLUMEUP, Louder)
+    NAV(VOLUMEDOWN, Softer)
+
+    NAV(BRL_DOT1, Dot7)
+    NAV(BRL_DOT2, Dot3)
+    NAV(BRL_DOT3, Dot2)
+    NAV(BRL_DOT4, Dot1)
+    NAV(BRL_DOT5, Dot4)
+    NAV(BRL_DOT6, Dot5)
+    NAV(BRL_DOT7, Dot6)
+    NAV(BRL_DOT8, Dot8)
+    NAV(BRL_DOT9, Space)
+#undef NAV
+
+    default:
+      {
+        int key = code - 0X2D0;
+
+        if ((key >= 0) && (key < TEXT_CELL_COUNT)) {
+          return enqueueKeyEvent(brl, BG_GRP_RoutingKeys, key, press);
+        }
+      }
+
+      return 0;
+  }
+
+  return enqueueKeyEvent(brl, BG_GRP_NavigationKeys, number, press);
+}
+
+ASYNC_INPUT_CALLBACK(handleKeyboardEvent) {
+  BrailleDisplay *brl = parameters->data;
+  static const char label[] = "keyboard";
+
+  if (parameters->error) {
+    logMessage(LOG_DEBUG, "%s read error: fd=%d: %s",
+               label, brl->data->keyboard.fileDescriptor, strerror(parameters->error));
+  } else if (parameters->end) {
+    logMessage(LOG_DEBUG, "%s end-of-file: fd=%d", 
+               label, brl->data->keyboard.fileDescriptor);
+  } else {
+    const struct input_event *event = parameters->buffer;
+
+    if (parameters->length >= sizeof(*event)) {
+      logInputPacket(event, sizeof(*event));
+
+      switch (event->type) {
+        case EV_KEY: {
+          int release = event->value == 0;
+          int press   = event->value == 1;
+
+          if (release || press) handleKeyEvent(brl, event->code, press);
+          break;
+        }
+
+        default:
+          break;
+      }
+
+      return sizeof(*event);
+    }
+  }
+
+  return 0;
+}
+
+static char *
+findEventDevice (const char *deviceName) {
+  char *devicePath = NULL;
+  char directoryPath[0X80];
+  DIR *directory;
+
+  snprintf(directoryPath, sizeof(directoryPath),
+           "/sys/bus/platform/devices/%s/input", deviceName);
+
+  if ((directory = opendir(directoryPath))) {
+    struct dirent *entry;
+
+    while ((entry = readdir(directory))) {
+      unsigned int eventNumber;
+      char extra;
+
+      if (sscanf(entry->d_name, "input%u%c", &eventNumber, &extra) == 1) {
+        char path[0X80];
+
+        snprintf(path, sizeof(path), "/dev/input/event%u", eventNumber);
+        if (!(devicePath = strdup(path))) logMallocError();
+        break;
+      }
+    }
+
+    closedir(directory);
+  } else {
+    logMessage(LOG_ERR, "event device input directory open error: %s: %s",
+               directoryPath, strerror(errno));
+  }
+
+  return devicePath;
+}
+
+static int
+openEventDevice (const char *deviceName) {
+  char *devicePath = findEventDevice(deviceName);
+
+  if (devicePath) {
+    int deviceDescriptor = open(devicePath, O_RDONLY);
+
+    if (deviceDescriptor != -1) {
+      if (ioctl(deviceDescriptor, EVIOCGRAB, 1) != -1) {
+        logMessage(LOG_INFO, "Event Device Opened: %s: %s: fd=%d",
+                   deviceName, devicePath, deviceDescriptor);
+
+        free(devicePath);
+        return deviceDescriptor;
+      } else {
+        logSystemError("ioctl[EVIOCGRAB]");
+      }
+
+      close(deviceDescriptor);
+    } else {
+      logMessage(LOG_ERR, "event device open error: %s: %s",
+                 devicePath, strerror(errno));
+    }
+
+    free(devicePath);
+  }
+
+  return -1;
+}
+#endif /* HAVE_LINUX_INPUT_H */
+
+static int
+openKeyboardDevice (BrailleDisplay *brl) {
+#ifdef HAVE_LINUX_INPUT_H
+  if ((brl->data->keyboard.fileDescriptor = openEventDevice(KEYBOARD_DEVICE_NAME)) != -1) {
+    if (asyncReadFile(&brl->data->keyboard.inputHandler,
+                      brl->data->keyboard.fileDescriptor,
+                      sizeof(struct input_event),
+                      handleKeyboardEvent, brl)) {
+      return 1;
+    }
+
+    close(brl->data->keyboard.fileDescriptor);
+    brl->data->keyboard.fileDescriptor = -1;
+  } else {
+    logSystemError("open[keyboard]");
+  }
+#endif /* HAVE_LINUX_INPUT_H */
+
+  return 0;
+}
+
+static void
+closeKeyboardDevice (BrailleDisplay *brl) {
+  if (brl->data->keyboard.inputHandler) {
+    asyncCancelRequest(brl->data->keyboard.inputHandler);
+    brl->data->keyboard.inputHandler = NULL;
+  }
+
+  if (brl->data->keyboard.fileDescriptor != -1) {
+    close(brl->data->keyboard.fileDescriptor);
+    brl->data->keyboard.fileDescriptor = -1;
+  }
+}
+
+static int
+openBrailleDevice (BrailleDisplay *brl) {
+  if ((brl->data->braille.fileDescriptor = open(BRAILLE_DEVICE_PATH, O_WRONLY)) != -1) {
+    return 1;
+  } else {
+    logSystemError("open[braille]");
+  }
+
+  return 0;
+}
+
+static void
+closeBrailleDevice (BrailleDisplay *brl) {
+  if (brl->data->braille.fileDescriptor != -1) {
+    close(brl->data->braille.fileDescriptor);
+    brl->data->braille.fileDescriptor = -1;
+  }
+}
+
+static int
+writeBrailleCells (BrailleDisplay *brl, const unsigned char *cells, size_t count) {
+  logOutputPacket(cells, count);
+  if (write(brl->data->braille.fileDescriptor, cells, count) != -1) return 1;
+
+  logSystemError("write[braille]");
+  return 0;
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  if (connectBrailleResource(brl, "null:", &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->keyboard.fileDescriptor = -1;
+    brl->data->keyboard.inputHandler = NULL;
+    brl->data->braille.fileDescriptor = -1;
+
+    if (connectResource(brl, device)) {
+      if (openBrailleDevice(brl)) {
+        if (openKeyboardDevice(brl)) {
+          brl->textColumns = TEXT_CELL_COUNT;
+
+          setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+          makeOutputTable(dotsTable_ISO11548_1);
+          brl->data->text.rewrite = 1;
+
+          return 1;
+        }
+
+        closeBrailleDevice(brl);
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    closeKeyboardDevice(brl);
+    closeBrailleDevice(brl);
+
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->text.rewrite)) {
+    unsigned char cells[brl->textColumns];
+
+    translateOutputCells(cells, brl->data->text.cells, brl->textColumns);
+    if (!writeBrailleCells(brl, cells, sizeof(cells))) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  return EOF;
+}
diff --git a/Drivers/Braille/B2G/brldefs-bg.h b/Drivers/Braille/B2G/brldefs-bg.h
new file mode 100644
index 0000000..4f12682
--- /dev/null
+++ b/Drivers/Braille/B2G/brldefs-bg.h
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://mielke.cc/brltty/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BG_BRLDEFS
+#define BRLTTY_INCLUDED_BG_BRLDEFS
+
+typedef enum {
+  BG_NAV_Dot1,
+  BG_NAV_Dot2,
+  BG_NAV_Dot3,
+  BG_NAV_Dot4,
+  BG_NAV_Dot5,
+  BG_NAV_Dot6,
+  BG_NAV_Dot7,
+  BG_NAV_Dot8,
+
+  BG_NAV_Space,
+  BG_NAV_Backward,
+  BG_NAV_Forward,
+
+  BG_NAV_Center,
+  BG_NAV_Left,
+  BG_NAV_Right,
+  BG_NAV_Up,
+  BG_NAV_Down,
+
+  BG_NAV_Louder,
+  BG_NAV_Softer,
+} BG_NavigationKey;
+
+typedef enum {
+  BG_GRP_NavigationKeys,
+  BG_GRP_RoutingKeys
+} BG_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_BG_BRLDEFS */ 
diff --git a/Drivers/Braille/B2G/metec_flat20_ioctl.h b/Drivers/Braille/B2G/metec_flat20_ioctl.h
new file mode 100644
index 0000000..d345de4
--- /dev/null
+++ b/Drivers/Braille/B2G/metec_flat20_ioctl.h
@@ -0,0 +1,32 @@
+#ifndef _METEC_FLAT20_IOCTL_H
+#define _METEC_FLAT20_IOCTL_H
+
+#define MAX_BRAILLE_LINE_SIZE			20
+
+#define METEC_FLAT20_IOC_MAGIC			0xE2
+
+#define UOUT1_ENABLE				(1 << 4)
+#define UOUT2_ENABLE				(1 << 5)
+#define UOUT3_ENABLE				(1 << 7)
+
+#define METEC_FLAT20_GET_DRIVER_VERSION		_IOC(_IOC_READ,  METEC_FLAT20_IOC_MAGIC, 0x01, 4)
+#define METEC_FLAT20_DISPLAY_CONTROL		_IOC(_IOC_READ,  METEC_FLAT20_IOC_MAGIC, 0x02, 4)
+	/* {PK} parameters for METEC_FLAT20_DISPLAY_CONTROL ioctl */
+	#define DISPLAY_ENABLE				1
+	#define DISPLAY_DISABLE				0
+#define	METEC_FLAT20_CLEAR_DISPLAY		_IOC(_IOC_WRITE, METEC_FLAT20_IOC_MAGIC, 0x03, 4)
+#define	METEC_FLAT20_DISPLAY_WRITE		_IOC(_IOC_WRITE, METEC_FLAT20_IOC_MAGIC, 0x04, 4)
+#define METEC_FLAT20_SET_DOT_STRENGTH		_IOC(_IOC_WRITE, METEC_FLAT20_IOC_MAGIC, 0x05, 4)
+	/* {PK} Dot Strength Values for METEC_FLAT20_SET_DOT_STRENGTH ioctl */
+	#define UOUT_155V_CONFIG_VALUE			0
+	#define UOUT_162V_CONFIG_VALUE			(UOUT1_ENABLE)
+	#define UOUT_168V_CONFIG_VALUE			(UOUT2_ENABLE)
+	#define UOUT_174V_CONFIG_VALUE			(UOUT2_ENABLE | UOUT1_ENABLE)
+	#define UOUT_177V_CONFIG_VALUE			(UOUT3_ENABLE)
+	#define UOUT_184V_CONFIG_VALUE			(UOUT3_ENABLE | UOUT1_ENABLE)
+	#define UOUT_191V_CONFIG_VALUE			(UOUT3_ENABLE | UOUT2_ENABLE)
+	#define UOUT_199V_CONFIG_VALUE			(UOUT3_ENABLE | UOUT2_ENABLE | UOUT1_ENABLE)
+	
+#define METEC_FLAT20_IOC_MAXNR			0x05
+
+#endif /* _METEC_FLAT20_IOCTL_H */
\ No newline at end of file
diff --git a/Drivers/Braille/Baum/Makefile.in b/Drivers/Braille/Baum/Makefile.in
new file mode 100644
index 0000000..9fc5171
--- /dev/null
+++ b/Drivers/Braille/Baum/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bm
+DRIVER_NAME = Baum
+DRIVER_USAGE = BrailleConnect 12/24/32/40/64/80, Brailliant 24/32/40/64/80, Conny 12, DM80 Plus, EcoVario 24/32/40/64/80, Inka, NLS eReader Zoomax, Orbit Reader 20/40, PocketVario 24, Pronto! V3 18/40, Pronto! V4 18/40, RBT 40/80, Refreshabraille 18, SuperVario 32/40/64/80, Vario 40/80, VarioConnect 12/24/32/40/64/80, VarioPro 40/64/80, VarioUltra 20/32/40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Baum/README.Inka b/Drivers/Braille/Baum/README.Inka
new file mode 100644
index 0000000..8f9c6b1
--- /dev/null
+++ b/Drivers/Braille/Baum/README.Inka
@@ -0,0 +1,24 @@
+Both the keyboard and the serial ports of the Inka should be connected in order
+to use it with BRLTTY. The Inka itself is capable of communicating entirely via
+the keyboard port, but the Linux kernel keyboard driver can't handle this.
+
+In order to use the Inka with BRLTTY it is necessary to have the latest version
+(dated 1998) of the firmware. If the Inka says either "mode 1" or "mode 2" at
+the end of the version number after it's connected to a computer and turned on
+then it has the necessary firmware. If it doesn't then it'll probably need an
+upgrade. To do this, contact Baum and give them the serial number of the Inka
+(which is printed on the under side of the device).
+
+The newer firmware supports two modes of operation. Mode 1 uses the old Inka
+protocol and transfers data at 57,600 baud with non-standard flow control. Mode
+2 uses the new Inka protocol and transfers data at 19,200 baud with no flow
+control.
+
+The Inka defaults to mode 1. This is shown on the braille display when 
+the Inka first initializes or after a soft reset.
+
+Before running BRLTTY, change the Inka to mode 2 by pressing tl1+tl2+tr3 (which
+is the digit 2 represented as a dot-six number). To return to mode 1 (neither
+necessary nor desirable under Linux), press tl1+tr3 (which is the digit 1
+represented as a dot-six number). The new mode setting is shown on the display
+at the end of the line, after the version number and date of the firmware.
diff --git a/Drivers/Braille/Baum/braille.c b/Drivers/Braille/Baum/braille.c
new file mode 100644
index 0000000..c73ff14
--- /dev/null
+++ b/Drivers/Braille/Baum/braille.c
@@ -0,0 +1,3436 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "parse.h"
+#include "async_wait.h"
+#include "ascii.h"
+
+typedef enum {
+  PARM_PROTOCOL,
+  PARM_VARIOKEYS
+} DriverParameter;
+#define BRLPARMS "protocol", "variokeys"
+
+#define BRLSTAT ST_TiemanStyle
+#define BRL_HAVE_STATUS_CELLS
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "brldefs-bm.h"
+
+BEGIN_KEY_NAME_TABLE(display)
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+7, "Display8"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+6, "Display7"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+5, "Display6"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+4, "Display5"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+3, "Display4"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+2, "Display3"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+1, "Display2"),
+  KEY_NAME_ENTRY(BM_KEY_DISPLAY+0, "Display1"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(command)
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+6, "Command7"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+5, "Command6"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+4, "Command5"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+3, "Command4"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+2, "Command3"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+1, "Command2"),
+  KEY_NAME_ENTRY(BM_KEY_COMMAND+0, "Command1"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(front)
+  KEY_NAME_ENTRY(BM_KEY_FRONT+0, "Front1"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+1, "Front2"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+2, "Front3"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+3, "Front4"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+4, "Front5"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+5, "Front6"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+6, "Front7"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+7, "Front8"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+8, "Front9"),
+  KEY_NAME_ENTRY(BM_KEY_FRONT+9, "Front10"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(back)
+  KEY_NAME_ENTRY(BM_KEY_BACK+0, "Back1"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+1, "Back2"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+2, "Back3"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+3, "Back4"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+4, "Back5"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+5, "Back6"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+6, "Back7"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+7, "Back8"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+8, "Back9"),
+  KEY_NAME_ENTRY(BM_KEY_BACK+9, "Back10"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(entry)
+  KEY_NAME_ENTRY(BM_KEY_B9, "B9"),
+  KEY_NAME_ENTRY(BM_KEY_B10, "B10"),
+  KEY_NAME_ENTRY(BM_KEY_B11, "B11"),
+
+  KEY_NAME_ENTRY(BM_KEY_F1, "F1"),
+  KEY_NAME_ENTRY(BM_KEY_F2, "F2"),
+  KEY_NAME_ENTRY(BM_KEY_F3, "F3"),
+  KEY_NAME_ENTRY(BM_KEY_F4, "F4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(dots)
+  KEY_NAME_ENTRY(BM_KEY_DOT1, "Dot1"),
+  KEY_NAME_ENTRY(BM_KEY_DOT2, "Dot2"),
+  KEY_NAME_ENTRY(BM_KEY_DOT3, "Dot3"),
+  KEY_NAME_ENTRY(BM_KEY_DOT4, "Dot4"),
+  KEY_NAME_ENTRY(BM_KEY_DOT5, "Dot5"),
+  KEY_NAME_ENTRY(BM_KEY_DOT6, "Dot6"),
+  KEY_NAME_ENTRY(BM_KEY_DOT7, "Dot7"),
+  KEY_NAME_ENTRY(BM_KEY_DOT8, "Dot8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(joystick)
+  KEY_NAME_ENTRY(BM_KEY_UP, "Up"),
+  KEY_NAME_ENTRY(BM_KEY_LEFT, "Left"),
+  KEY_NAME_ENTRY(BM_KEY_DOWN, "Down"),
+  KEY_NAME_ENTRY(BM_KEY_RIGHT, "Right"),
+  KEY_NAME_ENTRY(BM_KEY_PRESS, "Press"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(navpad)
+  KEY_NAME_ENTRY(BM_KEY_UP, "Up"),
+  KEY_NAME_ENTRY(BM_KEY_LEFT, "Left"),
+  KEY_NAME_ENTRY(BM_KEY_DOWN, "Down"),
+  KEY_NAME_ENTRY(BM_KEY_RIGHT, "Right"),
+  KEY_NAME_ENTRY(BM_KEY_PRESS, "Select"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(wheels)
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_UP+0, "FirstWheelUp"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_DOWN+0, "FirstWheelDown"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_PRESS+0, "FirstWheelPress"),
+
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_UP+1, "SecondWheelUp"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_DOWN+1, "SecondWheelDown"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_PRESS+1, "SecondWheelPress"),
+
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_UP+2, "ThirdWheelUp"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_DOWN+2, "ThirdWheelDown"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_PRESS+2, "ThirdWheelPress"),
+
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_UP+3, "FourthWheelUp"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_DOWN+3, "FourthWheelDown"),
+  KEY_NAME_ENTRY(BM_KEY_WHEEL_PRESS+3, "FourthWheelPress"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status)
+  KEY_NAME_ENTRY(BM_KEY_STATUS+0, "StatusButton1"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+1, "StatusButton2"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+2, "StatusButton3"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+3, "StatusButton4"),
+
+  KEY_NAME_ENTRY(BM_KEY_STATUS+4, "StatusKey1"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+5, "StatusKey2"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+6, "StatusKey3"),
+  KEY_NAME_ENTRY(BM_KEY_STATUS+7, "StatusKey4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(BM_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(horizontal)
+  KEY_GROUP_ENTRY(BM_GRP_HorizontalSensors, "HorizontalSensor"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(vertical)
+  KEY_GROUP_ENTRY(BM_GRP_LeftSensors, "LeftSensor"),
+  KEY_GROUP_ENTRY(BM_GRP_RightSensors, "RightSensor"),
+  KEY_GROUP_ENTRY(BM_GRP_ScaledLeftSensors, "ScaledLeftSensor"),
+  KEY_GROUP_ENTRY(BM_GRP_ScaledRightSensors, "ScaledRightSensor"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(NLS_Zoomax)
+  KEY_NAME_ENTRY(BM_KEY_B9, "BL"),
+  KEY_NAME_ENTRY(BM_KEY_B10, "Space"),
+
+  KEY_NAME_ENTRY(BM_KEY_F1, "S1"),
+  KEY_NAME_ENTRY(BM_KEY_F2, "S2"),
+  KEY_NAME_ENTRY(BM_KEY_F3, "S3"),
+  KEY_NAME_ENTRY(BM_KEY_F4, "S4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(orbit)
+  KEY_NAME_ENTRY(BM_KEY_B9, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(default)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(rb)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(orbit)
+  KEY_NAME_TABLE(orbit),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(navpad),
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(NLS_Zoomax)
+  KEY_NAME_TABLE(NLS_Zoomax),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(navpad),
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(b2g)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(navpad),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(connect)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(conny)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pronto)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pv)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(sv)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ultra)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(entry),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(joystick),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(inka)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(horizontal),
+  KEY_NAME_TABLE(vertical),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(dm80p)
+  KEY_NAME_SUBTABLE(display,7),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(v40)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(v80)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(command),
+  KEY_NAME_TABLE(front),
+  KEY_NAME_TABLE(back),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pro)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(wheels),
+  KEY_NAME_TABLE(status),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(vk)
+  KEY_NAME_SUBTABLE(display,6),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(default)
+DEFINE_KEY_TABLE(rb)
+DEFINE_KEY_TABLE(orbit)
+DEFINE_KEY_TABLE(NLS_Zoomax)
+DEFINE_KEY_TABLE(b2g)
+DEFINE_KEY_TABLE(connect)
+DEFINE_KEY_TABLE(conny)
+DEFINE_KEY_TABLE(pronto)
+DEFINE_KEY_TABLE(pv)
+DEFINE_KEY_TABLE(sv)
+DEFINE_KEY_TABLE(ultra)
+DEFINE_KEY_TABLE(inka)
+DEFINE_KEY_TABLE(dm80p)
+DEFINE_KEY_TABLE(v40)
+DEFINE_KEY_TABLE(v80)
+DEFINE_KEY_TABLE(pro)
+DEFINE_KEY_TABLE(vk)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(default),
+  &KEY_TABLE_DEFINITION(rb),
+  &KEY_TABLE_DEFINITION(orbit),
+  &KEY_TABLE_DEFINITION(NLS_Zoomax),
+  &KEY_TABLE_DEFINITION(b2g),
+  &KEY_TABLE_DEFINITION(connect),
+  &KEY_TABLE_DEFINITION(conny),
+  &KEY_TABLE_DEFINITION(pronto),
+  &KEY_TABLE_DEFINITION(pv),
+  &KEY_TABLE_DEFINITION(sv),
+  &KEY_TABLE_DEFINITION(ultra),
+  &KEY_TABLE_DEFINITION(inka),
+  &KEY_TABLE_DEFINITION(dm80p),
+  &KEY_TABLE_DEFINITION(v40),
+  &KEY_TABLE_DEFINITION(v80),
+  &KEY_TABLE_DEFINITION(pro),
+  &KEY_TABLE_DEFINITION(vk),
+END_KEY_TABLE_LIST
+
+/* Global Definitions */
+
+static const int probeLimit = 2;
+static const int probeTimeout = 200;
+
+#define KEY_GROUP_SIZE(count) (((count) + 7) / 8)
+#define MAXIMUM_CELL_COUNT 84
+#define VERTICAL_SENSOR_COUNT 27
+
+static int cellCount;
+static int cellsUpdated;
+static unsigned char internalCells[MAXIMUM_CELL_COUNT];
+static unsigned char externalCells[MAXIMUM_CELL_COUNT];
+
+typedef struct {
+  unsigned char navigationKeys[KEY_GROUP_SIZE(BM_KEY_COUNT)];
+  unsigned char routingKeys[KEY_GROUP_SIZE(MAXIMUM_CELL_COUNT)];
+  unsigned char horizontalSensors[KEY_GROUP_SIZE(MAXIMUM_CELL_COUNT)];
+  unsigned char leftSensors[KEY_GROUP_SIZE(VERTICAL_SENSOR_COUNT)];
+  unsigned char rightSensors[KEY_GROUP_SIZE(VERTICAL_SENSOR_COUNT)];
+} KeysState;
+
+static KeysState keysState;
+static unsigned char switchSettings;
+
+typedef struct {
+  const char *name;
+  const DotsTable *dotsTable;
+
+  unsigned int serialBaud;
+  SerialParity serialParity;
+
+  int (*readPacket) (BrailleDisplay *brl, unsigned char *packet, int size);
+  int (*writePacket) (BrailleDisplay *brl, const unsigned char *packet, int length);
+
+  int (*probeDevice) (BrailleDisplay *brl);
+  void (*processPackets) (BrailleDisplay *brl);
+
+  int (*writeCells) (BrailleDisplay *brl);
+  int (*writeCellRange) (BrailleDisplay *brl, unsigned int start, unsigned int count);
+} ProtocolOperations;
+
+struct BrailleDataStruct {
+  const ProtocolOperations *protocol;
+
+  struct {
+    unsigned char routingKeys;
+  } packetSize;
+};
+
+/* Internal Routines */
+
+static void
+logTextField (const char *name, const char *address, int size) {
+  while (size > 0) {
+    const char byte = address[size - 1];
+
+    if (byte && (byte != ' ')) break;
+    size -= 1;
+  }
+
+  logMessage(LOG_INFO, "%s: %.*s", name, size, address);
+}
+
+static int
+setGroupedKey (unsigned char *set, KeyNumber number, int press) {
+  unsigned char *byte = &set[number / 8];
+  unsigned char bit = 1 << (number % 8);
+
+  if (!(*byte & bit) == !press) return 0;
+
+  if (press) {
+    *byte |= bit;
+  } else {
+    *byte &= ~bit;
+  }
+
+  return 1;
+}
+
+static void
+clearKeyGroup (unsigned char *set, unsigned char count) {
+  memset(set, 0, KEY_GROUP_SIZE(count));
+}
+
+static void
+resetKeyGroup (unsigned char *set, unsigned char count, KeyNumber key) {
+  clearKeyGroup(set, count);
+  if (key > 0) setGroupedKey(set, key-1, 1);
+}
+
+static void
+updateKeyGroup (
+  BrailleDisplay *brl,
+  unsigned char *old, const unsigned char *new,
+  KeyGroup group, KeyNumber base, unsigned char count, int scaled
+) {
+  KeyNumber pressTable[count];
+  unsigned char pressCount = 0;
+  unsigned char offset;
+
+  for (offset=0; offset<count; offset+=1) {
+    KeyNumber number = base + offset;
+    int press = (new[offset / 8] & (1 << (offset % 8))) != 0;
+
+    if (setGroupedKey(old, number, press)) {
+      if (scaled) number = rescaleInteger(number, count-1, BRL_MSK_ARG);
+
+      if (press) {
+        pressTable[pressCount++] = number;
+      } else {
+        enqueueKeyEvent(brl, group, number, 0);
+      }
+    }
+  }
+
+  while (pressCount) enqueueKeyEvent(brl, group, pressTable[--pressCount], 1);
+}
+
+static void
+updateNavigationKeys (
+  BrailleDisplay *brl,
+  const unsigned char *new, KeyNumber base, unsigned char count
+) {
+  updateKeyGroup(brl, keysState.navigationKeys, new, BM_GRP_NavigationKeys, base, count, 0);
+}
+
+static void
+updateDisplayKeys (BrailleDisplay *brl, unsigned char new) {
+  updateNavigationKeys(brl, &new, BM_KEY_DISPLAY, BM_KEYS_DISPLAY);
+}
+
+static void
+updateEntryKeys (BrailleDisplay *brl, unsigned char *new) {
+  updateNavigationKeys(brl, new, BM_KEY_ENTRY, BM_KEYS_ENTRY);
+}
+
+static void
+updateJoystick (BrailleDisplay *brl, unsigned char *new) {
+  updateNavigationKeys(brl, new, BM_KEY_JOYSTICK, BM_KEYS_JOYSTICK);
+}
+
+static void
+updateRoutingKeys (BrailleDisplay *brl, const unsigned char *new, unsigned char count) {
+  updateKeyGroup(brl, keysState.routingKeys, new, BM_GRP_RoutingKeys, 0, count, 0);
+}
+
+static int
+updateCells (BrailleDisplay *brl) {
+  if (cellsUpdated) {
+    if (!brl->data->protocol->writeCells(brl)) return 0;
+    cellsUpdated = 0;
+  }
+  return 1;
+}
+
+static int
+updateCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  if (count) {
+    translateOutputCells(&externalCells[start], &internalCells[start], count);
+    cellsUpdated = 1;
+    if (!brl->data->protocol->writeCellRange(brl, start, count)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+clearCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  memset(&internalCells[start], 0, count);
+  return updateCellRange(brl, start, count);
+}
+
+static int
+putCells (BrailleDisplay *brl, const unsigned char *cells, unsigned int start, unsigned int count) {
+  unsigned int from;
+  unsigned int to;
+
+  if (cellsHaveChanged(&internalCells[start], cells, count, &from, &to, NULL)) {
+    if (!updateCellRange(brl, start+from, to-from)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+isAcceptableCellCount (int count) {
+  return (count > 0) && (count <= MAXIMUM_CELL_COUNT);
+}
+
+static void
+logUnexpectedCellCount (int count) {
+  logMessage(LOG_DEBUG, "unexpected cell count: %d", count);
+}
+
+static void
+logCellCount (BrailleDisplay *brl) {
+  switch ((brl->textColumns = cellCount)) {
+    case 44:
+    case 68:
+    case 84:
+      brl->textColumns -= 4;
+      break;
+
+    case 56:
+      brl->textColumns -= 16;
+      break;
+  }
+  brl->textRows = 1;
+  brl->statusRows = (brl->statusColumns = cellCount - brl->textColumns)? 1: 0;
+
+  logMessage(LOG_INFO, "Cell Count: %d (%d text, %d status)",
+             cellCount, brl->textColumns, brl->statusColumns);
+}
+
+static int
+changeCellCount (BrailleDisplay *brl, int count) {
+  int ok = 1;
+
+  if (count != cellCount) {
+    if (count > cellCount) {
+      if (!clearCellRange(brl, cellCount, count-cellCount)) ok = 0;
+
+      {
+        int number;
+        for (number=cellCount; number<count; number+=1) {
+          setGroupedKey(keysState.routingKeys, number, 0);
+          setGroupedKey(keysState.horizontalSensors, number, 0);
+        }
+      }
+    }
+
+    cellCount = count;
+    logCellCount(brl);
+    brl->resizeRequired = 1;
+  }
+
+  return ok;
+}
+
+/* Baum Protocol */
+
+typedef unsigned char BaumInteger[2];
+#define MAKE_BAUM_INTEGER_FIRST(i) ((i) & 0XFF)
+#define MAKE_BAUM_INTEGER_SECOND(i) (((i) >> 8) & 0XFF)
+#define MAKE_BAUM_INTEGER(i) MAKE_BAUM_INTEGER_FIRST((i)), MAKE_BAUM_INTEGER_SECOND((i))
+
+static inline uint16_t
+getBaumInteger (const BaumInteger integer) {
+  return (integer[1] << 8) | integer[0];
+}
+
+typedef enum {
+  BAUM_REQ_DisplayData             = 0X01,
+  BAUM_REQ_GetVersionNumber        = 0X05,
+  BAUM_REQ_GetKeys                 = 0X08,
+  BAUM_REQ_GetMode                 = 0X11,
+  BAUM_REQ_SetMode                 = 0X12,
+  BAUM_REQ_SetProtocolState        = 0X15,
+  BAUM_REQ_SetCommunicationChannel = 0X16,
+  BAUM_REQ_CausePowerdown          = 0X17,
+  BAUM_REQ_ModuleRegistration      = 0X50,
+  BAUM_REQ_DataRegisters           = 0X51,
+  BAUM_REQ_ServiceRegisters        = 0X52,
+  BAUM_REQ_GetDeviceIdentity       = 0X84,
+  BAUM_REQ_GetSerialNumber         = 0X8A,
+  BAUM_REQ_GetBluetoothName        = 0X8C,
+  BAUM_REQ_SetBluetoothName        = 0X8D,
+  BAUM_REQ_SetBluetoothPin         = 0X8E
+} BaumRequestCode;
+
+typedef enum {
+  BAUM_RSP_CellCount            = 0X01,
+  BAUM_RSP_VersionNumber        = 0X05,
+  BAUM_RSP_ModeSetting          = 0X11,
+  BAUM_RSP_CommunicationChannel = 0X16,
+  BAUM_RSP_PowerdownSignal      = 0X17,
+  BAUM_RSP_HorizontalSensors    = 0X20,
+  BAUM_RSP_VerticalSensors      = 0X21,
+  BAUM_RSP_RoutingKeys          = 0X22,
+  BAUM_RSP_Switches             = 0X23,
+  BAUM_RSP_DisplayKeys          = 0X24,
+  BAUM_RSP_HorizontalSensor     = 0X25,
+  BAUM_RSP_VerticalSensor       = 0X26,
+  BAUM_RSP_RoutingKey           = 0X27,
+  BAUM_RSP_Front6               = 0X28,
+  BAUM_RSP_Back6                = 0X29,
+  BAUM_RSP_CommandKeys          = 0X2B,
+  BAUM_RSP_Front10              = 0X2C,
+  BAUM_RSP_Back10               = 0X2D,
+  BAUM_RSP_EntryKeys            = 0X33,
+  BAUM_RSP_Joystick             = 0X34,
+  BAUM_RSP_ErrorCode            = 0X40,
+  BAUM_RSP_ModuleRegistration   = 0X50,
+  BAUM_RSP_DataRegisters        = 0X51,
+  BAUM_RSP_ServiceRegisters     = 0X52,
+  BAUM_RSP_DeviceIdentity       = 0X84,
+  BAUM_RSP_SerialNumber         = 0X8A,
+  BAUM_RSP_BluetoothName        = 0X8C,
+  BAUM_RSP_NLS_ZMX_BD           = 0XBD,
+  BAUM_RSP_NLS_ZMX_BE           = 0XBE,
+  BAUM_RSP_NLS_ZMX_BF           = 0XBF,
+} BaumResponseCode;
+
+typedef enum {
+  BAUM_MODE_KeyGroupCompressed          = 0X01,
+  BAUM_MODE_HorizontalSensorsEnabled    = 0X06,
+  BAUM_MODE_LeftSensorsEnabled  = 0X07,
+  BAUM_MODE_RoutingKeysEnabled          = 0X08,
+  BAUM_MODE_RightSensorsEnabled = 0X09,
+  BAUM_MODE_BackKeysEnabled             = 0X0A,
+  BAUM_MODE_DisplayRotated              = 0X10,
+  BAUM_MODE_DisplayEnabled              = 0X20,
+  BAUM_MODE_PowerdownEnabled            = 0X21,
+  BAUM_MODE_PowerdownTime               = 0X22,
+  BAUM_MODE_BluetoothEnabled            = 0X23,
+  BAUM_MODE_UsbCharge                   = 0X24
+} BaumMode;
+
+typedef enum {
+  BAUM_PDT_5Minutes  = 1,
+  BAUM_PDT_10Minutes = 2,
+  BAUM_PDT_1Hour     = 3,
+  BAUM_PDT_2Hours    = 4
+} BaumPowerdownTime;
+
+typedef enum {
+  BAUM_PDR_ProtocolRequested = 0X01,
+  BAUM_PDR_PowerSwitch       = 0X02,
+  BAUM_PDR_AutoPowerOff      = 0X04,
+  BAUM_PDR_BatteryLow        = 0X08,
+  BAUM_PDR_Charging          = 0X80
+} BaumPowerdownReason;
+
+typedef enum {
+  BAUM_SWT_DisableSensors  = 0X01,
+  BAUM_SWT_ScaledVertical  = 0X02,
+  BAUM_SWT_ShowSensor      = 0X40,
+  BAUM_SWT_BrailleKeyboard = 0X80
+} BaumSwitch;
+
+typedef enum {
+  BAUM_ERR_BluetoothSupport       = 0X0A,
+  BAUM_ERR_TransmitOverrun        = 0X10,
+  BAUM_ERR_ReceiveOverrun         = 0X11,
+  BAUM_ERR_TransmitTimeout        = 0X12,
+  BAUM_ERR_ReceiveTimeout         = 0X13,
+  BAUM_ERR_PacketType             = 0X14,
+  BAUM_ERR_PacketChecksum         = 0X15,
+  BAUM_ERR_PacketData             = 0X16,
+  BAUM_ERR_Test                   = 0X18,
+  BAUM_ERR_FlashWrite             = 0X19,
+  BAUM_ERR_CommunicationChannel   = 0X1F,
+  BAUM_ERR_SerialNumber           = 0X20,
+  BAUM_ERR_SerialParity           = 0X21,
+  BAUM_ERR_SerialOverrun          = 0X22,
+  BAUM_ERR_SerialFrame            = 0X24,
+  BAUM_ERR_LocalizationIdentifier = 0X25,
+  BAUM_ERR_LocalizationIndex      = 0X26,
+  BAUM_ERR_LanguageIdentifier     = 0X27,
+  BAUM_ERR_LanguageIndex          = 0X28,
+  BAUM_ERR_BrailleTableIdentifier = 0X29,
+  BAUM_ERR_BrailleTableIndex      = 0X2A
+} BaumError;
+
+#define BAUM_LENGTH_DeviceIdentity 18
+#define BAUM_LENGTH_SerialNumber 8
+#define BAUM_LENGTH_BluetoothName 14
+
+typedef enum {
+  BAUM_MRC_Acknowledge = 0X01,
+  BAUM_MRC_Query       = 0X04
+} BaumModuleRegistrationCommand;
+
+typedef enum {
+  BAUM_MRE_Addition  = 1,
+  BAUM_MRE_Removal   = 2,
+  BAUM_MRE_Rejection = 3
+} BaumModuleRegistrationEvent;
+
+typedef enum {
+  BAUM_DRC_Write = 0X00,
+  BAUM_DRC_Read  = 0X01,
+  BAUM_DRC_Reset = 0X80
+} BaumDataRegistersCommand;
+
+typedef enum {
+  BAUM_DRF_WheelsChanged  = 0X01,
+  BAUM_DRF_ButtonsChanged = 0X02,
+  BAUM_DRF_KeysChanged    = 0X04,
+  BAUM_DRF_PotsChanged    = 0X04,
+  BAUM_DRF_SensorsChanged = 0X08,
+  BAUM_DRF_ErrorOccurred  = 0X80
+} BaumDataRegistersFlag;
+
+typedef enum {
+  BAUM_DRE_WheelsNotConnected = 0X01,
+  BAUM_DRE_WheelsNotAdjusted  = 0X02,
+  BAUM_DRE_KeyBufferFull      = 0X04,
+  BAUM_DRE_SerialError        = 0X80
+} BaumDataRegistersError;
+
+typedef enum {
+  BAUM_SRC_Write = 0X00,
+  BAUM_SRC_Read  = 0X01
+} BaumServiceRegistersCommand;
+
+typedef union {
+  unsigned char bytes[2 + 0XFF];
+
+  struct {
+    unsigned char code;
+
+    union {
+      unsigned char cellCount;
+      unsigned char versionNumber;
+
+      struct {
+        unsigned char identifier;
+        unsigned char setting;
+      } PACKED mode;
+
+      unsigned char communicationChannel;
+      unsigned char powerdownReason;
+      unsigned char horizontalSensors[KEY_GROUP_SIZE(MAXIMUM_CELL_COUNT)];
+
+      struct {
+        unsigned char left[KEY_GROUP_SIZE(VERTICAL_SENSOR_COUNT)];
+        unsigned char right[KEY_GROUP_SIZE(VERTICAL_SENSOR_COUNT)];
+      } PACKED verticalSensors;
+
+      unsigned char routingKeys[KEY_GROUP_SIZE(MAXIMUM_CELL_COUNT)];
+      unsigned char switches;
+      unsigned char displayKeys;
+      unsigned char horizontalSensor;
+
+      union {
+        unsigned char left;
+        unsigned char right;
+      } PACKED verticalSensor;
+
+      unsigned char routingKey;
+      unsigned char front6[1];
+      unsigned char back6[1];
+      unsigned char commandKeys[1];
+      unsigned char front10[2];
+      unsigned char back10[2];
+      unsigned char entryKeys[2];
+      unsigned char joystick[1];
+      unsigned char errorCode;
+
+      struct {
+        unsigned char length;
+        BaumInteger moduleIdentifier;
+        BaumInteger serialNumber;
+
+        union {
+          struct {
+            BaumInteger hardwareVersion;
+            BaumInteger firmwareVersion;
+            unsigned char event;
+          } PACKED registration;
+
+          union {
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              signed char wheels[4];
+              unsigned char buttons;
+              unsigned char keys;
+              unsigned char sensors[KEY_GROUP_SIZE(80)];
+            } PACKED display80;
+
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              signed char wheels[3];
+              unsigned char buttons;
+              unsigned char keys;
+              unsigned char sensors[KEY_GROUP_SIZE(64)];
+            } PACKED display64;
+
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              unsigned char buttons;
+            } PACKED status;
+
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              signed char wheel;
+              unsigned char buttons;
+              unsigned char keypad[2];
+            } PACKED phone;
+
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              signed char wheel;
+              unsigned char keys;
+              unsigned char pots[6];
+            } PACKED audio;
+
+            struct {
+              unsigned char flags;
+              unsigned char errors;
+              unsigned char buttons;
+              unsigned char cursor;
+              unsigned char keys;
+              unsigned char pots[4];
+            } PACKED voice;
+          } registers;
+        } data;
+      } PACKED modular;
+
+      char deviceIdentity[BAUM_LENGTH_DeviceIdentity];
+      char serialNumber[BAUM_LENGTH_SerialNumber];
+      char bluetoothName[BAUM_LENGTH_BluetoothName];
+    } PACKED values;
+  } PACKED data;
+} PACKED BaumResponsePacket;
+
+typedef enum {
+  BAUM_DEVICE_Default = 0,
+
+  BAUM_DEVICE_Refreshabraille,
+  BAUM_DEVICE_Orbit,
+  BAUM_DEVICE_NLS_Zoomax,
+  BAUM_DEVICE_B2G,
+
+  BAUM_DEVICE_Conny,
+  BAUM_DEVICE_PocketVario,
+  BAUM_DEVICE_Pronto,
+  BAUM_DEVICE_SuperVario,
+  BAUM_DEVICE_VarioConnect,
+  BAUM_DEVICE_VarioUltra,
+
+  BAUM_DEVICE_Inka,
+  BAUM_DEVICE_DM80P,
+  BAUM_DEVICE_Vario40,
+  BAUM_DEVICE_Vario80,
+  BAUM_DEVICE_Modular
+} BaumDeviceType;
+
+typedef struct {
+  const char *string;
+  BaumDeviceType type;
+} BaumDeviceIdentityEntry;
+
+static const BaumDeviceIdentityEntry baumDeviceIdentityTable[] = {
+  { .string = "Refreshabraille",
+    .type = BAUM_DEVICE_Refreshabraille
+  },
+
+  { .string = "Orbit",
+    .type = BAUM_DEVICE_Orbit
+  },
+
+  { .string = "NLS eReader Zoomax",
+    .type = BAUM_DEVICE_NLS_Zoomax
+  },
+
+  { .string = "Conny (NBP B2G)",
+    .type = BAUM_DEVICE_B2G
+  },
+
+  { .string = "BrailleConnect",
+    .type = BAUM_DEVICE_VarioConnect
+  },
+
+  { .string = "Brailliant",
+    .type = BAUM_DEVICE_SuperVario
+  },
+
+  { .string = "Conny",
+    .type = BAUM_DEVICE_Conny
+  },
+
+  { .string = "PocketVario",
+    .type = BAUM_DEVICE_PocketVario
+  },
+
+  { .string = "Pronto",
+    .type = BAUM_DEVICE_Pronto
+  },
+
+  { .string = "SuperVario",
+    .type = BAUM_DEVICE_SuperVario
+  },
+
+  { .string = "SVario",
+    .type = BAUM_DEVICE_SuperVario
+  },
+
+  { .string = "Vario 40",
+    .type = BAUM_DEVICE_Vario40
+  },
+
+  { .string = "VarioConnect",
+    .type = BAUM_DEVICE_VarioConnect
+  },
+
+  { .string = "VarioUltra",
+    .type = BAUM_DEVICE_VarioUltra
+  },
+};
+
+static const unsigned char baumDeviceIdentityCount = ARRAY_COUNT(baumDeviceIdentityTable);
+static BaumDeviceType baumDeviceType;
+
+static void
+setBaumDeviceType (const char *identity, size_t size) {
+  const BaumDeviceIdentityEntry *bdi = baumDeviceIdentityTable;
+  const BaumDeviceIdentityEntry *end = bdi + baumDeviceIdentityCount;
+
+  while (bdi < end) {
+    size_t length = strlen(bdi->string);
+    const char *from = identity;
+    const char *to = from + size - length;
+
+    while (from <= to) {
+      if (*from == *bdi->string) {
+        if (memcmp(from, bdi->string, length) == 0) {
+          baumDeviceType = bdi->type;
+          return;
+        }
+      }
+
+      from += 1;
+    }
+
+    bdi += 1;
+  }
+}
+
+typedef enum {
+  BAUM_MODULE_Display80,
+  BAUM_MODULE_Display64,
+  BAUM_MODULE_Status,
+  BAUM_MODULE_Phone,
+  BAUM_MODULE_Audio,
+  BAUM_MODULE_Voice
+} BaumModuleType;
+
+typedef struct {
+  uint16_t identifier;
+  unsigned char type;
+  unsigned char cellCount;
+  unsigned char keyCount;
+  unsigned char buttonCount;
+  unsigned char wheelCount;
+  unsigned char potCount;
+  unsigned isDisplay:1;
+  unsigned hasCursorKeys:1;
+  unsigned hasKeypad:1;
+} BaumModuleDescription;
+
+static const BaumModuleDescription baumModuleDescriptions[] = {
+  { .identifier = 0X4180,
+    .type = BAUM_MODULE_Display80,
+    .cellCount = 80,
+    .wheelCount = 4,
+    .isDisplay = 1
+  }
+  ,
+  { .identifier = 0X4181,
+    .type = BAUM_MODULE_Display64,
+    .cellCount = 64,
+    .wheelCount = 3,
+    .isDisplay = 1
+  }
+  ,
+  { .identifier = 0X4190,
+    .type = BAUM_MODULE_Status,
+    .cellCount = 4,
+    .buttonCount = 4
+  }
+  ,
+  { .identifier = 0X4191,
+    .type = BAUM_MODULE_Phone,
+    .cellCount = 12,
+    .buttonCount = 4,
+    .wheelCount = 1,
+    .hasKeypad = 1
+  }
+  ,
+  { .identifier = 0X4192,
+    .type = BAUM_MODULE_Audio,
+    .keyCount = 5,
+    .wheelCount = 1,
+    .potCount = 6
+  }
+  ,
+  { .identifier = 0X4193,
+    .type = BAUM_MODULE_Voice,
+    .keyCount = 4,
+    .buttonCount = 3,
+    .potCount = 4,
+    .hasCursorKeys = 1
+  }
+  ,
+  { .identifier = 0 }
+};
+
+static const BaumModuleDescription *
+getBaumModuleDescription (uint16_t identifier) {
+  const BaumModuleDescription *bmd = baumModuleDescriptions;
+
+  while (bmd->identifier) {
+    if (bmd->identifier == identifier) return bmd;
+    bmd += 1;
+  }
+
+  logMessage(LOG_DEBUG, "unknown module identifier: %04X", identifier);
+  return NULL;
+}
+
+typedef struct {
+  const BaumModuleDescription *description;
+  uint16_t serialNumber;
+  uint16_t hardwareVersion;
+  uint16_t firmwareVersion;
+} BaumModuleRegistration;
+
+static void
+clearBaumModuleRegistration (BaumModuleRegistration *bmr) {
+  bmr->description = NULL;
+  bmr->serialNumber = 0;
+  bmr->hardwareVersion = 0;
+  bmr->firmwareVersion = 0;
+}
+
+static BaumModuleRegistration baumDisplayModule;
+static BaumModuleRegistration baumStatusModule;
+
+static BaumModuleRegistration *const baumModules[] = {
+  &baumDisplayModule,
+  &baumStatusModule,
+  NULL
+};
+
+static BaumModuleRegistration *
+getBaumModuleRegistration (const BaumModuleDescription *bmd, uint16_t serialNumber) {
+  if (bmd) {
+    BaumModuleRegistration *const *bmr = baumModules;
+
+    while (*bmr) {
+      if (((*bmr)->description == bmd) && ((*bmr)->serialNumber == serialNumber)) return *bmr;
+      bmr += 1;
+    }
+  }
+
+  return NULL;
+}
+
+static int
+getBaumModuleCellCount (void) {
+  int count = 0;
+
+  {
+    BaumModuleRegistration *const *bmr = baumModules;
+
+    while (*bmr) {
+      const BaumModuleDescription *bmd = (*bmr++)->description;
+      if (bmd) count += bmd->cellCount;
+    }
+  }
+
+  return count;
+}
+
+static void
+assumeBaumDeviceIdentity (const char *identity) {
+  logMessage(LOG_INFO, "Baum Device Identity: %s", identity);
+}
+
+static void
+handleBaumDeviceIdentity (const BaumResponsePacket *packet, int probing) {
+  const char *identity = packet->data.values.deviceIdentity;
+  size_t size = sizeof(packet->data.values.deviceIdentity);
+
+  logTextField("Baum Device Identity", identity, size);
+  if (probing) setBaumDeviceType(identity, size);
+}
+
+static void
+logBaumSerialNumber (const BaumResponsePacket *packet) {
+  logTextField("Baum Serial Number",
+               packet->data.values.serialNumber,
+               sizeof(packet->data.values.serialNumber));
+}
+
+static int
+logBaumPowerdownReason (BaumPowerdownReason reason) {
+  typedef struct {
+    BaumPowerdownReason bit;
+    const char *explanation;
+  } ReasonEntry;
+
+  static const ReasonEntry reasonTable[] = {
+    {BAUM_PDR_ProtocolRequested, strtext("driver request")},
+    {BAUM_PDR_PowerSwitch      , strtext("power switch")},
+    {BAUM_PDR_AutoPowerOff     , strtext("idle timeout")},
+    {BAUM_PDR_BatteryLow       , strtext("battery low")},
+    {0}
+  };
+
+  char buffer[0X100];
+  char delimiter = ':';
+  int length;
+
+  STR_BEGIN(buffer, sizeof(buffer));
+  STR_PRINTF("%s %s", STRINGIFY(DRIVER_NAME), gettext("Powerdown"));
+
+  for (const ReasonEntry *entry=reasonTable; entry->bit; entry+=1) {
+    if (reason & entry->bit) {
+      STR_PRINTF("%c %s", delimiter, gettext(entry->explanation));
+      delimiter = ',';
+    }
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+
+  logMessage(LOG_WARNING, "%.*s", length, buffer);
+  return 1;
+}
+
+static void
+adjustPacketLength (const unsigned char *bytes, size_t size, size_t *length) {
+  switch (bytes[0]) {
+    case BAUM_RSP_DeviceIdentity:
+      if (size == 17) {
+        if (memcmp(&bytes[1], "Refreshabraille ", (size - 1)) == 0) {
+          *length += 2;
+        } else if (memcmp(&bytes[1], "NLS eReader Zoom", (size - 1)) == 0) {
+          *length += 2;
+        }
+      }
+      break;
+
+    default:
+      break;
+  }
+}
+
+typedef enum {
+  BAUM_PVS_WAITING,
+  BAUM_PVS_STARTED,
+  BAUM_PVS_ESCAPED
+} BaumPacketVerificationState;
+
+typedef struct {
+  BaumPacketVerificationState state;
+} BaumPacketVerificationData;
+
+static BraillePacketVerifierResult
+verifyBaumPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  BaumPacketVerificationData *pvd = data;
+  unsigned char byte = bytes[size-1];
+  int escape = byte == ASCII_ESC;
+
+  switch (pvd->state) {
+    case BAUM_PVS_WAITING:
+      if (!escape) return BRL_PVR_INVALID;
+      pvd->state = BAUM_PVS_STARTED;
+      return BRL_PVR_EXCLUDE;
+
+    case BAUM_PVS_STARTED:
+      if (escape) {
+        pvd->state = BAUM_PVS_ESCAPED;
+        return BRL_PVR_EXCLUDE;
+      }
+      break;
+
+    case BAUM_PVS_ESCAPED:
+      pvd->state = BAUM_PVS_STARTED;
+      break;
+
+    default:
+      logMessage(LOG_NOTICE, "unexpected %s packet verification state: %u",
+                 brl->data->protocol->name, pvd->state);
+      return BRL_PVR_INVALID;
+  }
+
+  if (size == 1) {
+    switch (byte) {
+      case BAUM_RSP_Switches:
+        if (!cellCount) {
+          assumeBaumDeviceIdentity("DM80P");
+          baumDeviceType = BAUM_DEVICE_DM80P;
+          cellCount = 84;
+        }
+
+      case BAUM_RSP_CellCount:
+      case BAUM_RSP_VersionNumber:
+      case BAUM_RSP_CommunicationChannel:
+      case BAUM_RSP_PowerdownSignal:
+      case BAUM_RSP_DisplayKeys:
+      case BAUM_RSP_HorizontalSensor:
+      case BAUM_RSP_RoutingKey:
+      case BAUM_RSP_Front6:
+      case BAUM_RSP_Back6:
+      case BAUM_RSP_CommandKeys:
+      case BAUM_RSP_Joystick:
+      case BAUM_RSP_ErrorCode:
+      case BAUM_RSP_ModuleRegistration:
+      case BAUM_RSP_DataRegisters:
+      case BAUM_RSP_ServiceRegisters:
+        *length = 2;
+        break;
+
+      case BAUM_RSP_ModeSetting:
+      case BAUM_RSP_Front10:
+      case BAUM_RSP_Back10:
+      case BAUM_RSP_EntryKeys:
+        *length = 3;
+        break;
+
+      case BAUM_RSP_VerticalSensor:
+        *length = (baumDeviceType == BAUM_DEVICE_Inka)? 2: 3;
+        break;
+
+      case BAUM_RSP_VerticalSensors:
+      case BAUM_RSP_SerialNumber:
+        *length = 9;
+        break;
+
+      case BAUM_RSP_BluetoothName:
+        *length = 15;
+        break;
+
+      case BAUM_RSP_DeviceIdentity:
+        *length = 17;
+        break;
+
+      case BAUM_RSP_RoutingKeys:
+        if (!cellCount) {
+          assumeBaumDeviceIdentity("Inka");
+          baumDeviceType = BAUM_DEVICE_Inka;
+          cellCount = 56;
+        }
+
+        if (baumDeviceType == BAUM_DEVICE_Inka) {
+          *length = 2;
+          break;
+        }
+
+        *length = brl->data->packetSize.routingKeys + 1;
+        break;
+
+      case BAUM_RSP_HorizontalSensors:
+        *length = KEY_GROUP_SIZE(brl->textColumns) + 1;
+        break;
+
+      case BAUM_RSP_NLS_ZMX_BD:
+      case BAUM_RSP_NLS_ZMX_BE:
+        *length = 2;
+        break;
+
+      case BAUM_RSP_NLS_ZMX_BF:
+        *length = 2;
+        break;
+
+      default:
+        pvd->state = BAUM_PVS_WAITING;
+        return BRL_PVR_INVALID;
+    }
+  } else if (size == 2) {
+    switch (bytes[0]) {
+      case BAUM_RSP_ModuleRegistration:
+      case BAUM_RSP_DataRegisters:
+      case BAUM_RSP_ServiceRegisters:
+        if (byte < 4) return BRL_PVR_INVALID;
+        *length += byte;
+        break;
+
+      case BAUM_RSP_NLS_ZMX_BD:
+      case BAUM_RSP_NLS_ZMX_BE:
+        if (byte != ASCII_CR) return BRL_PVR_EXCLUDE;
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  adjustPacketLength(bytes, size, length);
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+readBaumPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  BaumPacketVerificationData pvd = {
+    .state = BAUM_PVS_WAITING
+  };
+
+  memset(packet, 0, size);
+  return readBraillePacket(brl, NULL, packet, size, verifyBaumPacket, &pvd);
+}
+
+static int
+getBaumPacket (BrailleDisplay *brl, BaumResponsePacket *packet) {
+  return readBaumPacket(brl, packet->bytes, sizeof(*packet));
+}
+
+static int
+writeBaumPacket (BrailleDisplay *brl, const unsigned char *packet, int length) {
+  unsigned char buffer[1 + (length * 2)];
+  unsigned char *byte = buffer;
+  *byte++ = ASCII_ESC;
+
+  {
+    int index = 0;
+    while (index < length)
+      if ((*byte++ = packet[index++]) == ASCII_ESC)
+        *byte++ = ASCII_ESC;
+  }
+
+  return writeBraillePacket(brl, NULL, buffer, (byte - buffer));
+}
+
+static int
+writeBaumModuleRegistrationCommand (
+  BrailleDisplay *brl,
+  uint16_t moduleIdentifier, uint16_t serialNumber,
+  BaumModuleRegistrationCommand command
+) {
+  const unsigned char request[] = {
+    BAUM_REQ_ModuleRegistration,
+    5, /* data length */
+    MAKE_BAUM_INTEGER(moduleIdentifier),
+    MAKE_BAUM_INTEGER(serialNumber),
+    command
+  };
+
+  return writeBaumPacket(brl, request, sizeof(request));
+}
+
+static int
+writeBaumDataRegisters (
+  BrailleDisplay *brl,
+  const BaumModuleRegistration *bmr,
+  const unsigned char *registers,
+  unsigned char start, unsigned char count
+) {
+  const BaumModuleDescription *bmd = bmr->description;
+
+  if (bmd) {
+    if (count < bmd->cellCount) count = bmd->cellCount;
+
+    if (count) {
+      unsigned char packet[2 + 7 + count];
+      unsigned char *byte = packet;
+
+      *byte++ = BAUM_REQ_DataRegisters;
+      *byte++ = 7 + count;
+
+      *byte++ = MAKE_BAUM_INTEGER_FIRST(bmd->identifier);
+      *byte++ = MAKE_BAUM_INTEGER_SECOND(bmd->identifier);
+
+      *byte++ = MAKE_BAUM_INTEGER_FIRST(bmr->serialNumber);
+      *byte++ = MAKE_BAUM_INTEGER_SECOND(bmr->serialNumber);
+
+      *byte++ = BAUM_DRC_Write;
+      *byte++ = start;
+      *byte++ = count;
+      byte = mempcpy(byte, registers, count);
+
+      if (!writeBaumPacket(brl, packet, byte-packet)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+typedef struct {
+  const KeyTableDefinition *keyTableDefinition;
+  int (*writeAllCells) (BrailleDisplay *brl);
+  int (*writeCellRange) (BrailleDisplay *brl, unsigned int start, unsigned int count);
+} BaumDeviceOperations;
+
+static int
+writeBaumCells_all (BrailleDisplay *brl) {
+  unsigned char packet[1 + cellCount];
+  unsigned char *byte = packet;
+
+  *byte++ = BAUM_REQ_DisplayData;
+  byte = mempcpy(byte, externalCells, cellCount);
+
+  return writeBaumPacket(brl, packet, byte-packet);
+}
+
+static int
+writeBaumCells_start (BrailleDisplay *brl) {
+  unsigned char packet[1 + 1 + cellCount];
+  unsigned char *byte = packet;
+
+  *byte++ = BAUM_REQ_DisplayData;
+  *byte++ = 0;
+  byte = mempcpy(byte, externalCells, cellCount);
+
+  return writeBaumPacket(brl, packet, byte-packet);
+}
+
+static int
+writeBaumCells_modular (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  if (start < brl->textColumns) {
+    unsigned int amount = MIN(count, brl->textColumns-start);
+
+    if (amount > 0) {
+      if (!writeBaumDataRegisters(brl, &baumDisplayModule, &externalCells[start], start, amount)) return 0;
+      start += amount;
+      count -= amount;
+    }
+  }
+
+  if (count > 0) {
+    if (!writeBaumDataRegisters(brl, &baumStatusModule, &externalCells[start], start-brl->textColumns, count)) return 0;
+  }
+
+  return 1;
+}
+
+static const BaumDeviceOperations baumDeviceOperations[] = {
+  [BAUM_DEVICE_Default] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(default),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Refreshabraille] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(rb),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Orbit] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(orbit),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_NLS_Zoomax] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(NLS_Zoomax),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_B2G] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(b2g),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Conny] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(conny),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_PocketVario] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(pv),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Pronto] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(pronto),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_SuperVario] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(sv),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_VarioConnect] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(connect),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_VarioUltra] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(ultra),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Inka] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(inka),
+    .writeAllCells = writeBaumCells_start
+  },
+
+  [BAUM_DEVICE_DM80P] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(dm80p),
+    .writeAllCells = writeBaumCells_start
+  },
+
+  [BAUM_DEVICE_Vario40] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(v40),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Vario80] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(v80),
+    .writeAllCells = writeBaumCells_all
+  },
+
+  [BAUM_DEVICE_Modular] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(pro),
+    .writeCellRange = writeBaumCells_modular
+  }
+};
+
+static int
+setBaumMode (BrailleDisplay *brl, unsigned char mode, unsigned char setting) {
+  const unsigned char request[] = {BAUM_REQ_SetMode, mode, setting};
+  return writeBaumPacket(brl, request, sizeof(request));
+}
+
+static void
+setBaumSwitches (BrailleDisplay *brl, unsigned char newSettings, int initialize) {
+  unsigned char changedSettings = newSettings ^ switchSettings;
+  switchSettings = newSettings;
+
+  {
+    typedef struct {
+      unsigned char switchBit;
+      unsigned char modeNumber;
+      unsigned char offValue;
+      unsigned char onValue;
+    } SwitchEntry;
+
+    static const SwitchEntry switchTable[] = {
+      {BAUM_SWT_ShowSensor, 0X01, 0, 2},
+      {BAUM_SWT_BrailleKeyboard, 0X03, 0, 3},
+      {0}
+    };
+    const SwitchEntry *entry = switchTable;
+
+    while (entry->switchBit) {
+      if (initialize || (changedSettings & entry->switchBit))
+        setBaumMode(brl, entry->modeNumber,
+                    ((switchSettings & entry->switchBit)? entry->onValue:
+                                                          entry->offValue));
+      ++entry;
+    }
+  }
+}
+
+static void
+setInkaSwitches (BrailleDisplay *brl, unsigned char newSettings, int initialize) {
+  newSettings ^= 0X0F;
+  setBaumSwitches(brl, ((newSettings & 0X03) | ((newSettings & 0X0C) << 4)), initialize);
+}
+
+static int
+handleBaumModuleRegistrationEvent (BrailleDisplay *brl, const BaumResponsePacket *packet) {
+  uint16_t moduleIdentifier = getBaumInteger(packet->data.values.modular.moduleIdentifier);
+  uint16_t serialNumber = getBaumInteger(packet->data.values.modular.serialNumber);
+  const BaumModuleDescription *bmd = getBaumModuleDescription(moduleIdentifier);
+
+  if (packet->data.values.modular.data.registration.event == BAUM_MRE_Addition) {
+    if (!writeBaumModuleRegistrationCommand(brl,
+                                            moduleIdentifier, serialNumber,
+                                            BAUM_MRC_Acknowledge)) {
+      return 0;
+    }
+
+    if (bmd) {
+      BaumModuleRegistration *bmr;
+
+      if (bmd->isDisplay) {
+        bmr = &baumDisplayModule;
+      } else if (bmd->type == BAUM_MODULE_Status) {
+        bmr = &baumStatusModule;
+      } else {
+        bmr = NULL;
+      }
+
+      if (bmr) {
+        if (bmr->description) clearBaumModuleRegistration(bmr);
+
+        bmr->description = bmd;
+        bmr->serialNumber = serialNumber;
+        bmr->hardwareVersion = getBaumInteger(packet->data.values.modular.data.registration.hardwareVersion);
+        bmr->firmwareVersion = getBaumInteger(packet->data.values.modular.data.registration.firmwareVersion);
+      }
+    }
+  } else {
+    BaumModuleRegistration *bmr = getBaumModuleRegistration(bmd, serialNumber);
+    if (bmr) clearBaumModuleRegistration(bmr);
+  }
+
+  return 1;
+}
+
+static void
+handleBaumDataRegistersEvent (BrailleDisplay *brl, const BaumResponsePacket *packet) {
+  const BaumModuleDescription *bmd = getBaumModuleDescription(getBaumInteger(packet->data.values.modular.moduleIdentifier));
+  const BaumModuleRegistration *bmr = getBaumModuleRegistration(bmd, getBaumInteger(packet->data.values.modular.serialNumber));
+
+  if (bmr) {
+    switch (bmd->type) {
+      {
+        unsigned char flags;
+        unsigned char UNUSED errors;
+        const signed char *wheel;
+        unsigned char wheels;
+        unsigned char buttons;
+        unsigned char keys;
+        const unsigned char *sensors;
+
+      case BAUM_MODULE_Display80:
+        flags = packet->data.values.modular.data.registers.display80.flags;
+        errors = packet->data.values.modular.data.registers.display80.errors;
+        wheel = packet->data.values.modular.data.registers.display80.wheels;
+        wheels = ARRAY_COUNT(packet->data.values.modular.data.registers.display80.wheels);
+        buttons = packet->data.values.modular.data.registers.display80.buttons;
+        keys = packet->data.values.modular.data.registers.display80.keys;
+        sensors = packet->data.values.modular.data.registers.display80.sensors;
+        goto doDisplay;
+
+      case BAUM_MODULE_Display64:
+        flags = packet->data.values.modular.data.registers.display64.flags;
+        errors = packet->data.values.modular.data.registers.display64.errors;
+        wheel = packet->data.values.modular.data.registers.display64.wheels;
+        wheels = ARRAY_COUNT(packet->data.values.modular.data.registers.display64.wheels);
+        buttons = packet->data.values.modular.data.registers.display64.buttons;
+        keys = packet->data.values.modular.data.registers.display64.keys;
+        sensors = packet->data.values.modular.data.registers.display64.sensors;
+        goto doDisplay;
+
+      doDisplay:
+        if (flags & BAUM_DRF_WheelsChanged) {
+          unsigned int index;
+
+          for (index=0; index<wheels; index+=1) {
+            signed char count = wheel[index];
+
+            while (count > 0) {
+              enqueueKey(brl, BM_GRP_NavigationKeys, (BM_KEY_WHEEL_UP + index));
+              count -= 1;
+            }
+
+            while (count < 0) {
+              enqueueKey(brl, BM_GRP_NavigationKeys, (BM_KEY_WHEEL_DOWN + index));
+              count += 1;
+            }
+          }
+        }
+
+        if (flags & BAUM_DRF_ButtonsChanged) {
+          updateNavigationKeys(brl, &buttons, BM_KEY_WHEEL_PRESS, wheels);
+        }
+
+        if (flags & BAUM_DRF_KeysChanged) {
+          updateDisplayKeys(brl, keys);
+        }
+
+        if (flags & BAUM_DRF_SensorsChanged) {
+          updateRoutingKeys(brl, sensors, brl->textColumns);
+        }
+
+        break;
+      }
+
+      case BAUM_MODULE_Status:
+        if (packet->data.values.modular.data.registers.status.flags & BAUM_DRF_ButtonsChanged) {
+          updateNavigationKeys(brl, &packet->data.values.modular.data.registers.status.buttons,
+                               BM_KEY_STATUS, BM_KEYS_STATUS);
+        }
+
+        break;
+
+      default:
+        logMessage(LOG_WARNING, "unsupported data register configuration: %u", bmd->type);
+        break;
+    }
+  }
+}
+
+static int
+getIdentityCellCount (char* deviceIdentity, const int length) {
+  char buffer[length+1];
+  memcpy(buffer, deviceIdentity, length);
+  buffer[length] = 0;
+
+  char *digits = strpbrk(buffer, "123456789");
+
+  if (digits) {
+    int count = atoi(digits);
+    if (isAcceptableCellCount(count)) return count;
+  }
+
+  return 0;
+}
+
+static int
+probeBaumDevice (BrailleDisplay *brl) {
+  int probes = 0;
+
+  do {
+    int identityCellCount = 0;
+
+    baumDeviceType = BAUM_DEVICE_Default;
+    cellCount = 0;
+
+    {
+      BaumModuleRegistration *const *bmr = baumModules;
+      while (*bmr) clearBaumModuleRegistration(*bmr++);
+    }
+
+    /* get the serial number for the log */
+    {
+      static const unsigned char request[] = {BAUM_REQ_GetSerialNumber};
+      if (!writeBaumPacket(brl, request, sizeof(request))) break;
+    }
+
+    /* newer models return an identity string which contains the cell count */
+    {
+      static const unsigned char request[] = {BAUM_REQ_GetDeviceIdentity};
+      if (!writeBaumPacket(brl, request, sizeof(request))) break;
+    }
+
+    /* try explicitly asking for the cell count */
+    {
+      static const unsigned char request[] = {BAUM_REQ_DisplayData, 0};
+      if (!writeBaumPacket(brl, request, sizeof(request))) break;
+    }
+
+    /* enqueue a request to get the initial key states */
+    {
+      static const unsigned char request[] = {BAUM_REQ_GetKeys};
+      if (!writeBaumPacket(brl, request, sizeof(request))) break;
+    }
+
+    /* the modular models need to be probed with a general call */
+    if (!writeBaumModuleRegistrationCommand(brl, 0, 0, BAUM_MRC_Query)) break;
+
+    while (awaitBrailleInput(brl, probeTimeout)) {
+      BaumResponsePacket response;
+      int size = getBaumPacket(brl, &response);
+
+      if (size) {
+        switch (response.data.code) {
+          case BAUM_RSP_VersionNumber:
+            continue;
+
+          case BAUM_RSP_RoutingKeys: /* Inka */
+            setInkaSwitches(brl, response.data.values.switches, 1);
+            return 1;
+
+          case BAUM_RSP_Switches: /* DM80P */
+            setBaumSwitches(brl, response.data.values.switches, 1);
+            return 1;
+
+          case BAUM_RSP_CellCount: { /* newer models */
+            unsigned char count = response.data.values.cellCount;
+
+            if (isAcceptableCellCount(count)) {
+              cellCount = count;
+              return 1;
+            }
+
+            logUnexpectedCellCount(count);
+            continue;
+          }
+
+          case BAUM_RSP_ModuleRegistration: /* modular models */
+            if (!handleBaumModuleRegistrationEvent(brl, &response)) return 0;
+            if (!baumDisplayModule.description) continue;
+            baumDeviceType = BAUM_DEVICE_Modular;
+            cellCount = getBaumModuleCellCount();
+            return 1;
+
+          case BAUM_RSP_DeviceIdentity: {
+            /* should contain fallback cell count */
+            int count = getIdentityCellCount(response.data.values.deviceIdentity,
+                                             sizeof(response.data.values.deviceIdentity));
+            if (count) identityCellCount = count;
+            handleBaumDeviceIdentity(&response, 1);
+            continue;
+          }
+
+          case BAUM_RSP_SerialNumber:
+            logBaumSerialNumber(&response);
+            continue;
+
+          case BAUM_RSP_ErrorCode:
+            if (response.data.values.errorCode != BAUM_ERR_PacketType) goto unexpectedPacket;
+            logMessage(LOG_DEBUG, "unsupported request");
+            continue;
+
+          default:
+          unexpectedPacket:
+            logUnexpectedPacket(response.bytes, size);
+            continue;
+        }
+      } else if (errno != EAGAIN) {
+        break;
+      }
+    }
+    if (errno != EAGAIN) break;
+
+    if (identityCellCount) {
+      /* Older models don't provide the actual cell count
+       * so it must be derived from the identity string.
+       */
+      switch ((cellCount = identityCellCount)) {
+        case 80: /* probably a Vario 80 */
+          baumDeviceType = BAUM_DEVICE_Vario80;
+          cellCount += 4;
+          break;
+      }
+
+      return 1;
+    }
+  } while (++probes < probeLimit);
+
+  return 0;
+}
+
+static void
+processBaumPackets (BrailleDisplay *brl) {
+  BaumResponsePacket packet;
+  int size;
+
+  while ((size = getBaumPacket(brl, &packet))) {
+    switch (packet.data.code) {
+      case BAUM_RSP_CellCount:
+        if (!changeCellCount(brl, packet.data.values.cellCount)) return;
+        continue;
+
+      case BAUM_RSP_DeviceIdentity:
+        handleBaumDeviceIdentity(&packet, 0);
+        continue;
+
+      case BAUM_RSP_SerialNumber:
+        logBaumSerialNumber(&packet);
+        continue;
+
+      case BAUM_RSP_CommunicationChannel:
+        continue;
+
+      case BAUM_RSP_PowerdownSignal:
+        if (!logBaumPowerdownReason(packet.data.values.powerdownReason)) continue;
+        errno = ENODEV;
+        return;
+
+      case BAUM_RSP_DisplayKeys: {
+        unsigned char keys;
+        unsigned char UNUSED count = 6;
+
+        switch (baumDeviceType) {
+          case BAUM_DEVICE_Inka:
+            keys = 0;
+#define KEY(inka,baum) if (!(packet.data.values.displayKeys & (inka))) keys |= (baum)
+            KEY(004, 001);
+            KEY(002, 002);
+            KEY(001, 004);
+            KEY(040, 010);
+            KEY(020, 020);
+            KEY(010, 040);
+#undef KEY
+            break;
+
+          case BAUM_DEVICE_DM80P:
+            keys = packet.data.values.displayKeys ^ 0X7F;
+            count = 7;
+            break;
+
+          case BAUM_DEVICE_Orbit:
+            count = 8;
+            /* fall through */
+
+          default:
+            keys = packet.data.values.displayKeys;
+            break;
+        }
+
+        updateDisplayKeys(brl, keys);
+        continue;
+      }
+
+      case BAUM_RSP_CommandKeys:
+        updateNavigationKeys(brl, packet.data.values.commandKeys,
+                             BM_KEY_COMMAND, BM_KEYS_COMMAND);
+        continue;
+
+      case BAUM_RSP_Front6:
+        updateNavigationKeys(brl, packet.data.values.front6,
+                             BM_KEY_FRONT, 6);
+        continue;
+
+      case BAUM_RSP_Back6:
+        updateNavigationKeys(brl, packet.data.values.back6,
+                             BM_KEY_BACK, 6);
+        continue;
+
+      case BAUM_RSP_Front10: {
+        unsigned char keys[2];
+        keys[0] = packet.data.values.front10[1];
+        keys[1] = packet.data.values.front10[0];
+        updateNavigationKeys(brl, keys, BM_KEY_FRONT, 10);
+        continue;
+      }
+
+      case BAUM_RSP_Back10: {
+        unsigned char keys[2];
+        keys[0] = packet.data.values.back10[1];
+        keys[1] = packet.data.values.back10[0];
+        updateNavigationKeys(brl, keys, BM_KEY_BACK, 10);
+        continue;
+      }
+
+      case BAUM_RSP_EntryKeys:
+        updateEntryKeys(brl, packet.data.values.entryKeys);
+        continue;
+
+      case BAUM_RSP_Joystick:
+        updateJoystick(brl, packet.data.values.joystick);
+        continue;
+
+      case BAUM_RSP_HorizontalSensor:
+        resetKeyGroup(packet.data.values.horizontalSensors, brl->textColumns, packet.data.values.horizontalSensor);
+      case BAUM_RSP_HorizontalSensors:
+        if (!(switchSettings & BAUM_SWT_DisableSensors)) {
+          updateKeyGroup(brl, keysState.horizontalSensors, packet.data.values.horizontalSensors,
+                         BM_GRP_HorizontalSensors, 0, brl->textColumns, 0);
+        }
+        continue;
+
+      case BAUM_RSP_VerticalSensor: {
+        unsigned char left = packet.data.values.verticalSensor.left;
+        unsigned char right;
+
+        if (baumDeviceType != BAUM_DEVICE_Inka) {
+          right = packet.data.values.verticalSensor.right;
+        } else if (left & 0X40) {
+          left -= 0X40;
+          right = 0;
+        } else {
+          right = left;
+          left = 0;
+        }
+
+        resetKeyGroup(packet.data.values.verticalSensors.left, VERTICAL_SENSOR_COUNT, left);
+        resetKeyGroup(packet.data.values.verticalSensors.right, VERTICAL_SENSOR_COUNT, right);
+      }
+
+      case BAUM_RSP_VerticalSensors:
+        if (!(switchSettings & BAUM_SWT_DisableSensors)) {
+          int scaled = (switchSettings & BAUM_SWT_ScaledVertical) != 0;
+
+          updateKeyGroup(brl, keysState.leftSensors, packet.data.values.verticalSensors.left,
+                         (scaled? BM_GRP_ScaledLeftSensors: BM_GRP_LeftSensors),
+                         0, VERTICAL_SENSOR_COUNT, scaled);
+          updateKeyGroup(brl, keysState.rightSensors, packet.data.values.verticalSensors.right,
+                         (scaled? BM_GRP_ScaledRightSensors: BM_GRP_RightSensors),
+                         0, VERTICAL_SENSOR_COUNT, scaled);
+        }
+        continue;
+
+      case BAUM_RSP_RoutingKey:
+        resetKeyGroup(packet.data.values.routingKeys, cellCount, packet.data.values.routingKey);
+        goto doRoutingKeys;
+
+      case BAUM_RSP_RoutingKeys:
+        if (baumDeviceType == BAUM_DEVICE_Inka) {
+          setInkaSwitches(brl, packet.data.values.switches, 0);
+          continue;
+        }
+
+      doRoutingKeys:
+        updateRoutingKeys(brl, packet.data.values.routingKeys, cellCount);
+        continue;
+
+      case BAUM_RSP_Switches:
+        setBaumSwitches(brl, packet.data.values.switches, 0);
+        continue;
+
+      case BAUM_RSP_ModuleRegistration:
+        if (handleBaumModuleRegistrationEvent(brl, &packet)) {
+        }
+
+        if (!changeCellCount(brl, getBaumModuleCellCount())) return;
+        continue;
+
+      case BAUM_RSP_DataRegisters:
+        handleBaumDataRegistersEvent(brl, &packet);
+        continue;
+
+      case BAUM_RSP_ErrorCode:
+        if (packet.data.values.errorCode != BAUM_ERR_PacketType) goto unexpectedPacket;
+        logMessage(LOG_DEBUG, "unsupported request");
+        continue;
+
+      case BAUM_RSP_NLS_ZMX_BD:
+      case BAUM_RSP_NLS_ZMX_BE:
+      case BAUM_RSP_NLS_ZMX_BF:
+        continue;
+
+      default:
+      unexpectedPacket:
+        logUnexpectedPacket(packet.bytes, size);
+        continue;
+    }
+  }
+}
+
+static int
+writeBaumCells (BrailleDisplay *brl) {
+  const BaumDeviceOperations *bdo = &baumDeviceOperations[baumDeviceType];
+  if (!bdo->writeAllCells) return 1;
+  return bdo->writeAllCells(brl);
+}
+
+static int
+writeBaumCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  const BaumDeviceOperations *bdo = &baumDeviceOperations[baumDeviceType];
+  if (!bdo->writeCellRange) return 1;
+  return bdo->writeCellRange(brl, start, count);
+}
+
+static const ProtocolOperations baumEscapeOperations = {
+  .name = "Baum Escape",
+  .dotsTable = &dotsTable_ISO11548_1,
+
+  .serialBaud = 19200,
+  .serialParity = SERIAL_PARITY_NONE,
+
+  .readPacket = readBaumPacket,
+  .writePacket = writeBaumPacket,
+
+  .probeDevice = probeBaumDevice,
+  .processPackets = processBaumPackets,
+
+  .writeCells = writeBaumCells,
+  .writeCellRange = writeBaumCellRange
+};
+
+/* HID Protocol */
+
+typedef union {
+  unsigned char bytes[0];
+
+  struct {
+    unsigned char type;
+
+    union {
+      unsigned char cellCount[16];
+      unsigned char routingKeys[16];
+      unsigned char displayKeys[16];
+      unsigned char routingKey[16];
+      unsigned char entryKeys[16];
+      unsigned char joystick[16];
+      char deviceIdentity[BAUM_LENGTH_DeviceIdentity];
+      char serialNumber[BAUM_LENGTH_SerialNumber];
+    } data;
+  } PACKED fields;
+} HidResponsePacket;
+
+typedef struct {
+  struct {
+    const unsigned char *table;
+    unsigned char count;
+  } const packetLengths;
+} HidPacketVerificationData;
+
+static BraillePacketVerifierResult
+verifyHidPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  HidPacketVerificationData *pvd = data;
+  unsigned char byte = bytes[size-1];
+
+  if (size == 1) {
+    if (byte < pvd->packetLengths.count) {
+      unsigned char l = pvd->packetLengths.table[byte];
+
+      if (l) {
+        *length = l;
+        return BRL_PVR_INCLUDE;
+      }
+    }
+
+    if (!cellCount) return BRL_PVR_INVALID;
+
+    switch (byte) {
+      case BAUM_RSP_RoutingKeys:
+        *length = brl->data->packetSize.routingKeys + 1;
+        break;
+
+      default:
+        return BRL_PVR_INVALID;
+    }
+  } else {
+    adjustPacketLength(bytes, size, length);
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+readHid1Packet (BrailleDisplay *brl, unsigned char *packet, int size) {
+  static const unsigned char packetLengths[] = {
+    [BAUM_RSP_CellCount]  = 2,
+    [BAUM_RSP_DisplayKeys]  = 2,
+    [BAUM_RSP_RoutingKey]  = 2,
+    [BAUM_RSP_EntryKeys]  = 3,
+    [BAUM_RSP_Joystick]  = 2,
+    [BAUM_RSP_DeviceIdentity]  = 17,
+    [BAUM_RSP_SerialNumber]  = 9,
+  };
+
+  HidPacketVerificationData pvd = {
+    .packetLengths = {
+      .table = packetLengths,
+      .count = ARRAY_COUNT(packetLengths)
+    }
+  };
+
+  memset(packet, 0, size);
+  return readBraillePacket(brl, NULL, packet, size, verifyHidPacket, &pvd);
+}
+
+static int
+readHid2Packet (BrailleDisplay *brl, unsigned char *packet, int size) {
+  static const unsigned char packetLengths[] = {
+    [BAUM_RSP_CellCount]  = 17,
+    [BAUM_RSP_DisplayKeys]  = 17,
+    [BAUM_RSP_RoutingKey]  = 17,
+    [BAUM_RSP_EntryKeys]  = 17,
+    [BAUM_RSP_Joystick]  = 17,
+    [BAUM_RSP_DeviceIdentity]  = 17,
+    [BAUM_RSP_SerialNumber]  = 17,
+  };
+
+  HidPacketVerificationData pvd = {
+    .packetLengths = {
+      .table = packetLengths,
+      .count = ARRAY_COUNT(packetLengths)
+    }
+  };
+
+  memset(packet, 0, size);
+  return readBraillePacket(brl, NULL, packet, size, verifyHidPacket, &pvd);
+}
+
+static int
+getHidPacket (BrailleDisplay *brl, HidResponsePacket *packet) {
+  return brl->data->protocol->readPacket(brl, packet->bytes, sizeof(*packet));
+}
+
+static int
+writeHidPacket (BrailleDisplay *brl, const unsigned char *packet, int length) {
+  return writeBraillePacket(brl, NULL, packet, length);
+}
+
+static void
+handleHidDeviceIdentity (const HidResponsePacket *packet, int probing) {
+  const char *identity = packet->fields.data.deviceIdentity;
+  size_t size = sizeof(packet->fields.data.deviceIdentity);
+
+  logTextField("Baum Device Identity", identity, size);
+  if (probing) setBaumDeviceType(identity, size);
+}
+
+static void
+logHidSerialNumber (const HidResponsePacket *packet) {
+  logTextField("Baum Serial Number",
+               packet->fields.data.serialNumber,
+               sizeof(packet->fields.data.serialNumber));
+}
+
+static int
+probeHidDevice (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {0X02, 0X00};
+
+  if (writeBraillePacket(brl, NULL, packet, sizeof(packet))) {
+    int haveCellCount = 0;
+    int haveDeviceIdentity = 0;
+    int identityCellCount = 0;
+
+    baumDeviceType = BAUM_DEVICE_Default;
+    cellCount = 0;
+
+    while (awaitBrailleInput(brl, probeTimeout)) {
+      HidResponsePacket packet;
+      size_t size = getHidPacket(brl, &packet);
+      if (!size) break;
+
+      switch (packet.fields.type) {
+        case BAUM_RSP_CellCount: {
+          unsigned char count = packet.fields.data.cellCount[0];
+
+          if (isAcceptableCellCount(count)) {
+            cellCount = count;
+            haveCellCount = 1;
+          } else {
+            logUnexpectedCellCount(count);
+          }
+
+          break;
+        }
+
+        case BAUM_RSP_DeviceIdentity: {
+          int count = getIdentityCellCount(packet.fields.data.deviceIdentity,
+                                           sizeof(packet.fields.data.deviceIdentity));
+          if (count) identityCellCount = count;
+          handleHidDeviceIdentity(&packet, 1);
+          haveDeviceIdentity = 1;
+          break;
+        }
+
+        case BAUM_RSP_SerialNumber:
+          logHidSerialNumber(&packet);
+          break;
+
+        default:
+          logUnexpectedPacket(packet.bytes, size);
+          break;
+      }
+
+      if (haveCellCount && haveDeviceIdentity) return 1;
+    }
+
+    if (!cellCount && identityCellCount) {
+      /* Older models don't provide the actual cell count
+       * so it must be derived from the identity string.
+       */
+      cellCount = identityCellCount;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static void
+processHidPackets (BrailleDisplay *brl) {
+  HidResponsePacket packet;
+  size_t size;
+
+  while ((size = getHidPacket(brl, &packet))) {
+    switch (packet.fields.type) {
+      case BAUM_RSP_CellCount:
+        if (!changeCellCount(brl, packet.fields.data.cellCount[0])) return;
+        continue;
+
+      case BAUM_RSP_RoutingKey:
+        resetKeyGroup(packet.fields.data.routingKeys, cellCount, packet.fields.data.routingKey[0]);
+      case BAUM_RSP_RoutingKeys:
+        updateRoutingKeys(brl, packet.fields.data.routingKeys, cellCount);
+        continue;
+
+      case BAUM_RSP_DisplayKeys:
+        updateDisplayKeys(brl, packet.fields.data.displayKeys[0]);
+        continue;
+
+      case BAUM_RSP_EntryKeys:
+        updateEntryKeys(brl, packet.fields.data.entryKeys);
+        continue;
+
+      case BAUM_RSP_Joystick:
+        updateJoystick(brl, packet.fields.data.joystick);
+        continue;
+
+      case BAUM_RSP_DeviceIdentity:
+        handleHidDeviceIdentity(&packet, 0);
+        continue;
+
+      case BAUM_RSP_SerialNumber:
+        logHidSerialNumber(&packet);
+        continue;
+
+      default:
+        logUnexpectedPacket(packet.bytes, size);
+        continue;
+    }
+  }
+}
+
+static int
+writeHidCells (BrailleDisplay *brl) {
+  unsigned char packet[1 + cellCount];
+  unsigned char *byte = packet;
+
+  *byte++ = BAUM_REQ_DisplayData;
+  byte = mempcpy(byte, externalCells, cellCount);
+
+  return writeHidPacket(brl, packet, byte-packet);
+}
+
+static int
+writeHidCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  return 1;
+}
+
+static const ProtocolOperations baumHid1Operations = {
+  .name = "Baum HID1",
+  .dotsTable = &dotsTable_ISO11548_1,
+
+  .readPacket = readHid1Packet,
+  .writePacket = writeHidPacket,
+
+  .probeDevice = probeHidDevice,
+  .processPackets = processHidPackets,
+
+  .writeCells = writeHidCells,
+  .writeCellRange = writeHidCellRange
+};
+
+static const ProtocolOperations baumHid2Operations = {
+  .name = "Baum HID2",
+  .dotsTable = &dotsTable_ISO11548_1,
+
+  .readPacket = readHid2Packet,
+  .writePacket = writeHidPacket,
+
+  .probeDevice = probeHidDevice,
+  .processPackets = processHidPackets,
+
+  .writeCells = writeHidCells,
+  .writeCellRange = writeHidCellRange
+};
+
+/* HandyTech Protocol */
+
+typedef enum {
+  HT_REQ_WRITE = 0X01,
+  HT_REQ_RESET = 0XFF
+} HandyTechRequestCode;
+
+typedef enum {
+  HT_RSP_KEY_B1    = 0X03,
+  HT_RSP_KEY_Up    = 0X04,
+  HT_RSP_KEY_B2    = 0X07,
+  HT_RSP_KEY_Dn    = 0X08,
+  HT_RSP_KEY_B3    = 0X0B,
+  HT_RSP_KEY_B4    = 0X0F,
+  HT_RSP_KEY_CR1   = 0X20,
+  HT_RSP_WRITE_ACK = 0X7E,
+  HT_RSP_RELEASE   = 0X80,
+  HT_RSP_IDENTITY  = 0XFE
+} HandyTechResponseCode;
+#define HT_IS_ROUTING_KEY(code) (((code) >= HT_RSP_KEY_CR1) && ((code) < (HT_RSP_KEY_CR1 + brl->textColumns)))
+
+typedef union {
+  unsigned char bytes[2];
+
+  struct {
+    unsigned char code;
+
+    union {
+      unsigned char identity;
+    } PACKED values;
+  } PACKED data;
+} PACKED HandyTechResponsePacket;
+
+typedef struct {
+  const char *name;
+  unsigned char identity;
+  unsigned char textCount;
+  unsigned char statusCount;
+} HandyTechModelEntry;
+
+static const HandyTechModelEntry handyTechModelTable[] = {
+  { "Modular 80",
+    0X88, 80, 4
+  }
+  ,
+  { "Modular 40",
+    0X89, 40, 4
+  }
+  ,
+  {NULL}        
+};
+static const HandyTechModelEntry *ht;
+
+static int
+readHandyTechPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  int offset = 0;
+  int length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    if (!gioReadByte(brl->gioEndpoint, &byte, offset>0)) {
+      if (offset > 0) logPartialPacket(packet, offset);
+      return 0;
+    }
+
+    if (offset < size) {
+      if (offset == 0) {
+        switch (byte) {
+          case HT_RSP_IDENTITY:
+            length = 2;
+            break;
+
+          case HT_RSP_WRITE_ACK:
+            length = 1;
+            break;
+
+          default: {
+            unsigned char key = byte & ~HT_RSP_RELEASE;
+            switch (key) {
+              default:
+                if (!HT_IS_ROUTING_KEY(key)) {
+                  logUnknownPacket(byte);
+                  continue;
+                }
+
+              case HT_RSP_KEY_Up:
+              case HT_RSP_KEY_Dn:
+              case HT_RSP_KEY_B1:
+              case HT_RSP_KEY_B2:
+              case HT_RSP_KEY_B3:
+              case HT_RSP_KEY_B4:
+                length = 1;
+                break;
+            }
+            break;
+          }
+        }
+      }
+
+      packet[offset] = byte;
+    } else {
+      if (offset == size) logTruncatedPacket(packet, offset);
+      logDiscardedByte(byte);
+    }
+
+    if (++offset == length) {
+      if (offset > size) {
+        offset = 0;
+        length = 0;
+        continue;
+      }
+
+      logInputPacket(packet, offset);
+      return length;
+    }
+  }
+}
+
+static int
+getHandyTechPacket (BrailleDisplay *brl, HandyTechResponsePacket *packet) {
+  return readHandyTechPacket(brl, packet->bytes, sizeof(*packet));
+}
+
+static int
+writeHandyTechPacket (BrailleDisplay *brl, const unsigned char *packet, int length) {
+  return writeBraillePacket(brl, NULL, packet, length);
+}
+
+static const HandyTechModelEntry *
+findHandyTechModel (unsigned char identity) {
+  const HandyTechModelEntry *model;
+
+  for (model=handyTechModelTable; model->name; ++model) {
+    if (identity == model->identity) {
+      logMessage(LOG_INFO, "Baum emulation: HandyTech Model: %02X -> %s", identity, model->name);
+      return model;
+    }
+  }
+
+  logMessage(LOG_WARNING, "Baum emulation: unknown HandyTech identity code: %02X", identity);
+  return NULL;
+}
+
+static int
+probeHandyTechDevice (BrailleDisplay *brl) {
+  int probes = 0;
+  static const unsigned char request[] = {HT_REQ_RESET};
+  while (writeHandyTechPacket(brl, request, sizeof(request))) {
+    while (awaitBrailleInput(brl, probeTimeout)) {
+      HandyTechResponsePacket response;
+      if (getHandyTechPacket(brl, &response)) {
+        if (response.data.code == HT_RSP_IDENTITY) {
+          if (!(ht = findHandyTechModel(response.data.values.identity))) return 0;
+          cellCount = ht->textCount;
+          return 1;
+        }
+      }
+    }
+    if (errno != EAGAIN) break;
+    if (++probes == probeLimit) break;
+  }
+
+  return 0;
+}
+
+static void
+processHandyTechPackets (BrailleDisplay *brl) {
+  HandyTechResponsePacket packet;
+  int size;
+
+  while ((size = getHandyTechPacket(brl, &packet))) {
+    unsigned char code = packet.data.code;
+
+    switch (code) {
+      case HT_RSP_IDENTITY: {
+        const HandyTechModelEntry *model = findHandyTechModel(packet.data.values.identity);
+        if (model && (model != ht)) {
+          ht = model;
+          if (!changeCellCount(brl, ht->textCount)) return;
+        }
+        continue;
+      }
+
+      case HT_RSP_WRITE_ACK:
+        continue;
+    }
+
+    {
+      unsigned char *set;
+      KeyGroup group;
+      unsigned char key = code & ~HT_RSP_RELEASE;
+      int press = (code & HT_RSP_RELEASE) == 0;
+
+      if (HT_IS_ROUTING_KEY(key)) {
+        set = keysState.routingKeys;
+        group = BM_GRP_RoutingKeys;
+        key -= HT_RSP_KEY_CR1;
+      } else {
+        set = keysState.navigationKeys;
+        group = BM_GRP_NavigationKeys;
+
+        switch (key) {
+#define KEY(ht,baum) case HT_RSP_KEY_##ht: key = BM_KEY_DISPLAY + baum; break
+          KEY(Up, 0);
+          KEY(B1, 1);
+          KEY(Dn, 2);
+          KEY(B2, 3);
+          KEY(B3, 4);
+          KEY(B4, 5);
+#undef KEY
+
+          default:
+            logUnexpectedPacket(packet.bytes, size);
+            continue;
+        }
+      }
+
+      if (setGroupedKey(set, key, press)) enqueueKeyEvent(brl, group, key, press);
+    }
+  }
+}
+
+static int
+writeHandyTechCells (BrailleDisplay *brl) {
+  unsigned char packet[1 + ht->statusCount + ht->textCount];
+  unsigned char *byte = packet;
+
+  *byte++ = HT_REQ_WRITE;
+
+  {
+    int count = ht->statusCount;
+    while (count-- > 0) *byte++ = 0;
+  }
+
+  byte = mempcpy(byte, externalCells, ht->textCount);
+
+  return writeHandyTechPacket(brl, packet, byte-packet);
+}
+
+static int
+writeHandyTechCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  return 1;
+}
+
+static const ProtocolOperations handyTechOperations = {
+  .name = "HandyTech",
+  .dotsTable = &dotsTable_ISO11548_1,
+
+  .serialBaud = 19200,
+  .serialParity = SERIAL_PARITY_ODD,
+
+  .readPacket = readHandyTechPacket,
+  .writePacket = writeHandyTechPacket,
+
+  .probeDevice = probeHandyTechDevice,
+  .processPackets = processHandyTechPackets,
+
+  .writeCells = writeHandyTechCells,
+  .writeCellRange = writeHandyTechCellRange
+};
+
+/* PowerBraille Protocol */
+
+#define PB_BUTTONS0_MARKER    0X60
+#define PB1_BUTTONS0_Display6 0X08
+#define PB1_BUTTONS0_Display5 0X04
+#define PB1_BUTTONS0_Display4 0X02
+#define PB1_BUTTONS0_Display2 0X01
+#define PB2_BUTTONS0_Display3 0X08
+#define PB2_BUTTONS0_Display5 0X04
+#define PB2_BUTTONS0_Display1 0X02
+#define PB2_BUTTONS0_Display2 0X01
+
+#define PB_BUTTONS1_MARKER    0XE0
+#define PB1_BUTTONS1_Display3 0X08
+#define PB1_BUTTONS1_Display1 0X02
+#define PB2_BUTTONS1_Display6 0X08
+#define PB2_BUTTONS1_Display4 0X02
+
+typedef enum {
+  PB_REQ_WRITE = 0X04,
+  PB_REQ_RESET = 0X0A
+} PowerBrailleRequestCode;
+
+typedef enum {
+  PB_RSP_IDENTITY = 0X05,
+  PB_RSP_SENSORS  = 0X08
+} PowerBrailleResponseCode;
+
+typedef union {
+  unsigned char bytes[11];
+
+  unsigned char buttons[2];
+
+  struct {
+    unsigned char zero;
+    unsigned char code;
+
+    union {
+      struct {
+        unsigned char cells;
+        unsigned char dots;
+        unsigned char version[4];
+        unsigned char checksum[4];
+      } PACKED identity;
+
+      struct {
+        unsigned char count;
+        unsigned char vertical[4];
+        unsigned char horizontal[10];
+      } PACKED sensors;
+    } PACKED values;
+  } PACKED data;
+} PACKED PowerBrailleResponsePacket;
+
+static int
+readPowerBraillePacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  int offset = 0;
+  int length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    if (!gioReadByte(brl->gioEndpoint, &byte, offset>0)) {
+      if (offset > 0) logPartialPacket(packet, offset);
+      return 0;
+    }
+  haveByte:
+
+    if (offset == 0) {
+      if (!byte) {
+        length = 2;
+      } else if ((byte & PB_BUTTONS0_MARKER) == PB_BUTTONS0_MARKER) {
+        length = 2;
+      } else {
+        logIgnoredByte(byte);
+        continue;
+      }
+    } else if (packet[0]) {
+      if ((byte & PB_BUTTONS1_MARKER) != PB_BUTTONS1_MARKER) {
+        logShortPacket(packet, offset);
+        offset = 0;
+        length = 0;
+        goto haveByte;
+      }
+    } else {
+      if (offset == 1) {
+        switch (byte) {
+          case PB_RSP_IDENTITY:
+            length = 12;
+            break;
+
+          case PB_RSP_SENSORS:
+            length = 3;
+            break;
+
+          default:
+            logUnknownPacket(byte);
+            offset = 0;
+            length = 0;
+            continue;
+        }
+      } else if ((offset == 2) && (packet[1] == PB_RSP_SENSORS)) {
+        length += byte;
+      }
+    }
+
+    if (offset < length) {
+      packet[offset] = byte;
+    } else {
+      if (offset == size) logTruncatedPacket(packet, offset);
+      logDiscardedByte(byte);
+    }
+
+    if (++offset == length) {
+      if (offset > size) {
+        offset = 0;
+        length = 0;
+        continue;
+      }
+
+      logInputPacket(packet, offset);
+      return length;
+    }
+  }
+}
+
+static int
+getPowerBraillePacket (BrailleDisplay *brl, PowerBrailleResponsePacket *packet) {
+  return readPowerBraillePacket(brl, packet->bytes, sizeof(*packet));
+}
+
+static int
+writePowerBraillePacket (BrailleDisplay *brl, const unsigned char *packet, int length) {
+  unsigned char buffer[2 + length];
+  unsigned char *byte = buffer;
+
+  *byte++ = 0XFF;
+  *byte++ = 0XFF;
+  byte = mempcpy(byte, packet, length);
+
+  return writeBraillePacket(brl, NULL, buffer, (byte - buffer));
+}
+
+static int
+probePowerBrailleDevice (BrailleDisplay *brl) {
+  int probes = 0;
+  static const unsigned char request[] = {PB_REQ_RESET};
+  while (writePowerBraillePacket(brl, request, sizeof(request))) {
+    while (awaitBrailleInput(brl, probeTimeout)) {
+      PowerBrailleResponsePacket response;
+      if (getPowerBraillePacket(brl, &response)) {
+        if (response.data.code == PB_RSP_IDENTITY) {
+          const unsigned char *version = response.data.values.identity.version;
+          logMessage(LOG_INFO, "Baum emulation: PowerBraille Version: %c%c%c%c",
+                     version[0], version[1], version[2], version[3]);
+          cellCount = response.data.values.identity.cells;
+          return 1;
+        }
+      }
+    }
+    if (errno != EAGAIN) break;
+    if (++probes == probeLimit) break;
+  }
+
+  return 0;
+}
+
+static void
+processPowerBraillePackets (BrailleDisplay *brl) {
+  PowerBrailleResponsePacket packet;
+  int size;
+
+  while ((size = getPowerBraillePacket(brl, &packet))) {
+    if (!packet.data.zero) {
+      switch (packet.data.code) {
+        case PB_RSP_IDENTITY:
+          if (!changeCellCount(brl, packet.data.values.identity.cells)) return;
+          continue;
+
+        case PB_RSP_SENSORS:
+          updateKeyGroup(brl, keysState.routingKeys, packet.data.values.sensors.horizontal,
+                         BM_GRP_RoutingKeys, 0, brl->textColumns, 0);
+          continue;
+
+        default:
+          break;
+      }
+    } else {
+      unsigned char keys = 0;
+
+#define KEY(key,index) if (packet.buttons[index] & PB2_BUTTONS##index##_Display##key) keys |= 1 << (key - 1)
+      KEY(1, 0);
+      KEY(2, 0);
+      KEY(3, 0);
+      KEY(4, 1);
+      KEY(5, 0);
+      KEY(6, 1);
+#undef KEY
+
+      /*
+       * The PB emulation is deficient as the protocol doesn't report any
+       * key status when all keys are released.  The ability to act on
+       * released keys as needed for multiple key combinations is,
+       * therefore, an unsolvable problem.  The TSI driver works around
+       * this limitation by guessing the "key held" state based on the fact
+       * that native Navigator/PowerBraille displays send repeated key
+       * status for as long as there is at least one key pressed. Baum's PB
+       * emulation, however, doesn't do this.
+       *
+       * Let's treat each packet as a discrete set of press/release events.
+       * The limited set of single key bindings will work just fine.
+       * Multi-key combinations won't work very well at all, though,
+       * because it's unlikely that the user will be able to press and/or
+       * release all of the keys quickly enough, and because releasing one
+       * key before the others will generate press events for the rest.
+       *
+       * This is far from perfect, but that's the best we can do. The PB
+       * emulation modes (either PB1 or PB2) should simply be avoided
+       * whenever possible, and BAUM or HT should be used instead.
+       */
+
+      {
+        const KeyGroup group = BM_GRP_NavigationKeys;
+        KeyNumber pressedKeys[BM_KEYS_DISPLAY];
+        unsigned char pressedCount = 0;
+        unsigned char offset;
+
+        for (offset=0; offset<BM_KEYS_DISPLAY; offset+=1) {
+          if (keys & (1 << offset)) {
+            KeyNumber number = BM_KEY_DISPLAY + offset;
+
+            enqueueKeyEvent(brl, group, number, 1);
+            pressedKeys[pressedCount++] = number;
+          }
+        }
+
+        while (pressedCount) enqueueKeyEvent(brl, group, pressedKeys[--pressedCount], 0);
+      }
+
+      continue;
+    }
+
+    logUnexpectedPacket(packet.bytes, size);
+  }
+}
+
+static int
+writePowerBrailleCells (BrailleDisplay *brl) {
+  unsigned char packet[6 + (brl->textColumns * 2)];
+  unsigned char *byte = packet;
+
+  *byte++ = PB_REQ_WRITE;
+  *byte++ = 0; /* cursor mode: disabled */
+  *byte++ = 0; /* cursor position: nowhere */
+  *byte++ = 1; /* cursor type: command */
+  *byte++ = brl->textColumns * 2; /* attribute-data pairs */
+  *byte++ = 0; /* start */
+
+  {
+    int i;
+    for (i=0; i<brl->textColumns; ++i) {
+      *byte++ = 0; /* attributes */
+      *byte++ = externalCells[i]; /* data */
+    }
+  }
+
+  return writePowerBraillePacket(brl, packet, byte-packet);
+}
+
+static int
+writePowerBrailleCellRange (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  return 1;
+}
+
+static const ProtocolOperations powerBrailleOperations = {
+  .name = "PowerBraille",
+  .dotsTable = &dotsTable_ISO11548_1,
+
+  .serialBaud = 9600,
+  .serialParity = SERIAL_PARITY_NONE,
+
+  .readPacket = readPowerBraillePacket,
+  .writePacket = writePowerBraillePacket,
+
+  .probeDevice = probePowerBrailleDevice,
+  .processPackets = processPowerBraillePackets,
+
+  .writeCells = writePowerBrailleCells,
+  .writeCellRange = writePowerBrailleCellRange
+};
+
+/* Driver Handlers */
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Vario 40 (40 cells) */
+      .vendor=0X0403, .product=0XFE70,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* PocketVario (24 cells) */
+      .vendor=0X0403, .product=0XFE71,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* SuperVario 40 (40 cells) */
+      .vendor=0X0403, .product=0XFE72,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* SuperVario 32 (32 cells) */
+      .vendor=0X0403, .product=0XFE73,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* SuperVario 64 (64 cells) */
+      .vendor=0X0403, .product=0XFE74,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* SuperVario 80 (80 cells) */
+      .vendor=0X0403, .product=0XFE75,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioPro 80 (80 cells) */
+      .vendor=0X0403, .product=0XFE76,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioPro 64 (64 cells) */
+      .vendor=0X0403, .product=0XFE77,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* Orbit Reader 20 (20 cells) */
+      .vendor=0X0483, .product=0XA1D3,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&baumHid1Operations,
+    },
+
+    { /* Orbit Reader 40 (40 cells) */
+      .vendor=0X0483, .product=0Xa366,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&baumHid1Operations,
+    },
+
+    { /* VarioPro 40 (40 cells) */
+      .vendor=0X0904, .product=0X2000,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* EcoVario 24 (24 cells) */
+      .vendor=0X0904, .product=0X2001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* EcoVario 40 (40 cells) */
+      .vendor=0X0904, .product=0X2002,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioConnect 40 (40 cells) */
+      .vendor=0X0904, .product=0X2007,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioConnect 32 (32 cells) */
+      .vendor=0X0904, .product=0X2008,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioConnect 24 (24 cells) */
+      .vendor=0X0904, .product=0X2009,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioConnect 64 (64 cells) */
+      .vendor=0X0904, .product=0X2010,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* VarioConnect 80 (80 cells) */
+      .vendor=0X0904, .product=0X2011,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* EcoVario 32 (32 cells) */
+      .vendor=0X0904, .product=0X2014,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* EcoVario 64 (64 cells) */
+      .vendor=0X0904, .product=0X2015,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* EcoVario 80 (80 cells) */
+      .vendor=0X0904, .product=0X2016,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* Refreshabraille 18 (18 cells) */
+      .vendor=0X0904, .product=0X3000,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&baumEscapeOperations
+    },
+
+    { /* Orbit in Refreshabraille Emulation Mode (18 cells) */
+      .vendor=0X0904, .product=0X3001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .verifyInterface=1,
+      .data=&baumHid1Operations
+    },
+
+    { /* Refreshabraille 18 (18 cells) */
+      .vendor=0X0904, .product=0X3001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface=1,
+      .data=&baumHid1Operations
+    },
+
+    { /* Pronto! V3 18 (18 cells) */
+      .vendor=0X0904, .product=0X4004,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Pronto! V3 40 (40 cells) */
+      .vendor=0X0904, .product=0X4005,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Pronto! V4 18 (18 cells) */
+      .vendor=0X0904, .product=0X4007,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid2Operations
+    },
+
+    { /* Pronto! V4 40 (40 cells) */
+      .vendor=0X0904, .product=0X4008,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid2Operations
+    },
+
+    { /* SuperVario2 40 (40 cells) */
+      .vendor=0X0904, .product=0X6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* PocketVario2 (24 cells) */
+      .vendor=0X0904, .product=0X6002,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* SuperVario2 32 (32 cells) */
+      .vendor=0X0904, .product=0X6003,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* SuperVario2 64 (64 cells) */
+      .vendor=0X0904, .product=0X6004,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* SuperVario2 80 (80 cells) */
+      .vendor=0X0904, .product=0X6005,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Brailliant2 40 (40 cells) */
+      .vendor=0X0904, .product=0X6006,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Brailliant2 24 (24 cells) */
+      .vendor=0X0904, .product=0X6007,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Brailliant2 32 (32 cells) */
+      .vendor=0X0904, .product=0X6008,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Brailliant2 64 (64 cells) */
+      .vendor=0X0904, .product=0X6009,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* Brailliant2 80 (80 cells) */
+      .vendor=0X0904, .product=0X600A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* VarioConnect 24 (24 cells) */
+      .vendor=0X0904, .product=0X6011,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* VarioConnect 32 (32 cells) */
+      .vendor=0X0904, .product=0X6012,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* VarioConnect 40 (40 cells) */
+      .vendor=0X0904, .product=0X6013,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid1Operations
+    },
+
+    { /* VarioUltra 20 (20 cells) */
+      .vendor=0X0904, .product=0X6101,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid2Operations
+    },
+
+    { /* VarioUltra 40 (40 cells) */
+      .vendor=0X0904, .product=0X6102,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid2Operations
+    },
+
+    { /* VarioUltra 32 (32 cells) */
+      .vendor=0X0904, .product=0X6103,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&baumHid2Operations
+    },
+
+    { /* NLS eReader Zoomax (20 cells) */
+      .vendor=0X1A86, .product=0X7523,
+      .parentVendor=0X1A40, .parentProduct=0X0101,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=2,
+      .data=&baumEscapeOperations
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &baumEscapeOperations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.ignoreWriteTimeouts = 1;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.discoverChannel = 1;
+  descriptor.bluetooth.options.applicationData = &baumEscapeOperations;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  const ProtocolOperations *requestedProtocol = NULL;
+  unsigned int useVarioKeys = 0;
+
+  {
+    static const ProtocolOperations *const values[] = {
+      NULL,
+      &baumEscapeOperations,
+      &baumHid1Operations,
+      &baumHid2Operations,
+      &handyTechOperations,
+      &powerBrailleOperations
+    };
+
+    static const char *choices[] = {"default", "escape", "hid1", "hid2", "ht","pb", NULL};
+    unsigned int index = 0;
+
+    if (validateChoice(&index, parameters[PARM_PROTOCOL], choices)) {
+      requestedProtocol = values[index];
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid protocol setting", parameters[PARM_PROTOCOL]);
+    }
+  }
+
+  if (!validateYesNo(&useVarioKeys, parameters[PARM_VARIOKEYS])) {
+    logMessage(LOG_WARNING, "%s: %s", "invalid vario keys setting", parameters[PARM_VARIOKEYS]);
+  }
+
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned int attempts = 0;
+
+      while (1) {
+        brl->data->protocol = requestedProtocol;
+        if (!brl->data->protocol) brl->data->protocol = gioGetApplicationData(brl->gioEndpoint);
+        logMessage(LOG_DEBUG, "probing with %s protocol", brl->data->protocol->name);
+
+        if (brl->data->protocol->serialBaud) {
+          const SerialParameters parameters = {
+            SERIAL_DEFAULT_PARAMETERS,
+            .baud = brl->data->protocol->serialBaud,
+            .parity = brl->data->protocol->serialParity
+          };
+
+          if (!gioReconfigureResource(brl->gioEndpoint, &parameters)) goto failed;
+        }
+
+        if (!gioDiscardInput(brl->gioEndpoint)) goto failed;
+
+        memset(&keysState, 0, sizeof(keysState));
+        switchSettings = 0;
+
+        if (brl->data->protocol->probeDevice(brl)) {
+          logCellCount(brl);
+
+          {
+            unsigned char *size = &brl->data->packetSize.routingKeys;
+
+            *size = KEY_GROUP_SIZE(cellCount);
+            if ((*size > 2) && (*size < 5)) *size = 5;
+          }
+
+          if ((baumDeviceType == BAUM_DEVICE_VarioConnect) && (cellCount == 12)) {
+            baumDeviceType = BAUM_DEVICE_Conny;
+          }
+
+          makeOutputTable(brl->data->protocol->dotsTable[0]);
+          if (!clearCellRange(brl, 0, cellCount)) goto failed;
+          if (!updateCells(brl)) goto failed;
+
+          {
+            const KeyTableDefinition *ktd;
+
+            if (useVarioKeys) {
+              ktd = &KEY_TABLE_DEFINITION(vk);
+            } else {
+              ktd = baumDeviceOperations[baumDeviceType].keyTableDefinition;
+            }
+
+            setBrailleKeyTable(brl, ktd);
+          }
+
+          return 1;
+        }
+
+        if (++attempts == 2) break;
+        asyncWait(700);
+      }
+
+    failed:
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+  free(brl->data);
+}
+
+static ssize_t
+brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  int count = brl->data->protocol->readPacket(brl, buffer, size);
+  if (!count) count = -1;
+  return count;
+}
+
+static ssize_t
+brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) {
+  return brl->data->protocol->writePacket(brl, packet, length)? length: -1;
+}
+
+static int
+brl_reset (BrailleDisplay *brl) {
+  return 0;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (!putCells(brl, brl->buffer, 0, brl->textColumns)) return 0;
+  return updateCells(brl);
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+  return putCells(brl, status, brl->textColumns, brl->statusColumns);
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  brl->data->protocol->processPackets(brl);
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Baum/brldefs-bm.h b/Drivers/Braille/Baum/brldefs-bm.h
new file mode 100644
index 0000000..0ad708e
--- /dev/null
+++ b/Drivers/Braille/Baum/brldefs-bm.h
@@ -0,0 +1,77 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BM_BRLDEFS
+#define BRLTTY_INCLUDED_BM_BRLDEFS
+
+#define BM_KEYS_DISPLAY 8
+#define BM_KEYS_COMMAND 7
+#define BM_KEYS_FRONT 10
+#define BM_KEYS_ENTRY 16
+#define BM_KEYS_JOYSTICK 5
+#define BM_KEYS_WHEEL 4
+#define BM_KEYS_STATUS 8
+
+typedef enum {
+  BM_KEY_DISPLAY = 0,
+  BM_KEY_COMMAND = BM_KEY_DISPLAY + BM_KEYS_DISPLAY,
+  BM_KEY_FRONT = BM_KEY_COMMAND + BM_KEYS_COMMAND,
+  BM_KEY_BACK = BM_KEY_FRONT + BM_KEYS_FRONT,
+  BM_KEY_ENTRY = BM_KEY_BACK + BM_KEYS_FRONT,
+  BM_KEY_JOYSTICK = BM_KEY_ENTRY + BM_KEYS_ENTRY,
+  BM_KEY_WHEEL_UP = BM_KEY_JOYSTICK + BM_KEYS_JOYSTICK,
+  BM_KEY_WHEEL_DOWN = BM_KEY_WHEEL_UP + BM_KEYS_WHEEL,
+  BM_KEY_WHEEL_PRESS = BM_KEY_WHEEL_DOWN + BM_KEYS_WHEEL,
+  BM_KEY_STATUS = BM_KEY_WHEEL_PRESS + BM_KEYS_WHEEL,
+  BM_KEY_COUNT = BM_KEY_STATUS + BM_KEYS_STATUS,
+
+  BM_KEY_B9 = BM_KEY_ENTRY,
+  BM_KEY_B10,
+  BM_KEY_B11,
+  BM_KEY_B12,
+  BM_KEY_F1,
+  BM_KEY_F2,
+  BM_KEY_F3,
+  BM_KEY_F4,
+  BM_KEY_DOT1,
+  BM_KEY_DOT2,
+  BM_KEY_DOT3,
+  BM_KEY_DOT4,
+  BM_KEY_DOT5,
+  BM_KEY_DOT6,
+  BM_KEY_DOT7,
+  BM_KEY_DOT8,
+
+  BM_KEY_UP = BM_KEY_JOYSTICK,
+  BM_KEY_LEFT,
+  BM_KEY_DOWN,
+  BM_KEY_RIGHT,
+  BM_KEY_PRESS,
+} BM_NavigationKey;
+
+typedef enum {
+  BM_GRP_NavigationKeys = 0,
+  BM_GRP_RoutingKeys,
+  BM_GRP_HorizontalSensors,
+  BM_GRP_LeftSensors,
+  BM_GRP_RightSensors,
+  BM_GRP_ScaledLeftSensors,
+  BM_GRP_ScaledRightSensors
+} BM_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_BM_BRLDEFS */ 
diff --git a/Drivers/Braille/BrailComm/Makefile.in b/Drivers/Braille/BrailComm/Makefile.in
new file mode 100644
index 0000000..14d33d4
--- /dev/null
+++ b/Drivers/Braille/BrailComm/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bc
+DRIVER_NAME = BrailComm
+DRIVER_USAGE = III
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/BrailComm/braille.c b/Drivers/Braille/BrailComm/braille.c
new file mode 100644
index 0000000..b159804
--- /dev/null
+++ b/Drivers/Braille/BrailComm/braille.c
@@ -0,0 +1,304 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include "io_serial.h"
+
+typedef enum {
+  PARM_BAUD
+} DriverParameter;
+#define BRLPARMS "baud"
+
+#define BRL_STATUS_FIELDS sfGeneric
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+
+static const int showOutputMapping = 0;
+
+static SerialDevice *serialDevice = NULL;
+static unsigned int serialBaud;
+static unsigned int charactersPerSecond;
+static const int *initialCommand;
+
+typedef enum {
+  IPT_MINIMUM_LINE     =   1,
+  IPT_MAXIMUM_LINE     =  25,
+  IPT_SEARCH_ATTRIBUTE =  90,
+  IPT_CURRENT_LINE     = 100,
+  IPT_CURRENT_LOCATION = 101,
+} InputPacketType;
+
+typedef union {
+  unsigned char bytes[4];
+
+  struct {
+    unsigned char type;
+
+    union {
+      struct {
+        unsigned char line;
+        unsigned char column;
+        unsigned char attributes;
+      } PACKED search;
+    } fields;
+  } PACKED data;
+} InputPacket;
+
+typedef int (*WriteFunction) (BrailleDisplay *brl);
+static WriteFunction writeFunction;
+
+static unsigned char statusCells[GSC_COUNT];
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  {
+    static const unsigned int baudTable[] = {9600, 19200, 0};
+    const char *baudParameter = parameters[PARM_BAUD];
+
+    if (!*baudParameter ||
+        !serialValidateBaud(&serialBaud, "baud", baudParameter, baudTable))
+      serialBaud = baudTable[0];
+  }
+
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if ((serialDevice = serialOpenDevice(device))) {
+    if (serialRestartDevice(serialDevice, serialBaud)) {
+      charactersPerSecond = serialBaud / 10;
+      writeFunction = NULL;
+
+      {
+        static const TranslationTable outputTable = {
+#define MAP(byte,cell) [cell] = byte
+#include "brl-out.h"
+#undef MAP
+        };
+        setOutputTable(outputTable);
+      }
+
+      {
+        static const int initialCommands[] = {
+          BRL_CMD_TUNES | BRL_FLG_TOGGLE_OFF,
+          BRL_CMD_CSRTRK | BRL_FLG_TOGGLE_OFF,
+          BRL_CMD_CSRVIS | BRL_FLG_TOGGLE_OFF,
+          BRL_CMD_ATTRVIS | BRL_FLG_TOGGLE_OFF,
+          EOF
+        };
+
+        initialCommand = initialCommands;
+      }
+
+      brl->textColumns = 80;
+      return 1;
+    }
+
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+writePacket (BrailleDisplay *brl, const unsigned char *packet, size_t size) {
+  logOutputPacket(packet, size);
+  brl->writeDelay += (size * 1000 / charactersPerSecond) + 1;
+  return serialWriteData(serialDevice, packet, size) != -1;
+}
+
+static int
+writeLine (BrailleDisplay *brl) {
+  unsigned char packet[2 + (brl->textColumns * 2)];
+  unsigned char *byte = packet;
+
+  *byte++ = statusCells[gscScreenCursorRow];
+  *byte++ = statusCells[gscScreenCursorColumn];
+
+  {
+    int i;
+
+    for (i=0; i<brl->textColumns; i+=1) {
+      *byte++ = translateOutputCell(brl->buffer[i]);
+      *byte++ = 0X07;
+    }
+  }
+
+  if (showOutputMapping) {
+    int row = statusCells[gscBrailleWindowRow];
+
+    if (--row < 0X10) {
+      int column;
+
+      for (column=0; column<80; column+=1) packet[2+(column*2)] = ' ';
+
+      for (column=0; column<0X10; column+=1) {
+        unsigned char *byte = &packet[2 + (column * 8)];
+        static const unsigned char hex[] = "0123456789ABCDEF";
+
+        *byte = hex[row];
+        byte += 2;
+
+        *byte = hex[column];
+        byte += 2;
+
+        *byte = (row << 4) | column;
+      }
+    }
+  }
+
+  return writePacket(brl, packet, byte-packet);
+}
+
+static int
+writeLocation (BrailleDisplay *brl) {
+  unsigned char packet[2];
+  unsigned char *byte = packet;
+
+  *byte++ = statusCells[gscScreenCursorRow];
+  *byte++ = statusCells[gscScreenCursorColumn];
+
+  return writePacket(brl, packet, byte-packet);
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (writeFunction) {
+    int ok = writeFunction(brl);
+    writeFunction = NULL;
+    if (!ok) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+  memcpy(statusCells, status, GSC_COUNT);
+  return 1;
+}
+
+static int
+readByte (unsigned char *byte, int wait) {
+  const int timeout = 100;
+  ssize_t result = serialReadData(serialDevice,
+                                  byte, sizeof(*byte),
+                                  (wait? timeout: 0), timeout);
+
+  if (result > 0) return 1;
+  if (result == 0) errno = EAGAIN;
+  return 0;
+}
+
+static int
+readPacket (BrailleDisplay *brl, InputPacket *packet) {
+  int length = 1;
+  int offset = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    {
+      int started = offset > 0;
+      if (!readByte(&byte, started)) {
+        if (started) logPartialPacket(packet->bytes, offset);
+        return 0;
+      }
+    }
+
+    if (!offset) {
+      switch (byte) {
+        case IPT_CURRENT_LINE:
+        case IPT_CURRENT_LOCATION:
+          length = 1;
+          break;
+
+        case IPT_SEARCH_ATTRIBUTE:
+          length = 4;
+          break;
+
+        default:
+          if ((byte >= IPT_MINIMUM_LINE) && (byte <= IPT_MAXIMUM_LINE)) {
+            length = 1;
+          } else {
+            logIgnoredByte(byte);
+            continue;
+          }
+          break;
+      }
+    }
+
+    packet->bytes[offset++] = byte;
+    if (offset == length) {
+      logInputPacket(packet->bytes, offset);
+      return length;
+    }
+  }
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  InputPacket packet;
+  int length;
+
+  if (context == KTB_CTX_WAITING) return BRL_CMD_NOOP;
+  if (writeFunction) return EOF;
+  while (*initialCommand != EOF) enqueueCommand(*initialCommand++);
+
+  while ((length = readPacket(brl, &packet))) {
+    if ((packet.data.type >= IPT_MINIMUM_LINE) &&
+        (packet.data.type <= IPT_MAXIMUM_LINE)) {
+      enqueueCommand(BRL_CMD_BLK(GOTOLINE) | BRL_FLG_MOTION_TOLEFT | (packet.data.type - IPT_MINIMUM_LINE));
+      writeFunction = writeLine;
+      return EOF;
+    }
+
+    switch (packet.data.type) {
+      case IPT_SEARCH_ATTRIBUTE:
+      case IPT_CURRENT_LINE:
+        enqueueCommand(BRL_CMD_HOME);
+        enqueueCommand(BRL_CMD_LNBEG);
+        writeFunction = writeLine;
+        return EOF;
+
+      case IPT_CURRENT_LOCATION:
+        writeFunction = writeLocation;
+        return EOF;
+
+      default:
+        logUnexpectedPacket(&packet, length);
+        break;
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/BrailComm/brl-out.h b/Drivers/Braille/BrailComm/brl-out.h
new file mode 100644
index 0000000..fd18768
--- /dev/null
+++ b/Drivers/Braille/BrailComm/brl-out.h
@@ -0,0 +1,339 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Dot combinations mapped more than once:
+ *    Dots     Bytes
+ *    0        0X00 0X20
+ *    12345678 0XDB 0XFF
+ *    1234568  0XC0 0XE1
+ *    12346    0X2B 0X98
+ *    123468   0X26 0XE0
+ *    1246     0X8D 0X95
+ *    126      0X3C 0XA1
+ *    1456     0X25 0XA2
+ *    156      0X3E 0X8C
+ *    16       0X86 0XA0
+ *    167      0X8F 0XA4
+ *    1678     0XEB 0XF8
+ *    235678   0XF6 0XFA
+ *    23568    0XB7 0XBD
+ *    2358     0X21 0X9E
+ *    2368     0XB8 0XE8
+ *    2578     0XC6 0XFC
+ *    267      0XBA 0XC5
+ *    346      0X5F 0XF7
+ *    3578     0X9D 0XDE
+ *    358      0X9B 0XEA
+ *    38       0XA8 0XBB
+ *    4        0X40 0X60
+ *    457      0X5E 0XBE
+ *    467      0XB6 0XF4
+ *    5        0X27 0XB2
+ *    58       0X87 0XAD
+ *    7        0XF2 0XFD
+ *    8        0XB9 0XFE
+ */
+
+/* Unmapped dot combinations:
+ *    1234567
+ *    123567
+ *    12467
+ *    12568
+ *    134568
+ *    13458
+ *    1358
+ *    1467
+ *    1468
+ *    234567
+ *    234568
+ *    235
+ *    2367
+ *    237
+ *    2468
+ *    256
+ *    25678
+ *    28
+ *    34568
+ *    3468
+ *    347
+ *    348
+ *    357
+ *    37
+ *    45
+ *    4578
+ *    458
+ *    57
+ *    67
+ */
+
+MAP(0X00, 0),
+MAP(0X01, BRL_DOT1|BRL_DOT7|BRL_DOT8),
+MAP(0X02, BRL_DOT1|BRL_DOT2|BRL_DOT7|BRL_DOT8),
+MAP(0X03, BRL_DOT1|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X04, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X05, BRL_DOT1|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X06, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X07, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X08, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X09, BRL_DOT2|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X0A, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X0B, BRL_DOT1|BRL_DOT3|BRL_DOT7|BRL_DOT8),
+MAP(0X0C, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT7|BRL_DOT8),
+MAP(0X0D, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X0E, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X0F, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X10, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X11, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X12, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X13, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X14, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X15, BRL_DOT1|BRL_DOT3|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X16, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X17, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X18, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X19, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X1A, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X1B, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X1C, BRL_DOT3|BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0X1D, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X1E, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X1F, BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X20, 0),
+MAP(0X21, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT8),
+MAP(0X22, BRL_DOT5|BRL_DOT6),
+MAP(0X23, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X24, BRL_DOT4|BRL_DOT6),
+MAP(0X25, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X26, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0X27, BRL_DOT5),
+MAP(0X28, BRL_DOT2|BRL_DOT3|BRL_DOT6),
+MAP(0X29, BRL_DOT3|BRL_DOT5|BRL_DOT6),
+MAP(0X2A, BRL_DOT3|BRL_DOT5),
+MAP(0X2B, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0X2C, BRL_DOT2),
+MAP(0X2D, BRL_DOT3|BRL_DOT6),
+MAP(0X2E, BRL_DOT3),
+MAP(0X2F, BRL_DOT3|BRL_DOT4),
+MAP(0X30, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0X31, BRL_DOT1|BRL_DOT8),
+MAP(0X32, BRL_DOT1|BRL_DOT2|BRL_DOT8),
+MAP(0X33, BRL_DOT1|BRL_DOT4|BRL_DOT8),
+MAP(0X34, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0X35, BRL_DOT1|BRL_DOT5|BRL_DOT8),
+MAP(0X36, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT8),
+MAP(0X37, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0X38, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT8),
+MAP(0X39, BRL_DOT2|BRL_DOT4|BRL_DOT8),
+MAP(0X3A, BRL_DOT2|BRL_DOT5),
+MAP(0X3B, BRL_DOT2|BRL_DOT3),
+MAP(0X3C, BRL_DOT1|BRL_DOT2|BRL_DOT6),
+MAP(0X3D, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6),
+MAP(0X3E, BRL_DOT1|BRL_DOT5|BRL_DOT6),
+MAP(0X3F, BRL_DOT2|BRL_DOT6),
+MAP(0X40, BRL_DOT4),
+MAP(0X41, BRL_DOT1|BRL_DOT7),
+MAP(0X42, BRL_DOT1|BRL_DOT2|BRL_DOT7),
+MAP(0X43, BRL_DOT1|BRL_DOT4|BRL_DOT7),
+MAP(0X44, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X45, BRL_DOT1|BRL_DOT5|BRL_DOT7),
+MAP(0X46, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT7),
+MAP(0X47, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X48, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT7),
+MAP(0X49, BRL_DOT2|BRL_DOT4|BRL_DOT7),
+MAP(0X4A, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X4B, BRL_DOT1|BRL_DOT3|BRL_DOT7),
+MAP(0X4C, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT7),
+MAP(0X4D, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT7),
+MAP(0X4E, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X4F, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT7),
+MAP(0X50, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT7),
+MAP(0X51, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X52, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT7),
+MAP(0X53, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT7),
+MAP(0X54, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X55, BRL_DOT1|BRL_DOT3|BRL_DOT6|BRL_DOT7),
+MAP(0X56, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT7),
+MAP(0X57, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0X58, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0X59, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0X5A, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0X5B, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6),
+MAP(0X5C, BRL_DOT1|BRL_DOT4|BRL_DOT6),
+MAP(0X5D, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X5E, BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X5F, BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0X60, BRL_DOT4),
+MAP(0X61, BRL_DOT1),
+MAP(0X62, BRL_DOT1|BRL_DOT2),
+MAP(0X63, BRL_DOT1|BRL_DOT4),
+MAP(0X64, BRL_DOT1|BRL_DOT4|BRL_DOT5),
+MAP(0X65, BRL_DOT1|BRL_DOT5),
+MAP(0X66, BRL_DOT1|BRL_DOT2|BRL_DOT4),
+MAP(0X67, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5),
+MAP(0X68, BRL_DOT1|BRL_DOT2|BRL_DOT5),
+MAP(0X69, BRL_DOT2|BRL_DOT4),
+MAP(0X6A, BRL_DOT2|BRL_DOT4|BRL_DOT5),
+MAP(0X6B, BRL_DOT1|BRL_DOT3),
+MAP(0X6C, BRL_DOT1|BRL_DOT2|BRL_DOT3),
+MAP(0X6D, BRL_DOT1|BRL_DOT3|BRL_DOT4),
+MAP(0X6E, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5),
+MAP(0X6F, BRL_DOT1|BRL_DOT3|BRL_DOT5),
+MAP(0X70, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4),
+MAP(0X71, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5),
+MAP(0X72, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5),
+MAP(0X73, BRL_DOT2|BRL_DOT3|BRL_DOT4),
+MAP(0X74, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5),
+MAP(0X75, BRL_DOT1|BRL_DOT3|BRL_DOT6),
+MAP(0X76, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT6),
+MAP(0X77, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X78, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0X79, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X7A, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT6),
+MAP(0X7B, BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X7C, BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0X7D, BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0X7E, BRL_DOT6),
+MAP(0X7F, BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0X80, BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X81, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT6),
+MAP(0X82, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0X83, BRL_DOT1|BRL_DOT6|BRL_DOT8),
+MAP(0X84, BRL_DOT3|BRL_DOT4|BRL_DOT5),
+MAP(0X85, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0X86, BRL_DOT1|BRL_DOT6),
+MAP(0X87, BRL_DOT5|BRL_DOT8),
+MAP(0X88, BRL_DOT1|BRL_DOT2|BRL_DOT6|BRL_DOT8),
+MAP(0X89, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0X8A, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0X8B, BRL_DOT1|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0X8C, BRL_DOT1|BRL_DOT5|BRL_DOT6),
+MAP(0X8D, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT6),
+MAP(0X8E, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0X8F, BRL_DOT1|BRL_DOT6|BRL_DOT7),
+MAP(0X90, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0X91, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0X92, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X93, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0X94, BRL_DOT2|BRL_DOT4|BRL_DOT6),
+MAP(0X95, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT6),
+MAP(0X96, BRL_DOT1|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0X97, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0X98, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0X99, BRL_DOT2|BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0X9A, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0X9B, BRL_DOT3|BRL_DOT5|BRL_DOT8),
+MAP(0X9C, BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0X9D, BRL_DOT3|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0X9E, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT8),
+MAP(0X9F, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT7),
+MAP(0XA0, BRL_DOT1|BRL_DOT6),
+MAP(0XA1, BRL_DOT1|BRL_DOT2|BRL_DOT6),
+MAP(0XA2, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0XA3, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0XA4, BRL_DOT1|BRL_DOT6|BRL_DOT7),
+MAP(0XA5, BRL_DOT1|BRL_DOT2|BRL_DOT6|BRL_DOT7),
+MAP(0XA6, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XA7, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XA8, BRL_DOT3|BRL_DOT8),
+MAP(0XA9, BRL_DOT3|BRL_DOT6|BRL_DOT7),
+MAP(0XAA, BRL_DOT3|BRL_DOT6|BRL_DOT8),
+MAP(0XAB, BRL_DOT2|BRL_DOT3|BRL_DOT8),
+MAP(0XAC, BRL_DOT2|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XAD, BRL_DOT5|BRL_DOT8),
+MAP(0XAE, BRL_DOT4|BRL_DOT8),
+MAP(0XAF, BRL_DOT4|BRL_DOT7),
+MAP(0XB0, BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0XB1, BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XB2, BRL_DOT5),
+MAP(0XB3, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6),
+MAP(0XB4, BRL_DOT2|BRL_DOT7),
+MAP(0XB5, BRL_DOT2|BRL_DOT5|BRL_DOT7),
+MAP(0XB6, BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0XB7, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XB8, BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT8),
+MAP(0XB9, BRL_DOT8),
+MAP(0XBA, BRL_DOT2|BRL_DOT6|BRL_DOT7),
+MAP(0XBB, BRL_DOT3|BRL_DOT8),
+MAP(0XBC, BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XBD, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XBE, BRL_DOT4|BRL_DOT5|BRL_DOT7),
+MAP(0XBF, BRL_DOT2|BRL_DOT6|BRL_DOT8),
+MAP(0XC0, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XC1, BRL_DOT3|BRL_DOT7|BRL_DOT8),
+MAP(0XC2, BRL_DOT1|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XC3, BRL_DOT2|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XC4, BRL_DOT2|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XC5, BRL_DOT2|BRL_DOT6|BRL_DOT7),
+MAP(0XC6, BRL_DOT2|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0XC7, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XC8, BRL_DOT7|BRL_DOT8),
+MAP(0XC9, BRL_DOT2|BRL_DOT5|BRL_DOT8),
+MAP(0XCA, BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XCB, BRL_DOT1|BRL_DOT3|BRL_DOT8),
+MAP(0XCC, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT8),
+MAP(0XCD, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT8),
+MAP(0XCE, BRL_DOT4|BRL_DOT7|BRL_DOT8),
+MAP(0XCF, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XD0, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT8),
+MAP(0XD1, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0XD2, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT8),
+MAP(0XD3, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT8),
+MAP(0XD4, BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT8),
+MAP(0XD5, BRL_DOT1|BRL_DOT3|BRL_DOT6|BRL_DOT8),
+MAP(0XD6, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT8),
+MAP(0XD7, BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XD8, BRL_DOT1|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0XD9, BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XDA, BRL_DOT1|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XDB, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XDC, BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XDD, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XDE, BRL_DOT3|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0XDF, BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XE0, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0XE1, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XE2, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0XE3, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XE4, BRL_DOT4|BRL_DOT6|BRL_DOT8),
+MAP(0XE5, BRL_DOT2|BRL_DOT7|BRL_DOT8),
+MAP(0XE6, BRL_DOT3|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XE7, BRL_DOT6|BRL_DOT8),
+MAP(0XE8, BRL_DOT2|BRL_DOT3|BRL_DOT6|BRL_DOT8),
+MAP(0XE9, BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT8),
+MAP(0XEA, BRL_DOT3|BRL_DOT5|BRL_DOT8),
+MAP(0XEB, BRL_DOT1|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XEC, BRL_DOT1|BRL_DOT2|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XED, BRL_DOT1|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XEE, BRL_DOT1|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XEF, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XF0, BRL_DOT1|BRL_DOT2|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XF1, BRL_DOT1|BRL_DOT2|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XF2, BRL_DOT7),
+MAP(0XF3, BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7),
+MAP(0XF4, BRL_DOT4|BRL_DOT6|BRL_DOT7),
+MAP(0XF5, BRL_DOT2|BRL_DOT3|BRL_DOT7|BRL_DOT8),
+MAP(0XF6, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XF7, BRL_DOT3|BRL_DOT4|BRL_DOT6),
+MAP(0XF8, BRL_DOT1|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XF9, BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XFA, BRL_DOT2|BRL_DOT3|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XFB, BRL_DOT2|BRL_DOT6|BRL_DOT7|BRL_DOT8),
+MAP(0XFC, BRL_DOT2|BRL_DOT5|BRL_DOT7|BRL_DOT8),
+MAP(0XFD, BRL_DOT7),
+MAP(0XFE, BRL_DOT8),
+MAP(0XFF, BRL_DOT1|BRL_DOT2|BRL_DOT3|BRL_DOT4|BRL_DOT5|BRL_DOT6|BRL_DOT7|BRL_DOT8)
diff --git a/Drivers/Braille/BrailleLite/Makefile.in b/Drivers/Braille/BrailleLite/Makefile.in
new file mode 100644
index 0000000..64b270e
--- /dev/null
+++ b/Drivers/Braille/BrailleLite/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bl
+DRIVER_NAME = BrailleLite
+DRIVER_USAGE = 18/40/M20/M40
+DRIVER_VERSION = 0.6.0 (June 2003)
+DRIVER_DEVELOPERS = Nikhil Nair
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/BrailleLite/README b/Drivers/Braille/BrailleLite/README
new file mode 100644
index 0000000..80c341b
--- /dev/null
+++ b/Drivers/Braille/BrailleLite/README
@@ -0,0 +1,121 @@
+Braille Lite Driver for BRLTTY
+
+This is a beta driver for the Blazie Engineering Braille Lite.
+written by Nikhil Nair,
+with additions from Nicolas Pitre <nico@fluxnic.net>
+and Pete De Vasto,
+further modifications by Stéphane Doyon <s.doyon@videotron.ca>,
+    starting August 2001.
+
+Both BrailleLite 18 and 40 should work, and there is now code that should
+auto-detect the display type.
+
+Edit braille.h to select baudrate and some features.
+
+On the BrailleLite, you should type 345-chord and p-chord to enter the 
+"speech box mode" required by BRLTTY.  You should type the same sequence 
+to exit that mode whenever you wish to return to the BrailleLite internal
+functions.
+
+Be sure your BrailleLite's serial port baudrate and the value configured
+in the braille.h file are the same.
+
+The Braille Lite 40 has two advance bars on it.  Brltty has been set up
+so that moving the display left or right works the same as it does
+when the unit is in its internal notetaker mode.
+
+See help.src for a description of the key bindings.
+
+History:
+Stéphane Doyon, April 2002, version 0.5.10:
+-Act as if kbemu was off in any context other than CMDS_SCREEN (in
+    particular typing a key won't throw you out of preferences menu
+    anymore).
+Stéphane Doyon, March 2002, version 0.5.9:
+-Added "kbemu" driver parameter, so can start with kbemu already active.
+Stéphane Doyon, February 2002, version 0.5.8:
+-Added CMD_LEARN, CMD_NXPROMPT/CMD_PRPROMPT and CMD_SIXDOTS. Several
+    VAL_PASSKEY effects were done through special cases: moved them
+    back into cmdtrans. Identified special/reserved and free bindings.
+    Cleaned out some #if 0s.
+Stéphane Doyon, January 2002, version 0.5.7:
+-Added bindings for CR_CUTAPPEND and CR_CUTLINE. Replaced CMD_CUT_BEG,
+    CMD_CUT_END and CMD_CSRJMP by CR_ equivalents. Added CR_SETMARK and
+    CR_GOTOMARK. These are a bit nasty: they reuse existing command dot
+    patterns from within o-chord. Well practically all the patterns are
+    taken!
+Stéphane Doyon, January 2002, version 0.5.6:
+-Fixed dot combinations with dots 7-8 on BL40 (for typing caps and ctrl).
+Stéphane Doyon, December 2001, version 0.5.5:
+-META is back. Now use VPC modifiers on VALPASSCHAR when TEXTTRANS
+    not defined. Fixed uppercase that always locked active. Sub-commands
+    of 35-chord no longer need to be chorded.
+Stéphane Doyon, November 2001, version 0.5.4:
+-Now using new generic VAL_PASS* mechanism for typing in keys, rather
+    than having the driver itself insert stuff into the console. We
+    no longer have the META function though, but we have ESCAPE.
+Stéphane Doyon and Dave Mielke, September 2001, version 0.5.3:
+-closebrl: don't clear display, use TCSADRAIN.
+-Slight API and name changes for BRLTTY 3.0. Commands can no longer be
+    assumed to be chars, changed type of cmdtrans.
+-Fixed binding for switching virtual terminal with o-chord... it was wrong.
+Stéphane Doyon, September 2001, version 0.5.2:
+-Added baudrate parameter. Fixed up initialization, in particular a bug
+    with use of qbase buffer unitialized.
+Stéphane Doyon, September 2001, version 0.5.1:
+-Added bindings for CMD_BACK and CR_MSGATTRIB.
+Stéphane Doyon, August 2001
+-Added a version number, as many people have been playing with this
+   driver. Arbitrarily started at 0.5.
+-Made the help more compact, and remove the duplication of the key
+   bindings list from README.
+-Use separate help files for BL18 and BL40, automatically generated from
+   brltty_genhelp.txt.
+-Option USE_TEXTTRANS in braille.h, allows mapping of braille keyboard
+   input to characters to be performed using the output text translation
+   table (use definable) instead of the hardcoded US table. Useful if you
+   don't use the US table or if you want to enter characters numbered
+   >127 (such as accents for me). Obviously your output dot translation
+   table must not have duplicate representations for characters you want
+   to type. If that option is selected, dot 8 no longer generates a META
+   key.
+-Added DOT8SHIFT for use with USE_TEXTTRANS: 26-chord means the next
+   input char should be looked up with dot8 added.
+-Added bindings for search forward/backward (NXSEARCH), move by
+   paragraphs (NXBLNKLN), switch to next/previous virtual terminal
+   (SWITCHVT_NEXT).
+-Added a binding (246) for escape. Sometimes you want escape alone, or
+   followed by delete... and the META key doesn't do it as it expects a
+   character to combine the META to.
+-Made the "restart driver" command dangerous (required chord), as it
+   tended to kill the driver for me if I continued typing while it
+   resets...
+-Modified repeat count so that you can give the command right away, no
+   need to do e-chord. You can do a one-digit repeat of a movement key in
+   three strokes now.
+-Abused the repeat count function to allow switching directly to a VT
+   given it's number: o-chord then VT number then s-chord or :-chord.
+   NB: Changed to v-chord / #-chord.
+-Fixed a very nice little bug that caused the internal cursor to go off
+   the display during g-chord with advance bar left.
+-Fixed a statement that caused a portability problem.
+-Fixed k-chord which activated but did not toggle off keyboard emulation.
+-Renamed brltty_genhelp.txt to help.src and brlttyhelp?.txt to help?.txt.
+
+Anecdotes on weird bugs:
+
+The code:
+    for (i = 0; i < blitesz; rawdata[i++] = blitetrans[rawdata[i]]);
+can, in some (not yet quite clear) circumstances and under other
+processor architectures, not have the desired effect: all the text
+appears shifted by one cell. Supposedly the behavior for such a construct
+is undefined with gcc. So we put the i++ outside the assignment
+statement.
+
+The code:
+   int_cursor = MAX (int_cursor - blitesz / 4, 1);
+did not have the intended effect. int_cursor was signed and blitesz
+unsigned. The compiler somehow decided to evaluate the comparison as if
+the first argument was unsigned, which caused the MAX strategy to fail
+and the cursor to go off the display. Declaring blitesz as signed fixed
+it.
diff --git a/Drivers/Braille/BrailleLite/bindings.h b/Drivers/Braille/BrailleLite/bindings.h
new file mode 100644
index 0000000..8b7eca8
--- /dev/null
+++ b/Drivers/Braille/BrailleLite/bindings.h
@@ -0,0 +1,276 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleLite/bindings.h - key bindings for BLazie Engineering's Braille Lite
+ * N. Nair, 5 September 1998
+ */
+
+#ifndef BRLTTY_INCLUDED_BL_BINDINGS
+#define BRLTTY_INCLUDED_BL_BINDINGS
+
+#include "brl_cmds.h"		/* for BRL_CMD_* codes */
+
+/* When the Braille Lite sends braille key information, bits 0-5 represent
+ * dots 1-6 and bit 6 represents the space bar.  For now, we mask out bit 6
+ * and just use 64-byte tables.
+ */
+
+/* The static arrays must only be in braille.c, so just in case ... */
+#ifdef BL_NEED_ARRAYS
+#undef BL_NEED_ARRAYS
+
+static const unsigned char brltrans[64] =
+{
+  ' ', 'a', '1', 'b', '\'', 'k', '2', 'l',
+  '`', 'c', 'i', 'f', '/', 'm', 's', 'p',
+  '"', 'e', '3', 'h', '9', 'o', '6', 'r',
+  '~', 'd', 'j', 'g', '>', 'n', 't', 'q',
+  ',', '*', '5', '<', '-', 'u', '8', 'v',
+  '.', '%', '{', '$', '+', 'x', '!', '&',
+  ';', ':', '4', '|', '0', 'z', '7', '(',
+  '_', '?', 'w', '}', '#', 'y', ')', '='
+};
+
+#ifdef USE_TEXTTRANS
+/* Map from key representations (bits 0-5 for dots 1-6) to BRLTTY dot
+   pattern representation (dot 1 bit 0, dot 4 bit 1, dot 2 bit 2, etc) */
+static const unsigned char keys_to_dots[0100] = {
+  /* 000 */ 0,
+  /* 001 */ BRL_DOT1,
+  /* 002 */ BRL_DOT2,
+  /* 003 */ BRL_DOT1 | BRL_DOT2,
+  /* 004 */ BRL_DOT3,
+  /* 005 */ BRL_DOT1 | BRL_DOT3,
+  /* 006 */ BRL_DOT2 | BRL_DOT3,
+  /* 007 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3,
+  /* 010 */ BRL_DOT4,
+  /* 011 */ BRL_DOT1 | BRL_DOT4,
+  /* 012 */ BRL_DOT2 | BRL_DOT4,
+  /* 013 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT4,
+  /* 014 */ BRL_DOT3 | BRL_DOT4,
+  /* 015 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT4,
+  /* 016 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT4,
+  /* 017 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4,
+  /* 020 */ BRL_DOT5,
+  /* 021 */ BRL_DOT1 | BRL_DOT5,
+  /* 022 */ BRL_DOT2 | BRL_DOT5,
+  /* 023 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT5,
+  /* 024 */ BRL_DOT3 | BRL_DOT5,
+  /* 025 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT5,
+  /* 026 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT5,
+  /* 027 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5,
+  /* 030 */ BRL_DOT4 | BRL_DOT5,
+  /* 031 */ BRL_DOT1 | BRL_DOT4 | BRL_DOT5,
+  /* 032 */ BRL_DOT2 | BRL_DOT4 | BRL_DOT5,
+  /* 033 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5,
+  /* 034 */ BRL_DOT3 | BRL_DOT4 | BRL_DOT5,
+  /* 035 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5,
+  /* 036 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5,
+  /* 037 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5,
+  /* 040 */ BRL_DOT6,
+  /* 041 */ BRL_DOT1 | BRL_DOT6,
+  /* 042 */ BRL_DOT2 | BRL_DOT6,
+  /* 043 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT6,
+  /* 044 */ BRL_DOT3 | BRL_DOT6,
+  /* 045 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT6,
+  /* 046 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT6,
+  /* 047 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6,
+  /* 050 */ BRL_DOT4 | BRL_DOT6,
+  /* 051 */ BRL_DOT1 | BRL_DOT4 | BRL_DOT6,
+  /* 052 */ BRL_DOT2 | BRL_DOT4 | BRL_DOT6,
+  /* 053 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6,
+  /* 054 */ BRL_DOT3 | BRL_DOT4 | BRL_DOT6,
+  /* 055 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6,
+  /* 056 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6,
+  /* 057 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6,
+  /* 060 */ BRL_DOT5 | BRL_DOT6,
+  /* 061 */ BRL_DOT1 | BRL_DOT5 | BRL_DOT6,
+  /* 062 */ BRL_DOT2 | BRL_DOT5 | BRL_DOT6,
+  /* 063 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6,
+  /* 064 */ BRL_DOT3 | BRL_DOT5 | BRL_DOT6,
+  /* 065 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6,
+  /* 066 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6,
+  /* 067 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6,
+  /* 070 */ BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 071 */ BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 072 */ BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 073 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 074 */ BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 075 */ BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 076 */ BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6,
+  /* 077 */ BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6
+};
+#endif /* USE_TEXTTRANS */
+
+/* This table is for global BRLTTY commands, to be passed straight back to
+ * the main module.  If keyboard emulation is off, they will work with or
+ * without a chord, unless they are marked as dangerous in dangcmd[] below.
+ *
+ * Note that key combinations used to initiate internal commands should be
+ * left as 0 here.
+ */
+
+static const int cmdtrans[0100] = {
+  /* Oct   Dots      Command */
+  /* 000 (      ) */ 0,
+  /* 001 (1     ) */ BRL_CMD_LNUP,
+  /* 002 ( 2    ) */ BRL_CMD_KEY(CURSOR_LEFT),
+  /* 003 (12    ) */ BRL_CMD_KEY(BACKSPACE),
+  /* 004 (  3   ) */ BRL_CMD_CHRLT,
+  /* 005 (1 3   ) */ 0 /*k: kbemu*/,
+  /* 006 ( 23   ) */ BRL_CMD_KEY(CURSOR_UP),
+  /* 007 (123   ) */ BRL_CMD_TOP_LEFT,
+  /* 010 (   4  ) */ BRL_CMD_LNDN,
+  /* 011 (1  4  ) */ BRL_CMD_CSRTRK,
+  /* 012 ( 2 4  ) */ BRL_CMD_DISPMD,
+  /* 013 (12 4  ) */ BRL_CMD_FREEZE,
+  /* 014 (  34  ) */ BRL_CMD_INFO,
+  /* 015 (1 34  ) */ BRL_CMD_MUTE,
+  /* 016 ( 234  ) */ BRL_CMD_NXSEARCH,
+  /* 017 (1234  ) */ BRL_CMD_PASTE,
+  /* 020 (    5 ) */ BRL_CMD_KEY(CURSOR_RIGHT),
+  /* 021 (1   5 ) */ 0 /*e: endcmd*/,
+  /* 022 ( 2  5 ) */ 0 /*25: prefs*/,
+  /* 023 (12  5 ) */ BRL_CMD_HOME,
+  /* 024 (  3 5 ) */ 0 /*35: meta*/,
+  /* 025 (1 3 5 ) */ 0 /*o: number*/,
+  /* 026 ( 23 5 ) */ BRL_CMD_LNBEG,
+  /* 027 (123 5 ) */ BRL_CMD_RESTARTBRL,
+  /* 030 (   45 ) */ BRL_CMD_CSRJMP_VERT,
+  /* 031 (1  45 ) */ BRL_CMD_KEY(DELETE),
+  /* 032 ( 2 45 ) */ BRL_CMD_BLK(ROUTE),
+  /* 033 (12 45 ) */ 0 /*g: internal cursor*/,
+  /* 034 (  345 ) */ 0 /*345: blite internal speechbox*/,
+  /* 035 (1 345 ) */ BRL_CMD_NXPGRPH,
+  /* 036 ( 2345 ) */ BRL_CMD_KEY(TAB),
+  /* 037 (12345 ) */ 0 /*q: finish uppercase*/,
+  /* 040 (     6) */ BRL_CMD_CHRRT,
+  /* 041 (1    6) */ BRL_CMD_BLK(COPY_LINE),
+  /* 042 ( 2   6) */ 0 /*26: dot8shift*/,
+  /* 043 (12   6) */ BRL_CMD_BLK(CLIP_ADD),
+  /* 044 (  3  6) */ BRL_CMD_SAY_LINE,
+  /* 045 (1 3  6) */ 0 /*u: uppsercase*/,
+  /* 046 ( 23  6) */ BRL_CMD_BLK(CLIP_NEW),
+  /* 047 (123  6) */ BRL_CMD_SWITCHVT_NEXT,
+  /* 050 (   4 6) */ BRL_CMD_KEY(ENTER),
+  /* 051 (1  4 6) */ 0 /*146: free*/,
+  /* 052 ( 2 4 6) */ BRL_CMD_KEY(ESCAPE),
+  /* 053 (12 4 6) */ BRL_CMD_PRPGRPH,
+  /* 054 (  34 6) */ 0 /*346: free*/,
+  /* 055 (1 34 6) */ 0 /*x: xtrl*/,
+  /* 056 ( 234 6) */ BRL_CMD_SIXDOTS,
+  /* 057 (1234 6) */ 0 /*12346: free*/,
+  /* 060 (    56) */ BRL_CMD_KEY(CURSOR_DOWN),
+  /* 061 (1   56) */ BRL_CMD_PRSEARCH,
+  /* 062 ( 2  56) */ BRL_CMD_LNEND,
+  /* 063 (12  56) */ BRL_CMD_BACK,
+  /* 064 (  3 56) */ BRL_CMD_BLK(COPY_RECT),
+  /* 065 (1 3 56) */ 0 /*z: abort*/,
+  /* 066 ( 23 56) */ 0 /*2356: rotate*/,
+  /* 067 (123 56) */ BRL_CMD_NXPROMPT,
+  /* 070 (   456) */ BRL_CMD_BOT_LEFT,
+  /* 071 (1  456) */ BRL_CMD_HELP,
+  /* 072 ( 2 456) */ 0 /*w: free*/,
+  /* 073 (12 456) */ BRL_CMD_LEARN,
+  /* 074 (  3456) */ BRL_CMD_SWITCHVT_PREV,
+  /* 075 (1 3456) */ 0 /*y: free*/,
+  /* 076 ( 23456) */ BRL_CMD_PRPROMPT,
+  /* 077 (123456) */ 0 /*123456: noop*/
+};
+
+/* Dangerous commands; 1 bit per command, order as cmdtrans[], set if
+ * the corresponding command is dangerous.
+ */
+
+static const unsigned char dangcmd[8] =
+{ 0x00, 0x88, 0x80, 0x05, 0x40, 0x00, 0x10, 0x00 };
+
+typedef int BarCmds[16];
+const BarCmds *barcmds;
+
+/* Two advance bar commands. */
+static const BarCmds bar2cmds =
+{
+/* LeftBar\ RightBar> None         Right        Left         Both         */
+/*         None    */ 0          , BRL_CMD_FWINRT , BRL_CMD_LNDN   , BRL_CMD_HWINRT ,
+/*         Right   */ BRL_CMD_LNUP   , BRL_CMD_ATTRDN , BRL_CMD_ATTRUP , 0          ,
+/*         Left    */ BRL_CMD_FWINLT , BRL_CMD_NXDIFLN, BRL_CMD_PRDIFLN, 0          ,
+/*         Both    */ BRL_CMD_HWINLT , BRL_CMD_BOT    , BRL_CMD_TOP    , 0
+};
+
+/* One advance bar commands. */
+static const BarCmds bar1cmds =
+{
+/*          None         Left         Right        Both         */
+/* None  */ 0          , BRL_CMD_FWINLT , BRL_CMD_FWINRT , 0          ,
+/* Right */ BRL_CMD_FWINRT , 0          , 0          , 0          ,
+/* Left  */ BRL_CMD_FWINLT , 0          , 0          , 0          ,
+/* Both  */ 0          , 0          , 0          , 0
+};
+
+/* Left whiz wheel commands. */
+static const int lwwcmds[] =
+/* None         Up           Down         Press        */
+  {0          , BRL_CMD_LNUP   , BRL_CMD_LNDN   , BRL_CMD_ATTRVIS};
+
+/* Right whiz wheel commands. */
+static const int rwwcmds[] =
+/* None         Up           Down         Press        */
+  {0          , BRL_CMD_FWINLT , BRL_CMD_FWINRT , BRL_CMD_CSRVIS };
+
+#endif /* BL_NEED_ARRAYS */
+
+
+/*
+ * Functions for the advance bar.  Currently, these are passed straight
+ * back to the main module, so have to be global commands.
+ */
+
+/* BrailleLite 18 */
+#define BLT_BARLT BRL_CMD_FWINLT
+#define BLT_BARRT BRL_CMD_FWINRT
+
+
+/* Internal commands.  The definitions use the ASCII codes from brltrans[]
+ * above.  All must be chorded.
+ */
+
+#define BLT_KBEMU 'k'
+#define BLT_ROTATE '7'
+#define BLT_POSITN 'g'
+#define BLT_REPEAT 'o'
+#define BLT_CONFIG '3'
+#define BLT_ENDCMD 'e'
+#define BLT_ABORT 'z'
+#define SWITCHVT_NEXT 'v'
+#define SWITCHVT_PREV '#'
+/* These are valid only in repeat input mode. They duplicate existing
+   normal commands. */
+#define O_SETMARK 's'
+#define O_GOTOMARK 'm'
+
+/* For keyboard emulation mode: */
+#define BLT_UPCASE 'u'
+#define BLT_UPCOFF 'q'
+#define BLT_CTRL 'x'
+#ifdef USE_TEXTTRANS
+#define BLT_DOT8SHIFT '5'
+#endif /* USE_TEXTTRANS */
+#define BLT_META '9'
+
+#endif /* BRLTTY_INCLUDED_BL_BINDINGS */
diff --git a/Drivers/Braille/BrailleLite/braille.c b/Drivers/Braille/BrailleLite/braille.c
new file mode 100644
index 0000000..c25c260
--- /dev/null
+++ b/Drivers/Braille/BrailleLite/braille.c
@@ -0,0 +1,852 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleLite/braille.c - Braille display library
+ * For Blazie Engineering's Braille Lite series
+ * Author: Nikhil Nair <nn201@cus.cam.ac.uk>
+ * Copyright (C) 1998 by Nikhil Nair.
+ * Some additions by: Nicolas Pitre <nico@fluxnic.net>
+ * Some modifications copyright 2001 by Stéphane Doyon <s.doyon@videotron.ca>.
+ * Some additions by: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+#include "timing.h"
+#include "async_wait.h"
+#include "message.h"
+
+typedef enum {
+  PARM_BAUDRATE,
+  PARM_KBEMU
+} DriverParameter;
+#define BRLPARMS "baudrate", "kbemu"
+
+#include "brl_driver.h"
+#include "braille.h"
+#include "io_serial.h"
+
+#define BL_NEED_ARRAYS
+#include "bindings.h"		/* for keybindings */
+
+#define QSZ 256			/* size of internal input queue in bytes */
+#define INT_CSR_SPEED 2		/* on/off time in cycles */
+#define ACK_TIMEOUT 1000	/* timeout in ms for an ACK to come back */
+
+SerialDevice *BL_serialDevice = NULL;		/* file descriptor for Braille display */
+
+static unsigned char *prevdata = NULL;	/* previously received data */
+static unsigned char *rawdata = NULL;	/* writebrl() buffer for raw Braille data */
+static int blitesz;	/* set to 18 or 40 */
+static int waiting_ack = 0;	/* waiting acknowledgement flag */
+static int reverse_kbd = 0;	/* reverse keyboard flag */
+static int intoverride = 0;	/* internal override flag -
+				 * highly dubious behaviour ...
+				 */
+static int int_cursor = 0;	/* position of internal cursor: 0 = none */
+static unsigned int kbemu = 1; /* keyboard emulation (whether you can type) */
+
+/* The input queue is only manipulated by the qput() and qget()
+ * functions.
+ */
+static unsigned char *qbase = NULL;	/* start of queue in memory */
+static int qoff = 0;		/* offset of first byte */
+static int qlen = 0;		/* number of items in the queue */
+
+
+/* Data type for a Braille Lite key, including translation into command
+ * codes:
+ */
+typedef struct
+  {
+    unsigned char raw;		/* raw value, after any keyboard reversal */
+    int cmd;			/* command code */
+    unsigned char asc;		/* ASCII translation of Braille keys */
+    unsigned char spcbar;	/* 1 = on, 0 = off */
+    unsigned char routing;	/* routing key number */
+  }
+blkey;
+
+
+static int
+qput (unsigned char c)
+{
+  if (qlen == QSZ)
+    return EOF;
+  qbase[(qoff + qlen++) % QSZ] = c;
+  return 0;
+}
+
+
+static int
+qget (blkey * kp)
+{
+  unsigned char c;
+  int how;
+  static const unsigned char counts[] = {1, 3, 3};
+  unsigned char count;
+
+  if (qlen == 0)
+    return EOF;
+  c = qbase[qoff];
+
+  /* extended sequences start with a zero */
+  how = (c == 0)? 1:
+        (c == 0x80 && blitesz != 18)? 2:
+        0;
+  count = counts[how];
+  if (qlen < count)
+    return EOF;
+
+  memset (kp, 0, sizeof (*kp));
+  switch (how) {
+    case 0: /* non-extended sequences (BL18) */
+      /* We must deal with keyboard reversal here: */
+      if (reverse_kbd)
+	{
+	  if (c >= 0x80)	/* advance bar */
+	    c ^= 0x03;
+	  else
+	    c = (c & 0x40) | ((c & 0x38) >> 3) | ((c & 0x07) << 3);
+	}
+
+      /* Now we fill in all the info about the keypress: */
+      if (c >= 0x80)		/* advance bar */
+	{
+	  kp->raw = c;
+	  switch (c) {
+	    case 0x83:		/* left */
+	      kp->cmd = BLT_BARLT;
+	      break;
+
+	    case 0x80:		/* right */
+	      kp->cmd = BLT_BARRT;
+	      break;
+
+	    default:		/* unrecognised keypress */
+	      kp->cmd = 0;
+              break;
+	  }
+	}
+      else
+	{
+	  kp->spcbar = ((c & 0x40)? 1: 0);
+	  c &= 0x3f;		/* leave only dot key info */
+	  kp->raw = c;
+	  kp->cmd = cmdtrans[c];
+	  kp->asc = brltrans[c];
+	}
+      break;
+
+    case 1: { /* extended sequences (BL40) */
+      unsigned char c2 = qbase[((qoff + 1) % QSZ)];
+      unsigned char c3 = qbase[((qoff + 2) % QSZ)];
+
+      /* We must deal with keyboard reversal here: */
+      if (reverse_kbd)
+	{
+	  if (c2 == 0)
+	    {			/* advance bars or routing keys */
+	      if (c3 & 0x80)	/* advance bars */
+		c3 = ((c3 & 0xF0) |
+		      ((c3 & 0x1) << 3) | ((c3 & 0x2) << 1) |
+		      ((c3 & 0x4) >> 1) | ((c3 & 0x8) >> 3));
+	      else if (c3 > 0 && c3 <= blitesz)
+		c3 = blitesz - c3 + 1;
+	    }
+	  else
+            {
+              c2 = (((c2 & 0x38) >> 3) | ((c2 & 0x07) << 3) |
+                    ((c2 & 0x40) << 1) | ((c2 & 0x80) >> 1));
+              c3 = ((c3 & 0x40) | ((c3 & 0x38) >> 3) | ((c3 & 0x07) << 3));
+            }
+	}
+
+      /* Now we fill in all the info about the keypress: */
+      if (c2 == 0)		/* advance bars or routing keys */
+	{
+	  kp->raw = c3;
+	  if (c3 & 0x80)
+            kp->cmd = (*barcmds)[c3 & 0xF];
+	  else if (c3 > 0 && c3 <= blitesz)
+	    kp->routing = c3;
+	}
+      else
+	{
+	  kp->spcbar = ((c3 & 0x40)? 1: 0);
+	  c3 &= 0x3f;		/* leave only dot key info */
+	  kp->raw = (c2 & 0xC0) | c3; /* combine info for all 8 dots */
+	  /* c2&0x3F and c3&0x3F are the same, i.e. dots 1-6. */
+	  kp->cmd = cmdtrans[c3];
+	  kp->asc = brltrans[c3];
+	}
+      break;
+    }
+
+    case 2: { /* extended sequences (millennium) */
+      unsigned char c3 = qbase[((qoff + 2) % QSZ)];
+
+      /* We must deal with keyboard reversal here: */
+      if (reverse_kbd)
+        c3 = ((c3 & 0x11) << 3) | ((c3 & 0x22) << 1) | ((c3 & 0x44) >> 1) | ((c3 & 0x88) >> 3);
+      kp->raw = c3;
+
+      if (c3 & 0x0f)
+        kp->cmd = (*barcmds)[((c3 & 0x1) << 3) | ((c3 & 0x2) << 1) | ((c3 & 0x4) >> 1) | ((c3 & 0x8) >> 3)];
+      else if (c3 & 0x30)
+        kp->cmd = rwwcmds[(c3 >> 4) & 0x3];
+      else if (c3 & 0xc0)
+        kp->cmd = lwwcmds[(c3 >> 6) & 0x3];
+      else
+        kp->cmd = 0;
+      break;
+    }
+
+    default:
+      kp->cmd = 0;
+      break;
+  }
+
+  /* adjust queue variables for next member */
+  qoff = (qoff + count) % QSZ;
+  qlen -= count;
+
+  return 0;
+}
+
+
+static void
+qfill (void)
+{
+  unsigned char c;		/* character buffer */
+
+  while (serialReadData (BL_serialDevice, &c, 1, 0, 0) == 1)
+    {
+      if (waiting_ack && c == 5)	/* ^e is the acknowledgement character ... */
+	waiting_ack = 0;
+      else
+	qput (c);
+    }
+}
+
+
+static void
+qflush (void)
+{
+  qfill();
+  qlen = 0;
+}
+
+static int
+await_ack (void) {
+  TimePeriod period;
+  startTimePeriod(&period, ACK_TIMEOUT);
+  waiting_ack = 1;
+  do {
+    asyncWait(10);	/* sleep for 10 ms */
+    qfill();
+    if (!waiting_ack) return 1;
+  } while (!afterTimePeriod(&period, NULL));
+  return 0;
+}
+
+static void
+write_prebrl (void) {
+  static const unsigned char request[] = {0X05, 0X44};			/* code to send before Braille */
+  serialWriteData(BL_serialDevice, request, sizeof(request));
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device)
+{
+  static const unsigned int good_baudrates[] =
+    {300,600,1200,2400,4800,9600,19200,38400, 0};
+  unsigned int baudrate;
+  /* Init string for Model detection */
+
+  if (!*parameters[PARM_BAUDRATE] ||
+      !serialValidateBaud(&baudrate, "baud rate",
+		    parameters[PARM_BAUDRATE], good_baudrates))
+    baudrate = BAUDRATE;
+
+  if (*parameters[PARM_KBEMU])
+    if (!validateYesNo(&kbemu, parameters[PARM_KBEMU]))
+      logMessage(LOG_WARNING, "%s: %s", "invalid keyboard emulation setting", parameters[PARM_KBEMU]);
+  kbemu = !!kbemu;
+
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  logMessage(LOG_DEBUG, "Opening serial port: %s", device);
+  if ((BL_serialDevice = serialOpenDevice(device))) {
+    if (serialRestartDevice(BL_serialDevice, baudrate)) {
+      if (serialSetFlowControl(BL_serialDevice, SERIAL_FLOW_HARDWARE)) {
+        if ((qbase = malloc(QSZ))) {
+          qflush();
+          write_prebrl();
+
+          if (await_ack()) {
+            logMessage(LOG_DEBUG, "Got response.");
+
+            /* Next, let's detect the BLT-Model (18, 40, M20, M40). */
+            barcmds = &bar2cmds;
+            {
+              unsigned char cells[18];
+
+              memset(cells, 0, sizeof(cells));
+              serialWriteData(BL_serialDevice, cells, sizeof(cells));
+              waiting_ack = 1;
+              asyncWait(400);
+              qfill();
+
+              if (waiting_ack) {
+                /* no response, so it must be BLT40 */
+                blitesz = 40;
+                brl->keyBindings = "40_m20_m40";
+              } else {
+                blitesz = sizeof(cells);
+                brl->keyBindings = "18";
+              }
+            }
+
+            {
+              static const unsigned char request[] = {0X05, 0X57};			/* code to send before Braille */
+
+              asyncWait(200);
+              qflush();
+              serialWriteData(BL_serialDevice, request, sizeof(request));
+              waiting_ack = 0;
+              asyncWait(200);
+              qfill();
+
+              if (qlen) {
+                char response[qlen + 1];
+                int length = 0;
+
+                do {
+                  unsigned char byte = qbase[qoff % QSZ];
+
+                  qoff = (qoff + 1) % QSZ, --qlen;
+                  if (!byte) break;
+                  response[length++] = byte;
+                } while (qlen);
+
+                response[length] = 0;
+                logMessage(LOG_INFO, "Braille Lite identity: %s", response);
+
+                if ((response[0] == 'X') &&
+                    (response[1] == ' ') &&
+                    (response[2] == 'B')) {
+                  blitesz = atoi(&response[3]);
+                  if (blitesz <= 20) barcmds = &bar1cmds;
+                }
+              }
+            }
+
+            logMessage(LOG_NOTICE, "Braille Lite %d detected.", blitesz);
+            brl->textColumns = blitesz;	/* initialise size of display - */
+            brl->textRows = 1;		/* Braille Lites are single line displays */
+
+            makeOutputTable(dotsTable_ISO11548_1);
+            makeInputTable();
+
+            /* Allocate space for buffers */
+            if ((prevdata = malloc(brl->textColumns))) {
+              memset(prevdata, 0, brl->textColumns);
+
+              if ((rawdata = malloc(brl->textColumns))) {
+                return 1;
+
+              //free(rawdata);
+              //rawdata = NULL;
+              } else {
+                logMallocError();
+              }
+
+              free(prevdata);
+              prevdata = NULL;
+            } else {
+              logMallocError();
+            }
+          } else {
+            logMessage(LOG_DEBUG, "BrailleLite not responding.");
+          }
+
+          free(qbase);
+          qbase = NULL;
+        } else {
+          logMallocError();
+        }
+      }
+    }
+
+    serialCloseDevice(BL_serialDevice);
+    BL_serialDevice = NULL;
+  }
+
+  return 0;
+}
+
+
+static void
+brl_destruct (BrailleDisplay * brl)
+{
+  if (rawdata) {
+    free(rawdata);
+    rawdata = NULL;
+  }
+
+  if (prevdata) {
+    free(prevdata);
+    prevdata = NULL;
+  }
+
+  if (qbase) {
+    free(qbase);
+    qbase = NULL;
+  }
+
+  if (BL_serialDevice) {
+    serialCloseDevice(BL_serialDevice);
+    BL_serialDevice = NULL;
+  }
+}
+
+
+static int
+brl_writeWindow (BrailleDisplay * brl, const wchar_t *text)
+{
+  short i;			/* loop counter */
+
+  /* If the intoverride flag is set, then calls to writebrl() from the main
+   * module are ignored, because the display is in internal use.
+   * This is highly antisocial behaviour!
+   */
+  if (intoverride)
+    return 1;
+
+  /* First, the internal cursor: */
+  if (int_cursor)
+    {
+      static int timer = 0;		/* for internal cursor */
+      timer = (timer + 1) % (INT_CSR_SPEED * 2);
+      brl->buffer[int_cursor - 1] = (timer < INT_CSR_SPEED)?
+                                      (BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7):
+                                      (BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8);
+    }
+
+  /* Next we must handle display reversal: */
+  if (reverse_kbd) {
+    for (i=0; i<blitesz; i+=1) {
+      rawdata[i] = translateInputCell(brl->buffer[blitesz - 1 - i]);
+    }
+  } else {
+    memcpy(rawdata, brl->buffer, blitesz);
+  }
+
+  /* Only refresh display if the data has changed: */
+  if (cellsHaveChanged(prevdata, rawdata, blitesz, NULL, NULL, NULL))
+    {
+      /* Dot mapping from standard to BrailleLite: */
+      translateOutputCells(rawdata, rawdata, blitesz);
+
+      /* First we process any pending keystrokes, just in case any of them
+       * are ^e ...
+       */
+      waiting_ack = 0;		/* Not really necessary, but ... */
+      qfill ();
+
+      /* Next we send the ^eD sequence, and wait for an ACK */
+      waiting_ack = 1;
+      /* send the ^ED... */
+      write_prebrl();
+      if (!await_ack()) return 1;
+
+      /* OK, now we'll suppose we're all clear to send Braille data. */
+      serialWriteData(BL_serialDevice, rawdata, blitesz);
+      await_ack();
+    }
+  return 1;
+}
+
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context)
+{
+  static enum {
+    ST_NORMAL,	/* transparent */
+    ST_CURSOR,	/* position internal cursor */
+    ST_REPEAT,	/* set repeat count */
+    ST_CONFIG	/* preferences options */
+  } state = ST_NORMAL;
+  static int repeat = 0;		/* repeat count for command */
+  static int repeatNext = 0; /* flag to indicate  whether 0 we repeat the
+				same command or 1 we get the next command
+				and repeat that. */
+  static int hold, shift, shiftlck, ctrl, meta;
+#ifdef USE_TEXTTRANS
+  static int dot8shift;
+#endif /* USE_TEXTTRANS */
+  static blkey key;
+  static char outmsg[41];
+  int temp = BRL_CMD_NOOP;
+
+ again:
+  if(repeatNext || repeat == 0) {
+    /* Process any new keystrokes: */
+    qfill ();
+    if (qget (&key) == EOF)	/* no keys to process */
+      return EOF;
+    repeatNext = 0;
+  }
+  if(repeat>0)
+    repeat--;
+
+  /* Our overall behaviour depends on the state variable (see above). */
+  switch (state)
+    {
+    case ST_NORMAL:			/* transparent */
+      /* First we deal with external commands: */
+      do
+	{
+	  /* if it's not an external command, go on */
+	  if (!key.cmd)
+	    break;
+
+	  /* if advance bar, return with corresponding command */
+	  if (key.asc == 0)
+	    return key.cmd;
+
+	  /* I thought I was smart when I suggested to remove CMD_CUT_END.
+	     Well now there's this nasty exception: the command offset
+	     depends on the display size! */
+	  if(key.cmd == BRL_CMD_BLK(COPY_RECT) || key.cmd == BRL_CMD_BLK(COPY_LINE))
+	    key.cmd += blitesz-1;
+
+	  if(key.spcbar && (key.cmd &BRL_MSK_BLK) == BRL_CMD_BLK(PASSKEY)) {
+          /*
+	    if(!kbemu)
+	      return EOF;
+          */
+	    if (!shiftlck)
+	      shift = 0;
+	    ctrl = meta = 0;
+#ifdef USE_TEXTTRANS
+	    dot8shift = 0;
+#endif /* USE_TEXTTRANS */
+	  }
+
+	  /* always OK if chorded */
+	  if (key.spcbar)
+	    return key.cmd;
+
+	  /* kbemu could be on, then go on */
+	  if (kbemu && context == KTB_CTX_DEFAULT)
+	    break;
+
+	  /* if it's a dangerous command it should have been chorded */
+	  if (dangcmd[(key.raw & 0x38) >> 3] & (1 << (key.raw & 0x07)))
+	    break;
+
+	  /* finally we are OK */
+	  return key.cmd;
+	}
+      while (0);
+
+      /* Next, internal commands: */
+      if (key.spcbar)
+	switch (key.asc)
+	  {
+	  case BLT_KBEMU:	/* set keyboard emulation */
+	    kbemu ^= 1;
+	    shift = shiftlck = ctrl = meta = 0;
+#ifdef USE_TEXTTRANS
+	    dot8shift = 0;
+#endif /* USE_TEXTTRANS */
+	    if(kbemu)
+	      message (NULL, gettext("keyboard emu on"), MSG_SILENT);
+	    else message (NULL, gettext("keyboard emu off"), MSG_SILENT);
+	    return BRL_CMD_NOOP;
+	  case BLT_ROTATE:	/* rotate Braille Lite by 180 degrees */
+	    reverse_kbd ^= 1;
+	    return BRL_CMD_NOOP;
+	  case BLT_POSITN:	/* position internal cursor */
+	    int_cursor = blitesz / 2;
+	    state = ST_CURSOR;
+	    return BRL_CMD_NOOP;
+	  case BLT_REPEAT:	/* set repeat count */
+	    hold = 0;
+	    snprintf (outmsg, sizeof(outmsg), "%s:", gettext("repeat count"));
+	    message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
+	    intoverride = 1;
+	    state = ST_REPEAT;
+	    return BRL_CMD_NOOP;
+	  case BLT_CONFIG:	/* configuration menu */
+	    snprintf (outmsg, sizeof(outmsg), "%s? [m/s/r/z]", gettext("config"));
+	    message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
+	    intoverride = 1;
+	    state = ST_CONFIG;
+	    return BRL_CMD_NOOP;
+	  case ' ':		/* practical exception for */
+	    /* If keyboard mode off, space bar == BRL_CMD_HOME */
+	    if (!kbemu || context != KTB_CTX_DEFAULT)
+	      return BRL_CMD_HOME;
+	  }
+
+      /* check for routing keys */
+      if (key.routing)
+	return (BRL_CMD_BLK(ROUTE) + key.routing - 1);
+
+      if (!kbemu)
+	return BRL_CMD_NOOP;
+
+      /* Now kbemu is definitely on. */
+      switch (key.raw & 0xC0)
+	{
+	case 0x40:		/* dot 7 */
+	  shift = 1;
+	  break;
+	case 0xC0:		/* dot 78 */
+	  ctrl = 1;
+	  break;
+	case 0x80:		/* dot 8 */
+#ifdef USE_TEXTTRANS
+	  dot8shift = 1;
+#else /* USE_TEXTTRANS */
+	  meta = 1;
+#endif /* USE_TEXTTRANS */
+	  break;
+	}
+
+      if (key.spcbar && key.asc != ' ')
+	switch (key.asc)
+	  {
+	  case BLT_UPCASE:	/* upper case next */
+	    if (shift)
+	      shiftlck = 1;
+	    else
+	      shift = 1;
+	    return BRL_CMD_NOOP;
+	  case BLT_UPCOFF:	/* cancel upper case */
+	    shift = shiftlck = 0;
+	    return BRL_CMD_NOOP;
+	  case BLT_CTRL:	/* control next */
+	    ctrl = 1;
+	    return BRL_CMD_NOOP;
+#ifdef USE_TEXTTRANS
+	  case BLT_DOT8SHIFT:	/* add dot 8 to next pattern */
+	    dot8shift = 1;
+	    return BRL_CMD_NOOP;
+#endif /* USE_TEXTTRANS */
+	  case BLT_META:	/* meta next */
+	    meta = 1;
+	    return BRL_CMD_NOOP;
+	  case BLT_ABORT:	/* abort - quit keyboard emulation */
+	    kbemu = 0;
+	    message (NULL, gettext("keyboard emu off"), MSG_SILENT);
+	    return BRL_CMD_NOOP;
+	  default:		/* unrecognised command */
+	    shift = shiftlck = ctrl = meta = 0;
+#ifdef USE_TEXTTRANS
+	    dot8shift = 0;
+#endif /* USE_TEXTTRANS */
+	    return BRL_CMD_NOOP;
+	  }
+
+      /* OK, it's an ordinary (non-chorded) keystroke, and kbemu is on. */
+#ifndef USE_TEXTTRANS
+      if (ctrl && key.asc >= 96)
+	/* old code was (key.asc & 0x1f) */
+	temp = BRL_CMD_BLK(PASSCHAR) | key.asc | BRL_FLG_INPUT_CONTROL;
+      else if (meta && key.asc >= 96)
+	temp = BRL_CMD_BLK(PASSCHAR) | key.asc | BRL_FLG_INPUT_META;
+      else if (shift && (key.asc & 0x40))
+	/* old code was (key.asc & 0xdf) */
+	temp = BRL_CMD_BLK(PASSCHAR) | key.asc | BRL_FLG_INPUT_SHIFT;
+      else
+	temp = BRL_CMD_BLK(PASSCHAR) | key.asc;
+#else /* USE_TEXTTRANS */
+      temp = BRL_CMD_BLK(PASSDOTS) |
+	(keys_to_dots[key.raw &0x3F]
+	 | ((meta) ? BRL_FLG_INPUT_META : 0)
+	 | ((ctrl) ? (BRL_DOT7 | BRL_DOT8) : 
+	    (shift) ? BRL_DOT7 : 
+	    (dot8shift) ? BRL_DOT8 : 0));
+#endif /* USE_TEXTTRANS */
+      if (!shiftlck)
+	shift = 0;
+      ctrl = meta = 0;
+#ifdef USE_TEXTTRANS
+      dot8shift = 0;
+#endif /* USE_TEXTTRANS */
+      outmsg[0] = 0;
+      return temp;
+
+    case ST_CURSOR:			/* position internal cursor */
+      switch (key.cmd)
+	{
+	case BRL_CMD_HOME:		/* go to middle */
+	  int_cursor = blitesz / 2;
+	  break;
+	case BRL_CMD_LNBEG:	/* beginning of display */
+	  int_cursor = 1;
+	  break;
+	case BRL_CMD_LNEND:	/* end of display */
+	  int_cursor = blitesz;
+	  break;
+	case BRL_CMD_FWINLT:	/* quarter left */
+	  int_cursor = MAX (int_cursor - blitesz / 4, 1);
+	  break;
+	case BRL_CMD_FWINRT:	/* quarter right */
+	  int_cursor = MIN (int_cursor + blitesz / 4, blitesz);
+	  break;
+	case BRL_CMD_CHRLT:	/* one character left */
+	  if (int_cursor > 1)
+	    int_cursor--;
+	  break;
+	case BRL_CMD_CHRRT:	/* one character right */
+	  if (int_cursor < blitesz)
+	    int_cursor++;
+	  break;
+	case BRL_CMD_BLK(ROUTE):	/* route cursor */
+	  if (key.spcbar)
+	    {
+	      temp = BRL_CMD_BLK(ROUTE) + int_cursor - 1;
+	      int_cursor = 0;
+	      state = ST_NORMAL;
+	    }
+	  return temp;
+	case BRL_CMD_BLK(CLIP_NEW):	/* begin copy */
+	case BRL_CMD_BLK(CLIP_ADD):
+	  if (key.spcbar)
+	    {
+	      temp = key.cmd + int_cursor - 1;
+	      int_cursor = 0;
+	      state = ST_NORMAL;
+	    }
+	  return temp;
+	case BRL_CMD_BLK(COPY_RECT):	/* end copy */
+	case BRL_CMD_BLK(COPY_LINE):
+	  if (key.spcbar)
+	    {
+	      temp = key.cmd + int_cursor - 1;
+	      int_cursor = 0;
+	      state = ST_NORMAL;
+	    }
+	  return temp;
+	case BRL_CMD_DISPMD: /* attribute info */
+	  temp = BRL_CMD_BLK(DESCCHAR) + int_cursor - 1;
+	  int_cursor = 0;
+	  state = ST_NORMAL;
+	  return temp;
+	default:
+	  if (key.asc == BLT_ABORT) {
+            /* cancel cursor positioning */
+	    int_cursor = 0;
+	    state = ST_NORMAL;
+          }
+	  break;
+	}
+      if (key.routing)
+	int_cursor = key.routing;
+      return BRL_CMD_NOOP;
+    case ST_REPEAT:			/* set repeat count */
+      if (key.asc >= '0' && key.asc <= '9')
+	{
+	  hold = (hold * 10 + key.asc - '0') % 100;
+	  if (hold) {
+	    snprintf (outmsg, sizeof(outmsg), "%s: %d", gettext("repeat count"), hold);
+	  } else {
+            snprintf (outmsg, sizeof(outmsg), "%s: ", gettext("repeat count"));
+          }
+	  intoverride = 0;
+	  message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
+	  intoverride = 1;
+	}
+      else if (key.routing)
+	{
+	  hold = key.routing +1;
+	  snprintf (outmsg, sizeof(outmsg), "%s: %d", gettext("repeat count"), hold);
+	  intoverride = 0;
+	  message (NULL, outmsg, MSG_SILENT | MSG_NODELAY);
+	  intoverride = 1;
+	}
+      else {
+	intoverride = 0;
+	outmsg[0] = 0;
+	state = ST_NORMAL;
+	if (hold > 0) {
+	  if (key.asc == SWITCHVT_NEXT || key.asc == SWITCHVT_PREV)
+	    /* That's chorded or not... */
+	    return BRL_CMD_BLK(SWITCHVT) + (hold-1);
+	  else if (key.asc == O_SETMARK)
+	    return BRL_CMD_BLK(SETMARK) + (hold-1);
+	  else if (key.asc == O_GOTOMARK)
+	    return BRL_CMD_BLK(GOTOMARK) + (hold-1);
+	  else if (key.spcbar)		/* chorded */
+	    switch (key.asc)
+	      {
+	      case BLT_ENDCMD:	/* set repeat count */
+		if (hold > 1) {
+		  /* repeat next command */
+		  repeat = hold;
+		  repeatNext = 1;
+		}
+		/* fall through */
+	      case BLT_ABORT:	/* abort or endcmd */
+		return BRL_CMD_NOOP;
+	      }
+	  /* if the key is any other, start repeating it. */
+	  repeat = hold;
+	  goto again;
+	}
+      }
+      return BRL_CMD_NOOP;
+    case ST_CONFIG:			/* preferences options */
+      switch (key.asc)
+	{
+	case 'm':		/* preferences menu */
+	  intoverride = 0;
+	  state = ST_NORMAL;
+	  return BRL_CMD_PREFMENU;
+	case 's':		/* save preferences */
+	  intoverride = 0;
+	  state = ST_NORMAL;
+	  return BRL_CMD_PREFSAVE;
+	case 'r':		/* restore saved preferences */
+	  intoverride = 0;
+	  state = ST_NORMAL;
+	  return BRL_CMD_PREFLOAD;
+	case BLT_ABORT:	/* abort */
+	  intoverride = 0;
+	  state = ST_NORMAL;
+	default:		/* in any case */
+	  return BRL_CMD_NOOP;
+	}
+    }
+
+  /* We should never reach this point ... */
+  return EOF;
+}
diff --git a/Drivers/Braille/BrailleLite/braille.h b/Drivers/Braille/BrailleLite/braille.h
new file mode 100644
index 0000000..f770fbe
--- /dev/null
+++ b/Drivers/Braille/BrailleLite/braille.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleLite/braille.h - Configurable definitions for the Braille Lite driver
+ * N. Nair, 6 September 1998
+ *
+ * Edit as necessary for your system.
+ */
+
+/* used by speech.c */
+#include "io_serial.h"
+extern SerialDevice *BL_serialDevice;
+
+/* We always expect 8 data bits, no parity, 1 stop bit. */
+/* Select baudrate to use */
+#define BAUDRATE 9600
+//#define BAUDRATE 38400
+
+/* Define the following for dots to character mapping for input to use 
+   the same (user-defined) table as is used for output, instead of the
+   hard-coded US table. */
+#define USE_TEXTTRANS
diff --git a/Drivers/Braille/BrailleMemo/Makefile.in b/Drivers/Braille/BrailleMemo/Makefile.in
new file mode 100644
index 0000000..6d5b3db
--- /dev/null
+++ b/Drivers/Braille/BrailleMemo/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mm
+DRIVER_NAME = BrailleMemo
+DRIVER_USAGE = Pocket (16), Smart (16), 32, 40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/BrailleMemo/braille.c b/Drivers/Braille/BrailleMemo/braille.c
new file mode 100644
index 0000000..9562c30
--- /dev/null
+++ b/Drivers/Braille/BrailleMemo/braille.c
@@ -0,0 +1,528 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-mm.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+#define START_INPUT_TIMEOUT 1000
+
+#define MM_KEY_GROUP_ENTRY(s,n) BRL_KEY_GROUP_ENTRY(MM, s, n)
+#define MM_KEY_NAME_ENTRY(s,k,n) BRL_KEY_NAME_ENTRY(MM, s, k, n)
+
+#define MM_SHIFT_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(SHIFT, k, n)
+#define MM_DOT_KEY_ENTRY(k) MM_KEY_NAME_ENTRY(DOT, k, "dot" #k)
+#define MM_EDIT_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(EDIT, k, n)
+#define MM_ARROW_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(ARROW, k, n)
+#define MM_DISPLAY_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(DISPLAY, k, n)
+
+BEGIN_KEY_NAME_TABLE(shift)
+  MM_SHIFT_KEY_ENTRY(F1, "PanLeft"),
+  MM_SHIFT_KEY_ENTRY(F3, "Extension"),
+  MM_SHIFT_KEY_ENTRY(F4, "PanRight"),
+
+  MM_SHIFT_KEY_ENTRY(F1, "F1"),
+  MM_SHIFT_KEY_ENTRY(F2, "F2"),
+  MM_SHIFT_KEY_ENTRY(F3, "F3"),
+  MM_SHIFT_KEY_ENTRY(F4, "F4"),
+
+  MM_SHIFT_KEY_ENTRY(CONTROL, "Control"),
+  MM_SHIFT_KEY_ENTRY(ALT, "Alt"),
+  MM_SHIFT_KEY_ENTRY(SELECT, "Select"),
+  MM_SHIFT_KEY_ENTRY(READ, "Read"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(dot)
+  MM_DOT_KEY_ENTRY(1),
+  MM_DOT_KEY_ENTRY(2),
+  MM_DOT_KEY_ENTRY(3),
+  MM_DOT_KEY_ENTRY(4),
+  MM_DOT_KEY_ENTRY(5),
+  MM_DOT_KEY_ENTRY(6),
+  MM_DOT_KEY_ENTRY(7),
+  MM_DOT_KEY_ENTRY(8),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(edit)
+  MM_EDIT_KEY_ENTRY(ESC, "Escape"),
+  MM_EDIT_KEY_ENTRY(INF, "Info"),
+
+  MM_EDIT_KEY_ENTRY(BS, "Backspace"),
+  MM_EDIT_KEY_ENTRY(DEL, "Delete"),
+  MM_EDIT_KEY_ENTRY(INS, "Insert"),
+
+  MM_EDIT_KEY_ENTRY(CHANGE, "Change"),
+  MM_EDIT_KEY_ENTRY(OK, "OK"),
+  MM_EDIT_KEY_ENTRY(SET, "Set"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(arrow)
+  MM_ARROW_KEY_ENTRY(UP, "ArrowUp"),
+  MM_ARROW_KEY_ENTRY(DOWN, "ArrowDown"),
+  MM_ARROW_KEY_ENTRY(LEFT, "ArrowLeft"),
+  MM_ARROW_KEY_ENTRY(RIGHT, "ArrowRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(route)
+  MM_KEY_GROUP_ENTRY(ROUTE, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(display)
+  MM_DISPLAY_KEY_ENTRY(BACKWARD, "Backward"),
+  MM_DISPLAY_KEY_ENTRY(FORWARD, "Forward"),
+
+  MM_DISPLAY_KEY_ENTRY(LSCROLL, "ScrollLeft"),
+  MM_DISPLAY_KEY_ENTRY(RSCROLL, "ScrollRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(pocket)
+  KEY_NAME_TABLE(shift),
+  KEY_NAME_TABLE(dot),
+  KEY_NAME_TABLE(edit),
+  KEY_NAME_TABLE(arrow),
+  KEY_NAME_TABLE(route),
+  KEY_NAME_TABLE(display),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(smart)
+  KEY_NAME_TABLE(shift),
+  KEY_NAME_TABLE(dot),
+  KEY_NAME_TABLE(edit),
+  KEY_NAME_TABLE(arrow),
+  KEY_NAME_TABLE(route),
+  KEY_NAME_TABLE(display),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(pocket)
+DEFINE_KEY_TABLE(smart)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(pocket),
+  &KEY_TABLE_DEFINITION(smart),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  const char *identityPrefix;
+  const char *modelName;
+  const KeyTableDefinition *keyTableDefinition;
+} ModelEntry;
+
+static const ModelEntry modelEntry_pocket = {
+  .identityPrefix = "BMpk",
+  .modelName = "Braille Memo Pocket",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(pocket)
+};
+
+static const ModelEntry modelEntry_smart = {
+  .identityPrefix = "BMsmart",
+  .modelName = "Braille Memo Smart",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(smart)
+};
+
+static const ModelEntry *const modelEntries[] = {
+  &modelEntry_pocket,
+  &modelEntry_smart,
+  NULL
+};
+
+struct BrailleDataStruct {
+  const ModelEntry *model;
+
+  unsigned char forceRewrite;
+  unsigned char textCells[MM_MAXIMUM_LINE_LENGTH];
+};
+
+static const unsigned char sizeTable[] = {16, 24, 32, 40, 46};
+static const unsigned char sizeCount = ARRAY_COUNT(sizeTable);
+
+static int
+isValidSize (unsigned char size) {
+  return memchr(sizeTable, size, sizeCount) != NULL;
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+writePacket (
+  BrailleDisplay *brl,
+  unsigned char code, unsigned char subcode,
+  const unsigned char *data, size_t length
+) {
+  unsigned char bytes[sizeof(MM_CommandHeader) + length];
+  unsigned char *byte = bytes;
+
+  *byte++ = MM_HEADER_ID1;
+  *byte++ = MM_HEADER_ID2;
+
+  *byte++ = code;
+  *byte++ = subcode;
+
+  *byte++ = (length >> 0) & 0XFF;
+  *byte++ = (length >> 8) & 0XFF;
+
+  if (data) byte = mempcpy(byte, data, length);
+
+  return writeBytes(brl, bytes, byte-bytes);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case MM_HEADER_ACK:
+        case MM_HEADER_NAK:
+          *length = 1;
+          break;
+
+        case MM_HEADER_ID1:
+          *length = sizeof(MM_CommandHeader);
+          break;
+
+        default:
+          if (isValidSize(byte)) {
+            *length = 1;
+            break;
+          }
+
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    case 2:
+      if (byte != MM_HEADER_ID2) return BRL_PVR_INVALID;
+      break;
+
+    case 5:
+      *length += byte;
+      break;
+
+    case 6:
+      *length += byte << 8;
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readBytes (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, MM_CommandPacket *packet) {
+  return readBytes(brl, packet, sizeof(*packet));
+}
+
+static int
+startDisplayMode (BrailleDisplay *brl) {
+  static const unsigned char data[] = {MM_BLINK_NO, 0};
+
+  if (writePacket(brl, MM_CMD_StartDisplayMode, 0, data, sizeof(data))) {
+    if (awaitBrailleInput(brl, START_INPUT_TIMEOUT)) {
+      MM_CommandPacket response;
+      size_t size = readPacket(brl, &response);
+
+      if (size) {
+        if (response.fields.header.id1 == MM_HEADER_ACK) return 1;
+        logUnexpectedPacket(response.bytes, size);
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+endDisplayMode (BrailleDisplay *brl) {
+  return writePacket(brl, MM_CMD_EndDisplayMode, 0, NULL, 0);
+}
+
+static int
+sendBrailleData (BrailleDisplay *brl, const unsigned char *cells, size_t count) {
+  return writePacket(brl, MM_CMD_SendBrailleData, 0, cells, count);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_10C4_EA60)
+    "Silicon Labs",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Pocket */
+      .vendor=0X10C4, .product=0XEA60,
+      .manufacturers = usbManufacturers_10C4_EA60,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .serial=&serialParameters
+    },
+
+    { /* Smart */
+      .vendor=0X1148, .product=0X0301,
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=3, .outputEndpoint=2,
+      .serial=&serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  descriptor.bluetooth.channelNumber = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+detectModel (BrailleDisplay *brl, const MM_IdentityPacket *identity) {
+  const ModelEntry *const *model = modelEntries;
+
+  while (*model) {
+    const char *prefix = (*model)->identityPrefix;
+
+    if (strncmp(identity->hardwareName, prefix, strlen(prefix)) == 0) {
+      brl->data->model = *model;
+      logMessage(LOG_INFO, "detected model: %s", brl->data->model->modelName);
+      return 1;
+    }
+
+    model += 1;
+  }
+
+  logMessage(LOG_WARNING, "unrecognized model: %s", identity->hardwareName);
+  brl->data->model = &modelEntry_pocket;
+  logMessage(LOG_INFO, "assumed model: %s", brl->data->model->modelName);
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  return writePacket(brl, MM_CMD_QueryIdentity, 0, NULL, 0);
+}
+
+static BraillePacketVerifierResult
+verifyIdentityResponse (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case 0X01:
+          *length = sizeof(MM_IdentityPacket);
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readIdentityResponse (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyIdentityResponse, NULL);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const MM_IdentityPacket *identity = packet;
+
+  if ((identity->lineLength == 0) || (identity->lineLength > MM_MAXIMUM_LINE_LENGTH)) return BRL_RSP_UNEXPECTED;
+  if ((identity->lineCount == 0) || (identity->lineCount > MM_MAXIMUM_LINE_COUNT)) return BRL_RSP_UNEXPECTED;
+
+  {
+    const char *byte = identity->hardwareName;
+    const char *end = byte + sizeof(identity->hardwareName);
+
+    while (byte < end) {
+      if (!*byte) break;
+      if (!iswprint(*byte)) return BRL_RSP_UNEXPECTED;
+      byte += 1;
+    }
+  }
+
+  return BRL_RSP_DONE;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      MM_IdentityPacket identity;
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readIdentityResponse, &identity, sizeof(identity),
+                              isIdentityResponse)) {
+        detectModel(brl, &identity);
+        brl->textColumns = identity.lineLength;
+
+        if (startDisplayMode(brl)) {
+          setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+          MAKE_OUTPUT_TABLE(0X80, 0X40, 0X20, 0X08, 0X04, 0X02, 0X10, 0X01);
+
+          brl->data->forceRewrite = 1;
+          return 1;
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, endDisplayMode);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
+    unsigned char cells[brl->textColumns];
+
+    translateOutputCells(cells, brl->data->textCells, brl->textColumns);
+    if (!sendBrailleData(brl, cells, sizeof(cells))) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  MM_CommandPacket packet;
+  size_t size;
+
+  while ((size = readPacket(brl, &packet))) {
+    if ((packet.fields.header.id1 == MM_HEADER_ID1) &&
+        (packet.fields.header.id2 == MM_HEADER_ID2)) {
+      switch (packet.fields.header.code) {
+        case MM_CMD_KeyCombination:
+          switch (packet.fields.data.keys.group) {
+            case MM_GRP_SHIFT:
+              if (!packet.fields.data.keys.value) {
+                enqueueKeys(brl, packet.fields.data.keys.shift, MM_GRP_SHIFT, 0);
+                continue;
+              }
+              break;
+
+            case MM_GRP_DOT:
+            case MM_GRP_EDIT:
+            case MM_GRP_ARROW:
+            case MM_GRP_DISPLAY:
+            {
+              KeyNumberSet shift = 0;
+
+              enqueueUpdatedKeys(brl, packet.fields.data.keys.shift, &shift, MM_GRP_SHIFT, 0);
+              enqueueKeys(brl, packet.fields.data.keys.value, packet.fields.data.keys.group, 0);
+              enqueueUpdatedKeys(brl, 0, &shift, MM_GRP_SHIFT, 0);
+              continue;
+            }
+
+            case MM_GRP_ROUTE:
+            {
+              unsigned char key = packet.fields.data.keys.value;
+
+              if ((key > 0) && (key <= brl->textColumns)) {
+                KeyNumberSet shift = 0;
+
+                enqueueUpdatedKeys(brl, packet.fields.data.keys.shift, &shift, MM_GRP_SHIFT, 0);
+                enqueueKey(brl, packet.fields.data.keys.group, key-1);
+                enqueueUpdatedKeys(brl, 0, &shift, MM_GRP_SHIFT, 0);
+                continue;
+              }
+
+              break;
+            }
+
+            default:
+              break;
+          }
+          break;
+
+        case MM_CMD_ShiftPress:
+        case MM_CMD_ShiftRelease:
+          continue;
+
+        default:
+          break;
+      }
+    }
+
+    logUnexpectedPacket(packet.bytes, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/BrailleMemo/brldefs-mm.h b/Drivers/Braille/BrailleMemo/brldefs-mm.h
new file mode 100644
index 0000000..cd741af
--- /dev/null
+++ b/Drivers/Braille/BrailleMemo/brldefs-mm.h
@@ -0,0 +1,158 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MM_BRLDEFS
+#define BRLTTY_INCLUDED_MM_BRLDEFS
+
+typedef enum {
+  MM_DEV_Pocket  = 0X02,
+  MM_DEV_Smart16 = 0X11,
+  MM_DEV_Smart40 = 0X12,
+} MM_DeviceIdentifier;
+
+typedef struct {
+  unsigned char packetType;
+  unsigned char deviceIdentifier;
+  unsigned char majorVersion;
+  unsigned char minorVersion;
+  unsigned char lineCount;
+  unsigned char lineLength;
+  unsigned char reserved;
+  unsigned char portIdentifier;
+  char hardwareName[24];
+} MM_IdentityPacket;
+
+#define MM_MAXIMUM_LINE_LENGTH 80
+#define MM_MAXIMUM_LINE_COUNT 1
+
+#define MM_HEADER_ID1 0XFF
+#define MM_HEADER_ID2 0XFF
+
+#define MM_HEADER_NAK 0X00
+#define MM_HEADER_ACK 0X01
+
+typedef enum {
+  MM_CMD_QueryIdentity    = 0X10,
+  MM_CMD_QueryLineSize    = 0X11,
+  MM_CMD_StartDisplayMode = 0X20,
+  MM_CMD_EndDisplayMode   = 0X28,
+  MM_CMD_SendBrailleData  = 0X31,
+  MM_CMD_SendDisplayData  = 0X32,
+  MM_CMD_KeyCombination   = 0Xf0,
+  MM_CMD_ShiftPress       = 0Xf2,
+  MM_CMD_ShiftRelease     = 0Xf3
+} MM_CommandCode;
+
+typedef enum {
+  MM_BLINK_NO   = 0,
+  MM_BLINK_SLOW = 1,
+  MM_BLINK_FAST = 2
+} MM_BlinkMode;
+
+typedef struct {
+  unsigned char id1;
+  unsigned char id2;
+  unsigned char code;
+  unsigned char subcode;
+  unsigned char lengthLow;
+  unsigned char lengthHigh;
+} MM_CommandHeader;
+
+typedef union {
+  unsigned char bytes[1];
+
+  struct {
+    MM_CommandHeader header;
+
+    union {
+      struct {
+        unsigned char blink;
+        unsigned char reserved;
+      } start;
+
+      struct {
+        unsigned char cells[MM_MAXIMUM_LINE_LENGTH * 2];
+      } send;
+
+      struct {
+        unsigned char group;
+        unsigned char value;
+        unsigned char shift;
+      } keys;
+    } data;
+  } fields;
+} MM_CommandPacket;
+
+typedef enum {
+  MM_SHIFT_F1      = 0,
+  MM_SHIFT_F4      = 1,
+  MM_SHIFT_CONTROL = 2,
+  MM_SHIFT_ALT     = 3,
+  MM_SHIFT_SELECT  = 4,
+  MM_SHIFT_READ    = 5,
+  MM_SHIFT_F2      = 6,
+  MM_SHIFT_F3      = 7
+} MM_ShiftKey;
+
+typedef enum {
+  MM_DOT_8 = 0,
+  MM_DOT_6 = 1,
+  MM_DOT_5 = 2,
+  MM_DOT_4 = 3,
+  MM_DOT_7 = 4,
+  MM_DOT_3 = 5,
+  MM_DOT_2 = 6,
+  MM_DOT_1 = 7
+} MM_DotKey;
+
+typedef enum {
+  MM_EDIT_ESC    = 0,
+  MM_EDIT_INF    = 1,
+  MM_EDIT_BS     = 2,
+  MM_EDIT_DEL    = 3,
+  MM_EDIT_INS    = 4,
+  MM_EDIT_CHANGE = 5,
+  MM_EDIT_OK     = 6,
+  MM_EDIT_SET    = 7
+} MM_EditKey;
+
+typedef enum {
+  MM_ARROW_UP    = 0,
+  MM_ARROW_DOWN  = 1,
+  MM_ARROW_LEFT  = 2,
+  MM_ARROW_RIGHT = 3
+} MM_ArrowKey;
+
+typedef enum {
+  MM_DISPLAY_BACKWARD = 0,
+  MM_DISPLAY_FORWARD  = 1,
+  MM_DISPLAY_LSCROLL  = 2,
+  MM_DISPLAY_RSCROLL  = 3
+} MM_DisplayKey;
+
+typedef enum {
+  MM_GRP_SHIFT   = 0,
+  MM_GRP_DOT     = 1,
+  MM_GRP_EDIT    = 2,
+  MM_GRP_ARROW   = 3,
+  MM_GRP_ROUTE   = 4,
+  MM_GRP_ERROR   = 5,
+  MM_GRP_DISPLAY = 6
+} MM_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_MM_BRLDEFS */ 
diff --git a/Drivers/Braille/BrailleNote/Makefile.in b/Drivers/Braille/BrailleNote/Makefile.in
new file mode 100644
index 0000000..b459327
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bn
+DRIVER_NAME = BrailleNote
+DRIVER_USAGE = 18/32, Apex
+DRIVER_VERSION = 1.0
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/BrailleNote/PROTOCOL b/Drivers/Braille/BrailleNote/PROTOCOL
new file mode 100644
index 0000000..1de06e6
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/PROTOCOL
@@ -0,0 +1,58 @@
+The BrailleNote operates at 38,400 baud, with 8 bits per character, with
+no parity, and with 1 stop bit.
+
+Whenever a key, or combination of keys (chord), is pressed, two bytes are
+sent to the computer. The first byte indicates which kind of key or chord
+has been used, and the second byte indicates which keys have been pressed.
+The byte sequence is sent once all of the keys have been released.
+
+0X80: Any combination of dots 1 through 6. The second byte consists of bit
+0 (0X01) for dot 1, bit 1 (0X02) for dot 2, bit 2 (0X04) for dot 3, bit 3
+(0X08) for dot 4, bit 4 (0X10) for dot 5, and bit 5 (0X20) for dot 6.
+
+0X81: Any combination of dots 1 through 6 in conjunction with the space
+bar. The second byte is as defined for 0X80. The following dot
+combinations are intercepted by the BrailleNote, and, therefore, aren't
+seen by the computer: [15], [125], [135], [1235], [136], [1356], [235],
+[123456\.
+
+0X82: Any combination of dots 1 through 6 in conjunction with both the
+space bar and the backspace key. The second byte is as defined for 0X80,
+except that bit 6 (0X40) is also always set.
+
+0X83: Any combination of dots 1 through 6 in conjunction with both the
+space bar and the enter key. The second byte is as for 0X80. The following
+dot combinations are intercepted by the BrailleNote, and, therefore,
+aren't seen by the computer: [1], [4], [2], [5], [3], [6], [145], [125],
+[234], [2345].
+
+0X84: Any combination of the thumb keys. The second byte consists of bit 0
+(0X01) for Previous, bit 1 (0X02) for Back, bit 2 (0X04) for Advance, and
+bit 3 (0X08) for Next. Three- and four-key combinations don't work.
+
+0X85: A cursor routing key. The second byte is 0X00 for the first
+(leftmost) key, 0x01 for the second, etc. This sequence is sent when the
+key is pressed, rather than when it is released, and is auto-repeated as
+long as the key is held down.
+
+If "<escape>?" is sent to it, then it responds with three bytes:
+
+    1: 0X86
+    2: The number of status cells.
+    3: The number of text cells.
+
+To refresh the braille cells: send "<escape>B", followed by one byte for
+each status cell, followed by one byte for each text cell. Those status
+and text bytes which match "<escape>" (0X1B) must be sent twice. 1-bits
+represent raised dots, and 0-bits represent lowered dots.
+
+    Bit  Hex  Dot
+     0    01   1
+     1    02   2
+     2    04   3
+     3    08   4
+     4    10   5
+     5    20   6
+     6    40   7
+     7    80   8
+
diff --git a/Drivers/Braille/BrailleNote/README b/Drivers/Braille/BrailleNote/README
new file mode 100644
index 0000000..9838000
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/README
@@ -0,0 +1,70 @@
+This directory contains the BRLTTY driver for the BrailleNote
+[http://www.braillenote.com], which is manufactured by Pulse Data International
+[http://www.pulsedata.co.nz] of New Zealand. As a component of BRLTTY, this
+driver is released under the terms of the GNU Public License. It has been
+tested with BraillNotes 18, 32, and Apex.
+
+It was implemented, and is being maintained, by Dave Mielke <dave@mielke.cc>.
+Thanks to Mike Pedersen <mpedersen@mindspring.com> for his help and advice.
+
+There are a number of ways to put the BrailleNote into its Braille Terminal
+Mode. The quickest way is to use the direct keyboard shortcut
+backspace+enter+[2345]. Another way is to go to the Main Menu by pressing
+space+[123456], and then selecting Braille Terminal Mode by pressing the letter
+"t" [2345]. Yet another way is to go to the Options Menu by pressing
+space+[135], then going to the Task Menu by pressing the letter "c" [14], and
+then selecting Braille Terminal Mode by pressing the letter "t" [2345].
+
+The BrailleNote does not inform the computer that it has been switched into its
+Braille Terminal Mode. This means that it's display will not automatically
+refresh with the contents of the computer screen until something happens which
+requires an updating of the state of its braille cells, e.g. when content
+within the text window changes, or, on those models which have them, when the
+information represented by its status cells changes. This could be caused by
+such things as new output from the computer, cursor motion, or window motion.
+One brutal but benign way to force a refresh of the braille cells is to
+manually restart the braille driver by pressing space+enter+[123456].
+
+*  For a list of commands by function, see "help-cmds.txt".
+*  For a list of commands by key combination, see "help-keys.txt".
+
+The thumb keys provide access to the most commonly used screen navigation
+commands. Combinations of dots 1 through 6 either provide access to the full
+set of screen navigation commands (including those which are accessible via the
+thumb keys) or, if one of the input modes has been selected, are a keyboard for
+the current virtual terminal. Combinations of dots 1 through 6, together with
+the space bar, provide access to commands which control, and make special
+requests of, the driver. Driver options can be enabled via combinations of dots
+1 through 6 together with the space bar and the enter key, and can be disabled
+via combinations of dots 1 through 6 together with the space bar and the
+backspace key. Some special commands, due to the need to assign logical key
+combinations, and aggravated because the BrailleNote itself intercepts certain
+key combinations for its own purposes, violate these conventions.
+
+The BrailleNote, at least as of the time of this writing, doesn't signal the
+pressing of dot 7 (backspace) and/or dot 8 (enter) unless the space bar is also
+simultaneously pressed. This makes the straightforward entry of 8-dot computer
+braille impossible. A scheme has been implemented, therefore, to get around
+this limitation. It allows the states (on or off) of both dot 7 and dot 8 to be
+either temporarily (just for the next character) or permanently changed. It
+uses the chord of dot 4 and the space bar, together with all possible
+combinations of dots 3, 5, and 6.
+
+Dot 5 requests a permanent state change, dot 3 represents dot 7, and dot 6
+represents dot 8. Pressing dot 4 together with the space bar indicates that you
+wish to enter one character; if an input mode has already been selected, then
+it indicates that you wish to temporarily change the states of dots 7 and 8.
+Pressing both dots 4 and 5 together with the space bar indicates that you wish
+to permanently switch to an input mode; if one has already been selected, then
+it indicates that you wish to permanently change the states of dots 7 and 8. In
+conjunction with either of the fore-going: pressing dot 3 turns dot 7 on, and
+pressing dot 6 turns dot 8 on. The braille keyboard can be returned to screen
+navigation mode by pressing the letter "n" (dots 1, 3, 4, ane 5) together with
+the space bar.
+
+When the BrailleNote's Visual Display is assigned to the serial port, then a
+free virtual terminal is activated and becomes that display. When the Visual
+Display is subsequently either turned off or reassigned to a different port,
+then this virtual terminal is released, and, if it is active, then the virtual
+terminal which was active when the display was assigned to the serial port is
+reactivated.
diff --git a/Drivers/Braille/BrailleNote/TODO b/Drivers/Braille/BrailleNote/TODO
new file mode 100644
index 0000000..7c75081
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/TODO
@@ -0,0 +1 @@
+Add support for: ATTRBLINK CSRBLINK CAPBLINK ATTRVIS CSRVIS NXBLNKLN PRBLNKLN SAYALL SND
diff --git a/Drivers/Braille/BrailleNote/braille.c b/Drivers/Braille/BrailleNote/braille.c
new file mode 100644
index 0000000..08f7390
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/braille.c
@@ -0,0 +1,571 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleNote/braille.c - Braille display library
+ * For Pulse Data International's Braille Note series
+ * Author: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "device.h"
+
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "brldefs-bn.h"
+#include "ttb.h"
+
+BEGIN_KEY_NAME_TABLE(all)
+  KEY_NAME_ENTRY(BN_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(BN_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(BN_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(BN_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(BN_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(BN_KEY_Dot6, "Dot6"),
+
+  KEY_NAME_ENTRY(BN_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(BN_KEY_Backspace, "Backspace"),
+  KEY_NAME_ENTRY(BN_KEY_Enter, "Enter"),
+
+  KEY_NAME_ENTRY(BN_KEY_Previous, "Previous"),
+  KEY_NAME_ENTRY(BN_KEY_Back, "Back"),
+  KEY_NAME_ENTRY(BN_KEY_Advance, "Advance"),
+  KEY_NAME_ENTRY(BN_KEY_Next, "Next"),
+
+  KEY_GROUP_ENTRY(BN_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(all),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+typedef union {
+  unsigned char bytes[3];
+  struct {
+    unsigned char code;
+    union {
+      unsigned char dotKeys;
+      unsigned char thumbKeys;
+      unsigned char routingKey;
+      unsigned char inputChar;
+      unsigned char inputVKey;
+
+      struct {
+        unsigned char statusCells;
+        unsigned char textCells;
+      } description;
+    } values;
+  } data;
+} ResponsePacket;
+
+#ifdef HAVE_LINUX_VT_H
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#endif /* HAVE_LINUX_VT_H */
+static int displayDescriptor = -1;
+static int displayTerminal;
+
+static unsigned char *cellBuffer = NULL;
+static unsigned int cellCount = 0;
+static unsigned char *statusArea;
+static int statusCells;
+static unsigned char *dataArea;
+static int dataCells;
+
+static int inputFlags = 0;
+
+static int
+readPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  int offset = 0;
+  int length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    if (!gioReadByte(brl->gioEndpoint, &byte, (offset > 0))) {
+      if (offset > 0) logPartialPacket(packet, offset);
+      return 0;
+    }
+
+    if (offset < size) {
+      if (offset == 0) {
+        switch (byte) {
+          case BN_RSP_DISPLAY:
+            length = 1;
+            break;
+
+          case BN_RSP_CHARACTER:
+          case BN_RSP_SPACE:
+          case BN_RSP_BACKSPACE:
+          case BN_RSP_ENTER:
+          case BN_RSP_THUMB:
+          case BN_RSP_ROUTE:
+          case BN_RSP_INPUT_CHAR:
+          case BN_RSP_INPUT_VKEY:
+          case BN_RSP_INPUT_RESET:
+          case BN_RSP_QWERTY_KEY:
+          case BN_RSP_QWERTY_MODS:
+            length = 2;
+            break;
+
+          case BN_RSP_DESCRIBE:
+            length = 3;
+            break;
+
+          default:
+            logUnknownPacket(byte);
+            offset = 0;
+            length = 0;
+            continue;
+        }
+      }
+
+      packet[offset] = byte;
+    } else {
+      if (offset == size) logTruncatedPacket(packet, offset);
+      logDiscardedByte(byte);
+    }
+
+    if (++offset == length) {
+      if (offset > size) {
+        offset = 0;
+        length = 0;
+        continue;
+      }
+
+      logInputPacket(packet, offset);
+      return length;
+    }
+  }
+}
+
+static int
+getPacket (BrailleDisplay *brl, ResponsePacket *packet) {
+  return readPacket(brl, packet->bytes, sizeof(*packet));
+}
+
+static int
+writePacket (BrailleDisplay *brl, const unsigned char *packet, int size) {
+  unsigned char buffer[1 + (size * 2)];
+  unsigned char *byte = buffer;
+
+  *byte++ = BN_REQ_BEGIN;
+
+  while (size > 0) {
+    if ((*byte++ = *packet++) == BN_REQ_BEGIN) *byte++ = BN_REQ_BEGIN;
+    --size;
+  }
+
+  return writeBraillePacket(brl, NULL, buffer, byte-buffer);
+}
+
+static int
+refreshCells (BrailleDisplay *brl) {
+  unsigned char buffer[1 + cellCount];
+  unsigned char *byte = buffer;
+
+  *byte++ = BN_REQ_WRITE;
+  byte = translateOutputCells(byte, cellBuffer, cellCount);
+
+  return writePacket(brl, buffer, byte-buffer);
+}
+
+static unsigned char
+getByte (BrailleDisplay *brl) {
+  unsigned char byte;
+  while (!awaitBrailleInput(brl, 1000000000));
+  gioReadByte(brl->gioEndpoint, &byte, 0);
+  return byte;
+}
+
+static int
+getVirtualTerminal (void) {
+  int vt = -1;
+#ifdef HAVE_LINUX_VT_H
+  FILE *console = getConsole();
+  if (console) {
+    int consoleDescriptor = fileno(console);
+    struct vt_stat state;
+    if (ioctl(consoleDescriptor, VT_GETSTATE, &state) != -1) {
+      vt = state.v_active;
+    }
+  }
+#endif /* HAVE_LINUX_VT_H */
+  return vt;
+}
+
+static void
+setVirtualTerminal (int vt) {
+#ifdef HAVE_LINUX_VT_H
+  FILE *console = getConsole();
+  if (console) {
+    int consoleDescriptor = fileno(console);
+    logMessage(LOG_DEBUG, "switching to virtual terminal %d", vt);
+    if (ioctl(consoleDescriptor, VT_ACTIVATE, vt) != -1) {
+      if (ioctl(consoleDescriptor, VT_WAITACTIVE, vt) != -1) {
+        logMessage(LOG_INFO, "switched to virtual terminal %d", vt);
+      } else {
+        logSystemError("virtual console wait");
+      }
+    } else {
+      logSystemError("virtual console activate");
+    }
+  }
+#endif /* HAVE_LINUX_VT_H */
+}
+
+static void
+openVisualDisplay (void) {
+#ifdef HAVE_LINUX_VT_H
+  if (displayDescriptor == -1) {
+    FILE *console = getConsole();
+    if (console) {
+      int consoleDescriptor = fileno(console);
+      if (ioctl(consoleDescriptor, VT_OPENQRY, &displayTerminal) != -1) {
+        char path[0X20];
+        snprintf(path, sizeof(path), "/dev/tty%d", displayTerminal);
+        if ((displayDescriptor = open(path, O_WRONLY)) != -1) {
+          logMessage(LOG_INFO, "visual display is %s", path);
+        }
+      }
+    }
+  }
+ if (displayDescriptor != -1) {
+   setVirtualTerminal(displayTerminal);
+  }
+#endif /* HAVE_LINUX_VT_H */
+}
+
+static void
+closeVisualDisplay (int vt) {
+  if (displayDescriptor != -1) {
+    if (getVirtualTerminal() == displayTerminal) {
+      setVirtualTerminal(vt);
+    }
+    close(displayDescriptor);
+    displayDescriptor = -1;
+    displayTerminal = 0;
+  }
+}
+
+static int
+writeVisualDisplay (unsigned char c) {
+  if (displayDescriptor != -1) {
+    if (write(displayDescriptor, &c, 1) == -1) {
+      logSystemError("write");
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+doVisualDisplay (BrailleDisplay *brl) {
+  int vt = getVirtualTerminal();
+  const unsigned char end[] = {ASCII_ESC, 0};
+  unsigned int state = 0;
+  openVisualDisplay();
+  writeVisualDisplay(BN_RSP_DISPLAY);
+  for (;;) {
+    unsigned char character = getByte(brl);
+    if (character == end[state]) {
+      if (++state == sizeof(end)) break;
+    } else {
+      if (state > 0) {
+        int i;
+        for (i=0; i<state; ++i) {
+          writeVisualDisplay(end[i]);
+        }
+        state = 0;
+      }
+      if (character == end[0]) {
+        state = 1;
+      } else {
+        writeVisualDisplay(character);
+      }
+    }
+  }
+  closeVisualDisplay(vt);
+  return EOF;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char request[] = {BN_REQ_DESCRIBE};
+
+  return writePacket(brl, request, sizeof(request));
+}
+
+static size_t
+readResponse (BrailleDisplay *brl, void *packet, size_t size) {
+  return readPacket(brl, packet, size);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const ResponsePacket *response = packet;
+
+  return (response->data.code == BN_RSP_DESCRIBE)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 38400
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* HumanWare APEX */
+      .vendor=0X1C71, .product=0XC004,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+virtualKeyToCommand (int vkey) {
+  switch (vkey) {
+    case 0X0D: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_ENTER;
+    case 0X1B: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_ESCAPE;
+    case 0X25: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_LEFT;
+    case 0X26: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_UP;
+    case 0X27: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_RIGHT;
+    case 0X28: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_DOWN;
+    case 0X2E: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_DELETE;
+    default:   return BRL_CMD_NOOP;
+  }
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (connectResource(brl, device)) {
+    ResponsePacket response;
+
+    if (probeBrailleDisplay(brl, 0, NULL, 100,
+                              writeIdentifyRequest,
+                              readResponse, &response, sizeof(response),
+                              isIdentityResponse)) {
+      statusCells = response.data.values.description.statusCells;
+      brl->textColumns = response.data.values.description.textCells;
+      brl->textRows = 1;
+
+      if ((statusCells == 5) && (brl->textColumns == 30)) {
+        statusCells -= 2;
+        brl->textColumns += 2;
+      }
+
+      dataCells = brl->textColumns * brl->textRows;
+      cellCount = statusCells + dataCells;
+
+      setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+      makeOutputTable(dotsTable_ISO11548_1);
+      makeInputTable();
+
+      if ((cellBuffer = malloc(cellCount))) {
+        memset(cellBuffer, 0, cellCount);
+        statusArea = cellBuffer;
+        dataArea = statusArea + statusCells;
+        refreshCells(brl);
+        return 1;
+      } else {
+        logSystemError("cell buffer allocation");
+      }
+    }
+
+    disconnectBrailleResource(brl, NULL);
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (cellBuffer) {
+    free(cellBuffer);
+    cellBuffer = NULL;
+  }
+}
+
+static ssize_t
+brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  int count = readPacket(brl, buffer, size);
+  if (!count) count = -1;
+  return count;
+}
+
+static ssize_t
+brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) {
+  return writePacket(brl, packet, length)? length: -1;
+}
+
+static int
+brl_reset (BrailleDisplay *brl) {
+  return 0;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(dataArea, brl->buffer, dataCells, NULL, NULL, NULL)) {
+    refreshCells(brl);
+  }
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  ResponsePacket packet;
+  int size;
+
+  while ((size = getPacket(brl, &packet))) {
+    switch (packet.data.code) {
+      case BN_RSP_ROUTE:
+        enqueueKey(brl, BN_GRP_RoutingKeys, packet.data.values.routingKey);
+        break;
+
+      case BN_RSP_DISPLAY:
+        doVisualDisplay(brl);
+        break;
+
+      case BN_RSP_INPUT_CHAR: {
+        int command;
+
+        switch (packet.data.values.inputChar) {
+          case 0X08:
+            command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_BACKSPACE;
+            break;
+
+          case 0X09:
+            command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_TAB;
+            break;
+
+          default:
+            command = BRL_CMD_BLK(PASSCHAR) | packet.data.values.inputChar;
+            break;
+        }
+
+        enqueueCommand(command | inputFlags);
+        inputFlags = 0;
+        break;
+      }
+
+      case BN_RSP_INPUT_VKEY: {
+        unsigned char vkey = packet.data.values.inputVKey;
+
+        switch (vkey) {
+          case 0XA2:
+            inputFlags |= BRL_FLG_INPUT_CONTROL;
+            break;
+
+          case 0XA4:
+            inputFlags |= BRL_FLG_INPUT_META;
+            break;
+
+          case 0X91:
+            inputFlags |= BRL_FLG_INPUT_SHIFT;
+            break;
+
+          default: {
+            int command = virtualKeyToCommand(vkey);
+
+            if (command) {
+              enqueueCommand(command | inputFlags);
+            }
+
+            inputFlags = 0;
+            break;
+          }
+        }
+        break;
+      }
+
+      case BN_RSP_INPUT_RESET:
+        inputFlags = 0;
+        break;
+
+      default: {
+        const KeyGroup group = BN_GRP_NavigationKeys;
+        KeyNumberSet keys = packet.data.values.dotKeys & 0X3F;
+        KeyNumber base = BN_KEY_Dot1;
+        KeyNumber modifier = 0;
+
+        switch (packet.data.code) {
+          case BN_RSP_CHARACTER:
+            if (keys) break;
+
+          case BN_RSP_SPACE:
+            modifier = BN_KEY_Space;
+            break;
+
+          case BN_RSP_BACKSPACE:
+            modifier = BN_KEY_Backspace;
+            break;
+
+          case BN_RSP_ENTER:
+            modifier = BN_KEY_Enter;
+            break;
+
+          case BN_RSP_THUMB:
+            keys = packet.data.values.thumbKeys & 0X0F;
+            base = BN_KEY_Previous;
+            break;
+
+          default:
+            logUnexpectedPacket(packet.bytes, size);
+            continue;
+        }
+
+        if (modifier) enqueueKeyEvent(brl, group, modifier, 1);
+        enqueueKeys(brl, keys, group, base);
+        if (modifier) enqueueKeyEvent(brl, group, modifier, 0);
+        break;
+      }
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/BrailleNote/brldefs-bn.h b/Drivers/Braille/BrailleNote/brldefs-bn.h
new file mode 100644
index 0000000..8e1fa53
--- /dev/null
+++ b/Drivers/Braille/BrailleNote/brldefs-bn.h
@@ -0,0 +1,76 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BN_BRLDEFS
+#define BRLTTY_INCLUDED_BN_BRLDEFS
+
+#include "ascii.h"
+
+typedef enum {
+  BN_REQ_BEGIN = ASCII_ESC,
+  BN_REQ_DESCRIBE = '?',
+  BN_REQ_WRITE = 'B'
+} BN_RequestType;
+
+typedef enum {
+  BN_RSP_CHARACTER = 0X80,
+  BN_RSP_SPACE = 0X81,
+  BN_RSP_BACKSPACE = 0X82,
+  BN_RSP_ENTER = 0X83,
+  BN_RSP_THUMB = 0X84,
+  BN_RSP_ROUTE = 0X85,
+  BN_RSP_DESCRIBE = 0X86,
+  BN_RSP_INPUT_CHAR = 0X88,
+  BN_RSP_INPUT_VKEY = 0X89,
+  BN_RSP_INPUT_RESET = 0X8A,
+  BN_RSP_QWERTY_KEY = 0X8C,
+  BN_RSP_QWERTY_MODS = 0X8D,
+  BN_RSP_DISPLAY = ASCII_ESC
+} BN_ResponseType;
+
+typedef enum {
+  BN_KEY_Dot1,
+  BN_KEY_Dot2,
+  BN_KEY_Dot3,
+  BN_KEY_Dot4,
+  BN_KEY_Dot5,
+  BN_KEY_Dot6,
+
+  BN_KEY_Space,
+  BN_KEY_Backspace,
+  BN_KEY_Enter,
+
+  BN_KEY_Previous,
+  BN_KEY_Back,
+  BN_KEY_Advance,
+  BN_KEY_Next
+} BN_NavigationKey;
+
+typedef enum {
+  BN_GRP_NavigationKeys,
+  BN_GRP_RoutingKeys
+} SK_KeyGroup;
+
+typedef enum {
+  BN_MOD_FUNCTION = 0X01,
+  BN_MOD_SHIFT    = 0X02,
+  BN_MOD_cONTROL  = 0X04,
+  BN_MOD_rEAD     = 0X08
+} BN_QwertyModifier;
+
+#endif /* BRLTTY_INCLUDED_BN_BRLDEFS */ 
diff --git a/Drivers/Braille/Braudi/Makefile.in b/Drivers/Braille/Braudi/Makefile.in
new file mode 100644
index 0000000..afacb91
--- /dev/null
+++ b/Drivers/Braille/Braudi/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bd
+DRIVER_NAME = Braudi
+DRIVER_USAGE = Pro
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Braudi/braille.c b/Drivers/Braille/Braudi/braille.c
new file mode 100644
index 0000000..bc053c7
--- /dev/null
+++ b/Drivers/Braille/Braudi/braille.c
@@ -0,0 +1,290 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ascii.h"
+
+#include "brl_driver.h"
+#include "io_serial.h"
+
+static SerialDevice *serialDevice = NULL;
+static unsigned int charactersPerSecond;
+static unsigned char *outputBuffer = NULL;
+
+static int
+readBytes (unsigned char *buffer, int size, size_t *length) {
+  *length = 0;
+
+  while (*length < size) {
+    unsigned char byte;
+
+    if (!serialReadChunk(serialDevice, buffer, length, 1, 0, 100)) {
+      return 0;
+    }
+    byte = buffer[*length - 1];
+
+    if ((*length == 1) && (byte == ASCII_ACK)) {
+      *length = 0;
+      continue;
+    }
+
+    if (byte == ASCII_CR) {
+      logBytes(LOG_DEBUG, "Read", buffer, *length);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, int count) {
+  logBytes(LOG_DEBUG, "Write", bytes, count);
+  if (serialWriteData(serialDevice, bytes, count) == -1) return 0;
+  brl->writeDelay += (count * 1000 / charactersPerSecond) + 1;
+  return 1;
+}
+
+static int
+writeAcknowledgement (BrailleDisplay *brl) {
+  static const unsigned char acknowledgement[] = {ASCII_ACK};
+  return writeBytes(brl, acknowledgement, sizeof(acknowledgement));
+}
+
+static int
+writeCells (BrailleDisplay *brl) {
+  static const unsigned char header[] = {'D'};
+  static const unsigned char trailer[] = {ASCII_CR};
+  unsigned char buffer[sizeof(header) + brl->textColumns + sizeof(trailer)];
+  unsigned char *byte = buffer;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  byte = translateOutputCells(byte, outputBuffer, brl->textColumns);
+  byte = mempcpy(byte, trailer, sizeof(trailer));
+
+  return writeBytes(brl, buffer, byte-buffer);
+}
+
+static int
+writeString (BrailleDisplay *brl, const char *string) {
+  return writeBytes(brl, (const unsigned char *)string, strlen(string));
+}
+
+static int
+skipCharacter (unsigned char character, const unsigned char **bytes, int *count) {
+  int found = 0;
+
+  while (*count) {
+    if (**bytes != character) break;
+    found = 1;
+    ++*bytes, --*count;
+  }
+
+  return found;
+}
+
+static int
+interpretNumber (int *number, const unsigned char **bytes, int *count) {
+  int ok = skipCharacter('0', bytes, count);
+  *number = 0;
+
+  while (*count) {
+    static unsigned char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+    const unsigned char *digit = memchr(digits, **bytes, sizeof(digits));
+    if (!digit) break;
+
+    *number = (*number * 10) + (digit - digits);
+    ok = 1;
+    ++*bytes, --*count;
+  }
+
+  return ok;
+}
+
+static int
+identifyDisplay (BrailleDisplay *brl) {
+  static const unsigned char identify[] = {'I', ASCII_CR};
+
+  if (writeBytes(brl, identify, sizeof(identify))) {
+    if (serialAwaitInput(serialDevice, 1000)) {
+      unsigned char identity[0X100];
+      size_t length;
+
+      if (readBytes(identity, sizeof(identity), &length)) {
+        static const unsigned char prefix[] = {'b', 'r', 'a', 'u', 'd', 'i', ' '};
+        if ((length >= sizeof(prefix)) &&
+            (memcmp(identity, prefix, sizeof(prefix)) == 0)) {
+          const unsigned char *bytes = memchr(identity, ',', length);
+          if (bytes) {
+            int count = length - (bytes - identity);
+            int cells;
+
+            ++bytes, --count;
+            skipCharacter(' ', &bytes, &count);
+            if (interpretNumber(&cells, &bytes, &count)) {
+              if (!count) {
+                logMessage(LOG_INFO, "Detected: %.*s", (int)length, identity);
+
+                brl->textColumns = cells;
+                brl->textRows = 1;
+
+                return 1;
+              }
+            }
+          }
+        }
+
+        logUnexpectedPacket(identity, length);
+      }
+    }
+  }
+  return 0;
+}
+
+static int
+setTable (BrailleDisplay *brl, int table) {
+  char buffer[0X10];
+  snprintf(buffer, sizeof(buffer), "L%d\r", table);
+  return writeString(brl, buffer);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if ((serialDevice = serialOpenDevice(device))) {
+    static const unsigned int baud = 9600;
+    charactersPerSecond = baud / 10;
+    if (serialRestartDevice(serialDevice, baud)) {
+      if (identifyDisplay(brl)) {
+        MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X10, 0X20, 0X40, 0X08, 0X80);
+  
+        if ((outputBuffer = malloc(brl->textColumns))) {
+          if (setTable(brl, 0)) {
+            memset(outputBuffer, 0, brl->textColumns);
+            writeCells(brl);
+
+            return 1;
+          }
+
+          free(outputBuffer);
+          outputBuffer = NULL;
+        } else {
+          logSystemError("Output buffer allocation");
+        }
+      }
+    }
+
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (outputBuffer) {
+    free(outputBuffer);
+    outputBuffer = NULL;
+  }
+
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(outputBuffer, brl->buffer, brl->textColumns, NULL, NULL, NULL)) {
+    writeCells(brl);
+  }
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char buffer[0X100];
+  size_t length;
+
+  while (readBytes(buffer, sizeof(buffer), &length)) {
+    const unsigned char *bytes = buffer;
+    int count = length;
+
+    if (count > 0) {
+      unsigned char category = *bytes++;
+      --count;
+
+      switch (category) {
+        case 'F': {
+          int keys;
+          writeAcknowledgement(brl);
+
+          if (interpretNumber(&keys, &bytes, &count)) {
+            if (!count) {
+              switch (keys) {
+                case  1: return BRL_CMD_TOP_LEFT;
+                case  2: return BRL_CMD_FWINLT;
+                case  3: return BRL_CMD_LNDN;
+                case  4: return BRL_CMD_LNUP;
+                case  5: return BRL_CMD_FWINRT;
+                case  6: return BRL_CMD_BOT_LEFT;
+                case 23: return BRL_CMD_LNBEG;
+                case 56: return BRL_CMD_LNEND;
+                case 14: return BRL_CMD_CSRVIS;
+                case 25: return BRL_CMD_DISPMD;
+                case 26: return BRL_CMD_INFO;
+                case 36: return BRL_CMD_HOME;
+              }
+            }
+          }
+
+          break;
+        }
+
+        case 'K': {
+          int key;
+          writeAcknowledgement(brl);
+
+          if (interpretNumber(&key, &bytes, &count)) {
+            if (!count) {
+              if ((key > 0) && (key <= brl->textColumns)) return BRL_CMD_BLK(ROUTE) + (key - 1);
+            }
+          }
+
+          break;
+        }
+      }
+    }
+
+    logUnexpectedPacket(buffer, length);
+  }
+
+  if (errno == EAGAIN) return EOF;
+  return BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/BrlAPI/Makefile.in b/Drivers/Braille/BrlAPI/Makefile.in
new file mode 100644
index 0000000..2e1c7e7
--- /dev/null
+++ b/Drivers/Braille/BrlAPI/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ba
+DRIVER_NAME = BrlAPI
+DRIVER_USAGE = BrlAPI client
+DRIVER_VERSION = 0.1, 2005
+DRIVER_DEVELOPERS = Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>, Samuel Thibault <samuel.thibault@ens-lyon.org>
+BRL_OBJS = @braille_libraries_ba@
+include $(SRC_TOP)braille.mk
+
+braille.$O: | brlapi
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/BrlAPI/README b/Drivers/Braille/BrlAPI/README
new file mode 100644
index 0000000..784f1d7
--- /dev/null
+++ b/Drivers/Braille/BrlAPI/README
@@ -0,0 +1,24 @@
+This driver allows BRLTTY to access braille displays through BrlAPI.  Multiple
+instances of brltty can hence be run, one using the real braille driver, the
+others accessing it through the API. Each instance of BRLTTY can use a different
+screen driver thanks to the -x command-line option.
+
+
+For example, in a gnome desktop you can run a second BRLTTY that reads
+gnome-terminal by running
+
+brltty -b ba -x as
+
+from your X session.
+
+
+Another example is accessing the Linux console of a remote host:
+
+ssh -R 4102:localhost:4101 root@theremotehost brltty -b ba -B host=localhost:1
+
+This will forward your local BrlAPI port (4101) to the remote machine (as 4102,
+i.e. :1), where you can then run brltty and tell it to connect to localhost:1.
+This will however take complete control of your braille device. To restrict the
+control to some local console (e.g. console 4), you can use e.g.
+
+ssh -R 4102:localhost:4101 root@theremotehost WINDOWPATH=4 brltty -b ba -B host=localhost:1
diff --git a/Drivers/Braille/BrlAPI/braille.c b/Drivers/Braille/BrlAPI/braille.c
new file mode 100644
index 0000000..1e8e53f
--- /dev/null
+++ b/Drivers/Braille/BrlAPI/braille.c
@@ -0,0 +1,250 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+#include "scr.h"
+#include "cmd_brlapi.h"
+#include "charset.h"
+
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+
+typedef enum {
+  PARM_HOST,
+  PARM_AUTH,
+  PARM_SPEECH_CHANGES,
+} DriverParameter;
+#define BRLPARMS "host", "auth", "speechChanges"
+
+#include "brl_driver.h"
+
+#define CHECK(cond, label) \
+  do { \
+    if (!(cond)) { \
+      logMessage(LOG_ERR, "%s", brlapi_strerror(&brlapi_error)); \
+      goto label; \
+    } \
+  } while (0);
+
+static brlapi_param_clientPriority_t currentPriority;
+static const brlapi_param_clientPriority_t qualityPriorities[] = {
+  [SCQ_NONE] = BRLAPI_PARAM_CLIENT_PRIORITY_DISABLE,
+  [SCQ_LOW]  = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT - 40,
+  [SCQ_POOR] = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT - 25,
+  [SCQ_FAIR] = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT - 10,
+  [SCQ_GOOD] = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT + 10,
+  [SCQ_HIGH] = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT + 30,
+};
+
+static int displaySize;
+static unsigned char *prevData;
+static wchar_t *prevText;
+static int prevCursor;
+static int prevShown;
+
+static int restart;
+
+static int
+ignoreSpeechChangeCommands (void) {
+  static const brlapi_keyCode_t commands[] = {
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_LOUDER,
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_SOFTER,
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_FASTER,
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_SLOWER,
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_HIGHER,
+    BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SAY_LOWER,
+  };
+
+  CHECK(brlapi_ignoreKeys(brlapi_rangeType_command, commands, ARRAY_COUNT(commands))>=0, failed);
+  return 1;
+
+failed:
+  return 0;
+}
+
+/* Function : brl_construct */
+/* Opens a connection with BrlAPI's server */
+static int brl_construct(BrailleDisplay *brl, char **parameters, const char *device)
+{
+  currentPriority = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT;
+
+  brlapi_connectionSettings_t settings;
+  settings.host = parameters[PARM_HOST];
+  settings.auth = parameters[PARM_AUTH];
+
+  CHECK((brlapi_openConnection(&settings, &settings)>=0), out);
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "connected to %s using %s", settings.host, settings.auth
+  );
+
+  CHECK((brlapi_enterTtyModeWithPath(NULL, 0, NULL)>=0), out0);
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "got tty successfully"
+  );
+
+  CHECK((brlapi_getDisplaySize(&brl->textColumns, &brl->textRows)==0), out1);
+  displaySize = brl->textColumns * brl->textRows;
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "got display size: %dx%d", brl->textColumns, brl->textRows
+  );
+
+  {
+    unsigned int speechChanges = 1;
+    const char *parameter = parameters[PARM_SPEECH_CHANGES];
+
+    if (*parameter) {
+      if (!validateYesNo(&speechChanges, parameter)) {
+         logMessage(LOG_WARNING, "%s: %s", "invalid speech changes setting", parameter);
+      } else if (!speechChanges) {
+        ignoreSpeechChangeCommands();
+      }
+    }
+  }
+
+  brl->hideCursor = 1;
+
+  prevData = malloc(displaySize);
+  CHECK((prevData!=NULL), out1);
+  memset(prevData, 0, displaySize);
+
+  prevText = malloc(displaySize * sizeof(wchar_t));
+  CHECK((prevText!=NULL), out2);
+  wmemset(prevText, WC_C(' '), displaySize);
+
+  prevShown = 0;
+  prevCursor = BRL_NO_CURSOR;
+  restart = 0;
+
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+             "Memory allocated, returning 1");
+  return 1;
+  
+out2:
+  free(prevData);
+out1:
+  brlapi_leaveTtyMode();
+out0:
+  brlapi_closeConnection();
+out:
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+             "Something went wrong, returning 0");
+  return 0;
+}
+
+/* Function : brl_destruct */
+/* Frees memory and closes the connection with BrlAPI */
+static void brl_destruct(BrailleDisplay *brl)
+{
+  free(prevData);
+  free(prevText);
+  brlapi_closeConnection();
+}
+
+static int
+setClientPriority (BrailleDisplay *brl) {
+  unsigned char worst = ARRAY_COUNT(qualityPriorities) - 1;
+  unsigned char quality = MIN(brl->quality, worst);
+  brlapi_param_clientPriority_t priority = qualityPriorities[quality];
+
+  if (priority != currentPriority) {
+    int result = brlapi_setParameter(
+      BRLAPI_PARAM_CLIENT_PRIORITY, 0,
+      BRLAPI_PARAMF_LOCAL, &priority, sizeof(priority)
+    );
+
+    if (result < 0) return 0;
+    currentPriority = priority;
+  }
+
+  return 1;
+}
+
+/* function : brl_writeWindow */
+/* Displays a text on the braille window, only if it's different from */
+/* the one already displayed */
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  setClientPriority(brl);
+
+  brlapi_writeArguments_t arguments = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+  int vt = currentVirtualTerminal();
+
+  if (vt == SCR_NO_VT) {
+    /* should leave display */
+    if (prevShown) {
+      brlapi_write(&arguments);
+      prevShown = 0;
+    }
+  } else {
+    if (prevShown &&
+        (memcmp(prevData,brl->buffer,displaySize) == 0) &&
+        (!text || (wmemcmp(prevText,text,displaySize) == 0)) &&
+        (brl->cursor == prevCursor)) {
+      return 1;
+    }
+
+    unsigned char and[displaySize];
+    memset(and, 0, sizeof(and));
+    arguments.andMask = and;
+    arguments.orMask = brl->buffer;
+
+    if (text) {
+      arguments.text = (char*) text;
+      arguments.textSize = displaySize * sizeof(wchar_t);
+      arguments.charset = (char*) getWcharCharset();
+    }
+
+    arguments.regionBegin = 1;
+    arguments.regionSize = displaySize;
+    arguments.cursor = (brl->cursor != BRL_NO_CURSOR)? (brl->cursor + 1): BRLAPI_CURSOR_OFF;
+
+    if (brlapi_write(&arguments)==0) {
+      memcpy(prevData,brl->buffer,displaySize);
+      if (text)
+	wmemcpy(prevText,text,displaySize);
+      else
+	wmemset(prevText,0,displaySize);
+      prevCursor = brl->cursor;
+      prevShown = 1;
+    } else {
+      logMessage(LOG_ERR, "write: %s", brlapi_strerror(&brlapi_error));
+      restart = 1;
+    }
+  }
+
+  return 1;
+}
+
+/* Function : brl_readCommand */
+/* Reads a command from the braille keyboard */
+static int brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context)
+{
+  brlapi_keyCode_t keycode;
+  if (restart) return BRL_CMD_RESTARTBRL;
+  switch (brlapi_readKey(0, &keycode)) {
+    case 0: return EOF;
+    case 1: return cmdBrlapiToBrltty(keycode);
+    default: return BRL_CMD_RESTARTBRL;
+  }
+}
diff --git a/Drivers/Braille/Canute/Makefile.in b/Drivers/Braille/Canute/Makefile.in
new file mode 100644
index 0000000..e797102
--- /dev/null
+++ b/Drivers/Braille/Canute/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = cn
+DRIVER_NAME = Canute
+DRIVER_USAGE = 360 (40x9)
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Canute/braille.c b/Drivers/Braille/Canute/braille.c
new file mode 100644
index 0000000..8f8169e
--- /dev/null
+++ b/Drivers/Braille/Canute/braille.c
@@ -0,0 +1,767 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "crc_generate.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "timing.h"
+
+#include "brl_driver.h"
+#include "brldefs-cn.h"
+
+#define PROBE_RETRY_LIMIT 0
+#define PROBE_RESPONSE_TIMEOUT 1000
+#define COMMAND_RESPONSE_TIMEOUT 10000
+#define MAXIMUM_RESPONSE_SIZE 0X100
+
+#define KEYS_POLL_INTERVAL 100
+#define MOTORS_POLL_INTERVAL 400
+#define ROW_UPDATE_TIME 1200
+#define CELLS_RESET_TIME 14000
+#define UPDATE_RETRY_DELAY 5000
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  KEY_NAME_ENTRY(CN_KEY_Help, "Help"),
+  KEY_NAME_ENTRY(CN_KEY_Refresh, "Refresh"),
+
+  KEY_NAME_ENTRY(CN_KEY_Line1, "Line1"),
+  KEY_NAME_ENTRY(CN_KEY_Line2, "Line2"),
+  KEY_NAME_ENTRY(CN_KEY_Line3, "Line3"),
+  KEY_NAME_ENTRY(CN_KEY_Line4, "Line4"),
+  KEY_NAME_ENTRY(CN_KEY_Line5, "Line5"),
+  KEY_NAME_ENTRY(CN_KEY_Line6, "Line6"),
+  KEY_NAME_ENTRY(CN_KEY_Line7, "Line7"),
+  KEY_NAME_ENTRY(CN_KEY_Line8, "Line8"),
+  KEY_NAME_ENTRY(CN_KEY_Line9, "Line9"),
+
+  KEY_NAME_ENTRY(CN_KEY_Back, "Back"),
+  KEY_NAME_ENTRY(CN_KEY_Menu, "Menu"),
+  KEY_NAME_ENTRY(CN_KEY_Forward, "Forward"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  unsigned char force;
+  unsigned char haveOldCells:1;
+  unsigned char haveNewCells:1;
+
+  unsigned char *oldCells;
+  unsigned char newCells[];
+} RowEntry;
+
+typedef BrailleResponseResult ProbeResponseHandler (
+  BrailleDisplay *brl,
+  const unsigned char *response, size_t size
+);
+
+struct BrailleDataStruct {
+  CRCGenerator *crcGenerator;
+  AsyncHandle keysPollerAlarm;
+
+  struct {
+    ProbeResponseHandler *responseHandler;
+    unsigned int protocolVersion;
+  } probe;
+
+  struct {
+    TimePeriod timeout;
+    unsigned char command;
+    unsigned char waiting:1;
+  } response;
+
+  struct {
+    TimePeriod retryDelay;
+    RowEntry **rowEntries;
+    unsigned int firstChangedRow;
+    unsigned int lastRowSent;
+    unsigned char resetCells:1;
+  } window;
+
+  struct {
+    TimePeriod delay;
+    CN_PacketInteger flags;
+  } status;
+
+  struct {
+    KeyNumberSet pressed;
+  } keys;
+};
+
+static crc_t
+makePacketChecksum (BrailleDisplay *brl, const void *packet, size_t size) {
+  CRCGenerator *crc = brl->data->crcGenerator;
+  crcResetGenerator(crc);
+  crcAddData(crc, packet, size);
+  return crcGetChecksum(crc);
+}
+
+typedef enum {
+  PVS_WAITING,
+  PVS_STARTED,
+  PVS_DONE
+} PacketVerificationState;
+
+typedef struct {
+  PacketVerificationState state;
+  unsigned escaped:1;
+} PacketVerificationData;
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  PacketVerificationData *pvd = data;
+  unsigned char *byte = &bytes[size-1];
+
+  if (*byte == CN_PACKET_FRAMING_BYTE) {
+    if ((pvd->state += 1) == PVS_DONE) {
+      if (pvd->escaped) return BRL_PVR_INVALID;
+      *length = size - 1;
+    } else {
+      *length = MAXIMUM_RESPONSE_SIZE;
+    }
+
+    return BRL_PVR_EXCLUDE;
+  }
+
+  if (pvd->state == PVS_WAITING) {
+    return BRL_PVR_INVALID;
+  }
+
+  if (*byte == CN_PACKET_ESCAPE_BYTE) {
+    if (pvd->escaped) return BRL_PVR_INVALID;
+    pvd->escaped = 1;
+    return BRL_PVR_EXCLUDE;
+  }
+
+  if (pvd->escaped) {
+    pvd->escaped = 0;
+    *byte ^= CN_PACKET_ESCAPE_BIT;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  while (1) {
+    PacketVerificationData pvd = {
+      .state = PVS_WAITING
+    };
+
+    size_t length = readBraillePacket(brl, NULL, packet, size, verifyPacket, &pvd);
+
+    if (length > 0) {
+      if (length < 3) {
+        logShortPacket(packet, length);
+        continue;
+      }
+
+      {
+        crc_t expected = CN_getResponseInteger(packet, (length -= 2));
+        crc_t actual = makePacketChecksum(brl, packet, length);
+
+        if (actual != expected) {
+          logBytes(LOG_WARNING,
+            "input packet checksum mismatch:"
+            " Actual:%"PRIcrc " Expected:%"PRIcrc,
+            packet, length,
+            actual, expected
+          );
+
+          continue;
+        }
+      }
+
+      {
+        const unsigned char *bytes = packet;
+        size_t expected = 0;
+
+        switch (bytes[0]) {
+          case CN_CMD_COLUMN_COUNT:
+          case CN_CMD_ROW_COUNT:
+          case CN_CMD_PROTOCOL_VERSION:
+          case CN_CMD_FIRMWARE_VERSION:
+          case CN_CMD_DEVICE_STATUS:
+          case CN_CMD_PRESSED_KEYS:
+          case CN_CMD_SEND_ROW:
+          case CN_CMD_RESET_CELLS:
+            expected = 3;
+            break;
+
+          default:
+            logUnexpectedPacket(packet, length);
+            continue;
+        }
+
+        if (length < expected) {
+          logTruncatedPacket(packet, length);
+          continue;
+        }
+      }
+    }
+
+    return length;
+  }
+}
+
+static inline void
+addByteToPacket (unsigned char **target, unsigned char byte) {
+  if ((byte == CN_PACKET_ESCAPE_BYTE) || (byte == CN_PACKET_FRAMING_BYTE)) {
+    *(*target)++ = CN_PACKET_ESCAPE_BYTE;
+    byte ^= CN_PACKET_ESCAPE_BIT;
+  }
+
+  *(*target)++ = byte;
+}
+
+static int
+writePacket (BrailleDisplay *brl, const unsigned char *packet, size_t size) {
+  logBytes(LOG_CATEGORY(OUTPUT_PACKETS), "raw", packet, size);
+
+  unsigned char buffer[1 + ((size + 2) * 2) + 1];
+  unsigned char *target = buffer;
+  *target++ = CN_PACKET_FRAMING_BYTE;
+
+  {
+    const unsigned char *source = packet;
+    const unsigned char *end = source + size;
+    while (source < end) addByteToPacket(&target, *source++);
+  }
+
+  {
+    uint16_t checksum = makePacketChecksum(brl, packet, size);
+    addByteToPacket(&target, (checksum & UINT8_MAX));
+    addByteToPacket(&target, (checksum >> 8));
+  }
+
+  *target++ = CN_PACKET_FRAMING_BYTE;
+  size = target - buffer;
+  int ok = writeBraillePacket(brl, NULL, buffer, size);
+
+  if (ok) {
+    brl->data->response.waiting = 1;
+    startTimePeriod(&brl->data->response.timeout, COMMAND_RESPONSE_TIMEOUT);
+    brl->data->response.command = packet[0];
+  } else {
+    brl->hasFailed = 1;
+  }
+
+  return ok;
+}
+
+static int
+writeSimpleCommand (BrailleDisplay *brl, unsigned char command) {
+  const unsigned char packet[] = {command};
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static RowEntry *
+getRowEntry (BrailleDisplay *brl, unsigned int index) {
+  return brl->data->window.rowEntries[index];
+}
+
+static void
+deallocateRowEntries (BrailleDisplay *brl, unsigned int count) {
+  RowEntry ***rowEntries = &brl->data->window.rowEntries;
+
+  if (*rowEntries) {
+    while (count > 0) free(getRowEntry(brl, --count));
+    free(*rowEntries);
+    *rowEntries = NULL;
+  }
+}
+
+static int
+allocateRowEntries (BrailleDisplay *brl) {
+  RowEntry ***rowEntries = &brl->data->window.rowEntries;
+
+  if (!(*rowEntries = malloc(ARRAY_SIZE(*rowEntries, brl->textRows)))) {
+    logMallocError();
+    return 0;
+  }
+
+  for (unsigned int index=0; index<brl->textRows; index+=1) {
+    RowEntry **row = &(*rowEntries)[index];
+    size_t rowLength = brl->textColumns;
+    size_t size = sizeof(**row) + (rowLength * 2);
+
+    if (!(*row = malloc(size))) {
+      logMallocError();
+      deallocateRowEntries(brl, (index + 1));
+      return 0;
+    }
+
+    memset(*row, 0, size);
+    (*row)->force = 1;
+    (*row)->oldCells = (*row)->newCells + rowLength;
+  }
+
+  return 1;
+}
+
+static void
+setRowHasChanged (BrailleDisplay *brl, unsigned int index) {
+  getRowEntry(brl, index)->haveNewCells = 1;
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "row has changed: %u", index);
+
+  if (index < brl->data->window.firstChangedRow) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "first changed row: %u", index);
+    brl->data->window.firstChangedRow = index;
+  }
+}
+
+static void
+resendRow (BrailleDisplay *brl) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "resending row: %u", brl->data->window.lastRowSent);
+  setRowHasChanged(brl, brl->data->window.lastRowSent);
+}
+
+static int
+refreshAllRows (BrailleDisplay *brl) {
+  brl->data->window.resetCells = 1;
+  return 1;
+}
+
+static int
+refreshRow (BrailleDisplay *brl, int row) {
+  return refreshAllRows(brl); // for now
+}
+
+ASYNC_ALARM_CALLBACK(CN_keysPoller) {
+  BrailleDisplay *brl = parameters->data;
+
+  if (!brl->data->response.waiting) {
+    writeSimpleCommand(brl, CN_CMD_PRESSED_KEYS);
+  } else if (afterTimePeriod(&brl->data->response.timeout, NULL)) {
+    unsigned char command = brl->data->response.command;
+    logMessage(LOG_WARNING, "command response timeout: Cmd:0X%02X", command);
+
+    switch (command) {
+      case CN_CMD_SEND_ROW:
+        resendRow(brl);
+        break;
+
+      case CN_CMD_RESET_CELLS:
+        brl->data->window.resetCells = 1;;
+        break;
+
+      default:
+        break;
+    }
+
+    writeSimpleCommand(brl, CN_CMD_DEVICE_STATUS);
+  }
+}
+
+static void
+stopKeysPoller (BrailleDisplay *brl) {
+  AsyncHandle *alarm = &brl->data->keysPollerAlarm;
+
+  if (*alarm) {
+    asyncCancelRequest(*alarm);
+    *alarm = NULL;
+  }
+}
+
+static int
+startKeysPoller (BrailleDisplay *brl) {
+  AsyncHandle alarm = brl->data->keysPollerAlarm;
+  if (alarm) return 1;
+
+  if (asyncNewRelativeAlarm(&alarm, 0, CN_keysPoller, brl)) {
+    if (asyncResetAlarmInterval(alarm, KEYS_POLL_INTERVAL)) {
+      brl->data->keysPollerAlarm = alarm;
+      return 1;
+    }
+
+    asyncCancelRequest(alarm);
+  }
+
+  return 0;
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  brl->data->response.waiting = 0;
+  ProbeResponseHandler *handler = brl->data->probe.responseHandler;
+  brl->data->probe.responseHandler = NULL;
+  return handler(brl, packet, size);
+}
+
+static BrailleResponseResult
+writeProbeCommand (BrailleDisplay *brl, unsigned char command, ProbeResponseHandler *handler) {
+  if (!writeSimpleCommand(brl, command)) return 0;
+  brl->data->probe.responseHandler = handler;
+  return 1;
+}
+
+static BrailleResponseResult
+writeNextProbeCommand (BrailleDisplay *brl, unsigned char command, ProbeResponseHandler *handler) {
+  return writeProbeCommand(brl, command, handler)? BRL_RSP_CONTINUE: BRL_RSP_FAIL;
+}
+
+static BrailleResponseResult
+handleDeviceStatus (BrailleDisplay *brl, const unsigned char *response, size_t size) {
+  if (response[0] != CN_CMD_DEVICE_STATUS) return BRL_RSP_UNEXPECTED;
+  brl->data->status.flags = CN_getResponseResult(response);
+  return BRL_RSP_DONE;
+}
+
+static BrailleResponseResult
+handleFirmwareVersion (BrailleDisplay *brl, const unsigned char *response, size_t size) {
+  if (response[0] != CN_CMD_FIRMWARE_VERSION) return BRL_RSP_UNEXPECTED;
+
+  response += 1;
+  size -= 1;
+  logMessage(LOG_INFO, "Firmware Version: %.*s", (int)size, response);
+
+  return writeNextProbeCommand(brl, CN_CMD_DEVICE_STATUS, handleDeviceStatus);
+}
+
+static BrailleResponseResult
+handleProtocolVersion (BrailleDisplay *brl, const unsigned char *response, size_t size) {
+  if (response[0] != CN_CMD_PROTOCOL_VERSION) return BRL_RSP_UNEXPECTED;
+  brl->data->probe.protocolVersion = CN_getResponseResult(response);
+  logMessage(LOG_INFO, "Protocol Version: %u", brl->data->probe.protocolVersion);
+  return writeNextProbeCommand(brl, CN_CMD_FIRMWARE_VERSION, handleFirmwareVersion);
+}
+
+static BrailleResponseResult
+handleRowCount (BrailleDisplay *brl, const unsigned char *response, size_t size) {
+  if (response[0] != CN_CMD_ROW_COUNT) return BRL_RSP_UNEXPECTED;
+  brl->textRows = CN_getResponseResult(response);
+  return writeNextProbeCommand(brl, CN_CMD_PROTOCOL_VERSION, handleProtocolVersion);
+}
+
+static BrailleResponseResult
+handleColumnCount (BrailleDisplay *brl, const unsigned char *response, size_t size) {
+  if (response[0] != CN_CMD_COLUMN_COUNT) return BRL_RSP_UNEXPECTED;
+  brl->textColumns = CN_getResponseResult(response);
+  return writeNextProbeCommand(brl, CN_CMD_ROW_COUNT, handleRowCount);
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  return writeProbeCommand(brl, CN_CMD_COLUMN_COUNT, handleColumnCount);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_16C0_05E1)
+    "bristolbraille.co.uk",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_STRING_LIST(usbProducts_16C0_05E1)
+    "Canute 360",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X16C0, .product=0X05E1,
+      .manufacturers = usbManufacturers_16C0_05E1,
+      .products = usbProducts_16C0_05E1,
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=3, .outputEndpoint=2,
+      .serial = &serialParameters,
+      .resetDevice = 1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    brl->data->crcGenerator = NULL;
+    brl->data->keysPollerAlarm = NULL;
+
+    brl->data->probe.responseHandler = NULL;
+    brl->data->probe.protocolVersion = 0;
+
+    brl->data->response.waiting = 0;
+
+    startTimePeriod(&brl->data->window.retryDelay, 0);
+    brl->data->window.rowEntries = NULL;
+    brl->data->window.resetCells = 0;
+
+    brl->data->keys.pressed = 0;
+
+    {
+      static const CRCAlgorithm algorithm = {
+        .primaryName = CN_CRC_ALGORITHM_NAME,
+        .checksumWidth = CN_CRC_CHECKSUM_WIDTH,
+        .reflectData = CN_CRC_REFLECT_DATA,
+        .reflectResult = CN_CRC_REFLECT_RESULT,
+        .generatorPolynomial = UINT16_C(CN_CRC_GENERATOR_POLYNOMIAL),
+        .initialValue = UINT16_C(CN_CRC_INITIAL_VALUE),
+        .xorMask = UINT16_C(CN_CRC_XOR_MASK),
+        .checkValue = UINT16_C(CN_CRC_CHECK_VALUE),
+        .residue = UINT16_C(CN_CRC_RESIDUE),
+      };
+
+      brl->data->crcGenerator = crcNewGenerator(&algorithm);
+    }
+
+    if (brl->data->crcGenerator) {
+      if (connectResource(brl, device)) {
+        unsigned char response[MAXIMUM_RESPONSE_SIZE];
+
+        if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT,
+                                NULL, PROBE_RESPONSE_TIMEOUT,
+                                writeIdentifyRequest,
+                                readPacket, &response, sizeof(response),
+                                isIdentityResponse)) {
+          if (allocateRowEntries(brl)) {
+            brl->refreshBrailleDisplay = refreshAllRows;
+            brl->refreshBrailleRow = refreshRow;
+            brl->cellSize = 6;
+
+            setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+            makeOutputTable(dotsTable_ISO11548_1);
+
+            if (startKeysPoller(brl)) {
+              return 1;
+            }
+
+            deallocateRowEntries(brl, brl->textRows);
+          }
+        }
+
+        disconnectBrailleResource(brl, NULL);
+      }
+
+      crcDestroyGenerator(brl->data->crcGenerator);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  stopKeysPoller(brl);
+  disconnectBrailleResource(brl, NULL);
+
+  deallocateRowEntries(brl, brl->textRows);
+  crcDestroyGenerator(brl->data->crcGenerator);
+
+  free(brl->data);
+  brl->data = NULL;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned int length = brl->textColumns;
+  const unsigned char *cells = brl->buffer;
+
+  for (unsigned int index=0; index<brl->textRows; index+=1) {
+    RowEntry *row = getRowEntry(brl, index);
+
+    if (cellsHaveChanged(row->newCells, cells, length, NULL, NULL, &row->force)) {
+      setRowHasChanged(brl, index);
+    }
+
+    cells += length;
+  }
+
+  return 1;
+}
+
+static int
+startUpdate (BrailleDisplay *brl) {
+  if (!afterTimePeriod(&brl->data->window.retryDelay, NULL)) return 0;
+
+  if (brl->data->window.resetCells) {
+    brl->data->window.resetCells = 0;
+    brl->data->window.firstChangedRow = 0;
+
+    for (unsigned int index=0; index<brl->textRows; index+=1) {
+      RowEntry *row = getRowEntry(brl, index);
+      row->haveNewCells = 1;
+      row->haveOldCells = 0;
+    }
+
+    writeSimpleCommand(brl, CN_CMD_RESET_CELLS);
+    return 1;
+  }
+
+  while (brl->data->window.firstChangedRow < brl->textRows) {
+    RowEntry *row = getRowEntry(brl, brl->data->window.firstChangedRow);
+
+    if (row->haveNewCells) {
+      unsigned int length = brl->textColumns;
+
+      if (row->haveOldCells) {
+        if (memcmp(row->newCells, row->oldCells, length) == 0) {
+          row->haveNewCells = 0;
+        }
+      }
+
+      if (row->haveNewCells) {
+        unsigned char packet[2 + length];
+        unsigned char *byte = packet;
+
+        *byte++ = CN_CMD_SEND_ROW;
+        *byte++ = brl->data->window.firstChangedRow;
+        byte = translateOutputCells(byte, row->newCells, length);
+
+        size_t size = byte - packet;
+        logBytes(LOG_CATEGORY(BRAILLE_DRIVER), "sending row: %u", packet, size, brl->data->window.firstChangedRow);
+
+        if (writePacket(brl, packet, size)) {
+          row->haveNewCells = 0;
+          brl->data->window.lastRowSent = brl->data->window.firstChangedRow++;
+          memcpy(row->oldCells, row->newCells, length);
+        }
+
+        return 1;
+      }
+    }
+
+    brl->data->window.firstChangedRow += 1;
+  }
+
+  return 0;
+}
+
+static void
+startNextCommand (BrailleDisplay *brl) {
+  if (!(brl->data->status.flags & CN_STATUS_MOTORS_ACTIVE)) {
+    startUpdate(brl);
+  } else if (afterTimePeriod(&brl->data->status.delay, NULL)) {
+    startTimePeriod(&brl->data->status.delay, MOTORS_POLL_INTERVAL);
+    writeSimpleCommand(brl, CN_CMD_DEVICE_STATUS);
+  }
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    brl->data->response.waiting = 0;
+    brl->writeDelay = 0;
+
+    unsigned char command = packet[0];
+    CN_PacketInteger result = CN_getResponseResult(packet);
+
+    unsigned int motorsTime = 0;
+
+    switch (command) {
+      case CN_CMD_PRESSED_KEYS:
+        enqueueUpdatedKeys(brl, result, &brl->data->keys.pressed, CN_GRP_NavigationKeys, 0);
+        startNextCommand(brl);
+        continue;
+
+      case CN_CMD_DEVICE_STATUS:
+        brl->data->status.flags = result;
+        continue;
+
+      case CN_CMD_SEND_ROW: {
+        RowEntry *row = getRowEntry(brl, brl->data->window.lastRowSent);
+
+        if (row->haveOldCells) {
+          motorsTime = ROW_UPDATE_TIME;
+        } else {
+          row->haveOldCells = 1;
+        }
+
+        break;
+      }
+
+      case CN_CMD_RESET_CELLS:
+        motorsTime = CELLS_RESET_TIME;
+        break;
+
+      default:
+        logUnexpectedPacket(packet, size);
+        continue;
+    }
+
+    if (result) {
+      logMessage(LOG_WARNING,
+        "command failed: Cmd:0X%02X Err:0X%02X",
+        command, result
+      );
+
+      switch (command) {
+        case CN_CMD_SEND_ROW:
+          resendRow(brl);
+          goto UPDATE_FAILED;
+
+        case CN_CMD_RESET_CELLS:
+          brl->data->window.resetCells = 1;
+          goto UPDATE_FAILED;
+
+        UPDATE_FAILED:
+          startTimePeriod(&brl->data->window.retryDelay, UPDATE_RETRY_DELAY);
+          continue;
+
+        default:
+          continue;;
+      }
+    } else if (motorsTime) {
+      brl->data->status.flags |= CN_STATUS_MOTORS_ACTIVE;
+      startTimePeriod(&brl->data->status.delay, motorsTime);
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Canute/brldefs-cn.h b/Drivers/Braille/Canute/brldefs-cn.h
new file mode 100644
index 0000000..c203050
--- /dev/null
+++ b/Drivers/Braille/Canute/brldefs-cn.h
@@ -0,0 +1,86 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CN_BRLDEFS
+#define BRLTTY_INCLUDED_CN_BRLDEFS
+
+typedef enum {
+  CN_CMD_COLUMN_COUNT = 0X00,
+  CN_CMD_ROW_COUNT = 0X01,
+  CN_CMD_PROTOCOL_VERSION = 0X03,
+  CN_CMD_SEND_ROW = 0X06,
+  CN_CMD_RESET_CELLS = 0X07,
+  CN_CMD_LOWER_ROWS = 0X09,
+  CN_CMD_PRESSED_KEYS = 0X0A,
+  CN_CMD_FIRMWARE_VERSION = 0X0B,
+  CN_CMD_DEVICE_STATUS = 0X0D,
+  CN_CMD_SET_ROW = 0X0E,
+} CN_Command;
+
+typedef enum {
+  CN_KEY_Help = 0,
+  CN_KEY_Line1 = 1,
+  CN_KEY_Line2 = 2,
+  CN_KEY_Line3 = 3,
+  CN_KEY_Line4 = 4,
+  CN_KEY_Line5 = 5,
+  CN_KEY_Line6 = 6,
+  CN_KEY_Line7 = 7,
+  CN_KEY_Line8 = 8,
+  CN_KEY_Line9 = 9,
+  CN_KEY_Refresh = 10,
+  CN_KEY_Back = 11,
+  CN_KEY_Menu = 12,
+  CN_KEY_Forward = 13,
+} CN_NavigationKey;
+
+typedef enum {
+  CN_GRP_NavigationKeys = 0,
+} CN_KeyGroup;
+
+typedef enum {
+  CN_STATUS_MOTORS_ACTIVE = 0X01,
+} CN_Status;
+
+#define CN_CRC_ALGORITHM_NAME "CRC-16/ISO-HDLC"
+#define CN_CRC_CHECKSUM_WIDTH 16
+#define CN_CRC_REFLECT_DATA 1
+#define CN_CRC_REFLECT_RESULT 1
+#define CN_CRC_GENERATOR_POLYNOMIAL 0X1021
+#define CN_CRC_INITIAL_VALUE 0XFFFF
+#define CN_CRC_XOR_MASK 0XFFFF
+#define CN_CRC_CHECK_VALUE 0X906E
+#define CN_CRC_RESIDUE 0XF0B8
+
+#define CN_PACKET_FRAMING_BYTE 0X7E
+#define CN_PACKET_ESCAPE_BYTE 0X7D
+#define CN_PACKET_ESCAPE_BIT 0X20
+
+typedef uint16_t CN_PacketInteger;
+
+static inline CN_PacketInteger CN_getResponseInteger(
+    const unsigned char *response, unsigned int offset) {
+  return response[offset] | (response[offset + 1] << 8);
+}
+
+static inline CN_PacketInteger CN_getResponseResult(
+    const unsigned char *response) {
+  return CN_getResponseInteger(response, 1);
+}
+
+#endif /* BRLTTY_INCLUDED_CN_BRLDEFS */
diff --git a/Drivers/Braille/Cebra/Makefile.in b/Drivers/Braille/Cebra/Makefile.in
new file mode 100644
index 0000000..5cdf4b0
--- /dev/null
+++ b/Drivers/Braille/Cebra/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ce
+DRIVER_NAME = Cebra
+DRIVER_USAGE = 20/40/60/80/100/120/140
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Cebra/braille.c b/Drivers/Braille/Cebra/braille.c
new file mode 100644
index 0000000..ed5f14e
--- /dev/null
+++ b/Drivers/Braille/Cebra/braille.c
@@ -0,0 +1,423 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-ce.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+#define MAXIMUM_RESPONSE_SIZE (0XFF + 4)
+#define MAXIMUM_CELL_COUNT 140
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  KEY_NAME_ENTRY(CE_KEY_PadLeft1, "PadLeft1"),
+  KEY_NAME_ENTRY(CE_KEY_PadUp1, "PadUp1"),
+  KEY_NAME_ENTRY(CE_KEY_PadCenter1, "PadCenter1"),
+  KEY_NAME_ENTRY(CE_KEY_PadDown1, "PadDown1"),
+  KEY_NAME_ENTRY(CE_KEY_PadRight1, "PadRight1"),
+
+  KEY_NAME_ENTRY(CE_KEY_LeftUpper1, "LeftUpper1"),
+  KEY_NAME_ENTRY(CE_KEY_LeftMiddle1, "LeftMiddle1"),
+  KEY_NAME_ENTRY(CE_KEY_LeftLower1, "LeftLower1"),
+  KEY_NAME_ENTRY(CE_KEY_RightUpper1, "RightUpper1"),
+  KEY_NAME_ENTRY(CE_KEY_RightMiddle1, "RightMiddle1"),
+  KEY_NAME_ENTRY(CE_KEY_RightLower1, "RightLower1"),
+
+  KEY_NAME_ENTRY(CE_KEY_PadLeft2, "PadLeft2"),
+  KEY_NAME_ENTRY(CE_KEY_PadUp2, "PadUp2"),
+  KEY_NAME_ENTRY(CE_KEY_PadCenter2, "PadCenter2"),
+  KEY_NAME_ENTRY(CE_KEY_PadDown2, "PadDown2"),
+  KEY_NAME_ENTRY(CE_KEY_PadRight2, "PadRight2"),
+
+  KEY_NAME_ENTRY(CE_KEY_LeftUpper2, "LeftUpper2"),
+  KEY_NAME_ENTRY(CE_KEY_LeftMiddle2, "LeftMiddle2"),
+  KEY_NAME_ENTRY(CE_KEY_LeftLower2, "LeftLower2"),
+  KEY_NAME_ENTRY(CE_KEY_RightUpper2, "RightUpper2"),
+  KEY_NAME_ENTRY(CE_KEY_RightMiddle2, "RightMiddle2"),
+  KEY_NAME_ENTRY(CE_KEY_RightLower2, "RightLower2"),
+
+  KEY_GROUP_ENTRY(CE_GRP_RoutingKey, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(novem)
+  KEY_NAME_ENTRY(0X03, "Dot7"),
+  KEY_NAME_ENTRY(0X07, "Dot3"),
+  KEY_NAME_ENTRY(0X0B, "Dot2"),
+  KEY_NAME_ENTRY(0X0F, "Dot1"),
+  KEY_NAME_ENTRY(0X13, "Dot4"),
+  KEY_NAME_ENTRY(0X17, "Dot5"),
+  KEY_NAME_ENTRY(0X1B, "Dot6"),
+  KEY_NAME_ENTRY(0X1F, "Dot8"),
+
+  KEY_NAME_ENTRY(0X10, "LeftSpace"),
+  KEY_NAME_ENTRY(0X18, "RightSpace"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(novem)
+  KEY_NAME_TABLE(novem),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+DEFINE_KEY_TABLE(novem)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+  &KEY_TABLE_DEFINITION(novem),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  unsigned char identifier;
+  unsigned char cellCount;
+  const KeyTableDefinition *ktd;
+} ModelEntry;
+
+static const ModelEntry modelTable[] = {
+  { .identifier = 0X68,
+    .cellCount = 0,
+    .ktd = &KEY_TABLE_DEFINITION(novem)
+  },
+
+  { .identifier = 0X70,
+    .cellCount = 0,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X72,
+    .cellCount = 20,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X74,
+    .cellCount = 40,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X76,
+    .cellCount = 60,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X78,
+    .cellCount = 80,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X7A,
+    .cellCount = 100,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X7C,
+    .cellCount = 120,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0X7E,
+    .cellCount = 140,
+    .ktd = &KEY_TABLE_DEFINITION(all)
+  },
+
+  { .identifier = 0 }
+};
+
+struct BrailleDataStruct {
+  const ModelEntry *model;
+  unsigned char forceRewrite;
+  unsigned char acknowledgementPending;
+  unsigned char textCells[MAXIMUM_CELL_COUNT];
+};
+
+static const ModelEntry *
+getModelEntry (unsigned char identifier) {
+  const ModelEntry *model = modelTable;
+
+  while (model->identifier) {
+    if (identifier == model->identifier) return model;
+    model += 1;
+  }
+
+  logMessage(LOG_WARNING, "unknown %s model: 0X%02X",
+             STRINGIFY(DRIVER_NAME), identifier);
+  return NULL;
+}
+
+static int
+setModel (BrailleDisplay *brl, unsigned char identifier) {
+  const ModelEntry *model = getModelEntry(identifier);
+
+  if (model) {
+    logMessage(LOG_NOTICE, "%s Model: 0X%02X, %u cells",
+               STRINGIFY(DRIVER_NAME), model->identifier, model->cellCount);
+
+    brl->data->model = model;
+    brl->textColumns = model->cellCount;
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+writePacket (BrailleDisplay *brl, unsigned char type, size_t size, const unsigned char *data) {
+  unsigned char bytes[size + 5];
+  unsigned char *byte = bytes;
+
+  *byte++ = CE_PKT_BEGIN;
+  *byte++ = brl->data->model->identifier;
+  *byte++ = size + 1;
+  *byte++ = type;
+  byte = mempcpy(byte, data, size);
+  *byte++ = CE_PKT_END;
+
+  return writeBytes(brl, bytes, byte-bytes);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  const unsigned char byte = bytes[size-1];
+
+  if (size == 1) {
+    switch (byte) {
+      case CE_RSP_Identity:
+        *length = 2;
+        break;
+
+      case CE_PKT_BEGIN:
+        *length = 3;
+        break;
+
+      default:
+        return BRL_PVR_INVALID;
+    }
+  } else {
+    switch (bytes[0]) {
+      case CE_PKT_BEGIN:
+        if (size == 2) {
+          if (byte != brl->data->model->identifier) {
+            if (!setModel(brl, byte)) return BRL_PVR_INVALID;
+            brl->resizeRequired = 1;
+          }
+        } else if (size == 3) {
+          *length += byte + 1;
+        } else if (size == *length) {
+          if (byte != CE_PKT_END) return BRL_PVR_INVALID;
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 19200,
+    .parity = SERIAL_PARITY_ODD
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0403, .product=0X6001, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  descriptor.bluetooth.channelNumber = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char bytes[] = {CE_REQ_Identify};
+  return writeBytes(brl, bytes, sizeof(bytes));
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const unsigned char *bytes = packet;
+
+  return (bytes[0] == CE_RSP_Identity)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned char response[MAXIMUM_RESPONSE_SIZE];
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        if (setModel(brl, response[1])) {
+          setBrailleKeyTable(brl, brl->data->model->ktd);
+          makeOutputTable(dotsTable_ISO11548_1);
+
+          brl->data->forceRewrite = 1;
+          brl->data->acknowledgementPending = 0;
+
+          return 1;
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (!brl->data->acknowledgementPending) {
+    if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
+      unsigned char cells[brl->textColumns];
+
+      translateOutputCells(cells, brl->data->textCells, brl->textColumns);
+      if (!writePacket(brl, CE_PKT_REQ_Write, brl->textColumns, cells)) return 0;
+      brl->data->acknowledgementPending = 1;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    switch (packet[0]) {
+      case CE_PKT_BEGIN: {
+        const unsigned char *bytes = &packet[4];
+        size_t count = packet[2] - 1;
+
+        switch (packet[3]) {
+          case CE_PKT_RSP_NavigationKey:
+            if (count == 1) {
+              KeyGroup group;
+              unsigned char key = bytes[0];
+              int press = !(key & CE_KEY_RELEASE);
+              key &= ~CE_KEY_RELEASE;
+
+              if ((key >= CE_KEY_ROUTING_MIN) && (key <= CE_KEY_ROUTING_MAX)) {
+                group = CE_GRP_RoutingKey;
+                key -= CE_KEY_ROUTING_MIN;
+              } else {
+                group = CE_GRP_NavigationKey;
+              }
+
+              enqueueKeyEvent(brl, group, key, press);
+              continue;
+            }
+            break;
+
+          case CE_PKT_RSP_Confirmation:
+            if (count > 0) {
+              switch (bytes[0]) {
+                case 0X7D:
+                  brl->data->forceRewrite = 1;
+                case 0X7E:
+                  brl->data->acknowledgementPending = 0;
+                  continue;
+
+                default:
+                  break;
+              }
+            }
+            break;
+
+          case CE_PKT_RSP_KeyboardKey:
+            while (count--) enqueueCommand(BRL_CMD_BLK(PASSAT) | BRL_ARG_PUT(*bytes++));
+            continue;
+
+          default:
+            break;
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Cebra/brldefs-ce.h b/Drivers/Braille/Cebra/brldefs-ce.h
new file mode 100644
index 0000000..f1c55e3
--- /dev/null
+++ b/Drivers/Braille/Cebra/brldefs-ce.h
@@ -0,0 +1,70 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CE_BRLDEFS
+#define BRLTTY_INCLUDED_CE_BRLDEFS
+
+#define CE_REQ_Identify 0XF8
+#define CE_RSP_Identity 0XFE
+
+#define CE_PKT_BEGIN 0X79
+#define CE_PKT_END 0X16
+#define CE_PKT_REQ_Write 0X01
+#define CE_PKT_RSP_NavigationKey 0X04
+#define CE_PKT_RSP_Confirmation 0X07
+#define CE_PKT_RSP_KeyboardKey 0X09
+
+typedef enum {
+  CE_KEY_PadLeft1     = 0X0C,
+  CE_KEY_PadUp1       = 0X0F,
+  CE_KEY_PadCenter1   = 0X10,
+  CE_KEY_PadDown1     = 0X13,
+  CE_KEY_PadRight1    = 0X14,
+
+  CE_KEY_LeftUpper1   = 0X07,
+  CE_KEY_LeftMiddle1  = 0X0B,
+  CE_KEY_LeftLower1   = 0X1B,
+  CE_KEY_RightUpper1  = 0X03,
+  CE_KEY_RightMiddle1 = 0X17,
+  CE_KEY_RightLower1  = 0X1F,
+
+  CE_KEY_PadLeft2     = 0X06,
+  CE_KEY_PadUp2       = 0X1A,
+  CE_KEY_PadCenter2   = 0X0A,
+  CE_KEY_PadDown2     = 0X19,
+  CE_KEY_PadRight2    = 0X0E,
+
+  CE_KEY_LeftUpper2   = 0X16,
+  CE_KEY_LeftMiddle2  = 0X12,
+  CE_KEY_LeftLower2   = 0X15,
+  CE_KEY_RightUpper2  = 0X1E,
+  CE_KEY_RightMiddle2 = 0X0D,
+  CE_KEY_RightLower2  = 0X1D,
+
+  CE_KEY_ROUTING_MIN = 0X20,
+  CE_KEY_ROUTING_MAX = 0X6F,
+
+  CE_KEY_RELEASE     = 0X80
+} CE_NavigationKey;
+
+typedef enum {
+  CE_GRP_NavigationKey,
+  CE_GRP_RoutingKey
+} CE_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_CE_BRLDEFS */ 
diff --git a/Drivers/Braille/CombiBraille/Makefile.in b/Drivers/Braille/CombiBraille/Makefile.in
new file mode 100644
index 0000000..f765fe6
--- /dev/null
+++ b/Drivers/Braille/CombiBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = cb
+DRIVER_NAME = CombiBraille
+DRIVER_USAGE = 25/45/85
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Nikhil Nair
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/CombiBraille/README b/Drivers/Braille/CombiBraille/README
new file mode 100644
index 0000000..6dc77db
--- /dev/null
+++ b/Drivers/Braille/CombiBraille/README
@@ -0,0 +1,138 @@
+Driver for Tieman B.V.'s CombiBraille series
+
+Copyright (C) 1995, 1996 by Nikhil Nair.
+
+This driver is part of BRLTTY, and as such it is placed under the
+terms of the GNU Lesser General Public License, as published by the Free
+Software Foundation.  Please see the file LICENSE-LGPL in the top-level
+directory for details.
+
+This driver was maintained by Nikhil Nair <nn201@cus.cam.ac.uk>.
+
+-----------------------------------------------------------------------------
+
+SUPPORTED HARDWARE
+==================
+
+This driver has only been used on a CombiBraille 40, although,
+according to technical specifications provided by Tieman
+B.V. <tieman@xs4all.nl>, it should work on the 20 and 80 cell models
+as well.  There is no support for the parallel interface, and so no
+possibility of a 2-dimensional setup using a second display.
+
+There is now some very rudimentary support for the internal speech
+synthesiser.  This, however, leaves much to be desired: there are only
+`speak line' and `mute' functions, and the speed and pitch of the
+speech is not yet configurable.
+
+
+CONFIGURATION AND COMPILATION
+=============================
+
+For general information about configuring and compiling BRLTTY, please
+see the BRLTTY manual.
+
+Any CombiBraille-specific configuration is done by editing the
+braille.h file in this directory.  However, such configuration is
+probably unnecessary as the display size is autodetected during
+initialisation at run-time.
+
+By default, BRLTTY can be started even if the CombiBraille is switched
+off or not connected - it will wait in the background, checking every
+five seconds for a display.  This behaviour can be changed by
+adjusting the values of ACK_TIMEOUT and MAX_ATTEMPTS.
+
+The driver should probably set the autorepeat delay and rate of the
+Braille display's keys during initialisation.  This is yet to be
+implemented.
+
+
+GETTING STARTED
+===============
+
+The CombiBraille must be connected to the serial device you chose
+during configuration, unless of course you use the -d option to
+BRLTTY.  The serial interface must be selected; this is done by
+holding the left-most thumb key while turning on the display.  BRLTTY
+should then display its startup message before starting to echo the
+screen.
+
+
+KEY BINDINGS
+============
+
+The key bindings - particularly with regard to the Braille dot keys -
+have been redesigned from the DOS driver provided with the
+CombiBraille.  However, the five thumb keys work similarly.  If we
+label them A to E from left to right, then A is FWINLT (go left one
+full window width), B is LNUP (go up one line), D is LNDN (go down one
+line) and E is FWINRT (go right one full window width).  C toggles
+cursor tracking, so C twice moves the window to the cursor position.
+
+The extra cursor routing key (over the gap between the status cells
+and the main display) is used to bring up the help screen.  Pressing
+this key again goes back to normal operation.  The help screen has a
+full list of key bindings (apart from the cursor routing keys); it can
+be found in plain text format in the file help.txt in this
+directory.  The dot keys have been numbered (from left to right): 3,
+2, 1, 4, 5, 6; capital letters in brackets refer to thumb keys,
+e.g. (A) means A alone, (BD) means B and D pressed together and (CC)
+means C, alone, twice.
+
+All functions bound to the thumb keys can also be executed by using
+the Braille dot keys.  The converse, however, is not the case, as
+there are far more available combinations of Braille dot keys than of
+thumb keys (using not more than two at once).
+
+Cursor Routing Keys
+-------------------
+
+The keys above the 20/40/80 cells of the main display can be used for
+a more accurate form of cursor routing, specifying that particular
+position rather than the start of the Braille window.
+
+The extra six cursor routing keys have special meanings.  The sixth
+from the left toggles help mode; the fifth toggles freeze mode; the
+fourth is the RESET button and the third is the CONFMENU button.
+
+Special Cut Function
+--------------------
+
+The main cursor routing keys, together with the leftmost two of the
+extra ones, can be used for a more advanced form of cutting.
+
+To mark the top left corner of the rectangle to be cut, press the
+leftmost cursor routing key followed immediately by the one over the
+appropriate cell.  Then, to mark the bottom right corner, press the
+second cursor routing key followed immediately by the one over the
+appropriate cell.
+
+
+THE STATUS CELLS
+================
+
+The status cells are used slightly differently from the DOS driver.
+The first four cells are used to denote the positions of the cursor
+and the Braille window, and form two lines of four numbers.  The top
+line is the cursor position in the format CCRR where CC is the column
+number and RR is the row number.  The second line is the window
+position in the same format.  The top left corner of the screen is
+0000, in this notation.
+
+The fifth status cell is a set of flags, as follows:
+
+Dot Number      Dot Present Means
+     1          The screen is frozen
+     2          Attribute display is on
+     3          Audio signals are on
+     4          The cursor is visible
+     5          Cursor shape is block
+     6          Cursor blink is on
+     7          Cursor tracking is on
+     8          Sliding window is on
+
+
+Nikhil Nair
+Trinity College, CAMBRIDGE, CB2 1TQ, England
+Tel.: +44 1223 368353
+Email: nn201@cus.cam.ac.uk
diff --git a/Drivers/Braille/CombiBraille/braille.c b/Drivers/Braille/CombiBraille/braille.c
new file mode 100644
index 0000000..1d9f8f2
--- /dev/null
+++ b/Drivers/Braille/CombiBraille/braille.c
@@ -0,0 +1,368 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ascii.h"
+
+#define BRL_STATUS_FIELDS sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+
+#include "brldefs-cb.h"
+#include "braille.h"
+
+BEGIN_KEY_NAME_TABLE(dot)
+  KEY_NAME_ENTRY(CB_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(CB_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(CB_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(CB_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(CB_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(CB_KEY_Dot6, "Dot6"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(thumb)
+  KEY_NAME_ENTRY(CB_KEY_Thumb1, "Thumb1"),
+  KEY_NAME_ENTRY(CB_KEY_Thumb2, "Thumb2"),
+  KEY_NAME_ENTRY(CB_KEY_Thumb3, "Thumb3"),
+  KEY_NAME_ENTRY(CB_KEY_Thumb4, "Thumb4"),
+  KEY_NAME_ENTRY(CB_KEY_Thumb5, "Thumb5"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status)
+  KEY_NAME_ENTRY(CB_KEY_Status1, "Status1"),
+  KEY_NAME_ENTRY(CB_KEY_Status2, "Status2"),
+  KEY_NAME_ENTRY(CB_KEY_Status3, "Status3"),
+  KEY_NAME_ENTRY(CB_KEY_Status4, "Status4"),
+  KEY_NAME_ENTRY(CB_KEY_Status5, "Status5"),
+  KEY_NAME_ENTRY(CB_KEY_Status6, "Status6"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(CB_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(dot),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(status),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+#define CONNECTION_TIMEOUT 1000
+#define CONNECTION_RETRIES 0
+#define MAX_INPUT_PACKET_SIZE 4
+#define MAX_TEXT_CELLS 80
+#define STATUS_CELLS 5
+
+BrailleDisplay *cbBrailleDisplay = NULL;
+
+typedef struct {
+  char identifier;
+  char textColumns;
+} ModelEntry;
+
+static const ModelEntry modelTable[] = {
+  { .identifier = 0,
+    .textColumns = 20,
+  },
+
+  { .identifier = 1,
+    .textColumns = 40,
+  },
+
+  { .identifier = 2,
+    .textColumns = 80,
+  },
+
+  { .identifier = 7,
+    .textColumns = 20,
+  },
+
+  { .identifier = 8,
+    .textColumns = 40,
+  },
+
+  { .identifier = 9,
+    .textColumns = 80,
+  },
+
+  { .textColumns = 0 }
+};
+
+static const ModelEntry *
+findModelEntry (unsigned char identifier) {
+  const ModelEntry *model = modelTable;
+
+  while (model->textColumns) {
+    if (identifier == model->identifier) return model;
+    model += 1;
+  }
+          
+  return NULL;
+}
+
+struct BrailleDataStruct {
+  const ModelEntry *model;
+
+  struct {
+    unsigned char refresh;
+    unsigned char previous[MAX_TEXT_CELLS];
+  } text;
+
+  struct {
+    unsigned char refresh;
+    unsigned char current[STATUS_CELLS];
+    unsigned char previous[STATUS_CELLS];
+  } status;
+};
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      if (byte != ASCII_ESC) return BRL_PVR_INVALID;
+      *length = 2;
+      return BRL_PVR_INCLUDE;
+
+    case 2:
+      switch (byte) {
+        case CB_PKT_KeepAlive:
+          *length = 2;
+          return BRL_PVR_INCLUDE;
+
+        case CB_PKT_DeviceIdentity:
+        case CB_PKT_RoutingKey:
+          *length = 3;
+          return BRL_PVR_INCLUDE;
+
+        case CB_PKT_NavigationKeys:
+          *length = 4;
+          return BRL_PVR_INCLUDE;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+
+    default:
+      return BRL_PVR_INCLUDE;
+  }
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *bytes, size_t size) {
+  return readBraillePacket(brl, NULL, bytes, size, verifyPacket, NULL);
+}
+
+static int
+writePacket (BrailleDisplay *brl, const void *bytes, size_t size) {
+  return writeBraillePacket(brl, NULL, bytes, size);
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {
+    ASCII_ESC, CB_PKT_DeviceIdentity
+  };
+
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const unsigned char *bytes = packet;
+  return (bytes[1] == CB_PKT_DeviceIdentity)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = CB_SERIAL_BAUD,
+    .flowControl = SERIAL_FLOW_HARDWARE,
+  };
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->text.refresh = 1;
+    brl->data->status.refresh = 1;
+
+    if (connectResource(brl, device)) {
+      unsigned char response[MAX_INPUT_PACKET_SIZE];
+
+      int detected = probeBrailleDisplay(
+        brl, CONNECTION_RETRIES, NULL, CONNECTION_TIMEOUT,
+        writeIdentifyRequest,
+        readPacket, response, sizeof(response),
+        isIdentityResponse
+      );
+
+      if (detected) {
+        unsigned char identifier = response[2];
+
+        if ((brl->data->model = findModelEntry(identifier))) {
+          brl->textColumns = brl->data->model->textColumns;
+          brl->textRows = 1;
+
+          brl->statusColumns = STATUS_CELLS;
+          brl->statusRows = 1;
+
+          setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+          MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X80, 0X40, 0X20, 0X08, 0X10);
+
+          cbBrailleDisplay = brl;
+          return 1;
+        } else {
+          logMessage(LOG_ERR, "detected unknown CombiBraille model with ID %02X", identifier);
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  cbBrailleDisplay = NULL;
+  disconnectBrailleResource(brl, NULL);
+  free(brl->data);
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *s) {
+  memcpy(brl->data->status.current, s, brl->statusColumns);
+  return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  int textChanged = cellsHaveChanged(
+    brl->data->text.previous, brl->buffer, brl->textColumns,
+    NULL, NULL, &brl->data->text.refresh
+  );
+
+  int statusChanged = cellsHaveChanged(
+    brl->data->status.previous,
+    brl->data->status.current,
+    brl->statusColumns,
+    NULL, NULL, &brl->data->status.refresh
+  );
+
+  /* Only refresh display if the data has changed: */
+  if (textChanged || statusChanged) {
+    static const unsigned char header[] = {
+      ASCII_ESC, CB_PKT_WriteCells
+    };
+
+    unsigned char buffer[sizeof(header) + ((brl->statusColumns + brl->textColumns) * 2)];
+
+    unsigned char *byte = buffer;
+    byte = mempcpy(byte, header, sizeof(header));
+
+    for (int i=0; i<brl->statusColumns; i+=1) {
+      const unsigned char c = translateOutputCell(brl->data->status.current[i]);
+      if (c == ASCII_ESC) *byte++ = c;
+      *byte++ = c;
+    }
+
+    for (int i=0; i<brl->textColumns; i+=1) {
+      const unsigned char c = translateOutputCell(brl->buffer[i]);
+      if (c == ASCII_ESC) *byte++ = c;
+      *byte++ = c;
+    }
+
+    {
+      const size_t size = byte - buffer;
+      if (!writePacket(brl, buffer, size)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAX_INPUT_PACKET_SIZE];
+  size_t length;
+
+  while ((length = readPacket(brl, packet, sizeof(packet)))) {
+    switch (packet[1]) {
+      case CB_PKT_KeepAlive:
+        continue;
+
+      case CB_PKT_RoutingKey: {
+        char key = packet[2];
+
+        if (key < 6) {
+          enqueueKey(brl, CB_GRP_NavigationKeys, (CB_KEY_Status1 + key));
+        } else {
+          enqueueKey(brl, CB_GRP_RoutingKeys, (key - 6));
+        }
+
+        continue;
+      }
+
+      case CB_PKT_NavigationKeys: {
+        KeyNumberSet keys = packet[2] | (packet[3] << 8);
+        enqueueKeys(brl, keys, CB_GRP_NavigationKeys, CB_KEY_Dot6);
+        continue;
+      }
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/CombiBraille/braille.h b/Drivers/Braille/CombiBraille/braille.h
new file mode 100644
index 0000000..aa1f59b
--- /dev/null
+++ b/Drivers/Braille/CombiBraille/braille.h
@@ -0,0 +1,21 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* used by speech.c */
+#include "brl_types.h"
+extern BrailleDisplay *cbBrailleDisplay;
diff --git a/Drivers/Braille/CombiBraille/brldefs-cb.h b/Drivers/Braille/CombiBraille/brldefs-cb.h
new file mode 100644
index 0000000..0a8bc87
--- /dev/null
+++ b/Drivers/Braille/CombiBraille/brldefs-cb.h
@@ -0,0 +1,59 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CB_BRLDEFS
+#define BRLTTY_INCLUDED_CB_BRLDEFS
+
+#define CB_SERIAL_BAUD 38400 /* baud rate for Braille display */
+
+typedef enum {
+  CB_PKT_KeepAlive = 0,
+  CB_PKT_DeviceIdentity = '?',
+  CB_PKT_WriteCells = 'B',
+  CB_PKT_RoutingKey = 'C',
+  CB_PKT_NavigationKeys = 'K',
+} CB_PacketType;
+
+typedef enum {
+  CB_KEY_Dot6 = 0,
+  CB_KEY_Dot5,
+  CB_KEY_Dot4,
+  CB_KEY_Dot1,
+  CB_KEY_Dot2,
+  CB_KEY_Dot3,
+
+  CB_KEY_Thumb1 = 8,
+  CB_KEY_Thumb2,
+  CB_KEY_Thumb3,
+  CB_KEY_Thumb4,
+  CB_KEY_Thumb5,
+
+  CB_KEY_Status1 = 16,
+  CB_KEY_Status2,
+  CB_KEY_Status3,
+  CB_KEY_Status4,
+  CB_KEY_Status5,
+  CB_KEY_Status6,
+} CB_NavigationKey;
+
+typedef enum {
+  CB_GRP_NavigationKeys = 0,
+  CB_GRP_RoutingKeys,
+} CB_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_CB_BRLDEFS */ 
diff --git a/Drivers/Braille/DotPad/Makefile.in b/Drivers/Braille/DotPad/Makefile.in
new file mode 100644
index 0000000..2f219e2
--- /dev/null
+++ b/Drivers/Braille/DotPad/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = dp
+DRIVER_NAME = DotPad
+DRIVER_USAGE = 
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/DotPad/braille.c b/Drivers/Braille/DotPad/braille.c
new file mode 100644
index 0000000..8df83e2
--- /dev/null
+++ b/Drivers/Braille/DotPad/braille.c
@@ -0,0 +1,1179 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "bitfield.h"
+#include "parse.h"
+
+#define BRL_STATUS_FIELDS sfTime, sfSpace, sfCursorAndWindowColumn3, sfSpace, sfCursorAndWindowRow2, sfSpace, sfScreenNumber, sfSpace, sfStateLetter
+#define BRL_HAVE_STATUS_CELLS
+
+typedef enum {
+  PARM_DISPLAY,
+} DP_DriverParameter;
+
+#define BRLPARMS "display"
+#include "brl_driver.h"
+#include "brldefs-dp.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+
+#define GRAPHIC_HORIZONTAL_SPACING 1
+#define GRAPHIC_VERTICAL_SPACING 2
+
+#define KEY_ENTRY(s,t,k,n) {.value = {.group=DP_GRP_##s, .number=DP_##t##_##k}, .name=n}
+#define SCROLL_KEY_ENTRY(k,n) KEY_ENTRY(ScrollKeys, SCL, k, n)
+#define KEYBOARD_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, KBD, k, n)
+#define PANNING_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, PAN, k, n)
+#define NAVIGATION_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, NAV, k, n)
+
+BEGIN_KEY_NAME_TABLE(scroll)
+  SCROLL_KEY_ENTRY(LEFT_PREV, "LeftPrev"),
+  SCROLL_KEY_ENTRY(LEFT_NEXT, "LeftNext"),
+  SCROLL_KEY_ENTRY(RIGHT_PREV, "RightPrev"),
+  SCROLL_KEY_ENTRY(RIGHT_NEXT, "RightNext"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  KEYBOARD_KEY_ENTRY(DOT1, "Dot1"),
+  KEYBOARD_KEY_ENTRY(DOT2, "Dot2"),
+  KEYBOARD_KEY_ENTRY(DOT3, "Dot3"),
+  KEYBOARD_KEY_ENTRY(DOT4, "Dot4"),
+  KEYBOARD_KEY_ENTRY(DOT5, "Dot5"),
+  KEYBOARD_KEY_ENTRY(DOT6, "Dot6"),
+  KEYBOARD_KEY_ENTRY(DOT7, "Dot7"),
+  KEYBOARD_KEY_ENTRY(DOT8, "Dot8"),
+
+  KEYBOARD_KEY_ENTRY(SPACE, "Space"),
+  KEYBOARD_KEY_ENTRY(SHIFT_LEFT, "LeftShift"),
+  KEYBOARD_KEY_ENTRY(SHIFT_RIGHT, "RightShift"),
+  KEYBOARD_KEY_ENTRY(CONTROL_LEFT, "LeftControl"),
+  KEYBOARD_KEY_ENTRY(CONTROL_RIGHT, "RightControl"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(panning)
+  PANNING_KEY_ENTRY(LEFT, "PanLeft"),
+  PANNING_KEY_ENTRY(RIGHT, "PanRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  NAVIGATION_KEY_ENTRY(CENTER, "NavCenter"),
+  NAVIGATION_KEY_ENTRY(LEFT, "NavLeft"),
+  NAVIGATION_KEY_ENTRY(RIGHT, "NavRight"),
+  NAVIGATION_KEY_ENTRY(UP, "NavUp"),
+  NAVIGATION_KEY_ENTRY(DOWN, "NavDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(function)
+  KEY_GROUP_ENTRY(DP_GRP_FunctionKeys, "FunctionKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(DP_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(scroll),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(panning),
+  KEY_NAME_TABLE(navigation),
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(function),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(panfn4)
+  KEY_NAME_TABLE(panning),
+  KEY_NAME_TABLE(function),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+DEFINE_KEY_TABLE(panfn4)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+  &KEY_TABLE_DEFINITION(panfn4),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  unsigned char *cells;
+  unsigned char destination;
+} ExternalRowEntry;
+
+typedef struct {
+  unsigned char *cells;
+
+  const ExternalRowEntry *upperRow;
+  const ExternalRowEntry *lowerRow;
+
+  unsigned char upperShift;
+  unsigned char lowerShift;
+
+  unsigned char upperMask;
+  unsigned char lowerMask;
+
+  unsigned char hasChanged;
+} InternalRowEntry;
+
+struct BrailleDataStruct {
+  DP_BoardInformation boardInformation;
+  unsigned char firmwareVersion[8];
+  unsigned char deviceName[10];
+  const KeyNameEntry *keyNameTable[7];
+
+  struct {
+    unsigned char scroll[4];
+    unsigned char perkins[4];
+    unsigned char routing[8];
+    unsigned char function[4];
+  } keys;
+
+  struct {
+    unsigned char destination;
+    unsigned char refreshTime;
+
+    unsigned char horizontalSpacing;
+    unsigned char verticalSpacing;
+
+    unsigned char cellWidth;
+    unsigned char cellHeight;
+
+    unsigned char externalColumns;
+    unsigned char externalRows;
+
+    unsigned char internalColumns;
+    unsigned char internalRows;
+  } display;
+
+  struct {
+    unsigned char *externalCells;
+    ExternalRowEntry *externalRows;
+
+    unsigned char *internalCells;
+    InternalRowEntry *internalRows;
+
+    unsigned char *statusCells;
+  } arrays;;
+};
+
+static void
+setExternalDisplayProperties (BrailleDisplay *brl, const DP_DisplayDescriptor *display) {
+  {
+    unsigned char dotsPerCell = brl->data->boardInformation.dotsPerCell;
+
+    unsigned char *width = &brl->data->display.cellWidth;
+    unsigned char *height = &brl->data->display.cellHeight;
+
+    switch (dotsPerCell) {
+      default:
+        logMessage(LOG_WARNING, "unexpected dots per cell: %u", dotsPerCell);
+        /* fall through */
+      case DP_DPC_8:
+        *width = 2;
+        *height = 4;
+        break;
+
+      case DP_DPC_6:
+        *width = 2;
+        *height = 3;
+        break;
+    }
+  }
+
+  brl->data->display.refreshTime = display->refreshTime;
+  brl->data->display.externalColumns = display->columnCount;
+  brl->data->display.externalRows = display->rowCount;
+}
+
+static unsigned char
+toInternalDimension (unsigned char externalCount, unsigned char externalDots, unsigned char internalDots, unsigned char internalSpacing) {
+  return (((externalCount * externalDots) - internalDots) / (internalDots + internalSpacing)) + 1;
+}
+
+static void
+setInternalDisplayProperties (BrailleDisplay *brl) {
+  brl->data->display.internalColumns = toInternalDimension(
+    brl->data->display.externalColumns,
+    brl->data->display.cellWidth,
+    2, brl->data->display.horizontalSpacing
+  );
+
+  brl->data->display.internalRows = toInternalDimension(
+    brl->data->display.externalRows,
+    brl->data->display.cellHeight,
+    4, brl->data->display.verticalSpacing
+  );
+
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "display properties: ghsp:%u gvsp:%u cell:%ux%u disp:%ux%u core:%ux%u",
+    brl->data->display.horizontalSpacing, brl->data->display.verticalSpacing,
+    brl->data->display.cellWidth, brl->data->display.cellHeight,
+    brl->data->display.externalColumns, brl->data->display.externalRows,
+    brl->data->display.internalColumns, brl->data->display.internalRows
+  );
+
+  brl->textColumns = brl->data->display.internalColumns;
+  brl->textRows = brl->data->display.internalRows;
+}
+
+static void
+useTextDisplay (BrailleDisplay *brl) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "using text display");
+
+  brl->data->display.destination = 0;
+  brl->data->display.horizontalSpacing = 0;
+  brl->data->display.verticalSpacing = 0;
+
+  setExternalDisplayProperties(brl, &brl->data->boardInformation.text);
+  setInternalDisplayProperties(brl);
+
+  brl->cellSize = brl->data->display.cellWidth * brl->data->display.cellHeight;
+}
+
+static void
+useGraphicDisplay (BrailleDisplay *brl) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "using graphic display");
+
+  if (brl->data->boardInformation.features & DP_HAS_TEXT_DISPLAY) {
+    brl->data->display.destination = brl->data->boardInformation.text.rowCount;
+  } else {
+    brl->data->display.destination = 1;
+  }
+
+  brl->data->display.horizontalSpacing = GRAPHIC_HORIZONTAL_SPACING;
+  brl->data->display.verticalSpacing = GRAPHIC_VERTICAL_SPACING;
+
+  setExternalDisplayProperties(brl, &brl->data->boardInformation.graphic);
+  setInternalDisplayProperties(brl);
+
+  if (brl->data->boardInformation.features & DP_HAS_TEXT_DISPLAY) {
+    brl->statusColumns = brl->data->boardInformation.text.columnCount;
+    brl->statusRows = 1;
+  }
+}
+
+static int
+selectDisplay (BrailleDisplay *brl, const char *parameter) {
+  typedef struct {
+    const char *name; // must be first
+    void (*useDisplay) (BrailleDisplay *brl);
+    unsigned char featureBit;
+  } ChoiceEntry;
+
+  static const ChoiceEntry choiceTable[] = {
+    { .name = "default" },
+
+    { .name = "text",
+      .useDisplay = useTextDisplay,
+      .featureBit = DP_HAS_TEXT_DISPLAY,
+    },
+
+    { .name = "graphic",
+      .useDisplay = useGraphicDisplay,
+      .featureBit = DP_HAS_GRAPHIC_DISPLAY,
+    },
+
+    { .name = NULL }
+  };
+
+  unsigned char features = brl->data->boardInformation.features;
+  unsigned int choiceIndex;
+
+  if (validateChoiceEx(&choiceIndex, parameter, choiceTable, sizeof(choiceTable[0]))) {
+    const ChoiceEntry *choice = &choiceTable[choiceIndex];
+
+    if (features & choice->featureBit) {
+      choice->useDisplay(brl);
+      return 1;
+    }
+
+    if (choice->featureBit) {
+      logMessage(LOG_WARNING, "no %s display", choice->name);
+    }
+  } else {
+    logMessage(LOG_WARNING, "invalid display setting: %s", parameter);
+  }
+
+  if (features & DP_HAS_GRAPHIC_DISPLAY) {
+    useGraphicDisplay(brl);
+  } else if (features & DP_HAS_TEXT_DISPLAY) {
+    useTextDisplay(brl);
+  } else {
+    logMessage(LOG_WARNING, "no supported display");
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+processParameters (BrailleDisplay *brl, char **parameters) {
+  if (!selectDisplay(brl, parameters[PARM_DISPLAY])) return 0;
+  return 1;
+}
+
+static ExternalRowEntry *
+getExternalRow (BrailleDisplay *brl, unsigned int index) {
+  return &brl->data->arrays.externalRows[index];
+}
+
+static void
+initializeExternalRows (BrailleDisplay *brl) {
+  unsigned char *cells = brl->data->arrays.externalCells;
+  unsigned char destination = brl->data->display.destination;
+
+  for (unsigned int index=0; index<brl->data->display.externalRows; index+=1) {
+    ExternalRowEntry *row = getExternalRow(brl, index);
+
+    row->cells = cells;
+    cells += brl->data->display.externalColumns;
+
+    row->destination = destination;
+    destination += 1;
+  }
+}
+
+static InternalRowEntry *
+getInternalRow (BrailleDisplay *brl, unsigned int index) {
+  return &brl->data->arrays.internalRows[index];
+}
+
+static void
+initializeInternalRows (BrailleDisplay *brl) {
+  unsigned char *cells = brl->data->arrays.internalCells + brl->data->display.verticalSpacing;
+
+  const unsigned char cellHeight = brl->data->display.cellHeight;
+  const unsigned char rowHeight = cellHeight + brl->data->display.verticalSpacing;
+  const unsigned char cellMask = (1 << cellHeight) - 1;
+
+  for (unsigned int index=0; index<brl->data->display.internalRows; index+=1) {
+    InternalRowEntry *row = getInternalRow(brl, index);
+
+    row->cells = cells;
+    cells += brl->data->display.internalColumns;
+
+    {
+      unsigned char offset = rowHeight * index;
+      row->upperRow = getExternalRow(brl, (offset / cellHeight));
+      row->upperShift = offset % cellHeight;
+      row->upperMask = (cellMask << row->upperShift) & cellMask;
+      row->upperMask |= row->upperMask << 4;
+
+      offset += 3;
+      row->lowerRow = getExternalRow(brl, (offset / cellHeight));
+      row->lowerShift = cellHeight - (offset % cellHeight) - 1;
+      row->lowerMask = cellMask >> row->lowerShift;
+      row->lowerMask |= row->lowerMask << 4;
+    }
+
+    row->hasChanged = 1;
+  }
+}
+
+static int
+makeArrays (BrailleDisplay *brl) {
+  if ((brl->data->arrays.externalCells = calloc(brl->data->display.externalRows, brl->data->display.externalColumns))) {
+    if ((brl->data->arrays.internalCells = calloc(brl->data->display.internalRows, brl->data->display.internalColumns))) {
+      if ((brl->data->arrays.externalRows = malloc(ARRAY_SIZE(brl->data->arrays.externalRows, brl->data->display.externalRows)))) {
+        if ((brl->data->arrays.internalRows = malloc(ARRAY_SIZE(brl->data->arrays.internalRows, brl->data->display.internalRows)))) {
+          int statusCellsAllocated = !brl->statusColumns;
+
+          if (!statusCellsAllocated) {
+            if ((brl->data->arrays.statusCells = calloc(brl->statusColumns, 1))) {
+              statusCellsAllocated = 1;
+            }
+          }
+
+          if (statusCellsAllocated) {
+            initializeExternalRows(brl);
+            initializeInternalRows(brl);
+            return 1;
+          }
+
+          free(brl->data->arrays.internalRows);
+        }
+
+        free(brl->data->arrays.externalRows);
+      }
+
+      free(brl->data->arrays.internalCells);
+    }
+
+    free(brl->data->arrays.externalCells);
+  }
+
+  logMallocError();
+  return 0;
+}
+
+static void
+deallocateArrays (BrailleDisplay *brl) {
+  free(brl->data->arrays.statusCells);
+
+  free(brl->data->arrays.internalRows);
+  free(brl->data->arrays.internalCells);
+
+  free(brl->data->arrays.externalRows);
+  free(brl->data->arrays.externalCells);
+}
+
+static uint16_t
+getUint16 (const unsigned char bytes[2]) {
+  union {
+    const unsigned char *ofBytes;
+    const uint16_t *ofUint16;
+  } address;
+
+  address.ofBytes = bytes;
+  return getBigEndian16(*address.ofUint16);
+}
+
+static void
+putUint16 (unsigned char bytes[2], uint16_t value) {
+  union {
+    unsigned char *ofBytes;
+    uint16_t *ofUint16;
+  } address;
+
+  address.ofBytes = bytes;
+  putBigEndian16(address.ofUint16, value);
+}
+
+static unsigned char
+makePacketChecksum (const DP_Packet *packet) {
+  unsigned char checksum = 0XA5;
+
+  {
+    const unsigned char *byte = &packet->fields.destination;
+    const unsigned char *end = byte + getUint16(packet->fields.length) - 1;
+    while (byte < end) checksum ^= *byte++;
+  }
+
+  return checksum;
+}
+
+static int
+writePacket (BrailleDisplay *brl, const DP_Packet *packet) {
+  size_t size = getUint16(packet->fields.length);
+  size += &packet->fields.destination - packet->bytes;
+
+  unsigned int type = getUint16(packet->fields.command) << 8;
+  type |= packet->fields.destination;
+
+  return writeBrailleMessage(brl, NULL, type, packet, size);
+}
+
+static int
+writeRequest (BrailleDisplay *brl, uint16_t command, uint8_t destination, const void *data, size_t size) {
+  if (!data) size = 0;
+  DP_Packet packet;
+
+  packet.fields.sync[0] = DP_PSB_SYNC1;
+  packet.fields.sync[1] = DP_PSB_SYNC2;
+
+  packet.fields.destination = destination;
+  putUint16(packet.fields.command, command);
+  packet.fields.seq = 0;
+
+  uint8_t *checksum = mempcpy(packet.fields.data, data, size);
+  uint16_t length = (checksum - &packet.fields.destination) + 1;
+  putUint16(packet.fields.length, length);
+  *checksum = makePacketChecksum(&packet);
+
+  return writePacket(brl, &packet);
+}
+
+static int
+verifyPacketChecksum (const DP_Packet *packet, unsigned char received) {
+  unsigned char expected = makePacketChecksum(packet);
+  if (received == expected) return 1;
+
+  logMessage(LOG_WARNING,
+    "checksum mismatch: Received:%02X Expected:%02X",
+    received, expected
+  );
+
+  return 0;
+}
+
+static int
+verifyPacketLength (const DP_Packet *packet, const BrailleDisplay *brl) {
+  uint16_t received = getUint16(packet->fields.length);
+  uint16_t command = getUint16(packet->fields.command);
+  uint16_t expected = 5;
+
+  switch (command) {
+    case DP_RSP_FIRMWARE_VERSION:
+      expected += sizeof(brl->data->firmwareVersion);
+      break;
+
+    case DP_RSP_DEVICE_NAME:
+      expected += sizeof(brl->data->deviceName);
+      break;
+
+    case DP_RSP_BOARD_INFORMATION:
+      expected += sizeof(brl->data->boardInformation);
+      break;
+
+    case DP_RSP_DISPLAY_LINE:
+      expected += 1;
+      break;
+
+    case DP_NTF_DISPLAY_LINE:
+      expected += 1;
+      break;
+
+    case DP_NTF_KEYS_SCROLL:
+      expected += sizeof(brl->data->keys.scroll);
+      break;
+
+    case DP_NTF_KEYS_PERKINS:
+      expected += sizeof(brl->data->keys.perkins);
+      break;
+
+    case DP_NTF_KEYS_ROUTING:
+      expected += sizeof(brl->data->keys.routing);
+      break;
+
+    case DP_NTF_KEYS_FUNCTION:
+      expected += sizeof(brl->data->keys.function);
+      break;
+
+    case DP_NTF_ERROR:
+      expected += 1;
+      break;
+  }
+
+  if (received != expected) {
+    logMessage(LOG_WARNING,
+      "length mismatch (command %04X): Received:%u Expected:%u",
+      getUint16(packet->fields.command), received, expected
+    );
+  }
+
+  return 1;
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      if (byte != DP_PSB_SYNC1) return BRL_PVR_INVALID;
+      *length = 4;
+      break;
+
+    case 2:
+      if (byte != DP_PSB_SYNC2) return BRL_PVR_INVALID;
+      break;
+
+    case 4:
+      *length += getUint16(&bytes[2]);
+      break;
+
+    default:
+      break;
+  }
+
+  if (size == *length) {
+    const void *packet = bytes;
+    verifyPacketChecksum(packet, byte);
+    verifyPacketLength(packet, brl);
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static int
+writeCells (BrailleDisplay *brl, unsigned char destination, const unsigned char *cells, unsigned int count) {
+  unsigned char data[1 + count];
+  unsigned char *byte = data;
+
+  *byte++ = 0;
+  byte = mempcpy(byte, cells, count);
+
+  return writeRequest(brl, DP_REQ_DISPLAY_LINE, destination, data, (byte - data));
+}
+
+static int
+writeStatusCells (BrailleDisplay *brl) {
+  return writeCells(brl, 0, brl->data->arrays.statusCells, brl->statusColumns);
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  translateOutputCells(brl->data->arrays.statusCells, cells, brl->statusColumns);
+  return writeStatusCells(brl);
+}
+
+static int
+writeExternalRow (BrailleDisplay *brl, const ExternalRowEntry *row) {
+  return writeCells(brl, row->destination, row->cells, brl->data->display.externalColumns);
+}
+
+static int
+refreshCells (BrailleDisplay *brl) {
+  const ExternalRowEntry *row = brl->data->arrays.externalRows;
+  const ExternalRowEntry *end = row + brl->data->display.externalRows;
+
+  while (row < end) {
+    if (!writeExternalRow(brl, row)) return 0;
+    row += 1;
+  }
+
+  if (!brl->statusColumns) return 1;
+  return writeStatusCells(brl);
+}
+
+static unsigned int
+getExternalCellOffset (BrailleDisplay *brl, unsigned int index) {
+  return index * (brl->data->display.cellWidth + brl->data->display.horizontalSpacing);
+}
+
+static unsigned char
+getExternalCell (BrailleDisplay *brl, const ExternalRowEntry *row, unsigned int index) {
+  unsigned int offset = getExternalCellOffset(brl, index);
+  index = offset / 2;
+  unsigned char cell = row->cells[index];
+
+  if (offset % 2) {
+    cell >>= 4;
+    cell |= row->cells[index + 1] << 4;
+  }
+
+  return cell;
+}
+
+static void
+putExternalCell (BrailleDisplay *brl, const ExternalRowEntry *row, unsigned int index, unsigned char cell) {
+  unsigned int offset = getExternalCellOffset(brl, index);
+  index = offset / 2;
+
+  if (offset % 2) {
+    unsigned char *dots = &row->cells[index];
+    *dots &= 0X0F;
+    *dots |= cell << 4;
+
+    dots += 1;
+    *dots &= 0XF0;
+    *dots |= cell >> 4;
+  } else {
+    row->cells[index] = cell;
+  }
+}
+
+static int
+writeInternalCells (BrailleDisplay *brl, const InternalRowEntry *internalRow, unsigned int from, unsigned int to) {
+  int upperUpdated = 0;
+  int lowerUpdated = 0;
+
+  while (from < to) {
+    unsigned char newCell = translateOutputCell(internalRow->cells[from]);
+
+    {
+      const ExternalRowEntry *upperRow = internalRow->upperRow;
+      unsigned char upperCell = getExternalCell(brl, upperRow, from);
+      unsigned char changedDots = (upperCell ^ (newCell << internalRow->upperShift)) & internalRow->upperMask;
+
+      if (changedDots) {
+        putExternalCell(brl, upperRow, from, (upperCell ^ changedDots));
+        upperUpdated = 1;
+      }
+    }
+
+    if (internalRow->lowerRow != internalRow->upperRow) {
+      const ExternalRowEntry *lowerRow = internalRow->lowerRow;
+      unsigned char lowerCell = getExternalCell(brl, lowerRow, from);
+      unsigned char changedDots = (lowerCell ^ (newCell >> internalRow->lowerShift)) & internalRow->lowerMask;
+
+      if (changedDots) {
+        putExternalCell(brl, lowerRow, from, (lowerCell ^ changedDots));
+        lowerUpdated = 1;
+      }
+    }
+
+    from += 1;
+  }
+
+  if (upperUpdated) {
+    if (!writeExternalRow(brl, internalRow->upperRow)) {
+      return 0;
+    }
+  }
+
+  if (lowerUpdated) {
+    if (!writeExternalRow(brl, internalRow->lowerRow)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned char *cells = brl->buffer;
+  unsigned char rowLength = brl->data->display.internalColumns;
+
+  for (unsigned int rowIndex=0; rowIndex<brl->data->display.internalRows; rowIndex+=1) {
+    InternalRowEntry *row = getInternalRow(brl, rowIndex);
+
+    unsigned int from;
+    unsigned int to;
+
+    int rowHasChanged = cellsHaveChanged(
+      row->cells, cells, rowLength,
+      &from, &to, &row->hasChanged
+    );
+
+    if (rowHasChanged) {
+      if (!writeInternalCells(brl, row, from, to)) {
+        return 0;
+      }
+    }
+
+    cells += rowLength;
+  }
+
+  return 1;
+}
+
+static int
+getDataSize (const DP_Packet *packet) {
+  return getUint16(packet->fields.length)
+       - 1 // checksum
+       - (packet->fields.data - &packet->fields.destination) // header
+       ;
+}
+
+static void
+reportRequestError (unsigned char code) {
+  static const char *const reasons[] = {
+    [DP_ERR_LENGTH]    = "unexpected length",
+    [DP_ERR_COMMAND]   = "unrecognized command",
+    [DP_ERR_CHECKSUM]  = "incorrect checksum",
+    [DP_ERR_PARAMETER] = "invalid parameter",
+    [DP_ERR_TIMEOUT]   = "read timed out",
+  };
+
+  const char *reason = NULL;
+  if (code < ARRAY_COUNT(reasons)) reason = reasons[code];
+  if (!reason) reason = "unknown problem";
+
+  logMessage(LOG_WARNING,
+    "request rejected by device: %u (%s)",
+    code, reason
+  );
+}
+
+static void
+reportDisplayError (unsigned char code) {
+  static const char *const reasons[] = {
+    [DP_DRC_ACK]      = "positive acknowledgement",
+    [DP_DRC_NACK]     = "negative acknowledgement",
+    [DP_DRC_WAIT]     = "wait",
+    [DP_DRC_CHECKSUM] = "incorrect checksum",
+  };
+
+  const char *reason = NULL;
+  if (code < ARRAY_COUNT(reasons)) reason = reasons[code];
+  if (!reason) reason = "unknown problem";
+
+  logMessage(LOG_WARNING,
+    "display rejected by device: %u (%s)",
+    code, reason
+  );
+}
+
+static void
+saveField (
+  const DP_Packet *packet, const char *label,
+  unsigned char *field, int fieldSize
+) {
+  int dataSize = getDataSize(packet);
+
+  if (dataSize > fieldSize) dataSize = fieldSize;
+  memcpy(field, packet->fields.data, dataSize);
+  while (dataSize < fieldSize) field[dataSize++] = ' ';
+
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "%s: %.*s", label, fieldSize, field
+  );
+}
+
+static unsigned char
+reverseByteBits (unsigned char fromByte) {
+  unsigned char toByte = 0;
+
+  unsigned char fromBit = 0X80;
+  unsigned char toBit = 0X01;
+
+  while (fromBit) {
+    if (fromByte & fromBit) toByte |= toBit;
+    fromBit >>= 1;
+    toBit <<= 1;
+  }
+
+  return toByte;
+}
+
+static int
+updateKeyGroup (
+  BrailleDisplay *brl, const DP_Packet *packet, KeyGroup keyGroup,
+  unsigned char *array, size_t arraySize
+) {
+  int dataSize = getDataSize(packet);
+
+  if (dataSize > 0) {
+    unsigned char data[arraySize];
+    if (dataSize > arraySize) dataSize = arraySize;
+
+    for (int i=0; i<dataSize; i+=1) {
+      data[i] = reverseByteBits(packet->fields.data[i]);
+    }
+
+    while (dataSize < arraySize) {
+      data[dataSize++] = 0;
+    }
+
+    if (!enqueueUpdatedKeyGroup(brl, (arraySize * 8), data, array, keyGroup)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  DP_Packet packet;
+  size_t size;
+
+  while ((size = readPacket(brl, packet.bytes, sizeof(packet)))) {
+    switch (getUint16(packet.fields.command)) {
+      case DP_RSP_FIRMWARE_VERSION: {
+        saveField(
+          &packet, "Firmware Version",
+          brl->data->firmwareVersion,
+          sizeof(brl->data->firmwareVersion)
+        );
+
+        acknowledgeBrailleMessage(brl);
+        continue;
+      }
+
+      case DP_RSP_DEVICE_NAME: {
+        saveField(
+          &packet, "Device Name",
+          brl->data->deviceName,
+          sizeof(brl->data->deviceName)
+        );
+
+        acknowledgeBrailleMessage(brl);
+        continue;
+      }
+
+      case DP_RSP_DISPLAY_LINE: {
+        unsigned char code = packet.fields.data[0];
+
+        if (code != DP_DRC_ACK) {
+          reportDisplayError(code);
+          acknowledgeBrailleMessage(brl);
+        }
+
+        continue;
+      }
+
+      case DP_NTF_DISPLAY_LINE:
+        acknowledgeBrailleMessage(brl);
+        continue;
+
+      case DP_NTF_KEYS_SCROLL: {
+        updateKeyGroup(
+          brl, &packet, DP_GRP_ScrollKeys,
+          brl->data->keys.scroll,
+          sizeof(brl->data->keys.scroll)
+        );
+
+        continue;
+      }
+
+      case DP_NTF_KEYS_PERKINS: {
+        updateKeyGroup(
+          brl, &packet, DP_GRP_PerkinsKeys,
+          brl->data->keys.perkins,
+          sizeof(brl->data->keys.perkins)
+        );
+
+        continue;
+      }
+
+      case DP_NTF_KEYS_ROUTING: {
+        updateKeyGroup(
+          brl, &packet, DP_GRP_RoutingKeys,
+          brl->data->keys.routing,
+          sizeof(brl->data->keys.routing)
+        );
+
+        continue;
+      }
+
+      case DP_NTF_KEYS_FUNCTION: {
+        updateKeyGroup(
+          brl, &packet, DP_GRP_FunctionKeys,
+          brl->data->keys.function,
+          sizeof(brl->data->keys.function)
+        );
+
+        continue;
+      }
+
+      case DP_NTF_ERROR: {
+        reportRequestError(packet.fields.data[0]);
+        acknowledgeBrailleMessage(brl);
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet.bytes, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
+
+static const KeyNameEntry **
+makeKeyNameTable (BrailleDisplay *brl) {
+  typedef struct {
+    const char *type;
+    const KeyNameEntry *keyNames;
+    unsigned char featureBit;
+  } OptionalKeysDescriptor;
+
+  static const OptionalKeysDescriptor optionalKeysTable[] = {
+    { .type = "scroll",
+      .keyNames = KEY_NAME_TABLE(scroll),
+      // not actually used
+    },
+
+    { .type = "keyboard",
+      .keyNames = KEY_NAME_TABLE(keyboard),
+      .featureBit = DP_HAS_PERKINS_KEYS,
+    },
+
+    { .type = "panning",
+      .keyNames = KEY_NAME_TABLE(panning),
+      .featureBit = DP_HAS_PANNING_KEYS,
+    },
+
+    { .type = "navigation",
+      .keyNames = KEY_NAME_TABLE(navigation),
+      .featureBit = DP_HAS_NAVIGATION_KEYS,
+    },
+
+    { .type = "routing",
+      .keyNames = KEY_NAME_TABLE(routing),
+      .featureBit = DP_HAS_ROUTING_KEYS,
+    },
+
+    { .type = "function",
+      .keyNames = KEY_NAME_TABLE(function),
+      .featureBit = DP_HAS_FUNCTION_KEYS,
+    },
+  };
+
+  const KeyNameEntry **names = brl->data->keyNameTable;
+  const OptionalKeysDescriptor *okd = optionalKeysTable;
+  const OptionalKeysDescriptor *end = okd + ARRAY_COUNT(optionalKeysTable);
+
+  while (okd < end) {
+    if (brl->data->boardInformation.features & okd->featureBit) {
+      char log[0X40];
+      STR_BEGIN(log, sizeof(log));
+      STR_PRINTF("has");
+
+      if (okd->featureBit == DP_HAS_FUNCTION_KEYS) {
+        STR_PRINTF(" %u", brl->data->boardInformation.functionKeyCount);
+      }
+
+      STR_PRINTF(" %s keys", okd->type);
+      STR_END;
+      logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "%s", log);
+
+      *names++ = okd->keyNames;
+    }
+
+    okd += 1;
+  }
+
+  *names = LAST_KEY_NAME_TABLE;
+  return brl->data->keyNameTable;
+}
+
+static void
+setKeyTable (BrailleDisplay *brl) {
+  const KeyTableDefinition *ktd = &KEY_TABLE_DEFINITION(all);
+  brl->keyBindings = ktd->bindings;
+  brl->keyNames = makeKeyNameTable(brl);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 115200,
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0403, .product=0X6010,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial=&serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+//descriptor.usb.options.readyDelay = 3000;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  return writeRequest(brl, DP_REQ_BOARD_INFORMATION, 0, NULL, 0);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const DP_Packet *response = packet;
+
+  if (getUint16(response->fields.command) != DP_RSP_BOARD_INFORMATION) {
+    return BRL_RSP_UNEXPECTED;
+  }
+
+  memcpy(
+    &brl->data->boardInformation, response->fields.data,
+    sizeof(brl->data->boardInformation)
+  );
+
+  {
+    DP_BoardInformation *info = &brl->data->boardInformation;
+
+    if (info->features & DP_HAS_FUNCTION_KEYS) {
+      if (!info->functionKeyCount) {
+        info->functionKeyCount = 4;
+      }
+    }
+  }
+
+  logBytes(LOG_CATEGORY(BRAILLE_DRIVER),
+    "Board Information",
+    &brl->data->boardInformation,
+    sizeof(brl->data->boardInformation)
+  );
+
+  acknowledgeBrailleMessage(brl);
+  return BRL_RSP_DONE;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      DP_Packet response;
+
+      int probed = probeBrailleDisplay(
+        brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+        writeIdentifyRequest, readPacket,
+        &response, sizeof(response),
+        isIdentityResponse
+      );
+
+      if (probed) {
+        if (processParameters(brl, parameters)) {
+          if (makeArrays(brl)) {
+            brl->acknowledgements.missing.timeout = (brl->data->display.refreshTime * 100) + 1000;
+
+            if (writeRequest(brl, DP_REQ_FIRMWARE_VERSION, 0, NULL, 0)) {
+              if (writeRequest(brl, DP_REQ_DEVICE_NAME, 0, NULL, 0)) {
+                setKeyTable(brl);
+
+                MAKE_OUTPUT_TABLE(
+                  DP_DSP_DOT1, DP_DSP_DOT2, DP_DSP_DOT3, DP_DSP_DOT4,
+                  DP_DSP_DOT5, DP_DSP_DOT6, DP_DSP_DOT7, DP_DSP_DOT8
+                );
+
+                brl->refreshBrailleDisplay = refreshCells;
+                return 1;
+              }
+            }
+
+            deallocateArrays(brl);
+          }
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  endBrailleMessages(brl);
+  disconnectBrailleResource(brl, NULL);
+
+  deallocateArrays(brl);
+  free(brl->data);
+}
diff --git a/Drivers/Braille/DotPad/brldefs-dp.h b/Drivers/Braille/DotPad/brldefs-dp.h
new file mode 100644
index 0000000..ac0e935
--- /dev/null
+++ b/Drivers/Braille/DotPad/brldefs-dp.h
@@ -0,0 +1,172 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DP_BRLDEFS
+#define BRLTTY_INCLUDED_DP_BRLDEFS
+
+#define DP_MAXIMUM_TEXT_COLUMNS 80
+
+typedef enum {
+  DP_REQ_FIRMWARE_VERSION = 0X0000,
+  DP_RSP_FIRMWARE_VERSION = 0X0001,
+
+  DP_REQ_DEVICE_NAME = 0X0100,
+  DP_RSP_DEVICE_NAME = 0X0101,
+
+  DP_REQ_BOARD_INFORMATION = 0X0110,
+  DP_RSP_BOARD_INFORMATION = 0X0111,
+
+  DP_REQ_DISPLAY_LINE = 0X0200,
+  DP_RSP_DISPLAY_LINE = 0X0201,
+  DP_NTF_DISPLAY_LINE = 0X0202,
+
+  DP_REQ_DISPLAY_CURSOR = 0X0210,
+  DP_RSP_DISPLAY_CURSOR = 0X0211,
+  DP_NTF_DISPLAY_CURSOR = 0X0212,
+
+  DP_NTF_KEYS_SCROLL = 0X0302,
+  DP_NTF_KEYS_PERKINS = 0X0312,
+  DP_NTF_KEYS_ROUTING = 0X0322,
+  DP_NTF_KEYS_FUNCTION = 0X0332,
+
+  DP_NTF_ERROR = 0X9902,
+} DP_Command;
+
+typedef enum {
+  DP_HAS_GRAPHIC_DISPLAY = 0X80,
+  DP_HAS_TEXT_DISPLAY = 0X40,
+  DP_HAS_PERKINS_KEYS = 0X20,
+  DP_HAS_ROUTING_KEYS = 0X10,
+  DP_HAS_NAVIGATION_KEYS = 0X08,
+  DP_HAS_PANNING_KEYS = 0X04,
+  DP_HAS_FUNCTION_KEYS = 0X02,
+} DP_Features;
+
+typedef enum {
+  DP_DPC_6 = 0,
+  DP_DPC_8 = 1,
+} DP_DotsPerCell;
+
+typedef struct {
+  unsigned char rowCount;     // 1, 2
+  unsigned char columnCount;  // 12, 14, 15, 16, 20, 24, 26, 28, 30, 32, 36, 40
+  unsigned char dividedLine;
+  unsigned char refreshTime;  // 100ms
+} DP_DisplayDescriptor;
+
+typedef struct {
+  unsigned char features;
+  unsigned char dotsPerCell;          // 0:6, 1:8
+  unsigned char distanceBetweenPins;  // 0.1mm
+  unsigned char functionKeyCount;
+
+  DP_DisplayDescriptor text;
+  DP_DisplayDescriptor graphic;
+} DP_BoardInformation;
+
+typedef enum {
+  DP_DSP_DOT1 = 0X01,
+  DP_DSP_DOT2 = 0X02,
+  DP_DSP_DOT3 = 0X04,
+  DP_DSP_DOT4 = 0X10,
+  DP_DSP_DOT5 = 0X20,
+  DP_DSP_DOT6 = 0X40,
+  DP_DSP_DOT7 = 0X08,
+  DP_DSP_DOT8 = 0X80,
+} DP_DisplayDots;
+
+typedef enum {
+  DP_DRC_ACK = 0,
+  DP_DRC_NACK = 1,
+  DP_DRC_WAIT = 2,
+  DP_DRC_CHECKSUM = 3,
+} DP_DisplayResponseCode;
+
+typedef enum {
+  DP_SCL_LEFT_NEXT = 28,
+  DP_SCL_LEFT_PREV = 29,
+  DP_SCL_RIGHT_NEXT = 30,
+  DP_SCL_RIGHT_PREV = 31,
+} DP_ScrollKey;
+
+typedef enum {
+  DP_KBD_DOT7 = 0,
+  DP_KBD_DOT3 = 1,
+  DP_KBD_DOT2 = 2,
+  DP_KBD_DOT1 = 3,
+  DP_KBD_DOT4 = 4,
+  DP_KBD_DOT5 = 5,
+  DP_KBD_DOT6 = 6,
+  DP_KBD_DOT8 = 7,
+
+  DP_KBD_SPACE = 8,
+  DP_KBD_SHIFT_LEFT = 9,
+  DP_KBD_CONTROL_LEFT = 10,
+  DP_KBD_SHIFT_RIGHT = 11,
+  DP_KBD_CONTROL_RIGHT = 12,
+  DP_PAN_LEFT = 13,
+  DP_PAN_RIGHT = 14,
+
+  DP_NAV_CENTER = 16,
+  DP_NAV_UP = 17,
+  DP_NAV_RIGHT = 18,
+  DP_NAV_DOWN = 19,
+  DP_NAV_LEFT = 20,
+} DP_PerkinsKey;
+
+typedef enum {
+  DP_GRP_ScrollKeys,
+  DP_GRP_PerkinsKeys,
+  DP_GRP_FunctionKeys,
+  DP_GRP_RoutingKeys,
+} DP_KeyGroup;
+
+typedef enum {
+  DP_ERR_LENGTH = 1,
+  DP_ERR_COMMAND = 2,
+  DP_ERR_CHECKSUM = 3,
+  DP_ERR_PARAMETER = 4,
+  DP_ERR_TIMEOUT = 5,
+} DP_ErrorCode;
+
+typedef enum {
+  DP_PSB_SYNC1 = 0XAA,
+  DP_PSB_SYNC2 = 0X55,
+} DP_PacketSyncByte;
+
+typedef enum {
+  DP_SEQ_TEXT = 0X80,
+} DP_PacketSeqFlag;
+
+typedef struct {
+  unsigned char sync[2];
+  unsigned char length[2];  // big endian
+  unsigned char destination;
+  unsigned char command[2];  // big endian
+  unsigned char seq;
+
+  unsigned char data[DP_MAXIMUM_TEXT_COLUMNS + 1];
+  // includes one-byte trailing checksum
+} DP_PacketFields;
+
+typedef union {
+  unsigned char bytes[sizeof(DP_PacketFields)];
+  DP_PacketFields fields;
+} DP_Packet;
+
+#endif /* BRLTTY_INCLUDED_DP_BRLDEFS */
diff --git a/Drivers/Braille/EcoBraille/LEEME b/Drivers/Braille/EcoBraille/LEEME
new file mode 100644
index 0000000..41a7a6f
--- /dev/null
+++ b/Drivers/Braille/EcoBraille/LEEME
@@ -0,0 +1,49 @@
+Fichero leeme del driver para la EcoBraille
+
+Version 1.01 (1 de Marzo de 2000)
+
+
+Licencia
+========
+
+Este software es gratuito.
+Este driver se distrbuye bajo la Licencia Pública General Reducida de GNU.
+Lease el fichero 'LICENSE-LGPL' para mas detalles de la licencia.
+Mandeme sus comentarios o errores encontrados al e-mail ofa@once.es
+puede hacerlo en castellano, catalan o en ingles.
+
+Puede coger la ultima version en ftp://ftp.once.es/pub/accessibility
+
+
+Hardware soportado
+==================
+
+Este driver trabaja con todos los modelos de lineas braille EcoBraille,
+incluido la Eco20, Eco40 y Eco80.
+
+Si necesita informacion de los producto EcoBraille, visite la pagina Web
+http://www.once.es/
+
+
+Configuracion de puertos
+------------------------
+En el fichero 'Makefile' localizado en el directorio BRLTTY, se dispone de 
+una linea en donde se puede definir el puerto serie utilizado. Por ejemplo;
+/dev/ttyS0.  La velocidad de conexion viene definida en el fichero braille.h
+
+Por defecto, la linea EcoBraille se comunica a 19200 baudios.
+
+
+Ejecutar el driver
+==================
+
+Para compilar y ejecutar el programa BRLTTY, lease la documentacion del
+mismo. 
+
+Debe ejecutar el programa BRLTTY con la linea EcoBraille conectada al 
+puerto serie y encendida.
+
+
+Oscar Fernandez
+ofa@once.es
+January,29 1999
diff --git a/Drivers/Braille/EcoBraille/Makefile.in b/Drivers/Braille/EcoBraille/Makefile.in
new file mode 100644
index 0000000..69d7d80
--- /dev/null
+++ b/Drivers/Braille/EcoBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ec
+DRIVER_NAME = EcoBraille
+DRIVER_USAGE = 20/40/80
+DRIVER_VERSION = 1.00
+DRIVER_DEVELOPERS = Oscar Fernandez <ofa@once.es>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/EcoBraille/README b/Drivers/Braille/EcoBraille/README
new file mode 100644
index 0000000..d9e9429
--- /dev/null
+++ b/Drivers/Braille/EcoBraille/README
@@ -0,0 +1,54 @@
+README file for the EcoBraille driver
+
+Version 1.01 (March 1, 2000)
+
+
+Copyright
+=========
+
+This driver is copyrighted under the GNU Lesser General Public License.
+It is free software.  See the file 'LICENSE-LGPL' for more details.
+Feel free to send your comments and to report bugs to me at ofa@once.es
+
+Can you write me in Spanish, catalonian or english.
+
+Can get the latest version in ftp://ftp.once.es/pub/accessibility
+
+
+Supported Hardware
+==================
+
+This driver is supposed to work with any spanish ECO braille display from the 
+ECO xx series, including the ECO 20, the ECO 40 and the ECO 80.
+
+If you want any information about ECO and its products, you can look
+at http://www.once.es/.
+
+Port Settings
+-------------
+In the file 'Makefile' located in the BRLTTY home directory, there is a 
+line where you can define the default (i.e. when not given on the command 
+line) serial port device (such as /dev/ttyS0).  The baudrate setup is 
+however defined in the braille.h file.
+
+By default, the ECO communicates at 19200 Baud. 
+
+
+Running the Driver
+==================
+
+To compile and run BRLTTY, please refer to the documentation about 
+BRLTTY itself.  One note though: when BRLTTY starts, it will display 
+on the screen some copyright stuff, current configuration (model and 
+firmware version setup) and the serial device in use.  That information 
+may be useful if nothing happens with the braille terminal.
+
+You may run BRLTTY even if no ECO is plugged to the appropriate serial
+port.  BRLTTY will wait for it.
+
+You need power on the eco braille line before to use.
+
+
+Oscar Fernandez
+ofa@once.es
+January,29 1999
diff --git a/Drivers/Braille/EcoBraille/braille.c b/Drivers/Braille/EcoBraille/braille.c
new file mode 100644
index 0000000..91f4af3
--- /dev/null
+++ b/Drivers/Braille/EcoBraille/braille.c
@@ -0,0 +1,436 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* EcoBraille/braille.c - Braille display library for ECO Braille series
+ * Copyright (C) 1999 by Oscar Fernandez <ofa@once.es>
+ * See the GNU Lesser General Public license for details in the LICENSE-LGPL file
+ *
+ * For debuging define DEBUG variable
+ */
+
+/* Changes:
+ *      mar 1' 2000:
+ *              - fix correct size of braille lines.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "braille.h"
+#include "io_serial.h"
+
+/* Braille display parameters */
+typedef struct{
+    const char *Name;
+    int Cols;
+    int NbStCells;
+} BRLPARAMS;
+
+static const BRLPARAMS Models[NB_MODEL] ={
+  {
+    /* ID == 0 */
+    "ECO20",
+    20,
+    2
+  }
+  ,
+  {
+    /* ID == 1 */
+    "ECO40",
+    40,
+    4
+  }
+  ,
+  {
+    /* ID == 2 */
+    "ECO80",
+    80,
+    4
+  }
+};
+
+#define BRLROWS		1
+#define MAX_STCELLS	4	/* hiest number of status cells */
+
+/* Global variables */
+static SerialDevice *serialDevice;			/* file descriptor for Braille display */
+static unsigned char *rawdata;		/* translated data to send to Braille */
+static unsigned char Status[MAX_STCELLS]; /* to hold status */
+static const BRLPARAMS *model;		/* points to terminal model config struct */
+static int BrailleSize=0;		/* Braille size of braille line */
+
+#ifdef DEBUG
+int brl_log;
+#endif /* DEBUG */
+
+/* Communication codes */
+static char BRL_ID[] = "\x10\x02\xF1";
+#define DIM_BRL_ID 3
+static unsigned char SYS_READY[] = {0X10, 0X02, 0XF1, 0X57, 0X57, 0X57, 0X10, 0X03};
+#define DIM_SYS_READY sizeof(SYS_READY)
+static char BRL_READY[] UNUSED = "\x10\x02\x2E";
+#define DIM_BRL_READY 3
+static char BRL_WRITE_PREFIX[] = "\x61\x10\x02\xBC";
+#define DIM_BRL_WRITE_PREFIX 4
+static char BRL_WRITE_SUFIX[] = "\x10\x03";
+#define DIM_BRL_WRITE_SUFIX 2
+static char BRL_KEY[] = "\x10\x02\x88";
+#define DIM_BRL_KEY 2
+
+
+/* Status Sensors */
+#define KEY_ST_SENSOR1	0xD5  /* Byte A */
+#define KEY_ST_SENSOR2	0xD6
+#define KEY_ST_SENSOR3	0xD0
+#define KEY_ST_SENSOR4	0xD1
+
+/* Main Sensors */
+#define KEY_MAIN_MIN	0x80
+#define KEY_MAIN_MAX	0xCF
+
+/* Front Keys */
+#define KEY_DOWN	0x01  /* Byte B */
+#define KEY_RIGHT	0x02
+#define KEY_CLICK	0x04
+#define KEY_LEFT	0x08
+#define KEY_UP		0x10
+
+/* Function Keys */
+#define KEY_F9		0x01  /* byte C */
+#define KEY_ALT		0x02
+#define KEY_F0		0x04
+#define KEY_SHIFT	0x40
+
+#define KEY_F1		0x01  /* Byte D */
+#define KEY_F2		0x02
+#define KEY_F3		0x04
+#define KEY_F4		0x08
+#define KEY_F5		0x10
+#define KEY_F6		0x20
+#define KEY_F7		0x40
+#define KEY_F8		0x80
+
+
+static int WriteToBrlDisplay(unsigned char *Data)
+{
+  unsigned int size = DIM_BRL_WRITE_PREFIX + BrailleSize + DIM_BRL_WRITE_SUFIX;
+  unsigned char buffer[size];
+  unsigned char *byte = buffer;
+  
+  byte = mempcpy(byte, BRL_WRITE_PREFIX, DIM_BRL_WRITE_PREFIX);
+  byte = mempcpy(byte, Data, BrailleSize);
+  byte = mempcpy(byte, BRL_WRITE_SUFIX, DIM_BRL_WRITE_SUFIX);
+ 
+  serialWriteData(serialDevice, buffer, byte-buffer);
+  return 0;
+}
+
+static int brl_construct(BrailleDisplay *brl, char **parameters, const char *device)
+{
+  short ModelID = MODEL;
+  unsigned char buffer[DIM_BRL_ID + 6];
+
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  rawdata = NULL;	/* clear pointers */
+
+  /* Open the Braille display device */
+  if (!(serialDevice = serialOpenDevice(device))) goto failure;
+  
+#ifdef DEBUG
+  lockUmask();
+  brl_log = open("/tmp/brllog", O_CREAT | O_WRONLY);
+  unlockUmask();
+
+  if(brl_log < 0){
+    goto failure;
+  }
+#endif /* DEBUG */
+
+  /* autodetecting ECO model */
+  do{
+      /* DTR back on */
+      serialRestartDevice(serialDevice, BAUDRATE);	/* activate new settings */
+      
+      /* The 2 next lines can be commented out to try autodetect once anyway */
+      if(ModelID != ECO_AUTO){
+	break;
+      }
+      	
+      if(serialReadData(serialDevice, &buffer, DIM_BRL_ID + 6, 600, 100) == DIM_BRL_ID + 6){
+	  if(memcmp (buffer, BRL_ID, DIM_BRL_ID) == 0){
+	  
+	    /* Possible values; 0x20, 0x40, 0x80 */
+	    int tmpModel=buffer[DIM_BRL_ID] / 0x20;
+
+	    switch(tmpModel){
+	     case 1: ModelID=0;
+	       break;
+	     case 2: ModelID=1;
+	       break;
+	     case 4: ModelID=2;
+	       break;
+	    default: ModelID=1;
+	    }
+	  }
+      }
+  }while(ModelID == ECO_AUTO);
+  
+  if(ModelID >= NB_MODEL || ModelID < 0){
+    goto failure;		/* unknown model */
+  }
+    
+  /* Need answer to BR */
+  /*do{*/
+      serialWriteData(serialDevice, SYS_READY, DIM_SYS_READY);
+      serialReadData(serialDevice, &buffer, DIM_BRL_READY + 6, 100, 100);
+      /*}while(strncmp (buffer, BRL_READY, DIM_BRL_READY));*/
+      
+      logMessage(LOG_DEBUG, "buffer is: %s",buffer);
+  
+  /* Set model params */
+  model = &Models[ModelID];
+  brl->textColumns = model->Cols;		/* initialise size of main display */
+  brl->textRows = BRLROWS;		/* ever is 1 in this type of braille lines */
+  
+  MAKE_OUTPUT_TABLE(0X10, 0X20, 0X40, 0X01, 0X02, 0X04, 0X80, 0X08);
+
+  /* Need to calculate the size; Cols + Status + 1 (space between) */
+  BrailleSize = brl->textColumns + model->NbStCells + 1;
+
+  /* Allocate space for buffers */
+  rawdata = malloc(BrailleSize); /* Phisical size */
+  if(!rawdata){
+     goto failure;
+  }    
+
+  /* Empty buffers */
+  memset(rawdata, 0, BrailleSize);
+  memset(Status, 0, MAX_STCELLS);
+
+return 1;
+
+failure:;
+  if(rawdata){
+     free(rawdata);
+  }
+       
+return 0;
+}
+
+
+static void brl_destruct(BrailleDisplay *brl)
+{
+  free(rawdata);
+  serialCloseDevice(serialDevice);
+
+#ifdef DEBUG  
+  close(brl_log);
+#endif /* DEBUG */
+}
+
+
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  unsigned char *byte = rawdata;
+  /* This Braille Line need to display all information, include status */
+  
+  /* Make status info to rawdata */
+  byte = translateOutputCells(byte, Status, model->NbStCells);
+
+  /* step a physical space with main cells */
+  *byte++ = 0;
+  
+  /* Make main info to rawdata */
+  byte = translateOutputCells(byte, brl->buffer, brl->textColumns);
+     
+  /* Write to Braille Display */
+  WriteToBrlDisplay(rawdata);
+  return 1;
+}
+
+
+static int brl_writeStatus(BrailleDisplay *brl, const unsigned char *st)
+{
+  /* Update status cells */
+  memcpy(Status, st, model->NbStCells);
+  return 1;
+}
+
+
+static int brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context)
+{
+  int res = EOF;
+  long bytes = 0;
+  unsigned char *pBuff;
+  unsigned char buff[18 + 1];
+  
+#ifdef DEBUG
+  char tmp[80];
+#endif /* DEBUG */
+
+  /* Read info from Braille Line */
+  if((bytes = serialReadData(serialDevice, buff, 18, 0, 0)) >= 9){
+
+#ifdef DEBUG
+     sprintf(tmp, "Type %d, Bytes read: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n",
+        type, buff[0], buff[1], buff[2], buff[3], buff[4], buff[5], buff[6], buff[7], buff[8], buff[9]);
+     write(brl_log, tmp, strlen(tmp)); 
+#endif /* DEBUG */
+  
+     /* Is a Key? */
+     if((pBuff=(unsigned char *)strstr((char *)buff, BRL_KEY))){  
+    
+        /* Byte A. Check Status sensors */
+	switch(*(pBuff+3)){
+	   case KEY_ST_SENSOR1:
+	        res = BRL_CMD_HELP;
+	        break;
+
+	   case KEY_ST_SENSOR2:
+	        res = BRL_CMD_PREFMENU;
+	        break;
+
+	   case KEY_ST_SENSOR3:
+	        res = BRL_CMD_DISPMD;
+	        break;
+
+	   case KEY_ST_SENSOR4:
+	        res = BRL_CMD_INFO;
+	        break;
+        }
+
+	/* Check Main Sensors */
+	if(*(pBuff+3) >= KEY_MAIN_MIN && *(pBuff+3) <= KEY_MAIN_MAX){
+	
+	   /* Nothing */
+	}
+	
+	/* Byte B. Check Front Keys */
+	switch(*(pBuff+4)){
+	   case KEY_DOWN: /* Down */
+	        res = BRL_CMD_LNDN;
+	        break;
+
+	   case KEY_RIGHT: /* Right */
+	        res = BRL_CMD_FWINRT;
+	        break;
+
+	   case KEY_CLICK: /* Eco20 Go to cursor */
+	   
+	        /* Only for ECO20, haven't function keys */
+		if(model->Cols==20){
+	           res = BRL_CMD_HOME;
+		}
+	        break;
+
+	   case KEY_LEFT: /* Left */
+	        res = BRL_CMD_FWINLT;
+	        break;
+
+	   case KEY_UP: /* Up  */
+	        res = BRL_CMD_LNUP;
+	        break;
+
+	   case KEY_UP|KEY_CLICK: /* Top of screen  */
+	        return(BRL_CMD_TOP);
+	        break;
+
+	   case KEY_DOWN|KEY_CLICK: /* Bottom of screen */
+	        return(BRL_CMD_BOT);
+	        break;
+
+	   case KEY_LEFT|KEY_CLICK: /* Left one half window */
+	        return(BRL_CMD_HWINLT);
+	        break;
+
+	   case KEY_RIGHT|KEY_CLICK: /* Right one half window */
+	        return(BRL_CMD_HWINRT);
+	        break;
+        }
+
+	/* Byte C. Some Function Keys */
+	switch(*(pBuff+5)){
+	   case KEY_F9:
+	        /* Nothing */
+	        break;
+
+           case KEY_ALT:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F0:
+	        /* Nothing */
+	        break;
+
+	   case KEY_SHIFT: /* Cursor traking */
+		if(*(pBuff+6)==KEY_F8){
+		     return(BRL_CMD_CSRTRK);
+		}
+	        break;
+        }
+	
+
+	/* Byte D. Rest of Function Keys */
+	switch(*(pBuff+6)){
+	   case KEY_F1:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F2:  /* go to cursor */
+                res = BRL_CMD_HOME;
+	        break;
+
+	   case KEY_F3:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F4:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F5: /* togle cursor visibility */
+	        res = BRL_CMD_CSRVIS;
+	        break;
+
+	   case KEY_F6:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F7:
+	        /* Nothing */
+	        break;
+
+	   case KEY_F8: /* Six dot mode */
+	        res = BRL_CMD_SIXDOTS;
+	        break;
+        }
+     }
+  }
+  
+return(res);
+}
diff --git a/Drivers/Braille/EcoBraille/braille.h b/Drivers/Braille/EcoBraille/braille.h
new file mode 100644
index 0000000..02127ad
--- /dev/null
+++ b/Drivers/Braille/EcoBraille/braille.h
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* EcoBraille/braille.h - Configurable definitions for the Eco Braille series
+ * Copyright (C) 1999 by Oscar Fernandez <ofa@once.es>
+ *
+ * Edit as necessary for your system.
+ */
+
+/* Device Identification Numbers (not to be changed) */
+#define ECO_AUTO	-1
+#define ECO_20		1
+#define ECO_40		2
+#define ECO_80     	3
+#define NB_MODEL        4
+
+
+/***** User Settings *****/
+#define MODEL   ECO_AUTO
+
+/* serial line baudrate... 
+ * Note that default braille device is defined in ../Makefile
+ */
+#define BAUDRATE 19200
+
+/* typematic settings */
+#define TYPEMATIC_DELAY 10	/* nbr of cycles before a key is repeated */
+#define TYPEMATIC_REPEAT 2	/* nbr of cycles between each key repeat */
+
+/* Delay in miliseconds between forced full refresh of the display.
+ * This is to minimize garbage effects due to noise on the serial line.
+ */
+#define REFRESH_RATE 1000
diff --git a/Drivers/Braille/EuroBraille/Makefile.in b/Drivers/Braille/EuroBraille/Makefile.in
new file mode 100644
index 0000000..1ad329b
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/Makefile.in
@@ -0,0 +1,67 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = eu
+DRIVER_NAME = EuroBraille
+DRIVER_USAGE = AzerBraille, Clio, Esys, Iris, NoteBraille, Scriba
+DRIVER_VERSION = 2.0
+DRIVER_DEVELOPERS = Yannick PLASSIARD <yan@mistigri.org>, Olivier BERT <obert01@mistigri.org>, Nicolas PITRE <nico@fluxnic.net>
+include $(SRC_TOP)braille.mk
+
+SRC_FILES = eu_braille.c eu_clio.c eu_esysiris.c
+
+OBJ_FILES = $(SRC_FILES:.c=.$O)
+
+braille.$O: $(OBJ_FILES)
+	$(MKREL) $@ $(OBJ_FILES)
+
+eu_braille.$O:
+	$(CC) $(BRL_CFLAGS) -o $@ -c $(SRC_DIR)/eu_braille.c
+
+eu_clio.$O:
+	$(CC) $(BRL_CFLAGS) -o $@ -c $(SRC_DIR)/eu_clio.c
+
+eu_esysiris.$O:
+	$(CC) $(BRL_CFLAGS) -o $@ -c $(SRC_DIR)/eu_esysiris.c
+
+###############################################################################
+
+EU_TRANSFER_OBJECTS = eutp_brl.$O eutp_convert.$O eutp_debug.$O eutp_pc.$O eutp_tools.$O eutp_transfer.$O eutp_main.$O
+
+EU_TRANSFER_TOOL = eutp$X
+EU_TRANSFER_MAN = $(SRC_DIR)/eutp.1
+
+$(EU_TRANSFER_TOOL): $(EU_TRANSFER_OBJECTS) brlapi
+	$(CC) $(LDFLAGS) -o $@ $(EU_TRANSFER_OBJECTS) $(API_LIBS)
+
+api: $(EU_TRANSFER_TOOL)
+braille-all:: $(BUILD_API)
+
+clean::
+	-rm -f $(EU_TRANSFER_TOOL) $(EU_TRANSFER_OBJECTS)
+
+install-api:: install-eu-transfer-tool
+
+install-eu-transfer-tool: $(EU_TRANSFER_TOOL) install-program-directory install-man1-directory
+	$(INSTALL_PROGRAM) $(EU_TRANSFER_TOOL) $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_DATA) $(EU_TRANSFER_MAN) $(INSTALL_MAN1_DIRECTORY)
+
+uninstall::
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/$(EU_TRANSFER_TOOL)
+	-rm -f $(INSTALL_MAN1_DIRECTORY)/$(EU_TRANSFER_MAN)
+
diff --git a/Drivers/Braille/EuroBraille/README b/Drivers/Braille/EuroBraille/README
new file mode 100644
index 0000000..6a5ea6d
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/README
@@ -0,0 +1,121 @@
+This is the EuroBraille family driver for BRLTTY, version 2.0.0
+
+Initial writing by Nicolas Pitre <nico@fluxnic.net>, Copyright 1997-1998.
+
+This code is copyrighted under the GNU Lesser General Public License.
+See the file LICENSE-LGPL included with BRLTTY for details.
+
+This driver is currently maintained by 
+	* Yannick Plassiard <yan@mistigri.org>
+	* Olivier Bert <obert01@mistigri.org>
+
+SUPPORTED HARDWARE
+==================
+All EuroBraille's displays should be supported here. However it has been tested
+on the following hardware:
+   - a Clio-Notebraille (20,40,80);
+   - a Scriba (20,40);
+   - an Azerbraille (40,80);
+   - a Clio-PupiBraille (80);
+   - an Iris (S20, S32, 40, KB40);
+   - an EsyS (12,40,64).
+   - The driver is also tested with a NoteBraille display in order to 
+     maintain backward compatibility.
+
+
+FIXES/ADDITIONS
+===============
+Version 2.0.0:
+	- Complete rewrite of the whole driver to support new EsysIris
+	  protocol (serial and ethernet connection supported).
+	- Support Iris (firmware 1.71 and later)
+	- Support ESYS 12/40 display.
+	- Ability to autodetect which protocol to use.
+
+Version 1.3.4:
+	- Support the suspend/resume BRLTTY feature.
+
+Version 1.3.3:
+	- Corrected several bugs in write/read operations;
+	- Corrected several key bugs on Iris models.
+
+Version 1.3.2:
+	- Remade the brl_ReadPacket and brl_WritePacket, to allow writting of 
+	  low-level packets and error code retrieving;
+	- Full Iris support;
+
+Version 1.3.1:
+	- Many code cleanups;
+	- Corrected AzerBraille 40 Programming keys bugs;
+	- Added a LOG_IO define to log in/out packets to a file;
+	- Started to debug the Iris identification failures, still not work
+	  perfectly;
+
+Version 1.3 :
+	- Made the driver BrlAPI compatible
+	- The transfer code has been removed: it will be now in a separate 
+	  program, called `tp' (in progress).
+	- code cleanups;
+
+Version 1.2 :
+	- Significantly reduced the size of the sourcecode: propper and easier
+	  to understand/customize;
+	- Added LCD support;
+	- Beta new Iris 20/32/40 support, not tested yet;
+	- Re-added Cut-n-paste routines: it was disabled, don't know why...
+
+Version 1.1a :
+	- Minor changes to function-keys to make the program easier to 
+	  undepstand. 
+	- The on-line help function disappeared, and is replaced by the 
+	  CMD_LEARN (learn mode) function directly implemented within BrlTty.
+
+
+Version 1.1:
+	- Fixed bugs due to changes of brltty core functions
+	- Changed keys' insertion: We now use sequencial mode for meta and
+	  control keys, to allow more combinations. Moreover the input table is
+	  now attached to the output table. 
+
+Version 1.0 :
+   	- added an internal menu to allow console switching or to enter in the
+	  help menu. 
+     	  Here, a Scriba-like interface was implemented. Read the "README.menu"
+     	  file if you didn't use a Scriba (or the Draculawin program) before to
+     	  learn more about this menu.
+   	- When the drivers starts, it now plays a melody on the terminal's 
+	  internal speaker. 
+	  This was made to ensure the user that the driver is loaded and is
+       	  ready to be used.
+
+Version 0.9: 
+   	- You can now view the internal date/time of your display, typing "#M" 
+	  (or "Alpha+L9" on a Scriba). This doesn't work very well - don't know 
+	  why...
+
+Version 0.8:
+   	- Minor changes were made in the use of the "inskey()" function.
+   	- Fixed bugs in the ViewOn() and the Program() functions.
+
+Version 0.7: 
+   	- Added the "Programming" and the "View on" functions. Their respective 
+     	  behaviors are as in the DOS program (ReadBraille). See the 
+	  "README.model" file where "Model" is the name of your display. 
+	  Actually, files are written for the Clio-Notebraille and the Scriba 
+	  displays.
+
+Version 0.6:
+   	- Fixed the "enter bug": The "\n" inserted code is now replaced by 
+	  "\x0D".
+
+Version 0.5: 
+   	- Added the ability for the user to type letters directly from his/her
+     	  terminal. The table implemented is the CBIFS (French) table.
+
+Comments are strongly welcomed, because I don't know how many people use the 
+EuroBraille version of BRLTTY in the world so if you use this driver, just 
+e-mail us what are your suggestions for next versions.
+
+Yannick Plassiard <yan@mistigri.org>
+Olivier Bert <obert01@mistigri.org>
+    October 19 2007
diff --git a/Drivers/Braille/EuroBraille/brldefs-eu.h b/Drivers/Braille/EuroBraille/brldefs-eu.h
new file mode 100644
index 0000000..96273bb
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/brldefs-eu.h
@@ -0,0 +1,224 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_EU_BRLDEFS
+#define BRLTTY_INCLUDED_EU_BRLDEFS
+
+#define EU_NAK_PAR 0X01		/* parity error */
+#define EU_NAK_NUM 0X02		/* frame numver error */
+#define EU_NAK_LNG 0X03		/* length error */
+#define EU_NAK_COM 0X04		/* command error */
+#define EU_NAK_DON 0X05		/* data error */
+#define EU_NAK_SYN 0X06		/* syntax error */
+#define EU_NAK_VOC 0X80		/* RV: Régime Vocal not supported by host */
+
+#define EU_LCD_CURSOR 0X02
+
+typedef enum {
+  EU_IRIS_20              = 0X01,
+  EU_IRIS_40              = 0X02,
+  EU_IRIS_S20             = 0X03,
+  EU_IRIS_S32             = 0X04,
+  EU_IRIS_KB20            = 0X05,
+  EU_IRIS_KB40            = 0X06,
+  EU_ESYS_12              = 0X07,
+  EU_ESYS_40              = 0X08,
+  EU_ESYS_LIGHT_40        = 0X09,
+  EU_ESYS_24              = 0X0A,
+  EU_ESYS_64              = 0X0B,
+  EU_ESYS_80              = 0X0C,
+  EU_ESYS_LIGHT_80        = 0x0D,
+  EU_ESYTIME_32           = 0X0E,
+  EU_ESYTIME_32_STANDARD  = 0X0F,
+  EU_ESYTIME_EVO          = 0x10,
+  EU_ESYTIME_EVO_STANDARD = 0x11
+} EU_EsysirisModel;
+
+typedef enum {
+  EU_IRIS_OPT_UnimanualKeyboard    = 0X00000001,
+  EU_IRIS_OPT_DialogueMode         = 0X00000002,
+  EU_IRIS_OPT_Grade2Braille        = 0X00001000,
+  EU_IRIS_OPT_MsnMessenger         = 0X00002000,
+  EU_IRIS_OPT_DaisyReader          = 0X00004000,
+  EU_IRIS_OPT_TelephoneExchange    = 0X00008000,
+  EU_IRIS_OPT_Mathematics          = 0X00010000,
+  EU_IRIS_OPT_Music                = 0X00020000,
+  EU_IRIS_OPT_HqVoiceSynthesis     = 0X00040000,
+  EU_IRIS_OPT_Documentation        = 0X00080000,
+  EU_IRIS_OPT_FileExplorer         = 0X00100000,
+  EU_IRIS_OPT_VocalMemo            = 0X00200000,
+  EU_IRIS_OPT_PcSerial             = 0X00400000,
+  EU_IRIS_OPT_PcEthernet           = 0X00800000,
+  EU_IRIS_OPT_Editor               = 0X01000000,
+  EU_IRIS_OPT_Spreadsheet          = 0X02000000,
+  EU_IRIS_OPT_Internet             = 0X04000000,
+  EU_IRIS_OPT_Calculator           = 0X08000000,
+  EU_IRIS_OPT_ScientificCalculator = 0X10000000,
+  EU_IRIS_OPT_Contact              = 0X20000000,
+  EU_IRIS_OPT_Agenda               = 0X40000000,
+  EU_IRIS_OPT_Libbraille           = 0X80000000
+} EU_IrisOption;
+
+typedef enum {
+  EU_ESYS_OPT_Editor                = 0X00040001,
+  EU_ESYS_OPT_Calculator            = 0X00040002,
+  EU_ESYS_OPT_AlarmClock            = 0X00040004,
+  EU_ESYS_OPT_Bluetooth             = 0X00000008,
+  EU_ESYS_OPT_USB                   = 0X00000010,
+  EU_ESYS_OPT_Readmath              = 0X00000100,
+  EU_ESYS_OPT_Jaws                  = 0X0001000,
+  EU_ESYS_OPT_WindowEyes            = 0X0002000,
+  EU_ESYS_OPT_SuperNova             = 0X0004000,
+  EU_ESYS_OPT_MobileSpeakPocket     = 0X01000000,
+  EU_ESYS_OPT_MobileSpeakSmartphone = 0X02000000,
+  EU_ESYS_OPT_Talks                 = 0X04000000,
+  EU_ESYS_OPT_Orange                = 0X08000000,
+  EU_ESYS_OPT_Tracker               = 0X10000000
+} EU_EsysOption;
+
+typedef enum {
+  EU_NAV_Sharp = 0X23,
+  EU_NAV_Star  = 0X2A,
+
+  EU_NAV_Zero  = 0X30,
+  EU_NAV_One   = 0X31,
+  EU_NAV_Two   = 0X32,
+  EU_NAV_Three = 0X33,
+  EU_NAV_Four  = 0X34,
+  EU_NAV_Five  = 0X35,
+  EU_NAV_Six   = 0X36,
+  EU_NAV_Seven = 0X37,
+  EU_NAV_Eight = 0X38,
+  EU_NAV_Nine  = 0X39,
+
+  EU_NAV_A     = 0X41,
+  EU_NAV_B     = 0X42,
+  EU_NAV_C     = 0X43,
+  EU_NAV_D     = 0X44,
+  EU_NAV_E     = 0X45,
+  EU_NAV_F     = 0X46,
+  EU_NAV_G     = 0X47,
+  EU_NAV_H     = 0X48,
+  EU_NAV_I     = 0X49,
+  EU_NAV_J     = 0X4A,
+  EU_NAV_K     = 0X4B,
+  EU_NAV_L     = 0X4C,
+  EU_NAV_M     = 0X4D,
+} EU_NavigationKey;
+
+typedef enum {
+  EU_INT_Dollar = 0X81,
+  EU_INT_U      = 0X82,
+  EU_INT_Z      = 0X83,
+
+  EU_INT_V      = 0X88,
+  EU_INT_W      = 0X89,
+  EU_INT_X      = 0X8A,
+  EU_INT_Y      = 0X8B
+} EU_InteractiveKey;
+
+typedef enum {
+  EU_DOT_1 =  0,
+  EU_DOT_2 =  1,
+  EU_DOT_3 =  2,
+  EU_DOT_4 =  3,
+  EU_DOT_5 =  4,
+  EU_DOT_6 =  5,
+  EU_DOT_B =  6,
+  EU_DOT_S =  7,
+  EU_DOT_7 =  8,
+  EU_DOT_8 =  9
+} EU_DotKey;
+
+typedef enum {
+  /* Iris linear and arrow keys */
+  EU_CMD_L1    =  0,
+  EU_CMD_L2    =  1,
+  EU_CMD_L3    =  2,
+  EU_CMD_L4    =  3,
+  EU_CMD_L5    =  4,
+  EU_CMD_L6    =  5,
+  EU_CMD_L7    =  6,
+  EU_CMD_L8    =  7,
+  EU_CMD_Up    =  8,
+  EU_CMD_Down  =  9,
+  EU_CMD_Right = 10,
+  EU_CMD_Left  = 11,
+
+  /* Esytime function keys */
+  EU_CMD_F1 =  0,
+  EU_CMD_F2 =  1,
+  EU_CMD_F3 =  2,
+  EU_CMD_F4 =  3,
+  EU_CMD_F8 =  4,
+  EU_CMD_F7 =  5,
+  EU_CMD_F6 =  6,
+  EU_CMD_F5 =  7,
+
+  /* Esys switches */
+  EU_CMD_Switch1Right =  0,
+  EU_CMD_Switch1Left  =  1,
+  EU_CMD_Switch2Right =  2,
+  EU_CMD_Switch2Left  =  3,
+  EU_CMD_Switch3Right =  4,
+  EU_CMD_Switch3Left  =  5,
+  EU_CMD_Switch4Right =  6,
+  EU_CMD_Switch4Left  =  7,
+  EU_CMD_Switch5Right =  8,
+  EU_CMD_Switch5Left  =  9,
+  EU_CMD_Switch6Right = 10,
+  EU_CMD_Switch6Left  = 11,
+
+  /* Esys and Esytime joystick #1 */
+  EU_CMD_LeftJoystickUp    = 16,
+  EU_CMD_LeftJoystickDown  = 17,
+  EU_CMD_LeftJoystickRight = 18,
+  EU_CMD_LeftJoystickLeft  = 19,
+  EU_CMD_LeftJoystickPress = 20, // activates internal menu
+
+  /* Esys and Esytime joystick #2 */
+  EU_CMD_RightJoystickUp    = 24,
+  EU_CMD_RightJoystickDown  = 25,
+  EU_CMD_RightJoystickRight = 26,
+  EU_CMD_RightJoystickLeft  = 27,
+  EU_CMD_RightJoystickPress = 28,
+} EU_CommandKey;
+
+typedef enum {
+  EU_BRL_Dot1      =  0,
+  EU_BRL_Dot2      =  1,
+  EU_BRL_Dot3      =  2,
+  EU_BRL_Dot4      =  3,
+  EU_BRL_Dot5      =  4,
+  EU_BRL_Dot6      =  5,
+  EU_BRL_Dot7      =  6,
+  EU_BRL_Dot8      =  7,
+  EU_BRL_Backspace =  8,
+  EU_BRL_Space     =  9
+} EU_BrailleKey;
+
+typedef enum {
+  EU_GRP_NavigationKeys,
+  EU_GRP_InteractiveKeys,
+  EU_GRP_CommandKeys,
+  EU_GRP_BrailleKeys,
+  EU_GRP_RoutingKeys1,
+  EU_GRP_RoutingKeys2
+} EU_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_EU_BRLDEFS */ 
diff --git a/Drivers/Braille/EuroBraille/eu_braille.c b/Drivers/Braille/EuroBraille/eu_braille.c
new file mode 100644
index 0000000..f6a4cc5
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eu_braille.c
@@ -0,0 +1,404 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/**
+ ** brl.c -- EuroBraille core driver file.
+ ** Made by Yannick PLASSIARD and Olivier BERT
+ */
+
+#include "prologue.h"
+
+typedef enum {
+  PARM_PROTOCOL
+}		DriverParameter;
+
+#define BRLPARMS "protocol"
+
+
+#include <stdio.h>
+#include <string.h>
+
+#include "message.h"
+#include "log.h"
+
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "parse.h"
+#include "async_wait.h"
+
+#include	"eu_protocol.h"
+
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(clio),
+  &KEY_TABLE_DEFINITION(iris),
+  &KEY_TABLE_DEFINITION(esys_small),
+  &KEY_TABLE_DEFINITION(esys_medium),
+  &KEY_TABLE_DEFINITION(esys_large),
+  &KEY_TABLE_DEFINITION(esytime),
+END_KEY_TABLE_LIST
+
+const InputOutputOperations *io = NULL;
+static const ProtocolOperations *protocol = NULL;
+
+static inline void
+updateWriteDelay (BrailleDisplay *brl, size_t count) {
+  brl->writeDelay += gioGetMillisecondsToTransfer(brl->gioEndpoint, count);
+}
+
+static int
+awaitInput_generic (BrailleDisplay *brl, int timeout) {
+  return awaitBrailleInput(brl, timeout);
+}
+
+static int
+readByte_generic (BrailleDisplay *brl, unsigned char *byte, int wait) {
+  return gioReadByte(brl->gioEndpoint, byte, wait);
+}
+
+static ssize_t
+writeData_generic (BrailleDisplay *brl, const void *data, size_t length) {
+  updateWriteDelay(brl, length);
+  return gioWriteData(brl->gioEndpoint, data, length);
+}
+
+static ssize_t
+writeData_USB (BrailleDisplay *brl, const void *data, size_t length) {
+  size_t offset = 0;
+
+  while (offset < length) {
+    unsigned char report[64];
+    size_t count = length - offset;
+
+    if (count > sizeof(report)) {
+      count = sizeof(report);
+    } else {
+      memset(&report[count], 0X55, (sizeof(report) - count));
+    }
+    memcpy(report, data+offset, count);
+
+    updateWriteDelay(brl, sizeof(report));
+    if (gioSetHidReport(brl->gioEndpoint, 0, report, sizeof(report)) < 0) return -1;
+
+    offset += count;
+  }
+
+  return length;
+}
+
+static const InputOutputOperations serialOperations = {
+  .awaitInput = awaitInput_generic,
+  .readByte = readByte_generic,
+  .writeData = writeData_generic
+};
+
+static const InputOutputOperations usbOperations = {
+  .protocol = &esysirisProtocolOperations,
+  .awaitInput = awaitInput_generic,
+  .readByte = readByte_generic,
+  .writeData = writeData_USB
+};
+
+static const InputOutputOperations bluetoothOperations = {
+  .protocol = &esysirisProtocolOperations,
+  .awaitInput = awaitInput_generic,
+  .readByte = readByte_generic,
+  .writeData = writeData_generic
+};
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600,
+    .parity = SERIAL_PARITY_EVEN
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Esys (version < 3.0, no SD card) */
+      .vendor=0XC251, .product=0X1122,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1123,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* Esys (version < 3.0, with SD card) */
+      .vendor=0XC251, .product=0X1124,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1125,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* Esys (version >= 3.0, no SD card) */
+      .vendor=0XC251, .product=0X1126,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1127,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* Esys (version >= 3.0, with SD card) */
+      .vendor=0XC251, .product=0X1128,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1129,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112B,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112C,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112D,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112E,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X112F,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* Esytime */
+      .vendor=0XC251, .product=0X1130,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .verifyInterface = 1,
+      .disableEndpointReset = 1
+    },
+
+    { /* Esytime (firmware 1.03, 2014-03-31) */
+      .vendor=0XC251, .product=0X1130,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=7, .outputEndpoint=0,
+      .verifyInterface = 1,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1131,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+
+    { /* reserved */
+      .vendor=0XC251, .product=0X1132,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=0,
+      .disableEndpointReset = 1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &serialOperations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbOperations;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.options.applicationData = &bluetoothOperations;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    io = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  io = NULL;
+  protocol = NULL;
+  makeOutputTable(dotsTable_ISO11548_1);
+
+  if (parameters[PARM_PROTOCOL]) {
+    static const char *const choices[] = {
+      "auto",
+      "azerbraille", "clio", "eurobraille",
+      "notebraille", "pupibraille", "scriba",
+      "esys", "esytime", "iris", "esysiris",
+      NULL
+    };
+
+    static const ProtocolOperations *const protocols[] = {
+      NULL, // auto
+
+      &clioProtocolOperations, // azerbraille
+      &clioProtocolOperations, // clio
+      &clioProtocolOperations, // eurobraille
+      &clioProtocolOperations, // notebraille
+      &clioProtocolOperations, // pupibraille
+      &clioProtocolOperations, // scriba
+
+      &esysirisProtocolOperations, // esys
+      &esysirisProtocolOperations, // esytime
+      &esysirisProtocolOperations, // iris
+      &esysirisProtocolOperations  // esysiris
+    };
+
+    unsigned int choice;
+
+    if (!validateChoice(&choice, parameters[PARM_PROTOCOL], choices)) {
+      logMessage(LOG_ERR, "unknown EuroBraille protocol: %s", 
+                 parameters[PARM_PROTOCOL]);
+      choice = 0;
+    }
+
+    protocol = protocols[choice];
+  }
+
+  if (connectResource(brl, device)) {
+    if (protocol) {
+      if (!io->protocol || (io->protocol == protocol)) {
+        if (protocol->initializeDevice(brl)) return 1;
+      } else {
+        logMessage(LOG_ERR, "protocol not supported by device: %s", protocol->protocolName);
+      }
+    } else if (io->protocol) {
+      protocol = io->protocol;
+      if (protocol->initializeDevice(brl)) return 1;
+    } else {
+      static const ProtocolOperations *const protocols[] = {
+        &esysirisProtocolOperations, &clioProtocolOperations,
+        NULL
+      };
+      const ProtocolOperations *const *p = protocols;
+
+      while (*p) {
+        const ProtocolOperations *protocol = *p++;
+
+        logMessage(LOG_NOTICE, "trying protocol: %s", protocol->protocolName);
+        if (protocol->initializeDevice(brl)) return 1;
+	asyncWait(700);
+      }
+    }
+
+    disconnectBrailleResource(brl, NULL);
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (protocol)
+    {
+      protocol = NULL;
+    }
+  disconnectBrailleResource(brl, NULL);
+}
+
+#ifdef BRL_HAVE_PACKET_IO
+static ssize_t
+brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  if (!protocol || !io)
+    return (-1);
+  return protocol->readPacket(brl, buffer, size);
+}
+
+static ssize_t
+brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) {
+  if (!protocol || !io)
+    return (-1);
+  return protocol->writePacket(brl, packet, length);
+}
+
+static int
+brl_reset (BrailleDisplay *brl) {
+  if (!protocol || !io)
+    return (-1);
+  return protocol->resetDevice(brl);
+}
+#endif /* BRL_HAVE_PACKET_IO */
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (!protocol) return 1;
+
+  if (text)
+    if (!protocol->writeVisual(brl, text))
+      return 0;
+
+  return protocol->writeWindow(brl);
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  if (protocol)
+    return protocol->readCommand(brl, context);
+  return EOF;
+}
diff --git a/Drivers/Braille/EuroBraille/eu_clio.c b/Drivers/Braille/EuroBraille/eu_clio.c
new file mode 100644
index 0000000..c26fa3a
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eu_clio.c
@@ -0,0 +1,746 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** EuroBraille/eu_clio.c 
+ ** Implements the NoteBraille/Clio/Scriba/Iris <= 1.70 protocol 
+ ** Made by Olivier BER` <obert01@mistigri.org>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "timing.h"
+#include "ascii.h"
+#include "brldefs-eu.h"
+#include "eu_protocol.h"
+
+#define BRAILLE_KEY_ENTRY(k,n) KEY_ENTRY(BrailleKeys, DOT, k, n)
+#define NAVIGATION_KEY_ENTRY(k,n) KEY_ENTRY(NavigationKeys, NAV, k, n)
+#define INTERACTIVE_KEY_ENTRY(k,n) KEY_ENTRY(InteractiveKeys, INT, k, n)
+
+BEGIN_KEY_NAME_TABLE(braille)
+  BRAILLE_KEY_ENTRY(1, "Dot1"),
+  BRAILLE_KEY_ENTRY(2, "Dot2"),
+  BRAILLE_KEY_ENTRY(3, "Dot3"),
+  BRAILLE_KEY_ENTRY(4, "Dot4"),
+  BRAILLE_KEY_ENTRY(5, "Dot5"),
+  BRAILLE_KEY_ENTRY(6, "Dot6"),
+  BRAILLE_KEY_ENTRY(7, "Dot7"),
+  BRAILLE_KEY_ENTRY(8, "Dot8"),
+  BRAILLE_KEY_ENTRY(B, "Backspace"),
+  BRAILLE_KEY_ENTRY(S, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(function)
+  NAVIGATION_KEY_ENTRY(E, "E"),
+  NAVIGATION_KEY_ENTRY(F, "F"),
+  NAVIGATION_KEY_ENTRY(G, "G"),
+  NAVIGATION_KEY_ENTRY(H, "H"),
+  NAVIGATION_KEY_ENTRY(I, "I"),
+  NAVIGATION_KEY_ENTRY(J, "J"),
+  NAVIGATION_KEY_ENTRY(K, "K"),
+  NAVIGATION_KEY_ENTRY(L, "L"),
+  NAVIGATION_KEY_ENTRY(M, "M"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keypad)
+  NAVIGATION_KEY_ENTRY(One, "One"),
+  NAVIGATION_KEY_ENTRY(Two, "Up"),
+  NAVIGATION_KEY_ENTRY(Three, "Three"),
+  NAVIGATION_KEY_ENTRY(A, "A"),
+
+  NAVIGATION_KEY_ENTRY(Four, "Left"),
+  NAVIGATION_KEY_ENTRY(Five, "Five"),
+  NAVIGATION_KEY_ENTRY(Six, "Right"),
+  NAVIGATION_KEY_ENTRY(B, "B"),
+
+  NAVIGATION_KEY_ENTRY(Seven, "Seven"),
+  NAVIGATION_KEY_ENTRY(Eight, "Down"),
+  NAVIGATION_KEY_ENTRY(Nine, "Nine"),
+  NAVIGATION_KEY_ENTRY(C, "C"),
+
+  NAVIGATION_KEY_ENTRY(Star, "Star"),
+  NAVIGATION_KEY_ENTRY(Zero, "Zero"),
+  NAVIGATION_KEY_ENTRY(Sharp, "Sharp"),
+  NAVIGATION_KEY_ENTRY(D, "D"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(interactive)
+  INTERACTIVE_KEY_ENTRY(Dollar, "Dollar"),
+  KEY_GROUP_ENTRY(EU_GRP_RoutingKeys1, "RoutingKey"),
+  INTERACTIVE_KEY_ENTRY(U, "U"),
+  INTERACTIVE_KEY_ENTRY(V, "V"),
+  INTERACTIVE_KEY_ENTRY(W, "W"),
+  INTERACTIVE_KEY_ENTRY(X, "X"),
+  INTERACTIVE_KEY_ENTRY(Y, "Y"),
+  INTERACTIVE_KEY_ENTRY(Z, "Z"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(clio)
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(function),
+  KEY_NAME_TABLE(keypad),
+  KEY_NAME_TABLE(interactive),
+END_KEY_NAME_TABLES
+
+PUBLIC_KEY_TABLE(clio)
+
+#define	INPUT_BUFFER_SIZE 1024
+#define MAXIMUM_DISPLAY_SIZE 80
+
+typedef struct {
+  char modelCode[3];
+  const char *modelName;
+  unsigned char cellCount;
+  unsigned isAzerBraille:1;
+  unsigned isEuroBraille:1;
+  unsigned isIris:1;
+  unsigned isNoteBraille:1;
+  unsigned isPupiBraille:1;
+  unsigned isScriba:1;
+  unsigned hasRoutingKeys:1;
+  unsigned hasVisualDisplay:1;
+} ModelEntry;
+
+static const ModelEntry modelTable[] = {
+  { .modelCode = "CE2",
+    .modelName = "Clio-EuroBraille 20",
+    .cellCount = 20,
+    .hasRoutingKeys = 1,
+    .isEuroBraille = 1
+  },
+
+  { .modelCode = "CE4",
+    .modelName = "Clio-EuroBraille 40",
+    .cellCount = 40,
+    .hasRoutingKeys = 1,
+    .isEuroBraille = 1
+  },
+
+  { .modelCode = "CE8",
+    .modelName = "Clio-EuroBraille 80",
+    .cellCount = 80,
+    .hasRoutingKeys = 1,
+    .isEuroBraille = 1
+  },
+
+  { .modelCode = "CN2",
+    .modelName = "Clio-NoteBraille 20",
+    .cellCount = 20,
+    .hasRoutingKeys = 1,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "CN4",
+    .modelName = "Clio-NoteBraille 40",
+    .cellCount = 40,
+    .hasRoutingKeys = 1,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "CN8",
+    .modelName = "Clio-NoteBraille 80",
+    .cellCount = 80,
+    .hasRoutingKeys = 1,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "Cp2",
+    .modelName = "Clio-PupiBraille 20",
+    .cellCount = 20,
+    .hasRoutingKeys = 1,
+    .isPupiBraille = 1
+  },
+
+  { .modelCode = "Cp4",
+    .modelName = "Clio-PupiBraille 40",
+    .cellCount = 40,
+    .hasRoutingKeys = 1,
+    .isPupiBraille = 1
+  },
+
+  { .modelCode = "Cp8",
+    .modelName = "Clio-PupiBraille 80",
+    .cellCount = 80,
+    .hasRoutingKeys = 1,
+    .isPupiBraille = 1
+  },
+
+  { .modelCode = "CZ4",
+    .modelName = "Clio-AzerBraille 40",
+    .cellCount = 40,
+    .hasRoutingKeys = 1,
+    .hasVisualDisplay = 1,
+    .isAzerBraille = 1
+  },
+
+  { .modelCode = "JN2",
+    .modelName = "Junior-NoteBraille 20",
+    .cellCount = 20,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "NB2",
+    .modelName = "NoteBraille 20",
+    .cellCount = 20,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "NB4",
+    .modelName = "NoteBraille 40",
+    .cellCount = 40,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "NB8",
+    .modelName = "NpoteBraille 80",
+    .cellCount = 80,
+    .hasVisualDisplay = 1,
+    .isNoteBraille = 1
+  },
+
+  { .modelCode = "JS2",
+    .modelName = "Junior-Scriba 20",
+    .cellCount = 20,
+    .hasRoutingKeys = 1,
+    .isScriba = 1
+  },
+
+  { .modelCode = "SB2",
+    .modelName = "Scriba 20",
+    .cellCount = 20,
+    .hasRoutingKeys = 1,
+    .isScriba = 1
+  },
+
+  { .modelCode = "SB4",
+    .modelName = "Scriba 40",
+    .cellCount = 40,
+    .hasRoutingKeys = 1,
+    .isScriba = 1
+  },
+
+  { .modelCode = "SC2",
+    .modelName = "Scriba 20",
+    .cellCount = 20
+  },
+
+  { .modelCode = "SC4",
+    .modelName = "Scriba 40",
+    .cellCount = 40
+  },
+
+  { .modelCode = "IR2",
+    .modelName = "Iris 20",
+    .cellCount = 20,
+    .hasVisualDisplay = 1,
+    .isIris = 1
+  },
+
+  { .modelCode = "IR4",
+    .modelName = "Iris 40",
+    .cellCount = 40,
+    .hasVisualDisplay = 1,
+    .isIris = 1
+  },
+
+  { .modelCode = "IS2",
+    .modelName = "Iris S20",
+    .cellCount = 20,
+    .isIris = 1
+  },
+
+  { .modelCode = "IS3",
+    .modelName = "Iris S32",
+    .cellCount = 32,
+    .isIris = 1
+  },
+
+  { .modelCode = "" }
+};
+
+static int haveSystemInformation;
+static unsigned char firmwareVersion[21];
+static const ModelEntry *model;
+
+static unsigned char forceWindowRewrite;
+static unsigned char forceVisualRewrite;
+static unsigned char forceCursorRewrite;
+static int inputPacketNumber;
+static int outputPacketNumber;
+
+static inline void
+forceRewrite (void) {
+  forceWindowRewrite = 1;
+  forceVisualRewrite = 1;
+  forceCursorRewrite = 1;
+}
+
+static int
+needsEscape (unsigned char byte) {
+  switch (byte) {
+    case ASCII_SOH:
+    case ASCII_EOT:
+    case ASCII_DLE:
+    case ASCII_ACK:
+    case ASCII_NAK:
+      return 1;
+  }
+
+  return 0;
+}
+
+static ssize_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  unsigned char buffer[size + 4];
+  int offset = 0;
+  int escape = 0;
+
+  while (1)
+    {
+      int started = offset > 0;
+      int escaped = 0;
+      unsigned char byte;
+
+      if (!io->readByte(brl, &byte, (started || escape)))
+        {
+          if (started) logPartialPacket(buffer, offset);
+          return (errno == EAGAIN)? 0: -1;
+        }
+
+      if (escape)
+        {
+          escape = 0;
+          escaped = 1;
+        }
+      else if (byte == ASCII_DLE)
+        {
+          escape = 1;
+          continue;
+        }
+
+      if (!escaped)
+        {
+          switch (byte)
+            {
+            case ASCII_SOH:
+              if (started)
+                {
+                  logShortPacket(buffer, offset);
+                  offset = 1;
+                  continue;
+                }
+              goto addByte;
+
+            case ASCII_EOT:
+              break;
+
+            default:
+              if (needsEscape(byte))
+                {
+                  if (started) logShortPacket(buffer, offset);
+                  offset = 0;
+                  continue;
+                }
+              break;
+            }
+        }
+
+      if (!started)
+        {
+          logIgnoredByte(byte);
+          continue;
+        }
+
+    addByte:
+      if (offset < sizeof(buffer))
+        {
+          buffer[offset] = byte;
+        }
+      else
+        {
+          if (offset == sizeof(buffer)) logTruncatedPacket(buffer, offset);
+          logDiscardedByte(byte);
+        }
+      offset += 1;
+
+      if (!escaped && (byte == ASCII_EOT))
+        {
+          if (offset > sizeof(buffer))
+            {
+              offset = 0;
+              continue;
+            }
+
+          logInputPacket(buffer, offset);
+          offset -= 1; /* remove EOT */
+
+          {
+            unsigned char parity = 0;
+
+            {
+              int i;
+
+              for (i=1; i<offset; i+=1)
+                {
+                  parity ^= buffer[i];
+                }
+            }
+
+            if (parity) {
+              static const unsigned char message[] = {ASCII_NAK, EU_NAK_PAR};
+
+              io->writeData(brl, message, sizeof(message));
+              offset = 0;
+              continue;
+            }
+          }
+
+          offset -= 1; /* remove parity */
+
+          {
+            static const unsigned char message[] = {ASCII_ACK};
+            io->writeData(brl, message, sizeof(message));
+          }
+
+          if (buffer[--offset] == inputPacketNumber)
+            {
+              offset = 0;
+              continue;
+            }
+          inputPacketNumber = buffer[offset];
+
+          memcpy(packet, &buffer[1], offset-1);
+          return offset;
+        }
+    }
+}
+
+static ssize_t
+writePacket (BrailleDisplay *brl, const void *packet, size_t size) {
+#define PUT(byte) \
+  if (needsEscape((byte))) *target++ = ASCII_DLE; \
+  *target++ = (byte); \
+  parity ^= (byte);
+
+  /* limit case, every char is escaped */
+  unsigned char	buffer[(size + 4) * 2]; 
+  unsigned char	*target = buffer;
+  const unsigned char *source = packet;
+  unsigned char	parity = 0;
+
+  *target++ = ASCII_SOH;
+  PUT(size);
+
+  while (size--) {
+    PUT(*source);
+    source += 1;
+  }
+
+  PUT(outputPacketNumber);
+  if (++outputPacketNumber >= 256) outputPacketNumber = 128;
+
+  PUT(parity);
+  *target++ = ASCII_EOT;
+
+  {
+    size_t count = target - buffer;
+    logOutputPacket(buffer, count);
+    return io->writeData(brl, buffer, count);
+  }
+#undef PUT
+}
+
+static int
+resetDevice (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {'S', 'I'};
+  return writePacket(brl, packet, sizeof(packet)) != -1;
+}
+
+static const ModelEntry *
+getModelEntry (const unsigned char *code) {
+  const ModelEntry *mdl = modelTable;
+
+  while (mdl->modelCode[0]) {
+    if (memcmp(mdl->modelCode, code, sizeof(mdl->modelCode)) == 0) return mdl;
+    mdl += 1;
+  }
+
+  return NULL;
+}
+
+static void
+handleSystemInformation (BrailleDisplay *brl, const unsigned char *packet) {
+  const unsigned char *p = packet;
+
+  while (1) {
+    unsigned char length = *(p++);
+
+    switch (p[0]) {
+      case 'S':
+        switch (p[1]) {
+          case 'I': {
+            unsigned char count = length - 2;
+            if (count >= sizeof(firmwareVersion)) count = sizeof(firmwareVersion) - 1;
+            memcpy(firmwareVersion, p+2, count);
+            model = getModelEntry(firmwareVersion);
+            return;
+          }
+
+          default:
+            break;
+        }
+        break;
+
+      default:
+        break;
+    }
+
+    p += length;
+  }
+}
+
+static int
+writeWindow (BrailleDisplay *brl) {
+  static unsigned char previousCells[MAXIMUM_DISPLAY_SIZE];
+  size_t size = brl->textColumns * brl->textRows;
+  unsigned char buffer[size + 2];
+
+  if (cellsHaveChanged(previousCells, brl->buffer, size, NULL, NULL, &forceWindowRewrite)) {
+    buffer[0] = 'D';
+    buffer[1] = 'P';
+    translateOutputCells(buffer+2, brl->buffer, size);
+    writePacket(brl, buffer, sizeof(buffer));
+  }
+
+  return 1;
+}
+
+static int
+writeVisual (BrailleDisplay *brl, const wchar_t *text) {
+  if (model->hasVisualDisplay) {
+    size_t size = brl->textColumns * brl->textRows;
+    int changed = 0;
+
+    {
+      static wchar_t previousText[MAXIMUM_DISPLAY_SIZE];
+
+      if (textHasChanged(previousText, text, size, NULL, NULL, &forceVisualRewrite)) changed = 1;
+    }
+
+    {
+      static int previousCursor;
+
+      if (cursorHasChanged(&previousCursor, brl->cursor, &forceCursorRewrite)) changed = 1;
+    }
+
+    if (changed) {
+      const wchar_t *source = text;
+      const wchar_t *end = source + size;
+      const wchar_t *cursor = (brl->cursor != BRL_NO_CURSOR)? source+brl->cursor: NULL;
+
+      unsigned char buffer[size + 4]; // code, subcode, and possibly two bytes for cursor
+      unsigned char *target = buffer;
+
+      *target++ = 'D';
+      *target++ = 'L';
+
+      while (source < end) {
+        if (source == cursor) {
+          *target++ = ASCII_ESC;
+          *target++ = EU_LCD_CURSOR;
+        }
+
+        {
+          wchar_t wc = *source++;
+          if (!iswLatin1(wc)) wc = '?';
+          *target++ = wc;
+        }
+      }
+
+      writePacket(brl, buffer, target-buffer);
+    }
+  }
+
+  return 1;
+}
+
+static int
+hasVisualDisplay (BrailleDisplay *brl) {
+  return model->hasVisualDisplay;
+}
+
+static int
+handleMode (BrailleDisplay *brl, const unsigned char *packet) {
+  if (*packet == 'B') {
+    forceRewrite();
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+handleKeyEvent (BrailleDisplay *brl, const unsigned char *packet) {
+  switch (packet[0]) {
+    case 'B': {
+      KeyNumberSet keys = ((packet[2] << 8) | packet[1]) & 0X3FF;
+
+      enqueueKeys(brl, keys, EU_GRP_BrailleKeys, 0);
+      return 1;
+    }
+
+    case 'I': {
+      unsigned char key = packet[1];
+
+      if ((key >= 1) && (key <= brl->textColumns)) {
+        enqueueKey(brl, EU_GRP_RoutingKeys1, key-1);
+      } else {
+        enqueueKey(brl, EU_GRP_InteractiveKeys, key);
+      }
+
+      return 1;
+    }
+
+    case 'T': 
+      enqueueKey(brl, EU_GRP_NavigationKeys, packet[1]);
+      return 1;
+
+    default :
+      break;
+  }
+
+  return 0;
+}
+
+static int
+readCommand (BrailleDisplay *brl, KeyTableCommandContext ctx) {
+  unsigned char	packet[INPUT_BUFFER_SIZE];
+  ssize_t length;
+
+  while ((length = readPacket(brl, packet, sizeof(packet))) > 0) {
+    switch (packet[1]) {
+      case 'S': 
+        handleSystemInformation(brl, packet);
+        haveSystemInformation = 1;
+        continue;
+
+      case 'R': 
+        if (handleMode(brl, packet+2)) continue;
+        break;
+
+      case 'K': 
+        if (handleKeyEvent(brl, packet+2)) continue;
+        break;
+
+      default: 
+        break;
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+
+  return (length == -1)? BRL_CMD_RESTARTBRL: EOF;
+}
+
+static int
+initializeDevice (BrailleDisplay *brl) {
+  int retriesLeft = 2;
+
+  haveSystemInformation = 0;
+  memset(firmwareVersion, 0, sizeof(firmwareVersion));
+  model = NULL;
+
+  forceRewrite();
+  inputPacketNumber = -1;
+  outputPacketNumber = 127;
+
+  do {
+    if (!resetDevice(brl)) return 0;
+
+    while (io->awaitInput(brl, 500)) {
+      if (readCommand(brl, KTB_CTX_DEFAULT) == BRL_CMD_RESTARTBRL) return 0;
+
+      if (haveSystemInformation) {
+        if (!model) {
+          int length = sizeof(model->modelCode);
+          logMessage(LOG_WARNING, "unknown EuroBraille model: %.*s",
+                     length, firmwareVersion);
+          return 0;
+        }
+
+        brl->textColumns = model->cellCount;
+
+        switch (firmwareVersion[2]) {
+          case '2':
+            brl->textColumns = 20;
+            break;
+
+          case '3':
+            brl->textColumns = 32;
+            break;
+
+          case '4':
+            brl->textColumns = 40;
+            break;
+
+          case '8':
+            brl->textColumns = 80;
+            break;
+
+          default:
+            break;
+        }
+
+        setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(clio));
+
+        logMessage(LOG_INFO, "Model Detected: %s (%u cells)",
+                   model->modelName, brl->textColumns);
+        return 1;
+      }
+    }
+  } while (retriesLeft-- && (errno == EAGAIN));
+
+  return 0;
+}
+
+const ProtocolOperations clioProtocolOperations = {
+  .protocolName = "clio",
+
+  .initializeDevice = initializeDevice,
+  .resetDevice = resetDevice,
+
+  .readPacket = readPacket,
+  .writePacket = writePacket,
+
+  .readCommand = readCommand,
+  .writeWindow = writeWindow,
+
+  .hasVisualDisplay = hasVisualDisplay,
+  .writeVisual = writeVisual
+};
diff --git a/Drivers/Braille/EuroBraille/eu_esysiris.c b/Drivers/Braille/EuroBraille/eu_esysiris.c
new file mode 100644
index 0000000..6e0b8dd
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eu_esysiris.c
@@ -0,0 +1,938 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** EuroBraille/eu_esysiris.c 
+ ** Implements the ESYS and IRIS rev >=1.71 protocol 
+ ** Made by Yannick PLASSIARD <yan@mistigri.org>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ascii.h"
+#include "brldefs-eu.h"
+#include "eu_protocol.h"
+#include "eu_protocoldef.h"
+
+#define MAXIMUM_DISPLAY_SIZE 80
+
+#define COMMAND_KEY_ENTRY(k,n) KEY_ENTRY(CommandKeys, CMD, k, n)
+#define BRAILLE_KEY_ENTRY(k,n) KEY_ENTRY(BrailleKeys, BRL, k, n)
+
+BEGIN_KEY_NAME_TABLE(linear)
+  COMMAND_KEY_ENTRY(L1, "L1"),
+  COMMAND_KEY_ENTRY(L2, "L2"),
+  COMMAND_KEY_ENTRY(L3, "L3"),
+  COMMAND_KEY_ENTRY(L4, "L4"),
+  COMMAND_KEY_ENTRY(L5, "L5"),
+  COMMAND_KEY_ENTRY(L6, "L6"),
+  COMMAND_KEY_ENTRY(L7, "L7"),
+  COMMAND_KEY_ENTRY(L8, "L8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(arrow)
+  COMMAND_KEY_ENTRY(Left, "Left"),
+  COMMAND_KEY_ENTRY(Right, "Right"),
+  COMMAND_KEY_ENTRY(Up, "Up"),
+  COMMAND_KEY_ENTRY(Down, "Down"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch1)
+  COMMAND_KEY_ENTRY(Switch1Left, "Switch1Left"),
+  COMMAND_KEY_ENTRY(Switch1Right, "Switch1Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch2)
+  COMMAND_KEY_ENTRY(Switch2Left, "Switch2Left"),
+  COMMAND_KEY_ENTRY(Switch2Right, "Switch2Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch3)
+  COMMAND_KEY_ENTRY(Switch3Left, "Switch3Left"),
+  COMMAND_KEY_ENTRY(Switch3Right, "Switch3Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch4)
+  COMMAND_KEY_ENTRY(Switch4Left, "Switch4Left"),
+  COMMAND_KEY_ENTRY(Switch4Right, "Switch4Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch5)
+  COMMAND_KEY_ENTRY(Switch5Left, "Switch5Left"),
+  COMMAND_KEY_ENTRY(Switch5Right, "Switch5Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switch6)
+  COMMAND_KEY_ENTRY(Switch6Left, "Switch6Left"),
+  COMMAND_KEY_ENTRY(Switch6Right, "Switch6Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(joystick1)
+  COMMAND_KEY_ENTRY(LeftJoystickLeft, "LeftJoystickLeft"),
+  COMMAND_KEY_ENTRY(LeftJoystickRight, "LeftJoystickRight"),
+  COMMAND_KEY_ENTRY(LeftJoystickUp, "LeftJoystickUp"),
+  COMMAND_KEY_ENTRY(LeftJoystickDown, "LeftJoystickDown"),
+  COMMAND_KEY_ENTRY(LeftJoystickPress, "LeftJoystickPress"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(joystick2)
+  COMMAND_KEY_ENTRY(RightJoystickLeft, "RightJoystickLeft"),
+  COMMAND_KEY_ENTRY(RightJoystickRight, "RightJoystickRight"),
+  COMMAND_KEY_ENTRY(RightJoystickUp, "RightJoystickUp"),
+  COMMAND_KEY_ENTRY(RightJoystickDown, "RightJoystickDown"),
+  COMMAND_KEY_ENTRY(RightJoystickPress, "RightJoystickPress"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  BRAILLE_KEY_ENTRY(Dot1, "Dot1"),
+  BRAILLE_KEY_ENTRY(Dot2, "Dot2"),
+  BRAILLE_KEY_ENTRY(Dot3, "Dot3"),
+  BRAILLE_KEY_ENTRY(Dot4, "Dot4"),
+  BRAILLE_KEY_ENTRY(Dot5, "Dot5"),
+  BRAILLE_KEY_ENTRY(Dot6, "Dot6"),
+  BRAILLE_KEY_ENTRY(Dot7, "Dot7"),
+  BRAILLE_KEY_ENTRY(Dot8, "Dot8"),
+  BRAILLE_KEY_ENTRY(Backspace, "Backspace"),
+  BRAILLE_KEY_ENTRY(Space, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(EU_GRP_RoutingKeys1, "RoutingKey1"),
+  KEY_GROUP_ENTRY(EU_GRP_RoutingKeys2, "RoutingKey2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(iris)
+  KEY_NAME_TABLE(linear),
+  KEY_NAME_TABLE(arrow),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(esys_small)
+  KEY_NAME_TABLE(switch1),
+  KEY_NAME_TABLE(switch2),
+  KEY_NAME_TABLE(joystick1),
+  KEY_NAME_TABLE(joystick2),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(esys_medium)
+  KEY_NAME_TABLE(switch1),
+  KEY_NAME_TABLE(switch2),
+  KEY_NAME_TABLE(switch3),
+  KEY_NAME_TABLE(switch4),
+  KEY_NAME_TABLE(joystick1),
+  KEY_NAME_TABLE(joystick2),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(esys_large)
+  KEY_NAME_TABLE(switch1),
+  KEY_NAME_TABLE(switch2),
+  KEY_NAME_TABLE(switch3),
+  KEY_NAME_TABLE(switch4),
+  KEY_NAME_TABLE(switch5),
+  KEY_NAME_TABLE(switch6),
+  KEY_NAME_TABLE(joystick1),
+  KEY_NAME_TABLE(joystick2),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(esytime)
+  KEY_NAME_TABLE(joystick1),
+  KEY_NAME_TABLE(joystick2),
+  KEY_NAME_TABLE(linear),
+  KEY_NAME_TABLE(keyboard),   // For braille keyboard when not in usb-hid mode.
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+PUBLIC_KEY_TABLE(iris)
+PUBLIC_KEY_TABLE(esys_small)
+PUBLIC_KEY_TABLE(esys_medium)
+PUBLIC_KEY_TABLE(esys_large)
+PUBLIC_KEY_TABLE(esytime)
+
+typedef struct {
+  const char *modelName;
+  const KeyTableDefinition *keyTable;
+  unsigned char modelIdentifier;
+  unsigned char cellCount;
+  unsigned hasBrailleKeyboard:1;
+  unsigned hasAzertyKeyboard:1;
+  unsigned hasVisualDisplay:1;
+  unsigned hasOpticalBar:1;
+  unsigned isIris:1;
+  unsigned isEsys:1;
+  unsigned isEsytime:1;
+} ModelEntry;
+
+static const ModelEntry modelTable[] = {
+  { .modelIdentifier = EU_IRIS_20,
+    .modelName = "Iris 20",
+    .cellCount = 20,
+    .hasBrailleKeyboard = 1,
+    .hasVisualDisplay = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_IRIS_40,
+    .modelName = "Iris 40",
+    .cellCount = 40,
+    .hasBrailleKeyboard = 1,
+    .hasVisualDisplay = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_IRIS_S20,
+    .modelName = "Iris S-20",
+    .cellCount = 20,
+    .hasBrailleKeyboard = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_IRIS_S32,
+    .modelName = "Iris S-32",
+    .cellCount = 32,
+    .hasBrailleKeyboard = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_IRIS_KB20,
+    .modelName = "Iris KB-20",
+    .cellCount = 20,
+    .hasAzertyKeyboard = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_IRIS_KB40,
+    .modelName = "Iris KB-40",
+    .cellCount = 40,
+    .hasAzertyKeyboard = 1,
+    .isIris = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(iris)
+  },
+
+  { .modelIdentifier = EU_ESYS_12,
+    .modelName = "Esys 12",
+    .cellCount = 12,
+    .hasBrailleKeyboard = 1,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_small)
+  },
+
+  { .modelIdentifier = EU_ESYS_40,
+    .modelName = "Esys 40",
+    .cellCount = 40,
+    .hasBrailleKeyboard = 1,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_medium)
+  },
+
+  { .modelIdentifier = EU_ESYS_LIGHT_40,
+    .modelName = "Esys Light 40",
+    .cellCount = 40,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_medium)
+  },
+
+  { .modelIdentifier = EU_ESYS_24,
+    .modelName = "Esys 24",
+    .cellCount = 24,
+    .hasBrailleKeyboard = 1,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_small)
+  },
+
+  { .modelIdentifier = EU_ESYS_64,
+    .modelName = "Esys 64",
+    .cellCount = 64,
+    .hasBrailleKeyboard = 1,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_medium)
+  },
+
+  { .modelIdentifier = EU_ESYS_80,
+    .modelName = "Esys 80",
+    .cellCount = 80,
+    .hasBrailleKeyboard = 1,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_large)
+  },
+
+  { .modelIdentifier = EU_ESYS_LIGHT_80,
+    .modelName = "Esys Light 80",
+    .cellCount = 80,
+    .isEsys = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esys_large)
+  },
+
+  { .modelIdentifier = EU_ESYTIME_32,
+    .modelName = "Esytime 32",
+    .cellCount = 32,
+    .hasBrailleKeyboard = 1,
+    .hasOpticalBar = 1,
+    .isEsytime = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esytime)
+  },
+
+  { .modelIdentifier = EU_ESYTIME_32_STANDARD,
+    .modelName = "Esytime 32 Standard",
+    .cellCount = 32,
+    .hasBrailleKeyboard = 1,
+    .isEsytime = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esytime)
+  },
+
+  { .modelIdentifier = EU_ESYTIME_EVO,
+    .modelName = "Esytime Evolution",
+    .cellCount = 32,
+    .hasBrailleKeyboard = 1,
+    .hasOpticalBar = 1,
+    .isEsytime = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esytime)
+  },
+
+  { .modelIdentifier = EU_ESYTIME_EVO_STANDARD,
+    .modelName = "Esytime Evolution Standard",
+    .cellCount = 32,
+    .hasBrailleKeyboard = 1,
+    .isEsytime = 1,
+    .keyTable = &KEY_TABLE_DEFINITION(esytime)
+  },
+
+  { .modelName = NULL }
+};
+
+static int haveSystemInformation;
+static const ModelEntry *model;
+static uint32_t firmwareVersion;
+static uint32_t protocolVersion;
+static uint32_t deviceOptions;
+static uint16_t maximumFrameLength;
+
+static unsigned char forceWindowRewrite;
+static unsigned char forceVisualRewrite;
+static unsigned char forceCursorRewrite;
+
+static unsigned char sequenceCheck;
+static unsigned char sequenceKnown;
+static unsigned char sequenceNumber;
+
+static KeyNumberSet commandKeys;
+
+static inline void
+forceRewrite (void) {
+  forceWindowRewrite = 1;
+  forceVisualRewrite = 1;
+  forceCursorRewrite = 1;
+}
+
+static ssize_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  unsigned char *buffer = packet;
+  const unsigned char pad = 0X55;
+  unsigned int offset = 0;
+  unsigned int length = 3;
+
+  while (1)
+    {
+      int started = offset > 0;
+      unsigned char byte;
+
+      if (!io->readByte(brl, &byte, started))
+        {
+          if (started) logPartialPacket(buffer, offset);
+          return (errno == EAGAIN)? 0: -1;
+        }
+
+      switch (offset)
+        {
+          case 0: {
+            unsigned char sequence = sequenceCheck;
+            sequenceCheck = 0;
+
+            if (sequence && sequenceKnown) {
+              if (byte == ++sequenceNumber) continue;
+              logInputProblem("Unexpected Sequence Number", &byte, 1);
+              sequenceKnown = 0;
+            }
+
+            if (byte == pad) continue;
+            if (byte == ASCII_STX) break;
+
+            if (sequence && !sequenceKnown) {
+              sequenceNumber = byte;
+              sequenceKnown = 1;
+            } else {
+              logIgnoredByte(byte);
+            }
+
+            continue;
+          }
+
+          case 1:
+            if ((byte == pad) && !sequenceKnown) {
+              sequenceNumber = buffer[0];
+              sequenceKnown = 1;
+              offset = 0;
+              continue;
+            }
+            break;
+
+          case 2:
+            length = ((buffer[1] << 8) | byte) + 2;
+            break;
+
+          default:
+            break;
+        }
+
+      if (offset < size)
+        {
+          buffer[offset] = byte;
+        }
+      else
+        {
+          if (offset == length) logTruncatedPacket(buffer, offset);
+          logDiscardedByte(byte);
+        }
+
+      if (++offset == length)
+        {
+          if (byte != ASCII_ETX)
+            {
+              logCorruptPacket(buffer, offset);
+              offset = 0;
+              length = 3;
+              continue;
+            }
+
+          sequenceCheck = 1;
+          logInputPacket(buffer, offset);
+          return offset;
+        }
+    }
+}
+
+static ssize_t
+writePacket (BrailleDisplay *brl, const void *packet, size_t size) {
+  int packetSize = size + 2;
+  unsigned char buf[packetSize + 2];
+  if (!io || !packet || !size)
+    return (-1);
+  buf[0] = ASCII_STX;
+  buf[1] = (packetSize >> 8) & 0x00FF;
+  buf[2] = packetSize & 0x00FF;
+  memcpy(buf + 3, packet, size);
+  buf[sizeof(buf)-1] = ASCII_ETX;
+  logOutputPacket(buf, sizeof(buf));
+  return io->writeData(brl, buf, sizeof(buf));
+}
+
+static const ModelEntry *
+getModelEntry (unsigned char identifier) {
+  const ModelEntry *mdl = modelTable;
+
+  while (mdl->modelName) {
+    if (mdl->modelIdentifier == identifier) return mdl;
+    mdl += 1;
+  }
+
+  return NULL;
+}
+
+static int
+handleSystemInformation (BrailleDisplay *brl, unsigned char *packet) {
+  int logLevel = LOG_INFO;
+  const char *infoDescription;
+  enum {Unknown, End, String, Dec8, Dec16, Hex32} infoType;
+
+  switch(packet[0]) {
+    case LP_SYSTEM_SHORTNAME: 
+      infoType = String;
+      infoDescription = "Short Name";
+      break;
+
+    case LP_SYSTEM_IDENTITY: 
+      infoType = End;
+      break;
+
+    case LP_SYSTEM_DISPLAY_LENGTH: 
+      if (haveSystemInformation) brl->resizeRequired = 1;
+      brl->textColumns = packet[1];
+
+      infoType = Dec8;
+      infoDescription = "Cell Count";
+      break;
+
+    case LP_SYSTEM_LANGUAGE: 
+      infoType = String;
+      infoDescription = "Country Code";
+      break;
+
+    case LP_SYSTEM_FRAME_LENGTH: 
+      maximumFrameLength = (packet[1] << 8)
+                         | (packet[2] << 0)
+                         ;
+
+      infoType = Dec16;
+      infoDescription = "Maximum Frame Length";
+      break;
+
+    case LP_SYSTEM_NAME: 
+      infoType = String;
+      infoDescription = "Long Name";
+      break;
+
+    case LP_SYSTEM_OPTION: 
+      deviceOptions = (packet[1] << 24)
+                    | (packet[2] << 16)
+                    | (packet[3] <<  8)
+                    | (packet[4] <<  0)
+                    ;
+
+      infoType = Hex32;
+      infoDescription = "Device Options";
+      break;
+
+    case LP_SYSTEM_PROTOCOL: 
+      protocolVersion = ((packet[1] - '0') << 16)
+                      | ((packet[3] - '0') <<  8)
+                      | ((packet[4] - '0') <<  0)
+                      ;
+
+      infoType = String;
+      infoDescription = "Protocol Version";
+      break;
+
+    case LP_SYSTEM_SERIAL: 
+      infoType = String;
+      infoDescription = "Serial Number";
+      break;
+
+    case LP_SYSTEM_TYPE:
+      {
+        unsigned char identifier = packet[1];
+
+        if (!(model = getModelEntry(identifier))) {
+          logMessage(LOG_WARNING, "unknown EuroBraille model: 0X%02X", identifier);
+        }
+      }
+
+      infoType = Dec8;
+      infoDescription = "Model Identifier";
+      break;
+
+    case LP_SYSTEM_SOFTWARE: 
+      firmwareVersion = ((packet[1] - '0') << 16)
+                      | ((packet[3] - '0') <<  8)
+                      | ((packet[4] - '0') <<  0)
+                      ;
+
+      infoType = String;
+      infoDescription = "Firmware Version";
+      break;
+
+    default:
+      infoType = Unknown;
+      break;
+  }
+
+  switch (infoType) {
+    case Unknown:
+      logMessage(LOG_WARNING, "unknown Esysiris system information subcode: 0X%02X", packet[0]);
+      break;
+
+    case End:
+      logMessage(LOG_DEBUG, "end of Esysiris system information");
+      return 1;
+
+    case String:
+      logMessage(logLevel, "Esysiris %s: %s", infoDescription, &packet[1]);
+      break;
+
+    case Dec8:
+      logMessage(logLevel, "Esysiris %s: %u", infoDescription, packet[1]);
+      break;
+
+    case Dec16:
+      logMessage(logLevel, "Esysiris %s: %u", infoDescription, (packet[1] << 8) | packet[2]);
+      break;
+
+    case Hex32:
+      logMessage(logLevel, "Esysiris %s: 0X%02X%02X%02X%02X",
+                 infoDescription, packet[1], packet[2], packet[3], packet[4]);
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unimplemented Esysiris system information subcode type: 0X%02X", infoType);
+      break;
+  }
+
+  return 0;
+}
+
+static int
+makeKeyboardCommand (BrailleDisplay *brl, const unsigned char *packet) {
+  unsigned char a = packet[1];
+  unsigned char b = packet[2];
+  unsigned char c = packet[3];
+  unsigned char d = packet[4];
+  int command = 0;
+
+  switch (a) {
+    case 0:
+      switch (b) {
+        case 0:
+          command = BRL_CMD_BLK(PASSCHAR) | d;
+          break;
+
+        case ASCII_BS:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_BACKSPACE;
+          break;
+
+        case ASCII_HT:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_TAB;
+          break;
+
+        case ASCII_CR:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_ENTER;
+          break;
+
+        case ASCII_ESC:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_ESCAPE;
+          break;
+
+        case 0X20: // space
+          command = BRL_CMD_BLK(PASSCHAR) | b;
+          break;
+
+        default:
+          if ((b >= 0X70) && (b <= 0X7B)) {
+            command = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + (b - 0X70));
+          }
+          break;
+      }
+      break;
+
+    case 1:
+      switch (b) {
+        case 0X07:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_HOME;
+          break;
+
+        case 0X08:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_END;
+          break;
+
+        case 0X09:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_PAGE_UP;
+          break;
+
+        case 0X0A:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_PAGE_DOWN;
+          break;
+
+        case 0X0B:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_LEFT;
+          break;
+
+        case 0X0C:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_RIGHT;
+          break;
+
+        case 0X0D:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_UP;
+          break;
+
+        case 0X0E:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_DOWN;
+          break;
+
+        case 0X0F:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_INSERT;
+          break;
+
+        case 0X10:
+          command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_DELETE;
+          break;
+
+        default:
+          break;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  if (!command) return BRL_CMD_NOOP;
+
+  if (c & 0X02) command |= BRL_FLG_INPUT_CONTROL;
+  if (c & 0X04) command |= BRL_FLG_INPUT_META;
+  return command;
+}
+
+static int
+handleKeyEvent (BrailleDisplay *brl, unsigned char *packet) {
+  switch (packet[0]) {
+    case LP_KEY_BRAILLE: {
+      KeyNumberSet keys = ((packet[1] << 8) | packet[2]) & 0X3Ff;
+      enqueueKeys(brl, keys, EU_GRP_BrailleKeys, 0);
+      return 1;
+    }
+
+    case LP_KEY_INTERACTIVE: {
+      unsigned char key = packet[2];
+
+      if ((key > 0) && (key <= brl->textColumns)) {
+        key -= 1;
+
+        switch (packet[1]) {
+          case INTERACTIVE_SINGLE_CLIC: // single click
+            enqueueKey(brl, EU_GRP_RoutingKeys1, key);
+          case INTERACTIVE_REPETITION: // repeat
+            return 1;
+
+          case INTERACTIVE_DOUBLE_CLIC: // double click
+            enqueueKey(brl, EU_GRP_RoutingKeys2, key);
+            return 1;
+
+          default:
+            break;
+        }
+      }
+
+      break;
+    }
+
+    case LP_KEY_COMMAND: {
+      KeyNumberSet keys;
+
+      if (model->isIris) {
+        keys = ((packet[1] << 8) | packet[2]) & 0XFFF;
+      } else {
+        keys = (packet[1] << 24) + (packet[2] << 16) + (packet[3] << 8) + packet[4];
+      }
+
+      if (model->isIris) {
+        enqueueKeys(brl, keys, EU_GRP_CommandKeys, 0);
+      } else {
+        enqueueUpdatedKeys(brl, keys, &commandKeys, EU_GRP_CommandKeys, 0);
+      }
+
+      return 1;
+    }
+
+    case LP_KEY_PC: {
+      int command = makeKeyboardCommand(brl, packet);
+
+      enqueueCommand(command);
+      if (command != BRL_CMD_NOOP) return 1;
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+static int
+readCommand (BrailleDisplay *brl, KeyTableCommandContext ctx) {
+  unsigned char	packet[2048];
+  ssize_t length;
+
+  while ((length = readPacket(brl, packet, sizeof(packet))) > 0) {
+    switch (packet[3]) {
+      case LP_SYSTEM:
+        if (handleSystemInformation(brl, packet+4)) haveSystemInformation = 1;
+        continue;
+
+      case LP_KEY:
+        if (handleKeyEvent(brl, packet+4)) continue;
+        break;
+
+      case LP_MODE:
+        if (packet[4] == LP_MODE_PILOT) {
+          /* return from internal menu */
+          forceRewrite();
+        }
+        continue;
+
+      case LP_VISU:
+        /* ignore visualization */
+        continue;
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet, length);
+  }
+
+  return (length == -1)? BRL_CMD_RESTARTBRL: EOF;
+}
+
+static int
+initializeDevice (BrailleDisplay *brl) {
+  int retriesLeft = 2;
+      
+  haveSystemInformation = 0;
+  model = NULL;
+  firmwareVersion = 0;
+  protocolVersion = 0;
+  deviceOptions = 0;
+  maximumFrameLength = 0;
+
+  forceRewrite();
+  sequenceCheck = 0;
+  sequenceKnown = 0;
+
+  commandKeys = 0;
+
+  do {
+    {
+      static const unsigned char packet[] = {LP_SYSTEM, LP_SYSTEM_IDENTITY};
+      if (writePacket(brl, packet, sizeof(packet)) == -1) return 0;
+    }
+
+    while (io->awaitInput(brl, 500)) {
+      if (readCommand(brl, KTB_CTX_DEFAULT) == BRL_CMD_RESTARTBRL) return 0;
+
+      if (haveSystemInformation) {
+        if (!model) return 0;
+        setBrailleKeyTable(brl, model->keyTable);
+
+        if (!maximumFrameLength) {
+          if (model->isIris) maximumFrameLength = 2048;
+          if (model->isEsys) maximumFrameLength = 128;
+          if (model->isEsytime) maximumFrameLength = 512;
+        }
+
+        logMessage(LOG_INFO, "Model Detected: %s (%u cells)",
+                   model->modelName, brl->textColumns);
+        return 1;
+      }
+    }
+  } while (retriesLeft-- && (errno == EAGAIN));
+
+  return 0;
+}
+
+static int
+resetDevice (BrailleDisplay *brl) {
+  return 0;
+}
+
+static int
+writeWindow (BrailleDisplay *brl) {
+  static unsigned char previousCells[MAXIMUM_DISPLAY_SIZE];
+  unsigned int size = brl->textColumns * brl->textRows;
+  
+  if (cellsHaveChanged(previousCells, brl->buffer, size, NULL, NULL, &forceWindowRewrite)) {
+    unsigned char data[size + 2];
+    unsigned char *byte = data;
+
+    *byte++ = LP_BRAILLE_DISPLAY;
+    *byte++ = LP_BRAILLE_DISPLAY_STATIC;
+    byte = translateOutputCells(byte, brl->buffer, size);
+
+    if (writePacket(brl, data, byte-data) == -1) return 0;
+  }
+
+  return 1;
+}
+
+static int
+hasVisualDisplay (BrailleDisplay *brl) {
+  return model->hasVisualDisplay;
+}
+
+static int
+writeVisual (BrailleDisplay *brl, const wchar_t *text) {
+  if (model->hasVisualDisplay) {
+    {
+      static wchar_t previousText[MAXIMUM_DISPLAY_SIZE];
+      unsigned int size = brl->textColumns * brl->textRows;
+      
+      if (textHasChanged(previousText, text, size, NULL, NULL, &forceVisualRewrite)) {
+        unsigned char data[size + 2];
+        unsigned char *byte = data;
+
+        *byte++ = LP_LCD_DISPLAY;
+        *byte++ = LP_LCD_DISPLAY_TEXT;
+
+        {
+          const wchar_t *character = text;
+          const wchar_t *end = character + size;
+
+          while (character < end) {
+            *byte++ = iswLatin1(*character)? *character: '?';
+            character += 1;
+          }
+        }
+
+        if (writePacket(brl, data, byte-data) == -1) return 0;
+      }
+    }
+
+    {
+      static int previousCursor;
+
+      if (cursorHasChanged(&previousCursor, brl->cursor, &forceCursorRewrite )) {
+        const unsigned char packet[] = {
+          LP_LCD_DISPLAY, LP_LCD_DISPLAY_CARET, ((brl->cursor != BRL_NO_CURSOR)? (brl->cursor + 1): 0)
+        };
+
+        if (writePacket(brl, packet, sizeof(packet)) == -1) return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+const ProtocolOperations esysirisProtocolOperations = {
+  .protocolName = "esysiris",
+
+  .initializeDevice = initializeDevice,
+  .resetDevice = resetDevice,
+
+  .readPacket = readPacket,
+  .writePacket = writePacket,
+
+  .readCommand = readCommand,
+  .writeWindow = writeWindow,
+
+  .hasVisualDisplay = hasVisualDisplay,
+  .writeVisual = writeVisual
+};
diff --git a/Drivers/Braille/EuroBraille/eu_protocol.h b/Drivers/Braille/EuroBraille/eu_protocol.h
new file mode 100644
index 0000000..afbe076
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eu_protocol.h
@@ -0,0 +1,72 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** EuroBraille/eu_protocol.h -- Protocol defines, structures and unions
+ ** This file contains all definitions about the two protocols.
+ ** It is also used to store protocol method headers and the main structure
+ ** to access the terminal generically by the High-level part.
+ **
+ ** See braille.c for the high-level access, and io.h for the low-level access.
+**/
+
+
+#ifndef __EU_PROTOCOL_H__
+#define __EU_PROTOCOL_H__
+
+#include "cmd_enqueue.h"
+#include "ktb_types.h"
+#include "brl_cmds.h"
+#include "brl_utils.h"
+#include "brl_base.h"
+
+typedef struct {
+  const char *protocolName;
+
+  int (*initializeDevice) (BrailleDisplay *brl);
+  int (*resetDevice) (BrailleDisplay *brl);
+
+  ssize_t (*readPacket) (BrailleDisplay *brl, void *packet, size_t size);
+  ssize_t (*writePacket) (BrailleDisplay *brl, const void *packet, size_t size);
+
+  int (*readCommand) (BrailleDisplay *brl, KeyTableCommandContext c);
+  int (*writeWindow) (BrailleDisplay *brl);
+
+  int (*hasVisualDisplay) (BrailleDisplay *brl);
+  int (*writeVisual) (BrailleDisplay *brl, const wchar_t *text);
+} ProtocolOperations;
+
+typedef struct {
+  const ProtocolOperations *protocol;
+  int (*awaitInput) (BrailleDisplay *brl, int timeout);
+  int (*readByte) (BrailleDisplay *brl, unsigned char *byte, int wait);
+  ssize_t (*writeData) (BrailleDisplay *brl, const void *data, size_t size);
+} InputOutputOperations;
+
+extern const InputOutputOperations *io;
+extern const ProtocolOperations clioProtocolOperations;
+extern const ProtocolOperations esysirisProtocolOperations;
+
+#define KEY_ENTRY(s,t,k,n) {.value = {.group=EU_GRP_##s, .number=EU_##t##_##k}, .name=n}
+EXTERNAL_KEY_TABLE(clio)
+EXTERNAL_KEY_TABLE(iris)
+EXTERNAL_KEY_TABLE(esys_small)
+EXTERNAL_KEY_TABLE(esys_medium)
+EXTERNAL_KEY_TABLE(esys_large)
+EXTERNAL_KEY_TABLE(esytime)
+
+#endif /* __EU_PROTOCOL_H__ */
diff --git a/Drivers/Braille/EuroBraille/eu_protocoldef.h b/Drivers/Braille/EuroBraille/eu_protocoldef.h
new file mode 100644
index 0000000..2bc7249
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eu_protocoldef.h
@@ -0,0 +1,178 @@
+
+/////////////////////////////////////////////////////////////////
+// LINK PROTOCOL - KEY
+    // this key is operational only if subkey is 'B','D' or 'B','S' (LP_BRAILLE_DISPLAY,LP_BRAILLE_DISPLAY_STATIC/LP_BRAILLE_DISPLAY_DYNAMIC
+#define LP_SPECIFIC_PROTOCOL '1'
+
+#define LP_SYSTEM           'S'
+#define LP_MODE             'R'
+#define LP_KEY              'K'
+#define LP_PARAMETER        'P'		//specific esytime2
+#define LP_TRANSFERT        't'		// Specific Esytime.
+#define LP_ENCRYPTION_KEY   'Z'
+#define LP_BRAILLE_DISPLAY  'B'
+#define LP_LCD_DISPLAY      'L'
+//#define LP_TEST             'T'
+//#define LP_SOUND            'M'
+//#define LP_DEBUG            'D'      // Trame de Débug
+#define LP_VISU             'V'      // Trame de Visualisation
+
+// LINK PROTOCOL LP_ENCRYPTION_KEY
+#define LP_ENCRYPTION_KEY_1 '1'
+#define LP_ENCRYPTION_KEY_2 '2'
+#define LP_ENCRYPTION_KEY_3 '3'
+#define LP_EXT_KEY          'X'
+#define LP_END_KEY          'E'
+
+// LINK PROTOCOL KEYBOARD - SUBKey
+#define LP_KEY_INTERACTIVE   'I'
+#define LP_KEY_COMMAND       'C'
+#define LP_KEY_OPTICAL       'O'
+#define LP_KEY_BRAILLE       'B'
+#define LP_KEY_PC            'Z'
+#define LP_KEY_FINGER        'F'
+#define LP_KEY_USB_HID_MODE  'U'
+#define LP_KEY_USB			 'u'		// Trame touche à générer via l'USB (Spécific Esytouch)
+
+
+// LINK PROTOCOL BRAILLEDISPLAY - SUBKEY
+#define LP_BRAILLE_DISPLAY_STATIC       'S'
+#define LP_BRAILLE_DISPLAY_DYNAMIC      'C'
+#define LP_BRAILLE_DISPLAY_BLINK        'B'
+//#define LP_BRAILLE_DISPLAY_DEBUG_TEXT     'X'
+
+// LINK PROTOCOL BRAILLEDISPLAY - SUBKEY
+#define LP_LCD_DISPLAY_TEXT         'T'
+#define LP_LCD_DISPLAY_CARET        'C'
+
+// LINK PROTOCOL SYSTEM - SUBKEY
+#define LP_SYSTEM_IDENTITY          'I'
+#define LP_SYSTEM_NAME              'N'
+#define LP_SYSTEM_SHORTNAME         'H'
+#define LP_SYSTEM_SERIAL            'S'
+#define LP_SYSTEM_LANGUAGE          'L'
+#define LP_SYSTEM_LANGUAGE_OPTION   'l'
+#define LP_SYSTEM_BATTERY           'B'
+#define LP_SYSTEM_DISPLAY_LENGTH    'G'
+#define LP_SYSTEM_TYPE              'T'
+#define LP_SYSTEM_OPTION            'O'
+#define LP_SYSTEM_SOFTWARE          'W'
+#define LP_SYSTEM_PROTOCOL          'P'
+#define LP_SYSTEM_FRAME_LENGTH      'M'
+#define LP_SYSTEM_DATE_AND_TIME		'D'
+#define LP_SYSTEM_OPTICAL_VALUE     'o'
+
+// LINK PROTOCOL PARAMETERS - SUBKEY
+#define LP_PARAMETER_NAME			'N'
+#define LP_PARAMETER_SHORT_NAME		'S'
+#define LP_PARAMETER_SERIAL			'R'
+#define LP_PARAMETER_OPTION			'O'
+
+
+// LINK PROTOCOL TEST - SUBKEY
+#define LP_TEST_LINK        'L'
+
+// LINK PROTOCOL MODE - SUBKEY
+#define LP_MODE_PILOT       'P'
+#define LP_MODE_INTERNAL    'I'
+#define LP_MODE_MENU        'M'
+#define LP_MODE_SPECIFIC_PROTOCOL 'S'
+
+// LINK PROTOCOL SOUND - SUBKEY
+#define LP_SOUND_PLAY       'P'
+#define LP_SOUND_STOP       'A'
+
+// LINK PROTOCOL DEBUG - SUBKey
+#define LP_DEBUG_SIMU       'S'     // FIX ME à supprimer dans quelques jours...
+#define LP_DEBUG_TRACE      'T'     // FIX ME à supprimer dans quelques jours...
+#define LP_DEBUG_LOG        'L'     
+#define LP_DEBUG_WARN       'W'     
+#define LP_DEBUG_ERROR      'E'     
+
+// LINK PROTOCOL VISU - SUBKey
+#define LP_VISU_TEXT        'T'     
+#define LP_VISU_DOT         'D'     
+
+// LINK PROTOCOL TRANSFERT - SUBKey
+#define LP_TRANSFERT_PARAMETERS		'p' // Request to receive or send the assignment table.
+//#define LP_TRANSFERT_TABLE          't' // Request to receive or send the assignment table.
+#define LP_TRANSFERT_FIRMWARE       'f' // Request to send the assignment table.
+#define LP_TRANSFERT_RECORD         'r'	// A record in Intel Hexa format.
+#define LP_TRANSFERT_NEXT_RECORD    'n' // Request to receive the next record.
+#define LP_TRANSFERT_ASK_CHECKSUM   'c' // Ask the checksum on complete hex file.
+#define LP_TRANSFERT_END            'e' // Notify the end of transfert.
+#define LP_TRANSFERT_ERROR          'o' // Error
+
+/////////////////////////////////////////////////////////////////
+// TYPE OF INTERACTIVE KEYS
+#define INTERACTIVE_SINGLE_CLIC	0x01
+#define INTERACTIVE_REPETITION	0x02
+#define INTERACTIVE_DOUBLE_CLIC 0x03
+
+/////////////////////////////////////////////////////////////////
+// IRIS COMMANDS KEYBOARD
+typedef enum {
+    IRIS_L1_KEY        = 0x00000001,
+    IRIS_L2_KEY        = 0x00000002,        
+    IRIS_L3_KEY        = 0x00000004,
+    IRIS_L4_KEY        = 0x00000008,
+    IRIS_L5_KEY        = 0x00000010,
+    IRIS_L6_KEY        = 0x00000020,
+    IRIS_L7_KEY        = 0x00000040,
+    IRIS_L8_KEY        = 0x00000080,
+    IRIS_UP_KEY        = 0x00000100,
+    IRIS_LEFT_KEY      = 0x00000800,
+    IRIS_RIGHT_KEY     = 0x00000400,
+    IRIS_DOWN_KEY      = 0x00000200,
+} IRIS_COMMAND, * PIRIS_COMMAND;
+
+/////////////////////////////////////////////////////////////////
+// ESYS COMMANDS KEYBOARD
+typedef enum {
+    ESYS_SCROLLER1_RIGHT_KEY        = 0x00000001,
+    ESYS_SCROLLER1_LEFT_KEY         = 0x00000002,        
+    ESYS_SCROLLER2_RIGHT_KEY        = 0x00000004,
+    ESYS_SCROLLER2_LEFT_KEY         = 0x00000008,
+    ESYS_SCROLLER3_RIGHT_KEY        = 0x00000010,
+    ESYS_SCROLLER3_LEFT_KEY         = 0x00000020,
+    ESYS_SCROLLER4_RIGHT_KEY        = 0x00000040,
+    ESYS_SCROLLER4_LEFT_KEY         = 0x00000080,
+    ESYS_SCROLLER5_RIGHT_KEY        = 0x00000100,
+    ESYS_SCROLLER5_LEFT_KEY         = 0x00000200,
+    ESYS_SCROLLER6_RIGHT_KEY        = 0x00000400,
+    ESYS_SCROLLER6_LEFT_KEY         = 0x00000800,
+    ESYS_JOYSTICK1_UP_KEY           = 0x00010000,
+    ESYS_JOYSTICK1_DOWN_KEY         = 0x00020000,
+    ESYS_JOYSTICK1_RIGHT_KEY        = 0x00040000,
+    ESYS_JOYSTICK1_LEFT_KEY         = 0x00080000,
+    ESYS_JOYSTICK1_MIDDLE_KEY       = 0x00100000,    
+    ESYS_JOYSTICK2_UP_KEY           = 0x01000000,
+    ESYS_JOYSTICK2_DOWN_KEY         = 0x02000000,
+    ESYS_JOYSTICK2_RIGHT_KEY        = 0x04000000,
+    ESYS_JOYSTICK2_LEFT_KEY         = 0x08000000,
+    ESYS_JOYSTICK2_MIDDLE_KEY       = 0x10000000
+} ESYS_COMMAND, * PESYS_COMMAND;
+
+/////////////////////////////////////////////////////////////////
+// ESYTIME COMMANDS KEYBOARD
+typedef enum {
+    ESYTIME_L1_KEY                     = 0x00000001,
+    ESYTIME_L2_KEY                     = 0x00000002,        
+    ESYTIME_L3_KEY                     = 0x00000004,
+    ESYTIME_L4_KEY                     = 0x00000008,
+    ESYTIME_L5_KEY                     = 0x00000010,
+    ESYTIME_L6_KEY                     = 0x00000020,
+    ESYTIME_L7_KEY                     = 0x00000040,
+    ESYTIME_L8_KEY                     = 0x00000080,
+    ESYTIME_JOYSTICK1_UP_KEY           = 0x00010000,
+    ESYTIME_JOYSTICK1_DOWN_KEY         = 0x00020000,
+    ESYTIME_JOYSTICK1_RIGHT_KEY        = 0x00040000,
+    ESYTIME_JOYSTICK1_LEFT_KEY         = 0x00080000,
+    ESYTIME_JOYSTICK1_MIDDLE_KEY       = 0x00100000,    
+    ESYTIME_JOYSTICK2_UP_KEY           = 0x01000000,
+    ESYTIME_JOYSTICK2_DOWN_KEY         = 0x02000000,
+    ESYTIME_JOYSTICK2_RIGHT_KEY        = 0x04000000,
+    ESYTIME_JOYSTICK2_LEFT_KEY         = 0x08000000,
+    ESYTIME_JOYSTICK2_MIDDLE_KEY       = 0x10000000
+} ESYTIME_COMMAND, * PESYSTIME_COMMAND;
+
diff --git a/Drivers/Braille/EuroBraille/eutp.1 b/Drivers/Braille/EuroBraille/eutp.1
new file mode 100644
index 0000000..1a9b103
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp.1
@@ -0,0 +1,35 @@
+.TH "EUTP" "1" "2013-11-13" "BrlAPI" "BrlAPI User's Manual"
+.SH NAME
+eutp \- EuroBraille file transferring
+.SH SYNOPSIS
+eutp
+.SH DESCRIPTION
+\fIeutp\fP lets you exchange files with
+a Clio terminal from EuroBraile.
+
+.SH "COMMAND\-LINE OPTIONS"
+
+None so far
+
+.SH RETURNED VALUE
+
+.TS
+lB lfCW.
+1	error
+.TE
+
+.SH SHELL EXPANSIONS
+Beware of special chars: * and . are often expanded by your shell, hence
+\fIsending files *\fR will probably do what you want, putting every file existing in
+the current directory onto the terminal, but \fI receiving files *\fR may not do what you
+want: it will only get every file which already exist in the current
+directory, skipping those you just created on your terminal !
+If you want to get every file which exist in the terminal, you
+should use \fI'*'\fR or something similar (please read your shell manual).
+
+The same warning applies to other special chars, such as $, ~, &,... which
+should be protected by surrounding arguments by quotes (') or by using single
+backslashes (\\) just before them (please read your shell manual).
+
+.SH AUTHOR
+Olivier Bert <olivier.bert@laposte.net>
diff --git a/Drivers/Braille/EuroBraille/eutp_brl.c b/Drivers/Braille/EuroBraille/eutp_brl.c
new file mode 100644
index 0000000..2b346f9
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_brl.c
@@ -0,0 +1,322 @@
+/*
+** brl.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+*/
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500
+#endif /* _XOPEN_SOURCE */
+
+/* globals */
+unsigned char extensions[] = {'K', 'L', 'B', 'T', 'A'};
+unsigned char positions[] = {3, 7, 16};
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include "brlapi.h"
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "eutp_brl.h"
+#include "eutp_pc.h"
+#include "eutp_tools.h"
+#include "eutp_transfer.h"
+
+void		eutp_abort(int exitstatus)
+{
+  brlapi_leaveRawMode();
+  brlapi_closeConnection();
+  exit(exitstatus);
+}
+
+/*
+** Asks a Yes/No question.
+** If the user says Yes (key # pressed) the function returns 1.
+** IF the answer is No (key * pressed) the function return 0.
+*/
+int		brl_yesno_question(char *prompt)
+{
+  unsigned char buf[BUFFER_SIZE];
+
+  brl_message(prompt, 0);
+  while (1)
+    {
+      brl_read(buf);
+      if (!strncmp((char*)buf, "\003KT#", 4))
+	return 1;
+      if (!strncmp((char*)buf, "\003KT*", 4))
+	return 0;
+	}
+}
+/*
+** Read raw data from the terminal
+*/
+ssize_t		brl_read(unsigned char *buf)
+{
+  ssize_t res;
+
+  while (1)
+    {
+      alarm(20);
+      res = brlapi_recvRaw(buf, BUFFER_SIZE);
+      alarm(0);
+      if (res < 0)
+	{
+	  brlapi_perror("reading on terminal");
+	  eutp_abort(E_READ);
+	  return 0;
+	}
+      if (res)
+	break;
+    }
+  return res;
+}
+
+ssize_t		brl_write(unsigned char *str, size_t len)
+{
+  ssize_t res;
+
+  alarm(20);
+  res = brlapi_sendRaw(str, len);
+  alarm(0);
+  if (res < 0)
+    {
+      brlapi_perror("Error writing to the terminal");
+      eutp_abort(E_WRITE);
+      return 0;
+    }
+  return res;
+}
+
+/*
+** Get model identification
+** ident must be a buffer of at least 20 bytes.
+*/
+void		get_ident(char* ident)
+{
+  char		buf[256];
+  char *p;
+  unsigned char ln = 0;
+
+  brl_writeStr("SI");
+  brl_read((unsigned char*)buf);
+  p = buf;
+  while (1)
+    {
+      ln = *(p++);
+      if (ln == 22 && !strncmp(p, "SI", 2))
+	{
+	  memcpy(ident, p + 2, 20);
+	  break;
+	}
+      else
+	p += ln;
+    }
+}
+
+/*
+** Write a string to the terminal
+** The string is supposed terminated by a \0 character
+*/
+void	brl_writeStr(char *str)
+{
+  brl_write((unsigned char*)str, strlen(str));
+}
+
+/*
+** Initialize the application and connect to brlapi
+** Init brlapi raw mode and print a welcome message on the terminal
+*/
+int		brl_init(t_env *env)
+{
+  int res;
+  char p[100];
+  unsigned int x, y;
+
+  /* Connect to BrlAPI */
+  if (brlapi_initializeConnection(NULL, NULL) < 0)
+    {
+      brlapi_perror("brlapi_initializeConnection");
+      return -1;
+    }
+
+  /* Get driver id & name */
+
+  res = brlapi_getDriverName(p, sizeof(p));
+  if (res == -1)
+    brlapi_perror("brlapi_getDriverName");
+  else
+    printf("Driver name: %s\n",p);
+
+  /* Get display size */
+  if (brlapi_getDisplaySize(&x, &y) < 0)
+    brlapi_perror("brlapi_getDisplaySize");
+  else
+    printf("Braille display has %d line%s of %d column%s\n",y,y>1?"s":"",x,x>1?"s":"");
+
+  /* Try entering raw mode, immediately go out from raw mode */
+  printf("Trying to enter in raw mode... ");
+  if (brlapi_enterRawMode("EuroBraille") < 0)
+    brlapi_perror("brlapi_getRaw");
+  else {
+    printf("Ok\n");
+  }
+  /* welcome message */
+  brl_lasting_message(EUTP_VERSION);
+  get_ident(env->ident);
+  printf("Identification: %20s\n", env->ident);
+  return 0;
+}
+
+/*
+** Closes the connection to BRLAPI
+*/
+int		brl_close(void)
+{
+  brlapi_leaveRawMode();
+  brlapi_closeConnection();
+  return 0;
+}
+
+/*
+** Displays a message to the braille terminal
+** The cursor can be positionned with the second argument
+*/
+int		brl_message(char *str, unsigned char cursorpos)
+{
+  int		ret = 0;
+  unsigned int len = strlen((char*)str);
+  char*	q = str;
+  unsigned char		i = 1;
+  unsigned char* buffer = malloc(512);
+  unsigned char*	p = buffer;
+
+  *p++ = 'D';
+  *p++ = 'M';
+  while (*q)
+    {
+      if (cursorpos >= 1 && i == cursorpos) {
+	*p++ = '\x1b';
+	*p++ = '\x02';
+      }
+      *p++ = *q++;
+      i++;
+    }
+  brl_write(buffer, cursorpos == 0 ? len + 2 : len + 4);
+  free(buffer);
+  return ret;
+}
+
+
+static int		showbrfile(t_env* env)
+{
+  unsigned char		cursorpos = positions[env->status];
+  unsigned char		ext = extensions[env->curext];
+  unsigned char* buf = malloc(256);
+  unsigned char* str = malloc(256); /* the string to display */
+
+  buf[0] = '\005';
+  buf[1] = 'F';
+  buf[2] = 'N';
+  buf[3] = ext;
+  buf[4] = (env->brfilenum & 0xff00) >> 2;
+  buf[5] = env->brfilenum & 0x00ff;
+  brl_write(buf+1, 5);
+  while (1)
+    {
+      brl_read(buf);
+      if (!strncmp((char*)buf, "\003KT", 3))
+	continue;
+      else
+	break;
+    }
+  if (!strncmp((char*)buf, "\003FE", 3))
+    {
+      env->brfilenum--;
+      return 0;
+    }
+  strcpy((char*)str, env->brpc ? "BR>PC " : "PC>BR ");
+  strncat((char*)str, ((char*)&(buf[6])), buf[0] - 5);
+  strcat((char*)str, ".");
+  strncat((char*)str, (char*)&ext, 1);
+  brl_message((char*)str, cursorpos);
+  free(buf);
+  free(str);
+  return 0;
+}
+
+
+
+/*
+** Show the list of files either of the braille terminal or of the PC
+** It is the main loop of the program.
+*/
+int		brl_listfiles(t_env* env)
+{
+  unsigned char		end = 0;
+  unsigned char*	buf = malloc(256);
+
+  env->curext = 0;
+  env->brpc = 1;
+  env->brfilenum = 1;
+  env->pcfilenum = 0;
+  env->status = 1;
+  while (!end)
+    {
+      if (env->brpc == 1)
+	showbrfile(env);
+      else
+	showpcfiles(env);
+      brl_read(buf);
+      if (!strncmp("\003KT*", (char*)buf, 4))
+	end = 1;
+      if (!strncmp("\003KT4", (char*)buf, 4) && env->status)
+	env->status--;
+      if (!strncmp("\003KT6", (char*)buf, 4) &&
+	  ((env->status != 2 && env->brpc)
+	   || (!env->brpc && env->status != 1)))
+	env->status++;
+      if (!strncmp("\003KT8", (char*)buf, 4))
+	{
+	  if (env->status == 0)
+	    env->brpc = !env->brpc;
+	  if (env->status == 1 && env->brpc)
+	    env->brfilenum++;
+	  if (env->status == 1 && !env->brpc && env->pcfilenum < env->n - 1)
+	    env->pcfilenum++;
+	  if (env->status == 2 && env->curext < MAXENT - 1)
+	    env->curext++, env->brfilenum = 1;
+	}
+      if (!strncmp("\003KT2", (char*)buf, 4))
+	{
+	  if (env->status == 0)
+	    env->brpc = !env->brpc;
+	  if (env->status == 1 && env->brfilenum > 1 && env->brpc)
+	    env->brfilenum--;
+	  if (env->status == 1 && env->pcfilenum > 0)
+	    env->pcfilenum--;
+	  if (env->status == 2 && env->curext > 0)
+	    env->curext--, env->brfilenum = 1;
+	}
+      if (!strncmp("\003KT#", (char*)buf, 4))
+	{
+	  if (env->brpc) {
+	    if (!brtopc(env))
+	      end = 1;
+	  }
+	  else
+	    {
+	      if (!pctobr(env))
+		end = 1;
+	    }
+	}
+    }
+  free(buf);
+  return 0;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_brl.h b/Drivers/Braille/EuroBraille/eutp_brl.h
new file mode 100644
index 0000000..cb0468f
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_brl.h
@@ -0,0 +1,86 @@
+/*
+** eutp_brl.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Wed Mar 16 18:41:29 2005 Olivier BERT
+Last update Thu Jun  7 18:05:45 2007 Olivier BERT
+*/
+
+#ifndef __EUTP_BRL_H_
+# define __EUTP_BRL_H_
+
+
+#include <iconv.h>
+#include <stdio.h>
+#include <string.h>
+#include "brlapi.h"
+
+/* EUTP version number */
+# define EUTP_VERSION	"EUTP 0.2.6"
+
+/* Exit codes */
+# define E_OK		0
+# define E_BRLAPI_ERROR	3
+# define E_READ		4
+# define E_WRITE	5
+
+/* some defines */
+# define HEADER_LINE "\x0cK/CP8 5.08-0C 1 16 FU \x1bi\x1b$"
+# define RULE_LINE "\x0bR 25,80,T8,16,24,32,40,48,56,64,72\x1BP\x1B$"
+# define READ_LINE	"FR"
+# define CLOSE_FILE	"FC"
+
+# define BUFFER_SIZE		500
+# define MAXENT		5
+
+/*
+** A structure that contains all informations about the file transfer */
+typedef struct
+{
+  /* the braille terminal selected file */
+  unsigned short brfilenum;
+  unsigned short pcfilenum;
+  /* the file extension index we are searching for on the braille terminal
+  ** It is a number between 0 and 3. 0 = 'K' 1 = 'L' etc... */
+  unsigned char		curextnum;
+  /* L'extension du fichier : K, L, B, T, A. Ce champ est utilisé
+     que pour le transfert pc->br. Concrètement, on pourrait se passer
+     de cette variable ou de celle du dessus */
+  unsigned char	curext;
+  /* cette variable dit si on est en train de changer le type de transfert (br->pc ou pc->br,
+     si on change l'extension ou si on change le nom du fichier. */
+  unsigned char		status;
+  /* Cette variable dit si on est en train de transférer du terminal vers le pc ou pas */
+  unsigned char		brpc;
+  /* Le nombre de fichiers sur le répertoire PC courant */
+  int		n;
+  /* Liste des fichiers PC dans le répertoire courant */
+  struct dirent**		list;
+  /* Pour le transfert pc->br, un descripteur de fichier pointant sur le fichier à transférer */
+  int		fd;
+  /* Stream associated with fd */
+  FILE*		fs;
+  /* nom du fichier à transférer */
+  char	filename[9];
+  /* convertisseurs pour les caractères */
+  iconv_t	dos2unix;
+  iconv_t	unix2dos;
+  char		ident[20];
+} t_env;
+
+
+int		brl_init(t_env *);
+int		brl_close(void);
+int             brl_message(char *str, unsigned char cursorpos);
+int             brl_yesno_question(char *str);
+int		brl_listfiles(t_env*);
+
+
+void		eutp_abort(int exitstatus);
+ssize_t		brl_read(unsigned char *);
+ssize_t		brl_write(unsigned char *, size_t len);
+void	brl_writeStr(char *str);
+
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_convert.c b/Drivers/Braille/EuroBraille/eutp_convert.c
new file mode 100644
index 0000000..e1414c2
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_convert.c
@@ -0,0 +1,202 @@
+/*
+** convert.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by
+** Login   <obert01@epita.fr>
+**
+** Started on  Thu Mar 31 17:11:59 2005
+Last update Wed Jun  6 13:03:41 2007 Olivier BERT
+*/
+
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <ctype.h>
+#include "brlapi.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <langinfo.h>
+#include <locale.h>
+#include "eutp_brl.h"
+
+
+int	       convert_init(t_env* env)
+{
+  setlocale(LC_ALL, "");
+  env->dos2unix = iconv_open(nl_langinfo(CODESET), "CP850");
+  env->unix2dos = iconv_open("CP850//translit", nl_langinfo(CODESET));
+  return 0;
+}
+
+size_t		dos2unix(t_env* env, char** map, size_t size)
+{
+  char *tmpmap, *tmpmap2;
+  char *p, *p2;
+  size_t i = size;
+  size_t o = 2 *size;
+  size_t newsize = 0;
+
+  if ((tmpmap = malloc(size)) == NULL)
+    {
+      perror("malloc");
+      eutp_abort(9);
+    }
+  if ((tmpmap2 = malloc(2 * size)) == NULL)
+    {
+      perror("malloc");
+      eutp_abort(9);
+    }
+  for (i = 0; i < size; i++)
+    tmpmap[i] = (*map)[i];
+  p = tmpmap;
+  p2 = tmpmap2;
+  iconv(env->dos2unix, &p, &i, &p2, &o);
+  newsize = (2 * size) - o;
+  *map = realloc(*map, newsize);
+  for (i = 0; i < newsize; i++)
+    {
+      (*map)[i] = tmpmap2[i];
+    }
+  free(tmpmap);
+  free(tmpmap2);
+  return newsize;
+}
+
+int		k2txt(t_env* env, char* srcfile, char* destfile)
+{
+  struct stat st;
+  int		fd;
+  size_t	size;
+  char	*map = 0; 
+  char	*newmap = 0;
+  unsigned int		i = 0, o = 0;
+  char		flg_hdr = 1;
+
+  if ((fd = open(srcfile, O_RDONLY)) == -1)
+    {
+      perror("open");
+      return 0;
+    }
+  fstat(fd, &st);
+  size = st.st_size;
+  map = malloc(size + 1);
+  newmap = malloc(size + 1);
+  read(fd, map, size);
+  map[size] = 0;
+  close(fd);
+  if ((fd = open(destfile, O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
+    {
+      perror("open");
+      return 0;
+    }
+  while (flg_hdr)
+    {
+      if (map[i] == 'R' && map[ i - 1] == '\x0b')
+	flg_hdr = 2;
+      if (flg_hdr == 2 && map[i] == '$' && map[i - 1] == '\x1b')
+	{
+	  i++;
+	  flg_hdr = 0;
+	  break;
+	}
+      i++;
+    }
+  while (i < size -  3) 
+    /* the 3 last characters are specific to the K format */
+    {
+      if (map[i] == 'P' && map[i - 1] == '\x1b')
+	newmap[o++] = '\n';
+      if (map[i] == 'L' && map[i - 1] == '\x1b')
+	newmap[o++] = '\n';
+      if (map[i] == '\x1b' && map[i - 1] == '\x1b')
+	newmap[o++] = '\x1b';
+      if (map[i] != '\x1b' && map[i - 1] != '\x1b')
+	newmap[o++] = map[i];
+      i++;
+    }
+  /* Convert text into the current charset */
+  size = dos2unix(env, &newmap, o);
+  write(fd, newmap, size);
+  free(map);
+  free(newmap);
+  close(fd);
+  return 1;
+}
+
+/*
+Cette fonction prend le nom du fichier PC (.txt ou .k ou autre) et le
+met sous la forme xxxxxxxx (8 lettres) et exhibe l'extension.
+Elle retourne 1 si une conversion vers .L est nécessaire,
+0 sinon. Le type du fichier est détecté uniquement par rapport à l'extension.
+*/
+int		normalize_filename(t_env* env)
+{
+  char* name = NULL;
+  int i = 0;
+  int j = 0;
+  int retval = 0;
+
+  env->curext = 0;
+  name = env->list[env->pcfilenum]->d_name;
+  /* Reconnaissance de l'extension */
+  i = strlen((char*)name);
+  if (name[i - 2] == '.' && (toupper(name[i - 1] == 'K')
+			     || toupper(name[i - 1]) == 'T'
+			     || toupper(name[i - 1] == 'A')
+			     || toupper(name[i - 1] == 'L')
+			     || toupper(name[i - 1]) == 'B'))
+    {
+      env->curext = toupper(name[i - 1]);
+      retval = 0;  /* no conversion neccesary */
+    }
+  else
+    {
+      env->curext = 'K';
+      retval = 1; /* Converting from Text file to .K needed */
+    }
+  i = 0;
+  j = 0;
+  while (name[i] && i <= 7 && name[i] != '.')
+    {
+      env->filename[j++] = name[i++];
+    }
+  while (i <= 7)
+    {
+      env->filename[j++] = ' ';
+      i++;
+    }
+  env->filename[j++] = 0;
+  return retval;
+}
+
+
+int		txt2k(char* srcfile, char* destfile)
+{
+  struct stat st;
+  int		fd;
+  size_t	size;
+  char* map;
+
+  if ((fd = open(srcfile, O_RDONLY)) == -1)
+    {
+      perror("open");
+      return 0;
+    }
+  fstat(fd, &st);
+  size = st.st_size;
+  map = malloc(size + 1);
+  read(fd, map, size);
+  map[size] = 0;
+  close(fd);
+  if ((fd = open(destfile, O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
+    {
+      perror("open");
+      return 0;
+    }
+  /* ecriture de l'en-tête */
+
+  return 1;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_convert.h b/Drivers/Braille/EuroBraille/eutp_convert.h
new file mode 100644
index 0000000..a02368d
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_convert.h
@@ -0,0 +1,16 @@
+/*
+** convert.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by
+** Login   <obert01@epita.fr>
+**
+** Started on  Thu Mar 31 17:12:05 2005
+** Last update Tue Mar 14 16:19:49 2006 Olivier BERT
+*/
+#ifndef __EUTP_CONVERT_H_
+#define __EUTP_CONVERT_H_
+
+int		k2txt(t_env* env, char* srcfile, char* destfile);
+int	normalize_filename(t_env*);
+int	convert_init(t_env *);
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_debug.c b/Drivers/Braille/EuroBraille/eutp_debug.c
new file mode 100644
index 0000000..26a8fad
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_debug.c
@@ -0,0 +1,23 @@
+/*
+** debug.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Wed Mar 30 12:50:37 2005 Olivier BERT
+** Last update Wed Mar 30 12:55:24 2005 Olivier BERT
+*/
+
+
+#include <stdio.h>
+
+int		debug_print_buf(unsigned char* buf)
+{
+  int i = 0;
+
+  printf("printing received buffer:\n");
+  for (i = 0; i < 80; i++)
+    printf("%x,", buf[i]);
+  printf("\n");
+  return 0;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_debug.h b/Drivers/Braille/EuroBraille/eutp_debug.h
new file mode 100644
index 0000000..dcc7725
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_debug.h
@@ -0,0 +1,14 @@
+/*
+** debug.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Wed Mar 30 12:53:37 2005 Olivier BERT
+** Last update Wed Mar 30 12:59:16 2005 Olivier BERT
+*/
+#ifndef __EUTP_DEBUG_H_
+#define __EUTP_DEBUG_H_
+
+int		debug_print_buf(unsigned char* buf);
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_main.c b/Drivers/Braille/EuroBraille/eutp_main.c
new file mode 100644
index 0000000..bc22306
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_main.c
@@ -0,0 +1,36 @@
+/*
+** eutp.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Wed Mar 16 18:31:17 2005 Olivier BERT
+Last update Wed Jun  6 13:31:16 2007 Olivier BERT
+*/
+
+#include <unistd.h>
+#include <stdlib.h>
+#include "eutp_brl.h"
+#include "eutp_convert.h"
+#include <stdio.h>
+
+#include "eutp_pc.h"
+
+
+
+int		 main(void)
+{
+  t_env		env;
+
+  if (brl_init(&env) != 0)
+    {
+      fprintf(stderr, "Error initializing brlapi !\n");
+      exit(E_BRLAPI_ERROR);
+    }
+  if (pc_init(&env) == -1)
+    exit(2);
+  convert_init(&env);
+  brl_listfiles(&env);
+  brl_close();
+  return 0;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_pc.c b/Drivers/Braille/EuroBraille/eutp_pc.c
new file mode 100644
index 0000000..31391f7
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_pc.c
@@ -0,0 +1,69 @@
+/*
+** pc.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Sun Mar 20 01:27:53 2005 Olivier BERT
+Last update Wed Jun  6 20:42:40 2007 Olivier BERT
+*/
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "eutp_brl.h"
+#include "eutp_pc.h"
+
+
+extern unsigned char extensions[];
+extern unsigned char positions[];
+
+/*
+** The filter : we don't want to have directories in the list
+*/
+static int	filter_files(const struct dirent* d)
+{
+  struct stat st;
+
+  stat(d->d_name, &st);
+  if (S_ISDIR(st.st_mode))
+    return 0;
+  return 1;
+}
+
+int		scanfiles(t_env *env)
+{
+  env->n = scandir(".", &(env->list), filter_files, alphasort);
+  if (env->n < 0)
+    {
+      perror("scandir");
+      return -1;
+    }
+  return env->n;
+}
+
+int		pc_init(t_env *env)
+{
+  return scanfiles(env);
+}
+
+/*
+** Show PC files
+*/
+int		showpcfiles(t_env* env)
+{
+  unsigned char	pos = positions[env->status];
+  char		str[BUFFER_SIZE]; /* what we display to the braille terminal */
+
+  strcpy(str, "PC>BR ");
+  strcat(str, env->list[env->pcfilenum]->d_name);
+  brl_message(str, pos);
+  return 0;
+}
+
diff --git a/Drivers/Braille/EuroBraille/eutp_pc.h b/Drivers/Braille/EuroBraille/eutp_pc.h
new file mode 100644
index 0000000..2e51927
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_pc.h
@@ -0,0 +1,19 @@
+#ifndef __EUTP_PC_H_
+#define __EUTP_PC_H_
+#include <dirent.h>
+#include "eutp_brl.h"
+
+int		showpcfiles(t_env*);
+int		pc_init(t_env*);
+
+
+
+/*
+** Libc prototypes
+*/
+int alphasort(const struct dirent **a, 
+	      const struct dirent **b);
+int scandir(const char *dir, struct dirent ***namelist,
+	   int(*filter)(const struct dirent *),
+	   int(*compar)(const struct dirent **, const struct dirent **));
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_static.h b/Drivers/Braille/EuroBraille/eutp_static.h
new file mode 100644
index 0000000..047144d
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_static.h
@@ -0,0 +1,16 @@
+/*
+** static.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by
+** Login   <obert01@epita.fr>
+**
+** Started on  Wed Jun  8 19:16:39 2005
+** Last update Wed Jun  8 19:19:57 2005 
+*/
+
+#ifndef __EUTP_STATIC_H_
+#define __EUTP_STATIC_H_
+static unsigned char extensions[] = {'K', 'L', 'B', 'T', 'A'};
+static unsigned char positions[] = {3, 7, 16};
+
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_tools.c b/Drivers/Braille/EuroBraille/eutp_tools.c
new file mode 100644
index 0000000..b939986
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_tools.c
@@ -0,0 +1,46 @@
+/*
+** tools.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by
+** Login   <obert01@epita.fr>
+**
+** Started on  Thu Mar 31 12:28:07 2005
+** Last update Fri Mar 24 17:31:39 2006 Olivier BERT
+*/
+
+#include <unistd.h>
+#include "eutp_brl.h"
+
+void			brl_lasting_message(char* msg)
+{
+  brl_message(msg, 0);
+  sleep(1);
+  return;
+}
+
+
+/*
+** When extracted from the braille terminal, the filenames are padded to
+** 8 chars with blank characters. This function removes theese blanks.
+*/
+void		remove_blanks(unsigned char *str)
+{
+  int i = 0;
+
+  while (str[i] && str[i] != ' ')
+    i++;
+  str[i] = 0;
+}
+
+void		pad_blanks(unsigned char *str)
+{
+  int i = 0;
+
+  while (str[i] && i < 8)
+    i++;
+  while (i < 8)
+    {
+      str[i++] = ' ';
+    }
+  str[i] = 0;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_tools.h b/Drivers/Braille/EuroBraille/eutp_tools.h
new file mode 100644
index 0000000..7956519
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_tools.h
@@ -0,0 +1,19 @@
+/*
+** tools.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by
+** Login   <obert01@epita.fr>
+**
+** Started on  Thu Mar 31 12:36:55 2005
+** Last update Fri Mar 24 17:31:41 2006 Olivier BERT
+*/
+
+#ifndef __EUTP_TOOLS_H_
+#define __EUTP_TOOLS_H_
+void		remove_blanks(unsigned char *str);
+void		pad_blanks(unsigned char* str);
+
+/* Display a lasting message */
+void			brl_lasting_message(char* msg);
+
+#endif
diff --git a/Drivers/Braille/EuroBraille/eutp_transfer.c b/Drivers/Braille/EuroBraille/eutp_transfer.c
new file mode 100644
index 0000000..0d897fa
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_transfer.c
@@ -0,0 +1,282 @@
+/*
+** transfer.c for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Sun Mar 20 16:10:06 2005 Olivier BERT
+Last update Fri Jun  1 15:23:17 2007 Olivier BERT
+*/
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500
+#endif /* _XOPEN_SOURCE */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "brlapi.h"
+#include "eutp_brl.h"
+#include "eutp_debug.h"
+#include "eutp_tools.h"
+#include "eutp_convert.h"
+
+extern unsigned char extensions[];
+
+
+int		brtopc(t_env* env)
+{
+  int		fd; /* fd of a temporary file */
+  unsigned int		lines = 0; /* number of packets transfered */
+  unsigned char destext = 0;  /* conversion of the destination file */
+  unsigned char		buf[BUFFER_SIZE]; /* received buffer */
+  unsigned char	str[BUFFER_SIZE];  /* sent buffer */
+  unsigned char ext = extensions[env->curext];  /* current extension */
+  unsigned char filename[15]; /* The transfered file name */
+  unsigned char filename1[15]; /* The filename without extension */
+
+  while (1)
+    {
+      /* Asking name of current selected file */
+      strcpy((char*)buf, "\005FN");
+      buf[3] = ext;
+      buf[4] = (env->brfilenum & 0xff00) >> 2;
+      buf[5] = env->brfilenum & 0x00ff;
+      brl_write(buf+1, 5);
+      brl_read(buf);
+      strcpy((char*)str, "Conv ");
+      strncat((char*)str, ((char*)&(buf[6])), buf[0] - 5);
+      filename[0] = 0;
+      strncat((char*)filename, ((char*)&(buf[6])), buf[0] - 5);
+      strcat((char*)str, ".");
+      remove_blanks(filename);
+      strcpy((char*)filename1, (char*)filename);
+      strcat((char*)filename, ".");
+      if (destext == 0) {
+	strncat((char*)str, ((char*)&(extensions[env->curext])), 1);
+	strncat((char*)filename, ((char*)&(extensions[env->curext])), 1);
+      }
+      else {
+	strcat((char*)str, "TXT");
+	strcat((char*)filename, "TXT");
+      }
+      brl_message((char*)str, 0);
+      brl_read(buf);
+      if (!strncmp((char*)buf, "\003KT*", 4))
+	return 1;
+      if (!strncmp((char*)buf, "\003KT#", 4))
+	break;
+      if (!strncmp((char*)buf, "\003KT8", 4))
+	destext = !destext;
+      if (!strncmp((char*)buf, "\003KT2", 4))
+	destext = !destext;
+    }
+  strcpy((char*)buf, "\005FO");
+  buf[3] = ext;
+  buf[4] = (env->brfilenum & 0xff00) >> 2;
+  buf[5] = env->brfilenum & 0x00ff;
+  brl_write(buf+1, 5);
+  brl_read(buf);
+  if (strncmp((char*)buf, "\003FE\x10", 4))
+    {
+      brl_lasting_message("Erreur ouv br");
+      return 1;
+    }
+  if ((fd = open((char*)filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
+    {
+      perror("open");
+      brl_message("! Err ecriture PC", 0);
+      sleep(1);
+      return 1;
+    }
+  while (1)
+    {
+      brl_writeStr(READ_LINE);
+      brl_read(buf);
+      if (!strncmp((char*)buf, "\003KT", 3))
+	{
+	  printf("touche appuyée\n");
+	  if (!strncmp((char*)buf, "\003KT*", 4))
+	    {
+	      printf("touche * appuyée\n");
+	      brl_lasting_message("! interrompu ");
+	      return 1;
+	    }
+	}
+      if (!strncmp((char*)buf, "\003FE", 3))
+	{
+	  if (buf[3] == '\x13')
+	      break;
+	  else
+	    {
+	      printf("code inattendu\n");
+	      brl_lasting_message("! transfert interrompu");
+	      return 1;
+	    }
+	}
+      sprintf((char*)str, "... %s %d", filename, lines);
+      brl_message((char*)str, 0);
+      write(fd, &(buf[3]), buf[0] - 2);
+      lines++;
+    }
+  brl_writeStr(CLOSE_FILE);
+  brl_read(buf);
+  if (strncmp((char*)buf, "\003FE\x10", 4))
+    {
+      brl_lasting_message("! erreur");
+      return 1;
+    }
+  printf("fichier fermé\n");
+  close(fd);
+  if (destext)
+    {
+      strcat((char*)filename1, ".TXT");
+      if (!k2txt(env, (char*)filename, (char*)filename1))
+	{
+	  brl_lasting_message("! Erreur conversion");
+	  return 1;
+	}
+    }
+  brl_lasting_message("! Fin transfert");
+  return 1;
+}
+
+
+/*
+** When this function returns 4242, it's the end of file
+** Otherwise, the length of the trame is returned.
+** The trame is formated to be directly sent to the braille terminal
+*/
+static unsigned int	read_trame_from_file(t_env* env, unsigned char* res,
+					     unsigned int *size)
+{
+  unsigned int retval = 0;
+  int ch;
+  int oldch;
+
+  *size = 0;
+  res[1] = 'F';
+  res[2] = 'W';
+  oldch = fgetc(env->fs);
+  ch = fgetc(env->fs);
+  *size = 4;
+  res[3] = oldch;
+  res[4] = ch;
+  while ((!(ch == '$' && oldch == '\x1B')) && (!(ch == '@' && oldch == '\x1B')) )
+    {
+      oldch = ch;
+      ch = fgetc(env->fs);
+      (*size)++;
+      res[*size] = ch;
+    }
+  if (ch == '@' && oldch == '\x1B')
+    {
+      printf("fin du fichier détectée\n");
+      retval = 1;
+    }
+  res[0] = *size;
+  return retval;
+}
+
+
+/*
+** PC to BR meta-function
+*/
+int		pctobr(t_env* env)
+{
+  char end = 0;
+  int i = 0;
+  unsigned int count = 0;
+  unsigned char conv = 0;
+  unsigned int lines = 0;
+  unsigned char		buf[BUFFER_SIZE]; /* received buffer */
+  unsigned char	str[BUFFER_SIZE];  /* sent buffer */
+  char* tmpfilename = "/tmp/eutp.tmp";
+
+  for (i = 0; i < BUFFER_SIZE; i++)
+    {
+      buf[i] = 0;
+      str[i] = 0;
+    }
+  conv = normalize_filename(env);
+  if ((env->fd = open(env->list[env->pcfilenum]->d_name, O_RDONLY)) == -1)
+    {
+      brl_message("!Erreur ouv pc", 0);
+      sleep(1);
+      return 0;
+    }
+  env->fs = fdopen(env->fd, "r");
+  /* Ouverture fichier en écriture sur le terminal braille */
+  str[0] = '\x0C';
+  strncpy((char*)&str[1], "Fo\x00", 3);
+  str[4] = env->curext;
+  strncpy((char*)&str[5], env->filename, 8);
+  brl_write(str+1, 12);
+  brl_read(buf);
+  if (!strncmp((char*)buf, "\003FE\x21", 4))
+    {
+      if (brl_yesno_question("! Remplacer ?      #"))
+	  str[3] = 1;
+      else
+	return 1;
+      brl_write(str+1, 12);
+      brl_read(buf);
+    }
+  if (strncmp((char*)buf, "\002FW", 3))
+    {
+      brl_lasting_message("! erreur ouv br");
+      brl_writeStr(CLOSE_FILE);
+      return 1;
+    }
+  while (!end)
+    {
+      end = read_trame_from_file(env, str, &count);
+      for (i = 0; i < 30; i++)
+	printf(",%d,", str[i]);
+      printf("\n");
+      if (count == 4242) /* fin fichier */
+	{
+	  printf("fin fichier\n");
+	  end = 1;
+	}
+      brl_write(str+1, count);
+      brl_read(buf);
+      if (strncmp((char*)buf, (char*)"\002FW", 3))
+	{
+	  printf("erreur transfert\n");
+	  for (i = 0; i < 30; i++)
+	    printf(",%d,", buf[i]);
+	  printf("\n");
+	  brl_lasting_message("! Erreur transfert");
+	  brl_writeStr(CLOSE_FILE);
+	  return 1;
+	}
+      lines++;
+      sprintf((char*)str, "... %s.%c %d", env->filename, env->curext, lines);
+      brl_message((char*)str, 0);
+    }
+  brl_writeStr(CLOSE_FILE);
+  brl_read(buf);
+  if (strncmp((char*)buf, "\003FE\x10", 4))
+    {
+      printf("errer fermeture\n");
+      for (i = 0; i < 30; i++)
+	printf(",%d,", buf[i]);
+      printf("\n");
+      brl_lasting_message("! err fermeture");
+      fclose(env->fs);
+      close(env->fd);
+      return 1;
+    }
+  brl_message("! Fin transfert", 0);
+  sleep(1);
+  fclose(env->fs);
+  close(env->fd);
+  if (conv)
+    unlink(tmpfilename);
+  return 1;
+}
diff --git a/Drivers/Braille/EuroBraille/eutp_transfer.h b/Drivers/Braille/EuroBraille/eutp_transfer.h
new file mode 100644
index 0000000..9bd918f
--- /dev/null
+++ b/Drivers/Braille/EuroBraille/eutp_transfer.h
@@ -0,0 +1,17 @@
+/*
+** transfer.h for eutp in /home/obert01/work/eutp/src
+**
+** Made by Olivier BERT
+** Login   <obert01@epita.fr>
+**
+** Started on  Sun Mar 20 16:10:12 2005 Olivier BERT
+** Last update Thu May 26 14:10:18 2005 
+*/
+
+#ifndef __EUTP_TRANSFER_H_
+#define __EUTP_TRANSFER_H_
+
+int		brtopc(t_env*);
+int		pctobr(t_env*);
+
+#endif
diff --git a/Drivers/Braille/FrankAudiodata/Makefile.in b/Drivers/Braille/FrankAudiodata/Makefile.in
new file mode 100644
index 0000000..30870f3
--- /dev/null
+++ b/Drivers/Braille/FrankAudiodata/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = fa
+DRIVER_NAME = FrankAudiodata
+DRIVER_USAGE = B2K84
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/FrankAudiodata/braille.c b/Drivers/Braille/FrankAudiodata/braille.c
new file mode 100644
index 0000000..95a8dc5
--- /dev/null
+++ b/Drivers/Braille/FrankAudiodata/braille.c
@@ -0,0 +1,408 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_generic.h"
+#include "io_usb.h"
+#include "ezusb.h"
+
+#define BRL_STATUS_FIELDS sfCursorCoordinates2, sfWindowCoordinates2
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-fa.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+
+#define MAXIMUM_RESPONSE_SIZE 0X20
+#define WRITE_CELLS_LIMIT 62
+
+#define TEXT_CELL_COUNT 80
+#define STATUS_CELL_COUNT 4
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  BRL_KEY_NAME_ENTRY(FA, NAV, K1, "K1"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K2, "K2"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K3, "K3"),
+
+  BRL_KEY_NAME_ENTRY(FA, NAV, K4, "K4"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K5, "K5"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K6, "K6"),
+
+  BRL_KEY_NAME_ENTRY(FA, NAV, K7, "K7"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K8, "K8"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, K9, "K9"),
+
+  BRL_KEY_NAME_ENTRY(FA, NAV, F1, "F1"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, F2, "F2"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, F3, "F3"),
+
+  BRL_KEY_NAME_ENTRY(FA, NAV, F4, "F4"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, F5, "F5"),
+  BRL_KEY_NAME_ENTRY(FA, NAV, F6, "F6"),
+
+  BRL_KEY_GROUP_ENTRY(FA, ROUTE, "RoutingKey"),
+  BRL_KEY_GROUP_ENTRY(FA, SLIDE, "Slider"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  int (*prepare) (BrailleDisplay *brl);
+} ProductEntry;
+
+typedef struct {
+  size_t length;
+  unsigned char buffer[8];
+} DeviceResponse;
+
+struct BrailleDataStruct {
+  const ProductEntry *product;
+
+  struct {
+    KeyNumberSet navigation;
+    unsigned char routing[TEXT_CELL_COUNT / 8];
+  } keys;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[TEXT_CELL_COUNT];
+  } text;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[STATUS_CELL_COUNT];
+  } status;
+
+  struct {
+    DeviceResponse response1;
+    DeviceResponse serialNumber[2];
+    DeviceResponse response4;
+    DeviceResponse response5;
+    DeviceResponse response6;
+    DeviceResponse response7;
+    DeviceResponse response8;
+  } device;
+};
+
+static UsbDevice *
+getDevice (BrailleDisplay *brl) {
+  UsbChannel *channel = gioGetResourceObject(brl->gioEndpoint);
+  return channel->device;
+}
+
+static int
+installStage (UsbDevice *device, unsigned int stage, EzusbAction action) {
+  char name[0X40];
+
+  snprintf(
+    name, sizeof(name), "%s-bfa-stage%u",
+    PACKAGE_TARNAME, stage
+  );
+
+  return ezusbInstallBlob(device, name, action);
+}
+
+static int
+installFirmware (BrailleDisplay *brl) {
+  UsbDevice *device = getDevice(brl);
+
+  if (!ezusbStopCPU(device)) return 0;
+  if (!installStage(device, 1, EZUSB_ACTION_RW_INTERNAL)) return 0;
+
+  if (!ezusbResetCPU(device)) return 0;
+  if (!installStage(device, 2, EZUSB_ACTION_RW_MEMORY)) return 0;
+
+  if (!ezusbStopCPU(device)) return 0;
+  if (!installStage(device, 3, EZUSB_ACTION_RW_INTERNAL)) return 0;
+
+  if (!ezusbResetCPU(device)) return 0;
+  return 1;
+}
+
+static int
+prepare1016 (BrailleDisplay *brl) {
+  if (!installFirmware(brl)) return 0;
+  return 0; // force a retry - the product ID should now be 0X1017
+}
+
+static const ProductEntry productEntry_1016 = {
+  .prepare = prepare1016,
+};
+
+static int
+askDevice (UsbDevice *device, uint16_t value, uint16_t index, DeviceResponse *values) {
+  ssize_t result = usbControlRead(
+    device, UsbControlRecipient_Device, UsbControlType_Vendor,
+    0XC0, value, index,
+    values->buffer, sizeof(values->buffer), 1000
+  );
+
+  if (result == -1) return 0;
+  values->length = result;
+
+  logBytes(LOG_CATEGORY(BRAILLE_DRIVER),
+    "response: %04X %04X",
+    values->buffer, values->length, value, index
+  );
+
+  return 1;
+}
+
+static int
+prepare1017 (BrailleDisplay *brl) {
+  UsbDevice *device = getDevice(brl);
+
+  if (!askDevice(device, 0X0000, 0X0001, &brl->data->device.response1)) return 0;
+  if (!askDevice(device, 0X0001, 0X0000, &brl->data->device.serialNumber[0])) return 0;
+  if (!askDevice(device, 0X0001, 0X0001, &brl->data->device.serialNumber[1])) return 0;
+  if (!askDevice(device, 0X0001, 0X0002, &brl->data->device.response4)) return 0;
+  if (!askDevice(device, 0X0001, 0X0004, &brl->data->device.response5)) return 0;
+  if (!askDevice(device, 0X0001, 0X0005, &brl->data->device.response6)) return 0;
+  if (!askDevice(device, 0X0001, 0X0006, &brl->data->device.response7)) return 0;
+  if (!askDevice(device, 0X0001, 0X0007, &brl->data->device.response8)) return 0;
+
+  return 1;
+}
+
+static const ProductEntry productEntry_1017 = {
+  .prepare = prepare1017,
+};
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1: {
+      switch (byte) {
+        case FA_PKT_SLIDER:
+          *length += 3;
+          break;
+
+        case FA_PKT_NAV:
+          *length += 4;
+          break;
+
+        case FA_PKT_ROUTE:
+          *length += ARRAY_COUNT(brl->data->keys.routing);
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* B2K84 (before firmware installation) */
+      .vendor=0X0904, .product=0X1016,
+      .configuration=1, .interface=0, .alternative=0,
+      .data = &productEntry_1016
+    },
+
+    { /* B2K84 (after firmware installation) */
+      .vendor=0X0904, .product=0X1017,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data = &productEntry_1017
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->product = gioGetApplicationData(brl->gioEndpoint);
+    if (brl->data->product->prepare(brl)) return 1;
+    disconnectBrailleResource(brl, NULL);
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+      makeOutputTable(dotsTable_ISO11548_1);
+
+      brl->textColumns = TEXT_CELL_COUNT;
+      brl->statusColumns = STATUS_CELL_COUNT;
+
+      brl->data->keys.navigation = 0;
+      brl->data->text.rewrite = 1;
+      brl->data->status.rewrite = 1;
+
+      return 1;
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+writeCells (BrailleDisplay *brl, const unsigned char *cells, unsigned int from, unsigned int to, unsigned int offset) {
+  unsigned char packet[1 + (to - from)];
+
+  while (from < to) {
+    unsigned char *byte = packet;
+    unsigned int count = to - from;
+    count = MIN(count, WRITE_CELLS_LIMIT);
+
+    *byte++ = from + offset;
+    byte = mempcpy(byte, cells, count);
+    if (!writeBytes(brl, packet, (byte - packet))) return 0;
+
+    from += count;
+    cells += count;
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  unsigned int from, to;
+
+  if (cellsHaveChanged(brl->data->status.cells, cells, brl->statusColumns,
+                       &from, &to, &brl->data->status.rewrite)) {
+    if (!writeCells(brl, &cells[from], from, to, 0)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned int from, to;
+
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, brl->textColumns,
+                       &from, &to, &brl->data->text.rewrite)) {
+    size_t count = to - from;
+    unsigned char cells[count];
+
+    translateOutputCells(cells, &brl->data->text.cells[from], count);
+    if (!writeCells(brl, cells, from, to, STATUS_CELL_COUNT)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    switch (packet[0]) {
+      case FA_PKT_NAV: {
+        KeyNumberSet keys = (packet[1] <<  0)
+                          | (packet[2] <<  8)
+                          | (packet[3] << 16)
+                          | (packet[4] << 24)
+                          ;
+
+        enqueueUpdatedKeys(brl, keys, &brl->data->keys.navigation, FA_GRP_NAV, 0);
+        continue;
+      }
+
+      case FA_PKT_ROUTE: {
+        enqueueUpdatedKeyGroup(
+          brl, TEXT_CELL_COUNT,
+          &packet[1], brl->data->keys.routing,
+          FA_GRP_ROUTE
+        );
+
+        continue;
+      }
+
+      case FA_PKT_SLIDER: {
+	int value = (packet[2] * 0XFF) / 0XF4;
+        value = MIN(value, 0XFF);
+        enqueueKey(brl, FA_GRP_SLIDE, value);
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/FrankAudiodata/brldefs-fa.h b/Drivers/Braille/FrankAudiodata/brldefs-fa.h
new file mode 100644
index 0000000..6d15ed0
--- /dev/null
+++ b/Drivers/Braille/FrankAudiodata/brldefs-fa.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_FA_BRLDEFS
+#define BRLTTY_INCLUDED_FA_BRLDEFS
+
+typedef enum {
+  FA_PKT_SLIDER = 2,
+  FA_PKT_NAV = 8,
+  FA_PKT_ROUTE = 9,
+} FA_PacketType;
+
+typedef enum {
+  FA_NAV_K1 = 2,
+  FA_NAV_K2 = 1,
+  FA_NAV_K3 = 0,
+
+  FA_NAV_K4 = 8,
+  FA_NAV_K5 = 9,
+  FA_NAV_K6 = 10,
+
+  FA_NAV_K7 = 3,
+  FA_NAV_K8 = 4,
+  FA_NAV_K9 = 5,
+
+  FA_NAV_F1 = 18,
+  FA_NAV_F2 = 17,
+  FA_NAV_F3 = 16,
+
+  FA_NAV_F4 = 19,
+  FA_NAV_F5 = 20,
+  FA_NAV_F6 = 21,
+} FA_NavigationKey;
+
+typedef enum {
+  FA_GRP_NAV,
+  FA_GRP_ROUTE,
+  FA_GRP_SLIDE,
+} FA_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_FA_BRLDEFS */
diff --git a/Drivers/Braille/FreedomScientific/Makefile.in b/Drivers/Braille/FreedomScientific/Makefile.in
new file mode 100644
index 0000000..ec6f216
--- /dev/null
+++ b/Drivers/Braille/FreedomScientific/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = fs
+DRIVER_NAME = FreedomScientific
+DRIVER_USAGE = Focus 1 44/70/84, Focus 2 40/80, Focus 3+ (Blue) 14/40/80, PAC Mate 20/40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/FreedomScientific/README b/Drivers/Braille/FreedomScientific/README
new file mode 100644
index 0000000..95c7de4
--- /dev/null
+++ b/Drivers/Braille/FreedomScientific/README
@@ -0,0 +1,40 @@
+This directory contains the BRLTTY driver for the Focus and PAC Mate braille
+displays from Freedom Scientific [http://www.freedomscientific.com]. It has
+been implemented, and is being maintained, by Dave Mielke <dave@mielke.cc>.
+
+The most important single command is the one to access the help screen. On the
+PAC Mate display this is the key just to the right of the center marker (also
+referred to as HK5 below). On the Focus, this is chord H (Space+Dots125).
+
+Serial, USB, and Bluetooth are all supported. The following models are 
+recognized:
+*  Focus 1: 44, 70, 84
+*  Focus 2: 40, 80
+*  Focus Blue: 14, 40, 80
+*  PAC Mate: 20, 40
+
+If an unrecognized model is connected then the driver makes an attempt to
+determine its size and to provide generic capabilities.
+
+The upper row of routing buttons on the PAC Mate are a set of hot keys. They
+provide quick access to various braille display functions and options (see the
+help screen for what they actually do). There are three markers at the back of
+the display's top surface, i.e. just behind the routing buttons, which help you
+locate and distinguish each hot key. The keys immediately below the left and
+right markers are the left and right GDF keys. Between them are eight keys ...
+four on each side of the center marker. The buttons between the left GDF key
+and the center marker are named (from left to right) HK1, HK2, HK3, and HK4;
+those between the center marker and the right GDF key are named (from left to
+right) HK5, HK6, HK7, and HK8. Any button to the left of the left GDF key acts
+as the left advance key, and any button to the right of the right GDF key acts
+as the right advance key. The following drawing depicts this layout visually:
+
+                ++                     ++                     ++
+                ||                     ||                     ||
+                ||                     ||                     ||
+  ...+---++---++---++---++---++---++---++---++---++---++---++---++---++---+...
+  Left Advance||GDF||HK1||HK2||HK3||HK4||HK5||HK6||HK7||HK8||GDF||RightAdvance
+  ...+---++---++---++---++---++---++---++---++---++---++---++---++---++---+...
+
+
+
diff --git a/Drivers/Braille/FreedomScientific/braille.c b/Drivers/Braille/FreedomScientific/braille.c
new file mode 100644
index 0000000..261b01b
--- /dev/null
+++ b/Drivers/Braille/FreedomScientific/braille.c
@@ -0,0 +1,1068 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* FreedomScientific/braille.c - Braille display library
+ * Freedom Scientific's Focus and PacMate series
+ * Author: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parse.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+
+#define BRLSTAT ST_AlvaStyle
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "brldefs-fs.h"
+
+BEGIN_KEY_NAME_TABLE(common)
+  KEY_NAME_ENTRY(FS_KEY_PanLeft, "PanLeft"),
+  KEY_NAME_ENTRY(FS_KEY_PanRight, "PanRight"),
+  KEY_NAME_ENTRY(FS_KEY_LeftSelector, "LeftSelector"),
+  KEY_NAME_ENTRY(FS_KEY_RightSelector, "RightSelector"),
+
+  KEY_GROUP_ENTRY(FS_GRP_RoutingKeys, "RoutingKey"),
+  KEY_GROUP_ENTRY(FS_GRP_NavrowKeys, "NavrowKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(nav)
+  KEY_NAME_ENTRY(FS_KEY_LeftWheel, "LeftNavPress"),
+  KEY_NAME_ENTRY(FS_KEY_RightWheel, "RightNavPress"),
+
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+0, "LeftNavUp"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+1, "LeftNavDown"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+2, "RightNavDown"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+3, "RightNavUp"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  KEY_NAME_ENTRY(FS_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(FS_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(FS_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(FS_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(FS_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(FS_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(FS_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(FS_KEY_Dot8, "Dot8"),
+
+  KEY_NAME_ENTRY(FS_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(FS_KEY_LeftShift, "LeftShift"),
+  KEY_NAME_ENTRY(FS_KEY_RightShift, "RightShift"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(rockers)
+  KEY_NAME_ENTRY(FS_KEY_LeftRockerUp, "LeftRockerUp"),
+  KEY_NAME_ENTRY(FS_KEY_LeftRockerDown, "LeftRockerDown"),
+  KEY_NAME_ENTRY(FS_KEY_RightRockerUp, "RightRockerUp"),
+  KEY_NAME_ENTRY(FS_KEY_RightRockerDown, "RightRockerDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(bumpers)
+  KEY_NAME_ENTRY(FS_KEY_LeftBumperUp, "LeftBumperUp"),
+  KEY_NAME_ENTRY(FS_KEY_LeftBumperDown, "LeftBumperDown"),
+  KEY_NAME_ENTRY(FS_KEY_RightBumperUp, "RightBumperUp"),
+  KEY_NAME_ENTRY(FS_KEY_RightBumperDown, "RightBumperDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(focus1)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(nav),
+  KEY_NAME_TABLE(keyboard),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(focus14)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(nav),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(rockers),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(focus40)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(nav),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(rockers),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(focus80)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(nav),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(bumpers),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(wheel)
+  KEY_NAME_ENTRY(FS_KEY_LeftWheel, "LeftWheelPress"),
+  KEY_NAME_ENTRY(FS_KEY_RightWheel, "RightWheelPress"),
+
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+0, "LeftWheelUp"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+1, "LeftWheelDown"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+2, "RightWheelDown"),
+  KEY_NAME_ENTRY(FS_KEY_WHEEL+3, "RightWheelUp"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(hot)
+  KEY_NAME_ENTRY(FS_KEY_HOT+0, "Hot1"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+1, "Hot2"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+2, "Hot3"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+3, "Hot4"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+4, "Hot5"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+5, "Hot6"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+6, "Hot7"),
+  KEY_NAME_ENTRY(FS_KEY_HOT+7, "Hot8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(pacmate)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(wheel),
+  KEY_NAME_TABLE(hot),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(focus1)
+DEFINE_KEY_TABLE(focus14)
+DEFINE_KEY_TABLE(focus40)
+DEFINE_KEY_TABLE(focus80)
+DEFINE_KEY_TABLE(pacmate)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(focus1),
+  &KEY_TABLE_DEFINITION(focus14),
+  &KEY_TABLE_DEFINITION(focus40),
+  &KEY_TABLE_DEFINITION(focus80),
+  &KEY_TABLE_DEFINITION(pacmate),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  const KeyTableDefinition *keyTableDefinition;
+  signed char hotkeysRow;
+} ModelTypeEntry;
+
+typedef enum {
+  MOD_TYPE_Focus,
+  MOD_TYPE_PacMate
+} ModelType;
+
+static const ModelTypeEntry modelTypeTable[] = {
+  [MOD_TYPE_Focus] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(focus1),
+    .hotkeysRow = -1
+  },
+
+  [MOD_TYPE_PacMate] = {
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(pacmate),
+    .hotkeysRow = 1
+  }
+};
+
+typedef struct {
+  const char *identifier;
+  const DotsTable *dotsTable;
+  unsigned char cellCount;
+  unsigned char type;
+} ModelEntry;
+
+static const DotsTable dotsTable_Focus1 = {
+  0X01, 0X02, 0X04, 0X10, 0X20, 0X40, 0X08, 0X80
+};
+
+static const ModelEntry modelTable[] = {
+  { .identifier = "Focus 14",
+    .dotsTable = &dotsTable_ISO11548_1,
+    .cellCount = 14,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "Focus 40",
+    .dotsTable = &dotsTable_ISO11548_1,
+    .cellCount = 40,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "Focus 44",
+    .dotsTable = &dotsTable_Focus1,
+    .cellCount = 44,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "Focus 70",
+    .dotsTable = &dotsTable_Focus1,
+    .cellCount = 70,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "Focus 80",
+    .dotsTable = &dotsTable_ISO11548_1,
+    .cellCount = 80,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "Focus 84",
+    .dotsTable = &dotsTable_Focus1,
+    .cellCount = 84,
+    .type = MOD_TYPE_Focus
+  },
+
+  { .identifier = "pm display 20",
+    .dotsTable = &dotsTable_ISO11548_1,
+    .cellCount = 20,
+    .type = MOD_TYPE_PacMate
+  },
+
+  { .identifier = "pm display 40",
+    .dotsTable = &dotsTable_ISO11548_1,
+    .cellCount = 40,
+    .type = MOD_TYPE_PacMate
+  },
+
+  { .identifier = NULL }
+};
+
+typedef void (*AcknowledgementHandler) (BrailleDisplay *brl, int ok);
+
+struct BrailleDataStruct {
+  int queryAcknowledged;
+  const ModelEntry *model;
+  const KeyTableDefinition *keyTableDefinition;
+
+  ModelEntry genericModelEntry;
+  char genericModelIdentifier[FS_INFO_MODEL_SIZE];
+
+  unsigned char outputBuffer[UINT8_MAX + 1];
+  int writeFirst;
+  int writeLast;
+  int writingFirst;
+  int writingLast;
+
+  AcknowledgementHandler acknowledgementHandler;
+  AsyncHandle missingAcknowledgementAlarm;
+
+  unsigned char configFlags;
+  int firmnessSetting;
+
+  int outputPayloadLimit;
+
+  uint64_t oldKeys;
+};
+
+static int
+writePacket (
+  BrailleDisplay *brl,
+  unsigned char type,
+  unsigned char arg1,
+  unsigned char arg2,
+  unsigned char arg3,
+  const unsigned char *data
+) {
+  FS_Packet packet;
+  int size = sizeof(packet.header);
+  unsigned char checksum = 0;
+
+  checksum -= (packet.header.type = type);
+  checksum -= (packet.header.arg1 = arg1);
+  checksum -= (packet.header.arg2 = arg2);
+  checksum -= (packet.header.arg3 = arg3);
+
+  if (data) {
+    unsigned char length = packet.header.arg1;
+    int index;
+
+    for (index=0; index<length; index+=1)
+      checksum -= (packet.payload.bytes[index] = data[index]);
+
+    packet.payload.bytes[length] = checksum;
+    size += length + 1;
+  }
+
+  return writeBraillePacket(brl, NULL, &packet, size);
+}
+
+static void
+logNegativeAcknowledgement (const FS_Packet *packet) {
+  const char *problem;
+  const char *component;
+
+  switch (packet->header.arg1) {
+    default:
+      problem = "unknown problem";
+      break;
+    case FS_ERR_TIMEOUT:
+      problem = "timeout during packet transmission";
+      break;
+    case FS_ERR_CHECKSUM:
+      problem = "incorrect checksum";
+      break;
+    case FS_ERR_TYPE:
+      problem = "unknown packet type";
+      break;
+    case FS_ERR_PARAMETER:
+      problem = "invalid parameter value";
+      break;
+    case FS_ERR_SIZE:
+      problem = "write size too large";
+      break;
+    case FS_ERR_POSITION:
+      problem = "write start too large";
+      break;
+    case FS_ERR_OVERRUN:
+      problem = "message FIFO overflow";
+      break;
+    case FS_ERR_POWER:
+      problem = "insufficient USB power";
+      break;
+    case FS_ERR_SPI:
+      problem = "SPI bus timeout";
+      break;
+  }
+
+  switch (packet->header.arg2) {
+    default:
+      component = "unknown component";
+      break;
+    case FS_EXT_HVADJ:
+      component = "VariBraille packet";
+      break;
+    case FS_EXT_BEEP:
+      component = "beep packet";
+      break;
+    case FS_EXT_CLEAR:
+      component = "ClearMsgBuf function";
+      break;
+    case FS_EXT_LOOP:
+      component = "timing loop of ParseCommands function";
+      break;
+    case FS_EXT_TYPE:
+      component = "ParseCommands function";
+      break;
+    case FS_EXT_CMDWRITE:
+      component = "CmdWrite function";
+      break;
+    case FS_EXT_UPDATE:
+      component = "update packet";
+      break;
+    case FS_EXT_DIAG:
+      component = "diag packet";
+      break;
+    case FS_EXT_QUERY:
+      component = "query packet";
+      break;
+    case FS_EXT_WRITE:
+      component = "write packet";
+      break;
+  }
+
+  logMessage(LOG_WARNING, "Negative Acknowledgement: [%02X] %s in [%02X] %s",
+             packet->header.arg1, problem,
+             packet->header.arg2, component);
+}
+
+static void
+handleConfigAcknowledgement (BrailleDisplay *brl, int ok) {
+  brl->data->configFlags = 0;
+}
+
+static void
+handleFirmnessAcknowledgement (BrailleDisplay *brl, int ok) {
+  brl->data->firmnessSetting = -1;
+}
+
+static void
+handleWriteAcknowledgement (BrailleDisplay *brl, int ok) {
+  if (!ok) {
+    if ((brl->data->writeFirst == -1) ||
+        (brl->data->writingFirst < brl->data->writeFirst))
+      brl->data->writeFirst = brl->data->writingFirst;
+
+    if ((brl->data->writeLast == -1) ||
+        (brl->data->writingLast > brl->data->writeLast))
+      brl->data->writeLast = brl->data->writingLast;
+  }
+}
+
+static int handleAcknowledgement (BrailleDisplay *brl, int ok);
+
+ASYNC_ALARM_CALLBACK(handleMissingAcknowledgementAlarm) {
+  BrailleDisplay *brl = parameters->data;
+
+  asyncDiscardHandle(brl->data->missingAcknowledgementAlarm);
+  brl->data->missingAcknowledgementAlarm = NULL;
+
+  logMessage(LOG_WARNING, "missing ACK: assuming NAK");
+  handleAcknowledgement(brl, 0);
+}
+
+static int
+setMissingAcknowledgementAlarm (BrailleDisplay *brl, int timeout) {
+  if (!brl->data->missingAcknowledgementAlarm) {
+    if (!asyncNewRelativeAlarm(&brl->data->missingAcknowledgementAlarm, timeout,
+                         handleMissingAcknowledgementAlarm, brl)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static void
+cancelMissingAcknowledgementAlarm (BrailleDisplay *brl) {
+  if (brl->data->missingAcknowledgementAlarm) {
+    asyncCancelRequest(brl->data->missingAcknowledgementAlarm);
+    brl->data->missingAcknowledgementAlarm = NULL;
+  }
+}
+
+static void
+setAcknowledgementHandler (BrailleDisplay *brl, AcknowledgementHandler handler) {
+  brl->data->acknowledgementHandler = handler;
+  setMissingAcknowledgementAlarm(brl, 500);
+}
+
+static int
+writeRequest (BrailleDisplay *brl) {
+  if (brl->data->acknowledgementHandler) return 1;
+
+  if (brl->data->configFlags) {
+    if (!writePacket(brl, FS_PKT_CONFIG, brl->data->configFlags, 0, 0, NULL)) {
+      return 0;
+    }
+
+    setAcknowledgementHandler(brl, handleConfigAcknowledgement);
+    return 1;
+  }
+
+  if (brl->data->firmnessSetting >= 0) {
+    if (!writePacket(brl, FS_PKT_HVADJ, brl->data->firmnessSetting, 0, 0, NULL)) {
+      return 0;
+    }
+
+    setAcknowledgementHandler(brl, handleFirmnessAcknowledgement);
+    return 1;
+  }
+
+  if (brl->data->writeLast != -1) {
+    unsigned int count = brl->data->writeLast + 1 - brl->data->writeFirst;
+    unsigned char buffer[count];
+    int truncate = count > brl->data->outputPayloadLimit;
+
+    if (truncate) count = brl->data->outputPayloadLimit;
+    translateOutputCells(buffer, &brl->data->outputBuffer[brl->data->writeFirst], count);
+    if (!writePacket(brl, FS_PKT_WRITE, count, brl->data->writeFirst, 0, buffer)) {
+      return 0;
+    }
+
+    setAcknowledgementHandler(brl, handleWriteAcknowledgement);
+    brl->data->writingFirst = brl->data->writeFirst;
+
+    if (truncate) {
+      brl->data->writingLast = (brl->data->writeFirst += count) - 1;
+    } else {
+      brl->data->writingLast = brl->data->writeLast;
+      brl->data->writeFirst = -1;
+      brl->data->writeLast = -1;
+    }
+
+    return 1;
+  }
+
+  return 1;
+}
+
+static int
+handleAcknowledgement (BrailleDisplay *brl, int ok) {
+  brl->data->acknowledgementHandler(brl, ok);
+  brl->data->acknowledgementHandler = NULL;
+  return writeRequest(brl);
+}
+
+static void
+updateCells (
+  BrailleDisplay *brl,
+  const unsigned char *cells,
+  unsigned char count,
+  unsigned char offset
+) {
+  unsigned int from;
+  unsigned int to;
+
+  if (cellsHaveChanged(&brl->data->outputBuffer[offset], cells, count, &from, &to, NULL)) {
+    int first = from + offset;
+    int last = to + offset - 1;
+
+    if ((brl->data->writeFirst == -1) || (first < brl->data->writeFirst))
+      brl->data->writeFirst = first;
+
+    if (last > brl->data->writeLast) brl->data->writeLast = last;
+  }
+}
+
+typedef struct {
+  unsigned char checksum;
+} ReadPacketData;
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  ReadPacketData *rpd = data;
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case FS_PKT_ACK:
+        case FS_PKT_NAK:
+        case FS_PKT_KEY:
+        case FS_PKT_EXTKEY:
+        case FS_PKT_BUTTON:
+        case FS_PKT_WHEEL:
+        case FS_PKT_INFO:
+          *length = sizeof(FS_PacketHeader);
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+
+      rpd->checksum = 0;
+      break;
+
+    case 2:
+      if (bytes[0] & 0X80) *length += byte + 1;
+      break;
+
+    default:
+      break;
+  }
+
+  rpd->checksum -= byte;
+  if ((size == *length) && (size > sizeof(FS_PacketHeader)) && rpd->checksum) return BRL_PVR_INVALID;
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, FS_Packet *packet) {
+  ReadPacketData rpd;
+
+  return readBraillePacket(brl, NULL, packet, sizeof(*packet), verifyPacket, &rpd);
+}
+
+static size_t
+getPacket (BrailleDisplay *brl, FS_Packet *packet) {
+  while (1) {
+    size_t count = readPacket(brl, packet);
+
+    if (count > 0) {
+      switch (packet->header.type) {
+        {
+          int ok;
+
+        case FS_PKT_NAK:
+          cancelMissingAcknowledgementAlarm(brl);
+          logNegativeAcknowledgement(packet);
+
+          if (!brl->data->acknowledgementHandler) {
+            logMessage(LOG_WARNING, "unexpected NAK");
+            continue;
+          }
+
+          switch (packet->header.arg1) {
+            case FS_ERR_TIMEOUT: {
+              int originalLimit = brl->data->outputPayloadLimit;
+
+              if (brl->data->outputPayloadLimit > brl->data->model->cellCount)
+                brl->data->outputPayloadLimit = brl->data->model->cellCount;
+
+              if (brl->data->outputPayloadLimit > 1)
+                brl->data->outputPayloadLimit -= 1;
+
+              if (brl->data->outputPayloadLimit != originalLimit) {
+                logMessage(LOG_WARNING, "maximum payload length reduced from %d to %d",
+                           originalLimit, brl->data->outputPayloadLimit);
+              }
+
+              break;
+            }
+          }
+
+          ok = 0;
+          goto doAcknowledgement;
+
+        case FS_PKT_ACK:
+          cancelMissingAcknowledgementAlarm(brl);
+
+          if (!brl->data->acknowledgementHandler) {
+            logMessage(LOG_WARNING, "unexpected ACK");
+            continue;
+          }
+
+          ok = 1;
+          goto doAcknowledgement;
+
+        doAcknowledgement:
+          if (handleAcknowledgement(brl, ok)) continue;
+          count = 0;
+          break;
+        }
+
+        default:
+          break;
+      }
+    }
+
+    return count;
+  }
+}
+
+static int
+setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting) {
+  brl->data->firmnessSetting = setting * 0XFF / BRL_FIRMNESS_MAXIMUM;
+  return writeRequest(brl);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 57600
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Focus 1 */
+      .vendor=0X0F4E, .product=0X0100,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=1
+    },
+
+    { /* PAC Mate */
+      .vendor=0X0F4E, .product=0X0111,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=1
+    },
+
+    { /* Focus 2 */
+      .vendor=0X0F4E, .product=0X0112,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=1
+    },
+
+    { /* Focus 3+ */
+      .vendor=0X0F4E, .product=0X0114,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=1,
+      .disableEndpointReset = 1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  descriptor.bluetooth.channelNumber = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+setModel (BrailleDisplay *brl, const char *modelName, const char *firmware) {
+  brl->data->model = modelTable;
+  while (brl->data->model->identifier) {
+    if (strcmp(brl->data->model->identifier, modelName) == 0) break;
+    brl->data->model += 1;
+  }
+
+  if (!brl->data->model->identifier) {
+    logMessage(LOG_WARNING, "Detected unknown model: %s", modelName);
+
+    brl->data->model = &brl->data->genericModelEntry;
+    memset(&brl->data->genericModelEntry, 0, sizeof(brl->data->genericModelEntry));
+
+    brl->data->genericModelEntry.identifier = "Generic";
+    brl->data->genericModelEntry.cellCount = 20;
+    brl->data->genericModelEntry.dotsTable = &dotsTable_ISO11548_1;
+    brl->data->genericModelEntry.type = MOD_TYPE_PacMate;
+
+    {
+      typedef struct {
+	const char *identifier;
+	const DotsTable *dotsTable;
+      } ExceptionEntry;
+
+      static const ExceptionEntry exceptionTable[] = {
+	{"Focus", &dotsTable_Focus1},
+	{NULL   , NULL         }
+      };
+      const ExceptionEntry *exception = exceptionTable;
+
+      while (exception->identifier) {
+	if (strncmp(exception->identifier, modelName, strlen(exception->identifier)) == 0) {
+	  brl->data->genericModelEntry.dotsTable = exception->dotsTable;
+	  break;
+	}
+
+	exception += 1;
+      }
+    }
+
+    {
+      const char *word = strrchr(modelName, ' ');
+
+      if (word) {
+	unsigned int size;
+
+	if (isUnsignedInteger(&size, ++word)) {
+          if (size <= ARRAY_COUNT(brl->data->outputBuffer)) {
+            brl->data->genericModelEntry.cellCount = size;
+
+            snprintf(brl->data->genericModelIdentifier, sizeof(brl->data->genericModelIdentifier),
+                     "%s %d",
+                     brl->data->genericModelEntry.identifier,
+                     brl->data->genericModelEntry.cellCount);
+
+            brl->data->genericModelEntry.identifier = brl->data->genericModelIdentifier;
+          }
+	}
+      }
+    }
+  }
+
+  if (brl->data->model) {
+    brl->data->keyTableDefinition = modelTypeTable[brl->data->model->type].keyTableDefinition;
+    makeOutputTable(brl->data->model->dotsTable[0]);
+
+    memset(brl->data->outputBuffer, 0, brl->data->model->cellCount);
+    brl->data->writeFirst = 0;
+    brl->data->writeLast = brl->data->model->cellCount - 1;
+
+    brl->data->acknowledgementHandler = NULL;
+    brl->data->missingAcknowledgementAlarm = NULL;
+    brl->data->configFlags = 0;
+    brl->data->firmnessSetting = -1;
+
+    if (brl->data->model->type == MOD_TYPE_Focus) {
+      unsigned char firmwareVersion = firmware[0] - '0';
+
+      if (firmwareVersion >= 3) {
+        /* send the extended keys packet (FS_PKT_EXTKEY) */
+	brl->data->configFlags |= FS_CFG_EXTKEY;
+
+	if (brl->data->model->cellCount < 20) {
+	  brl->data->keyTableDefinition = &KEY_TABLE_DEFINITION(focus14);
+	} else if (brl->data->model->cellCount < 80) {
+	  brl->data->keyTableDefinition = &KEY_TABLE_DEFINITION(focus40);
+	} else {
+	  brl->data->keyTableDefinition = &KEY_TABLE_DEFINITION(focus80);
+	}
+      }
+    }
+
+    brl->data->oldKeys = 0;
+
+    logMessage(LOG_INFO, "Detected %s: cells=%d, firmware=%s",
+	       brl->data->model->identifier,
+	       brl->data->model->cellCount,
+	       firmware);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  brl->data->queryAcknowledged = 0;
+  brl->data->model = NULL;
+  return writePacket(brl, FS_PKT_QUERY, 0, 0, 0, NULL);
+}
+
+static size_t
+readResponse (BrailleDisplay *brl, void *packet, size_t size) {
+  return readPacket(brl, packet);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const FS_Packet *response = packet;
+
+  switch (response->header.type) {
+    case FS_PKT_INFO:
+      if (!setModel(brl, response->payload.info.model, response->payload.info.firmware)) return BRL_RSP_FAIL;
+      break;
+
+    case FS_PKT_ACK:
+      brl->data->queryAcknowledged = 1;
+      break;
+
+    case FS_PKT_NAK:
+      logNegativeAcknowledgement(response);
+      brl->data->queryAcknowledged = 0;
+      brl->data->model = NULL;
+      return BRL_RSP_CONTINUE;
+
+    default:
+      return BRL_RSP_UNEXPECTED;
+  }
+
+  return (brl->data->queryAcknowledged && brl->data->model)? BRL_RSP_DONE: BRL_RSP_CONTINUE;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->outputPayloadLimit = 0XFF;
+
+    if (connectResource(brl, device)) {
+      FS_Packet response;
+
+      if (probeBrailleDisplay(brl, 2, NULL, 100,
+                              writeIdentifyRequest,
+                              readResponse, &response, sizeof(response),
+                              isIdentityResponse)) {
+        logMessage(LOG_DEBUG, "Manufacturer: %s", response.payload.info.manufacturer);
+        logMessage(LOG_DEBUG, "Model: %s", response.payload.info.model);
+        logMessage(LOG_DEBUG, "Firmware: %s", response.payload.info.firmware);
+
+        brl->textColumns = brl->data->model->cellCount;
+        brl->textRows = 1;
+
+        setBrailleKeyTable(brl, brl->data->keyTableDefinition);
+        brl->setBrailleFirmness = setBrailleFirmness;
+
+        return writeRequest(brl);
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+    brl->data = NULL;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  cancelMissingAcknowledgementAlarm(brl);
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  updateCells(brl, brl->buffer, brl->data->model->cellCount, 0);
+  return writeRequest(brl);
+}
+
+static void
+updateKeys (BrailleDisplay *brl, uint64_t newKeys, KeyNumber keyBase, unsigned char keyCount) {
+  const KeyGroup group = FS_GRP_NavigationKeys;
+  KeyNumber number = keyBase;
+
+  KeyNumber pressKeys[keyCount];
+  unsigned int pressCount = 0;
+
+  uint64_t keyBit = UINT64_C(0X1) << keyBase;
+  newKeys <<= keyBase;
+  newKeys |= brl->data->oldKeys & ~(((UINT64_C(0X1) << keyCount) - 1) << keyBase);
+
+  while (brl->data->oldKeys != newKeys) {
+    uint64_t oldKey = brl->data->oldKeys & keyBit;
+    uint64_t newKey = newKeys & keyBit;
+
+    if (oldKey && !newKey) {
+      enqueueKeyEvent(brl, group, number, 0);
+      brl->data->oldKeys &= ~keyBit;
+    } else if (newKey && !oldKey) {
+      pressKeys[pressCount++] = number;
+      brl->data->oldKeys |= keyBit;
+    }
+
+    keyBit <<= 1;
+    number += 1;
+  }
+
+  while (pressCount) enqueueKeyEvent(brl, group, pressKeys[--pressCount], 1);
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  FS_Packet packet;
+  size_t count;
+
+  while ((count = getPacket(brl, &packet))) {
+    switch (packet.header.type) {
+      case FS_PKT_KEY: {
+        uint64_t newKeys = packet.header.arg1 |
+                           (packet.header.arg2 << 8) |
+                           (packet.header.arg3 << 16);
+
+        updateKeys(brl, newKeys, 0, 24);
+        continue;
+      }
+
+      case FS_PKT_EXTKEY: {
+        uint64_t newKeys = packet.payload.extkey.bytes[0];
+
+        updateKeys(brl, newKeys, 24, 8);
+        continue;
+      }
+
+      case FS_PKT_BUTTON: {
+        KeyNumber number = packet.header.arg1;
+        unsigned char press = (packet.header.arg2 & 0X01) != 0;
+        KeyGroup group = packet.header.arg3;
+
+        if (group == modelTypeTable[brl->data->model->type].hotkeysRow) {
+          static const KeyNumber keys[] = {
+            FS_KEY_LeftSelector,
+            FS_KEY_HOT+0, FS_KEY_HOT+1, FS_KEY_HOT+2, FS_KEY_HOT+3,
+            FS_KEY_HOT+4, FS_KEY_HOT+5, FS_KEY_HOT+6, FS_KEY_HOT+7,
+            FS_KEY_RightSelector
+          };
+
+          static const unsigned char keyCount = ARRAY_COUNT(keys);
+          const unsigned char base = (brl->data->model->cellCount - keyCount) / 2;
+
+          if (number < base) {
+            number = FS_KEY_PanLeft;
+          } else if ((number -= base) >= keyCount) {
+            number = FS_KEY_PanRight;
+          } else {
+            number = keys[number];
+          }
+
+          group = FS_GRP_NavigationKeys;
+        } else {
+          group += 1;
+        }
+
+        enqueueKeyEvent(brl, group, number, press);
+        continue;
+      }
+
+      case FS_PKT_WHEEL: {
+        const KeyGroup group = FS_GRP_NavigationKeys;
+        const KeyNumber number = FS_KEY_WHEEL + ((packet.header.arg1 >> 3) & 0X7);
+        unsigned int count = packet.header.arg1 & 0X7;
+
+        while (count) {
+          enqueueKey(brl, group, number);
+          count -= 1;
+        }
+
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(&packet, count);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
+
+static ssize_t
+brl_readPacket (BrailleDisplay *brl, void *buffer, size_t length) {
+  FS_Packet packet;
+  size_t count = readPacket(brl, &packet);
+
+  if (count == 0) return (errno == EAGAIN)? 0: -1;
+  if (count > sizeof(packet.header)) count -= 1;
+
+  if (length < count) {
+    logMessage(LOG_WARNING,
+               "Input packet buffer too small:"
+               " %"PRIsize
+               " < %"PRIsize,
+               length, count);
+    count = length;
+  }
+
+  memcpy(buffer, &packet, count);
+  return count;
+}
+
+static ssize_t
+brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) {
+  const unsigned char *bytes = packet;
+  size_t size = 4;
+
+  if (length >= size) {
+    int hasPayload = 0;
+
+    if (bytes[0] & 0X80) {
+      size += bytes[1];
+      hasPayload = 1;
+    }
+
+    if (length >= size) {
+      if (length > size) {
+        logMessage(LOG_WARNING,
+                   "output packet buffer larger than necessary:"
+                   " %"PRIsize
+                   " > %"PRIsize,
+                   length, size);
+      }
+
+      return writePacket(brl, bytes[0], bytes[1], bytes[2], bytes[3],
+                         (hasPayload? &bytes[4]: NULL))?
+             size: -1;
+    }
+  }
+
+  logMessage(LOG_WARNING,
+             "output packet buffer too small:"
+             " %"PRIsize
+             " < %"PRIsize,
+             length, size);
+
+  errno = EIO;
+  return -1;
+}
+
+static int
+brl_reset (BrailleDisplay *brl) {
+  return 0;
+}
diff --git a/Drivers/Braille/FreedomScientific/brldefs-fs.h b/Drivers/Braille/FreedomScientific/brldefs-fs.h
new file mode 100644
index 0000000..de5214f
--- /dev/null
+++ b/Drivers/Braille/FreedomScientific/brldefs-fs.h
@@ -0,0 +1,138 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_FS_BRLDEFS
+#define BRLTTY_INCLUDED_FS_BRLDEFS
+
+typedef enum {
+  FS_PKT_QUERY = 0X00,  /* host->unit: request device information */
+  FS_PKT_ACK = 0X01,    /* unit->host: acknowledge packet receipt */
+  FS_PKT_NAK = 0X02,    /* unit->host: negative acknowledge, report error */
+  FS_PKT_KEY = 0X03,    /* unit->host: key event */
+  FS_PKT_BUTTON = 0X04, /* unit->host: routing button event */
+  FS_PKT_WHEEL = 0X05,  /* unit->host: whiz wheel event */
+  FS_PKT_HVADJ = 0X08,  /* host->unit: set braille display voltage */
+  FS_PKT_BEEP = 0X09,   /* host->unit: sound short beep */
+  FS_PKT_CONFIG = 0X0F, /* host->unit: configure device options */
+  FS_PKT_INFO = 0X80,   /* unit->host: response to query packet */
+  FS_PKT_WRITE = 0X81,  /* host->unit: write to braille display */
+  FS_PKT_EXTKEY = 0X82  /* unit->host: extended keys event */
+} FS_PacketType;
+
+typedef enum {
+  FS_EXT_HVADJ = 0X08,    /* error in varibraille packet */
+  FS_EXT_BEEP = 0X09,     /* error in beep packet */
+  FS_EXT_CLEAR = 0X31,    /* error in ClearMsgBuf function */
+  FS_EXT_LOOP = 0X32,     /* timing loop in ParseCommands function */
+  FS_EXT_TYPE = 0X33,     /* unknown packet type in ParseCommands function */
+  FS_EXT_CMDWRITE = 0X34, /* error in CmdWrite function */
+  FS_EXT_UPDATE = 0X7E,   /* error in update packet */
+  FS_EXT_DIAG = 0X7F,     /* error in diag packet */
+  FS_EXT_QUERY = 0X80,    /* error in query packet */
+  FS_EXT_WRITE = 0X81     /* error in write packet */
+} FS_ExtendedPacketType;
+
+typedef enum {
+  FS_ERR_TIMEOUT = 0X30,   /* no data received from host for a while */
+  FS_ERR_CHECKSUM = 0X31,  /* incorrect checksum */
+  FS_ERR_TYPE = 0X32,      /* unsupported packet type */
+  FS_ERR_PARAMETER = 0X33, /* invalid parameter */
+  FS_ERR_SIZE = 0X34,      /* write size too large */
+  FS_ERR_POSITION = 0X35,  /* write position too large */
+  FS_ERR_OVERRUN = 0X36,   /* message queue overflow */
+  FS_ERR_POWER = 0X37,     /* insufficient USB power */
+  FS_ERR_SPI = 0X38        /* timeout on SPI bus */
+} FS_ErrorCode;
+
+typedef enum {
+  FS_CFG_EXTKEY = 0X02 /* send extended key events */
+} FS_ConfigFlag;
+
+typedef struct {
+  unsigned char type;
+  unsigned char arg1;
+  unsigned char arg2;
+  unsigned char arg3;
+} FS_PacketHeader;
+
+#define FS_INFO_MANUFACTURER_SIZE 24
+#define FS_INFO_MODEL_SIZE 16
+#define FS_INFO_FIRMWARE_SIZE 8
+
+typedef struct {
+  FS_PacketHeader header;
+
+  union {
+    unsigned char bytes[0X100];
+
+    struct {
+      char manufacturer[FS_INFO_MANUFACTURER_SIZE];
+      char model[FS_INFO_MODEL_SIZE];
+      char firmware[FS_INFO_FIRMWARE_SIZE];
+    } info;
+
+    struct {
+      unsigned char bytes[4];
+    } extkey;
+  } payload;
+} FS_Packet;
+
+#define FS_KEYS_WHEEL 8
+#define FS_KEYS_HOT 8
+
+typedef enum {
+  FS_KEY_Dot1 = 0,
+  FS_KEY_Dot2 = 1,
+  FS_KEY_Dot3 = 2,
+  FS_KEY_Dot4 = 3,
+  FS_KEY_Dot5 = 4,
+  FS_KEY_Dot6 = 5,
+  FS_KEY_Dot7 = 6,
+  FS_KEY_Dot8 = 7,
+
+  FS_KEY_LeftWheel = 8,
+  FS_KEY_RightWheel = 9,
+  FS_KEY_LeftShift = 10,
+  FS_KEY_RightShift = 11,
+  FS_KEY_PanLeft = 12,
+  FS_KEY_PanRight = 13,
+  FS_KEY_Space = 15,
+
+  FS_KEY_LeftSelector = 16,
+  FS_KEY_RightSelector = 17,
+  FS_KEY_LeftBumperUp = 20,
+  FS_KEY_LeftBumperDown = 21,
+  FS_KEY_RightBumperUp = 22,
+  FS_KEY_RightBumperDown = 23,
+
+  FS_KEY_LeftRockerUp = 28,
+  FS_KEY_LeftRockerDown = 29,
+  FS_KEY_RightRockerUp = 30,
+  FS_KEY_RightRockerDown = 31,
+
+  FS_KEY_WHEEL,
+  FS_KEY_HOT = FS_KEY_WHEEL + FS_KEYS_WHEEL
+} FS_NavigationKey;
+
+typedef enum {
+  FS_GRP_NavigationKeys = 0,
+  FS_GRP_RoutingKeys,
+  FS_GRP_NavrowKeys
+} FS_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_FS_BRLDEFS */ 
diff --git a/Drivers/Braille/HID/braille.c b/Drivers/Braille/HID/braille.c
new file mode 100644
index 0000000..ab3fb4d
--- /dev/null
+++ b/Drivers/Braille/HID/braille.c
@@ -0,0 +1,447 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "brldefs-hid.h"
+#include "third_party/brltty/Headers/bitmask.h"
+#include "third_party/brltty/Headers/brl_base.h"
+#include "third_party/brltty/Headers/brl_driver.h"
+#include "third_party/brltty/Headers/brl_types.h"
+#include "third_party/brltty/Headers/brl_utils.h"
+#include "third_party/brltty/Headers/gio_types.h"
+#include "third_party/brltty/Headers/hid_defs.h"
+#include "third_party/brltty/Headers/hid_items.h"
+#include "third_party/brltty/Headers/hid_types.h"
+#include "third_party/brltty/Headers/io_generic.h"
+#include "third_party/brltty/Headers/ktb_types.h"
+#include "third_party/brltty/Headers/log.h"
+#include "third_party/brltty/Programs/gio_internal.h"
+
+struct BrailleDataStruct {
+  struct {
+    unsigned char count;
+    KEYS_BITMASK(mask);
+  } pressedKeys;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAX_OUTPUT_SIZE];
+  } text;
+
+  struct {
+    // The HID report ID used by both input and output. Currently expects both
+    // to be the same.
+    uint32_t reportId;
+    // The size of the HID input report.
+    uint32_t inputSizeBytes;
+    // Map each input report bit to the HID usage it represents.
+    uint32_t inputReportUsages[MAX_INPUT_SIZE];
+    // Map each input report bit to the Key (internal number) it represents.
+    uint32_t inputReportKeys[MAX_INPUT_SIZE];
+    // The first (lowest) bit number of the contiguous group of routing keys.
+    uint32_t inputRoutingFirstBit;
+  } reportInfo;
+};
+
+// Parses the Braille display's HID report descriptor, in order to understand
+// how to parse input reports and prepare output reports.
+static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
+  memset(brl->data->reportInfo.inputReportUsages, 0,
+         MAX_INPUT_SIZE * sizeof(brl->data->reportInfo.inputReportUsages[0]));
+  memset(brl->data->reportInfo.inputReportKeys, 0,
+         MAX_INPUT_SIZE * sizeof(brl->data->reportInfo.inputReportKeys[0]));
+  brl->data->reportInfo.inputRoutingFirstBit = -1;
+
+  // If you'd like to print all items in the HID report then call
+  // hid_inspect#hidListItems()
+
+  // These variables save attributes from the current stack of items:
+  //   The current Usage Page.
+  uint32_t usagePage = 0;
+  //   Number of bits per usage.
+  uint32_t reportSize = 0;
+  //   Number of usages in this stack.
+  uint32_t reportCount = 0;
+  //   The Usages in this stack, stored as a list of individual usages.
+  uint32_t usages[MAX_USAGE_COUNT];
+  uint32_t usageIndex = 0;
+  //   The Usages in this stack, stored as a minimum and maximum value.
+  uint32_t usageMin = 0;
+  uint32_t usageMax = 0;
+  //   The report ID. This is implicitly zero by default, until some value
+  //   provided by the descriptor.
+  uint32_t reportId = 0;
+
+  //   The current bit of the INPUT report, used for writing to the
+  //   inputReportUsages map.
+  uint32_t inputReportBit = 0;
+
+  // These variables will be "learned" while parsing the report descriptor;
+  // set them to "unset" defaults so that parsing can detect if an inconsistency
+  // occurs.
+  //   Stores the number of output cells so that brltty knows how to prepare
+  //   output cells.
+  brl->textColumns = -1;
+  //   The report ID that will be providing Braille Display input and output.
+  brl->data->reportInfo.reportId = -1;
+
+  const unsigned char *nextByte = items->bytes;
+  size_t bytesLeft = items->count;
+  HidItem item;
+  int parsingError = 0;
+  while (1) {
+    if (!hidNextItem(&item, &nextByte, &bytesLeft)) {
+      break;
+    }
+    if (item.tag == HID_ITM_UsagePage) {
+      usagePage = item.value.u;
+    }
+    if (item.tag == HID_ITM_Collection) {
+      // Collections help differentiate between groupings of usages, e.g.
+      // between multiple rows of output braille cells. All devices we've tested
+      // so far provide their BD usages in one collection, so this driver does
+      // not yet support differentiating between collections.
+      //
+      // The type of collection would have been specified by usage items before
+      // this collection item, so reset the usage data structure (since as noted
+      // above we are ignoring collection designations).
+      usageIndex = 0;
+    }
+    if (item.tag == HID_ITM_ReportID) {
+      reportId = item.value.u;
+    }
+    if (item.tag == HID_ITM_Usage) {
+      usageMin = usageMax = -1;
+      uint32_t usage = item.value.u;
+      usages[usageIndex++] = usage;
+    }
+    if (item.tag == HID_ITM_UsageMinimum) {
+      usageMin = item.value.u;
+    }
+    if (item.tag == HID_ITM_UsageMaximum) {
+      usageMax = item.value.u;
+    }
+    if (item.tag == HID_ITM_ReportSize) {
+      reportSize = item.value.u;
+    }
+    if (item.tag == HID_ITM_ReportCount) {
+      reportCount = item.value.u;
+    }
+    if (item.tag == HID_ITM_Input || item.tag == HID_ITM_Output ||
+        item.tag == HID_ITM_Feature) {
+      // Reset the usage index now that we're going to process the usages array.
+      usageIndex = 0;
+      // Set the BD report ID to the current reportId.
+      if (usagePage == HID_UPG_Braille) {
+        if (brl->data->reportInfo.reportId != -1 &&
+            brl->data->reportInfo.reportId != reportId) {
+          logMessage(LOG_ERR,
+                     "Found multiple report IDs that include Braille usages");
+          parsingError = 1;
+          break;
+        }
+        brl->data->reportInfo.reportId = reportId;
+      }
+    }
+
+    if (item.tag == HID_ITM_Input) {
+      if (reportId == brl->data->reportInfo.reportId) {
+        // Skip past constant bits
+        if ((item.value.u & HID_USG_FLG_CONSTANT) == HID_USG_FLG_CONSTANT) {
+          inputReportBit += reportSize * reportCount;
+          continue;
+        }
+        // Skip past usages from unexpected pages.
+        if (usagePage != HID_UPG_Braille && usagePage != HID_UPG_Button) {
+          inputReportBit += reportSize * reportCount;
+          continue;
+        }
+        // Fail if we get a usage of unexpected type or size
+        if (reportSize != 1) {
+          logMessage(LOG_ERR, "Unexpected input item input size %u != 1",
+                     reportSize);
+          parsingError = 1;
+          break;
+        }
+        if ((item.value.u & HID_USG_FLG_VARIABLE) != HID_USG_FLG_VARIABLE) {
+          logMessage(LOG_ERR, "Unexpected non-variable input item");
+          parsingError = 1;
+          break;
+        }
+        // Fail if we get a usage range that doesn't match the report count
+        if (usageMin != -1 && (usageMin + reportCount - 1) != usageMax) {
+          logMessage(LOG_ERR, "Invalid usage range: min=%u max=%u count=%u",
+                     usageMin, usageMax, reportCount);
+          parsingError = 1;
+          break;
+        }
+        if (inputReportBit > MAX_INPUT_SIZE) {
+          logMessage(LOG_ERR, "Unexpected input report with more than %u bits",
+                     MAX_INPUT_SIZE);
+          parsingError = 1;
+          break;
+        }
+        for (int i = 0; i < reportCount; i++) {
+          if (usageMin != -1) {
+            brl->data->reportInfo.inputReportUsages[inputReportBit++] =
+                usageMin++;
+          } else {
+            brl->data->reportInfo.inputReportUsages[inputReportBit++] =
+                usages[i];
+          }
+        }
+      }
+    }
+    if (item.tag == HID_ITM_Output) {
+      if (usagePage == HID_UPG_Braille) {
+        if (reportId != brl->data->reportInfo.reportId) {
+          logMessage(LOG_ERR,
+                     "Unexpected differing output and input report IDs");
+          parsingError = 1;
+          break;
+        }
+        if (reportSize != 8) {
+          logMessage(LOG_ERR, "Invalid output bit size %u", reportSize);
+          parsingError = 1;
+          break;
+        }
+        if (brl->textColumns != -1) {
+          logMessage(LOG_ERR, "Unexpected received multiple BD output reports");
+          parsingError = 1;
+          break;
+        }
+        brl->textColumns = reportCount;
+      }
+    }
+  }
+  free(items);
+  if (parsingError) {
+    logMessage(LOG_ERR, "There were parsing errors.");
+    return 0;
+  }
+  if (brl->data->reportInfo.reportId == -1) {
+    logMessage(LOG_ERR, "Could not find a Braille Display report ID");
+    return 0;
+  }
+  if (brl->textColumns == -1) {
+    logMessage(LOG_ERR, "Could not find the Braille Display output cell count");
+    return 0;
+  }
+  for (int i = 0; i < MAX_INPUT_SIZE; i++) {
+    logMessage(LOG_DEBUG, "bit=%d report=%i", i,
+               brl->data->reportInfo.inputReportUsages[i]);
+
+    // While parsing the descript we built up map inputReportUsages from
+    // bit->usage. However, brltty doesn't use usages to describe key events:
+    // brltty uses a key table that was provided by brl_construct. This logic
+    // builds up a new map from bit->key where the key values come from the key
+    // table that we provided to brltty. This allows the driver to map from
+    // INPUT bit to the brltty-known key name.
+    for (int j = 0; j < KEY_MAP_COUNT; j++) {
+      if (KEY_MAP[j][0] == brl->data->reportInfo.inputReportUsages[i]) {
+        brl->data->reportInfo.inputReportKeys[i] = KEY_MAP[j][1];
+      }
+    }
+    // Routing keys are handled differently. They all use the same usage,
+    // while their bit number (starting from the first one) defines the actual
+    // routing key number.
+    if (brl->data->reportInfo.inputReportUsages[i] == HID_USG_BRL_RouterKey) {
+      if (brl->data->reportInfo.inputRoutingFirstBit == -1) {
+        brl->data->reportInfo.inputRoutingFirstBit = i;
+      } else if (brl->data->reportInfo.inputReportUsages[i - 1] !=
+                 HID_USG_BRL_RouterKey) {
+        // Expect that all routing key INPUTs are sent as a contiguous group, so
+        // return error if the descriptor describes something like "... ROUTING
+        // DOT1 ROUTING ...".
+        logMessage(LOG_ERR,
+                   "Unexpected non-contiguous group of router keys at "
+                   "%d with previous entry %u and first bit %d",
+                   i, brl->data->reportInfo.inputReportUsages[i - 1],
+                   brl->data->reportInfo.inputRoutingFirstBit);
+        return 0;
+      }
+    }
+  }
+
+  int inputSizeBytes = (inputReportBit + 7) / 8;
+  int hasNumberedReport = brl->data->reportInfo.reportId != 0;
+  if (hasNumberedReport != 0) {
+    // The first byte of input should contain the report ID, then all other
+    // bytes should be parsed as input.
+    brl->data->reportInfo.inputSizeBytes = inputSizeBytes + 1;
+  } else {
+    // The entire input report should be parsed as input.
+    brl->data->reportInfo.inputSizeBytes = inputSizeBytes;
+  }
+  // Zero-out the input key mask used to track the current state of input key
+  // presses.
+  BITMASK_ZERO(brl->data->pressedKeys.mask);
+  return 1;
+}
+
+// Enqueues a key event to brltty's internal key processing logic.
+// brltty waits for all keys to be released, then looks at the combined set of
+// key-down events to understand what key combination was pressed by looking
+// up possible key combinations from the HID.ktb keytable.
+static int handleKeyEvent(BrailleDisplay *brl, unsigned char key, int press) {
+  KeyGroup group;
+  if (key < HID_KEY_ROUTING) {
+    group = HID_GRP_NavigationKeys;
+  } else {
+    group = HID_GRP_RoutingKeys;
+    key -= HID_KEY_ROUTING;
+  }
+  return enqueueKeyEvent(brl, group, key, press);
+}
+
+// Possibly enqueues a key-down action.
+static int handleKeyPress(BrailleDisplay *brl, unsigned char key) {
+  if (BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
+
+  BITMASK_SET(brl->data->pressedKeys.mask, key);
+  brl->data->pressedKeys.count += 1;
+
+  handleKeyEvent(brl, key, 1);
+  return 1;
+}
+
+// Possibly enqueues a key-action action.
+static int handleKeyRelease(BrailleDisplay *brl, unsigned char key) {
+  if (!BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
+
+  BITMASK_CLEAR(brl->data->pressedKeys.mask, key);
+  brl->data->pressedKeys.count -= 1;
+
+  handleKeyEvent(brl, key, 0);
+  return 1;
+}
+
+// Parses a HID input report into brltty key actions.
+// Called by brltty when it wants to parse an input byte array.
+static void handlePressedKeysArray(BrailleDisplay *brl, unsigned char *keys) {
+  // Per HIDRAW spec if input descriptor report number is not zero then the
+  // first byte in the input should be the report number.
+  int hasNumberedReport = brl->data->reportInfo.reportId != 0;
+  if (hasNumberedReport && keys[0] != brl->data->reportInfo.reportId) {
+    logMessage(LOG_WARNING, "Unexpected input report %u", keys[0]);
+    return;
+  }
+
+  int numInputBytes = hasNumberedReport
+                          ? brl->data->reportInfo.inputSizeBytes - 1
+                          : brl->data->reportInfo.inputSizeBytes;
+  const unsigned char *byte = hasNumberedReport ? keys + 1 : keys;
+  for (int byteNum = 0; byteNum < numInputBytes; byteNum++) {
+    for (int bit = 0; bit <= 7; bit++) {
+      int bitNum = byteNum * 8 + bit;
+      char key;
+      if (brl->data->reportInfo.inputReportUsages[bitNum] ==
+          HID_USG_BRL_RouterKey) {
+        int routingKeyNum = bitNum - brl->data->reportInfo.inputRoutingFirstBit;
+        key = HID_KEY_ROUTING + routingKeyNum;
+      } else {
+        key = brl->data->reportInfo.inputReportKeys[bitNum];
+      }
+      if (key != 0) {
+        if ((*byte) & (1 << bit)) {
+          logMessage(LOG_DEBUG, "Pressed bit %d usage %u", bitNum,
+                     brl->data->reportInfo.inputReportUsages[bitNum]);
+          handleKeyPress(brl, key);
+        } else {
+          handleKeyRelease(brl, key);
+        }
+      }
+    }
+    byte++;
+  }
+}
+
+static int writeHidCells(BrailleDisplay *brl, const unsigned char *cells,
+                         unsigned char cellCount) {
+  // HIDRAW expects the report ID in the first byte, followed by the
+  // output report.
+  int bufferSize = cellCount + 1;
+  unsigned char buffer[bufferSize];
+  buffer[0] = brl->data->reportInfo.reportId;
+  memcpy(buffer + 1, cells, cellCount);
+  return brl->gioEndpoint->handleMethods->writeData(
+      brl->gioEndpoint->handle, buffer, bufferSize, /*timeout=*/0);
+}
+
+// Standard functions expected by brltty for any brltty driver. These were
+// essentially copied the Humanware driver with minimal modifications.
+
+static int connectResource(BrailleDisplay *brl, const char *identifier) {
+  static const HidModelEntry hidModelTable[] = {
+      {
+          // Model name is not used to control driver behavior, so always
+          // expect "HID" as set by hid_android.c#getGenericHIDDeviceName().
+          .name = "HID",
+      },
+      {.name = NULL, .vendor = 0}};
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+  descriptor.hid.modelTable = hidModelTable;
+  return connectBrailleResource(brl, identifier, &descriptor, NULL) ? 1 : 0;
+}
+
+static int brl_construct(BrailleDisplay *brl, char **parameters,
+                         const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      HidItemsDescriptor *items =
+          brl->gioEndpoint->handleMethods->getHidDescriptor(
+              brl->gioEndpoint->handle);
+      if (probeHidDisplay(brl, items)) {
+        setBrailleKeyTable(brl, &keyTableDefinition_HID);
+        makeOutputTable(dotsTable_ISO11548_1);
+        brl->data->text.rewrite = 1;
+        return 1;
+      }
+      disconnectBrailleResource(brl, NULL);
+    }
+    free(brl->data);
+    brl->data = NULL;
+  } else {
+    logMallocError();
+  }
+  return 0;
+}
+
+static void brl_destruct(BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+  free(brl->data);
+}
+
+// Called by brltty when it wants to write output to the Braille display.
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text) {
+  const size_t count = brl->textColumns;
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, count, NULL, NULL,
+                       &brl->data->text.rewrite)) {
+    unsigned char cells[count];
+
+    translateOutputCells(cells, brl->data->text.cells, count);
+    if (!writeHidCells(brl, cells, count)) return 0;
+  }
+  return 1;
+}
+
+static int brl_readCommand(BrailleDisplay *brl,
+                           KeyTableCommandContext context) {
+  unsigned char packet[MAX_INPUT_SIZE];
+  while (1) {
+    size_t length = brl->gioEndpoint->handleMethods->readData(
+        brl->gioEndpoint->handle, packet, MAX_INPUT_SIZE,
+        /*initialTimeout unused*/ 0, /*subsequentTimeout unused*/ 0);
+    if (length == 0) {
+      break;
+    }
+    handlePressedKeysArray(brl, packet);
+  }
+  return EOF;
+}
diff --git a/Drivers/Braille/HID/brldefs-hid.h b/Drivers/Braille/HID/brldefs-hid.h
new file mode 100644
index 0000000..ae0c998
--- /dev/null
+++ b/Drivers/Braille/HID/brldefs-hid.h
@@ -0,0 +1,97 @@
+#ifndef BRLTTY_INCLUDED_HID_BRLDEFS
+#define BRLTTY_INCLUDED_HID_BRLDEFS
+
+#include "third_party/brltty/Headers/hid_defs.h"
+#include "third_party/brltty/Headers/ktb_types.h"
+
+#define MAX_INPUT_SIZE 0XFF
+#define MAX_OUTPUT_SIZE 0XFF
+#define MAX_USAGE_COUNT 0xFF
+#define MAXIMUM_KEY_VALUE 0XFF
+#define KEYS_BITMASK(name) BITMASK(name, (MAXIMUM_KEY_VALUE + 1), int)
+
+// Enum of Key values that are used by the KEYS_BITMASK in order to track
+// which keys are currently pressed. The specific values are meaningless and
+// only serve as a temporary identifier in the input parsing logic.
+typedef enum {
+  HID_KEY_Dot1 = 1,
+  HID_KEY_Dot2,
+  HID_KEY_Dot3,
+  HID_KEY_Dot4,
+  HID_KEY_Dot5,
+  HID_KEY_Dot6,
+  HID_KEY_Dot7,
+  HID_KEY_Dot8,
+  HID_KEY_Space,
+
+  HID_KEY_PanLeft,
+  HID_KEY_PanRight,
+
+  HID_KEY_DPadUp,
+  HID_KEY_DPadDown,
+  HID_KEY_DPadLeft,
+  HID_KEY_DPadRight,
+  HID_KEY_DPadCenter,
+
+  HID_KEY_RockerUp,
+  HID_KEY_RockerDown,
+
+  HID_KEY_ROUTING,
+} HID_Keys;
+
+// Maps from official Braille Display HID usages to the custom Key enum.
+int KEY_MAP[][2] = {
+    {HID_USG_BRL_KeyboardDot1, HID_KEY_Dot1},
+    {HID_USG_BRL_KeyboardDot2, HID_KEY_Dot2},
+    {HID_USG_BRL_KeyboardDot3, HID_KEY_Dot3},
+    {HID_USG_BRL_KeyboardDot4, HID_KEY_Dot4},
+    {HID_USG_BRL_KeyboardDot5, HID_KEY_Dot5},
+    {HID_USG_BRL_KeyboardDot6, HID_KEY_Dot6},
+    {HID_USG_BRL_KeyboardDot7, HID_KEY_Dot7},
+    {HID_USG_BRL_KeyboardDot8, HID_KEY_Dot8},
+    {HID_USG_BRL_KeyboardSpace, HID_KEY_Space},
+    {HID_USG_BRL_PanLeft, HID_KEY_PanLeft},
+    {HID_USG_BRL_PanRight, HID_KEY_PanRight},
+    {HID_USG_BRL_DPadUp, HID_KEY_DPadUp},
+    {HID_USG_BRL_DPadDown, HID_KEY_DPadDown},
+    {HID_USG_BRL_DPadLeft, HID_KEY_DPadLeft},
+    {HID_USG_BRL_DPadRight, HID_KEY_DPadRight},
+    {HID_USG_BRL_DPadCenter, HID_KEY_DPadCenter},
+    {HID_USG_BRL_RockerUp, HID_KEY_RockerUp},
+    {HID_USG_BRL_RockerDown, HID_KEY_RockerDown},
+    // Router keys are handled separately.
+};
+int KEY_MAP_COUNT = sizeof(KEY_MAP) / sizeof(KEY_MAP[0]);
+
+typedef enum { HID_GRP_NavigationKeys = 0, HID_GRP_RoutingKeys } HID_KEYGroup;
+
+// Maps from the Key enum to a textual Key name used by the HID.ktb keytable.
+static const KeyNameEntry keyNameTable[] = {
+    {.value.number = HID_KEY_Dot1, .name = "Dot1"},
+    {.value.number = HID_KEY_Dot2, .name = "Dot2"},
+    {.value.number = HID_KEY_Dot3, .name = "Dot3"},
+    {.value.number = HID_KEY_Dot4, .name = "Dot4"},
+    {.value.number = HID_KEY_Dot5, .name = "Dot5"},
+    {.value.number = HID_KEY_Dot6, .name = "Dot6"},
+    {.value.number = HID_KEY_Dot7, .name = "Dot7"},
+    {.value.number = HID_KEY_Dot8, .name = "Dot8"},
+    {.value.number = HID_KEY_Space, .name = "Space"},
+    {.value.number = HID_KEY_PanLeft, .name = "PanLeft"},
+    {.value.number = HID_KEY_PanRight, .name = "PanRight"},
+    {.value.number = HID_KEY_DPadUp, .name = "DPadUp"},
+    {.value.number = HID_KEY_DPadDown, .name = "DPadDown"},
+    {.value.number = HID_KEY_DPadLeft, .name = "DPadLeft"},
+    {.value.number = HID_KEY_DPadRight, .name = "DPadRight"},
+    {.value.number = HID_KEY_DPadCenter, .name = "DPadCenter"},
+    {.value.number = HID_KEY_RockerUp, .name = "RockerUp"},
+    {.value.number = HID_KEY_RockerDown, .name = "RockerDown"},
+    {.value = {.group = HID_GRP_RoutingKeys, .number = KTB_KEY_ANY},
+     .name = "RoutingKey"},
+    {.name = NULL}};
+
+static const KeyNameEntry *const keyNameTables_HID[] = {keyNameTable, NULL};
+
+static const KeyTableDefinition keyTableDefinition_HID = {
+    .bindings = "HID", .names = keyNameTables_HID};
+
+#endif /* BRLTTY_INCLUDED_HID_BRLDEFS */
diff --git a/Drivers/Braille/HIMS/Makefile.in b/Drivers/Braille/HIMS/Makefile.in
new file mode 100644
index 0000000..d6f806d
--- /dev/null
+++ b/Drivers/Braille/HIMS/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = hm
+DRIVER_NAME = HIMS
+DRIVER_USAGE = Braille Sense, SyncBraille, Braille Edge, Smart Beetle, QBrailleXL
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/HIMS/braille.c b/Drivers/Braille/HIMS/braille.c
new file mode 100644
index 0000000..b826c9e
--- /dev/null
+++ b/Drivers/Braille/HIMS/braille.c
@@ -0,0 +1,740 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-hm.h"
+
+#define MAXIMUM_CELL_COUNT 40
+
+BEGIN_KEY_NAME_TABLE(common)
+  KEY_GROUP_ENTRY(HM_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(braille)
+  KEY_NAME_ENTRY(HM_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(HM_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(HM_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(HM_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(HM_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(HM_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(HM_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(HM_KEY_Dot8, "Dot8"),
+  KEY_NAME_ENTRY(HM_KEY_Space, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(pan)
+  KEY_NAME_ENTRY(HM_KEY_Backward, "Backward"),
+  KEY_NAME_ENTRY(HM_KEY_Forward, "Forward"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(BS_scroll)
+  KEY_NAME_ENTRY(HM_KEY_BS_LeftScrollUp, "LeftScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_BS_LeftScrollDown, "LeftScrollDown"),
+  KEY_NAME_ENTRY(HM_KEY_BS_RightScrollUp, "RightScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_BS_RightScrollDown, "RightScrollDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(BE_scroll)
+  KEY_NAME_ENTRY(HM_KEY_BE_LeftScrollUp, "LeftScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_BE_LeftScrollDown, "LeftScrollDown"),
+  KEY_NAME_ENTRY(HM_KEY_BE_RightScrollUp, "RightScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_BE_RightScrollDown, "RightScrollDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(f14)
+  KEY_NAME_ENTRY(HM_KEY_F1, "F1"),
+  KEY_NAME_ENTRY(HM_KEY_F2, "F2"),
+  KEY_NAME_ENTRY(HM_KEY_F3, "F3"),
+  KEY_NAME_ENTRY(HM_KEY_F4, "F4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(f58)
+  KEY_NAME_ENTRY(HM_KEY_F5, "F5"),
+  KEY_NAME_ENTRY(HM_KEY_F6, "F6"),
+  KEY_NAME_ENTRY(HM_KEY_F7, "F7"),
+  KEY_NAME_ENTRY(HM_KEY_F8, "F8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(lp)
+  KEY_NAME_ENTRY(HM_KEY_LeftPadUp, "LeftPadUp"),
+  KEY_NAME_ENTRY(HM_KEY_LeftPadDown, "LeftPadDown"),
+  KEY_NAME_ENTRY(HM_KEY_LeftPadLeft, "LeftPadLeft"),
+  KEY_NAME_ENTRY(HM_KEY_LeftPadRight, "LeftPadRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(rp)
+  KEY_NAME_ENTRY(HM_KEY_RightPadUp, "RightPadUp"),
+  KEY_NAME_ENTRY(HM_KEY_RightPadDown, "RightPadDown"),
+  KEY_NAME_ENTRY(HM_KEY_RightPadLeft, "RightPadLeft"),
+  KEY_NAME_ENTRY(HM_KEY_RightPadRight, "RightPadRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(pan)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(f14),
+  KEY_NAME_TABLE(pan),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(scroll)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(f14),
+  KEY_NAME_TABLE(BS_scroll),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(qwerty)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(f14),
+  KEY_NAME_TABLE(BS_scroll),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(edge)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(f14),
+  KEY_NAME_TABLE(f58),
+  KEY_NAME_TABLE(BE_scroll),
+  KEY_NAME_TABLE(lp),
+  KEY_NAME_TABLE(rp),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(SB_scroll)
+  KEY_NAME_ENTRY(HM_KEY_SB_LeftScrollUp, "LeftScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_SB_LeftScrollDown, "LeftScrollDown"),
+  KEY_NAME_ENTRY(HM_KEY_SB_RightScrollUp, "RightScrollUp"),
+  KEY_NAME_ENTRY(HM_KEY_SB_RightScrollDown, "RightScrollDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(sync)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(SB_scroll),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(beetle)
+  KEY_NAME_ENTRY(HM_KEY_BS_RightScrollUp, "Backward"),
+  KEY_NAME_ENTRY(HM_KEY_BS_RightScrollDown, "Forward"),
+
+  KEY_NAME_ENTRY(HM_KEY_F1, "F1"),
+  KEY_NAME_ENTRY(HM_KEY_F4, "F2"),
+  KEY_NAME_ENTRY(HM_KEY_F3, "F3"),
+  KEY_NAME_ENTRY(HM_KEY_F2, "F4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(beetle)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(beetle),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(pan)
+DEFINE_KEY_TABLE(scroll)
+DEFINE_KEY_TABLE(qwerty)
+DEFINE_KEY_TABLE(edge)
+DEFINE_KEY_TABLE(sync)
+DEFINE_KEY_TABLE(beetle)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(pan),
+  &KEY_TABLE_DEFINITION(scroll),
+  &KEY_TABLE_DEFINITION(qwerty),
+  &KEY_TABLE_DEFINITION(edge),
+  &KEY_TABLE_DEFINITION(sync),
+  &KEY_TABLE_DEFINITION(beetle),
+END_KEY_TABLE_LIST
+
+typedef enum {
+  IPT_CURSOR  = 0X00,
+  IPT_KEYS    = 0X01,
+  IPT_CELLS   = 0X02
+} InputPacketType;
+
+typedef union {
+  unsigned char bytes[10];
+
+  struct {
+    unsigned char start;
+    unsigned char type;
+    unsigned char count;
+    unsigned char data;
+    unsigned char reserved[4];
+    unsigned char checksum;
+    unsigned char end;
+  } PACKED data;
+} PACKED InputPacket;
+
+typedef struct {
+  const KeyTableDefinition *keyTable;
+  unsigned char id1;
+  unsigned char id2;
+} IdentityEntry;
+
+static const IdentityEntry panIdentity = {
+  .keyTable = &KEY_TABLE_DEFINITION(pan)
+};
+
+static const IdentityEntry scrollIdentity = {
+  .id1 = 0X4C, .id2 = 0X58,
+  .keyTable = &KEY_TABLE_DEFINITION(scroll)
+};
+
+static const IdentityEntry qwerty2Identity = {
+  .id1 = 0X53, .id2 = 0X58,
+  .keyTable = &KEY_TABLE_DEFINITION(qwerty)
+};
+
+static const IdentityEntry qwerty1Identity = {
+  .id1 = 0X51, .id2 = 0X58,
+  .keyTable = &KEY_TABLE_DEFINITION(qwerty)
+};
+
+static const IdentityEntry edgeIdentity = {
+  .id1 = 0X42, .id2 = 0X45,
+  .keyTable = &KEY_TABLE_DEFINITION(edge)
+};
+
+typedef struct {
+  const char *modelName;
+  const char *resourceNamePrefix;
+  const KeyTableDefinition *keyTable;
+  const KeyTableDefinition * (*testIdentities) (BrailleDisplay *brl);
+  int (*getDefaultCellCount) (BrailleDisplay *brl, unsigned int *count);
+} ProtocolEntry;
+
+struct BrailleDataStruct {
+  const ProtocolEntry *protocol;
+  unsigned char previousCells[MAXIMUM_CELL_COUNT];
+};
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1: {
+      switch (byte) {
+        case 0X1C:
+          *length = 4;
+          break;
+
+        case 0XFA:
+          *length = 10;
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  if (size == *length) {
+    switch (bytes[0]) {
+      case 0X1C: {
+        if (byte != 0X1F) return BRL_PVR_INVALID;
+        break;
+      }
+
+      case 0XFA: {
+        if (byte != 0XFB) return BRL_PVR_INVALID;
+
+        const InputPacket *packet = (const void *)bytes;
+        int checksum = -packet->data.checksum;
+        for (size_t i=0; i<size; i+=1) checksum += packet->bytes[i];
+
+        if ((checksum & 0XFF) != packet->data.checksum) {
+          logInputProblem("incorrect input checksum", packet->bytes, size);
+          return BRL_PVR_INVALID;
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, InputPacket *packet) {
+  return readBraillePacket(brl, NULL, packet, sizeof(*packet), verifyPacket, NULL);
+}
+
+static size_t
+readBytes (BrailleDisplay *brl, void *packet, size_t size) {
+  return readPacket(brl, packet);
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+testIdentity (BrailleDisplay *brl, unsigned char id1, unsigned char id2) {
+  const unsigned char sequence[] = {0X1C, id1, id2, 0X1F};
+  const size_t length = sizeof(sequence);
+
+  if (writeBytes(brl, sequence, length)) {
+    while (awaitBrailleInput(brl, 200)) {
+      InputPacket response;
+      size_t size = readPacket(brl, &response);
+      if (!size) break;
+
+      if (response.bytes[0] == sequence[0]) {
+        return memcmp(response.bytes, sequence, length) == 0;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static const KeyTableDefinition *
+testIdentities (BrailleDisplay *brl, const IdentityEntry *const *identities) {
+  while (*identities) {
+    const IdentityEntry *identity = *identities;
+    const char *name = identity->keyTable->bindings;
+
+    if (!identity->id1 && !identity->id2) {
+      logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "assuming identity: %s", name);
+    } else {
+      logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "testing identity: %s", name);
+
+      if (!testIdentity(brl, identity->id1, identity->id2)) {
+        identities += 1;
+        continue;
+      }
+    }
+
+    return identity->keyTable;
+  }
+
+  return NULL;
+}
+
+static const KeyTableDefinition *
+testBrailleSenseIdentities (BrailleDisplay *brl) {
+  static const IdentityEntry *const identities[] = {
+    &qwerty2Identity,
+    &qwerty1Identity,
+    &scrollIdentity,
+    &panIdentity,
+    NULL
+  };
+
+  return testIdentities(brl, identities);
+}
+
+static const KeyTableDefinition *
+testBrailleEdgeIdentities (BrailleDisplay *brl) {
+  static const IdentityEntry *const identities[] = {
+    &edgeIdentity,
+    NULL
+  };
+
+  return testIdentities(brl, identities);
+}
+
+static int
+writePacket (
+  BrailleDisplay *brl,
+  unsigned char type, unsigned char mode,
+  const unsigned char *data1, size_t length1,
+  const unsigned char *data2, size_t length2
+) {
+  unsigned char packet[2 + 1 + 1 + 2 + length1 + 1 + 1 + 2 + length2 + 1 + 4 + 1 + 2];
+  unsigned char *byte = packet;
+  unsigned char *checksum;
+
+  /* DS */
+  *byte++ = type;
+  *byte++ = type;
+
+  /* M */
+  *byte++ = mode;
+
+  /* DS1 */
+  *byte++ = 0XF0;
+
+  /* Cnt1 */
+  *byte++ = (length1 >> 0) & 0XFF;
+  *byte++ = (length1 >> 8) & 0XFF;
+
+  /* D1 */
+  byte = mempcpy(byte, data1, length1);
+
+  /* DE1 */
+  *byte++ = 0XF1;
+
+  /* DS2 */
+  *byte++ = 0XF2;
+
+  /* Cnt2 */
+  *byte++ = (length2 >> 0) & 0XFF;
+  *byte++ = (length2 >> 8) & 0XFF;
+
+  /* D2 */
+  if (data2) byte = mempcpy(byte, data2, length2);
+
+  /* DE2 */
+  *byte++ = 0XF3;
+
+  /* Reserved */
+  {
+    int count = 4;
+    while (count--) *byte++ = 0;
+  }
+
+  /* Chk */
+  *(checksum = byte++) = 0;
+
+  /* DE */
+  *byte++ = 0XFD;
+  *byte++ = 0XFD;
+
+  {
+    unsigned char sum = 0;
+    const unsigned char *ptr = packet;
+    while (ptr != byte) sum += *ptr++;
+    *checksum = sum;
+  }
+
+  return writeBytes(brl, packet, byte - packet);
+}
+
+
+static int
+getBrailleSenseDefaultCellCount (BrailleDisplay *brl, unsigned int *count) {
+  *count = 32;
+  return 1;
+}
+
+static const ProtocolEntry brailleSenseProtocol = {
+  .modelName = "Braille Sense",
+  .keyTable = &KEY_TABLE_DEFINITION(pan),
+  .testIdentities = testBrailleSenseIdentities,
+  .getDefaultCellCount = getBrailleSenseDefaultCellCount
+};
+
+
+static int
+getSyncBrailleDefaultCellCount (BrailleDisplay *brl, unsigned int *count) {
+  return 0;
+}
+
+static const ProtocolEntry syncBrailleProtocol = {
+  .modelName = "SyncBraille",
+  .keyTable = &KEY_TABLE_DEFINITION(sync),
+  .getDefaultCellCount = getSyncBrailleDefaultCellCount
+};
+
+
+static int
+getBrailleEdgeDefaultCellCount (BrailleDisplay *brl, unsigned int *count) {
+  *count = 40;
+  return 1;
+}
+
+static const ProtocolEntry brailleEdgeProtocol = {
+  .modelName = "Braille Edge",
+  .resourceNamePrefix = "BrailleEDGE",
+  .keyTable = &KEY_TABLE_DEFINITION(edge),
+  .testIdentities = testBrailleEdgeIdentities,
+  .getDefaultCellCount = getBrailleEdgeDefaultCellCount
+};
+
+
+static const ProtocolEntry *protocolTable[] = {
+  &brailleSenseProtocol,
+  &syncBrailleProtocol,
+  &brailleEdgeProtocol,
+  NULL
+};
+
+
+static int
+writeCells (BrailleDisplay *brl) {
+  const size_t count = MIN(brl->textColumns*brl->textRows, MAXIMUM_CELL_COUNT);
+  unsigned char cells[count];
+
+  translateOutputCells(cells, brl->data->previousCells, count);
+  return writePacket(brl, 0XFC, 0X01, cells, count, NULL, 0);
+}
+
+static int
+clearCells (BrailleDisplay *brl) {
+  memset(brl->data->previousCells, 0, MIN(brl->textColumns*brl->textRows, MAXIMUM_CELL_COUNT));
+  return writeCells(brl);
+}
+
+static int
+writeCellCountRequest (BrailleDisplay *brl) {
+  static const unsigned char data[] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+  };
+
+  return writePacket(brl, 0XFB, 0X01, data, sizeof(data), NULL, 0);
+}
+
+static BrailleResponseResult
+isCellCountResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const InputPacket *response = packet;
+
+  return (response->data.type == IPT_CELLS)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+getCellCount (BrailleDisplay *brl, unsigned int *count) {
+  InputPacket response;
+
+  if (probeBrailleDisplay(brl, 2, NULL, 1000,
+                          writeCellCountRequest,
+                          readBytes, &response, sizeof(response.bytes),
+                          isCellCountResponse)) {
+    *count = response.data.data;
+    return 1;
+  }
+
+  return brl->data->protocol->getDefaultCellCount(brl, count);
+}
+
+static void
+setKeyTable (BrailleDisplay *brl, const KeyTableDefinition *ktd) {
+  if (!ktd) ktd = brl->data->protocol->keyTable;
+
+  switch (brl->textColumns) {
+    case 14:
+      if (ktd == &KEY_TABLE_DEFINITION(scroll)) {
+        ktd = &KEY_TABLE_DEFINITION(beetle);
+      }
+      break;
+  }
+
+  setBrailleKeyTable(brl, ktd);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 115200
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_0403_6001)
+    "FTDI",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Braille Sense (USB 1.1) */
+      .version = UsbSpecificationVersion_1_1,
+      .vendor=0X045E, .product=0X930A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&brailleSenseProtocol
+    },
+
+    { /* Braille Sense (USB 2.0) */
+      .version = UsbSpecificationVersion_2_0,
+      .vendor=0X045E, .product=0X930A,
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface=1,
+      .disableAutosuspend=1,
+      .data=&brailleSenseProtocol
+    },
+
+    { /* Braille Sense U2 (USB 2.0) */
+      .version = UsbSpecificationVersion_2_0,
+      .vendor=0X045E, .product=0X930A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface=1,
+      .disableAutosuspend=1,
+      .data=&brailleSenseProtocol
+    },
+
+    { /* Sync Braille */
+      .vendor=0X0403, .product=0X6001,
+      .manufacturers = usbManufacturers_0403_6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .data=&syncBrailleProtocol
+    },
+
+    { /* Braille Edge and QBrailleXL */
+      .vendor=0X045E, .product=0X930B,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .disableAutosuspend=1,
+      .data=&brailleEdgeProtocol
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &brailleSenseProtocol;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  descriptor.bluetooth.channelNumber = 4;
+  descriptor.bluetooth.discoverChannel = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      if (!(brl->data->protocol = gioGetApplicationData(brl->gioEndpoint))) {
+        char *name = gioGetResourceName(brl->gioEndpoint);
+        brl->data->protocol = &brailleSenseProtocol;
+
+        if (name) {
+          const ProtocolEntry *const *protocolAddress = protocolTable;
+
+          while (*protocolAddress) {
+            const ProtocolEntry *protocol = *protocolAddress;
+            const char *prefix = protocol->resourceNamePrefix;
+
+            if (prefix) {
+              if (strncasecmp(name, prefix, strlen(prefix)) == 0) {
+                brl->data->protocol = protocol;
+                break;
+              }
+            }
+
+            protocolAddress += 1;
+          }
+
+          free(name);
+        }
+      }
+
+      logMessage(LOG_INFO, "detected: %s", brl->data->protocol->modelName);
+
+      const KeyTableDefinition *ktd =
+        brl->data->protocol->testIdentities?
+          brl->data->protocol->testIdentities(brl):
+          NULL;
+
+      if (getCellCount(brl, &brl->textColumns)) {
+        brl->textRows = 1;
+
+        setKeyTable(brl, ktd);
+        makeOutputTable(dotsTable_ISO11548_1);
+  
+        if (clearCells(brl)) return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  size_t count = brl->textColumns * brl->textRows;
+
+  if (cellsHaveChanged(brl->data->previousCells, brl->buffer, count, NULL, NULL, NULL)) {
+    if (!writeCells(brl)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  InputPacket packet;
+  int length;
+
+  while ((length = readPacket(brl, &packet))) {
+    switch (packet.data.type) {
+      case IPT_CURSOR: {
+        unsigned char key = packet.data.data;
+
+        enqueueKey(brl, HM_GRP_RoutingKeys, key);
+        continue;
+      }
+
+      case IPT_KEYS: {
+        KeyNumberSet bits = (packet.data.reserved[0] << 0X00)
+                          | (packet.data.reserved[1] << 0X08)
+                          | (packet.data.reserved[2] << 0X10)
+                          | (packet.data.reserved[3] << 0X18);
+
+        enqueueKeys(brl, bits, HM_GRP_NavigationKeys, 0);
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(&packet, length);
+  }
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+
+  return EOF;
+}
diff --git a/Drivers/Braille/HIMS/brldefs-hm.h b/Drivers/Braille/HIMS/brldefs-hm.h
new file mode 100644
index 0000000..b9c3e55
--- /dev/null
+++ b/Drivers/Braille/HIMS/brldefs-hm.h
@@ -0,0 +1,86 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HM_BRLDEFS
+#define BRLTTY_INCLUDED_HM_BRLDEFS
+
+typedef enum {
+  /* braille keyboard keys */
+  HM_KEY_Dot1  = 0,
+  HM_KEY_Dot2  = 1,
+  HM_KEY_Dot3  = 2,
+  HM_KEY_Dot4  = 3,
+  HM_KEY_Dot5  = 4,
+  HM_KEY_Dot6  = 5,
+  HM_KEY_Dot7  = 6,
+  HM_KEY_Dot8  = 7,
+  HM_KEY_Space = 8,
+
+  /* Braille Sense/Edge function keys */
+  HM_KEY_F1 =  9,
+  HM_KEY_F2 = 10,
+  HM_KEY_F3 = 11,
+  HM_KEY_F4 = 12,
+
+  /* Braille Sense panning keys */
+  HM_KEY_Backward = 13,
+  HM_KEY_Forward  = 14,
+
+  /* SyncBraille scroll keys */
+  HM_KEY_SB_LeftScrollUp    = 12,
+  HM_KEY_SB_RightScrollUp   = 13,
+  HM_KEY_SB_RightScrollDown = 14,
+  HM_KEY_SB_LeftScrollDown  = 15,
+
+  /* Braille Sense scroll keys */
+  HM_KEY_BS_LeftScrollUp    = 16,
+  HM_KEY_BS_LeftScrollDown  = 17,
+  HM_KEY_BS_RightScrollUp   = 18,
+  HM_KEY_BS_RightScrollDown = 19,
+
+  /* Braille Edge scroll keys */
+  HM_KEY_BE_LeftScrollUp    = 16,
+  HM_KEY_BE_RightScrollUp   = 17,
+  HM_KEY_BE_RightScrollDown = 18,
+  HM_KEY_BE_LeftScrollDown  = 19,
+
+  /* Braille Edge function keys */
+  HM_KEY_F5 = 20,
+  HM_KEY_F6 = 21,
+  HM_KEY_F7 = 22,
+  HM_KEY_F8 = 23,
+
+  /* Braille Edge left pad */
+  HM_KEY_LeftPadUp     = 24,
+  HM_KEY_LeftPadDown   = 25,
+  HM_KEY_LeftPadLeft   = 26,
+  HM_KEY_LeftPadRight  = 27,
+
+  /* Braille Edge right pad */
+  HM_KEY_RightPadUp    = 28,
+  HM_KEY_RightPadDown  = 29,
+  HM_KEY_RightPadLeft  = 30,
+  HM_KEY_RightPadRight = 31
+} HM_NavigationKey;
+
+typedef enum {
+  HM_GRP_NavigationKeys = 0,
+  HM_GRP_RoutingKeys
+} HM_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_HM_BRLDEFS */ 
diff --git a/Drivers/Braille/HandyTech/Makefile.in b/Drivers/Braille/HandyTech/Makefile.in
new file mode 100644
index 0000000..a576f07
--- /dev/null
+++ b/Drivers/Braille/HandyTech/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ht
+DRIVER_NAME = HandyTech
+DRIVER_USAGE = Modular 20/40/80, Modular Evolution 64/88, Modular Connect 88, Active Braille, Active Braille S, Active Star 40, Actilino, Activator, Basic Braille 16/20/32/40/48/64/80, Braillino, Braille Wave, Easy Braille, Braille Star 40/80, Connect Braille 40, Bookworm
+DRIVER_VERSION = 0.6
+DRIVER_DEVELOPERS = Andreas Gross <andi.gross@gmx.de>, Dave Mielke <dave@mielke.cc>, Mario Lang <mlang@delysid.org>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/HandyTech/README b/Drivers/Braille/HandyTech/README
new file mode 100644
index 0000000..88d2608
--- /dev/null
+++ b/Drivers/Braille/HandyTech/README
@@ -0,0 +1,80 @@
+This directory contains the BRLTTY driver for braille displays which are
+manufactured by Handy Tech Elektronik GmbH [http://www.handytech.com/] of
+Germany. As a component of BRLTTY, this driver is released under the terms of
+the GNU Public License.
+
+This driver was originally implemented by Andreas Gross <andi@andi-bika.de>. It
+has since been extended, and is being maintained, by Dave Mielke
+<dave@mielke.cc>. Thanks to Mike Pedersen <mpedersen@mindspring.com> for his
+help with the design and testing of the Braille Star component. Thanks to Jill
+Clark <jill@handytech.com> of Handy Tech Elektronik for having graciously lent
+us a Bookworm so that its component could be tested. Thanks to Mario Lang 
+<mlang@delysid.org> for his help with the development of USB and bluetooth 
+support.
+
+A HandyTech display can be used via its serial, USB, or bluetooth interface. If
+you're using a serial connection then specify "serial:/path/to/device", e.g. 
+"serial:/dev/ttyS0". If you're using a USB connection then specify "usb:" (the
+trailing colon is important). If you're using a bluetooth connection then
+specify "bluez:address", e.g. "bluez:01:23:45:67:89:AB".
+
+Active Braille:
+---------------
+
+An external keyboard can be connected to your Active Braille via USB. The 
+keyboard is presented to the host as though it were a true HID (Human Interface 
+Device) in its own right. If you are using USB (not yet supported) to connect 
+your Active Braille to the host, most modern operating systems will 
+automatically recognize and correctly handle the keyboard. If you are using 
+Bluetooth to connect your Active Braille to the host, you will need to do some 
+additional setup to get the keyboard working.
+
+For Bluetooth on Linux: Add the Bluetooth device address and the PIN of your 
+Active Braille to the file /var/lib/bluetooth/<host-address>/pincodes so that 
+it will successfully pair with the host. Then, to enable keyboard input from 
+the Active Braille, execute the following command as root:
+
+   bluez-test-input connect 00:01:02:03:04:05
+
+Note that you will need to replace the sample Bluetooth device address in the 
+example above with that of your Active Braille.
+
+Basic Braille:
+--------------
+
+Starting with the Active Braille, Handy Tech has introduced a resistor 
+which limits the amount of power drawn via USB. This eliminates 
+excessive draining of the host's battery. This artificial reduction in 
+power consumtion has the side effect that the dots do not lower as 
+quickly. This leads to a kind of blurring when you scroll rapidly 
+through a screen. If you are a user who does a lot of rapid scrolling, 
+you will likely notice this effect. The Basic Braille allows this 
+behaviour to be configured. If you can afford to draw a little more 
+power via USB and would like to get rid of the blurring, here is how 
+to configure it:
+
+  Press all six display keys at once and hold for at least one second.
+  You will see "Config mode" on the display.
+  Now press cursor routing key 7 to toggle "HIGH USB POWER".
+  Pressing display key 6 will leave Config mode.
+
+The symmetry of the Basic Braille makes it easy to use when rotated 
+180 degrees such that the routing keys are below (rather than above) 
+the braille cells. If you would prefer to use your Basic Braille this 
+way, simply press both the first and last routing keys simultaneously 
+while in Config mode.
+
+Braille Star:
+-------------
+
+Braille Star models allow the use of an external keyboard for input. It can be
+used not only while the Braille Star is in "Notetaker" mode, i.e. while not
+connected to any external PC hardware, but also as a keyboard for the PC
+itself. Either connect the second PS/2 cable from your display to your PC
+(that's the old way), or just disable "Ext. keyboard autoselect" in the
+internal menu. This latter, much simpler, approach causes the keyboard's
+signals to be sent over whatever interface (serial, USB, or bluetooth) you're
+using to connect your display to your PC. It's particularly advantageous to use
+the external keyboard in conjunction with a bluetooth connection since this
+combination effectively transforms the Braille Star into a completely wireless
+yet fully functional braille terminal.
diff --git a/Drivers/Braille/HandyTech/braille.c b/Drivers/Braille/HandyTech/braille.c
new file mode 100644
index 0000000..4bc2c44
--- /dev/null
+++ b/Drivers/Braille/HandyTech/braille.c
@@ -0,0 +1,1768 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "bitfield.h"
+#include "parse.h"
+#include "timing.h"
+#include "async_wait.h"
+#include "ascii.h"
+
+typedef enum {
+  PARM_SETTIME
+} DriverParameter;
+#define BRLPARMS "settime"
+
+#define BRLSTAT ST_AlvaStyle
+#define BRL_HAVE_STATUS_CELLS
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "brldefs-ht.h"
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(HT_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(dots)
+  KEY_NAME_ENTRY(HT_KEY_B1, "B1"),
+  KEY_NAME_ENTRY(HT_KEY_B2, "B2"),
+  KEY_NAME_ENTRY(HT_KEY_B3, "B3"),
+  KEY_NAME_ENTRY(HT_KEY_B4, "B4"),
+
+  KEY_NAME_ENTRY(HT_KEY_B5, "B5"),
+  KEY_NAME_ENTRY(HT_KEY_B6, "B6"),
+  KEY_NAME_ENTRY(HT_KEY_B7, "B7"),
+  KEY_NAME_ENTRY(HT_KEY_B8, "B8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keypad)
+  KEY_NAME_ENTRY(HT_KEY_B12, "B12"),
+  KEY_NAME_ENTRY(HT_KEY_Zero, "Zero"),
+  KEY_NAME_ENTRY(HT_KEY_B13, "B13"),
+  KEY_NAME_ENTRY(HT_KEY_B14, "B14"),
+
+  KEY_NAME_ENTRY(HT_KEY_B11, "B11"),
+  KEY_NAME_ENTRY(HT_KEY_One, "One"),
+  KEY_NAME_ENTRY(HT_KEY_Two, "Two"),
+  KEY_NAME_ENTRY(HT_KEY_Three, "Three"),
+
+  KEY_NAME_ENTRY(HT_KEY_B10, "B10"),
+  KEY_NAME_ENTRY(HT_KEY_Four, "Four"),
+  KEY_NAME_ENTRY(HT_KEY_Five, "Five"),
+  KEY_NAME_ENTRY(HT_KEY_Six, "Six"),
+
+  KEY_NAME_ENTRY(HT_KEY_B9, "B9"),
+  KEY_NAME_ENTRY(HT_KEY_Seven, "Seven"),
+  KEY_NAME_ENTRY(HT_KEY_Eight, "Eight"),
+  KEY_NAME_ENTRY(HT_KEY_Nine, "Nine"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(rockers)
+  KEY_NAME_ENTRY(HT_KEY_Escape, "LeftRockerTop"),
+  KEY_NAME_ENTRY(HT_KEY_Return, "LeftRockerBottom"),
+
+  KEY_NAME_ENTRY(HT_KEY_Up, "RightRockerTop"),
+  KEY_NAME_ENTRY(HT_KEY_Down, "RightRockerBottom"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  KEY_NAME_ENTRY(HT_KEY_Escape, "Display1"),
+  KEY_NAME_ENTRY(HT_KEY_LeftCenter, "Display2"),
+  KEY_NAME_ENTRY(HT_KEY_Return, "Display3"),
+
+  KEY_NAME_ENTRY(HT_KEY_Up, "Display4"),
+  KEY_NAME_ENTRY(HT_KEY_RightCenter, "Display5"),
+  KEY_NAME_ENTRY(HT_KEY_Down, "Display6"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(joystick)
+  KEY_NAME_ENTRY(HT_KEY_JoystickLeft, "Left"),
+  KEY_NAME_ENTRY(HT_KEY_JoystickRight, "Right"),
+  KEY_NAME_ENTRY(HT_KEY_JoystickUp, "Up"),
+  KEY_NAME_ENTRY(HT_KEY_JoystickDown, "Down"),
+  KEY_NAME_ENTRY(HT_KEY_JoystickAction, "Action"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(modular)
+  KEY_NAME_ENTRY(HT_KEY_Up, "Left"),
+  KEY_NAME_ENTRY(HT_KEY_Down, "Right"),
+
+  KEY_NAME_ENTRY(HT_KEY_STATUS+0, "Status1"),
+  KEY_NAME_ENTRY(HT_KEY_STATUS+1, "Status2"),
+  KEY_NAME_ENTRY(HT_KEY_STATUS+2, "Status3"),
+  KEY_NAME_ENTRY(HT_KEY_STATUS+3, "Status4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(mdlr)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(keypad),
+  KEY_NAME_TABLE(modular),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(modularEvolution)
+  KEY_NAME_ENTRY(HT_KEY_Space, "Left"),
+  KEY_NAME_ENTRY(HT_KEY_SpaceRight, "Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(me64)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(modularEvolution),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(me88)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(keypad),
+  KEY_NAME_TABLE(modularEvolution),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(mc88)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(keypad),
+  KEY_NAME_TABLE(modularEvolution),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(brailleStar)
+  KEY_NAME_ENTRY(HT_KEY_Space, "SpaceLeft"),
+  KEY_NAME_ENTRY(HT_KEY_SpaceRight, "SpaceRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(bs40)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bs80)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(keypad),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(brln)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(as40)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ab)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ab_s)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+  KEY_NAME_TABLE(joystick),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(cb40)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(brailleWave)
+  KEY_NAME_ENTRY(HT_KEY_Up, "Left"),
+  KEY_NAME_ENTRY(HT_KEY_Down, "Right"),
+
+  KEY_NAME_ENTRY(HT_KEY_Escape, "Escape"),
+  KEY_NAME_ENTRY(HT_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(HT_KEY_Return, "Return"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(wave)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(brailleWave),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(easyBraille)
+  KEY_NAME_ENTRY(HT_KEY_Up, "Left"),
+  KEY_NAME_ENTRY(HT_KEY_Down, "Right"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(easy)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(easyBraille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLE(basicBraille)
+  KEY_NAME_ENTRY(HT_KEY_B2, "Display3"),
+  KEY_NAME_ENTRY(HT_KEY_B3, "Display2"),
+  KEY_NAME_ENTRY(HT_KEY_B4, "Display1"),
+  KEY_NAME_ENTRY(HT_KEY_B5, "Display4"),
+  KEY_NAME_ENTRY(HT_KEY_B6, "Display5"),
+  KEY_NAME_ENTRY(HT_KEY_B7, "Display6"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(bb)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(basicBraille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bbp)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(alo)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(rockers),
+  KEY_NAME_TABLE(brailleStar),
+  KEY_NAME_TABLE(joystick),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ac4)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(dots),
+  KEY_NAME_TABLE(navigation),
+  KEY_NAME_TABLE(brailleStar),
+  KEY_NAME_TABLE(joystick),
+END_KEY_NAME_TABLES
+
+typedef enum {
+  HT_BWK_Backward = 0X01,
+  HT_BWK_Forward = 0X08,
+
+  HT_BWK_Escape = 0X02,
+  HT_BWK_Enter = 0X04
+} HT_BookwormKey;
+
+BEGIN_KEY_NAME_TABLE(bookworm)
+  KEY_NAME_ENTRY(HT_BWK_Backward, "Backward"),
+  KEY_NAME_ENTRY(HT_BWK_Forward, "Forward"),
+
+  KEY_NAME_ENTRY(HT_BWK_Escape, "Escape"),
+  KEY_NAME_ENTRY(HT_BWK_Enter, "Enter"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(bkwm)
+  KEY_NAME_TABLE(bookworm),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(mdlr)
+DEFINE_KEY_TABLE(me64)
+DEFINE_KEY_TABLE(me88)
+DEFINE_KEY_TABLE(mc88)
+DEFINE_KEY_TABLE(bs40)
+DEFINE_KEY_TABLE(bs80)
+DEFINE_KEY_TABLE(brln)
+DEFINE_KEY_TABLE(as40)
+DEFINE_KEY_TABLE(ab)
+DEFINE_KEY_TABLE(ab_s)
+DEFINE_KEY_TABLE(cb40)
+DEFINE_KEY_TABLE(wave)
+DEFINE_KEY_TABLE(easy)
+DEFINE_KEY_TABLE(bb)
+DEFINE_KEY_TABLE(bbp)
+DEFINE_KEY_TABLE(alo)
+DEFINE_KEY_TABLE(ac4)
+DEFINE_KEY_TABLE(bkwm)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(mdlr),
+  &KEY_TABLE_DEFINITION(me64),
+  &KEY_TABLE_DEFINITION(me88),
+  &KEY_TABLE_DEFINITION(mc88),
+  &KEY_TABLE_DEFINITION(bs40),
+  &KEY_TABLE_DEFINITION(bs80),
+  &KEY_TABLE_DEFINITION(brln),
+  &KEY_TABLE_DEFINITION(as40),
+  &KEY_TABLE_DEFINITION(ab),
+  &KEY_TABLE_DEFINITION(ab_s),
+  &KEY_TABLE_DEFINITION(cb40),
+  &KEY_TABLE_DEFINITION(wave),
+  &KEY_TABLE_DEFINITION(easy),
+  &KEY_TABLE_DEFINITION(bb),
+  &KEY_TABLE_DEFINITION(bbp),
+  &KEY_TABLE_DEFINITION(alo),
+  &KEY_TABLE_DEFINITION(ac4),
+  &KEY_TABLE_DEFINITION(bkwm),
+END_KEY_TABLE_LIST
+
+static int
+endSession_Bookworm (BrailleDisplay *brl) {
+  static const unsigned char sessionEnd[] = {0X05, 0X07};
+  return writeBrailleMessage(brl, NULL, 0, sessionEnd, sizeof(sessionEnd));
+}
+
+typedef int ByteInterpreter (BrailleDisplay *brl, unsigned char byte);
+static ByteInterpreter interpretByte_key;
+static ByteInterpreter interpretByte_Bookworm;
+
+typedef int (CellWriter) (BrailleDisplay *brl);
+static CellWriter writeCells_statusAndText;
+static CellWriter writeCells_Bookworm;
+static CellWriter writeCells_Evolution;
+
+static SetBrailleFirmnessMethod setBrailleFirmness;
+
+static SetTouchSensitivityMethod setTouchSensitivity_Evolution;
+static SetTouchSensitivityMethod setTouchSensitivity_ActiveBraille;
+
+typedef struct {
+  const char *name;
+  const KeyTableDefinition *keyTableDefinition;
+
+  ByteInterpreter *interpretByte;
+  CellWriter *writeCells;
+  SetBrailleFirmnessMethod *setBrailleFirmness;
+  SetTouchSensitivityMethod *setTouchSensitivity;
+
+  BrailleSessionEnder *sessionEnder;
+
+  HT_ModelIdentifier identifier:8;
+  unsigned char textCells;
+  unsigned char statusCells;
+
+  unsigned hasATC:1; /* Active Tactile Control */
+  unsigned hasTime:1;
+} ModelEntry;
+
+static const ModelEntry modelTable[] = {
+  { .identifier = HT_MODEL_Modular20,
+    .name = "Modular 20+4",
+    .textCells = 20,
+    .statusCells = 4,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(mdlr),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_Modular40,
+    .name = "Modular 40+4",
+    .textCells = 40,
+    .statusCells = 4,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(mdlr),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_Modular80,
+    .name = "Modular 80+4",
+    .textCells = 80,
+    .statusCells = 4,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(mdlr),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_ModularEvolution64,
+    .name = "Modular Evolution 64",
+    .textCells = 64,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(me64),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setTouchSensitivity = setTouchSensitivity_Evolution,
+    .hasATC = 1
+  },
+
+  { .identifier = HT_MODEL_ModularEvolution88,
+    .name = "Modular Evolution 88",
+    .textCells = 88,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(me88),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setTouchSensitivity = setTouchSensitivity_Evolution,
+    .hasATC = 1
+  },
+
+  { .identifier = HT_MODEL_BrailleWave,
+    .name = "Braille Wave",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(wave),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_Bookworm,
+    .name = "Bookworm",
+    .textCells = 8,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(bkwm),
+    .interpretByte = interpretByte_Bookworm,
+    .writeCells = writeCells_Bookworm,
+    .sessionEnder = endSession_Bookworm
+  },
+
+  { .identifier = HT_MODEL_Braillino,
+    .name = "Braillino",
+    .textCells = 20,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(brln),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_BrailleStar40,
+    .name = "Braille Star 40",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(bs40),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_BrailleStar80,
+    .name = "Braille Star 80",
+    .textCells = 80,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(bs80),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_statusAndText
+  },
+
+  { .identifier = HT_MODEL_EasyBraille,
+    .name = "Easy Braille",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(easy),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution
+  },
+
+  { .identifier = HT_MODEL_ActiveBraille,
+    .name = "Active Braille",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(ab),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setBrailleFirmness = setBrailleFirmness,
+    .setTouchSensitivity = setTouchSensitivity_ActiveBraille,
+    .hasATC = 1,
+    .hasTime = 1
+  },
+
+#define HT_BASIC_BRAILLE(cells)                     \
+  { .identifier = HT_MODEL_BasicBraille##cells,     \
+    .name = "Basic Braille " STRINGIFY(cells),      \
+    .textCells = cells,                             \
+    .statusCells = 0,                               \
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(bb),\
+    .interpretByte = interpretByte_key,             \
+    .writeCells = writeCells_Evolution              \
+  }
+  HT_BASIC_BRAILLE(16),
+  HT_BASIC_BRAILLE(20),
+  HT_BASIC_BRAILLE(32),
+  HT_BASIC_BRAILLE(40),
+  HT_BASIC_BRAILLE(48),
+  HT_BASIC_BRAILLE(64),
+  HT_BASIC_BRAILLE(80),
+  HT_BASIC_BRAILLE(160),
+#undef HT_BASIC_BRAILLE
+
+#define HT_BASIC_BRAILLE_PLUS(cells)                 \
+  { .identifier = HT_MODEL_BasicBraillePlus##cells,  \
+    .name = "Basic Braille Plus " STRINGIFY(cells),  \
+    .textCells = cells,                              \
+    .statusCells = 0,                                \
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(bbp),\
+    .interpretByte = interpretByte_key,              \
+    .writeCells = writeCells_Evolution               \
+  }
+  HT_BASIC_BRAILLE_PLUS(20),
+  HT_BASIC_BRAILLE_PLUS(32),
+  HT_BASIC_BRAILLE_PLUS(40),
+  HT_BASIC_BRAILLE_PLUS(48),
+  HT_BASIC_BRAILLE_PLUS(64),
+  HT_BASIC_BRAILLE_PLUS(80),
+  HT_BASIC_BRAILLE_PLUS(84),
+#undef HT_BASIC_BRAILLE_PLUS
+
+  { .identifier = HT_MODEL_Actilino,
+    .name = "Actilino",
+    .textCells = 16,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(alo),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setBrailleFirmness = setBrailleFirmness,
+    .setTouchSensitivity = setTouchSensitivity_ActiveBraille,
+    .hasATC = 1,
+    .hasTime = 1
+  },
+
+  { .identifier = HT_MODEL_Activator,
+    .name = "Activator",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(ac4),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setBrailleFirmness = setBrailleFirmness,
+    .setTouchSensitivity = setTouchSensitivity_ActiveBraille,
+    .hasATC = 1,
+    .hasTime = 1
+  },
+
+  { .identifier = HT_MODEL_ActiveStar40,
+    .name = "Active Star 40",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(as40),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setBrailleFirmness = setBrailleFirmness,
+    .setTouchSensitivity = setTouchSensitivity_ActiveBraille,
+    .hasATC = 1,
+    .hasTime = 1
+  },
+
+  { .identifier = HT_MODEL_ModularConnect88,
+    .name = "Modular Connect 88",
+    .textCells = 88,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(mc88),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+  },
+
+  { .identifier = HT_MODEL_ConnectBraille40,
+    .name = "Connect Braille 40",
+    .textCells = 40,
+    .statusCells = 0,
+    .keyTableDefinition = &KEY_TABLE_DEFINITION(cb40),
+    .interpretByte = interpretByte_key,
+    .writeCells = writeCells_Evolution,
+    .setBrailleFirmness = setBrailleFirmness,
+    .hasTime = 1
+  },
+
+  { /* end of table */
+    .name = NULL
+  }
+};
+
+static const ModelEntry modelEntry_ab_s = {
+  .identifier = HT_MODEL_ActiveBraille,
+  .name = "Active Braille S",
+  .textCells = 40,
+  .statusCells = 0,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(ab_s),
+  .interpretByte = interpretByte_key,
+  .writeCells = writeCells_Evolution,
+  .setBrailleFirmness = setBrailleFirmness,
+  .setTouchSensitivity = setTouchSensitivity_ActiveBraille,
+  .hasATC = 1,
+  .hasTime = 1
+};
+
+#define MAXIMUM_TEXT_CELLS   160
+#define MAXIMUM_STATUS_CELLS 4
+
+typedef enum {
+  BDS_OFF,
+  BDS_READY
+} BrailleDisplayState;
+
+struct BrailleDataStruct {
+  const ModelEntry *model;              /* points to terminal model config struct */
+
+  unsigned char rawData[MAXIMUM_TEXT_CELLS];            /* translated data to send to Braille */
+  unsigned char prevData[MAXIMUM_TEXT_CELLS];   /* previously sent raw data */
+
+  unsigned char rawStatus[MAXIMUM_STATUS_CELLS];         /* to hold status info */
+  unsigned char prevStatus[MAXIMUM_STATUS_CELLS];        /* to hold previous status */
+
+  BrailleDisplayState currentState;
+  TimePeriod statePeriod;
+
+  unsigned int retryCount;
+  unsigned char updateRequired;
+};
+
+/* USB IO */
+#include "io_usb.h"
+#include "usb_hid.h"
+
+#define HT_HID_REPORT_TIMEOUT 100
+
+typedef enum {
+  HT_HID_RPT_OutData    = 0X01, /* receive data from device */
+  HT_HID_RPT_InData     = 0X02, /* send data to device */
+  HT_HID_RPT_InCommand  = 0XFB, /* run USB-HID firmware command */
+  HT_HID_RPT_OutVersion = 0XFC, /* get version of USB-HID firmware */
+  HT_HID_RPT_OutBaud    = 0XFD, /* get baud rate of serial connection */
+  HT_HID_RPT_InBaud     = 0XFE, /* set baud rate of serial connection */
+} HT_HidReportNumber;
+
+typedef enum {
+  HT_HID_CMD_FlushBuffers = 0X01, /* flush input and output buffers */
+} HtHidCommand;
+
+static size_t hidOutDataSize = 0;
+static size_t hidInDataSize = 0;
+static size_t hidInCommandSize = 0;
+static size_t hidOutVersionSize = 0;
+static size_t hidOutBaudSize = 0;
+static size_t hidInBaudSize = 0;
+
+static uint16_t hidFirmwareVersion;
+static unsigned char *hidInputReport = NULL;
+#define hidInputLength (hidInputReport[1])
+#define hidInputBuffer (&hidInputReport[2])
+static unsigned char hidInputOffset;
+
+static ssize_t
+getHidReport (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  unsigned char number, unsigned char *buffer, uint16_t size
+) {
+  ssize_t result = usbHidGetReport(device, definition->interface,
+                                   number, buffer, size, HT_HID_REPORT_TIMEOUT);
+  if (result > 0 && buffer[0] != number) {
+    logMessage(LOG_WARNING, "unexpected HID report number: expected %02X, received %02X",
+               number, buffer[0]);
+    errno = EIO;
+    result = -1;
+  }
+
+  return result;
+}
+
+static int
+allocateHidInputBuffer (void) {
+  if (hidOutDataSize) {
+    if ((hidInputReport = malloc(hidOutDataSize))) {
+      hidInputLength = 0;
+      hidInputOffset = 0;
+      return 1;
+    } else {
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+static void
+deallocateHidInputBuffer (void) {
+  if (hidInputReport) {
+    free(hidInputReport);
+    hidInputReport = NULL;
+  }
+}
+
+static int
+getHidFirmwareVersion (BrailleDisplay *brl) {
+  hidFirmwareVersion = 0;
+
+  if (hidOutVersionSize) {
+    unsigned char report[hidOutVersionSize];
+    ssize_t result = gioGetHidReport(brl->gioEndpoint,
+                                     HT_HID_RPT_OutVersion, report, sizeof(report));
+
+    if (result > 0) {
+      hidFirmwareVersion = (report[1] << 8) | report[2];
+      logMessage(LOG_INFO, "USB-HID Firmware Version: %u.%u", report[1], report[2]);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+executeHidFirmwareCommand (BrailleDisplay *brl, HtHidCommand command) {
+  if (hidInCommandSize) {
+    unsigned char report[hidInCommandSize];
+
+    report[0] = HT_HID_RPT_InCommand;
+    report[1] = command;
+
+    if (gioWriteHidReport(brl->gioEndpoint, report, sizeof(report)) != -1) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+typedef struct {
+  int (*initializeSession) (BrailleDisplay *brl);
+} GeneralOperations;
+
+typedef struct {
+  const GeneralOperations *general;
+  GioUsbAwaitInputMethod *awaitInput;
+  GioUsbReadDataMethod *readData;
+  GioUsbWriteDataMethod *writeData;
+  UsbInputFilter *inputFilter;
+} UsbOperations;
+
+static int
+initializeUsbSession2 (BrailleDisplay *brl) {
+  static const BrailleReportSizeEntry reportTable[] = {
+    {.identifier=HT_HID_RPT_OutData, .input=&hidOutDataSize},
+    {.identifier=HT_HID_RPT_InData, .output=&hidInDataSize},
+    {.identifier=HT_HID_RPT_InCommand, .output=&hidInCommandSize},
+    {.identifier=HT_HID_RPT_OutVersion, .input=&hidOutVersionSize},
+    {.identifier=HT_HID_RPT_OutBaud, .input=&hidOutBaudSize},
+    {.identifier=HT_HID_RPT_InBaud, .output=&hidInBaudSize},
+    {.identifier=0}
+  };
+
+  if (getBrailleReportSizes(brl, reportTable)) {
+    if (allocateHidInputBuffer()) {
+      if (getHidFirmwareVersion(brl)) {
+        if (executeHidFirmwareCommand(brl, HT_HID_CMD_FlushBuffers)) {
+          return 1;
+        }
+      }
+
+      deallocateHidInputBuffer();
+    }
+  }
+
+  return 0;
+}
+
+static int
+awaitUsbInput2 (
+  UsbDevice *device, const UsbChannelDefinition *definition, int milliseconds
+) {
+  if (hidOutDataSize) {
+    if (hidInputOffset < hidInputLength) return 1;
+
+    TimePeriod period;
+    startTimePeriod(&period, milliseconds);
+
+    while (1) {
+      ssize_t result = getHidReport(device, definition, HT_HID_RPT_OutData,
+                                    hidInputReport, hidOutDataSize);
+
+      if (result == -1) return 0;
+      hidInputOffset = 0;
+      if (hidInputLength > 0) return 1;
+
+      if (afterTimePeriod(&period, NULL)) break;
+      asyncWait(BRAILLE_DRIVER_INPUT_POLL_INTERVAL);
+    }
+  }
+
+  errno = EAGAIN;
+  return 0;
+}
+
+static ssize_t
+readUsbData2 (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  void *data, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  unsigned char *buffer = data;
+  int count = 0;
+
+  while (count < size) {
+    if (!awaitUsbInput2(device, definition,
+                        count? subsequentTimeout: initialTimeout)) {
+      if (errno != EAGAIN) count = -1;
+      break;
+    }
+
+    {
+      size_t amount = MIN(size-count, hidInputLength-hidInputOffset);
+
+      memcpy(&buffer[count], &hidInputBuffer[hidInputOffset], amount);
+      hidInputOffset += amount;
+      count += amount;
+    }
+  }
+
+  return count;
+}
+
+static ssize_t
+writeUsbData2 (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  const void *data, size_t size, int timeout
+) {
+  const unsigned char *buffer = data;
+  int index = 0;
+
+  if (hidInDataSize) {
+    while (size) {
+      unsigned char report[hidInDataSize];
+      unsigned char count = MIN(size, (sizeof(report) - 2));
+      int result;
+
+      report[0] = HT_HID_RPT_InData;
+      report[1] = count;
+      memcpy(report+2, &buffer[index], count);
+      memset(&report[count+2], 0, sizeof(report)-count-2);
+
+      result = usbHidSetReport(device, definition->interface,
+                               report[0], report, sizeof(report),
+                               HT_HID_REPORT_TIMEOUT);
+      if (result == -1) return -1;
+
+      index += count;
+      size -= count;
+    }
+  }
+
+  return index;
+}
+
+static const GeneralOperations generalOperations2 = {
+  .initializeSession = initializeUsbSession2
+};
+
+static const UsbOperations usbOperations2 = {
+  .general = &generalOperations2,
+  .awaitInput = awaitUsbInput2,
+  .readData = readUsbData2,
+  .writeData = writeUsbData2
+};
+
+static int
+initializeUsbSession3 (BrailleDisplay *brl) {
+  static const BrailleReportSizeEntry reportTable[] = {
+    {.identifier=HT_HID_RPT_OutData, .input=&hidOutDataSize},
+    {.identifier=HT_HID_RPT_InData, .output=&hidInDataSize},
+    {.identifier=0}
+  };
+
+  return getBrailleReportSizes(brl, reportTable);
+}
+
+static ssize_t
+writeUsbData3 (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  const void *data, size_t size, int timeout
+) {
+  const unsigned char *buffer = data;
+  int index = 0;
+
+  if (hidInDataSize) {
+    while (size) {
+      unsigned char report[hidInDataSize];
+      const unsigned char count = MIN(size, (sizeof(report) - 2));
+      int result;
+
+      report[0] = HT_HID_RPT_InData;
+      report[1] = count;
+      memset(mempcpy(report+2, &buffer[index], count), 0, sizeof(report)-count-2);
+
+      result = usbWriteEndpoint(device, definition->outputEndpoint,
+                                report, sizeof(report), 1000);
+      if (result == -1) return -1;
+
+      index += count;
+      size -= count;
+    }
+  }
+
+  return index;
+}
+
+static int
+filterUsbInput3 (UsbInputFilterData *data) {
+  unsigned char *buffer = data->buffer;
+
+  if ((data->length >= 2) &&
+      (data->length == hidOutDataSize) && 
+      (buffer[0] == HT_HID_RPT_OutData) &&
+      (buffer[1] <= (data->length - 2))) {
+    data->length = buffer[1];
+    memmove(data->buffer, data->buffer+2, data->length);
+  }
+
+  return 1;
+}
+
+static const GeneralOperations generalOperations3 = {
+  .initializeSession = initializeUsbSession3
+};
+
+static const UsbOperations usbOperations3 = {
+  .general = &generalOperations3,
+  .writeData = writeUsbData3,
+  .inputFilter = filterUsbInput3
+};
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        default:
+          *length = 1;
+          break;
+
+        case HT_PKT_OK:
+          *length = 2;
+          break;
+
+        case HT_PKT_Extended:
+          *length = 4;
+          break;
+      }
+      break;
+
+    case 3:
+      if (bytes[0] == HT_PKT_Extended) *length += byte;
+      break;
+
+    case 5:
+      if ((bytes[0] == HT_PKT_Extended) &&
+          (bytes[1] == HT_MODEL_ActiveBraille) &&
+          (bytes[2] == 2) &&
+          (bytes[3] == HT_EXTPKT_Confirmation) &&
+          (byte == 0X15))
+        *length += 1;
+      break;
+
+    default:
+      break;
+  }
+
+  if ((size == *length) && (bytes[0] == HT_PKT_Extended) && (byte != ASCII_SYN)) {
+    return BRL_PVR_INVALID;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  return readBraillePacket(brl, NULL, buffer, size, verifyPacket, NULL);
+}
+
+static ssize_t
+brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  const size_t length = readPacket(brl, buffer, size);
+
+  if (length == 0 && errno != EAGAIN) return -1;
+  return length;
+}
+
+static ssize_t
+brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) {
+  return writeBrailleMessage(brl, NULL, 0, packet, length)? length: -1;
+}
+
+static void
+setState (BrailleDisplay *brl, BrailleDisplayState state) {
+  if (state == brl->data->currentState) {
+    ++brl->data->retryCount;
+  } else {
+    brl->data->retryCount = 0;
+    brl->data->currentState = state;
+  }
+
+  startTimePeriod(&brl->data->statePeriod, 1000);
+  // logMessage(LOG_DEBUG, "State: %d+%d", brl->data->currentState, brl->data->retryCount);
+}
+
+static int
+brl_reset (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {HT_PKT_Reset};
+  return writeBraillePacket(brl, NULL, packet, sizeof(packet));
+}
+
+static int
+identifyModel (BrailleDisplay *brl, unsigned char identifier) {
+  for (
+    brl->data->model = modelTable;
+    brl->data->model->name && (brl->data->model->identifier != identifier);
+    brl->data->model++
+  );
+
+  if (!brl->data->model->name) {
+    logMessage(LOG_ERR, "Detected unknown HandyTech model with ID %02X.",
+               identifier);
+    return 0;
+  }
+
+  if (brl->data->model->identifier == HT_MODEL_ActiveBraille) {
+    GioEndpoint *endpoint = brl->gioEndpoint;
+    char *serialNumber = NULL;
+
+    switch (gioGetResourceType(endpoint)) {
+      case GIO_TYPE_USB: {
+        UsbChannel *channel = gioGetResourceObject(endpoint);
+        serialNumber = usbGetSerialNumber(channel->device, 1000);
+        break;
+      }
+
+      default: {
+        serialNumber = gioGetResourceName(endpoint);
+        break;
+      }
+    }
+
+    if (serialNumber) {
+      const char *slash = strchr(serialNumber, '/');
+
+      if (slash) {
+        if (slash[1] == 'S') brl->data->model = &modelEntry_ab_s;
+      }
+
+      free(serialNumber);
+    }
+  }
+
+  logMessage(LOG_INFO, "Detected %s: %d data %s, %d status %s.",
+             brl->data->model->name,
+             brl->data->model->textCells, (brl->data->model->textCells == 1)? "cell": "cells",
+             brl->data->model->statusCells, (brl->data->model->statusCells == 1)? "cell": "cells");
+
+  brl->textColumns = brl->data->model->textCells;                       /* initialise size of display */
+  brl->textRows = 1;
+  brl->statusColumns = brl->data->model->statusCells;
+  brl->statusRows = 1;
+
+  setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+  brl->setBrailleFirmness = brl->data->model->setBrailleFirmness;
+  brl->setTouchSensitivity = brl->data->model->setTouchSensitivity;
+
+  memset(brl->data->rawStatus, 0, brl->data->model->statusCells);
+  memset(brl->data->rawData, 0, brl->data->model->textCells);
+
+  brl->data->retryCount = 0;
+  brl->data->updateRequired = 0;
+  brl->data->currentState = BDS_OFF;
+  setState(brl, BDS_READY);
+
+  return 1;
+}
+
+static int
+writeExtendedPacket (
+  BrailleDisplay *brl, HT_ExtendedPacketType type,
+  const unsigned char *data, unsigned char size
+) {
+  HT_Packet packet;
+  packet.fields.type = HT_PKT_Extended;
+  packet.fields.data.extended.model = brl->data->model->identifier;
+  packet.fields.data.extended.length = size + 1; /* type byte is included */
+  packet.fields.data.extended.type = type;
+  if (data) memcpy(packet.fields.data.extended.data.bytes, data, size);
+  packet.fields.data.extended.data.bytes[size] = ASCII_SYN;
+  size += 5; /* EXT, ID, LEN, TYPE, ..., SYN */
+  return writeBrailleMessage(brl, NULL, type, &packet, size);
+}
+
+static int
+setAtcMode (BrailleDisplay *brl, unsigned char value) {
+  const unsigned char data[] = {value};
+  return writeExtendedPacket(brl, HT_EXTPKT_SetAtcMode, data, sizeof(data));
+}
+
+static int
+setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting) {
+  const unsigned char data[] = {setting * 2 / BRL_FIRMNESS_MAXIMUM};
+  return writeExtendedPacket(brl, HT_EXTPKT_SetFirmness, data, sizeof(data));
+}
+
+static int
+setTouchSensitivity_Evolution (BrailleDisplay *brl, TouchSensitivity setting) {
+  const unsigned char data[] = {0XFF - (setting * 0XF0 / BRL_SENSITIVITY_MAXIMUM)};
+  return writeExtendedPacket(brl, HT_EXTPKT_SetAtcSensitivity, data, sizeof(data));
+}
+
+static int
+setTouchSensitivity_ActiveBraille (BrailleDisplay *brl, TouchSensitivity setting) {
+  const unsigned char data[] = {setting * 6 / BRL_SENSITIVITY_MAXIMUM};
+  return writeExtendedPacket(brl, HT_EXTPKT_SetAtcSensitivity2, data, sizeof(data));
+}
+
+typedef int (DateTimeProcessor) (BrailleDisplay *brl, const HT_DateTime *dateTime);
+static DateTimeProcessor *dateTimeProcessor = NULL;
+
+static int
+requestDateTime (BrailleDisplay *brl, DateTimeProcessor *processor) {
+  int result = writeExtendedPacket(brl, HT_EXTPKT_GetRTC, NULL, 0);
+
+  if (result) {
+    dateTimeProcessor = processor;
+  }
+
+  return result;
+}
+
+static int
+logDateTime (BrailleDisplay *brl, const HT_DateTime *dateTime) {
+  logMessage(LOG_INFO,
+             "date and time of %s:"
+             " %04" PRIu16 "-%02" PRIu8 "-%02" PRIu8
+             " %02" PRIu8 ":%02" PRIu8 ":%02" PRIu8,
+             brl->data->model->name,
+             getBigEndian16(dateTime->year), dateTime->month, dateTime->day,
+             dateTime->hour, dateTime->minute, dateTime->second);
+
+  return 1;
+}
+
+static int
+synchronizeDateTime (BrailleDisplay *brl, const HT_DateTime *dateTime) {
+  long int delta;
+  TimeValue hostTime;
+  getCurrentTime(&hostTime);
+
+  {
+    TimeValue deviceTime;
+
+    {
+      TimeComponents components = {
+        .year = getBigEndian16(dateTime->year),
+        .month = dateTime->month - 1,
+        .day = dateTime->day - 1,
+        .hour = dateTime->hour,
+        .minute = dateTime->minute,
+        .second = dateTime->second
+      };
+
+      makeTimeValue(&deviceTime, &components);
+    }
+
+    delta = millisecondsBetween(&hostTime, &deviceTime);
+    if (delta < 0) delta = -delta;
+  }
+
+  if (delta > 1000) {
+    TimeComponents components;
+    HT_DateTime payload;
+
+    expandTimeValue(&hostTime, &components);
+    putLittleEndian16(&payload.year, components.year);
+    payload.month = components.month + 1;
+    payload.day = components.day + 1;
+    payload.hour = components.hour;
+    payload.minute = components.minute;
+    payload.second = components.second;
+
+    logMessage(LOG_DEBUG, "Time difference between host and device: %ld.%03ld",
+               (delta / MSECS_PER_SEC), (delta % MSECS_PER_SEC));
+
+    if (writeExtendedPacket(brl, HT_EXTPKT_SetRTC,
+                            (unsigned char *)&payload, sizeof(payload))) {
+      return requestDateTime(brl, logDateTime);
+    }
+  }
+
+  return 1;
+}
+
+static int
+initializeSession (BrailleDisplay *brl) {
+  const GeneralOperations *ops = gioGetApplicationData(brl->gioEndpoint);
+
+  if (ops) {
+    if (ops->initializeSession) {
+      if (!ops->initializeSession(brl)) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static void
+setUsbConnectionProperties (
+  GioUsbConnectionProperties *properties,
+  const UsbChannelDefinition *definition
+) {
+  if (definition->data) {
+    const UsbOperations *usbOps = definition->data;
+
+    properties->applicationData = usbOps->general;
+    properties->writeData = usbOps->writeData;
+    properties->readData = usbOps->readData;
+    properties->awaitInput = usbOps->awaitInput;
+    properties->inputFilter = usbOps->inputFilter;
+  }
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 19200,
+    .parity = SERIAL_PARITY_ODD
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_0403_6001)
+    "FTDI",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* GoHubs chip */
+      .vendor=0X0921, .product=0X1200,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .serial = &serialParameters
+    },
+
+    { /* FTDI chip */
+      .vendor=0X0403, .product=0X6001,
+      .manufacturers = usbManufacturers_0403_6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters
+    },
+
+    { /* Easy Braille (HID) */
+      .vendor=0X1FE4, .product=0X0044,
+      .configuration=1, .interface=0, .alternative=0,
+      .data=&usbOperations2
+    },
+
+    { /* Braille Star 40 (HID) */
+      .vendor=0X1FE4, .product=0X0074,
+      .configuration=1, .interface=0, .alternative=0,
+      .data=&usbOperations2
+    },
+
+    { /* USB-HID adapter */
+      .vendor=0X1FE4, .product=0X0003,
+      .configuration=1, .interface=0, .alternative=0,
+      .data=&usbOperations2
+    },
+
+    { /* Active Braille */
+      .vendor=0X1FE4, .product=0X0054,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 16 */
+      .vendor=0X1FE4, .product=0X0081,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 20 */
+      .vendor=0X1FE4, .product=0X0082,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 32 */
+      .vendor=0X1FE4, .product=0X0083,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 40 */
+      .vendor=0X1FE4, .product=0X0084,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 48 */
+      .vendor=0X1FE4, .product=0X008A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 64 */
+      .vendor=0X1FE4, .product=0X0086,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 80 */
+      .vendor=0X1FE4, .product=0X0087,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Basic Braille 160 */
+      .vendor=0X1FE4, .product=0X008B,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Actilino */
+      .vendor=0X1FE4, .product=0X0061,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Activator */
+      .vendor=0X1FE4, .product=0X00A4,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Active Star 40 */
+      .vendor=0X1FE4, .product=0X0064,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+
+    { /* Connect Braille 40 */
+      .vendor=0X1FE4, .product=0X0055,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .data=&usbOperations3
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.setConnectionProperties = setUsbConnectionProperties;
+  descriptor.usb.options.inputTimeout = 100;
+  descriptor.usb.options.requestTimeout = 100;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.discoverChannel = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, initializeSession)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const HT_Packet *response = packet;
+
+  return (response->fields.type == HT_PKT_OK)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned int setTime = 0;
+      HT_Packet response;
+
+      if (*parameters[PARM_SETTIME]) {
+        if (!validateYesNo(&setTime, parameters[PARM_SETTIME])) {
+          logMessage(LOG_WARNING, "%s: %s", "invalid set time setting",
+                     parameters[PARM_SETTIME]);
+        }
+      }
+
+      setTime = !!setTime;
+
+      if (probeBrailleDisplay(brl, 3, NULL, 100,
+                              brl_reset,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        if (identifyModel(brl, response.fields.data.ok.model)) {
+          makeOutputTable(dotsTable_ISO11548_1);
+
+          if (brl->data->model->hasATC) {
+            setAtcMode(brl, 1);
+          }
+
+          if (setTime) {
+            if (brl->data->model->hasTime) {
+              requestDateTime(brl, synchronizeDateTime);
+            } else {
+              logMessage(LOG_INFO, "%s does not support setting the clock",
+                         brl->data->model->name);
+            }
+          }
+
+          return 1;
+        }
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (brl->data) {
+    disconnectBrailleResource(brl, brl->data->model->sessionEnder);
+
+    free(brl->data);
+    brl->data = NULL;
+  }
+
+  deallocateHidInputBuffer();
+}
+
+static int
+writeCells (BrailleDisplay *brl) {
+  return brl->data->model->writeCells(brl);
+}
+
+static int
+writeCells_statusAndText (BrailleDisplay *brl) {
+  unsigned char buffer[1 + brl->data->model->statusCells + brl->data->model->textCells];
+  unsigned char *byte = buffer;
+
+  *byte++ = HT_PKT_Braille;
+  byte = mempcpy(byte, brl->data->rawStatus, brl->data->model->statusCells);
+  byte = mempcpy(byte, brl->data->rawData, brl->data->model->textCells);
+
+  return writeBrailleMessage(brl, NULL, HT_PKT_Braille, buffer, byte-buffer);
+}
+
+static int
+writeCells_Bookworm (BrailleDisplay *brl) {
+  unsigned char buffer[1 + brl->data->model->statusCells + brl->data->model->textCells + 1];
+
+  buffer[0] = 0X01;
+  memcpy(buffer+1, brl->data->rawData, brl->data->model->textCells);
+  buffer[sizeof(buffer)-1] = ASCII_SYN;
+  return writeBrailleMessage(brl, NULL, 0X01, buffer, sizeof(buffer));
+}
+
+static int
+writeCells_Evolution (BrailleDisplay *brl) {
+  return writeExtendedPacket(brl, HT_EXTPKT_Braille,
+                             brl->data->rawData, brl->data->model->textCells);
+}
+
+static int
+updateCells (BrailleDisplay *brl) {
+  if (!brl->data->updateRequired) return 1;
+  if (brl->data->currentState != BDS_READY) return 1;
+
+  if (!writeCells(brl)) return 0;
+  brl->data->updateRequired = 0;
+  return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  const size_t cellCount = brl->data->model->textCells;
+
+  if (cellsHaveChanged(brl->data->prevData, brl->buffer, cellCount, NULL, NULL, NULL)) {
+    translateOutputCells(brl->data->rawData, brl->data->prevData, cellCount);
+    brl->data->updateRequired = 1;
+  }
+
+  return updateCells(brl);
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *st) {
+  const size_t cellCount = brl->data->model->statusCells;
+
+  if (cellsHaveChanged(brl->data->prevStatus, st, cellCount, NULL, NULL, NULL)) {
+    translateOutputCells(brl->data->rawStatus, brl->data->prevStatus, cellCount);
+    brl->data->updateRequired = 1;
+  }
+
+  return 1;
+}
+
+static int
+interpretByte_key (BrailleDisplay *brl, unsigned char byte) {
+  int release = (byte & HT_KEY_RELEASE) != 0;
+  if (release) byte ^= HT_KEY_RELEASE;
+
+  if ((byte >= HT_KEY_ROUTING) &&
+      (byte < (HT_KEY_ROUTING + brl->data->model->textCells))) {
+    return enqueueKeyEvent(brl, HT_GRP_RoutingKeys, byte - HT_KEY_ROUTING, !release);
+  }
+
+  if ((byte >= HT_KEY_STATUS) &&
+      (byte < (HT_KEY_STATUS + brl->data->model->statusCells))) {
+    return enqueueKeyEvent(brl, HT_GRP_NavigationKeys, byte, !release);
+  }
+
+  if (byte > 0) {
+    return enqueueKeyEvent(brl, HT_GRP_NavigationKeys, byte, !release);
+  }
+
+  return 0;
+}
+
+static int
+interpretByte_Bookworm (BrailleDisplay *brl, unsigned char byte) {
+  static const KeyNumber keys[] = {
+    HT_BWK_Backward,
+    HT_BWK_Forward,
+    HT_BWK_Escape,
+    HT_BWK_Enter,
+    0
+  };
+
+  const KeyNumber *key = keys;
+  const KeyGroup group = HT_GRP_NavigationKeys;
+
+  if (!byte) return 0;
+  {
+    unsigned char bits = byte;
+
+    while (*key) bits &= ~*key++;
+    if (bits) return 0;
+    key = keys;
+  }
+
+  while (*key) {
+    if ((byte & *key) && !enqueueKeyEvent(brl, group, *key, 1)) return 0;
+    key += 1;
+  }
+
+  do {
+    key -= 1;
+    if ((byte & *key) && !enqueueKeyEvent(brl, group, *key, 0)) return 0;
+  } while (key != keys);
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  while (1) {
+    HT_Packet packet;
+    size_t size = readPacket(brl, &packet, sizeof(packet));
+
+    if (size == 0) {
+      if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+      break;
+    }
+
+    /* a kludge to handle the Bookworm going offline */
+    if (brl->data->model->identifier == HT_MODEL_Bookworm) {
+      if (packet.fields.type == 0X06) {
+        if (brl->data->currentState != BDS_OFF) {
+          /* if we get another byte right away then the device
+           * has gone offline and is echoing its display
+           */
+          if (awaitBrailleInput(brl, 10)) {
+            setState(brl, BDS_OFF);
+            continue;
+          }
+
+          /* if an input error occurred then restart the driver */
+          if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+
+          /* no additional input so fall through and interpret the packet as keys */
+        }
+      }
+    }
+
+    switch (packet.fields.type) {
+      case HT_PKT_OK:
+        if (packet.fields.data.ok.model == brl->data->model->identifier) {
+          releaseBrailleKeys(brl);
+          brl->data->updateRequired = 1;
+          continue;
+        }
+        break;
+
+      default:
+        switch (brl->data->currentState) {
+          case BDS_OFF:
+            continue;
+
+          case BDS_READY:
+            switch (packet.fields.type) {
+              case HT_PKT_NAK:
+                brl->data->updateRequired = 1;
+              case HT_PKT_ACK:
+                acknowledgeBrailleMessage(brl);
+                continue;
+
+              case HT_PKT_Extended: {
+                unsigned char length = packet.fields.data.extended.length - 1;
+                const unsigned char *bytes = &packet.fields.data.extended.data.bytes[0];
+
+                switch (packet.fields.data.extended.type) {
+                  case HT_EXTPKT_Confirmation:
+                    switch (bytes[0]) {
+                      case HT_PKT_NAK:
+                        brl->data->updateRequired = 1;
+                      case HT_PKT_ACK:
+                        acknowledgeBrailleMessage(brl);
+                        continue;
+
+                      default:
+                        break;
+                    }
+                    break;
+
+                  case HT_EXTPKT_Key:
+                    if (brl->data->model->interpretByte(brl, bytes[0])) {
+                      updateCells(brl);
+                      return EOF;
+                    }
+                    break;
+
+                  case HT_EXTPKT_Scancode: {
+                    while (length--)
+                      enqueueCommand(BRL_CMD_BLK(PASSAT) | BRL_ARG_PUT(*bytes++));
+                    continue;
+                  }
+
+                  case HT_EXTPKT_GetRTC: {
+                    const HT_DateTime *const payload = (HT_DateTime *)bytes;
+                    DateTimeProcessor *processor = dateTimeProcessor;
+                    dateTimeProcessor = NULL;
+
+                    if (processor) {
+                      if (!processor(brl, payload)) {
+                        break;
+                      }
+                    }
+
+                    continue;
+                  }
+
+                  case HT_EXTPKT_AtcInfo: {
+                    unsigned int readingPosition = BRL_MSK_ARG;
+                    unsigned int highestPressure = 0;
+
+                    if (bytes[0]) {
+                      const unsigned int cellCount = brl->data->model->textCells + brl->data->model->statusCells;
+                      unsigned int cellIndex = bytes[0] - 1;
+                      unsigned int dataIndex;
+
+                      for (dataIndex=1; dataIndex<length; dataIndex+=1) {
+                        const unsigned char byte = bytes[dataIndex];
+
+                        const unsigned char pressures[] = {
+                          HIGH_NIBBLE(byte) >> 4,
+                          LOW_NIBBLE(byte)
+                        };
+
+                        const unsigned int pressureCount = ARRAY_COUNT(pressures);
+                        unsigned int pressureIndex;
+
+                        for (pressureIndex=0; pressureIndex<pressureCount; pressureIndex+=1) {
+                          const unsigned char pressure = pressures[pressureIndex];
+
+                          if (pressure > highestPressure) {
+                            highestPressure = pressure;
+                            readingPosition = cellIndex;
+                          }
+
+                          cellIndex += 1;
+                        }
+                      }
+
+                      if (readingPosition >= cellCount) readingPosition = BRL_MSK_ARG;
+                    }
+
+                    enqueueCommand(BRL_CMD_BLK(TOUCH_AT) | readingPosition);
+                    continue;
+                  }
+
+                  case HT_EXTPKT_ReadingPosition: {
+                    const unsigned int cellCount = brl->data->model->textCells + brl->data->model->statusCells;
+                    unsigned int readingPosition = bytes[0];
+
+                    if ((readingPosition == 0XFF) || (readingPosition >= cellCount)) {
+                      readingPosition = BRL_MSK_ARG;
+                    }
+
+                    enqueueCommand(BRL_CMD_BLK(TOUCH_AT) | readingPosition);
+                    continue;
+                  }
+
+                  default:
+                    break;
+                }
+                break;
+              }
+
+              default:
+                if (brl->data->model->interpretByte(brl, packet.fields.type)) {
+                  updateCells(brl);
+                  return EOF;
+                }
+                break;
+            }
+            break;
+        }
+        break;
+    }
+
+    logUnexpectedPacket(packet.bytes, size);
+    logMessage(LOG_WARNING, "state %d", brl->data->currentState);
+  }
+
+  updateCells(brl);
+
+  return EOF;
+}
diff --git a/Drivers/Braille/HandyTech/brldefs-ht.h b/Drivers/Braille/HandyTech/brldefs-ht.h
new file mode 100644
index 0000000..8d7a16e
--- /dev/null
+++ b/Drivers/Braille/HandyTech/brldefs-ht.h
@@ -0,0 +1,229 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HT_BRLDEFS
+#define BRLTTY_INCLUDED_HT_BRLDEFS
+
+#define HT_USB_VENDOR 0X1FE4
+
+typedef enum {
+  HT_MODEL_UsbHidAdapter = 0X03,
+  HT_MODEL_BrailleWave = 0X05,
+  HT_MODEL_ModularEvolution64 = 0X36,
+  HT_MODEL_ModularEvolution88 = 0X38,
+  HT_MODEL_ModularConnect88 = 0X3A,
+  HT_MODEL_EasyBraille = 0X44,
+  HT_MODEL_ActiveBraille = 0X54,
+  HT_MODEL_ConnectBraille40 = 0X55,
+  HT_MODEL_Actilino = 0X61,
+  HT_MODEL_ActiveStar40 = 0X64,
+  HT_MODEL_Braillino = 0X72,
+  HT_MODEL_BrailleStar40 = 0X74,
+  HT_MODEL_BrailleStar80 = 0X78,
+  HT_MODEL_Modular20 = 0X80,
+  HT_MODEL_BasicBraille16 = 0X81,
+  HT_MODEL_BasicBraille20 = 0X82,
+  HT_MODEL_BasicBraille32 = 0X83,
+  HT_MODEL_BasicBraille40 = 0X84,
+  HT_MODEL_BasicBraille64 = 0X86,
+  HT_MODEL_BasicBraille80 = 0X87,
+  HT_MODEL_Modular80 = 0X88,
+  HT_MODEL_Modular40 = 0X89,
+  HT_MODEL_BasicBraille48 = 0X8A,
+  HT_MODEL_BasicBraille160 = 0X8B,
+  HT_MODEL_Bookworm = 0X90,
+  HT_MODEL_BasicBraillePlus20 = 0X92,
+  HT_MODEL_BasicBraillePlus32 = 0X93,
+  HT_MODEL_BasicBraillePlus40 = 0X94,
+  HT_MODEL_BasicBraillePlus64 = 0X96,
+  HT_MODEL_BasicBraillePlus80 = 0X97,
+  HT_MODEL_BasicBraillePlus48 = 0X9A,
+  HT_MODEL_BasicBraillePlus84 = 0X9C,
+  HT_MODEL_Activator = 0XA4
+} HT_ModelIdentifier;
+
+/* Packet definition */
+typedef enum {
+  HT_PKT_Braille  = 0X01,
+  HT_PKT_Extended = 0X79,
+  HT_PKT_NAK      = 0X7D,
+  HT_PKT_ACK      = 0X7E,
+  HT_PKT_OK       = 0XFE,
+  HT_PKT_Reset    = 0XFF
+} HT_PacketType;
+
+typedef enum {
+  HT_EXTPKT_Braille               = HT_PKT_Braille,
+  HT_EXTPKT_Key                   = 0X04,
+  HT_EXTPKT_Confirmation          = 0X07,
+  HT_EXTPKT_Scancode              = 0X09,
+  HT_EXTPKT_Ping                  = 0X19,
+  HT_EXTPKT_GetSerialNumber       = 0X41,
+  HT_EXTPKT_SetRTC                = 0X44,
+  HT_EXTPKT_GetRTC                = 0X45,
+  HT_EXTPKT_GetBluetoothPIN       = 0X47,
+  HT_EXTPKT_SetAtcMode            = 0X50,
+  HT_EXTPKT_SetAtcSensitivity     = 0X51,
+  HT_EXTPKT_AtcInfo               = 0X52,
+  HT_EXTPKT_SetAtcSensitivity2    = 0X53,
+  HT_EXTPKT_GetAtcSensitivity2    = 0X54,
+  HT_EXTPKT_ReadingPosition       = 0X55,
+  HT_EXTPKT_SetFirmness           = 0X60,
+  HT_EXTPKT_GetFirmness           = 0X61,
+  HT_EXTPKT_GetProtocolProperties = 0XC1,
+  HT_EXTPKT_GetFirmwareVersion    = 0XC2
+} HT_ExtendedPacketType;
+
+typedef struct {
+  uint16_t year;
+  uint8_t month;
+  uint8_t day;
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+} PACKED HT_DateTime;
+
+typedef enum {
+  HT_MCAP_reqBatteryManagementInformation  = 0X001,
+  HT_MCAP_BatteryCalibrationAndTestMode    = 0X002,
+  HT_MCAP_getRealTimeClock                 = 0X004,
+  HT_MCAP_setRealTimeClock                 = 0X008,
+  HT_MCAP_getSerialNumber                  = 0X010,
+  HT_MCAP_setSerialNumber                  = 0X020,
+  HT_MCAP_getBluetoothPIN                  = 0X040,
+  HT_MCAP_setBluetoothPIN                  = 0X080,
+  HT_MCAP_setServiceInformation            = 0X100,
+  HT_MCAP_getServiceInformation            = 0X200
+} HT_MaintainanceCapabilities;
+
+typedef enum {
+  HT_ICAP_hasInternalMode               = 0X001,
+  HT_ICAP_updNormalModeFirmware         = 0X002,
+  HT_ICAP_updBrailleProcessorFirmware   = 0X004,
+  HT_ICAP_updUsbProcessorFirmware       = 0X008,
+  HT_ICAP_updBluetoothModuleFirmware    = 0X010,
+  HT_ICAP_getBrailleSystemConfiguration = 0X020,
+  HT_ICAP_setBrailleSystemConfiguration = 0X040
+} HT_InternalModeCapabilities;
+
+typedef struct {
+  unsigned char majorVersion;
+  unsigned char minorVersion;
+  unsigned char cellCount;
+  unsigned char hasSensitivity;
+  unsigned char maximumSensitivity;
+  unsigned char hasFirmness;
+  unsigned char maximumFirmness;
+  HT_MaintainanceCapabilities maintainanceCapabilities:16;
+  HT_InternalModeCapabilities internalModeCapabilities:16;
+} PACKED HT_ProtocolProperties;
+
+typedef union {
+  unsigned char bytes[4 + 0XFF];
+
+  struct {
+    unsigned char type;
+
+    union {
+      struct {
+        unsigned char model;
+      } PACKED ok;
+
+      struct {
+        unsigned char model;
+        unsigned char length;
+        unsigned char type;
+
+        union {
+          HT_DateTime dateTime;
+          HT_ProtocolProperties protocolProperties;
+          unsigned char bytes[0XFF];
+        } data;
+      } PACKED extended;
+    } data;
+  } PACKED fields;
+} HT_Packet;
+
+typedef enum {
+  HT_KEY_None = 0,
+
+  HT_KEY_B1 = 0X03,
+  HT_KEY_B2 = 0X07,
+  HT_KEY_B3 = 0X0B,
+  HT_KEY_B4 = 0X0F,
+
+  HT_KEY_B5 = 0X13,
+  HT_KEY_B6 = 0X17,
+  HT_KEY_B7 = 0X1B,
+  HT_KEY_B8 = 0X1F,
+
+  HT_KEY_Up = 0X04,
+  HT_KEY_Down = 0X08,
+
+  /* Keypad keys (star80 and modular) */
+  HT_KEY_B12 = 0X01,
+  HT_KEY_Zero = 0X05,
+  HT_KEY_B13 = 0X09,
+  HT_KEY_B14 = 0X0D,
+
+  HT_KEY_B11 = 0X11,
+  HT_KEY_One = 0X15,
+  HT_KEY_Two = 0X19,
+  HT_KEY_Three = 0X1D,
+
+  HT_KEY_B10 = 0X02,
+  HT_KEY_Four = 0X06,
+  HT_KEY_Five = 0X0A,
+  HT_KEY_Six = 0X0E,
+
+  HT_KEY_B9 = 0X12,
+  HT_KEY_Seven = 0X16,
+  HT_KEY_Eight = 0X1A,
+  HT_KEY_Nine = 0X1E,
+
+  /* Braille wave/star keys */
+  HT_KEY_Escape = 0X0C,
+  HT_KEY_Space = 0X10,
+  HT_KEY_Return = 0X14,
+
+  /* Braille star keys */
+  HT_KEY_SpaceRight = 0X18,
+
+  /* Actilino keys */
+  HT_KEY_JoystickLeft = 0X74,
+  HT_KEY_JoystickRight = 0X75,
+  HT_KEY_JoystickUp = 0X76,
+  HT_KEY_JoystickDown = 0X77,
+  HT_KEY_JoystickAction = 0X78,
+
+  /* Activator keys */
+  HT_KEY_LeftCenter = 0X7A,
+  HT_KEY_RightCenter = 0X7B,
+
+  /* ranges and flags */
+  HT_KEY_ROUTING = 0X20,
+  HT_KEY_STATUS = 0X70,
+  HT_KEY_RELEASE = 0X80
+} HT_NavigationKey;
+
+typedef enum {
+  HT_GRP_NavigationKeys = 0,
+  HT_GRP_RoutingKeys
+} HT_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_HT_BRLDEFS */ 
diff --git a/Drivers/Braille/Hedo/Makefile.in b/Drivers/Braille/Hedo/Makefile.in
new file mode 100644
index 0000000..191b3fe
--- /dev/null
+++ b/Drivers/Braille/Hedo/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = hd
+DRIVER_NAME = Hedo
+DRIVER_USAGE = ProfiLine, MobilLine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Hedo/braille.c b/Drivers/Braille/Hedo/braille.c
new file mode 100644
index 0000000..20867bb
--- /dev/null
+++ b/Drivers/Braille/Hedo/braille.c
@@ -0,0 +1,395 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-hd.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+
+#define MAXIMUM_RESPONSE_SIZE 3
+#define MAXIMUM_TEXT_CELL_COUNT 80
+#define MAXIMUM_STATUS_CELL_COUNT 4
+
+BEGIN_KEY_NAME_TABLE(pfl)
+  KEY_NAME_ENTRY(HD_PFL_K1, "K1"),
+  KEY_NAME_ENTRY(HD_PFL_K2, "K2"),
+  KEY_NAME_ENTRY(HD_PFL_K3, "K3"),
+
+  KEY_NAME_ENTRY(HD_PFL_B1, "B1"),
+  KEY_NAME_ENTRY(HD_PFL_B2, "B2"),
+  KEY_NAME_ENTRY(HD_PFL_B3, "B3"),
+  KEY_NAME_ENTRY(HD_PFL_B4, "B4"),
+  KEY_NAME_ENTRY(HD_PFL_B5, "B5"),
+  KEY_NAME_ENTRY(HD_PFL_B6, "B6"),
+  KEY_NAME_ENTRY(HD_PFL_B7, "B7"),
+  KEY_NAME_ENTRY(HD_PFL_B8, "B8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(mbl)
+  KEY_NAME_ENTRY(HD_MBL_B1, "B1"),
+  KEY_NAME_ENTRY(HD_MBL_B2, "B2"),
+  KEY_NAME_ENTRY(HD_MBL_B3, "B3"),
+
+  KEY_NAME_ENTRY(HD_MBL_B4, "B4"),
+  KEY_NAME_ENTRY(HD_MBL_B5, "B5"),
+  KEY_NAME_ENTRY(HD_MBL_B6, "B6"),
+
+  KEY_NAME_ENTRY(HD_MBL_K1, "K1"),
+  KEY_NAME_ENTRY(HD_MBL_K2, "K2"),
+  KEY_NAME_ENTRY(HD_MBL_K3, "K3"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(HD_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(pfl)
+  KEY_NAME_TABLE(pfl),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(mbl)
+  KEY_NAME_TABLE(mbl),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(pfl)
+DEFINE_KEY_TABLE(mbl)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(pfl),
+  &KEY_TABLE_DEFINITION(mbl),
+END_KEY_TABLE_LIST
+
+typedef int KeysPacketInterpreter (BrailleDisplay *brl, const unsigned char *packet);
+
+typedef struct {
+  const char *modelName;
+  const KeyTableDefinition *keyTableDefinition;
+
+  BraillePacketVerifier *verifyPacket;
+  KeysPacketInterpreter *interpretKeysPacket;
+
+  unsigned char textCellCount;
+  unsigned char statusCellCount;
+
+  unsigned char firstRoutingKey;
+  unsigned char acknowledgementResponse;
+} ModelEntry;
+
+struct BrailleDataStruct {
+  const ModelEntry *model;
+
+  unsigned char forceRewrite;
+  unsigned char textCells[MAXIMUM_TEXT_CELL_COUNT];
+  unsigned char statusCells[MAXIMUM_STATUS_CELL_COUNT];
+
+  KeyNumberSet navigationKeys;
+};
+
+static BraillePacketVerifierResult
+verifyPacket_ProfiLine (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  switch (size) {
+    case 1:
+      *length = 1;
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+interpretKeysPacket_ProfiLine (BrailleDisplay *brl, const unsigned char *packet) {
+  const unsigned char code = packet[0];
+  const unsigned char release = 0X80;
+  const int press = !(code & release);
+  unsigned char key = code & ~release;
+  KeyGroup group;
+
+  if (key < brl->data->model->firstRoutingKey) {
+    group = HD_GRP_NavigationKeys;
+  } else if (key < (brl->data->model->firstRoutingKey + brl->textColumns)) {
+    group = HD_GRP_RoutingKeys;
+    key -= brl->data->model->firstRoutingKey;
+  } else {
+    return 0;
+  }
+
+  enqueueKeyEvent(brl, group, key, press);
+  return 1;
+}
+
+static const ModelEntry modelEntry_ProfiLine = {
+  .modelName = "ProfiLine USB",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(pfl),
+
+  .verifyPacket = verifyPacket_ProfiLine,
+  .interpretKeysPacket = interpretKeysPacket_ProfiLine,
+
+  .textCellCount = 80,
+  .statusCellCount = 4,
+
+  .firstRoutingKey = 0X20,
+  .acknowledgementResponse = 0X7E
+};
+
+static BraillePacketVerifierResult
+verifyPacket_MobilLine (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  off_t index = size - 1;
+  unsigned char byte = bytes[index];
+
+  if ((byte >> 4) == index) {
+    if (index == 0) *length = 3;
+  } else if (size == 1) {
+    *length = 1;
+  } else {
+    return BRL_PVR_INVALID;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+interpretKeysPacket_MobilLine (BrailleDisplay *brl, const unsigned char *packet) {
+  const unsigned char *byte = packet;
+
+  if (!(*byte >> 4)) {
+    const unsigned char *end = packet + 3;
+    KeyNumberSet keys = 0;
+    unsigned char shift = 0;
+
+    while (byte < end) {
+      keys |= (*byte++ & 0XF) << shift;
+      shift += 4;
+    }
+
+    enqueueUpdatedKeys(brl, keys, &brl->data->navigationKeys,
+                       HD_GRP_NavigationKeys, 0);
+    return 1;
+  }
+
+  if (*byte >= brl->data->model->firstRoutingKey) {
+    unsigned char key = *byte - brl->data->model->firstRoutingKey;
+
+    if (key < brl->textColumns) {
+      enqueueKey(brl, HD_GRP_RoutingKeys, key);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static const ModelEntry modelEntry_MobilLine = {
+  .modelName = "MobilLine USB",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(mbl),
+
+  .verifyPacket = verifyPacket_MobilLine,
+  .interpretKeysPacket = interpretKeysPacket_MobilLine,
+
+  .textCellCount = 40,
+  .statusCellCount = 2,
+
+  .firstRoutingKey = 0X40,
+  .acknowledgementResponse = 0X30
+};
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, brl->data->model->verifyPacket, NULL);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters_ProfiLine = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 19200,
+    .parity = SERIAL_PARITY_ODD
+  };
+
+  static const SerialParameters serialParameters_MobilLine = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600,
+    .parity = SERIAL_PARITY_ODD
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_0403_6001)
+    "Hedo Reha Technik GmbH",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* ProfiLine */
+      .vendor=0X0403, .product=0XDE59,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters_ProfiLine,
+      .data = &modelEntry_ProfiLine
+    },
+
+    { /* MobilLine */
+      .vendor=0X0403, .product=0XDE58,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters_MobilLine,
+      .data = &modelEntry_MobilLine
+    },
+
+    { /* MobilLine */
+      .vendor=0X0403, .product=0X6001,
+      .manufacturers = usbManufacturers_0403_6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters_MobilLine,
+      .data = &modelEntry_MobilLine
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+disconnectResource (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+writeCells (BrailleDisplay *brl, int wait) {
+  unsigned char packet[1 + brl->data->model->statusCellCount + brl->data->model->textCellCount];
+  unsigned char *byte = packet;
+
+  *byte++ = HD_REQ_WRITE_CELLS;
+  byte = mempcpy(byte, brl->data->statusCells, brl->data->model->statusCellCount);
+  byte = translateOutputCells(byte, brl->data->textCells, brl->data->model->textCellCount);
+
+  {
+    size_t count = byte - packet;
+
+    if (wait) return writeBrailleMessage(brl, NULL, 0, packet, count);
+    return writeBraillePacket(brl, NULL, packet, count);
+  }
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  memset(brl->data->textCells, 0, sizeof(brl->data->textCells));
+  memset(brl->data->statusCells, 0, sizeof(brl->data->statusCells));
+  return writeCells(brl, 0);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const unsigned char *bytes = packet;
+
+  return (bytes[0] == brl->data->model->acknowledgementResponse)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned char response[MAXIMUM_RESPONSE_SIZE];
+
+      brl->data->model = gioGetApplicationData(brl->gioEndpoint);
+      brl->textColumns = brl->data->model->textCellCount;
+      makeOutputTable(dotsTable_ISO11548_1);
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+
+        brl->data->forceRewrite = 1;
+        return 1;
+      }
+
+      disconnectResource(brl);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectResource(brl);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
+    if (!writeCells(brl, 1)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    if (packet[0] == brl->data->model->acknowledgementResponse) {
+      acknowledgeBrailleMessage(brl);
+    } else if (!brl->data->model->interpretKeysPacket(brl, packet)) {
+      logUnexpectedPacket(packet, size);
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Hedo/brldefs-hd.h b/Drivers/Braille/Hedo/brldefs-hd.h
new file mode 100644
index 0000000..e0ef2b3
--- /dev/null
+++ b/Drivers/Braille/Hedo/brldefs-hd.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HD_BRLDEFS
+#define BRLTTY_INCLUDED_HD_BRLDEFS
+
+typedef enum {
+  HD_REQ_WRITE_CELLS = 0X01
+} HD_RequestCode;
+
+typedef enum {
+  HD_PFL_K1 = 0X04,
+  HD_PFL_K2 = 0X03,
+  HD_PFL_K3 = 0X08,
+
+  HD_PFL_B1 = 0X03,
+  HD_PFL_B2 = 0X07,
+  HD_PFL_B3 = 0X0B,
+  HD_PFL_B4 = 0X0F,
+  HD_PFL_B5 = 0X13,
+  HD_PFL_B6 = 0X17,
+  HD_PFL_B7 = 0X1B,
+  HD_PFL_B8 = 0X1F
+} HD_KeyCode_ProfiLine;
+
+typedef enum {
+  HD_MBL_B1 =  0,
+  HD_MBL_B2 =  1,
+  HD_MBL_B3 =  2,
+
+  HD_MBL_B4 =  4,
+  HD_MBL_B5 =  5,
+  HD_MBL_B6 =  6,
+
+  HD_MBL_K1 =  8,
+  HD_MBL_K2 =  9,
+  HD_MBL_K3 = 10
+} HD_KeyCode_MobilLine;
+
+typedef enum {
+  HD_GRP_NavigationKeys,
+  HD_GRP_RoutingKeys
+} HD_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_HD_BRLDEFS */ 
diff --git a/Drivers/Braille/HumanWare/Makefile.in b/Drivers/Braille/HumanWare/Makefile.in
new file mode 100644
index 0000000..b1046a6
--- /dev/null
+++ b/Drivers/Braille/HumanWare/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = hw
+DRIVER_NAME = HumanWare
+DRIVER_USAGE = Brailliant BI 14/32/40, Brailliant BI 20X/40X, Brailliant B 80, BrailleNote Touch, BrailleOne, APH Chameleon 20, APH Mantis Q40, NLS eReader
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/HumanWare/braille.c b/Drivers/Braille/HumanWare/braille.c
new file mode 100644
index 0000000..f115316
--- /dev/null
+++ b/Drivers/Braille/HumanWare/braille.c
@@ -0,0 +1,1256 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ascii.h"
+#include "bitmask.h"
+#include "async_wait.h"
+
+#include "brl_driver.h"
+#include "brldefs-hw.h"
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(HW_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(braille)
+  KEY_NAME_ENTRY(HW_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(HW_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(HW_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(HW_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(HW_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(HW_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(HW_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(HW_KEY_Dot8, "Dot8"),
+  KEY_NAME_ENTRY(HW_KEY_Space, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(command)
+  KEY_NAME_ENTRY(HW_KEY_Command1, "Display1"),
+  KEY_NAME_ENTRY(HW_KEY_Command2, "Display2"),
+  KEY_NAME_ENTRY(HW_KEY_Command3, "Display3"),
+  KEY_NAME_ENTRY(HW_KEY_Command4, "Display4"),
+  KEY_NAME_ENTRY(HW_KEY_Command5, "Display5"),
+  KEY_NAME_ENTRY(HW_KEY_Command6, "Display6"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(joystick)
+  KEY_NAME_ENTRY(HW_KEY_Up, "Up"),
+  KEY_NAME_ENTRY(HW_KEY_Down, "Down"),
+  KEY_NAME_ENTRY(HW_KEY_Left, "Left"),
+  KEY_NAME_ENTRY(HW_KEY_Right, "Right"),
+  KEY_NAME_ENTRY(HW_KEY_Action, "Action"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(thumb)
+  KEY_NAME_ENTRY(HW_KEY_ThumbPrevious, "ThumbPrevious"),
+  KEY_NAME_ENTRY(HW_KEY_ThumbLeft, "ThumbLeft"),
+  KEY_NAME_ENTRY(HW_KEY_ThumbRight, "ThumbRight"),
+  KEY_NAME_ENTRY(HW_KEY_ThumbNext, "ThumbNext"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(BI14)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(joystick),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(BI32)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(command),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(BI40)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(command),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(B80)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(command),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(touch)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(C20)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(M40)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(NLS)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(one)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(BI40X)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+  KEY_NAME_TABLE(command),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(BI20X)
+  KEY_NAME_TABLE(routing),
+  KEY_NAME_TABLE(thumb),
+  KEY_NAME_TABLE(braille),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(BI14)
+DEFINE_KEY_TABLE(BI32)
+DEFINE_KEY_TABLE(BI40)
+DEFINE_KEY_TABLE(B80)
+DEFINE_KEY_TABLE(touch)
+DEFINE_KEY_TABLE(C20)
+DEFINE_KEY_TABLE(M40)
+DEFINE_KEY_TABLE(NLS)
+DEFINE_KEY_TABLE(one)
+DEFINE_KEY_TABLE(BI40X)
+DEFINE_KEY_TABLE(BI20X)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(BI14),
+  &KEY_TABLE_DEFINITION(BI32),
+  &KEY_TABLE_DEFINITION(BI40),
+  &KEY_TABLE_DEFINITION(B80),
+  &KEY_TABLE_DEFINITION(touch),
+  &KEY_TABLE_DEFINITION(C20),
+  &KEY_TABLE_DEFINITION(M40),
+  &KEY_TABLE_DEFINITION(NLS),
+  &KEY_TABLE_DEFINITION(one),
+  &KEY_TABLE_DEFINITION(BI40X),
+  &KEY_TABLE_DEFINITION(BI20X),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  const char *modelName;
+  const KeyTableDefinition *keyTableDefinition;
+  HW_ModelIdentifier modelIdentifier;
+  unsigned char pressedKeysReportSize;
+
+  unsigned char hasBrailleKeys:1;
+  unsigned char hasCommandKeys:1;
+  unsigned char hasJoystick:1;
+  unsigned char hasSecondThumbKeys:1;
+} ModelEntry;
+
+static const ModelEntry modelEntry_BI14 = {
+  .modelName = "Brailliant BI 14",
+  .hasBrailleKeys = 1,
+  .hasJoystick = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(BI14)
+};
+
+static const ModelEntry modelEntry_BI32 = {
+  .modelName = "Brailliant BI 32",
+  .hasBrailleKeys = 1,
+  .hasCommandKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(BI32)
+};
+
+static const ModelEntry modelEntry_BI40 = {
+  .modelName = "Brailliant BI 40",
+  .hasBrailleKeys = 1,
+  .hasCommandKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(BI40)
+};
+
+static const ModelEntry modelEntry_B80 = {
+  .modelName = "Brailliant B 80",
+  .hasCommandKeys = 1,
+  .hasSecondThumbKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(B80)
+};
+
+static const ModelEntry modelEntry_touch = {
+  .modelName = "BrailleNote Touch",
+  .modelIdentifier = HW_MODEL_HW_BRAILLE_NOTE_TOUCH,
+  .hasBrailleKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(touch)
+};
+
+static const ModelEntry modelEntry_C20 = {
+  .modelName = "APH Chameleon 20",
+  .modelIdentifier = HW_MODEL_APH_CHAMELEON_20,
+  .hasBrailleKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(C20)
+};
+
+static const ModelEntry modelEntry_M40 = {
+  .modelName = "APH Mantis Q40",
+  .modelIdentifier = HW_MODEL_APH_MANTIS_Q40,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(M40)
+};
+
+static const ModelEntry modelEntry_NLS = {
+  .modelName = "NLS eReader",
+  .modelIdentifier = HW_MODEL_NLS_EREADER,
+  .hasBrailleKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(NLS)
+};
+
+static const ModelEntry modelEntry_one = {
+  .modelName = "HumanWare BrailleOne",
+  .modelIdentifier = HW_MODEL_HW_BRAILLE_ONE,
+  .hasBrailleKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(one)
+};
+
+static const ModelEntry modelEntry_BI40X = {
+  .modelName = "Brailliant BI 40X",
+  .pressedKeysReportSize = 46,
+  .hasBrailleKeys = 1,
+  .hasCommandKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(BI40X)
+};
+
+static const ModelEntry modelEntry_BI20X = {
+  .modelName = "Brailliant BI 20X",
+  .hasBrailleKeys = 1,
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(BI20X)
+};
+
+static const ModelEntry *modelTable[] = {
+  &modelEntry_BI14,
+  &modelEntry_BI32,
+  &modelEntry_BI40,
+  &modelEntry_B80,
+  &modelEntry_touch,
+  &modelEntry_C20,
+  &modelEntry_M40,
+  &modelEntry_NLS,
+  &modelEntry_one,
+  &modelEntry_BI40X,
+  &modelEntry_BI20X,
+};
+
+static unsigned char modelCount = ARRAY_COUNT(modelTable);
+
+static const ModelEntry *
+getModelByIdentifier (HW_ModelIdentifier identifier) {
+  if (identifier) {
+    const ModelEntry *const *model = modelTable;
+    const ModelEntry *const *end = model + modelCount;
+
+    while (model < end) {
+      if ((*model)->modelIdentifier == identifier) return *model;
+      model += 1;
+    }
+  }
+
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+    "unknown model identifier: %u", identifier
+  );
+
+  return NULL;
+}
+
+#define OPEN_READY_DELAY 100
+
+#define SERIAL_PROBE_RESPONSE_TIMEOUT 1000
+#define SERIAL_PROBE_RETRY_LIMIT 0
+
+#define SERIAL_INIT_RESEND_DELAY 100
+#define SERIAL_INIT_RESEND_LIMIT 10
+
+#define MAXIMUM_TEXT_CELL_COUNT 0XFF
+
+#define MAXIMUM_KEY_VALUE 0XFF
+#define KEYS_BITMASK(name) BITMASK(name, (MAXIMUM_KEY_VALUE + 1), int)
+
+#define BRAILLE_KEY_COUNT (8 + 1)
+#define COMMAND_KEY_COUNT 6
+#define THUMB_KEY_COUNT 4
+#define JOYSTICK_KEY_COUNT 5
+
+typedef struct {
+  const char *name;
+  int (*probeDisplay) (BrailleDisplay *brl);
+  int (*writeCells) (BrailleDisplay *brl, const unsigned char *cells, unsigned char count);
+  int (*processInputPacket) (BrailleDisplay *brl);
+  int (*keepAwake) (BrailleDisplay *brl);
+} ProtocolEntry;
+
+struct BrailleDataStruct {
+  const ProtocolEntry *protocol;
+  const ModelEntry *model;
+
+  uint32_t firmwareVersion;
+  unsigned isOffline:1;
+
+  struct {
+    unsigned char count;
+    KEYS_BITMASK(mask);
+  } pressedKeys;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAXIMUM_TEXT_CELL_COUNT];
+  } text;
+
+  struct {
+    struct {
+      unsigned char resendCount;
+    } init;
+  } serial;
+
+  struct {
+    struct {
+      unsigned char reportSize;
+    } pressedKeys;
+  } hid;
+};
+
+static const ModelEntry *
+getModelByCellCount (BrailleDisplay *brl) {
+  unsigned int cellCount = brl->textColumns;
+
+  switch (cellCount) {
+    case 14: return &modelEntry_BI14;
+    case 32: return &modelEntry_BI32;
+    case 40: return &modelEntry_BI40;
+    case 80: return &modelEntry_B80;
+
+    default:
+      logMessage(LOG_WARNING, "unknown cell count: %u", cellCount);
+      return NULL;
+  }
+}
+
+static int
+setModel (BrailleDisplay *brl) {
+  if (!brl->data->model) {
+    if (!(brl->data->model = getModelByCellCount(brl))) {
+      return 0;
+    }
+  }
+
+  logMessage(LOG_DEBUG, "Model Name: %s", brl->data->model->modelName);
+  return 1;
+}
+
+static int
+getDecimalValue (const char *digits, unsigned int count) {
+  const char *end = digits + count;
+  unsigned int result = 0;
+  const char zero = '0';
+
+  while (digits < end) {
+    char digit = *digits++;
+    if (digit >= zero) digit -= zero;
+
+    if (digit < 0) return 0;
+    if (digit > 9) return 0;
+
+    result *= 10;
+    result += digit;
+  }
+
+  return result;
+}
+
+static void
+setFirmwareVersion (BrailleDisplay *brl, unsigned char major, unsigned char minor, unsigned char build) {
+  logMessage(LOG_INFO, "Firmware Version: %u.%u.%u", major, minor, build);
+  brl->data->firmwareVersion = (major << 16) | (minor << 8) << (build << 0);
+}
+
+static int
+handleKeyEvent (BrailleDisplay *brl, unsigned char key, int press) {
+  KeyGroup group;
+
+  if (key < HW_KEY_ROUTING) {
+    group = HW_GRP_NavigationKeys;
+  } else {
+    group = HW_GRP_RoutingKeys;
+    key -= HW_KEY_ROUTING;
+  }
+
+  return enqueueKeyEvent(brl, group, key, press);
+}
+
+static int
+isCalibrationKey (BrailleDisplay *brl, unsigned char key) {
+  switch (key) {
+    default:
+      return 0;
+
+    case HW_KEY_CAL_OK:
+    case HW_KEY_CAL_FAIL:
+    case HW_KEY_CAL_EMPTY:
+    case HW_KEY_CAL_RESET:
+      break;
+  }
+
+  releaseBrailleKeys(brl);
+  BITMASK_ZERO(brl->data->pressedKeys.mask);
+  brl->data->pressedKeys.count = 0;
+  return 1;
+}
+
+static int
+handleKeyPress (BrailleDisplay *brl, unsigned char key) {
+  if (BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
+
+  BITMASK_SET(brl->data->pressedKeys.mask, key);
+  brl->data->pressedKeys.count += 1;
+
+  handleKeyEvent(brl, key, 1);
+  return 1;
+}
+
+static int
+handleKeyRelease (BrailleDisplay *brl, unsigned char key) {
+  if (!BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
+
+  BITMASK_CLEAR(brl->data->pressedKeys.mask, key);
+  brl->data->pressedKeys.count -= 1;
+
+  handleKeyEvent(brl, key, 0);
+  return 1;
+}
+
+static void
+handlePressedKeysArray (BrailleDisplay *brl, unsigned char *keys, size_t count) {
+  KEYS_BITMASK(pressedMask);
+  BITMASK_ZERO(pressedMask);
+  unsigned int pressedCount = 0;
+
+  {
+    const unsigned char *key = keys;
+    const unsigned char *end = keys + count;
+
+    while (key < end) {
+      if (!*key) break;
+
+      if (!BITMASK_TEST(pressedMask, *key)) {
+        BITMASK_SET(pressedMask, *key);
+        pressedCount += 1;
+
+        if (isCalibrationKey(brl, *key)) return;
+        handleKeyPress(brl, *key);
+      }
+
+      key += 1;
+    }
+  }
+
+  if (brl->data->pressedKeys.count > pressedCount) {
+    for (unsigned int key=0; key<=MAXIMUM_KEY_VALUE; key+=1) {
+      if (!BITMASK_TEST(pressedMask, key)) {
+        if (handleKeyRelease(brl, key)) {
+          if (brl->data->pressedKeys.count == pressedCount) {
+            break;
+          }
+        }
+      }
+    }
+  }
+}
+
+static void
+handlePoweringOff (BrailleDisplay *brl) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "powering off");
+  brl->data->isOffline = 1;
+}
+
+static BraillePacketVerifierResult
+verifySerialPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      if (byte != ASCII_ESC) return BRL_PVR_INVALID;
+      *length = 3;
+      break;
+
+    case 3:
+      *length += byte;
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readSerialPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  return readBraillePacket(brl, NULL, buffer, size, verifySerialPacket, NULL);
+}
+
+static int
+writeSerialPacket (BrailleDisplay *brl, unsigned char type, unsigned char length, const void *data) {
+  HW_Packet packet;
+
+  packet.fields.header = ASCII_ESC;
+  packet.fields.type = type;
+  packet.fields.length = length;
+
+  if (data) memcpy(packet.fields.data.bytes, data, length);
+  length += packet.fields.data.bytes - packet.bytes;
+
+  return writeBraillePacket(brl, NULL, &packet, length);
+}
+
+static int
+writeSerialRequest (BrailleDisplay *brl, unsigned char type) {
+  return writeSerialPacket(brl, type, 0, NULL);
+}
+
+static int
+writeSerialIdentifyRequest (BrailleDisplay *brl) {
+  return writeSerialRequest(brl, HW_MSG_INIT);
+}
+
+static size_t
+readSerialResponse (BrailleDisplay *brl, void *packet, size_t size) {
+  return readSerialPacket(brl, packet, size);
+}
+
+static BrailleResponseResult
+isSerialIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const HW_Packet *response = packet;
+
+  if (response->fields.type != HW_MSG_INIT_RESP) return BRL_RSP_UNEXPECTED;
+  if (!response->fields.data.init.stillInitializing) return BRL_RSP_DONE;
+
+  if (++brl->data->serial.init.resendCount > SERIAL_INIT_RESEND_LIMIT) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "channel initialization timeout");
+    return BRL_RSP_FAIL;
+  }
+
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "channel still initializing");
+  asyncWait(SERIAL_INIT_RESEND_DELAY);
+
+  if (writeSerialIdentifyRequest(brl)) return BRL_RSP_CONTINUE;
+  return BRL_RSP_FAIL;
+}
+
+static int
+probeSerialDisplay (BrailleDisplay *brl) {
+  HW_Packet response;
+
+  brl->data->serial.init.resendCount = 0;
+
+  if (probeBrailleDisplay(brl, SERIAL_PROBE_RETRY_LIMIT,
+                          NULL, SERIAL_PROBE_RESPONSE_TIMEOUT,
+                          writeSerialIdentifyRequest,
+                          readSerialResponse, &response, sizeof(response.bytes),
+                          isSerialIdentityResponse)) {
+    logMessage(LOG_INFO, "detected Humanware device: model=%u cells=%u",
+               response.fields.data.init.modelIdentifier,
+               response.fields.data.init.cellCount);
+
+    {
+      unsigned char identifier = response.fields.data.init.modelIdentifier;
+      const ModelEntry *model = getModelByIdentifier(identifier);
+
+      if (model) {
+        if (!brl->data->model) {
+          brl->data->model = model;
+        } else if (model != brl->data->model) {
+        }
+      }
+    }
+
+    brl->textColumns = response.fields.data.init.cellCount;
+    setModel(brl);
+
+    writeSerialRequest(brl, HW_MSG_GET_FIRMWARE_VERSION);
+    writeSerialRequest(brl, HW_MSG_GET_KEYS);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeSerialCells (BrailleDisplay *brl, const unsigned char *cells, unsigned char count) {
+  return writeSerialPacket(brl, HW_MSG_DISPLAY, count, cells);
+}
+
+static int
+processSerialInputPacket (BrailleDisplay *brl) {
+  HW_Packet packet;
+  size_t length = readSerialPacket(brl, &packet, sizeof(packet));
+  if (!length) return 0;
+  brl->data->isOffline = 0;
+
+  switch (packet.fields.type) {
+    case HW_MSG_KEYS:
+      handlePressedKeysArray(brl, packet.fields.data.bytes, packet.fields.length);
+      break;
+
+    case HW_MSG_KEY_DOWN: {
+      unsigned char key = packet.fields.data.key.id;
+      if (isCalibrationKey(brl, key)) break;
+
+      handleKeyPress(brl, key);
+      break;
+    }
+
+    case HW_MSG_KEY_UP:
+      handleKeyRelease(brl, packet.fields.data.key.id);
+      break;
+
+    case HW_MSG_FIRMWARE_VERSION_RESP:
+      setFirmwareVersion(brl,
+        packet.fields.data.firmwareVersion.major,
+        packet.fields.data.firmwareVersion.minor,
+        packet.fields.data.firmwareVersion.build);
+      break;
+
+    case HW_MSG_KEEP_AWAKE_RESP:
+      break;
+
+    case HW_MSG_POWERING_OFF:
+      handlePoweringOff(brl);
+      break;
+
+    default:
+      logUnexpectedPacket(&packet, length);
+      break;
+  }
+
+  return 1;
+}
+
+static int
+keepSerialAwake (BrailleDisplay *brl) {
+  return writeSerialRequest(brl, HW_MSG_KEEP_AWAKE);
+}
+
+static const ProtocolEntry serialProtocol = {
+  .name = "serial",
+  .probeDisplay = probeSerialDisplay,
+  .writeCells = writeSerialCells,
+  .processInputPacket = processSerialInputPacket,
+  .keepAwake = keepSerialAwake
+};
+
+static ssize_t
+readHidFeature (
+  BrailleDisplay *brl, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size
+) {
+  ssize_t length = gioGetHidFeature(brl->gioEndpoint, identifier, buffer, size);
+
+  if (length != -1) {
+    if ((length > 0) && (*buffer == identifier)) {
+      logInputPacket(buffer, length);
+      return length;
+    }
+
+    errno = EAGAIN;
+  }
+
+  logSystemError("HID feature read");
+  return -1;
+}
+
+static int
+writeHidReport (BrailleDisplay *brl, const void *data, size_t size) {
+  logOutputPacket(data, size);
+
+  {
+    ssize_t result = gioWriteHidReport(brl->gioEndpoint, data, size);
+    if (result != -1) return 1;
+  }
+
+  logSystemError("HID report write");
+  return 0;
+}
+
+static BraillePacketVerifierResult
+verifyHidPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        case HW_REP_FTR_Capabilities:
+          *length = sizeof(HW_CapabilitiesReport);
+          break;
+
+        case HW_REP_FTR_Settings:
+          *length = sizeof(HW_SettingsReport);
+          break;
+
+        case HW_REP_FTR_Configuration:
+          *length = sizeof(HW_ConfigurationReport);
+          break;
+
+        case HW_REP_IN_PressedKeys:
+          *length = brl->data->hid.pressedKeys.reportSize;
+          break;
+
+        case HW_REP_FTR_KeepAwake:
+          *length = sizeof(HW_KeepAwakeReport);
+          break;
+
+        case HW_REP_IN_PoweringOff:
+          *length = sizeof(HW_PoweringOffReport);
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readHidPacket (BrailleDisplay *brl, void *buffer, size_t size) {
+  return readBraillePacket(brl, NULL, buffer, size, verifyHidPacket, NULL);
+}
+
+static size_t
+getPressedKeysReportSize (BrailleDisplay *brl) {
+  {
+    size_t size = gioGetHidInputSize(brl->gioEndpoint, HW_REP_IN_PressedKeys);
+    if (size) return size;
+  }
+
+  {
+    size_t size = brl->data->model->pressedKeysReportSize;
+    if (size) return size;
+  }
+
+  size_t size = 1;
+  size += brl->textColumns;
+  size += THUMB_KEY_COUNT;
+  if (brl->data->model->hasBrailleKeys) size += BRAILLE_KEY_COUNT;
+  if (brl->data->model->hasCommandKeys) size += COMMAND_KEY_COUNT;
+  if (brl->data->model->hasJoystick) size += JOYSTICK_KEY_COUNT;
+  if (brl->data->model->hasSecondThumbKeys) size += THUMB_KEY_COUNT;
+  return size;
+}
+
+static int
+probeHidDisplay (BrailleDisplay *brl) {
+  brl->textColumns = 0;
+
+  if (!brl->textColumns) {
+    size_t size = gioGetHidOutputSize(brl->gioEndpoint, HW_REP_OUT_WriteCells);
+    if (size > 4) brl->textColumns = size - 4;
+  }
+
+  if (!brl->textColumns) {
+    HW_CapabilitiesReport capabilities;
+    unsigned char *const buffer = (unsigned char *)&capabilities;
+    const size_t size = sizeof(capabilities);
+
+    ssize_t length = readHidFeature(brl, HW_REP_FTR_Capabilities, buffer, size);
+    if (length == -1) return 0;
+    memset(&buffer[length], 0, (size - length));
+
+    setFirmwareVersion(brl,
+      getDecimalValue(&capabilities.version.major, 1),
+      getDecimalValue(&capabilities.version.minor, 1),
+      getDecimalValue(&capabilities.version.build[0], 2)
+    );
+
+    brl->textColumns = capabilities.cellCount;
+  }
+
+  {
+    unsigned char *size = &brl->data->hid.pressedKeys.reportSize;
+    *size = getPressedKeysReportSize(brl);
+
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER),
+      "pressed keys report size: %u", *size
+    );
+  }
+
+  if (!setModel(brl)) return 0;
+  return 1;
+}
+
+static int
+writeHidCells (BrailleDisplay *brl, const unsigned char *cells, unsigned char count) {
+  unsigned char buffer[4 + count];
+  unsigned char *byte = buffer;
+
+  *byte++ = HW_REP_OUT_WriteCells;
+  *byte++ = 1;
+  *byte++ = 0;
+  *byte++ = count;
+  byte = mempcpy(byte, cells, count);
+
+  return writeHidReport(brl, buffer, byte-buffer);
+}
+
+static int
+processHidInputPacket (BrailleDisplay *brl) {
+  unsigned char packet[0XFF];
+  size_t length = readHidPacket(brl, packet, sizeof(packet));
+  if (!length) return 0;
+  brl->data->isOffline = 0;
+
+  switch (packet[0]) {
+    case HW_REP_IN_PressedKeys: {
+      const unsigned int offset = 1;
+
+      handlePressedKeysArray(brl, packet+offset, length-offset);
+      break;
+    }
+
+    case HW_REP_IN_PoweringOff:
+      handlePoweringOff(brl);
+      break;
+
+    default:
+      logUnexpectedPacket(packet, length);
+    case HW_REP_FTR_Settings:
+    case HW_REP_FTR_Configuration:
+      break;
+  }
+
+  return 1;
+}
+
+static int
+keepHidAwake (BrailleDisplay *brl) {
+  HW_KeepAwakeReport report;
+
+  memset(&report, 0, sizeof(report));
+  report.reportIdentifier = HW_REP_FTR_KeepAwake;
+
+  return writeHidReport(brl, &report, sizeof(report));
+}
+
+static const ProtocolEntry hidProtocol = {
+  .name = "HID",
+  .probeDisplay = probeHidDisplay,
+  .writeCells = writeHidCells,
+  .processInputPacket = processHidInputPacket,
+  .keepAwake = keepHidAwake
+};
+
+typedef struct {
+  const ProtocolEntry *protocol;
+  const ModelEntry *model;
+} ResourceData;
+
+static const ResourceData resourceData_serial_generic = {
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_serial_BI14 = {
+  .model = &modelEntry_BI14,
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_serial_C20 = {
+  .model = &modelEntry_C20,
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_serial_M40 = {
+  .model = &modelEntry_M40,
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_serial_NLS = {
+  .model = &modelEntry_NLS,
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_serial_one = {
+  .model = &modelEntry_one,
+  .protocol = &serialProtocol
+};
+
+static const ResourceData resourceData_HID_generic = {
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_touch = {
+  .model = &modelEntry_touch,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_C20 = {
+  .model = &modelEntry_C20,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_M40 = {
+  .model = &modelEntry_M40,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_NLS = {
+  .model = &modelEntry_NLS,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_one = {
+  .model = &modelEntry_one,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_BI40X = {
+  .model = &modelEntry_BI40X,
+  .protocol = &hidProtocol
+};
+
+static const ResourceData resourceData_HID_BI20X = {
+  .model = &modelEntry_BI20X,
+  .protocol = &hidProtocol
+};
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 115200,
+    .parity = SERIAL_PARITY_EVEN
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Brailliant BI 32/40, Brailliant B 80 (serial protocol) */
+      .vendor=0X1C71, .product=0XC005, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=2, .outputEndpoint=3,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_generic,
+      .resetDevice = 1
+    },
+
+    { /* Brailliant BI 14 (serial protocol) */
+      .vendor=0X1C71, .product=0XC021, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_BI14,
+      .resetDevice = 1
+    },
+
+    { /* APH Chameleon 20 (serial protocol) */
+      .vendor=0X1C71, .product=0XC104, 
+      .configuration=1, .interface=5, .alternative=0,
+      .inputEndpoint=10, .outputEndpoint=11,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_C20,
+      .resetDevice = 1
+    },
+
+    { /* APH Mantis Q40 (serial protocol) */
+      .vendor=0X1C71, .product=0XC114, 
+      .configuration=1, .interface=5, .alternative=0,
+      .inputEndpoint=10, .outputEndpoint=11,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_M40,
+      .resetDevice = 1
+    },
+
+    { /* NLS eReader (serial protocol) */
+      .vendor=0X1C71, .product=0XCE04, 
+      .configuration=1, .interface=5, .alternative=0,
+      .inputEndpoint=10, .outputEndpoint=11,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_NLS,
+      .resetDevice = 1
+    },
+
+    { /* Humanware BrailleOne (serial protocol) */
+      .vendor=0X1C71, .product=0XC124, 
+      .configuration=1, .interface=5, .alternative=0,
+      .inputEndpoint=10, .outputEndpoint=11,
+      .serial = &serialParameters,
+      .data = &resourceData_serial_one,
+      .resetDevice = 1
+    },
+
+    { /* non-Touch models (HID protocol) */
+      .vendor=0X1C71, .product=0XC006,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1,
+      .data = &resourceData_HID_generic
+    },
+
+    { /* BrailleNote Touch (HID protocol) */
+      .vendor=0X1C71, .product=0XC00A,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1,
+      .data = &resourceData_HID_touch
+    },
+
+    { /* APH Chameleon 20 (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XC101, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_C20,
+      .resetDevice = 1
+    },
+
+    { /* APH Chameleon 20 (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XC101, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_C20,
+      .resetDevice = 1
+    },
+
+    { /* APH Mantis Q40 (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XC111, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_M40,
+      .resetDevice = 1
+    },
+
+    { /* APH Mantis Q40 (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XC111, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_M40,
+      .resetDevice = 1
+    },
+
+    { /* NLS eReader (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XCE01, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_NLS,
+      .resetDevice = 1
+    },
+
+    { /* NLS eReader (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XCE01, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_NLS,
+      .resetDevice = 1
+    },
+
+    { /* Humanware BrailleOne (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XC121, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_one,
+      .resetDevice = 1
+    },
+
+    { /* Humanware BrailleOne (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XC121, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_one,
+      .resetDevice = 1
+    },
+
+    { /* Humanware Brailliant BI 40X (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XC131, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_BI40X,
+      .resetDevice = 1
+    },
+
+    { /* Humanware Brailliant BI 40X (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XC131, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_BI40X,
+      .resetDevice = 1
+    },
+
+    { /* Humanware Brailliant BI 20X (HID protocol, firmware 1.0) */
+      .vendor=0X1C71, .product=0XC141, 
+      .configuration=1, .interface=1, .alternative=0,
+      .inputEndpoint=4, .outputEndpoint=5,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_BI20X,
+      .resetDevice = 1
+    },
+
+    { /* Humanware Brailliant BI 20X (HID protocol, firmware 1.1) */
+      .vendor=0X1C71, .product=0XC141, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .verifyInterface = 1,
+      .data = &resourceData_HID_BI20X,
+      .resetDevice = 1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  BEGIN_HID_MODEL_TABLE
+    { .name = "APH Chameleon 20",
+      .data = &resourceData_HID_C20,
+    },
+
+    { .name = "APH Mantis Q40",
+      .data = &resourceData_HID_M40,
+    },
+
+    { .name = "NLS eReader Humanware",
+      .data = &resourceData_HID_NLS,
+    },
+
+    { .name = "Humanware BrailleOne",
+      .data = &resourceData_HID_one,
+    },
+
+    { .name = "Brailliant BI 40X",
+      .data = &resourceData_HID_BI40X,
+    },
+
+    { .name = "Brailliant BI 20X",
+      .data = &resourceData_HID_BI20X,
+    },
+  END_HID_MODEL_TABLE
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &resourceData_serial_generic;
+  descriptor.serial.options.readyDelay = OPEN_READY_DELAY;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.readyDelay = OPEN_READY_DELAY;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.discoverChannel = 1;
+  descriptor.bluetooth.options.applicationData = &resourceData_serial_generic;
+  descriptor.bluetooth.options.readyDelay = OPEN_READY_DELAY;
+
+  descriptor.hid.modelTable = hidModelTable;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    const ResourceData *resourceData = gioGetApplicationData(brl->gioEndpoint);
+    brl->data->protocol = resourceData->protocol;
+    brl->data->model = resourceData->model;
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+disconnectResource (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      if (brl->data->protocol->probeDisplay(brl)) {
+        setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+        makeOutputTable(dotsTable_ISO11548_1);
+        brl->data->text.rewrite = 1;
+        return 1;
+      }
+
+      disconnectResource(brl);
+    }
+
+    free(brl->data);
+    brl->data = NULL;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectResource(brl);
+  free(brl->data);
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  const size_t count = brl->textColumns;
+
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, count, NULL, NULL, &brl->data->text.rewrite)) {
+    unsigned char cells[count];
+
+    translateOutputCells(cells, brl->data->text.cells, count);
+    if (!brl->data->protocol->writeCells(brl, cells, count)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  while (brl->data->protocol->processInputPacket(brl));
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+  if (brl->data->isOffline) return BRL_CMD_OFFLINE;
+  return EOF;
+}
diff --git a/Drivers/Braille/HumanWare/brldefs-hw.h b/Drivers/Braille/HumanWare/brldefs-hw.h
new file mode 100644
index 0000000..9c9fa58
--- /dev/null
+++ b/Drivers/Braille/HumanWare/brldefs-hw.h
@@ -0,0 +1,184 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HW_BRLDEFS
+#define BRLTTY_INCLUDED_HW_BRLDEFS
+
+typedef enum {
+  HW_MSG_INIT                  = 0X00,
+  HW_MSG_INIT_RESP             = 0X01,
+  HW_MSG_DISPLAY               = 0X02,
+  HW_MSG_GET_KEYS              = 0X03,
+  HW_MSG_KEYS                  = 0X04,
+  HW_MSG_KEY_DOWN              = 0X05,
+  HW_MSG_KEY_UP                = 0X06,
+  HW_MSG_FIRMWARE_UPDATE       = 0X07,
+  HW_MSG_FIRMWARE_RESP         = 0X08,
+  HW_MSG_CONFIGURATION_UPDATE  = 0X09,
+  HW_MSG_CONFIGURATION_RESP    = 0X0A,
+  HW_MSG_GET_CONFIGURATION     = 0X0B,
+  HW_MSG_GET_FIRMWARE_VERSION  = 0X0C,
+  HW_MSG_FIRMWARE_VERSION_RESP = 0X0D,
+  HW_MSG_KEEP_AWAKE            = 0X0E,
+  HW_MSG_KEEP_AWAKE_RESP       = 0X0F,
+  HW_MSG_POWERING_OFF          = 0X10
+} HW_MessageType;
+
+typedef enum {
+  HW_MODEL_HW_BRAILLE_NOTE_TOUCH = 0X10,
+  HW_MODEL_APH_CHAMELEON_20 = 0X11,
+  HW_MODEL_APH_MANTIS_Q40 = 0X12,
+  HW_MODEL_NLS_EREADER = 0X13,
+  HW_MODEL_HW_BRAILLE_ONE = 0X14,
+} HW_ModelIdentifier;
+
+typedef union {
+  unsigned char bytes[3 + 0XFF];
+
+  struct {
+    unsigned char header;
+    unsigned char type;
+    unsigned char length;
+
+    union {
+      unsigned char bytes[0XFF];
+
+      struct {
+        unsigned char stillInitializing;
+        unsigned char modelIdentifier;
+        unsigned char cellCount;
+      } PACKED init;
+
+      struct {
+        unsigned char id;
+      } PACKED key;
+
+      struct {
+        unsigned char have;
+        unsigned char major;
+        unsigned char minor;
+        unsigned char build;
+      } PACKED firmwareVersion;
+    } data;
+  } PACKED fields;
+} HW_Packet;
+
+typedef enum {
+  HW_REP_FTR_Capabilities  = 1,
+  HW_REP_FTR_Settings      = 2,
+  HW_REP_FTR_Configuration = 3,
+  HW_REP_IN_PressedKeys    = 4,
+  HW_REP_OUT_WriteCells    = 5,
+  HW_REP_FTR_KeepAwake     = 6,
+  HW_REP_IN_PoweringOff    = 7
+} HW_ReportIdentifier;
+
+typedef struct {
+  unsigned char reportIdentifier;
+  char systemLanguage[2];
+
+  struct {
+    char major;
+    char minor;
+    char build[2];
+  } version;
+
+  char serialNumber[16];
+  unsigned char zero;
+  unsigned char cellCount;
+  unsigned char cellType;
+  unsigned char pad[13];
+} HW_CapabilitiesReport;
+
+typedef struct {
+  unsigned char reportIdentifier;
+  unsigned char dotPressure;
+} HW_SettingsReport;
+
+typedef struct {
+  unsigned char reportIdentifier;
+  unsigned char fill1;
+  unsigned char fill2;
+  unsigned char cellCount;
+
+  struct {
+    unsigned char firstIndex;
+    unsigned char lastIndex;
+  } primaryRoutingKeys;
+
+  struct {
+    unsigned char firstIndex;
+    unsigned char lastIndex;
+  } secondaryRoutingKeys;
+} HW_ConfigurationReport;
+
+typedef struct {
+  unsigned char reportIdentifier;
+  unsigned char fill;
+} HW_KeepAwakeReport;
+
+typedef struct {
+  unsigned char reportIdentifier;
+  unsigned char fill;
+} HW_PoweringOffReport;
+
+typedef enum {
+  HW_KEY_Reset = 1,
+
+  HW_KEY_Dot1 = 2,
+  HW_KEY_Dot2 = 3,
+  HW_KEY_Dot3 = 4,
+  HW_KEY_Dot4 = 5,
+  HW_KEY_Dot5 = 6,
+  HW_KEY_Dot6 = 7,
+  HW_KEY_Dot7 = 8,
+  HW_KEY_Dot8 = 9,
+  HW_KEY_Space = 10,
+
+  HW_KEY_Command1 = 11,
+  HW_KEY_Command2 = 12,
+  HW_KEY_Command3 = 13,
+  HW_KEY_Command4 = 14,
+  HW_KEY_Command5 = 15,
+  HW_KEY_Command6 = 16,
+
+  HW_KEY_ThumbPrevious = 17,
+  HW_KEY_ThumbLeft = 18,
+  HW_KEY_ThumbRight = 19,
+  HW_KEY_ThumbNext = 20,
+
+  HW_KEY_Up = 21,
+  HW_KEY_Down = 22,
+  HW_KEY_Left = 23,
+  HW_KEY_Right = 24,
+  HW_KEY_Action = 25,
+
+  HW_KEY_CAL_OK = 30,
+  HW_KEY_CAL_FAIL = 31,
+  HW_KEY_CAL_EMPTY = 32,
+  HW_KEY_CAL_RESET = 34,
+
+  HW_KEY_ROUTING = 80
+} HW_NavigationKey;
+
+typedef enum {
+  HW_GRP_NavigationKeys = 0,
+  HW_GRP_RoutingKeys
+} HW_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_HW_BRLDEFS */ 
diff --git a/Drivers/Braille/Inceptor/Makefile.in b/Drivers/Braille/Inceptor/Makefile.in
new file mode 100644
index 0000000..be82dcd
--- /dev/null
+++ b/Drivers/Braille/Inceptor/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ic
+DRIVER_NAME = Inceptor
+DRIVER_USAGE = BrailleMe (20)
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>, Vipul Kute <vipul.kute@gmail.com>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Inceptor/braille.c b/Drivers/Braille/Inceptor/braille.c
new file mode 100644
index 0000000..fa949da
--- /dev/null
+++ b/Drivers/Braille/Inceptor/braille.c
@@ -0,0 +1,626 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-ic.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+#define MAXIMUM_TEXT_CELLS 0XFF
+
+BEGIN_KEY_NAME_TABLE(common)
+  KEY_NAME_ENTRY(IC_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(IC_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(IC_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(IC_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(IC_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(IC_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(IC_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(IC_KEY_Dot8, "Dot8"),
+
+  KEY_NAME_ENTRY(IC_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(IC_KEY_MoveUp, "MoveUp"),
+  KEY_NAME_ENTRY(IC_KEY_MoveDown, "MoveDown"),
+  KEY_NAME_ENTRY(IC_KEY_PanLeft, "PanLeft"),
+  KEY_NAME_ENTRY(IC_KEY_PanRight, "PanRight"),
+
+  KEY_GROUP_ENTRY(IC_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(bb)
+  KEY_NAME_TABLE(common),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(nvda)
+  KEY_NAME_TABLE(common),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(bb)
+DEFINE_KEY_TABLE(nvda)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(bb),
+  &KEY_TABLE_DEFINITION(nvda),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  const KeyTableDefinition *keyTableDefinition;
+  void (*remapKeyNumbers) (KeyNumberSet *keys);
+  unsigned adjustRoutingKey:1;
+
+  struct {
+    const KeyNumberSetMapEntry *entries;
+    size_t count;
+  } keyNumberSetMap;
+} InputOutputData;
+
+struct BrailleDataStruct {
+  const InputOutputData *io;
+  KeyNumberSetMap *keyNumberSetMap;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAXIMUM_TEXT_CELLS];
+  } braille;
+
+  struct {
+    unsigned char rewrite;
+    wchar_t characters[MAXIMUM_TEXT_CELLS];
+  } text;
+
+  struct {
+    unsigned char rewrite;
+    int position;
+  } cursor;
+};
+
+#define KEY_BIT_Dot1 KEY_NUMBER_BIT(IC_KEY_Dot1)
+#define KEY_BIT_Dot2 KEY_NUMBER_BIT(IC_KEY_Dot2)
+#define KEY_BIT_Dot3 KEY_NUMBER_BIT(IC_KEY_Dot3)
+#define KEY_BIT_Dot4 KEY_NUMBER_BIT(IC_KEY_Dot4)
+#define KEY_BIT_Dot5 KEY_NUMBER_BIT(IC_KEY_Dot5)
+#define KEY_BIT_Dot6 KEY_NUMBER_BIT(IC_KEY_Dot6)
+#define KEY_BIT_Dot7 KEY_NUMBER_BIT(IC_KEY_Dot7)
+#define KEY_BIT_Dot8 KEY_NUMBER_BIT(IC_KEY_Dot8)
+
+#define KEY_BIT_Space KEY_NUMBER_BIT(IC_KEY_Space)
+#define KEY_BIT_MoveUp KEY_NUMBER_BIT(IC_KEY_MoveUp)
+#define KEY_BIT_MoveDown KEY_NUMBER_BIT(IC_KEY_MoveDown)
+#define KEY_BIT_PanLeft KEY_NUMBER_BIT(IC_KEY_PanLeft)
+#define KEY_BIT_PanRight KEY_NUMBER_BIT(IC_KEY_PanRight)
+#define KEY_BIT_Back KEY_NUMBER_BIT(IC_KEY_Back)
+#define KEY_BIT_Enter KEY_NUMBER_BIT(IC_KEY_Enter)
+
+static void
+remapKeyNumbers_BrailleBack (KeyNumberSet *keys) {
+  static const KeyNumberMapEntry map[] = {
+    {.to=IC_KEY_MoveUp  , .from=IC_KEY_MoveDown},
+    {.to=IC_KEY_MoveDown, .from=IC_KEY_PanLeft },
+    {.to=IC_KEY_PanLeft , .from=IC_KEY_Back    },
+    {.to=IC_KEY_PanRight, .from=IC_KEY_Enter   },
+    {.to=IC_KEY_Back    , .from=KTB_KEY_ANY    },
+    {.to=IC_KEY_Enter   , .from=KTB_KEY_ANY    },
+  };
+
+  remapKeyNumbers(keys, map, ARRAY_COUNT(map));
+}
+
+static const KeyNumberSetMapEntry keyNumberSetMap_BrailleBack[] = {
+  { .to = KEY_BIT_Dot7,
+    .from = KEY_BIT_Space | KEY_BIT_Dot7
+  },
+
+  { .to = KEY_BIT_Dot8,
+    .from = KEY_BIT_Space | KEY_BIT_Dot8
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot4 | KEY_BIT_Dot5
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot4 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot3 | KEY_BIT_Dot4 | KEY_BIT_Dot5
+  },
+
+  { .to = KEY_BIT_Dot7 | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot4 | KEY_BIT_Dot7
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot4
+  },
+
+  { .to = KEY_BIT_Dot7 | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot3,
+    .from = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot4 | KEY_BIT_Dot7
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot3,
+    .from = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot4
+  },
+
+  { .to = KEY_BIT_Dot7 | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4 | KEY_BIT_Dot7
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4
+  },
+};
+
+static const InputOutputData ioData_BrailleBack = {
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bb),
+  .remapKeyNumbers = remapKeyNumbers_BrailleBack,
+
+  .keyNumberSetMap = {
+    .entries = keyNumberSetMap_BrailleBack,
+    .count = ARRAY_COUNT(keyNumberSetMap_BrailleBack)
+  }
+};
+
+static void
+remapKeyNumbers_NVDA (KeyNumberSet *keys) {
+}
+
+static const KeyNumberSetMapEntry keyNumberSetMap_NVDA[] = {
+  { .to = KEY_BIT_Space | KEY_BIT_MoveUp,
+    .from = KEY_BIT_Space | KEY_BIT_MoveUp | KEY_BIT_Dot2
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_MoveDown,
+    .from = KEY_BIT_Space | KEY_BIT_MoveDown | KEY_BIT_Dot2
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_PanLeft,
+    .from = KEY_BIT_Space | KEY_BIT_PanLeft | KEY_BIT_Dot2
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_PanRight,
+    .from = KEY_BIT_Space | KEY_BIT_PanRight | KEY_BIT_Dot2
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_MoveUp,
+    .from = KEY_BIT_Space | KEY_BIT_MoveUp | KEY_BIT_Dot4
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_MoveDown,
+    .from = KEY_BIT_Space | KEY_BIT_MoveDown | KEY_BIT_Dot4
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot3,
+    .from = KEY_BIT_Space | KEY_BIT_PanLeft
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_PanRight
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot3,
+    .from = KEY_BIT_Space | KEY_BIT_MoveUp
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot5 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_MoveDown
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot3,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot3 | KEY_BIT_Dot8
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot4 | KEY_BIT_Dot5 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot6 | KEY_BIT_Dot8
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot4 | KEY_BIT_Dot5 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot6
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot3 | KEY_BIT_Dot4 | KEY_BIT_Dot5 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot3
+  },
+
+  { .to = KEY_BIT_Dot7 | KEY_BIT_Dot1,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_MoveUp
+  },
+
+  { .to = KEY_BIT_Dot8 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_MoveDown
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4,
+    .from = KEY_BIT_Dot8 | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4,
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot2 | KEY_BIT_Dot3 | KEY_BIT_Dot4 | KEY_BIT_Dot5,
+    .from = KEY_BIT_Space | KEY_BIT_Dot5
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot5 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot4 | KEY_BIT_Dot5
+  },
+
+  { .to = KEY_BIT_Space | KEY_BIT_Dot4 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot2 | KEY_BIT_Dot5 | KEY_BIT_Dot6
+  },
+
+  { .to = KEY_BIT_Dot7 | KEY_BIT_Dot6,
+    .from = KEY_BIT_Space | KEY_BIT_Dot1 | KEY_BIT_Dot4
+  },
+
+  { .from = KEY_BIT_Dot7 | KEY_BIT_Dot8 },
+};
+
+static const InputOutputData ioData_NVDA = {
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(nvda),
+  .remapKeyNumbers = remapKeyNumbers_NVDA,
+  .adjustRoutingKey = 1,
+
+  .keyNumberSetMap = {
+    .entries = keyNumberSetMap_NVDA,
+    .count = ARRAY_COUNT(keyNumberSetMap_NVDA)
+  }
+};
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+writePacket (
+  BrailleDisplay *brl,
+  unsigned char type, unsigned char mode,
+  const unsigned char *data1, size_t length1,
+  const unsigned char *data2, size_t length2
+) {
+  unsigned char packet[2 + 1 + 1 + 2 + length1 + 1 + 1 + 2 + length2 + 1 + 4 + 1 + 2];
+  unsigned char *byte = packet;
+
+  /* DS */
+  *byte++ = type;
+  *byte++ = type;
+
+  /* M */
+  *byte++ = mode;
+
+  /* DS1 */
+  *byte++ = 0XF0;
+
+  /* Cnt1 */
+  *byte++ = (length1 >> 0) & 0XFF;
+  *byte++ = (length1 >> 8) & 0XFF;
+
+  /* D1 */
+  if (data1) byte = mempcpy(byte, data1, length1);
+
+  /* DE1 */
+  *byte++ = 0XF1;
+
+  /* DS2 */
+  *byte++ = 0XF2;
+
+  /* Cnt2 */
+  *byte++ = (length2 >> 0) & 0XFF;
+  *byte++ = (length2 >> 8) & 0XFF;
+
+  /* D2 */
+  if (data2) byte = mempcpy(byte, data2, length2);
+
+  /* DE2 */
+  *byte++ = 0XF3;
+
+  /* Reserved */
+  {
+    int count = 4;
+    while (count--) *byte++ = 0;
+  }
+
+  /* Chk */
+  unsigned char *checksum = byte++;
+  *checksum = 0;
+
+  /* DE */
+  *byte++ = 0XFD;
+  *byte++ = 0XFD;
+
+  {
+    unsigned char sum = 0;
+    const unsigned char *ptr = packet;
+
+    while (ptr != byte) sum += *ptr++;
+    *checksum = sum;
+  }
+
+  return writeBytes(brl, packet, (byte - packet));
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1: {
+      switch (byte) {
+        case 0XFA:
+          *length = 10;
+          break;
+
+        // an ASCII LF is being sent after each Bluetooth packet
+        case 0X0A:
+          return BRL_PVR_IGNORE;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  if (size == *length) {
+    switch (bytes[0]) {
+      case 0XFA: {
+        if (byte != 0XFB) return BRL_PVR_INVALID;
+
+        const InputPacket *packet = (const void *)bytes;
+        int checksum = -packet->fields.checksum;
+        for (size_t i=0; i<size; i+=1) checksum += packet->bytes[i];
+
+        if ((checksum & 0XFF) != packet->fields.checksum) {
+          logInputProblem("incorrect input checksum", packet->bytes, size);
+          return BRL_PVR_INVALID;
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X1209, .product=0XABC0,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &ioData_NVDA;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.discoverChannel = 1;
+  descriptor.bluetooth.options.applicationData = &ioData_BrailleBack;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->io = gioGetApplicationData(brl->gioEndpoint);
+
+    brl->data->keyNumberSetMap = newKeyNumberSetMap(
+      brl->data->io->keyNumberSetMap.entries,
+      brl->data->io->keyNumberSetMap.count
+    );
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char data1[20] = {0};
+  return writePacket(brl, 0XFB, 0X01, data1, sizeof(data1), NULL, 0);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *bytes, size_t size) {
+  const InputPacket *packet = bytes;
+  if (packet->fields.type != 0X02) return BRL_RSP_UNEXPECTED;
+
+  brl->textColumns = packet->fields.data;
+  return BRL_RSP_DONE;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->io = NULL;
+    brl->data->keyNumberSetMap = NULL;
+
+    if (connectResource(brl, device)) {
+      InputPacket response;
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        setBrailleKeyTable(brl, brl->data->io->keyTableDefinition);
+        makeOutputTable(dotsTable_ISO11548_1);
+        brl->cellSize = 6;
+
+        brl->data->braille.rewrite = 1;
+        brl->data->text.rewrite = 1;
+        brl->data->cursor.rewrite = 1;
+        return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    {
+      KeyNumberSetMap *map = brl->data->keyNumberSetMap;
+      if (map) destroyKeyNumberSetMap(map);
+    }
+
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  int cellCount = brl->textColumns;
+
+  int newBraille =
+      cellsHaveChanged(brl->data->braille.cells, brl->buffer, cellCount,
+                       NULL, NULL, &brl->data->braille.rewrite);
+
+  int newText =
+      textHasChanged(brl->data->text.characters, text, cellCount,
+                     NULL, NULL, &brl->data->text.rewrite);
+
+  if (newBraille || newText) {
+    unsigned char cells[cellCount];
+    translateOutputCells(cells, brl->data->braille.cells, cellCount);
+
+    unsigned char attributes[cellCount];
+    memset(attributes, 0, sizeof(attributes));
+
+    for (int i=0; i<cellCount; i+=1) {
+      unsigned char *byte = &attributes[i];
+
+      if (text) {
+        wchar_t character = text[i];
+
+        if (iswupper(character)) *byte |= 0X01;
+      }
+    }
+
+    if (!writePacket(brl, 0XFC, 0X01,
+                     cells, sizeof(cells),
+                     attributes, sizeof(attributes))) return 0;
+  }
+
+  return 1;
+}
+
+static void
+splitKeys (KeyNumberSet keys, KeyNumberSet *navigation, KeyNumberSet *routing) {
+  {
+    const KeyNumber routingShift = IC_KEY_RoutingKey1;
+    const KeyNumberSet navigationMask = KEY_NUMBER_BIT(routingShift) - 1;
+
+    *navigation = keys & navigationMask;
+    *routing = keys >> routingShift;
+  }
+
+  if (*routing) {
+    KeyNumber shift = IC_KEY_RoutingKey17 - IC_KEY_RoutingKey1;
+    KeyNumberSet mask = KEY_NUMBER_BIT(shift) - 1;
+
+    KeyNumberSet left = *routing & mask;
+    *routing &= ~mask;
+
+    *routing <<= 4;
+    *routing |= left;
+  }
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  InputPacket packet;
+  size_t size;
+
+  while ((size = readPacket(brl, &packet, sizeof(packet)))) {
+    switch (packet.fields.type) {
+      case 0X00: {
+        unsigned char key = packet.fields.data;
+        if (brl->data->io->adjustRoutingKey) key -= 1;
+        if (key < brl->textColumns) enqueueKey(brl, IC_GRP_RoutingKeys, key);
+        continue;
+      }
+
+      case 0X01: {
+        KeyNumberSet navigation;
+        KeyNumberSet routing;
+
+        {
+          KeyNumberSet keys = (packet.fields.reserved[0] << 0X00)
+                            | (packet.fields.reserved[1] << 0X08)
+                            | (packet.fields.reserved[2] << 0X10)
+                            | (packet.fields.reserved[3] << 0X18)
+                            ;
+
+          brl->data->io->remapKeyNumbers(&keys);
+          splitKeys(keys, &navigation, &routing);
+        }
+
+        if (navigation) {
+          remapKeyNumberSet(&navigation, brl->data->keyNumberSetMap);
+
+          if (navigation) {
+            enqueueKeyEvents(brl, navigation, IC_GRP_NavigationKeys, 0, 1);
+            enqueueKeys(brl, routing, IC_GRP_RoutingKeys, 0);
+            enqueueKeyEvents(brl, navigation, IC_GRP_NavigationKeys, 0, 0);
+          }
+        }
+
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(&packet, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Inceptor/brldefs-ic.h b/Drivers/Braille/Inceptor/brldefs-ic.h
new file mode 100644
index 0000000..eaf0276
--- /dev/null
+++ b/Drivers/Braille/Inceptor/brldefs-ic.h
@@ -0,0 +1,78 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IC_BRLDEFS
+#define BRLTTY_INCLUDED_IC_BRLDEFS
+
+typedef union {
+  unsigned char bytes[10];
+
+  struct {
+    unsigned char start;
+    unsigned char type;
+    unsigned char count;
+    unsigned char data;
+    unsigned char reserved[4];
+    unsigned char checksum;
+    unsigned char end;
+  } PACKED fields;
+} InputPacket;
+
+typedef enum {
+  IC_KEY_Dot1 = 0,
+  IC_KEY_Dot2 = 1,
+  IC_KEY_Dot3 = 2,
+  IC_KEY_Dot4 = 3,
+  IC_KEY_Dot5 = 4,
+  IC_KEY_Dot6 = 5,
+  IC_KEY_Dot7 = 6,
+  IC_KEY_Dot8 = 7,
+
+  IC_KEY_Space = 8,
+  IC_KEY_MoveUp = 9,
+  IC_KEY_MoveDown = 10,
+  IC_KEY_PanLeft = 11,
+  IC_KEY_PanRight = 12,
+  IC_KEY_Back = 13,
+  IC_KEY_Enter = 14,
+
+  IC_KEY_RoutingKey1 = 16,
+  IC_KEY_RoutingKey2 = 17,
+  IC_KEY_RoutingKey3 = 18,
+  IC_KEY_RoutingKey4 = 19,
+  IC_KEY_RoutingKey5 = 20,
+  IC_KEY_RoutingKey6 = 21,
+  IC_KEY_RoutingKey7 = 22,
+  IC_KEY_RoutingKey8 = 23,
+
+  IC_KEY_RoutingKey9 = 24,
+  IC_KEY_RoutingKey10 = 25,
+  IC_KEY_RoutingKey11 = 26,
+  IC_KEY_RoutingKey12 = 27,
+  IC_KEY_RoutingKey17 = 28,
+  IC_KEY_RoutingKey18 = 29,
+  IC_KEY_RoutingKey19 = 30,
+  IC_KEY_RoutingKey20 = 31,
+} IC_NavigationKey;
+
+typedef enum {
+  IC_GRP_NavigationKeys = 0,
+  IC_GRP_RoutingKeys
+} IC_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_IC_BRLDEFS */ 
diff --git a/Drivers/Braille/Iris/Makefile.in b/Drivers/Braille/Iris/Makefile.in
new file mode 100644
index 0000000..157f596
--- /dev/null
+++ b/Drivers/Braille/Iris/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ir
+DRIVER_NAME = Iris
+DRIVER_USAGE = KB
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Iris/braille.c b/Drivers/Braille/Iris/braille.c
new file mode 100644
index 0000000..dc185ec
--- /dev/null
+++ b/Drivers/Braille/Iris/braille.c
@@ -0,0 +1,2097 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "ascii.h"
+#include "cmd.h"
+#include "parse.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_alarm.h"
+#include "timing.h"
+#include "ports.h"
+#include "message.h"
+
+#define BRL_HAVE_PACKET_IO
+
+typedef enum {
+  PARM_EMBEDDED,
+  PARM_LATCH_DELAY,
+  PARM_PROTOCOL
+} DriverParameter;
+#define BRLPARMS "embedded", "latchdelay", "protocol"
+
+#include "brl_driver.h"
+#include "brldefs-ir.h"
+
+BEGIN_KEY_NAME_TABLE(common)
+  KEY_NAME_ENTRY(IR_KEY_L1, "L1"),
+  KEY_NAME_ENTRY(IR_KEY_L2, "L2"),
+  KEY_NAME_ENTRY(IR_KEY_L3, "L3"),
+  KEY_NAME_ENTRY(IR_KEY_L4, "L4"),
+  KEY_NAME_ENTRY(IR_KEY_L5, "L5"),
+  KEY_NAME_ENTRY(IR_KEY_L6, "L6"),
+  KEY_NAME_ENTRY(IR_KEY_L7, "L7"),
+  KEY_NAME_ENTRY(IR_KEY_L8, "L8"),
+
+  KEY_NAME_ENTRY(IR_KEY_Menu, "Menu"),
+  KEY_NAME_ENTRY(IR_KEY_Z, "Z"),
+
+  KEY_GROUP_ENTRY(IR_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(brl)
+  KEY_NAME_ENTRY(IR_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(IR_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(IR_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(IR_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(IR_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(IR_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(IR_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(IR_KEY_Dot8, "Dot8"),
+  KEY_NAME_ENTRY(IR_KEY_Backspace, "Backspace"),
+  KEY_NAME_ENTRY(IR_KEY_Space, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(pc)
+  KEY_GROUP_ENTRY(IR_GRP_Xt, "Xt"),
+  KEY_GROUP_ENTRY(IR_GRP_XtE0, "XtE0"),
+  KEY_GROUP_ENTRY(IR_GRP_XtE1, "XtE1"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(brl)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(brl),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pc)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(pc),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(brl)
+DEFINE_KEY_TABLE(pc)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(brl),
+  &KEY_TABLE_DEFINITION(pc),
+END_KEY_TABLE_LIST
+
+#define IR_MAXIMUM_PACKET_SIZE 0X100
+
+#define IR_INTERNAL_SPEED 9600
+#define IR_EXTERNAL_SPEED_EUROBRAILLE 9600
+#define IR_EXTERNAL_SPEED_NATIVE 57600
+
+/* Input/output ports */
+#define IR_PORT_BASE 0X340
+#define IR_PORT_INPUT   (IR_PORT_BASE + 0)
+#define IR_PORT_OUTPUT  (IR_PORT_BASE + 1)
+#define IR_PORT_OUTPUT2 (IR_PORT_BASE + 2)
+
+typedef struct {
+  GioEndpoint *gioEndpoint;
+  SerialParameters serialParameters;
+
+  const char *name;
+  int speed;
+
+  int (*writeNativePacket) (
+    BrailleDisplay *brl, GioEndpoint *endpoint,
+    const unsigned char *packet, size_t size
+  );
+
+  void (*handleNativeAcknowledgement) (BrailleDisplay *brl);
+
+  unsigned int state;
+  unsigned int length; /* useful when reading Eurobraille packets */
+  unsigned int escape;
+
+  unsigned char *position;
+  unsigned char packet[IR_MAXIMUM_PACKET_SIZE];
+} Port;
+
+typedef enum {
+  IR_PROTOCOL_EUROBRAILLE,
+  IR_PROTOCOL_NATIVE
+} ProtocolIndex;
+
+typedef struct {
+  const char *protocolName;
+  int externalSpeed;
+
+  size_t (*readExternalPacket) (BrailleDisplay *brl, Port *port, void *packet, size_t size);
+  unsigned forwardAcknowledgements:1;
+
+  int (*forwardInternalPacket) (
+    BrailleDisplay *brl,
+    const unsigned char *packet, size_t size
+  );
+
+  void (*forwardExternalPacket) (
+    BrailleDisplay *brl,
+    const unsigned char *packet, size_t size,
+    int forward
+  );
+
+  int (*beginForwarding) (BrailleDisplay *brl);
+  int (*endForwarding) (BrailleDisplay *brl);
+
+  ProtocolIndex next;
+} ProtocolEntry;
+
+static void setExternalProtocol (BrailleDisplay *brl, ProtocolIndex index);
+#define IR_PROTOCOL_DEFAULT IR_PROTOCOL_EUROBRAILLE
+
+typedef struct {
+  unsigned char base;
+  unsigned char composite;
+} CompositeCharacterEntry;
+
+static const CompositeCharacterEntry compositeCharacterTable_circumflex[] = {
+  {.base=0X61, .composite=0XE2}, // aâ
+  {.base=0X65, .composite=0XEA}, // eê
+  {.base=0X69, .composite=0XEE}, // iî
+  {.base=0X6F, .composite=0XF4}, // oô
+  {.base=0X75, .composite=0XFB}, // uû
+
+  {.base=0X41, .composite=0XC2}, // AÂ
+  {.base=0X45, .composite=0XCA}, // EÊ
+  {.base=0X49, .composite=0XCE}, // IÎ
+  {.base=0X4F, .composite=0XD4}, // OÔ
+  {.base=0X55, .composite=0XDB}, // UÛ
+
+  {.base=0X00, .composite=0XA8}
+};
+
+static const CompositeCharacterEntry compositeCharacterTable_trema[] = {
+  {.base=0X61, .composite=0XE4}, // aä
+  {.base=0X65, .composite=0XEB}, // eë
+  {.base=0X69, .composite=0XEF}, // iï
+  {.base=0X6F, .composite=0XF6}, // oö
+  {.base=0X75, .composite=0XFC}, // uü
+
+  {.base=0X41, .composite=0XC4}, // AÄ
+  {.base=0X45, .composite=0XCB}, // EË
+  {.base=0X49, .composite=0XCF}, // IÏ
+  {.base=0X4F, .composite=0XD6}, // OÖ
+  {.base=0X55, .composite=0XDC}, // UÜ
+
+  {.base=0X00, .composite=0X5E}
+};
+
+static const CompositeCharacterEntry *compositeCharacterTables[] = {
+  compositeCharacterTable_circumflex,
+  compositeCharacterTable_trema
+};
+
+typedef enum {
+  xtsLeftShiftPressed,
+  xtsRightShiftPressed,
+  xtsShiftLocked,
+
+  xtsLeftControlPressed,
+  xtsRightControlPressed,
+
+  xtsLeftAltPressed,
+  xtsRightAltPressed,
+
+  xtsLeftWindowsPressed,
+  xtsRightWindowsPressed,
+
+  xtsInsertPressed,
+  xtsFnPressed
+} XtState;
+
+#define XTS_BIT(number) (1 << (number))
+#define XTS_TEST(bits) (brl->data->xt.state & (bits))
+#define XTS_SHIFT XTS_TEST(XTS_BIT(xtsLeftShiftPressed) | XTS_BIT(xtsRightShiftPressed) | XTS_BIT(xtsShiftLocked))
+#define XTS_CONTROL XTS_TEST(XTS_BIT(xtsLeftControlPressed) | XTS_BIT(xtsRightControlPressed))
+#define XTS_ALT XTS_TEST(XTS_BIT(xtsLeftAltPressed))
+#define XTS_ALTGR XTS_TEST(XTS_BIT(xtsRightAltPressed))
+#define XTS_WIN XTS_TEST(XTS_BIT(xtsLeftWindowsPressed))
+#define XTS_INSERT XTS_TEST(XTS_BIT(xtsInsertPressed))
+#define XTS_FN XTS_TEST(XTS_BIT(xtsFnPressed))
+
+typedef enum {
+  XtKeyType_ignore = 0, /* required for uninitialized entries */
+  XtKeyType_modifier,
+  XtKeyType_lock,
+  XtKeyType_character,
+  XtKeyType_function,
+  XtKeyType_complex,
+  XtKeyType_composite
+} XtKeyType;
+
+typedef struct {
+  unsigned char type;
+  unsigned char arg1;
+  unsigned char arg2;
+  unsigned char arg3;
+} XtKeyEntry;
+
+typedef enum {
+  XT_KEYS_00,
+  XT_KEYS_E0,
+  XT_KEYS_E1
+} XT_KEY_SET;
+
+#define XT_RELEASE 0X80
+#define XT_KEY(set,key) ((XT_KEYS_##set << 7) | (key))
+
+static const XtKeyEntry xtKeyTable[] = {
+  /* row 1 */
+  [XT_KEY(00,0X01)] = { // key 1: escape
+    .type = XtKeyType_function,
+    .arg1=0X1B
+  }
+  ,
+  [XT_KEY(00,0X3B)] = { // key 2: F1
+    .type = XtKeyType_function,
+    .arg1=0X70
+  }
+  ,
+  [XT_KEY(00,0X3C)] = { // key 3: F2
+    .type = XtKeyType_function,
+    .arg1=0X71
+  }
+  ,
+  [XT_KEY(00,0X3D)] = { // key 4: F3
+    .type = XtKeyType_function,
+    .arg1=0X72
+  }
+  ,
+  [XT_KEY(00,0X3E)] = { // key 5: F4
+    .type = XtKeyType_function,
+    .arg1=0X73
+  }
+  ,
+  [XT_KEY(00,0X3F)] = { // key 6: F5
+    .type = XtKeyType_function,
+    .arg1=0X74
+  }
+  ,
+  [XT_KEY(00,0X40)] = { // key 7: F6
+    .type = XtKeyType_function,
+    .arg1=0X75
+  }
+  ,
+  [XT_KEY(00,0X41)] = { // key 8: F7
+    .type = XtKeyType_function,
+    .arg1=0X76
+  }
+  ,
+  [XT_KEY(00,0X42)] = { // key 9: F8
+    .type = XtKeyType_function,
+    .arg1=0X77
+  }
+  ,
+  [XT_KEY(00,0X43)] = { // key 10: F9
+    .type = XtKeyType_function,
+    .arg1=0X78
+  }
+  ,
+  [XT_KEY(00,0X44)] = { // key 11: F10
+    .type = XtKeyType_function,
+    .arg1=0X79
+  }
+  ,
+  [XT_KEY(00,0X57)] = { // key 12: F11
+    .type = XtKeyType_function,
+    .arg1=0X7A
+  }
+  ,
+  [XT_KEY(00,0X58)] = { // key 13: F12
+    .type = XtKeyType_function,
+    .arg1=0X7B
+  }
+  ,
+  [XT_KEY(00,0X46)] = { // key 14: scroll lock
+    .type = XtKeyType_ignore
+  }
+  ,
+  [XT_KEY(E1,0X1D)] = { // key 15: pause break
+    .type = XtKeyType_ignore
+  }
+  ,
+  [XT_KEY(E0,0X52)] = { // key 16: insert
+    .type = XtKeyType_complex,
+    .arg1=0X0F, .arg2=1, .arg3=xtsInsertPressed
+  }
+  ,
+  [XT_KEY(E0,0X53)] = { // key 17: delete
+    .type = XtKeyType_function,
+    .arg1=0X10, .arg2=1
+  }
+  ,
+
+  /* row 2 */
+  [XT_KEY(00,0X02)] = { // key 1: &1
+    .type = XtKeyType_character,
+    .arg1=0X26, .arg2=0X31
+  }
+  ,
+  [XT_KEY(00,0X03)] = { // key 2: é2~
+    .type = XtKeyType_character,
+    .arg1=0XE9, .arg2=0X32, .arg3=0X7E
+  }
+  ,
+  [XT_KEY(00,0X04)] = { // key 3: "3#
+    .type = XtKeyType_character,
+    .arg1=0X22, .arg2=0X33, .arg3=0X23
+  }
+  ,
+  [XT_KEY(00,0X05)] = { // key 4: '4{
+    .type = XtKeyType_character,
+    .arg1=0X27, .arg2=0X34, .arg3=0X7B
+  }
+  ,
+  [XT_KEY(00,0X06)] = { // key 5: (5[
+    .type = XtKeyType_character,
+    .arg1=0X28, .arg2=0X35, .arg3=0X5B
+  }
+  ,
+  [XT_KEY(00,0X07)] = { // key 6: -6|
+    .type = XtKeyType_character,
+    .arg1=0X2D, .arg2=0X36, .arg3=0X7C
+  }
+  ,
+  [XT_KEY(00,0X08)] = { // key 7: è7`
+    .type = XtKeyType_character,
+    .arg1=0XE8, .arg2=0X37, .arg3=0X60
+  }
+  ,
+  [XT_KEY(00,0X09)] = { // key 8: _8
+    .type = XtKeyType_character,
+    .arg1=0X5F, .arg2=0X38, .arg3=0X5C
+  }
+  ,
+  [XT_KEY(00,0X0A)] = { // key 9: ç9^
+    .type = XtKeyType_character,
+    .arg1=0XE7, .arg2=0X39, .arg3=0X5E
+  }
+  ,
+  [XT_KEY(00,0X0B)] = { // key 10: à0@
+    .type = XtKeyType_character,
+    .arg1=0XE0, .arg2=0X30, .arg3=0X40
+  }
+  ,
+  [XT_KEY(00,0X0C)] = { // key 11: )°]
+    .type = XtKeyType_character,
+    .arg1=0X29, .arg2=0XB0, .arg3=0X5D
+  }
+  ,
+  [XT_KEY(00,0X0D)] = { // key 12: =+}
+    .type = XtKeyType_character,
+    .arg1=0X3D, .arg2=0X2B, .arg3=0X7D
+  }
+  ,
+  [XT_KEY(00,0X29)] = { // key 13: ²
+    .type = XtKeyType_character,
+    .arg1=0XB2
+  }
+  ,
+  [XT_KEY(00,0X0E)] = { // key 14: backspace
+    .type = XtKeyType_function,
+    .arg1=0X08
+  }
+  ,
+
+  /* row 3 */
+  [XT_KEY(00,0X0F)] = { // key 1: tab
+    .type = XtKeyType_function,
+    .arg1=0X09
+  }
+  ,
+  [XT_KEY(00,0X10)] = { // key 2: aA
+    .type = XtKeyType_character,
+    .arg1=0X61, .arg2=0X41
+  }
+  ,
+  [XT_KEY(00,0X11)] = { // key 3: zZ
+    .type = XtKeyType_character,
+    .arg1=0X7A, .arg2=0X5A
+  }
+  ,
+  [XT_KEY(00,0X12)] = { // key 4: eE€
+    .type = XtKeyType_character,
+    .arg1=0X65, .arg2=0X45, .arg3=0X80
+  }
+  ,
+  [XT_KEY(00,0X13)] = { // key 5: rR®
+    .type = XtKeyType_character,
+    .arg1=0X72, .arg2=0X52, .arg3=0XAE
+  }
+  ,
+  [XT_KEY(00,0X14)] = { // key 6: tT™
+    .type = XtKeyType_character,
+    .arg1=0X74, .arg2=0X54, .arg3=0X99
+  }
+  ,
+  [XT_KEY(00,0X15)] = { // key 7: yY
+    .type = XtKeyType_character,
+    .arg1=0X79, .arg2=0X59
+  }
+  ,
+  [XT_KEY(00,0X16)] = { // key 8: uU
+    .type = XtKeyType_character,
+    .arg1=0X75, .arg2=0X55
+  }
+  ,
+  [XT_KEY(00,0X17)] = { // key 9: iI
+    .type = XtKeyType_character,
+    .arg1=0X69, .arg2=0X49
+  }
+  ,
+  [XT_KEY(00,0X18)] = { // key 10: oO
+    .type = XtKeyType_character,
+    .arg1=0X6F, .arg2=0X4F
+  }
+  ,
+  [XT_KEY(00,0X19)] = { // key 11: pP
+    .type = XtKeyType_character,
+    .arg1=0X70, .arg2=0X50
+  }
+  ,
+  [XT_KEY(00,0X1A)] = { // key 12: circumflex tréma
+    .type = XtKeyType_composite,
+    .arg1=1, .arg2=2
+  }
+  ,
+  [XT_KEY(00,0X1B)] = { // key 13: $£¤
+    .type = XtKeyType_character,
+    .arg1=0X24, .arg2=0XA3, .arg3=0XA4
+  }
+  ,
+  [XT_KEY(00,0X1C)] = { // key 14: return
+    .type = XtKeyType_function,
+    .arg1=0X0D
+  }
+  ,
+
+  /* row 4 */
+  [XT_KEY(00,0X3A)] = { // key 1: shift lock
+    .type = XtKeyType_lock,
+    .arg1=xtsShiftLocked
+  }
+  ,
+  [XT_KEY(00,0X1E)] = { // key 2: qQ
+    .type = XtKeyType_character,
+    .arg1=0X71, .arg2=0X51
+  }
+  ,
+  [XT_KEY(00,0X1F)] = { // key 3: sS
+    .type = XtKeyType_character,
+    .arg1=0X73, .arg2=0X53
+  }
+  ,
+  [XT_KEY(00,0X20)] = { // key 4: dD
+    .type = XtKeyType_character,
+    .arg1=0X64, .arg2=0X44
+  }
+  ,
+  [XT_KEY(00,0X21)] = { // key 5: fF
+    .type = XtKeyType_character,
+    .arg1=0X66, .arg2=0X46
+  }
+  ,
+  [XT_KEY(00,0X22)] = { // key 6: gG
+    .type = XtKeyType_character,
+    .arg1=0X67, .arg2=0X47
+  }
+  ,
+  [XT_KEY(00,0X23)] = { // key 7: hH
+    .type = XtKeyType_character,
+    .arg1=0X68, .arg2=0X48
+  }
+  ,
+  [XT_KEY(00,0X24)] = { // key 8: jJ
+    .type = XtKeyType_character,
+    .arg1=0X6A, .arg2=0X4A
+  }
+  ,
+  [XT_KEY(00,0X25)] = { // key 9: kK
+    .type = XtKeyType_character,
+    .arg1=0X6B, .arg2=0X4B
+  }
+  ,
+  [XT_KEY(00,0X26)] = { // key 10: lL
+    .type = XtKeyType_character,
+    .arg1=0X6C, .arg2=0X4C
+  }
+  ,
+  [XT_KEY(00,0X27)] = { // key 11: mM
+    .type = XtKeyType_character,
+    .arg1=0X6D, .arg2=0X4D
+  }
+  ,
+  [XT_KEY(00,0X28)] = { // key 12: ù%
+    .type = XtKeyType_character,
+    .arg1=0XF9, .arg2=0X25
+  }
+  ,
+  [XT_KEY(00,0X2B)] = { // key 13: *µ
+    .type = XtKeyType_character,
+    .arg1=0X2A, .arg2=0XB5
+  }
+  ,
+  [XT_KEY(00,0X1C)] = { // key 14: return
+    .type = XtKeyType_function,
+    .arg1=0X0D
+  }
+  ,
+
+  /* row 5 */
+  [XT_KEY(00,0X2A)] = { // key 1: left shift
+    .type = XtKeyType_modifier,
+    .arg1=xtsLeftShiftPressed, .arg2=xtsShiftLocked
+  }
+  ,
+  [XT_KEY(00,0X2C)] = { // key 2: wW
+    .type = XtKeyType_character,
+    .arg1=0X77, .arg2=0X57
+  }
+  ,
+  [XT_KEY(00,0X2D)] = { // key 3: xX
+    .type = XtKeyType_character,
+    .arg1=0X78, .arg2=0X58
+  }
+  ,
+  [XT_KEY(00,0X2E)] = { // key 4: cC©
+    .type = XtKeyType_character,
+    .arg1=0X63, .arg2=0X43, .arg3=0XA9
+  }
+  ,
+  [XT_KEY(00,0X2F)] = { // key 5: vV
+    .type = XtKeyType_character,
+    .arg1=0X76, .arg2=0X56
+  }
+  ,
+  [XT_KEY(00,0X30)] = { // key 6: bB
+    .type = XtKeyType_character,
+    .arg1=0X62, .arg2=0X42
+  }
+  ,
+  [XT_KEY(00,0X31)] = { // key 7: nN
+    .type = XtKeyType_character,
+    .arg1=0X6E, .arg2=0X4E
+  }
+  ,
+  [XT_KEY(00,0X32)] = { // key 8: ,?
+    .type = XtKeyType_character,
+    .arg1=0X2C, .arg2=0X3F
+  }
+  ,
+  [XT_KEY(00,0X33)] = { // key 9: ;.
+    .type = XtKeyType_character,
+    .arg1=0X3B, .arg2=0X2E
+  }
+  ,
+  [XT_KEY(00,0X34)] = { // key 10: :/
+    .type = XtKeyType_character,
+    .arg1=0X3A, .arg2=0X2F
+  }
+  ,
+  [XT_KEY(00,0X35)] = { // key 11: !§
+    .type = XtKeyType_character,
+    .arg1=0X21, .arg2=0XA7
+  }
+  ,
+  [XT_KEY(00,0X56)] = { // key 12: <>
+    .type = XtKeyType_character,
+    .arg1=0X3C, .arg2=0X3E
+  }
+  ,
+  [XT_KEY(00,0X36)] = { // key 13: right shift
+    .type = XtKeyType_modifier,
+    .arg1=xtsRightShiftPressed, .arg2=xtsShiftLocked
+  }
+  ,
+
+  /* row 6 */
+  [XT_KEY(00,0X1D)] = { // key 1: left control
+    .type = XtKeyType_modifier,
+    .arg1=xtsLeftControlPressed
+  }
+  ,
+  [XT_KEY(E1,0X01)] = { // key 2: fn
+    .type = XtKeyType_modifier,
+    .arg1=xtsFnPressed
+  }
+  ,
+  [XT_KEY(E0,0X5B)] = { // key 3: left windows
+    .type = XtKeyType_complex,
+    .arg1=0X5B, .arg3=xtsLeftWindowsPressed
+  }
+  ,
+  [XT_KEY(00,0X38)] = { // key 4: left alt
+    .type = XtKeyType_modifier,
+    .arg1=xtsLeftAltPressed
+  }
+  ,
+  [XT_KEY(00,0X39)] = { // key 5: space
+    .type = XtKeyType_function,
+    .arg1=0X20
+  }
+  ,
+  [XT_KEY(E0,0X38)] = { // key 6: right alt
+    .type = XtKeyType_modifier,
+    .arg1=xtsRightAltPressed
+  }
+  ,
+  [XT_KEY(E0,0X5D)] = { // key 7: right windows
+    .type = XtKeyType_function,
+    .arg1=0X5D
+  }
+  ,
+  [XT_KEY(E0,0X1D)] = { // key 8: right control
+    .type = XtKeyType_modifier,
+    .arg1=xtsRightControlPressed
+  }
+  ,
+
+  /* arrow keys */
+  [XT_KEY(E0,0X48)] = { // key 1: up arrow
+    .type = XtKeyType_function,
+    .arg1=0X0D, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X4B)] = { // key 2: left arrow
+    .type = XtKeyType_function,
+    .arg1=0X0B, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X50)] = { // key 3: down arrow
+    .type = XtKeyType_function,
+    .arg1=0X0E, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X4D)] = { // key 4: right arrow
+    .type = XtKeyType_function,
+    .arg1=0X0C, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X49)] = { // fn + key 1: page up
+    .type = XtKeyType_function,
+    .arg1=0X09, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X47)] = { // fn + key 2: home
+    .type = XtKeyType_function,
+    .arg1=0X07, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X51)] = { // fn + key 3: page down
+    .type = XtKeyType_function,
+    .arg1=0X0A, .arg2=1
+  }
+  ,
+  [XT_KEY(E0,0X4F)] = { // fn + key 4: end
+    .type = XtKeyType_function,
+    .arg1=0X08, .arg2=1
+  }
+};
+
+struct BrailleDataStruct {
+  unsigned isConnected:1;
+
+  unsigned isEmbedded:1;
+  unsigned isSuspended:1;
+  unsigned isForwarding:1;
+
+  unsigned haveVisualDisplay:1;
+
+  struct {
+    Port port;
+    int (*handlePacket) (BrailleDisplay *brl, const void *packet, size_t size);
+    int (*isOffline) (BrailleDisplay *brl);
+    KeyNumberSet linearKeys;
+  } internal;
+
+  struct {
+    Port port;
+    GioHandleInputObject *hio;
+    const ProtocolEntry *protocol;
+    unsigned char cells[0XFF];
+  } external;
+
+  struct {
+    AsyncHandle monitor;
+
+    int delay;
+    int interval;
+
+    TimeValue started;
+    long int elapsed;
+    unsigned pulled:1;
+  } latch;
+
+  struct {
+    unsigned char refresh;
+    unsigned char cells[0XFF];
+  } braille;
+
+  struct {
+    const CompositeCharacterEntry *composite;
+    const XtKeyEntry *key;
+    uint16_t state;
+  } xt;
+
+  unsigned char *firmwareVersion;
+  char serialNumber[5];
+};
+
+/* Function readNativePacket */
+/* Returns the size of the read packet. */
+/* 0 means no packet has been read and there is no error. */
+/* -1 means an error occurred */
+static size_t
+readNativePacket (BrailleDisplay *brl, Port *port, void *packet, size_t size) {
+  unsigned char byte;
+  int wait = 0;
+
+  while (gioReadByte(port->gioEndpoint, &byte, (port->state && wait))) {
+    size_t length = port->position - port->packet;
+
+    wait = 1;
+
+    if (port->state) {
+      switch (byte) {
+        case ASCII_DLE:
+          if (!port->escape) {
+            port->escape = 1;
+            continue;
+          }
+
+        case ASCII_EOT:
+          if (!port->escape) {
+            port->state = 0;
+
+            if (length <= size) {
+              memcpy(packet, port->packet, length);
+              logInputPacket(packet, length);
+              return length;
+            }
+
+            logInputProblem("packet buffer too small", port->packet, length);
+            break;
+          }
+
+        default:
+          if (length < sizeof(port->packet)) {
+            *port->position = byte;
+          } else {
+            if (length == sizeof(port->packet)) logTruncatedPacket(port->packet, length);
+            logDiscardedByte(byte);
+          }
+
+          port->position += 1;
+          port->escape = 0;
+          break;
+      }
+    } else if (byte == ASCII_SOH) {
+      port->state = 1;
+      port->escape = 0;
+      port->position = port->packet;
+    } else if (byte == ASCII_ACK) {
+      port->handleNativeAcknowledgement(brl);
+    } else {
+      logIgnoredByte(byte);
+    }
+  }
+
+  if (errno != EAGAIN) logSystemError("readNativePacket");
+  return 0;
+}
+
+static size_t
+readEurobraillePacket (BrailleDisplay *brl, Port *port, void *packet, size_t size) {
+  unsigned char byte;
+  int wait = 0;
+
+  while (gioReadByte(port->gioEndpoint, &byte, (port->state && wait))) {
+    wait = 1;
+
+    switch (port->state) {
+      case 0:
+        if (byte == ASCII_STX) {
+          port->state = 1;
+          port->position = port->packet;
+          port->length = 0;
+        } else {
+          logIgnoredByte(byte);
+        }
+        break;
+
+      case 1:
+        port->length |= byte << 8;
+        port->state = 2;
+        break;
+
+      case 2:
+        port->length |= byte;
+
+        if (port->length < 3) {
+          logMessage(LOG_WARNING, "invalid Eurobraille packet declared size: %d", port->length);
+          port->state = 0;
+        } else {
+          port->length -= 2;
+
+          if (port->length > sizeof(port->packet)) {
+            logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "readEuroBraillePacket: rejecting packet whose declared size is too large");
+            port->state = 0;
+          } else {
+            port->state = 3;
+          }
+        }
+        break;
+
+      case 3:
+        *port->position++ = byte;
+
+        if ((port->position - port->packet) == port->length) port->state = 4;
+        break;
+
+      case 4:
+        if (byte == ASCII_ETX) {
+          size_t length = port->position - port->packet;
+
+          port->state = 0;
+
+          if (length <= size) {
+            memcpy(packet, port->packet, length);
+            logInputPacket(packet, length);
+            return length;
+          }
+
+          logInputProblem("packet buffer too small", port->packet, length);
+        } else {
+          logMessage(LOG_WARNING, "Eurobraille packet with real size exceeding declared size");
+          logDiscardedByte(byte);
+          port->state = 5;
+        }
+        break;
+
+      case 5:
+        if (byte == ASCII_ETX) {
+          port->state = 0;
+        } else {
+          logDiscardedByte(byte);
+        }
+        break;
+
+      default:
+        logMessage(LOG_WARNING, "readEurobraillePacket: reached unknown state %d", port->state);
+        port->state = 0;
+        break;
+    }
+  }
+
+  return 0;
+}
+
+static inline int
+needsEscape (unsigned char byte) {
+  static const unsigned char escapedChars[0X20] = {
+    [ASCII_SOH] = 1, [ASCII_EOT] = 1, [ASCII_DLE] = 1,
+    [ASCII_ACK] = 1, [ASCII_NAK] = 1,
+  };
+
+  if (byte < sizeof(escapedChars)) return escapedChars[byte];
+  return 0;
+}
+
+static int
+writeNativePacket_internal (
+  BrailleDisplay *brl, GioEndpoint *endpoint,
+  const unsigned char *packet, size_t size
+) {
+  return writeBrailleMessage(brl, endpoint, 0, packet, size);
+}
+
+static int
+writeNativePacket_external (
+  BrailleDisplay *brl, GioEndpoint *endpoint,
+  const unsigned char *packet, size_t size
+) {
+  return writeBraillePacket(brl, endpoint, packet, size);
+}
+
+static void
+handleNativeAcknowledgement_internal (BrailleDisplay *brl) {
+  acknowledgeBrailleMessage(brl);
+
+  if (brl->data->isForwarding && brl->data->external.protocol->forwardAcknowledgements) {
+    static const unsigned char acknowledgement[] = {ASCII_ACK};
+
+    writeBraillePacket(brl, brl->data->external.port.gioEndpoint,
+                       acknowledgement, sizeof(acknowledgement));
+  }
+}
+
+static void
+handleNativeAcknowledgement_external (BrailleDisplay *brl) {
+}
+
+static size_t
+writeNativePacket (
+  BrailleDisplay *brl, Port *port,
+  const unsigned char *packet, size_t size
+) {
+  unsigned char	buffer[(size * 2) + 2];
+  size_t count;
+
+  {
+    const unsigned char *source = packet;
+    unsigned char *target = buffer;
+
+    *target++ = ASCII_SOH;
+
+    while (size--) {
+      if (needsEscape(*source)) *target++ = ASCII_DLE;
+      *target++ = *source++;
+    }
+
+    *target++ = ASCII_EOT;
+    count = target - buffer;
+  }
+
+  if (!port->writeNativePacket(brl, port->gioEndpoint, buffer, count)) return 0;
+  return count;
+}
+
+/*
+static ssize_t
+tryWriteNativePacket (BrailleDisplay *brl, Port *port, const void *packet, size_t size) {
+  ssize_t res;
+  while ( ! (res = writeNativePacket(brl, port, packet, size)) ) {
+    if (errno != EAGAIN) return 0;
+  }
+  return res;
+}
+*/
+
+static int
+writeEurobraillePacket (BrailleDisplay *brl, Port *port, const void *data, size_t size) {
+  size_t count;
+  size_t packetSize = size + 2;
+  unsigned char	packet[packetSize + 2];
+  unsigned char *p = packet;
+
+  *p++ = ASCII_STX;
+  *p++ = (packetSize >> 8) & 0X00FF;
+  *p++ = packetSize & 0X00FF;  
+  p = mempcpy(p, data, size);
+  *p++ = ASCII_ETX;
+
+  count = p - packet;
+  if (!writeBraillePacket(brl, port->gioEndpoint, packet, count)) return 0;
+  return count;
+}
+
+static int
+writeEurobrailleStringPacket (BrailleDisplay *brl, Port *port, const char *string) {
+  return writeEurobraillePacket(brl, port, string, strlen(string) + 1);
+}
+
+/* Low-level write of dots to the braile display */
+/* No check is performed to avoid several consecutive identical writes at this level */
+static size_t
+writeDots (BrailleDisplay *brl, Port *port, const unsigned char *dots) {
+  size_t size = brl->textColumns * brl->textRows;
+  unsigned char packet[IR_WINDOW_SIZE_MAXIMUM + 1];
+  unsigned char *p = packet;
+  int i;
+
+  *p++ = IR_OPT_WriteBraille;
+  for (i=0; i<IR_WINDOW_SIZE_MAXIMUM-size; i+=1) *p++ = 0; 
+  for (i=0; i<size; i+=1) *p++ = dots[size-i-1];
+  return writeNativePacket(brl, port, packet, sizeof(packet));
+}
+
+/* Low-level write of text to the braile display */
+/* No check is performed to avoid several consecutive identical writes at this level */
+static size_t
+writeWindow (BrailleDisplay *brl, const unsigned char *text) {
+  size_t size = brl->textColumns * brl->textRows;
+  unsigned char dots[size];
+
+  translateOutputCells(dots, text, size);
+  return writeDots(brl, &brl->data->internal.port, dots);
+}
+
+static size_t
+clearWindow (BrailleDisplay *brl) {
+  size_t size = brl->textColumns * brl->textRows;
+  unsigned char window[size];
+
+  memset(window, 0, sizeof(window));
+  return writeWindow(brl, window);
+}
+
+static void
+activateBraille(void) {
+  writePort1(IR_PORT_OUTPUT, 0X01);
+  asyncWait(9);
+  writePort1(IR_PORT_OUTPUT, 0X00);
+}
+
+static void
+deactivateBraille(void) {
+  writePort1(IR_PORT_OUTPUT, 0X02);
+  asyncWait(9);
+  writePort1(IR_PORT_OUTPUT, 0X00);
+}
+
+static ssize_t brl_readPacket (BrailleDisplay *brl, void *packet, size_t size)
+{
+  if (brl->data->isEmbedded && (brl->data->isSuspended || brl->data->isForwarding)) return 0;
+  return readNativePacket(brl, &brl->data->internal.port, packet, size);
+}
+
+/* Function brl_writePacket */
+/* Returns 1 if the packet is actually written, 0 if the packet is not written */
+static ssize_t brl_writePacket (BrailleDisplay *brl, const void *packet, size_t size)
+{
+  if (brl->data->isSuspended || brl->data->isForwarding) {
+    errno = EAGAIN;
+    return 0;
+  }
+  return writeNativePacket(brl, &brl->data->internal.port, packet, size);
+}
+
+static int brl_reset (BrailleDisplay *brl)
+{
+  return 0;
+}
+
+static int
+sendInteractiveKey (BrailleDisplay *brl, Port *port, unsigned char key) {
+  const unsigned char packet[] = {IR_IPT_InteractiveKey, key};
+
+  return writeNativePacket(brl, port, packet, sizeof(packet));
+}
+
+static int
+sendMenuKey (BrailleDisplay *brl, Port *port) {
+  return sendInteractiveKey(brl, port, 'Q');
+}
+
+typedef struct {
+  int (*handleZKey) (BrailleDisplay *brl, Port *port);
+  int (*handleRoutingKey) (BrailleDisplay *brl, Port *port, unsigned char key);
+  int (*handlePCKey) (BrailleDisplay *brl, Port *port, int repeat, unsigned char escape, unsigned char key);
+  int (*handleFunctionKeys) (BrailleDisplay *brl, Port *port, KeyNumberSet keys);
+  int (*handleBrailleKeys) (BrailleDisplay *brl, Port *port, KeyNumberSet keys);
+} KeyHandlers;
+
+static int
+null_handleZKey(BrailleDisplay *brl, Port *port) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "ignoring Z key");
+  return 1;
+}
+
+static int
+core_handleZKey(BrailleDisplay *brl, Port *port) {
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "Z key pressed");
+  setExternalProtocol(brl, brl->data->external.protocol->next);
+
+  {
+    Port *port = &brl->data->external.port;
+
+    port->speed = brl->data->external.protocol->externalSpeed;
+    port->serialParameters.baud = port->speed;
+    if (!gioReconfigureResource(port->gioEndpoint, &port->serialParameters)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+core_handleRoutingKey(BrailleDisplay *brl, Port *port, unsigned char key) {
+  return enqueueKey(brl, IR_GRP_RoutingKeys, key-1);
+}
+
+static int
+core_handlePCKey(BrailleDisplay *brl, Port *port, int repeat, unsigned char escape, unsigned char code) {
+  return enqueueXtScanCode(brl, code, escape, IR_GRP_Xt, IR_GRP_XtE0, IR_GRP_XtE1);
+}
+
+static int
+core_handleFunctionKeys(BrailleDisplay *brl, Port *port, KeyNumberSet keys) {
+  return enqueueUpdatedKeys(brl, keys, &brl->data->internal.linearKeys, IR_GRP_NavigationKeys, IR_KEY_L1);
+}
+
+static int
+core_handleBrailleKeys(BrailleDisplay *brl, Port *port, KeyNumberSet keys) {
+  return enqueueKeys(brl, keys, IR_GRP_NavigationKeys, IR_KEY_Dot1);
+}
+
+static const KeyHandlers keyHandlers_embedded = {
+  .handleZKey = core_handleZKey,
+  .handleRoutingKey = core_handleRoutingKey,
+  .handlePCKey = core_handlePCKey,
+  .handleFunctionKeys = core_handleFunctionKeys,
+  .handleBrailleKeys = core_handleBrailleKeys
+};
+
+static const KeyHandlers keyHandlers_nonembedded = {
+  .handleZKey = null_handleZKey,
+  .handleRoutingKey = core_handleRoutingKey,
+  .handlePCKey = core_handlePCKey,
+  .handleFunctionKeys = core_handleFunctionKeys,
+  .handleBrailleKeys = core_handleBrailleKeys
+};
+
+static int
+eurobrl_handleRoutingKey(BrailleDisplay *brl, Port *port, unsigned char key) {
+  unsigned char data[] = {
+    0X4B, 0X49, 1, key
+  };
+  return writeEurobraillePacket(brl, port, data, sizeof(data));
+}
+
+static int
+eurobrl_handlePCKey(BrailleDisplay *brl, Port *port, int repeat, unsigned char escape, unsigned char key) {
+  unsigned char data[] = {0X4B, 0X5A, 0, 0, 0, 0};
+  const XtKeyEntry *xke = &xtKeyTable[key & ~XT_RELEASE];
+
+  switch (escape) {
+    case 0XE0:
+      xke += XT_KEY(E0, 0);
+      break;
+
+    case 0XE1:
+      xke += XT_KEY(E1, 0);
+      break;
+
+    default:
+    case 0X00:
+      xke += XT_KEY(00, 0);
+      break;
+  }
+
+  if (xke >= (xtKeyTable + ARRAY_COUNT(xtKeyTable))) {
+    static const XtKeyEntry xtKeyEntry = {
+      .type = XtKeyType_ignore
+    };
+
+    xke = &xtKeyEntry;
+  }
+
+  if (key & XT_RELEASE) {
+    int current = xke == brl->data->xt.key;
+    brl->data->xt.key = NULL;
+
+    switch (xke->type) {
+      case XtKeyType_modifier:
+        brl->data->xt.state &= ~XTS_BIT(xke->arg1);
+        return 1;
+
+      case XtKeyType_complex:
+        brl->data->xt.state &= ~XTS_BIT(xke->arg3);
+        if (current) goto isFunction;
+        return 1;
+
+      default:
+        return 1;
+    }
+  } else {
+    brl->data->xt.key = xke;
+
+    switch (xke->type) {
+      case XtKeyType_modifier:
+        brl->data->xt.state |= XTS_BIT(xke->arg1);
+        brl->data->xt.state &= ~XTS_BIT(xke->arg2);
+        return 1;
+
+      case XtKeyType_complex:
+        brl->data->xt.state |= XTS_BIT(xke->arg3);
+        return 1;
+
+      case XtKeyType_lock:
+        brl->data->xt.state |= XTS_BIT(xke->arg1);
+        return 1;
+
+      case XtKeyType_character:
+        if (xke->arg3 && XTS_ALTGR) {
+          data[5] = xke->arg3;
+        } else if (xke->arg2 && XTS_SHIFT) {
+          data[5] = xke->arg2;
+        } else {
+          data[5] = xke->arg1;
+        }
+        break;
+
+      case XtKeyType_function:
+      isFunction:
+        data[3] = xke->arg1;
+        data[2] = xke->arg2;
+        break;
+
+      case XtKeyType_composite: {
+        unsigned char index;
+
+        if (xke->arg2 && XTS_SHIFT) {
+          index = xke->arg2;
+        } else {
+          index = xke->arg1;
+        }
+
+        if (index) brl->data->xt.composite = compositeCharacterTables[index - 1];
+        return 1;
+      }
+
+      default:
+        return 1;
+    }
+  }
+
+  if (XTS_TEST(XTS_BIT(xtsLeftShiftPressed) | XTS_BIT(xtsRightShiftPressed))) data[4] |= 0X01;
+  if (XTS_CONTROL) data[4] |= 0X02;
+  if (XTS_ALT) data[4] |= 0X04;
+  if (XTS_TEST(XTS_BIT(xtsShiftLocked))) data[4] |= 0X08;
+  if (XTS_WIN) data[4] |= 0X10;
+  if (XTS_ALTGR) data[4] |= 0X20;
+  if (XTS_INSERT) data[4] |= 0X80;
+
+  if (brl->data->xt.composite) {
+    unsigned char *byte = &data[5];
+
+    if (*byte) {
+      const CompositeCharacterEntry *cce = brl->data->xt.composite;
+
+      while (cce->base) {
+        if (cce->base == *byte) {
+          *byte = cce->composite;
+          break;
+        }
+
+        cce += 1;
+      }
+
+      if (!cce->base && cce->composite) {
+        unsigned char original = *byte;
+        *byte = cce->composite;
+        if (!writeEurobraillePacket(brl, port, data, sizeof(data))) return 0;
+        *byte = original;
+      }
+    }
+
+    brl->data->xt.composite = NULL;
+  }
+
+  return writeEurobraillePacket(brl, port, data, sizeof(data));
+}
+
+static int
+eurobrl_handleFunctionKeys(BrailleDisplay *brl, Port *port, KeyNumberSet keys) {
+  if (keys) {
+    unsigned char data[] = {
+      0X4B, 0X43, 0, (
+        (keys & 0XF) |
+        ((keys >> 1) & 0XF0)
+      )
+    };
+
+    if (!writeEurobraillePacket(brl, port, data, sizeof(data))) return 0;
+  }
+
+  return 1;
+}
+
+static int
+eurobrl_handleBrailleKeys(BrailleDisplay *brl, Port *port, KeyNumberSet keys) {
+  unsigned char data[] = {
+    0X4B, 0X42,
+    (keys >> 8) & 0XFF,
+    keys & 0XFF
+  };
+
+  return writeEurobraillePacket(brl, port, data, sizeof(data));
+}
+
+static const KeyHandlers keyHandlers_eurobraille = {
+  .handleZKey = null_handleZKey,
+  .handleRoutingKey = eurobrl_handleRoutingKey,
+  .handlePCKey = eurobrl_handlePCKey,
+  .handleFunctionKeys = eurobrl_handleFunctionKeys,
+  .handleBrailleKeys = eurobrl_handleBrailleKeys
+};
+
+static int
+writeExternalCells (BrailleDisplay *brl) {
+  return writeDots(brl, &brl->data->internal.port, brl->data->external.cells);
+}
+
+static void
+saveExternalCells (BrailleDisplay *brl, const unsigned char *cells) {
+  memcpy(brl->data->external.cells, cells, brl->textColumns);
+}
+
+static int
+handleNativePacket (BrailleDisplay *brl, Port *port, const KeyHandlers *keyHandlers, const unsigned char *packet, size_t size) {
+  if (size == 2) {
+    if (packet[0] == IR_IPT_InteractiveKey) {
+      if (packet[1] == 'W') {
+        return keyHandlers->handleZKey(brl, port);
+      }
+
+      if ((1 <= packet[1]) && (packet[1] <= (brl->textColumns * brl->textRows))) {
+        return keyHandlers->handleRoutingKey(brl, port, packet[1]);
+      }
+    }
+  } else if (size == 3) {
+    int repeat = (packet[0] == IR_IPT_XtKeyCodeRepeat);
+
+    if ((packet[0] == IR_IPT_XtKeyCode) || repeat) {
+      return keyHandlers->handlePCKey(brl, port, repeat, packet[1], packet[2]);
+    }
+
+    if (packet[0] == IR_IPT_LinearKeys) {
+      KeyNumberSet keys = (packet[1] << 8) | packet[2];
+
+      return keyHandlers->handleFunctionKeys(brl, port, keys);
+    }
+
+    if (packet[0] == IR_IPT_BrailleKeys) {
+      KeyNumberSet keys = (packet[1] << 8) | packet[2];
+
+      return keyHandlers->handleBrailleKeys(brl, port, keys);
+    }
+  }
+
+  logUnexpectedPacket(packet, size);
+  return 0;
+}
+
+static int
+forwardInternalPacket_native (
+  BrailleDisplay *brl,
+  const unsigned char *packet, size_t size
+) {
+  return writeNativePacket(brl, &brl->data->external.port, packet, size);
+}
+
+static int
+forwardInternalPacket_eurobraille (
+  BrailleDisplay *brl,
+  const unsigned char *packet, size_t size
+) {
+  handleNativePacket(brl, &brl->data->external.port, &keyHandlers_eurobraille, packet, size);
+  return 1;
+}
+
+static void
+forwardExternalPacket_native (
+  BrailleDisplay *brl,
+  const unsigned char *packet, size_t size,
+  int forward
+) {
+  if (forward) {
+    writeNativePacket(brl, &brl->data->internal.port, packet, size);
+  }
+}
+
+static void
+forwardExternalPacket_eurobraille (
+  BrailleDisplay *brl,
+  const unsigned char *packet, size_t size,
+  int forward
+) {
+  if (size==2 && packet[0]=='S' && packet[1]=='I') {
+    /* Send system information */
+    Port *port = &brl->data->external.port;
+    char str[256];
+
+    writeEurobrailleStringPacket(brl, port, "SNIRIS_KB_40");
+
+    writeEurobrailleStringPacket(brl, port, "SHIR4");
+
+    snprintf(str, sizeof(str), "SS%s", brl->data->serialNumber);
+    writeEurobrailleStringPacket(brl, port, str);
+
+    writeEurobrailleStringPacket(brl, port, "SLFR");
+
+    str[0] = 'S';
+    str[1] = 'G';
+    str[2] = brl->textColumns;
+    writeEurobraillePacket(brl, port, str, 3);
+
+    str[0] = 'S';
+    str[1] = 'T';
+    str[2] = 6;
+    writeEurobraillePacket(brl, port, str, 3);
+
+    snprintf(str, sizeof(str), "So%d%da", 0XEF, 0XF8);
+    writeEurobrailleStringPacket(brl, port, str);
+
+    writeEurobrailleStringPacket(brl, port, "SW1.92");
+
+    writeEurobrailleStringPacket(brl, port, "SP1.00 30-10-2006");
+
+    snprintf(str, sizeof(str), "SM%d", 0X08);
+    writeEurobrailleStringPacket(brl, port, str);
+
+    writeEurobrailleStringPacket(brl, port, "SI");
+  } else if (size==brl->textColumns+2 && packet[0]=='B' && packet[1]=='S') {
+    /* Write dots to braille display */
+    saveExternalCells(brl, packet+2);
+    if (forward) writeExternalCells(brl);
+  } else {
+    logBytes(LOG_WARNING, "forwardEurobraillePacket could not handle this packet: ", packet, size);
+  }
+}
+
+static int
+beginForwarding_native (BrailleDisplay *brl) {
+  return sendMenuKey(brl, &brl->data->external.port);
+}
+
+static int
+endForwarding_native (BrailleDisplay *brl) {
+  return sendMenuKey(brl, &brl->data->external.port);
+}
+
+static int
+beginForwarding_eurobraille (BrailleDisplay *brl) {
+  brl->data->xt.composite = NULL;
+  brl->data->xt.key = NULL;
+  brl->data->xt.state = 0;
+
+  writeExternalCells(brl);
+  return 1;
+}
+
+static int
+endForwarding_eurobraille (BrailleDisplay *brl) {
+  return 1;
+}
+
+static const ProtocolEntry protocolTable[] = {
+  [IR_PROTOCOL_EUROBRAILLE] = {
+    .protocolName = strtext("eurobraille"),
+    .externalSpeed = IR_EXTERNAL_SPEED_EUROBRAILLE,
+
+    .readExternalPacket = readEurobraillePacket,
+    .forwardAcknowledgements = 0,
+
+    .forwardInternalPacket = forwardInternalPacket_eurobraille,
+    .forwardExternalPacket = forwardExternalPacket_eurobraille,
+
+    .beginForwarding = beginForwarding_eurobraille,
+    .endForwarding = endForwarding_eurobraille,
+
+    .next = IR_PROTOCOL_NATIVE
+  },
+
+  [IR_PROTOCOL_NATIVE] = {
+    .protocolName = strtext("native"),
+    .externalSpeed = IR_EXTERNAL_SPEED_NATIVE,
+
+    .readExternalPacket = readNativePacket,
+    .forwardAcknowledgements = 1,
+
+    .forwardInternalPacket = forwardInternalPacket_native,
+    .forwardExternalPacket = forwardExternalPacket_native,
+
+    .beginForwarding = beginForwarding_native,
+    .endForwarding = endForwarding_native,
+
+    .next = IR_PROTOCOL_EUROBRAILLE
+  },
+};
+
+static const unsigned char protocolCount = ARRAY_COUNT(protocolTable);
+
+static void
+setExternalProtocol (BrailleDisplay *brl, ProtocolIndex index) {
+  brl->data->external.protocol = &protocolTable[index];
+}
+
+static int
+enterPacketForwardMode (BrailleDisplay *brl) {
+  logMessage(LOG_INFO,
+             "entering packet forward mode (port=%s, protocol=%s, speed=%d)",
+             brl->data->external.port.name,
+             brl->data->external.protocol->protocolName,
+             brl->data->external.port.speed);
+
+  {
+    char msg[brl->textColumns+1];
+
+    snprintf(msg, sizeof(msg), "%s (%s)",
+             gettext("PC mode"),
+             gettext(brl->data->external.protocol->protocolName));
+    message(NULL, msg, MSG_NODELAY);
+  }
+
+  if (!brl->data->external.protocol->beginForwarding(brl)) return 0;
+  brl->data->isForwarding = 1;
+  return 1;
+}
+
+static int
+leavePacketForwardMode (BrailleDisplay *brl) {
+  logMessage(LOG_INFO, "leaving packet forward mode");
+  if (!brl->data->external.protocol->endForwarding(brl)) return 0;
+  brl->data->isForwarding = 0;
+  brl->data->braille.refresh = 1;
+  return 1;
+}
+
+static int
+forwardExternalPackets (BrailleDisplay *brl) {
+  const ProtocolEntry *protocol = brl->data->external.protocol;
+  unsigned char packet[IR_MAXIMUM_PACKET_SIZE];
+  size_t size;
+
+  while ((size = protocol->readExternalPacket(brl, &brl->data->external.port, packet, sizeof(packet)))) {
+    protocol->forwardExternalPacket(brl, packet, size,
+                                    (brl->data->isForwarding && !brl->data->isSuspended));
+  }
+
+  return errno == EAGAIN;
+}
+
+GIO_INPUT_HANDLER(irHandleExternalInput) {
+  BrailleDisplay *brl = parameters->data;
+
+  if (!forwardExternalPackets(brl)) brl->hasFailed = 1;
+  return 0;
+}
+
+static inline int
+isMenuKeyPacket (const unsigned char *packet, size_t size) {
+  return (size == 2) && (packet[0] == IR_IPT_InteractiveKey) && (packet[1] == 'Q');
+}
+
+static int
+handleInternalPacket_embedded (BrailleDisplay *brl, const void *packet, size_t size) {
+  if (brl->data->isSuspended) return 1;
+
+  /* The test for Menu key should come first since this key toggles
+   * packet forward mode on/off
+   */
+  if (isMenuKeyPacket(packet, size)) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "menu key pressed");
+
+    if (brl->data->isForwarding) {
+      if (!leavePacketForwardMode(brl)) return 0;
+    } else {
+      if (!enterPacketForwardMode(brl)) return 0;
+    }
+  } else if (brl->data->isForwarding) {
+    if (!brl->data->external.protocol->forwardInternalPacket(brl, packet, size)) return 0;
+  } else {
+    handleNativePacket(brl, NULL, &keyHandlers_embedded, packet, size);
+  }
+
+  return 1;
+}
+
+static int
+isOffline_embedded (BrailleDisplay *brl) {
+  return brl->data->isForwarding || brl->data->isSuspended;
+}
+
+static int
+handleInternalPacket_nonembedded (BrailleDisplay *brl, const void *packet, size_t size) {
+  int menuKeyPressed = isMenuKeyPacket(packet, size);
+
+  if (menuKeyPressed) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "menu key pressed");
+
+    if (brl->data->isConnected) {
+      logMessage(LOG_INFO, "device disconnected");
+      brl->data->isConnected = 0;
+      return 1;
+    }
+  }
+
+  if (!brl->data->isConnected) {
+    logMessage(LOG_INFO, "device reconnected");
+    brl->data->isConnected = 1;
+    brl->data->braille.refresh = 1;
+    if (menuKeyPressed) return 1;
+  }
+  
+  handleNativePacket(brl, NULL, &keyHandlers_nonembedded, packet, size);
+  return 1;
+}
+
+static int
+isOffline_nonembedded (BrailleDisplay *brl) {
+  return !brl->data->isConnected;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[IR_MAXIMUM_PACKET_SIZE];
+  size_t size;
+
+  while ((size = readNativePacket(brl, &brl->data->internal.port, packet, sizeof(packet)))) {
+    if (!brl->data->internal.handlePacket(brl, packet, size)) goto failure;
+  }
+
+  if (errno != EAGAIN) goto failure;
+  if (brl->data->internal.isOffline(brl)) return BRL_CMD_OFFLINE;
+  return EOF;
+
+failure:
+  return BRL_CMD_RESTARTBRL;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *characters) {
+  const size_t size = brl->textColumns * brl->textRows;
+
+  if (brl->data->isForwarding) return 1;
+
+  if (cellsHaveChanged(brl->data->braille.cells, brl->buffer, size, NULL, NULL, &brl->data->braille.refresh)) {
+    size_t size = writeWindow(brl, brl->buffer);
+
+    if (!size) return 0;
+  }
+
+  return 1;
+}
+
+static ssize_t askDevice(BrailleDisplay *brl, IrisOutputPacketType request, unsigned char *response, size_t size)
+{
+  {
+    const unsigned char data[] = {request};
+    if (! writeNativePacket(brl, &brl->data->internal.port, data, sizeof(data)) ) return 0;
+    drainBrailleOutput(brl, 0);
+  }
+
+  while (gioAwaitInput(brl->data->internal.port.gioEndpoint, 1000)) {
+    size_t res = readNativePacket(brl, &brl->data->internal.port, response, size);
+    if (res) return res;
+    if (errno != EAGAIN) break;
+  }
+
+  return 0;
+}
+
+static int
+suspendDevice (BrailleDisplay *brl) {
+  if (!brl->data->isEmbedded) return 1;
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "suspending device");
+  brl->data->isSuspended = 1;
+
+  if (brl->data->isForwarding) {
+    if (!sendMenuKey(brl, &brl->data->external.port)) return 0;
+  }
+
+  if (!clearWindow(brl)) return 0;
+  drainBrailleOutput(brl, 50);
+  deactivateBraille();
+  setBrailleOffline(brl);
+  return 1;
+}
+
+static int
+resumeDevice (BrailleDisplay *brl) {
+  if (!brl->data->isEmbedded) return 1;
+  logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "resuming device");
+  activateBraille();
+
+  if (brl->data->isForwarding) {
+    if (!sendMenuKey(brl, &brl->data->external.port)) return 0;
+  } else {
+    brl->data->braille.refresh = 1;
+    setBrailleOnline(brl);
+  }
+
+  brl->data->isSuspended = 0;
+  return 1;
+}
+
+static void
+closePort (Port *port) {
+  if (port->gioEndpoint) {
+    gioDisconnectResource(port->gioEndpoint);
+    port->gioEndpoint = NULL;
+  }
+}
+
+static int
+openPort (Port *port) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .parity = SERIAL_PARITY_EVEN
+  };
+
+  GioDescriptor gioDescriptor;
+  gioInitializeDescriptor(&gioDescriptor);
+
+  port->serialParameters = serialParameters;
+  port->serialParameters.baud = port->speed;
+  gioDescriptor.serial.parameters = &port->serialParameters;
+
+  closePort(port);
+
+  if ((port->gioEndpoint = gioConnectResource(port->name, &gioDescriptor))) {
+    port->state = 0;
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+openInternalPort (BrailleDisplay *brl) {
+  Port *port = &brl->data->internal.port;
+
+  if (openPort(port)) {
+    brl->gioEndpoint = port->gioEndpoint;
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+closeInternalPort (BrailleDisplay *brl) {
+  brl->gioEndpoint = NULL;
+  closePort(&brl->data->internal.port);
+}
+
+static void
+stopExternalInputHandler (BrailleDisplay *brl) {
+  if (brl->data->external.hio) {
+    gioDestroyHandleInputObject(brl->data->external.hio);
+    brl->data->external.hio = NULL;
+  }
+}
+
+static int
+openExternalPort (BrailleDisplay *brl) {
+  stopExternalInputHandler(brl);
+
+  if (openPort(&brl->data->external.port)) {
+    brl->data->external.hio =
+      gioNewHandleInputObject(brl->data->external.port.gioEndpoint,
+                                BRAILLE_DRIVER_INPUT_POLL_INTERVAL,
+                                irHandleExternalInput, brl);
+
+    if (brl->data->external.hio) return 1;
+  }
+
+  return 0;
+}
+
+static void
+closeExternalPort (BrailleDisplay *brl) {
+  stopExternalInputHandler(brl);
+  closePort(&brl->data->external.port);
+}
+
+static int
+checkLatchState (BrailleDisplay *brl) {
+  unsigned char pulled = !(readPort1(IR_PORT_INPUT) & 0X04);
+
+  if (brl->data->latch.pulled) {
+    if (pulled) {
+      long int elapsed = getMonotonicElapsed(&brl->data->latch.started);
+      int result = (brl->data->latch.elapsed <= brl->data->latch.delay) &&
+                   (elapsed > brl->data->latch.delay);
+
+      brl->data->latch.elapsed = elapsed;
+      return result;
+    }
+
+    brl->data->latch.pulled = 0;
+    logMessage(LOG_INFO, "latch released");
+  } else if (pulled) {
+    getMonotonicTime(&brl->data->latch.started);
+    brl->data->latch.elapsed = 0;
+    brl->data->latch.pulled = 1;
+    logMessage(LOG_INFO, "latch pulled");    
+  }
+
+  return 0;
+}
+
+ASYNC_ALARM_CALLBACK(irMonitorLatch) {
+  BrailleDisplay *brl = parameters->data;
+
+  if (checkLatchState(brl)) {
+    if (!(brl->data->isSuspended? resumeDevice(brl): suspendDevice(brl))) brl->hasFailed = 1;
+  }
+}
+
+static int
+startLatchMonitor (BrailleDisplay *brl) {
+  if (brl->data->latch.monitor) return 1;
+  if (!brl->data->latch.delay) return 1;
+
+  if (asyncNewRelativeAlarm(&brl->data->latch.monitor, 0, irMonitorLatch, brl)) {
+    if (asyncResetAlarmInterval(brl->data->latch.monitor, brl->data->latch.interval)) {
+      brl->data->latch.pulled = 0;
+      return 1;
+    }
+
+    asyncCancelRequest(brl->data->latch.monitor);
+    brl->data->latch.monitor = NULL;
+  }
+
+  return 0;
+}
+
+static void
+stopLatchMonitor (BrailleDisplay *brl) {
+  if (brl->data->latch.monitor) {
+    asyncCancelRequest(brl->data->latch.monitor);
+    brl->data->latch.monitor = NULL;
+  }
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    unsigned int embedded;
+
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    brl->data->isConnected = 1;
+
+    brl->data->isSuspended = 0;
+    brl->data->isForwarding = 0;
+
+    brl->data->haveVisualDisplay = 0;
+
+    brl->data->internal.port.gioEndpoint = NULL;
+    brl->data->internal.port.writeNativePacket = writeNativePacket_internal;
+    brl->data->internal.port.handleNativeAcknowledgement = handleNativeAcknowledgement_internal;
+    brl->data->internal.linearKeys = 0;
+
+    brl->data->external.port.gioEndpoint = NULL;
+    brl->data->external.port.writeNativePacket = writeNativePacket_external;
+    brl->data->external.port.handleNativeAcknowledgement = handleNativeAcknowledgement_external;
+    brl->data->external.hio = NULL;
+    memset(brl->data->external.cells, 0, sizeof(brl->data->external.cells));
+
+    brl->data->latch.monitor = NULL;
+    brl->data->latch.delay = IR_DEFAULT_LATCH_DELAY;
+    brl->data->latch.interval = IR_DEFAULT_LATCH_INTERVAL;
+
+    brl->data->braille.refresh = 1;
+
+    if (validateYesNo(&embedded, parameters[PARM_EMBEDDED])) {
+      int internalPortOpened = 0;
+
+      brl->data->isEmbedded = !!embedded;
+      logMessage(LOG_INFO, "Driver Mode: %s",
+                 (brl->data->isEmbedded? "embedded": "non-embedded"));
+
+      if (brl->data->isEmbedded) {
+        {
+          const char *parameter = parameters[PARM_PROTOCOL];
+          const char *choices[protocolCount + 1];
+          unsigned int choice;
+
+          for (choice=0; choice<protocolCount; choice+=1) {
+            choices[choice] = protocolTable[choice].protocolName;
+          }
+          choices[protocolCount] = NULL;
+
+          if (!validateChoice(&choice, parameter, choices)) {
+            choice = IR_PROTOCOL_DEFAULT;
+            logMessage(LOG_WARNING, "invalid protocol setting: %s", parameter);
+          }
+
+          setExternalProtocol(brl, choice);
+          logMessage(LOG_INFO, "External Protocol: %s", brl->data->external.protocol->protocolName);
+        }
+
+        {
+          const char *parameter = parameters[PARM_LATCH_DELAY];
+
+          if (*parameter) {
+            static const int minimum = 0;
+            static const int maximum = 100;
+            int value;
+
+            if (validateInteger(&value, parameter, &minimum, &maximum)) {
+              brl->data->latch.delay = value * 100;
+            } else {
+              logMessage(LOG_WARNING, "invalid latch delay setting: %s", parameter);
+            }
+          }
+        }
+
+        if (startLatchMonitor(brl)) {
+          if (enablePorts(LOG_ERR, IR_PORT_BASE, 3) != -1) {
+            brl->data->external.port.name = device;
+            brl->data->external.port.speed = brl->data->external.protocol->externalSpeed;
+
+            if (openExternalPort(brl)) {
+              brl->data->internal.port.name = "serial:ttyS1";
+              brl->data->internal.port.speed = IR_INTERNAL_SPEED;
+
+              if (openInternalPort(brl)) {
+                brl->data->internal.handlePacket = handleInternalPacket_embedded;
+                brl->data->internal.isOffline = isOffline_embedded;
+
+                activateBraille();
+                internalPortOpened = 1;
+              }
+            }
+          } else {
+            logSystemError("ioperm");
+          }
+        }
+      } else {
+        brl->data->internal.port.name = device;
+        brl->data->internal.port.speed = IR_EXTERNAL_SPEED_NATIVE;
+
+        if (openInternalPort(brl)) {
+          brl->data->internal.handlePacket = handleInternalPacket_nonembedded;
+          brl->data->internal.isOffline = isOffline_nonembedded;
+
+          brl->data->isConnected = 1;
+          internalPortOpened = 1;
+        }
+      }
+
+      if (internalPortOpened) {
+        unsigned char deviceResponse[IR_MAXIMUM_PACKET_SIZE];
+        ssize_t size;
+
+        if (!(size = askDevice(brl, IR_OPT_VersionRequest, deviceResponse, sizeof(deviceResponse)) )) {
+          logMessage(LOG_WARNING, "received no response to version request");
+        } else if (size < 3) {
+          logBytes(LOG_WARNING, "short firmware version response", deviceResponse, size);
+        }  else if (deviceResponse[0] != IR_IPT_VersionResponse) {
+          logBytes(LOG_WARNING, "unexpected firmware version response", deviceResponse, size);
+        } else {
+          const KeyTableDefinition *ktd;
+
+          switch (deviceResponse[1]) {
+            case 'a':
+            case 'A':
+              ktd = &KEY_TABLE_DEFINITION(pc);
+              brl->textColumns = IR_WINDOW_SIZE_MAXIMUM;
+              break;
+
+            case 'l':
+            case 'L':
+              ktd = &KEY_TABLE_DEFINITION(brl);
+              brl->textColumns = IR_WINDOW_SIZE_MAXIMUM;
+              brl->data->haveVisualDisplay = 1;
+              break;
+
+            case 's':
+            case 'S':
+              ktd = &KEY_TABLE_DEFINITION(brl);
+              brl->textColumns = IR_WINDOW_SIZE_SMALL;
+              break;
+
+            default:
+              logBytes(LOG_WARNING, "unrecognized device type in firmware version response", deviceResponse, size);
+              ktd = NULL;
+              break;
+          }
+
+          if (ktd) {
+            setBrailleKeyTable(brl, ktd);
+
+            if ((brl->data->firmwareVersion = malloc(size - 1))) {
+              memcpy(brl->data->firmwareVersion, deviceResponse+2, size-2);
+              brl->data->firmwareVersion[size-2] = 0;
+              logMessage(LOG_INFO, "Firmware Version: %s", brl->data->firmwareVersion);
+
+              if (!(size = askDevice(brl, IR_OPT_SerialNumberRequest, deviceResponse, sizeof(deviceResponse)))) {
+                logMessage(LOG_WARNING, "Received no response to serial number request.");
+              } else if (size != IR_OPT_SERIALNUMBERRESPONSE_LENGTH) {
+                logBytes(LOG_WARNING, "short serial number response", deviceResponse, size);
+              } else if (deviceResponse[0] != IR_IPT_SerialNumberResponse) {
+                logBytes(LOG_WARNING, "unexpected serial number response", deviceResponse, size);
+              } else {
+                if (deviceResponse[1] != IR_OPT_SERIALNUMBERRESPONSE_NOWINDOWLENGTH) {
+                  brl->textColumns = deviceResponse[1];
+                }
+
+                {
+                  char *byte = brl->data->serialNumber;
+
+                  byte = mempcpy(byte, deviceResponse+2,
+                                 (sizeof(brl->data->serialNumber) - 1));
+                  *byte = 0;
+                  logMessage(LOG_INFO, "Serial Number: %s", brl->data->serialNumber);
+                }
+
+                logMessage(LOG_INFO, "Display Size: %u", brl->textColumns);
+                logMessage(LOG_INFO, "Visual Display: %s",
+                           (brl->data->haveVisualDisplay? "yes": "no"));
+
+                makeOutputTable(dotsTable_ISO11548_1);
+                return 1;
+              }
+
+              free(brl->data->firmwareVersion);
+            } else {
+              logMallocError();
+            }
+          }
+        }
+      }
+    } else {
+      logMessage(LOG_WARNING, "invalid embedded setting: %s", parameters[PARM_EMBEDDED]);
+    }
+
+    stopLatchMonitor(brl);
+    closeExternalPort(brl);
+    closeInternalPort(brl);
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (brl->data->isEmbedded) {
+    clearWindow(brl);
+    drainBrailleOutput(brl, 50);
+    deactivateBraille();
+  }
+
+  if (brl->data) {
+    stopLatchMonitor(brl);
+    closeExternalPort(brl);
+    closeInternalPort(brl);
+    free(brl->data->firmwareVersion);
+
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
diff --git a/Drivers/Braille/Iris/brldefs-ir.h b/Drivers/Braille/Iris/brldefs-ir.h
new file mode 100644
index 0000000..bd47578
--- /dev/null
+++ b/Drivers/Braille/Iris/brldefs-ir.h
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IR_BRLDEFS
+#define BRLTTY_INCLUDED_IR_BRLDEFS
+
+#define IR_DEFAULT_LATCH_DELAY 1000
+#define IR_DEFAULT_LATCH_INTERVAL 100
+
+#define IR_WINDOW_SIZE_MAXIMUM 40 /* Maximum size for braille window and visual display */
+#define IR_WINDOW_SIZE_SMALL 32 /* Size of braille window on the Iris S */
+
+#define IR_OPT_SERIALNUMBERRESPONSE_LENGTH 6
+#define IR_OPT_SERIALNUMBERRESPONSE_NOWINDOWLENGTH 0xff
+
+typedef enum {
+  IR_IPT_BrailleKeys     = 'B',
+  IR_IPT_LinearKeys      = 'C',
+  IR_IPT_InteractiveKey  = 'I',
+  IR_IPT_SerialNumberResponse    = 'S', 
+  IR_IPT_XtKeyCode       = 'U',
+  IR_IPT_XtKeyCodeRepeat = 'u',
+  IR_IPT_VersionResponse = 'V'
+} IrisInputPacketType;
+
+typedef enum {
+  IR_OPT_WriteBraille   = 'B',
+  IR_OPT_WriteVisual    = 'L',
+  IR_OPT_SerialNumberRequest = 'S',
+  IR_OPT_VersionRequest = 'V'
+} IrisOutputPacketType;
+
+typedef enum {
+  /* linear keys */
+  IR_KEY_L1 = 0X00,
+  IR_KEY_L2 = 0X01,
+  IR_KEY_L3 = 0X02,
+  IR_KEY_L4 = 0X03,
+  IR_KEY_L5 = 0X05,
+  IR_KEY_L6 = 0X06,
+  IR_KEY_L7 = 0X07,
+  IR_KEY_L8 = 0X08,
+
+  /* braille keys */
+  IR_KEY_Dot1      = 0X10,
+  IR_KEY_Dot2      = 0X11,
+  IR_KEY_Dot3      = 0X12,
+  IR_KEY_Dot4      = 0X13,
+  IR_KEY_Dot5      = 0X14,
+  IR_KEY_Dot6      = 0X15,
+  IR_KEY_Dot7      = 0X16,
+  IR_KEY_Dot8      = 0X17,
+  IR_KEY_Backspace = 0X18,
+  IR_KEY_Space     = 0X19,
+
+  /* special keys */
+  IR_KEY_Menu = 0X20,
+  IR_KEY_Z    = 0X21
+} IR_NavigationKey;
+
+typedef enum {
+  IR_GRP_NavigationKeys = 0,
+  IR_GRP_RoutingKeys,
+  IR_GRP_Xt,
+  IR_GRP_XtE0,
+  IR_GRP_XtE1
+} IR_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_IR_BRLDEFS */ 
diff --git a/Drivers/Braille/Libbraille/Makefile.in b/Drivers/Braille/Libbraille/Makefile.in
new file mode 100644
index 0000000..a12dca6
--- /dev/null
+++ b/Drivers/Braille/Libbraille/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = lb
+DRIVER_NAME = Libbraille
+DRIVER_USAGE = Libbraille
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Sébastien Sablé <sable@users.sourceforge.net>
+BRL_OBJS = @braille_libraries_lb@
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -I$(LIBBRAILLE_ROOT)/include -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Libbraille/braille.c b/Drivers/Braille/Libbraille/braille.c
new file mode 100644
index 0000000..80ec661
--- /dev/null
+++ b/Drivers/Braille/Libbraille/braille.c
@@ -0,0 +1,219 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Libbraille/braille.c - Braille display driver using libbraille
+ *
+ * Written by Sébastien Sablé <sable@users.sourceforge.net>
+ *
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "core.h"
+
+#include <braille.h>
+
+typedef enum {
+  PARM_DEVICE,
+  PARM_DRIVER,
+  PARM_TABLE
+} DriverParameter;
+#define BRLPARMS "device", "driver", "table"
+
+#include "brl_driver.h"
+
+static int
+brl_construct(BrailleDisplay *brl, char **parameters, const char *device)
+{
+  if(*parameters[PARM_DEVICE])
+    braille_config(BRL_DEVICE, parameters[PARM_DEVICE]);
+
+  if(*parameters[PARM_DRIVER])
+    braille_config(BRL_DRIVER, parameters[PARM_DRIVER]);
+
+  if(*parameters[PARM_TABLE])
+    braille_config(BRL_TABLE, parameters[PARM_TABLE]);
+
+  if(braille_init())
+    {
+      logMessage(LOG_INFO, "Libbraille Version: %s", braille_info(BRL_VERSION));
+
+#ifdef BRL_PATH
+      logMessage(LOG_DEBUG, "Libbraille Installation Directory: %s", braille_info(BRL_PATH));
+#endif /* BRL_PATH */
+
+#ifdef BRL_PATHCONF
+      logMessage(LOG_DEBUG, "Libbraille Configuration Directory: %s", braille_info(BRL_PATHCONF));
+#endif /* BRL_PATHCONF */
+
+#ifdef BRL_PATHTBL
+      logMessage(LOG_DEBUG, "Libbraille Tables Directory: %s", braille_info(BRL_PATHTBL));
+#endif /* BRL_PATHTBL */
+
+#ifdef BRL_PATHDRV
+      logMessage(LOG_DEBUG, "Libbraille Drivers Directory: %s", braille_info(BRL_PATHDRV));
+#endif /* BRL_PATHDRV */
+
+      logMessage(LOG_INFO, "Libbraille Table: %s", braille_info(BRL_TABLE));
+      logMessage(LOG_INFO, "Libbraille Driver: %s", braille_info(BRL_DRIVER));
+      logMessage(LOG_INFO, "Libbraille Device: %s", braille_info(BRL_DEVICE));
+
+      logMessage(LOG_INFO, "Display Type: %s", braille_info(BRL_TERMINAL));
+      logMessage(LOG_INFO, "Display Size: %d", braille_size());
+
+      brl->textColumns = braille_size();  /* initialise size of display */
+      brl->textRows = 1;
+
+      MAKE_OUTPUT_TABLE(
+        BRAILLE(1, 0, 0, 0, 0, 0, 0, 0),
+        BRAILLE(0, 1, 0, 0, 0, 0, 0, 0),
+        BRAILLE(0, 0, 1, 0, 0, 0, 0, 0),
+        BRAILLE(0, 0, 0, 1, 0, 0, 0, 0),
+        BRAILLE(0, 0, 0, 0, 1, 0, 0, 0),
+        BRAILLE(0, 0, 0, 0, 0, 1, 0, 0),
+        BRAILLE(0, 0, 0, 0, 0, 0, 1, 0),
+        BRAILLE(0, 0, 0, 0, 0, 0, 0, 1)
+      );
+      makeInputTable();
+  
+      braille_timeout(100);
+
+      return 1;
+    }
+  else
+    {
+      logMessage(LOG_DEBUG, "Libbraille initialization error: %s", braille_geterror());
+    }
+  
+  return 0;
+}
+
+static void
+brl_destruct(BrailleDisplay *brl)
+{
+  braille_close();
+}
+
+static int
+brl_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  if(text)
+    {
+      char bytes[brl->textColumns];
+      int i;
+
+      for(i = 0; i < brl->textColumns; ++i)
+        {
+          wchar_t character = text[i];
+          bytes[i] = iswLatin1(character)? character: '?';
+        }
+      braille_write(bytes, brl->textColumns);
+
+      if(brl->cursor != BRL_NO_CURSOR)
+        {
+          braille_filter(translateOutputCell(getScreenCursorDots()), brl->cursor);
+        }
+
+      braille_render();
+    }
+
+  return 1;
+}
+
+static int
+brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context)
+{
+  int res = EOF;
+  signed char status;
+  brl_key key;
+
+  status = braille_read(&key);
+  if(status == -1)
+    {
+      logMessage(LOG_ERR, "error in braille_read: %s", braille_geterror());
+      res = BRL_CMD_RESTARTBRL;
+    }
+  else if(status)
+    {
+      switch(key.type)
+	{
+	case BRL_NONE:
+	  break;
+	case BRL_CURSOR:
+	  res = BRL_CMD_BLK(ROUTE) + key.code;
+	  break;
+	case BRL_CMD:
+	  switch(key.code)
+	    {
+	    case BRLK_UP:
+	      res = BRL_CMD_KEY(CURSOR_UP);
+	      break;
+	    case BRLK_DOWN:
+	      res = BRL_CMD_KEY(CURSOR_DOWN);
+	      break;
+	    case BRLK_RIGHT:
+	      res = BRL_CMD_KEY(CURSOR_RIGHT);
+	      break;
+	    case BRLK_LEFT:
+	      res = BRL_CMD_KEY(CURSOR_LEFT);
+	      break;
+	    case BRLK_INSERT:
+	      res = BRL_CMD_KEY(INSERT);
+	      break;
+	    case BRLK_HOME:
+	      res = BRL_CMD_KEY(HOME);
+	      break;
+	    case BRLK_END:
+	      res = BRL_CMD_KEY(END);
+	      break;
+	    case BRLK_PAGEUP:
+	      res = BRL_CMD_KEY(PAGE_UP);
+	      break;
+	    case BRLK_PAGEDOWN:
+	      res = BRL_CMD_KEY(PAGE_DOWN);
+	      break;
+	    case BRLK_BACKWARD:
+	      res = BRL_CMD_FWINLT;
+	      break;
+	    case BRLK_FORWARD:
+	      res = BRL_CMD_FWINRT;
+	      break;
+	    case BRLK_ABOVE:
+	      res = BRL_CMD_LNUP;
+	      break;
+	    case BRLK_BELOW:
+	      res = BRL_CMD_LNDN;
+	      break;
+	    default:
+	      break;
+	    }
+	  break;
+	case BRL_KEY:
+	  res = BRL_CMD_BLK(PASSDOTS) | translateInputCell(key.braille);
+	  break;
+	default:
+          break;
+	}
+    }
+
+  return res;
+}
diff --git a/Drivers/Braille/LogText/DEVELOP b/Drivers/Braille/LogText/DEVELOP
new file mode 100644
index 0000000..dbe9be9
--- /dev/null
+++ b/Drivers/Braille/LogText/DEVELOP
@@ -0,0 +1,133 @@
+File: PROTOCOL
+by Hans Schou <blind@schou.dk>
+
+General description of LogText
+------------------------------
+http://www.dinf.org/tiresias/Equipment/Notetakers.htm
+Personal aid for braille users. Piezo-electric, 8 dot matrix, 32 character
+braille display with a braille keyboard. Also provided with a 32 character
+LCD text display. Available in two versions: two-hand version has a tilted
+keyboard for easy positioning of hands; one-hand keyboard is designed for
+operation by either the right or the left hand. Functions include reading,
+editing and storage of text and a clock. Text storage approximates 200
+typewritten pages. Data storage: 512KB RAM (with lithium battery backup) of
+which 328KB is for text storage, 56KB for working storage, 56KB for
+terminal buffer, 56KB for input/output buffer and 16KB for system. One
+standard parallel port and 2 RS232-E compatible ports. Power supply by
+mains adaptor 220/12V AC +/- 10%, 20VA or by built-in NiCad rechargeable
+batteries. Length: 380mm Width: 180mm Height: 65mm Weight: 2.7kg
+
+Techinal description
+--------------------
+The LogText is almost like a VT-100 terminal regarding functionality.
+It has an internal screen buffer of 80x25 characters and it is then
+only neccesary to update the internal screen when there is changes.
+The user can move around on the internal screen without communicating
+with the computer. The LogText do request updates of the specefic
+line but it is not neccesary to update a line if it has not changed.
+When the LogText is in cursor tracking mode and the user presses one
+of the arrow keys, the LogText will first request an update of the
+specefic line and then a request for update of the line on the computer
+screen which hold the cursor. A sequence could look like 0xff 0x03 0xff
+0x00 when in cursor tracking mode.
+
+Dot key strokes are send as plain ASCII. Pressing dot-1 will just send
+ASCII 'a' 0x61 and dot-17 will send ASCII 'A' 0x41. Cursor movement
+keys is escaped with 0x00 and the following character is similar to
+VT-100 cursor movement codes. An arrow up will send the sequence
+0x00 0x48. As 0x00 is used for escaping cursor movements and 0xff is
+used for requesting updates, these two codes can not be send from the
+LogText. This gives that the LogText can send 254 different charcaters.
+The translation table used is codepage XXX and can then produce six
+danish letters. The rest of the table is used for abbreviations.
+
+Data from the computer to LogText is send as a protocol without ACK.
+A full description of the computer to LogText protocol can be found
+in the protocol section.
+
+If the user does not press any keys there will be no communication
+from the LogText to the computer. The driver should detect any changes
+on the computer screen and send an update if the line the user is
+looking has changed. The computer will know what the currentline is
+by saving the last absolute line which was requested from the LogText.
+That is, if the user requested line 4 and is in cursor tracking mode
+the sequence would be 0xff 0x04 0xff 0x00. In the case the computer
+will know that the user is looking at line 4 and that the LogText
+should have an update if line 4 changes. It is not neccesary to update
+lines from the computer screen if the user is not looking at them.
+If the cursor moves an update should be send.
+
+If a wrong protocol package is send to Logtext it will go into an error
+mode and stop receiving.
+
+Communication
+-------------
+The RS-232 communication is fixed to 9600 baud n 8 1.
+
+Logtext protocol
+----------------
+
+1. Request from Logtext
+
+1.1. Update a line: <start><line>
+ start: start code 0xFF
+ 
+ line: line number to update
+   0x01 <= line 0x19
+
+ Example:
+  Update of line 4: 0xff 0x04
+
+1.2. Update cursor line: <start><0>
+ start: start code 0xFF
+
+ 0: code 0x00
+
+ The sequence is send at start up of the LogText and
+ when in cursor tracking mode.
+
+ Example:
+  Update current line: 0xff 0x00
+
+2. Answer from computer
+
+2.1. Protocol: <start><line><cursor><column><count><text>
+ start: start code 0xFF.
+
+ line: line number on screen.
+   0x01 <= line 0x19
+
+ cursor: cursor position on this line.
+   0x01 <= cursor <= 0x50
+ Special cursor == 0x00, no cursor on this line.
+
+ column: text starts from this column.
+   0x01 <= column <= 0x50
+
+ count: number of bytes in text.
+   0x00 <= count <= 0x50
+
+ text: ASCII characters. Any character except 0xFF.
+  It is not possible to turn on all dots (0xFF).
+
+ Example:
+   0xff 0x01 0x04 0x01 0x03 0x61 0x62 0x63
+   will display 'abc' at top left with the cursor at the end.
+
+2.2. Automatic cursor tracking.
+If the mode is currently in cursor tracking and the cursor moves
+to another line, the computer should send a new updated line to
+LogText. If the cursor moves within the same line the computer sends
+an update of the cursor position.
+
+
+Logtext manufactor
+------------------
+ Tactilog
+ Roskildevej 15
+ DK-7441 Bording
+ Denmark
+
+ Contact: Poul Erik Skov
+ Phone: +45 86 86 20 88
+ Fax: +45 86 86 21 88
diff --git a/Drivers/Braille/LogText/Makefile.in b/Drivers/Braille/LogText/Makefile.in
new file mode 100644
index 0000000..0c21fb3
--- /dev/null
+++ b/Drivers/Braille/LogText/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = lt
+DRIVER_NAME = LogText
+DRIVER_USAGE = 32
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/LogText/PICTURE b/Drivers/Braille/LogText/PICTURE
new file mode 100644
index 0000000..31fe54b
--- /dev/null
+++ b/Drivers/Braille/LogText/PICTURE
@@ -0,0 +1,27 @@
+   LCDLCDLCDLCDLCDLCDLCD
+         321   456
+      C7     u     8A
+            ljr
+             d
+           L   R
+ e BRAILLEBRAILLEBRAILLE e
+
+LCD: LCD display
+1: dot 1
+2: dot 2
+3: dot 3
+4: dot 4
+5: dot 5
+6: dot 6
+7: dot 7
+8: dot 8
+C: Control
+A: Alt
+u: cursor up
+l: cursor left
+r: cursor right
+d: cursor down
+j: jump (I'm not sure what it does)
+L: Left, I guess this is internal left
+R: Right, I guess this is internal right
+e: Enter key (one in both sides of the braille)
diff --git a/Drivers/Braille/LogText/PROTOCOL b/Drivers/Braille/LogText/PROTOCOL
new file mode 100644
index 0000000..418eb2f
--- /dev/null
+++ b/Drivers/Braille/LogText/PROTOCOL
@@ -0,0 +1,72 @@
+File: Develop
+by Hans Schou
+
+Logtext manufactor:
+ Tactilog
+ Roskildevej 15
+ DK-7441 Bording
+
+ Contact: Poul Erik Skov
+ Phone: +45 86 86 20 88
+ Fax: +45 86 86 21 88
+
+9600 baud
+
+Logtext protocol
+----------------
+
+1. Request from Logtext
+
+1.1. Update a line: <start><line>
+ start: start code 0xFF
+ 
+ line: line number to update
+   0x01 <= line 0x19
+when going into terminal
+
+1.2. Update cursor line: <start><0>
+ start: start code 0xFF
+
+ 0: code 0x00
+
+2. Answer from computer
+
+2.1. Protocol: <start><line><cursor><column><count><text>
+ start: start code 0xFF.
+
+ line: line number on screen.
+   0x01 <= line 0x19
+
+ cursor: cursor position on this line.
+   0x01 <= cursor <= 0x50
+ Special cursor == 0x00, no cursor on this line.
+
+ column: text starts from this column.
+   0x01 <= column <= 0x50
+
+ count: number of bytes in text.
+   0x00 <= count <= 0x50
+
+ text: ASCII characters. Any character except 0xFF.
+  It is not possible to turn on all dots (0xFF).
+
+
+2.2. Automatic cursor tracking.
+If the mode is currently in cursor tracking and the
+cursor moves to another line, the computer should send
+a new updated line to Logtext. If the cursor moves
+within the same line the computer sends an update of
+the cursor position.
+
+Regardless of the mode, the computer always send an
+updated line if the content has changed.
+
+
+-------------------------
+If a wrong protocol package is send
+the Logtext will go into an error mode
+and stop receiving.
+
+Key pressed on Logtext comes as ANSI codes.
+Press dot 1 gives 'a' and 12 gives 'b'.
+Cursor movements comes as ANSI starting with 0x00.
diff --git a/Drivers/Braille/LogText/README b/Drivers/Braille/LogText/README
new file mode 100644
index 0000000..74615f7
--- /dev/null
+++ b/Drivers/Braille/LogText/README
@@ -0,0 +1,13 @@
+This directory contains the BRLTTY driver for the LogText, which is
+manufactured by Tactilog of Denmark. It was implemented, and is being
+maintained, by Dave Mielke <dave@mielke.cc>. Thanks to Hans Schou
+<chlor@schou.dk> for his help and advice, and to Thomas Sørensen
+<thomassoerens@wanadoo.dk> for testing. As a component of BRLTTY, this driver
+is released under the terms of the GNU Public License.
+
+Contact: Poul Erik Skov
+  Phone: +45 86 86 20 88
+    Fax: +45 86 86 21 88
+Address: Tactilog
+	 Roskildevej 15
+	 DK-7441 Bording
diff --git a/Drivers/Braille/LogText/braille.c b/Drivers/Braille/LogText/braille.c
new file mode 100644
index 0000000..ece25ce
--- /dev/null
+++ b/Drivers/Braille/LogText/braille.c
@@ -0,0 +1,631 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* LogText/braille.c - Braille display library
+ * For Tactilog's LogText
+ * Author: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "file.h"
+#include "device.h"
+#include "async_wait.h"
+#include "ascii.h"
+
+#define BRL_STATUS_FIELDS sfGeneric
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "braille.h"
+#include "io_serial.h"
+
+static SerialDevice *serialDevice = NULL;
+
+#define screenHeight 25
+#define screenWidth 80
+typedef unsigned char ScreenImage[screenHeight][screenWidth];
+static ScreenImage sourceImage;
+static ScreenImage targetImage;
+
+static const char *downloadPath = "logtext-download";
+
+typedef enum {
+   DEV_OFFLINE,
+   DEV_ONLINE,
+   DEV_READY
+} DeviceStatus;
+static DeviceStatus deviceStatus;
+
+static KeyTableCommandContext currentContext;
+static unsigned char currentLine;
+static unsigned char cursorRow;
+static unsigned char cursorColumn;
+
+#ifndef __MINGW32__
+static int
+makeFifo (const char *path, mode_t mode) {
+   struct stat status;
+   if (lstat(path, &status) != -1) {
+      if (S_ISFIFO(status.st_mode)) return 1;
+      logMessage(LOG_ERR, "Download object not a FIFO: %s", path);
+   } else if (errno == ENOENT) {
+      lockUmask();
+      mode_t mask = umask(0);
+      int result = mkfifo(path, mode);
+      int error = errno;
+      umask(mask);
+      unlockUmask();
+
+      if (result != -1) return 1;
+      errno = error;
+      logSystemError("Download FIFO creation");
+   }
+   return 0;
+}
+#endif /* __MINGW32__ */
+
+static int
+makeDownloadFifo (void) {
+#ifdef __MINGW32__
+   return 0;
+#else /* __MINGW32__ */
+   return makeFifo(downloadPath, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH);
+#endif /* __MINGW32__ */
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+   {
+      static TranslationTable outputTable = {
+#include "brl-out.h"
+      };
+
+      setOutputTable(outputTable);
+      makeInputTable();
+
+      {
+         const unsigned char byte = 0XFF;
+
+         if (memchr(outputTable, byte, sizeof(outputTable))) {
+            outputTable[translateInputCell(byte)] = ASCII_SUB;
+         }
+      }
+   }
+
+   if (!isSerialDeviceIdentifier(&device)) {
+      unsupportedDeviceIdentifier(device);
+      return 0;
+   }
+
+   makeDownloadFifo();
+   if ((serialDevice = serialOpenDevice(device))) {
+      if (serialRestartDevice(serialDevice, 9600)) {
+         brl->textRows = screenHeight;
+         brl->textColumns = screenWidth;
+         brl->buffer = &sourceImage[0][0];
+         memset(sourceImage, 0, sizeof(sourceImage));
+         deviceStatus = DEV_ONLINE;
+         return 1;
+      }
+      serialCloseDevice(serialDevice);
+      serialDevice = NULL;
+   }
+   return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+   serialCloseDevice(serialDevice);
+   serialDevice = NULL;
+}
+
+static int
+checkData (const unsigned char *data, unsigned int length) {
+   if ((length < 5) || (length != (data[4] + 5))) {
+      logMessage(LOG_ERR, "Bad length: %d", length);
+   } else if (data[0] != 255) {
+      logMessage(LOG_ERR, "Bad header: %d", data[0]);
+   } else if ((data[1] < 1) || (data[1] > screenHeight)) {
+      logMessage(LOG_ERR, "Bad line: %d", data[1]);
+   } else if (data[2] > screenWidth) {
+      logMessage(LOG_ERR, "Bad cursor: %d", data[2]);
+   } else if ((data[3] < 1) || (data[3] > screenWidth)) {
+      logMessage(LOG_ERR, "Bad column: %d", data[3]);
+   } else if (data[4] > (screenWidth - (data[3] - 1))) {
+      logMessage(LOG_ERR, "Bad count: %d", data[4]);
+   } else {
+      return 1;
+   }
+   return 0;
+}
+
+static int
+sendBytes (const unsigned char *bytes, size_t count) {
+   if (serialWriteData(serialDevice, bytes, count) == -1) {
+      logSystemError("LogText write");
+      return 0;
+   }
+   return 1;
+}
+
+static int
+sendData (unsigned char line, unsigned char column, unsigned char count) {
+   unsigned char data[5 + count];
+   unsigned char *target = data;
+   unsigned char *source = &targetImage[line][column];
+   *target++ = 0XFF;
+   *target++ = line + 1;
+   *target++ = (line == cursorRow)? cursorColumn+1: 0;
+   *target++ = column + 1;
+   *target++ = count;
+   logBytes(LOG_DEBUG, "Output dots", source, count);
+   target = translateOutputCells(target, source, count);
+   count = target - data;
+   logBytes(LOG_DEBUG, "LogText write", data, count);
+   if (checkData(data, count)) {
+      if (sendBytes(data, count)) {
+         return 1;
+      }
+   }
+   return 0;
+}
+
+static int
+sendLine (unsigned char line, int force) {
+   unsigned char *source = &sourceImage[line][0];
+   unsigned char *target = &targetImage[line][0];
+   unsigned char start = 0;
+   unsigned char count = screenWidth;
+   while (count > 0) {
+      if (source[count-1] != target[count-1]) break;
+      --count;
+   }
+   while (start < count) {
+      if (source[start] != target[start]) break;
+      ++start;
+   }
+   if ((count -= start) || force) {
+      logMessage(LOG_DEBUG, "LogText line: line=%d, column=%d, count=%d", line, start, count);
+      memcpy(&target[start], &source[start], count);
+      if (!sendData(line, start, count)) {
+         return 0;
+      }
+   }
+   return 1;
+}
+
+static int
+sendCurrentLine (void) {
+   return sendLine(currentLine, 0);
+}
+
+static int
+sendCursorRow (void) {
+   return sendLine(cursorRow, 1);
+}
+
+static int
+handleUpdate (unsigned char line) {
+   logMessage(LOG_DEBUG, "Request line: (0X%2.2X) 0X%2.2X dec=%d", KEY_UPDATE, line, line);
+   if (!line) return sendCursorRow();
+   if (line <= screenHeight) {
+      currentLine = line - 1;
+      return sendCurrentLine();
+   }
+   logMessage(LOG_WARNING, "Invalid line request: %d", line);
+   return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+   if (deviceStatus == DEV_READY) {
+      sendCurrentLine();
+   }
+   return 1;
+}
+
+static int
+isOnline (void) {
+   int online = serialTestLineDSR(serialDevice);
+   if (online) {
+      if (deviceStatus < DEV_ONLINE) {
+         deviceStatus = DEV_ONLINE;
+         logMessage(LOG_WARNING, "LogText online.");
+      }
+   } else {
+      if (deviceStatus > DEV_OFFLINE) {
+         deviceStatus = DEV_OFFLINE;
+         logMessage(LOG_WARNING, "LogText offline.");
+      }
+   }
+   return online;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+   if (isOnline()) {
+      if (status[GSC_FIRST] == GSC_MARKER) {
+         unsigned char row = status[gscScreenCursorRow];
+         unsigned char column = status[gscScreenCursorColumn];
+         row = MAX(1, MIN(row, screenHeight)) - 1;
+         column = MAX(1, MIN(column, screenWidth)) - 1;
+         if (deviceStatus < DEV_READY) {
+            memset(targetImage, 0, sizeof(targetImage));
+            currentContext = KTB_CTX_DEFAULT;
+            currentLine = row;
+            cursorRow = screenHeight;
+            cursorColumn = screenWidth;
+            deviceStatus = DEV_READY;
+         }
+         if ((row != cursorRow) || (column != cursorColumn)) {
+            logMessage(LOG_DEBUG, "cursor moved: [%d,%d] -> [%d,%d]", cursorColumn, cursorRow, column, row);
+            cursorRow = row;
+            cursorColumn = column;
+            sendCursorRow();
+         }
+      }
+   }
+   return 1;
+}
+
+static int
+readKey (void) {
+   unsigned char key;
+   unsigned char arg;
+   if (serialReadData(serialDevice, &key, 1, 0, 0) != 1) return EOF;
+   switch (key) {
+      default:
+         arg = 0;
+         break;
+      case KEY_FUNCTION:
+      case KEY_FUNCTION2:
+      case KEY_UPDATE:
+         while (serialReadData(serialDevice, &arg, 1, 0, 0) != 1) asyncWait(1);
+         break;
+   }
+   {
+      int result = COMPOUND_KEY(key, arg);
+      logMessage(LOG_DEBUG, "Key read: %4.4X", result);
+      return result;
+   }
+}
+
+/*askUser
+static unsigned char *selectedLine;
+
+static void
+replaceCharacters (const unsigned char *address, size_t count) {
+   translateInputCells(&selectedLine[cursorColumn], address, count);
+   cursorColumn += count;
+}
+
+static void
+insertCharacters (const unsigned char *address, size_t count) {
+   memmove(&selectedLine[cursorColumn+count], &selectedLine[cursorColumn], screenWidth-cursorColumn-count);
+   replaceCharacters(address, count);
+}
+
+static void
+deleteCharacters (size_t count) {
+   memmove(&selectedLine[cursorColumn], &selectedLine[cursorColumn+count], screenWidth-cursorColumn-count);
+   memset(&selectedLine[screenWidth-count], translateInputCell(' '), count);
+}
+
+static void
+clearCharacters (void) {
+   cursorColumn = 0;
+   deleteCharacters(screenWidth);
+}
+
+static void
+selectLine (unsigned char line) {
+   selectedLine = &sourceImage[cursorRow = line][0];
+   clearCharacters();
+   deviceStatus = DEV_ONLINE;
+}
+
+static unsigned char *
+askUser (const unsigned char *prompt) {
+   unsigned char from;
+   unsigned char to;
+   selectLine(screenHeight-1);
+   logMessage(LOG_DEBUG, "Prompt: %s", prompt);
+   replaceCharacters(prompt, strlen(prompt));
+   from = to = ++cursorColumn;
+   sendCursorRow();
+   while (1) {
+      int key = readKey();
+      if (key == EOF) {
+         asyncWait(1);
+         continue;
+      }
+      if ((key & KEY_MASK) == KEY_UPDATE) {
+         handleUpdate(key >> KEY_SHIFT);
+         continue;
+      }
+      if (isgraph(key)) {
+         if (to < screenWidth) {
+            unsigned char character = key & KEY_MASK;
+            insertCharacters(&character, 1);
+            ++to;
+         } else {
+            ringConsoleBell();
+         }
+      } else {
+         switch (key) {
+            case CR:
+               if (to > from) {
+                  size_t length = to - from;
+                  unsigned char *response = malloc(length+1);
+                  if (response) {
+                     translateOutputCells(response, &selectedLine[from], length);
+                     response[length] = 0;
+                     logMessage(LOG_DEBUG, "Response: %s", response);
+                     return response;
+                  } else {
+                     logSystemError("Download file path allocation");
+                  }
+               }
+               return NULL;
+            case BS:
+               if (cursorColumn > from) {
+                  --cursorColumn;
+                  deleteCharacters(1);
+                  --to;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            case DEL:
+               if (cursorColumn < to) {
+                  deleteCharacters(1);
+                  --to;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            case KEY_FUNCTION_CURSOR_LEFT:
+               if (cursorColumn > from) {
+                  --cursorColumn;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            case KEY_FUNCTION_CURSOR_LEFT_JUMP:
+               if (cursorColumn > from) {
+                  cursorColumn = from;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            case KEY_FUNCTION_CURSOR_RIGHT:
+               if (cursorColumn < to) {
+                  ++cursorColumn;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            case KEY_FUNCTION_CURSOR_RIGHT_JUMP:
+               if (cursorColumn < to) {
+                  cursorColumn = to;
+               } else {
+                  ringConsoleBell();
+               }
+               break;
+            default:
+               ringConsoleBell();
+               break;
+         }
+      }
+      sendCursorRow();
+   }
+}
+*/
+
+static void
+downloadFile (void) {
+   if (makeDownloadFifo()) {
+      int file = open(downloadPath, O_RDONLY);
+      if (file != -1) {
+         struct stat status;
+         if (fstat(file, &status) != -1) {
+            unsigned char buffer[0X400];
+            const unsigned char *address = buffer;
+            int count = 0;
+            while (1) {
+               const unsigned char *newline;
+               if (!count) {
+                  count = read(file, buffer, sizeof(buffer));
+                  if (!count) {
+                     static const unsigned char fileTrailer[] = {0X1A};
+                     sendBytes(fileTrailer, sizeof(fileTrailer));
+                     break;
+                  }
+                  if (count == -1) {
+                     logSystemError("Download file read");
+                     break;
+                  }
+                  address = buffer;
+               }
+               if ((newline = memchr(address, '\n', count))) {
+                  static const unsigned char lineTrailer[] = {ASCII_CR, ASCII_LF};
+                  size_t length = newline - address;
+                  if (!sendBytes(address, length++)) break;
+                  if (!sendBytes(lineTrailer, sizeof(lineTrailer))) break;
+                  address += length;
+                  count -= length;
+               } else {
+                  if (!sendBytes(address, count)) break;
+                  count = 0;
+               }
+            }
+         } else {
+            logSystemError("Download file status");
+         }
+         if (close(file) == -1) {
+            logSystemError("Download file close");
+         }
+      } else {
+         logSystemError("Download file open");
+      }
+   } else {
+      logMessage(LOG_WARNING, "Download path not specified.");
+   }
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+   int key = readKey();
+   if (context != currentContext) {
+      logMessage(LOG_DEBUG, "Context switch: %d -> %d", currentContext, context);
+      switch (currentContext = context) {
+         case KTB_CTX_DEFAULT:
+            deviceStatus = DEV_ONLINE;
+            break;
+         default:
+            break;
+      }
+   }
+   if (key != EOF) {
+      switch (key) {
+         case KEY_FUNCTION_ENTER:
+            return BRL_CMD_KEY(ENTER);
+         case KEY_FUNCTION_TAB:
+            return BRL_CMD_KEY(TAB);
+         case KEY_FUNCTION_CURSOR_UP:
+            return BRL_CMD_KEY(CURSOR_UP);
+         case KEY_FUNCTION_CURSOR_DOWN:
+            return BRL_CMD_KEY(CURSOR_DOWN);
+         case KEY_FUNCTION_CURSOR_LEFT:
+            return BRL_CMD_KEY(CURSOR_LEFT);
+         case KEY_FUNCTION_CURSOR_RIGHT:
+            return BRL_CMD_KEY(CURSOR_RIGHT);
+         case KEY_FUNCTION_CURSOR_UP_JUMP:
+            return BRL_CMD_KEY(HOME);
+         case KEY_FUNCTION_CURSOR_DOWN_JUMP:
+            return BRL_CMD_KEY(END);
+         case KEY_FUNCTION_CURSOR_LEFT_JUMP:
+            return BRL_CMD_KEY(PAGE_UP);
+         case KEY_FUNCTION_CURSOR_RIGHT_JUMP:
+            return BRL_CMD_KEY(PAGE_DOWN);
+         case KEY_FUNCTION_F1:
+            return BRL_CMD_KFN(1);
+         case KEY_FUNCTION_F2:
+            return BRL_CMD_KFN(2);
+         case KEY_FUNCTION_F3:
+            return BRL_CMD_KFN(3);
+         case KEY_FUNCTION_F4:
+            return BRL_CMD_KFN(4);
+         case KEY_FUNCTION_F5:
+            return BRL_CMD_KFN(5);
+         case KEY_FUNCTION_F6:
+            return BRL_CMD_KFN(6);
+         case KEY_FUNCTION_F7:
+            return BRL_CMD_KFN(7);
+         case KEY_FUNCTION_F9:
+            return BRL_CMD_KFN(9);
+         case KEY_FUNCTION_F10:
+            return BRL_CMD_KFN(10);
+         case KEY_COMMAND: {
+            int command;
+            while ((command = readKey()) == EOF) asyncWait(1);
+            logMessage(LOG_DEBUG, "Received command: (0x%2.2X) 0x%4.4X", KEY_COMMAND, command);
+            switch (command) {
+               case KEY_COMMAND:
+                  /* pressing the escape command twice will pass it through */
+                  return BRL_CMD_BLK(PASSDOTS) + translateInputCell(KEY_COMMAND);
+               case KEY_COMMAND_SWITCHVT_PREV:
+                  return BRL_CMD_SWITCHVT_PREV;
+               case KEY_COMMAND_SWITCHVT_NEXT:
+                  return BRL_CMD_SWITCHVT_NEXT;
+               case KEY_COMMAND_SWITCHVT_1:
+                  return BRL_CMD_BLK(SWITCHVT) + 0;
+               case KEY_COMMAND_SWITCHVT_2:
+                  return BRL_CMD_BLK(SWITCHVT) + 1;
+               case KEY_COMMAND_SWITCHVT_3:
+                  return BRL_CMD_BLK(SWITCHVT) + 2;
+               case KEY_COMMAND_SWITCHVT_4:
+                  return BRL_CMD_BLK(SWITCHVT) + 3;
+               case KEY_COMMAND_SWITCHVT_5:
+                  return BRL_CMD_BLK(SWITCHVT) + 4;
+               case KEY_COMMAND_SWITCHVT_6:
+                  return BRL_CMD_BLK(SWITCHVT) + 5;
+               case KEY_COMMAND_SWITCHVT_7:
+                  return BRL_CMD_BLK(SWITCHVT) + 6;
+               case KEY_COMMAND_SWITCHVT_8:
+                  return BRL_CMD_BLK(SWITCHVT) + 7;
+               case KEY_COMMAND_SWITCHVT_9:
+                  return BRL_CMD_BLK(SWITCHVT) + 8;
+               case KEY_COMMAND_SWITCHVT_10:
+                  return BRL_CMD_BLK(SWITCHVT) + 9;
+               case KEY_COMMAND_PAGE_UP:
+                  return BRL_CMD_KEY(PAGE_UP);
+               case KEY_COMMAND_PAGE_DOWN:
+                  return BRL_CMD_KEY(PAGE_DOWN);
+               case KEY_COMMAND_PREFMENU:
+                  currentLine = 0;
+                  cursorRow = 0;
+                  cursorColumn = 31;
+                  sendCursorRow();
+                  return BRL_CMD_PREFMENU;
+               case KEY_COMMAND_PREFSAVE:
+                  return BRL_CMD_PREFSAVE;
+               case KEY_COMMAND_PREFLOAD:
+                  return BRL_CMD_PREFLOAD;
+               case KEY_COMMAND_FREEZE_ON:
+                  return BRL_CMD_FREEZE | BRL_FLG_TOGGLE_ON;
+               case KEY_COMMAND_FREEZE_OFF:
+                  return BRL_CMD_FREEZE | BRL_FLG_TOGGLE_OFF;
+               case KEY_COMMAND_RESTARTBRL:
+                  return BRL_CMD_RESTARTBRL;
+               case KEY_COMMAND_DOWNLOAD:
+                  downloadFile();
+                  break;
+               default:
+                  logMessage(LOG_WARNING, "Unknown command: (0X%2.2X) 0X%4.4X", KEY_COMMAND, command);
+                  break;
+            }
+            break;
+         }
+         default:
+            switch (key & KEY_MASK) {
+               case KEY_UPDATE:
+                  handleUpdate(key >> KEY_SHIFT);
+                  break;
+               case KEY_FUNCTION:
+                  logMessage(LOG_WARNING, "Unknown function: (0X%2.2X) 0X%4.4X", KEY_COMMAND, key>>KEY_SHIFT);
+                  break;
+               default: {
+                  unsigned char dots = translateInputCell(key);
+                  logMessage(LOG_DEBUG, "Received character: 0X%2.2X dec=%d dots=%2.2X", key, key, dots);
+                  return BRL_CMD_BLK(PASSDOTS) + dots;
+               }
+            }
+            break;
+      }
+   }
+   return EOF;
+}
diff --git a/Drivers/Braille/LogText/braille.h b/Drivers/Braille/LogText/braille.h
new file mode 100644
index 0000000..e8e3d59
--- /dev/null
+++ b/Drivers/Braille/LogText/braille.h
@@ -0,0 +1,109 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* LogText/braille.h - Configurable definitions for the LogText driver
+ * Dave Mielke <dave@mielke.cc> (October 2001)
+ *
+ * Edit as necessary for your system.
+ */
+
+/* KEY_COMMAND commands */
+#define KEY_COMMAND 0X9F /* dots-37 */
+#define KEY_COMMAND_SWITCHVT_PREV 0X2D /* '-' dots-368 */
+#define KEY_COMMAND_SWITCHVT_NEXT 0X2B /* '+' dots-2358 */
+#define KEY_COMMAND_SWITCHVT_1    0X31 /* '1' dots-18 */
+#define KEY_COMMAND_SWITCHVT_2    0X32 /* '2' dots-128 */
+#define KEY_COMMAND_SWITCHVT_3    0X33 /* '3' dots-148 */
+#define KEY_COMMAND_SWITCHVT_4    0X34 /* '4' dots-1458 */
+#define KEY_COMMAND_SWITCHVT_5    0X35 /* '5' dots-158 */
+#define KEY_COMMAND_SWITCHVT_6    0X36 /* '6' dots-1248 */
+#define KEY_COMMAND_SWITCHVT_7    0X37 /* '7' dots-12458 */
+#define KEY_COMMAND_SWITCHVT_8    0X38 /* '8' dots-1258 */
+#define KEY_COMMAND_SWITCHVT_9    0X39 /* '9' dots-248 */
+#define KEY_COMMAND_SWITCHVT_10   0X30 /* '0' dots-2458 */
+#define KEY_COMMAND_PAGE_UP       0X75 /* 'u' dots-136 */
+#define KEY_COMMAND_PAGE_DOWN     0X64 /* 'd' dots-145 */
+#define KEY_COMMAND_FREEZE_OFF    0X66 /* 'f' dots-124 */
+#define KEY_COMMAND_FREEZE_ON     0X46 /* 'F' dots-1247 */
+#define KEY_COMMAND_INFO          0X49 /* 'I' dots-247 */
+#define KEY_COMMAND_PREFMENU      0X50 /* 'P' dots-12347 */
+#define KEY_COMMAND_PREFSAVE      0X53 /* 'S' dots-2347 */
+#define KEY_COMMAND_PREFLOAD      0X4C /* 'L' dots-1237 */
+#define KEY_COMMAND_RESTARTBRL    0X52 /* 'R' dots-12357 */
+#define KEY_COMMAND_DOWNLOAD      0X44 /* 'D' dots-1457 */
+
+#define KEY_SHIFT 8
+#define KEY_MASK ((1 << KEY_SHIFT) - 1)
+#define COMPOUND_KEY(key,arg) ((key) | ((arg) << KEY_SHIFT))
+
+/* function keys */
+#define KEY_FUNCTION 0X00
+#define FUNCTION_KEY(arg) COMPOUND_KEY(KEY_FUNCTION, arg)
+#define KEY_FUNCTION_ENTER             FUNCTION_KEY(0X1C) /* Enter          p 6,8 L/R */
+#define KEY_FUNCTION_CURSOR_LEFT_JUMP  FUNCTION_KEY(0X47)
+#define KEY_FUNCTION_CURSOR_UP         FUNCTION_KEY(0X48)
+#define KEY_FUNCTION_CURSOR_UP_JUMP    FUNCTION_KEY(0X49)
+#define KEY_FUNCTION_CURSOR_LEFT       FUNCTION_KEY(0X4B)
+#define KEY_FUNCTION_CURSOR_RIGHT      FUNCTION_KEY(0X4D)
+#define KEY_FUNCTION_CURSOR_RIGHT_JUMP FUNCTION_KEY(0X4F)
+#define KEY_FUNCTION_CURSOR_DOWN       FUNCTION_KEY(0X50)
+#define KEY_FUNCTION_CURSOR_DOWN_JUMP  FUNCTION_KEY(0X51)
+#define KEY_FUNCTION_F1                FUNCTION_KEY(0X78) /* F1 (1)         p 1,8 L/R */
+#define KEY_FUNCTION_F2                FUNCTION_KEY(0X79) /* F2 (2)         p 1,2,8 L/R */
+#define KEY_FUNCTION_F3                FUNCTION_KEY(0X7A) /* F3 (3)         p 1,4,8 L/R */
+#define KEY_FUNCTION_F4                FUNCTION_KEY(0X7B) /* F4 (4)         p 1,4,5,8 L/R */
+#define KEY_FUNCTION_F5                FUNCTION_KEY(0X7C) /* F5 (5)         p 1,5,8 L/R */
+#define KEY_FUNCTION_F6                FUNCTION_KEY(0X7D) /* F6 (6)         p 1,2,4,8 L/R */
+#define KEY_FUNCTION_F7                FUNCTION_KEY(0X7E) /* F7 (7)         p 1,2,4,5,8 L/R */
+#define KEY_FUNCTION_F8              //FUNCTION_KEY(0X7E) /* F8 (8)         p 1,2,5,8 L/R */
+#define KEY_FUNCTION_F9                FUNCTION_KEY(0X7F) /* F9 (9)         p 2,4,8 L/R */
+#define KEY_FUNCTION_F10               FUNCTION_KEY(0X81) /* F10 (0)        p 2,4,5,8 L/R */
+#define KEY_FUNCTION_TAB               FUNCTION_KEY(0XA5) /* Tab            p 8  L/R */
+// The following are defined in the manual but don't appear to work.
+#define KEY_FUNCTION_F11             //FUNCTION_KEY(0XXX) /* F11 (k + p 8)  p 1,3,8 L/R */
+#define KEY_FUNCTION_F12             //FUNCTION_KEY(0XXX) /* F12 (l + p 8)  p 1,2,3,8 L/R */
+#define KEY_FUNCTION_HOME            //FUNCTION_KEY(0XXX) /* Home (M)       p 1,3,4,7 L/R */
+#define KEY_FUNCTION_UP_ARROW        //FUNCTION_KEY(0XXX) /* Up arrow (U)   p 1,3,6,7 L/R */
+#define KEY_FUNCTION_PAGE_UP         //FUNCTION_KEY(0XXX) /* Page up (P)    p 1,2,3,4,7 L/R */
+#define KEY_FUNCTION_LEFT_ARROW      //FUNCTION_KEY(0XXX) /* Left arrow (V) p 1,2,3,6,7 L/R */
+#define KEY_FUNCTION_RIGHT_ARROW     //FUNCTION_KEY(0XXX) /* Right arrow(R) p 1,2,3,5,7 L/R */
+#define KEY_FUNCTION_ENDN            //FUNCTION_KEY(0XXX) /* End (N)        p 1,3,4,5,7 L/R */
+#define KEY_FUNCTION_DOWN_ARROW      //FUNCTION_KEY(0XXX) /* Down arrow (W) p 2,4,5,6,7 L/R */
+#define KEY_FUNCTION_PAGE_DOWN       //FUNCTION_KEY(0XXX) /* Page down (O)  p 1,3,5,7 L/R */
+#define KEY_FUNCTION_DELETE          //FUNCTION_KEY(0XXX) /* Delete (T)     p 2,3,4,5,7 L/R */
+#define KEY_FUNCTION_LEFT_ARROW_GRAY //FUNCTION_KEY(0XXX) /* Left arrow, gr p 1,2,3,6,7,8 L/R */
+#define KEY_FUNCTION_PRINT_SCREEN    //FUNCTION_KEY(0XXX) /* Print screen   p 2,4,6,8 L/R */
+#define KEY_FUNCTION_PAUSE_BREAK     //FUNCTION_KEY(0XXX) /* Pause/break    p 4,5,6,8 L/R */
+#define KEY_FUNCTION_BACKSPACE       //FUNCTION_KEY(0XXX) /* Backspace      p 1,3,5,8 L/R */
+#define KEY_FUNCTION_NULL            //FUNCTION_KEY(0XXX) /* Null           p 7 L/R */
+
+#define KEY_FUNCTION2 0XE0
+#define FUNCTION2_KEY(arg) COMPOUND_KEY(KEY_FUNCTION2, arg)
+#define KEY_FUNCTION_INSERT            FUNCTION2_KEY(0X97) /* Insert (S)     p 2,3,4,7 L/R */
+#define KEY_FUNCTION_HOME_GRAY       //FUNCTION2_KEY(0X97) /* Home, gray     p 1,3,4,7,8 L/R */
+#define KEY_FUNCTION_UP_ARROW_GRAY     FUNCTION2_KEY(0X98) /* Up arrow, gray p 1,3,6,7,8 L/R */
+#define KEY_FUNCTION_PAGE_UP_GRAY      FUNCTION2_KEY(0X99) /* Page up, gray  p 1,2,3,4,7,8 L/R */
+#define KEY_FUNCTION_DOWN_ARROW_GRAY   FUNCTION2_KEY(0X9A) /* Down arrow, gr p 2,4,5,6,7,8 L/R */
+#define KEY_FUNCTION_RIGHT_ARROW_GRAY  FUNCTION2_KEY(0X9D) /* Right arrow,gr p 1,2,3,5,7,8 L/R */
+#define KEY_FUNCTION_END_GRAY          FUNCTION2_KEY(0X9F) /* End, gray      p 1,3,4,5,7,8 L/R */
+#define KEY_FUNCTION_PAGE_DOWN_GRAY    FUNCTION2_KEY(0XA1) /* Page down,gray p 1,3,5,7,8 L/R */
+#define KEY_FUNCTION_INSERT_GRAY       FUNCTION2_KEY(0XA2) /* Insert, gray   p 2,3,4,7,8 L/R */
+#define KEY_FUNCTION_DELETE_GRAY       FUNCTION2_KEY(0XA3) /* Delete, gray   p 2,3,4,5,7,8 L/R */
+
+/* Update screen, not a key */
+#define KEY_UPDATE 0XFF
diff --git a/Drivers/Braille/LogText/brl-out.h b/Drivers/Braille/LogText/brl-out.h
new file mode 100644
index 0000000..d11def5
--- /dev/null
+++ b/Drivers/Braille/LogText/brl-out.h
@@ -0,0 +1,274 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+[0] = 0X20, // mellemrum
+[BRL_DOT1] = 0X61, // a at
+[BRL_DOT1 | BRL_DOT2] = 0X62, // b bliver
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3] = 0X6C, // l lige
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4] = 0X70, // p p}
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X71, // q under
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X82, // skal
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X90, // Skal
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X00, // NUL
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XF0, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X51, // Q Under
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X11, // DC1
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XF1, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X87, // den
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X80, // Den
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XBE, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0X26, // & amper
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X50, // P P}
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X10, // DLE
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0X9E, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5] = 0X72, // r rigtig
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X85, // ret
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XB7, // Ret
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XB5, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XA0, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0X52, // R Rigtig
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X12, // DC2
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0XAF, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6] = 0X76, // v ved
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0X56, // V Ved
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X16, // SYN
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0X5B, // ’ ven.kant
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7] = 0X4C, // L Lige
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X0C, // FF
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT8] = 0X9C, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4] = 0X66, // f for
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5] = 0X67, // g g|r
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X8B, // gennem
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XD8, // Gennem
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XA5, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XA4, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X47, // G G|r
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X07, // BEL
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0X37, // 7 syv
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6] = 0X89, // ned
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XD3, // Ned
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE3, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0X95, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT7] = 0X46, // F For
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X06, // ACK
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT8] = 0X36, // 6 seks
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5] = 0X68, // h har
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6] = 0X81, // te
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X9A, // Te
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE7, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XF5, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT7] = 0X48, // H Har
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X08, // BS
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT8] = 0X38, // 8 otte
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6] = 0X88, // en
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT7] = 0XD2, // En
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XCF, // 
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT8] = 0X28, // ( ven.par.
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT7] = 0X42, // B Bliver
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT7 | BRL_DOT8] = 0X02, // STX
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT8] = 0X32, // 2 to
+[BRL_DOT1 | BRL_DOT3] = 0X6B, // k kan
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4] = 0X6D, // m med
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X6E, // n n}r
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X79, // y han
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X59, // Y Han
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X19, // EM
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XF7, // 
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X4E, // N N}r
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X0E, // SO
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XFC, // 
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X78, // x over
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X58, // X Over
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X18, // CAN
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0X2A, // * stjerne
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X4D, // M Med
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X0D, // CR
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0X3C, // < mindre
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5] = 0X6F, // o op
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X7A, // z efter
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X5A, // Z Efter
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X1A, // SUB
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XF2, // 
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0X4F, // O Op
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X0F, // SI
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0X7D, // † tub slut
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6] = 0X75, // u hun
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0X55, // U Hun
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X15, // NAC
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XEC, // 
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT7] = 0X4B, // K Kan
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X0B, // VT
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT8] = 0XAB, // 
+[BRL_DOT1 | BRL_DOT4] = 0X63, // c og
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5] = 0X64, // d du
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X93, // de
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XE2, // De
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE8, // 
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X5D, //  h|j.kant
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X44, // D Du
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X04, // EOT
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0X34, // 4 fire
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6] = 0X8C, // men
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XD7, // Men
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE6, // 
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XFB, // 
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT7] = 0X43, // C Og
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X03, // ETX
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT8] = 0X33, // 3 tre
+[BRL_DOT1 | BRL_DOT5] = 0X65, // e eller
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6] = 0X96, // er
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XEA, // Er
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE4, // 
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X24, // $ dollar
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT7] = 0X45, // E Eller
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X05, // ENQ
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT8] = 0X35, // 5 fem
+[BRL_DOT1 | BRL_DOT6] = 0X86, // } s}
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT7] = 0X8F, // ] S}
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X1D, // GS
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT8] = 0X5C, //  backsl.
+[BRL_DOT1 | BRL_DOT7] = 0X41, // A At
+[BRL_DOT1 | BRL_DOT7 | BRL_DOT8] = 0X01, // SOH
+[BRL_DOT1 | BRL_DOT8] = 0X31, // 1 en,et
+[BRL_DOT2] = 0X2C, // , komma
+[BRL_DOT2 | BRL_DOT3] = 0X3B, // ; semikol.
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4] = 0X73, // s som
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X74, // t til
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X97, // der
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XEB, // Der
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE9, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XA3, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X54, // T Til
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X14, // DC4
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XF4, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X8A, // det
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XD4, // Det
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XD0, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XF3, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X53, // S Som
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X13, // DC3
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0X3E, // > st|rre
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5] = 0XCA, // fra
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X22, // " anf|rsel
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X60, // ` acc.grav
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XDF, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X3D, // = lig med
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0XC2, // Fra
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0XAD, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0X2B, // + plus
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6] = 0XCB, // ham
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0XC3, // Ham
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE5, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XFE, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT7] = 0XC6, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0XC7, // 
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT8] = 0XAC, // 
+[BRL_DOT2 | BRL_DOT4] = 0X69, // i
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5] = 0X6A, // j jeg
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X77, // w hvad
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X57, // W Hvad
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X17, // ETB
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XAE, // 
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X4A, // J Jeg
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X0A, // LF
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0X30, // 0 nul
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6] = 0X9B, // | f|r
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X9D, // \ F|r
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X1C, // FS
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0X7B, // ‘ tub beg
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT7] = 0X49, // I
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X09, // HT
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT8] = 0X39, // 9 ni
+[BRL_DOT2 | BRL_DOT5] = 0X3A, // : kolon
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6] = 0XC9, // deres
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XC1, // Deres
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XED, // 
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XF6, // 
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT7] = 0XB8, // 
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0XBD, // 
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT8] = 0X2F, // / h|j.skr}
+[BRL_DOT2 | BRL_DOT6] = 0X3F, // ? sp|rgsm.
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT7] = 0XA7, // 
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XA8, // 
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT8] = 0XEE, // 
+[BRL_DOT2 | BRL_DOT7] = 0XA6, // 
+[BRL_DOT2 | BRL_DOT7 | BRL_DOT8] = 0XB6, // 
+[BRL_DOT2 | BRL_DOT8] = 0X83, // 
+[BRL_DOT3] = 0X2E, // . punktum
+[BRL_DOT3 | BRL_DOT4] = 0X98, // hvor
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X91, // { v{re
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0XDC, // ve
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XDA, // Ve
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XDD, // 
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X23, // # nummer
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X92, // [ V{re
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X1B, // ESC
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0X29, // ) h|j.par.
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0XDB, // et
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XD9, // Et
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XE0, // 
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XA2, // 
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0XB0, // Hvor
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0XD6, // 
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0XA1, // 
+[BRL_DOT3 | BRL_DOT5] = 0XCC, // igen
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0XC8, // af
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XC0, // Af
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XD1, // 
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X25, // % procent
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0XC4, // Igen
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X99, // 
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0X94, // 
+[BRL_DOT3 | BRL_DOT6] = 0XCD, // var
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0XC5, // Var
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X1F, // US
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0X2D, // - min,bind
+[BRL_DOT3 | BRL_DOT7] = 0X9F, // 
+[BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0XA9, // 
+[BRL_DOT3 | BRL_DOT8] = 0XFA, // 
+[BRL_DOT4] = 0XB9, // an
+[BRL_DOT4 | BRL_DOT5] = 0XBA, // be
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0XBC, // le
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XB4, // Le
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XD5, // 
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0X7C, // › lodret
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0XB2, // Be
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X1E, // RS
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XFD, // 
+[BRL_DOT4 | BRL_DOT6] = 0XBB, // ke
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XB3, // Ke
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XCE, // 
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XEF, // 
+[BRL_DOT4 | BRL_DOT7] = 0XB1, // An
+[BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X40, // @ master
+[BRL_DOT4 | BRL_DOT8] = 0XF8, // 
+[BRL_DOT5] = 0XBF, // opl|sn.
+[BRL_DOT5 | BRL_DOT6] = 0X21, // ! udr}b
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X84, // 
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X8E, // 
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XE1, // 
+[BRL_DOT5 | BRL_DOT7] = 0X8D, // 
+[BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0XDE, // 
+[BRL_DOT5 | BRL_DOT8] = 0XF9, // 
+[BRL_DOT6] = 0X27, // ' apostrof
+[BRL_DOT6 | BRL_DOT7] = 0X5E, // ^ hat
+[BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0XAA, // 
+[BRL_DOT6 | BRL_DOT8] = 0X7E, // ~ tilde
+[BRL_DOT7] = 0XFF, // (causes problems so converted to SUB)
+[BRL_DOT7 | BRL_DOT8] = 0X5F, // _ und.str.
+[BRL_DOT8] = 0X7F  // 
diff --git a/Drivers/Braille/MDV/Makefile.in b/Drivers/Braille/MDV/Makefile.in
new file mode 100644
index 0000000..212a2f0
--- /dev/null
+++ b/Drivers/Braille/MDV/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = md
+DRIVER_NAME = MDV
+DRIVER_USAGE = MB208, MB248, MB408L, MB408L+, Lilli Blu
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/MDV/README b/Drivers/Braille/MDV/README
new file mode 100644
index 0000000..b252b48
--- /dev/null
+++ b/Drivers/Braille/MDV/README
@@ -0,0 +1,14 @@
+Braille display driver for MDV displays
+
+Written by Stéphane Doyon (s.doyon@videotron.ca) in collaboration with
+Simone Dal Maso <sdalmaso@protec.it>.
+
+This is version 0.8 (August 2000) of this driver.
+It is being tested on MB408S, should also support MB208 and MB408L.
+
+Set MDV to use protocol 5.
+
+This version adds a lot to the previously released version, and attempts to
+correct a bug in the last alpha development version. However the current
+version has not been tested yet, I am still waiting for feedback, so it is not
+impossible for it to be completely broken!
diff --git a/Drivers/Braille/MDV/braille.c b/Drivers/Braille/MDV/braille.c
new file mode 100644
index 0000000..dd1c71d
--- /dev/null
+++ b/Drivers/Braille/MDV/braille.c
@@ -0,0 +1,524 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ascii.h"
+
+#define BRL_STATUS_FIELDS sfWindowCoordinates2
+#define BRL_HAVE_STATUS_CELLS
+
+#include "brl_driver.h"
+#include "brldefs-md.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+
+#define MAXIMUM_TEXT_CELLS 80
+#define MAXIMUM_STATUS_CELLS 2
+
+BEGIN_KEY_NAME_TABLE(common)
+  BRL_KEY_NAME_ENTRY(MD, NAV, LEFT, "Left"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, UP, "Up"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, RIGHT, "Right"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, DOWN, "Down"),
+
+  BRL_KEY_NAME_ENTRY(MD, NAV, SHIFT, "Shift"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, LONG, "Long"),
+
+  BRL_KEY_GROUP_ENTRY(MD, RK, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT1, "Dot1"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT2, "Dot2"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT3, "Dot3"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT4, "Dot4"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT5, "Dot5"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT6, "Dot6"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT7, "Dot7"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, DOT8, "Dot8"),
+  BRL_KEY_NAME_ENTRY(MD, BRL, SPACE, "Space"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(fkeys)
+  BRL_KEY_NAME_ENTRY(MD, NAV, F1, "F1"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F2, "F2"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F3, "F3"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F4, "F4"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F5, "F5"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F6, "F6"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F7, "F7"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F8, "F8"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F9, "F9"),
+  BRL_KEY_NAME_ENTRY(MD, NAV, F10, "F10"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status)
+  BRL_KEY_GROUP_ENTRY(MD, SK, "StatusKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(default)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(fkeys),
+  KEY_NAME_TABLE(status),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(kbd)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(keyboard),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(fk)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(fkeys),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(fk_s)
+  KEY_NAME_TABLE(common),
+  KEY_NAME_TABLE(fkeys),
+  KEY_NAME_TABLE(status),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(default)
+DEFINE_KEY_TABLE(kbd)
+DEFINE_KEY_TABLE(fk)
+DEFINE_KEY_TABLE(fk_s)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(default),
+  &KEY_TABLE_DEFINITION(kbd),
+  &KEY_TABLE_DEFINITION(fk),
+  &KEY_TABLE_DEFINITION(fk_s),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  const unsigned int *bauds;
+} InputOutputOperations;
+
+static const unsigned int serialBauds[] = {38400, 19200, 0};
+
+static const InputOutputOperations serialOperations = {
+  .bauds = serialBauds
+};
+
+static const unsigned int usbBauds[] = {38400, 0};
+
+static const InputOutputOperations usbOperations = {
+  .bauds = usbBauds
+};
+
+struct BrailleDataStruct {
+  const InputOutputOperations *io;
+
+  unsigned shiftPressed:1;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAXIMUM_TEXT_CELLS];
+  } text;
+
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAXIMUM_STATUS_CELLS];
+  } status;
+};
+
+static const KeyTableDefinition *
+getKeyTableDefinition (BrailleDisplay *brl) {
+  switch (brl->textColumns) {
+    case 24:
+      if (!brl->statusColumns) return &KEY_TABLE_DEFINITION(kbd);
+      break;
+
+    case 40:
+      if (!brl->statusColumns) return &KEY_TABLE_DEFINITION(fk);
+      return &KEY_TABLE_DEFINITION(fk_s);
+
+    default:
+      break;
+  }
+
+  return &KEY_TABLE_DEFINITION(default);
+}
+
+static uint16_t
+calculateChecksum (const unsigned char *from, const unsigned char *to) {
+  uint16_t checksum = 0;
+
+  while (from < to) {
+    checksum += *from++;
+  }
+
+  return checksum ^ 0XAA55;
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+writePacket (BrailleDisplay *brl, unsigned char code, const void *data, unsigned char length) {
+  MD_Packet packet;
+  unsigned char *byte = packet.fields.data.bytes;
+
+  packet.fields.soh = ASCII_SOH;
+  packet.fields.stx = ASCII_STX;
+  packet.fields.etx = ASCII_ETX;
+
+  packet.fields.code = code;
+  packet.fields.length = length;
+  byte = mempcpy(byte, data, length);
+
+  uint16_t checksum = calculateChecksum(&packet.fields.stx, byte);
+  *byte++ = checksum & 0XFF;
+  *byte++ = checksum >> 8;
+
+  return writeBytes(brl, packet.bytes, byte-packet.bytes);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      if (byte != ASCII_SOH) return BRL_PVR_INVALID;
+      *length = 5;
+      break;
+
+    case 2:
+      if (byte != ASCII_STX) return BRL_PVR_INVALID;
+      break;
+
+    case 4:
+      *length += byte + 2;
+      break;
+
+    case 5:
+      if (byte != ASCII_ETX) return BRL_PVR_INVALID;
+      break;
+
+    default:
+      if (size == *length) {
+        const unsigned char *from = &bytes[1];
+        const unsigned char *to = &bytes[size-2];
+        uint16_t checksum = (to[1] << 8) | to[0];
+
+        if (checksum != calculateChecksum(from, to)) {
+          return BRL_PVR_INVALID;
+        }
+      }
+
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readBytes (BrailleDisplay *brl, void *packet, size_t size) {
+  int ok = readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+
+  if (ok) {
+    if (!writePacket(brl, MD_CODE_ACKNOWLEDGE, NULL, 0)) {
+      brl->hasFailed = 1;
+    }
+  }
+
+  return ok;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, MD_Packet *packet) {
+  return readBytes(brl, packet, sizeof(*packet));
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 19200
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0403, .product=0X6001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &serialOperations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbOperations;
+
+  descriptor.bluetooth.discoverChannel = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->io = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  return writePacket(brl, MD_CODE_IDENTIFY, NULL, 0);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const MD_Packet *response = packet;
+  unsigned char code = response->fields.code;
+
+  if (code == MD_CODE_IDENTITY) return BRL_RSP_DONE;
+  if (code == MD_CODE_ACKNOWLEDGE) return BRL_RSP_CONTINUE;
+  return BRL_RSP_UNEXPECTED;
+}
+
+static int
+probeDevice (BrailleDisplay *brl, MD_Packet *response) {
+  return probeBrailleDisplay(
+    brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT, writeIdentifyRequest,
+    readBytes, response, sizeof(*response), isIdentityResponse
+  );
+}
+
+static int
+probe (BrailleDisplay *brl, MD_Packet *response) {
+  if (brl->data->io) {
+    if (brl->data->io->bauds) {
+      const unsigned int *baud = brl->data->io->bauds;
+
+      if (*baud) {
+        do {
+          SerialParameters parameters;
+          gioInitializeSerialParameters(&parameters);
+
+          parameters.baud = *baud;
+          logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "probing at %u baud", parameters.baud);
+
+          if (!gioReconfigureResource(brl->gioEndpoint, &parameters)) break;
+          if (probeDevice(brl, response)) return 1;
+        } while (*++baud);
+
+        return 0;
+      }
+    }
+  }
+
+  return probeDevice(brl, response);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->io = NULL;
+
+    if (connectResource(brl, device)) {
+      MD_Packet response;
+
+      if (probe(brl, &response)) {
+        logMessage(LOG_INFO,
+          "MDV Model Description:"
+          " Version:%u.%u Text:%u Status:%u Dots:%u Routing:%s",
+          response.fields.data.identity.majorVersion,
+          response.fields.data.identity.minorVersion,
+          response.fields.data.identity.textCellCount,
+          response.fields.data.identity.statusCellCount,
+          response.fields.data.identity.dotsPerCell,
+          (response.fields.data.identity.haveRoutingKeys? "yes": "no")
+        );
+
+        brl->textColumns = response.fields.data.identity.textCellCount;
+        brl->statusColumns = response.fields.data.identity.statusCellCount;
+        setBrailleKeyTable(brl, getKeyTableDefinition(brl));
+
+        brl->data->shiftPressed = 0;
+        brl->data->text.rewrite = 1;
+        brl->data->status.rewrite = 1;
+
+        MAKE_OUTPUT_TABLE(0X08, 0X04, 0X02, 0X80, 0X40, 0X20, 0X01, 0X10);
+        return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  if (cellsHaveChanged(brl->data->status.cells, cells, brl->statusColumns, NULL, NULL, &brl->data->status.rewrite)) {
+    brl->data->text.rewrite = 1;
+  }
+
+  return 1;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->text.rewrite)) {
+    unsigned char cells[brl->statusColumns + brl->textColumns];
+    unsigned char *cell = cells;
+
+    cell = mempcpy(cell, brl->data->status.cells, brl->statusColumns);
+    cell = translateOutputCells(cell, brl->data->text.cells, brl->textColumns);
+
+    if (!writePacket(brl, MD_CODE_WRITE_ALL, cells, (cell - cells))) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  MD_Packet packet;
+  size_t size;
+
+  while ((size = readPacket(brl, &packet))) {
+    switch (packet.fields.code) {
+      case MD_CODE_NAVIGATION_KEY: {
+        unsigned char key = packet.fields.data.navigationKey.key;
+
+        switch (key) {
+          case MD_NAV_SHIFT_PRESS:
+            brl->data->shiftPressed = 1;
+            goto doShiftEvent;
+
+          case MD_NAV_SHIFT_RELEASE:
+            brl->data->shiftPressed = 0;
+            goto doShiftEvent;
+
+          doShiftEvent:
+            enqueueKeyEvent(brl, MD_GRP_NAV, MD_NAV_SHIFT, brl->data->shiftPressed);
+            break;
+
+          default: {
+            int shiftPressed = ((key & MD_NAV_SHIFT) != 0) && !brl->data->shiftPressed;
+            int longPressed = (key & MD_NAV_LONG) != 0;
+
+            key &= MD_NAV_MASK_KEY;
+            MD_KeyGroup group = MD_GRP_NAV;
+
+            if (shiftPressed) enqueueKeyEvent(brl, group, MD_NAV_SHIFT, 1);
+            if (longPressed) enqueueKeyEvent(brl, group, MD_NAV_LONG, 1);
+            enqueueKey(brl, group, key);
+            if (longPressed) enqueueKeyEvent(brl, group, MD_NAV_LONG, 0);
+            if (shiftPressed) enqueueKeyEvent(brl, group, MD_NAV_SHIFT, 0);
+
+            break;
+          }
+        }
+
+        break;
+      }
+
+      case MD_CODE_BRAILLE_KEY: {
+        MD_KeyGroup group = MD_GRP_BRL;
+        unsigned char spacePressed = packet.fields.data.brailleKey.isChord != 0;
+
+        if (spacePressed) enqueueKeyEvent(brl, group, MD_BRL_SPACE, 1);
+        enqueueKeys(brl, packet.fields.data.brailleKey.dots, group, 0);
+        if (spacePressed) enqueueKeyEvent(brl, group, MD_BRL_SPACE, 0);
+
+        break;
+      }
+
+      {
+        unsigned char key;
+        int press;
+
+      case MD_CODE_ROUTING_PRESS:
+        key = packet.fields.data.routingPress.key;
+        press = 1;
+        goto doRoutingKey;
+
+      case MD_CODE_ROUTING_RELEASE:
+        key = packet.fields.data.routingRelease.key;
+        press = 0;
+        goto doRoutingKey;
+
+      doRoutingKey:
+        key &= ~MD_ROUTING_SHIFT;
+
+        if (key >= MD_ROUTING_FIRST) {
+          key -= MD_ROUTING_FIRST;
+          MD_KeyGroup group;
+
+          if (key < brl->statusColumns) {
+            group = MD_GRP_SK;
+          } else if ((key -= brl->statusColumns) < brl->textColumns) {
+            group = MD_GRP_RK;
+          } else {
+            break;
+          }
+
+          enqueueKeyEvent(brl, group, key, press);
+        }
+
+        break;
+      }
+
+      case MD_CODE_ACKNOWLEDGE:
+        break;
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet.bytes, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/MDV/brldefs-md.h b/Drivers/Braille/MDV/brldefs-md.h
new file mode 100644
index 0000000..198bf28
--- /dev/null
+++ b/Drivers/Braille/MDV/brldefs-md.h
@@ -0,0 +1,134 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MD_BRLDEFS
+#define BRLTTY_INCLUDED_MD_BRLDEFS
+
+typedef union {
+  unsigned char bytes[1];
+
+  struct {
+    unsigned char soh;
+    unsigned char stx;
+    unsigned char code;
+    unsigned char length;
+    unsigned char etx;
+
+    union {
+      unsigned char bytes[0XFF];
+
+      struct {
+        unsigned char key;
+      } navigationKey;
+
+      struct {
+        unsigned char key;
+      } routingPress;
+
+      struct {
+        unsigned char key;
+      } routingRelease;
+
+      struct {
+        unsigned char isChord;
+        unsigned char dots;
+        unsigned char ascii;
+      } brailleKey;
+
+      struct {
+        unsigned char textCellCount;
+        unsigned char statusCellCount;
+        unsigned char dotsPerCell;
+        unsigned char haveRoutingKeys;
+        unsigned char majorVersion;
+        unsigned char minorVersion;
+      } identity;
+    } data;
+
+    /* Declare the checksum bytes here to ensure that the size is correct
+     * even though the actual checksum is just after the last data byte.
+     */
+    unsigned char checksum[2];
+  } PACKED fields;
+} MD_Packet;
+
+typedef enum {
+  MD_CODE_WRITE_ALL       =   0,
+  MD_CODE_WRITE_STATUS    =   1,
+  MD_CODE_WRITE_TEXT      =   2,
+  MD_CODE_WRITE_LCD       =   5,
+  MD_CODE_NAVIGATION_KEY  =  16,
+  MD_CODE_ROUTING_PRESS   =  17,
+  MD_CODE_ROUTING_RELEASE =  18,
+  MD_CODE_BRAILLE_KEY     =  21,
+  MD_CODE_IDENTIFY        =  36,
+  MD_CODE_IDENTITY        =  37,
+  MD_CODE_ACKNOWLEDGE     = 127,
+} MD_PacketCode;
+
+typedef enum {
+  MD_NAV_F1            = 0X01,
+  MD_NAV_F2            = 0X02,
+  MD_NAV_F3            = 0X03,
+  MD_NAV_F4            = 0X04,
+  MD_NAV_F5            = 0X05,
+  MD_NAV_F6            = 0X06,
+  MD_NAV_F7            = 0X07,
+  MD_NAV_F8            = 0X08,
+  MD_NAV_F9            = 0X09,
+  MD_NAV_F10           = 0X0A,
+  MD_NAV_LEFT          = 0X0B,
+  MD_NAV_UP            = 0X0C,
+  MD_NAV_RIGHT         = 0X0D,
+  MD_NAV_DOWN          = 0X0E,
+  MD_NAV_MASK_KEY      = 0X0F,
+
+  MD_NAV_SHIFT         = 0X10,
+  MD_NAV_LONG          = 0X20,
+  MD_NAV_MASK_MOD      = 0X30,
+
+  MD_NAV_SHIFT_PRESS   = 0X3F,
+  MD_NAV_SHIFT_RELEASE = 0X40,
+} MD_NavigationKey;
+
+typedef enum {
+  MD_BRL_DOT1  = 0,
+  MD_BRL_DOT2  = 1,
+  MD_BRL_DOT3  = 2,
+  MD_BRL_DOT4  = 3,
+  MD_BRL_DOT5  = 4,
+  MD_BRL_DOT6  = 5,
+  MD_BRL_DOT7  = 6,
+  MD_BRL_DOT8  = 7,
+  MD_BRL_SPACE = 8,
+} MD_BrailleKey;
+
+typedef enum {
+  MD_ROUTING_FIRST = 0X01,
+  MD_ROUTING_MASK  = 0X7F,
+  MD_ROUTING_SHIFT = 0X80,
+} MD_RoutingKey;
+
+typedef enum {
+  MD_GRP_NAV,
+  MD_GRP_BRL,
+  MD_GRP_RK,
+  MD_GRP_SK,
+} MD_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_MD_BRLDEFS */ 
diff --git a/Drivers/Braille/Metec/Makefile.in b/Drivers/Braille/Metec/Makefile.in
new file mode 100644
index 0000000..23f6a59
--- /dev/null
+++ b/Drivers/Braille/Metec/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mt
+DRIVER_NAME = Metec
+DRIVER_USAGE = BD-40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Metec/braille.c b/Drivers/Braille/Metec/braille.c
new file mode 100644
index 0000000..8cd5d5e
--- /dev/null
+++ b/Drivers/Braille/Metec/braille.c
@@ -0,0 +1,495 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-mt.h"
+
+BEGIN_KEY_NAME_TABLE(3keys)
+  KEY_NAME_ENTRY(MT_KEY_LeftUp, "Up"),
+  KEY_NAME_ENTRY(MT_KEY_LeftSelect, "Select"),
+  KEY_NAME_ENTRY(MT_KEY_LeftDown, "Down"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(6keys)
+  KEY_NAME_ENTRY(MT_KEY_LeftUp, "LeftUp"),
+  KEY_NAME_ENTRY(MT_KEY_LeftSelect, "LeftSelect"),
+  KEY_NAME_ENTRY(MT_KEY_LeftDown, "LeftDown"),
+
+  KEY_NAME_ENTRY(MT_KEY_RightUp, "RightUp"),
+  KEY_NAME_ENTRY(MT_KEY_RightSelect, "RightSelect"),
+  KEY_NAME_ENTRY(MT_KEY_RightDown, "RightDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(4keys)
+  KEY_NAME_ENTRY(MT_KEY_LeftUp, "LeftUp"),
+  KEY_NAME_ENTRY(MT_KEY_LeftDown, "LeftDown"),
+
+  KEY_NAME_ENTRY(MT_KEY_RightUp, "RightUp"),
+  KEY_NAME_ENTRY(MT_KEY_RightDown, "RightDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing1)
+  KEY_GROUP_ENTRY(MT_GRP_RoutingKeys1, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status1)
+  KEY_GROUP_ENTRY(MT_GRP_StatusKeys1, "StatusKey"),
+END_KEY_NAME_TABLE
+
+/*
+BEGIN_KEY_NAME_TABLE(front)
+  KEY_NAME_ENTRY(MT_KEY_CursorLeft, "CursorLeft"),
+  KEY_NAME_ENTRY(MT_KEY_CursorUp, "CursorUp"),
+  KEY_NAME_ENTRY(MT_KEY_CursorRight, "CursorRight"),
+  KEY_NAME_ENTRY(MT_KEY_CursorDown, "CursorDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing2)
+  KEY_GROUP_ENTRY(MT_GRP_RoutingKeys2, "RoutingKey2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(status2)
+  KEY_GROUP_ENTRY(MT_GRP_StatusKeys2, "StatusKey2"),
+END_KEY_NAME_TABLE
+*/
+
+BEGIN_KEY_NAME_TABLES(bd1_3)
+  KEY_NAME_TABLE(3keys),
+  KEY_NAME_TABLE(routing1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bd1_3s)
+  KEY_NAME_TABLE(3keys),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(status1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bd1_6)
+  KEY_NAME_TABLE(6keys),
+  KEY_NAME_TABLE(routing1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bd1_6s)
+  KEY_NAME_TABLE(6keys),
+  KEY_NAME_TABLE(routing1),
+  KEY_NAME_TABLE(status1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bd2)
+  KEY_NAME_TABLE(4keys),
+  KEY_NAME_TABLE(routing1),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(bd1_3)
+DEFINE_KEY_TABLE(bd1_3s)
+DEFINE_KEY_TABLE(bd1_6)
+DEFINE_KEY_TABLE(bd1_6s)
+DEFINE_KEY_TABLE(bd2)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(bd1_3),
+  &KEY_TABLE_DEFINITION(bd1_3s),
+  &KEY_TABLE_DEFINITION(bd1_6),
+  &KEY_TABLE_DEFINITION(bd1_6s),
+  &KEY_TABLE_DEFINITION(bd2),
+END_KEY_TABLE_LIST
+
+#define MT_IDENTITY_PACKET_SIZE 0X400
+#define MT_STATUS_PACKET_SIZE 8
+
+#define MT_ROUTING_KEYS_SECONDARY 100
+#define MT_ROUTING_KEYS_NONE 0XFF
+
+#define MT_MODULE_SIZE 8
+#define MT_MODULES_MAXIMUM 10
+#define MT_CELLS_MAXIMUM (MT_MODULES_MAXIMUM * MT_MODULE_SIZE)
+
+typedef struct {
+  int (*beginProtocol) (BrailleDisplay *brl);
+  void (*endProtocol) (BrailleDisplay *brl);
+
+  int (*setHighVoltage) (BrailleDisplay *brl, int on);
+  int (*getDeviceIdentity) (BrailleDisplay *brl);
+
+  int (*handleInput) (BrailleDisplay *brl);
+} ProtocolOperations;
+
+struct BrailleDataStruct {
+  const ProtocolOperations *protocol;
+
+  unsigned char oldCells[MT_CELLS_MAXIMUM];
+  unsigned char newCells[MT_CELLS_MAXIMUM];
+
+  unsigned char cellCount;
+  unsigned char textCount;
+  unsigned char statusCount;
+
+  unsigned char moduleCount;
+  unsigned char writeModule[MT_MODULES_MAXIMUM];
+
+  KeyNumberSet allNavigationKeys;
+  KeyNumberSet pressedNavigationKeys;
+  unsigned char routingKey;
+
+  union {
+    struct {
+      AsyncHandle statusAlarm;
+    } usb;
+  } proto;
+};
+
+static void
+setCellCount (BrailleDisplay *brl, unsigned char count) {
+  brl->data->moduleCount = (brl->data->cellCount = count) / MT_MODULE_SIZE;
+
+  switch (count) {
+    case 22:
+    case 42:
+      brl->data->statusCount = 2;
+      break;
+
+    default:
+      brl->data->statusCount = 0;
+      break;
+  }
+
+  brl->data->textCount = brl->data->cellCount - brl->data->statusCount;
+  brl->textColumns = brl->data->textCount;
+  brl->statusColumns = brl->data->statusCount;
+}
+
+static void
+handleNavigationKeys (BrailleDisplay *brl, KeyNumberSet keys) {
+  keys &= brl->data->allNavigationKeys;
+  enqueueUpdatedKeys(brl, keys, &brl->data->pressedNavigationKeys, MT_GRP_NavigationKeys, 0);
+}
+
+static void
+handleRoutingKeyEvent (BrailleDisplay *brl, unsigned char key, int press) {
+  if (key != MT_ROUTING_KEYS_NONE) {
+    KeyGroup group;
+
+    {
+      KeyGroup routing;
+      KeyGroup status;
+
+      if (key < MT_ROUTING_KEYS_SECONDARY) {
+        routing = MT_GRP_RoutingKeys1;
+        status = MT_GRP_StatusKeys1;
+      } else {
+        key -= MT_ROUTING_KEYS_SECONDARY;
+        routing = MT_GRP_RoutingKeys2;
+        status = MT_GRP_StatusKeys2;
+      }
+
+      if (key < brl->data->statusCount) {
+        group = status;
+      } else if ((key -= brl->data->statusCount) < brl->data->textCount) {
+        group = routing;
+      } else {
+        return;
+      }
+    }
+
+    enqueueKeyEvent(brl, group, key, press);
+  }
+}
+
+static void
+handleRoutingKey (BrailleDisplay *brl, unsigned char key) {
+  if (key != brl->data->routingKey) {
+    handleRoutingKeyEvent(brl, brl->data->routingKey, 0);
+    handleRoutingKeyEvent(brl, key, 1);
+    brl->data->routingKey = key;
+  }
+}
+
+#include "io_usb.h"
+
+#define MT_USB_CONTROL_RECIPIENT UsbControlRecipient_Device
+#define MT_USB_CONTROL_TYPE UsbControlType_Vendor
+
+static int setUsbStatusAlarm (BrailleDisplay *brl);
+
+static ssize_t
+tellUsbDevice (
+  BrailleDisplay *brl, unsigned char request,
+  const void *data, size_t length
+) {
+  return gioTellResource(brl->gioEndpoint,
+                         MT_USB_CONTROL_RECIPIENT, MT_USB_CONTROL_TYPE,
+                         request, 0, 0, data, length);
+}
+
+static ssize_t
+askUsbDevice (
+  BrailleDisplay *brl, unsigned char request,
+  void *buffer, size_t size
+) {
+  return gioAskResource(brl->gioEndpoint,
+                        MT_USB_CONTROL_RECIPIENT, MT_USB_CONTROL_TYPE,
+                        request, 0, 0, buffer, size);
+}
+
+static ssize_t
+getUsbStatusPacket (BrailleDisplay *brl, unsigned char *packet) {
+  return askUsbDevice(brl, 0X80, packet, MT_STATUS_PACKET_SIZE);
+}
+
+ASYNC_ALARM_CALLBACK(handleUsbStatusAlarm) {
+  BrailleDisplay *brl = parameters->data;
+  unsigned char packet[MT_STATUS_PACKET_SIZE];
+
+  asyncDiscardHandle(brl->data->proto.usb.statusAlarm);
+  brl->data->proto.usb.statusAlarm = NULL;
+
+  memset(packet, 0, sizeof(packet));
+
+  if (getUsbStatusPacket(brl, packet))  {
+    logInputPacket(packet, sizeof(packet));
+    handleRoutingKey(brl, packet[0]);
+    handleNavigationKeys(brl, (packet[2] | (packet[3] << 8)));
+    setUsbStatusAlarm(brl);
+  } else {
+    enqueueCommand(BRL_CMD_RESTARTBRL);
+  }
+}
+
+static int
+setUsbStatusAlarm (BrailleDisplay *brl) {
+  return asyncNewRelativeAlarm(&brl->data->proto.usb.statusAlarm,
+                         BRAILLE_DRIVER_INPUT_POLL_INTERVAL,
+                         handleUsbStatusAlarm, brl);
+}
+
+static int
+beginUsbProtocol (BrailleDisplay *brl) {
+  brl->data->proto.usb.statusAlarm = NULL;
+  setUsbStatusAlarm(brl);
+
+  return 1;
+}
+
+static void
+endUsbProtocol (BrailleDisplay *brl) {
+  if (brl->data->proto.usb.statusAlarm) {
+    asyncCancelRequest(brl->data->proto.usb.statusAlarm);
+    brl->data->proto.usb.statusAlarm = NULL;
+  }
+}
+
+static int
+setUsbHighVoltage (BrailleDisplay *brl, int on) {
+  const unsigned char data[] = {
+    (on? 0XEF: 0X00),
+    0, 0, 0, 0, 0, 0, 0
+  };
+
+  return tellUsbDevice(brl, 0X01, data, sizeof(data)) != -1;
+}
+
+static int
+getUsbDeviceIdentity (BrailleDisplay *brl) {
+  UsbChannel *channel = gioGetResourceObject(brl->gioEndpoint);
+  UsbDevice *device = channel->device;
+  unsigned int counter = 2;
+
+  do {
+    static const unsigned char data[] = {0};
+
+    if (tellUsbDevice(brl, 0X04, data, sizeof(data)) != -1) {
+      unsigned char identity[MT_IDENTITY_PACKET_SIZE];
+      ssize_t result = usbReadEndpoint(device, 1, identity, sizeof(identity), 1000);
+
+      if (result != -1) return 1;
+    }
+  } while (--counter);
+
+  return 0;
+}
+
+static int
+handleUsbInput (BrailleDisplay *brl) {
+  return 1;
+}
+
+static const ProtocolOperations usbProtocolOperations = {
+  .beginProtocol = beginUsbProtocol,
+  .endProtocol = endUsbProtocol,
+
+  .setHighVoltage = setUsbHighVoltage,
+  .getDeviceIdentity = getUsbDeviceIdentity,
+
+  .handleInput = handleUsbInput
+};
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0452, .product=0X0100,
+      .configuration=1, .interface=0, .alternative=0,
+      .disableEndpointReset=1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbProtocolOperations;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->protocol = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+disconnectResource (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      if (brl->data->protocol->setHighVoltage(brl, 1)) {
+        unsigned char statusPacket[MT_STATUS_PACKET_SIZE];
+
+        brl->data->protocol->getDeviceIdentity(brl);
+
+        if (getUsbStatusPacket(brl, statusPacket)) {
+          setCellCount(brl, statusPacket[1]);
+
+          {
+            unsigned int moduleNumber;
+
+            for (moduleNumber=0; moduleNumber<brl->data->moduleCount; moduleNumber+=1) {
+              brl->data->writeModule[moduleNumber] = 1;
+            }
+          }
+
+          MAKE_OUTPUT_TABLE(0X80, 0X40, 0X20, 0X10, 0X08, 0X04, 0X02, 0X01);
+
+          {
+            const KeyTableDefinition *ktd;
+
+            if (statusPacket[2] & 0X80) {
+              ktd = brl->data->statusCount? &KEY_TABLE_DEFINITION(bd1_3s):
+                                            &KEY_TABLE_DEFINITION(bd1_3);
+            } else {
+              ktd = brl->data->statusCount? &KEY_TABLE_DEFINITION(bd1_6s):
+                                            &KEY_TABLE_DEFINITION(bd1_6);
+            }
+
+            brl->data->allNavigationKeys = makeKeyNumberSet(ktd->names, MT_GRP_NavigationKeys);
+            setBrailleKeyTable(brl, ktd);
+          }
+
+          brl->data->pressedNavigationKeys = 0;
+          brl->data->routingKey = MT_ROUTING_KEYS_NONE;
+
+          if (brl->data->protocol->beginProtocol(brl)) return 1;
+        }
+
+        brl->data->protocol->setHighVoltage(brl, 0);
+      }
+
+      disconnectResource(brl);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+  
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  brl->data->protocol->endProtocol(brl);
+  brl->data->protocol->setHighVoltage(brl, 0);
+  disconnectResource(brl);
+  free(brl->data);
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  const unsigned char *source = brl->data->newCells;
+  unsigned char *target = brl->data->oldCells;
+  unsigned int moduleNumber;
+
+  memcpy(&brl->data->newCells[brl->data->statusCount], brl->buffer, brl->data->textCount);
+
+  for (moduleNumber=0; moduleNumber<brl->data->moduleCount; moduleNumber+=1) {
+    if (cellsHaveChanged(target, source, MT_MODULE_SIZE, NULL, NULL, &brl->data->writeModule[moduleNumber])) {
+      unsigned char cells[MT_MODULE_SIZE];
+
+      translateOutputCells(cells, source, MT_MODULE_SIZE);
+      if (tellUsbDevice(brl, 0X0A+moduleNumber, cells, MT_MODULE_SIZE) == -1) return 0;
+    }
+
+    source += MT_MODULE_SIZE;
+    target += MT_MODULE_SIZE;
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  const unsigned int count = brl->data->statusCount;
+
+  if (count) {
+    unsigned char *target = &brl->data->newCells[0];
+    const unsigned char *end = target + count;
+
+    while (target < end) {
+      unsigned char cell = *cells++;
+
+      if (!cell) break;
+      *target++ = cell;
+    }
+
+    while (target < end) *target++ = 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  return brl->data->protocol->handleInput(brl)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Metec/brldefs-mt.h b/Drivers/Braille/Metec/brldefs-mt.h
new file mode 100644
index 0000000..0da7f02
--- /dev/null
+++ b/Drivers/Braille/Metec/brldefs-mt.h
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MT_BRLDEFS
+#define BRLTTY_INCLUDED_MT_BRLDEFS
+
+typedef enum {
+  /* status[2] */
+  MT_KEY_LeftUp      =  6,
+  MT_KEY_LeftSelect  =  4,
+  MT_KEY_LeftDown    =  2,
+  MT_KEY_RightUp     =  3,
+  MT_KEY_RightSelect =  1,
+  MT_KEY_RightDown   =  0,
+
+  /* status[3] (front keys from left to right) */
+  MT_KEY_CursorLeft  = 10,
+  MT_KEY_CursorDown  = 14,
+  MT_KEY_CursorUp    = 11,
+  MT_KEY_CursorRight = 12
+} MT_NavigationKey;
+
+typedef enum {
+  MT_GRP_NavigationKeys = 0,
+
+  MT_GRP_RoutingKeys1,
+  MT_GRP_StatusKeys1,
+
+  MT_GRP_RoutingKeys2,
+  MT_GRP_StatusKeys2
+} MT_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_MT_BRLDEFS */ 
diff --git a/Drivers/Braille/MiniBraille/Makefile.in b/Drivers/Braille/MiniBraille/Makefile.in
new file mode 100644
index 0000000..0faa2b6
--- /dev/null
+++ b/Drivers/Braille/MiniBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mn
+DRIVER_NAME = MiniBraille
+DRIVER_USAGE = 20
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Brailcom o.p.s. <technik@brailcom.cz>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/MiniBraille/README b/Drivers/Braille/MiniBraille/README
new file mode 100644
index 0000000..fa85c97
--- /dev/null
+++ b/Drivers/Braille/MiniBraille/README
@@ -0,0 +1,18 @@
+Subject
+=======
+Brltty driver for Tieman MiniBraille (the 20-cell braille display)
+
+Authors
+=======
+Programmers at Brailcom o.p.s, Czech republic. See http://www.brailcom.cz/
+  for more info.
+Contact email: <technik@brailcom.cz>
+
+Credits
+=======
+Special thanks to Tieman, which gives me programming info.
+
+Status
+======
+Unmaintained, because I have no more this HW.
+
diff --git a/Drivers/Braille/MiniBraille/braille.c b/Drivers/Braille/MiniBraille/braille.c
new file mode 100644
index 0000000..10d517a
--- /dev/null
+++ b/Drivers/Braille/MiniBraille/braille.c
@@ -0,0 +1,474 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* MiniBraille/braille.c - Braille display library
+ * the following Tieman B.V. braille terminals are supported
+ *
+ * - MiniBraille v 1.5 (20 braille cells + 2 status)
+ *   (probably other versions too)
+ *
+ * Brailcom o.p.s. <technik@brailcom.cz>
+ *
+ * Thanks to Tieman B.V., which gives me protocol information. Author.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "log.h"
+#include "timing.h"
+#include "ascii.h"
+#include "message.h"
+
+#define BRL_STATUS_FIELDS sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+
+#include "io_serial.h"
+static SerialDevice *serialDevice = NULL;
+static const unsigned int serialBaud = 9600;
+static unsigned int serialCharactersPerSecond;
+
+#define KEY_F1     0x01
+#define KEY_F2     0x02
+#define KEY_LEFT   0x04
+#define KEY_UP     0x08
+#define KEY_CENTER 0x10
+#define KEY_DOWN   0x20
+#define KEY_RIGHT  0x40
+
+#define POST_COMMAND_DELAY 30
+
+static unsigned char textCells[20];
+static unsigned char statusCells[2];
+static int refreshNeeded;
+
+static int
+writeData (BrailleDisplay *brl, const unsigned char *bytes, int count) {
+  ssize_t result = serialWriteData(serialDevice, bytes, count);
+
+  if (result == -1) {
+    logSystemError("write");
+    return 0;
+  }
+
+  drainBrailleOutput(brl, 0);
+  brl->writeDelay += (result * 1000 / serialCharactersPerSecond) + POST_COMMAND_DELAY;
+  return 1;
+}
+
+static int
+writeCells  (BrailleDisplay *brl) {
+  static const unsigned char beginSequence[] = {ASCII_ESC, 'Z', '1'};
+  static const unsigned char endSequence[] = {ASCII_CR};
+
+  unsigned char buffer[sizeof(beginSequence) + sizeof(statusCells) + sizeof(textCells) + sizeof(endSequence)];
+  unsigned char *byte = buffer;
+
+  byte = mempcpy(byte, beginSequence, sizeof(beginSequence));
+  byte = translateOutputCells(byte, statusCells, sizeof(statusCells));
+  byte = translateOutputCells(byte, textCells, sizeof(textCells));
+  byte = mempcpy(byte, endSequence, sizeof(endSequence));
+
+  return writeData(brl, buffer, byte-buffer);
+}
+
+static void
+updateCells (unsigned char *target, const unsigned char *source, size_t count) {
+  if (cellsHaveChanged(target, source, count, NULL, NULL, NULL)) {
+    refreshNeeded = 1;
+  }
+}
+
+static void
+clearCells (unsigned char *cells, size_t count) {
+  memset(cells, 0, count);
+  refreshNeeded = 1;
+}
+
+static int
+beep (BrailleDisplay *brl) {
+  static const unsigned char sequence[] = {ASCII_ESC, 'B', ASCII_CR};
+  return writeData(brl, sequence, sizeof(sequence));
+}
+
+static int
+inputFunction_showTime (BrailleDisplay *brl) {
+  time_t clock = time(NULL);
+  const struct tm *local = localtime(&clock);
+  char text[sizeof(textCells) + 1];
+  strftime(text, sizeof(text), "%Y-%m-%d %H:%M:%S", local);
+  message(NULL, text, 0);
+  return BRL_CMD_NOOP;
+}
+
+static unsigned char cursorDots;
+static unsigned char cursorOffset;
+
+static void
+putCursor (BrailleDisplay *brl) {
+  brl->buffer[cursorOffset] = cursorDots;
+}
+
+static int
+inputFunction_incrementCursor (BrailleDisplay *brl) {
+  if (++cursorOffset < sizeof(textCells)) return BRL_CMD_NOOP;
+
+  cursorOffset = 0;
+  return BRL_CMD_FWINRT;
+}
+
+static int
+inputFunction_decrementCursor (BrailleDisplay *brl) {
+  if (cursorOffset) {
+    --cursorOffset;
+    return BRL_CMD_NOOP;
+  }
+
+  cursorOffset = sizeof(textCells) - 1;
+  return BRL_CMD_FWINLT;
+}
+
+typedef struct InputModeStruct InputMode;
+
+typedef enum {
+  IBT_unbound = 0, /* automatically set if not explicitly initialized */
+  IBT_command,
+  IBT_block,
+  IBT_function,
+  IBT_submode
+} InputBindingType;
+
+typedef union {
+  int command;
+  int block;
+  int (*function) (BrailleDisplay *brl);
+  const InputMode *submode;
+} InputBindingValue;
+
+typedef struct {
+  InputBindingType type;
+  InputBindingValue value;
+} InputBinding;
+
+struct InputModeStruct {
+  InputBinding keyF1, keyF2, keyLeft, keyUp, keyCenter, keyDown, keyRight;
+
+  unsigned temporary:1;
+  void (*modifyWindow) (BrailleDisplay *brl);
+  const char *name;
+};
+
+#define BIND(k,t,v) .key##k = {.type = IBT_##t, .value.t = (v)}
+#define BIND_COMMAND(k,c) BIND(k, command, BRL_CMD_##c)
+#define BIND_BLOCK(k,b) BIND(k, block, BRL_CMD_BLK(b))
+#define BIND_FUNCTION(k,f) BIND(k, function, inputFunction_##f)
+#define BIND_SUBMODE(k,m) BIND(k, submode, &inputMode_##m)
+
+static const InputMode inputMode_char_f1 = {
+  BIND_BLOCK(F1, SETLEFT),
+  BIND_BLOCK(F2, DESCCHAR),
+  BIND_BLOCK(Left, CLIP_ADD),
+  BIND_BLOCK(Up, CLIP_NEW),
+  BIND_BLOCK(Center, ROUTE),
+  BIND_BLOCK(Down, COPY_RECT),
+  BIND_BLOCK(Right, COPY_LINE),
+
+  .temporary = 1,
+  .name = "Char-F1"
+};
+
+static const InputMode inputMode_f1_f1 = {
+  BIND_COMMAND(F1, HELP),
+  BIND_COMMAND(F2, LEARN),
+  BIND_COMMAND(Left, INFO),
+  BIND_FUNCTION(Right, showTime),
+  BIND_COMMAND(Up, PREFLOAD),
+  BIND_COMMAND(Down, PREFMENU),
+  BIND_COMMAND(Center, PREFSAVE),
+
+  .temporary = 1,
+  .name = "F1-F1"
+};
+
+static const InputMode inputMode_f1_f2 = {
+  BIND_COMMAND(F1, FREEZE),
+  BIND_COMMAND(F2, DISPMD),
+  BIND_COMMAND(Left, ATTRVIS),
+  BIND_COMMAND(Right, CSRVIS),
+  BIND_COMMAND(Up, SKPBLNKWINS),
+  BIND_COMMAND(Down, SKPIDLNS),
+  BIND_COMMAND(Center, SIXDOTS),
+
+  .temporary = 1,
+  .name = "F1-F2"
+};
+
+static const InputMode inputMode_f1_left = {
+
+  .temporary = 1,
+  .name = "F1-Left"
+};
+
+static const InputMode inputMode_f1_right = {
+  BIND_COMMAND(F2, AUTOSPEAK),
+  BIND_COMMAND(Left, SAY_ABOVE),
+  BIND_COMMAND(Right, SAY_BELOW),
+  BIND_COMMAND(Up, MUTE),
+  BIND_COMMAND(Down, SAY_LINE),
+  BIND_COMMAND(Center, SPKHOME),
+
+  .temporary = 1,
+  .name = "F1-Right"
+};
+
+static const InputMode inputMode_f1_up = {
+  BIND_COMMAND(F1, PRSEARCH),
+  BIND_COMMAND(F2, NXSEARCH),
+  BIND_COMMAND(Left, ATTRUP),
+  BIND_COMMAND(Right, ATTRDN),
+  BIND_COMMAND(Up, PRPGRPH),
+  BIND_COMMAND(Down, NXPGRPH),
+  BIND_COMMAND(Center, CSRJMP_VERT),
+
+  .temporary = 1,
+  .name = "F1-Up"
+};
+
+static const InputMode inputMode_f1_down = {
+  BIND_COMMAND(F1, PRPROMPT),
+  BIND_COMMAND(F2, NXPROMPT),
+  BIND_COMMAND(Left, FWINLTSKIP),
+  BIND_COMMAND(Right, FWINRTSKIP),
+  BIND_COMMAND(Up, PRDIFLN),
+  BIND_COMMAND(Down, NXDIFLN),
+  BIND_COMMAND(Center, PASTE),
+
+  .temporary = 1,
+  .name = "F1-Down"
+};
+
+static const InputMode inputMode_f1_center = {
+  BIND_SUBMODE(F1, char_f1),
+  BIND_FUNCTION(Left, decrementCursor),
+  BIND_FUNCTION(Right, incrementCursor),
+  BIND_COMMAND(Up, LNUP),
+  BIND_COMMAND(Down, LNDN),
+
+  .temporary = 0,
+  .modifyWindow = putCursor,
+  .name = "F1-Center"
+};
+
+static const InputMode inputMode_f1 = {
+  BIND_SUBMODE(F1, f1_f1),
+  BIND_SUBMODE(F2, f1_f2),
+  BIND_SUBMODE(Left, f1_left),
+  BIND_SUBMODE(Right, f1_right),
+  BIND_SUBMODE(Up, f1_up),
+  BIND_SUBMODE(Down, f1_down),
+  BIND_SUBMODE(Center, f1_center),
+
+  .temporary = 1,
+  .name = "F1"
+};
+
+static const InputMode inputMode_f2 = {
+  BIND_COMMAND(F1, TOP_LEFT),
+  BIND_COMMAND(F2, BOT_LEFT),
+  BIND_COMMAND(Left, LNBEG),
+  BIND_COMMAND(Right, LNEND),
+  BIND_COMMAND(Up, TOP),
+  BIND_COMMAND(Down, BOT),
+  BIND_COMMAND(Center, CSRTRK),
+
+  .temporary = 1,
+  .name = "F2"
+};
+
+static const InputMode inputMode_basic = {
+  BIND_SUBMODE(F1, f1),
+  BIND_SUBMODE(F2, f2),
+  BIND_COMMAND(Left, FWINLT),
+  BIND_COMMAND(Right, FWINRT),
+  BIND_COMMAND(Up, LNUP),
+  BIND_COMMAND(Down, LNDN),
+  BIND_COMMAND(Center, RETURN),
+
+  .temporary = 0,
+  .name = "Basic"
+};
+
+static const InputMode *inputMode;
+static TimePeriod inputPeriod;
+
+static void
+setInputMode (const InputMode *mode) {
+  if (mode->temporary) {
+    char title[sizeof(textCells) + 1];
+    snprintf(title, sizeof(title), "%s Mode", mode->name);
+    message(NULL, title, MSG_NODELAY|MSG_SILENT);
+  }
+
+  inputMode = mode;
+  startTimePeriod(&inputPeriod, 3000);
+}
+
+static void
+resetInputMode (void) {
+  setInputMode(&inputMode_basic);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if ((serialDevice = serialOpenDevice(device))) {
+    if (serialRestartDevice(serialDevice, serialBaud)) {
+      serialCharactersPerSecond = serialBaud / serialGetCharacterBits(serialDevice);
+
+      /* hm, how to switch to 38400 ? 
+      static const unsigned char sequence[] = {ASCII_ESC, 'V', ASCII_CR};
+      writeData(brl, sequence, sizeof(sequence));
+      serialDiscardInput(serialDevice);
+      serialSetBaud(serialDevice, 38400);
+      */
+
+      MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X80, 0X40, 0X20, 0X08, 0X10);
+      clearCells(textCells,  sizeof(textCells));
+      clearCells(statusCells,  sizeof(statusCells));
+      resetInputMode();
+
+      cursorDots = 0XFF;
+      cursorOffset = sizeof(textCells) / 2;
+
+      brl->textColumns = sizeof(textCells);
+      brl->textRows = 1;
+      brl->statusColumns = sizeof(statusCells);
+      brl->statusRows = 1;
+
+      beep(brl);
+      return 1;
+    }
+
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (inputMode->modifyWindow) inputMode->modifyWindow(brl);
+  updateCells(textCells, brl->buffer, sizeof(textCells));
+  if (refreshNeeded && !inputMode->temporary) {
+    writeCells(brl);
+    refreshNeeded = 0;
+  }
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *s) {
+  updateCells(statusCells, s, sizeof(statusCells));
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char byte;
+  const InputMode *mode;
+  const InputBinding *binding;
+
+  {
+    int result = serialReadData(serialDevice, &byte, 1, 0, 0);
+
+    if (result == 0) {
+      if (inputMode->temporary)
+        if (afterTimePeriod(&inputPeriod, NULL))
+          resetInputMode();
+
+      return EOF;
+    }
+
+    if (result == -1) {
+      logSystemError("read");
+      return BRL_CMD_RESTARTBRL;
+    }
+  }
+
+  mode = inputMode;
+  if (mode->temporary) resetInputMode();
+
+  switch (byte) {
+    case KEY_F1:     binding = &mode->keyF1;     break;
+    case KEY_F2:     binding = &mode->keyF2;     break;
+    case KEY_LEFT:   binding = &mode->keyLeft;   break;
+    case KEY_RIGHT:  binding = &mode->keyRight;  break;
+    case KEY_UP:     binding = &mode->keyUp;     break;
+    case KEY_DOWN:   binding = &mode->keyDown;   break;
+    case KEY_CENTER: binding = &mode->keyCenter; break;
+
+    default:
+      logMessage(LOG_WARNING, "unhandled key: %s -> %02X", mode->name, byte);
+      beep(brl);
+      return EOF;
+  }
+
+  switch (binding->type) {
+    case IBT_unbound:
+      logMessage(LOG_WARNING, "unbound key: %s -> %02X", mode->name, byte);
+      beep(brl);
+      break;
+
+    case IBT_command:
+      return binding->value.command;
+
+    case IBT_block:
+      return binding->value.block + cursorOffset;
+
+    case IBT_function:
+      return binding->value.function(brl);
+
+    case IBT_submode: {
+      setInputMode(binding->value.submode);
+      break;
+    }
+
+    default:
+      logMessage(LOG_WARNING, "unhandled input binding type: %02X", binding->type);
+      break;
+  }
+
+  return BRL_CMD_NOOP;
+}
diff --git a/Drivers/Braille/MultiBraille/Makefile.in b/Drivers/Braille/MultiBraille/Makefile.in
new file mode 100644
index 0000000..8d8ee1b
--- /dev/null
+++ b/Drivers/Braille/MultiBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mb
+DRIVER_NAME = MultiBraille
+DRIVER_USAGE = MB125CR, MB145CR, MB185CR
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Wolfgang Astleitner
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/MultiBraille/README b/Drivers/Braille/MultiBraille/README
new file mode 100644
index 0000000..784d653
--- /dev/null
+++ b/Drivers/Braille/MultiBraille/README
@@ -0,0 +1,224 @@
+Version V1.0, March/April 2000
+
+Driver for Tieman B.V.'s MultiBraille series
+
+Copyright (C) 2000 by Wolfgang Astleitner.
+( Modification of CombiBraille-driver: 
+  Copyright (C) 1995, 1996 by Nikhil Nair.
+)
+
+This driver is part of BRLTTY, and as such it is placed under the
+terms of the GNU Lesser General Public License, as published by the Free
+Software Foundation.  Please see the file LICENSE-LGPL in the top-level
+directory for details.
+
+This driver is maintained by Wolfgang Astleitner <wolfgang.astleitner@liwest.at>.
+
+-----------------------------------------------------------------------------
+
+SUPPORTED HARDWARE
+==================
+
+This driver has only been tested on a "tieman Braillenotebook V2.1" with a
+build-in MultiBraille MB145CR, but the following hardware should also be
+supported:
+
+- Brailleline 125 (MB125CR)
+- Brailleline 145 (MB145CR) / PICO II
+- Brailleline 185 (MB185CR)
+
+I received the technical documentation for these displays from tieman germany
+and developed the driver so that it should be able to identify the above listed
+displays.
+
+Problem:
+the tech. doc. is poor (I know that my spoken/written english is not
+good, but what I saw there made me believe that I am a native speaker ;-)
+You have to be very creative to get something out from these docs...
+A lot of trial and error-programming...
+
+I did not include support for the speech synthesiser yet.
+Mainly because I didn't get the clue how to realize it - the section in the
+tech. docs about this is too poor or I didn't just get it ...
+
+
+CONFIGURATION AND COMPILATION
+=============================
+
+For general information about configuring and compiling BRLTTY, please
+see the BRLTTY manual.
+
+Any MultiBraille-specific configuration is done by editing the
+braille.h file in this directory.  However, such configuration is
+probably unnecessary as the display size is autodetected during
+initialisation at run-time.
+
+By default, BRLTTY can be started even if the MultiBraille is switched
+off or not connected - it will wait in the background, checking every
+five seconds for a display.  This behaviour can be changed by
+adjusting the values of ACK_TIMEOUT and MAX_ATTEMPTS.
+
+The driver should probably set the autorepeat delay and rate of the
+Braille display's keys during initialisation.  This is yet to be
+implemented.
+
+The braille dot keys, the front keys and 3 cursor routing keys can be
+customized in 'tables.h' (read also below).
+
+
+GETTING STARTED
+===============
+
+The MultiBraille must be connected to the serial device you chose
+during configuration, unless of course you use the -d option to
+BRLTTY.  The serial interface must be selected; this is done by
+holding the left-most thumb key while turning on the display.  BRLTTY
+should then display its startup message before starting to echo the
+screen.
+
+
+KEY BINDINGS
+============
+
+The key bindings - particularly with regard to the Braille dot keys -
+have been redesigned from the DOS driver provided with the
+CombiBraille.  However, the five thumb keys work similarly.  If we
+label them A to E from left to right, then A is FWINLT (go left one
+full window width), B is LNUP (go up one line), D is LNDN (go down one
+line) and E is FWINRT (go right one full window width).  C toggles
+cursor tracking, so C twice moves the window to the cursor position.
+
+This can be customized in 'tables.h' (array cmd_T_trans[]).
+
+The extra cursor routing key (over the gap between the status cells
+and the main display) is used to bring up the help screen.  Pressing
+this key again goes back to normal operation.  The help screen has a
+full list of key bindings (apart from the cursor routing keys); it can
+be found in plain text format in the file help.txt in this
+directory.  The dot keys have been numbered (from left to right): 3,
+2, 1, 4, 5, 6; capital letters in brackets refer to thumb keys,
+e.g. (A) means A alone and (CC) means C twice.
+
+All functions bound to the thumb keys can also be executed by using
+the Braille dot keys.  The converse, however, is not the case, as
+there are far more available combinations of Braille dot keys than of
+thumb keys.
+
+The commands bound to the Braille dot keys can be customized in 'tables.h'
+(array cmd_S_trans[]).
+
+Cursor Routing Keys
+-------------------
+
+The keys above the 20/40/80 cells of the main display can be used for
+a more accurate form of cursor routing, specifying that particular
+position rather than the start of the Braille window.
+
+The extra six cursor routing keys have special meanings.  The sixth
+from the left toggles help mode; the fifth is the RESET button and 
+the fourth is the CONFMENU button.
+Key number 1 seems to do nothing (not connected? - tech. doc says something
+about that - funny, why then even put it there ????)
+
+Cursor routing keys 4 to 6 can be customized in 'tables.h', array cmd_R_trans[].
+
+Special Cut Function
+--------------------
+
+The main cursor routing keys, together with the leftmost two of the
+working extra ones (key number 2 and 3), can be used for a more advanced
+form of cutting.
+
+To mark the top left corner of the rectangle to be cut, press the
+leftmost cursor routing key followed immediately by the one over the
+appropriate cell.  Then, to mark the bottom right corner, press the
+second cursor routing key followed immediately by the one over the
+appropriate cell.
+
+
+THE STATUS CELLS
+================
+
+The status cells are used slightly differently from the DOS driver.
+The first four cells are used to denote the positions of the cursor
+and the Braille window, and form two lines of four numbers.  The top
+line is the cursor position in the format CCRR where CC is the column
+number and RR is the row number.  The second line is the window
+position in the same format.  The top left corner of the screen is
+0000, in this notation.
+
+The fifth status cell is a set of flags, as follows:
+
+Dot Number      Dot Present Means
+     1          The screen is frozen
+     2          Attribute display is on
+     3          Audio signals are on
+     4          The cursor is visible
+     5          Cursor shape is block
+     6          Cursor blink is on
+     7          Cursor tracking is on
+     8          Sliding window is on
+
+
+THANKS
+======
+
+Guenter Grill   The owner of the braille display
+(owner)         Testing, tips, phone calls to tieman germany and netherl.
+                to organise technical documentation (was a really hard job -
+                tieman ignored my emails, but Guenter didn't give up calling -
+                finally we've received the needed information ;-)
+
+Mario Lang      Tips, explanations, ...
+(TU Graz)       Mario is also using linux and brltty and he had access to a
+                combibraille. So he could help me finding the differences
+                between CombiBraille and MultiBraille
+
+Nicolas Pitre   For always emailing back so fast when I needed some
+(brltty)        information about brltty itself
+
+tieman          For sending me technical documentation
+
+
+PERSONAL NOTES
+==============
+
+I myself can't read braille. Neither with sensing nor visually.
+For me it's absolutely impressing how blind people work on computers
+using a braille display that only displays 40 chars / 1 line.
+The information (the whole screen) needs to be 'rebuilt internally'.
+I even can't remember a number with six digits ... It is amazing to me.
+
+My favorite resolution is 1280x1024. This is what blind people only can
+dream about. When writing this driver I came in contact with the 'other
+world'. A world without coloured text, fat-printing, highlighted text, 
+and so on - finally I learned to use 'lynx'! (I began to like this tool).
+
+At the beginning I only thought that I need to install linux on Guenter's
+notebook.
+But - his braille display wasn't supported. So I wrote this driver. I learned
+many new things when writing the driver and I hope that this driver will help
+many people who need braille displays. It was my first contact with braille and
+I hope the result is not that bad ;-)
+
+If you find bugs, have some comments (solving something in a better way, ..),
+please contact me:
+
+Wolfgang Astleitner
+Email: wolfgang.astleitner@liwest.at
+
+Problem: Responses will take some time because I am not the owner of the
+braille-notebook. So if I want to build in improvements I have to borrow
+the notebook from Guenter again!
+
+
+PS: Please don't mind my english. As I wrote before, it's not good...
+
+
+*******
+This readme is mainly based on CombiBraille/README
+Nikhil Nair
+Trinity College, CAMBRIDGE, CB2 1TQ, England
+Tel.: +44 1223 368353
+Email: nn201@cus.cam.ac.uk
+
diff --git a/Drivers/Braille/MultiBraille/braille.c b/Drivers/Braille/MultiBraille/braille.c
new file mode 100644
index 0000000..10f8f27
--- /dev/null
+++ b/Drivers/Braille/MultiBraille/braille.c
@@ -0,0 +1,350 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* MultiBraille/braille.c - Braille display library
+ * the following Tieman B.V. braille terminals are supported
+ * (infos out of a techn. product description sent to me from tieman by fax):
+ *
+ * - Brailleline 125 (no explicit description)
+ * - Brailleline PICO II or MB145CR (45 braille modules + 1 dummy)
+ * - Brailleline MB185CR (85 braille modules + 1 dummy)
+ *
+ * Wolfgang Astleitner, March/April 2000
+ * Email: wolfgang.astleitner@liwest.at
+ * braille.c,v 1.0
+ *
+ * Mostly based on CombiBraille/braille.c by Nikhil Nair
+ */
+
+
+/* Description of the escape-sequences used by these displays:
+ - [ESC][0]
+   signal sent to the braille display so that we get the init message
+ - [ESC][V][braille length][firmware version][CR]
+   init message sent back by the braille display
+   * braille length:   20 / 25 / 40 / 80 (decimal)
+   * firmware version: needs to be divided by 10.0: so if we receive
+     21 (decimal) --> version 2.1
+ - [ESC][F][braillekey data][CR]
+   don't know what this is good for. description: init the PC for reading
+   the top keys as braille keys (0: mode off, 1: mode on)
+ - [ESC][Z][braille data][CR]
+   braille data from PC to braille display
+   (braille-encoded characters ([20|25|40|80] * 8 bit)
+ - [ESC][B][beep data][CR]
+   send a beep to the piezo-beeper:
+   1: long beep
+   0: short beep
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "timing.h"
+#include "async_wait.h"
+#include "ascii.h"
+
+#define BRL_STATUS_FIELDS sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "braille.h"
+#include "tables.h"		/* for keybindings */
+#include "io_serial.h"
+
+SerialDevice *MB_serialDevice;			/* file descriptor for Braille display */
+static int brlcols;		/* length of braille display (auto-detected) */
+static unsigned char *prevdata;	/* previously received data */
+static unsigned char status[5], oldstatus[5];	/* status cells - always five */
+static unsigned char *rawdata;		/* writebrl() buffer for raw Braille data */
+static short rawlen;			/* length of rawdata buffer */
+
+/* message event coming from the braille display to the PC */
+typedef struct KeyStroke {
+	int block;				/* EOF or blocknumber: */
+										/* front keys: 84 (~ [ESC][T][keynumber][CR] )
+										 *   (MB185CR also block with '0'-'9', '*', '#')
+										 * top keys: 83 (~ [ESC][S][keynumber][CR] )
+										 * cursorrouting keys: 82 (~ [ESC][R][keynumber][CR] )
+                                                                                 */
+	int key;					/* => keynumber */
+} KeyStroke;
+
+
+/* Function prototypes: */
+static struct KeyStroke getbrlkey (void);		/* get a keystroke from the MultiBraille */
+
+static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+	short n, success;		/* loop counters, flags, etc. */
+	unsigned char *init_seq = (unsigned char *)"\002\0330";	/* string to send to Braille to initialise: [ESC][0] */
+	unsigned char *init_ack = (unsigned char *)"\002\033V";	/* string to expect as acknowledgement: [ESC][V]... */
+	unsigned char c;
+	TimePeriod period;
+
+	if (!isSerialDeviceIdentifier(&device)) {
+		unsupportedDeviceIdentifier(device);
+		return 0;
+	}
+
+	brlcols = -1;		/* length of braille display (auto-detected) */
+	prevdata = rawdata = NULL;		/* clear pointers */
+
+	/* No need to load translation tables, as these are now
+	 * defined in tables.h
+	 */
+
+	/* Now open the Braille display device for random access */
+	if (!(MB_serialDevice = serialOpenDevice(device))) goto failure;
+	if (!serialRestartDevice(MB_serialDevice, BAUDRATE)) goto failure;
+	if (!serialSetFlowControl(MB_serialDevice, SERIAL_FLOW_HARDWARE)) goto failure;
+
+	/* MultiBraille initialisation procedure:
+	 * [ESC][V][Braillelength][Software Version][CR]
+	 * I guess, they mean firmware version with software version :*}
+	 * firmware version == [Software Version] / 10.0
+         */
+	success = 0;
+	if (init_seq[0])
+		if (serialWriteData (MB_serialDevice, init_seq + 1, init_seq[0]) != init_seq[0])
+			goto failure;
+	startTimePeriod (&period, ACK_TIMEOUT);		/* initialise timeout testing */
+	n = 0;
+	do {
+		asyncWait (20);
+		if (serialReadData (MB_serialDevice, &c, 1, 0, 0) == 0)
+			continue;
+		if (n < init_ack[0] && c != init_ack[1 + n])
+			continue;
+		if (n == init_ack[0]) {
+			brlcols = c, success = 1;
+
+			/* reading version-info */
+			/* firmware version == [Software Version] / 10.0 */
+			serialReadData (MB_serialDevice, &c, 1, 0, 0);
+			logMessage (LOG_INFO, "MultiBraille: Version: %2.1f", c/10.0);
+
+			/* read trailing [CR] */
+			serialReadData (MB_serialDevice, &c, 1, 0, 0);
+		}
+		n++;
+	}
+	while (!afterTimePeriod (&period, NULL) && n <= init_ack[0]);
+
+	if (success && (brlcols > 0)) {
+          if ((prevdata = malloc(brlcols))) {
+            if ((rawdata = malloc(20 + (brlcols * 2)))) {
+              brl->textColumns = brlcols;
+              brl->textRows = 1;
+
+              brl->statusColumns = 5;
+              brl->statusRows = 1;
+
+              MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X80, 0X40, 0X20, 0X08, 0X10);
+              return 1;
+            } else {
+              logMallocError();
+            }
+
+            free(prevdata);
+          } else {
+            logMallocError();
+          }
+        }
+
+      failure:
+	if (MB_serialDevice) {
+           serialCloseDevice(MB_serialDevice);
+           MB_serialDevice = NULL;
+        }
+	return 0;
+}
+
+
+static void brl_destruct (BrailleDisplay *brl) {
+	unsigned char *pre_data = (unsigned char *)"\002\033Z";	/* string to send to */
+	unsigned char *post_data = (unsigned char *)"\001\015";
+	unsigned char *close_seq = (unsigned char *)"";
+
+	rawlen = 0;
+	if (pre_data[0]) {
+		memcpy (rawdata + rawlen, pre_data + 1, pre_data[0]);
+		rawlen += pre_data[0];
+	}
+	/* Clear the five status cells and the main display: */
+	memset (rawdata + rawlen, 0, 5 + 1+ brl->textColumns * brl->textRows);
+	rawlen += 5 + 1 + brl->textColumns * brl->textRows;  /* +1 is for dummy module */
+	if (post_data[0]) {
+		memcpy (rawdata + rawlen, post_data + 1, post_data[0]);
+		rawlen += post_data[0];
+	}
+
+	/* Send closing sequence: */
+	if (close_seq[0]) {
+		memcpy (rawdata + rawlen, close_seq + 1, close_seq[0]);
+		rawlen += close_seq[0];
+	}
+	serialWriteData (MB_serialDevice, rawdata, rawlen);
+
+	free (prevdata);
+	free (rawdata);
+
+	serialCloseDevice (MB_serialDevice);
+}
+
+
+
+static int brl_writeStatus (BrailleDisplay *brl, const unsigned char *s) {
+	/* Dot mapping: */
+	translateOutputCells(status, s, 5);
+	return 1;
+}
+
+
+static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+	int textChanged = cellsHaveChanged(prevdata, brl->buffer, brl->textColumns*brl->textRows, NULL, NULL, NULL);
+	int statusChanged = cellsHaveChanged(oldstatus, status, 5, NULL, NULL, NULL);
+	short i;			/* loop counter */
+	unsigned char *pre_data = (unsigned char *)"\002\033Z";	/* bytewise accessible copies */
+	unsigned char *post_data = (unsigned char *)"\001\015";
+
+	/* Only refresh display if the data has changed: */
+	if (textChanged || statusChanged) {
+		/* Dot mapping from standard to MultiBraille: */
+		translateOutputCells(brl->buffer, brl->buffer, brl->textColumns*brl->textRows);
+
+    rawlen = 0;
+		if (pre_data[0]) {
+			memcpy (rawdata + rawlen, pre_data + 1, pre_data[0]);
+			rawlen += pre_data[0];
+		}
+		
+		/* HACK - ALERT ;-)
+		 * 6th module is a dummy-modul and not wired!
+		 * but I need to but a dummy char at the beginning, else the stati are shifted ...
+                 */
+		rawdata[rawlen++] = 0;
+
+		/* write stati */
+		for (i = 0; i < 5; i++) {
+			rawdata[rawlen++] = status[i];
+		}
+		
+		
+		/* write braille message itself */
+		for (i = 0; i < brl->textColumns * brl->textRows; i++) {
+			rawdata[rawlen++] = brl->buffer[i];
+		}
+      
+		if (post_data[0]) {
+			memcpy (rawdata + rawlen, post_data + 1, post_data[0]);
+			rawlen += post_data[0];
+		}
+    
+		serialWriteData (MB_serialDevice, rawdata, rawlen);
+	}
+	return 1;
+}
+
+
+static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+	static short status = 0;	/* cursor routing keys mode */
+	
+	KeyStroke keystroke;
+
+	keystroke = getbrlkey ();
+	if (keystroke.block == EOF)
+		return EOF;
+		
+	if (keystroke.block != 'R') {
+		/* translate only 'T' and 'S' events
+		 * I kicked argtrans[] (never ever needed) --> simply return 0x00
+                 */
+		if (keystroke.block == 'T')
+			keystroke.key = cmd_T_trans[keystroke.key];
+		else
+			keystroke.key = cmd_S_trans[keystroke.key];
+		status = 0;
+                if ((keystroke.key == BRL_CMD_BLK(COPY_LINE)) || (keystroke.key == BRL_CMD_BLK(COPY_RECT)))
+                  keystroke.key += brlcols - 1;
+		return keystroke.key;
+	} else { /* directly process 'R' events */
+	  /* cursor routing key 1 or 2 pressed: begin / end block to copy'n'paste */
+		/* (counting starts with 0 !) */
+		if (keystroke.key == 1 || keystroke.key == 2) {
+			status = keystroke.key;
+			return EOF;
+		}
+		/* cursor routing keys 3, 4 and 5: invidiually defined functions */
+		/* (counting starts with 0 !) */
+		if (keystroke.key >= 3 && keystroke.key <= 5) {
+			return cmd_R_trans[keystroke.key];
+		}
+		switch (status) {
+			case 0:			/* ordinary cursor routing */
+				return keystroke.key + BRL_CMD_BLK(ROUTE) - MB_CR_EXTRAKEYS;
+			case 1:			/* begin block */
+				status = 0;
+				return keystroke.key + BRL_CMD_BLK(CLIP_NEW) - MB_CR_EXTRAKEYS;
+			case 2:			/* end block */
+				status = 0;
+				return keystroke.key + BRL_CMD_BLK(COPY_RECT) - MB_CR_EXTRAKEYS;
+		}
+		status = 0;
+	}
+	/* should never reach this, just to keep compiler happy ;-) */
+	return EOF;
+}
+
+
+/* getbrlkey ()
+ * returns a keystroke event
+ */
+
+static struct KeyStroke getbrlkey (void) {
+	unsigned char c, c_temp;		/* character buffer */
+	KeyStroke keystroke;
+		
+	while (serialReadData (MB_serialDevice, &c, 1, 0, 0) == 1) {
+		if (c != ASCII_ESC) continue;	/* advance to next ESC-sequence */
+
+		serialReadData (MB_serialDevice, &c, 1, 0, 0);		/* read block number */
+		switch (c) {
+			case 'T':			/* front key message, (MB185CR only: also '0'-'9', '*', '#') */
+			case 'S':			/* top key message [1-2-4--8-16-32] */
+			case 'R':			/* cursor routing key [0 - maxkeys] */
+				keystroke.block = c;
+				serialReadData (MB_serialDevice, &c, 1, 0, 0);		/* read keynumber */
+				keystroke.key = c;
+				serialReadData (MB_serialDevice, &c, 1, 0, 0);		/* read trailing [CR] */
+  				/* logMessage(LOG_NOTICE, "MultiBraille.o: Receiving: Key=%d, Block=%c", keystroke.key, keystroke.block); */
+				return keystroke;
+			default:			/* not supported command --> ignore */
+				c_temp = c;
+				keystroke.block = EOF;	/* invalid / not supported keystroke */
+				serialReadData (MB_serialDevice, &c, 1, 0, 0);		/* read keynumber */
+  				keystroke.key = 0;
+  				logMessage(LOG_NOTICE, "MultiBraille.o: Ignored: Key=%d, Block=%c", keystroke.key, c_temp);
+				return keystroke;
+		}
+	}
+	keystroke.block = EOF;
+	keystroke.key = 0;
+	return keystroke;
+}
diff --git a/Drivers/Braille/MultiBraille/braille.h b/Drivers/Braille/MultiBraille/braille.h
new file mode 100644
index 0000000..68ceabf
--- /dev/null
+++ b/Drivers/Braille/MultiBraille/braille.h
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* MultiBraille/braille.h - Configurable definitions for the
+ * following Tieman B.V. braille terminals
+ * (infos out of a techn. product description sent to me from tieman by fax):
+ *
+ * - Brailleline 125 (no explicit description in tech. docs)
+ * - Brailleline PICO II or MB145CR (45 braille modules + 1 dummy)
+ * - Brailleline MB185CR (85 braille modules + 1 dummy)
+ *
+ * Wolfgang Astleitner, March/April 2000
+ * Email: wolfgang.astleitner@liwest.at
+ * brlconf.h,v 1.0
+ *
+ * Based on CombiBraille/brlconf.h by Nikhil Nair
+ *
+ * Edit as necessary for your system.
+ */
+
+/* used by braille.c */
+#include "io_serial.h"
+extern SerialDevice *MB_serialDevice;
+
+#define BAUDRATE 38400		/* baud rate for Braille display */
+
+/* The following sequences are sent at initialisation time, at termination
+ * and before and after Braille data.  The first byte is the length of the
+ * sequence.
+ *
+ * Initialisation is treated specially, as there may not be a Braille
+ * display connected.  This relies on a reply from the display.
+ */
+#define ACK_TIMEOUT 5000	/* acknowledgement timeout in milliseconds */
+#define MAX_ATTEMPTS 100		/* total tiimeout = timeout * attempts */
+				/* try forever if MAX_ATTEMPTS = 0 */
+
+#define MB_CR_EXTRAKEYS 6  /* amount of extra cursor routing keys. */
+                           /* should always be 6 (-> tech. docs) */ 
diff --git a/Drivers/Braille/MultiBraille/tables.h b/Drivers/Braille/MultiBraille/tables.h
new file mode 100644
index 0000000..24f53bf
--- /dev/null
+++ b/Drivers/Braille/MultiBraille/tables.h
@@ -0,0 +1,132 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* MultiBraille/tables.h - keybindings for the MultiBraille
+ * Wolfgang Astleitner, March 2000
+ * tables.h,v 1.1
+ */
+
+/*
+  Calculation mask of key-values for top-keys: 1-2-4--8-16-32  (Key 3-2-1--4-5-6)
+
+ hex-values returned when pressing either a front-key or top-key combination
+ ===========================================================================
+    'S'   'T'    S-bits  'T' meaning                           used brltty-cmd
+   ====  ====   ======= ==== ================================  ===============
+   Movement keys:
+ * 0x07         321          top of screen                     BRL_CMD_TOP
+ * 0x38             456      bottom of screen                  BRL_CMD_BOT
+ ? 0x06          21          up several lines                  BRL_CMD_NXDIFLN
+ ? 0x18             45       down several lines                BRL_CMD_PRDIFLN
+ * 0x04  0x0e     1     (B)  up one line                       BRL_CMD_LNUP
+ * 0x08  0x13       4   (D)  down one line                     BRL_CMD_LNDN
+ ? 0x21  0x10   3     6 (CC) cursor position                   BRL_CMD_BLK(ROUTE)
+ * 0x03         32           beginning of line                 BRL_CMD_LNBEG
+ * 0x30              56      end of line                       BRL_CMD_LNEND
+ * 0x05         3 1          left one character                BRL_CMD_CHRLT
+ * 0x28             4 6      right one character               BRL_CMD_CHRRT
+ * 0x2a          2  4 6      left one half window              BRL_CMD_HWINLT
+ * 0x15         3 1  5       right one half window             BRL_CMD_HWINRT
+ * 0x01  0x0d   3       (A)  left one full window              BRL_CMD_FWINLT
+ * 0x20  0x14         6 (E)  right one full window             BRL_CMD_FWINRT
+
+  Other functions:
+ * 0x34           1  56      speak current line                BRL_CMD_SAY_LINE
+ * 0x24           1   6      mute speech                       BRL_CMD_MUTE
+ ? 0x1e          21 45       route cursor to start of window   BRL_CMD_HOME
+ * 0x23         32    6      copy start                         BRL_CMD_BLK(CLIP_NEW)
+ * 0x31         3    56      copy end                           BRL_CMD_BLK(COPY_RECT)+brlcols-1
+ * 0x0f         321 4        paste                             BRL_CMD_PASTE
+ * 0x10              5       cursor visibility on/off          BRL_CMD_CSRVIS
+ * 0x0c  0x10     1 4   (C)  cursor tracking on/off            BRL_CMD_CSRTRK
+ * 0x02          2           cursor blink on/off               BRL_CMD_CSRBLINK
+ * 0x2c           1 4 6      capital letter blink on/off       BRL_CMD_CAPBLINK
+ ? 0x12          2   5       block/underline cursor            BRL_CMD_ATTRVIS
+ * 0x13         32   5       six/eight dot braille text        BRL_CMD_SIXDOTS
+ * 0x3a          2  456      sliding window on/off             BRL_CMD_SLIDEWIN
+ * 0x1a          2  45       skip identical lines on/off       BRL_CMD_SKPIDLNS
+ * 0x0b         32  4        audio signals on/off              BRL_CMD_TUNES
+ * 0x0d         3 1 4        attribute display on/off          BRL_CMD_DISPMD
+ * 0x0e          21 4        freeze mode on/off                BRL_CMD_FREEZE
+ * 0x16          21  5       help display on/off               BRL_CMD_HELP
+ * 0x09         3   4        status mode on/off                BRL_CMD_INFO
+
+  Preferences control:
+ * 0x3f         321 456      save preferences        BRL_CMD_PREFSAVE
+ * 0x2d         3 1 4 6      enter preferences menu          BRL_CMD_PREFMENU
+ * 0x17         321  5       restore preferences          BRL_CMD_PREFLOAD
+
+  Explanation:
+	A '?' before a line means that I was not sure if the used command is really OK.
+	A '*' means that the used command should be fine
+ */
+
+
+
+/* 
+ Command translation table for 'T' events (front/thumb keys, block with keys '0-9', '*', '#'): 
+ key numbers for front keys are: (keys in brackets are not available for all braille lines!
+   13 - 14 - (15) - 16 - (17) - (18) - 19 - 20 - (21) - (22)
+ key numbers for block keys ( '0' - '9', '*', '#'; MB185CR only):
+   1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12
+	 
+ So if you have for example a MB185CR and you want to react front key with number 15
+ for example to jump to the beginning of the line, you have to do the following:
+ * Search in table cmd_T_trans the element with index 0x0f (== decimal 15)
+   (Attention: counting starts with 0)
+ * instead of the default entry '0x00' enter BRL_CMD_LNBEG (all commands are explained above)
+ * rebuild brltty and the next time you are pressing front key 15 the cursor will
+   jump to the beginning of the line
+ 
+*/
+static int cmd_T_trans[23] = {
+/* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+/* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, BRL_CMD_FWINLT, BRL_CMD_LNUP, 0x00, 
+/* 0x10 */ BRL_CMD_CSRTRK, 0x00, 0x00, BRL_CMD_LNDN, BRL_CMD_FWINRT, 0x00, 0x00
+};
+
+
+/* Command translation table for 'S' events (braille dot keys) */
+/* 63 customizable key-bindings! (index 0 can't be customized ..) */
+/* combinations returning 0x00 are ignored */
+static int cmd_S_trans[0x40] = {
+/* 0x00 */  0x00,         BRL_CMD_FWINLT,   BRL_CMD_CSRBLINK, BRL_CMD_LNBEG,   
+/* 0x04 */  BRL_CMD_LNUP,     BRL_CMD_CHRLT,    BRL_CMD_NXDIFLN,  BRL_CMD_TOP,
+/* 0x08 */  BRL_CMD_LNDN,     BRL_CMD_INFO,     0x00,         BRL_CMD_TUNES,      
+/* 0x0c */  BRL_CMD_CSRTRK,   BRL_CMD_DISPMD,   BRL_CMD_FREEZE,   BRL_CMD_PASTE, 
+/* 0x10 */  BRL_CMD_CSRVIS,   0x00,         BRL_CMD_ATTRVIS,  BRL_CMD_SIXDOTS,        
+/* 0x14 */  0x00,         BRL_CMD_HWINRT,   BRL_CMD_HELP,     BRL_CMD_PREFLOAD, 
+/* 0x18 */  BRL_CMD_PRDIFLN,  0x00,         BRL_CMD_SKPIDLNS, 0x00,        
+/* 0x1c */  0x00,         0x00,         BRL_CMD_HOME,     0x00, 
+/* 0x20 */  BRL_CMD_FWINRT,   BRL_CMD_BLK(ROUTE),     0x00,         BRL_CMD_BLK(CLIP_NEW), 
+/* 0x24 */  BRL_CMD_MUTE,     0x00,         0x00,         0x00, 
+/* 0x28 */  BRL_CMD_CHRRT,    0x00,         BRL_CMD_HWINLT,   0x00,        
+/* 0x2c */  BRL_CMD_CAPBLINK, BRL_CMD_PREFMENU, 0x00,         0x00, 
+/* 0x30 */  BRL_CMD_LNEND,    BRL_CMD_BLK(COPY_RECT), 0x00,         0x00,        
+/* 0x34 */  BRL_CMD_SAY_LINE, 0x00,         0x00,         0x00, 
+/* 0x38 */  BRL_CMD_BOT,      0x00,         BRL_CMD_SLIDEWIN, 0x00,        
+/* 0x3c */  0x00,         0x00,         0x00,         BRL_CMD_PREFSAVE, 
+};
+
+
+/* Command translation table for 'R' events (cursor routing keys)*/
+/* only keys 3, 4, 5 (startindex: 0) can be customized!! others are
+   ignored (hard-coded)! */
+static int cmd_R_trans[MB_CR_EXTRAKEYS] = {
+/* 0x00 */ 0x00, 0x00, 0x00, BRL_CMD_PREFMENU, BRL_CMD_PREFLOAD, BRL_CMD_HELP
+};
diff --git a/Drivers/Braille/NinePoint/Makefile.in b/Drivers/Braille/NinePoint/Makefile.in
new file mode 100644
index 0000000..4b49e32
--- /dev/null
+++ b/Drivers/Braille/NinePoint/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = np
+DRIVER_NAME = NinePoint
+DRIVER_USAGE = 8
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/NinePoint/braille.c b/Drivers/Braille/NinePoint/braille.c
new file mode 100644
index 0000000..04553b7
--- /dev/null
+++ b/Drivers/Braille/NinePoint/braille.c
@@ -0,0 +1,280 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-np.h"
+
+#define PROBE_RETRY_LIMIT 3
+#define PROBE_INPUT_TIMEOUT 1000
+#define MAXIMUM_RESPONSE_SIZE 3
+#define MAXIMUM_CELL_COUNT (NP_KEY_ROUTING_MAX - NP_KEY_ROUTING_MIN + 1)
+
+BEGIN_KEY_NAME_TABLE(navigation)
+  KEY_NAME_ENTRY(NP_KEY_Brl1, "Brl1"),
+  KEY_NAME_ENTRY(NP_KEY_Brl2, "Brl2"),
+  KEY_NAME_ENTRY(NP_KEY_Brl3, "Brl3"),
+  KEY_NAME_ENTRY(NP_KEY_Brl4, "Brl4"),
+  KEY_NAME_ENTRY(NP_KEY_Brl5, "Brl5"),
+  KEY_NAME_ENTRY(NP_KEY_Brl6, "Brl6"),
+  KEY_NAME_ENTRY(NP_KEY_Brl7, "Brl7"),
+  KEY_NAME_ENTRY(NP_KEY_Brl8, "Brl8"),
+
+  KEY_NAME_ENTRY(NP_KEY_Enter,     "Enter"),
+  KEY_NAME_ENTRY(NP_KEY_Space,     "Space"),
+  KEY_NAME_ENTRY(NP_KEY_PadCenter, "PadCenter"),
+  KEY_NAME_ENTRY(NP_KEY_PadLeft,   "PadLeft"),
+  KEY_NAME_ENTRY(NP_KEY_PadRight,  "PadRight"),
+  KEY_NAME_ENTRY(NP_KEY_PadUp,     "PadUp"),
+  KEY_NAME_ENTRY(NP_KEY_PadDown,   "PadDown"),
+  KEY_NAME_ENTRY(NP_KEY_NavLeft,   "NavLeft"),
+  KEY_NAME_ENTRY(NP_KEY_NavRight,  "NavRight"),
+
+  KEY_GROUP_ENTRY(NP_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+struct BrailleDataStruct {
+  unsigned char forceRewrite;
+  unsigned char textCells[MAXIMUM_CELL_COUNT];
+};
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  unsigned char *bytes = packet;
+  size_t offset = 0;
+  size_t length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    {
+      int started = offset > 0;
+
+      if (!gioReadByte(brl->gioEndpoint, &byte, started)) {
+        if (started) logPartialPacket(bytes, offset);
+        return 0;
+      }
+    }
+
+  gotByte:
+    if (offset == 0) {
+      switch (byte) {
+        case 0XFC:
+          length = 2;
+          break;
+
+        case 0XFD:
+          length = 2;
+          break;
+
+        default:
+          logIgnoredByte(byte);
+          continue;
+      }
+    } else {
+      int unexpected = 0;
+
+      if (offset == 1) {
+        if (bytes[0] == 0XFD) {
+          switch (byte) {
+            case 0X2F:
+              length = 3;
+              break;
+
+            default:
+              unexpected = 1;
+              break;
+          }
+        }
+      }
+
+      if (unexpected) {
+        logShortPacket(bytes, offset);
+        offset = 0;
+        length = 0;
+        goto gotByte;
+      }
+    }
+
+    if (offset < size) {
+      bytes[offset] = byte;
+
+      if (offset == (length - 1)) {
+        logInputPacket(bytes, length);
+        return length;
+      }
+    } else {
+      if (offset == size) logTruncatedPacket(bytes, offset);
+      logDiscardedByte(byte);
+    }
+
+    offset += 1;
+  }
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.bluetooth.channelNumber = 1;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  return 1;
+}
+  
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const unsigned char *bytes = packet;
+    
+  return ((size == 3) && (bytes[0] == 0XFD) && (bytes[1] == 0X2F))?
+         BRL_RSP_DONE:
+         BRL_RSP_UNEXPECTED;
+}
+                        
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned char response[MAXIMUM_RESPONSE_SIZE];
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+        MAKE_OUTPUT_TABLE(0X01, 0X04, 0X10, 0X02, 0X08, 0X20, 0X40, 0X80);
+
+        brl->textColumns = MAXIMUM_CELL_COUNT;
+        brl->data->forceRewrite = 1;
+        return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
+    unsigned char bytes[(brl->textColumns * 2) + 2];
+    unsigned char *byte = bytes;
+
+    {
+      int i;
+
+      for (i=brl->textColumns-1; i>=0; i-=1) {
+        *byte++ = 0XFC;
+        *byte++ = translateOutputCell(brl->data->textCells[i]);
+      }
+    }
+
+    *byte++ = 0XFD;
+    *byte++ = 0X10;
+
+    if (!writeBytes(brl, bytes, byte-bytes)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    switch (packet[0]) {
+      case 0XFD:
+        switch (packet[1]) {
+          case 0X2F:
+            continue;
+
+          default:
+            break;
+        }
+        break;
+
+      case 0XFC: {
+        unsigned int key = packet[1];
+        if ((key >= NP_KEY_ROUTING_MIN) && (key <= NP_KEY_ROUTING_MAX)) {
+          enqueueKey(brl, NP_GRP_RoutingKeys, (key - NP_KEY_ROUTING_MIN));
+          continue;
+        } else {
+          int press = !!(key & NP_KEY_NAVIGATION_PRESS);
+          if (press) key &= ~NP_KEY_NAVIGATION_PRESS;
+          enqueueKeyEvent(brl, NP_GRP_NavigationKeys, key, press);
+          continue;
+        }
+        break;
+      }
+    }
+
+    logUnexpectedPacket(packet, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/NinePoint/brldefs-np.h b/Drivers/Braille/NinePoint/brldefs-np.h
new file mode 100644
index 0000000..2f81943
--- /dev/null
+++ b/Drivers/Braille/NinePoint/brldefs-np.h
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_NP_BRLDEFS
+#define BRLTTY_INCLUDED_NP_BRLDEFS
+
+#define NP_KEY_NAVIGATION_PRESS 0X20
+#define NP_KEY_ROUTING_MIN 0X80
+#define NP_KEY_ROUTING_MAX 0X87
+
+typedef enum {
+  NP_KEY_Brl1      = 0X41,
+  NP_KEY_Brl2      = 0X42,
+  NP_KEY_Brl3      = 0X43,
+  NP_KEY_Brl4      = 0X44,
+  NP_KEY_Brl5      = 0X45,
+  NP_KEY_Brl6      = 0X46,
+  NP_KEY_Brl7      = 0X47,
+  NP_KEY_Brl8      = 0X48,
+  NP_KEY_Enter     = 0X49,
+  NP_KEY_Space     = 0X4A,
+  NP_KEY_PadCenter = 0X4B,
+  NP_KEY_PadLeft   = 0X4C,
+  NP_KEY_PadRight  = 0X4D,
+  NP_KEY_PadUp     = 0X51,
+  NP_KEY_PadDown   = 0X53,
+  NP_KEY_NavLeft   = 0X55,
+  NP_KEY_NavRight  = 0X52
+} NP_NavigationKey;
+
+typedef enum {
+  NP_GRP_NavigationKeys,
+  NP_GRP_RoutingKeys
+} NP_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_NP_BRLDEFS */ 
diff --git a/Drivers/Braille/Papenmeier/Makefile.in b/Drivers/Braille/Papenmeier/Makefile.in
new file mode 100644
index 0000000..a07eb78
--- /dev/null
+++ b/Drivers/Braille/Papenmeier/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = pm
+DRIVER_NAME = Papenmeier
+DRIVER_USAGE = Compact 486, Compact/Tiny, IB 80 CR Soft, 2D Lite (plus), 2D Screen Soft, EL 80, EL 2D 40/66/80, EL 40/66/70/80 S, EL 40/60/80 C, EL 2D 80 S, EL 40 P, EL 80 II, Elba 20/32, Trio 40/Elba20/Elba32, Live 20/40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = August Hörandl <august.hoerandl@gmx.at>, Heimo Schön <heimo.schoen@gmx.at>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Papenmeier/README b/Drivers/Braille/Papenmeier/README
new file mode 100644
index 0000000..8c3f974
--- /dev/null
+++ b/Drivers/Braille/Papenmeier/README
@@ -0,0 +1,231 @@
+README file for the Papenmeier Driver
+=====================================
+
+Version 2.1 (October 2001)
+
+Copyright
+=========
+
+This driver is copyrighted under the GNU Lesser General Public License.
+It is free software.  See the file 'LICENSE-LGPL' for more details.
+Feel free to send your comments and to report bugs (if any :)) to us at
+august.hoerandl@gmx.at or heimo.schoen@gmx.at
+
+Supported Hardware
+==================
+This driver is supposed to work with all the Papenmeier Terminals.
+The driver is able to autodetect the connected terminal.
+
+Connection
+==========
+This driver supports serial and USB communication.
+
+Running the Driver
+==================
+To compile and run BRLTTY, please refer to the documentation about 
+BRLTTY itself. One note though: when BRLTTY starts, it will display 
+on the screen some copyright stuff, current configuration (model and 
+firmware version setup) and the device in use.  That information 
+may be useful if nothing happens with the braille terminal.
+
+
+Key Bindings and Status Cells
+=============================
+The driver is fully configurable - all key bindings and the status cells
+can be fully programmed:
+
+The configuration can be done by
+- changing the default configuration in the source file brl-cfg.h
+  any changes will be distributed to the online help during compile
+- edit the config file /etc/brltty/brltty-pm.conf; don't forget to supply 
+  a fitting help file
+
+If you don't want to use the config file you can safely remove the
+config file /etc/brltty/brltty-pm.conf and use the compiled in
+defaults. By default the driver is compiled with the READ_CONFIG
+symbol defined.  Without this symbols the code to read the config file
+won't be compiled into the driver which yields a driver with a much
+smaller size.
+
+Help File:
+==========
+The online help is generated from the compiled in defaults - it uses
+the same syntax as the config file to give the user a short overview of
+the status cells and the keymapping.
+By default the help command is mapped to the key on the first status cell.
+
+Config File
+===========
+The configuration file may include the definitions for several terminals.
+Look at Papenmeier/brltty-pm.conf and the Papenmeier/*.hlp files for 
+examples.
+
+Syntax:
+#     Comment to the end of line
+name = value   you can use 'is' instead of '='; actually you don't have to
+ write '=', 'is' or 'and' at all. The following lines are equivalent:
+name = val
+name is val
+name val
+
+Start a new terminal definition:
+identification = NUMBER
+the number is the code returned by the terminal on identification 
+request; this starts a new terminal defintion - no need to change this info
+
+Info about the terminal - Name and size of the terminal:
+terminal = "String to describe the Terminal"
+helpfile = "brltty-pm1.hlp"   # name of the helpfile
+displaysize = 40       # number of cells  
+statuscells = 0        # number of status cells (horizontal or vertical)
+frontkeys = 9          # number of keys on the front of the terminal
+haseasybar             # terminal has an easybar (EL types)
+
+Terminal settings:
+Statusdisplay settings:
+there are different display modes:
+ flag: left half: line number, right half: no/all bits set - used with
+   vertical status cells
+ horiz: two digits on the vertical status-display
+ number: display two digits in one cell (status on horiz.display)
+example: 
+status 4 = horiz cursorcol
+status 6 = flag tracking
+a list of the possible info to display (see brl.h and/or Papenmeier/hlp.auto.h)
+current - current line number
+row - cursor position - row
+col - cursor position - column
+tracking - cursor tracking
+dispmode - dispmode (text / attribut)
+frozen - screen frozen
+visible - cursor visible
+size - cursor size
+blink - cursor blink
+capitalblink - capital letter blink
+dots - 6 or 8 dots
+sound - sound
+skip - skip identical lines
+underline - attribute underlining
+blinkattr - blinking of attribute underlining
+
+Key definition:
+status NUMBER     - key on status cell
+front NUMBER      - key on the front side of the terminal
+routing           - one of the routing keys is pressed
+easy DESCR        - easy bar key 
+  DESCR may be: left, left2, up, up2, down, down2, right, right2
+switch NUMBER     - switches on the left and right side of the 
+  display; numbered from 1 (leftmost switch up) to 8 (rightmost switch down)
+
+Modifier key settings:
+any key can be defined as a modifier (like the shift or alt key on the 
+standard keyboard). 
+
+Input mode:
+during inputmode the modifier keys are used to input the 8 dots of a 
+character. Attention: the modifiers keys are used to simulate
+the different dots, so INPUTMODE or INPUTMODE OFF should be
+mapped to a single key.
+
+Commandkey settings:
+list of definitions:
+  command = key 
+  command = modifier and key
+The command is executed on keypress; if you specify only modifiers 
+the command is executed when the first modifier is released.
+For commands which toggle a setting it is possible to add ON or OFF to 
+the command to define a predictable state change
+  command ON = <as above>
+  command OFF = <as above>
+A list of the possible commands (the following list is sometimes out of 
+date - see brldefs.h and/or Papenmeier/hlp.auto.h for the actual values), the names 
+may be used with or without the leading CMD_:
+NOOP - do nothing
+LNUP - go up one line
+LNDN - go down one line
+WINUP - go up several lines
+WINDN - go down several lines
+PRDIFLN - go up to line with different content
+NXDIFLN - go down to line with different content
+ATTRUP - go up to line with different attributes
+ATTRDN - go down to line with different attributes
+PRBLNKLN - go to last line of previous paragraph
+NXBLNKLN - go to first line of next paragraph
+PRSEARCH - search up for content of cut buffer
+NXSEARCH - search down for content of cut buffer
+TOP - go to top line
+BOT - go to bottom line
+TOP_LEFT - go to top-left corner
+BOT_LEFT - go to bottom-left corner
+CHRLT - go left one character
+CHRRT - go right one character
+HWINLT - go left one half window
+HWINRT - go right one half window
+FWINLT - go left one full window
+FWINRT - go right one full window
+FWINLTSKIP - go left to non-blank window
+FWINRTSKIP - go right to non-blank window
+LNBEG - go to beginning of line
+LNEND - go to end of line
+HOME - go to cursor
+BACK - go to last motion
+CSRJMP - route cursor to top-left corner of braille window
+CSRJMP_VERT - route cursor to top line of window
+CUT_BEG - cut text from top-left corner of braille window
+CUT_END - cut text to bottom-right corner of braille window
+PASTE - insert cut buffer at cursor
+FREEZE - freeze/unfreeze screen
+DISPMD - toggle display attributes/text
+SIXDOTS - toggle text style 6-dot/8-dot
+SLIDEWIN - toggle sliding window on/off
+SKPIDLNS - toggle skipping of identical lines on/off
+SKPBLNKWINS - toggle skipping of blank windows on/off
+CSRVIS - toggle cursor visibility on/off
+CSRHIDE_QK - toggle quick hide of cursor
+CSRTRK - toggle cursor tracking on/off
+CSRSIZE - toggle cursor style underline/block
+CSRBLINK - toggle cursor blinking on/off
+ATTRVIS - toggle attribute underlining on/off
+ATTRBLINK - toggle attribute blinking on/off
+CAPBLINK - toggle capital letter blinking on/off
+SND - toggle sound on/off
+HELP - display driver help
+INFO - display status summary
+PREFMENU - present preferences menu
+PREFSAVE - save preferences
+PREFLOAD - reload preferences
+SAY - speak current line
+SAYALL - speak rest of screen
+MUTE - stop speaking immediately
+SPKHOME - goto current/last speech position
+SWITCHVT_PREV - switch to previous virtual terminal
+SWITCHVT_NEXT - switch to next virtual terminal
+RESTARTBRL - reinitialize braille driver
+RESTARTSPEECH - reinitialize speech driver
+INPUTMODE - toggle input mode
+Values to be used in combination with "routing":
+names may be used with or without the leading CR_:
+ROUTEOFFSET - route cursor to character
+BEGBLKOFFSET - define the beginning of a block
+ENDBLKOFFSET - define the end of a block
+SWITCHVT - switch virtual terminal
+NXINDENT - find next line not more indented than routing key indicates
+PRINDENT - find previous line not more indented than routing key indicates
+MSGATTRIB - message attributes of character
+Special commands to send keystrokes, 
+the names may be used with or without the leading VPK_:
+RETURN - send RETURN key
+TAB - send TAB key
+BACKSPACE - send BACKSPACE key
+ESCAPE - send ESCAPE key
+CURSOR_LEFT - send CURSOR_LEFT key
+CURSOR_RIGHT - send CURSOR_RIGHT key
+CURSOR_UP - send CURSOR_UP key
+CURSOR_DOWN - send CURSOR_DOWN key
+PAGE_UP - send PAGE_UP key
+PAGE_DOWN - send PAGE_DOWN key
+HOME - send HOME key
+END - send END key
+INSERT - send INSERT key
+DELETE - send DELETE key
+FUNCTION - send FUNCTION key
diff --git a/Drivers/Braille/Papenmeier/braille.c b/Drivers/Braille/Papenmeier/braille.c
new file mode 100644
index 0000000..1686a9c
--- /dev/null
+++ b/Drivers/Braille/Papenmeier/braille.c
@@ -0,0 +1,1302 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* This Driver was written as a project in the
+ *   HTL W1, Abteilung Elektrotechnik, Wien - Österreich
+ *   (Technical High School, Department for electrical engineering,
+ *     Vienna, Austria)  http://www.ee.htlw16.ac.at
+ *  by
+ *   Tibor Becker
+ *   Michael Burger
+ *   Herbert Gruber
+ *   Heimo Schön
+ * Teacher:
+ *   August Hörandl <august.hoerandl@gmx.at>
+ */
+/*
+ * Support for all Papenmeier Terminal + config file
+ *   Heimo.Schön <heimo.schoen@gmx.at>
+ *   August Hörandl <august.hoerandl@gmx.at>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "bitfield.h"
+#include "async_wait.h"
+#include "ascii.h"
+#include "ktb.h"
+
+#define BRL_STATUS_FIELDS sfGeneric
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-pm.h"
+#include "models.h"
+ 
+/*--- Input/Output Operations ---*/
+
+typedef struct {
+  const unsigned int *baudList;
+  const SerialFlowControl flowControl;
+  unsigned char protocol1;
+  unsigned char protocol2;
+} InputOutputOperations;
+
+/*--- Serial Operations ---*/
+
+static const unsigned int serialBauds[] = {19200, 38400, 0};
+static const InputOutputOperations serialOperations = {
+  .baudList = serialBauds,
+  .flowControl = SERIAL_FLOW_HARDWARE,
+  .protocol1 = 1,
+  .protocol2 = 1
+};
+
+/*--- USB Operations ---*/
+
+static const unsigned int usbBauds[] = {115200, 57600, 0};
+static const InputOutputOperations usbOperations = {
+  .baudList = usbBauds,
+  .flowControl = SERIAL_FLOW_NONE,
+  .protocol1 = 0,
+  .protocol2 = 3
+};
+
+/*--- Bluetooth Operations ---*/
+
+static const InputOutputOperations bluetoothOperations = {
+  .baudList = NULL,
+  .flowControl = SERIAL_FLOW_NONE,
+  .protocol1 = 0,
+  .protocol2 = 3
+};
+
+/*--- Protocol Operation Utilities ---*/
+
+typedef struct {
+  void (*initializeTerminal) (BrailleDisplay *brl);
+  void (*releaseResources) (BrailleDisplay *brl);
+  int (*readCommand) (BrailleDisplay *brl, KeyTableCommandContext context);
+  void (*writeText) (BrailleDisplay *brl, unsigned int start, unsigned int count);
+  void (*writeStatus) (BrailleDisplay *brl, unsigned int start, unsigned int count);
+  void (*flushCells) (BrailleDisplay *brl);
+  int (*setBrailleFirmness) (BrailleDisplay *brl, BrailleFirmness setting);
+} ProtocolOperations;
+
+typedef enum {
+  PM_GSC_DOTS = 0,
+  PM_GSC_FLAG,
+  PM_GSC_NUMBER,
+  PM_GSC_POSITION
+} PM_GenericStatusFormat;
+
+typedef struct {
+  unsigned char format;
+  unsigned char value;
+} PM_GenericStatusCode;
+
+typedef struct {
+  int first;
+  int last;
+} PM_KeyRange1;
+
+typedef struct {
+  KeyGroup group;
+  KeyNumber number;
+} PM_InputMapping2;
+
+struct BrailleDataStruct {
+  const InputOutputOperations *io;
+  const ModelEntry *model;
+  const ProtocolOperations *protocol;
+
+  unsigned char textCells[PM_MAXIMUM_TEXT_CELLS];
+  unsigned char statusCells[PM_MAXIMUM_STATUS_CELLS];
+
+  struct {
+    MakeNumberFunction *makeNumber;
+    MakeFlagFunction *makeFlag;
+
+    PM_GenericStatusCode codes[PM_MAXIMUM_STATUS_CELLS];
+    unsigned char initialized;
+  } gsc;
+
+  union {
+    struct {
+      struct {
+        PM_KeyRange1 front;
+        PM_KeyRange1 bar;
+        PM_KeyRange1 switches;
+        PM_KeyRange1 status;
+        PM_KeyRange1 cursor;
+        unsigned char switchState;
+      } rcv;
+
+      struct {
+        unsigned char textOffset;
+        unsigned char statusOffset;
+      } xmt;
+    } p1;
+
+    struct {
+      PM_InputMapping2 *inputMap;
+      unsigned char *inputState;
+
+      int inputKeySize;
+      int inputBytes;
+      int inputBits;
+
+      int refreshRequired;
+    } p2;
+  } prot;
+};
+
+static int
+writePacket (BrailleDisplay *brl, const void *packet, size_t size) {
+  return writeBraillePacket(brl, NULL, packet, size);
+}
+
+static int
+interpretIdentity (BrailleDisplay *brl, unsigned char id, int major, int minor) {
+  unsigned int modelIndex;
+
+  logMessage(LOG_INFO, "Papenmeier ID: %d  Version: %d.%02d", id, major, minor);
+
+  for (modelIndex=0; modelIndex<modelCount; modelIndex+=1) {
+    if (modelTable[modelIndex].modelIdentifier == id) {
+      brl->data->model = &modelTable[modelIndex];
+      logMessage(LOG_INFO, "%s  Size: %d",
+                 brl->data->model->modelName,
+                 brl->data->model->textColumns);
+
+      brl->textColumns = brl->data->model->textColumns;
+      brl->textRows = 1;
+      brl->statusRows = (brl->statusColumns = brl->data->model->statusCount)? 1: 0;
+
+      setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+
+      return 1;
+    }
+  }
+
+  logMessage(LOG_WARNING, "unknown Papenmeier ID: %d", id);
+  return 0;
+}
+
+/*--- Protocol 1 Operations ---*/
+
+static BraillePacketVerifierResult
+verifyPacket1 (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      *length = 2;
+      if (byte != ASCII_STX) return BRL_PVR_INVALID;
+      break;
+
+    case 2:
+      switch (byte) {
+        case PM_P1_PKT_IDENTITY:
+          *length = 10;
+          break;
+
+        case PM_P1_PKT_RECEIVE:
+          *length = 6;
+          break;
+
+        case 0X03:
+        case 0X04:
+        case 0X05:
+        case 0X06:
+        case 0X07:
+          *length = 3;
+          break;
+
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    case 6:
+      switch (bytes[1]) {
+        case PM_P1_PKT_RECEIVE:
+          *length = (bytes[4] << 8) | byte;
+          if (*length != 10) return BRL_PVR_INVALID;
+          break;
+
+        default:
+          break;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  if (size == *length) {
+    if (byte != ASCII_ETX) {
+      return BRL_PVR_INVALID;
+    }
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket1 (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket1, NULL);
+}
+
+static int
+writePacket1 (BrailleDisplay *brl, unsigned int xmtAddress, unsigned int count, const unsigned char *data) {
+  if (count) {
+    unsigned char header[] = {
+      ASCII_STX,
+      PM_P1_PKT_SEND,
+      0, 0, /* big endian data offset */
+      0, 0  /* big endian packet length */
+    };
+    static const unsigned char trailer[] = {ASCII_ETX};
+
+    unsigned int size = sizeof(header) + count + sizeof(trailer);
+    unsigned char buffer[size];
+    unsigned char *byte = buffer;
+
+    header[2] = xmtAddress >> 8;
+    header[3] = xmtAddress & 0XFF;
+
+    header[4] = size >> 8;
+    header[5] = size & 0XFF;
+
+    byte = mempcpy(byte, header, sizeof(header));
+    byte = mempcpy(byte, data, count);
+    byte = mempcpy(byte, trailer, sizeof(trailer));
+
+    if (!writePacket(brl, buffer, byte-buffer)) return 0;
+  }
+  return 1;
+}
+
+static int
+interpretIdentity1 (BrailleDisplay *brl, const unsigned char *identity) {
+  {
+    unsigned char id = identity[2];
+    unsigned char major = identity[3];
+    unsigned char minor = ((identity[4] * 10) + identity[5]);
+    if (!interpretIdentity(brl, id, major, minor)) return 0;
+  }
+
+  /* routing key codes: 0X300 -> status -> cursor */
+  brl->data->prot.p1.rcv.status.first = PM_P1_RCV_KEYROUTE;
+  brl->data->prot.p1.rcv.status.last  = brl->data->prot.p1.rcv.status.first + 3 * (brl->data->model->statusCount - 1);
+  brl->data->prot.p1.rcv.cursor.first = brl->data->prot.p1.rcv.status.last + 3;
+  brl->data->prot.p1.rcv.cursor.last  = brl->data->prot.p1.rcv.cursor.first + 3 * (brl->data->model->textColumns - 1);
+  logMessage(LOG_DEBUG, "Routing Keys: status=%03X-%03X cursor=%03X-%03X",
+             brl->data->prot.p1.rcv.status.first, brl->data->prot.p1.rcv.status.last,
+             brl->data->prot.p1.rcv.cursor.first, brl->data->prot.p1.rcv.cursor.last);
+
+  /* function key codes: 0X000 -> front -> bar -> switches */
+  brl->data->prot.p1.rcv.front.first = PM_P1_RCV_KEYFUNC + 3;
+  brl->data->prot.p1.rcv.front.last  = brl->data->prot.p1.rcv.front.first + 3 * (brl->data->model->frontKeys - 1);
+  brl->data->prot.p1.rcv.bar.first = brl->data->prot.p1.rcv.front.last + 3;
+  brl->data->prot.p1.rcv.bar.last  = brl->data->prot.p1.rcv.bar.first + 3 * ((brl->data->model->hasBar? 8: 0) - 1);
+  brl->data->prot.p1.rcv.switches.first = brl->data->prot.p1.rcv.bar.last + 3;
+  brl->data->prot.p1.rcv.switches.last  = brl->data->prot.p1.rcv.switches.first + 3 * ((brl->data->model->hasBar? 8: 0) - 1);
+  logMessage(LOG_DEBUG, "Function Keys: front=%03X-%03X bar=%03X-%03X switches=%03X-%03X",
+             brl->data->prot.p1.rcv.front.first, brl->data->prot.p1.rcv.front.last,
+             brl->data->prot.p1.rcv.bar.first, brl->data->prot.p1.rcv.bar.last,
+             brl->data->prot.p1.rcv.switches.first, brl->data->prot.p1.rcv.switches.last);
+
+  /* cell offsets: 0X00 -> status -> text */
+  brl->data->prot.p1.xmt.statusOffset = 0;
+  brl->data->prot.p1.xmt.textOffset = brl->data->prot.p1.xmt.statusOffset + brl->data->model->statusCount;
+  logMessage(LOG_DEBUG, "Cell Offsets: status=%02X text=%02X",
+             brl->data->prot.p1.xmt.statusOffset, brl->data->prot.p1.xmt.textOffset);
+
+  return 1;
+}
+
+static int
+handleSwitches1 (BrailleDisplay *brl, uint16_t time) {
+  unsigned char state = time & 0XFF;
+  KeyNumber pressStack[8];
+  unsigned char pressCount = 0;
+  const KeyGroup group = PM_GRP_SWT;
+  KeyNumber number = 0;
+  unsigned char bit = 0X1;
+
+  while (brl->data->prot.p1.rcv.switchState != state) {
+    if ((state & bit) && !(brl->data->prot.p1.rcv.switchState & bit)) {
+      pressStack[pressCount++] = number;
+      brl->data->prot.p1.rcv.switchState |= bit;
+    } else if (!(state & bit) && (brl->data->prot.p1.rcv.switchState & bit)) {
+      if (!enqueueKeyEvent(brl, group, number, 0)) return 0;
+      brl->data->prot.p1.rcv.switchState &= ~bit;
+    }
+
+    number += 1;
+    bit <<= 1;
+  }
+
+  while (pressCount) {
+    if (!enqueueKeyEvent(brl, group, pressStack[--pressCount], 1)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+handleKey1 (BrailleDisplay *brl, uint16_t code, int press, uint16_t time) {
+  int key;
+
+  if (brl->data->prot.p1.rcv.front.first <= code && 
+      code <= brl->data->prot.p1.rcv.front.last) { /* front key */
+    key = (code - brl->data->prot.p1.rcv.front.first) / 3;
+    return enqueueKeyEvent(brl, PM_GRP_FK1, key, press);
+  }
+
+  if (brl->data->prot.p1.rcv.status.first <= code && 
+      code <= brl->data->prot.p1.rcv.status.last) { /* status key */
+    key = (code - brl->data->prot.p1.rcv.status.first) / 3;
+    return enqueueKeyEvent(brl, PM_GRP_SK1, key, press);
+  }
+
+  if (brl->data->prot.p1.rcv.bar.first <= code && 
+      code <= brl->data->prot.p1.rcv.bar.last) { /* easy access bar */
+    if (!handleSwitches1(brl, time)) return 0;
+
+    key = (code - brl->data->prot.p1.rcv.bar.first) / 3;
+    return enqueueKeyEvent(brl, PM_GRP_BAR, key, press);
+  }
+
+  if (brl->data->prot.p1.rcv.switches.first <= code && 
+      code <= brl->data->prot.p1.rcv.switches.last) { /* easy access bar */
+    return handleSwitches1(brl, time);
+  //key = (code - brl->data->prot.p1.rcv.switches.first) / 3;
+  //return enqueueKeyEvent(brl, PM_GRP_SWT, key, press);
+  }
+
+  if (brl->data->prot.p1.rcv.cursor.first <= code && 
+      code <= brl->data->prot.p1.rcv.cursor.last) { /* Routing Keys */ 
+    key = (code - brl->data->prot.p1.rcv.cursor.first) / 3;
+    return enqueueKeyEvent(brl, PM_GRP_RK1, key, press);
+  }
+
+  logMessage(LOG_WARNING, "unexpected key: %04X", code);
+  return 1;
+}
+
+static int
+disableOutputTranslation1 (BrailleDisplay *brl, unsigned char xmtOffset, int count) {
+  unsigned char buffer[count];
+  memset(buffer, 1, sizeof(buffer));
+  return writePacket1(brl, PM_P1_XMT_BRLWRITE+xmtOffset,
+                      sizeof(buffer), buffer);
+}
+
+static void
+initializeTable1 (BrailleDisplay *brl) {
+  disableOutputTranslation1(brl, brl->data->prot.p1.xmt.statusOffset, brl->data->model->statusCount);
+  disableOutputTranslation1(brl, brl->data->prot.p1.xmt.textOffset, brl->data->model->textColumns);
+}
+
+static void
+writeText1 (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  unsigned char buffer[count];
+  translateOutputCells(buffer, brl->data->textCells+start, count);
+  writePacket1(brl, PM_P1_XMT_BRLDATA+brl->data->prot.p1.xmt.textOffset+start, count, buffer);
+}
+
+static void
+writeStatus1 (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  unsigned char buffer[count];
+  translateOutputCells(buffer, brl->data->statusCells+start, count);
+  writePacket1(brl, PM_P1_XMT_BRLDATA+brl->data->prot.p1.xmt.statusOffset+start, count, buffer);
+}
+
+static void
+flushCells1 (BrailleDisplay *brl) {
+}
+
+static void
+initializeTerminal1 (BrailleDisplay *brl) {
+  initializeTable1(brl);
+  drainBrailleOutput(brl, 0);
+
+  writeStatus1(brl, 0, brl->data->model->statusCount);
+  drainBrailleOutput(brl, 0);
+
+  writeText1(brl, 0, brl->data->model->textColumns);
+  drainBrailleOutput(brl, 0);
+}
+
+static int
+readCommand1 (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[PM_P1_MAXIMUM_PACKET_SIZE];
+  size_t length;
+
+  while ((length = readPacket1(brl, packet, sizeof(packet)))) {
+    switch (packet[1]) {
+      case PM_P1_PKT_IDENTITY:
+        if (interpretIdentity1(brl, packet)) brl->resizeRequired = 1;
+        asyncWait(200);
+        initializeTerminal1(brl);
+        break;
+
+      case PM_P1_PKT_RECEIVE:
+        handleKey1(brl, ((packet[2] << 8) | packet[3]),
+                   (packet[6] == PM_P1_KEY_PRESSED),
+                   ((packet[7] << 8) | packet[8]));
+        continue;
+
+      {
+        const char *message;
+
+      case 0X03:
+        message = "missing identification byte";
+        goto logError;
+
+      case 0X04:
+        message = "data too long";
+        goto logError;
+
+      case 0X05:
+        message = "data starts beyond end of structure";
+        goto logError;
+
+      case 0X06:
+        message = "data extends beyond end of structure";
+        goto logError;
+
+      case 0X07:
+        message = "data framing error";
+        goto logError;
+
+      logError:
+        logMessage(LOG_WARNING, "Output packet error: %02X: %s", packet[1], message);
+        initializeTerminal1(brl);
+        break;
+      }
+
+      default:
+        logUnexpectedPacket(packet, length);
+        break;
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
+
+static void
+releaseResources1 (BrailleDisplay *brl) {
+}
+
+static const ProtocolOperations protocolOperations1 = {
+  initializeTerminal1, releaseResources1,
+  readCommand1,
+  writeText1, writeStatus1, flushCells1,
+  NULL
+};
+
+static int
+writeIdentifyRequest1 (BrailleDisplay *brl) {
+  static const unsigned char badPacket[] = {
+    ASCII_STX,
+    PM_P1_PKT_SEND,
+    0, 0,			/* position */
+    0, 0,			/* wrong number of bytes */
+    ASCII_ETX
+  };
+
+  return writePacket(brl, badPacket, sizeof(badPacket));
+}
+
+static BrailleResponseResult
+isIdentityResponse1 (BrailleDisplay *brl, const void *packet, size_t size) {
+  const unsigned char *packet1 = packet;
+
+  return (packet1[1] == PM_P1_PKT_IDENTITY)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+static int
+identifyTerminal1 (BrailleDisplay *brl) {
+  unsigned char response[PM_P1_MAXIMUM_PACKET_SIZE];			/* answer has 10 chars */
+  int detected = probeBrailleDisplay(brl, 0, NULL, 1000,
+                                     writeIdentifyRequest1,
+                                     readPacket1, response, sizeof(response),
+                                     isIdentityResponse1);
+
+  if (detected) {
+    if (interpretIdentity1(brl, response)) {
+      brl->data->protocol = &protocolOperations1;
+      brl->data->prot.p1.rcv.switchState = 0;
+
+      makeOutputTable(dotsTable_ISO11548_1);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/*--- Protocol 2 Operations ---*/
+
+#define PM2_MAX_PACKET_SIZE 0X203
+#define PM2_MAKE_BYTE(high, low) ((LOW_NIBBLE((high)) << 4) | LOW_NIBBLE((low)))
+#define PM2_MAKE_INTEGER2(tens,ones) ((LOW_NIBBLE((tens)) * 10) + LOW_NIBBLE((ones)))
+
+typedef struct {
+  unsigned char bytes[PM2_MAX_PACKET_SIZE];
+  unsigned char type;
+  unsigned char length;
+
+  union {
+    unsigned char bytes[0XFF];
+  } data;
+} Packet2;
+
+static BraillePacketVerifierResult
+verifyPacket2 (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  Packet2 *packet = data;
+  unsigned char byte = bytes[size-1];
+
+  switch (byte) {
+    case ASCII_STX:
+      if (size != 1) break;
+      *length = 5;
+      return BRL_PVR_INCLUDE;
+
+    case ASCII_ETX:
+      if (size != *length) break;
+      return BRL_PVR_INCLUDE;
+
+    default: {
+      unsigned char type = HIGH_NIBBLE(byte);
+      unsigned char value = LOW_NIBBLE(byte);
+      int isIdentityPacket = packet->type == 0X0A;
+
+      switch (size) {
+        case 1:
+          break;
+
+        case 2:
+          if (type != 0X40) break;
+          packet->type = value;
+          return BRL_PVR_INCLUDE;
+
+        case 3:
+          if (type != 0X50) break;
+          packet->length = value << 4;
+          return BRL_PVR_INCLUDE;
+
+        case 4:
+          if (type != 0X50) break;
+          packet->length |= value;
+
+          {
+            size_t increment = packet->length;
+
+            if (!isIdentityPacket) increment *= 2;
+            *length += increment;
+
+            return BRL_PVR_INCLUDE;
+          }
+
+        default: {
+          size_t index;
+
+          if (type != 0X30) break;
+          if (size == *length) break;
+          index = size - 5;
+
+          if (isIdentityPacket) {
+            packet->data.bytes[index] = byte;
+          } else {
+            int high = !(index % 2);
+            index /= 2;
+
+            if (high) {
+              packet->data.bytes[index] = value << 4;
+            } else {
+              packet->data.bytes[index] |= value;
+            }
+          }
+
+          return BRL_PVR_INCLUDE;
+        }
+      }
+
+      break;
+    }
+  }
+
+  return BRL_PVR_INVALID;
+}
+
+static size_t
+readPacket2 (BrailleDisplay *brl, void *packet, size_t size) {
+  Packet2 *packet2 = packet;
+
+  return readBraillePacket(brl, NULL, packet2->bytes, size, verifyPacket2, packet);
+}
+
+static int
+writePacket2 (BrailleDisplay *brl, unsigned char command, unsigned char count, const unsigned char *data) {
+  unsigned char buffer[(count * 2) + 5];
+  unsigned char *byte = buffer;
+
+  *byte++ = ASCII_STX;
+  *byte++ = 0X40 | command;
+  *byte++ = 0X50 | (count >> 4);
+  *byte++ = 0X50 | (count & 0XF);
+
+  while (count-- > 0) {
+    *byte++ = 0X30 | (*data >> 4);
+    *byte++ = 0X30 | (*data & 0XF);
+    data++;
+  }
+
+  *byte++ = ASCII_ETX;
+  return writePacket(brl, buffer, byte-buffer);
+}
+
+static int
+interpretIdentity2 (BrailleDisplay *brl, const unsigned char *identity) {
+  {
+    unsigned char id = PM2_MAKE_BYTE(identity[0], identity[1]);
+    unsigned char major = LOW_NIBBLE(identity[2]);
+    unsigned char minor = PM2_MAKE_INTEGER2(identity[3], identity[4]);
+    if (!interpretIdentity(brl, id, major, minor)) return 0;
+  }
+
+  return 1;
+}
+
+static void
+writeCells2 (BrailleDisplay *brl, unsigned int start, unsigned int count) {
+  brl->data->prot.p2.refreshRequired = 1;
+}
+
+static void
+flushCells2 (BrailleDisplay *brl) {
+  if (brl->data->prot.p2.refreshRequired) {
+    unsigned char buffer[0XFF];
+    unsigned char *byte = buffer;
+
+    /* The status cells. */
+    byte = translateOutputCells(byte, brl->data->statusCells, brl->data->model->statusCount);
+
+    /* Two dummy cells for each key on the left side. */
+    if (brl->data->model->protocolRevision < 2) {
+      int count = brl->data->model->leftKeys;
+      while (count-- > 0) {
+        *byte++ = 0;
+        *byte++ = 0;
+      }
+    }
+
+    /* The text cells. */
+    byte = translateOutputCells(byte, brl->data->textCells, brl->data->model->textColumns);
+
+    /* Two dummy cells for each key on the right side. */
+    if (brl->data->model->protocolRevision < 2) {
+      int count = brl->data->model->rightKeys;
+      while (count-- > 0) {
+        *byte++ = 0;
+        *byte++ = 0;
+      }
+    }
+
+    writePacket2(brl, 3, byte-buffer, buffer);
+    brl->data->prot.p2.refreshRequired = 0;
+  }
+}
+
+static void
+initializeTerminal2 (BrailleDisplay *brl) {
+  memset(brl->data->prot.p2.inputState, 0, brl->data->prot.p2.inputBytes);
+  brl->data->prot.p2.refreshRequired = 1;
+
+  /* Don't send the init packet by default as that was done at the factory
+   * and shouldn't need to be done again. We'll keep the code, though,
+   * just in case it's ever needed. Perhaps there should be a driver
+   * parameter to control it.
+   */
+  if (0) {
+    unsigned char data[13];
+    unsigned char size = 0;
+
+    data[size++] = brl->data->model->modelIdentifier; /* device identification code */
+
+    /* serial baud (bcd-encoded, six digits, one per nibble) */
+    /* set to zero for default (57,600) */
+    data[size++] = 0;
+    data[size++] = 0;
+    data[size++] = 0;
+
+    data[size++] = brl->data->model->statusCount; /* number of vertical braille cells */
+    data[size++] = brl->data->model->leftKeys; /* number of left keys and switches */
+    data[size++] = brl->data->model->textColumns; /* number of horizontal braille cells */
+    data[size++] = brl->data->model->rightKeys; /* number of right keys and switches */
+
+    data[size++] = 2; /* number of routing keys per braille cell */
+    data[size++] = 0; /* size of LCD */
+
+    data[size++] = 1; /* keys and switches mixed into braille data stream */
+    data[size++] = 0; /* easy access bar mixed into braille data stream */
+    data[size++] = 1; /* routing keys mixed into braille data stream */
+
+    logBytes(LOG_DEBUG, "Init Packet", data, size);
+    writePacket2(brl, 1, size, data);
+  }
+}
+
+static int 
+readCommand2 (BrailleDisplay *brl, KeyTableCommandContext context) {
+  Packet2 packet;
+
+  while (readPacket2(brl, &packet, PM2_MAX_PACKET_SIZE)) {
+    switch (packet.type) {
+      default:
+        logMessage(LOG_DEBUG, "Packet ignored: %02X", packet.type);
+        break;
+
+      case 0X0B: {
+        int bytes = MIN(packet.length, brl->data->prot.p2.inputBytes);
+        int byte;
+
+        /* Find out which keys have been released. */
+        for (byte=0; byte<bytes; byte+=1) {
+          unsigned char old = brl->data->prot.p2.inputState[byte];
+          unsigned char new = packet.data.bytes[byte];
+
+          if (new != old) {
+            PM_InputMapping2 *mapping = &brl->data->prot.p2.inputMap[byte * 8];
+            unsigned char bit = 0X01;
+
+            while (bit) {
+              if (!(new & bit) && (old & bit)) {
+                enqueueKeyEvent(brl, mapping->group, mapping->number, 0);
+                if ((brl->data->prot.p2.inputState[byte] &= ~bit) == new) break;
+              }
+
+              mapping += 1;
+              bit <<= 1;
+            }
+          }
+        }
+
+        /* Find out which keys have been pressed. */
+        for (byte=0; byte<bytes; byte+=1) {
+          unsigned char old = brl->data->prot.p2.inputState[byte];
+          unsigned char new = packet.data.bytes[byte];
+
+          if (new != old) {
+            PM_InputMapping2 *mapping = &brl->data->prot.p2.inputMap[byte * 8];
+            unsigned char bit = 0X01;
+
+            while (bit) {
+              if ((new & bit) && !(old & bit)) {
+                enqueueKeyEvent(brl, mapping->group, mapping->number, 1);
+                if ((brl->data->prot.p2.inputState[byte] |= bit) == new) break;
+              }
+
+              mapping += 1;
+              bit <<= 1;
+            }
+          }
+        }
+
+        continue;
+      }
+
+      case 0X0C: {
+        unsigned char modifiers = packet.data.bytes[0];
+        unsigned char code = packet.data.bytes[1];
+
+        if (modifiers & 0X80) {
+          int command = BRL_CMD_BLK(PASSXT) | code;
+
+          if (modifiers & 0X01) command |= BRL_FLG_KBD_RELEASE;
+          if (modifiers & 0X02) command |= BRL_FLG_KBD_EMUL0;
+          if (modifiers & 0X04) command |= BRL_FLG_KBD_EMUL1;
+
+          enqueueCommand(command);
+        } else {
+          KeyNumberSet keys = (modifiers << 8) | code;
+
+#define BIT(key) (1 << (key))
+          if (keys & (BIT(PM_KBD_LeftSpace) | BIT(PM_KBD_RightSpace))) {
+            keys &= ~BIT(PM_KBD_Space);
+          }
+#undef BIT
+
+          enqueueKeys(brl, keys, PM_GRP_KBD, 0);
+        }
+
+        continue;
+      }
+    }
+  }
+
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+  return EOF;
+}
+
+static void
+releaseResources2 (BrailleDisplay *brl) {
+  if (brl->data->prot.p2.inputState) {
+    free(brl->data->prot.p2.inputState);
+    brl->data->prot.p2.inputState = NULL;
+  }
+
+  if (brl->data->prot.p2.inputMap) {
+    free(brl->data->prot.p2.inputMap);
+    brl->data->prot.p2.inputMap = NULL;
+  }
+}
+
+static int
+setBrailleFirmness2 (BrailleDisplay *brl, BrailleFirmness setting) {
+  unsigned char data[] = {(setting * 98 / BRL_FIRMNESS_MAXIMUM) + 2, 0X99};
+  return writePacket2(brl, 6, sizeof(data), data);
+}
+
+static const ProtocolOperations protocolOperations2 = {
+  initializeTerminal2, releaseResources2,
+  readCommand2,
+  writeCells2, writeCells2, flushCells2,
+  setBrailleFirmness2
+};
+
+typedef struct {
+  unsigned char byte;
+  unsigned char bit;
+  unsigned char size;
+} InputModule2;
+
+static void
+addInputMapping2 (BrailleDisplay *brl, const InputModule2 *module, unsigned char bit, KeyGroup group, KeyNumber number) {
+  if (brl->data->model->protocolRevision < 2) {
+    bit += module->bit;
+  } else {
+    bit += 8 - module->bit - module->size;
+  }
+
+  {
+    PM_InputMapping2 *mapping = &brl->data->prot.p2.inputMap[(module->byte * 8) + bit];
+    mapping->group = group;
+    mapping->number = number;
+  }
+}
+
+static int
+nextInputModule2 (InputModule2 *module, unsigned char size) {
+  if (!module->bit) {
+    if (!module->byte) return 0;
+    module->byte -= 1;
+    module->bit = 8;
+  }
+  module->bit -= module->size = size;
+  return 1;
+}
+
+static void
+mapInputKey2 (BrailleDisplay *brl, int count, InputModule2 *module, KeyGroup group, KeyNumber rear, KeyNumber front) {
+  while (count--) {
+    nextInputModule2(module, brl->data->prot.p2.inputKeySize);
+    addInputMapping2(brl, module, 0, group, rear);
+    addInputMapping2(brl, module, 1, group, front);
+  }
+}
+
+static void
+mapInputModules2 (BrailleDisplay *brl) {
+  InputModule2 module;
+  module.byte = brl->data->prot.p2.inputBytes;
+  module.bit = 0;
+
+  {
+    int i;
+    for (i=0; i<brl->data->prot.p2.inputBits; ++i) {
+      PM_InputMapping2 *mapping = &brl->data->prot.p2.inputMap[i];
+      mapping->group = 0;
+      mapping->number = 0;
+    }
+  }
+
+  mapInputKey2(brl, brl->data->model->rightKeys, &module, PM_GRP_SWT, PM_SWT_RightKeyRear, PM_SWT_RightKeyFront);
+
+  {
+    unsigned char column = brl->data->model->textColumns;
+    while (column) {
+      nextInputModule2(&module, 1);
+      addInputMapping2(brl, &module, 0, PM_GRP_RK2, --column);
+
+      nextInputModule2(&module, 1);
+      addInputMapping2(brl, &module, 0, PM_GRP_RK1, column);
+    }
+  }
+
+  mapInputKey2(brl, brl->data->model->leftKeys, &module, PM_GRP_SWT, PM_SWT_LeftKeyRear, PM_SWT_LeftKeyFront);
+
+  {
+    unsigned char cell = brl->data->model->statusCount;
+    while (cell) {
+      nextInputModule2(&module, 1);
+      addInputMapping2(brl, &module, 0, PM_GRP_SK2, cell-1);
+
+      nextInputModule2(&module, 1);
+      addInputMapping2(brl, &module, 0, PM_GRP_SK1, cell--);
+    }
+  }
+
+  module.bit = 0;
+  nextInputModule2(&module, 8);
+  addInputMapping2(brl, &module, 0, PM_GRP_BAR, PM_BAR_Up2);
+  addInputMapping2(brl, &module, 1, PM_GRP_BAR, PM_BAR_Up1);
+  addInputMapping2(brl, &module, 2, PM_GRP_BAR, PM_BAR_Down1);
+  addInputMapping2(brl, &module, 3, PM_GRP_BAR, PM_BAR_Down2);
+  addInputMapping2(brl, &module, 4, PM_GRP_BAR, PM_BAR_Right1);
+  addInputMapping2(brl, &module, 5, PM_GRP_BAR, PM_BAR_Left1);
+  addInputMapping2(brl, &module, 6, PM_GRP_BAR, PM_BAR_Right2);
+  addInputMapping2(brl, &module, 7, PM_GRP_BAR, PM_BAR_Left2);
+}
+
+static int
+writeIdentifyRequest2 (BrailleDisplay *brl) {
+  return writePacket2(brl, 2, 0, NULL);
+}
+
+static BrailleResponseResult
+isIdentityResponse2 (BrailleDisplay *brl, const void *packet, size_t size) {
+  const Packet2 *packet2 = packet;
+
+  if (packet2->type == 0X0A) return BRL_RSP_DONE;
+  logUnexpectedPacket(packet2->bytes, size);
+  return BRL_RSP_CONTINUE;
+}
+
+static int
+identifyTerminal2 (BrailleDisplay *brl) {
+  Packet2 packet;			/* answer has 10 chars */
+  int detected = probeBrailleDisplay(brl, brl->data->io->protocol2-1, NULL, 100,
+                                     writeIdentifyRequest2,
+                                     readPacket2, &packet, PM2_MAX_PACKET_SIZE,
+                                     isIdentityResponse2);
+
+  if (detected) {
+    if (interpretIdentity2(brl, packet.data.bytes)) {
+      brl->data->protocol = &protocolOperations2;
+
+      MAKE_OUTPUT_TABLE(0X80, 0X40, 0X20, 0X10, 0X08, 0X04, 0X02, 0X01);
+
+      brl->data->prot.p2.inputKeySize = (brl->data->model->protocolRevision < 2)? 4: 8;
+      {
+        int keyCount = brl->data->model->leftKeys + brl->data->model->rightKeys;
+
+        brl->data->prot.p2.inputBytes = keyCount + 1 +
+                                        ((((keyCount * brl->data->prot.p2.inputKeySize) +
+                                           ((brl->data->model->textColumns + brl->data->model->statusCount) * 2)
+                                          ) + 7) / 8);
+      }
+      brl->data->prot.p2.inputBits = brl->data->prot.p2.inputBytes * 8;
+
+      if ((brl->data->prot.p2.inputMap = malloc(brl->data->prot.p2.inputBits * sizeof(*brl->data->prot.p2.inputMap)))) {
+        mapInputModules2(brl);
+
+        if ((brl->data->prot.p2.inputState = malloc(brl->data->prot.p2.inputBytes))) {
+          return 1;
+        }
+
+        free(brl->data->prot.p2.inputMap);
+        brl->data->prot.p2.inputMap = NULL;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/*--- Driver Operations ---*/
+
+static int
+identifyTerminal (BrailleDisplay *brl) {
+  if (brl->data->io->protocol1 && identifyTerminal1(brl)) return 1;
+  if (brl->data->io->protocol2 && identifyTerminal2(brl)) return 1;
+  return 0;
+}
+
+static int
+startTerminal (BrailleDisplay *brl) {
+  if (gioDiscardInput(brl->gioEndpoint)) {
+    if (identifyTerminal(brl)) {
+      brl->setBrailleFirmness = brl->data->protocol->setBrailleFirmness;
+
+      memset(brl->data->textCells, 0, brl->data->model->textColumns);
+      memset(brl->data->statusCells, 0, brl->data->model->statusCount);
+
+      brl->data->protocol->initializeTerminal(brl);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0403, .product=0XF208,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2,
+      .serial = &serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &serialOperations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbOperations;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.options.applicationData = &bluetoothOperations;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->io = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+    brl->data->io = NULL;
+    brl->data->model = NULL;
+    brl->data->protocol = NULL;
+    brl->data->gsc.initialized = 0;
+
+    if (connectResource(brl, device)) {
+      const unsigned int *baud = brl->data->io->baudList;
+
+      if (baud) {
+        while (*baud) {
+          SerialParameters serialParameters;
+
+          gioInitializeSerialParameters(&serialParameters);
+          serialParameters.baud = *baud;
+          serialParameters.flowControl = brl->data->io->flowControl;
+          logMessage(LOG_DEBUG, "probing Papenmeier display at %u baud", *baud);
+
+          if (gioReconfigureResource(brl->gioEndpoint, &serialParameters)) {
+            if (startTerminal(brl)) {
+               return 1;
+            }
+          }
+
+          baud += 1;
+        }
+      } else if (startTerminal(brl)) {
+        return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+  brl->data->protocol->releaseResources(brl);
+  free(brl->data);
+}
+
+static void
+updateCells (
+  BrailleDisplay *brl,
+  unsigned int count, const unsigned char *data, unsigned char *cells,
+  void (*writeCells) (BrailleDisplay *brl, unsigned int start, unsigned int count)
+) {
+  unsigned int from, to;
+
+  if (cellsHaveChanged(cells, data, count, &from, &to, NULL)) {
+    writeCells(brl, from, to-from);
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  updateCells(brl, brl->data->model->textColumns, brl->buffer, brl->data->textCells, brl->data->protocol->writeText);
+  brl->data->protocol->flushCells(brl);
+  return 1;
+}
+
+static void
+initializeGenericStatusCodes (BrailleDisplay *brl) {
+  const size_t count = ARRAY_COUNT(brl->data->gsc.codes);
+  int commands[count];
+
+  getKeyGroupCommands(brl->keyTable, PM_GRP_SK1, commands, count);
+
+  {
+    unsigned int i;
+
+    for (i=0; i<count; i+=1) {
+      PM_GenericStatusCode *code = &brl->data->gsc.codes[i];
+
+#define SET(COMMAND,FORMAT,VALUE) case (COMMAND): code->format = (FORMAT); code->value = (VALUE); break;
+      switch (commands[i] & BRL_MSK_CMD) {
+        default:
+        SET(BRL_CMD_NOOP, PM_GSC_DOTS, 0);
+
+        SET(BRL_CMD_HELP, PM_GSC_NUMBER, gscBrailleWindowRow);
+        SET(BRL_CMD_LEARN, PM_GSC_POSITION, gscBrailleWindowColumn);
+        SET(BRL_CMD_CSRJMP_VERT, PM_GSC_NUMBER, gscScreenCursorRow);
+        SET(BRL_CMD_INFO, PM_GSC_NUMBER, gscScreenCursorColumn);
+        SET(BRL_CMD_PREFMENU, PM_GSC_NUMBER, gscScreenNumber);
+
+        SET(BRL_CMD_FREEZE, PM_GSC_FLAG, gscFrozenScreen);
+        SET(BRL_CMD_DISPMD, PM_GSC_FLAG, gscDisplayMode);
+        SET(BRL_CMD_SIXDOTS, PM_GSC_FLAG, gscSixDotComputerBraille);
+        SET(BRL_CMD_SLIDEWIN, PM_GSC_FLAG, gscSlidingBrailleWindow);
+        SET(BRL_CMD_SKPIDLNS, PM_GSC_FLAG, gscSkipIdenticalLines);
+        SET(BRL_CMD_SKPBLNKWINS, PM_GSC_FLAG, gscSkipBlankBrailleWindows);
+        SET(BRL_CMD_CSRVIS, PM_GSC_FLAG, gscShowScreenCursor);
+        SET(BRL_CMD_CSRHIDE, PM_GSC_FLAG, gscHideScreenCursor);
+        SET(BRL_CMD_CSRTRK, PM_GSC_FLAG, gscTrackScreenCursor);
+        SET(BRL_CMD_CSRSIZE, PM_GSC_FLAG, gscScreenCursorStyle);
+        SET(BRL_CMD_CSRBLINK, PM_GSC_FLAG, gscBlinkingScreenCursor);
+        SET(BRL_CMD_ATTRVIS, PM_GSC_FLAG, gscShowAttributes);
+        SET(BRL_CMD_ATTRBLINK, PM_GSC_FLAG, gscBlinkingAttributes);
+        SET(BRL_CMD_CAPBLINK, PM_GSC_FLAG, gscBlinkingCapitals);
+        SET(BRL_CMD_TUNES, PM_GSC_FLAG, gscAlertTunes);
+        SET(BRL_CMD_AUTOREPEAT, PM_GSC_FLAG, gscAutorepeat);
+        SET(BRL_CMD_AUTOSPEAK, PM_GSC_FLAG, gscAutospeak);
+        SET(BRL_CMD_BRLUCDOTS, PM_GSC_FLAG, gscBrailleTypingMode);
+      }
+#undef SET
+    }
+  }
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *s) {
+  if (brl->data->model->statusCount) {
+    unsigned char cells[brl->data->model->statusCount];
+
+    if (s[GSC_FIRST] == GSC_MARKER) {
+      unsigned int i;
+
+      if (!brl->data->gsc.initialized) {
+        if (brl->data->model->statusCount < 13) {
+          brl->data->gsc.makeNumber = makePortraitNumber;
+          brl->data->gsc.makeFlag = makePortraitFlag;
+        } else {
+          brl->data->gsc.makeNumber = makeSeascapeNumber;
+          brl->data->gsc.makeFlag = makeSeascapeFlag;
+        }
+
+        initializeGenericStatusCodes(brl);
+        brl->data->gsc.initialized = 1;
+      }
+
+      for (i=0; i<brl->data->model->statusCount; i+=1) {
+        unsigned char *cell = &cells[i];
+
+        if (i < ARRAY_COUNT(brl->data->gsc.codes)) {
+          const PM_GenericStatusCode *code = &brl->data->gsc.codes[i];
+
+          switch (code->format) {
+            case PM_GSC_DOTS:
+              *cell = code->value;
+              continue;
+
+            case PM_GSC_FLAG:
+              *cell = brl->data->gsc.makeFlag(i+1, s[code->value]);
+              continue;
+
+            case PM_GSC_POSITION:
+              if (s[code->value] == 1) break;
+            case PM_GSC_NUMBER:
+              *cell = brl->data->gsc.makeNumber(s[code->value]);
+              continue;
+
+            default:
+              break;
+          }
+        }
+
+        *cell = 0;
+      }
+    } else {
+      unsigned int i = 0;
+
+      while (i < brl->data->model->statusCount) {
+        unsigned char dots = s[i];
+
+        if (!dots) break;
+        cells[i++] = dots;
+      }
+
+      while (i < brl->data->model->statusCount) cells[i++] = 0;
+    }
+
+    updateCells(brl, brl->data->model->statusCount, cells, brl->data->statusCells, brl->data->protocol->writeStatus);
+  }
+
+  return 1;
+}
+
+static int 
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  return brl->data->protocol->readCommand(brl, context);
+}
diff --git a/Drivers/Braille/Papenmeier/brldefs-pm.h b/Drivers/Braille/Papenmeier/brldefs-pm.h
new file mode 100644
index 0000000..14f83b3
--- /dev/null
+++ b/Drivers/Braille/Papenmeier/brldefs-pm.h
@@ -0,0 +1,115 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PM_BRLDEFS
+#define BRLTTY_INCLUDED_PM_BRLDEFS
+
+#define PM_MAXIMUM_TEXT_CELLS 80 
+#define PM_MAXIMUM_STATUS_CELLS 22
+
+#define PM_P1_MAXIMUM_PACKET_SIZE 100
+#define PM_P1_PKT_SEND     'S'
+#define PM_P1_PKT_RECEIVE  'K'
+#define PM_P1_PKT_IDENTITY 'I'
+#define PM_P1_KEY_PRESSED 1
+
+/* protocol 1 offsets within input data structure */
+#define PM_P1_RCV_KEYFUNC  0X0000 /* physical and logical function keys */
+#define PM_P1_RCV_KEYROUTE 0X0300 /* routing keys */
+#define PM_P1_RCV_SENSOR   0X0600 /* sensors or secondary routing keys */
+
+/* protocol 1 offsets within output data structure */
+#define PM_P1_XMT_BRLDATA  0X0000 /* data for braille display */
+#define PM_P1_XMT_LCDDATA  0X0100 /* data for LCD */
+#define PM_P1_XMT_BRLWRITE 0X0200 /* how to write each braille cell:
+                                   * 0 = convert data according to braille table (default)
+                                   * 1 = write directly
+                                   * 2 = mark end of braille display
+                                   */
+#define PM_P1_XMT_BRLCELL  0X0300 /* description of each braille cell:
+                                   * 0 = has cursor routing key
+                                   * 1 = has cursor routing key and sensor
+                                   */
+#define PM_P1_XMT_ASC2BRL  0X0400 /* ASCII to braille translation table */
+#define PM_P1_XMT_LCDUSAGE 0X0500 /* source of LCD data:
+                                   * 0 = same as braille display
+                                   * 1 = not same as braille display
+                                   */
+#define PM_P1_XMT_CSRPOSN  0X0501 /* cursor position (0 for no cursor) */
+#define PM_P1_XMT_CSRDOTS  0X0502 /* cursor represenation in braille dots */
+#define PM_P1_XMT_BRL2ASC  0X0503 /* braille to ASCII translation table */
+#define PM_P1_XMT_LENFBSEQ 0X0603 /* length of feedback sequence for speech synthesizer */
+#define PM_P1_XMT_LENKPSEQ 0X0604 /* length of keypad sequence */
+#define PM_P1_XMT_TIMEK1K2 0X0605 /* key code suppression time for moving from K1 to K2 (left) */
+#define PM_P1_XMT_TIMEK3K4 0X0606 /* key code suppression time for moving from K3 to K4 (up) */
+#define PM_P1_XMT_TIMEK5K6 0X0607 /* key code suppression time for moving from K5 to K6 (right) */
+#define PM_P1_XMT_TIMEK7K8 0X0608 /* key code suppression time for moving from K7 to K8 (down) */
+#define PM_P1_XMT_TIMEROUT 0X0609 /* routing time interval */
+#define PM_P1_XMT_TIMEOPPO 0X060A /* key code suppression time for opposite movements */
+
+typedef enum {
+  PM_BAR_Left1 = 0,
+  PM_BAR_Left2,
+  PM_BAR_Up1,
+  PM_BAR_Up2,
+  PM_BAR_Right1,
+  PM_BAR_Right2,
+  PM_BAR_Down1,
+  PM_BAR_Down2,
+} PM_BarKey;
+
+typedef enum {
+  PM_SWT_LeftSwitchRear = 0,
+  PM_SWT_LeftSwitchFront,
+  PM_SWT_LeftKeyRear,
+  PM_SWT_LeftKeyFront,
+  PM_SWT_RightKeyRear,
+  PM_SWT_RightKeyFront,
+  PM_SWT_RightSwitchRear,
+  PM_SWT_RightSwitchFront
+} PM_SwitchKey;
+
+typedef enum {
+  PM_KBD_Dot1 = 0,
+  PM_KBD_Dot2,
+  PM_KBD_Dot3,
+  PM_KBD_Dot4,
+  PM_KBD_Dot5,
+  PM_KBD_Dot6,
+  PM_KBD_Dot7,
+  PM_KBD_Dot8,
+
+  PM_KBD_RightThumb,
+  PM_KBD_Space,
+  PM_KBD_LeftThumb,
+  PM_KBD_RightSpace,
+  PM_KBD_LeftSpace
+} PM_KeyboardKey;
+
+typedef enum {
+  PM_GRP_BAR,
+  PM_GRP_SWT,
+  PM_GRP_KBD,
+  PM_GRP_FK1,
+  PM_GRP_RK1,
+  PM_GRP_RK2,
+  PM_GRP_SK1,
+  PM_GRP_SK2
+} PM_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_PM_BRLDEFS */ 
diff --git a/Drivers/Braille/Papenmeier/models.h b/Drivers/Braille/Papenmeier/models.h
new file mode 100644
index 0000000..679992c
--- /dev/null
+++ b/Drivers/Braille/Papenmeier/models.h
@@ -0,0 +1,506 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+typedef struct {
+  unsigned char modelIdentifier;
+  unsigned char protocolRevision;
+  const char *modelName;
+  const KeyTableDefinition *keyTableDefinition;
+
+  uint8_t textColumns;
+  uint8_t frontKeys;
+  uint8_t hasBar;
+  uint8_t leftSwitches;
+  uint8_t rightSwitches;
+  uint8_t leftKeys;
+  uint8_t rightKeys;
+  uint8_t statusCount;
+} ModelEntry; 
+
+#define PM_MODEL_IDENTITY(identifier, model, name, protocol)	\
+  .modelIdentifier = identifier, \
+  .modelName = name, \
+  .protocolRevision = protocol, \
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(model)
+
+#define PM_CELL_COUNTS(columns, status) \
+  .textColumns = columns, \
+  .statusCount = status
+
+#define PM_FRONT_KEYS(front) \
+  .frontKeys = front
+
+#define PM_BAR(ls, rs, lk, rk) \
+  .hasBar = 1, \
+  .leftSwitches = ls, \
+  .rightSwitches = rs, \
+  .leftKeys = lk, \
+  .rightKeys = rk
+
+
+BEGIN_KEY_NAME_TABLE(bar)
+  BRL_KEY_NAME_ENTRY(PM, BAR, Left1, "BarLeft1"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Left2, "BarLeft2"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Right1, "BarRight1"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Right2, "BarRight2"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Up1, "BarUp1"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Up2, "BarUp2"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Down1, "BarDown1"),
+  BRL_KEY_NAME_ENTRY(PM, BAR, Down2, "BarDown2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(switches)
+  BRL_KEY_NAME_ENTRY(PM, SWT, LeftSwitchRear, "LeftSwitchRear"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, LeftSwitchFront, "LeftSwitchFront"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, RightSwitchRear, "RightSwitchRear"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, RightSwitchFront, "RightSwitchFront"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keys)
+  BRL_KEY_NAME_ENTRY(PM, SWT, LeftKeyRear, "LeftKeyRear"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, LeftKeyFront, "LeftKeyFront"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, RightKeyRear, "RightKeyRear"),
+  BRL_KEY_NAME_ENTRY(PM, SWT, RightKeyFront, "RightKeyFront"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(front9)
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 0, "Function"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 1, "Cursor"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 2, "Backward"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 3, "Up"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 4, "Home"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 5, "Down"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 6, "Forward"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 7, "Braille"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 8, "Attribute"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(front13)
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 0, "Dot7"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 1, "Dot3"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 2, "Dot2"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 3, "Dot1"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 4, "Up"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 5, "Home"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 6, "Shift"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 7, "End"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 8, "Down"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 9, "Dot4"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 10, "Dot5"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 11, "Dot6"),
+  BRL_KEY_NUMBER_ENTRY(PM, FK1, 12, "Dot8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot1, "Dot1"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot2, "Dot2"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot3, "Dot3"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot4, "Dot4"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot5, "Dot5"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot6, "Dot6"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot7, "Dot7"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, Dot8, "Dot8"),
+
+  BRL_KEY_NAME_ENTRY(PM, KBD, Space, "Space"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, LeftSpace, "LeftSpace"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, RightSpace, "RightSpace"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, LeftThumb, "LeftThumb"),
+  BRL_KEY_NAME_ENTRY(PM, KBD, RightThumb, "RightThumb"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routingKeys1)
+  BRL_KEY_GROUP_ENTRY(PM, RK1, "RoutingKey1"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routingKeys2)
+  BRL_KEY_GROUP_ENTRY(PM, RK2, "RoutingKey2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(statusKeys1)
+  BRL_KEY_GROUP_ENTRY(PM, SK1, "Status"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(statusKeys2)
+  BRL_KEY_GROUP_ENTRY(PM, SK2, "StatusKey2"),
+END_KEY_NAME_TABLE
+
+
+BEGIN_KEY_NAME_TABLES(c_486)
+  KEY_NAME_TABLE(front9),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(2d_l)
+  KEY_NAME_TABLE(front9),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(c)
+  KEY_NAME_TABLE(front9),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(2d_s)
+  KEY_NAME_TABLE(front13),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ib_80)
+  KEY_NAME_TABLE(front9),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el_2d_40)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el_2d_66)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el_80)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el_2d_80)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el_40_p)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(elba_32)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(elba_20)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(switches),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el40s)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el80_ii)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el66s)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el80s)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(trio)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el70s)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el2d_80s)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(statusKeys1),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+  KEY_NAME_TABLE(statusKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(elb_tr_20)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(elb_tr_32)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el40c)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el60c)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(el80c)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(routingKeys1),
+  KEY_NAME_TABLE(routingKeys2),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(live)
+  KEY_NAME_TABLE(bar),
+  KEY_NAME_TABLE(keys),
+  KEY_NAME_TABLE(keyboard),
+  KEY_NAME_TABLE(routingKeys1),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(c_486)
+DEFINE_KEY_TABLE(2d_l)
+DEFINE_KEY_TABLE(c)
+DEFINE_KEY_TABLE(2d_s)
+DEFINE_KEY_TABLE(ib_80)
+DEFINE_KEY_TABLE(el_2d_40)
+DEFINE_KEY_TABLE(el_2d_66)
+DEFINE_KEY_TABLE(el_80)
+DEFINE_KEY_TABLE(el_2d_80)
+DEFINE_KEY_TABLE(el_40_p)
+DEFINE_KEY_TABLE(elba_32)
+DEFINE_KEY_TABLE(elba_20)
+DEFINE_KEY_TABLE(el40s)
+DEFINE_KEY_TABLE(el80_ii)
+DEFINE_KEY_TABLE(el66s)
+DEFINE_KEY_TABLE(el80s)
+DEFINE_KEY_TABLE(trio)
+DEFINE_KEY_TABLE(el70s)
+DEFINE_KEY_TABLE(el2d_80s)
+DEFINE_KEY_TABLE(elb_tr_20)
+DEFINE_KEY_TABLE(elb_tr_32)
+DEFINE_KEY_TABLE(el40c)
+DEFINE_KEY_TABLE(el60c)
+DEFINE_KEY_TABLE(el80c)
+DEFINE_KEY_TABLE(live)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(c_486),
+  &KEY_TABLE_DEFINITION(2d_l),
+  &KEY_TABLE_DEFINITION(c),
+  &KEY_TABLE_DEFINITION(2d_s),
+  &KEY_TABLE_DEFINITION(ib_80),
+  &KEY_TABLE_DEFINITION(el_2d_40),
+  &KEY_TABLE_DEFINITION(el_2d_66),
+  &KEY_TABLE_DEFINITION(el_80),
+  &KEY_TABLE_DEFINITION(el_2d_80),
+  &KEY_TABLE_DEFINITION(el_40_p),
+  &KEY_TABLE_DEFINITION(elba_32),
+  &KEY_TABLE_DEFINITION(elba_20),
+  &KEY_TABLE_DEFINITION(el40s),
+  &KEY_TABLE_DEFINITION(el80_ii),
+  &KEY_TABLE_DEFINITION(el66s),
+  &KEY_TABLE_DEFINITION(el80s),
+  &KEY_TABLE_DEFINITION(trio),
+  &KEY_TABLE_DEFINITION(el70s),
+  &KEY_TABLE_DEFINITION(el2d_80s),
+  &KEY_TABLE_DEFINITION(elb_tr_20),
+  &KEY_TABLE_DEFINITION(elb_tr_32),
+  &KEY_TABLE_DEFINITION(el40c),
+  &KEY_TABLE_DEFINITION(el60c),
+  &KEY_TABLE_DEFINITION(el80c),
+  &KEY_TABLE_DEFINITION(live),
+END_KEY_TABLE_LIST
+
+
+static const ModelEntry modelTable[] = {
+  { PM_MODEL_IDENTITY(0, c_486, "BrailleX Compact 486", 1),
+    PM_CELL_COUNTS(40, 0),
+    PM_FRONT_KEYS(9)
+  },
+
+  { PM_MODEL_IDENTITY(1, 2d_l, "BrailleX 2D Lite (plus)", 1),
+    PM_CELL_COUNTS(40, 13),
+    PM_FRONT_KEYS(9)
+  },
+
+  { PM_MODEL_IDENTITY(2, c, "BrailleX Compact/Tiny", 1),
+    PM_CELL_COUNTS(40, 0),
+    PM_FRONT_KEYS(9)
+  },
+
+  { PM_MODEL_IDENTITY(3, 2d_s, "BrailleX 2D Screen Soft", 1),
+    PM_CELL_COUNTS(80, 22),
+    PM_FRONT_KEYS(13)
+  },
+
+  { PM_MODEL_IDENTITY(6, ib_80, "BrailleX IB 80 CR Soft", 1),
+    PM_CELL_COUNTS(80, 4),
+    PM_FRONT_KEYS(9)
+  },
+
+  { PM_MODEL_IDENTITY(64, el_2d_40, "BrailleX EL 2D-40", 1),
+    PM_CELL_COUNTS(40, 13),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(65, el_2d_66, "BrailleX EL 2D-66", 1),
+    PM_CELL_COUNTS(66, 13),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(66, el_80, "BrailleX EL 80", 1),
+    PM_CELL_COUNTS(80, 2),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(67, el_2d_80, "BrailleX EL 2D-80", 1),
+    PM_CELL_COUNTS(80, 20),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(68, el_40_p, "BrailleX EL 40 P", 1),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(1, 1, 1, 0)
+  },
+
+  { PM_MODEL_IDENTITY(69, elba_32, "BrailleX Elba 32", 1),
+    PM_CELL_COUNTS(32, 0),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(70, elba_20, "BrailleX Elba 20", 1),
+    PM_CELL_COUNTS(20, 0),
+    PM_BAR(1, 1, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(85, el40s, "BrailleX EL40s", 1),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(86, el80_ii, "BrailleX EL80-II", 1),
+    PM_CELL_COUNTS(80, 2),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(87, el66s, "BrailleX EL66s", 1),
+    PM_CELL_COUNTS(66, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(88, el80s, "BrailleX EL80s", 1),
+    PM_CELL_COUNTS(80, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(89, trio, "BrailleX Trio", 2),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(90, el70s, "BrailleX EL70s", 1),
+    PM_CELL_COUNTS(70, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(91, el2d_80s, "BrailleX EL2D-80s", 1),
+    PM_CELL_COUNTS(80, 20),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(92, elb_tr_20, "BrailleX Elba (Trio 20)", 2),
+    PM_CELL_COUNTS(20, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(93, elb_tr_32, "BrailleX Elba (Trio 32)", 2),
+    PM_CELL_COUNTS(32, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(95, el40c, "BrailleX EL40c", 1),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(96, el60c, "BrailleX EL60c", 1),
+    PM_CELL_COUNTS(60, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(97, el80c, "BrailleX EL80c", 1),
+    PM_CELL_COUNTS(80, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(98, live, "BrailleX Live 40", 2),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(99, live, "BrailleX Live+ 40", 2),
+    PM_CELL_COUNTS(40, 0),
+    PM_BAR(0, 0, 1, 1)
+  },
+
+  { PM_MODEL_IDENTITY(100, live, "BrailleX Live 20", 2),
+    PM_CELL_COUNTS(20, 0),
+    PM_BAR(0, 0, 1, 1)
+  }
+};
+
+static const unsigned int modelCount = ARRAY_COUNT(modelTable);
diff --git a/Drivers/Braille/Pegasus/Makefile.in b/Drivers/Braille/Pegasus/Makefile.in
new file mode 100644
index 0000000..2b72e73
--- /dev/null
+++ b/Drivers/Braille/Pegasus/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = pg
+DRIVER_NAME = Pegasus
+DRIVER_USAGE = 20/27/40/80
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Pegasus/braille.c b/Drivers/Braille/Pegasus/braille.c
new file mode 100644
index 0000000..7a6b3f5
--- /dev/null
+++ b/Drivers/Braille/Pegasus/braille.c
@@ -0,0 +1,638 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "parse.h"
+
+//#define BRL_STATUS_FIELDS sf...
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "brldefs-pg.h"
+
+static const char productPrefix[] = "PBC";
+static const unsigned char productPrefixLength = sizeof(productPrefix) - 1;
+
+static int rewriteRequired;
+static unsigned char textCells[80];
+static unsigned char statusCells[2];
+
+BEGIN_KEY_NAME_TABLE(all)
+  KEY_NAME_ENTRY(PG_KEY_LeftShift, "LeftShift"),
+  KEY_NAME_ENTRY(PG_KEY_RightShift, "RightShift"),
+  KEY_NAME_ENTRY(PG_KEY_LeftControl, "LeftControl"),
+  KEY_NAME_ENTRY(PG_KEY_RighTControl, "RighTControl"),
+
+  KEY_NAME_ENTRY(PG_KEY_Left, "Left"),
+  KEY_NAME_ENTRY(PG_KEY_Right, "Right"),
+  KEY_NAME_ENTRY(PG_KEY_Up, "Up"),
+  KEY_NAME_ENTRY(PG_KEY_Down, "Down"),
+
+  KEY_NAME_ENTRY(PG_KEY_Home, "Home"),
+  KEY_NAME_ENTRY(PG_KEY_End, "End"),
+  KEY_NAME_ENTRY(PG_KEY_Enter, "Enter"),
+  KEY_NAME_ENTRY(PG_KEY_Escape, "Escape"),
+
+  KEY_GROUP_ENTRY(PG_GRP_RoutingKeys, "RoutingKey"),
+  KEY_NAME_ENTRY(PG_KEY_Status+0, "Status1"),
+  KEY_NAME_ENTRY(PG_KEY_Status+1, "Status2"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(all),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+typedef struct {
+  int (*identifyModel) (BrailleDisplay *brl);
+  int (*writeCells) (BrailleDisplay *brl, const unsigned char *cells, unsigned int count);
+} InputOutputMethods;
+
+typedef struct {
+  int (*openPort) (const char *device);
+  void (*closePort) ();
+  int (*awaitInput) (int milliseconds);
+  int (*readBytes) (unsigned char *buffer, int length, int wait);
+  int (*writeBytes) (const unsigned char *buffer, int length);
+  const InputOutputMethods *methods;
+} InputOutputOperations;
+static const InputOutputOperations *io;
+
+typedef enum {
+  IPT_KEY_NAVIGATION = 0X13,
+  IPT_KEY_SIMULATION = 0XFE,
+  IPT_KEY_ROUTING    = 0XFF
+} InputPacketType;
+
+typedef union {
+  unsigned char bytes[1];
+
+  char product[44 + 1];
+
+  struct {
+    unsigned char type;
+
+    union {
+      struct {
+        unsigned char type;
+        unsigned char value;
+        unsigned char release;
+      } PACKED key;
+    } fields;
+  } PACKED data;
+} PACKED InputPacket;
+
+static void
+setCellCounts (BrailleDisplay *brl, int size) {
+  brl->statusColumns = ARRAY_COUNT(statusCells);
+  brl->statusRows = 1;
+  brl->textColumns = size - brl->statusColumns;
+  brl->textRows = 1;
+
+  setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+}
+
+static int
+getCellCounts (BrailleDisplay *brl, char *product) {
+  unsigned int length = strlen(product);
+
+  {
+    static const unsigned char indexes[] = {3, 42, 0};
+    const unsigned char *index = indexes;
+
+    while (*index) {
+      if (*index < length) {
+        unsigned char size = product[*index];
+        static const unsigned char sizes[] = {22, 29, 42, 82};
+
+        if (memchr(sizes, size, sizeof(sizes))) {
+          setCellCounts(brl, size);
+          return 1;
+        }
+      }
+
+      index += 1;
+    }
+  }
+
+  {
+    static const char delimiters[] = " ";
+    char *word;
+
+    if ((word = strtok(product, delimiters))) {
+      if (strncmp(word, productPrefix, productPrefixLength) == 0) {
+        if ((word = strtok(NULL, delimiters))) {
+          int size;
+
+          if (!(*word && isInteger(&size, word))) size = 0;
+          while (strtok(NULL, delimiters));
+
+          if ((size > ARRAY_COUNT(statusCells)) &&
+              (size <= (ARRAY_COUNT(statusCells) + ARRAY_COUNT(textCells)))) {
+            setCellCounts(brl, size);
+            return 1;
+          }
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+readByte (unsigned char *byte, int wait) {
+  int count = io->readBytes(byte, 1, wait);
+  if (count > 0) return 1;
+
+  if (count == 0) errno = EAGAIN;
+  return 0;
+}
+
+static int
+readPacket (BrailleDisplay *brl, InputPacket *packet) {
+  typedef enum {
+    IPG_PRODUCT,
+    IPG_KEY,
+    IPG_DEFAULT
+  } InputPacketGroup;
+  InputPacketGroup group = IPG_DEFAULT;
+
+  int length = 1;
+  int offset = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    {
+      int started = offset > 0;
+      if (!readByte(&byte, started)) {
+        if (started) logPartialPacket(packet->bytes, offset);
+        return 0;
+      }
+    }
+
+  gotByte:
+    if (!offset) {
+      switch (byte) {
+        case IPT_KEY_NAVIGATION:
+        case IPT_KEY_SIMULATION:
+        case IPT_KEY_ROUTING:
+          group = IPG_KEY;
+          length = 4;
+          break;
+
+        default:
+          if (byte == productPrefix[0]) {
+            group = IPG_PRODUCT;
+            length = sizeof(packet->product) - 1;
+          } else {
+            logIgnoredByte(byte);
+            continue;
+          }
+          break;
+      }
+    } else {
+      int unexpected = 0;
+
+      switch (group) {
+        case IPG_PRODUCT:
+          if (offset < productPrefixLength) {
+            if (byte != productPrefix[offset]) unexpected = 1;
+          } else if (byte == '@') {
+            length = offset + 1;
+          }
+          break;
+
+        case IPG_KEY:
+          if (offset == 1) {
+            if (byte != packet->bytes[0]) unexpected = 1;
+          } else if (offset == 3) {
+            if (byte != 0X19) unexpected = 1;
+          }
+          break;
+
+        default:
+          break;
+      }
+
+      if (unexpected) {
+        logShortPacket(packet->bytes, offset);
+        group = IPG_DEFAULT;
+        offset = 0;
+        length = 1;
+        goto gotByte;
+      }
+    }
+
+    packet->bytes[offset++] = byte;
+    if (offset == length) {
+      if (group == IPG_PRODUCT) {
+        packet->bytes[length] = 0;
+      }
+
+      logInputPacket(packet->bytes, offset);
+      return length;
+    }
+  }
+}
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *buffer, int count) {
+  logOutputPacket(buffer, count);
+  if (io->writeBytes(buffer, count) != -1) return 1;
+  return 0;
+}
+
+static int
+writeCells (BrailleDisplay *brl) {
+  unsigned int textCount = brl->textColumns;
+  unsigned int statusCount = brl->statusColumns;
+  unsigned char cells[textCount + statusCount];
+  unsigned char *cell = cells;
+
+  while (textCount) *cell++ = translateOutputCell(textCells[--textCount]);
+  while (statusCount) *cell++ = translateOutputCell(statusCells[--statusCount]);
+
+  return io->methods->writeCells(brl, cells, cell-cells);
+}
+
+static void
+updateCells (unsigned char *target, const unsigned char *source, unsigned int count) {
+  if (cellsHaveChanged(target, source, count, NULL, NULL, NULL)) {
+    rewriteRequired = 1;
+  }
+}
+
+/* Serial IO */
+#include "io_serial.h"
+
+static SerialDevice *serialDevice = NULL;
+#define SERIAL_BAUD 9600
+
+static int
+openSerialPort (const char *device) {
+  if ((serialDevice = serialOpenDevice(device))) {
+    if (serialRestartDevice(serialDevice, SERIAL_BAUD))
+      if (serialSetFlowControl(serialDevice, SERIAL_FLOW_HARDWARE))
+        return 1;
+
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+
+  return 0;
+}
+
+static void
+closeSerialPort (void) {
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+awaitSerialInput (int milliseconds) {
+  return serialAwaitInput(serialDevice, milliseconds);
+}
+
+static int
+readSerialBytes (unsigned char *buffer, int count, int wait) {
+  const int timeout = 100;
+  return serialReadData(serialDevice, buffer, count,
+                        (wait? timeout: 0), timeout);
+}
+
+static int
+writeSerialBytes (const unsigned char *buffer, int length) {
+  return serialWriteData(serialDevice, buffer, length);
+}
+
+static int
+identifySerialModel (BrailleDisplay *brl) {
+  static const unsigned char request[] = {0X40, 0X50, 0X53};
+
+  if (writeBytes(brl, request, sizeof(request))) {
+    while (io->awaitInput(1000)) {
+      InputPacket response;
+
+      while (readPacket(brl, &response)) {
+        if (response.data.type == productPrefix[0]) {
+          if (getCellCounts(brl, response.product)) {
+            return 1;
+          }
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+writeSerialCells (BrailleDisplay *brl, const unsigned char *cells, unsigned int count) {
+  static const unsigned char header[] = {0X40, 0X50, 0X4F};
+  static const unsigned char trailer[] = {0X18, 0X20, 0X20};
+
+  unsigned char buffer[sizeof(header) + count + sizeof(trailer)];
+  unsigned char *byte = buffer;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  byte = mempcpy(byte, cells, count);
+  byte = mempcpy(byte, trailer, sizeof(trailer));
+
+  return writeBytes(brl, buffer, byte-buffer);
+}
+
+static const InputOutputMethods serialMethods = {
+  identifySerialModel, writeSerialCells
+};
+
+static const InputOutputOperations serialOperations = {
+  openSerialPort, closeSerialPort,
+  awaitSerialInput, readSerialBytes, writeSerialBytes,
+  &serialMethods
+};
+
+/* USB IO */
+#include "io_usb.h"
+
+static UsbChannel *usbChannel = NULL;
+
+static int
+openUsbPort (const char *device) {
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X4242, .product=0X0001,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=2
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  if ((usbChannel = usbOpenChannel(usbChannelDefinitions, (void *)device))) {
+    return 1;
+  }
+  return 0;
+}
+
+static void
+closeUsbPort (void) {
+  if (usbChannel) {
+    usbCloseChannel(usbChannel);
+    usbChannel = NULL;
+  }
+}
+
+static int
+awaitUsbInput (int milliseconds) {
+  return usbAwaitInput(usbChannel->device,
+                       usbChannel->definition->inputEndpoint,
+                       milliseconds);
+}
+
+static int
+readUsbBytes (unsigned char *buffer, int length, int wait) {
+  const int timeout = 100;
+  int count = usbReadData(usbChannel->device,
+                          usbChannel->definition->inputEndpoint,
+                          buffer, length,
+                          (wait? timeout: 0), timeout);
+
+  if (count != -1) return count;
+  if (errno == EAGAIN) return 0;
+  return -1;
+}
+
+static int
+writeUsbBytes (const unsigned char *buffer, int length) {
+  return usbWriteEndpoint(usbChannel->device,
+                          usbChannel->definition->outputEndpoint,
+                          buffer, length, 1000);
+}
+
+static int
+identifyUsbModel (BrailleDisplay *brl) {
+  int ok = 0;
+  char *product;
+
+  if ((product = usbGetProduct(usbChannel->device, 1000))) {
+    if (getCellCounts(brl, product)) {
+      ok = 1;
+    }
+
+    free(product);
+  }
+
+  return ok;
+}
+
+static int
+writeUsbCells (BrailleDisplay *brl, const unsigned char *cells, unsigned int count) {
+  unsigned char buffer[1 + count];
+  unsigned char *byte = buffer;
+
+  *byte++ = 0X43;
+  byte = mempcpy(byte, cells, count);
+
+  return writeBytes(brl, buffer, byte-buffer);
+}
+
+static const InputOutputMethods usbMethods = {
+  identifyUsbModel, writeUsbCells
+};
+
+static const InputOutputOperations usbOperations = {
+  openUsbPort, closeUsbPort,
+  awaitUsbInput, readUsbBytes, writeUsbBytes,
+  &usbMethods
+};
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (isSerialDeviceIdentifier(&device)) {
+    io = &serialOperations;
+  } else if (isUsbDeviceIdentifier(&device)) {
+    io = &usbOperations;
+  } else {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if (io->openPort(device)) {
+    if (io->methods->identifyModel(brl)) {
+      makeOutputTable(dotsTable_ISO11548_1);
+  
+      rewriteRequired = 1;
+      memset(textCells, 0, sizeof(textCells));
+      memset(statusCells, 0, sizeof(statusCells));
+
+      return 1;
+    }
+
+    io->closePort();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  io->closePort();
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  updateCells(textCells, brl->buffer, brl->textColumns);
+
+  if (rewriteRequired) {
+    if (!writeCells(brl)) return 0;
+    rewriteRequired = 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) {
+  updateCells(statusCells, cells, brl->statusColumns);
+  return 1;
+}
+
+static int
+enqueueNavigationKey (BrailleDisplay *brl, KeyNumber modifier, KeyNumber key) {
+  const KeyGroup group = PG_GRP_NavigationKeys;
+  const int modifierSpecified = modifier != PG_KEY_None;
+
+  if (modifierSpecified && !enqueueKeyEvent(brl, group, modifier, 1)) return 0;
+  if (!enqueueKey(brl, group, key)) return 0;
+  if (modifierSpecified && !enqueueKeyEvent(brl, group, modifier, 0)) return 0;
+  return 1;
+}
+
+static int
+interpretNavigationKey (BrailleDisplay *brl, unsigned char key) {
+#define KEY(code,modifier,key) case (code): return enqueueNavigationKey(brl, (modifier), (key))
+  switch (key) {
+    KEY(0X15, PG_KEY_None, PG_KEY_Left);
+    KEY(0X4D, PG_KEY_None, PG_KEY_Right);
+    KEY(0X3D, PG_KEY_None, PG_KEY_Up);
+    KEY(0X54, PG_KEY_None, PG_KEY_Down);
+
+    KEY(0X16, PG_KEY_None, PG_KEY_Home);
+    KEY(0X1C, PG_KEY_None, PG_KEY_Enter);
+    KEY(0X36, PG_KEY_None, PG_KEY_End);
+    KEY(0X2C, PG_KEY_None, PG_KEY_Escape);
+
+    KEY(0X27, PG_KEY_LeftControl, PG_KEY_Left);
+    KEY(0X28, PG_KEY_LeftControl, PG_KEY_Right);
+    KEY(0X21, PG_KEY_LeftControl, PG_KEY_Up);
+    KEY(0X22, PG_KEY_LeftControl, PG_KEY_Down);
+
+    KEY(0X3F, PG_KEY_LeftControl, PG_KEY_Enter);
+    KEY(0X2F, PG_KEY_LeftControl, PG_KEY_End);
+    KEY(0X56, PG_KEY_LeftControl, PG_KEY_Escape);
+
+    KEY(0X1F, PG_KEY_LeftShift, PG_KEY_Left);
+    KEY(0X20, PG_KEY_LeftShift, PG_KEY_Right);
+    KEY(0X5B, PG_KEY_LeftShift, PG_KEY_Down);
+
+    KEY(0X17, PG_KEY_LeftShift, PG_KEY_Home);
+    KEY(0X3A, PG_KEY_LeftShift, PG_KEY_Enter);
+    KEY(0X3B, PG_KEY_LeftShift, PG_KEY_End);
+    KEY(0X18, PG_KEY_LeftShift, PG_KEY_Escape);
+
+    KEY(0X37, PG_KEY_RightShift, PG_KEY_Left);
+    KEY(0X33, PG_KEY_RightShift, PG_KEY_Right);
+    KEY(0X38, PG_KEY_RightShift, PG_KEY_Down);
+
+    KEY(0X2A, PG_KEY_RightShift, PG_KEY_Home);
+    KEY(0X31, PG_KEY_RightShift, PG_KEY_Enter);
+    KEY(0X32, PG_KEY_RightShift, PG_KEY_End);
+    KEY(0X30, PG_KEY_RightShift, PG_KEY_Escape);
+
+    default:
+      break;
+  }
+#undef KEY
+
+  return 0;
+}
+
+static int
+interpretSimulationKey (BrailleDisplay *brl, unsigned char key) {
+  switch (key) {
+    default:
+      break;
+  }
+
+  return interpretNavigationKey(brl, key);
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  InputPacket packet;
+  int length;
+
+  while ((length = readPacket(brl, &packet))) {
+    switch (packet.data.type) {
+      case IPT_KEY_NAVIGATION:
+        if (interpretNavigationKey(brl, packet.data.fields.key.value)) continue;
+        break;
+
+      case IPT_KEY_SIMULATION:
+        if (interpretSimulationKey(brl, packet.data.fields.key.value)) continue;
+        break;
+
+      case IPT_KEY_ROUTING: {
+        unsigned char code = packet.data.fields.key.value;
+        KeyGroup group;
+        KeyNumber number;
+
+        if ((code >= 81) && (code <= 82)) {
+          group = PG_GRP_NavigationKeys;
+          number = PG_KEY_Status + (code - 81);
+        } else if ((code > 0) && (code <= brl->textColumns)) {
+          group = PG_GRP_RoutingKeys;
+          number = code - 1;
+        } else {
+          break;
+        }
+
+        enqueueKey(brl, group, number);
+        continue;
+      }
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet.bytes, length);
+  }
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+
+  return EOF;
+}
diff --git a/Drivers/Braille/Pegasus/brldefs-pg.h b/Drivers/Braille/Pegasus/brldefs-pg.h
new file mode 100644
index 0000000..def9557
--- /dev/null
+++ b/Drivers/Braille/Pegasus/brldefs-pg.h
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PG_BRLDEFS
+#define BRLTTY_INCLUDED_PG_BRLDEFS
+
+typedef enum {
+  PG_KEY_None = 0,
+
+  PG_KEY_LeftShift,
+  PG_KEY_RightShift,
+  PG_KEY_LeftControl,
+  PG_KEY_RighTControl,
+
+  PG_KEY_Left,
+  PG_KEY_Right,
+  PG_KEY_Up,
+  PG_KEY_Down,
+
+  PG_KEY_Home,
+  PG_KEY_End,
+  PG_KEY_Enter,
+  PG_KEY_Escape,
+
+  PG_KEY_Status
+} PG_NavigationKey;
+
+typedef enum {
+  PG_GRP_NavigationKeys = 0,
+  PG_GRP_RoutingKeys
+} PG_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_PG_BRLDEFS */ 
diff --git a/Drivers/Braille/Seika/Makefile.in b/Drivers/Braille/Seika/Makefile.in
new file mode 100644
index 0000000..a8ef665
--- /dev/null
+++ b/Drivers/Braille/Seika/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = sk
+DRIVER_NAME = Seika
+DRIVER_USAGE = 3/4/5 (40), 80, Mini (16)
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Seika/braille.c b/Drivers/Braille/Seika/braille.c
new file mode 100644
index 0000000..f7b7e19
--- /dev/null
+++ b/Drivers/Braille/Seika/braille.c
@@ -0,0 +1,856 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-sk.h"
+
+BEGIN_KEY_NAME_TABLE(display)
+  KEY_NAME_ENTRY(SK_BDP_K1, "K1"),
+  KEY_NAME_ENTRY(SK_BDP_K2, "K2"),
+  KEY_NAME_ENTRY(SK_BDP_K3, "K3"),
+  KEY_NAME_ENTRY(SK_BDP_K4, "K4"),
+  KEY_NAME_ENTRY(SK_BDP_K5, "K5"),
+  KEY_NAME_ENTRY(SK_BDP_K6, "K6"),
+  KEY_NAME_ENTRY(SK_BDP_K7, "K7"),
+  KEY_NAME_ENTRY(SK_BDP_K8, "K8"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(notetaker)
+  KEY_NAME_ENTRY(SK_NTK_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(SK_NTK_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(SK_NTK_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(SK_NTK_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(SK_NTK_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(SK_NTK_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(SK_NTK_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(SK_NTK_Dot8, "Dot8"),
+
+  KEY_NAME_ENTRY(SK_NTK_Backspace, "Backspace"),
+  KEY_NAME_ENTRY(SK_NTK_Space, "Space"),
+
+  KEY_NAME_ENTRY(SK_NTK_LeftButton, "LeftButton"),
+  KEY_NAME_ENTRY(SK_NTK_RightButton, "RightButton"),
+
+  KEY_NAME_ENTRY(SK_NTK_LeftJoystickPress, "LeftJoystickPress"),
+  KEY_NAME_ENTRY(SK_NTK_LeftJoystickLeft, "LeftJoystickLeft"),
+  KEY_NAME_ENTRY(SK_NTK_LeftJoystickRight, "LeftJoystickRight"),
+  KEY_NAME_ENTRY(SK_NTK_LeftJoystickUp, "LeftJoystickUp"),
+  KEY_NAME_ENTRY(SK_NTK_LeftJoystickDown, "LeftJoystickDown"),
+
+  KEY_NAME_ENTRY(SK_NTK_RightJoystickPress, "RightJoystickPress"),
+  KEY_NAME_ENTRY(SK_NTK_RightJoystickLeft, "RightJoystickLeft"),
+  KEY_NAME_ENTRY(SK_NTK_RightJoystickRight, "RightJoystickRight"),
+  KEY_NAME_ENTRY(SK_NTK_RightJoystickUp, "RightJoystickUp"),
+  KEY_NAME_ENTRY(SK_NTK_RightJoystickDown, "RightJoystickDown"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(SK_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(bdp)
+  KEY_NAME_TABLE(display),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(ntk)
+  KEY_NAME_TABLE(notetaker),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(bdp)
+DEFINE_KEY_TABLE(ntk)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(bdp),
+  &KEY_TABLE_DEFINITION(ntk),
+END_KEY_TABLE_LIST
+
+typedef enum {
+  IPT_identity,
+  IPT_keys,
+  IPT_routing,
+  IPT_combined
+} InputPacketType;
+
+typedef struct {
+  unsigned char bytes[4 + 0XFF];
+  unsigned char type;
+
+  union {
+    KeyNumberSet keys;
+    const unsigned char *routing;
+
+    struct {
+      KeyNumberSet keys;
+      const unsigned char *routing;
+    } combined;
+
+    struct {
+      unsigned char cellCount;
+      unsigned char keyCount;
+      unsigned char routingCount;
+    } identity;
+  } fields;
+} InputPacket;
+
+typedef struct {
+  const char *name;
+  const KeyTableDefinition *keyTableDefinition;
+  void (*initializeData) (void);
+  int (*readPacket) (BrailleDisplay *brl, InputPacket *packet);
+  BrailleRequestWriter *writeIdentifyRequest;
+  int (*writeCells) (BrailleDisplay *brl);
+} ProtocolOperations;
+
+typedef struct {
+  const ProtocolOperations *const *protocols;
+} InputOutputOperations;
+
+static const InputOutputOperations *io;
+static const ProtocolOperations *protocol;
+
+static unsigned char keyCount;
+static unsigned char routingCount;
+
+static unsigned char forceRewrite;
+static unsigned char textCells[80];
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return protocol->readPacket(brl, packet);
+}
+
+static int
+writePacket (BrailleDisplay *brl, const void *packet, size_t size) {
+  return writeBraillePacket(brl, NULL, packet, size);
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  const InputPacket *response = packet;
+
+  return (response->type == IPT_identity)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED;
+}
+
+typedef enum {
+  TBT_ANY = 0X80,
+  TBT_DECIMAL,
+  TBT_SIZE,
+  TBT_ID1,
+  TBT_ID2,
+  TBT_KEYS
+} TemplateByteType;
+
+typedef struct {
+  const unsigned char *bytes;
+  unsigned char length;
+  unsigned char type;
+} TemplateEntry;
+
+#define TEMPLATE_ENTRY(name) { \
+  .bytes = templateString_##name, \
+  .length = sizeof(templateString_##name), \
+  .type = IPT_##name \
+}
+
+static const unsigned char templateString_keys[] = {
+  TBT_KEYS, TBT_KEYS
+};
+static const TemplateEntry templateEntry_keys = TEMPLATE_ENTRY(keys);
+
+static int
+ntvWriteCells0 (BrailleDisplay *brl) {
+  return 1;
+}
+
+static int
+ntvWriteCells40 (BrailleDisplay *brl) {
+  static const unsigned char header[] = {
+    0XFF, 0XFF,
+    0X73, 0X65, 0X69, 0X6B, 0X61,
+    0X00
+  };
+
+  unsigned char packet[sizeof(header) + (brl->textColumns*2)];
+  unsigned char *byte = packet;
+
+  byte = mempcpy(byte, header, sizeof(header));
+
+  {
+    unsigned int i;
+
+    for (i=0; i<brl->textColumns; i+=1) {
+      *byte++ = 0;
+      *byte++ = translateOutputCell(textCells[i]);
+    }
+  }
+
+  return writePacket(brl, packet, byte-packet);
+}
+
+static int
+ntvWriteCells80 (BrailleDisplay *brl) {
+  static const unsigned char header[] = {
+    0XFF, 0XFF,
+    0X73, 0X38, 0X30,
+    0X00, 0X00, 0X00
+  };
+
+  unsigned char packet[sizeof(header) + brl->textColumns];
+  unsigned char *byte = packet;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  byte = translateOutputCells(byte, textCells, brl->textColumns);
+  return writePacket(brl, packet, byte-packet);
+}
+
+typedef struct {
+  int (*ntvWriteCells) (BrailleDisplay *brl);
+  const TemplateEntry *routingTemplate;
+} ModelEntry;
+
+static const ModelEntry *bdpModel;
+
+static int
+bdpSetModel (unsigned char cellCount) {
+  switch (cellCount) {
+    case 0: {
+      static const ModelEntry modelEntry = {
+        .ntvWriteCells = ntvWriteCells0
+      };
+
+      bdpModel = &modelEntry;
+      return 1;
+    }
+
+    case 40: {
+      static const unsigned char templateString_routing[] = {
+        0X00, 0X08, 0X09, 0X00, 0X00, 0X00, 0X00,
+        TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY,
+        0X00, 0X08, 0X09, 0X00, 0X00, 0X00, 0X00,
+        0X00, 0X00, 0X00, 0X00, 0X00
+      };
+      static const TemplateEntry templateEntry_routing = TEMPLATE_ENTRY(routing);
+
+      static const ModelEntry modelEntry = {
+        .ntvWriteCells = ntvWriteCells40,
+        .routingTemplate = &templateEntry_routing
+      };
+
+      bdpModel = &modelEntry;
+      return 1;
+    }
+
+    case 80: {
+      static const unsigned char templateString_routing[] = {
+        0X00, 0X08, 0X0F, 0X00, 0X00, 0X00, 0X00,
+        TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY,
+        TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY,
+        0X00, 0X00, 0X00, 0X00, 0X00
+      };
+      static const TemplateEntry templateEntry_routing = TEMPLATE_ENTRY(routing);
+
+      static const ModelEntry modelEntry = {
+        .ntvWriteCells = ntvWriteCells80,
+        .routingTemplate = &templateEntry_routing
+      };
+
+      bdpModel = &modelEntry;
+      return 1;
+    }
+
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+static void
+bdpInitializeData (void) {
+  bdpSetModel(0);
+}
+
+typedef struct {
+  const TemplateEntry *const *const templates;
+  const TemplateEntry *template;
+  const TemplateEntry *const alternate;
+} BdpReadPacketData;
+
+static BraillePacketVerifierResult
+bdpVerifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  BdpReadPacketData *rpd = data;
+  size_t offset = size - 1;
+  unsigned char byte = bytes[offset];
+
+checkByte:
+  switch (size) {
+    case 1: {
+      const TemplateEntry *const *templateAddress = rpd->templates;
+
+      while ((rpd->template = *templateAddress++)) {
+        if (byte == *rpd->template->bytes) break;
+      }
+
+      if (!rpd->template) {
+        if ((byte & 0XE0) != 0X60) return BRL_PVR_INVALID;
+        rpd->template = &templateEntry_keys;
+      }
+
+      break;
+    }
+
+    default: {
+      unsigned char type = rpd->template->bytes[offset];
+
+      switch (type) {
+        case TBT_ANY:
+          break;
+
+        case TBT_DECIMAL:
+          if (byte < '0') goto unexpectedByte;
+          if (byte > '9') goto unexpectedByte;
+          break;
+
+        case TBT_SIZE:
+          if (byte == 40) break;
+          if (byte == 80) break;
+          goto unexpectedByte;
+
+        case TBT_ID1:
+          if (!strchr("3458", byte)) goto unexpectedByte;
+          break;
+
+        case TBT_ID2:
+          if (!strchr("0 ", byte)) goto unexpectedByte;
+          break;
+
+        case TBT_KEYS:
+          if ((byte & 0XE0) != 0XE0) goto unexpectedByte;
+          break;
+
+        default:
+          if (byte != type) goto unexpectedByte;
+          break;
+      }
+
+      break;
+    }
+  }
+
+  *length = rpd->template->length;
+  return BRL_PVR_INCLUDE;
+
+unexpectedByte:
+  if ((offset == 1) && (rpd->template->type == IPT_identity)) {
+    rpd->template = rpd->alternate;
+    goto checkByte;
+  }
+
+  return BRL_PVR_INVALID;
+}
+
+static int
+bdpReadPacket (
+  BrailleDisplay *brl,
+  InputPacket *packet,
+  const TemplateEntry *identityTemplate,
+  const TemplateEntry *alternateTemplate,
+  void (*interpretIdentity) (InputPacket *packet)
+) {
+  const TemplateEntry *const templateTable[] = {
+    identityTemplate,
+    bdpModel->routingTemplate,
+    NULL
+  };
+
+  BdpReadPacketData rpd = {
+    .templates = templateTable,
+    .alternate = alternateTemplate,
+    .template = NULL
+  };
+
+  size_t length = readBraillePacket(brl, NULL,
+                                    packet->bytes, sizeof(packet->bytes),
+                                    bdpVerifyPacket, &rpd);
+
+  if (length) {
+    switch ((packet->type = rpd.template->type)) {
+      case IPT_identity:
+        interpretIdentity(packet);
+        bdpSetModel(packet->fields.identity.cellCount);
+        break;
+
+      case IPT_keys: {
+        const unsigned char *byte = packet->bytes + length;
+        packet->fields.keys = 0;
+
+        do {
+          packet->fields.keys <<= 8;
+          packet->fields.keys |= *--byte & 0X1F;
+        } while (byte != packet->bytes);
+
+        break;
+      }
+
+      case IPT_routing:
+        packet->fields.routing = &packet->bytes[7];
+        break;
+    }
+  }
+
+  return length;
+}
+
+static void
+pbcInterpretIdentity (InputPacket *packet) {
+  packet->fields.identity.cellCount = packet->bytes[2];
+  packet->fields.identity.keyCount = 16;
+  packet->fields.identity.routingCount = packet->fields.identity.cellCount;
+}
+
+static int
+pbcReadPacket (BrailleDisplay *brl, InputPacket *packet) {
+  static const unsigned char templateString_identity[] = {
+    0X00, 0X05, TBT_SIZE, 0X08,
+    TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY,
+    TBT_ANY, TBT_ANY, TBT_ANY, TBT_ANY
+  };
+  static const TemplateEntry identityTemplate = TEMPLATE_ENTRY(identity);
+
+  return bdpReadPacket(brl, packet, &identityTemplate, bdpModel->routingTemplate, pbcInterpretIdentity);
+}
+
+static int
+pbcWriteIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {0XFF, 0XFF, 0X0A};
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static int
+pbcWriteCells (BrailleDisplay *brl) {
+  static const unsigned char header[] = {
+    0XFF, 0XFF, 0X04,
+    0X00, 0X63, 0X00
+  };
+
+  unsigned char packet[sizeof(header) + 2 + (brl->textColumns * 2)];
+  unsigned char *byte = packet;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  *byte++ = brl->textColumns * 2;
+  *byte++ = 0;
+
+  {
+    int i;
+    for (i=0; i<brl->textColumns; i+=1) {
+      *byte++ = 0;
+      *byte++ = translateOutputCell(textCells[i]);
+    }
+  }
+
+  return writePacket(brl, packet, byte-packet);
+}
+
+static const ProtocolOperations pbcProtocolOperations = {
+  .name = "PowerBraille Compatibility",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bdp),
+  .initializeData = bdpInitializeData,
+  .readPacket = pbcReadPacket,
+  .writeIdentifyRequest = pbcWriteIdentifyRequest,
+  .writeCells = pbcWriteCells
+};
+
+static void
+ntvInterpretIdentity (InputPacket *packet) {
+  packet->fields.identity.cellCount = (packet->bytes[5] == '8')? 80: 40;
+  packet->fields.identity.keyCount = 16;
+  packet->fields.identity.routingCount = packet->fields.identity.cellCount;
+}
+
+static int
+ntvReadPacket (BrailleDisplay *brl, InputPacket *packet) {
+  static const unsigned char templateString_identity[] = {
+    0X73, 0X65, 0X69, 0X6B, 0X61, TBT_ID1, TBT_ID2,
+    0X76, TBT_DECIMAL, 0X2E, TBT_DECIMAL, TBT_DECIMAL
+  };
+  static const TemplateEntry identityTemplate = TEMPLATE_ENTRY(identity);
+
+  return bdpReadPacket(brl, packet, &identityTemplate, &templateEntry_keys, ntvInterpretIdentity);
+}
+
+static int
+ntvWriteIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {0XFF, 0XFF, 0X1C};
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static int
+ntvWriteCells (BrailleDisplay *brl) {
+  return bdpModel->ntvWriteCells(brl);
+}
+
+static const ProtocolOperations ntvProtocolOperations = {
+  .name = "Seika Braille Display",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(bdp),
+  .initializeData = bdpInitializeData,
+  .readPacket = ntvReadPacket,
+  .writeIdentifyRequest = ntvWriteIdentifyRequest,
+  .writeCells = ntvWriteCells
+};
+
+static void
+ntkInitializeData (void) {
+}
+
+static BraillePacketVerifierResult
+ntkVerifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      *length = 4;
+    case 2:
+      if (byte != 0XFF) return BRL_PVR_INVALID;
+      break;
+
+    case 4:
+      *length += byte;
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static int
+ntkReadPacket (BrailleDisplay *brl, InputPacket *packet) {
+  size_t length;
+
+  while ((length = readBraillePacket(brl, NULL,
+                                     packet->bytes, sizeof(packet->bytes),
+                                     ntkVerifyPacket, NULL))) {
+    unsigned char type = packet->bytes[2];
+
+    switch (type) {
+      case 0XA2:
+        packet->type = IPT_identity;
+        packet->fields.identity.cellCount = packet->bytes[5];
+        packet->fields.identity.keyCount = packet->bytes[4];
+        packet->fields.identity.routingCount = packet->bytes[6];
+        break;
+
+      case 0XA4:
+        packet->type = IPT_routing;
+        packet->fields.routing = &packet->bytes[4];
+        break;
+
+      {
+        KeyNumberSet *keys;
+        const unsigned char *byte;
+
+      case 0XA6:
+        packet->type = IPT_keys;
+        keys = &packet->fields.keys;
+        byte = packet->bytes + length;
+        goto doKeys;
+
+      case 0XA8:
+        packet->type = IPT_combined;
+        keys = &packet->fields.combined.keys;
+        byte = packet->fields.combined.routing = packet->bytes +  4 + ((keyCount + 7) / 8);
+        goto doKeys;
+
+      doKeys:
+        *keys = 0;
+
+        while (--byte != &packet->bytes[3]) {
+          *keys <<= 8;
+          *keys |= *byte;
+        }
+
+        break;
+      }
+
+      default:
+        logUnknownPacket(type);
+        continue;
+    }
+
+    break;
+  }
+
+  return length;
+}
+
+static int
+ntkWriteIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {0XFF, 0XFF, 0XA1};
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static int
+ntkWriteCells (BrailleDisplay *brl) {
+  static const unsigned char header[] = {0XFF, 0XFF, 0XA3};
+  unsigned char packet[sizeof(header) + 1 + brl->textColumns];
+  unsigned char *byte = packet;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  *byte++ = brl->textColumns;
+  byte = translateOutputCells(byte, textCells, brl->textColumns);
+
+  return writePacket(brl, packet, byte-packet);
+}
+
+static const ProtocolOperations ntkProtocolOperations = {
+  .name = "Seika Note Taker",
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(ntk),
+  .initializeData = ntkInitializeData,
+  .readPacket = ntkReadPacket,
+  .writeIdentifyRequest = ntkWriteIdentifyRequest,
+  .writeCells = ntkWriteCells
+};
+
+static const ProtocolOperations *const allProtocols[] = {
+  &ntkProtocolOperations,
+  &ntvProtocolOperations,
+  &pbcProtocolOperations,
+  NULL
+};
+
+static const ProtocolOperations *const nativeProtocols[] = {
+  &ntkProtocolOperations,
+  &ntvProtocolOperations,
+  NULL
+};
+
+static const InputOutputOperations serialOperations = {
+  .protocols = nativeProtocols
+};
+
+static const InputOutputOperations usbOperations = {
+  .protocols = allProtocols
+};
+
+static const InputOutputOperations bluetoothOperations = {
+  .protocols = nativeProtocols
+};
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS,
+    .baud = 9600
+  };
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_10C4_EA60)
+    "Silicon Labs",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_STRING_LIST(usbManufacturers_10C4_EA80)
+    "Silicon Laboratories",
+  END_USB_STRING_LIST
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* Braille Display */
+      .vendor=0X10C4, .product=0XEA60,
+      .manufacturers = usbManufacturers_10C4_EA60,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1, .outputEndpoint=1,
+      .serial=&serialParameters
+    },
+
+    { /* Note Taker */
+      .vendor=0X10C4, .product=0XEA80,
+      .manufacturers = usbManufacturers_10C4_EA80,
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1,
+      .serial=&serialParameters
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &serialOperations;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbOperations;
+
+  descriptor.bluetooth.channelNumber = 1;
+  descriptor.bluetooth.options.applicationData = &bluetoothOperations;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    io = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (connectResource(brl, device)) {
+    const ProtocolOperations *const *protocolAddress = io->protocols;
+
+    while ((protocol = *protocolAddress++)) {
+      InputPacket response;
+
+      logMessage(LOG_DEBUG, "trying protocol %s", protocol->name);
+      protocol->initializeData();
+
+      if (probeBrailleDisplay(brl, 2, NULL, 200,
+                              protocol->writeIdentifyRequest,
+                              readPacket, &response, sizeof(response.bytes),
+                              isIdentityResponse)) {
+        logMessage(LOG_DEBUG, "Seika Protocol: %s", protocol->name);
+        logMessage(LOG_DEBUG, "Seika Size: %u", response.fields.identity.cellCount);
+
+        brl->textColumns = response.fields.identity.cellCount;
+        keyCount = response.fields.identity.keyCount;
+        routingCount = response.fields.identity.routingCount;
+
+        setBrailleKeyTable(brl, protocol->keyTableDefinition);
+        makeOutputTable(dotsTable_ISO11548_1);
+
+        forceRewrite = 1;
+        return 1;
+      }
+    }
+
+    disconnectBrailleResource(brl, NULL);
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(textCells, brl->buffer, brl->textColumns, NULL, NULL, &forceRewrite)) {
+    if (!protocol->writeCells(brl)) return 0;
+  }
+
+  return 1;
+}
+
+static void
+processKeys (BrailleDisplay *brl, KeyNumberSet keys, const unsigned char *routing) {
+  KeyValue pressedKeys[keyCount + routingCount];
+  unsigned int pressedCount = 0;
+
+  if (keys) {
+    KeyNumberSet bit = KEY_NUMBER_BIT(0);
+    KeyNumber number = 0;
+
+    while (number < keyCount) {
+      if (keys & bit) {
+        KeyValue *kv = &pressedKeys[pressedCount++];
+
+        enqueueKeyEvent(brl, (kv->group = SK_GRP_NavigationKeys), (kv->number = number), 1);
+        if (!(keys &= ~bit)) break;
+      }
+
+      bit <<= 1;
+      number += 1;
+    }
+  }
+
+  if (routing) {
+    const unsigned char *byte = routing;
+    unsigned char number = 0;
+
+    while (number < routingCount) {
+      if (*byte) {
+        unsigned char bit = 0X1;
+
+        do {
+          if (*byte & bit) {
+            KeyValue *kv = &pressedKeys[pressedCount++];
+
+            enqueueKeyEvent(brl, (kv->group = SK_GRP_RoutingKeys), (kv->number = number), 1);
+          }
+
+          number += 1;
+        } while ((bit <<= 1));
+      } else {
+        number += 8;
+      }
+
+      byte += 1;
+    }
+  }
+
+  while (pressedCount) {
+    KeyValue *kv = &pressedKeys[--pressedCount];
+    enqueueKeyEvent(brl, kv->group, kv->number, 0);
+  }
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  InputPacket packet;
+  size_t length;
+
+  while ((length = protocol->readPacket(brl, &packet))) {
+    switch (packet.type) {
+      case IPT_keys:
+        processKeys(brl, packet.fields.keys, NULL);
+        continue;
+
+      case IPT_routing:
+        processKeys(brl, 0, packet.fields.routing);
+        continue;
+
+      case IPT_combined:
+        processKeys(brl, packet.fields.combined.keys, packet.fields.combined.routing);
+        continue;
+
+      default:
+        break;
+    }
+
+    logUnexpectedPacket(packet.bytes, length);
+  }
+  if (errno != EAGAIN) return BRL_CMD_RESTARTBRL;
+
+  return EOF;
+}
diff --git a/Drivers/Braille/Seika/brldefs-sk.h b/Drivers/Braille/Seika/brldefs-sk.h
new file mode 100644
index 0000000..95f3ff7
--- /dev/null
+++ b/Drivers/Braille/Seika/brldefs-sk.h
@@ -0,0 +1,67 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SK_BRLDEFS
+#define BRLTTY_INCLUDED_SK_BRLDEFS
+
+typedef enum {
+  SK_BDP_K1 =  0,
+  SK_BDP_K7 =  1,
+  SK_BDP_K8 =  2,
+  SK_BDP_K6 =  3,
+  SK_BDP_K5 =  4,
+  SK_BDP_K2 =  9,
+  SK_BDP_K3 = 11,
+  SK_BDP_K4 = 12
+} SK_BrailleDisplayKey;
+
+typedef enum {
+  SK_NTK_Dot1               =  0,
+  SK_NTK_Dot2               =  1,
+  SK_NTK_Dot3               =  2,
+  SK_NTK_Dot4               =  3,
+  SK_NTK_Dot5               =  4,
+  SK_NTK_Dot6               =  5,
+  SK_NTK_Dot7               =  6,
+  SK_NTK_Dot8               =  7,
+
+  SK_NTK_Backspace          =  8,
+  SK_NTK_Space              =  9,
+
+  SK_NTK_LeftButton         = 10,
+  SK_NTK_RightButton        = 11,
+
+  SK_NTK_LeftJoystickPress  = 12,
+  SK_NTK_LeftJoystickLeft   = 13,
+  SK_NTK_LeftJoystickRight  = 14,
+  SK_NTK_LeftJoystickUp     = 15,
+  SK_NTK_LeftJoystickDown   = 16,
+
+  SK_NTK_RightJoystickPress = 17,
+  SK_NTK_RightJoystickLeft  = 18,
+  SK_NTK_RightJoystickRight = 19,
+  SK_NTK_RightJoystickUp    = 20,
+  SK_NTK_RightJoystickDown  = 21
+} SK_NoteTakerKey;
+
+typedef enum {
+  SK_GRP_NavigationKeys = 0,
+  SK_GRP_RoutingKeys
+} SK_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_SK_BRLDEFS */ 
diff --git a/Drivers/Braille/Skeleton/Makefile.in b/Drivers/Braille/Skeleton/Makefile.in
new file mode 100644
index 0000000..e4f8a0e
--- /dev/null
+++ b/Drivers/Braille/Skeleton/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = xx
+DRIVER_NAME = DirectoryName
+DRIVER_USAGE = basic example
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Skeleton/braille.c b/Drivers/Braille/Skeleton/braille.c
new file mode 100644
index 0000000..70948c3
--- /dev/null
+++ b/Drivers/Braille/Skeleton/braille.c
@@ -0,0 +1,195 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+#include "brldefs-xx.h"
+
+#define PROBE_RETRY_LIMIT 2
+#define PROBE_INPUT_TIMEOUT 1000
+#define MAXIMUM_RESPONSE_SIZE (0XFF + 4)
+#define MAXIMUM_TEXT_CELLS 0XFF
+
+BEGIN_KEY_NAME_TABLE(navigation)
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(navigation),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+END_KEY_TABLE_LIST
+
+struct BrailleDataStruct {
+  struct {
+    unsigned char rewrite;
+    unsigned char cells[MAXIMUM_TEXT_CELLS];
+  } text;
+};
+
+static int
+writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
+  return writeBraillePacket(brl, NULL, bytes, count);
+}
+
+static int
+writePacket (BrailleDisplay *brl, const unsigned char *packet, size_t size) {
+  unsigned char bytes[size];
+  unsigned char *byte = bytes;
+
+  byte = mempcpy(byte, packet, size);
+
+  return writeBytes(brl, bytes, byte-bytes);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  unsigned char byte = bytes[size-1];
+
+  switch (size) {
+    case 1:
+      switch (byte) {
+        default:
+          return BRL_PVR_INVALID;
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, void *packet, size_t size) {
+  return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS
+  };
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeIdentifyRequest (BrailleDisplay *brl) {
+  static const unsigned char packet[] = {0};
+  return writePacket(brl, packet, sizeof(packet));
+}
+
+static BrailleResponseResult
+isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
+  return BRL_RSP_UNEXPECTED;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      unsigned char response[MAXIMUM_RESPONSE_SIZE];
+
+      if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
+                              writeIdentifyRequest,
+                              readPacket, &response, sizeof(response),
+                              isIdentityResponse)) {
+        setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all));
+
+        makeOutputTable(dotsTable_ISO11548_1);
+      //MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X08, 0X10, 0X20, 0X40, 0X80);
+
+        brl->data->text.rewrite = 1;
+        return 1;
+      }
+
+      disconnectBrailleResource(brl, NULL);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (cellsHaveChanged(brl->data->text.cells, brl->buffer, brl->textColumns,
+                       NULL, NULL, &brl->data->text.rewrite)) {
+    unsigned char cells[brl->textColumns];
+
+    translateOutputCells(cells, brl->data->text.cells, brl->textColumns);
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  unsigned char packet[MAXIMUM_RESPONSE_SIZE];
+  size_t size;
+
+  while ((size = readPacket(brl, packet, sizeof(packet)))) {
+    logUnexpectedPacket(packet, size);
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Skeleton/brldefs-xx.h b/Drivers/Braille/Skeleton/brldefs-xx.h
new file mode 100644
index 0000000..e140a29
--- /dev/null
+++ b/Drivers/Braille/Skeleton/brldefs-xx.h
@@ -0,0 +1,22 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_XX_BRLDEFS
+#define BRLTTY_INCLUDED_XX_BRLDEFS
+
+#endif /* BRLTTY_INCLUDED_XX_BRLDEFS */ 
diff --git a/Drivers/Braille/TSI/Makefile.in b/Drivers/Braille/TSI/Makefile.in
new file mode 100644
index 0000000..b8bfec0
--- /dev/null
+++ b/Drivers/Braille/TSI/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = ts
+DRIVER_NAME = TSI
+DRIVER_USAGE = Navigator 20/40/80, PowerBraille 40/65/80
+DRIVER_VERSION = 2.74 (April 2004)
+DRIVER_DEVELOPERS = Stéphane Doyon <s.doyon@videotron.ca>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/TSI/README b/Drivers/Braille/TSI/README
new file mode 100644
index 0000000..eebeb07
--- /dev/null
+++ b/Drivers/Braille/TSI/README
@@ -0,0 +1,239 @@
+This is the BRLTTY driver for TSI braille displays,
+Version 2.74 April 2004
+
+Author and maintainer:
+        Stéphane Doyon
+E-Mail: s.doyon@videotron.ca
+
+This driver contains the code to support most of Telesensory Systems' braille
+displays. It is a part of the source code for BRLTTY. The information here
+pertains only to the support of the TSI displays. You must also read the
+documentation of the main module of BRLTTY.
+
+There is only a handful of BRLTTY users. If you are trying out my driver,
+please let me know! I am willing to help and I am open to suggestions.
+
+See the headers of brl.c for history notes.
+
+Supported braille displays
+==========================
+  Navigator 20 / 40 / 80 (latest firmware version only?)
+  PowerBraille 40 / 65 / 80
+
+Unsupported displays:
+  Older firmware versions of the navigator models may not work.
+
+I have used older versions of this driver every day for over two years with a
+Navigator 40, so I can say that BRLTTY and this driver are pretty stable.
+Since version 2.0 of this driver, I have integrated support for PB80
+displays. I have used a PowerBraille 80 for nearly a year now, and a
+PowerBraille 40 for a few months. I have had success reports from about 10
+other people (most of them use a PowerBraille 40). Navigator 40 has not been
+tested since version 2.1 of this driver (I changed my Navigator 40 for a
+PowerBraille 40). Navigator 20 and 80 should work but has never been tested.
+
+I am currently trying to debug support for two emulators of the
+PowerBraille 40. 
+- BAUM displays (Vario?): This display emulates a PowerBraille 40. However,
+    it cannot operate at 19200baud as PowerBraille models can. The driver
+    now detects this and falls back to 9600baud. I don't know much about
+    BAUM display models. I have had two success reports but also one failure
+    report.
+- mdv mb408s: This display also emulates a PowerBraille 40 and it too is
+    capable of operating only at 9600baud. However it also has additional
+    timing constraints. Please uncomment the FORCE_FULL_SEND_DELAY option 
+    in braille.h before compiling. This has been tested by only one person.
+However with both these emulators, there is another limitation: the key
+configurations are not the same as that of any TSI model and some key
+combinations for certain commands may be difficult or impossible to
+reproduce. Specific drivers need to be written for these models, and efforts
+are under way. Until then, the driver remains useful and at least partly
+functional even with this limitation.
+
+For impatients
+==============
+
+If you really want to go ahead right now without further reading, well then
+go for it: no special configuration is really necessary. All you have to know
+is the key to bring up the help screen:
+  - For navigator users: press the "cursor left" and "cursor right" keys
+    together.
+  - For PB40 users: press the first and the last cursor routing keys
+    together (cursor routing keys 1 and 40).
+  - For PB65/80 users: Press cursor routing keys 1 and 80 together, or press
+    the two small square buttons on top and to the left of the display.
+
+Connecting the display
+===========================
+
+Navigator users should connect their displays the same way as they do
+for use under DOS. PowerBraille users however might require additional
+instructions.
+
+This driver only supports the serial interface to the PowerBraille: you
+cannot use its parallel port. You will therefore need a serial cable to
+connect your PowerBraille to your computer. The cable must be "straight
+through" (not "null-modem"). Connect one end to one of the COM ports of
+your computer and the other to the DCE port (the 9-pin female connector)
+on the PowerBraille.
+
+Configuration of the driver
+===========================
+
+The file braille.h contains some few parameters specific to this driver
+which you can modify, but you really don't have to. No adjustments are
+necessary: everything will be autodetected by the driver when you start BRLTTY.
+
+PowerBraille displays can operate at 19200 baud. If you don't trust your serial 
+port to operate at that speed (for example, if you're using DOS), you can 
+disable this feature with the HighBaud=no driver parameter, and crawl at 9600 
+baud.
+
+The HighBaud= driver parameter specifies whether or not the driver is permitted 
+to use 19200 baud. It can be set to either yes (the default) or no. Note that 
+if this parameter is set to no but the display has been configured to operate 
+at 19200 baud then it won't be autodetected.
+
+The SetBaud= driver parameter specifies which baud is to be used. It can be set 
+to 4800, 9600, or 19200. The default is to use 19200 baud if the display 
+supports it or to use the baud at which the display was autodetected if it 
+doesn't.
+
+You usually won't need to specify any of these driver parameters. A case where 
+you would, though, is if you're using a PowerBraille on DOS because DOS 
+doesn't support higher than 9600 baud. In this situation, you'll want to 
+specify the HighBaud=no driver parameter. You'll still have a problem, though, 
+if the PowerBraille is currently configured to use 19200 baud. If this is the 
+case, first connect it to a system that does support 19200 baud and specify the 
+SetBaud=9600 driver parameter. So, for example:
+
+   on Linux: -B SetBaud=9600
+   on DOS: -B HighBaud=no
+
+Note that braille driver parameters can be specified either via the -B 
+(uppercase) command line option or via the braille-parameters brltty.conf 
+directive. The format is name=value. So, for example:
+
+   on the command line: -B HighBaud=no
+   in brltty.conf: braille-parameters HighBaud=no
+
+Initialization and reset
+========================
+
+When BRLTTY is started, it will call a function within this driver to detect
+and initialize a braille display. If none is connected, then the whole
+BRLTTY process will sleep until a braille display is connected and turned on.
+Immediatly, it will be detected, identified, initialized, and updated with
+a welcome message, and then with some text from the screen. It will then be
+ready for use.
+
+This means that your braille display need not be active when your machine
+boots (when BRLTTY starts up).
+
+Furthermore, you may also turn off your display at any time while BRLTTY is
+running, take it away for the day while your computer is still running, and
+connect it back later. After 2 or 3 seconds, BRLTTY will realize that the
+display is off or disconnected. It will then go back to waiting for you to
+connect it and turn it on, at which point it will reinitialize it.
+
+Key bindings
+============
+
+The online help describes the key bindings: for each available function, the
+display key or combination of keys that activates it is listed. For details
+on the functions themselves, see the documentation of the main module of
+BRLTTY. Here again is how to bring up the help screen:
+  - For navigator users: press the "cursor left" and "cursor right" keys
+    together.
+  - For PB40 users: press the first and the last cursor routing keys
+    together (cursor routing keys 1 and 40).
+  - For PB65/80 users: Press cursor routing keys 1 and 80 together, or press
+    the two small square buttons on top and to the left of the display.
+
+I am open to suggestions. If you don't like the help screen, if you find the
+names of the keys confusing, if you find an inconsistency or if you just don't
+like the bindings I have chosen, send me your comments. It is not very hard to
+change which keys trigger which function. I have tryed to come up with the
+best arrangement but you might have a better idea.
+
+If any of the keys mentioned in the help screen do not work or are somehow
+inconsistant, please let me know.
+
+In general the key bindings are the same for all the diplays, Navigator
+and PowerBraille models alike. The Navigator 80 and the PoewrBraille 40 simply
+have some added key bindings to take advantage of their routing keys.
+Furthermore, certain key combinations that can conviniently be pressed on
+the Navigator 20/40 cannot be entered on a PowerBraille since it does not
+have the cursor left/right keys and since it uses rockers instead of button
+pairs.
+
+But you do not have to worry, since the help screen that is provided will
+vary according to the model of the display that is detected. In fact, the
+first line of the help screen identifies the display model that was detected.
+If this is incorrect, then you're in trouble and you should contact me!
+
+I've tryed to keep key bindings similar to what is used under DOS, but there
+are several differences. Basic movement of the braille display is the
+same, but several bindings have been added for more elaborate
+functionalities. You also have those keys that are mapped to the keyboard
+arrow keys, but I did not think it was really relevant to have a key act as the
+RETURN key. Also the link/unlink button does not exactly work the same: the
+rightmost of the front middle buttons simply jumps to the cursor; it is not
+a toggle. The left button toggles the link/unlink (whether or not the braille
+display should follow the cursor if it moves), but unlinking the braille
+display from the cursor can be done without bringing the braille window back
+to the cursor beeforehand.
+
+Also note that there is no function to combine lines and wrap words...
+There is however a function to skip identical lines.
+
+For Navigator 20/40 users:
+    You will find one interesting addition to cursor routing: now you do not
+only have a function that routes the cursor to the biginning of the display,
+you also have another function that brings it somewhere towards the end of
+the display. I find it quite useful. BRLTTY also supports a cut-and-paste
+functionality that can be triggered from the braille display. This has
+forced me to add a very strange twist to the interface. Users of braille
+displays with cursor routing keys normally will mark text to be cut/copied
+by using some combination of these routing keys. Since we don't have them,
+I've added a key binding that brings you into a special mode in which a
+"virtual" cursor appears. By moving that special cursor around you can mark up
+the start of the text to cut. As soon as this mark is made, you are returned
+to the normal mode of operation. You can then move to the end of the
+text to mark, reactivate the special cursor mode, and mark the end of the
+text. At that point the text will by copied in a buffer, and you'll be able to
+paste it wherever you want. Note that there also are two shortcut keys to do
+cut & paste: one combination states that the block of text to be copied starts
+at the beginning of the display and the other states that the block of text
+ends at the end of the braille display. Just experiment with it in an editor.
+you'll see it's great! Mail me if you're mixed up.
+
+For Navigator 80 / PowerBraile users:
+    You will find that several key bindings use combinations of cursor
+routing keys. You may not be used to that. In particular the cut-and-paste
+functions, used to mark the start and the end of a block of text to be
+copied, uses combinations of three keys. One more thing so that you don't get
+confused: it is impossible to mark the beginning of a block of text to
+cut/copy in the last two cells of the braille display, and it is impossible
+to mark the end of a block in the first two cells. There is a function that
+allows movement of the braille window by only a few characters left or
+right: this usually suffices to get around this limitation. Again if you
+have suggestions as to what would be most convenient, do not hesitate to
+contact me.
+
+Credits
+=======
+
+Thanks to Jason Fayre (I don't have his current address) for his patient help
+in getting PowerBraille 40 support working: he tested each version of the
+PowerBraile support until it worked. I myself did not have access to a PB40.
+We worked over E-mail.
+
+Thanks to Nicolas Pitre <nico@fluxnic.net> for extensive help writing the very
+first version of this driver. Initially, this code was derived from that of
+the driver for the Alva ABT40. Surprising how little they have in common now.
+
+-------------------------------------------------------------------------------
+Well that's about it! Try it out and do let me know how it goes PLEASE!
+
+Hope this is of use to you!
diff --git a/Drivers/Braille/TSI/TODO b/Drivers/Braille/TSI/TODO
new file mode 100644
index 0000000..b409472
--- /dev/null
+++ b/Drivers/Braille/TSI/TODO
@@ -0,0 +1,6 @@
+Coexistance of the key bindings for Nav 40, Nav 80, PB40 and PB80 is
+becoming difficult to manage, and impossible to customize. Should
+consider splitting the layouts. Also should check how others thought of
+doing user-configurable bindings. Finally should consider some mechanism
+for automatic help file generation, because they are a nuisance to
+maintain as well.
diff --git a/Drivers/Braille/TSI/braille.c b/Drivers/Braille/TSI/braille.c
new file mode 100644
index 0000000..2ddc0b6
--- /dev/null
+++ b/Drivers/Braille/TSI/braille.c
@@ -0,0 +1,903 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* TSI/braille.c - Braille display driver for TSI displays
+ *
+ * Written by Stéphane Doyon (s.doyon@videotron.ca)
+ *
+ * It attempts full support for Navigator 20/40/80 and Powerbraille 40/65/80.
+ * It is designed to be compiled into BRLTTY version 3.5.
+ *
+ * History:
+ * Version 2.74 apr2004: use message() to report low battery condition.
+ * Version 2.73 jan2004: Fix key bindings for speech commands for PB80.
+ *   Add CMD_SPKHOME to help.
+ * Version 2.72 jan2003: brl->buffer now allocated by core.
+ * Version 2.71: Added CMD_LEARN, BRL_CMD_NXPROMPT/CMD_PRPROMPT and CMD_SIXDOTS.
+ * Version 2.70: Added CR_CUTAPPEND, BRL_BLK_CUTLINE, BRL_BLK_SETMARK, BRL_BLK_GOTOMARK
+ *   and CR_SETLEFT. Changed binding for NXSEARCH.. Adjusted PB80 cut&paste
+ *   bindings. Replaced CMD_CUT_BEG/CMD_CUT_END by CR_CUTBEGIN/CR_CUTRECT,
+ *   and CMD_CSRJMP by CR_ROUTE+0. Adjusted cut_cursor for new cut&paste
+ *   bindings (untested).
+ * Version 2.61: Adjusted key bindings for preferences menu.
+ * Version 2.60: Use TCSADRAIN when closing serial port. Slight API and
+ *   name changes for BRLTTY 3.0. Argument to readbrl now ignore, instead
+ *   of being validated. 
+ * Version 2.59: Added bindings for CMD_LNBEG/LNEND.
+ * Version 2.58: Added bindings for CMD_BACK and CR_MSGATTRIB.
+ * Version 2.57: Fixed help screen/file for Nav80. We finally have a
+ *   user who confirms it works!
+ * Version 2.56: Added key binding for NXSEARCH.
+ * Version 2.55: Added key binding for NXINDENT and NXBLNKLNS.
+ * Version 2.54: Added key binding for switchvt.
+ * Version 2.53: The IXOFF bit in the termios setting was inverted?
+ * Version 2.52: Changed LOG_NOTICE to LOG_INFO. Was too noisy.
+ * Version 2.51: Added CMD_RESTARTSPEECH.
+ * Version 2.5: Added CMD_SPKHOME, sacrificed LNBEG and LNEND.
+ * Version 2.4: Refresh display even if unchanged after every now and then so
+ *   that it will clear up if it was garbled. Added speech key bindings (had
+ *   to change a few bindings to make room). Added SKPEOLBLNK key binding.
+ * Version 2.3: Reset serial port attributes at each detection attempt in
+ *   initbrl. This should help BRLTTY recover if another application (such
+ *   as kudzu) scrambles the serial port while BRLTTY is running.
+ * Unnumbered version: Fixes for dynmically loading drivers (declare all
+ *   non-exported functions and variables static).
+ * Version 2.2beta3: Option to disable CTS checking. Apparently, Vario
+ *   does not raise CTS when connected.
+ * Version 2.2beta1: Exploring problems with emulators of TSI (PB40): BAUM
+ *   and mdv mb408s. See if we can provide timing options for more flexibility.
+ * Version 2.1: Help screen fix for new keys in preferences menu.
+ * Version 2.1beta1: Less delays in writing braille to display for
+ *   nav20/40 and pb40, delays still necessary for pb80 on probably for nav80.
+ *   Additional routing keys for navigator. Cut&paste binding that combines
+ *   routing key and normal key.
+ * Version 2.0: Tested with Nav40 PB40 PB80. Support for functions added
+ *   in BRLTTY 2.0: added key bindings for new fonctions (attributes and
+ *   routing). Support for PB at 19200baud. Live detection of display, checks
+ *   both at 9600 and 19200baud. RS232 wire monitoring. Ping when idle to 
+ *   detect when display turned off and issue a CMD_RESTARTBRL.
+ * Version 1.2 (not released) introduces support for PB65/80. Rework of key
+ *   binding mechanism and readbrl(). Slight modifications to routing keys
+ *   support, + corrections. May have broken routing key support for PB40.
+ * Version 1.1 worked on nav40 and was reported to work on pb40.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parse.h"
+#include "io_generic.h"
+#include "message.h"
+
+typedef enum {
+  PARM_HIGH_BAUD,
+  PARM_SET_BAUD
+} DriverParameter;
+#define BRLPARMS "highbaud", "setbaud"
+
+#include "brl_driver.h"
+#include "braille.h"
+#include "brldefs-ts.h"
+
+BEGIN_KEY_NAME_TABLE(routing)
+  KEY_GROUP_ENTRY(TS_GRP_RoutingKeys, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(nav_small)
+  KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"),
+  KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"),
+  KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"),
+  KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_NavLeft, "NavLeft"),
+  KEY_NAME_ENTRY(TS_KEY_NavRight, "NavRight"),
+  KEY_NAME_ENTRY(TS_KEY_NavUp, "NavUp"),
+  KEY_NAME_ENTRY(TS_KEY_NavDown, "NavDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "ThumbLeft"),
+  KEY_NAME_ENTRY(TS_KEY_ThumbRight, "ThumbRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(nav_large)
+  KEY_NAME_ENTRY(TS_KEY_CursorLeft, "CursorLeft"),
+  KEY_NAME_ENTRY(TS_KEY_CursorRight, "CursorRight"),
+  KEY_NAME_ENTRY(TS_KEY_CursorUp, "CursorUp"),
+  KEY_NAME_ENTRY(TS_KEY_CursorDown, "CursorDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_NavLeft, "LeftOuter"),
+  KEY_NAME_ENTRY(TS_KEY_NavRight, "RightOuter"),
+  KEY_NAME_ENTRY(TS_KEY_NavUp, "LeftInner"),
+  KEY_NAME_ENTRY(TS_KEY_NavDown, "RightInner"),
+
+  KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "LeftThumb"),
+  KEY_NAME_ENTRY(TS_KEY_ThumbRight, "RightThumb"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(pb_small)
+  KEY_NAME_ENTRY(TS_KEY_CursorUp, "LeftRockerUp"),
+  KEY_NAME_ENTRY(TS_KEY_CursorDown, "LeftRockerDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_NavLeft, "Backward"),
+  KEY_NAME_ENTRY(TS_KEY_NavRight, "Forward"),
+  KEY_NAME_ENTRY(TS_KEY_NavUp, "RightRockerUp"),
+  KEY_NAME_ENTRY(TS_KEY_NavDown, "RightRockerDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_ThumbLeft, "Convex"),
+  KEY_NAME_ENTRY(TS_KEY_ThumbRight, "Concave"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(pb_large)
+  KEY_NAME_ENTRY(TS_KEY_Button1, "Button1"),
+  KEY_NAME_ENTRY(TS_KEY_Button2, "Button2"),
+  KEY_NAME_ENTRY(TS_KEY_Button3, "Button3"),
+  KEY_NAME_ENTRY(TS_KEY_Button4, "Button4"),
+
+  KEY_NAME_ENTRY(TS_KEY_Bar1, "Bar1"),
+  KEY_NAME_ENTRY(TS_KEY_Bar2, "Bar2"),
+  KEY_NAME_ENTRY(TS_KEY_Bar3, "Bar3"),
+  KEY_NAME_ENTRY(TS_KEY_Bar4, "Bar4"),
+
+  KEY_NAME_ENTRY(TS_KEY_Switch1Up, "Switch1Up"),
+  KEY_NAME_ENTRY(TS_KEY_Switch1Down, "Switch1Down"),
+  KEY_NAME_ENTRY(TS_KEY_Switch2Up, "Switch2Up"),
+  KEY_NAME_ENTRY(TS_KEY_Switch2Down, "Switch2Down"),
+  KEY_NAME_ENTRY(TS_KEY_Switch3Up, "Switch3Up"),
+  KEY_NAME_ENTRY(TS_KEY_Switch3Down, "Switch3Down"),
+  KEY_NAME_ENTRY(TS_KEY_Switch4Up, "Switch4Up"),
+  KEY_NAME_ENTRY(TS_KEY_Switch4Down, "Switch4Down"),
+
+  KEY_NAME_ENTRY(TS_KEY_LeftRockerUp, "LeftRockerUp"),
+  KEY_NAME_ENTRY(TS_KEY_LeftRockerDown, "LeftRockerDown"),
+  KEY_NAME_ENTRY(TS_KEY_RightRockerUp, "RightRockerUp"),
+  KEY_NAME_ENTRY(TS_KEY_RightRockerDown, "RightRockerDown"),
+
+  KEY_NAME_ENTRY(TS_KEY_Convex, "Convex"),
+  KEY_NAME_ENTRY(TS_KEY_Concave, "Concave"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(nav20)
+  KEY_NAME_TABLE(nav_small),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(nav40)
+  KEY_NAME_TABLE(nav_small),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(nav80)
+  KEY_NAME_TABLE(nav_large),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pb40)
+  KEY_NAME_TABLE(pb_small),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pb65)
+  KEY_NAME_TABLE(pb_large),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(pb80)
+  KEY_NAME_TABLE(pb_large),
+  KEY_NAME_TABLE(routing),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(nav20)
+DEFINE_KEY_TABLE(nav40)
+DEFINE_KEY_TABLE(nav80)
+DEFINE_KEY_TABLE(pb40)
+DEFINE_KEY_TABLE(pb65)
+DEFINE_KEY_TABLE(pb80)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(nav20),
+  &KEY_TABLE_DEFINITION(nav40),
+  &KEY_TABLE_DEFINITION(nav80),
+  &KEY_TABLE_DEFINITION(pb40),
+  &KEY_TABLE_DEFINITION(pb65),
+  &KEY_TABLE_DEFINITION(pb80),
+END_KEY_TABLE_LIST
+
+/* Stabilization delay after changing baud rate */
+#define BAUD_DELAY 100
+
+/* for routing keys */
+#define ROUTING_BYTES_VERTICAL 4
+#define ROUTING_BYTES_MAXIMUM 11
+#define ROUTING_BYTES_40 9
+#define ROUTING_BYTES_80 14
+#define ROUTING_BYTES_81 15
+
+/* Description of reply to query */
+#define IDENTITY_H1 0X00
+#define IDENTITY_H2 0X05
+
+/* Routing keys information (2 bytes header) */
+#define ROUTING_H1 0x00
+#define ROUTING_H2 0x08
+
+/* input codes signaling low battery power (2bytes) */
+#define BATTERY_H1 0x00
+#define BATTERY_H2 0x01
+
+/* Bit definition of key codes returned by the display.
+ * Navigator and pb40 return 2 bytes, pb65/80 returns 6. Each byte has a
+ * different specific mask/signature in the 3 most significant bits.
+ * Other bits indicate whether a specific key is pressed.
+ * See readbrl().
+ */
+
+/* We combine all key bits into one KeyNumberSet. Each byte is masked by the
+ * corresponding "mask" to extract valid bits then those are shifted by
+ * "shift" and or'ed into the 32bits "code".
+ */
+
+/* bits to take into account when checking each byte's signature */
+#define KEYS_BYTE_SIGNATURE_MASK 0XE0
+
+/* how we describe each byte */
+typedef struct {
+  unsigned char signature; /* it's signature */
+  unsigned char mask; /* bits that do represent keys */
+  unsigned char shift; /* where to shift them into "code" */
+} KeysByteDescriptor;
+
+/* Description of bytes for navigator and pb40. */
+static const KeysByteDescriptor keysDescriptor_Navigator[] = {
+  {.signature=0X60, .mask=0X1F, .shift=0},
+  {.signature=0XE0, .mask=0X1F, .shift=5}
+};
+
+/* Description of bytes for pb65/80 */
+static const KeysByteDescriptor keysDescriptor_PowerBraille[] = {
+  {.signature=0X40, .mask=0X0F, .shift=10},
+  {.signature=0XC0, .mask=0X0F, .shift=14},
+  {.signature=0X20, .mask=0X05, .shift=18},
+  {.signature=0XA0, .mask=0X05, .shift=21},
+  {.signature=0X60, .mask=0X1F, .shift=24},
+  {.signature=0XE0, .mask=0X1F, .shift=5}
+};
+
+typedef struct {
+  const char *modelName;
+  const KeyTableDefinition *keyTableDefinition;
+
+  unsigned char routingBytes;
+  signed char routingKeyCount;
+
+  unsigned slowUpdate:2;
+  unsigned highBaudSupported:1;
+} ModelEntry;
+
+static const ModelEntry modelNavigator20 = {
+  .modelName = "Navigator 20",
+
+  .routingBytes = ROUTING_BYTES_40,
+  .routingKeyCount = 20,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(nav20)
+};
+
+static const ModelEntry modelNavigator40 = {
+  .modelName = "Navigator 40",
+
+  .routingBytes = ROUTING_BYTES_40,
+  .routingKeyCount = 40,
+
+  .slowUpdate = 1,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(nav40)
+};
+
+static const ModelEntry modelNavigator80 = {
+  .modelName = "Navigator 80",
+
+  .routingBytes = ROUTING_BYTES_80,
+  .routingKeyCount = 80,
+
+  .slowUpdate = 2,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(nav80)
+};
+
+static const ModelEntry modelPowerBraille40 = {
+  .modelName = "Power Braille 40",
+
+  .routingBytes = ROUTING_BYTES_40,
+  .routingKeyCount = 40,
+
+  .highBaudSupported = 1,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(pb40)
+};
+
+static const ModelEntry modelPowerBraille65 = {
+  .modelName = "Power Braille 65",
+
+  .routingBytes = ROUTING_BYTES_81,
+  .routingKeyCount = 65,
+
+  .slowUpdate = 2,
+  .highBaudSupported = 1,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(pb65)
+};
+
+static const ModelEntry modelPowerBraille80 = {
+  .modelName = "Power Braille 80",
+
+  .routingBytes = ROUTING_BYTES_81,
+  .routingKeyCount = 81,
+
+  .slowUpdate = 2,
+  .highBaudSupported = 1,
+
+  .keyTableDefinition = &KEY_TABLE_DEFINITION(pb80)
+};
+
+typedef enum {
+  IPT_IDENTITY,
+  IPT_ROUTING,
+  IPT_BATTERY,
+  IPT_KEYS
+} InputPacketType;
+
+typedef struct {
+  union {
+    unsigned char bytes[1];
+
+    struct {
+      unsigned char header[2];
+      unsigned char columns;
+      unsigned char dots;
+      char version[4];
+      unsigned char checksum[4];
+    } identity;
+
+    struct {
+      unsigned char header[2];
+      unsigned char count;
+      unsigned char vertical[ROUTING_BYTES_VERTICAL];
+      unsigned char horizontal[0X100 - 4];
+    } routing;
+
+    unsigned char keys[6];
+  } fields;
+
+  InputPacketType type;
+
+  union {
+    struct {
+      unsigned char count;
+    } routing;
+
+    struct {
+      const KeysByteDescriptor *descriptor;
+      unsigned char count;
+    } keys;
+  } data;
+} InputPacket;
+
+struct BrailleDataStruct {
+  const ModelEntry *model;
+  SerialParameters serialParameters;
+  unsigned char routingKeys[ROUTING_BYTES_MAXIMUM];
+
+  unsigned char forceWrite;
+  unsigned char cellCount;
+  unsigned char cells[0XFF];
+
+  struct {
+    unsigned char major;
+    unsigned char minor;
+  } version;
+
+  /* Type of delay the display requires after sending it a command.
+   * 0 -> no delay, 1 -> drain only, 2 -> drain + wait for SEND_DELAY.
+   */
+  unsigned char slowUpdate;
+};
+
+static ssize_t
+writeBytes (BrailleDisplay *brl, const void *data, size_t size) {
+  brl->writeDelay += brl->data->slowUpdate * 24;
+  return writeBraillePacket(brl, NULL, data, size);
+}
+
+static BraillePacketVerifierResult
+verifyPacket (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+) {
+  InputPacket *packet = data;
+  const off_t index = size - 1;
+  const unsigned char byte = bytes[index];
+
+  if (size == 1) {
+    switch (byte) {
+      case IDENTITY_H1:
+        packet->type = IPT_IDENTITY;
+        *length = 2;
+        break;
+
+      default:
+        if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_Navigator[0].signature) {
+          packet->data.keys.descriptor = keysDescriptor_Navigator;
+          packet->data.keys.count = ARRAY_COUNT(keysDescriptor_Navigator);
+          goto isKeys;
+        }
+
+        if ((byte & KEYS_BYTE_SIGNATURE_MASK) == keysDescriptor_PowerBraille[0].signature) {
+          packet->data.keys.descriptor = keysDescriptor_PowerBraille;
+          packet->data.keys.count = ARRAY_COUNT(keysDescriptor_PowerBraille);
+          goto isKeys;
+        }
+
+        return BRL_PVR_INVALID;
+
+      isKeys:
+        packet->type = IPT_KEYS;
+        *length = packet->data.keys.count;
+        break;
+    }
+  } else {
+    switch (packet->type) {
+      case IPT_IDENTITY:
+        if (size == 2) {
+          switch (byte) {
+            case IDENTITY_H2:
+              *length = sizeof(packet->fields.identity);
+              break;
+
+            case ROUTING_H2:
+              packet->type = IPT_ROUTING;
+              *length = 3;
+              break;
+
+            case BATTERY_H2:
+              packet->type = IPT_BATTERY;
+              break;
+
+            default:
+              return BRL_PVR_INVALID;
+          }
+        }
+        break;
+
+      case IPT_ROUTING:
+        if (size == 3) {
+          packet->data.routing.count = byte;
+          *length += packet->data.routing.count;
+        }
+        break;
+
+      case IPT_KEYS:
+        if ((byte & KEYS_BYTE_SIGNATURE_MASK) != packet->data.keys.descriptor[index].signature) return BRL_PVR_INVALID;
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return BRL_PVR_INCLUDE;
+}
+
+static size_t
+readPacket (BrailleDisplay *brl, InputPacket *packet) {
+  return readBraillePacket(brl, NULL, &packet->fields, sizeof(packet->fields), verifyPacket, packet);
+}
+
+static int
+getIdentity (BrailleDisplay *brl, InputPacket *reply) {
+  static const unsigned char request[] = {0xFF, 0xFF, 0x0A};
+
+  if (writeBytes(brl, request, sizeof(request))) {
+    if (awaitBrailleInput(brl, 100)) {
+      size_t count = readPacket(brl, reply);
+
+      if (count > 0) {
+        if (reply->type == IPT_IDENTITY) return 1;
+        logUnexpectedPacket(reply->fields.bytes, count);
+      }
+    } else {
+      logMessage(LOG_DEBUG, "no response");
+    }
+  }
+
+  return 0;
+}
+
+static int
+setAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval) {
+  const unsigned char request[] = {
+    0XFF, 0XFF, 0X0D,
+    on? ((delay + 9) / 10) /* 10ms */: 0XFF /* long delay */,
+    on? ((interval + 9) / 10) /* 10ms */: 0XFF /* long interval */
+  };
+
+  return writeBytes(brl, request, sizeof(request));
+}
+
+static int
+setLocalBaud (BrailleDisplay *brl, unsigned int baud) {
+  SerialParameters *parameters = &brl->data->serialParameters;
+
+  logMessage(LOG_DEBUG, "trying at %u baud", baud);
+  if (parameters->baud == baud) return 1;
+
+  parameters->baud = baud;
+  return gioReconfigureResource(brl->gioEndpoint, parameters);
+}
+
+static int
+setRemoteBaud (BrailleDisplay *brl, unsigned int baud) {
+  unsigned char request[] = {0xFF, 0xFF, 0x05, 0};
+  unsigned char *byte = &request[sizeof(request) - 1];
+
+  switch (baud) {
+    case TS_BAUD_LOW:
+      *byte = 2;
+      break;
+
+    case TS_BAUD_NORMAL:
+      *byte = 3;
+      break;
+
+    case TS_BAUD_HIGH:
+      *byte = 4;
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "display does not support %u baud", baud);
+      return 0;
+  }
+
+  logMessage(LOG_WARNING, "switching display to %u baud", baud);
+  return writeBraillePacket(brl, NULL, request, sizeof(request));
+}
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  static const SerialParameters serialParameters = {
+    SERIAL_DEFAULT_PARAMETERS
+  };
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  descriptor.serial.parameters = &serialParameters;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    brl->data->serialParameters = serialParameters;
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+disconnectResource (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if ((brl->data = malloc(sizeof(*brl->data)))) {
+    memset(brl->data, 0, sizeof(*brl->data));
+
+    if (connectResource(brl, device)) {
+      InputPacket reply;
+
+      unsigned int allowHighBaud = 1;
+      unsigned int oldBaud;
+      unsigned int newBaud;
+
+      {
+        const char *parameter = parameters[PARM_HIGH_BAUD];
+
+        if (parameter && *parameter) {
+          if (!validateYesNo(&allowHighBaud, parameter)) {
+            logMessage(LOG_WARNING, "unsupported high baud setting: %s", parameter);
+          }
+        }
+      }
+
+      {
+        static const unsigned int bauds[] = {
+          TS_BAUD_NORMAL, TS_BAUD_HIGH, 0
+        };
+
+        const unsigned int *baud = bauds;
+
+        while (1) {
+          if (!*baud) goto failure;
+          oldBaud = *baud++;
+
+          if (allowHighBaud || (oldBaud <= TS_BAUD_NORMAL)) {
+            if (setLocalBaud(brl, oldBaud)) {
+              if (getIdentity(brl, &reply)) {
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      brl->data->cellCount = reply.fields.identity.columns;
+      brl->data->version.major = reply.fields.identity.version[1] - '0';
+      brl->data->version.minor = reply.fields.identity.version[3] - '0';
+
+      logMessage(LOG_INFO, "display replied: %u cells, version %u.%u",
+                 brl->data->cellCount,
+                 brl->data->version.major,
+                 brl->data->version.minor);
+
+      switch (brl->data->cellCount) {
+        case 20:
+          brl->data->model = &modelNavigator20;
+          break;
+
+        case 40:
+          brl->data->model = (brl->data->version.major > 3)?
+                               &modelPowerBraille40:
+                               &modelNavigator40;
+          break;
+
+        case 80:
+          brl->data->model = &modelNavigator80;
+          break;
+
+        case 65:
+          brl->data->model = &modelPowerBraille65;
+          break;
+
+        case 81:
+          brl->data->model = &modelPowerBraille80;
+          break;
+
+        default:
+          logMessage(LOG_ERR, "unrecognized braille display size: %u", brl->data->cellCount);
+          goto failure;
+      }
+
+      logMessage(LOG_INFO, "detected %s", brl->data->model->modelName);
+
+      brl->data->slowUpdate = brl->data->model->slowUpdate;
+
+#ifdef FORCE_DRAIN_AFTER_SEND
+      brl->data->slowUpdate = 1;
+#endif /* FORCE_DRAIN_AFTER_SEND */
+
+#ifdef FORCE_FULL_SEND_DELAY
+      brl->data->slowUpdate = 2;
+#endif /* FORCE_FULL_SEND_DELAY */
+
+      newBaud = oldBaud;
+      if (allowHighBaud && brl->data->model->highBaudSupported) newBaud = TS_BAUD_HIGH;
+
+      {
+        const char *parameter = parameters[PARM_SET_BAUD];
+
+        if (parameter && *parameter) {
+          static const int minimum = 1;
+          int value;
+
+          if (validateInteger(&value, parameter, &minimum, NULL)) {
+            newBaud = value;
+          } else {
+            logMessage(LOG_WARNING, "unsupported set baud setting: %s", parameter);
+          }
+        }
+      }
+
+      if (newBaud != oldBaud) {
+        if (!setRemoteBaud(brl, newBaud)) goto failure;
+        drainBrailleOutput(brl, BAUD_DELAY);
+        if (!setLocalBaud(brl, newBaud)) goto failure;
+        logMessage(LOG_DEBUG, "now using %u baud - checking if display followed", newBaud);
+
+        if (getIdentity(brl, &reply)) {
+          logMessage(LOG_DEBUG, "display responded at %u baud", newBaud);
+        } else {
+          logMessage(LOG_INFO,
+                     "display did not respond at %u baud"
+                     " - going back to %u baud",
+                     newBaud, oldBaud);
+
+          if (!setLocalBaud(brl, oldBaud)) goto failure;
+          drainBrailleOutput(brl, BAUD_DELAY);
+
+          if (getIdentity(brl, &reply)) {
+            logMessage(LOG_INFO, "found display again at %u baud", oldBaud);
+          } else {
+            logMessage(LOG_ERR, "display lost after baud switch");
+            goto failure;
+          }
+        }
+      }
+
+      setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
+      makeOutputTable(dotsTable_ISO11548_1);
+
+      brl->textColumns = brl->data->cellCount;		/* initialise size of display */
+      brl->setAutorepeatProperties = setAutorepeatProperties;
+
+      memset(brl->data->routingKeys, 0, sizeof(brl->data->routingKeys));
+      brl->data->forceWrite = 1;
+
+      return 1;
+
+    failure:
+      disconnectResource(brl);
+    }
+
+    free(brl->data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void 
+brl_destruct (BrailleDisplay *brl) {
+  disconnectResource(brl);
+
+  if (brl->data) {
+    free(brl->data);
+    brl->data = NULL;
+  }
+}
+
+static int
+writeCells (BrailleDisplay *brl, unsigned int from, unsigned int to) {
+  static const unsigned char header[] = {
+    0XFF, 0XFF, 0X04, 0X00, 0X99, 0X00
+  };
+
+  unsigned int length = to - from;
+  unsigned char packet[sizeof(header) + 2 + (length * 2)];
+  unsigned char *byte = packet;
+  unsigned int i;
+
+  byte = mempcpy(byte, header, sizeof(header));
+  *byte++ = 2 * length;
+  *byte++ = from;
+
+  for (i=0; i<length; i+=1) {
+    *byte++ = 0;
+    *byte++ = translateOutputCell(brl->data->cells[from + i]);
+  }
+
+  /* Some displays apparently don't like rapid updating. Most or all apprently
+   * don't do flow control. If we update the display too often and too fast,
+   * then the packets queue up in the send queue, the info displayed is not up
+   * to date, and the info displayed continues to change after we stop
+   * updating while the queue empties (like when you release the arrow key and
+   * the display continues changing for a second or two). We also risk
+   * overflows which put garbage on the display, or often what happens is that
+   * some cells from previously displayed lines will remain and not be cleared
+   * or replaced; also the pinging fails and the display gets
+   * reinitialized... To expose the problem skim/scroll through a long file
+   * (with long lines) holding down the up/down arrow key on the PC keyboard.
+   *
+   * pb40 has no problems: it apparently can take whatever we throw at
+   * it. Nav40 is good but we drain just to be safe.
+   *
+   * pb80 (twice larger but twice as fast as nav40) cannot take a continuous
+   * full speed flow. There is no flow control: apparently not supported
+   * properly on at least pb80. My pb80 is recent yet the hardware version is
+   * v1.0a, so this may be a hardware problem that was fixed on pb40.  There's
+   * some UART handshake mode that might be relevant but only seems to break
+   * everything (on both pb40 and pb80)...
+   *
+   * Nav80 is untested but as it receives at 9600, we probably need to
+   * compensate there too.
+   *
+   * Finally, some TSI emulators (at least the mdv mb408s) may have timing
+   * limitations.
+   *
+   * I no longer have access to a Nav40 and PB80 for testing: I only have a
+   * PB40.
+   */
+
+  return writeBytes(brl, packet, (byte - packet));
+}
+
+static int 
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned int from, to;
+    
+  if (cellsHaveChanged(brl->data->cells, brl->buffer, brl->data->cellCount,
+                       &from, &to, &brl->data->forceWrite)) {
+    if (!writeCells(brl, from, to)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+handleInputPacket (BrailleDisplay *brl, const InputPacket *packet) {
+  switch (packet->type) {
+    case IPT_KEYS: {
+      KeyNumberSet keys = 0;
+      unsigned int i;
+
+      for (i=0; i<packet->data.keys.count; i+=1) {
+        const KeysByteDescriptor *kbd = &packet->data.keys.descriptor[i];
+
+        keys |= (packet->fields.keys[i] & kbd->mask) << kbd->shift;
+      }
+
+      enqueueKeys(brl, keys, TS_GRP_NavigationKeys, 0);
+      return 1;
+    }
+
+    case IPT_ROUTING: {
+      if (packet->data.routing.count != brl->data->model->routingBytes) return 0;
+
+      enqueueUpdatedKeyGroup(brl, brl->data->model->routingKeyCount,
+                             packet->fields.routing.horizontal,
+                             brl->data->routingKeys,
+                             TS_GRP_RoutingKeys);
+      return 1;
+    }
+
+    case IPT_BATTERY:
+      message(NULL, gettext("battery low"), 0);
+      return 1;
+
+    default:
+      return 0;
+  }
+}
+
+static int 
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  /* Key press codes come in pairs of bytes for nav and pb40, in 6bytes
+   * for pb65/80. Each byte has bits representing individual keys + a special
+   * mask/signature in the most significant 3bits.
+   *
+   * The low battery warning from the display is a specific 2bytes code.
+   *
+   * Finally, the routing keys have a special 2bytes header followed by 9, 14
+   * or 15 bytes of info (1bit for each routing key). The first 4bytes describe
+   * vertical routing keys and are ignored in this driver.
+   *
+   * We might get a query reply, since we send queries when we don't get
+   * any keys in a certain time. That a 2byte header + 10 more bytes ignored.
+   */
+
+  InputPacket packet;
+  size_t size;
+
+  while ((size = readPacket(brl, &packet))) {
+    if (!handleInputPacket(brl, &packet)) {
+      logUnexpectedPacket(packet.fields.bytes, size);
+    }
+  }
+
+  return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/TSI/braille.h b/Drivers/Braille/TSI/braille.h
new file mode 100644
index 0000000..62004b1
--- /dev/null
+++ b/Drivers/Braille/TSI/braille.h
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* TSI/braille.h - Configuration file for the TSI braille
+ *                 display driver (brl.c)
+ * Written by Stéphane Doyon (s.doyon@videotron.ca)
+ *
+ * This file is intended for version 2.2beta3 of the driver.
+ */
+
+/* Configuration file for the TSI driver.
+ * Edit as needed...
+ */
+
+/* Timing options: If you get a garbled display, you might try to uncomment
+   one of the following defines. This attempts to avoid flow control problems
+   by inserting various small delays. This is normally not needed and it will
+   make the display slightly sluggish. However these options may be helpful
+   in getting some TSI emulators to work correctly.
+
+   For the TSI emulation mode of the mdv mb408s display, the following
+   options are recommended:
+     uncomment FORCE_FULL_SEND_DELAY
+     leave FORCE_DRAIN_AFTER_SEND commented out
+     leave SEND_DELAY set to 30
+*/
+
+/* This option forces BRLTTY to wait for the OS's buffer for the serial port
+   to be flushed after each update to the braille display. (Note that we do
+   not wait for the buffer on the UART chip to be flushed, which might take
+   something like 30-60ms.) */
+/*#define FORCE_DRAIN_AFTER_SEND 1*/
+
+/* This option, in addition to waiting like the previous option, also imposes
+   a wait of SEND_DELAY ms after sending a display update. It also imposes a
+   wait of 2*SEND_DELAY ms before and SEND_DELAY after sending a ping. */
+/*#define FORCE_FULL_SEND_DELAY 1*/
+
+/* Note that the previous options are automatically triggered when some
+   display models are recognized: Nav40 triggers FORCE_DRAIN_AFTER_SEND (is it
+   necessary?), PB65/80 and Nav80 trigger FORCE_FULL_SEND_DELAY (and should be
+   fine with SEND_DELAY set to 30). Only PB40 (and presumably Nav20?) are fine
+   without timing adjustment. Also remember that there is a 40ms delay for
+   every cycle of the main BRLTTY loop. */
+#define SEND_DELAY 30
+
+/* TODO: an option that deactivates partial updates, so that the whole display
+   is always updated... */
+
+/* End of driver config */
diff --git a/Drivers/Braille/TSI/brldefs-ts.h b/Drivers/Braille/TSI/brldefs-ts.h
new file mode 100644
index 0000000..9eadc8a
--- /dev/null
+++ b/Drivers/Braille/TSI/brldefs-ts.h
@@ -0,0 +1,89 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TS_BRLDEFS
+#define BRLTTY_INCLUDED_TS_BRLDEFS
+
+#define TS_BAUD_LOW     4800
+#define TS_BAUD_NORMAL  9600
+#define TS_BAUD_HIGH   19200
+
+typedef enum {
+  /* Navigator - byte 1
+   * Nav20/40: right side
+   * Nav80: navigation keys, right thumb key
+   * PB40: right rocker up/down, concave, forward/backward (on top)
+   */
+  TS_KEY_NavLeft    = 0,
+  TS_KEY_NavUp      = 1,
+  TS_KEY_NavRight   = 2,
+  TS_KEY_NavDown    = 3,
+  TS_KEY_ThumbRight = 4,
+
+  /* Navigator - byte 2
+   * Nav20/40: left side
+   * nav80: cursor keys, left thumb key
+   * PB40: left rocker up/down, convex
+   */
+  TS_KEY_CursorLeft  = 5,
+  TS_KEY_CursorUp    = 6,
+  TS_KEY_CursorRight = 7,
+  TS_KEY_CursorDown  = 8,
+  TS_KEY_ThumbLeft   = 9,
+
+  // Power Braille - byte 6
+  TS_KEY_Button1        = 5,
+  TS_KEY_LeftRockerUp   = 6,
+  TS_KEY_Button2        = 7,
+  TS_KEY_LeftRockerDown = 8,
+  TS_KEY_Convex         = 9,
+
+  // Power Braille - byte 1
+  TS_KEY_Switch1Up   = 10,
+  TS_KEY_Switch1Down = 11,
+  TS_KEY_Switch2Up   = 12,
+  TS_KEY_Switch2Down = 13,
+
+  // Power Braille - byte 2
+  TS_KEY_Switch3Up   = 14,
+  TS_KEY_Switch3Down = 15,
+  TS_KEY_Switch4Up   = 16,
+  TS_KEY_Switch4Down = 17,
+
+  // Power Braille - byte 3
+  TS_KEY_Bar3 = 18,
+  TS_KEY_Bar4 = 20,
+
+  // Power Braille - byte 4
+  TS_KEY_Button3 = 21,
+  TS_KEY_Button4 = 23,
+
+  // Power Braille - byte 5
+  TS_KEY_Bar1            = 24,
+  TS_KEY_RightRockerUp   = 25,
+  TS_KEY_Bar2            = 26,
+  TS_KEY_RightRockerDown = 27,
+  TS_KEY_Concave         = 28
+} TS_NavigationKey;
+
+typedef enum {
+  TS_GRP_NavigationKeys = 0,
+  TS_GRP_RoutingKeys
+} TS_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_TS_BRLDEFS */ 
diff --git a/Drivers/Braille/TTY/Makefile.in b/Drivers/Braille/TTY/Makefile.in
new file mode 100644
index 0000000..5b97ffb
--- /dev/null
+++ b/Drivers/Braille/TTY/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = tt
+DRIVER_NAME = TTY
+DRIVER_USAGE = terminal connected via serial port
+DRIVER_VERSION = 0.2 (August, 2004)
+DRIVER_DEVELOPERS = Samuel Thibault <samuel.thibault@ens-lyon.org>
+BRL_OBJS = @braille_libraries_tt@
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/TTY/README b/Drivers/Braille/TTY/README
new file mode 100644
index 0000000..e3c86c5
--- /dev/null
+++ b/Drivers/Braille/TTY/README
@@ -0,0 +1,54 @@
+This driver allows to use any tty as peripheral for BrlTTY. There are many ways
+to use it.
+
+By ssh-ing from another machine
+-------------------------------
+
+ssh from machB to machA, and give the current tty as device to brltty:
+
+samy@machB$ ssh machA
+samy@machA$ su -
+Password:
+root@machA# brltty -b tt -d `tty` -B term=$TERM -n
+
+By using an Xterm
+-----------------
+
+Just give the tty used by the xterm to brltty.
+
+$ su -
+Password:
+# brltty -b tt -d `tty` -B term=$TERM -n
+
+By using a real tty
+-------------------
+
+Just give the serial port on which it is connected to brltty:
+
+# brltty -b tt -d /dev/ttyS0 -B term=ampex232,baud=19200,charset=IBM850,locale=fr_FR.IBM850
+
+I here had to precise the type of terminal (I have an Ampex232, see somewhere
+like /usr/share/terminfo for your own's.
+
+I also had to tell that it uses the IBM850 codepage table. And I hence also
+had to tell to use a special locale: fr_FR.IBM850, since I want french,
+with IBM850 charset. I had to generate since by hand by adding
+fr_FR.IBM850 IBM850
+to my /etc/locale.gen, and then relaunch locale-gen
+
+
+Notes
+-----
+
+Another machine connected by a null-modem can be used as a tty: just run
+minicom on it, and use vt100 as type of terminal.
+
+If a BrlAPI application writes dots (Gnopernicus for instance) you won't
+get the text.
+
+See help.txt (or press F1) to get help on key bindings
+Default configuration should be fine, but if not, get sure to use 8-dot text
+style and not to show attributes in the configuration menu (F4).
+
+Note that when the displayed window is not supposed to contain the cursor, the
+cursor is actually put below the displayed window.
diff --git a/Drivers/Braille/TTY/braille.c b/Drivers/Braille/TTY/braille.c
new file mode 100644
index 0000000..ee7d7fc
--- /dev/null
+++ b/Drivers/Braille/TTY/braille.c
@@ -0,0 +1,425 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+
+#include <limits.h>
+#ifndef MB_LEN_MAX
+#define MB_LEN_MAX 16
+#endif /* MB_LEN_MAX */
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+static iconv_t conversionDescriptor = NULL;
+#endif /* HAVE_ICONV_H */
+
+#include "log.h"
+#include "parse.h"
+#include "charset.h"
+#include "unicode.h"
+#include "get_curses.h"
+
+#ifdef GOT_CURSES
+#define newLine() addch('\n')
+#else /* GOT_CURSES */
+#define addstr(string) serialWriteData(ttyDevice, string, strlen(string))
+#define addch(character) do { unsigned char __c = (character); serialWriteData(ttyDevice, &__c, 1); } while(0)
+#define getch() getch_noCurses()
+#define newLine() addstr("\r\n")
+#endif /* GOT_CURSES */
+
+#ifdef GOT_CURSES
+#define BRLPARM_TERM "term",
+#else /* GOT_CURSES */
+#define BRLPARM_TERM
+#endif /* GOT_CURSES */
+
+#ifdef HAVE_ICONV_H
+#define BRLPARM_CHARSET "charset",
+#else /* HAVE_ICONV_H */
+#define BRLPARM_CHARSET
+#endif /* HAVE_ICONV_H */
+
+typedef enum {
+  PARM_BAUD,
+
+#ifdef GOT_CURSES
+  PARM_TERM,
+#endif /* GOT_CURSES */
+
+  PARM_LINES,
+  PARM_COLUMNS,
+
+#ifdef HAVE_ICONV_H
+  PARM_CHARSET,
+#endif /* HAVE_ICONV_H */
+
+  PARM_LOCALE
+} DriverParameter;
+#define BRLPARMS "baud", BRLPARM_TERM "lines", "columns", BRLPARM_CHARSET "locale"
+
+#include "brl_driver.h"
+#include "braille.h"
+#include "io_serial.h"
+
+#define MAX_WINDOW_LINES 3
+#define MAX_WINDOW_COLUMNS 80
+#define MAX_WINDOW_SIZE (MAX_WINDOW_LINES * MAX_WINDOW_COLUMNS)
+
+static SerialDevice *ttyDevice = NULL;
+static FILE *ttyStream = NULL;
+static char *classificationLocale = NULL;
+
+#ifdef GOT_CURSES
+static SCREEN *ttyScreen = NULL;
+#else /* GOT_CURSES */
+static inline int
+getch_noCurses (void) {
+  unsigned char c;
+  if (serialReadData(ttyDevice, &c, 1, 0, 0) == 1) return c;
+  return EOF;
+}
+#endif /* GOT_CURSES */
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  unsigned int ttyBaud = 9600;
+  const char *ttyType = "vt100";
+  int windowLines = 1;
+  int windowColumns = 40;
+
+#ifdef HAVE_ICONV_H
+  const char *characterSet = getLocaleCharset();
+#endif /* HAVE_ICONV_H */
+
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  {
+    unsigned int baud = ttyBaud;
+
+    if (serialValidateBaud(&baud, "TTY baud", parameters[PARM_BAUD], NULL)) {
+      ttyBaud = baud;
+    }
+  }
+
+#ifdef GOT_CURSES
+  if (*parameters[PARM_TERM]) {
+    ttyType = parameters[PARM_TERM];
+  }
+#endif /* GOT_CURSES */
+
+  {
+    static const int minimum = 1;
+    static const int maximum = MAX_WINDOW_LINES;
+    int lines = windowLines;
+
+    if (validateInteger(&lines, parameters[PARM_LINES], &minimum, &maximum)) {
+      windowLines = lines;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid line count", parameters[PARM_LINES]);
+    }
+  }
+
+  {
+    static const int minimum = 1;
+    static const int maximum = MAX_WINDOW_COLUMNS;
+    int columns = windowColumns;
+
+    if (validateInteger(&columns, parameters[PARM_COLUMNS], &minimum, &maximum)) {
+      windowColumns = columns;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid column count", parameters[PARM_COLUMNS]);
+    }
+  }
+
+#ifdef HAVE_ICONV_H
+  if (*parameters[PARM_CHARSET]) {
+    characterSet = parameters[PARM_CHARSET];
+  }
+#endif /* HAVE_ICONV_H */
+
+  if (*parameters[PARM_LOCALE]) {
+    classificationLocale = parameters[PARM_LOCALE];
+  }
+
+#ifdef HAVE_ICONV_H
+  if ((conversionDescriptor = iconv_open(characterSet, "WCHAR_T")) != (iconv_t)-1) {
+#endif /* HAVE_ICONV_H */
+    if ((ttyDevice = serialOpenDevice(device))) {
+      if (serialRestartDevice(ttyDevice, ttyBaud)) {
+#ifdef GOT_CURSES
+        if ((ttyStream = serialGetStream(ttyDevice))) {
+          if ((ttyScreen = newterm(ttyType, ttyStream, ttyStream))) {
+            cbreak();
+            noecho();
+            nonl();
+
+            nodelay(stdscr, TRUE);
+            intrflush(stdscr, FALSE);
+            keypad(stdscr, TRUE);
+
+            clear();
+            refresh();
+#endif /* GOT_CURSES */
+
+            brl->textColumns = windowColumns;
+            brl->textRows = windowLines; 
+
+            logMessage(LOG_INFO, "TTY: type=%s baud=%u size=%dx%d",
+                       ttyType, ttyBaud, windowColumns, windowLines);
+            return 1;
+#ifdef GOT_CURSES
+          } else {
+            logSystemError("newterm");
+          }
+
+          ttyStream = NULL;
+        }
+#endif /* GOT_CURSES */
+      }
+
+      serialCloseDevice(ttyDevice);
+      ttyDevice = NULL;
+    }
+
+#ifdef HAVE_ICONV_H
+    iconv_close(conversionDescriptor);
+  } else {
+    logSystemError("iconv_open");
+  }
+
+  conversionDescriptor = NULL;
+#endif /* HAVE_ICONV_H */
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+#ifdef GOT_CURSES
+  if (ttyScreen) {
+    endwin();
+
+#ifndef __MINGW32__
+    delscreen(ttyScreen);
+#endif /* __MINGW32__ */
+
+    ttyScreen = NULL;
+  }
+#endif /* GOT_CURSES */
+
+  if (ttyDevice) {
+    ttyStream = NULL;
+    serialCloseDevice(ttyDevice);
+    ttyDevice = NULL;
+  }
+
+#ifdef HAVE_ICONV_H
+  if (conversionDescriptor) {
+    iconv_close(conversionDescriptor);
+    conversionDescriptor = NULL;
+  }
+#endif /* HAVE_ICONV_H */
+}
+
+static void
+writeText (const wchar_t *buffer, int columns) {
+  int column;
+  for (column=0; column<columns; column++) {
+    wchar_t c = buffer[column];
+
+#ifdef HAVE_ICONV_H
+    char *pc = (char*) &c;
+    size_t sc = sizeof(wchar_t);
+    char d[MB_LEN_MAX+1];
+    char *pd = d;
+    size_t sd = MB_LEN_MAX;
+
+    if (iconv(conversionDescriptor, &pc, &sc, &pd, &sd) != (size_t)-1) {
+      *pd = 0;
+      addstr(d);
+    } else
+#endif /* HAVE_ICONV_H */
+    {
+      addch(c);
+    }
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  static unsigned char previousContent[MAX_WINDOW_SIZE];
+  static int previousCursor = -1;
+  char *previousLocale;
+
+  if (!cellsHaveChanged(previousContent, brl->buffer, brl->textColumns*brl->textRows, NULL, NULL, NULL) &&
+      (brl->cursor == previousCursor)) {
+    return 1;
+  }
+
+  previousCursor = brl->cursor;
+
+  if (classificationLocale) {
+    previousLocale = setlocale(LC_CTYPE, NULL);
+    setlocale(LC_CTYPE, classificationLocale);
+  } else {
+    previousLocale = NULL;
+  }
+
+#ifdef GOT_CURSES
+  clear();
+#else /* GOT_CURSES */
+  newLine();
+#endif /* GOT_CURSES */
+
+  {
+    wchar_t braille[brl->textColumns];
+
+    for (unsigned int row=0; row<brl->textRows; row++) {
+      unsigned int offset = row * brl->textColumns;
+      writeText(&text[offset], brl->textColumns);
+
+      for (unsigned int column=0; column<brl->textColumns; column+=1) {
+        unsigned char c = brl->buffer[offset + column];
+        braille[column] = UNICODE_BRAILLE_ROW
+                        | (!!(c & BRL_DOT1) << 0)
+                        | (!!(c & BRL_DOT2) << 1)
+                        | (!!(c & BRL_DOT3) << 2)
+                        | (!!(c & BRL_DOT4) << 3)
+                        | (!!(c & BRL_DOT5) << 4)
+                        | (!!(c & BRL_DOT6) << 5)
+                        | (!!(c & BRL_DOT7) << 6)
+                        | (!!(c & BRL_DOT8) << 7)
+                        ;
+      }
+
+      newLine();
+      writeText(braille, brl->textColumns);
+
+      if (row < (brl->textRows - 1)) {
+        newLine();
+      }
+    }
+  }
+
+#ifdef GOT_CURSES
+  if ((brl->cursor != BRL_NO_CURSOR) && (brl->cursor < (brl->textColumns * brl->textRows))) {
+    move(brl->cursor/brl->textColumns, brl->cursor%brl->textColumns);
+  } else {
+    move(brl->textRows, 0);
+  }
+
+  refresh();
+#else /* GOT_CURSES */
+  if ((brl->textRows == 1) && (brl->cursor != BRL_NO_CURSOR) && (brl->cursor < brl->textColumns)) {
+    addch('\r');
+    writeText(text, brl->cursor);
+  } else {
+    newLine();
+  }
+#endif /* GOT_CURSES */
+
+  if (previousLocale) setlocale(LC_CTYPE, previousLocale);
+  return 1;
+}
+
+static int
+keyToCommand (BrailleDisplay *brl, KeyTableCommandContext context, int key) {
+  switch (key) {
+    case EOF: return EOF;
+
+    default:
+      if (key <= 0XFF) return BRL_CMD_CHAR(key);
+      logMessage(LOG_WARNING, "unrecognized curses key: %d", key);
+      return BRL_CMD_NOOP;
+
+#ifdef GOT_CURSES
+#define MAP(key,cmd) case KEY_##key: return BRL_CMD_##cmd
+    MAP(BACKSPACE, KEY(BACKSPACE));
+
+    MAP(LEFT, FWINLT);
+    MAP(RIGHT, FWINRT);
+    MAP(UP, LNUP);
+    MAP(DOWN, LNDN);
+
+    MAP(PPAGE, PRDIFLN);
+    MAP(NPAGE, NXDIFLN);
+    MAP(A3, PRDIFLN);
+    MAP(C3, NXDIFLN);
+
+    MAP(HOME, TOP);
+    MAP(END, BOT);
+    MAP(A1, TOP);
+    MAP(C1, BOT);
+
+    MAP(IC, ATTRUP);
+    MAP(DC, ATTRDN);
+    MAP(B2, HOME);
+
+    MAP(F(1), HELP);
+    MAP(F(2), LEARN);
+    MAP(F(3), INFO);
+    MAP(F(4), PREFMENU);
+
+    MAP(F(5), PRPROMPT);
+    MAP(F(6), NXPROMPT);
+    MAP(F(7), PRPGRPH);
+    MAP(F(8), NXPGRPH);
+
+    MAP(F(9), LNBEG);
+    MAP(F(10), CHRLT);
+    MAP(F(11), CHRRT);
+    MAP(F(12), LNEND);
+#undef MAP
+#endif /* GOT_CURSES */
+  }
+}
+
+static int
+readKey (BrailleDisplay *brl) {
+  int key = getch();
+
+#ifdef GOT_CURSES
+  if (key == ERR) return EOF;
+#endif /* GOT_CURSES */
+
+  if (key != EOF) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "curses key: %d", key);
+  }
+
+  return key;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  int command = keyToCommand(brl, context, readKey(brl));
+
+  if (command != EOF) {
+    logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "command: 0X%04X", command);
+  }
+
+  return command;
+}
diff --git a/Drivers/Braille/TTY/braille.h b/Drivers/Braille/TTY/braille.h
new file mode 100644
index 0000000..1cb5e77
--- /dev/null
+++ b/Drivers/Braille/TTY/braille.h
@@ -0,0 +1,17 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
diff --git a/Drivers/Braille/TechniBraille/Makefile.in b/Drivers/Braille/TechniBraille/Makefile.in
new file mode 100644
index 0000000..a3bc31f
--- /dev/null
+++ b/Drivers/Braille/TechniBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = tn
+DRIVER_NAME = TechniBraille
+DRIVER_USAGE = Manager 40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/TechniBraille/braille.c b/Drivers/Braille/TechniBraille/braille.c
new file mode 100644
index 0000000..3611cb4
--- /dev/null
+++ b/Drivers/Braille/TechniBraille/braille.c
@@ -0,0 +1,341 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "brl_driver.h"
+
+static unsigned char brailleCells[0XFF];
+static wchar_t visualText[0XFF];
+
+#include "io_serial.h"
+static SerialDevice *serialDevice = NULL;
+static unsigned int charactersPerSecond;
+
+static int
+readPacket (BrailleDisplay *brl, unsigned char *packet, int length) {
+  size_t offset = 0;
+  int size = -1;
+
+  while (offset < length) {
+    const unsigned char *byte = &packet[offset];
+
+    if (!serialReadChunk(serialDevice, packet, &offset, 1, 0, 1000)) {
+      if (errno == EAGAIN) {
+        if (!offset) return 0;
+        logPartialPacket(packet, offset);
+      }
+      return -1;
+    }
+
+    if (offset == 1) {
+      if (*byte) {
+        logDiscardedByte(packet[0]);
+        offset = 0;
+      }
+    } else {
+      if (offset == 2) {
+        switch (*byte) {
+          default:
+            size = 1;
+            break;
+        }
+        size += offset;
+      }
+
+      if (offset == size) {
+        logInputPacket(packet, offset);
+        return offset;
+      }
+    }
+  }
+
+  logTruncatedPacket(packet, offset);
+  return 0;
+}
+
+static int
+writePacket (BrailleDisplay *brl, unsigned char function, unsigned char *data, unsigned char count) {
+  unsigned char buffer[count + 4];
+  unsigned char *byte = buffer;
+
+  *byte++ = 0;
+  *byte++ = function;
+  *byte++ = count;
+  byte = mempcpy(byte, data, count);
+
+  {
+    unsigned char checksum = 0;
+    const unsigned char *ptr = buffer;
+    while (ptr < byte) checksum ^= *ptr++;
+    *byte++ = checksum;
+  }
+
+  {
+    int size = byte - buffer;
+    logOutputPacket(buffer, size);
+    brl->writeDelay += (count * 1000 / charactersPerSecond) + 1;
+    if (serialWriteData(serialDevice, buffer, size) != -1) return 1;
+  }
+
+  logSystemError("serial write");
+  return 0;
+}
+
+static int
+writeBrailleCells (BrailleDisplay *brl) {
+  size_t count = brl->textColumns;
+  unsigned char cells[count];
+
+  translateOutputCells(cells, brailleCells, count);
+  return writePacket(brl, 1, cells, count);
+}
+
+static int
+clearBrailleCells (BrailleDisplay *brl) {
+  memset(brailleCells, 0, brl->textColumns);
+  return writeBrailleCells(brl);
+}
+
+static int
+writeVisualText (BrailleDisplay *brl) {
+  unsigned char bytes[brl->textColumns];
+  int i;
+
+  for (i=0; i<brl->textColumns; ++i) {
+    wchar_t character = visualText[i];
+    bytes[i] = iswLatin1(character)? character: '?';
+  }
+
+  return writePacket(brl, 2, bytes, brl->textColumns);
+}
+
+static int
+clearVisualText (BrailleDisplay *brl) {
+  wmemset(visualText, WC_C(' '), brl->textColumns);
+  return writeVisualText(brl);
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+
+  if ((serialDevice = serialOpenDevice(device))) {
+    unsigned int baud = 19200;
+    charactersPerSecond = baud / 11;
+
+    if (serialRestartDevice(serialDevice, baud)) {
+      if (serialSetParity(serialDevice, SERIAL_PARITY_EVEN)) {
+        if (writePacket(brl, 4, NULL, 0)) {
+          while (serialAwaitInput(serialDevice, 500)) {
+            unsigned char response[3];
+            int size = readPacket(brl, response, sizeof(response));
+            if (size <= 0) break;
+
+            if (response[1] == 4) {
+              brl->textColumns = response[2];
+              brl->textRows = 1;
+
+              makeOutputTable(dotsTable_ISO11548_1);
+              makeInputTable();
+
+              if (!clearBrailleCells(brl)) break;
+              if (!clearVisualText(brl)) break;
+              if (!writeBrailleCells(brl)) break;
+
+              return 1;
+            }
+          }
+        }
+      }
+    }
+
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+  
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+    serialDevice = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (text) {
+    if (wmemcmp(text, visualText, brl->textColumns) != 0) {
+      wmemcpy(visualText, text, brl->textColumns);
+      if (!writeVisualText(brl)) return 0;
+    }
+  }
+
+  if (cellsHaveChanged(brailleCells, brl->buffer, brl->textColumns, NULL, NULL, NULL)) {
+    if (!writeBrailleCells(brl)) return 0;
+  }
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  while (1) {
+    unsigned char packet[3];
+    int size = readPacket(brl, packet, sizeof(packet));
+    if (size == 0) break;
+    if (size < 0) return BRL_CMD_RESTARTBRL;
+
+    switch (packet[1]) {
+      default:
+        break;
+
+      case 1:
+        return BRL_CMD_BLK(PASSDOTS) | translateInputCell(packet[2]);
+
+      case 2: {
+        unsigned char column = packet[2];
+        if (column && (column <= brl->textColumns)) return BRL_CMD_BLK(ROUTE) + (column - 1);
+        break;
+      }
+
+      case 3:
+        switch (packet[2]) {
+          default:
+            break;
+
+          // left rear: two columns, one row
+          case 0X02: // ESC
+            return BRL_CMD_LEARN;
+          case 0X01: // M
+            return BRL_CMD_PREFMENU;
+
+          // left middle: cross
+          case 0X06: // up
+            return BRL_CMD_LNUP;
+          case 0X03: // left
+            return BRL_CMD_FWINLT;
+          case 0X05: // right
+            return BRL_CMD_FWINRT;
+          case 0X04: // down
+            return BRL_CMD_LNDN;
+
+          // left front: two columns, three rows
+          case 0X09: // ins
+            return BRL_CMD_RETURN;
+          case 0X0A: // E
+            return BRL_CMD_TOP;
+          case 0X0B: // supp
+            return BRL_CMD_CSRTRK;
+          case 0X0C: // L
+            return BRL_CMD_BOT;
+          case 0X07: // extra 1 (40s only)
+            return BRL_CMD_CHRLT;
+          case 0X08: // extra 2 (40s only)
+            return BRL_CMD_CHRRT;
+
+          case 0x0E: // left thumb
+            return BRL_CMD_KEY(BACKSPACE);
+          case 0x0F: // right thumb
+            return BRL_CMD_BLK(PASSDOTS);
+          case 0x3F: // both thumbs
+            return BRL_CMD_KEY(ENTER);
+
+          case 0X29: // key under dot 7
+            return BRL_CMD_KEY(ESCAPE);
+          case 0X2A: // key under dot 8
+            return BRL_CMD_KEY(TAB);
+
+          // right rear: one column, one row
+          case 0X19: // extra 3 (40s only)
+            return BRL_CMD_INFO;
+
+          // right middle: one column, two rows
+          case 0X1B: // extra 4 (40s only)
+            return BRL_CMD_PRDIFLN;
+          case 0X1A: // extra 5 (40s only)
+            return BRL_CMD_NXDIFLN;
+
+          // right front: one column, four rows
+          case 0X2B: // slash (40s only)
+            return BRL_CMD_FREEZE;
+          case 0X2C: // asterisk (40s only)
+            return BRL_CMD_DISPMD;
+          case 0X2D: // minus (40s only)
+            return BRL_CMD_ATTRVIS;
+          case 0X2E: // plus (40s only)
+            return BRL_CMD_CSRVIS;
+
+          // first (top) row of numeric pad
+          case 0X37: // seven (40s only)
+            return BRL_CMD_KEY(HOME);
+          case 0X38: // eight (40s only)
+            return BRL_CMD_KEY(CURSOR_UP);
+          case 0X39: // nine (40s only)
+            return BRL_CMD_KEY(PAGE_UP);
+
+          // second row of numeric pad
+          case 0X34: // four (40s only)
+            return BRL_CMD_KEY(CURSOR_LEFT);
+          case 0X35: // five (40s only)
+            return BRL_CMD_CSRJMP_VERT;
+          case 0X36: // six (40s only)
+            return BRL_CMD_KEY(CURSOR_RIGHT);
+
+          // third row of numeric pad
+          case 0X31: // one (40s only)
+            return BRL_CMD_KEY(END);
+          case 0X32: // two (40s only)
+            return BRL_CMD_KEY(CURSOR_DOWN);
+          case 0X33: // three (40s only)
+            return BRL_CMD_KEY(PAGE_DOWN);
+
+          // fourth (bottom) row of numeric pad
+          case 0X28: // verr num (40s only)
+            return BRL_CMD_SIXDOTS;
+          case 0X30: // zero (40s only)
+            return BRL_CMD_KEY(INSERT);
+          case 0X2F: // supp (40s only)
+            return BRL_CMD_KEY(DELETE);
+        }
+        break;
+
+      /* When data is written to the display it acknowledges with:
+       * 0X00 0X04 0Xxx
+       * where xx is the number of bytes written.
+       */
+      case 4:
+        continue;
+    }
+
+    logUnexpectedPacket(packet, size);
+  }
+
+  return EOF;
+}
diff --git a/Drivers/Braille/VideoBraille/Makefile.in b/Drivers/Braille/VideoBraille/Makefile.in
new file mode 100644
index 0000000..7022873
--- /dev/null
+++ b/Drivers/Braille/VideoBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = vd
+DRIVER_NAME = VideoBraille
+DRIVER_USAGE = 40
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+	
diff --git a/Drivers/Braille/VideoBraille/README b/Drivers/Braille/VideoBraille/README
new file mode 100644
index 0000000..1e95215
--- /dev/null
+++ b/Drivers/Braille/VideoBraille/README
@@ -0,0 +1,23 @@
+Introduction
+
+This is the driver for the Italian Videobraille, the braille display distributed by Tiflosoft.
+This driver works ONLY on the VB 40 models and doesn't work with the PRO 9600 models.
+I can write also a PRO 9600 extension if somebody asks me, because it consists of a small change to the code; I haven't implemented it just because I couldn't test it.
+
+About implementation
+
+This driver is not perfect. I encountered many problems based mainly on timing, and the write function has to write the line to the display twice, don't ask me why: it's only the result of tests.
+To implement the driver, I contacted the designer and author of the display to have some documentation.
+
+If you want to contact me for any kind of problem or suggestion, my e-mail address is christian_comaschi@libero.it
+But, before you contact me telling me that the driver doesn't work, check that:
+- your Videobraille is not PRO 9600 (if it is and you need the driver, just ask);
+- in braille.h LPTPORT is set to the correct address: 0x378 for LPT1, 0x278 for LPT2,...
+If all is correct, then try changing in braille.h the VBDELAY, VBCLOCK and VBREFRESHDELAY values: these values depend on computer speed, so they need to fit your computer. I think this is not a good way of timing, but I wanted to respect the original implementation, because when I used other kinds of timing I encountered problems.
+
+About cut & paste
+
+I have updated the driver later to implement cut & paste this way: you mark the beginning of a block by keeping one of the 40 small buttons pressed and pressing the Edit key; you mark the end of a block by keeping a small button pressed and pressing Menu.
+You have to choose the small button to press like in cursor routing.
+Then, to paste you just have to press the Attributes key together with the Menu key.
+For this implementation I had to invent key bindings because cut and paste was not a feature of the original DOS driver. Anyway, it's easy to remember them.
diff --git a/Drivers/Braille/VideoBraille/README.it b/Drivers/Braille/VideoBraille/README.it
new file mode 100644
index 0000000..dcfd6a3
--- /dev/null
+++ b/Drivers/Braille/VideoBraille/README.it
@@ -0,0 +1,23 @@
+Introduzione
+
+Questo è il driver per il display braille Videobraille, distribuito dalla Tiflosoft.
+Questo driver funziona SOLO con il modello VB 40 e non funziona con l'aggiornamento PRO 9600.
+Potrei eventualmente aggiungere anche il supporto per il modello PRO 9600 se mi fosse richiesto, poiché si tratta solo di qualche breve modifica al codice; non ho implementato ancora il supporto per questo modello semplicemente perché non avrei avuto modo di testarlo.
+
+Implementazione
+
+Questo driver non è perfetto. Ho riscontrato vari problemi relativi soprattutto alla temporizzazione, e ho dovuto implementare una funzione di scrittura che scrive ogni riga due volte sul display braille, non chiedetermi perché: è solo questione di test.
+Per implementare il driver, ho contattato direttamente chi ha progettato questo display (Acquistapace) per ottenere della documentazione.
+
+Se volete contattarmi per qualunque problema o suggerimento, il mio indirizzo e-mail è christian_comaschi@libero.it
+Però, prima di contattarmi e scrivere che il driver non funziona, controllate che:
+- la vostra Videobraille non è PRO 9600 (se lo è e volete il driver, chiedete);
+- nel file braille.h LPTPORT è impostato correttamente: 0x378 per LPT1, 0x278 per LPT2,...
+Se tutto è giusto, provate a modificare in braille.h i valori VBDELAY, VBCLOCK e VBREFRESHDELAY: questi valori dipendono dalla velocità del computer, devono quindi essere adattati al vostro processore. Non penso che questo sia un buon modo di impostare i tempi di attesa, ma ho voluto rispettare l'implementazione originale, perché quando ho usato altri tipi di temporizzazione ho riscontrato problemi.
+
+Implementazione della funzione taglia/incolla
+
+Ho aggiornato in seguito il driver per implementare la funzione taglia/incolla in questo modo: si seleziona l'inizio di un blocco tenendo premuto uno dei 40 tastini dell'aggancio cursore e premendo Edit; si contrassegna la fine di un blocco tenendo premuto uno dei 40 tastini e premendo Menu.
+Il tastino da premere va selezionato come nella procedura di aggancio cursore.
+Quindi, per incollare il testo, basta premere contemporaneamente i tasti Attr e Menu.
+Per questa implementazione ho dovuto inventare le combinazioni dei tasti perché la funzione copia/incolla non esisteva nel driver DOS originale.
diff --git a/Drivers/Braille/VideoBraille/braille.c b/Drivers/Braille/VideoBraille/braille.c
new file mode 100644
index 0000000..5d30b2b
--- /dev/null
+++ b/Drivers/Braille/VideoBraille/braille.c
@@ -0,0 +1,206 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Thanks to the authors of the Vario-HT driver: the implementation of this
+ * driver is similar to the Vario-HT one.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "timing.h"
+#include "ports.h"
+
+#include "brl_driver.h"
+#include "braille.h"
+
+static unsigned char lastbuff[40];
+
+#define LPTSTATUSPORT LPTPORT+1
+#define LPTCONTROLPORT LPTPORT+2
+  
+static void vbclockpause() {
+  int i;
+  for (i = 0; i<=VBCLOCK*100; i++) ;
+}
+
+static void vbdisplay(unsigned char *vbBuf) {
+  int i,j;
+  char b;
+  for (j = 0; j<VBSIZE; j++) {
+    for (i = 7; i>=0; i--) {
+      b = (vbBuf[j] << i) & VBLPTDATA;
+      writePort1(LPTPORT, b);
+      vbclockpause();
+      writePort1(LPTPORT, b | VBLPTCLOCK);
+      vbclockpause();
+    }
+  }
+  writePort1(LPTPORT, b | VBLPTCLOCK);
+  for (i = 0; i<=7; i++) vbclockpause();
+  writePort1(LPTPORT, 0);
+  for (i = 0; i<=7; i++) vbclockpause();
+  writePort1(LPTPORT, VBLPTSTROBE);
+  for (i = 0; i<=7; i++) vbclockpause();
+  writePort1(LPTPORT, 0);
+  vbclockpause();
+}
+
+static int vbinit() {
+  if (enablePorts(LOG_ERR, LPTPORT, 3)) {
+    if (enablePorts(LOG_ERR, 0X80, 1)) {
+      makeOutputTable(dotsTable_ISO11548_1);
+
+      {
+        unsigned char alldots[40];
+        memset(alldots, 0XFF, 40);
+        vbdisplay(alldots);
+      }
+
+      return 0;
+    }
+    disablePorts(LPTPORT, 3);
+  }
+
+  logMessage(LOG_ERR, "Error: must be superuser");
+  return -1;
+}
+
+void vbsleep(long x) {
+  int i;
+  for (i = 0; i<x; i++) writePort1(0x80, 1);
+}
+
+static void BrButtons(vbButtons *dest) {
+  char i;
+  dest->bigbuttons = 0;
+  dest->keypressed = 0;
+  for (i = 47; i>=40; i--) {
+    writePort1(LPTPORT, i);
+    vbsleep(VBDELAY);
+    if ((readPort1(LPTSTATUSPORT) & 0x08)==0) {
+      dest->bigbuttons |= (1 << (i-40));
+      dest->keypressed = 1;
+    }
+  }
+  dest->routingkey = 0;
+  for (i = 40; i>0; i--) {
+    writePort1(LPTPORT, i-1);
+    vbsleep(VBDELAY);
+    if ((readPort1(LPTSTATUSPORT) & 0x08)==0) {
+      dest->routingkey = i;
+      dest->keypressed = 1;
+      break;
+    }
+  }
+}
+
+static int brl_construct(BrailleDisplay *brl, char **parameters, const char *dev) {
+  /*	Seems to signal en error */ 
+  if (!vbinit()) {
+    /* Theese are pretty static */ 
+    brl->textColumns=40;
+    brl->textRows=1;
+    return 1;
+  }
+  return 0;
+}
+
+static void brl_destruct(BrailleDisplay *brl) {
+}
+
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text) {
+  const size_t cells = 40;
+  unsigned char outbuff[cells];
+
+  /* Only display something if the data actually differs, this 
+  *  could most likely cause some problems in redraw situations etc
+  *  but since the darn thing wants to redraw quite frequently otherwise 
+  *  this still makes a better lookin result */ 
+  if (cellsHaveChanged(lastbuff, brl->buffer, cells, NULL, NULL, NULL)) {
+    translateOutputCells(outbuff, brl->buffer, cells);
+    vbdisplay(outbuff);
+    vbdisplay(outbuff);
+    brl->writeDelay += VBREFRESHDELAY;
+  }
+  return 1;
+}
+
+static int brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context) {
+  vbButtons buttons;
+  BrButtons(&buttons);
+  if (!buttons.keypressed) {
+    return EOF;
+  } else {
+    vbButtons b;
+    do {
+      BrButtons(&b);
+      buttons.bigbuttons |= b.bigbuttons;
+
+      {
+        const TimeValue duration = {
+          .seconds = 0,
+          .nanoseconds = 1 * NSECS_PER_USEC
+        };
+
+        accurateDelay(&duration);
+      }
+    } while (b.keypressed);
+    /* Test which buttons has been pressed */
+    if (buttons.bigbuttons==KEY_UP) return BRL_CMD_LNUP;
+    else if (buttons.bigbuttons==KEY_LEFT) return BRL_CMD_FWINLT;
+    else if (buttons.bigbuttons==KEY_RIGHT) return BRL_CMD_FWINRT;
+    else if (buttons.bigbuttons==KEY_DOWN) return BRL_CMD_LNDN;
+    else if (buttons.bigbuttons==KEY_ATTRIBUTES) return BRL_CMD_ATTRVIS;
+    else if (buttons.bigbuttons==KEY_CURSOR) return BRL_CMD_CSRVIS;
+    else if (buttons.bigbuttons==KEY_HOME) {
+      /* If a routing key has been pressed, then mark the beginning of a block;
+         go to cursor position otherwise */
+      return (buttons.routingkey>0) ? BRL_CMD_BLK(CLIP_NEW)+buttons.routingkey-1 : BRL_CMD_HOME;
+    }
+    else if (buttons.bigbuttons==KEY_MENU) {
+      /* If a routing key has been pressed, then mark the end of a block;
+         go to preferences menu otherwise */
+      return (buttons.routingkey>0) ? BRL_CMD_BLK(COPY_RECT)+buttons.routingkey-1 : BRL_CMD_PREFMENU;
+    }
+    else if (buttons.bigbuttons==(KEY_ATTRIBUTES | KEY_MENU)) return BRL_CMD_PASTE;
+    else if (buttons.bigbuttons==(KEY_CURSOR | KEY_LEFT)) return BRL_CMD_CHRLT;
+    else if (buttons.bigbuttons==(KEY_HOME | KEY_RIGHT)) return BRL_CMD_CHRRT;
+    else if (buttons.bigbuttons==(KEY_UP | KEY_LEFT)) return BRL_CMD_TOP_LEFT;
+    else if (buttons.bigbuttons==(KEY_RIGHT | KEY_DOWN)) return BRL_CMD_BOT_LEFT;
+    else if (buttons.bigbuttons==(KEY_ATTRIBUTES | KEY_DOWN)) return BRL_CMD_HELP;
+    else if (buttons.bigbuttons==(KEY_MENU | KEY_CURSOR)) return BRL_CMD_INFO;
+    else if (buttons.bigbuttons==0) {
+      /* A cursor routing key has been pressed */
+      if (buttons.routingkey>0) {
+        const TimeValue duration = {
+          .seconds = 0,
+          .nanoseconds = 5 * NSECS_PER_USEC
+        };
+
+        accurateDelay(&duration);
+        return BRL_CMD_BLK(ROUTE)+buttons.routingkey-1;
+      }
+      else return EOF;
+    } else
+      return EOF;
+  }
+}
diff --git a/Drivers/Braille/VideoBraille/braille.h b/Drivers/Braille/VideoBraille/braille.h
new file mode 100644
index 0000000..8aaf253
--- /dev/null
+++ b/Drivers/Braille/VideoBraille/braille.h
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define VBSIZE 40
+#define LPTPORT 0x278
+#define VBDELAY 3
+#define VBCLOCK 5
+#define VBREFRESHDELAY 40 // Min. time to wait before updating the display
+
+// Don't touch the following definitions
+#define VBLPTSTROBE 0x40
+#define VBLPTCLOCK 0x20
+#define VBLPTDATA 0x80
+
+#define KEY_UP 0x1
+#define KEY_LEFT 0x2
+#define KEY_RIGHT 0x4
+#define KEY_DOWN 0x8
+#define KEY_ATTRIBUTES 0x10
+#define KEY_CURSOR 0x20
+#define KEY_HOME 0x40
+#define KEY_MENU 0x80
+
+typedef struct {
+  unsigned char bigbuttons;
+  char routingkey : 7;
+  char keypressed : 1;
+} vbButtons;
+
diff --git a/Drivers/Braille/Virtual/Makefile.in b/Drivers/Braille/Virtual/Makefile.in
new file mode 100644
index 0000000..528fd8c
--- /dev/null
+++ b/Drivers/Braille/Virtual/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = vr
+DRIVER_NAME = Virtual
+DRIVER_USAGE = remote client/server via TCP/IP or local socket
+DRIVER_VERSION = 0.1
+DRIVER_DEVELOPERS = Mario Lang <mlang@delysid.org>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Virtual/README b/Drivers/Braille/Virtual/README
new file mode 100644
index 0000000..b0a52ca
--- /dev/null
+++ b/Drivers/Braille/Virtual/README
@@ -0,0 +1,170 @@
+The Virtual Braille Display Driver
+----------------------------------
+
+This is a virtual braille display driver for BRLTTY.  It uses a simple
+text-based protocol over a socket to transmit information to, and to receive
+information from, the virtual braille display application (from now on, "the
+display"). The intention of this driver is two-fold:
+ * Give interested developers the ability to test BRLTTY without requiring
+   access to a real braille display.
+ * Enable the implementation of a visual braille display based on this driver
+   which allows a sighted person to see, and even to experience, how a blind
+   person uses a braille display to work with a computer.
+
+This driver supports the following devices:
+   client:[address]
+      The driver periodically tries to connect to the display at the specified 
+      network address.
+   server:[address]
+      The driver waits on the specified network address for a connection 
+      request from the display.
+
+If the network address isn't specified then the driver's default TCP/IP port on
+the local host is used. The address may be:
+   On Unix systems: /path/to/unix-socket-file
+      If the address starts with a slash (/) then it's interpreted as the path 
+      to a Unix socket file. If the driver is the client then the socket file 
+      must already exist. If the driver is the server then the socket file is 
+      created by the driver and must not already exist.
+   On Windows NT/2000/XP/2003 systems: \\.\pipe\pipename
+      If the address starts with a backslash (\) then it's interpreted as the
+      path to a named pipe. If the driver is the client, then the named pipe
+      must already exist. If the driver is the server then the named pipe is
+      created by the driver and must not already exist. Beware of the shell's
+      interpretation of backslashes.
+   On all systems: [host][:[port]]
+      A TCP/IP address. Both the host and port components of the address are 
+      optional. The delimiting colon (:) is only required if the port is being 
+      specified. The host may be a name, an alias, or an IPV4 address (in 
+      dotted-decimal form); if it's omitted then the local host is assumed. The 
+      port may be a service name or a port number; if it's omitted then 35752 
+      is assumed.
+
+
+The Protocol
+------------
+
+After the connection between the driver and the display is complete, data is
+exchanged between them via command lines. The driver first waits for a "cells"
+command in order to determine the dimensions of the braille window. After this
+has been taken care of, commands freely flow in both directions without any
+restrictions. The braille window dimensions are appropriately reconfigured
+whenever another "cells" command is received. The "quit" command, which is also
+recognized during the initial wait for the first "cells" command, instructs the
+driver to close its end of the connection and to restart.
+
+The command lines are in plain text. Each is terminated by a line-feed [0X0A]
+(usually known on Unix systems as a new-line). A carriage-return [0X0D] may
+optionally precede the line-feed. Command lines sent to the display contain a
+carriage-return if, and only if, the most recent command line received from the
+display contained one.
+
+Command lines are composed of space-delimited words, numbers, and strings.
+Space consists of one or more blanks and/or tabs. Case is only significant
+within a string.
+
+A "number" may be specified as decimal, octal, or hexadecimal. The usual C
+syntax is recognized. If the first digit is 0 then the number is octal. If the
+first digit isn't 0 then the number is decimal. If the number begins with
+either 0X or 0x then the number is hexadecimal. The following three numbers are
+the same:
+   15   Decimal
+   017  Octal
+   0XF  Hexadecimal
+
+A "string" is a sequence of characters (including space characters) enclosed
+within double quotes ("). Special characters within a string are prefixed with
+a backslash (\). The following special characters are supported:
+   \\   A literal backslash.
+   \"   A literal double quote.
+   \Xxx Any character specified via its hexadecimal value.
+For example: "A \" (double quote) must be escaped with a \\ (backslash)."
+
+A "dots" is a string which describes a braille character sequence. Space
+(blanks and/or tabs) within the string are ignored. The braille characters are
+separated from one another by a vertical bar (|). Each braille character
+consists of the numbers (1 through 8) of the dots which represent it. For
+example, the first three letters of the English alphabet would be "1|12|14".
+
+
+Commands Recognized by the Driver
+---------------------------------
+
+Cells columns [rows]
+   The number of columns and rows in the text portion of the display. If the
+   number of rows isn't supplied then 1 is assumed. This is the first command
+   which the display must send to the driver. It may be sent at any other time
+   as well to notify the driver that the dimensions of the text portion of the
+   display have changed.
+
+Quit
+   Upon receipt of this command, the driver closes its end of the connection
+   and then restarts. It's recognized at any time, including during the initial
+   wait for the first "cells" command from the display.
+
+<basic-command> [state]
+   A basic command for the BRLTTY core. It may be any of the BRL_CMD_ constants
+   (without the BRL_CMD_ prefix) defined within "brldefs.h", e.g. LnDn. The
+   optional <state> operand (which may be either on or off), when applied to an
+   attribute toggling command, forces that attribute to the specified state.
+
+<cell-command> number
+   A cell command for the BRLTTY core. It may be any of the BRL_BLK_ constants
+   (without the BRL_BLK_ prefix) defined within "brldefs.h", e.g. Route. The cell
+   number, starting at 1 for the leftmost cell, must be supplied. If the
+   display has more than one row then the cells for the top row are counted
+   first, e.g. if the display has 32 columns and 2 rows then 35 refers to the
+   third cell on the second row.
+
+<key-command> [number]
+   A key command for the BRLTTY core. It may be any of the VPK_ constants
+   (without the VPK_ prefix) defined within "brldefs.h", e.g. Return. The
+   Function command requires that the function key number, starting at 1, be
+   supplied. All the other commands in this set don't accept a number operand.
+
+
+Commands Sent to the Display
+----------------------------
+
+Braille dots
+   Cells to be presented on the text portion of the display. Excess cells in
+   the data should be ignored. Excess cells on the display should be blank.
+
+Visual string
+   Characters to be presented on the visual portion of the display. Excess
+   characters in the data should be ignored. Excess characters on the display
+   should be blank.
+
+Status dots
+   Cells to be presented on the status portion of the display. Excess cells in
+   the data should be ignored. Excess cells on the display should be blank.
+
+<attribute> <setting>
+   The current setting of any of several attributes within the BRLTTY core.
+   These are only sent if "Status Style" is set to "Generic". <attribute> is
+   the name of any of the STAT_ constants (without the STAT_ prefix) defined
+   within "brldefs.h", e.g. BrlRow. <setting> is a number. Rows and columns
+   start at 1. Flags use 0 for "off" and 1 for "on".
+
+
+Security Implications
+---------------------
+
+Please be aware that this driver, when used carelessly, might open severe
+security holes. Connections to or from this driver are in no way authenticated
+so you yourself need to take care of protecting access to a BRLTTY instance
+which uses it. In particular, by sending out information about the currently
+active virtual console, this driver might expose valuable information about
+your system to a potential attacker. The attacker might also use the
+<key-command> family of commands to directly manipulate, possibly even
+inputting data to, your current, or even another, virtual console. In effect,
+this driver could be used to completely remote-control your system.
+
+Please be aware of these implications when experimenting with this driver. If
+you're using TCP/IP, be sure to protect access to the used host and port via
+facilities such as firewalls. You should take special precautions on multi-user
+systems where untrusted people could use unprotected access to this driver to
+gain root access to the machine.
+
+
+ -- Mario Lang <mlang@delysid.org>
diff --git a/Drivers/Braille/Virtual/braille.c b/Drivers/Braille/Virtual/braille.c
new file mode 100644
index 0000000..5451fc8
--- /dev/null
+++ b/Drivers/Braille/Virtual/braille.c
@@ -0,0 +1,1266 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef __MINGW32__
+#include <ws2tcpip.h>
+#include "system_windows.h"
+#else /* __MINGW32__ */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif /* __MINGW32__ */
+#include "get_select.h"
+
+#if !defined(AF_LOCAL) && defined(AF_UNIX)
+#define AF_LOCAL AF_UNIX
+#endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */
+ 
+#if !defined(PF_LOCAL) && defined(PF_UNIX)
+#define PF_LOCAL PF_UNIX
+#endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */
+ 
+#ifdef WINDOWS
+#undef AF_LOCAL
+#endif /* WINDOWS */
+
+#ifdef __MINGW32__
+#define close(fd) CloseHandle((HANDLE)(fd))
+#define LogSocketError(msg) logWindowsSocketError(msg)
+#else /* __MINGW32__ */
+#define LogSocketError(msg) logSystemError(msg)
+#endif /* __MINGW32__ */
+
+#include "log.h"
+#include "io_misc.h"
+#include "parse.h"
+#include "async_wait.h"
+#include "charset.h"
+#include "cmd.h"
+
+#define BRL_STATUS_FIELDS sfGeneric
+#define BRL_HAVE_STATUS_CELLS
+#include "brl_driver.h"
+#include "braille.h"
+
+static int fileDescriptor = -1;
+
+#define INPUT_SIZE 0X200
+static char inputBuffer[INPUT_SIZE];
+static size_t inputLength;
+static size_t inputStart;
+static int inputEnd;
+static int inputCarriageReturn;
+static const char *inputDelimiters = " ";
+
+#define OUTPUT_SIZE 0X200
+static char outputBuffer[OUTPUT_SIZE];
+static size_t outputLength;
+
+typedef struct {
+  const CommandEntry *entry;
+  unsigned int count;
+} CommandDescriptor;
+static CommandDescriptor *commandDescriptors = NULL;
+static const size_t commandSize = sizeof(*commandDescriptors);
+static size_t commandCount;
+
+static int brailleColumns;
+static int brailleRows;
+static int brailleCount;
+static unsigned char *brailleCells = NULL;
+static wchar_t *textCharacters = NULL;
+
+static int statusColumns;
+static int statusRows;
+static int statusCount;
+static unsigned char *statusCells = NULL;
+static unsigned char genericCells[GSC_COUNT];
+
+typedef struct {
+#ifdef AF_LOCAL
+  int (*getLocalConnection) (const struct sockaddr_un *address);
+#endif /* AF_LOCAL */
+
+#ifdef __MINGW32__
+  int (*getNamedPipeConnection) (const char *path);
+#endif /* __MINGW32__ */
+
+  int (*getInetConnection) (const struct sockaddr_in *address);
+} ModeEntry;
+static const ModeEntry *mode;
+
+typedef struct {
+  int (*read) (int descriptor, void *buffer, int size);
+} OperationsEntry;
+static const OperationsEntry *operations;
+
+static int
+readNetworkSocket (int descriptor, void *buffer, int size) {
+  if (awaitSocketInput(descriptor, 0)) {
+    int count = recv(descriptor, buffer, size, 0);
+    if (count != -1) return count;
+    LogSocketError("recv");
+  }
+
+  return -1;
+}
+
+static const OperationsEntry socketOperationsEntry = {
+  readNetworkSocket
+};
+
+static char *
+formatSocketAddress (const struct sockaddr *address) {
+  char *string;
+
+  switch (address->sa_family) {
+#ifdef AF_LOCAL
+    case AF_LOCAL: {
+      const struct sockaddr_un *localAddress = (const struct sockaddr_un *)address;
+
+      string = strdup(localAddress->sun_path);
+      break;
+    }
+#endif /* AF_LOCAL */
+
+    case AF_INET: {
+      const struct sockaddr_in *inetAddress = (const struct sockaddr_in *)address;
+      const char *host = inet_ntoa(inetAddress->sin_addr);
+      unsigned short port = ntohs(inetAddress->sin_port);
+      char buffer[strlen(host) + 7];
+
+      snprintf(buffer, sizeof(buffer), "%s:%u", host, port);
+      string = strdup(buffer);
+      break;
+    }
+
+    default:
+      string = strdup("");
+      break;
+  }
+
+  if (!string) logMallocError();
+  return string;
+}
+
+static int
+acceptSocketConnection (
+  int (*getSocket) (void),
+  int (*prepareQueue) (int socket),
+  void (*unbindAddress) (const struct sockaddr *address),
+  const struct sockaddr *localAddress, socklen_t localSize,
+  struct sockaddr *remoteAddress, socklen_t *remoteSize
+) {
+  int serverSocket = -1;
+  int queueSocket;
+
+  if ((queueSocket = getSocket()) != -1) {
+    if (!prepareQueue || prepareQueue(queueSocket)) {
+      if (bind(queueSocket, localAddress, localSize) != -1) {
+        if (listen(queueSocket, 1) != -1) {
+          int attempts = 0;
+
+          {
+            char *address = formatSocketAddress(localAddress);
+
+            if (address) {
+              logMessage(LOG_NOTICE, "listening on: %s", address);
+              free(address);
+            }
+          }
+
+          while (1) {
+            fd_set readMask;
+            struct timeval timeout;
+
+            FD_ZERO(&readMask);
+            FD_SET(queueSocket, &readMask);
+
+            memset(&timeout, 0, sizeof(timeout));
+            timeout.tv_sec = 10;
+
+            ++attempts;
+            switch (select(queueSocket+1, &readMask, NULL, NULL, &timeout)) {
+              case -1:
+                if (errno == EINTR) continue;
+                LogSocketError("select");
+                break;
+
+              case 0:
+                logMessage(LOG_DEBUG, "no connection yet, still waiting (%d).", attempts);
+                continue;
+
+              default: {
+                if (!FD_ISSET(queueSocket, &readMask)) continue;
+
+                if ((serverSocket = accept(queueSocket, remoteAddress, remoteSize)) != -1) {
+                  char *address = formatSocketAddress(remoteAddress);
+
+                  if (address) {
+                    logMessage(LOG_NOTICE, "client is: %s", address);
+                    free(address);
+                  }
+                } else {
+                  LogSocketError("accept");
+                }
+              }
+            }
+            break;
+          }
+        } else {
+          LogSocketError("listen");
+        }
+
+        if (unbindAddress) unbindAddress(localAddress);
+      } else {
+        LogSocketError("bind");
+      }
+    }
+
+    close(queueSocket);
+  } else {
+    LogSocketError("socket");
+  }
+
+  operations = &socketOperationsEntry;
+  return serverSocket;
+}
+
+static int
+requestConnection (
+  int (*getSocket) (void),
+  const struct sockaddr *remoteAddress, socklen_t remoteSize
+) {
+  int clientSocket;
+
+  {
+    char *address = formatSocketAddress(remoteAddress);
+
+    if (address) {
+      logMessage(LOG_DEBUG, "connecting to: %s", address);
+      free(address);
+    }
+  }
+
+  if ((clientSocket = getSocket()) != -1) {
+    if (connect(clientSocket, remoteAddress, remoteSize) != -1) {
+      {
+        char *address = formatSocketAddress(remoteAddress);
+
+        if (address) {
+          logMessage(LOG_NOTICE, "connected to: %s", address);
+          free(address);
+        }
+      }
+
+      operations = &socketOperationsEntry;
+      return clientSocket;
+    } else {
+      logMessage(LOG_WARNING, "connect error: %s", strerror(errno));
+    }
+
+    close(clientSocket);
+  } else {
+    LogSocketError("socket");
+  }
+
+  return -1;
+}
+
+static int
+setSocketReuseAddress (int socket) {
+  int yes = 1;
+
+  if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)) != -1) {
+    return 1;
+  } else {
+    LogSocketError("setsockopt REUSEADDR");
+  }
+  return 0;
+}
+
+#ifdef AF_LOCAL
+static int
+setLocalAddress (const char *string, struct sockaddr_un *address) {
+  int ok = 1;
+
+  memset(address, 0, sizeof(*address));
+  address->sun_family = AF_LOCAL;
+
+  if (strlen(string) < sizeof(address->sun_path)) {
+    strncpy(address->sun_path, string, sizeof(address->sun_path)-1);
+  } else {
+    ok = 0;
+    logMessage(LOG_WARNING, "Local socket path too long: %s", string);
+  }
+
+  return ok;
+}
+
+static int
+getLocalSocket (void) {
+  return socket(PF_LOCAL, SOCK_STREAM, 0);
+}
+
+static void
+unbindLocalAddress (const struct sockaddr *address) {
+  const struct sockaddr_un *localAddress = (const struct sockaddr_un *)address;
+  if (unlink(localAddress->sun_path) == -1) {
+    logSystemError("unlink");
+  }
+}
+
+static int
+acceptLocalConnection (const struct sockaddr_un *localAddress) {
+  struct sockaddr_un remoteAddress;
+  socklen_t remoteSize = sizeof(remoteAddress);
+
+  return acceptSocketConnection(getLocalSocket, NULL, unbindLocalAddress,
+                                (const struct sockaddr *)localAddress, sizeof(*localAddress),
+                                (struct sockaddr *)&remoteAddress, &remoteSize);
+}
+
+static int
+requestLocalConnection (const struct sockaddr_un *remoteAddress) {
+  return requestConnection(getLocalSocket,
+                           (const struct sockaddr *)remoteAddress, sizeof(*remoteAddress));
+}
+#endif /* AF_LOCAL */
+
+#ifdef __MINGW32__
+static int
+readNamedPipe (int descriptor, void *buffer, int size) {
+  {
+    DWORD available;
+
+    if (!PeekNamedPipe((HANDLE)descriptor, NULL, 0, NULL, &available, NULL)) {
+      logWindowsSystemError("PeekNamedPipe");
+      return 0;
+    }
+
+    if (!available) {
+      errno = EAGAIN;
+      return -1;
+    }
+
+    if (available < size) size = available;
+  }
+
+  {
+    DWORD received;
+    OVERLAPPED overl = {0, 0, {{0, 0}}, NULL};
+    overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+    if (!ReadFile((HANDLE)descriptor, buffer, size, &received, &overl)) {
+      if (GetLastError() != ERROR_IO_PENDING) {
+        logWindowsSystemError("ReadPipe");
+        received = 0;
+      } else if (!GetOverlappedResult((HANDLE)descriptor, &overl, &received, TRUE)) {
+        logWindowsSystemError("GetOverlappedResult");
+        received = 0;
+      }
+    }
+
+    CloseHandle(overl.hEvent);
+    return received;
+  }
+}
+
+static const OperationsEntry namedPipeOperationsEntry = {
+  readNamedPipe
+};
+
+static int
+acceptNamedPipeConnection (const char *path) {
+  HANDLE h;
+  OVERLAPPED overl = {0, 0, {{0, 0}}, NULL};
+  DWORD res;
+  int attempts = 0;
+
+  if ((h = CreateNamedPipe(path, 
+                                PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+                                PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
+                                1, 0, 0, 0, NULL)) == INVALID_HANDLE_VALUE) {
+    logWindowsSystemError("CreateNamedPipe");
+    return -1;
+  }
+
+  overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+  if (!ConnectNamedPipe(h, &overl)) {
+    switch (GetLastError()) {
+      case ERROR_IO_PENDING:
+        while ((res = WaitForSingleObject(overl.hEvent, 10000)) != WAIT_OBJECT_0) {
+          if (res == WAIT_TIMEOUT) {
+            ++attempts;
+            logMessage(LOG_DEBUG, "no connection yet, still waiting (%d).", attempts);
+          } else {
+            logWindowsSystemError("ConnectNamedPipe");
+            CloseHandle(h);
+            h = (HANDLE) -1;
+            break;
+          }
+        }
+
+      case ERROR_PIPE_CONNECTED:
+        break;
+
+      default:
+        logWindowsSystemError("ConnectNamedPipe");
+        CloseHandle(h);
+        h = (HANDLE) -1;
+        break;
+    }
+  }
+
+  CloseHandle(overl.hEvent);
+  operations = &namedPipeOperationsEntry;
+  return (int)h;
+}
+
+static int
+requestNamedPipeConnection (const char *path) {
+  HANDLE h;
+
+  if ((h = CreateFile(path,
+                      GENERIC_READ|GENERIC_WRITE,
+                      FILE_SHARE_READ|FILE_SHARE_WRITE,
+                      NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE) {
+    logWindowsSystemError("Connect to named pipe");
+    return -1;
+  }
+
+  operations = &namedPipeOperationsEntry;
+  return (int)h;
+}
+#endif /* __MINGW32__ */
+
+static int
+setInetAddress (const char *string, struct sockaddr_in *address) {
+  int ok = 1;
+  char *hostName = strdup(string);
+
+  if (hostName) {
+    char *portNumber = strchr(hostName, ':');
+
+    if (portNumber) {
+      *portNumber++ = 0;
+      if (!*portNumber) portNumber = NULL;
+    }
+
+    memset(address, 0, sizeof(*address));
+    address->sin_family = AF_INET;
+
+    if (*hostName) {
+      const struct hostent *host = gethostbyname(hostName);
+      if (host && (host->h_addrtype == AF_INET) && (host->h_length == sizeof(address->sin_addr))) {
+        memcpy(&address->sin_addr, host->h_addr, sizeof(address->sin_addr));
+      } else {
+        ok = 0;
+        logMessage(LOG_WARNING, "Unknown host name: %s", hostName);
+      }
+    } else {
+      address->sin_addr.s_addr = INADDR_ANY;
+    }
+
+    if (portNumber) {
+      int port;
+
+      if (isInteger(&port, portNumber)) {
+        if ((port > 0) && (port <= 0XFFFF)) {
+          address->sin_port = htons(port);
+        } else {
+          ok = 0;
+          logMessage(LOG_WARNING, "Invalid port number: %s", portNumber);
+        }
+      } else {
+        const struct servent *service = getservbyname(portNumber, "tcp");
+
+        if (service) {
+          address->sin_port = service->s_port;
+        } else {
+          ok = 0;
+          logMessage(LOG_WARNING, "Unknown service: %s", portNumber);
+        }
+      }
+    } else {
+      address->sin_port = htons(VR_DEFAULT_PORT);
+    }
+
+    free(hostName);
+  } else {
+    ok = 0;
+    logMallocError();
+  }
+
+  return ok;
+}
+
+static int
+getInetSocket (void) {
+  return socket(PF_INET, SOCK_STREAM, 0);
+}
+
+static int
+prepareInetQueue (int socket) {
+  if (setSocketReuseAddress(socket)) return 1;
+  return 0;
+}
+
+static int
+acceptInetConnection (const struct sockaddr_in *localAddress) {
+  struct sockaddr_in remoteAddress;
+  socklen_t remoteSize = sizeof(remoteAddress);
+
+  return acceptSocketConnection(getInetSocket, prepareInetQueue, NULL,
+                                (const struct sockaddr *)localAddress, sizeof(*localAddress),
+                                (struct sockaddr *)&remoteAddress, &remoteSize);
+}
+
+static int
+requestInetConnection (const struct sockaddr_in *remoteAddress) {
+  return requestConnection(getInetSocket,
+                           (const struct sockaddr *)remoteAddress, sizeof(*remoteAddress));
+}
+
+static char *
+makeString (const char *characters, int count) {
+  char *string = malloc(count+1);
+
+  if (string) {
+    memcpy(string, characters, count);
+    string[count] = 0;
+  } else {
+    logMallocError();
+  }
+
+  return string;
+}
+
+static char *
+copyString (const char *string) {
+  return makeString(string, strlen(string));
+}
+
+static int
+fillInputBuffer (void) {
+  if ((inputLength < INPUT_SIZE) && !inputEnd) {
+    int count = operations->read(fileDescriptor, &inputBuffer[inputLength], INPUT_SIZE-inputLength);
+    if (!count) {
+      inputEnd = 1;
+    } else if (count != -1) {
+      inputLength += count;
+    } else if (errno != EAGAIN) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+static char *
+readCommandLine (void) {
+  if (fillInputBuffer()) {
+    if (inputStart < inputLength) {
+      const char *newline = memchr(&inputBuffer[inputStart], '\n', inputLength-inputStart);
+
+      if (newline) {
+        char *string;
+        int stringLength = newline - inputBuffer;
+        inputCarriageReturn = 0;
+
+        if ((newline != inputBuffer) && (*(newline-1) == '\r')) {
+          inputCarriageReturn = 1;
+          stringLength -= 1;
+        }
+
+        string = makeString(inputBuffer, stringLength);
+        inputLength -= ++newline - inputBuffer;
+        memmove(inputBuffer, newline, inputLength);
+        inputStart = 0;
+        return string;
+      } else {
+        inputStart = inputLength;
+      }
+    } else if (inputEnd) {
+      char *string;
+
+      if (inputLength) {
+        string = makeString(inputBuffer, inputLength);
+        inputLength = 0;
+        inputStart = 0;
+      } else {
+        string = copyString("quit");
+      }
+
+      return string;
+    }
+  }
+
+  return NULL;
+}
+
+static const char *
+nextWord (void) {
+  return strtok(NULL, inputDelimiters);
+}
+
+static int
+compareWords (const char *word1, const char *word2) {
+  return strcasecmp(word1, word2);
+}
+
+static int
+testWord (const char *suppliedWord, const char *desiredWord) {
+  return compareWords(suppliedWord, desiredWord) == 0;
+}
+
+static int
+flushOutput (void) {
+  const char *buffer = outputBuffer;
+  size_t length = outputLength;
+
+  while (length) {
+#ifdef __MINGW32__
+    DWORD sent;
+    OVERLAPPED overl = {0, 0, {{0, 0}}, CreateEvent(NULL, TRUE, FALSE, NULL)};
+    if ((!WriteFile((HANDLE) fileDescriptor, buffer, length, &sent, &overl)
+      && GetLastError() != ERROR_IO_PENDING) ||
+      !GetOverlappedResult((HANDLE) fileDescriptor, &overl, &sent, TRUE)) {
+        LogSocketError("WriteFile");
+        CloseHandle(overl.hEvent);
+        memmove(outputBuffer, buffer, (outputLength = length));
+        return 0;
+      }
+    CloseHandle(overl.hEvent);
+#else /* __MINGW32__ */
+    int sent;
+    sent = send(fileDescriptor, buffer, length, 0);
+
+    if (sent == -1) {
+      if (errno == EINTR) continue;
+      LogSocketError("send");
+      memmove(outputBuffer, buffer, (outputLength = length));
+      return 0;
+    }
+#endif /* __MINGW32__ */
+
+    buffer += sent;
+    length -= sent;
+  }
+
+  outputLength = 0;
+  return 1;
+}
+
+static int
+writeBytes (const char *bytes, size_t length) {
+  while (length) {
+    size_t count = OUTPUT_SIZE - outputLength;
+    if (length < count) count = length;
+    memcpy(&outputBuffer[outputLength], bytes, count);
+    bytes += count;
+    length -= count;
+    if ((outputLength += count) == OUTPUT_SIZE)
+      if (!flushOutput())
+        return 0;
+  }
+
+  return 1;
+}
+
+static int
+writeByte (char byte) {
+  return writeBytes(&byte, 1);
+}
+
+static int
+writeString (const char *string) {
+  return writeBytes(string, strlen(string));
+}
+
+static int
+writeCharacter (wchar_t character) {
+  Utf8Buffer buffer;
+  size_t count = convertWcharToUtf8(character, buffer);
+  return writeBytes(buffer, count);
+}
+
+static int
+writeDots (const unsigned char *cells, int count) {
+  const unsigned char *cell = cells;
+
+  while (count-- > 0) {
+    char dots[9];
+    char *d = dots;
+
+    if (cell != cells) *d++ = '|';
+    if (*cell) {
+      if (*cell & BRL_DOT1) *d++ = '1';
+      if (*cell & BRL_DOT2) *d++ = '2';
+      if (*cell & BRL_DOT3) *d++ = '3';
+      if (*cell & BRL_DOT4) *d++ = '4';
+      if (*cell & BRL_DOT5) *d++ = '5';
+      if (*cell & BRL_DOT6) *d++ = '6';
+      if (*cell & BRL_DOT7) *d++ = '7';
+      if (*cell & BRL_DOT8) *d++ = '8';
+    } else {
+      *d++ = ' ';
+    }
+    ++cell;
+
+    if (!writeBytes(dots, d-dots)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+writeLine (void) {
+  if (inputCarriageReturn)
+    if (!writeByte('\r'))
+      return 0;
+
+  if (writeByte('\n'))
+    if (flushOutput())
+      return 1;
+
+  return 0;
+}
+
+static void
+sortCommands (int (*compareCommands) (const void *item1, const void *item2)) {
+  qsort(commandDescriptors, commandCount, commandSize, compareCommands);
+}
+
+static int
+compareCommandCodes (const void *item1, const void *item2) {
+  const CommandDescriptor *descriptor1 = item1;
+  const CommandDescriptor *descriptor2 = item2;
+
+  int code1 = descriptor1->entry->code;
+  int code2 = descriptor2->entry->code;
+
+  if (code1 < code2) return -1;
+  if (code1 > code2) return 1;
+  return 0;
+}
+
+static void
+sortCommandsByCode (void) {
+  sortCommands(compareCommandCodes);
+}
+
+static int
+compareCommandNames (const void *item1, const void *item2) {
+  const CommandDescriptor *descriptor1 = item1;
+  const CommandDescriptor *descriptor2 = item2;
+
+  return strcmp(descriptor1->entry->name, descriptor2->entry->name);
+}
+
+static void
+sortCommandsByName (void) {
+  sortCommands(compareCommandNames);
+}
+
+static int
+allocateCommandDescriptors (void) {
+  if (!commandDescriptors) {
+    commandCount = getCommandCount();
+    commandDescriptors = malloc(commandCount * commandSize);
+
+    if (!commandDescriptors) {
+      logMallocError();
+      return 0;
+    }
+
+    {
+      CommandDescriptor *descriptor = commandDescriptors;
+      const CommandEntry *entry = commandTable;
+      while (entry->name) {
+        descriptor->entry = entry++;
+        descriptor->count = 0;
+        ++descriptor;
+      }
+    }
+
+    sortCommandsByCode();
+    {
+      CommandDescriptor *descriptor = commandDescriptors + commandCount;
+      int previousBlock = -1;
+
+      while (descriptor-- != commandDescriptors) {
+        int code = descriptor->entry->code;
+        int currentBlock = code & BRL_MSK_BLK;
+
+        if (currentBlock != previousBlock) {
+          if (currentBlock) {
+            descriptor->count = (BRL_MSK_ARG + 1) - (code & BRL_MSK_ARG);
+          }
+          previousBlock = currentBlock;
+        }
+      }
+    }
+
+    sortCommandsByName();
+  }
+
+  return 1;
+}
+
+static void
+deallocateCommandDescriptors (void) {
+  if (commandDescriptors) {
+    free(commandDescriptors);
+    commandDescriptors = NULL;
+  }
+}
+
+static int
+compareCommandName (const void *key, const void *item) {
+  const char *name = key;
+  const CommandDescriptor *descriptor = item;
+  return compareWords(name, descriptor->entry->name);
+}
+
+static const CommandDescriptor *
+findCommand (const char *name) {
+  return bsearch(name, commandDescriptors, commandCount, commandSize, compareCommandName);
+}
+
+static int
+dimensionsChanged (BrailleDisplay *brl) {
+  int ok = 1;
+  const char *word;
+
+  int columns1;
+  int rows1;
+
+  int columns2 = 0;
+  int rows2 = 0;
+
+  if ((word = nextWord())) {
+    if (isInteger(&columns1, word) && (columns1 > 0)) {
+      rows1 = 1;
+
+      if ((word = nextWord())) {
+        if (isInteger(&rows1, word) && (rows1 > 0)) {
+          if ((word = nextWord())) {
+            if (isInteger(&columns2, word) && (columns2 > 0)) {
+              rows2 = 0;
+
+              if ((word = nextWord())) {
+                if (isInteger(&rows2, word) && (rows2 > 0)) {
+                } else {
+                  logMessage(LOG_WARNING, "invalid status row count: %s", word);
+                  ok = 0;
+                }
+              }
+            } else {
+              logMessage(LOG_WARNING, "invalid status column count: %s", word);
+              ok = 0;
+            }
+          }
+        } else {
+          logMessage(LOG_WARNING, "invalid text row count: %s", word);
+          ok = 0;
+        }
+      }
+    } else {
+      logMessage(LOG_WARNING, "invalid text column count: %s", word);
+      ok = 0;
+    }
+  } else {
+    logMessage(LOG_WARNING, "missing text column count");
+    ok = 0;
+  }
+
+  if (ok) {
+    int count1 = columns1 * rows1;
+    int count2 = columns2 * rows2;
+    unsigned char *braille;
+    wchar_t *text;
+    unsigned char *status;
+
+    if ((braille = calloc(count1, sizeof(*braille)))) {
+      if ((text = calloc(count1, sizeof(*text)))) {
+        if ((status = calloc(count2, sizeof(*status)))) {
+          brailleColumns = columns1;
+          brailleRows = rows1;
+          brailleCount = count1;
+
+          statusColumns = columns2;
+          statusRows = rows2;
+          statusCount = count2;
+
+          if (brailleCells) free(brailleCells);
+          brailleCells = braille;
+          memset(brailleCells, 0, count1);
+
+          if (textCharacters) free(textCharacters);
+          textCharacters = text;
+          wmemset(textCharacters, WC_C(' '), count1);
+
+          if (statusCells) free(statusCells);
+          statusCells = status;
+          memset(statusCells, 0, count2);
+          memset(genericCells, 0, GSC_COUNT);
+
+          brl->textColumns = brailleColumns;
+          brl->textRows = brailleRows;
+          brl->statusColumns = statusColumns;
+          brl->statusRows = statusRows;
+          return 1;
+        }
+
+        free(text);
+      }
+
+      free(braille);
+    }
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (!allocateCommandDescriptors()) return 0;
+
+  inputLength = 0;
+  inputStart = 0;
+  inputEnd = 0;
+  outputLength = 0;
+
+  if (hasQualifier(&device, "client")) {
+    static const ModeEntry clientModeEntry = {
+#ifdef AF_LOCAL
+      requestLocalConnection,
+#endif /* AF_LOCAL */
+
+#ifdef __MINGW32__
+      requestNamedPipeConnection,
+#endif /* __MINGW32__ */
+
+      requestInetConnection
+    };
+    mode = &clientModeEntry;
+  } else if (hasQualifier(&device, "server")) {
+    static const ModeEntry serverModeEntry = {
+#ifdef AF_LOCAL
+      acceptLocalConnection,
+#endif /* AF_LOCAL */
+
+#ifdef __MINGW32__
+      acceptNamedPipeConnection,
+#endif /* __MINGW32__ */
+
+      acceptInetConnection
+    };
+    mode = &serverModeEntry;
+  } else {
+    unsupportedDeviceIdentifier(device);
+    goto failed;
+  }
+  if (!*device) device = VR_DEFAULT_SOCKET;
+
+#ifdef AF_LOCAL
+  if (device[0] == '/') {
+    struct sockaddr_un address;
+    if (setLocalAddress(device, &address)) {
+      fileDescriptor = mode->getLocalConnection(&address);
+    }
+  } else
+#endif /* AF_LOCAL */
+
+#ifdef __MINGW32__
+  if (device[0] == '\\') {
+    fileDescriptor = mode->getNamedPipeConnection(device);
+  } else {
+    static WSADATA wsadata;
+    if (WSAStartup(MAKEWORD(1, 1), &wsadata)) {
+      logWindowsSystemError("socket library start");
+      goto failed;
+    }
+  }
+#endif /* __MINGW32__ */
+
+  {
+    struct sockaddr_in address;
+    if (setInetAddress(device, &address)) {
+      fileDescriptor = mode->getInetConnection(&address);
+    }
+  }
+
+  if (fileDescriptor != -1) {
+    char *line = NULL;
+
+    while (1) {
+      if (line) free(line);
+      if ((line = readCommandLine())) {
+        const char *word;
+        logMessage(LOG_DEBUG, "command received: %s", line);
+
+        if ((word = strtok(line, inputDelimiters))) {
+          if (testWord(word, "cells")) {
+            if (dimensionsChanged(brl)) {
+              free(line);
+              return 1;
+            }
+          } else if (testWord(word, "quit")) {
+            break;
+          } else {
+            logMessage(LOG_WARNING, "unexpected command: %s", word);
+          }
+        }
+      } else {
+        asyncWait(1000);
+      }
+    }
+    if (line) free(line);
+
+    close(fileDescriptor);
+    fileDescriptor = -1;
+  }
+
+failed:
+  deallocateCommandDescriptors();
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  if (statusCells) {
+    free(statusCells);
+    statusCells = NULL;
+  }
+
+  if (textCharacters) {
+    free(textCharacters);
+    textCharacters = NULL;
+  }
+
+  if (brailleCells) {
+    free(brailleCells);
+    brailleCells = NULL;
+  }
+
+  if (fileDescriptor != -1) {
+    close(fileDescriptor);
+    fileDescriptor = -1;
+  }
+
+  deallocateCommandDescriptors();
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  if (text) {
+    if (wmemcmp(text, textCharacters, brailleCount) != 0) {
+      const wchar_t *address = text;
+      int count = brailleCount;
+
+      writeString("Visual \"");
+
+      while (count-- > 0) {
+        wchar_t character = *address++;
+
+        switch (character) {
+          case WC_C('"'):
+          case WC_C('\\'):
+            writeCharacter(WC_C('\\'));
+            /* fall through */
+          default:
+            writeCharacter(character);
+            break;
+        }
+      }
+
+      writeString("\"");
+      writeLine();
+
+      wmemcpy(textCharacters, text, brailleCount);
+    }
+  }
+
+  if (cellsHaveChanged(brailleCells, brl->buffer, brailleCount, NULL, NULL, NULL)) {
+    writeString("Braille \"");
+    writeDots(brl->buffer, brailleCount);
+    writeString("\"");
+    writeLine();
+  }
+
+  return 1;
+}
+
+static int
+brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) {
+  int generic = status[GSC_FIRST] == GSC_MARKER;
+  unsigned char *cells;
+  int count;
+
+  if (generic) {
+    cells = genericCells;
+    count = GSC_COUNT;
+  } else {
+    cells = statusCells;
+    count = statusCount;
+  }
+
+  if (cellsHaveChanged(cells, status, count, NULL, NULL, NULL)) {
+    if (generic) {
+      int all = cells[GSC_FIRST] != GSC_MARKER;
+      int i;
+
+      for (i=1; i<count; ++i) {
+        unsigned char value = status[i];
+        if (all || (value != cells[i])) {
+          static const char *const names[] = {
+            [GSC_FIRST] = NULL,
+            [gscBrailleWindowColumn] = "BRLCOL",
+            [gscBrailleWindowRow] = "BRLROW",
+            [gscScreenCursorColumn] = "CSRCOL",
+            [gscScreenCursorRow] = "CSRROW",
+            [gscScreenNumber] = "SCRNUM",
+            [gscFrozenScreen] = "FREEZE",
+            [gscDisplayMode] = "DISPMD",
+            [gscSixDotComputerBraille] = "SIXDOTS",
+            [gscContractedBraille] = "CONTRACTED",
+            [gscSlidingBrailleWindow] = "SLIDEWIN",
+            [gscSkipIdenticalLines] = "SKPIDLNS",
+            [gscSkipBlankBrailleWindows] = "SKPBLNKWINS",
+            [gscShowScreenCursor] = "CSRVIS",
+            [gscHideScreenCursor] = "CSRHIDE",
+            [gscTrackScreenCursor] = "CSRTRK",
+            [gscScreenCursorStyle] = "CSRSIZE",
+            [gscBlinkingScreenCursor] = "CSRBLINK",
+            [gscShowAttributes] = "ATTRVIS",
+            [gscBlinkingAttributes] = "ATTRBLINK",
+            [gscBlinkingCapitals] = "CAPBLINK",
+            [gscAlertTunes] = "TUNES",
+            [gscAutorepeat] = "AUTOREPEAT",
+            [gscAutospeak] = "AUTOSPEAK",
+            [gscBrailleTypingMode] = "BRLUCDOTS"
+          };
+          const int nameCount = ARRAY_COUNT(names);
+
+          if (i < nameCount) {
+            const char *name = names[i];
+            if (name) {
+              char buffer[0X40];
+              snprintf(buffer, sizeof(buffer), "%s %d", name, value);
+              writeString(buffer);
+              writeLine();
+            }
+          }
+        }
+      }
+    } else {
+      writeString("Status \"");
+      writeDots(cells, count);
+      writeString("\"");
+      writeLine();
+    }
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  int command = EOF;
+  char *line = readCommandLine();
+
+  if (line) {
+    const char *word;
+    logMessage(LOG_DEBUG, "Command received: %s", line);
+
+    if ((word = strtok(line, inputDelimiters))) {
+      if (testWord(word, "cells")) {
+        if (dimensionsChanged(brl)) brl->resizeRequired = 1;
+      } else if (testWord(word, "quit")) {
+        command = BRL_CMD_RESTARTBRL;
+      } else {
+        const CommandDescriptor *descriptor = findCommand(word);
+        if (descriptor) {
+          int needsNumber = descriptor->count > 0;
+          int numberSpecified = 0;
+          int switchSpecified = 0;
+          int block;
+
+          command = descriptor->entry->code;
+          block = command & BRL_MSK_BLK;
+
+          while ((word = nextWord())) {
+            if (block == 0) {
+              if (!switchSpecified) {
+                if (testWord(word, "on")) {
+                  switchSpecified = 1;
+                  command |= BRL_FLG_TOGGLE_ON;
+                  continue;
+                }
+
+                if (testWord(word, "off")) {
+                  switchSpecified = 1;
+                  command |= BRL_FLG_TOGGLE_OFF;
+                  continue;
+                }
+              }
+            }
+
+            if (needsNumber && !numberSpecified) {
+              int number;
+              if (isInteger(&number, word)) {
+                if ((number > 0) && (number <= descriptor->count)) {
+                  numberSpecified = 1;
+                  command += number;
+                  continue;
+                } else {
+                  logMessage(LOG_WARNING, "Number out of range.");
+                }
+              }
+            }
+
+            logMessage(LOG_WARNING, "unknown option: %s", word);
+          }
+
+          if (needsNumber && !numberSpecified) {
+            logMessage(LOG_WARNING, "Number not specified.");
+            command = EOF;
+          }
+        } else {
+          logMessage(LOG_WARNING, "unknown command: %s", word);
+        }
+      }
+    }
+
+    free(line);
+  }
+
+  return command;
+}
diff --git a/Drivers/Braille/Virtual/braille.h b/Drivers/Braille/Virtual/braille.h
new file mode 100644
index 0000000..1d97773
--- /dev/null
+++ b/Drivers/Braille/Virtual/braille.h
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Virtual/braille.h - Configurable definitions for the Virtual driver
+ *
+ * Edit as necessary for your system.
+ */
+
+#define VR_DEFAULT_SOCKET "127.0.0.1"
+#define VR_DEFAULT_PORT 35752
diff --git a/Drivers/Braille/VisioBraille/Makefile.in b/Drivers/Braille/VisioBraille/Makefile.in
new file mode 100644
index 0000000..fdb0603
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/Makefile.in
@@ -0,0 +1,57 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = vs
+DRIVER_NAME = VisioBraille
+DRIVER_USAGE = 20/40
+DRIVER_VERSION = 0.2 (2002)
+DRIVER_DEVELOPERS = Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) -c $(SRC_DIR)/braille.c
+
+###############################################################################
+
+VSTP_OBJECTS = vstp_main.$O vstp_transfer.$O
+
+vstp$X: $(VSTP_OBJECTS) brlapi
+	$(CC) $(LDFLAGS) -o $@ $(VSTP_OBJECTS) $(API_LIBS) $(LDLIBS)
+
+vstp_main.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/vstp_main.c
+
+vstp_transfer.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/vstp_transfer.c
+
+api: vstp$X
+braille-all:: $(BUILD_API)
+
+clean::
+	-rm -f vstp$X
+
+install-api:: install-vstp
+
+install-vstp: vstp$X install-program-directory install-man1-directory
+	$(INSTALL_PROGRAM) vstp$X $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/vstp.1 $(INSTALL_MAN1_DIRECTORY)
+
+uninstall::
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/vstp$X
+	-rm -f $(INSTALL_MAN1_DIRECTORY)/vstp.1
+
diff --git a/Drivers/Braille/VisioBraille/README b/Drivers/Braille/VisioBraille/README
new file mode 100644
index 0000000..fe8aec4
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/README
@@ -0,0 +1,101 @@
+Ceci est le fichier README relatif à la version 0.2 du pilote VisioBraille
+pour BRLTTY.
+
+Ce pilote a été testé avec succès avec les terminaux équipés de PROMs 0.0g et
+5.0q-fr. Il est vraisemblable que toutes les PROMs de version >=4
+soient supportées par le présent pilote.
+
+Auteur et mainteneur actuel:
+Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+
+Bien que ce pilote soit utilisé de manière intensive, il n'est pas impossible
+que certaines erreurs subsistent.
+Si vous en trouvez, ou si vous avez des commentaires/suggestions quant à
+l'évolution de ce pilote, n'hésitez surtout pas à me contacter. 
+
+Notes pour l'installation et Paramètres
+=======================================
+
+Une table braille compatible avec celle fournie en standard dans le TVB
+(basée sur la table SAGEM) est disponible, dans le fichier text.visiob.tbl du
+répertoire ../../BrailleTables
+
+Paramètres acceptés par ce pilote :
+
+- displaysize : permet de spécifier la taille de la plage tactile.
+
+- promversion: permet de spécifier la version de PROM utilisée.
+Ce paramètre n'est nécessaire que pour des PROMs de version inférieure ou
+égale à 3. Le support de ces PROMs n'étant disponible que pour Linux, et
+moyennant une modification du noyau, merci de contacter directement l'auteur
+si vous souhaitez utiliser une telle PROM.
+
+- baud: permet de spécifier la vitesse de la communication série.
+Ce parmètre n'est pas nécessaire pour les terminaux VisioBraille et
+n'est fourni que pour permettre l'utilisation d'émulateurs de terminaux qui
+ne pourraient pas communiquer à une vitesse suffisante. Par conséquent,
+n'utilisez pas ce paramètre, à moins de savoir ce que vous faites.
+
+Pour des informations sur la manière de passer des paramètres au pilote, ou
+pour toute autre précision concernant l'installation et l'utilisation de
+brltty, merci de consulter la documentation du répertoire ../../Documents
+
+Notes pour l'utilisation. 
+=========================
+
+  Voici la description des fonctions attachées aux touches des claviers
+auxiliaires ainsi qu'aux commandes ploc-ploc.
+
+~~a Active l'aide de BRLTTY
+~~b Active/désactive les bips
+~~c Menu de configuration BRLTTY (~~c pour le quitter)
+~~d page down
+~~e end
+~~h home 
+~~i Active/désactive le mode info (Cf le manuel BRLTTY pour en savoir plus)
+~~r Réinitialise les paramètres de BRLTTY
+~~s Sauvegarde la configuration des paramètres BRLTTY  
+~~u page up
+
+Clavier auxiliaire: 
+Sous Linux, les touches A1,A2,A3,A6,A7,A8 permettent d'activer les
+6 premières consoles virtuelles (correspond à alt-f1 à alt-f6).
+L'action de ces touches n'est pas définie sur les autres systèmes
+d'exploitation.
+B6 coin supérieur gauche de l'écran
+D6 coin inférieur gauche de l'écran
+A4 fenêtre non vide précédente
+A5 fenêtre non vide suivante
+B7 ligne précédente
+D7 ligne suivante
+C6 fenêtre précédente
+C8 fenêtre suivante
+C7 fenêtre contenant le curseur
+C1 flèche gauche
+C3 flèche droite
+B2 flèche haut
+D2 flèche bas
+B3 curseur on/off
+D1 delete
+
+  En outre, les touches backspace, tab, ctrl, alt... sont siulées de la même
+manière que sous DOS et Windows.
+
+Autres liens
+============
+
+Un programme permettant le transfert de visiobases entre PC et TVB est
+disponible à l'adresse suivante :
+
+http://brl.thefreecat.org
+
+Ce qu'il reste à faire
+======================
+
+  Pour les prochaines versions du driver, il serait peut-être souhaitable
+d'inclure la possibilité de reprohrammer les touches, un peu comme cela se
+fait sous DOS avec la commande ~~f.
+
+
+-- 
+Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
diff --git a/Drivers/Braille/VisioBraille/braille.c b/Drivers/Braille/VisioBraille/braille.c
new file mode 100644
index 0000000..181c8b4
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/braille.c
@@ -0,0 +1,443 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+#include "scr.h"
+#include "message.h"
+
+typedef enum {
+  PARM_DISPSIZE=0,
+  PARM_PROMVER=1,
+  PARM_BAUD=2
+} DriverParameter;
+#define BRLPARMS "displaysize", "promversion", "baud"
+
+#define BRL_HAVE_PACKET_IO
+#include "brl_driver.h"
+#include "braille.h"
+#include "brldefs-vs.h"
+#include "io_serial.h"
+
+#define MAXPACKETSIZE 512
+
+static SerialDevice *serialDevice;
+#ifdef SendIdReq
+static struct TermInfo {
+  unsigned char code; 
+  unsigned char version[3];
+  unsigned char f1;
+  unsigned char size[2];
+  unsigned char dongle;
+  unsigned char clock;
+  unsigned char routing;
+  unsigned char flash;
+  unsigned char prog;
+  unsigned char lcd;
+  unsigned char f2[11];
+} terminfo;
+#endif /* SendIdReq */
+
+/* Function : brl_writePacket */ 
+/* Sends a packet of size bytes, stored at address p to the braille terminal */
+/* Returns 0 if everything is right, -1 if an error occured while sending */
+static ssize_t brl_writePacket(BrailleDisplay *brl, const void *packet, size_t size)
+{
+  const unsigned char *p = packet;
+  int lgtho = 1;
+  unsigned char obuf[MAXPACKETSIZE];
+  const unsigned char *x;
+  unsigned char *y = obuf;
+  unsigned char chksum=0;
+  int i,res;
+
+  *y++ = 02;
+  for (x=p; (x-p) < size; x++) {
+    chksum ^= *x;
+    if ((*x) <= 5) {
+      *y = 01;
+      y++; lgtho++; 
+      *y = ( *x ) | 0x40;
+    } else *y = *x;
+    y++; lgtho++; 
+  }
+  if (chksum<=5) {
+    *y = 1; y++; lgtho++;  
+    chksum |= 0x40;
+  }
+  *y = chksum; y++; lgtho++; 
+  *y = 3; y++; lgtho++; 
+  for (i=1; i<=5; i++) {
+    if (serialWriteData(serialDevice,obuf,lgtho) != lgtho) continue; /* write failed, retry */
+    serialAwaitOutput(serialDevice);
+    serialAwaitInput(serialDevice, 1000);
+    res = serialReadData(serialDevice,&chksum,1,0,0);
+    if ((res==1) && (chksum == 0x04)) return 0;
+  }
+  return (-1);
+}
+
+/* Function : brl_readPacket */
+/* Reads a packet of at most size bytes from the braille terminal */
+/* and puts it at the specified adress */
+/* Packets are read into a local buffer until completed and valid */
+/* and are then copied to the buffer pointed by p. In this case, */
+/* The size of the packet is returned */
+/* If a packet is too long, it is discarded and a message sent to the syslog */
+/* "+" packets are silently discarded, since they are only disturbing us */
+static ssize_t brl_readPacket(BrailleDisplay *brl, void *p, size_t size) 
+{
+  size_t offset = 0;
+  static unsigned char ack = 04;
+  static unsigned char nack = 05;
+  static int apacket = 0;
+  static unsigned char prefix, checksum;
+  unsigned char ch;
+  static unsigned char buf[MAXPACKETSIZE]; 
+  static unsigned char *q;
+  if ((p==NULL) || (size<2) || (size>MAXPACKETSIZE)) return 0; 
+  while (serialReadChunk(serialDevice,&ch,&offset,1,0,1000)) {
+    if (ch==0x02) {
+      apacket = 1;
+      prefix = 0xff; 
+      checksum = 0;
+      q = &buf[0];
+    } else if (apacket) {
+      if (ch==0x01) {
+        prefix &= ~(0x40); 
+      } else if (ch==0x03) {
+        if (checksum==0) {
+          serialWriteData(serialDevice,&ack,1); 
+          apacket = 0; q--;
+          if (buf[0]!='+') {
+            memcpy(p,buf,(q-buf));
+            return q-&buf[0]; 
+          }
+        } else {
+          serialWriteData(serialDevice,&nack,1);
+          apacket = 0;
+          return 0;
+        }
+      } else {
+        if ((q-&buf[0])>=size) {
+          logMessage(LOG_WARNING,"Packet too long: discarded");
+          apacket = 0;
+          return 0;
+        }
+        ch &= prefix; prefix |= 0x40;
+        checksum ^= ch;
+        (*q) = ch; q++;   
+      }
+    }
+    offset = 0;
+  } 
+  return 0;
+}
+
+/* Function : brl_reset */
+/* This routine is called by the brlnet server, when an application that */
+/* requested a raw-mode communication with the braille terminal dies before */
+/* restoring a normal communication mode */
+static int brl_reset(BrailleDisplay *brl)
+{
+  static unsigned char RescuePacket[] = {'#'}; 
+  brl_writePacket(brl,RescuePacket,sizeof(RescuePacket));
+  return 1;
+}
+
+/* Function : brl_construct */
+/* Opens and configures the serial port properly */
+/* if brl->textColumns <= 0 when brl_construct is called, then brl->textColumns is initialized */
+/* either with the BRAILLEDISPLAYSIZE constant, defined in braille.h */
+/* or with the size got through identification request if it succeeds */
+/* Else, brl->textColumns is left unmodified by brl_construct, so that */
+/* the braille display can be resized without reloading the driver */ 
+static int brl_construct(BrailleDisplay *brl, char **parameters, const char *device)
+{
+#ifdef SendIdReq
+  unsigned char ch = '?';
+  int i;
+#endif /* SendIdReq */
+  int ds = BRAILLEDISPLAYSIZE;
+  int promVersion = 4;
+  unsigned int ttyBaud = 57600;
+  if (*parameters[PARM_DISPSIZE]) {
+    int dsmin=20, dsmax=40;
+    if (!validateInteger(&ds, parameters[PARM_DISPSIZE], &dsmin, &dsmax))
+      logMessage(LOG_WARNING, "%s: %s", "invalid braille display size", parameters[PARM_DISPSIZE]);
+  }
+  if (*parameters[PARM_PROMVER]) {
+    int pvmin=3, pvmax=6;
+    if (!validateInteger(&promVersion, parameters[PARM_PROMVER], &pvmin, &pvmax))
+      logMessage(LOG_WARNING, "%s: %s", "invalid PROM version", parameters[PARM_PROMVER]);
+  }
+  if (*parameters[PARM_BAUD]) {
+    unsigned int baud;
+    if (serialValidateBaud(&baud, "TTY baud", parameters[PARM_BAUD], NULL)) {
+      ttyBaud = baud;
+    }
+  }
+
+  if (!isSerialDeviceIdentifier(&device)) {
+    unsupportedDeviceIdentifier(device);
+    return 0;
+  }
+  if (!(serialDevice = serialOpenDevice(device))) return 0;
+  serialSetParity(serialDevice, SERIAL_PARITY_ODD);
+  if (promVersion<4) serialSetFlowControl(serialDevice, SERIAL_FLOW_INPUT_CTS);
+  serialRestartDevice(serialDevice,ttyBaud); 
+#ifdef SendIdReq
+  {
+    brl_writePacket(brl,(unsigned char *) &ch,1); 
+    i=5; 
+    while (i>0) {
+      if (brl_readPacket(brl,(unsigned char *) &terminfo,sizeof(terminfo))!=0) {
+        if (terminfo.code=='?') {
+          terminfo.f2[10] = '\0';
+          break;
+        }
+      }
+      i--;
+    }
+    if (i==0) {
+      logMessage(LOG_WARNING,"Unable to identify terminal properly");  
+      if (!brl->textColumns) brl->textColumns = BRAILLEDISPLAYSIZE;  
+    } else {
+      logMessage(LOG_INFO,"Braille terminal description:");
+      logMessage(LOG_INFO,"   version=%c%c%c",terminfo.version[0],terminfo.version[1],terminfo.version[2]);
+      logMessage(LOG_INFO,"   f1=%c",terminfo.f1);
+      logMessage(LOG_INFO,"   size=%c%c",terminfo.size[0],terminfo.size[1]);
+      logMessage(LOG_INFO,"   dongle=%c",terminfo.dongle);
+      logMessage(LOG_INFO,"   clock=%c",terminfo.clock);
+      logMessage(LOG_INFO,"   routing=%c",terminfo.routing);
+      logMessage(LOG_INFO,"   flash=%c",terminfo.flash);
+      logMessage(LOG_INFO,"   prog=%c",terminfo.prog);
+      logMessage(LOG_INFO,"   lcd=%c",terminfo.lcd);
+      logMessage(LOG_INFO,"   f2=%s",terminfo.f2);  
+      if (brl->textColumns<=0)
+        brl->textColumns = (terminfo.size[0]-'0')*10 + (terminfo.size[1]-'0');
+    }
+  }
+#else /* SendIdReq */
+  brl->textColumns = ds;
+#endif /* SendIdReq */
+  brl->textRows=1; 
+
+  {
+    /* The following table defines how internal brltty format is converted to */
+    /* VisioBraille format. */
+    /* The table is declared static so that it is in data segment and not */
+    /* in the stack */ 
+    static const TranslationTable outputTable = {
+#include "brl-out.h"
+    };
+    setOutputTable(outputTable);
+  }
+
+  return 1;
+}
+
+/* Function : brl_destruct */
+/* Closes the braille device and deallocates dynamic structures */
+static void brl_destruct(BrailleDisplay *brl)
+{
+  if (serialDevice) {
+    serialCloseDevice(serialDevice);
+  }
+}
+
+/* function : brl_writeWindow */
+/* Displays a text on the braille window, only if it's different from */
+/* the one alreadz displayed */
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  static unsigned char brailleDisplay[81]= { 0x3e }; /* should be large enough for everyone */
+  static unsigned char prevData[80];
+
+  if (cellsHaveChanged(prevData, brl->buffer, brl->textColumns, NULL, NULL, NULL)) {
+    translateOutputCells(brailleDisplay+1, brl->buffer, brl->textColumns);
+    if (brl_writePacket(brl, (unsigned char *)&brailleDisplay, brl->textColumns+1) == -1) return 0;
+  }
+
+  return 1;
+}
+
+/* Function : keyToCommand */
+/* Converts a key code to a brltty command according to the context */
+static int keyToCommand(BrailleDisplay *brl, KeyTableCommandContext context, int code)
+{
+  static int ctrlpressed = 0; 
+  static int altpressed = 0;
+  static int cut = 0;
+  static int descchar = 0;
+  unsigned char ch;
+  int type;
+  ch = code & 0xff;
+  type = code & (~ 0xff);
+  if (code==0) return 0;
+  if (code==EOF) return EOF;
+  if (type==BRL_VSMSK_CHAR) {
+    int command = ch | BRL_CMD_BLK(PASSCHAR) | altpressed | ctrlpressed;
+    altpressed = ctrlpressed = 0;
+    return command;
+  }
+  if (type==BRL_VSMSK_ROUTING) {
+    ctrlpressed = altpressed = 0;
+    switch (cut) {
+      case 0:
+        if (descchar) { descchar = 0; return ((int) ch) | BRL_CMD_BLK(DESCCHAR); }
+        else return ((int) ch) | BRL_CMD_BLK(ROUTE);
+      case 1: cut++; return ((int) ch) | BRL_CMD_BLK(CLIP_NEW);
+      case 2: cut = 0; return ((int) ch) | BRL_CMD_BLK(COPY_LINE);
+    }
+    return EOF; /* Should not be reached */
+  } else if (type==BRL_VSMSK_FUNCTIONKEY) {
+    ctrlpressed = altpressed = 0;
+    switch (code) {
+      case BRL_VSKEY_A1: return BRL_CMD_BLK(SWITCHVT);
+      case BRL_VSKEY_A2: return BRL_CMD_BLK(SWITCHVT)+1;
+      case BRL_VSKEY_A3: return BRL_CMD_BLK(SWITCHVT)+2;
+      case BRL_VSKEY_A6: return BRL_CMD_BLK(SWITCHVT)+3;
+      case BRL_VSKEY_A7: return BRL_CMD_BLK(SWITCHVT)+4;
+      case BRL_VSKEY_A8: return BRL_CMD_BLK(SWITCHVT)+5;
+      case BRL_VSKEY_B5: cut = 1; return EOF;
+      case BRL_VSKEY_B6: return BRL_CMD_TOP_LEFT; 
+      case BRL_VSKEY_D6: return BRL_CMD_BOT_LEFT;
+      case BRL_VSKEY_A4: return BRL_CMD_FWINLTSKIP;
+      case BRL_VSKEY_B8: return BRL_CMD_FWINLTSKIP;
+      case BRL_VSKEY_A5: return BRL_CMD_FWINRTSKIP;
+      case BRL_VSKEY_D8: return BRL_CMD_FWINRTSKIP;
+      case BRL_VSKEY_B7: return BRL_CMD_LNUP;
+      case BRL_VSKEY_D7: return BRL_CMD_LNDN;
+      case BRL_VSKEY_C8: return BRL_CMD_FWINRT;
+      case BRL_VSKEY_C6: return BRL_CMD_FWINLT;
+      case BRL_VSKEY_C7: return BRL_CMD_HOME;
+      case BRL_VSKEY_B2: return BRL_CMD_KEY(CURSOR_UP);
+      case BRL_VSKEY_D2: return BRL_CMD_KEY(CURSOR_DOWN);
+      case BRL_VSKEY_C3: return BRL_CMD_KEY(CURSOR_RIGHT);
+      case BRL_VSKEY_C1: return BRL_CMD_KEY(CURSOR_LEFT);
+      case BRL_VSKEY_B3: return BRL_CMD_CSRVIS;
+      case BRL_VSKEY_D1: return BRL_CMD_KEY(DELETE);  
+      case BRL_VSKEY_D3: return BRL_CMD_KEY(INSERT);
+      case BRL_VSKEY_C5: return BRL_CMD_PASTE;
+      case BRL_VSKEY_D5: descchar = 1; return EOF;
+      default: return EOF;
+    }
+  } else if (type==BRL_VSMSK_OTHER) {
+    /* ctrlpressed = 0; */
+    if ((ch>=0xe1) && (ch<=0xea)) {
+      int flags = altpressed;
+      ch-=0xe1;
+      altpressed = 0;
+      return flags | BRL_CMD_BLK(PASSKEY) | ( BRL_KEY_FUNCTION + ch); 
+    }
+    /* altpressed = 0; */
+    switch (code) {
+      case BRL_VSKEY_PLOC_LT: return BRL_CMD_SIXDOTS;
+      case BRL_VSKEY_BACKSPACE: return BRL_CMD_KEY(BACKSPACE);
+      case BRL_VSKEY_TAB: return BRL_CMD_KEY(TAB);
+      case BRL_VSKEY_RETURN: return BRL_CMD_KEY(ENTER);
+      case BRL_VSKEY_PLOC_PLOC_A: return BRL_CMD_HELP;
+      case BRL_VSKEY_PLOC_PLOC_B: return BRL_CMD_TUNES; 
+      case BRL_VSKEY_PLOC_PLOC_C: return BRL_CMD_PREFMENU;
+      case BRL_VSKEY_PLOC_PLOC_D: return BRL_CMD_KEY(PAGE_DOWN);
+      case BRL_VSKEY_PLOC_PLOC_E: return BRL_CMD_KEY(END);
+      case BRL_VSKEY_PLOC_PLOC_F: return BRL_CMD_FREEZE;
+      case BRL_VSKEY_PLOC_PLOC_H: return BRL_CMD_KEY(HOME);
+      case BRL_VSKEY_PLOC_PLOC_I: return BRL_CMD_INFO;
+      case BRL_VSKEY_PLOC_PLOC_L: return BRL_CMD_LEARN;
+      case BRL_VSKEY_PLOC_PLOC_R: return BRL_CMD_PREFLOAD;
+      case BRL_VSKEY_PLOC_PLOC_S: return BRL_CMD_PREFSAVE;
+      case BRL_VSKEY_PLOC_PLOC_T: return BRL_CMD_CSRTRK;
+      case BRL_VSKEY_PLOC_PLOC_U: return BRL_CMD_KEY(PAGE_UP);
+      case BRL_VSKEY_CONTROL: ctrlpressed = BRL_FLG_INPUT_CONTROL; return BRL_CMD_NOOP;
+      case BRL_VSKEY_ALT: altpressed = BRL_FLG_INPUT_META; return BRL_CMD_NOOP;   
+      case BRL_VSKEY_ESCAPE: return BRL_CMD_KEY(ESCAPE);
+      default: return EOF;
+    }
+  }
+  return EOF; 
+}
+
+/* Function : readKey */
+/* Reads a key. The result is context-independent */
+/* The intermediate value contains a keycode, masked with the key type */
+/* the keytype is one of BRL_NORMALCHAR, BRL_FUNCTIONKEY or BRL_ROUTING */
+/* for a normal character, the keycode is the latin1-code of the character */
+/* for function-keys, codes 0 to 31 are reserved for A1 to D8 keys */
+/* codes after 32 are for ~~* combinations, the order has to be determined */
+/* for BRL_ROUTING, the code is the ofset to route, starting from 0 */
+static int readKey(BrailleDisplay *brl)
+{
+  unsigned char ch, packet[MAXPACKETSIZE];
+  static int routing = 0;
+  ssize_t packetSize;
+  packetSize = brl_readPacket(brl,packet,sizeof(packet));
+  if (packetSize==0) return EOF;
+  if ((packet[0]!=0x3c) && (packet[0]!=0x3d) && (packet[0]!=0x23)) {
+    logUnexpectedPacket(packet, packetSize);
+    return EOF;
+  }
+  ch = packet[1];
+  if (routing) {
+    routing=0;
+    if (ch>=0xc0)  return (packet[1]-0xc0) | BRL_VSMSK_ROUTING;
+    return EOF;
+  }
+  if ((ch>=0xc0) && (ch<=0xdf)) return (ch-0xc0) | BRL_VSMSK_FUNCTIONKEY;
+  if (ch==0x91) {
+    routing = 1;
+    return BRL_CMD_NOOP;
+  } 
+  if ((ch>=0x20) && (ch<=0x9e)) {
+    switch (ch) {
+      case 0x80: ch = 0xc7; break;
+      case 0x81: ch = 0xfc; break;
+      case 0x82: ch = 0xe9; break;
+      case 0x83: ch = 0xe2; break;
+      case 0x84: ch = 0xe4; break;
+      case 0x85: ch = 0xe0; break;
+      case 0x87: ch = 0xe7; break;
+      case 0x88: ch = 0xea; break;
+      case 0x89: ch = 0xeb; break;
+      case 0x8a: ch = 0xe8; break; 
+      case 0x8b: ch = 0xef; break;
+      case 0x8c: ch = 0xee; break;
+      case 0x8f: ch = 0xc0; break;
+      case 0x93: ch = 0xf4; break;
+      case 0x94: ch = 0xf6; break;
+      case 0x96: ch = 0xfb; break; 
+      case 0x97: ch = 0xf9; break;
+      case 0x9e: ch = 0x60; break;
+    }
+    return ch | BRL_VSMSK_CHAR;
+  }
+  return ch | BRL_VSMSK_OTHER;
+}
+
+/* Function : brl_readCommand */
+/* Reads a command from the braille keyboard */
+static int brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context)
+{
+  return keyToCommand(brl,context,readKey(brl));
+}
diff --git a/Drivers/Braille/VisioBraille/braille.h b/Drivers/Braille/VisioBraille/braille.h
new file mode 100644
index 0000000..b7490df
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/braille.h
@@ -0,0 +1,22 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define BRAILLEDISPLAYSIZE 40
+/* #define SENDIDREQ */
+#define BRLRAWCAPABLE /* We support raw mode communication */
+#define BRLKEY2CMD /* We support key to brltty commands conversion */
diff --git a/Drivers/Braille/VisioBraille/brl-out.h b/Drivers/Braille/VisioBraille/brl-out.h
new file mode 100644
index 0000000..7dc9c3c
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/brl-out.h
@@ -0,0 +1,274 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+[0] = 0X20,
+[BRL_DOT1] = 0X41,
+[BRL_DOT1 | BRL_DOT2] = 0X42,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3] = 0X4C,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4] = 0X50,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X51,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X2F,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XEF,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X6F,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XAF,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X11,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X91,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XD1,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X40,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X00,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X80,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XC0,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X10,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X90,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0XD0,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5] = 0X52,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X5b,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X1B,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X9B,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XDB,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0X12,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X92,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0XD2,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6] = 0X56,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0X16,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X96,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XD6,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7] = 0X0C,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X8C,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT8] = 0XCC,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4] = 0X46,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5] = 0X47,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X37,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XF7,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X77,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XB7,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X07,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X87,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XC7,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6] = 0X36,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XF6,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X76,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XB6,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT7] = 0X06,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X86,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT4 | BRL_DOT8] = 0XC6,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5] = 0X48,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6] = 0X38,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XF8,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X78,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XB8,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT7] = 0X08,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X88,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT5 | BRL_DOT8] = 0XC8,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6] = 0X32,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT7] = 0XF2,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X72,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT6 | BRL_DOT8] = 0XB2,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT7] = 0X02,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT7 | BRL_DOT8] = 0X82,
+[BRL_DOT1 | BRL_DOT2 | BRL_DOT8] = 0XC2,
+[BRL_DOT1 | BRL_DOT3] = 0X4B,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4] = 0X4D,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X4E,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X59,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X19,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X99,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XD9,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X0E,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X8E,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XCE,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X58,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X18,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X98,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XD8,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X0D,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X8D,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0XCD,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5] = 0X4F,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X5A,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X1A,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X9A,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XDA,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0X0F,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X8F,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0XCF,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6] = 0X55,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0X15,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X95,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XD5,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT7] = 0X0B,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X8B,
+[BRL_DOT1 | BRL_DOT3 | BRL_DOT8] = 0XCB,
+[BRL_DOT1 | BRL_DOT4] = 0X43,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5] = 0X44,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X34,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XF4,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X74,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XB4,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X04,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X84,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XC4,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6] = 0X33,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XF3,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X73,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XB3,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT7] = 0X03,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X83,
+[BRL_DOT1 | BRL_DOT4 | BRL_DOT8] = 0XC3,
+[BRL_DOT1 | BRL_DOT5] = 0X45,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6] = 0X35,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XF5,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X75,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XB5,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT7] = 0X05,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X85,
+[BRL_DOT1 | BRL_DOT5 | BRL_DOT8] = 0XC5,
+[BRL_DOT1 | BRL_DOT6] = 0X31,
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT7] = 0XF1,
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X71,
+[BRL_DOT1 | BRL_DOT6 | BRL_DOT8] = 0XB1,
+[BRL_DOT1 | BRL_DOT7] = 0X01,
+[BRL_DOT1 | BRL_DOT7 | BRL_DOT8] = 0X81,
+[BRL_DOT1 | BRL_DOT8] = 0XC1,
+[BRL_DOT2] = 0X2C,
+[BRL_DOT2 | BRL_DOT3] = 0X3B,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4] = 0X53,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X54,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X5D,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X1D,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X9D,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XDD,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X14,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X94,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XD4,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X5C,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X1C,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X9C,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XDC,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0X13,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X93,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0XD3,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5] = 0X21,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X3D,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0Xfd,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X7D,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XBD,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0XE1,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X61,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0XA1,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6] = 0X3C,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0XFC,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X7C,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XBC,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT7] = 0XFB,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X7B,
+[BRL_DOT2 | BRL_DOT3 | BRL_DOT8] = 0XBB,
+[BRL_DOT2 | BRL_DOT4] = 0X49,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5] = 0X4A,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X57,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0X17,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X97,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XD7,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0X0A,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X8A,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XCA,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6] = 0X39,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XF9,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X79,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XB9,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT7] = 0X09,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X89,
+[BRL_DOT2 | BRL_DOT4 | BRL_DOT8] = 0XC9,
+[BRL_DOT2 | BRL_DOT5] = 0X3A,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6] = 0X2E,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XEE,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X6E,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XAE,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT7] = 0XFA,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X7A,
+[BRL_DOT2 | BRL_DOT5 | BRL_DOT8] = 0XBA,
+[BRL_DOT2 | BRL_DOT6] = 0X3F,
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT7] = 0XFF,
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X7F,
+[BRL_DOT2 | BRL_DOT6 | BRL_DOT8] = 0XBF,
+[BRL_DOT2 | BRL_DOT7] = 0XEC,
+[BRL_DOT2 | BRL_DOT7 | BRL_DOT8] = 0X6C,
+[BRL_DOT2 | BRL_DOT8] = 0XAC,
+[BRL_DOT3] = 0X27,
+[BRL_DOT3 | BRL_DOT4] = 0X2A,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5] = 0X26,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X30,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XF0,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X70,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XB0,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0XE6,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X66,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XA6,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6] = 0X5E,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0X1E,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X9E,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XDE,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT7] = 0XEA,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X6A,
+[BRL_DOT3 | BRL_DOT4 | BRL_DOT8] = 0XAA,
+[BRL_DOT3 | BRL_DOT5] = 0X29,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6] = 0X3E,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XFE,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X7E,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XBE,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT7] = 0XE9,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X69,
+[BRL_DOT3 | BRL_DOT5 | BRL_DOT8] = 0XA9,
+[BRL_DOT3 | BRL_DOT6] = 0X2D,
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT7] = 0XED,
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X6D,
+[BRL_DOT3 | BRL_DOT6 | BRL_DOT8] = 0XAD,
+[BRL_DOT3 | BRL_DOT7] = 0XE7,
+[BRL_DOT3 | BRL_DOT7 | BRL_DOT8] = 0X67,
+[BRL_DOT3 | BRL_DOT8] = 0XA7,
+[BRL_DOT4] = 0X22,
+[BRL_DOT4 | BRL_DOT5] = 0X25,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6] = 0X24,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XE4,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X64,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XA4,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT7] = 0XE5,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X65,
+[BRL_DOT4 | BRL_DOT5 | BRL_DOT8] = 0XA5,
+[BRL_DOT4 | BRL_DOT6] = 0X23,
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT7] = 0XE3,
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X63,
+[BRL_DOT4 | BRL_DOT6 | BRL_DOT8] = 0XA3,
+[BRL_DOT4 | BRL_DOT7] = 0XE2,
+[BRL_DOT4 | BRL_DOT7 | BRL_DOT8] = 0X62,
+[BRL_DOT4 | BRL_DOT8] = 0XA2,
+[BRL_DOT5] = 0X5F,
+[BRL_DOT5 | BRL_DOT6] = 0X2B,
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT7] = 0XEB,
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X6B,
+[BRL_DOT5 | BRL_DOT6 | BRL_DOT8] = 0XAB,
+[BRL_DOT5 | BRL_DOT7] = 0X1F,
+[BRL_DOT5 | BRL_DOT7 | BRL_DOT8] = 0X9F,
+[BRL_DOT5 | BRL_DOT8] = 0XDF,
+[BRL_DOT6] = 0X28,
+[BRL_DOT6 | BRL_DOT7] = 0XE8,
+[BRL_DOT6 | BRL_DOT7 | BRL_DOT8] = 0X68,
+[BRL_DOT6 | BRL_DOT8] = 0XA8,
+[BRL_DOT7] = 0XE0,
+[BRL_DOT7 | BRL_DOT8] = 0X60,
+[BRL_DOT8] = 0XA0
diff --git a/Drivers/Braille/VisioBraille/brldefs-vs.h b/Drivers/Braille/VisioBraille/brldefs-vs.h
new file mode 100644
index 0000000..0d550cd
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/brldefs-vs.h
@@ -0,0 +1,100 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* brldefs-vs.h : Useful definitions to handle keys entered at */
+/* VisioBraille's keyboard */ 
+
+#ifndef BRLTTY_INCLUDED_VS_BRLDEFS
+#define BRLTTY_INCLUDED_VS_BRLDEFS
+
+#define BRL_VSMSK_CHAR        0x100
+#define BRL_VSMSK_ROUTING     0x200
+#define BRL_VSMSK_FUNCTIONKEY 0x400
+#define BRL_VSMSK_OTHER       0x800
+
+/* Symbolic definitions for VisioBraille's function keys */
+#define BRL_VSKEY_A1 0x400
+#define BRL_VSKEY_A2 0x401
+#define BRL_VSKEY_A3 0x402
+#define BRL_VSKEY_A4 0x403
+#define BRL_VSKEY_A5 0x404
+#define BRL_VSKEY_A6 0x405
+#define BRL_VSKEY_A7 0x406
+#define BRL_VSKEY_A8 0x407
+#define BRL_VSKEY_B1 0x408
+#define BRL_VSKEY_B2 0x409
+#define BRL_VSKEY_B3 0x40a
+#define BRL_VSKEY_B4 0x40b
+#define BRL_VSKEY_B5 0x40c
+#define BRL_VSKEY_B6 0x40d
+#define BRL_VSKEY_B7 0x40e
+#define BRL_VSKEY_B8 0x40f
+#define BRL_VSKEY_C1 0x410
+#define BRL_VSKEY_C2 0x411
+#define BRL_VSKEY_C3 0x412
+#define BRL_VSKEY_C4 0x413
+#define BRL_VSKEY_C5 0x414
+#define BRL_VSKEY_C6 0x415
+#define BRL_VSKEY_C7 0x416
+#define BRL_VSKEY_C8 0x417
+#define BRL_VSKEY_D1 0x418
+#define BRL_VSKEY_D2 0x419
+#define BRL_VSKEY_D3 0x41a
+#define BRL_VSKEY_D4 0x41b
+#define BRL_VSKEY_D5 0x41c
+#define BRL_VSKEY_D6 0x41d
+#define BRL_VSKEY_D7 0x41e
+#define BRL_VSKEY_D8 0x41f
+
+#define BRL_VSKEY_PLOC_LT 0x801
+#define BRL_VSKEY_BACKSPACE 0x808
+#define BRL_VSKEY_TAB 0x809
+#define BRL_VSKEY_RETURN 0x80d
+
+#define BRL_VSKEY_PLOC_PLOC_A 0x8A1
+#define BRL_VSKEY_PLOC_PLOC_B 0x8A2
+#define BRL_VSKEY_PLOC_PLOC_C 0x8A3
+#define BRL_VSKEY_PLOC_PLOC_D 0x8A4
+#define BRL_VSKEY_PLOC_PLOC_E 0x8A5
+#define BRL_VSKEY_PLOC_PLOC_F 0x8A6
+#define BRL_VSKEY_PLOC_PLOC_G 0x8A7
+#define BRL_VSKEY_PLOC_PLOC_H 0x8A8
+#define BRL_VSKEY_PLOC_PLOC_I 0x8A9
+#define BRL_VSKEY_PLOC_PLOC_J 0x8AA
+#define BRL_VSKEY_PLOC_PLOC_K 0x8AB
+#define BRL_VSKEY_PLOC_PLOC_L 0x8AC
+#define BRL_VSKEY_PLOC_PLOC_M 0x8AD
+#define BRL_VSKEY_PLOC_PLOC_N 0x8AE
+#define BRL_VSKEY_PLOC_PLOC_O 0x8AF
+#define BRL_VSKEY_PLOC_PLOC_P 0x8B0
+#define BRL_VSKEY_PLOC_PLOC_Q 0x8B1
+#define BRL_VSKEY_PLOC_PLOC_R 0x8B2
+#define BRL_VSKEY_PLOC_PLOC_S 0x8B3
+#define BRL_VSKEY_PLOC_PLOC_T 0x8B4
+#define BRL_VSKEY_PLOC_PLOC_U 0x8B5
+#define BRL_VSKEY_PLOC_PLOC_V 0x8B6
+#define BRL_VSKEY_PLOC_PLOC_W 0x8B7
+#define BRL_VSKEY_PLOC_PLOC_X 0x8B8
+#define BRL_VSKEY_PLOC_PLOC_Y 0x8B9
+#define BRL_VSKEY_PLOC_PLOC_Z 0x8BA
+
+#define BRL_VSKEY_CONTROL 0x8BE
+#define BRL_VSKEY_ALT 0x8BF
+#define BRL_VSKEY_ESCAPE 0x8e0
+
+#endif /* BRLTTY_INCLUDED_VS_BRLDEFS */ 
diff --git a/Drivers/Braille/VisioBraille/vstp.1 b/Drivers/Braille/VisioBraille/vstp.1
new file mode 100644
index 0000000..e3c21c6
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/vstp.1
@@ -0,0 +1,117 @@
+.TH "VSTP" "1" "2002-07-15" "BrlAPI" "BrlAPI User's Manual"
+.SH NAME
+vstpg, vstpp \- VisioBraille file transferring
+.SH SYNOPSIS
+vstpg [\-ifbnd] [\-s \fIsocketport\fP] [\-k \fIkeyname\fP] [\-o \fIconfigname\fP] file ...
+.SH DESCRIPTION
+\fIvstpg\fP (resp. \fIvstpp\fP) gets (resp. puts) files from (resp. onto)
+a VisioBraille terminal.
+
+For communicating with the terminal, you must launch brltty with the BrlNet
+driver, and telling BrlNet to use the VisioBraille driver.
+
+Before putting on the terminal, file names are truncated to 8 characters
+without any extension.
+
+Before getting from terminal, leading path and trailing extensions are removed,
+but put back for local filename.
+
+.SH "COMMAND\-LINE OPTIONS"
+
+.TP
+\fB\-i\fR
+ask for confirmation of transfer, for each file (on the terminal)
+.TP
+\fB\-f\fR
+don't ask for such a confirmation (default)
+.TP
+\fB\-b\fR
+if they exists, recursively rename the old files with an added .x suffix,
+just like logrotate does
+.TP
+\fB\-n\fR
+do not keep such backup file (default)
+.TP
+\fB\-s\fR \fIsocketport\fR
+use socketport as the port number instead of default for connecting to BrlNet
+.TP
+\fB\-k\fR \fIfilename\fR
+use filename as key path instead of default for reading BrlNet's
+authentication key
+.TP
+\fB\-d\fR
+use current directory rather than the download directory
+(see \fIvbs_dir\fR below)
+.TP
+\fB\-o\fR \fIfilename\fR
+also read filename as config file
+
+.SH CONFIG FILE
+
+\fIvstpg\fP and \fIvstpp\fP read a configuration file \fB$HOME/.vstprc\fR which
+contains keywords or equalities, one per line (what follows a # is
+ignored).
+
+You can ask them to also read any other file thanks to the \fB\-o\fR option.
+
+Here are keywords:
+
+.TP
+\fBbackup\fR
+make \fB\-b\fR option the default
+
+.TP
+\fBnobackup\fR
+make \fB\-f\fR option the default
+
+.TP
+and equalities:
+
+.TP
+\fBkeyname\fR = \fIfilename\fR
+use this file instead of default, to find BrlNet's authentication key
+
+.TP
+\fBsocketport\fR = \fIport\fR
+use this port number, instead of default, to connect to BrlNet
+
+.TP
+\fBvbs_ext\fR = \fI.ext\fR
+use .ext as an extension for downloaded files (.vis by default)
+this is overridden on command line if an extension is provided in the file name
+
+.TP
+\fBvbs_dir\fR = \fIpath\fR
+use path instead of current directory for putting files, except when
+using the \fB\-d\fR option, or if the filename begins with '.'
+
+.SH RETURNED VALUE
+
+.TS
+lB lfCW.
+1	syntax error on command line
+2	connection with BrlNet error
+3	Unix file error
+4	Protocol error
+16	interrupted by user
+.TE
+
+.SH SHELL EXPANSIONS
+Beware of special chars: * and . are often expanded by your shell, hence
+\fIvstpp *\fR will probably do what you want, putting every file existing in
+the current directory onto the terminal, but \fIvstpg *\fR may not do what you
+want: it will only get every file which already exist in the current
+directory, skipping those you just created on your terminal !
+If you want to get every file which exist in the terminal, you
+should use \fIvstpg '*'\fR or something similar (please read your shell manual).
+
+The same warning applies to other special chars, such as $, ~, &,... which
+should be protected by surrounding arguments by quotes (') or by using single
+backslashes (\\) just before them (please read your shell manual).
+
+.SH BUGS
+The one we could find has been corrected :)
+
+.SH AUTHOR
+Samuel Thibault <samuel.thibault@ens\-lyon.org>
+and Sebastien Hinderer <sebastien.hinderer@ens\-lyon.fr>
diff --git a/Drivers/Braille/VisioBraille/vstp.h b/Drivers/Braille/VisioBraille/vstp.h
new file mode 100644
index 0000000..b1a2f1a
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/vstp.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (C) 2006-2023 S&S
+ *  Samuel Thibault <samuel.thibault@ens-lyon.org>
+ *  Sébastien Hinderer <sebastien.hinderer@ens-lyon.org>
+ *
+ * This program is free software ; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation ; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the program ; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* vstp.h
+ * files transferring with VisioBraille terminals
+ */
+#ifndef __VBCP_H
+#define __VBCP_H
+
+/* different possible names for file */
+
+#define VSTP_PUT	"vstpp"
+#define VSTP_GET	"vstpg"
+
+#define SIZE_PUT	250 /* just like xfl */
+
+#define NUM_TRIES	3
+
+#define TRY_TIMEOUT	20 /* seconds to wait between write retries */
+
+/* variables used when transferring */
+
+extern int transferring;
+extern unsigned char burstmode;
+extern int backup;
+extern char *visiobases_dir;
+extern char *visiobases_ext;
+
+/* transfer functions */
+
+typedef void transferfun(char *filename);
+
+extern transferfun fileget;
+extern transferfun fileput;
+
+extern void transfer_init(transferfun f);
+extern void transfer_finish(transferfun f);
+
+extern void transfer_abort(int exitnum);
+
+extern void transfer_timeout(int signum);
+
+/* Packet types */
+
+#define VB_INIT_PARAMS	'I'
+#define VB_LOAD		'L'
+#define VB_UNLOAD	'U'
+#define VB_AUTOMATIC	'A'
+#define VB_MANUAL	'M'
+#define VB_OK		'Y'
+#define VB_FILEHERE	'F'
+#define VB_NEXT		'N'
+#define VB_ACK_DATA	'K'
+#define VB_HERES_DATA	'D'
+#define VB_DATA_OVER	'Z'
+#define VB_FILES_OVER	"E"
+
+#define VB_RESET	"#"
+
+#define VB_FILET_AGENDA	'A'
+#define VB_FILET_TEXTE	'T'
+
+#define VB_MAXFNLEN	8
+
+/* return codes */
+
+#define RET_EPARSE	1	/* syntax error on command line */
+#define RET_ECONN	2	/* connection with BrlNet error */
+#define RET_EUNIX	3	/* unix file error */
+#define RET_EPROTO	4	/* protocol error */
+#define RET_INT		16	/* interrupted by user */
+
+#endif /* __VBCP_H */
diff --git a/Drivers/Braille/VisioBraille/vstp_main.c b/Drivers/Braille/VisioBraille/vstp_main.c
new file mode 100644
index 0000000..6ed8361
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/vstp_main.c
@@ -0,0 +1,276 @@
+/*
+ *  Copyright (C) 2006-2023 S&S
+ *  Samuel Thibault <samuel.thibault@ens-lyon.org>
+ *  Sébastien Hinderer <sebastien.hinderer@ens-lyon.org>
+ *
+ * This program is free software ; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation ; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the program ; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* vstp_main.c
+ * files transferring with VisioBraille terminals
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include "brlapi.h"
+#include "vstp.h"
+
+#define VSTPRC ".vstprc"
+#define LINELENGTH 255
+
+char *socketport = NULL;
+char *keyname=NULL;
+
+void handleint(int signum) {
+ fprintf(stderr,"aborting on signal %d\n",signum);
+ transfer_abort(RET_INT);
+}
+
+/* every possible option */
+#define OPTIONS "ifbnskd"
+
+static void printusage(char *name) {
+ printf(VSTP_GET "/" VSTP_PUT " : get files from / put files to a VisioBraille terminal\n");
+ printf("Usage: %s [options] [files]\n",name);
+ printf("[files] are Unix filenames\n");
+ printf(" -i            ask for confirmation\n");
+ printf(" -f            don't ask for confirmation (default)\n");
+ printf(" -b            create backup (.vis~) file if file already exists\n");
+ printf(" -n            do not create backup (.vis~) file (default)\n");
+ printf(" -s port       use port as port number instead of default\n");
+ printf(" -k filename   use filename as key path instead of default\n");
+ printf(" -d            put files into current directory\n");
+ printf(" -o filename   also use filename as options file\n");
+ exit(RET_EPARSE);
+}
+
+static void grr(char *name) {
+ printusage(name);
+ exit(RET_EPARSE);
+}
+
+static transferfun *CheckSendOrRecv(char *name){
+ if (strstr(name,VSTP_PUT) != NULL) return fileput;
+ if (strstr(name,VSTP_GET) != NULL) return fileget;
+ printf("Please call me as " VSTP_PUT " or as " VSTP_GET ".\n");
+ grr(name);
+ exit(RET_EPARSE);
+}
+
+static void Parse(char *filename) {
+ FILE *fd;
+ char s[LINELENGTH+1];
+ char *c,*d;
+
+ if (!(fd=fopen(filename,"r"))) return;
+
+ while (fgets(s,LINELENGTH,fd)) {
+  if ((c=strchr(s,'#')))
+   *c='\0';
+  if ((c=strchr(s,'\n')))
+   *c='\0';
+  if ((c=strchr(s,'='))) {
+   /* '=' option : key name and value */
+   for (d=c-1; *d==' ' || *d=='\t'; d--);
+   *(d+1)='\0';
+   for (c++;   *c==' ' || *c=='\t'; c++);
+   *(c-1)='\0';
+   if (!strcmp(s,"keyname")) {
+    keyname = (char *) malloc(strlen(c)+1);
+    strcpy(keyname,c);
+   } else if (!strcmp(s,"socketport")) {
+    socketport = (char *) malloc(strlen(c)+1);
+    strcpy(socketport,c);
+   } else if (!strcmp(s,"vbs_ext")) {
+    visiobases_ext = (char *) malloc(5);
+    strncpy(visiobases_ext,c,4);
+    visiobases_ext[4]=0;
+   } else if (!strcmp(s,"vbs_dir")) {
+    visiobases_dir = (char *) malloc(strlen(c)+1);
+    strcpy(visiobases_dir,c);
+   }
+  } else {
+   if (!strcmp(s,"backup")) {
+    backup=1;
+   } else if (!strcmp(s,"nobackup")) {
+    backup=0;
+   }
+  }
+ }
+ fclose(fd);
+}
+
+static void CheckOptions(int argc, char **argv) {
+ int n,m;
+ int i;
+ for(n=1;n<argc;n++) {
+/* an option ? */
+  if (argv[n][0]=='-') {
+/* is it "--" ? */
+   if (argv[n][1]=='-') {
+/* --blabla options are not used */
+    if (argv[n][2]) {
+     printf("long option not recognized : %s\n",argv[n]);
+     grr(argv[0]);
+    } else return;
+   }
+
+   m=n;
+/* -blabla, check every letter */
+   for (i=1;argv[n][i];i++) {
+    if (argv[n][i]=='d')
+     if (visiobases_dir) {
+      free(visiobases_dir);
+      visiobases_dir=NULL;
+     }
+    if (argv[n][i]=='h') {
+     printusage(argv[0]);
+     exit(0);
+    }
+    if (argv[n][i]=='s') {
+     if (++m==argc) grr(argv[0]);
+     socketport=argv[m];
+    }
+    if (argv[n][i]=='k') {
+     if (++m==argc) grr(argv[0]);
+     keyname=argv[m];
+    }
+    if (argv[n][i]=='o') {
+     if (++m==argc) grr(argv[0]);
+     Parse(argv[m]);
+    }
+    if (argv[n][i]=='b')
+     backup=1;
+    if (argv[n][i]=='n')
+     backup=0;
+    if (argv[n][i]=='f')
+     burstmode=VB_AUTOMATIC;
+    if (argv[n][i]=='i')
+     burstmode=VB_MANUAL;
+    if (strchr(OPTIONS,argv[n][i])==NULL) {
+     printf("option not recognized : -%c\n",argv[n][i]);
+     grr(argv[0]);
+    }
+   }
+   n=m;
+  }
+ }
+}
+
+int main(int argc, char *argv[]) {
+ transferfun *transfer;
+ char driverName[13];
+ int stilloptions=1;
+ char *home;
+ brlapi_settings_t brlapi_settings;
+ 
+ transfer=CheckSendOrRecv(argv[0]);
+
+/* first use options file */
+ if ((home=getenv("HOME"))) {
+  char vstprc[strlen(home)+strlen(VSTPRC)+2];
+  strcpy(vstprc,home);
+  strcat(vstprc,"/" VSTPRC);
+  Parse(vstprc);
+ }
+
+/* a first pass to check options and record them, before doing anything */
+ CheckOptions(argc--,argv++);
+
+/* ok, one can try to open the socket */
+ brlapi_settings.host = socketport;
+ brlapi_settings.auth = keyname; 
+ if (brlapi_initializeConnection(&brlapi_settings,NULL)<0)
+ {
+  brlapi_perror("Couldn't initialize connection with BrlAPI");
+  exit(RET_ECONN);
+ }
+ if (brlapi_getDriverName(driverName, sizeof(driverName))<12)
+ {
+  brlapi_perror("Couldn't get driver name");
+  brlapi_closeConnection();
+  exit(RET_ECONN);
+ }
+ if (strcmp(driverName,"VisioBraille"))
+ {
+  fprintf(stderr,"braille driver is not VisioBraille\n");
+  brlapi_closeConnection();
+  exit(RET_ECONN);  
+ }
+ 
+ if (brlapi_enterRawMode("VisioBraille")<0) {
+  fprintf(stderr,"Couldn't get raw mode\n");
+  brlapi_closeConnection();
+  exit(RET_ECONN);
+ }
+
+ signal(SIGINT,handleint);
+ signal(SIGTERM,handleint);
+
+#ifdef SIGHUP
+ signal(SIGHUP,handleint);
+#endif /* SIGHUP */
+
+#ifdef SIGQUIT
+ signal(SIGQUIT,handleint);
+#endif /* SIGQUIT */
+
+#ifdef SIGPIPE
+ signal(SIGPIPE,handleint);
+#endif /* SIGPIPE */
+
+#ifdef SIGALRM
+ signal(SIGALRM,transfer_timeout);
+#endif /* SIGALRM */
+
+ if (visiobases_dir && chdir(visiobases_dir)<0) {
+  perror(visiobases_dir);
+  fprintf(stderr,"couldn't chdir to download dir, please use -d if you want to store files in .\n");
+  exit(RET_EUNIX);
+ }
+
+ for(;argc;argc--, argv++) {
+/* is it an option ? */
+  if (stilloptions)
+  if (argv[0][0]=='-') {
+   switch (argv[0][1]) {
+     case '-': stilloptions=0; continue;
+     case 's': /* already parsed */
+     case 'k':
+     case 'm':
+	       argc--;
+	       argv++;
+     case 'b':
+     case 'n':
+     case 'f':
+     case 'i':
+     case 'd':
+     default:
+	       continue;
+   }
+  }
+  
+/* no, a file name, let's try to transfer it */
+  transfer(argv[0]);
+ }
+ printf("transfers finished\n");
+ transfer_finish(transfer);
+ brlapi_leaveRawMode(); /* can't do much it it fails ! */
+ brlapi_closeConnection();
+ return 0;
+}
diff --git a/Drivers/Braille/VisioBraille/vstp_transfer.c b/Drivers/Braille/VisioBraille/vstp_transfer.c
new file mode 100644
index 0000000..88101c1
--- /dev/null
+++ b/Drivers/Braille/VisioBraille/vstp_transfer.c
@@ -0,0 +1,417 @@
+/*
+ *  Copyright (C) 2006-2023 S&S
+ *  Samuel Thibault <samuel.thibault@ens-lyon.org>
+ *  Sébastien Hinderer <sebastien.hinderer@ens-lyon.org>
+ *
+ * This program is free software ; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation ; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the program ; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * vstp_transfer.c
+ *
+ * handles file transfers
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "brlapi.h"
+#include "brlapi_protocol.h"
+
+#include "vstp.h"
+
+#ifdef __MINGW32__
+#define alarm(t) (void)0
+#endif
+
+/* SEND */
+/* tries to send a message */
+#define SEND(buf,size) \
+do {\
+ if (brlapi_sendRaw(buf,size)<0) {\
+  perror("while sending");\
+  transfer_abort(RET_ECONN);\
+ }\
+}\
+while (0)
+
+/* RECV */
+/* tries to get a message */
+/* on error, try to clean */
+/* also set a timeout */
+#define RECV() \
+while (1) {\
+ alarm(TRY_TIMEOUT);\
+ res=brlapi_recvRaw(ibuf,BRLAPI_MAXPACKETSIZE);\
+ alarm(0);\
+ if (res<0) {\
+  perror("while receiving");\
+  transfer_abort(RET_ECONN);\
+ }\
+ if (ibuf[0]==VB_RESET[0]) {\
+  fprintf(stderr,"transfer interrupt by user !\n");\
+  transfer_abort(RET_INT);\
+ }\
+ if (res) break;\
+}
+
+int transferring = 0;
+unsigned char burstmode = VB_AUTOMATIC;
+int backup = 0;
+char *visiobases_dir = NULL;
+char *visiobases_ext = ".vis";
+static unsigned char numpacket;
+static int sizetransferred;
+static char ibuf[BRLAPI_MAXPACKETSIZE];
+static int osize;
+static int otries;
+static char obuf[BRLAPI_MAXPACKETSIZE];
+static unsigned char filename[VB_MAXFNLEN+4+1];
+
+/* showPacket */
+/* eventually show the packet and abort, if tries number is reached */
+
+static void showPacket(int size) {
+ int i;
+ fprintf(stderr,"unexpected %c packet, size %d, content :\n", ibuf[0], size);
+ for (i=0;i<size;i++) {
+  fprintf(stderr,"%2x ", ibuf[i]);
+  if (!((i+1)%10)) fprintf(stderr,"\n");
+ }
+ if (otries++<NUM_TRIES) return;
+ fprintf(stderr,"couldn't recover, so aborting...\n");
+ transfer_abort(RET_EPROTO);
+}
+/* WaitForOk */
+/* wait for "Y" packet */
+static void WaitForOk(void) {
+ int res;
+ do { SEND(obuf,osize); RECV() }
+ while ((res==0 || ibuf[0]!=VB_OK) && (showPacket(res),1));
+}
+
+/* renameBackups */
+/* renames backups, increasing their number, like logrotate does */
+/* returns value returned by the last rename call */
+int renameBackups(const char *filename) {
+#define SIZEMAX_SUFFIX (1+20+1)
+ int n=strlen(filename);
+ char to[n+SIZEMAX_SUFFIX],from[n+SIZEMAX_SUFFIX];
+ struct stat st;
+ int i,max;
+
+ memcpy(to,filename,n);
+ for (max=1;max;max++) {
+  sprintf(to+n,".%c",max);
+  if (stat(to,&st)<0) break;
+ }
+ if (!max) {
+  fprintf(stderr,"too many backups for %s\n",filename);
+  errno=ENAMETOOLONG;
+  return -1;
+ }
+ memcpy(from,filename,n);
+ strcpy(from+n,".1");
+ i=max-1;
+ while(i) {
+  sprintf(from+n,".%u",i);
+  if (rename(from,to)<0) {
+   fprintf(stderr,"%s -> %s failed\n",from,to);
+   return -1;
+  }
+  if (--i) strcpy(to+n,from+n);
+  else break;
+ }
+ return(rename(filename,from));
+}
+
+/* fileget */
+/* get file f on VisioBraille */
+void fileget(char *f) {
+ int n=strlen(f),lnpath,lnext;
+ char *c,*d;
+ int res;
+ int fd;
+ struct stat st;
+ char *path,*ext;
+
+ if (n==0) return;
+
+/* find path, if any */
+ for (c=f+n-1; c>=f && *c!='/'; c--);
+ if (c>=f) {
+  path=f;
+  lnpath=c-f;
+  *c++='\0';
+  f=c;
+ } else {
+  path=NULL;
+  lnpath=-1;
+ }
+
+/* remove extension */
+ for (c=f; *c && *c!='.'; c++);
+ if (*c) {
+  ext=c;
+  lnext=n-(ext-f);
+ } else {
+  ext=NULL;
+  lnext=-1;
+ }
+ n=c-f;
+
+ if (path) printf("getting %s in %s\n",f,path);
+ else printf("getting %s\n",f);
+
+ transfer_init(fileget);
+
+/* sending filename (can contain * and ?) */
+ obuf[0]=VB_UNLOAD;
+ memcpy(&obuf[1],f,n);
+ osize=n+1;
+ otries=0;
+
+ while(1) {
+  SEND(obuf,osize);
+  numpacket='1';
+  RECV();
+  if (ibuf[0]==VB_FILES_OVER[0]) break; /* end of file list */
+  if (res<3) { showPacket(res); continue; }
+  if (ibuf[0]!=VB_FILEHERE) { showPacket(res); continue; }
+  if (ibuf[1]!=numpacket) { showPacket(res); continue; }
+
+/* ok, VisioBraille proposed a file, let's try to get it */
+  if (res-3>VB_MAXFNLEN) {
+   fprintf(stderr,"name too long, giving up that file\n");
+   obuf[0]=VB_NEXT;
+   obuf[1]=ibuf[1];
+   osize=2;
+   otries=0;
+   continue;
+  }
+/* copy its name */
+  {
+   char fullpath[(lnpath+1)+(res-3)+(ext?lnext+1:strlen(visiobases_ext))+1];
+   if (path) {
+    strcpy(fullpath,path);
+    strcat(fullpath,"/");
+   }
+   for(c=ibuf+3,d=fullpath+lnpath+1;c-ibuf<res;*d++=tolower(*c++))
+   fullpath[lnpath+1+res-3]='\0';
+   strcat(fullpath,ext?ext:visiobases_ext);
+
+   if (backup && stat(fullpath,&st)>=0)
+    if (renameBackups(fullpath)==-1)
+     perror("couldn't rename backups, overwriting");
+   if ((fd=open(fullpath,O_WRONLY|O_CREAT|O_TRUNC,0644))<0) {
+    /* openout failed, give up that file */
+    perror(fullpath);
+    fprintf(stderr,"open failed, giving up that file\n");
+    obuf[0]=VB_NEXT;
+    obuf[1]=ibuf[1];
+    osize=2;
+    otries=0;
+    continue;
+   }
+
+/* start transfer : */
+   obuf[0]=VB_ACK_DATA;
+   obuf[1]=numpacket;
+   osize=2;
+   otries=0;
+   numpacket=(numpacket+1)&'7';
+   sizetransferred=0;
+/* ready to transfer ! */
+   while (1) {
+    SEND(obuf,osize);
+    printf("\r%s: %dKo...",fullpath,sizetransferred>>10);
+    fflush(stdout);
+    RECV();
+    if (ibuf[0]==VB_DATA_OVER) break;
+    if (res<2) { showPacket(res); continue; }
+    if (ibuf[0]!=VB_HERES_DATA) { showPacket(res); continue; }
+    if (ibuf[1]!=numpacket) { showPacket(res); continue; }
+    if (write(fd,ibuf+2,res-2)<res-2) {
+     fprintf(stderr,"writing data on disk for file %s\n"
+ 		    "So giving up\n",fullpath);
+     transfer_abort(RET_EUNIX);
+    }
+    obuf[0]=VB_ACK_DATA;
+    obuf[1]=numpacket;
+    osize=2;
+    otries=0;
+    sizetransferred+=res-2;
+    numpacket=(numpacket+1)&'7';
+   }
+/* transfer finished */
+   close(fd);
+   printf("ok\n");
+   obuf[0]=VB_OK;
+   osize=1;
+   otries=0;
+  }
+ }
+ transferring=0;
+}
+
+
+static int tryToFind(char *f, int *fd) {
+ if ((*fd=open(f,O_RDONLY))<0) {
+  strcpy(ibuf,f);
+  strcat(ibuf,visiobases_ext);
+  if ((*fd=open(ibuf,O_RDONLY))<0) {
+   strcpy(ibuf,f);
+   strcat(ibuf,".vis");
+   if ((*fd=open(ibuf,O_RDONLY))<0) {
+    strcpy(ibuf,f);
+    strcat(ibuf,".Vis");
+    if ((*fd=open(ibuf,O_RDONLY))<0) {
+     strcpy(ibuf,f);
+     strcat(ibuf,".VIS");
+     if ((*fd=open(ibuf,O_RDONLY))<0) {
+      return 0;
+     }
+    }
+   }
+  }
+ }
+ return 1;
+}
+
+/* fileput */
+/* send file f to VisioBraille */
+void fileput(char *f) {
+ int n;
+ char *c;
+ int res;
+ int fd;
+
+ if (visiobases_dir && (f[0]!='.' || (f[1]!='.' && f[1]!='/') || (f[1]=='.' && f[2]!='/'))) {
+  char *f2 = malloc(strlen(visiobases_dir)+1+strlen(f));
+  strcpy(f2,visiobases_dir);
+  strcat(f2,f);
+  if (tryToFind(f2, &fd)) {
+   free(f2);
+   goto ok;
+  }
+  fprintf(stderr,"couldn't get it from download directory, trying from current directory.\n");
+  free(f2);
+ }
+ if (!tryToFind(f, &fd)) {
+  fprintf(stderr,"open failed, giving up that file\n");
+  return;
+ }
+
+ok:
+ transfer_init(fileput);
+
+ printf("putting %s\n",f);
+
+/* truncate filename : no extension, 8 chars max */
+ if ((c=strrchr(f,'/'))) f=c+1;
+ for (c=f;*c && *c!='.' && c<f+VB_MAXFNLEN;c++);
+ n=c-f;
+
+ obuf[0]=VB_FILEHERE;
+ obuf[1]=numpacket='1';
+ obuf[2]=VB_FILET_AGENDA;
+ memcpy(&obuf[3],f,n);
+ memcpy(filename,f,n);
+ filename[n]=0;
+ osize=n+3;
+ otries=0;
+
+ sizetransferred=0;
+ while(1) {
+  SEND(obuf,osize);
+  printf("\r%s: %dKo...",filename,sizetransferred>>10);
+  fflush(stdout);
+  RECV();
+  if (ibuf[0]==VB_NEXT) break;
+  if (res<2) { showPacket(res); continue; }
+  if (ibuf[0]!=VB_ACK_DATA) { showPacket(res); continue; }
+  if (ibuf[1]!=numpacket) { showPacket(res); continue; }
+  if ((res=read(fd,&obuf[2],SIZE_PUT))<0) {
+   fprintf(stderr,"reading data on disk for file %s failed: %s\n"
+		  "So giving up for this file\n",strerror(errno),filename);
+   break;
+  }
+  if (res==0) { /* eof */
+   obuf[0]=VB_DATA_OVER;
+   osize=1;
+   otries=0;
+   WaitForOk();
+   break;
+  }
+  obuf[0]=VB_HERES_DATA;
+  obuf[1]=numpacket=(numpacket+1)&'7';
+  osize=res+2;
+  otries=0;
+  sizetransferred+=res;
+ }
+/* transfer finished */
+ close(fd);
+ printf("ok\n");
+}
+
+/* transfer_init */
+/* send the correct "I" packet, according to options */
+void transfer_init(transferfun *f) {
+ if (transferring) return;
+ transferring=1;
+
+ obuf[0]=VB_INIT_PARAMS;
+ obuf[1]=(f==fileget?VB_UNLOAD:VB_LOAD);
+ obuf[2]=burstmode;
+ osize=3;
+ otries=0;
+ WaitForOk();
+ numpacket='1';
+}
+
+/* transfer_finish */
+/* terminate transfer (also end of file list) */
+void transfer_finish(transferfun *f) {
+ if (!transferring) return;
+ transferring=0;
+ if (f==fileput)
+  SEND(VB_FILES_OVER,strlen(VB_FILES_OVER));
+}
+
+/* transfer_abort */
+/* if something nasty occured, try to clean */
+void transfer_abort(int exitnum) {
+ brlapi_sendRaw(VB_RESET,strlen(VB_RESET));
+ brlapi_leaveRaw();
+ brlapi_closeConnection();
+ exit(exitnum);
+}
+
+/* transfer_timeout */
+/* called when RecvPacket timed out */
+void transfer_timeout(int signum) {
+ if (otries++>=NUM_TRIES) {
+  fprintf(stderr,"No reply from terminal ! Assuming dead, hence aborting\n");
+  transfer_abort(RET_EPROTO);
+ }
+ SEND(obuf,osize);
+ alarm(1);
+}
diff --git a/Drivers/Braille/Voyager/Makefile.in b/Drivers/Braille/Voyager/Makefile.in
new file mode 100644
index 0000000..2b525ac
--- /dev/null
+++ b/Drivers/Braille/Voyager/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = vo
+DRIVER_NAME = Voyager
+DRIVER_USAGE = 44/70, Part232 (serial adapter), BraillePen/EasyLink
+DRIVER_VERSION = 0.3 (June 2009)
+DRIVER_DEVELOPERS = Stéphane Doyon <s.doyon@videotron.ca>
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) $(KDRIVER) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/Voyager/README b/Drivers/Braille/Voyager/README
new file mode 100644
index 0000000..2a3c6be
--- /dev/null
+++ b/Drivers/Braille/Voyager/README
@@ -0,0 +1,82 @@
+BRLTTY Driver for the Tieman Voyager Braille Display
+This is the user-space-only version of the driver.
+Version 0.10 (March 2004)
+
+Copyright 2004 by Stéphane Doyon  <s.doyon@videotron.ca>
+
+This is a partial rewrite of the driver which functions entirely from
+user-space (whereas the previous driver depended on a kernel driver for
+the USB communication with the Voyager display).
+
+This driver supports the Tieman Voyager 44 and 70.
+
+The driver works with later 2.4.x kernels and with at least some versions
+in the 2.6.x series.
+Note that the kernel brlvger driver used with the old BRLTTY driver does
+not work properly with 2.6.x kernels.
+
+Many thanks to the Tieman people: Corand van Strien, Ivar Illing, Daphne
+Vogelaar and Ingrid Vogel. They provided us with a Braille display (as
+well as programming information) so that we could write this driver. They
+replaced the display when it broke and they answered our technical
+questions. It is very motivating when companies take an interest in such
+projects and are so supportive.
+
+Thanks to Andor Demarteau <ademarte@students.cs.uu.nl> who got this whole
+project started and beta-tested all our early buggy attempts.
+
+Thanks to Stéphane Dalton who wrote the initial version of the old kernel
+driver. Without his initiative this project would not have been a success.
+
+This rewrite of the driver should allow more flexibility, but is as of yet
+very young and not thoroughly tested.
+
+When BRLTTY will be running, press the leftmost routing key to bring up
+the help screen so you can read the details of the key bindings.
+
+Users of the 2.6.x kernel may experience some difficulties.  The old
+kernel driver, which is included in the official kernels, will actually
+get in the way of this driver as it will claim the USB interface to the
+display, and I've seen some hangs occur when BRLTTY asks to disconnect
+the kernel driver. If you have this problem you should make sure the
+kernel module called brlvger is not inserted into your kernel. One simple
+way to prevent that module from auto-loading is to rename the module file
+so it isn't found.
+cd /lib/modules/`uname -r`/kernel/drivers/usb/
+mv brlvger.o brlvger.o.hold
+If lsmod shows the brlvger module as already loaded then do
+rmmod brlvger
+
+The old kernel driver will soon be pulled out of 2.6.x kernels.
+
+BRLTTY currently assumes the default braille device to be a serial port,
+so you must tell it that it is in fact USB.
+Either provide the "--with-braille-device=usb:" option when running the
+BRLTTY top-level ./configure command, or use the braille-device parameter
+in your BRLTTY configuration file (/etc/brltty.conf):
+braille-device usb:
+
+If you actually have more than one Voyager display connected at the same
+time, you can also specify the serial number of the device that you want
+to use.
+
+This driver knows a number of driver parameters which you can pass via
+the braille-parameters clause in your BRLTTY configuration file
+(/etc/brltty.conf) or via brltty's -B (--braille-parameters=) command
+line option.
+
+InputMode: This parameter specifies whether or not the eight top keys
+function as a braille keyboard. When set to "no" (the default), top
+key combinations perform various navigational and operational
+functions. When set to yes", they function as an 8-dot braille
+keyboard. If B, C, or B+C is pressed along with any top key
+combination then it's as if this parameter were set to "no".
+
+There are only a handful of BRLTTY users, so if you are trying out this
+driver, please drop us a note, even if you have no problems!
+
+Note to BRLTTY developers: This is my first attempt at combining the key
+binding definitions with the help file text in one place, through
+annotations in the braille.c file. If you change the key bindings,
+currently you need python installed to be able to rebuild the help
+files.
diff --git a/Drivers/Braille/Voyager/TODO b/Drivers/Braille/Voyager/TODO
new file mode 100644
index 0000000..0f969f5
--- /dev/null
+++ b/Drivers/Braille/Voyager/TODO
@@ -0,0 +1,5 @@
+Possible improvements:
+-Get comments and possibly rework key bindings.
+-Add some more bindings: for toggling some environment settings
+   for instance.
+-Turn the display off when idle to save power on laptops.
diff --git a/Drivers/Braille/Voyager/braille.c b/Drivers/Braille/Voyager/braille.c
new file mode 100644
index 0000000..7af51cb
--- /dev/null
+++ b/Drivers/Braille/Voyager/braille.c
@@ -0,0 +1,1031 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Voyager/braille.c - Braille display driver for Tieman Voyager displays.
+ *
+ * Written by Stéphane Doyon  <s.doyon@videotron.ca>
+ *
+ * It is being tested on Voyager 44, should also support Voyager 70.
+ * It is designed to be compiled in BRLTTY version 4.1.
+ *
+ * History:
+ * 0.21, January 2005:
+ *       Remove gcc4 signedness/unsignedness incompatibilities.
+ * 0.20, June 2004:
+ *       Add statuscells parameter.
+ *       Rename brlinput parameter to inputmode.
+ *       Change default inputmode to no.
+ *       Chorded functions work without chording when inputmode is no.
+ *       Move complex routing key combinations to front/dot keys.
+ *       Duplicate status key bindings on front/dot keys.
+ *       Execute on first release rather than on all released.
+ *       Add support for the part232 serial adapter.
+ * 0.10, March 2004: Use BRLTTY core repeat functions. Add brlinput parameter
+ *   and toggle to disallow braille typing.
+ * 0.01, January 2004: fork from the original driver which relied on an
+ *   in-kernel USB driver.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "async_wait.h"
+#include "ascii.h"
+#include "bitfield.h"
+
+#include "brl_driver.h"
+#include "brldefs-vo.h"
+
+
+BEGIN_KEY_NAME_TABLE(all)
+  KEY_GROUP_ENTRY(VO_GRP_RoutingKeys, "RoutingKey"),
+
+  KEY_NAME_ENTRY(VO_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(VO_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(VO_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(VO_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(VO_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(VO_KEY_Dot6, "Dot6"),
+  KEY_NAME_ENTRY(VO_KEY_Dot7, "Dot7"),
+  KEY_NAME_ENTRY(VO_KEY_Dot8, "Dot8"),
+
+  KEY_NAME_ENTRY(VO_KEY_Thumb1, "Thumb1"),
+  KEY_NAME_ENTRY(VO_KEY_Thumb2, "Thumb2"),
+  KEY_NAME_ENTRY(VO_KEY_Left, "Left"),
+  KEY_NAME_ENTRY(VO_KEY_Up, "Up"),
+  KEY_NAME_ENTRY(VO_KEY_Down, "Down"),
+  KEY_NAME_ENTRY(VO_KEY_Right, "Right"),
+  KEY_NAME_ENTRY(VO_KEY_Thumb3, "Thumb3"),
+  KEY_NAME_ENTRY(VO_KEY_Thumb4, "Thumb4"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLE(bp)
+  KEY_GROUP_ENTRY(VO_GRP_RoutingKeys, "RoutingKey"),
+
+  KEY_NAME_ENTRY(BP_KEY_Dot1, "Dot1"),
+  KEY_NAME_ENTRY(BP_KEY_Dot2, "Dot2"),
+  KEY_NAME_ENTRY(BP_KEY_Dot3, "Dot3"),
+  KEY_NAME_ENTRY(BP_KEY_Dot4, "Dot4"),
+  KEY_NAME_ENTRY(BP_KEY_Dot5, "Dot5"),
+  KEY_NAME_ENTRY(BP_KEY_Dot6, "Dot6"),
+
+  KEY_NAME_ENTRY(BP_KEY_Shift, "Shift"),
+  KEY_NAME_ENTRY(BP_KEY_Space, "Space"),
+  KEY_NAME_ENTRY(BP_KEY_Control, "Control"),
+
+  KEY_NAME_ENTRY(BP_KEY_JoystickEnter, "JoystickEnter"),
+  KEY_NAME_ENTRY(BP_KEY_JoystickLeft, "JoystickLeft"),
+  KEY_NAME_ENTRY(BP_KEY_JoystickRight, "JoystickRight"),
+  KEY_NAME_ENTRY(BP_KEY_JoystickUp, "JoystickUp"),
+  KEY_NAME_ENTRY(BP_KEY_JoystickDown, "JoystickDown"),
+
+  KEY_NAME_ENTRY(BP_KEY_ScrollLeft, "ScrollLeft"),
+  KEY_NAME_ENTRY(BP_KEY_ScrollRight, "ScrollRight"),
+END_KEY_NAME_TABLE
+
+BEGIN_KEY_NAME_TABLES(all)
+  KEY_NAME_TABLE(all),
+END_KEY_NAME_TABLES
+
+BEGIN_KEY_NAME_TABLES(bp)
+  KEY_NAME_TABLE(bp),
+END_KEY_NAME_TABLES
+
+DEFINE_KEY_TABLE(all)
+DEFINE_KEY_TABLE(bp)
+
+BEGIN_KEY_TABLE_LIST
+  &KEY_TABLE_DEFINITION(all),
+  &KEY_TABLE_DEFINITION(bp),
+END_KEY_TABLE_LIST
+
+
+#define READY_BEEP_DURATION 200
+#define MAXIMUM_CELL_COUNT 70 /* arbitrary max for allocations */
+
+static unsigned char forceWrite;
+static unsigned char cellCount;
+#define IS_TEXT_RANGE(key1,key2) (((key1) <= (key2)) && ((key2) < cellCount))
+#define IS_TEXT_KEY(key) IS_TEXT_RANGE((key), (key))
+
+/* Structure to remember which keys are pressed */
+typedef struct {
+  uint16_t navigation;
+  unsigned char routing[MAXIMUM_CELL_COUNT];
+} Keys;
+
+static Keys pressedKeys;
+static char keysInitialized;
+
+static void
+initializeKeys (void) {
+  if (!keysInitialized) {
+    memset(&pressedKeys, 0, sizeof(pressedKeys));
+    keysInitialized = 1;
+  }
+}
+
+static void
+updateKeys (BrailleDisplay *brl, const unsigned char *packet) {
+  Keys currentKeys;
+
+  unsigned char navigationPresses[0X10];
+  int navigationPressCount = 0;
+
+  unsigned char routingPresses[6];
+  int routingPressCount = 0;
+
+  initializeKeys();
+  memset(&currentKeys, 0, sizeof(currentKeys));
+  currentKeys.navigation = (packet[1] << 8) | packet[0];
+
+  {
+    unsigned char key = 0;
+    uint16_t bit = 0X1;
+
+    while (key < 0X10) {
+      if ((pressedKeys.navigation & bit) && !(currentKeys.navigation & bit)) {
+        enqueueKeyEvent(brl, VO_GRP_NavigationKeys, key, 0);
+      } else if (!(pressedKeys.navigation & bit) && (currentKeys.navigation & bit)) {
+        navigationPresses[navigationPressCount++] = key;
+      }
+
+      bit <<= 1;
+      key += 1;
+    }
+  }
+  
+  {
+    int i;
+
+    for (i=2; i<8; i+=1) {
+      unsigned char key = packet[i];
+      if (!key) break;
+
+      if ((key < 1) || (key > cellCount)) {
+        logMessage(LOG_NOTICE, "invalid routing key number: %u", key);
+        continue;
+      }
+      key -= 1;
+
+      currentKeys.routing[key] = 1;
+      if (!pressedKeys.routing[key]) routingPresses[routingPressCount++] = key;
+    }
+  }
+
+  {
+    unsigned char key;
+
+    for (key=0; key<cellCount; key+=1)
+      if (pressedKeys.routing[key] && !currentKeys.routing[key])
+        enqueueKeyEvent(brl, VO_GRP_RoutingKeys, key, 0);
+  }
+
+  while (navigationPressCount)
+    enqueueKeyEvent(brl, VO_GRP_NavigationKeys, navigationPresses[--navigationPressCount], 1);
+
+  while (routingPressCount)
+    enqueueKeyEvent(brl, VO_GRP_RoutingKeys, routingPresses[--routingPressCount], 1);
+
+  pressedKeys = currentKeys;
+}
+
+
+typedef struct {
+  int (*getCellCount) (BrailleDisplay *brl, unsigned char *length);
+  int (*logSerialNumber) (BrailleDisplay *brl);
+  int (*logHardwareVersion) (BrailleDisplay *brl);
+  int (*logFirmwareVersion) (BrailleDisplay *brl);
+  int (*setDisplayVoltage) (BrailleDisplay *brl, unsigned char voltage);
+  int (*getDisplayVoltage) (BrailleDisplay *brl, unsigned char *voltage);
+  int (*getDisplayCurrent) (BrailleDisplay *brl, unsigned char *current);
+  int (*setDisplayState) (BrailleDisplay *brl, unsigned char state);
+  int (*writeBraille) (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start);
+  int (*updateKeys) (BrailleDisplay *brl);
+  int (*soundBeep) (BrailleDisplay *brl, unsigned char duration);
+} ProtocolOperations;
+
+static const ProtocolOperations *protocol;
+
+
+#define SERIAL_BAUD 38400
+#define SERIAL_READY_DELAY 400
+#define SERIAL_INPUT_TIMEOUT 100
+#define SERIAL_WAIT_TIMEOUT 200
+
+#define BLUETOOTH_CHANNEL_NUMBER 1
+#define BLUETOOTH_READY_DELAY 800
+
+static int
+tellResource (
+  BrailleDisplay *brl,
+  uint8_t request, uint16_t value, uint16_t index,
+  const unsigned char *data, uint16_t size
+) {
+  ssize_t result = gioTellResource(brl->gioEndpoint, UsbControlRecipient_Endpoint, UsbControlType_Vendor,
+                                   request, value, index, data, size);
+  return result != -1;
+}
+
+static int
+askResource (
+  BrailleDisplay *brl,
+  uint8_t request, uint16_t value, uint16_t index,
+  unsigned char *buffer, uint16_t size
+) {
+  ssize_t result = gioAskResource(brl->gioEndpoint, UsbControlRecipient_Endpoint, UsbControlType_Vendor,
+                                  request, value, index, buffer, size);
+  int ok = result != -1;
+
+  if (ok) {
+    logInputPacket(buffer, result);
+  }
+
+  return ok;
+}
+
+
+static const char *const serialDeviceNames[] = {"Serial Adapter", "Base Unit"};
+
+static int
+writeSerialPacket (BrailleDisplay *brl, unsigned char code, unsigned char *data, unsigned char count) {
+  unsigned char buffer[2 + (count * 2)];
+  unsigned char size = 0;
+  unsigned char index;
+
+  buffer[size++] = ASCII_ESC;
+  buffer[size++] = code;
+
+  for (index=0; index<count; ++index)
+    if ((buffer[size++] = data[index]) == buffer[0])
+      buffer[size++] = buffer[0];
+
+  return writeBraillePacket(brl, NULL, buffer, size);
+}
+
+static int
+readSerialPacket (BrailleDisplay *brl, unsigned char *packet, int size) {
+  int started = 0;
+  int escape = 0;
+  int offset = 0;
+  int length = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    if (!gioReadByte(brl->gioEndpoint, &byte, (started || escape))) {
+      if (started) logPartialPacket(packet, offset);
+      return 0;
+    }
+
+    if (byte == ASCII_ESC) {
+      if ((escape = !escape)) continue;
+    } else if (escape) {
+      escape = 0;
+
+      if (offset > 0) {
+        logShortPacket(packet, offset);
+        offset = 0;
+        length = 0;
+      } else {
+        started = 1;
+      }
+    }
+
+    if (!started) {
+      logIgnoredByte(byte);
+      continue;
+    }
+
+    if (offset < size) {
+      if (offset == 0) {
+        switch (byte) {
+          case 0X43:
+          case 0X47:
+            length = 2;
+            break;
+
+          case 0X4C:
+            length = 3;
+            break;
+
+          case 0X46:
+          case 0X48:
+            length = 5;
+            break;
+
+          case 0X4B:
+            length = 9;
+            break;
+
+          case 0X53:
+            length = 10;
+            break;
+
+          default:
+            logUnknownPacket(byte);
+            started = 0;
+            continue;
+        }
+      }
+
+      packet[offset] = byte;
+    } else {
+      if (offset == size) logTruncatedPacket(packet, offset);
+      logDiscardedByte(byte);
+    }
+
+    if (++offset == length) {
+      if (offset > size) {
+        offset = 0;
+        length = 0;
+        started = 0;
+        continue;
+      }
+
+      logInputPacket(packet, offset);
+      return length;
+    }
+  }
+}
+
+static int
+nextSerialPacket (BrailleDisplay *brl, unsigned char code, unsigned char *buffer, int size, int wait) {
+  int length;
+
+  if (wait)
+    if (!awaitBrailleInput(brl, SERIAL_WAIT_TIMEOUT))
+      return 0;
+
+  while ((length = readSerialPacket(brl, buffer, size))) {
+    if (buffer[0] == code) return length;
+    logUnexpectedPacket(buffer, length);
+  }
+
+  return 0;
+}
+
+static int
+getSerialCellCount (BrailleDisplay *brl, unsigned char *count) {
+  const unsigned int code = 0X4C;
+  if (writeSerialPacket(brl, code, NULL, 0)) {
+    unsigned char buffer[3];
+    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
+      *count = buffer[2];
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int
+logSerialSerialNumber (BrailleDisplay *brl) {
+  unsigned char device;
+
+  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
+    const unsigned char code = 0X53;
+    unsigned char buffer[10];
+
+    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
+    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
+    logMessage(LOG_INFO, "%s Serial Number: %02X%02X%02X%02X%02X%02X%02X%02X",
+               serialDeviceNames[buffer[1]],
+               buffer[2], buffer[3], buffer[4], buffer[5],
+               buffer[6], buffer[7], buffer[8], buffer[9]);
+  }
+
+  return 1;
+}
+
+static int
+logSerialHardwareVersion (BrailleDisplay *brl) {
+  unsigned char device;
+
+  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
+    const unsigned char code = 0X48;
+    unsigned char buffer[5];
+
+    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
+    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
+    logMessage(LOG_INFO, "%s Hardware Version: %c.%c.%c", 
+               serialDeviceNames[buffer[1]],
+               buffer[2], buffer[3], buffer[4]);
+  }
+
+  return 1;
+}
+
+static int
+logSerialFirmwareVersion (BrailleDisplay *brl) {
+  unsigned char device;
+
+  for (device=0; device<ARRAY_COUNT(serialDeviceNames); ++device) {
+    const unsigned char code = 0X46;
+    unsigned char buffer[5];
+
+    if (!writeSerialPacket(brl, code, &device, 1)) return 0;
+    if (!nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) return 0;
+    logMessage(LOG_INFO, "%s Firmware Version: %c.%c.%c", 
+               serialDeviceNames[buffer[1]],
+               buffer[2], buffer[3], buffer[4]);
+  }
+
+  return 1;
+}
+
+static int
+setSerialDisplayVoltage (BrailleDisplay *brl, unsigned char voltage) {
+  return writeSerialPacket(brl, 0X56, &voltage, 1);
+}
+
+static int
+getSerialDisplayVoltage (BrailleDisplay *brl, unsigned char *voltage) {
+  const unsigned char code = 0X47;
+  if (writeSerialPacket(brl, code, NULL, 0)) {
+    unsigned char buffer[2];
+    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
+      *voltage = buffer[1];
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int
+getSerialDisplayCurrent (BrailleDisplay *brl, unsigned char *current) {
+  const unsigned int code = 0X43;
+  if (writeSerialPacket(brl, code, NULL, 0)) {
+    unsigned char buffer[2];
+    if (nextSerialPacket(brl, code, buffer, sizeof(buffer), 1)) {
+      *current = buffer[1];
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int
+setSerialDisplayState (BrailleDisplay *brl, unsigned char state) {
+  return writeSerialPacket(brl, 0X44, &state, 1);
+}
+
+static int
+writeSerialBraille (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
+  unsigned char buffer[2 + count];
+  unsigned char size = 0;
+  buffer[size++] = start;
+  buffer[size++] = count;
+  memcpy(&buffer[size], cells, count);
+  size += count;
+  return writeSerialPacket(brl, 0X42, buffer, size);
+}
+
+static int
+updateSerialKeys (BrailleDisplay *brl) {
+  const unsigned char code = 0X4B;
+  unsigned char packet[9];
+
+  while (nextSerialPacket(brl, code, packet, sizeof(packet), 0)) {
+    updateKeys(brl, &packet[1]);
+  }
+
+  return errno == EAGAIN;
+}
+
+static int
+soundSerialBeep (BrailleDisplay *brl, unsigned char duration) {
+  return writeSerialPacket(brl, 0X41, &duration, 1);
+}
+
+static const ProtocolOperations serialProtocolOperations = {
+  .getCellCount = getSerialCellCount,
+  .logSerialNumber = logSerialSerialNumber,
+  .logHardwareVersion = logSerialHardwareVersion,
+  .logFirmwareVersion = logSerialFirmwareVersion,
+  .setDisplayVoltage = setSerialDisplayVoltage,
+  .getDisplayVoltage = getSerialDisplayVoltage,
+  .getDisplayCurrent = getSerialDisplayCurrent,
+  .setDisplayState = setSerialDisplayState,
+  .writeBraille = writeSerialBraille,
+  .updateKeys = updateSerialKeys,
+  .soundBeep = soundSerialBeep
+};
+
+
+static int
+getUsbCellCount (BrailleDisplay *brl, unsigned char *count) {
+  unsigned char buffer[2];
+
+  if (!askResource(brl, 0X06, 0, 0, buffer, sizeof(buffer))) return 0;
+  *count = buffer[1];
+  return 1;
+}
+
+static wchar_t *
+getUsbString (BrailleDisplay *brl, uint8_t request) {
+  UsbDescriptor descriptor;
+
+  if (askResource(brl, request, 0, 0, descriptor.bytes, sizeof(descriptor.bytes))) {
+    size_t count = (descriptor.string.bLength - 2) / sizeof(descriptor.string.wData[0]);
+    wchar_t *string = malloc((count + 1) * sizeof(*string));
+
+    if (string) {
+      string[count] = 0;
+
+      while (count) {
+        count -= 1;
+        string[count] = getLittleEndian16(descriptor.string.wData[count]);
+      }
+
+      return string;
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+static int
+logUsbString (BrailleDisplay *brl, uint8_t request, const char *description) {
+  wchar_t *string = getUsbString(brl, request);
+
+  if (string) {
+    logMessage(LOG_INFO, "%s: %" PRIws, description, string);
+    free(string);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+logUsbSerialNumber (BrailleDisplay *brl) {
+  return logUsbString(brl, 0X03, "Serial Number");
+}
+
+static int
+logUsbHardwareVersion (BrailleDisplay *brl) {
+  unsigned char buffer[2];
+
+  if (!askResource(brl, 0X04, 0, 0, buffer, sizeof(buffer))) return 0;
+  logMessage(LOG_INFO, "Hardware Version: %u.%u",
+             buffer[0], buffer[1]);
+  return 1;
+}
+
+static int
+logUsbFirmwareVersion (BrailleDisplay *brl) {
+  return logUsbString(brl, 0X05, "Firmware Version");
+}
+
+static int
+setUsbDisplayVoltage (BrailleDisplay *brl, unsigned char voltage) {
+  return tellResource(brl, 0X01, voltage, 0, NULL, 0);
+}
+
+static int
+getUsbDisplayVoltage (BrailleDisplay *brl, unsigned char *voltage) {
+  unsigned char buffer[1];
+
+  if (!askResource(brl, 0X02, 0, 0, buffer, sizeof(buffer))) return 0;
+  *voltage = buffer[0];
+  return 1;
+}
+
+static int
+getUsbDisplayCurrent (BrailleDisplay *brl, unsigned char *current) {
+  unsigned char buffer[1];
+
+  if (!askResource(brl, 0X08, 0, 0, buffer, sizeof(buffer))) return 0;
+  *current = buffer[0];
+  return 1;
+}
+
+static int
+setUsbDisplayState (BrailleDisplay *brl, unsigned char state) {
+  return tellResource(brl, 0X00, state, 0, NULL, 0);
+}
+
+static int
+writeUsbBraille (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
+  return tellResource(brl, 0X07, 0, start, cells, count);
+}
+
+static int
+updateUsbKeys (BrailleDisplay *brl) {
+  while (1) {
+    unsigned char packet[8];
+
+    {
+      ssize_t result = gioReadData(brl->gioEndpoint, packet, sizeof(packet), 0);
+      if (!result) return 1;
+
+      if (result < 0) {
+        if (errno == ENODEV) {
+          /* Display was disconnected */
+          return 0;
+        }
+
+        logMessage(LOG_ERR, "USB read error: %s", strerror(errno));
+        keysInitialized = 0;
+        return 1;
+      }
+
+      if (result < sizeof(packet)) {
+        /* The display should only ever deliver packets of exactly 8 bytes */
+        logPartialPacket(packet, result);
+        keysInitialized = 0;
+        return 1;
+      }
+
+      logInputPacket(packet, result);
+    }
+
+    updateKeys(brl, packet);
+  }
+}
+
+static int
+soundUsbBeep (BrailleDisplay *brl, unsigned char duration) {
+  return tellResource(brl, 0X09, duration, 0, NULL, 0);
+}
+
+static const ProtocolOperations usbProtocolOperations = {
+  .getCellCount = getUsbCellCount,
+  .logSerialNumber = logUsbSerialNumber,
+  .logHardwareVersion = logUsbHardwareVersion,
+  .logFirmwareVersion = logUsbFirmwareVersion,
+  .setDisplayVoltage = setUsbDisplayVoltage,
+  .getDisplayVoltage = getUsbDisplayVoltage,
+  .getDisplayCurrent = getUsbDisplayCurrent,
+  .setDisplayState = setUsbDisplayState,
+  .writeBraille = writeUsbBraille,
+  .updateKeys = updateUsbKeys,
+  .soundBeep = soundUsbBeep
+};
+
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  SerialParameters serialParameters;
+
+  BEGIN_USB_CHANNEL_DEFINITIONS
+    { /* all models */
+      .vendor=0X0798, .product=0X0001, 
+      .configuration=1, .interface=0, .alternative=0,
+      .inputEndpoint=1
+    },
+  END_USB_CHANNEL_DEFINITIONS
+
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  gioInitializeSerialParameters(&serialParameters);
+  serialParameters.baud = SERIAL_BAUD;
+  serialParameters.flowControl = SERIAL_FLOW_HARDWARE;
+
+  descriptor.serial.parameters = &serialParameters;
+  descriptor.serial.options.applicationData = &serialProtocolOperations;
+  descriptor.serial.options.readyDelay = SERIAL_READY_DELAY;
+  descriptor.serial.options.inputTimeout = SERIAL_INPUT_TIMEOUT;
+
+  descriptor.usb.channelDefinitions = usbChannelDefinitions;
+  descriptor.usb.options.applicationData = &usbProtocolOperations;
+
+  descriptor.bluetooth.channelNumber = BLUETOOTH_CHANNEL_NUMBER;
+  descriptor.bluetooth.options.applicationData = &serialProtocolOperations;
+  descriptor.bluetooth.options.readyDelay = BLUETOOTH_READY_DELAY;
+  descriptor.bluetooth.options.inputTimeout = SERIAL_INPUT_TIMEOUT;
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    protocol = gioGetApplicationData(brl->gioEndpoint);
+    return 1;
+  }
+
+  return 0;
+}
+
+
+typedef struct {
+  const char *name;
+  const KeyTableDefinition *keyTable;
+} ProductEntry;
+
+static const ProductEntry productEntry_Voyager = {
+  .name = "Voyager",
+  .keyTable = &KEY_TABLE_DEFINITION(all)
+};
+
+static const ProductEntry productEntry_BraillePen = {
+  .name = "Braille Pen",
+  .keyTable = &KEY_TABLE_DEFINITION(bp)
+};
+
+
+typedef struct {
+  const ProductEntry *product;
+  int (*writeBraille) (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start);
+  unsigned char reportedCellCount;
+  unsigned char actualCellCount;
+  unsigned partialUpdates:1;
+} ModelEntry;
+
+static const ModelEntry *model;
+
+typedef struct {
+  struct {
+    const unsigned char *cells;
+    unsigned char offset;
+    unsigned char count;
+  } from;
+
+  struct {
+    unsigned char *cells;
+    unsigned char offset;
+  } to;
+} WriteBrailleData;
+
+static void
+addHiddenCells (WriteBrailleData *wbd, unsigned char size) {
+  while (size) {
+    wbd->to.cells[wbd->to.offset++] = 0;
+    size -= 1;
+  }
+}
+
+static void
+addActualCells (WriteBrailleData *wbd, unsigned char size) {
+  unsigned char count;
+
+  if (!size) size = wbd->from.count;
+  if ((count = size) > wbd->from.count) count = wbd->from.count;
+
+  if (count) {
+    memcpy(&wbd->to.cells[wbd->to.offset], &wbd->from.cells[wbd->from.offset], count);
+    wbd->from.count -= count;
+    wbd->from.offset += count;
+    wbd->to.offset += count;
+  }
+
+  addHiddenCells(wbd, size-count);
+}
+
+static int
+writeBraille0 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
+  return protocol->writeBraille(brl, cells, count, start);
+}
+
+static int
+writeBraille2 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
+  if (!model->partialUpdates) {
+    unsigned char buffer[count + 2];
+    WriteBrailleData wbd = {
+      .from = {
+        .cells = cells,
+        .offset = 0,
+        .count = cellCount
+      },
+
+      .to = {
+        .cells = buffer,
+        .offset = 0
+      }
+    };
+
+    addHiddenCells(&wbd, 2);
+    addActualCells(&wbd, 0);
+    return protocol->writeBraille(brl, buffer, sizeof(buffer), 0);
+  }
+
+  return protocol->writeBraille(brl, cells, count, start+2);
+}
+
+static int
+writeBraille4 (BrailleDisplay *brl, const unsigned char *cells, unsigned char count, unsigned char start) {
+  if (!model->partialUpdates) {
+    unsigned char buffer[count + 4];
+    WriteBrailleData wbd = {
+      .from = {
+        .cells = cells,
+        .offset = 0,
+        .count = cellCount
+      },
+
+      .to = {
+        .cells = buffer,
+        .offset = 0
+      }
+    };
+
+    addHiddenCells(&wbd, 2);
+    addActualCells(&wbd, 6);
+    addHiddenCells(&wbd, 2);
+    addActualCells(&wbd, 0);
+    return protocol->writeBraille(brl, buffer, sizeof(buffer), 0);
+  }
+
+  if (start >= 6) {
+    return protocol->writeBraille(brl, &cells[start], count, start+4);
+  }
+
+  if ((start + count) <= 6) {
+    return protocol->writeBraille(brl, &cells[start], count, start+2);
+  }
+
+  {
+    unsigned char buffer[count + 2];
+    WriteBrailleData wbd = {
+      .from = {
+        .cells = cells,
+        .offset = start,
+        .count = count
+      },
+
+      .to = {
+        .cells = buffer,
+        .offset = 0
+      }
+    };
+
+    addActualCells(&wbd, 6-start);
+    addHiddenCells(&wbd, 2);
+    addActualCells(&wbd, 0);
+    return protocol->writeBraille(brl, buffer, sizeof(buffer), start+2);
+  }
+}
+
+static const ModelEntry modelTable[] = {
+  { .product = &productEntry_Voyager,
+    .reportedCellCount = 48,
+    .actualCellCount = 44,
+    .writeBraille = writeBraille4,
+    .partialUpdates = 1,
+  },
+
+  { .product = &productEntry_Voyager,
+    .reportedCellCount = 72,
+    .actualCellCount = 70,
+    .writeBraille = writeBraille2,
+    .partialUpdates = 1,
+  },
+
+  { .product = &productEntry_BraillePen,
+    .reportedCellCount = 12,
+    .actualCellCount = 12,
+    .writeBraille = writeBraille0,
+  },
+
+  { .product = NULL }
+};
+
+
+/* Global variables */
+static unsigned char *previousCells = NULL; /* previous pattern displayed */
+static unsigned char *translatedCells = NULL; /* buffer to prepare new pattern */
+
+/* Voltage: from 0->300V to 255->200V.
+ * Presumably this is voltage for dot firmness.
+ * Presumably 0 makes dots hardest, 255 makes them softest.
+ * We are told 265V is normal operating voltage but we don't know the scale.
+ */
+static int
+setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting) {
+  unsigned char voltage = 0XFF - (setting * 0XFF / BRL_FIRMNESS_MAXIMUM);
+  logMessage(LOG_DEBUG, "setting display voltage: %02X", voltage);
+  return protocol->setDisplayVoltage(brl, voltage);
+}
+
+static int
+soundBeep (BrailleDisplay *brl, unsigned char duration) {
+  if (!protocol->soundBeep(brl, duration)) return 0;
+  asyncWait(duration);
+  return 1;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (connectResource(brl, device)) {
+    if (protocol->getCellCount(brl, &cellCount)) {
+      model = modelTable;
+
+      while (model->product) {
+        if (model->reportedCellCount == cellCount) {
+          const ProductEntry *product = model->product;
+          logMessage(LOG_INFO, "Product: %s", product->name);
+
+          cellCount = model->actualCellCount;
+          logMessage(LOG_INFO, "Cell Count: %u", cellCount);
+
+          protocol->logSerialNumber(brl);
+          protocol->logHardwareVersion(brl);
+          protocol->logFirmwareVersion(brl);
+
+          /* translatedCells holds the status cells and the text cells.
+           * We export directly to BRLTTY only the text cells.
+           */
+          brl->textColumns = cellCount;		/* initialize size of display */
+          brl->textRows = 1;		/* always 1 */
+
+          setBrailleKeyTable(brl, product->keyTable);
+          brl->setBrailleFirmness = setBrailleFirmness;
+
+          if ((previousCells = malloc(cellCount))) {
+            if ((translatedCells = malloc(cellCount))) {
+              if (protocol->setDisplayState(brl, 1)) {
+                makeOutputTable(dotsTable_ISO11548_1);
+                keysInitialized = 0;
+                forceWrite = 1;
+
+                soundBeep(brl, READY_BEEP_DURATION);
+                return 1;
+              }
+
+              free(translatedCells);
+              translatedCells = NULL;
+            } else {
+              logMallocError();
+            }
+
+            free(previousCells);
+            previousCells = NULL;
+          } else {
+            logMallocError();
+          }
+
+          break;
+        }
+
+        model += 1;
+      }
+
+      if (!model->reportedCellCount) {
+        logMessage(LOG_ERR, "unsupported cell count: %u", cellCount);
+        model = NULL;
+      }
+    }
+
+    disconnectBrailleResource(brl, NULL);
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl) {
+  disconnectBrailleResource(brl, NULL);
+
+  if (translatedCells) {
+    free(translatedCells);
+    translatedCells = NULL;
+  }
+
+  if (previousCells) {
+    free(previousCells);
+    previousCells = NULL;
+  }
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
+  unsigned int from = 0;
+  unsigned int to = cellCount;
+  int changed;
+
+  if (model->partialUpdates) {
+    changed = cellsHaveChanged(previousCells, brl->buffer, cellCount, &from, &to, &forceWrite);
+  } else {
+    changed = cellsHaveChanged(previousCells, brl->buffer, cellCount, NULL, NULL, &forceWrite);
+  }
+
+  if (changed) {
+    translateOutputCells(&translatedCells[from], &brl->buffer[from], to-from);
+    if (!model->writeBraille(brl, translatedCells, to-from, from)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  return protocol->updateKeys(brl)? EOF: BRL_CMD_RESTARTBRL;
+}
diff --git a/Drivers/Braille/Voyager/brldefs-vo.h b/Drivers/Braille/Voyager/brldefs-vo.h
new file mode 100644
index 0000000..5065983
--- /dev/null
+++ b/Drivers/Braille/Voyager/brldefs-vo.h
@@ -0,0 +1,71 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_VO_BRLDEFS
+#define BRLTTY_INCLUDED_VO_BRLDEFS
+
+typedef enum {
+  /* The top round keys behind the routing keys, named as if they
+   * are to be used for braille input.
+   */
+  VO_KEY_Dot1 = 0,
+  VO_KEY_Dot2 = 1,
+  VO_KEY_Dot3 = 2,
+  VO_KEY_Dot4 = 3,
+  VO_KEY_Dot5 = 4,
+  VO_KEY_Dot6 = 5,
+  VO_KEY_Dot7 = 6,
+  VO_KEY_Dot8 = 7,
+
+  /* The front keys */
+  VO_KEY_Thumb1 =  8, /* Leftmost */
+  VO_KEY_Thumb2 =  9, /* Second from left */
+  VO_KEY_Left   = 10, /* Round key to the left of the central pad */
+  VO_KEY_Up     = 11, /* Up position of central pad */
+  VO_KEY_Down   = 12, /* Down position of central pad */
+  VO_KEY_Right  = 13, /* Round key to the right of the central pad */
+  VO_KEY_Thumb3 = 14, /* Second from right */
+  VO_KEY_Thumb4 = 15  /* Rightmost */
+} VO_NavigationKey;
+
+typedef enum {
+  BP_KEY_Dot1    = 0,
+  BP_KEY_Dot2    = 1,
+  BP_KEY_Dot3    = 2,
+  BP_KEY_Dot4    = 3,
+  BP_KEY_Dot5    = 4,
+  BP_KEY_Dot6    = 5,
+  BP_KEY_Shift   = 6,
+  BP_KEY_Control = 7,
+
+  BP_KEY_ScrollLeft     =  8,
+  BP_KEY_JoystickEnter  =  9,
+  BP_KEY_JoystickLeft   = 10,
+  BP_KEY_JoystickUp     = 11,
+  BP_KEY_JoystickDown   = 12,
+  BP_KEY_JoystickRight  = 13,
+  BP_KEY_Space          = 14,
+  BP_KEY_ScrollRight    = 15
+} BP_NavigationKey;
+
+typedef enum {
+  VO_GRP_NavigationKeys = 0,
+  VO_GRP_RoutingKeys
+} VO_KeyGroup;
+
+#endif /* BRLTTY_INCLUDED_VO_BRLDEFS */ 
diff --git a/Drivers/Braille/XWindow/Makefile.in b/Drivers/Braille/XWindow/Makefile.in
new file mode 100644
index 0000000..61f5dd3
--- /dev/null
+++ b/Drivers/Braille/XWindow/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = xw
+DRIVER_NAME = XWindow
+DRIVER_USAGE = graphical (X11, Windows)
+DRIVER_VERSION = 0.1, 2004
+DRIVER_DEVELOPERS = Samuel Thibault <samuel.thibault@ens-lyon.org>
+BRL_OBJS = @braille_libraries_xw@
+include $(SRC_TOP)braille.mk
+
+braille.$O:
+	$(CC) $(BRL_CFLAGS) $(X11_INCLUDES) -c $(SRC_DIR)/braille.c
+
diff --git a/Drivers/Braille/XWindow/README b/Drivers/Braille/XWindow/README
new file mode 100644
index 0000000..5ca9fa6
--- /dev/null
+++ b/Drivers/Braille/XWindow/README
@@ -0,0 +1,71 @@
+Description
+===========
+
+This driver lets use a Graphical User Interface as BrlTTY device:
+it behaves just like a real braille device, its cost excepted :)
+
+It is mostly intended for sighted developers who don't have access to a real
+device, but still want to have an idea of the accessiblity of their application.
+
+How to run basically
+====================
+
+Assuming you're under X, first launch brltty:
+$ brltty -b xw -x as -A auth=none
+
+Then you can launch a BrlAPI application:
+$ ~/brltty/Programs/apitest
+or you can launch text applications in AT-SPI terminals like gnome-terminal.
+More details can be found on http://brl.thefreecat.org/text-apps-a11y-test.html
+
+You may give several parameters to the driver:
+$ brltty -b xw -t identity -B tkparms="-geometry -0+0 -display :1",lines=2,cols=20,model=bare
+will set a 20x2 braille display appear in the upper right corner of the
+:1 display, without any navigation key. There is also the vs model which
+will show up most of VisioBraille device's keys.
+
+You should configure your window manager not to let the BRLTTY window get
+keyboard focus, so as to be able to simulate keypresses by pressing buttons.
+
+Redirecting the output
+======================
+
+The output of this virtual braille device can be shown on another computer, by
+redirecting the X output. Let's say you will run brltty on "mycomputer", with
+the output on "othercomputer". First you have to let mycomputer connect to
+othercomputer's X server:
+
+othercomputer$ xhost +mycomputer
+
+And then you can start brltty on mycomputer:
+
+mycomputer$ DISPLAY=othercomputer:0 brltty -b xw -B input=on
+
+More fine-grain access control can be achieved by using xauth.
+
+Another way, which might be easier depending on your configuration, is to
+forward X through ssh:
+
+othercomputer$ ssh -X mycomputer
+mycomputer$ brltty -b xw -B input=on
+
+
+The input=on option makes the keypresses in the device window get simulated
+on mycomputer, just like a real braille device.
+
+*** Don't set input=on without using DISPLAY with a different display,
+because otherwise emulated keypresses will just loop ! ***
+
+Braille cells
+=============
+
+If you want braille display as well, you'll need the ClearlyU font
+(included in recent xfonts-base packages) and a UTF-8 locale for
+characters: if your usual locale is en_US, launch brltty with a
+prepended LC_CTYPE:
+
+LC_CTYPE=en_US.UTF-8 brltty ...
+
+On windows, you need to copy the file UBraille.ttf into the C:\Windows\Fonts\
+folder. It can be found in this directory as well as in the Fonts\ top-level
+folder of BRLTTY's Windows installed files hierarchy.
diff --git a/Drivers/Braille/XWindow/README.UBraille b/Drivers/Braille/XWindow/README.UBraille
new file mode 100644
index 0000000..ec5e63d
--- /dev/null
+++ b/Drivers/Braille/XWindow/README.UBraille
@@ -0,0 +1,3 @@
+UBraille.ttf is a set of true type font glyphs for the Unicode braille range 
+[U+28yx]. It was developed by Vyacheslav Dikonov <sdiconov@mail.ru>, and can be 
+downloaded from: http://yudit.org/download/fonts/UBraille/
diff --git a/Drivers/Braille/XWindow/UBraille.ttf b/Drivers/Braille/XWindow/UBraille.ttf
new file mode 100644
index 0000000..18a9f12
--- /dev/null
+++ b/Drivers/Braille/XWindow/UBraille.ttf
Binary files differ
diff --git a/Drivers/Braille/XWindow/braille.c b/Drivers/Braille/XWindow/braille.c
new file mode 100644
index 0000000..ff8114f
--- /dev/null
+++ b/Drivers/Braille/XWindow/braille.c
@@ -0,0 +1,1320 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+#include <signal.h>
+
+#include "log.h"
+#include "parse.h"
+#include "charset.h"
+#include "unicode.h"
+
+#if defined(WINDOWS)
+#define USE_WINDOWS
+#elif defined(HAVE_PKG_XAW)
+#define USE_XAW
+#define USE_XT
+#include <X11/Intrinsic.h>
+#include <X11/Xaw/Form.h>
+#include <X11/Xaw/Paned.h>
+#include <X11/Xaw/Label.h>
+#include <X11/Xaw/Command.h>
+#include <X11/Xaw/Repeater.h>
+#include <X11/Xaw/SimpleMenu.h>
+#include <X11/Xaw/SmeLine.h>
+#include <X11/Xaw/SmeBSB.h>
+#elif defined(HAVE_PKG_XAW3D)
+#define USE_XAW
+#define USE_XT
+#define XAW_INTERNATIONALIZATION
+#include <X11/Intrinsic.h>
+#include <X11/Xaw3d/Form.h>
+#include <X11/Xaw3d/Paned.h>
+#include <X11/Xaw3d/Label.h>
+#include <X11/Xaw3d/Command.h>
+#include <X11/Xaw3d/Repeater.h>
+#include <X11/Xaw3d/SimpleMenu.h>
+#include <X11/Xaw3d/SmeLine.h>
+#include <X11/Xaw3d/SmeBSB.h>
+#elif defined(HAVE_PKG_NEXTAW)
+#define USE_XAW
+#define USE_XT
+#include <X11/Intrinsic.h>
+#include <X11/neXtaw/Form.h>
+#include <X11/neXtaw/Paned.h>
+#include <X11/neXtaw/Label.h>
+#include <X11/neXtaw/Command.h>
+#include <X11/neXtaw/Repeater.h>
+#include <X11/neXtaw/SimpleMenu.h>
+#include <X11/neXtaw/SmeLine.h>
+#include <X11/neXtaw/SmeBSB.h>
+#elif defined(HAVE_PKG_XAWPLUS)
+#define USE_XAW
+#define USE_XT
+#include <X11/Intrinsic.h>
+#include <X11/XawPlus/Form.h>
+#include <X11/XawPlus/Paned.h>
+#include <X11/XawPlus/Label.h>
+#include <X11/XawPlus/Command.h>
+#include <X11/XawPlus/Repeater.h>
+#include <X11/XawPlus/SimpleMenu.h>
+#include <X11/XawPlus/SmeLine.h>
+#include <X11/XawPlus/SmeBSB.h>
+#elif defined(HAVE_PKG_XM)
+#define USE_XM
+#define USE_XT
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/PanedW.h>
+#include <Xm/Label.h>
+#include <Xm/PushB.h>
+#include <Xm/ToggleB.h>
+#include <Xm/RowColumn.h>
+#include <Xm/MenuShell.h>
+#else /* HAVE_PKG_ */
+#error GUI toolkit either unspecified or unsupported
+#endif /* HAVE_PKG_ */
+
+#ifdef USE_XT
+#define XK_MISCELLANY
+#include <X11/Xlib.h>
+#include <X11/StringDefs.h>
+#include <X11/keysymdef.h>
+#include <X11/Shell.h>
+#endif /* USE_XT */
+
+#ifdef USE_WINDOWS
+#include <commctrl.h>
+#include <windowsx.h>
+#define XtNumber(t) (sizeof(t)/sizeof(*t))
+typedef void *XtPointer;
+#endif /* USE_WINDOWS */
+
+#if defined(USE_XAW)
+#define formWidgetClass       formWidgetClass
+#define panedWidgetClass      panedWidgetClass
+#define labelWidgetClass      labelWidgetClass
+#define commandWidgetClass    commandWidgetClass
+#define repeaterWidgetClass   repeaterWidgetClass
+#define menuEntryWidgetClass  smeBSBObjectClass
+#define CreatePopupMenu(title, toplevel) \
+	XtCreatePopupShell(title, simpleMenuWidgetClass, toplevel, NULL, 0)
+#define AddMenuSeparator(title, menu) \
+	XtVaCreateManagedWidget(title, smeLineObjectClass, menu, NULL)
+#define AddMenuLabel(title, menu) \
+	XtVaCreateManagedWidget(title, smeBSBObjectClass, menu, NULL);
+#define AddMenuRadio(title, menu, cb, checked) \
+	XtVaCreateManagedWidget(title, menuEntryWidgetClass, menu, \
+	NvalueChangedCallback, cb, NtoggleState, checked ? check : None, \
+	XtNleftMargin, 9, \
+	NULL);
+#define Nlabel                XtNlabel
+#define Ncallback             XtNcallback
+#define NvalueChangedCallback XtNcallback
+#define Ntop                  XtNtop
+#define Nbottom               XtNbottom
+#define Nleft                 XtNleft
+#define Nright                XtNright
+#define ChainTop              XtChainTop
+#define ChainBottom           XtChainTop
+#define ChainLeft             XtChainLeft
+#define ChainRight            XtChainLeft
+#define NvertDistance         XtNvertDistance
+#define NhorizDistance        XtNhorizDistance
+#define NtoggleState          XtNleftBitmap
+#define MenuWidget            Widget
+#elif defined(USE_XM)
+#define formWidgetClass       xmFormWidgetClass
+#define panedWidgetClass      xmPanedWindowWidgetClass
+#define labelWidgetClass      xmLabelWidgetClass
+#define commandWidgetClass    xmPushButtonWidgetClass
+#define repeaterWidgetClass   xmPushButtonWidgetClass
+#define menuEntryWidgetClass  xmToggleButtonWidgetClass
+#define CreatePopupMenu(title, toplevel) \
+	XmCreatePopupMenu(toplevel, title, NULL, 0)
+#define AddMenuSeparator(title, menu) while (0) { }
+#define AddMenuLabel(title, menu) \
+	XtVaCreateManagedWidget(title, xmToggleButtonWidgetClass, menu, NULL);
+#define AddMenuRadio(title, menu, cb, checked) \
+	XtVaCreateManagedWidget(title, menuEntryWidgetClass, menu, \
+	NvalueChangedCallback, cb, NtoggleState, checked ? XmSET : XmUNSET, \
+	NULL);
+#define Nlabel                XmNlabelString
+#define Ncallback             XmNactivateCallback
+#define NvalueChangedCallback XmNvalueChangedCallback
+#define Ntop                  XmNtopAttachment
+#define Nbottom               XmNbottomAttachment
+#define Nleft                 XmNleftAttachment
+#define Nright                XmNrightAttachment
+#define ChainTop              XmATTACH_FORM
+#define ChainBottom           XmATTACH_NONE
+#define ChainLeft             XmATTACH_FORM
+#define ChainRight            XmATTACH_NONE
+#define NvertDistance         XmNtopOffset
+#define NhorizDistance        XmNleftOffset
+#define NtoggleState          XmNset
+#define MenuWidget            Widget
+#elif defined(USE_WINDOWS)
+#define Widget                HWND
+#define MenuWidget            HMENU
+#define CreatePopupMenu(title, toplevel) \
+	CreatePopupMenu()
+#define AddMenuSeparator(title, menu) \
+	AppendMenu(menu, MF_SEPARATOR, 0, NULL)
+#define AddMenuLabel(title, menu) \
+	AppendMenu(menu, MF_STRING | MF_DISABLED, 0, title)
+#define AddMenuRadio(title, menu, cb, check) \
+	AppendMenu(menu, MF_STRING | (check?MF_CHECKED:0), cb, title)
+#define CHRX 16
+#define CHRY 20
+#define RIGHTMARGIN 100
+#else /* USE_ */
+#error GUI toolkit paradigm either unspecified or unsupported
+#endif /* USE_ */
+
+typedef enum {
+  PARM_TKPARMS,
+  PARM_LINES,
+  PARM_COLUMNS,
+  PARM_MODEL,
+  PARM_INPUT,
+  PARM_FONT
+} DriverParameter;
+#define BRLPARMS "tkparms", "lines", "columns", "model", "input", "font"
+
+#include "brl_driver.h"
+#include "braille.h"
+
+#define MAXLINES 3
+#define MAXCOLS 88
+#define WHOLESIZE (MAXLINES * MAXCOLS)
+static int cols,lines;
+static int input;
+static const char *model = "simple";
+static const char *fontname = "-*-clearlyu-*-*-*-*-*-*-*-*-*-*-iso10646-1,-*-fixed-*-*-*-*-*-*-*-*-*-*-iso10646-1,-*-unifont-*-*-*-*-*-*-*-*-*-*-iso10646-1,-*-fixed-*-*-*-*-*-*-*-*-*-*-iso8859-1";
+static int xtArgc = 1;
+static char *xtDefArgv[]= { "brltty", NULL };
+static char **xtArgv = xtDefArgv;
+static int regenerate;
+static int generateToplevel(void);
+static void destroyToplevel(void);
+#if defined(USE_XAW) || defined(USE_WINDOWS)
+static unsigned char displayedWindow[WHOLESIZE];
+#endif /* USE_XAW || USE_WINDOWS */
+static wchar_t displayedVisual[WHOLESIZE];
+
+#define BUTWIDTH 48
+#define BUTHEIGHT 32
+
+static Widget toplevel,hbox,display[WHOLESIZE];
+static MenuWidget menu;
+#if defined(USE_XAW) || defined(USE_WINDOWS)
+static Widget displayb[WHOLESIZE];
+#endif /* USE_XAW || USE_WINDOWS */
+#ifdef USE_XAW
+static Pixmap check;
+#endif /* USE_XAW */
+static int lastcursor = BRL_NO_CURSOR;
+#ifdef USE_XT
+static Atom wm_delete_window;
+static Widget vbox,keybox;
+static Pixel displayForeground,displayBackground;
+static XtAppContext app_con;
+#ifdef USE_XM
+static XmString display_cs;
+#endif /* USE_XAW */
+#endif /* USE_XT */
+#ifdef USE_XAW
+static XFontSet fontset;
+#elif defined(USE_WINDOWS)
+static HFONT font;
+static int totlines;
+#endif /* USE_WINDOWS */
+
+#ifdef USE_WINDOWS
+static int modelWidth,modelHeight;
+#endif /* USE_WINDOWS */
+
+#ifdef USE_XT
+static void KeyPressCB(Widget w, XtPointer closure, XtPointer callData)
+{
+  logMessage(LOG_DEBUG,"keypresscb(%p)", closure);
+  enqueueCommand((long) closure);
+}
+
+static void keypress(Widget w, XEvent *event, String *params, Cardinal *num_params) {
+  static Modifiers my_modifiers;
+  long keypressed;
+  Modifiers modifiers, modifier;
+  KeySym keysym;
+
+  if (event->type != KeyPress && event->type != KeyRelease) {
+    logMessage(LOG_ERR,"keypress is not a KeyPress");
+    return;
+  }
+  keysym = XtGetActionKeysym(event, &modifiers);
+  modifiers |= my_modifiers;
+  logMessage(LOG_DEBUG,"keypress(%#lx), modif(%#x)", keysym, modifiers);
+
+  /* latin1 */
+  if (keysym < 0x100) keysym |= 0x1000000;
+
+  if ((keysym & 0x1f000000) == 0x1000000) {
+    /* unicode */
+    if ((keysym & ~UNICODE_CELL_MASK) == UNICODE_BRAILLE_ROW)
+      keypressed = BRL_CMD_BLK(PASSDOTS) | (keysym & 0xff);
+    else {
+      int c = convertWcharToChar(keysym & 0xffffff);
+      if (c == EOF) {
+	logMessage(LOG_DEBUG, "non translatable unicode U+%lx", keysym & 0xffffff);
+	return;
+      }
+      keypressed = BRL_CMD_BLK(PASSCHAR) | c;
+    }
+  }
+  else switch(keysym) {
+    case XK_Shift_L:
+    case XK_Shift_R:   modifier = ShiftMask;   goto modif;
+    case XK_Control_L:
+    case XK_Control_R: modifier = ControlMask; goto modif;
+    case XK_Alt_L:
+    case XK_Alt_R:
+    case XK_Meta_L:
+    case XK_Meta_R:    modifier = Mod1Mask;    goto modif;
+    case XK_KP_Enter:
+    case XK_Return:       keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_ENTER;           break;
+    case XK_KP_Tab:
+    case XK_Tab:          keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_TAB;             break;
+    case XK_BackSpace:    keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_BACKSPACE;       break;
+    case XK_Escape:       keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_ESCAPE;          break;
+    case XK_KP_Left:
+    case XK_Left:         keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_LEFT;     break;
+    case XK_KP_Right:
+    case XK_Right:        keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_RIGHT;    break;
+    case XK_KP_Up:
+    case XK_Up:           keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_UP;       break;
+    case XK_KP_Down:
+    case XK_Down:         keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_DOWN;     break;
+    case XK_KP_Page_Up:
+    case XK_Page_Up:      keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_PAGE_UP;         break;
+    case XK_KP_Page_Down:
+    case XK_Page_Down:    keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_PAGE_DOWN;       break;
+    case XK_KP_Home:
+    case XK_Home:         keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_HOME;            break;
+    case XK_KP_End:
+    case XK_End:          keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_END;             break;
+    case XK_KP_Insert:
+    case XK_Insert:       keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_INSERT;          break;
+    case XK_KP_Delete:
+    case XK_Delete:       keypressed = BRL_CMD_BLK(PASSKEY) | BRL_KEY_DELETE;          break;
+    case XK_KP_F1:
+    case XK_F1:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  0); break;
+    case XK_KP_F2:
+    case XK_F2:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  1); break;
+    case XK_KP_F3:
+    case XK_F3:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  2); break;
+    case XK_KP_F4:
+    case XK_F4:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  3); break;
+    case XK_F5:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  4); break;
+    case XK_F6:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  5); break;
+    case XK_F7:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  6); break;
+    case XK_F8:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  7); break;
+    case XK_F9:           keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  8); break;
+    case XK_F10:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION +  9); break;
+    case XK_F11:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 10); break;
+    case XK_F12:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 11); break;
+    case XK_F13:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 12); break;
+    case XK_F14:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 13); break;
+    case XK_F15:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 14); break;
+    case XK_F16:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 15); break;
+    case XK_F17:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 16); break;
+    case XK_F18:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 17); break;
+    case XK_F19:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 18); break;
+    case XK_F20:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 19); break;
+    case XK_F21:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 20); break;
+    case XK_F22:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 21); break;
+    case XK_F23:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 22); break;
+    case XK_F24:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 23); break;
+    case XK_F25:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 24); break;
+    case XK_F26:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 25); break;
+    case XK_F27:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 26); break;
+    case XK_F28:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 27); break;
+    case XK_F29:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 28); break;
+    case XK_F30:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 29); break;
+    case XK_F31:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 30); break;
+    case XK_F32:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 31); break;
+    case XK_F33:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 32); break;
+    case XK_F34:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 33); break;
+    case XK_F35:          keypressed = BRL_CMD_BLK(PASSKEY) | (BRL_KEY_FUNCTION + 34); break;
+    case XK_KP_Space:     keypressed = BRL_CMD_BLK(PASSCHAR) | ' '; break;
+    case XK_KP_Equal:     keypressed = BRL_CMD_BLK(PASSCHAR) | '='; break;
+    case XK_KP_Multiply:  keypressed = BRL_CMD_BLK(PASSCHAR) | '*'; break;
+    case XK_KP_Add:       keypressed = BRL_CMD_BLK(PASSCHAR) | '+'; break;
+    case XK_KP_Separator: keypressed = BRL_CMD_BLK(PASSCHAR) | ','; break;
+    case XK_KP_Subtract:  keypressed = BRL_CMD_BLK(PASSCHAR) | '-'; break;
+    case XK_KP_Decimal:   keypressed = BRL_CMD_BLK(PASSCHAR) | '.'; break;
+    case XK_KP_Divide:    keypressed = BRL_CMD_BLK(PASSCHAR) | '/'; break;
+    case XK_KP_0:         keypressed = BRL_CMD_BLK(PASSCHAR) | '0'; break;
+    case XK_KP_1:         keypressed = BRL_CMD_BLK(PASSCHAR) | '1'; break;
+    case XK_KP_2:         keypressed = BRL_CMD_BLK(PASSCHAR) | '2'; break;
+    case XK_KP_3:         keypressed = BRL_CMD_BLK(PASSCHAR) | '3'; break;
+    case XK_KP_4:         keypressed = BRL_CMD_BLK(PASSCHAR) | '4'; break;
+    case XK_KP_5:         keypressed = BRL_CMD_BLK(PASSCHAR) | '5'; break;
+    case XK_KP_6:         keypressed = BRL_CMD_BLK(PASSCHAR) | '6'; break;
+    case XK_KP_7:         keypressed = BRL_CMD_BLK(PASSCHAR) | '7'; break;
+    case XK_KP_8:         keypressed = BRL_CMD_BLK(PASSCHAR) | '8'; break;
+    case XK_KP_9:         keypressed = BRL_CMD_BLK(PASSCHAR) | '9'; break;
+    default: logMessage(LOG_DEBUG,"unsupported keysym %lx",keysym); return;
+  }
+
+  if (modifiers & ControlMask) keypressed |= BRL_FLG_INPUT_CONTROL;
+  if (modifiers & Mod1Mask) keypressed |= BRL_FLG_INPUT_META;
+  if (modifiers & ShiftMask) keypressed |= BRL_FLG_INPUT_SHIFT;
+  if (modifiers & LockMask) keypressed |= BRL_FLG_INPUT_UPPER;
+  if (event->type != KeyPress) keypressed = BRL_CMD_NOOP;
+
+  logMessage(LOG_DEBUG,"keypressed %#lx", keypressed);
+  enqueueCommand(keypressed);
+  return;
+
+modif:
+  logMessage(LOG_DEBUG,"modifier %#x", modifier);
+  if (event->type == KeyPress)
+    my_modifiers |= modifier;
+  else
+    my_modifiers &= ~modifier;
+}
+
+static void route(Widget w, XEvent *event, String *params, Cardinal *num_params)
+{
+  int index = atoi(params[0]);
+  logMessage(LOG_DEBUG,"route(%u)", index);
+
+  if (event->xbutton.state & ControlMask) {
+    enqueueCommand(BRL_CMD_BLK(CLIP_NEW) | (index&BRL_MSK_ARG));
+  } else if (event->xbutton.state & Mod1Mask) {
+    enqueueCommand(BRL_CMD_BLK(COPY_LINE) | (index&BRL_MSK_ARG));
+  } else {
+    enqueueCommand(BRL_CMD_BLK(ROUTE) | (index&BRL_MSK_ARG));
+  }
+}
+
+static void quit(Widget w, XEvent *event, String *params, Cardinal *num_params)
+{
+  XtAppSetExitFlag(app_con);
+}
+#endif /* USE_XT */
+
+static inline Widget crKeyBut(char *name, long keycode, int repeat,
+    int horizDistance, int vertDistance)
+{
+  Widget button;
+#if defined(USE_XT)
+  button = XtVaCreateManagedWidget(name,
+    repeat?repeaterWidgetClass:commandWidgetClass, keybox,
+    XtNwidth, BUTWIDTH, XtNheight, BUTHEIGHT,
+#ifdef USE_XAW
+    XtNinitialDelay, 500, XtNminimumDelay, 100,
+#endif /* USE_XAW */
+    NhorizDistance, horizDistance,
+    NvertDistance, vertDistance,
+    Ntop, ChainTop,
+    Nbottom, ChainBottom,
+    Nleft, ChainLeft,
+    Nright, ChainRight,
+    NULL);
+  XtAddCallback(button, Ncallback, KeyPressCB, (XtPointer) keycode);
+#elif defined(USE_WINDOWS)
+  button = CreateWindow(WC_BUTTON, name, WS_CHILD | WS_VISIBLE, horizDistance, totlines*CHRY+1+vertDistance, BUTWIDTH, BUTHEIGHT, toplevel, NULL, NULL, NULL);
+  SetWindowLongPtr(button, GWLP_USERDATA, (LONG_PTR) keycode);
+#else /* USE_ */
+#error Toolkit button creation unspecified
+#endif /* USE_ */
+  return button;
+}
+
+struct button {
+  char *label;
+  long keycode;
+  int repeat;
+  int x,y;
+};
+
+struct model {
+  const char *name;
+  struct button *buttons;
+  int width,height;
+};
+
+static const struct model *keyModel;
+
+static struct button buttons_simple[] = {
+  { "Dot1",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT1  , 0, 0, 0 },
+  { "Dot2",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT2  , 0, 0, 1 },
+  { "Dot3",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT3  , 0, 0, 2 },
+  { "Dot4",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT4  , 0, 1, 0 },
+  { "Dot5",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT5  , 0, 1, 1 },
+  { "Dot6",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT6  , 0, 1, 2 },
+  { "Dot7",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT7  , 0, 0, 3 },
+  { "Dot8",   BRL_CMD_BLK(PASSDOTS)  | BRL_DOT8  , 0, 1, 3 },
+  { "`",      BRL_CMD_TOP_LEFT, 0, 3, 0 },
+  { "^",      BRL_CMD_LNUP,   1, 4, 0 },
+  { "Paste",  BRL_CMD_PASTE,  0, 5, 0 },
+  { "<",      BRL_CMD_FWINLT, 1, 3, 1 },
+  { "Home",   BRL_CMD_HOME,   0, 4, 1 },
+  { ">",      BRL_CMD_FWINRT, 1, 5, 1 },
+  { "<=",     BRL_CMD_FWINLTSKIP, 0, 3, 2 },
+  { "v",      BRL_CMD_LNDN,   1, 4, 2 },
+  { "=>",     BRL_CMD_FWINRTSKIP, 0, 5, 2 },
+  { "alt-c",  BRL_FLG_INPUT_META    | BRL_CMD_BLK(PASSCHAR) | 'c', 0, 3, 3 },
+  { "ctrl-c", BRL_FLG_INPUT_CONTROL | BRL_CMD_BLK(PASSCHAR) | 'c', 0, 4, 3 },
+  { "a",      BRL_CMD_BLK(PASSCHAR)                        | 'a', 0, 5, 3 },
+  { "A",      BRL_CMD_BLK(PASSCHAR)                        | 'A', 0, 6, 3 },
+  { "Alt-F1", BRL_FLG_INPUT_META | BRL_KEY_FUNCTION | BRL_CMD_BLK(PASSKEY) , 0, 7, 3 },
+  { "Frez",   BRL_CMD_FREEZE,   0, 6, 0 },
+  { "Bksp",   BRL_CMD_KEY(BACKSPACE),   0, 6, 1 },
+  { "Help",   BRL_CMD_HELP,     0, 7, 0 },
+  { "Pref",   BRL_CMD_PREFMENU, 0, 7, 1 },
+  { "PL",     BRL_CMD_PREFLOAD, 0, 6, 2 },
+  { "PS",     BRL_CMD_PREFSAVE, 0, 7, 2 },
+  { NULL,     0,                0, 0, 0},
+};
+
+static struct button buttons_vs[] = {
+	/*
+  { "VT1",  BRL_CMD_BLK(SWITCHVT)+0,   1, 0, 1 },
+  { "VT2",  BRL_CMD_BLK(SWITCHVT)+1, 1, 1, 1 },
+  { "VT3",  BRL_CMD_BLK(SWITCHVT)+2, 1, 2, 1 },
+  { "VT4",  BRL_CMD_BLK(SWITCHVT)+3, 1, 6, 1 },
+  { "VT5",  BRL_CMD_BLK(SWITCHVT)+4, 1, 7, 1 },
+  { "VT6",  BRL_CMD_BLK(SWITCHVT)+5, 1, 8, 1 },
+	*/
+  //{ "B5", EOF, /* cut */      1, 5, 2 },
+  { "TOP",  BRL_CMD_TOP_LEFT,   1, 6, 2 },
+  { "BOT",  BRL_CMD_BOT_LEFT,   1, 6, 4 },
+  { "<=",   BRL_CMD_FWINLTSKIP, 1, 1, 0 },
+  { "<=",   BRL_CMD_FWINLTSKIP, 1, 8, 2 },
+  { "=>",   BRL_CMD_FWINRTSKIP, 1, 2, 0 },
+  { "=>",   BRL_CMD_FWINRTSKIP, 1, 8, 4 },
+  { "-^-",  BRL_CMD_LNUP,       1, 7, 2 },
+  { "-v-",  BRL_CMD_LNDN,       1, 7, 4 },
+  { "->",   BRL_CMD_FWINRT,     1, 8, 3 },
+  { "<-",   BRL_CMD_FWINLT,     1, 6, 3 },
+  { "HOME", BRL_CMD_HOME,       1, 7, 3 },
+  { "^",    BRL_CMD_KEY(CURSOR_UP),    1, 1, 2 },
+  { "v",    BRL_CMD_KEY(CURSOR_DOWN),  1, 1, 4 },
+  { ">",    BRL_CMD_KEY(CURSOR_RIGHT), 1, 2, 3 },
+  { "<",    BRL_CMD_KEY(CURSOR_LEFT),  1, 0, 3 },
+  //{ "B3",   BRL_CMD_CSRVIS,     1, 2, 2 },
+  { "DEL",  BRL_CMD_KEY(DELETE),       1, 0, 4 },
+  { "INS",  BRL_CMD_KEY(INSERT),       1, 2, 4 },
+  //{ "C5",   BRL_CMD_PASTE,      1, 5, 3 },
+  //{ "D5",   EOF,                1, 5, 4 },
+  //{ "B4",   EOF,                1, 3, 2 },
+
+  //{ "B1",   EOF,                1, 0, 2 },
+  //{ "C2",   EOF,                1, 1, 3 },
+  //{ "C4",   EOF,                1, 3, 3 },
+  //{ "D4",   EOF,                1, 3, 4 },
+  { NULL,   0,                  0, 0, 0},
+};
+
+static const struct model models[] = {
+  { "normal",	buttons_simple,	4, 4 },
+  { "vs",	buttons_vs,	9, 5 },
+};
+
+static void setModel(Widget w, XtPointer closure, XtPointer data)
+{
+  intptr_t newModel = (intptr_t) closure;
+  if (newModel == XtNumber(models))
+    keyModel = NULL;
+  else
+    keyModel = &models[newModel];
+  regenerate = 1;
+}
+
+static void createKeyButtons(struct button *buttons) {
+  struct button *b;
+  for (b=buttons; b->label; b++)
+    crKeyBut(b->label, b->keycode, b->repeat, b->x*(BUTWIDTH+1), b->y*(BUTHEIGHT+1));
+}
+
+struct radioInt {
+  const char *name;
+  int value;
+};
+
+static const struct radioInt colsRadio [] = {
+  { "80", 80 },
+  { "60", 60 },
+  { "40", 40 },
+  { "20", 20 },
+  { "8",  8  },
+};
+
+static struct radioInt linesRadio [] = {
+  { "3", 3 },
+  { "2", 2 },
+  { "1", 1 },
+};
+
+static void setWidth(Widget w, XtPointer closure, XtPointer data)
+{
+  intptr_t newCols = (intptr_t) closure;
+  cols = newCols;
+  regenerate = 1;
+}
+
+static void setHeight(Widget w, XtPointer closure, XtPointer data)
+{
+  intptr_t newLines = (intptr_t) closure;
+  lines = newLines;
+  regenerate = 1;
+}
+
+typedef void (*actionfun_t)(Widget, XtPointer, XtPointer);
+
+enum actions {
+  SETMODEL,
+  SETWIDTH,
+  SETHEIGHT,
+};
+
+static actionfun_t actionfun[] = {
+  [SETMODEL] = setModel,
+  [SETWIDTH] = setWidth,
+  [SETHEIGHT] = setHeight,
+};
+
+#if defined(USE_XT)
+#define SET_ACTION(cb, set) \
+  (cb)[0].callback = (XtCallbackProc) actionfun[set]
+#define SET_VALUE(cb, value) \
+  (cb)[0].closure = (void*)(intptr_t) (value)
+#elif defined(USE_WINDOWS)
+#define SET_ACTION(cb, set) \
+  (cb) = (set) << 8
+#define SET_VALUE(cb, value) \
+  (cb) = ((cb) & (~0xff)) | (value)
+#define GET_ACTIONFUN(cbint) \
+  actionfun[(cbint) >> 8]
+#define GET_VALUE(cbint) \
+  ((cbint) & 0xff)
+#else /* USE_ */
+#error Toolkit callback recording unspecified
+#endif /* USE_ */
+  
+#ifdef USE_WINDOWS
+static LRESULT CALLBACK wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+  if (uMsg == WM_COMMAND) {
+    long keypressed;
+    hwnd = GET_WM_COMMAND_HWND(wParam, lParam);
+    keypressed = GetWindowLongPtr(hwnd, GWLP_USERDATA);
+    if (keypressed) {
+      enqueueCommand(keypressed);
+    } else {
+      /* menu entry */
+      GET_ACTIONFUN(wParam)(NULL, (XtPointer)(GET_VALUE(wParam)), NULL);
+    }
+    return 0;
+  }
+  if (uMsg == WM_CONTEXTMENU) {
+    TrackPopupMenu(menu, TPM_LEFTALIGN|TPM_RIGHTBUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, toplevel, NULL);
+    return 0;
+  }
+  return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+#define BRAILLE_USB 82
+int CALLBACK fontEnumProc(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, DWORD FontType, LPARAM lParam) {
+	int shift = 8*sizeof(lpntme->ntmFontSig.fsUsb[0]);
+	if (!(lpntme->ntmFontSig.fsUsb[BRAILLE_USB / shift] &
+		(1 << (BRAILLE_USB % shift))))
+		return 1;
+	font = CreateFont(CHRY-6, CHRX-4, 0, 0, 0, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, (LPCTSTR) lpelfe->elfFullName);
+	if (!font) {
+		logWindowsSystemError("Couldn't load font");
+		logMessage(LOG_ERR,"font %s", lpelfe->elfFullName);
+		return 1;
+	}
+	logMessage(LOG_INFO, "Using braille font `%s\'",lpelfe->elfFullName);
+	return 0;
+}
+#endif /* USE_WINDOWS */
+
+static int brl_readCommand(BrailleDisplay *brl, KeyTableCommandContext context)
+{
+#if defined(USE_XT)
+  while (XtAppPending(app_con)) {
+    XtAppProcessEvent(app_con,XtIMAll);
+    if (XtAppGetExitFlag(app_con))
+      raise(SIGTERM);
+#elif defined(USE_WINDOWS)
+    MSG msg;
+
+    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+      if (msg.message == WM_QUIT
+	  || msg.message == WM_DESTROY
+	  || msg.message == WM_CLOSE)
+	raise(SIGTERM);
+      else {
+	TranslateMessage(&msg);
+	DispatchMessage(&msg);
+      }
+#else /* USE_ */
+#error Toolkit loop unspecified
+#endif /* USE_ */
+    if (regenerate) {
+      regenerate = 0;
+      destroyToplevel();
+      generateToplevel();
+      brl->textColumns = cols;
+      brl->textRows = lines;
+      brl->resizeRequired = 1;
+    }
+  }
+  return EOF;
+}
+
+#ifdef USE_XT
+static char *fallback_resources[] = {
+  "*display.background: lightgreen",
+#ifdef USE_XAW
+  "*displayb.background: black",
+  "*displayb.foreground: white",
+#endif /* USE_XAW */
+  "*keybox.background: lightgrey",
+  "*menu.Label: Brltty",
+  "*menu.background: lightgrey",
+  NULL
+};
+#endif /* USE_XT */
+
+#ifdef USE_XM
+static void popup(Widget w, XEvent *event, String *params, Cardinal *num_params)
+{
+  Widget shell = XtParent(menu);
+  XmMenuPosition(menu, &event->xbutton);
+  XtManageChild(menu);
+  XtPopup(shell, XtGrabNone);
+}
+#endif /* USE_XM */
+
+static int generateToplevel(void)
+{
+#ifdef USE_XT
+  int argc;
+  char **argv;
+#ifdef USE_XAW
+  char *def_string_return;
+  char **missing_charset_list_return;
+  int missing_charset_count_return;
+#endif /* USE_XAW */
+  XtActionsRec actions [] = {
+    { "route", route },
+    { "keypress", keypress },
+#ifdef USE_XM
+    { "popup", popup },
+#endif /* USE_XM */
+    { "Quit", quit },
+    };
+  char translations[] = "<Message>WM_PROTOCOLS: Quit()";
+  char inputActions[] = "\
+:<Key>: keypress()\n\
+:<KeyUp>: keypress()\n";
+  char popupAction[] =
+	"None<Btn3Down>: "
+#if defined(USE_XAW)
+	"XawPositionSimpleMenu(menu) MenuPopup(menu)"
+#elif defined(USE_XM)
+	"popup()"
+#endif /* USE_ */
+	"\n";
+  Widget tmp_vbox;
+  char *disp;
+#ifdef USE_XAW
+  char *dispb;
+#endif /* USE_XAW */
+  XtCallbackRec cb[2] = { { NULL, NULL }, { NULL, NULL } };
+#endif /* USE_XT */
+#ifdef USE_WINDOWS
+  UINT cb = 0;
+#endif /* USE_WINDOWS */
+  const struct radioInt *radioInt;
+  const struct model *radioModel;
+  int y,x;
+
+#if defined(USE_XT)
+  argc = xtArgc;
+  if ((argv = malloc((xtArgc + 1) * sizeof(*xtArgv)))) {
+    memcpy(argv, xtArgv, (xtArgc + 1) * sizeof(*xtArgv));
+
+    /* toplevel */
+    toplevel = XtVaOpenApplication(&app_con, "Brltty",
+      NULL, 0,
+      &argc, argv, fallback_resources,
+      sessionShellWidgetClass,
+      XtNallowShellResize, True,
+      XtNinput, input ? True : False,
+      NULL);
+
+    XtAppAddActions(app_con,actions,XtNumber(actions));
+    XtOverrideTranslations(toplevel,XtParseTranslationTable(translations));
+
+    free(argv);
+  } else {
+    logMallocError();
+    toplevel = NULL;
+  }
+
+#elif defined(USE_WINDOWS)
+  {
+    HWND root = GetDesktopWindow();
+    HDC hdc = GetDC(root);
+    EnumFontFamiliesEx(hdc, NULL, (void*) fontEnumProc, 0, 0);
+    ReleaseDC(root, hdc);
+    if (!font) {
+      logMessage(LOG_ERR,"Error while loading braille font");
+      totlines = lines;
+    } else {
+      totlines = 2*lines;
+    }
+  }
+
+  {
+    WNDCLASS wndclass = {
+      .style = 0,
+      .lpfnWndProc = wndProc,
+      .cbClsExtra = 0,
+      .cbWndExtra = 0,
+      .hInstance = NULL,
+      .hIcon = LoadIcon(NULL, IDI_APPLICATION), /* TODO: nice icon */
+      .hCursor = LoadCursor(NULL, IDC_ARROW),
+      .hbrBackground = NULL,
+      .lpszMenuName = NULL,
+      .lpszClassName = "BRLTTYWClass",
+    };
+    if (!(RegisterClass(&wndclass)) &&
+	GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {
+      logWindowsSystemError("RegisterClass");
+      if (font) {
+	DeleteObject(font);
+	font = NULL;
+      }
+      return 0;
+    }
+    modelWidth = cols*CHRX;
+    if (keyModel) {
+      if (keyModel->width*(BUTWIDTH+1)+1 > modelWidth)
+	modelWidth = keyModel->width *(BUTWIDTH +1)-1;
+      modelHeight = keyModel->height*(BUTHEIGHT+1);
+    } else {
+      modelHeight = 0;
+    }
+    if (!(toplevel = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
+	    "BRLTTYWClass", "BRLTTY",
+	    WS_POPUP, GetSystemMetrics(SM_CXSCREEN)-modelWidth-RIGHTMARGIN, 0,
+	    modelWidth, totlines*CHRY+modelHeight, NULL, NULL, NULL, NULL))) {
+      logWindowsSystemError("CreateWindow");
+      if (font) {
+	DeleteObject(font);
+	font = NULL;
+      }
+      return 0;
+    }
+  }
+#else /* USE_ */
+#error Toolkit toplevel creation unspecified
+#endif /* USE_ */
+
+  /* vertical separation */
+#ifdef USE_XT
+  vbox = XtVaCreateManagedWidget("vbox",panedWidgetClass,toplevel,
+#ifdef USE_XM
+    XmNmarginHeight, 0,
+    XmNmarginWidth, 0,
+    XmNspacing, 1,
+#endif /* USE_XM */
+    XtNresize, True,
+    XtNtranslations, XtParseTranslationTable(popupAction),
+    NULL);
+  if (input)
+    XtAugmentTranslations(vbox, XtParseTranslationTable(inputActions));
+#endif /* USE_XT */
+
+#ifdef USE_XAW
+  if (!(fontset = XCreateFontSet(XtDisplay(toplevel), fontname, &missing_charset_list_return, &missing_charset_count_return, &def_string_return)))
+    logMessage(LOG_ERR,"Error while loading unicode font");
+  if (missing_charset_count_return) {
+    int i;
+    for (i=0; i<missing_charset_count_return; i++)
+      logMessage(LOG_INFO,"Could not load a unicode font for charset %s",missing_charset_list_return[i]);
+    XFreeStringList(missing_charset_list_return);
+  }
+#endif /* USE_XAW */
+  
+#ifdef USE_XT
+  /* horizontal separation */
+  hbox = XtVaCreateManagedWidget("hbox",panedWidgetClass,vbox,
+    XtNorientation, XtEhorizontal,
+#ifdef USE_XM
+    XmNmarginHeight, 0,
+    XmNmarginWidth, 0,
+    XmNspacing, 0,
+#endif /* USE_XM */
+#ifdef USE_XAW
+    XtNshowGrip,False,
+#else /* USE_XAW */
+    XmNpaneMaximum,20*lines,
+    XmNpaneMinimum,20*lines,
+    XmNskipAdjust, True,
+#endif /* USE_XAW */
+    XtNresize, True,
+    NULL);
+
+  /* display Label */
+  disp=XtMalloc(2);
+  disp[0]=' ';
+  disp[1]=0;
+
+#ifdef USE_XAW
+  dispb=XtMalloc(4);
+  dispb[0]=0xe0|((0x28>>4)&0x0f);
+  dispb[1]=0x80|((0x28<<2)&0x3f);
+  dispb[2]=0x80;
+  dispb[3]=0;
+#endif /* USE_XAW */
+
+#ifdef USE_XM
+  display_cs = XmStringCreateLocalized(disp);
+#endif /* USE_XM */
+#endif /* USE_XT */
+
+#ifdef USE_WINDOWS
+  hbox = CreateWindow(WC_STATIC, "", WS_CHILD | WS_VISIBLE, 0, 0,
+       modelWidth, totlines*CHRY+modelHeight, toplevel, NULL, NULL, NULL);
+#endif /* USE_WINDOWS */
+
+  for (x=0;x<cols;x++) {
+#ifdef USE_XT
+    /* vertical separation */
+    tmp_vbox = XtVaCreateManagedWidget("tmp_vbox",panedWidgetClass,hbox,
+#ifdef USE_XAW
+      XtNshowGrip,False,
+#else /* USE_XAW */
+      XmNpaneMaximum,20,
+      XmNpaneMinimum,20,
+      XmNskipAdjust, True,
+#endif /* USE_XAW */
+#ifdef USE_XM
+      XmNmarginHeight, 0,
+      XmNmarginWidth, 0,
+      XmNspacing, 0,
+#endif /* USE_XM */
+      XtNresize, True,
+      NULL);
+#endif /* USE_XT */
+
+    for (y=0;y<lines;y++) {
+#if defined(USE_XT)
+      char action[] = "<Btn1Up>: route(100)";
+      XtTranslations transl;
+
+      snprintf(action,sizeof(action),"<Btn1Up>: route(%u)",y*cols+x);
+      transl = XtParseTranslationTable(action);
+
+      display[y*cols+x] = XtVaCreateManagedWidget("display",labelWidgetClass,tmp_vbox,
+	XtNtranslations, transl,
+#ifdef USE_XAW
+	XtNshowGrip,False,
+	XtNinternational, True,
+#else /* USE_XAW */
+	XmNpaneMaximum,20,
+	XmNpaneMinimum,20,
+	XmNskipAdjust, True,
+#endif /* USE_XAW */
+#ifdef USE_XAW
+	XtNlabel, disp,
+	fontset ? XNFontSet : NULL, fontset, NULL
+#else /* USE_XAW */
+	XmNlabelString, display_cs, NULL
+#endif /* USE_XAW */
+	);
+
+#ifdef USE_XAW
+      if (fontset) {
+	displayb[y*cols+x] = XtVaCreateManagedWidget("displayb",labelWidgetClass,tmp_vbox,
+	  XtNtranslations, transl,
+	  XtNinternational, True,
+	  XNFontSet, fontset,
+	  XtNshowGrip,False,
+	  XtNlabel, dispb,
+	  NULL);
+      }
+#endif /* USE_XAW */
+#elif defined(USE_WINDOWS)
+      display[y*cols+x] = CreateWindow(WC_BUTTON, " ", WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_PUSHLIKE, x*CHRX, y*CHRY, CHRX, CHRY, toplevel, NULL, NULL, NULL);
+      SetWindowLongPtr(display[y*cols+x], GWLP_USERDATA, (LONG_PTR) (BRL_CMD_BLK(ROUTE) | ((y*cols+x)&BRL_MSK_ARG)));
+      if (font) {
+        displayb[y*cols+x] = CreateWindowW(WC_BUTTONW, WS_C(" "), WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BS_PUSHLIKE, x*CHRX, (lines+y)*CHRY, CHRX, CHRY, toplevel, NULL, NULL, NULL);
+        SetWindowLongPtr(displayb[y*cols+x], GWLP_USERDATA, (LONG_PTR) (BRL_CMD_BLK(ROUTE) | ((y*cols+x)&BRL_MSK_ARG)));
+	SendMessage(displayb[y*cols+x], WM_SETFONT, (WPARAM) font, TRUE);
+      }
+#else /* USE_ */
+#error Toolkit display unspecified
+#endif /* USE_ */
+    }
+  }
+#ifdef USE_XT
+#ifdef USE_XM
+  XmStringFree(display_cs);
+#endif /* USE_XM */
+  XtFree(disp);
+#ifdef USE_XAW
+  XtFree(dispb);
+#endif /* USE_XAW */
+#endif /* USE_XT */
+#ifdef USE_XT
+  XtVaGetValues(display[0],
+    XtNforeground, &displayForeground,
+    XtNbackground, &displayBackground,
+    NULL);
+#endif /* USE_XT */
+
+  if (keyModel) {
+    /* key box */
+#ifdef USE_XT
+    keybox = XtVaCreateManagedWidget("keybox",formWidgetClass,vbox,
+#ifdef USE_XAW
+      XtNdefaultDistance,0,
+#endif /* USE_XAW */
+      NULL);
+#endif /* USE_XT */
+    createKeyButtons(keyModel->buttons);
+  }
+
+  menu = CreatePopupMenu("menu", toplevel);
+
+#ifdef USE_XAW
+  if (!check) {
+    static unsigned char checkimg [] = {
+      0x00, 0x00, 0xc0, 0x60, 0x33, 0x1e, 0x0c, 0x00
+    };
+    check = XCreateBitmapFromData(XtDisplay(toplevel),
+	RootWindowOfScreen(XtScreen(toplevel)), (char *) checkimg, 8, 8);
+  }
+#endif /* USE_XAW */
+
+#ifdef USE_XAW
+  AddMenuSeparator("WidthLine", menu);
+#endif /* USE_XAW */
+  AddMenuLabel("Width", menu);
+  SET_ACTION(cb, SETWIDTH);
+  for (radioInt = colsRadio; radioInt < &colsRadio[XtNumber(colsRadio)]; radioInt++) {
+    SET_VALUE(cb, radioInt->value);
+    AddMenuRadio(radioInt->name, menu, cb, radioInt->value == cols);
+  }
+
+  AddMenuSeparator("HeightLine", menu);
+  AddMenuLabel("Height", menu);
+  SET_ACTION(cb, SETHEIGHT);
+  for (radioInt = linesRadio; radioInt < &linesRadio[XtNumber(linesRadio)]; radioInt++) {
+    SET_VALUE(cb, radioInt->value);
+    AddMenuRadio(radioInt->name, menu, cb, radioInt->value == lines);
+  }
+
+  AddMenuSeparator("ModelLine", menu);
+  AddMenuLabel("Model", menu);
+  SET_ACTION(cb, SETMODEL);
+  for (radioModel = models; radioModel < &models[XtNumber(models)]; radioModel++) {
+    SET_VALUE(cb, radioModel-models);
+    AddMenuRadio(radioModel->name, menu, cb, radioModel == keyModel);
+  }
+
+  SET_VALUE(cb, XtNumber(models));
+  AddMenuRadio("bare", menu, cb, !keyModel);
+
+  /* go go go */
+#if defined(USE_XT)
+  XtRealizeWidget(toplevel);
+  if (!wm_delete_window)
+    wm_delete_window = XInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW", False);
+  XSetWMProtocols(XtDisplay(toplevel),XtWindow(toplevel),&wm_delete_window,1);
+#elif defined(USE_WINDOWS)
+  ShowWindow(toplevel, SW_SHOWDEFAULT);
+  UpdateWindow(toplevel);
+#else /* USE_ */
+#error Toolkit toplevel realization unspecified
+#endif /* USE_ */
+#if defined(USE_XAW) || defined(USE_WINDOWS)
+  memset(displayedWindow,0,sizeof(displayedWindow));
+#endif /* USE_XAW || USE_WINDOWS */
+  memset(displayedVisual,0,sizeof(displayedVisual));
+  lastcursor = BRL_NO_CURSOR;
+  return 1;
+}
+
+static int brl_construct(BrailleDisplay *brl, char **parameters, const char *device)
+{
+  lines=1;
+  if (*parameters[PARM_LINES]) {
+    static const int minimum = 1;
+    static const int maximum = MAXLINES;
+    int value;
+    if (validateInteger(&value, parameters[PARM_LINES], &minimum, &maximum)) {
+      lines=value;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid line count", parameters[PARM_LINES]);
+    }
+  }
+
+  cols=40;
+  if (*parameters[PARM_COLUMNS]) {
+    static const int minimum = 1;
+    static const int maximum = MAXCOLS;
+    int value;
+    if (validateInteger(&value, parameters[PARM_COLUMNS], &minimum, &maximum)) {
+      cols=value;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid column count", parameters[PARM_COLUMNS]);
+    }
+  }
+
+  if (*parameters[PARM_INPUT]) {
+    unsigned int value;
+    if (validateOnOff(&value, parameters[PARM_INPUT])) {
+      input = value;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid input setting", parameters[PARM_INPUT]);
+    }
+  }
+
+  if (*parameters[PARM_TKPARMS]) {
+    int reallocated = 0;
+
+    {
+      int count;
+      char **args1 = splitString(parameters[PARM_TKPARMS], ' ', &count);
+
+      if (args1) {
+        char **args2 = realloc(args1, (count+2) * sizeof(char *));
+
+        if (args2) {
+          char *name = strdup(xtDefArgv[0]);
+
+          args1 = NULL;
+
+          if (name) {
+            memmove(args2+1, args2, (count+1) * sizeof(char *));
+            args2[0] = name;
+            count += 1;
+
+            if (xtArgv != xtDefArgv) deallocateStrings(xtArgv);
+            xtArgv = args2;
+            xtArgc = count;
+            args2 = NULL;
+
+            reallocated = 1;
+          } else {
+            logMallocError();
+          }
+
+          if (args2) deallocateStrings(args2);
+        } else {
+          logMallocError();
+        }
+
+        if (args1) deallocateStrings(args1);
+      }
+    }
+
+    if (!reallocated) return 0;
+  }
+
+  if (*parameters[PARM_MODEL]) {
+    model = parameters[PARM_MODEL];
+    for (keyModel = models; keyModel < &models[XtNumber(models)] && strcmp(keyModel->name,model); keyModel++);
+    if (keyModel == &models[XtNumber(models)]) keyModel = NULL;
+  }
+
+  if (*parameters[PARM_FONT]) {
+    fontname = parameters[PARM_FONT];
+  }
+
+#if defined(USE_XT)
+  XtToolkitThreadInitialize();
+  XtSetLanguageProc(NULL, NULL, NULL);
+#endif /* USE_XT */ 
+
+  brl->textColumns=cols;
+  brl->textRows=lines;
+
+  return generateToplevel();
+}
+static void destroyToplevel(void)
+{
+#if defined(USE_XT)
+#ifdef USE_XAW
+  if (fontset) {
+    XFreeFontSet(XtDisplay(toplevel),fontset);
+    fontset = NULL;
+  }
+  check = None;
+#endif /* USE_XAW */
+  XtDestroyApplicationContext(app_con);
+  app_con = NULL;
+#elif defined(USE_WINDOWS)
+  DestroyMenu(menu);
+  if (!DestroyWindow(toplevel))
+    logWindowsSystemError("DestroyWindow");
+  if (font) {
+    DeleteObject(font);
+    font = NULL;
+  }
+#else /* USE_ */
+#error Toolkit toplevel destruction unspecified
+#endif /* USE_ */
+}
+
+static void brl_destruct(BrailleDisplay *brl)
+{
+  destroyToplevel();
+}
+
+static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  unsigned int from, to;
+  wchar_t wc;
+  int i;
+#ifdef USE_XM
+  char data[2];
+#elif defined(USE_XAW)
+  Utf8Buffer utf8;
+#elif defined(USE_WINDOWS)
+  wchar_t data[3];
+#endif
+
+  if (lastcursor != brl->cursor) {
+    if (lastcursor != BRL_NO_CURSOR) {
+#if defined(USE_XT)
+      XtVaSetValues(display[lastcursor],
+	XtNforeground, displayForeground,
+	XtNbackground, displayBackground,
+	NULL);
+#elif defined(USE_WINDOWS)
+      SendMessage(display[lastcursor],BM_SETSTATE,FALSE,0);
+#else /* USE_ */
+#error Toolkit cursor not specified
+#endif /* USE_ */
+    }
+    lastcursor = brl->cursor;
+    if (lastcursor != BRL_NO_CURSOR) {
+#if defined(USE_XT)
+      XtVaSetValues(display[lastcursor],
+	XtNforeground, displayBackground,
+	XtNbackground, displayForeground,
+	NULL);
+#elif defined(USE_WINDOWS)
+      SendMessage(display[lastcursor],BM_SETSTATE,TRUE,0);
+#else /* USE_ */
+#error Toolkit cursor not specified
+#endif /* USE_ */
+    }
+  }
+
+  if (text && wmemcmp(text,displayedVisual,brl->textRows*brl->textColumns)) {
+    for (i=0;i<brl->textRows*brl->textColumns;i++) {
+      if (displayedVisual[i] != text[i]) {
+	wc = text[i];
+	if (wc == 0) wc = WC_C(' ');
+#ifdef USE_XM
+	if (wc < 0x100)
+	  data[0] = wc;
+	else
+	  data[0] = '?';
+	data[1] = 0;
+#elif defined(USE_XAW)
+	convertWcharToUtf8(wc, utf8);
+#elif defined(USE_WINDOWS)
+	data[0] = wc;
+	if (data[0]==WC_C('&')) {
+	  data[1] = WC_C('&');
+	  data[2] = 0;
+	} else
+	  data[1]=0;
+#else /* USE_ */
+#error Toolkit cursor not specified
+#endif /* USE_ */
+
+#if defined(USE_XT)
+#ifdef USE_XM
+	display_cs = XmStringCreateLocalized(data);
+#endif /* USE_XM */
+	XtVaSetValues(display[i],
+#ifdef USE_XAW
+	  XtNlabel, utf8,
+#else /* USE_XAW */
+	  XmNlabelString, display_cs,
+#endif /* USE_XAW */
+	  NULL);
+#ifdef USE_XM
+	XmStringFree(display_cs);
+#endif /* USE_XM */
+#elif defined(USE_WINDOWS)
+	SetWindowTextW(display[i],data);
+#else /* USE_ */
+#error Toolkit display refresh unspecified
+#endif /* USE_ */
+	displayedVisual[i] = text[i];
+      }
+    }
+  }
+
+#if defined(USE_XAW) || defined(USE_WINDOWS)
+  if (!cellsHaveChanged(displayedWindow,brl->buffer,brl->textRows*brl->textColumns,&from,&to,NULL) || !displayb[0]) return 1;
+
+  for (i=from;i<to;i++) {
+    unsigned char c = brl->buffer[i];
+    c =
+       (!!(c&BRL_DOT1))<<0
+      |(!!(c&BRL_DOT2))<<1
+      |(!!(c&BRL_DOT3))<<2
+      |(!!(c&BRL_DOT4))<<3
+      |(!!(c&BRL_DOT5))<<4
+      |(!!(c&BRL_DOT6))<<5
+      |(!!(c&BRL_DOT7))<<6
+      |(!!(c&BRL_DOT8))<<7;
+#ifdef USE_XAW
+    convertWcharToUtf8(UNICODE_BRAILLE_ROW | c, utf8);
+
+    XtVaSetValues(displayb[i], XtNlabel, utf8, NULL);
+#elif defined(USE_WINDOWS)
+    data[0] = UNICODE_BRAILLE_ROW | c;
+    data[1] = 0;
+    SetWindowTextW(displayb[i],data);
+#endif /* USE_WINDOWS */
+  }
+#endif /* USE_XAW || USE_WINDOWS */
+  return 1;
+}
diff --git a/Drivers/Braille/XWindow/braille.h b/Drivers/Braille/XWindow/braille.h
new file mode 100644
index 0000000..1cb5e77
--- /dev/null
+++ b/Drivers/Braille/XWindow/braille.h
@@ -0,0 +1,17 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
diff --git a/Drivers/BrlAPI/WindowEyes/Makefile.in b/Drivers/BrlAPI/WindowEyes/Makefile.in
new file mode 100644
index 0000000..7c428b1
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/Makefile.in
@@ -0,0 +1,40 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+WE_OBJS = webrloem.$O
+WE_NAME = webrloem109
+WE_LIB = $(WE_NAME).$(LIB_EXT)
+WE_DLL = $(WE_NAME).$(LIB_EXT)
+WE_IMPLIB = $(ARC_PFX)$(WE_NAME).$(ARC_EXT)
+WE_IMPLIB_VERSIONED = $(ARC_PFX)$(WE_NAME).$(ARC_EXT)
+WE_DEF = $(WE_NAME).def
+
+we: $(WE_DYNAMIC_LIBRARY)
+
+we-dynamic-library-windows: $(WE_DLL)
+$(WE_DLL): $(WE_OBJS) brlapi
+	-rm -f implib.a lib.def
+	$(MKLIB:<name>=${WE_LIB}) $@ $(WE_OBJS) $(API_LIBS)
+	[ ! -f implib.a ] || mv implib.a $(WE_IMPLIB)
+	[ ! -f lib.def ] || mv lib.def $(WE_DEF)
+
+webrloem.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/webrloem.c
+
+test$X: we-dynamic-library-windows $(SRC_DIR)/test.c
+	$(CC) $(LDFLAGS) -o $@ $(SRC_DIR)/test.c -L. -l$(WE_NAME) $(LDLIBS)
diff --git a/Drivers/BrlAPI/WindowEyes/README b/Drivers/BrlAPI/WindowEyes/README
new file mode 100644
index 0000000..f6f684f
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/README
@@ -0,0 +1,35 @@
+Starting from version 7.0, WindowEyes can use OEM-provided drivers, this is
+such driver for making it use the BrlAPI interface.
+
+It can be added by
+* copying bin/webrloem109.dll and bin/brlapi-0.5.dll to the window eyes
+directory, along wineyes.exe,
+* copy/pasting the content of braille.ini into your braille.ini (found in the
+Application Data\GW Micro\Window-Eyes/users/default/ ), and in the same file,
+appending the following two lines to the [BrailleDisplays] section:
+
+brl##=oem109
+brl###=oem109-raw
+
+where ## and ### are the numbers right after the last one of that section, e.g.
+if there was
+
+brl55=zephyr
+
+then these lines should be added:
+
+brl56=oem109
+brl57=oem109-raw
+
+Restart WindowEyes, two BrlAPI choices should now appear last in the list.
+
+- BrlAPI generic should work with any device, as it binds the brltty commands to
+  WindowEyes commands.
+- BrlAPI device-specific permits to define device-specific bindings: WindowEyes
+  will get keyboard keycode as such, the user can then bind them at will from
+  the WindowEyes interface.
+
+Note that here the Com1/Com2 parameter does not refer to an actual serial
+device (the serial/usb configuration shall be done in brltty.conf), it refers to
+how keycodes should be reported to WindowEyes: Com1 should always be used for
+BrlAPI generic, and Com2 for BrlAPI device-specific.
diff --git a/Drivers/BrlAPI/WindowEyes/braille.ini b/Drivers/BrlAPI/WindowEyes/braille.ini
new file mode 100644
index 0000000..53fbc2d
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/braille.ini
@@ -0,0 +1,139 @@
+[oem109]
+Name=BrlAPI generic
+Type=109
+b1=go up one line
+b2=go down one line
+b3=go up several lines
+b4=go down several lines
+b5=go up to nearest line with different content
+b6=go down to nearest line with different content
+b7=go up to nearest line with different highlighting
+b8=go down to nearest line with different highlighting
+b9=go to top line
+b10=go to bottom line
+b11=go to beginning of top line
+b12=go to beginning of bottom line
+b13=go up to last line of previous paragraph
+b14=go down to first line of next paragraph
+b15=go up to previous command prompt
+b16=go down to next command prompt
+b17=go left one character
+b18=go right one character
+b19=go left half a window
+b20=go right half a window
+b21=go left one window
+b22=go right one window
+b23=go left to nearest non-blank window
+b24=go right to nearest non-blank window
+b25=go to beginning of line
+b26=go to end of line
+b27=go to cursor
+b28=go back (undo unexpected cursor tracking motion)
+b29=toggle screen mode frozen/live
+b30=toggle display mode attributes/text
+b31=toggle text style 6-dot/8-dot
+b32=toggle sliding window on/off
+b33=toggle skipping of lines with identical content on/off
+b34=toggle skipping of blank windows on/off
+b35=toggle cursor visibility on/off
+b36=toggle cursor tracking on/off
+b37=toggle cursor style block/underline
+b38=toggle cursor blinking on/off
+b39=toggle attribute underlining on/off
+b40=toggle attribute blinking on/off
+b41=toggle capital letter blinking on/off
+b42=toggle alert tunes on/off
+b43=toggle autorepeat on/off
+b44=toggle autospeak on/off
+b45=enter/leave help display
+b46=enter/leave status display
+b47=enter/leave command learn mode
+b48=enter/leave preferences menu
+b49=save current preferences
+b50=restore saved preferences
+b51=stop speaking immediately
+b52=go to current (most recent) speech position
+b53=speak current line
+b54=speak from top of screen through current line
+b55=speak from current line through bottom of screen
+b56=decrease speech rate
+b57=increase speech rate
+b58=decrease speech volume
+b59=increase speech volume
+b60=switch to previous virtual terminal
+b61=switch to next virtual terminal
+b62=bring cursor to line (no horizontal motion)
+b63=reinitialize braille driver
+b64=reinitialize speech driver
+brailleport=Com1
+action1=1048576;0;1003
+action1=2097152;0;1004
+
+[oem109-raw]
+Name=BrlAPI device-specific
+Type=109
+b1=b0
+b2=b1
+b3=b2
+b4=b3
+b5=b4
+b6=b5
+b7=b6
+b8=b7
+b9=b8
+b10=b9
+b11=b10
+b12=b11
+b13=b12
+b14=b13
+b15=b14
+b16=b15
+b17=b16
+b18=b17
+b19=b18
+b20=b19
+b21=b20
+b22=b21
+b23=b22
+b24=b23
+b25=b24
+b26=b25
+b27=b26
+b28=b27
+b29=b28
+b30=b29
+b31=b30
+b32=b31
+b33=b32
+b34=b33
+b35=b34
+b36=b35
+b37=b36
+b38=b37
+b39=b38
+b40=b39
+b41=b40
+b42=b41
+b43=b42
+b44=b43
+b45=b44
+b46=b45
+b47=b46
+b48=b47
+b49=b48
+b50=b49
+b51=b50
+b52=b51
+b53=b52
+b54=b53
+b55=b54
+b56=b55
+b57=b56
+b58=b57
+b59=b58
+b60=b59
+b61=b60
+b62=b61
+b63=b62
+b64=b63
+brailleport=Com2
diff --git a/Drivers/BrlAPI/WindowEyes/test.c b/Drivers/BrlAPI/WindowEyes/test.c
new file mode 100644
index 0000000..6b832e6
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/test.c
@@ -0,0 +1,55 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 2009-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <windows.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "webrloem.h"
+
+int main(void) {
+  DWORD handle;
+  DWORD key1, key2;
+  int route, route2;
+  bool status;
+  int x, s;
+  unsigned char test[] ={0, 1, 2, 4, 8, 16, 32, 64, 128, 255};
+  printf("connecting\n");
+  assert(WEBrailleOpen(0, 0, &handle));
+  printf("getting info\n");
+  assert(WEGetBrailleDisplayInfo(handle, &x, &s));
+  printf("display %d+%d\n", x, s);
+  assert(WEUpdateBrailleDisplay(handle, test, sizeof(test), NULL, 0));
+  printf("wrote\n");
+  while (!WEGetBrailleKey(handle, &key1, &key2, &route, &route2, &status))
+    Sleep(50);
+  printf("key %08lx%08lx %d %d %d\n", key2, key1, route, route2, status);
+  while (!WEGetBrailleKey(handle, &key1, &key2, &route, &route2, &status))
+    Sleep(50);
+  printf("key %08lx%08lx %d %d %d\n", key2, key1, route, route2, status);
+  while (!WEGetBrailleKey(handle, &key1, &key2, &route, &route2, &status))
+    Sleep(50);
+  printf("key %08lx%08lx %d %d %d\n", key2, key1, route, route2, status);
+  while (!WEGetBrailleKey(handle, &key1, &key2, &route, &route2, &status))
+    Sleep(50);
+  printf("key %08lx%08lx %d %d %d\n", key2, key1, route, route2, status);
+  assert(WEBrailleClose(handle));
+  return 0;
+}
diff --git a/Drivers/BrlAPI/WindowEyes/webrloem.c b/Drivers/BrlAPI/WindowEyes/webrloem.c
new file mode 100644
index 0000000..324a4b2
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/webrloem.c
@@ -0,0 +1,244 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define BRLAPI_NO_SINGLE_SESSION
+#define BRLAPI_NO_DEPRECATED
+
+#include <windows.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "brlapi.h"
+
+#include "webrloem.h"
+
+#define C(cmd) (BRLAPI_KEY_CMD_##cmd & BRLAPI_KEY_CMD_ARG_MASK)
+
+static const unsigned command_button[] = {
+  [ C(LNUP) ] =			1  /* go up one line */,
+  [ C(LNDN) ] =			2  /* go down one line */,
+  [ C(WINUP) ] =		3  /* go up several lines */,
+  [ C(WINDN) ] =		4  /* go down several lines */,
+  [ C(PRDIFLN) ] =		5  /* go up to nearest line with different content */,
+  [ C(NXDIFLN) ] =		6  /* go down to nearest line with different content */,
+  [ C(ATTRUP) ] =		7  /* go up to nearest line with different highlighting */,
+  [ C(ATTRDN) ] =		8  /* go down to nearest line with different highlighting */,
+  [ C(TOP) ] =			9  /* go to top line */,
+  [ C(BOT) ] =			10 /* go to bottom line */,
+  [ C(TOP_LEFT) ] =		11 /* go to beginning of top line */,
+  [ C(BOT_LEFT) ] =		12 /* go to beginning of bottom line */,
+  [ C(PRPGRPH) ] =		13 /* go up to last line of previous paragraph */,
+  [ C(NXPGRPH) ] =		14 /* go down to first line of next paragraph */,
+  [ C(PRPROMPT) ] =		15 /* go up to previous command prompt */,
+  [ C(NXPROMPT) ] =		16 /* go down to next command prompt */,
+  [ C(CHRLT) ] =		17 /* go left one character */,
+  [ C(CHRRT) ] =		18 /* go right one character */,
+  [ C(HWINLT) ] =		19 /* go left half a window */,
+  [ C(HWINRT) ] =		20 /* go right half a window */,
+  [ C(FWINLT) ] =		21 /* go left one window */,
+  [ C(FWINRT) ] =		22 /* go right one window */,
+  [ C(FWINLTSKIP) ] =		23 /* go left to nearest non-blank window */,
+  [ C(FWINRTSKIP) ] =		24 /* go right to nearest non-blank window */,
+  [ C(LNBEG) ] =		25 /* go to beginning of line */,
+  [ C(LNEND) ] =		26 /* go to end of line */,
+  [ C(HOME) ] =			27 /* go to cursor */,
+  [ C(BACK) ] =			28 /* go back (undo unexpected cursor tracking motion) */,
+  [ C(FREEZE) ] =		29 /* toggle screen mode frozen/live */,
+  [ C(DISPMD) ] =		30 /* toggle display mode attributes/text */,
+  [ C(SIXDOTS) ] =		31 /* toggle text style 6-dot/8-dot */,
+  [ C(SLIDEWIN) ] =		32 /* toggle sliding window on/off */,
+  [ C(SKPIDLNS) ] =		33 /* toggle skipping of lines with identical content on/off */,
+  [ C(SKPBLNKWINS) ] =		34 /* toggle skipping of blank windows on/off */,
+  [ C(CSRVIS) ] =		35 /* toggle cursor visibility on/off */,
+  [ C(CSRTRK) ] =		36 /* toggle cursor tracking on/off */,
+  [ C(CSRSIZE) ] =		37 /* toggle cursor style block/underline */,
+  [ C(CSRBLINK) ] =		38 /* toggle cursor blinking on/off */,
+  [ C(ATTRVIS) ] =		39 /* toggle attribute underlining on/off */,
+  [ C(ATTRBLINK) ] =		40 /* toggle attribute blinking on/off */,
+  [ C(CAPBLINK) ] =		41 /* toggle capital letter blinking on/off */,
+  [ C(TUNES) ] =		42 /* toggle alert tunes on/off */,
+  [ C(AUTOREPEAT) ] =		43 /* toggle autorepeat on/off */,
+  [ C(AUTOSPEAK) ] =		44 /* toggle autospeak on/off */,
+  [ C(HELP) ] =			45 /* enter/leave help display */,
+  [ C(INFO) ] =			46 /* enter/leave status display */,
+  [ C(LEARN) ] =		47 /* enter/leave command learn mode */,
+  [ C(PREFMENU) ] =		48 /* enter/leave preferences menu */,
+  [ C(PREFSAVE) ] =		49 /* save current preferences */,
+  [ C(PREFLOAD) ] =		50 /* restore saved preferences */,
+  [ C(MUTE) ] =			51 /* stop speaking immediately */,
+  [ C(SPKHOME) ] =		52 /* go to current (most recent) speech position */,
+  [ C(SAY_LINE) ] =		53 /* speak current line */,
+  [ C(SAY_ABOVE) ] =		54 /* speak from top of screen through current line */,
+  [ C(SAY_BELOW) ] =		55 /* speak from current line through bottom of screen */,
+  [ C(SAY_SLOWER) ] =		56 /* decrease speech rate */,
+  [ C(SAY_FASTER) ] =		57 /* increase speech rate */,
+  [ C(SAY_SOFTER) ] =		58 /* decrease speech volume */,
+  [ C(SAY_LOUDER) ] =		59 /* increase speech volume */,
+  [ C(SWITCHVT_PREV) ] =	60 /* switch to previous virtual terminal */,
+  [ C(SWITCHVT_NEXT) ] =	61 /* switch to next virtual terminal */,
+  [ C(CSRJMP_VERT) ] =		62 /* bring cursor to line (no horizontal motion) */,
+  [ C(RESTARTBRL) ] =		63 /* reinitialize braille driver */,
+  [ C(RESTARTSPEECH) ] =	64 /* reinitialize speech driver */,
+};
+
+int raw;
+
+bool WEBrailleOpen(int portType, int portNumber, DWORD *handle)
+{
+  brlapi_handle_t *brl_handle = malloc(brlapi_getHandleSize());
+  brlapi_range_t ranges[] = {
+    {
+      .first = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ROUTE,
+      .last = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ROUTE | BRLAPI_KEY_CMD_ARG_MASK,
+    }
+  };
+  int i,j;
+  brlapi_range_t *cmd_ranges;
+
+  if (brlapi__openConnection(brl_handle, NULL, NULL) == INVALID_HANDLE_VALUE)
+    goto error;
+
+  raw = portNumber == 2;
+  
+  if (raw) {
+    char name[BRLAPI_MAXNAMELENGTH+1];
+    if (brlapi__getDriverName(brl_handle, name, sizeof(name)) == -1)
+      goto error;
+    if (brlapi__enterTtyModeWithPath(brl_handle, NULL, 0, name) == -1)
+      goto error;
+  } else {
+    if (brlapi__enterTtyModeWithPath(brl_handle, NULL, 0, NULL) == -1)
+      goto error;
+  }
+  if (brlapi__ignoreAllKeys(brl_handle) == -1)
+    goto error;
+  if (brlapi__acceptKeyRanges(brl_handle, ranges, sizeof(ranges)/sizeof(ranges[0])) == -1)
+    goto error;
+
+  cmd_ranges = malloc(64 * sizeof(cmd_ranges[0]));
+  for (i = 0, j = 0; i < sizeof(command_button)/sizeof(command_button[0]); i++) {
+    if (command_button[i]) {
+      cmd_ranges[j].first = cmd_ranges[j].last = BRLAPI_KEY_TYPE_CMD | (BRLAPI_KEY_CMD(0) + command_button[i]);
+      j++;
+    }
+  }
+  if (brlapi__acceptKeyRanges(brl_handle, cmd_ranges, j) == -1)
+    goto error;
+  free(cmd_ranges);
+
+  *handle = (DWORD) brl_handle;
+  return TRUE;
+
+error:
+  free(brl_handle);
+  return FALSE;
+}
+
+bool WEBrailleClose(DWORD handle)
+{
+  brlapi_handle_t *brl_handle = (void*) handle;
+  brlapi__closeConnection(brl_handle);
+  return TRUE;
+}
+
+bool WEGetBrailleDisplayInfo(DWORD handle, int *numberOfCells, int *numberOfStatusCells)
+{
+  brlapi_handle_t *brl_handle = (void*) handle;
+  unsigned int x, y;
+  
+  if (brlapi__getDisplaySize(brl_handle, &x, &y) == -1)
+    return FALSE;
+
+  *numberOfCells = x * y;
+  *numberOfStatusCells = 0;
+  return TRUE;
+}
+
+static bool keysent = FALSE;
+
+bool WEGetBrailleKey(DWORD handle, DWORD *key1, DWORD *key2, int *routingKey, int *doubleRoutingKey, bool *routingOverStatusCell)
+{
+  brlapi_handle_t *brl_handle = (void*) handle;
+  brlapi_keyCode_t code;
+  unsigned cmd;
+
+  *key1 = 0;
+  *key2 = 0;
+  *routingKey = 0;
+  *doubleRoutingKey = 0;
+  *routingOverStatusCell = FALSE;
+
+  if (keysent) {
+    keysent = FALSE;
+    return TRUE;
+  }
+
+  if (brlapi__readKey(brl_handle, 0, &code) != 1)
+    return FALSE;
+
+  if (raw) {
+    keysent = TRUE;
+    *key1 = code & 0xffffffffu;
+    *key2 = code >> 32;
+    return TRUE;
+  }
+
+  if ((code & BRLAPI_KEY_TYPE_MASK) != BRLAPI_KEY_TYPE_CMD)
+    return FALSE;
+
+  cmd = code & BRLAPI_KEY_CMD_BLK_MASK;
+
+  if (cmd == BRLAPI_KEY_CMD_ROUTE) {
+    *routingKey = (code & BRLAPI_KEY_CMD_ARG_MASK) + 1;
+  } else {
+    unsigned button;
+    unsigned arg = code & BRLAPI_KEY_CMD_ARG_MASK;
+
+    if (arg >= sizeof(command_button)/sizeof(command_button[0]))
+      return FALSE;
+
+    button = command_button[arg];
+    if ((button > 64) || (button == 0))
+      return FALSE;
+    if (button > 32)
+      *key2 = 1 << (button - 1 - 32);
+    else
+      *key1 = 1 << (button - 1);
+  }
+
+  keysent = TRUE;
+  return TRUE;
+}
+
+bool WEUpdateBrailleDisplay(DWORD handle, BYTE *pMainCells, int mainCellsCount, BYTE *pStatusCells, int statusCellsCount)
+{
+  brlapi_handle_t *brl_handle = (void*) handle;
+  int numCells, numStatCells;
+
+  if (!WEGetBrailleDisplayInfo(handle, &numCells, &numStatCells))
+    return FALSE;
+
+  {
+    unsigned char dots[numCells];
+    memcpy(dots, pMainCells, mainCellsCount);
+    memset(dots + mainCellsCount, 0, numCells - mainCellsCount);
+    if (brlapi__writeDots(brl_handle, dots) == -1)
+      return FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/Drivers/BrlAPI/WindowEyes/webrloem.h b/Drivers/BrlAPI/WindowEyes/webrloem.h
new file mode 100644
index 0000000..8b344e2
--- /dev/null
+++ b/Drivers/BrlAPI/WindowEyes/webrloem.h
@@ -0,0 +1,23 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+extern bool WEBrailleOpen(int, int, DWORD *);
+extern bool WEGetBrailleKey(DWORD, DWORD *, DWORD *, int *, int *, bool *);
+extern bool WEGetBrailleDisplayInfo(DWORD, int*, int *);
+extern bool WEUpdateBrailleDisplay(DWORD, BYTE *, int , BYTE *, int);
+extern bool WEBrailleClose(DWORD);
diff --git a/Drivers/Screen/Android/Makefile.in b/Drivers/Screen/Android/Makefile.in
new file mode 100644
index 0000000..688e886
--- /dev/null
+++ b/Drivers/Screen/Android/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = an
+DRIVER_NAME = Android
+DRIVER_USAGE = Android
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Android/screen.c b/Drivers/Screen/Android/screen.c
new file mode 100644
index 0000000..aecb41a
--- /dev/null
+++ b/Drivers/Screen/Android/screen.c
@@ -0,0 +1,588 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "report.h"
+#include "alert.h"
+#include "brl_cmds.h"
+#include "unicode.h"
+
+#include "scr_driver.h"
+#include "system_java.h"
+#include "core.h"
+
+static JNIEnv *env = NULL;
+static jclass screenDriverClass = NULL;
+static jclass inputHandlersClass = NULL;
+
+static jint screenNumber, screenColumns, screenRows;
+static jint locationLeft, locationTop, locationRight, locationBottom;
+static jint selectionLeft, selectionTop, selectionRight, selectionBottom;
+
+static const char *problemText;
+
+static int
+findScreenDriverClass (void) {
+  return findJavaClass(env, &screenDriverClass, JAVA_OBJ_BRLTTY("ScreenDriver"));
+}
+
+static int
+findInputHandlersClass (void) {
+  return findJavaClass(env, &inputHandlersClass, JAVA_OBJ_BRLTTY("InputHandlers"));
+}
+
+static int
+callSimpleInputHandler (jmethodID *method, const char *name) {
+  if (findInputHandlersClass()) {
+    if (findJavaStaticMethod(env, method, inputHandlersClass, name,
+                             JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                            ))) {
+      jboolean result = (*env)->CallStaticBooleanMethod(
+        env, inputHandlersClass, *method
+      );
+
+      if (!clearJavaException(env, 1)) {
+        if (result != JNI_FALSE) {
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+REPORT_LISTENER(androidScreenDriverReportListener) {
+  char *event = parameters->listenerData;
+
+  if (findScreenDriverClass()) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, screenDriverClass, "reportEvent",
+                             JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                             JAVA_SIG_CHAR // event
+                                            ))) {
+      (*env)->CallStaticVoidMethod(env, screenDriverClass, method, *event);
+      clearJavaException(env, 1);
+    }
+  }
+}
+
+typedef struct {
+  ReportListenerInstance *listener;
+  ReportIdentifier identifier;
+  char character;
+} ReportEntry;
+
+static ReportEntry reportEntries[] = {
+  { .character = 'b',
+    .identifier = REPORT_BRAILLE_DEVICE_ONLINE
+  },
+
+  { .character = 'B',
+    .identifier = REPORT_BRAILLE_DEVICE_OFFLINE
+  },
+
+  { .character = 'k',
+    .identifier = REPORT_BRAILLE_KEY_EVENT
+  },
+
+  { .character = 0 }
+};
+
+static int
+construct_AndroidScreen (void) {
+  for (ReportEntry *rpt=reportEntries; rpt->character; rpt+=1) {
+    rpt->listener = registerReportListener(
+      rpt->identifier, androidScreenDriverReportListener, &rpt->character
+    );
+
+    if (!rpt->listener) break;
+  }
+
+  return 1;
+}
+
+static void
+destruct_AndroidScreen (void) {
+  for (ReportEntry *rpt=reportEntries; rpt->character; rpt+=1) {
+    if (rpt->listener) {
+      unregisterReportListener(rpt->listener);
+      rpt->listener = NULL;
+    }
+  }
+}
+
+static int
+poll_AndroidScreen (void) {
+  return 0;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_android_ScreenDriver, screenUpdated, void
+) {
+  mainScreenUpdated();
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_android_ScreenDriver, exportScreenProperties, void,
+  jint number, jint columns, jint rows,
+  jint locLeft, jint locTop, int locRight, int locBottom,
+  jint selLeft, jint selTop, int selRight, int selBottom
+) {
+  screenNumber = number;
+  screenColumns = columns;
+  screenRows = rows;
+
+  locationLeft = locLeft;
+  locationTop = locTop;
+  locationRight = locRight;
+  locationBottom = locBottom;
+
+  selectionLeft = selLeft;
+  selectionTop = selTop;
+  selectionRight = selRight;
+  selectionBottom = selBottom;
+}
+
+static int
+refresh_AndroidScreen (void) {
+  problemText = NULL;
+
+  if (findScreenDriverClass()) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, screenDriverClass, "refreshScreen",
+                             JAVA_SIG_METHOD(JAVA_SIG_CHAR,
+                                            ))) {
+      jchar result = (*env)->CallStaticCharMethod(env, screenDriverClass, method);
+
+      if (clearJavaException(env, 1)) {
+        problemText = gettext("Java exception occurred");
+      } else {
+        if (result == 'l') {
+          setBrailleOff(gettext("screen locked"));
+          return 1;
+        }
+
+        if (result == 'r') {
+          setBrailleOff(gettext("braille released"));
+          return 1;
+        }
+      }
+    } else {
+      problemText = gettext("Java method not found");
+    }
+  } else {
+    problemText = gettext("Java class not found");
+  }
+
+  setBrailleOn();
+  return 1;
+}
+
+static void
+describe_AndroidScreen (ScreenDescription *description) {
+  if ((description->unreadable = problemText)) {
+    description->cols = strlen(problemText);
+    description->rows = 1;
+    description->posx = 0;
+    description->posy = 0;
+    description->number = 0;
+  } else {
+    description->number = screenNumber;
+
+    description->cols = screenColumns;
+    description->rows = screenRows;
+
+    description->posx = locationLeft + selectionLeft;
+    description->posy = locationTop + selectionTop;
+
+    description->hasCursor = 0;
+    description->hasSelection = 0;
+
+    if (selectionLeft >= 0) {
+      if ((selectionLeft == selectionRight) && (selectionTop == selectionBottom)) {
+        description->hasCursor = 1;
+      } else {
+        description->hasSelection = 1;
+      }
+    }
+  }
+}
+
+static int
+getRowCharacters (JNIEnv *env, ScreenCharacter *characters, jcharArray jCharacters, jint rowIndex, jint columnIndex, jint columnCount) {
+  jint characterCount = (*env)->GetArrayLength(env, jCharacters);
+  if (clearJavaException(env, 1)) return 0;
+
+  {
+    ScreenCharacter *target = characters;
+    ScreenCharacter *targetEnd = target + columnCount;
+
+    if (characterCount > 0) {
+      jchar cCharacters[characterCount];
+      (*env)->GetCharArrayRegion(env, jCharacters, 0, characterCount, cCharacters);
+      if (clearJavaException(env, 1)) return 0;
+
+      const jchar *source = cCharacters;
+      const jchar *sourceEnd = source + characterCount;
+
+      while (source < sourceEnd) {
+        if (target == targetEnd) break;
+
+        target->text = *source;
+        target->attributes = SCR_COLOUR_DEFAULT;
+
+        target += 1;
+        source += 1;
+      }
+    }
+
+    clearScreenCharacters(target, (targetEnd - target));
+  }
+
+  int top = locationTop + selectionTop;
+  int bottom = locationTop + selectionBottom;
+
+  if ((rowIndex >= top) && (rowIndex < bottom)) {
+    int from = MAX(locationLeft, columnIndex);
+    int to = MIN(locationRight, (columnIndex + columnCount));
+
+    if (rowIndex == top) {
+      int left = locationLeft + selectionLeft;
+      if (left > from) from = left;
+    }
+
+    if ((rowIndex + 1) == bottom) {
+      int right = locationLeft + selectionRight;
+      if (right < to) to = right;
+    }
+
+    if (from < to) {
+      from -= columnIndex;
+      to -= columnIndex;
+      if (to > characterCount) to = characterCount;
+
+      ScreenCharacter *target = characters + from;
+      const ScreenCharacter *targetEnd = characters + to;
+
+      while (target < targetEnd) {
+        target->attributes = SCR_COLOUR_FG_BLACK | SCR_COLOUR_BG_LIGHT_GREY;
+        target += 1;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+readRowCharacters (ScreenCharacter *characters, jint rowIndex, jint columnIndex, jint columnCount) {
+  if (findScreenDriverClass()) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, screenDriverClass, "getRowText",
+                             JAVA_SIG_METHOD(JAVA_SIG_ARRAY(JAVA_SIG_CHAR),
+                                             JAVA_SIG_INT // row
+                                             JAVA_SIG_INT // column
+                                            ))) {
+      jcharArray jCharacters = (*env)->CallStaticObjectMethod(
+        env, screenDriverClass, method, rowIndex, columnIndex
+      );
+
+      if (!clearJavaException(env, 1)) {
+        if (jCharacters) {
+          int ok = getRowCharacters(
+            env, characters, jCharacters,
+            rowIndex, columnIndex, columnCount
+          );
+
+          (*env)->DeleteLocalRef(env, jCharacters);
+          jCharacters = NULL;
+
+          return ok;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+readCharacters_AndroidScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  if (validateScreenBox(box, screenColumns, screenRows)) {
+    if (problemText) {
+      setScreenMessage(box, buffer, problemText);
+    } else {
+      for (int rowIndex=0; rowIndex<box->height; rowIndex+=1) {
+        if (!readRowCharacters(&buffer[rowIndex * box->width], (rowIndex + box->top), box->left, box->width)) return 0;
+      }
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+routeCursor_AndroidScreen (int column, int row, int screen) {
+  if (findScreenDriverClass()) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, screenDriverClass, "routeCursor",
+                             JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                             JAVA_SIG_INT // column
+                                             JAVA_SIG_INT // row
+                                            ))) {
+      jboolean result = (*env)->CallStaticBooleanMethod(env, screenDriverClass, method, column, row);
+
+      if (!clearJavaException(env, 1)) {
+        if (result != JNI_FALSE) {
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+insertKey_AndroidScreen (ScreenKey key) {
+  if (findInputHandlersClass()) {
+    wchar_t character = key & SCR_KEY_CHAR_MASK;
+
+    setScreenKeyModifiers(&key, 0);
+
+    if (!isSpecialKey(key)) {
+      static jmethodID method = 0;
+
+      if (findJavaStaticMethod(env, &method, inputHandlersClass, "inputCharacter",
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                               JAVA_SIG_CHAR // character
+                                              ))) {
+        jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, character);
+
+        if (!clearJavaException(env, 1)) {
+          if (result != JNI_FALSE) {
+            return 1;
+          }
+        }
+      }
+    } else if (character < SCR_KEY_FUNCTION) {
+#define SIZE UNICODE_CELL_NUMBER(SCR_KEY_FUNCTION)
+#define KEY(key,method) [UNICODE_CELL_NUMBER(SCR_KEY_##key)] = method
+
+      static const char *const methodNames[SIZE] = {
+        KEY(ENTER, "keyHandler_enter"),
+        KEY(TAB, "keyHandler_tab"),
+        KEY(BACKSPACE, "keyHandler_backspace"),
+        KEY(ESCAPE, "keyHandler_escape"),
+        KEY(CURSOR_LEFT, "keyHandler_cursorLeft"),
+        KEY(CURSOR_RIGHT, "keyHandler_cursorRight"),
+        KEY(CURSOR_UP, "keyHandler_cursorUp"),
+        KEY(CURSOR_DOWN, "keyHandler_cursorDown"),
+        KEY(PAGE_UP, "keyHandler_pageUp"),
+        KEY(PAGE_DOWN, "keyHandler_pageDown"),
+        KEY(HOME, "keyHandler_home"),
+        KEY(END, "keyHandler_end"),
+        KEY(INSERT, "keyHandler_insert"),
+        KEY(DELETE, "keyHandler_delete"),
+      };
+
+      const unsigned int key = UNICODE_CELL_NUMBER(character);
+      const char *methodName = methodNames[key];
+      if (!methodName) return 0;
+
+      static jmethodID methodIdentifiers[SIZE];
+      jmethodID *methodIdentifier = &methodIdentifiers[key];
+
+#undef SIZE
+#undef KEY
+
+      if (findJavaStaticMethod(env, methodIdentifier, inputHandlersClass, methodName,
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                              ))) {
+        jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, *methodIdentifier);
+
+        if (!clearJavaException(env, 1)) {
+          if (result != JNI_FALSE) {
+            return 1;
+          }
+        }
+      }
+    } else {
+      static jmethodID method = 0;
+
+      if (findJavaStaticMethod(env, &method, inputHandlersClass, "keyHandler_function",
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                               JAVA_SIG_INT // key
+                                              ))) {
+        jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, character-SCR_KEY_FUNCTION);
+
+        if (!clearJavaException(env, 1)) {
+          if (result != JNI_FALSE) {
+            return 1;
+          }
+        }
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "unsupported key: %04X", key);
+  return 0;
+}
+
+#define SIMPLE_INPUT_HANDLER_COMMAND(command, handler) \
+  case BRL_CMD_##command: { \
+    static jmethodID method = 0; \
+    if (callSimpleInputHandler(&method, handler)) return 1; \
+    break; \
+  }
+
+static int
+handleCommand_AndroidScreen (int command) {
+  int blk = command & BRL_MSK_BLK;
+  int arg = command & BRL_MSK_ARG;
+  int cmd = blk | arg;
+
+  switch (cmd) {
+    SIMPLE_INPUT_HANDLER_COMMAND(TXTSEL_ALL, "textHandler_selectAll")
+    SIMPLE_INPUT_HANDLER_COMMAND(HOST_COPY, "textHandler_copySelection")
+    SIMPLE_INPUT_HANDLER_COMMAND(HOST_CUT, "textHandler_cutSelection")
+    SIMPLE_INPUT_HANDLER_COMMAND(HOST_PASTE, "textHandler_pasteClipboard")
+
+    SIMPLE_INPUT_HANDLER_COMMAND(INDICATORS, "globalAction_showStatusIndicators")
+
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_TITLE, "globalAction_showWindowTitle")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_BRL_ACTIONS, "globalAction_brailleActions")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_HOME, "globalAction_homeScreen")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_BACK, "globalAction_backButton")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_DEV_SETTINGS, "globalAction_quickSettings")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_DEV_OPTIONS, "globalAction_deviceOptions")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_LIST, "globalAction_recentApplications")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_MENU, "globalAction_menuButton")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_APP_ALERTS, "globalAction_notificationsShade")
+
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_ACTV, "globalAction_toActiveWindow")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_PREV, "globalAction_toPreviousWindow")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_AREA_NEXT, "globalAction_toNextWindow")
+
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_FRST, "globalAction_toFirstItem")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_PREV, "globalAction_toPreviousItem")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_NEXT, "globalAction_toNextItem")
+    SIMPLE_INPUT_HANDLER_COMMAND(GUI_ITEM_LAST, "globalAction_toLastItem")
+
+    default: {
+      switch (blk) {
+        case BRL_CMD_BLK(PASSDOTS): {
+          if ((command & BRL_FLG_INPUT_META) == 0) return 0;
+
+          if (findInputHandlersClass()) {
+            static jmethodID method = 0;
+
+            if (findJavaStaticMethod(env, &method, inputHandlersClass, "performStructuralMotion",
+                                     JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                                     JAVA_SIG_BYTE // dots
+                                                    ))) {
+              jbyte dots = arg;
+              jboolean result = (*env)->CallStaticBooleanMethod(env, inputHandlersClass, method, dots);
+              if (clearJavaException(env, 1)) result = JNI_FALSE;
+              if (result == JNI_TRUE) return 1;
+            }
+          }
+
+          break;
+        }
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  alert(ALERT_COMMAND_REJECTED);
+  return 1;
+}
+
+static int
+clearSelection_AndroidScreen (void) {
+  static jmethodID method = 0;
+  return callSimpleInputHandler(&method, "textHandler_clearSelection");
+}
+
+static int
+setSelection_AndroidScreen (int startColumn, int startRow, int endColumn, int endRow) {
+  if (findInputHandlersClass()) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, inputHandlersClass, "textHandler_setSelection",
+                             JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                             JAVA_SIG_INT // startColumn
+                                             JAVA_SIG_INT // startRow
+                                             JAVA_SIG_INT // endColumn
+                                             JAVA_SIG_INT // endRow
+                                            ))) {
+      jboolean result = (*env)->CallStaticBooleanMethod(
+        env, inputHandlersClass, method,
+        startColumn, startRow, endColumn, endRow
+      );
+
+      if (!clearJavaException(env, 1)) {
+        if (result != JNI_FALSE) {
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+
+  main->base.poll = poll_AndroidScreen;
+  main->base.refresh = refresh_AndroidScreen;
+  main->base.describe = describe_AndroidScreen;
+
+  main->base.readCharacters = readCharacters_AndroidScreen;
+  main->base.routeCursor = routeCursor_AndroidScreen;
+
+  main->base.insertKey = insertKey_AndroidScreen;
+  main->base.handleCommand = handleCommand_AndroidScreen;
+
+  main->base.clearSelection = clearSelection_AndroidScreen;
+  main->base.setSelection = setSelection_AndroidScreen;
+
+  main->construct = construct_AndroidScreen;
+  main->destruct = destruct_AndroidScreen;
+
+  env = getJavaNativeInterface();
+}
diff --git a/Drivers/Screen/AtSpi/Makefile.in b/Drivers/Screen/AtSpi/Makefile.in
new file mode 100644
index 0000000..ac736eb
--- /dev/null
+++ b/Drivers/Screen/AtSpi/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = as
+DRIVER_NAME = AtSpi
+DRIVER_USAGE = X11 desktop via AT-SPI 1
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SCR_OBJS = @screen_libraries_as@
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) $(CSPI_INCLUDES) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/AtSpi/screen.c b/Drivers/Screen/AtSpi/screen.c
new file mode 100644
index 0000000..f707a4a
--- /dev/null
+++ b/Drivers/Screen/AtSpi/screen.c
@@ -0,0 +1,749 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <locale.h>
+
+#ifdef __MINGW32__
+#include "win_pthread.h"
+#else /* __MINGW32__ */
+#include <semaphore.h>
+#endif /* __MINGW32__ */
+
+#include <cspi/spi.h>
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+
+#include "log.h"
+#include "parse.h"
+#include "thread.h"
+#include "brl_cmds.h"
+
+typedef enum {
+  PARM_TYPE
+} ScreenParameters;
+#define SCRPARMS "type"
+
+#include "scr_driver.h"
+
+static int typeText = 0, typeTerminal = 1, typeAll = 0;
+static AccessibleText *curTerm;
+static Accessible *curFocus;
+
+static long curNumRows, curNumCols;
+static wchar_t **curRows;
+static long *curRowLengths;
+static long curPosX,curPosY;
+static pthread_mutex_t updateMutex = PTHREAD_MUTEX_INITIALIZER;
+
+pthread_t SPI_main_thread;
+
+/* having our own implementation is much more independant on locales */
+
+typedef struct {
+  int remaining;
+  wint_t current;
+} my_mbstate_t;
+
+static my_mbstate_t internal;
+
+static size_t my_mbrtowc(wchar_t *pwc, const char *s, size_t n, my_mbstate_t *ps) {
+  const unsigned char *c = (const unsigned char *) s;
+  int read = 0;
+  if (!c) {
+    if (ps->remaining) {
+      errno = EILSEQ;
+      return (size_t)(-1);
+    }
+    return 0;
+  }
+
+  if (n && !ps->remaining) {
+    /* initial state */
+    if (!(*c&0x80)) {
+      /* most frequent case: ascii */
+      if (pwc)
+	*pwc = *c;
+      if (!*c)
+	return 0;
+      return 1;
+    } else if (!(*c&0x40)) {
+      /* utf-8 char continuation, shouldn't happen with remaining == 0 ! */
+      goto error;
+    } else {
+      /* new utf-8 char, get remaining chars */
+      read = 1;
+      if (!(*c&0x20)) {
+	ps->remaining = 1;
+	ps->current = *c&((1<<5)-1);
+      } else if (!(*c&0x10)) {
+	ps->remaining = 2;
+	ps->current = *c&((1<<4)-1);
+      } else if (!(*c&0x08)) {
+	ps->remaining = 3;
+	ps->current = *c&((1<<3)-1);
+      } else if (!(*c&0x04)) {
+	ps->remaining = 4;
+	ps->current = *c&((1<<2)-1);
+      } else if (!(*c&0x02)) {
+	ps->remaining = 5;
+	ps->current = *c&((1<<1)-1);
+      } else
+	/* 0xff and 0xfe are not allowed */
+	goto error;
+      c++;
+    }
+  }
+  /* looking for continuation chars */
+  while (n-read) {
+    if ((*c&0xc0) != 0X80)
+      /* not continuation char, error ! */
+      goto error;
+    /* utf-8 char continuation */
+    ps->current = (ps->current<<6) | (*c&((1<<6)-1));
+    read++;
+    if (!(--ps->remaining)) {
+      if (pwc)
+	*pwc = ps->current;
+      if (!ps->current)
+	/* shouldn't coded this way, but well... */
+	return 0;
+      return read;
+    }
+    c++;
+  }
+  return (size_t)(-2);
+error:
+  errno = EILSEQ;
+  return (size_t)(-1);
+}
+
+static size_t my_mbsrtowcs(wchar_t *dest, const char **src, size_t len, my_mbstate_t *ps) {
+  int put = 0;
+  size_t skip;
+  wchar_t buf,*bufp;
+
+  if (!ps) ps = &internal;
+  if (dest)
+    bufp = dest;
+  else
+    bufp = &buf;
+
+  while (len-put || !dest) {
+    skip = my_mbrtowc(bufp, *src, 6, ps);
+    switch (skip) {
+      case (size_t)(-2): errno = EILSEQ; /* shouldn't happen ! */
+      case (size_t)(-1): return (size_t)(-1);
+      case 0: *src = NULL; return put;
+    }
+    *src += skip;
+    if (dest) bufp++;
+    put++;
+  }
+  return put;
+}
+
+static size_t my_mbrlen(const char *s, size_t n, my_mbstate_t *ps) {
+  return my_mbrtowc(NULL, s, n, ps?ps:&internal);
+}
+
+static size_t my_mbslen(const char *s, size_t n) {
+  my_mbstate_t ps;
+  size_t ret=0;
+  size_t eaten;
+  memset(&ps,0,sizeof(ps));
+  while(n) {
+    if ((eaten = my_mbrlen(s,n,&ps))<0)
+      return eaten;
+    if (!(eaten)) return ret;
+    s+=eaten;
+    n-=eaten;
+    ret++;
+  }
+  return ret;
+}
+
+static void addRows(long pos, long num) {
+  curNumRows += num;
+  curRows = realloc(curRows,curNumRows*sizeof(*curRows));
+  curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths));
+  memmove(curRows      +pos+num,curRows      +pos,(curNumRows-(pos+num))*sizeof(*curRows));
+  memmove(curRowLengths+pos+num,curRowLengths+pos,(curNumRows-(pos+num))*sizeof(*curRowLengths));
+}
+
+static void delRows(long pos, long num) {
+  long y;
+  for (y=pos;y<pos+num;y++)
+    free(curRows[y]);
+  memmove(curRows      +pos,curRows      +pos+num,(curNumRows-(pos+num))*sizeof(*curRows));
+  memmove(curRowLengths+pos,curRowLengths+pos+num,(curNumRows-(pos+num))*sizeof(*curRowLengths));
+  curNumRows -= num;
+  curRows = realloc(curRows,curNumRows*sizeof(*curRows));
+  curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths));
+}
+
+static int
+processParameters_AtSpiScreen (char **parameters) {
+  if (*parameters[PARM_TYPE]) {
+    static const char *const choices[] = {"text"   , "terminal"   , "all"   , NULL}; 
+    static       int  *const flags  [] = {&typeText, &typeTerminal, &typeAll, NULL};
+    int count;
+    char **types = splitString(parameters[PARM_TYPE], '+', &count);
+
+    {
+      int *const *flag = flags;
+      while (*flag) **flag++ = 0;
+    }
+
+    {
+      int index;
+      for (index=0; index<count; index++) {
+        const char *type = types[index];
+        unsigned int choice;
+
+        if (validateChoice(&choice, type, choices)) {
+          int *flag = flags[choice];
+          if ((flag == &typeAll) && (index > 0)) {
+            logMessage(LOG_WARNING, "widget type is mutually exclusive: %s", type);
+          } else if (*flag || typeAll) {
+            logMessage(LOG_WARNING, "widget type specified more than once: %s", type);
+          } else {
+            *flag = 1;
+          }
+        } else {
+          logMessage(LOG_WARNING, "%s: %s", "invalid widget type", type);
+        }
+      }
+    }
+
+    deallocateStrings(types);
+  }
+
+  return 1;
+}
+
+static void findPosition(long position, long *px, long *py) {
+  long offset=0, newoffset, x, y;
+  /* XXX: I don't know what they do with necessary combining accents */
+  for (y=0; y<curNumRows; y++) {
+    if ((newoffset = offset + curRowLengths[y]) > position)
+      break;
+    offset = newoffset;
+  }
+  if (y==curNumRows) {
+    if (!curNumRows) {
+      y = 0;
+      x = 0;
+    } else {
+      /* this _can_ happen, when deleting while caret is at the end of the
+       * terminal: caret position is only updated afterwards... In the
+       * meanwhile, keep caret at the end of last line. */
+      y = curNumRows-1;
+      x = curRowLengths[y];
+    }
+  } else
+    x = position-offset;
+  *px = x;
+  *py = y;
+}
+
+static void caretPosition(long caret) {
+  findPosition(caret,&curPosX,&curPosY);
+}
+
+static void finiTerm(void) {
+  logMessage(LOG_DEBUG,"end of term %p",curTerm);
+  Accessible_unref(curTerm);
+  curTerm = NULL;
+  Accessible_unref(curFocus);
+  curFocus = NULL;
+  curPosX = curPosY = 0;
+}
+
+static void restartTerm(Accessible *newTerm, AccessibleText *newTextTerm) {
+  char *c,*d;
+  const char *e;
+  long i,len;
+  char *text;
+
+  if (curFocus)
+    finiTerm();
+  Accessible_ref(curFocus = newTerm);
+  curTerm = newTextTerm;
+  logMessage(LOG_DEBUG,"new term %p",curTerm);
+  text = AccessibleText_getText(curTerm,0,LONG_MAX);
+  curNumRows = 0;
+  if (curRows) {
+    for (i=0;i<curNumRows;i++)
+      free(curRows[i]);
+    free(curRows);
+  }
+  free(curRowLengths);
+  c = text;
+  while (*c) {
+    curNumRows++;
+    if (!(c = strchr(c,'\n')))
+      break;
+    c++;
+  }
+  logMessage(LOG_DEBUG,"%ld rows",curNumRows);
+  curRows = malloc(curNumRows * sizeof(*curRows));
+  curRowLengths = malloc(curNumRows * sizeof(*curRowLengths));
+  i = 0;
+  curNumCols = 0;
+  for (c = text; *c; c = d+1) {
+    d = strchr(c,'\n');
+    if (d)
+      *d = 0;
+    e = c;
+    curRowLengths[i] = (len = my_mbsrtowcs(NULL,&e,0,NULL)) + (d != NULL);
+    if (len > curNumCols)
+      curNumCols = len;
+    else if (len < 0) {
+      if (len==-2)
+	logMessage(LOG_ERR,"unterminated sequence %s",c);
+      else if (len==-1)
+	logSystemError("mbrlen");
+      curRowLengths[i] = (len = -1) + (d != NULL);
+    }
+    curRows[i] = malloc((len + (d!=NULL)) * sizeof(*curRows[i]));
+    e = c;
+    my_mbsrtowcs(curRows[i],&e,len,NULL);
+    if (d)
+      curRows[i][len]='\n';
+    else
+      break;
+    i++;
+  }
+  logMessage(LOG_DEBUG,"%ld cols",curNumCols);
+  SPI_freeString(text);
+  caretPosition(AccessibleText_getCaretOffset(curTerm));
+}
+
+static struct evList {
+  struct evList *next;
+  const AccessibleEvent *ev;
+} *evs;
+
+static void evListenerCB(const AccessibleEvent *event, void *user_data) {
+  static int running = 0;
+  struct evList *ev = malloc(sizeof(*ev));
+  AccessibleEvent_ref(event);
+  AccessibleText *newText;
+  ev->next = evs;
+  ev->ev = event;
+  evs = ev;
+  int state_changed_focused;
+
+  /* this is not atomic but we can only be recursively called within calls
+   * to the lib */
+  if (running)
+    return;
+  else
+    running = 1;
+
+  while (evs) {
+    pthread_mutex_lock(&updateMutex);
+    /* pickup a list of events to handle */
+    ev = evs;
+    evs = NULL;
+    for (; ev; AccessibleEvent_unref(ev->ev), ev = ev->next) {
+      event = ev->ev;
+      state_changed_focused = !strcmp(event->type,"object:state-changed:focused");
+      if (state_changed_focused && !event->detail1) {
+	if (event->source == curFocus)
+	  finiTerm();
+      } else if (!strcmp(event->type,"focus:") || (state_changed_focused && event->detail1)) {
+	if (!(newText = Accessible_getText(event->source))) {
+	  if (curFocus) finiTerm();
+	} else {
+          AccessibleRole role = Accessible_getRole(event->source);
+	  if (typeAll ||
+	      (typeText && ((role == SPI_ROLE_TEXT) || (role == SPI_ROLE_PASSWORD_TEXT) || (role == SPI_ROLE_PARAGRAPH))) ||
+	      (typeTerminal && (role == SPI_ROLE_TERMINAL))) {
+	    restartTerm(event->source, newText);
+	  } else {
+	    logMessage(LOG_DEBUG,"AT SPI widget not for us");
+	    if (curFocus) finiTerm();
+	  }
+	}
+      } else if (!strcmp(event->type,"object:text-caret-moved")) {
+	if (event->source != curFocus) continue;
+	logMessage(LOG_DEBUG,"caret move to %lu",event->detail1);
+	caretPosition(event->detail1);
+      } else if (!strcmp(event->type,"object:text-changed:delete")) {
+	long x,y,toDelete = event->detail2;
+	long length = 0, toCopy;
+	long downTo; /* line that will provide what will follow x */
+	logMessage(LOG_DEBUG,"delete %lu from %lu",event->detail2,event->detail1);
+	if (event->source != curFocus) continue;
+	findPosition(event->detail1,&x,&y);
+	downTo = y;
+	if (downTo < curNumRows)
+		length = curRowLengths[downTo];
+	while (x+toDelete >= length) {
+	  downTo++;
+	  if (downTo <= curNumRows - 1)
+	    length += curRowLengths[downTo];
+	  else {
+	    /* imaginary extra line doesn't provide more length, and shouldn't need to ! */
+	    if (x+toDelete > length) {
+	      logMessage(LOG_ERR,"deleting past end of text !");
+	      /* discarding */
+	      toDelete = length - x;
+	    }
+	    break; /* deleting up to end */
+	  }
+	}
+	if (length-toDelete>0) {
+	  /* still something on line y */
+	  if (y!=downTo) {
+	    curRowLengths[y] = length-toDelete;
+	    curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+	  }
+	  if ((toCopy = length-toDelete-x))
+	    memmove(curRows[y]+x,curRows[downTo]+curRowLengths[downTo]-toCopy,toCopy*sizeof(*curRows[downTo]));
+	  if (y==downTo) {
+	    curRowLengths[y] = length-toDelete;
+	    curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+	  }
+	} else {
+	  /* kills this line as well ! */
+	  y--;
+	}
+	if (downTo>=curNumRows)
+	  /* imaginary extra lines don't need to be deleted */
+	  downTo=curNumRows-1;
+	delRows(y+1,downTo-y);
+	caretPosition(AccessibleText_getCaretOffset(curTerm));
+      } else if (!strcmp(event->type,"object:text-changed:insert")) {
+	long len=event->detail2,semilen,x,y;
+	char *added;
+	const char *adding,*c;
+	logMessage(LOG_DEBUG,"insert %lu from %lu",event->detail2,event->detail1);
+	if (event->source != curFocus) continue;
+	findPosition(event->detail1,&x,&y);
+	adding = c = added = AccessibleTextChangedEvent_getChangeString(event);
+	if (x && (c = strchr(adding,'\n'))) {
+	  /* splitting line */
+	  addRows(y,1);
+	  semilen=my_mbslen(adding,c+1-adding);
+	  curRowLengths[y]=x+semilen;
+	  if (x+semilen-1>curNumCols)
+	    curNumCols=x+semilen-1;
+
+	  /* copy beginning */
+	  curRows[y]=malloc(curRowLengths[y]*sizeof(*curRows[y]));
+	  memcpy(curRows[y],curRows[y+1],x*sizeof(*curRows[y]));
+	  /* add */
+	  my_mbsrtowcs(curRows[y]+x,&adding,semilen,NULL);
+	  len-=semilen;
+	  adding=c+1;
+	  /* shift end */
+	  curRowLengths[y+1]-=x;
+	  memmove(curRows[y+1],curRows[y+1]+x,curRowLengths[y+1]*sizeof(*curRows[y+1]));
+	  x=0;
+	  y++;
+	}
+	while ((c = strchr(adding,'\n'))) {
+	  /* adding lines */
+	  addRows(y,1);
+	  semilen=my_mbslen(adding,c+1-adding);
+	  curRowLengths[y]=semilen;
+	  if (semilen-1>curNumCols)
+	    curNumCols=semilen-1;
+	  curRows[y]=malloc(semilen*sizeof(*curRows[y]));
+	  my_mbsrtowcs(curRows[y],&adding,semilen,NULL);
+	  len-=semilen;
+	  adding=c+1;
+	  y++;
+	}
+	if (len) {
+	  /* still length to add on the line following it */
+	  if (y==curNumRows) {
+	    /* It won't insert ending \n yet */
+	    addRows(y,1);
+	    curRows[y]=NULL;
+	    curRowLengths[y]=0;
+	  }
+	  curRowLengths[y] += len;
+	  curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+	  memmove(curRows[y]+x+len,curRows[y]+x,(curRowLengths[y]-(x+len))*sizeof(*curRows[y]));
+	  my_mbsrtowcs(curRows[y]+x,&adding,len,NULL);
+	  if (curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n')>curNumCols)
+	    curNumCols=curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n');
+	}
+	SPI_freeString(added);
+	caretPosition(AccessibleText_getCaretOffset(curTerm));
+      } else
+	logMessage(LOG_INFO,"event %s, source %p, detail1 %lu detail2 %lu",event->type,event->source,event->detail1,event->detail2);
+    }
+    pthread_mutex_unlock(&updateMutex);
+  }
+  running = 0;
+}
+
+THREAD_FUNCTION(asOpenScreenThread) {
+  AccessibleEventListener *evListener;
+  sem_t *SPI_init_sem = argument;
+  int res;
+  static const char *events[] = {
+    "object:text-changed",
+    "object:text-caret-moved",
+    "object:state-changed:focused",
+    "focus:",
+  };
+  const char **event;
+  if ((res=SPI_init())) {
+    logMessage(LOG_ERR,"SPI_init returned %d",res);
+    return 0;
+  }
+  if (!(evListener = SPI_createAccessibleEventListener(evListenerCB,NULL)))
+    logMessage(LOG_ERR,"SPI_createAccessibleEventListener failed");
+  else for (event=events; event<&events[sizeof(events)/sizeof(*events)]; event++)
+    if (!(SPI_registerGlobalEventListener(evListener,*event)))
+      logMessage(LOG_ERR,"SPI_registerGlobalEventListener(%s) failed",*event);
+  sem_post(SPI_init_sem);
+  SPI_event_main();
+  if (!(SPI_deregisterGlobalEventListenerAll(evListener)))
+    logMessage(LOG_ERR,"SPI_deregisterGlobalEventListenerAll failed");
+  AccessibleEventListener_unref(evListener);
+  if (curFocus)
+    finiTerm();
+  if ((res=SPI_exit()))
+    logMessage(LOG_ERR,"SPI_exit returned %d",res);
+  return NULL;
+}
+
+static int
+construct_AtSpiScreen (void) {
+  sem_t SPI_init_sem;
+  sem_init(&SPI_init_sem,0,0);
+  XInitThreads();
+  if (createThread("driver-screen-AtSpi",
+                        &SPI_main_thread, NULL,
+                        asOpenScreenThread, (void *)&SPI_init_sem)) {
+    logMessage(LOG_ERR,"main SPI thread failed to be launched");
+    return 0;
+  }
+  do {
+    errno = 0;
+  } while (sem_wait(&SPI_init_sem) == -1 && errno == EINTR);
+  if (errno) {
+    logSystemError("SPI initialization wait failed");
+    return 0;
+  }
+  logMessage(LOG_DEBUG,"SPI initialized");
+  return 1;
+}
+
+static void
+destruct_AtSpiScreen (void) {
+  SPI_event_quit();
+  pthread_join(SPI_main_thread,NULL);
+  logMessage(LOG_DEBUG,"SPI stopped");
+}
+
+static int
+currentVirtualTerminal_AtSpiScreen (void) {
+  return curTerm? 0: -1;
+}
+
+static const char nonatspi [] = "not an AT-SPI text widget";
+
+static void
+describe_AtSpiScreen (ScreenDescription *description) {
+  pthread_mutex_lock(&updateMutex);
+  if (curTerm) {
+    description->cols = curNumCols;
+    description->rows = curNumRows?curNumRows:1;
+    description->posx = curPosX;
+    description->posy = curPosY;
+  } else {
+    description->unreadable = nonatspi;
+    description->rows = 1;
+    description->cols = strlen(nonatspi);
+    description->posx = 0;
+    description->posy = 0;
+  }
+  pthread_mutex_unlock(&updateMutex);
+  description->number = currentVirtualTerminal_AtSpiScreen();
+}
+
+static int
+readCharacters_AtSpiScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  long x,y;
+  clearScreenCharacters(buffer,box->height*box->width);
+  pthread_mutex_lock(&updateMutex);
+  if (!curTerm) {
+    setScreenMessage(box, buffer, nonatspi);
+    goto out;
+  }
+  if (!curNumRows) {
+    goto out;
+  }
+  if (!validateScreenBox(box, curNumCols, curNumRows)) {
+    goto out;
+  }
+  for (y=0; y<box->height; y++) {
+    if (curRowLengths[box->top+y]) {
+      for (x=0; x<box->width; x++) {
+        if (box->left+x<curRowLengths[box->top+y] - (curRows[box->top+y][curRowLengths[box->top+y]-1]=='\n')) {
+          buffer[y*box->width+x].text = curRows[box->top+y][box->left+x];
+        }
+      }
+    }
+  }
+out:
+  pthread_mutex_unlock(&updateMutex);
+  return 1;
+}
+
+static int
+insertKey_AtSpiScreen (ScreenKey key) {
+  long keysym;
+  int modMeta=0, modControl=0;
+
+  setScreenKeyModifiers(&key, SCR_KEY_CONTROL);
+
+  if (isSpecialKey(key)) {
+    switch (key & SCR_KEY_CHAR_MASK) {
+      case SCR_KEY_ENTER:         keysym = XK_KP_Enter;  break;
+      case SCR_KEY_TAB:           keysym = XK_Tab;       break;
+      case SCR_KEY_BACKSPACE:     keysym = XK_BackSpace; break;
+      case SCR_KEY_ESCAPE:        keysym = XK_Escape;    break;
+      case SCR_KEY_CURSOR_LEFT:   keysym = XK_Left;      break;
+      case SCR_KEY_CURSOR_RIGHT:  keysym = XK_Right;     break;
+      case SCR_KEY_CURSOR_UP:     keysym = XK_Up;        break;
+      case SCR_KEY_CURSOR_DOWN:   keysym = XK_Down;      break;
+      case SCR_KEY_PAGE_UP:       keysym = XK_Page_Up;   break;
+      case SCR_KEY_PAGE_DOWN:     keysym = XK_Page_Down; break;
+      case SCR_KEY_HOME:          keysym = XK_Home;      break;
+      case SCR_KEY_END:           keysym = XK_End;       break;
+      case SCR_KEY_INSERT:        keysym = XK_Insert;    break;
+      case SCR_KEY_DELETE:        keysym = XK_Delete;    break;
+      case SCR_KEY_FUNCTION + 0:  keysym = XK_F1;        break;
+      case SCR_KEY_FUNCTION + 1:  keysym = XK_F2;        break;
+      case SCR_KEY_FUNCTION + 2:  keysym = XK_F3;        break;
+      case SCR_KEY_FUNCTION + 3:  keysym = XK_F4;        break;
+      case SCR_KEY_FUNCTION + 4:  keysym = XK_F5;        break;
+      case SCR_KEY_FUNCTION + 5:  keysym = XK_F6;        break;
+      case SCR_KEY_FUNCTION + 6:  keysym = XK_F7;        break;
+      case SCR_KEY_FUNCTION + 7:  keysym = XK_F8;        break;
+      case SCR_KEY_FUNCTION + 8:  keysym = XK_F9;        break;
+      case SCR_KEY_FUNCTION + 9:  keysym = XK_F10;       break;
+      case SCR_KEY_FUNCTION + 10: keysym = XK_F11;       break;
+      case SCR_KEY_FUNCTION + 11: keysym = XK_F12;       break;
+      case SCR_KEY_FUNCTION + 12: keysym = XK_F13;       break;
+      case SCR_KEY_FUNCTION + 13: keysym = XK_F14;       break;
+      case SCR_KEY_FUNCTION + 14: keysym = XK_F15;       break;
+      case SCR_KEY_FUNCTION + 15: keysym = XK_F16;       break;
+      case SCR_KEY_FUNCTION + 16: keysym = XK_F17;       break;
+      case SCR_KEY_FUNCTION + 17: keysym = XK_F18;       break;
+      case SCR_KEY_FUNCTION + 18: keysym = XK_F19;       break;
+      case SCR_KEY_FUNCTION + 19: keysym = XK_F20;       break;
+      case SCR_KEY_FUNCTION + 20: keysym = XK_F21;       break;
+      case SCR_KEY_FUNCTION + 21: keysym = XK_F22;       break;
+      case SCR_KEY_FUNCTION + 22: keysym = XK_F23;       break;
+      case SCR_KEY_FUNCTION + 23: keysym = XK_F24;       break;
+      case SCR_KEY_FUNCTION + 24: keysym = XK_F25;       break;
+      case SCR_KEY_FUNCTION + 25: keysym = XK_F26;       break;
+      case SCR_KEY_FUNCTION + 26: keysym = XK_F27;       break;
+      case SCR_KEY_FUNCTION + 27: keysym = XK_F28;       break;
+      case SCR_KEY_FUNCTION + 28: keysym = XK_F29;       break;
+      case SCR_KEY_FUNCTION + 29: keysym = XK_F30;       break;
+      case SCR_KEY_FUNCTION + 30: keysym = XK_F31;       break;
+      case SCR_KEY_FUNCTION + 31: keysym = XK_F32;       break;
+      case SCR_KEY_FUNCTION + 32: keysym = XK_F33;       break;
+      case SCR_KEY_FUNCTION + 33: keysym = XK_F34;       break;
+      case SCR_KEY_FUNCTION + 34: keysym = XK_F35;       break;
+      default: logMessage(LOG_WARNING, "key not insertable: %04X", key); return 0;
+    }
+  } else {
+    wchar_t wc;
+
+    if (key & SCR_KEY_ALT_LEFT) {
+      key &= ~SCR_KEY_ALT_LEFT;
+      modMeta = 1;
+    }
+
+    if (key & SCR_KEY_CONTROL) {
+      key &= ~SCR_KEY_CONTROL;
+      modControl = 1;
+    }
+
+    wc = key & SCR_KEY_CHAR_MASK;
+    if (wc < 0x100)
+      keysym = wc; /* latin1 character */
+    else
+      keysym = 0x1000000 | wc;
+  }
+  logMessage(LOG_DEBUG, "inserting key: %04X -> %s%s%ld",
+             key,
+             (modMeta? "meta ": ""),
+             (modControl? "control ": ""),
+             keysym);
+
+  {
+    int ok = 0;
+
+    if (!modMeta || SPI_generateKeyboardEvent(XK_Meta_L,NULL,SPI_KEY_PRESS)) {
+      if (!modControl || SPI_generateKeyboardEvent(XK_Control_L,NULL,SPI_KEY_PRESS)) {
+        if (SPI_generateKeyboardEvent(keysym,NULL,SPI_KEY_SYM)) {
+          ok = 1;
+        } else {
+          logMessage(LOG_WARNING, "key insertion failed.");
+        }
+
+        if (modControl && !SPI_generateKeyboardEvent(XK_Control_L,NULL,SPI_KEY_RELEASE)) {
+          logMessage(LOG_WARNING, "control release failed.");
+          ok = 0;
+        }
+      } else {
+        logMessage(LOG_WARNING, "control press failed.");
+      }
+
+      if (modMeta && !SPI_generateKeyboardEvent(XK_Meta_L,NULL,SPI_KEY_RELEASE)) {
+        logMessage(LOG_WARNING, "meta release failed.");
+        ok = 0;
+      }
+    } else {
+      logMessage(LOG_WARNING, "meta press failed.");
+    }
+
+    return ok;
+  }
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.describe = describe_AtSpiScreen;
+  main->base.readCharacters = readCharacters_AtSpiScreen;
+  main->base.insertKey = insertKey_AtSpiScreen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_AtSpiScreen;
+  main->processParameters = processParameters_AtSpiScreen;
+  main->construct = construct_AtSpiScreen;
+  main->destruct = destruct_AtSpiScreen;
+}
diff --git a/Drivers/Screen/AtSpi2/Makefile.in b/Drivers/Screen/AtSpi2/Makefile.in
new file mode 100644
index 0000000..47bfac3
--- /dev/null
+++ b/Drivers/Screen/AtSpi2/Makefile.in
@@ -0,0 +1,37 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = a2
+DRIVER_NAME = AtSpi2
+DRIVER_USAGE = X11 desktop via AT-SPI 2
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SCR_OBJS = @screen_libraries_a2@
+include $(SRC_TOP)screen.mk
+
+OBJECT_FILES = a2_screen.$O xsel.$O
+
+screen.$O: $(OBJECT_FILES)
+	$(MKREL) $@ $(OBJECT_FILES)
+
+a2_screen.$O:
+	$(CC) $(SCR_CFLAGS) $(ATSPI2_INCLUDES) $(DBUS_INCLUDES) $(GLIB2_INCLUDES) -c $(SRC_DIR)/a2_screen.c
+
+%.$O: $(SRC_TOP)$(PGM_DIR)/%.c $(SRC_TOP)$(HDR_DIR)/%.h
+	$(CC) $(SCR_CFLAGS) -c $<
+
diff --git a/Drivers/Screen/AtSpi2/a2_screen.c b/Drivers/Screen/AtSpi2/a2_screen.c
new file mode 100644
index 0000000..9d5ca1d
--- /dev/null
+++ b/Drivers/Screen/AtSpi2/a2_screen.c
@@ -0,0 +1,1892 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+#include "embed.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <locale.h>
+
+#ifdef HAVE_ATSPI_GET_A11Y_BUS
+#include <atspi/atspi.h>
+#endif /* HAVE_ATSPI_GET_A11Y_BUS */
+
+#ifdef __MINGW32__
+#include "win_pthread.h"
+#else /* __MINGW32__ */
+#include <semaphore.h>
+#endif /* __MINGW32__ */
+
+#include <dbus/dbus.h>
+#define SPI2_DBUS_INTERFACE		"org.a11y.atspi"
+#define SPI2_DBUS_INTERFACE_REG		SPI2_DBUS_INTERFACE".Registry"
+#define SPI2_DBUS_PATH_REG		"/org/a11y/atspi/registry"
+#define SPI2_DBUS_PATH_ROOT		"/org/a11y/atspi/accessible/root"
+#define SPI2_DBUS_PATH_DEC		SPI2_DBUS_PATH_REG "/deviceeventcontroller"
+#define SPI2_DBUS_INTERFACE_DEC		SPI2_DBUS_INTERFACE".DeviceEventController"
+#define SPI2_DBUS_INTERFACE_DEL		SPI2_DBUS_INTERFACE".DeviceEventListener"
+#define SPI2_DBUS_INTERFACE_EVENT	SPI2_DBUS_INTERFACE".Event"
+#define SPI2_DBUS_INTERFACE_TREE	SPI2_DBUS_INTERFACE".Tree"
+#define SPI2_DBUS_INTERFACE_TEXT	SPI2_DBUS_INTERFACE".Text"
+#define SPI2_DBUS_INTERFACE_ACCESSIBLE	SPI2_DBUS_INTERFACE".Accessible"
+
+
+#define ATSPI_STATE_ACTIVE 1
+#define ATSPI_STATE_FOCUSED 12
+
+#ifdef HAVE_X11_KEYSYM_H
+#include <X11/keysym.h>
+#endif /* HAVE_X11_KEYSYM_H */
+
+#ifdef HAVE_PKG_X11
+#include "xsel.h"
+#include "clipboard.h"
+#endif /* HAVE_PKG_X11 */
+
+#include "log.h"
+#include "report.h"
+#include "parse.h"
+#include "thread.h"
+#include "brl_cmds.h"
+#include "async_handle.h"
+#include "async_io.h"
+#include "async_alarm.h"
+#include "async_event.h"
+
+typedef enum {
+  PARM_RELEASE,
+  PARM_TYPE
+} ScreenParameters;
+#define SCRPARMS "release", "type"
+
+#include "scr_driver.h"
+
+typedef enum {
+  TYPE_ALL,
+  TYPE_TERMINAL,
+  TYPE_TEXT,
+  TYPE_COUNT
+} TypeValue;
+
+static unsigned int releaseScreen;
+static unsigned char typeFlags[TYPE_COUNT];
+
+static char *curSender;
+static char *curPath;
+static char *curRole;
+static ScreenContentQuality curQuality;
+
+static long curNumRows, curNumCols;
+static wchar_t **curRows;
+static long *curRowLengths;
+static long curCaret,curPosX,curPosY;
+
+static DBusConnection *bus = NULL;
+
+static int updated;
+
+#ifdef HAVE_PKG_X11
+static Display *dpy;
+static XSelData xselData;
+static char *clipboardContent;
+#endif /* HAVE_PKG_X11 */
+
+/* having our own implementation is much more independant on locales */
+
+typedef struct {
+  int remaining;
+  wint_t current;
+} my_mbstate_t;
+
+static my_mbstate_t internal;
+
+static size_t my_mbrtowc(wchar_t *pwc, const char *s, size_t n, my_mbstate_t *ps) {
+  const unsigned char *c = (const unsigned char *) s;
+  int read = 0;
+  if (!c) {
+    if (ps->remaining) {
+      errno = EILSEQ;
+      return (size_t)(-1);
+    }
+    return 0;
+  }
+
+  if (n && !ps->remaining) {
+    /* initial state */
+    if (!(*c&0x80)) {
+      /* most frequent case: ascii */
+      if (pwc)
+	*pwc = *c;
+      if (!*c)
+	return 0;
+      return 1;
+    } else if (!(*c&0x40)) {
+      /* utf-8 char continuation, shouldn't happen with remaining == 0 ! */
+      goto error;
+    } else {
+      /* new utf-8 char, get remaining chars */
+      read = 1;
+      if (!(*c&0x20)) {
+	ps->remaining = 1;
+	ps->current = *c&((1<<5)-1);
+      } else if (!(*c&0x10)) {
+	ps->remaining = 2;
+	ps->current = *c&((1<<4)-1);
+      } else if (!(*c&0x08)) {
+	ps->remaining = 3;
+	ps->current = *c&((1<<3)-1);
+      } else if (!(*c&0x04)) {
+	ps->remaining = 4;
+	ps->current = *c&((1<<2)-1);
+      } else if (!(*c&0x02)) {
+	ps->remaining = 5;
+	ps->current = *c&((1<<1)-1);
+      } else
+	/* 0xff and 0xfe are not allowed */
+	goto error;
+      c++;
+    }
+  }
+  /* looking for continuation chars */
+  while (n-read) {
+    if ((*c&0xc0) != 0X80)
+      /* not continuation char, error ! */
+      goto error;
+    /* utf-8 char continuation */
+    ps->current = (ps->current<<6) | (*c&((1<<6)-1));
+    read++;
+    if (!(--ps->remaining)) {
+      if (pwc)
+	*pwc = ps->current;
+      if (!ps->current)
+	/* shouldn't coded this way, but well... */
+	return 0;
+      return read;
+    }
+    c++;
+  }
+  return (size_t)(-2);
+error:
+  errno = EILSEQ;
+  return (size_t)(-1);
+}
+
+static size_t my_mbsrtowcs(wchar_t *dest, const char **src, size_t len, my_mbstate_t *ps) {
+  int put = 0;
+  size_t skip;
+  wchar_t buf,*bufp;
+
+  if (!ps) ps = &internal;
+  if (dest)
+    bufp = dest;
+  else
+    bufp = &buf;
+
+  while (len-put || !dest) {
+    skip = my_mbrtowc(bufp, *src, 6, ps);
+    switch (skip) {
+      case (size_t)(-2):
+        errno = EILSEQ; /* shouldn't happen ! */
+        /* fall through */
+      case (size_t)(-1):
+        return (size_t)(-1);
+
+      case 0:
+        *src = NULL;
+        return put;
+    }
+    *src += skip;
+    if (dest) bufp++;
+    put++;
+  }
+  return put;
+}
+
+static size_t my_mbrlen(const char *s, size_t n, my_mbstate_t *ps) {
+  return my_mbrtowc(NULL, s, n, ps?ps:&internal);
+}
+
+static size_t my_mbslen(const char *s, size_t n) {
+  my_mbstate_t ps;
+  size_t ret=0;
+  size_t eaten;
+  memset(&ps,0,sizeof(ps));
+  while(n) {
+    if ((ssize_t)(eaten = my_mbrlen(s,n,&ps))<0)
+      return eaten;
+    if (!(eaten)) return ret;
+    s+=eaten;
+    n-=eaten;
+    ret++;
+  }
+  return ret;
+}
+
+static void addRows(long pos, long num) {
+  curNumRows += num;
+  curRows = realloc(curRows,curNumRows*sizeof(*curRows));
+  curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths));
+  memmove(curRows      +pos+num,curRows      +pos,(curNumRows-(pos+num))*sizeof(*curRows));
+  memmove(curRowLengths+pos+num,curRowLengths+pos,(curNumRows-(pos+num))*sizeof(*curRowLengths));
+}
+
+static void delRows(long pos, long num) {
+  long y;
+  for (y=pos;y<pos+num;y++)
+    free(curRows[y]);
+  memmove(curRows      +pos,curRows      +pos+num,(curNumRows-(pos+num))*sizeof(*curRows));
+  memmove(curRowLengths+pos,curRowLengths+pos+num,(curNumRows-(pos+num))*sizeof(*curRowLengths));
+  curNumRows -= num;
+  curRows = realloc(curRows,curNumRows*sizeof(*curRows));
+  curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths));
+}
+
+static int
+processParameters_AtSpi2Screen (char **parameters) {
+  releaseScreen = 1;
+  {
+    const char *parameter = parameters[PARM_RELEASE];
+
+    if (*parameter) {
+      if (!validateYesNo(&releaseScreen, parameter)) {
+        logMessage(LOG_WARNING, "invalid release screen setting: %s", parameter);
+      }
+    }
+  }
+
+  {
+    const char *parameter = parameters[PARM_TYPE];
+
+    for (unsigned int index=0; index<TYPE_COUNT; index+=1) {
+      typeFlags[index] = 0;
+    }
+
+    if (*parameter) {
+      if (!isAbbreviation("default", parameter)) {
+        int count;
+        char **types = splitString(parameter, '+', &count);
+
+        if (types) {
+          static const char *const choices[] = {
+            [TYPE_ALL] = "all",
+            [TYPE_TERMINAL] = "terminal",
+            [TYPE_TEXT] = "text",
+            [TYPE_COUNT] = NULL
+          };
+
+          for (unsigned int index=0; index<count; index+=1) {
+            const char *type = types[index];
+            unsigned int choice;
+
+            if (!validateChoice(&choice, type, choices)) {
+              logMessage(LOG_WARNING, "%s: %s", "invalid widget type", type);
+            } else if ((choice == TYPE_ALL) && (index > 0)) {
+              logMessage(LOG_WARNING, "widget type is mutually exclusive: %s", type);
+            } else if (typeFlags[choice] || typeFlags[TYPE_ALL]) {
+              logMessage(LOG_WARNING, "widget type specified more than once: %s", type);
+            } else {
+              typeFlags[choice] = 1;
+            }
+          }
+
+          deallocateStrings(types);
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+/* Creates a method call message */
+static DBusMessage *
+new_method_call(const char *sender, const char *path, const char *interface, const char *method)
+{
+  DBusError error;
+  DBusMessage *msg;
+
+  dbus_error_init(&error);
+  msg = dbus_message_new_method_call(sender, path, interface, method);
+  if (dbus_error_is_set(&error)) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "error while making %s message: %s %s", method, error.name, error.message);
+
+    dbus_error_free(&error);
+    return NULL;
+  }
+  if (!msg) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "no memory while making %s message", method);
+    return NULL;
+  }
+  return msg;
+}
+
+/* Sends a method call message, and returns the reply, if any. This unrefs the message.  */
+static DBusMessage *
+send_with_reply_and_block(DBusConnection *bus, DBusMessage *msg, int timeout_ms, const char *doing)
+{
+  DBusError error;
+  DBusMessage *reply;
+
+  dbus_error_init(&error);
+  reply = dbus_connection_send_with_reply_and_block(bus, msg, timeout_ms, &error);
+  dbus_message_unref(msg);
+  if (dbus_error_is_set(&error)) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "error while %s: %s %s", doing, error.name, error.message);
+    dbus_error_free(&error);
+    return NULL;
+  }
+  if (!reply) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "timeout while %s", doing);
+    return NULL;
+  }
+  if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "error while %s", doing);
+    dbus_message_unref(reply);
+    return NULL;
+  }
+  return reply;
+}
+
+static void findPosition(long position, long *px, long *py) {
+  long offset=0, newoffset, x, y;
+  /* XXX: I don't know what they do with necessary combining accents */
+  for (y=0; y<curNumRows; y++) {
+    if ((newoffset = offset + curRowLengths[y]) > position)
+      break;
+    offset = newoffset;
+  }
+  if (y==curNumRows) {
+    if (!curNumRows) {
+      y = 0;
+      x = 0;
+    } else {
+      /* this _can_ happen, when deleting while caret is at the end of the
+       * terminal: caret position is only updated afterwards... In the
+       * meanwhile, keep caret at the end of last line. */
+      y = curNumRows-1;
+      x = curRowLengths[y];
+    }
+  } else
+    x = position-offset;
+  *px = x;
+  *py = y;
+}
+
+static long findCoordinates(long xx, long yy) {
+  long offset=0, y;
+  /* XXX: I don't know what they do with necessary combining accents */
+  if (yy >= curNumRows) {
+    return -1;
+  }
+  for (y=0; y<yy; y++) {
+    offset += curRowLengths[y];
+  }
+  if (xx >= curRowLengths[y])
+    xx = curRowLengths[y]-1;
+  return offset + xx;
+}
+
+static void caretPosition(long caret) {
+  if (caret < 0) {
+    caret = 0;
+  }
+  findPosition(caret,&curPosX,&curPosY);
+  curCaret = caret;
+}
+
+static void finiTerm(void) {
+  unsigned i;
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "end of term %s:%s",curSender,curPath);
+  free(curSender);
+  curSender = NULL;
+  free(curPath);
+  curPath = NULL;
+  free(curRole);
+  curRole = NULL;
+  curPosX = curPosY = 0;
+  if (curRows) {
+    for (i=0;i<curNumRows;i++)
+      free(curRows[i]);
+    free(curRows);
+  }
+  curRows = NULL;
+  free(curRowLengths);
+  curRowLengths = NULL;
+  curNumCols = curNumRows = 0;
+}
+
+#define ROLE_TERMINAL "terminal"
+#define ROLE_TEXT "text"
+
+static int
+isRole (const char *role) {
+  if (!curRole) return 0;
+  return strcmp(curRole, role) == 0;
+}
+
+static int
+isTerminal (void) {
+  return isRole(ROLE_TERMINAL);
+}
+
+static int
+isText (void) {
+  return isRole(ROLE_TEXT);
+}
+
+/* Get the role of an AT-SPI2 object */
+static char *getRole(const char *sender, const char *path) {
+  const char *text;
+  char *res = NULL;
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter;
+
+  msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetRoleName");
+  if (!msg)
+    return NULL;
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting role");
+  if (!reply)
+    return NULL;
+
+  dbus_message_iter_init(reply, &iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "GetRoleName didn't return a string but '%c'", dbus_message_iter_get_arg_type(&iter));
+    goto out;
+  }
+  dbus_message_iter_get_basic(&iter, &text);
+  res = strdup(text);
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Get the get interfaces of an AT-SPI2 object */
+static int getHasTextInterface(const char *sender, const char *path) {
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter;
+  DBusMessageIter iter_array;
+  int ret = 0;
+
+  msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetInterfaces");
+  if (!msg)
+    return 0;
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting interfaces");
+  if (!reply)
+    return 0;
+
+  dbus_message_iter_init(reply, &iter);
+  dbus_message_iter_recurse (&iter, &iter_array);
+  while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
+  {
+    const char *iface;
+    dbus_message_iter_get_basic (&iter_array, &iface);
+
+    if (!strcmp (iface, "org.a11y.atspi.Text"))
+    {
+      ret = 1;
+      goto out;
+    }
+    dbus_message_iter_next (&iter_array);
+  }
+
+out:
+  dbus_message_unref(reply);
+  return ret;
+}
+
+static char *getName(const char *sender, const char *path) {
+  const char *name;
+  char *res = NULL;
+  DBusMessage *msg, *reply = NULL;
+  const char *interface = SPI2_DBUS_INTERFACE_ACCESSIBLE;
+  const char *property = "Name";
+  DBusMessageIter iter, iter_variant;
+
+  msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get");
+  if (!msg)
+    return NULL;
+  dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting name");
+  if (!reply)
+    return NULL;
+
+  dbus_message_iter_init(reply, &iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "getName didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
+    goto out;
+  }
+  dbus_message_iter_recurse(&iter, &iter_variant);
+  if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_STRING) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "getName didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
+    goto out;
+  }
+  dbus_message_iter_get_basic(&iter_variant, &name);
+  res = strdup(name);
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Get the text of an AT-SPI2 object */
+static char *getText(const char *sender, const char *path) {
+  const char *text;
+  char *res = NULL;
+  DBusMessage *msg, *reply;
+  dbus_int32_t begin = 0;
+  dbus_int32_t end = -1;
+  DBusMessageIter iter;
+
+  msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_TEXT, "GetText");
+  if (!msg)
+    return NULL;
+  dbus_message_append_args(msg, DBUS_TYPE_INT32, &begin, DBUS_TYPE_INT32, &end, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting text");
+  if (!reply)
+    return NULL;
+
+  dbus_message_iter_init(reply, &iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "GetText didn't return a string but '%c'", dbus_message_iter_get_arg_type(&iter));
+    goto out;
+  }
+  dbus_message_iter_get_basic(&iter, &text);
+  res = strdup(text);
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Get the caret of an AT-SPI2 object */
+static dbus_int32_t getCaret(const char *sender, const char *path) {
+  dbus_int32_t res = -1;
+  DBusMessage *msg, *reply = NULL;
+  const char *interface = SPI2_DBUS_INTERFACE_TEXT;
+  const char *property = "CaretOffset";
+  DBusMessageIter iter, iter_variant;
+
+  msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get");
+  if (!msg)
+    return -1;
+  dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting caret");
+  if (!reply)
+    return -1;
+
+  dbus_message_iter_init(reply, &iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "getCaret didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
+    goto out;
+  }
+  dbus_message_iter_recurse(&iter, &iter_variant);
+  if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_INT32) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "getCaret didn't return an int32 but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
+    goto out;
+  }
+  dbus_message_iter_get_basic(&iter_variant, &res);
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "Got caret %d", res);
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Switched to a new terminal, restart from scratch */
+static void restartTerm(const char *sender, const char *path) {
+  char *text = getText(sender, path);
+  if (!text) {
+    text = getName(sender, path);
+    if (!text) return;
+  }
+
+  char *c,*d;
+  const char *e;
+  long i,len;
+
+  curSender = strdup(sender);
+  curPath = strdup(path);
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "new term %s:%s with text %s", curSender, curPath, text);
+
+  if (curRows) {
+    for (i=0;i<curNumRows;i++)
+      free(curRows[i]);
+    free(curRows);
+  }
+  curNumRows = 0;
+  free(curRowLengths);
+  c = text;
+  while (*c) {
+    curNumRows++;
+    if (!(c = strchr(c,'\n')))
+      break;
+    c++;
+  }
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "%ld rows",curNumRows);
+  curRows = malloc(curNumRows * sizeof(*curRows));
+  curRowLengths = malloc(curNumRows * sizeof(*curRowLengths));
+  i = 0;
+  curNumCols = 0;
+  for (c = text; *c; c = d+1) {
+    d = strchr(c,'\n');
+    if (d)
+      *d = 0;
+    e = c;
+    curRowLengths[i] = (len = my_mbsrtowcs(NULL,&e,0,NULL)) + (d != NULL);
+    if (len > curNumCols)
+      curNumCols = len;
+    else if (len < 0) {
+      if (len==-2)
+	logMessage(LOG_ERR,"unterminated sequence %s",c);
+      else if (len==-1)
+	logSystemError("mbrlen");
+      curRowLengths[i] = (len = 0) + (d != NULL);
+    }
+    curRows[i] = malloc((len + (d!=NULL)) * sizeof(*curRows[i]));
+    e = c;
+    my_mbsrtowcs(curRows[i],&e,len,NULL);
+    if (d)
+      curRows[i][len]='\n';
+    else
+      break;
+    i++;
+  }
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "%ld cols",curNumCols);
+  caretPosition(getCaret(sender, path));
+  free(text);
+}
+
+/* Switched to a new object, check whether we want to read it, and if so, restart with it */
+static void tryRestartTerm(const char *sender, const char *path) {
+  if (curPath) finiTerm();
+  restartTerm(sender, path);
+
+  curRole = getRole(sender, path);
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "state changed focus to role %s", curRole);
+
+  curQuality = getHasTextInterface(sender, path)? SCQ_POOR: SCQ_NONE;
+  unsigned char requested = typeFlags[TYPE_ALL];
+
+  if (!requested) {
+    if (isTerminal()) {
+      curQuality = SCQ_GOOD;
+      requested = typeFlags[TYPE_TERMINAL];
+    } else if (isText()) {
+      curQuality = SCQ_FAIR;
+      requested = typeFlags[TYPE_TEXT];
+    }
+  }
+
+  if (requested) curQuality = SCQ_GOOD;     
+}
+
+/* Get the state of an object */
+static dbus_uint32_t *getState(const char *sender, const char *path)
+{
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter, iter_array;
+  dbus_uint32_t *states, *ret = NULL;
+  int count;
+
+  msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetState");
+  if (!msg)
+    return NULL;
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting state");
+  if (!reply)
+    return NULL;
+
+  if (strcmp (dbus_message_get_signature (reply), "au") != 0)
+  {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "unexpected signature %s while getting active state", dbus_message_get_signature(reply));
+    goto out;
+  }
+  dbus_message_iter_init (reply, &iter);
+  dbus_message_iter_recurse (&iter, &iter_array);
+  dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
+  if (count != 2)
+  {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "unexpected signature %s while getting active state", dbus_message_get_signature(reply));
+    goto out;
+  }
+  ret = malloc(sizeof(*ret) * count);
+  memcpy(ret, states, sizeof(*ret) * count);
+
+out:
+  dbus_message_unref(reply);
+  return ret;
+}
+
+/* Check whether an ancestor of this object is active */
+static int checkActiveParent(const char *sender, const char *path) {
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter, iter_variant, iter_struct;
+  int res = 0;
+  const char *interface = SPI2_DBUS_INTERFACE_ACCESSIBLE;
+  const char *property = "Parent";
+  dbus_uint32_t *states;
+
+  msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get");
+  if (!msg)
+    return 0;
+  dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "checking active object");
+  if (!reply)
+    return 0;
+
+  if (strcmp (dbus_message_get_signature (reply), "v") != 0)
+  {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "unexpected signature %s while checking active object", dbus_message_get_signature(reply));
+    goto out;
+  }
+
+  dbus_message_iter_init (reply, &iter);
+  dbus_message_iter_recurse (&iter, &iter_variant);
+  dbus_message_iter_recurse (&iter_variant, &iter_struct);
+  dbus_message_iter_get_basic (&iter_struct, &sender);
+  dbus_message_iter_next (&iter_struct);
+  dbus_message_iter_get_basic (&iter_struct, &path);
+
+  states = getState(sender, path);
+  if (states) {
+    res = (states[0] & (1<<ATSPI_STATE_ACTIVE)) != 0 || checkActiveParent(sender, path);
+    free(states);
+  } else {
+    res = 0;
+  }
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Check whether this object is the focused object (which is way faster than
+ * browsing all objects of the desktop) */
+static int reinitTerm(const char *sender, const char *path) {
+  dbus_uint32_t *states = getState(sender, path);
+  int active = 0;
+
+  if (!states)
+    return 0;
+
+  /* Whether this widget is active */
+  active = (states[0] & (1<<ATSPI_STATE_ACTIVE)) != 0;
+
+  if (states[0] & (1<<ATSPI_STATE_FOCUSED)) {
+    free(states);
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "%s %s is focused!", sender, path);
+    /* This widget is focused */
+    if (active) {
+      /* And it is active, we are done.  */
+      tryRestartTerm(sender, path);
+      return 1;
+    } else {
+      /* Check that a parent is active.  */
+      return checkActiveParent(sender, path);
+    }
+  }
+
+  free(states);
+  return 0;
+}
+
+/* Try to find an active object among children of the given object */
+
+/* We need to take care of bogus applications which have children loops, so we
+ * need to compare newly found children with the list of ancestors */
+struct pathList {
+  const char *sender;
+  const char *path;
+  struct pathList *prev;
+  int loop;
+};
+static int findTerm(const char *sender, const char *path, int active, int depth, struct pathList *list);
+static int recurseFindTerm(const char *sender, const char *path, int active, int depth, struct pathList *list) {
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter, iter_array, iter_struct;
+  int res = 0;
+
+  msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetChildren");
+  if (!msg)
+    return 0;
+  reply = send_with_reply_and_block(bus, msg, 1000, "getting active object");
+  if (!reply)
+    return 0;
+
+  if (strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
+  {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "unexpected signature %s while getting active object", dbus_message_get_signature(reply));
+    goto out;
+  }
+  dbus_message_iter_init(reply, &iter);
+  dbus_message_iter_recurse (&iter, &iter_array);
+  while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
+  {
+    const char *childsender, *childpath;
+    struct pathList *cur;
+
+    dbus_message_iter_recurse (&iter_array, &iter_struct);
+    dbus_message_iter_get_basic (&iter_struct, &childsender);
+    dbus_message_iter_next (&iter_struct);
+    dbus_message_iter_get_basic (&iter_struct, &childpath);
+
+    /* Make sure that the child is not the same as an ancestor, to avoid
+     * recursing indefinitely.  */
+    for (cur = list; cur; cur = cur->prev)
+      if (!strcmp(childsender, cur->sender) && !strcmp(childpath, cur->path))
+      {
+	/* Loop detected, avoid continuing looking at this part of the tree
+	which is not actually a tree! */
+	cur->loop = 1;
+	break;
+      }
+    if (! cur)
+    {
+      struct pathList me = {
+	.sender = sender,
+	.path = path,
+	.prev = list,
+      };
+      if (findTerm(childsender, childpath, active, depth, &me))
+      {
+	res = 1;
+	goto out;
+      }
+      if (me.loop) {
+        /* There is a loop up to us.  Avoid continuing looking here which may
+         * entail an exponential number of lookups. */
+	break;
+      }
+    }
+
+    dbus_message_iter_next (&iter_array);
+  }
+
+out:
+  dbus_message_unref(reply);
+  return res;
+}
+
+/* Test whether this object is active, and if not recurse in its children */
+static int findTerm(const char *sender, const char *path, int active, int depth, struct pathList *list) {
+  dbus_uint32_t *states = getState(sender, path);
+
+  if (!states)
+    return 0;
+
+  if (states[0] & (1<<ATSPI_STATE_ACTIVE))
+    /* This application is active */
+    active = 1;
+
+  if (states[0] & (1<<ATSPI_STATE_FOCUSED) && active)
+  {
+    /* And this widget is focused */
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "%s %s is focused!", sender, path);
+    free(states);
+    tryRestartTerm(sender, path);
+    return 1;
+  }
+
+  free(states);
+  return recurseFindTerm(sender, path, active, depth+1, list);
+}
+
+/* Find out currently focused terminal, starting from registry */
+static void initTerm(void) {
+  recurseFindTerm(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_ROOT, 0, 0, NULL);
+}
+
+/* Handle incoming events */
+static void AtSpi2HandleEvent(const char *interface, DBusMessage *message)
+{
+  DBusMessageIter iter, iter_variant;
+  const char *detail;
+  dbus_int32_t detail1, detail2;
+  const char *member = dbus_message_get_member(message);
+  const char *sender = dbus_message_get_sender(message);
+  const char *path = dbus_message_get_path(message);
+  int StateChanged_focused;
+
+  dbus_message_iter_init(message, &iter);
+
+  if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRUCT) {
+    /* skip struct */
+    dbus_message_iter_next(&iter);
+  }
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "message detail not a string but '%c'", dbus_message_iter_get_arg_type(&iter));
+    return;
+  }
+  dbus_message_iter_get_basic(&iter, &detail);
+
+  dbus_message_iter_next(&iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "message detail1 not an int32 but '%c'", dbus_message_iter_get_arg_type(&iter));
+    return;
+  }
+  dbus_message_iter_get_basic(&iter, &detail1);
+
+  dbus_message_iter_next(&iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "message detail2 not an int32 but '%c'", dbus_message_iter_get_arg_type(&iter));
+    return;
+  }
+  dbus_message_iter_get_basic(&iter, &detail2);
+
+  dbus_message_iter_next(&iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "message detail2 not a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
+    return;
+  }
+  dbus_message_iter_recurse(&iter, &iter_variant);
+
+  StateChanged_focused =
+       !strcmp(interface, "Object")
+    && !strcmp(member, "StateChanged")
+    && !strcmp(detail, "focused");
+
+  if (StateChanged_focused && !detail1) {
+    if (curSender && !strcmp(sender, curSender) && !strcmp(path, curPath))
+      finiTerm();
+  } else if (!strcmp(interface,"Focus") || (StateChanged_focused && detail1)) {
+    tryRestartTerm(sender, path);
+  } else if (!strcmp(interface, "Object") && !strcmp(member, "TextCaretMoved")) {
+    if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return;
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "caret move to %d", detail1);
+    caretPosition(detail1);
+  } else if (!strcmp(interface, "Object") && !strcmp(member, "TextChanged") && !strcmp(detail, "delete")) {
+    long x,y,toDelete = detail2;
+    const char *deleted;
+    long length = 0, toCopy;
+    long downTo; /* line that will provide what will follow x */
+    if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return;
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "delete %d from %d",detail2,detail1);
+    if (detail1 < 0) {
+      logMessage(LOG_ERR,"deleting %ld %d before beginning of text!", toDelete, detail1);
+      toDelete -= -detail1;
+      detail1 = 0;
+    }
+    if (toDelete <= 0) {
+      return;
+    }
+    findPosition(detail1,&x,&y);
+    if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_STRING) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "ergl, not string but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
+      return;
+    }
+    dbus_message_iter_get_basic(&iter_variant, &deleted);
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "'%s'",deleted);
+    downTo = y;
+    if (downTo < curNumRows)
+      length = curRowLengths[downTo];
+    while (x+toDelete >= length) {
+      downTo++;
+      if (downTo <= curNumRows - 1)
+	length += curRowLengths[downTo];
+      else {
+	/* imaginary extra line doesn't provide more length, and shouldn't need to ! */
+	if (x+toDelete > length) {
+	  logMessage(LOG_ERR,"deleting %ld %ld past end of text !", toDelete, x+toDelete - length);
+	  /* discarding */
+	  if (x > length)
+	    x = length;
+	  toDelete = length - x;
+	}
+	break; /* deleting up to end */
+      }
+    }
+    if (toDelete <= 0)
+      return;
+    if (length-toDelete>0) {
+      /* still something on line y */
+      if (y!=downTo) {
+	curRowLengths[y] = length-toDelete;
+	curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+      }
+      if ((toCopy = length-toDelete-x))
+	memmove(curRows[y]+x,curRows[downTo]+curRowLengths[downTo]-toCopy,toCopy*sizeof(*curRows[downTo]));
+      if (y==downTo) {
+	curRowLengths[y] = length-toDelete;
+	curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+      }
+    } else {
+      /* kills this line as well ! */
+      y--;
+    }
+    if (downTo>=curNumRows)
+      /* imaginary extra lines don't need to be deleted */
+      downTo=curNumRows-1;
+    if (downTo>y) {
+      delRows(y+1,downTo-y);
+    }
+    caretPosition(curCaret);
+  } else if (!strcmp(interface, "Object") && !strcmp(member, "TextChanged") && !strcmp(detail, "insert")) {
+    long len=detail2,semilen,x,y;
+    const char *added;
+    const char *adding,*c;
+    if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return;
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "insert %d from %d",detail2,detail1);
+    findPosition(detail1,&x,&y);
+    if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_STRING) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "ergl, not string but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
+      return;
+    }
+    if (detail1 < 0) {
+      logMessage(LOG_ERR,"adding %ld %d before beginning of text!", len, detail1);
+      detail1 = 0;
+    }
+    dbus_message_iter_get_basic(&iter_variant, &added);
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "'%s'",added);
+    adding = c = added;
+    if (y < curNumRows && x > curRowLengths[y]) {
+      logMessage(LOG_ERR,"adding %ld %ld past end of text!", len, x - curRowLengths[y]);
+      x = curRowLengths[y];
+    }
+    if (x && (c = strchr(adding,'\n'))) {
+      /* splitting line */
+      addRows(y,1);
+      semilen=my_mbslen(adding,c+1-adding);
+      curRowLengths[y]=x+semilen;
+      if (x+semilen-1>curNumCols)
+	curNumCols=x+semilen-1;
+
+      /* copy beginning */
+      curRows[y]=malloc(curRowLengths[y]*sizeof(*curRows[y]));
+      memcpy(curRows[y],curRows[y+1],x*sizeof(*curRows[y]));
+      /* add */
+      my_mbsrtowcs(curRows[y]+x,&adding,semilen,NULL);
+      len-=semilen;
+      adding=c+1;
+      /* shift end */
+      curRowLengths[y+1]-=x;
+      memmove(curRows[y+1],curRows[y+1]+x,curRowLengths[y+1]*sizeof(*curRows[y+1]));
+      x=0;
+      y++;
+    }
+    while ((c = strchr(adding,'\n'))) {
+      /* adding lines */
+      addRows(y,1);
+      semilen=my_mbslen(adding,c+1-adding);
+      curRowLengths[y]=semilen;
+      if (semilen-1>curNumCols)
+	curNumCols=semilen-1;
+      curRows[y]=malloc(semilen*sizeof(*curRows[y]));
+      my_mbsrtowcs(curRows[y],&adding,semilen,NULL);
+      len-=semilen;
+      adding=c+1;
+      y++;
+    }
+    if (len) {
+      /* still length to add on the line following it */
+      if (y==curNumRows) {
+	/* It won't insert ending \n yet */
+	addRows(y,1);
+	curRows[y]=NULL;
+	curRowLengths[y]=0;
+      }
+      curRowLengths[y] += len;
+      curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y]));
+      memmove(curRows[y]+x+len,curRows[y]+x,(curRowLengths[y]-(x+len))*sizeof(*curRows[y]));
+      my_mbsrtowcs(curRows[y]+x,&adding,len,NULL);
+      if (curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n')>curNumCols)
+	curNumCols=curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n');
+    }
+    caretPosition(curCaret);
+  } else {
+    return;
+  }
+  updated = 1;
+}
+
+static int closeX = 1;
+
+static void
+onScreenDriverFailure (const char *cause) {
+  logMessage(LOG_ERR, "screen driver failure: %s", cause);
+  closeX = 1;
+  brlttyInterrupt(WAIT_STOP);
+}
+
+#ifdef HAVE_PKG_X11
+/* Integration of X11 events with brltty monitors */
+static AsyncHandle a2XWatch;
+static ReportListenerInstance *coreSelUpdatedListener;
+static int settingClipboard;
+
+/* Called when X selection got updated, update the BRLTTY clipboard content */
+void a2XSelUpdated(const char *data, unsigned long size) {
+  if (!data) return;
+
+  char content[size + 1];
+  memcpy(content, data, size);
+  content[size] = 0;
+
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER), "X Selection got '%s'", content);
+  settingClipboard = 1;
+  setMainClipboardContent(content);
+  settingClipboard = 0;
+}
+
+#ifdef HAVE_PTHREAD_ATFORK
+/* Drop X connection without shutting down its resources. Needed at fork. */
+void a2DropX(void) {
+  close(XConnectionNumber(dpy));
+  dpy = NULL;
+}
+#endif /* HAVE_PTHREAD_ATFORK */
+
+/* Called when X events are available, process them */
+ASYNC_MONITOR_CALLBACK(a2ProcessX) {
+  XEvent ev;
+
+  if (closeX) {
+    asyncCancelRequest(a2XWatch);
+    a2XWatch = NULL;
+    return 1;
+  }
+
+  while (XPending(dpy)) {
+    XNextEvent(dpy, &ev);
+    XSelProcess(dpy, &xselData, &ev, clipboardContent, a2XSelUpdated);
+  }
+
+  return 1;
+}
+
+/* Called when BRLTTY selection got updated, update the X clipboard content */
+REPORT_LISTENER(a2CoreSelUpdated) {
+  const ApiParameterUpdatedReport *report = parameters->reportData;
+  if (report->parameter != BRLAPI_PARAM_CLIPBOARD_CONTENT) return;
+  if (settingClipboard) return;
+  char *newContent = getMainClipboardContent();
+
+  if (newContent) {
+    if (!clipboardContent || (strcmp(clipboardContent, newContent) != 0)) {
+      free(clipboardContent);
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER), "core Selection got '%s'", newContent);
+      clipboardContent = newContent;
+      XSelSet(dpy, &xselData);
+    } else {
+      free(newContent);
+    }
+  }
+}
+
+static int ErrorHandler(Display *dpy, XErrorEvent *ev) {
+  char buffer[128];
+  XGetErrorText(dpy, ev->error_code, buffer, sizeof(buffer));
+  logMessage(LOG_ERR, "X Error %d, %s", ev->type, buffer);
+  logMessage(LOG_ERR, "resource %#010lx, req %u:%u",ev->resourceid,ev->request_code,ev->minor_code);
+  onScreenDriverFailure("X error");
+  return 0;
+}
+
+static int IOErrorHandler(Display *dpy) {
+  onScreenDriverFailure("X I/O error");
+  return 0;
+}
+
+#ifdef HAVE_XSETIOERROREXITHANDLER
+static void IOErrorExitHandler(Display *dpy, void *data) {
+  onScreenDriverFailure("X I/O Error Exit");
+  return;
+}
+#endif
+
+#endif /* HAVE_PKG_X11 */
+
+/* Integration of DBus watches with brltty monitors */
+
+struct a2Watch
+{
+  AsyncHandle input_monitor;
+  AsyncHandle output_monitor;
+  DBusWatch *watch;
+};
+
+int a2ProcessWatch(const AsyncMonitorCallbackParameters *parameters, int flags)
+{
+  struct a2Watch *a2Watch = parameters->data;
+  DBusWatch *watch = a2Watch->watch;
+  /* Read/Write on socket */
+  dbus_watch_handle(watch, parameters->error?DBUS_WATCH_ERROR:flags);
+  /* And process messages */
+  while (dbus_connection_dispatch(bus) != DBUS_DISPATCH_COMPLETE)
+    ;
+  if (updated)
+  {
+    updated = 0;
+    mainScreenUpdated();
+  }
+  return dbus_watch_get_enabled(watch);
+}
+
+ASYNC_MONITOR_CALLBACK(a2ProcessInput) {
+  if (a2ProcessWatch(parameters, DBUS_WATCH_READABLE)) return 1;
+
+  struct a2Watch *a2Watch = parameters->data;
+  asyncDiscardHandle(a2Watch->input_monitor);
+  a2Watch->input_monitor = NULL;
+  return 0;
+}
+
+ASYNC_MONITOR_CALLBACK(a2ProcessOutput) {
+  if (a2ProcessWatch(parameters, DBUS_WATCH_WRITABLE)) return 1;
+
+  struct a2Watch *a2Watch = parameters->data;
+  asyncDiscardHandle(a2Watch->output_monitor);
+  a2Watch->output_monitor = NULL;
+  return 0;
+}
+
+dbus_bool_t a2AddWatch(DBusWatch *watch, void *data)
+{
+  struct a2Watch *a2Watch = calloc(1, sizeof(*a2Watch));
+  a2Watch->watch = watch;
+  int flags = dbus_watch_get_flags(watch);
+  if (dbus_watch_get_enabled(watch))
+  {
+    if (flags & DBUS_WATCH_READABLE)
+      asyncMonitorFileInput(&a2Watch->input_monitor, dbus_watch_get_unix_fd(watch), a2ProcessInput, a2Watch);
+    if (flags & DBUS_WATCH_WRITABLE)
+      asyncMonitorFileOutput(&a2Watch->output_monitor, dbus_watch_get_unix_fd(watch), a2ProcessOutput, a2Watch);
+  }
+  dbus_watch_set_data(watch, a2Watch, NULL);
+  return TRUE;
+}
+
+void a2RemoveWatch(DBusWatch *watch, void *data)
+{
+  struct a2Watch *a2Watch = dbus_watch_get_data(watch);
+  dbus_watch_set_data(watch, NULL, NULL);
+  if (a2Watch->input_monitor)
+    asyncCancelRequest(a2Watch->input_monitor);
+  if (a2Watch->output_monitor)
+    asyncCancelRequest(a2Watch->output_monitor);
+  free(a2Watch);
+}
+
+void a2WatchToggled(DBusWatch *watch, void *data)
+{
+  if (dbus_watch_get_enabled(watch)) {
+    if (!dbus_watch_get_data(watch))
+      a2AddWatch(watch, data);
+  } else {
+    if (dbus_watch_get_data(watch))
+      a2RemoveWatch(watch, data);
+  }
+}
+
+/* Integration of DBus timeouts with brltty monitors */
+
+struct a2Timeout
+{
+  AsyncHandle monitor;
+  DBusTimeout *timeout;
+};
+
+ASYNC_ALARM_CALLBACK(a2ProcessTimeout)
+{
+  struct a2Timeout *a2Timeout = parameters->data;
+  DBusTimeout *timeout = a2Timeout->timeout;
+  /* Process timeout */
+  dbus_timeout_handle(timeout);
+  /* And process messages */
+  while (dbus_connection_dispatch(bus) != DBUS_DISPATCH_COMPLETE)
+    ;
+  if (updated)
+  {
+    updated = 0;
+    mainScreenUpdated();
+  }
+  asyncDiscardHandle(a2Timeout->monitor);
+  a2Timeout->monitor = NULL;
+  if (dbus_timeout_get_enabled(timeout))
+    /* Still enabled, requeue it */
+    asyncNewRelativeAlarm(&a2Timeout->monitor, dbus_timeout_get_interval(timeout), a2ProcessTimeout, a2Timeout);
+}
+
+dbus_bool_t a2AddTimeout(DBusTimeout *timeout, void *data)
+{
+  struct a2Timeout *a2Timeout = calloc(1, sizeof(*a2Timeout));
+  a2Timeout->timeout = timeout;
+  if (dbus_timeout_get_enabled(timeout))
+    asyncNewRelativeAlarm(&a2Timeout->monitor, dbus_timeout_get_interval(timeout), a2ProcessTimeout, a2Timeout);
+  dbus_timeout_set_data(timeout, a2Timeout, NULL);
+  return TRUE;
+}
+
+void a2RemoveTimeout(DBusTimeout *timeout, void *data)
+{
+  struct a2Timeout *a2Timeout = dbus_timeout_get_data(timeout);
+  dbus_timeout_set_data(timeout, NULL, NULL);
+  if (a2Timeout->monitor)
+    asyncCancelRequest(a2Timeout->monitor);
+  free(a2Timeout);
+}
+
+void a2TimeoutToggled(DBusTimeout *timeout, void *data)
+{
+  if (dbus_timeout_get_enabled(timeout)) {
+    if (!dbus_timeout_get_data(timeout))
+      a2AddTimeout(timeout, data);
+  } else {
+    if (dbus_timeout_get_data(timeout))
+      a2RemoveTimeout(timeout, data);
+  }
+}
+
+/* Driver construction / destruction */
+
+static int addWatch(const char *message, const char *event) {
+  DBusError error;
+  DBusMessage *msg, *reply;
+
+  dbus_error_init(&error);
+  dbus_bus_add_match(bus, message, &error);
+  if (dbus_error_is_set(&error)) {
+    logMessage(LOG_ERR, "error while adding watch %s: %s %s", message, error.name, error.message);
+    dbus_error_free(&error);
+    return 0;
+  }
+ 
+  if (!event)
+    return 1;
+
+  /* Register as event listener. */
+  msg = new_method_call(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_REG, SPI2_DBUS_INTERFACE_REG, "RegisterEvent");
+  if (!msg)
+    return 0;
+  dbus_message_append_args(msg, DBUS_TYPE_STRING, &event, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "registering listener");
+  if (!reply)
+    return 0;
+
+  dbus_message_unref(reply);
+  return 1;
+}
+
+static int
+addWatches (void) {
+  typedef struct {
+    const char *message;
+    const char *event;
+  } WatchEntry;
+
+  static const WatchEntry watchTable[] = {
+    { .message = "type='method_call',interface='"SPI2_DBUS_INTERFACE_TREE"'",
+      .event = NULL
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Focus'",
+      .event = "focus"
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object'",
+      .event = "object"
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='ChildrenChanged'",
+      .event = "object:childrenchanged"
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='TextChanged'",
+      .event = "object:textchanged"
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='TextCaretMoved'",
+      .event = "object:textcaretmoved"
+    },
+
+    { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='StateChanged'",
+      .event = "object:statechanged"
+    },
+
+    { .message = NULL }
+  };
+
+  for (const WatchEntry *watch=watchTable; watch->message; watch+=1) {
+    if (!addWatch(watch->message, watch->event)) {
+      logMessage(LOG_ERR, "can't add watch %s %s", watch->message, watch->event);
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DBusHandlerResult AtSpi2Filter(DBusConnection *connection, DBusMessage *message, void *user_data)
+{
+  int type = dbus_message_get_type(message);
+  const char *interface = dbus_message_get_interface(message);
+  const char *member = dbus_message_get_member(message);
+
+  if (type == DBUS_MESSAGE_TYPE_SIGNAL) {
+    if (!strncmp(interface, SPI2_DBUS_INTERFACE_EVENT".", strlen(SPI2_DBUS_INTERFACE_EVENT"."))) {
+      AtSpi2HandleEvent(interface + strlen(SPI2_DBUS_INTERFACE_EVENT"."), message);
+    } else if (!strcmp(interface, DBUS_INTERFACE_LOCAL) &&
+               !strcmp(member, "Disconnected")) {
+      onScreenDriverFailure("DBus disconnected");
+    } else {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "unknown signal: Intf:%s Msg:%s", interface, member);
+    }
+  } else {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "unknown message: Type:%d Intf:%s Msg:%s", type, interface, member);
+  }
+
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static int
+construct_AtSpi2Screen (void) {
+  DBusError error;
+
+  dbus_error_init(&error);
+#ifdef HAVE_ATSPI_GET_A11Y_BUS
+  bus = atspi_get_a11y_bus();
+  if (!bus)
+#endif /* HAVE_ATSPI_GET_A11Y_BUS */
+  {
+    bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
+    if (dbus_error_is_set(&error)) {
+      logMessage(LOG_ERR, "can't get dbus session bus: %s %s", error.name, error.message);
+      dbus_error_free(&error);
+      goto noBus;
+    }
+  }
+
+  if (!bus) {
+    logMessage(LOG_ERR, "can't get dbus session bus");
+    goto noBus;
+  }
+
+  if (!dbus_connection_add_filter(bus, AtSpi2Filter, NULL, NULL)) {
+    logMessage(LOG_ERR, "can't add atspi2 filter");
+    goto noConnection;
+  }
+  if (!addWatches()) goto noWatches;
+
+  if (!curPath) {
+    initTerm();
+  } else if (!reinitTerm(curSender, curPath)) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "caching failed, restarting from scratch");
+    initTerm();
+  }
+
+  dbus_connection_set_watch_functions(bus, a2AddWatch, a2RemoveWatch, a2WatchToggled, NULL, NULL);
+  dbus_connection_set_timeout_functions(bus, a2AddTimeout, a2RemoveTimeout, a2TimeoutToggled, NULL, NULL);
+
+#ifdef HAVE_PKG_X11
+  closeX = 0;
+  dpy = XOpenDisplay(NULL);
+  if (dpy) {
+    XSetErrorHandler(ErrorHandler);
+    XSetIOErrorHandler(IOErrorHandler);
+#ifdef HAVE_XSETIOERROREXITHANDLER
+    XSetIOErrorExitHandler(dpy, IOErrorExitHandler, NULL);
+#endif
+    XSelInit(dpy, &xselData);
+    XFlush(dpy);
+#ifdef HAVE_PTHREAD_ATFORK
+    pthread_atfork(NULL, NULL, a2DropX);
+#endif /* HAVE_PTHREAD_ATFORK */
+    asyncMonitorFileInput(&a2XWatch, XConnectionNumber(dpy), a2ProcessX, NULL);
+    coreSelUpdatedListener = registerReportListener(REPORT_API_PARAMETER_UPDATED, a2CoreSelUpdated , NULL);
+  }
+#endif /* HAVE_PKG_X11 */
+
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER), "SPI2 initialized");
+  brlttyEnableInterrupt();
+  return 1;
+
+noWatches:
+
+noConnection:
+  dbus_connection_unref(bus);
+
+noBus:
+  onScreenDriverFailure("driver couldn't start");
+  return 0;
+}
+
+static void
+destruct_AtSpi2Screen (void) {
+  brlttyDisableInterrupt();
+#ifdef HAVE_PKG_X11
+  if (dpy) {
+    if (coreSelUpdatedListener) {
+      unregisterReportListener(coreSelUpdatedListener);
+      coreSelUpdatedListener = NULL;
+    }
+    if (a2XWatch) {
+      asyncCancelRequest(a2XWatch);
+      a2XWatch = NULL;
+    }
+    XCloseDisplay(dpy);
+    dpy = NULL;
+    free(clipboardContent);
+    clipboardContent = NULL;
+  }
+#endif /* HAVE_PKG_X11 */
+  dbus_connection_remove_filter(bus, AtSpi2Filter, NULL);
+  dbus_connection_close(bus);
+  dbus_connection_unref(bus);
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "SPI2 stopped");
+  finiTerm();
+}
+
+static int
+currentVirtualTerminal_AtSpi2Screen (void) {
+  return (curPath || !releaseScreen)? 0: SCR_NO_VT;
+}
+
+static const char msgNotAtSpi [] = "not an AT-SPI2 text widget";
+
+static int
+poll_AtSpi2Screen (void)
+{
+  return 0;
+}
+
+ASYNC_EVENT_CALLBACK(AtSpi2ScreenUpdated) {
+  mainScreenUpdated();
+}
+
+static int
+refresh_AtSpi2Screen (void)
+{
+  return 1;
+}
+
+static void
+describe_AtSpi2Screen (ScreenDescription *description) {
+  if (curPath) {
+    description->cols = curPosX>=curNumCols?curPosX+1:curNumCols;
+    description->rows = curNumRows?curNumRows:1;
+    description->posx = curPosX;
+    description->posy = curPosY;
+    description->quality = curQuality;
+  } else {
+    const char *message = msgNotAtSpi;
+    if (releaseScreen) description->unreadable = message;
+
+    description->rows = 1;
+    description->cols = strlen(message);
+    description->posx = 0;
+    description->posy = 0;
+    description->quality = SCQ_NONE;
+  }
+
+  description->number = currentVirtualTerminal_AtSpi2Screen();
+}
+
+static int
+readCharacters_AtSpi2Screen (const ScreenBox *box, ScreenCharacter *buffer) {
+  clearScreenCharacters(buffer, (box->height * box->width));
+
+  if (!curPath) {
+    setScreenMessage(box, buffer, msgNotAtSpi);
+    return 1;
+  }
+
+  if (!curNumCols || !curNumRows) return 0;
+  short cols = (curPosX >= curNumCols)? (curPosX + 1): curNumCols;
+  if (!validateScreenBox(box, cols, curNumRows)) return 0;
+
+  for (unsigned int y=0; y<box->height; y+=1) {
+    if (curRowLengths[box->top+y]) {
+      for (unsigned int x=0; x<box->width; x+=1) {
+        if (box->left+x < curRowLengths[box->top+y] - (curRows[box->top+y][curRowLengths[box->top+y]-1]==WC_C('\n'))) {
+          buffer[y*box->width+x].text = curRows[box->top+y][box->left+x];
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+enum key_type_e {
+  PRESS,
+  RELEASE,
+  PRESSRELEASE,
+  SYM
+};
+
+static int
+AtSpi2GenerateKeyboardEvent (dbus_uint32_t keysym, enum key_type_e key_type)
+{
+  DBusMessage *msg, *reply;
+  const char *s = "";
+
+  msg = new_method_call(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_DEC, SPI2_DBUS_INTERFACE_DEC, "GenerateKeyboardEvent");
+  if (!msg)
+    return 0;
+  dbus_message_append_args(msg, DBUS_TYPE_INT32, &keysym, DBUS_TYPE_STRING, &s, DBUS_TYPE_UINT32, &key_type, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "generating keyboard event");
+  if (!reply)
+    return 0;
+
+  return 1;
+}
+
+static int
+insertKey_AtSpi2Screen (ScreenKey key) {
+  long keysym;
+  int modMeta=0, modControl=0;
+
+  mapScreenKey(&key);
+  setScreenKeyModifiers(&key, SCR_KEY_CONTROL);
+
+  if (isSpecialKey(key)) {
+    switch (key & SCR_KEY_CHAR_MASK) {
+#ifdef HAVE_X11_KEYSYM_H
+      case SCR_KEY_ENTER:         keysym = XK_KP_Enter;  break;
+      case SCR_KEY_TAB:           keysym = XK_Tab;       break;
+      case SCR_KEY_BACKSPACE:     keysym = XK_BackSpace; break;
+      case SCR_KEY_ESCAPE:        keysym = XK_Escape;    break;
+      case SCR_KEY_CURSOR_LEFT:   keysym = XK_Left;      break;
+      case SCR_KEY_CURSOR_RIGHT:  keysym = XK_Right;     break;
+      case SCR_KEY_CURSOR_UP:     keysym = XK_Up;        break;
+      case SCR_KEY_CURSOR_DOWN:   keysym = XK_Down;      break;
+      case SCR_KEY_PAGE_UP:       keysym = XK_Page_Up;   break;
+      case SCR_KEY_PAGE_DOWN:     keysym = XK_Page_Down; break;
+      case SCR_KEY_HOME:          keysym = XK_Home;      break;
+      case SCR_KEY_END:           keysym = XK_End;       break;
+      case SCR_KEY_INSERT:        keysym = XK_Insert;    break;
+      case SCR_KEY_DELETE:        keysym = XK_Delete;    break;
+      case SCR_KEY_FUNCTION + 0:  keysym = XK_F1;        break;
+      case SCR_KEY_FUNCTION + 1:  keysym = XK_F2;        break;
+      case SCR_KEY_FUNCTION + 2:  keysym = XK_F3;        break;
+      case SCR_KEY_FUNCTION + 3:  keysym = XK_F4;        break;
+      case SCR_KEY_FUNCTION + 4:  keysym = XK_F5;        break;
+      case SCR_KEY_FUNCTION + 5:  keysym = XK_F6;        break;
+      case SCR_KEY_FUNCTION + 6:  keysym = XK_F7;        break;
+      case SCR_KEY_FUNCTION + 7:  keysym = XK_F8;        break;
+      case SCR_KEY_FUNCTION + 8:  keysym = XK_F9;        break;
+      case SCR_KEY_FUNCTION + 9:  keysym = XK_F10;       break;
+      case SCR_KEY_FUNCTION + 10: keysym = XK_F11;       break;
+      case SCR_KEY_FUNCTION + 11: keysym = XK_F12;       break;
+      case SCR_KEY_FUNCTION + 12: keysym = XK_F13;       break;
+      case SCR_KEY_FUNCTION + 13: keysym = XK_F14;       break;
+      case SCR_KEY_FUNCTION + 14: keysym = XK_F15;       break;
+      case SCR_KEY_FUNCTION + 15: keysym = XK_F16;       break;
+      case SCR_KEY_FUNCTION + 16: keysym = XK_F17;       break;
+      case SCR_KEY_FUNCTION + 17: keysym = XK_F18;       break;
+      case SCR_KEY_FUNCTION + 18: keysym = XK_F19;       break;
+      case SCR_KEY_FUNCTION + 19: keysym = XK_F20;       break;
+      case SCR_KEY_FUNCTION + 20: keysym = XK_F21;       break;
+      case SCR_KEY_FUNCTION + 21: keysym = XK_F22;       break;
+      case SCR_KEY_FUNCTION + 22: keysym = XK_F23;       break;
+      case SCR_KEY_FUNCTION + 23: keysym = XK_F24;       break;
+      case SCR_KEY_FUNCTION + 24: keysym = XK_F25;       break;
+      case SCR_KEY_FUNCTION + 25: keysym = XK_F26;       break;
+      case SCR_KEY_FUNCTION + 26: keysym = XK_F27;       break;
+      case SCR_KEY_FUNCTION + 27: keysym = XK_F28;       break;
+      case SCR_KEY_FUNCTION + 28: keysym = XK_F29;       break;
+      case SCR_KEY_FUNCTION + 29: keysym = XK_F30;       break;
+      case SCR_KEY_FUNCTION + 30: keysym = XK_F31;       break;
+      case SCR_KEY_FUNCTION + 31: keysym = XK_F32;       break;
+      case SCR_KEY_FUNCTION + 32: keysym = XK_F33;       break;
+      case SCR_KEY_FUNCTION + 33: keysym = XK_F34;       break;
+      case SCR_KEY_FUNCTION + 34: keysym = XK_F35;       break;
+#else /* HAVE_X11_KEYSYM_H */
+#warning insertion of non-character key presses not supported by this build - check that X11 protocol headers have been installed
+#endif /* HAVE_X11_KEYSYM_H */
+      default: logMessage(LOG_WARNING, "key not insertable: %04X", key); return 0;
+    }
+  } else {
+    wchar_t wc;
+
+    if (key & SCR_KEY_ALT_LEFT) {
+      key &= ~SCR_KEY_ALT_LEFT;
+      modMeta = 1;
+    }
+
+    if (key & SCR_KEY_CONTROL) {
+      key &= ~SCR_KEY_CONTROL;
+      modControl = 1;
+    }
+
+    wc = key & SCR_KEY_CHAR_MASK;
+    if (wc < 0x100)
+      keysym = wc; /* latin1 character */
+    else
+      keysym = 0x1000000 | wc;
+  }
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "inserting key: %04X -> %s%s%ld",
+             key,
+             (modMeta? "meta ": ""),
+             (modControl? "control ": ""),
+             keysym);
+
+  {
+    int ok = 0;
+
+#ifdef HAVE_X11_KEYSYM_H
+    if (!modMeta || AtSpi2GenerateKeyboardEvent(XK_Meta_L,PRESS)) {
+      if (!modControl || AtSpi2GenerateKeyboardEvent(XK_Control_L,PRESS)) {
+#endif /* HAVE_X11_KEYSYM_H */
+
+        if (AtSpi2GenerateKeyboardEvent(keysym,SYM)) {
+          ok = 1;
+        } else {
+          logMessage(LOG_WARNING, "key insertion failed.");
+        }
+
+#ifdef HAVE_X11_KEYSYM_H
+        if (modControl && !AtSpi2GenerateKeyboardEvent(XK_Control_L,RELEASE)) {
+          logMessage(LOG_WARNING, "control release failed.");
+          ok = 0;
+        }
+      } else {
+        logMessage(LOG_WARNING, "control press failed.");
+      }
+
+      if (modMeta && !AtSpi2GenerateKeyboardEvent(XK_Meta_L,RELEASE)) {
+        logMessage(LOG_WARNING, "meta release failed.");
+        ok = 0;
+      }
+    } else {
+      logMessage(LOG_WARNING, "meta press failed.");
+    }
+#endif /* HAVE_X11_KEYSYM_H */
+
+    return ok;
+  }
+}
+
+static int
+setSelection_AtSpi2Screen (int beginOffset, int endOffset) {
+  dbus_bool_t result;
+  DBusMessage *msg, *reply;
+  DBusMessageIter iter;
+  dbus_int32_t num = 0;
+  dbus_int32_t begin = beginOffset;
+  dbus_int32_t end = endOffset;
+
+  msg = new_method_call(curSender, curPath, SPI2_DBUS_INTERFACE_TEXT, "SetSelection");
+  if (!msg)
+    return 0;
+  dbus_message_append_args(msg, DBUS_TYPE_INT32, &num, DBUS_TYPE_INT32, &begin, DBUS_TYPE_INT32, &end, DBUS_TYPE_INVALID);
+  reply = send_with_reply_and_block(bus, msg, 1000, "setting selection");
+  if (!reply)
+    return 0;
+
+  dbus_message_iter_init(reply, &iter);
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "SetSelection didn't return a boolean but '%c'", dbus_message_iter_get_arg_type(&iter));
+    result = 0;
+    goto out;
+  }
+  dbus_message_iter_get_basic(&iter, &result);
+
+out:
+  dbus_message_unref(reply);
+  return result;
+}
+
+static int
+highlightRegion_AtSpi2Screen (int left, int right, int top, int bottom) {
+  int begin, end;
+
+  /* It is safe to play with selections only with terminals */
+  if (!isTerminal()) return 0;
+
+  if (top != bottom)
+    /* AtSpi selection only supports linear selection */
+    return 0;
+
+  begin = findCoordinates(left, top);
+  if (begin == -1)
+    return 0;
+  end = findCoordinates(right, bottom);
+  if (end == -1)
+    return 0;
+
+  return setSelection_AtSpi2Screen(begin, end+1);
+}
+
+static int
+unhighlightRegion_AtSpi2Screen (void) {
+  /* It is safe to play with selections only with terminals */
+  if (!isTerminal()) return 0;
+
+  return setSelection_AtSpi2Screen(0, 0);
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.poll = poll_AtSpi2Screen;
+  main->base.refresh = refresh_AtSpi2Screen;
+  main->base.describe = describe_AtSpi2Screen;
+  main->base.readCharacters = readCharacters_AtSpi2Screen;
+  main->base.insertKey = insertKey_AtSpi2Screen;
+  main->base.highlightRegion = highlightRegion_AtSpi2Screen;
+  main->base.unhighlightRegion = unhighlightRegion_AtSpi2Screen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_AtSpi2Screen;
+  main->processParameters = processParameters_AtSpi2Screen;
+  main->construct = construct_AtSpi2Screen;
+  main->destruct = destruct_AtSpi2Screen;
+}
diff --git a/Drivers/Screen/FileViewer/Makefile.in b/Drivers/Screen/FileViewer/Makefile.in
new file mode 100644
index 0000000..d2f7c89
--- /dev/null
+++ b/Drivers/Screen/FileViewer/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = fv
+DRIVER_NAME = FileViewer
+DRIVER_USAGE = file viewer
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/FileViewer/screen.c b/Drivers/Screen/FileViewer/screen.c
new file mode 100644
index 0000000..94b75b4
--- /dev/null
+++ b/Drivers/Screen/FileViewer/screen.c
@@ -0,0 +1,397 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "alert.h"
+#include "strfmt.h"
+#include "utf8.h"
+#include "brl_cmds.h"
+#include "embed.h"
+
+typedef enum {
+  PARM_FILE,
+} ScreenParameters;
+
+#define SCRPARMS "file"
+#include "scr_driver.h"
+
+static const char *filePath;
+static wchar_t *fileCharacters;
+
+typedef struct {
+  unsigned int offset;
+  unsigned int length;
+} LineDescriptor;
+
+static LineDescriptor *lineDescriptors;
+static unsigned int lineSize;
+static unsigned int lineCount;
+
+static int screenWidth;
+static int cursorOffset;
+
+static void
+destruct_FileViewerScreen (void) {
+  brlttyDisableInterrupt();
+
+  if (lineDescriptors) {
+    free(lineDescriptors);
+    lineDescriptors = NULL;
+  }
+
+  if (fileCharacters) {
+    free(fileCharacters);
+    fileCharacters = NULL;
+  }
+}
+
+static int
+processParameters_FileViewerScreen (char **parameters) {
+  filePath = parameters[PARM_FILE];
+  if (filePath && !*filePath) filePath = NULL;
+
+  return 1;
+}
+
+static int
+addLine (const wchar_t *from, const wchar_t *to) {
+  size_t lineLength = to - from;
+  if (lineLength > screenWidth) screenWidth = lineLength;
+
+  if (lineCount == lineSize) {
+    size_t newSize = lineSize? lineSize<<1: 0X80;
+    LineDescriptor *newArray = realloc(lineDescriptors, ARRAY_SIZE(lineDescriptors, newSize));
+
+    if (!newArray) {
+      logMallocError();
+      return 0;
+    }
+
+    lineDescriptors = newArray;
+    lineSize = newSize;
+  }
+
+  LineDescriptor *line = &lineDescriptors[lineCount++];
+  line->offset = from - fileCharacters;
+  line->length = to - from;
+
+  return 1;
+}
+
+static int
+setScreenContent (const char *text) {
+  unsigned int characterCount = countUtf8Characters(text);
+  fileCharacters = malloc(characterCount * sizeof(*fileCharacters));
+
+  if (fileCharacters) {
+    makeWcharsFromUtf8(text, fileCharacters, characterCount);
+
+    const wchar_t *current = fileCharacters;
+    const wchar_t *end = current + characterCount;
+
+    while (current < end) {
+      wchar_t *next = wcschr(current, WC_C('\n'));
+
+      if (!next) {
+        if (!addLine(current, end)) return 0;
+        break;
+      }
+
+      if (!addLine(current, next)) return 0;
+      current = next + 1;
+    }
+
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static int
+loadFile (void) {
+  const char *problem = NULL;
+
+  if (filePath) {
+    struct stat status;
+
+    if (stat(filePath, &status) != -1) {
+      size_t fileSize = status.st_size;
+      char *text = malloc(fileSize + 1);
+
+      if (text) {
+        int fileDescriptor = open(filePath, O_RDONLY);
+
+        if (fileDescriptor != -1) {
+          ssize_t result = read(fileDescriptor, text, fileSize);
+
+          if (result != -1) {
+            if (result < fileSize) fileSize = result;
+            text[fileSize] = 0;
+            setScreenContent(text);
+          } else {
+            problem = strerror(errno);
+          }
+
+          close(fileDescriptor);
+        } else {
+          problem = strerror(errno);
+        }
+
+        free(text);
+      } else {
+        problem = strerror(errno);
+      }
+    } else {
+      problem = strerror(errno);
+    }
+  } else {
+    problem = gettext("file not specified");
+    filePath = NULL;
+  }
+
+  if (!problem) return 1;
+  char log[0X100];
+  STR_BEGIN(log, sizeof(log));
+
+  if (filePath) STR_PRINTF("%s: ", filePath);
+  STR_PRINTF("%s", problem);
+
+  STR_END;
+  logMessage(LOG_WARNING, "%s", log);
+
+  setScreenContent(log);
+  return 0;
+}
+
+static int
+construct_FileViewerScreen (void) {
+  fileCharacters = NULL;
+
+  lineDescriptors = NULL;
+  lineSize = 0;
+  lineCount = 0;
+
+  screenWidth = 0;
+  cursorOffset = 0;
+
+  loadFile();
+  brlttyEnableInterrupt();
+  return 1;
+}
+
+static int
+poll_FileViewerScreen (void) {
+  return 0;
+}
+
+static int
+refresh_FileViewerScreen (void) {
+  return 1;
+}
+
+static int
+toScreenRow (int offset) {
+  return offset / screenWidth;
+}
+
+static int
+toScreenColumn (int offset) {
+  return offset % screenWidth;
+}
+
+static void
+describe_FileViewerScreen (ScreenDescription *description) {
+  description->rows = lineCount;
+  description->cols = screenWidth;
+  description->posy = toScreenRow(cursorOffset);
+  description->posx = toScreenColumn(cursorOffset);
+}
+
+static int
+readCharacters_FileViewerScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  if (validateScreenBox(box, screenWidth, lineCount)) {
+    ScreenCharacter *target = buffer;
+
+    for (unsigned int row=0; row<box->height; row+=1) {
+      const LineDescriptor *line = &lineDescriptors[box->top + row];
+
+      unsigned int from = box->left;
+      unsigned int to = from + box->width;
+
+      for (unsigned int column=from; column<to; column+=1) {
+        target->text = (column < line->length)? fileCharacters[line->offset + column]: WC_C(' ');
+        target->attributes = SCR_COLOUR_DEFAULT;
+        target += 1;
+      }
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+toScreenOffset (int row, int column) {
+  return (row * screenWidth) + column;
+}
+
+static int
+routeCursor_FileViewerScreen (int column, int row, int screen) {
+  cursorOffset = toScreenOffset(row, column);
+  return 1;
+}
+
+static void
+moveCursor (int amount) {
+  int newOffset = cursorOffset + amount;
+
+  if ((newOffset >= 0) && (newOffset < (lineCount * screenWidth))) {
+    cursorOffset = newOffset;
+  } else {
+    alert(ALERT_COMMAND_REJECTED);
+  }
+}
+
+static int
+isBlankRow (int row) {
+  const LineDescriptor *line = &lineDescriptors[row];
+  const wchar_t *character = &fileCharacters[line->offset];
+  const wchar_t *end = character + line->length;
+
+  while (character < end) {
+    if (!iswspace(*character)) return 0;
+    character += 1;
+  }
+
+  return 1;
+}
+
+static void
+findPreviousParagraph (void) {
+  int wasBlank = 1;
+  int row = toScreenRow(cursorOffset);
+
+  while (row > 0) {
+    int isBlank = isBlankRow(--row);
+
+    if (isBlank != wasBlank) {
+      if ((wasBlank = isBlank)) {
+        cursorOffset = toScreenOffset(row+1, 0);
+        return;
+      }
+    }
+  }
+
+  if (wasBlank) {
+    alert(ALERT_COMMAND_REJECTED);
+  } else {
+    cursorOffset = toScreenOffset(row, 0);
+  }
+}
+
+static void
+findNextParagraph (void) {
+  int wasBlank = 0;
+  int row = toScreenRow(cursorOffset);
+
+  while (row < lineCount) {
+    int isBlank = isBlankRow(row);
+
+    if (isBlank != wasBlank) {
+      if (!(wasBlank = isBlank)) {
+        cursorOffset = toScreenOffset(row, 0);
+        return;
+      }
+    }
+
+    row += 1;
+  }
+
+  alert(ALERT_COMMAND_REJECTED);
+}
+
+static int
+handleCommand_FileViewerScreen (int command) {
+  switch (command) {
+    case BRL_CMD_KEY(ESCAPE):
+      brlttyInterrupt(WAIT_STOP);
+      return 1;
+
+    case BRL_CMD_KEY(CURSOR_LEFT):
+      moveCursor(-1);
+      return 1;
+
+    case BRL_CMD_KEY(CURSOR_RIGHT):
+      moveCursor(1);
+      return 1;
+
+    case BRL_CMD_KEY(CURSOR_UP):
+      moveCursor(-screenWidth);
+      return 1;
+
+    case BRL_CMD_KEY(CURSOR_DOWN):
+      moveCursor(screenWidth);
+      return 1;
+
+    case BRL_CMD_KEY(PAGE_UP):
+      findPreviousParagraph();
+      return 1;
+
+    case BRL_CMD_KEY(PAGE_DOWN):
+      findNextParagraph();
+      return 1;
+
+    case BRL_CMD_KEY(HOME):
+      cursorOffset = 0;
+      return 1;
+
+    case BRL_CMD_KEY(END):
+      cursorOffset = toScreenOffset(lineCount-1, 0);
+      return 1;
+  }
+
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+
+  main->base.poll = poll_FileViewerScreen;
+  main->base.refresh = refresh_FileViewerScreen;
+
+  main->base.describe = describe_FileViewerScreen;
+  main->base.readCharacters = readCharacters_FileViewerScreen;
+
+  main->base.routeCursor = routeCursor_FileViewerScreen;
+  main->base.handleCommand = handleCommand_FileViewerScreen;
+
+  main->processParameters = processParameters_FileViewerScreen;
+  main->construct = construct_FileViewerScreen;
+  main->destruct = destruct_FileViewerScreen;
+}
diff --git a/Drivers/Screen/Grub/Makefile.in b/Drivers/Screen/Grub/Makefile.in
new file mode 100644
index 0000000..05b7286
--- /dev/null
+++ b/Drivers/Screen/Grub/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = gb
+DRIVER_NAME = Grub
+DRIVER_USAGE = Grub boot loader
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Grub/screen.c b/Drivers/Screen/Grub/screen.c
new file mode 100644
index 0000000..d810d82
--- /dev/null
+++ b/Drivers/Screen/Grub/screen.c
@@ -0,0 +1,28 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "scr_driver.h"
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+}
diff --git a/Drivers/Screen/Hurd/Makefile.in b/Drivers/Screen/Hurd/Makefile.in
new file mode 100644
index 0000000..42c870e
--- /dev/null
+++ b/Drivers/Screen/Hurd/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = hd
+DRIVER_NAME = Hurd
+DRIVER_USAGE = The Hurd
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Hurd/screen.c b/Drivers/Screen/Hurd/screen.c
new file mode 100644
index 0000000..66ec370
--- /dev/null
+++ b/Drivers/Screen/Hurd/screen.c
@@ -0,0 +1,447 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <hurd/console.h>
+
+#include "log.h"
+#include "brl_cmds.h"
+#include "utf8.h"
+
+#include "scr_driver.h"
+#include "screen.h"
+#include "unicode.h"
+
+static char *
+vtPath (const char *base, unsigned char vt) {
+  size_t length = strlen(base);
+  char buffer[length+1];
+  snprintf(buffer, length, base, vt);
+  buffer[length] = 0;
+  return strdup(buffer);
+}
+
+static int
+currentVt(void) {
+  char link[]=HURD_VCSDIR "/00";
+  char *c,*d;
+  int size,ret;
+  if ((size = readlink(HURD_CURVCSPATH,link,sizeof(link))) == -1) {
+    /* console may not be started yet or in X mode, don't flood */
+    if (errno != ENOENT && errno != ENOTDIR && errno != ENODEV)
+      logSystemError("reading " HURD_CURVCSPATH " link");
+    return 1;
+  }
+  link[size]='\0';
+  if (!(c = strrchr(link,'/')))
+    c = link;
+  else
+    c++;
+  if (!*c)
+    /* bad number */
+    return 1;
+  ret = strtol(c,&d,10);
+  if (*d)
+    /* bad number */
+    return 1;
+  return ret;
+}
+
+static int
+openDevice (const char *path, const char *description, int flags) {
+  int file;
+  logMessage(LOG_DEBUG, "Opening %s device: %s", description, path);
+  if ((file = open(path, flags)) == -1) {
+    logMessage(LOG_ERR, "Cannot open %s device: %s: %s",
+               description, path, strerror(errno));
+  }
+  return file;
+}
+
+static const char *const consolePath = HURD_INPUTPATH;
+static int consoleDescriptor;
+
+static void
+closeConsole (void) {
+  if (consoleDescriptor != -1) {
+    if (close(consoleDescriptor) == -1) {
+      logSystemError("Console close");
+    }
+    logMessage(LOG_DEBUG, "Console closed: fd=%d", consoleDescriptor);
+    consoleDescriptor = -1;
+  }
+}
+
+static int
+openConsole (unsigned char vt) {
+  char *path = vtPath(consolePath, vt?vt:currentVt());
+  if (path) {
+    int console = openDevice(path, "console", O_RDWR|O_NOCTTY);
+    if (console != -1) {
+      closeConsole();
+      consoleDescriptor = console;
+      logMessage(LOG_DEBUG, "Console opened: %s: fd=%d", path, consoleDescriptor);
+      free(path);
+      return 1;
+    }
+    logSystemError("Console open");
+    free(path);
+  }
+  return 0;
+}
+
+static const char *const screenPath = HURD_DISPLAYPATH;
+static int screenDescriptor;
+static const struct cons_display *screenMap;
+static size_t screenMapSize;
+#define screenDisplay ((conchar_t *)((wchar_t *) screenMap + screenMap->screen.matrix))
+static unsigned char virtualTerminal; /* currently shown (0 means system's) */
+static unsigned char lastReadVt; /* last shown */
+
+static void
+closeScreen (void) {
+  if (screenDescriptor != -1) {
+    if (munmap((void *) screenMap, screenMapSize) == -1) {
+      logSystemError("Screen unmap");
+    }
+    if (close(screenDescriptor) == -1) {
+      logSystemError("Screen close");
+    }
+    logMessage(LOG_DEBUG, "Screen closed: fd=%d mmap=%p", screenDescriptor, screenMap);
+    screenDescriptor = -1;
+    screenMap = NULL;
+  }
+}
+
+static int
+openScreen (unsigned char vt) {
+  char *path = vtPath(screenPath, vt?vt:currentVt());
+  if (path) {
+    int screen = openDevice(path, "screen", O_RDONLY);
+    if (screen != -1) {
+      struct stat st;
+      if (fstat(screen, &st) != -1) {
+        const struct cons_display *map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, screen, 0);
+        if (map != MAP_FAILED) {
+          if (map->magic == CONS_MAGIC && map->version >> CONS_VERSION_MAJ_SHIFT ==0 && openConsole(vt)) {
+            closeScreen();
+            screenDescriptor = screen;
+            screenMap = map;
+            screenMapSize = st.st_size;
+            logMessage(LOG_DEBUG, "Screen opened: %s: fd=%d map=%p", path, screenDescriptor, screenMap);
+            free(path);
+            return 1;
+          }
+          munmap((void *) map, st.st_size);
+        } else logSystemError("Screen map");
+      } else logSystemError("Getting size of Screen");
+      close(screen);
+    }
+    free(path);
+  }
+  return 0;
+}
+
+static int
+processParameters_HurdScreen (char **parameters) {
+  return 1;
+}
+
+static int
+construct_HurdScreen (void) {
+  screenDescriptor = -1;
+  consoleDescriptor = -1;
+  return openScreen(0);
+}
+
+static void
+destruct_HurdScreen (void) {
+  closeConsole();
+  closeScreen();
+}
+
+static void
+getScreenDescription (ScreenDescription *description) {
+  description->rows = screenMap->screen.height;
+  description->cols = screenMap->screen.width;
+  description->posx = screenMap->cursor.col;
+  description->posy = screenMap->cursor.row;
+}
+
+static void
+getConsoleDescription (ScreenDescription *description) {
+  description->number = virtualTerminal ? virtualTerminal : currentVt();
+}
+
+static void
+describe_HurdScreen (ScreenDescription *description) {
+  getScreenDescription(description);
+  getConsoleDescription(description);
+}
+
+#ifndef offsetof
+#define offsetof(type,field) ((size_t) &((type *) 0)->field)
+#endif
+static int
+readCharacters_HurdScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  uint32_t lines, start, row, col;
+  ScreenDescription description;
+  describe_HurdScreen(&description);
+  if (lastReadVt != description.number) {
+    openScreen(description.number);
+    lastReadVt = description.number;
+  }
+  if (!validateScreenBox(box, description.cols, description.rows)) return 0;
+
+  lines = screenMap->screen.lines;
+  start = screenMap->screen.cur_line;
+  for (row=start+box->top; row<start+box->top+box->height; ++row)
+    for (col=box->left; col<box->left+box->width; ++col) {
+      wchar_t text;
+      conchar_attr_t attr;
+      text = screenDisplay[(row%lines)*description.cols+col].chr;
+#ifdef CONS_WCHAR_MASK
+      text &= CONS_WCHAR_MASK;
+#endif
+#ifdef CONS_WCHAR_CONTINUED
+      if (text & CONS_WCHAR_CONTINUED)
+	text = UNICODE_ZERO_WIDTH_SPACE;
+#endif
+      buffer->text = text;
+      attr = screenDisplay[(row%lines)*description.cols+col].attr;
+      buffer->attributes = attr.fgcol | (attr.bgcol << 4)
+	| (attr.intensity == CONS_ATTR_INTENSITY_BOLD?SCR_ATTR_FG_BRIGHT:0)
+	| (attr.blinking?SCR_ATTR_BLINK:0);
+      buffer++;
+    }
+  return 1;
+}
+
+static int
+insertByte (unsigned char byte) {
+  if (write(consoleDescriptor, &byte, 1) != -1)
+    return 1;
+  logSystemError("Console write");
+  return 0;
+}
+
+static int
+insertMapped (ScreenKey key, int (*insertCharacter)(wchar_t character)) {
+  wchar_t buffer[2];
+  wchar_t *sequence;
+  wchar_t *end;
+
+  setScreenKeyModifiers(&key, 0);
+
+  if (isSpecialKey(key)) {
+    switch (key & SCR_KEY_CHAR_MASK) {
+      case SCR_KEY_ENTER:
+        sequence = WS_C("\r");
+        break;
+      case SCR_KEY_TAB:
+        sequence = WS_C("\t");
+        break;
+      case SCR_KEY_BACKSPACE:
+        sequence = WS_C("\x7f");
+        break;
+      case SCR_KEY_ESCAPE:
+        sequence = WS_C("\x1b");
+        break;
+      case SCR_KEY_CURSOR_LEFT:
+        sequence = WS_C("\x1b[D");
+        break;
+      case SCR_KEY_CURSOR_RIGHT:
+        sequence = WS_C("\x1b[C");
+        break;
+      case SCR_KEY_CURSOR_UP:
+        sequence = WS_C("\x1b[A");
+        break;
+      case SCR_KEY_CURSOR_DOWN:
+        sequence = WS_C("\x1b[B");
+        break;
+      case SCR_KEY_PAGE_UP:
+        sequence = WS_C("\x1b[5~");
+        break;
+      case SCR_KEY_PAGE_DOWN:
+        sequence = WS_C("\x1b[6~");
+        break;
+      case SCR_KEY_HOME:
+        sequence = WS_C("\x1b[1~");
+        break;
+      case SCR_KEY_END:
+        sequence = WS_C("\x1b[4~");
+        break;
+      case SCR_KEY_INSERT:
+        sequence = WS_C("\x1b[2~");
+        break;
+      case SCR_KEY_DELETE:
+        sequence = WS_C("\x1b[3~");
+        break;
+      case SCR_KEY_FUNCTION + 0:
+        sequence = WS_C("\x1bOP");
+        break;
+      case SCR_KEY_FUNCTION + 1:
+        sequence = WS_C("\x1bOQ");
+        break;
+      case SCR_KEY_FUNCTION + 2:
+        sequence = WS_C("\x1bOR");
+        break;
+      case SCR_KEY_FUNCTION + 3:
+        sequence = WS_C("\x1bOS");
+        break;
+      case SCR_KEY_FUNCTION + 4:
+        sequence = WS_C("\x1b[15~");
+        break;
+      case SCR_KEY_FUNCTION + 5:
+        sequence = WS_C("\x1b[17~");
+        break;
+      case SCR_KEY_FUNCTION + 6:
+        sequence = WS_C("\x1b[18~");
+        break;
+      case SCR_KEY_FUNCTION + 7:
+        sequence = WS_C("\x1b[19~");
+        break;
+      case SCR_KEY_FUNCTION + 8:
+        sequence = WS_C("\x1b[20~");
+        break;
+      case SCR_KEY_FUNCTION + 9:
+        sequence = WS_C("\x1b[21~");
+        break;
+      case SCR_KEY_FUNCTION + 10:
+        sequence = WS_C("\x1b[23~");
+        break;
+      case SCR_KEY_FUNCTION + 11:
+        sequence = WS_C("\x1b[24~");
+        break;
+      case SCR_KEY_FUNCTION + 12:
+        sequence = WS_C("\x1b[25~");
+        break;
+      case SCR_KEY_FUNCTION + 13:
+        sequence = WS_C("\x1b[26~");
+        break;
+      case SCR_KEY_FUNCTION + 14:
+        sequence = WS_C("\x1b[28~");
+        break;
+      case SCR_KEY_FUNCTION + 15:
+        sequence = WS_C("\x1b[29~");
+        break;
+      case SCR_KEY_FUNCTION + 16:
+        sequence = WS_C("\x1b[31~");
+        break;
+      case SCR_KEY_FUNCTION + 17:
+        sequence = WS_C("\x1b[32~");
+        break;
+      case SCR_KEY_FUNCTION + 18:
+        sequence = WS_C("\x1b[33~");
+        break;
+      case SCR_KEY_FUNCTION + 19:
+        sequence = WS_C("\x1b[34~");
+        break;
+      default:
+        logMessage(LOG_WARNING, "Key %04X not suported in ANSI mode.", key);
+        return 0;
+    }
+    end = sequence + wcslen(sequence);
+  } else {
+    sequence = end = buffer + ARRAY_COUNT(buffer);
+    *--sequence = key & SCR_KEY_CHAR_MASK;
+
+    if (key & SCR_KEY_ALT_LEFT)
+      *--sequence = 0X1B;
+  }
+
+  while (sequence != end)
+    if (!insertCharacter(*sequence++))
+      return 0;
+  return 1;
+}
+
+static int
+insertUtf8 (wchar_t character) {
+  Utf8Buffer utf8;
+  size_t utfs = convertWcharToUtf8(character, utf8);
+  int i;
+  for (i=0; i<utfs; ++i)
+    if (!insertByte(utf8[i]))
+      return 0;
+  return 1;
+}
+
+static int
+insertKey_HurdScreen (ScreenKey key) {
+  logMessage(LOG_DEBUG, "Insert key: %4.4X", key);
+  return insertMapped(key, insertUtf8); 
+}
+
+static int
+validateVt (int vt) {
+  if ((vt >= 1) && (vt <= 99)) return 1;
+  logMessage(LOG_DEBUG, "Virtual terminal %d is out of range.", vt);
+  return 0;
+}
+
+static int
+selectVirtualTerminal_HurdScreen (int vt) {
+  if (vt == virtualTerminal) return 1;
+  if (vt && !validateVt(vt)) return 0;
+  return openScreen(vt);
+}
+
+static int
+switchVirtualTerminal_HurdScreen (int vt) {
+  if (validateVt(vt)) {
+    char link[]=HURD_VCSDIR "/00";
+    snprintf(link, sizeof(link), HURD_VCSDIR "/%u", vt);
+    if (symlink(link, HURD_CURVCSPATH) != -1) {
+      logMessage(LOG_DEBUG, "Switched to virtual terminal %d.", vt);
+      return 1;
+    } else {
+      logSystemError("symlinking to switch vt");
+    }
+  }
+  return 0;
+}
+
+static int
+currentVirtualTerminal_HurdScreen (void) {
+  ScreenDescription description;
+  getConsoleDescription(&description);
+  return description.number;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.describe = describe_HurdScreen;
+  main->base.readCharacters = readCharacters_HurdScreen;
+  main->base.insertKey = insertKey_HurdScreen;
+  main->base.selectVirtualTerminal = selectVirtualTerminal_HurdScreen;
+  main->base.switchVirtualTerminal = switchVirtualTerminal_HurdScreen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_HurdScreen;
+  main->processParameters = processParameters_HurdScreen;
+  main->construct = construct_HurdScreen;
+  main->destruct = destruct_HurdScreen;
+}
diff --git a/Drivers/Screen/Hurd/screen.h b/Drivers/Screen/Hurd/screen.h
new file mode 100644
index 0000000..cda388c
--- /dev/null
+++ b/Drivers/Screen/Hurd/screen.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_HURD
+#define BRLTTY_INCLUDED_SCR_HURD
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define HURD_CONSDIR		"/dev/cons"
+#define HURD_VCSDIR		"/dev/vcs"
+#define HURD_INPUTPATH		HURD_VCSDIR "/%u/input"
+#define HURD_DISPLAYPATH	HURD_VCSDIR "/%u/display"
+#define HURD_CURVCSPATH		HURD_CONSDIR "/vcs"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_HURD */
diff --git a/Drivers/Screen/Linux/Makefile.in b/Drivers/Screen/Linux/Makefile.in
new file mode 100644
index 0000000..74251a0
--- /dev/null
+++ b/Drivers/Screen/Linux/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = lx
+DRIVER_NAME = Linux
+DRIVER_USAGE = Linux
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Linux/screen.c b/Drivers/Screen/Linux/screen.c
new file mode 100644
index 0000000..5fb51d6
--- /dev/null
+++ b/Drivers/Screen/Linux/screen.c
@@ -0,0 +1,2507 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/tty.h>
+#include <linux/vt.h>
+#include <linux/kd.h>
+#include <linux/tiocl.h>
+#include <linux/major.h>
+
+#ifndef VT_GETHIFONTMASK
+#define VT_GETHIFONTMASK 0X560D
+#endif /* VT_GETHIFONTMASK */
+
+#include "log.h"
+#include "report.h"
+#include "message.h"
+#include "async_handle.h"
+#include "async_io.h"
+#include "device.h"
+#include "io_misc.h"
+#include "timing.h"
+#include "parse.h"
+#include "brl_cmds.h"
+#include "kbd_keycodes.h"
+#include "ascii.h"
+#include "unicode.h"
+#include "charset.h"
+#include "scr_gpm.h"
+#include "system_linux.h"
+
+typedef enum {
+  PARM_CHARSET,
+  PARM_FALLBACK_TEXT,
+  PARM_HIGH_FONT_BIT,
+  PARM_LOG_SCREEN_FONT_MAP,
+  PARM_RPI_SPACES_BUG,
+  PARM_UNICODE,
+  PARM_VIRTUAL_TERMINAL_NUMBER,
+  PARM_WIDECHAR_PADDING,
+} ScreenParameters;
+#define SCRPARMS "charset", "fallbacktext", "hfb", "logsfm", "rpispacesbug", "unicode", "vt", "widecharpadding"
+
+#include "scr_driver.h"
+#include "screen.h"
+
+static const char *problemText;
+static const char *fallbackText;
+
+static unsigned int logScreenFontMap;
+static unsigned int rpiSpacesBug;
+static unsigned int unicodeEnabled;
+static int virtualTerminalNumber;
+static unsigned int widecharPadding;
+
+#define UNICODE_ROW_DIRECT 0XF000
+
+typedef enum {
+  CONV_OK,
+  CONV_ILLEGAL,
+  CONV_SHORT,
+  CONV_OVERFLOW,
+  CONV_ERROR
+} CharacterConversionResult;
+
+#if defined(HAVE_ICONV_H)
+#include <iconv.h>
+
+typedef struct {
+  iconv_t iconvHandle;
+} CharsetConverter;
+
+#define ICONV_NULL ((iconv_t)-1)
+#define CHARSET_CONVERTER_INITIALIZER {.iconvHandle = ICONV_NULL}
+
+static int
+allocateCharsetConverter (CharsetConverter *converter, const char *sourceCharset, const char *targetCharset) {
+  if (converter->iconvHandle == ICONV_NULL) {
+    if ((converter->iconvHandle = iconv_open(targetCharset, sourceCharset)) == ICONV_NULL) {
+      logSystemError("iconv_open");
+      return 0;
+    }
+  }
+  return 1;
+}
+
+static void
+deallocateCharsetConverter (CharsetConverter *converter) {
+  if (converter->iconvHandle != ICONV_NULL) {
+    iconv_close(converter->iconvHandle);
+    converter->iconvHandle = ICONV_NULL;
+  }
+}
+
+static CharacterConversionResult
+convertCharacters (
+  CharsetConverter *converter,
+  const char **inputAddress, size_t *inputLength,
+  char **outputAddress, size_t *outputLength
+) {
+  ssize_t result = iconv(converter->iconvHandle, (char **)inputAddress, inputLength, outputAddress, outputLength);
+  if (result != -1) return CONV_OK;
+  if (errno == EILSEQ) return CONV_ILLEGAL;
+  if (errno == EINVAL) return CONV_SHORT;
+  if (errno == E2BIG) return CONV_OVERFLOW;
+  logSystemError("iconv");
+  return CONV_ERROR;
+}
+#else /* charset conversion definitions */
+typedef struct {
+  char aStructNeedsAtLeastOneField;
+} CharsetConverter;
+
+#define CHARSET_CONVERTER_INITIALIZER {0}
+
+static int
+allocateCharsetConverter (CharsetConverter *converter, const char *sourceCharset, const char *targetCharset) {
+  return 1;
+}
+
+static void
+deallocateCharsetConverter (CharsetConverter *converter) {
+}
+
+static CharacterConversionResult
+convertCharacters (
+  CharsetConverter *converter,
+  const char **inputAddress, size_t *inputLength,
+  char **outputAddress, size_t *outputLength
+) {
+  *(*outputAddress)++ = *(*inputAddress)++;
+  *inputLength -= 1;
+  *outputLength -= 1;
+  return CONV_OK;
+}
+#endif /* charset conversion definitions */
+
+typedef struct {
+  char *name;
+  unsigned isMultiByte:1;
+  CharsetConverter charsetToWchar;
+  CharsetConverter wcharToCharset;
+} CharsetEntry;
+
+static CharsetEntry *charsetEntries = NULL;
+static unsigned int charsetCount = 0;
+static unsigned int charsetIndex = 0;
+
+static inline CharsetEntry *
+getCharsetEntry (void) {
+  return &charsetEntries[charsetIndex];
+}
+
+static void
+deallocateCharsetEntries (void) {
+  if (charsetEntries) {
+    while (charsetCount) {
+      CharsetEntry *charset = &charsetEntries[--charsetCount];
+      free(charset->name);
+      deallocateCharsetConverter(&charset->charsetToWchar);
+      deallocateCharsetConverter(&charset->wcharToCharset);
+    }
+
+    free(charsetEntries);
+    charsetEntries = NULL;
+  }
+}
+
+static int
+allocateCharsetEntries (const char *names) {
+  int ok = 0;
+  int count;
+  char **namesArray = splitString(names, '+', &count);
+
+  if (namesArray) {
+    CharsetEntry *entries = calloc(count, sizeof(*entries));
+
+    if (entries) {
+      charsetEntries = entries;
+      charsetCount = 0;
+      charsetIndex = 0;
+      ok = 1;
+
+      while (charsetCount < count) {
+        CharsetEntry *charset = &charsetEntries[charsetCount];
+
+        if (!(charset->name = strdup(namesArray[charsetCount]))) {
+          logMallocError();
+          ok = 0;
+          deallocateCharsetEntries();
+          break;
+        }
+
+        charset->isMultiByte = 0;
+
+        {
+          static const CharsetConverter nullCharsetConverter = CHARSET_CONVERTER_INITIALIZER;
+          charset->charsetToWchar = nullCharsetConverter;
+          charset->wcharToCharset = nullCharsetConverter;
+        }
+
+        charsetCount += 1;
+      }
+    }
+
+    deallocateStrings(namesArray);
+  }
+
+  return ok;
+}
+
+static CharacterConversionResult
+convertCharsToWchar (const char *chars, size_t length, wchar_t *character, size_t *size) {
+  unsigned int count = charsetCount;
+
+  while (count--) {
+    CharsetEntry *charset = getCharsetEntry();
+    CharsetConverter *converter = &charset->charsetToWchar;
+    CharacterConversionResult result = CONV_ERROR;
+
+    if (allocateCharsetConverter(converter, charset->name, getWcharCharset())) {
+      const char *inptr = chars;
+      size_t inlen = length;
+      char *outptr = (char *)character;
+      size_t outlen = sizeof(*character);
+
+      if ((result = convertCharacters(converter, &inptr, &inlen, &outptr, &outlen)) == CONV_OK)
+        if (size)
+          *size = inptr - chars;
+    }
+
+    if (result == CONV_SHORT) charset->isMultiByte = 1;
+    if (result != CONV_ILLEGAL) return result;
+    if (++charsetIndex == charsetCount) charsetIndex = 0;
+  }
+
+  return CONV_ILLEGAL;
+}
+
+static CharacterConversionResult
+convertWcharToChars (wchar_t character, char *chars, size_t length, size_t *size) {
+  CharsetEntry *charset = getCharsetEntry();
+  CharsetConverter *converter = &charset->wcharToCharset;
+  CharacterConversionResult result = CONV_ERROR;
+
+  if (allocateCharsetConverter(converter, getWcharCharset(), charset->name)) {
+    const char *inptr = (char *)&character;
+    size_t inlen = sizeof(character);
+    char *outptr = chars;
+    size_t outlen = length;
+
+    if ((result = convertCharacters(converter, &inptr, &inlen, &outptr, &outlen)) == CONV_OK) {
+      size_t count = outptr - chars;
+      if (size) *size = count;
+      if (count > 1) charset->isMultiByte = 1;
+    } else if ((result == CONV_OVERFLOW) && length) {
+      charset->isMultiByte = 1;
+    }
+  }
+
+  return result;
+}
+
+static wint_t
+convertCharacter (const wchar_t *character) {
+  static unsigned char spaces = 0;
+  static unsigned char length = 0;
+  static char buffer[MB_LEN_MAX];
+  const wchar_t cellMask = 0XFF;
+
+  if (!character) {
+    length = 0;
+    if (!spaces) return WEOF;
+    spaces -= 1;
+    return WC_C(' ');
+  }
+
+  if ((*character & ~cellMask) != UNICODE_ROW_DIRECT) {
+    length = 0;
+    return *character;
+  }
+
+  if (length < sizeof(buffer)) {
+    buffer[length++] = *character & cellMask;
+
+    while (1) {
+      wchar_t wc;
+      CharacterConversionResult result = convertCharsToWchar(buffer, length, &wc, NULL);
+
+      if (result == CONV_OK) {
+        length = 0;
+        return wc;
+      }
+
+      if (result == CONV_SHORT) break;
+      if (result != CONV_ILLEGAL) break;
+
+      if (!--length) break;
+      memmove(buffer, buffer+1, length);
+    }
+  }
+
+  spaces += 1;
+  return WEOF;
+}
+
+static int
+setDeviceName (const char **name, const char *const *names, int strict, const char *description) {
+  return (*name = resolveDeviceName(names, strict, description)) != NULL;
+}
+
+static char *
+vtName (const char *name, unsigned char vt) {
+  char *string;
+
+  if (vt) {
+    int length = strlen(name);
+    if (name[length-1] == '0') length -= 1;
+
+    char buffer[length+4];
+    snprintf(buffer, sizeof(buffer), "%.*s%u", length, name, vt);
+
+    string = strdup(buffer);
+  } else {
+    string = strdup(name);
+  }
+
+  if (!string) logMallocError();
+  return string;
+}
+
+static const char *consoleName = NULL;
+
+static int
+setConsoleName (void) {
+  static const char *const names[] = {"tty0", "vc/0", NULL};
+  return setDeviceName(&consoleName, names, 0, "console");
+}
+
+static void
+closeConsole (int *fd) {
+  if (*fd != -1) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER), "closing console: fd=%d", *fd);
+    if (close(*fd) == -1) logSystemError("close[console]");
+    *fd = -1;
+  }
+}
+
+static int
+openConsole (int *fd, int vt) {
+  int opened = 0;
+  char *name = vtName(consoleName, vt);
+
+  if (name) {
+    int console = openCharacterDevice(name, O_WRONLY|O_NOCTTY, TTY_MAJOR, vt);
+
+    if (console != -1) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "console opened: %s: fd=%d", name, console);
+
+      closeConsole(fd);
+      *fd = console;
+      opened = 1;
+    }
+
+    free(name);
+  }
+
+  return opened;
+}
+
+static int
+controlConsole (int *fd, int vt, int operation, void *argument) {
+  int result = ioctl(*fd, operation, argument);
+
+  if (result == -1) {
+    if (errno == EIO) {
+      logMessage(LOG_ERR,
+                 "console control error %d: fd=%d vt=%d op=0X%04X: %s",
+                 errno, *fd, vt, operation, strerror(errno));
+
+      if (openConsole(fd, vt)) {
+        result = ioctl(*fd, operation, argument);
+      }
+    }
+  }
+
+  return result;
+}
+
+static int consoleDescriptor;
+
+static void
+closeCurrentConsole (void) {
+  closeConsole(&consoleDescriptor);
+}
+
+static int
+openCurrentConsole (void) {
+  return openConsole(&consoleDescriptor, virtualTerminalNumber);
+}
+
+static int
+controlCurrentConsole (int operation, void *argument) {
+  if (consoleDescriptor != -1) {
+    return controlConsole(&consoleDescriptor, virtualTerminalNumber, operation, argument);
+  }
+
+  switch (operation) {
+    case GIO_UNIMAP: {
+      struct unimapdesc *sfm = argument;
+      memset(sfm, 0, sizeof(*sfm));
+      sfm->entries = NULL;
+      sfm->entry_ct = 0;
+      return 0;
+    }
+
+    case KDFONTOP: {
+      struct console_font_op *cfo = argument;
+
+      if (cfo->op == KD_FONT_OP_GET) {
+        cfo->charcount = 0;
+        cfo->width = 8;
+        cfo->height = 16;
+        return 0;
+      }
+
+      break;
+    }
+
+    case VT_GETHIFONTMASK: {
+      unsigned short *mask = argument;
+      *mask = 0;
+      return 0;
+    }
+
+    case KDGETMODE: {
+      int *mode = argument;
+      *mode = KD_TEXT;
+      return 0;
+    }
+
+    default:
+      break;
+  }
+
+  errno = EAGAIN;
+  return -1;
+}
+
+static const int NO_CONSOLE = 0;
+static const int MAIN_CONSOLE = 0;
+static int mainConsoleDescriptor;
+
+static void
+closeMainConsole (void) {
+  closeConsole(&mainConsoleDescriptor);
+}
+
+static int
+openMainConsole (void) {
+  return openConsole(&mainConsoleDescriptor, MAIN_CONSOLE);
+}
+
+static int
+controlMainConsole (int operation, void *argument) {
+  return controlConsole(&mainConsoleDescriptor, MAIN_CONSOLE, operation, argument);
+}
+
+static const char *unicodeName = NULL;
+
+static int
+setUnicodeName (void) {
+  static const char *const names[] = {"vcsu", "vcsu0", NULL};
+  return setDeviceName(&unicodeName, names, 1, "unicode");
+}
+
+static void
+closeUnicode (int *fd) {
+  if (*fd != -1) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER), "closing unicode: fd=%d", *fd);
+    if (close(*fd) == -1) logSystemError("close[unicode]");
+    *fd = -1;
+  }
+}
+
+static int
+openUnicode (int *fd, int vt) {
+  if (!unicodeName) return 0;
+  if (*fd != -1) return 1;
+
+  int opened = 0;
+  char *name = vtName(unicodeName, vt);
+
+  if (name) {
+    int unicode = openCharacterDevice(name, O_RDWR, VCS_MAJOR, 0X40|vt);
+
+    if (unicode != -1) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "unicode opened: %s: fd=%d", name, unicode);
+
+      closeUnicode(fd);
+      *fd = unicode;
+      opened = 1;
+    } else {
+      unicodeName = NULL;
+    }
+
+    free(name);
+  }
+
+  return opened;
+}
+
+static int unicodeDescriptor;
+
+static void
+closeCurrentUnicode (void) {
+  closeUnicode(&unicodeDescriptor);
+}
+
+static int
+openCurrentUnicode (void) {
+  if (!unicodeEnabled) return 0;
+  return openUnicode(&unicodeDescriptor, virtualTerminalNumber);
+}
+
+static size_t
+readUnicodeDevice (off_t offset, void *buffer, size_t size) {
+  if (openCurrentUnicode()) {
+    const ssize_t count = pread(unicodeDescriptor, buffer, size, offset);
+
+    if (count != -1) {
+      if (rpiSpacesBug) {
+        uint32_t *character = buffer;
+        const uint32_t *end = character + (count / sizeof(*character));
+
+        while (character < end) {
+          if (*character == 0X20202020) {
+            static unsigned char bugLogged = 0;
+
+            if (!bugLogged) {
+              logMessage(LOG_WARNING, "Linux screen driver: RPI spaces bug detected");
+              bugLogged = 1;
+            }
+
+            *character = ' ';
+          }
+
+          character += 1;
+        }
+      }
+
+      return count;
+    }
+
+    if (errno != ENODATA) logSystemError("unicode read");
+  }
+
+  return 0;
+}
+
+static unsigned char *unicodeCacheBuffer;
+static size_t unicodeCacheSize;
+static size_t unicodeCacheUsed;
+
+static size_t
+readUnicodeCache (off_t offset, void *buffer, size_t size) {
+  if (offset <= unicodeCacheUsed) {
+    size_t left = unicodeCacheUsed - offset;
+    if (size > left) size = left;
+
+    memcpy(buffer, &unicodeCacheBuffer[offset], size);
+    return size;
+  } else {
+    logMessage(LOG_ERR, "invalid unicode cache offset: %u", (unsigned int)offset);
+  }
+
+  return 0;
+}
+
+static int
+readUnicodeData (off_t offset, void *buffer, size_t size) {
+  size_t count = (unicodeCacheBuffer? readUnicodeCache: readUnicodeDevice)(offset, buffer, size);
+  if (count == size) return 1;
+
+  logMessage(LOG_ERR,
+             "truncated unicode data: expected %zu bytes but read %zu",
+             size, count);
+
+  return 0;
+}
+
+static int
+readUnicodeContent (off_t offset, uint32_t *buffer, size_t count) {
+  count *= sizeof(*buffer);
+  offset *= sizeof(*buffer);
+  return readUnicodeData(offset, buffer, count);
+}
+
+static int
+refreshUnicodeCache (size_t size) {
+  size *= 4;
+
+  if (size > unicodeCacheSize) {
+    const unsigned int bits = 10;
+    const unsigned int mask = (1 << bits) - 1;
+
+    size |= mask;
+    size += 1;
+    unsigned char *buffer = malloc(size);
+
+    if (!buffer) {
+      logMallocError();
+      return 0;
+    }
+
+    if (unicodeCacheBuffer) free(unicodeCacheBuffer);
+    unicodeCacheBuffer = buffer;
+    unicodeCacheSize = size;
+  }
+
+  unicodeCacheUsed = readUnicodeDevice(0, unicodeCacheBuffer, unicodeCacheSize);
+  return 1;
+}
+
+static const char *screenName = NULL;
+static int screenDescriptor;
+
+static int isMonitorable;
+static THREAD_LOCAL AsyncHandle screenMonitor = NULL;
+
+static int screenUpdated;
+
+static int currentConsoleNumber;
+static int inTextMode;
+static TimePeriod mappingRecalculationTimer;
+
+typedef struct {
+  unsigned char rows;
+  unsigned char columns;
+} ScreenSize;
+
+typedef struct {
+  unsigned char column;
+  unsigned char row;
+} ScreenLocation;
+
+typedef struct {
+  ScreenSize size;
+  ScreenLocation location;
+} ScreenHeader;
+
+#ifdef HAVE_SYS_POLL_H
+#include <poll.h>
+
+static int
+canMonitorScreen (void) {
+  struct pollfd pollDescriptor = {
+    .fd = screenDescriptor,
+    .events = POLLPRI
+  };
+
+  return poll(&pollDescriptor, 1, 0) == 1;
+}
+
+#else /* can poll */
+static int
+canMonitorScreen (void) {
+  return 0;
+}
+#endif /* can poll */
+
+static int
+setScreenName (void) {
+  static const char *const names[] = {"vcsa", "vcsa0", "vcc/a", NULL};
+  return setDeviceName(&screenName, names, 0, "screen");
+}
+
+static int
+openScreenDevice (int *fd, int vt) {
+  int opened = 0;
+  char *name = vtName(screenName, vt);
+
+  if (name) {
+    int screen = openCharacterDevice(name, O_RDWR, VCS_MAJOR, 0X80|vt);
+
+    if (screen != -1) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "screen opened: %s: fd=%d", name, screen);
+
+      *fd = screen;
+      opened = 1;
+    }
+
+    free(name);
+  }
+
+  return opened;
+}
+
+static void
+closeCurrentScreen (void) {
+  if (screenMonitor) {
+    asyncCancelRequest(screenMonitor);
+    screenMonitor = NULL;
+  }
+
+  if (screenDescriptor != -1) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+               "closing screen: fd=%d", screenDescriptor);
+
+    if (close(screenDescriptor) == -1) logSystemError("close[screen]");
+    screenDescriptor = -1;
+  }
+}
+
+static int
+setCurrentScreen (unsigned char vt) {
+  int screen;
+  if (!openScreenDevice(&screen, vt)) return 0;
+
+  closeCurrentConsole();
+  closeCurrentUnicode();
+  closeCurrentScreen();
+  screenDescriptor = screen;
+  virtualTerminalNumber = vt;
+
+  isMonitorable = canMonitorScreen();
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "screen is monitorable: %s",
+             (isMonitorable? "yes": "no"));
+
+  screenMonitor = NULL;
+  screenUpdated = 1;
+  return 1;
+}
+
+static size_t
+readScreenDevice (off_t offset, void *buffer, size_t size) {
+  const ssize_t count = pread(screenDescriptor, buffer, size, offset);
+  if (count != -1) return count;
+
+  logSystemError("screen read");
+  return 0;
+}
+
+static unsigned char *screenCacheBuffer;
+static size_t screenCacheSize;
+
+static size_t
+readScreenCache (off_t offset, void *buffer, size_t size) {
+  if (offset <= screenCacheSize) {
+    size_t left = screenCacheSize - offset;
+
+    if (size > left) size = left;
+    memcpy(buffer, &screenCacheBuffer[offset], size);
+    return size;
+  } else {
+    logMessage(LOG_ERR, "invalid screen cache offset: %u", (unsigned int)offset);
+  }
+
+  return 0;
+}
+
+static int
+readScreenData (off_t offset, void *buffer, size_t size) {
+  size_t count = (screenCacheBuffer? readScreenCache: readScreenDevice)(offset, buffer, size);
+  if (count == size) return 1;
+
+  logMessage(LOG_ERR,
+             "truncated screen data: expected %zu bytes but read %zu",
+             size, count);
+
+  return 0;
+}
+
+static int
+readScreenHeader (ScreenHeader *header) {
+  return readScreenData(0, header, sizeof(*header));
+}
+
+static int
+readScreenSize (ScreenSize *size) {
+  return readScreenData(0, size, sizeof(*size));
+}
+
+static int
+readScreenContent (off_t offset, uint16_t *buffer, size_t count) {
+  count *= sizeof(*buffer);
+  offset *= sizeof(*buffer);
+  offset += sizeof(ScreenHeader);
+  return readScreenData(offset, buffer, count);
+}
+
+static size_t
+getScreenBufferSize (const ScreenSize *screenSize) {
+  return (screenSize->columns * screenSize->rows * 2) + sizeof(ScreenHeader);
+}
+
+static size_t
+refreshScreenBuffer (unsigned char **screenBuffer, size_t *screenSize) {
+  if (!*screenBuffer) {
+    ScreenHeader header;
+
+    {
+      size_t size = sizeof(header);
+      size_t count = readScreenDevice(0, &header, size);
+      if (!count) return 0;
+
+      if (count < size) {
+        logBytes(LOG_ERR, "truncated screen header", &header, count);
+        return 0;
+      }
+    }
+
+    {
+      size_t size = getScreenBufferSize(&header.size);
+      unsigned char *buffer = malloc(size);
+
+      if (!buffer) {
+        logMallocError();
+        return 0;
+      }
+
+      *screenBuffer = buffer;
+      *screenSize = size;
+    }
+  }
+
+  while (1) {
+    size_t count = readScreenDevice(0, *screenBuffer, *screenSize);
+    if (!count) return 0;
+
+    if (count < sizeof(ScreenHeader)) {
+      logBytes(LOG_ERR, "truncated screen header", *screenBuffer, count);
+      return 0;
+    }
+
+    {
+      ScreenHeader *header = (void *)*screenBuffer;
+      size_t size = getScreenBufferSize(&header->size);
+      if (count >= size) return header->size.columns * header->size.rows;
+
+      {
+        unsigned char *buffer = realloc(*screenBuffer, size);
+
+        if (!buffer) {
+          logMallocError();
+          return 0;
+        }
+
+        *screenBuffer = buffer;
+        *screenSize = size;
+      }
+    }
+  }
+}
+
+static struct unipair *screenFontMapTable = NULL;
+static unsigned short screenFontMapSize = 0;
+static unsigned short screenFontMapCount;
+
+static int
+setScreenFontMap (int force) {
+  struct unimapdesc sfm;
+  unsigned short size = force? 0: screenFontMapCount;
+
+  if (!size) size = 0X100;
+
+  while (1) {
+    sfm.entry_ct = size;
+
+    if (!(sfm.entries = malloc(sfm.entry_ct * sizeof(*sfm.entries)))) {
+      logMallocError();
+      return 0;
+    }
+
+    if (controlCurrentConsole(GIO_UNIMAP, &sfm) != -1) break;
+    free(sfm.entries);
+
+    if (errno != ENOMEM) {
+      logSystemError("ioctl[GIO_UNIMAP]");
+      return 0;
+    }
+
+    if (!(size <<= 1)) {
+      logMessage(LOG_ERR, "screen font map too big");
+      return 0;
+    }
+  }
+
+  if (!force) {
+    if (sfm.entry_ct == screenFontMapCount) {
+      if (memcmp(sfm.entries, screenFontMapTable, sfm.entry_ct*sizeof(sfm.entries[0])) == 0) {
+        free(sfm.entries);
+        return 0;
+      }
+    }
+  }
+
+  if (screenFontMapTable) free(screenFontMapTable);
+  screenFontMapTable = sfm.entries;
+  screenFontMapCount = sfm.entry_ct;
+  screenFontMapSize = size;
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "Font Map Size: %d", screenFontMapCount);
+
+  if (logScreenFontMap) {
+    for (unsigned int i=0; i<screenFontMapCount; i+=1) {
+      const struct unipair *map = &screenFontMapTable[i];
+
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                 "SFM[%03u]: U+%04X@%04X",
+                 i, map->unicode, map->fontpos);
+    }
+  }
+
+  return 1;
+}
+
+static int vgaCharacterCount;
+static int vgaLargeTable;
+
+static int
+setVgaCharacterCount (int force) {
+  int oldCount = vgaCharacterCount;
+
+  {
+    struct console_font_op cfo = {
+      .width = UINT_MAX,
+      .height = UINT_MAX,
+      .op = KD_FONT_OP_GET
+    };
+
+    vgaCharacterCount = 0;
+    {
+      static unsigned char isNotImplemented = 0;
+
+      if (!isNotImplemented) {
+        if (controlCurrentConsole(KDFONTOP, &cfo) != -1) {
+          logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                     "Font Properties: %ux%u*%u",
+                     cfo.width, cfo.height, cfo.charcount);
+          vgaCharacterCount = cfo.charcount;
+        } else {
+          if (errno == ENOSYS) isNotImplemented = 1;
+
+          if (errno != EINVAL) {
+            logMessage(LOG_WARNING, "ioctl[KDFONTOP[GET]]: %s", strerror(errno));
+          }
+        }
+      }
+    }
+  }
+
+  if (!vgaCharacterCount) {
+    unsigned int index;
+
+    for (index=0; index<screenFontMapCount; ++index) {
+      const struct unipair *map = &screenFontMapTable[index];
+
+      if (vgaCharacterCount <= map->fontpos) vgaCharacterCount = map->fontpos + 1;
+    }
+  }
+
+  vgaCharacterCount = ((vgaCharacterCount - 1) | 0XFF) + 1;
+  vgaLargeTable = vgaCharacterCount > 0X100;
+
+  if (!force) {
+    if (vgaCharacterCount == oldCount) {
+      return 0;
+    }
+  }
+
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "VGA Character Count: %d(%s)",
+             vgaCharacterCount,
+             vgaLargeTable? "large": "small");
+
+  return 1;
+}
+
+static unsigned short highFontBit;
+static unsigned short fontAttributesMask;
+static unsigned short unshiftedAttributesMask;
+static unsigned short shiftedAttributesMask;
+
+static void
+setAttributesMasks (unsigned short bit) {
+  fontAttributesMask = bit;
+  unshiftedAttributesMask = bit - 1;
+  shiftedAttributesMask = ~unshiftedAttributesMask & ~bit;
+  unshiftedAttributesMask &= 0XFF00;
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+             "Attributes Masks: Font:%04X Unshifted:%04X Shifted:%04X",
+             fontAttributesMask, unshiftedAttributesMask, shiftedAttributesMask);
+}
+
+static int
+determineAttributesMasks (void) {
+  if (!vgaLargeTable) {
+    setAttributesMasks(0);
+  } else if (highFontBit) {
+    setAttributesMasks(highFontBit);
+  } else {
+    {
+      unsigned short mask;
+
+      if (controlCurrentConsole(VT_GETHIFONTMASK, &mask) == -1) {
+        if (errno != EINVAL) logSystemError("ioctl[VT_GETHIFONTMASK]");
+      } else if (mask & 0XFF) {
+        logMessage(LOG_ERR, "high font mask has bit set in low-order byte: %04X", mask);
+      } else {
+        setAttributesMasks(mask);
+        return 1;
+      }
+    }
+
+    {
+      ScreenSize size;
+
+      if (readScreenSize(&size)) {
+        const size_t count = size.columns * size.rows;
+        unsigned short buffer[count];
+
+        if (readScreenContent(0, buffer, ARRAY_COUNT(buffer))) {
+          unsigned int counts[0X10];
+          memset(counts, 0, sizeof(counts));
+
+          for (unsigned int index=0; index<count; index+=1) {
+            counts[(buffer[index] & 0X0F00) >> 8] += 1;
+          }
+
+          setAttributesMasks((counts[0XE] > counts[0X7])? 0X0100: 0X0800);
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static wchar_t translationTable[0X200];
+
+static int
+setTranslationTable (int force) {
+  int mappingChanged = 0;
+  int sfmChanged = setScreenFontMap(force);
+  int vccChanged = (sfmChanged || force)? setVgaCharacterCount(force): 0;
+
+  if (vccChanged || force) determineAttributesMasks();
+
+  if (sfmChanged || vccChanged) {
+    unsigned int count = ARRAY_COUNT(translationTable);
+
+    for (unsigned int i=0; i<count; i+=1) {
+      translationTable[i] = UNICODE_ROW_DIRECT | i;
+    }
+
+    {
+      unsigned int screenFontMapIndex = screenFontMapCount;
+
+      while (screenFontMapIndex > 0) {
+        const struct unipair *sfm = &screenFontMapTable[--screenFontMapIndex];
+
+        if (sfm->fontpos < count) {
+          wchar_t *character = &translationTable[sfm->fontpos];
+          if (*character == 0X20) continue;
+          *character = sfm->unicode;
+        }
+      }
+    }
+
+    mappingChanged = 1;
+  }
+
+  if (mappingChanged) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER), "character mapping changed");
+  }
+
+  restartTimePeriod(&mappingRecalculationTimer);
+  return mappingChanged;
+}
+
+static int
+readScreenRow (int row, size_t size, ScreenCharacter *characters, int *offsets) {
+  off_t offset = row * size;
+
+  uint16_t vgaBuffer[size];
+  if (!readScreenContent(offset, vgaBuffer, size)) return 0;
+
+  uint32_t unicodeBuffer[size];
+  const uint32_t *unicode = NULL;
+
+  if (unicodeEnabled) {
+    if (readUnicodeContent(offset, unicodeBuffer, size)) {
+      unicode = unicodeBuffer;
+    }
+  }
+
+  ScreenCharacter *character = characters;
+  int column = 0;
+
+  {
+    const uint16_t *vga = vgaBuffer;
+    const uint16_t *end = vga + size;
+    int blanks = 0;
+
+    while (vga < end) {
+      wint_t wc;
+
+      if (unicode) {
+        wc = *unicode++;
+
+        if ((blanks > 0) && (wc == WC_C(' '))) {
+          blanks -= 1;
+          wc = WEOF;
+        } else if (widecharPadding) {
+          blanks = 0;
+        } else {
+          blanks = getCharacterWidth(wc) - 1;
+        }
+      } else {
+        uint16_t position = *vga & 0XFF;
+        if (*vga & fontAttributesMask) position |= 0X100;
+        wc = convertCharacter(&translationTable[position]);
+      }
+
+      if (wc != WEOF) {
+        if (character) {
+          character->attributes = ((*vga & unshiftedAttributesMask) |
+                                   ((*vga & shiftedAttributesMask) >> 1)) >> 8;
+
+          character->text = wc;
+          character += 1;
+        }
+
+        if (offsets) offsets[column] = vga - vgaBuffer;
+        column += 1;
+      }
+
+      vga += 1;
+    }
+  }
+
+  if (!unicode) {
+    wint_t wc;
+
+    while ((wc = convertCharacter(NULL)) != WEOF) {
+      if (column < size) {
+        if (character) {
+          character->text = wc;
+          character->attributes = SCR_COLOUR_DEFAULT;
+          character += 1;
+        }
+
+        if (offsets) offsets[column] = size - 1;
+        column += 1;
+      }
+    }
+  }
+
+  while (column < size) {
+    if (character) {
+      static const ScreenCharacter pad = {
+        .text = WC_C(' '),
+        .attributes = SCR_COLOUR_DEFAULT
+      };
+
+      *character++ = pad;
+    }
+
+    if (offsets) offsets[column] = size - 1;
+    column += 1;
+  }
+
+  return 1;
+}
+
+static void
+adjustCursorColumn (short *column, short row, short columns) {
+  int offsets[columns];
+
+  if (readScreenRow(row, columns, NULL, offsets)) {
+    int first = 0;
+    int last = columns - 1;
+
+    while (first <= last) {
+      int current = (first + last) / 2;
+
+      if (offsets[current] < *column) {
+        first = current + 1;
+      } else {
+        last = current - 1;
+      }
+    }
+
+    if (first == columns) first -= 1;
+    *column = first;
+  }
+}
+
+#ifdef HAVE_LINUX_INPUT_H
+#include <linux/input.h>
+
+static const LinuxKeyCode *xtKeys;
+static const LinuxKeyCode *atKeys;
+static int atKeyPressed;
+static int ps2KeyPressed;
+#endif /* HAVE_LINUX_INPUT_H */
+
+static UinputObject *uinputKeyboard = NULL;
+static ReportListenerInstance *brailleDeviceOfflineListener;
+
+static void
+closeKeyboard (void) {
+  if (uinputKeyboard) {
+    destroyUinputObject(uinputKeyboard);
+    uinputKeyboard = NULL;
+  }
+}
+
+static int
+openKeyboard (void) {
+  if (!uinputKeyboard) {
+    if (!(uinputKeyboard = newUinputKeyboard("Linux Screen Driver Keyboard"))) {
+      return 0;
+    }
+
+    atexit(closeKeyboard);
+  }
+
+  return 1;
+}
+
+static void
+resetKeyboard (void) {
+  if (uinputKeyboard) {
+    releasePressedKeys(uinputKeyboard);
+  }
+}
+
+static int
+processParameters_LinuxScreen (char **parameters) {
+  fallbackText = parameters[PARM_FALLBACK_TEXT];
+
+  {
+    const char *names = parameters[PARM_CHARSET];
+
+    if (!names || !*names) names = getLocaleCharset();
+    if (!allocateCharsetEntries(names)) return 0;
+  }
+
+  highFontBit = 0;
+  {
+    const char *parameter = parameters[PARM_HIGH_FONT_BIT];
+
+    if (parameter && *parameter) {
+      int bit = 0;
+
+      static const int minimum = 0;
+      static const int maximum = 7;
+
+      static const char *choices[] = {"auto", "vga", "fb", NULL};
+      unsigned int choice;
+
+      if (validateInteger(&bit, parameter, &minimum, &maximum)) {
+        highFontBit = 1 << (bit + 8);
+      } else if (!validateChoice(&choice, parameter, choices)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid high font bit", parameter);
+      } else if (choice) {
+        static const unsigned short bits[] = {0X0800, 0X0100};
+        highFontBit = bits[choice-1];
+      }
+    }
+  }
+
+  logScreenFontMap = 0;
+  {
+    const char *parameter = parameters[PARM_LOG_SCREEN_FONT_MAP];
+
+    if (parameter && *parameter) {
+      if (!validateYesNo(&logScreenFontMap, parameter)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid log screen font map setting", parameter);
+      }
+    }
+  }
+
+  rpiSpacesBug = 0;
+  {
+    const char *parameter = parameters[PARM_RPI_SPACES_BUG];
+
+    if (parameter && *parameter) {
+      if (!validateYesNo(&rpiSpacesBug, parameter)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid RPI spaces bug setting", parameter);
+      }
+    }
+  }
+
+  unicodeEnabled = 1;
+  {
+    const char *parameter = parameters[PARM_UNICODE];
+
+    if (parameter && *parameter) {
+      if (!validateYesNo(&unicodeEnabled, parameter)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid direct unicode setting", parameter);
+      }
+    }
+  }
+
+  virtualTerminalNumber = 0;
+  {
+    const char *parameter = parameters[PARM_VIRTUAL_TERMINAL_NUMBER];
+
+    if (parameter && *parameter) {
+      static const int minimum = 0;
+      static const int maximum = MAX_NR_CONSOLES;
+
+      if (!validateInteger(&virtualTerminalNumber, parameter, &minimum, &maximum)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid virtual terminal number", parameter);
+      }
+    }
+  }
+
+  widecharPadding = 0;
+  {
+    const char *parameter = parameters[PARM_WIDECHAR_PADDING];
+
+    if (parameter && *parameter) {
+      if (!validateYesNo(&widecharPadding, parameter)) {
+        logMessage(LOG_WARNING, "%s: %s", "invalid widechar padding setting", parameter);
+      }
+    }
+  }
+
+  return 1;
+}
+
+static void
+releaseParameters_LinuxScreen (void) {
+  deallocateCharsetEntries();
+}
+
+REPORT_LISTENER(lxBrailleDeviceOfflineListener) {
+  resetKeyboard();
+}
+
+static int
+construct_LinuxScreen (void) {
+  mainConsoleDescriptor = -1;
+  screenDescriptor = -1;
+  consoleDescriptor = -1;
+  unicodeDescriptor = -1;
+
+  screenUpdated = 0;
+  screenCacheBuffer = NULL;
+  screenCacheSize = 0;
+
+  unicodeCacheBuffer = NULL;
+  unicodeCacheSize = 0;
+  unicodeCacheUsed = 0;
+
+  currentConsoleNumber = 0;
+  inTextMode = 1;
+  startTimePeriod(&mappingRecalculationTimer, 4000);
+
+  brailleDeviceOfflineListener = NULL;
+
+#ifdef HAVE_LINUX_INPUT_H
+  xtKeys = linuxKeyMap_xt00;
+  atKeys = linuxKeyMap_at00;
+  atKeyPressed = 1;
+  ps2KeyPressed = 1;
+#endif /* HAVE_LINUX_INPUT_H */
+
+  if (setScreenName()) {
+    if (setConsoleName()) {
+      if (unicodeEnabled) {
+        if (!setUnicodeName()) {
+          unicodeEnabled = 0;
+        }
+      }
+
+      if (openMainConsole()) {
+        if (setCurrentScreen(virtualTerminalNumber)) {
+          openKeyboard();
+          brailleDeviceOfflineListener = registerReportListener(REPORT_BRAILLE_DEVICE_OFFLINE, lxBrailleDeviceOfflineListener, NULL);
+          return 1;
+        }
+      }
+    }
+  }
+
+  closeCurrentConsole();
+  closeCurrentScreen();
+  closeMainConsole();
+  return 0;
+}
+
+static void
+destruct_LinuxScreen (void) {
+  if (brailleDeviceOfflineListener) {
+    unregisterReportListener(brailleDeviceOfflineListener);
+    brailleDeviceOfflineListener = NULL;
+  }
+
+  closeCurrentConsole();
+  consoleName = NULL;
+
+  closeCurrentScreen();
+  screenName = NULL;
+
+  if (screenFontMapTable) {
+    free(screenFontMapTable);
+    screenFontMapTable = NULL;
+  }
+  screenFontMapSize = 0;
+  screenFontMapCount = 0;
+
+  if (screenCacheBuffer) {
+    free(screenCacheBuffer);
+    screenCacheBuffer = NULL;
+  }
+  screenCacheSize = 0;
+
+  if (unicodeCacheBuffer) {
+    free(unicodeCacheBuffer);
+    unicodeCacheBuffer = NULL;
+  }
+  unicodeCacheSize = 0;
+  unicodeCacheUsed = 0;
+
+  closeMainConsole();
+}
+
+ASYNC_MONITOR_CALLBACK(lxScreenUpdated) {
+  asyncDiscardHandle(screenMonitor);
+  screenMonitor = NULL;
+
+  screenUpdated = 1;
+  mainScreenUpdated();
+
+  return 0;
+}
+
+static int
+poll_LinuxScreen (void) {
+  int poll = !isMonitorable? 1:
+             screenMonitor? 0:
+             !asyncMonitorFileAlert(&screenMonitor, screenDescriptor,
+                                    lxScreenUpdated, NULL);
+
+  if (poll) screenUpdated = 1;
+  return poll;
+}
+
+static int
+getConsoleState (struct vt_stat *state) {
+  if (controlMainConsole(VT_GETSTATE, state) != -1) return 1;
+  logSystemError("ioctl[VT_GETSTATE]");
+  problemText = gettext("can't get console state");
+  return 0;
+}
+
+static int
+isUnusedConsole (int vt) {
+  int isUnused = 1;
+  unsigned char *buffer = NULL;
+  size_t size = 0;
+
+  if (refreshScreenBuffer(&buffer, &size)) {
+    const ScreenHeader *header = (void *)buffer;
+    const uint16_t *from = (void *)(buffer + sizeof(*header));
+    const uint16_t *to = (void *)(buffer + getScreenBufferSize(&header->size));
+
+    if (from < to) {
+      const uint16_t vga = *from++;
+
+      while (from < to) {
+        if (*from++ != vga) {
+          isUnused = 0;
+          break;
+        }
+      }
+    }
+  }
+
+  if (buffer) free(buffer);
+  return isUnused;
+}
+
+static int
+canOpenCurrentConsole (void) {
+  typedef uint16_t OpenableConsoles;
+  static OpenableConsoles openableConsoles = 0;
+
+  struct vt_stat state;
+  if (!getConsoleState(&state)) return 0;
+
+  int console = virtualTerminalNumber;
+  if (!console) console = state.v_active;
+  OpenableConsoles bit = 1 << console;
+
+  if (bit && !(openableConsoles & bit)) {
+    if (console != MAIN_CONSOLE) {
+      if (!(state.v_state & bit)) {
+        if (isUnusedConsole(console)) {
+          return 0;
+        }
+      }
+    }
+
+    openableConsoles |= bit;
+  }
+
+  return 1;
+}
+
+static int
+getConsoleNumber (void) {
+  int console;
+
+  if (virtualTerminalNumber) {
+    console = virtualTerminalNumber;
+  } else {
+    struct vt_stat state;
+    if (!getConsoleState(&state)) return NO_CONSOLE;
+    console = state.v_active;
+  }
+
+  if (console != currentConsoleNumber) {
+    closeCurrentConsole();
+  }
+
+  if (consoleDescriptor == -1) {
+    if (!canOpenCurrentConsole()) {
+      problemText = gettext("console not in use");
+    } else if (!openCurrentConsole()) {
+      problemText = gettext("can't open console");
+    }
+
+    setTranslationTable(1);
+  }
+
+  return console;
+}
+
+static int
+testTextMode (void) {
+  if (problemText) return 0;
+  int mode;
+
+  if (controlCurrentConsole(KDGETMODE, &mode) == -1) {
+    logSystemError("ioctl[KDGETMODE]");
+  } else if (mode == KD_TEXT) {
+    if (afterTimePeriod(&mappingRecalculationTimer, NULL)) setTranslationTable(0);
+    return 1;
+  }
+
+  problemText = gettext("screen not in text mode");
+  return 0;
+}
+
+static int
+refreshCache (void) {
+  size_t size = refreshScreenBuffer(&screenCacheBuffer, &screenCacheSize);
+  if (!size) return 0;
+
+  if (unicodeEnabled) {
+    if (!refreshUnicodeCache(size)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+refresh_LinuxScreen (void) {
+  if (screenUpdated) {
+    while (1) {
+      problemText = NULL;
+
+      if (!refreshCache()) {
+        problemText = gettext("can't read screen content");
+        goto done;
+      }
+
+      {
+        int consoleNumber = getConsoleNumber();
+        if (consoleNumber == currentConsoleNumber) break;
+
+        logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                   "console number changed: %u -> %u",
+                   currentConsoleNumber, consoleNumber);
+
+        currentConsoleNumber = consoleNumber;
+      }
+    }
+
+    inTextMode = testTextMode();
+    screenUpdated = 0;
+
+  done:
+    if (problemText) {
+      if (*fallbackText) {
+        problemText = gettext(fallbackText);
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+getScreenDescription (ScreenDescription *description) {
+  ScreenHeader header;
+
+  if (readScreenHeader(&header)) {
+    description->cols = header.size.columns;
+    description->rows = header.size.rows;
+
+    description->posx = header.location.column;
+    description->posy = header.location.row;
+
+    adjustCursorColumn(&description->posx, description->posy, description->cols);
+    return 1;
+  }
+
+  problemText = gettext("can't read screen header");
+  return 0;
+}
+
+static void
+describe_LinuxScreen (ScreenDescription *description) {
+  if (!screenCacheBuffer) {
+    problemText = NULL;
+    currentConsoleNumber = getConsoleNumber();
+    inTextMode = testTextMode();
+  }
+
+  if ((description->number = currentConsoleNumber)) {
+    if (inTextMode) {
+      if (getScreenDescription(description)) {
+      }
+    }
+  }
+
+  if ((description->unreadable = problemText)) {
+    description->cols = strlen(problemText);
+    description->rows = 1;
+
+    description->posx = 0;
+    description->posy = 0;
+  }
+}
+
+static int
+readCharacters_LinuxScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  ScreenSize size;
+
+  if (readScreenSize(&size)) {
+    if (validateScreenBox(box, size.columns, size.rows)) {
+      if (problemText) {
+        setScreenMessage(box, buffer, problemText);
+        return 1;
+      }
+
+      for (unsigned int row=0; row<box->height; row+=1) {
+        ScreenCharacter characters[size.columns];
+        if (!readScreenRow(box->top+row, size.columns, characters, NULL)) return 0;
+
+        memcpy(buffer, &characters[box->left],
+               (box->width * sizeof(characters[0])));
+
+        buffer += box->width;
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+getCapsLockState (void) {
+  char leds;
+  if (controlCurrentConsole(KDGETLED, &leds) != -1)
+    if (leds & LED_CAP)
+      return 1;
+  return 0;
+}
+
+static inline int
+hasModUpper (ScreenKey key) {
+  return (key & SCR_KEY_UPPER) && !getCapsLockState();
+}
+
+static inline int
+hasModShift (ScreenKey key) {
+  return !!(key & SCR_KEY_SHIFT);
+}
+
+static inline int
+hasModControl (ScreenKey key) {
+  return !!(key & SCR_KEY_CONTROL);
+}
+
+static inline int
+hasModAltLeft (ScreenKey key) {
+  return !!(key & SCR_KEY_ALT_LEFT);
+}
+
+static inline int
+hasModAltRight (ScreenKey key) {
+  return !!(key & SCR_KEY_ALT_RIGHT);
+}
+
+static inline int
+hasModGui (ScreenKey key) {
+  return !!(key & SCR_KEY_GUI);
+}
+
+static int
+injectKeyEvent (int key, int press) {
+  logMessage(
+    LOG_CATEGORY(SCREEN_DRIVER) | LOG_DEBUG,
+    "injecting key %s: %02X",
+    (press? "press": "release"), key
+  );
+
+  if (!openKeyboard()) return 0;
+  return writeKeyEvent(uinputKeyboard, key, press);
+}
+
+static int
+insertUinput (
+  LinuxKeyCode code,
+  int modUpper, int modShift, int modControl,
+  int modAltLeft, int modAltRight
+) {
+#ifdef HAVE_LINUX_INPUT_H
+  if (code) {
+#define KEY_EVENT(KEY, PRESS) { if (!injectKeyEvent((KEY), (PRESS))) return 0; }
+    if (modUpper) {
+      KEY_EVENT(KEY_CAPSLOCK, 1);
+      KEY_EVENT(KEY_CAPSLOCK, 0);
+    }
+
+    if (modShift) KEY_EVENT(KEY_LEFTSHIFT, 1);
+    if (modControl) KEY_EVENT(KEY_LEFTCTRL, 1);
+    if (modAltLeft) KEY_EVENT(KEY_LEFTALT, 1);
+    if (modAltRight) KEY_EVENT(KEY_RIGHTALT, 1);
+
+    KEY_EVENT(code, 1);
+    KEY_EVENT(code, 0);
+
+    if (modAltRight) KEY_EVENT(KEY_RIGHTALT, 0);
+    if (modAltLeft) KEY_EVENT(KEY_LEFTALT, 0);
+    if (modControl) KEY_EVENT(KEY_LEFTCTRL, 0);
+    if (modShift) KEY_EVENT(KEY_LEFTSHIFT, 0);
+
+    if (modUpper) {
+      KEY_EVENT(KEY_CAPSLOCK, 1);
+      KEY_EVENT(KEY_CAPSLOCK, 0);
+    }
+#undef KEY_EVENT
+
+    return 1;
+  }
+#endif /* HAVE_LINUX_INPUT_H */
+
+  return 0;
+}
+
+static int
+insertByte (char byte) {
+  logMessage(
+    LOG_CATEGORY(SCREEN_DRIVER) | LOG_DEBUG,
+    "inserting byte: %02X", byte
+  );
+
+  if (controlCurrentConsole(TIOCSTI, &byte) != -1) return 1;
+  logSystemError("ioctl[TIOCSTI]");
+  logPossibleCause("BRLTTY is running without the CAP_SYS_ADMIN kernel capability (see man 7 capabilities)");
+  logPossibleCause("the sysctl parameter dev.tty.legacy_tiocsti is off (see https://lore.kernel.org/linux-hardening/Y0m9l52AKmw6Yxi1@hostpad/)");
+
+  message(
+    NULL, "Linux character injection (TIOCSTI) is disabled on this system",
+    (MSG_SILENT)
+  );
+
+  return 0;
+}
+
+static int
+insertBytes (const char *byte, size_t count) {
+  while (count) {
+    if (!insertByte(*byte++)) return 0;
+    count -= 1;
+  }
+  return 1;
+}
+
+static int
+insertXlate (wchar_t character) {
+  char bytes[MB_LEN_MAX];
+  size_t count;
+  CharacterConversionResult result = convertWcharToChars(character, bytes, sizeof(bytes), &count);
+
+  if (result != CONV_OK) {
+    uint32_t value = character;
+
+    logMessage(LOG_WARNING, "character not supported in xlate mode: 0X%02"PRIX32, value);
+    return 0;
+  }
+
+  return insertBytes(bytes, count);
+}
+
+static int
+insertUnicode (wchar_t character) {
+  {
+    Utf8Buffer utf8;
+    size_t utfs = convertWcharToUtf8(character, utf8);
+
+    if (utfs) return insertBytes(utf8, utfs);
+  }
+
+  {
+    uint32_t value = character;
+
+    logMessage(LOG_WARNING, "character not supported in unicode keyboard mode: 0X%02"PRIX32, value);
+  }
+
+  return 0;
+}
+
+static int
+insertCode (ScreenKey key, int raw) {
+  const LinuxKeyCode *map;
+  unsigned char code;
+  unsigned char escape;
+
+  setScreenKeyModifiers(&key, SCR_KEY_SHIFT | SCR_KEY_CONTROL);
+
+#define KEY_TO_XT(KEY, ESCAPE, CODE) \
+  case (KEY): \
+  map = linuxKeyMap_xt ## ESCAPE; \
+  code = XT_KEY_ ## ESCAPE ## _ ## CODE; \
+  escape = XT_MOD_ ## ESCAPE; \
+  break;
+
+  switch (key & SCR_KEY_CHAR_MASK) {
+    KEY_TO_XT(SCR_KEY_ESCAPE, 00, Escape)
+    KEY_TO_XT(SCR_KEY_F1, 00, F1)
+    KEY_TO_XT(SCR_KEY_F2, 00, F2)
+    KEY_TO_XT(SCR_KEY_F3, 00, F3)
+    KEY_TO_XT(SCR_KEY_F4, 00, F4)
+    KEY_TO_XT(SCR_KEY_F5, 00, F5)
+    KEY_TO_XT(SCR_KEY_F6, 00, F6)
+    KEY_TO_XT(SCR_KEY_F7, 00, F7)
+    KEY_TO_XT(SCR_KEY_F8, 00, F8)
+    KEY_TO_XT(SCR_KEY_F9, 00, F9)
+    KEY_TO_XT(SCR_KEY_F10, 00, F10)
+    KEY_TO_XT(SCR_KEY_F11, 00, F11)
+    KEY_TO_XT(SCR_KEY_F12, 00, F12)
+
+    KEY_TO_XT(SCR_KEY_F13, 00, F13)
+    KEY_TO_XT(SCR_KEY_F14, 00, F14)
+    KEY_TO_XT(SCR_KEY_F15, 00, F15)
+    KEY_TO_XT(SCR_KEY_F16, 00, F16)
+    KEY_TO_XT(SCR_KEY_F17, 00, F17)
+    KEY_TO_XT(SCR_KEY_F18, 00, F18)
+    KEY_TO_XT(SCR_KEY_F19, 00, F19)
+    KEY_TO_XT(SCR_KEY_F20, 00, F20)
+    KEY_TO_XT(SCR_KEY_F21, 00, F21)
+    KEY_TO_XT(SCR_KEY_F22, 00, F22)
+    KEY_TO_XT(SCR_KEY_F23, 00, F23)
+    KEY_TO_XT(SCR_KEY_F24, 00, F24)
+
+    KEY_TO_XT('`', 00, Grave)
+    KEY_TO_XT('1', 00, 1)
+    KEY_TO_XT('2', 00, 2)
+    KEY_TO_XT('3', 00, 3)
+    KEY_TO_XT('4', 00, 4)
+    KEY_TO_XT('5', 00, 5)
+    KEY_TO_XT('6', 00, 6)
+    KEY_TO_XT('7', 00, 7)
+    KEY_TO_XT('8', 00, 8)
+    KEY_TO_XT('9', 00, 9)
+    KEY_TO_XT('0', 00, 0)
+    KEY_TO_XT('-', 00, Minus)
+    KEY_TO_XT('=', 00, Equal)
+    KEY_TO_XT(SCR_KEY_BACKSPACE, 00, Backspace)
+
+    KEY_TO_XT(SCR_KEY_TAB, 00, Tab)
+    KEY_TO_XT('q', 00, Q)
+    KEY_TO_XT('w', 00, W)
+    KEY_TO_XT('e', 00, E)
+    KEY_TO_XT('r', 00, R)
+    KEY_TO_XT('t', 00, T)
+    KEY_TO_XT('y', 00, Y)
+    KEY_TO_XT('u', 00, U)
+    KEY_TO_XT('i', 00, I)
+    KEY_TO_XT('o', 00, O)
+    KEY_TO_XT('p', 00, P)
+    KEY_TO_XT('[', 00, LeftBracket)
+    KEY_TO_XT(']', 00, RightBracket)
+    KEY_TO_XT('\\', 00, Backslash)
+
+    KEY_TO_XT('a', 00, A)
+    KEY_TO_XT('s', 00, S)
+    KEY_TO_XT('d', 00, D)
+    KEY_TO_XT('f', 00, F)
+    KEY_TO_XT('g', 00, G)
+    KEY_TO_XT('h', 00, H)
+    KEY_TO_XT('j', 00, J)
+    KEY_TO_XT('k', 00, K)
+    KEY_TO_XT('l', 00, L)
+    KEY_TO_XT(';', 00, Semicolon)
+    KEY_TO_XT('\'', 00, Apostrophe)
+    KEY_TO_XT(SCR_KEY_ENTER, 00, Enter)
+
+    KEY_TO_XT('z', 00, Z)
+    KEY_TO_XT('x', 00, X)
+    KEY_TO_XT('c', 00, C)
+    KEY_TO_XT('v', 00, V)
+    KEY_TO_XT('b', 00, B)
+    KEY_TO_XT('n', 00, N)
+    KEY_TO_XT('m', 00, M)
+    KEY_TO_XT(',', 00, Comma)
+    KEY_TO_XT('.', 00, Period)
+    KEY_TO_XT('/', 00, Slash)
+
+    KEY_TO_XT(' ', 00, Space)
+
+    KEY_TO_XT(SCR_KEY_INSERT, E0, Insert)
+    KEY_TO_XT(SCR_KEY_DELETE, E0, Delete)
+    KEY_TO_XT(SCR_KEY_HOME, E0, Home)
+    KEY_TO_XT(SCR_KEY_END, E0, End)
+    KEY_TO_XT(SCR_KEY_PAGE_UP, E0, PageUp)
+    KEY_TO_XT(SCR_KEY_PAGE_DOWN, E0, PageDown)
+
+    KEY_TO_XT(SCR_KEY_CURSOR_UP, E0, ArrowUp)
+    KEY_TO_XT(SCR_KEY_CURSOR_LEFT, E0, ArrowLeft)
+    KEY_TO_XT(SCR_KEY_CURSOR_DOWN, E0, ArrowDown)
+    KEY_TO_XT(SCR_KEY_CURSOR_RIGHT, E0, ArrowRight)
+
+    default:
+      logMessage(LOG_WARNING, "key not supported in raw keyboard mode: %04X", key);
+      return 0;
+  }
+#undef KEY_TO_XT
+
+  {
+    const int modUpper = hasModUpper(key);
+    const int modShift = hasModShift(key);
+    const int modControl = hasModControl(key);
+    const int modAltLeft = hasModAltLeft(key);
+    const int modAltRight = hasModAltRight(key);
+    const int modGui = hasModGui(key);
+
+    if (raw) {
+      char codes[22];
+      unsigned int count = 0;
+
+      if (modUpper) {
+        codes[count++] = XT_KEY_00_CapsLock;
+        codes[count++] = XT_KEY_00_CapsLock | XT_BIT_RELEASE;
+      }
+
+      if (modShift) codes[count++] = XT_KEY_00_LeftShift;
+      if (modControl) codes[count++] = XT_KEY_00_LeftControl;
+      if (modAltLeft) codes[count++] = XT_KEY_00_LeftAlt;
+
+      if (modAltRight) {
+        codes[count++] = XT_MOD_E0;
+        codes[count++] = XT_KEY_E0_RightAlt;
+      }
+
+      if (modGui) {
+        codes[count++] = XT_MOD_E0;
+        codes[count++] = XT_KEY_E0_LeftGUI;
+      }
+
+      if (escape) codes[count++] = escape;
+      codes[count++] = code;
+
+      if (escape) codes[count++] = escape;
+      codes[count++] = code | XT_BIT_RELEASE;
+
+      if (modGui) {
+        codes[count++] = XT_MOD_E0;
+        codes[count++] = XT_KEY_E0_LeftGUI | XT_BIT_RELEASE;
+      }
+
+      if (modAltRight) {
+        codes[count++] = XT_MOD_E0;
+        codes[count++] = XT_KEY_E0_RightAlt | XT_BIT_RELEASE;
+      }
+
+      if (modAltLeft) codes[count++] = XT_KEY_00_LeftAlt | XT_BIT_RELEASE;
+      if (modControl) codes[count++] = XT_KEY_00_LeftControl | XT_BIT_RELEASE;
+      if (modShift) codes[count++] = XT_KEY_00_LeftShift | XT_BIT_RELEASE;
+
+      if (modUpper) {
+        codes[count++] = XT_KEY_00_CapsLock;
+        codes[count++] = XT_KEY_00_CapsLock | XT_BIT_RELEASE;
+      }
+
+      return insertBytes(codes, count);
+    } else {
+      LinuxKeyCode mapped = map[code];
+
+      if (!mapped) {
+        logMessage(LOG_WARNING, "key not supported in medium raw keyboard mode: %04X", key);
+        return 0;
+      }
+
+      return insertUinput(mapped, modUpper, modShift, modControl, modAltLeft, modAltRight);
+    }
+  }
+}
+
+static int
+insertTranslated (ScreenKey key, int (*insertCharacter)(wchar_t character)) {
+  wchar_t buffer[2];
+  const wchar_t *sequence;
+  const wchar_t *end;
+
+  setScreenKeyModifiers(&key, 0);
+
+  if (isSpecialKey(key)) {
+    switch (key) {
+      case SCR_KEY_ENTER:
+        sequence = WS_C("\r");
+        break;
+
+      case SCR_KEY_TAB:
+        sequence = WS_C("\t");
+        break;
+
+      case SCR_KEY_BACKSPACE:
+        sequence = WS_C("\x7f");
+        break;
+
+      case SCR_KEY_ESCAPE:
+        sequence = WS_C("\x1b");
+        break;
+
+      case SCR_KEY_CURSOR_LEFT:
+        sequence = WS_C("\x1b[D");
+        break;
+
+      case SCR_KEY_CURSOR_RIGHT:
+        sequence = WS_C("\x1b[C");
+        break;
+
+      case SCR_KEY_CURSOR_UP:
+        sequence = WS_C("\x1b[A");
+        break;
+
+      case SCR_KEY_CURSOR_DOWN:
+        sequence = WS_C("\x1b[B");
+        break;
+
+      case SCR_KEY_PAGE_UP:
+        sequence = WS_C("\x1b[5~");
+        break;
+
+      case SCR_KEY_PAGE_DOWN:
+        sequence = WS_C("\x1b[6~");
+        break;
+
+      case SCR_KEY_HOME:
+        sequence = WS_C("\x1b[1~");
+        break;
+
+      case SCR_KEY_END:
+        sequence = WS_C("\x1b[4~");
+        break;
+
+      case SCR_KEY_INSERT:
+        sequence = WS_C("\x1b[2~");
+        break;
+
+      case SCR_KEY_DELETE:
+        sequence = WS_C("\x1b[3~");
+        break;
+
+      case SCR_KEY_F1:
+        sequence = WS_C("\x1b[[A");
+        break;
+
+      case SCR_KEY_F2:
+        sequence = WS_C("\x1b[[B");
+        break;
+
+      case SCR_KEY_F3:
+        sequence = WS_C("\x1b[[C");
+        break;
+
+      case SCR_KEY_F4:
+        sequence = WS_C("\x1b[[D");
+        break;
+
+      case SCR_KEY_F5:
+        sequence = WS_C("\x1b[[E");
+        break;
+
+      case SCR_KEY_F6:
+        sequence = WS_C("\x1b[17~");
+        break;
+
+      case SCR_KEY_F7:
+        sequence = WS_C("\x1b[18~");
+        break;
+
+      case SCR_KEY_F8:
+        sequence = WS_C("\x1b[19~");
+        break;
+
+      case SCR_KEY_F9:
+        sequence = WS_C("\x1b[20~");
+        break;
+
+      case SCR_KEY_F10:
+        sequence = WS_C("\x1b[21~");
+        break;
+
+      case SCR_KEY_F11:
+        sequence = WS_C("\x1b[23~");
+        break;
+
+      case SCR_KEY_F12:
+        sequence = WS_C("\x1b[24~");
+        break;
+
+      case SCR_KEY_F13:
+        sequence = WS_C("\x1b[25~");
+        break;
+
+      case SCR_KEY_F14:
+        sequence = WS_C("\x1b[26~");
+        break;
+
+      case SCR_KEY_F15:
+        sequence = WS_C("\x1b[28~");
+        break;
+
+      case SCR_KEY_F16:
+        sequence = WS_C("\x1b[29~");
+        break;
+
+      case SCR_KEY_F17:
+        sequence = WS_C("\x1b[31~");
+        break;
+
+      case SCR_KEY_F18:
+        sequence = WS_C("\x1b[32~");
+        break;
+
+      case SCR_KEY_F19:
+        sequence = WS_C("\x1b[33~");
+        break;
+
+      case SCR_KEY_F20:
+        sequence = WS_C("\x1b[34~");
+        break;
+
+      default:
+        if (insertCode(key, 0)) return 1;
+        logMessage(LOG_WARNING, "key not supported in xlate keyboard mode: %04X", key);
+        return 0;
+    }
+
+    end = sequence + wcslen(sequence);
+  } else {
+    wchar_t *character = buffer + ARRAY_COUNT(buffer);
+
+    end = character;
+    *--character = key & SCR_KEY_CHAR_MASK;
+
+    if (hasModAltLeft(key)) {
+      int meta;
+
+      if (controlCurrentConsole(KDGKBMETA, &meta) == -1) return 0;
+
+      switch (meta) {
+        case K_ESCPREFIX:
+          *--character = ASCII_ESC;
+          break;
+
+        case K_METABIT:
+          if (*character >= 0X80) {
+            logMessage(LOG_WARNING, "can't add meta bit to character: U+%04X", (uint32_t)*character);
+            return 0;
+          }
+
+          *character |= 0X80;
+          break;
+
+        default:
+          logMessage(LOG_WARNING, "unsupported keyboard meta mode: %d", meta);
+          return 0;
+      }
+    }
+
+    sequence = character;
+  }
+
+  while (sequence != end) {
+    if (!insertCharacter(*sequence)) return 0;
+    sequence += 1;
+  }
+
+  return 1;
+}
+
+static int
+insertKey_LinuxScreen (ScreenKey key) {
+  int ok = 0;
+  int mode;
+
+  if (controlCurrentConsole(KDGKBMODE, &mode) != -1) {
+    switch (mode) {
+      case K_RAW:
+        if (insertCode(key, 1)) ok = 1;
+        break;
+
+      case K_MEDIUMRAW:
+        if (insertCode(key, 0)) ok = 1;
+        break;
+
+      case K_XLATE:
+        if (insertTranslated(key, insertXlate)) ok = 1;
+        break;
+
+      case K_UNICODE:
+        if (insertTranslated(key, insertUnicode)) ok = 1;
+        break;
+
+#ifdef K_OFF
+      case K_OFF:
+        ok = 1;
+        break;
+#endif /* K_OFF */
+
+      default:
+        logMessage(LOG_WARNING, "unsupported keyboard mode: %d", mode);
+        break;
+    }
+  } else {
+    logSystemError("ioctl[KDGKBMODE]");
+  }
+
+  return ok;
+}
+
+typedef struct {
+  char subcode;
+  struct tiocl_selection selection;
+} PACKED RegionSelectionArgument;
+
+static int
+selectRegion (RegionSelectionArgument *argument) {
+  if (controlCurrentConsole(TIOCLINUX, argument) != -1) return 1;
+  if (errno != EINVAL) logSystemError("ioctl[TIOCLINUX]");
+  return 0;
+}
+
+static int
+highlightRegion_LinuxScreen (int left, int right, int top, int bottom) {
+  RegionSelectionArgument argument = {
+    .subcode = TIOCL_SETSEL,
+
+    .selection = {
+      .xs = left + 1,
+      .ys = top + 1,
+      .xe = right + 1,
+      .ye = bottom + 1,
+      .sel_mode = TIOCL_SELCHAR
+    }
+  };
+
+  return selectRegion(&argument);
+}
+
+static int
+unhighlightRegion_LinuxScreen (void) {
+  RegionSelectionArgument argument = {
+    .subcode = TIOCL_SETSEL,
+
+    .selection = {
+      .xs = 0,
+      .ys = 0,
+      .xe = 0,
+      .ye = 0,
+      .sel_mode = TIOCL_SELCLEAR
+    }
+  };
+
+  return selectRegion(&argument);
+}
+
+static int
+validateVt (int vt) {
+  if ((vt >= 1) && (vt <= MAX_NR_CONSOLES)) return 1;
+  logMessage(LOG_WARNING, "virtual terminal out of range: %d", vt);
+  return 0;
+}
+
+static int
+selectVirtualTerminal_LinuxScreen (int vt) {
+  if (vt == virtualTerminalNumber) return 1;
+  if (vt && !validateVt(vt)) return 0;
+  return setCurrentScreen(vt);
+}
+
+static int
+switchVirtualTerminal_LinuxScreen (int vt) {
+  if (validateVt(vt)) {
+    if (selectVirtualTerminal_LinuxScreen(0)) {
+      if (controlMainConsole(VT_ACTIVATE, (void *)(intptr_t)vt) != -1) {
+        logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+                   "switched to virtual tertminal %d", vt);
+        return 1;
+      } else {
+        logSystemError("ioctl[VT_ACTIVATE]");
+      }
+    }
+  }
+  return 0;
+}
+
+static int
+currentVirtualTerminal_LinuxScreen (void) {
+  return currentConsoleNumber;
+}
+
+static int
+userVirtualTerminal_LinuxScreen (int number) {
+  return MAX_NR_CONSOLES + 1 + number;
+}
+
+static int
+handleCommand_LinuxScreen (int command) {
+  int blk = command & BRL_MSK_BLK;
+  int arg UNUSED = command & BRL_MSK_ARG;
+  int cmd = blk | arg;
+
+  switch (cmd) {
+    default:
+#ifdef HAVE_LINUX_INPUT_H
+      switch (blk) {
+        case BRL_CMD_BLK(PASSXT):
+          if (command & BRL_FLG_KBD_RELEASE) arg |= XT_BIT_RELEASE;
+
+          {
+            int handled = 0;
+
+            if (command & BRL_FLG_KBD_EMUL0) {
+              xtKeys = linuxKeyMap_xtE0;
+            } else if (arg == XT_MOD_E0) {
+              xtKeys = linuxKeyMap_xtE0;
+              handled = 1;
+	    } else if (command & BRL_FLG_KBD_EMUL1) {
+              xtKeys = linuxKeyMap_xtE1;
+            } else if (arg == XT_MOD_E1) {
+              xtKeys = linuxKeyMap_xtE1;
+              handled = 1;
+            }
+
+            if (handled) return 1;
+          }
+
+	  {
+            LinuxKeyCode key = xtKeys[arg & ~XT_BIT_RELEASE];
+            int press = !(arg & XT_BIT_RELEASE);
+
+            xtKeys = linuxKeyMap_xt00;
+
+            if (key) return injectKeyEvent(key, press);
+	  }
+          break;
+
+	case BRL_CMD_BLK(PASSAT):
+          {
+            int handled = 0;
+
+            if (command & BRL_FLG_KBD_RELEASE) {
+              atKeyPressed = 0;
+            } else if (arg == AT_MOD_RELEASE) {
+              atKeyPressed = 0;
+              handled = 1;
+            }
+
+            if (command & BRL_FLG_KBD_EMUL0) {
+              atKeys = linuxKeyMap_atE0;
+            } else if (arg == AT_MOD_E0) {
+              atKeys = linuxKeyMap_atE0;
+              handled = 1;
+	    } else if (command & BRL_FLG_KBD_EMUL1) {
+              atKeys = linuxKeyMap_atE1;
+            } else if (arg == AT_MOD_E1) {
+              atKeys = linuxKeyMap_atE1;
+              handled = 1;
+            }
+
+            if (handled) return 1;
+          }
+
+          {
+            LinuxKeyCode key = atKeys[arg];
+            int press = atKeyPressed;
+
+            atKeys = linuxKeyMap_at00;
+            atKeyPressed = 1;
+
+            if (key) return injectKeyEvent(key, press);
+          }
+          break;
+
+	case BRL_CMD_BLK(PASSPS2):
+          {
+            int handled = 0;
+
+            if (command & BRL_FLG_KBD_RELEASE) {
+              ps2KeyPressed = 0;
+            } else if (arg == PS2_MOD_RELEASE) {
+              ps2KeyPressed = 0;
+              handled = 1;
+            }
+
+            if (handled) return 1;
+          }
+
+          {
+            LinuxKeyCode key = linuxKeyMap_ps2[arg];
+            int press = ps2KeyPressed;
+
+            ps2KeyPressed = 1;
+
+            if (key) return injectKeyEvent(key, press);
+          }
+          break;
+
+        default:
+          break;
+      }
+#endif /* HAVE_LINUX_INPUT_H */
+      break;
+  }
+
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  gpmIncludeScreenHandlers(main);
+
+  main->base.poll = poll_LinuxScreen;
+  main->base.refresh = refresh_LinuxScreen;
+  main->base.describe = describe_LinuxScreen;
+  main->base.readCharacters = readCharacters_LinuxScreen;
+  main->base.insertKey = insertKey_LinuxScreen;
+  main->base.highlightRegion = highlightRegion_LinuxScreen;
+  main->base.unhighlightRegion = unhighlightRegion_LinuxScreen;
+  main->base.selectVirtualTerminal = selectVirtualTerminal_LinuxScreen;
+  main->base.switchVirtualTerminal = switchVirtualTerminal_LinuxScreen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_LinuxScreen;
+  main->base.handleCommand = handleCommand_LinuxScreen;
+
+  main->processParameters = processParameters_LinuxScreen;
+  main->releaseParameters = releaseParameters_LinuxScreen;
+  main->construct = construct_LinuxScreen;
+  main->destruct = destruct_LinuxScreen;
+  main->userVirtualTerminal = userVirtualTerminal_LinuxScreen;
+}
diff --git a/Drivers/Screen/Linux/screen.h b/Drivers/Screen/Linux/screen.h
new file mode 100644
index 0000000..5efb4a7
--- /dev/null
+++ b/Drivers/Screen/Linux/screen.h
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_LINUX
+#define BRLTTY_INCLUDED_SCR_LINUX
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef unsigned short int UnicodeNumber;
+typedef UnicodeNumber ApplicationCharacterMap[0X100];
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_LINUX */
diff --git a/Drivers/Screen/PcBios/Makefile.in b/Drivers/Screen/PcBios/Makefile.in
new file mode 100644
index 0000000..ad76357
--- /dev/null
+++ b/Drivers/Screen/PcBios/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = pb
+DRIVER_NAME = PcBios
+DRIVER_USAGE = MS-DOS
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/PcBios/screen.c b/Drivers/Screen/PcBios/screen.c
new file mode 100644
index 0000000..878a4cf
--- /dev/null
+++ b/Drivers/Screen/PcBios/screen.c
@@ -0,0 +1,231 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pc.h>
+#include <go32.h>
+#include <dpmi.h>
+#include <sys/farptr.h>
+
+#include "log.h"
+#include "brl_cmds.h"
+#include "charset.h"
+#include "kbd_keycodes.h"
+
+#include "scr_driver.h"
+
+static int
+processParameters_PcBiosScreen (char **parameters) {
+  return 1;
+}
+
+static int
+construct_PcBiosScreen (void) {
+  return 1;
+}
+
+static void
+destruct_PcBiosScreen (void) {
+}
+
+static void
+describe_PcBiosScreen (ScreenDescription *description) {
+  int row, column;
+  description->rows = ScreenRows();
+  description->cols = ScreenCols();
+  ScreenGetCursor(&row, &column);
+  description->posx = column;
+  description->posy = row;
+  description->number = 1;
+}
+
+static int
+readCharacters_PcBiosScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  unsigned offset = ScreenPrimary;
+  ScreenDescription description;
+  int row, col;
+  describe_PcBiosScreen(&description);
+  if (!validateScreenBox(box, description.cols, description.rows)) return 0;
+  _farsetsel(_go32_conventional_mem_selector());
+  for (row=box->top; row<box->top+box->height; ++row)
+    for (col=box->left; col<box->left+box->width; ++col) {
+      buffer->text = _farnspeekb(offset + row*description.cols*2 + col*2);
+      buffer->attributes = _farnspeekb(offset + row*description.cols*2 + col*2 + 1);
+      buffer++;
+    }
+  return 1;
+}
+
+static int
+simulateKey (unsigned char scanCode, unsigned char asciiCode) {
+  __dpmi_regs r;
+  r.h.ah = 0x05;
+  r.h.ch = scanCode;
+  r.h.cl = asciiCode;
+  __dpmi_int(0x16, &r);
+  return !r.h.ah;
+}
+
+static int
+insertMapped (ScreenKey key) {
+  char buffer[2];
+  char *sequence;
+  char *end;
+
+  if (isSpecialKey(key)) {
+    switch (key & SCR_KEY_CHAR_MASK) {
+      case SCR_KEY_ENTER:
+        sequence = "\r";
+        break;
+      case SCR_KEY_TAB:
+        sequence = "\t";
+        break;
+      case SCR_KEY_BACKSPACE:
+        sequence = "\x08";
+        break;
+      case SCR_KEY_ESCAPE:
+        sequence = "\x1b";
+        break;
+      case SCR_KEY_CURSOR_LEFT:
+	return(simulateKey(105, 0));
+      case SCR_KEY_CURSOR_RIGHT:
+        return(simulateKey(106, 0));
+      case SCR_KEY_CURSOR_UP:
+        return(simulateKey(103, 0));
+      case SCR_KEY_CURSOR_DOWN:
+        return(simulateKey(108, 0));
+      case SCR_KEY_PAGE_UP:
+        return(simulateKey(104, 0));
+      case SCR_KEY_PAGE_DOWN:
+        return(simulateKey(109, 0));
+      case SCR_KEY_HOME:
+        return(simulateKey(102, 0));
+      case SCR_KEY_END:
+        return(simulateKey(107, 0));
+      case SCR_KEY_INSERT:
+        return(simulateKey(110, 0));
+      case SCR_KEY_DELETE:
+        return(simulateKey(111, 0));
+      case SCR_KEY_FUNCTION + 0:
+      case SCR_KEY_FUNCTION + 1:
+      case SCR_KEY_FUNCTION + 2:
+      case SCR_KEY_FUNCTION + 3:
+      case SCR_KEY_FUNCTION + 4:
+      case SCR_KEY_FUNCTION + 5:
+      case SCR_KEY_FUNCTION + 6:
+      case SCR_KEY_FUNCTION + 7:
+      case SCR_KEY_FUNCTION + 8:
+      case SCR_KEY_FUNCTION + 9:
+      case SCR_KEY_FUNCTION + 10:
+        return(simulateKey(key - SCR_KEY_FUNCTION + 59, 0));
+      case SCR_KEY_FUNCTION + 11:
+        return(simulateKey(87, 0));
+      case SCR_KEY_FUNCTION + 12:
+        return(simulateKey(88, 0));
+      default:
+        logMessage(LOG_WARNING, "Key %4.4X not suported.", key);
+        return 0;
+    }
+    end = sequence + strlen(sequence);
+  } else {
+    int character = key & SCR_KEY_CHAR_MASK;
+    int c = convertWcharToChar(character);
+
+    if (c== EOF) {
+      logMessage(LOG_WARNING, "Character %d not supported", character);
+      return 0;
+    }
+
+    sequence = end = buffer + ARRAY_COUNT(buffer);
+    *--sequence = key & SCR_KEY_CHAR_MASK;
+
+    if (key & SCR_KEY_ALT_LEFT)
+      *--sequence = 0X1B;
+  }
+
+  while (sequence != end)
+    if (simulateKey(0, *sequence++))
+      return 0;
+  return 1;
+}
+
+static int
+insertKey_PcBiosScreen (ScreenKey key) {
+  logMessage(LOG_DEBUG, "Insert key: %4.4X", key);
+  return insertMapped(key); 
+}
+
+static int
+currentVirtualTerminal_PcBiosScreen (void) {
+  return 1;
+}
+
+static int
+handleCommand_PcBiosScreen (int command) {
+  int blk = command & BRL_MSK_BLK;
+  int arg = command & BRL_MSK_ARG;
+  int press = 0;
+
+  switch (blk) {
+    case BRL_CMD_BLK(PASSXT):
+      press = !(arg & 0X80);
+      arg &= 0X7F;
+      /* fallthrough */
+    case BRL_CMD_BLK(PASSAT): {
+      press |= !(command & BRL_FLG_KBD_RELEASE);
+
+      if (arg >= 0X80)
+	return 0;
+
+      if (command & BRL_FLG_KBD_EMUL0) {
+	if (!simulateKey(0XE0, 0))
+	  return 0;
+      } else if (command & BRL_FLG_KBD_EMUL1) {
+	if (!simulateKey(0XE1, 0))
+	  return 0;
+      }
+
+      if (blk == BRL_CMD_BLK(PASSAT))
+	arg = AT2XT[arg];
+
+      if (!press)
+	arg |= 0x80;
+
+      return simulateKey(arg, 0);
+    }
+  }
+
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.describe = describe_PcBiosScreen;
+  main->base.readCharacters = readCharacters_PcBiosScreen;
+  main->base.insertKey = insertKey_PcBiosScreen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_PcBiosScreen;
+  main->base.handleCommand = handleCommand_PcBiosScreen;
+  main->processParameters = processParameters_PcBiosScreen;
+  main->construct = construct_PcBiosScreen;
+  main->destruct = destruct_PcBiosScreen;
+}
diff --git a/Drivers/Screen/Screen/Makefile.in b/Drivers/Screen/Screen/Makefile.in
new file mode 100644
index 0000000..12ae865
--- /dev/null
+++ b/Drivers/Screen/Screen/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = sc
+DRIVER_NAME = Screen
+DRIVER_USAGE = patched screen program
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Screen/screen.c b/Drivers/Screen/Screen/screen.c
new file mode 100644
index 0000000..983e73d
--- /dev/null
+++ b/Drivers/Screen/Screen/screen.c
@@ -0,0 +1,336 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SHMGET
+#include <sys/ipc.h>
+#include <sys/shm.h>
+static key_t shmKey;
+static int shmIdentifier;
+#endif /* HAVE_SHMGET */
+
+#ifdef HAVE_SHM_OPEN
+#include <sys/mman.h>
+static const char *shmPath = "/screen";
+static int shmFileDescriptor = -1;
+#endif /* HAVE_SHM_OPEN */
+
+#include "log.h"
+#include "strfmt.h"
+#include "hostcmd.h"
+#include "charset.h"
+#include "ascii.h"
+
+#include "scr_driver.h"
+#include "screen.h"
+
+static unsigned char *shmAddress = NULL;
+static const mode_t shmMode = S_IRWXU;
+static const int shmSize = 4 + ((66 * 132) * 2);
+
+static int
+construct_ScreenScreen (void) {
+#ifdef HAVE_SHMGET
+  {
+    key_t keys[2];
+    int keyCount = 0;
+
+    /* The original, static key. */
+    keys[keyCount++] = 0xBACD072F;
+
+    /* The new, dynamically generated, per user key. */
+    {
+      int project = 'b';
+      const char *path = getenv("HOME");
+      if (!path || !*path) path = "/";
+      logMessage(LOG_DEBUG, "Shared memory file system object: %s", path);
+      if ((keys[keyCount] = ftok(path, project)) != -1) {
+        keyCount++;
+      } else {
+        logMessage(LOG_WARNING, "Per user shared memory key not generated: %s",
+                   strerror(errno));
+      }
+    }
+
+    while (keyCount > 0) {
+      shmKey = keys[--keyCount];
+      logMessage(LOG_DEBUG, "Trying shared memory key: 0X%" PRIkey, shmKey);
+      if ((shmIdentifier = shmget(shmKey, shmSize, shmMode)) != -1) {
+        if ((shmAddress = shmat(shmIdentifier, NULL, 0)) != (unsigned char *)-1) {
+          logMessage(LOG_INFO, "Screen image shared memory key: 0X%" PRIkey, shmKey);
+          return 1;
+        } else {
+          logMessage(LOG_WARNING, "Cannot attach shared memory segment 0X%" PRIkey ": %s",
+                     shmKey, strerror(errno));
+        }
+      } else {
+        logMessage(LOG_WARNING, "Cannot access shared memory segment 0X%" PRIkey ": %s",
+                   shmKey, strerror(errno));
+      }
+    }
+    shmIdentifier = -1;
+  }
+#endif /* HAVE_SHMGET */
+
+#ifdef HAVE_SHM_OPEN
+  {
+    if ((shmFileDescriptor = shm_open(shmPath, O_RDONLY, shmMode)) != -1) {
+      if ((shmAddress = mmap(0, shmSize, PROT_READ, MAP_SHARED, shmFileDescriptor, 0)) != MAP_FAILED) {
+        return 1;
+      } else {
+        logSystemError("mmap");
+      }
+
+      close(shmFileDescriptor);
+      shmFileDescriptor = -1;
+    } else {
+      logSystemError("shm_open");
+    }
+  }
+#endif /* HAVE_SHM_OPEN */
+
+  return 0;
+}
+
+static const unsigned char *
+getAuxiliaryData (void) {
+  return &shmAddress[4 + (shmAddress[0] * shmAddress[1] * 2)];
+}
+
+static int
+currentVirtualTerminal_ScreenScreen (void) {
+  return getAuxiliaryData()[0];
+}
+
+static int
+doScreenCommand (const char *command, ...) {
+  va_list args;
+  int count = 0;
+
+  va_start(args, command);
+  while (va_arg(args, char *)) ++count;
+  va_end(args);
+
+  {
+    char window[0X10];
+    const char *argv[count + 6];
+    const char **arg = argv;
+
+    *arg++ = "screen";
+
+    *arg++ = "-p";
+    snprintf(window, sizeof(window), "%d", currentVirtualTerminal_ScreenScreen());
+    *arg++ = window;
+
+    *arg++ = "-X";
+    *arg++ = command;
+
+    va_start(args, command);
+    while ((*arg++ = va_arg(args, char *)));
+    va_end(args);
+
+    {
+      int result = executeHostCommand(argv);
+
+      if (result == 0) return 1;
+      logMessage(LOG_ERR, "screen error: %d", result);
+    }
+  }
+
+  return 0;
+}
+
+static int
+userVirtualTerminal_ScreenScreen (int number) {
+  return 1 + number;
+}
+
+static void
+describe_ScreenScreen (ScreenDescription *description) {
+  description->cols = shmAddress[0];
+  description->rows = shmAddress[1];
+  description->posx = shmAddress[2];
+  description->posy = shmAddress[3];
+  description->number = currentVirtualTerminal_ScreenScreen();
+}
+
+static int
+readCharacters_ScreenScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  ScreenDescription description;                 /* screen statistics */
+  describe_ScreenScreen(&description);
+  if (validateScreenBox(box, description.cols, description.rows)) {
+    ScreenCharacter *character = buffer;
+    unsigned char *text = shmAddress + 4 + (box->top * description.cols) + box->left;
+    unsigned char *attributes = text + (description.cols * description.rows);
+    size_t increment = description.cols - box->width;
+    int row;
+    for (row=0; row<box->height; row++) {
+      int column;
+      for (column=0; column<box->width; column++) {
+        wint_t wc = convertCharToWchar(*text++);
+        if (wc == WEOF) wc = L'?';
+        character->text = wc;
+        character->attributes = *attributes++;
+        character++;
+      }
+      text += increment;
+      attributes += increment;
+    }
+    return 1;
+  }
+  return 0;
+}
+
+static int
+insertKey_ScreenScreen (ScreenKey key) {
+  const char *sequence;
+  char buffer[0X10];
+
+  setScreenKeyModifiers(&key, 0);
+  wchar_t character = key & SCR_KEY_CHAR_MASK;
+
+  if (isSpecialKey(key)) {
+    const unsigned char flags = getAuxiliaryData()[1];
+
+#define KEY(key,string) case (key): sequence = (string); break
+#define CURSOR_KEY(key,string1,string2) KEY((key), ((flags & 0X01)? (string1): (string2)))
+
+    switch (character) {
+      KEY(SCR_KEY_ENTER, "\r");
+      KEY(SCR_KEY_TAB, "\t");
+      KEY(SCR_KEY_BACKSPACE, "\x7f");
+      KEY(SCR_KEY_ESCAPE, "\x1b");
+
+      CURSOR_KEY(SCR_KEY_CURSOR_LEFT , "\x1bOD", "\x1b[D");
+      CURSOR_KEY(SCR_KEY_CURSOR_RIGHT, "\x1bOC", "\x1b[C");
+      CURSOR_KEY(SCR_KEY_CURSOR_UP   , "\x1bOA", "\x1b[A");
+      CURSOR_KEY(SCR_KEY_CURSOR_DOWN , "\x1bOB", "\x1b[B");
+
+      KEY(SCR_KEY_PAGE_UP, "\x1b[5~");
+      KEY(SCR_KEY_PAGE_DOWN, "\x1b[6~");
+      KEY(SCR_KEY_HOME, "\x1b[1~");
+      KEY(SCR_KEY_END, "\x1b[4~");
+      KEY(SCR_KEY_INSERT, "\x1b[2~");
+      KEY(SCR_KEY_DELETE, "\x1b[3~");
+      KEY(SCR_KEY_FUNCTION+0, "\x1bOP");
+      KEY(SCR_KEY_FUNCTION+1, "\x1bOQ");
+      KEY(SCR_KEY_FUNCTION+2, "\x1bOR");
+      KEY(SCR_KEY_FUNCTION+3, "\x1bOS");
+      KEY(SCR_KEY_FUNCTION+4, "\x1b[15~");
+      KEY(SCR_KEY_FUNCTION+5, "\x1b[17~");
+      KEY(SCR_KEY_FUNCTION+6, "\x1b[18~");
+      KEY(SCR_KEY_FUNCTION+7, "\x1b[19~");
+      KEY(SCR_KEY_FUNCTION+8, "\x1b[20~");
+      KEY(SCR_KEY_FUNCTION+9, "\x1b[21~");
+      KEY(SCR_KEY_FUNCTION+10, "\x1b[23~");
+      KEY(SCR_KEY_FUNCTION+11, "\x1b[24~");
+      KEY(SCR_KEY_FUNCTION+12, "\x1b[25~");
+      KEY(SCR_KEY_FUNCTION+13, "\x1b[26~");
+      KEY(SCR_KEY_FUNCTION+14, "\x1b[28~");
+      KEY(SCR_KEY_FUNCTION+15, "\x1b[29~");
+      KEY(SCR_KEY_FUNCTION+16, "\x1b[31~");
+      KEY(SCR_KEY_FUNCTION+17, "\x1b[32~");
+      KEY(SCR_KEY_FUNCTION+18, "\x1b[33~");
+      KEY(SCR_KEY_FUNCTION+19, "\x1b[34~");
+
+      default:
+        logMessage(LOG_WARNING, "unsupported key: %04X", key);
+        return 0;
+    }
+
+#undef CURSOR_KEY
+#undef KEY
+  } else {
+    int byte = convertWcharToChar(character);
+
+    if (byte == EOF) {
+      logMessage(LOG_WARNING, "character not supported in local character set: 0X%04X", key);
+      return 0;
+    }
+
+    STR_BEGIN(buffer, sizeof(buffer));
+    if (key & SCR_KEY_ALT_LEFT) STR_PRINTF("%c", ASCII_ESC);
+    STR_PRINTF("\\%03o", byte);
+    STR_END;
+
+    sequence = buffer;
+  }
+
+  logBytes(LOG_CATEGORY(SCREEN_DRIVER), "insert bytes", sequence, strlen(sequence));
+  return doScreenCommand("stuff", sequence, NULL);
+}
+
+static int
+switchVirtualTerminal_ScreenScreen (int vt) {
+  if ((vt -= 1) < 0) return 0;
+  char number[0X10];
+  snprintf(number, sizeof(number), "%d", vt);
+  return doScreenCommand("select", number, NULL);
+}
+
+static int
+nextVirtualTerminal_ScreenScreen (void) {
+  return doScreenCommand("next", NULL);
+}
+
+static int
+previousVirtualTerminal_ScreenScreen (void) {
+  return doScreenCommand("prev", NULL);
+}
+
+static void
+destruct_ScreenScreen (void) {
+#ifdef HAVE_SHMGET
+  if (shmIdentifier != -1) {
+    shmdt(shmAddress);
+  }
+#endif /* HAVE_SHMGET */
+
+#ifdef HAVE_SHM_OPEN
+  if (shmFileDescriptor != -1) {
+    munmap(shmAddress, shmSize);
+    close(shmFileDescriptor);
+    shmFileDescriptor = -1;
+  }
+#endif /* HAVE_SHM_OPEN */
+
+  shmAddress = NULL;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.currentVirtualTerminal = currentVirtualTerminal_ScreenScreen;
+  main->base.describe = describe_ScreenScreen;
+  main->base.readCharacters = readCharacters_ScreenScreen;
+  main->base.insertKey = insertKey_ScreenScreen;
+  main->base.switchVirtualTerminal = switchVirtualTerminal_ScreenScreen;
+  main->base.nextVirtualTerminal = nextVirtualTerminal_ScreenScreen;
+  main->base.previousVirtualTerminal = previousVirtualTerminal_ScreenScreen;
+  main->construct = construct_ScreenScreen;
+  main->destruct = destruct_ScreenScreen;
+  main->userVirtualTerminal = userVirtualTerminal_ScreenScreen;
+}
diff --git a/Drivers/Screen/Screen/screen.h b/Drivers/Screen/Screen/screen.h
new file mode 100644
index 0000000..4b5971f
--- /dev/null
+++ b/Drivers/Screen/Screen/screen.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_SHM
+#define BRLTTY_INCLUDED_SCR_SHM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_SHM */
diff --git a/Drivers/Screen/Skeleton/Makefile.in b/Drivers/Screen/Skeleton/Makefile.in
new file mode 100644
index 0000000..e150438
--- /dev/null
+++ b/Drivers/Screen/Skeleton/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = xx
+DRIVER_NAME = DirectoryName
+DRIVER_USAGE = basic example
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Skeleton/screen.c b/Drivers/Screen/Skeleton/screen.c
new file mode 100644
index 0000000..d810d82
--- /dev/null
+++ b/Drivers/Screen/Skeleton/screen.c
@@ -0,0 +1,28 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "scr_driver.h"
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+}
diff --git a/Drivers/Screen/TerminalEmulator/Makefile.in b/Drivers/Screen/TerminalEmulator/Makefile.in
new file mode 100644
index 0000000..b817d28
--- /dev/null
+++ b/Drivers/Screen/TerminalEmulator/Makefile.in
@@ -0,0 +1,36 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = em
+DRIVER_NAME = TerminalEmulator
+DRIVER_USAGE = terminal emulator with shared memory segment
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+include $(SRC_TOP)screen.mk
+
+OBJECT_FILES = em_screen.$O scr_terminal.$O msg_queue.$O
+
+screen.$O: $(OBJECT_FILES)
+	$(MKREL) $@ $(OBJECT_FILES)
+
+em_screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/em_screen.c
+
+%.$O: $(SRC_TOP)$(PGM_DIR)/%.c $(SRC_TOP)$(HDR_DIR)/%.h
+	$(CC) $(SCR_CFLAGS) -c $<
+
diff --git a/Drivers/Screen/TerminalEmulator/em_screen.c b/Drivers/Screen/TerminalEmulator/em_screen.c
new file mode 100644
index 0000000..d4429aa
--- /dev/null
+++ b/Drivers/Screen/TerminalEmulator/em_screen.c
@@ -0,0 +1,545 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "scr_terminal.h"
+#include "msg_queue.h"
+#include "utf8.h"
+#include "hostcmd.h"
+#include "parse.h"
+#include "file.h"
+#include "program.h"
+#include "async_handle.h"
+#include "async_io.h"
+#include "embed.h"
+
+typedef enum {
+  PARM_DIRECTORY,
+  PARM_EMULATOR,
+  PARM_GROUP,
+  PARM_HOME,
+  PARM_PATH,
+  PARM_SHELL,
+  PARM_USER,
+} ScreenParameters;
+
+#define SCRPARMS "directory", "emulator", "group", "home", "path", "shell", "user"
+#include "scr_driver.h"
+
+static char *directoryParameter = NULL;
+static char *emulatorParameter = NULL;
+static char *groupParameter = NULL;
+static char *homeParameter = NULL;
+static char *pathParameter = NULL;
+static char *shellParameter = NULL;
+static char *userParameter = NULL;
+
+static void
+setParameter (char **variable, char **parameters, ScreenParameters parameter) {
+  char *value = parameters[parameter];
+  if (value && !*value) value = NULL;
+  *variable = value;
+}
+
+static int
+processParameters_TerminalEmulatorScreen (char **parameters) {
+  setParameter(&directoryParameter, parameters, PARM_DIRECTORY);
+  setParameter(&emulatorParameter, parameters, PARM_EMULATOR);
+  setParameter(&groupParameter, parameters, PARM_GROUP);
+  setParameter(&homeParameter, parameters, PARM_HOME);
+  setParameter(&pathParameter, parameters, PARM_PATH);
+  setParameter(&shellParameter, parameters, PARM_SHELL);
+  setParameter(&userParameter, parameters, PARM_USER);
+  return 1;
+}
+
+static void
+handleException (const char *cause) {
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER), "stopping: %s", cause);
+  brlttyInterrupt(WAIT_STOP);
+}
+
+static AsyncHandle emulatorMonitorHandle = NULL;
+static FILE *emulatorStream = NULL;
+static char *emulatorStreamBuffer = NULL;
+static size_t emulatorStreamBufferSize = 0;
+
+static const char *problemText = NULL;
+static ScreenSegmentHeader *screenSegment = NULL;
+static ScreenSegmentHeader *cachedSegment = NULL;
+
+static int haveTerminalMessageQueue = 0;
+static int terminalMessageQueue;
+static int haveSegmentUpdatedHandler = 0;
+static int haveEmulatorExitingHandler = 0;
+
+static int
+sendTerminalMessage (MessageType type, const void *content, size_t length) {
+  if (!haveTerminalMessageQueue) return 0;
+  return sendMessage(terminalMessageQueue, type, content, length, 0);
+}
+
+static void
+messageHandler_segmentUpdated (const MessageHandlerParameters *parameters) {
+  mainScreenUpdated();
+}
+
+static void
+messageHandler_emulatorExiting (const MessageHandlerParameters *parameters) {
+  handleException("emulator exiting");
+}
+
+static void
+enableMessages (key_t key) {
+  haveTerminalMessageQueue = getMessageQueue(&terminalMessageQueue, key);
+
+  if (haveTerminalMessageQueue) {
+    haveSegmentUpdatedHandler = startMessageReceiver(
+      "screen-segment-updated-receiver",
+      terminalMessageQueue, TERM_MSG_SEGMENT_UPDATED,
+      0, messageHandler_segmentUpdated, NULL
+    );
+
+    haveEmulatorExitingHandler = startMessageReceiver(
+      "terminal-emulator-exiting-receiver",
+      terminalMessageQueue, TERM_MSG_EMULATOR_EXITING,
+      0, messageHandler_emulatorExiting, NULL
+    );
+  }
+}
+
+static int
+accessSegmentForPath (const char *path) {
+  key_t key;
+
+  if (makeTerminalKey(&key, path)) {
+    if ((screenSegment = getScreenSegmentForKey(key))) {
+      problemText = gettext("no screen cache");
+      enableMessages(key);
+      return 1;
+    } else {
+      problemText = gettext("screen not accessible");
+    }
+  }
+
+  return 0;
+}
+
+static void
+destruct_TerminalEmulatorScreen (void) {
+  brlttyDisableInterrupt();
+
+  if (emulatorMonitorHandle) {
+    asyncCancelRequest(emulatorMonitorHandle);
+    emulatorMonitorHandle = NULL;
+  }
+
+  if (emulatorStream) {
+    fclose(emulatorStream);
+    emulatorStream = NULL;
+  }
+
+  if (emulatorStreamBuffer) {
+    free(emulatorStreamBuffer);
+    emulatorStreamBuffer = NULL;
+  }
+
+  if (screenSegment) {
+    detachScreenSegment(screenSegment);
+    screenSegment = NULL;
+  }
+
+  if (cachedSegment) {
+    free(cachedSegment);
+    cachedSegment = NULL;
+  }
+}
+
+static void
+driverDirectiveHandler_path (const char *const *operands) {
+  const char *path = operands[0];
+
+  if (path) {
+    if (!screenSegment) {
+      accessSegmentForPath(path);
+    }
+  }
+}
+
+typedef void DriverDirectiveHandler (const char *const *operands);
+
+typedef struct {
+  const char *name;
+  DriverDirectiveHandler *handler;
+} DriverDirectiveEntry;
+
+#define DRIVER_DIRECTIVE_ENTRY(directive) { \
+  .name = #directive, \
+  .handler = driverDirectiveHandler_ ## directive, \
+}
+
+static const DriverDirectiveEntry driverDirectiveTable[] = {
+  DRIVER_DIRECTIVE_ENTRY(path),
+};
+
+static int
+handleDriverDirective (const char *line) {
+  char buffer[strlen(line) + 1];
+  strcpy(buffer, line);
+
+  const char *operandTable[9];
+  unsigned int operandCount = 0;
+
+  {
+    char *string = buffer;
+
+    while (operandCount < (ARRAY_COUNT(operandTable) - 1)) {
+      static const char delimiters[] = " ";
+      char *next = strtok(string, delimiters);
+      if (!next) break;
+
+      operandTable[operandCount++] = next;
+      string = NULL;
+    }
+
+    operandTable[operandCount] = NULL;
+  }
+
+  if (operandCount > 0) {
+    const char **operands = operandTable;
+    const char *name = *operands++;
+
+    const DriverDirectiveEntry *entry = driverDirectiveTable;
+    const DriverDirectiveEntry *end = entry + ARRAY_COUNT(driverDirectiveTable);
+
+    while (entry < end) {
+      if (strcasecmp(name, entry->name) == 0) {
+        entry->handler(operands);
+        return 1;
+      }
+
+      entry += 1;
+    }
+  }
+
+  return 0;
+}
+
+ASYNC_MONITOR_CALLBACK(emEmulatorMonitor) {
+  const char *line;
+
+  {
+    int ok = readLine(
+      emulatorStream, &emulatorStreamBuffer,
+      &emulatorStreamBufferSize, NULL
+    );
+
+    if (!ok) {
+      const char *cause =
+        ferror(emulatorStream)? "emulator stream error":
+        feof(emulatorStream)? "end of emulator stream":
+        "emulator monitor failure";
+
+      handleException(cause);
+      return 0;
+    }
+
+    line = emulatorStreamBuffer;
+  }
+
+  if (!handleDriverDirective(line)) {
+    logMessage(LOG_NOTICE, "%s", line);
+  }
+
+  return 1;
+}
+
+static char *
+makeDefaultEmulatorPath (void) {
+  typedef char *PathMaker (const char *name);
+
+  static PathMaker *pathMakers[] = {
+    makeProgramPath,
+    makeCommandPath,
+  };
+
+  PathMaker **pathMaker = pathMakers;
+  PathMaker **end = pathMaker + ARRAY_COUNT(pathMakers);
+
+  while (pathMaker < end) {
+    char *path = (*pathMaker)("brltty-pty");
+
+    if (path) {
+      if (testProgramPath(path)) {
+        logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+          "default terminal emulator: %s", path
+        );
+
+        return path;
+      }
+
+      free(path);
+    }
+
+    pathMaker += 1;
+  }
+
+  logMessage(LOG_WARNING, "default terminal emulator not found");
+  return NULL;
+}
+
+static int
+startEmulator (void) {
+  char *emulator = emulatorParameter;
+
+  if (!emulator) {
+    if (!(emulator = makeDefaultEmulatorPath())) {
+      return 0;
+    }
+  }
+
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER),
+    "terminal emulator command: %s", emulator
+  );
+
+  const char *arguments[13];
+  unsigned int argumentCount = 0;
+
+  arguments[argumentCount++] = emulator;
+  arguments[argumentCount++] = "--driver-directives";
+
+  if (userParameter) {
+    arguments[argumentCount++] = "--user";
+    arguments[argumentCount++] = userParameter;
+  }
+
+  if (groupParameter) {
+    arguments[argumentCount++] = "--group";
+    arguments[argumentCount++] = groupParameter;
+  }
+
+  if (directoryParameter) {
+    arguments[argumentCount++] = "--working-directory";
+    arguments[argumentCount++] = directoryParameter;
+  }
+
+  if (homeParameter) {
+    arguments[argumentCount++] = "--home-directory";
+    arguments[argumentCount++] = homeParameter;
+  }
+
+  arguments[argumentCount++] = "--";
+  if (shellParameter) arguments[argumentCount++] = shellParameter;
+  arguments[argumentCount++] = NULL;
+
+  HostCommandOptions options;
+  initializeHostCommandOptions(&options);
+  options.asynchronous = 1;
+  options.standardError = &emulatorStream;
+
+  int exitStatus = runHostCommand(arguments, &options);
+  if (emulator != emulatorParameter) free(emulator);
+  emulator = NULL;
+
+  if (!exitStatus) {
+    detachStandardStreams();
+
+    if (asyncMonitorFileInput(&emulatorMonitorHandle, fileno(emulatorStream), emEmulatorMonitor, NULL)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+construct_TerminalEmulatorScreen (void) {
+  brlttyEnableInterrupt();
+
+  emulatorMonitorHandle = NULL;
+  emulatorStream = NULL;
+  emulatorStreamBuffer = NULL;
+  emulatorStreamBufferSize = 0;
+
+  problemText = gettext("screen not available");
+  screenSegment = NULL;
+  cachedSegment = NULL;
+
+  haveTerminalMessageQueue = 0;
+  haveSegmentUpdatedHandler = 0;
+  haveEmulatorExitingHandler = 0;
+
+  if (pathParameter) {
+    if (accessSegmentForPath(pathParameter)) return 1;
+  } else if (startEmulator()) {
+    problemText = gettext("screen not attached");
+    return 1;
+  } else {
+    problemText = gettext("no screen emulator");
+  }
+
+  handleException("driver construction failure");
+//destruct_TerminalEmulatorScreen();
+  return 0;
+}
+
+static int
+poll_TerminalEmulatorScreen (void) {
+  return !haveSegmentUpdatedHandler;
+}
+
+static int
+refresh_TerminalEmulatorScreen (void) {
+  if (!screenSegment) return 0;
+  size_t size = screenSegment->segmentSize;
+
+  if (cachedSegment) {
+    if (cachedSegment->segmentSize != size) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER), "deallocating old screen cache");
+      free(cachedSegment);
+      cachedSegment = NULL;
+    }
+  }
+
+  if (!cachedSegment) {
+    logMessage(LOG_CATEGORY(SCREEN_DRIVER), "allocating new screen cache");
+
+    if (!(cachedSegment = malloc(size))) {
+      logMallocError();
+      return 0;
+    }
+  }
+
+  memcpy(cachedSegment, screenSegment, size);
+  return 1;
+}
+
+static ScreenSegmentHeader *
+getSegment (void) {
+  ScreenSegmentHeader *segment = cachedSegment;
+  if (!segment) segment = screenSegment;
+  return segment;
+}
+
+static int
+currentVirtualTerminal_TerminalEmulatorScreen (void) {
+  ScreenSegmentHeader *segment = getSegment();
+  return segment? segment->screenNumber: 0;
+}
+
+static void
+describe_TerminalEmulatorScreen (ScreenDescription *description) {
+  ScreenSegmentHeader *segment = getSegment();
+
+  if (!segment) {
+    description->unreadable = problemText;
+    description->cols = strlen(description->unreadable);
+    description->rows = 1;
+    description->posx = 0;
+    description->posy = 0;
+  } else {
+    description->number = currentVirtualTerminal_TerminalEmulatorScreen();
+    description->rows = segment->screenHeight;
+    description->cols = segment->screenWidth;
+    description->posy = segment->cursorRow;
+    description->posx = segment->cursorColumn;
+  }
+}
+
+static void
+setScreenAttribute (ScreenAttributes *attributes, ScreenAttributes attribute, unsigned char level, int bright) {
+  if (level >= 0X20) {
+    *attributes |= attribute;
+    if (bright && (level >= 0XD0)) *attributes |= SCR_ATTR_FG_BRIGHT;
+  }
+}
+
+static int
+readCharacters_TerminalEmulatorScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  ScreenSegmentHeader *segment = getSegment();
+
+  if (!segment) {
+    setScreenMessage(box, buffer, problemText);
+    return 1;
+  }
+
+  if (validateScreenBox(box, segment->screenWidth, segment->screenHeight)) {
+    ScreenCharacter *target = buffer;
+
+    for (unsigned int row=0; row<box->height; row+=1) {
+      const ScreenSegmentCharacter *source =
+        getScreenCharacter(segment, box->top + row, box->left, NULL);
+
+      for (unsigned int column=0; column<box->width; column+=1) {
+        target->text = source->text;
+
+        {
+          ScreenAttributes *attributes = &target->attributes;
+          *attributes = 0;
+          if (source->blink) *attributes |= SCR_ATTR_BLINK;
+
+          setScreenAttribute(attributes, SCR_ATTR_FG_RED,   source->foreground.red,   1);
+          setScreenAttribute(attributes, SCR_ATTR_FG_GREEN, source->foreground.green, 1);
+          setScreenAttribute(attributes, SCR_ATTR_FG_BLUE,  source->foreground.blue,  1);
+
+          setScreenAttribute(attributes, SCR_ATTR_BG_RED,   source->background.red,   0);
+          setScreenAttribute(attributes, SCR_ATTR_BG_GREEN, source->background.green, 0);
+          setScreenAttribute(attributes, SCR_ATTR_BG_BLUE,  source->background.blue,  0);
+        }
+
+        source += 1;
+        target += 1;
+      }
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+insertKey_TerminalEmulatorScreen (ScreenKey key) {
+  setScreenKeyModifiers(&key, 0);
+
+  wchar_t character = key & SCR_KEY_CHAR_MASK;
+  Utf8Buffer utf8;
+  size_t length = convertWcharToUtf8(character, utf8);
+
+  return sendTerminalMessage(TERM_MSG_INPUT_TEXT, utf8, length);
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.currentVirtualTerminal = currentVirtualTerminal_TerminalEmulatorScreen;
+  main->base.describe = describe_TerminalEmulatorScreen;
+  main->base.readCharacters = readCharacters_TerminalEmulatorScreen;
+  main->base.insertKey = insertKey_TerminalEmulatorScreen;
+  main->base.poll = poll_TerminalEmulatorScreen;
+  main->base.refresh = refresh_TerminalEmulatorScreen;
+
+  main->processParameters = processParameters_TerminalEmulatorScreen;
+  main->construct = construct_TerminalEmulatorScreen;
+  main->destruct = destruct_TerminalEmulatorScreen;
+}
diff --git a/Drivers/Screen/Windows/Makefile.in b/Drivers/Screen/Windows/Makefile.in
new file mode 100644
index 0000000..0501c70
--- /dev/null
+++ b/Drivers/Screen/Windows/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = wn
+DRIVER_NAME = Windows
+DRIVER_USAGE = Microsoft Windows
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)screen.mk
+
+screen.$O:
+	$(CC) $(SCR_CFLAGS) -c $(SRC_DIR)/screen.c
+
diff --git a/Drivers/Screen/Windows/screen.c b/Drivers/Screen/Windows/screen.c
new file mode 100644
index 0000000..8a9f750
--- /dev/null
+++ b/Drivers/Screen/Windows/screen.c
@@ -0,0 +1,568 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "parse.h"
+#include "brl_cmds.h"
+#include "system_windows.h"
+#include "kbd_keycodes.h"
+#include "unicode.h"
+#include "ascii.h"
+
+typedef enum {
+  PARM_ROOT,
+  PARM_FOLLOWFOCUS
+} ScreenParameters;
+#define SCRPARMS "root", "followfocus"
+static unsigned int root, followFocus;
+
+#include "scr_driver.h"
+
+static HANDLE consoleOutput = INVALID_HANDLE_VALUE;
+static HANDLE consoleInput = INVALID_HANDLE_VALUE;
+
+static int
+processParameters_WindowsScreen (char **parameters) {
+  root = 0;
+  if (*parameters[PARM_ROOT])
+    if (!validateYesNo(&root, parameters[PARM_ROOT]))
+      logMessage(LOG_WARNING, "%s: %s", "invalid root setting", parameters[PARM_ROOT]);
+  if (root && AttachConsoleProc)
+    logMessage(LOG_WARNING, "No need for root BRLTTY on newer (XP or later) systems");
+
+  followFocus = 1;
+  if (*parameters[PARM_FOLLOWFOCUS])
+    if (!validateYesNo(&followFocus, parameters[PARM_FOLLOWFOCUS]))
+      logMessage(LOG_WARNING, "%s: %s", "invalid follow focus setting", parameters[PARM_FOLLOWFOCUS]);
+  if (followFocus && !AttachConsoleProc)
+    logMessage(LOG_WARNING, "Cannot follow focus on older (pre-XP) systems");
+  return 1;
+}
+
+static int
+openStdHandles (void) {
+  if ((consoleOutput == INVALID_HANDLE_VALUE &&
+    (consoleOutput = CreateFile("CONOUT$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL)) == INVALID_HANDLE_VALUE)
+    ||(consoleInput == INVALID_HANDLE_VALUE &&
+    (consoleInput = CreateFile("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL)) == INVALID_HANDLE_VALUE)) {
+    logWindowsSystemError("GetStdHandle");
+    return 0;
+  }
+  return 1;
+}
+
+static void
+closeStdHandles (void) {
+  CloseHandle(consoleInput);
+  consoleInput = INVALID_HANDLE_VALUE;
+  CloseHandle(consoleOutput);
+  consoleOutput = INVALID_HANDLE_VALUE;
+}
+
+static int
+tryToAttach (HWND win) {
+#define CONSOLEWINDOW "ConsoleWindowClass"
+  static char class[] = CONSOLEWINDOW;
+  DWORD process;
+  if (GetClassName(win, class, sizeof(class)) != strlen(CONSOLEWINDOW)
+      || memcmp(class,CONSOLEWINDOW,strlen(CONSOLEWINDOW)))
+    return 0;
+  if (!GetWindowThreadProcessId(win, &process))
+    return 0;
+  FreeConsole();
+  if (!AttachConsoleProc(process))
+    return 0;
+  closeStdHandles();
+  return openStdHandles();
+}
+
+static int
+construct_WindowsScreen (void) {
+  if (followFocus && (AttachConsoleProc || root)) {
+    /* disable ^C */
+    SetConsoleCtrlHandler(NULL,TRUE);
+    if (!FreeConsole() && GetLastError() != ERROR_INVALID_PARAMETER)
+      logWindowsSystemError("FreeConsole");
+    return 1;
+  }
+  return openStdHandles();
+}
+
+static void
+destruct_WindowsScreen (void) {
+  closeStdHandles();
+}
+
+static ALTTABINFO altTabInfo;
+static HWND altTab;
+static char altTabName[128];
+static BOOL CALLBACK
+findAltTab (HWND win, LPARAM lparam) {
+  if (GetAltTabInfoAProc(win, -1, &altTabInfo, NULL, 0)) {
+    altTab = win;
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static CONSOLE_SCREEN_BUFFER_INFO info;
+static const char *unreadable;
+static int cols;
+static int rows;
+
+static int
+currentVirtualTerminal_WindowsScreen (void) {
+  HWND win;
+
+  unreadable = NULL;
+  altTab = NULL;
+
+  if (followFocus && (AttachConsoleProc || root) && GetAltTabInfoAProc) {
+    altTabInfo.cbSize = sizeof(altTabInfo);
+    EnumWindows(findAltTab, 0);
+
+    if (altTab) {
+      if (!(GetAltTabInfoAProc(altTab,
+	      altTabInfo.iColFocus + altTabInfo.iRowFocus * altTabInfo.cColumns,
+	      &altTabInfo, altTabName, sizeof(altTabName)))) {
+	altTab = NULL;
+      } else {
+	return 0;
+      }
+    }
+  }
+
+  if (!(win = GetForegroundWindow())) win = INVALID_HANDLE_VALUE;
+
+  if (!AttachConsoleProc && root) {
+    unreadable = "root BRLTTY";
+  } else if (win == INVALID_HANDLE_VALUE) {
+    unreadable = "no foreground window";
+  } else if (followFocus && AttachConsoleProc && !tryToAttach(win)) {
+    unreadable = "no attachable console";
+  } else if (consoleOutput == INVALID_HANDLE_VALUE) {
+    unreadable = "can't open console output";
+  } else if (!(GetConsoleScreenBufferInfo(consoleOutput, &info))) {
+    logWindowsSystemError("GetConsoleScreenBufferInfo");
+    unreadable = "can't read console information";
+
+    CloseHandle(consoleOutput);
+    consoleOutput = INVALID_HANDLE_VALUE;
+  }
+
+  return (LONG_PTR)win;
+}
+
+static void
+describe_WindowsScreen (ScreenDescription *description) {
+  description->number = (int) currentVirtualTerminal_WindowsScreen();
+  description->unreadable = unreadable;
+  if (unreadable) {
+    description->rows = 1;
+    description->cols = strlen(unreadable);
+    description->posx = 0;
+    description->posy = 0;
+    description->hasCursor = 0;
+  } else if (altTab) {
+    description->rows = 1;
+    description->cols = strlen(altTabName);
+    description->posx = 0;
+    description->posy = 0;
+    description->hasCursor = 0;
+  } else {
+    description->cols = info.srWindow.Right + 1 - info.srWindow.Left;
+    description->rows = info.srWindow.Bottom + 1 - info.srWindow.Top;
+    description->posx = info.dwCursorPosition.X - info.srWindow.Left;
+    description->posy = info.dwCursorPosition.Y - info.srWindow.Top; 
+    description->hasCursor = 1;
+  }
+  cols = description->cols;
+  rows = description->rows;
+}
+
+static int
+readCharacters_WindowsScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  int x, xx, y;
+  static int wide;
+  COORD coord;
+
+  BOOL WINAPI (*fun) (HANDLE, void*, DWORD, COORD, LPDWORD);
+  const char *name;
+  size_t size;
+  void *buf;
+  WORD *bufAttr;
+
+  if (!validateScreenBox(box, cols, rows)) return 0;
+
+  if (unreadable) {
+    setScreenMessage(box, buffer, unreadable);
+    return 1;
+  }
+
+  if (altTab) {
+    setScreenMessage(box, buffer, altTabName);
+    return 1;
+  }
+
+  coord.X = box->left + info.srWindow.Left;
+  coord.Y = box->top + info.srWindow.Top;
+
+  if (!wide) {
+    wchar_t buf;
+    DWORD read;
+    if (ReadConsoleOutputCharacterW(consoleOutput, &buf, 1, coord, &read))
+      wide = 1;
+    else {
+      if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+	wide = -1;
+      else {
+	logWindowsSystemError("ReadConsoleOutputCharacterW");
+	return 0;
+      }
+    }
+  }
+#define USE(f, t) (fun = (typeof(fun))f, name = #f, size = sizeof(t))
+  if (wide > 0)
+    USE(ReadConsoleOutputCharacterW, wchar_t);
+  else
+    USE(ReadConsoleOutputCharacterA, char);
+#undef USE
+
+  if (!(buf = malloc(box->width*size))) {
+    logSystemError("malloc for Windows console reading");
+    return 0;
+  }
+
+  if (!(bufAttr = malloc(box->width*sizeof(WORD)))) {
+    logSystemError("malloc for Windows console reading");
+    free(buf);
+    return 0;
+  }
+
+  for (y=0; y<box->height; y++, coord.Y++) {
+    DWORD read;
+
+    if (!fun(consoleOutput, buf, box->width, coord, &read)) {
+      logWindowsSystemError(name);
+      break;
+    }
+
+    if (wide > 0) {
+      for (x=0, xx = 0; x<box->width && xx < read; x++, xx++) {
+	wchar_t wc = ((wchar_t*)buf)[xx];
+	int i;
+
+	buffer[y*box->width+x].text = wc;
+	for (i = 1; i < getCharacterWidth(wc); i++) {
+	  x++;
+	  buffer[y*box->width+x].text = UNICODE_ZERO_WIDTH_SPACE;
+	}
+      }
+      for ( ; x<box->width; x++) {
+	buffer[y*box->width+x].text = L' ';
+      }
+    } else {
+      for (x=0; x<read; x++) {
+	/* TODO: GetConsoleCP and convert */
+	buffer[y*box->width+x].text = ((char*)buf)[x];
+      }
+      for ( ; x<box->width; x++) {
+	buffer[y*box->width+x].text = ' ';
+      }
+    }
+
+    if (!ReadConsoleOutputAttribute(consoleOutput, bufAttr, box->width, coord, &read)) {
+      logWindowsSystemError(name);
+      break;
+    }
+
+    for (x=0, xx=0; x<box->width && xx < read; x++, xx++) {
+      int i;
+      buffer[y*box->width+x].attributes = bufAttr[xx];
+      for (i = 1; i < getCharacterWidth(buffer[y*box->width+x].text); i++) {
+	x++;
+        buffer[y*box->width+x].attributes = bufAttr[xx];
+      }
+    }
+    for ( ; x < box->width; x++)
+      buffer[y*box->width+x].attributes = 0;
+  }
+
+  free(buf);
+  free(bufAttr);
+
+  return (y == box->height);
+}
+
+static int 
+doInsertWriteConsoleInput (BOOL down, WCHAR wchar, WORD vk, WORD scancode, DWORD controlKeyState) {
+  DWORD num;
+  INPUT_RECORD buf;
+  KEY_EVENT_RECORD *keyE = &buf.Event.KeyEvent;
+
+  buf.EventType = KEY_EVENT;
+  memset(keyE, 0, sizeof(*keyE));
+  keyE->bKeyDown = down;
+  keyE->wRepeatCount = 1;
+  keyE->wVirtualKeyCode = vk;
+  keyE->wVirtualScanCode = scancode;
+  keyE->uChar.UnicodeChar = wchar;
+  keyE->dwControlKeyState = controlKeyState;
+  if (WriteConsoleInputW(consoleInput, &buf, 1, &num)) {
+    if (num == 1) {
+      return 1;
+    } else {
+      logMessage(LOG_ERR, "inserted %d keys, expected 1", (int)num);
+    }
+  } else {
+    if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+      keyE->uChar.AsciiChar = wchar;
+      if (WriteConsoleInputA(consoleInput, &buf, 1, &num)) {
+	if (num == 1) {
+	  return 1;
+	} else {
+	  logMessage(LOG_ERR, "inserted %d keys, expected 1", (int)num);
+	}
+      }
+    }
+    logWindowsSystemError("WriteConsoleInput");
+    CloseHandle(consoleInput);
+    consoleInput = INVALID_HANDLE_VALUE;
+  }
+  return 0;
+}
+
+static int 
+doInsertSendInput (BOOL down, WCHAR wchar, WORD vk, WORD scancode, DWORD flags) {
+  if (SendInputProc) {
+    UINT num;
+    INPUT input;
+    KEYBDINPUT *ki = &input.ki;
+
+    input.type = INPUT_KEYBOARD;
+    memset(ki, 0, sizeof(*ki));
+    ki->dwFlags = flags;
+    if (wchar) {
+      ki->wVk = 0;
+      ki->wScan = wchar;
+      ki->dwFlags |= KEYEVENTF_UNICODE;
+    } else if (vk) {
+      ki->wVk = vk;
+      ki->wScan = scancode;
+    } else {
+      ki->wVk = 0;
+      ki->wScan = scancode;
+      ki->dwFlags |= KEYEVENTF_SCANCODE;
+    }
+
+    if (!down)
+      ki->dwFlags |= KEYEVENTF_KEYUP;
+
+    num = SendInput(1, &input, sizeof(INPUT));
+    switch (num) {
+      case 1:  return 1;
+      case 0:  logWindowsSystemError("SendInput"); break;
+      default: logMessage(LOG_ERR, "inserted %d keys, expected 1", num); break;
+    }
+    return 0;
+  } else {
+    keybd_event(vk, scancode, flags | (down ? 0 : KEYEVENTF_KEYUP), 0);
+    return 1;
+  }
+}
+
+static int
+insertKey_WindowsScreen (ScreenKey key) {
+  SHORT vk = 0;
+  SHORT scancode = 0;
+  DWORD controlKeyState = 0;
+  WCHAR wchar = 0;
+
+  mapScreenKey(&key);
+
+  logMessage(LOG_DEBUG, "Insert key: %4.4X",key);
+  if (isSpecialKey(key)) {
+    switch (key & SCR_KEY_CHAR_MASK) {
+      case SCR_KEY_ENTER:         vk = VK_RETURN; wchar='\r'; break;
+      case SCR_KEY_TAB:           vk = VK_TAB;    wchar='\t'; break;
+      case SCR_KEY_BACKSPACE:     vk = VK_BACK;   wchar='\b'; break;
+      case SCR_KEY_ESCAPE:        vk = VK_ESCAPE; wchar=ASCII_ESC; break;
+      case SCR_KEY_CURSOR_LEFT:   vk = VK_LEFT;   break;
+      case SCR_KEY_CURSOR_RIGHT:  vk = VK_RIGHT;  break;
+      case SCR_KEY_CURSOR_UP:     vk = VK_UP;     break;
+      case SCR_KEY_CURSOR_DOWN:   vk = VK_DOWN;   break;
+      case SCR_KEY_PAGE_UP:       vk = VK_PRIOR;  break;
+      case SCR_KEY_PAGE_DOWN:     vk = VK_NEXT;   break;
+      case SCR_KEY_HOME:          vk = VK_HOME;   break;
+      case SCR_KEY_END:           vk = VK_END;    break;
+      case SCR_KEY_INSERT:        vk = VK_INSERT; break;
+      case SCR_KEY_DELETE:        vk = VK_DELETE; break;
+      case SCR_KEY_FUNCTION + 0:  vk = VK_F1;     break;
+      case SCR_KEY_FUNCTION + 1:  vk = VK_F2;     break;
+      case SCR_KEY_FUNCTION + 2:  vk = VK_F3;     break;
+      case SCR_KEY_FUNCTION + 3:  vk = VK_F4;     break;
+      case SCR_KEY_FUNCTION + 4:  vk = VK_F5;     break;
+      case SCR_KEY_FUNCTION + 5:  vk = VK_F6;     break;
+      case SCR_KEY_FUNCTION + 6:  vk = VK_F7;     break;
+      case SCR_KEY_FUNCTION + 7:  vk = VK_F8;     break;
+      case SCR_KEY_FUNCTION + 8:  vk = VK_F9;     break;
+      case SCR_KEY_FUNCTION + 9:  vk = VK_F10;    break;
+      case SCR_KEY_FUNCTION + 10: vk = VK_F11;    break;
+      case SCR_KEY_FUNCTION + 11: vk = VK_F12;    break;
+      case SCR_KEY_FUNCTION + 12: vk = VK_F13;    break;
+      case SCR_KEY_FUNCTION + 13: vk = VK_F14;    break;
+      case SCR_KEY_FUNCTION + 14: vk = VK_F15;    break;
+      case SCR_KEY_FUNCTION + 15: vk = VK_F16;    break;
+      case SCR_KEY_FUNCTION + 16: vk = VK_F17;    break;
+      case SCR_KEY_FUNCTION + 17: vk = VK_F18;    break;
+      case SCR_KEY_FUNCTION + 18: vk = VK_F19;    break;
+      case SCR_KEY_FUNCTION + 19: vk = VK_F20;    break;
+      case SCR_KEY_FUNCTION + 20: vk = VK_F21;    break;
+      case SCR_KEY_FUNCTION + 21: vk = VK_F22;    break;
+      case SCR_KEY_FUNCTION + 22: vk = VK_F23;    break;
+      case SCR_KEY_FUNCTION + 23: vk = VK_F24;    break;
+      default: logMessage(LOG_WARNING, "Key %4.4X not suported.", key);
+               return 0;
+    }
+  } else {
+    if (key & SCR_KEY_ALT_LEFT) {
+      controlKeyState |= LEFT_ALT_PRESSED;
+      key &= ~ SCR_KEY_ALT_LEFT;
+    }
+    if (key & SCR_KEY_ALT_RIGHT) {
+      controlKeyState |= RIGHT_ALT_PRESSED;
+      key &= ~ SCR_KEY_ALT_RIGHT;
+    }
+    if (key & SCR_KEY_SHIFT) {
+      controlKeyState |= SHIFT_PRESSED;
+      key &= ~ SCR_KEY_SHIFT;
+    }
+    if (key & SCR_KEY_CONTROL) {
+      controlKeyState |= LEFT_CTRL_PRESSED;
+      key &= ~ SCR_KEY_CONTROL;
+    }
+    wchar = key & SCR_KEY_CHAR_MASK;
+    vk = VkKeyScanW(wchar);
+    if (vk == -1 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+      vk = VkKeyScan(wchar);
+    if (vk != -1) {
+      logMessage(LOG_DEBUG, "vk is %4.4X", vk);
+      if (vk & 0x100) controlKeyState |= SHIFT_PRESSED;
+      if ((vk & 0x600) == 0x600) {
+	controlKeyState |= RIGHT_CTRL_PRESSED;
+	controlKeyState |= RIGHT_ALT_PRESSED;
+      } else {
+        if (vk & 0x200) controlKeyState |= LEFT_CTRL_PRESSED;
+        if (vk & 0x400) controlKeyState |= LEFT_ALT_PRESSED;
+      }
+      vk = vk & 0xff;
+    } else vk = 0;
+  }
+
+  scancode = MapVirtualKey(vk, 0);
+
+  logMessage(LOG_DEBUG,"wchar %x vk %x scancode %d ks %d", wchar, vk, scancode, (int)controlKeyState);
+  if (consoleInput != INVALID_HANDLE_VALUE && !unreadable) {
+    logMessage(LOG_DEBUG, "using WriteConsoleInput");
+    if (!doInsertWriteConsoleInput(TRUE, wchar, vk, scancode, controlKeyState))
+      return 0;
+    if (!doInsertWriteConsoleInput(FALSE, wchar, vk, scancode, controlKeyState))
+      return 0;
+    return 1;
+  } else {
+    logMessage(LOG_DEBUG, "using SendInput");
+    if (controlKeyState & LEFT_CTRL_PRESSED)
+      if (!doInsertSendInput(TRUE, 0, VK_CONTROL, 0, 0))
+        return 0;
+    if (controlKeyState & SHIFT_PRESSED)
+      if (!doInsertSendInput(TRUE, 0, VK_SHIFT, 0, 0))
+        return 0;
+    if (controlKeyState & LEFT_ALT_PRESSED)
+      if (!doInsertSendInput(TRUE, 0, VK_MENU, 0, 0))
+        return 0;
+    if (controlKeyState & RIGHT_ALT_PRESSED)
+      if (!doInsertSendInput(TRUE, 0, VK_RMENU, 0, 0))
+        return 0;
+    if (!doInsertSendInput(TRUE, wchar, vk, scancode, 0))
+      return 0;
+    if (!doInsertSendInput(FALSE, wchar, vk, scancode, 0))
+      return 0;
+    if (controlKeyState & RIGHT_ALT_PRESSED)
+      if (!doInsertSendInput(FALSE, 0, VK_RMENU, 0, 0))
+        return 0;
+    if (controlKeyState & LEFT_ALT_PRESSED)
+      if (!doInsertSendInput(FALSE, 0, VK_MENU, 0, 0))
+        return 0;
+    if (controlKeyState & SHIFT_PRESSED)
+      if (!doInsertSendInput(FALSE, 0, VK_SHIFT, 0, 0))
+        return 0;
+    if (controlKeyState & LEFT_CTRL_PRESSED)
+      if (!doInsertSendInput(FALSE, 0, VK_CONTROL, 0, 0))
+        return 0;
+    return 1;
+  }
+  return 0;
+}
+
+static int
+handleCommand_WindowsScreen (int command) {
+  int blk = command & BRL_MSK_BLK;
+  int arg = command & BRL_MSK_ARG;
+  int press = 0;
+
+  switch (blk) {
+    case BRL_CMD_BLK(PASSXT):
+      press = !(arg & 0X80);
+      arg &= 0X7F;
+      /* fallthrough */
+      goto do_send;
+    case BRL_CMD_BLK(PASSAT): {
+      press |= !(command & BRL_FLG_KBD_RELEASE);
+
+do_send:
+      if (arg >= 0X80)
+	return 0;
+
+      if (command & BRL_FLG_KBD_EMUL1)
+	return 0;
+
+      if (blk == BRL_CMD_BLK(PASSAT))
+	arg = AT2XT[arg];
+
+      return doInsertSendInput (press, 0, 0, arg,
+	  command & BRL_FLG_KBD_EMUL0 ? KEYEVENTF_EXTENDEDKEY : 0);
+    }
+  }
+
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeRealScreen(main);
+  main->base.currentVirtualTerminal = currentVirtualTerminal_WindowsScreen;
+  main->base.describe = describe_WindowsScreen;
+  main->base.readCharacters = readCharacters_WindowsScreen;
+  main->base.insertKey = insertKey_WindowsScreen;
+  main->base.handleCommand = handleCommand_WindowsScreen;
+  main->processParameters = processParameters_WindowsScreen;
+  main->construct = construct_WindowsScreen;
+  main->destruct = destruct_WindowsScreen;
+}
diff --git a/Drivers/Speech/Alva/Makefile.in b/Drivers/Speech/Alva/Makefile.in
new file mode 100644
index 0000000..a66cdd3
--- /dev/null
+++ b/Drivers/Speech/Alva/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = al
+DRIVER_NAME = Alva
+DRIVER_USAGE = braille device
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Alva/speech.c b/Drivers/Speech/Alva/speech.c
new file mode 100644
index 0000000..82ea391
--- /dev/null
+++ b/Drivers/Speech/Alva/speech.c
@@ -0,0 +1,114 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Alva/speech.c - Speech library
+ * For the Alva Delphi.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "spk_driver.h"
+#include "speech.h"		/* for speech definitions */
+#include "Drivers/Braille/Alva/braille.h"
+
+/* charset conversion table from iso latin-1 == iso 8859-1 to cp437==ibmpc
+ * for chars >=128. 
+ */
+static unsigned char latin2cp437[0X80] =
+  {199, 252, 233, 226, 228, 224, 229, 231,
+   234, 235, 232, 239, 238, 236, 196, 197,
+   201, 181, 198, 244, 247, 242, 251, 249,
+   223, 214, 220, 243, 183, 209, 158, 159,
+   255, 173, 155, 156, 177, 157, 188, 21,
+   191, 169, 166, 174, 170, 237, 189, 187,
+   248, 241, 253, 179, 180, 230, 20, 250,
+   184, 185, 167, 175, 172, 171, 190, 168,
+   192, 193, 194, 195, 142, 143, 146, 128,
+   200, 144, 202, 203, 204, 205, 206, 207,
+   208, 165, 210, 211, 212, 213, 153, 215,
+   216, 217, 218, 219, 154, 221, 222, 225,
+   133, 160, 131, 227, 132, 134, 145, 135,
+   138, 130, 136, 137, 141, 161, 140, 139,
+   240, 164, 149, 162, 147, 245, 148, 246,
+   176, 151, 163, 150, 129, 178, 254, 152};
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters)
+{
+  return 1;
+}
+
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t len, size_t count, const unsigned char *attributes)
+{
+  static unsigned char *pre_speech = (unsigned char *)PRE_SPEECH;
+  static unsigned char *post_speech = (unsigned char *)POST_SPEECH;
+  unsigned char buf[256];
+  unsigned char c;
+  int i;
+
+  if (pre_speech[0])
+    {
+      memcpy (buf, pre_speech + 1, pre_speech[0]);
+      AL_writeData (buf, pre_speech[0]);
+    }
+  for (i = 0; i < len; i++)
+    {
+      c = buffer[i];
+      if (c >= 0X80) c = latin2cp437[c-0X80];
+      if (c < 33)	/* space or control character */
+	{
+	  buf[0] = ' ';
+	  AL_writeData (buf, 1);
+	}
+      else if (c > MAX_TRANS)
+	AL_writeData (&c, 1);
+      else
+	{
+	  memcpy (buf, vocab[c - 33], strlen (vocab[c - 33]));
+	  AL_writeData (buf, strlen (vocab[c - 33]));
+	}
+    }
+  if (post_speech[0])
+    {
+      memcpy (buf, post_speech + 1, post_speech[0]);
+      AL_writeData (buf, post_speech[0]);
+    }
+}
+
+
+static void
+spk_mute (SpeechSynthesizer *spk)
+{
+  static unsigned char *mute_seq = (unsigned char *)MUTE_SEQ;
+  unsigned char buffer[32];
+
+  memcpy (buffer, mute_seq + 1, mute_seq[0]);
+  AL_writeData (buffer, mute_seq[0]);
+}
+
+
+static void
+spk_destruct (SpeechSynthesizer *spk)
+{
+}
diff --git a/Drivers/Speech/Alva/speech.h b/Drivers/Speech/Alva/speech.h
new file mode 100644
index 0000000..ade9651
--- /dev/null
+++ b/Drivers/Speech/Alva/speech.h
@@ -0,0 +1,132 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Alva/speech.h - definitions for rudimentary speech support
+ */
+
+/* These sequences are sent to the Delphi before and after the
+ * speech data itself.  The first byte is the length, so embedded nuls are
+ * allowed.
+ */
+#define PRE_SPEECH "\002\033S"
+#define POST_SPEECH "\003\r\r\r"
+
+/* This is sent to mute the speech.  The format is the same as above. */
+#define MUTE_SEQ "\003\033S\030"
+
+/* The speech data itself is translated character by character.  If a
+ * character is less than 33 (i.e. space or control), it is replaced by a
+ * space.  If the character is more than MAX_TRANS, it is passed through
+ * as is.  Otherwise the character n is replaced by the string vocab[n - 33].
+ */
+
+#define MAX_TRANS 126
+static const char *vocab[MAX_TRANS - 32] = {" exclamation ",
+                                            " double quote ",
+                                            " hash ",
+                                            " dollar ",
+                                            " percent ",
+                                            " ampersand ",
+                                            " quote ",
+                                            " left paren ",
+                                            " right paren ",
+                                            " star ",
+                                            " plus ",
+                                            " comma ",
+                                            " dash ",
+                                            " dot ",
+                                            " slash ",
+                                            "0",
+                                            "1",
+                                            "2",
+                                            "3",
+                                            "4",
+                                            "5",
+                                            "6",
+                                            "7",
+                                            "8",
+                                            "9",
+                                            " colon ",
+                                            " semicolon ",
+                                            " less than ",
+                                            " equals ",
+                                            " greater than ",
+                                            " question ",
+                                            " at ",
+                                            "A",
+                                            "B",
+                                            "C",
+                                            "D",
+                                            "E",
+                                            "F",
+                                            "G",
+                                            "H",
+                                            "I",
+                                            "J",
+                                            "K",
+                                            "L",
+                                            "M",
+                                            "N",
+                                            "O",
+                                            "P",
+                                            "Q",
+                                            "R",
+                                            "S",
+                                            "T",
+                                            "U",
+                                            "V",
+                                            "W",
+                                            "X",
+                                            "Y",
+                                            "Z",
+                                            " left bracket ",
+                                            " backslash ",
+                                            " right bracket ",
+                                            " uparrow ",
+                                            " underline ",
+                                            " accent ",
+                                            "a",
+                                            "b",
+                                            "c",
+                                            "d",
+                                            "e",
+                                            "f",
+                                            "g",
+                                            "h",
+                                            "i",
+                                            "j",
+                                            "k",
+                                            "l",
+                                            "m",
+                                            "n",
+                                            "o",
+                                            "p",
+                                            "q",
+                                            "r",
+                                            "s",
+                                            "t",
+                                            "u",
+                                            "v",
+                                            "w",
+                                            "x",
+                                            "y",
+                                            "z",
+                                            " left brace ",
+                                            " bar ",
+                                            " right brace ",
+                                            " tilde "};
diff --git a/Drivers/Speech/Android/Makefile.in b/Drivers/Speech/Android/Makefile.in
new file mode 100644
index 0000000..739491e
--- /dev/null
+++ b/Drivers/Speech/Android/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = an
+DRIVER_NAME = Android
+DRIVER_USAGE = OS-specific
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = Dave Mielke <dave@mielke.cc>
+SPK_OBJS = @speech_libraries_ad@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Android/speech.c b/Drivers/Speech/Android/speech.c
new file mode 100644
index 0000000..09a2e8f
--- /dev/null
+++ b/Drivers/Speech/Android/speech.c
@@ -0,0 +1,226 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+
+#include "spk_driver.h"
+#include "system_java.h"
+
+struct SpeechDataStruct {
+  JNIEnv *env;
+  jclass driverClass;
+  jmethodID startMethod;
+  jmethodID stopMethod;
+  jmethodID sayMethod;
+  jmethodID muteMethod;
+  jmethodID setVolumeMethod;
+  jmethodID setRateMethod;
+  jmethodID setPitchMethod;
+};
+
+static int
+findDriverClass (SpeechSynthesizer *spk) {
+  return findJavaClass(
+    spk->driver.data->env, &spk->driver.data->driverClass,
+    JAVA_OBJ_BRLTTY("speech/SpeechDriver")
+  );
+}
+
+static void
+releaseDriverClass (SpeechSynthesizer *spk) {
+  jclass class = spk->driver.data->driverClass;
+
+  if (class) {
+    JNIEnv *env = spk->driver.data->env;
+
+    (*env)->DeleteGlobalRef(env, class);
+  }
+}
+
+static int
+findDriverMethod (SpeechSynthesizer *spk, jmethodID *method, const char *name, const char *signature) {
+  if (findDriverClass(spk)) {
+    if (findJavaStaticMethod(spk->driver.data->env, method, spk->driver.data->driverClass, name, signature)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_android_speech_SpeechDriver, tellLocation, void,
+  jlong synthesizer, jint location
+) {
+  tellSpeechLocation(javaPtrFromLong(synthesizer), location);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_android_speech_SpeechDriver, tellFinished, void,
+  jlong synthesizer
+) {
+  tellSpeechFinished(javaPtrFromLong(synthesizer));
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  if (findDriverMethod(spk, &spk->driver.data->sayMethod, "sayText",
+                       JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                       JAVA_SIG_LONG // synthesizer
+                                       JAVA_SIG_CHAR_SEQUENCE // text
+                                      ))) {
+    jstring string = (*spk->driver.data->env)->NewStringUTF(spk->driver.data->env, (const char *)buffer);
+    if (string) {
+      jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(
+        spk->driver.data->env, spk->driver.data->driverClass,
+        spk->driver.data->sayMethod, javaPtrToLong(spk), string
+      );
+
+      (*spk->driver.data->env)->DeleteLocalRef(spk->driver.data->env, string);
+      string = NULL;
+
+      if (!clearJavaException(spk->driver.data->env, 1)) {
+        if (result == JNI_TRUE) {
+        }
+      }
+    } else {
+      logMallocError();
+      clearJavaException(spk->driver.data->env, 0);
+    }
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  if (findDriverMethod(spk, &spk->driver.data->muteMethod, "stopSpeaking",
+                       JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                      ))) {
+    jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->muteMethod);
+
+    if (!clearJavaException(spk->driver.data->env, 1)) {
+      if (result == JNI_TRUE) {
+      }
+    }
+  }
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  if (findDriverMethod(spk, &spk->driver.data->setVolumeMethod, "setVolume",
+                       JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                       JAVA_SIG_FLOAT // volume
+                                      ))) {
+    jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->setVolumeMethod, getFloatSpeechVolume(setting));
+
+    if (!clearJavaException(spk->driver.data->env, 1)) {
+      if (result == JNI_TRUE) {
+      }
+    }
+  }
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  if (findDriverMethod(spk, &spk->driver.data->setRateMethod, "setRate",
+                       JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                       JAVA_SIG_FLOAT // rate
+                                      ))) {
+    jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->setRateMethod, getFloatSpeechRate(setting));
+
+    if (!clearJavaException(spk->driver.data->env, 1)) {
+      if (result == JNI_TRUE) {
+      }
+    }
+  }
+}
+
+static void
+spk_setPitch (SpeechSynthesizer *spk, unsigned char setting) {
+  if (findDriverMethod(spk, &spk->driver.data->setPitchMethod, "setPitch",
+                       JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                       JAVA_SIG_FLOAT // pitch
+                                      ))) {
+    jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->setPitchMethod, getFloatSpeechPitch(setting));
+
+    if (!clearJavaException(spk->driver.data->env, 1)) {
+      if (result == JNI_TRUE) {
+      }
+    }
+  }
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  if ((spk->driver.data = malloc(sizeof(*spk->driver.data)))) {
+    memset(spk->driver.data, 0, sizeof(*spk->driver.data));
+
+    spk->driver.data->env = getJavaNativeInterface();
+    spk->driver.data->driverClass = NULL;
+    spk->driver.data->startMethod = 0;
+    spk->driver.data->stopMethod = 0;
+    spk->driver.data->sayMethod = 0;
+    spk->driver.data->muteMethod = 0;
+    spk->driver.data->setVolumeMethod = 0;
+    spk->driver.data->setRateMethod = 0;
+    spk->driver.data->setPitchMethod = 0;
+
+    spk->setVolume = spk_setVolume;
+    spk->setRate = spk_setRate;
+    spk->setPitch = spk_setPitch;
+
+    if (spk->driver.data->env) {
+      if (findDriverMethod(spk, &spk->driver.data->startMethod, "startEngine",
+                           JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                          ))) {
+        jboolean result = (*spk->driver.data->env)->CallStaticBooleanMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->startMethod);
+
+        if (!clearJavaException(spk->driver.data->env, 1)) {
+          if (result == JNI_TRUE) {
+            return 1;
+          }
+        }
+      }
+    }
+
+    releaseDriverClass(spk);
+    free(spk->driver.data);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  if (findDriverMethod(spk, &spk->driver.data->stopMethod, "stopEngine",
+                       JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                      ))) {
+    (*spk->driver.data->env)->CallStaticVoidMethod(spk->driver.data->env, spk->driver.data->driverClass, spk->driver.data->stopMethod);
+    clearJavaException(spk->driver.data->env, 1);
+  }
+
+  releaseDriverClass(spk);
+  free(spk->driver.data);
+  spk->driver.data = NULL;
+}
diff --git a/Drivers/Speech/BrailleLite/Makefile.in b/Drivers/Speech/BrailleLite/Makefile.in
new file mode 100644
index 0000000..862be48
--- /dev/null
+++ b/Drivers/Speech/BrailleLite/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = bl
+DRIVER_NAME = BrailleLite
+DRIVER_USAGE = braille device
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/BrailleLite/speech.c b/Drivers/Speech/BrailleLite/speech.c
new file mode 100644
index 0000000..09d65b4
--- /dev/null
+++ b/Drivers/Speech/BrailleLite/speech.c
@@ -0,0 +1,125 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleLite/speech.c - Speech library
+ * For Blazie Engineering's Braille Lite 18/40
+ * Maintained by Nikhil Nair <nn201@cus.cam.ac.uk>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "spk_driver.h"
+#include "speech.h"		/* for BLite speech definitions */
+#include "Drivers/Braille/BrailleLite/braille.h"		/* for BLite speech definitions */
+
+
+#if 0
+/* charset conversion table from iso latin-1 == iso 8859-1 to cp437==ibmpc
+ * for chars >=128. 
+ */
+static unsigned char latin2cp437[0X80] =
+  {199, 252, 233, 226, 228, 224, 229, 231,
+   234, 235, 232, 239, 238, 236, 196, 197,
+   201, 181, 198, 244, 247, 242, 251, 249,
+   223, 214, 220, 243, 183, 209, 158, 159,
+   255, 173, 155, 156, 177, 157, 188, 21,
+   191, 169, 166, 174, 170, 237, 189, 187,
+   248, 241, 253, 179, 180, 230, 20, 250,
+   184, 185, 167, 175, 172, 171, 190, 168,
+   192, 193, 194, 195, 142, 143, 146, 128,
+   200, 144, 202, 203, 204, 205, 206, 207,
+   208, 165, 210, 211, 212, 213, 153, 215,
+   216, 217, 218, 219, 154, 221, 222, 225,
+   133, 160, 131, 227, 132, 134, 145, 135,
+   138, 130, 136, 137, 141, 161, 140, 139,
+   240, 164, 149, 162, 147, 245, 148, 246,
+   176, 151, 163, 150, 129, 178, 254, 152};
+#endif /* 0 */
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters)
+{
+  return 1;
+}
+
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t len, size_t count, const unsigned char *attributes)
+{
+  static unsigned char pre_speech[] = { PRE_SPEECH };
+  static unsigned char post_speech[] = { POST_SPEECH };
+
+  /* Keep it simple for now: */
+  if (pre_speech[0])
+    serialWriteData (BL_serialDevice, pre_speech + 1, pre_speech[0]);
+  serialWriteData (BL_serialDevice, buffer, len);
+  if (post_speech[0])
+    serialWriteData (BL_serialDevice, post_speech + 1, post_speech[0]);
+
+#if 0
+  unsigned char c;
+  int i;
+
+  if (pre_speech[0])
+    {
+      memcpy (rawdata, pre_speech + 1, pre_speech[0]);
+      write (brl_fd, rawdata, pre_speech[0]);
+    }
+  for (i = 0; i < len; i++)
+    {
+      c = buffer[i];
+      if (c >= 0X80) c = latin2cp437[c-0X80];
+      if (c < 33)	/* space or control character */
+	{
+	  rawdata[0] = ' ';
+	  write (brl_fd, rawdata, 1);
+	}
+      else if (c > MAX_TRANS)
+	write (brl_fd, &c, 1);
+      else
+	{
+	  memcpy (rawdata, vocab[c - 33], strlen (vocab[c - 33]));
+	  write (brl_fd, rawdata, strlen (vocab[c - 33]));
+	}
+    }
+  if (post_speech[0])
+    {
+      memcpy (rawdata, post_speech + 1, post_speech[0]);
+      write (brl_fd, rawdata, post_speech[0]);
+    }
+#endif /* 0 */
+}
+
+
+static void
+spk_mute (SpeechSynthesizer *spk)
+{
+  unsigned char mute_seq[] = {MUTE_SEQ };
+
+  serialWriteData (BL_serialDevice, mute_seq + 1, mute_seq[0]);
+}
+
+
+static void
+spk_destruct (SpeechSynthesizer *spk)
+{
+}
diff --git a/Drivers/Speech/BrailleLite/speech.h b/Drivers/Speech/BrailleLite/speech.h
new file mode 100644
index 0000000..13ff32c
--- /dev/null
+++ b/Drivers/Speech/BrailleLite/speech.h
@@ -0,0 +1,139 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BrailleLite/speech.h - definitions for rudimentary speech support
+ * N. Nair, 11 September 1998
+ */
+
+/* These sequences are sent to the Braille Lite before and after the
+ * speech data itself.  The first byte is the length, so embedded nuls are
+ * allowed.
+ */
+#define PRE_SPEECH "\006\0050B\0051O"
+#define POST_SPEECH "\007\r\0050O\0051B"
+
+/* This is sent to mute the speech.  The format is the same as above. */
+#define MUTE_SEQ "\015\0050B\0051O\030\0050O\0051B"
+
+/* The speech data itself is translated character by character.  If a
+ * character is less than 33 (i.e. space or control), it is replaced by a
+ * space.  If the character is more than MAX_TRANS, it is passed through
+ * as is.  Otherwise the character n is replaced by the string vocab[n - 33].
+ */
+
+#define MAX_TRANS 32
+
+#if MAX_TRANS > 32
+static unsigned char *vocab[MAX_TRANS - 32] =
+{
+  " exclamation ",
+  " double quote ",
+  " hash ",
+  " dollar ",
+  " percent ",
+  " ampersand ",
+  " quote ",
+  " left paren ",
+  " right paren ",
+  " star ",
+  " plus ",
+  " comma ",
+  " dash ",
+  " dot ",
+  " slash ",
+  "0",
+  "1",
+  "2",
+  "3",
+  "4",
+  "5",
+  "6",
+  "7",
+  "8",
+  "9",
+  " colon ",
+  " semicolon ",
+  " less than ",
+  " equals ",
+  " greater than ",
+  " question ",
+  " at ",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  " left bracket ",
+  " backslash ",
+  " right bracket ",
+  " uparrow ",
+  " underline ",
+  " accent ",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  " left brace ",
+  " bar ",
+  " right brace ",
+  " tilde "
+};
+#endif /* MAX_TRANS > 32 */
diff --git a/Drivers/Speech/CombiBraille/Makefile.in b/Drivers/Speech/CombiBraille/Makefile.in
new file mode 100644
index 0000000..8043dc6
--- /dev/null
+++ b/Drivers/Speech/CombiBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = cb
+DRIVER_NAME = CombiBraille
+DRIVER_USAGE = braille device
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/CombiBraille/speech.c b/Drivers/Speech/CombiBraille/speech.c
new file mode 100644
index 0000000..cbd5c89
--- /dev/null
+++ b/Drivers/Speech/CombiBraille/speech.c
@@ -0,0 +1,242 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "io_generic.h"
+#include "async_wait.h"
+
+#include "spk_driver.h"
+#include "speech.h"		/* for speech definitions */
+#include "Drivers/Braille/CombiBraille/braille.h"
+
+static unsigned char *speechBuffer;
+static size_t speechSize;
+static size_t speechLength;
+
+static const char *const vocab[MAX_TRANS - 32] = {
+  " exclamation ",
+  " double quote ",
+  " hash ",
+  " dollar ",
+  " percent ",
+  " ampersand ",
+  " quote ",
+  " left paren ",
+  " right paren ",
+  " star ",
+  " plus ",
+  " comma ",
+  " dash ",
+  " dot ",
+  " slash ",
+  "0",
+  "1",
+  "2",
+  "3",
+  "4",
+  "5",
+  "6",
+  "7",
+  "8",
+  "9",
+  " colon ",
+  " semicolon ",
+  " less than ",
+  " equals ",
+  " greater than ",
+  " question ",
+  " at ",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  " left bracket ",
+  " backslash ",
+  " right bracket ",
+  " uparrow ",
+  " underline ",
+  " accent ",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  " left brace ",
+  " bar ",
+  " right brace ",
+  " tilde "
+};
+
+/* charset conversion table from iso latin-1 == iso 8859-1 to cp437==ibmpc
+ * for chars >=128. 
+ */
+static unsigned char latin2CP437[0X80] = {
+  199, 252, 233, 226, 228, 224, 229, 231,
+  234, 235, 232, 239, 238, 236, 196, 197,
+  201, 181, 198, 244, 247, 242, 251, 249,
+  223, 214, 220, 243, 183, 209, 158, 159,
+  255, 173, 155, 156, 177, 157, 188, 21,
+  191, 169, 166, 174, 170, 237, 189, 187,
+  248, 241, 253, 179, 180, 230, 20, 250,
+  184, 185, 167, 175, 172, 171, 190, 168,
+  192, 193, 194, 195, 142, 143, 146, 128,
+  200, 144, 202, 203, 204, 205, 206, 207,
+  208, 165, 210, 211, 212, 213, 153, 215,
+  216, 217, 218, 219, 154, 221, 222, 225,
+  133, 160, 131, 227, 132, 134, 145, 135,
+  138, 130, 136, 137, 141, 161, 140, 139,
+  240, 164, 149, 162, 147, 245, 148, 246,
+  176, 151, 163, 150, 129, 178, 254, 152
+};
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  speechBuffer = NULL;
+  speechSize = 0;
+  speechLength = 0;
+  return 1;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  if (speechBuffer) {
+    free(speechBuffer);
+    speechBuffer = NULL;
+  }
+}
+
+static int
+addBytes (const unsigned char *bytes, size_t count) {
+  size_t newLength = speechLength + count;
+
+  if (newLength > speechSize) {
+    size_t newSize = ((newLength | 0XFF) + 1) << 1;
+    unsigned char *newBuffer = realloc(speechBuffer, newSize);
+
+    if (!newBuffer) {
+      logMallocError();
+      return 0;
+    }
+
+    speechBuffer = newBuffer;
+    speechSize = newSize;
+  }
+
+  memcpy(&speechBuffer[speechLength], bytes, count);
+  speechLength += count;
+  return 1;
+}
+
+static int
+addSequence (const char *sequence) {
+  return addBytes((const unsigned char *)&sequence[1], sequence[0]);
+}
+
+static void
+flushSpeech (void) {
+  if (speechLength) {
+    if (cbBrailleDisplay) {
+      GioEndpoint *endpoint = cbBrailleDisplay->gioEndpoint;
+
+      if (endpoint) {
+        gioWriteData(endpoint, speechBuffer, speechLength);
+        asyncWait(speechLength * 1000 / gioGetBytesPerSecond(endpoint));
+        speechLength = 0;
+      }
+    }
+  }
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  addSequence(PRE_SPEECH);
+
+  for (unsigned int i=0; i<length; i+=1) {
+    unsigned char byte = buffer[i];
+    if (byte >= 0X80) byte = latin2CP437[byte-0X80];
+
+    unsigned char *byteAddress = &byte;
+    size_t byteCount = 1;
+
+    if (byte < 33) {	/* space or control character */
+      byte = ' ';
+    } else if (byte <= MAX_TRANS) {
+      const char *word = vocab[byte - 33];
+      byteAddress = (unsigned char *)word;
+      byteCount = strlen(word);
+    }
+
+    addBytes(byteAddress, byteCount);
+  }
+
+  addSequence(POST_SPEECH);
+  flushSpeech();
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  addSequence(MUTE_SEQ);
+  flushSpeech();
+}
diff --git a/Drivers/Speech/CombiBraille/speech.h b/Drivers/Speech/CombiBraille/speech.h
new file mode 100644
index 0000000..7fc99ba
--- /dev/null
+++ b/Drivers/Speech/CombiBraille/speech.h
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* These sequences are sent to the CombiBraille before and after the
+ * speech data itself.  The first byte is the length, so embedded nuls are
+ * allowed.
+ */
+#define PRE_SPEECH "\011\033S@f9@w5\n"
+#define POST_SPEECH "\003\n.\n"
+
+/* This is sent to mute the speech.  The format is the same as above. */
+#define MUTE_SEQ "\003\033S\030"
+
+/* The speech data itself is translated character by character.  If a
+ * character is less than 33 (i.e. space or control), it is replaced by a
+ * space.  If the character is more than MAX_TRANS, it is passed through
+ * as is.  Otherwise the character n is replaced by the string vocab[n - 33].
+ */
+
+#define MAX_TRANS 126
diff --git a/Drivers/Speech/ExternalSpeech/Makefile.in b/Drivers/Speech/ExternalSpeech/Makefile.in
new file mode 100644
index 0000000..d001f7b
--- /dev/null
+++ b/Drivers/Speech/ExternalSpeech/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = xs
+DRIVER_NAME = ExternalSpeech
+DRIVER_USAGE = sends speech to external program and receives spoken position indexes
+DRIVER_VERSION = 1.0 (April 2019)
+DRIVER_DEVELOPERS = Stéphane Doyon <s.doyon@videotron.ca>
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/ExternalSpeech/README b/Drivers/Speech/ExternalSpeech/README
new file mode 100644
index 0000000..d6289d5
--- /dev/null
+++ b/Drivers/Speech/ExternalSpeech/README
@@ -0,0 +1,54 @@
+ExternalSpeech
+by Stéphane Doyon <s.doyon@videotron.ca>
+Version 0.7 beta, September 2001
+
+This speech driver interfaces with an external program that will handle
+speech synthesis. The protocol for talking to the external program is
+quite arbitrary: there is nothing standard about it.
+
+As opposed to the GenericSay driver, this driver is intended for more
+sophisticated features:
+
+- Communication with the external program is bi-directional, and BRLTTY
+constantly listens for progress updates from the external program. BRLTTY
+can thus perform speech tracking: the braille display can move as the
+words are spoken. BRLTTY is then also able to determine when the text is
+finished speaking. Finally, if speech is stopped by the user, BRLTTY knows
+at what position it stopped, so the user does not have to search for
+the stopping point when he/she wants to resume speech synthesis.
+
+- Video attributes of the characters are passed with the text, so that the
+external program can interpret them and add appropriate vocal markings. 
+
+Currently, only one external program exists that can talk this protocol. It
+uses IBM's Viavoice TTS AKA Viavoice Outloud software speech synthesis
+library.
+
+NB IBM offers the Viavoice TTS product at no cost for
+personal / pseudo-evaluation use. Unfortunately their software is not
+free in the GNU sense nor opensource, and a license is required for
+commercial use. It is however much more usable and of far superior quality
+than rsynth, festival, and the TTS scripts I have seen for MBROLA. 
+
+My external speech program does some speech preprocessing and then uses
+the Viavoice library. It is still quite alpha. You can find it at:
+http://pages.infinit.net/sdoyon/via.html
+It will be kept separate from BRLTTY (in part because of the non-free
+nature of Viavoice TTS).
+
+The path to the external program, and the uid and gid under which it is
+executed, can be either:
+-hard-coded in speech.h
+-specified in the BRLTTY configuration file through
+   the speech-parameter keyword.
+-Specified on the command-line using the -S option.
+
+In the last two cases, the format is:
+   program=<extProgPath>[,uid=<numerical_uid>][,gid=<numerical_gid>]
+without space.
+As in:
+speech-parameter program=/home/steph/via-0.3/via_wrapper,uid=501,gid=501
+
+Avoid executing the external program as root if at all possible. The
+account you choose may require some rights such as access to the
+soundcard device.
diff --git a/Drivers/Speech/ExternalSpeech/speech.c b/Drivers/Speech/ExternalSpeech/speech.c
new file mode 100644
index 0000000..1bef097
--- /dev/null
+++ b/Drivers/Speech/ExternalSpeech/speech.c
@@ -0,0 +1,282 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* ExternalSpeech/speech.c - Speech library (driver)
+ * For external programs, using my own protocol. Features indexing.
+ * Stéphane Doyon <s.doyon@videotron.ca>
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "log.h"
+#include "timing.h"
+#include "io_misc.h"
+#include "async_handle.h"
+#include "async_io.h"
+
+typedef enum {
+  PARM_SOCKET_PATH,
+} DriverParameter;
+
+#define SPKPARMS "socket_path"
+#include "spk_driver.h"
+#include "speech.h"
+
+static const char *socketPath = NULL;
+static struct sockaddr_un socketAddress;
+static int socketDescriptor = -1;
+
+static uint16_t totalCharacterCount;
+#define TRACK_DATA_SIZE 2
+static AsyncHandle trackHandle = NULL;
+
+ASYNC_INPUT_CALLBACK(xsHandleSpeechTrackingInput) {
+  SpeechSynthesizer *spk = parameters->data;
+
+  if (parameters->error) {
+    logMessage(LOG_WARNING,
+      "speech tracking input error %d: %s",
+      parameters->error, strerror(parameters->error)
+    );
+  } else if (parameters->end) {
+    logMessage(LOG_WARNING, "speech tracking end-of-file");
+  } else if (parameters->length >= TRACK_DATA_SIZE) {
+    const unsigned char *buffer = parameters->buffer;
+    uint16_t location = (buffer[0] << 8) | buffer[1];
+
+    if (location < totalCharacterCount) {
+      tellSpeechLocation(spk, location);
+    } else {
+      tellSpeechFinished(spk);
+    }
+
+    return TRACK_DATA_SIZE;
+  }
+
+  return 0;
+}
+
+static int
+amConnected (void) {
+  return socketDescriptor != -1;
+}
+
+static int
+connectToServer (SpeechSynthesizer *spk) {
+  if (amConnected()) return 1;
+
+  logMessage(LOG_CATEGORY(SPEECH_DRIVER), "connecting to server: %s", socketPath);
+  int sd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+  if (sd != -1) {
+    if (setCloseOnExec(sd, 1)) {
+      if (connect(sd, (const struct sockaddr *)&socketAddress, sizeof(socketAddress)) != -1) {
+        if (setBlockingIo(sd, 0)) {
+          if (asyncReadSocket(&trackHandle, sd, TRACK_DATA_SIZE, xsHandleSpeechTrackingInput, spk)) {
+            logMessage(LOG_CATEGORY(SPEECH_DRIVER), "connected to server: fd=%d", sd);
+            socketDescriptor = sd;
+            return 1;
+          }
+        }
+      } else {
+        logSystemError("connect");
+      }
+    }
+
+    close(sd);
+  } else {
+    logSystemError("socket");
+  }
+
+  return 0;
+}
+
+static void
+disconnectFromServer (void) {
+  if (amConnected()) {
+    logMessage(LOG_CATEGORY(SPEECH_DRIVER), "disconnecting from server");
+
+    if (trackHandle) {
+      asyncCancelRequest(trackHandle);
+      trackHandle = NULL;
+    }
+
+    close(socketDescriptor);
+    socketDescriptor = -1;
+  }
+}
+
+static int
+sendPacket (SpeechSynthesizer *spk, const unsigned char *packet, size_t length) {
+  if (!amConnected()) {
+    if (!connectToServer(spk)) {
+      return 0;
+    }
+  }
+
+  const unsigned char *position = packet;
+  const unsigned char *end = position + length;
+
+  TimePeriod period;
+  startTimePeriod(&period, XS_WRITE_TIMEOUT);
+
+  while (position < end) {
+    if (afterTimePeriod(&period, NULL)) {
+      logMessage(LOG_ERR, "ExternalSpeech write timed out");
+      disconnectFromServer();
+      return 0;
+    }
+
+    ssize_t result = write(socketDescriptor, position, (end - position));
+
+    if (result == -1) {
+      if ((errno == EINTR) || (errno == EAGAIN)) continue;
+
+      logMessage(LOG_ERR,
+        "ExternalSpeech write error %d: %s",
+        errno, strerror(errno)
+      );
+
+      disconnectFromServer();
+      if (!connectToServer(spk)) return 0;
+
+      position = packet;
+      continue;
+    }
+
+    position += result;
+  }
+
+  return 1;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes) {
+  if (!attributes) count = 0;
+
+  unsigned char packet[5 + length + count];
+  unsigned char *p = packet;
+
+  *p++ = 4;
+  *p++ = length >> 8;
+  *p++ = length & 0XFF;
+  *p++ = count >> 8;
+  *p++ = count & 0XFF;
+  p = mempcpy(p, text, length);
+  p = mempcpy(p, attributes, count);
+
+  if (sendPacket(spk, packet, (p - packet))) {
+    totalCharacterCount = count;
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  logMessage(LOG_CATEGORY(SPEECH_DRIVER), "mute");
+
+  unsigned char packet[] = {1};
+  sendPacket(spk, packet, sizeof(packet));
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  unsigned char percentage = getIntegerSpeechVolume(setting, 100);
+
+  logMessage(
+    LOG_CATEGORY(SPEECH_DRIVER),
+    "set volume to %u (%u%%)",
+    setting, percentage
+  );
+
+  unsigned char packet[] = {2, percentage};
+  sendPacket(spk, packet, sizeof(packet));
+}
+
+static int
+sendFloatSetting (SpeechSynthesizer *spk, unsigned char code, float value) {
+  unsigned char packet[5] = {code};
+  const unsigned char *v = (const unsigned char *)&value;
+
+#ifdef WORDS_BIGENDIAN
+  packet[1] = v[0];
+  packet[2] = v[1];
+  packet[3] = v[2];
+  packet[4] = v[3];
+#else /* WORDS_BIGENDIAN */
+  packet[1] = v[3];
+  packet[2] = v[2];
+  packet[3] = v[1];
+  packet[4] = v[0];
+#endif /* WORDS_BIGENDIAN */
+
+  return sendPacket(spk, packet, sizeof(packet));
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  float stretch = 1.0 / getFloatSpeechRate(setting); 
+
+  logMessage(
+    LOG_CATEGORY(SPEECH_DRIVER),
+    "set rate to %u (time scale %f)",
+    setting, stretch
+  );
+
+  sendFloatSetting(spk, 3, stretch);
+}
+
+static void
+spk_setPitch (SpeechSynthesizer *spk, unsigned char setting) {
+  float multiplier = getFloatSpeechPitch(setting); 
+
+  logMessage(
+    LOG_CATEGORY(SPEECH_DRIVER),
+    "set pitch to %u (multiplier %f)",
+    setting, multiplier
+  );
+
+  sendFloatSetting(spk, 5, multiplier);
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+  spk->setPitch = spk_setPitch;
+
+  socketPath = parameters[PARM_SOCKET_PATH];
+  if (!socketPath || !*socketPath) socketPath = XS_DEFAULT_SOCKET_PATH;
+
+  memset(&socketAddress, 0, sizeof(socketAddress));
+  socketAddress.sun_family = AF_UNIX;
+  strncpy(socketAddress.sun_path, socketPath, sizeof(socketAddress.sun_path)-1);
+
+  socketDescriptor = -1;
+  trackHandle = NULL;
+  return connectToServer(spk);
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  disconnectFromServer();
+}
diff --git a/Drivers/Speech/ExternalSpeech/speech.h b/Drivers/Speech/ExternalSpeech/speech.h
new file mode 100644
index 0000000..234553a
--- /dev/null
+++ b/Drivers/Speech/ExternalSpeech/speech.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* ExternalSpeech/speech.h */
+
+/* The following are the default parameters that will be used if no parameter
+   is specified on the command-line (-p) or in the brltty.conf file
+   (speech-driverparm option). */
+
+/* The default path of the UNIX-domain socket for the external helper program.
+ */
+#define XS_DEFAULT_SOCKET_PATH "/tmp/exs-data"
+
+/* The maxdimum amount of time that a write to the socket may take. */
+#define XS_WRITE_TIMEOUT 2000
diff --git a/Drivers/Speech/Festival/Makefile.in b/Drivers/Speech/Festival/Makefile.in
new file mode 100644
index 0000000..844e797
--- /dev/null
+++ b/Drivers/Speech/Festival/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = fv
+DRIVER_NAME = Festival
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Festival/README b/Drivers/Speech/Festival/README
new file mode 100644
index 0000000..8a43755
--- /dev/null
+++ b/Drivers/Speech/Festival/README
@@ -0,0 +1,12 @@
+This directory contains the BRLTTY speech driver for the Festival text to
+speech engine from the Centre for Speech Technology Research
+[http://www.cstr.ed.ac.uk/] of the University of Edinburgh
+[http://www.ed.ac.uk/]. It was implemented by Nikhil Nair
+<nn201@cus.cam.ac.uk>, and is being maintained by Dave Mielke <dave@mielke.cc>.
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   command   /path/to/festival/command
+   name      Kevin, Kal
+
diff --git a/Drivers/Speech/Festival/speech.c b/Drivers/Speech/Festival/speech.c
new file mode 100644
index 0000000..563edf7
--- /dev/null
+++ b/Drivers/Speech/Festival/speech.c
@@ -0,0 +1,171 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* BRLTTY speech driver for the Festival text to speech engine.
+ * Written by: Nikhil Nair <nn201@cus.cam.ac.uk>
+ * Maintained by: Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+
+#include "log.h"
+
+typedef enum {
+  PARM_COMMAND,
+  PARM_NAME
+} DriverParameter;
+#define SPKPARMS "command", "name"
+
+#include "spk_driver.h"
+#include "speech.h"		/* for speech definitions */
+
+static char **festivalParameters = NULL;
+static FILE *festivalStream = NULL;
+static float festivalRate;
+
+static int writeCommand (const char *command, int reopen);
+
+static int
+setRate (float setting, int reopen) {
+  char command[0X40];
+  snprintf(command, sizeof(command), "(Parameter.set 'Duration_Stretch %f)", 1.0/setting);
+  return writeCommand(command, reopen);
+}
+
+static void
+closeStream (void) {
+  logMessage(LOG_DEBUG, "stopping festival");
+  pclose(festivalStream);
+  festivalStream = NULL;
+}
+
+static int
+openStream (void) {
+  const char *command = festivalParameters[PARM_COMMAND];
+  if (!command || !*command) command = "festival";
+
+  logMessage(LOG_DEBUG, "starting festival: command=%s", command);
+  if ((festivalStream = popen(command, "w"))) {
+    setvbuf(festivalStream, NULL, _IOLBF, 0X1000);
+
+    if (!writeCommand("(gc-status nil)", 0)) return 0;
+    if (!writeCommand("(audio_mode 'async)", 0)) return 0;
+    if (!writeCommand("(Parameter.set 'Audio_Method 'netaudio)", 0)) return 0;
+
+    {
+      const char *name = festivalParameters[PARM_NAME];
+      if (name && *name) {
+        if (strcasecmp(name, "Kevin") == 0) {
+          if (!writeCommand("(voice_ked_diphone)", 0)) return 0;
+        } else if (strcasecmp(name, "Kal") == 0) {
+          if (!writeCommand("(voice_kal_diphone)", 0)) return 0;
+        } else {
+          logMessage(LOG_WARNING, "unknown Festival voice name: %s", name);
+        }
+      }
+    }
+
+    if (festivalRate != 0.0)
+      if (!setRate(festivalRate, 0))
+        return 0;
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+writeString (const char *string, int reopen) {
+  if (!festivalStream) {
+    if (!reopen) return 0;
+    if (!openStream()) return 0;
+  }
+
+  fputs(string, festivalStream);
+  if (!ferror(festivalStream)) return 1;
+
+  logSystemError("fputs");
+  closeStream();
+  return 0;
+}
+
+static int
+writeCommand (const char *command, int reopen) {
+  return writeString(command, reopen) && writeString("\n", 0);
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  setRate(festivalRate=getFloatSpeechRate(setting), 1);
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  spk->setRate = spk_setRate;
+
+  festivalParameters = parameters;
+  festivalRate = 0.0;
+  return openStream();
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  if (writeCommand("(quit)", 0)) closeStream();
+  festivalParameters = NULL;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  int started = 0;
+  unsigned int index;
+
+  for (index=0; index<length; index+=1) {
+    unsigned char byte = buffer[index];
+    if (iscntrl(byte)) byte = ' ';
+
+    if (!isspace(byte)) {
+      if (!started) {
+        if (!writeString("(SayText \"", 1)) return;
+        started = 1;
+      }
+    }
+
+    if (started) {
+      const char bytes[] = {'\\', byte, 0};
+      const char *string = bytes;
+
+      if (!strchr("\\\"", byte)) string += 1;
+      if (!writeString(string, 0)) return;
+    }
+  }
+
+  if (started) {
+    if (!writeString("\")\n", 0)) return;
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  writeCommand("(audio_mode 'shutup)", 0);
+}
diff --git a/Drivers/Speech/Festival/speech.h b/Drivers/Speech/Festival/speech.h
new file mode 100644
index 0000000..3096775
--- /dev/null
+++ b/Drivers/Speech/Festival/speech.h
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Festival/speech.h - definitions for rudimentary speech support
+ * N. Nair, 4 October 1998
+ */
+
+/* These sequences are sent to Festival's command mode.
+ * The first byte is the length, so embedded nuls are allowed.
+ */
+
diff --git a/Drivers/Speech/FestivalLite/Makefile.in b/Drivers/Speech/FestivalLite/Makefile.in
new file mode 100644
index 0000000..f777a5f
--- /dev/null
+++ b/Drivers/Speech/FestivalLite/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = fl
+DRIVER_NAME = FestivalLite
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_fl@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -I$(FLITE_ROOT)/include/flite -DREGISTER_VOX=register_$(FLITE_VOICE) -DUNREGISTER_VOX=unregister_$(FLITE_VOICE) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/FestivalLite/README b/Drivers/Speech/FestivalLite/README
new file mode 100644
index 0000000..d2c7e6f
--- /dev/null
+++ b/Drivers/Speech/FestivalLite/README
@@ -0,0 +1,23 @@
+This directory contains the BRLTTY speech driver for the Festival Lite text to
+speech engine from the CMU Speech Center [http://www.speech.cs.cmu.edu/flite/]
+of Carnegie Mellon University [http://www.cmu.edu]. It was implemented, and is
+being maintained, by Mario Lang <mlang@delysid.org>.
+
+BRLTTY's configure script automatically includes this driver if Festival Lite
+has been installed. The default is to check the directories /usr, /usr/local,
+/usr/local/FestivalLite, /usr/local/flite, /opt/FestivalLite, and /opt/flite. 
+The actual location can be explicitly specified via --with-flite. 
+
+Additionally, Festival Lite's language, lexicon and voice can be specified
+using the configure script options --with-flite-language, --with-flite-lexicon
+and --with-flite-voice. The default voice is cmu_us_kal16.
+
+This driver and that for the Theta text to speech engine cannot be linked into
+BRLTTY's binary at the same time (via --enable-speech-driver) because their
+run-time libraries contain conflicting symbols.
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   pitch     50-200 (percent from default)
+
diff --git a/Drivers/Speech/FestivalLite/speech.c b/Drivers/Speech/FestivalLite/speech.c
new file mode 100644
index 0000000..b18eae2
--- /dev/null
+++ b/Drivers/Speech/FestivalLite/speech.c
@@ -0,0 +1,148 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* FestivalLite/speech.c - Speech library
+ * For the Festival Lite text to speech package
+ * Maintained by Mario Lang <mlang@delysid.org>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+  PARM_pitch
+} DriverParameter;
+#define SPKPARMS "pitch"
+
+#include "spk_driver.h"
+#include <flite.h>
+#include <flite_version.h>
+
+extern	cst_voice *	REGISTER_VOX	(const char *voxdir);
+extern	void		UNREGISTER_VOX	(cst_voice *voice);
+
+static	cst_voice	*voice		= NULL;
+static	const char	*outtype	= "play";
+
+static	pid_t		child		= -1;
+
+static	int		fds[2];
+static	int		*const readfd	= &fds[0];
+static	int		*const writefd	= &fds[1];
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting)
+{
+  feat_set_float(voice->features, "duration_stretch", 1.0/getFloatSpeechRate(setting));
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters)
+{
+  spk->setRate = spk_setRate;
+
+  child = -1;
+  flite_init();
+  voice = REGISTER_VOX(NULL);
+
+  {
+    int pitch = 100, minpitch = 50, maxpitch = 200;
+    if (*parameters[PARM_pitch])
+      if (!validateInteger(&pitch, parameters[PARM_pitch], &minpitch, &maxpitch))
+        logMessage(LOG_WARNING, "%s: %s", "invalid pitch specification", parameters[PARM_pitch]);
+    feat_set_int(voice->features, "int_f0_target_mean", pitch);
+  }
+
+  logMessage(LOG_INFO, "Festival Lite Engine: version %s-%s, %s",
+	     FLITE_PROJECT_VERSION, FLITE_PROJECT_STATE,
+	     FLITE_PROJECT_DATE);
+  return 1;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk)
+{
+  spk_mute(spk);
+
+  UNREGISTER_VOX(voice);
+  voice = NULL;
+}
+
+static int
+doChild (void) {
+  FILE *stream = fdopen(*readfd, "r");
+
+  if (stream) {
+    char buffer[0X400];
+    char *line;
+
+    while ((line = fgets(buffer, sizeof(buffer), stream))) {
+      flite_text_to_speech(line, voice, outtype);
+    }
+
+    fclose(stream);
+  }
+
+  return 0;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes)
+{
+  if (child != -1) goto ready;
+
+  if (pipe(fds) != -1) {
+    if ((child = fork()) == -1) {
+      logSystemError("fork");
+    } else if (child == 0) {
+      _exit(doChild());
+    } else
+    ready: {
+      unsigned char text[length + 1];
+      memcpy(text, buffer, length);
+      text[length] = '\n';
+      write(*writefd, text, sizeof(text));
+      return;
+    }
+
+    close(*readfd);
+    close(*writefd);
+  } else {
+    logSystemError("pipe");
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk)
+{
+  if (child != -1) {
+    close(*readfd);
+    close(*writefd);
+
+    kill(child, SIGKILL);
+    waitpid(child, NULL, 0);
+    child = -1;
+  }
+}
diff --git a/Drivers/Speech/GenericSay/Makefile.in b/Drivers/Speech/GenericSay/Makefile.in
new file mode 100644
index 0000000..fc358f7
--- /dev/null
+++ b/Drivers/Speech/GenericSay/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = gs
+DRIVER_NAME = GenericSay
+DRIVER_USAGE = pipes to /usr/local/bin/say
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/GenericSay/README b/Drivers/Speech/GenericSay/README
new file mode 100644
index 0000000..ce5b901
--- /dev/null
+++ b/Drivers/Speech/GenericSay/README
@@ -0,0 +1,25 @@
+BRLTTY speech driver for "GEnericSay"
+
+This driver is intended to be used with any speech synthesis program
+like the rsynth package.
+
+This is preliminary code, still experimental.  Need improovements...
+
+From the perspective of brltty: The command "/usr/local/bin/say" is
+invoked with no arguments, and each text string which is to be spoken is
+written to the standard input of that command. A period and a new-line (in
+that order), i.e. ".\n", are appended to each text string as this is a
+reasonably standard way to instruct a speech synthesizer to start speaking
+right away. When the speech is to be stopped, i.e. interrupted and
+flushed, then the standard input of the command is closed. The command is
+restarted the next time there is something else to speak.
+
+From the perspective of the command which must be supplied by the user: It
+must be called "/usr/local/bin/say". It is invoked without any arguments.
+It must first send any required initialization sequences, and then copy
+its standard input, to the speech synthesizer. Upon detecting end-of-file
+on its standard input, it should send the "shut up" sequence, followed by
+any required shut down sequences, to the synthesizer.
+
+The subdirectory "commands" of this directory contains examples of
+"/usr/local/bin/say" scripts for various speech synthesizers.
diff --git a/Drivers/Speech/GenericSay/commands/AccentSA b/Drivers/Speech/GenericSay/commands/AccentSA
new file mode 100755
index 0000000..38294f8
--- /dev/null
+++ b/Drivers/Speech/GenericSay/commands/AccentSA
@@ -0,0 +1,376 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BrlTTY GenericSay helper script for the Accent/SA Speech Synthesizer
+# It should be installed as "/usr/local/bin/say".
+
+# Return the name of this script.
+proc programName {} {
+   global argv0
+   return [file tail $argv0]
+}
+
+# Write a message, prefixed with the name of this script, to standard error.
+proc programMessage {message} {
+   global argv0
+   puts stderr "[programName]: $message"
+}
+
+# Display an error message, and exit with the specified return code.
+proc programError {code {message ""}} {
+   if {[string length $message] > 0} {
+      programMessage $message
+   }
+   exit $code
+}
+
+# Standard exit routine when command line errors are detected.
+proc syntaxError {message} {
+   programError 2 $message
+}
+
+# Standard exit routine when something is wrong with the parameter values.
+proc semanticError {message} {
+   programError 3 $message
+}
+
+# Close the speech synthesizer device.
+proc closeDevice {} {
+   global deviceStream
+   close $deviceStream; unset deviceStream
+}
+
+# Maintain an event which automatically closes the speech synthesizer
+# device if no new data has arrived for a while.
+proc scheduleClose {} {
+   global closeEvent closeTimeout
+   if {[info exists closeEvent]} {
+      after cancel $closeEvent; unset closeEvent
+   }
+   set closeEvent [after $closeTimeout {unset closeEvent; closeDevice}]
+}
+
+# Send data to the speech synthesizer.
+proc sendData {data} {
+   global deviceStream
+   puts -nonewline $deviceStream $data
+   flush $deviceStream
+}
+
+# Send a command to the speech synthesizer.
+proc sendCommand {command} {
+   sendData "\x1b$command"
+}
+
+# Configure a speech synthesizer setting.
+proc configureParameter {settingVariable command} {
+   upvar #0 $settingVariable setting
+   if {[info exists setting]} {
+      if {[llength $command] == 1} {
+	 sendCommand "$command$setting"
+      } else {
+         foreach sequence [lindex $command $setting] {
+	    sendCommand $sequence
+	 }
+      }
+   }
+}
+
+# Open and configure the speech synthesizer device.
+# If it's already open, then don't do anything.
+# If it can't be opened, then silently exit with a non-zero return code.
+proc openDevice {} {
+   global devicePath deviceStream
+   if {![info exists deviceStream]} {
+      if {[catch [list open $devicePath {WRONLY NOCTTY}] response] != 0} {
+         exit 10
+      }
+      set deviceStream $response
+      sendCommand "=F"
+      sendCommand "=B"
+      sendCommand "Oi"
+      configureParameter speechVolume "A"
+      configureParameter speechRate "R"
+      configureParameter voicePitch "P"
+      configureParameter voiceType "V"
+      configureParameter spacePause "S"
+      configureParameter sentenceIntonation "M"
+      configureParameter punctuationMode {Op {OP Or} {OP OR}}
+      configureParameter hyphenMode {Om OM ON}
+   }
+}
+
+# Insure that the speech synthesizer device is open, and then
+# instruct it to speak the content of the supplied string.
+# Arrange for the device to be automatically closed if nothing more
+# needs to be spoken for a reasonable amount of time.
+proc sayString {string} {
+   openDevice
+   scheduleClose
+   sendData "$string\r"
+}
+
+# Tell the speech synthesizer to stop speaking immediately,
+# and to flush its input buffer.
+proc flushSynthesizer {} {
+   sendCommand "=x"
+}
+
+# Check a device path.
+proc checkDevice {description path} {
+   if {![file exists $path]} {
+      semanticError "$description not found: $path"
+   }
+   if {[string compare [set type [file type $path]] characterSpecial] != 0} {
+      semanticError "incorrect $description type: $type: $path"
+   }
+   if {![file writable $path]} {
+      semanticError "$description not writable: $path"
+   }
+}
+
+# Check a keyword value.
+proc checkKeyword {description value keywords} {
+   if {[regexp -nocase {^[a-z]+$} $value]} {
+      if {[set index [lsearch -glob $keywords [set value [string tolower $value]]*]] >= 0} {
+	 if {[lsearch -glob [lreplace $keywords $index $index] $value*] >= 0} {
+	    syntaxError "ambiguous $description: $value"
+	 }
+	 return [lsearch -glob $keywords $value*]
+      }
+   }
+   syntaxError "invalid $description: $value"
+}
+
+# Check an integer value.
+proc checkInteger {description value {minimum ""} {maximum ""}} {
+   if {![regexp {^(0|-?[1-9][0-9]*)$} $value]} {
+      syntaxError "invalid $description: $value"
+   }
+   if {[string length $minimum] > 0} {
+      if {$value < $minimum} {
+         syntaxError "$description less than $minimum: $value"
+      }
+   }
+   if {[string length $maximum] > 0} {
+      if {$value > $maximum} {
+         syntaxError "$description greater than $maximum: $value"
+      }
+   }
+}
+
+# Set the "device" parameter.
+# It must be either the absolute or the relative path to the speech synthesizer device.
+proc set-device {path} {
+   global devicePath
+   set devicePath $path
+}
+
+# Set the "close" parameter.
+# It must be a non-negative integral number of seconds.
+proc set-close {close} {
+   global closeTimeout
+   checkInteger "close timeout" $close 0
+   set closeTimeout $close
+}
+
+# Set the "volume" parameter.
+# It must be an integer from 0 through 9.
+proc set-volume {volume} {
+   global speechVolume
+   checkInteger "speech volume" $volume 0 9
+   set speechVolume $volume
+}
+
+# Set the "rate" parameter.
+# It must be an integer from 0 through 17.
+proc set-rate {rate} {
+   global speechRate
+   set rates "0123456789ABCDEFGH"
+   checkInteger "speech rate" $rate 0 [expr {[string length $rates] - 1}]
+   set speechRate [string index $rates $rate]
+}
+
+# Set the "pitch" parameter.
+# It must be an integer from 0 through 9.
+proc set-pitch {pitch} {
+   global voicePitch
+   checkInteger "voice pitch" $pitch 0 9
+   set voicePitch $pitch
+}
+
+# Set the "voice" parameter.
+# It must be an integer from 0 through 9.
+proc set-voice {voice} {
+   global voiceType
+   checkInteger "voice type" $voice 0 9
+   set voiceType $voice
+}
+
+# Set the "pause" parameter.
+# It must be an integer from 0 through 9.
+proc set-pause {pause} {
+   global spacePause
+   checkInteger "space pause" $pause 0 9
+   set spacePause $pause
+}
+
+# Set the "intonation" parameter.
+# It must be an integer from 0 through 4.
+proc set-intonation {intonation} {
+   global sentenceIntonation
+   checkInteger "sentence intonation" $intonation 0 4
+   set sentenceIntonation [expr {($intonation + 1) % 5}]
+}
+
+# Set the "punctuation" parameter.
+# It must be one of none, common, all.
+proc set-punctuation {punctuation} {
+   global punctuationMode
+   set punctuationMode [checkKeyword "punctuation mode" $punctuation {none common all}]
+}
+
+# Set the "hyphen" parameter.
+# It must be one of no, dash, minus.
+proc set-hyphen {hyphen} {
+   global hyphenMode
+   set hyphenMode [checkKeyword "hyphen mode" $hyphen {no dash minus}]
+}
+
+# Set a parameter.
+# For the list of settable parameters, see the set-... handlers.
+# They may be specified within the system configuration file,
+# within the user configuration file, or as command line options.
+proc setParameter {name value} {
+   if {[set count [llength [set handlers [info procs "set-[string tolower $name]*"]]]] == 0} {
+      syntaxError "invalid parameter name: $name"
+   } elseif {$count > 1} {
+      syntaxError "ambiguous parameter name: $name"
+   } elseif {[string length $value] == 0} {
+      syntaxError "missing parameter value: $name"
+   } else {
+      eval [lindex $handlers 0] [list $value]
+   }
+}
+
+# Process the configuration file.
+# If it does not exist, then silently continue.
+# If it does exist but cannot be opened, then display a warning.
+proc setParameters {path} {
+   if {[catch [list open $path {RDONLY}] response] != 0} {
+      global errorCode
+      set type [lindex $errorCode 0]
+      set code [lindex $errorCode 1]
+      set warn 1
+      if {[string compare $type POSIX] == 0} {
+         if {[lsearch -exact {ENOENT} $code] >= 0} {
+	    set warn 0
+	 }
+      }
+      if {$warn} {
+	 programMessage $response
+      }
+   } else {
+      set file $response
+      while {[gets $file line] >= 0} {
+         if {[set length [string length [set line [string trim $line]]]] == 0} {
+	    continue
+	 }
+	 if {[string compare [string index $line 0] #] == 0} {
+	    continue
+	 }
+	 regexp {^([^ ]+)(.*)$} $line x name value
+	 setParameter $name [string trim $value]
+      }
+      close $file; unset file
+   }
+}
+
+# Process the command line options, and remove them from the argument list.
+proc processOptions {} {
+   global argv
+   while {[llength $argv] > 0} {
+      set name [lindex $argv 0]
+      if {[string length $name] == 0} {
+         break
+      }
+      if {[string compare [set character [string index $name 0]] -] != 0} {
+         break
+      }
+      set argv [lrange $argv 1 end]
+      if {[string length [set name [string trimleft $name $character]]] == 0} {
+         break
+      }
+      if {[llength $argv] == 0} {
+	 set value ""
+      } else {
+	 set value [lindex $argv 0]
+	 set argv [lrange $argv 1 end]
+      }
+      setParameter $name $value
+   }
+}
+
+proc prepareParameters {} {
+   global devicePath closeTimeout
+   checkDevice "synthesizer device" $devicePath
+   set closeTimeout [expr {$closeTimeout * 1000}]; # After wants milliseconds.
+}
+
+# Assign defaults to the parameters.
+setParameter device "/dev/ttyS0"
+setParameter close 5
+
+# Process the system configuration file.
+setParameters "/usr/local/etc/[programName].conf"
+
+# Process the user configuration file.
+set variable env(HOME)
+if {[info exists $variable]} {
+   if {[string length [set directory [set $variable]]] > 0} {
+      setParameters [file join $directory ".[programName]rc"]
+   }
+}
+
+# If no arguments have been supplied, then be a brltty GenericSay helper command. 
+if {[llength $argv] == 0} {
+   prepareParameters
+   set inputStream stdin
+   fconfigure $inputStream -blocking 0
+   fileevent $inputStream readable {
+      if {[eof $inputStream]} {
+	 if {[info exists deviceStream]} {
+	    flushSynthesizer
+	 }
+	 set returnCode 0
+      } else {
+	 sayString [read $inputStream]
+      }
+   }
+   vwait returnCode
+   exit $returnCode
+}
+
+# Process the command line options.
+processOptions
+prepareParameters
+
+# Speak the positional arguments as a sequence of words.
+sayString [join $argv " "]
+exit 0
diff --git a/Drivers/Speech/GenericSay/commands/say b/Drivers/Speech/GenericSay/commands/say
new file mode 100755
index 0000000..43bffc0
--- /dev/null
+++ b/Drivers/Speech/GenericSay/commands/say
@@ -0,0 +1,34 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Simple BrlTTY GenericSay helper script.
+# It should be installed as "/usr/local/bin/say".
+
+# If your speech synthesizer does not require any special initialization
+# sequences, and if you either are satisfied with its default settings or
+# don't know how to change them, then this helper script is for you. It
+# does no more than simply send the text to be spoken directly to your
+# synthesizer. Note that, being totally general, it cannot, and does
+# not, implement the mute function. All you need to do is:
+# - Set the device (see "device=" line below).
+# - Install the script (as /usr/local/bin/say).
+
+device=/dev/ttyS0
+exec cat >"${device}"
+exit $?
diff --git a/Drivers/Speech/GenericSay/speech.c b/Drivers/Speech/GenericSay/speech.c
new file mode 100644
index 0000000..8007964
--- /dev/null
+++ b/Drivers/Speech/GenericSay/speech.c
@@ -0,0 +1,92 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* GenericSay/speech.c - To use a generic 'say' command, like for the
+ * rsynth package.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+typedef enum {
+   PARM_COMMAND
+} DriverParameter;
+#define SPKPARMS "command"
+
+#include "spk_driver.h"
+#include "speech.h"
+
+static const char *commandPath;	/* default full path for the say command */
+static FILE *commandStream = NULL;
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters)
+{
+  const char *command = parameters[PARM_COMMAND];
+  commandPath = *command? command: SAY_CMD;
+  logMessage(LOG_INFO, "Speech Command: %s", commandPath);
+  return 1;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  if (!commandStream) {
+    if (!(commandStream = popen(commandPath, "w"))) {
+      logMessage(LOG_WARNING, "cannot start command: %s: %s",
+                 commandPath, strerror(errno));
+    }
+  }
+
+  if (commandStream) {
+    if (fwrite(buffer, length, 1, commandStream)) {
+      static const char trailer[] = {'\n'};
+
+      if (fwrite(trailer, sizeof(trailer), 1, commandStream)) {
+        if (fflush(commandStream) == EOF) {
+        } else {
+          logSystemError("fflush");
+        }
+      } else {
+        logSystemError("fwrite");
+      }
+    } else {
+      logSystemError("fwrite");
+    }
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk)
+{
+  if (commandStream)
+    {
+       pclose(commandStream);
+       commandStream = NULL;
+    }
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk)
+{
+   spk_mute(spk);
+}
diff --git a/Drivers/Speech/GenericSay/speech.h b/Drivers/Speech/GenericSay/speech.h
new file mode 100644
index 0000000..b9cfd29
--- /dev/null
+++ b/Drivers/Speech/GenericSay/speech.h
@@ -0,0 +1,24 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* GenericSay/speech.h - definitions for rudimentary speech support
+ */
+
+#ifndef SAY_CMD
+   #define SAY_CMD "/usr/local/bin/say"
+#endif /* SAY_CMD */
diff --git a/Drivers/Speech/Mikropuhe/Makefile.in b/Drivers/Speech/Mikropuhe/Makefile.in
new file mode 100644
index 0000000..2b05857
--- /dev/null
+++ b/Drivers/Speech/Mikropuhe/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mp
+DRIVER_NAME = Mikropuhe
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_mp@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CXX) $(SPK_CXXFLAGS) -I$(MIKROPUHE_ROOT) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Mikropuhe/README b/Drivers/Speech/Mikropuhe/README
new file mode 100644
index 0000000..4c58361
--- /dev/null
+++ b/Drivers/Speech/Mikropuhe/README
@@ -0,0 +1,26 @@
+This directory contains the BRLTTY speech driver for the Mikropuhe text to
+speech engine [http://www.mikropuhe.com] from TimeHouse
+[http://www.timehouse.fi/oy/en/]. It was implemented, and is being maintained,
+by Dave Mielke <dave@mielke.cc>.
+
+BRLTTY's configure script automatically includes this driver if Mikropuhe has
+been installed. The default is to check the directories /usr, /usr/local,
+/usr/local/Mikropuhe, /usr/local/mikropuhe, /opt/Mikropuhe, and /opt/mikropuhe. 
+The actual location can be explicitly specified via --with-mikropuhe.
+
+Check the date of the file "libmplinux.so" within the Mikropuhe package to
+ensure that you have a recent enough version. It must not be dated earlier than
+February 23, 2004. Please see the instructions on the Mikropuhe CD-ROM or at
+[www.mikropuhe.com].
+
+This driver can be either dynamically loaded (at run time) or built directly
+into a dynamically linked BRLTTY binary (via --with-speech-driver), but it
+cannot be built into a statically linked BRLTTY binary (via
+--enable-standalone-programs) because the Mikropuhe package doesn't contain a
+static archive. 
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   name      name (/path/to/mikropuhe/name.pu5)
+   pitch     -10-10
diff --git a/Drivers/Speech/Mikropuhe/speech.c b/Drivers/Speech/Mikropuhe/speech.c
new file mode 100644
index 0000000..60b4c8f
--- /dev/null
+++ b/Drivers/Speech/Mikropuhe/speech.c
@@ -0,0 +1,504 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Mikropuhe/speech.c - Speech library
+ * For the Mikropuhe text to speech package
+ * Maintained by Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "log.h"
+#include "file.h"
+#include "parse.h"
+#include "thread.h"
+#include "queue.h"
+#include "notes.h"
+#include "pcm.h"
+#include "dynld.h"
+#include "core.h"
+
+typedef enum {
+  PARM_NAME,
+  PARM_PITCH
+} DriverParameter;
+#define SPKPARMS "name", "pitch"
+
+#include "spk_driver.h"
+#include <mpwrfile.h>
+
+#ifdef ENABLE_SHARED_OBJECTS
+static void *speechLibrary = NULL;
+#endif /* ENABLE_SHARED_OBJECTS */
+
+static MPINT_ChannelInitExType mpChannelInitEx = NULL;
+static MPINT_ChannelExitType mpChannelExit = NULL;
+static MPINT_ChannelSpeakFileType mpChannelSpeakFile = NULL;
+
+typedef struct {
+  const char *name;
+  void *address;
+} SymbolEntry;
+#define SYMBOL_ENTRY(name) {"MPINT_" #name, &mp##name}
+static const SymbolEntry symbolTable[] = {
+  SYMBOL_ENTRY(ChannelInitEx),
+  SYMBOL_ENTRY(ChannelExit),
+  SYMBOL_ENTRY(ChannelSpeakFile),
+  {NULL, NULL}
+};
+
+typedef struct {
+  int tags;
+  int length;
+} SpeechSegment;
+
+static Queue *speechQueue = NULL;
+static pthread_mutex_t speechMutex;
+static pthread_cond_t speechConditional;
+
+static int synthesisThreadStarted = 0;
+static pthread_t synthesisThread;
+
+static void *speechChannel = NULL;
+static MPINT_SpeakFileParams speechParameters;
+static PcmDevice *pcm = NULL;
+
+static int
+openSoundDevice (void) {
+  if (!pcm) {
+    if (!(pcm = openPcmDevice(LOG_WARNING, opt_pcmDevice))) return 0;
+
+    speechParameters.nChannels = setPcmChannelCount(pcm, 1);
+    speechParameters.nSampleFreq = setPcmSampleRate(pcm, 22050);
+    {
+      typedef struct {
+        PcmAmplitudeFormat internal;
+        int external;
+      } FormatEntry;
+      static const FormatEntry formatTable[] = {
+        {PCM_FMT_S16L   , 16},
+        {PCM_FMT_U8     ,  8},
+        {PCM_FMT_UNKNOWN,  0}
+      };
+      const FormatEntry *format = formatTable;
+      while (format->internal != PCM_FMT_UNKNOWN) {
+        if (setPcmAmplitudeFormat(pcm, format->internal) == format->internal) break;
+        ++format;
+      }
+      if (format->internal == PCM_FMT_UNKNOWN) {
+        logMessage(LOG_WARNING, "No supported sound format.");
+        closePcmDevice(pcm);
+        pcm = NULL;
+        return 0;
+      }
+      speechParameters.nBits = format->external;
+    }
+    logMessage(LOG_DEBUG, "Mikropuhe audio configuration: channels=%d rate=%d bits=%d",
+               speechParameters.nChannels, speechParameters.nSampleFreq, speechParameters.nBits);
+  }
+  return 1;
+}
+
+static void
+closeSoundDevice (void) {
+  if (pcm) {
+    closePcmDevice(pcm);
+    pcm = NULL;
+  }
+}
+
+static SpeechSegment *
+allocateSpeechSegment (const unsigned char *bytes, int length, int tags) {
+  SpeechSegment *segment;
+  if ((segment = malloc(sizeof(*segment) + length))) {
+    memcpy(segment+1, bytes, length);
+    segment->length = length;
+    segment->tags = tags;
+  }
+  return segment;
+}
+
+static void
+deallocateSpeechSegment (SpeechSegment *segment) {
+  free(segment);
+}
+
+static void
+deallocateSpeechItem (void *item, void *data) {
+  deallocateSpeechSegment(item);
+}
+
+static void
+logSynthesisError (int code, const char *action) {
+  const char *explanation;
+  switch (code) {
+    default:
+      explanation = "unknown";
+      break;
+    case MPINT_ERR_GENERAL:
+      explanation = "general";
+      break;
+    case MPINT_ERR_SYNTH:
+      explanation = "text synthesis";
+      break;
+    case MPINT_ERR_MEM:
+      explanation = "insufficient memory";
+      break;
+    case MPINT_ERR_DESTFILEOPEN:
+      explanation = "file open";
+      break;
+    case MPINT_ERR_DESTFILEWRITE:
+      explanation = "file write";
+      break;
+    case MPINT_ERR_EINVAL:
+      explanation = "parameter";
+      break;
+    case MPINT_ERR_INITBADKEY:
+      explanation = "invalid key";
+      break;
+    case MPINT_ERR_INITNOVOICES:
+      explanation = "no voices";
+      break;
+    case MPINT_ERR_INITVOICEFAIL:
+      explanation = "voice load";
+      break;
+    case MPINT_ERR_INITTEXTPARSE:
+      explanation = "text parser load";
+      break;
+    case MPINT_ERR_SOUNDCARD:
+      explanation = "sound device";
+      break;
+  }
+  logMessage(LOG_ERR, "Mikropuhe %s error: %s", action, explanation);
+}
+
+static int
+writeSound (const void *bytes, unsigned int count, void *data, void *reserved) {
+  if (synthesisThreadStarted) {
+    if (writePcmData(pcm, bytes, count)) return 0;
+    logSystemError("Mikropuhe write");
+    return MPINT_ERR_GENERAL;
+  }
+  return 1;
+}
+
+static int
+synthesizeSpeech (const unsigned char *bytes, int length, int tags) {
+  speechParameters.nTags = tags;
+  if (mpChannelSpeakFile) {
+    int error;
+    char string[length+1];
+    memcpy(string, bytes, length);
+    string[length] = 0;
+    if (!(error = mpChannelSpeakFile(speechChannel, string, NULL, &speechParameters))) return 1;
+    if (error != 1) logSynthesisError(error, "channel speak");
+  }
+  return 0;
+}
+
+static void
+synthesizeSpeechSegment (const SpeechSegment *segment) {
+  synthesizeSpeech((unsigned char *)(segment+1), segment->length, segment->tags);
+}
+
+static void
+synthesizeSpeechSegments (void) {
+  SpeechSegment *segment;
+  while ((segment = dequeueItem(speechQueue))) {
+    if (segment->tags) {
+      synthesizeSpeechSegment(segment);
+    } else if (synthesisThreadStarted && openSoundDevice()) {
+      pthread_mutex_unlock(&speechMutex);
+      synthesizeSpeechSegment(segment);
+      pthread_mutex_lock(&speechMutex);
+    }
+    deallocateSpeechSegment(segment);
+  }
+}
+
+static int
+awaitSpeechSegment (void) {
+  while (synthesisThreadStarted) {
+    int error;
+
+    if (pcm) {
+      struct timeval now;
+      struct timespec timeout;
+      gettimeofday(&now, NULL);
+      timeout.tv_sec = now.tv_sec + 3;
+      timeout.tv_nsec = now.tv_usec * 1000;
+      error = pthread_cond_timedwait(&speechConditional, &speechMutex, &timeout);
+    } else {
+      error = pthread_cond_wait(&speechConditional, &speechMutex);
+    }
+
+    switch (error) {
+      case 0:
+        return 1;
+
+      case ETIMEDOUT:
+        closeSoundDevice();
+        continue;
+
+      default:
+        logSystemError("pthread_cond_timedwait");
+        return 0;
+    }
+  }
+  return 0;
+}
+
+THREAD_FUNCTION(mpSpeechSynthesisThread) {
+  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+  pthread_mutex_lock(&speechMutex);
+
+  while (synthesisThreadStarted) {
+    synthesizeSpeechSegments();
+    awaitSpeechSegment();
+  }
+
+  pthread_mutex_unlock(&speechMutex);
+  return NULL;
+}
+
+static int
+startSynthesisThread (void) {
+  int error;
+  if (synthesisThreadStarted) return 1;
+
+  synthesisThreadStarted = 1;
+  if (!(error = pthread_mutex_init(&speechMutex, NULL))) {
+    if (!(error = pthread_cond_init(&speechConditional, NULL))) {
+      pthread_attr_t attributes;
+      if (!(error = pthread_attr_init(&attributes))) {
+        pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_JOINABLE);
+        error = createThread("driver-speech-Mikropuhe",
+                                  &synthesisThread, &attributes,
+                                  mpSpeechSynthesisThread, NULL);
+        pthread_attr_destroy(&attributes);
+        if (!error) {
+          return 1;
+        } else {
+          logMessage(LOG_ERR, "Cannot create speech thread: %s", strerror(error));
+        }
+      } else {
+        logMessage(LOG_ERR, "Cannot initialize speech thread attributes: %s", strerror(error));
+      }
+
+      pthread_cond_destroy(&speechConditional);
+    } else {
+      logMessage(LOG_ERR, "Cannot initialize speech conditional: %s", strerror(error));
+    }
+
+    pthread_mutex_destroy(&speechMutex);
+  } else {
+    logMessage(LOG_ERR, "Cannot initialize speech mutex: %s", strerror(error));
+  }
+  synthesisThreadStarted = 0;
+  return 0;
+}
+
+static void
+stopSynthesisThread (void) {
+  if (synthesisThreadStarted) {
+    synthesisThreadStarted = 0;
+    pthread_mutex_lock(&speechMutex);
+    pthread_cond_signal(&speechConditional);
+    pthread_mutex_unlock(&speechMutex);
+    pthread_join(synthesisThread, NULL);
+    pthread_cond_destroy(&speechConditional);
+    pthread_mutex_destroy(&speechMutex);
+  }
+}
+
+static int
+enqueueSpeech (const unsigned char *bytes, int length, int tags) {
+  if (startSynthesisThread()) {
+    SpeechSegment *segment;
+    if ((segment = allocateSpeechSegment(bytes, length, tags))) {
+      Element *element;
+      pthread_mutex_lock(&speechMutex);
+      element = enqueueItem(speechQueue, segment);
+      if (element) {
+        pthread_cond_signal(&speechConditional);
+        pthread_mutex_unlock(&speechMutex);
+        return 1;
+      }
+      pthread_mutex_unlock(&speechMutex);
+      deallocateSpeechSegment(segment);
+    }
+  }
+  return 0;
+}
+
+static int
+enqueueText (const unsigned char *bytes, int length) {
+  return enqueueSpeech(bytes, length, 0);
+}
+
+static int
+enqueueTag (const char *tag) {
+  return enqueueSpeech((unsigned char *)tag, strlen(tag), MPINT_TAGS_OWN|MPINT_TAGS_SAPI5);
+}
+
+static void
+loadSynthesisLibrary (void) {
+#ifdef ENABLE_SHARED_OBJECTS
+  if (!speechLibrary) {
+    static const char *name = "libmplinux." LIBRARY_EXTENSION;
+    char *path = makePath(MIKROPUHE_ROOT, name);
+    if (path) {
+      if ((speechLibrary = loadSharedObject(path))) {
+        const SymbolEntry *symbol = symbolTable;
+        while (symbol->name) {
+          void **address = symbol->address;
+          if (findSharedSymbol(speechLibrary, symbol->name, address)) {
+            logMessage(LOG_DEBUG, "Mikropuhe symbol: %s -> %p",
+                       symbol->name, *address);
+          } else {
+            logMessage(LOG_ERR, "Mikropuhe symbol not found: %s", symbol->name);
+          }
+          ++symbol;
+        }
+      } else {
+        logMessage(LOG_ERR, "Mikropuhe library not loaded: %s", path);
+      }
+      free(path);
+    }
+  }
+#endif /* ENABLE_SHARED_OBJECTS */
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  if (enqueueText(buffer, length))
+    enqueueTag("<break time=\"none\"/>");
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  stopSynthesisThread();
+  if (pcm) cancelPcmOutput(pcm);
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  char tag[0X40];
+  unsigned int percentage = getIntegerSpeechVolume(setting, 100);
+  snprintf(tag, sizeof(tag), "<volume level=\"%d\"/>",
+           MAX(percentage, 1));
+  enqueueTag(tag);
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  char tag[0X40];
+  snprintf(tag, sizeof(tag), "<rate absspeed=\"%d\"/>",
+           getIntegerSpeechRate(setting, 10)-10);
+  enqueueTag(tag);
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  int code;
+
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+
+  loadSynthesisLibrary();
+
+  if ((speechQueue = newQueue(deallocateSpeechItem, NULL))) {
+    if (mpChannelInitEx) {
+      if (!(code = mpChannelInitEx(&speechChannel, NULL, NULL, NULL))) {
+        memset(&speechParameters, 0, sizeof(speechParameters));
+        speechParameters.nWriteWavHeader = 0;
+        speechParameters.pfnWrite = writeSound;
+        speechParameters.pWriteData = NULL;
+
+        {
+          const char *name = parameters[PARM_NAME];
+          if (name && *name) {
+            char tag[0X100];
+            snprintf(tag, sizeof(tag), "<voice name=\"%s\"/>", name);
+            enqueueTag(tag);
+          }
+        }
+
+        {
+          const char *pitch = parameters[PARM_PITCH];
+          if (pitch && *pitch) {
+            int setting = 0;
+            static const int minimum = -10;
+            static const int maximum = 10;
+            if (validateInteger(&setting, pitch, &minimum, &maximum)) {
+              char tag[0X100];
+              snprintf(tag, sizeof(tag), "<pitch absmiddle=\"%d\"/>", setting);
+              enqueueTag(tag);
+            } else {
+              logMessage(LOG_WARNING, "%s: %s", "invalid pitch specification", pitch);
+            }
+          }
+        }
+
+        return 1;
+      } else {
+        logSynthesisError(code, "channel initialization");
+      }
+    }
+  } else {
+    logMessage(LOG_ERR, "Cannot allocate speech queue.");
+  }
+
+  spk_destruct(spk);
+  return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  stopSynthesisThread();
+  closeSoundDevice();
+
+  if (speechQueue) {
+    deallocateQueue(speechQueue);
+    speechQueue = NULL;
+  }
+
+  if (speechChannel) {
+    if (mpChannelExit) mpChannelExit(speechChannel, NULL, 0);
+    speechChannel = NULL;
+  }
+
+#ifdef ENABLE_SHARED_OBJECTS
+  if (speechLibrary) {
+    const SymbolEntry *symbol = symbolTable;
+    while (symbol->name) {
+      void **address = (symbol++)->address;
+      *address = NULL;
+    }
+
+    unloadSharedObject(speechLibrary);
+    speechLibrary = NULL;
+  }
+#endif /* ENABLE_SHARED_OBJECTS */
+}
diff --git a/Drivers/Speech/MultiBraille/Makefile.in b/Drivers/Speech/MultiBraille/Makefile.in
new file mode 100644
index 0000000..325298f
--- /dev/null
+++ b/Drivers/Speech/MultiBraille/Makefile.in
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = mb
+DRIVER_NAME = MultiBraille
+DRIVER_USAGE = braille device
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/MultiBraille/speech.c b/Drivers/Speech/MultiBraille/speech.c
new file mode 100644
index 0000000..1b146a5
--- /dev/null
+++ b/Drivers/Speech/MultiBraille/speech.c
@@ -0,0 +1,104 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "spk_driver.h"
+#include "speech.h"		/* for speech definitions */
+#include "Drivers/Braille/MultiBraille/braille.h"
+
+/* charset conversion table from iso latin-1 == iso 8859-1 to cp437==ibmpc
+ * for chars >=128. 
+ */
+static unsigned char latin2cp437[0X80] =
+  {199, 252, 233, 226, 228, 224, 229, 231,
+   234, 235, 232, 239, 238, 236, 196, 197,
+   201, 181, 198, 244, 247, 242, 251, 249,
+   223, 214, 220, 243, 183, 209, 158, 159,
+   255, 173, 155, 156, 177, 157, 188, 21,
+   191, 169, 166, 174, 170, 237, 189, 187,
+   248, 241, 253, 179, 180, 230, 20, 250,
+   184, 185, 167, 175, 172, 171, 190, 168,
+   192, 193, 194, 195, 142, 143, 146, 128,
+   200, 144, 202, 203, 204, 205, 206, 207,
+   208, 165, 210, 211, 212, 213, 153, 215,
+   216, 217, 218, 219, 154, 221, 222, 225,
+   133, 160, 131, 227, 132, 134, 145, 135,
+   138, 130, 136, 137, 141, 161, 140, 139,
+   240, 164, 149, 162, 147, 245, 148, 246,
+   176, 151, 163, 150, 129, 178, 254, 152};
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters)
+{
+  return 1;
+}
+
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t len, size_t count, const unsigned char *attributes)
+{
+  unsigned char *pre_speech = (unsigned char *)PRE_SPEECH;
+  unsigned char *post_speech = (unsigned char *)POST_SPEECH;
+  unsigned char c;
+  int i;
+
+  if (pre_speech[0])
+    {
+      serialWriteData (MB_serialDevice, pre_speech+1, pre_speech[0]);
+    }
+  for (i = 0; i < len; i++)
+    {
+      c = buffer[i];
+      if (c >= 0X80) c = latin2cp437[c-0X80];
+      if (c < 33)	/* space or control character */
+	{
+	  static const char blank = ' ';
+	  serialWriteData (MB_serialDevice, &blank, 1);
+	}
+      else if (c > MAX_TRANS)
+	serialWriteData (MB_serialDevice, &c, 1);
+      else
+	{
+          const char *word = vocab[c - 33];
+	  serialWriteData (MB_serialDevice, word, strlen (word));
+	}
+    }
+  if (post_speech[0])
+    {
+      serialWriteData (MB_serialDevice, post_speech+1, post_speech[0]);
+    }
+}
+
+
+static void
+spk_mute (SpeechSynthesizer *spk)
+{
+  unsigned char *mute_seq = (unsigned char *)MUTE_SEQ;
+  serialWriteData (MB_serialDevice, mute_seq+1, mute_seq[0]);
+}
+
+
+static void
+spk_destruct (SpeechSynthesizer *spk)
+{
+}
diff --git a/Drivers/Speech/MultiBraille/speech.h b/Drivers/Speech/MultiBraille/speech.h
new file mode 100644
index 0000000..2e0fb3f
--- /dev/null
+++ b/Drivers/Speech/MultiBraille/speech.h
@@ -0,0 +1,132 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* These sequences are sent to the CombiBraille before and after the
+ * speech data itself.  The first byte is the length, so embedded nuls are
+ * allowed.
+ */
+#define PRE_SPEECH "\002\033S"
+#define POST_SPEECH "\001\015"
+
+/* This is sent to mute the speech.  The format is the same as above. */
+#define MUTE_SEQ "\003\033S\030"
+
+/* The speech data itself is translated character by character.  If a
+ * character is less than 33 (i.e. space or control), it is replaced by a
+ * space.  If the character is more than MAX_TRANS, it is passed through
+ * as is.  Otherwise the character n is replaced by the string vocab[n - 33].
+ */
+
+#define MAX_TRANS 126
+static const char *vocab[MAX_TRANS - 32] =
+{
+  " exclamation ",
+  " double quote ",
+  " hash ",
+  " dollar ",
+  " percent ",
+  " ampersand ",
+  " quote ",
+  " left paren ",
+  " right paren ",
+  " star ",
+  " plus ",
+  " comma ",
+  " dash ",
+  " dot ",
+  " slash ",
+  "0",
+  "1",
+  "2",
+  "3",
+  "4",
+  "5",
+  "6",
+  "7",
+  "8",
+  "9",
+  " colon ",
+  " semicolon ",
+  " less than ",
+  " equals ",
+  " greater than ",
+  " question ",
+  " at ",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  " left bracket ",
+  " backslash ",
+  " right bracket ",
+  " uparrow ",
+  " underline ",
+  " accent ",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  " left brace ",
+  " bar ",
+  " right brace ",
+  " tilde "
+};
diff --git a/Drivers/Speech/Skeleton/Makefile.in b/Drivers/Speech/Skeleton/Makefile.in
new file mode 100644
index 0000000..b37fc28
--- /dev/null
+++ b/Drivers/Speech/Skeleton/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = xx
+DRIVER_NAME = DirectoryName
+DRIVER_USAGE = basic example
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_xx@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Skeleton/speech.c b/Drivers/Speech/Skeleton/speech.c
new file mode 100644
index 0000000..b1851e7
--- /dev/null
+++ b/Drivers/Speech/Skeleton/speech.c
@@ -0,0 +1,61 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "spk_driver.h"
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+}
+
+static void
+spk_setPitch (SpeechSynthesizer *spk, unsigned char setting) {
+}
+
+static void
+spk_setPunctuation (SpeechSynthesizer *spk, SpeechPunctuation setting) {
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+  spk->setPitch = spk_setPitch;
+  spk->setPunctuation = spk_setPunctuation;
+
+  return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+}
diff --git a/Drivers/Speech/SpeechDispatcher/Makefile.in b/Drivers/Speech/SpeechDispatcher/Makefile.in
new file mode 100644
index 0000000..68fd9a2
--- /dev/null
+++ b/Drivers/Speech/SpeechDispatcher/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = sd
+DRIVER_NAME = SpeechDispatcher
+DRIVER_USAGE = multi-engine server
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = $(SPEECHD_LIBS)
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) $(SPEECHD_INCLUDES) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/SpeechDispatcher/README b/Drivers/Speech/SpeechDispatcher/README
new file mode 100644
index 0000000..dab2275
--- /dev/null
+++ b/Drivers/Speech/SpeechDispatcher/README
@@ -0,0 +1,25 @@
+This directory contains the BRLTTY speech driver for the SpeechDispatcher text
+to speech server [http://www.freebsoft.org/speechd]. It was implemented, and is
+being maintained, by Dave Mielke <dave@mielke.cc>.
+
+BRLTTY's configure script automatically includes this driver if Speech
+Dispatcher has been installed. The default is to check the directories /usr,
+/usr/local, /usr/local/speech-dispatcher, /usr/local/speechd,
+/opt/speech-dispatcher, and /opt/speechd. The actual location can be explicitly
+specified via --with-speechd.
+
+If this driver is built as a dynamically loaded shared object (the default),
+i.e. it isn't linked into BRLTTY's binary via --with-speech-driver, then the
+directory $SPEECHD_HOME/lib must be added to the colon-delimited list of
+directories in the LD_LIBRARY_PATH environment variable before BRLTTY is
+started because Speech Dispatcher's own shared objects don't contain run-time
+search paths for their internal dependencies.
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   port      1-65535
+   module    name (flite, festival, epos-generic, dtk-generic, ...)
+   language  two-letter language code
+   voice     type (male1, female1, male2, female2, male3, female3,
+                   child_male, child_female)
diff --git a/Drivers/Speech/SpeechDispatcher/speech.c b/Drivers/Speech/SpeechDispatcher/speech.c
new file mode 100644
index 0000000..9d3f6f9
--- /dev/null
+++ b/Drivers/Speech/SpeechDispatcher/speech.c
@@ -0,0 +1,293 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+  PARM_PORT,
+  PARM_MODULE,
+  PARM_LANGUAGE,
+  PARM_VOICE,
+  PARM_NAME
+} DriverParameter;
+#define SPKPARMS "port", "module", "language", "voice", "name"
+
+#include "spk_driver.h"
+
+#include <libspeechd.h>
+
+static SPDConnection *connectionHandle = NULL;
+static const char *moduleName;
+static const char *languageName;
+static SPDVoiceType voiceType;
+static const char *voiceName;
+static signed int relativeVolume;
+static signed int relativeRate;
+static signed int relativePitch;
+static SPDPunctuation punctuationVerbosity;
+
+static void
+clearSettings (void) {
+  moduleName = NULL;
+  languageName = NULL;
+  voiceType = -1;
+  voiceName = NULL;
+  relativeVolume = 0;
+  relativeRate = 0;
+  relativePitch = 0;
+  punctuationVerbosity = -1;
+}
+
+static void
+closeConnection (void) {
+  if (connectionHandle) {
+    spd_close(connectionHandle);
+    connectionHandle = NULL;
+  }
+}
+
+typedef void (*SpeechdAction) (const void *data);
+
+static void
+speechdAction (SpeechdAction action, const void *data) {
+  if (connectionHandle) {
+    action(data);
+    if (!connectionHandle->stream) closeConnection();
+  }
+}
+
+static void
+setModule (const void *data) {
+  if (moduleName) spd_set_output_module(connectionHandle, moduleName);
+}
+
+static void
+setLanguage (const void *data) {
+  if (languageName) spd_set_language(connectionHandle, languageName);
+}
+
+static void
+setVoiceType (const void *data) {
+  if (voiceType != -1) spd_set_voice_type(connectionHandle, voiceType);
+}
+
+static void
+setVoiceName (const void *data) {
+  if (voiceName) spd_set_synthesis_voice(connectionHandle, voiceName);
+}
+
+static void
+setVolume (const void *data) {
+  spd_set_volume(connectionHandle, relativeVolume);
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  relativeVolume = getIntegerSpeechVolume(setting, 100) - 100;
+  speechdAction(setVolume, NULL);
+  logMessage(LOG_DEBUG, "set volume: %u -> %d", setting, relativeVolume);
+}
+
+static void
+setRate (const void *data) {
+  spd_set_voice_rate(connectionHandle, relativeRate);
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  relativeRate = getIntegerSpeechRate(setting, 100) - 100;
+  speechdAction(setRate, NULL);
+  logMessage(LOG_DEBUG, "set rate: %u -> %d", setting, relativeRate);
+}
+
+static void
+setPitch (const void *data) {
+  spd_set_voice_pitch(connectionHandle, relativePitch);
+}
+
+static void
+spk_setPitch (SpeechSynthesizer *spk, unsigned char setting) {
+  relativePitch = getIntegerSpeechPitch(setting, 100) - 100;
+  speechdAction(setPitch, NULL);
+  logMessage(LOG_DEBUG, "set pitch: %u -> %d", setting, relativePitch);
+}
+
+static void
+setPunctuation (const void *data) {
+  if (punctuationVerbosity != -1) spd_set_punctuation(connectionHandle, punctuationVerbosity);
+}
+
+static void
+spk_setPunctuation (SpeechSynthesizer *spk, SpeechPunctuation setting) {
+  punctuationVerbosity = (setting <= SPK_PUNCTUATION_NONE)? SPD_PUNCT_NONE: 
+                         (setting >= SPK_PUNCTUATION_ALL)? SPD_PUNCT_ALL: 
+                         SPD_PUNCT_SOME;
+  speechdAction(setPunctuation, NULL);
+  logMessage(LOG_DEBUG, "set punctuation: %u -> %d", setting, punctuationVerbosity);
+}
+
+static void
+cancelSpeech (const void *data) {
+  spd_cancel(connectionHandle);
+}
+
+static int
+openConnection (void) {
+  if (!connectionHandle) {
+    if (!(connectionHandle = spd_open("brltty", "main", NULL, SPD_MODE_THREADED))) {
+      logMessage(LOG_ERR, "speech dispatcher open failure");
+      return 0;
+    }
+
+    {
+      static const SpeechdAction actions[] = {
+        setModule,
+        setLanguage,
+        setVoiceType,
+        setVoiceName,
+        setVolume,
+        setRate,
+        setPitch,
+        setPunctuation,
+        NULL
+      };
+      const SpeechdAction *action = actions;
+      while (*action) speechdAction(*action++, NULL);
+    }
+  }
+
+  return 1;
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+  spk->setPitch = spk_setPitch;
+  spk->setPunctuation = spk_setPunctuation;
+
+  clearSettings();
+
+  if (parameters[PARM_PORT] && *parameters[PARM_PORT]) {
+    static const int minimumPort = 0X1;
+    static const int maximumPort = 0XFFFF;
+    int port = 0;
+
+    if (validateInteger(&port, parameters[PARM_PORT], &minimumPort, &maximumPort)) {
+      char number[0X10];
+      snprintf(number, sizeof(number), "%d", port);
+      setenv("SPEECHD_PORT", number, 1);
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid port number", parameters[PARM_PORT]);
+    }
+  }
+
+  if (parameters[PARM_MODULE] && *parameters[PARM_MODULE]) {
+    moduleName = parameters[PARM_MODULE];
+  }
+
+  if (parameters[PARM_LANGUAGE] && *parameters[PARM_LANGUAGE]) {
+    languageName = parameters[PARM_LANGUAGE];
+  }
+
+  if (parameters[PARM_VOICE] && *parameters[PARM_VOICE]) {
+    static const SPDVoiceType voices[] = {
+      SPD_MALE1, SPD_FEMALE1,
+      SPD_MALE2, SPD_FEMALE2,
+      SPD_MALE3, SPD_FEMALE3,
+      SPD_CHILD_MALE, SPD_CHILD_FEMALE
+    };
+
+    static const char *choices[] = {
+      "male1", "female1",
+      "male2", "female2",
+      "male3", "female3",
+      "child_male", "child_female",
+      NULL
+    };
+
+    unsigned int choice = 0;
+
+    if (validateChoice(&choice, parameters[PARM_VOICE], choices)) {
+      voiceType = voices[choice];
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid voice type", parameters[PARM_VOICE]);
+    }
+  }
+
+  if (parameters[PARM_NAME] && *parameters[PARM_NAME]) {
+    voiceName = parameters[PARM_NAME];
+  }
+
+  return openConnection();
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  closeConnection();
+  clearSettings();
+}
+
+typedef struct {
+  const unsigned char *text;
+  size_t length;
+  size_t count;
+  SPDPriority priority;
+} SayData;
+
+static void
+sayText (const void *data) {
+  const SayData *say = data;
+
+  if (say->count == 1) {
+    char string[say->length + 1];
+    memcpy(string, say->text, say->length);
+    string[say->length] = 0;
+    spd_char(connectionHandle, say->priority, string);
+  } else {
+    spd_sayf(connectionHandle, say->priority, "%.*s", (int)say->length, say->text);
+  }
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes) {
+  const SayData say = {
+    .text = text,
+    .length = length,
+    .count = count,
+    .priority = SPD_MESSAGE
+  };
+
+  speechdAction(sayText, &say);
+  if (!connectionHandle) {
+    if (openConnection()) {
+      speechdAction(sayText, &say);
+    }
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  speechdAction(cancelSpeech, NULL);
+}
diff --git a/Drivers/Speech/Swift/Makefile.in b/Drivers/Speech/Swift/Makefile.in
new file mode 100644
index 0000000..8ac3bfb
--- /dev/null
+++ b/Drivers/Speech/Swift/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = sw
+DRIVER_NAME = Swift
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_sw@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -I$(SWIFT_ROOT)/include -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Swift/README b/Drivers/Speech/Swift/README
new file mode 100644
index 0000000..f619cd1
--- /dev/null
+++ b/Drivers/Speech/Swift/README
@@ -0,0 +1,17 @@
+This directory contains the BRLTTY speech driver for the Swift text to speech
+engine from Cepstral [http://www.cepstral.com/]. It was implemented, and is
+being maintained, by Dave Mielke <dave@mielke.cc>.
+
+BRLTTY's configure script automatically includes this driver if Swift has been
+installed. The default is to check the directories /usr, /usr/local,
+/usr/local/Swift, /usr/local/swift, /opt/Swift, and /opt/swift. The actual
+location can be explicitly specified via --with-swift.
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   name      name (human), /path/to/directory
+
+If the human name of the voice is specified then it is interpreted as a Swift
+voice directory if it begins with a slash and as a subdirectory of
+$SWIFT_HOME/voices/ if it doesn't.
diff --git a/Drivers/Speech/Swift/speech.c b/Drivers/Speech/Swift/speech.c
new file mode 100644
index 0000000..d4724bb
--- /dev/null
+++ b/Drivers/Speech/Swift/speech.c
@@ -0,0 +1,189 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Swift/speech.c - Speech library
+ * For the Swift text to speech package
+ * Maintained by Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "log.h"
+
+typedef enum {
+  PARM_NAME
+} DriverParameter;
+#define SPKPARMS "name"
+
+#include "spk_driver.h"
+#include <swift.h>
+
+static swift_engine *swiftEngine = NULL;
+static swift_port *swiftPort = NULL;
+
+static void
+speechError (swift_result_t result, const char *action) {
+  logMessage(LOG_ERR, "Swift %s error: %s", action, swift_strerror(result));
+}
+
+static int
+setPortParameter (const char *name, swift_val *value) {
+  swift_result_t result = swift_port_set_param(swiftPort, name, value, SWIFT_ASYNC_NONE);
+  if (result == SWIFT_SUCCESS) return 1;
+  speechError(result, "parameter set");
+  return 0;
+}
+
+static int
+setStringParameter (const char *name, const char *value) {
+  logMessage(LOG_DEBUG, "setting swift string parameter: %s=%s", name, value);
+  if (setPortParameter(name, swift_val_string(value))) return 1;
+  logMessage(LOG_WARNING, "Couldn't set %s=%s", name, value);
+  return 0;
+}
+
+static int
+setIntegerParameter (const char *name, int value) {
+  logMessage(LOG_DEBUG, "setting swift integer parameter: %s=%d", name, value);
+  if (setPortParameter(name, swift_val_int(value))) return 1;
+  logMessage(LOG_WARNING, "Couldn't set %s=%d", name, value);
+  return 0;
+}
+
+static int
+setVolume (int percentage) {
+  return setIntegerParameter("audio/volume", percentage);
+}
+
+static int
+setRate (int wordsPerMinute) {
+  return setIntegerParameter("speech/rate", wordsPerMinute);
+}
+
+static int
+setEnvironmentVariable (const char *name, const char *value) {
+  logMessage(LOG_DEBUG, "setting swift environment variable: %s=%s", name, value);
+  if (setenv(name, value, 1) != -1) return 1;
+  logSystemError("environment variable set");
+  return 0;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  swift_result_t result;
+  swift_background_t job;
+
+  if ((result = swift_port_speak_text(swiftPort, buffer, length, NULL, &job, NULL)) != SWIFT_SUCCESS) {
+    speechError(result, "port speak");
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  swift_result_t result;
+
+  if ((result = swift_port_stop(swiftPort, SWIFT_ASYNC_CURRENT, SWIFT_EVENT_NOW)) != SWIFT_SUCCESS) {
+  //speechError(result, "port stop");
+  }
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  setVolume(getIntegerSpeechVolume(setting, 100));
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  setRate((int)(getFloatSpeechRate(setting) * 170.0));
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  swift_result_t result;
+
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+
+  if (setEnvironmentVariable("SWIFT_HOME", SWIFT_ROOT)) {
+    swift_params *engineParameters;
+
+    if ((engineParameters = swift_params_new(NULL))) {
+      if ((swiftEngine = swift_engine_open(engineParameters))) {
+        if ((swiftPort = swift_port_open(swiftEngine, NULL))) {
+          {
+            const char *name = parameters[PARM_NAME];
+            if (name && *name) {
+              swift_voice *voice;
+              if (*name == '/') {
+                logMessage(LOG_DEBUG, "setting swift voice directory: %s", name);
+                voice = swift_port_set_voice_from_dir(swiftPort, name);
+              } else {
+                logMessage(LOG_DEBUG, "setting swift voice name: %s", name);
+                voice = swift_port_set_voice_by_name(swiftPort, name);
+              }
+
+              if (!voice) {
+                 logMessage(LOG_WARNING, "Swift voice set error: %s", name);
+              }
+            }
+          }
+
+          setStringParameter("tts/content-type", "text/plain");
+
+          logMessage(LOG_INFO, "Swift Engine: %s for %s, version %s, %s",
+                     swift_engine_name, swift_platform, swift_version, swift_date);
+          return 1;
+        } else {
+          logMessage(LOG_ERR, "Swift port open error.");
+        }
+
+        if ((result = swift_engine_close(swiftEngine)) != SWIFT_SUCCESS) {
+          speechError(result, "engine close");
+        }
+        swiftEngine = NULL;
+      } else {
+        logMessage(LOG_ERR, "Swift engine open error.");
+      }
+    } else {
+      logMessage(LOG_ERR, "Swift engine parameters allocation error.");
+    }
+  }
+
+  return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  swift_result_t result;
+
+  if (swiftPort) {
+    if ((result = swift_port_close(swiftPort)) != SWIFT_SUCCESS) {
+      speechError(result, "port close");
+    }
+    swiftPort = NULL;
+  }
+
+  if (swiftEngine) {
+    if ((result = swift_engine_close(swiftEngine)) != SWIFT_SUCCESS) {
+      speechError(result, "engine close");
+    }
+    swiftEngine = NULL;
+  }
+}
diff --git a/Drivers/Speech/Theta/Makefile.in b/Drivers/Speech/Theta/Makefile.in
new file mode 100644
index 0000000..c9f7818
--- /dev/null
+++ b/Drivers/Speech/Theta/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = th
+DRIVER_NAME = Theta
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_th@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CXX) $(SPK_CXXFLAGS) -I$(THETA_ROOT)/include -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/Theta/README b/Drivers/Speech/Theta/README
new file mode 100644
index 0000000..0dec93c
--- /dev/null
+++ b/Drivers/Speech/Theta/README
@@ -0,0 +1,33 @@
+This directory contains the BRLTTY speech driver for the Theta text to speech
+engine from Cepstral [http://www.cepstral.com/]. It was implemented, and is
+being maintained, by Dave Mielke <dave@mielke.cc>.
+
+BRLTTY's configure script automatically includes this driver if Theta has been
+installed. The default is to check the directories /usr, /usr/local,
+/usr/local/Theta, /usr/local/theta, /opt/Theta, and /opt/theta. The actual
+location can be explicitly specified via --with-theta.
+
+This driver and that for the FestivalLite text to speech engine cannot be
+linked into BRLTTY's binary at the same time (via --enable-speech-driver)
+because their run-time libraries contain conflicting symbols.
+
+If this driver is built as a dynamically loaded shared object (the default),
+i.e. it isn't linked into BRLTTY's binary via --with-speech-driver, then the
+directory $THETA_HOME/lib must be added to the colon-delimited list of
+directories in the LD_LIBRARY_PATH environment variable before BRLTTY is
+started because Theta's own shared objects don't contain run-time search paths
+for their internal dependencies.
+
+This driver recognizes the following parameters:
+
+   Parameter Settings
+   age       1-99 (years)
+   gender    male, female, neuter
+   language  two-letter language code
+   name      name (human), /path/to/directory
+   pitch     -2.0-2.0 (octaves relative to default)
+
+If the human name of the voice is specified then the voice path is searched. 
+This path can be explicitly defined by storing a colon-delimited list of
+directories in the THETA_VOXPATH environment variable. The default is to look
+in $THETA_HOME/voices.
diff --git a/Drivers/Speech/Theta/speech.c b/Drivers/Speech/Theta/speech.c
new file mode 100644
index 0000000..97c3e99
--- /dev/null
+++ b/Drivers/Speech/Theta/speech.c
@@ -0,0 +1,234 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Theta/speech.c - Speech library
+ * For the Theta text to speech package
+ * Maintained by Dave Mielke <dave@mielke.cc>
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+  PARM_AGE,
+  PARM_GENDER,
+  PARM_LANGUAGE,
+  PARM_NAME,
+  PARM_PITCH
+} DriverParameter;
+#define SPKPARMS "age", "gender", "language", "name", "pitch"
+
+#include "spk_driver.h"
+
+struct theta_sfx_block_ops { /* used but not defined in header */
+  char dummy;
+};
+#include <theta.h>
+
+static cst_voice *voice = NULL;
+static pid_t child = -1;
+static int pipeDescriptors[2];
+static const int *pipeOutput = &pipeDescriptors[0];
+static const int *pipeInput = &pipeDescriptors[1];
+
+static void
+initializeTheta (void) {
+  static int initialized = 0;
+  if (!initialized) {
+    {
+      const char *directory = THETA_ROOT "/voices";
+      setenv("THETA_VOXPATH", directory, 0);
+    }
+
+    setenv("THETA_HOME", THETA_ROOT, 0);
+    theta_init(NULL);
+    initialized = 1;
+  }
+}
+
+static void
+loadVoice (theta_voice_desc *descriptor) {
+  if ((voice = theta_load_voice(descriptor))) {
+    logMessage(LOG_INFO, "Voice: %s(%s,%d)",
+               theta_voice_human(voice),
+               theta_voice_gender(voice),
+               theta_voice_age(voice));
+  } else {
+    logMessage(LOG_WARNING, "Voice load error: %s [%s]",
+               descriptor->human, descriptor->voxname);
+  }
+}
+
+static int
+doChild (void) {
+  FILE *stream = fdopen(*pipeOutput, "r");
+  char buffer[0X400];
+  char *line;
+  while ((line = fgets(buffer, sizeof(buffer), stream))) {
+    theta_tts(line, voice);
+  }
+  return 0;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+  if (voice) {
+    if (child != -1) goto ready;
+
+    if (pipe(pipeDescriptors) != -1) {
+      if ((child = fork()) == -1) {
+        logSystemError("fork");
+      } else if (child == 0) {
+        _exit(doChild());
+      } else
+      ready: {
+        unsigned char text[length + 1];
+        memcpy(text, buffer, length);
+        text[length] = '\n';
+        write(*pipeInput, text, sizeof(text));
+        return;
+      }
+
+      close(*pipeInput);
+      close(*pipeOutput);
+    } else {
+      logSystemError("pipe");
+    }
+  }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+  if (child != -1) {
+    close(*pipeInput);
+    close(*pipeOutput);
+
+    kill(child, SIGKILL);
+    waitpid(child, NULL, 0);
+    child = -1;
+  }
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+  theta_set_rescale(voice, getFloatSpeechVolume(setting), NULL);
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+  theta_set_rate_stretch(voice, 1.0/getFloatSpeechRate(setting), NULL);
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  theta_voice_search criteria;
+
+  memset(&criteria, 0, sizeof(criteria));
+  initializeTheta();
+
+  spk->setVolume = spk_setVolume;
+  spk->setRate = spk_setRate;
+
+  if (*parameters[PARM_GENDER]) {
+    const char *const choices[] = {"male", "female", "neuter", NULL};
+    unsigned int choice;
+    if (validateChoice(&choice, parameters[PARM_GENDER], choices)) {
+      criteria.gender = (char *)choices[choice];
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid gender specification", parameters[PARM_GENDER]);
+    }
+  }
+
+  if (*parameters[PARM_LANGUAGE]) criteria.lang = parameters[PARM_LANGUAGE];
+
+  if (*parameters[PARM_AGE]) {
+    const char *word = parameters[PARM_AGE];
+    int value;
+    int younger;
+    static const int minimumAge = 1;
+    static const int maximumAge = 99;
+    if ((younger = word[0] == '-') && word[1]) ++word;
+    if (validateInteger(&value, word, &minimumAge, &maximumAge)) {
+      if (younger) value = -value;
+      criteria.age = value;
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", "invalid age specification", word);
+    }
+  }
+
+  {
+    const char *name = parameters[PARM_NAME];
+    if (name && (*name == '/')) {
+      theta_voice_desc *descriptor = theta_try_voxdir(name, &criteria);
+      if (descriptor) {
+        loadVoice(descriptor);
+        theta_free_voice_desc(descriptor);
+      }
+    } else {
+      theta_voice_desc *descriptors = theta_enum_voices(theta_voxpath, &criteria);
+      if (descriptors) {
+        theta_voice_desc *descriptor;
+        for (descriptor=descriptors; descriptor; descriptor=descriptor->next) {
+          if (*name)
+            if (strcasecmp(name, descriptor->human) != 0)
+              continue;
+          loadVoice(descriptor);
+          if (voice) break;
+        }
+        theta_free_voicelist(descriptors);
+      }
+    }
+  }
+
+  if (voice) {
+    {
+      float pitch = 0.0;
+      static const float minimumPitch = -2.0;
+      static const float maximumPitch = 2.0;
+      if (validateFloat(&pitch, parameters[PARM_PITCH], &minimumPitch, &maximumPitch)) {
+        theta_set_pitch_shift(voice, pitch, NULL);
+      } else {
+        logMessage(LOG_WARNING, "%s: %s", "invalid pitch shift specification", parameters[PARM_PITCH]);
+      }
+    }
+
+    logMessage(LOG_INFO, "Theta Engine: version %s", theta_version);
+    return 1;
+  }
+
+  logMessage(LOG_WARNING, "No voices found.");
+  return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+  spk_mute(spk);
+
+  if (voice) {
+    theta_unload_voice(voice);
+    voice = NULL;
+  }
+}
diff --git a/Drivers/Speech/ViaVoice/Makefile.in b/Drivers/Speech/ViaVoice/Makefile.in
new file mode 100644
index 0000000..1ddfe20
--- /dev/null
+++ b/Drivers/Speech/ViaVoice/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = vv
+DRIVER_NAME = ViaVoice
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 
+DRIVER_DEVELOPERS = 
+SPK_OBJS = @speech_libraries_vv@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/ViaVoice/README b/Drivers/Speech/ViaVoice/README
new file mode 100644
index 0000000..db246fe
--- /dev/null
+++ b/Drivers/Speech/ViaVoice/README
@@ -0,0 +1,173 @@
+This directory contains the BRLTTY speech synthesizer driver for the ViaVoice
+[http://www.ibm.com/software/speech/dev/ttssdk_linux.html] text to speech
+engine from International Business Machines (IBM) [http://www.ibm.com]. It was
+implemented, and is being maintained, by Dave Mielke <Dave@Mielke.cc>.
+
+ViaVoice was its original name. It's currently  known as Voxin
+[http://voxin.oralux.net/], and is currently being maintained by the Oralux
+Association [http://oralux.org/]. Other names by which ViaVoice has been
+known include Eloquence, Outloud, and DecTalk.
+
+This driver can be either dynamically loaded (at run time) or built directly
+into a dynamically linked BRLTTY binary (via --with-speech-driver),
+but it can't be built into a statically linked BRLTTY binary (via
+--enable-standalone-programs) because the Voxin package doesn't contain
+static archives.
+
+The sox command must be installed because this driver uses it in order to
+resample from one of the underlying speech engine's fixed set of rates (see
+the quality parameter, below) to one that's supported by the sound card.
+
+This driver recognizes the following parameters (see below for details):
+
+   Parameter       Value
+   Quality         fair, poor, good
+   Mode            words, letters, punctuation, phonetic
+   Synthesize      sentences, all
+   Abbreviations   on, off
+   Years           on, off
+   Language        dialect-language[-encoding]
+   Voice           built-in type or ViaVoice name
+   Gender          male, female
+   HeadSize        0-100
+   PitchBaseline   40-422 [Hertz]
+   Expressiveness  0-100 (pitch fluctuation)
+   Roughness       0-100
+   Breathiness     0-100
+   Volume          0-100 [percent]
+   Speed           70-1297 [words per minute]
+
+The quality parameter sets the sample rate of the generated audio:
+
+   Value   Rate
+   Poor    8000Hz
+  *Fair   11025Hz
+   Good   22050Hz
+
+The mode parameter determines how the text is spoken:
+
+  Value         Action
+  *Words        words are spoken
+   Letters      letters are spoken
+   Punctuation  letters and punctuation are spoken
+   Phonetic     letters are spoken phonetically
+
+The synthesize parameter determines how the text is synthesized:
+
+   Value      Action
+  *Sentences  each sentence is synthesized separately
+   All        the entire text is synthesized
+
+The abbreviations parameter determines how known abbreviations are spoken:
+
+   Value  Action
+  *On     abbreviations are expanded
+   Off    abbreviations aren't expanded
+
+The years parameter determines how four-digit numbers are spoken:
+
+   Value  Action            Example
+  *On     as year numbers   twenty nineteen
+   Off    as other numbers  two thousand nineteen
+
+The language parameter specifies which language is to be synthesized. The
+components of a language name are separated from one another by a dash [-].
+The first component is the dialect, the second component is the language,
+and the optional third component is the encoding (character set). Case
+doesn't matter, and each component may be individually abbreviated as
+desired. For example, ca-fr means Canadian-French. The following languages
+are supported:
+
+  *American-English
+   British-English
+   Castilian-Spanish
+   Mexican-Spanish
+   Standard-French
+   Canadian-French
+   Standard-German
+   Standard-Italian
+   Standard-Mandarin-GBK
+   Standard-Mandarin-PinYin
+   Standard-Mandarin-UCS2
+   Taiwanese-Mandarin-Big5
+   Taiwanese-Mandarin-ZhuYin
+   Taiwanese-Mandarin-PinYin
+   Taiwanese-Mandarin-UCS2
+   Brazilian-Portuguese
+   Standard-Japanese-SJIS
+   Standard-Japanese-UCS2
+   Standard-Finnish
+   Standard-Korean-UHC
+   Standard-Korean-UCS2
+   Standard-Cantonese-GBK
+   Standard-Cantonese-UCS2
+   HongKong-Cantonese-Big5
+   HongKong-Cantonese-UCS2
+   Standard-Dutch
+   Standard-Norwegian
+   Standard-Swedish
+   Standard-Danish
+   Standard-Thai-TIS620
+
+The voice parameter specifies which type of voice is to be synthesized. It
+may be either a built-in voice type or a ViaVoice voice name. The built-in
+voice types are: man (the default), woman, child, patriarch (elderly man),
+matriarch (elderly woman). The ViaVoice voice names are:
+
+   Language              Type       Name
+   American English      Man        Reed
+   American English      Woman      Shelley
+   American English      Child      Sandy
+   American English      Patriarch  Grandpa
+   American English      Matriarch  Grandma
+   British English       Man        Justin
+   British English       Woman      Jane
+   British English       Child      Nicky
+   British English       Patriarch  Gramps
+   British English       Matriarch  Nanny
+   Castilian Spanish     Man        Carlos
+   Castilian Spanish     Woman      Pilar
+   Castilian Spanish     Child      Pepe
+   Castilian Spanish     Patriarch  Abuelo
+   Castilian Spanish     Matriarch  Abuela
+   Standard French       Man        Jacques
+   Standard French       Woman      Jacqueline
+   Standard French       Child      Marius
+   Standard French       Patriarch  Grandpère
+   Standard French       Matriarch  Mamie
+   Standard German       Man        Max
+   Standard German       Woman      Gisela
+   Standard German       Child      Matti
+   Standard German       Patriarch  Opa
+   Standard German       Matriarch  Oma
+   Standard Italian      Man        Enrico
+   Standard Italian      Woman      Lucia
+   Standard Italian      Child      Chicco
+   Standard Italian      Patriarch  Nonno
+   Standard Italian      Matriarch  Nonna
+   Standard Mandarin     Man        Li3 Jing4
+   Standard Mandarin     Woman      Wang2 Yan4
+   Standard Mandarin     Child      Li3 Dong1 Dong1
+   Standard Mandarin     Patriarch  Ye2 Ye
+   Standard Mandarin     Matriarch  Nai3 Nai
+   Taiwanese Mandarin    Man        Zhi4 Ming2
+   Taiwanese Mandarin    Woman      Chun1 Jiao1
+   Taiwanese Mandarin    Child      Xiao3 Bu4 Dian3
+   Taiwanese Mandarin    Patriarch  A3 Gong1
+   Taiwanese Mandarin    Matriarch  A3 Ma4
+   Brazilian Portuguese  Man        João
+   Brazilian Portuguese  Woman      Cláudia
+   Brazilian Portuguese  Child      Chico
+   Brazilian Portuguese  Patriarch  Avô
+   Brazilian Portuguese  Matriarch  Avó
+   Standard Japanese     Man        Taroo
+   Standard Japanese     Woman      Hanako
+   Standard Japanese     Child      Jiroo
+   Standard Japanese     Patriarch  Taroo
+   Standard Japanese     Matriarch  Obaachan
+   Standard Finnish      Man        Antti
+   Standard Finnish      Woman      Tarja
+   Standard Finnish      Child      Pekka
+   Standard Finnish      Patriarch  Isoisä
+   Standard Finnish      Matriarch  Isoäiti
+
diff --git a/Drivers/Speech/ViaVoice/languages.h b/Drivers/Speech/ViaVoice/languages.h
new file mode 100644
index 0000000..70e1f96
--- /dev/null
+++ b/Drivers/Speech/ViaVoice/languages.h
@@ -0,0 +1,228 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+{ .identifier = eciGeneralAmericanEnglish,
+  .name = "American-English",
+  .language = "en",
+  .territory = "US",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciBritishEnglish,
+  .name = "British-English",
+  .language = "en",
+  .territory = "GB",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciCastilianSpanish,
+  .name = "Castilian-Spanish",
+  .language = "es",
+  .territory = "ES",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciMexicanSpanish,
+  .name = "Mexican-Spanish",
+  .language = "es",
+  .territory = "MX",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardFrench,
+  .name = "Standard-French",
+  .language = "fr",
+  .territory = "FR",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciCanadianFrench,
+  .name = "Canadian-French",
+  .language = "fr",
+  .territory = "CA",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardGerman,
+  .name = "Standard-German",
+  .language = "de",
+  .territory = "DE",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardItalian,
+  .name = "Standard-Italian",
+  .language = "it",
+  .territory = "IT",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciMandarinChineseGB,
+  .name = "Standard-Mandarin-GBK",
+  .language = "zh",
+  .territory = "CN",
+  .encoding = "GBK"
+},
+
+{ .identifier = eciMandarinChinesePinYin,
+  .name = "Standard-Mandarin-PinYin",
+  .language = "zh",
+  .territory = "CN",
+  .encoding = "GBK"
+},
+
+{ .identifier = eciMandarinChineseUCS,
+  .name = "Standard-Mandarin-UCS2",
+  .language = "zh",
+  .territory = "CN",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciTaiwaneseMandarinBig5,
+  .name = "Taiwanese-Mandarin-Big5",
+  .language = "zh",
+  .territory = "TW",
+  .encoding = "BIG5"
+},
+
+{ .identifier = eciTaiwaneseMandarinZhuYin,
+  .name = "Taiwanese-Mandarin-ZhuYin",
+  .language = "zh",
+  .territory = "TW",
+  .encoding = "BIG5"
+},
+
+{ .identifier = eciTaiwaneseMandarinPinYin,
+  .name = "Taiwanese-Mandarin-PinYin",
+  .language = "zh",
+  .territory = "TW",
+  .encoding = "BIG5"
+},
+
+{ .identifier = eciTaiwaneseMandarinUCS,
+  .name = "Taiwanese-Mandarin-UCS2",
+  .language = "zh",
+  .territory = "TW",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciBrazilianPortuguese,
+  .name = "Brazilian-Portuguese",
+  .language = "pt",
+  .territory = "BR",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardJapaneseSJIS,
+  .name = "Standard-Japanese-SJIS",
+  .language = "ja",
+  .territory = "JP",
+  .encoding = "SJIS"
+},
+
+{ .identifier = eciStandardJapaneseUCS,
+  .name = "Standard-Japanese-UCS2",
+  .language = "ja",
+  .territory = "JP",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciStandardFinnish,
+  .name = "Standard-Finnish",
+  .language = "fi",
+  .territory = "FI",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardKoreanUHC,
+  .name = "Standard-Korean-UHC",
+  .language = "ko",
+  .territory = "KR",
+  .encoding = "UHC"
+},
+
+{ .identifier = eciStandardKoreanUCS,
+  .name = "Standard-Korean-UCS2",
+  .language = "ko",
+  .territory = "KR",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciStandardCantoneseGB,
+  .name = "Standard-Cantonese-GBK",
+  .language = "zh",
+  .territory = "HK",
+  .encoding = "GBK"
+},
+
+{ .identifier = eciStandardCantoneseUCS,
+  .name = "Standard-Cantonese-UCS2",
+  .language = "zh",
+  .territory = "HK",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciHongKongCantoneseBig5,
+  .name = "HongKong-Cantonese-Big5",
+  .language = "zh",
+  .territory = "HK",
+  .encoding = "BIG5"
+},
+
+{ .identifier = eciHongKongCantoneseUCS,
+  .name = "HongKong-Cantonese-UCS2",
+  .language = "zh",
+  .territory = "HK",
+  .encoding = "UCS-2"
+},
+
+{ .identifier = eciStandardDutch,
+  .name = "Standard-Dutch",
+  .language = "nl",
+  .territory = "NL",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardNorwegian,
+  .name = "Standard-Norwegian",
+  .language = "no",
+  .territory = "NO",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardSwedish,
+  .name = "Standard-Swedish",
+  .language = "sv",
+  .territory = "SE",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardDanish,
+  .name = "Standard-Danish",
+  .language = "da",
+  .territory = "DK",
+  .encoding = "ISO-8859-1"
+},
+
+{ .identifier = eciStandardThaiTIS,
+  .name = "Standard-Thai-TIS620",
+  .language = "th",
+  .territory = "TH",
+  .encoding = "TIS-620"
+},
+
diff --git a/Drivers/Speech/ViaVoice/speech.c b/Drivers/Speech/ViaVoice/speech.c
new file mode 100644
index 0000000..06f0711
--- /dev/null
+++ b/Drivers/Speech/ViaVoice/speech.c
@@ -0,0 +1,919 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <eci.h>
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+#define ICONV_NULL ((iconv_t)-1)
+#else /* HAVE_ICONV_H */
+#warning iconv is not available
+#endif /* HAVE_ICONV_H */
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+   PARM_Quality,
+   PARM_Mode,
+   PARM_Synthesize,
+   PARM_Abbreviations,
+   PARM_Years,
+   PARM_Language,
+   PARM_Voice,
+   PARM_Gender,
+   PARM_HeadSize,
+   PARM_PitchBaseline,
+   PARM_Expressiveness,
+   PARM_Roughness,
+   PARM_Breathiness,
+   PARM_Volume,
+   PARM_Speed
+} DriverParameter;
+#define SPKPARMS "Quality", "Mode", "Synthesize", "Abbreviations", "Years", "Language", "Voice", "Gender", "HeadSize", "PitchBaseline", "Expressiveness", "Roughness", "Breathiness", "Volume", "Speed"
+
+#include "spk_driver.h"
+
+#define MAXIMUM_SAMPLES 0X800
+
+typedef void SaveSettingFunction (SpeechSynthesizer *spk, int setting);
+
+typedef struct {
+   const char *name; // must be first
+   int rate;
+} QualityChoice;
+
+static const QualityChoice qualityChoices[] = {
+   {  .name = "poor",
+      .rate = 8000
+   },
+
+   {  .name = "fair",
+      .rate = 11025
+   },
+
+   {  .name = "good",
+      .rate = 22050
+   },
+
+   {  .name = NULL  }
+};
+
+static const char *abbreviationsChoices[] = {"on", "off", NULL};
+static const char *yearsChoices[] = {"off", "on", NULL};
+static const char *synthesizeChoices[] = {"sentences", "all", NULL};
+static const char *modeChoices[] = {"words", "letters", "punctuation", "phonetic", NULL};
+static const char *genderChoices[] = {"male", "female", NULL};
+
+typedef struct {
+   const char *name; // must be first
+   const char *language;
+   const char *territory;
+   const char *encoding;
+   int identifier;
+} LanguageChoice;
+
+static const LanguageChoice languageChoices[] = {
+   #include "languages.h"
+   { .name = NULL }
+};
+
+typedef enum {
+   VOICE_TYPE_CURRENT,
+   VOICE_TYPE_MAN1,
+   VOICE_TYPE_WOMAN1,
+   VOICE_TYPE_CHILD1,
+   VOICE_TYPE_MAN2,
+   VOICE_TYPE_MAN3,
+   VOICE_TYPE_WOMAN2,
+   VOICE_TYPE_MATRIARCH1,
+   VOICE_TYPE_PATRIARCH1,
+   VOICE_TYPE_USER1,
+   VOICE_TYPE_USER2,
+   VOICE_TYPE_USER3,
+   VOICE_TYPE_USER4,
+   VOICE_TYPE_USER5,
+   VOICE_TYPE_USER6,
+   VOICE_TYPE_USER7
+} VoiceType;
+
+typedef struct {
+   const char *name; // must be first
+   int language;
+   VoiceType type;
+} VoiceChoice;
+
+#define GENERIC_VOICE_LANGUAGE NODEFINEDCODESET
+#define GENERIC_VOICE(t,n) { .name=#n, .language=GENERIC_VOICE_LANGUAGE, .type=VOICE_TYPE_##t }
+
+static const VoiceChoice voiceChoices[] = {
+   GENERIC_VOICE(MAN1, man),
+   GENERIC_VOICE(WOMAN1, woman),
+   GENERIC_VOICE(CHILD1, child),
+   GENERIC_VOICE(PATRIARCH1, patriarch),
+   GENERIC_VOICE(MATRIARCH1, matriarch),
+   #include "voices.h"
+   { .name = NULL }
+};
+
+static int
+isGenericVoice (const VoiceChoice *voice) {
+   return voice->language == GENERIC_VOICE_LANGUAGE;
+}
+
+struct SpeechDataStruct {
+   struct {
+      uint32_t binary;
+      char text[20];
+   } version;
+
+   struct {
+      ECIHand handle;
+      unsigned useSSML:1;
+   } eci;
+
+   struct {
+      const LanguageChoice *language;
+      const VoiceChoice *voice;
+   } setting;
+
+   struct {
+      short *buffer;
+      FILE *stream;
+      char *command;
+   } pcm;
+
+   struct {
+      char *buffer;
+      size_t size;
+   } say;
+
+   struct {
+      int unitType;
+      int inputType;
+   } current;
+
+#ifdef ICONV_NULL
+   struct {
+      iconv_t handle;
+   } iconv;
+#endif /* ICONV_NULL */
+};
+
+static void
+saveLanguageSetting (SpeechSynthesizer *spk, int setting) {
+   spk->driver.data->setting.language = &languageChoices[setting];
+}
+
+static void
+saveVoiceSetting (SpeechSynthesizer *spk, int setting) {
+   spk->driver.data->setting.voice = &voiceChoices[setting];
+}
+
+static void
+reportError (SpeechSynthesizer *spk, const char *routine) {
+   int status = eciProgStatus(spk->driver.data->eci.handle);
+   char message[100];
+   eciErrorMessage(spk->driver.data->eci.handle, message);
+   logMessage(LOG_ERR, "%s error %4.4X: %s", routine, status, message);
+}
+
+static void
+reportSetting (const char *type, const char *description, const char *setting, const char *unit) {
+   if (!unit) unit = "";
+   logMessage(LOG_DEBUG, "%s setting: %s = %s%s", type, description, setting, unit);
+}
+
+static void
+reportParameter (const char *type, const char *description, int setting, const void *choices, size_t size, const char *unit) {
+   char buffer[0X10];
+   const char *value = buffer;
+
+   if (setting == -1) {
+      value = "unknown";
+   } else if (choices) {
+      const void *choice = choices;
+
+      while (1) {
+         typedef struct {
+            const char *name;
+         } Entry;
+
+         const Entry *entry = choice;
+         const char *name = entry->name;
+         if (!name) break;
+         int index = (choice - choices) / size;
+
+         if (setting == index) {
+            value = name;
+            break;
+         }
+
+         choice += size;
+      }
+   }
+
+   if (value == buffer) snprintf(buffer, sizeof(buffer), "%d", setting);
+   reportSetting(type, description, value, unit);
+}
+
+static int
+getEnvironmentParameter (SpeechSynthesizer *spk, enum ECIParam parameter) {
+   return eciGetParam(spk->driver.data->eci.handle, parameter);
+}
+
+static const char *
+getEnvironmentParameterUnit (enum ECIParam parameter) {
+   switch (parameter) {
+      default:
+         return "";
+   }
+}
+
+static void
+reportEnvironmentParameter (SpeechSynthesizer *spk, const char *description, enum ECIParam parameter, const void *choices, size_t size) {
+   int setting = getEnvironmentParameter(spk, parameter);
+   reportParameter("environment", description, setting, choices, size, getEnvironmentParameterUnit(parameter));
+}
+
+static int
+setEnvironmentParameter (SpeechSynthesizer *spk, const char *description, enum ECIParam parameter, int setting) {
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "set environment parameter: %s: %d=%d", description, parameter, setting);
+   return eciSetParam(spk->driver.data->eci.handle, parameter, setting) >= 0;
+}
+
+static int
+choiceEnvironmentParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIParam parameter, const void *choices, size_t size, SaveSettingFunction *save) {
+   int ok = !*value;
+
+   if (!ok) {
+      unsigned int setting;
+
+      if (validateChoiceEx(&setting, value, choices, size)) {
+         if (save) {
+            save(spk, setting);
+            ok = 1;
+         } else if (setEnvironmentParameter(spk, description, parameter, setting)) {
+            ok = 1;
+         } else {
+            logMessage(LOG_WARNING, "%s not supported: %s", description, value);
+         }
+      } else {
+         logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
+      }
+   }
+
+   if (!save) reportEnvironmentParameter(spk, description, parameter, choices, size);
+   return ok;
+}
+
+static int
+setUnitType (SpeechSynthesizer *spk, int newUnits) {
+   if (newUnits != spk->driver.data->current.unitType) {
+      if (!setEnvironmentParameter(spk, "real world units", eciRealWorldUnits, newUnits)) return 0;
+      spk->driver.data->current.unitType = newUnits;
+   }
+
+   return 1;
+}
+
+static int
+useInternalUnits (SpeechSynthesizer *spk) {
+   return setUnitType(spk, 0);
+}
+
+static int
+useExternalUnits (SpeechSynthesizer *spk) {
+   return setUnitType(spk, 1);
+}
+
+static int
+useParameterUnit (SpeechSynthesizer *spk, enum ECIVoiceParam parameter) {
+   switch (parameter) {
+      case eciVolume:
+         if (!useInternalUnits(spk)) return 0;
+         break;
+
+      case eciPitchBaseline:
+      case eciSpeed:
+         if (!useExternalUnits(spk)) return 0;
+         break;
+
+      default:
+         break;
+   }
+
+   return 1;
+}
+
+static int
+getVoiceParameter (SpeechSynthesizer *spk, enum ECIVoiceParam parameter) {
+   if (!useParameterUnit(spk, parameter)) return 0;
+   return eciGetVoiceParam(spk->driver.data->eci.handle, VOICE_TYPE_CURRENT, parameter);
+}
+
+static const char *
+getVoiceParameterUnit (enum ECIVoiceParam parameter) {
+   switch (parameter) {
+      case eciVolume:
+         return "%";
+
+      case eciPitchBaseline:
+         return "Hz";
+
+      case eciSpeed:
+         return "wpm";
+
+      default:
+         return "";
+   }
+}
+
+static void
+reportVoiceParameter (SpeechSynthesizer *spk, const char *description, enum ECIVoiceParam parameter, const char *const *choices) {
+   reportParameter("voice", description, getVoiceParameter(spk, parameter), choices, sizeof(*choices), getVoiceParameterUnit(parameter));
+}
+
+static int
+setVoiceParameter (SpeechSynthesizer *spk, const char *description, enum ECIVoiceParam parameter, int setting) {
+   if (!useParameterUnit(spk, parameter)) return 0;
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "set voice parameter: %s: %d=%d%s", description, parameter, setting, getVoiceParameterUnit(parameter));
+   return eciSetVoiceParam(spk->driver.data->eci.handle, VOICE_TYPE_CURRENT, parameter, setting) >= 0;
+}
+
+static int
+choiceVoiceParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIVoiceParam parameter, const char *const *choices) {
+   int ok = !*value;
+
+   if (!ok) {
+      unsigned int setting;
+
+      if (validateChoice(&setting, value, choices)) {
+         if (setVoiceParameter(spk, description, parameter, setting)) {
+            ok = 1;
+         } else {
+            logMessage(LOG_WARNING, "%s not supported: %s", description, value);
+         }
+      } else {
+         logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
+      }
+   }
+
+   reportVoiceParameter(spk, description, parameter, choices);
+   return ok;
+}
+
+static int
+rangeVoiceParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIVoiceParam parameter, int minimum, int maximum) {
+   int ok = 0;
+
+   if (*value) {
+      int setting;
+
+      if (validateInteger(&setting, value, &minimum, &maximum)) {
+         if (setVoiceParameter(spk, description, parameter, setting)) {
+            ok = 1;
+         }
+      } else {
+         logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
+      }
+   }
+
+   reportVoiceParameter(spk, description, parameter, NULL);
+   return ok;
+}
+
+static void
+spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
+   setVoiceParameter(spk, "volume", eciVolume, getIntegerSpeechVolume(setting, 100));
+}
+
+static void
+spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
+   setVoiceParameter(spk, "rate", eciSpeed, (int)(getFloatSpeechRate(setting) * 210.0));
+}
+
+static const LanguageChoice *
+findLanguage (int identifier) {
+   const LanguageChoice *choice = languageChoices;
+
+   while (choice->name) {
+      if (choice->identifier == identifier) return choice;
+      choice += 1;
+   }
+
+   logMessage(LOG_WARNING, "language identifier not defined: 0X%08X", identifier);
+   return NULL;
+}
+
+static const LanguageChoice *
+getLanguage (SpeechSynthesizer *spk) {
+   int identifier = getEnvironmentParameter(spk, eciLanguageDialect);
+   return findLanguage(identifier);
+}
+
+static void
+stopSpeech (SpeechSynthesizer *spk) {
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "stop");
+
+   if (eciStop(spk->driver.data->eci.handle)) {
+   } else {
+      reportError(spk, "eciStop");
+   }
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+   stopSpeech(spk);
+}
+
+static int
+pcmMakeCommand (SpeechSynthesizer *spk) {
+   int rate = getEnvironmentParameter(spk, eciSampleRate);
+   char buffer[0X100];
+
+   snprintf(
+      buffer, sizeof(buffer),
+      "sox -q -t raw -c 1 -b %" PRIsize " -e signed-integer -r %d - -d",
+      (sizeof(*spk->driver.data->pcm.buffer) * 8), qualityChoices[rate].rate
+   );
+
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "PCM command: %s", buffer);
+   spk->driver.data->pcm.command = strdup(buffer);
+   if (spk->driver.data->pcm.command) return 1;
+   logMallocError();
+   return 0;
+}
+
+static int
+pcmOpenStream (SpeechSynthesizer *spk) {
+   if (!spk->driver.data->pcm.stream) {
+      if (!spk->driver.data->pcm.command) {
+         if (!pcmMakeCommand(spk)) {
+            return 0;
+         }
+      }
+
+      if (!(spk->driver.data->pcm.stream = popen(spk->driver.data->pcm.command, "w"))) {
+         logMessage(LOG_WARNING, "can't start command: %s", strerror(errno));
+         return 0;
+      }
+
+      setvbuf(spk->driver.data->pcm.stream, NULL, _IONBF, 0);
+   }
+
+   return 1;
+}
+
+static void
+pcmCloseStream (SpeechSynthesizer *spk) {
+   if (spk->driver.data->pcm.stream) {
+      pclose(spk->driver.data->pcm.stream);
+      spk->driver.data->pcm.stream = NULL;
+   }
+}
+
+static enum ECICallbackReturn
+clientCallback (ECIHand eci, enum ECIMessage message, long parameter, void *data) {
+   SpeechSynthesizer *spk = data;
+
+   switch (message) {
+      case eciWaveformBuffer:
+         logMessage(LOG_CATEGORY(SPEECH_DRIVER), "write samples: %ld", parameter);
+         fwrite(spk->driver.data->pcm.buffer, sizeof(*spk->driver.data->pcm.buffer), parameter, spk->driver.data->pcm.stream);
+         if (ferror(spk->driver.data->pcm.stream)) return eciDataAbort;
+         break;
+
+      case eciIndexReply:
+         logMessage(LOG_CATEGORY(SPEECH_DRIVER), "index reply: %ld", parameter);
+         tellSpeechLocation(spk, parameter);
+         break;
+
+      default:
+         break;
+   }
+
+   return eciDataProcessed;
+}
+
+static int
+addText (SpeechSynthesizer *spk, const char *text) {
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "add text: \"%s\"", text);
+   if (eciAddText(spk->driver.data->eci.handle, text)) return 1;
+   reportError(spk, "eciAddText");
+   return 0;
+}
+
+static int
+insertIndex (SpeechSynthesizer *spk, int index) {
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "insert index: %d", index);
+   if (eciInsertIndex(spk->driver.data->eci.handle, index)) return 1;
+   reportError(spk, "eciInsertIndex");
+   return 0;
+}
+
+static int
+setInputType (SpeechSynthesizer *spk, int newInputType) {
+   if (newInputType != spk->driver.data->current.inputType) {
+      if (!setEnvironmentParameter(spk, "input type", eciInputType, newInputType)) return 0;
+      spk->driver.data->current.inputType = newInputType;
+   }
+
+   return 1;
+}
+
+static int
+disableAnnotations (SpeechSynthesizer *spk) {
+   return setInputType(spk, 0);
+}
+
+static int
+enableAnnotations (SpeechSynthesizer *spk) {
+   return setInputType(spk, 1);
+}
+
+static int
+writeAnnotation (SpeechSynthesizer *spk, const char *annotation) {
+   if (!enableAnnotations(spk)) return 0;
+
+   char text[0X100];
+   snprintf(text, sizeof(text), " `%s ", annotation);
+   return addText(spk, text);
+}
+
+#ifdef ICONV_NULL
+static int
+prepareTextConversion (SpeechSynthesizer *spk) {
+   spk->driver.data->iconv.handle = ICONV_NULL;
+
+   const LanguageChoice *choice = getLanguage(spk);
+   if (!choice) return 0;
+   iconv_t *handle = iconv_open(choice->encoding, "UTF-8");
+
+   if (handle == ICONV_NULL) {
+      logMessage(LOG_WARNING, "character encoding not supported: %s: %s", choice->encoding, strerror(errno));
+      return 0;
+   }
+
+   spk->driver.data->iconv.handle = handle;
+   logMessage(LOG_CATEGORY(SPEECH_DRIVER), "using character encoding: %s", choice->encoding);
+   return 1;
+}
+#endif /* ICONV_NULL */
+
+static int
+ensureSayBufferSize (SpeechSynthesizer *spk, size_t size) {
+   if (size > spk->driver.data->say.size) {
+      size |= 0XFF;
+      size += 1;
+      char *newBuffer = malloc(size);
+
+      if (!newBuffer) {
+         logSystemError("speech buffer allocation");
+         return 0;
+      }
+
+      if (spk->driver.data->say.buffer) free(spk->driver.data->say.buffer);
+      spk->driver.data->say.buffer = newBuffer;
+      spk->driver.data->say.size = size;
+   }
+
+   return 1;
+}
+
+static int
+addCharacters (SpeechSynthesizer *spk, const unsigned char *buffer, int from, int to) {
+   int length = to - from;
+   if (!length) return 1;
+
+   if (!ensureSayBufferSize(spk, length+1)) return 0;
+   memcpy(spk->driver.data->say.buffer, &buffer[from], length);
+   spk->driver.data->say.buffer[length] = 0;
+   return addText(spk, spk->driver.data->say.buffer);
+}
+
+static int
+addSegment (SpeechSynthesizer *spk, const unsigned char *buffer, int from, int to, const int *indexMap) {
+   if (spk->driver.data->eci.useSSML) {
+      for (int index=from; index<to; index+=1) {
+         const char *entity = NULL;
+
+         switch (buffer[index]) {
+            case '<':
+               entity = "lt";
+               break;
+
+            case '>':
+               entity = "gt";
+               break;
+
+            case '&':
+               entity = "amp";
+               break;
+
+            case '"':
+               entity = "quot";
+               break;
+
+            case '\'':
+               entity = "apos";
+               break;
+
+            default:
+               continue;
+         }
+
+         if (!addCharacters(spk, buffer, from, index)) return 0;
+         from = index + 1;
+
+         size_t length = strlen(entity);
+         char text[1 + length + 2];
+         snprintf(text, sizeof(text), "&%s;", entity);
+         if (!addText(spk, text)) return 0;
+      }
+
+      if (!addCharacters(spk, buffer, from, to)) return 0;
+   } else {
+#ifdef ICONV_NULL
+      char *inputStart = (char *)&buffer[from];
+      size_t inputLeft = to - from;
+      size_t outputLeft = inputLeft * 10;
+      char outputBuffer[outputLeft];
+      char *outputStart = outputBuffer;
+      int result = iconv(spk->driver.data->iconv.handle, &inputStart, &inputLeft, &outputStart, &outputLeft);
+
+      if (result == -1) {
+         logSystemError("iconv");
+         return 0;
+      }
+
+      if (!addCharacters(spk, (unsigned char *)outputBuffer, 0, (outputStart - outputBuffer))) return 0;
+#else /* ICONV_NULL */
+      if (!addCharacters(spk, buffer, from, to)) return 0;
+#endif /* ICONV_NULL */
+   }
+
+   return insertIndex(spk, indexMap[to]);
+}
+
+static int
+addSegments (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, const int *indexMap) {
+   if (spk->driver.data->eci.useSSML && !addText(spk, "<speak>")) return 0;
+
+   int wasSpace = 1;
+   int from = 0;
+   int to;
+
+   for (to=0; to<length; to+=1) {
+      int isSpace = isspace(buffer[to])? 1: 0;
+
+      if (isSpace != wasSpace) {
+         wasSpace = isSpace;
+
+         if (to > from) {
+            if (!addSegment(spk, buffer, from, to, indexMap)) return 0;
+            from = to;
+         }
+      }
+   }
+
+   if (!addSegment(spk, buffer, from, to, indexMap)) return 0;
+   if (spk->driver.data->eci.useSSML && !addText(spk, "</speak>")) return 0;
+   return 1;
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
+   int ok = 0;
+
+   if (pcmOpenStream(spk)) {
+      int indexMap[length + 1];
+
+      {
+         int from = 0;
+         int to = 0;
+
+         while (from < length) {
+            char character = buffer[from];
+            indexMap[from++] = ((character & 0X80) && !(character & 0X40))? -1: to++;
+         }
+
+         indexMap[from] = to;
+      }
+
+      if (addSegments(spk, buffer, length, indexMap)) {
+         logMessage(LOG_CATEGORY(SPEECH_DRIVER), "synthesize");
+
+         if (eciSynthesize(spk->driver.data->eci.handle)) {
+            logMessage(LOG_CATEGORY(SPEECH_DRIVER), "synchronize");
+
+            if (eciSynchronize(spk->driver.data->eci.handle)) {
+               logMessage(LOG_CATEGORY(SPEECH_DRIVER), "finished");
+               tellSpeechFinished(spk);
+               ok = 1;
+            } else {
+               reportError(spk, "eciSynchronize");
+            }
+         } else {
+            reportError(spk, "eciSynthesize");
+         }
+      }
+
+      if (!ok) stopSpeech(spk);
+      pcmCloseStream(spk);
+   }
+}
+
+static void
+setLanguageAndVoice (SpeechSynthesizer *spk) {
+   const LanguageChoice *language = spk->driver.data->setting.language;
+   const VoiceChoice *voice = spk->driver.data->setting.voice;
+
+   if (voice && !isGenericVoice(voice)) {
+      if (!language) {
+         language = findLanguage(voice->language);
+      } else if (language->identifier != voice->language) {
+         logMessage(LOG_WARNING, "voice %s is incompatible with language %s", voice->name, language->name);
+      }
+   }
+
+   if (language) {
+      if (!setEnvironmentParameter(spk, "language", eciLanguageDialect, language->identifier)) {
+         logMessage(LOG_WARNING, "language not supported: %s", language->name);
+      }
+   }
+
+   language = getLanguage(spk);
+   reportSetting("environment", "language", language->name, NULL);
+
+   if (voice) {
+      logMessage(LOG_CATEGORY(SPEECH_DRIVER), "copy voice: %d (%s)", voice->type, voice->name);
+
+      if (eciCopyVoice(spk->driver.data->eci.handle, voice->type, VOICE_TYPE_CURRENT)) {
+         const char *description = isGenericVoice(voice)? "type": "name";
+         reportSetting("voice", description, voice->name, NULL);
+      } else {
+         reportError(spk, "eciCopyVoice");
+      }
+   }
+}
+
+static void
+setParameters (SpeechSynthesizer *spk, char **parameters) {
+   choiceEnvironmentParameter(spk, "quality (sample rate)", parameters[PARM_Quality], eciSampleRate, qualityChoices, sizeof(*qualityChoices), NULL);
+   choiceEnvironmentParameter(spk, "mode (text mode)", parameters[PARM_Mode], eciTextMode, modeChoices, sizeof(*modeChoices), NULL);
+   choiceEnvironmentParameter(spk, "synthesize (synth mode)", parameters[PARM_Synthesize], eciSynthMode, synthesizeChoices, sizeof(*synthesizeChoices), NULL);
+   choiceEnvironmentParameter(spk, "abbreviations (dictionary)", parameters[PARM_Abbreviations], eciDictionary, abbreviationsChoices, sizeof(*abbreviationsChoices), NULL);
+   choiceEnvironmentParameter(spk, "years (number mode)", parameters[PARM_Years], eciNumberMode, yearsChoices, sizeof(*yearsChoices), NULL);
+
+   choiceEnvironmentParameter(spk, "language", parameters[PARM_Language], eciLanguageDialect, languageChoices, sizeof(*languageChoices), saveLanguageSetting);
+   choiceEnvironmentParameter(spk, "voice name", parameters[PARM_Voice], eciNumParams, voiceChoices, sizeof(*voiceChoices), saveVoiceSetting);
+   setLanguageAndVoice(spk);
+
+   choiceVoiceParameter(spk, "gender", parameters[PARM_Gender], eciGender, genderChoices);
+   rangeVoiceParameter(spk, "head size", parameters[PARM_HeadSize], eciHeadSize, 0, 100);
+   rangeVoiceParameter(spk, "pitch baseline", parameters[PARM_PitchBaseline], eciPitchBaseline, 40, 422);
+   rangeVoiceParameter(spk, "expressiveness (pitch fluctuation)", parameters[PARM_Expressiveness], eciPitchFluctuation, 0, 100);
+   rangeVoiceParameter(spk, "roughness", parameters[PARM_Roughness], eciRoughness, 0, 100);
+   rangeVoiceParameter(spk, "breathiness", parameters[PARM_Breathiness], eciBreathiness, 0, 100);
+   rangeVoiceParameter(spk, "volume", parameters[PARM_Volume], eciVolume, 0, 100);
+   rangeVoiceParameter(spk, "speed", parameters[PARM_Speed], eciSpeed, 70, 1297);
+
+#ifdef ICONV_NULL
+   spk->driver.data->eci.useSSML = !prepareTextConversion(spk);
+#else /* ICONV_NULL */
+   spk->driver.data->eci.useSSML = 1;
+#endif /* ICONV_NULL */
+
+   if (spk->driver.data->eci.useSSML) {
+      logMessage(LOG_CATEGORY(SPEECH_DRIVER), "using SSML");
+   }
+}
+
+static void
+writeAnnotations (SpeechSynthesizer *spk) {
+   if (spk->driver.data->eci.useSSML) {
+      writeAnnotation(spk, "gfa1"); // enable SSML
+      writeAnnotation(spk, "gfa2");
+   }
+
+   disableAnnotations(spk);
+}
+
+static uint32_t
+parseVersion (const char *text) {
+   uint32_t result = 0;
+   const unsigned int width = 8;
+   unsigned int shift = sizeof(result) * 8 / width * width;
+
+   while (*text && shift) {
+      char *end;
+      long digit = strtol(text, &end, 10);
+
+      shift -= width;
+      result |= digit << shift;
+
+      text = end;
+      if (*text) text += 1;
+   }
+
+   return result;
+}
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+   spk->setVolume = spk_setVolume;
+   spk->setRate = spk_setRate;
+
+   if ((spk->driver.data = malloc(sizeof(*spk->driver.data)))) {
+      memset(spk->driver.data, 0, sizeof(*spk->driver.data));
+
+      eciVersion(spk->driver.data->version.text);
+      logMessage(LOG_INFO, "ViaVoice engine version: %s", spk->driver.data->version.text);
+      spk->driver.data->version.binary = parseVersion(spk->driver.data->version.text);
+
+      spk->driver.data->eci.handle = NULL_ECI_HAND;
+      spk->driver.data->eci.useSSML = 0;
+
+      spk->driver.data->setting.language = NULL;
+      spk->driver.data->setting.voice = NULL;
+
+      spk->driver.data->pcm.buffer = NULL;
+      spk->driver.data->pcm.stream = NULL;
+      spk->driver.data->pcm.command = NULL;
+
+      spk->driver.data->say.buffer = NULL;
+      spk->driver.data->say.size = 0;
+
+      if ((spk->driver.data->eci.handle = eciNew()) != NULL_ECI_HAND) {
+         eciRegisterCallback(spk->driver.data->eci.handle, clientCallback, (void *)spk);
+
+         spk->driver.data->current.unitType = getEnvironmentParameter(spk, eciRealWorldUnits);
+         spk->driver.data->current.inputType = getEnvironmentParameter(spk, eciInputType);
+
+         if ((spk->driver.data->pcm.buffer = calloc(MAXIMUM_SAMPLES, sizeof(*spk->driver.data->pcm.buffer)))) {
+            if (eciSetOutputBuffer(spk->driver.data->eci.handle, MAXIMUM_SAMPLES, spk->driver.data->pcm.buffer)) {
+               setParameters(spk, parameters);
+               writeAnnotations(spk);
+               return 1;
+            } else {
+               reportError(spk, "eciSetOutputBuffer");
+            }
+
+            free(spk->driver.data->pcm.buffer);
+         } else {
+            logMallocError();
+         }
+
+         eciDelete(spk->driver.data->eci.handle);
+      } else {
+         logMessage(LOG_ERR, "ViaVoice engine initialization failure");
+      }
+
+      free(spk->driver.data);
+      spk->driver.data = NULL;
+   } else {
+      logMallocError();
+   }
+
+   return 0;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+   if (spk->driver.data) {
+      if (spk->driver.data->eci.handle) eciDelete(spk->driver.data->eci.handle);
+      if (spk->driver.data->say.buffer) free(spk->driver.data->say.buffer);
+      if (spk->driver.data->pcm.buffer) free(spk->driver.data->pcm.buffer);
+      if (spk->driver.data->pcm.command) free(spk->driver.data->pcm.command);
+      pcmCloseStream(spk);
+
+#ifdef ICONV_NULL
+      if (spk->driver.data->iconv.handle != ICONV_NULL) iconv_close(spk->driver.data->iconv.handle);
+#endif /* ICONV_NULL */
+
+      free(spk->driver.data);
+      spk->driver.data = NULL;
+   }
+}
diff --git a/Drivers/Speech/ViaVoice/voices.h b/Drivers/Speech/ViaVoice/voices.h
new file mode 100644
index 0000000..8ed3c4f
--- /dev/null
+++ b/Drivers/Speech/ViaVoice/voices.h
@@ -0,0 +1,293 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+{ .name = "Reed",
+  .language = eciGeneralAmericanEnglish,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Shelley",
+  .language = eciGeneralAmericanEnglish,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Sandy",
+  .language = eciGeneralAmericanEnglish,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Grandpa",
+  .language = eciGeneralAmericanEnglish,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Grandma",
+  .language = eciGeneralAmericanEnglish,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Justin",
+  .language = eciBritishEnglish,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Jane",
+  .language = eciBritishEnglish,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Nicky",
+  .language = eciBritishEnglish,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Gramps",
+  .language = eciBritishEnglish,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Nanny",
+  .language = eciBritishEnglish,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Carlos",
+  .language = eciCastilianSpanish,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Pilar",
+  .language = eciCastilianSpanish,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Pepe",
+  .language = eciCastilianSpanish,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Abuelo",
+  .language = eciCastilianSpanish,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Abuela",
+  .language = eciCastilianSpanish,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Jacques",
+  .language = eciStandardFrench,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Jacqueline",
+  .language = eciStandardFrench,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Marius",
+  .language = eciStandardFrench,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Grandpère",
+  .language = eciStandardFrench,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Mamie",
+  .language = eciStandardFrench,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Max",
+  .language = eciStandardGerman,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Gisela",
+  .language = eciStandardGerman,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Matti",
+  .language = eciStandardGerman,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Opa",
+  .language = eciStandardGerman,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Oma",
+  .language = eciStandardGerman,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Enrico",
+  .language = eciStandardItalian,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Lucia",
+  .language = eciStandardItalian,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Chicco",
+  .language = eciStandardItalian,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Nonno",
+  .language = eciStandardItalian,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Nonna",
+  .language = eciStandardItalian,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Li3 Jing4",
+  .language = eciMandarinChinese,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Wang2 Yan4",
+  .language = eciMandarinChinese,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Li3 Dong1 Dong1",
+  .language = eciMandarinChinese,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Ye2 Ye",
+  .language = eciMandarinChinese,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Nai3 Nai",
+  .language = eciMandarinChinese,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Zhi4 Ming2",
+  .language = eciTaiwaneseMandarin,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Chun1 Jiao1",
+  .language = eciTaiwaneseMandarin,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Xiao3 Bu4 Dian3",
+  .language = eciTaiwaneseMandarin,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "A3 Gong1",
+  .language = eciTaiwaneseMandarin,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "A3 Ma4",
+  .language = eciTaiwaneseMandarin,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "João",
+  .language = eciBrazilianPortuguese,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Cláudia",
+  .language = eciBrazilianPortuguese,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Chico",
+  .language = eciBrazilianPortuguese,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Avô",
+  .language = eciBrazilianPortuguese,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Avó",
+  .language = eciBrazilianPortuguese,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Taroo",
+  .language = eciStandardJapanese,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Hanako",
+  .language = eciStandardJapanese,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Jiroo",
+  .language = eciStandardJapanese,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Taroo",
+  .language = eciStandardJapanese,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Obaachan",
+  .language = eciStandardJapanese,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
+{ .name = "Antti",
+  .language = eciStandardFinnish,
+  .type = VOICE_TYPE_MAN1
+},
+
+{ .name = "Tarja",
+  .language = eciStandardFinnish,
+  .type = VOICE_TYPE_WOMAN1
+},
+
+{ .name = "Pekka",
+  .language = eciStandardFinnish,
+  .type = VOICE_TYPE_CHILD1
+},
+
+{ .name = "Isoisä",
+  .language = eciStandardFinnish,
+  .type = VOICE_TYPE_PATRIARCH1
+},
+
+{ .name = "Isoäiti",
+  .language = eciStandardFinnish,
+  .type = VOICE_TYPE_MATRIARCH1
+},
+
diff --git a/Drivers/Speech/eSpeak-NG/Makefile.in b/Drivers/Speech/eSpeak-NG/Makefile.in
new file mode 100644
index 0000000..a192490
--- /dev/null
+++ b/Drivers/Speech/eSpeak-NG/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = en
+DRIVER_NAME = eSpeak-NG
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 0.1
+DRIVER_DEVELOPERS = Nicolas Pitre <nico@fluxnic.net>, Ondřej Lysoněk <olysonek@redhat.com>
+SPK_OBJS = @speech_libraries_en@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -I$(ESPEAK_NG_ROOT)/include -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/eSpeak-NG/README b/Drivers/Speech/eSpeak-NG/README
new file mode 100644
index 0000000..825848a
--- /dev/null
+++ b/Drivers/Speech/eSpeak-NG/README
@@ -0,0 +1,34 @@
+This directory contains the BRLTTY speech driver for the eSpeak-NG text to
+speech engine.  The eSpeak-NG web site is
+https://github.com/espeak-ng/espeak-ng/.
+
+eSpeak-NG comes with a simple command-line tool called espeak-ng which can be
+used to test it, and to recompile modified dictionaries, etc. Please see
+the espeak-ng man page for more information.
+
+BRLTTY's configure script automatically includes this driver if the
+eSpeak-NG development library is installed.
+
+This driver recognizes the following parameters:
+
+path
+
+	Specifies the directory containing the espeak-ng-data directory.
+	If not specified, the default location is used.
+	
+punctlist
+
+	Specified a list of punctuation characters whose names are to be
+	spoken when the speech punctuation parameter is set to "Some".
+	If not specified, a default list is used.
+
+voice
+
+	Specifies a voice/language to use.  The complete list of available
+	voices may be obtained with the command 'espeak-ng --voices'.
+
+maxrate
+
+	Overrides the maximum speech rate value. The default is 450.
+	This cannot be lower than 80.
+
diff --git a/Drivers/Speech/eSpeak-NG/speech.c b/Drivers/Speech/eSpeak-NG/speech.c
new file mode 100644
index 0000000..4bc6208
--- /dev/null
+++ b/Drivers/Speech/eSpeak-NG/speech.c
@@ -0,0 +1,174 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+	PARM_PATH,
+	PARM_PUNCTLIST,
+	PARM_VOICE,
+	PARM_MAXRATE
+} DriverParameter;
+#define SPKPARMS "path", "punctlist", "voice", "maxrate"
+
+#include "spk_driver.h"
+
+#include <espeak-ng/speak_lib.h>
+
+static int maxrate = espeakRATE_MAXIMUM;
+
+static void
+spk_say(SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes)
+{
+	int result;
+
+	/* add 1 to the length in order to pass along the trailing zero */
+	result = espeak_Synth(buffer, length+1, 0, POS_CHARACTER, 0,
+			espeakCHARS_UTF8, NULL, (void *)spk);
+	if (result != EE_OK)
+		logMessage(LOG_ERR, "eSpeak-NG: Synth() returned error %d", result);
+}
+
+static void
+spk_mute(SpeechSynthesizer *spk)
+{
+	espeak_Cancel();
+}
+
+static int SynthCallback(short *audio, int numsamples, espeak_EVENT *events)
+{
+	SpeechSynthesizer *spk = events->user_data;
+
+	while (events->type != espeakEVENT_LIST_TERMINATED) {
+		if (events->type == espeakEVENT_WORD)
+			tellSpeechLocation(spk, events->text_position - 1);
+		if (events->type == espeakEVENT_MSG_TERMINATED)
+			tellSpeechFinished(spk);
+		events++;
+	}
+	return 0;
+}
+
+static void
+spk_drain(SpeechSynthesizer *spk)
+{
+	espeak_Synchronize();
+}
+
+static void
+spk_setVolume(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int volume = getIntegerSpeechVolume(setting, 50);
+	espeak_SetParameter(espeakVOLUME, volume, 0);
+}
+
+static void
+spk_setRate(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int h_range = (maxrate - espeakRATE_MINIMUM)/2;
+	int rate = getIntegerSpeechRate(setting, h_range) + espeakRATE_MINIMUM;
+	espeak_SetParameter(espeakRATE, rate, 0);
+}
+
+static void
+spk_setPitch(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int pitch = getIntegerSpeechPitch(setting, 50);
+	espeak_SetParameter(espeakPITCH, pitch, 0);
+}
+
+static void
+spk_setPunctuation(SpeechSynthesizer *spk, SpeechPunctuation setting)
+{
+	espeak_PUNCT_TYPE punct;
+	if (setting <= SPK_PUNCTUATION_NONE)
+		punct = espeakPUNCT_NONE;
+	else if (setting >= SPK_PUNCTUATION_ALL)
+		punct = espeakPUNCT_ALL;
+	else
+		punct = espeakPUNCT_SOME;
+	espeak_SetParameter(espeakPUNCTUATION, punct, 0);
+}
+
+static int spk_construct(SpeechSynthesizer *spk, char **parameters)
+{
+	const char *data_path, *voicename, *punctlist;
+	int result;
+
+	spk->setVolume = spk_setVolume;
+	spk->setRate = spk_setRate;
+	spk->setPitch = spk_setPitch;
+	spk->setPunctuation = spk_setPunctuation;
+	spk->drain = spk_drain;
+
+	logMessage(LOG_INFO, "eSpeak-NG version %s", espeak_Info(NULL));
+
+	data_path = parameters[PARM_PATH];
+	if (data_path && !*data_path)
+		data_path = NULL;
+	result = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0);
+	if (result < 0) {
+		logMessage(LOG_ERR, "eSpeak-NG: initialization failed");
+		return 0;
+	}
+
+	voicename = parameters[PARM_VOICE];
+	if(!voicename || !*voicename)
+		voicename = "en";
+	result = espeak_SetVoiceByName(voicename);
+	if (result != EE_OK) {
+		espeak_VOICE voice_select;
+		memset(&voice_select, 0, sizeof(voice_select));
+		voice_select.languages = voicename;
+		result = espeak_SetVoiceByProperties(&voice_select);
+	}
+	if (result != EE_OK) {
+		logMessage(LOG_ERR, "eSpeak-NG: unable to load voice '%s'", voicename);
+		return 0;
+	}
+
+	punctlist = parameters[PARM_PUNCTLIST];
+	if (punctlist && *punctlist) {
+		wchar_t w_punctlist[strlen(punctlist) + 1];
+		int i = 0;
+		while ((w_punctlist[i] = punctlist[i]) != 0) i++;
+		espeak_SetPunctuationList(w_punctlist);
+	}
+
+	if (parameters[PARM_MAXRATE]) {
+		int val = atoi(parameters[PARM_MAXRATE]);
+		if (val > espeakRATE_MINIMUM) maxrate = val;
+	}
+
+	espeak_SetSynthCallback(SynthCallback);
+
+	return 1;
+}
+
+static void spk_destruct(SpeechSynthesizer *spk)
+{
+	espeak_Cancel();
+	espeak_Terminate();
+}
diff --git a/Drivers/Speech/eSpeak/Makefile.in b/Drivers/Speech/eSpeak/Makefile.in
new file mode 100644
index 0000000..8edcac9
--- /dev/null
+++ b/Drivers/Speech/eSpeak/Makefile.in
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+DRIVER_CODE = es
+DRIVER_NAME = eSpeak
+DRIVER_USAGE = software engine
+DRIVER_VERSION = 0.3 
+DRIVER_DEVELOPERS = Nicolas Pitre <nico@fluxnic.net>
+SPK_OBJS = @speech_libraries_es@
+include $(SRC_TOP)speech.mk
+
+speech.$O:
+	$(CC) $(SPK_CFLAGS) -I$(ESPEAK_ROOT)/include -c $(SRC_DIR)/speech.c
+
diff --git a/Drivers/Speech/eSpeak/README b/Drivers/Speech/eSpeak/README
new file mode 100644
index 0000000..23efe34
--- /dev/null
+++ b/Drivers/Speech/eSpeak/README
@@ -0,0 +1,33 @@
+This directory contains the BRLTTY speech driver for the eSpeak text to
+speech engine.  The eSpeak web site is http://espeak.sourceforge.net/.
+
+eSpeak comes with a simple command-line tool called espeak which can be
+used to test it, and to recompile modified dictionaries, etc. Please see
+the espeak man page for more information.
+
+BRLTTY's configure script automatically includes this driver if the
+eSpeak development library is installed.
+
+This driver recognizes the following parameters:
+
+path
+
+	Specifies the directory containing the espeak-data directory.
+	If not specified, the default location is used.
+	
+punctlist
+
+	Specified a list of punctuation characters whose names are to be
+	spoken when the speech punctuation parameter is set to "Some".
+	If not specified, a default list is used.
+
+voice
+
+	Specifies a voice/language to use.  The complete list of available
+	voices may be obtained with the command 'espeak --voices'.
+
+maxrate
+
+	Overrides the maximum speech rate value. The default is 450.
+	This cannot be lower than 80.
+
diff --git a/Drivers/Speech/eSpeak/speech.c b/Drivers/Speech/eSpeak/speech.c
new file mode 100644
index 0000000..1d62c02
--- /dev/null
+++ b/Drivers/Speech/eSpeak/speech.c
@@ -0,0 +1,179 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+
+typedef enum {
+	PARM_PATH,
+	PARM_PUNCTLIST,
+	PARM_VOICE,
+	PARM_MAXRATE
+} DriverParameter;
+#define SPKPARMS "path", "punctlist", "voice", "maxrate"
+
+#include "spk_driver.h"
+
+#include <espeak/speak_lib.h>
+
+#if ESPEAK_API_REVISION < 6
+#define espeakRATE_MINIMUM	80
+#define espeakRATE_MAXIMUM	450
+#endif /* ESPEAK_API_REVISION < 6 */
+
+static int maxrate = espeakRATE_MAXIMUM;
+
+static void
+spk_say(SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes)
+{
+	int result;
+
+	/* add 1 to the length in order to pass along the trailing zero */
+	result = espeak_Synth(buffer, length+1, 0, POS_CHARACTER, 0,
+			espeakCHARS_UTF8, NULL, (void *)spk);
+	if (result != EE_OK)
+		logMessage(LOG_ERR, "eSpeak: Synth() returned error %d", result);
+}
+
+static void
+spk_mute(SpeechSynthesizer *spk)
+{
+	espeak_Cancel();
+}
+
+static int SynthCallback(short *audio, int numsamples, espeak_EVENT *events)
+{
+	SpeechSynthesizer *spk = events->user_data;
+
+	while (events->type != espeakEVENT_LIST_TERMINATED) {
+		if (events->type == espeakEVENT_WORD)
+			tellSpeechLocation(spk, events->text_position - 1);
+		if (events->type == espeakEVENT_MSG_TERMINATED)
+			tellSpeechFinished(spk);
+		events++;
+	}
+	return 0;
+}
+
+static void
+spk_drain(SpeechSynthesizer *spk)
+{
+	espeak_Synchronize();
+}
+
+static void
+spk_setVolume(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int volume = getIntegerSpeechVolume(setting, 50);
+	espeak_SetParameter(espeakVOLUME, volume, 0);
+}
+
+static void
+spk_setRate(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int h_range = (maxrate - espeakRATE_MINIMUM)/2;
+	int rate = getIntegerSpeechRate(setting, h_range) + espeakRATE_MINIMUM;
+	espeak_SetParameter(espeakRATE, rate, 0);
+}
+
+static void
+spk_setPitch(SpeechSynthesizer *spk, unsigned char setting)
+{
+	int pitch = getIntegerSpeechPitch(setting, 50);
+	espeak_SetParameter(espeakPITCH, pitch, 0);
+}
+
+static void
+spk_setPunctuation(SpeechSynthesizer *spk, SpeechPunctuation setting)
+{
+	espeak_PUNCT_TYPE punct;
+	if (setting <= SPK_PUNCTUATION_NONE)
+		punct = espeakPUNCT_NONE;
+	else if (setting >= SPK_PUNCTUATION_ALL)
+		punct = espeakPUNCT_ALL;
+	else
+		punct = espeakPUNCT_SOME;
+	espeak_SetParameter(espeakPUNCTUATION, punct, 0);
+}
+
+static int spk_construct(SpeechSynthesizer *spk, char **parameters)
+{
+	const char *data_path, *voicename, *punctlist;
+	int result;
+
+	spk->setVolume = spk_setVolume;
+	spk->setRate = spk_setRate;
+	spk->setPitch = spk_setPitch;
+	spk->setPunctuation = spk_setPunctuation;
+	spk->drain = spk_drain;
+
+	logMessage(LOG_INFO, "eSpeak version %s", espeak_Info(NULL));
+
+	data_path = parameters[PARM_PATH];
+	if (data_path && !*data_path)
+		data_path = NULL;
+	result = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0);
+	if (result < 0) {
+		logMessage(LOG_ERR, "eSpeak: initialization failed");
+		return 0;
+	}
+
+	voicename = parameters[PARM_VOICE];
+	if(!voicename || !*voicename)
+		voicename = "default";
+	result = espeak_SetVoiceByName(voicename);
+	if (result != EE_OK) {
+		espeak_VOICE voice_select;
+		memset(&voice_select, 0, sizeof(voice_select));
+		voice_select.languages = voicename;
+		result = espeak_SetVoiceByProperties(&voice_select);
+	}
+	if (result != EE_OK) {
+		logMessage(LOG_ERR, "eSpeak: unable to load voice '%s'", voicename);
+		return 0;
+	}
+
+	punctlist = parameters[PARM_PUNCTLIST];
+	if (punctlist && *punctlist) {
+		wchar_t w_punctlist[strlen(punctlist) + 1];
+		int i = 0;
+		while ((w_punctlist[i] = punctlist[i]) != 0) i++;
+		espeak_SetPunctuationList(w_punctlist);
+	}
+
+	if (parameters[PARM_MAXRATE]) {
+		int val = atoi(parameters[PARM_MAXRATE]);
+		if (val > espeakRATE_MINIMUM) maxrate = val;
+	}
+
+	espeak_SetSynthCallback(SynthCallback);
+
+	return 1;
+}
+
+static void spk_destruct(SpeechSynthesizer *spk)
+{
+	espeak_Cancel();
+	espeak_Terminate();
+}
diff --git a/Headers/addresses.h b/Headers/addresses.h
new file mode 100644
index 0000000..897f991
--- /dev/null
+++ b/Headers/addresses.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ADDRESSES
+#define BRLTTY_INCLUDED_ADDRESSES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *getAddressName (void *address, ptrdiff_t *offset);
+extern int setAddressName (void *address, const char *format, ...);
+extern void unsetAddressName (void *address);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ADDRESSES */
diff --git a/Headers/alert.h b/Headers/alert.h
new file mode 100644
index 0000000..4e199d1
--- /dev/null
+++ b/Headers/alert.h
@@ -0,0 +1,88 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ALERT
+#define BRLTTY_INCLUDED_ALERT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  ALERT_NONE,
+
+  ALERT_BRAILLE_ON,
+  ALERT_BRAILLE_OFF,
+
+  ALERT_COMMAND_DONE,
+  ALERT_COMMAND_REJECTED,
+
+  ALERT_MARK_SET,
+
+  ALERT_CLIPBOARD_BEGIN,
+  ALERT_CLIPBOARD_END,
+
+  ALERT_NO_CHANGE,
+  ALERT_TOGGLE_ON,
+  ALERT_TOGGLE_OFF,
+
+  ALERT_CURSOR_LINKED,
+  ALERT_CURSOR_UNLINKED,
+
+  ALERT_SCREEN_FROZEN,
+  ALERT_SCREEN_UNFROZEN,
+  ALERT_FREEZE_REMINDER,
+
+  ALERT_WRAP_DOWN,
+  ALERT_WRAP_UP,
+
+  ALERT_SKIP_FIRST,
+  ALERT_SKIP_ONE,
+  ALERT_SKIP_SEVERAL,
+  ALERT_BOUNCE,
+
+  ALERT_ROUTING_STARTED,
+  ALERT_ROUTING_SUCCEEDED,
+  ALERT_ROUTING_FAILED,
+
+  ALERT_MODIFIER_ONCE,
+  ALERT_MODIFIER_LOCK,
+  ALERT_MODIFIER_OFF,
+
+  ALERT_CONSOLE_BELL,
+  ALERT_KEYS_AUTORELEASED,
+
+  ALERT_SCROLL_UP,
+
+  ALERT_CONTEXT_DEFAULT,
+  ALERT_CONTEXT_PERSISTENT,
+  ALERT_CONTEXT_TEMPORARY,
+} AlertIdentifier;
+
+extern void alert (AlertIdentifier identifier);
+
+extern int showDotPattern (unsigned char dots, unsigned char duration);
+
+extern void speakAlertMessage(const char *message);
+extern void speakAlertText(const wchar_t *text);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ALERT */
diff --git a/Headers/ascii.h b/Headers/ascii.h
new file mode 100644
index 0000000..db62dfe
--- /dev/null
+++ b/Headers/ascii.h
@@ -0,0 +1,66 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASCII
+#define BRLTTY_INCLUDED_ASCII
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  ASCII_NUL = 0X00, /* ^@: Null Character */
+  ASCII_SOH = 0X01, /* ^A: Start of Header */
+  ASCII_STX = 0X02, /* ^B: Start of Text */
+  ASCII_ETX = 0X03, /* ^C: End of Text */
+  ASCII_EOT = 0X04, /* ^D: End of Transmission */
+  ASCII_ENQ = 0X05, /* ^E: Enquiry */
+  ASCII_ACK = 0X06, /* ^F: Acknowledgement */
+  ASCII_BEL = 0X07, /* ^G: Bell */
+  ASCII_BS = 0X08,  /* ^H: BackSpace */
+  ASCII_HT = 0X09,  /* ^I: Horizontal Tab (Character Tabulation) */
+  ASCII_LF = 0X0A,  /* ^J: Line Feed */
+  ASCII_VT = 0X0B,  /* ^K: Vertical Tab (Line Tabulation) */
+  ASCII_FF = 0X0C,  /* ^L: Form Feed */
+  ASCII_CR = 0X0D,  /* ^M: Carriage Return */
+  ASCII_SO = 0X0E,  /* ^N: Shift Out */
+  ASCII_SI = 0X0F,  /* ^O: Shift In */
+  ASCII_DLE = 0X10, /* ^P: Data Link Escape */
+  ASCII_DC1 = 0X11, /* ^Q: Device Control One (X-ON) */
+  ASCII_DC2 = 0X12, /* ^R: Device Control Two */
+  ASCII_DC3 = 0X13, /* ^S: Device Control Three (X-OFF) */
+  ASCII_DC4 = 0X14, /* ^T: Device Control Four */
+  ASCII_NAK = 0X15, /* ^U: Negative Acknowledgement */
+  ASCII_SYN = 0X16, /* ^V: Synchronous Idle */
+  ASCII_ETB = 0X17, /* ^W: End of Transmission Block */
+  ASCII_CAN = 0X18, /* ^X: Cancel */
+  ASCII_EM = 0X19,  /* ^Y: End of Medium */
+  ASCII_SUB = 0X1A, /* ^Z: Substitute */
+  ASCII_ESC = 0X1B, /* ^[: Escape */
+  ASCII_FS = 0X1C,  /* ^\: File Separator (Formation Separator Four) */
+  ASCII_GS = 0X1D,  /* ^]: Group Separator (Formation Separator Three) */
+  ASCII_RS = 0X1E,  /* ^^: Record Separator (Formation Separator Two) */
+  ASCII_US = 0X1F,  /* ^_: Unit Separator (Formation Separator One) */
+  ASCII_DEL = 0X7F  /* ^?: Delete */
+} AsciiControlCharacter;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASCII */
diff --git a/Headers/async_alarm.h b/Headers/async_alarm.h
new file mode 100644
index 0000000..4962de7
--- /dev/null
+++ b/Headers/async_alarm.h
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_ALARM
+#define BRLTTY_INCLUDED_ASYNC_ALARM
+
+#include "async_types_alarm.h"
+#include "async_types_handle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int asyncNewAbsoluteAlarm (
+  AsyncHandle *handle,
+  const TimeValue *time,
+  AsyncAlarmCallback *callback,
+  void *data
+);
+
+extern int asyncNewRelativeAlarm (
+  AsyncHandle *handle,
+  int milliseconds,
+  AsyncAlarmCallback *callback,
+  void *data
+);
+
+extern int asyncResetAlarmTo (AsyncHandle handle, const TimeValue *time);
+extern int asyncResetAlarmIn (AsyncHandle handle, int milliseconds);
+extern int asyncResetAlarmInterval (AsyncHandle handle, int milliseconds);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_ALARM */
diff --git a/Headers/async_event.h b/Headers/async_event.h
new file mode 100644
index 0000000..51473ad
--- /dev/null
+++ b/Headers/async_event.h
@@ -0,0 +1,43 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_EVENT
+#define BRLTTY_INCLUDED_ASYNC_EVENT
+
+#include "async_types_event.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern AsyncEvent *asyncNewEvent (AsyncEventCallback *callback, void *data);
+extern void asyncDiscardEvent (AsyncEvent *event);
+extern int asyncSignalEvent (AsyncEvent *event, void *data);
+
+typedef AsyncEvent *AsyncEventCreator (void *data);
+
+extern AsyncEvent *asyncGetProgramEvent (
+  AsyncEvent **event, const char *name,
+  AsyncEventCreator *createEvent, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_EVENT */
diff --git a/Headers/async_handle.h b/Headers/async_handle.h
new file mode 100644
index 0000000..843035c
--- /dev/null
+++ b/Headers/async_handle.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_HANDLE
+#define BRLTTY_INCLUDED_ASYNC_HANDLE
+
+#include "async_types_handle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int asyncTestHandle (AsyncHandle handle);
+extern void asyncDiscardHandle (AsyncHandle handle);
+extern void asyncCancelRequest (AsyncHandle handle);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_HANDLE */
diff --git a/Headers/async_io.h b/Headers/async_io.h
new file mode 100644
index 0000000..9320b4c
--- /dev/null
+++ b/Headers/async_io.h
@@ -0,0 +1,97 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_IO
+#define BRLTTY_INCLUDED_ASYNC_IO
+
+#include "async_types_io.h"
+#include "async_types_handle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int asyncMonitorFileInput (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncMonitorSocketInput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncMonitorFileOutput (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncMonitorSocketOutput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncMonitorFileAlert (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncMonitorSocketAlert (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern int asyncReadFile (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  size_t size,
+  AsyncInputCallback *callback, void *data
+);
+
+extern int asyncReadSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  size_t size,
+  AsyncInputCallback *callback, void *data
+);
+
+extern int asyncWriteFile (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  const void *buffer, size_t size,
+  AsyncOutputCallback *callback, void *data
+);
+
+extern int asyncWriteSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  const void *buffer, size_t size,
+  AsyncOutputCallback *callback, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_IO */
diff --git a/Headers/async_signal.h b/Headers/async_signal.h
new file mode 100644
index 0000000..5ca57db
--- /dev/null
+++ b/Headers/async_signal.h
@@ -0,0 +1,122 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_SIGNAL
+#define BRLTTY_INCLUDED_ASYNC_SIGNAL
+
+#include "prologue.h"
+#include "async_types_handle.h"
+
+#undef ASYNC_CAN_HANDLE_SIGNALS
+#undef ASYNC_CAN_BLOCK_SIGNALS
+#undef ASYNC_CAN_MONITOR_SIGNALS
+#undef ASYNC_CAN_OBTAIN_SIGNALS
+
+#ifdef HAVE_SIGNAL_H
+#define ASYNC_CAN_HANDLE_SIGNALS
+#include <signal.h>
+
+#ifdef SIG_SETMASK
+#define ASYNC_CAN_BLOCK_SIGNALS
+#define ASYNC_CAN_MONITOR_SIGNALS
+
+#ifdef SIGRTMIN
+#define ASYNC_CAN_OBTAIN_SIGNALS
+#endif /* SIGRTMIN */
+#endif /* SIG_SETMASK */
+#endif /* HAVE_SIGNAL_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef ASYNC_CAN_HANDLE_SIGNALS
+/* Type sighandler_t isn't defined on all platforms. */
+#define ASYNC_SIGNAL_HANDLER(name) void name (int signalNumber)
+typedef ASYNC_SIGNAL_HANDLER(AsyncSignalHandler);
+
+extern int asyncHandleSignal (int signalNumber, AsyncSignalHandler *newHandler, AsyncSignalHandler **oldHandler);
+extern int asyncIgnoreSignal (int signalNumber, AsyncSignalHandler **oldHandler);
+extern int asyncRevertSignal (int signalNumber, AsyncSignalHandler **oldHandler);
+extern ASYNC_SIGNAL_HANDLER(asyncEmptySignalHandler);
+
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+extern int asyncSetSignalBlocked (int signalNumber, int state);
+extern int asyncIsSignalBlocked (int signalNumber);
+
+#define ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(name) void name (void *data)
+typedef ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(AsyncWithSignalsBlockedFunction);
+
+extern int asyncWithSignalsBlocked (
+  const sigset_t *mask,
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+);
+
+extern int asyncWithSignalBlocked (
+  int number,
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+);
+
+extern int asyncWithAllSignalsBlocked (
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+);
+
+extern int asyncWithObtainableSignalsBlocked (
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+);
+
+extern int asyncBlockObtainableSignals (void);
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+
+#ifdef ASYNC_CAN_MONITOR_SIGNALS
+typedef struct {
+  int signal;
+  void *data;
+} AsyncSignalCallbackParameters;
+
+#define ASYNC_SIGNAL_CALLBACK(name) int name (const AsyncSignalCallbackParameters *parameters)
+typedef ASYNC_SIGNAL_CALLBACK(AsyncSignalCallback);
+
+extern int asyncMonitorSignal (
+  AsyncHandle *handle, int signal,
+  AsyncSignalCallback *callback, void *data
+);
+#endif /* ASYNC_CAN_MONITOR_SIGNALS */
+
+
+#ifdef ASYNC_CAN_OBTAIN_SIGNALS
+extern int asyncClaimSignalNumber (int signal);
+extern int asyncReleaseSignalNumber (int signal);
+
+extern int asyncObtainSignalNumber (void);
+extern int asyncRelinquishSignalNumber (int signal);
+
+#endif /* ASYNC_CAN_OBTAIN_SIGNALS */
+#endif /* ASYNC_CAN_HANDLE_SIGNALS */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_SIGNAL */
diff --git a/Headers/async_task.h b/Headers/async_task.h
new file mode 100644
index 0000000..6ee0c5f
--- /dev/null
+++ b/Headers/async_task.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TASK
+#define BRLTTY_INCLUDED_ASYNC_TASK
+
+#include "async_types_task.h"
+#include "async_types_event.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int asyncAddTask (
+  AsyncEvent *event,
+  AsyncTaskCallback *callback, void *data
+);
+
+extern AsyncEvent *asyncNewAddTaskEvent (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TASK */
diff --git a/Headers/async_types_alarm.h b/Headers/async_types_alarm.h
new file mode 100644
index 0000000..bee1e5e
--- /dev/null
+++ b/Headers/async_types_alarm.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_ALARM
+#define BRLTTY_INCLUDED_ASYNC_TYPES_ALARM
+
+#include "timing_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const TimeValue *now;
+  void *data;
+} AsyncAlarmCallbackParameters;
+
+#define ASYNC_ALARM_CALLBACK(name) void name (const AsyncAlarmCallbackParameters *parameters)
+typedef ASYNC_ALARM_CALLBACK(AsyncAlarmCallback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_ALARM */
diff --git a/Headers/async_types_event.h b/Headers/async_types_event.h
new file mode 100644
index 0000000..973462b
--- /dev/null
+++ b/Headers/async_types_event.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_EVENT
+#define BRLTTY_INCLUDED_ASYNC_TYPES_EVENT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct AsyncEventStruct AsyncEvent;
+
+typedef struct {
+  void *eventData;
+  void *signalData;
+} AsyncEventCallbackParameters;
+
+#define ASYNC_EVENT_CALLBACK(name) void name (const AsyncEventCallbackParameters *parameters)
+typedef ASYNC_EVENT_CALLBACK(AsyncEventCallback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_EVENT */
diff --git a/Headers/async_types_handle.h b/Headers/async_types_handle.h
new file mode 100644
index 0000000..c3107a6
--- /dev/null
+++ b/Headers/async_types_handle.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_HANDLE
+#define BRLTTY_INCLUDED_ASYNC_TYPES_HANDLE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct AsyncHandleStruct *AsyncHandle;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_HANDLE */
diff --git a/Headers/async_types_io.h b/Headers/async_types_io.h
new file mode 100644
index 0000000..7a169b0
--- /dev/null
+++ b/Headers/async_types_io.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_IO
+#define BRLTTY_INCLUDED_ASYNC_TYPES_IO
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  void *data;
+  int error;
+} AsyncMonitorCallbackParameters;
+
+#define ASYNC_MONITOR_CALLBACK(name) int name (const AsyncMonitorCallbackParameters *parameters)
+typedef ASYNC_MONITOR_CALLBACK(AsyncMonitorCallback);
+
+typedef struct {
+  void *data;
+  const void *buffer;
+  size_t size;
+  size_t length;
+  int error;
+  unsigned char end : 1;
+} AsyncInputCallbackParameters;
+
+#define ASYNC_INPUT_CALLBACK(name) size_t name (const AsyncInputCallbackParameters *parameters)
+typedef ASYNC_INPUT_CALLBACK(AsyncInputCallback);
+
+typedef struct {
+  void *data;
+  const void *buffer;
+  size_t size;
+  int error;
+} AsyncOutputCallbackParameters;
+
+#define ASYNC_OUTPUT_CALLBACK(name) void name (const AsyncOutputCallbackParameters *parameters)
+typedef ASYNC_OUTPUT_CALLBACK(AsyncOutputCallback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_IO */
diff --git a/Headers/async_types_task.h b/Headers/async_types_task.h
new file mode 100644
index 0000000..f0ffb62
--- /dev/null
+++ b/Headers/async_types_task.h
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_TASK
+#define BRLTTY_INCLUDED_ASYNC_TYPES_TASK
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define ASYNC_TASK_CALLBACK(name) void name (void *data)
+typedef ASYNC_TASK_CALLBACK(AsyncTaskCallback);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_TASK */
diff --git a/Headers/async_types_wait.h b/Headers/async_types_wait.h
new file mode 100644
index 0000000..dcf9198
--- /dev/null
+++ b/Headers/async_types_wait.h
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_TYPES_WAIT
+#define BRLTTY_INCLUDED_ASYNC_TYPES_WAIT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define ASYNC_CONDITION_TESTER(name) int name (void *data)
+typedef ASYNC_CONDITION_TESTER(AsyncConditionTester);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_TYPES_WAIT */
diff --git a/Headers/async_wait.h b/Headers/async_wait.h
new file mode 100644
index 0000000..6c6edf5
--- /dev/null
+++ b/Headers/async_wait.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_WAIT
+#define BRLTTY_INCLUDED_ASYNC_WAIT
+
+#include "async_types_wait.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int asyncAwaitCondition (int timeout, AsyncConditionTester *testCondition, void *data);
+extern void asyncWait (int duration);
+extern void asyncWaitFor (AsyncConditionTester *testCondition, void *data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_WAIT */
diff --git a/Headers/atb.h b/Headers/atb.h
new file mode 100644
index 0000000..603a580
--- /dev/null
+++ b/Headers/atb.h
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ATB
+#define BRLTTY_INCLUDED_ATB
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct AttributesTableStruct AttributesTable;
+extern AttributesTable *attributesTable;
+
+extern void lockAttributesTable (void);
+extern void unlockAttributesTable (void);
+
+extern AttributesTable *compileAttributesTable (const char *name);
+extern void destroyAttributesTable (AttributesTable *table);
+
+extern char *ensureAttributesTableExtension (const char *path);
+extern char *makeAttributesTablePath (const char *directory, const char *name);
+
+extern int replaceAttributesTable (const char *directory, const char *name);
+
+extern unsigned char convertAttributesToDots (AttributesTable *table, unsigned char attributes);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ATB */
diff --git a/Headers/auth.h b/Headers/auth.h
new file mode 100644
index 0000000..ace9ebf
--- /dev/null
+++ b/Headers/auth.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_AUTH
+#define BRLTTY_INCLUDED_AUTH
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct AuthDescriptorStruct AuthDescriptor;
+extern AuthDescriptor *authBeginClient (const char *parameter);
+extern AuthDescriptor *authBeginServer (const char *parameter);
+extern void authEnd (AuthDescriptor *auth);
+extern int authPerform (AuthDescriptor *auth, FileDescriptor fd);
+
+extern void formatAddress (
+  char *buffer, size_t bufferSize,
+  const void *address, socklen_t addressSize
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_AUTH */
diff --git a/Headers/beep.h b/Headers/beep.h
new file mode 100644
index 0000000..9c69c0b
--- /dev/null
+++ b/Headers/beep.h
@@ -0,0 +1,42 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BEEP
+#define BRLTTY_INCLUDED_BEEP
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint16_t BeepFrequency;
+typedef uint16_t BeepDuration;
+
+extern int playBeep (BeepFrequency frequency, BeepDuration duration);
+
+extern int canBeep (void);
+extern int synchronousBeep (BeepFrequency frequency, BeepDuration duration);
+extern int asynchronousBeep (BeepFrequency frequency, BeepDuration duration);
+extern int startBeep (BeepFrequency frequency);
+extern int stopBeep (void);
+extern void endBeep (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BEEP */
diff --git a/Headers/bell.h b/Headers/bell.h
new file mode 100644
index 0000000..71d1dcc
--- /dev/null
+++ b/Headers/bell.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BELL
+#define BRLTTY_INCLUDED_BELL
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int setConsoleBellMonitoring (int on);
+extern void alertConsoleBell (void);
+
+extern int canMonitorConsoleBell (void);
+extern int startMonitoringConsoleBell (void);
+extern void stopMonitoringConsoleBell (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BELL */
diff --git a/Headers/bitfield.h b/Headers/bitfield.h
new file mode 100644
index 0000000..ff2d83c
--- /dev/null
+++ b/Headers/bitfield.h
@@ -0,0 +1,106 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BITFIELD
+#define BRLTTY_INCLUDED_BITFIELD
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define HIGH_NIBBLE(byte) ((byte) & 0XF0)
+#define LOW_NIBBLE(byte) ((byte) & 0XF)
+
+static inline void
+swapBytes (unsigned char *byte1, unsigned char *byte2) {
+  unsigned char byte = *byte1;
+  *byte1 = *byte2;
+  *byte2 = byte;
+}
+
+typedef union {
+  uint8_t bytes[0];
+  uint16_t u16;
+  uint32_t u32;
+  uint64_t u64;
+} BytesOverlay;
+
+#define GET_ENDIAN_FUNCTION(which, bits) \
+  static inline uint##bits##_t get##which##Endian##bits (uint##bits##_t from)
+
+#define PUT_ENDIAN_FUNCTION(which, bits) \
+  static inline void put##which##Endian##bits (uint##bits##_t *to, uint##bits##_t from)
+
+#define DEFINE_PHYSICAL_ENDIAN_FUNCTIONS(bits) \
+  GET_ENDIAN_FUNCTION(Native, bits) { \
+    return from; \
+  } \
+  \
+  GET_ENDIAN_FUNCTION(Other, bits) { \
+    BytesOverlay overlay = {.u##bits = from}; \
+    uint8_t *first = overlay.bytes; \
+    uint8_t *second = first + (bits / 8); \
+    \
+    do { \
+      swapBytes(first++, --second); \
+    } while (first != second); \
+    \
+    return overlay.u##bits; \
+  } \
+  \
+  PUT_ENDIAN_FUNCTION(Native, bits) { \
+    *to = getNativeEndian##bits(from); \
+  } \
+  \
+  PUT_ENDIAN_FUNCTION(Other, bits) { \
+    *to = getOtherEndian##bits(from); \
+  }
+
+DEFINE_PHYSICAL_ENDIAN_FUNCTIONS(16)
+DEFINE_PHYSICAL_ENDIAN_FUNCTIONS(32)
+DEFINE_PHYSICAL_ENDIAN_FUNCTIONS(64)
+
+#define DEFINE_ENDIAN_FUNCTIONS_FOR_BITS(bits, logical, physical) \
+  GET_ENDIAN_FUNCTION(logical, bits) { \
+    return get##physical##Endian##bits(from); \
+  } \
+  \
+  PUT_ENDIAN_FUNCTION(logical, bits) { \
+    put##physical##Endian##bits(to, from); \
+  }
+
+#define DEFINE_ENDIAN_FUNCTIONS(logical, physical) \
+  DEFINE_ENDIAN_FUNCTIONS_FOR_BITS(16, logical, physical) \
+  DEFINE_ENDIAN_FUNCTIONS_FOR_BITS(32, logical, physical) \
+  DEFINE_ENDIAN_FUNCTIONS_FOR_BITS(64, logical, physical)
+
+#ifdef WORDS_BIGENDIAN
+  DEFINE_ENDIAN_FUNCTIONS(Little, Other)
+  DEFINE_ENDIAN_FUNCTIONS(Big, Native)
+#else /* WORDS_BIGENDIAN */
+  DEFINE_ENDIAN_FUNCTIONS(Little, Native)
+  DEFINE_ENDIAN_FUNCTIONS(Big, Other)
+#endif /* WORDS_BIGENDIAN */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BITFIELD */
diff --git a/Headers/bitmask.h b/Headers/bitmask.h
new file mode 100644
index 0000000..e96942b
--- /dev/null
+++ b/Headers/bitmask.h
@@ -0,0 +1,68 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BITMASK
+#define BRLTTY_INCLUDED_BITMASK
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* These macros are meant for internal use only. */
+#define BITMASK_ELEMENT_SIZE(element) (sizeof(element) * 8)
+#define BITMASK_INDEX(bit,size) ((bit) / (size))
+#define BITMASK_SHIFT(bit,size) ((bit) % (size))
+#define BITMASK_ELEMENT_COUNT(bits,size) (BITMASK_INDEX((bits)-1, (size)) + 1)
+#define BITMASK_ELEMENT(name,bit) ((name)[BITMASK_INDEX((bit), BITMASK_ELEMENT_SIZE((name)[0]))])
+#define BITMASK_BIT(name,bit) (UINTMAX_C(1) << BITMASK_SHIFT((bit), BITMASK_ELEMENT_SIZE((name)[0])))
+
+/* These macros are for public use. */
+#define BITMASK(name,bits,type) unsigned type name[BITMASK_ELEMENT_COUNT((bits), BITMASK_ELEMENT_SIZE(type))]
+#define BITMASK_SIZE(name) BITMASK_ELEMENT_SIZE((name))
+#define BITMASK_ZERO(name) memset((name), 0, sizeof(name))
+#define BITMASK_CLEAR(name,bit) (BITMASK_ELEMENT((name), (bit)) &= ~BITMASK_BIT((name), (bit)))
+#define BITMASK_SET(name,bit) (BITMASK_ELEMENT((name), (bit)) |= BITMASK_BIT((name), (bit)))
+#define BITMASK_TEST(name,bit) (BITMASK_ELEMENT((name), (bit)) & BITMASK_BIT((name), (bit)))
+
+static inline unsigned char
+popcount (unsigned int bits) {
+#ifdef HAVE_BUILTIN_POPCOUNT
+  return __builtin_popcount(bits);
+#else /* __builtin_popcount */
+  unsigned char count = 0;
+
+  while (bits) {
+    if (bits & 0X1) count += 1;
+    bits >>= 1;
+  }
+
+  return count;
+#endif /* __builtin_popcount */
+}
+
+#define BITMASK_COUNT(name,variable) \
+unsigned int variable = 0; \
+for (int i=0; i<ARRAY_COUNT(name); i+=1) variable += popcount(name[i]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BITMASK */
diff --git a/Headers/brl_base.h b/Headers/brl_base.h
new file mode 100644
index 0000000..c790c3c
--- /dev/null
+++ b/Headers/brl_base.h
@@ -0,0 +1,251 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_BASE
+#define BRLTTY_INCLUDED_BRL_BASE
+
+#include "brl_types.h"
+#include "gio_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define KEY_TABLE_LIST_REFERENCE const KeyTableDefinition *const *
+#define KEY_TABLE_LIST_SYMBOL CONCATENATE(brl_ktb_,DRIVER_CODE)
+#define KEY_TABLE_LIST_DECLARATION const KeyTableDefinition *const KEY_TABLE_LIST_SYMBOL[]
+#define LAST_KEY_TABLE_DEFINITION NULL
+#define BEGIN_KEY_TABLE_LIST \
+  extern KEY_TABLE_LIST_DECLARATION; \
+  KEY_TABLE_LIST_DECLARATION = {
+#define END_KEY_TABLE_LIST LAST_KEY_TABLE_DEFINITION};
+
+#define BRL_KEY_GROUP(drv,grp) drv ## _GRP_ ## grp
+#define BRL_KEY_NAME(drv,grp,key) drv ## _ ## grp ## _ ## key
+
+#define BRL_KEY_GROUP_ENTRY(drv,grp,nam) KEY_GROUP_ENTRY(BRL_KEY_GROUP(drv, grp), nam)
+#define BRL_KEY_NUMBER_ENTRY(drv,grp,num,nam) {.value={.group=BRL_KEY_GROUP(drv, grp), .number=(num)}, .name=nam}
+#define BRL_KEY_NAME_ENTRY(drv,grp,key,nam) BRL_KEY_NUMBER_ENTRY(drv, grp, BRL_KEY_NAME(drv, grp, key), nam)
+
+static inline void
+setBrailleKeyTable (BrailleDisplay *brl, const KeyTableDefinition *ktd) {
+  brl->keyBindings = ktd->bindings;
+  brl->keyNames = ktd->names;
+}
+
+#define TRANSLATION_TABLE_SIZE 0X100
+typedef unsigned char TranslationTable[TRANSLATION_TABLE_SIZE];
+
+#define DOTS_TABLE_SIZE 8
+typedef unsigned char DotsTable[DOTS_TABLE_SIZE];
+
+extern const DotsTable dotsTable_ISO11548_1;
+extern const DotsTable dotsTable_rotated;
+
+extern void makeTranslationTable (const DotsTable dots, TranslationTable table);
+extern void reverseTranslationTable (const TranslationTable from, TranslationTable to);
+
+extern void setOutputTable (const TranslationTable table);
+extern void makeOutputTable (const DotsTable dots);
+extern void *translateOutputCells (unsigned char *target, const unsigned char *source, size_t count);
+extern unsigned char translateOutputCell (unsigned char cell);
+
+extern void makeInputTable (void);
+extern void *translateInputCells (unsigned char *target, const unsigned char *source, size_t count);
+extern unsigned char translateInputCell (unsigned char cell);
+
+#define MAKE_OUTPUT_TABLE(dot1, dot2, dot3, dot4, dot5, dot6, dot7, dot8) \
+  do {                                                                    \
+    static const DotsTable dots = {(dot1), (dot2), (dot3), (dot4),        \
+                                   (dot5), (dot6), (dot7), (dot8)};       \
+    makeOutputTable(dots);                                                \
+  } while (0)
+
+extern int awaitBrailleInput (BrailleDisplay *brl, int timeout);
+
+typedef int BrailleSessionInitializer (BrailleDisplay *brl);
+
+extern int connectBrailleResource (
+  BrailleDisplay *brl,
+  const char *identifier,
+  const GioDescriptor *descriptor,
+  BrailleSessionInitializer *initializeSession
+);
+
+typedef int BrailleSessionEnder (BrailleDisplay *brl);
+
+extern void disconnectBrailleResource (
+  BrailleDisplay *brl,
+  BrailleSessionEnder *endSession
+);
+
+typedef enum {
+  BRL_PVR_INVALID,
+  BRL_PVR_INCLUDE,
+  BRL_PVR_EXCLUDE,
+  BRL_PVR_IGNORE
+} BraillePacketVerifierResult;
+
+typedef BraillePacketVerifierResult BraillePacketVerifier (
+  BrailleDisplay *brl,
+  unsigned char *bytes, size_t size,
+  size_t *length, void *data
+);
+
+extern size_t readBraillePacket (
+  BrailleDisplay *brl,
+  GioEndpoint *endpoint,
+  void *packet, size_t size,
+  BraillePacketVerifier *verifyPacket, void *data
+);
+
+extern int writeBraillePacket (
+  BrailleDisplay *brl, GioEndpoint *endpoint,
+  const void *packet, size_t size
+);
+
+extern int writeBrailleMessage(BrailleDisplay *brl, GioEndpoint *endpoint,
+                               unsigned int type, const void *packet,
+                               size_t size);
+
+extern int acknowledgeBrailleMessage (BrailleDisplay *brl);
+extern void endBrailleMessages(BrailleDisplay *brl);
+
+typedef struct {
+  size_t *input;
+  size_t *output;
+  size_t *feature;
+  unsigned char identifier;
+} BrailleReportSizeEntry;
+
+extern int getBrailleReportSizes (BrailleDisplay *brl, const BrailleReportSizeEntry *table);
+extern int getBrailleReportSize (BrailleDisplay *brl, unsigned char identifier, HidReportSize *size);
+
+typedef int BrailleRequestWriter (BrailleDisplay *brl);
+
+typedef size_t BraillePacketReader (
+  BrailleDisplay *brl,
+  void *packet, size_t size
+);
+
+typedef enum {
+  BRL_RSP_CONTINUE,
+  BRL_RSP_DONE,
+  BRL_RSP_FAIL,
+  BRL_RSP_UNEXPECTED
+} BrailleResponseResult;
+
+typedef BrailleResponseResult BrailleResponseHandler (
+  BrailleDisplay *brl,
+  const void *packet, size_t size
+);
+
+extern int probeBrailleDisplay (
+  BrailleDisplay *brl, unsigned int retryLimit,
+  GioEndpoint *endpoint, int inputTimeout,
+  BrailleRequestWriter *writeRequest,
+  BraillePacketReader *readPacket, void *responsePacket, size_t responseSize,
+  BrailleResponseHandler *handleResponse
+);
+
+extern void releaseBrailleKeys (BrailleDisplay *brl);
+
+typedef uint32_t KeyNumberSet;
+#define KEY_NUMBER_BIT(number) (UINT32_C(1) << (number))
+
+static inline int
+isKeyNumberIncluded (KeyNumberSet set, KeyNumber number) {
+  return !!(set & KEY_NUMBER_BIT(number));
+}
+
+static inline void
+setKeyNumberIncluded (KeyNumberSet *set, KeyNumber number, int yes) {
+  KeyNumberSet bit = KEY_NUMBER_BIT(number);
+
+  if (yes) {
+    *set |= bit;
+  } else {
+    *set &= ~bit;
+  }
+}
+
+typedef struct {
+  KeyNumber from;
+  KeyNumber to;
+} KeyNumberMapEntry;
+
+extern KeyNumberSet mapKeyNumbers (KeyNumberSet fromKeys, const KeyNumberMapEntry *map, size_t count);
+extern void remapKeyNumbers (KeyNumberSet *keys, const KeyNumberMapEntry *map, size_t count);
+
+typedef struct {
+  KeyNumberSet from;
+  KeyNumberSet to;
+} KeyNumberSetMapEntry;
+
+typedef struct KeyNumberSetMapStruct KeyNumberSetMap;
+extern KeyNumberSetMap *newKeyNumberSetMap (const KeyNumberSetMapEntry *entries, size_t count);
+extern void destroyKeyNumberSetMap (KeyNumberSetMap *map);
+extern KeyNumberSet mapKeyNumberSet (KeyNumberSet keys, const KeyNumberSetMap *map);
+extern void remapKeyNumberSet (KeyNumberSet *keys, const KeyNumberSetMap *map);
+
+extern KeyNumberSet makeKeyNumberSet (KEY_NAME_TABLES_REFERENCE keys, KeyGroup group);
+
+extern int enqueueKeyEvent (
+  BrailleDisplay *brl,
+  KeyGroup group, KeyNumber number, int press
+);
+
+extern int enqueueKeyEvents (
+  BrailleDisplay *brl,
+  KeyNumberSet set, KeyGroup group, KeyNumber number, int press
+);
+
+extern int enqueueKey (
+  BrailleDisplay *brl,
+  KeyGroup group, KeyNumber number
+);
+
+extern int enqueueKeys (
+  BrailleDisplay *brl,
+  KeyNumberSet set, KeyGroup group, KeyNumber number
+);
+
+extern int enqueueUpdatedKeys (
+  BrailleDisplay *brl,
+  KeyNumberSet new, KeyNumberSet *old, KeyGroup group, KeyNumber number
+);
+
+extern int enqueueUpdatedKeyGroup (
+  BrailleDisplay *brl,
+  unsigned int count,
+  const unsigned char *new,
+  unsigned char *old,
+  KeyGroup group
+);
+
+extern int enqueueXtScanCode (
+  BrailleDisplay *brl,
+  unsigned char code, unsigned char escape,
+  KeyGroup group00, KeyGroup groupE0, KeyGroup groupE1
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_BASE */
diff --git a/Headers/brl_cmds.h b/Headers/brl_cmds.h
new file mode 100644
index 0000000..cd3278f
--- /dev/null
+++ b/Headers/brl_cmds.h
@@ -0,0 +1,422 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_CMDS
+#define BRLTTY_INCLUDED_BRL_CMDS
+
+#include "brl_dots.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define BRL_BITS_ARG 8
+#define BRL_BITS_BLK 8
+#define BRL_BITS_FLG 8
+#define BRL_BITS_EXT 8
+
+#define BRL_SHIFT_ARG 0
+#define BRL_SHIFT_BLK (BRL_SHIFT_ARG + BRL_BITS_ARG)
+#define BRL_SHIFT_FLG (BRL_SHIFT_BLK + BRL_BITS_BLK)
+#define BRL_SHIFT_EXT (BRL_SHIFT_FLG + BRL_BITS_FLG)
+
+#define BRL_CODE_MASK(name) ((1 << BRL_BITS_##name) - 1)
+#define BRL_CODE_GET(name,code) (((code) >> BRL_SHIFT_##name) & BRL_CODE_MASK(name))
+#define BRL_CODE_PUT(name,value) ((value) << BRL_SHIFT_##name)
+#define BRL_CODE_SET(name,value) BRL_CODE_PUT(name, ((value) & BRL_CODE_MASK(name)))
+
+#define BRL_ARG_PUT(arg) BRL_CODE_PUT(ARG, (arg))
+#define BRL_BLK_PUT(blk) BRL_CODE_PUT(BLK, (blk))
+#define BRL_FLG_PUT(fLG) BRL_CODE_PUT(FLG, (flg))
+#define BRL_EXT_PUT(ext) BRL_CODE_PUT(EXT, (ext))
+
+#define BRL_MSK(name) BRL_CODE_PUT(name, BRL_CODE_MASK(name))
+#define BRL_MSK_ARG BRL_MSK(ARG)
+#define BRL_MSK_BLK BRL_MSK(BLK)
+#define BRL_MSK_FLG BRL_MSK(FLG)
+#define BRL_MSK_EXT BRL_MSK(EXT)
+#define BRL_MSK_CMD (BRL_MSK_BLK | BRL_MSK_ARG)
+
+#define BRL_ARG_GET(code) (BRL_CODE_GET(ARG, (code)) | (BRL_CODE_GET(EXT, (code)) << BRL_BITS_ARG))
+#define BRL_ARG_SET(value) (BRL_CODE_SET(ARG, (value)) | BRL_CODE_SET(EXT, ((value) >> BRL_BITS_ARG)))
+
+/* The following command codes are return values for brl_readCommand().
+ * The BRL_CMD_* codes are guaranteed to be between 1 and 255
+ * inclusive, with the exception of BRL_CMD_NOOP = 0.
+ *
+ * brl_readCommand() should return EOF if there are no keys to return.  If,
+ * however, if it has masked out the key for internal use, it may return
+ * BRL_CMD_NOOP - in which case it will be called again immediately, rather
+ * than waiting for the next cycle.  This can save internal nesting ...
+ */
+
+/*
+ * Please comment all BRL_CMD_* definitions. They are
+ * used during automatic command table generation.
+ */
+
+typedef enum {
+  /* special commands which must be first and remain in order */
+  BRL_CMD_NOOP = 0 /* do nothing */,
+
+  /* vertical motion */
+  BRL_CMD_LNUP /* go up one line */,
+  BRL_CMD_LNDN /* go down one line */,
+  BRL_CMD_WINUP /* go up several lines */,
+  BRL_CMD_WINDN /* go down several lines */,
+  BRL_CMD_PRDIFLN /* go up to nearest line with different content */,
+  BRL_CMD_NXDIFLN /* go down to nearest line with different content */,
+  BRL_CMD_ATTRUP /* go up to nearest line with different highlighting */,
+  BRL_CMD_ATTRDN /* go down to nearest line with different highlighting */,
+  BRL_CMD_TOP /* go to top line */,
+  BRL_CMD_BOT /* go to bottom line */,
+  BRL_CMD_TOP_LEFT /* go to beginning of top line */,
+  BRL_CMD_BOT_LEFT /* go to beginning of bottom line */,
+  BRL_CMD_PRPGRPH /* go up to first line of paragraph */,
+  BRL_CMD_NXPGRPH /* go down to first line of next paragraph */,
+  BRL_CMD_PRPROMPT /* go up to previous command prompt */,
+  BRL_CMD_NXPROMPT /* go down to next command prompt */,
+  BRL_CMD_PRSEARCH /* search backward for clipboard text */,
+  BRL_CMD_NXSEARCH /* search forward for clipboard text */,
+
+  /* horizontal motion */
+  BRL_CMD_CHRLT /* go left one character */,
+  BRL_CMD_CHRRT /* go right one character */,
+  BRL_CMD_HWINLT /* go left half a braille window */,
+  BRL_CMD_HWINRT /* go right half a braille window */,
+  BRL_CMD_FWINLT /* go backward one braille window */,
+  BRL_CMD_FWINRT /* go forward one braille window */,
+  BRL_CMD_FWINLTSKIP /* go backward skipping blank braille windows */,
+  BRL_CMD_FWINRTSKIP /* go forward skipping blank braille windows */,
+  BRL_CMD_LNBEG /* go to beginning of line */,
+  BRL_CMD_LNEND /* go to end of line */,
+
+  /* implicit motion */
+  BRL_CMD_HOME /* go to screen cursor */,
+  BRL_CMD_BACK /* go back after cursor tracking */,
+  BRL_CMD_RETURN /* go to screen cursor or go back after cursor tracking */,
+
+  /* feature activation and deactivation */
+  BRL_CMD_FREEZE /* set screen image frozen/unfrozen */,
+  BRL_CMD_DISPMD /* set display mode attributes/text */,
+  BRL_CMD_SIXDOTS /* set text style 6-dot/8-dot */,
+  BRL_CMD_SLIDEWIN /* set sliding braille window on/off */,
+  BRL_CMD_SKPIDLNS /* set skipping of lines with identical content on/off */,
+  BRL_CMD_SKPBLNKWINS /* set skipping of blank braille windows on/off */,
+  BRL_CMD_CSRVIS /* set screen cursor visibility on/off */,
+  BRL_CMD_CSRHIDE /* set hidden screen cursor on/off */,
+  BRL_CMD_CSRTRK /* set track screen cursor on/off */,
+  BRL_CMD_CSRSIZE /* set screen cursor style block/underline */,
+  BRL_CMD_CSRBLINK /* set screen cursor blinking on/off */,
+  BRL_CMD_ATTRVIS /* set attribute underlining on/off */,
+  BRL_CMD_ATTRBLINK /* set attribute blinking on/off */,
+  BRL_CMD_CAPBLINK /* set capital letter blinking on/off */,
+  BRL_CMD_TUNES /* set alert tunes on/off */,
+  BRL_CMD_AUTOREPEAT /* set autorepeat on/off */,
+  BRL_CMD_AUTOSPEAK /* set autospeak on/off */,
+
+  /* mode selection */
+  BRL_CMD_HELP /* enter/leave help display */,
+  BRL_CMD_INFO /* enter/leave status display */,
+  BRL_CMD_LEARN /* enter/leave command learn mode */,
+
+  /* preference setting */
+  BRL_CMD_PREFMENU /* enter/leave preferences menu */,
+  BRL_CMD_PREFSAVE /* save preferences to disk */,
+  BRL_CMD_PREFLOAD /* restore preferences from disk */,
+
+  /* menu navigation */
+  BRL_CMD_MENU_FIRST_ITEM /* go up to first item */,
+  BRL_CMD_MENU_LAST_ITEM /* go down to last item */,
+  BRL_CMD_MENU_PREV_ITEM /* go up to previous item */,
+  BRL_CMD_MENU_NEXT_ITEM /* go down to next item */,
+  BRL_CMD_MENU_PREV_SETTING /* select previous choice */,
+  BRL_CMD_MENU_NEXT_SETTING /* select next choice */,
+
+  /* speech controls */
+  BRL_CMD_MUTE /* stop speaking */,
+  BRL_CMD_SPKHOME /* go to current speaking position */,
+  BRL_CMD_SAY_LINE /* speak current line */,
+  BRL_CMD_SAY_ABOVE /* speak from top of screen through current line */,
+  BRL_CMD_SAY_BELOW /* speak from current line through bottom of screen */,
+  BRL_CMD_SAY_SLOWER /* decrease speaking rate */,
+  BRL_CMD_SAY_FASTER /* increase speaking rate */,
+  BRL_CMD_SAY_SOFTER /* decrease speaking volume */,
+  BRL_CMD_SAY_LOUDER /* increase speaking volume */,
+
+  /* virtual terminal switching */
+  BRL_CMD_SWITCHVT_PREV /* switch to the previous virtual terminal */,
+  BRL_CMD_SWITCHVT_NEXT /* switch to the next virtual terminal */,
+
+  /* miscellaneous */
+  BRL_CMD_CSRJMP_VERT /* bring screen cursor to current line */,
+  BRL_CMD_PASTE /* insert clipboard text after screen cursor */,
+  BRL_CMD_RESTARTBRL /* restart braille driver */,
+  BRL_CMD_RESTARTSPEECH /* restart speech driver */,
+
+  BRL_CMD_OFFLINE /* braille display temporarily unavailable */,
+
+  BRL_CMD_SHIFT /* cycle the Shift sticky input modifier (next, on, off) */,
+  BRL_CMD_UPPER /* cycle the Upper sticky input modifier (next, on, off) */,
+  BRL_CMD_CONTROL /* cycle the Control sticky input modifier (next, on, off) */,
+  BRL_CMD_META /* cycle the Meta (Left Alt) sticky input modifier (next, on,
+                  off) */
+  ,
+
+  BRL_CMD_TIME /* show current date and time */,
+  BRL_CMD_MENU_PREV_LEVEL /* go to previous menu level */,
+
+  BRL_CMD_ASPK_SEL_LINE /* set autospeak selected line on/off */,
+  BRL_CMD_ASPK_SEL_CHAR /* set autospeak selected character on/off */,
+  BRL_CMD_ASPK_INS_CHARS /* set autospeak inserted characters on/off */,
+  BRL_CMD_ASPK_DEL_CHARS /* set autospeak deleted characters on/off */,
+  BRL_CMD_ASPK_REP_CHARS /* set autospeak replaced characters on/off */,
+  BRL_CMD_ASPK_CMP_WORDS /* set autospeak completed words on/off */,
+
+  BRL_CMD_SPEAK_CURR_CHAR /* speak current character */,
+  BRL_CMD_SPEAK_PREV_CHAR /* go to and speak previous character */,
+  BRL_CMD_SPEAK_NEXT_CHAR /* go to and speak next character */,
+  BRL_CMD_SPEAK_CURR_WORD /* speak current word */,
+  BRL_CMD_SPEAK_PREV_WORD /* go to and speak previous word */,
+  BRL_CMD_SPEAK_NEXT_WORD /* go to and speak next word */,
+  BRL_CMD_SPEAK_CURR_LINE /* speak current line */,
+  BRL_CMD_SPEAK_PREV_LINE /* go to and speak previous line */,
+  BRL_CMD_SPEAK_NEXT_LINE /* go to and speak next line */,
+  BRL_CMD_SPEAK_FRST_CHAR /* go to and speak first non-blank character on line
+                           */
+  ,
+  BRL_CMD_SPEAK_LAST_CHAR /* go to and speak last non-blank character on line */
+  ,
+  BRL_CMD_SPEAK_FRST_LINE /* go to and speak first non-blank line on screen */,
+  BRL_CMD_SPEAK_LAST_LINE /* go to and speak last non-blank line on screen */,
+  BRL_CMD_DESC_CURR_CHAR /* describe current character */,
+  BRL_CMD_SPELL_CURR_WORD /* spell current word */,
+  BRL_CMD_ROUTE_CURR_LOCN /* bring screen cursor to speech cursor */,
+  BRL_CMD_SPEAK_CURR_LOCN /* speak speech cursor location */,
+  BRL_CMD_SHOW_CURR_LOCN /* set speech cursor visibility on/off */,
+
+  BRL_CMD_CLIP_SAVE /* save clipboard to disk */,
+  BRL_CMD_CLIP_RESTORE /* restore clipboard from disk */,
+
+  BRL_CMD_BRLUCDOTS /* set braille typing mode dots/text */,
+  BRL_CMD_BRLKBD /* set braille keyboard enabled/disabled */,
+
+  BRL_CMD_UNSTICK /* clear all sticky input modifiers */,
+  BRL_CMD_ALTGR /* cycle the AltGr (Right Alt) sticky input modifier (next, on,
+                   off) */
+  ,
+  BRL_CMD_GUI /* cycle the GUI (Windows) sticky input modifier (next, on, off)
+               */
+  ,
+
+  BRL_CMD_BRL_STOP /* stop the braille driver */,
+  BRL_CMD_BRL_START /* start the braille driver */,
+  BRL_CMD_SPK_STOP /* stop the speech driver */,
+  BRL_CMD_SPK_START /* start the speech driver */,
+  BRL_CMD_SCR_STOP /* stop the screen driver */,
+  BRL_CMD_SCR_START /* start the screen driver */,
+
+  BRL_CMD_SELECTVT_PREV /* bind to the previous virtual terminal */,
+  BRL_CMD_SELECTVT_NEXT /* bind to the next virtual terminal */,
+
+  BRL_CMD_PRNBWIN /* go backward to nearest non-blank braille window */,
+  BRL_CMD_NXNBWIN /* go forward to nearest non-blank braille window */,
+
+  BRL_CMD_TOUCH_NAV /* set touch navigation on/off */,
+
+  BRL_CMD_SPEAK_INDENT /* speak indent of current line */,
+  BRL_CMD_ASPK_INDENT /* set autospeak indent of current line on/off */,
+
+  BRL_CMD_REFRESH /* refresh braille display */,
+  BRL_CMD_INDICATORS /* show various device status indicators */,
+
+  BRL_CMD_TXTSEL_CLEAR /* clear the text selection */,
+  BRL_CMD_TXTSEL_ALL /* select all of the text */,
+  BRL_CMD_HOST_COPY /* copy selected text to host clipboard */,
+  BRL_CMD_HOST_CUT /* cut selected text to host clipboard */,
+  BRL_CMD_HOST_PASTE /* insert host clipboard text after screen cursor */,
+
+  BRL_CMD_GUI_TITLE /* show the window title */,
+  BRL_CMD_GUI_BRL_ACTIONS /* open the braille actions window */,
+  BRL_CMD_GUI_HOME /* go to the home screen */,
+  BRL_CMD_GUI_BACK /* go back to the previous screen */,
+  BRL_CMD_GUI_DEV_SETTINGS /* open the device settings window */,
+  BRL_CMD_GUI_DEV_OPTIONS /* open the device options window */,
+  BRL_CMD_GUI_APP_LIST /* open the application list window */,
+  BRL_CMD_GUI_APP_MENU /* open the application-specific menu */,
+  BRL_CMD_GUI_APP_ALERTS /* open the application alerts window */,
+
+  BRL_CMD_GUI_AREA_ACTV /* return to the active screen area */,
+  BRL_CMD_GUI_AREA_PREV /* switch to the previous screen area */,
+  BRL_CMD_GUI_AREA_NEXT /* switch to the next screen area */,
+
+  BRL_CMD_GUI_ITEM_FRST /* move to the first item in the screen area */,
+  BRL_CMD_GUI_ITEM_PREV /* move to the previous item in the screen area */,
+  BRL_CMD_GUI_ITEM_NEXT /* move to the next item in the screen area */,
+  BRL_CMD_GUI_ITEM_LAST /* move to the last item in the screen area */,
+
+  BRL_CMD_SAY_LOWER /* decrease speaking pitch */,
+  BRL_CMD_SAY_HIGHER /* increase speaking pitch */,
+  BRL_CMD_SAY_ALL /* speak from top of screen through bottom of screen */,
+
+  BRL_CMD_CONTRACTED /* set contracted/computer braille */,
+  BRL_CMD_COMPBRL6 /* set six/eight dot computer braille */,
+
+  BRL_CMD_PREFRESET /* reset preferences to defaults */,
+
+  BRL_basicCommandCount /* must be last */
+} BRL_BasicCommand;
+
+/*
+ * Please comment all BRL_BLK_* definitions. They are
+ * used during automatic help file generation.
+ */
+typedef enum {
+  BRL_BLK_BASIC = 0 /* (must be first) */,
+  BRL_BLK_ROUTE /* bring screen cursor to character */,
+  BRL_BLK_CLIP_NEW /* start new clipboard at character */,
+  BRL_BLK_CLIP_ADD /* append to clipboard from character */,
+  BRL_BLK_COPY_RECT /* rectangular copy to character */,
+  BRL_BLK_COPY_LINE /* linear copy to character */,
+  BRL_BLK_SWITCHVT /* switch to specific virtual terminal */,
+  BRL_BLK_PRINDENT /* go up to nearest line with less indent than character */,
+  BRL_BLK_NXINDENT /* go down to nearest line with less indent than character */
+  ,
+  BRL_BLK_DESCCHAR /* describe character */,
+  BRL_BLK_SETLEFT /* place left end of braille window at character */,
+  BRL_BLK_SETMARK /* remember current braille window position */,
+  BRL_BLK_GOTOMARK /* go to remembered braille window position */,
+  BRL_BLK_GOTOLINE /* go to selected line */,
+  BRL_BLK_PRDIFCHAR /* go up to nearest line with different character */,
+  BRL_BLK_NXDIFCHAR /* go down to nearest line with different character */,
+  BRL_BLK_CLIP_COPY /* copy characters to clipboard */,
+  BRL_BLK_CLIP_APPEND /* append characters to clipboard */,
+  BRL_BLK_PASTE_HISTORY /* insert clipboard history entry after screen cursor */
+  ,
+  BRL_BLK_SET_TEXT_TABLE /* set text table */,
+  BRL_BLK_SET_ATTRIBUTES_TABLE /* set attributes table */,
+  BRL_BLK_SET_CONTRACTION_TABLE /* set contraction table */,
+  BRL_BLK_SET_KEYBOARD_TABLE /* set keyboard table */,
+  BRL_BLK_SET_LANGUAGE_PROFILE /* set language profile */,
+  BRL_BLK_ROUTE_LINE /* bring screen cursor to line */,
+  BRL_BLK_REFRESH_LINE /* refresh braille line */,
+  BRL_BLK_TXTSEL_START /* start text selection */,
+  BRL_BLK_TXTSEL_SET /* set text selection */,
+  BRL_BLK_ROUTE_SPEECH /* bring speech cursor to character */,
+  BRL_BLK_1D /* (reserved) */,
+  BRL_BLK_SELECTVT /* bind to specific virtual terminal */,
+  BRL_BLK_ALERT /* render an alert */,
+  BRL_BLK_PASSKEY /* (emulate special key) */,
+  BRL_BLK_PASSCHAR /* type unicode character */,
+  BRL_BLK_PASSDOTS /* type braille dots */,
+  BRL_BLK_PASSAT /* AT (set 2) keyboard scan code */,
+  BRL_BLK_PASSXT /* XT (set 1) keyboard scan code */,
+  BRL_BLK_PASSPS2 /* PS/2 (set 3) keyboard scan code */,
+  BRL_BLK_CONTEXT /* switch to command context */,
+  BRL_BLK_TOUCH_AT /* current reading location */,
+  BRL_BLK_MACRO /* execute command macro */,
+  BRL_BLK_HOSTCMD /* run host command */,
+
+  BRL_blockCommandCount /* must be last */
+} BRL_BlockCommand;
+
+typedef enum {
+  BRL_KEY_ENTER,
+  BRL_KEY_TAB,
+  BRL_KEY_BACKSPACE,
+  BRL_KEY_ESCAPE,
+  BRL_KEY_CURSOR_LEFT,
+  BRL_KEY_CURSOR_RIGHT,
+  BRL_KEY_CURSOR_UP,
+  BRL_KEY_CURSOR_DOWN,
+  BRL_KEY_PAGE_UP,
+  BRL_KEY_PAGE_DOWN,
+  BRL_KEY_HOME,
+  BRL_KEY_END,
+  BRL_KEY_INSERT,
+  BRL_KEY_DELETE,
+  BRL_KEY_FUNCTION
+} BRL_Key;
+
+#define BRL_CMD_BLK(cmd) (BRL_BLK_##cmd << BRL_SHIFT_BLK)
+#define BRL_CMD_ARG(blk,arg) (BRL_CMD_BLK(blk) | BRL_ARG_SET((arg)))
+#define BRL_CMD_CHAR(wc) BRL_CMD_ARG(PASSCHAR, (wc))
+#define BRL_CMD_KEY(key) BRL_CMD_ARG(PASSKEY, (BRL_KEY_##key))
+#define BRL_CMD_KFN(n) BRL_CMD_KEY(FUNCTION + ((n) - 1))
+
+#define BRL_FLG_TOGGLE_ON   0X010000 /* enable feature */
+#define BRL_FLG_TOGGLE_OFF  0X020000 /* disable feature */
+#define BRL_FLG_TOGGLE_MASK (BRL_FLG_TOGGLE_ON | BRL_FLG_TOGGLE_OFF) /* mask for all toggle flags */
+
+#define BRL_FLG_MOTION_ROUTE  0X040000 /* bring screen cursor into braille window after function */
+#define BRL_FLG_MOTION_SCALED 0X080000 /* scale arg=0X00-0XFF to screen height */
+#define BRL_FLG_MOTION_TOLEFT 0X100000 /* go to beginning of line */
+
+#define BRL_FLG_INPUT_SHIFT   0X010000 /* shift key pressed */
+#define BRL_FLG_INPUT_UPPER   0X020000 /* convert to uppercase */
+#define BRL_FLG_INPUT_CONTROL 0X040000 /* control key pressed */
+#define BRL_FLG_INPUT_META    0X080000 /* meta (left alt) key pressed */
+#define BRL_FLG_INPUT_ALTGR   0X100000 /* altgr (right alt) key pressed */
+#define BRL_FLG_INPUT_GUI     0X200000 /* gui (windows) key pressed */
+#define BRL_FLG_INPUT_ESCAPED 0X400000 /* prefix with escape */
+
+#define BRL_FLG_KBD_RELEASE 0X010000 /* it is a release scan code */
+#define BRL_FLG_KBD_EMUL0   0X020000 /* it is an emulation 0 scan code */
+#define BRL_FLG_KBD_EMUL1   0X040000 /* it is an emulation 1 scan code */
+
+/* The bits for each braille dot.
+ *
+ * This is the same dot-to-bit mapping which is:
+ * +  specified by the ISO/TR 11548-1 standard
+ * +  used within the Unicode braille row:
+ */
+/*
+ * From least- to most-significant octal digit:
+ * +  the first contains dots 1-3
+ * +  the second contains dots 4-6
+ * +  the third contains dots 7-8
+ * 
+ * Here are a few ways to illustrate a braille cell:
+ *    By Dot   By Bit   As Octal
+ *    Number   Number    Digits
+ *     1  4     0  3    001  010
+ *     2  5     1  4    002  020
+ *     3  6     2  5    004  040
+ *     7  8     6  7    100  200
+ */
+#define BRL_ARG_DOT(n) BRL_ARG_SET(1 << ((n) - 1))
+#define BRL_DOT1 BRL_ARG_DOT(1) /* upper-left dot of standard braille cell */
+#define BRL_DOT2 BRL_ARG_DOT(2) /* middle-left dot of standard braille cell */
+#define BRL_DOT3 BRL_ARG_DOT(3) /* lower-left dot of standard braille cell */
+#define BRL_DOT4 BRL_ARG_DOT(4) /* upper-right dot of standard braille cell */
+#define BRL_DOT5 BRL_ARG_DOT(5) /* middle-right dot of standard braille cell */
+#define BRL_DOT6 BRL_ARG_DOT(6) /* lower-right dot of standard braille cell */
+#define BRL_DOT7 BRL_ARG_DOT(7) /* lower-left dot of computer braille cell */
+#define BRL_DOT8 BRL_ARG_DOT(8) /* lower-right dot of computer braille cell */
+#define BRL_DOTC BRL_ARG_DOT(9) /* chord (space bar on braille keyboard) */
+
+#define BRL_ALL_DOTS ( \
+  BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 | \
+  BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8 \
+)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_CMDS */
diff --git a/Headers/brl_custom.h b/Headers/brl_custom.h
new file mode 100644
index 0000000..61c8eaa
--- /dev/null
+++ b/Headers/brl_custom.h
@@ -0,0 +1,52 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_CUSTOM
+#define BRLTTY_INCLUDED_BRL_CUSTOM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  /* must be first - do not modify */
+  BRL_CUSTOM_BASIC_COMMAND_BASE = BRL_basicCommandCount - 1,
+
+  /* Define all custom basic commands below this point.
+   * Their names should begin with BRL_CMD_CUSTOM_.
+   * No values should be assigned.
+   */
+
+} BRL_CustomBasicCommand;
+
+typedef enum {
+  /* must be first - do not modify */
+  BRL_CUSTOM_BLOCK_COMMAND_BASE = BRL_blockCommandCount - 1,
+
+  /* Define all custom block commands below this point.
+   * Their names should begin with BRL_BLK_CUSTOM_.
+   * No values should be assigned.
+   */
+
+} BRL_CustomBlockCommand;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_CUSTOM */
diff --git a/Headers/brl_dots.h b/Headers/brl_dots.h
new file mode 100644
index 0000000..0753752
--- /dev/null
+++ b/Headers/brl_dots.h
@@ -0,0 +1,160 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_DOTS
+#define BRLTTY_INCLUDED_BRL_DOTS
+
+#include <strings.h>
+
+#ifdef __MINGW32__
+extern int ffs (int i);
+#endif /* __MINGW32__ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* The bits for each braille dot as defined by the ISO 11548-1 standard.
+ *
+ * From least- to most-significant octal digit:
+ * +  the first contains dots 1-3
+ * +  the second contains dots 4-6
+ * +  the third contains dots 7-8
+ * 
+ * Here are a few ways to illustrate a braille cell:
+ *    By Dot   By Bit   As Octal
+ *    Number   Number    Digits
+ *     1  4     0  3    001  010
+ *     2  5     1  4    002  020
+ *     3  6     2  5    004  040
+ *     7  8     6  7    100  200
+ */
+
+typedef unsigned char BrlDots;
+
+#define BRL_DOT_COUNT 8
+
+#define BRL_DOT(number) (BrlDots)(1 << ((number) - 1))
+#define BRL_DOT_1 BRL_DOT(1) /* upper-left dot of standard braille cell */
+#define BRL_DOT_2 BRL_DOT(2) /* middle-left dot of standard braille cell */
+#define BRL_DOT_3 BRL_DOT(3) /* lower-left dot of standard braille cell */
+#define BRL_DOT_4 BRL_DOT(4) /* upper-right dot of standard braille cell */
+#define BRL_DOT_5 BRL_DOT(5) /* middle-right dot of standard braille cell */
+#define BRL_DOT_6 BRL_DOT(6) /* lower-right dot of standard braille cell */
+#define BRL_DOT_7 BRL_DOT(7) /* lower-left dot of computer braille cell */
+#define BRL_DOT_8 BRL_DOT(8) /* lower-right dot of computer braille cell */
+
+#define BRL_DOTS_LEFT (BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_7)
+#define BRL_DOTS_RIGHT (BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_8)
+#define BRL_DOTS_ALL (BRL_DOTS_LEFT | BRL_DOTS_RIGHT)
+
+static inline BrlDots
+brlGetLeftDots (BrlDots cell) {
+  return cell & BRL_DOTS_LEFT;
+}
+
+static inline BrlDots
+brlGetRightDots (BrlDots cell) {
+  return cell & BRL_DOTS_RIGHT;
+}
+
+static inline BrlDots
+brlGetLeftDotsToRightDots (BrlDots cell) {
+  BrlDots dots = 0;
+  if (cell & BRL_DOT_1) dots |= BRL_DOT_4;
+  if (cell & BRL_DOT_2) dots |= BRL_DOT_5;
+  if (cell & BRL_DOT_3) dots |= BRL_DOT_6;
+  if (cell & BRL_DOT_7) dots |= BRL_DOT_8;
+  return dots;
+}
+
+static inline BrlDots
+brlGetRightDotsToLeftDots (BrlDots cell) {
+  BrlDots dots = 0;
+  if (cell & BRL_DOT_4) dots |= BRL_DOT_1;
+  if (cell & BRL_DOT_5) dots |= BRL_DOT_2;
+  if (cell & BRL_DOT_6) dots |= BRL_DOT_3;
+  if (cell & BRL_DOT_8) dots |= BRL_DOT_7;
+  return dots;
+}
+
+static inline BrlDots
+brlGetLeftDotsToRightDotsAlt (BrlDots cell) {
+  BrlDots dots = 0;
+  if (cell & BRL_DOT_1) dots |= BRL_DOT_6;
+  if (cell & BRL_DOT_2) dots |= BRL_DOT_5;
+  if (cell & BRL_DOT_3) dots |= BRL_DOT_4;
+  if (cell & BRL_DOT_7) dots |= BRL_DOT_8;
+  return dots;
+}
+
+static inline BrlDots
+brlGetRightDotsToLeftDotsAlt (BrlDots cell) {
+  BrlDots dots = 0;
+  if (cell & BRL_DOT_4) dots |= BRL_DOT_3;
+  if (cell & BRL_DOT_5) dots |= BRL_DOT_2;
+  if (cell & BRL_DOT_6) dots |= BRL_DOT_1;
+  if (cell & BRL_DOT_8) dots |= BRL_DOT_7;
+  return dots;
+}
+
+static inline BrlDots
+brlNumberToDot (char number) {
+  return ((number >= '1') && (number <= '8'))? BRL_DOT(number - '0'): 0;
+}
+
+static inline char
+brlDotToNumber (BrlDots dot) {
+  int shift = ffs(dot);
+  return shift? ((char)shift + '0'): 0;
+}
+
+static inline void
+brlRemapDot (BrlDots *dots, BrlDots from, BrlDots to) {
+  if (*dots & from) {
+    *dots &= ~from;
+    *dots |= to;
+  }
+}
+
+typedef BrlDots BrlDotTable[BRL_DOT_COUNT];
+
+typedef char BrlDotNumbersBuffer[BRL_DOT_COUNT + 1];
+
+static inline unsigned int
+brlDotsToNumbers (BrlDots dots, BrlDotNumbersBuffer numbers) {
+  char *number = numbers;
+
+  while (dots) {
+    int shift = ffs(dots) - 1;
+    dots -= 1 << shift;
+    *number++ = (char)shift + '1';
+  }
+
+  *number = 0;
+  return (unsigned int)(number - numbers);
+}
+
+/* The Unicode row used for literal braille dot representations. */
+#define BRL_UNICODE_ROW 0X2800
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_DOTS */
diff --git a/Headers/brl_driver.h b/Headers/brl_driver.h
new file mode 100644
index 0000000..b71d4e7
--- /dev/null
+++ b/Headers/brl_driver.h
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_DRIVER
+#define BRLTTY_INCLUDED_BRL_DRIVER
+
+#include <stdio.h>
+
+#include "brl_types.h"
+#include "brl_cmds.h"
+#include "brl_utils.h"
+#include "brl_base.h"
+#include "status_types.h"
+#include "io_generic.h"
+#include "cmd_enqueue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef BRLPARMS
+static const char *const brl_parameters[] = {BRLPARMS, NULL};
+#else /* BRLPARMS */
+#define brl_parameters NULL
+#endif /* BRLPARMS */
+
+#ifdef BRL_STATUS_FIELDS
+static const unsigned char brl_statusFields[] = {BRL_STATUS_FIELDS, sfEnd};
+#else /* BRL_STATUS_FIELDS */
+#define brl_statusFields NULL
+#endif /* BRL_STATUS_FIELDS */
+
+static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device);
+static void brl_destruct (BrailleDisplay *brl);
+
+static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context);
+static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *characters);
+
+#ifdef BRL_HAVE_STATUS_CELLS
+static int brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells);
+#else /* BRL_HAVE_STATUS_CELLS */
+#define brl_writeStatus NULL
+#endif /* BRL_HAVE_STATUS_CELLS */
+
+#ifdef BRL_HAVE_PACKET_IO
+static ssize_t brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size);
+static ssize_t brl_writePacket (BrailleDisplay *brl, const void *buffer, size_t size);
+static int brl_reset (BrailleDisplay *brl);
+#else /* BRL_HAVE_PACKET_IO */
+#define brl_readPacket NULL
+#define brl_writePacket NULL
+#define brl_reset NULL
+#endif /* BRL_HAVE_PACKET_IO */
+
+#ifndef BRLSYMBOL
+#define BRLSYMBOL CONCATENATE(brl_driver_,DRIVER_CODE)
+#endif /* BRLSYMBOL */
+
+extern const BrailleDriver BRLSYMBOL;
+const BrailleDriver BRLSYMBOL = {
+  DRIVER_DEFINITION_INITIALIZER,
+
+  brl_parameters,
+  brl_statusFields,
+
+  brl_construct,
+  brl_destruct,
+
+  brl_readCommand,
+  brl_writeWindow,
+  brl_writeStatus,
+
+  brl_readPacket,
+  brl_writePacket,
+  brl_reset
+};
+
+DRIVER_VERSION_DECLARATION(brl);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_DRIVER */
diff --git a/Headers/brl_types.h b/Headers/brl_types.h
new file mode 100644
index 0000000..b31a598
--- /dev/null
+++ b/Headers/brl_types.h
@@ -0,0 +1,169 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_TYPES
+#define BRLTTY_INCLUDED_BRL_TYPES
+
+#include "async_types_handle.h"
+#include "ctb_types.h"
+#include "driver.h"
+#include "gio_types.h"
+#include "ktb_types.h"
+#include "queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define BRL_NO_CURSOR -1
+
+typedef enum {
+  BRL_FIRMNESS_MINIMUM,
+  BRL_FIRMNESS_LOW,
+  BRL_FIRMNESS_MEDIUM,
+  BRL_FIRMNESS_HIGH,
+  BRL_FIRMNESS_MAXIMUM
+} BrailleFirmness;
+
+typedef enum {
+  BRL_SENSITIVITY_MINIMUM,
+  BRL_SENSITIVITY_LOW,
+  BRL_SENSITIVITY_MEDIUM,
+  BRL_SENSITIVITY_HIGH,
+  BRL_SENSITIVITY_MAXIMUM
+} TouchSensitivity;
+
+typedef enum {
+  BRL_TYPING_TEXT,
+  BRL_TYPING_DOTS
+} BrailleTypingMode;
+
+typedef struct BrailleDisplayStruct BrailleDisplay;
+typedef struct BrailleDataStruct BrailleData;
+
+typedef int RefreshBrailleDisplayMethod (BrailleDisplay *brl);
+typedef int RefreshBrailleRowMethod (BrailleDisplay *brl, int row);
+
+typedef int SetBrailleFirmnessMethod (BrailleDisplay *brl, BrailleFirmness setting);
+typedef int SetTouchSensitivityMethod (BrailleDisplay *brl, TouchSensitivity setting);
+typedef int SetAutorepeatPropertiesMethod (BrailleDisplay *brl, int on, int delay, int interval);
+
+typedef struct {
+  struct {
+    ContractionCache cache;
+    int length;
+
+    struct {
+      int *array;
+      unsigned int size;
+    } offsets;
+  } contracted;
+} BrailleRowDescriptor;
+
+struct BrailleDisplayStruct {
+  BrailleData *data;
+
+  RefreshBrailleDisplayMethod *refreshBrailleDisplay;
+  RefreshBrailleRowMethod *refreshBrailleRow;
+
+  SetBrailleFirmnessMethod *setBrailleFirmness;
+  SetTouchSensitivityMethod *setTouchSensitivity;
+  SetAutorepeatPropertiesMethod *setAutorepeatProperties;
+
+  unsigned int textColumns;
+  unsigned int textRows;
+  unsigned int statusColumns;
+  unsigned int statusRows;
+  unsigned char cellSize;
+
+  const char *keyBindings;
+  KEY_NAME_TABLES_REFERENCE keyNames;
+  KeyTable *keyTable;
+
+  GioEndpoint *gioEndpoint;
+  unsigned int writeDelay;
+
+  unsigned char *buffer;
+  void (*bufferResized)(unsigned int rows, unsigned int columns);
+
+  struct {
+    BrailleRowDescriptor *array;
+    unsigned int size;
+  } rowDescriptors;
+
+  int cursor;
+  unsigned char quality;
+
+  unsigned char noDisplay:1;
+  unsigned char hasFailed:1;
+  unsigned char isOffline:1;
+  unsigned char isSuspended:1;
+
+  unsigned char isCoreBuffer : 1;
+  unsigned char resizeRequired : 1;
+
+  unsigned char hideCursor:1;
+
+  struct {
+    Queue *messages;
+    AsyncHandle alarm;
+
+    struct {
+      int timeout;
+      unsigned int count;
+      unsigned int limit;
+    } missing;
+  } acknowledgements;
+};
+
+static inline int
+hasEightDotCells (const BrailleDisplay *brl) {
+  return brl->cellSize >= 8;
+}
+
+static inline int
+isMultiRow (const BrailleDisplay *brl) {
+  return brl->textRows > 1;
+}
+
+typedef struct {
+  DRIVER_DEFINITION_DECLARATION;
+
+  const char *const *parameters;
+  const unsigned char *statusFields;
+
+  int (*construct) (BrailleDisplay *brl, char **parameters, const char *device);
+  void (*destruct) (BrailleDisplay *brl);
+
+  int (*readCommand) (BrailleDisplay *brl, KeyTableCommandContext context);
+  int (*writeWindow) (BrailleDisplay *brl, const wchar_t *characters);
+  int (*writeStatus) (BrailleDisplay *brl, const unsigned char *cells);
+
+  ssize_t (*readPacket) (BrailleDisplay *brl, void *buffer, size_t size);
+  ssize_t (*writePacket) (BrailleDisplay *brl, const void *packet, size_t size);
+  int (*reset) (BrailleDisplay *brl);
+  
+  int (*readKey) (BrailleDisplay *brl);
+  int (*keyToCommand) (BrailleDisplay *brl, KeyTableCommandContext context, int key);
+} BrailleDriver;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_TYPES */
diff --git a/Headers/brl_utils.h b/Headers/brl_utils.h
new file mode 100644
index 0000000..c2cee94
--- /dev/null
+++ b/Headers/brl_utils.h
@@ -0,0 +1,70 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_UTILS
+#define BRLTTY_INCLUDED_BRL_UTILS
+
+#include "brl_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void drainBrailleOutput (BrailleDisplay *brl, int minimumDelay);
+
+extern void announceBrailleOffline (void);
+extern void announceBrailleOnline (void);
+
+extern void setBrailleOffline (BrailleDisplay *brl);
+extern void setBrailleOnline (BrailleDisplay *brl);
+
+extern int cellsHaveChanged (
+  unsigned char *cells, const unsigned char *new, unsigned int count,
+  unsigned int *from, unsigned int *to, unsigned char *force
+);
+
+extern int textHasChanged (
+  wchar_t *text, const wchar_t *new, unsigned int count,
+  unsigned int *from, unsigned int *to, unsigned char *force
+);
+
+extern int cursorHasChanged (int *cursor, int new, unsigned char *force);
+
+extern unsigned char toLowerDigit (unsigned char upper);
+
+typedef const unsigned char DigitsTable[11];
+typedef unsigned char MakeNumberFunction (int x);
+typedef unsigned char MakeFlagFunction (int number, int on);
+
+extern const DigitsTable landscapeDigits;
+extern MakeNumberFunction makeLandscapeNumber;
+extern MakeFlagFunction makeLandscapeFlag;
+
+extern const DigitsTable seascapeDigits;
+extern MakeNumberFunction makeSeascapeNumber;
+extern MakeFlagFunction makeSeascapeFlag;
+
+extern const DigitsTable portraitDigits;
+extern MakeNumberFunction makePortraitNumber;
+extern MakeFlagFunction makePortraitFlag;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_UTILS */
diff --git a/Headers/charset.h b/Headers/charset.h
new file mode 100644
index 0000000..15aeb14
--- /dev/null
+++ b/Headers/charset.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CHARSET
+#define BRLTTY_INCLUDED_CHARSET
+
+#include "utf8.h"
+#include "lock.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int isCharsetLatin1 (const char *name);
+
+extern char *getLocaleName (void);
+extern int isPosixLocale (const char *locale);
+
+extern const char *setCharset (const char *name);
+extern const char *getCharset (void);
+
+extern const char *getLocaleCharset (void);
+extern const char *getWcharCharset (void);
+
+extern size_t convertCharToUtf8 (char c, Utf8Buffer utf8);
+extern int convertUtf8ToChar (const char **utf8, size_t *utfs);
+
+extern wint_t convertCharToWchar (char c);
+extern int convertWcharToChar (wchar_t wc);
+
+extern int lockCharset (LockOptions options);
+extern void unlockCharset (void);
+
+typedef char *PathMaker (const char *directory, const char *name);
+extern char *getFileForLocale (const char *directory, PathMaker *pathMaker);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CHARSET */
diff --git a/Headers/cldr.h b/Headers/cldr.h
new file mode 100644
index 0000000..f73762c
--- /dev/null
+++ b/Headers/cldr.h
@@ -0,0 +1,67 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CLDR
+#define BRLTTY_INCLUDED_CLDR
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *sequence;
+  const char *name;
+  void *data;
+} CLDR_AnnotationHandlerParameters;
+
+#define CLDR_ANNOTATION_HANDLER(name) int name (const CLDR_AnnotationHandlerParameters *parameters)
+typedef CLDR_ANNOTATION_HANDLER(CLDR_AnnotationHandler);
+
+typedef struct CLDR_DocumentParserObjectStruct CLDR_DocumentParserObject;
+
+extern CLDR_DocumentParserObject *cldrNewDocumentParser (
+  CLDR_AnnotationHandler *handler, void *data
+);
+
+extern void cldrDestroyDocumentParser (
+  CLDR_DocumentParserObject *dpo
+);
+
+extern int cldrParseText (
+  CLDR_DocumentParserObject *dpo,
+  const char *text, size_t size, int final
+);
+
+extern int cldrParseDocument (
+  const char *document, size_t size,
+  CLDR_AnnotationHandler *handler, void *data
+);
+
+extern const char cldrAnnotationsDirectory[];
+extern const char cldrAnnotationsExtension[];
+
+extern int cldrParseFile (
+  const char *name,
+  CLDR_AnnotationHandler *handler, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CLDR */
diff --git a/Headers/clipboard.h b/Headers/clipboard.h
new file mode 100644
index 0000000..1f78f42
--- /dev/null
+++ b/Headers/clipboard.h
@@ -0,0 +1,64 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CLIPBOARD
+#define BRLTTY_INCLUDED_CLIPBOARD
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct ClipboardObjectStruct ClipboardObject;
+
+extern ClipboardObject *newClipboard (void);
+extern void destroyClipboard (ClipboardObject *cpb);
+
+extern int clearClipboardContent (ClipboardObject *cpb);
+extern int truncateClipboardContent (ClipboardObject *cpb, size_t length);
+
+extern int setClipboardContent (ClipboardObject *cpb, const wchar_t *characters, size_t length);
+extern int appendClipboardContent (ClipboardObject *cpb, const wchar_t *characters, size_t length);
+
+extern int setClipboardContentUTF8 (ClipboardObject *cpb, const char *text);
+extern int appendClipboardContentUTF8 (ClipboardObject *cpb, const char *text);
+
+extern const wchar_t *getClipboardContent (ClipboardObject *cpb, size_t *length);
+extern char *getClipboardContentUTF8 (ClipboardObject *cpb);
+extern size_t getClipboardContentLength (ClipboardObject *cpb);
+
+static inline int
+isClipboardEmpty (ClipboardObject *cpb) {
+  return !getClipboardContentLength(cpb);
+}
+
+extern int addClipboardHistory (ClipboardObject *cpb, const wchar_t *characters, size_t length);
+extern const wchar_t *getClipboardHistory (ClipboardObject *cpb, unsigned int index, size_t *length);
+
+extern ClipboardObject *getMainClipboard (void);
+extern void lockMainClipboard (void);
+extern void unlockMainClipboard (void);
+
+extern void onMainClipboardUpdated (void);
+extern int setMainClipboardContent (const char *content);
+extern char *getMainClipboardContent (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CLIPBOARD */
diff --git a/Headers/cmd.h b/Headers/cmd.h
new file mode 100644
index 0000000..c625ff0
--- /dev/null
+++ b/Headers/cmd.h
@@ -0,0 +1,61 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD
+#define BRLTTY_INCLUDED_CMD
+
+#include "strfmth.h"
+#include "cmd_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const CommandEntry commandTable[];
+extern int getCommandCount (void);
+extern const CommandEntry *findCommandEntry (int code);
+
+typedef struct {
+  const char *name;
+  int bit;
+} CommandModifierEntry;
+
+extern const CommandModifierEntry commandModifierTable_toggle[];
+extern const CommandModifierEntry commandModifierTable_motion[];
+extern const CommandModifierEntry commandModifierTable_row[];
+extern const CommandModifierEntry commandModifierTable_vertical[];
+extern const CommandModifierEntry commandModifierTable_input[];
+extern const CommandModifierEntry commandModifierTable_character[];
+extern const CommandModifierEntry commandModifierTable_braille[];
+extern const CommandModifierEntry commandModifierTable_keyboard[];
+
+typedef enum {
+  CDO_IncludeName    = 0X1,
+  CDO_IncludeOperand = 0X2,
+  CDO_DefaultOperand = 0X4
+} CommandDescriptionOption;
+
+extern STR_DECLARE_FORMATTER(describeCommand, int command, CommandDescriptionOption options);
+extern void logCommand (int command);
+extern void logTransformedCommand (int oldCommand, int newCommand);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD */
diff --git a/Headers/cmd_enqueue.h b/Headers/cmd_enqueue.h
new file mode 100644
index 0000000..b5d3c5e
--- /dev/null
+++ b/Headers/cmd_enqueue.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_ENQUEUE
+#define BRLTTY_INCLUDED_CMD_ENQUEUE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int enqueueCommand (int command);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_ENQUEUE */
diff --git a/Headers/cmd_types.h b/Headers/cmd_types.h
new file mode 100644
index 0000000..2e4ddd2
--- /dev/null
+++ b/Headers/cmd_types.h
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_TYPES
+#define BRLTTY_INCLUDED_CMD_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *name;
+  const char *description;
+  int code;
+
+  unsigned char isToggle : 1;
+  unsigned char isMotion : 1;
+  unsigned char isRow : 1;
+  unsigned char isVertical : 1;
+  unsigned char isHorizontal : 1;
+  unsigned char isPanning : 1;
+
+  unsigned char isInput : 1;
+  unsigned char isCharacter : 1;
+  unsigned char isBraille : 1;
+
+  unsigned char isKeyboard : 1;
+
+  unsigned char isRouting : 1;
+  unsigned char isColumn : 1;
+  unsigned char isOffset : 1;
+  unsigned char isRange : 1;
+} CommandEntry;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_TYPES */
diff --git a/Headers/cmdline.h b/Headers/cmdline.h
new file mode 100644
index 0000000..357bc5f
--- /dev/null
+++ b/Headers/cmdline.h
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMDLINE
+#define BRLTTY_INCLUDED_CMDLINE
+
+#include "cmdline_types.h"
+#include "datafile.h"
+#include "program.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern ProgramExitStatus processOptions(const CommandLineDescriptor *descriptor,
+                                        int *argumentCount,
+                                        char ***argumentVector);
+
+#define PROCESS_OPTIONS(descriptorVariable, argcVariable, argvVariable)    \
+  {                                                                        \
+    ProgramExitStatus exitStatus =                                         \
+        processOptions(&descriptorVariable, &argcVariable, &argvVariable); \
+    if (exitStatus == PROG_EXIT_FORCE) return PROG_EXIT_SUCCESS;           \
+    if (exitStatus != PROG_EXIT_SUCCESS) return exitStatus;                \
+  }
+
+extern void resetOptions(const CommandLineOptions *options);
+
+typedef struct {
+  void (*beginStream)(const char *name, void *data);
+  void (*endStream)(int incomplete, void *data);
+  DataFileParameters dataFileParameters;
+} InputFilesProcessingParameters;
+
+extern ProgramExitStatus processInputFiles(
+    char **paths, int count, const InputFilesProcessingParameters *parameters);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMDLINE */
diff --git a/Headers/cmdline_types.h b/Headers/cmdline_types.h
new file mode 100644
index 0000000..08eafb3
--- /dev/null
+++ b/Headers/cmdline_types.h
@@ -0,0 +1,112 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMDLINE_TYPES
+#define BRLTTY_INCLUDED_CMDLINE_TYPES
+
+#include "strfmth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define OPT_WORD_TRUE "on"
+#define OPT_WORD_FALSE "off"
+
+typedef enum {
+  OPT_Extend = 0X01,
+  OPT_Config = 0X02,
+  OPT_EnvVar = 0X04,
+  OPT_Format = 0X08,
+} CommandLineOptionFlag;
+
+typedef struct {
+  const char *word;
+  const char *argument;
+  const char *description;
+
+  struct {
+    const char *setting;
+    int (*adjust)(char **setting);
+  } internal;
+
+  unsigned char letter;
+  unsigned char bootParameter;
+  unsigned char flags;
+
+  union {
+    int *flag;
+    char **string;
+  } setting;
+
+  union {
+    const char *const *array;
+    STR_DECLARE_FORMATTER((*format), unsigned int index);
+  } strings;
+} CommandLineOption;
+
+typedef struct {
+  const CommandLineOption *table;
+  size_t count;
+} CommandLineOptions;
+
+#define BEGIN_OPTION_TABLE(name)                   \
+  static const CommandLineOption name##Table[] = { \
+      {.word = "help",                             \
+       .letter = 'h',                              \
+       .description = strtext("Show this usage summary, and then exit.")},
+
+#define END_OPTION_TABLE(name)             \
+  }                                        \
+  ;                                        \
+  static const CommandLineOptions name = { \
+      .table = name##Table,                \
+      .count = ARRAY_COUNT(name##Table),   \
+  };
+
+#define DECLARE_USAGE_NOTES(name) const char *const name[]
+#define BEGIN_USAGE_NOTES(name) DECLARE_USAGE_NOTES(name) = {
+#define END_USAGE_NOTES \
+  NULL                  \
+  }                     \
+  ;
+#define USAGE_NOTES(...) \
+  (const char *const *const[]) { __VA_ARGS__, NULL }
+
+typedef struct {
+  const char *purpose;
+  const char *parameters;
+  const char *const *const *notes;
+} CommandLineUsage;
+
+typedef struct {
+  const CommandLineOptions *options;
+
+  const char *applicationName;
+  char **configurationFile;
+  int *doEnvironmentVariables;
+  int *doBootParameters;
+
+  const CommandLineUsage usage;
+} CommandLineDescriptor;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMDLINE_TYPES */
diff --git a/Headers/common_java.h b/Headers/common_java.h
new file mode 100644
index 0000000..9a4dde3
--- /dev/null
+++ b/Headers/common_java.h
@@ -0,0 +1,143 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_COMMON_JAVA
+#define BRLTTY_INCLUDED_COMMON_JAVA
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define JAVA_METHOD(object, name, type) \
+  JNIEXPORT type JNICALL Java_ ## object ## _ ## name (JNIEnv *env,
+
+#define JAVA_INSTANCE_METHOD(object, name, type, ...) \
+  JAVA_METHOD(object, name, type) jobject this, ## __VA_ARGS__)
+
+#define JAVA_STATIC_METHOD(object, name, type, ...) \
+  JAVA_METHOD(object, name, type) jclass class, ## __VA_ARGS__)
+
+#define JAVA_SIG_VOID                      "V"
+#define JAVA_SIG_BOOLEAN                   "Z"
+#define JAVA_SIG_BYTE                      "B"
+#define JAVA_SIG_CHAR                      "C"
+#define JAVA_SIG_SHORT                     "S"
+#define JAVA_SIG_INT                       "I"
+#define JAVA_SIG_LONG                      "J"
+#define JAVA_SIG_FLOAT                     "F"
+#define JAVA_SIG_DOUBLE                    "D"
+#define JAVA_SIG_OBJECT(path)              "L" path ";"
+#define JAVA_SIG_ARRAY(element)            "[" element
+#define JAVA_SIG_METHOD(returns,arguments) "(" arguments ")" returns
+
+#define JAVA_CONSTRUCTOR_NAME "<init>"
+#define JAVA_SIG_CONSTRUCTOR(arguments)    JAVA_SIG_METHOD(JAVA_SIG_VOID, arguments)
+
+#define JAVA_OBJ_LANG(name) "java/lang/" name
+#define JAVA_OBJ_IO(name) "java/io/" name
+#define JAVA_OBJ_UTIL(name) "java/util/" name
+#define JAVA_OBJ_CONCURRENT(name) JAVA_OBJ_UTIL("concurrent/" name)
+
+#define JAVA_OBJ_CHAR_SEQUENCE JAVA_OBJ_LANG("CharSequence")
+#define JAVA_OBJ_CLASS JAVA_OBJ_LANG("Class")
+#define JAVA_OBJ_EOF_EXCEPTION JAVA_OBJ_IO("EOFException")
+#define JAVA_OBJ_ILLEGAL_ARGUMENT_EXCEPTION JAVA_OBJ_LANG("IllegalArgumentException")
+#define JAVA_OBJ_ILLEGAL_STATE_EXCEPTION JAVA_OBJ_LANG("IllegalStateException")
+#define JAVA_OBJ_INTERRUPTED_IO_EXCEPTION JAVA_OBJ_IO("InterruptedIOException")
+#define JAVA_OBJ_ITERATOR JAVA_OBJ_UTIL("Iterator")
+#define JAVA_OBJ_LOCALE JAVA_OBJ_UTIL("Locale")
+#define JAVA_OBJ_NULL_POINTER_EXCEPTION JAVA_OBJ_LANG("NullPointerException")
+#define JAVA_OBJ_OBJECT JAVA_OBJ_LANG("Object")
+#define JAVA_OBJ_OUT_OF_MEMORY_ERROR JAVA_OBJ_LANG("OutOfMemoryError")
+#define JAVA_OBJ_STRING JAVA_OBJ_LANG("String")
+#define JAVA_OBJ_THREAD JAVA_OBJ_LANG("Thread")
+#define JAVA_OBJ_TIMEOUT_EXCEPTION JAVA_OBJ_CONCURRENT("TimeoutException")
+#define JAVA_OBJ_UNSATISFIED_LINK_ERROR JAVA_OBJ_LANG("UnsatisfiedLinkError")
+
+#define JAVA_SIG_CHAR_SEQUENCE JAVA_SIG_OBJECT(JAVA_OBJ_CHAR_SEQUENCE)
+#define JAVA_SIG_CLASS JAVA_SIG_OBJECT(JAVA_OBJ_CLASS)
+#define JAVA_SIG_ITERATOR JAVA_SIG_OBJECT(JAVA_OBJ_ITERATOR)
+#define JAVA_SIG_LOCALE JAVA_SIG_OBJECT(JAVA_OBJ_LOCALE)
+#define JAVA_SIG_STRING JAVA_SIG_OBJECT(JAVA_OBJ_STRING)
+#define JAVA_SIG_THREAD JAVA_SIG_OBJECT(JAVA_OBJ_THREAD)
+
+#define JAVA_CLASS_VARIABLE(name) jclass name = NULL
+#define JAVA_METHOD_VARIABLE(name) jmethodID name = 0;
+
+static inline int
+javaFindClass (JNIEnv *env, jclass *class, const char *name) {
+  if (*class) return 1;
+  return !!(*class = (*env)->FindClass(env, name));
+}
+
+static inline int
+javaFindMethod (
+  JNIEnv *env, jmethodID *method, jclass class,
+  const char *name, const char *signature
+) {
+  if (*method) return 1;
+  return !!(*method = (*env)->GetMethodID(env, class, name, signature));
+}
+
+#define JAVA_FIND_METHOD(env, method, class, name, arguments, returns) \
+(javaFindMethod(env, method, class, name, JAVA_SIG_METHOD(returns, arguments)))
+
+#define JAVA_FIND_CONSTRUCTOR(env, constructor, class, arguments) \
+(javaFindMethod(env, constructor, class, JAVA_CONSTRUCTOR_NAME, JAVA_SIG_CONSTRUCTOR(arguments)))
+
+#define javaPtrToLong(p) ((jlong)(intptr_t)(p))
+#define javaPtrFromLong(l) ((void *)(intptr_t)(l))
+
+static inline int
+javaFindClassAndMethod (
+  JNIEnv *env,
+  jclass *class, const char *className,
+  jmethodID *method, const char *methodName,
+  const char *signature
+) {
+  return javaFindClass(env, class, className)
+      && javaFindMethod(env, method, *class, methodName, signature);
+}
+
+static inline jboolean
+javaHasExceptionOccurred (JNIEnv *env) {
+  return (*env)->ExceptionCheck(env);
+}
+
+static inline jthrowable
+javaGetException (JNIEnv *env) {
+  return (*env)->ExceptionOccurred(env);
+}
+
+static inline void
+javaDescribeException (JNIEnv *env) {
+  return (*env)->ExceptionDescribe(env);
+}
+
+static inline void
+javaClearException (JNIEnv *env) {
+  return (*env)->ExceptionClear(env);
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_COMMON_JAVA */
diff --git a/Headers/crc.h b/Headers/crc.h
new file mode 100644
index 0000000..e832047
--- /dev/null
+++ b/Headers/crc.h
@@ -0,0 +1,27 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC
+#define BRLTTY_INCLUDED_CRC
+
+#include "crc_algorithms.h"
+#include "crc_properties.h"
+#include "crc_generate.h"
+#include "crc_verify.h"
+
+#endif /* BRLTTY_INCLUDED_CRC */
diff --git a/Headers/crc_algorithms.h b/Headers/crc_algorithms.h
new file mode 100644
index 0000000..7731c36
--- /dev/null
+++ b/Headers/crc_algorithms.h
@@ -0,0 +1,61 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_ALGORITHMS
+#define BRLTTY_INCLUDED_CRC_ALGORITHMS
+
+#include "crc_definitions.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  CRC_ALGORITHM_CLASS_UNKNOWN,
+  CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_ALGORITHM_CLASS_CONFIRMED,
+  CRC_ALGORITHM_CLASS_ACADEMIC,
+  CRC_ALGORITHM_CLASS_THIRD_PARTY,
+} CRCAlgorithmClass;
+
+typedef struct {
+  const char *primaryName; // the official name of the algorithm
+  const char *const *secondaryNames; // other names that the algorithm is known by (NULL terminated)
+  CRCAlgorithmClass algorithmClass;
+
+  unsigned char checksumWidth; // the width of the checksum (in bits)
+  unsigned char reflectData; // reflect each data byte before processing it
+  unsigned char reflectResult; // reflect the final value (before the xor)
+
+  crc_t generatorPolynomial; // the polynomial that generates the checksum
+  crc_t initialValue; // the starting value (before any processing)
+  crc_t xorMask; // the xor (exclussive or) mask to apply to the final value
+
+  crc_t checkValue; // the checksum for the official check data ("123456789")
+  crc_t residue; // the final value (no reflection or xor) of the check data
+                 // followed by its checksum (in network byte order)
+} CRCAlgorithm;
+
+extern const CRCAlgorithm *crcProvidedAlgorithms[];
+extern const CRCAlgorithm *crcGetProvidedAlgorithm (const char *name);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_ALGORITHMS */
diff --git a/Headers/crc_definitions.h b/Headers/crc_definitions.h
new file mode 100644
index 0000000..570df64
--- /dev/null
+++ b/Headers/crc_definitions.h
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_DEFINITIONS
+#define BRLTTY_INCLUDED_CRC_DEFINITIONS
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint32_t crc_t;
+#define CRC_C UINT32_C
+#define PRIcrc PRIX32
+
+#ifndef UINT24_C
+#define UINT24_C UINT32_C
+#endif /* UINT24_C */
+
+#ifndef UINT24_MAX
+#define UINT24_MAX UINT24_C(0XFFFFFF)
+#endif /* UINT24_MAX */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_DEFINITIONS */
diff --git a/Headers/crc_generate.h b/Headers/crc_generate.h
new file mode 100644
index 0000000..cf5a333
--- /dev/null
+++ b/Headers/crc_generate.h
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_GENERATE
+#define BRLTTY_INCLUDED_CRC_GENERATE
+
+#include <sys/types.h>
+#include "crc_algorithms.h"
+#include "crc_properties.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct CRCGeneratorStruct CRCGenerator;
+extern CRCGenerator *crcNewGenerator (const CRCAlgorithm *algorithm);
+extern void crcResetGenerator (CRCGenerator *crc);
+extern void crcDestroyGenerator (CRCGenerator *crc);
+
+extern void crcAddByte (CRCGenerator *crc, uint8_t byte);
+extern void crcAddData (CRCGenerator *crc, const void *data, size_t size);
+
+extern crc_t crcGetChecksum (const CRCGenerator *crc);
+extern crc_t crcGetResidue (CRCGenerator *crc);
+
+extern const CRCAlgorithm *crcGetAlgorithm (const CRCGenerator *crc);
+extern const CRCProperties *crcGetProperties (const CRCGenerator *crc);
+extern crc_t crcGetValue (const CRCGenerator *crc);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_GENERATE */
diff --git a/Headers/crc_properties.h b/Headers/crc_properties.h
new file mode 100644
index 0000000..315f847
--- /dev/null
+++ b/Headers/crc_properties.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_PROPERTIES
+#define BRLTTY_INCLUDED_CRC_PROPERTIES
+
+#include "crc_definitions.h"
+#include "crc_algorithms.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define CRC_BYTE_WIDTH 8
+#define CRC_BYTE_INDEXED_TABLE_SIZE (UINT8_MAX + 1)
+
+extern crc_t crcMostSignificantBit (unsigned int width);
+extern crc_t crcReflectBits (crc_t fromValue, unsigned int width);
+extern void crcReflectByte (uint8_t *byte);
+extern void crcReflectValue (crc_t *value, const CRCAlgorithm *algorithm);
+
+typedef struct {
+  unsigned int byteShift; // the bit offset of the high-order byte of the value
+  crc_t mostSignificantBit; // the most significant bit of the value
+  crc_t valueMask; // the mask for removing overflow bits in the value
+  const uint8_t *dataTranslationTable; // for optimizing data reflection
+
+  // for preevaluating a common calculation on each data byte
+  crc_t remainderCache[CRC_BYTE_INDEXED_TABLE_SIZE];
+} CRCProperties;
+
+extern void crcMakeProperties (
+  CRCProperties *properties,
+  const CRCAlgorithm *algorithm
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_PROPERTIES */
diff --git a/Headers/crc_verify.h b/Headers/crc_verify.h
new file mode 100644
index 0000000..5a616da
--- /dev/null
+++ b/Headers/crc_verify.h
@@ -0,0 +1,53 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_VERIFY
+#define BRLTTY_INCLUDED_CRC_VERIFY
+
+#include <sys/types.h>
+#include <stdint.h>
+#include "crc_generate.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const uint8_t crcCheckData[];
+extern const uint8_t crcCheckSize;
+
+extern int crcVerifyChecksum (const CRCGenerator *crc, crc_t expected);
+extern int crcVerifyResidue (CRCGenerator *crc);
+
+extern int crcVerifyAlgorithm (const CRCAlgorithm *algorithm);
+extern int crcVerifyProvidedAlgorithms (void);
+
+extern int crcVerifyAlgorithmWithData (
+  const CRCAlgorithm *algorithm,
+  const void *data, size_t size, crc_t expected
+);
+
+extern int crcVerifyAlgorithmWithString (
+  const CRCAlgorithm *algorithm,
+  const char *string, crc_t expected
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_VERIFY */
diff --git a/Headers/ctb.h b/Headers/ctb.h
new file mode 100644
index 0000000..16c2f9e
--- /dev/null
+++ b/Headers/ctb.h
@@ -0,0 +1,58 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CTB
+#define BRLTTY_INCLUDED_CTB
+
+#include "ctb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct ContractionTableStruct ContractionTable;
+extern ContractionTable *contractionTable;
+
+extern void lockContractionTable (void);
+extern void unlockContractionTable (void);
+
+extern ContractionTable *compileContractionTable (const char *name);
+extern void destroyContractionTable (ContractionTable *table);
+
+extern char *ensureContractionTableExtension (const char *path);
+extern char *makeContractionTablePath (const char *directory, const char *name);
+
+extern char *getContractionTableForLocale (const char *directory);
+extern int replaceContractionTable (const char *directory, const char *name);
+
+extern void contractText(
+    ContractionTable *contractionTable, /* Pointer to translation table */
+    ContractionCache *contractionCache,
+    const wchar_t *inputBuffer,  /* What is to be translated */
+    int *inputLength,            /* Its length */
+    unsigned char *outputBuffer, /* Where the translation is to go */
+    int *outputLength,           /* length of this area */
+    int *offsetsMap, /* Array of offsets of translated chars in source */
+    int cursorOffset /* Position of coursor in source */
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CTB */
diff --git a/Headers/ctb_types.h b/Headers/ctb_types.h
new file mode 100644
index 0000000..999a519
--- /dev/null
+++ b/Headers/ctb_types.h
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CTB_TYPES
+#define BRLTTY_INCLUDED_CTB_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define CTB_NO_OFFSET -1
+#define CTB_NO_CURSOR -1
+
+typedef enum {
+  CTB_CAP_NONE,
+  CTB_CAP_SIGN,
+  CTB_CAP_DOT7
+} CTB_CapitalizationMode;
+
+typedef struct {
+  struct {
+    wchar_t *characters;
+    unsigned int size;
+    unsigned int count;
+    unsigned int consumed;
+  } input;
+
+  struct {
+    unsigned char *cells;
+    unsigned int size;
+    unsigned int count;
+    unsigned int maximum;
+  } output;
+
+  struct {
+    int *array;
+    unsigned int size;
+    unsigned int count;
+  } offsets;
+
+  int cursorOffset;
+  unsigned char expandCurrentWord;
+  unsigned char capitalizationMode;
+} ContractionCache;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CTB_TYPES */
diff --git a/Headers/dataarea.h b/Headers/dataarea.h
new file mode 100644
index 0000000..96c99ec
--- /dev/null
+++ b/Headers/dataarea.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DATAAREA
+#define BRLTTY_INCLUDED_DATAAREA
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct DataAreaStruct DataArea;
+extern DataArea *newDataArea (void);
+extern void destroyDataArea (DataArea *area);
+extern void resetDataArea (DataArea *area);
+
+typedef unsigned long int DataOffset;
+extern int allocateDataItem (DataArea *area, DataOffset *offset, size_t size, size_t alignment);
+extern void *getDataItem (DataArea *area, DataOffset offset);
+extern size_t getDataSize (DataArea *area);
+extern int saveDataItem (DataArea *area, DataOffset *offset, const void *item, size_t size, size_t alignment);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DATAAREA */
diff --git a/Headers/datafile.h b/Headers/datafile.h
new file mode 100644
index 0000000..27bd36f
--- /dev/null
+++ b/Headers/datafile.h
@@ -0,0 +1,202 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DATAFILE
+#define BRLTTY_INCLUDED_DATAFILE
+
+#include <stdio.h>
+
+#include "variables.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int setBaseDataVariables (const VariableInitializer *initializers);
+extern int setTableDataVariables (const char *tableExtension, const char *subtableExtension);
+
+extern FILE *openDataFile (const char *path, const char *mode, int optional);
+
+typedef struct DataFileStruct DataFile;
+
+#define DATA_OPERANDS_PROCESSOR(name) int name (DataFile *file, void *data)
+typedef DATA_OPERANDS_PROCESSOR(DataOperandsProcessor);
+
+typedef enum {
+  DFO_NO_COMMENTS = 0X01
+} DataFileOptions;
+
+typedef struct {
+  DataOperandsProcessor *processOperands;
+  void (*logFileName) (const char *name, void *data);
+  void *data;
+  unsigned char options;
+} DataFileParameters;
+
+extern int processDataFile (const char *name, const DataFileParameters *parameters);
+extern void reportDataError (DataFile *file, const char *format, ...) PRINTF(2, 3);
+
+extern int processDataStream (
+  DataFile *includer,
+  FILE *stream, const char *name,
+  const DataFileParameters *parameters
+);
+
+extern int compareKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count);
+extern int compareKeywords (const wchar_t *keyword1, const wchar_t *keyword2);
+
+extern int isKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count);
+extern int isNumber (int *number, const wchar_t *characters, int length);
+extern int isHexadecimalDigit (wchar_t character, int *value, int *shift);
+extern int isOctalDigit (wchar_t character, int *value, int *shift);
+
+extern int findDataOperand (DataFile *file, const char *description);
+extern int getDataCharacter (DataFile *file, wchar_t *character);
+extern int ungetDataCharacters (DataFile *file, unsigned int count);
+
+typedef struct {
+  const wchar_t *characters;
+  int length;
+} DataOperand;
+
+extern int getDataOperand (DataFile *file, DataOperand *operand, const char *description);
+extern int getTextOperand (DataFile *file, DataOperand *text, const char *description);
+extern void getTextRemaining (DataFile *file, DataOperand *text);
+
+typedef struct {
+  unsigned char length;
+  wchar_t characters[0XFF];
+} DataString;
+
+extern int parseDataString (DataFile *file, DataString *string, const wchar_t *characters, int length, int noUnicode);
+extern int getDataString (DataFile *file, DataString *string, int noUnicode, const char *description);
+
+extern int writeHexadecimalCharacter (FILE *stream, wchar_t character);
+extern int writeEscapedCharacter (FILE *stream, wchar_t character);
+extern int writeEscapedCharacters (FILE *stream, const wchar_t *characters, size_t count);
+
+typedef struct {
+  unsigned char length;
+  unsigned char bytes[0XFF];
+} ByteOperand;
+
+#define CELLS_OPERAND_DELIMITER WC_C('-')
+#define CELLS_OPERAND_SPACE WC_C('0')
+
+extern int parseCellsOperand (DataFile *file, ByteOperand *cells, const wchar_t *characters, int length);
+extern int getCellsOperand (DataFile *file, ByteOperand *cells, const char *description);
+
+extern int writeDots (FILE *stream, unsigned char cell);
+extern int writeDotsCell (FILE *stream, unsigned char cell);
+extern int writeDotsCells (FILE *stream, const unsigned char *cells, size_t count);
+
+extern int writeUtf8Cell (FILE *stream, unsigned char cell);
+extern int writeUtf8Cells (FILE *stream, const unsigned char *cells, size_t count);
+
+typedef struct {
+  const wchar_t *name;
+  DataOperandsProcessor *processor;
+  unsigned char unconditional : 1;
+} DataDirective;
+
+typedef struct {
+  struct {
+    const DataDirective *table;
+    size_t count;
+  } const unsorted;
+
+  struct {
+    const DataDirective **table;
+    size_t count;
+  } sorted;
+
+  const DataDirective *unnamed;
+} DataDirectives;
+
+#define BEGIN_DATA_DIRECTIVE_TABLE \
+  static const DataDirective unsortedDirectives[] = {
+
+#define END_DATA_DIRECTIVE_TABLE }; \
+  static DataDirectives directives = { \
+    .unsorted = { \
+      .table = unsortedDirectives, \
+      .count = ARRAY_COUNT(unsortedDirectives) \
+    }, \
+    \
+    .sorted = { \
+      .table = NULL, \
+      .count = 0 \
+    }, \
+    \
+    .unnamed = NULL \
+  };
+
+extern int processDirectiveOperand (DataFile *file, DataDirectives *directives, const char *description, void *data);
+
+#define DATA_CONDITION_TESTER(name) int name (DataFile *file, const DataOperand *identifier, void *data)
+typedef DATA_CONDITION_TESTER(DataConditionTester);
+
+extern int processConditionOperands (
+  DataFile *file,
+  DataConditionTester *testCondition, int negateCondition,
+  const char *description, void *data
+);
+
+extern DATA_OPERANDS_PROCESSOR(processIncludeOperands);
+extern int includeDataFile (DataFile *file, const wchar_t *name, int length);
+
+#define DATA_NESTING_DIRECTIVES \
+  {.name=WS_C("include"), .processor=processIncludeOperands}
+
+extern DATA_OPERANDS_PROCESSOR(processElseOperands);
+extern DATA_OPERANDS_PROCESSOR(processEndIfOperands);
+
+#define DATA_CONDITION_DIRECTIVES \
+  {.name=WS_C("else"), .processor=processElseOperands, .unconditional=1}, \
+  {.name=WS_C("endif"), .processor=processEndIfOperands, .unconditional=1}
+
+extern DATA_OPERANDS_PROCESSOR(processIfVarOperands);
+extern DATA_OPERANDS_PROCESSOR(processIfNotVarOperands);
+extern DATA_OPERANDS_PROCESSOR(processBeginVariablesOperands);
+extern DATA_OPERANDS_PROCESSOR(processEndVariablesOperands);
+extern DATA_OPERANDS_PROCESSOR(processListVariablesOperands);
+extern DATA_OPERANDS_PROCESSOR(processAssignDefaultOperands);
+extern DATA_OPERANDS_PROCESSOR(processAssignOperands);
+
+#define DATA_VARIABLE_DIRECTIVES \
+  {.name=WS_C("ifvar"), .processor=processIfVarOperands, .unconditional=1}, \
+  {.name=WS_C("ifnotvar"), .processor=processIfNotVarOperands, .unconditional=1}, \
+  {.name=WS_C("beginvariables"), .processor=processBeginVariablesOperands}, \
+  {.name=WS_C("endvariables"), .processor=processEndVariablesOperands}, \
+  {.name=WS_C("listvariables"), .processor=processListVariablesOperands}, \
+  {.name=WS_C("assigndefault"), .processor=processAssignDefaultOperands}, \
+  {.name=WS_C("assign"), .processor=processAssignOperands}
+
+#define BRL_DOT_COUNT 8
+extern const wchar_t brlDotNumbers[BRL_DOT_COUNT];
+extern const unsigned char brlDotBits[BRL_DOT_COUNT];
+extern int brlDotNumberToIndex (wchar_t number, int *index);
+extern int brlDotBitToIndex (unsigned char bit, int *index);
+
+extern int getDotOperand (DataFile *file, int *index);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DATAFILE */
diff --git a/Headers/device.h b/Headers/device.h
new file mode 100644
index 0000000..a3ce864
--- /dev/null
+++ b/Headers/device.h
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DEVICE
+#define BRLTTY_INCLUDED_DEVICE
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern FILE *getConsole (void);
+extern int writeToConsole (const unsigned char *bytes, size_t count);
+extern int ringConsoleBell (void);
+
+extern const char *getDeviceDirectory (void);
+extern char *getDevicePath (const char *device);
+extern const char *resolveDeviceName (const char *const *names, int strict, const char *description);
+
+#define DEVICE_PARAMETER_SEPARATOR '+'
+extern char **getDeviceParameters (const char *const *names, const char *identifier);
+
+#undef ALLOW_DOS_DEVICE_NAMES
+#if defined(__MSDOS__) || (defined(WINDOWS) && !defined(__CYGWIN__))
+#define ALLOW_DOS_DEVICE_NAMES 1
+extern int isDosDevice (const char *identifier, const char *prefix);
+#endif /* DOS or Windows (but not Cygwin) */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DEVICE */
diff --git a/Headers/driver.h b/Headers/driver.h
new file mode 100644
index 0000000..1588df6
--- /dev/null
+++ b/Headers/driver.h
@@ -0,0 +1,70 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DRIVER
+#define BRLTTY_INCLUDED_DRIVER
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *name;
+  const char *code;
+  const char *comment;
+  const char *version;
+  const char *developers;
+  const char *date;
+  const char *time;
+} DriverDefinition;
+
+#define DRIVER_DEFINITION_DECLARATION const DriverDefinition definition
+
+#define DRIVER_DEFINITION_INITIALIZER .definition = { \
+  .name = STRINGIFY(DRIVER_NAME), \
+  .code = STRINGIFY(DRIVER_CODE), \
+  .comment = DRIVER_COMMENT, \
+  .version = DRIVER_VERSION, \
+  .developers = DRIVER_DEVELOPERS, \
+  .date = __DATE__, \
+  .time = __TIME__ \
+}
+
+#define DRIVER_VERSION_STRING PACKAGE_VERSION
+#define DRIVER_VERSION_DECLARATION(type) const char CONCATENATE(type##_version_,DRIVER_CODE)[] = DRIVER_VERSION_STRING
+
+extern void unsupportedDeviceIdentifier (const char *identifier);
+
+extern void logOutputPacket (const void *packet, size_t size);
+extern void logInputPacket (const void *packet, size_t size);
+extern void logInputProblem (const char *problem, const unsigned char *bytes, size_t count);
+extern void logIgnoredByte (unsigned char byte);
+extern void logDiscardedByte (unsigned char byte);
+extern void logUnknownPacket (unsigned char byte);
+extern void logPartialPacket (const void *packet, size_t size);
+extern void logTruncatedPacket (const void *packet, size_t size);
+extern void logShortPacket (const void *packet, size_t size);
+extern void logUnexpectedPacket (const void *packet, size_t size);
+extern void logCorruptPacket (const void *packet, size_t size);
+extern void logDiscardedBytes (const unsigned char *bytes, size_t count);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DRIVER */
diff --git a/Headers/drivers.h b/Headers/drivers.h
new file mode 100644
index 0000000..41f1a7e
--- /dev/null
+++ b/Headers/drivers.h
@@ -0,0 +1,55 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DRIVERS
+#define BRLTTY_INCLUDED_DRIVERS
+
+#include "driver.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const void *address;
+  const DriverDefinition *definition;
+} DriverEntry;
+
+extern int isDriverAvailable (const char *code, const char *codes);
+extern int isDriverIncluded (const char *code, const DriverEntry *table);
+extern int haveDriver (const char *code, const char *codes, const DriverEntry *table);
+extern const char *getDefaultDriver (const DriverEntry *table);
+
+extern const void *loadDriver (
+  const char *driverCode, void **driverObject,
+  const char *driverDirectory, const DriverEntry *driverTable,
+  const char *typeName, char typeLetter, const char *symbolPrefix,
+  const void *nullAddress, const DriverDefinition *nullDefinition
+);
+
+extern void identifyDriver (
+  const char *type,
+  const DriverDefinition *definition,
+  int full
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DRIVERS */
diff --git a/Headers/dynld.h b/Headers/dynld.h
new file mode 100644
index 0000000..80e17f7
--- /dev/null
+++ b/Headers/dynld.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DYNLD
+#define BRLTTY_INCLUDED_DYNLD
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void *loadSharedObject (const char *path);
+extern void unloadSharedObject (void *object);
+
+extern int findSharedSymbol (void *object, const char *symbol, void *pointerAddress);
+extern const char *getSharedSymbolName (void *address, ptrdiff_t *offset);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DYNLD */
diff --git a/Headers/embed.h b/Headers/embed.h
new file mode 100644
index 0000000..b623d07
--- /dev/null
+++ b/Headers/embed.h
@@ -0,0 +1,70 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_EMBED
+#define BRLTTY_INCLUDED_EMBED
+
+#include "program.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  WAIT_STOP,
+  WAIT_CONTINUE
+} WaitResult;
+
+FUNCTION_DECLARE(brlttyConstruct, ProgramExitStatus, (int argc, char *argv[]));
+FUNCTION_DECLARE(brlttyDestruct, int, (void));
+
+FUNCTION_DECLARE(brlttyEnableInterrupt, int, (void));
+FUNCTION_DECLARE(brlttyDisableInterrupt, int, (void));
+
+FUNCTION_DECLARE(brlttyInterrupt, int, (WaitResult waitResult));
+FUNCTION_DECLARE(brlttyWait, WaitResult, (int duration));
+
+FUNCTION_DECLARE(changeLogLevel, int, (const char *operand));
+FUNCTION_DECLARE(changeLogCategories, int, (const char *operand));
+
+FUNCTION_DECLARE(changeTextTable, int, (const char *name));
+FUNCTION_DECLARE(changeAttributesTable, int, (const char *name));
+FUNCTION_DECLARE(changeContractionTable, int, (const char *name));
+FUNCTION_DECLARE(changeKeyboardTable, int, (const char *name));
+
+FUNCTION_DECLARE(restartBrailleDriver, void, (void));
+FUNCTION_DECLARE(changeBrailleDriver, int, (const char *driver));
+FUNCTION_DECLARE(changeBrailleParameters, int, (const char *parameters));
+FUNCTION_DECLARE(changeBrailleDevice, int, (const char *device));
+
+FUNCTION_DECLARE(restartSpeechDriver, void, (void));
+FUNCTION_DECLARE(changeSpeechDriver, int, (const char *driver));
+FUNCTION_DECLARE(changeSpeechParameters, int, (const char *parameters));
+
+FUNCTION_DECLARE(restartScreenDriver, void, (void));
+FUNCTION_DECLARE(changeScreenDriver, int, (const char *driver));
+FUNCTION_DECLARE(changeScreenParameters, int, (const char *parameters));
+
+FUNCTION_DECLARE(changeMessageLocale, int, (const char *parameters));
+FUNCTION_DECLARE(showMessage, void, (const char *text));
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_EMBED */
diff --git a/Headers/ezusb.h b/Headers/ezusb.h
new file mode 100644
index 0000000..1802f66
--- /dev/null
+++ b/Headers/ezusb.h
@@ -0,0 +1,73 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_EZUSB
+#define BRLTTY_INCLUDED_EZUSB
+
+#include "io_usb.h"
+#include "ihex_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define EZUSB_REQUEST_RECIPIENT UsbControlRecipient_Device
+#define EZUSB_REQUEST_TYPE UsbControlType_Vendor
+#define EZUSB_REQUEST_INDEX 0
+
+typedef uint8_t EzusbAction;
+#define EZUSB_ACTION_RW_INTERNAL 0XA0
+#define EZUSB_ACTION_RW_EEPROM   0XA2
+#define EZUSB_ACTION_RW_MEMORY   0XA3
+
+extern int ezusbWriteData (
+  UsbDevice *device, EzusbAction action, IhexAddress address,
+  const unsigned char *data, size_t length
+);
+
+extern int ezusbReadData (
+  UsbDevice *device, EzusbAction action, IhexAddress address,
+  unsigned char *buffer, size_t size
+);
+
+extern int ezusbVerifyData (
+  UsbDevice *device, EzusbAction action, IhexAddress address,
+  const unsigned char *data, size_t length
+);
+
+#define EZUSB_CPUCS_ADDRESS 0X7F92
+#define EZUSB_CPUCS_RESET 0X00
+#define EZUSB_CPUCS_STOP  0X01
+
+extern int ezusbWriteCPUCS (UsbDevice *device, uint8_t state);
+extern int ezusbStopCPU (UsbDevice *device);
+extern int ezusbResetCPU (UsbDevice *device);
+
+extern int ezusbInstallBlob (
+  UsbDevice *device, const char *name, EzusbAction action
+);
+
+extern int ezusbProcessBlob (
+  const char *name, IhexRecordHandler *handler, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_EZUSB */
diff --git a/Headers/file.h b/Headers/file.h
new file mode 100644
index 0000000..4af2ce0
--- /dev/null
+++ b/Headers/file.h
@@ -0,0 +1,129 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_FILE
+#define BRLTTY_INCLUDED_FILE
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <strfmth.h>
+
+#include "get_sockets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define CURRENT_DIRECTORY_NAME "."
+#define PARENT_DIRECTORY_NAME ".."
+
+extern int isPathSeparator (const char character);
+extern int isAbsolutePath (const char *path);
+extern char *getPathDirectory (const char *path);
+extern const char *locatePathName (const char *path);
+extern const char *locatePathExtension (const char *path);
+extern int isExplicitPath (const char *path);
+
+extern char *joinPath (const char *const *components, unsigned int count);
+extern char *makePath (const char *directory, const char *file);
+
+extern int hasFileExtension (const char *path, const char *extension);
+extern char *replaceFileExtension (const char *path, const char *extension);
+extern char *ensureFileExtension (const char *path, const char *extension);
+extern char *makeFilePath (const char *directory, const char *name, const char *extension);
+
+extern int testPath (const char *path);
+extern int testFilePath (const char *path);
+extern int testProgramPath (const char *path);
+extern int testDirectoryPath (const char *path);
+
+extern void lockUmask (void);
+extern void unlockUmask (void);
+
+extern int createDirectory (const char *path, int worldWritable);
+extern int ensureDirectory (const char *path, int worldWritable);
+extern int ensurePathDirectory (const char *path);
+
+extern void setUpdatableDirectory (const char *directory);
+extern const char *getUpdatableDirectory (void);
+extern char *makeUpdatablePath (const char *file);
+
+extern void setWritableDirectory (const char *directory);
+extern const char *getWritableDirectory (void);
+extern char *makeWritablePath (const char *file);
+
+extern char *getWorkingDirectory (void);
+extern int setWorkingDirectory (const char *path);
+
+extern char *getHomeDirectory (void);
+
+extern const char *const *getAllOverrideDirectories (void);
+extern const char *getPrimaryOverrideDirectory (void);
+extern void forgetOverrideDirectories (void);
+
+extern int acquireFileLock (int file, int exclusive);
+extern int attemptFileLock (int file, int exclusive);
+extern int releaseFileLock (int file);
+
+extern void registerProgramStream (const char *name, FILE **stream);
+
+extern FILE *openFile (const char *path, const char *mode, int optional);
+
+typedef struct {
+  void *data;
+
+  struct {
+    char *text;
+    size_t length;
+    unsigned int number;
+  } line;
+} LineHandlerParameters;
+
+typedef int LineHandler (const LineHandlerParameters *parameters);
+extern int processLines (FILE *file, LineHandler handleLine, void *data);
+extern int readLine (FILE *file, char **buffer, size_t *size, size_t *length);
+
+extern STR_DECLARE_FORMATTER(formatInputError, const char *file, const int *line, const char *format, va_list arguments);
+
+extern void detachStandardInput(void);
+extern void detachStandardOutput(void);
+extern void detachStandardError(void);
+extern void detachStandardStreams(void);
+
+extern ssize_t readFileDescriptor (FileDescriptor fileDescriptor, void *buffer, size_t size);
+extern ssize_t writeFileDescriptor (FileDescriptor fileDescriptor, const void *buffer, size_t size);
+
+#ifdef GOT_SOCKETS
+extern ssize_t readSocketDescriptor (SocketDescriptor socketDescriptor, void *buffer, size_t size);
+extern ssize_t writeSocketDescriptor (SocketDescriptor socketDescriptor, const void *buffer, size_t size);
+#endif /* GOT_SOCKETS */
+
+extern int getConsoleSize (size_t *width, size_t *height);
+extern const char *getConsoleEncoding (void);
+extern void writeWithConsoleEncoding (FILE *stream, const char *bytes, size_t count);
+
+extern const char *getNamedPipeDirectory (void);
+extern int createAnonymousPipe (FileDescriptor *pipeInput, FileDescriptor *pipeOutput);
+
+extern char *readSymbolicLink (const char *path);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_FILE */
diff --git a/Headers/fm.h b/Headers/fm.h
new file mode 100644
index 0000000..67a1926
--- /dev/null
+++ b/Headers/fm.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_FM
+#define BRLTTY_INCLUDED_FM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int fmEnablePorts (int errorLevel);
+extern void fmDisablePorts (void);
+
+extern int fmTestCard (int errorLevel);
+extern void fmResetCard (void);
+
+extern void fmPlayTone (int channel, unsigned int pitch, unsigned long int duration, unsigned int volume);
+extern void fmStartTone (int channel, int pitch);
+extern void fmStopTone (int channel);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_FM */
diff --git a/Headers/fm_adlib.h b/Headers/fm_adlib.h
new file mode 100644
index 0000000..3c103ad
--- /dev/null
+++ b/Headers/fm_adlib.h
@@ -0,0 +1,138 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_FM_ADLIB
+#define BRLTTY_INCLUDED_FM_ADLIB
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * Miscellaneous FM chip soundcard routines for BRLTTY.
+ * Implemented by Dave Mielke <dave@mielke.cc>.
+ * Method gleaned from sccw, a morse code program written
+ * by Steven J. Merrifield <sjm@ee.latrobe.edu.au> (VK3ESM).
+ * Must compile with -O2.
+ * Must link with -lm.
+ * May compile with -DDEBUG_ADLIB.
+ */
+
+extern void AL_writeRegister (int number, unsigned char data);
+extern unsigned char AL_readStatus (void);
+
+/* I/O ports. */
+#define ALP_REGISTER 0X388 /* for writing the register number */
+#define ALP_DATA (ALP_REGISTER + 1) /* for writing data to the selected register */
+#define ALP_STATUS ALP_REGISTER /* for reading the status of the card */
+
+/* Status register bits. */
+#define AL_STAT_EXP 0X80 /* a timer has expired */
+#define AL_STAT_EXP1 0X40 /* timer 1 has expired */
+#define AL_STAT_EXP2 0X20 /* timer 2 has expired */
+
+#define ALR_FIRST 0X01
+#define ALR_LAST 0XF5
+
+#define ALR_INIT 0X01
+#define ALR_T1DATA 0X02
+#define ALR_T2DATA 0X03
+#define ALR_TCTL 0X04
+
+#define AL_INIT_WFSLCT 0x20 /* wave form select */
+
+#define AL_TCTL_RESET 0X80
+#define AL_TCTL_T1MASK 0X40
+#define AL_TCTL_T2MASK 0X20
+#define AL_TCTL_T2START 0X02
+#define AL_TCTL_T1START 0X01
+
+extern const unsigned char AL_channelOffsets[];
+extern const unsigned char AL_channelCount;
+#define ALR_MODULATOR(group,channel) ((group) + AL_channelOffsets[(channel)])
+#define ALR_CARRIER(group,channel) (ALR_MODULATOR((group),(channel)) + 3)
+
+#define ALG_EFFECT 0X20
+#define AL_EFCT_AMPLMOD 0X80 /* apply amplitude modulation */
+#define AL_EFCT_VIBRATO 0X40 /* apply vibrato */
+#define AL_EFCT_SUSTAIN 0X20 /* do not release */
+#define AL_EFCT_KSR     0X10 /* keyboard scaling rate (faster decay at higher freq) */
+#define AL_HARMONIC_SHIFT 0
+#define AL_SUBHARMONIC_1  0X00 /* 1 octave lower */
+#define AL_HARMONIC_1     0X01 /* the specified frequency */
+#define AL_HARMONIC_2     0X02 /* 1 octave higher */
+#define AL_HARMONIC_3     0X03 /* 1 octave and a 5th higher */
+#define AL_HARMONIC_4     0X04 /* 2 octaves higher */
+#define AL_HARMONIC_5     0X05 /* 2 octaves and a 3rd higher */
+#define AL_HARMONIC_6     0X06 /* 2 octaves and a 5th higher */
+#define AL_HARMONIC_7     0X07 /* 2 octaves and a diminished 7th higher */
+#define AL_HARMONIC_8     0X08 /* 3 octaves higher */
+#define AL_HARMONIC_9     0X09 /* 3 octaves and a 2nd higher */
+#define AL_HARMONIC_10    0X0A /* 3 octaves and a 3rd higher */
+#define AL_HARMONIC_11    0X0B /* 3 octaves and ? higher */
+#define AL_HARMONIC_12    0X0C /* 3 octaves and a 5th higher */
+#define AL_HARMONIC_13    0X0D /* 3 octaves and ? higher */
+#define AL_HARMONIC_14    0X0E /* 3 octaves and a diminished 7th higher */
+#define AL_HARMONIC_15    0X0F /* 3 octaves and ? higher */
+
+#define ALG_LEVEL 0X40
+#define AL_LKS_SHIFT 6 /* level key scaling (softer at higher freq) */
+#define AL_LKS_0   0X00 /* 0 dB per octave */
+#define AL_LKS_1p5 0X02 /* 1.5 dB per octave */
+#define AL_LKS_3   0X01 /* 3 dB per octave */
+#define AL_LKS_6   0X03 /* 6 dB per octave */
+#define AL_VOLUME_SHIFT 0 /* each bit attenuates by specific amount */
+#define AL_VOLUME_24   0X20 /* 24 dB */
+#define AL_VOLUME_12   0X10 /* 12 dB */
+#define AL_VOLUME_6    0X08 /* 6 dB */
+#define AL_VOLUME_3    0X04 /* 3 dB */
+#define AL_VOLUME_1p5  0X02 /* 1.5 dB */
+#define AL_VOLUME_0p75 0X01 /* 0.75 dB */
+#define AL_VOLUME_LOUD 0X00 /* loudest (no attenuation) */
+#define AL_VOLUME_SOFT 0X3F /* softest (47.25 dB attenuation) */
+
+#define ALG_ATTDEC 0X60
+#define AL_ATTACK_SHIFT 4
+#define AL_ATTACK_SLOW 0X0
+#define AL_ATTACK_FAST 0XF
+#define AL_DECAY_SHIFT 0
+#define AL_DECAY_SLOW 0X0
+#define AL_DECAY_FAST 0XF
+
+#define ALG_SUSREL 0X80
+#define AL_SUSTAIN_SHIFT 4
+#define AL_SUSTAIN_24   0X8 /* after 24 dB decay */
+#define AL_SUSTAIN_12   0X4 /* after 12 dB decay */
+#define AL_SUSTAIN_6    0X2 /* after 6 dB decay */
+#define AL_SUSTAIN_3    0X1 /* after 3 dB decay */
+#define AL_SUSTAIN_LOUD 0X0 /* the loudest (right after attack) */
+#define AL_SUSTAIN_SOFT 0XF /* the softest (after 45 dB decay) */
+#define AL_RELEASE_SHIFT 0
+#define AL_RELEASE_SLOW 0X0
+#define AL_RELEASE_FAST 0XF
+
+#define ALR_FREQUENCY_LSB(channel) (0XA0 + (channel))
+#define ALR_FREQUENCY_MSB(channel) (0XB0 + (channel))
+#define AL_OCTAVE_SHIFT 2
+#define AL_FREQ_ON 0X20
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_FM_ADLIB */
diff --git a/Headers/get_curses.h b/Headers/get_curses.h
new file mode 100644
index 0000000..c148894
--- /dev/null
+++ b/Headers/get_curses.h
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GET_CURSES
+#define BRLTTY_INCLUDED_GET_CURSES
+
+#include "prologue.h"
+#undef GOT_CURSES
+#undef GOT_CURSES_WCH
+
+#if defined(HAVE_PKG_CURSES)
+#define GOT_CURSES
+#include <curses.h>
+
+#elif defined(HAVE_PKG_NCURSES)
+#define GOT_CURSES
+#include <ncurses.h>
+
+#elif defined(HAVE_PKG_NCURSESW)
+#define GOT_CURSES
+#define GOT_CURSES_WCH
+#include <ncursesw/ncurses.h>
+
+#elif defined(HAVE_PKG_PDCURSES)
+#define GOT_CURSES
+#include <curses.h>
+
+#elif defined(HAVE_PKG_PDCURSESU)
+#define GOT_CURSES
+#include <curses.h>
+
+#elif defined(HAVE_PKG_PDCURSESW)
+#define GOT_CURSES
+#define GOT_CURSES_WCH
+#define PDC_WIDE
+#include <curses.h>
+
+#else /* curses package */
+#warning curses package either unspecified or unsupported
+#endif /* curses package */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GET_CURSES */
diff --git a/Headers/get_select.h b/Headers/get_select.h
new file mode 100644
index 0000000..1be2cb0
--- /dev/null
+++ b/Headers/get_select.h
@@ -0,0 +1,43 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GET_SELECT
+#define BRLTTY_INCLUDED_GET_SELECT
+
+#include "prologue.h"
+#undef GOT_SELECT
+
+#ifdef HAVE_SELECT
+#define GOT_SELECT
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else /* HAVE_SYS_SELECT_H */
+#include <sys/time.h>
+#endif /* HAVE_SYS_SELECT_H */
+#endif /* HAVE_SELECT */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GET_SELECT */
diff --git a/Headers/get_sockets.h b/Headers/get_sockets.h
new file mode 100644
index 0000000..693e6cf
--- /dev/null
+++ b/Headers/get_sockets.h
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GET_SOCKETS
+#define BRLTTY_INCLUDED_GET_SOCKETS
+
+#include "prologue.h"
+#undef GOT_SOCKETS
+
+#if defined(HAVE_SYS_SOCKET_H)
+#define GOT_SOCKETS
+#include <sys/socket.h>
+
+#elif defined(__MINGW32__)
+#define GOT_SOCKETS
+
+typedef int socklen_t;
+
+#ifndef EINPROGRESS
+#ifdef WSAEINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif /* WSAEINPROGRESS */
+#endif /* EINPROGRESS */
+
+#endif /* have sockets */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GET_SOCKETS */
diff --git a/Headers/get_thread.h b/Headers/get_thread.h
new file mode 100644
index 0000000..123ecc6
--- /dev/null
+++ b/Headers/get_thread.h
@@ -0,0 +1,43 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GET_THREAD
+#define BRLTTY_INCLUDED_GET_THREAD
+
+#include "prologue.h"
+#undef GOT_PTHREADS
+
+#if defined(__MINGW32__)
+#define GOT_PTHREADS
+#include "win_pthread.h"
+
+#elif defined(HAVE_POSIX_THREADS)
+#define GOT_PTHREADS
+#include <pthread.h>
+
+#endif /* posix thread definitions */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GET_THREAD */
diff --git a/Headers/gettime.h b/Headers/gettime.h
new file mode 100644
index 0000000..7688f74
--- /dev/null
+++ b/Headers/gettime.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GETTIME
+#define BRLTTY_INCLUDED_GETTIME
+
+#include "prologue.h"
+#include <sys/time.h>
+
+#ifdef HAVE_CLOCK_GETTIME
+#include <time.h>
+#endif /* HAVE_CLOCK_GETTIME */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+static inline int
+getRealTime (struct timeval *now) {
+  int result;
+
+#if defined(HAVE_CLOCK_GETTIME) && !defined(__MINGW32__)
+  struct timespec time;
+  result = clock_gettime(CLOCK_REALTIME, &time);
+  now->tv_sec = time.tv_sec;
+  now->tv_usec = time.tv_nsec / 1000;
+#else /* getRealTime */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+  result = gettimeofday(now, NULL);
+#pragma GCC diagnostic pop
+#endif /* getRealTime */
+
+  return result;
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GETTIME */
diff --git a/Headers/gio_types.h b/Headers/gio_types.h
new file mode 100644
index 0000000..e5833b0
--- /dev/null
+++ b/Headers/gio_types.h
@@ -0,0 +1,122 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GIO_TYPES
+#define BRLTTY_INCLUDED_GIO_TYPES
+
+#include "serial_types.h"
+#include "usb_types.h"
+#include "hid_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef ssize_t GioUsbWriteDataMethod (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  const void *data, size_t size, int timeout
+);
+
+typedef int GioUsbAwaitInputMethod (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  int timeout
+);
+
+typedef ssize_t GioUsbReadDataMethod (
+  UsbDevice *device, const UsbChannelDefinition *definition,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+typedef struct {
+  const void *applicationData;
+  GioUsbWriteDataMethod *writeData;
+  GioUsbAwaitInputMethod *awaitInput;
+  GioUsbReadDataMethod *readData;
+  UsbInputFilter *inputFilter;
+} GioUsbConnectionProperties;
+
+typedef void GioUsbSetConnectionPropertiesMethod (
+  GioUsbConnectionProperties *properties,
+  const UsbChannelDefinition *definition
+);
+
+typedef struct {
+  const void *applicationData;
+  int readyDelay;
+  int inputTimeout;
+  int outputTimeout;
+  int requestTimeout;
+  unsigned char ignoreWriteTimeouts : 1;
+} GioOptions;
+
+typedef struct {
+  struct {
+    const SerialParameters *parameters;
+    GioOptions options;
+  } serial;
+
+  struct {
+    const UsbChannelDefinition *channelDefinitions;
+    GioUsbSetConnectionPropertiesMethod *setConnectionProperties;
+    GioOptions options;
+  } usb;
+
+  struct {
+    uint8_t channelNumber;
+    unsigned char discoverChannel:1;
+    GioOptions options;
+  } bluetooth;
+
+  struct {
+    const HidModelEntry *modelTable;
+    GioOptions options;
+  } hid;
+
+  struct {
+    GioOptions options;
+  } null;
+} GioDescriptor;
+
+typedef struct GioEndpointStruct GioEndpoint;
+
+typedef int GioTestIdentifierMethod (const char **identifier);
+
+typedef enum {
+  GIO_TYPE_UNSPECIFIED = 0,
+  GIO_TYPE_SERIAL,
+  GIO_TYPE_USB,
+  GIO_TYPE_BLUETOOTH,
+  GIO_TYPE_HID,
+  GIO_TYPE_NULL,
+} GioTypeIdentifier;
+
+typedef struct {
+  GioTestIdentifierMethod *testIdentifier;
+
+  struct {
+    const char *name;
+    GioTypeIdentifier identifier;
+  } type;
+} GioPublicProperties;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GIO_TYPES */
diff --git a/Headers/hid_braille.h b/Headers/hid_braille.h
new file mode 100644
index 0000000..114fe4c
--- /dev/null
+++ b/Headers/hid_braille.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_BRAILLE
+#define BRLTTY_INCLUDED_HID_BRAILLE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_BRAILLE */
diff --git a/Headers/hid_defs.h b/Headers/hid_defs.h
new file mode 100644
index 0000000..8186cff
--- /dev/null
+++ b/Headers/hid_defs.h
@@ -0,0 +1,593 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_DEFS
+#define BRLTTY_INCLUDED_HID_DEFS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  HID_ITM_UsagePage         = 0X04,
+  HID_ITM_Usage             = 0X08,
+  HID_ITM_LogicalMinimum    = 0X14,
+  HID_ITM_UsageMinimum      = 0X18,
+  HID_ITM_LogicalMaximum    = 0X24,
+  HID_ITM_UsageMaximum      = 0X28,
+  HID_ITM_PhysicalMinimum   = 0X34,
+  HID_ITM_DesignatorIndex   = 0X38,
+  HID_ITM_PhysicalMaximum   = 0X44,
+  HID_ITM_DesignatorMinimum = 0X48,
+  HID_ITM_UnitExponent      = 0X54,
+  HID_ITM_DesignatorMaximum = 0X58,
+  HID_ITM_Unit              = 0X64,
+  HID_ITM_ReportSize        = 0X74,
+  HID_ITM_StringIndex       = 0X78,
+  HID_ITM_Input             = 0X80,
+  HID_ITM_ReportID          = 0X84,
+  HID_ITM_StringMinimum     = 0X88,
+  HID_ITM_Output            = 0X90,
+  HID_ITM_ReportCount       = 0X94,
+  HID_ITM_StringMaximum     = 0X98,
+  HID_ITM_Collection        = 0XA0,
+  HID_ITM_Push              = 0XA4,
+  HID_ITM_Delimiter         = 0XA8,
+  HID_ITM_Feature           = 0XB0,
+  HID_ITM_Pop               = 0XB4,
+  HID_ITM_EndCollection     = 0XC0,
+} HidItemTag;
+
+#define HID_ITEM_TAG_MASK 0XFC
+#define HID_ITEM_SIZE_MASK 0X03
+
+#define HID_ITEM_TAG(item) ((item) & HID_ITEM_TAG_MASK)
+#define HID_ITEM_SIZE(item) ((item) & HID_ITEM_SIZE_MASK)
+#define HID_ITEM_TAG_BIT(tag) (UINT64_C(1) << ((tag) >> 2))
+
+static inline int
+hidHasSignedValue (HidItemTag tag) {
+  switch (tag) {
+    case HID_ITM_LogicalMinimum:
+    case HID_ITM_LogicalMaximum:
+    case HID_ITM_PhysicalMinimum:
+    case HID_ITM_PhysicalMaximum:
+    case HID_ITM_UnitExponent:
+      return 1;
+
+    default:
+      return 0;
+  }
+}
+
+typedef enum {
+  HID_COL_Physical    = 0X00,
+  HID_COL_Application = 0X01,
+  HID_COL_Logical     = 0X02,
+} HidCollectionType;
+
+typedef enum {
+  HID_USG_TYPE_Undefined = 0,
+
+  // Controls
+  HID_USG_TYPE_LinearControl,
+  HID_USG_TYPE_OnOffControl,
+  HID_USG_TYPE_MomentaryControl,
+  HID_USG_TYPE_OneShotControl,
+  HID_USG_TYPE_RetriggerControl,
+
+  // Data
+  HID_USG_TYPE_Selector,
+  HID_USG_TYPE_StaticValue,
+  HID_USG_TYPE_StaticFlag,
+  HID_USG_TYPE_DynamicValue,
+  HID_USG_TYPE_DynamicFlag,
+
+  // Collections
+  HID_USG_TYPE_NamedArray,
+  HID_USG_TYPE_ApplicationCollection,
+  HID_USG_TYPE_LogicalCollection,
+  HID_USG_TYPE_PhysicalCollection,
+  HID_USG_TYPE_UsageSwitch,
+  HID_USG_TYPE_UsageModifier,
+
+  HID_USG_TYPE_UD   = HID_USG_TYPE_Undefined,
+  HID_USG_TYPE_LC   = HID_USG_TYPE_LinearControl,
+  HID_USG_TYPE_OOC  = HID_USG_TYPE_OnOffControl,
+  HID_USG_TYPE_MC   = HID_USG_TYPE_MomentaryControl,
+  HID_USG_TYPE_OSC  = HID_USG_TYPE_OneShotControl,
+  HID_USG_TYPE_RTC  = HID_USG_TYPE_RetriggerControl,
+  HID_USG_TYPE_SEL  = HID_USG_TYPE_Selector,
+  HID_USG_TYPE_SV   = HID_USG_TYPE_StaticValue,
+  HID_USG_TYPE_SF   = HID_USG_TYPE_StaticFlag,
+  HID_USG_TYPE_DV   = HID_USG_TYPE_DynamicValue,
+  HID_USG_TYPE_DF   = HID_USG_TYPE_DynamicFlag,
+  HID_USG_TYPE_NARY = HID_USG_TYPE_NamedArray,
+  HID_USG_TYPE_CA   = HID_USG_TYPE_ApplicationCollection,
+  HID_USG_TYPE_CL   = HID_USG_TYPE_LogicalCollection,
+  HID_USG_TYPE_CP   = HID_USG_TYPE_PhysicalCollection,
+  HID_USG_TYPE_US   = HID_USG_TYPE_UsageSwitch,
+  HID_USG_TYPE_UM   = HID_USG_TYPE_UsageModifier,
+} HidUsageType;
+
+typedef enum {
+  HID_USG_GDT_Pointer                   = 0X01,
+  HID_USG_GDT_Mouse                     = 0X02,
+  HID_USG_GDT_Joystick                  = 0X04,
+  HID_USG_GDT_GamePad                   = 0X05,
+  HID_USG_GDT_Keyboard                  = 0X06,
+  HID_USG_GDT_Keypad                    = 0X07,
+  HID_USG_GDT_MultiAxisController       = 0X08,
+  HID_USG_GDT_TabletPCSystemControls    = 0X09,
+  HID_USG_GDT_X                         = 0X30,
+  HID_USG_GDT_Y                         = 0X31,
+  HID_USG_GDT_Z                         = 0X32,
+  HID_USG_GDT_Rx                        = 0X33,
+  HID_USG_GDT_Ry                        = 0X34,
+  HID_USG_GDT_Rz                        = 0X35,
+  HID_USG_GDT_Slider                    = 0X36,
+  HID_USG_GDT_Dial                      = 0X37,
+  HID_USG_GDT_Wheel                     = 0X38,
+  HID_USG_GDT_HatSwitch                 = 0X39,
+  HID_USG_GDT_CountedBuffer             = 0X3A,
+  HID_USG_GDT_ByteCount                 = 0X3B,
+  HID_USG_GDT_MotionWakeup              = 0X3C,
+  HID_USG_GDT_Start                     = 0X3D,
+  HID_USG_GDT_Select                    = 0X3E,
+  HID_USG_GDT_Vx                        = 0X40,
+  HID_USG_GDT_Vy                        = 0X41,
+  HID_USG_GDT_Vz                        = 0X42,
+  HID_USG_GDT_Vbrx                      = 0X43,
+  HID_USG_GDT_Vbry                      = 0X44,
+  HID_USG_GDT_Vbrz                      = 0X45,
+  HID_USG_GDT_Vno                       = 0X46,
+  HID_USG_GDT_FeatureNotification       = 0X47,
+  HID_USG_GDT_ResolutionMultiplier      = 0X48,
+  HID_USG_GDT_SystemControl             = 0X80,
+  HID_USG_GDT_SystemPowerDown           = 0X81,
+  HID_USG_GDT_SystemSleep               = 0X82,
+  HID_USG_GDT_SystemWakeUp              = 0X83,
+  HID_USG_GDT_SystemContextMenu         = 0X84,
+  HID_USG_GDT_SystemMainMenu            = 0X85,
+  HID_USG_GDT_SystemAppMenu             = 0X86,
+  HID_USG_GDT_SystemMenuHelp            = 0X87,
+  HID_USG_GDT_SystemMenuExit            = 0X88,
+  HID_USG_GDT_SystemMenuSelect          = 0X89,
+  HID_USG_GDT_SystemMenuRight           = 0X8A,
+  HID_USG_GDT_SystemMenuLeft            = 0X8B,
+  HID_USG_GDT_SystemMenuUp              = 0X8C,
+  HID_USG_GDT_SystemMenuDown            = 0X8D,
+  HID_USG_GDT_SystemColdRestart         = 0X8E,
+  HID_USG_GDT_SystemWarmRestart         = 0X8F,
+  HID_USG_GDT_DPadUp                    = 0X90,
+  HID_USG_GDT_DPadDown                  = 0X91,
+  HID_USG_GDT_DPadRight                 = 0X92,
+  HID_USG_GDT_DPadLeft                  = 0X93,
+  HID_USG_GDT_SystemDock                = 0XA0,
+  HID_USG_GDT_SystemUndock              = 0XA1,
+  HID_USG_GDT_SystemSetup               = 0XA2,
+  HID_USG_GDT_SystemBreak               = 0XA3,
+  HID_USG_GDT_SystemDebuggerBreak       = 0XA4,
+  HID_USG_GDT_ApplicationBreak          = 0XA5,
+  HID_USG_GDT_ApplicationDebuggerBreak  = 0XA6,
+  HID_USG_GDT_SystemSpeakerMute         = 0XA7,
+  HID_USG_GDT_SystemHibernate           = 0XA8,
+  HID_USG_GDT_SystemDisplayInvert       = 0XB0,
+  HID_USG_GDT_SystemDisplayInternal     = 0XB1,
+  HID_USG_GDT_SystemDisplayExternal     = 0XB2,
+  HID_USG_GDT_SystemDisplayBoth         = 0XB3,
+  HID_USG_GDT_SystemDisplayDual         = 0XB4,
+  HID_USG_GDT_SystemDisplayToggleIntExt = 0XB5,
+  HID_USG_GDT_SystemDisplaySwap         = 0XB6,
+  HID_USG_GDT_SystemDisplayLCDAutoscale = 0XB7,
+} HidGenericDesktopUsage;
+
+typedef enum {
+  HID_USG_KBD_KeyboardErrorRollOver     = 0X01,
+  HID_USG_KBD_KeyboardPostFail          = 0X02,
+  HID_USG_KBD_KeyboardErrorUndefined    = 0X03,
+  HID_USG_KBD_KeyboardA                 = 0X04,
+  HID_USG_KBD_KeyboardB                 = 0X05,
+  HID_USG_KBD_KeyboardC                 = 0X06,
+  HID_USG_KBD_KeyboardD                 = 0X07,
+  HID_USG_KBD_KeyboardE                 = 0X08,
+  HID_USG_KBD_KeyboardF                 = 0X09,
+  HID_USG_KBD_KeyboardG                 = 0X0A,
+  HID_USG_KBD_KeyboardH                 = 0X0B,
+  HID_USG_KBD_KeyboardI                 = 0X0C,
+  HID_USG_KBD_KeyboardJ                 = 0X0D,
+  HID_USG_KBD_KeyboardK                 = 0X0E,
+  HID_USG_KBD_KeyboardL                 = 0X0F,
+  HID_USG_KBD_KeyboardM                 = 0X10,
+  HID_USG_KBD_KeyboardN                 = 0X11,
+  HID_USG_KBD_KeyboardO                 = 0X12,
+  HID_USG_KBD_KeyboardP                 = 0X13,
+  HID_USG_KBD_KeyboardQ                 = 0X14,
+  HID_USG_KBD_KeyboardR                 = 0X15,
+  HID_USG_KBD_KeyboardS                 = 0X16,
+  HID_USG_KBD_KeyboardT                 = 0X17,
+  HID_USG_KBD_KeyboardU                 = 0X18,
+  HID_USG_KBD_KeyboardV                 = 0X19,
+  HID_USG_KBD_KeyboardW                 = 0X1A,
+  HID_USG_KBD_KeyboardX                 = 0X1B,
+  HID_USG_KBD_KeyboardY                 = 0X1C,
+  HID_USG_KBD_KeyboardZ                 = 0X1D,
+  HID_USG_KBD_Keyboard1                 = 0X1E,
+  HID_USG_KBD_Keyboard2                 = 0X1F,
+  HID_USG_KBD_Keyboard3                 = 0X20,
+  HID_USG_KBD_Keyboard4                 = 0X21,
+  HID_USG_KBD_Keyboard5                 = 0X22,
+  HID_USG_KBD_Keyboard6                 = 0X23,
+  HID_USG_KBD_Keyboard7                 = 0X24,
+  HID_USG_KBD_Keyboard8                 = 0X25,
+  HID_USG_KBD_Keyboard9                 = 0X26,
+  HID_USG_KBD_Keyboard0                 = 0X27,
+  HID_USG_KBD_KeyboardEnter             = 0X28,
+  HID_USG_KBD_KeyboardEscape            = 0X29,
+  HID_USG_KBD_KeyboardBackspace         = 0X2A,
+  HID_USG_KBD_KeyboardTab               = 0X2B,
+  HID_USG_KBD_KeyboardSpace             = 0X2C,
+  HID_USG_KBD_KeyboardMinus             = 0X2D,
+  HID_USG_KBD_KeyboardEqual             = 0X2E,
+  HID_USG_KBD_KeyboardLeftBracket       = 0X2F,
+  HID_USG_KBD_KeyboardRightBracket      = 0X30,
+  HID_USG_KBD_KeyboardBackslash         = 0X31,
+  HID_USG_KBD_KeyboardEurope1           = 0X32,
+  HID_USG_KBD_KeyboardSemicolon         = 0X33,
+  HID_USG_KBD_KeyboardApostrophe        = 0X34,
+  HID_USG_KBD_KeyboardGrave             = 0X35,
+  HID_USG_KBD_KeyboardComma             = 0X36,
+  HID_USG_KBD_KeyboardPeriod            = 0X37,
+  HID_USG_KBD_KeyboardSlash             = 0X38,
+  HID_USG_KBD_KeyboardCapsLock          = 0X39,
+  HID_USG_KBD_KeyboardF1                = 0X3A,
+  HID_USG_KBD_KeyboardF2                = 0X3B,
+  HID_USG_KBD_KeyboardF3                = 0X3C,
+  HID_USG_KBD_KeyboardF4                = 0X3D,
+  HID_USG_KBD_KeyboardF5                = 0X3E,
+  HID_USG_KBD_KeyboardF6                = 0X3F,
+  HID_USG_KBD_KeyboardF7                = 0X40,
+  HID_USG_KBD_KeyboardF8                = 0X41,
+  HID_USG_KBD_KeyboardF9                = 0X42,
+  HID_USG_KBD_KeyboardF10               = 0X43,
+  HID_USG_KBD_KeyboardF11               = 0X44,
+  HID_USG_KBD_KeyboardF12               = 0X45,
+  HID_USG_KBD_KeyboardPrintScreen       = 0X46,
+  HID_USG_KBD_KeyboardScrollLock        = 0X47,
+  HID_USG_KBD_KeyboardPause             = 0X48,
+  HID_USG_KBD_KeyboardInsert            = 0X49,
+  HID_USG_KBD_KeyboardHome              = 0X4A,
+  HID_USG_KBD_KeyboardPageUp            = 0X4B,
+  HID_USG_KBD_KeyboardDelete            = 0X4C,
+  HID_USG_KBD_KeyboardEnd               = 0X4D,
+  HID_USG_KBD_KeyboardPageDown          = 0X4E,
+  HID_USG_KBD_KeyboardRightArrow        = 0X4F,
+  HID_USG_KBD_KeyboardLeftArrow         = 0X50,
+  HID_USG_KBD_KeyboardDownArrow         = 0X51,
+  HID_USG_KBD_KeyboardUpArrow           = 0X52,
+  HID_USG_KBD_KeypadNumLock             = 0X53,
+  HID_USG_KBD_KeypadSlash               = 0X54,
+  HID_USG_KBD_KeypadAsterisk            = 0X55,
+  HID_USG_KBD_KeypadMinus               = 0X56,
+  HID_USG_KBD_KeypadPlus                = 0X57,
+  HID_USG_KBD_KeypadEnter               = 0X58,
+  HID_USG_KBD_Keypad1                   = 0X59,
+  HID_USG_KBD_Keypad2                   = 0X5A,
+  HID_USG_KBD_Keypad3                   = 0X5B,
+  HID_USG_KBD_Keypad4                   = 0X5C,
+  HID_USG_KBD_Keypad5                   = 0X5D,
+  HID_USG_KBD_Keypad6                   = 0X5E,
+  HID_USG_KBD_Keypad7                   = 0X5F,
+  HID_USG_KBD_Keypad8                   = 0X60,
+  HID_USG_KBD_Keypad9                   = 0X61,
+  HID_USG_KBD_Keypad0                   = 0X62,
+  HID_USG_KBD_KeypadPeriod              = 0X63,
+  HID_USG_KBD_KeyboardEurope2           = 0X64,
+  HID_USG_KBD_KeyboardApplication       = 0X65,
+  HID_USG_KBD_KeyboardPower             = 0X66,
+  HID_USG_KBD_KeypadEqual               = 0X67,
+  HID_USG_KBD_KeyboardF13               = 0X68,
+  HID_USG_KBD_KeyboardF14               = 0X69,
+  HID_USG_KBD_KeyboardF15               = 0X6A,
+  HID_USG_KBD_KeyboardF16               = 0X6B,
+  HID_USG_KBD_KeyboardF17               = 0X6C,
+  HID_USG_KBD_KeyboardF18               = 0X6D,
+  HID_USG_KBD_KeyboardF19               = 0X6E,
+  HID_USG_KBD_KeyboardF20               = 0X6F,
+  HID_USG_KBD_KeyboardF21               = 0X70,
+  HID_USG_KBD_KeyboardF22               = 0X71,
+  HID_USG_KBD_KeyboardF23               = 0X72,
+  HID_USG_KBD_KeyboardF24               = 0X73,
+  HID_USG_KBD_KeyboardExecute           = 0X74,
+  HID_USG_KBD_KeyboardHelp              = 0X75,
+  HID_USG_KBD_KeyboardMenu              = 0X76,
+  HID_USG_KBD_KeyboardSelect            = 0X77,
+  HID_USG_KBD_KeyboardStop              = 0X78,
+  HID_USG_KBD_KeyboardAgain             = 0X79,
+  HID_USG_KBD_KeyboardUndo              = 0X7A,
+  HID_USG_KBD_KeyboardCut               = 0X7B,
+  HID_USG_KBD_KeyboardCopy              = 0X7C,
+  HID_USG_KBD_KeyboardPaste             = 0X7D,
+  HID_USG_KBD_KeyboardFind              = 0X7E,
+  HID_USG_KBD_KeyboardMute              = 0X7F,
+  HID_USG_KBD_KeyboardVolumeUp          = 0X80,
+  HID_USG_KBD_KeyboardVolumeDown        = 0X81,
+  HID_USG_KBD_KeyboardLockingCapsLock   = 0X82,
+  HID_USG_KBD_KeyboardLockingNumLock    = 0X83,
+  HID_USG_KBD_KeyboardLockingScrollLock = 0X84,
+  HID_USG_KBD_KeypadComma               = 0X85,
+  HID_USG_KBD_KeypadEqualSign           = 0X86,
+  HID_USG_KBD_KeyboardInternational1    = 0X87,
+  HID_USG_KBD_KeyboardInternational2    = 0X88,
+  HID_USG_KBD_KeyboardInternational3    = 0X89,
+  HID_USG_KBD_KeyboardInternational4    = 0X8A,
+  HID_USG_KBD_KeyboardInternational5    = 0X8B,
+  HID_USG_KBD_KeyboardInternational6    = 0X8C,
+  HID_USG_KBD_KeyboardInternational7    = 0X8D,
+  HID_USG_KBD_KeyboardInternational8    = 0X8E,
+  HID_USG_KBD_KeyboardInternational9    = 0X8F,
+  HID_USG_KBD_KeyboardLanguage1         = 0X90,
+  HID_USG_KBD_KeyboardLanguage2         = 0X91,
+  HID_USG_KBD_KeyboardLanguage3         = 0X92,
+  HID_USG_KBD_KeyboardLanguage4         = 0X93,
+  HID_USG_KBD_KeyboardLanguage5         = 0X94,
+  HID_USG_KBD_KeyboardLanguage6         = 0X95,
+  HID_USG_KBD_KeyboardLanguage7         = 0X96,
+  HID_USG_KBD_KeyboardLanguage8         = 0X97,
+  HID_USG_KBD_KeyboardLanguage9         = 0X98,
+  HID_USG_KBD_KeyboardAlternateErase    = 0X99,
+  HID_USG_KBD_KeyboardSystemRequest     = 0X9A,
+  HID_USG_KBD_KeyboardCancel            = 0X9B,
+  HID_USG_KBD_KeyboardClear             = 0X9C,
+  HID_USG_KBD_KeyboardPrior             = 0X9D,
+  HID_USG_KBD_KeyboardReturn            = 0X9E,
+  HID_USG_KBD_KeyboardSeparator         = 0X9F,
+  HID_USG_KBD_KeyboardOut               = 0XA0,
+  HID_USG_KBD_KeyboardOper              = 0XA1,
+  HID_USG_KBD_KeyboardClearAgain        = 0XA2,
+  HID_USG_KBD_KeyboardCrSel             = 0XA3,
+  HID_USG_KBD_KeyboardExSel             = 0XA4,
+  HID_USG_KBD_Keypad00                  = 0XB0,
+  HID_USG_KBD_Keypad000                 = 0XB1,
+  HID_USG_KBD_ThousandsSeparator        = 0XB2,
+  HID_USG_KBD_DecimalSeparator          = 0XB3,
+  HID_USG_KBD_CurrencyUnit              = 0XB4,
+  HID_USG_KBD_CurrencySubunit           = 0XB5,
+  HID_USG_KBD_KeypadLeftParenthesis     = 0XB6,
+  HID_USG_KBD_KeypadRightParenthesis    = 0XB7,
+  HID_USG_KBD_KeypadLeftBrace           = 0XB8,
+  HID_USG_KBD_KeypadRightBrace          = 0XB9,
+  HID_USG_KBD_KeypadTab                 = 0XBA,
+  HID_USG_KBD_KeypadBackspace           = 0XBB,
+  HID_USG_KBD_KeypadA                   = 0XBC,
+  HID_USG_KBD_KeypadB                   = 0XBD,
+  HID_USG_KBD_KeypadC                   = 0XBE,
+  HID_USG_KBD_KeypadD                   = 0XBF,
+  HID_USG_KBD_KeypadE                   = 0XC0,
+  HID_USG_KBD_KeypadF                   = 0XC1,
+  HID_USG_KBD_KeypadBitwiseXOR          = 0XC2,
+  HID_USG_KBD_KeypadExponentiate        = 0XC3,
+  HID_USG_KBD_KeypadModulo              = 0XC4,
+  HID_USG_KBD_KeypadLess                = 0XC5,
+  HID_USG_KBD_KeypadGreater             = 0XC6,
+  HID_USG_KBD_KeypadBitwiseAND          = 0XC7,
+  HID_USG_KBD_KeypadBooleanAND          = 0XC8,
+  HID_USG_KBD_KeypadBitwiseOR           = 0XC9,
+  HID_USG_KBD_KeypadBooleanOR           = 0XCA,
+  HID_USG_KBD_KeypadColon               = 0XCB,
+  HID_USG_KBD_KeypadNumber              = 0XCC,
+  HID_USG_KBD_KeypadSpace               = 0XCD,
+  HID_USG_KBD_KeypadAt                  = 0XCE,
+  HID_USG_KBD_KeypadBoleanNOT           = 0XCF,
+  HID_USG_KBD_KeypadMemoryStore         = 0XD0,
+  HID_USG_KBD_KeypadMemoryRecall        = 0XD1,
+  HID_USG_KBD_KeypadMemoryClear         = 0XD2,
+  HID_USG_KBD_KeypadMemoryAdd           = 0XD3,
+  HID_USG_KBD_KeypadMemorySubtract      = 0XD4,
+  HID_USG_KBD_KeypadMemoryMultiply      = 0XD5,
+  HID_USG_KBD_KeypadMemoryDivide        = 0XD6,
+  HID_USG_KBD_KeypadPlusMinus           = 0XD7,
+  HID_USG_KBD_KeypadClear               = 0XD8,
+  HID_USG_KBD_KeypadClearEntry          = 0XD9,
+  HID_USG_KBD_KeypadBinary              = 0XDA,
+  HID_USG_KBD_KeypadOctal               = 0XDB,
+  HID_USG_KBD_KeypadDecimal             = 0XDC,
+  HID_USG_KBD_KeypadHexadecimal         = 0XDD,
+  HID_USG_KBD_KeyboardLeftControl       = 0XE0,
+  HID_USG_KBD_KeyboardLeftShift         = 0XE1,
+  HID_USG_KBD_KeyboardLeftAlt           = 0XE2,
+  HID_USG_KBD_KeyboardLeftGUI           = 0XE3,
+  HID_USG_KBD_KeyboardRightControl      = 0XE4,
+  HID_USG_KBD_KeyboardRightShift        = 0XE5,
+  HID_USG_KBD_KeyboardRightAlt          = 0XE6,
+  HID_USG_KBD_KeyboardRightGUI          = 0XE7,
+} HidKeyboardUsage;
+
+typedef enum {
+  HID_USG_LED_NumLock                 = 0X01,
+  HID_USG_LED_CapsLock                = 0X02,
+  HID_USG_LED_ScrollLock              = 0X03,
+  HID_USG_LED_Compose                 = 0X04,
+  HID_USG_LED_Kana                    = 0X05,
+  HID_USG_LED_Power                   = 0X06,
+  HID_USG_LED_Shift                   = 0X07,
+  HID_USG_LED_DoNotDisturb            = 0X08,
+  HID_USG_LED_Mute                    = 0X09,
+  HID_USG_LED_ToneEnable              = 0X0A,
+  HID_USG_LED_HighCutFilter           = 0X0B,
+  HID_USG_LED_LowCutFilter            = 0X0C,
+  HID_USG_LED_EqualizerEnable         = 0X0D,
+  HID_USG_LED_SoundFieldOn            = 0X0E,
+  HID_USG_LED_SurroundOn              = 0X0F,
+  HID_USG_LED_Repeat                  = 0X10,
+  HID_USG_LED_Stereo                  = 0X11,
+  HID_USG_LED_SamplingRateDetect      = 0X12,
+  HID_USG_LED_Spinning                = 0X13,
+  HID_USG_LED_CAV                     = 0X14,
+  HID_USG_LED_CLV                     = 0X15,
+  HID_USG_LED_RecordingFormatDetect   = 0X16,
+  HID_USG_LED_OffHook                 = 0X17,
+  HID_USG_LED_Ring                    = 0X18,
+  HID_USG_LED_MessageWaiting          = 0X19,
+  HID_USG_LED_DataMode                = 0X1A,
+  HID_USG_LED_BatteryOperation        = 0X1B,
+  HID_USG_LED_BatteryOK               = 0X1C,
+  HID_USG_LED_BatteryLow              = 0X1D,
+  HID_USG_LED_Speaker                 = 0X1E,
+  HID_USG_LED_HeadSet                 = 0X1F,
+  HID_USG_LED_Hold                    = 0X20,
+  HID_USG_LED_Microphone              = 0X21,
+  HID_USG_LED_Coverage                = 0X22,
+  HID_USG_LED_NightMode               = 0X23,
+  HID_USG_LED_SendCalls               = 0X24,
+  HID_USG_LED_CallPickup              = 0X25,
+  HID_USG_LED_Conference              = 0X26,
+  HID_USG_LED_StandBy                 = 0X27,
+  HID_USG_LED_CameraOn                = 0X28,
+  HID_USG_LED_CameraOff               = 0X29,
+  HID_USG_LED_OnLine                  = 0X2A,
+  HID_USG_LED_OffLine                 = 0X2B,
+  HID_USG_LED_Busy                    = 0X2C,
+  HID_USG_LED_Ready                   = 0X2D,
+  HID_USG_LED_PaperOut                = 0X2E,
+  HID_USG_LED_PaperJam                = 0X2F,
+  HID_USG_LED_Remote                  = 0X30,
+  HID_USG_LED_Forward                 = 0X31,
+  HID_USG_LED_Reverse                 = 0X32,
+  HID_USG_LED_Stop                    = 0X33,
+  HID_USG_LED_Rewind                  = 0X34,
+  HID_USG_LED_FastForward             = 0X35,
+  HID_USG_LED_Play                    = 0X36,
+  HID_USG_LED_Pause                   = 0X37,
+  HID_USG_LED_Record                  = 0X38,
+  HID_USG_LED_Error                   = 0X39,
+  HID_USG_LED_UsageSelectedIndicator  = 0X3A,
+  HID_USG_LED_UsageInUseIndicator     = 0X3B,
+  HID_USG_LED_UsageMultiModeIndicator = 0X3C,
+  HID_USG_LED_IndicatorOn             = 0X3D,
+  HID_USG_LED_IndicatorFlash          = 0X3E,
+  HID_USG_LED_IndicatorSlowBlink      = 0X3F,
+  HID_USG_LED_IndicatorFastBlink      = 0X40,
+  HID_USG_LED_IndicatorOff            = 0X41,
+  HID_USG_LED_FlashOnTime             = 0X42,
+  HID_USG_LED_SlowBlinkOnTime         = 0X43,
+  HID_USG_LED_SlowBlinkOffTime        = 0X44,
+  HID_USG_LED_FastBlinkOnTime         = 0X45,
+  HID_USG_LED_FastBlinkOffTime        = 0X46,
+  HID_USG_LED_UsageIndicatorColor     = 0X47,
+  HID_USG_LED_IndicatorRed            = 0X48,
+  HID_USG_LED_IndicatorGreen          = 0X49,
+  HID_USG_LED_IndicatorAmber          = 0X4A,
+  HID_USG_LED_GenericIndicator        = 0X4B,
+  HID_USG_LED_SystemSuspend           = 0X4C,
+  HID_USG_LED_ExternalPowerConnected  = 0X4D,
+} HidLEDsUsage;
+
+typedef enum {
+  HID_USG_BRL_BrailleDisplay         = 0X001,
+  HID_USG_BRL_BrailleRow             = 0X002,
+  HID_USG_BRL_8DotCell               = 0X003,
+  HID_USG_BRL_6DotCell               = 0X004,
+  HID_USG_BRL_CellCount              = 0X005,
+  HID_USG_BRL_ScreenReaderControl    = 0X006,
+  HID_USG_BRL_ScreenReaderIdentifier = 0X007,
+  HID_USG_BRL_RouterSet1             = 0X0FA,
+  HID_USG_BRL_RouterSet2             = 0X0FB,
+  HID_USG_BRL_RouterSet3             = 0X0FC,
+  HID_USG_BRL_RouterKey              = 0X100,
+  HID_USG_BRL_RowRouterKey           = 0X101,
+  HID_USG_BRL_BrailleButtons         = 0X200,
+  HID_USG_BRL_KeyboardDot1           = 0X201,
+  HID_USG_BRL_KeyboardDot2           = 0X202,
+  HID_USG_BRL_KeyboardDot3           = 0X203,
+  HID_USG_BRL_KeyboardDot4           = 0X204,
+  HID_USG_BRL_KeyboardDot5           = 0X205,
+  HID_USG_BRL_KeyboardDot6           = 0X206,
+  HID_USG_BRL_KeyboardDot7           = 0X207,
+  HID_USG_BRL_KeyboardDot8           = 0X208,
+  HID_USG_BRL_KeyboardSpace          = 0X209,
+  HID_USG_BRL_KeyboardLeftSpace      = 0X20A,
+  HID_USG_BRL_KeyboardRightSpace     = 0X20B,
+  HID_USG_BRL_FrontControls          = 0X20C,
+  HID_USG_BRL_LeftControls           = 0X20D,
+  HID_USG_BRL_RightControls          = 0X20E,
+  HID_USG_BRL_TopControls            = 0X20F,
+  HID_USG_BRL_JoystickCenter         = 0X210,
+  HID_USG_BRL_JoystickUp             = 0X211,
+  HID_USG_BRL_JoystickDown           = 0X212,
+  HID_USG_BRL_JoystickLeft           = 0X213,
+  HID_USG_BRL_JoystickRight          = 0X214,
+  HID_USG_BRL_DPadCenter             = 0X215,
+  HID_USG_BRL_DPadUp                 = 0X216,
+  HID_USG_BRL_DPadDown               = 0X217,
+  HID_USG_BRL_DPadLeft               = 0X218,
+  HID_USG_BRL_DPadRight              = 0X219,
+  HID_USG_BRL_PanLeft                = 0X21A,
+  HID_USG_BRL_PanRight               = 0X21B,
+  HID_USG_BRL_RockerUp               = 0X21C,
+  HID_USG_BRL_RockerDown             = 0X21D,
+  HID_USG_BRL_RockerPress            = 0X21E,
+} HidBrailleUsage;
+
+typedef enum {
+  HID_UPG_GenericDesktop          = 0X01,
+  HID_UPG_Simulation              = 0X02,
+  HID_UPG_VirtualReality          = 0X03,
+  HID_UPG_Sport                   = 0X04,
+  HID_UPG_Game                    = 0X05,
+  HID_UPG_GenericDevice           = 0X06,
+  HID_UPG_Keyboard_Keypad         = 0X07,
+  HID_UPG_LEDs                    = 0X08,
+  HID_UPG_Button                  = 0X09,
+  HID_UPG_Ordinal                 = 0X0A,
+  HID_UPG_Telephony               = 0X0B,
+  HID_UPG_Consumer                = 0X0C,
+  HID_UPG_Digitizer               = 0X0D,
+  HID_UPG_PhysicalInterfaceDevice = 0X0F,
+  HID_UPG_Unicode                 = 0X10,
+  HID_UPG_AlphanumericDisplay     = 0X14,
+  HID_UPG_MedicalInstruments      = 0X40,
+  HID_UPG_Braille                 = 0X41,
+  HID_UPG_BarCodeScanner          = 0X8C,
+  HID_UPG_Scale                   = 0X8D,
+  HID_UPG_MagneticStripeReader    = 0X8E,
+  HID_UPG_Camera                  = 0X90,
+  HID_UPG_Arcade                  = 0X91,
+} HidUsagePage;
+
+typedef enum {
+  HID_USG_FLG_CONSTANT      = 0X001,
+  HID_USG_FLG_VARIABLE      = 0X002,
+  HID_USG_FLG_RELATIVE      = 0X004,
+  HID_USG_FLG_WRAP          = 0X008,
+  HID_USG_FLG_NON_LINEAR    = 0X010,
+  HID_USG_FLG_NO_PREFERRED  = 0X020,
+  HID_USG_FLG_NULL_STATE    = 0X040,
+  HID_USG_FLG_VOLATILE      = 0X080,
+  HID_USG_FLG_BUFFERED_BYTE = 0X100,
+
+  HID_USG_FLG_DATA           = 0, // !HID_USG_FLG_CONSTANT,
+  HID_USG_FLG_ARRAY          = 0, // !HID_USG_FLG_VARIABLE,
+  HID_USG_FLG_ABSOLUTE       = 0, // !HID_USG_FLAG_RELATIVE,
+} HidUsageFlags;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_DEFS */
diff --git a/Headers/hid_inspect.h b/Headers/hid_inspect.h
new file mode 100644
index 0000000..91f3310
--- /dev/null
+++ b/Headers/hid_inspect.h
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_INSPECT
+#define BRLTTY_INCLUDED_HID_INSPECT
+
+#include "hid_types.h"
+#include "strfmth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  unsigned char count;
+  HidReportIdentifier identifiers[];
+} HidReports;
+
+extern HidReports *hidGetReports (const HidItemsDescriptor *items);
+
+typedef int HidItemLister (const char *line, void *data);
+extern int hidListItems (const HidItemsDescriptor *items, HidItemLister *listItem, void *data);
+
+extern STR_DECLARE_FORMATTER(hidFormatUsageFlags, HidUnsignedValue flags);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_INSPECT */
diff --git a/Headers/hid_items.h b/Headers/hid_items.h
new file mode 100644
index 0000000..0cf9f58
--- /dev/null
+++ b/Headers/hid_items.h
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_ITEMS
+#define BRLTTY_INCLUDED_HID_ITEMS
+
+#include "hid_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef union {
+  HidUnsignedValue u;
+  HidSignedValue s;
+} HidItemValue;
+
+typedef struct {
+  HidItemValue value;
+  uint8_t tag;
+  uint8_t valueSize;
+} HidItem;
+
+extern int hidNextItem (
+  HidItem *item,
+  const unsigned char **bytes,
+  size_t *count
+);
+
+extern unsigned char hidItemValueSize (unsigned char item);
+
+extern int hidReportSize (
+  const HidItemsDescriptor *items,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_ITEMS */
diff --git a/Headers/hid_tables.h b/Headers/hid_tables.h
new file mode 100644
index 0000000..4ef244f
--- /dev/null
+++ b/Headers/hid_tables.h
@@ -0,0 +1,121 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_TABLES
+#define BRLTTY_INCLUDED_HID_TABLES
+
+#include "hid_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *name;
+  HidUnsignedValue value;
+} HidTableEntryHeader;
+
+#define HID_TABLE_ENTRY_HEADER HidTableEntryHeader header
+
+#define HID_TABLE_ENTRY(prefix, suffix, ...) { \
+  .header = { \
+    .name = #suffix, \
+    .value = prefix ## _ ## suffix, \
+  }, \
+  __VA_ARGS__ \
+}
+
+typedef struct {
+  const void *const entries;
+  size_t const size;
+  size_t const count;
+  const HidTableEntryHeader **sorted;
+} HidTable;
+
+#define HID_BEGIN_TABLE(type) static const Hid##type##Entry hid##type##Entries[] = {
+
+#define HID_END_TABLE(type) }; \
+static HidTable hid##type##Table = { \
+  .entries = hid##type##Entries, \
+  .size = sizeof(hid##type##Entries[0]), \
+  .count = ARRAY_COUNT(hid##type##Entries), \
+  .sorted = NULL \
+}; \
+const Hid##type##Entry *hid##type##Entry (HidUnsignedValue value) { \
+  return hidTableEntry(&hid##type##Table, value); \
+}
+
+#define HID_TABLE_METHODS(type) \
+extern const Hid##type##Entry *hid##type##Entry (HidUnsignedValue value);
+
+typedef struct {
+  HID_TABLE_ENTRY_HEADER;
+} HidItemTagEntry;
+HID_TABLE_METHODS(ItemTag)
+
+typedef struct {
+  HID_TABLE_ENTRY_HEADER;
+} HidCollectionTypeEntry;
+HID_TABLE_METHODS(CollectionType)
+
+typedef struct {
+  HID_TABLE_ENTRY_HEADER;
+} HidUsageTypeEntry;
+HID_TABLE_METHODS(UsageType)
+
+#define HID_USAGE_ENTRY_HEADER \
+  HID_TABLE_ENTRY_HEADER; \
+  unsigned char usageType
+
+typedef struct {
+  HID_USAGE_ENTRY_HEADER;
+} HidUsageEntryHeader;
+
+typedef struct {
+  HID_USAGE_ENTRY_HEADER;
+} HidGenericDesktopUsageEntry;
+HID_TABLE_METHODS(GenericDesktopUsage)
+
+typedef struct {
+  HID_USAGE_ENTRY_HEADER;
+} HidKeyboardUsageEntry;
+HID_TABLE_METHODS(KeyboardUsage)
+
+typedef struct {
+  HID_USAGE_ENTRY_HEADER;
+} HidLEDsUsageEntry;
+HID_TABLE_METHODS(LEDsUsage)
+
+typedef struct {
+  HID_USAGE_ENTRY_HEADER;
+} HidBrailleUsageEntry;
+HID_TABLE_METHODS(BrailleUsage)
+
+typedef struct {
+  HID_TABLE_ENTRY_HEADER;
+  HidTable *usageTable;
+} HidUsagePageEntry;
+HID_TABLE_METHODS(UsagePage)
+
+extern const void *hidTableEntry (HidTable *table, HidUnsignedValue value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_TABLES */
diff --git a/Headers/hid_types.h b/Headers/hid_types.h
new file mode 100644
index 0000000..130bd77
--- /dev/null
+++ b/Headers/hid_types.h
@@ -0,0 +1,94 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_TYPES
+#define BRLTTY_INCLUDED_HID_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint16_t HidDeviceIdentifier;
+typedef uint8_t HidReportIdentifier;
+
+typedef uint32_t HidUnsignedValue;
+typedef int32_t HidSignedValue;
+
+typedef struct {
+  size_t count;
+  unsigned char bytes[];
+} HidItemsDescriptor;
+
+typedef struct {
+  size_t input;
+  size_t output;
+  size_t feature;
+} HidReportSize;
+
+typedef struct {
+  HidDeviceIdentifier vendorIdentifier;
+  HidDeviceIdentifier productIdentifier;
+} HidCommonProperties;
+
+typedef struct {
+  const char *manufacturerName;
+  const char *productDescription;
+  const char *serialNumber;
+} HidUSBProperties;
+
+typedef struct {
+  const char *macAddress;
+  const char *deviceName;
+} HidBluetoothProperties;
+
+typedef struct {
+  HidCommonProperties common;
+  HidUSBProperties usb;
+} HidUSBFilter;
+
+typedef struct {
+  HidCommonProperties common;
+  HidBluetoothProperties bluetooth;
+} HidBluetoothFilter;
+
+typedef struct {
+  HidCommonProperties common;
+  HidUSBProperties usb;
+  HidBluetoothProperties bluetooth;
+
+  struct {
+    unsigned char wantUSB:1;
+    unsigned char wantBluetooth:1;
+  } flags;
+} HidFilter;
+
+typedef struct {
+  const void *data;
+  const char *name;
+  HidDeviceIdentifier vendor;
+  HidDeviceIdentifier product;
+} HidModelEntry;
+
+#define BEGIN_HID_MODEL_TABLE static const HidModelEntry hidModelTable[] = {
+#define END_HID_MODEL_TABLE { .name=NULL, .vendor=0 } };
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_TYPES */
diff --git a/Headers/hostcmd.h b/Headers/hostcmd.h
new file mode 100644
index 0000000..a3b5f4d
--- /dev/null
+++ b/Headers/hostcmd.h
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HOSTCMD
+#define BRLTTY_INCLUDED_HOSTCMD
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  FILE **standardInput;
+  FILE **standardOutput;
+  FILE **standardError;
+
+  unsigned char asynchronous : 1;
+} HostCommandOptions;
+
+extern void initializeHostCommandOptions (HostCommandOptions *options);
+
+extern int runHostCommand (
+  const char *const *command,
+  const HostCommandOptions *options
+);
+
+extern int executeHostCommand (const char *const *command);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HOSTCMD */
diff --git a/Headers/ihex.h b/Headers/ihex.h
new file mode 100644
index 0000000..a83b429
--- /dev/null
+++ b/Headers/ihex.h
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IHEX
+#define BRLTTY_INCLUDED_IHEX
+
+#include "ihex_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern size_t ihexRecordLength (size_t count);
+
+extern int ihexMakeRecord (
+  char *buffer, size_t size,
+  IhexType type, IhexAddress address,
+  const IhexByte *data, IhexCount count
+);
+
+extern int ihexMakeDataRecord (
+  char *buffer, size_t size,
+  IhexAddress address, const IhexByte *data, IhexCount count
+);
+
+extern int ihexMakeEndRecord (char *buffer, size_t size);
+
+extern int ihexProcessFile (
+  const char *path, IhexRecordHandler *handler, void *data
+);
+
+#define IHEX_FILES_SUBDIRECTORY "firmware"
+#define IHEX_FILE_EXTENSION ".ihex"
+
+extern char *ihexEnsureExtension (const char *path);
+extern char *ihexMakePath (const char *directory, const char *name);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IHEX */
diff --git a/Headers/ihex_types.h b/Headers/ihex_types.h
new file mode 100644
index 0000000..59ec0ba
--- /dev/null
+++ b/Headers/ihex_types.h
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IHEX_TYPES
+#define BRLTTY_INCLUDED_IHEX_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint8_t IhexType;
+#define IHEX_TYPE_DATA 0X00
+#define IHEX_TYPE_END  0X01
+
+typedef uint16_t IhexAddress;
+typedef uint8_t  IhexCount;
+typedef uint8_t  IhexByte;
+
+typedef struct {
+  IhexAddress address;
+  IhexType type;
+
+  IhexCount count;
+  IhexByte data[];
+} IhexParsedRecord;
+
+typedef int IhexRecordHandler (const IhexParsedRecord *record, void *data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IHEX_TYPES */
diff --git a/Headers/io_bluetooth.h b/Headers/io_bluetooth.h
new file mode 100644
index 0000000..3ed4016
--- /dev/null
+++ b/Headers/io_bluetooth.h
@@ -0,0 +1,76 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_BLUETOOTH
+#define BRLTTY_INCLUDED_IO_BLUETOOTH
+
+#include "async_types_io.h"
+#include "strfmth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct BluetoothConnectionStruct BluetoothConnection;
+
+extern void bthForgetDevices (void);
+
+extern const char *bthGetNameOfDevice (BluetoothConnection *connection, int timeout);
+extern const char *bthGetNameAtAddress (const char *address, int timeout);
+extern const char *const *bthGetDriverCodes (const char *address, int timeout);
+
+typedef struct {
+  const char *driver;
+  uint64_t address;
+  int timeout;
+  uint8_t channel;
+  unsigned char discover:1;
+} BluetoothConnectionRequest;
+
+extern void bthInitializeConnectionRequest (BluetoothConnectionRequest *request);
+extern int bthApplyParameters (BluetoothConnectionRequest *request, const char *identifier);
+
+extern int bthParseAddress (uint64_t *address, const char *string);
+extern int bthParseChannelNumber (uint8_t *channel, const char *string);
+extern STR_DECLARE_FORMATTER (bthFormatAddress, uint64_t address);
+
+extern BluetoothConnection *bthOpenConnection (const BluetoothConnectionRequest *request);
+extern void bthCloseConnection (BluetoothConnection *connection);
+extern const char *bthMakeConnectionIdentifier (BluetoothConnection *connection, char *buffer, size_t size);
+
+extern uint64_t bthGetAddress (BluetoothConnection *connection);
+extern uint8_t bthGetChannel (BluetoothConnection *connection);
+
+extern int bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data);
+extern int bthAwaitInput (BluetoothConnection *connection, int timeout);
+
+extern ssize_t bthReadData (
+  BluetoothConnection *connection, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t bthWriteData (BluetoothConnection *connection, const void *buffer, size_t size);
+
+#define BLUETOOTH_DEVICE_QUALIFIER "bluetooth"
+extern int isBluetoothDeviceIdentifier (const char **identifier);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_BLUETOOTH */
diff --git a/Headers/io_generic.h b/Headers/io_generic.h
new file mode 100644
index 0000000..6ba8339
--- /dev/null
+++ b/Headers/io_generic.h
@@ -0,0 +1,160 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_GENERIC
+#define BRLTTY_INCLUDED_IO_GENERIC
+
+#include "gio_types.h"
+#include "async_types_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const GioPublicProperties *gioGetPublicProperties (const char **identifier);
+
+extern void gioInitializeDescriptor (GioDescriptor *descriptor);
+extern void gioInitializeSerialParameters (SerialParameters *parameters);
+
+extern GioEndpoint *gioConnectResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+);
+
+extern const void *gioGetApplicationData (GioEndpoint *endpoint);
+extern int gioDisconnectResource (GioEndpoint *endpoint);
+
+extern const char *gioMakeResourceIdentifier (GioEndpoint *endpoint, char *buffer, size_t size);
+extern char *gioGetResourceIdentifier (GioEndpoint *endpoint);
+
+extern char *gioGetResourceName (GioEndpoint *endpoint);
+extern GioTypeIdentifier gioGetResourceType (GioEndpoint *endpoint);
+extern void *gioGetResourceObject (GioEndpoint *endpoint);
+
+extern ssize_t gioWriteData (GioEndpoint *endpoint, const void *data, size_t size);
+extern int gioAwaitInput (GioEndpoint *endpoint, int timeout);
+extern ssize_t gioReadData (GioEndpoint *endpoint, void *buffer, size_t size, int wait);
+extern int gioReadByte (GioEndpoint *endpoint, unsigned char *byte, int wait);
+extern int gioDiscardInput (GioEndpoint *endpoint);
+
+extern int gioMonitorInput (GioEndpoint *endpoint, AsyncMonitorCallback *callback, void *data);
+
+extern int gioReconfigureResource (
+  GioEndpoint *endpoint,
+  const SerialParameters *parameters
+);
+
+extern unsigned int gioGetBytesPerSecond (GioEndpoint *endpoint);
+extern unsigned int gioGetMillisecondsToTransfer (GioEndpoint *endpoint, size_t bytes);
+
+extern ssize_t gioTellResource (
+  GioEndpoint *endpoint,
+  uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  const void *data, uint16_t size
+);
+
+extern ssize_t gioAskResource (
+  GioEndpoint *endpoint,
+  uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  void *buffer, uint16_t size
+);
+
+extern int gioGetHidReportSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+);
+
+extern size_t gioGetHidInputSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+);
+
+extern size_t gioGetHidOutputSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+);
+
+extern size_t gioGetHidFeatureSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+);
+
+extern ssize_t gioGetHidReport (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size
+);
+
+extern ssize_t gioReadHidReport (
+  GioEndpoint *endpoint,
+  unsigned char *buffer, size_t size
+);
+
+extern ssize_t gioSetHidReport (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size
+);
+
+extern ssize_t gioWriteHidReport (
+  GioEndpoint *endpoint,
+  const unsigned char *data, size_t size
+);
+
+extern ssize_t gioGetHidFeature (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size
+);
+
+extern ssize_t gioReadHidFeature (
+  GioEndpoint *endpoint,
+  unsigned char *buffer, size_t size
+);
+
+extern ssize_t gioSetHidFeature (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size
+);
+
+extern ssize_t gioWriteHidFeature (
+  GioEndpoint *endpoint,
+  const unsigned char *data, size_t size
+);
+
+typedef struct {
+  void *const data;
+  int error;
+} GioHandleInputParameters;
+
+#define GIO_INPUT_HANDLER(name) int name (GioHandleInputParameters *parameters)
+typedef GIO_INPUT_HANDLER(GioInputHandler);
+typedef struct GioHandleInputObjectStruct GioHandleInputObject;
+
+extern GioHandleInputObject *gioNewHandleInputObject (
+  GioEndpoint *endpoint, int pollInterval,
+  GioInputHandler *handler, void *data
+);
+
+extern void gioDestroyHandleInputObject (GioHandleInputObject *hio);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_GENERIC */
diff --git a/Headers/io_hid.h b/Headers/io_hid.h
new file mode 100644
index 0000000..f6d5a21
--- /dev/null
+++ b/Headers/io_hid.h
@@ -0,0 +1,93 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_HID
+#define BRLTTY_INCLUDED_IO_HID
+
+#include "hid_types.h"
+#include "async_types_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct HidDeviceStruct HidDevice;
+
+extern void hidInitializeUSBFilter (HidUSBFilter *filter);
+extern HidDevice *hidOpenUSBDevice (const HidUSBFilter *filter);
+
+extern void hidInitializeBluetoothFilter (HidBluetoothFilter *filter);
+extern HidDevice *hidOpenBluetoothDevice (const HidBluetoothFilter *filter);
+
+extern void hidInitializeFilter (HidFilter *filter);
+
+extern int hidSetFilterIdentifiers (
+  HidFilter *filter, const char *vendor, const char *product
+);
+
+extern int hidOpenDeviceWithFilter (
+  HidDevice **device, const HidFilter *filter
+);
+
+extern int hidOpenDeviceWithParameters (
+  HidDevice **device, const char *string
+);
+
+extern void hidCloseDevice (HidDevice *device);
+
+extern const HidItemsDescriptor *hidGetItems (HidDevice *device);
+
+extern int hidGetReportSize (
+  HidDevice *device,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+);
+
+extern ssize_t hidGetReport (HidDevice *device, unsigned char *buffer, size_t size);
+extern ssize_t hidSetReport (HidDevice *device, const unsigned char *report, size_t size);
+
+extern ssize_t hidGetFeature (HidDevice *device, unsigned char *buffer, size_t size);
+extern ssize_t hidSetFeature (HidDevice *device, const unsigned char *feature, size_t size);
+
+extern int hidWriteData (HidDevice *device, const unsigned char *data, size_t size);
+extern int hidMonitorInput (HidDevice *device, AsyncMonitorCallback *callback, void *data);
+extern int hidAwaitInput (HidDevice *device, int timeout);
+
+extern ssize_t hidReadData (
+  HidDevice *device, unsigned char *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern int hidGetDeviceIdentifiers (HidDevice *device, HidDeviceIdentifier *vendor, HidDeviceIdentifier *product);
+extern const char *hidGetDeviceAddress (HidDevice *device);
+extern const char *hidGetDeviceName (HidDevice *device);
+extern const char *hidGetHostPath (HidDevice *device);
+extern const char *hidGetHostDevice (HidDevice *device);
+
+extern const char *hidMakeDeviceIdentifier (
+  HidDevice *device, char *buffer, size_t size
+);
+
+#define HID_DEVICE_QUALIFIER "hid"
+extern int isHidDeviceIdentifier (const char **identifier);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_HID */
diff --git a/Headers/io_misc.h b/Headers/io_misc.h
new file mode 100644
index 0000000..33b4582
--- /dev/null
+++ b/Headers/io_misc.h
@@ -0,0 +1,73 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_MISC
+#define BRLTTY_INCLUDED_IO_MISC
+
+#include "get_sockets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void closeFile (FileDescriptor *fileDescriptor);
+extern int awaitFileInput (FileDescriptor fileDescriptor, int timeout);
+extern int awaitFileOutput (FileDescriptor fileDescriptor, int timeout);
+extern int awaitFileAlert (FileDescriptor fileDescriptor, int timeout);
+
+extern ssize_t readFile (
+  FileDescriptor fileDescriptor, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t writeFile (FileDescriptor fileDescriptor, const void *buffer, size_t size);
+
+#ifdef GOT_SOCKETS
+extern void closeSocket (SocketDescriptor *socketDescriptor);
+extern int awaitSocketInput (SocketDescriptor socketDescriptor, int timeout);
+extern int awaitSocketOutput (SocketDescriptor socketDescriptor, int timeout);
+extern int awaitSocketAlert (SocketDescriptor socketDescriptor, int timeout);
+
+extern ssize_t readSocket (
+  SocketDescriptor socketDescriptor, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t writeSocket (SocketDescriptor socketDescriptor, const void *buffer, size_t size);
+
+extern int connectSocket (
+  SocketDescriptor socketDescriptor,
+  const struct sockaddr *address,
+  size_t addressLength,
+  int timeout
+);
+
+extern int setSocketLingerTime (SocketDescriptor socketDescriptor, int seconds);
+extern int setSocketNoLinger (SocketDescriptor socketDescriptor);
+#endif /* GOT_SOCKETS */
+
+extern int changeOpenFlags (FileDescriptor fileDescriptor, int flagsToClear, int flagsToSet);
+extern int setOpenFlags (FileDescriptor fileDescriptor, int state, int flags);
+extern int setBlockingIo (FileDescriptor fileDescriptor, int state);
+extern int setCloseOnExec (FileDescriptor fileDescriptor, int state);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_MISC */
diff --git a/Headers/io_serial.h b/Headers/io_serial.h
new file mode 100644
index 0000000..b58d7a4
--- /dev/null
+++ b/Headers/io_serial.h
@@ -0,0 +1,100 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_SERIAL
+#define BRLTTY_INCLUDED_IO_SERIAL
+
+#include <stdio.h>
+
+#include "serial_types.h"
+#include "async_types_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct SerialDeviceStruct SerialDevice;
+
+#define SERIAL_DEVICE_QUALIFIER "serial"
+extern int isSerialDeviceIdentifier (const char **identifier);
+
+extern int serialValidateBaud (unsigned int *baud, const char *description, const char *word, const unsigned int *choices);
+
+extern SerialDevice *serialOpenDevice (const char *identifier);
+extern void serialCloseDevice (SerialDevice *serial);
+extern int serialRestartDevice (SerialDevice *serial, unsigned int baud);
+
+extern const char *serialMakeDeviceIdentifier (SerialDevice *serial, char *buffer, size_t size);
+extern const char *serialGetDevicePath (SerialDevice *serial);
+extern FILE *serialGetStream (SerialDevice *serial);
+
+extern int serialDiscardInput (SerialDevice *serial);
+extern int serialDiscardOutput (SerialDevice *serial);
+extern int serialFlushOutput (SerialDevice *serial);
+
+extern int serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data);
+extern int serialAwaitInput (SerialDevice *serial, int timeout);
+extern int serialAwaitOutput (SerialDevice *serial);
+
+extern ssize_t serialReadData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern int serialReadChunk (
+  SerialDevice *serial,
+  void *buffer, size_t *offset, size_t count,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t serialWriteData (
+  SerialDevice *serial,
+  const void *data, size_t size
+);
+
+extern int serialParseBaud (unsigned int *baud, const char *string);
+extern int serialParseDataBits (unsigned int *bits, const char *string);
+extern int serialParseStopBits (unsigned int *bits, const char *string);
+extern int serialParseParity (SerialParity *parity, const char *string);
+extern int serialParseFlowControl (SerialFlowControl *flow, const char *string);
+
+extern int serialSetParameters (SerialDevice *serial, const SerialParameters *parameters);
+extern int serialSetBaud (SerialDevice *serial, unsigned int baud);
+extern int serialSetDataBits (SerialDevice *serial, unsigned int bits);
+extern int serialSetStopBits (SerialDevice *serial, SerialStopBits bits);
+extern int serialSetParity (SerialDevice *serial, SerialParity parity);
+extern int serialSetFlowControl (SerialDevice *serial, SerialFlowControl flow);
+
+extern unsigned int serialGetCharacterSize (const SerialParameters *parameters);
+extern unsigned int serialGetCharacterBits (SerialDevice *serial);
+
+extern int serialSetLineRTS (SerialDevice *serial, int up);
+extern int serialSetLineDTR (SerialDevice *serial, int up);
+
+extern int serialTestLineCTS (SerialDevice *serial);
+extern int serialTestLineDSR (SerialDevice *serial);
+
+extern int serialWaitLineCTS (SerialDevice *serial, int up, int flank);
+extern int serialWaitLineDSR (SerialDevice *serial, int up, int flank);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_SERIAL */
diff --git a/Headers/io_usb.h b/Headers/io_usb.h
new file mode 100644
index 0000000..af0534d
--- /dev/null
+++ b/Headers/io_usb.h
@@ -0,0 +1,250 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_USB
+#define BRLTTY_INCLUDED_IO_USB
+
+#include "prologue.h"
+#include "usb_types.h"
+#include "async_types_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern UsbDevice *usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data);
+extern void usbForgetDevices (void);
+
+extern void usbCloseDevice (UsbDevice *device);
+extern int usbDisableAutosuspend (UsbDevice *device);
+
+extern const UsbDeviceDescriptor *usbDeviceDescriptor (UsbDevice *device);
+#define USB_IS_PRODUCT(descriptor,vendor,product) ((getLittleEndian16((descriptor)->idVendor) == (vendor)) && (getLittleEndian16((descriptor)->idProduct) == (product)))
+
+extern int usbNextDescriptor (
+  UsbDevice *device,
+  const UsbDescriptor **descriptor
+);
+extern const UsbConfigurationDescriptor *usbConfigurationDescriptor (
+  UsbDevice *device
+);
+extern const UsbInterfaceDescriptor *usbInterfaceDescriptor (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+);
+extern unsigned int usbAlternativeCount (
+  UsbDevice *device,
+  unsigned char interface
+);
+extern const UsbEndpointDescriptor *usbEndpointDescriptor (
+  UsbDevice *device,
+  unsigned char endpointAddress
+);
+
+extern int usbConfigureDevice (
+  UsbDevice *device,
+  unsigned char configuration
+);
+extern int usbGetConfiguration (
+  UsbDevice *device,
+  unsigned char *configuration
+);
+
+extern int usbOpenInterface (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+);
+extern void usbCloseInterface (
+  UsbDevice *device
+);
+
+extern int usbResetDevice (UsbDevice *device);
+extern int usbClearHalt (UsbDevice *device, unsigned char endpointAddress);
+
+extern ssize_t usbControlRead (
+  UsbDevice *device,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+);
+extern ssize_t usbControlWrite (
+  UsbDevice *device,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  const void *buffer,
+  uint16_t length,
+  int timeout
+);
+
+extern ssize_t usbGetDescriptor (
+  UsbDevice *device,
+  unsigned char type,
+  unsigned char number,
+  unsigned int index,
+  UsbDescriptor *descriptor,
+  int timeout
+);
+extern int usbGetDeviceDescriptor (
+  UsbDevice *device,
+  UsbDeviceDescriptor *descriptor
+);
+extern int usbGetLanguage (
+  UsbDevice *device,
+  uint16_t *language,
+  int timeout
+);
+extern char *usbGetString (
+  UsbDevice *device,
+  unsigned char number,
+  int timeout
+);
+extern char *usbDecodeString (const UsbStringDescriptor *descriptor);
+extern char *usbGetManufacturer (UsbDevice *device, int timeout);
+extern char *usbGetProduct (UsbDevice *device, int timeout);
+extern char *usbGetSerialNumber (UsbDevice *device, int timeout);
+
+extern void usbLogString (
+  UsbDevice *device,
+  unsigned char number,
+  const char *description
+);
+
+typedef int (UsbStringVerifier) (const char *reference, const char *value);
+extern UsbStringVerifier usbStringEquals;
+extern UsbStringVerifier usbStringMatches;
+extern int usbVerifyString (
+  UsbDevice *device,
+  UsbStringVerifier verify,
+  unsigned char index,
+  const char *value
+);
+extern int usbVerifyManufacturerName (UsbDevice *device, const char *eRegExp);
+extern int usbVerifyProductDescription (UsbDevice *device, const char *eRegExp);
+extern int usbVerifySerialNumber (UsbDevice *device, const char *string);
+
+extern int usbParseVendorIdentifier (uint16_t *identifier, const char *string);
+extern int usbVerifyVendorIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier);
+
+extern int usbParseProductIdentifier (uint16_t *identifier, const char *string);
+extern int usbVerifyProductIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier);
+
+extern void usbBeginInput (
+  UsbDevice *device,
+  unsigned char endpointNumber
+);
+
+extern int usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern ssize_t usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+);
+extern ssize_t usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+);
+
+typedef struct {
+  void *context;
+  void *buffer;
+  size_t size;
+  ssize_t count;
+  int error;
+} UsbResponse;
+extern void *usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+);
+
+extern int usbCancelRequest (UsbDevice *device, void *request);
+
+extern void *usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+);
+
+extern int usbAwaitInput (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  int timeout
+);
+extern ssize_t usbReadData (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int initialTimeout,
+  int subsequentTimeout
+);
+
+extern ssize_t usbWriteData (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *data,
+  size_t length,
+  int timeout
+);
+
+extern int usbAddInputFilter (UsbDevice *device, UsbInputFilter *filter);
+
+extern const UsbSerialOperations *usbGetSerialOperations (UsbDevice *device);
+extern int usbSetSerialParameters (UsbDevice *device, const SerialParameters *parameters);
+
+typedef struct {
+  const UsbChannelDefinition *definition;
+  UsbDevice *device;
+} UsbChannel;
+
+extern UsbChannel *usbOpenChannel (const UsbChannelDefinition *definitions, const char *identifier);
+extern void usbCloseChannel (UsbChannel *channel);
+extern const char *usbMakeChannelIdentifier (UsbChannel *channel, char *buffer, size_t size);
+
+extern const char *const *usbGetDriverCodes (uint16_t vendor, uint16_t product);
+
+#define USB_DEVICE_QUALIFIER "usb"
+extern int isUsbDeviceIdentifier (const char **identifier);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_USB */
diff --git a/Headers/kbd_keycodes.h b/Headers/kbd_keycodes.h
new file mode 100644
index 0000000..0185a82
--- /dev/null
+++ b/Headers/kbd_keycodes.h
@@ -0,0 +1,737 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KBD_KEYCODES
+#define BRLTTY_INCLUDED_KBD_KEYCODES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  XT_KEY_00_Escape            = 0X01,
+  XT_KEY_00_1                 = 0X02,
+  XT_KEY_00_2                 = 0X03,
+  XT_KEY_00_3                 = 0X04,
+  XT_KEY_00_4                 = 0X05,
+  XT_KEY_00_5                 = 0X06,
+  XT_KEY_00_6                 = 0X07,
+  XT_KEY_00_7                 = 0X08,
+  XT_KEY_00_8                 = 0X09,
+  XT_KEY_00_9                 = 0X0A,
+  XT_KEY_00_0                 = 0X0B,
+  XT_KEY_00_Minus             = 0X0C,
+  XT_KEY_00_Equal             = 0X0D,
+  XT_KEY_00_Backspace         = 0X0E,
+  XT_KEY_00_Tab               = 0X0F,
+  XT_KEY_00_Q                 = 0X10,
+  XT_KEY_00_W                 = 0X11,
+  XT_KEY_00_E                 = 0X12,
+  XT_KEY_00_R                 = 0X13,
+  XT_KEY_00_T                 = 0X14,
+  XT_KEY_00_Y                 = 0X15,
+  XT_KEY_00_U                 = 0X16,
+  XT_KEY_00_I                 = 0X17,
+  XT_KEY_00_O                 = 0X18,
+  XT_KEY_00_P                 = 0X19,
+  XT_KEY_00_LeftBracket       = 0X1A,
+  XT_KEY_00_RightBracket      = 0X1B,
+  XT_KEY_00_Enter             = 0X1C,
+  XT_KEY_00_LeftControl       = 0X1D,
+  XT_KEY_00_A                 = 0X1E,
+  XT_KEY_00_S                 = 0X1F,
+  XT_KEY_00_D                 = 0X20,
+  XT_KEY_00_F                 = 0X21,
+  XT_KEY_00_G                 = 0X22,
+  XT_KEY_00_H                 = 0X23,
+  XT_KEY_00_J                 = 0X24,
+  XT_KEY_00_K                 = 0X25,
+  XT_KEY_00_L                 = 0X26,
+  XT_KEY_00_Semicolon         = 0X27,
+  XT_KEY_00_Apostrophe        = 0X28,
+  XT_KEY_00_Grave             = 0X29,
+  XT_KEY_00_LeftShift         = 0X2A,
+  XT_KEY_00_Backslash         = 0X2B,
+  XT_KEY_00_Europe1           = 0X2B,
+  XT_KEY_00_Z                 = 0X2C,
+  XT_KEY_00_X                 = 0X2D,
+  XT_KEY_00_C                 = 0X2E,
+  XT_KEY_00_V                 = 0X2F,
+  XT_KEY_00_B                 = 0X30,
+  XT_KEY_00_N                 = 0X31,
+  XT_KEY_00_M                 = 0X32,
+  XT_KEY_00_Comma             = 0X33,
+  XT_KEY_00_Period            = 0X34,
+  XT_KEY_00_Slash             = 0X35,
+  XT_KEY_00_RightShift        = 0X36,
+  XT_KEY_00_KPAsterisk        = 0X37,
+  XT_KEY_00_LeftAlt           = 0X38,
+  XT_KEY_00_Space             = 0X39,
+  XT_KEY_00_CapsLock          = 0X3A,
+  XT_KEY_00_F1                = 0X3B,
+  XT_KEY_00_F2                = 0X3C,
+  XT_KEY_00_F3                = 0X3D,
+  XT_KEY_00_F4                = 0X3E,
+  XT_KEY_00_F5                = 0X3F,
+  XT_KEY_00_F6                = 0X40,
+  XT_KEY_00_F7                = 0X41,
+  XT_KEY_00_F8                = 0X42,
+  XT_KEY_00_F9                = 0X43,
+  XT_KEY_00_F10               = 0X44,
+  XT_KEY_00_NumLock           = 0X45,
+  XT_KEY_00_ScrollLock        = 0X46,
+  XT_KEY_00_KP7               = 0X47,
+  XT_KEY_00_KP8               = 0X48,
+  XT_KEY_00_KP9               = 0X49,
+  XT_KEY_00_KPMinus           = 0X4A,
+  XT_KEY_00_KP4               = 0X4B,
+  XT_KEY_00_KP5               = 0X4C,
+  XT_KEY_00_KP6               = 0X4D,
+  XT_KEY_00_KPPlus            = 0X4E,
+  XT_KEY_00_KP1               = 0X4F,
+  XT_KEY_00_KP2               = 0X50,
+  XT_KEY_00_KP3               = 0X51,
+  XT_KEY_00_KP0               = 0X52,
+  XT_KEY_00_KPPeriod          = 0X53,
+  XT_KEY_00_SystemRequest     = 0X54,
+  XT_KEY_00_Europe2           = 0X56,
+  XT_KEY_00_F11               = 0X57,
+  XT_KEY_00_F12               = 0X58,
+  XT_KEY_00_KPEqual           = 0X59,
+  XT_KEY_00_International6    = 0X5C,
+  XT_KEY_00_F13               = 0X64,
+  XT_KEY_00_F14               = 0X65,
+  XT_KEY_00_F15               = 0X66,
+  XT_KEY_00_F16               = 0X67,
+  XT_KEY_00_F17               = 0X68,
+  XT_KEY_00_F18               = 0X69,
+  XT_KEY_00_F19               = 0X6A,
+  XT_KEY_00_F20               = 0X6B,
+  XT_KEY_00_F21               = 0X6C,
+  XT_KEY_00_F22               = 0X6D,
+  XT_KEY_00_F23               = 0X6E,
+  XT_KEY_00_International2    = 0X70,
+  XT_KEY_00_CrSel             = 0X72,
+  XT_KEY_00_International1    = 0X73,
+  XT_KEY_00_ExSel             = 0X74,
+  XT_KEY_00_EnlHelp           = 0X75,
+  XT_KEY_00_F24               = 0X76,
+  XT_KEY_00_Language5         = 0X76,
+  XT_KEY_00_Language4         = 0X77,
+  XT_KEY_00_Language3         = 0X78,
+  XT_KEY_00_International4    = 0X79,
+  XT_KEY_00_International5    = 0X7B,
+  XT_KEY_00_International3    = 0X7D,
+  XT_KEY_00_KPComma           = 0X7E,
+  XT_KEY_00_Find              = 0X82,
+  XT_KEY_00_Language2         = 0XF1,
+  XT_KEY_00_Language1         = 0XF2,
+} XtKey00;
+
+typedef enum {
+  XT_KEY_E0_Redo              = 0X07,
+  XT_KEY_E0_Undo              = 0X08,
+  XT_KEY_E0_Paste             = 0X0A,
+  XT_KEY_E0_MediaPrevious     = 0X10,
+  XT_KEY_E0_Cut               = 0X17,
+  XT_KEY_E0_Copy              = 0X18,
+  XT_KEY_E0_MediaNext         = 0X19,
+  XT_KEY_E0_KPEnter           = 0X1C,
+  XT_KEY_E0_RightControl      = 0X1D,
+  XT_KEY_E0_Mail              = 0X1E,
+  XT_KEY_E0_Mute              = 0X20,
+  XT_KEY_E0_Calculator        = 0X21,
+  XT_KEY_E0_MediaPlayPause    = 0X22,
+  XT_KEY_E0_MediaStop         = 0X24,
+  XT_KEY_E0_MediaEject        = 0X2C,
+  XT_KEY_E0_VolumeDown        = 0X2E,
+  XT_KEY_E0_VolumeUp          = 0X30,
+  XT_KEY_E0_WebHome           = 0X32,
+  XT_KEY_E0_KPSlash           = 0X35,
+  XT_KEY_E0_PrintScreen       = 0X37,
+  XT_KEY_E0_RightAlt          = 0X38,
+  XT_KEY_E0_Help              = 0X3B,
+  XT_KEY_E0_MediaAudio        = 0X3C,
+  XT_KEY_E0_Home              = 0X47,
+  XT_KEY_E0_ArrowUp           = 0X48,
+  XT_KEY_E0_PageUp            = 0X49,
+  XT_KEY_E0_ArrowLeft         = 0X4B,
+  XT_KEY_E0_ArrowRight        = 0X4D,
+  XT_KEY_E0_End               = 0X4F,
+  XT_KEY_E0_ArrowDown         = 0X50,
+  XT_KEY_E0_PageDown          = 0X51,
+  XT_KEY_E0_Insert            = 0X52,
+  XT_KEY_E0_Delete            = 0X53,
+  XT_KEY_E0_LeftGUI           = 0X5B,
+  XT_KEY_E0_RightGUI          = 0X5C,
+  XT_KEY_E0_Context           = 0X5D,
+  XT_KEY_E0_Power             = 0X5E,
+  XT_KEY_E0_Sleep             = 0X5F,
+  XT_KEY_E0_Wake              = 0X63,
+  XT_KEY_E0_MediaPictures     = 0X64,
+  XT_KEY_E0_WebSearch         = 0X65,
+  XT_KEY_E0_WebBookmarks      = 0X66,
+  XT_KEY_E0_WebRefresh        = 0X67,
+  XT_KEY_E0_WebStop           = 0X68,
+  XT_KEY_E0_WebForward        = 0X69,
+  XT_KEY_E0_WebBack           = 0X6A,
+  XT_KEY_E0_MyComputer        = 0X6B,
+  XT_KEY_E0_Mail_X1           = 0X6C,
+  XT_KEY_E0_MediaVideo        = 0X6D,
+} XtKeyE0;
+
+typedef enum {
+  XT_KEY_E1_Pause = 0X1D,
+} XtKeyE1;
+
+#define XT_BIT_RELEASE 0X80
+#define XT_MOD_00      0X00
+#define XT_MOD_E0      0XE0
+#define XT_MOD_E1      0XE1
+#define XT_KEY(group,name) ((XT_MOD_##group << 8) | XT_KEY_##group##_##name)
+
+typedef enum {
+  AT_KEY_00_F9                = 0X01,
+  AT_KEY_00_F7_X1             = 0X02,
+  AT_KEY_00_F5                = 0X03,
+  AT_KEY_00_F3                = 0X04,
+  AT_KEY_00_F1                = 0X05,
+  AT_KEY_00_F2                = 0X06,
+  AT_KEY_00_F12               = 0X07,
+  AT_KEY_00_F13               = 0X08,
+  AT_KEY_00_F10               = 0X09,
+  AT_KEY_00_F8                = 0X0A,
+  AT_KEY_00_F6                = 0X0B,
+  AT_KEY_00_F4                = 0X0C,
+  AT_KEY_00_Tab               = 0X0D,
+  AT_KEY_00_Grave             = 0X0E,
+  AT_KEY_00_KPEqual           = 0X0F,
+  AT_KEY_00_F14               = 0X10,
+  AT_KEY_00_LeftAlt           = 0X11,
+  AT_KEY_00_LeftShift         = 0X12,
+  AT_KEY_00_International2    = 0X13,
+  AT_KEY_00_LeftControl       = 0X14,
+  AT_KEY_00_Q                 = 0X15,
+  AT_KEY_00_1                 = 0X16,
+  AT_KEY_00_F15               = 0X18,
+  AT_KEY_00_Z                 = 0X1A,
+  AT_KEY_00_S                 = 0X1B,
+  AT_KEY_00_A                 = 0X1C,
+  AT_KEY_00_W                 = 0X1D,
+  AT_KEY_00_2                 = 0X1E,
+  AT_KEY_00_F16               = 0X20,
+  AT_KEY_00_C                 = 0X21,
+  AT_KEY_00_X                 = 0X22,
+  AT_KEY_00_D                 = 0X23,
+  AT_KEY_00_E                 = 0X24,
+  AT_KEY_00_4                 = 0X25,
+  AT_KEY_00_3                 = 0X26,
+  AT_KEY_00_International6    = 0X27,
+  AT_KEY_00_F17               = 0X28,
+  AT_KEY_00_Space             = 0X29,
+  AT_KEY_00_V                 = 0X2A,
+  AT_KEY_00_F                 = 0X2B,
+  AT_KEY_00_T                 = 0X2C,
+  AT_KEY_00_R                 = 0X2D,
+  AT_KEY_00_5                 = 0X2E,
+  AT_KEY_00_F18               = 0X30,
+  AT_KEY_00_N                 = 0X31,
+  AT_KEY_00_B                 = 0X32,
+  AT_KEY_00_H                 = 0X33,
+  AT_KEY_00_G                 = 0X34,
+  AT_KEY_00_Y                 = 0X35,
+  AT_KEY_00_6                 = 0X36,
+  AT_KEY_00_F19               = 0X38,
+  AT_KEY_00_CrSel             = 0X39,
+  AT_KEY_00_M                 = 0X3A,
+  AT_KEY_00_J                 = 0X3B,
+  AT_KEY_00_U                 = 0X3C,
+  AT_KEY_00_7                 = 0X3D,
+  AT_KEY_00_8                 = 0X3E,
+  AT_KEY_00_F20               = 0X40,
+  AT_KEY_00_Comma             = 0X41,
+  AT_KEY_00_K                 = 0X42,
+  AT_KEY_00_I                 = 0X43,
+  AT_KEY_00_O                 = 0X44,
+  AT_KEY_00_0                 = 0X45,
+  AT_KEY_00_9                 = 0X46,
+  AT_KEY_00_F21               = 0X48,
+  AT_KEY_00_Period            = 0X49,
+  AT_KEY_00_Slash             = 0X4A,
+  AT_KEY_00_L                 = 0X4B,
+  AT_KEY_00_Semicolon         = 0X4C,
+  AT_KEY_00_P                 = 0X4D,
+  AT_KEY_00_Minus             = 0X4E,
+  AT_KEY_00_F22               = 0X50,
+  AT_KEY_00_International1    = 0X51,
+  AT_KEY_00_Apostrophe        = 0X52,
+  AT_KEY_00_ExSel             = 0X53,
+  AT_KEY_00_LeftBracket       = 0X54,
+  AT_KEY_00_Equal             = 0X55,
+  AT_KEY_00_F23               = 0X57,
+  AT_KEY_00_CapsLock          = 0X58,
+  AT_KEY_00_RightShift        = 0X59,
+  AT_KEY_00_Enter             = 0X5A,
+  AT_KEY_00_RightBracket      = 0X5B,
+  AT_KEY_00_EnlHelp           = 0X5C,
+  AT_KEY_00_Backslash         = 0X5D,
+  AT_KEY_00_Europe1           = 0X5D,
+  AT_KEY_00_F24               = 0X5F,
+  AT_KEY_00_Language5         = 0X5F,
+  AT_KEY_00_Europe2           = 0X61,
+  AT_KEY_00_Language4         = 0X62,
+  AT_KEY_00_Language3         = 0X63,
+  AT_KEY_00_International4    = 0X64,
+  AT_KEY_00_Backspace         = 0X66,
+  AT_KEY_00_International5    = 0X67,
+  AT_KEY_00_KP1               = 0X69,
+  AT_KEY_00_International3    = 0X6A,
+  AT_KEY_00_KP4               = 0X6B,
+  AT_KEY_00_KP7               = 0X6C,
+  AT_KEY_00_KPComma           = 0X6D,
+  AT_KEY_00_KP0               = 0X70,
+  AT_KEY_00_KPPeriod          = 0X71,
+  AT_KEY_00_KP2               = 0X72,
+  AT_KEY_00_KP5               = 0X73,
+  AT_KEY_00_KP6               = 0X74,
+  AT_KEY_00_KP8               = 0X75,
+  AT_KEY_00_Escape            = 0X76,
+  AT_KEY_00_NumLock           = 0X77,
+  AT_KEY_00_F11               = 0X78,
+  AT_KEY_00_KPPlus            = 0X79,
+  AT_KEY_00_KP3               = 0X7A,
+  AT_KEY_00_KPMinus           = 0X7B,
+  AT_KEY_00_KPAsterisk        = 0X7C,
+  AT_KEY_00_KP9               = 0X7D,
+  AT_KEY_00_ScrollLock        = 0X7E,
+  AT_KEY_00_F7                = 0X83,
+  AT_KEY_00_SystemRequest     = 0X84,
+  AT_KEY_00_Language2         = 0XF1,
+  AT_KEY_00_Language1         = 0XF2,
+} AtKey00;
+
+typedef enum {
+  AT_KEY_E0_Help              = 0X05,
+  AT_KEY_E0_MediaAudio        = 0X06,
+  AT_KEY_E0_MediaPictures     = 0X08,
+  AT_KEY_E0_WebSearch         = 0X10,
+  AT_KEY_E0_RightAlt          = 0X11,
+  AT_KEY_E0_RightControl      = 0X14,
+  AT_KEY_E0_MediaPrevious     = 0X15,
+  AT_KEY_E0_WebBookmarks      = 0X18,
+  AT_KEY_E0_MediaEject        = 0X1A,
+  AT_KEY_E0_Mail              = 0X1C,
+  AT_KEY_E0_LeftGUI           = 0X1F,
+  AT_KEY_E0_WebRefresh        = 0X20,
+  AT_KEY_E0_VolumeDown        = 0X21,
+  AT_KEY_E0_Mute              = 0X23,
+  AT_KEY_E0_RightGUI          = 0X27,
+  AT_KEY_E0_WebStop           = 0X28,
+  AT_KEY_E0_Calculator        = 0X2B,
+  AT_KEY_E0_Context           = 0X2F,
+  AT_KEY_E0_WebForward        = 0X30,
+  AT_KEY_E0_VolumeUp          = 0X32,
+  AT_KEY_E0_MediaPlayPause    = 0X34,
+  AT_KEY_E0_Redo              = 0X36,
+  AT_KEY_E0_Power             = 0X37,
+  AT_KEY_E0_WebBack           = 0X38,
+  AT_KEY_E0_WebHome           = 0X3A,
+  AT_KEY_E0_MediaStop         = 0X3B,
+  AT_KEY_E0_Undo              = 0X3D,
+  AT_KEY_E0_Sleep             = 0X3F,
+  AT_KEY_E0_MyComputer        = 0X40,
+  AT_KEY_E0_Cut               = 0X43,
+  AT_KEY_E0_Copy              = 0X44,
+  AT_KEY_E0_Paste             = 0X46,
+  AT_KEY_E0_Mail_X1           = 0X48,
+  AT_KEY_E0_KPSlash           = 0X4A,
+  AT_KEY_E0_MediaNext         = 0X4D,
+  AT_KEY_E0_MediaVideo        = 0X50,
+  AT_KEY_E0_KPEnter           = 0X5A,
+  AT_KEY_E0_Wake              = 0X5E,
+  AT_KEY_E0_End               = 0X69,
+  AT_KEY_E0_ArrowLeft         = 0X6B,
+  AT_KEY_E0_Home              = 0X6C,
+  AT_KEY_E0_Insert            = 0X70,
+  AT_KEY_E0_Delete            = 0X71,
+  AT_KEY_E0_ArrowDown         = 0X72,
+  AT_KEY_E0_ArrowRight        = 0X74,
+  AT_KEY_E0_ArrowUp           = 0X75,
+  AT_KEY_E0_PageDown          = 0X7A,
+  AT_KEY_E0_PrintScreen       = 0X7C,
+  AT_KEY_E0_PageUp            = 0X7D,
+} AtKeyE0;
+
+typedef enum {
+  AT_KEY_E1_Pause = 0X14,
+} AtKeyE1;
+
+#define AT_MOD_RELEASE 0XF0
+#define AT_MOD_00      0X00
+#define AT_MOD_E0      0XE0
+#define AT_MOD_E1      0XE1
+#define AT_KEY(group,name) ((AT_MOD_##group << 8) | AT_KEY_##group##_##name)
+
+typedef enum {
+  PS2_KEY_EnlHelp        = 0X01,
+  PS2_KEY_ExSel          = 0X03,
+  PS2_KEY_CrSel          = 0X04,
+  PS2_KEY_F1             = 0X07,
+  PS2_KEY_Escape         = 0X08,
+  PS2_KEY_Tab            = 0X0D,
+  PS2_KEY_Grave          = 0X0E,
+  PS2_KEY_F2             = 0X0F,
+  PS2_KEY_LeftControl    = 0X11,
+  PS2_KEY_LeftShift      = 0X12,
+  PS2_KEY_Europe2        = 0X13,
+  PS2_KEY_CapsLock       = 0X14,
+  PS2_KEY_Q              = 0X15,
+  PS2_KEY_1              = 0X16,
+  PS2_KEY_F3             = 0X17,
+  PS2_KEY_LeftAlt        = 0X19,
+  PS2_KEY_Z              = 0X1A,
+  PS2_KEY_S              = 0X1B,
+  PS2_KEY_A              = 0X1C,
+  PS2_KEY_W              = 0X1D,
+  PS2_KEY_2              = 0X1E,
+  PS2_KEY_F4             = 0X1F,
+  PS2_KEY_C              = 0X21,
+  PS2_KEY_X              = 0X22,
+  PS2_KEY_D              = 0X23,
+  PS2_KEY_E              = 0X24,
+  PS2_KEY_4              = 0X25,
+  PS2_KEY_3              = 0X26,
+  PS2_KEY_F5             = 0X27,
+  PS2_KEY_Space          = 0X29,
+  PS2_KEY_V              = 0X2A,
+  PS2_KEY_F              = 0X2B,
+  PS2_KEY_T              = 0X2C,
+  PS2_KEY_R              = 0X2D,
+  PS2_KEY_5              = 0X2E,
+  PS2_KEY_F6             = 0X2F,
+  PS2_KEY_N              = 0X31,
+  PS2_KEY_B              = 0X32,
+  PS2_KEY_H              = 0X33,
+  PS2_KEY_G              = 0X34,
+  PS2_KEY_Y              = 0X35,
+  PS2_KEY_6              = 0X36,
+  PS2_KEY_F7             = 0X37,
+  PS2_KEY_RightAlt       = 0X39,
+  PS2_KEY_M              = 0X3A,
+  PS2_KEY_J              = 0X3B,
+  PS2_KEY_U              = 0X3C,
+  PS2_KEY_7              = 0X3D,
+  PS2_KEY_8              = 0X3E,
+  PS2_KEY_F8             = 0X3F,
+  PS2_KEY_Comma          = 0X41,
+  PS2_KEY_K              = 0X42,
+  PS2_KEY_I              = 0X43,
+  PS2_KEY_O              = 0X44,
+  PS2_KEY_0              = 0X45,
+  PS2_KEY_9              = 0X46,
+  PS2_KEY_F9             = 0X47,
+  PS2_KEY_Period         = 0X49,
+  PS2_KEY_Slash          = 0X4A,
+  PS2_KEY_L              = 0X4B,
+  PS2_KEY_Semicolon      = 0X4C,
+  PS2_KEY_P              = 0X4D,
+  PS2_KEY_Minus          = 0X4E,
+  PS2_KEY_F10            = 0X4F,
+  PS2_KEY_International1 = 0X51,
+  PS2_KEY_Apostrophe     = 0X52,
+  PS2_KEY_Europe1        = 0X53,
+  PS2_KEY_LeftBracket    = 0X54,
+  PS2_KEY_Equal          = 0X55,
+  PS2_KEY_F11            = 0X56,
+  PS2_KEY_PrintScreen    = 0X57,
+  PS2_KEY_RightControl   = 0X58,
+  PS2_KEY_RightShift     = 0X59,
+  PS2_KEY_Enter          = 0X5A,
+  PS2_KEY_RightBracket   = 0X5B,
+  PS2_KEY_Backslash      = 0X5C,
+  PS2_KEY_International3 = 0X5D,
+  PS2_KEY_F12            = 0X5E,
+  PS2_KEY_ScrollLock     = 0X5F,
+  PS2_KEY_ArrowDown      = 0X60,
+  PS2_KEY_ArrowLeft      = 0X61,
+  PS2_KEY_Pause          = 0X62,
+  PS2_KEY_ArrowUp        = 0X63,
+  PS2_KEY_Delete         = 0X64,
+  PS2_KEY_End            = 0X65,
+  PS2_KEY_Backspace      = 0X66,
+  PS2_KEY_Insert         = 0X67,
+  PS2_KEY_KP1            = 0X69,
+  PS2_KEY_ArrowRight     = 0X6A,
+  PS2_KEY_KP4            = 0X6B,
+  PS2_KEY_KP7            = 0X6C,
+  PS2_KEY_PageDown       = 0X6D,
+  PS2_KEY_Home           = 0X6E,
+  PS2_KEY_PageUp         = 0X6F,
+  PS2_KEY_KP0            = 0X70,
+  PS2_KEY_KPPeriod       = 0X71,
+  PS2_KEY_KP2            = 0X72,
+  PS2_KEY_KP5            = 0X73,
+  PS2_KEY_KP6            = 0X74,
+  PS2_KEY_KP8            = 0X75,
+  PS2_KEY_NumLock        = 0X76,
+  PS2_KEY_KPSlash        = 0X77,
+  PS2_KEY_KPEnter        = 0X79,
+  PS2_KEY_KP3            = 0X7A,
+  PS2_KEY_KPComma        = 0X7B,
+  PS2_KEY_KPPlus         = 0X7C,
+  PS2_KEY_KP9            = 0X7D,
+  PS2_KEY_KPAsterisk     = 0X7E,
+  PS2_KEY_KPMinus        = 0X84,
+  PS2_KEY_International5 = 0X85,
+  PS2_KEY_International4 = 0X86,
+  PS2_KEY_International2 = 0X87,
+  PS2_KEY_LeftGUI        = 0X8B,
+  PS2_KEY_RightGUI       = 0X8C,
+  PS2_KEY_Context        = 0X8D,
+} Ps2Key;
+
+#define PS2_MOD_RELEASE 0XF0
+
+typedef enum {
+  HID_ERR_RollOver             = 0X01,
+  HID_ERR_PostFail             = 0X02,
+  HID_ERR_Undefined            = 0X03,
+  HID_KEY_A                    = 0X04,
+  HID_KEY_B                    = 0X05,
+  HID_KEY_C                    = 0X06,
+  HID_KEY_D                    = 0X07,
+  HID_KEY_E                    = 0X08,
+  HID_KEY_F                    = 0X09,
+  HID_KEY_G                    = 0X0A,
+  HID_KEY_H                    = 0X0B,
+  HID_KEY_I                    = 0X0C,
+  HID_KEY_J                    = 0X0D,
+  HID_KEY_K                    = 0X0E,
+  HID_KEY_L                    = 0X0F,
+  HID_KEY_M                    = 0X10,
+  HID_KEY_N                    = 0X11,
+  HID_KEY_O                    = 0X12,
+  HID_KEY_P                    = 0X13,
+  HID_KEY_Q                    = 0X14,
+  HID_KEY_R                    = 0X15,
+  HID_KEY_S                    = 0X16,
+  HID_KEY_T                    = 0X17,
+  HID_KEY_U                    = 0X18,
+  HID_KEY_V                    = 0X19,
+  HID_KEY_W                    = 0X1A,
+  HID_KEY_X                    = 0X1B,
+  HID_KEY_Y                    = 0X1C,
+  HID_KEY_Z                    = 0X1D,
+  HID_KEY_1                    = 0X1E,
+  HID_KEY_2                    = 0X1F,
+  HID_KEY_3                    = 0X20,
+  HID_KEY_4                    = 0X21,
+  HID_KEY_5                    = 0X22,
+  HID_KEY_6                    = 0X23,
+  HID_KEY_7                    = 0X24,
+  HID_KEY_8                    = 0X25,
+  HID_KEY_9                    = 0X26,
+  HID_KEY_0                    = 0X27,
+  HID_KEY_Enter                = 0X28,
+  HID_KEY_Escape               = 0X29,
+  HID_KEY_Backspace            = 0X2A,
+  HID_KEY_Tab                  = 0X2B,
+  HID_KEY_Space                = 0X2C,
+  HID_KEY_Minus                = 0X2D,
+  HID_KEY_Equal                = 0X2E,
+  HID_KEY_LeftBracket          = 0X2F,
+  HID_KEY_RightBracket         = 0X30,
+  HID_KEY_Backslash            = 0X31,
+  HID_KEY_Europe1              = 0X32,
+  HID_KEY_Semicolon            = 0X33,
+  HID_KEY_Apostrophe           = 0X34,
+  HID_KEY_Grave                = 0X35,
+  HID_KEY_Comma                = 0X36,
+  HID_KEY_Period               = 0X37,
+  HID_KEY_Slash                = 0X38,
+  HID_KEY_CapsLock             = 0X39,
+  HID_KEY_F1                   = 0X3A,
+  HID_KEY_F2                   = 0X3B,
+  HID_KEY_F3                   = 0X3C,
+  HID_KEY_F4                   = 0X3D,
+  HID_KEY_F5                   = 0X3E,
+  HID_KEY_F6                   = 0X3F,
+  HID_KEY_F7                   = 0X40,
+  HID_KEY_F8                   = 0X41,
+  HID_KEY_F9                   = 0X42,
+  HID_KEY_F10                  = 0X43,
+  HID_KEY_F11                  = 0X44,
+  HID_KEY_F12                  = 0X45,
+  HID_KEY_PrintScreen          = 0X46,
+  HID_KEY_ScrollLock           = 0X47,
+  HID_KEY_Pause                = 0X48,
+  HID_KEY_Insert               = 0X49,
+  HID_KEY_Home                 = 0X4A,
+  HID_KEY_PageUp               = 0X4B,
+  HID_KEY_Delete               = 0X4C,
+  HID_KEY_End                  = 0X4D,
+  HID_KEY_PageDown             = 0X4E,
+  HID_KEY_ArrowRight           = 0X4F,
+  HID_KEY_ArrowLeft            = 0X50,
+  HID_KEY_ArrowDown            = 0X51,
+  HID_KEY_ArrowUp              = 0X52,
+  HID_KEY_NumLock              = 0X53,
+  HID_KEY_KPSlash              = 0X54,
+  HID_KEY_KPAsterisk           = 0X55,
+  HID_KEY_KPMinus              = 0X56,
+  HID_KEY_KPPlus               = 0X57,
+  HID_KEY_KPEnter              = 0X58,
+  HID_KEY_KP1                  = 0X59,
+  HID_KEY_KP2                  = 0X5A,
+  HID_KEY_KP3                  = 0X5B,
+  HID_KEY_KP4                  = 0X5C,
+  HID_KEY_KP5                  = 0X5D,
+  HID_KEY_KP6                  = 0X5E,
+  HID_KEY_KP7                  = 0X5F,
+  HID_KEY_KP8                  = 0X60,
+  HID_KEY_KP9                  = 0X61,
+  HID_KEY_KP0                  = 0X62,
+  HID_KEY_KPPeriod             = 0X63,
+  HID_KEY_Europe2              = 0X64,
+  HID_KEY_Context              = 0X65,
+  HID_KEY_Power                = 0X66,
+  HID_KEY_KPEqual              = 0X67,
+  HID_KEY_F13                  = 0X68,
+  HID_KEY_F14                  = 0X69,
+  HID_KEY_F15                  = 0X6A,
+  HID_KEY_F16                  = 0X6B,
+  HID_KEY_F17                  = 0X6C,
+  HID_KEY_F18                  = 0X6D,
+  HID_KEY_F19                  = 0X6E,
+  HID_KEY_F20                  = 0X6F,
+  HID_KEY_F21                  = 0X70,
+  HID_KEY_F22                  = 0X71,
+  HID_KEY_F23                  = 0X72,
+  HID_KEY_F24                  = 0X73,
+  HID_KEY_Execute              = 0X74,
+  HID_KEY_Help                 = 0X75,
+  HID_KEY_Menu                 = 0X76,
+  HID_KEY_Select               = 0X77,
+  HID_KEY_Stop                 = 0X78,
+  HID_KEY_Again                = 0X79,
+  HID_KEY_Undo                 = 0X7A,
+  HID_KEY_Cut                  = 0X7B,
+  HID_KEY_Copy                 = 0X7C,
+  HID_KEY_Paste                = 0X7D,
+  HID_KEY_Find                 = 0X7E,
+  HID_KEY_Mute                 = 0X7F,
+  HID_KEY_VolumeUp             = 0X80,
+  HID_KEY_VolumeDown           = 0X81,
+  HID_KEY_CapsLocking          = 0X82,
+  HID_KEY_NumLocking           = 0X83,
+  HID_KEY_ScrollLocking        = 0X84,
+  HID_KEY_KPComma              = 0X85,
+  HID_KEY_Equal_X1             = 0X86,
+  HID_KEY_International1       = 0X87, // Ro
+  HID_KEY_International2       = 0X88, // Katakana/Hiragana
+  HID_KEY_International3       = 0X89, // Yen
+  HID_KEY_International4       = 0X8A, // Henkan
+  HID_KEY_International5       = 0X8B, // Muhenkan
+  HID_KEY_International6       = 0X8C, // PC9800 Keypad '
+  HID_KEY_International7       = 0X8D,
+  HID_KEY_International8       = 0X8E,
+  HID_KEY_International9       = 0X8F,
+  HID_KEY_Language1            = 0X90, // Hanguel/English
+  HID_KEY_Language2            = 0X91, // Hanja
+  HID_KEY_Language3            = 0X92, // Katakana
+  HID_KEY_Language4            = 0X93, // Hiragana
+  HID_KEY_Language5            = 0X94, // Zenkaku/Hankaku
+  HID_KEY_Language6            = 0X95,
+  HID_KEY_Language7            = 0X96,
+  HID_KEY_Language8            = 0X97,
+  HID_KEY_Language9            = 0X98,
+  HID_KEY_AlternateErase       = 0X99,
+  HID_KEY_SystemReequest       = 0X9A,
+  HID_KEY_Cancel               = 0X9B,
+  HID_KEY_Clear                = 0X9C,
+  HID_KEY_Prior                = 0X9D,
+  HID_KEY_Return               = 0X9E,
+  HID_KEY_Separator            = 0X9F,
+  HID_KEY_Out                  = 0XA0,
+  HID_KEY_Oper                 = 0XA1,
+  HID_KEY_ClearAgain           = 0XA2,
+  HID_KEY_CrSel                = 0XA3,
+  HID_KEY_ExSel                = 0XA4,
+  HID_KEY_KP00                 = 0XB0,
+  HID_KEY_KP000                = 0XB1,
+  HID_KEY_KPThousandsSeparator = 0XB2,
+  HID_KEY_KPDecimalSeparator   = 0XB3,
+  HID_KEY_KPCurrencyUnit       = 0XB4,
+  HID_KEY_KPCurrencySubunit    = 0XB5,
+  HID_KEY_KPLeftParenthesis    = 0XB6,
+  HID_KEY_KPRightParenthesis   = 0XB7,
+  HID_KEY_KPLeftBrace          = 0XB8,
+  HID_KEY_KPRightBrace         = 0XB9,
+  HID_KEY_KPTab                = 0XBA,
+  HID_KEY_KPBackspace          = 0XBB,
+  HID_KEY_KPA                  = 0XBC,
+  HID_KEY_KPB                  = 0XBD,
+  HID_KEY_KPC                  = 0XBE,
+  HID_KEY_KPD                  = 0XBF,
+  HID_KEY_KPE                  = 0XC0,
+  HID_KEY_KPF                  = 0XC1,
+  HID_KEY_KPBitwiseXor         = 0XC2,
+  HID_KEY_KPExponentiate       = 0XC3,
+  HID_KEY_KPmodulo             = 0XC4,
+  HID_KEY_KPLess               = 0XC5,
+  HID_KEY_KPGreater            = 0XC6,
+  HID_KEY_KPBitwiseAnd         = 0XC7,
+  HID_KEY_KPBooleanAnd         = 0XC8,
+  HID_KEY_KPBitwiseOr          = 0XC9,
+  HID_KEY_KPBooleanOr          = 0XCA,
+  HID_KEY_KPColon              = 0XCB,
+  HID_KEY_KPNumber             = 0XCC,
+  HID_KEY_KPSpace              = 0XCD,
+  HID_KEY_KPAt                 = 0XCE,
+  HID_KEY_KPBooleanNot         = 0XCF,
+  HID_KEY_KPMemoryStore        = 0XD0,
+  HID_KEY_KPMemoryRecall       = 0XD1,
+  HID_KEY_KPMemoryClear        = 0XD2,
+  HID_KEY_KPMemoryAdd          = 0XD3,
+  HID_KEY_KPMemorySubtract     = 0XD4,
+  HID_KEY_KPMemoryMultiply     = 0XD5,
+  HID_KEY_KPMemoryDivide       = 0XD6,
+  HID_KEY_KPPlusMinus          = 0XD7,
+  HID_KEY_KPClear              = 0XD8,
+  HID_KEY_KPClearEntry         = 0XD9,
+  HID_KEY_KPBinary             = 0XDA,
+  HID_KEY_KPOctal              = 0XDB,
+  HID_KEY_KPDecimal            = 0XDC,
+  HID_KEY_KPHexadecimal        = 0XDD,
+  HID_KEY_LeftControl          = 0XE0,
+  HID_KEY_LeftShift            = 0XE1,
+  HID_KEY_LeftAlt              = 0XE2,
+  HID_KEY_LeftGUI              = 0XE3,
+  HID_KEY_RightControl         = 0XE4,
+  HID_KEY_RightShift           = 0XE5,
+  HID_KEY_RightAlt             = 0XE6,
+  HID_KEY_RightGUI             = 0XE7,
+} HidKey;
+
+extern const unsigned char AT2XT[0X80];
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KBD_KEYCODES */
diff --git a/Headers/ktb.h b/Headers/ktb.h
new file mode 100644
index 0000000..4d86595
--- /dev/null
+++ b/Headers/ktb.h
@@ -0,0 +1,76 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB
+#define BRLTTY_INCLUDED_KTB
+
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern KeyTable *compileKeyTable (const char *name, KEY_NAME_TABLES_REFERENCE keys);
+extern void destroyKeyTable (KeyTable *table);
+
+typedef int KeyNameEntryHandler (const KeyNameEntry *kne, void *data);
+extern int forEachKeyName (KEY_NAME_TABLES_REFERENCE keys, KeyNameEntryHandler *handleKeyNameEntry, void *data);
+extern const KeyNameEntry *findKeyNameEntry (KeyTable *table, const KeyValue *value);
+
+typedef int KeyTableWriteLineMethod (const wchar_t *line, void *data);
+typedef int KeyTableWriteHeaderMethod (const wchar_t *text, unsigned int level, void *data);
+typedef int KeyTableBeginElementMethod (unsigned int level, void *data);
+typedef int KeyTableEndListMethod (void *data);
+
+typedef struct {
+  KeyTableWriteHeaderMethod *writeHeader;
+  KeyTableBeginElementMethod *beginElement;
+  KeyTableEndListMethod *endList;
+} KeyTableListMethods;
+
+extern int listKeyTable (KeyTable *table, const KeyTableListMethods *methods, KeyTableWriteLineMethod *writeLine, void *data);
+extern int listKeyNames (KEY_NAME_TABLES_REFERENCE keys, KeyTableWriteLineMethod *writeLine, void *data);
+extern int auditKeyTable (KeyTable *table, const char *path);
+
+extern char *ensureKeyTableExtension (const char *path);
+extern char *makeKeyTablePath (const char *directory, const char *name);
+
+extern char *makeKeyboardTablePath (const char *directory, const char *name);
+extern char *makeInputTablePath (const char *directory, const char *driver, const char *name);
+
+extern void resetKeyTable (KeyTable *table);
+extern void releaseAllKeys (KeyTable *table);
+
+extern KeyTableState processKeyEvent (
+  KeyTable *table, unsigned char context,
+  KeyGroup keyGroup, KeyNumber keyNumber, int press
+);
+
+extern void setKeyTableLogLabel (KeyTable *table, const char *label);
+extern void setLogKeyEventsFlag (KeyTable *table, const unsigned char *flag);
+extern void setKeyboardEnabledFlag (KeyTable *table, const unsigned char *flag);
+extern void setKeyAutoreleaseTime (KeyTable *table, unsigned char setting);
+
+extern void getKeyGroupCommands (KeyTable *table, KeyGroup group, int *commands, unsigned int size);
+extern int *getBoundCommands (KeyTable *table, unsigned int *count);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB */
diff --git a/Headers/ktb_types.h b/Headers/ktb_types.h
new file mode 100644
index 0000000..e18c87a
--- /dev/null
+++ b/Headers/ktb_types.h
@@ -0,0 +1,92 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_TYPES
+#define BRLTTY_INCLUDED_KTB_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MAX_KEYS_PER_GROUP 0X100
+#define KTB_KEY_ANY 0XFF
+#define KTB_KEY_MAX 0XFE
+
+typedef unsigned char KeyGroup;
+typedef unsigned char KeyNumber;
+
+typedef struct {
+  KeyGroup group;
+  KeyNumber number;
+} KeyValue;
+
+typedef struct {
+  const char *name;
+  KeyValue value;
+} KeyNameEntry;
+
+#define KEY_NAME_TABLE(name) keyNameTable_##name
+#define KEY_NAME_TABLE_DECLARATION(name) const KeyNameEntry KEY_NAME_TABLE(name)[]
+#define KEY_NAME_ENTRY(keyNumber,keyName) {.value.number=keyNumber, .name=keyName}
+#define KEY_GROUP_ENTRY(keyGroup,keyName) {.value={.group=keyGroup, .number=KTB_KEY_ANY}, .name=keyName}
+#define LAST_KEY_NAME_ENTRY {.name=NULL}
+#define BEGIN_KEY_NAME_TABLE(name) static KEY_NAME_TABLE_DECLARATION(name) = {
+#define END_KEY_NAME_TABLE LAST_KEY_NAME_ENTRY};
+#define KEY_NAME_SUBTABLE(name,count) (KEY_NAME_TABLE(name) + (ARRAY_COUNT(KEY_NAME_TABLE(name)) - 1 - (count)))
+
+#define KEY_NAME_TABLES(name) keyNameTables_##name
+#define KEY_NAME_TABLES_REFERENCE const KeyNameEntry *const *
+#define KEY_NAME_TABLES_DECLARATION(name) const KeyNameEntry *const KEY_NAME_TABLES(name)[]
+#define LAST_KEY_NAME_TABLE NULL
+#define BEGIN_KEY_NAME_TABLES(name) static KEY_NAME_TABLES_DECLARATION(name) = {
+#define END_KEY_NAME_TABLES LAST_KEY_NAME_TABLE};
+
+typedef struct {
+  const char *bindings;
+  KEY_NAME_TABLES_REFERENCE names;
+} KeyTableDefinition;
+
+#define KEY_TABLE_DEFINITION(name) keyTableDefinition_##name
+#define KEY_TABLE_DECLARATION(name) const KeyTableDefinition KEY_TABLE_DEFINITION(name)
+#define KEY_TABLE_INITIALIZER(name) = {.bindings=#name, .names=KEY_NAME_TABLES(name)}
+#define DEFINE_KEY_TABLE(name) \
+  static KEY_TABLE_DECLARATION(name) KEY_TABLE_INITIALIZER(name);
+#define PUBLIC_KEY_TABLE(name) \
+  KEY_TABLE_DECLARATION(name) KEY_TABLE_INITIALIZER(name);
+#define EXTERNAL_KEY_TABLE(name) extern KEY_TABLE_DECLARATION(name);
+
+typedef enum {
+  KTS_UNBOUND,
+  KTS_MODIFIERS,
+  KTS_COMMAND,
+  KTS_HOTKEY
+} KeyTableState;
+
+typedef struct KeyTableStruct KeyTable;
+
+typedef enum {
+  KTB_CTX_MENU,
+  KTB_CTX_WAITING,
+  KTB_CTX_DEFAULT /* this one must be defined last */
+} KeyTableCommandContext;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_TYPES */
diff --git a/Headers/leds.h b/Headers/leds.h
new file mode 100644
index 0000000..87b911d
--- /dev/null
+++ b/Headers/leds.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_LEDS
+#define BRLTTY_INCLUDED_LEDS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int setLedMonitoring (int on);
+
+extern int canMonitorLeds (void);
+extern int startMonitoringLeds (void);
+extern void stopMonitoringLeds (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_LEDS */
diff --git a/Headers/lock.h b/Headers/lock.h
new file mode 100644
index 0000000..8b8e030
--- /dev/null
+++ b/Headers/lock.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_LOCK
+#define BRLTTY_INCLUDED_LOCK
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct LockDescriptorStruct LockDescriptor;
+
+typedef enum {
+  LOCK_Exclusive = 0X1,
+  LOCK_NoWait    = 0X2
+} LockOptions;
+
+extern LockDescriptor *newLockDescriptor (void);
+extern LockDescriptor *getLockDescriptor (LockDescriptor **lock, const char *name);
+extern void freeLockDescriptor (LockDescriptor *lock);
+
+extern int obtainLock (LockDescriptor *lock, LockOptions options);
+extern void releaseLock (LockDescriptor *lock);
+
+static inline void obtainExclusiveLock (LockDescriptor *lock) {
+  obtainLock(lock, LOCK_Exclusive);
+}
+
+static inline void obtainSharedLock (LockDescriptor *lock) {
+  obtainLock(lock, 0);
+}
+
+static inline int tryExclusiveLock (LockDescriptor *lock) {
+  return obtainLock(lock, LOCK_Exclusive | LOCK_NoWait);
+}
+
+static inline int trySharedLock (LockDescriptor *lock) {
+  return obtainLock(lock, LOCK_NoWait);
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_LOCK */
diff --git a/Headers/log.h b/Headers/log.h
new file mode 100644
index 0000000..4c47b71
--- /dev/null
+++ b/Headers/log.h
@@ -0,0 +1,144 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_LOG
+#define BRLTTY_INCLUDED_LOG
+
+#include "prologue.h"
+
+#include <stdarg.h>
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif /* HAVE_SYSLOG_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern unsigned char systemLogLevel;
+extern unsigned char stderrLogLevel;
+
+#ifndef LOG_INFO
+typedef enum {
+  LOG_EMERG,
+  LOG_ALERT,
+  LOG_CRIT,
+  LOG_ERR,
+  LOG_WARNING,
+  LOG_NOTICE,
+  LOG_INFO,
+  LOG_DEBUG
+} SyslogLevel;
+#endif /* log level definitions */
+
+extern const char *const logLevelNames[];
+extern const unsigned int logLevelCount;
+
+#define LOG_LEVEL_WIDTH 8
+#define LOG_LEVEL_MASK (LOG_LEVEL_WIDTH - 1)
+
+#define LOG_CATEGORY_INDEX(name) LOG_CTG_##name
+#define LOG_CATEGORY(name) ((LOG_CATEGORY_INDEX(name) + 1) << LOG_LEVEL_WIDTH)
+
+typedef enum {
+  LOG_CATEGORY_INDEX(INPUT_PACKETS),
+  LOG_CATEGORY_INDEX(OUTPUT_PACKETS),
+
+  LOG_CATEGORY_INDEX(BRAILLE_KEYS),
+  LOG_CATEGORY_INDEX(KEYBOARD_KEYS),
+
+  LOG_CATEGORY_INDEX(CURSOR_TRACKING),
+  LOG_CATEGORY_INDEX(CURSOR_ROUTING),
+
+  LOG_CATEGORY_INDEX(UPDATE_EVENTS),
+  LOG_CATEGORY_INDEX(SPEECH_EVENTS),
+  LOG_CATEGORY_INDEX(ASYNC_EVENTS),
+  LOG_CATEGORY_INDEX(SERVER_EVENTS),
+
+  LOG_CATEGORY_INDEX(GENERIC_IO),
+  LOG_CATEGORY_INDEX(SERIAL_IO),
+  LOG_CATEGORY_INDEX(USB_IO),
+  LOG_CATEGORY_INDEX(BLUETOOTH_IO),
+  LOG_CATEGORY_INDEX(HID_IO),
+
+  LOG_CATEGORY_INDEX(BRAILLE_DRIVER),
+  LOG_CATEGORY_INDEX(SPEECH_DRIVER),
+  LOG_CATEGORY_INDEX(SCREEN_DRIVER),
+
+  LOG_CATEGORY_COUNT /* must be last */
+} LogCategoryIndex;
+
+extern const char *getLogCategoryName (LogCategoryIndex index);
+extern const char *getLogCategoryTitle (LogCategoryIndex index);
+
+extern void disableAllLogCategories (void);
+extern int setLogCategory (const char *name);
+extern const char logCategoryName_all[];
+extern const char logCategoryPrefix_disable;
+
+extern unsigned char categoryLogLevel;
+extern unsigned char logCategoryFlags[LOG_CATEGORY_COUNT];
+#define LOG_CATEGORY_FLAG(name) logCategoryFlags[LOG_CATEGORY_INDEX(name)]
+
+extern void openLogFile (const char *path);
+extern void closeLogFile (void);
+
+extern void openSystemLog (void);
+extern void closeSystemLog (void);
+
+extern int pushLogPrefix (const char *prefix);
+extern int popLogPrefix (void);
+
+typedef size_t LogDataFormatter (char *buffer, size_t size, const void *data);
+extern int logData (int level, LogDataFormatter *formatLogData, const void *data);
+
+extern int logMessage (int level, const char *format, ...) PRINTF(2, 3);
+extern int vlogMessage (int level, const char *format, va_list *arguments);
+
+extern int logBytes (int level, const char *label, const void *data, size_t length, ...) PRINTF(2, 5);
+extern int logSymbol (int level, void *address, const char *format, ...) PRINTF(3, 4);
+
+extern int logActionProblem (int level, int error, const char *action);
+extern int logActionError (int error, const char *action);
+
+extern int logSystemProblem (int level, const char *action);
+extern int logSystemError (const char *action);
+extern int logMallocError (void);
+
+extern int logUnsupportedFeature (const char *name);
+extern int logUnsupportedOperation (const char *name);
+#define logUnsupportedFunction() logUnsupportedOperation(__func__)
+extern int logPossibleCause (const char *cause);
+
+#ifdef WINDOWS
+extern int logWindowsError (DWORD code, const char *action);
+extern int logWindowsSystemError (const char *action);
+
+#ifdef __MINGW32__
+extern int logWindowsSocketError (const char *action);
+#endif /* __MINGW32__ */
+#endif /* WINDOWS */
+
+extern int logBacktrace (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_LOG */
diff --git a/Headers/log_history.h b/Headers/log_history.h
new file mode 100644
index 0000000..4912df5
--- /dev/null
+++ b/Headers/log_history.h
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_LOG_HISTORY
+#define BRLTTY_INCLUDED_LOG_HISTORY
+
+#include "thread.h"
+#include "timing.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  LPO_NOLOG  = 0X01,
+  LPO_SQUASH = 0X02,
+} LogEntryPushOptions;
+
+typedef struct LogEntryStruct LogEntry;
+extern const LogEntry *getPreviousLogEntry (const LogEntry *entry);
+extern const char *getLogEntryText (const LogEntry *entry);
+extern const TimeValue *getLogEntryTime (const LogEntry *entry);
+extern unsigned int getLogEntryCount (const LogEntry *entry);
+
+extern int pushLogEntry (LogEntry **head, const char *text, LogEntryPushOptions options);
+extern int popLogEntry (LogEntry **head);
+
+extern const LogEntry *getNewestLogMessage (int freeze);
+extern void pushLogMessage (const char *message);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_LOG_HISTORY */
diff --git a/Headers/menu.h b/Headers/menu.h
new file mode 100644
index 0000000..1653dc1
--- /dev/null
+++ b/Headers/menu.h
@@ -0,0 +1,123 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MENU
+#define BRLTTY_INCLUDED_MENU
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct MenuStruct Menu;
+typedef struct MenuItemStruct MenuItem;
+
+typedef struct {
+  const char *label;
+  const char *comment;
+} MenuString;
+
+extern Menu *newMenu (void);
+extern void destroyMenu (Menu *menu);
+
+extern MenuItem *newTextMenuItem (Menu *menu, const MenuString *name, const char *text);
+
+typedef void NumericMenuItemFormatter (
+  Menu *menu, unsigned char value,
+  char *buffer, size_t size
+);
+
+extern MenuItem *newNumericMenuItem (
+  Menu *menu, unsigned char *setting, const MenuString *name,
+  unsigned char minimum, unsigned char maximum, unsigned char step,
+  const char *unit, NumericMenuItemFormatter *formatter
+);
+
+extern MenuItem *newTimeMenuItem (
+  Menu *menu, unsigned char *setting,
+  const MenuString *name
+);
+
+extern MenuItem *newPercentMenuItem (
+  Menu *menu, unsigned char *setting,
+  const MenuString *name, unsigned char step
+);
+
+extern MenuItem *newStringsMenuItem (
+  Menu *menu, unsigned char *setting, const MenuString *name,
+  const MenuString *strings, unsigned char count
+);
+
+#define newEnumeratedMenuItem(menu, setting, name, strings) newStringsMenuItem(menu, setting, name, strings, ARRAY_COUNT(strings))
+extern MenuItem *newBooleanMenuItem (Menu *menu, unsigned char *setting, const MenuString *name);
+
+extern MenuItem *newFilesMenuItem (
+  Menu *menu, const MenuString *name,
+  const char *directory, const char *subdirectory, const char *extension,
+  const char *initial, int none
+);
+
+typedef void MenuToolFunction (void);
+extern MenuItem *newToolMenuItem (Menu *menu, const MenuString *name, MenuToolFunction *function);
+
+extern Menu *newSubmenuMenuItem (
+  Menu *menu, const MenuString *name
+);
+
+typedef int MenuItemTester (void);
+extern void setMenuItemTester (MenuItem *item, MenuItemTester *handler);
+
+typedef int MenuItemChanged (const MenuItem *item, unsigned char setting);
+extern void setMenuItemChanged (MenuItem *item, MenuItemChanged *handler);
+
+extern unsigned int getMenuNumber (const Menu *menu);
+extern Menu *getMenuParent (const Menu *menu);
+extern unsigned int getMenuSize (const Menu *menu);
+extern unsigned int getMenuIndex (const Menu *menu);
+extern MenuItem *getMenuItem (Menu *menu, unsigned int index);
+
+extern int isMenuItemSettable (const MenuItem *item);
+extern int isMenuItemAction (const MenuItem *item);
+extern int isMenuItemVisible (const MenuItem *item);
+
+extern Menu *getMenuItemMenu (const MenuItem *item);
+extern unsigned int getMenuItemIndex (const MenuItem *item);
+extern const char *getMenuItemTitle (const MenuItem *item);
+extern const char *getMenuItemSubtitle (const MenuItem *item);
+extern const char *getMenuItemValue (const MenuItem *item);
+extern const char *getMenuItemText (const MenuItem *item);
+extern const char *getMenuItemComment (const MenuItem *item);
+
+extern void changeMenuItem (MenuItem *item);
+extern int changeMenuItemPrevious (Menu *menu, int wrap);
+extern int changeMenuItemNext (Menu *menu, int wrap);
+extern int changeMenuItemFirst (Menu *menu);
+extern int changeMenuItemLast (Menu *menu);
+extern int changeMenuItemIndex (Menu *menu, unsigned int index);
+
+extern int changeMenuSettingPrevious (Menu *menu, int wrap);
+extern int changeMenuSettingNext (Menu *menu, int wrap);
+extern int changeMenuSettingScaled (Menu *menu, unsigned int index, unsigned int count);
+
+extern MenuItem *getCurrentMenuItem (Menu *menu);
+extern Menu *getCurrentSubmenu (Menu *menu);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MENU */
diff --git a/Headers/message.h b/Headers/message.h
new file mode 100644
index 0000000..02baed7
--- /dev/null
+++ b/Headers/message.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MESSAGE
+#define BRLTTY_INCLUDED_MESSAGE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  MSG_SILENT  = 0X1, /* don't speak the message */
+  MSG_LOG     = 0X2, /* add the message to the log's message stack */
+  MSG_NODELAY = 0X4, /* don't wait */
+  MSG_SYNC    = 0X8  /* run synchronously */
+} MessageOptions;
+
+extern int message (const char *mode, const char *text, MessageOptions options);
+
+extern int messageHoldTimeout;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MESSAGE */
diff --git a/Headers/messages.h b/Headers/messages.h
new file mode 100644
index 0000000..c32188e
--- /dev/null
+++ b/Headers/messages.h
@@ -0,0 +1,77 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MESSAGES
+#define BRLTTY_INCLUDED_MESSAGES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int setMessagesDirectory (const char *directory);
+extern const char *getMessagesDirectory (void);
+
+extern int setMessagesLocale (const char *specifier);
+extern const char *getMessagesLocale (void);
+
+extern int setMessagesDomain (const char *name);
+extern const char *getMessagesDomain (void);
+
+extern void ensureAllMessagesProperties (void);
+
+extern int loadMessageCatalog (void);
+extern void releaseMessageCatalog (void);
+
+extern uint32_t getMessageCount (void);
+extern const char *getMessagesMetadata (void);
+extern char *getMessagesProperty (const char *name);
+extern char *getMessagesAttribute (const char *property, const char *name);
+
+#define MESSAGES_PROPERTY_MIME_VERSION "MIME-Version"
+#define MESSAGES_PROPERTY_CONTENT_TYPE "Content-Type"
+#define MESSAGES_PROPERTY_CONTENT_TRANSFER_ENCODING "Content-Transfer-Encoding"
+
+#define MESSAGES_PROPERTY_PROJECT_VERSION "Project-Id-Version"
+#define MESSAGES_PROPERTY_LANGUAGE_TEAM "Language-Team"
+#define MESSAGES_PROPERTY_MSGID_BUGS "Report-Msgid-Bugs-To"
+
+#define MESSAGES_PROPERTY_LANGUAGE_CODE "Language"
+#define MESSAGES_PROPERTY_PLURAL_FORMS "Plural-Forms"
+
+#define MESSAGES_PROPERTY_LAST_TRANSLATOR "Last-Translator"
+#define MESSAGES_PROPERTY_REVISION_DATE "PO-Revision-Date"
+
+typedef struct MessageStruct Message;
+extern const char *getMessageText (const Message *message);
+extern uint32_t getMessageLength (const Message *message);
+
+extern const Message *getSourceMessage (unsigned int index);
+extern const Message *getTranslatedMessage (unsigned int index);
+
+extern int findSourceMessage (const char *text, size_t textLength, unsigned int *index);
+extern const Message *findSimpleTranslation (const char *text, size_t length);
+extern const Message *findPluralTranslation (const char *const *strings);
+
+extern const char *getSimpleTranslation (const char *text);
+extern const char *getPluralTranslation (const char *singular, const char *plural, unsigned long int count);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MESSAGES */
diff --git a/Headers/midi.h b/Headers/midi.h
new file mode 100644
index 0000000..c8e7d01
--- /dev/null
+++ b/Headers/midi.h
@@ -0,0 +1,47 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MIDI
+#define BRLTTY_INCLUDED_MIDI
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *const midiInstrumentTable[];
+extern const unsigned int midiInstrumentCount;
+extern const char *midiGetInstrumentGroup (unsigned char instrument);
+
+typedef struct MidiDeviceStruct MidiDevice;
+typedef void (*MidiBufferFlusher) (unsigned char *buffer, int count);
+
+extern MidiDevice *openMidiDevice (int errorLevel, const char *device);
+extern void closeMidiDevice (MidiDevice *midi);
+extern int flushMidiDevice (MidiDevice *midi);
+extern int setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument);
+extern int beginMidiBlock (MidiDevice *midi);
+extern int endMidiBlock (MidiDevice *midi);
+extern int startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume);
+extern int stopMidiNote (MidiDevice *midi, unsigned char channel);
+extern int insertMidiWait (MidiDevice *midi, int duration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MIDI */
diff --git a/Headers/mntfs.h b/Headers/mntfs.h
new file mode 100644
index 0000000..e342afa
--- /dev/null
+++ b/Headers/mntfs.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTFS
+#define BRLTTY_INCLUDED_MNTFS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int mountFileSystem (const char *path, const char *reference, const char *type);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTFS */
diff --git a/Headers/mntpt.h b/Headers/mntpt.h
new file mode 100644
index 0000000..b5c4a46
--- /dev/null
+++ b/Headers/mntpt.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTPT
+#define BRLTTY_INCLUDED_MNTPT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef int (*MountPointTester) (const char *path, const char *type);
+
+extern char *findMountPoint (MountPointTester test);
+
+extern int makeMountPoint (const char *path, const char *reference, const char *type);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTPT */
diff --git a/Headers/morse.h b/Headers/morse.h
new file mode 100644
index 0000000..64da467
--- /dev/null
+++ b/Headers/morse.h
@@ -0,0 +1,63 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MORSE
+#define BRLTTY_INCLUDED_MORSE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MORSE_UNITS_MARK_SHORT  1
+#define MORSE_UNITS_MARK_LONG   3
+#define MORSE_UNITS_GAP_SYMBOL  1
+#define MORSE_UNITS_GAP_LETTER  3
+#define MORSE_UNITS_GAP_WORD    7
+#define MORSE_UNITS_PER_WORD   50
+#define MORSE_UNITS_PER_GROUP  60
+
+typedef struct MorseObjectStruct MorseObject;
+extern void *newMorseObject (void);
+extern void destroyMorseObject (MorseObject *morse);
+
+extern unsigned int getMorsePitch (MorseObject *morse);
+extern int setMorsePitch (MorseObject *morse, unsigned int frequency);
+
+extern unsigned int getMorseWordsPerMinute (MorseObject *morse);
+extern int setMorseWordsPerMinute (MorseObject *morse, unsigned int rate);
+
+extern unsigned int getMorseGroupsPerMinute (MorseObject *morse);
+extern int setMorseGroupsPerMinute (MorseObject *morse, unsigned int rate);
+
+extern int addMorseString (MorseObject *morse, const char *string);
+extern int addMorseCharacters (MorseObject *morse, const wchar_t *characters, size_t count);
+extern int addMorseCharacter (MorseObject *morse, wchar_t character);
+extern int addMorseSpace (MorseObject *morse);
+
+typedef uint8_t MorsePattern;
+extern MorsePattern getMorsePattern (wchar_t character);
+extern int addMorsePattern (MorseObject *morse, MorsePattern pattern);
+
+extern int playMorseSequence (MorseObject *morse);
+extern void clearMorseSequence (MorseObject *morse);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MORSE */
diff --git a/Headers/msg_queue.h b/Headers/msg_queue.h
new file mode 100644
index 0000000..39b74c0
--- /dev/null
+++ b/Headers/msg_queue.h
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MSG_QUEUE
+#define BRLTTY_INCLUDED_MSG_QUEUE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef long int MessageType;
+
+extern int sendMessage(int queue, MessageType type, const void *content,
+                       size_t length, int flags);
+extern ssize_t receiveMessage(int queue, MessageType *type, void *buffer,
+                              size_t size, int flags);
+
+typedef struct {
+  void *data;
+  MessageType type;
+
+  size_t length;
+  char content[];
+} MessageHandlerParameters;
+
+typedef void MessageHandler(const MessageHandlerParameters *parameters);
+extern int startMessageReceiver(const char *name, int queue, MessageType type,
+                                size_t size, MessageHandler *handler,
+                                void *data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MSG_QUEUE */
diff --git a/Headers/note_types.h b/Headers/note_types.h
new file mode 100644
index 0000000..0b95ae7
--- /dev/null
+++ b/Headers/note_types.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_NOTE_TYPES
+#define BRLTTY_INCLUDED_NOTE_TYPES
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef NO_FLOAT
+typedef uint32_t NoteFrequency;
+#define PRIfreq PRIu32
+#else /* NO_FLOAT */
+typedef float NoteFrequency;
+#define PRIfreq "f"
+#endif /* NO_FLOAT */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_NOTE_TYPES */
diff --git a/Headers/notes.h b/Headers/notes.h
new file mode 100644
index 0000000..75b05f7
--- /dev/null
+++ b/Headers/notes.h
@@ -0,0 +1,68 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_NOTES
+#define BRLTTY_INCLUDED_NOTES
+
+#include "note_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define NOTES_PER_OCTAVE 12
+#define NOTE_MIDDLE_C 60
+
+extern unsigned char getLowestNote (void);
+extern unsigned char getHighestNote (void);
+
+extern uint32_t getIntegerNoteFrequency (unsigned char note);
+extern unsigned char getNearestNote (NoteFrequency frequency);
+
+#ifdef NO_FLOAT
+#define getNoteFrequency getIntegerNoteFrequency
+#else /* NO_FLOAT */
+extern float getRealNoteFrequency (unsigned char note);
+#define getNoteFrequency getRealNoteFrequency
+#endif /* NO_FLOAT */
+
+typedef struct NoteDeviceStruct NoteDevice;
+
+typedef struct {
+  NoteDevice * (*construct) (int errorLevel);
+  void (*destruct) (NoteDevice *device);
+
+  int (*tone) (NoteDevice *device, unsigned int duration, NoteFrequency frequency);
+  int (*note) (NoteDevice *device, unsigned int duration, unsigned char note);
+
+  int (*flush) (NoteDevice *device);
+} NoteMethods;
+
+extern const NoteMethods beepNoteMethods;
+extern const NoteMethods pcmNoteMethods;
+extern const NoteMethods midiNoteMethods;
+extern const NoteMethods fmNoteMethods;
+
+extern char *opt_pcmDevice;
+extern char *opt_midiDevice;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_NOTES */
diff --git a/Headers/params.h b/Headers/params.h
new file mode 100644
index 0000000..b0dedae
--- /dev/null
+++ b/Headers/params.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PARAMS
+#define BRLTTY_INCLUDED_PARAMS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern char *getBootParameters (const char *name);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PARAMS */
diff --git a/Headers/parse.h b/Headers/parse.h
new file mode 100644
index 0000000..d8a2d86
--- /dev/null
+++ b/Headers/parse.h
@@ -0,0 +1,111 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PARSE
+#define BRLTTY_INCLUDED_PARSE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern char *joinStrings (const char *const *strings, int count);
+extern int changeStringSetting (char **setting, const char *value);
+extern int extendStringSetting (char **setting, const char *value, int prepend);
+extern void deallocateStrings (char **array);
+extern char **splitString (const char *string, char delimiter, int *count);
+
+extern int changeListSetting (char ***list, char **setting, const char *value);
+
+extern int rescaleInteger (int value, int from, int to);
+extern int isInteger (int *value, const char *string);
+extern int isUnsignedInteger (unsigned int *value, const char *string);
+extern int isLogLevel (unsigned int *level, const char *string);
+
+extern int isAbbreviation (const char *actual, const char *supplied);
+extern int isAbbreviatedPhrase (const char *actual, const char *supplied);
+
+extern int validateInteger (int *value, const char *string, const int *minimum, const int *maximum);
+extern int validateChoice (unsigned int *value, const char *string, const char *const *choices);
+extern int validateChoiceEx (unsigned int *value, const char *string, const void *choices, size_t size);
+
+typedef struct {
+  const char *on;
+  const char *off;
+} FlagKeywordPair;
+
+static inline const char *
+getFlagKeyword (const FlagKeywordPair *fkp, int state) {
+  return state? fkp->on: fkp->off;
+}
+
+extern FlagKeywordPair fkpOnOff;
+extern FlagKeywordPair fkpTrueFalse;
+extern FlagKeywordPair fkpYesNo;
+extern FlagKeywordPair fkp10;
+
+static inline const char *
+getFlagKeywordOnOff (int state) {
+  return getFlagKeyword(&fkpOnOff, state);
+}
+
+static inline const char *
+getFlagKeywordTrueFalse (int state) {
+  return getFlagKeyword(&fkpTrueFalse, state);
+}
+
+static inline const char *
+getFlagKeywordYesNo (int state) {
+  return getFlagKeyword(&fkpYesNo, state);
+}
+
+static inline const char *
+getFlagKeyword10 (int state) {
+  return getFlagKeyword(&fkp10, state);
+}
+
+extern int validateFlagKeyword (unsigned int *value, const char *string);
+extern int validateFlag (unsigned int *value, const char *string, const FlagKeywordPair *fkp);
+extern int validateOnOff (unsigned int *value, const char *string);
+extern int validateYesNo (unsigned int *value, const char *string);
+
+#ifndef NO_FLOAT
+extern int isFloat (float *value, const char *string);
+extern int validateFloat (float *value, const char *string, const float *minimum, const float *maximum);
+#endif /* NO_FLOAT */
+
+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+extern int validateUser(uid_t *value, const char *string, gid_t *group);
+extern int validateGroup(gid_t *value, const char *string);
+#endif /* defined(HAVE_PWD_H) && defined(HAVE_GRP_H) */
+
+#define PATH_SEPARATOR_CHARACTER       '/'
+#define PARAMETER_SEPARATOR_CHARACTER  ','
+#define PARAMETER_ASSIGNMENT_CHARACTER '='
+#define PARAMETER_QUALIFIER_CHARACTER  ':'
+
+extern int hasQualifier (const char **identifier, const char *qualifier);
+extern int hasNoQualifier (const char *identifier);
+
+extern char **getParameters (const char *const *names, const char *qualifier, const char *parameters);
+extern void logParameters (const char *const *names, char **values, const char *description);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PARSE */
diff --git a/Headers/pcm.h b/Headers/pcm.h
new file mode 100644
index 0000000..d1a49bc
--- /dev/null
+++ b/Headers/pcm.h
@@ -0,0 +1,87 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PCM
+#define BRLTTY_INCLUDED_PCM
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  PCM_FMT_S8,   // signed, 8 bits, linear
+  PCM_FMT_U8,   // unsigned, 8 bits, linear
+
+  PCM_FMT_S16B, // signed, 16 bits, linear, big endian
+  PCM_FMT_U16B, // unsigned, 16 bits, linear, big endian
+
+  PCM_FMT_S16L, // signed, 16 bits, linear, little endian
+  PCM_FMT_U16L, // unsigned, 16 bits, linear, little endian
+
+  PCM_FMT_ULAW, // signed, 8 bits, logarithmic
+  PCM_FMT_ALAW, // signed, 8 bits, logarithmic
+
+  PCM_FMT_UNKNOWN
+} PcmAmplitudeFormat;
+
+#ifdef WORDS_BIGENDIAN
+#define PCM_FMT_S16N PCM_FMT_S16B
+#define PCM_FMT_U16N PCM_FMT_U16B
+#else /* WORDS_BIGENDIAN */
+#define PCM_FMT_S16N PCM_FMT_S16L
+#define PCM_FMT_U16N PCM_FMT_U16L
+#endif /* WORDS_BIGENDIAN */
+
+#define PCM_MAX_SAMPLE_SIZE 2
+
+typedef union {
+  uint8_t bytes[PCM_MAX_SAMPLE_SIZE];
+} PcmSample;
+
+typedef uint8_t PcmSampleSize;
+typedef PcmSampleSize (*PcmSampleMaker) (PcmSample *sample, int16_t amplitude);
+extern PcmSampleMaker getPcmSampleMaker (PcmAmplitudeFormat format);
+
+typedef struct PcmDeviceStruct PcmDevice;
+
+extern PcmDevice *openPcmDevice (int errorLevel, const char *device);
+extern void closePcmDevice (PcmDevice *pcm);
+
+extern int getPcmBlockSize (PcmDevice *pcm);
+
+extern int getPcmSampleRate (PcmDevice *pcm);
+extern int setPcmSampleRate (PcmDevice *pcm, int rate);
+
+extern int getPcmChannelCount (PcmDevice *pcm);
+extern int setPcmChannelCount (PcmDevice *pcm, int channels);
+
+extern PcmAmplitudeFormat getPcmAmplitudeFormat (PcmDevice *pcm);
+extern PcmAmplitudeFormat setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format);
+
+extern int writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count);
+extern void pushPcmOutput (PcmDevice *pcm);
+extern void awaitPcmOutput (PcmDevice *pcm);
+extern void cancelPcmOutput (PcmDevice *pcm);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PCM */
diff --git a/Headers/pgmpath.h b/Headers/pgmpath.h
new file mode 100644
index 0000000..18e75cc
--- /dev/null
+++ b/Headers/pgmpath.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PGMPATH
+#define BRLTTY_INCLUDED_PGMPATH
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern char *getProgramPath (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PGMPATH */
diff --git a/Headers/pgmprivs.h b/Headers/pgmprivs.h
new file mode 100644
index 0000000..9b024a6
--- /dev/null
+++ b/Headers/pgmprivs.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PGMPRIVS
+#define BRLTTY_INCLUDED_PGMPRIVS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *const *getPrivilegeParameterNames (void);
+extern const char *getPrivilegeParametersPlatform (void);
+extern void establishProgramPrivileges (char **parameters, int stayPrivileged);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PGMPRIVS */
diff --git a/Headers/pid.h b/Headers/pid.h
new file mode 100644
index 0000000..2153b0e
--- /dev/null
+++ b/Headers/pid.h
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PID
+#define BRLTTY_INCLUDED_PID
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#if defined(__MSDOS__) || defined(GRUB_RUNTIME)
+typedef int ProcessIdentifier;
+#define PRIpid "d"
+#define SCNpid "d"
+#define MY_PROCESS_ID 1
+
+#elif defined(__MINGW32__)
+typedef DWORD ProcessIdentifier;
+#define PRIpid "lu"
+#define SCNpid "lu"
+
+#else /* Unix */
+typedef pid_t ProcessIdentifier;
+#define PRIpid "d"
+#define SCNpid "d"
+#endif /* platform-dependent definitions */
+
+extern ProcessIdentifier getProcessIdentifier (void);
+extern int testProcessIdentifier (ProcessIdentifier pid);
+extern int cancelProcess (ProcessIdentifier pid);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PID */
diff --git a/Headers/ports.h b/Headers/ports.h
new file mode 100644
index 0000000..e7ae60d
--- /dev/null
+++ b/Headers/ports.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PORTS
+#define BRLTTY_INCLUDED_PORTS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int enablePorts (int errorLevel, unsigned short int base, unsigned short int count);
+extern int disablePorts (unsigned short int base, unsigned short int count);
+
+extern unsigned char readPort1 (unsigned short int port);
+extern void writePort1 (unsigned short int port, unsigned char value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PORTS */
diff --git a/Headers/prefs.h b/Headers/prefs.h
new file mode 100644
index 0000000..0a7e735
--- /dev/null
+++ b/Headers/prefs.h
@@ -0,0 +1,251 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PREFS
+#define BRLTTY_INCLUDED_PREFS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  csBottomDots,
+  csAllDots,
+  csLowerLeftDot,
+  csLowerRightDot,
+  csNoDots
+} CursorStyles;
+
+typedef enum {
+  bvComputer8,
+  bvContracted6,
+  bvComputer6,
+  bvContracted8,
+} BrailleVariant;
+
+typedef enum {
+  sbwAll,
+  sbwEndOfLine,
+  sbwRestOfLine
+} SkipBlankWindowsMode;
+
+typedef enum {
+  sayImmediate,
+  sayEnqueue
+} SayMode;
+
+typedef enum {
+  sucNone,
+  sucSayCap,
+  sucRaisePitch
+} SpeechUppercaseIndicator;
+
+typedef enum {
+  swsNone,
+  swsSaySpace
+} SpeechWhitespaceIndicator;
+
+typedef enum {
+  tf24Hour,
+  tf12Hour
+} TimeFormat;
+
+typedef enum {
+  tsColon,
+  tsDot
+} TimeSeparator;
+
+typedef enum {
+  dpNone,
+  dpBeforeTime,
+  dpAfterTime
+} DatePosition;
+
+typedef enum {
+  dfYearMonthDay,
+  dfMonthDayYear,
+  dfDayMonthYear
+} DateFormat;
+
+typedef enum {
+  dsDash,
+  dsSlash,
+  dsDot
+} DateSeparator;
+
+typedef enum {
+  spNone,
+  spLeft,
+  spRight
+} StatusPosition;
+
+typedef enum {
+  ssNone,
+  ssSpace,
+  ssBlock,
+  ssStatusSide,
+  ssTextSide
+} StatusSeparator;
+
+typedef enum {
+  atOff,
+  at5s,
+  at10s,
+  at20s,
+  at40s
+} AutoreleaseTime;
+
+typedef enum {
+  ctdNone,
+  ctd250ms,
+  ctd500ms,
+  ctd1s,
+  ctd2s
+} CursorTrackingDelay;
+
+typedef struct {
+  unsigned char magic[2];
+  unsigned char showScreenCursor;
+  unsigned char version;
+  unsigned char showAttributes;
+  unsigned char touchSensitivity;
+  unsigned char blinkingScreenCursor;
+  unsigned char autorepeatEnabled;
+  unsigned char blinkingCapitals;
+  unsigned char longPressTime;
+  unsigned char blinkingAttributes;
+  unsigned char autorepeatInterval;
+  unsigned char screenCursorStyle;
+  unsigned char sayLineMode;
+  unsigned char screenCursorVisibleTime;
+  unsigned char autospeak;
+  unsigned char screenCursorInvisibleTime;
+  unsigned char pcmVolume;
+  unsigned char capitalsVisibleTime;
+  unsigned char midiVolume;
+  unsigned char capitalsInvisibleTime;
+  unsigned char fmVolume;
+  unsigned char attributesVisibleTime;
+  unsigned char highlightBrailleWindowLocation;
+  unsigned char attributesInvisibleTime;
+  unsigned char trackScreenPointer;
+  unsigned char brailleVariant;
+  unsigned char autorepeatPanning;
+  unsigned char slidingBrailleWindow;
+  unsigned char eagerSlidingBrailleWindow;
+  unsigned char alertTunes;
+  unsigned char tuneDevice;
+  unsigned char skipIdenticalLines;
+  unsigned char alertMessages;
+  unsigned char skipBlankBrailleWindowsMode;
+  unsigned char alertDots;
+  unsigned char skipBlankBrailleWindows;
+  unsigned char midiInstrument;
+  unsigned char expandCurrentWord;
+  unsigned char brailleWindowOverlap;
+  unsigned char speechRate;
+  unsigned char speechVolume;
+  unsigned char brailleFirmness;
+  unsigned char speechPunctuation;
+  unsigned char speechPitch;
+  unsigned char statusPosition;
+  unsigned char statusCount;
+  unsigned char statusSeparator;
+  unsigned char statusFields[10];
+
+  /*****************************************************************************/
+  /* No fields above this point should be added, removed, or reordered so that */
+  /* backward compatibility with old binary preference files will be retained. */
+  /*                                                                           */
+  /* Fields below this point may be modified as desired.                       */
+  /*****************************************************************************/
+
+  unsigned char brailleKeyboardEnabled;
+  unsigned char brailleTypingMode;
+  unsigned char brailleQuickSpace;
+
+  unsigned char wordWrap;
+  unsigned char capitalizationMode;
+  unsigned char startSelectionWithRoutingKey;
+
+  unsigned char speechUppercaseIndicator;
+  unsigned char speechWhitespaceIndicator;
+
+  unsigned char autospeakSelectedLine;
+  unsigned char autospeakSelectedCharacter;
+  unsigned char autospeakInsertedCharacters;
+  unsigned char autospeakDeletedCharacters;
+  unsigned char autospeakReplacedCharacters;
+  unsigned char autospeakCompletedWords;
+  unsigned char autospeakLineIndent;
+
+  unsigned char showSpeechCursor;
+  unsigned char speechCursorStyle;
+  unsigned char blinkingSpeechCursor;
+  unsigned char speechCursorVisibleTime;
+  unsigned char speechCursorInvisibleTime;
+
+  unsigned char timeFormat;
+  unsigned char timeSeparator;
+  unsigned char showSeconds;
+  unsigned char datePosition;
+  unsigned char dateFormat;
+  unsigned char dateSeparator;
+
+  unsigned char consoleBellAlert;
+  unsigned char keyboardLedAlerts;
+
+  unsigned char speakKeyContext;
+  unsigned char speakModifierKey;
+
+  unsigned char autoreleaseTime;
+  unsigned char onFirstRelease;
+  unsigned char touchNavigation;
+
+  unsigned char scrollAwareCursorNavigation;
+  unsigned char cursorTrackingDelay;
+  unsigned char trackScreenScroll;
+
+  unsigned char saveOnExit;
+  unsigned char showSubmenuSizes;
+  unsigned char showAdvancedSubmenus;
+  unsigned char showAllItems;
+} PACKED PreferenceSettings;
+
+extern PreferenceSettings prefs;		/* current preferences settings */
+
+#define PREFS_TIME_MULTIPLIER 10
+#define PREFS2MSECS(time) ((time) * PREFS_TIME_MULTIPLIER)
+#define MSECS2PREFS(msecs) (((msecs) + (PREFS_TIME_MULTIPLIER - 1)) / PREFS_TIME_MULTIPLIER)
+
+typedef struct PreferenceDefinitionStruct PreferenceDefinition;
+extern const PreferenceDefinition *findPreference (const char *name);
+
+extern void resetPreferences (void);
+extern int setPreference (char *string);
+extern void setStatusFields (const unsigned char *fields);
+
+extern char *makePreferencesFilePath (const char *name);
+extern int loadPreferencesFile (const char *path);
+extern int savePreferencesFile (const char *path);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PREFS */
diff --git a/Headers/program.h b/Headers/program.h
new file mode 100644
index 0000000..5af144a
--- /dev/null
+++ b/Headers/program.h
@@ -0,0 +1,63 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PROGRAM
+#define BRLTTY_INCLUDED_PROGRAM
+
+#include "pid.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char standardStreamArgument[];
+extern const char standardInputName[];
+extern const char standardOutputName[];
+extern const char standardErrorName[];
+
+typedef enum {
+  PROG_EXIT_SUCCESS  = 0,
+  PROG_EXIT_FORCE    = 1,
+  PROG_EXIT_SYNTAX   = 2,
+  PROG_EXIT_SEMANTIC = 3,
+  PROG_EXIT_FATAL    = 4
+} ProgramExitStatus;
+
+extern const char *programPath;
+extern const char *programName;
+
+extern void beginProgram (int argumentCount, char **argumentVector);
+extern void endProgram (void);
+
+typedef void ProgramExitHandler (void *data);
+extern void onProgramExit (const char *name, ProgramExitHandler *handler, void *data);
+extern void registerProgramMemory (const char *name, void *pointer);
+
+extern const char *getProgramDirectory(void);
+extern int fixInstallPath (char **path);
+extern char *makeProgramPath(const char *name);
+extern char *makeCommandPath(const char *name);
+
+extern int createPidFile (const char *path, ProcessIdentifier pid);
+extern int cancelProgram (const char *pidFile);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PROGRAM */
diff --git a/Headers/prologue.h b/Headers/prologue.h
new file mode 100644
index 0000000..474810d
--- /dev/null
+++ b/Headers/prologue.h
@@ -0,0 +1,487 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PROLOGUE
+#define BRLTTY_INCLUDED_PROLOGUE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#undef HAVE_BUILTIN_POPCOUNT
+#undef HAVE_SYNC_SYNCHRONIZE
+
+#ifdef __has_builtin
+#if __has_builtin(__builtin_popcount)
+#define HAVE_BUILTIN_POPCOUNT
+#endif /* __has_builtin(__builtin_popcount) */
+
+#if __has_builtin(__sync_synchronize)
+#define HAVE_SYNC_SYNCHRONIZE
+#endif /* __has_builtin(__sync_synchronize) */
+#endif /* __has_builtin */
+
+#ifndef HAVE_SYNC_SYNCHRONIZE
+static inline void __sync_synchronize (void) {}
+#endif /* HAVE_SYNC_SYNCHRONIZE */
+
+#define CONCATENATE_1(a,b) a##b
+#define CONCATENATE(a,b) CONCATENATE_1(a,b)
+
+#define STRINGIFY_1(a) #a
+#define STRINGIFY(a) STRINGIFY_1(a)
+
+// only use in the global context
+#define NULL_TERMINATED_STRING_ARRAY(...) (const char *const []){__VA_ARGS__, NULL}
+
+#define MIN(a, b)  (((a) < (b))? (a): (b)) 
+#define MAX(a, b)  (((a) > (b))? (a): (b)) 
+
+#define ARRAY_COUNT(array) (sizeof((array)) / sizeof((array)[0]))
+#define ARRAY_SIZE(pointer, count) ((count) * sizeof(*(pointer)))
+
+#define IS_WITHIN_RANGE(index,start,end) (((index) >= (start)) && ((index) < (end)))
+#define IS_WITHIN_BOUNDS(index,count) IS_WITHIN_RANGE((index), 0, (count))
+
+#define SYMBOL_TYPE(name) name ## _t
+#define SYMBOL_POINTER(name) static SYMBOL_TYPE(name) *name##_p = NULL;
+
+#define VARIABLE_DECLARATION(variableName, variableType) \
+  variableType variableName
+#define VARIABLE_TYPEDEF(variableName, variableType) \
+  typedef VARIABLE_DECLARATION(SYMBOL_TYPE(variableName), variableType)
+#define VARIABLE_DECLARE(variableName, variableType) \
+  VARIABLE_TYPEDEF(variableName, variableType); \
+  extern SYMBOL_TYPE(variableName) variableName
+
+#define FUNCTION_DECLARATION(functionName, returnType, argumentList) \
+  returnType functionName argumentList
+#define FUNCTION_TYPEDEF(functionName, returnType, argumentList) \
+  typedef FUNCTION_DECLARATION(SYMBOL_TYPE(functionName), returnType, argumentList)
+#define FUNCTION_DECLARE(functionName, returnType, argumentList) \
+  FUNCTION_TYPEDEF(functionName, returnType, argumentList); \
+  extern SYMBOL_TYPE(functionName) functionName
+
+#ifdef HAVE_CONFIG_H
+#ifdef FOR_BUILD
+#include "forbuild.h"
+#else /* FOR_BUILD */
+#include "config.h"
+#endif /* FOR_BUILD */
+#endif /* HAVE_CONFIG_H */
+
+#ifdef __ANDROID__
+#ifndef __ANDROID_API__
+#define __ANDROID_API__ 19
+#endif /* __ANDROID_API__ */
+#endif /* __ANDROID__ */
+
+#if defined(__CYGWIN__) || defined(__MINGW32__)
+#define WINDOWS
+
+#ifndef HAVE_SDKDDKVER_H
+#include <w32api.h>
+
+#ifndef _WIN32_WINNT_NT4
+#define _WIN32_WINNT_NT4 WindowsNT4
+#endif /* _WIN32_WINNT_NT4 */
+
+#ifndef _WIN32_WINNT_WIN95
+#define _WIN32_WINNT_WIN95 Windows95
+#endif /* _WIN32_WINNT_WIN95 */
+
+#ifndef _WIN32_WINNT_WIN98
+#define _WIN32_WINNT_WIN98 Windows98
+#endif /* _WIN32_WINNT_WIN98 */
+
+#ifndef _WIN32_WINNT_WINME
+#define _WIN32_WINNT_WINME WindowsME
+#endif /* _WIN32_WINNT_WINME */
+
+#ifndef _WIN32_WINNT_WIN2K
+#define _WIN32_WINNT_WIN2K Windows2000
+#endif /* _WIN32_WINNT_WIN2K */
+
+#ifndef _WIN32_WINNT_WINXP
+#define _WIN32_WINNT_WINXP WindowsXP
+#endif /* _WIN32_WINNT_WINXP */
+
+#ifndef _WIN32_WINNT_WS03
+#define _WIN32_WINNT_WS03 Windows2003
+#endif /* _WIN32_WINNT_WS03 */
+
+#ifndef _WIN32_WINNT_VISTA
+#define _WIN32_WINNT_VISTA WindowsVista
+#endif /* _WIN32_WINNT_VISTA */
+#endif /* HAVE_SDKDDKVER_H */
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_WINXP
+#endif /* _WIN32_WINNT */
+
+#ifndef WINVER
+#define WINVER _WIN32_WINNT
+#endif /* WINVER */
+
+#ifdef __MINGW32__
+#ifndef __USE_W32_SOCKETS
+#define __USE_W32_SOCKETS
+#endif /* __USE_W32_SOCKETS */
+
+#include <ws2tcpip.h>
+#endif /* __MINGW32__ */
+
+#include <windows.h>
+#endif /* WINDOWS */
+
+#ifdef __MINGW32__
+#include <_mingw.h>
+#endif /* __MINGW32__ */
+
+/*
+ * The (poorly named) macro "interface" is unfortunately defined within
+ * Windows headers. Fortunately, though, it's also only ever used within
+ * them so it's safe to undefine it here.
+ */
+#ifdef interface
+#undef interface
+#endif /* interface */
+
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#ifdef __MINGW32__
+#if (__MINGW32_MAJOR_VERSION < 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION < 10))
+extern int gettimeofday (struct timeval *tvp, void *tzp);
+#endif /* gettimeofday */
+
+#if !defined(__MINGW64_VERSION_MAJOR) && ((__MINGW32_MAJOR_VERSION < 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION < 15)))
+extern void usleep (int usec);
+#endif /* usleep */
+#endif /* __MINGW32__ */
+
+#ifdef GRUB_RUNTIME
+#undef NESTED_FUNC_ATTR
+#define NESTED_FUNC_ATTR __attribute__((__regparm__(1)))
+
+/* missing needed standard integer definitions */
+#define INT16_MAX 0X7FFF
+#define INT32_MAX 0X7FFFFFFF
+#define UINT32_C(i) (i)
+#define PRIuGRUB_UINT16_T "u"
+#define PRIuGRUB_UINT8_T "u"
+
+/* to get gettext() declared */
+#define GRUB_POSIX_GETTEXT_DOMAIN "brltty"
+
+/* disable the use of floating-point operations */
+#define NO_FLOAT
+#define float NO_FLOAT
+#define double NO_DOUBLE
+#endif /* GRUB_RUNHTIME */
+
+#ifdef __MSDOS__
+#include <stdarg.h>
+
+extern int snprintf (char *str, size_t size, const char *format, ...);
+extern int vsnprintf (char *str, size_t size, const char *format, va_list ap);
+
+#define lstat(file_name, buf) stat(file_name, buf)
+#endif /* __MSDOS__ */
+
+#ifdef __MINGW32__
+typedef HANDLE FileDescriptor;
+#define INVALID_FILE_DESCRIPTOR INVALID_HANDLE_VALUE
+#define PRIfd "p"
+#define closeFileDescriptor(fd) CloseHandle(fd)
+
+typedef SOCKET SocketDescriptor;
+#define INVALID_SOCKET_DESCRIPTOR INVALID_SOCKET
+#define PRIsd "d"
+#define closeSocketDescriptor(sd) closesocket(sd)
+#else /* __MINGW32__ */
+typedef int FileDescriptor;
+#define INVALID_FILE_DESCRIPTOR -1
+#define PRIfd "d"
+#define closeFileDescriptor(fd) close(fd)
+
+typedef int SocketDescriptor;
+#define INVALID_SOCKET_DESCRIPTOR -1
+#define PRIsd "d"
+#define closeSocketDescriptor(sd) close(sd)
+#endif /* __MINGW32__ */
+
+#ifdef WINDOWS
+#define getSystemError() GetLastError()
+
+#ifdef __CYGWIN__
+#include <sys/cygwin.h>
+
+#define getSocketError() errno
+#define setErrno(error) errno = cygwin_internal(CW_GET_ERRNO_FROM_WINERROR, (error))
+#else /* __CYGWIN__ */
+#define getSocketError() WSAGetLastError()
+#define setErrno(error) errno = win_toErrno((error))
+
+#ifndef WIN_ERRNO_STORAGE_CLASS
+#define WIN_ERRNO_STORAGE_CLASS
+extern
+#endif /* WIN_ERRNO_STORAGE_CLASS */
+WIN_ERRNO_STORAGE_CLASS int win_toErrno (DWORD error);
+#endif /* __CYGWIN__ */
+
+#else /* WINDOWS */
+#define getSystemError() errno
+#define getSocketError() errno
+
+#define setErrno(error)
+#endif /* WINDOWS */
+
+#define setSystemErrno() setErrno(getSystemError())
+#define setSocketErrno() setErrno(getSocketError())
+
+#if defined(__MINGW32__)
+#define PRIsize "u"
+#define PRIssize "d"
+
+#else /* format for size_t and ssize_t */
+#define PRIsize "zu"
+#define PRIssize "zd"
+#endif /* format for size_t and ssize_t */
+
+#if defined(__CYGWIN__)
+#define PRIkey "llX"
+#elif defined(__FreeBSD__)
+#define PRIkey "lX"
+#elif defined(__OpenBSD__)
+#define PRIkey "lX"
+#else /* format for key_t */
+#define PRIkey PRIX32
+#endif /* format for key_t */
+
+#if defined(__MSDOS__)
+#undef WCHAR_MAX
+
+#elif defined(HAVE_WCHAR_H)
+#include <wchar.h>
+#include <wctype.h>
+
+#endif /* HAVE_WCHAR_H */
+
+#ifdef WCHAR_MAX
+#define WC_C(wc) L##wc
+#define WS_C(ws) L##ws
+#define PRIwc "lc"
+#define PRIws "ls"
+#define iswLatin1(wc) ((wc) < 0X100)
+#else /* HAVE_WCHAR_H */
+#include <ctype.h>
+#include <string.h>
+
+#define wchar_t unsigned char
+#define wint_t int
+
+#define WEOF EOF
+#define WCHAR_MAX UINT8_MAX
+
+#define wmemchr(source,character,count) memchr((const char *)(source), (char)(character), (count))
+#define wmemcmp(source1,source2,count) memcmp((const char *)(source1), (const char *)(source2), (count))
+#define wmemcpy(target,source,count) memcpy((char *)(target), (const char *)(source), (count))
+#define wmemmove(target,source,count) memmove((char *)(target), (const char *)(source), (count))
+#define wmemset(target,character,count) memset((char *)(target), (char)(character), (count))
+
+#define wcscasecmp(source1,source2) strcasecmp((const char *)(source1), (const char *)(source2))
+#define wcsncasecmp(source1,source2,count) strncasecmp((const char *)(source1), (const char *)(source2), (count))
+#define wcscat(target,source) strcat((char *)(target), (const char *)(source))
+#define wcsncat(target,source,count) strncat((char *)(target), (const char *)(source), (count))
+#define wcscmp(source1,source2) strcmp((const char *)(source1), (const char *)(source2))
+#define wcsncmp(source1,source2,count) strncmp((const char *)(source1), (const char *)(source2), (count))
+#define wcscpy(target,source) strcpy((char *)(target), (const char *)(source))
+#define wcsncpy(target,source,count) strncpy((char *)(target), (const char *)(source), (count))
+#define wcslen(source) strlen((const char *)(source))
+#define wcsnlen(source,count) strnlen((const char *)(source), (count))
+
+#define wcschr(source,character) ((wchar_t *)strchr((const char *)(source), (char)(character)))
+#define wcscoll(source1,source2) strcoll((const char *)(source1), (const char *)(source2))
+#define wcscspn(source,reject) strcspn((const char *)(source), (const char *)(reject))
+#define wcsdup(source) strdup((const char *)(source))
+#define wcspbrk(source,accept) strpbrk((const char *)(source), (const char *)(accept))
+#define wcsrchr(source,character) strrchr((const char *)(source), (char)(character))
+#define wcsspn(source,accept) strspn((const char *)(source), (const char *)(accept))
+#define wcsstr(source,substring) strstr((const char *)(source), (const char *)(substring))
+#define wcstok(target,delimiters,end) ((wchar_t *)strtok(((char *)(target)), ((const char *)(delimiters))))
+#define wcswcs(source,substring) strstr((const char *)(source), (const char *)(substring))
+#define wcsxfrm(target,source,count) strxfrm((char *)(target), (const char *)(source), (count))
+#define wcstoul(nptr, endptr, base) strtoul(((const char *)(nptr)), ((char **)(endptr)), (base))
+
+#define wcstol(source,end,base) strtol((const char *)(source), (char **)(end), (base))
+#define wcstoll(source,end,base) strtoll((const char *)(source), (char **)(end), (base))
+
+#define iswalnum(character) isalnum((int)(character))
+#define iswalpha(character) isalpha((int)(character))
+#define iswblank(character) isblank((int)(character))
+#define iswcntrl(character) iscntrl((int)(character))
+#define iswdigit(character) isdigit((int)(character))
+#define iswgraph(character) isgraph((int)(character))
+#define iswlower(character) islower((int)(character))
+#define iswprint(character) isprint((int)(character))
+#define iswpunct(character) ispunct((int)(character))
+#define iswspace(character) isspace((int)(character))
+#define iswupper(character) isupper((int)(character))
+#define iswxdigit(character) isxdigit((int)(character))
+
+#define towlower(character) tolower((int)(character))
+#define towupper(character) toupper((int)(character))
+
+#define swprintf(target,count,source,...) snprintf((char *)(target), (count), (const char *)(source), ## __VA_ARGS__)
+#define vswprintf(target,count,source,args) vsnprintf((char *)(target), (count), (const char *)(source), (args))
+
+typedef unsigned char mbstate_t;
+
+static inline size_t
+mbrtowc (wchar_t *pwc, const char *s, size_t n, mbstate_t *ps) {
+  if (!s) return 0;
+  if (!n) return 0;
+  if (pwc) *pwc = *s & 0XFF;
+  if (!*s) return 0;
+  return 1;
+}
+
+static inline size_t
+wcrtomb (char *s, wchar_t wc, mbstate_t *ps) {
+  if (s) *s = wc;
+  return 1;
+}
+
+static inline int
+mbsinit (const mbstate_t *ps) {
+  return 1;
+}
+
+#define WC_C(wc) ((wchar_t)wc)
+#define WS_C(ws) ((const wchar_t *)ws)
+#define PRIwc "c"
+#define PRIws "s"
+#define iswLatin1(wc) (1)
+#endif /* HAVE_WCHAR_H */
+
+#ifdef WORDS_BIGENDIAN
+#define CHARSET_ENDIAN_SUFFIX "BE"
+#else /* WORDS_BIGENDIAN */
+#define CHARSET_ENDIAN_SUFFIX "LE"
+#endif /* WORDS_BIGENDIAN */
+
+#define WCHAR_CHARSET ("UCS-" SIZEOF_WCHAR_T_STR CHARSET_ENDIAN_SUFFIX)
+
+#ifndef HAVE_MEMPCPY
+static inline void *
+mempcpy (void *dest, const void *src, size_t size) {
+  extern void *memcpy (void *dest, const void *src, size_t size);
+  char *address = memcpy(dest, src, size);
+  return address + size;
+}
+#endif /* HAVE_MEMPCPY */
+
+#ifndef HAVE_WMEMPCPY
+#define wmempcpy(dest,src,count) (wmemcpy((dest), (src), (count)) + (count))
+#endif /* HAVE_WMEMPCPY */
+
+#ifndef WRITABLE_DIRECTORY
+#define WRITABLE_DIRECTORY ""
+#endif /* WRITABLE_DIRECTORY */
+
+#ifdef HAVE_VAR_ATTRIBUTE_PACKED
+#define PACKED __attribute__((packed))
+#else /* HAVE_VAR_ATTRIBUTE_PACKED */
+#define PACKED
+#endif /* HAVE_VAR_ATTRIBUTE_PACKED */
+
+#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT
+#define PRINTF(fmt,var) __attribute__((format(__printf__, fmt, var)))
+#else /* HAVE_FUNC_ATTRIBUTE_FORMAT */
+#define PRINTF(fmt,var)
+#endif /* HAVE_FUNC_ATTRIBUTE_FORMAT */
+
+#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT_ARG
+#define FORMAT_ARG(n) __attribute__((format_arg((n))))
+#else /* HAVE_FUNC_ATTRIBUTE_FORMAT_ARG */
+#define FORMAT_ARG(n)
+#endif /* HAVE_FUNC_ATTRIBUTE_FORMAT_ARG */
+
+#ifdef HAVE_FUNC_ATTRIBUTE_NORETURN
+#define NORETURN __attribute__((noreturn))
+#else /* HAVE_FUNC_ATTRIBUTE_NORETURN */
+#define NORETURN
+#endif /* HAVE_FUNC_ATTRIBUTE_NORETURN */
+
+#ifdef HAVE_FUNC_ATTRIBUTE_UNUSED
+#define UNUSED __attribute__((unused))
+#else /* HAVE_FUNC_ATTRIBUTE_UNUSED */
+#define UNUSED
+#endif /* HAVE_FUNC_ATTRIBUTE_UNUSED */
+
+#ifdef ENABLE_I18N_SUPPORT
+#include <libintl.h>
+#else /* ENABLE_I18N_SUPPORT */
+extern char *gettext (const char *text) FORMAT_ARG(1);
+
+extern char *ngettext (
+  const char *singular, const char *plural, unsigned long int count
+) FORMAT_ARG(1) FORMAT_ARG(2);
+#endif /* ENABLE_I18N_SUPPORT */
+#define strtext(string) string
+
+#ifndef USE_PKG_BEEP_NONE
+#define HAVE_BEEP_SUPPORT
+#endif /* USE_PKG_BEEP_NONE */
+
+#ifndef USE_PKG_PCM_NONE
+#define HAVE_PCM_SUPPORT
+#endif /* USE_PKG_PCM_NONE */
+
+#ifndef USE_PKG_MIDI_NONE
+#define HAVE_MIDI_SUPPORT
+#endif /* USE_PKG_MIDI_NONE */
+
+#ifndef USE_PKG_FM_NONE
+#define HAVE_FM_SUPPORT
+#endif /* USE_PKG_FM_NONE */
+
+/* configure is still making a few mistakes with respect to the grub environment */
+#ifdef GRUB_RUNTIME
+
+/* some headers exist but probably shouldn't */
+#undef HAVE_SIGNAL_H
+
+/* including <time.h> on its own yields compile errors */
+#undef HAVE_DECL_LOCALTIME_R
+#define HAVE_DECL_LOCALTIME_R 1
+
+/* AC_CHECK_FUNC() is checking local libraries - these are the errors that matter */
+#undef HAVE_FCHDIR
+#undef HAVE_SELECT
+#endif /* GRUB_RUNTIME */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PROLOGUE */
diff --git a/Headers/pty_object.h b/Headers/pty_object.h
new file mode 100644
index 0000000..930cbc1
--- /dev/null
+++ b/Headers/pty_object.h
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PTY_OBJECT
+#define BRLTTY_INCLUDED_PTY_OBJECT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct PtyObjectStruct PtyObject;
+
+extern PtyObject *ptyNewObject(void);
+extern void ptyDestroyObject(PtyObject *pty);
+
+extern const char *ptyGetPath(const PtyObject *pty);
+extern int ptyGetMaster(const PtyObject *pty);
+
+extern void ptySetLogLevel(PtyObject *pty, unsigned char level);
+extern void ptySetLogInput(PtyObject *pty, int yes);
+
+extern int ptyWriteInputData(PtyObject *pty, const void *data, size_t length);
+extern int ptyWriteInputCharacter(PtyObject *pty, wchar_t character,
+                                  int kxMode);
+
+extern void ptyCloseMaster(PtyObject *pty);
+extern int ptyOpenSlave(const PtyObject *pty, int *fileDescriptor);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PTY_OBJECT */
diff --git a/Headers/pty_screen.h b/Headers/pty_screen.h
new file mode 100644
index 0000000..1f895aa
--- /dev/null
+++ b/Headers/pty_screen.h
@@ -0,0 +1,80 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PTY_SCREEN
+#define BRLTTY_INCLUDED_PTY_SCREEN
+
+#include "get_curses.h"
+#include "pty_object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int ptyBeginScreen(PtyObject *pty, int driverDirectives);
+extern void ptyEndScreen(void);
+extern void ptyRefreshScreen(void);
+
+extern void ptySetCursorPosition(unsigned int row, unsigned int column);
+extern void ptySetCursorRow(unsigned int row);
+extern void ptySetCursorColumn(unsigned int column);
+
+extern void ptySaveCursorPosition(void);
+extern void ptyRestoreCursorPosition(void);
+
+extern void ptySetScrollRegion(unsigned int top, unsigned int bottom);
+extern int ptyAmWithinScrollRegion(void);
+extern void ptyScrollDown(unsigned int count);
+extern void ptyScrollUp(unsigned int count);
+
+extern void ptyMoveCursorUp(unsigned int amount);
+extern void ptyMoveCursorDown(unsigned int amount);
+extern void ptyMoveCursorLeft(unsigned int amount);
+extern void ptyMoveCursorRight(unsigned int amount);
+
+extern void ptyMoveUp1(void);
+extern void ptyMoveDown1(void);
+
+extern void ptyTabBackward(void);
+extern void ptyTabForward(void);
+
+extern void ptyInsertLines(unsigned int count);
+extern void ptyDeleteLines(unsigned int count);
+
+extern void ptyInsertCharacters(unsigned int count);
+extern void ptyDeleteCharacters(unsigned int count);
+extern void ptyAddCharacter(unsigned char character);
+
+extern void ptySetCursorVisibility(unsigned int visibility);
+extern void ptySetAttributes(attr_t attributes);
+extern void ptyAddAttributes(attr_t attributes);
+extern void ptyRemoveAttributes(attr_t attributes);
+extern void ptySetForegroundColor(int color);
+extern void ptySetBackgroundColor(int color);
+
+extern void ptyClearToEndOfLine(void);
+extern void ptyClearToBeginningOfLine(void);
+extern void ptyClearToEndOfDisplay(void);
+
+extern void ptySetScreenLogLevel(unsigned char level);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PTY_SCREEN */
diff --git a/Headers/pty_terminal.h b/Headers/pty_terminal.h
new file mode 100644
index 0000000..80e2042
--- /dev/null
+++ b/Headers/pty_terminal.h
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PTY_TERMINAL
+#define BRLTTY_INCLUDED_PTY_TERMINAL
+
+#include "pty_object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *ptyGetTerminalType(void);
+
+extern int ptyBeginTerminal(PtyObject *pty, int driverDirectives);
+extern void ptyEndTerminal(void);
+
+extern int ptyProcessTerminalInput(PtyObject *pty);
+extern int ptyProcessTerminalOutput(const unsigned char *bytes, size_t count);
+
+extern void ptySetTerminalLogLevel(unsigned char level);
+extern void ptySetLogTerminalInput(int yes);
+extern void ptySetLogTerminalOutput(int yes);
+extern void ptySetLogTerminalSequences(int yes);
+extern void ptySetLogUnexpectedTerminalIO(int yes);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PTY_TERMINAL */
diff --git a/Headers/queue.h b/Headers/queue.h
new file mode 100644
index 0000000..5194890
--- /dev/null
+++ b/Headers/queue.h
@@ -0,0 +1,75 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_QUEUE
+#define BRLTTY_INCLUDED_QUEUE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct QueueStruct Queue;
+typedef struct ElementStruct Element;
+typedef void ItemDeallocator (void *item, void *data);
+typedef int ItemComparator (const void *newItem, const void *existingItem, void *queueData);
+
+extern Queue *newQueue (ItemDeallocator *deallocateItem, ItemComparator *compareItems);
+extern void deallocateQueue (Queue *queue);
+
+typedef Queue *QueueCreator (void *data);
+extern Queue *getProgramQueue (
+  Queue **queue, const char *name, int create,
+  QueueCreator *createQueue, void *data
+);
+
+extern int getQueueSize (const Queue *queue);
+extern void *getQueueData (const Queue *queue);
+extern void *setQueueData (Queue *queue, void *data);
+
+extern Element *getQueueHead (const Queue *queue);
+extern Element *getQueueElement (const Queue *queue, unsigned int index);
+
+extern Element *getStackHead (const Queue *queue);
+extern Element *getStackElement (const Queue *queue, unsigned int index);
+
+extern Element *enqueueItem (Queue *queue, void *item);
+extern void *dequeueItem (Queue *queue);
+extern int deleteItem (Queue *queue, void *item);
+
+extern Queue *getElementQueue (const Element *element);
+extern int getElementIdentifier (const Element *element);
+extern void *getElementItem (const Element *element);
+
+extern void deleteElements (Queue *queue);
+extern void deleteElement (Element *element);
+extern void requeueElement (Element *element);
+extern void moveElement (Element *element, Queue *queue);
+
+typedef int ItemTester (const void *item, void *data);
+extern Element *findElement (const Queue *queue, ItemTester *testItem, void *data);
+extern void *findItem (const Queue *queue, ItemTester *testItem, void *data);
+extern Element *findElementWithItem (const Queue *queue, void *item);
+
+typedef int ItemProcessor (void *item, void *data);
+extern Element *processQueue (Queue *queue, ItemProcessor *processItem, void *data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_QUEUE */
diff --git a/Headers/revision.h b/Headers/revision.h
new file mode 100644
index 0000000..8269077
--- /dev/null
+++ b/Headers/revision.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_REVISION
+#define BRLTTY_INCLUDED_REVISION
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *getRevisionIdentifier (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_REVISION */
diff --git a/Headers/rgx.h b/Headers/rgx.h
new file mode 100644
index 0000000..8b9c0af
--- /dev/null
+++ b/Headers/rgx.h
@@ -0,0 +1,162 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_RGX
+#define BRLTTY_INCLUDED_RGX
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct RGX_ObjectStruct RGX_Object;
+typedef struct RGX_MatcherStruct RGX_Matcher;
+
+extern RGX_Object *rgxNewObject (void *data);
+extern void rgxDestroyObject (RGX_Object *rgx);
+
+typedef struct {
+  const RGX_Matcher *matcher;
+
+  struct {
+    const wchar_t *characters;
+    size_t length;
+  } pattern;
+
+  struct {
+    void *internal;
+    const wchar_t *characters;
+    size_t length;
+  } text;
+
+  struct {
+    size_t count;
+  } capture;
+
+  struct {
+    void *object;
+    void *pattern;
+    void *match;
+  } data;
+} RGX_Match;
+
+#define RGX_MATCH_HANDLER(name) int name (const RGX_Match *match)
+typedef RGX_MATCH_HANDLER(RGX_MatchHandler);
+
+extern RGX_Matcher *rgxAddPatternCharacters (
+  RGX_Object *rgx,
+  const wchar_t *characters, size_t length,
+  RGX_MatchHandler *handler, void *data
+);
+
+extern RGX_Matcher *rgxAddPatternString (
+  RGX_Object *rgx,
+  const wchar_t *string,
+  RGX_MatchHandler *handler, void *data
+);
+
+extern RGX_Matcher *rgxAddPatternUTF8 (
+  RGX_Object *rgx,
+  const char *string,
+  RGX_MatchHandler *handler, void *data
+);
+
+extern RGX_Matcher *rgxMatchTextCharacters (
+  RGX_Object *rgx,
+  const wchar_t *characters, size_t length,
+  RGX_Match **result, void *data
+);
+
+extern RGX_Matcher *rgxMatchTextString (
+  RGX_Object *rgx,
+  const wchar_t *string,
+  RGX_Match **result, void *data
+);
+
+extern RGX_Matcher *rgxMatchTextUTF8 (
+  RGX_Object *rgx,
+  const char *string,
+  RGX_Match **result, void *data
+);
+
+extern int rgxGetNameNumberCharacters (
+  const RGX_Matcher *matcher,
+  const wchar_t *characters, size_t length,
+  size_t *number
+);
+
+extern int rgxGetNameNumberString (
+  const RGX_Matcher *matcher,
+  const wchar_t *string,
+  size_t *number
+);
+
+extern int rgxGetNameNumberUTF8 (
+  const RGX_Matcher *matcher,
+  const char *string,
+  size_t *number
+);
+
+extern size_t rgxGetCaptureCount (
+  const RGX_Match *match
+);
+
+extern int rgxGetCaptureBounds (
+  const RGX_Match *match,
+  size_t number, size_t *from, size_t *to
+);
+
+extern int rgxGetCaptureText (
+  const RGX_Match *match,
+  size_t number, const wchar_t **characters, size_t *length
+);
+
+typedef enum {
+  RGX_OPTION_CLEAR,
+  RGX_OPTION_SET,
+  RGX_OPTION_TOGGLE,
+  RGX_OPTION_TEST
+} RGX_OptionAction;
+
+typedef enum {
+  RGX_COMPILE_ANCHOR_START,
+
+  RGX_COMPILE_IGNORE_CASE,
+  RGX_COMPILE_UNICODE_PROPERTIES,
+} RGX_CompileOption;
+
+extern int rgxCompileOption (
+  RGX_Object *rgx,
+  RGX_OptionAction action,
+  RGX_CompileOption option
+);
+
+typedef enum {
+  RGX_MATCH_ANCHOR_START,
+} RGX_MatchOption;
+
+extern int rgxMatchOption (
+  RGX_Matcher *matcher,
+  RGX_OptionAction action,
+  RGX_MatchOption option
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_RGX */
diff --git a/Headers/scr_base.h b/Headers/scr_base.h
new file mode 100644
index 0000000..8cb26dd
--- /dev/null
+++ b/Headers/scr_base.h
@@ -0,0 +1,74 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_BASE
+#define BRLTTY_INCLUDED_SCR_BASE
+
+#include "scr_types.h"
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int isSpecialKey (ScreenKey key);
+extern void setScreenKeyModifiers (ScreenKey *key, ScreenKey which);
+extern void mapScreenKey (ScreenKey *key);
+
+typedef struct {
+  const char * (*getTitle) (void);
+
+  void (*onForeground) (void);
+  void (*onBackground) (void);
+
+  int (*poll) (void);
+  int (*refresh) (void);
+  void (*describe) (ScreenDescription *);
+
+  int (*readCharacters) (const ScreenBox *box, ScreenCharacter *buffer);
+  int (*insertKey) (ScreenKey key);
+  int (*routeCursor) (int column, int row, int screen);
+
+  int (*highlightRegion) (int left, int right, int top, int bottom);
+  int (*unhighlightRegion) (void);
+  int (*getPointer) (int *column, int *row);
+
+  int (*clearSelection) (void);
+  int (*setSelection) (int startColumn, int startRow, int endColumn, int endRow);
+
+  int (*currentVirtualTerminal) (void);
+  int (*selectVirtualTerminal) (int vt);
+  int (*switchVirtualTerminal) (int vt);
+  int (*nextVirtualTerminal) (void);
+  int (*previousVirtualTerminal) (void);
+
+  int (*handleCommand) (int command);
+  KeyTableCommandContext (*getCommandContext) (void);
+} BaseScreen;
+
+extern void initializeBaseScreen (BaseScreen *);
+extern void describeBaseScreen (BaseScreen *, ScreenDescription *);
+
+extern int validateScreenBox (const ScreenBox *box, int columns, int rows);
+extern void setScreenMessage (const ScreenBox *box, ScreenCharacter *buffer, const char *message);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_BASE */
diff --git a/Headers/scr_driver.h b/Headers/scr_driver.h
new file mode 100644
index 0000000..88c96f9
--- /dev/null
+++ b/Headers/scr_driver.h
@@ -0,0 +1,63 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_DRIVER
+#define BRLTTY_INCLUDED_SCR_DRIVER
+
+#include "scr_utils.h"
+#include "scr_real.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Routines provided by this screen driver. */
+static void scr_initialize (MainScreen *main);
+
+#ifdef SCRPARMS
+  static const char *const scr_parameters[] = {SCRPARMS, NULL};
+#endif /* SCRPARMS */
+
+#ifndef SCRSYMBOL
+#  define SCRSYMBOL CONCATENATE(scr_driver_,DRIVER_CODE)
+#endif /* SCRSYMBOL */
+
+#ifndef SCRCONST
+#  define SCRCONST const
+#endif /* SCRCONST */
+
+extern SCRCONST ScreenDriver SCRSYMBOL;
+SCRCONST ScreenDriver SCRSYMBOL = {
+  DRIVER_DEFINITION_INITIALIZER,
+
+#ifdef SCRPARMS
+  scr_parameters,
+#else /* SCRPARMS */
+  NULL,
+#endif /* SCRPARMS */
+
+  scr_initialize
+};
+
+DRIVER_VERSION_DECLARATION(scr);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_DRIVER */
diff --git a/Headers/scr_emulator.h b/Headers/scr_emulator.h
new file mode 100644
index 0000000..d912aec
--- /dev/null
+++ b/Headers/scr_emulator.h
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_EMULATOR
+#define BRLTTY_INCLUDED_SCR_EMULATOR
+
+#include "scr_terminal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void moveScreenCharacters(ScreenSegmentCharacter *to,
+                                 const ScreenSegmentCharacter *from,
+                                 size_t count);
+extern void setScreenCharacters(ScreenSegmentCharacter *from,
+                                const ScreenSegmentCharacter *to,
+                                const ScreenSegmentCharacter *character);
+extern void propagateScreenCharacter(ScreenSegmentCharacter *from,
+                                     const ScreenSegmentCharacter *to);
+
+#define SCREEN_SEGMENT_COLOR(r, g, b) \
+  { .red = r, .green = g, .blue = b }
+#define SCREEN_SEGMENT_COLOR_LEVEL 0XAA
+#define SCREEN_SEGMENT_COLOR_BLACK SCREEN_SEGMENT_COLOR(0, 0, 0)
+#define SCREEN_SEGMENT_COLOR_WHITE                                             \
+  SCREEN_SEGMENT_COLOR(SCREEN_SEGMENT_COLOR_LEVEL, SCREEN_SEGMENT_COLOR_LEVEL, \
+                       SCREEN_SEGMENT_COLOR_LEVEL)
+
+extern void fillScreenRows(ScreenSegmentHeader *segment, unsigned int row,
+                           unsigned int count,
+                           const ScreenSegmentCharacter *character);
+extern void moveScreenRows(ScreenSegmentHeader *segment, unsigned int from,
+                           unsigned int to, unsigned int count);
+extern void scrollScreenRows(ScreenSegmentHeader *segment, unsigned int top,
+                             unsigned int size, unsigned int count, int down);
+
+extern ScreenSegmentHeader *createScreenSegment(int *identifier, key_t key,
+                                                int columns, int rows,
+                                                int enableRowArray);
+extern int destroyScreenSegment(int identifier);
+
+extern int createMessageQueue(int *queue, key_t key);
+extern int destroyMessageQueue(int queue);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_EMULATOR */
diff --git a/Headers/scr_gpm.h b/Headers/scr_gpm.h
new file mode 100644
index 0000000..f4e23ee
--- /dev/null
+++ b/Headers/scr_gpm.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_GPM
+#define BRLTTY_INCLUDED_SCR_GPM
+
+#include "scr_main.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void gpmIncludeScreenHandlers (MainScreen *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_GPM */
diff --git a/Headers/scr_main.h b/Headers/scr_main.h
new file mode 100644
index 0000000..9f3a06f
--- /dev/null
+++ b/Headers/scr_main.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_MAIN
+#define BRLTTY_INCLUDED_SCR_MAIN
+
+#include "scr_base.h"
+#include "driver.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  BaseScreen base;
+
+  int (*processParameters) (char **parameters);
+  void (*releaseParameters) (void);
+
+  int (*construct) (void);
+  void (*destruct) (void);
+
+  int (*userVirtualTerminal) (int number);
+} MainScreen;
+
+extern void initializeMainScreen (MainScreen *);
+
+struct ScreenDriverStruct {
+  DRIVER_DEFINITION_DECLARATION;
+  const char *const *parameters;
+
+  void (*initialize) (MainScreen *main);		/* initialize speech device */
+};
+
+extern void mainScreenUpdated (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_MAIN */
diff --git a/Headers/scr_real.h b/Headers/scr_real.h
new file mode 100644
index 0000000..351b300
--- /dev/null
+++ b/Headers/scr_real.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_REAL
+#define BRLTTY_INCLUDED_SCR_REAL
+
+#include "scr_main.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void initializeRealScreen (MainScreen *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_REAL */
diff --git a/Headers/scr_terminal.h b/Headers/scr_terminal.h
new file mode 100644
index 0000000..0245a35
--- /dev/null
+++ b/Headers/scr_terminal.h
@@ -0,0 +1,118 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_TERMINAL
+#define BRLTTY_INCLUDED_SCR_TERMINAL
+
+#include <sys/ipc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int makeTerminalKey(key_t *key, const char *path);
+
+typedef enum {
+  TERM_MSG_INPUT_TEXT = 't',        // driver->emulator - UTF-8
+  TERM_MSG_SEGMENT_UPDATED = 'u',   // emulator->driver - no content
+  TERM_MSG_EMULATOR_EXITING = 'x',  // emulator->driver - no content
+} TerminalMessageType;
+
+extern int getMessageQueue(int *queue, key_t key);
+
+typedef struct {
+  uint8_t red;
+  uint8_t green;
+  uint8_t blue;
+} ScreenSegmentColor;
+
+typedef struct {
+  uint32_t text;
+
+  ScreenSegmentColor foreground;
+  ScreenSegmentColor background;
+  uint8_t alpha;
+
+  unsigned char blink : 1;
+  unsigned char underline : 1;
+} ScreenSegmentCharacter;
+
+typedef struct {
+  uint32_t charactersOffset;
+} ScreenSegmentRow;
+
+typedef struct {
+  uint32_t headerSize;
+  uint32_t segmentSize;
+
+  uint32_t screenHeight;
+  uint32_t screenWidth;
+
+  uint32_t cursorRow;
+  uint32_t cursorColumn;
+
+  uint32_t screenNumber;
+  uint32_t commonFlags;
+  uint32_t privateFlags;
+
+  uint32_t rowsOffset;
+  uint32_t rowSize;
+
+  uint32_t charactersOffset;
+  uint32_t characterSize;
+} ScreenSegmentHeader;
+
+extern int getScreenSegment(int *identifier, key_t key);
+extern ScreenSegmentHeader *attachScreenSegment(int identifier);
+extern int detachScreenSegment(ScreenSegmentHeader *segment);
+
+extern ScreenSegmentHeader *getScreenSegmentForKey(key_t key);
+extern ScreenSegmentHeader *getScreenSegmentForPath(const char *path);
+extern void logScreenSegment(const ScreenSegmentHeader *segment);
+
+static inline int haveScreenRowArray(const ScreenSegmentHeader *segment) {
+  return !!segment->rowsOffset;
+}
+
+static inline unsigned int getScreenRowWidth(
+    const ScreenSegmentHeader *segment) {
+  return segment->screenWidth * segment->characterSize;
+}
+
+static inline unsigned int getScreenCharacterCount(
+    const ScreenSegmentHeader *segment) {
+  return segment->screenWidth * segment->screenHeight;
+}
+
+extern void *getScreenItem(ScreenSegmentHeader *segment, uint32_t offset);
+extern ScreenSegmentRow *getScreenRowArray(ScreenSegmentHeader *segment);
+extern ScreenSegmentCharacter *getScreenCharacterArray(
+    ScreenSegmentHeader *segment, const ScreenSegmentCharacter **end);
+
+extern ScreenSegmentCharacter *getScreenRow(ScreenSegmentHeader *segment,
+                                            unsigned int row,
+                                            const ScreenSegmentCharacter **end);
+extern ScreenSegmentCharacter *getScreenCharacter(
+    ScreenSegmentHeader *segment, unsigned int row, unsigned int column,
+    const ScreenSegmentCharacter **end);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_TERMINAL */
diff --git a/Headers/scr_types.h b/Headers/scr_types.h
new file mode 100644
index 0000000..9d6818f
--- /dev/null
+++ b/Headers/scr_types.h
@@ -0,0 +1,165 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_TYPES
+#define BRLTTY_INCLUDED_SCR_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  SCR_ATTR_FG_BLUE   = 0X01,
+  SCR_ATTR_FG_GREEN  = 0X02,
+  SCR_ATTR_FG_RED    = 0X04,
+  SCR_ATTR_FG_BRIGHT = 0X08,
+  SCR_ATTR_BG_BLUE   = 0X10,
+  SCR_ATTR_BG_GREEN  = 0X20,
+  SCR_ATTR_BG_RED    = 0X40,
+  SCR_ATTR_BLINK     = 0X80,
+
+  SCR_MASK_FG = SCR_ATTR_FG_RED | SCR_ATTR_FG_GREEN | SCR_ATTR_FG_BLUE | SCR_ATTR_FG_BRIGHT,
+  SCR_MASK_BG = SCR_ATTR_BG_RED | SCR_ATTR_BG_GREEN | SCR_ATTR_BG_BLUE,
+
+  SCR_COLOUR_FG_BLACK          = 0,
+  SCR_COLOUR_FG_BLUE           = SCR_ATTR_FG_BLUE,
+  SCR_COLOUR_FG_GREEN          = SCR_ATTR_FG_GREEN,
+  SCR_COLOUR_FG_CYAN           = SCR_ATTR_FG_GREEN | SCR_ATTR_FG_BLUE,
+  SCR_COLOUR_FG_RED            = SCR_ATTR_FG_RED,
+  SCR_COLOUR_FG_MAGENTA        = SCR_ATTR_FG_RED | SCR_ATTR_FG_BLUE,
+  SCR_COLOUR_FG_BROWN          = SCR_ATTR_FG_RED | SCR_ATTR_FG_GREEN,
+  SCR_COLOUR_FG_LIGHT_GREY     = SCR_ATTR_FG_RED | SCR_ATTR_FG_GREEN | SCR_ATTR_FG_BLUE,
+  SCR_COLOUR_FG_DARK_GREY      = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_BLACK,
+  SCR_COLOUR_FG_LIGHT_BLUE     = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_BLUE,
+  SCR_COLOUR_FG_LIGHT_GREEN    = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_GREEN,
+  SCR_COLOUR_FG_LIGHT_CYAN     = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_CYAN,
+  SCR_COLOUR_FG_LIGHT_RED      = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_RED,
+  SCR_COLOUR_FG_LIGHT_MAGENTA  = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_MAGENTA,
+  SCR_COLOUR_FG_YELLOW         = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_BROWN,
+  SCR_COLOUR_FG_WHITE          = SCR_ATTR_FG_BRIGHT | SCR_COLOUR_FG_LIGHT_GREY,
+
+  SCR_COLOUR_BG_BLACK      = 0,
+  SCR_COLOUR_BG_BLUE       = SCR_ATTR_BG_BLUE,
+  SCR_COLOUR_BG_GREEN      = SCR_ATTR_BG_GREEN,
+  SCR_COLOUR_BG_CYAN       = SCR_ATTR_BG_GREEN | SCR_ATTR_BG_BLUE,
+  SCR_COLOUR_BG_RED        = SCR_ATTR_BG_RED,
+  SCR_COLOUR_BG_MAGENTA    = SCR_ATTR_BG_RED | SCR_ATTR_BG_BLUE,
+  SCR_COLOUR_BG_BROWN      = SCR_ATTR_BG_RED | SCR_ATTR_BG_GREEN,
+  SCR_COLOUR_BG_LIGHT_GREY = SCR_ATTR_BG_RED | SCR_ATTR_BG_GREEN | SCR_ATTR_BG_BLUE,
+
+  SCR_COLOUR_DEFAULT = SCR_COLOUR_FG_LIGHT_GREY | SCR_COLOUR_BG_BLACK
+} ScreenAttributes;
+
+typedef struct {
+  wchar_t text;
+  ScreenAttributes attributes;
+} ScreenCharacter;
+
+typedef enum {
+  SCQ_NONE = 0,
+  SCQ_LOW,
+  SCQ_POOR,
+  SCQ_FAIR,
+  SCQ_GOOD,
+  SCQ_HIGH,
+} ScreenContentQuality;
+
+typedef struct {
+  const char *unreadable;
+  ScreenContentQuality quality;
+
+  int number;		      /* screen number */
+  short cols, rows;	/* screen dimensions */
+  short posx, posy;	/* cursor position */
+
+  unsigned char hasCursor:1;
+  unsigned char hasSelection:1;
+} ScreenDescription;
+
+typedef struct {
+  short left, top;	/* top-left corner (offset from 0) */
+  short width, height;	/* dimensions */
+} ScreenBox;
+
+#define SCR_KEY_SHIFT     0X40000000
+#define SCR_KEY_UPPER     0X20000000
+#define SCR_KEY_CONTROL   0X10000000
+#define SCR_KEY_ALT_LEFT  0X08000000
+#define SCR_KEY_ALT_RIGHT 0X04000000
+#define SCR_KEY_GUI       0X02000000
+#define SCR_KEY_CHAR_MASK 0X00FFFFFF
+
+#define SCR_KEY_UNICODE_ROW 0XF800
+
+typedef enum {
+  SCR_KEY_ENTER = SCR_KEY_UNICODE_ROW,
+  SCR_KEY_TAB,
+  SCR_KEY_BACKSPACE,
+  SCR_KEY_ESCAPE,
+  SCR_KEY_CURSOR_LEFT,
+  SCR_KEY_CURSOR_RIGHT,
+  SCR_KEY_CURSOR_UP,
+  SCR_KEY_CURSOR_DOWN,
+  SCR_KEY_PAGE_UP,
+  SCR_KEY_PAGE_DOWN,
+  SCR_KEY_HOME,
+  SCR_KEY_END,
+  SCR_KEY_INSERT,
+  SCR_KEY_DELETE,
+  SCR_KEY_FUNCTION,
+
+  SCR_KEY_F1 = SCR_KEY_FUNCTION,
+  SCR_KEY_F2,
+  SCR_KEY_F3,
+  SCR_KEY_F4,
+  SCR_KEY_F5,
+  SCR_KEY_F6,
+  SCR_KEY_F7,
+  SCR_KEY_F8,
+  SCR_KEY_F9,
+  SCR_KEY_F10,
+  SCR_KEY_F11,
+  SCR_KEY_F12,
+  SCR_KEY_F13,
+  SCR_KEY_F14,
+  SCR_KEY_F15,
+  SCR_KEY_F16,
+  SCR_KEY_F17,
+  SCR_KEY_F18,
+  SCR_KEY_F19,
+  SCR_KEY_F20,
+  SCR_KEY_F21,
+  SCR_KEY_F22,
+  SCR_KEY_F23,
+  SCR_KEY_F24,
+} ScreenKey;
+
+static inline int isSpecialKey(ScreenKey key) {
+  return (key & (SCR_KEY_CHAR_MASK & ~0XFF)) == SCR_KEY_UNICODE_ROW;
+}
+
+/* must be less than 0 */
+#define SCR_NO_VT -1
+
+typedef struct ScreenDriverStruct ScreenDriver;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_TYPES */
diff --git a/Headers/scr_utils.h b/Headers/scr_utils.h
new file mode 100644
index 0000000..3365078
--- /dev/null
+++ b/Headers/scr_utils.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_UTILS
+#define BRLTTY_INCLUDED_SCR_UTILS
+
+#include "scr_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void clearScreenCharacters (ScreenCharacter *characters, size_t count);
+extern void setScreenCharacterText (ScreenCharacter *characters, wchar_t text, size_t count);
+extern void setScreenCharacterAttributes (ScreenCharacter *characters, unsigned char attributes, size_t count);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_UTILS */
diff --git a/Headers/serial_types.h b/Headers/serial_types.h
new file mode 100644
index 0000000..fd013e6
--- /dev/null
+++ b/Headers/serial_types.h
@@ -0,0 +1,85 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_TYPES
+#define BRLTTY_INCLUDED_SERIAL_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  SERIAL_STOP_1   = 1,
+  SERIAL_STOP_2   = 2,
+  SERIAL_STOP_1_5 = 3
+} SerialStopBits;
+
+typedef enum {
+  SERIAL_PARITY_SPACE,
+  SERIAL_PARITY_ODD,
+  SERIAL_PARITY_EVEN,
+  SERIAL_PARITY_MARK,
+  SERIAL_PARITY_NONE
+} SerialParity;
+
+typedef enum {
+  SERIAL_FLOW_OUTPUT_XON = 0X001, /* output controlled by X-ON/X-OFF(input) */
+  SERIAL_FLOW_OUTPUT_CTS = 0X002, /* output controlled by CTS(input) */
+  SERIAL_FLOW_OUTPUT_DSR = 0X004, /* output controlled by DSR(input) */
+  SERIAL_FLOW_OUTPUT_RTS = 0X008, /* output indicated by RTS(output) */
+
+  SERIAL_FLOW_INPUT_XON  = 0X010, /* input controlled by X-ON/X-OFF(output) */
+  SERIAL_FLOW_INPUT_RTS  = 0X020, /* input controlled by RTS(output) */
+  SERIAL_FLOW_INPUT_DTR  = 0X040, /* input controlled by DTR(output) */
+  SERIAL_FLOW_INPUT_DSR  = 0X080, /* input enabled by DSR(input) */
+
+  SERIAL_FLOW_INPUT_CTS  = 0X100, /* input indicated by CTS(input) */
+
+  SERIAL_FLOW_HARDWARE   = (SERIAL_FLOW_OUTPUT_CTS | SERIAL_FLOW_OUTPUT_RTS),
+
+  SERIAL_FLOW_NONE       = 0X00  /* no input or output flow control */
+} SerialFlowControl;
+
+typedef struct {
+  unsigned int baud;
+  unsigned int dataBits;
+  SerialStopBits stopBits;
+  SerialParity parity;
+  SerialFlowControl flowControl;
+} SerialParameters;
+
+#define SERIAL_DEFAULT_BAUD 9600
+#define SERIAL_DEFAULT_DATA_BITS 8
+#define SERIAL_DEFAULT_STOP_BITS SERIAL_STOP_1
+#define SERIAL_DEFAULT_PARITY SERIAL_PARITY_NONE
+#define SERIAL_DEFAULT_FLOW_CONTROL SERIAL_FLOW_NONE
+
+#define SERIAL_DEFAULT_PARAMETERS \
+  .baud = SERIAL_DEFAULT_BAUD, \
+  .dataBits = SERIAL_DEFAULT_DATA_BITS, \
+  .stopBits = SERIAL_DEFAULT_STOP_BITS, \
+  .parity = SERIAL_DEFAULT_PARITY, \
+  .flowControl = SERIAL_DEFAULT_FLOW_CONTROL
+
+#define SERIAL_PARAMETERS_INITIALIZER {SERIAL_DEFAULT_PARAMETERS}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_TYPES */
diff --git a/Headers/service.h b/Headers/service.h
new file mode 100644
index 0000000..b29a55a
--- /dev/null
+++ b/Headers/service.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERVICE
+#define BRLTTY_INCLUDED_SERVICE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int installService(const char *name, const char *description,
+                          const char *configurationFile);
+extern int removeService (const char *name);
+
+extern int notifyServiceReady (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERVICE */
diff --git a/Headers/spk.h b/Headers/spk.h
new file mode 100644
index 0000000..7164385
--- /dev/null
+++ b/Headers/spk.h
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK
+#define BRLTTY_INCLUDED_SPK
+
+#include "spk_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void constructSpeechSynthesizer (SpeechSynthesizer *spk);
+extern void destructSpeechSynthesizer (SpeechSynthesizer *spk);
+
+extern int startSpeechDriverThread (SpeechSynthesizer *spk, char **parameters);
+extern void stopSpeechDriverThread (SpeechSynthesizer *spk);
+
+extern int muteSpeech (SpeechSynthesizer *spk, const char *reason);
+
+extern int canDrainSpeech (SpeechSynthesizer *spk);
+extern int drainSpeech (SpeechSynthesizer *spk);
+
+extern int sayUtf8Characters (
+  SpeechSynthesizer *spk,
+  const char *text, const unsigned char *attributes,
+  size_t length, size_t count,
+  SayOptions options
+);
+
+extern int sayWideCharacters (
+  SpeechSynthesizer *spk,
+  const wchar_t *characters, const unsigned char *attributes,
+  size_t count, SayOptions options
+);
+
+extern int sayString (
+  SpeechSynthesizer *spk,
+  const char *string, SayOptions options
+);
+
+extern int canSetSpeechVolume (SpeechSynthesizer *spk);
+extern int setSpeechVolume (SpeechSynthesizer *spk, int setting, int say);
+extern int toNormalizedSpeechVolume (unsigned char volume);
+
+extern int canSetSpeechRate (SpeechSynthesizer *spk);
+extern int setSpeechRate (SpeechSynthesizer *spk, int setting, int say);
+extern int toNormalizedSpeechRate (unsigned char rate);
+
+extern int canSetSpeechPitch (SpeechSynthesizer *spk);
+extern int setSpeechPitch (SpeechSynthesizer *spk, int setting, int say);
+extern int toNormalizedSpeechPitch (unsigned char pitch);
+
+extern int canSetSpeechPunctuation (SpeechSynthesizer *spk);
+extern int setSpeechPunctuation (SpeechSynthesizer *spk, SpeechPunctuation setting, int say);
+
+extern int haveSpeechDriver (const char *code);
+extern const char *getDefaultSpeechDriver (void);
+extern const SpeechDriver *loadSpeechDriver (const char *code, void **driverObject, const char *driverDirectory);
+extern void identifySpeechDriver (const SpeechDriver *driver, int full);
+extern void identifySpeechDrivers (int full);
+extern const SpeechDriver *speech;
+extern const SpeechDriver noSpeech;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK */
diff --git a/Headers/spk_base.h b/Headers/spk_base.h
new file mode 100644
index 0000000..52f9a11
--- /dev/null
+++ b/Headers/spk_base.h
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK_BASE
+#define BRLTTY_INCLUDED_SPK_BASE
+
+#include "spk_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int tellSpeechFinished (SpeechSynthesizer *spk);
+extern int tellSpeechLocation (SpeechSynthesizer *spk, int index);
+
+extern unsigned int getIntegerSpeechVolume (unsigned char setting, unsigned int normal);
+extern unsigned int getIntegerSpeechRate (unsigned char setting, unsigned int normal);
+extern unsigned int getIntegerSpeechPitch (unsigned char setting, unsigned int normal);
+
+#ifndef NO_FLOAT
+extern float getFloatSpeechVolume (unsigned char setting);
+extern float getFloatSpeechRate (unsigned char setting);
+extern float getFloatSpeechPitch (unsigned char setting);
+#endif /* NO_FLOAT */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK_BASE */
diff --git a/Headers/spk_driver.h b/Headers/spk_driver.h
new file mode 100644
index 0000000..d22ed12
--- /dev/null
+++ b/Headers/spk_driver.h
@@ -0,0 +1,69 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK_DRIVER
+#define BRLTTY_INCLUDED_SPK_DRIVER
+
+#include "spk_types.h"
+#include "spk_base.h"
+#include "spk.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef SPKPARMS
+static const char *const spk_parameters[] = {SPKPARMS, NULL};
+#else /* SPKPARMS */
+#define spk_parameters NULL
+#endif /* SPKPARMS */
+
+static int spk_construct (SpeechSynthesizer *spk, char **parameters);
+static void spk_destruct (SpeechSynthesizer *spk);
+
+static void spk_say (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes);
+static void spk_mute (SpeechSynthesizer *spk);
+
+#ifndef SPKSYMBOL
+#define SPKSYMBOL CONCATENATE(spk_driver_,DRIVER_CODE)
+#endif /* SPKSYMBOL */
+
+#ifndef SPKCONST
+#define SPKCONST const
+#endif /* SPKCONST */
+
+extern SPKCONST SpeechDriver SPKSYMBOL;
+SPKCONST SpeechDriver SPKSYMBOL = {
+  DRIVER_DEFINITION_INITIALIZER,
+
+  spk_parameters,
+
+  spk_construct,
+  spk_destruct,
+
+  spk_say,
+  spk_mute
+};
+
+DRIVER_VERSION_DECLARATION(spk);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK_DRIVER */
diff --git a/Headers/spk_types.h b/Headers/spk_types.h
new file mode 100644
index 0000000..2c05eeb
--- /dev/null
+++ b/Headers/spk_types.h
@@ -0,0 +1,107 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK_TYPES
+#define BRLTTY_INCLUDED_SPK_TYPES
+
+#include "driver.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  SAY_OPT_MUTE_FIRST      = 0X01,
+  SAY_OPT_HIGHER_PITCH    = 0X02,
+  SAY_OPT_ALL_PUNCTUATION = 0X04,
+} SayOptions;
+
+#define SPK_VOLUME_DEFAULT 10
+#define SPK_VOLUME_MAXIMUM (SPK_VOLUME_DEFAULT * 2)
+
+#define SPK_RATE_DEFAULT 10
+#define SPK_RATE_MAXIMUM (SPK_RATE_DEFAULT * 2)
+
+#define SPK_PITCH_DEFAULT 10
+#define SPK_PITCH_MAXIMUM (SPK_PITCH_DEFAULT * 2)
+
+typedef enum {
+  SPK_PUNCTUATION_NONE,
+  SPK_PUNCTUATION_SOME,
+  SPK_PUNCTUATION_ALL
+} SpeechPunctuation;
+
+typedef struct SpeechSynthesizerStruct SpeechSynthesizer;
+typedef struct SpeechDriverThreadStruct SpeechDriverThread;
+typedef struct SpeechDataStruct SpeechData;
+
+#define SPK_SCR_NONE -1
+#define SPK_LOC_NONE -1
+
+typedef void SetSpeechVolumeMethod (SpeechSynthesizer *spk, unsigned char setting);
+typedef void SetSpeechRateMethod (SpeechSynthesizer *spk, unsigned char setting);
+typedef void SetSpeechPitchMethod (SpeechSynthesizer *spk, unsigned char setting);
+typedef void SetSpeechPunctuationMethod (SpeechSynthesizer *spk, SpeechPunctuation setting);
+typedef void DrainSpeechMethod (SpeechSynthesizer *spk);
+
+typedef void SetSpeechFinishedMethod (SpeechSynthesizer *spk);
+typedef void SetSpeechLocationMethod (SpeechSynthesizer *spk, int location);
+
+struct SpeechSynthesizerStruct {
+  unsigned char sayBanner : 1;
+  unsigned char canAutospeak : 1;
+
+  struct {
+    int screenNumber;
+    int firstLine;
+    int speechLocation;
+    unsigned char isActive : 1;
+  } track;
+
+  SetSpeechVolumeMethod *setVolume;
+  SetSpeechRateMethod *setRate;
+  SetSpeechPitchMethod *setPitch;
+  SetSpeechPunctuationMethod *setPunctuation;
+  DrainSpeechMethod *drain;
+
+  SetSpeechFinishedMethod *setFinished;
+  SetSpeechLocationMethod *setLocation;
+
+  struct {
+    SpeechDriverThread *thread;
+    SpeechData *data;
+  } driver;
+};
+
+typedef struct {
+  DRIVER_DEFINITION_DECLARATION;
+
+  const char *const *parameters;
+
+  int (*construct) (SpeechSynthesizer *spk, char **parameters);
+  void (*destruct) (SpeechSynthesizer *spk);
+
+  void (*say) (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes);
+  void (*mute) (SpeechSynthesizer *spk);
+} SpeechDriver;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK_TYPES */
diff --git a/Headers/status_types.h b/Headers/status_types.h
new file mode 100644
index 0000000..3dd74cd
--- /dev/null
+++ b/Headers/status_types.h
@@ -0,0 +1,98 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_STATUS_TYPES
+#define BRLTTY_INCLUDED_STATUS_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  sfEnd = 0,
+  sfWindowCoordinates2,
+  sfWindowColumn,
+  sfWindowRow,
+  sfCursorCoordinates2,
+  sfCursorColumn,
+  sfCursorRow,
+  sfCursorAndWindowColumn2,
+  sfCursorAndWindowRow2,
+  sfScreenNumber,
+  sfStateDots,
+  sfStateLetter,
+  sfTime,
+  sfAlphabeticWindowCoordinates,
+  sfAlphabeticCursorCoordinates,
+  sfGeneric,
+
+  /*****************************************************************************/
+  /* No fields above this point should be added, removed, or reordered so that */
+  /* backward compatibility with old binary preference files will be retained. */
+  /*                                                                           */
+  /* Fields below this point may be modified as desired.                       */
+  /*****************************************************************************/
+
+  sfCursorCoordinates3,
+  sfWindowCoordinates3,
+  sfCursorAndWindowColumn3,
+  sfCursorAndWindowRow3,
+
+  sfSpace,
+} StatusField;
+
+#define GSC_MARKER 0XFF /* must be in GSC_FIRST */
+typedef enum {
+  GSC_FIRST = 0 /* must be first */,
+
+  /* numbers */
+  gscBrailleWindowColumn,
+  gscBrailleWindowRow,
+  gscScreenCursorColumn,
+  gscScreenCursorRow,
+  gscScreenNumber,
+
+  /* flags */
+  gscFrozenScreen,
+  gscDisplayMode,
+  gscSixDotComputerBraille,
+  gscContractedBraille,
+  gscSlidingBrailleWindow,
+  gscSkipIdenticalLines,
+  gscSkipBlankBrailleWindows,
+  gscShowScreenCursor,
+  gscHideScreenCursor,
+  gscTrackScreenCursor,
+  gscScreenCursorStyle,
+  gscBlinkingScreenCursor,
+  gscShowAttributes,
+  gscBlinkingAttributes,
+  gscBlinkingCapitals,
+  gscAlertTunes,
+  gscAutorepeat,
+  gscAutospeak,
+  gscBrailleTypingMode,
+
+  GSC_COUNT /* must be last */
+} BRL_GenericStatusCell;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_STATUS_TYPES */
diff --git a/Headers/stdiox.h b/Headers/stdiox.h
new file mode 100644
index 0000000..61eb325
--- /dev/null
+++ b/Headers/stdiox.h
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_STDIOX
+#define BRLTTY_INCLUDED_STDIOX
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+static inline void
+flushStream (FILE *stream) {
+  fflush(stream);
+
+#ifdef __MSDOS__
+  fsync(fileno(stream));
+#endif /* __MSDOS__ */
+}
+
+#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(__MINGW32__)
+#define lockStream(stream) flockfile((stream))
+#define unlockStream(stream) funlockfile((stream))
+#else /* _POSIX_THREAD_SAFE_FUNCTIONS */
+#define lockStream(stream)
+#define unlockStream(stream)
+#endif /* _POSIX_THREAD_SAFE_FUNCTIONS */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_STDIOX */
diff --git a/Headers/strfmt.h b/Headers/strfmt.h
new file mode 100644
index 0000000..1f7f160
--- /dev/null
+++ b/Headers/strfmt.h
@@ -0,0 +1,74 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_STRFMT
+#define BRLTTY_INCLUDED_STRFMT
+
+#include <stdio.h>
+
+#include "strfmth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define STR_BEGIN(buffer, size) { \
+char *strNext = (buffer); \
+const char *const strStart = strNext; \
+const char *const strEnd = strStart + (size); \
+const char *const strLast = strEnd - 1; \
+*strNext = 0;
+
+#define STR_END }
+
+#define STR_LENGTH (size_t)(strNext - strStart)
+
+#define STR_NEXT strNext
+
+#define STR_LEFT (size_t)(strEnd - strNext)
+
+#define STR_POP() ((strNext > strStart)? --strNext: NULL)
+
+#define STR_ADJUST(length) \
+do { if ((strNext += (length)) > strLast) strNext = (char *)strLast; } while (0)
+
+#define STR_BEGIN_FORMATTER(name, ...) \
+STR_DECLARE_FORMATTER(name, __VA_ARGS__) { \
+  size_t strFormatterResult; \
+  STR_BEGIN(strFormatterBuffer, strFormatterSize);
+
+#define STR_END_FORMATTER \
+  strFormatterResult = STR_LENGTH; \
+  STR_END; \
+  return strFormatterResult; \
+}
+
+#define STR_FORMAT(formatter, ...) \
+STR_ADJUST(formatter(STR_NEXT, STR_LEFT, __VA_ARGS__))
+
+#define STR_PRINTF(...) \
+STR_FORMAT(snprintf, __VA_ARGS__)
+
+#define STR_VPRINTF(format, arguments) \
+STR_FORMAT(vsnprintf, format, arguments)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_STRFMT */
diff --git a/Headers/strfmth.h b/Headers/strfmth.h
new file mode 100644
index 0000000..becab12
--- /dev/null
+++ b/Headers/strfmth.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_STRFMTH
+#define BRLTTY_INCLUDED_STRFMTH
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define STR_DECLARE_FORMATTER(name, ...) size_t name ( \
+  char *const strFormatterBuffer, \
+  size_t const strFormatterSize, \
+  __VA_ARGS__ \
+)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_STRFMTH */
diff --git a/Headers/system.h b/Headers/system.h
new file mode 100644
index 0000000..6c58668
--- /dev/null
+++ b/Headers/system.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM
+#define BRLTTY_INCLUDED_SYSTEM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void initializeSystemObject (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM */
diff --git a/Headers/system_darwin.h b/Headers/system_darwin.h
new file mode 100644
index 0000000..c40a15d
--- /dev/null
+++ b/Headers/system_darwin.h
@@ -0,0 +1,63 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM_DARWIN
+#define BRLTTY_INCLUDED_SYSTEM_DARWIN
+
+#include <CoreFoundation/CFRunLoop.h>
+
+#import <Foundation/NSThread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern IOReturn executeRunLoop (int seconds);
+extern void addRunLoopSource (CFRunLoopSourceRef source);
+extern void removeRunLoopSource (CFRunLoopSourceRef source);
+
+#define MAP_DARWIN_ERROR(from,to) case (from): errno = (to); break;
+extern void setDarwinSystemError (IOReturn result);
+
+@interface AsynchronousResult: NSObject
+@property (assign, readonly) int isFinished;
+@property (assign, readonly) IOReturn finalStatus;
+
+- (int) wait
+  : (int) timeout;
+
+- (void) setStatus
+  : (IOReturn) status;
+@end
+
+@interface AsynchronousTask: AsynchronousResult
+@property (assign, readonly) NSThread *taskThread;
+@property (assign, readonly) CFRunLoopRef taskRunLoop;
+
+- (IOReturn) run;
+
+- (int) start;
+
+- (void) stop;
+@end
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM_DARWIN */
diff --git a/Headers/system_java.h b/Headers/system_java.h
new file mode 100644
index 0000000..6ef31c0
--- /dev/null
+++ b/Headers/system_java.h
@@ -0,0 +1,72 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM_JAVA
+#define BRLTTY_INCLUDED_SYSTEM_JAVA
+
+#include "prologue.h"
+#include "common_java.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __ANDROID__
+#define JAVA_JNI_VERSION JNI_VERSION_1_6
+#define JAVA_OBJ_BRLTTY(name) "org/a11y/brltty/android/" name
+#endif /* __ANDROID__ */
+
+extern JavaVM *getJavaInvocationInterface (void);
+extern JNIEnv *getJavaNativeInterface (void);
+extern int clearJavaException (JNIEnv *env, int describe);
+
+FUNCTION_DECLARE(setJavaClassLoader, int, (JNIEnv *env, jobject instance));
+extern int findJavaClass (JNIEnv *env, jclass *class, const char *path);
+
+extern int findJavaConstructor (
+  JNIEnv *env, jmethodID *constructor,
+  jclass class, const char *signature
+);
+
+extern int findJavaInstanceMethod (
+  JNIEnv *env, jmethodID *method,
+  jclass class, const char *name, const char *signature
+);
+
+extern int findJavaStaticMethod (
+  JNIEnv *env, jmethodID *method,
+  jclass class, const char *name, const char *signature
+);
+
+extern int findJavaInstanceField (
+  JNIEnv *env, jfieldID *field,
+  jclass class, const char *name, const char *signature
+);
+
+extern int findJavaStaticField (
+  JNIEnv *env, jfieldID *field,
+  jclass class, const char *name, const char *signature
+);
+
+extern char *getJavaLocaleName (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM_JAVA */
diff --git a/Headers/system_linux.h b/Headers/system_linux.h
new file mode 100644
index 0000000..eb1faf0
--- /dev/null
+++ b/Headers/system_linux.h
@@ -0,0 +1,111 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM_LINUX
+#define BRLTTY_INCLUDED_SYSTEM_LINUX
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *path;
+  void *data;
+} PathProcessorParameters;
+
+typedef int PathProcessor (const PathProcessorParameters *parameters);
+extern int processPathTree (const char *path, PathProcessor *processPath, void *data);
+
+extern int compareGroups (gid_t group1, gid_t group2);
+extern void sortGroups (gid_t *groups, size_t count);
+extern void removeDuplicateGroups (gid_t *groups, size_t *count);
+
+typedef void GroupsProcessor (const gid_t *groups, size_t count, void *data);
+extern void processSupplementaryGroups (GroupsProcessor *processGroups, void *data);
+extern int haveSupplementaryGroups (const gid_t *groups, size_t count);
+
+extern int installKernelModule (const char *name, unsigned char *status);
+extern int installSpeakerModule (void);
+extern int installUinputModule (void);
+
+extern int openCharacterDevice (const char *name, int flags, int major, int minor);
+
+typedef struct UinputObjectStruct UinputObject;
+extern UinputObject *newUinputObject (const char *name);
+extern void destroyUinputObject (UinputObject *uinput);
+
+extern int getUinputFileDescriptor (UinputObject *uinput);
+extern int createUinputDevice (UinputObject *uinput);
+
+extern int enableUinputEventType (UinputObject *uinput, int type);
+extern int writeInputEvent (UinputObject *uinput, uint16_t type, uint16_t code, int32_t value);
+
+extern int enableUinputKey (UinputObject *uinput, int key);
+extern int writeKeyEvent (UinputObject *uinput, int key, int press);
+extern int releasePressedKeys (UinputObject *uinput);
+
+extern int writeRepeatDelay (UinputObject *uinput, int delay);
+extern int writeRepeatPeriod (UinputObject *uinput, int period);
+
+extern int enableUinputSound (UinputObject *uinput, int sound);
+extern int enableUinputLed (UinputObject *uinput, int led);
+
+extern UinputObject *newUinputKeyboard (const char *name);
+
+typedef struct InputEventMonitorStruct InputEventMonitor;
+typedef struct input_event InputEvent;
+typedef int UinputObjectPreparer (UinputObject *uinput);
+typedef void InputEventHandler (const InputEvent *event);
+
+extern InputEventMonitor *newInputEventMonitor (
+  const char *name,
+  UinputObjectPreparer *prepareUinputObject,
+  InputEventHandler *handleInputEvent
+);
+
+extern void destroyInputEventMonitor (
+  InputEventMonitor *monitor
+);
+
+typedef uint8_t LinuxKeyCode;
+#define LINUX_KEY_MAP_NAME(type) linuxKeyMap_ ## type
+#define LINUX_KEY_MAP(type) const LinuxKeyCode LINUX_KEY_MAP_NAME(type)[0X100]
+
+extern LINUX_KEY_MAP(xt00);
+extern LINUX_KEY_MAP(xtE0);
+extern LINUX_KEY_MAP(xtE1);
+extern LINUX_KEY_MAP(at00);
+extern LINUX_KEY_MAP(atE0);
+extern LINUX_KEY_MAP(atE1);
+extern LINUX_KEY_MAP(ps2);
+extern LINUX_KEY_MAP(hid);
+
+typedef struct {
+  const char *name;
+  const LinuxKeyCode *keys;
+  unsigned int count;
+} LinuxKeyMapDescriptor;
+
+extern const LinuxKeyMapDescriptor linuxKeyMapDescriptors[];
+extern const unsigned char linuxKeyMapCount;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM_LINUX */
diff --git a/Headers/system_msdos.h b/Headers/system_msdos.h
new file mode 100644
index 0000000..ba2db13
--- /dev/null
+++ b/Headers/system_msdos.h
@@ -0,0 +1,56 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM_MSDOS
+#define BRLTTY_INCLUDED_SYSTEM_MSDOS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void msdosBackground (void);
+
+extern unsigned long msdosUSleep (unsigned long microseconds);
+
+extern unsigned short msdosGetCodePage (void);
+
+static inline unsigned long
+msdosMakeAddress (unsigned short segment, unsigned short offset) {
+  return ((unsigned long)segment << 4) + (unsigned long)offset;
+}
+
+static inline void msdosBreakAddress (
+  unsigned long address, int absolute,
+  unsigned short *segment, unsigned short *offset
+) {
+  if (absolute) {
+    if (segment) *segment = (address >> 4) & 0XF000;
+    if (offset) *offset = address & 0XFFFF;
+  } else {
+    if (segment) *segment = address >> 4;
+    if (offset) *offset = address & 0XF;
+  }
+}
+
+#define MSDOS_PIT_FREQUENCY UINT64_C(1193180)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM_MSDOS */
diff --git a/Headers/system_windows.h b/Headers/system_windows.h
new file mode 100644
index 0000000..acd819d
--- /dev/null
+++ b/Headers/system_windows.h
@@ -0,0 +1,97 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SYSTEM_WINDOWS
+#define BRLTTY_INCLUDED_SYSTEM_WINDOWS
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define WIN_PROC_STUB(name) typeof(name) (*name##Proc)
+
+
+/* ntdll.dll */
+#include <ntdef.h>
+
+#ifndef STATUS_SUCCESS
+#include <ntstatus.h>
+#endif /* STATUS_SUCCESS */
+
+#ifndef HAVE_PROCESS_INFORMATION_CLASS
+typedef enum _PROCESSINFOCLASS {
+  ProcessUserModeIOPL = 16,
+} PROCESSINFOCLASS, PROCESS_INFORMATION_CLASS;
+#else /* HAVE_PROCESS_INFORMATION_CLASS */
+#  ifndef HAVE_ProcessUserModeIOPL
+#    define ProcessUserModeIOPL 16
+#  endif /* ProcessUserModeIOPL */
+#endif /* HAVE_PROCESS_INFORMATION_CLASS */
+
+extern NTSTATUS WINAPI NtSetInformationProcess (HANDLE, PROCESS_INFORMATION_CLASS, PVOID, ULONG);
+extern WIN_PROC_STUB(NtSetInformationProcess);
+
+
+/* kernel32.dll: console */
+#ifdef RC_INVOKED
+#include <wincon.h>
+#endif /* RC_INVOKED */
+extern WIN_PROC_STUB(AttachConsole);
+
+extern WINBASEAPI int WINAPI GetLocaleInfoEx (LPCWSTR, LCTYPE, LPWSTR, int);
+extern WIN_PROC_STUB(GetLocaleInfoEx);
+
+#ifndef LOCALE_NAME_USER_DEFAULT
+#define LOCALE_NAME_USER_DEFAULT NULL
+#endif /* LOCALE_NAME_USER_DEFAULT */
+
+#ifndef LOCALE_SNAME
+#define LOCALE_SNAME 0X5CL
+#endif /* LOCALE_SNAME */
+
+
+/* user32.dll */
+extern WIN_PROC_STUB(GetAltTabInfoA);
+extern WIN_PROC_STUB(SendInput);
+
+
+/* ws2_32.dll */
+#ifdef __MINGW32__
+#ifndef RC_INVOKED
+#include <ws2tcpip.h>
+#endif /* RC_INVOKED */
+
+extern WIN_PROC_STUB(getaddrinfo);
+extern WIN_PROC_STUB(freeaddrinfo);
+
+#define getaddrinfo(host,port,hints,res) getaddrinfoProc(host,port,hints,res)
+#define freeaddrinfo(res) freeaddrinfoProc(res)
+
+extern char *getWindowsLocaleName (void);
+#endif /* __MINGW32__ */
+
+
+extern char *makeWindowsCommandLine (const char *const *arguments);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SYSTEM_WINDOWS */
diff --git a/Headers/thread.h b/Headers/thread.h
new file mode 100644
index 0000000..e2a347e
--- /dev/null
+++ b/Headers/thread.h
@@ -0,0 +1,120 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_THREAD
+#define BRLTTY_INCLUDED_THREAD
+
+#include "get_thread.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef GOT_PTHREADS
+#define THREAD_FUNCTION(name) void *name (void *argument)
+typedef THREAD_FUNCTION(ThreadFunction);
+
+extern int createThread (
+  const char *name,
+  pthread_t *thread, const pthread_attr_t *attributes,
+  ThreadFunction *function, void *argument
+);
+
+extern int callThreadFunction (
+  const char *name, ThreadFunction *function,
+  void *argument, void **result
+);
+
+extern int lockMutex (pthread_mutex_t *mutex);
+extern int unlockMutex (pthread_mutex_t *mutex);
+#endif /* GOT_PTHREADS */
+
+extern size_t formatThreadName (char *buffer, size_t size);
+extern void setThreadName (const char *name);
+
+#if defined(PTHREAD_MUTEX_INITIALIZER)
+typedef pthread_mutex_t CriticalSectionLock;
+#define CRITICAL_SECTION_LOCK_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+
+static inline void
+enterCriticalSection (CriticalSectionLock *lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void
+leaveCriticalSection (CriticalSectionLock *lock) {
+  pthread_mutex_unlock(lock);
+}
+
+#else /* critical section lock */
+typedef unsigned char CriticalSectionLock;
+#define CRITICAL_SECTION_LOCK_INITIALIZER 0
+
+static inline void
+enterCriticalSection (CriticalSectionLock *lock) {
+}
+
+static inline void
+leaveCriticalSection (CriticalSectionLock *lock) {
+}
+#endif /* critical section lock */
+
+#define THREAD_SPECIFIC_DATA_NEW(name) void *name##_new (void)
+typedef THREAD_SPECIFIC_DATA_NEW(ThreadSpecificData);
+
+#define THREAD_SPECIFIC_DATA_DESTROY(name) void name##_destroy (void *data)
+typedef THREAD_SPECIFIC_DATA_DESTROY(ThreadSpecificData);
+
+typedef struct {
+  ThreadSpecificData_new *new;
+  ThreadSpecificData_destroy *destroy;
+
+#if defined(PTHREAD_MUTEX_INITIALIZER)
+  pthread_mutex_t mutex;
+
+  struct {
+    pthread_key_t value;
+    unsigned created;
+  } key;
+
+#define THREAD_SPECIFIC_DATA_INITIALIZER() \
+  .mutex = PTHREAD_MUTEX_INITIALIZER, .key = { .created = 0 }
+
+#else /* thread specific data */
+  void *data;
+
+#define THREAD_SPECIFIC_DATA_INITIALIZER() \
+  .data = NULL
+#endif /* thread specific data */
+} ThreadSpecificDataControl;
+
+#define THREAD_SPECIFIC_DATA_CONTROL(name) \
+  static ThreadSpecificDataControl name = { \
+    .new = name ## _new, \
+    .destroy = name ## _destroy, \
+    \
+    THREAD_SPECIFIC_DATA_INITIALIZER() \
+  }
+
+extern void *getThreadSpecificData (ThreadSpecificDataControl *ctl);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_THREAD */
diff --git a/Headers/timing.h b/Headers/timing.h
new file mode 100644
index 0000000..52c6975
--- /dev/null
+++ b/Headers/timing.h
@@ -0,0 +1,63 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TIMING
+#define BRLTTY_INCLUDED_TIMING
+
+#include <timing_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void getCurrentTime (TimeValue *time);
+extern void setCurrentTime (const TimeValue *time);
+
+extern void makeTimeValue (TimeValue *value, const TimeComponents *components);
+extern void expandTimeValue (const TimeValue *value, TimeComponents *components);
+extern size_t formatSeconds (char *buffer, size_t size, const char *format, int32_t seconds);
+
+extern void normalizeTimeValue (TimeValue *time);
+extern void adjustTimeValue (TimeValue *time, int milliseconds);
+
+extern int compareTimeValues (const TimeValue *first, const TimeValue *second);
+extern long int millisecondsBetween (const TimeValue *from, const TimeValue *to);
+
+extern long int millisecondsTillNextSecond (const TimeValue *reference);
+extern long int millisecondsTillNextMinute (const TimeValue *reference);
+
+extern void getMonotonicTime (TimeValue *now);
+extern long int getMonotonicElapsed (const TimeValue *start);
+
+typedef struct {
+  TimeValue start;
+  long int length;
+} TimePeriod;
+
+extern void startTimePeriod (TimePeriod *period, long int length);
+extern void restartTimePeriod (TimePeriod *period);
+extern int afterTimePeriod (const TimePeriod *period, long int *elapsed);
+
+extern void approximateDelay (int milliseconds);		/* sleep for `msec' milliseconds */
+extern void accurateDelay (const TimeValue *duration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TIMING */
diff --git a/Headers/timing_types.h b/Headers/timing_types.h
new file mode 100644
index 0000000..a213cc4
--- /dev/null
+++ b/Headers/timing_types.h
@@ -0,0 +1,72 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TIMING_TYPES
+#define BRLTTY_INCLUDED_TIMING_TYPES
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MSECS_PER_SEC  1000
+#define USECS_PER_MSEC 1000
+#define NSECS_PER_USEC 1000
+#define USECS_PER_SEC  (USECS_PER_MSEC * MSECS_PER_SEC)
+#define NSECS_PER_MSEC (NSECS_PER_USEC * USECS_PER_MSEC)
+#define NSECS_PER_SEC  (NSECS_PER_USEC * USECS_PER_MSEC * MSECS_PER_SEC)
+
+#define SECS_PER_MIN 60
+#define MINS_PER_HR  60
+#define HRS_PER_DAY  24
+#define DAYS_PER_WK  7
+#define SECS_PER_HR  (SECS_PER_MIN * MINS_PER_HR)
+#define SECS_PER_DAY (SECS_PER_MIN * MINS_PER_HR * HRS_PER_DAY)
+#define SECS_PER_WK  (SECS_PER_MIN * MINS_PER_HR * HRS_PER_DAY * DAYS_PER_WK)
+#define MINS_PER_DAY (MINS_PER_HR * HRS_PER_DAY)
+#define MINS_PER_WK  (MINS_PER_HR * HRS_PER_DAY * DAYS_PER_WK)
+#define HRS_PER_WK   (HRS_PER_DAY * DAYS_PER_WK)
+
+typedef struct {
+  int32_t seconds;
+  int32_t nanoseconds;
+} TimeValue;
+
+typedef struct {
+  struct tm time;
+
+  uint16_t year;
+  uint8_t month;
+  uint8_t day;
+
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+
+  int32_t nanosecond;
+} TimeComponents;
+
+#define PRIsec PRIi32
+#define PRInsec PRIi32
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TIMING_TYPES */
diff --git a/Headers/ttb.h b/Headers/ttb.h
new file mode 100644
index 0000000..3017a07
--- /dev/null
+++ b/Headers/ttb.h
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TTB
+#define BRLTTY_INCLUDED_TTB
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct TextTableStruct TextTable;
+extern TextTable *textTable;
+
+extern void lockTextTable (void);
+extern void unlockTextTable (void);
+
+extern TextTable *compileTextTable (const char *name);
+extern void destroyTextTable (TextTable *table);
+
+extern char *ensureTextTableExtension (const char *path);
+extern char *makeTextTablePath (const char *directory, const char *name);
+
+extern char *getTextTableForLocale (const char *directory);
+extern int replaceTextTable (const char *directory, const char *name);
+
+extern unsigned char convertCharacterToDots (TextTable *table, wchar_t character);
+extern wchar_t convertDotsToCharacter (TextTable *table, unsigned char dots);
+extern wchar_t convertInputToCharacter (unsigned char dots);
+
+extern void setTryBaseCharacter (TextTable *table, unsigned char yes);
+
+extern size_t getTextTableRowsMask (TextTable *table, uint8_t *mask, size_t size);
+extern int getTextTableRowCells (TextTable *table, uint32_t rowIndex, uint8_t *cells, uint8_t *defined);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TTB */
diff --git a/Headers/tune.h b/Headers/tune.h
new file mode 100644
index 0000000..bf62679
--- /dev/null
+++ b/Headers/tune.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TUNE
+#define BRLTTY_INCLUDED_TUNE
+
+#include "tune_types.h"
+#include "note_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  unsigned char note;     /* standard MIDI values (0 means silence) */
+                          /* 1 through 127 are semitones, 60 is middle C */
+  unsigned char duration; /* milliseconds (0 means stop) */
+} NoteElement;
+
+#define NOTE_PLAY(duration,note) {note, duration}
+#define NOTE_REST(duration) NOTE_PLAY(duration, 0)
+#define NOTE_STOP() NOTE_REST(0)
+
+typedef struct {
+  NoteFrequency frequency; /* Hertz (0 means silence) */
+  int duration;        /* milliseconds (0 means stop) */
+} ToneElement;
+
+#define TONE_PLAY(duration,frequency) {frequency, duration}
+#define TONE_REST(duration) TONE_PLAY(duration, 0)
+#define TONE_STOP() TONE_REST(0)
+
+extern void suppressTuneDeviceOpenErrors (void);
+
+extern int tuneSetDevice (TuneDevice device);
+extern void tunePlayNotes (const NoteElement *tune);
+extern void tunePlayTones (const ToneElement *tune);
+extern void tuneWait (int time);
+extern void tuneSynchronize (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TUNE */
diff --git a/Headers/tune_builder.h b/Headers/tune_builder.h
new file mode 100644
index 0000000..624e870
--- /dev/null
+++ b/Headers/tune_builder.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TUNE_BUILDER
+#define BRLTTY_INCLUDED_TUNE_BUILDER
+
+#include "cmdline_types.h"
+#include "tune.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define NOTES_PER_SCALE 7
+
+typedef enum {
+  TUNE_STATUS_OK,
+  TUNE_STATUS_SYNTAX,
+  TUNE_STATUS_FATAL
+} TuneStatus;
+
+typedef struct TuneBuilderStruct TuneBuilder;
+extern DECLARE_USAGE_NOTES(tuneBuilderUsageNotes);
+
+extern TuneBuilder *newTuneBuilder(void);
+extern void resetTuneBuilder(TuneBuilder *tune);
+extern void destroyTuneBuilder(TuneBuilder *tb);
+
+extern TuneStatus getTuneStatus(TuneBuilder *tb);
+extern void setTuneSourceName(TuneBuilder *tb, const char *name);
+extern void setTuneSourceIndex(TuneBuilder *tb, unsigned int index);
+extern void incrementTuneSourceIndex(TuneBuilder *tb);
+
+extern int parseTuneString(TuneBuilder *tune, const char *string);
+extern int parseTuneText(TuneBuilder *tune, const wchar_t *text);
+extern ToneElement *getTune(TuneBuilder *tune);
+
+extern int addTone(TuneBuilder *tune, const ToneElement *tone);
+extern int addNote(TuneBuilder *tune, unsigned char note, int duration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TUNE_BUILDER */
diff --git a/Headers/tune_types.h b/Headers/tune_types.h
new file mode 100644
index 0000000..cbcd616
--- /dev/null
+++ b/Headers/tune_types.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TUNE_TYPES
+#define BRLTTY_INCLUDED_TUNE_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  tdBeeper,
+  tdPcm,
+  tdMidi,
+  tdFm
+} TuneDevice;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TUNE_TYPES */
diff --git a/Headers/tune_utils.h b/Headers/tune_utils.h
new file mode 100644
index 0000000..c0630e4
--- /dev/null
+++ b/Headers/tune_utils.h
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TUNE_UTILS
+#define BRLTTY_INCLUDED_TUNE_UTILS
+
+#include "tune_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char *getTuneDeviceName (TuneDevice device);
+extern int parseTuneDevice (const char *setting);
+extern int setTuneDevice (void);
+
+extern int parseTuneVolume (const char *setting);
+extern int parseTuneInstrument (const char *setting);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TUNE_UTILS */
diff --git a/Headers/unicode.h b/Headers/unicode.h
new file mode 100644
index 0000000..0ede1aa
--- /dev/null
+++ b/Headers/unicode.h
@@ -0,0 +1,108 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_UNICODE
+#define BRLTTY_INCLUDED_UNICODE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef HAVE_WCHAR_H 
+#define UNICODE_REPLACEMENT_CHARACTER 0XFFFD
+#else /* HAVE_WCHAR_H */
+#define UNICODE_REPLACEMENT_CHARACTER '?'
+#endif /* HAVE_WCHAR_H */
+
+#define UNICODE_ZERO_WIDTH_SPACE 0X200B
+#define UNICODE_BYTE_ORDER_MARK 0XFEFF
+#define UNICODE_LAST_CHARACTER 0X10FEFF
+
+#define UNICODE_BRAILLE_ROW 0X2800
+
+#define UNICODE_SURROGATE_BEGIN 0XD800
+#define UNICODE_SURROGATE_END 0XDFFF
+#define UNICODE_SURROGATE_SHIFT 10
+#define UNICODE_SURROGATE_LOW (1 << UNICODE_SURROGATE_SHIFT)
+#define UNICODE_SURROGATE_MASK (UNICODE_SURROGATE_LOW - 1)
+
+#define UNICODE_CELL_BITS 8
+#define UNICODE_ROW_BITS 8
+#define UNICODE_PLANE_BITS 8
+#define UNICODE_GROUP_BITS 7
+
+#define UNICODE_CELL_SHIFT 0
+#define UNICODE_ROW_SHIFT (UNICODE_CELL_SHIFT + UNICODE_CELL_BITS)
+#define UNICODE_PLANE_SHIFT (UNICODE_ROW_SHIFT + UNICODE_ROW_BITS)
+#define UNICODE_GROUP_SHIFT (UNICODE_PLANE_SHIFT + UNICODE_PLANE_BITS)
+
+#define UNICODE_CELLS_PER_ROW (1 << UNICODE_CELL_BITS)
+#define UNICODE_ROWS_PER_PLANE (1 << UNICODE_ROW_BITS)
+#define UNICODE_PLANES_PER_GROUP (1 << UNICODE_PLANE_BITS)
+#define UNICODE_GROUP_COUNT (1 << UNICODE_GROUP_BITS)
+
+#define UNICODE_CELL_MAXIMUM (UNICODE_CELLS_PER_ROW - 1)
+#define UNICODE_ROW_MAXIMUM (UNICODE_ROWS_PER_PLANE - 1)
+#define UNICODE_PLANE_MAXIMUM (UNICODE_PLANES_PER_GROUP - 1)
+#define UNICODE_GROUP_MAXIMUM (UNICODE_GROUP_COUNT - 1)
+
+#define UNICODE_CELL_MASK (UNICODE_CELL_MAXIMUM << UNICODE_CELL_SHIFT)
+#define UNICODE_ROW_MASK (UNICODE_ROW_MAXIMUM << UNICODE_ROW_SHIFT)
+#define UNICODE_PLANE_MASK (UNICODE_PLANE_MAXIMUM << UNICODE_PLANE_SHIFT)
+#define UNICODE_GROUP_MASK (UNICODE_GROUP_MAXIMUM << UNICODE_GROUP_SHIFT)
+#define UNICODE_CHARACTER_MASK (UNICODE_CELL_MASK | UNICODE_ROW_MASK | UNICODE_PLANE_MASK | UNICODE_GROUP_MASK)
+
+#define UNICODE_CELL_NUMBER(c) (((c) & UNICODE_CELL_MASK) >> UNICODE_CELL_SHIFT)
+#define UNICODE_ROW_NUMBER(c) (((c) & UNICODE_ROW_MASK) >> UNICODE_ROW_SHIFT)
+#define UNICODE_PLANE_NUMBER(c) (((c) & UNICODE_PLANE_MASK) >> UNICODE_PLANE_SHIFT)
+#define UNICODE_GROUP_NUMBER(c) (((c) & UNICODE_GROUP_MASK) >> UNICODE_GROUP_SHIFT)
+#define UNICODE_CHARACTER(group,plane,row,cell) (((group) << UNICODE_GROUP_SHIFT) | ((plane) << UNICODE_PLANE_SHIFT) | ((row) << UNICODE_ROW_SHIFT) | ((cell) << UNICODE_CELL_SHIFT))
+
+extern int getCharacterName (wchar_t character, char *buffer, size_t size);
+extern int getCharacterByName (wchar_t *character, const char *name);
+
+extern int getCharacterAlias (wchar_t character, char *buffer, size_t size);
+extern int getCharacterByAlias (wchar_t *character, const char *alias);
+
+extern int getCharacterWidth (wchar_t character);
+
+extern int isBrailleCharacter (wchar_t character);
+extern int isIdeographicCharacter (wchar_t character);
+extern int isEmojiSequence (const wchar_t *characters, size_t count);
+extern wchar_t getReplacementCharacter (void);
+
+extern int composeCharacters (
+  size_t *length, const wchar_t *characters,
+  wchar_t *buffer, unsigned int *map
+);
+
+extern size_t decomposeCharacter (
+  wchar_t character, wchar_t *buffer, size_t length
+);
+
+extern wchar_t getBaseCharacter (wchar_t character);
+extern wchar_t getTransliteratedCharacter (wchar_t character);
+
+typedef int CharacterHandler (wchar_t character, void *data);
+extern int handleBestCharacter (wchar_t character, CharacterHandler handleCharacter, void *data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_UNICODE */
diff --git a/Headers/usb_hid.h b/Headers/usb_hid.h
new file mode 100644
index 0000000..eea0243
--- /dev/null
+++ b/Headers/usb_hid.h
@@ -0,0 +1,92 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_HID
+#define BRLTTY_INCLUDED_USB_HID
+
+#include "hid_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  UsbHidRequest_GetReport   = 0X01,
+  UsbHidRequest_GetIdle     = 0X02,
+  UsbHidRequest_GetProtocol = 0X03,
+  UsbHidRequest_SetReport   = 0X09,
+  UsbHidRequest_SetIdle     = 0X0A,
+  UsbHidRequest_SetProtocol = 0X0B
+} UsbHidRequest;
+
+typedef enum {
+  UsbHidReportType_Input   = 0X01,
+  UsbHidReportType_Output  = 0X02,
+  UsbHidReportType_Feature = 0X03
+} UsbHidReportType;
+
+extern const UsbHidDescriptor *usbHidDescriptor (UsbDevice *device);
+
+extern HidItemsDescriptor *usbHidGetItems (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char number,
+  int timeout
+);
+
+extern ssize_t usbHidGetReport (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  unsigned char *buffer,
+  uint16_t size,
+  int timeout
+);
+
+extern ssize_t usbHidSetReport (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  const unsigned char *data,
+  uint16_t length,
+  int timeout
+);
+
+extern ssize_t usbHidGetFeature (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  unsigned char *buffer,
+  uint16_t size,
+  int timeout
+);
+
+extern ssize_t usbHidSetFeature (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  const unsigned char *data,
+  uint16_t length,
+  int timeout
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_HID */
diff --git a/Headers/usb_types.h b/Headers/usb_types.h
new file mode 100644
index 0000000..b3dc709
--- /dev/null
+++ b/Headers/usb_types.h
@@ -0,0 +1,325 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_TYPES
+#define BRLTTY_INCLUDED_USB_TYPES
+
+#include "serial_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Descriptor types. */
+typedef enum {
+  UsbDescriptorType_Device        = 0X01,
+  UsbDescriptorType_Configuration = 0X02,
+  UsbDescriptorType_String        = 0X03,
+  UsbDescriptorType_Interface     = 0X04,
+  UsbDescriptorType_Endpoint      = 0X05,
+  UsbDescriptorType_HID           = 0X21,
+  UsbDescriptorType_Report        = 0X22
+} UsbDescriptorType;
+
+/* Descriptor sizes. */
+typedef enum {
+  UsbDescriptorSize_Device        = 18,
+  UsbDescriptorSize_Configuration =  9,
+  UsbDescriptorSize_String        =  2,
+  UsbDescriptorSize_Interface     =  9,
+  UsbDescriptorSize_Endpoint      =  7,
+  UsbDescriptorSize_HID           =  6,
+  UsbDescriptorSize_Class         =  3
+} UsbDescriptorSize;
+
+typedef enum {
+  UsbSpecificationVersion_1_0 = 0X0100,
+  UsbSpecificationVersion_1_1 = 0X0110,
+  UsbSpecificationVersion_2_0 = 0X0200,
+  UsbSpecificationVersion_3_0 = 0X0300
+} UsbSpecificationVersion;
+
+/* Configuration attributes (bmAttributes). */
+typedef enum {
+  UsbConfigurationAttribute_BusPowered   = 0X80,
+  UsbConfigurationAttribute_SelfPowered  = 0X40,
+  UsbConfigurationAttribute_RemoteWakeup = 0X20
+} UsbConfigurationAttribute;
+
+/* Device and interface classes (bDeviceClass, bInterfaceClass). */
+typedef enum {
+  UsbClass_PerInterface = 0X00,
+  UsbClass_Audio        = 0X01,
+  UsbClass_Comm         = 0X02,
+  UsbClass_Hid          = 0X03,
+  UsbClass_Physical     = 0X05,
+  UsbClass_Printer      = 0X07,
+  UsbClass_MassStorage  = 0X08,
+  UsbClass_Hub          = 0X09,
+  UsbClass_Data         = 0X0A,
+  UsbClass_AppSpec      = 0XFE,
+  UsbClass_VendorSpec   = 0XFF
+} UsbClass;
+
+/* Endpoint numbers (bEndpointAddress). */
+typedef enum {
+  UsbEndpointNumber_Mask = 0X0F
+} UsbEndpointNumber;
+#define USB_ENDPOINT_NUMBER(descriptor) ((descriptor)->bEndpointAddress & UsbEndpointNumber_Mask)
+
+/* Endpoint directions (bEndpointAddress). */
+typedef enum {
+  UsbEndpointDirection_Output = 0X00,
+  UsbEndpointDirection_Input  = 0X80,
+  UsbEndpointDirection_Mask   = 0X80
+} UsbEndpointDirection;
+#define USB_ENDPOINT_DIRECTION(descriptor) ((descriptor)->bEndpointAddress & UsbEndpointDirection_Mask)
+
+/* Endpoint transfer types (bmAttributes). */
+typedef enum {
+  UsbEndpointTransfer_Control     = 0X00,
+  UsbEndpointTransfer_Isochronous = 0X01,
+  UsbEndpointTransfer_Bulk        = 0X02,
+  UsbEndpointTransfer_Interrupt   = 0X03,
+  UsbEndpointTransfer_Mask        = 0X03
+} UsbEndpointTransfer;
+#define USB_ENDPOINT_TRANSFER(descriptor) ((descriptor)->bmAttributes & UsbEndpointTransfer_Mask)
+
+/* Endpoint isochronous types (bmAttributes). */
+typedef enum {
+  UsbEndpointIsochronous_Asynchronous = 0X04,
+  UsbEndpointIsochronous_Adaptable    = 0X08,
+  UsbEndpointIsochronous_Synchronous  = 0X0C,
+  UsbEndpointIsochronous_Mask         = 0X0C
+} UsbEndpointIsochronous;
+#define USB_ENDPOINT_ISOCHRONOUS(descriptor) ((descriptor)->bmAttributes & UsbEndpointIsochronous_Mask)
+
+/* Control transfer recipients. */
+typedef enum {
+  UsbControlRecipient_Device    = 0X00,
+  UsbControlRecipient_Interface = 0X01,
+  UsbControlRecipient_Endpoint  = 0X02,
+  UsbControlRecipient_Other     = 0X03,
+  UsbControlRecipient_Mask      = 0X1F
+} UsbControlRecipient;
+
+/* Control transfer types. */
+typedef enum {
+  UsbControlType_Standard = 0X00,
+  UsbControlType_Class    = 0X20,
+  UsbControlType_Vendor   = 0X40,
+  UsbControlType_Reserved = 0X60,
+  UsbControlType_Mask     = 0X60
+} UsbControlType;
+
+/* Transfer directions. */
+typedef enum {
+  UsbControlDirection_Output = 0X00,
+  UsbControlDirection_Input  = 0X80,
+  UsbControlDirection_Mask   = 0X80
+} UsbControlDirection;
+
+/* Standard control requests. */
+typedef enum {
+  UsbStandardRequest_GetStatus        = 0X00,
+  UsbStandardRequest_ClearFeature     = 0X01,
+  UsbStandardRequest_GetState         = 0X02,
+  UsbStandardRequest_SetFeature       = 0X03,
+  UsbStandardRequest_SetAddress       = 0X05,
+  UsbStandardRequest_GetDescriptor    = 0X06,
+  UsbStandardRequest_SetDescriptor    = 0X07,
+  UsbStandardRequest_GetConfiguration = 0X08,
+  UsbStandardRequest_SetConfiguration = 0X09,
+  UsbStandardRequest_GetInterface     = 0X0A,
+  UsbStandardRequest_SetInterface     = 0X0B,
+  UsbStandardRequest_SynchFrame       = 0X0C
+} UsbStandardRequest;
+
+/* Standard features. */
+typedef enum {
+  UsbFeature_Endpoint_Stall      = 0X00,
+  UsbFeature_Device_RemoteWakeup = 0X01
+} UsbFeature;
+
+typedef struct {
+  uint8_t bLength;         /* Descriptor size in bytes. */
+  uint8_t bDescriptorType; /* Descriptor type. */
+} PACKED UsbDescriptorHeader;
+
+typedef struct {
+  uint8_t bLength;            /* Descriptor size in bytes (18). */
+  uint8_t bDescriptorType;    /* Descriptor type (1 == device). */
+  uint16_t bcdUSB;            /* USB revision number. */
+  uint8_t bDeviceClass;       /* Device class. */
+  uint8_t bDeviceSubClass;    /* Device subclass. */
+  uint8_t bDeviceProtocol;    /* Device protocol. */
+  uint8_t bMaxPacketSize0;    /* Maximum packet size in bytes for endpoint 0. */
+  uint16_t idVendor;          /* Vendor identifier. */
+  uint16_t idProduct;         /* Product identifier. */
+  uint16_t bcdDevice;         /* Product revision number. */
+  uint8_t iManufacturer;      /* String index for manufacturer name. */
+  uint8_t iProduct;           /* String index for product description. */
+  uint8_t iSerialNumber;      /* String index for serial number. */
+  uint8_t bNumConfigurations; /* Number of configurations. */
+} PACKED UsbDeviceDescriptor;
+
+typedef struct {
+  uint8_t bLength;             /* Descriptor size in bytes (9). */
+  uint8_t bDescriptorType;     /* Descriptor type (2 == configuration). */
+  uint16_t wTotalLength;       /* Block size in bytes for all descriptors. */
+  uint8_t bNumInterfaces;      /* Number of interfaces. */
+  uint8_t bConfigurationValue; /* Configuration number. */
+  uint8_t iConfiguration;      /* String index for configuration description. */
+  uint8_t bmAttributes;        /* Configuration attributes. */
+  uint8_t bMaxPower;           /* Maximum power in 2 milliamp units. */
+} PACKED UsbConfigurationDescriptor;
+
+typedef struct {
+  uint8_t bLength;         /* Descriptor size in bytes (2 + numchars/2). */
+  uint8_t bDescriptorType; /* Descriptor type (3 == string). */
+  uint16_t wData[127];     /* 16-bit characters. */
+} PACKED UsbStringDescriptor;
+
+typedef struct {
+  uint8_t bLength;            /* Descriptor size in bytes (9). */
+  uint8_t bDescriptorType;    /* Descriptor type (4 == interface). */
+  uint8_t bInterfaceNumber;   /* Interface number. */
+  uint8_t bAlternateSetting;  /* Interface alternative. */
+  uint8_t bNumEndpoints;      /* Number of endpoints. */
+  uint8_t bInterfaceClass;    /* Interface class. */
+  uint8_t bInterfaceSubClass; /* Interface subclass. */
+  uint8_t bInterfaceProtocol; /* Interface protocol. */
+  uint8_t iInterface;         /* String index for interface description. */
+} PACKED UsbInterfaceDescriptor;
+
+typedef struct {
+  uint8_t bLength;          /* Descriptor size in bytes (7, 9 for audio). */
+  uint8_t bDescriptorType;  /* Descriptor type (5 == endpoint). */
+  uint8_t bEndpointAddress; /* Endpoint number (ored with 0X80 if input. */
+  uint8_t bmAttributes;     /* Endpoint type and attributes. */
+  uint16_t wMaxPacketSize;  /* Maximum packet size in bytes. */
+  uint8_t bInterval;        /* Maximum interval in milliseconds between transfers. */
+  uint8_t bRefresh;
+  uint8_t bSynchAddress;
+} PACKED UsbEndpointDescriptor;
+
+typedef struct {
+  uint8_t bDescriptorType;
+  uint16_t wDescriptorLength;
+} PACKED UsbClassDescriptor;
+
+typedef struct {
+  uint8_t bLength;          /* Descriptor size in bytes (6). */
+  uint8_t bDescriptorType;  /* Descriptor type (33 == HID). */
+  uint16_t bcdHID;
+  uint8_t bCountryCode;
+  uint8_t bNumDescriptors;
+  UsbClassDescriptor descriptors[(0XFF - UsbDescriptorSize_HID) / UsbDescriptorSize_Class];
+} PACKED UsbHidDescriptor;
+
+typedef union {
+  UsbDescriptorHeader header;
+  UsbDeviceDescriptor device;
+  UsbConfigurationDescriptor configuration;
+  UsbStringDescriptor string;
+  UsbInterfaceDescriptor interface;
+  UsbEndpointDescriptor endpoint;
+  UsbHidDescriptor hid;
+  unsigned char bytes[0XFF];
+} UsbDescriptor;
+
+typedef struct {
+  uint8_t bRequestType; /* Recipient, direction, and type. */
+  uint8_t bRequest;     /* Request code. */
+  uint16_t wValue;      /* Request value. */
+  uint16_t wIndex;      /* Recipient number (language for strings). */
+  uint16_t wLength;     /* Data length in bytes. */
+} PACKED UsbSetupPacket;
+
+#define BEGIN_USB_STRING_LIST(name) static const char *const name[] = {
+#define END_USB_STRING_LIST NULL};
+
+typedef struct {
+  const void *data;
+  const SerialParameters *serial;
+
+  const char *const *manufacturers;
+  const char *const *products;
+
+  uint16_t version;
+  uint16_t vendor;
+  uint16_t product;
+  uint16_t parentVendor;
+  uint16_t parentProduct;
+
+  unsigned char configuration;
+  unsigned char interface;
+  unsigned char alternative;
+  unsigned char inputEndpoint;
+  unsigned char outputEndpoint;
+
+  unsigned char disableAutosuspend:1;
+  unsigned char disableEndpointReset:1;
+  unsigned char verifyInterface:1;
+  unsigned char resetDevice:1;
+} UsbChannelDefinition;
+
+#define BEGIN_USB_CHANNEL_DEFINITIONS static const UsbChannelDefinition usbChannelDefinitions[] = {
+#define END_USB_CHANNEL_DEFINITIONS { .vendor=0 } };
+
+typedef struct UsbDeviceStruct UsbDevice;
+typedef struct UsbChooseChannelDataStruct UsbChooseChannelData;
+typedef int UsbDeviceChooser (UsbDevice *device, UsbChooseChannelData *data);
+
+typedef struct {
+  void *const buffer;
+  const size_t size;
+
+  ssize_t length;
+} UsbInputFilterData;
+
+typedef struct UsbSerialDataStruct UsbSerialData;
+typedef int UsbInputFilter (UsbInputFilterData *data);
+
+typedef struct {
+  const char *name;
+
+  int (*enableAdapter) (UsbDevice *device);
+  void (*disableAdapter) (UsbDevice *device);
+
+  int (*makeData) (UsbDevice *device, UsbSerialData **serialData);
+  void (*destroyData) (UsbSerialData *usd);
+
+  int (*setLineConfiguration) (UsbDevice *device, unsigned int baud, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity, SerialFlowControl flowControl);
+  int (*setLineProperties) (UsbDevice *device, unsigned int baud, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity);
+  int (*setBaud) (UsbDevice *device, unsigned int baud);
+  int (*setDataFormat) (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity);
+  int (*setFlowControl) (UsbDevice *device, SerialFlowControl flow);
+
+  int (*setDtrState) (UsbDevice *device, int state);
+  int (*setRtsState) (UsbDevice *device, int state);
+
+  UsbInputFilter *inputFilter;
+  ssize_t (*writeData) (UsbDevice *device, const void *data, size_t size);
+} UsbSerialOperations;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_TYPES */
diff --git a/Headers/utf8.h b/Headers/utf8.h
new file mode 100644
index 0000000..2e92782
--- /dev/null
+++ b/Headers/utf8.h
@@ -0,0 +1,58 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_UTF8
+#define BRLTTY_INCLUDED_UTF8
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+wchar_t *allocateCharacters (size_t count);
+
+#define UTF8_SIZE(bits) (((bits) < 8)? 1: (((bits) + 3) / 5))
+#define UTF8_LEN_MAX UTF8_SIZE(32)
+typedef char Utf8Buffer[UTF8_LEN_MAX + 1];
+
+extern size_t convertCodepointToUtf8 (uint32_t codepoint, Utf8Buffer utf8);
+extern int convertUtf8ToCodepoint (uint32_t *codepoint, const char **utf8, size_t *utfs);
+
+extern size_t convertWcharToUtf8 (wchar_t wc, Utf8Buffer utf8);
+extern wint_t convertUtf8ToWchar (const char **utf8, size_t *utfs);
+
+extern void convertUtf8ToWchars (const char **utf8, wchar_t **characters, size_t count);
+
+extern size_t makeUtf8FromWchars (const wchar_t *characters, unsigned int count, char *buffer, size_t size);
+extern char *getUtf8FromWchars (const wchar_t *characters, unsigned int count, size_t *length);
+
+extern size_t makeWcharsFromUtf8 (const char *text, wchar_t *characters, size_t size);
+extern size_t countUtf8Characters (const char *text);
+
+extern int writeUtf8Character (FILE *stream, wchar_t character);
+extern int writeUtf8Characters (FILE *stream, const wchar_t *characters, size_t count);
+extern int writeUtf8ByteOrderMark (FILE *stream);
+
+extern int isCharsetUTF8 (const char *name);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_UTF8 */
diff --git a/Headers/variables.h b/Headers/variables.h
new file mode 100644
index 0000000..fde1ebe
--- /dev/null
+++ b/Headers/variables.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_VARIABLES
+#define BRLTTY_INCLUDED_VARIABLES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct VariableNestingLevelStruct VariableNestingLevel;
+typedef struct VariableStruct Variable;
+
+typedef struct {
+  const char *name;
+  const char *value;
+} VariableInitializer;
+
+extern VariableNestingLevel *newVariableNestingLevel (VariableNestingLevel *previous, const char *name);
+extern VariableNestingLevel *removeVariableNestingLevel (VariableNestingLevel *vnl);
+
+extern VariableNestingLevel *claimVariableNestingLevel (VariableNestingLevel *vnl);
+extern void releaseVariableNestingLevel (VariableNestingLevel *vnl);
+
+extern void listVariables (VariableNestingLevel *from);
+extern const Variable *findReadableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length);
+extern Variable *findWritableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length);
+
+extern void deleteVariables (VariableNestingLevel *vnl);
+extern int setVariable (Variable *variable, const wchar_t *value, int length);
+
+extern void getVariableName (const Variable *variable, const wchar_t **characters, int *length);
+extern void getVariableValue (const Variable *variable, const wchar_t **characters, int *length);
+
+extern int setStringVariable (VariableNestingLevel *vnl, const char *name, const char *value);
+extern int setStringVariables (VariableNestingLevel *vnl, const VariableInitializer *initializers);
+
+extern VariableNestingLevel *getGlobalVariables (int create);
+extern int setGlobalVariable (const char *name, const char *value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_VARIABLES */
diff --git a/Headers/win_errno.h b/Headers/win_errno.h
new file mode 100644
index 0000000..a30a10d
--- /dev/null
+++ b/Headers/win_errno.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <errno.h>
+
+WIN_ERRNO_STORAGE_CLASS int
+win_toErrno (DWORD error) {
+  switch (error) {
+    case NO_ERROR: return 0;
+    case ERROR_INVALID_OPERATION: return EPERM;
+    case ERROR_FILE_NOT_FOUND: return ENOENT;
+    case ERROR_FILE_EXISTS: return EEXIST;
+    case ERROR_BAD_EXE_FORMAT: return ENOEXEC;
+    case ERROR_INVALID_HANDLE: return EBADF;
+    case ERROR_WAIT_NO_CHILDREN: return ECHILD;
+    case ERROR_NO_SYSTEM_RESOURCES: return EAGAIN;
+    case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM;
+    case ERROR_OUTOFMEMORY: return ENOMEM;
+    case ERROR_ACCESS_DENIED: return EACCES;
+    case ERROR_INVALID_ADDRESS: return EFAULT;
+    case ERROR_BAD_ARGUMENTS: return EINVAL;
+    case ERROR_TOO_MANY_OPEN_FILES: return ENFILE;
+    case ERROR_OPEN_FILES: return EBUSY;
+    case ERROR_HANDLE_DISK_FULL: return ENOSPC;
+    case ERROR_DISK_FULL: return ENOSPC;
+    case ERROR_WRITE_PROTECT: return EROFS;
+    case ERROR_BROKEN_PIPE: return EPIPE;
+    case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG;
+    case ERROR_NOT_SUPPORTED: return ENOSYS;
+    case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY;
+    case ERROR_DEVICE_IN_USE: return EBUSY;
+    case ERROR_DEVICE_REMOVED: return ENODEV;
+    case ERROR_DEVICE_NOT_AVAILABLE: return ENODEV;
+    case ERROR_DEVICE_NOT_CONNECTED: return ENODEV;
+    case WSAEBADF: return EBADF;
+    case WSAEACCES: return EACCES;
+    case WSAEFAULT: return EFAULT;
+    case WSAEINVAL: return EINVAL;
+    case WSAEMFILE: return EMFILE;
+    case WSAEWOULDBLOCK: return EAGAIN;
+    case WSAENAMETOOLONG: return ENAMETOOLONG;
+    case WSAENOTEMPTY: return ENOTEMPTY;
+    default: return EIO;
+  }
+}
diff --git a/Headers/win_pthread.h b/Headers/win_pthread.h
new file mode 100644
index 0000000..0112efa
--- /dev/null
+++ b/Headers/win_pthread.h
@@ -0,0 +1,425 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* This is a minimal pthread implementation based on windows functions.
+ * It is *not* intended to be complete - just complete enough to get
+ * BRLTTY running.
+ */
+
+#ifndef BRLTTY_INCLUDED_WIN_PTHREAD
+#define BRLTTY_INCLUDED_WIN_PTHREAD
+
+#include "prologue.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#include "gettime.h"
+
+#ifndef ETIMEDOUT
+#define ETIMEDOUT EAGAIN
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define winPthreadAssertWindows(expr) do { if (!(expr)) { setSystemErrno(); return errno; } } while (0)
+#define winPthreadAssertPthread(expr) do { int ret = (expr); if (ret) return ret; } while (0)
+#define winPthreadAssert(expr) do { if (!(expr)) return EIO; } while (0)
+
+#if !defined(__struct_timespec_defined) && !defined(__MINGW64_VERSION_MAJOR)
+struct timespec {
+  time_t  tv_sec;  /* Seconds */
+  long    tv_nsec; /* Nanoseconds */
+};
+#endif /* struct timespec */
+
+static inline DWORD pthread_gettimeout_np(const struct timespec *abs_timeout) {
+  struct timeval now;
+  LONG timeout;
+  getRealTime(&now);
+  timeout = (abs_timeout->tv_sec  - now.tv_sec) * 1000 +
+            (abs_timeout->tv_nsec - now.tv_usec * 1000) / 1000000;
+  if (timeout < 0)
+    timeout = 0;
+  return timeout;
+}
+
+/***********
+ * threads *
+ ***********/
+
+typedef DWORD pthread_attr_t;
+typedef HANDLE pthread_t;
+
+static inline pthread_t pthread_self(void) {
+  return GetCurrentThread();
+}
+
+static inline int pthread_equal(pthread_t t1, pthread_t t2) {
+  return t1 == t2;
+}
+
+static inline int pthread_attr_init (pthread_attr_t *attr) {
+  *attr = 0;
+  return 0;
+}
+
+#define PTHREAD_CREATE_DETACHED 1
+static inline int pthread_attr_setdetachstate (pthread_attr_t *attr, int yes) {
+  /* not supported, ignore */
+  return 0;
+}
+
+static inline int pthread_attr_setstacksize (pthread_attr_t *attr, size_t stacksize) {
+  /* not supported, ignore */
+  return 0;
+}
+
+static inline int pthread_attr_destroy (pthread_attr_t *attr) {
+  return 0;
+}
+
+/* "real" cleanup handling not yet implemented */
+typedef struct {
+  void (*routine) (void *);
+  void *arg;
+} __pthread_cleanup_handler;
+
+void pthread_cleanup_push (void (*routine) (void *), void *arg);
+#define pthread_cleanup_push(routine, arg) do { \
+  __pthread_cleanup_handler __cleanup_handler = {routine, arg};
+
+void pthread_cleanup_pop (int execute);
+#define pthread_cleanup_pop(execute) \
+  if (execute) __cleanup_handler.routine(__cleanup_handler.arg); \
+} while (0);
+
+static inline int pthread_create (
+  pthread_t *thread, const pthread_attr_t *attr,
+  void * (*fun) (void *), void *arg
+) {
+  if (attr && *attr)
+    return EINVAL;
+  winPthreadAssertWindows(*thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) fun, arg, 0, NULL));
+  return 0;
+}
+
+static inline int pthread_setcancelstate (int state, int *oldstate) {
+  /* not yet implemented :( */
+  return 0;
+}
+
+static inline int pthread_cancel (pthread_t thread) {
+  /* This is quite harsh :( */
+  winPthreadAssertWindows(TerminateThread(thread, 0));
+  return 0;
+}
+
+static inline void pthread_exit (void *res) {
+  ExitThread((DWORD) (DWORD_PTR) res);
+}
+
+static inline int pthread_join (pthread_t thread, void **res) {
+again:
+  switch (WaitForSingleObject(thread, INFINITE)) {
+    default:
+    case WAIT_FAILED:
+      setSystemErrno();
+      return errno;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      break;
+    case WAIT_TIMEOUT:
+      goto again;
+    }
+  if (res) {
+    DWORD _res;
+    if (GetExitCodeThread(thread, &_res))
+      *res = (void *)(DWORD_PTR)_res;
+  }
+  return 0;
+}
+
+/***********
+ * mutexes *
+ ***********/
+
+#define PTHREAD_MUTEX_INITIALIZER NULL
+typedef HANDLE pthread_mutex_t;
+#define PTHREAD_MUTEX_RECURSIVE 1
+typedef int pthread_mutexattr_t;
+
+static inline int pthread_mutexattr_init(pthread_mutexattr_t *attr) {
+  *attr = PTHREAD_MUTEX_RECURSIVE;
+  return 0;
+}
+
+static inline int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) {
+  if (type != PTHREAD_MUTEX_RECURSIVE)
+    return EINVAL;
+  *attr = type;
+  return 0;
+}
+
+static inline int pthread_mutex_init (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) {
+  if (attr && *attr!=PTHREAD_MUTEX_RECURSIVE)
+    return EINVAL;
+  winPthreadAssertWindows(*mutex = CreateMutex(NULL, FALSE, NULL));
+  return 0;
+}
+
+static inline int pthread_mutex_unlock (pthread_mutex_t *mutex) {
+  winPthreadAssertWindows(ReleaseMutex(*mutex));
+  return 0;
+}
+
+static inline int pthread_mutex_lock (pthread_mutex_t *mutex);
+static inline int __pthread_mutex_alloc_concurrently (pthread_mutex_t *mutex) {
+    HANDLE mutex_init_mutex;
+  /* Get access to one global named mutex to serialize mutex initialization */
+  winPthreadAssertWindows((mutex_init_mutex = CreateMutex(NULL, FALSE, "StarPU mutex init")));
+  winPthreadAssertPthread(pthread_mutex_lock(&mutex_init_mutex));
+  /* Now we are the one that can initialize it */
+  if (!*mutex)
+    winPthreadAssertPthread(pthread_mutex_init(mutex,NULL));
+  winPthreadAssertPthread(pthread_mutex_unlock(&mutex_init_mutex));
+    winPthreadAssertWindows(CloseHandle(mutex_init_mutex));
+  return 0;
+}
+
+static inline int pthread_mutex_lock (pthread_mutex_t *mutex) {
+  if (!*mutex)
+    __pthread_mutex_alloc_concurrently (mutex);
+again:
+  switch (WaitForSingleObject(*mutex, INFINITE)) {
+    default:
+    case WAIT_FAILED:
+      setSystemErrno();
+      return errno;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      return 0;
+    case WAIT_TIMEOUT:
+      goto again;
+  }
+}
+
+static inline int pthread_mutex_trylock (pthread_mutex_t *mutex) {
+  if (!*mutex)
+    __pthread_mutex_alloc_concurrently (mutex);
+  switch (WaitForSingleObject(*mutex, 0)) {
+    default:
+    case WAIT_FAILED:
+      setSystemErrno();
+      return errno;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      return 0;
+    case WAIT_TIMEOUT:
+      return EBUSY;
+  }
+}
+
+static inline int pthread_mutex_destroy (pthread_mutex_t *mutex) {
+  winPthreadAssertWindows(CloseHandle(*mutex));
+  *mutex = INVALID_HANDLE_VALUE;
+  return 0;
+}
+
+/**************
+ * semaphores *
+ **************/
+
+typedef HANDLE sem_t;
+
+static inline int sem_init(sem_t *sem, int pshared, unsigned int value) {
+  winPthreadAssertWindows(*sem = CreateSemaphore(NULL, value, MAXLONG, NULL));
+  return 0;
+}
+
+static inline int do_sem_wait(sem_t *sem, DWORD timeout) {
+  switch (WaitForSingleObject(*sem, timeout)) {
+    default:
+    case WAIT_FAILED:
+      setSystemErrno();
+      return -1;
+    case WAIT_TIMEOUT:
+      errno = EAGAIN;
+      return -1;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      return 0;
+  }
+}
+
+#define sem_wait(sem) do_sem_wait(sem, INFINITE)
+#define sem_trywait(sem) do_sem_wait(sem, 0)
+
+static inline int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout) {
+  return do_sem_wait(sem, pthread_gettimeout_np(abs_timeout));
+}
+
+static inline int sem_post(sem_t *sem) {
+  winPthreadAssertWindows(ReleaseSemaphore(*sem, 1, NULL));
+  return 0;
+}
+
+static inline int sem_destroy(sem_t *sem) {
+  winPthreadAssertWindows(CloseHandle(*sem));
+  return 0;
+}
+
+/**************
+ * conditions *
+ **************/
+
+typedef struct {
+  HANDLE sem;
+  volatile unsigned nbwait;
+} pthread_cond_t;
+#define PTHREAD_COND_INITIALIZER { NULL, 0}
+
+typedef unsigned pthread_condattr_t;
+
+static inline int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr) {
+  if (attr)
+    return EINVAL;
+  winPthreadAssertWindows(cond->sem = CreateSemaphore(NULL, 0, MAXLONG, NULL));
+  cond->nbwait = 0;
+  return 0;
+}
+
+static inline int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *time) {
+  if (!cond->sem)
+    winPthreadAssertPthread(pthread_cond_init(cond,NULL));
+  cond->nbwait++;
+  winPthreadAssertPthread(pthread_mutex_unlock(mutex));
+  switch (WaitForSingleObject(cond->sem, pthread_gettimeout_np(time))) {
+    default:
+    case WAIT_FAILED:
+      setSystemErrno();
+      winPthreadAssertPthread(pthread_mutex_lock(mutex));
+      return errno;
+    case WAIT_TIMEOUT:
+      winPthreadAssertPthread(pthread_mutex_lock(mutex));
+      return ETIMEDOUT;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      break;
+  }
+  winPthreadAssertPthread(pthread_mutex_lock(mutex));
+  cond->nbwait--;
+  return 0;
+}
+
+static inline int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex) {
+  if (!cond->sem)
+    winPthreadAssertPthread(pthread_cond_init(cond,NULL));
+  cond->nbwait++;
+  winPthreadAssertPthread(pthread_mutex_unlock(mutex));
+again:
+  switch (WaitForSingleObject(cond->sem, INFINITE)) {
+    case WAIT_FAILED:
+      setSystemErrno();
+      winPthreadAssert(!pthread_mutex_lock(mutex));
+      return errno;
+    case WAIT_TIMEOUT:
+      goto again;
+    case WAIT_ABANDONED:
+    case WAIT_OBJECT_0:
+      break;
+  }
+  winPthreadAssertPthread(pthread_mutex_lock(mutex));
+  cond->nbwait--;
+  return 0;
+}
+
+static inline int pthread_cond_signal (pthread_cond_t *cond) {
+  if (!cond->sem)
+    winPthreadAssertPthread(pthread_cond_init(cond,NULL));
+  if (cond->nbwait)
+    ReleaseSemaphore(cond->sem, 1, NULL);
+  return 0;
+}
+
+static inline int pthread_cond_broadcast (pthread_cond_t *cond) {
+  if (!cond->sem)
+    winPthreadAssertPthread(pthread_cond_init(cond,NULL));
+  ReleaseSemaphore(cond->sem, cond->nbwait, NULL);
+  return 0;
+}
+
+static inline int pthread_cond_destroy (pthread_cond_t *cond) {
+  if (cond->sem) {
+  winPthreadAssertWindows(CloseHandle(cond->sem));
+    cond->sem = NULL;
+  }
+  return 0;
+}
+
+/*******
+ * TLS *
+ *******/
+
+typedef DWORD pthread_key_t;
+#define PTHREAD_ONCE_INIT {PTHREAD_MUTEX_INITIALIZER, 0}
+typedef struct {
+  pthread_mutex_t mutex;
+  unsigned done;
+} pthread_once_t;
+
+static inline int pthread_once (pthread_once_t *once, void (*oncefun)(void)) {
+  winPthreadAssertPthread(pthread_mutex_lock(&once->mutex));
+  if (!once->done) {
+    oncefun();
+    once->done = 1;
+  }
+  winPthreadAssertPthread(pthread_mutex_unlock(&once->mutex));
+  return 0;
+}
+
+static inline int pthread_key_create (pthread_key_t *key, void (*freefun)(void *)) {
+  DWORD res;
+  winPthreadAssertWindows((res = TlsAlloc()) != 0xFFFFFFFF);
+  *key = res;
+  return 0;
+}
+
+static inline int pthread_key_delete (pthread_key_t key) {
+  winPthreadAssertWindows(TlsFree(key));
+  return 0;
+}
+
+static inline void *pthread_getspecific (pthread_key_t key) {
+  return TlsGetValue(key);
+}
+
+static inline int pthread_setspecific (pthread_key_t key, const void *data) {
+  winPthreadAssertWindows(TlsSetValue(key, (LPVOID) data));
+  return 0;
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_WIN_PTHREAD */
diff --git a/Headers/xsel.h b/Headers/xsel.h
new file mode 100644
index 0000000..de891a1
--- /dev/null
+++ b/Headers/xsel.h
@@ -0,0 +1,53 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 2019 by Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_XSEL
+#define BRLAPI_INCLUDED_XSEL
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  Atom sel, selProp;
+  Window selWindow;
+
+  Atom targetsAtom, utf8, incr;
+
+#ifdef HAVE_X11_EXTENSIONS_XFIXES_H
+  int xfixesEventBase, xfixesErrorBase;
+  Bool haveXfixes;
+#endif /* HAVE_X11_EXTENSIONS_XFIXES_H */
+} XSelData;
+
+typedef void (*XSelUpdate)(const char *data, unsigned long size);
+
+void XSelInit(Display *dpy, XSelData *data);
+void XSelSet(Display *dpy, XSelData *data);
+int XSelProcess(Display *dpy, XSelData *data, XEvent *ev, const char *content, XSelUpdate update);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_XSEL */
diff --git a/Initramfs/Dracut/Makefile.in b/Initramfs/Dracut/Makefile.in
new file mode 100644
index 0000000..8a7f6de
--- /dev/null
+++ b/Initramfs/Dracut/Makefile.in
@@ -0,0 +1,34 @@
+MODULE_NAME = 99brltty
+MODULES_DIRECTORY = /usr/lib/dracut/modules.d
+MODULE_DIRECTORY = $(INSTALL_ROOT)$(MODULES_DIRECTORY)/$(MODULE_NAME)
+
+install: install-module install-documentation install-configuration-files
+
+install-module: install-module-directory
+	for file in *.sh; \
+	do $(INSTALL_SCRIPT) $$file $(MODULE_DIRECTORY); \
+	done
+
+install-documentation: install-module-directory
+	$(INSTALL_DATA) README $(MODULE_DIRECTORY)
+
+install-module-directory:
+	$(INSTALL_DIRECTORY) $(MODULE_DIRECTORY)
+
+OPTIONS_FILE = cmdline
+CONFIGURATION_FILE = dracut.conf
+INITRAMFS_SUBDIRECTORY = Initramfs
+INITRAMFS_DIRECTORY = $(TABLES_DIRECTORY)/$(INITRAMFS_SUBDIRECTORY)
+INSTALL_INITRAMFS_DIRECTORY = $(INSTALL_ROOT)$(INITRAMFS_DIRECTORY)
+
+install-initramfs-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_INITRAMFS_DIRECTORY)
+
+install-configuration-files: install-initramfs-directory
+	$(INSTALL_DATA) $(OPTIONS_FILE) $(INSTALL_INITRAMFS_DIRECTORY)
+	$(INSTALL_DATA) $(CONFIGURATION_FILE) $(INSTALL_INITRAMFS_DIRECTORY)
+	sed -e '/^ *BRLTTY_DRACUT_LOCALE *= *"/s/".*"/"${LANG}"/' -i $(INSTALL_INITRAMFS_DIRECTORY)/$(CONFIGURATION_FILE)
+
+uninstall:
+	-rm -f -r  $(MODULE_DIRECTORY)
+
diff --git a/Initramfs/Dracut/README b/Initramfs/Dracut/README
new file mode 100644
index 0000000..154e625
--- /dev/null
+++ b/Initramfs/Dracut/README
@@ -0,0 +1,215 @@
+Braille Support for Dracut
+==========================
+
+This module implements braille accessibility within the initramfs (initial
+ramdisk file system). If BRLTTY is installed (``/usr/bin/brltty`` exists) then
+it is added to the image. The braille drivers, text tables, contraction tables,
+attributes tables, keyboard tables, speech drivers, and preferences file
+configured via ``/etc/brltty.conf`` are included. Local customizations within
+``/etc/xdg/brltty/`` are also included.
+
+BRLTTY is started as early as possible by the initramfs so that braille
+accessibility will be active when (should the need arise) control is
+transferred to the emergency shell due to a problem preparing the root file
+system. Additionally, braille accessibility is active at any of the specifiable
+initramfs breakpoints (see the ``rd.break`` kernel parameter) except for the
+one named ``cmdline`` because it occurs earlier than the point at which BRLTTY
+is started.
+
+The braille driver can start immediately if either serial or USB communication
+is being used. If Bluetooth communication is being used then the braille driver
+won't start until the ``initqueue`` phase because the Systemd service manager
+isn't available until then.
+
+Bluetooth support within the initramfs is disabled by default because it's
+insecure. The reason for this is that the link keys for those devices that have
+already been paired on the full system are copied into the initramfs image so
+that repairing won't be necessary. If you absolutely must use Bluetooth
+communication, or if you just want to and this issue is of no concern to you,
+then enable the ``BRLTTY_DRACUT_BLUETOOTH_SUPPORT`` option within the file
+``dracut.conf`` (which is in ``/etc/brltty/Initramfs/``).
+
+Environment Variables
+---------------------
+
+A number of environment variables may be used to influence how BRLTTY is
+installed into the initramfs image. Their names all begin with the
+``BRLTTY_DRACUT_`` prefix. In addition to exporting them from the current
+environment, they may be defined within the optional file ``dracut.conf``
+(which is in ``/etc/brltty/Initramfs/``). Within this file:
+
+* Blank lines are ignored.
+* Lines that start with a hash [``#``] are comments.
+* All other lines should be simple shell-style assignments. For example::
+
+    NAME="string"
+
+The following environment variables are supported:
+
+BRLTTY_DRACUT_LOCALE
+  The preferred locale (used to determine the default text and contraction
+  tables). For example::
+
+    BRLTTY_DRACUT_LOCALE=cs_CZ.UTF-8
+
+  If needed, i.e. if the text and contraction tables haven't both been
+  configured via ``/etc/brltty.conf``, this information must be explicitly set
+  because it can't be determined from the system configuration when building
+  the image (see `<https://bugzilla.redhat.com/show_bug.cgi?id=1584036>`_).
+
+BRLTTY_DRACUT_BRAILLE_DRIVERS
+  A space-separated list of additional braille drivers to include. For
+  example::
+
+    BRLTTY_DRACUT_BRAILLE_DRIVERS="pm eu"
+
+BRLTTY_DRACUT_BLUETOOTH_SUPPORT
+  Whether or not Bluetooth support should be included within the initramfs. For
+  example::
+
+    BRLTTY_DRACUT_BLUETOOTH_SUPPORT=no
+
+  Set this variable to ``yes`` if communication with your braille device is via
+  Bluetooth. Note that this option is insecure because the link keys for all of
+  your paired Bluetooth devices will be stored within the initramfs image.
+
+BRLTTY_DRACUT_TEXT_TABLES
+  A space-separated list of additional text tables to include. For example::
+
+    BRLTTY_DRACUT_TEXT_TABLES="de fr"
+
+BRLTTY_DRACUT_CONTRACTION_TABLES
+  A space-separated list of additional contraction tables to include. For
+  example::
+
+    BRLTTY_DRACUT_CONTRACTION_TABLES="de-g2 fr-g2"
+
+BRLTTY_DRACUT_ATTRIBUTES_TABLES
+  A space-separated list of additional attributes tables to include. For
+  example::
+
+    BRLTTY_DRACUT_ATTRIBUTES_TABLES="upper_lower left_right"
+
+BRLTTY_DRACUT_KEYBOARD_TABLES
+  A space-separated list of additional keyboard tables to include. For
+  example::
+
+    BRLTTY_DRACUT_KEYBOARD_TABLES="keypad desktop"
+
+BRLTTY_DRACUT_SPEECH_DRIVERS
+  A space-separated list of additional speech drivers to include. For example::
+
+    BRLTTY_DRACUT_SPEECH_DRIVERS="es fl"
+
+Kernel Command Line Parameters
+------------------------------
+
+This module interprets kernel command line parameters that begin with the
+``rd.brltty.`` prefix. Explicit values for these parameters can, of course, be
+specified on the kernel command line (e.g. within ``grub.cfg`` or within
+``/etc/default/grub``). In addition, default values for them can be defined
+within the optional file ``cmdline`` (which is in ``/etc/brltty/Initramfs/``).
+
+The order of precedence, from lowest to highest, for a value is:
+
+* BRLTTY built-in default
+* ``/etc/brltty.conf``
+* initramfs override
+* ``/etc/brltty/Initramfs/cmdline``
+* kernel command line
+
+Blank lines are ignored. A hash [``#``] anywhere on a line begins a comment
+which continues till the end of that line - only those characters before it are
+significant. Any number of space-separated options may be specified on a line.
+The syntax for an option is ``name=value`` (without the ``rd.brltty.`` prefix).
+The ``=value`` part is optional - if there's no ``=`` then the value is
+``yes``.
+
+Disabling BRLTTY
+````````````````
+
+Given that BRLTTY is a rather complex application, there are a number of kernel
+parameters that can be used to selectively disable a number of its components.
+In all cases, if any of these parameters isn't specified then it defaults to
+``1`` (enabled). They are:
+
+  =========================  =========================================
+  Kernel Parameter           Effect
+  -------------------------  -----------------------------------------
+  ``rd.brltty=0``            Completely disable BRLTTY.
+  ``rd.brltty.bluetooth=0``  Disable Bluetooth support.
+  ``rd.brltty.sound=0``      Disable speech drivers and sound support.
+  ``rd.brltty.pulse=0``      Disable the Pulse Audio server.
+  ``rd.brltty.speechd=0``    Disable the Speech Dispatcher server.
+  =========================  =========================================
+
+Specifying BRLTTY options
+`````````````````````````
+
+All of BRLTTY's options that can be specified via environment variables (see
+``man brltty`` for the full list) can be specified via kernel parameters. For
+example, the kernel parameter ``rd.brltty.braille_driver=vo`` is mapped to the
+environment variable assignment ``BRLTTY_BRAILLE_DRIVER=vo``. Some of the more
+interesting ones are:
+
+  ================================  ===============================
+  Kernel Parameter                  Default Value
+  --------------------------------  -------------------------------
+  ``rd.brltty.braille_driver``      setting in ``/etc/brltty.conf``
+  ``rd.brltty.braille_parameters``  setting in ``/etc/brltty.conf``
+  ``rd.brltty.braille_device``      setting in ``/etc/brltty.conf``
+  ``rd.brltty.text_table``          setting in ``/etc/brltty.conf``
+  ``rd.brltty.attributes_table``    setting in ``/etc/brltty.conf``
+  ``rd.brltty.contraction_table``   setting in ``/etc/brltty.conf``
+  ``rd.brltty.keyboard_table``      setting in ``/etc/brltty.conf``
+  ``rd.brltty.speech_driver``       setting in ``/etc/brltty.conf``
+  ``rd.brltty.speech_parameters``   setting in ``/etc/brltty.conf``
+  ``rd.brltty.log_file``            ``/run/initramfs/brltty.log``
+  ``rd.brltty.log_level``           setting in ``/etc/brltty.conf``
+  ================================  ===============================
+
+Speech Drivers
+--------------
+
+Since speech drivers, by their very nature, require sound:
+
+* None of them can start until the ``initqueue`` hook has been executed.
+
+* None of them can start if sound has been disabled (via the rd.brltty.sound=0
+  kernel parameter).
+
+Speech defaults to being off even if a speech driver has been configured via
+``/etc/brltty.conf``. This is because your preferred speech driver may not work
+all that well, or may even cause problems, when used within the more primitive
+initramfs environment. If you'd like to use speech then you need to explicitly
+set the driver. While you can do so via the ``rd.brltty.speech_driver`` kernel
+command line parameter, the easiest way to configure a persistent setting is
+via the ``speech_driver`` option in ``/etc/brltty/Initramfs/cmdline``.
+
+Here's how well each of the speech drivers works:
+
+en
+  The ``en`` [eSpeak-NG] speech driver works.
+
+es
+  The ``es`` [eSpeak] speech driver works. It requires Pulse Audio, so:
+
+  * It doesn't start until the ``initqueue`` hook has been executed.
+
+  * It can't start if Pulse Audio fails to start or has been disabled (via
+    the rd.brltty.pulse=0 kernel parameter).
+
+fl
+  The ``fl`` [Festival Lite] speech driver doesn't work - it runs but doesn't
+  speak. This appears to be due to a bug within Festival Lite itself because
+  it also remains silent when tested on the full system.
+
+fv
+  The ``fv`` [Festival] speech driver doesn't work - it crashes. This appears
+  to be due to a bug within Festival itself because it also crashes when tested
+  on the full system.
+
+sd
+  The ``sd`` [Speech Dispatcher] speech driver hasn't been tested and probably
+  won't work.
+
diff --git a/Initramfs/Dracut/alsa-start.sh b/Initramfs/Dracut/alsa-start.sh
new file mode 100755
index 0000000..26ab4a1
--- /dev/null
+++ b/Initramfs/Dracut/alsa-start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+getargbool 1 rd.brltty && getargbool 1 rd.brltty.sound && {
+   alsaunmute
+}
diff --git a/Initramfs/Dracut/bluetooth-start.sh b/Initramfs/Dracut/bluetooth-start.sh
new file mode 100755
index 0000000..0d574d1
--- /dev/null
+++ b/Initramfs/Dracut/bluetooth-start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+getargbool 1 rd.brltty && getargbool 1 rd.brltty.bluetooth && {
+   systemctl -q is-active bluetooth || {
+      systemctl --no-block start bluetooth
+   }
+}
diff --git a/Initramfs/Dracut/brltty-start.sh b/Initramfs/Dracut/brltty-start.sh
new file mode 100755
index 0000000..ea51437
--- /dev/null
+++ b/Initramfs/Dracut/brltty-start.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+export BRLTTY_START_MESSAGE="initramfs starting"
+export BRLTTY_STOP_MESSAGE="initramfs finished"
+
+BRLTTY_OVERRIDE_PREFERENCE="braille-keyboard-enabled=yes"
+BRLTTY_OVERRIDE_PREFERENCE+=",braille-input-mode=text"
+export BRLTTY_OVERRIDE_PREFERENCE
+
+export BRLTTY_SCREEN_DRIVER="lx"
+export BRLTTY_SPEECH_DRIVER="no"
+
+export BRLTTY_WRITABLE_DIRECTORY="/run"
+export BRLTTY_PID_FILE="${BRLTTY_WRITABLE_DIRECTORY}/brltty.pid"
+export BRLTTY_LOG_FILE="${BRLTTY_WRITABLE_DIRECTORY}/initramfs/brltty.log"
+
+export BRLTTY_UPDATABLE_DIRECTORY="/etc"
+export BRLTTY_PREFERENCES_FILE="${BRLTTY_UPDATABLE_DIRECTORY}/brltty.prefs"
+
+brlttySetOption() {
+   local option="${1}"
+   local name="${option%%=*}"
+
+   if [ "${name}" = "${option}" ]
+   then
+      local value="yes"
+   else
+      local value="${option#*=}"
+   fi
+
+   [ -z "${name}" ] || {
+      name="${name^^?}"
+      export "BRLTTY_${name}=${value}"
+   }
+}
+
+brlttySetConfiguredOptions() {
+   local file="/etc/brltty/Initramfs/cmdline"
+
+   [ -f "${file}" ] && [ -r "${file}" ] && {
+      local line
+
+      while read line
+      do
+         set -- ${line%%#*}
+         local option
+
+         for option
+         do
+            brlttySetOption "${option}"
+         done
+      done <"${file}"
+   }
+}
+
+brlttySetExplicitOptions() {
+   local option
+
+   for option
+   do
+      [[ "${option}" =~ ^"rd.brltty."(.*) ]] && {
+         brlttySetOption "${BASH_REMATCH[1]}"
+      }
+   done
+}
+
+brlttySetConfiguredOptions
+brlttySetExplicitOptions $(getcmdline)
+getargbool 1 rd.brltty.sound || export BRLTTY_SPEECH_DRIVER="no"
+
+getargbool 1 rd.brltty && (
+   # Give the kernel a bit of time to finish creating the /dev/input/ devices
+   # (e.g. so that brltty can perform keyboard discovery for keyboard tables)
+   # without delaying the boot.
+
+   sleep 1
+   brltty -E +n
+) &
diff --git a/Initramfs/Dracut/brltty-stop.sh b/Initramfs/Dracut/brltty-stop.sh
new file mode 100755
index 0000000..27611ab
--- /dev/null
+++ b/Initramfs/Dracut/brltty-stop.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+brltty -E -C
diff --git a/Initramfs/Dracut/cmdline b/Initramfs/Dracut/cmdline
new file mode 100644
index 0000000..39aefd3
--- /dev/null
+++ b/Initramfs/Dracut/cmdline
@@ -0,0 +1,16 @@
+# This file is where you can specify any rd.brltty. kernel parameters that are
+# to be included within the initramfs image and applied as default values.
+
+# The order of precedence, from lowest to highest, for an option's value is:
+# * BRLTTY built-in default
+# * /etc/brltty.conf
+# * initramfs override
+# * this file
+# * kernel command line
+
+# Blank lines are ignored. A hash [#] anywhere on a line begins a comment which
+# continues till the end of that line - only those characters before it are
+# significant. Any number of space-separated options may be specified on a
+# line. The syntax for an option is name=value (without the rd.brltty. prefix).
+# The =value part is optional - if there's no = then the value is yes.
+
diff --git a/Initramfs/Dracut/dbus-start.sh b/Initramfs/Dracut/dbus-start.sh
new file mode 100755
index 0000000..fd9e210
--- /dev/null
+++ b/Initramfs/Dracut/dbus-start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+getargbool 1 rd.brltty && getargbool 1 rd.brltty.bluetooth && {
+   systemctl -q is-active dbus || {
+      systemctl --no-block start dbus
+   }
+}
diff --git a/Initramfs/Dracut/dracut.conf b/Initramfs/Dracut/dracut.conf
new file mode 100644
index 0000000..18621d2
--- /dev/null
+++ b/Initramfs/Dracut/dracut.conf
@@ -0,0 +1,27 @@
+# The preferred locale (used to determine the default text and contraction tables).
+BRLTTY_DRACUT_LOCALE=""
+
+# A space-separated list of additional braille drivers to include.
+BRLTTY_DRACUT_BRAILLE_DRIVERS=""
+
+# Whether or not Bluetooth support should be included within the initramfs.
+# Set this variable to yes if communication with your braille device is via
+# Bluetooth. Note that this option is insecure because the link keys for all of
+# your paired Bluetooth devices will be stored within the initramfs image.
+BRLTTY_DRACUT_BLUETOOTH_SUPPORT=no
+
+# A space-separated list of additional text tables to include.
+BRLTTY_DRACUT_TEXT_TABLES=""
+
+# A space-separated list of additional contraction tables to include.
+BRLTTY_DRACUT_CONTRACTION_TABLES=""
+
+# A space-separated list of additional attributes tables to include.
+BRLTTY_DRACUT_ATTRIBUTES_TABLES=""
+
+# A space-separated list of additional keyboard tables to include.
+BRLTTY_DRACUT_KEYBOARD_TABLES=""
+
+# A space-separated list of additional speech drivers to include.
+BRLTTY_DRACUT_SPEECH_DRIVERS=""
+
diff --git a/Initramfs/Dracut/module-setup.sh b/Initramfs/Dracut/module-setup.sh
new file mode 100755
index 0000000..cd49a25
--- /dev/null
+++ b/Initramfs/Dracut/module-setup.sh
@@ -0,0 +1,326 @@
+#!/bin/bash
+
+# called by dracut
+check() {
+   require_binaries brltty brltty-lsinc || return 1
+   return 0
+}
+
+# called by dracut
+depends() {
+   return 0
+}
+
+# called by dracut
+installkernel() {
+   instmods pcspkr uinput
+   [ -d "${initdir}/etc/bluetooth" ] && instmods =drivers/bluetooth =net/bluetooth
+   [ -d "${initdir}/etc/alsa" ] && instmods =sound
+   return 0
+}
+
+# called by dracut
+install() {
+   brlttyImportInstallOptions
+   local -A includedDrivers
+
+   local BRLTTY_EXECUTABLE_PATH="/usr/bin/brltty"
+   inst_binary "${BRLTTY_EXECUTABLE_PATH}"
+   local brlttyLog="$(LC_ALL="${BRLTTY_DRACUT_LOCALE:-${LANG}}" "${BRLTTY_EXECUTABLE_PATH}" -E -v -e -ldebug 2>&1)"
+   
+   export BRLTTY_CONFIGURATION_FILE="/etc/brltty.conf"
+   brlttyIncludeDataFiles "${BRLTTY_CONFIGURATION_FILE}"
+
+   brlttyIncludeDataFiles $(brlttyGetProperty "including data file")
+   brlttyIncludeScreenDrivers lx
+
+   brlttyIncludeBrailleDrivers $(brlttyGetConfiguredDrivers braille)
+   brlttyIncludeBrailleDrivers ${BRLTTY_DRACUT_BRAILLE_DRIVERS}
+
+   brlttyIncludeSpeechDrivers $(brlttyGetConfiguredDrivers speech)
+   brlttyIncludeSpeechDrivers ${BRLTTY_DRACUT_SPEECH_DRIVERS}
+      
+   brlttyIncludeTables Text        ttb ${BRLTTY_DRACUT_TEXT_TABLES}
+   brlttyIncludeTables Contraction ctb ${BRLTTY_DRACUT_CONTRACTION_TABLES}
+   brlttyIncludeTables Attributes  atb ${BRLTTY_DRACUT_ATTRIBUTES_TABLES}
+   brlttyIncludeTables Keyboard    ktb ${BRLTTY_DRACUT_KEYBOARD_TABLES}
+
+   brlttyInstallPreferencesFile "/etc/brltty.prefs"
+   brlttyInstallDirectories "/etc/xdg/brltty"
+   inst_simple "/etc/brltty/Initramfs/cmdline"
+
+   if [ "${BRLTTY_DRACUT_BLUETOOTH_SUPPORT}" = "yes" ]
+   then
+      brlttyIncludeBluetoothSupport
+   fi
+
+   inst_hook cmdline 05 "${moddir}/brltty-start.sh"
+   inst_hook cleanup 95 "${moddir}/brltty-stop.sh"
+
+   dracut_need_initqueue
+}
+
+brlttyInstallPreferencesFile() {
+   local path="${1}"
+   local file=$(brlttyGetProperty "Preferences File")
+
+   if [ -n "${file}" ]
+   then
+      if [ "${file}" = "${file#/}" ]
+      then
+         local directory=$(brlttyGetProperty "Updatable Directory")
+
+         if [ -n "${directory}" ]
+         then
+            file="${directory}/${file}"
+         fi
+      fi
+
+      if [ -f "${file}" ]
+      then
+         inst_simple "${file}" "${path}"
+      fi
+   fi
+}
+
+brlttyIncludeBrailleDrivers() {
+   local code
+
+   for code
+   do
+      brlttyIncludeDriver b "${code}" || continue
+
+      local directory="/etc/brltty/Input/${code}"
+      brlttyIncludeDataFiles "${directory}/"*.ktb
+      inst_multiple -o "${directory}/"*.txt
+   done
+}
+
+brlttyIncludeSpeechDrivers() {
+   local code
+
+   for code
+   do
+      brlttyIncludeDriver s "${code}" || continue
+
+      case "${code}"
+      in
+         en)
+            inst_binary espeak-ng
+            brlttyInstallDirectories "/usr/share/espeak-ng-data"
+            ;;
+
+         es)
+            inst_binary espeak
+            brlttyInstallDirectories "/usr/share/espeak-data"
+            brlttyIncludePulseAudioSupport
+            ;;
+
+         fl)
+            inst_binary flite
+            ;;
+
+         fv)
+            inst_binary festival
+            brlttyInstallDirectories /etc/festival
+            brlttyInstallDirectories /usr/lib*/festival
+            brlttyInstallDirectories /usr/share/festival/lib
+            ;;
+
+         sd)
+            inst_binary speech-dispatcher
+            brlttyInstallDirectories /etc/speech-dispatcher
+            brlttyInstallDirectories /usr/lib*/speech-dispatcher
+            brlttyInstallDirectories /usr/lib*/speech-dispatcher-modules
+            brlttyInstallDirectories /usr/share/speech-dispatcher
+            brlttyInstallDirectories /usr/share/sounds/speech-dispatcher
+            brlttyInstallSystemdUnits speech-dispatcherd.service
+            inst_hook initqueue 98 "${moddir}/speechd-start.sh"
+            ;;
+      esac
+
+      brlttyIncludeAlsaSupport
+   done
+}
+
+brlttyIncludeScreenDrivers() {
+   local code
+
+   for code
+   do
+      brlttyIncludeDriver x "${code}" || continue
+   done
+}
+
+brlttyIncludeDriver() {
+   local type="${1}"
+   local code="${2}"
+
+   [ "${code}" = "no" ] && return 1
+   local driver="${type}${code}"
+
+   [ -n "${includedDrivers[${driver}]}" ] && return 2
+   includedDrivers[${driver}]=1
+
+   inst_libdir_file "brltty/libbrltty${driver}.so*"
+   return 0
+}
+
+brlttyIncludeTables() {
+   local subdirectory="${1}"
+   local extension="${2}"
+   shift 2
+   local name
+
+   for name
+   do
+      brlttyIncludeDataFiles "/etc/brltty/${subdirectory}/${name}.${extension}"
+   done
+}
+
+brlttyIncludeDataFiles() {
+   local file
+
+   while read -r file
+   do
+      inst_simple "${file}"
+   done < <(brltty-lsinc "${@}")
+}
+
+brlttyGetConfiguredDrivers() {
+   local category="${1}"
+   brlttyGetProperty "checking for ${category} driver"
+}
+
+brlttyGetProperty() {
+   local name="${1}"
+   echo "${brlttyLog}" | awk "/: *${name} *:/ {print \$NF}"
+}
+
+brlttyImportInstallOptions() {
+   local file="/etc/brltty/Initramfs/dracut.conf"
+   [ -f "${file}" ] && [ -r "${file}" ] && . "${file}"
+}
+
+brlttyIncludePulseAudioSupport() {
+   [ -d "${initdir}/etc/pulse" ] && return 0
+
+   brlttyInstallDirectories /etc/pulse
+   brlttyInstallDirectories /usr/share/pulseaudio
+   brlttyInstallDirectories /usr/lib*/pulseaudio
+   brlttyInstallDirectories /usr/lib*/pulse-*
+   brlttyInstallDirectories /usr/libexec/pulse
+
+   inst_multiple -o pulseaudio pactl pacmd
+   inst_multiple -o pamon paplay parec parecord
+
+   brlttyAddUserEntries pulse
+   brlttyAddGroupEntries pulse pulse-access pulse-rt
+
+   brlttyIncludeAlsaSupport
+   brlttyIncludeMessageBusSupport
+   inst_simple /etc/dbus-1/system.d/pulseaudio-system.conf
+
+   inst_binary chmod
+   inst_hook initqueue 97 "${moddir}/pulse-start.sh"
+   inst_hook cleanup 98 "${moddir}/pulse-stop.sh"
+}
+
+brlttyIncludeAlsaSupport() {
+   [ -d "${initdir}/etc/alsa" ] && return 0;
+
+   brlttyInstallDirectories /etc/alsa
+   rm -f "${initdir}/etc/alsa/conf.d/"*
+
+   brlttyInstallDirectories /usr/share/alsa
+   brlttyInstallDirectories /usr/lib/alsa
+   brlttyInstallDirectories /usr/lib*/alsa-lib
+
+   inst_multiple -o alsactl alsaucm alsamixer amixer aplay
+   inst_script alsaunmute
+
+   inst_hook initqueue 96 "${moddir}/alsa-start.sh"
+}
+
+brlttyIncludeBluetoothSupport() {
+   [ -d "${initdir}/etc/bluetooth" ] && return 0
+
+   brlttyInstallDirectories /etc/bluetooth
+   brlttyInstallDirectories /var/lib/bluetooth
+
+   inst_multiple -o bluetoothctl hciconfig hcitool sdptool
+   inst_binary /usr/libexec/bluetooth/bluetoothd
+   brlttyInstallSystemdUnits bluetooth.service bluetooth.target
+
+   inst_hook initqueue 97 "${moddir}/bluetooth-start.sh"
+   brlttyIncludeMessageBusSupport
+}
+
+brlttyIncludeMessageBusSupport() {
+   [ -d "${initdir}/etc/dbus-1" ] && return 0
+
+   brlttyAddMessageBusUsers /usr/share/dbus-1/system.d/*
+   brlttyAddMessageBusUsers /etc/dbus-1/system.d/*
+
+   brlttyInstallDirectories /etc/dbus-1
+   brlttyInstallDirectories /usr/share/dbus-1
+   brlttyInstallDirectories /usr/libexec/dbus-1
+
+   inst_multiple dbus-daemon dbus-send dbus-cleanup-sockets dbus-monitor
+   brlttyInstallSystemdUnits dbus.service dbus.socket
+
+   inst_hook initqueue 96 "${moddir}/dbus-start.sh"
+}
+
+brlttyAddMessageBusUsers() {
+   set -- dbus $(sed -n -r -e 's/^.* user="([^"]*)".*$/\1/p' "${@}" | sort -u)
+   brlttyAddUserEntries "${@}"
+   brlttyAddGroupEntries "${@}"
+}
+
+brlttyAddUserEntries() {
+   brlttyAddEntries passwd "${@}"
+}
+
+brlttyAddGroupEntries() {
+   brlttyAddEntries group "${@}"
+}
+
+brlttyAddEntries() {
+   local file="${1}"
+   shift 1
+
+   local source="/etc/${file}"
+   local target="${initdir}${source}"
+   local name
+
+   for name
+   do
+      grep -q -e "^${name}:" "${target}" || {
+         local line="$(grep "^${name}:" "${source}")"
+         [ -n "${line}" ] && echo >>"${target}" "${line}"
+      }
+   done
+}
+
+brlttyInstallSystemdUnits() {
+   local unit
+
+   for unit
+   do
+      inst_simple "/usr/lib/systemd/system/${unit}"
+   done
+}
+
+brlttyInstallDirectories() {
+   local directory
+
+   for directory
+   do
+      [ -d "${directory}" ] && {
+         eval set -- $(find "${directory}" -printf "'%p'\n")
+         inst_multiple "${@}"
+      }
+   done
+}
+
diff --git a/Initramfs/Dracut/pulse-start.sh b/Initramfs/Dracut/pulse-start.sh
new file mode 100755
index 0000000..ed9af36
--- /dev/null
+++ b/Initramfs/Dracut/pulse-start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+getargbool 1 rd.brltty && getargbool 1 rd.brltty.sound && getargbool 1 rd.brltty.pulse && {
+   chmod a+w /tmp && pulseaudio --daemonize=yes --system --disallow-exit --disallow-module-loading --disable-shm
+}
diff --git a/Initramfs/Dracut/pulse-stop.sh b/Initramfs/Dracut/pulse-stop.sh
new file mode 100755
index 0000000..5f2b033
--- /dev/null
+++ b/Initramfs/Dracut/pulse-stop.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+read </run/pulse/pid pid && kill -0 "${pid}" && kill -KILL "${pid}"
diff --git a/Initramfs/Dracut/speechd-start.sh b/Initramfs/Dracut/speechd-start.sh
new file mode 100755
index 0000000..4467c27
--- /dev/null
+++ b/Initramfs/Dracut/speechd-start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+getargbool 1 rd.brltty && getargbool 1 rd.brltty.sound && getargbool 1 rd.brltty.speechd && {
+   systemctl -q is-active speech-dispatcherd || {
+      systemctl --no-block start speech-dispatcherd
+   }
+}
diff --git a/LICENSE-LGPL b/LICENSE-LGPL
new file mode 100644
index 0000000..b124cf5
--- /dev/null
+++ b/LICENSE-LGPL
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..4c6875c
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,135 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+all install uninstall install-documents:
+	cd $(PGM_DIR) && $(MAKE) $@
+
+install-extras: install-appstream install-messages install-polkit install-systemd install-udev
+
+install-appstream uninstall-appstream:
+	cd Autostart/AppStream && $(MAKE) $(@:-appstream=)
+
+install-dracut uninstall-dracut:
+	cd Initramfs/Dracut && $(MAKE) $(@:-dracut=)
+
+install-messages uninstall-messages:
+	cd $(MSG_DIR) && $(MAKE) $(@:-messages=)
+
+install-polkit uninstall-polkit:
+	cd Authorization/Polkit && $(MAKE) $(@:-polkit=)
+
+install-systemd uninstall-systemd:
+	cd Autostart/Systemd && $(MAKE) $(@:-systemd=)
+
+install-udev uninstall-udev:
+	cd Autostart/Udev && $(MAKE) $(@:-udev=)
+
+ARCHIVE_NAME = $(PACKAGE_TARNAME)-$(PACKAGE_VERSION)
+TAR_EXTENSION = tar
+GZIP_EXTENSION = gz
+BZIP2_EXTENSION = bz2
+ZIP_EXTENSION = zip
+XZ_EXTENSION = xz
+
+SRC_TAR = $(ARCHIVE_NAME).$(TAR_EXTENSION)
+$(SRC_TAR):
+	$(SRC_TOP)mktar $(SRC_TOP) $(ARCHIVE_NAME) $@
+src-tar: $(SRC_TAR)
+
+SRC_TAR_GZIP = $(SRC_TAR).$(GZIP_EXTENSION)
+$(SRC_TAR_GZIP): $(SRC_TAR)
+	gzip -9 -c $(SRC_TAR) >$@
+src-tar-gzip: $(SRC_TAR_GZIP)
+
+SRC_TAR_BZIP2 = $(SRC_TAR).$(BZIP2_EXTENSION)
+$(SRC_TAR_BZIP2): $(SRC_TAR)
+	bzip2 -9 -c $(SRC_TAR) >$@
+src-tar-bzip2: $(SRC_TAR_BZIP2)
+
+SRC_TAR_XZ = $(SRC_TAR).$(XZ_EXTENSION)
+$(SRC_TAR_XZ): $(SRC_TAR)
+	xz -9 -c $(SRC_TAR) >$@
+src-tar-xz: $(SRC_TAR_XZ)
+
+rpm: $(SRC_TAR_GZIP)
+	$(SRC_TOP)mkrpm $(SRC_TAR_GZIP)
+
+SRC_ZIP = $(ARCHIVE_NAME).$(ZIP_EXTENSION)
+$(SRC_ZIP):
+	$(SRC_TOP)mkzip $(SRC_TOP) $(ARCHIVE_NAME) $@
+src-zip: $(SRC_ZIP)
+
+BIN_DIR = `pwd`/BinTree
+bin-tree:
+	$(INSTALL_DIRECTORY) $(BIN_DIR)
+	$(MAKE) install INSTALL_ROOT=$(BIN_DIR)
+
+BIN_TAR = $(ARCHIVE_NAME).$(HOST_OS).$(HOST_CPU).$(TAR_EXTENSION)
+$(BIN_TAR): bin-tree
+	tar -C $(BIN_DIR) -c -f $@ .
+bin-tar: $(BIN_TAR)
+
+BIN_TAR_GZIP = $(BIN_TAR).$(GZIP_EXTENSION)
+$(BIN_TAR_GZIP): $(BIN_TAR)
+	gzip -9 -c $(BIN_TAR) >$@
+bin-tar-gzip: $(BIN_TAR_GZIP)
+
+BIN_TAR_BZIP2 = $(BIN_TAR).$(BZIP2_EXTENSION)
+$(BIN_TAR_BZIP2): $(BIN_TAR)
+	bzip2 -9 -c $(BIN_TAR) >$@
+bin-tar-bzip2: $(BIN_TAR_BZIP2)
+
+BIN_TAR_XZ = $(BIN_TAR).$(XZ_EXTENSION)
+$(BIN_TAR_XZ): $(BIN_TAR)
+	xz -9 -c $(BIN_TAR) >$@
+bin-tar-xz: $(BIN_TAR_XZ)
+
+ITERATE = \
+   -for directory in \
+   $(PGM_DIR) \
+   $(DOC_DIR) \
+   $(MSG_DIR) \
+   Autostart/AppStream \
+   Autostart/Systemd \
+   Autostart/Udev \
+   Autostart/X11 \
+   Authorization/Polkit \
+   Initramfs/Dracut \
+   Android/Gradle \
+   ; do (cd $$directory && $(MAKE) $@); done
+
+clean:: clean-archives
+	$(ITERATE)
+	-rm -f -r -- $(BIN_DIR)
+
+clean-archives:
+	-rm -f -- *.$(TAR_EXTENSION)
+	-rm -f -- *.$(GZIP_EXTENSION)
+	-rm -f -- *.$(BZIP2_EXTENSION)
+	-rm -f -- *.$(XZ_EXTENSION)
+	-rm -f -- *.$(ZIP_EXTENSION)
+	-rm -f -- *.rpm
+
+distclean:: clean
+	$(ITERATE)
+	-rm -f brltty.spec
+	-rm -fr autom4te*.cache
+	-rm -f config.log config.cache config.status
+	-rm -f config.mk config.h brltty-config.sh brltty.pc
+	-rm -f forbuild.log forbuild.cache forbuild.status
+	-rm -f forbuild.mk forbuild.h
diff --git a/Messages/Makefile.in b/Messages/Makefile.in
new file mode 100644
index 0000000..a8e6eb3
--- /dev/null
+++ b/Messages/Makefile.in
@@ -0,0 +1,76 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+all:: objects languages
+
+COPYRIGHT_HOLDER = The BRLTTY Developers
+LANGUAGE_CODES = ar de fr it ru zh
+
+LOCALE_SUBDIRECTORY = locale
+MESSAGES_SUBDIRECTORY = LC_MESSAGES
+
+TEMPLATE_EXTENSION = pot
+SOURCE_EXTENSION = po
+OBJECT_EXTENSION = mo
+
+LANGUAGES_FILE = LINGUAS
+TEMPLATE_FILE = $(SRC_DIR)/$(PACKAGE_TARNAME).$(TEMPLATE_EXTENSION)
+OBJECT_FILE = $(PACKAGE_TARNAME).$(OBJECT_EXTENSION)
+
+SOURCE_FILES = $(LANGUAGE_CODES:=.$(SOURCE_EXTENSION))
+OBJECT_FILES = $(LANGUAGE_CODES:=.$(OBJECT_EXTENSION))
+
+languages: $(LANGUAGES_FILE)
+$(LANGUAGES_FILE): FORCE
+	: >$@; \
+	for language in $(LANGUAGE_CODES); do \
+	echo $$language >>$@; \
+	done
+
+template $(TEMPLATE_EXTENSION): $(TEMPLATE_FILE)
+$(TEMPLATE_FILE): $(BLD_TOP)$(PGM_DIR)/cmds.auto.h FORCE
+	$(SRC_TOP)mkpot -p "$(PACKAGE_TARNAME)" -n "$(PACKAGE_VERSION)" -b "$(PACKAGE_BUGREPORT)" -c "$(COPYRIGHT_HOLDER)" -o $@ -- $(SRC_TOP) $(BLD_TOP)
+
+sources: $(SOURCE_FILES)
+%.$(SOURCE_EXTENSION): FORCE
+	$(MSGMERGE) --quiet --update --force-po --no-wrap --no-fuzzy-matching -- $(SRC_DIR)/$@ $(TEMPLATE_FILE)
+	set -- `date --utc +"%Y %m %d %H %M %z"` && \
+	sed -e "/^.PO-Revision-Date:/s/....-..-.. ..:..[+-]..../$$1-$$2-$$3 $$4:$$5$$6/" -i $(SRC_DIR)/$@
+
+objects: $(OBJECT_FILES)
+%.$(OBJECT_EXTENSION): FORCE
+	$(MSGFMT) --output-file $@ -- $(SRC_DIR)/$(@F:.$(OBJECT_EXTENSION)=.$(SOURCE_EXTENSION))
+	directory="$(LOCALE_SUBDIRECTORY)/$(@:.$(OBJECT_EXTENSION)=)/$(MESSAGES_SUBDIRECTORY)"; \
+	$(INSTALL_DIRECTORY) $${directory}; \
+	cd $${directory}; \
+	test -f $(OBJECT_FILE) || $(SYMLINK) ../../../$@ $(OBJECT_FILE);
+
+clean::
+	-rm -f -- *.$(OBJECT_EXTENSION) *~
+	-rm -f -r -- $(LOCALE_SUBDIRECTORY)
+
+install:: objects install-locale-directory
+	for language in $(LANGUAGE_CODES); do \
+	directory="$(INSTALL_LOCALE_DIRECTORY)/$${language}/$(MESSAGES_SUBDIRECTORY)" && \
+        $(INSTALL_DIRECTORY) "$${directory}" && \
+	$(INSTALL_DATA) "$${language}.$(OBJECT_EXTENSION)" "$${directory}/$(OBJECT_FILE)"; \
+	done
+
+uninstall::
+	-rm -f -- $(INSTALL_LOCALE_DIRECTORY)/*/*/$(OBJECT_FILE)
+
diff --git a/Messages/TRANSLATORS b/Messages/TRANSLATORS
new file mode 100644
index 0000000..9ffe331
--- /dev/null
+++ b/Messages/TRANSLATORS
@@ -0,0 +1,5 @@
+de Mario Lang <MLang@blind.guru>
+fr Jean-Philippe Mengual <jpmengual@debian.org>
+it Sebastiano Pistore <SebastianoPistore.info@protonmail.ch>
+ru Маргарита Мельникова <margaretmelnikova@gmail.com>
+zh 高生旺 <Coscell@molerat.net>
diff --git a/Messages/ar.po b/Messages/ar.po
new file mode 100644
index 0000000..fa4583b
--- /dev/null
+++ b/Messages/ar.po
@@ -0,0 +1,4061 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 4.5\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-09-07 15:29+0200\n"
+"PO-Revision-Date: 2022-03-20 23:40+0300\n"
+"Last-Translator: Ikrami Ahmad <ikrami@blind.gov.qa>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.0\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr "\"%s\" بدأ بصفته \"%s\"\n"
+
+#. xgettext: This phrase describes the colour of a character on the screen.
+#. xgettext: %1$s is the (already translated) foreground colour.
+#. xgettext: %2$s is the (already translated) background colour.
+#: Programs/cmd_utils.c:169
+#, c-format
+msgid "%1$s on %2$s"
+msgstr "%1$s على %2$s"
+
+#. xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+#. xgettext: (%u represents the number of hours)
+#: Programs/cmd_miscellaneous.c:89
+#, c-format
+msgid "%u o'clock"
+msgid_plural "%u o'clock"
+msgstr[0] "%u الساعة"
+msgstr[1] "%u o'clock"
+
+#. xgettext: This is a number (%u) of seconds (time units).
+#: Programs/cmd_miscellaneous.c:107
+#, c-format
+msgid "%u second"
+msgid_plural "%u seconds"
+msgstr[0] "%u ثانية"
+msgstr[1] "%u من الثواني"
+
+#: Programs/menu_prefs.c:543 Programs/menu_prefs.c:544
+#: Programs/menu_prefs.c:546 Programs/menu_prefs.c:547
+#: Programs/menu_prefs.c:550 Programs/menu_prefs.c:551
+#: Programs/menu_prefs.c:552 Programs/menu_prefs.c:554
+#: Programs/menu_prefs.c:555 Programs/menu_prefs.c:561
+msgid "1 cell"
+msgstr "خلية واحدة"
+
+#: Programs/menu_prefs.c:900
+msgid "1 second"
+msgstr "ثانية واحدة"
+
+#: Programs/menu_prefs.c:962
+msgid "10 seconds"
+msgstr "10 ثواني"
+
+#: Programs/menu_prefs.c:1277
+msgid "12 Hour"
+msgstr "12 ساعة"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:553
+msgid "2 cells"
+msgstr "خليتين"
+
+#: Programs/menu_prefs.c:901
+msgid "2 seconds"
+msgstr "ثانيتين"
+
+#: Programs/menu_prefs.c:963
+msgid "20 seconds"
+msgstr "12 ثانية"
+
+#: Programs/menu_prefs.c:1276
+msgid "24 Hour"
+msgstr "24 ساعة"
+
+#: Programs/menu_prefs.c:898
+msgid "250 milliseconds"
+msgstr "250 ميلي ثانية"
+
+#: Programs/menu_prefs.c:557 Programs/menu_prefs.c:558
+#: Programs/menu_prefs.c:559 Programs/menu_prefs.c:560
+msgid "3 cells"
+msgstr "3 خلايا"
+
+#: Programs/menu_prefs.c:964
+msgid "40 seconds"
+msgstr "40 ثانية"
+
+#: Programs/menu_prefs.c:961
+msgid "5 seconds"
+msgstr "5 ثواني"
+
+#: Programs/menu_prefs.c:899
+msgid "500 milliseconds"
+msgstr "500 ميلي ثانية"
+
+#: Programs/menu_prefs.c:733
+msgid "6-dot"
+msgstr "ست نقاط"
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr "استخدام مجموعة أحرف 8."
+
+#: Programs/menu_prefs.c:732
+msgid "8-dot"
+msgstr "ثمان نقاط"
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr "<إيقاف>"
+
+#: Programs/config.c:1566
+msgid "API Parameter"
+msgstr "معلمة API"
+
+#. xgettext: This is the description of the PASSAT command.
+#: Programs/cmds.auto.h:1471
+msgid "AT (set 2) keyboard scan code"
+msgstr "AT (مجموعة 2) رمز مسح لوحة المفاتيح"
+
+#. xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+#: Programs/midi.c:145
+msgid "Accordion"
+msgstr "الأكورديون"
+
+#. Bass
+#. xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+#: Programs/midi.c:180
+msgid "Acoustic Bass"
+msgstr "إيقاع الصدا"
+
+#. Piano
+#. xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+#: Programs/midi.c:80
+msgid "Acoustic Grand Piano"
+msgstr "اكوستيك جراند بيانو"
+
+#. Guitar
+#. xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+#: Programs/midi.c:155
+msgid "Acoustic Guitar (nylon)"
+msgstr "جيتار صوتي (نايلون)"
+
+#. xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+#: Programs/midi.c:158
+msgid "Acoustic Guitar (steel)"
+msgstr "جيتار صوتي (صلب)"
+
+#: Programs/menu_prefs.c:1303
+msgid "After Time"
+msgstr "بعد الوقت"
+
+#. xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+#: Programs/midi.c:434
+msgid "Agogo"
+msgstr "اجوجو"
+
+#: Programs/menu_prefs.c:1491
+msgid "Alert"
+msgstr "تنبيه"
+
+#: Programs/menu_prefs.c:1104
+msgid "Alert Dots"
+msgstr "نقاط التنبيه"
+
+#: Programs/menu_prefs.c:1109
+msgid "Alert Messages"
+msgstr "رسائل التنبيه"
+
+#: Programs/menu_prefs.c:1049
+msgid "Alert Tunes"
+msgstr "نغمات التنبيه"
+
+#: Programs/menu_prefs.c:864 Programs/menu_prefs.c:1194
+msgid "All"
+msgstr "الكل"
+
+#: Programs/menu_prefs.c:555
+msgid "Alphabetic Cursor Coordinates"
+msgstr "إحداثيات المؤشر الأبجدية"
+
+#: Programs/menu_prefs.c:554
+msgid "Alphabetic Window Coordinates"
+msgstr "إحداثيات النافذة الأبجدية"
+
+#. xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+#: Programs/midi.c:283
+msgid "Alto Saxophone"
+msgstr "ألتو ساكسفون"
+
+#. xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+#: Programs/midi.c:474
+msgid "Applause"
+msgstr "تصفيق"
+
+#: Programs/log.c:118
+msgid "Async Events"
+msgstr "أحداث غير متزامنة"
+
+#: Programs/menu_prefs.c:807
+msgid "Attributes Blink Period"
+msgstr "فترة وميض السمات"
+
+#: Programs/menu_prefs.c:815
+msgid "Attributes Percent Visible"
+msgstr "النسبة المئوية للسمات المرئية"
+
+#: Programs/config.c:1059 Programs/menu_prefs.c:1406
+msgid "Attributes Table"
+msgstr "جدول السمات"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr "التحرير التلقائي"
+
+#: Programs/menu_prefs.c:967
+msgid "Autorelease Time"
+msgstr "وقت التحرير التلقائي"
+
+#: Programs/menu_prefs.c:984
+msgid "Autorepeat Enabled"
+msgstr "تم تمكين التكرار التلقائي"
+
+#: Programs/menu_prefs.c:990
+msgid "Autorepeat Interval"
+msgstr "فاصل التكرار التلقائي"
+
+#: Programs/menu_prefs.c:997
+msgid "Autorepeat Panning"
+msgstr "التمرير خلال التكرار التلقائي"
+
+#: Programs/menu_prefs.c:1119
+msgid "Autospeak"
+msgstr "النطق التلقائي"
+
+#: Programs/menu_prefs.c:1116
+msgid "Autospeak Options"
+msgstr "خيارات النطق التلقائي"
+
+#: Programs/config.c:335
+msgid "Autospeak Threshold"
+msgstr "حدود النطق التلقائي"
+
+#: Programs/config.c:2012
+msgid "BRLTTY stopped"
+msgstr "توقف BRLTTY"
+
+#. xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+#: Programs/midi.c:421
+msgid "Bag Pipe"
+msgstr "كيس الأنابيب"
+
+#. xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+#: Programs/midi.c:409
+msgid "Banjo"
+msgstr "البانجوز"
+
+#. xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+#: Programs/midi.c:289
+msgid "Baritone Saxophone"
+msgstr "ساكسفون الباريتون"
+
+#. xgettext: This is the name of MIDI musical instrument group #5.
+#: Programs/midi.c:37
+msgid "Bass"
+msgstr "صوت عميق"
+
+#. xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+#: Programs/midi.c:298
+msgid "Bassoon"
+msgstr "الباسون"
+
+#: Programs/menu_prefs.c:1056
+msgid "Beeper"
+msgstr "الصفارة"
+
+#: Programs/menu_prefs.c:1302
+msgid "Before Time"
+msgstr "قبل الوقت"
+
+#. xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+#: Programs/midi.c:465
+msgid "Bird Tweet"
+msgstr "تغريدة الطيور"
+
+#: Programs/menu_prefs.c:801
+msgid "Blinking Attributes"
+msgstr "وميض السمات"
+
+#: Programs/menu_prefs.c:823
+msgid "Blinking Capitals"
+msgstr "وميض الحروف الكبيرة"
+
+#: Programs/menu_prefs.c:774
+msgid "Blinking Screen Cursor"
+msgstr "وميض مؤشر الشاشة"
+
+#: Programs/menu_prefs.c:1248
+msgid "Blinking Speech Cursor"
+msgstr "وميض مؤشر النطق"
+
+#: Programs/menu_prefs.c:665 Programs/menu_prefs.c:1362
+msgid "Block"
+msgstr "حجب"
+
+#. xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+#: Programs/midi.c:317
+msgid "Blown Bottle"
+msgstr "زجاجة في مهب"
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr "بلوتوث I / O"
+
+#: Programs/config.c:1787
+msgid "Braille Device"
+msgstr "جهاز برايل"
+
+#: Programs/config.c:1783
+msgid "Braille Driver"
+msgstr "سائقة برايل"
+
+#: Programs/log.c:148
+msgid "Braille Driver Events"
+msgstr "أحداث سائقة برايل"
+
+#: Programs/menu_prefs.c:752
+msgid "Braille Firmness"
+msgstr "صلابة نقاط برايل"
+
+#: Programs/log.c:82
+msgid "Braille Key Events"
+msgstr "أحداث مفاتيح برايل"
+
+#: Programs/config.c:1786
+msgid "Braille Parameter"
+msgstr "معايير برايل"
+
+#: Programs/menu_prefs.c:698
+msgid "Braille Presentation"
+msgstr "تمثيل برايل"
+
+#: Programs/menu_prefs.c:1389
+msgid "Braille Tables"
+msgstr "جداول برايل"
+
+#: Programs/menu_prefs.c:932
+msgid "Braille Typing"
+msgstr "الإدخال ببرايل"
+
+#: Programs/menu_prefs.c:706
+msgid "Braille Variant"
+msgstr "أنواع برايل"
+
+#: Programs/menu_prefs.c:885
+msgid "Braille Window Overlap"
+msgstr "تداخل نوافذ برايل"
+
+#: Programs/config.c:552
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr "شفرة سائقة برايل (%s, %s, أو أياً من {%s})."
+
+#. xgettext: This is the name of MIDI musical instrument group #8.
+#: Programs/midi.c:46
+msgid "Brass"
+msgstr "نحاس"
+
+#. xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+#: Programs/midi.c:270
+msgid "Brass Section"
+msgstr "القسم النحاسي"
+
+#. xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+#: Programs/midi.c:459
+msgid "Breath Noise"
+msgstr "صوت التنفس"
+
+#. xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+#: Programs/midi.c:83
+msgid "Bright Acoustic Piano"
+msgstr "برايت اكوستيك بيانو"
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr "أنظمة المصادقة / التخويل BrlAPI"
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr "مضيف BrlAPI و / أو المنفذ المراد الاتصال به"
+
+#: Programs/menu_prefs.c:1424
+msgid "Build Information"
+msgstr "معلومات الإصدار"
+
+#: Programs/menu_prefs.c:725
+msgid "Capitalization Mode"
+msgstr "نمط الأحرف الكبيرة"
+
+#: Programs/menu_prefs.c:828
+msgid "Capitals Blink Period"
+msgstr "مدة وميض المؤشر للحروف الكبيرة"
+
+#: Programs/menu_prefs.c:836
+msgid "Capitals Percent Visible"
+msgstr "نسبة رؤية الأحرف الكبيرة"
+
+#: Programs/menu_prefs.c:1514
+msgid "Category Log Level"
+msgstr "مستوى سجل الأقسام"
+
+#. Chromatic Percussion
+#. xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+#: Programs/midi.c:105
+msgid "Celesta"
+msgstr "سيليستا"
+
+#. xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+#: Programs/midi.c:211
+msgid "Cello"
+msgstr "التشيلو"
+
+#. xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+#: Programs/midi.c:242
+msgid "Choir Aahs"
+msgstr "جوقة آآس"
+
+#. xgettext: This is the name of MIDI musical instrument group #2.
+#: Programs/midi.c:28
+msgid "Chromatic Percussion"
+msgstr "قرع لوني"
+
+#. xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+#: Programs/midi.c:139
+msgid "Church Organ"
+msgstr "أورغان كنسي"
+
+#. xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+#: Programs/midi.c:301
+msgid "Clarinet"
+msgstr "كلارينيت"
+
+#. xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+#: Programs/midi.c:101
+msgid "Clavinet"
+msgstr "كلافينيت"
+
+#: Programs/menu.c:894
+msgid "Close"
+msgstr "إغلاق"
+
+#: Programs/menu_prefs.c:1286
+msgid "Colon"
+msgstr "نقطتين"
+
+#: Programs/menu_prefs.c:702
+msgid "Computer Braille"
+msgstr "البرايل الحاسوبي"
+
+#: Programs/menu_prefs.c:736
+msgid "Computer Braille Cell Type"
+msgstr "نوع خلية البرايل الحاسوبي"
+
+#: Programs/menu_prefs.c:1448
+msgid "Configuration Directory"
+msgstr "مسار التكوين"
+
+#: Programs/config.c:2955 Programs/menu_prefs.c:1453
+msgid "Configuration File"
+msgstr "ملف التكوين"
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr "جرس وحدة التحكم"
+
+#: Programs/menu_prefs.c:1035
+msgid "Console Bell Alert"
+msgstr "تنبيه جرس وحدة التحكم"
+
+#. xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+#: Programs/midi.c:214
+msgid "Contrabass"
+msgstr "كونتراباس"
+
+#: Programs/menu_prefs.c:703
+msgid "Contracted Braille"
+msgstr "برايل بالاختصارات"
+
+#: Programs/config.c:1027 Programs/menu_prefs.c:1399
+msgid "Contraction Table"
+msgstr "جدول الاختصارات"
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr "جدول الاختصارات."
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr "جدول التحقق من الاختصارات."
+
+#: Programs/menu_prefs.c:1492
+msgid "Critical"
+msgstr "حرج"
+
+#: Programs/menu_prefs.c:546
+msgid "Cursor Column"
+msgstr "عمود المؤشر"
+
+#: Programs/menu_prefs.c:545 Programs/menu_prefs.c:557
+msgid "Cursor Coordinates"
+msgstr "إحداثيات المؤشر"
+
+#: Programs/log.c:100
+msgid "Cursor Routing"
+msgstr "توجيه المؤشر"
+
+#: Programs/menu_prefs.c:547
+msgid "Cursor Row"
+msgstr "صف المؤشر"
+
+#: Programs/log.c:94
+msgid "Cursor Tracking"
+msgstr "تتبع المؤشر"
+
+#: Programs/menu_prefs.c:904
+msgid "Cursor Tracking Delay"
+msgstr "تأخر تتبع المؤشر"
+
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:559
+msgid "Cursor and Window Column"
+msgstr "عمود المؤشر والنافذة"
+
+#: Programs/menu_prefs.c:549 Programs/menu_prefs.c:560
+msgid "Cursor and Window Row"
+msgstr "صف المؤشر والنافذة"
+
+#: Programs/menu_prefs.c:1324
+msgid "Dash"
+msgstr "شرطة"
+
+#: Programs/menu_prefs.c:1317
+msgid "Date Format"
+msgstr "تنسيق التاريخ"
+
+#: Programs/menu_prefs.c:1306
+msgid "Date Position"
+msgstr "وضع التاريخ"
+
+#: Programs/menu_prefs.c:1329
+msgid "Date Separator"
+msgstr "فاصل التاريخ"
+
+#: Programs/menu_prefs.c:1314
+msgid "Day Month Year"
+msgstr "يوم شهر سنة"
+
+#: Programs/menu_prefs.c:1497
+msgid "Debug"
+msgstr "تصحيح الأخطاء"
+
+#: Programs/config.c:573
+msgid "Device for accessing braille display."
+msgstr "جهاز للوصول إلى شاشة عرض برايل."
+
+#: Programs/config.c:532
+msgid "Disable the application programming interface."
+msgstr "تعطيل واجهة برمجة التطبيق."
+
+#. xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+#: Programs/midi.c:173
+msgid "Distortion Guitar"
+msgstr "الغيتار المزعج"
+
+#: Programs/config.c:675
+msgid "Do not autospeak when braille is not being used."
+msgstr "لا تتحدث تلقائيًا عند عدم استخدام طريقة برايل."
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr "لا تكتب أي نص إلى جهاز برايل"
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr "لا ترجع إلى أساس حرف Unicode."
+
+#: Programs/config.c:443
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr "لا تقم بالتبديل إلى مستخدم لا يتمتع بامتيازات أو التخلي عن أي امتيازات (عضويات المجتمع، وما إلى ذلك)."
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr "تم"
+
+#: Programs/menu_prefs.c:1287 Programs/menu_prefs.c:1326
+msgid "Dot"
+msgstr "نقطة"
+
+#: Programs/menu_prefs.c:942
+msgid "Dots via Unicode Braille"
+msgstr "النقاط عبر يونيكود برايل"
+
+#. Organ
+#. xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+#: Programs/midi.c:130
+msgid "Drawbar Organ"
+msgstr "أورغان الجر"
+
+#: Programs/config.c:2976 Programs/menu_prefs.c:1473
+msgid "Drivers Directory"
+msgstr "مسار التعريفات"
+
+#. xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+#: Programs/midi.c:126
+msgid "Dulcimer"
+msgstr "السنطور"
+
+#: Programs/menu_prefs.c:879
+msgid "Eager Sliding Braille Window"
+msgstr "الحرص على نافذة برايل المنزلقة"
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "تحرير الجدول."
+
+#. xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+#: Programs/midi.c:183
+msgid "Electric Bass (finger)"
+msgstr "جهير كهربائي (إصبع)"
+
+#. xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+#: Programs/midi.c:186
+msgid "Electric Bass (pick)"
+msgstr "جهير كهربائي (معول)"
+
+#. xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+#: Programs/midi.c:86
+msgid "Electric Grand Piano"
+msgstr "بيانو كهربائي كبير"
+
+#. xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+#: Programs/midi.c:164
+msgid "Electric Guitar (clean)"
+msgstr "جيتار كهربائي (نظيف)"
+
+#. xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+#: Programs/midi.c:161
+msgid "Electric Guitar (jazz)"
+msgstr "جيتار كهربائي (جاز)"
+
+#. xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+#: Programs/midi.c:167
+msgid "Electric Guitar (muted)"
+msgstr "جيتار كهربائي (مكتوم)"
+
+#. xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+#: Programs/midi.c:92
+msgid "Electric Piano 1"
+msgstr "بيانو كهربائي 1"
+
+#. xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+#: Programs/midi.c:95
+msgid "Electric Piano 2"
+msgstr "بيانو كهربائي 2"
+
+#: Programs/menu_prefs.c:1490
+msgid "Emergency"
+msgstr "الطوارئ"
+
+#: Programs/menu_prefs.c:541
+msgid "End"
+msgstr "النهاية"
+
+#: Programs/menu_prefs.c:865
+msgid "End of Line"
+msgstr "نهاية السطر"
+
+#. xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+#: Programs/midi.c:295
+msgid "English Horn"
+msgstr "مزمار إنجليزي"
+
+#: Programs/menu_prefs.c:1229
+msgid "Enqueue"
+msgstr "قائمة الانتظار"
+
+#. xgettext: This is the name of MIDI musical instrument group #7.
+#: Programs/midi.c:43
+msgid "Ensemble"
+msgstr "الفرقة"
+
+#: Programs/menu_prefs.c:1493
+msgid "Error"
+msgstr "خطأ"
+
+#. xgettext: This is the name of MIDI musical instrument group #14.
+#: Programs/midi.c:68
+msgid "Ethnic Instruments"
+msgstr "الآلات العرقية"
+
+#: Programs/menu_prefs.c:1032
+msgid "Event Alerts"
+msgstr "تنبيهات الأحداث"
+
+#: Programs/menu_prefs.c:713
+msgid "Expand Current Word"
+msgstr "توسيع الكلمة الحالية"
+
+#: Programs/config.c:487
+msgid "Explicit preference settings."
+msgstr "إعدادات التفضيل الصريحة."
+
+#: Programs/menu_prefs.c:1059
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:1097
+msgid "FM Volume"
+msgstr "مستوى صوت  FM"
+
+#. Synth FM
+#. xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+#: Programs/midi.c:380
+msgid "FX 1 (rain)"
+msgstr "FX 1 (مطر)"
+
+#. xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+#: Programs/midi.c:383
+msgid "FX 2 (soundtrack)"
+msgstr "FX 2 (موسيقى تصويرية)"
+
+#. xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+#: Programs/midi.c:386
+msgid "FX 3 (crystal)"
+msgstr "FX 3 (كريستال)"
+
+#. xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+#: Programs/midi.c:389
+msgid "FX 4 (atmosphere)"
+msgstr "FX 4 (الغلاف الجوي)"
+
+#. xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+#: Programs/midi.c:392
+msgid "FX 5 (brightness)"
+msgstr "FX 5 (السطوع)"
+
+#. xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+#: Programs/midi.c:395
+msgid "FX 6 (goblins)"
+msgstr "FX 6 (العفاريت)"
+
+#. xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+#: Programs/midi.c:398
+msgid "FX 7 (echoes)"
+msgstr "FX 7 (أصداء)"
+
+#. xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+#. xgettext: (sci-fi is a common short form for science fiction)
+#: Programs/midi.c:402
+msgid "FX 8 (sci-fi)"
+msgstr "FX 8 (خيال علمي)"
+
+#. xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+#: Programs/midi.c:424
+msgid "Fiddle"
+msgstr "كمان"
+
+#. xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+#: Programs/midi.c:308
+msgid "Flute"
+msgstr "الفلوت"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr "إجبار العرض المباشر."
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr "تنسيق كل سطر عند العرض."
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr "تنسيق ملف الإدخال."
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr "تنسيق ملف العرض."
+
+#. xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+#: Programs/midi.c:267
+msgid "French Horn"
+msgstr "البوق الفرنسي"
+
+#. xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+#: Programs/midi.c:189
+msgid "Fretless Bass"
+msgstr "فريتلس باس"
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr "مجمدة"
+
+#: Programs/menu_prefs.c:556
+msgid "Generic"
+msgstr "أساسي"
+
+#: Programs/log.c:64
+msgid "Generic Input"
+msgstr "الإدخال الأساسي"
+
+#. xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+#: Programs/midi.c:108
+msgid "Glockenspiel"
+msgstr "جلوكنسبيل"
+
+#. xgettext: This is the name of MIDI musical instrument group #4.
+#: Programs/midi.c:34
+msgid "Guitar"
+msgstr "غيتار"
+
+#. Sound Effects
+#. xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+#: Programs/midi.c:456
+msgid "Guitar Fret Noise"
+msgstr "ضجيج الجيتار الحنق"
+
+#. xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+#: Programs/midi.c:176
+msgid "Guitar Harmonics"
+msgstr "التوافقيات الغيتار"
+
+#. xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+#: Programs/midi.c:477
+msgid "Gunshot"
+msgstr "طلق ناري"
+
+#. xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+#: Programs/midi.c:148
+msgid "Harmonica"
+msgstr "هارمونيكا"
+
+#. xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+#: Programs/midi.c:98
+msgid "Harpsichord"
+msgstr "بيان القيثاري"
+
+#. xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+#: Programs/midi.c:471
+msgid "Helicopter"
+msgstr "هليكوبتر"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "شاشة المساعدة"
+
+#: Programs/menu_prefs.c:748 Programs/menu_prefs.c:1013
+msgid "High"
+msgstr "مرتفع"
+
+#: Programs/menu_prefs.c:921
+msgid "Highlight Braille Window Location"
+msgstr "تحديد موقع نافذة برايل"
+
+#. xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+#: Programs/midi.c:89
+msgid "Honkytonk Piano"
+msgstr "هونكيتونك بيانو"
+
+#: Programs/menu_prefs.c:1228
+msgid "Immediate"
+msgstr "مباشر"
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr "مكتبة XKB غير متوافقة \\ n\n"
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr "دعم خادم XKB غير متوافق \\ n\n"
+
+#: Programs/menu_prefs.c:1496
+msgid "Information"
+msgstr "معلومات"
+
+#: Programs/menu_prefs.c:956
+msgid "Input Options"
+msgstr "خيارات الإدخال"
+
+#: Programs/log.c:70
+msgid "Input Packets"
+msgstr "حزم الإدخال"
+
+#: Programs/config.c:418
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr "قم بتثبيت خدمة %s ، ثم قم بالإنهاء."
+
+#: Programs/menu_prefs.c:1500
+msgid "Internal Parameters"
+msgstr "المعايير الداخلية"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr "فئة Java غير موجودة"
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr "حدث استثناء Java"
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr "لم يتم العثور على طريقة جافا"
+
+#. xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+#: Programs/midi.c:418
+msgid "Kalimba"
+msgstr "كاليمبا"
+
+#: Programs/config.c:1709
+msgid "Key Bindings"
+msgstr "ارتباطات المفاتيح"
+
+#: Programs/config.c:1237
+msgid "Key Help"
+msgstr "مساعدة المفاتيح"
+
+#: Programs/config.c:1714 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr "جدول المفاتيح"
+
+#: Programs/menu_prefs.c:935
+msgid "Keyboard Enabled"
+msgstr "لوحة المفاتيح مفعلة"
+
+#: Programs/log.c:88
+msgid "Keyboard Key Events"
+msgstr "أحداث أزرار لوحة المفاتيح"
+
+#: Programs/menu_prefs.c:1042
+msgid "Keyboard LED Alerts"
+msgstr "تنبيهات إضاءة للوحة المفاتيح"
+
+#: Programs/config.c:1322 Programs/menu_prefs.c:1024
+msgid "Keyboard Table"
+msgstr "جدول لوحة المفاتيح"
+
+#. xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+#: Programs/midi.c:415
+msgid "Koto"
+msgstr "Koto"
+
+#: Programs/config.c:3112
+msgid "Language"
+msgstr "اللغة"
+
+#. Synth Lead
+#. xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+#: Programs/midi.c:330
+msgid "Lead 1 (square)"
+msgstr "الأثر 1 (مربع)"
+
+#. xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+#: Programs/midi.c:333
+msgid "Lead 2 (sawtooth)"
+msgstr "الأثر 2 (سن المنشار)"
+
+#. xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+#: Programs/midi.c:336
+msgid "Lead 3 (calliope)"
+msgstr "الأثر 3 (كاليوب)"
+
+#. xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+#: Programs/midi.c:339
+msgid "Lead 4 (chiff)"
+msgstr "الأثر 4 (شيف)"
+
+#. xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+#: Programs/midi.c:342
+msgid "Lead 5 (charang)"
+msgstr "الأثر 5 (تشارانج)"
+
+#. xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+#: Programs/midi.c:345
+msgid "Lead 6 (voice)"
+msgstr "الأثر 6 (صوت)"
+
+#. xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+#: Programs/midi.c:348
+msgid "Lead 7 (fifths)"
+msgstr "الأثر 7 (الأخماس)"
+
+#. xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+#: Programs/midi.c:351
+msgid "Lead 8 (bass + lead)"
+msgstr "الأثر 8 (باس + رصاص)"
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "نمط التعلم"
+
+#: Programs/menu_prefs.c:1341
+msgid "Left"
+msgstr "اليسار"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr "قائمة بأسماء المفاتيح."
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr "عرض جدول المفاتيح في تنسيق شاشة التعليمات."
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr "عرض جدول المفاتيح في تنسيق نصي معاد بناءه."
+
+#: Programs/menu_prefs.c:1483
+msgid "Locale Directory"
+msgstr "دليل اللغة"
+
+#: Programs/menu_prefs.c:1519
+msgid "Log Categories"
+msgstr "فئات السجل"
+
+#: Programs/config.c:890
+msgid "Log Level"
+msgstr "مستوى السجل"
+
+#: Programs/menu_prefs.c:1580
+msgid "Log Messages"
+msgstr "رسائل السجل"
+
+#: Programs/config.c:773
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr "تسجيل الإصدارات الأساسية ، و API ، وبرامج التشغيل المدمجة ، ثم إنهاء البرنامج."
+
+#: Programs/config.c:738
+msgid "Log to standard error rather than to the system log."
+msgstr "تسجيل الدخول إلى الخطأ القياسي بدلاً من سجل النظام."
+
+#: Programs/config.c:752
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)."
+msgstr "مستوى السجل (%s أو أياً من {%s}) و/أو فئات السجل للتمكين (أي مزيج من {%s}, كل مسبوقة اختياريًا بـ %s لتعطيل)."
+
+#: Programs/menu_prefs.c:978
+msgid "Long Press Time"
+msgstr "مدة الضغط المطول"
+
+#: Programs/menu_prefs.c:746 Programs/menu_prefs.c:1011
+msgid "Low"
+msgstr "منخفض"
+
+#: Programs/menu_prefs.c:666
+msgid "Lower Left Dot"
+msgstr "النقطة اليسرى السفلية"
+
+#: Programs/menu_prefs.c:667
+msgid "Lower Right Dot"
+msgstr "النقطة اليمنى السفلية"
+
+#: Programs/menu_prefs.c:1058
+msgid "MIDI"
+msgstr "ميدي"
+
+#: Programs/config.c:722
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr "محدد جهاز MIDI (الواجهة الرقمية للآلات الموسيقية)."
+
+#: Programs/menu_prefs.c:1088
+msgid "MIDI Instrument"
+msgstr "أداة MIDI"
+
+#: Programs/menu_prefs.c:1078
+msgid "MIDI Volume"
+msgstr "حجم صوت MIDI"
+
+#: Programs/menu_prefs.c:1443
+msgid "Mailing List"
+msgstr "القائمة البريدية"
+
+#. xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+#: Programs/midi.c:117
+msgid "Marimba"
+msgstr "ماريمبا"
+
+#: Programs/menu_prefs.c:749 Programs/menu_prefs.c:1014
+msgid "Maximum"
+msgstr "الحد الأقصى"
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr "الحد الأقصى لسطر العرض."
+
+#: Programs/menu_prefs.c:747 Programs/menu_prefs.c:1012
+msgid "Medium"
+msgstr "متوسط"
+
+#. xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+#: Programs/midi.c:446
+msgid "Melodic Tom"
+msgstr "لحني توم"
+
+#: Programs/menu_prefs.c:679
+msgid "Menu Options"
+msgstr "خيارات القائمة"
+
+#: Programs/config.c:731
+msgid "Message hold timeout (in 10ms units)."
+msgstr "مدة تعليق الرسالة (بوحدة عُشْر الثانية)."
+
+#: Programs/config.c:893
+msgid "Messages Directory"
+msgstr "دليل الرسائل"
+
+#: Programs/config.c:892
+msgid "Messages Domain"
+msgstr "مجال الرسائل"
+
+#: Programs/config.c:891
+msgid "Messages Locale"
+msgstr "لغة الرسائل"
+
+#: Programs/menu_prefs.c:745 Programs/menu_prefs.c:1010
+msgid "Minimum"
+msgstr "الحد الأدنى"
+
+#: Programs/config.c:682
+#, c-format
+msgid "Minimum screen content quality to autospeak (one of {%s})."
+msgstr "الحد الأدنى لجودة محتوى الشاشة للنطق التلقائي (واحد من { %s})."
+
+#: Programs/menu_prefs.c:1313
+msgid "Month Day Year"
+msgstr "شهر يوم سنه"
+
+#. xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+#: Programs/midi.c:111
+msgid "Music Box"
+msgstr "صندوق الموسيقى"
+
+#: Programs/menu_prefs.c:1058
+msgid "Musical Instrument Digital Interface"
+msgstr "الواجهة الرقمية للآلات الموسيقية"
+
+#. xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+#: Programs/midi.c:264
+msgid "Muted Trumpet"
+msgstr "البوق الصامت"
+
+#: Programs/config.c:623
+msgid "Name of or path to attributes table."
+msgstr "اسم أو مسار جدول السمات."
+
+#: Programs/config.c:615
+msgid "Name of or path to contraction table."
+msgstr "اسم أو مسار جدول الاختصارات."
+
+#: Programs/config.c:479
+msgid "Name of or path to default preferences file."
+msgstr "اسم أو مسار ملف التفضيلات الافتراضي."
+
+#: Programs/config.c:632
+msgid "Name of or path to keyboard table."
+msgstr "اسم أو مسار جدول لوحة المفاتيح."
+
+#: Programs/config.c:668
+msgid "Name of or path to speech input object."
+msgstr "اسم أو مسار كائن إدخال الكلام."
+
+#: Programs/config.c:605
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr "اسم أو مسار جدول النص (أو %s)."
+
+#: Programs/menu_prefs.c:845
+msgid "Navigation Options"
+msgstr "خيارات التنقل"
+
+#: Programs/menu.c:523
+msgid "No"
+msgstr "لا"
+
+#: Programs/menu_prefs.c:720
+msgid "No Capitalization"
+msgstr "دون أحرف كبيرة"
+
+#: Programs/menu_prefs.c:897 Programs/menu_prefs.c:1192
+#: Programs/menu_prefs.c:1205 Programs/menu_prefs.c:1218
+#: Programs/menu_prefs.c:1301 Programs/menu_prefs.c:1340
+#: Programs/menu_prefs.c:1360
+msgid "None"
+msgstr "لا شيء"
+
+#: Programs/menu_prefs.c:1495
+msgid "Notice"
+msgstr "ملاحظة"
+
+#. xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+#: Programs/midi.c:292
+msgid "Oboe"
+msgstr "المزمار"
+
+#. xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+#: Programs/midi.c:326
+msgid "Ocarina"
+msgstr "الأكرينا"
+
+#: Programs/menu_prefs.c:960
+msgid "Off"
+msgstr "تعطيل"
+
+#: Programs/config.c:1800
+msgid "Old Preferences File"
+msgstr "ملف التفضيلات القديم"
+
+#: Programs/menu_prefs.c:973
+msgid "On First Release"
+msgstr "في الإصدار الأول"
+
+#. xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+#: Programs/midi.c:251
+msgid "Orchestra Hit"
+msgstr "هيت الأوركسترا"
+
+#. xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+#: Programs/midi.c:223
+msgid "Orchestral Harp"
+msgstr "القيثارة الأوركسترالية"
+
+#. xgettext: This is the name of MIDI musical instrument group #3.
+#: Programs/midi.c:31
+msgid "Organ"
+msgstr "الأورغن"
+
+#: Programs/log.c:76
+msgid "Output Packets"
+msgstr "حزم العرض"
+
+#. xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+#: Programs/midi.c:170
+msgid "Overdriven Guitar"
+msgstr "قيثارة"
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr "وضع الكمبيوتر"
+
+#: Programs/menu_prefs.c:1057
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:712
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr "محدد جهاز PCM (الصوت الرقمي لبطاقة الصوت)."
+
+#: Programs/menu_prefs.c:1070
+msgid "PCM Volume"
+msgstr "حجم صوت PCM"
+
+#. xgettext: This is the description of the PASSPS2 command.
+#: Programs/cmds.auto.h:1487
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr "PS / 2 (المجموعة 3) رمز مسح لوحة المفاتيح"
+
+#: Programs/menu_prefs.c:1433
+msgid "Package Revision"
+msgstr "مراجعة الحزمة"
+
+#: Programs/menu_prefs.c:1428
+msgid "Package Version"
+msgstr "نسخة الحزمة"
+
+#. Synth Pad
+#. xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+#: Programs/midi.c:355
+msgid "Pad 1 (new age)"
+msgstr "المسار 1 (العصر الجديد)"
+
+#. xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+#: Programs/midi.c:358
+msgid "Pad 2 (warm)"
+msgstr "المسار 2 (دافئ)"
+
+#. xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+#: Programs/midi.c:361
+msgid "Pad 3 (polysynth)"
+msgstr "المسار 3 (بوليسينث)"
+
+#. xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+#: Programs/midi.c:364
+msgid "Pad 4 (choir)"
+msgstr "المسار 4 (جوقة)"
+
+#. xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+#: Programs/midi.c:367
+msgid "Pad 5 (bowed)"
+msgstr "المسار 5 (منحنية)"
+
+#. xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+#: Programs/midi.c:370
+msgid "Pad 6 (metallic)"
+msgstr "المسار 6 (معدني)"
+
+#. xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+#: Programs/midi.c:373
+msgid "Pad 7 (halo)"
+msgstr "المسار 7 (مرحبًا)"
+
+#. xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+#: Programs/midi.c:376
+msgid "Pad 8 (sweep)"
+msgstr "المسار 8 (مسح)"
+
+#. xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+#: Programs/midi.c:314
+msgid "Pan Flute"
+msgstr "مزمار عوام"
+
+#: Programs/config.c:541
+msgid "Parameters for the application programming interface."
+msgstr "معلمات لواجهة برمجة التطبيق."
+
+#: Programs/config.c:563
+msgid "Parameters for the braille driver."
+msgstr "معلمات لبرنامج تشغيل برايل."
+
+#: Programs/config.c:436
+msgid "Parameters for the privilege establishment stage."
+msgstr "معلمات مرحلة إنشاء الامتياز."
+
+#: Programs/config.c:703
+msgid "Parameters for the screen driver."
+msgstr "معلمات لبرنامج تشغيل الشاشة."
+
+#: Programs/config.c:660
+msgid "Parameters for the speech driver."
+msgstr "معلمات لبرنامج تشغيل النطق."
+
+#: Programs/config.c:470
+msgid "Path to default settings file."
+msgstr "المسار إلى ملف الإعدادات الافتراضية."
+
+#: Programs/config.c:524
+msgid "Path to directory containing drivers."
+msgstr "المسار إلى المجلد الذي يحتوي على ملفات التعريف."
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:595
+msgid "Path to directory containing tables."
+msgstr "المسار إلى المجلد الذي يحتوي على الجداول."
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr "مسار المجلد الذي يحتوي على جداول نصية."
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr "المسار إلى المجلد لتحميل ملفات التعريف."
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr "مسار المجلد للجداول النصية."
+
+#: Programs/brltest.c:78 Programs/config.c:514
+msgid "Path to directory which can be written to."
+msgstr "المسار إلى الدليل الذي يمكن الكتابة إليه."
+
+#: Programs/config.c:504
+msgid "Path to directory which contains files that can be updated."
+msgstr "المسار إلى المجلد الذي يحتوي على الملفات التي يمكن تحديثها."
+
+#: Programs/config.c:404
+msgid "Path to directory which contains message localizations."
+msgstr "المسار إلى المجلد الذي يحتوي على ترجمة الرسائل."
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr "مسار جداول إدخال النصوص."
+
+#: Programs/config.c:761
+msgid "Path to log file."
+msgstr "مسار ملف السجل."
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr "مسار جدول عرض النص."
+
+#: Programs/config.c:460
+msgid "Path to process identifier file."
+msgstr "مسار ملف معالجة المعرف."
+
+#: Programs/config.c:494
+msgid "Patterns that match command prompts."
+msgstr "الأنماط التي تطابق موجهات الأوامر."
+
+#. xgettext: This is the name of MIDI musical instrument group #15.
+#: Programs/midi.c:71
+msgid "Percussive Instruments"
+msgstr "الآلات الإيقاعية"
+
+#. xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+#: Programs/midi.c:133
+msgid "Percussive Organ"
+msgstr "جهاز قرع"
+
+#. xgettext: This is the name of MIDI musical instrument group #1.
+#: Programs/midi.c:25
+msgid "Piano"
+msgstr "بيانو"
+
+#. Pipe
+#. xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+#: Programs/midi.c:305
+msgid "Piccolo"
+msgstr "البيتشولو"
+
+#. xgettext: This is the name of MIDI musical instrument group #10.
+#: Programs/midi.c:52
+msgid "Pipe"
+msgstr "المزمار"
+
+#. xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+#: Programs/midi.c:220
+msgid "Pizzicato Strings"
+msgstr "سلاسل Pizzicato"
+
+#: Drivers/Braille/Baum/braille.c:1158
+msgid "Powerdown"
+msgstr "إغلاق الطاقة"
+
+#: Programs/config.c:2956 Programs/menu_prefs.c:1463
+msgid "Preferences File"
+msgstr "ملف التفضيلات"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr "قائمة التفضيلات"
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr "طباعة ملخص الاستخدام (كل الخيارات)، ثم الخروج."
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr "طباعة ملخص الاستخدام (الخيارات شائعة الاستخدام فقط)، ثم الخروج."
+
+#: Programs/config.c:844
+msgid "Privilege Parameter"
+msgstr "معيار الامتياز"
+
+#: Programs/menu_prefs.c:1414
+msgid "Profiles"
+msgstr "الأوضاع"
+
+#: Programs/config.c:640
+msgid "Properties of eligible keyboards."
+msgstr "خصائص لوحات المفاتيح المؤهلة."
+
+#: Programs/menu_prefs.c:950
+msgid "Quick Space"
+msgstr "مساحة سريعة"
+
+#: Programs/menu_prefs.c:1209
+msgid "Raise Pitch"
+msgstr "رفع حدة النغمة"
+
+#: Programs/config.c:395
+msgid "Recognize environment variables."
+msgstr "التعرف على متغيرات البيئة."
+
+#. xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+#: Programs/midi.c:311
+msgid "Recorder"
+msgstr "المسجل"
+
+#. xgettext: This is the name of MIDI musical instrument group #9.
+#: Programs/midi.c:49
+msgid "Reed"
+msgstr "القراءة"
+
+#. xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+#: Programs/midi.c:142
+msgid "Reed Organ"
+msgstr "أورغان ريد"
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr "إعادة تنسيق الإدخال."
+
+#: Programs/config.c:585
+msgid "Release braille device when screen or window is unreadable."
+msgstr "حرر جهاز برايل عندما تكون الشاشة أو النافذة غير قابلة للقراءة."
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr "البقاء كعملية في الواجهة"
+
+#: Programs/config.c:411
+msgid "Remain a foreground process."
+msgstr "البقاء كعملية في الواجهة."
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr "إزالة النقطتين سبعة وثمانية."
+
+#: Programs/config.c:426
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr "قم بإزالة خدمة %s، ثم قم بالإنهاء."
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr "الإبلاغ عن مشاكل الجدول الرئيسي."
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr "الإبلاغ عن الأحرف الموجودة في خط الشاشة الحالي والتي لم يتم تحديدها داخل جدول النص."
+
+#: Programs/menu_prefs.c:866
+msgid "Rest of Line"
+msgstr "بقية السطر"
+
+#: Programs/menu_prefs.c:1562
+msgid "Restart Braille Driver"
+msgstr "إعادة تشغيل ملف تعريف برايل"
+
+#: Programs/menu_prefs.c:1574
+msgid "Restart Screen Driver"
+msgstr "إعادة تشغيل ملف تعريف الشاشة"
+
+#: Programs/menu_prefs.c:1568
+msgid "Restart Speech Driver"
+msgstr "إعادة تشغيل ملف تعريف النطق"
+
+#. xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+#: Programs/midi.c:452
+msgid "Reverse Cymbal"
+msgstr "عكس الرمز"
+
+#: Programs/menu_prefs.c:1342
+msgid "Right"
+msgstr "يمين"
+
+#. xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+#: Programs/midi.c:136
+msgid "Rock Organ"
+msgstr "أورغان صخري"
+
+#: Programs/menu_prefs.c:674
+msgid "Save on Exit"
+msgstr "الحفظ عند الخروج"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1208
+msgid "Say Cap"
+msgstr "نطق Cap للأحرف الكبيرة"
+
+#: Programs/menu_prefs.c:1232
+msgid "Say Line Mode"
+msgstr "نطق نمط السطر"
+
+#: Programs/menu_prefs.c:1219
+msgid "Say Space"
+msgstr "نطق المسافة"
+
+#: Programs/menu_prefs.c:780
+msgid "Screen Cursor Blink Period"
+msgstr "فترة وميض مؤشر الشاشة"
+
+#: Programs/menu_prefs.c:788
+msgid "Screen Cursor Percent Visible"
+msgstr "نسبة رؤية مؤشر الشاشة"
+
+#: Programs/menu_prefs.c:768
+msgid "Screen Cursor Style"
+msgstr "نمط مؤشر الشاشة"
+
+#: Programs/config.c:2453
+msgid "Screen Driver"
+msgstr "ملف تعريف الشاشة"
+
+#: Programs/log.c:160
+msgid "Screen Driver Events"
+msgstr "أحداث ملف تعريف الشاشة"
+
+#: Programs/menu_prefs.c:550
+msgid "Screen Number"
+msgstr "رقم الشاشة"
+
+#: Programs/config.c:2459
+msgid "Screen Parameter"
+msgstr "إعداد الشاشة"
+
+#: Programs/config.c:693
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr "كود برنامج تشغيل الشاشة (%s, %s, أو أياً من {%s})."
+
+#: Programs/menu_prefs.c:891
+msgid "Scroll-aware Cursor Navigation"
+msgstr "التنقل في المؤشر مع التمرير"
+
+#. xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+#: Programs/midi.c:462
+msgid "Seashore"
+msgstr "شاطئ البحر"
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr "المسلسل I / O"
+
+#: Programs/log.c:124
+msgid "Server Events"
+msgstr "أحداث الخادم"
+
+#. xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+#: Programs/midi.c:320
+msgid "Shakuhachi"
+msgstr "شاكوهاتشي"
+
+#. xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+#: Programs/midi.c:412
+msgid "Shamisen"
+msgstr "شاميسين"
+
+#. xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+#: Programs/midi.c:427
+msgid "Shanai"
+msgstr "شاناي"
+
+#: Programs/menu_prefs.c:687
+msgid "Show Advanced Submenus"
+msgstr "إظهار القوائم الفرعية المتقدمة"
+
+#: Programs/menu_prefs.c:692
+msgid "Show All Items"
+msgstr "إظهار كافة العناصر"
+
+#: Programs/menu_prefs.c:796
+msgid "Show Attributes"
+msgstr "إظهار السمات"
+
+#: Programs/menu_prefs.c:763
+msgid "Show Screen Cursor"
+msgstr "إظهار مؤشر الشاشة"
+
+#: Programs/menu_prefs.c:1295
+msgid "Show Seconds"
+msgstr "إظهار الثواني"
+
+#: Programs/menu_prefs.c:1237
+msgid "Show Speech Cursor"
+msgstr "إظهار مؤشر النطق"
+
+#: Programs/menu_prefs.c:682
+msgid "Show Submenu Sizes"
+msgstr "إظهار أحجام القوائم الفرعية"
+
+#. Ethnic Instruments
+#. xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+#: Programs/midi.c:406
+msgid "Sitar"
+msgstr "سيتار"
+
+#: Programs/menu_prefs.c:858
+msgid "Skip Blank Braille Windows"
+msgstr "تخطي نوافذ برايل الفارغة"
+
+#: Programs/menu_prefs.c:852
+msgid "Skip Identical Lines"
+msgstr "تخطي الأسطر المتطابقة"
+
+#: Programs/menu_prefs.c:869
+msgid "Skip Which Blank Braille Windows"
+msgstr "تخطي أي نوافذ برايل فارغة"
+
+#. xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+#: Programs/midi.c:192
+msgid "Slap Bass 1"
+msgstr "صفعة باس 1"
+
+#. xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+#: Programs/midi.c:195
+msgid "Slap Bass 2"
+msgstr "صفعة باس 2"
+
+#: Programs/menu_prefs.c:1325
+msgid "Slash"
+msgstr "شرطة مائلة"
+
+#: Programs/menu_prefs.c:874
+msgid "Sliding Braille Window"
+msgstr "نافذة برايل منزلقة"
+
+#: Programs/menu_prefs.c:1193
+msgid "Some"
+msgstr "بعض"
+
+#. Reed
+#. xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+#: Programs/midi.c:280
+msgid "Soprano Saxophone"
+msgstr "سوبرانو ساكسفون"
+
+#. xgettext: This is the name of MIDI musical instrument group #16.
+#: Programs/midi.c:74
+msgid "Sound Effects"
+msgstr "مؤثرات صوتية"
+
+#: Programs/menu_prefs.c:561 Programs/menu_prefs.c:1361
+msgid "Space"
+msgstr "مسافة"
+
+#: Programs/menu_prefs.c:1154
+msgid "Speak Completed Words"
+msgstr "نطق الكلمات المكتملة"
+
+#: Programs/menu_prefs.c:1142
+msgid "Speak Deleted Characters"
+msgstr "نطق الأحرف المحذوفة"
+
+#: Programs/menu_prefs.c:1136
+msgid "Speak Inserted Characters"
+msgstr "نطق الأحرف المدرجة"
+
+#: Programs/menu_prefs.c:1160
+msgid "Speak Line Indent"
+msgstr "نطق محاذاة السطر"
+
+#: Programs/menu_prefs.c:1148
+msgid "Speak Replaced Characters"
+msgstr "نطق الأحرف المستبدلة"
+
+#: Programs/menu_prefs.c:1130
+msgid "Speak Selected Character"
+msgstr "نطق الحرف المحدد"
+
+#: Programs/menu_prefs.c:1124
+msgid "Speak Selected Line"
+msgstr "نطق السطر المحدد"
+
+#: Programs/menu_prefs.c:1254
+msgid "Speech Cursor Blink Period"
+msgstr "فترة وميض مؤشر النطق"
+
+#: Programs/menu_prefs.c:1262
+msgid "Speech Cursor Percent Visible"
+msgstr "نسبة رؤية مؤشر النطق"
+
+#: Programs/menu_prefs.c:1242
+msgid "Speech Cursor Style"
+msgstr "نمط مؤشر الكلام"
+
+#: Programs/config.c:2228
+msgid "Speech Driver"
+msgstr "ملف تعريف النطق"
+
+#: Programs/log.c:154
+msgid "Speech Driver Events"
+msgstr "أحداث ملف تعريف النطق"
+
+#: Programs/log.c:112
+msgid "Speech Events"
+msgstr "أحداث النطق"
+
+#. Create the file system object for speech input.
+#: Programs/config.c:3023
+msgid "Speech Input"
+msgstr "إدخال النطق"
+
+#: Programs/menu_prefs.c:1167
+msgid "Speech Options"
+msgstr "خيارات النطق"
+
+#: Programs/config.c:2231
+msgid "Speech Parameter"
+msgstr "إعداد النطق"
+
+#: Programs/menu_prefs.c:1184
+msgid "Speech Pitch"
+msgstr "حدة الكلام"
+
+#: Programs/menu_prefs.c:1197
+msgid "Speech Punctuation"
+msgstr "نطق علامات الترقيم"
+
+#: Programs/menu_prefs.c:1177
+msgid "Speech Rate"
+msgstr "سرعة الكلام"
+
+#: Programs/menu_prefs.c:1212
+msgid "Speech Uppercase Indicator"
+msgstr "الإعلان عن الأحرف الكبيرة"
+
+#: Programs/menu_prefs.c:1170
+msgid "Speech Volume"
+msgstr "مستوى صوت النطق"
+
+#: Programs/menu_prefs.c:1222
+msgid "Speech Whitespace Indicator"
+msgstr "الإعلان عن المسافات البيضاء"
+
+#: Programs/config.c:650
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr "كود ملف تعريف النطق (%s, %s, أو أياً من {%s})."
+
+#: Programs/menu_prefs.c:1509
+msgid "Standard Error Log Level"
+msgstr "مستوى سجل الخطأ القياسي"
+
+#: Programs/menu_prefs.c:926
+msgid "Start Selection with Routing Key"
+msgstr "بدء التحديد باستخدام زر المؤشر"
+
+#: Programs/menu_prefs.c:551
+msgid "State Dots"
+msgstr "نقاط الحالة"
+
+#: Programs/menu_prefs.c:552
+msgid "State Letter"
+msgstr "حرف الحالة"
+
+#: Programs/menu_prefs.c:1336
+msgid "Status Cells"
+msgstr "خلايا الحالة"
+
+#: Programs/menu_prefs.c:1352
+msgid "Status Count"
+msgstr "عدد الحالة"
+
+#: Programs/menu_prefs.c:565
+msgid "Status Field"
+msgstr "حقل الحالة"
+
+#: Programs/menu_prefs.c:1345
+msgid "Status Position"
+msgstr "موضع الحالة"
+
+#: Programs/menu_prefs.c:1367
+msgid "Status Separator"
+msgstr "فاصل الحالة"
+
+#: Programs/menu_prefs.c:1363
+msgid "Status Side"
+msgstr "جانب الحالة"
+
+#. xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+#: Programs/midi.c:437
+msgid "Steel Drums"
+msgstr "درامز حديدي"
+
+#: Programs/config.c:450
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr "إيقاف مثيل موجود لـ %s، ثم الإنهاء."
+
+#. Ensemble
+#. xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+#: Programs/midi.c:230
+msgid "String Ensemble 1"
+msgstr "فرقة الوتر 1"
+
+#. xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+#: Programs/midi.c:233
+msgid "String Ensemble 2"
+msgstr "فرقة الوتر 2"
+
+#. xgettext: This is the name of MIDI musical instrument group #6.
+#: Programs/midi.c:40
+msgid "Strings"
+msgstr "الأوتار"
+
+#: Programs/menu_prefs.c:722
+msgid "Superimpose Dot 7"
+msgstr "فرض النقطة 7"
+
+#: Programs/config.c:744
+msgid "Suppress start-up messages."
+msgstr "إيقاف رسائل بدء التشغيل."
+
+#. xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+#: Programs/midi.c:198
+msgid "Synth Bass 1"
+msgstr "موالفة باس 1"
+
+#. xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+#: Programs/midi.c:201
+msgid "Synth Bass 2"
+msgstr "موالفة باس 2"
+
+#. xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+#: Programs/midi.c:449
+msgid "Synth Drum"
+msgstr "موالفة طبل"
+
+#. xgettext: This is the name of MIDI musical instrument group #13.
+#. xgettext: (synth is a common short form for synthesizer)
+#. xgettext: (FM is the acronym for Frequency Modulation)
+#: Programs/midi.c:65
+msgid "Synth FM"
+msgstr "Synth FM"
+
+#. xgettext: This is the name of MIDI musical instrument group #11.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:56
+msgid "Synth Lead"
+msgstr "Synth Lead"
+
+#. xgettext: This is the name of MIDI musical instrument group #12.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:60
+msgid "Synth Pad"
+msgstr "موالفة الأثر"
+
+#. xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+#: Programs/midi.c:248
+msgid "Synth Voice"
+msgstr "موالفة صوت"
+
+#. xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+#: Programs/midi.c:273
+msgid "SynthBrass 1"
+msgstr "موالفة النحاس 1"
+
+#. xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+#: Programs/midi.c:276
+msgid "SynthBrass 2"
+msgstr "موالفة النحاس 2"
+
+#. xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+#: Programs/midi.c:236
+msgid "SynthStrings 1"
+msgstr "موالفة الأوتار 1"
+
+#. xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+#: Programs/midi.c:239
+msgid "SynthStrings 2"
+msgstr "موالفة الأوتار 2"
+
+#: Programs/menu_prefs.c:1504
+msgid "System Log Level"
+msgstr "مستوى سجل النظام"
+
+#: Programs/config.c:2977 Programs/menu_prefs.c:1478
+msgid "Tables Directory"
+msgstr "مسار الجداول"
+
+#. xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+#: Programs/midi.c:443
+msgid "Taiko Drum"
+msgstr "تايكو درام"
+
+#. xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+#: Programs/midi.c:151
+msgid "Tango Accordion"
+msgstr "تانجو أكورديون"
+
+#. xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+#: Programs/midi.c:468
+msgid "Telephone Ring"
+msgstr "رنين الهاتف"
+
+#. xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+#: Programs/midi.c:286
+msgid "Tenor Saxophone"
+msgstr "تينور ساكسفون"
+
+#: Programs/menu_prefs.c:760
+msgid "Text Indicators"
+msgstr "مؤشرات النص"
+
+#: Programs/menu_prefs.c:1364
+msgid "Text Side"
+msgstr "جانب النص"
+
+#: Programs/config.c:1003 Programs/menu_prefs.c:1392
+msgid "Text Table"
+msgstr "جدول النص"
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr "جدول النص."
+
+#: Programs/config.c:381
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr "النص الذي سيتم عرضه عند بدء تشغيل سائقة برايل ويتم نطقه عند بدء تشغيل سائقة النطق."
+
+#: Programs/config.c:388
+msgid "The text to be shown when the braille driver stops."
+msgstr "النص الذي سيتم عرضه عند توقف برنامج تشغيل برايل."
+
+#: Programs/menu_prefs.c:553
+msgid "Time"
+msgstr "الوقت"
+
+#: Programs/menu_prefs.c:1280
+msgid "Time Format"
+msgstr "تنسيق الوقت"
+
+#: Programs/menu_prefs.c:1272
+msgid "Time Presentation"
+msgstr "عرض الوقت"
+
+#: Programs/menu_prefs.c:1290
+msgid "Time Separator"
+msgstr "فاصل الوقت"
+
+#. xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+#: Programs/midi.c:226
+msgid "Timpani"
+msgstr "تيمباني"
+
+#. Percussive Instruments
+#. xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+#: Programs/midi.c:431
+msgid "Tinkle Bell"
+msgstr "تينكل بيل"
+
+#: Programs/menu_prefs.c:1558
+msgid "Tools"
+msgstr "أدوات"
+
+#: Programs/menu_prefs.c:1003
+msgid "Touch Navigation"
+msgstr "التنقل باللمس"
+
+#: Programs/menu_prefs.c:1017
+msgid "Touch Sensitivity"
+msgstr "حساسية اللمس"
+
+#: Programs/menu_prefs.c:915
+msgid "Track Screen Pointer"
+msgstr "تتبع مؤشر الشاشة"
+
+#: Programs/menu_prefs.c:909
+msgid "Track Screen Scroll"
+msgstr "تتبع تمرير الشاشة"
+
+#: Programs/menu_prefs.c:941
+msgid "Translated via Text Table"
+msgstr "تم ترجمته عبر جدول النص"
+
+#. xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+#: Programs/midi.c:217
+msgid "Tremolo Strings"
+msgstr "سلاسل Tremolo"
+
+#. xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+#: Programs/midi.c:258
+msgid "Trombone"
+msgstr "الترومبون"
+
+#. Brass
+#. xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+#: Programs/midi.c:255
+msgid "Trumpet"
+msgstr "البوق"
+
+#. xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+#: Programs/midi.c:261
+msgid "Tuba"
+msgstr "آلة توبا"
+
+#. xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+#: Programs/midi.c:123
+msgid "Tubular Bells"
+msgstr "أجراس أنبوبي"
+
+#: Programs/menu_prefs.c:1062
+msgid "Tune Device"
+msgstr "ضبط الجهاز"
+
+#: Programs/menu_prefs.c:945
+msgid "Typing Mode"
+msgstr "نمط الكتابة"
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr "USB I/O"
+
+#: Programs/menu_prefs.c:664
+msgid "Underline"
+msgstr "تسطير"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr "غير مجمد"
+
+#: Programs/config.c:2974 Programs/menu_prefs.c:1458
+msgid "Updatable Directory"
+msgstr "دليل قابل للتحديث"
+
+#: Programs/log.c:106
+msgid "Update Events"
+msgstr "أحداث التحديث"
+
+#: Programs/options.c:197
+msgid "Usage"
+msgstr "الاستخدام"
+
+#: Programs/menu_prefs.c:721
+msgid "Use Capital Sign"
+msgstr "استخدام علامة الأحرف الكبيرة"
+
+#. xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+#: Programs/midi.c:114
+msgid "Vibraphone"
+msgstr "الفيبرافون"
+
+#. xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+#: Programs/midi.c:208
+msgid "Viola"
+msgstr "فيولا"
+
+#. Strings
+#. xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+#: Programs/midi.c:205
+msgid "Violin"
+msgstr "كمان"
+
+#. xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+#: Programs/midi.c:245
+msgid "Voice Oohs"
+msgstr "صوت Oohs"
+
+#: Programs/menu_prefs.c:1494
+msgid "Warning"
+msgstr "تحذير"
+
+#: Programs/menu_prefs.c:1438
+msgid "Web Site"
+msgstr "الموقع"
+
+#. xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+#: Programs/midi.c:323
+msgid "Whistle"
+msgstr "صفارة"
+
+#: Programs/menu_prefs.c:543
+msgid "Window Column"
+msgstr "عمود النافذة"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:558
+msgid "Window Coordinates"
+msgstr "إحداثيات النافذة"
+
+#: Programs/menu_prefs.c:544
+msgid "Window Row"
+msgstr "صف النافذة"
+
+#. xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+#: Programs/midi.c:440
+msgid "Woodblock"
+msgstr "الخشب"
+
+#: Programs/menu_prefs.c:848
+msgid "Word Wrap"
+msgstr "التفاف الكلام"
+
+#: Programs/config.c:2948
+msgid "Working Directory"
+msgstr "المسار العامل"
+
+#: Programs/config.c:2975 Programs/menu_prefs.c:1468
+msgid "Writable Directory"
+msgstr "المسار القابل للكتباة"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr "كتابة إخراج التصحيح إلى stdout"
+
+#: Programs/config.c:767
+msgid "Write the start-up logs, and then exit."
+msgstr "اكتب سجلات البدء ، ثم اخرج."
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr "شاشة X للاتصال مع"
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr "مشكلة الوصول إلى دليل وقت تشغيل XDG"
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr "تم إنشاء دليل وقت تشغيل XDG"
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr "دليل وقت تشغيل XDG موجود"
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr "XFree (wm_name) للتغيير"
+
+#. xgettext: This is the description of the PASSXT command.
+#: Programs/cmds.auto.h:1479
+msgid "XT (set 1) keyboard scan code"
+msgstr "XT (مجموعة 1) رمز مسح لوحة المفاتيح"
+
+#. xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+#: Programs/midi.c:120
+msgid "Xylophone"
+msgstr "إكسيليفون"
+
+#: Programs/menu_prefs.c:1312
+msgid "Year Month Day"
+msgstr "سنة شهر يوم"
+
+#: Programs/menu.c:524
+msgid "Yes"
+msgstr "نعم"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr "[host][:port]"
+
+#: Programs/menu_prefs.c:665
+msgid "all dots"
+msgstr "كل النقاط"
+
+#: Programs/cmd_miscellaneous.c:104
+msgid "and"
+msgstr "و"
+
+#. xgettext: This is the description of the CLIP_APPEND command.
+#: Programs/cmds.auto.h:1346
+msgid "append characters to clipboard"
+msgstr "إلحاق الأحرف بالحافظة"
+
+#. xgettext: This is the description of the CLIP_ADD command.
+#: Programs/cmds.auto.h:1223
+msgid "append to clipboard from character"
+msgstr "إلحاق الحافظة من الحرف"
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr "عند المؤشر"
+
+#. xgettext: This is the description of the KEY_BACKSPACE command.
+#: Programs/cmds.auto.h:1527
+msgid "backspace key"
+msgstr "مفتاح الحذف الخلفي"
+
+#: Drivers/Braille/Baum/braille.c:1149 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr "البطارية منخفضة"
+
+#. xgettext: This is the description of the SELECTVT command.
+#: Programs/cmds.auto.h:1437
+msgid "bind to specific virtual terminal"
+msgstr "الالتزام بنافذة أوامر افتراضية محددة"
+
+#. xgettext: This is the description of the SELECTVT_NEXT command.
+#: Programs/cmds.auto.h:957
+msgid "bind to the next virtual terminal"
+msgstr "ربط بنافذة الأوامر الافتراضية التالية"
+
+#. xgettext: This is the description of the SELECTVT_PREV command.
+#: Programs/cmds.auto.h:950
+msgid "bind to the previous virtual terminal"
+msgstr "ربط بنافذة الأوامر الافتراضية السابقة"
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "أسود"
+
+#: Programs/core.c:1125
+msgid "blank line"
+msgstr "سطر فارغ"
+
+#: Programs/cmd_utils.c:173
+msgid "blinking"
+msgstr "وميض"
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "أزرق"
+
+#: Programs/config.c:2993
+#, c-format
+msgid "braille device not specified"
+msgstr "جهاز برايل غير محدد"
+
+#. xgettext: This is the description of the OFFLINE command.
+#: Programs/cmds.auto.h:621
+msgid "braille display temporarily unavailable"
+msgstr "شاشة برايل غير متاحة مؤقتًا"
+
+#: Programs/config.c:1737
+msgid "braille driver initialization failed"
+msgstr "فشلت تهيئة برنامج تشغيل برايل"
+
+#: Programs/config.c:1816
+msgid "braille driver not loadable"
+msgstr "برنامج تشغيل برايل غير قابل للتحميل"
+
+#: Programs/config.c:2077
+msgid "braille driver restarting"
+msgstr "إعادة تشغيل سائقة برايل"
+
+#: Programs/cmd_miscellaneous.c:163
+msgid "braille driver stopped"
+msgstr "توقف سائقة برايل"
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr "تم تحرير برايل"
+
+#. xgettext: This is the description of the ROUTE command.
+#: Programs/cmds.auto.h:1207
+msgid "bring screen cursor to character"
+msgstr "إحضار مؤشر الشاشة إلى الحرف"
+
+#. xgettext: This is the description of the CSRJMP_VERT command.
+#: Programs/cmds.auto.h:593
+msgid "bring screen cursor to current line"
+msgstr "إحضار مؤشر الشاشة إلى السطر الحالي"
+
+#. xgettext: This is the description of the ROUTE_LINE command.
+#: Programs/cmds.auto.h:1404
+msgid "bring screen cursor to line"
+msgstr "إحضار مؤشر الشاشة إلى السطر"
+
+#. xgettext: This is the description of the ROUTE_CURR_LOCN command.
+#: Programs/cmds.auto.h:835
+msgid "bring screen cursor to speech cursor"
+msgstr "إحضار مؤشر الشاشة إلى مؤشر النطق"
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "بني"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr "لا يمكن الحصول على حالة وحدة التحكم"
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr "لا يمكن فتح وحدة التحكم"
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr "لا يمكن قراءة محتوى الشاشة"
+
+#: Drivers/Screen/Linux/screen.c:1601
+msgid "can't read screen header"
+msgstr "لا يمكن قراءة رأس الشاشة"
+
+#: Programs/ctb_translate.c:393
+msgid "cannot access internal contraction table"
+msgstr "لا يمكن الوصول إلى جدول الاختصارات الداخلي"
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr "لا يمكن تجميع جدول السمات"
+
+#: Programs/ctb_translate.c:387
+msgid "cannot compile contraction table"
+msgstr "لا يمكن تجميع جدول الاختصارات"
+
+#: Programs/config.c:1720
+msgid "cannot compile key table"
+msgstr "لا يمكن تجميع جدول المفاتيح"
+
+#: Programs/config.c:1284
+msgid "cannot compile keyboard table"
+msgstr "لا يمكن تجميع جدول لوحة المفاتيح"
+
+#: Programs/ttb_translate.c:275
+msgid "cannot compile text table"
+msgstr "لا يمكن تجميع جدول النص"
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr "لا يمكن الاتصال بالعميل الخفي لأجهزة برايل في %s \\ n\n"
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr "لا يمكن الاتصال بشاشة %s \\ n\n"
+
+#: Programs/file.c:381
+msgid "cannot create directory"
+msgstr "لا يمكن إنشاء المسار"
+
+#: Programs/program.c:150
+#, c-format
+msgid "cannot determine program directory"
+msgstr "لا يمكن تحديد دليل البرنامج"
+
+#: Programs/config.c:2951 Programs/menu.c:618
+msgid "cannot determine working directory"
+msgstr "لا يمكن تحديد دليل العمل"
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr "لا يمكن العثور على الإجراء"
+
+#: Programs/program.c:158
+msgid "cannot fix install path"
+msgstr "لا يمكن إصلاح مسار التثبيت"
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr "لا يمكن الحصول على tty \\ n\n"
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr "لا يمكن الحصول على tty %d \\ n\n"
+
+#: Programs/file.c:518
+msgid "cannot get working directory"
+msgstr "لا يمكن الحصول على دليل العمل"
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr "لا يمكن انتزاع الإطارات على الشاشة %d \\ n\n"
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr "لا يمكن تجاهل المفاتيح \\ n\n"
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr "لا يمكن تحميل جدول السمات"
+
+#: Programs/ctb_translate.c:407
+msgid "cannot load contraction table"
+msgstr "لا يمكن تحميل جدول الاختصارات"
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr "لا يمكن تحميل المكتبة"
+
+#: Programs/ttb_translate.c:295
+msgid "cannot load text table"
+msgstr "لا يمكن تحميل جدول نصي"
+
+#: Programs/file.c:370
+msgid "cannot make world writable"
+msgstr "لا يمكن جعل العالم قابل للكتابة"
+
+#: Programs/config.c:1239
+msgid "cannot open key help"
+msgstr "لا يمكن فتح مساعدة المفاتيح"
+
+#: Programs/program.c:262
+msgid "cannot open process identifier file"
+msgstr "لا يمكن فتح ملف معرف العملية"
+
+#: Programs/menu.c:616
+msgid "cannot open working directory"
+msgstr "لا يمكن فتح مسار العمل"
+
+#: Programs/prefs.c:363
+msgid "cannot read preferences file"
+msgstr "لا يمكن قراءة ملف التفضيلات"
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr "لا يمكن تعيين التركيز على %# 010x \n"
+
+#: Programs/file.c:532 Programs/menu.c:605
+msgid "cannot set working directory"
+msgstr "لا يمكن تعيين دليل العمل"
+
+#: Programs/prefs.c:576
+msgid "cannot write to preferences file"
+msgstr "لا يمكن الكتابة إلى ملف التفضيلات"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1070
+msgid "cap"
+msgstr "cap"
+
+#: Programs/menu_prefs.c:886 Programs/menu_prefs.c:1353
+msgid "cells"
+msgstr "خلايا"
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr "تم تجاهل التغييرات"
+
+#. xgettext: This is the description of the UNSTICK command.
+#: Programs/cmds.auto.h:887
+msgid "clear all sticky input modifiers"
+msgstr "امسح كافة معدّلات الإدخال اللاصقة"
+
+#. xgettext: This is the description of the TXTSEL_CLEAR command.
+#: Programs/cmds.auto.h:1019
+msgid "clear the text selection"
+msgstr "امسح تحديد النص"
+
+#: Programs/cmd_speech.c:501
+msgid "column"
+msgstr "عمود"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr "config"
+
+#: Programs/options.c:812
+msgid "configuration directive specified more than once"
+msgstr "تم تحديد توجيه التكوين أكثر من مرة"
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr "وحدة التحكم ليست قيد الاستخدام"
+
+#: Programs/menu_prefs.c:1056
+msgid "console tone generator"
+msgstr "مولد نغمة وحدة التحكم"
+
+#. xgettext: This is the description of the CLIP_COPY command.
+#: Programs/cmds.auto.h:1338
+msgid "copy characters to clipboard"
+msgstr "نسخ الأحرف إلى الحافظة"
+
+#. xgettext: This is the description of the HOST_COPY command.
+#: Programs/cmds.auto.h:1033
+msgid "copy selected text to host clipboard"
+msgstr "نسخ النص المحدد لاستضافة الحافظة"
+
+#: Programs/config.c:729
+msgid "csecs"
+msgstr "csecs"
+
+#. xgettext: This is the description of the TOUCH_AT command.
+#: Programs/cmds.auto.h:1503
+msgid "current reading location"
+msgstr "موقع القراءة الحالي"
+
+#. xgettext: This is the description of the KEY_CURSOR_DOWN command.
+#: Programs/cmds.auto.h:1567
+msgid "cursor-down key"
+msgstr "مفتاح المؤشر لأسفل"
+
+#. xgettext: This is the description of the KEY_CURSOR_LEFT command.
+#: Programs/cmds.auto.h:1543
+msgid "cursor-left key"
+msgstr "مفتاح المؤشر الأيسر"
+
+#. xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+#: Programs/cmds.auto.h:1551
+msgid "cursor-right key"
+msgstr "مفتاح المؤشر الأيمن"
+
+#. xgettext: This is the description of the KEY_CURSOR_UP command.
+#: Programs/cmds.auto.h:1559
+msgid "cursor-up key"
+msgstr "مفتاح المؤشر لأعلى"
+
+#. xgettext: This is the description of the HOST_CUT command.
+#: Programs/cmds.auto.h:1040
+msgid "cut selected text to host clipboard"
+msgstr "قص النص المحدد للحافظة المستضيفة"
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr "ازرق سماوي"
+
+#. xgettext: This is the description of the ALTGR command.
+#: Programs/cmds.auto.h:894
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr "دورة معدل الإدخال الثابت AltGr (اليمين البديل) (التالي ، تشغيل ، إيقاف تشغيل)"
+
+#. xgettext: This is the description of the CONTROL command.
+#: Programs/cmds.auto.h:642
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr "دورة في معدل الإدخال الثابت للتحكم (التالي ، تشغيل ، إيقاف تشغيل)"
+
+#. xgettext: This is the description of the GUI command.
+#: Programs/cmds.auto.h:901
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr "دورة في معدل الإدخال الثابت GUI (Windows) (التالي ، تشغيل ، إيقاف تشغيل)"
+
+#. xgettext: This is the description of the META command.
+#: Programs/cmds.auto.h:649
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr "دورة معدّل الإدخال الثابت Meta (Alt الأيسر) (التالي ، تشغيل ، إيقاف)"
+
+#. xgettext: This is the description of the SHIFT command.
+#: Programs/cmds.auto.h:628
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr "دورة معدل الإدخال اللاصق Shift (التالي ، تشغيل ، إيقاف تشغيل)"
+
+#. xgettext: This is the description of the UPPER command.
+#: Programs/cmds.auto.h:635
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr "دورة معدل الإدخال العلوي اللاصق (التالي ، تشغيل ، إيقاف تشغيل)"
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "رمادي غامق"
+
+#. xgettext: This is the description of the SAY_LOWER command.
+#: Programs/cmds.auto.h:1168
+msgid "decrease speaking pitch"
+msgstr "خفض حدة الكلام"
+
+#. xgettext: This is the description of the SAY_SLOWER command.
+#: Programs/cmds.auto.h:550
+msgid "decrease speaking rate"
+msgstr "خفض سرعة الكلام"
+
+#. xgettext: This is the description of the SAY_SOFTER command.
+#: Programs/cmds.auto.h:564
+msgid "decrease speaking volume"
+msgstr "خفض مستوى الصوت"
+
+#. xgettext: This is the description of the KEY_DELETE command.
+#: Programs/cmds.auto.h:1615
+msgid "delete key"
+msgstr "مفتاح الحذف"
+
+#. xgettext: This is the description of the DESCCHAR command.
+#: Programs/cmds.auto.h:1275
+msgid "describe character"
+msgstr "وصف الحرف"
+
+#. xgettext: This is the description of the DESC_CURR_CHAR command.
+#: Programs/cmds.auto.h:820
+msgid "describe current character"
+msgstr "وصف الحرف الحالي"
+
+#: Programs/config.c:710 Programs/config.c:720
+msgid "device"
+msgstr "الجهاز"
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:400 Programs/config.c:500
+#: Programs/config.c:510 Programs/config.c:520 Programs/config.c:591
+msgid "directory"
+msgstr "المسار"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr "العرض"
+
+#. xgettext: This is the description of the NOOP command.
+#: Programs/cmds.auto.h:5
+msgid "do nothing"
+msgstr "لا تفعل شيء"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr "تم"
+
+#: Programs/menu_prefs.c:666
+msgid "dot 7"
+msgstr "النقطة 7"
+
+#: Programs/menu_prefs.c:667
+msgid "dot 8"
+msgstr "النقطة 8"
+
+#: Programs/menu_prefs.c:664
+msgid "dots 7 and 8"
+msgstr "النقاط 7 و8"
+
+#: Drivers/Braille/Baum/braille.c:1146
+msgid "driver request"
+msgstr "طلب ملف التعريف"
+
+#: Programs/config.c:549 Programs/config.c:647 Programs/config.c:690
+msgid "driver,..."
+msgstr "سائقة،..."
+
+#. xgettext: This is the description of the KEY_END command.
+#: Programs/cmds.auto.h:1599
+msgid "end key"
+msgstr "مفتاح النهاية"
+
+#. xgettext: This is the description of the KEY_ENTER command.
+#: Programs/cmds.auto.h:1511
+msgid "enter key"
+msgstr "مفتاح الدخول"
+
+#. xgettext: This is the description of the LEARN command.
+#: Programs/cmds.auto.h:436
+msgid "enter/leave command learn mode"
+msgstr "دخول/مغادرة وضع تعلم الأوامر"
+
+#. xgettext: This is the description of the HELP command.
+#: Programs/cmds.auto.h:422
+msgid "enter/leave help display"
+msgstr "دخول/مغادرة مساعدة العرض"
+
+#. xgettext: This is the description of the PREFMENU command.
+#: Programs/cmds.auto.h:443
+msgid "enter/leave preferences menu"
+msgstr "دخول/مغادرة قائمة التفضيلات"
+
+#. xgettext: This is the description of the INFO command.
+#: Programs/cmds.auto.h:429
+msgid "enter/leave status display"
+msgstr "دخول / مغادرة عرض الحالة"
+
+#. xgettext: This is the description of the KEY_ESCAPE command.
+#: Programs/cmds.auto.h:1535
+msgid "escape key"
+msgstr "مفتاح الهروب"
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "eurobraille"
+
+#. xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+#: Programs/cmd_miscellaneous.c:102
+msgid "exactly"
+msgstr "تماما"
+
+#: Programs/config.c:873
+msgid "excess argument"
+msgstr "حجة زائدة"
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr "تنفيذ \"%s\" (من \" %s\") \n"
+
+#: Programs/pgmprivs_linux.c:2045
+msgid "executing as the invoking user"
+msgstr "تنفيذ بصفته المستخدم المستدعي"
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr "فشل تنفيذ \"%s\": %s \n"
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr "فشل في الحصول على التركيز الأول \n"
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:457
+#: Programs/config.c:466 Programs/config.c:476 Programs/config.c:602
+#: Programs/config.c:612 Programs/config.c:621 Programs/config.c:629
+#: Programs/config.c:666 Programs/config.c:759
+msgid "file"
+msgstr "ملف"
+
+#: Programs/options.c:998
+#, c-format
+msgid "file '%s' processing error."
+msgstr "خطأ في معالجة الملف '%s'."
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr "فشل تفرع \"%s\":%s\n"
+
+#. xgettext: This is the description of the KEY_FUNCTION command.
+#: Programs/cmds.auto.h:1624
+msgid "function key"
+msgstr "مفتاح وظيفي"
+
+#. xgettext: This is the description of the BACK command.
+#: Programs/cmds.auto.h:271
+msgid "go back after cursor tracking"
+msgstr "الرجوع بعد تتبع المؤشر"
+
+#. xgettext: This is the description of the GUI_BACK command.
+#: Programs/cmds.auto.h:1077
+msgid "go back to the previous screen"
+msgstr "العودة للشاشة السابقة"
+
+#. xgettext: This is the description of the FWINLT command.
+#: Programs/cmds.auto.h:210
+msgid "go backward one braille window"
+msgstr "الرجوع للخلف نافذة برايل واحدة"
+
+#. xgettext: This is the description of the FWINLTSKIP command.
+#: Programs/cmds.auto.h:228
+msgid "go backward skipping blank braille windows"
+msgstr "الرجوع للخلف مع تخطي نوافذ برايل الفارغة"
+
+#. xgettext: This is the description of the PRNBWIN command.
+#: Programs/cmds.auto.h:966
+msgid "go backward to nearest non-blank braille window"
+msgstr "الرجوع للخلف إلى أقرب نافذة برايل غير فارغة"
+
+#. xgettext: This is the description of the LNDN command.
+#: Programs/cmds.auto.h:23
+msgid "go down one line"
+msgstr "النزول سطر واحد"
+
+#. xgettext: This is the description of the WINDN command.
+#: Programs/cmds.auto.h:41
+msgid "go down several lines"
+msgstr "النزول عدة أسطر"
+
+#. xgettext: This is the description of the NXPGRPH command.
+#: Programs/cmds.auto.h:133
+msgid "go down to first line of next paragraph"
+msgstr "النزول إلى السطر الأول من الفقرة التالية"
+
+#. xgettext: This is the description of the MENU_LAST_ITEM command.
+#: Programs/cmds.auto.h:475
+msgid "go down to last item"
+msgstr "النزول إلى آخر عنصر"
+
+#. xgettext: This is the description of the NXDIFCHAR command.
+#: Programs/cmds.auto.h:1330
+msgid "go down to nearest line with different character"
+msgstr "النزول إلى أقرب سطر بحرف مختلف"
+
+#. xgettext: This is the description of the NXDIFLN command.
+#: Programs/cmds.auto.h:59
+msgid "go down to nearest line with different content"
+msgstr "النزول إلى أقرب سطر بمحتوى مختلف"
+
+#. xgettext: This is the description of the ATTRDN command.
+#: Programs/cmds.auto.h:77
+msgid "go down to nearest line with different highlighting"
+msgstr "النزول إلى أقرب سطر مع تمييز مختلف"
+
+#. xgettext: This is the description of the NXINDENT command.
+#: Programs/cmds.auto.h:1267
+msgid "go down to nearest line with less indent than character"
+msgstr "النزول إلى أقرب سطر بمسافة أحرف بادئة أقل"
+
+#. xgettext: This is the description of the NXPROMPT command.
+#: Programs/cmds.auto.h:151
+msgid "go down to next command prompt"
+msgstr "النزول إلى موجه الأوامر التالي"
+
+#. xgettext: This is the description of the MENU_NEXT_ITEM command.
+#: Programs/cmds.auto.h:493
+msgid "go down to next item"
+msgstr "النزول إلى العنصر التالي"
+
+#. xgettext: This is the description of the FWINRT command.
+#: Programs/cmds.auto.h:219
+msgid "go forward one braille window"
+msgstr "الانتقال للأمام بمقدار نافذة برايل واحدة"
+
+#. xgettext: This is the description of the FWINRTSKIP command.
+#: Programs/cmds.auto.h:237
+msgid "go forward skipping blank braille windows"
+msgstr "الانتقال للأمام مع تخطي نوافذ برايل الفارغة"
+
+#. xgettext: This is the description of the NXNBWIN command.
+#: Programs/cmds.auto.h:975
+msgid "go forward to nearest non-blank braille window"
+msgstr "الانتقال للأمام إلى أقرب نافذة برايل غير فارغة"
+
+#. xgettext: This is the description of the HWINLT command.
+#: Programs/cmds.auto.h:192
+msgid "go left half a braille window"
+msgstr "الذهاب يسار نصف نافذة برايل"
+
+#. xgettext: This is the description of the CHRLT command.
+#: Programs/cmds.auto.h:174
+msgid "go left one character"
+msgstr "الذهاب يسارا حرف واحد"
+
+#. xgettext: This is the description of the HWINRT command.
+#: Programs/cmds.auto.h:201
+msgid "go right half a braille window"
+msgstr "الذهاب يمينًا نصف نافذة برايل"
+
+#. xgettext: This is the description of the CHRRT command.
+#: Programs/cmds.auto.h:183
+msgid "go right one character"
+msgstr "الذهاب يمينا حرف واحد"
+
+#. xgettext: This is the description of the SPEAK_FRST_CHAR command.
+#: Programs/cmds.auto.h:789
+msgid "go to and speak first non-blank character on line"
+msgstr "الانتقال ونطق أول حرف غير فارغ على السطر"
+
+#. xgettext: This is the description of the SPEAK_FRST_LINE command.
+#: Programs/cmds.auto.h:805
+msgid "go to and speak first non-blank line on screen"
+msgstr "الانتقال ونطق أول حرف غير فارغ على الشاشة"
+
+#. xgettext: This is the description of the SPEAK_LAST_CHAR command.
+#: Programs/cmds.auto.h:797
+msgid "go to and speak last non-blank character on line"
+msgstr "الانتقال ونطق آخر حرف غير فارغ على السطر"
+
+#. xgettext: This is the description of the SPEAK_LAST_LINE command.
+#: Programs/cmds.auto.h:813
+msgid "go to and speak last non-blank line on screen"
+msgstr "الانتقال ونطق آخر حرف غير فارغ على الشاشة"
+
+#. xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+#: Programs/cmds.auto.h:735
+msgid "go to and speak next character"
+msgstr "الانتقال ونطق الحرف التالي"
+
+#. xgettext: This is the description of the SPEAK_NEXT_LINE command.
+#: Programs/cmds.auto.h:781
+msgid "go to and speak next line"
+msgstr "الانتقال ونطق السطر التالي"
+
+#. xgettext: This is the description of the SPEAK_NEXT_WORD command.
+#: Programs/cmds.auto.h:758
+msgid "go to and speak next word"
+msgstr "الانتقال ونطق الكلمة التالية"
+
+#. xgettext: This is the description of the SPEAK_PREV_CHAR command.
+#: Programs/cmds.auto.h:727
+msgid "go to and speak previous character"
+msgstr "الانتقال ونطق الحرف السابق"
+
+#. xgettext: This is the description of the SPEAK_PREV_LINE command.
+#: Programs/cmds.auto.h:773
+msgid "go to and speak previous line"
+msgstr "الانتقال ونطق السطر السابق"
+
+#. xgettext: This is the description of the SPEAK_PREV_WORD command.
+#: Programs/cmds.auto.h:750
+msgid "go to and speak previous word"
+msgstr "الانتقال ونطق الكلمة السابقة"
+
+#. xgettext: This is the description of the BOT_LEFT command.
+#: Programs/cmds.auto.h:115
+msgid "go to beginning of bottom line"
+msgstr "الانتقال إلى بداية آخر سطر"
+
+#. xgettext: This is the description of the LNBEG command.
+#: Programs/cmds.auto.h:246
+msgid "go to beginning of line"
+msgstr "الانتقال إلى بداية السطر"
+
+#. xgettext: This is the description of the TOP_LEFT command.
+#: Programs/cmds.auto.h:105
+msgid "go to beginning of top line"
+msgstr "الانتقال إلى بداية أول سطر"
+
+#. xgettext: This is the description of the BOT command.
+#: Programs/cmds.auto.h:95
+msgid "go to bottom line"
+msgstr "الذهاب إلى آخر سطر"
+
+#. xgettext: This is the description of the SPKHOME command.
+#: Programs/cmds.auto.h:522
+msgid "go to current speaking position"
+msgstr "الذهاب إلى موضع النطق الحالي"
+
+#. xgettext: This is the description of the LNEND command.
+#: Programs/cmds.auto.h:255
+msgid "go to end of line"
+msgstr "الذهاب إلى آخر السطر"
+
+#. xgettext: This is the description of the MENU_PREV_LEVEL command.
+#: Programs/cmds.auto.h:664
+msgid "go to previous menu level"
+msgstr "الذهاب إلى مستوى القائمة السابق"
+
+#. xgettext: This is the description of the GOTOMARK command.
+#: Programs/cmds.auto.h:1300
+msgid "go to remembered braille window position"
+msgstr "الذهاب إلى موضع نافذة برايل الذي تم تذكره"
+
+#. xgettext: This is the description of the HOME command.
+#: Programs/cmds.auto.h:263
+msgid "go to screen cursor"
+msgstr "الذهاب إلى مؤشر الشاشة"
+
+#. xgettext: This is the description of the RETURN command.
+#: Programs/cmds.auto.h:279
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr "الانتقال إلى مؤشر الشاشة أو الرجوع بعد تتبع المؤشر"
+
+#. xgettext: This is the description of the GOTOLINE command.
+#: Programs/cmds.auto.h:1310
+msgid "go to selected line"
+msgstr "الذهاب إلى السطر المحدد"
+
+#. xgettext: This is the description of the GUI_HOME command.
+#: Programs/cmds.auto.h:1069
+msgid "go to the home screen"
+msgstr "الذهاب للشاشة الرئيسية"
+
+#. xgettext: This is the description of the TOP command.
+#: Programs/cmds.auto.h:86
+msgid "go to top line"
+msgstr "الذهاب للسطر العلوي"
+
+#. xgettext: This is the description of the LNUP command.
+#: Programs/cmds.auto.h:14
+msgid "go up one line"
+msgstr "الانتقال سطر لأعلى"
+
+#. xgettext: This is the description of the WINUP command.
+#: Programs/cmds.auto.h:32
+msgid "go up several lines"
+msgstr "الانتقال لأعلى عدة أسطر"
+
+#. xgettext: This is the description of the MENU_FIRST_ITEM command.
+#: Programs/cmds.auto.h:466
+msgid "go up to first item"
+msgstr "الصعود إلى أول عنصر"
+
+#. xgettext: This is the description of the PRPGRPH command.
+#: Programs/cmds.auto.h:124
+msgid "go up to first line of paragraph"
+msgstr "الصعود إلى السطر الأول من الفقرة"
+
+#. xgettext: This is the description of the PRDIFCHAR command.
+#: Programs/cmds.auto.h:1320
+msgid "go up to nearest line with different character"
+msgstr "الصعود إلى أقرب سطر بحرف مختلف"
+
+#. xgettext: This is the description of the PRDIFLN command.
+#: Programs/cmds.auto.h:50
+msgid "go up to nearest line with different content"
+msgstr "الصعود إلى أقرب سطر بمحتوى مختلف"
+
+#. xgettext: This is the description of the ATTRUP command.
+#: Programs/cmds.auto.h:68
+msgid "go up to nearest line with different highlighting"
+msgstr "الصعود إلى أقرب سطر مع تمييز مختلف"
+
+#. xgettext: This is the description of the PRINDENT command.
+#: Programs/cmds.auto.h:1257
+msgid "go up to nearest line with less indent than character"
+msgstr "الصعود إلى أقرب سطر بمسافة أحرف بادئة أقل"
+
+#. xgettext: This is the description of the PRPROMPT command.
+#: Programs/cmds.auto.h:142
+msgid "go up to previous command prompt"
+msgstr "الصعود إلى موجه الأوامر السابق"
+
+#. xgettext: This is the description of the MENU_PREV_ITEM command.
+#: Programs/cmds.auto.h:484
+msgid "go up to previous item"
+msgstr "الصعود إلى العنصر السابق"
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "أخضر"
+
+#: Programs/pgmprivs_linux.c:2167
+msgid "group permissions added"
+msgstr "تمت إضافة أذونات المجموعة"
+
+#: Programs/cmd_miscellaneous.c:213
+msgid "help not available"
+msgstr "المساعدة غير متوفرة"
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr "شاشة المساعدة غير قابلة للقراءة"
+
+#. xgettext: This is the description of the KEY_HOME command.
+#: Programs/cmds.auto.h:1591
+msgid "home key"
+msgstr "مفتاح البداية"
+
+#: Programs/config.c:570
+msgid "identifier,..."
+msgstr "المعرف ، ..."
+
+#: Drivers/Braille/Baum/braille.c:1148
+msgid "idle timeout"
+msgstr "مهلة الخمول"
+
+#. xgettext: This is the description of the SAY_HIGHER command.
+#: Programs/cmds.auto.h:1175
+msgid "increase speaking pitch"
+msgstr "زيادة حدة الكلام"
+
+#. xgettext: This is the description of the SAY_FASTER command.
+#: Programs/cmds.auto.h:557
+msgid "increase speaking rate"
+msgstr "زيادة سرعة الكلام"
+
+#. xgettext: This is the description of the SAY_LOUDER command.
+#: Programs/cmds.auto.h:571
+msgid "increase speaking volume"
+msgstr "زيادة مستوى صوت الكلام"
+
+#: Programs/core.c:1128
+msgid "indent"
+msgstr "المحاذاة"
+
+#. xgettext: This is the description of the PASTE_HISTORY command.
+#: Programs/cmds.auto.h:1354
+msgid "insert clipboard history entry after screen cursor"
+msgstr "إدراج محتوى الذاكرة المحفوظ بعد مؤشر الشاشة"
+
+#. xgettext: This is the description of the PASTE command.
+#: Programs/cmds.auto.h:600
+msgid "insert clipboard text after screen cursor"
+msgstr "إدخال نص الحافظة بعد مؤشر الشاشة"
+
+#. xgettext: This is the description of the HOST_PASTE command.
+#: Programs/cmds.auto.h:1047
+msgid "insert host clipboard text after screen cursor"
+msgstr "إدخال نص الحافظة المضيفة بعد مؤشر الشاشة"
+
+#. xgettext: This is the description of the KEY_INSERT command.
+#: Programs/cmds.auto.h:1607
+msgid "insert key"
+msgstr "مفتاح الإدراج"
+
+#: Programs/program.c:166
+msgid "install path not absolute"
+msgstr "مسار التثبيت ليس مطلقا"
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr "إعداد العداد غير صالح"
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr "تسلسل هروب غير صالح"
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr "إعداد إشارة غير صالح"
+
+#: Programs/config.c:2863
+msgid "invalid message hold timeout"
+msgstr "رسالة مهلة الانتظار غير صالحة"
+
+#. the operand for an option is invalid
+#: Programs/options.c:594
+msgid "invalid operand"
+msgstr "معامل غير صالح"
+
+#: Programs/brlapi_server.c:4516
+msgid "invalid thread stack size"
+msgstr "حجم كومة الخيط غير صالح"
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr "محاكاة لوحة المفاتيح تعطيل"
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr "محاكاة لوحة المفاتيح تشغيل"
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr "أزرق فاتح"
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr "ضوء سماوي"
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr "أخضر فاتح"
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr "رمادي فاتح"
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr "أرجواني فاتح"
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr "أحمر فاتح"
+
+#: Programs/cmd_speech.c:500
+msgid "line"
+msgstr "سطر"
+
+#. xgettext: This is the description of the COPY_LINE command.
+#: Programs/cmds.auto.h:1239
+msgid "linear copy to character"
+msgstr "نسخة خطية للحرف"
+
+#: Programs/config.c:750
+msgid "lvl|cat,..."
+msgstr "lvl|cat,..."
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "أرجواني"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:588
+msgid "missing operand"
+msgstr "المعامل المفقود"
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr "اسم المعلمة مفقود"
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr "مؤهل المعلمة مفقود"
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr "قيمة المعلمة مفقودة"
+
+#. xgettext: This is the description of the GUI_ITEM_FRST command.
+#: Programs/cmds.auto.h:1140
+msgid "move to the first item in the screen area"
+msgstr "الانتقال إلى العنصر الأول في منطقة الشاشة"
+
+#. xgettext: This is the description of the GUI_ITEM_LAST command.
+#: Programs/cmds.auto.h:1161
+msgid "move to the last item in the screen area"
+msgstr "الانتقال إلى العنصر الأخير في منطقة الشاشة"
+
+#. xgettext: This is the description of the GUI_ITEM_NEXT command.
+#: Programs/cmds.auto.h:1154
+msgid "move to the next item in the screen area"
+msgstr "الانتقال إلى العنصر التالي في منطقة الشاشة"
+
+#. xgettext: This is the description of the GUI_ITEM_PREV command.
+#: Programs/cmds.auto.h:1147
+msgid "move to the previous item in the screen area"
+msgstr "الانتقال إلى العنصر السابق في منطقة الشاشة"
+
+#: Programs/msgtest.c:57
+msgid "name"
+msgstr "الاسم"
+
+#: Programs/config.c:433 Programs/config.c:485 Programs/config.c:538
+#: Programs/config.c:560 Programs/config.c:638 Programs/config.c:657
+#: Programs/config.c:700
+msgid "name=value,..."
+msgstr "الاسم = القيمة ، ..."
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr "أصلي"
+
+#: Programs/config.c:1248
+msgid "no key bindings"
+msgstr "لا توجد اختصارات"
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr "لا توجد شاشة"
+
+#: Programs/config.c:116
+msgid "none"
+msgstr "لا شيء"
+
+#: Programs/config.c:1537
+msgid "not saved"
+msgstr "لم يتم الحفظ"
+
+#: Programs/pgmprivs_linux.c:2018
+msgid "not switching to an unprivileged user"
+msgstr "عدم التبديل إلى مستخدم لا يتمتع بامتيازات"
+
+#. xgettext: This is the description of the GUI_APP_ALERTS command.
+#: Programs/cmds.auto.h:1112
+msgid "open the application alerts window"
+msgstr "فتح نافذة تنبيهات التطبيق"
+
+#. xgettext: This is the description of the GUI_APP_LIST command.
+#: Programs/cmds.auto.h:1098
+msgid "open the application list window"
+msgstr "فتح نافذة قائمة التطبيق"
+
+#. xgettext: This is the description of the GUI_APP_MENU command.
+#: Programs/cmds.auto.h:1105
+msgid "open the application-specific menu"
+msgstr "فتح القائمة الخاصة بالتطبيقات"
+
+#. xgettext: This is the description of the GUI_BRL_ACTIONS command.
+#: Programs/cmds.auto.h:1061
+msgid "open the braille actions window"
+msgstr "فتح نافذة إجراءات برايل"
+
+#. xgettext: This is the description of the GUI_DEV_OPTIONS command.
+#: Programs/cmds.auto.h:1091
+msgid "open the device options window"
+msgstr "فتح نافذة خيارات الجهاز"
+
+#. xgettext: This is the description of the GUI_DEV_SETTINGS command.
+#: Programs/cmds.auto.h:1084
+msgid "open the device settings window"
+msgstr "فتح نافذة إعدادات الجهاز"
+
+#: Programs/options.c:202
+msgid "option"
+msgstr "خيار"
+
+#: Programs/pgmprivs_linux.c:2152
+msgid "ownership claimed"
+msgstr "تم الحصول على الملكية"
+
+#. xgettext: This is the description of the KEY_PAGE_DOWN command.
+#: Programs/cmds.auto.h:1583
+msgid "page-down key"
+msgstr "مفتاح صفحة لأسفل"
+
+#. xgettext: This is the description of the KEY_PAGE_UP command.
+#: Programs/cmds.auto.h:1575
+msgid "page-up key"
+msgstr "مفتاح صفحة لأعلى"
+
+#: Programs/msgtest.c:42
+msgid "path"
+msgstr "المسار"
+
+#: Programs/config.c:2836
+msgid "pid file not specified"
+msgstr "ملف pid غير محدد"
+
+#: Programs/program.c:308
+msgid "pid file open error"
+msgstr "خطأ فتح ملف pid"
+
+#: Programs/spk.c:232
+msgid "pitch"
+msgstr "حدة الصوت"
+
+#. xgettext: This is the description of the SETLEFT command.
+#: Programs/cmds.auto.h:1283
+msgid "place left end of braille window at character"
+msgstr "ضع الطرف الأيسر من نافذة برايل على الحرف"
+
+#: Drivers/Braille/Baum/braille.c:1147
+msgid "power switch"
+msgstr "مفتاح الطاقة"
+
+#: Programs/config.c:680
+msgid "quality"
+msgstr "الجودة"
+
+#: Programs/spk.c:206
+msgid "rate"
+msgstr "السرعة"
+
+#. xgettext: This is the description of the COPY_RECT command.
+#: Programs/cmds.auto.h:1231
+msgid "rectangular copy to character"
+msgstr "نسخة مستطيلة للحرف"
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "أحمر"
+
+#. xgettext: This is the description of the REFRESH command.
+#: Programs/cmds.auto.h:1005
+msgid "refresh braille display"
+msgstr "تحديث شاشة برايل"
+
+#. xgettext: This is the description of the REFRESH_LINE command.
+#: Programs/cmds.auto.h:1413
+msgid "refresh braille line"
+msgstr "تحديث سطر برايل"
+
+#: Programs/config.c:492
+msgid "regexp,..."
+msgstr "regexp ، ..."
+
+#: Programs/config.c:2081
+#, c-format
+msgid "reinitializing braille driver"
+msgstr "إعادة تشغيل سائقة برايل"
+
+#: Programs/config.c:2594
+#, c-format
+msgid "reinitializing screen driver"
+msgstr "إعادة تشغيل سائقة الشاشة"
+
+#: Programs/config.c:2394
+#, c-format
+msgid "reinitializing speech driver"
+msgstr "إعادة تشغيل سائقة النطق"
+
+#. xgettext: This is the description of the SETMARK command.
+#: Programs/cmds.auto.h:1291
+msgid "remember current braille window position"
+msgstr "تذكر الوضع الحالي لنافذة برايل"
+
+#. xgettext: This is the description of the ALERT command.
+#: Programs/cmds.auto.h:1445
+msgid "render an alert"
+msgstr "تقديم تنبيه"
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr "تكرار العد"
+
+#. xgettext: This is the description of the RESTARTBRL command.
+#: Programs/cmds.auto.h:607
+msgid "restart braille driver"
+msgstr "إعادة تشغيل سائقة البرايل"
+
+#. xgettext: This is the description of the RESTARTSPEECH command.
+#: Programs/cmds.auto.h:614
+msgid "restart speech driver"
+msgstr "إعادة تشغيل سائقة الكلام"
+
+#. xgettext: This is the description of the CLIP_RESTORE command.
+#: Programs/cmds.auto.h:864
+msgid "restore clipboard from disk"
+msgstr "استعادة الحافظة من القرص"
+
+#. xgettext: This is the description of the PREFLOAD command.
+#: Programs/cmds.auto.h:457
+msgid "restore preferences from disk"
+msgstr "استعادة التفضيلات من القرص"
+
+#. xgettext: This is the description of the GUI_AREA_ACTV command.
+#: Programs/cmds.auto.h:1119
+msgid "return to the active screen area"
+msgstr "العودة إلى منطقة الشاشة النشطة"
+
+#. xgettext: This is the description of the CLIP_SAVE command.
+#: Programs/cmds.auto.h:857
+msgid "save clipboard to disk"
+msgstr "حفظ الحافظة على القرص"
+
+#. xgettext: This is the description of the PREFSAVE command.
+#: Programs/cmds.auto.h:450
+msgid "save preferences to disk"
+msgstr "حفظ التفضيلات على القرص"
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr "مخطط + ..."
+
+#: Programs/config.c:2470
+msgid "screen driver not loadable"
+msgstr "سائقة الشاشة غير قابلة للتحميل"
+
+#: Programs/config.c:2591
+msgid "screen driver restarting"
+msgstr "يتم إعادة تشغيل سائقة الشاشة"
+
+#: Programs/cmd_miscellaneous.c:171
+msgid "screen driver stopped"
+msgstr "توقفت سائقة الشاشة"
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr "الشاشة مغلقة"
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr "الشاشة ليست في نمط النص"
+
+#. xgettext: This is the description of the PRSEARCH command.
+#: Programs/cmds.auto.h:158
+msgid "search backward for clipboard text"
+msgstr "البحث للخلف عن نص الحافظة"
+
+#. xgettext: This is the description of the NXSEARCH command.
+#: Programs/cmds.auto.h:165
+msgid "search forward for clipboard text"
+msgstr "البحث للأمام عن نص الحافظة"
+
+#: Programs/menu.c:469
+msgid "seconds"
+msgstr "ثواني"
+
+#. xgettext: This is the description of the TXTSEL_ALL command.
+#: Programs/cmds.auto.h:1026
+msgid "select all of the text"
+msgstr "حدد كل النص"
+
+#. xgettext: This is the description of the MENU_NEXT_SETTING command.
+#: Programs/cmds.auto.h:507
+msgid "select next choice"
+msgstr "حدد الخيار التالي"
+
+#. xgettext: This is the description of the MENU_PREV_SETTING command.
+#: Programs/cmds.auto.h:500
+msgid "select previous choice"
+msgstr "حدد الخيار السابق"
+
+#. xgettext: This is the description of the TUNES command.
+#: Programs/cmds.auto.h:399
+msgid "set alert tunes on/off"
+msgstr "تشغيل وتعطيل نغمات التنبيه"
+
+#. xgettext: This is the description of the ATTRBLINK command.
+#: Programs/cmds.auto.h:383
+msgid "set attribute blinking on/off"
+msgstr "تشغيل وتعطيل سمة الوميض"
+
+#. xgettext: This is the description of the ATTRVIS command.
+#: Programs/cmds.auto.h:375
+msgid "set attribute underlining on/off"
+msgstr "تشغيل وتعطيل سمة التسطير"
+
+#. xgettext: This is the description of the SET_ATTRIBUTES_TABLE command.
+#: Programs/cmds.auto.h:1370
+msgid "set attributes table"
+msgstr "ضبط سمة الجدول"
+
+#. xgettext: This is the description of the AUTOREPEAT command.
+#: Programs/cmds.auto.h:407
+msgid "set autorepeat on/off"
+msgstr "تشغيل وتعطيل التكرار التلقائي"
+
+#. xgettext: This is the description of the ASPK_CMP_WORDS command.
+#: Programs/cmds.auto.h:712
+msgid "set autospeak completed words on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للكلمات المكتملة"
+
+#. xgettext: This is the description of the ASPK_DEL_CHARS command.
+#: Programs/cmds.auto.h:696
+msgid "set autospeak deleted characters on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للحروف المحذوفة"
+
+#. xgettext: This is the description of the ASPK_INDENT command.
+#: Programs/cmds.auto.h:998
+msgid "set autospeak indent of current line on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي لمحاذاة السطر الحالي"
+
+#. xgettext: This is the description of the ASPK_INS_CHARS command.
+#: Programs/cmds.auto.h:688
+msgid "set autospeak inserted characters on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للحروف المدخلة"
+
+#. xgettext: This is the description of the AUTOSPEAK command.
+#: Programs/cmds.auto.h:415
+msgid "set autospeak on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي"
+
+#. xgettext: This is the description of the ASPK_REP_CHARS command.
+#: Programs/cmds.auto.h:704
+msgid "set autospeak replaced characters on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للحروف المُستبدلة"
+
+#. xgettext: This is the description of the ASPK_SEL_CHAR command.
+#: Programs/cmds.auto.h:680
+msgid "set autospeak selected character on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للحرف المحدد"
+
+#. xgettext: This is the description of the ASPK_SEL_LINE command.
+#: Programs/cmds.auto.h:672
+msgid "set autospeak selected line on/off"
+msgstr "تشغيل وتعطيل النطق التلقائي للسطر المحدد"
+
+#. xgettext: This is the description of the BRLKBD command.
+#: Programs/cmds.auto.h:880
+msgid "set braille keyboard enabled/disabled"
+msgstr "تشغيل أو تعطيل لوحة مفاتيح برايل"
+
+#. xgettext: This is the description of the BRLUCDOTS command.
+#: Programs/cmds.auto.h:872
+msgid "set braille typing mode dots/text"
+msgstr "ضبط نمط كتابة برايل إما نص أو نقاط"
+
+#. xgettext: This is the description of the CAPBLINK command.
+#: Programs/cmds.auto.h:391
+msgid "set capital letter blinking on/off"
+msgstr "تشغيل وتعطيل الوميض للأحرف الكبيرة"
+
+#. xgettext: This is the description of the CONTRACTED command.
+#: Programs/cmds.auto.h:1190
+msgid "set contracted/computer braille"
+msgstr "اختيار البرايل الحاسوبي أو المختصر"
+
+#. xgettext: This is the description of the SET_CONTRACTION_TABLE command.
+#: Programs/cmds.auto.h:1378
+msgid "set contraction table"
+msgstr "اختيار جدول الاختصارات"
+
+#. xgettext: This is the description of the DISPMD command.
+#: Programs/cmds.auto.h:295
+msgid "set display mode attributes/text"
+msgstr "ضبط نمط العرض إما نص أو سمات"
+
+#. xgettext: This is the description of the CSRHIDE command.
+#: Programs/cmds.auto.h:343
+msgid "set hidden screen cursor on/off"
+msgstr "تشغيل أو تعطيل إخفاء مؤشر الشاشة"
+
+#. xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+#: Programs/cmds.auto.h:1386
+msgid "set keyboard table"
+msgstr "تعيين جدول لوحة المفاتيح"
+
+#. xgettext: This is the description of the SET_LANGUAGE_PROFILE command.
+#: Programs/cmds.auto.h:1394
+msgid "set language profile"
+msgstr "تعيين ملف تعريف اللغة"
+
+#. xgettext: This is the description of the CSRBLINK command.
+#: Programs/cmds.auto.h:367
+msgid "set screen cursor blinking on/off"
+msgstr "تشغيل أو تعطيل وميض مؤشر الشاشة"
+
+#. xgettext: This is the description of the CSRSIZE command.
+#: Programs/cmds.auto.h:359
+msgid "set screen cursor style block/underline"
+msgstr "تعيين شكل مؤشر الشاشة كسطر أو ككتلة"
+
+#. xgettext: This is the description of the CSRVIS command.
+#: Programs/cmds.auto.h:335
+msgid "set screen cursor visibility on/off"
+msgstr "تشغيل أو تعطيل رؤية مؤشر الشاشة"
+
+#. xgettext: This is the description of the FREEZE command.
+#: Programs/cmds.auto.h:287
+msgid "set screen image frozen/unfrozen"
+msgstr "تجميد أو إلغاء تجميد صورة الشاشة"
+
+#. xgettext: This is the description of the COMPBRL6 command.
+#: Programs/cmds.auto.h:1198
+msgid "set six/eight dot computer braille"
+msgstr "تعيين برايل سداسي أو ثماني النقاط"
+
+#. xgettext: This is the description of the SKPBLNKWINS command.
+#: Programs/cmds.auto.h:327
+msgid "set skipping of blank braille windows on/off"
+msgstr "تشغيل أو تعطيل تجاهل نوافذ برايل الخالية"
+
+#. xgettext: This is the description of the SKPIDLNS command.
+#: Programs/cmds.auto.h:319
+msgid "set skipping of lines with identical content on/off"
+msgstr "تشغيل أو تعطيل تجاهل الأسطر ذات المحتوى المتطابق"
+
+#. xgettext: This is the description of the SLIDEWIN command.
+#: Programs/cmds.auto.h:311
+msgid "set sliding braille window on/off"
+msgstr "تشغيل أو تعطيل انزلاق نافذة برايل"
+
+#. xgettext: This is the description of the SHOW_CURR_LOCN command.
+#: Programs/cmds.auto.h:850
+msgid "set speech cursor visibility on/off"
+msgstr "تعطيل أو تشغيل رؤية مؤشر النطق"
+
+#. xgettext: This is the description of the TXTSEL_SET command.
+#: Programs/cmds.auto.h:1429
+msgid "set text selection"
+msgstr "ضبط تحديد النص"
+
+#. xgettext: This is the description of the SIXDOTS command.
+#: Programs/cmds.auto.h:303
+msgid "set text style 6-dot/8-dot"
+msgstr "تعيين شكل النص إما 6 نقاط أو 8 نقاط"
+
+#. xgettext: This is the description of the SET_TEXT_TABLE command.
+#: Programs/cmds.auto.h:1362
+msgid "set text table"
+msgstr "تعيين جدول النص"
+
+#. xgettext: This is the description of the TOUCH_NAV command.
+#: Programs/cmds.auto.h:983
+msgid "set touch navigation on/off"
+msgstr "تشغيل أو تعطيل التنقل باللمس"
+
+#. xgettext: This is the description of the CSRTRK command.
+#: Programs/cmds.auto.h:351
+msgid "set track screen cursor on/off"
+msgstr "تشغيل أو تعطيل تتبع مؤشر الشاشة"
+
+#. xgettext: This is the description of the TIME command.
+#: Programs/cmds.auto.h:656
+msgid "show current date and time"
+msgstr "إظهار التاريخ والوقت الحاليين"
+
+#. xgettext: This is the description of the GUI_TITLE command.
+#: Programs/cmds.auto.h:1054
+msgid "show the window title"
+msgstr "عرض  عنوان النافذة"
+
+#. xgettext: This is the description of the INDICATORS command.
+#: Programs/cmds.auto.h:1012
+msgid "show various device status indicators"
+msgstr "عرض مؤشرات حالة الجهاز المختلفة"
+
+#: Programs/menu_prefs.c:1057
+msgid "soundcard digital audio"
+msgstr "بطاقة الصوت الرقمية"
+
+#: Programs/menu_prefs.c:1059
+msgid "soundcard synthesizer"
+msgstr "مركب كرت الصوت"
+
+#: Programs/cmd.c:277 Programs/core.c:1051
+msgid "space"
+msgstr "مسافة"
+
+#. xgettext: This is the description of the SPEAK_CURR_CHAR command.
+#: Programs/cmds.auto.h:719
+msgid "speak current character"
+msgstr "نطق الحرف الحالي"
+
+#. xgettext: This is the description of the SAY_LINE command.
+#. xgettext: This is the description of the SPEAK_CURR_LINE command.
+#: Programs/cmds.auto.h:529 Programs/cmds.auto.h:765
+msgid "speak current line"
+msgstr "نطق السطر الحالي"
+
+#. xgettext: This is the description of the SPEAK_CURR_WORD command.
+#: Programs/cmds.auto.h:742
+msgid "speak current word"
+msgstr "نطق الكلمة الحالية"
+
+#. xgettext: This is the description of the SAY_BELOW command.
+#: Programs/cmds.auto.h:543
+msgid "speak from current line through bottom of screen"
+msgstr "النطق بدءاً من السطر الحالي وحتى أسفل الشاشة"
+
+#. xgettext: This is the description of the SAY_ALL command.
+#: Programs/cmds.auto.h:1182
+msgid "speak from top of screen through bottom of screen"
+msgstr "النطق بدءً من أعلى الشاشة إلى أسفلها"
+
+#. xgettext: This is the description of the SAY_ABOVE command.
+#: Programs/cmds.auto.h:536
+msgid "speak from top of screen through current line"
+msgstr "النطق بدءً من أعلى الشاشة وحتى السطر الحالي"
+
+#. xgettext: This is the description of the SPEAK_INDENT command.
+#: Programs/cmds.auto.h:990
+msgid "speak indent of current line"
+msgstr "نطق محاذاة السطر الحالي"
+
+#. xgettext: This is the description of the SPEAK_CURR_LOCN command.
+#: Programs/cmds.auto.h:842
+msgid "speak speech cursor location"
+msgstr "نطق موقع مؤشر الكلام"
+
+#: Programs/msgtest.c:50
+msgid "specifier"
+msgstr "محدد"
+
+#: Programs/config.c:2242
+msgid "speech driver not loadable"
+msgstr "برنامج تشغيل النطق غير قابل للتحميل"
+
+#: Programs/config.c:2391
+msgid "speech driver restarting"
+msgstr "يتم إعادة تشغيل سائقة النطق"
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr "توقفت سائقة النطق"
+
+#. xgettext: This is the description of the SPELL_CURR_WORD command.
+#: Programs/cmds.auto.h:827
+msgid "spell current word"
+msgstr "تهجئة الكلمة الحالية"
+
+#. xgettext: This is the description of the CLIP_NEW command.
+#: Programs/cmds.auto.h:1215
+msgid "start new clipboard at character"
+msgstr "بدء حافظة جديدة عند الحرف"
+
+#. xgettext: This is the description of the TXTSEL_START command.
+#: Programs/cmds.auto.h:1421
+msgid "start text selection"
+msgstr "بدء تحديد النص"
+
+#. xgettext: This is the description of the BRL_START command.
+#: Programs/cmds.auto.h:915
+msgid "start the braille driver"
+msgstr "بدء سائقة برايل"
+
+#. xgettext: This is the description of the SCR_START command.
+#: Programs/cmds.auto.h:943
+msgid "start the screen driver"
+msgstr "بدء سائقة الشاشة"
+
+#. xgettext: This is the description of the SPK_START command.
+#: Programs/cmds.auto.h:929
+msgid "start the speech driver"
+msgstr "بدء سائقة النطق"
+
+#. xgettext: This is the description of the MUTE command.
+#: Programs/cmds.auto.h:514
+msgid "stop speaking"
+msgstr "إيقاف التكلم"
+
+#. xgettext: This is the description of the BRL_STOP command.
+#: Programs/cmds.auto.h:908
+msgid "stop the braille driver"
+msgstr "إيقاف سائقة البرايل"
+
+#. xgettext: This is the description of the SCR_STOP command.
+#: Programs/cmds.auto.h:936
+msgid "stop the screen driver"
+msgstr "إيقاف سائقة الشاشة"
+
+#. xgettext: This is the description of the SPK_STOP command.
+#: Programs/cmds.auto.h:922
+msgid "stop the speech driver"
+msgstr "إيقاف سائقة النطق"
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr "معالج خطأ قديم غريب \n"
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr "string"
+
+#. xgettext: This is the description of the CONTEXT command.
+#: Programs/cmds.auto.h:1495
+msgid "switch to command context"
+msgstr "التبديل إلى سياق الأمر"
+
+#. xgettext: This is the description of the SWITCHVT command.
+#: Programs/cmds.auto.h:1247
+msgid "switch to specific virtual terminal"
+msgstr "التبديل إلى نافذة أوامر افتراضية محددة"
+
+#. xgettext: This is the description of the GUI_AREA_NEXT command.
+#: Programs/cmds.auto.h:1133
+msgid "switch to the next screen area"
+msgstr "التبديل إلى منطقة الشاشة التالية"
+
+#. xgettext: This is the description of the SWITCHVT_NEXT command.
+#: Programs/cmds.auto.h:585
+msgid "switch to the next virtual terminal"
+msgstr "التبديل إلى نافذة الأوامر الافتراضية التالية"
+
+#. xgettext: This is the description of the GUI_AREA_PREV command.
+#: Programs/cmds.auto.h:1126
+msgid "switch to the previous screen area"
+msgstr "التبديل إلى منطقة الشاشة السابقة"
+
+#. xgettext: This is the description of the SWITCHVT_PREV command.
+#: Programs/cmds.auto.h:578
+msgid "switch to the previous virtual terminal"
+msgstr "التبديل إلى نافذة الأوامر الافتراضية السابقة"
+
+#: Programs/pgmprivs_linux.c:1994
+msgid "switched to unprivileged user"
+msgstr "تحولت إلى مستخدم لا يتمتع بامتيازات"
+
+#. xgettext: This is the description of the KEY_TAB command.
+#: Programs/cmds.auto.h:1519
+msgid "tab key"
+msgstr "مفتاح التنقل"
+
+#: Programs/config.c:379 Programs/config.c:386
+msgid "text"
+msgstr "نص"
+
+#: Programs/msgtest.c:45
+msgid "the locale directory containing the translations"
+msgstr "دليل اللغة الذي يحتوي على الترجمات"
+
+#: Programs/msgtest.c:52
+msgid "the locale in which to look up a translation"
+msgstr "المكان الذي تبحث فيه عن الترجمة"
+
+#: Programs/msgtest.c:59
+msgid "the name of the domain containing the translations"
+msgstr "اسم المجال الذي يحتوي على الترجمات"
+
+#. xgettext: This is the description of the PASSDOTS command.
+#: Programs/cmds.auto.h:1463
+msgid "type braille dots"
+msgstr "اكتب نقاط برايل"
+
+#. xgettext: This is the description of the PASSCHAR command.
+#: Programs/cmds.auto.h:1454
+msgid "type unicode character"
+msgstr "اكتب حرف unicode"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr "نوع كتلة غير متوقع"
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr "cmd غير متوقع"
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr "أمر غير معالج"
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr "طلب مجهول"
+
+#: Programs/options.c:828
+msgid "unknown configuration directive"
+msgstr "توجيه تكوين غير معروف"
+
+#: Programs/config.c:791
+msgid "unknown log level or category"
+msgstr "مستوى أو فئة سجل غير معروفة"
+
+#. an unknown option has been specified
+#: Programs/options.c:575
+msgid "unknown option"
+msgstr "خيار غير معروف"
+
+#: Programs/config.c:327
+msgid "unknown screen content quality"
+msgstr "جودة محتوى الشاشة غير معروفة"
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr "إعداد غير مدعوم"
+
+#: Programs/spk.c:180
+msgid "volume"
+msgstr "مستوى الصوت"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "أبيض"
+
+#: Programs/pgmprivs_linux.c:1856
+msgid "working directory changed"
+msgstr "تم تغيير الدليل العامل"
+
+#: Programs/msgtest.c:65
+msgid "write the translations using UTF-8"
+msgstr "اكتب الترجمات باستخدام UTF-8"
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr "xbrlapi: تعذر العثور على رمز مفتاح لإعادة تعيينه لمحاكاة Keyword غير المنضمة%08X\n"
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr "xbrlapi: تعذرت الترجمة keysym %08X to keycode.\n"
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr "xbrlapi: X خطأ %d, %s على عرض %s\n"
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr "xbrlapi: تنسيق سيئ VT number\n"
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr "xbrlapi: نوع سيء لرقم VT\n"
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr "xbrlapi: لا يمكن الحصول على نافذة الجذر XFree86_VT property\n"
+
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr "xbrlapi: لا يمكن كتابة اسم النافذة %s\n"
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr "xbrlapi: didn't grab parent of %#010lx\n"
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr "xbrlapi: didn't grab window %#010lx\n"
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr "xbrlapi: didn't grab window %#010lx but got focus\n"
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr "xbrlapi: more than one item for VT number\n"
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr "xbrlapi: no XFree86_VT atom\n"
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr "xbrlapi: no items for VT number\n"
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr "xbrlapi: resource %#010lx, req %u:%u\n"
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr "xbrlapi: unhandled event type: %d\n"
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr "xbrlapi: window %#010lx changed to NULL name\n"
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "أصفر"
diff --git a/Messages/brltty.pot b/Messages/brltty.pot
new file mode 100644
index 0000000..242323c
--- /dev/null
+++ b/Messages/brltty.pot
@@ -0,0 +1,4352 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR The BRLTTY Developers
+# This file is distributed under the same license as the brltty package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 6.5\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2023-02-02 15:58-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: Programs/brltty.c:209
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr ""
+
+#. xgettext: This phrase describes the colour of a character on the screen.
+#. xgettext: %1$s is the (already translated) foreground colour.
+#. xgettext: %2$s is the (already translated) background colour.
+#: Programs/cmd_utils.c:176
+#, c-format
+msgid "%1$s on %2$s"
+msgstr ""
+
+#. xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+#. xgettext: (%u represents the number of hours)
+#: Programs/cmd_miscellaneous.c:89
+#, c-format
+msgid "%u o'clock"
+msgid_plural "%u o'clock"
+msgstr[0] ""
+msgstr[1] ""
+
+#. xgettext: This is a number (%u) of seconds (time units).
+#: Programs/cmd_miscellaneous.c:107
+#, c-format
+msgid "%u second"
+msgid_plural "%u seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: Programs/menu_prefs.c:543
+#: Programs/menu_prefs.c:544
+#: Programs/menu_prefs.c:546
+#: Programs/menu_prefs.c:547
+#: Programs/menu_prefs.c:550
+#: Programs/menu_prefs.c:551
+#: Programs/menu_prefs.c:552
+#: Programs/menu_prefs.c:554
+#: Programs/menu_prefs.c:555
+#: Programs/menu_prefs.c:561
+msgid "1 cell"
+msgstr ""
+
+#: Programs/menu_prefs.c:902
+msgid "1 second"
+msgstr ""
+
+#: Programs/menu_prefs.c:964
+msgid "10 seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:1279
+msgid "12 Hour"
+msgstr ""
+
+#: Programs/menu_prefs.c:542
+#: Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:548
+#: Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:553
+msgid "2 cells"
+msgstr ""
+
+#: Programs/menu_prefs.c:903
+msgid "2 seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:965
+msgid "20 seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:1278
+msgid "24 Hour"
+msgstr ""
+
+#: Programs/menu_prefs.c:900
+msgid "250 milliseconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:557
+#: Programs/menu_prefs.c:558
+#: Programs/menu_prefs.c:559
+#: Programs/menu_prefs.c:560
+msgid "3 cells"
+msgstr ""
+
+#: Programs/menu_prefs.c:966
+msgid "40 seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:963
+msgid "5 seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:901
+msgid "500 milliseconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:734
+msgid "6-dot"
+msgstr ""
+
+#: Programs/brltty-ttb.c:169
+msgid "8-bit character set to use."
+msgstr ""
+
+#: Programs/menu_prefs.c:733
+msgid "8-dot"
+msgstr ""
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr ""
+
+#. xgettext: This is the description of the PASSAT command.
+#: Programs/cmds.auto.h:1487
+msgid "AT (set 2) keyboard scan code"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+#: Programs/midi.c:145
+msgid "Accordion"
+msgstr ""
+
+#. Bass
+#. xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+#: Programs/midi.c:180
+msgid "Acoustic Bass"
+msgstr ""
+
+#. Piano
+#. xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+#: Programs/midi.c:80
+msgid "Acoustic Grand Piano"
+msgstr ""
+
+#. Guitar
+#. xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+#: Programs/midi.c:155
+msgid "Acoustic Guitar (nylon)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+#: Programs/midi.c:158
+msgid "Acoustic Guitar (steel)"
+msgstr ""
+
+#: Programs/menu_prefs.c:1305
+msgid "After Time"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+#: Programs/midi.c:434
+msgid "Agogo"
+msgstr ""
+
+#: Programs/menu_prefs.c:1493
+msgid "Alert"
+msgstr ""
+
+#: Programs/menu_prefs.c:1106
+msgid "Alert Dots"
+msgstr ""
+
+#: Programs/menu_prefs.c:1111
+msgid "Alert Messages"
+msgstr ""
+
+#: Programs/menu_prefs.c:1051
+msgid "Alert Tunes"
+msgstr ""
+
+#: Programs/menu_prefs.c:866
+#: Programs/menu_prefs.c:1196
+msgid "All"
+msgstr ""
+
+#: Programs/menu_prefs.c:555
+msgid "Alphabetic Cursor Coordinates"
+msgstr ""
+
+#: Programs/menu_prefs.c:554
+msgid "Alphabetic Window Coordinates"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+#: Programs/midi.c:283
+msgid "Alto Saxophone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+#: Programs/midi.c:474
+msgid "Applause"
+msgstr ""
+
+#: Programs/log.c:112
+msgid "Async Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:808
+msgid "Attributes Blink Period"
+msgstr ""
+
+#: Programs/menu_prefs.c:816
+msgid "Attributes Percent Visible"
+msgstr ""
+
+#: Programs/menu_prefs.c:1408
+msgid "Attributes Table"
+msgstr ""
+
+#: Programs/xbrlapi.c:1043
+msgid "Augment an X session by supporting input typed on the braille device, showing the title of the focused window on the braille display, and switching braille focus to it."
+msgstr ""
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr ""
+
+#: Programs/menu_prefs.c:969
+msgid "Autorelease Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:986
+msgid "Autorepeat Enabled"
+msgstr ""
+
+#: Programs/menu_prefs.c:992
+msgid "Autorepeat Interval"
+msgstr ""
+
+#: Programs/menu_prefs.c:999
+msgid "Autorepeat Panning"
+msgstr ""
+
+#: Programs/menu_prefs.c:1121
+msgid "Autospeak"
+msgstr ""
+
+#: Programs/menu_prefs.c:1118
+msgid "Autospeak Options"
+msgstr ""
+
+#: Programs/config.c:2048
+msgid "BRLTTY stopped"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+#: Programs/midi.c:421
+msgid "Bag Pipe"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+#: Programs/midi.c:409
+msgid "Banjo"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+#: Programs/midi.c:289
+msgid "Baritone Saxophone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #5.
+#: Programs/midi.c:37
+msgid "Bass"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+#: Programs/midi.c:298
+msgid "Bassoon"
+msgstr ""
+
+#: Programs/menu_prefs.c:1058
+msgid "Beeper"
+msgstr ""
+
+#: Programs/menu_prefs.c:1304
+msgid "Before Time"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+#: Programs/midi.c:465
+msgid "Bird Tweet"
+msgstr ""
+
+#: Programs/menu_prefs.c:802
+msgid "Blinking Attributes"
+msgstr ""
+
+#: Programs/menu_prefs.c:824
+msgid "Blinking Capitals"
+msgstr ""
+
+#: Programs/menu_prefs.c:775
+msgid "Blinking Screen Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:1250
+msgid "Blinking Speech Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:665
+#: Programs/menu_prefs.c:1364
+msgid "Block"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+#: Programs/midi.c:317
+msgid "Blown Bottle"
+msgstr ""
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr ""
+
+#: Programs/config.c:1817
+msgid "Braille Device"
+msgstr ""
+
+#: Programs/log.c:154
+msgid "Braille Driver Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:753
+msgid "Braille Firmness"
+msgstr ""
+
+#: Programs/log.c:76
+msgid "Braille Key Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:699
+msgid "Braille Presentation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1391
+msgid "Braille Tables"
+msgstr ""
+
+#: Programs/menu_prefs.c:934
+msgid "Braille Typing"
+msgstr ""
+
+#: Programs/menu_prefs.c:707
+msgid "Braille Variant"
+msgstr ""
+
+#: Programs/menu_prefs.c:887
+msgid "Braille Window Overlap"
+msgstr ""
+
+#: Programs/config.c:406
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/brltty-ktb.c:47
+msgid "Braille driver code."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #8.
+#: Programs/midi.c:46
+msgid "Brass"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+#: Programs/midi.c:270
+msgid "Brass Section"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+#: Programs/midi.c:459
+msgid "Breath Noise"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+#: Programs/midi.c:83
+msgid "Bright Acoustic Piano"
+msgstr ""
+
+#: Programs/xbrlapi.c:94
+msgid "BrlAPI authorization/authentication schemes"
+msgstr ""
+
+#: Programs/xbrlapi.c:87
+msgid "BrlAPI host and/or port to connect to"
+msgstr ""
+
+#: Programs/menu_prefs.c:1426
+msgid "Build Information"
+msgstr ""
+
+#: Programs/menu_prefs.c:726
+msgid "Capitalization Mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:829
+msgid "Capitals Blink Period"
+msgstr ""
+
+#: Programs/menu_prefs.c:837
+msgid "Capitals Percent Visible"
+msgstr ""
+
+#: Programs/menu_prefs.c:1516
+msgid "Category Log Level"
+msgstr ""
+
+#. Chromatic Percussion
+#. xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+#: Programs/midi.c:105
+msgid "Celesta"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+#: Programs/midi.c:211
+msgid "Cello"
+msgstr ""
+
+#: Programs/brltty-atb.c:49
+msgid "Check an attributes table."
+msgstr ""
+
+#: Programs/brltty-ttb.c:2513
+msgid "Check/edit a text (computer braille) table, or convert it from one format to another."
+msgstr ""
+
+#: Programs/brltty-ctb.c:449
+msgid "Check/validate a contraction (literary braille) table, or translate text into contracted braille."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+#: Programs/midi.c:242
+msgid "Choir Aahs"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #2.
+#: Programs/midi.c:28
+msgid "Chromatic Percussion"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+#: Programs/midi.c:139
+msgid "Church Organ"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+#: Programs/midi.c:301
+msgid "Clarinet"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+#: Programs/midi.c:101
+msgid "Clavinet"
+msgstr ""
+
+#: Programs/menu.c:891
+msgid "Close"
+msgstr ""
+
+#: Programs/menu_prefs.c:1288
+msgid "Colon"
+msgstr ""
+
+#: Programs/brltty-tune.c:158
+msgid "Compose a tune with the tune builder and play it with the tone generator."
+msgstr ""
+
+#: Programs/menu_prefs.c:703
+msgid "Computer Braille"
+msgstr ""
+
+#: Programs/menu_prefs.c:737
+msgid "Computer Braille Cell Type"
+msgstr ""
+
+#: Programs/menu_prefs.c:1450
+msgid "Configuration Directory"
+msgstr ""
+
+#: Programs/menu_prefs.c:1455
+msgid "Configuration File"
+msgstr ""
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr ""
+
+#: Programs/menu_prefs.c:1037
+msgid "Console Bell Alert"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+#: Programs/midi.c:214
+msgid "Contrabass"
+msgstr ""
+
+#: Programs/menu_prefs.c:704
+msgid "Contracted Braille"
+msgstr ""
+
+#: Programs/menu_prefs.c:1401
+msgid "Contraction Table"
+msgstr ""
+
+#: Programs/brltty-ctb.c:73
+msgid "Contraction table."
+msgstr ""
+
+#: Programs/brltty-ctb.c:87
+msgid "Contraction verification table."
+msgstr ""
+
+#: Programs/xbrlapi.c:695
+msgid "Could not get XKB major opcode\n"
+msgstr ""
+
+#: Programs/menu_prefs.c:1494
+msgid "Critical"
+msgstr ""
+
+#: Programs/menu_prefs.c:546
+msgid "Cursor Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:557
+msgid "Cursor Coordinates"
+msgstr ""
+
+#: Programs/log.c:94
+msgid "Cursor Routing"
+msgstr ""
+
+#: Programs/menu_prefs.c:547
+msgid "Cursor Row"
+msgstr ""
+
+#: Programs/log.c:88
+msgid "Cursor Tracking"
+msgstr ""
+
+#: Programs/menu_prefs.c:906
+msgid "Cursor Tracking Delay"
+msgstr ""
+
+#: Programs/menu_prefs.c:548
+#: Programs/menu_prefs.c:559
+msgid "Cursor and Window Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:560
+msgid "Cursor and Window Row"
+msgstr ""
+
+#: Programs/menu_prefs.c:1326
+msgid "Dash"
+msgstr ""
+
+#: Programs/menu_prefs.c:1319
+msgid "Date Format"
+msgstr ""
+
+#: Programs/menu_prefs.c:1308
+msgid "Date Position"
+msgstr ""
+
+#: Programs/menu_prefs.c:1331
+msgid "Date Separator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1316
+msgid "Day Month Year"
+msgstr ""
+
+#: Programs/menu_prefs.c:1499
+msgid "Debug"
+msgstr ""
+
+#: Programs/config.c:427
+msgid "Device for accessing braille display."
+msgstr ""
+
+#: Programs/config.c:572
+msgid "Disable the application programming interface."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+#: Programs/midi.c:173
+msgid "Distortion Guitar"
+msgstr ""
+
+#: Programs/config.c:502
+msgid "Do not autospeak when braille is not being used."
+msgstr ""
+
+#: Programs/xbrlapi.c:107
+msgid "Do not write any text to the braille device"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:70
+msgid "Don't fall back to the Unicode base character."
+msgstr ""
+
+#: Programs/config.c:625
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr ""
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr ""
+
+#: Programs/menu_prefs.c:1289
+#: Programs/menu_prefs.c:1328
+msgid "Dot"
+msgstr ""
+
+#: Programs/menu_prefs.c:944
+msgid "Dots via Unicode Braille"
+msgstr ""
+
+#. Organ
+#. xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+#: Programs/midi.c:130
+msgid "Drawbar Organ"
+msgstr ""
+
+#: Programs/menu_prefs.c:1475
+msgid "Drivers Directory"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+#: Programs/midi.c:126
+msgid "Dulcimer"
+msgstr ""
+
+#: Programs/menu_prefs.c:881
+msgid "Eager Sliding Braille Window"
+msgstr ""
+
+#: Programs/brltty-hid.c:201
+msgid "Echo (in hexadecimal) input received from the device."
+msgstr ""
+
+#: Programs/brltty-ttb.c:148
+msgid "Edit table."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+#: Programs/midi.c:183
+msgid "Electric Bass (finger)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+#: Programs/midi.c:186
+msgid "Electric Bass (pick)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+#: Programs/midi.c:86
+msgid "Electric Grand Piano"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+#: Programs/midi.c:164
+msgid "Electric Guitar (clean)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+#: Programs/midi.c:161
+msgid "Electric Guitar (jazz)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+#: Programs/midi.c:167
+msgid "Electric Guitar (muted)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+#: Programs/midi.c:92
+msgid "Electric Piano 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+#: Programs/midi.c:95
+msgid "Electric Piano 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1492
+msgid "Emergency"
+msgstr ""
+
+#: Programs/menu_prefs.c:541
+msgid "End"
+msgstr ""
+
+#: Programs/menu_prefs.c:867
+msgid "End of Line"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+#: Programs/midi.c:295
+msgid "English Horn"
+msgstr ""
+
+#: Programs/menu_prefs.c:1231
+msgid "Enqueue"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #7.
+#: Programs/midi.c:43
+msgid "Ensemble"
+msgstr ""
+
+#: Programs/menu_prefs.c:1495
+msgid "Error"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #14.
+#: Programs/midi.c:68
+msgid "Ethnic Instruments"
+msgstr ""
+
+#: Programs/menu_prefs.c:1034
+msgid "Event Alerts"
+msgstr ""
+
+#: Programs/menu_prefs.c:714
+msgid "Expand Current Word"
+msgstr ""
+
+#: Programs/config.c:564
+msgid "Explicit preference settings."
+msgstr ""
+
+#: Programs/menu_prefs.c:1061
+msgid "FM"
+msgstr ""
+
+#: Programs/menu_prefs.c:1099
+msgid "FM Volume"
+msgstr ""
+
+#. Synth FM
+#. xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+#: Programs/midi.c:380
+msgid "FX 1 (rain)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+#: Programs/midi.c:383
+msgid "FX 2 (soundtrack)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+#: Programs/midi.c:386
+msgid "FX 3 (crystal)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+#: Programs/midi.c:389
+msgid "FX 4 (atmosphere)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+#: Programs/midi.c:392
+msgid "FX 5 (brightness)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+#: Programs/midi.c:395
+msgid "FX 6 (goblins)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+#: Programs/midi.c:398
+msgid "FX 7 (echoes)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+#. xgettext: (sci-fi is a common short form for science fiction)
+#: Programs/midi.c:402
+msgid "FX 8 (sci-fi)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+#: Programs/midi.c:424
+msgid "Fiddle"
+msgstr ""
+
+#: Programs/brltty-hid.c:76
+msgid "Filter for a Bluetooth device."
+msgstr ""
+
+#: Programs/brltty-hid.c:70
+msgid "Filter for a USB device (the default if not ambiguous)."
+msgstr ""
+
+#: Programs/brltty-hid.c:1065
+msgid "Find HID devices, list report descriptors, read/write reports/features, or monitor input from a HID device."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+#: Programs/midi.c:308
+msgid "Flute"
+msgstr ""
+
+#: Programs/brltty-ctb.c:65
+msgid "Force immediate output."
+msgstr ""
+
+#: Programs/brltty-ttb.c:155
+msgid "Format of input file."
+msgstr ""
+
+#: Programs/brltty-ttb.c:162
+msgid "Format of output file."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+#: Programs/midi.c:267
+msgid "French Horn"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+#: Programs/midi.c:189
+msgid "Fretless Bass"
+msgstr ""
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr ""
+
+#: Programs/menu_prefs.c:556
+msgid "Generic"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+#: Programs/midi.c:108
+msgid "Glockenspiel"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #4.
+#: Programs/midi.c:34
+msgid "Guitar"
+msgstr ""
+
+#. Sound Effects
+#. xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+#: Programs/midi.c:456
+msgid "Guitar Fret Noise"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+#: Programs/midi.c:176
+msgid "Guitar Harmonics"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+#: Programs/midi.c:477
+msgid "Gunshot"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+#: Programs/midi.c:148
+msgid "Harmonica"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+#: Programs/midi.c:98
+msgid "Harpsichord"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+#: Programs/midi.c:471
+msgid "Helicopter"
+msgstr ""
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr ""
+
+#: Programs/menu_prefs.c:668
+msgid "Hide"
+msgstr ""
+
+#: Programs/menu_prefs.c:749
+#: Programs/menu_prefs.c:1015
+msgid "High"
+msgstr ""
+
+#: Programs/menu_prefs.c:923
+msgid "Highlight Braille Window Location"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+#: Programs/midi.c:89
+msgid "Honkytonk Piano"
+msgstr ""
+
+#: Programs/log.c:148
+msgid "Human Interface I/O"
+msgstr ""
+
+#: Programs/menu_prefs.c:1230
+msgid "Immediate"
+msgstr ""
+
+#: Programs/xbrlapi.c:691
+msgid "Incompatible XKB library\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:693
+msgid "Incompatible XKB server support\n"
+msgstr ""
+
+#: Programs/menu_prefs.c:1498
+msgid "Information"
+msgstr ""
+
+#: Programs/menu_prefs.c:958
+msgid "Input Options"
+msgstr ""
+
+#: Programs/log.c:64
+msgid "Input Packets"
+msgstr ""
+
+#: Programs/config.c:754
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr ""
+
+#: Programs/menu_prefs.c:1502
+msgid "Internal Parameters"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+#: Programs/midi.c:418
+msgid "Kalimba"
+msgstr ""
+
+#: Programs/config.c:1743
+msgid "Key Bindings"
+msgstr ""
+
+#: Programs/config.c:1246
+msgid "Key Help"
+msgstr ""
+
+#: Programs/config.c:1748
+#: Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr ""
+
+#: Programs/menu_prefs.c:937
+msgid "Keyboard Enabled"
+msgstr ""
+
+#: Programs/log.c:82
+msgid "Keyboard Key Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:1044
+msgid "Keyboard LED Alerts"
+msgstr ""
+
+#: Programs/menu_prefs.c:1026
+msgid "Keyboard Table"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+#: Programs/midi.c:415
+msgid "Koto"
+msgstr ""
+
+#: Programs/config.c:3132
+msgid "Language"
+msgstr ""
+
+#. Synth Lead
+#. xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+#: Programs/midi.c:330
+msgid "Lead 1 (square)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+#: Programs/midi.c:333
+msgid "Lead 2 (sawtooth)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+#: Programs/midi.c:336
+msgid "Lead 3 (calliope)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+#: Programs/midi.c:339
+msgid "Lead 4 (chiff)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+#: Programs/midi.c:342
+msgid "Lead 5 (charang)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+#: Programs/midi.c:345
+msgid "Lead 6 (voice)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+#: Programs/midi.c:348
+msgid "Lead 7 (fifths)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+#: Programs/midi.c:351
+msgid "Lead 8 (bass + lead)"
+msgstr ""
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:1343
+msgid "Left"
+msgstr ""
+
+#: Programs/brltty-hid.c:167
+msgid "List each report's identifier and sizes."
+msgstr ""
+
+#: Programs/brltty-ktb.c:59
+msgid "List key names."
+msgstr ""
+
+#: Programs/brltty-ktb.c:65
+msgid "List key table in help screen format."
+msgstr ""
+
+#: Programs/brltty-ktb.c:71
+msgid "List key table in reStructuredText format."
+msgstr ""
+
+#: Programs/brltty-hid.c:161
+msgid "List the HID report descriptor's items."
+msgstr ""
+
+#: Programs/brltty-cldr.c:237
+msgid "List the characters defined within a CLDR (Common Locale Data Repository Project) annotations file."
+msgstr ""
+
+#: Programs/brltty-lsinc.c:95
+msgid "List the paths to a data file and those which it recursively includes."
+msgstr ""
+
+#: Programs/menu_prefs.c:1485
+msgid "Locale Directory"
+msgstr ""
+
+#: Programs/menu_prefs.c:1521
+msgid "Log Categories"
+msgstr ""
+
+#: Programs/menu_prefs.c:1582
+msgid "Log Messages"
+msgstr ""
+
+#: Programs/config.c:380
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr ""
+
+#: Programs/config.c:612
+msgid "Log to standard error rather than to the system log."
+msgstr ""
+
+#: Programs/config.c:597
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)."
+msgstr ""
+
+#: Programs/menu_prefs.c:980
+msgid "Long Press Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:747
+#: Programs/menu_prefs.c:1013
+msgid "Low"
+msgstr ""
+
+#: Programs/menu_prefs.c:666
+msgid "Lower Left Dot"
+msgstr ""
+
+#: Programs/menu_prefs.c:667
+msgid "Lower Right Dot"
+msgstr ""
+
+#: Programs/menu_prefs.c:1060
+msgid "MIDI"
+msgstr ""
+
+#: Programs/config.c:682
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr ""
+
+#: Programs/menu_prefs.c:1090
+msgid "MIDI Instrument"
+msgstr ""
+
+#: Programs/menu_prefs.c:1080
+msgid "MIDI Volume"
+msgstr ""
+
+#: Programs/menu_prefs.c:1445
+msgid "Mailing List"
+msgstr ""
+
+#: Programs/brltty-clip.c:154
+msgid "Manage brltty's clipboard from the command line."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+#: Programs/midi.c:117
+msgid "Marimba"
+msgstr ""
+
+#: Programs/brltty-hid.c:118
+msgid "Match the full device address (Bluetooth only - all six two-digit, hexadecimal octets separated by a colon [:])."
+msgstr ""
+
+#: Programs/brltty-hid.c:90
+msgid "Match the product identifier (four hexadecimal digits)."
+msgstr ""
+
+#: Programs/brltty-hid.c:125
+msgid "Match the start of the device name (Bluetooth only)."
+msgstr ""
+
+#: Programs/brltty-hid.c:97
+msgid "Match the start of the manufacturer name (USB only)."
+msgstr ""
+
+#: Programs/brltty-hid.c:104
+msgid "Match the start of the product description (USB only)."
+msgstr ""
+
+#: Programs/brltty-hid.c:111
+msgid "Match the start of the serial number (USB only)."
+msgstr ""
+
+#: Programs/brltty-hid.c:83
+msgid "Match the vendor identifier (four hexadecimal digits)."
+msgstr ""
+
+#: Programs/menu_prefs.c:750
+#: Programs/menu_prefs.c:1016
+msgid "Maximum"
+msgstr ""
+
+#: Programs/brltty-ctb.c:53
+msgid "Maximum length of an output line."
+msgstr ""
+
+#: Programs/menu_prefs.c:748
+#: Programs/menu_prefs.c:1014
+msgid "Medium"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+#: Programs/midi.c:446
+msgid "Melodic Tom"
+msgstr ""
+
+#: Programs/menu_prefs.c:680
+msgid "Menu Options"
+msgstr ""
+
+#: Programs/config.c:642
+msgid "Message hold timeout (in 10ms units)."
+msgstr ""
+
+#: Programs/menu_prefs.c:746
+#: Programs/menu_prefs.c:1012
+msgid "Minimum"
+msgstr ""
+
+#: Programs/config.c:509
+#, c-format
+msgid "Minimum screen content quality to autospeak (one of {%s})."
+msgstr ""
+
+#: Programs/menu_prefs.c:1315
+msgid "Month Day Year"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+#: Programs/midi.c:111
+msgid "Music Box"
+msgstr ""
+
+#: Programs/menu_prefs.c:1060
+msgid "Musical Instrument Digital Interface"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+#: Programs/midi.c:264
+msgid "Muted Trumpet"
+msgstr ""
+
+#: Programs/config.c:467
+msgid "Name of or path to attributes table."
+msgstr ""
+
+#: Programs/config.c:459
+msgid "Name of or path to contraction table."
+msgstr ""
+
+#: Programs/config.c:556
+msgid "Name of or path to default preferences file."
+msgstr ""
+
+#: Programs/config.c:539
+msgid "Name of or path to keyboard table."
+msgstr ""
+
+#: Programs/config.c:495
+msgid "Name of or path to speech input object."
+msgstr ""
+
+#: Programs/config.c:449
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr ""
+
+#: Programs/menu_prefs.c:846
+msgid "Navigation Options"
+msgstr ""
+
+#: Programs/menu.c:523
+msgid "No"
+msgstr ""
+
+#: Programs/menu_prefs.c:721
+msgid "No Capitalization"
+msgstr ""
+
+#: .c:6
+#: Programs/menu_prefs.c:899
+#: Programs/menu_prefs.c:1194
+#: Programs/menu_prefs.c:1207
+#: Programs/menu_prefs.c:1220
+#: Programs/menu_prefs.c:1303
+#: Programs/menu_prefs.c:1342
+#: Programs/menu_prefs.c:1362
+msgid "None"
+msgstr ""
+
+#: Programs/menu_prefs.c:1497
+msgid "Notice"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+#: Programs/midi.c:292
+msgid "Oboe"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+#: Programs/midi.c:326
+msgid "Ocarina"
+msgstr ""
+
+#: Programs/menu_prefs.c:962
+msgid "Off"
+msgstr ""
+
+#: Programs/config.c:1836
+msgid "Old Preferences File"
+msgstr ""
+
+#: Programs/menu_prefs.c:975
+msgid "On First Release"
+msgstr ""
+
+#: Programs/cmdline.c:281
+msgid "Options"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+#: Programs/midi.c:251
+msgid "Orchestra Hit"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+#: Programs/midi.c:223
+msgid "Orchestral Harp"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #3.
+#: Programs/midi.c:31
+msgid "Organ"
+msgstr ""
+
+#: Programs/log.c:70
+msgid "Output Packets"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+#: Programs/midi.c:170
+msgid "Overdriven Guitar"
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1548
+msgid "PC mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:1059
+msgid "PCM"
+msgstr ""
+
+#: Programs/config.c:672
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr ""
+
+#: Programs/menu_prefs.c:1072
+msgid "PCM Volume"
+msgstr ""
+
+#. xgettext: This is the description of the PASSPS2 command.
+#: Programs/cmds.auto.h:1503
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr ""
+
+#: Programs/menu_prefs.c:1435
+msgid "Package Revision"
+msgstr ""
+
+#: Programs/menu_prefs.c:1430
+msgid "Package Version"
+msgstr ""
+
+#. Synth Pad
+#. xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+#: Programs/midi.c:355
+msgid "Pad 1 (new age)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+#: Programs/midi.c:358
+msgid "Pad 2 (warm)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+#: Programs/midi.c:361
+msgid "Pad 3 (polysynth)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+#: Programs/midi.c:364
+msgid "Pad 4 (choir)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+#: Programs/midi.c:367
+msgid "Pad 5 (bowed)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+#: Programs/midi.c:370
+msgid "Pad 6 (metallic)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+#: Programs/midi.c:373
+msgid "Pad 7 (halo)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+#: Programs/midi.c:376
+msgid "Pad 8 (sweep)"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+#: Programs/midi.c:314
+msgid "Pan Flute"
+msgstr ""
+
+#: Programs/config.c:581
+msgid "Parameters for the application programming interface."
+msgstr ""
+
+#: Programs/config.c:417
+msgid "Parameters for the braille driver."
+msgstr ""
+
+#: Programs/config.c:634
+msgid "Parameters for the privilege establishment stage."
+msgstr ""
+
+#: Programs/config.c:530
+msgid "Parameters for the screen driver."
+msgstr ""
+
+#: Programs/config.c:487
+msgid "Parameters for the speech driver."
+msgstr ""
+
+#: Programs/config.c:396
+msgid "Path to default settings file."
+msgstr ""
+
+#: Programs/config.c:703
+msgid "Path to directory containing drivers."
+msgstr ""
+
+#: Programs/brltest.c:65
+#: Programs/brltty-atb.c:35
+#: Programs/brltty-ctb.c:96
+#: Programs/brltty-ktb.c:80
+#: Programs/config.c:693
+msgid "Path to directory containing tables."
+msgstr ""
+
+#: Programs/brltty-ttb.c:186
+msgid "Path to directory containing text tables."
+msgstr ""
+
+#: Programs/brltty-ktb.c:89
+msgid "Path to directory for loading drivers."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:79
+msgid "Path to directory for text tables."
+msgstr ""
+
+#: Programs/brltest.c:83
+#: Programs/config.c:723
+msgid "Path to directory which can be written to."
+msgstr ""
+
+#: Programs/config.c:713
+msgid "Path to directory which contains files that can be updated."
+msgstr ""
+
+#: Programs/config.c:732
+msgid "Path to directory which contains message localizations."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:50
+msgid "Path to input text table."
+msgstr ""
+
+#: Programs/config.c:606
+msgid "Path to log file."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:58
+msgid "Path to output text table."
+msgstr ""
+
+#: Programs/config.c:741
+msgid "Path to process identifier file."
+msgstr ""
+
+#: Programs/config.c:663
+msgid "Patterns that match command prompts."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #15.
+#: Programs/midi.c:71
+msgid "Percussive Instruments"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+#: Programs/midi.c:133
+msgid "Percussive Organ"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #1.
+#: Programs/midi.c:25
+msgid "Piano"
+msgstr ""
+
+#. Pipe
+#. xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+#: Programs/midi.c:305
+msgid "Piccolo"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #10.
+#: Programs/midi.c:52
+msgid "Pipe"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+#: Programs/midi.c:220
+msgid "Pizzicato Strings"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1159
+msgid "Powerdown"
+msgstr ""
+
+#: Programs/menu_prefs.c:1465
+msgid "Preferences File"
+msgstr ""
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr ""
+
+#: Programs/menu_prefs.c:1416
+msgid "Profiles"
+msgstr ""
+
+#: Programs/config.c:547
+msgid "Properties of eligible keyboards."
+msgstr ""
+
+#: Programs/menu_prefs.c:952
+msgid "Quick Space"
+msgstr ""
+
+#: .c:11
+#: Programs/menu_prefs.c:1211
+msgid "Raise Pitch"
+msgstr ""
+
+#: Programs/brltty-hid.c:181
+msgid "Read (get) a feature report (two hexadecimal digits)."
+msgstr ""
+
+#: Programs/brltty-hid.c:174
+msgid "Read (get) an input report (two hexadecimal digits)."
+msgstr ""
+
+#: Programs/config.c:386
+msgid "Recognize environment variables."
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+#: Programs/midi.c:311
+msgid "Recorder"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #9.
+#: Programs/midi.c:49
+msgid "Reed"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+#: Programs/midi.c:142
+msgid "Reed Organ"
+msgstr ""
+
+#: Programs/brltty-ctb.c:59
+msgid "Reformat input."
+msgstr ""
+
+#: Programs/config.c:439
+msgid "Release braille device when screen or window is unreadable."
+msgstr ""
+
+#: Programs/xbrlapi.c:119
+msgid "Remain a foreground process"
+msgstr ""
+
+#: Programs/config.c:618
+msgid "Remain a foreground process."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:64
+msgid "Remove dots seven and eight."
+msgstr ""
+
+#: Programs/config.c:761
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr ""
+
+#: Programs/brltty-ktb.c:53
+msgid "Report problems with the key table."
+msgstr ""
+
+#: Programs/brltty-ttb.c:176
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr ""
+
+#: Programs/menu_prefs.c:868
+msgid "Rest of Line"
+msgstr ""
+
+#: Programs/menu_prefs.c:1564
+msgid "Restart Braille Driver"
+msgstr ""
+
+#: Programs/menu_prefs.c:1576
+msgid "Restart Screen Driver"
+msgstr ""
+
+#: Programs/menu_prefs.c:1570
+msgid "Restart Speech Driver"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+#: Programs/midi.c:452
+msgid "Reverse Cymbal"
+msgstr ""
+
+#: Programs/menu_prefs.c:1344
+msgid "Right"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+#: Programs/midi.c:136
+msgid "Rock Organ"
+msgstr ""
+
+#: Programs/brltty-pty.c:389
+msgid "Run a shell or terminal manager within a pty (virtual terminal) and export its screen via a shared memory segment so that brltty can read it via its Terminal Emulator screen driver."
+msgstr ""
+
+#: Programs/menu_prefs.c:675
+msgid "Save on Exit"
+msgstr ""
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: .c:9 .c:10
+#: Programs/menu_prefs.c:1210
+msgid "Say Cap"
+msgstr ""
+
+#: Programs/menu_prefs.c:1234
+msgid "Say Line Mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:1221
+msgid "Say Space"
+msgstr ""
+
+#: Programs/menu_prefs.c:781
+msgid "Screen Cursor Blink Period"
+msgstr ""
+
+#: Programs/menu_prefs.c:789
+msgid "Screen Cursor Percent Visible"
+msgstr ""
+
+#: Programs/menu_prefs.c:769
+msgid "Screen Cursor Style"
+msgstr ""
+
+#: Programs/log.c:166
+msgid "Screen Driver Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:550
+msgid "Screen Number"
+msgstr ""
+
+#: Programs/config.c:520
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/config.c:857
+msgid "Screen reader for those who use a braille device."
+msgstr ""
+
+#: Programs/menu_prefs.c:893
+msgid "Scroll-aware Cursor Navigation"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+#: Programs/midi.c:462
+msgid "Seashore"
+msgstr ""
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr ""
+
+#: Programs/log.c:118
+msgid "Server Events"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+#: Programs/midi.c:320
+msgid "Shakuhachi"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+#: Programs/midi.c:412
+msgid "Shamisen"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+#: Programs/midi.c:427
+msgid "Shanai"
+msgstr ""
+
+#: Programs/menu_prefs.c:688
+msgid "Show Advanced Submenus"
+msgstr ""
+
+#: Programs/menu_prefs.c:693
+msgid "Show All Items"
+msgstr ""
+
+#: Programs/menu_prefs.c:797
+msgid "Show Attributes"
+msgstr ""
+
+#: Programs/menu_prefs.c:764
+msgid "Show Screen Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:1297
+msgid "Show Seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:1239
+msgid "Show Speech Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:683
+msgid "Show Submenu Sizes"
+msgstr ""
+
+#: Programs/brltty-hid.c:137
+msgid "Show the device address (USB serial number, Bluetooth device address, etc)."
+msgstr ""
+
+#: Programs/brltty-hid.c:143
+msgid "Show the device name (USB manufacturer and/or product strings, Bluetooth device name, etc)."
+msgstr ""
+
+#: Programs/brltty-hid.c:155
+msgid "Show the host device (usually its absolute path)."
+msgstr ""
+
+#: Programs/brltty-hid.c:149
+msgid "Show the host path (USB topology, Bluetooth host controller address, etc)."
+msgstr ""
+
+#: Programs/brltty-hid.c:131
+msgid "Show the vendor and product identifiers."
+msgstr ""
+
+#: Headers/cmdline_types.h:72
+msgid "Show this usage summary, and then exit."
+msgstr ""
+
+#. Ethnic Instruments
+#. xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+#: Programs/midi.c:406
+msgid "Sitar"
+msgstr ""
+
+#: Programs/menu_prefs.c:860
+msgid "Skip Blank Braille Windows"
+msgstr ""
+
+#: Programs/menu_prefs.c:854
+msgid "Skip Identical Lines"
+msgstr ""
+
+#: Programs/menu_prefs.c:871
+msgid "Skip Which Blank Braille Windows"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+#: Programs/midi.c:192
+msgid "Slap Bass 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+#: Programs/midi.c:195
+msgid "Slap Bass 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1327
+msgid "Slash"
+msgstr ""
+
+#: Programs/menu_prefs.c:876
+msgid "Sliding Braille Window"
+msgstr ""
+
+#: Programs/menu_prefs.c:1195
+msgid "Some"
+msgstr ""
+
+#. Reed
+#. xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+#: Programs/midi.c:280
+msgid "Soprano Saxophone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #16.
+#: Programs/midi.c:74
+msgid "Sound Effects"
+msgstr ""
+
+#: Programs/menu_prefs.c:561
+#: Programs/menu_prefs.c:1363
+msgid "Space"
+msgstr ""
+
+#: Programs/menu_prefs.c:1156
+msgid "Speak Completed Words"
+msgstr ""
+
+#: Programs/menu_prefs.c:1144
+msgid "Speak Deleted Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1138
+msgid "Speak Inserted Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1162
+msgid "Speak Line Indent"
+msgstr ""
+
+#: Programs/menu_prefs.c:1150
+msgid "Speak Replaced Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1132
+msgid "Speak Selected Character"
+msgstr ""
+
+#: Programs/menu_prefs.c:1126
+msgid "Speak Selected Line"
+msgstr ""
+
+#: Programs/menu_prefs.c:1256
+msgid "Speech Cursor Blink Period"
+msgstr ""
+
+#: Programs/menu_prefs.c:1264
+msgid "Speech Cursor Percent Visible"
+msgstr ""
+
+#: Programs/menu_prefs.c:1244
+msgid "Speech Cursor Style"
+msgstr ""
+
+#: Programs/log.c:160
+msgid "Speech Driver Events"
+msgstr ""
+
+#: Programs/log.c:106
+msgid "Speech Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:1169
+msgid "Speech Options"
+msgstr ""
+
+#: Programs/menu_prefs.c:1186
+msgid "Speech Pitch"
+msgstr ""
+
+#: Programs/menu_prefs.c:1199
+msgid "Speech Punctuation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1179
+msgid "Speech Rate"
+msgstr ""
+
+#: Programs/menu_prefs.c:1214
+msgid "Speech Uppercase Indicator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1172
+msgid "Speech Volume"
+msgstr ""
+
+#: Programs/menu_prefs.c:1224
+msgid "Speech Whitespace Indicator"
+msgstr ""
+
+#: Programs/config.c:477
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/menu_prefs.c:1511
+msgid "Standard Error Log Level"
+msgstr ""
+
+#: Programs/menu_prefs.c:928
+msgid "Start Selection with Routing Key"
+msgstr ""
+
+#: Programs/menu_prefs.c:551
+msgid "State Dots"
+msgstr ""
+
+#: Programs/menu_prefs.c:552
+msgid "State Letter"
+msgstr ""
+
+#: Programs/menu_prefs.c:1338
+msgid "Status Cells"
+msgstr ""
+
+#: Programs/menu_prefs.c:1354
+msgid "Status Count"
+msgstr ""
+
+#: Programs/menu_prefs.c:565
+msgid "Status Field"
+msgstr ""
+
+#: Programs/menu_prefs.c:1347
+msgid "Status Position"
+msgstr ""
+
+#: Programs/menu_prefs.c:1369
+msgid "Status Separator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1365
+msgid "Status Side"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+#: Programs/midi.c:437
+msgid "Steel Drums"
+msgstr ""
+
+#: Programs/config.c:747
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr ""
+
+#. Ensemble
+#. xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+#: Programs/midi.c:230
+msgid "String Ensemble 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+#: Programs/midi.c:233
+msgid "String Ensemble 2"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #6.
+#: Programs/midi.c:40
+msgid "Strings"
+msgstr ""
+
+#: Programs/menu_prefs.c:723
+msgid "Superimpose Dot 7"
+msgstr ""
+
+#: Programs/config.c:589
+msgid "Suppress start-up messages."
+msgstr ""
+
+#: Programs/cmdline.c:243
+msgid "Syntax"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+#: Programs/midi.c:198
+msgid "Synth Bass 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+#: Programs/midi.c:201
+msgid "Synth Bass 2"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+#: Programs/midi.c:449
+msgid "Synth Drum"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #13.
+#. xgettext: (synth is a common short form for synthesizer)
+#. xgettext: (FM is the acronym for Frequency Modulation)
+#: Programs/midi.c:65
+msgid "Synth FM"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #11.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:56
+msgid "Synth Lead"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #12.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:60
+msgid "Synth Pad"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+#: Programs/midi.c:248
+msgid "Synth Voice"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+#: Programs/midi.c:273
+msgid "SynthBrass 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+#: Programs/midi.c:276
+msgid "SynthBrass 2"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+#: Programs/midi.c:236
+msgid "SynthStrings 1"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+#: Programs/midi.c:239
+msgid "SynthStrings 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1506
+msgid "System Log Level"
+msgstr ""
+
+#: Programs/menu_prefs.c:1480
+msgid "Tables Directory"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+#: Programs/midi.c:443
+msgid "Taiko Drum"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+#: Programs/midi.c:151
+msgid "Tango Accordion"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+#: Programs/midi.c:468
+msgid "Telephone Ring"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+#: Programs/midi.c:286
+msgid "Tenor Saxophone"
+msgstr ""
+
+#: Programs/apitest.c:482
+msgid "Test BrlAPI functions."
+msgstr ""
+
+#: Programs/brltest.c:100
+msgid "Test a braille driver."
+msgstr ""
+
+#: Programs/scrtest.c:137
+msgid "Test a screen driver."
+msgstr ""
+
+#: Programs/spktest.c:115
+msgid "Test a speech driver."
+msgstr ""
+
+#: Programs/msgtest.c:251
+msgid "Test message localization using the message catalog reader."
+msgstr ""
+
+#: Programs/crctest.c:121
+msgid "Test supported CRC (Cyclic Redundancy Check) checksum algorithms."
+msgstr ""
+
+#: Programs/menu_prefs.c:761
+msgid "Text Indicators"
+msgstr ""
+
+#: Programs/menu_prefs.c:1366
+msgid "Text Side"
+msgstr ""
+
+#: Programs/menu_prefs.c:1394
+msgid "Text Table"
+msgstr ""
+
+#: Programs/brltty-ctb.c:80
+msgid "Text table."
+msgstr ""
+
+#: Programs/brltty-cldr.c:42
+msgid "The format of each output line."
+msgstr ""
+
+#: Programs/brltty-hid.c:208
+msgid "The input timeout (in seconds)."
+msgstr ""
+
+#: Programs/config.c:649
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr ""
+
+#: Programs/config.c:656
+msgid "The text to be shown when the braille driver stops."
+msgstr ""
+
+#: Programs/menu_prefs.c:553
+msgid "Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:1282
+msgid "Time Format"
+msgstr ""
+
+#: Programs/menu_prefs.c:1274
+msgid "Time Presentation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1292
+msgid "Time Separator"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+#: Programs/midi.c:226
+msgid "Timpani"
+msgstr ""
+
+#. Percussive Instruments
+#. xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+#: Programs/midi.c:431
+msgid "Tinkle Bell"
+msgstr ""
+
+#: Programs/menu_prefs.c:1560
+msgid "Tools"
+msgstr ""
+
+#: Programs/menu_prefs.c:1005
+msgid "Touch Navigation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1019
+msgid "Touch Sensitivity"
+msgstr ""
+
+#: Programs/menu_prefs.c:917
+msgid "Track Screen Pointer"
+msgstr ""
+
+#: Programs/menu_prefs.c:911
+msgid "Track Screen Scroll"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:239
+msgid "Translate one binary braille representation to another."
+msgstr ""
+
+#: Programs/brltty-morse.c:136
+msgid "Translate text into Morse Code tones."
+msgstr ""
+
+#: Programs/menu_prefs.c:943
+msgid "Translated via Text Table"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+#: Programs/midi.c:217
+msgid "Tremolo Strings"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+#: Programs/midi.c:258
+msgid "Trombone"
+msgstr ""
+
+#. Brass
+#. xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+#: Programs/midi.c:255
+msgid "Trumpet"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+#: Programs/midi.c:261
+msgid "Tuba"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+#: Programs/midi.c:123
+msgid "Tubular Bells"
+msgstr ""
+
+#: Programs/menu_prefs.c:1064
+msgid "Tune Device"
+msgstr ""
+
+#: Programs/menu_prefs.c:947
+msgid "Typing Mode"
+msgstr ""
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr ""
+
+#: Programs/menu_prefs.c:664
+msgid "Underline"
+msgstr ""
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr ""
+
+#: Programs/menu_prefs.c:1460
+msgid "Updatable Directory"
+msgstr ""
+
+#: Programs/log.c:100
+msgid "Update Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:722
+msgid "Use Capital Sign"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+#: Programs/midi.c:114
+msgid "Vibraphone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+#: Programs/midi.c:208
+msgid "Viola"
+msgstr ""
+
+#. Strings
+#. xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+#: Programs/midi.c:205
+msgid "Violin"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+#: Programs/midi.c:245
+msgid "Voice Oohs"
+msgstr ""
+
+#: Programs/menu_prefs.c:1496
+msgid "Warning"
+msgstr ""
+
+#: Programs/menu_prefs.c:1440
+msgid "Web Site"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+#: Programs/midi.c:323
+msgid "Whistle"
+msgstr ""
+
+#: Programs/menu_prefs.c:543
+msgid "Window Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:542
+#: Programs/menu_prefs.c:558
+msgid "Window Coordinates"
+msgstr ""
+
+#: Programs/menu_prefs.c:544
+msgid "Window Row"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+#: Programs/midi.c:440
+msgid "Woodblock"
+msgstr ""
+
+#: Programs/menu_prefs.c:849
+msgid "Word Wrap"
+msgstr ""
+
+#: Programs/menu_prefs.c:1470
+msgid "Writable Directory"
+msgstr ""
+
+#: Programs/brltty-hid.c:195
+msgid "Write (set) a feature report (see below)."
+msgstr ""
+
+#: Programs/brltty-hid.c:188
+msgid "Write (set) an output report (see below)."
+msgstr ""
+
+#: Programs/brltty-lscmds.c:254
+msgid "Write a brltty command reference in reStructuredText."
+msgstr ""
+
+#: Programs/xbrlapi.c:113
+msgid "Write debugging output to stdout"
+msgstr ""
+
+#: Programs/tbl2hex.c:215
+msgid "Write the hexadecimal array representation of a compiled table."
+msgstr ""
+
+#: Programs/config.c:768
+msgid "Write the start-up logs, and then exit."
+msgstr ""
+
+#: Programs/xbrlapi.c:101
+msgid "X display to connect to"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr ""
+
+#: Programs/xbrlapi.c:814
+msgid "XFree(wm_name) for change"
+msgstr ""
+
+#. xgettext: This is the description of the PASSXT command.
+#: Programs/cmds.auto.h:1495
+msgid "XT (set 1) keyboard scan code"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+#: Programs/midi.c:120
+msgid "Xylophone"
+msgstr ""
+
+#: Programs/menu_prefs.c:1314
+msgid "Year Month Day"
+msgstr ""
+
+#: Programs/menu.c:524
+msgid "Yes"
+msgstr ""
+
+#: Programs/xbrlapi.c:85
+msgid "[host][:port]"
+msgstr ""
+
+#: Programs/menu_prefs.c:665
+msgid "all dots"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:104
+msgid "and"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_APPEND command.
+#: Programs/cmds.auto.h:1353
+msgid "append characters to clipboard"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_ADD command.
+#: Programs/cmds.auto.h:1230
+msgid "append to clipboard from character"
+msgstr ""
+
+#: Programs/cmd.c:291
+msgid "at cursor"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_BACKSPACE command.
+#: Programs/cmds.auto.h:1543
+msgid "backspace key"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1150
+#: Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr ""
+
+#. xgettext: This is the description of the SELECTVT command.
+#: Programs/cmds.auto.h:1453
+msgid "bind to specific virtual terminal"
+msgstr ""
+
+#. xgettext: This is the description of the SELECTVT_NEXT command.
+#: Programs/cmds.auto.h:957
+msgid "bind to the next virtual terminal"
+msgstr ""
+
+#. xgettext: This is the description of the SELECTVT_PREV command.
+#: Programs/cmds.auto.h:950
+msgid "bind to the previous virtual terminal"
+msgstr ""
+
+#.
+#: Programs/cmd_utils.c:151
+msgid "black"
+msgstr ""
+
+#: Programs/core.c:1126
+msgid "blank line"
+msgstr ""
+
+#: Programs/cmd_utils.c:180
+msgid "blinking"
+msgstr ""
+
+#. B
+#: Programs/cmd_utils.c:152
+msgid "blue"
+msgstr ""
+
+#: Programs/config.c:3013
+#, c-format
+msgid "braille device not specified"
+msgstr ""
+
+#. xgettext: This is the description of the OFFLINE command.
+#: Programs/cmds.auto.h:621
+msgid "braille display temporarily unavailable"
+msgstr ""
+
+#: Programs/config.c:1852
+msgid "braille driver not loadable"
+msgstr ""
+
+#: Programs/config.c:2113
+msgid "braille driver restarting"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:163
+msgid "braille driver stopped"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr ""
+
+#. xgettext: This is the description of the ROUTE command.
+#: Programs/cmds.auto.h:1214
+msgid "bring screen cursor to character"
+msgstr ""
+
+#. xgettext: This is the description of the CSRJMP_VERT command.
+#: Programs/cmds.auto.h:593
+msgid "bring screen cursor to current line"
+msgstr ""
+
+#. xgettext: This is the description of the ROUTE_LINE command.
+#: Programs/cmds.auto.h:1411
+msgid "bring screen cursor to line"
+msgstr ""
+
+#. xgettext: This is the description of the ROUTE_CURR_LOCN command.
+#: Programs/cmds.auto.h:835
+msgid "bring screen cursor to speech cursor"
+msgstr ""
+
+#. xgettext: This is the description of the ROUTE_SPEECH command.
+#: Programs/cmds.auto.h:1445
+msgid "bring speech cursor to character"
+msgstr ""
+
+#. RG
+#: Programs/cmd_utils.c:157
+msgid "brown"
+msgstr ""
+
+#: Programs/brltty-hid.c:186
+#: Programs/brltty-hid.c:193
+msgid "bytes"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1485
+msgid "can't get console state"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1563
+msgid "can't open console"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1609
+msgid "can't read screen content"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1654
+msgid "can't read screen header"
+msgstr ""
+
+#: Programs/ctb_translate.c:393
+msgid "cannot access internal contraction table"
+msgstr ""
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr ""
+
+#: Programs/ctb_translate.c:387
+msgid "cannot compile contraction table"
+msgstr ""
+
+#: Programs/config.c:1754
+msgid "cannot compile key table"
+msgstr ""
+
+#: Programs/config.c:1293
+msgid "cannot compile keyboard table"
+msgstr ""
+
+#: Programs/ttb_translate.c:275
+msgid "cannot compile text table"
+msgstr ""
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:205
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:680
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr ""
+
+#: Programs/file.c:433
+msgid "cannot create directory"
+msgstr ""
+
+#: Programs/program.c:151
+#, c-format
+msgid "cannot determine program directory"
+msgstr ""
+
+#: Programs/config.c:2972
+#: Programs/menu.c:618
+msgid "cannot determine working directory"
+msgstr ""
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr ""
+
+#: Programs/program.c:165
+msgid "cannot fix install path"
+msgstr ""
+
+#: Programs/xbrlapi.c:260
+msgid "cannot get tty\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:266
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr ""
+
+#: Programs/file.c:572
+msgid "cannot get working directory"
+msgstr ""
+
+#: Programs/xbrlapi.c:730
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:273
+msgid "cannot ignore keys\n"
+msgstr ""
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr ""
+
+#: Programs/ctb_translate.c:407
+msgid "cannot load contraction table"
+msgstr ""
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr ""
+
+#: Programs/ttb_translate.c:295
+msgid "cannot load text table"
+msgstr ""
+
+#: Programs/file.c:422
+msgid "cannot make world writable"
+msgstr ""
+
+#: Programs/config.c:1248
+msgid "cannot open key help"
+msgstr ""
+
+#: Programs/program.c:289
+msgid "cannot open process identifier file"
+msgstr ""
+
+#: Programs/menu.c:616
+msgid "cannot open working directory"
+msgstr ""
+
+#: Programs/prefs.c:363
+msgid "cannot read preferences file"
+msgstr ""
+
+#: Programs/xbrlapi.c:338
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr ""
+
+#: Programs/file.c:586
+#: Programs/menu.c:605
+msgid "cannot set working directory"
+msgstr ""
+
+#: Programs/prefs.c:576
+msgid "cannot write to preferences file"
+msgstr ""
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1071
+msgid "cap"
+msgstr ""
+
+#: Programs/menu_prefs.c:888
+#: Programs/menu_prefs.c:1355
+msgid "cells"
+msgstr ""
+
+#: Programs/cmd_preferences.c:95
+msgid "changes discarded"
+msgstr ""
+
+#: Programs/brltty-ktb.c:290
+msgid "check a key table, list the key naems it can use, or write the key bindings it defines in useful formats."
+msgstr ""
+
+#. xgettext: This is the description of the UNSTICK command.
+#: Programs/cmds.auto.h:887
+msgid "clear all sticky input modifiers"
+msgstr ""
+
+#. xgettext: This is the description of the TXTSEL_CLEAR command.
+#: Programs/cmds.auto.h:1019
+msgid "clear the text selection"
+msgstr ""
+
+#: Programs/cmd_speech.c:501
+msgid "column"
+msgstr ""
+
+#: Programs/brltty-pty.c:240
+msgid "command not found"
+msgstr ""
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr ""
+
+#: Programs/cmdline.c:944
+msgid "configuration directive specified more than once"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1561
+msgid "console not in use"
+msgstr ""
+
+#: Programs/menu_prefs.c:1058
+msgid "console tone generator"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_COPY command.
+#: Programs/cmds.auto.h:1345
+msgid "copy characters to clipboard"
+msgstr ""
+
+#. xgettext: This is the description of the HOST_COPY command.
+#: Programs/cmds.auto.h:1033
+msgid "copy selected text to host clipboard"
+msgstr ""
+
+#: Programs/config.c:640
+msgid "csecs"
+msgstr ""
+
+#. xgettext: This is the description of the TOUCH_AT command.
+#: Programs/cmds.auto.h:1519
+msgid "current reading location"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_CURSOR_DOWN command.
+#: Programs/cmds.auto.h:1583
+msgid "cursor-down key"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_CURSOR_LEFT command.
+#: Programs/cmds.auto.h:1559
+msgid "cursor-left key"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+#: Programs/cmds.auto.h:1567
+msgid "cursor-right key"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_CURSOR_UP command.
+#: Programs/cmds.auto.h:1575
+msgid "cursor-up key"
+msgstr ""
+
+#. xgettext: This is the description of the HOST_CUT command.
+#: Programs/cmds.auto.h:1040
+msgid "cut selected text to host clipboard"
+msgstr ""
+
+#. GB
+#: Programs/cmd_utils.c:154
+msgid "cyan"
+msgstr ""
+
+#. xgettext: This is the description of the ALTGR command.
+#: Programs/cmds.auto.h:894
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr ""
+
+#. xgettext: This is the description of the CONTROL command.
+#: Programs/cmds.auto.h:642
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr ""
+
+#. xgettext: This is the description of the GUI command.
+#: Programs/cmds.auto.h:901
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr ""
+
+#. xgettext: This is the description of the META command.
+#: Programs/cmds.auto.h:649
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr ""
+
+#. xgettext: This is the description of the SHIFT command.
+#: Programs/cmds.auto.h:628
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr ""
+
+#. xgettext: This is the description of the UPPER command.
+#: Programs/cmds.auto.h:635
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr ""
+
+#. L
+#: Programs/cmd_utils.c:159
+msgid "dark grey"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_LOWER command.
+#: Programs/cmds.auto.h:1168
+msgid "decrease speaking pitch"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_SLOWER command.
+#: Programs/cmds.auto.h:550
+msgid "decrease speaking rate"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_SOFTER command.
+#: Programs/cmds.auto.h:564
+msgid "decrease speaking volume"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_DELETE command.
+#: Programs/cmds.auto.h:1631
+msgid "delete key"
+msgstr ""
+
+#. xgettext: This is the description of the DESCCHAR command.
+#: Programs/cmds.auto.h:1282
+msgid "describe character"
+msgstr ""
+
+#. xgettext: This is the description of the DESC_CURR_CHAR command.
+#: Programs/cmds.auto.h:820
+msgid "describe current character"
+msgstr ""
+
+#: Programs/config.c:670
+#: Programs/config.c:680
+msgid "device"
+msgstr ""
+
+#: Programs/brltest.c:61
+#: Programs/brltest.c:79
+#: Programs/brltty-atb.c:31
+#: Programs/brltty-ctb.c:92
+#: Programs/brltty-ktb.c:76
+#: Programs/brltty-ktb.c:85
+#: Programs/brltty-trtxt.c:75
+#: Programs/config.c:689
+#: Programs/config.c:699
+#: Programs/config.c:709
+#: Programs/config.c:719
+#: Programs/config.c:728
+msgid "directory"
+msgstr ""
+
+#: Programs/xbrlapi.c:99
+msgid "display"
+msgstr ""
+
+#. xgettext: This is the description of the NOOP command.
+#: Programs/cmds.auto.h:5
+msgid "do nothing"
+msgstr ""
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr ""
+
+#: Programs/menu_prefs.c:666
+msgid "dot 7"
+msgstr ""
+
+#: Programs/menu_prefs.c:667
+msgid "dot 8"
+msgstr ""
+
+#: Programs/menu_prefs.c:664
+msgid "dots 7 and 8"
+msgstr ""
+
+#: Programs/brltty-ktb.c:45
+msgid "driver"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1147
+msgid "driver request"
+msgstr ""
+
+#: Programs/config.c:403
+#: Programs/config.c:474
+#: Programs/config.c:517
+msgid "driver,..."
+msgstr ""
+
+#. xgettext: This is the description of the KEY_END command.
+#: Programs/cmds.auto.h:1615
+msgid "end key"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_ENTER command.
+#: Programs/cmds.auto.h:1527
+msgid "enter key"
+msgstr ""
+
+#. xgettext: This is the description of the LEARN command.
+#: Programs/cmds.auto.h:436
+msgid "enter/leave command learn mode"
+msgstr ""
+
+#. xgettext: This is the description of the HELP command.
+#: Programs/cmds.auto.h:422
+msgid "enter/leave help display"
+msgstr ""
+
+#. xgettext: This is the description of the PREFMENU command.
+#: Programs/cmds.auto.h:443
+msgid "enter/leave preferences menu"
+msgstr ""
+
+#. xgettext: This is the description of the INFO command.
+#: Programs/cmds.auto.h:429
+msgid "enter/leave status display"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_ESCAPE command.
+#: Programs/cmds.auto.h:1551
+msgid "escape key"
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1497
+msgid "eurobraille"
+msgstr ""
+
+#. xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+#: Programs/cmd_miscellaneous.c:102
+msgid "exactly"
+msgstr ""
+
+#: Programs/config.c:874
+msgid "excess argument"
+msgstr ""
+
+#. parent
+#: Programs/brltty.c:219
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2047
+msgid "executing as the invoking user"
+msgstr ""
+
+#. execv() shouldn't return
+#: Programs/brltty.c:226
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:737
+msgid "failed to get first focus\n"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:47
+#: Programs/brltty-trtxt.c:55
+#: Programs/config.c:392
+#: Programs/config.c:446
+#: Programs/config.c:456
+#: Programs/config.c:465
+#: Programs/config.c:493
+#: Programs/config.c:536
+#: Programs/config.c:553
+#: Programs/config.c:604
+#: Programs/config.c:738
+msgid "file"
+msgstr ""
+
+#: Programs/cmdline.c:1130
+#, c-format
+msgid "file '%s' processing error."
+msgstr ""
+
+#. failed
+#: Programs/brltty.c:214
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_FUNCTION command.
+#: Programs/cmds.auto.h:1640
+msgid "function key"
+msgstr ""
+
+#: Programs/log.c:124
+msgid "generic I/O"
+msgstr ""
+
+#. xgettext: This is the description of the BACK command.
+#: Programs/cmds.auto.h:271
+msgid "go back after cursor tracking"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_BACK command.
+#: Programs/cmds.auto.h:1077
+msgid "go back to the previous screen"
+msgstr ""
+
+#. xgettext: This is the description of the FWINLT command.
+#: Programs/cmds.auto.h:210
+msgid "go backward one braille window"
+msgstr ""
+
+#. xgettext: This is the description of the FWINLTSKIP command.
+#: Programs/cmds.auto.h:228
+msgid "go backward skipping blank braille windows"
+msgstr ""
+
+#. xgettext: This is the description of the PRNBWIN command.
+#: Programs/cmds.auto.h:966
+msgid "go backward to nearest non-blank braille window"
+msgstr ""
+
+#. xgettext: This is the description of the LNDN command.
+#: Programs/cmds.auto.h:23
+msgid "go down one line"
+msgstr ""
+
+#. xgettext: This is the description of the WINDN command.
+#: Programs/cmds.auto.h:41
+msgid "go down several lines"
+msgstr ""
+
+#. xgettext: This is the description of the NXPGRPH command.
+#: Programs/cmds.auto.h:133
+msgid "go down to first line of next paragraph"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_LAST_ITEM command.
+#: Programs/cmds.auto.h:475
+msgid "go down to last item"
+msgstr ""
+
+#. xgettext: This is the description of the NXDIFCHAR command.
+#: Programs/cmds.auto.h:1337
+msgid "go down to nearest line with different character"
+msgstr ""
+
+#. xgettext: This is the description of the NXDIFLN command.
+#: Programs/cmds.auto.h:59
+msgid "go down to nearest line with different content"
+msgstr ""
+
+#. xgettext: This is the description of the ATTRDN command.
+#: Programs/cmds.auto.h:77
+msgid "go down to nearest line with different highlighting"
+msgstr ""
+
+#. xgettext: This is the description of the NXINDENT command.
+#: Programs/cmds.auto.h:1274
+msgid "go down to nearest line with less indent than character"
+msgstr ""
+
+#. xgettext: This is the description of the NXPROMPT command.
+#: Programs/cmds.auto.h:151
+msgid "go down to next command prompt"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_NEXT_ITEM command.
+#: Programs/cmds.auto.h:493
+msgid "go down to next item"
+msgstr ""
+
+#. xgettext: This is the description of the FWINRT command.
+#: Programs/cmds.auto.h:219
+msgid "go forward one braille window"
+msgstr ""
+
+#. xgettext: This is the description of the FWINRTSKIP command.
+#: Programs/cmds.auto.h:237
+msgid "go forward skipping blank braille windows"
+msgstr ""
+
+#. xgettext: This is the description of the NXNBWIN command.
+#: Programs/cmds.auto.h:975
+msgid "go forward to nearest non-blank braille window"
+msgstr ""
+
+#. xgettext: This is the description of the HWINLT command.
+#: Programs/cmds.auto.h:192
+msgid "go left half a braille window"
+msgstr ""
+
+#. xgettext: This is the description of the CHRLT command.
+#: Programs/cmds.auto.h:174
+msgid "go left one character"
+msgstr ""
+
+#. xgettext: This is the description of the HWINRT command.
+#: Programs/cmds.auto.h:201
+msgid "go right half a braille window"
+msgstr ""
+
+#. xgettext: This is the description of the CHRRT command.
+#: Programs/cmds.auto.h:183
+msgid "go right one character"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_FRST_CHAR command.
+#: Programs/cmds.auto.h:789
+msgid "go to and speak first non-blank character on line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_FRST_LINE command.
+#: Programs/cmds.auto.h:805
+msgid "go to and speak first non-blank line on screen"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_LAST_CHAR command.
+#: Programs/cmds.auto.h:797
+msgid "go to and speak last non-blank character on line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_LAST_LINE command.
+#: Programs/cmds.auto.h:813
+msgid "go to and speak last non-blank line on screen"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+#: Programs/cmds.auto.h:735
+msgid "go to and speak next character"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_NEXT_LINE command.
+#: Programs/cmds.auto.h:781
+msgid "go to and speak next line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_NEXT_WORD command.
+#: Programs/cmds.auto.h:758
+msgid "go to and speak next word"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_PREV_CHAR command.
+#: Programs/cmds.auto.h:727
+msgid "go to and speak previous character"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_PREV_LINE command.
+#: Programs/cmds.auto.h:773
+msgid "go to and speak previous line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_PREV_WORD command.
+#: Programs/cmds.auto.h:750
+msgid "go to and speak previous word"
+msgstr ""
+
+#. xgettext: This is the description of the BOT_LEFT command.
+#: Programs/cmds.auto.h:115
+msgid "go to beginning of bottom line"
+msgstr ""
+
+#. xgettext: This is the description of the LNBEG command.
+#: Programs/cmds.auto.h:246
+msgid "go to beginning of line"
+msgstr ""
+
+#. xgettext: This is the description of the TOP_LEFT command.
+#: Programs/cmds.auto.h:105
+msgid "go to beginning of top line"
+msgstr ""
+
+#. xgettext: This is the description of the BOT command.
+#: Programs/cmds.auto.h:95
+msgid "go to bottom line"
+msgstr ""
+
+#. xgettext: This is the description of the SPKHOME command.
+#: Programs/cmds.auto.h:522
+msgid "go to current speaking position"
+msgstr ""
+
+#. xgettext: This is the description of the LNEND command.
+#: Programs/cmds.auto.h:255
+msgid "go to end of line"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_PREV_LEVEL command.
+#: Programs/cmds.auto.h:664
+msgid "go to previous menu level"
+msgstr ""
+
+#. xgettext: This is the description of the GOTOMARK command.
+#: Programs/cmds.auto.h:1307
+msgid "go to remembered braille window position"
+msgstr ""
+
+#. xgettext: This is the description of the HOME command.
+#: Programs/cmds.auto.h:263
+msgid "go to screen cursor"
+msgstr ""
+
+#. xgettext: This is the description of the RETURN command.
+#: Programs/cmds.auto.h:279
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr ""
+
+#. xgettext: This is the description of the GOTOLINE command.
+#: Programs/cmds.auto.h:1317
+msgid "go to selected line"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_HOME command.
+#: Programs/cmds.auto.h:1069
+msgid "go to the home screen"
+msgstr ""
+
+#. xgettext: This is the description of the TOP command.
+#: Programs/cmds.auto.h:86
+msgid "go to top line"
+msgstr ""
+
+#. xgettext: This is the description of the LNUP command.
+#: Programs/cmds.auto.h:14
+msgid "go up one line"
+msgstr ""
+
+#. xgettext: This is the description of the WINUP command.
+#: Programs/cmds.auto.h:32
+msgid "go up several lines"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_FIRST_ITEM command.
+#: Programs/cmds.auto.h:466
+msgid "go up to first item"
+msgstr ""
+
+#. xgettext: This is the description of the PRPGRPH command.
+#: Programs/cmds.auto.h:124
+msgid "go up to first line of paragraph"
+msgstr ""
+
+#. xgettext: This is the description of the PRDIFCHAR command.
+#: Programs/cmds.auto.h:1327
+msgid "go up to nearest line with different character"
+msgstr ""
+
+#. xgettext: This is the description of the PRDIFLN command.
+#: Programs/cmds.auto.h:50
+msgid "go up to nearest line with different content"
+msgstr ""
+
+#. xgettext: This is the description of the ATTRUP command.
+#: Programs/cmds.auto.h:68
+msgid "go up to nearest line with different highlighting"
+msgstr ""
+
+#. xgettext: This is the description of the PRINDENT command.
+#: Programs/cmds.auto.h:1264
+msgid "go up to nearest line with less indent than character"
+msgstr ""
+
+#. xgettext: This is the description of the PRPROMPT command.
+#: Programs/cmds.auto.h:142
+msgid "go up to previous command prompt"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_PREV_ITEM command.
+#: Programs/cmds.auto.h:484
+msgid "go up to previous item"
+msgstr ""
+
+#. G
+#: Programs/cmd_utils.c:153
+msgid "green"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2169
+msgid "group permissions added"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:213
+msgid "help not available"
+msgstr ""
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_HOME command.
+#: Programs/cmds.auto.h:1607
+msgid "home key"
+msgstr ""
+
+#: Programs/brltty-hid.c:81
+#: Programs/brltty-hid.c:88
+#: Programs/brltty-hid.c:172
+#: Programs/brltty-hid.c:179
+msgid "identifier"
+msgstr ""
+
+#: Programs/config.c:424
+msgid "identifier,..."
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1149
+msgid "idle timeout"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_HIGHER command.
+#: Programs/cmds.auto.h:1175
+msgid "increase speaking pitch"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_FASTER command.
+#: Programs/cmds.auto.h:557
+msgid "increase speaking rate"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_LOUDER command.
+#: Programs/cmds.auto.h:571
+msgid "increase speaking volume"
+msgstr ""
+
+#: Programs/core.c:1129
+msgid "indent"
+msgstr ""
+
+#. xgettext: This is the description of the PASTE_HISTORY command.
+#: Programs/cmds.auto.h:1361
+msgid "insert clipboard history entry after screen cursor"
+msgstr ""
+
+#. xgettext: This is the description of the PASTE command.
+#: Programs/cmds.auto.h:600
+msgid "insert clipboard text after screen cursor"
+msgstr ""
+
+#. xgettext: This is the description of the HOST_PASTE command.
+#: Programs/cmds.auto.h:1047
+msgid "insert host clipboard text after screen cursor"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_INSERT command.
+#: Programs/cmds.auto.h:1623
+msgid "insert key"
+msgstr ""
+
+#: Programs/program.c:173
+msgid "install path not absolute"
+msgstr ""
+
+#: Programs/brltty-hid.c:206
+msgid "integer"
+msgstr ""
+
+#: Programs/cmdline.c:104
+msgid "invalid counter setting"
+msgstr ""
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr ""
+
+#: Programs/cmdline.c:113
+msgid "invalid flag setting"
+msgstr ""
+
+#: Programs/config.c:2890
+msgid "invalid message hold timeout"
+msgstr ""
+
+#: Programs/cmdline.c:667
+msgid "invalid operand"
+msgstr ""
+
+#: Programs/brlapi_server.c:4544
+msgid "invalid thread stack size"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr ""
+
+#. L  B
+#: Programs/cmd_utils.c:160
+msgid "light blue"
+msgstr ""
+
+#. L GB
+#: Programs/cmd_utils.c:162
+msgid "light cyan"
+msgstr ""
+
+#. L G
+#: Programs/cmd_utils.c:161
+msgid "light green"
+msgstr ""
+
+#. RGB
+#: Programs/cmd_utils.c:158
+msgid "light grey"
+msgstr ""
+
+#. LR B
+#: Programs/cmd_utils.c:164
+msgid "light magenta"
+msgstr ""
+
+#. LR
+#: Programs/cmd_utils.c:163
+msgid "light red"
+msgstr ""
+
+#: Programs/cmd_speech.c:500
+msgid "line"
+msgstr ""
+
+#. xgettext: This is the description of the COPY_LINE command.
+#: Programs/cmds.auto.h:1246
+msgid "linear copy to character"
+msgstr ""
+
+#: Programs/brltty-pty.c:112
+msgid "log escape sequences and special characters received from the pty slave"
+msgstr ""
+
+#: Programs/brltty-pty.c:100
+msgid "log input written to the pty slave"
+msgstr ""
+
+#: Programs/brltty-pty.c:106
+msgid "log output received from the pty slave that isn't an escape sequence or a special character"
+msgstr ""
+
+#: Programs/brltty-pty.c:118
+msgid "log unexpected input/output"
+msgstr ""
+
+#: Programs/config.c:595
+msgid "lvl|cat,..."
+msgstr ""
+
+#. R B
+#: Programs/cmd_utils.c:156
+msgid "magenta"
+msgstr ""
+
+#: Programs/cmdline.c:662
+msgid "missing operand"
+msgstr ""
+
+#: Programs/parse.c:533
+msgid "missing parameter name"
+msgstr ""
+
+#: Programs/parse.c:519
+msgid "missing parameter qualifier"
+msgstr ""
+
+#: Programs/parse.c:495
+msgid "missing parameter value"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_ITEM_FRST command.
+#: Programs/cmds.auto.h:1140
+msgid "move to the first item in the screen area"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_ITEM_LAST command.
+#: Programs/cmds.auto.h:1161
+msgid "move to the last item in the screen area"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_ITEM_NEXT command.
+#: Programs/cmds.auto.h:1154
+msgid "move to the next item in the screen area"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_ITEM_PREV command.
+#: Programs/cmds.auto.h:1147
+msgid "move to the previous item in the screen area"
+msgstr ""
+
+#: Programs/msgtest.c:57
+msgid "name"
+msgstr ""
+
+#: Programs/config.c:414
+#: Programs/config.c:484
+#: Programs/config.c:527
+#: Programs/config.c:545
+#: Programs/config.c:562
+#: Programs/config.c:578
+#: Programs/config.c:631
+msgid "name=value,..."
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1513
+msgid "native"
+msgstr ""
+
+#: Programs/menu_prefs.c:668
+msgid "no dots"
+msgstr ""
+
+#: Programs/config.c:1257
+msgid "no key bindings"
+msgstr ""
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr ""
+
+#: Drivers/Screen/TerminalEmulator/em_screen.c:137
+msgid "no screen cache"
+msgstr ""
+
+#: Drivers/Screen/TerminalEmulator/em_screen.c:398
+msgid "no screen emulator"
+msgstr ""
+
+#: Programs/cmd_preferences.c:47
+msgid "not saved"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2020
+msgid "not switching to an unprivileged user"
+msgstr ""
+
+#: Programs/brltty-hid.c:116
+msgid "octets"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_ALERTS command.
+#: Programs/cmds.auto.h:1112
+msgid "open the application alerts window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_LIST command.
+#: Programs/cmds.auto.h:1098
+msgid "open the application list window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_MENU command.
+#: Programs/cmds.auto.h:1105
+msgid "open the application-specific menu"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_BRL_ACTIONS command.
+#: Programs/cmds.auto.h:1061
+msgid "open the braille actions window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_DEV_OPTIONS command.
+#: Programs/cmds.auto.h:1091
+msgid "open the device options window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_DEV_SETTINGS command.
+#: Programs/cmds.auto.h:1084
+msgid "open the device settings window"
+msgstr ""
+
+#: Programs/cmdline.c:246
+msgid "option"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2154
+msgid "ownership claimed"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_PAGE_DOWN command.
+#: Programs/cmds.auto.h:1599
+msgid "page-down key"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_PAGE_UP command.
+#: Programs/cmds.auto.h:1591
+msgid "page-up key"
+msgstr ""
+
+#: Programs/msgtest.c:42
+msgid "path"
+msgstr ""
+
+#: Programs/config.c:2863
+msgid "pid file not specified"
+msgstr ""
+
+#: Programs/program.c:335
+msgid "pid file open error"
+msgstr ""
+
+#: Programs/spk.c:232
+msgid "pitch"
+msgstr ""
+
+#. xgettext: This is the description of the SETLEFT command.
+#: Programs/cmds.auto.h:1290
+msgid "place left end of braille window at character"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1148
+msgid "power switch"
+msgstr ""
+
+#: Programs/config.c:507
+msgid "quality"
+msgstr ""
+
+#: Programs/spk.c:206
+msgid "rate"
+msgstr ""
+
+#. xgettext: This is the description of the COPY_RECT command.
+#: Programs/cmds.auto.h:1238
+msgid "rectangular copy to character"
+msgstr ""
+
+#. R
+#: Programs/cmd_utils.c:155
+msgid "red"
+msgstr ""
+
+#. xgettext: This is the description of the REFRESH command.
+#: Programs/cmds.auto.h:1005
+msgid "refresh braille display"
+msgstr ""
+
+#. xgettext: This is the description of the REFRESH_LINE command.
+#: Programs/cmds.auto.h:1420
+msgid "refresh braille line"
+msgstr ""
+
+#: Programs/config.c:661
+msgid "regexp,..."
+msgstr ""
+
+#: Programs/config.c:2117
+#, c-format
+msgid "reinitializing braille driver"
+msgstr ""
+
+#: Programs/config.c:2635
+#, c-format
+msgid "reinitializing screen driver"
+msgstr ""
+
+#: Programs/config.c:2432
+#, c-format
+msgid "reinitializing speech driver"
+msgstr ""
+
+#. xgettext: This is the description of the SETMARK command.
+#: Programs/cmds.auto.h:1298
+msgid "remember current braille window position"
+msgstr ""
+
+#. xgettext: This is the description of the ALERT command.
+#: Programs/cmds.auto.h:1461
+msgid "render an alert"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr ""
+
+#. xgettext: This is the description of the PREFRESET command.
+#: Programs/cmds.auto.h:1205
+msgid "reset preferences to defaults"
+msgstr ""
+
+#. xgettext: This is the description of the RESTARTBRL command.
+#: Programs/cmds.auto.h:607
+msgid "restart braille driver"
+msgstr ""
+
+#. xgettext: This is the description of the RESTARTSPEECH command.
+#: Programs/cmds.auto.h:614
+msgid "restart speech driver"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_RESTORE command.
+#: Programs/cmds.auto.h:864
+msgid "restore clipboard from disk"
+msgstr ""
+
+#. xgettext: This is the description of the PREFLOAD command.
+#: Programs/cmds.auto.h:457
+msgid "restore preferences from disk"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_AREA_ACTV command.
+#: Programs/cmds.auto.h:1119
+msgid "return to the active screen area"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_SAVE command.
+#: Programs/cmds.auto.h:857
+msgid "save clipboard to disk"
+msgstr ""
+
+#. xgettext: This is the description of the PREFSAVE command.
+#: Programs/cmds.auto.h:450
+msgid "save preferences to disk"
+msgstr ""
+
+#: Programs/xbrlapi.c:92
+msgid "scheme+..."
+msgstr ""
+
+#: Programs/config.c:2511
+msgid "screen driver not loadable"
+msgstr ""
+
+#: Programs/config.c:2632
+msgid "screen driver restarting"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:171
+msgid "screen driver stopped"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr ""
+
+#: Drivers/Screen/TerminalEmulator/em_screen.c:141
+msgid "screen not accessible"
+msgstr ""
+
+#: Drivers/Screen/TerminalEmulator/em_screen.c:395
+msgid "screen not attached"
+msgstr ""
+
+#: Drivers/Screen/TerminalEmulator/em_screen.c:384
+msgid "screen not available"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1584
+msgid "screen not in text mode"
+msgstr ""
+
+#. xgettext: This is the description of the PRSEARCH command.
+#: Programs/cmds.auto.h:158
+msgid "search backward for clipboard text"
+msgstr ""
+
+#. xgettext: This is the description of the NXSEARCH command.
+#: Programs/cmds.auto.h:165
+msgid "search forward for clipboard text"
+msgstr ""
+
+#: Programs/menu.c:469
+msgid "seconds"
+msgstr ""
+
+#. xgettext: This is the description of the TXTSEL_ALL command.
+#: Programs/cmds.auto.h:1026
+msgid "select all of the text"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_NEXT_SETTING command.
+#: Programs/cmds.auto.h:507
+msgid "select next choice"
+msgstr ""
+
+#. xgettext: This is the description of the MENU_PREV_SETTING command.
+#: Programs/cmds.auto.h:500
+msgid "select previous choice"
+msgstr ""
+
+#. xgettext: This is the description of the TUNES command.
+#: Programs/cmds.auto.h:399
+msgid "set alert tunes on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ATTRBLINK command.
+#: Programs/cmds.auto.h:383
+msgid "set attribute blinking on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ATTRVIS command.
+#: Programs/cmds.auto.h:375
+msgid "set attribute underlining on/off"
+msgstr ""
+
+#. xgettext: This is the description of the SET_ATTRIBUTES_TABLE command.
+#: Programs/cmds.auto.h:1377
+msgid "set attributes table"
+msgstr ""
+
+#. xgettext: This is the description of the AUTOREPEAT command.
+#: Programs/cmds.auto.h:407
+msgid "set autorepeat on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_CMP_WORDS command.
+#: Programs/cmds.auto.h:712
+msgid "set autospeak completed words on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_DEL_CHARS command.
+#: Programs/cmds.auto.h:696
+msgid "set autospeak deleted characters on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_INDENT command.
+#: Programs/cmds.auto.h:998
+msgid "set autospeak indent of current line on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_INS_CHARS command.
+#: Programs/cmds.auto.h:688
+msgid "set autospeak inserted characters on/off"
+msgstr ""
+
+#. xgettext: This is the description of the AUTOSPEAK command.
+#: Programs/cmds.auto.h:415
+msgid "set autospeak on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_REP_CHARS command.
+#: Programs/cmds.auto.h:704
+msgid "set autospeak replaced characters on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_SEL_CHAR command.
+#: Programs/cmds.auto.h:680
+msgid "set autospeak selected character on/off"
+msgstr ""
+
+#. xgettext: This is the description of the ASPK_SEL_LINE command.
+#: Programs/cmds.auto.h:672
+msgid "set autospeak selected line on/off"
+msgstr ""
+
+#. xgettext: This is the description of the BRLKBD command.
+#: Programs/cmds.auto.h:880
+msgid "set braille keyboard enabled/disabled"
+msgstr ""
+
+#. xgettext: This is the description of the BRLUCDOTS command.
+#: Programs/cmds.auto.h:872
+msgid "set braille typing mode dots/text"
+msgstr ""
+
+#. xgettext: This is the description of the CAPBLINK command.
+#: Programs/cmds.auto.h:391
+msgid "set capital letter blinking on/off"
+msgstr ""
+
+#. xgettext: This is the description of the CONTRACTED command.
+#: Programs/cmds.auto.h:1190
+msgid "set contracted/computer braille"
+msgstr ""
+
+#. xgettext: This is the description of the SET_CONTRACTION_TABLE command.
+#: Programs/cmds.auto.h:1385
+msgid "set contraction table"
+msgstr ""
+
+#. xgettext: This is the description of the DISPMD command.
+#: Programs/cmds.auto.h:295
+msgid "set display mode attributes/text"
+msgstr ""
+
+#. xgettext: This is the description of the CSRHIDE command.
+#: Programs/cmds.auto.h:343
+msgid "set hidden screen cursor on/off"
+msgstr ""
+
+#. xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+#: Programs/cmds.auto.h:1393
+msgid "set keyboard table"
+msgstr ""
+
+#. xgettext: This is the description of the SET_LANGUAGE_PROFILE command.
+#: Programs/cmds.auto.h:1401
+msgid "set language profile"
+msgstr ""
+
+#. xgettext: This is the description of the CSRBLINK command.
+#: Programs/cmds.auto.h:367
+msgid "set screen cursor blinking on/off"
+msgstr ""
+
+#. xgettext: This is the description of the CSRSIZE command.
+#: Programs/cmds.auto.h:359
+msgid "set screen cursor style block/underline"
+msgstr ""
+
+#. xgettext: This is the description of the CSRVIS command.
+#: Programs/cmds.auto.h:335
+msgid "set screen cursor visibility on/off"
+msgstr ""
+
+#. xgettext: This is the description of the FREEZE command.
+#: Programs/cmds.auto.h:287
+msgid "set screen image frozen/unfrozen"
+msgstr ""
+
+#. xgettext: This is the description of the COMPBRL6 command.
+#: Programs/cmds.auto.h:1198
+msgid "set six/eight dot computer braille"
+msgstr ""
+
+#. xgettext: This is the description of the SKPBLNKWINS command.
+#: Programs/cmds.auto.h:327
+msgid "set skipping of blank braille windows on/off"
+msgstr ""
+
+#. xgettext: This is the description of the SKPIDLNS command.
+#: Programs/cmds.auto.h:319
+msgid "set skipping of lines with identical content on/off"
+msgstr ""
+
+#. xgettext: This is the description of the SLIDEWIN command.
+#: Programs/cmds.auto.h:311
+msgid "set sliding braille window on/off"
+msgstr ""
+
+#. xgettext: This is the description of the SHOW_CURR_LOCN command.
+#: Programs/cmds.auto.h:850
+msgid "set speech cursor visibility on/off"
+msgstr ""
+
+#. xgettext: This is the description of the TXTSEL_SET command.
+#: Programs/cmds.auto.h:1436
+msgid "set text selection"
+msgstr ""
+
+#. xgettext: This is the description of the SIXDOTS command.
+#: Programs/cmds.auto.h:303
+msgid "set text style 6-dot/8-dot"
+msgstr ""
+
+#. xgettext: This is the description of the SET_TEXT_TABLE command.
+#: Programs/cmds.auto.h:1369
+msgid "set text table"
+msgstr ""
+
+#. xgettext: This is the description of the TOUCH_NAV command.
+#: Programs/cmds.auto.h:983
+msgid "set touch navigation on/off"
+msgstr ""
+
+#. xgettext: This is the description of the CSRTRK command.
+#: Programs/cmds.auto.h:351
+msgid "set track screen cursor on/off"
+msgstr ""
+
+#. xgettext: This is the description of the TIME command.
+#: Programs/cmds.auto.h:656
+msgid "show current date and time"
+msgstr ""
+
+#: Programs/brltty-pty.c:66
+msgid "show the absolute path to the pty slave"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_TITLE command.
+#: Programs/cmds.auto.h:1054
+msgid "show the window title"
+msgstr ""
+
+#. xgettext: This is the description of the INDICATORS command.
+#: Programs/cmds.auto.h:1012
+msgid "show various device status indicators"
+msgstr ""
+
+#: Programs/menu_prefs.c:1059
+msgid "soundcard digital audio"
+msgstr ""
+
+#: Programs/menu_prefs.c:1061
+msgid "soundcard synthesizer"
+msgstr ""
+
+#: Programs/cmd.c:278
+#: Programs/core.c:1052
+msgid "space"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_CURR_CHAR command.
+#: Programs/cmds.auto.h:719
+msgid "speak current character"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_LINE command.
+#. xgettext: This is the description of the SPEAK_CURR_LINE command.
+#: Programs/cmds.auto.h:529
+#: Programs/cmds.auto.h:765
+msgid "speak current line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_CURR_WORD command.
+#: Programs/cmds.auto.h:742
+msgid "speak current word"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_BELOW command.
+#: Programs/cmds.auto.h:543
+msgid "speak from current line through bottom of screen"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_ALL command.
+#: Programs/cmds.auto.h:1182
+msgid "speak from top of screen through bottom of screen"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_ABOVE command.
+#: Programs/cmds.auto.h:536
+msgid "speak from top of screen through current line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_INDENT command.
+#: Programs/cmds.auto.h:990
+msgid "speak indent of current line"
+msgstr ""
+
+#. xgettext: This is the description of the SPEAK_CURR_LOCN command.
+#: Programs/cmds.auto.h:842
+msgid "speak speech cursor location"
+msgstr ""
+
+#: Programs/msgtest.c:50
+msgid "specifier"
+msgstr ""
+
+#: Programs/config.c:2280
+msgid "speech driver not loadable"
+msgstr ""
+
+#: Programs/config.c:2429
+msgid "speech driver restarting"
+msgstr ""
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr ""
+
+#. xgettext: This is the description of the SPELL_CURR_WORD command.
+#: Programs/cmds.auto.h:827
+msgid "spell current word"
+msgstr ""
+
+#: Programs/brltty-pty.c:403
+msgid "standard input isn't a terminal"
+msgstr ""
+
+#: Programs/brltty-pty.c:408
+msgid "standard output isn't a terminal"
+msgstr ""
+
+#. xgettext: This is the description of the CLIP_NEW command.
+#: Programs/cmds.auto.h:1222
+msgid "start new clipboard at character"
+msgstr ""
+
+#. xgettext: This is the description of the TXTSEL_START command.
+#: Programs/cmds.auto.h:1428
+msgid "start text selection"
+msgstr ""
+
+#. xgettext: This is the description of the BRL_START command.
+#: Programs/cmds.auto.h:915
+msgid "start the braille driver"
+msgstr ""
+
+#. xgettext: This is the description of the SCR_START command.
+#: Programs/cmds.auto.h:943
+msgid "start the screen driver"
+msgstr ""
+
+#. xgettext: This is the description of the SPK_START command.
+#: Programs/cmds.auto.h:929
+msgid "start the speech driver"
+msgstr ""
+
+#. xgettext: This is the description of the MUTE command.
+#: Programs/cmds.auto.h:514
+msgid "stop speaking"
+msgstr ""
+
+#. xgettext: This is the description of the BRL_STOP command.
+#: Programs/cmds.auto.h:908
+msgid "stop the braille driver"
+msgstr ""
+
+#. xgettext: This is the description of the SCR_STOP command.
+#: Programs/cmds.auto.h:936
+msgid "stop the screen driver"
+msgstr ""
+
+#. xgettext: This is the description of the SPK_STOP command.
+#: Programs/cmds.auto.h:922
+msgid "stop the speech driver"
+msgstr ""
+
+#: Programs/xbrlapi.c:682
+msgid "strange old error handler\n"
+msgstr ""
+
+#: Programs/brltty-cldr.c:39
+#: Programs/brltty-hid.c:95
+#: Programs/brltty-hid.c:102
+#: Programs/brltty-hid.c:109
+#: Programs/brltty-hid.c:123
+msgid "string"
+msgstr ""
+
+#. xgettext: This is the description of the CONTEXT command.
+#: Programs/cmds.auto.h:1511
+msgid "switch to command context"
+msgstr ""
+
+#. xgettext: This is the description of the SWITCHVT command.
+#: Programs/cmds.auto.h:1254
+msgid "switch to specific virtual terminal"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_AREA_NEXT command.
+#: Programs/cmds.auto.h:1133
+msgid "switch to the next screen area"
+msgstr ""
+
+#. xgettext: This is the description of the SWITCHVT_NEXT command.
+#: Programs/cmds.auto.h:585
+msgid "switch to the next virtual terminal"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_AREA_PREV command.
+#: Programs/cmds.auto.h:1126
+msgid "switch to the previous screen area"
+msgstr ""
+
+#. xgettext: This is the description of the SWITCHVT_PREV command.
+#: Programs/cmds.auto.h:578
+msgid "switch to the previous virtual terminal"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1996
+msgid "switched to unprivileged user"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_TAB command.
+#: Programs/cmds.auto.h:1535
+msgid "tab key"
+msgstr ""
+
+#: Programs/config.c:647
+#: Programs/config.c:654
+msgid "text"
+msgstr ""
+
+#: Programs/brltty-pty.c:87
+msgid "the directory to change to"
+msgstr ""
+
+#: Programs/brltty-pty.c:94
+msgid "the home directory to use"
+msgstr ""
+
+#: Programs/msgtest.c:45
+msgid "the locale directory containing the translations"
+msgstr ""
+
+#: Programs/msgtest.c:52
+msgid "the locale in which to look up a translation"
+msgstr ""
+
+#: Programs/msgtest.c:59
+msgid "the name of the domain containing the translations"
+msgstr ""
+
+#: Programs/brltty-pty.c:80
+msgid "the name or number of the group to run as"
+msgstr ""
+
+#: Programs/brltty-pty.c:73
+msgid "the name or number of the user to run as"
+msgstr ""
+
+#. xgettext: This is the description of the PASSDOTS command.
+#: Programs/cmds.auto.h:1479
+msgid "type braille dots"
+msgstr ""
+
+#. xgettext: This is the description of the PASSCHAR command.
+#: Programs/cmds.auto.h:1470
+msgid "type unicode character"
+msgstr ""
+
+#: Programs/xbrlapi.c:1002
+msgid "unexpected block type"
+msgstr ""
+
+#: Programs/xbrlapi.c:892
+msgid "unexpected cmd"
+msgstr ""
+
+#: Programs/cmd_queue.c:158
+msgid "unhandled command"
+msgstr ""
+
+#: Programs/cmd.c:199
+msgid "unknown command"
+msgstr ""
+
+#: Programs/cmdline.c:960
+msgid "unknown configuration directive"
+msgstr ""
+
+#: Programs/config.c:786
+msgid "unknown log level or category"
+msgstr ""
+
+#: Programs/cmdline.c:657
+msgid "unknown option"
+msgstr ""
+
+#: Programs/config.c:328
+msgid "unknown screen content quality"
+msgstr ""
+
+#: Programs/parse.c:562
+msgid "unsupported parameter"
+msgstr ""
+
+#: Programs/spk.c:180
+msgid "volume"
+msgstr ""
+
+#. LRGB
+#: Programs/cmd_utils.c:166
+msgid "white"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1856
+msgid "working directory changed"
+msgstr ""
+
+#: Programs/brltty-pty.c:60
+msgid "write driver directives to standard error"
+msgstr ""
+
+#: Programs/msgtest.c:65
+msgid "write the translations using UTF-8"
+msgstr ""
+
+#: Programs/xbrlapi.c:939
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:926
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:902
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:481
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:484
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:463
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:317
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:792
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:810
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:608
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:472
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:468
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:440
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr ""
+
+#. Server refused our Xkb remapping request, probably the buggy version 21, ignore error
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: server refused our mapping request, could not synthesize key\n"
+msgstr ""
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:839
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:818
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr ""
+
+#. LRG
+#: Programs/cmd_utils.c:165
+msgid "yellow"
+msgstr ""
diff --git a/Messages/de.po b/Messages/de.po
new file mode 100644
index 0000000..c808c16
--- /dev/null
+++ b/Messages/de.po
@@ -0,0 +1,4083 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 4.5\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-09-07 15:29+0200\n"
+"PO-Revision-Date: 2021-09-13 12:42+0200\n"
+"Last-Translator: Angela Engel <angela.engel@gmx.at>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr "\"%s\" gestartet als \"%s\"\n"
+
+#. xgettext: This phrase describes the colour of a character on the screen.
+#. xgettext: %1$s is the (already translated) foreground colour.
+#. xgettext: %2$s is the (already translated) background colour.
+#: Programs/cmd_utils.c:169
+#, c-format
+msgid "%1$s on %2$s"
+msgstr "%1$s auf %2$s"
+
+#. xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+#. xgettext: (%u represents the number of hours)
+#: Programs/cmd_miscellaneous.c:89
+#, c-format
+msgid "%u o'clock"
+msgid_plural "%u o'clock"
+msgstr[0] ""
+msgstr[1] ""
+
+#. xgettext: This is a number (%u) of seconds (time units).
+#: Programs/cmd_miscellaneous.c:107
+#, c-format
+msgid "%u second"
+msgid_plural "%u seconds"
+msgstr[0] "%u Sekunde"
+msgstr[1] "%u Sekunden"
+
+#: Programs/menu_prefs.c:543 Programs/menu_prefs.c:544
+#: Programs/menu_prefs.c:546 Programs/menu_prefs.c:547
+#: Programs/menu_prefs.c:550 Programs/menu_prefs.c:551
+#: Programs/menu_prefs.c:552 Programs/menu_prefs.c:554
+#: Programs/menu_prefs.c:555 Programs/menu_prefs.c:561
+msgid "1 cell"
+msgstr "1 Zelle"
+
+#: Programs/menu_prefs.c:900
+msgid "1 second"
+msgstr "1 Sekunde"
+
+#: Programs/menu_prefs.c:962
+msgid "10 seconds"
+msgstr "10 Sekunden"
+
+#: Programs/menu_prefs.c:1277
+msgid "12 Hour"
+msgstr "12 Stunden"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:553
+msgid "2 cells"
+msgstr "2 Zellen"
+
+#: Programs/menu_prefs.c:901
+msgid "2 seconds"
+msgstr "2 Sekunden"
+
+#: Programs/menu_prefs.c:963
+msgid "20 seconds"
+msgstr "20 Sekunden"
+
+#: Programs/menu_prefs.c:1276
+msgid "24 Hour"
+msgstr "24 Stunden"
+
+#: Programs/menu_prefs.c:898
+msgid "250 milliseconds"
+msgstr "250 Millisekunden"
+
+#: Programs/menu_prefs.c:557 Programs/menu_prefs.c:558
+#: Programs/menu_prefs.c:559 Programs/menu_prefs.c:560
+msgid "3 cells"
+msgstr "3 Zellen"
+
+#: Programs/menu_prefs.c:964
+msgid "40 seconds"
+msgstr "40 Sekunden"
+
+#: Programs/menu_prefs.c:961
+msgid "5 seconds"
+msgstr "5 Sekunden"
+
+#: Programs/menu_prefs.c:899
+msgid "500 milliseconds"
+msgstr "500 Millisekunden"
+
+#: Programs/menu_prefs.c:733
+msgid "6-dot"
+msgstr "6-Punkt-Braille"
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr "8-bit Zeichensatz der verwendet werden soll."
+
+#: Programs/menu_prefs.c:732
+msgid "8-dot"
+msgstr "8-Punkt-Braille"
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr "<aus>"
+
+#: Programs/config.c:1566
+msgid "API Parameter"
+msgstr "API Parameter"
+
+#. xgettext: This is the description of the PASSAT command.
+#: Programs/cmds.auto.h:1471
+msgid "AT (set 2) keyboard scan code"
+msgstr "AT (set 2) Tastatur-Scancode"
+
+#. xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+#: Programs/midi.c:145
+msgid "Accordion"
+msgstr "Akkordeon"
+
+#. Bass
+#. xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+#: Programs/midi.c:180
+msgid "Acoustic Bass"
+msgstr "Akustischer Bass"
+
+#. Piano
+#. xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+#: Programs/midi.c:80
+msgid "Acoustic Grand Piano"
+msgstr "Akustischer Konzertflügel"
+
+#. Guitar
+#. xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+#: Programs/midi.c:155
+msgid "Acoustic Guitar (nylon)"
+msgstr "Akustische Gitarre (Nylonsaiten)"
+
+#. xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+#: Programs/midi.c:158
+msgid "Acoustic Guitar (steel)"
+msgstr "Akustische Gitarre (Stahlsaiten)"
+
+#: Programs/menu_prefs.c:1303
+msgid "After Time"
+msgstr "Nach Uhrzeit"
+
+#. xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+#: Programs/midi.c:434
+msgid "Agogo"
+msgstr "Agogo"
+
+#: Programs/menu_prefs.c:1491
+msgid "Alert"
+msgstr "Alarm"
+
+#: Programs/menu_prefs.c:1104
+msgid "Alert Dots"
+msgstr "Warnpunkte"
+
+#: Programs/menu_prefs.c:1109
+msgid "Alert Messages"
+msgstr "Warnmeldungen"
+
+#: Programs/menu_prefs.c:1049
+msgid "Alert Tunes"
+msgstr "Warntöne"
+
+#: Programs/menu_prefs.c:864 Programs/menu_prefs.c:1194
+msgid "All"
+msgstr "Alle"
+
+#: Programs/menu_prefs.c:555
+msgid "Alphabetic Cursor Coordinates"
+msgstr "Alphabetische Cursorkoordinaten"
+
+#: Programs/menu_prefs.c:554
+msgid "Alphabetic Window Coordinates"
+msgstr "Alphabetische Braillezeilenkoordinaten"
+
+#. xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+#: Programs/midi.c:283
+msgid "Alto Saxophone"
+msgstr "Alt Saxofon"
+
+#. xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+#: Programs/midi.c:474
+msgid "Applause"
+msgstr "Applaus"
+
+#: Programs/log.c:118
+msgid "Async Events"
+msgstr "Asynchrone Ereignisse"
+
+#: Programs/menu_prefs.c:807
+msgid "Attributes Blink Period"
+msgstr "Blinkdauer für Atribute"
+
+#: Programs/menu_prefs.c:815
+msgid "Attributes Percent Visible"
+msgstr "Anzeigedauer von Attributen in Prozent"
+
+#: Programs/config.c:1059 Programs/menu_prefs.c:1406
+msgid "Attributes Table"
+msgstr "Attributtabelle"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr "Automatische Einrastbeendung"
+
+#: Programs/menu_prefs.c:967
+msgid "Autorelease Time"
+msgstr "Zeit bis zur automatischen Einrastbeendung"
+
+#: Programs/menu_prefs.c:984
+msgid "Autorepeat Enabled"
+msgstr "Automatische Wiedderholung aktiviert"
+
+#: Programs/menu_prefs.c:990
+msgid "Autorepeat Interval"
+msgstr "Wiederholungsintervall"
+
+#: Programs/menu_prefs.c:997
+msgid "Autorepeat Panning"
+msgstr "Automatische Wiederholung für Seitwertsbewegungen"
+
+#: Programs/menu_prefs.c:1119
+msgid "Autospeak"
+msgstr "Automatisches Sprechen"
+
+#: Programs/menu_prefs.c:1116
+msgid "Autospeak Options"
+msgstr "Optionen für automatisches Sprechen"
+
+#: Programs/config.c:335
+msgid "Autospeak Threshold"
+msgstr ""
+
+#: Programs/config.c:2012
+msgid "BRLTTY stopped"
+msgstr "BRLTTY gestoppt"
+
+#. xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+#: Programs/midi.c:421
+msgid "Bag Pipe"
+msgstr "Sackpfeife"
+
+#. xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+#: Programs/midi.c:409
+msgid "Banjo"
+msgstr "Banjo"
+
+#. xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+#: Programs/midi.c:289
+msgid "Baritone Saxophone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #5.
+#: Programs/midi.c:37
+msgid "Bass"
+msgstr "Bass"
+
+#. xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+#: Programs/midi.c:298
+msgid "Bassoon"
+msgstr "Fagott"
+
+#: Programs/menu_prefs.c:1056
+msgid "Beeper"
+msgstr "PC-Piepser"
+
+#: Programs/menu_prefs.c:1302
+msgid "Before Time"
+msgstr "Vor Uhrzeit"
+
+#. xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+#: Programs/midi.c:465
+msgid "Bird Tweet"
+msgstr "Vogelzwitschern"
+
+#: Programs/menu_prefs.c:801
+msgid "Blinking Attributes"
+msgstr "Blinkende Attribute"
+
+#: Programs/menu_prefs.c:823
+msgid "Blinking Capitals"
+msgstr "Blinkende Großbuchstaben"
+
+#: Programs/menu_prefs.c:774
+msgid "Blinking Screen Cursor"
+msgstr "Blinkender Bildschirmcursor"
+
+#: Programs/menu_prefs.c:1248
+msgid "Blinking Speech Cursor"
+msgstr "Blinkender Sprachcursor"
+
+#: Programs/menu_prefs.c:665 Programs/menu_prefs.c:1362
+msgid "Block"
+msgstr "Block"
+
+#. xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+#: Programs/midi.c:317
+msgid "Blown Bottle"
+msgstr "Geblasene Flasche"
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr "Bluetooth E/A"
+
+#: Programs/config.c:1787
+msgid "Braille Device"
+msgstr "Braillegerät"
+
+#: Programs/config.c:1783
+msgid "Braille Driver"
+msgstr "Brailletreiber"
+
+#: Programs/log.c:148
+msgid "Braille Driver Events"
+msgstr "Brailletreiberereignisse"
+
+#: Programs/menu_prefs.c:752
+msgid "Braille Firmness"
+msgstr "Punktstärke"
+
+#: Programs/log.c:82
+msgid "Braille Key Events"
+msgstr "Tastenereignisse der Braillezeile"
+
+#: Programs/config.c:1786
+msgid "Braille Parameter"
+msgstr "Braille-Parameter"
+
+#: Programs/menu_prefs.c:698
+msgid "Braille Presentation"
+msgstr "Brailledarstellung"
+
+#: Programs/menu_prefs.c:1389
+msgid "Braille Tables"
+msgstr "Brailletabellen"
+
+#: Programs/menu_prefs.c:932
+msgid "Braille Typing"
+msgstr "Braille-Eingabe"
+
+#: Programs/menu_prefs.c:706
+msgid "Braille Variant"
+msgstr "Braillevariante"
+
+#: Programs/menu_prefs.c:885
+msgid "Braille Window Overlap"
+msgstr "Braillezeilenüberlappung"
+
+#: Programs/config.c:552
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr "Brailletreiber-Code: (%s, %s, oder einer von {%s})."
+
+#. xgettext: This is the name of MIDI musical instrument group #8.
+#: Programs/midi.c:46
+msgid "Brass"
+msgstr "Blechblasinstrument"
+
+#. xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+#: Programs/midi.c:270
+msgid "Brass Section"
+msgstr "Blechbläser Abteilung"
+
+#. xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+#: Programs/midi.c:459
+msgid "Breath Noise"
+msgstr "Atemgeräusch"
+
+#. xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+#: Programs/midi.c:83
+msgid "Bright Acoustic Piano"
+msgstr "Akustisches helles Klavier"
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr "BrlAPI Authorisierungs-/Berechtigungs-Schemen"
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr "BrlAPI Host und/oder Port mit dem verbunden werden soll"
+
+#: Programs/menu_prefs.c:1424
+msgid "Build Information"
+msgstr "Build Informationen"
+
+#: Programs/menu_prefs.c:725
+msgid "Capitalization Mode"
+msgstr "Großbuchstabenmodus"
+
+#: Programs/menu_prefs.c:828
+msgid "Capitals Blink Period"
+msgstr "Blinkdauer bei Großbuchstaben"
+
+#: Programs/menu_prefs.c:836
+msgid "Capitals Percent Visible"
+msgstr "Anzeigedauer von Großbuchstaben in Prozent"
+
+#: Programs/menu_prefs.c:1514
+msgid "Category Log Level"
+msgstr "Protokollierungsausführlichkeit für Kategorien"
+
+#. Chromatic Percussion
+#. xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+#: Programs/midi.c:105
+msgid "Celesta"
+msgstr "Celesta"
+
+#. xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+#: Programs/midi.c:211
+msgid "Cello"
+msgstr "Cello"
+
+#. xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+#: Programs/midi.c:242
+msgid "Choir Aahs"
+msgstr "Chor (Aaaa)"
+
+#. xgettext: This is the name of MIDI musical instrument group #2.
+#: Programs/midi.c:28
+msgid "Chromatic Percussion"
+msgstr "Chromatische Perkussion"
+
+#. xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+#: Programs/midi.c:139
+msgid "Church Organ"
+msgstr "Kirchenorgel"
+
+#. xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+#: Programs/midi.c:301
+msgid "Clarinet"
+msgstr "Klarinette"
+
+#. xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+#: Programs/midi.c:101
+msgid "Clavinet"
+msgstr ""
+
+#: Programs/menu.c:894
+msgid "Close"
+msgstr "Schließen"
+
+#: Programs/menu_prefs.c:1286
+msgid "Colon"
+msgstr "Doppelpunkt"
+
+#: Programs/menu_prefs.c:702
+msgid "Computer Braille"
+msgstr "Computerbraille"
+
+#: Programs/menu_prefs.c:736
+msgid "Computer Braille Cell Type"
+msgstr ""
+
+#: Programs/menu_prefs.c:1448
+msgid "Configuration Directory"
+msgstr "Konfigurationsverzeichnis"
+
+#: Programs/config.c:2955 Programs/menu_prefs.c:1453
+msgid "Configuration File"
+msgstr "Konfigurationsdatei"
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr "Konsolenglöckchen"
+
+#: Programs/menu_prefs.c:1035
+msgid "Console Bell Alert"
+msgstr "Konsolen-Tongenerator Hinweis"
+
+#. xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+#: Programs/midi.c:214
+msgid "Contrabass"
+msgstr "Kontrabass"
+
+#: Programs/menu_prefs.c:703
+msgid "Contracted Braille"
+msgstr "Braille-Kurzschrift"
+
+#: Programs/config.c:1027 Programs/menu_prefs.c:1399
+msgid "Contraction Table"
+msgstr "Kurzschrifttabelle"
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr "Kurzschrifttabelle."
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr "Kurzschriftverifikationstabelle"
+
+#: Programs/menu_prefs.c:1492
+msgid "Critical"
+msgstr "Kritisch"
+
+#: Programs/menu_prefs.c:546
+msgid "Cursor Column"
+msgstr "Cursorspalte"
+
+#: Programs/menu_prefs.c:545 Programs/menu_prefs.c:557
+msgid "Cursor Coordinates"
+msgstr "Cursorkoordinaten"
+
+#: Programs/log.c:100
+msgid "Cursor Routing"
+msgstr "Cursorrouting"
+
+#: Programs/menu_prefs.c:547
+msgid "Cursor Row"
+msgstr "Cursorzeile"
+
+#: Programs/log.c:94
+msgid "Cursor Tracking"
+msgstr "Cursorverfolgung"
+
+#: Programs/menu_prefs.c:904
+msgid "Cursor Tracking Delay"
+msgstr "Zeitverzögerung für Cursorverfolgung"
+
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:559
+msgid "Cursor and Window Column"
+msgstr "Cursor- und Braillezeilenspalte"
+
+#: Programs/menu_prefs.c:549 Programs/menu_prefs.c:560
+msgid "Cursor and Window Row"
+msgstr "Cursor- und Braillezeilenzeile"
+
+#: Programs/menu_prefs.c:1324
+msgid "Dash"
+msgstr "Bindestrich"
+
+#: Programs/menu_prefs.c:1317
+msgid "Date Format"
+msgstr "Datumsformat"
+
+#: Programs/menu_prefs.c:1306
+msgid "Date Position"
+msgstr "Datumsposition"
+
+#: Programs/menu_prefs.c:1329
+msgid "Date Separator"
+msgstr "Datum Trennzeichen"
+
+#: Programs/menu_prefs.c:1314
+msgid "Day Month Year"
+msgstr "Tag Monat Jahr"
+
+#: Programs/menu_prefs.c:1497
+msgid "Debug"
+msgstr "Debug"
+
+#: Programs/config.c:573
+msgid "Device for accessing braille display."
+msgstr "Pfad zur Schnittstelle für den Zugriff auf die Braillezeile."
+
+#: Programs/config.c:532
+msgid "Disable the application programming interface."
+msgstr "Deaktiviere die Programmierschnittstelle."
+
+#. xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+#: Programs/midi.c:173
+msgid "Distortion Guitar"
+msgstr "Verzerrte Gitarre"
+
+#: Programs/config.c:675
+msgid "Do not autospeak when braille is not being used."
+msgstr "Nicht automatisch sprechen wenn keine Braillezeile in Verwendung ist."
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr "Schreibe keinen Text auf die Braillezeile"
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr "Nicht auf das Unicode Basiszeichen zurückfallen."
+
+#: Programs/config.c:443
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr ""
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr "Fertig"
+
+#: Programs/menu_prefs.c:1287 Programs/menu_prefs.c:1326
+msgid "Dot"
+msgstr "Punkt"
+
+#: Programs/menu_prefs.c:942
+msgid "Dots via Unicode Braille"
+msgstr "Punkte via Unicode Braille"
+
+#. Organ
+#. xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+#: Programs/midi.c:130
+msgid "Drawbar Organ"
+msgstr "Orgel mit Zugstange"
+
+#: Programs/config.c:2976 Programs/menu_prefs.c:1473
+msgid "Drivers Directory"
+msgstr "Treiber-Verzeichnis"
+
+#. xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+#: Programs/midi.c:126
+msgid "Dulcimer"
+msgstr "Dulcimer"
+
+#: Programs/menu_prefs.c:879
+#, fuzzy
+msgid "Eager Sliding Braille Window"
+msgstr "Erweiterte Braillezeilenausschnittsüberlappung"
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "Tabelle editieren."
+
+#. xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+#: Programs/midi.c:183
+msgid "Electric Bass (finger)"
+msgstr "Elektrischer Bass (Finger)"
+
+# Elektronischer Bass (Pick)
+#. xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+#: Programs/midi.c:186
+msgid "Electric Bass (pick)"
+msgstr "Elektronischer Bass (Pick)"
+
+#. xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+#: Programs/midi.c:86
+msgid "Electric Grand Piano"
+msgstr "Elektrischer Konzertflügel"
+
+#. xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+#: Programs/midi.c:164
+msgid "Electric Guitar (clean)"
+msgstr "Elektrische Gitarre (rein)"
+
+#. xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+#: Programs/midi.c:161
+msgid "Electric Guitar (jazz)"
+msgstr "Elektrische Gitarre (Jazz)"
+
+#. xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+#: Programs/midi.c:167
+msgid "Electric Guitar (muted)"
+msgstr "Elektrische Gitarre (gedämpft)"
+
+#. xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+#: Programs/midi.c:92
+msgid "Electric Piano 1"
+msgstr "Elektrisches Klavier 1"
+
+#. xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+#: Programs/midi.c:95
+msgid "Electric Piano 2"
+msgstr "Elektrisches Klavier 2"
+
+#: Programs/menu_prefs.c:1490
+msgid "Emergency"
+msgstr "Notfall"
+
+#: Programs/menu_prefs.c:541
+msgid "End"
+msgstr "Ende"
+
+#: Programs/menu_prefs.c:865
+msgid "End of Line"
+msgstr "Zeilenende"
+
+#. xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+#: Programs/midi.c:295
+msgid "English Horn"
+msgstr "Englisches Horn"
+
+#: Programs/menu_prefs.c:1229
+msgid "Enqueue"
+msgstr "Anhängen"
+
+#. xgettext: This is the name of MIDI musical instrument group #7.
+#: Programs/midi.c:43
+msgid "Ensemble"
+msgstr "Ensemble"
+
+#: Programs/menu_prefs.c:1493
+msgid "Error"
+msgstr "Fehler"
+
+#. xgettext: This is the name of MIDI musical instrument group #14.
+#: Programs/midi.c:68
+msgid "Ethnic Instruments"
+msgstr "Ethnische Instrumente"
+
+#: Programs/menu_prefs.c:1032
+msgid "Event Alerts"
+msgstr "Warnungen bei Ereignissen"
+
+#: Programs/menu_prefs.c:713
+msgid "Expand Current Word"
+msgstr "Aktuelles Wort ausschreiben"
+
+#: Programs/config.c:487
+msgid "Explicit preference settings."
+msgstr "Explizite Einstellungen."
+
+#: Programs/menu_prefs.c:1059
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:1097
+msgid "FM Volume"
+msgstr "FM Lautstärke"
+
+#. Synth FM
+#. xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+#: Programs/midi.c:380
+msgid "FX 1 (rain)"
+msgstr "FX 1 (Regen)"
+
+#. xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+#: Programs/midi.c:383
+msgid "FX 2 (soundtrack)"
+msgstr "FX 2 (Soundtrack)"
+
+#. xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+#: Programs/midi.c:386
+msgid "FX 3 (crystal)"
+msgstr "FX 3 (Kristall)"
+
+#. xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+#: Programs/midi.c:389
+msgid "FX 4 (atmosphere)"
+msgstr "FX 4 (Atmosphäre)"
+
+#. xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+#: Programs/midi.c:392
+msgid "FX 5 (brightness)"
+msgstr "FX 5 (Helligkeit)"
+
+#. xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+#: Programs/midi.c:395
+msgid "FX 6 (goblins)"
+msgstr "FX 6 (Kobolde)"
+
+#. xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+#: Programs/midi.c:398
+msgid "FX 7 (echoes)"
+msgstr "FX 7 (Echos)"
+
+#. xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+#. xgettext: (sci-fi is a common short form for science fiction)
+#: Programs/midi.c:402
+msgid "FX 8 (sci-fi)"
+msgstr "FX 8 (Sci-Fi)"
+
+#. xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+#: Programs/midi.c:424
+msgid "Fiddle"
+msgstr "Fiedel"
+
+#. xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+#: Programs/midi.c:308
+msgid "Flute"
+msgstr "Flöte"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr "Sofortige Ausgabe erzwingen."
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr "Format einer Ausgabezeile."
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr "Format der Eingabedatei."
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr "Format der Ausgabedatei."
+
+#. xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+#: Programs/midi.c:267
+msgid "French Horn"
+msgstr "Waldhorn"
+
+#. xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+#: Programs/midi.c:189
+msgid "Fretless Bass"
+msgstr "bundloser Bass"
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr "Eingefroren"
+
+#: Programs/menu_prefs.c:556
+msgid "Generic"
+msgstr "Allgemein"
+
+#: Programs/log.c:64
+msgid "Generic Input"
+msgstr "allgemeine Eingaben"
+
+#. xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+#: Programs/midi.c:108
+msgid "Glockenspiel"
+msgstr "Glockenspiel"
+
+#. xgettext: This is the name of MIDI musical instrument group #4.
+#: Programs/midi.c:34
+msgid "Guitar"
+msgstr "Gitarre"
+
+#. Sound Effects
+#. xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+#: Programs/midi.c:456
+msgid "Guitar Fret Noise"
+msgstr "Gitarre, Geräusch an der Bundleiste"
+
+#. xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+#: Programs/midi.c:176
+msgid "Guitar Harmonics"
+msgstr "Gitarre, Obertöne"
+
+#. xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+#: Programs/midi.c:477
+msgid "Gunshot"
+msgstr "Gewehrschuss"
+
+#. xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+#: Programs/midi.c:148
+msgid "Harmonica"
+msgstr "Harmonika"
+
+#. xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+#: Programs/midi.c:98
+msgid "Harpsichord"
+msgstr "Harpsichord"
+
+#. xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+#: Programs/midi.c:471
+msgid "Helicopter"
+msgstr "Helikopter"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "Hilfe-Schirm"
+
+#: Programs/menu_prefs.c:748 Programs/menu_prefs.c:1013
+msgid "High"
+msgstr "Hoch"
+
+#: Programs/menu_prefs.c:921
+msgid "Highlight Braille Window Location"
+msgstr "Braillezeilenposition hervorheben"
+
+#. xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+#: Programs/midi.c:89
+msgid "Honkytonk Piano"
+msgstr "Honky-tonk Klavier"
+
+#: Programs/menu_prefs.c:1228
+msgid "Immediate"
+msgstr "Sofort"
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr "Inkompatible XKB Bibliothek\n"
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr "Inkompatible XKB Serverunterstützung\n"
+
+#: Programs/menu_prefs.c:1496
+msgid "Information"
+msgstr "Information"
+
+#: Programs/menu_prefs.c:956
+msgid "Input Options"
+msgstr "Eingabeoptionen"
+
+#: Programs/log.c:70
+msgid "Input Packets"
+msgstr "eingehende Datenpakete"
+
+#: Programs/config.c:418
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr "Installiere das Service %s und beende dann."
+
+#: Programs/menu_prefs.c:1500
+msgid "Internal Parameters"
+msgstr "Interne Parameter"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr "Java-Klasse nicht gefunden"
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr "Java-Methode nicht gefunden"
+
+#. xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+#: Programs/midi.c:418
+msgid "Kalimba"
+msgstr "Kalimba"
+
+#: Programs/config.c:1709
+msgid "Key Bindings"
+msgstr "Tastenbelegungen"
+
+#: Programs/config.c:1237
+msgid "Key Help"
+msgstr "Tastenhilfe"
+
+#: Programs/config.c:1714 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr "Tastentabelle"
+
+#: Programs/menu_prefs.c:935
+msgid "Keyboard Enabled"
+msgstr "Tastatur aktiviert"
+
+#: Programs/log.c:88
+msgid "Keyboard Key Events"
+msgstr "Tastaturereignisse"
+
+#: Programs/menu_prefs.c:1042
+msgid "Keyboard LED Alerts"
+msgstr "Tastaturer-LED Warnungen"
+
+#: Programs/config.c:1322 Programs/menu_prefs.c:1024
+msgid "Keyboard Table"
+msgstr "Tastaturtabelle"
+
+#. xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+#: Programs/midi.c:415
+msgid "Koto"
+msgstr "Koto"
+
+#: Programs/config.c:3112
+msgid "Language"
+msgstr "Sprache"
+
+#. Synth Lead
+#. xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+#: Programs/midi.c:330
+msgid "Lead 1 (square)"
+msgstr "Hauptstimme 1 (rechteckig)"
+
+#. xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+#: Programs/midi.c:333
+msgid "Lead 2 (sawtooth)"
+msgstr "Hauptstimme 2 (Sägezahn)"
+
+#. xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+#: Programs/midi.c:336
+msgid "Lead 3 (calliope)"
+msgstr "Hauptstimme 3 (sternförmig)"
+
+#. xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+#: Programs/midi.c:339
+msgid "Lead 4 (chiff)"
+msgstr "Hauptstimme 4 (klare Ansprache)"
+
+#. xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+#: Programs/midi.c:342
+msgid "Lead 5 (charang)"
+msgstr "Hauptstimme 5 (Charang)"
+
+#. xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+#: Programs/midi.c:345
+msgid "Lead 6 (voice)"
+msgstr "Hauptstimme 6 (Sprache)"
+
+#. xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+#: Programs/midi.c:348
+msgid "Lead 7 (fifths)"
+msgstr "Hauptstimme 7 (Quinten)"
+
+#. xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+#: Programs/midi.c:351
+msgid "Lead 8 (bass + lead)"
+msgstr "Hauptstimme 8 (Bass und Führung)"
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "Lernmodus"
+
+#: Programs/menu_prefs.c:1341
+msgid "Left"
+msgstr "Links"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr "Tastennamen auflisten."
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr "Tastentabelle im Hilfe-Schirm Format auflisten."
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr "Tastentabelle im reStructuredText-Format auflisten."
+
+#: Programs/menu_prefs.c:1483
+msgid "Locale Directory"
+msgstr "Lokalisierungsverzeichnis"
+
+#: Programs/menu_prefs.c:1519
+msgid "Log Categories"
+msgstr "Protokollierungskategorien"
+
+#: Programs/config.c:890
+msgid "Log Level"
+msgstr "Protokollierungsausführlichkeit"
+
+#: Programs/menu_prefs.c:1580
+msgid "Log Messages"
+msgstr "Protokollierungsmeldungen"
+
+#: Programs/config.c:773
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr "Protokolliere die Version des Kerns, der API, der integrierten Treiber und beende dann."
+
+#: Programs/config.c:738
+msgid "Log to standard error rather than to the system log."
+msgstr "Protokolliere via Standardfehlerausgabe anstatt System Log."
+
+#: Programs/config.c:752
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)."
+msgstr ""
+
+#: Programs/menu_prefs.c:978
+msgid "Long Press Time"
+msgstr "Zeitverzögerung"
+
+#: Programs/menu_prefs.c:746 Programs/menu_prefs.c:1011
+msgid "Low"
+msgstr "Niedrig"
+
+#: Programs/menu_prefs.c:666
+msgid "Lower Left Dot"
+msgstr "Unterer linker Punkt"
+
+#: Programs/menu_prefs.c:667
+msgid "Lower Right Dot"
+msgstr "Unterer rechter Punkt"
+
+#: Programs/menu_prefs.c:1058
+msgid "MIDI"
+msgstr "MIDI"
+
+#: Programs/config.c:722
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr "MIDI (Musical Instrument Digital Interface) Geräts-Angabe."
+
+#: Programs/menu_prefs.c:1088
+msgid "MIDI Instrument"
+msgstr "MIDI Instrument"
+
+#: Programs/menu_prefs.c:1078
+msgid "MIDI Volume"
+msgstr "MIDI Lautstärke"
+
+#: Programs/menu_prefs.c:1443
+msgid "Mailing List"
+msgstr "Mailing-Liste"
+
+#. xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+#: Programs/midi.c:117
+msgid "Marimba"
+msgstr "Marimba"
+
+#: Programs/menu_prefs.c:749 Programs/menu_prefs.c:1014
+msgid "Maximum"
+msgstr "Maximal"
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr "Maximale Länge einer Ausgabezeile."
+
+#: Programs/menu_prefs.c:747 Programs/menu_prefs.c:1012
+msgid "Medium"
+msgstr "Mittel"
+
+#. xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+#: Programs/midi.c:446
+msgid "Melodic Tom"
+msgstr "Melodische Trommel"
+
+#: Programs/menu_prefs.c:679
+msgid "Menu Options"
+msgstr "Menüoptionen"
+
+#: Programs/config.c:731
+msgid "Message hold timeout (in 10ms units)."
+msgstr "Meldungsanzeigedauer (in 10ms Einheiten)."
+
+#: Programs/config.c:893
+#, fuzzy
+msgid "Messages Directory"
+msgstr "Tabellenverzeichnis"
+
+#: Programs/config.c:892
+msgid "Messages Domain"
+msgstr ""
+
+#: Programs/config.c:891
+msgid "Messages Locale"
+msgstr ""
+
+#: Programs/menu_prefs.c:745 Programs/menu_prefs.c:1010
+msgid "Minimum"
+msgstr "Minimal"
+
+#: Programs/config.c:682
+#, c-format
+msgid "Minimum screen content quality to autospeak (one of {%s})."
+msgstr ""
+
+#: Programs/menu_prefs.c:1313
+msgid "Month Day Year"
+msgstr "Monat Tag Jahr"
+
+#. xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+#: Programs/midi.c:111
+msgid "Music Box"
+msgstr "Musikbox"
+
+#: Programs/menu_prefs.c:1058
+msgid "Musical Instrument Digital Interface"
+msgstr "Musical Instrument Digital Interface"
+
+#. xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+#: Programs/midi.c:264
+msgid "Muted Trumpet"
+msgstr "Gedämpfte Trompete"
+
+#: Programs/config.c:623
+msgid "Name of or path to attributes table."
+msgstr "Name der oder Pfad zur Attributtabelle."
+
+#: Programs/config.c:615
+msgid "Name of or path to contraction table."
+msgstr "Name der oder Pfad zur Kurzschrifttabelle."
+
+#: Programs/config.c:479
+msgid "Name of or path to default preferences file."
+msgstr "Name der oder Pfad zur Standardeinstellungsdatei."
+
+#: Programs/config.c:632
+msgid "Name of or path to keyboard table."
+msgstr "Name der oder Pfad zur Tastentabelle."
+
+#: Programs/config.c:668
+msgid "Name of or path to speech input object."
+msgstr "Name des oder Pfad zum Spracheingabeobjekts."
+
+#: Programs/config.c:605
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr "Name der oder Pfad zur Texttabelle (oder %s)."
+
+#: Programs/menu_prefs.c:845
+msgid "Navigation Options"
+msgstr "Navigationsoptionen"
+
+#: Programs/menu.c:523
+msgid "No"
+msgstr "Nein"
+
+#: Programs/menu_prefs.c:720
+msgid "No Capitalization"
+msgstr "Keine Großschreibung"
+
+#: Programs/menu_prefs.c:897 Programs/menu_prefs.c:1192
+#: Programs/menu_prefs.c:1205 Programs/menu_prefs.c:1218
+#: Programs/menu_prefs.c:1301 Programs/menu_prefs.c:1340
+#: Programs/menu_prefs.c:1360
+msgid "None"
+msgstr "Keine"
+
+#: Programs/menu_prefs.c:1495
+msgid "Notice"
+msgstr "Hinweis"
+
+#. xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+#: Programs/midi.c:292
+msgid "Oboe"
+msgstr "Oboe"
+
+#. xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+#: Programs/midi.c:326
+msgid "Ocarina"
+msgstr "Okarina"
+
+#: Programs/menu_prefs.c:960
+msgid "Off"
+msgstr "Aus"
+
+#: Programs/config.c:1800
+msgid "Old Preferences File"
+msgstr "Alte Einstellungsdatei"
+
+#: Programs/menu_prefs.c:973
+msgid "On First Release"
+msgstr "Beim ersten Loslassen"
+
+#. xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+#: Programs/midi.c:251
+msgid "Orchestra Hit"
+msgstr "Orchester Schlagzeug"
+
+#. xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+#: Programs/midi.c:223
+msgid "Orchestral Harp"
+msgstr "Orchesterharfe"
+
+#. xgettext: This is the name of MIDI musical instrument group #3.
+#: Programs/midi.c:31
+msgid "Organ"
+msgstr "Orgel"
+
+#: Programs/log.c:76
+msgid "Output Packets"
+msgstr "ausgehende Datenpakete"
+
+#. xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+#: Programs/midi.c:170
+msgid "Overdriven Guitar"
+msgstr "Übersteuerte Gitarre"
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr "PC-Modus"
+
+#: Programs/menu_prefs.c:1057
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:712
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr ""
+
+#: Programs/menu_prefs.c:1070
+msgid "PCM Volume"
+msgstr "PCM Lautstärke"
+
+#. xgettext: This is the description of the PASSPS2 command.
+#: Programs/cmds.auto.h:1487
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr "PS/2 (set 3) Tastatur-Scancode"
+
+#: Programs/menu_prefs.c:1433
+msgid "Package Revision"
+msgstr "Paketrevision"
+
+#: Programs/menu_prefs.c:1428
+msgid "Package Version"
+msgstr "Paket Version"
+
+#. Synth Pad
+#. xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+#: Programs/midi.c:355
+msgid "Pad 1 (new age)"
+msgstr "Basisstimme 1 (New Age)"
+
+#. xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+#: Programs/midi.c:358
+msgid "Pad 2 (warm)"
+msgstr "Basisstimme 2 (warm)"
+
+#. xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+#: Programs/midi.c:361
+msgid "Pad 3 (polysynth)"
+msgstr "Basisstimme 3 (Mehrfachsynthesizer)"
+
+#. xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+#: Programs/midi.c:364
+msgid "Pad 4 (choir)"
+msgstr "Basisstimme 4 (Chor)"
+
+#. xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+#: Programs/midi.c:367
+msgid "Pad 5 (bowed)"
+msgstr "Basisstimme 5 (Streicher)"
+
+#. xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+#: Programs/midi.c:370
+msgid "Pad 6 (metallic)"
+msgstr "Basisstimme 6 (metallisch)"
+
+#. xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+#: Programs/midi.c:373
+msgid "Pad 7 (halo)"
+msgstr "Basisstimme 7 (Lichtschleier)"
+
+#. xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+#: Programs/midi.c:376
+msgid "Pad 8 (sweep)"
+msgstr "Basisstimme 8 (Sweep)"
+
+#. xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+#: Programs/midi.c:314
+msgid "Pan Flute"
+msgstr "Panflöte"
+
+#: Programs/config.c:541
+msgid "Parameters for the application programming interface."
+msgstr "Parameter für die Programmierschnittstelle."
+
+#: Programs/config.c:563
+msgid "Parameters for the braille driver."
+msgstr "Parameter für den Brailletreiber."
+
+#: Programs/config.c:436
+#, fuzzy
+msgid "Parameters for the privilege establishment stage."
+msgstr "Parameter für den Brailletreiber."
+
+#: Programs/config.c:703
+msgid "Parameters for the screen driver."
+msgstr "Parameter für den Bildschirmtreiber."
+
+#: Programs/config.c:660
+msgid "Parameters for the speech driver."
+msgstr "Parameter für den Sprachausgabentreiber."
+
+#: Programs/config.c:470
+msgid "Path to default settings file."
+msgstr "Pfad zur Standardkonfigurationsdatei."
+
+#: Programs/config.c:524
+msgid "Path to directory containing drivers."
+msgstr "Pfad zum Verzeichnis das Treiber enthält."
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:595
+msgid "Path to directory containing tables."
+msgstr "Pfad zum Tabellen-Verzeichnis."
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr "Pfad zum einem Verzeichnis das Texttabellen enthält."
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr "Pfad zum Verzeichnis aus dem Treiber geladen werden."
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr "Pfad zum Verzeichnis für Texttabellen."
+
+#: Programs/brltest.c:78 Programs/config.c:514
+msgid "Path to directory which can be written to."
+msgstr "Pfad zum Verzeichnis in dem Dateien erstellt werden können."
+
+#: Programs/config.c:504
+msgid "Path to directory which contains files that can be updated."
+msgstr "Pfad zum Verzeichnis in dem Dateien modifiziert werden können."
+
+#: Programs/config.c:404
+msgid "Path to directory which contains message localizations."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr "Pfad der Brailletabelle für den Eingabetext."
+
+#: Programs/config.c:761
+msgid "Path to log file."
+msgstr "Pfad zur Protokolldatei."
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr "Pfad der Brailletabelle für den Ausgabetext."
+
+#: Programs/config.c:460
+msgid "Path to process identifier file."
+msgstr "Pfad zur Prozessidentifikationsdatei."
+
+#: Programs/config.c:494
+msgid "Patterns that match command prompts."
+msgstr "Muster die den Kommandoprompt beschreiben."
+
+#. xgettext: This is the name of MIDI musical instrument group #15.
+#: Programs/midi.c:71
+msgid "Percussive Instruments"
+msgstr "Perkussive Instrumente"
+
+#. xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+#: Programs/midi.c:133
+msgid "Percussive Organ"
+msgstr "Perkussive Orgel"
+
+#. xgettext: This is the name of MIDI musical instrument group #1.
+#: Programs/midi.c:25
+msgid "Piano"
+msgstr "Klavier"
+
+#. Pipe
+#. xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+#: Programs/midi.c:305
+msgid "Piccolo"
+msgstr "Piccoloflöte"
+
+#. xgettext: This is the name of MIDI musical instrument group #10.
+#: Programs/midi.c:52
+msgid "Pipe"
+msgstr "Pfeife"
+
+#. xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+#: Programs/midi.c:220
+msgid "Pizzicato Strings"
+msgstr "Pizzikato-Saiten"
+
+#: Drivers/Braille/Baum/braille.c:1158
+msgid "Powerdown"
+msgstr "Abschaltung"
+
+#: Programs/config.c:2956 Programs/menu_prefs.c:1463
+msgid "Preferences File"
+msgstr "Einstellungsdatei"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr "Einstellungsmenü"
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr "Zeige eine Benutzungszusammenfassung (alle Optionen) und beende dann."
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr "Zeige eine Benutzungszusammenfassung (nur häufig verwendete Optionen) und beende dann."
+
+#: Programs/config.c:844
+#, fuzzy
+msgid "Privilege Parameter"
+msgstr "Braille-Parameter"
+
+#: Programs/menu_prefs.c:1414
+msgid "Profiles"
+msgstr "Profile"
+
+#: Programs/config.c:640
+msgid "Properties of eligible keyboards."
+msgstr "Eigenschaften von geeigneten Tastaturen."
+
+#: Programs/menu_prefs.c:950
+msgid "Quick Space"
+msgstr "Doppelhub"
+
+#: Programs/menu_prefs.c:1209
+msgid "Raise Pitch"
+msgstr "Erhöhe Tonhöhe"
+
+#: Programs/config.c:395
+msgid "Recognize environment variables."
+msgstr "Verwende Umgebungsvariablen."
+
+#. xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+#: Programs/midi.c:311
+msgid "Recorder"
+msgstr "Blockflöte"
+
+#. xgettext: This is the name of MIDI musical instrument group #9.
+#: Programs/midi.c:49
+msgid "Reed"
+msgstr "Rohrblatt"
+
+#. xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+#: Programs/midi.c:142
+msgid "Reed Organ"
+msgstr "Harmonium"
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr "Eingabe neu formatieren."
+
+#: Programs/config.c:585
+msgid "Release braille device when screen or window is unreadable."
+msgstr "Gib die Braillezeile frei wenn der Bildschirm oder das Fenster nicht lesbar ist."
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr "Verbleibe als Fordergrundprozess"
+
+#: Programs/config.c:411
+msgid "Remain a foreground process."
+msgstr "Verbleibe als Fordergrundprozess."
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr "Entferne Punkt sieben und acht."
+
+#: Programs/config.c:426
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr "Entferne das Service %s und beende dann."
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr "Probleme mit der Tastatur-Befehlstabelle melden."
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr "Jene Zeichen der aktuellen Bildschirmschriftart melden, welche nicht im aktuellen Braillezeichensatz definiert sind."
+
+#: Programs/menu_prefs.c:866
+msgid "Rest of Line"
+msgstr "Rest der Zeile"
+
+#: Programs/menu_prefs.c:1562
+msgid "Restart Braille Driver"
+msgstr "Brailletreiber neu starten"
+
+#: Programs/menu_prefs.c:1574
+msgid "Restart Screen Driver"
+msgstr "Bildschirmtreiber neu starten"
+
+#: Programs/menu_prefs.c:1568
+msgid "Restart Speech Driver"
+msgstr "Sprachausgabentreiber neu starten"
+
+#. xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+#: Programs/midi.c:452
+msgid "Reverse Cymbal"
+msgstr "Becken Rückseite"
+
+#: Programs/menu_prefs.c:1342
+msgid "Right"
+msgstr "Rechts"
+
+#. xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+#: Programs/midi.c:136
+msgid "Rock Organ"
+msgstr "Rock-Orgel"
+
+#: Programs/menu_prefs.c:674
+msgid "Save on Exit"
+msgstr "Beim Verlassen speichern"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1208
+msgid "Say Cap"
+msgstr "Sprich Groß"
+
+#: Programs/menu_prefs.c:1232
+msgid "Say Line Mode"
+msgstr "Zeilen-Vorlesemodus"
+
+#: Programs/menu_prefs.c:1219
+msgid "Say Space"
+msgstr "Sprich Leerzeichen"
+
+#: Programs/menu_prefs.c:780
+msgid "Screen Cursor Blink Period"
+msgstr "Blinkdauer des Bildschirmcursors"
+
+#: Programs/menu_prefs.c:788
+msgid "Screen Cursor Percent Visible"
+msgstr "Anzeigedauer des Bildschirmcursors in Prozent"
+
+#: Programs/menu_prefs.c:768
+msgid "Screen Cursor Style"
+msgstr "Bildschirmcursordarstellung"
+
+#: Programs/config.c:2453
+msgid "Screen Driver"
+msgstr "Bildschirmtreiber"
+
+#: Programs/log.c:160
+msgid "Screen Driver Events"
+msgstr "Bildschirmtreiberereignisse"
+
+#: Programs/menu_prefs.c:550
+msgid "Screen Number"
+msgstr "Bildschirmnummer"
+
+#: Programs/config.c:2459
+msgid "Screen Parameter"
+msgstr "Bildschirm-Parameter"
+
+#: Programs/config.c:693
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr "Bildschirmtreiber Kurzbezeichnung (%s, %s, oder eine von {%s})."
+
+#: Programs/menu_prefs.c:891
+msgid "Scroll-aware Cursor Navigation"
+msgstr "Scrollsensitive Cursornavigation"
+
+#. xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+#: Programs/midi.c:462
+msgid "Seashore"
+msgstr "Brandung"
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr "Serielle E/A"
+
+#: Programs/log.c:124
+msgid "Server Events"
+msgstr "Server Ereignisse"
+
+#. xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+#: Programs/midi.c:320
+msgid "Shakuhachi"
+msgstr "Shakuhachi"
+
+#. xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+#: Programs/midi.c:412
+msgid "Shamisen"
+msgstr "Shamisen"
+
+#. xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+#: Programs/midi.c:427
+msgid "Shanai"
+msgstr "Shanai"
+
+#: Programs/menu_prefs.c:687
+msgid "Show Advanced Submenus"
+msgstr "Zeige erweiterte Untermenüs"
+
+#: Programs/menu_prefs.c:692
+msgid "Show All Items"
+msgstr "Zeige alle Elemente"
+
+#: Programs/menu_prefs.c:796
+msgid "Show Attributes"
+msgstr "Zeige Attribute"
+
+#: Programs/menu_prefs.c:763
+msgid "Show Screen Cursor"
+msgstr "Zeige Bildschirmcursor"
+
+#: Programs/menu_prefs.c:1295
+msgid "Show Seconds"
+msgstr "Sekunden anzeigen"
+
+#: Programs/menu_prefs.c:1237
+msgid "Show Speech Cursor"
+msgstr "Zeige Sprachcursor"
+
+#: Programs/menu_prefs.c:682
+msgid "Show Submenu Sizes"
+msgstr "Zeige Größen von Untermenüs"
+
+#. Ethnic Instruments
+#. xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+#: Programs/midi.c:406
+msgid "Sitar"
+msgstr "Sitar"
+
+#: Programs/menu_prefs.c:858
+msgid "Skip Blank Braille Windows"
+msgstr "Leere Braillezeileninhalte überspringen"
+
+#: Programs/menu_prefs.c:852
+msgid "Skip Identical Lines"
+msgstr "Identische Zeilen überspringen"
+
+#: Programs/menu_prefs.c:869
+#, fuzzy
+msgid "Skip Which Blank Braille Windows"
+msgstr "Zu überspringende leere Braille-Inhalte"
+
+#. xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+#: Programs/midi.c:192
+msgid "Slap Bass 1"
+msgstr "Geschlagener Bass 1"
+
+#. xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+#: Programs/midi.c:195
+msgid "Slap Bass 2"
+msgstr "Geschlagener Bass 2"
+
+#: Programs/menu_prefs.c:1325
+msgid "Slash"
+msgstr "Schrägstrich"
+
+#: Programs/menu_prefs.c:874
+msgid "Sliding Braille Window"
+msgstr "überlappende Braillezeilenausschnitte"
+
+#: Programs/menu_prefs.c:1193
+msgid "Some"
+msgstr "Einige"
+
+#. Reed
+#. xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+#: Programs/midi.c:280
+msgid "Soprano Saxophone"
+msgstr ""
+
+#. xgettext: This is the name of MIDI musical instrument group #16.
+#: Programs/midi.c:74
+msgid "Sound Effects"
+msgstr "Soundeffekte"
+
+#: Programs/menu_prefs.c:561 Programs/menu_prefs.c:1361
+msgid "Space"
+msgstr "Leer"
+
+#: Programs/menu_prefs.c:1154
+msgid "Speak Completed Words"
+msgstr "Sprich vervollständigte Wörter"
+
+#: Programs/menu_prefs.c:1142
+msgid "Speak Deleted Characters"
+msgstr "Sprich gelöschte Zeichen"
+
+#: Programs/menu_prefs.c:1136
+msgid "Speak Inserted Characters"
+msgstr "Sprich eingefügte Zeichen"
+
+#: Programs/menu_prefs.c:1160
+msgid "Speak Line Indent"
+msgstr "Zeilen-Einrückung sprechen"
+
+#: Programs/menu_prefs.c:1148
+msgid "Speak Replaced Characters"
+msgstr "Sprich ersetzte Zeichen"
+
+#: Programs/menu_prefs.c:1130
+msgid "Speak Selected Character"
+msgstr "Sprich hervorgehobenes Zeichen"
+
+#: Programs/menu_prefs.c:1124
+msgid "Speak Selected Line"
+msgstr "Sprich hervorgehobene Zeile"
+
+#: Programs/menu_prefs.c:1254
+msgid "Speech Cursor Blink Period"
+msgstr "Blinkdauer des Sprachcursors"
+
+#: Programs/menu_prefs.c:1262
+msgid "Speech Cursor Percent Visible"
+msgstr "Anzeigedauer des Sprachcursors in Prozent"
+
+#: Programs/menu_prefs.c:1242
+msgid "Speech Cursor Style"
+msgstr "Sprachcursordarstellung"
+
+#: Programs/config.c:2228
+msgid "Speech Driver"
+msgstr "Sprachausgabentreiber"
+
+#: Programs/log.c:154
+msgid "Speech Driver Events"
+msgstr "Ereignisse des Sprachausgabentreibers"
+
+#: Programs/log.c:112
+msgid "Speech Events"
+msgstr "Sprachausgabenereignisse"
+
+#. Create the file system object for speech input.
+#: Programs/config.c:3023
+msgid "Speech Input"
+msgstr "Spracheingabe"
+
+#: Programs/menu_prefs.c:1167
+msgid "Speech Options"
+msgstr "Sprachausgabenoptionen"
+
+#: Programs/config.c:2231
+msgid "Speech Parameter"
+msgstr "Sprachausgaben-Parameter"
+
+#: Programs/menu_prefs.c:1184
+msgid "Speech Pitch"
+msgstr "Tonhöhe"
+
+#: Programs/menu_prefs.c:1197
+msgid "Speech Punctuation"
+msgstr "Satzzeichenansage"
+
+#: Programs/menu_prefs.c:1177
+msgid "Speech Rate"
+msgstr "Sprechgeschwindigkeit"
+
+#: Programs/menu_prefs.c:1212
+msgid "Speech Uppercase Indicator"
+msgstr "Großbuchstabenansage"
+
+#: Programs/menu_prefs.c:1170
+msgid "Speech Volume"
+msgstr "Sprachlautstärke"
+
+#: Programs/menu_prefs.c:1222
+msgid "Speech Whitespace Indicator"
+msgstr "Leerraumansage"
+
+#: Programs/config.c:650
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr "Sprachausgabentreiber Kurzbezeichnung (%s, %s, oder eine von {%s})."
+
+#: Programs/menu_prefs.c:1509
+msgid "Standard Error Log Level"
+msgstr "Protokollierungsausführlichkeit für die Standard-Fehlerausgabe"
+
+#: Programs/menu_prefs.c:926
+msgid "Start Selection with Routing Key"
+msgstr "Auswahl durch Cursorrouting-Taste starten"
+
+#: Programs/menu_prefs.c:551
+msgid "State Dots"
+msgstr "Statuspunkte"
+
+#: Programs/menu_prefs.c:552
+msgid "State Letter"
+msgstr "Statusbuchstabe"
+
+#: Programs/menu_prefs.c:1336
+msgid "Status Cells"
+msgstr "Statuszellen"
+
+#: Programs/menu_prefs.c:1352
+msgid "Status Count"
+msgstr "Gesamtanzahl der Statuszellen"
+
+#: Programs/menu_prefs.c:565
+msgid "Status Field"
+msgstr "Statusfeld"
+
+#: Programs/menu_prefs.c:1345
+msgid "Status Position"
+msgstr "Position der Statuszellen"
+
+#: Programs/menu_prefs.c:1367
+msgid "Status Separator"
+msgstr "Status-Trennzeichen"
+
+#: Programs/menu_prefs.c:1363
+msgid "Status Side"
+msgstr "Statusseitig"
+
+#. xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+#: Programs/midi.c:437
+msgid "Steel Drums"
+msgstr "Steel Drums"
+
+#: Programs/config.c:450
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr "Stoppe eine laufende Instanz von %s und beende dann."
+
+#. Ensemble
+#. xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+#: Programs/midi.c:230
+msgid "String Ensemble 1"
+msgstr "Saiten Ensemble 1"
+
+#. xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+#: Programs/midi.c:233
+msgid "String Ensemble 2"
+msgstr "Saiten Ensemble 2"
+
+#. xgettext: This is the name of MIDI musical instrument group #6.
+#: Programs/midi.c:40
+msgid "Strings"
+msgstr "Saiteninstrumente"
+
+#: Programs/menu_prefs.c:722
+msgid "Superimpose Dot 7"
+msgstr "Punkt 7 hinzufügen"
+
+#: Programs/config.c:744
+msgid "Suppress start-up messages."
+msgstr "Unterdrücke Meldungen zum Startvorgang."
+
+#. xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+#: Programs/midi.c:198
+msgid "Synth Bass 1"
+msgstr "Synth Bass 1"
+
+#. xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+#: Programs/midi.c:201
+msgid "Synth Bass 2"
+msgstr "Synth Bass 2"
+
+#. xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+#: Programs/midi.c:449
+msgid "Synth Drum"
+msgstr "Synth Trommel"
+
+#. xgettext: This is the name of MIDI musical instrument group #13.
+#. xgettext: (synth is a common short form for synthesizer)
+#. xgettext: (FM is the acronym for Frequency Modulation)
+#: Programs/midi.c:65
+msgid "Synth FM"
+msgstr "Synth FM"
+
+#. xgettext: This is the name of MIDI musical instrument group #11.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:56
+msgid "Synth Lead"
+msgstr "Synth Hauptstimme"
+
+#. xgettext: This is the name of MIDI musical instrument group #12.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:60
+msgid "Synth Pad"
+msgstr "Synth Basisstimme"
+
+#. xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+#: Programs/midi.c:248
+msgid "Synth Voice"
+msgstr "Synth Stimme"
+
+#. xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+#: Programs/midi.c:273
+msgid "SynthBrass 1"
+msgstr "Synth Blechbläser 1"
+
+#. xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+#: Programs/midi.c:276
+msgid "SynthBrass 2"
+msgstr "Synth Blechbläser 2"
+
+#. xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+#: Programs/midi.c:236
+msgid "SynthStrings 1"
+msgstr "Synth Saiteninstrumente 1"
+
+#. xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+#: Programs/midi.c:239
+msgid "SynthStrings 2"
+msgstr "Synth Saiteninstrumente 2"
+
+#: Programs/menu_prefs.c:1504
+msgid "System Log Level"
+msgstr "System-Protokollierungsausführlichkeit"
+
+#: Programs/config.c:2977 Programs/menu_prefs.c:1478
+msgid "Tables Directory"
+msgstr "Tabellenverzeichnis"
+
+#. xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+#: Programs/midi.c:443
+msgid "Taiko Drum"
+msgstr "Taiko-Trommel"
+
+#. xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+#: Programs/midi.c:151
+msgid "Tango Accordion"
+msgstr "Bandoneon"
+
+#. xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+#: Programs/midi.c:468
+msgid "Telephone Ring"
+msgstr "Telefonklingeln"
+
+#. xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+#: Programs/midi.c:286
+msgid "Tenor Saxophone"
+msgstr ""
+
+#: Programs/menu_prefs.c:760
+msgid "Text Indicators"
+msgstr "Darstellungsform"
+
+#: Programs/menu_prefs.c:1364
+msgid "Text Side"
+msgstr "Textseitig"
+
+#: Programs/config.c:1003 Programs/menu_prefs.c:1392
+msgid "Text Table"
+msgstr "Texttabelle"
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr "Texttabelle."
+
+#: Programs/config.c:381
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr "Text der angezeigt und/oder gesprochen werden soll, wenn der Braille- und/oder Sprachausgabentreiber gestartet wird."
+
+#: Programs/config.c:388
+msgid "The text to be shown when the braille driver stops."
+msgstr "Text der gezeigt werden soll wenn der Brailletreiber angehalten wird."
+
+#: Programs/menu_prefs.c:553
+msgid "Time"
+msgstr "Uhrzeit"
+
+#: Programs/menu_prefs.c:1280
+msgid "Time Format"
+msgstr "Zeitformat"
+
+#: Programs/menu_prefs.c:1272
+msgid "Time Presentation"
+msgstr "Zeitanzeige"
+
+#: Programs/menu_prefs.c:1290
+msgid "Time Separator"
+msgstr "Uhrzeit-Trennzeichen"
+
+#. xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+#: Programs/midi.c:226
+msgid "Timpani"
+msgstr "Pauken"
+
+#. Percussive Instruments
+#. xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+#: Programs/midi.c:431
+msgid "Tinkle Bell"
+msgstr "Glöckchen"
+
+#: Programs/menu_prefs.c:1558
+msgid "Tools"
+msgstr "Werkzeuge"
+
+#: Programs/menu_prefs.c:1003
+msgid "Touch Navigation"
+msgstr "Berührungsbasierte Navigation"
+
+#: Programs/menu_prefs.c:1017
+msgid "Touch Sensitivity"
+msgstr "Berührungsempfindlichkeit"
+
+#: Programs/menu_prefs.c:915
+msgid "Track Screen Pointer"
+msgstr "Mauszeigerverfolgung"
+
+#: Programs/menu_prefs.c:909
+msgid "Track Screen Scroll"
+msgstr "Bildschirmscrollverfolgung"
+
+#: Programs/menu_prefs.c:941
+msgid "Translated via Text Table"
+msgstr "Durch Texttabelle übersetzt"
+
+#. xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+#: Programs/midi.c:217
+msgid "Tremolo Strings"
+msgstr "Tremolo Saiten"
+
+#. xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+#: Programs/midi.c:258
+msgid "Trombone"
+msgstr "Posaune"
+
+#. Brass
+#. xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+#: Programs/midi.c:255
+msgid "Trumpet"
+msgstr "Trompete"
+
+#. xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+#: Programs/midi.c:261
+msgid "Tuba"
+msgstr "Tuba"
+
+#. xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+#: Programs/midi.c:123
+msgid "Tubular Bells"
+msgstr "Röhrenglocken"
+
+#: Programs/menu_prefs.c:1062
+msgid "Tune Device"
+msgstr "Tongeber"
+
+#: Programs/menu_prefs.c:945
+msgid "Typing Mode"
+msgstr "Eingabe-Modus"
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr "USB E/A"
+
+#: Programs/menu_prefs.c:664
+msgid "Underline"
+msgstr "Unterlegung"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr "Aufgetaut"
+
+#: Programs/config.c:2974 Programs/menu_prefs.c:1458
+msgid "Updatable Directory"
+msgstr "Modifizierbares Verzeichnis"
+
+#: Programs/log.c:106
+msgid "Update Events"
+msgstr "Update Ereignisse"
+
+#: Programs/options.c:197
+msgid "Usage"
+msgstr "Benutzung"
+
+#: Programs/menu_prefs.c:721
+msgid "Use Capital Sign"
+msgstr "Verwende Großschreibungszeichen"
+
+#. xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+#: Programs/midi.c:114
+msgid "Vibraphone"
+msgstr "Vibraphon"
+
+#. xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+#: Programs/midi.c:208
+msgid "Viola"
+msgstr "Bratsche"
+
+#. Strings
+#. xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+#: Programs/midi.c:205
+msgid "Violin"
+msgstr "Violine"
+
+#. xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+#: Programs/midi.c:245
+msgid "Voice Oohs"
+msgstr "Stimme (Ooh)"
+
+#: Programs/menu_prefs.c:1494
+msgid "Warning"
+msgstr "Warnung"
+
+#: Programs/menu_prefs.c:1438
+msgid "Web Site"
+msgstr "Webseite"
+
+#. xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+#: Programs/midi.c:323
+msgid "Whistle"
+msgstr "Pfeife"
+
+#: Programs/menu_prefs.c:543
+msgid "Window Column"
+msgstr "Braillezeilenspalte"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:558
+msgid "Window Coordinates"
+msgstr "Braillezeilenkoordinaten"
+
+#: Programs/menu_prefs.c:544
+msgid "Window Row"
+msgstr "Braillezielenzeile"
+
+#. xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+#: Programs/midi.c:440
+msgid "Woodblock"
+msgstr "Holzblock"
+
+#: Programs/menu_prefs.c:848
+msgid "Word Wrap"
+msgstr "Wortumbruch"
+
+#: Programs/config.c:2948
+msgid "Working Directory"
+msgstr "Arbeitsverzeichnis"
+
+#: Programs/config.c:2975 Programs/menu_prefs.c:1468
+msgid "Writable Directory"
+msgstr "Verzeichnis mit Schreibzugriff"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr ""
+
+#: Programs/config.c:767
+msgid "Write the start-up logs, and then exit."
+msgstr "Schreibe das Startprotokoll und beende dann."
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr "X Display mit dem Verbindung aufgenommen werden soll"
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr "XDG-Laufzeitverzeichnis erstellt"
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr "XDG-Laufzeitverzeichnis existiert"
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr ""
+
+#. xgettext: This is the description of the PASSXT command.
+#: Programs/cmds.auto.h:1479
+msgid "XT (set 1) keyboard scan code"
+msgstr "XT (set 1) Tastatur-Scancode"
+
+#. xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+#: Programs/midi.c:120
+msgid "Xylophone"
+msgstr "Xylophon"
+
+#: Programs/menu_prefs.c:1312
+msgid "Year Month Day"
+msgstr "Jahr Monat Tag"
+
+#: Programs/menu.c:524
+msgid "Yes"
+msgstr "Ja"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr "[Host][:Port]"
+
+#: Programs/menu_prefs.c:665
+msgid "all dots"
+msgstr "Alle Punkte"
+
+#: Programs/cmd_miscellaneous.c:104
+msgid "and"
+msgstr "und"
+
+#. xgettext: This is the description of the CLIP_APPEND command.
+#: Programs/cmds.auto.h:1346
+msgid "append characters to clipboard"
+msgstr "hänge Zeichenkette an die Zwischenablage an"
+
+#. xgettext: This is the description of the CLIP_ADD command.
+#: Programs/cmds.auto.h:1223
+msgid "append to clipboard from character"
+msgstr "hänge ab Zeichen an die Zwischenablage an"
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr "beim Cursor"
+
+#. xgettext: This is the description of the KEY_BACKSPACE command.
+#: Programs/cmds.auto.h:1527
+msgid "backspace key"
+msgstr "Rücktaste"
+
+#: Drivers/Braille/Baum/braille.c:1149 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr "Akku schwach"
+
+#. xgettext: This is the description of the SELECTVT command.
+#: Programs/cmds.auto.h:1437
+msgid "bind to specific virtual terminal"
+msgstr "an ein bestimmtes virtuellen Terminal anbinden"
+
+#. xgettext: This is the description of the SELECTVT_NEXT command.
+#: Programs/cmds.auto.h:957
+msgid "bind to the next virtual terminal"
+msgstr "an das nächste virtuelle Terminal anbinden"
+
+#. xgettext: This is the description of the SELECTVT_PREV command.
+#: Programs/cmds.auto.h:950
+msgid "bind to the previous virtual terminal"
+msgstr "an das vorhergehende virtuelle Terminal anbinden"
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "Schwarz"
+
+#: Programs/core.c:1125
+msgid "blank line"
+msgstr "leere Zeile"
+
+#: Programs/cmd_utils.c:173
+msgid "blinking"
+msgstr ""
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "Blau"
+
+#: Programs/config.c:2993
+#, c-format
+msgid "braille device not specified"
+msgstr "Braillegerät nicht spezifiziert"
+
+#. xgettext: This is the description of the OFFLINE command.
+#: Programs/cmds.auto.h:621
+msgid "braille display temporarily unavailable"
+msgstr "Braillezeile vorübergehend nicht verfügbar"
+
+#: Programs/config.c:1737
+msgid "braille driver initialization failed"
+msgstr "Initialisierung des Brailletreibers fehlgeschlagen"
+
+#: Programs/config.c:1816
+msgid "braille driver not loadable"
+msgstr "Brailletreiber kann nicht geladen werden"
+
+#: Programs/config.c:2077
+msgid "braille driver restarting"
+msgstr "Braille Treiber wird neu gestartet"
+
+#: Programs/cmd_miscellaneous.c:163
+msgid "braille driver stopped"
+msgstr "Braille Treiber gestoppt"
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr "Braille freigegeben"
+
+#. xgettext: This is the description of the ROUTE command.
+#: Programs/cmds.auto.h:1207
+msgid "bring screen cursor to character"
+msgstr "Bewege Bildschirmcursor zu Zeichen"
+
+#. xgettext: This is the description of the CSRJMP_VERT command.
+#: Programs/cmds.auto.h:593
+msgid "bring screen cursor to current line"
+msgstr "Bewege Bildschirmcursor zur aktuellen Braillezeilenposition"
+
+#. xgettext: This is the description of the ROUTE_LINE command.
+#: Programs/cmds.auto.h:1404
+msgid "bring screen cursor to line"
+msgstr "Bewege Bildschirmcursor zu Zeile"
+
+#. xgettext: This is the description of the ROUTE_CURR_LOCN command.
+#: Programs/cmds.auto.h:835
+msgid "bring screen cursor to speech cursor"
+msgstr "Bewege Bildschirmcursor zur Sprachcursorposition"
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "Braun"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr "kann console nicht öffnen"
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr "kann Bildschirminhalt nicht lesen"
+
+#: Drivers/Screen/Linux/screen.c:1601
+#, fuzzy
+msgid "can't read screen header"
+msgstr "Bildschirmtreiber starten"
+
+#: Programs/ctb_translate.c:393
+msgid "cannot access internal contraction table"
+msgstr ""
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr "kann Attributtabelle nicht kompilieren"
+
+#: Programs/ctb_translate.c:387
+msgid "cannot compile contraction table"
+msgstr "kann Kurzschrifttabelle nicht kompilieren"
+
+#: Programs/config.c:1720
+msgid "cannot compile key table"
+msgstr "kann Tastentabelle nicht kompilieren"
+
+#: Programs/config.c:1284
+msgid "cannot compile keyboard table"
+msgstr "kann Tastentabelle nicht kompilieren"
+
+#: Programs/ttb_translate.c:275
+msgid "cannot compile text table"
+msgstr "kann Texttabelle nicht kompilieren"
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr "Kann nicht mit Braillegerätedienst brltty via %s verbinden\n"
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr "kann mit Braillezeile %s nicht verbinden\n"
+
+#: Programs/file.c:381
+msgid "cannot create directory"
+msgstr "kann Verzeichnis nicht erstellen"
+
+#: Programs/program.c:150
+#, c-format
+msgid "cannot determine program directory"
+msgstr "kann Programmverzeichnis nicht bestimmen"
+
+#: Programs/config.c:2951 Programs/menu.c:618
+msgid "cannot determine working directory"
+msgstr "kann Arbeitsverzeichnis nicht bestimmen"
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr "kann Prozedur nicht finden"
+
+#: Programs/program.c:158
+msgid "cannot fix install path"
+msgstr "kann Installationspfad nicht beheben"
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr ""
+
+#: Programs/file.c:518
+msgid "cannot get working directory"
+msgstr "kann Arbeitsverzeichnis nicht erfragen"
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr "kann tasten nicht ignorieren\n"
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr "kann Attributtabelle nicht laden"
+
+#: Programs/ctb_translate.c:407
+msgid "cannot load contraction table"
+msgstr ""
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr "kann Programmbibliothek nicht laden"
+
+#: Programs/ttb_translate.c:295
+msgid "cannot load text table"
+msgstr "kann Texttabelle nicht laden"
+
+#: Programs/file.c:370
+#, fuzzy
+msgid "cannot make world writable"
+msgstr "kann Tastentabelle nicht kompilieren"
+
+#: Programs/config.c:1239
+msgid "cannot open key help"
+msgstr "kann Tastenhilfe nicht öffnen"
+
+#: Programs/program.c:262
+msgid "cannot open process identifier file"
+msgstr "kann Prozessidentifikationsdatei nicht öffnen"
+
+#: Programs/menu.c:616
+msgid "cannot open working directory"
+msgstr "kann Arbeitsverzeichnis nicht öffnen"
+
+#: Programs/prefs.c:363
+msgid "cannot read preferences file"
+msgstr "kann Einstellungsdatei nicht lesen"
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr "kann Fokus nicht auf %#010x setzen\n"
+
+#: Programs/file.c:532 Programs/menu.c:605
+msgid "cannot set working directory"
+msgstr "kann Arbeitsverzeichnis nicht wechseln"
+
+#: Programs/prefs.c:576
+msgid "cannot write to preferences file"
+msgstr "kann Einstellungsdatei nicht schreiben"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1070
+msgid "cap"
+msgstr "Groß"
+
+#: Programs/menu_prefs.c:886 Programs/menu_prefs.c:1353
+msgid "cells"
+msgstr "Zellen"
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr "Änderungen verworfen"
+
+#. xgettext: This is the description of the UNSTICK command.
+#: Programs/cmds.auto.h:887
+msgid "clear all sticky input modifiers"
+msgstr "alle eingerasteten Steuertasten löschen"
+
+#. xgettext: This is the description of the TXTSEL_CLEAR command.
+#: Programs/cmds.auto.h:1019
+msgid "clear the text selection"
+msgstr ""
+
+#: Programs/cmd_speech.c:501
+msgid "column"
+msgstr "Spalte"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr ""
+
+#: Programs/options.c:812
+msgid "configuration directive specified more than once"
+msgstr "Konfigurationsdirektive mehrfach angegeben"
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr "Konsole nicht in Verwendung"
+
+#: Programs/menu_prefs.c:1056
+msgid "console tone generator"
+msgstr "Konsolen-Tongenerator"
+
+#. xgettext: This is the description of the CLIP_COPY command.
+#: Programs/cmds.auto.h:1338
+msgid "copy characters to clipboard"
+msgstr "kopiere Zeichenkette in die Zwischenablage"
+
+#. xgettext: This is the description of the HOST_COPY command.
+#: Programs/cmds.auto.h:1033
+#, fuzzy
+msgid "copy selected text to host clipboard"
+msgstr "kopiere Zeichenkette in die Zwischenablage"
+
+#: Programs/config.c:729
+msgid "csecs"
+msgstr ""
+
+#. xgettext: This is the description of the TOUCH_AT command.
+#: Programs/cmds.auto.h:1503
+msgid "current reading location"
+msgstr "aktuelle Leseposition"
+
+#. xgettext: This is the description of the KEY_CURSOR_DOWN command.
+#: Programs/cmds.auto.h:1567
+msgid "cursor-down key"
+msgstr "Pfeil-Runter-Taste"
+
+#. xgettext: This is the description of the KEY_CURSOR_LEFT command.
+#: Programs/cmds.auto.h:1543
+msgid "cursor-left key"
+msgstr "Pfeil-Links-Taste"
+
+#. xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+#: Programs/cmds.auto.h:1551
+msgid "cursor-right key"
+msgstr "Pfeil-Rechts-Taste"
+
+#. xgettext: This is the description of the KEY_CURSOR_UP command.
+#: Programs/cmds.auto.h:1559
+msgid "cursor-up key"
+msgstr "Pfeil-Rauf-Taste"
+
+#. xgettext: This is the description of the HOST_CUT command.
+#: Programs/cmds.auto.h:1040
+msgid "cut selected text to host clipboard"
+msgstr ""
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr "Zyan"
+
+#. xgettext: This is the description of the ALTGR command.
+#: Programs/cmds.auto.h:894
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der AltGr (rechte Alt) Taste umschalten (ändern, ein, aus)"
+
+#. xgettext: This is the description of the CONTROL command.
+#: Programs/cmds.auto.h:642
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der Steuerung-Taste umschalten (ändern, ein, aus)"
+
+#. xgettext: This is the description of the GUI command.
+#: Programs/cmds.auto.h:901
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der GUI (Windows) Taste umschalten (ändern, ein, aus)"
+
+#. xgettext: This is the description of the META command.
+#: Programs/cmds.auto.h:649
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der Meta (linke Alt) Taste umschalten (ändern, ein, aus)"
+
+#. xgettext: This is the description of the SHIFT command.
+#: Programs/cmds.auto.h:628
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der Umschalttaste umschalten (ändern, ein, aus)"
+
+#. xgettext: This is the description of the UPPER command.
+#: Programs/cmds.auto.h:635
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr "Einrastfunktion der Feststelltaste umschalten (ändern, ein, aus)"
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "Dunkelgrau"
+
+#. xgettext: This is the description of the SAY_LOWER command.
+#: Programs/cmds.auto.h:1168
+msgid "decrease speaking pitch"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_SLOWER command.
+#: Programs/cmds.auto.h:550
+msgid "decrease speaking rate"
+msgstr "langsamer sprechen"
+
+#. xgettext: This is the description of the SAY_SOFTER command.
+#: Programs/cmds.auto.h:564
+msgid "decrease speaking volume"
+msgstr "leiser sprechen"
+
+#. xgettext: This is the description of the KEY_DELETE command.
+#: Programs/cmds.auto.h:1615
+msgid "delete key"
+msgstr "Entfernen-Taste"
+
+#. xgettext: This is the description of the DESCCHAR command.
+#: Programs/cmds.auto.h:1275
+msgid "describe character"
+msgstr "beschreibe Zeichen"
+
+#. xgettext: This is the description of the DESC_CURR_CHAR command.
+#: Programs/cmds.auto.h:820
+msgid "describe current character"
+msgstr "beschreibe aktuelles Zeichen"
+
+#: Programs/config.c:710 Programs/config.c:720
+msgid "device"
+msgstr "Gerät"
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:400 Programs/config.c:500
+#: Programs/config.c:510 Programs/config.c:520 Programs/config.c:591
+msgid "directory"
+msgstr "Verzeichnis"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr "display"
+
+#. xgettext: This is the description of the NOOP command.
+#: Programs/cmds.auto.h:5
+msgid "do nothing"
+msgstr "nichts unternehmen"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr "Fertig"
+
+#: Programs/menu_prefs.c:666
+msgid "dot 7"
+msgstr "Punkt 7"
+
+#: Programs/menu_prefs.c:667
+msgid "dot 8"
+msgstr "Punkt 8"
+
+#: Programs/menu_prefs.c:664
+msgid "dots 7 and 8"
+msgstr "Punkte 7 und 8"
+
+#: Drivers/Braille/Baum/braille.c:1146
+msgid "driver request"
+msgstr "Treiberanfrage"
+
+#: Programs/config.c:549 Programs/config.c:647 Programs/config.c:690
+msgid "driver,..."
+msgstr "Treiber,..."
+
+#. xgettext: This is the description of the KEY_END command.
+#: Programs/cmds.auto.h:1599
+msgid "end key"
+msgstr "Ende-Taste"
+
+#. xgettext: This is the description of the KEY_ENTER command.
+#: Programs/cmds.auto.h:1511
+msgid "enter key"
+msgstr "Eingabetaste"
+
+#. xgettext: This is the description of the LEARN command.
+#: Programs/cmds.auto.h:436
+msgid "enter/leave command learn mode"
+msgstr "Aufrufen/Verlassen des Kommandolernmodus"
+
+#. xgettext: This is the description of the HELP command.
+#: Programs/cmds.auto.h:422
+msgid "enter/leave help display"
+msgstr "Aufrufen/Verlassen der Hilfe"
+
+#. xgettext: This is the description of the PREFMENU command.
+#: Programs/cmds.auto.h:443
+msgid "enter/leave preferences menu"
+msgstr "Aufrufen/Verlassen des Einstellungsmenüs"
+
+#. xgettext: This is the description of the INFO command.
+#: Programs/cmds.auto.h:429
+msgid "enter/leave status display"
+msgstr "Aufrufen/Verlassen der Statusanzeige"
+
+#. xgettext: This is the description of the KEY_ESCAPE command.
+#: Programs/cmds.auto.h:1535
+msgid "escape key"
+msgstr "Escape-Taste"
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "Eurobraille"
+
+#. xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+#: Programs/cmd_miscellaneous.c:102
+msgid "exactly"
+msgstr "genau"
+
+#: Programs/config.c:873
+msgid "excess argument"
+msgstr "überschüssiges Argument"
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr "führe \"%s\" (von \"%s\") aus\n"
+
+#: Programs/pgmprivs_linux.c:2045
+msgid "executing as the invoking user"
+msgstr ""
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr "Ausführen von \"%s\" fehlgeschlagen: %s\n"
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:457
+#: Programs/config.c:466 Programs/config.c:476 Programs/config.c:602
+#: Programs/config.c:612 Programs/config.c:621 Programs/config.c:629
+#: Programs/config.c:666 Programs/config.c:759
+msgid "file"
+msgstr "Datei"
+
+#: Programs/options.c:998
+#, c-format
+msgid "file '%s' processing error."
+msgstr "Verarbeitungsfehler in Datei '%s'."
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_FUNCTION command.
+#: Programs/cmds.auto.h:1624
+msgid "function key"
+msgstr "Funktionstaste"
+
+#. xgettext: This is the description of the BACK command.
+#: Programs/cmds.auto.h:271
+msgid "go back after cursor tracking"
+msgstr "Gehe zurück nach Cursorverfolgung"
+
+#. xgettext: This is the description of the GUI_BACK command.
+#: Programs/cmds.auto.h:1077
+#, fuzzy
+msgid "go back to the previous screen"
+msgstr "Gehe nach oben zum vorherigen Element"
+
+#. xgettext: This is the description of the FWINLT command.
+#: Programs/cmds.auto.h:210
+msgid "go backward one braille window"
+msgstr "Gehe eine Braillezeilenlänge zurück"
+
+#. xgettext: This is the description of the FWINLTSKIP command.
+#: Programs/cmds.auto.h:228
+msgid "go backward skipping blank braille windows"
+msgstr "Gehe zurück und überspringe leere Braillezeilenausschnitte"
+
+#. xgettext: This is the description of the PRNBWIN command.
+#: Programs/cmds.auto.h:966
+msgid "go backward to nearest non-blank braille window"
+msgstr "Gehe zurück zum nähesten befüllten Braillezeilenausschnitt"
+
+#. xgettext: This is the description of the LNDN command.
+#: Programs/cmds.auto.h:23
+msgid "go down one line"
+msgstr "Gehe eine Zeile nach unten"
+
+#. xgettext: This is the description of the WINDN command.
+#: Programs/cmds.auto.h:41
+msgid "go down several lines"
+msgstr "Gehe mehrere Zeilen nach unten"
+
+#. xgettext: This is the description of the NXPGRPH command.
+#: Programs/cmds.auto.h:133
+msgid "go down to first line of next paragraph"
+msgstr "Gehe zur ersten Zeile des nächsten Absatzes"
+
+#. xgettext: This is the description of the MENU_LAST_ITEM command.
+#: Programs/cmds.auto.h:475
+msgid "go down to last item"
+msgstr "Gehe nach unten zum letzten Element"
+
+#. xgettext: This is the description of the NXDIFCHAR command.
+#: Programs/cmds.auto.h:1330
+msgid "go down to nearest line with different character"
+msgstr "Gehe zu nächster Zeile mit anderem Zeichen"
+
+#. xgettext: This is the description of the NXDIFLN command.
+#: Programs/cmds.auto.h:59
+msgid "go down to nearest line with different content"
+msgstr "Gehe zu nächster Zeile mit anderem Inhalt"
+
+#. xgettext: This is the description of the ATTRDN command.
+#: Programs/cmds.auto.h:77
+msgid "go down to nearest line with different highlighting"
+msgstr "Gehe zu nächster Zeile mit anderer Hervorhebung"
+
+#. xgettext: This is the description of the NXINDENT command.
+#: Programs/cmds.auto.h:1267
+msgid "go down to nearest line with less indent than character"
+msgstr "Gehe zu nächster Zeile mit weniger Einrückung als Zeichen"
+
+#. xgettext: This is the description of the NXPROMPT command.
+#: Programs/cmds.auto.h:151
+msgid "go down to next command prompt"
+msgstr "Gehe zum nächsten Kommandoprompt"
+
+#. xgettext: This is the description of the MENU_NEXT_ITEM command.
+#: Programs/cmds.auto.h:493
+msgid "go down to next item"
+msgstr "Gehe nach unten zum nächsten Element"
+
+#. xgettext: This is the description of the FWINRT command.
+#: Programs/cmds.auto.h:219
+msgid "go forward one braille window"
+msgstr "Gehe eine Braillezeilenlänge vorwärts"
+
+#. xgettext: This is the description of the FWINRTSKIP command.
+#: Programs/cmds.auto.h:237
+msgid "go forward skipping blank braille windows"
+msgstr "Gehe vorwärts und überspringe leere Braillezeilenausschnitte"
+
+#. xgettext: This is the description of the NXNBWIN command.
+#: Programs/cmds.auto.h:975
+msgid "go forward to nearest non-blank braille window"
+msgstr "Gehe vorwärts zum nähesten befüllten Braillezeilenausschnitt"
+
+#. xgettext: This is the description of the HWINLT command.
+#: Programs/cmds.auto.h:192
+msgid "go left half a braille window"
+msgstr "Gehe eine halbe Braillezeilenlänge nach links"
+
+#. xgettext: This is the description of the CHRLT command.
+#: Programs/cmds.auto.h:174
+msgid "go left one character"
+msgstr "Gehe ein Zeichen nach links"
+
+#. xgettext: This is the description of the HWINRT command.
+#: Programs/cmds.auto.h:201
+msgid "go right half a braille window"
+msgstr "Gehe eine halbe Braillezeilenlänge nach rechts"
+
+#. xgettext: This is the description of the CHRRT command.
+#: Programs/cmds.auto.h:183
+msgid "go right one character"
+msgstr "Gehe ein Zeichen nach rechts"
+
+#. xgettext: This is the description of the SPEAK_FRST_CHAR command.
+#: Programs/cmds.auto.h:789
+msgid "go to and speak first non-blank character on line"
+msgstr "Gehe zu und sprich erstes nicht leeres Zeichen der Zeile"
+
+#. xgettext: This is the description of the SPEAK_FRST_LINE command.
+#: Programs/cmds.auto.h:805
+msgid "go to and speak first non-blank line on screen"
+msgstr "Gehe zu und sprich erste nicht leere Zeile des Bildschirms"
+
+#. xgettext: This is the description of the SPEAK_LAST_CHAR command.
+#: Programs/cmds.auto.h:797
+msgid "go to and speak last non-blank character on line"
+msgstr "Gehe zu und sprich letztes nicht leeres Zeichen der Zeile"
+
+#. xgettext: This is the description of the SPEAK_LAST_LINE command.
+#: Programs/cmds.auto.h:813
+msgid "go to and speak last non-blank line on screen"
+msgstr "Gehe zu und sprich letzte nicht leere Zeile des Bildschirms"
+
+#. xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+#: Programs/cmds.auto.h:735
+msgid "go to and speak next character"
+msgstr "Gehe zu und sprich nächstes Zeichen"
+
+#. xgettext: This is the description of the SPEAK_NEXT_LINE command.
+#: Programs/cmds.auto.h:781
+msgid "go to and speak next line"
+msgstr "Gehe zu und sprich nächste Zeile"
+
+#. xgettext: This is the description of the SPEAK_NEXT_WORD command.
+#: Programs/cmds.auto.h:758
+msgid "go to and speak next word"
+msgstr "Gehe zu und sprich nächstes Wort"
+
+#. xgettext: This is the description of the SPEAK_PREV_CHAR command.
+#: Programs/cmds.auto.h:727
+msgid "go to and speak previous character"
+msgstr "Gehe zu und sprich vorheriges Zeichen"
+
+#. xgettext: This is the description of the SPEAK_PREV_LINE command.
+#: Programs/cmds.auto.h:773
+msgid "go to and speak previous line"
+msgstr "Gehe zu und sprich vorherige Zeile"
+
+#. xgettext: This is the description of the SPEAK_PREV_WORD command.
+#: Programs/cmds.auto.h:750
+msgid "go to and speak previous word"
+msgstr "Gehe zu und sprich vorheriges Wort"
+
+#. xgettext: This is the description of the BOT_LEFT command.
+#: Programs/cmds.auto.h:115
+msgid "go to beginning of bottom line"
+msgstr "Gehe zum Anfang der untersten Zeile"
+
+#. xgettext: This is the description of the LNBEG command.
+#: Programs/cmds.auto.h:246
+msgid "go to beginning of line"
+msgstr "Gehe zum Anfang der Zeile"
+
+#. xgettext: This is the description of the TOP_LEFT command.
+#: Programs/cmds.auto.h:105
+msgid "go to beginning of top line"
+msgstr "Gehe zum Anfang der obersten Zeile"
+
+#. xgettext: This is the description of the BOT command.
+#: Programs/cmds.auto.h:95
+msgid "go to bottom line"
+msgstr "Gehe zur untersten Zeile"
+
+#. xgettext: This is the description of the SPKHOME command.
+#: Programs/cmds.auto.h:522
+msgid "go to current speaking position"
+msgstr "Gehe zur aktuellen Sprachposition"
+
+#. xgettext: This is the description of the LNEND command.
+#: Programs/cmds.auto.h:255
+msgid "go to end of line"
+msgstr "Gehe zum Ende der Zeile"
+
+#. xgettext: This is the description of the MENU_PREV_LEVEL command.
+#: Programs/cmds.auto.h:664
+msgid "go to previous menu level"
+msgstr "Gehe zur vorherigen Menüebene"
+
+#. xgettext: This is the description of the GOTOMARK command.
+#: Programs/cmds.auto.h:1300
+msgid "go to remembered braille window position"
+msgstr "Gehe zu gespeicherter Braillezeilenposition"
+
+#. xgettext: This is the description of the HOME command.
+#: Programs/cmds.auto.h:263
+msgid "go to screen cursor"
+msgstr "Gehe zum Bildschirmcursor"
+
+#. xgettext: This is the description of the RETURN command.
+#: Programs/cmds.auto.h:279
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr "Gehe zum Bildschirmcursor oder gehe zurück nach Cursorverfolgung"
+
+#. xgettext: This is the description of the GOTOLINE command.
+#: Programs/cmds.auto.h:1310
+msgid "go to selected line"
+msgstr "Gehe zu hervorgehobener Zeile"
+
+#. xgettext: This is the description of the GUI_HOME command.
+#: Programs/cmds.auto.h:1069
+#, fuzzy
+msgid "go to the home screen"
+msgstr "Bildschirmtreiber stoppen"
+
+#. xgettext: This is the description of the TOP command.
+#: Programs/cmds.auto.h:86
+msgid "go to top line"
+msgstr "Gehe zur obersten Zeile"
+
+#. xgettext: This is the description of the LNUP command.
+#: Programs/cmds.auto.h:14
+msgid "go up one line"
+msgstr "Gehe eine Zeile nach oben"
+
+#. xgettext: This is the description of the WINUP command.
+#: Programs/cmds.auto.h:32
+msgid "go up several lines"
+msgstr "Gehe mehrere Zeilen nach oben"
+
+#. xgettext: This is the description of the MENU_FIRST_ITEM command.
+#: Programs/cmds.auto.h:466
+msgid "go up to first item"
+msgstr "Gehe nach oben zum ersten Element"
+
+#. xgettext: This is the description of the PRPGRPH command.
+#: Programs/cmds.auto.h:124
+msgid "go up to first line of paragraph"
+msgstr "Gehe zur ersten Zeile des Absatzes"
+
+#. xgettext: This is the description of the PRDIFCHAR command.
+#: Programs/cmds.auto.h:1320
+msgid "go up to nearest line with different character"
+msgstr "Gehe zu vorheriger Zeile mit anderem Zeichen"
+
+#. xgettext: This is the description of the PRDIFLN command.
+#: Programs/cmds.auto.h:50
+msgid "go up to nearest line with different content"
+msgstr "Gehe zu vorheriger Zeile mit anderem Inhalt"
+
+#. xgettext: This is the description of the ATTRUP command.
+#: Programs/cmds.auto.h:68
+msgid "go up to nearest line with different highlighting"
+msgstr "Gehe zu vorheriger Zeile mit anderer Hervorhebung"
+
+#. xgettext: This is the description of the PRINDENT command.
+#: Programs/cmds.auto.h:1257
+msgid "go up to nearest line with less indent than character"
+msgstr "Gehe zu vorheriger Zeile mit weniger Einrückung als Zeichen"
+
+#. xgettext: This is the description of the PRPROMPT command.
+#: Programs/cmds.auto.h:142
+msgid "go up to previous command prompt"
+msgstr "Gehe zum vorherigen Kommandoprompt"
+
+#. xgettext: This is the description of the MENU_PREV_ITEM command.
+#: Programs/cmds.auto.h:484
+msgid "go up to previous item"
+msgstr "Gehe nach oben zum vorherigen Element"
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "Grün"
+
+#: Programs/pgmprivs_linux.c:2167
+msgid "group permissions added"
+msgstr "Gruppenberechtigungen hinzugefügt"
+
+#: Programs/cmd_miscellaneous.c:213
+msgid "help not available"
+msgstr "Hilfe nicht verfügbar"
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr "Hilfe nicht lesbar"
+
+#. xgettext: This is the description of the KEY_HOME command.
+#: Programs/cmds.auto.h:1591
+msgid "home key"
+msgstr "Pos1-Taste"
+
+#: Programs/config.c:570
+msgid "identifier,..."
+msgstr "Bezeichnung,..."
+
+#: Drivers/Braille/Baum/braille.c:1148
+msgid "idle timeout"
+msgstr "Inaktivitätszeitüberschreitung"
+
+#. xgettext: This is the description of the SAY_HIGHER command.
+#: Programs/cmds.auto.h:1175
+msgid "increase speaking pitch"
+msgstr "die Tonhöhe der Sprachausgabe erhöhen"
+
+#. xgettext: This is the description of the SAY_FASTER command.
+#: Programs/cmds.auto.h:557
+msgid "increase speaking rate"
+msgstr "Schneller sprechen"
+
+#. xgettext: This is the description of the SAY_LOUDER command.
+#: Programs/cmds.auto.h:571
+msgid "increase speaking volume"
+msgstr "Lauter sprechen"
+
+#: Programs/core.c:1128
+msgid "indent"
+msgstr "Einrückung"
+
+#. xgettext: This is the description of the PASTE_HISTORY command.
+#: Programs/cmds.auto.h:1354
+msgid "insert clipboard history entry after screen cursor"
+msgstr "Text der Zwischenablagenhistorie nach Bildschirmcursor einfügen"
+
+#. xgettext: This is the description of the PASTE command.
+#: Programs/cmds.auto.h:600
+msgid "insert clipboard text after screen cursor"
+msgstr "Text der Zwischenablage an Bildschirmcursorposition einfügen"
+
+#. xgettext: This is the description of the HOST_PASTE command.
+#: Programs/cmds.auto.h:1047
+#, fuzzy
+msgid "insert host clipboard text after screen cursor"
+msgstr "Text der Zwischenablage an Bildschirmcursorposition einfügen"
+
+#. xgettext: This is the description of the KEY_INSERT command.
+#: Programs/cmds.auto.h:1607
+msgid "insert key"
+msgstr "Einfüge-Taste"
+
+#: Programs/program.c:166
+msgid "install path not absolute"
+msgstr "Installationspfad ist nicht absolut"
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr "ungültige Zähler-Einstellung"
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr "ungültige Escape-Sequenz"
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr "ungültige Schalter-Einstellung"
+
+#: Programs/config.c:2863
+msgid "invalid message hold timeout"
+msgstr "ungültige Meldungsanzeigedauer"
+
+#. the operand for an option is invalid
+#: Programs/options.c:594
+#, fuzzy
+msgid "invalid operand"
+msgstr "fehlender Operand"
+
+#: Programs/brlapi_server.c:4516
+msgid "invalid thread stack size"
+msgstr "ungültige Threadstapelgröße"
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr "Tastaturemulation aus"
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr "Tastaturemulation ein"
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr "Hellblau"
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr "Hellzyan"
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr "Hellgrün"
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr "Hellgrau"
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr "Hellmagenta"
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr "Hellrot"
+
+#: Programs/cmd_speech.c:500
+msgid "line"
+msgstr "Zeile"
+
+#. xgettext: This is the description of the COPY_LINE command.
+#: Programs/cmds.auto.h:1239
+msgid "linear copy to character"
+msgstr "kopiere ohne Zeilenumbrüche bis Zeichen"
+
+#: Programs/config.c:750
+msgid "lvl|cat,..."
+msgstr "lvl|kat,..."
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "Magenta"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:588
+msgid "missing operand"
+msgstr "fehlender Operand"
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr "fehlender Parametername"
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr "fehlende Parameterkennzeichnung"
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr "fehlender Parameterwert"
+
+#. xgettext: This is the description of the GUI_ITEM_FRST command.
+#: Programs/cmds.auto.h:1140
+msgid "move to the first item in the screen area"
+msgstr "gehe zum ersten Element des Bildschirmbereiches"
+
+#. xgettext: This is the description of the GUI_ITEM_LAST command.
+#: Programs/cmds.auto.h:1161
+msgid "move to the last item in the screen area"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_ITEM_NEXT command.
+#: Programs/cmds.auto.h:1154
+msgid "move to the next item in the screen area"
+msgstr "gehe zum nächsten Element des Bildschirmbereiches"
+
+#. xgettext: This is the description of the GUI_ITEM_PREV command.
+#: Programs/cmds.auto.h:1147
+msgid "move to the previous item in the screen area"
+msgstr "gehe zum vorherigen Element des Bildschirmbereiches"
+
+#: Programs/msgtest.c:57
+msgid "name"
+msgstr "Name"
+
+#: Programs/config.c:433 Programs/config.c:485 Programs/config.c:538
+#: Programs/config.c:560 Programs/config.c:638 Programs/config.c:657
+#: Programs/config.c:700
+msgid "name=value,..."
+msgstr "Name=Wert,..."
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr "eigenes"
+
+#: Programs/config.c:1248
+msgid "no key bindings"
+msgstr "Keine Tastenbelegungen"
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr "Kein Bildschirm"
+
+#: Programs/config.c:116
+msgid "none"
+msgstr "keine"
+
+#: Programs/config.c:1537
+msgid "not saved"
+msgstr "nicht gespeichert"
+
+#: Programs/pgmprivs_linux.c:2018
+msgid "not switching to an unprivileged user"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_ALERTS command.
+#: Programs/cmds.auto.h:1112
+msgid "open the application alerts window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_LIST command.
+#: Programs/cmds.auto.h:1098
+msgid "open the application list window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_APP_MENU command.
+#: Programs/cmds.auto.h:1105
+msgid "open the application-specific menu"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_BRL_ACTIONS command.
+#: Programs/cmds.auto.h:1061
+#, fuzzy
+msgid "open the braille actions window"
+msgstr "Gehe eine halbe Braillezeilenlänge nach links"
+
+#. xgettext: This is the description of the GUI_DEV_OPTIONS command.
+#: Programs/cmds.auto.h:1091
+msgid "open the device options window"
+msgstr ""
+
+#. xgettext: This is the description of the GUI_DEV_SETTINGS command.
+#: Programs/cmds.auto.h:1084
+msgid "open the device settings window"
+msgstr ""
+
+#: Programs/options.c:202
+msgid "option"
+msgstr "Option"
+
+#: Programs/pgmprivs_linux.c:2152
+msgid "ownership claimed"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_PAGE_DOWN command.
+#: Programs/cmds.auto.h:1583
+msgid "page-down key"
+msgstr "Seite-Runter Taste"
+
+#. xgettext: This is the description of the KEY_PAGE_UP command.
+#: Programs/cmds.auto.h:1575
+msgid "page-up key"
+msgstr "Seite-Rauf Taste"
+
+#: Programs/msgtest.c:42
+msgid "path"
+msgstr ""
+
+#: Programs/config.c:2836
+msgid "pid file not specified"
+msgstr "PID-Datei nicht spezifiziert"
+
+#: Programs/program.c:308
+msgid "pid file open error"
+msgstr "Fehler beim öffnen der PID-Datei"
+
+#: Programs/spk.c:232
+msgid "pitch"
+msgstr "Tonhöhe"
+
+#. xgettext: This is the description of the SETLEFT command.
+#: Programs/cmds.auto.h:1283
+msgid "place left end of braille window at character"
+msgstr "Positioniere linken Rand der Braillezeile bei Zeichen"
+
+#: Drivers/Braille/Baum/braille.c:1147
+msgid "power switch"
+msgstr "Ein-/Aus-Schalter"
+
+#: Programs/config.c:680
+msgid "quality"
+msgstr ""
+
+#: Programs/spk.c:206
+msgid "rate"
+msgstr "Geschwindigkeit"
+
+#. xgettext: This is the description of the COPY_RECT command.
+#: Programs/cmds.auto.h:1231
+msgid "rectangular copy to character"
+msgstr "kopiere mit Zeilenumbrüchen bis Zeichen"
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "Rot"
+
+#. xgettext: This is the description of the REFRESH command.
+#: Programs/cmds.auto.h:1005
+#, fuzzy
+msgid "refresh braille display"
+msgstr "Pfad zur Schnittstelle für den Zugriff auf die Braillezeile."
+
+#. xgettext: This is the description of the REFRESH_LINE command.
+#: Programs/cmds.auto.h:1413
+#, fuzzy
+msgid "refresh braille line"
+msgstr "Brailletreiber neu starten"
+
+#: Programs/config.c:492
+msgid "regexp,..."
+msgstr "regexp,..."
+
+#: Programs/config.c:2081
+#, c-format
+msgid "reinitializing braille driver"
+msgstr "reinitialisiere Brailletreiber"
+
+#: Programs/config.c:2594
+#, c-format
+msgid "reinitializing screen driver"
+msgstr "reinitialisiere Bildschirmtreiber"
+
+#: Programs/config.c:2394
+#, c-format
+msgid "reinitializing speech driver"
+msgstr "reinitialisiere Sprachausgabentreiber"
+
+#. xgettext: This is the description of the SETMARK command.
+#: Programs/cmds.auto.h:1291
+msgid "remember current braille window position"
+msgstr "Speichere aktuelle Braillezeilenposition"
+
+#. xgettext: This is the description of the ALERT command.
+#: Programs/cmds.auto.h:1445
+msgid "render an alert"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr "Wiederholungsanzahl"
+
+#. xgettext: This is the description of the RESTARTBRL command.
+#: Programs/cmds.auto.h:607
+msgid "restart braille driver"
+msgstr "Brailletreiber neu starten"
+
+#. xgettext: This is the description of the RESTARTSPEECH command.
+#: Programs/cmds.auto.h:614
+msgid "restart speech driver"
+msgstr "Sprachausgabentreiber neu starten"
+
+#. xgettext: This is the description of the CLIP_RESTORE command.
+#: Programs/cmds.auto.h:864
+msgid "restore clipboard from disk"
+msgstr "gespeicherte Zwischenablage von Festplatte wiederherstellen"
+
+#. xgettext: This is the description of the PREFLOAD command.
+#: Programs/cmds.auto.h:457
+msgid "restore preferences from disk"
+msgstr "gespeicherte Einstellungen wiederherstellen"
+
+#. xgettext: This is the description of the GUI_AREA_ACTV command.
+#: Programs/cmds.auto.h:1119
+#, fuzzy
+msgid "return to the active screen area"
+msgstr "Bildschirmtreiber starten"
+
+#. xgettext: This is the description of the CLIP_SAVE command.
+#: Programs/cmds.auto.h:857
+msgid "save clipboard to disk"
+msgstr "Speichert den Inhalt der Zwischenablage auf die Festplatte"
+
+#. xgettext: This is the description of the PREFSAVE command.
+#: Programs/cmds.auto.h:450
+msgid "save preferences to disk"
+msgstr "Einstellungen speichern"
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr ""
+
+#: Programs/config.c:2470
+msgid "screen driver not loadable"
+msgstr "Bildschirmtreiber kann nicht geladen werden"
+
+#: Programs/config.c:2591
+msgid "screen driver restarting"
+msgstr "Bildschirmtreiber wird neu gestartet"
+
+#: Programs/cmd_miscellaneous.c:171
+msgid "screen driver stopped"
+msgstr "Bildschirmtreiber gestoppt"
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr "Bildschirm gesperrt"
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr "Bildschirm nicht im Textmodus"
+
+#. xgettext: This is the description of the PRSEARCH command.
+#: Programs/cmds.auto.h:158
+msgid "search backward for clipboard text"
+msgstr "suche rückwärts nach Inhalt der Zwischenablage"
+
+#. xgettext: This is the description of the NXSEARCH command.
+#: Programs/cmds.auto.h:165
+msgid "search forward for clipboard text"
+msgstr "Suche vorwärts nach Inhalt der Zwischenablage"
+
+#: Programs/menu.c:469
+msgid "seconds"
+msgstr "Sekunden"
+
+#. xgettext: This is the description of the TXTSEL_ALL command.
+#: Programs/cmds.auto.h:1026
+msgid "select all of the text"
+msgstr "selektiere den gesamten Text"
+
+#. xgettext: This is the description of the MENU_NEXT_SETTING command.
+#: Programs/cmds.auto.h:507
+msgid "select next choice"
+msgstr "wähle nächste Möglichkeit"
+
+#. xgettext: This is the description of the MENU_PREV_SETTING command.
+#: Programs/cmds.auto.h:500
+msgid "select previous choice"
+msgstr "wähle vorherige Möglichkeit"
+
+#. xgettext: This is the description of the TUNES command.
+#: Programs/cmds.auto.h:399
+msgid "set alert tunes on/off"
+msgstr "schalte Signaltöne ein/aus"
+
+#. xgettext: This is the description of the ATTRBLINK command.
+#: Programs/cmds.auto.h:383
+msgid "set attribute blinking on/off"
+msgstr "schalte blinkende Attribute ein/aus"
+
+#. xgettext: This is the description of the ATTRVIS command.
+#: Programs/cmds.auto.h:375
+msgid "set attribute underlining on/off"
+msgstr "schalte unterstrichene Attribute ein/aus"
+
+#. xgettext: This is the description of the SET_ATTRIBUTES_TABLE command.
+#: Programs/cmds.auto.h:1370
+msgid "set attributes table"
+msgstr "Setze Attributtabelle"
+
+#. xgettext: This is the description of the AUTOREPEAT command.
+#: Programs/cmds.auto.h:407
+msgid "set autorepeat on/off"
+msgstr "schalte automatische Wiederholung ein/aus"
+
+#. xgettext: This is the description of the ASPK_CMP_WORDS command.
+#: Programs/cmds.auto.h:712
+msgid "set autospeak completed words on/off"
+msgstr "schalte automatisches Sprechen vervollständigter Wörter ein/aus"
+
+#. xgettext: This is the description of the ASPK_DEL_CHARS command.
+#: Programs/cmds.auto.h:696
+msgid "set autospeak deleted characters on/off"
+msgstr "schalte automatisches Sprechen gelöschter Zeichen ein/aus"
+
+#. xgettext: This is the description of the ASPK_INDENT command.
+#: Programs/cmds.auto.h:998
+msgid "set autospeak indent of current line on/off"
+msgstr "schalte automatisches Sprechen der Einrückung der aktuellen Zeile ein/aus"
+
+#. xgettext: This is the description of the ASPK_INS_CHARS command.
+#: Programs/cmds.auto.h:688
+msgid "set autospeak inserted characters on/off"
+msgstr "schalte automatisches Sprechen eingefügter Zeichen ein/aus"
+
+#. xgettext: This is the description of the AUTOSPEAK command.
+#: Programs/cmds.auto.h:415
+msgid "set autospeak on/off"
+msgstr "schalte automatisches Sprechen ein/aus"
+
+#. xgettext: This is the description of the ASPK_REP_CHARS command.
+#: Programs/cmds.auto.h:704
+msgid "set autospeak replaced characters on/off"
+msgstr "schalte automatisches Sprechen ersätzter Wörter ein/aus"
+
+#. xgettext: This is the description of the ASPK_SEL_CHAR command.
+#: Programs/cmds.auto.h:680
+msgid "set autospeak selected character on/off"
+msgstr "schalte automatisches Sprechen des hervorgehobenen Zeichens ein/aus"
+
+#. xgettext: This is the description of the ASPK_SEL_LINE command.
+#: Programs/cmds.auto.h:672
+msgid "set autospeak selected line on/off"
+msgstr "schalte automatisches Sprechen der hervorgehobenen Zeile ein/aus"
+
+#. xgettext: This is the description of the BRLKBD command.
+#: Programs/cmds.auto.h:880
+msgid "set braille keyboard enabled/disabled"
+msgstr "Setze Braille-Tastatur Ein/Aus"
+
+#. xgettext: This is the description of the BRLUCDOTS command.
+#: Programs/cmds.auto.h:872
+msgid "set braille typing mode dots/text"
+msgstr "Setze Braille Eingabemodus Punkte/Text"
+
+#. xgettext: This is the description of the CAPBLINK command.
+#: Programs/cmds.auto.h:391
+msgid "set capital letter blinking on/off"
+msgstr "schalte blinkende Großbuchstaben ein/aus"
+
+#. xgettext: This is the description of the CONTRACTED command.
+#: Programs/cmds.auto.h:1190
+msgid "set contracted/computer braille"
+msgstr ""
+
+#. xgettext: This is the description of the SET_CONTRACTION_TABLE command.
+#: Programs/cmds.auto.h:1378
+msgid "set contraction table"
+msgstr "Setze Kurzschrifttabelle"
+
+#. xgettext: This is the description of the DISPMD command.
+#: Programs/cmds.auto.h:295
+msgid "set display mode attributes/text"
+msgstr "wechsle zwischen Anzeigemodus Attribute/Text"
+
+#. xgettext: This is the description of the CSRHIDE command.
+#: Programs/cmds.auto.h:343
+msgid "set hidden screen cursor on/off"
+msgstr "Schalte versteckten Bildschirmcursor ein/aus"
+
+#. xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+#: Programs/cmds.auto.h:1386
+msgid "set keyboard table"
+msgstr "Setze Tastaturtabelle"
+
+#. xgettext: This is the description of the SET_LANGUAGE_PROFILE command.
+#: Programs/cmds.auto.h:1394
+msgid "set language profile"
+msgstr "Setze Sprachprofil"
+
+#. xgettext: This is the description of the CSRBLINK command.
+#: Programs/cmds.auto.h:367
+msgid "set screen cursor blinking on/off"
+msgstr "Schalte blinkenden Bildschirmcursor ein/aus"
+
+#. xgettext: This is the description of the CSRSIZE command.
+#: Programs/cmds.auto.h:359
+msgid "set screen cursor style block/underline"
+msgstr "Art des Bildschirmcursors umschalten Block/Unterstrich"
+
+#. xgettext: This is the description of the CSRVIS command.
+#: Programs/cmds.auto.h:335
+msgid "set screen cursor visibility on/off"
+msgstr "Schalte die Sichtbarkeit des Bildschirmcursors ein/aus"
+
+#. xgettext: This is the description of the FREEZE command.
+#: Programs/cmds.auto.h:287
+msgid "set screen image frozen/unfrozen"
+msgstr "Standbild ein-/ausschalten"
+
+#. xgettext: This is the description of the COMPBRL6 command.
+#: Programs/cmds.auto.h:1198
+msgid "set six/eight dot computer braille"
+msgstr ""
+
+#. xgettext: This is the description of the SKPBLNKWINS command.
+#: Programs/cmds.auto.h:327
+msgid "set skipping of blank braille windows on/off"
+msgstr "Schalte Überspringen leerer Braillezeileninhalte ein/aus"
+
+#. xgettext: This is the description of the SKPIDLNS command.
+#: Programs/cmds.auto.h:319
+msgid "set skipping of lines with identical content on/off"
+msgstr "schalte Überspringen von Zeilen mit identischem Inhalt ein/aus"
+
+#. xgettext: This is the description of the SLIDEWIN command.
+#: Programs/cmds.auto.h:311
+msgid "set sliding braille window on/off"
+msgstr "überlappende Braillezeilenausschnitte ein-/ausschalten"
+
+#. xgettext: This is the description of the SHOW_CURR_LOCN command.
+#: Programs/cmds.auto.h:850
+msgid "set speech cursor visibility on/off"
+msgstr "Schalte die Sichtbarkeit des Sprachcursors ein/aus"
+
+#. xgettext: This is the description of the TXTSEL_SET command.
+#: Programs/cmds.auto.h:1429
+#, fuzzy
+msgid "set text selection"
+msgstr "Setze Texttabelle"
+
+#. xgettext: This is the description of the SIXDOTS command.
+#: Programs/cmds.auto.h:303
+msgid "set text style 6-dot/8-dot"
+msgstr "wechsle zwischen 6-Punkt und 8-Punkt Braille"
+
+#. xgettext: This is the description of the SET_TEXT_TABLE command.
+#: Programs/cmds.auto.h:1362
+msgid "set text table"
+msgstr "Setze Texttabelle"
+
+#. xgettext: This is the description of the TOUCH_NAV command.
+#: Programs/cmds.auto.h:983
+msgid "set touch navigation on/off"
+msgstr "Schalte berührungsbasierte Navigation ein/aus"
+
+#. xgettext: This is the description of the CSRTRK command.
+#: Programs/cmds.auto.h:351
+msgid "set track screen cursor on/off"
+msgstr "Schalte die Verfolgung des Bildschirmcursors ein/aus"
+
+#. xgettext: This is the description of the TIME command.
+#: Programs/cmds.auto.h:656
+msgid "show current date and time"
+msgstr "Aktuelles Datum und Uhrzeit anzeigen"
+
+#. xgettext: This is the description of the GUI_TITLE command.
+#: Programs/cmds.auto.h:1054
+msgid "show the window title"
+msgstr "zeige den Fenstertitel"
+
+#. xgettext: This is the description of the INDICATORS command.
+#: Programs/cmds.auto.h:1012
+msgid "show various device status indicators"
+msgstr ""
+
+#: Programs/menu_prefs.c:1057
+msgid "soundcard digital audio"
+msgstr "Digital Audio Soundkarte"
+
+#: Programs/menu_prefs.c:1059
+msgid "soundcard synthesizer"
+msgstr "Synthesizer der Soundkarte"
+
+#: Programs/cmd.c:277 Programs/core.c:1051
+msgid "space"
+msgstr "leer"
+
+#. xgettext: This is the description of the SPEAK_CURR_CHAR command.
+#: Programs/cmds.auto.h:719
+msgid "speak current character"
+msgstr "sprich aktuelles Zeichen"
+
+#. xgettext: This is the description of the SAY_LINE command.
+#. xgettext: This is the description of the SPEAK_CURR_LINE command.
+#: Programs/cmds.auto.h:529 Programs/cmds.auto.h:765
+msgid "speak current line"
+msgstr "sprich aktuelle Zeile"
+
+#. xgettext: This is the description of the SPEAK_CURR_WORD command.
+#: Programs/cmds.auto.h:742
+msgid "speak current word"
+msgstr "sprich aktuelles Wort"
+
+#. xgettext: This is the description of the SAY_BELOW command.
+#: Programs/cmds.auto.h:543
+msgid "speak from current line through bottom of screen"
+msgstr "sprich von aktueller Zeile bis zum Ende des Bildschirms"
+
+#. xgettext: This is the description of the SAY_ALL command.
+#: Programs/cmds.auto.h:1182
+msgid "speak from top of screen through bottom of screen"
+msgstr ""
+
+#. xgettext: This is the description of the SAY_ABOVE command.
+#: Programs/cmds.auto.h:536
+msgid "speak from top of screen through current line"
+msgstr "sprich vom Beginn des Bildschirms bis zur aktuellen Zeile"
+
+#. xgettext: This is the description of the SPEAK_INDENT command.
+#: Programs/cmds.auto.h:990
+msgid "speak indent of current line"
+msgstr "sprich die Einrückung der aktuellen Zeile"
+
+#. xgettext: This is the description of the SPEAK_CURR_LOCN command.
+#: Programs/cmds.auto.h:842
+msgid "speak speech cursor location"
+msgstr "Sprich Sprachcursorposition"
+
+#: Programs/msgtest.c:50
+msgid "specifier"
+msgstr ""
+
+#: Programs/config.c:2242
+msgid "speech driver not loadable"
+msgstr "Sprachausgabentreiber kann nicht geladen werden"
+
+#: Programs/config.c:2391
+msgid "speech driver restarting"
+msgstr "Bildschirmtreiber wird neu gestartet"
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr "Bildschirmtreiber gestoppt"
+
+#. xgettext: This is the description of the SPELL_CURR_WORD command.
+#: Programs/cmds.auto.h:827
+msgid "spell current word"
+msgstr "buchstabiere aktuelles Wort"
+
+#. xgettext: This is the description of the CLIP_NEW command.
+#: Programs/cmds.auto.h:1215
+msgid "start new clipboard at character"
+msgstr "beginne neue Auswahl bei Zeichen"
+
+#. xgettext: This is the description of the TXTSEL_START command.
+#: Programs/cmds.auto.h:1421
+#, fuzzy
+msgid "start text selection"
+msgstr "Sprachausgabentreiber starten"
+
+#. xgettext: This is the description of the BRL_START command.
+#: Programs/cmds.auto.h:915
+msgid "start the braille driver"
+msgstr "Brailletreiber starten"
+
+#. xgettext: This is the description of the SCR_START command.
+#: Programs/cmds.auto.h:943
+msgid "start the screen driver"
+msgstr "Bildschirmtreiber starten"
+
+#. xgettext: This is the description of the SPK_START command.
+#: Programs/cmds.auto.h:929
+msgid "start the speech driver"
+msgstr "Sprachausgabentreiber starten"
+
+#. xgettext: This is the description of the MUTE command.
+#: Programs/cmds.auto.h:514
+msgid "stop speaking"
+msgstr "Sprechen stoppen"
+
+#. xgettext: This is the description of the BRL_STOP command.
+#: Programs/cmds.auto.h:908
+msgid "stop the braille driver"
+msgstr "Brailletreiber stoppen"
+
+#. xgettext: This is the description of the SCR_STOP command.
+#: Programs/cmds.auto.h:936
+msgid "stop the screen driver"
+msgstr "Bildschirmtreiber stoppen"
+
+#. xgettext: This is the description of the SPK_STOP command.
+#: Programs/cmds.auto.h:922
+msgid "stop the speech driver"
+msgstr "Sprachausgabentreiber stoppen"
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr "befremdlicher alter Fehlerbehandler\n"
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr "Zeichenkette"
+
+#. xgettext: This is the description of the CONTEXT command.
+#: Programs/cmds.auto.h:1495
+msgid "switch to command context"
+msgstr "wechsle in den Kommandokontext"
+
+#. xgettext: This is the description of the SWITCHVT command.
+#: Programs/cmds.auto.h:1247
+msgid "switch to specific virtual terminal"
+msgstr "Wechsle zu einem spezifischen virtuellen Terminal"
+
+#. xgettext: This is the description of the GUI_AREA_NEXT command.
+#: Programs/cmds.auto.h:1133
+#, fuzzy
+msgid "switch to the next screen area"
+msgstr "Wechsle zum nächsten virtuellen Terminal"
+
+#. xgettext: This is the description of the SWITCHVT_NEXT command.
+#: Programs/cmds.auto.h:585
+msgid "switch to the next virtual terminal"
+msgstr "Wechsle zum nächsten virtuellen Terminal"
+
+#. xgettext: This is the description of the GUI_AREA_PREV command.
+#: Programs/cmds.auto.h:1126
+#, fuzzy
+msgid "switch to the previous screen area"
+msgstr "Wechsle zum vorherigen virtuellen Terminal"
+
+#. xgettext: This is the description of the SWITCHVT_PREV command.
+#: Programs/cmds.auto.h:578
+msgid "switch to the previous virtual terminal"
+msgstr "Wechsle zum vorherigen virtuellen Terminal"
+
+#: Programs/pgmprivs_linux.c:1994
+msgid "switched to unprivileged user"
+msgstr ""
+
+#. xgettext: This is the description of the KEY_TAB command.
+#: Programs/cmds.auto.h:1519
+msgid "tab key"
+msgstr "Tabulator-Taste"
+
+#: Programs/config.c:379 Programs/config.c:386
+msgid "text"
+msgstr "Text"
+
+#: Programs/msgtest.c:45
+msgid "the locale directory containing the translations"
+msgstr ""
+
+#: Programs/msgtest.c:52
+msgid "the locale in which to look up a translation"
+msgstr ""
+
+#: Programs/msgtest.c:59
+msgid "the name of the domain containing the translations"
+msgstr ""
+
+#. xgettext: This is the description of the PASSDOTS command.
+#: Programs/cmds.auto.h:1463
+msgid "type braille dots"
+msgstr "Braillezeichen eingeben"
+
+#. xgettext: This is the description of the PASSCHAR command.
+#: Programs/cmds.auto.h:1454
+msgid "type unicode character"
+msgstr "Unicode-Zeichen eingeben"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr "unerwarteter Blocktyp"
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr "unerwartetes Kommando"
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr "nicht erkanntes Kommando"
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr "unbekanntes Kommando"
+
+#: Programs/options.c:828
+msgid "unknown configuration directive"
+msgstr "unbekannte Konfigurationsdirektive"
+
+#: Programs/config.c:791
+msgid "unknown log level or category"
+msgstr "unbekannte Protokollierungsausführlichkeit oder Kategorie"
+
+#. an unknown option has been specified
+#: Programs/options.c:575
+msgid "unknown option"
+msgstr "unbekannte Option"
+
+#: Programs/config.c:327
+msgid "unknown screen content quality"
+msgstr ""
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr "nicht unterstützter Parameter"
+
+#: Programs/spk.c:180
+msgid "volume"
+msgstr "Lautstärke"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "Weiß"
+
+#: Programs/pgmprivs_linux.c:1856
+#, fuzzy
+msgid "working directory changed"
+msgstr "Arbeitsverzeichnis"
+
+#: Programs/msgtest.c:65
+msgid "write the translations using UTF-8"
+msgstr ""
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr "xbrlapi: ungültiges Format für VT Nummer\n"
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr "xbrlapi: ungültiger Typ für VT Nummer\n"
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr "xbrlapi: mehr als ein Element für VT Nummer\n"
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr "xbrlapi: keine Elemente für VT Nummer\n"
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr ""
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr "xbrlapi: unbehandelter Ereignistyp: %d\n"
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr ""
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "Gelb"
+
diff --git a/Messages/fr.po b/Messages/fr.po
new file mode 100644
index 0000000..bd368e9
--- /dev/null
+++ b/Messages/fr.po
@@ -0,0 +1,4300 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 4.3\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-09-07 15:29+0200\n"
+"PO-Revision-Date: 2021-09-07 13:30+0000\n"
+"Last-Translator: Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr "\"%s\" démarré par \"%s\"\n"
+
+#. xgettext: This phrase describes the colour of a character on the screen.
+#. xgettext: %1$s is the (already translated) foreground colour.
+#. xgettext: %2$s is the (already translated) background colour.
+#: Programs/cmd_utils.c:169
+#, c-format
+msgid "%1$s on %2$s"
+msgstr "%1$s sur %2$s"
+
+#. xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+#. xgettext: (%u represents the number of hours)
+#: Programs/cmd_miscellaneous.c:89
+#, c-format
+msgid "%u o'clock"
+msgid_plural "%u o'clock"
+msgstr[0] "%u heure"
+msgstr[1] "%u heures"
+
+#. xgettext: This is a number (%u) of seconds (time units).
+#: Programs/cmd_miscellaneous.c:107
+#, c-format
+msgid "%u second"
+msgid_plural "%u seconds"
+msgstr[0] "%u seconde"
+msgstr[1] "%u secondes"
+
+#: Programs/menu_prefs.c:543 Programs/menu_prefs.c:544
+#: Programs/menu_prefs.c:546 Programs/menu_prefs.c:547
+#: Programs/menu_prefs.c:550 Programs/menu_prefs.c:551
+#: Programs/menu_prefs.c:552 Programs/menu_prefs.c:554
+#: Programs/menu_prefs.c:555 Programs/menu_prefs.c:561
+msgid "1 cell"
+msgstr "1 cellule"
+
+#: Programs/menu_prefs.c:900
+msgid "1 second"
+msgstr "1 seconde"
+
+#: Programs/menu_prefs.c:962
+msgid "10 seconds"
+msgstr "10 secondes"
+
+#: Programs/menu_prefs.c:1277
+msgid "12 Hour"
+msgstr "12 heures"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:553
+msgid "2 cells"
+msgstr "2 cellules"
+
+#: Programs/menu_prefs.c:901
+msgid "2 seconds"
+msgstr "2 secondes"
+
+#: Programs/menu_prefs.c:963
+msgid "20 seconds"
+msgstr "20 secondes"
+
+#: Programs/menu_prefs.c:1276
+msgid "24 Hour"
+msgstr "24 heures"
+
+#: Programs/menu_prefs.c:898
+msgid "250 milliseconds"
+msgstr "250 millisecondes"
+
+#: Programs/menu_prefs.c:557 Programs/menu_prefs.c:558
+#: Programs/menu_prefs.c:559 Programs/menu_prefs.c:560
+msgid "3 cells"
+msgstr "3 cellules"
+
+#: Programs/menu_prefs.c:964
+msgid "40 seconds"
+msgstr "40 secondes"
+
+#: Programs/menu_prefs.c:961
+msgid "5 seconds"
+msgstr "5 secondes"
+
+#: Programs/menu_prefs.c:899
+msgid "500 milliseconds"
+msgstr "500 millisecondes"
+
+#: Programs/menu_prefs.c:733
+msgid "6-dot"
+msgstr "6 points"
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr "Utilisation de caractères 8 bits"
+
+#: Programs/menu_prefs.c:732
+msgid "8-dot"
+msgstr "8 points"
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr "<Inactif>"
+
+#: Programs/config.c:1566
+msgid "API Parameter"
+msgstr "Paramètre de l'API"
+
+#. xgettext: This is the description of the PASSAT command.
+#: Programs/cmds.auto.h:1471
+msgid "AT (set 2) keyboard scan code"
+msgstr "Scancode AT du clavier (set 2)"
+
+#. xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+#: Programs/midi.c:145
+msgid "Accordion"
+msgstr "Accordéon"
+
+#. Bass
+#. xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+#: Programs/midi.c:180
+msgid "Acoustic Bass"
+msgstr "Basse accoustique"
+
+#. Piano
+#. xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+#: Programs/midi.c:80
+msgid "Acoustic Grand Piano"
+msgstr "Piano à queue accoustique"
+
+#. Guitar
+#. xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+#: Programs/midi.c:155
+msgid "Acoustic Guitar (nylon)"
+msgstr "Guitare accoustique (nylon)"
+
+#. xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+#: Programs/midi.c:158
+msgid "Acoustic Guitar (steel)"
+msgstr "Guitare accoustique (métal)"
+
+#: Programs/menu_prefs.c:1303
+msgid "After Time"
+msgstr "Après l'heure"
+
+#. xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+#: Programs/midi.c:434
+msgid "Agogo"
+msgstr "Agogo"
+
+#: Programs/menu_prefs.c:1491
+msgid "Alert"
+msgstr "Alerte"
+
+#: Programs/menu_prefs.c:1104
+msgid "Alert Dots"
+msgstr "Points d'avertissement"
+
+#: Programs/menu_prefs.c:1109
+msgid "Alert Messages"
+msgstr "Messages d'avertissement"
+
+#: Programs/menu_prefs.c:1049
+msgid "Alert Tunes"
+msgstr "Sons d'avertissement"
+
+#: Programs/menu_prefs.c:864 Programs/menu_prefs.c:1194
+msgid "All"
+msgstr "Toutes"
+
+#: Programs/menu_prefs.c:555
+msgid "Alphabetic Cursor Coordinates"
+msgstr "Coordonnées Alphabétiques du Curseur"
+
+#: Programs/menu_prefs.c:554
+msgid "Alphabetic Window Coordinates"
+msgstr "Coordonnées Alphabétiques de la Fenêtre"
+
+#. xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+#: Programs/midi.c:283
+msgid "Alto Saxophone"
+msgstr "Saxophone Alto"
+
+#. xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+#: Programs/midi.c:474
+msgid "Applause"
+msgstr "Applaudissements"
+
+#: Programs/log.c:118
+msgid "Async Events"
+msgstr "Événements asynchrones"
+
+#: Programs/menu_prefs.c:807
+msgid "Attributes Blink Period"
+msgstr "Période de Clignottement des Attributs"
+
+#: Programs/menu_prefs.c:815
+msgid "Attributes Percent Visible"
+msgstr "Pourcentage de Visibilité des Attributs"
+
+#: Programs/config.c:1059 Programs/menu_prefs.c:1406
+msgid "Attributes Table"
+msgstr "Table d'attributs"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr "Relâchement automatique"
+
+#: Programs/menu_prefs.c:967
+msgid "Autorelease Time"
+msgstr "Délai avant le relâchement automatique"
+
+#: Programs/menu_prefs.c:984
+msgid "Autorepeat Enabled"
+msgstr "Répétition Automatique Activée"
+
+#: Programs/menu_prefs.c:990
+msgid "Autorepeat Interval"
+msgstr "Intervalle de la répétition automatique"
+
+#: Programs/menu_prefs.c:997
+msgid "Autorepeat Panning"
+msgstr "Répétition automatique du défilement"
+
+#: Programs/menu_prefs.c:1119
+msgid "Autospeak"
+msgstr "Parole automatique"
+
+#: Programs/menu_prefs.c:1116
+msgid "Autospeak Options"
+msgstr "Options pour l'énonciation automatique"
+
+#: Programs/config.c:335
+msgid "Autospeak Threshold"
+msgstr "Seuil d'énonciation automatique"
+
+#: Programs/config.c:2012
+msgid "BRLTTY stopped"
+msgstr "Arrêt de BRLTTY"
+
+#. xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+#: Programs/midi.c:421
+msgid "Bag Pipe"
+msgstr "Cornemuse"
+
+#. xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+#: Programs/midi.c:409
+msgid "Banjo"
+msgstr "Banjo"
+
+#. xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+#: Programs/midi.c:289
+msgid "Baritone Saxophone"
+msgstr "Saxophone Baryton"
+
+#. xgettext: This is the name of MIDI musical instrument group #5.
+#: Programs/midi.c:37
+msgid "Bass"
+msgstr "Basse"
+
+#. xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+#: Programs/midi.c:298
+msgid "Bassoon"
+msgstr "Basson"
+
+#: Programs/menu_prefs.c:1056
+msgid "Beeper"
+msgstr "Beepeur"
+
+#: Programs/menu_prefs.c:1302
+msgid "Before Time"
+msgstr "Avant l'heure"
+
+#. xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+#: Programs/midi.c:465
+msgid "Bird Tweet"
+msgstr "Gazouillement d'oiseaux"
+
+#: Programs/menu_prefs.c:801
+msgid "Blinking Attributes"
+msgstr "Clignotement des attributs"
+
+#: Programs/menu_prefs.c:823
+msgid "Blinking Capitals"
+msgstr "Clignotement des majuscules"
+
+#: Programs/menu_prefs.c:774
+msgid "Blinking Screen Cursor"
+msgstr "Clignotement du curseur de l'écran"
+
+#: Programs/menu_prefs.c:1248
+msgid "Blinking Speech Cursor"
+msgstr "Clignotement du curseur de la synthèse"
+
+#: Programs/menu_prefs.c:665 Programs/menu_prefs.c:1362
+msgid "Block"
+msgstr "Pavé"
+
+#. xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+#: Programs/midi.c:317
+msgid "Blown Bottle"
+msgstr "Bouteille cassée"
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr "E/S Bluetooth"
+
+#: Programs/config.c:1787
+msgid "Braille Device"
+msgstr "Périphérique braille"
+
+#: Programs/config.c:1783
+msgid "Braille Driver"
+msgstr "Pilote braille"
+
+#: Programs/log.c:148
+msgid "Braille Driver Events"
+msgstr "Événements liés au pilote braille"
+
+#: Programs/menu_prefs.c:752
+msgid "Braille Firmness"
+msgstr "Rugosité du braille"
+
+#: Programs/log.c:82
+msgid "Braille Key Events"
+msgstr "Événements liés au clavier braille"
+
+#: Programs/config.c:1786
+msgid "Braille Parameter"
+msgstr "Paramètre braille"
+
+#: Programs/menu_prefs.c:698
+msgid "Braille Presentation"
+msgstr "Affichage braille"
+
+#: Programs/menu_prefs.c:1389
+msgid "Braille Tables"
+msgstr "Tables braille"
+
+#: Programs/menu_prefs.c:932
+msgid "Braille Typing"
+msgstr "Saisie Braille"
+
+#: Programs/menu_prefs.c:706
+msgid "Braille Variant"
+msgstr "Variante de Braille"
+
+#: Programs/menu_prefs.c:885
+msgid "Braille Window Overlap"
+msgstr "Chevauchement de fenêtre braille"
+
+#: Programs/config.c:552
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr "Code du pilote braille : (%s, %s, ou un parmi {%s})."
+
+#. xgettext: This is the name of MIDI musical instrument group #8.
+#: Programs/midi.c:46
+msgid "Brass"
+msgstr "Cuivres"
+
+#. xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+#: Programs/midi.c:270
+msgid "Brass Section"
+msgstr "Section cuivres"
+
+#. xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+#: Programs/midi.c:459
+msgid "Breath Noise"
+msgstr "Bruit de souffle"
+
+#. xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+#: Programs/midi.c:83
+msgid "Bright Acoustic Piano"
+msgstr "Piano accoustique brillant"
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr "Schémas d'autorisation/authentification de BrlAPI"
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr "Hôte de BrlAPI et port auquel se connecter"
+
+#: Programs/menu_prefs.c:1424
+msgid "Build Information"
+msgstr "Informations de construction"
+
+#: Programs/menu_prefs.c:725
+msgid "Capitalization Mode"
+msgstr "Mode de majuscules"
+
+#: Programs/menu_prefs.c:828
+msgid "Capitals Blink Period"
+msgstr "Période de Clignottement des Majuscules"
+
+#: Programs/menu_prefs.c:836
+msgid "Capitals Percent Visible"
+msgstr "Pourcentage de Visibilité des Majuscules"
+
+#: Programs/menu_prefs.c:1514
+msgid "Category Log Level"
+msgstr "Niveau de journalisation par catégorie"
+
+#. Chromatic Percussion
+#. xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+#: Programs/midi.c:105
+msgid "Celesta"
+msgstr "Celesta"
+
+#. xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+#: Programs/midi.c:211
+msgid "Cello"
+msgstr "violoncelle"
+
+#. xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+#: Programs/midi.c:242
+msgid "Choir Aahs"
+msgstr "Haha de choeur"
+
+#. xgettext: This is the name of MIDI musical instrument group #2.
+#: Programs/midi.c:28
+msgid "Chromatic Percussion"
+msgstr "Percussions chromatiques"
+
+#. xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+#: Programs/midi.c:139
+msgid "Church Organ"
+msgstr "Orgue d'église"
+
+#. xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+#: Programs/midi.c:301
+msgid "Clarinet"
+msgstr "Clarinette"
+
+#. xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+#: Programs/midi.c:101
+msgid "Clavinet"
+msgstr "Clavinet"
+
+#: Programs/menu.c:894
+msgid "Close"
+msgstr "Fermer"
+
+#: Programs/menu_prefs.c:1286
+msgid "Colon"
+msgstr "Deux-points"
+
+#: Programs/menu_prefs.c:702
+msgid "Computer Braille"
+msgstr "Braille Informatique"
+
+#: Programs/menu_prefs.c:736
+msgid "Computer Braille Cell Type"
+msgstr "Cellule de type Braille Informatique"
+
+#: Programs/menu_prefs.c:1448
+msgid "Configuration Directory"
+msgstr "Répertoire de configuration"
+
+#: Programs/config.c:2955 Programs/menu_prefs.c:1453
+msgid "Configuration File"
+msgstr "Fichier de configuration"
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr "Beep de la console"
+
+#: Programs/menu_prefs.c:1035
+msgid "Console Bell Alert"
+msgstr "Beep d'alerte de la console"
+
+#. xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+#: Programs/midi.c:214
+msgid "Contrabass"
+msgstr "Contrebasse"
+
+#: Programs/menu_prefs.c:703
+msgid "Contracted Braille"
+msgstr "Braille abrégé"
+
+#: Programs/config.c:1027 Programs/menu_prefs.c:1399
+msgid "Contraction Table"
+msgstr "Table de braille abrégé"
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr "Table de braille abrégé."
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr "Table de vérification du braille abrégé."
+
+#: Programs/menu_prefs.c:1492
+msgid "Critical"
+msgstr "Critique"
+
+#: Programs/menu_prefs.c:546
+msgid "Cursor Column"
+msgstr "Colonne du Curseur"
+
+#: Programs/menu_prefs.c:545 Programs/menu_prefs.c:557
+msgid "Cursor Coordinates"
+msgstr "Coordonnées du Curseur"
+
+#: Programs/log.c:100
+msgid "Cursor Routing"
+msgstr "Déplacement du curseur"
+
+#: Programs/menu_prefs.c:547
+msgid "Cursor Row"
+msgstr "Ligne du Curseur"
+
+#: Programs/log.c:94
+msgid "Cursor Tracking"
+msgstr "Poursuite du curseur"
+
+#: Programs/menu_prefs.c:904
+msgid "Cursor Tracking Delay"
+msgstr "Durée de la poursuite du curseur"
+
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:559
+msgid "Cursor and Window Column"
+msgstr "Colonne du Curseur et de la Fenêtre"
+
+#: Programs/menu_prefs.c:549 Programs/menu_prefs.c:560
+msgid "Cursor and Window Row"
+msgstr "Ligne du Curseur et de la Fenêtre"
+
+#: Programs/menu_prefs.c:1324
+msgid "Dash"
+msgstr "Tiret"
+
+#: Programs/menu_prefs.c:1317
+msgid "Date Format"
+msgstr "Format de la date"
+
+#: Programs/menu_prefs.c:1306
+msgid "Date Position"
+msgstr "Position de la date"
+
+#: Programs/menu_prefs.c:1329
+msgid "Date Separator"
+msgstr "Séparateur de la date"
+
+#: Programs/menu_prefs.c:1314
+msgid "Day Month Year"
+msgstr "Jour Mois Année"
+
+#: Programs/menu_prefs.c:1497
+msgid "Debug"
+msgstr "Débogage"
+
+#: Programs/config.c:573
+msgid "Device for accessing braille display."
+msgstr "Périphérique d'accès à l'afficheur braille."
+
+#: Programs/config.c:532
+msgid "Disable the application programming interface."
+msgstr "Désactiver l'interface de programmation de l'application (API)."
+
+#. xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+#: Programs/midi.c:173
+msgid "Distortion Guitar"
+msgstr "Guitare avec distortion"
+
+#: Programs/config.c:675
+msgid "Do not autospeak when braille is not being used."
+msgstr "Pas de parole automatique quand le braille ne sera pas utilisé"
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr "Ne pas écrire de texte sur le périphérique braille"
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr "Ne se rabat pas sur des caractères en unicode"
+
+#: Programs/config.c:443
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr "Ne pas passer à un utilisateur non privilégié ou renoncer à tous les privilèges (appartenance à des groupes, capacités, etc.)."
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr "Fait"
+
+#: Programs/menu_prefs.c:1287 Programs/menu_prefs.c:1326
+msgid "Dot"
+msgstr "Point"
+
+#: Programs/menu_prefs.c:942
+msgid "Dots via Unicode Braille"
+msgstr "Points en braille unicode"
+
+#. Organ
+#. xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+#: Programs/midi.c:130
+msgid "Drawbar Organ"
+msgstr "Orgue drawbar"
+
+#: Programs/config.c:2976 Programs/menu_prefs.c:1473
+msgid "Drivers Directory"
+msgstr "Répertoire des pilotes"
+
+#. xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+#: Programs/midi.c:126
+msgid "Dulcimer"
+msgstr "Dulcimer"
+
+#: Programs/menu_prefs.c:879
+msgid "Eager Sliding Braille Window"
+msgstr "Défilement dynamique de la fenêtre braille"
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "Éditer la table"
+
+#. xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+#: Programs/midi.c:183
+msgid "Electric Bass (finger)"
+msgstr "Basse électrique (doigt)"
+
+#. xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+#: Programs/midi.c:186
+msgid "Electric Bass (pick)"
+msgstr "Basse électrique (plectre)"
+
+#. xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+#: Programs/midi.c:86
+msgid "Electric Grand Piano"
+msgstr "Piano à queue électrique"
+
+#. xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+#: Programs/midi.c:164
+msgid "Electric Guitar (clean)"
+msgstr "Guitare électrique (propre)"
+
+#. xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+#: Programs/midi.c:161
+msgid "Electric Guitar (jazz)"
+msgstr "Guitare électrique (jazz)"
+
+#. xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+#: Programs/midi.c:167
+msgid "Electric Guitar (muted)"
+msgstr "Guitare électrique (étouffée)"
+
+#. xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+#: Programs/midi.c:92
+msgid "Electric Piano 1"
+msgstr "Piano électrique 1"
+
+#. xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+#: Programs/midi.c:95
+msgid "Electric Piano 2"
+msgstr "Piano électrique 2"
+
+#: Programs/menu_prefs.c:1490
+msgid "Emergency"
+msgstr "Urgence"
+
+#: Programs/menu_prefs.c:541
+msgid "End"
+msgstr "Fin"
+
+#: Programs/menu_prefs.c:865
+msgid "End of Line"
+msgstr "Fin de la ligne"
+
+#. xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+#: Programs/midi.c:295
+msgid "English Horn"
+msgstr "Corne anglaise"
+
+#: Programs/menu_prefs.c:1229
+msgid "Enqueue"
+msgstr "Dans la file"
+
+#. xgettext: This is the name of MIDI musical instrument group #7.
+#: Programs/midi.c:43
+msgid "Ensemble"
+msgstr "Ensemble"
+
+#: Programs/menu_prefs.c:1493
+msgid "Error"
+msgstr "Erreur"
+
+#. xgettext: This is the name of MIDI musical instrument group #14.
+#: Programs/midi.c:68
+msgid "Ethnic Instruments"
+msgstr "Instruments folkloriques"
+
+#: Programs/menu_prefs.c:1032
+msgid "Event Alerts"
+msgstr "Signaux d'événements"
+
+#: Programs/menu_prefs.c:713
+msgid "Expand Current Word"
+msgstr "Extension du mot actuel"
+
+#: Programs/config.c:487
+msgid "Explicit preference settings."
+msgstr "Paramètres explicites d'une préférence."
+
+#: Programs/menu_prefs.c:1059
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:1097
+msgid "FM Volume"
+msgstr "Volume FM"
+
+#. Synth FM
+#. xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+#: Programs/midi.c:380
+msgid "FX 1 (rain)"
+msgstr "FX 1 (pluie)"
+
+#. xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+#: Programs/midi.c:383
+msgid "FX 2 (soundtrack)"
+msgstr "FX 2 (piste son)"
+
+#. xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+#: Programs/midi.c:386
+msgid "FX 3 (crystal)"
+msgstr "FX 3 (cristal)"
+
+#. xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+#: Programs/midi.c:389
+msgid "FX 4 (atmosphere)"
+msgstr "FX 4 (atmosphère)"
+
+#. xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+#: Programs/midi.c:392
+msgid "FX 5 (brightness)"
+msgstr "FX 5 (éclat)"
+
+#. xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+#: Programs/midi.c:395
+msgid "FX 6 (goblins)"
+msgstr "FX 6 (lutin)"
+
+#. xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+#: Programs/midi.c:398
+msgid "FX 7 (echoes)"
+msgstr "FX 7 (échos)"
+
+#. xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+#. xgettext: (sci-fi is a common short form for science fiction)
+#: Programs/midi.c:402
+msgid "FX 8 (sci-fi)"
+msgstr "FX 8 (SF)"
+
+#. xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+#: Programs/midi.c:424
+msgid "Fiddle"
+msgstr "violon"
+
+#. xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+#: Programs/midi.c:308
+msgid "Flute"
+msgstr "flûte"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr "Forcer une sortie immédiate."
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr "Format de chacune des lignes de sortie."
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr "Format du fichier d'entrée."
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr "Format du fichier de sortie."
+
+#. xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+#: Programs/midi.c:267
+msgid "French Horn"
+msgstr "Corne française"
+
+#. xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+#: Programs/midi.c:189
+msgid "Fretless Bass"
+msgstr "Basse glissée"
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr "Gelé"
+
+#: Programs/menu_prefs.c:556
+msgid "Generic"
+msgstr "Générique"
+
+#: Programs/log.c:64
+msgid "Generic Input"
+msgstr "Entrée générique"
+
+#. xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+#: Programs/midi.c:108
+msgid "Glockenspiel"
+msgstr "Glockenspiel"
+
+#. xgettext: This is the name of MIDI musical instrument group #4.
+#: Programs/midi.c:34
+msgid "Guitar"
+msgstr "Guitare"
+
+#. Sound Effects
+#. xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+#: Programs/midi.c:456
+msgid "Guitar Fret Noise"
+msgstr "Bruit de glissement sur une guitare"
+
+#. xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+#: Programs/midi.c:176
+msgid "Guitar Harmonics"
+msgstr "Harmoniques de guitare"
+
+#. xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+#: Programs/midi.c:477
+msgid "Gunshot"
+msgstr "Coup de feu"
+
+#. xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+#: Programs/midi.c:148
+msgid "Harmonica"
+msgstr "Harmonica"
+
+#. xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+#: Programs/midi.c:98
+msgid "Harpsichord"
+msgstr "Clavecin"
+
+#. xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+#: Programs/midi.c:471
+msgid "Helicopter"
+msgstr "Hélicoptère"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "Écran d'aide"
+
+#: Programs/menu_prefs.c:748 Programs/menu_prefs.c:1013
+msgid "High"
+msgstr "Forte"
+
+#: Programs/menu_prefs.c:921
+msgid "Highlight Braille Window Location"
+msgstr "Surlignement de l'emplacement de la fenêtre braille"
+
+#. xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+#: Programs/midi.c:89
+msgid "Honkytonk Piano"
+msgstr "Piano bastringue"
+
+#: Programs/menu_prefs.c:1228
+msgid "Immediate"
+msgstr "Immédiat"
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr "Bibliothèque XKB incompatible\n"
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr "Support du serveur XKB incompatible\n"
+
+#: Programs/menu_prefs.c:1496
+msgid "Information"
+msgstr "Informations"
+
+#: Programs/menu_prefs.c:956
+msgid "Input Options"
+msgstr "Options d'entrée"
+
+#: Programs/log.c:70
+msgid "Input Packets"
+msgstr "Paquets entrants"
+
+#: Programs/config.c:418
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr "Installez le service %s et quittez."
+
+#: Programs/menu_prefs.c:1500
+msgid "Internal Parameters"
+msgstr "Paramètres internes"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr "Classe Java non trouvée"
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr "Une exception Java s'est produite"
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr "Méthode Java non trouvée"
+
+#. xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+#: Programs/midi.c:418
+msgid "Kalimba"
+msgstr "Kalimba"
+
+#: Programs/config.c:1709
+msgid "Key Bindings"
+msgstr "Associations de touches"
+
+#: Programs/config.c:1237
+msgid "Key Help"
+msgstr "Touche Aide"
+
+#: Programs/config.c:1714 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr "Table de touches"
+
+#: Programs/menu_prefs.c:935
+msgid "Keyboard Enabled"
+msgstr "Clavier Activé"
+
+#: Programs/log.c:88
+msgid "Keyboard Key Events"
+msgstr "Événements liés aux touches du clavier"
+
+#: Programs/menu_prefs.c:1042
+msgid "Keyboard LED Alerts"
+msgstr "Alertes lumineuses du clavier"
+
+#: Programs/config.c:1322 Programs/menu_prefs.c:1024
+msgid "Keyboard Table"
+msgstr "Tables des touches du clavier"
+
+#. xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+#: Programs/midi.c:415
+msgid "Koto"
+msgstr "Koto"
+
+#: Programs/config.c:3112
+msgid "Language"
+msgstr "Langue"
+
+#. Synth Lead
+#. xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+#: Programs/midi.c:330
+msgid "Lead 1 (square)"
+msgstr "Position 1 (carré)"
+
+#. xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+#: Programs/midi.c:333
+msgid "Lead 2 (sawtooth)"
+msgstr "Position 2 (en dents de scie)"
+
+#. xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+#: Programs/midi.c:336
+msgid "Lead 3 (calliope)"
+msgstr "Position 3 (Calliope)"
+
+#. xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+#: Programs/midi.c:339
+msgid "Lead 4 (chiff)"
+msgstr "Position 4 (chiff)"
+
+#. xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+#: Programs/midi.c:342
+msgid "Lead 5 (charang)"
+msgstr "Position 5 (charang)"
+
+#. xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+#: Programs/midi.c:345
+msgid "Lead 6 (voice)"
+msgstr "Position 6 (voix)"
+
+#. xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+#: Programs/midi.c:348
+msgid "Lead 7 (fifths)"
+msgstr "Position 7 (cinquièmes)"
+
+#. xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+#: Programs/midi.c:351
+msgid "Lead 8 (bass + lead)"
+msgstr "Position 8 (basse + position)"
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "mode d'apprentissage"
+
+#: Programs/menu_prefs.c:1341
+msgid "Left"
+msgstr "Gauche"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr "Liste les noms des touches"
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr "Liste la table de touches au format écran d'aide"
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr "Liste la table de touches au format reStructuredText"
+
+#: Programs/menu_prefs.c:1483
+msgid "Locale Directory"
+msgstr "Répertoire de la locale"
+
+#: Programs/menu_prefs.c:1519
+msgid "Log Categories"
+msgstr "Catégories de journal"
+
+#: Programs/config.c:890
+msgid "Log Level"
+msgstr "Niveau de journalisation"
+
+#: Programs/menu_prefs.c:1580
+msgid "Log Messages"
+msgstr "Messages du journal"
+
+#: Programs/config.c:773
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr "Enregistre les versions de l'application principale, de l'API, des pilotes intégrés puis quitter"
+
+#: Programs/config.c:738
+msgid "Log to standard error rather than to the system log."
+msgstr "Enregistre sur l'erreur standard plutôt que sur le journal du système."
+
+#: Programs/config.c:752
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)."
+msgstr "Un niveau de journalisation (%s ou une valeur parmi {%s}) et/ou des atégories de journalisation à activer (n'importe quelle combinaison de {%s}, chacune éventuellement préfixée par %s pour la désactiver)."
+
+#: Programs/menu_prefs.c:978
+msgid "Long Press Time"
+msgstr "Durée d'appui prolongée"
+
+#: Programs/menu_prefs.c:746 Programs/menu_prefs.c:1011
+msgid "Low"
+msgstr "Faible"
+
+#: Programs/menu_prefs.c:666
+msgid "Lower Left Dot"
+msgstr "Point en bas à gauche"
+
+#: Programs/menu_prefs.c:667
+msgid "Lower Right Dot"
+msgstr "Point en bas à droite"
+
+#: Programs/menu_prefs.c:1058
+msgid "MIDI"
+msgstr "MIDI"
+
+#: Programs/config.c:722
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr "Identifiant du périphérique MIDI (Musical Instrument Digital Interface)."
+
+#: Programs/menu_prefs.c:1088
+msgid "MIDI Instrument"
+msgstr "Instrument MIDI"
+
+#: Programs/menu_prefs.c:1078
+msgid "MIDI Volume"
+msgstr "Volume MIDI"
+
+#: Programs/menu_prefs.c:1443
+msgid "Mailing List"
+msgstr "Liste de Diffusion"
+
+#. xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+#: Programs/midi.c:117
+msgid "Marimba"
+msgstr "Marimba"
+
+#: Programs/menu_prefs.c:749 Programs/menu_prefs.c:1014
+msgid "Maximum"
+msgstr "Maximum"
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr "Longueur maximale d'une ligne d'affichage"
+
+#: Programs/menu_prefs.c:747 Programs/menu_prefs.c:1012
+msgid "Medium"
+msgstr "Moyenne"
+
+#. xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+#: Programs/midi.c:446
+msgid "Melodic Tom"
+msgstr "tom mélodique"
+
+#: Programs/menu_prefs.c:679
+msgid "Menu Options"
+msgstr "Options du menu"
+
+#: Programs/config.c:731
+msgid "Message hold timeout (in 10ms units)."
+msgstr "Durée d'apparition du message (par unités de 10ms)."
+
+#: Programs/config.c:893
+msgid "Messages Directory"
+msgstr "Répertoire des Messages"
+
+#: Programs/config.c:892
+msgid "Messages Domain"
+msgstr "Domaine des Messages"
+
+#: Programs/config.c:891
+msgid "Messages Locale"
+msgstr "Locale des Messages"
+
+#: Programs/menu_prefs.c:745 Programs/menu_prefs.c:1010
+msgid "Minimum"
+msgstr "Minimum"
+
+#: Programs/config.c:682
+#, c-format
+msgid "Minimum screen content quality to autospeak (one of {%s})."
+msgstr "Le niveau minimal de qualité du contenu de l'écran à partir duquel activer l'énonciation automatique (une valeur parmi {%s})."
+
+#: Programs/menu_prefs.c:1313
+msgid "Month Day Year"
+msgstr "Mois Jour Année"
+
+#. xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+#: Programs/midi.c:111
+msgid "Music Box"
+msgstr "Boîte à musique"
+
+#: Programs/menu_prefs.c:1058
+msgid "Musical Instrument Digital Interface"
+msgstr "Interface numérique d'instrument de musique"
+
+#. xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+#: Programs/midi.c:264
+msgid "Muted Trumpet"
+msgstr "Trompette bouchée"
+
+#: Programs/config.c:623
+msgid "Name of or path to attributes table."
+msgstr "Nom ou chemin vers la table d'attributs."
+
+#: Programs/config.c:615
+msgid "Name of or path to contraction table."
+msgstr "Nom ou chemin vers la table de braille abrégé."
+
+#: Programs/config.c:479
+msgid "Name of or path to default preferences file."
+msgstr "Nom ou chemin vers le fichier des préférences par défaut."
+
+#: Programs/config.c:632
+msgid "Name of or path to keyboard table."
+msgstr "Nom ou chemin vers le fichier de la table de touches."
+
+#: Programs/config.c:668
+msgid "Name of or path to speech input object."
+msgstr "Nom ou chemin vers l'objet d'entrée de la synthèse."
+
+#: Programs/config.c:605
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr "Nom ou chemin vers la table de caractères"
+
+#: Programs/menu_prefs.c:845
+msgid "Navigation Options"
+msgstr "Options de navigation"
+
+#: Programs/menu.c:523
+msgid "No"
+msgstr "Non"
+
+#: Programs/menu_prefs.c:720
+msgid "No Capitalization"
+msgstr "Pas de mise en majuscule"
+
+#: Programs/menu_prefs.c:897 Programs/menu_prefs.c:1192
+#: Programs/menu_prefs.c:1205 Programs/menu_prefs.c:1218
+#: Programs/menu_prefs.c:1301 Programs/menu_prefs.c:1340
+#: Programs/menu_prefs.c:1360
+msgid "None"
+msgstr "Aucune"
+
+#: Programs/menu_prefs.c:1495
+msgid "Notice"
+msgstr "Remarque"
+
+#. xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+#: Programs/midi.c:292
+msgid "Oboe"
+msgstr "Hautbois"
+
+#. xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+#: Programs/midi.c:326
+msgid "Ocarina"
+msgstr "Ocarina"
+
+#: Programs/menu_prefs.c:960
+msgid "Off"
+msgstr "Inactif"
+
+#: Programs/config.c:1800
+msgid "Old Preferences File"
+msgstr "Ancien fichier des préférences"
+
+#: Programs/menu_prefs.c:973
+msgid "On First Release"
+msgstr "Au Premier Relâchement"
+
+#. xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+#: Programs/midi.c:251
+msgid "Orchestra Hit"
+msgstr "Orchestre de percussions"
+
+#. xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+#: Programs/midi.c:223
+msgid "Orchestral Harp"
+msgstr "Harpe d'orchestre"
+
+#. xgettext: This is the name of MIDI musical instrument group #3.
+#: Programs/midi.c:31
+msgid "Organ"
+msgstr "Orgue"
+
+#: Programs/log.c:76
+msgid "Output Packets"
+msgstr "Paquets sortants"
+
+#. xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+#: Programs/midi.c:170
+msgid "Overdriven Guitar"
+msgstr "Guitare overdriven"
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr "Mode PC"
+
+#: Programs/menu_prefs.c:1057
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:712
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr "Identifiant du périphérique PCM (carte son numérique)"
+
+#: Programs/menu_prefs.c:1070
+msgid "PCM Volume"
+msgstr "Volume PCM"
+
+#. xgettext: This is the description of the PASSPS2 command.
+#: Programs/cmds.auto.h:1487
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr "Scancode de clavier PS/2 (réglé à 3)"
+
+#: Programs/menu_prefs.c:1433
+msgid "Package Revision"
+msgstr "Révision du paquet"
+
+#: Programs/menu_prefs.c:1428
+msgid "Package Version"
+msgstr "Version du paquet"
+
+#. Synth Pad
+#. xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+#: Programs/midi.c:355
+msgid "Pad 1 (new age)"
+msgstr "bloc 1 (new age)"
+
+#. xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+#: Programs/midi.c:358
+msgid "Pad 2 (warm)"
+msgstr "bloc 2 (chaud)"
+
+#. xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+#: Programs/midi.c:361
+msgid "Pad 3 (polysynth)"
+msgstr "Bloc 3 (polysynthé)"
+
+#. xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+#: Programs/midi.c:364
+msgid "Pad 4 (choir)"
+msgstr "Bloc 4 (choeur)"
+
+#. xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+#: Programs/midi.c:367
+msgid "Pad 5 (bowed)"
+msgstr "Bloc 5 (huées)"
+
+#. xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+#: Programs/midi.c:370
+msgid "Pad 6 (metallic)"
+msgstr "Bloc 6 (métallique)"
+
+#. xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+#: Programs/midi.c:373
+msgid "Pad 7 (halo)"
+msgstr "Bloc 7 (acclamations)"
+
+#. xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+#: Programs/midi.c:376
+msgid "Pad 8 (sweep)"
+msgstr "Bloc 8 (coup de balai)"
+
+#. xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+#: Programs/midi.c:314
+msgid "Pan Flute"
+msgstr "Flûte de pan"
+
+#: Programs/config.c:541
+msgid "Parameters for the application programming interface."
+msgstr "Paramètres pour l'interface de programmation pour applications."
+
+#: Programs/config.c:563
+msgid "Parameters for the braille driver."
+msgstr "Paramètres pour le pilote braille."
+
+#: Programs/config.c:436
+msgid "Parameters for the privilege establishment stage."
+msgstr "Paramètres pour la phase d'établissement de privilège."
+
+#: Programs/config.c:703
+msgid "Parameters for the screen driver."
+msgstr "Paramètres pour le pilote de l'écran."
+
+#: Programs/config.c:660
+msgid "Parameters for the speech driver."
+msgstr "Paramètres pour le pilote de synthèse vocale."
+
+#: Programs/config.c:470
+msgid "Path to default settings file."
+msgstr "Chemin du fichier des paramètres par défaut."
+
+#: Programs/config.c:524
+msgid "Path to directory containing drivers."
+msgstr "Chemin vers le répertoire contenant les pilotes."
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:595
+msgid "Path to directory containing tables."
+msgstr "Chemin du répertoire contenant les tables."
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr "Chemin du répertoire contenant les tables de caractères."
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr "Chemin du répertoire de chargement des pilotes."
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr "Chemin du répertoire des tables de texte"
+
+#: Programs/brltest.c:78 Programs/config.c:514
+msgid "Path to directory which can be written to."
+msgstr "Chemin du répertoire où il est possible d'écrire."
+
+#: Programs/config.c:504
+msgid "Path to directory which contains files that can be updated."
+msgstr "Chemin du répertoire où des fichiers peuvent être mis à jour."
+
+#: Programs/config.c:404
+msgid "Path to directory which contains message localizations."
+msgstr "Chemin vers le répertoire contenant les traductions des messages"
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr "Chemin de la table de saisie de texte."
+
+#: Programs/config.c:761
+msgid "Path to log file."
+msgstr "Chemin du fichier journal."
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr "Chemin de la table de texte affiché"
+
+#: Programs/config.c:460
+msgid "Path to process identifier file."
+msgstr "Chemin du fichier d'identifiants de processus."
+
+#: Programs/config.c:494
+msgid "Patterns that match command prompts."
+msgstr "Motifs pour reconnaître les invites de commandes."
+
+#. xgettext: This is the name of MIDI musical instrument group #15.
+#: Programs/midi.c:71
+msgid "Percussive Instruments"
+msgstr "Instruments à percussions"
+
+#. xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+#: Programs/midi.c:133
+msgid "Percussive Organ"
+msgstr "Orgue à percussion"
+
+#. xgettext: This is the name of MIDI musical instrument group #1.
+#: Programs/midi.c:25
+msgid "Piano"
+msgstr "Piano"
+
+#. Pipe
+#. xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+#: Programs/midi.c:305
+msgid "Piccolo"
+msgstr "Piccolo"
+
+#. xgettext: This is the name of MIDI musical instrument group #10.
+#: Programs/midi.c:52
+msgid "Pipe"
+msgstr "Tuyau"
+
+#. xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+#: Programs/midi.c:220
+msgid "Pizzicato Strings"
+msgstr "Pizzicati"
+
+#: Drivers/Braille/Baum/braille.c:1158
+msgid "Powerdown"
+msgstr "Extinction"
+
+#: Programs/config.c:2956 Programs/menu_prefs.c:1463
+msgid "Preferences File"
+msgstr "Fichier des préférences"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr "Menu des préférences"
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr "Afficher un résumé de l'utilisation (toutes les options) et quitter."
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr "Affiche un résumé de l'utilisation (seulement les options couramment utilisées), puis quitte."
+
+#: Programs/config.c:844
+msgid "Privilege Parameter"
+msgstr "Paramètre de Privilège"
+
+#: Programs/menu_prefs.c:1414
+msgid "Profiles"
+msgstr "Profils"
+
+#: Programs/config.c:640
+msgid "Properties of eligible keyboards."
+msgstr "Propriétés des claviers éligibles."
+
+#: Programs/menu_prefs.c:950
+msgid "Quick Space"
+msgstr "Espace Rapide"
+
+#: Programs/menu_prefs.c:1209
+msgid "Raise Pitch"
+msgstr "Augmenter le ton"
+
+#: Programs/config.c:395
+msgid "Recognize environment variables."
+msgstr "Reconnaît les variables d'environnement."
+
+#. xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+#: Programs/midi.c:311
+msgid "Recorder"
+msgstr "Magnétophone"
+
+#. xgettext: This is the name of MIDI musical instrument group #9.
+#: Programs/midi.c:49
+msgid "Reed"
+msgstr "Anche"
+
+#. xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+#: Programs/midi.c:142
+msgid "Reed Organ"
+msgstr "Orgue à hanche"
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr "Entrée de reformatage."
+
+#: Programs/config.c:585
+msgid "Release braille device when screen or window is unreadable."
+msgstr "Abandonne le périphérique braille quand il n'est pas possible de lire l'écran ou la fenêtre."
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr "Rester un processus interactif"
+
+#: Programs/config.c:411
+msgid "Remain a foreground process."
+msgstr "Rappelle un processus en tâche de fond."
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr "Supprime les points sept et huit"
+
+#: Programs/config.c:426
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr "Supprime le service %s et quitte."
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr "Signaler des problèmes avec la table de touches."
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr "Signale les caractères dans une police à l'écran n'ayant pas été définis dans la table de caractères"
+
+#: Programs/menu_prefs.c:866
+msgid "Rest of Line"
+msgstr "Reste de la ligne"
+
+#: Programs/menu_prefs.c:1562
+msgid "Restart Braille Driver"
+msgstr "Redémarre le pilote braille"
+
+#: Programs/menu_prefs.c:1574
+msgid "Restart Screen Driver"
+msgstr "Redémarre le pilote de l'écran"
+
+#: Programs/menu_prefs.c:1568
+msgid "Restart Speech Driver"
+msgstr "Redémarre le pilote de synthèse vocale"
+
+#. xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+#: Programs/midi.c:452
+msgid "Reverse Cymbal"
+msgstr "Cymbales renversées"
+
+#: Programs/menu_prefs.c:1342
+msgid "Right"
+msgstr "Droite"
+
+#. xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+#: Programs/midi.c:136
+msgid "Rock Organ"
+msgstr "Orgue de rock"
+
+#: Programs/menu_prefs.c:674
+msgid "Save on Exit"
+msgstr "Enregistrer en quittant"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1208
+msgid "Say Cap"
+msgstr "Dire l'état du verrouillage majuscule"
+
+#: Programs/menu_prefs.c:1232
+msgid "Say Line Mode"
+msgstr "Mode Dire la ligne"
+
+#: Programs/menu_prefs.c:1219
+msgid "Say Space"
+msgstr "Dire les espaces"
+
+#: Programs/menu_prefs.c:780
+msgid "Screen Cursor Blink Period"
+msgstr "Période de Clignottement du Curseur de l'écran"
+
+#: Programs/menu_prefs.c:788
+msgid "Screen Cursor Percent Visible"
+msgstr "Pourcentage de Visibilité du Curseur de l'écran"
+
+#: Programs/menu_prefs.c:768
+msgid "Screen Cursor Style"
+msgstr "Apparence du curseur de l'écran"
+
+#: Programs/config.c:2453
+msgid "Screen Driver"
+msgstr "Pilote de l'écran"
+
+#: Programs/log.c:160
+msgid "Screen Driver Events"
+msgstr "Événements du pilote de l'écran"
+
+#: Programs/menu_prefs.c:550
+msgid "Screen Number"
+msgstr "Numéro de l'écran"
+
+#: Programs/config.c:2459
+msgid "Screen Parameter"
+msgstr "Paramètre de l'écran"
+
+#: Programs/config.c:693
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr "Code du pilote de l'écran : (%s, %s, ou un parmi {%s})."
+
+#: Programs/menu_prefs.c:891
+msgid "Scroll-aware Cursor Navigation"
+msgstr "Navigation du curseur sensible au défilement"
+
+#. xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+#: Programs/midi.c:462
+msgid "Seashore"
+msgstr "Bord de mer"
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr "E/S série"
+
+#: Programs/log.c:124
+msgid "Server Events"
+msgstr "Événements du serveur"
+
+#. xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+#: Programs/midi.c:320
+msgid "Shakuhachi"
+msgstr "Shakuhachi"
+
+#. xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+#: Programs/midi.c:412
+msgid "Shamisen"
+msgstr "Shamisen"
+
+#. xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+#: Programs/midi.c:427
+msgid "Shanai"
+msgstr "Shanai"
+
+#: Programs/menu_prefs.c:687
+msgid "Show Advanced Submenus"
+msgstr "Afficher les sous-menus avancés"
+
+#: Programs/menu_prefs.c:692
+msgid "Show All Items"
+msgstr "Afficher tous les éléments"
+
+#: Programs/menu_prefs.c:796
+msgid "Show Attributes"
+msgstr "Afficher les attributs"
+
+#: Programs/menu_prefs.c:763
+msgid "Show Screen Cursor"
+msgstr "Afficher le curseur de l'écran"
+
+#: Programs/menu_prefs.c:1295
+msgid "Show Seconds"
+msgstr "Afficher les secondes"
+
+#: Programs/menu_prefs.c:1237
+msgid "Show Speech Cursor"
+msgstr "Afficher le curseur de la synthèse"
+
+#: Programs/menu_prefs.c:682
+msgid "Show Submenu Sizes"
+msgstr "Afficher la taille des sous-menus"
+
+#. Ethnic Instruments
+#. xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+#: Programs/midi.c:406
+msgid "Sitar"
+msgstr "Cithare"
+
+#: Programs/menu_prefs.c:858
+msgid "Skip Blank Braille Windows"
+msgstr "Passer les fenêtres braille vierges"
+
+#: Programs/menu_prefs.c:852
+msgid "Skip Identical Lines"
+msgstr "Passer les lignes identiques"
+
+#: Programs/menu_prefs.c:869
+msgid "Skip Which Blank Braille Windows"
+msgstr "Quelles fenêtres braille vierges sauter."
+
+#. xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+#: Programs/midi.c:192
+msgid "Slap Bass 1"
+msgstr "Basse frappée 1"
+
+#. xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+#: Programs/midi.c:195
+msgid "Slap Bass 2"
+msgstr "Basse frappée 2"
+
+#: Programs/menu_prefs.c:1325
+msgid "Slash"
+msgstr "Barre oblique"
+
+#: Programs/menu_prefs.c:874
+msgid "Sliding Braille Window"
+msgstr "Faire défiler la fenêtre"
+
+#: Programs/menu_prefs.c:1193
+msgid "Some"
+msgstr "Quelques"
+
+#. Reed
+#. xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+#: Programs/midi.c:280
+msgid "Soprano Saxophone"
+msgstr "Saxophone Soprano"
+
+#. xgettext: This is the name of MIDI musical instrument group #16.
+#: Programs/midi.c:74
+msgid "Sound Effects"
+msgstr "Effets sonore"
+
+#: Programs/menu_prefs.c:561 Programs/menu_prefs.c:1361
+msgid "Space"
+msgstr "Espace"
+
+#: Programs/menu_prefs.c:1154
+msgid "Speak Completed Words"
+msgstr "Dire les mots complétés"
+
+#: Programs/menu_prefs.c:1142
+msgid "Speak Deleted Characters"
+msgstr "Dire les caractères effacés"
+
+#: Programs/menu_prefs.c:1136
+msgid "Speak Inserted Characters"
+msgstr "Dire les caractères insérés"
+
+#: Programs/menu_prefs.c:1160
+msgid "Speak Line Indent"
+msgstr "Énoncer l'indentation de la ligne"
+
+#: Programs/menu_prefs.c:1148
+msgid "Speak Replaced Characters"
+msgstr "Dire les caractères remplacés"
+
+#: Programs/menu_prefs.c:1130
+msgid "Speak Selected Character"
+msgstr "Dire le caractère sélectionné"
+
+#: Programs/menu_prefs.c:1124
+msgid "Speak Selected Line"
+msgstr "Lire la ligne sélectionnée"
+
+#: Programs/menu_prefs.c:1254
+msgid "Speech Cursor Blink Period"
+msgstr "Période de Clignottement du Curseur Vocal"
+
+#: Programs/menu_prefs.c:1262
+msgid "Speech Cursor Percent Visible"
+msgstr "Pourcentage de Visibilité du Curseur Vocal"
+
+#: Programs/menu_prefs.c:1242
+msgid "Speech Cursor Style"
+msgstr "Apparence du curseur de la synthèse"
+
+#: Programs/config.c:2228
+msgid "Speech Driver"
+msgstr "Pilote de synthèse vocale"
+
+#: Programs/log.c:154
+msgid "Speech Driver Events"
+msgstr "Événements du pilote de synthèse vocale"
+
+#: Programs/log.c:112
+msgid "Speech Events"
+msgstr "Événements liés à la parole"
+
+#. Create the file system object for speech input.
+#: Programs/config.c:3023
+msgid "Speech Input"
+msgstr "Entrée de la parole"
+
+#: Programs/menu_prefs.c:1167
+msgid "Speech Options"
+msgstr "Options de la synthèse"
+
+#: Programs/config.c:2231
+msgid "Speech Parameter"
+msgstr "Paramètre de synthèse vocale"
+
+#: Programs/menu_prefs.c:1184
+msgid "Speech Pitch"
+msgstr "Ton de la voix"
+
+#: Programs/menu_prefs.c:1197
+msgid "Speech Punctuation"
+msgstr "Ponctuation pour la synthèse"
+
+#: Programs/menu_prefs.c:1177
+msgid "Speech Rate"
+msgstr "Vitesse de la synthèse"
+
+#: Programs/menu_prefs.c:1212
+msgid "Speech Uppercase Indicator"
+msgstr "Indicateur vocal de majuscules"
+
+#: Programs/menu_prefs.c:1170
+msgid "Speech Volume"
+msgstr "Volume de la synthèse"
+
+#: Programs/menu_prefs.c:1222
+msgid "Speech Whitespace Indicator"
+msgstr "Indicateur vocal d'espace insécable"
+
+#: Programs/config.c:650
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr "Code du pilote de synthèse : (%s, %s, ou un parmi {%s})."
+
+#: Programs/menu_prefs.c:1509
+msgid "Standard Error Log Level"
+msgstr "Niveau standard de journalisation des erreurs"
+
+#: Programs/menu_prefs.c:926
+msgid "Start Selection with Routing Key"
+msgstr "Commence la sélection avec touche de routage"
+
+#: Programs/menu_prefs.c:551
+msgid "State Dots"
+msgstr "Points d'état"
+
+#: Programs/menu_prefs.c:552
+msgid "State Letter"
+msgstr "Lettre d'état"
+
+#: Programs/menu_prefs.c:1336
+msgid "Status Cells"
+msgstr "Cellules d'état"
+
+#: Programs/menu_prefs.c:1352
+msgid "Status Count"
+msgstr "Nombre d'états"
+
+#: Programs/menu_prefs.c:565
+msgid "Status Field"
+msgstr "Champ de l'état"
+
+#: Programs/menu_prefs.c:1345
+msgid "Status Position"
+msgstr "Position de l'état"
+
+#: Programs/menu_prefs.c:1367
+msgid "Status Separator"
+msgstr "Séparateur de l'état"
+
+#: Programs/menu_prefs.c:1363
+msgid "Status Side"
+msgstr "Limite de l'état"
+
+#. xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+#: Programs/midi.c:437
+msgid "Steel Drums"
+msgstr "Batterie métal"
+
+#: Programs/config.c:450
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr "Arrêter une session de %s en cours et quitter."
+
+#. Ensemble
+#. xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+#: Programs/midi.c:230
+msgid "String Ensemble 1"
+msgstr "Ensemble de cordes 1"
+
+#. xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+#: Programs/midi.c:233
+msgid "String Ensemble 2"
+msgstr "Ensemble de cordes 2"
+
+#. xgettext: This is the name of MIDI musical instrument group #6.
+#: Programs/midi.c:40
+msgid "Strings"
+msgstr "Cordes"
+
+#: Programs/menu_prefs.c:722
+msgid "Superimpose Dot 7"
+msgstr "Point 7 superposé"
+
+#: Programs/config.c:744
+msgid "Suppress start-up messages."
+msgstr "Supprime les messages au démarrage."
+
+#. xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+#: Programs/midi.c:198
+msgid "Synth Bass 1"
+msgstr "Basse synthétique 1"
+
+#. xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+#: Programs/midi.c:201
+msgid "Synth Bass 2"
+msgstr "Basse synthétique 2"
+
+#. xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+#: Programs/midi.c:449
+msgid "Synth Drum"
+msgstr "Batterie synthétique"
+
+#. xgettext: This is the name of MIDI musical instrument group #13.
+#. xgettext: (synth is a common short form for synthesizer)
+#. xgettext: (FM is the acronym for Frequency Modulation)
+#: Programs/midi.c:65
+msgid "Synth FM"
+msgstr "FM synthétique"
+
+#. xgettext: This is the name of MIDI musical instrument group #11.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:56
+msgid "Synth Lead"
+msgstr "Position synthé"
+
+#. xgettext: This is the name of MIDI musical instrument group #12.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:60
+msgid "Synth Pad"
+msgstr "Bloc synthé"
+
+#. xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+#: Programs/midi.c:248
+msgid "Synth Voice"
+msgstr "Voix synthétique"
+
+#. xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+#: Programs/midi.c:273
+msgid "SynthBrass 1"
+msgstr "Cuivre synthétique 1"
+
+#. xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+#: Programs/midi.c:276
+msgid "SynthBrass 2"
+msgstr "Cuivre synthétique 2"
+
+#. xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+#: Programs/midi.c:236
+msgid "SynthStrings 1"
+msgstr "Cordes synthétiques 1"
+
+#. xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+#: Programs/midi.c:239
+msgid "SynthStrings 2"
+msgstr "Cordes synthétiques 2"
+
+#: Programs/menu_prefs.c:1504
+msgid "System Log Level"
+msgstr "Niveau de journalisation du système"
+
+#: Programs/config.c:2977 Programs/menu_prefs.c:1478
+msgid "Tables Directory"
+msgstr "Répertoire des tables"
+
+#. xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+#: Programs/midi.c:443
+msgid "Taiko Drum"
+msgstr "Batterie Taiko"
+
+#. xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+#: Programs/midi.c:151
+msgid "Tango Accordion"
+msgstr "Accordéon tango"
+
+#. xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+#: Programs/midi.c:468
+msgid "Telephone Ring"
+msgstr "Sonnerie de téléphone"
+
+#. xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+#: Programs/midi.c:286
+msgid "Tenor Saxophone"
+msgstr "Saxophone Ténor"
+
+#: Programs/menu_prefs.c:760
+msgid "Text Indicators"
+msgstr "Indicateurs de texte"
+
+#: Programs/menu_prefs.c:1364
+msgid "Text Side"
+msgstr "Limite du texte"
+
+#: Programs/config.c:1003 Programs/menu_prefs.c:1392
+msgid "Text Table"
+msgstr "Table de texte"
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr "Table de caractères."
+
+#: Programs/config.c:381
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr "Texte à afficher au démarrage du pilote braille ou à dire quand le pilote de synthèse vocal se lance."
+
+#: Programs/config.c:388
+msgid "The text to be shown when the braille driver stops."
+msgstr "Texte à afficher quand le pilote braille s'arrête."
+
+#: Programs/menu_prefs.c:553
+msgid "Time"
+msgstr "Heure"
+
+#: Programs/menu_prefs.c:1280
+msgid "Time Format"
+msgstr "Format de l'heure"
+
+#: Programs/menu_prefs.c:1272
+msgid "Time Presentation"
+msgstr "Présentation de l'heure"
+
+#: Programs/menu_prefs.c:1290
+msgid "Time Separator"
+msgstr "Séparateur de l'heure"
+
+#. xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+#: Programs/midi.c:226
+msgid "Timpani"
+msgstr "Timbales"
+
+#. Percussive Instruments
+#. xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+#: Programs/midi.c:431
+msgid "Tinkle Bell"
+msgstr "Tintement de cloches"
+
+#: Programs/menu_prefs.c:1558
+msgid "Tools"
+msgstr "Outils"
+
+#: Programs/menu_prefs.c:1003
+msgid "Touch Navigation"
+msgstr "Navigation tactile"
+
+#: Programs/menu_prefs.c:1017
+msgid "Touch Sensitivity"
+msgstr "Sensibilité du tactile"
+
+#: Programs/menu_prefs.c:915
+msgid "Track Screen Pointer"
+msgstr "Poursuivre le pointeur de l'écran"
+
+#: Programs/menu_prefs.c:909
+msgid "Track Screen Scroll"
+msgstr "Suivre le défilement de l'écran"
+
+#: Programs/menu_prefs.c:941
+msgid "Translated via Text Table"
+msgstr "Traduit via la table de caractères"
+
+#. xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+#: Programs/midi.c:217
+msgid "Tremolo Strings"
+msgstr "Cordes avec tremolo"
+
+#. xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+#: Programs/midi.c:258
+msgid "Trombone"
+msgstr "Trombone"
+
+#. Brass
+#. xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+#: Programs/midi.c:255
+msgid "Trumpet"
+msgstr "Trompette"
+
+#. xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+#: Programs/midi.c:261
+msgid "Tuba"
+msgstr "Tuba"
+
+#. xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+#: Programs/midi.c:123
+msgid "Tubular Bells"
+msgstr "Cloches tubulaires"
+
+#: Programs/menu_prefs.c:1062
+msgid "Tune Device"
+msgstr "Périphérique pour les sons"
+
+#: Programs/menu_prefs.c:945
+msgid "Typing Mode"
+msgstr "Mode de saisie"
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr "E/S USB"
+
+#: Programs/menu_prefs.c:664
+msgid "Underline"
+msgstr "Souligné"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr "Dégelé"
+
+#: Programs/config.c:2974 Programs/menu_prefs.c:1458
+msgid "Updatable Directory"
+msgstr "Répertoire qu'on peut mettre à jour"
+
+#: Programs/log.c:106
+msgid "Update Events"
+msgstr "Événements de mise à jour"
+
+#: Programs/options.c:197
+msgid "Usage"
+msgstr "Utilisation"
+
+#: Programs/menu_prefs.c:721
+msgid "Use Capital Sign"
+msgstr "Utilisation du signe majuscule"
+
+#. xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+#: Programs/midi.c:114
+msgid "Vibraphone"
+msgstr "Vibraphone"
+
+#. xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+#: Programs/midi.c:208
+msgid "Viola"
+msgstr "Alto"
+
+#. Strings
+#. xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+#: Programs/midi.c:205
+msgid "Violin"
+msgstr "Violon"
+
+#. xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+#: Programs/midi.c:245
+msgid "Voice Oohs"
+msgstr "Hoos vocaux"
+
+#: Programs/menu_prefs.c:1494
+msgid "Warning"
+msgstr "Attention"
+
+#: Programs/menu_prefs.c:1438
+msgid "Web Site"
+msgstr "Site Öeb"
+
+#. xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+#: Programs/midi.c:323
+msgid "Whistle"
+msgstr "sifflet"
+
+#: Programs/menu_prefs.c:543
+msgid "Window Column"
+msgstr "Colonne de la Fenêtre"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:558
+msgid "Window Coordinates"
+msgstr "Coordonnées de la Fenêtre"
+
+#: Programs/menu_prefs.c:544
+msgid "Window Row"
+msgstr "Ligne de la Fenêtre"
+
+#. xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+#: Programs/midi.c:440
+msgid "Woodblock"
+msgstr "bloc de boip"
+
+#: Programs/menu_prefs.c:848
+msgid "Word Wrap"
+msgstr "Césure par mot"
+
+#: Programs/config.c:2948
+msgid "Working Directory"
+msgstr "Répertoire de travail"
+
+#: Programs/config.c:2975 Programs/menu_prefs.c:1468
+msgid "Writable Directory"
+msgstr "Répertoire accessible en écriture"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr "Écrire sortie de débogage sur stdout"
+
+#: Programs/config.c:767
+msgid "Write the start-up logs, and then exit."
+msgstr "Écrit les journaux de démarrage et quitte."
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr "Afficheur X auquel se connecter"
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr "problème d'accès au répertoire XDG utilisé lors de l'exécution"
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr "Répertoire XDG utilisé lors de l'exécution créé"
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr "Le répertoire XDG utilisé lors de l'exécution existe"
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr "XFree(wm_name) pour changer"
+
+#. xgettext: This is the description of the PASSXT command.
+#: Programs/cmds.auto.h:1479
+msgid "XT (set 1) keyboard scan code"
+msgstr "Scancode de clavier XT (set 1)"
+
+#. xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+#: Programs/midi.c:120
+msgid "Xylophone"
+msgstr "Xylophone"
+
+#: Programs/menu_prefs.c:1312
+msgid "Year Month Day"
+msgstr "Année Mois Jour"
+
+#: Programs/menu.c:524
+msgid "Yes"
+msgstr "Oui"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr "[hôte][:port]"
+
+#: Programs/menu_prefs.c:665
+msgid "all dots"
+msgstr "Tous les points"
+
+#: Programs/cmd_miscellaneous.c:104
+msgid "and"
+msgstr "et"
+
+#. xgettext: This is the description of the CLIP_APPEND command.
+#: Programs/cmds.auto.h:1346
+msgid "append characters to clipboard"
+msgstr "envoie des caractères dans le presse-papier"
+
+#. xgettext: This is the description of the CLIP_ADD command.
+#: Programs/cmds.auto.h:1223
+msgid "append to clipboard from character"
+msgstr "ajoute le caractère au presse-papier"
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr "au curseur"
+
+#. xgettext: This is the description of the KEY_BACKSPACE command.
+#: Programs/cmds.auto.h:1527
+msgid "backspace key"
+msgstr "Touche retour arrière"
+
+#: Drivers/Braille/Baum/braille.c:1149 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr "Batterie faible"
+
+#. xgettext: This is the description of the SELECTVT command.
+#: Programs/cmds.auto.h:1437
+msgid "bind to specific virtual terminal"
+msgstr "Envoie dans un terminal virtuel particulier"
+
+#. xgettext: This is the description of the SELECTVT_NEXT command.
+#: Programs/cmds.auto.h:957
+msgid "bind to the next virtual terminal"
+msgstr "Envoie au terminal virtuel suivant"
+
+#. xgettext: This is the description of the SELECTVT_PREV command.
+#: Programs/cmds.auto.h:950
+msgid "bind to the previous virtual terminal"
+msgstr "Envoie au terminal virtuel précédent"
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "noir"
+
+#: Programs/core.c:1125
+msgid "blank line"
+msgstr "ligne vierge"
+
+#: Programs/cmd_utils.c:173
+msgid "blinking"
+msgstr "clignottant"
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "bleu"
+
+#: Programs/config.c:2993
+#, c-format
+msgid "braille device not specified"
+msgstr "Périphérique braille non spécifié"
+
+#. xgettext: This is the description of the OFFLINE command.
+#: Programs/cmds.auto.h:621
+msgid "braille display temporarily unavailable"
+msgstr "Afficheur braille temporairement non disponible"
+
+#: Programs/config.c:1737
+msgid "braille driver initialization failed"
+msgstr "Échec de l'initialisation du pilote braille"
+
+#: Programs/config.c:1816
+msgid "braille driver not loadable"
+msgstr "Impossible de charger le pilote braille"
+
+#: Programs/config.c:2077
+msgid "braille driver restarting"
+msgstr "le pilote braille redémarre"
+
+#: Programs/cmd_miscellaneous.c:163
+msgid "braille driver stopped"
+msgstr "pilote braille arrêté"
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr "braille libéré"
+
+#. xgettext: This is the description of the ROUTE command.
+#: Programs/cmds.auto.h:1207
+msgid "bring screen cursor to character"
+msgstr "Amener le curseur de l'écran au caractère"
+
+#. xgettext: This is the description of the CSRJMP_VERT command.
+#: Programs/cmds.auto.h:593
+msgid "bring screen cursor to current line"
+msgstr "Amène le curseur de l'écran sur la ligne actuelle"
+
+#. xgettext: This is the description of the ROUTE_LINE command.
+#: Programs/cmds.auto.h:1404
+msgid "bring screen cursor to line"
+msgstr "amène le curseur de l'écran sur la ligne"
+
+#. xgettext: This is the description of the ROUTE_CURR_LOCN command.
+#: Programs/cmds.auto.h:835
+msgid "bring screen cursor to speech cursor"
+msgstr "Amène le curseur de l'écran au curseur de synthèse"
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "marron"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr "ne peut obtenir l'état de la console"
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr "ne peut ouvrir la console"
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr "ne peut lire le contenu de l'écran"
+
+#: Drivers/Screen/Linux/screen.c:1601
+msgid "can't read screen header"
+msgstr "ne peut lire l'en-tête de l'écran"
+
+#: Programs/ctb_translate.c:393
+msgid "cannot access internal contraction table"
+msgstr "ne peut accéder à la table d'abrègement interne"
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr "Ne peut pas compiler la table des attributs"
+
+#: Programs/ctb_translate.c:387
+msgid "cannot compile contraction table"
+msgstr "Ne peut pas compiler la table de braille abrégé"
+
+#: Programs/config.c:1720
+msgid "cannot compile key table"
+msgstr "Ne peut pas compiler la table des touches"
+
+#: Programs/config.c:1284
+msgid "cannot compile keyboard table"
+msgstr "Ne peut pas compiler la table des touches"
+
+#: Programs/ttb_translate.c:275
+msgid "cannot compile text table"
+msgstr "Ne peut pas compiler la table des texte"
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr "Ne peut pas se connecter au démon des périphériques braille de brltty sur %s\n"
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr "Ne peut pas se connecter à l'afficheur %s\n"
+
+#: Programs/file.c:381
+msgid "cannot create directory"
+msgstr "Ne peut pas créer de répertoire"
+
+#: Programs/program.c:150
+#, c-format
+msgid "cannot determine program directory"
+msgstr "Ne peut pas déterminer le répertoire du programme"
+
+#: Programs/config.c:2951 Programs/menu.c:618
+msgid "cannot determine working directory"
+msgstr "Ne peut pas déterminer le répertoire de travail"
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr "Ne peut pas trouver la procédure"
+
+#: Programs/program.c:158
+msgid "cannot fix install path"
+msgstr "Ne peut pas corriger le chemin d'installation"
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr "Ne peut pas récupérer de tty\n"
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr "Ne peut pas récupérer le tty %d\n"
+
+#: Programs/file.c:518
+msgid "cannot get working directory"
+msgstr "Ne peut pas obtenir de répertoire de travail"
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr "Ne peut pas extraire de fenêtres sur l'écran %d\n"
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr "Ne peut pas ignorer des touches\n"
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr "Ne peut pas charger la table des attributs"
+
+#: Programs/ctb_translate.c:407
+msgid "cannot load contraction table"
+msgstr "ne peut charger la table d'abrègement"
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr "Ne peut pas charger la bibliothèque"
+
+#: Programs/ttb_translate.c:295
+msgid "cannot load text table"
+msgstr "Ne peut pas charger la table de texte"
+
+#: Programs/file.c:370
+msgid "cannot make world writable"
+msgstr "ne peut donner la permission d'écrire à tout le monde"
+
+#: Programs/config.c:1239
+msgid "cannot open key help"
+msgstr "Ne peut pas ouvrir l'aide de la touche"
+
+#: Programs/program.c:262
+msgid "cannot open process identifier file"
+msgstr "Ne peut pas ouvrir le fichier d'identifiants de processus"
+
+#: Programs/menu.c:616
+msgid "cannot open working directory"
+msgstr "Ne peut pas ouvrir le répertoire de travail"
+
+#: Programs/prefs.c:363
+msgid "cannot read preferences file"
+msgstr "Ne peut pas lire le fichier des préférences"
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr "Ne peut pas paramétrer le focus sur %#010x\n"
+
+#: Programs/file.c:532 Programs/menu.c:605
+msgid "cannot set working directory"
+msgstr "Ne peut pas paramétrer le répertoire de travail"
+
+#: Programs/prefs.c:576
+msgid "cannot write to preferences file"
+msgstr "Ne peut pas écrire dans le fichier des préférences"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1070
+msgid "cap"
+msgstr "Verr.maj"
+
+#: Programs/menu_prefs.c:886 Programs/menu_prefs.c:1353
+msgid "cells"
+msgstr "cellules"
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr "Changements annulés"
+
+#. xgettext: This is the description of the UNSTICK command.
+#: Programs/cmds.auto.h:887
+msgid "clear all sticky input modifiers"
+msgstr "Vider les modificateurs d'entrée polluants"
+
+#. xgettext: This is the description of the TXTSEL_CLEAR command.
+#: Programs/cmds.auto.h:1019
+msgid "clear the text selection"
+msgstr "réinitialiser la sélection de texxte"
+
+#: Programs/cmd_speech.c:501
+msgid "column"
+msgstr "colonne"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr "Config"
+
+#: Programs/options.c:812
+msgid "configuration directive specified more than once"
+msgstr "Ligne de configuration spécifiée plus d'une fois"
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr "La console n'est pas utilisée"
+
+#: Programs/menu_prefs.c:1056
+msgid "console tone generator"
+msgstr "Générateur de beep de console"
+
+#. xgettext: This is the description of the CLIP_COPY command.
+#: Programs/cmds.auto.h:1338
+msgid "copy characters to clipboard"
+msgstr "Copie les caractères dans le presse-papier"
+
+#. xgettext: This is the description of the HOST_COPY command.
+#: Programs/cmds.auto.h:1033
+msgid "copy selected text to host clipboard"
+msgstr "copie le texte sélectionné vers le presse-papier hôte"
+
+#: Programs/config.c:729
+msgid "csecs"
+msgstr "csecs"
+
+#. xgettext: This is the description of the TOUCH_AT command.
+#: Programs/cmds.auto.h:1503
+msgid "current reading location"
+msgstr "Emplacement courant de la lecture"
+
+#. xgettext: This is the description of the KEY_CURSOR_DOWN command.
+#: Programs/cmds.auto.h:1567
+msgid "cursor-down key"
+msgstr "Touche curseur vers le bas"
+
+#. xgettext: This is the description of the KEY_CURSOR_LEFT command.
+#: Programs/cmds.auto.h:1543
+msgid "cursor-left key"
+msgstr "Touche curseur à gauche"
+
+#. xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+#: Programs/cmds.auto.h:1551
+msgid "cursor-right key"
+msgstr "Touche curseur à droite"
+
+#. xgettext: This is the description of the KEY_CURSOR_UP command.
+#: Programs/cmds.auto.h:1559
+msgid "cursor-up key"
+msgstr "Touche curseur vers le haut"
+
+#. xgettext: This is the description of the HOST_CUT command.
+#: Programs/cmds.auto.h:1040
+msgid "cut selected text to host clipboard"
+msgstr "coupe le texte sélectionné vers le presse-papier hôte"
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr "cyan"
+
+#. xgettext: This is the description of the ALTGR command.
+#: Programs/cmds.auto.h:894
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr "Change le cycle de l'action d'AltGr (Alt Droite) (la prochaine fois, activé, désactivé)"
+
+#. xgettext: This is the description of the CONTROL command.
+#: Programs/cmds.auto.h:642
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr "Change le cycle du modificateur Contrôle (la prochaine fois, activé, désactivé)"
+
+#. xgettext: This is the description of the GUI command.
+#: Programs/cmds.auto.h:901
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr "Change le cycle du modificateur Touche Windows (La prochaine fois, activé, désactivé)"
+
+#. xgettext: This is the description of the META command.
+#: Programs/cmds.auto.h:649
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr "Change le cycle du modificateur Meta (Alt Gauche) (La prochaine fois, activé, désactivé)"
+
+#. xgettext: This is the description of the SHIFT command.
+#: Programs/cmds.auto.h:628
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr "Change le cycle de la touche Majuscule (Shift), (La prochaine fois, activé, désactivé)"
+
+#. xgettext: This is the description of the UPPER command.
+#: Programs/cmds.auto.h:635
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr "Change le cycle de la touche Verrmaj (Upper), (La prochaine fois, activé, désactivé)"
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "gris foncé"
+
+#. xgettext: This is the description of the SAY_LOWER command.
+#: Programs/cmds.auto.h:1168
+msgid "decrease speaking pitch"
+msgstr "diminuer la hauteur de la voix"
+
+#. xgettext: This is the description of the SAY_SLOWER command.
+#: Programs/cmds.auto.h:550
+msgid "decrease speaking rate"
+msgstr "Diminuer la vitesse de la parole"
+
+#. xgettext: This is the description of the SAY_SOFTER command.
+#: Programs/cmds.auto.h:564
+msgid "decrease speaking volume"
+msgstr "Diminuer le volume de la parole"
+
+#. xgettext: This is the description of the KEY_DELETE command.
+#: Programs/cmds.auto.h:1615
+msgid "delete key"
+msgstr "Touche supprimer"
+
+#. xgettext: This is the description of the DESCCHAR command.
+#: Programs/cmds.auto.h:1275
+msgid "describe character"
+msgstr "Décrire le caractère"
+
+#. xgettext: This is the description of the DESC_CURR_CHAR command.
+#: Programs/cmds.auto.h:820
+msgid "describe current character"
+msgstr "Décrire le caractère actuel"
+
+#: Programs/config.c:710 Programs/config.c:720
+msgid "device"
+msgstr "périphérique"
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:400 Programs/config.c:500
+#: Programs/config.c:510 Programs/config.c:520 Programs/config.c:591
+msgid "directory"
+msgstr "répertoire"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr "écran"
+
+#. xgettext: This is the description of the NOOP command.
+#: Programs/cmds.auto.h:5
+msgid "do nothing"
+msgstr "Ne fait rien"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr "Fait"
+
+#: Programs/menu_prefs.c:666
+msgid "dot 7"
+msgstr "point 7"
+
+#: Programs/menu_prefs.c:667
+msgid "dot 8"
+msgstr "point 8"
+
+#: Programs/menu_prefs.c:664
+msgid "dots 7 and 8"
+msgstr "Points 7 et 8"
+
+#: Drivers/Braille/Baum/braille.c:1146
+msgid "driver request"
+msgstr "Requête du pilote"
+
+#: Programs/config.c:549 Programs/config.c:647 Programs/config.c:690
+msgid "driver,..."
+msgstr "pilote,..."
+
+#. xgettext: This is the description of the KEY_END command.
+#: Programs/cmds.auto.h:1599
+msgid "end key"
+msgstr "touche fin"
+
+#. xgettext: This is the description of the KEY_ENTER command.
+#: Programs/cmds.auto.h:1511
+msgid "enter key"
+msgstr "touche entrée"
+
+#. xgettext: This is the description of the LEARN command.
+#: Programs/cmds.auto.h:436
+msgid "enter/leave command learn mode"
+msgstr "entrer/quitter le mode d'apprentissage des commandes"
+
+#. xgettext: This is the description of the HELP command.
+#: Programs/cmds.auto.h:422
+msgid "enter/leave help display"
+msgstr "entrer/quitter l'affichage de l'aide"
+
+#. xgettext: This is the description of the PREFMENU command.
+#: Programs/cmds.auto.h:443
+msgid "enter/leave preferences menu"
+msgstr "entrer/quitter le menu des préférences"
+
+#. xgettext: This is the description of the INFO command.
+#: Programs/cmds.auto.h:429
+msgid "enter/leave status display"
+msgstr "entrer/quitter l'affichage de l'état"
+
+#. xgettext: This is the description of the KEY_ESCAPE command.
+#: Programs/cmds.auto.h:1535
+msgid "escape key"
+msgstr "Touche Échap"
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "Eurobraille"
+
+#. xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+#: Programs/cmd_miscellaneous.c:102
+msgid "exactly"
+msgstr "exactement"
+
+#: Programs/config.c:873
+msgid "excess argument"
+msgstr "Trop d'arguments"
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr "exécution de \"%s\" (à partir de \"%s\")\n"
+
+#: Programs/pgmprivs_linux.c:2045
+msgid "executing as the invoking user"
+msgstr "exécution en tant que l'utilisateur appelant"
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr "Échec de l'exécution de \"%s\" : %s\n"
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr "échec de la récupération du premier focus\n"
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:457
+#: Programs/config.c:466 Programs/config.c:476 Programs/config.c:602
+#: Programs/config.c:612 Programs/config.c:621 Programs/config.c:629
+#: Programs/config.c:666 Programs/config.c:759
+msgid "file"
+msgstr "fichier"
+
+#: Programs/options.c:998
+#, c-format
+msgid "file '%s' processing error."
+msgstr "Erreur de traitement du fichier '%s'"
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr "Échec du fork de \"%s\" : %s\n"
+
+#. xgettext: This is the description of the KEY_FUNCTION command.
+#: Programs/cmds.auto.h:1624
+msgid "function key"
+msgstr "Touche de fonction"
+
+#. xgettext: This is the description of the BACK command.
+#: Programs/cmds.auto.h:271
+msgid "go back after cursor tracking"
+msgstr "Revenir après la poursuite du curseur"
+
+#. xgettext: This is the description of the GUI_BACK command.
+#: Programs/cmds.auto.h:1077
+msgid "go back to the previous screen"
+msgstr "retourne à l'écran précédent"
+
+#. xgettext: This is the description of the FWINLT command.
+#: Programs/cmds.auto.h:210
+msgid "go backward one braille window"
+msgstr "Recule d'une fenêtre braille"
+
+#. xgettext: This is the description of the FWINLTSKIP command.
+#: Programs/cmds.auto.h:228
+msgid "go backward skipping blank braille windows"
+msgstr "Recule en sautant les fenêtres braille vides"
+
+#. xgettext: This is the description of the PRNBWIN command.
+#: Programs/cmds.auto.h:966
+msgid "go backward to nearest non-blank braille window"
+msgstr "Recule à la fenêtre braille non vide la plus proche"
+
+#. xgettext: This is the description of the LNDN command.
+#: Programs/cmds.auto.h:23
+msgid "go down one line"
+msgstr "Descendre d'une ligne"
+
+#. xgettext: This is the description of the WINDN command.
+#: Programs/cmds.auto.h:41
+msgid "go down several lines"
+msgstr "Descend de plusieurs lignes"
+
+#. xgettext: This is the description of the NXPGRPH command.
+#: Programs/cmds.auto.h:133
+msgid "go down to first line of next paragraph"
+msgstr "Descend à la première ligne du paragraphe suivant"
+
+#. xgettext: This is the description of the MENU_LAST_ITEM command.
+#: Programs/cmds.auto.h:475
+msgid "go down to last item"
+msgstr "Descend au dernier élément"
+
+#. xgettext: This is the description of the NXDIFCHAR command.
+#: Programs/cmds.auto.h:1330
+msgid "go down to nearest line with different character"
+msgstr "Descend à la ligne la plus proche ayant un caractère différent"
+
+#. xgettext: This is the description of the NXDIFLN command.
+#: Programs/cmds.auto.h:59
+msgid "go down to nearest line with different content"
+msgstr "Descend à la ligne la plus proche ayant un contenu différent"
+
+#. xgettext: This is the description of the ATTRDN command.
+#: Programs/cmds.auto.h:77
+msgid "go down to nearest line with different highlighting"
+msgstr "Descend à la ligne la plus proche ayant un surlignement différent"
+
+#. xgettext: This is the description of the NXINDENT command.
+#: Programs/cmds.auto.h:1267
+msgid "go down to nearest line with less indent than character"
+msgstr "Descend à la ligne la plus proche ayant moins d'indentation que de caractère"
+
+#. xgettext: This is the description of the NXPROMPT command.
+#: Programs/cmds.auto.h:151
+msgid "go down to next command prompt"
+msgstr "Descend à l'invite de commande suivant"
+
+#. xgettext: This is the description of the MENU_NEXT_ITEM command.
+#: Programs/cmds.auto.h:493
+msgid "go down to next item"
+msgstr "Descend à l'élément suivant"
+
+#. xgettext: This is the description of the FWINRT command.
+#: Programs/cmds.auto.h:219
+msgid "go forward one braille window"
+msgstr "Avance d'une fenêtre braille"
+
+#. xgettext: This is the description of the FWINRTSKIP command.
+#: Programs/cmds.auto.h:237
+msgid "go forward skipping blank braille windows"
+msgstr "Avance en sautant les fenêtres braille vides"
+
+#. xgettext: This is the description of the NXNBWIN command.
+#: Programs/cmds.auto.h:975
+msgid "go forward to nearest non-blank braille window"
+msgstr "Va à la prochaine fenêtre braille non vide"
+
+#. xgettext: This is the description of the HWINLT command.
+#: Programs/cmds.auto.h:192
+msgid "go left half a braille window"
+msgstr "Va d'une demi-fenêtre braille à gauche"
+
+#. xgettext: This is the description of the CHRLT command.
+#: Programs/cmds.auto.h:174
+msgid "go left one character"
+msgstr "Va au caractère à gauche"
+
+#. xgettext: This is the description of the HWINRT command.
+#: Programs/cmds.auto.h:201
+msgid "go right half a braille window"
+msgstr "Va d'une demi-fenêtre braille à droite"
+
+#. xgettext: This is the description of the CHRRT command.
+#: Programs/cmds.auto.h:183
+msgid "go right one character"
+msgstr "Va à droite d'un caractère"
+
+#. xgettext: This is the description of the SPEAK_FRST_CHAR command.
+#: Programs/cmds.auto.h:789
+msgid "go to and speak first non-blank character on line"
+msgstr "Aller et lire le premier caractère non-vide de la ligne"
+
+#. xgettext: This is the description of the SPEAK_FRST_LINE command.
+#: Programs/cmds.auto.h:805
+msgid "go to and speak first non-blank line on screen"
+msgstr "Aller et lire la première ligne non-vide de l'écran"
+
+#. xgettext: This is the description of the SPEAK_LAST_CHAR command.
+#: Programs/cmds.auto.h:797
+msgid "go to and speak last non-blank character on line"
+msgstr "Aller et lire le dernier caractère non-vide de la ligne"
+
+#. xgettext: This is the description of the SPEAK_LAST_LINE command.
+#: Programs/cmds.auto.h:813
+msgid "go to and speak last non-blank line on screen"
+msgstr "Aller et lire la dernière ligne non-vide de l'écran"
+
+#. xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+#: Programs/cmds.auto.h:735
+msgid "go to and speak next character"
+msgstr "Va et lit le caractère suivant"
+
+#. xgettext: This is the description of the SPEAK_NEXT_LINE command.
+#: Programs/cmds.auto.h:781
+msgid "go to and speak next line"
+msgstr "Va et lit la ligne suivante"
+
+#. xgettext: This is the description of the SPEAK_NEXT_WORD command.
+#: Programs/cmds.auto.h:758
+msgid "go to and speak next word"
+msgstr "Aller et lire le mot suivant"
+
+#. xgettext: This is the description of the SPEAK_PREV_CHAR command.
+#: Programs/cmds.auto.h:727
+msgid "go to and speak previous character"
+msgstr "Va et lit le caractère précédent"
+
+#. xgettext: This is the description of the SPEAK_PREV_LINE command.
+#: Programs/cmds.auto.h:773
+msgid "go to and speak previous line"
+msgstr "Va et lit la ligne précédente"
+
+#. xgettext: This is the description of the SPEAK_PREV_WORD command.
+#: Programs/cmds.auto.h:750
+msgid "go to and speak previous word"
+msgstr "Va et lit le mot précédent"
+
+#. xgettext: This is the description of the BOT_LEFT command.
+#: Programs/cmds.auto.h:115
+msgid "go to beginning of bottom line"
+msgstr "Va au début de la toute dernière ligne"
+
+#. xgettext: This is the description of the LNBEG command.
+#: Programs/cmds.auto.h:246
+msgid "go to beginning of line"
+msgstr "Va au début de la ligne"
+
+#. xgettext: This is the description of the TOP_LEFT command.
+#: Programs/cmds.auto.h:105
+msgid "go to beginning of top line"
+msgstr "Va au début de la toute première ligne"
+
+#. xgettext: This is the description of the BOT command.
+#: Programs/cmds.auto.h:95
+msgid "go to bottom line"
+msgstr "Va à la toute dernière ligne"
+
+#. xgettext: This is the description of the SPKHOME command.
+#: Programs/cmds.auto.h:522
+msgid "go to current speaking position"
+msgstr "Va à la position actuelle de la synthèse vocale"
+
+#. xgettext: This is the description of the LNEND command.
+#: Programs/cmds.auto.h:255
+msgid "go to end of line"
+msgstr "Va à la fin de la ligne"
+
+#. xgettext: This is the description of the MENU_PREV_LEVEL command.
+#: Programs/cmds.auto.h:664
+msgid "go to previous menu level"
+msgstr "Va au niveau de menu précédent"
+
+#. xgettext: This is the description of the GOTOMARK command.
+#: Programs/cmds.auto.h:1300
+msgid "go to remembered braille window position"
+msgstr "Va à la position de la fenêtre braille dont on se rappelle"
+
+#. xgettext: This is the description of the HOME command.
+#: Programs/cmds.auto.h:263
+msgid "go to screen cursor"
+msgstr "Va au curseur de l'écran"
+
+#. xgettext: This is the description of the RETURN command.
+#: Programs/cmds.auto.h:279
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr "Va au curseur de l'écran ou revient d'une poursuite du curseur"
+
+#. xgettext: This is the description of the GOTOLINE command.
+#: Programs/cmds.auto.h:1310
+msgid "go to selected line"
+msgstr "Va à la ligne sélectionnée"
+
+#. xgettext: This is the description of the GUI_HOME command.
+#: Programs/cmds.auto.h:1069
+msgid "go to the home screen"
+msgstr "va à l'écran d'accueil"
+
+#. xgettext: This is the description of the TOP command.
+#: Programs/cmds.auto.h:86
+msgid "go to top line"
+msgstr "Va à la toute première ligne"
+
+#. xgettext: This is the description of the LNUP command.
+#: Programs/cmds.auto.h:14
+msgid "go up one line"
+msgstr "Remonte d'une ligne"
+
+#. xgettext: This is the description of the WINUP command.
+#: Programs/cmds.auto.h:32
+msgid "go up several lines"
+msgstr "Remonte de plusieurs ligne"
+
+#. xgettext: This is the description of the MENU_FIRST_ITEM command.
+#: Programs/cmds.auto.h:466
+msgid "go up to first item"
+msgstr "Remonte au premier élément"
+
+#. xgettext: This is the description of the PRPGRPH command.
+#: Programs/cmds.auto.h:124
+msgid "go up to first line of paragraph"
+msgstr "Monte à la première ligne du paragraphe"
+
+#. xgettext: This is the description of the PRDIFCHAR command.
+#: Programs/cmds.auto.h:1320
+msgid "go up to nearest line with different character"
+msgstr "Remonte à la ligne la plus proche ayant un caractère différent"
+
+#. xgettext: This is the description of the PRDIFLN command.
+#: Programs/cmds.auto.h:50
+msgid "go up to nearest line with different content"
+msgstr "Remonte à la ligne la plus proche ayant un contenu différent"
+
+#. xgettext: This is the description of the ATTRUP command.
+#: Programs/cmds.auto.h:68
+msgid "go up to nearest line with different highlighting"
+msgstr "Remonte à la ligne la plus proche ayant un surlignement différent"
+
+#. xgettext: This is the description of the PRINDENT command.
+#: Programs/cmds.auto.h:1257
+msgid "go up to nearest line with less indent than character"
+msgstr "Remonte à la ligne la plus proche ayant moins d'indentation que de caractère"
+
+#. xgettext: This is the description of the PRPROMPT command.
+#: Programs/cmds.auto.h:142
+msgid "go up to previous command prompt"
+msgstr "Remonte à l'invite de commande précédent"
+
+#. xgettext: This is the description of the MENU_PREV_ITEM command.
+#: Programs/cmds.auto.h:484
+msgid "go up to previous item"
+msgstr "Remonte à l'élément précédent"
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "vert"
+
+#: Programs/pgmprivs_linux.c:2167
+msgid "group permissions added"
+msgstr "permissions de groupe ajoutées"
+
+#: Programs/cmd_miscellaneous.c:213
+msgid "help not available"
+msgstr "Aide non disponible"
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr "Écran d'aide illisible"
+
+#. xgettext: This is the description of the KEY_HOME command.
+#: Programs/cmds.auto.h:1591
+msgid "home key"
+msgstr "Touche origine"
+
+#: Programs/config.c:570
+msgid "identifier,..."
+msgstr "identifiant,..."
+
+#: Drivers/Braille/Baum/braille.c:1148
+msgid "idle timeout"
+msgstr "Délai d'inactivité"
+
+#. xgettext: This is the description of the SAY_HIGHER command.
+#: Programs/cmds.auto.h:1175
+msgid "increase speaking pitch"
+msgstr "augmenter la hauteur de la voix"
+
+#. xgettext: This is the description of the SAY_FASTER command.
+#: Programs/cmds.auto.h:557
+msgid "increase speaking rate"
+msgstr "Augmente le débit de la parole"
+
+#. xgettext: This is the description of the SAY_LOUDER command.
+#: Programs/cmds.auto.h:571
+msgid "increase speaking volume"
+msgstr "Augmente le volume de la parole"
+
+#: Programs/core.c:1128
+msgid "indent"
+msgstr "indentation"
+
+#. xgettext: This is the description of the PASTE_HISTORY command.
+#: Programs/cmds.auto.h:1354
+msgid "insert clipboard history entry after screen cursor"
+msgstr "Insère l'entrée de l'historique du presse-papiers après le curseur de l'écran"
+
+#. xgettext: This is the description of the PASTE command.
+#: Programs/cmds.auto.h:600
+msgid "insert clipboard text after screen cursor"
+msgstr "insère le texte du presse-papier après le curseur de l'écran"
+
+#. xgettext: This is the description of the HOST_PASTE command.
+#: Programs/cmds.auto.h:1047
+msgid "insert host clipboard text after screen cursor"
+msgstr "insère le texte du presse-papier hôte après le curseur de l'écran"
+
+#. xgettext: This is the description of the KEY_INSERT command.
+#: Programs/cmds.auto.h:1607
+msgid "insert key"
+msgstr "touche Insère"
+
+#: Programs/program.c:166
+msgid "install path not absolute"
+msgstr "Chemin d'installation non absolu"
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr "Paramètre de compteur invalide"
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr "Séquence d'échappement non valide"
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr "Paramètre du drapeau invalide"
+
+#: Programs/config.c:2863
+msgid "invalid message hold timeout"
+msgstr "Délai d'apparition du message non valide"
+
+#. the operand for an option is invalid
+#: Programs/options.c:594
+msgid "invalid operand"
+msgstr "opérande invalide"
+
+#: Programs/brlapi_server.c:4516
+msgid "invalid thread stack size"
+msgstr "Taille de la pile du thread invalide"
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr "Désactivation de l'émulation du clavier"
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr "Émulation du clavier activée"
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr "bleu clair"
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr "cyan clair"
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr "vert clair"
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr "gris clair"
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr "magenta clair"
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr "rouge clair"
+
+#: Programs/cmd_speech.c:500
+msgid "line"
+msgstr "ligne"
+
+#. xgettext: This is the description of the COPY_LINE command.
+#: Programs/cmds.auto.h:1239
+msgid "linear copy to character"
+msgstr "copie linéaire jusqu'au caractère"
+
+#: Programs/config.c:750
+msgid "lvl|cat,..."
+msgstr "lvl|cat,..."
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "magenta"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:588
+msgid "missing operand"
+msgstr "Opérande manquant"
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr "Nm du paramètre manquant"
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr "Qualifieur du paramètre manquant"
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr "Valeur du paramètre manquante"
+
+#. xgettext: This is the description of the GUI_ITEM_FRST command.
+#: Programs/cmds.auto.h:1140
+msgid "move to the first item in the screen area"
+msgstr "va au premier élément de la zone de l'écran"
+
+#. xgettext: This is the description of the GUI_ITEM_LAST command.
+#: Programs/cmds.auto.h:1161
+msgid "move to the last item in the screen area"
+msgstr "va au dernier élément de la zone de l'écran"
+
+#. xgettext: This is the description of the GUI_ITEM_NEXT command.
+#: Programs/cmds.auto.h:1154
+msgid "move to the next item in the screen area"
+msgstr "va au prochain élément de la zone de l'écran"
+
+#. xgettext: This is the description of the GUI_ITEM_PREV command.
+#: Programs/cmds.auto.h:1147
+msgid "move to the previous item in the screen area"
+msgstr "va à l'élément précédent de la zone de l'écran"
+
+#: Programs/msgtest.c:57
+msgid "name"
+msgstr "nom"
+
+#: Programs/config.c:433 Programs/config.c:485 Programs/config.c:538
+#: Programs/config.c:560 Programs/config.c:638 Programs/config.c:657
+#: Programs/config.c:700
+msgid "name=value,..."
+msgstr "nom=valeur,..."
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr "natif"
+
+#: Programs/config.c:1248
+msgid "no key bindings"
+msgstr "Pas de raccourci clavier"
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr "pas d'écran"
+
+#: Programs/config.c:116
+msgid "none"
+msgstr "Aucun"
+
+#: Programs/config.c:1537
+msgid "not saved"
+msgstr "Non enregistré"
+
+#: Programs/pgmprivs_linux.c:2018
+msgid "not switching to an unprivileged user"
+msgstr "ne passe pas à un utilisateur non privilégié"
+
+#. xgettext: This is the description of the GUI_APP_ALERTS command.
+#: Programs/cmds.auto.h:1112
+msgid "open the application alerts window"
+msgstr "ouvre la fenêtre des alertes de l'application"
+
+#. xgettext: This is the description of the GUI_APP_LIST command.
+#: Programs/cmds.auto.h:1098
+msgid "open the application list window"
+msgstr "ouvre la fenêtre de liste des applications"
+
+#. xgettext: This is the description of the GUI_APP_MENU command.
+#: Programs/cmds.auto.h:1105
+msgid "open the application-specific menu"
+msgstr "ouvre le menu spécifique à l'application"
+
+#. xgettext: This is the description of the GUI_BRL_ACTIONS command.
+#: Programs/cmds.auto.h:1061
+msgid "open the braille actions window"
+msgstr "ouvre la fenêtre des actions brailles"
+
+#. xgettext: This is the description of the GUI_DEV_OPTIONS command.
+#: Programs/cmds.auto.h:1091
+msgid "open the device options window"
+msgstr "ouvre la fenêtre des options de l'appareil"
+
+#. xgettext: This is the description of the GUI_DEV_SETTINGS command.
+#: Programs/cmds.auto.h:1084
+msgid "open the device settings window"
+msgstr "ouvre la fenêtre des réglages de l'appareil"
+
+#: Programs/options.c:202
+msgid "option"
+msgstr "option"
+
+#: Programs/pgmprivs_linux.c:2152
+msgid "ownership claimed"
+msgstr "propriété obtenue"
+
+#. xgettext: This is the description of the KEY_PAGE_DOWN command.
+#: Programs/cmds.auto.h:1583
+msgid "page-down key"
+msgstr "Touche page suivante"
+
+#. xgettext: This is the description of the KEY_PAGE_UP command.
+#: Programs/cmds.auto.h:1575
+msgid "page-up key"
+msgstr "touche page précédente"
+
+#: Programs/msgtest.c:42
+msgid "path"
+msgstr "chemin"
+
+#: Programs/config.c:2836
+msgid "pid file not specified"
+msgstr "Fichier pid non spécifié"
+
+#: Programs/program.c:308
+msgid "pid file open error"
+msgstr "Erreur à l'ouverture du fichier pid"
+
+#: Programs/spk.c:232
+msgid "pitch"
+msgstr "Ton"
+
+#. xgettext: This is the description of the SETLEFT command.
+#: Programs/cmds.auto.h:1283
+msgid "place left end of braille window at character"
+msgstr "met le coin gauche de la fenêtre braille sur le caractère"
+
+#: Drivers/Braille/Baum/braille.c:1147
+msgid "power switch"
+msgstr "Changement d'état d'alimentation"
+
+#: Programs/config.c:680
+msgid "quality"
+msgstr "qualité"
+
+#: Programs/spk.c:206
+msgid "rate"
+msgstr "Débit"
+
+#. xgettext: This is the description of the COPY_RECT command.
+#: Programs/cmds.auto.h:1231
+msgid "rectangular copy to character"
+msgstr "copie rectangulaire jusqu'au caractère"
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "rouge"
+
+#. xgettext: This is the description of the REFRESH command.
+#: Programs/cmds.auto.h:1005
+msgid "refresh braille display"
+msgstr "rafraîchit l'afficheur braille"
+
+#. xgettext: This is the description of the REFRESH_LINE command.
+#: Programs/cmds.auto.h:1413
+msgid "refresh braille line"
+msgstr "rafraîchit la ligne braille"
+
+#: Programs/config.c:492
+msgid "regexp,..."
+msgstr "regexp,..."
+
+#: Programs/config.c:2081
+#, c-format
+msgid "reinitializing braille driver"
+msgstr "Réinitialisation du pilote braille"
+
+#: Programs/config.c:2594
+#, c-format
+msgid "reinitializing screen driver"
+msgstr "Réinitialisation du pilote de l'écran"
+
+#: Programs/config.c:2394
+#, c-format
+msgid "reinitializing speech driver"
+msgstr "Réinitialisation de la synthèse vocale"
+
+#. xgettext: This is the description of the SETMARK command.
+#: Programs/cmds.auto.h:1291
+msgid "remember current braille window position"
+msgstr "Mémorise la position actuelle de la fenêtre braille"
+
+#. xgettext: This is the description of the ALERT command.
+#: Programs/cmds.auto.h:1445
+msgid "render an alert"
+msgstr "Émettre une alerte"
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr "Répétition du comptage"
+
+#. xgettext: This is the description of the RESTARTBRL command.
+#: Programs/cmds.auto.h:607
+msgid "restart braille driver"
+msgstr "Redémarre le pilote braille"
+
+#. xgettext: This is the description of the RESTARTSPEECH command.
+#: Programs/cmds.auto.h:614
+msgid "restart speech driver"
+msgstr "Redémarre le pilote de synthèse vocale"
+
+#. xgettext: This is the description of the CLIP_RESTORE command.
+#: Programs/cmds.auto.h:864
+msgid "restore clipboard from disk"
+msgstr "Restaure le presse-papier à partir du disque"
+
+#. xgettext: This is the description of the PREFLOAD command.
+#: Programs/cmds.auto.h:457
+msgid "restore preferences from disk"
+msgstr "Restaure les préférences à partir du disque"
+
+#. xgettext: This is the description of the GUI_AREA_ACTV command.
+#: Programs/cmds.auto.h:1119
+msgid "return to the active screen area"
+msgstr "retourne à la zone active de l'écran"
+
+#. xgettext: This is the description of the CLIP_SAVE command.
+#: Programs/cmds.auto.h:857
+msgid "save clipboard to disk"
+msgstr "Enregistre le presse-papier sur le disque"
+
+#. xgettext: This is the description of the PREFSAVE command.
+#: Programs/cmds.auto.h:450
+msgid "save preferences to disk"
+msgstr "Enregistre les préférences sur le disque"
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr "schéma+..."
+
+#: Programs/config.c:2470
+msgid "screen driver not loadable"
+msgstr "Pilote d'écran non chargeable"
+
+#: Programs/config.c:2591
+msgid "screen driver restarting"
+msgstr "le pilote de l'écran redémarre"
+
+#: Programs/cmd_miscellaneous.c:171
+msgid "screen driver stopped"
+msgstr "pilote de l'écran arrêté"
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr "écran verrouillé"
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr "L'écran n'est pas en mode texte"
+
+#. xgettext: This is the description of the PRSEARCH command.
+#: Programs/cmds.auto.h:158
+msgid "search backward for clipboard text"
+msgstr "Recherche en arrière du texte dans le presse papier"
+
+#. xgettext: This is the description of the NXSEARCH command.
+#: Programs/cmds.auto.h:165
+msgid "search forward for clipboard text"
+msgstr "Recherche en avant du texte dans le presse papier"
+
+#: Programs/menu.c:469
+msgid "seconds"
+msgstr "secondes"
+
+#. xgettext: This is the description of the TXTSEL_ALL command.
+#: Programs/cmds.auto.h:1026
+msgid "select all of the text"
+msgstr "sélectionne tout le texte"
+
+#. xgettext: This is the description of the MENU_NEXT_SETTING command.
+#: Programs/cmds.auto.h:507
+msgid "select next choice"
+msgstr "Sélection du choix suivant"
+
+#. xgettext: This is the description of the MENU_PREV_SETTING command.
+#: Programs/cmds.auto.h:500
+msgid "select previous choice"
+msgstr "Sélection du choix précédent"
+
+#. xgettext: This is the description of the TUNES command.
+#: Programs/cmds.auto.h:399
+msgid "set alert tunes on/off"
+msgstr "Active ou désactive les sons d'avertissement"
+
+#. xgettext: This is the description of the ATTRBLINK command.
+#: Programs/cmds.auto.h:383
+msgid "set attribute blinking on/off"
+msgstr "Active ou désactive le clignottement des attributs"
+
+#. xgettext: This is the description of the ATTRVIS command.
+#: Programs/cmds.auto.h:375
+msgid "set attribute underlining on/off"
+msgstr "Active ou désactive le soulignement des attributs"
+
+#. xgettext: This is the description of the SET_ATTRIBUTES_TABLE command.
+#: Programs/cmds.auto.h:1370
+msgid "set attributes table"
+msgstr "Définit la table des attributs"
+
+#. xgettext: This is the description of the AUTOREPEAT command.
+#: Programs/cmds.auto.h:407
+msgid "set autorepeat on/off"
+msgstr "Active ou désactive la répétition automatique"
+
+#. xgettext: This is the description of the ASPK_CMP_WORDS command.
+#: Programs/cmds.auto.h:712
+msgid "set autospeak completed words on/off"
+msgstr "Activer/désactiver la diction automatique des mots complétés"
+
+#. xgettext: This is the description of the ASPK_DEL_CHARS command.
+#: Programs/cmds.auto.h:696
+msgid "set autospeak deleted characters on/off"
+msgstr "Activer/désactiver la diction automatique des mots effacés"
+
+#. xgettext: This is the description of the ASPK_INDENT command.
+#: Programs/cmds.auto.h:998
+msgid "set autospeak indent of current line on/off"
+msgstr "active/désactive l'énonciation automatique de l'indentation de la ligne courante"
+
+#. xgettext: This is the description of the ASPK_INS_CHARS command.
+#: Programs/cmds.auto.h:688
+msgid "set autospeak inserted characters on/off"
+msgstr "Activer/désactiver la diction automatique des caractères insérés"
+
+#. xgettext: This is the description of the AUTOSPEAK command.
+#: Programs/cmds.auto.h:415
+msgid "set autospeak on/off"
+msgstr "Active ou désactive la parle automatique"
+
+#. xgettext: This is the description of the ASPK_REP_CHARS command.
+#: Programs/cmds.auto.h:704
+msgid "set autospeak replaced characters on/off"
+msgstr "Active ou désactive la diction automatique des caractères remplacés"
+
+#. xgettext: This is the description of the ASPK_SEL_CHAR command.
+#: Programs/cmds.auto.h:680
+msgid "set autospeak selected character on/off"
+msgstr "Activer/désactiver la diction automatique du caractère sélectionné"
+
+#. xgettext: This is the description of the ASPK_SEL_LINE command.
+#: Programs/cmds.auto.h:672
+msgid "set autospeak selected line on/off"
+msgstr "Activer/désactiver la diction automatique de la ligne sélectionnée"
+
+#. xgettext: This is the description of the BRLKBD command.
+#: Programs/cmds.auto.h:880
+msgid "set braille keyboard enabled/disabled"
+msgstr "Active ou désactive le clavier braille"
+
+#. xgettext: This is the description of the BRLUCDOTS command.
+#: Programs/cmds.auto.h:872
+msgid "set braille typing mode dots/text"
+msgstr "définit le mode de saisie braille à texte/points"
+
+#. xgettext: This is the description of the CAPBLINK command.
+#: Programs/cmds.auto.h:391
+msgid "set capital letter blinking on/off"
+msgstr "Active ou désactive le clignottement des lettres majuscules"
+
+#. xgettext: This is the description of the CONTRACTED command.
+#: Programs/cmds.auto.h:1190
+msgid "set contracted/computer braille"
+msgstr "active le braille abrégé/informatique"
+
+#. xgettext: This is the description of the SET_CONTRACTION_TABLE command.
+#: Programs/cmds.auto.h:1378
+msgid "set contraction table"
+msgstr "Définit la table de braille abrégé"
+
+#. xgettext: This is the description of the DISPMD command.
+#: Programs/cmds.auto.h:295
+msgid "set display mode attributes/text"
+msgstr "Bascule l'affichage entre les modes attributs-texte"
+
+#. xgettext: This is the description of the CSRHIDE command.
+#: Programs/cmds.auto.h:343
+msgid "set hidden screen cursor on/off"
+msgstr "Cache ou fait apparaître le curseur de l'écran"
+
+#. xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+#: Programs/cmds.auto.h:1386
+msgid "set keyboard table"
+msgstr "Définit la table de touches"
+
+#. xgettext: This is the description of the SET_LANGUAGE_PROFILE command.
+#: Programs/cmds.auto.h:1394
+msgid "set language profile"
+msgstr "Définir le profil de la langue"
+
+#. xgettext: This is the description of the CSRBLINK command.
+#: Programs/cmds.auto.h:367
+msgid "set screen cursor blinking on/off"
+msgstr "Active ou désactive le clignotement du curseur de l'écran"
+
+#. xgettext: This is the description of the CSRSIZE command.
+#: Programs/cmds.auto.h:359
+msgid "set screen cursor style block/underline"
+msgstr "Bascule entre un style de curseur souligné ou en bloc"
+
+#. xgettext: This is the description of the CSRVIS command.
+#: Programs/cmds.auto.h:335
+msgid "set screen cursor visibility on/off"
+msgstr "Rend le curseur visible ou invisible"
+
+#. xgettext: This is the description of the FREEZE command.
+#: Programs/cmds.auto.h:287
+msgid "set screen image frozen/unfrozen"
+msgstr "Geler/dégeler l'image de l'écran"
+
+#. xgettext: This is the description of the COMPBRL6 command.
+#: Programs/cmds.auto.h:1198
+msgid "set six/eight dot computer braille"
+msgstr "active le braille informatique six/huit points"
+
+#. xgettext: This is the description of the SKPBLNKWINS command.
+#: Programs/cmds.auto.h:327
+msgid "set skipping of blank braille windows on/off"
+msgstr "Active ou désactive le saut des fenêtres braille vides"
+
+#. xgettext: This is the description of the SKPIDLNS command.
+#: Programs/cmds.auto.h:319
+msgid "set skipping of lines with identical content on/off"
+msgstr "Activation/désactivation du saut de ligne ayant un contenu identique"
+
+#. xgettext: This is the description of the SLIDEWIN command.
+#: Programs/cmds.auto.h:311
+msgid "set sliding braille window on/off"
+msgstr "Active ou désactive le glissement de la fenêtre braille"
+
+#. xgettext: This is the description of the SHOW_CURR_LOCN command.
+#: Programs/cmds.auto.h:850
+msgid "set speech cursor visibility on/off"
+msgstr "Rend le curseur de synthèse visible ou invisible"
+
+#. xgettext: This is the description of the TXTSEL_SET command.
+#: Programs/cmds.auto.h:1429
+msgid "set text selection"
+msgstr "définit la sélection de texte"
+
+#. xgettext: This is the description of the SIXDOTS command.
+#: Programs/cmds.auto.h:303
+msgid "set text style 6-dot/8-dot"
+msgstr "Bascule l'apparencu de texte entre 6 et 8 points"
+
+#. xgettext: This is the description of the SET_TEXT_TABLE command.
+#: Programs/cmds.auto.h:1362
+msgid "set text table"
+msgstr "Définit la table du texte"
+
+#. xgettext: This is the description of the TOUCH_NAV command.
+#: Programs/cmds.auto.h:983
+msgid "set touch navigation on/off"
+msgstr "Active ou désactive la navigation tactile"
+
+#. xgettext: This is the description of the CSRTRK command.
+#: Programs/cmds.auto.h:351
+msgid "set track screen cursor on/off"
+msgstr "Active ou désacti(e la poursuite du curseur de l'écran"
+
+#. xgettext: This is the description of the TIME command.
+#: Programs/cmds.auto.h:656
+msgid "show current date and time"
+msgstr "Affiche la date et l'heure actuelles"
+
+#. xgettext: This is the description of the GUI_TITLE command.
+#: Programs/cmds.auto.h:1054
+msgid "show the window title"
+msgstr "affiche le titre de la fenêtre"
+
+#. xgettext: This is the description of the INDICATORS command.
+#: Programs/cmds.auto.h:1012
+msgid "show various device status indicators"
+msgstr "affiche divers indicateurs de status de l'appareil"
+
+#: Programs/menu_prefs.c:1057
+msgid "soundcard digital audio"
+msgstr "Son numérique de la carte son"
+
+#: Programs/menu_prefs.c:1059
+msgid "soundcard synthesizer"
+msgstr "Synthétiseur de la carte son"
+
+#: Programs/cmd.c:277 Programs/core.c:1051
+msgid "space"
+msgstr "Espace"
+
+#. xgettext: This is the description of the SPEAK_CURR_CHAR command.
+#: Programs/cmds.auto.h:719
+msgid "speak current character"
+msgstr "Lire le caractère actuel"
+
+#. xgettext: This is the description of the SAY_LINE command.
+#. xgettext: This is the description of the SPEAK_CURR_LINE command.
+#: Programs/cmds.auto.h:529 Programs/cmds.auto.h:765
+msgid "speak current line"
+msgstr "Lit vocalement la ligne actuelle"
+
+#. xgettext: This is the description of the SPEAK_CURR_WORD command.
+#: Programs/cmds.auto.h:742
+msgid "speak current word"
+msgstr "Lire le mot actuel"
+
+#. xgettext: This is the description of the SAY_BELOW command.
+#: Programs/cmds.auto.h:543
+msgid "speak from current line through bottom of screen"
+msgstr "Lit vocalement depuis la ligne actuelle jusqu'au bas de l'écran"
+
+#. xgettext: This is the description of the SAY_ALL command.
+#: Programs/cmds.auto.h:1182
+msgid "speak from top of screen through bottom of screen"
+msgstr "énonce l'écran de haut en bas"
+
+#. xgettext: This is the description of the SAY_ABOVE command.
+#: Programs/cmds.auto.h:536
+msgid "speak from top of screen through current line"
+msgstr "Lit vocalement du haut de l'écran jusquà la ligne actuelle"
+
+#. xgettext: This is the description of the SPEAK_INDENT command.
+#: Programs/cmds.auto.h:990
+msgid "speak indent of current line"
+msgstr "énonce l'indentation de la ligne courante"
+
+#. xgettext: This is the description of the SPEAK_CURR_LOCN command.
+#: Programs/cmds.auto.h:842
+msgid "speak speech cursor location"
+msgstr "Lit là où est le curseur de synthèse"
+
+#: Programs/msgtest.c:50
+msgid "specifier"
+msgstr "spécificateur"
+
+#: Programs/config.c:2242
+msgid "speech driver not loadable"
+msgstr "Pilote de synthèse vocale non chargeable"
+
+#: Programs/config.c:2391
+msgid "speech driver restarting"
+msgstr "le pilote de la synthèse vocale redémarre"
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr "pilote de la synthèse vocale arrêté"
+
+#. xgettext: This is the description of the SPELL_CURR_WORD command.
+#: Programs/cmds.auto.h:827
+msgid "spell current word"
+msgstr "Épelle le mot actuel"
+
+#. xgettext: This is the description of the CLIP_NEW command.
+#: Programs/cmds.auto.h:1215
+msgid "start new clipboard at character"
+msgstr "Démarre un nouveau presse-papier au caractère"
+
+#. xgettext: This is the description of the TXTSEL_START command.
+#: Programs/cmds.auto.h:1421
+msgid "start text selection"
+msgstr "commence la sélection de texte"
+
+#. xgettext: This is the description of the BRL_START command.
+#: Programs/cmds.auto.h:915
+msgid "start the braille driver"
+msgstr "Démarre le pilote braille"
+
+#. xgettext: This is the description of the SCR_START command.
+#: Programs/cmds.auto.h:943
+msgid "start the screen driver"
+msgstr "Démarre le pilote de l'écran"
+
+#. xgettext: This is the description of the SPK_START command.
+#: Programs/cmds.auto.h:929
+msgid "start the speech driver"
+msgstr "Démarre le pilote de synthèse vocale"
+
+#. xgettext: This is the description of the MUTE command.
+#: Programs/cmds.auto.h:514
+msgid "stop speaking"
+msgstr "Arrêt de la parole"
+
+#. xgettext: This is the description of the BRL_STOP command.
+#: Programs/cmds.auto.h:908
+msgid "stop the braille driver"
+msgstr "Démarre le pilote braille"
+
+#. xgettext: This is the description of the SCR_STOP command.
+#: Programs/cmds.auto.h:936
+msgid "stop the screen driver"
+msgstr "Arrête le pilote de l'écran"
+
+#. xgettext: This is the description of the SPK_STOP command.
+#: Programs/cmds.auto.h:922
+msgid "stop the speech driver"
+msgstr "Arrête le pilote de synthèse vocale"
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr "ancien gestionnaire d'erreurs étrange\n"
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr "chaîne"
+
+#. xgettext: This is the description of the CONTEXT command.
+#: Programs/cmds.auto.h:1495
+msgid "switch to command context"
+msgstr "Va au contexte de la commande"
+
+#. xgettext: This is the description of the SWITCHVT command.
+#: Programs/cmds.auto.h:1247
+msgid "switch to specific virtual terminal"
+msgstr "Va à un terminal virtuel spécifique"
+
+#. xgettext: This is the description of the GUI_AREA_NEXT command.
+#: Programs/cmds.auto.h:1133
+msgid "switch to the next screen area"
+msgstr "passe à la zone suivante de l'écran"
+
+#. xgettext: This is the description of the SWITCHVT_NEXT command.
+#: Programs/cmds.auto.h:585
+msgid "switch to the next virtual terminal"
+msgstr "Va au terminal virtuel suivant"
+
+#. xgettext: This is the description of the GUI_AREA_PREV command.
+#: Programs/cmds.auto.h:1126
+msgid "switch to the previous screen area"
+msgstr "passe à la zone précédente de l'écran"
+
+#. xgettext: This is the description of the SWITCHVT_PREV command.
+#: Programs/cmds.auto.h:578
+msgid "switch to the previous virtual terminal"
+msgstr "Va au terminal virtuel précédent"
+
+#: Programs/pgmprivs_linux.c:1994
+msgid "switched to unprivileged user"
+msgstr "passé à un utilisateur non privilégié"
+
+#. xgettext: This is the description of the KEY_TAB command.
+#: Programs/cmds.auto.h:1519
+msgid "tab key"
+msgstr "Touche tab"
+
+#: Programs/config.c:379 Programs/config.c:386
+msgid "text"
+msgstr "Texte"
+
+#: Programs/msgtest.c:45
+msgid "the locale directory containing the translations"
+msgstr "le répertoire de la locale contenant les traductions"
+
+#: Programs/msgtest.c:52
+msgid "the locale in which to look up a translation"
+msgstr "la locale dans laquelle chercher une traduction"
+
+#: Programs/msgtest.c:59
+msgid "the name of the domain containing the translations"
+msgstr "le nom du domaine contenant les traductions"
+
+#. xgettext: This is the description of the PASSDOTS command.
+#: Programs/cmds.auto.h:1463
+msgid "type braille dots"
+msgstr "Saisie de points braille"
+
+#. xgettext: This is the description of the PASSCHAR command.
+#: Programs/cmds.auto.h:1454
+msgid "type unicode character"
+msgstr "Caractère de type unicode"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr "Type de bloc inattendu"
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr "Cmd imprévue"
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr "Commande non prise en charge"
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr "Commande inconnue"
+
+#: Programs/options.c:828
+msgid "unknown configuration directive"
+msgstr "Ligne de configuration non reconnue"
+
+#: Programs/config.c:791
+msgid "unknown log level or category"
+msgstr "Niveau ou catégorie de journalisation inconnu"
+
+#. an unknown option has been specified
+#: Programs/options.c:575
+msgid "unknown option"
+msgstr "Option non reconnue"
+
+#: Programs/config.c:327
+msgid "unknown screen content quality"
+msgstr "qualité du contenu de l'écran inconnue"
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr "Paramètre non supporté"
+
+#: Programs/spk.c:180
+msgid "volume"
+msgstr "Volume"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "blanc"
+
+#: Programs/pgmprivs_linux.c:1856
+msgid "working directory changed"
+msgstr "répertoire courant changé"
+
+#: Programs/msgtest.c:65
+msgid "write the translations using UTF-8"
+msgstr "écrire les traductions en utilisant UTF-8"
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr "xbrlapi : N'a pas pu trouver keycode assignable à la simulation du caractère %08X qui n'est pas lié à une touche\n"
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr "xbrlapi : N'a pas pu trouver de modificateur applicable à %d pour obtenir le symbole de touche %08X\n"
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr "xbrlapi : N'a pas pu traduire le symbole de touche %08X en code de touche.\n"
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr "xbrlapi: Erreur X %d, %s sur l'écran %s\n"
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr "xbrlapi : Mauvais format de numéro de terminal virtuel\n"
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr "xbrlapi : Mauvais type de numéro de terminal virtuel\n"
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr "xbrlapi : Ne peut pas trouver la propriété XFree86_VT de la fenêtre racine\n"
+
+#
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr "xbrlapi : Ne peut pas écrire le nom de la fenêtre %s\n"
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr "xbrlapi : n'a pas extrait le parent de %#010lx\n"
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr "xbrlapi : n'a pas extrait la fenêtre de %#010lx\n"
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr "xbrlapi : n'a pas extrait la fenêtre %#010lx mais a récupéré le focus\n"
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr "xbrlapi : Plus d'un élément pour le numéro de terminal virtuel\n"
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr "xbrlapi : Pas d'atome XFree86_VT\n"
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr "xbrlapi : Pas d'éléments pour le numéro de terminal virtuel\n"
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr "xbrlapi : ressource %#010lx, req %u:%u\n"
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr "xbrlapi : Type d'événement non pris en charge: %d\n"
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr "xbrlapi : Le nom de la fenêtre %#010lx est devenu NULL\n"
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "jaune"
+
+#~ msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)"
+#~ msgstr "Niveau de journalisation (%s ou un parmi {%s}) et/ou catégories de journal à activer (n'importe quelle combinaison de {%s}, chacune précédée éventuellement de %s pour la désactiver)"
+
+#~ msgid "Cell Type"
+#~ msgstr "Type de Cellule"
+
+#~ msgid "Path to directory which contains message translations."
+#~ msgstr "Chemin du répertoire contenant les traductions des messages"
+
+#~ msgid "Alto Sax"
+#~ msgstr "Saxo Alto"
+
+#~ msgid "Baritone Sax"
+#~ msgstr "Saxo bariton"
+
+#~ msgid "Clavi"
+#~ msgstr "Claviers"
+
+#~ msgid "Soprano Sax"
+#~ msgstr "Saxo soprano"
+
+#~ msgid "Tenor Sax"
+#~ msgstr "Saxo tenor"
+
+#~ msgid "6-Dot Computer"
+#~ msgstr "Informatique 6 Points"
+
+#~ msgid "6-Dot Contracted"
+#~ msgstr "Abrégé 6 Points"
+
+#~ msgid "8-Dot Computer"
+#~ msgstr "Informatique 8 Points"
+
+#~ msgid "8-Dot Contracted"
+#~ msgstr "Abrégé 8 Points"
+
+#~ msgid "Alphabetic Braille Window Coordinates"
+#~ msgstr "Coordonnées alphabétiques de la fenêtre braille"
+
+#~ msgid "Alphabetic Screen Cursor Coordinates"
+#~ msgstr "Coordonnées alphabétiques du curseur à l'écran"
+
+#~ msgid "April"
+#~ msgstr "Avril"
+
+#~ msgid "Attributes Invisible Time"
+#~ msgstr "Durée de l'invisibilité des attributs"
+
+#~ msgid "Attributes Visible Time"
+#~ msgstr "Durée de visibilité des attributs"
+
+#~ msgid "August"
+#~ msgstr "Août"
+
+#~ msgid "Braille Window Column"
+#~ msgstr "Colonne de la fenêtre braille"
+
+#~ msgid "Braille Window Coordinates"
+#~ msgstr "Coordonnées de la fenêtre braille"
+
+#~ msgid "Braille Window Row"
+#~ msgstr "Ligne de la fenêtre braille"
+
+#~ msgid "Capitals Invisible Time"
+#~ msgstr "Durée d'invisibilité des majuscules"
+
+#~ msgid "Capitals Visible Time"
+#~ msgstr "Durée de visibilité des majuscules"
+
+#~ msgid "December"
+#~ msgstr "Décembre"
+
+#~ msgid "February"
+#~ msgstr "Février"
+
+#~ msgid "January"
+#~ msgstr "Janvier"
+
+#~ msgid "July"
+#~ msgstr "Juillet"
+
+#~ msgid "June"
+#~ msgstr "Juin"
+
+#~ msgid "March"
+#~ msgstr "Mars"
+
+#~ msgid "May"
+#~ msgstr "Mai"
+
+#~ msgid "November"
+#~ msgstr "Novembre"
+
+#~ msgid "October"
+#~ msgstr "Octobre"
+
+#~ msgid "Screen Cursor Column"
+#~ msgstr "Colonne du curseur de l'écran"
+
+#~ msgid "Screen Cursor Coordinates"
+#~ msgstr "Coordonnées du curseur de l'écran"
+
+#~ msgid "Screen Cursor Invisible Time"
+#~ msgstr "Durée d'invisibilité du curseur d'écran"
+
+#~ msgid "Screen Cursor Row"
+#~ msgstr "Ligne du curseur de l'écran"
+
+#~ msgid "Screen Cursor Visible Time"
+#~ msgstr "Durée de visibilité du curseur de l'écran"
+
+#~ msgid "Screen Cursor and Braille Window Column"
+#~ msgstr "Colonne du curseur de l'écran et de la fenêtre braille"
+
+#~ msgid "Screen Cursor and Braille Window Row"
+#~ msgstr "Ligne du curseur de l'écran et de la fenêtre braille"
+
+#~ msgid "September"
+#~ msgstr "Septembre"
+
+#~ msgid "Speech Cursor Invisible Time"
+#~ msgstr "Durée d'invisibilité du curseur"
+
+#~ msgid "Speech Cursor Visible Time"
+#~ msgstr "Durée de visibilité du curseur de la synthèse"
+
+#~ msgid "blink"
+#~ msgstr "clignotant"
+
+#~ msgid "list all of the translations (the default)"
+#~ msgstr "liste toutes les traductions (action par défaut)"
+
+#~ msgid "percentage"
+#~ msgstr "Pourcentage"
+
+#~ msgid "second"
+#~ msgid_plural "seconds"
+#~ msgstr[0] "seconde"
+#~ msgstr[1] "secondes"
+
+#~ msgid "show the message count"
+#~ msgstr "affiche le nombre de messages"
+
+#~ msgid "show the translation metadata"
+#~ msgstr "affiche les métadonnées de traduction"
+
+#~ msgid "Braille Type"
+#~ msgstr "Type de Braille"
+
+#~ msgid "6-Dot Computer Braille"
+#~ msgstr "Braille informatique 6 points"
+
+#~ msgid "8-Dot Computer Braille"
+#~ msgstr "Braille informatique 8 points"
+
+#~ msgid "Literary Braille"
+#~ msgstr "Braille Littéraire"
+
+#~ msgid "Text Style"
+#~ msgstr "Apparence du texte"
+
+#~ msgid "Bug Reports"
+#~ msgstr "Rapports de bug"
+
+#~ msgid "Autorepeat"
+#~ msgstr "Répétition automatique"
+
+#~ msgid "Braille Display Orientation"
+#~ msgstr "Orientation de l'affichage braille"
+
+#~ msgid "Braille Input Mode"
+#~ msgstr "Mode de saisie braille"
+
+#~ msgid "Braille Keyboard Enabled"
+#~ msgstr "Clavier braille activé"
+
+#~ msgid "Normal"
+#~ msgstr "Normal"
+
+#~ msgid "Rotated"
+#~ msgstr "Cyclique"
+
+#~ msgid "Check for undefined characters."
+#~ msgstr "Vérifie les ca!actères indéfinis"
+
+#~ msgid "Blinking Cursor"
+#~ msgstr "Clignotement du curseur"
+
+#~ msgid "Cursor Invisible Time"
+#~ msgstr "Durée d'invisibilité du curseur"
+
+#~ msgid "Cursor Style"
+#~ msgstr "Apparence du curseur"
+
+#~ msgid "Cursor Visible Time"
+#~ msgstr "Durée de visibilité du curseur"
+
+#~ msgid "Show Cursor"
+#~ msgstr "Afficher le curseur"
+
+#~ msgid "State Directory"
+#~ msgstr "Répertoire d'état"
+
+#~ msgid "Window Follows Pointer"
+#~ msgstr "La fenêtre suit le pointeur"
+
+#~ msgid "drag cursor"
+#~ msgstr "Entraîne le curseur"
+
+#~ msgid "go up to last line of previous paragraph"
+#~ msgstr "Remonte à la dernière ligne du paragraphe précédent"
+
+#~ msgid "left margin"
+#~ msgstr "Marge de gauche"
+
+#~ msgid "normalized position"
+#~ msgstr "Position normalisée"
+
+#~ msgid "set control modifier of next typed character or emulated key on/off"
+#~ msgstr "Activation/désactivation du modificateur de contrôle au prochain caractère tapé ou à la prochaine touche émulée"
+
+#~ msgid "set cursor tracking on/off"
+#~ msgstr "Active ou désactive la poursuite du curseur"
+
+#~ msgid "set meta modifier of next typed character or emulated key on/off"
+#~ msgstr "Activation/désactivation d'un modificateur Meta au prochain caractère tapé ou à la prochaine touche émulée"
+
+#~ msgid "set shift modifier of next typed character or emulated key on/off"
+#~ msgstr "Activation/désactivation du modificateur Shift au prochain caractère tapé ou à la prochaine touche émulée"
+
+#~ msgid "set speech location visibility on/off"
+#~ msgstr "Activer/désactiver la verbosité de la position"
+
+#~ msgid "set upper modifier of next typed character or emulated key on/off"
+#~ msgstr "Activation/désactivation du modificateur Verr.maj au prochain caractère tapé ou à la prochaine touche émulée"
+
+#~ msgid "switch to virtual terminal"
+#~ msgstr "Change de terminal virtuel"
diff --git a/Messages/it.po b/Messages/it.po
new file mode 100644
index 0000000..230c597
--- /dev/null
+++ b/Messages/it.po
@@ -0,0 +1,3654 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 5.6\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-01-25 09:02-0500\n"
+"PO-Revision-Date: 2018-10-22 20:50+0200\n"
+"Last-Translator: Sebastiano Pistore <SebastianoPistore.info@protonmail.ch>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 2.2\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr ""
+
+#: Programs/menu_prefs.c:457 Programs/menu_prefs.c:458
+#: Programs/menu_prefs.c:460 Programs/menu_prefs.c:461
+#: Programs/menu_prefs.c:464 Programs/menu_prefs.c:465
+#: Programs/menu_prefs.c:466 Programs/menu_prefs.c:468
+#: Programs/menu_prefs.c:469
+msgid "1 cell"
+msgstr ""
+
+#: Programs/menu_prefs.c:789
+msgid "1 second"
+msgstr "1 secondo"
+
+#: Programs/menu_prefs.c:851
+msgid "10 seconds"
+msgstr "10 secondi"
+
+#: Programs/menu_prefs.c:1158
+msgid "12 Hour"
+msgstr "12 ore"
+
+#: Programs/menu_prefs.c:456 Programs/menu_prefs.c:459
+#: Programs/menu_prefs.c:462 Programs/menu_prefs.c:463
+#: Programs/menu_prefs.c:467
+msgid "2 cells"
+msgstr "2 celle"
+
+#: Programs/menu_prefs.c:790
+msgid "2 seconds"
+msgstr "2 secondi"
+
+#: Programs/menu_prefs.c:852
+msgid "20 seconds"
+msgstr "20 secondi"
+
+#: Programs/menu_prefs.c:1157
+msgid "24 Hour"
+msgstr "24 ore"
+
+#: Programs/menu_prefs.c:787
+msgid "250 milliseconds"
+msgstr "250 millisecondi"
+
+#: Programs/menu_prefs.c:853
+msgid "40 seconds"
+msgstr "40 secondi"
+
+#: Programs/menu_prefs.c:850
+msgid "5 seconds"
+msgstr "5 secondi"
+
+#: Programs/menu_prefs.c:788
+msgid "500 milliseconds"
+msgstr "500 millisecondi"
+
+#: Programs/menu_prefs.c:615
+msgid "6-Dot Computer Braille"
+msgstr ""
+
+#: Programs/menu_prefs.c:613
+msgid "8-Dot Computer Braille"
+msgstr ""
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr ""
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr ""
+
+#: Programs/config.c:1382
+msgid "API Parameter"
+msgstr ""
+
+#: Programs/cmds.auto.h:1252
+msgid "AT (set 2) keyboard scan code"
+msgstr ""
+
+#: Programs/midi.c:67
+msgid "Accordion"
+msgstr ""
+
+#. Bass
+#: Programs/midi.c:80
+msgid "Acoustic Bass"
+msgstr ""
+
+#. Piano
+#: Programs/midi.c:44
+msgid "Acoustic Grand Piano"
+msgstr ""
+
+#. Guitar
+#: Programs/midi.c:71
+msgid "Acoustic Guitar (nylon)"
+msgstr ""
+
+#: Programs/midi.c:72
+msgid "Acoustic Guitar (steel)"
+msgstr ""
+
+#: Programs/menu_prefs.c:1184
+msgid "After Time"
+msgstr ""
+
+#: Programs/midi.c:171
+msgid "Agogo"
+msgstr ""
+
+#: Programs/menu_prefs.c:1374
+msgid "Alert"
+msgstr "Avviso"
+
+#: Programs/menu_prefs.c:993
+msgid "Alert Dots"
+msgstr ""
+
+#: Programs/menu_prefs.c:998
+msgid "Alert Messages"
+msgstr ""
+
+#: Programs/menu_prefs.c:938
+msgid "Alert Tunes"
+msgstr ""
+
+#: Programs/menu_prefs.c:753 Programs/menu_prefs.c:1032
+msgid "All"
+msgstr ""
+
+#: Programs/menu_prefs.c:468
+msgid "Alphabetic Braille Window Coordinates"
+msgstr ""
+
+#: Programs/menu_prefs.c:469
+msgid "Alphabetic Screen Cursor Coordinates"
+msgstr ""
+
+#: Programs/midi.c:117
+msgid "Alto Sax"
+msgstr ""
+
+#: Programs/midi.c:185
+msgid "Applause"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:60
+msgid "April"
+msgstr "Aprile"
+
+#: Programs/log.c:118
+msgid "Async Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:710
+msgid "Attributes Invisible Time"
+msgstr ""
+
+#: Programs/config.c:2827 Programs/menu_prefs.c:1280
+msgid "Attributes Table"
+msgstr ""
+
+#: Programs/menu_prefs.c:704
+msgid "Attributes Visible Time"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:64
+msgid "August"
+msgstr "Agosto"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr ""
+
+#: Programs/menu_prefs.c:856
+msgid "Autorelease Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:873
+msgid "Autorepeat Enabled"
+msgstr ""
+
+#: Programs/menu_prefs.c:879
+msgid "Autorepeat Interval"
+msgstr ""
+
+#: Programs/menu_prefs.c:886
+msgid "Autorepeat Panning"
+msgstr ""
+
+#: Programs/menu_prefs.c:1075
+msgid "Autospeak"
+msgstr ""
+
+#: Programs/config.c:1827
+msgid "BRLTTY stopped"
+msgstr ""
+
+#: Programs/midi.c:166
+msgid "Bag Pipe"
+msgstr ""
+
+#: Programs/midi.c:162
+msgid "Banjo"
+msgstr ""
+
+#: Programs/midi.c:119
+msgid "Baritone Sax"
+msgstr ""
+
+#: Programs/midi.c:28
+msgid "Bass"
+msgstr "Basso"
+
+#: Programs/midi.c:122
+msgid "Bassoon"
+msgstr ""
+
+#: Programs/menu_prefs.c:945
+msgid "Beeper"
+msgstr ""
+
+#: Programs/menu_prefs.c:1183
+msgid "Before Time"
+msgstr ""
+
+#: Programs/midi.c:182
+msgid "Bird Tweet"
+msgstr ""
+
+#: Programs/menu_prefs.c:698
+msgid "Blinking Attributes"
+msgstr ""
+
+#: Programs/menu_prefs.c:716
+msgid "Blinking Capitals"
+msgstr ""
+
+#: Programs/menu_prefs.c:675
+msgid "Blinking Screen Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:1133
+msgid "Blinking Speech Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:576 Programs/menu_prefs.c:1243
+msgid "Block"
+msgstr ""
+
+#: Programs/midi.c:129
+msgid "Blown Bottle"
+msgstr ""
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr ""
+
+#: Programs/config.c:1603
+msgid "Braille Device"
+msgstr "Dispositivo braille"
+
+#: Programs/config.c:1599
+msgid "Braille Driver"
+msgstr ""
+
+#: Programs/log.c:148
+msgid "Braille Driver Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:653
+msgid "Braille Firmness"
+msgstr ""
+
+#: Programs/log.c:82
+msgid "Braille Key Events"
+msgstr ""
+
+#: Programs/config.c:1602
+msgid "Braille Parameter"
+msgstr ""
+
+#: Programs/menu_prefs.c:609
+msgid "Braille Presentation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1270
+msgid "Braille Tables"
+msgstr ""
+
+#: Programs/menu_prefs.c:821
+msgid "Braille Typing"
+msgstr ""
+
+#: Programs/menu_prefs.c:457
+msgid "Braille Window Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:456
+msgid "Braille Window Coordinates"
+msgstr ""
+
+#: Programs/menu_prefs.c:774
+msgid "Braille Window Overlap"
+msgstr ""
+
+#: Programs/menu_prefs.c:458
+msgid "Braille Window Row"
+msgstr ""
+
+#: Programs/config.c:475
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/midi.c:31
+msgid "Brass"
+msgstr ""
+
+#: Programs/midi.c:112
+msgid "Brass Section"
+msgstr ""
+
+#: Programs/midi.c:180
+msgid "Breath Noise"
+msgstr ""
+
+#: Programs/midi.c:45
+msgid "Bright Acoustic Piano"
+msgstr ""
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr ""
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr ""
+
+#: Programs/menu_prefs.c:1307
+msgid "Build Information"
+msgstr ""
+
+#: Programs/menu_prefs.c:638
+msgid "Capitalization Mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:727
+msgid "Capitals Invisible Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:721
+msgid "Capitals Visible Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:1397
+msgid "Category Log Level"
+msgstr ""
+
+#. Chromatic Percussion
+#: Programs/midi.c:53
+msgid "Celesta"
+msgstr "Celesta"
+
+#: Programs/midi.c:91
+msgid "Cello"
+msgstr "Violoncello"
+
+#: Programs/midi.c:102
+msgid "Choir Aahs"
+msgstr ""
+
+#: Programs/midi.c:25
+msgid "Chromatic Percussion"
+msgstr ""
+
+#: Programs/midi.c:65
+msgid "Church Organ"
+msgstr "Organo a canne"
+
+#: Programs/midi.c:123
+msgid "Clarinet"
+msgstr ""
+
+#: Programs/midi.c:51
+msgid "Clavi"
+msgstr ""
+
+#: Programs/menu.c:820
+msgid "Close"
+msgstr "Chiudi"
+
+#: Programs/menu_prefs.c:1167
+msgid "Colon"
+msgstr ""
+
+#: Programs/menu_prefs.c:1331
+msgid "Configuration Directory"
+msgstr ""
+
+#: Programs/config.c:2770 Programs/menu_prefs.c:1336
+msgid "Configuration File"
+msgstr ""
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr ""
+
+#: Programs/menu_prefs.c:924
+msgid "Console Bell Alert"
+msgstr ""
+
+#: Programs/midi.c:92
+msgid "Contrabass"
+msgstr "Contrabasso"
+
+#: Programs/menu_prefs.c:614
+msgid "Contracted Braille"
+msgstr ""
+
+#: Programs/config.c:2834 Programs/menu_prefs.c:1288
+msgid "Contraction Table"
+msgstr ""
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr ""
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr ""
+
+#: Programs/menu_prefs.c:1375
+msgid "Critical"
+msgstr ""
+
+#: Programs/log.c:100
+msgid "Cursor Routing"
+msgstr ""
+
+#: Programs/log.c:94
+msgid "Cursor Tracking"
+msgstr ""
+
+#: Programs/menu_prefs.c:793
+msgid "Cursor Tracking Delay"
+msgstr ""
+
+#: Programs/menu_prefs.c:1205
+msgid "Dash"
+msgstr "Trattino"
+
+#: Programs/menu_prefs.c:1198
+msgid "Date Format"
+msgstr "Formato data"
+
+#: Programs/menu_prefs.c:1187
+msgid "Date Position"
+msgstr ""
+
+#: Programs/menu_prefs.c:1210
+msgid "Date Separator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1195
+msgid "Day Month Year"
+msgstr "Giorno mese anno"
+
+#: Programs/menu_prefs.c:1380
+msgid "Debug"
+msgstr "Debug"
+
+#: Programs/cmd_miscellaneous.c:68
+msgid "December"
+msgstr "Dicembre"
+
+#: Programs/config.c:496
+msgid "Device for accessing braille display."
+msgstr ""
+
+#: Programs/config.c:455
+msgid "Disable the application programming interface."
+msgstr ""
+
+#: Programs/midi.c:77
+msgid "Distortion Guitar"
+msgstr "Chitarra con distorsore"
+
+#: Programs/config.c:598
+msgid "Do not autospeak when braille is not being used."
+msgstr ""
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr ""
+
+#: Programs/config.c:366
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr ""
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr ""
+
+#: Programs/menu_prefs.c:1168 Programs/menu_prefs.c:1207
+msgid "Dot"
+msgstr "Punto"
+
+#: Programs/menu_prefs.c:831
+msgid "Dots via Unicode Braille"
+msgstr ""
+
+#. Organ
+#: Programs/midi.c:62
+msgid "Drawbar Organ"
+msgstr ""
+
+#: Programs/config.c:2791 Programs/menu_prefs.c:1356
+msgid "Drivers Directory"
+msgstr ""
+
+#: Programs/midi.c:60
+msgid "Dulcimer"
+msgstr "Saltèrio"
+
+#: Programs/menu_prefs.c:768
+msgid "Eager Sliding Braille Window"
+msgstr ""
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "Modificare tabella."
+
+#: Programs/midi.c:81
+msgid "Electric Bass (finger)"
+msgstr ""
+
+#: Programs/midi.c:82
+msgid "Electric Bass (pick)"
+msgstr ""
+
+#: Programs/midi.c:46
+msgid "Electric Grand Piano"
+msgstr ""
+
+#: Programs/midi.c:74
+msgid "Electric Guitar (clean)"
+msgstr ""
+
+#: Programs/midi.c:73
+msgid "Electric Guitar (jazz)"
+msgstr ""
+
+#: Programs/midi.c:75
+msgid "Electric Guitar (muted)"
+msgstr ""
+
+#: Programs/midi.c:48
+msgid "Electric Piano 1"
+msgstr ""
+
+#: Programs/midi.c:49
+msgid "Electric Piano 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1373
+msgid "Emergency"
+msgstr "Emergenza"
+
+#: Programs/menu_prefs.c:455
+msgid "End"
+msgstr "Fine"
+
+#: Programs/menu_prefs.c:754
+msgid "End of Line"
+msgstr ""
+
+#: Programs/midi.c:121
+msgid "English Horn"
+msgstr "Corno inglese"
+
+#: Programs/menu_prefs.c:1067
+msgid "Enqueue"
+msgstr ""
+
+#: Programs/midi.c:30
+msgid "Ensemble"
+msgstr ""
+
+#: Programs/menu_prefs.c:1376
+msgid "Error"
+msgstr "Errore"
+
+#: Programs/midi.c:37
+msgid "Ethnic Instruments"
+msgstr ""
+
+#: Programs/menu_prefs.c:921
+msgid "Event Alerts"
+msgstr ""
+
+#: Programs/menu_prefs.c:626
+msgid "Expand Current Word"
+msgstr ""
+
+#: Programs/config.c:410
+msgid "Explicit preference settings."
+msgstr ""
+
+#: Programs/menu_prefs.c:948
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:986
+msgid "FM Volume"
+msgstr "Volume FM"
+
+#. Synth FM
+#: Programs/midi.c:152
+msgid "FX 1 (rain)"
+msgstr ""
+
+#: Programs/midi.c:153
+msgid "FX 2 (soundtrack)"
+msgstr ""
+
+#: Programs/midi.c:154
+msgid "FX 3 (crystal)"
+msgstr ""
+
+#: Programs/midi.c:155
+msgid "FX 4 (atmosphere)"
+msgstr ""
+
+#: Programs/midi.c:156
+msgid "FX 5 (brightness)"
+msgstr ""
+
+#: Programs/midi.c:157
+msgid "FX 6 (goblins)"
+msgstr ""
+
+#: Programs/midi.c:158
+msgid "FX 7 (echoes)"
+msgstr ""
+
+#: Programs/midi.c:159
+msgid "FX 8 (sci-fi)"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:58
+msgid "February"
+msgstr "Febbraio"
+
+#: Programs/midi.c:167
+msgid "Fiddle"
+msgstr ""
+
+#: Programs/midi.c:126
+msgid "Flute"
+msgstr "Flauto"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr ""
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr ""
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr ""
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr ""
+
+#: Programs/midi.c:111
+msgid "French Horn"
+msgstr "Corno francese"
+
+#: Programs/midi.c:83
+msgid "Fretless Bass"
+msgstr ""
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr ""
+
+#: Programs/menu_prefs.c:470
+msgid "Generic"
+msgstr "Generico"
+
+#: Programs/log.c:64
+msgid "Generic Input"
+msgstr ""
+
+#: Programs/midi.c:54
+msgid "Glockenspiel"
+msgstr "Glockenspiel"
+
+#: Programs/midi.c:27
+msgid "Guitar"
+msgstr "Chitarra"
+
+#. Sound Effects
+#: Programs/midi.c:179
+msgid "Guitar Fret Noise"
+msgstr ""
+
+#: Programs/midi.c:78
+msgid "Guitar Harmonics"
+msgstr ""
+
+#: Programs/midi.c:186
+msgid "Gunshot"
+msgstr ""
+
+#: Programs/midi.c:68
+msgid "Harmonica"
+msgstr ""
+
+#: Programs/midi.c:50
+msgid "Harpsichord"
+msgstr "Clavicembalo"
+
+#: Programs/midi.c:184
+msgid "Helicopter"
+msgstr "Elicottero"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "Schermata di aiuto"
+
+#: Programs/menu_prefs.c:649 Programs/menu_prefs.c:902
+msgid "High"
+msgstr ""
+
+#: Programs/menu_prefs.c:810
+msgid "Highlight Braille Window Location"
+msgstr ""
+
+#: Programs/midi.c:47
+msgid "Honkytonk Piano"
+msgstr ""
+
+#: Programs/menu_prefs.c:1066
+msgid "Immediate"
+msgstr ""
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr ""
+
+#: Programs/menu_prefs.c:1379
+msgid "Information"
+msgstr "Informazione"
+
+#: Programs/menu_prefs.c:845
+msgid "Input Options"
+msgstr "Opzioni di input"
+
+#: Programs/log.c:70
+msgid "Input Packets"
+msgstr "Pacchetti di input"
+
+#: Programs/config.c:341
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr ""
+
+#: Programs/menu_prefs.c:1383
+msgid "Internal Parameters"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:57
+msgid "January"
+msgstr "Gennaio"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:63
+msgid "July"
+msgstr "Luglio"
+
+#: Programs/cmd_miscellaneous.c:62
+msgid "June"
+msgstr "Giugno"
+
+#: Programs/midi.c:165
+msgid "Kalimba"
+msgstr "Kalimba"
+
+#: Programs/config.c:1525
+msgid "Key Bindings"
+msgstr ""
+
+#: Programs/config.c:1069
+msgid "Key Help"
+msgstr ""
+
+#: Programs/config.c:1530 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr ""
+
+#: Programs/menu_prefs.c:824
+msgid "Keyboard Enabled"
+msgstr ""
+
+#: Programs/log.c:88
+msgid "Keyboard Key Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:931
+msgid "Keyboard LED Alerts"
+msgstr ""
+
+#: Programs/config.c:2841 Programs/menu_prefs.c:913
+msgid "Keyboard Table"
+msgstr ""
+
+#: Programs/midi.c:164
+msgid "Koto"
+msgstr "Koto"
+
+#: Programs/config.c:2973
+msgid "Language"
+msgstr "Lingua"
+
+#. Synth Lead
+#: Programs/midi.c:134
+msgid "Lead 1 (square)"
+msgstr ""
+
+#: Programs/midi.c:135
+msgid "Lead 2 (sawtooth)"
+msgstr ""
+
+#: Programs/midi.c:136
+msgid "Lead 3 (calliope)"
+msgstr ""
+
+#: Programs/midi.c:137
+msgid "Lead 4 (chiff)"
+msgstr ""
+
+#: Programs/midi.c:138
+msgid "Lead 5 (charang)"
+msgstr ""
+
+#: Programs/midi.c:139
+msgid "Lead 6 (voice)"
+msgstr ""
+
+#: Programs/midi.c:140
+msgid "Lead 7 (fifths)"
+msgstr ""
+
+#: Programs/midi.c:141
+msgid "Lead 8 (bass + lead)"
+msgstr ""
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "Modalità apprendimento"
+
+#: Programs/menu_prefs.c:1222
+msgid "Left"
+msgstr "Sinistra"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr ""
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr ""
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr ""
+
+#: Programs/menu_prefs.c:616
+msgid "Literary Braille"
+msgstr ""
+
+#: Programs/menu_prefs.c:1366
+msgid "Locale Directory"
+msgstr ""
+
+#: Programs/menu_prefs.c:1402
+msgid "Log Categories"
+msgstr ""
+
+#: Programs/config.c:836
+msgid "Log Level"
+msgstr ""
+
+#: Programs/menu_prefs.c:1463
+msgid "Log Messages"
+msgstr "Messaggi di log"
+
+#: Programs/config.c:688
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr ""
+
+#: Programs/config.c:653
+msgid "Log to standard error rather than to the system log."
+msgstr ""
+
+#: Programs/config.c:667
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)"
+msgstr ""
+
+#: Programs/menu_prefs.c:867
+msgid "Long Press Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:647 Programs/menu_prefs.c:900
+msgid "Low"
+msgstr ""
+
+#: Programs/menu_prefs.c:577
+msgid "Lower Left Dot"
+msgstr ""
+
+#: Programs/menu_prefs.c:578
+msgid "Lower Right Dot"
+msgstr ""
+
+#: Programs/menu_prefs.c:947
+msgid "MIDI"
+msgstr "MIDI"
+
+#: Programs/config.c:637
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr ""
+
+#: Programs/menu_prefs.c:977
+msgid "MIDI Instrument"
+msgstr "Strumento MIDI"
+
+#: Programs/menu_prefs.c:967
+msgid "MIDI Volume"
+msgstr "Volume MIDI"
+
+#: Programs/menu_prefs.c:1326
+msgid "Mailing List"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:59
+msgid "March"
+msgstr "Marzo"
+
+#: Programs/midi.c:57
+msgid "Marimba"
+msgstr "Marimba"
+
+#: Programs/menu_prefs.c:650 Programs/menu_prefs.c:903
+msgid "Maximum"
+msgstr ""
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:61
+msgid "May"
+msgstr "Maggio"
+
+#: Programs/menu_prefs.c:648 Programs/menu_prefs.c:901
+msgid "Medium"
+msgstr ""
+
+#: Programs/midi.c:175
+msgid "Melodic Tom"
+msgstr ""
+
+#: Programs/menu_prefs.c:590
+msgid "Menu Options"
+msgstr ""
+
+#: Programs/config.c:646
+msgid "Message hold timeout (in 10ms units)."
+msgstr ""
+
+#: Programs/config.c:839
+msgid "Messages Directory"
+msgstr ""
+
+#: Programs/config.c:838
+msgid "Messages Domain"
+msgstr ""
+
+#: Programs/config.c:837
+msgid "Messages Locale"
+msgstr ""
+
+#: Programs/menu_prefs.c:646 Programs/menu_prefs.c:899
+msgid "Minimum"
+msgstr "Minimo"
+
+#: Programs/menu_prefs.c:1194
+msgid "Month Day Year"
+msgstr "Mese giorno, anno"
+
+#: Programs/midi.c:55
+msgid "Music Box"
+msgstr ""
+
+#: Programs/menu_prefs.c:947
+msgid "Musical Instrument Digital Interface"
+msgstr ""
+
+#: Programs/midi.c:110
+msgid "Muted Trumpet"
+msgstr ""
+
+#: Programs/config.c:537
+msgid "Name of or path to attributes table."
+msgstr ""
+
+#: Programs/config.c:546
+msgid "Name of or path to contraction table."
+msgstr ""
+
+#: Programs/config.c:402
+msgid "Name of or path to default preferences file."
+msgstr ""
+
+#: Programs/config.c:555
+msgid "Name of or path to keyboard table."
+msgstr ""
+
+#: Programs/config.c:591
+msgid "Name of or path to speech input object."
+msgstr ""
+
+#: Programs/config.c:528
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr ""
+
+#: Programs/menu_prefs.c:734
+msgid "Navigation Options"
+msgstr ""
+
+#: Programs/menu.c:449
+msgid "No"
+msgstr "No"
+
+#: Programs/menu_prefs.c:633
+msgid "No Capitalization"
+msgstr ""
+
+#: Programs/menu_prefs.c:786 Programs/menu_prefs.c:1030
+#: Programs/menu_prefs.c:1043 Programs/menu_prefs.c:1056
+#: Programs/menu_prefs.c:1182 Programs/menu_prefs.c:1221
+#: Programs/menu_prefs.c:1241
+msgid "None"
+msgstr "Nessuno"
+
+#: Programs/menu_prefs.c:1378
+msgid "Notice"
+msgstr "Avviso"
+
+#: Programs/cmd_miscellaneous.c:67
+msgid "November"
+msgstr "Novembre"
+
+#: Programs/midi.c:120
+msgid "Oboe"
+msgstr "Oboe"
+
+#: Programs/midi.c:132
+msgid "Ocarina"
+msgstr "Ocarina"
+
+#: Programs/cmd_miscellaneous.c:66
+msgid "October"
+msgstr "Ottobre"
+
+#: Programs/menu_prefs.c:849
+msgid "Off"
+msgstr "Off"
+
+#: Programs/config.c:1616
+msgid "Old Preferences File"
+msgstr ""
+
+#: Programs/menu_prefs.c:862
+msgid "On First Release"
+msgstr ""
+
+#: Programs/midi.c:105
+msgid "Orchestra Hit"
+msgstr ""
+
+#: Programs/midi.c:95
+msgid "Orchestral Harp"
+msgstr ""
+
+#: Programs/midi.c:26
+msgid "Organ"
+msgstr "Organo"
+
+#: Programs/log.c:76
+msgid "Output Packets"
+msgstr ""
+
+#: Programs/midi.c:76
+msgid "Overdriven Guitar"
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:946
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:627
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr ""
+
+#: Programs/menu_prefs.c:959
+msgid "PCM Volume"
+msgstr ""
+
+#: Programs/cmds.auto.h:1266
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr ""
+
+#: Programs/menu_prefs.c:1316
+msgid "Package Revision"
+msgstr ""
+
+#: Programs/menu_prefs.c:1311
+msgid "Package Version"
+msgstr ""
+
+#. Synth Pad
+#: Programs/midi.c:143
+msgid "Pad 1 (new age)"
+msgstr ""
+
+#: Programs/midi.c:144
+msgid "Pad 2 (warm)"
+msgstr ""
+
+#: Programs/midi.c:145
+msgid "Pad 3 (polysynth)"
+msgstr ""
+
+#: Programs/midi.c:146
+msgid "Pad 4 (choir)"
+msgstr ""
+
+#: Programs/midi.c:147
+msgid "Pad 5 (bowed)"
+msgstr ""
+
+#: Programs/midi.c:148
+msgid "Pad 6 (metallic)"
+msgstr ""
+
+#: Programs/midi.c:149
+msgid "Pad 7 (halo)"
+msgstr ""
+
+#: Programs/midi.c:150
+msgid "Pad 8 (sweep)"
+msgstr ""
+
+#: Programs/midi.c:128
+msgid "Pan Flute"
+msgstr "Flauto di Pan"
+
+#: Programs/config.c:464
+msgid "Parameters for the application programming interface."
+msgstr ""
+
+#: Programs/config.c:486
+msgid "Parameters for the braille driver."
+msgstr ""
+
+#: Programs/config.c:359
+msgid "Parameters for the privilege establishment stage."
+msgstr ""
+
+#: Programs/config.c:618
+msgid "Parameters for the screen driver."
+msgstr ""
+
+#: Programs/config.c:583
+msgid "Parameters for the speech driver."
+msgstr ""
+
+#: Programs/config.c:393
+msgid "Path to default settings file."
+msgstr ""
+
+#: Programs/config.c:447
+msgid "Path to directory containing drivers."
+msgstr ""
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:518
+msgid "Path to directory containing tables."
+msgstr ""
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr ""
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr ""
+
+#: Programs/brltest.c:78 Programs/config.c:437
+msgid "Path to directory which can be written to."
+msgstr ""
+
+#: Programs/config.c:427
+msgid "Path to directory which contains files that can be updated."
+msgstr ""
+
+#: Programs/config.c:327
+msgid "Path to directory which contains message translations."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr ""
+
+#: Programs/config.c:676
+msgid "Path to log file."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr ""
+
+#: Programs/config.c:383
+msgid "Path to process identifier file."
+msgstr ""
+
+#: Programs/config.c:417
+msgid "Patterns that match command prompts."
+msgstr ""
+
+#: Programs/midi.c:38
+msgid "Percussive Instruments"
+msgstr ""
+
+#: Programs/midi.c:63
+msgid "Percussive Organ"
+msgstr ""
+
+#: Programs/midi.c:24
+msgid "Piano"
+msgstr "Pianoforte"
+
+#. Pipe
+#: Programs/midi.c:125
+msgid "Piccolo"
+msgstr ""
+
+#: Programs/midi.c:33
+msgid "Pipe"
+msgstr ""
+
+#: Programs/midi.c:94
+msgid "Pizzicato Strings"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1119
+msgid "Powerdown"
+msgstr ""
+
+#: Programs/config.c:2771 Programs/menu_prefs.c:1346
+msgid "Preferences File"
+msgstr "File delle preferenze"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr ""
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr ""
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr ""
+
+#: Programs/config.c:790
+msgid "Privilege Parameter"
+msgstr ""
+
+#: Programs/menu_prefs.c:1297
+msgid "Profiles"
+msgstr "Profili"
+
+#: Programs/config.c:563
+msgid "Properties of eligible keyboards."
+msgstr ""
+
+#: Programs/menu_prefs.c:839
+msgid "Quick Space"
+msgstr ""
+
+#: Programs/menu_prefs.c:1047
+msgid "Raise Pitch"
+msgstr ""
+
+#: Programs/config.c:318
+msgid "Recognize environment variables."
+msgstr ""
+
+#: Programs/midi.c:127
+msgid "Recorder"
+msgstr "Registratore"
+
+#: Programs/midi.c:32
+msgid "Reed"
+msgstr ""
+
+#: Programs/midi.c:66
+msgid "Reed Organ"
+msgstr ""
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr ""
+
+#: Programs/config.c:508
+msgid "Release braille device when screen or window is unreadable."
+msgstr ""
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr ""
+
+#: Programs/config.c:334
+msgid "Remain a foreground process."
+msgstr ""
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr ""
+
+#: Programs/config.c:349
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr ""
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr ""
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr ""
+
+#: Programs/menu_prefs.c:755
+msgid "Rest of Line"
+msgstr ""
+
+#: Programs/menu_prefs.c:1445
+msgid "Restart Braille Driver"
+msgstr ""
+
+#: Programs/menu_prefs.c:1457
+msgid "Restart Screen Driver"
+msgstr ""
+
+#: Programs/menu_prefs.c:1451
+msgid "Restart Speech Driver"
+msgstr ""
+
+#: Programs/midi.c:177
+msgid "Reverse Cymbal"
+msgstr ""
+
+#: Programs/menu_prefs.c:1223
+msgid "Right"
+msgstr ""
+
+#: Programs/midi.c:64
+msgid "Rock Organ"
+msgstr ""
+
+#: Programs/menu_prefs.c:585
+msgid "Save on Exit"
+msgstr "Salva all'uscita"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1046
+msgid "Say Cap"
+msgstr ""
+
+#: Programs/menu_prefs.c:1070
+msgid "Say Line Mode"
+msgstr ""
+
+#: Programs/menu_prefs.c:1057
+msgid "Say Space"
+msgstr ""
+
+#: Programs/menu_prefs.c:460
+msgid "Screen Cursor Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:459
+msgid "Screen Cursor Coordinates"
+msgstr ""
+
+#: Programs/menu_prefs.c:687
+msgid "Screen Cursor Invisible Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:461
+msgid "Screen Cursor Row"
+msgstr ""
+
+#: Programs/menu_prefs.c:669
+msgid "Screen Cursor Style"
+msgstr ""
+
+#: Programs/menu_prefs.c:681
+msgid "Screen Cursor Visible Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:462
+msgid "Screen Cursor and Braille Window Column"
+msgstr ""
+
+#: Programs/menu_prefs.c:463
+msgid "Screen Cursor and Braille Window Row"
+msgstr ""
+
+#: Programs/config.c:2268
+msgid "Screen Driver"
+msgstr ""
+
+#: Programs/log.c:160
+msgid "Screen Driver Events"
+msgstr ""
+
+#: Programs/menu_prefs.c:464
+msgid "Screen Number"
+msgstr ""
+
+#: Programs/config.c:2274
+msgid "Screen Parameter"
+msgstr ""
+
+#: Programs/config.c:608
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/menu_prefs.c:780
+msgid "Scroll-aware Cursor Navigation"
+msgstr ""
+
+#: Programs/midi.c:181
+msgid "Seashore"
+msgstr "Onde del mare"
+
+#: Programs/cmd_miscellaneous.c:65
+msgid "September"
+msgstr "Settembre"
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr "I/O seriale"
+
+#: Programs/log.c:124
+msgid "Server Events"
+msgstr ""
+
+#: Programs/midi.c:130
+msgid "Shakuhachi"
+msgstr "Shakuhachi"
+
+#: Programs/midi.c:163
+msgid "Shamisen"
+msgstr "Shamisen"
+
+#: Programs/midi.c:168
+msgid "Shanai"
+msgstr "Shanai"
+
+#: Programs/menu_prefs.c:598
+msgid "Show Advanced Submenus"
+msgstr ""
+
+#: Programs/menu_prefs.c:603
+msgid "Show All Items"
+msgstr ""
+
+#: Programs/menu_prefs.c:693
+msgid "Show Attributes"
+msgstr ""
+
+#: Programs/menu_prefs.c:664
+msgid "Show Screen Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:1176
+msgid "Show Seconds"
+msgstr ""
+
+#: Programs/menu_prefs.c:1122
+msgid "Show Speech Cursor"
+msgstr ""
+
+#: Programs/menu_prefs.c:593
+msgid "Show Submenu Sizes"
+msgstr ""
+
+#. Ethnic Instruments
+#: Programs/midi.c:161
+msgid "Sitar"
+msgstr "Sitar"
+
+#: Programs/menu_prefs.c:747
+msgid "Skip Blank Braille Windows"
+msgstr ""
+
+#: Programs/menu_prefs.c:741
+msgid "Skip Identical Lines"
+msgstr ""
+
+#: Programs/menu_prefs.c:758
+msgid "Skip Which Blank Braille Windows"
+msgstr ""
+
+#: Programs/midi.c:84
+msgid "Slap Bass 1"
+msgstr ""
+
+#: Programs/midi.c:85
+msgid "Slap Bass 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1206
+msgid "Slash"
+msgstr "Barra"
+
+#: Programs/menu_prefs.c:763
+msgid "Sliding Braille Window"
+msgstr ""
+
+#: Programs/menu_prefs.c:1031
+msgid "Some"
+msgstr ""
+
+#. Reed
+#: Programs/midi.c:116
+msgid "Soprano Sax"
+msgstr ""
+
+#: Programs/midi.c:39
+msgid "Sound Effects"
+msgstr "Effetti sonori"
+
+#: Programs/menu_prefs.c:1242
+msgid "Space"
+msgstr "Spazio"
+
+#: Programs/menu_prefs.c:1110
+msgid "Speak Completed Words"
+msgstr ""
+
+#: Programs/menu_prefs.c:1098
+msgid "Speak Deleted Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1092
+msgid "Speak Inserted Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1116
+msgid "Speak Line Indent"
+msgstr ""
+
+#: Programs/menu_prefs.c:1104
+msgid "Speak Replaced Characters"
+msgstr ""
+
+#: Programs/menu_prefs.c:1086
+msgid "Speak Selected Character"
+msgstr ""
+
+#: Programs/menu_prefs.c:1080
+msgid "Speak Selected Line"
+msgstr ""
+
+#: Programs/menu_prefs.c:1145
+msgid "Speech Cursor Invisible Time"
+msgstr ""
+
+#: Programs/menu_prefs.c:1127
+msgid "Speech Cursor Style"
+msgstr ""
+
+#: Programs/menu_prefs.c:1139
+msgid "Speech Cursor Visible Time"
+msgstr ""
+
+#: Programs/config.c:2043
+msgid "Speech Driver"
+msgstr ""
+
+#: Programs/log.c:154
+msgid "Speech Driver Events"
+msgstr ""
+
+#: Programs/log.c:112
+msgid "Speech Events"
+msgstr ""
+
+#. Create the file system object for speech input.
+#: Programs/config.c:2882
+msgid "Speech Input"
+msgstr ""
+
+#: Programs/menu_prefs.c:1005
+msgid "Speech Options"
+msgstr ""
+
+#: Programs/config.c:2046
+msgid "Speech Parameter"
+msgstr ""
+
+#: Programs/menu_prefs.c:1022
+msgid "Speech Pitch"
+msgstr ""
+
+#: Programs/menu_prefs.c:1035
+msgid "Speech Punctuation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1015
+msgid "Speech Rate"
+msgstr ""
+
+#: Programs/menu_prefs.c:1050
+msgid "Speech Uppercase Indicator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1008
+msgid "Speech Volume"
+msgstr ""
+
+#: Programs/menu_prefs.c:1060
+msgid "Speech Whitespace Indicator"
+msgstr ""
+
+#: Programs/config.c:573
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr ""
+
+#: Programs/menu_prefs.c:1392
+msgid "Standard Error Log Level"
+msgstr ""
+
+#: Programs/menu_prefs.c:815
+msgid "Start Selection with Routing Key"
+msgstr ""
+
+#: Programs/menu_prefs.c:465
+msgid "State Dots"
+msgstr ""
+
+#: Programs/menu_prefs.c:466
+msgid "State Letter"
+msgstr ""
+
+#: Programs/menu_prefs.c:1217
+msgid "Status Cells"
+msgstr ""
+
+#: Programs/menu_prefs.c:1233
+msgid "Status Count"
+msgstr ""
+
+#: Programs/menu_prefs.c:474
+msgid "Status Field"
+msgstr ""
+
+#: Programs/menu_prefs.c:1226
+msgid "Status Position"
+msgstr ""
+
+#: Programs/menu_prefs.c:1248
+msgid "Status Separator"
+msgstr ""
+
+#: Programs/menu_prefs.c:1244
+msgid "Status Side"
+msgstr ""
+
+#: Programs/midi.c:172
+msgid "Steel Drums"
+msgstr ""
+
+#: Programs/config.c:373
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr ""
+
+#. Ensemble
+#: Programs/midi.c:98
+msgid "String Ensemble 1"
+msgstr ""
+
+#: Programs/midi.c:99
+msgid "String Ensemble 2"
+msgstr ""
+
+#: Programs/midi.c:29
+msgid "Strings"
+msgstr "Stringe"
+
+#: Programs/menu_prefs.c:635
+msgid "Superimpose Dot 7"
+msgstr ""
+
+#: Programs/config.c:659
+msgid "Suppress start-up messages."
+msgstr ""
+
+#: Programs/midi.c:86
+msgid "Synth Bass 1"
+msgstr ""
+
+#: Programs/midi.c:87
+msgid "Synth Bass 2"
+msgstr ""
+
+#: Programs/midi.c:176
+msgid "Synth Drum"
+msgstr ""
+
+#: Programs/midi.c:36
+msgid "Synth FM"
+msgstr ""
+
+#: Programs/midi.c:34
+msgid "Synth Lead"
+msgstr ""
+
+#: Programs/midi.c:35
+msgid "Synth Pad"
+msgstr ""
+
+#: Programs/midi.c:104
+msgid "Synth Voice"
+msgstr ""
+
+#: Programs/midi.c:113
+msgid "SynthBrass 1"
+msgstr ""
+
+#: Programs/midi.c:114
+msgid "SynthBrass 2"
+msgstr ""
+
+#: Programs/midi.c:100
+msgid "SynthStrings 1"
+msgstr ""
+
+#: Programs/midi.c:101
+msgid "SynthStrings 2"
+msgstr ""
+
+#: Programs/menu_prefs.c:1387
+msgid "System Log Level"
+msgstr ""
+
+#: Programs/config.c:2792 Programs/menu_prefs.c:1361
+msgid "Tables Directory"
+msgstr ""
+
+#: Programs/midi.c:174
+msgid "Taiko Drum"
+msgstr ""
+
+#: Programs/midi.c:69
+msgid "Tango Accordion"
+msgstr ""
+
+#: Programs/midi.c:183
+msgid "Telephone Ring"
+msgstr ""
+
+#: Programs/midi.c:118
+msgid "Tenor Sax"
+msgstr ""
+
+#: Programs/menu_prefs.c:661
+msgid "Text Indicators"
+msgstr ""
+
+#: Programs/menu_prefs.c:1245
+msgid "Text Side"
+msgstr ""
+
+#: Programs/menu_prefs.c:619
+msgid "Text Style"
+msgstr "Stile testo"
+
+#: Programs/config.c:2813 Programs/menu_prefs.c:1273
+msgid "Text Table"
+msgstr ""
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr ""
+
+#: Programs/config.c:304
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr ""
+
+#: Programs/config.c:311
+msgid "The text to be shown when the braille driver stops."
+msgstr ""
+
+#: Programs/menu_prefs.c:467
+msgid "Time"
+msgstr "Orario"
+
+#: Programs/menu_prefs.c:1161
+msgid "Time Format"
+msgstr "Formato dell'oro"
+
+#: Programs/menu_prefs.c:1153
+msgid "Time Presentation"
+msgstr ""
+
+#: Programs/menu_prefs.c:1171
+msgid "Time Separator"
+msgstr ""
+
+#: Programs/midi.c:96
+msgid "Timpani"
+msgstr "Timpani"
+
+#. Percussive Instruments
+#: Programs/midi.c:170
+msgid "Tinkle Bell"
+msgstr ""
+
+#: Programs/menu_prefs.c:1441
+msgid "Tools"
+msgstr "Strumenti"
+
+#: Programs/menu_prefs.c:892
+msgid "Touch Navigation"
+msgstr ""
+
+#: Programs/menu_prefs.c:906
+msgid "Touch Sensitivity"
+msgstr "Sensibilità al tocco"
+
+#: Programs/menu_prefs.c:804
+msgid "Track Screen Pointer"
+msgstr ""
+
+#: Programs/menu_prefs.c:798
+msgid "Track Screen Scroll"
+msgstr ""
+
+#: Programs/menu_prefs.c:830
+msgid "Translated via Text Table"
+msgstr ""
+
+#: Programs/midi.c:93
+msgid "Tremolo Strings"
+msgstr ""
+
+#: Programs/midi.c:108
+msgid "Trombone"
+msgstr ""
+
+#. Brass
+#: Programs/midi.c:107
+msgid "Trumpet"
+msgstr "Tromba"
+
+#: Programs/midi.c:109
+msgid "Tuba"
+msgstr ""
+
+#: Programs/midi.c:59
+msgid "Tubular Bells"
+msgstr ""
+
+#: Programs/menu_prefs.c:951
+msgid "Tune Device"
+msgstr "Accordare dispositivo"
+
+#: Programs/menu_prefs.c:834
+msgid "Typing Mode"
+msgstr ""
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr ""
+
+#: Programs/menu_prefs.c:575
+msgid "Underline"
+msgstr "Sottolineato"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr ""
+
+#: Programs/config.c:2789 Programs/menu_prefs.c:1341
+msgid "Updatable Directory"
+msgstr ""
+
+#: Programs/log.c:106
+msgid "Update Events"
+msgstr ""
+
+#: Programs/options.c:151
+msgid "Usage"
+msgstr ""
+
+#: Programs/menu_prefs.c:634
+msgid "Use Capital Sign"
+msgstr ""
+
+#: Programs/midi.c:56
+msgid "Vibraphone"
+msgstr ""
+
+#: Programs/midi.c:90
+msgid "Viola"
+msgstr ""
+
+#. Strings
+#: Programs/midi.c:89
+msgid "Violin"
+msgstr "Violino"
+
+#: Programs/midi.c:103
+msgid "Voice Oohs"
+msgstr ""
+
+#: Programs/menu_prefs.c:1377
+msgid "Warning"
+msgstr "Avviso"
+
+#: Programs/menu_prefs.c:1321
+msgid "Web Site"
+msgstr "Sito web"
+
+#: Programs/midi.c:131
+msgid "Whistle"
+msgstr ""
+
+#: Programs/midi.c:173
+msgid "Woodblock"
+msgstr ""
+
+#: Programs/menu_prefs.c:737
+msgid "Word Wrap"
+msgstr "A capo automatico"
+
+#: Programs/config.c:2763
+msgid "Working Directory"
+msgstr "Directory di lavoro"
+
+#: Programs/config.c:2790 Programs/menu_prefs.c:1351
+msgid "Writable Directory"
+msgstr "Directory con accesso in scrittura"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr ""
+
+#: Programs/config.c:682
+msgid "Write the start-up logs, and then exit."
+msgstr ""
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr ""
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr ""
+
+#: Programs/cmds.auto.h:1259
+msgid "XT (set 1) keyboard scan code"
+msgstr ""
+
+#: Programs/midi.c:58
+msgid "Xylophone"
+msgstr "Xilofono"
+
+#: Programs/menu_prefs.c:1193
+msgid "Year Month Day"
+msgstr "Anno mese giorno"
+
+#: Programs/menu.c:450
+msgid "Yes"
+msgstr "Sì"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr ""
+
+#: Programs/menu_prefs.c:576
+msgid "all dots"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:121
+msgid "and"
+msgstr "e"
+
+#: Programs/cmds.auto.h:1142
+msgid "append characters to clipboard"
+msgstr ""
+
+#: Programs/cmds.auto.h:1033
+msgid "append to clipboard from character"
+msgstr ""
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr ""
+
+#: Programs/cmds.auto.h:1301
+msgid "backspace key"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1110 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr ""
+
+#: Programs/cmds.auto.h:1222
+msgid "bind to specific virtual terminal"
+msgstr ""
+
+#: Programs/cmds.auto.h:835
+msgid "bind to the next virtual terminal"
+msgstr ""
+
+#: Programs/cmds.auto.h:829
+msgid "bind to the previous virtual terminal"
+msgstr ""
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "nero"
+
+#: Programs/core.c:1120
+msgid "blank line"
+msgstr ""
+
+#: Programs/cmd_utils.c:168
+msgid "blink"
+msgstr ""
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "blu"
+
+#: Programs/config.c:2853
+#, c-format
+msgid "braille device not specified"
+msgstr "dispositivo braille non specificato"
+
+#: Programs/cmds.auto.h:544
+msgid "braille display temporarily unavailable"
+msgstr ""
+
+#: Programs/config.c:1553
+msgid "braille driver initialization failed"
+msgstr ""
+
+#: Programs/config.c:1632
+msgid "braille driver not loadable"
+msgstr ""
+
+#: Programs/config.c:1892
+msgid "braille driver restarting"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:178
+msgid "braille driver stopped"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr ""
+
+#: Programs/cmds.auto.h:1019
+msgid "bring screen cursor to character"
+msgstr ""
+
+#: Programs/cmds.auto.h:520
+msgid "bring screen cursor to current line"
+msgstr ""
+
+#: Programs/cmds.auto.h:1193
+msgid "bring screen cursor to line"
+msgstr ""
+
+#: Programs/cmds.auto.h:730
+msgid "bring screen cursor to speech cursor"
+msgstr ""
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "marrone"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1601
+msgid "can't read screen header"
+msgstr ""
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr ""
+
+#: Programs/ctb_translate.c:365
+msgid "cannot compile contraction table"
+msgstr ""
+
+#: Programs/config.c:1536
+msgid "cannot compile key table"
+msgstr ""
+
+#: Programs/config.c:1113
+msgid "cannot compile keyboard table"
+msgstr ""
+
+#: Programs/ttb_translate.c:256
+msgid "cannot compile text table"
+msgstr ""
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr ""
+
+#: Programs/file.c:360
+msgid "cannot create directory"
+msgstr ""
+
+#: Programs/program.c:148
+#, c-format
+msgid "cannot determine program directory"
+msgstr ""
+
+#: Programs/config.c:2766 Programs/menu.c:544
+msgid "cannot determine working directory"
+msgstr ""
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr ""
+
+#: Programs/program.c:156
+msgid "cannot fix install path"
+msgstr ""
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr ""
+
+#: Programs/file.c:497
+msgid "cannot get working directory"
+msgstr ""
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr ""
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr ""
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr ""
+
+#: Programs/ttb_translate.c:276
+msgid "cannot load text table"
+msgstr ""
+
+#: Programs/file.c:349
+msgid "cannot make world writable"
+msgstr ""
+
+#: Programs/config.c:1071
+msgid "cannot open key help"
+msgstr ""
+
+#: Programs/program.c:260
+msgid "cannot open process identifier file"
+msgstr ""
+
+#: Programs/menu.c:542
+msgid "cannot open working directory"
+msgstr ""
+
+#: Programs/prefs.c:350
+msgid "cannot read preferences file"
+msgstr ""
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr ""
+
+#: Programs/file.c:511 Programs/menu.c:531
+msgid "cannot set working directory"
+msgstr ""
+
+#: Programs/prefs.c:525
+msgid "cannot write to preferences file"
+msgstr ""
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1065
+msgid "cap"
+msgstr ""
+
+#: Programs/menu_prefs.c:775 Programs/menu_prefs.c:1234
+msgid "cells"
+msgstr ""
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr ""
+
+#: Programs/cmds.auto.h:775
+msgid "clear all sticky input modifiers"
+msgstr ""
+
+#: Programs/cmds.auto.h:889
+msgid "clear the text selection"
+msgstr ""
+
+#: Programs/cmd_speech.c:475
+msgid "column"
+msgstr "colonna"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr ""
+
+#: Programs/options.c:807
+msgid "configuration directive specified more than once"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr ""
+
+#: Programs/menu_prefs.c:945
+msgid "console tone generator"
+msgstr ""
+
+#: Programs/cmds.auto.h:1135
+msgid "copy characters to clipboard"
+msgstr ""
+
+#: Programs/cmds.auto.h:901
+msgid "copy selected text to host clipboard"
+msgstr ""
+
+#: Programs/config.c:644 Programs/menu_prefs.c:503
+msgid "csecs"
+msgstr ""
+
+#: Programs/cmds.auto.h:1280
+msgid "current reading location"
+msgstr ""
+
+#: Programs/cmds.auto.h:1336
+msgid "cursor-down key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1315
+msgid "cursor-left key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1322
+msgid "cursor-right key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1329
+msgid "cursor-up key"
+msgstr ""
+
+#: Programs/cmds.auto.h:907
+msgid "cut selected text to host clipboard"
+msgstr ""
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr ""
+
+#: Programs/cmds.auto.h:781
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr ""
+
+#: Programs/cmds.auto.h:562
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr ""
+
+#: Programs/cmds.auto.h:787
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr ""
+
+#: Programs/cmds.auto.h:568
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr ""
+
+#: Programs/cmds.auto.h:550
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr ""
+
+#: Programs/cmds.auto.h:556
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr ""
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "grigio scuro"
+
+#: Programs/cmds.auto.h:483
+msgid "decrease speaking rate"
+msgstr ""
+
+#: Programs/cmds.auto.h:495
+msgid "decrease speaking volume"
+msgstr ""
+
+#: Programs/cmds.auto.h:1378
+msgid "delete key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1079
+msgid "describe character"
+msgstr ""
+
+#: Programs/cmds.auto.h:717
+msgid "describe current character"
+msgstr ""
+
+#: Programs/config.c:625 Programs/config.c:635
+msgid "device"
+msgstr ""
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:323 Programs/config.c:423
+#: Programs/config.c:433 Programs/config.c:443 Programs/config.c:514
+msgid "directory"
+msgstr "directory"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr ""
+
+#: Programs/cmds.auto.h:4
+msgid "do nothing"
+msgstr "non fare nulla"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr ""
+
+#: Programs/menu_prefs.c:577
+msgid "dot 7"
+msgstr ""
+
+#: Programs/menu_prefs.c:578
+msgid "dot 8"
+msgstr ""
+
+#: Programs/menu_prefs.c:575
+msgid "dots 7 and 8"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1107
+msgid "driver request"
+msgstr ""
+
+#: Programs/config.c:472 Programs/config.c:570 Programs/config.c:605
+msgid "driver,..."
+msgstr ""
+
+#: Programs/cmds.auto.h:1364
+msgid "end key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1287
+msgid "enter key"
+msgstr ""
+
+#: Programs/cmds.auto.h:384
+msgid "enter/leave command learn mode"
+msgstr ""
+
+#: Programs/cmds.auto.h:372
+msgid "enter/leave help display"
+msgstr ""
+
+#: Programs/cmds.auto.h:390
+msgid "enter/leave preferences menu"
+msgstr ""
+
+#: Programs/cmds.auto.h:378
+msgid "enter/leave status display"
+msgstr ""
+
+#: Programs/cmds.auto.h:1308
+msgid "escape key"
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "eurobraille"
+
+#: Programs/cmd_miscellaneous.c:118
+msgid "exactly"
+msgstr ""
+
+#: Programs/config.c:819
+msgid "excess argument"
+msgstr ""
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2045
+msgid "executing as the invoking user"
+msgstr ""
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr ""
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:380
+#: Programs/config.c:389 Programs/config.c:399 Programs/config.c:525
+#: Programs/config.c:535 Programs/config.c:544 Programs/config.c:553
+#: Programs/config.c:589 Programs/config.c:674
+msgid "file"
+msgstr "file"
+
+#: Programs/options.c:993
+#, c-format
+msgid "file '%s' processing error."
+msgstr ""
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr ""
+
+#: Programs/cmds.auto.h:1386
+msgid "function key"
+msgstr ""
+
+#: Programs/cmds.auto.h:240
+msgid "go back after cursor tracking"
+msgstr ""
+
+#: Programs/cmds.auto.h:939
+msgid "go back to the previous screen"
+msgstr ""
+
+#: Programs/cmds.auto.h:186
+msgid "go backward one braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:202
+msgid "go backward skipping blank braille windows"
+msgstr ""
+
+#: Programs/cmds.auto.h:843
+msgid "go backward to nearest non-blank braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:20
+msgid "go down one line"
+msgstr ""
+
+#: Programs/cmds.auto.h:36
+msgid "go down several lines"
+msgstr ""
+
+#: Programs/cmds.auto.h:118
+msgid "go down to first line of next paragraph"
+msgstr ""
+
+#: Programs/cmds.auto.h:418
+msgid "go down to last item"
+msgstr ""
+
+#: Programs/cmds.auto.h:1128
+msgid "go down to nearest line with different character"
+msgstr ""
+
+#: Programs/cmds.auto.h:52
+msgid "go down to nearest line with different content"
+msgstr ""
+
+#: Programs/cmds.auto.h:68
+msgid "go down to nearest line with different highlighting"
+msgstr ""
+
+#: Programs/cmds.auto.h:1072
+msgid "go down to nearest line with less indent than character"
+msgstr ""
+
+#: Programs/cmds.auto.h:134
+msgid "go down to next command prompt"
+msgstr ""
+
+#: Programs/cmds.auto.h:434
+msgid "go down to next item"
+msgstr ""
+
+#: Programs/cmds.auto.h:194
+msgid "go forward one braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:210
+msgid "go forward skipping blank braille windows"
+msgstr ""
+
+#: Programs/cmds.auto.h:851
+msgid "go forward to nearest non-blank braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:170
+msgid "go left half a braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:154
+msgid "go left one character"
+msgstr ""
+
+#: Programs/cmds.auto.h:178
+msgid "go right half a braille window"
+msgstr ""
+
+#: Programs/cmds.auto.h:162
+msgid "go right one character"
+msgstr ""
+
+#: Programs/cmds.auto.h:690
+msgid "go to and speak first non-blank character on line"
+msgstr ""
+
+#: Programs/cmds.auto.h:704
+msgid "go to and speak first non-blank line on screen"
+msgstr ""
+
+#: Programs/cmds.auto.h:697
+msgid "go to and speak last non-blank character on line"
+msgstr ""
+
+#: Programs/cmds.auto.h:711
+msgid "go to and speak last non-blank line on screen"
+msgstr ""
+
+#: Programs/cmds.auto.h:643
+msgid "go to and speak next character"
+msgstr ""
+
+#: Programs/cmds.auto.h:683
+msgid "go to and speak next line"
+msgstr ""
+
+#: Programs/cmds.auto.h:663
+msgid "go to and speak next word"
+msgstr ""
+
+#: Programs/cmds.auto.h:636
+msgid "go to and speak previous character"
+msgstr ""
+
+#: Programs/cmds.auto.h:676
+msgid "go to and speak previous line"
+msgstr ""
+
+#: Programs/cmds.auto.h:656
+msgid "go to and speak previous word"
+msgstr ""
+
+#: Programs/cmds.auto.h:102
+msgid "go to beginning of bottom line"
+msgstr ""
+
+#: Programs/cmds.auto.h:218
+msgid "go to beginning of line"
+msgstr ""
+
+#: Programs/cmds.auto.h:93
+msgid "go to beginning of top line"
+msgstr ""
+
+#: Programs/cmds.auto.h:84
+msgid "go to bottom line"
+msgstr ""
+
+#: Programs/cmds.auto.h:459
+msgid "go to current speaking position"
+msgstr ""
+
+#: Programs/cmds.auto.h:226
+msgid "go to end of line"
+msgstr ""
+
+#: Programs/cmds.auto.h:581
+msgid "go to previous menu level"
+msgstr ""
+
+#: Programs/cmds.auto.h:1101
+msgid "go to remembered braille window position"
+msgstr ""
+
+#: Programs/cmds.auto.h:233
+msgid "go to screen cursor"
+msgstr ""
+
+#: Programs/cmds.auto.h:247
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr ""
+
+#: Programs/cmds.auto.h:1110
+msgid "go to selected line"
+msgstr ""
+
+#: Programs/cmds.auto.h:932
+msgid "go to the home screen"
+msgstr ""
+
+#: Programs/cmds.auto.h:76
+msgid "go to top line"
+msgstr ""
+
+#: Programs/cmds.auto.h:12
+msgid "go up one line"
+msgstr ""
+
+#: Programs/cmds.auto.h:28
+msgid "go up several lines"
+msgstr ""
+
+#: Programs/cmds.auto.h:410
+msgid "go up to first item"
+msgstr ""
+
+#: Programs/cmds.auto.h:110
+msgid "go up to first line of paragraph"
+msgstr ""
+
+#: Programs/cmds.auto.h:1119
+msgid "go up to nearest line with different character"
+msgstr ""
+
+#: Programs/cmds.auto.h:44
+msgid "go up to nearest line with different content"
+msgstr ""
+
+#: Programs/cmds.auto.h:60
+msgid "go up to nearest line with different highlighting"
+msgstr ""
+
+#: Programs/cmds.auto.h:1063
+msgid "go up to nearest line with less indent than character"
+msgstr ""
+
+#: Programs/cmds.auto.h:126
+msgid "go up to previous command prompt"
+msgstr ""
+
+#: Programs/cmds.auto.h:426
+msgid "go up to previous item"
+msgstr ""
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "verde"
+
+#: Programs/pgmprivs_linux.c:2167
+msgid "group permissions added"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:228
+msgid "help not available"
+msgstr ""
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr ""
+
+#: Programs/cmds.auto.h:1357
+msgid "home key"
+msgstr ""
+
+#: Programs/config.c:493
+msgid "identifier,..."
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1109
+msgid "idle timeout"
+msgstr ""
+
+#: Programs/cmds.auto.h:489
+msgid "increase speaking rate"
+msgstr ""
+
+#: Programs/cmds.auto.h:501
+msgid "increase speaking volume"
+msgstr ""
+
+#: Programs/core.c:1123
+msgid "indent"
+msgstr ""
+
+#: Programs/cmds.auto.h:1149
+msgid "insert clipboard history entry after screen cursor"
+msgstr ""
+
+#: Programs/cmds.auto.h:526
+msgid "insert clipboard text after screen cursor"
+msgstr ""
+
+#: Programs/cmds.auto.h:913
+msgid "insert host clipboard text after screen cursor"
+msgstr ""
+
+#: Programs/cmds.auto.h:1371
+msgid "insert key"
+msgstr ""
+
+#: Programs/program.c:164
+msgid "install path not absolute"
+msgstr ""
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr ""
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr ""
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr ""
+
+#: Programs/config.c:2678
+msgid "invalid message hold timeout"
+msgstr ""
+
+#. the operand for an option is invalid
+#: Programs/options.c:589
+msgid "invalid operand"
+msgstr ""
+
+#: Programs/brlapi_server.c:4413
+msgid "invalid thread stack size"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr ""
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr ""
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr ""
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr ""
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr ""
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr ""
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr ""
+
+#: Programs/cmd_speech.c:474
+msgid "line"
+msgstr ""
+
+#: Programs/cmds.auto.h:1047
+msgid "linear copy to character"
+msgstr ""
+
+#: Programs/config.c:665
+msgid "lvl|cat,..."
+msgstr ""
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "magenta"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:583
+msgid "missing operand"
+msgstr ""
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr ""
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr ""
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr ""
+
+#: Programs/cmds.auto.h:993
+msgid "move to the first item in the screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:1011
+msgid "move to the last item in the screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:1005
+msgid "move to the next item in the screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:999
+msgid "move to the previous item in the screen area"
+msgstr ""
+
+#: Programs/config.c:356 Programs/config.c:408 Programs/config.c:461
+#: Programs/config.c:483 Programs/config.c:561 Programs/config.c:580
+#: Programs/config.c:615
+msgid "name=value,..."
+msgstr ""
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr ""
+
+#: Programs/config.c:1080
+msgid "no key bindings"
+msgstr ""
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr ""
+
+#: Programs/config.c:717
+msgid "none"
+msgstr ""
+
+#: Programs/config.c:1353
+msgid "not saved"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2018
+msgid "not switching to an unprivileged user"
+msgstr ""
+
+#: Programs/cmds.auto.h:969
+msgid "open the application alerts window"
+msgstr ""
+
+#: Programs/cmds.auto.h:957
+msgid "open the application list window"
+msgstr ""
+
+#: Programs/cmds.auto.h:963
+msgid "open the application-specific menu"
+msgstr ""
+
+#: Programs/cmds.auto.h:925
+msgid "open the braille actions window"
+msgstr ""
+
+#: Programs/cmds.auto.h:951
+msgid "open the device options window"
+msgstr ""
+
+#: Programs/cmds.auto.h:945
+msgid "open the device settings window"
+msgstr ""
+
+#: Programs/options.c:155
+msgid "option"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:2152
+msgid "ownership claimed"
+msgstr ""
+
+#: Programs/cmds.auto.h:1350
+msgid "page-down key"
+msgstr ""
+
+#: Programs/cmds.auto.h:1343
+msgid "page-up key"
+msgstr ""
+
+#: Programs/menu_prefs.c:509
+msgid "percentage"
+msgstr "percentuale"
+
+#: Programs/config.c:2651
+msgid "pid file not specified"
+msgstr ""
+
+#: Programs/program.c:306
+msgid "pid file open error"
+msgstr ""
+
+#: Programs/spk.c:200
+msgid "pitch"
+msgstr ""
+
+#: Programs/cmds.auto.h:1086
+msgid "place left end of braille window at character"
+msgstr ""
+
+#: Drivers/Braille/Baum/braille.c:1108
+msgid "power switch"
+msgstr ""
+
+#: Programs/spk.c:186
+msgid "rate"
+msgstr ""
+
+#: Programs/cmds.auto.h:1040
+msgid "rectangular copy to character"
+msgstr ""
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "rosso"
+
+#: Programs/cmds.auto.h:877
+msgid "refresh braille display"
+msgstr ""
+
+#: Programs/cmds.auto.h:1201
+msgid "refresh braille line"
+msgstr ""
+
+#: Programs/config.c:415
+msgid "regexp,..."
+msgstr ""
+
+#: Programs/config.c:1896
+#, c-format
+msgid "reinitializing braille driver"
+msgstr ""
+
+#: Programs/config.c:2409
+#, c-format
+msgid "reinitializing screen driver"
+msgstr ""
+
+#: Programs/config.c:2209
+#, c-format
+msgid "reinitializing speech driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:1093
+msgid "remember current braille window position"
+msgstr ""
+
+#: Programs/cmds.auto.h:1229
+msgid "render an alert"
+msgstr ""
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr ""
+
+#: Programs/cmds.auto.h:532
+msgid "restart braille driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:538
+msgid "restart speech driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:755
+msgid "restore clipboard from disk"
+msgstr ""
+
+#: Programs/cmds.auto.h:402
+msgid "restore preferences from disk"
+msgstr ""
+
+#: Programs/cmds.auto.h:975
+msgid "return to the active screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:749
+msgid "save clipboard to disk"
+msgstr ""
+
+#: Programs/cmds.auto.h:396
+msgid "save preferences to disk"
+msgstr ""
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr ""
+
+#: Programs/config.c:2285
+msgid "screen driver not loadable"
+msgstr ""
+
+#: Programs/config.c:2406
+msgid "screen driver restarting"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:186
+msgid "screen driver stopped"
+msgstr ""
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr ""
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr ""
+
+#: Programs/cmds.auto.h:140
+msgid "search backward for clipboard text"
+msgstr ""
+
+#: Programs/cmds.auto.h:146
+msgid "search forward for clipboard text"
+msgstr ""
+
+#: Programs/cmd_miscellaneous.c:122
+msgid "second"
+msgid_plural "seconds"
+msgstr[0] "secondo"
+msgstr[1] "secondi"
+
+#: Programs/cmds.auto.h:895
+msgid "select all of the text"
+msgstr ""
+
+#: Programs/cmds.auto.h:446
+msgid "select next choice"
+msgstr ""
+
+#: Programs/cmds.auto.h:440
+msgid "select previous choice"
+msgstr ""
+
+#: Programs/cmds.auto.h:352
+msgid "set alert tunes on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:338
+msgid "set attribute blinking on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:331
+msgid "set attribute underlining on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:1163
+msgid "set attributes table"
+msgstr ""
+
+#: Programs/cmds.auto.h:359
+msgid "set autorepeat on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:623
+msgid "set autospeak completed words on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:609
+msgid "set autospeak deleted characters on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:871
+msgid "set autospeak indent of current line on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:602
+msgid "set autospeak inserted characters on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:366
+msgid "set autospeak on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:616
+msgid "set autospeak replaced characters on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:595
+msgid "set autospeak selected character on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:588
+msgid "set autospeak selected line on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:769
+msgid "set braille keyboard enabled/disabled"
+msgstr ""
+
+#: Programs/cmds.auto.h:762
+msgid "set braille typing mode dots/text"
+msgstr ""
+
+#: Programs/cmds.auto.h:345
+msgid "set capital letter blinking on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:1170
+msgid "set contraction table"
+msgstr ""
+
+#: Programs/cmds.auto.h:261
+msgid "set display mode attributes/text"
+msgstr ""
+
+#: Programs/cmds.auto.h:303
+msgid "set hidden screen cursor on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:1177
+msgid "set keyboard table"
+msgstr ""
+
+#: Programs/cmds.auto.h:1184
+msgid "set language profile"
+msgstr ""
+
+#: Programs/cmds.auto.h:324
+msgid "set screen cursor blinking on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:317
+msgid "set screen cursor style block/underline"
+msgstr ""
+
+#: Programs/cmds.auto.h:296
+msgid "set screen cursor visibility on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:254
+msgid "set screen image frozen/unfrozen"
+msgstr ""
+
+#: Programs/cmds.auto.h:289
+msgid "set skipping of blank braille windows on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:282
+msgid "set skipping of lines with identical content on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:275
+msgid "set sliding braille window on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:743
+msgid "set speech cursor visibility on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:1215
+msgid "set text selection"
+msgstr ""
+
+#: Programs/cmds.auto.h:268
+msgid "set text style 6-dot/8-dot"
+msgstr ""
+
+#: Programs/cmds.auto.h:1156
+msgid "set text table"
+msgstr ""
+
+#: Programs/cmds.auto.h:858
+msgid "set touch navigation on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:310
+msgid "set track screen cursor on/off"
+msgstr ""
+
+#: Programs/cmds.auto.h:574
+msgid "show current date and time"
+msgstr ""
+
+#: Programs/cmds.auto.h:919
+msgid "show the window title"
+msgstr ""
+
+#: Programs/cmds.auto.h:883
+msgid "show various device status indicators"
+msgstr ""
+
+#: Programs/menu_prefs.c:946
+msgid "soundcard digital audio"
+msgstr ""
+
+#: Programs/menu_prefs.c:948
+msgid "soundcard synthesizer"
+msgstr ""
+
+#: Programs/cmd.c:277 Programs/core.c:1046
+msgid "space"
+msgstr "spazio"
+
+#: Programs/cmds.auto.h:629
+msgid "speak current character"
+msgstr ""
+
+#: Programs/cmds.auto.h:465 Programs/cmds.auto.h:669
+msgid "speak current line"
+msgstr ""
+
+#: Programs/cmds.auto.h:649
+msgid "speak current word"
+msgstr ""
+
+#: Programs/cmds.auto.h:477
+msgid "speak from current line through bottom of screen"
+msgstr ""
+
+#: Programs/cmds.auto.h:471
+msgid "speak from top of screen through current line"
+msgstr ""
+
+#: Programs/cmds.auto.h:864
+msgid "speak indent of current line"
+msgstr ""
+
+#: Programs/cmds.auto.h:736
+msgid "speak speech cursor location"
+msgstr ""
+
+#: Programs/config.c:2057
+msgid "speech driver not loadable"
+msgstr ""
+
+#: Programs/config.c:2206
+msgid "speech driver restarting"
+msgstr ""
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr ""
+
+#: Programs/cmds.auto.h:723
+msgid "spell current word"
+msgstr ""
+
+#: Programs/cmds.auto.h:1026
+msgid "start new clipboard at character"
+msgstr ""
+
+#: Programs/cmds.auto.h:1208
+msgid "start text selection"
+msgstr ""
+
+#: Programs/cmds.auto.h:799
+msgid "start the braille driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:823
+msgid "start the screen driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:811
+msgid "start the speech driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:452
+msgid "stop speaking"
+msgstr ""
+
+#: Programs/cmds.auto.h:793
+msgid "stop the braille driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:817
+msgid "stop the screen driver"
+msgstr ""
+
+#: Programs/cmds.auto.h:805
+msgid "stop the speech driver"
+msgstr ""
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr ""
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr ""
+
+#: Programs/cmds.auto.h:1273
+msgid "switch to command context"
+msgstr ""
+
+#: Programs/cmds.auto.h:1054
+msgid "switch to specific virtual terminal"
+msgstr ""
+
+#: Programs/cmds.auto.h:987
+msgid "switch to the next screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:513
+msgid "switch to the next virtual terminal"
+msgstr ""
+
+#: Programs/cmds.auto.h:981
+msgid "switch to the previous screen area"
+msgstr ""
+
+#: Programs/cmds.auto.h:507
+msgid "switch to the previous virtual terminal"
+msgstr ""
+
+#: Programs/pgmprivs_linux.c:1994
+msgid "switched to unprivileged user"
+msgstr ""
+
+#: Programs/cmds.auto.h:1294
+msgid "tab key"
+msgstr ""
+
+#: Programs/config.c:302 Programs/config.c:309
+msgid "text"
+msgstr "testo"
+
+#: Programs/cmds.auto.h:1245
+msgid "type braille dots"
+msgstr "tipo punti braille"
+
+#: Programs/cmds.auto.h:1237
+msgid "type unicode character"
+msgstr "tipo caratteri unicode"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr ""
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr ""
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr ""
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr ""
+
+#: Programs/options.c:823
+msgid "unknown configuration directive"
+msgstr ""
+
+#: Programs/config.c:737
+msgid "unknown log level or category"
+msgstr ""
+
+#. an unknown option has been specified
+#: Programs/options.c:570
+msgid "unknown option"
+msgstr "opzione sconosciuta"
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr ""
+
+#: Programs/spk.c:172
+msgid "volume"
+msgstr "volume"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "bianco"
+
+#: Programs/pgmprivs_linux.c:1856
+msgid "working directory changed"
+msgstr ""
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr ""
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr ""
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr ""
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "giallo"
+
+#~ msgid "Bug Reports"
+#~ msgstr "Segnalazioni bug"
+
+#~ msgid "Normal"
+#~ msgstr "Normale"
diff --git a/Messages/ru.po b/Messages/ru.po
new file mode 100644
index 0000000..dc2849d
--- /dev/null
+++ b/Messages/ru.po
@@ -0,0 +1,3632 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 4.5\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-01-27 11:47-0500\n"
+"PO-Revision-Date: 2021-02-07 08:18-0500\n"
+"Last-Translator: Маргарита Мельникова <margaretmelnikova@gmail.com>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr "\"%s\" запущено от имени \"%s\"\n"
+
+#: Programs/menu_prefs.c:457 Programs/menu_prefs.c:458
+#: Programs/menu_prefs.c:460 Programs/menu_prefs.c:461
+#: Programs/menu_prefs.c:464 Programs/menu_prefs.c:465
+#: Programs/menu_prefs.c:466 Programs/menu_prefs.c:468
+#: Programs/menu_prefs.c:469
+msgid "1 cell"
+msgstr "1 клетка"
+
+#: Programs/menu_prefs.c:789
+msgid "1 second"
+msgstr "1 секунда"
+
+#: Programs/menu_prefs.c:851
+msgid "10 seconds"
+msgstr "10 секунд"
+
+#: Programs/menu_prefs.c:1158
+msgid "12 Hour"
+msgstr "12 часов"
+
+#: Programs/menu_prefs.c:456 Programs/menu_prefs.c:459
+#: Programs/menu_prefs.c:462 Programs/menu_prefs.c:463
+#: Programs/menu_prefs.c:467
+msgid "2 cells"
+msgstr "2 клетки"
+
+#: Programs/menu_prefs.c:790
+msgid "2 seconds"
+msgstr "2 секунды"
+
+#: Programs/menu_prefs.c:852
+msgid "20 seconds"
+msgstr "20 секунд"
+
+#: Programs/menu_prefs.c:1157
+msgid "24 Hour"
+msgstr "24 часа"
+
+#: Programs/menu_prefs.c:787
+msgid "250 milliseconds"
+msgstr "250 миллисекунд"
+
+#: Programs/menu_prefs.c:853
+msgid "40 seconds"
+msgstr "40 секунд"
+
+#: Programs/menu_prefs.c:850
+msgid "5 seconds"
+msgstr "5 секунд"
+
+#: Programs/menu_prefs.c:788
+msgid "500 milliseconds"
+msgstr "500 миллисекунд"
+
+#: Programs/menu_prefs.c:615
+msgid "6-Dot Computer Braille"
+msgstr "Шеститочечный компьютерный брайль"
+
+#: Programs/menu_prefs.c:613
+msgid "8-Dot Computer Braille"
+msgstr "восьмиточечный компьютерный брайль"
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr "используется 8-битная кодировка"
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr "выкл."
+
+#: Programs/config.c:1367
+msgid "API Parameter"
+msgstr "Параметр API"
+
+#: Programs/cmds.auto.h:1252
+msgid "AT (set 2) keyboard scan code"
+msgstr "AT (set 2) скан-код клавиатуры"
+
+#: Programs/midi.c:67
+msgid "Accordion"
+msgstr "аккордеон"
+
+#. Bass
+#: Programs/midi.c:80
+msgid "Acoustic Bass"
+msgstr "акустический бас"
+
+#. Piano
+#: Programs/midi.c:44
+msgid "Acoustic Grand Piano"
+msgstr "акустический рояль"
+
+#. Guitar
+#: Programs/midi.c:71
+msgid "Acoustic Guitar (nylon)"
+msgstr "акустическая гитара (нейлон)"
+
+#: Programs/midi.c:72
+msgid "Acoustic Guitar (steel)"
+msgstr "акустическая гитара (сталь)"
+
+#: Programs/menu_prefs.c:1184
+msgid "After Time"
+msgstr "По истечении времени"
+
+#: Programs/midi.c:171
+msgid "Agogo"
+msgstr "Агого"
+
+#: Programs/menu_prefs.c:1374
+msgid "Alert"
+msgstr "уведомление"
+
+#: Programs/menu_prefs.c:993
+msgid "Alert Dots"
+msgstr "уведомление  точек"
+
+#: Programs/menu_prefs.c:998
+msgid "Alert Messages"
+msgstr "уведомление сообщения"
+
+#: Programs/menu_prefs.c:938
+msgid "Alert Tunes"
+msgstr "уведомление тоны/звуки"
+
+#: Programs/menu_prefs.c:753 Programs/menu_prefs.c:1032
+msgid "All"
+msgstr "всё"
+
+#: Programs/menu_prefs.c:468
+msgid "Alphabetic Braille Window Coordinates"
+msgstr "алфавитные координаты окна Брайля"
+
+#: Programs/menu_prefs.c:469
+msgid "Alphabetic Screen Cursor Coordinates"
+msgstr "алфавитные координаты экранного курсора"
+
+#: Programs/midi.c:117
+msgid "Alto Sax"
+msgstr "саксофон-альт"
+
+#: Programs/midi.c:185
+msgid "Applause"
+msgstr "аплодисменты"
+
+#: Programs/cmd_miscellaneous.c:60
+msgid "April"
+msgstr "апрель"
+
+#: Programs/log.c:116
+msgid "Async Events"
+msgstr "асинхронные события"
+
+#: Programs/menu_prefs.c:710
+msgid "Attributes Invisible Time"
+msgstr "атрибуты - скрытое время"
+
+#: Programs/config.c:2812 Programs/menu_prefs.c:1280
+msgid "Attributes Table"
+msgstr "атрибуты таблица"
+
+#: Programs/menu_prefs.c:704
+msgid "Attributes Visible Time"
+msgstr "атрибуты время видимости"
+
+#: Programs/cmd_miscellaneous.c:64
+msgid "August"
+msgstr "август"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr "автоматическая разблокировка"
+
+#: Programs/menu_prefs.c:856
+msgid "Autorelease Time"
+msgstr "время автоматической разблокировки"
+
+#: Programs/menu_prefs.c:873
+msgid "Autorepeat Enabled"
+msgstr "автоповтор включён"
+
+#: Programs/menu_prefs.c:879
+msgid "Autorepeat Interval"
+msgstr "интервал автоповтора"
+
+#: Programs/menu_prefs.c:886
+msgid "Autorepeat Panning"
+msgstr "панорамный автоповтор"
+
+#: Programs/menu_prefs.c:1075
+msgid "Autospeak"
+msgstr "автопроговаривание"
+
+#: Programs/config.c:1812
+msgid "BRLTTY stopped"
+msgstr "BRLTTY остановлено"
+
+#: Programs/midi.c:166
+msgid "Bag Pipe"
+msgstr "волынка"
+
+#: Programs/midi.c:162
+msgid "Banjo"
+msgstr "Банджо"
+
+#: Programs/midi.c:119
+msgid "Baritone Sax"
+msgstr "саксофон-баритон"
+
+#: Programs/midi.c:28
+msgid "Bass"
+msgstr "бас"
+
+#: Programs/midi.c:122
+msgid "Bassoon"
+msgstr "фагот"
+
+#: Programs/menu_prefs.c:945
+msgid "Beeper"
+msgstr "пейджер"
+
+#: Programs/menu_prefs.c:1183
+msgid "Before Time"
+msgstr "до начала"
+
+#: Programs/midi.c:182
+msgid "Bird Tweet"
+msgstr "птичий щебет"
+
+#: Programs/menu_prefs.c:698
+msgid "Blinking Attributes"
+msgstr "атрибуты мигания"
+
+#: Programs/menu_prefs.c:716
+msgid "Blinking Capitals"
+msgstr "мигающие заглавные буквы"
+
+#: Programs/menu_prefs.c:675
+msgid "Blinking Screen Cursor"
+msgstr "мигающий экранный курсор"
+
+#: Programs/menu_prefs.c:1133
+msgid "Blinking Speech Cursor"
+msgstr "мигающий речевой курсор"
+
+#: Programs/menu_prefs.c:576 Programs/menu_prefs.c:1243
+msgid "Block"
+msgstr "заблокировать"
+
+#: Programs/midi.c:129
+msgid "Blown Bottle"
+msgstr "выдувная бутылка"
+
+#: Programs/log.c:140
+msgid "Bluetooth I/O"
+msgstr "Bluetooth вкл/выкл"
+
+#: Programs/config.c:1588
+msgid "Braille Device"
+msgstr "устройство Брайля"
+
+#: Programs/config.c:1584
+msgid "Braille Driver"
+msgstr "драйвер Брайля"
+
+#: Programs/log.c:146
+msgid "Braille Driver Events"
+msgstr "события драйвера Брайля"
+
+#: Programs/menu_prefs.c:653
+msgid "Braille Firmness"
+msgstr "твёрдость точек Брайля"
+
+#: Programs/log.c:80
+msgid "Braille Key Events"
+msgstr "события клавиши ввода Брайля"
+
+#: Programs/config.c:1587
+msgid "Braille Parameter"
+msgstr "Параметр Брайля"
+
+#: Programs/menu_prefs.c:609
+msgid "Braille Presentation"
+msgstr "представление Брайля"
+
+#: Programs/menu_prefs.c:1270
+msgid "Braille Tables"
+msgstr "Таблицы Брайля"
+
+#: Programs/menu_prefs.c:821
+msgid "Braille Typing"
+msgstr "ввод Брайля"
+
+#: Programs/menu_prefs.c:457
+msgid "Braille Window Column"
+msgstr "окно Брайля по вертикали"
+
+#: Programs/menu_prefs.c:456
+msgid "Braille Window Coordinates"
+msgstr "координаты окна Брайля"
+
+#: Programs/menu_prefs.c:774
+msgid "Braille Window Overlap"
+msgstr "окно Брайля поверх других"
+
+#: Programs/menu_prefs.c:458
+msgid "Braille Window Row"
+msgstr "окно Брайля по горизонтали"
+
+#: Programs/config.c:464
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr "код драйвера Брайля (%s, %s, или один из {%s})."
+
+#: Programs/midi.c:31
+msgid "Brass"
+msgstr "духовые инструменты"
+
+#: Programs/midi.c:112
+msgid "Brass Section"
+msgstr "духовые инструменты"
+
+#: Programs/midi.c:180
+msgid "Breath Noise"
+msgstr "звук дыхания"
+
+#: Programs/midi.c:45
+msgid "Bright Acoustic Piano"
+msgstr "мощное акустическое пианино"
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr "Схемы авторизации/аутентификации BrlApi"
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr "хост и/или port для подключения BrlApi"
+
+#: Programs/menu_prefs.c:1326
+msgid "Bug Reports"
+msgstr "отчёты о неполадках"
+
+#: Programs/menu_prefs.c:1307
+msgid "Build Information"
+msgstr "информация о сборке программы"
+
+#: Programs/menu_prefs.c:638
+msgid "Capitalization Mode"
+msgstr "режим заглавных букв"
+
+#: Programs/menu_prefs.c:727
+msgid "Capitals Invisible Time"
+msgstr "заглавные буквы - скрытое время"
+
+#: Programs/menu_prefs.c:721
+msgid "Capitals Visible Time"
+msgstr "заглавные буквы - время видимости"
+
+#: Programs/menu_prefs.c:1397
+msgid "Category Log Level"
+msgstr "категория Уровень ведения журнала об ошибках"
+
+#. Chromatic Percussion
+#: Programs/midi.c:53
+msgid "Celesta"
+msgstr "челеста"
+
+#: Programs/midi.c:91
+msgid "Cello"
+msgstr "виолончель"
+
+#: Programs/midi.c:102
+msgid "Choir Aahs"
+msgstr "хоровые придыхания"
+
+#: Programs/midi.c:25
+msgid "Chromatic Percussion"
+msgstr "хроматическая перкуссия"
+
+#: Programs/midi.c:65
+msgid "Church Organ"
+msgstr "церковный орган"
+
+#: Programs/midi.c:123
+msgid "Clarinet"
+msgstr "кларнет"
+
+#: Programs/midi.c:51
+msgid "Clavi"
+msgstr "клавир"
+
+#: Programs/menu.c:820
+msgid "Close"
+msgstr "закрыть"
+
+#: Programs/menu_prefs.c:1167
+msgid "Colon"
+msgstr "двоеточие"
+
+#: Programs/menu_prefs.c:1331
+msgid "Configuration Directory"
+msgstr "Каталог конфигурации"
+
+#: Programs/config.c:2755 Programs/menu_prefs.c:1336
+msgid "Configuration File"
+msgstr "файл конфигурации"
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr "консольный звуковой сигнал"
+
+#: Programs/menu_prefs.c:924
+msgid "Console Bell Alert"
+msgstr "консольный звуковой сигнал уведомление"
+
+#: Programs/midi.c:92
+msgid "Contrabass"
+msgstr "контрабас"
+
+#: Programs/menu_prefs.c:614
+msgid "Contracted Braille"
+msgstr "Краткопись"
+
+#: Programs/config.c:2819 Programs/menu_prefs.c:1288
+msgid "Contraction Table"
+msgstr "таблица краткописных сокращений"
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr "таблица краткописных сокращений."
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr "верификационная таблица сокращений."
+
+#: Programs/menu_prefs.c:1375
+msgid "Critical"
+msgstr "ключевое"
+
+#: Programs/log.c:98
+msgid "Cursor Routing"
+msgstr "направление курсора"
+
+#: Programs/log.c:92
+msgid "Cursor Tracking"
+msgstr "отслеживание курсора"
+
+#: Programs/menu_prefs.c:793
+msgid "Cursor Tracking Delay"
+msgstr "задержка отслеживания курсора"
+
+#: Programs/menu_prefs.c:1205
+msgid "Dash"
+msgstr "тире"
+
+#: Programs/menu_prefs.c:1198
+msgid "Date Format"
+msgstr "формат данных"
+
+#: Programs/menu_prefs.c:1187
+msgid "Date Position"
+msgstr "Позиционирование  данных"
+
+#: Programs/menu_prefs.c:1210
+msgid "Date Separator"
+msgstr "разделитель данных"
+
+#: Programs/menu_prefs.c:1195
+msgid "Day Month Year"
+msgstr "День Месяц Год"
+
+#: Programs/menu_prefs.c:1380
+msgid "Debug"
+msgstr "устранить неполадки"
+
+#: Programs/cmd_miscellaneous.c:68
+msgid "December"
+msgstr "декабрь"
+
+#: Programs/config.c:485
+msgid "Device for accessing braille display."
+msgstr "устройство доступа к дисплею Брайля"
+
+#: Programs/config.c:444
+msgid "Disable the application programming interface."
+msgstr "отключить програмный интерфейс  приложения"
+
+#: Programs/midi.c:77
+msgid "Distortion Guitar"
+msgstr "дисторшн-гитара"
+
+#: Programs/config.c:587
+msgid "Do not autospeak when braille is not being used."
+msgstr "не проговаривать автоматически, когда Брайль не используется"
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr "не пишите никакого текста на устройство Брайля"
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr "не возвращайтесь к основной кодировке Unicode"
+
+#: Programs/config.c:355
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr "не переключайтесь в режим обычного пользователя и не отказывайтесь от каких бы то ни было преимуществ (совместное использование, дополнительные возможности и Т. Д.)."
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr "готово"
+
+#: Programs/menu_prefs.c:1168 Programs/menu_prefs.c:1207
+msgid "Dot"
+msgstr "точка"
+
+#: Programs/menu_prefs.c:831
+msgid "Dots via Unicode Braille"
+msgstr "точки посредством Unicode Брайля"
+
+#. Organ
+#: Programs/midi.c:62
+msgid "Drawbar Organ"
+msgstr "тяговый орган"
+
+#: Programs/config.c:2776 Programs/menu_prefs.c:1356
+msgid "Drivers Directory"
+msgstr "каталог драйверов"
+
+#: Programs/midi.c:60
+msgid "Dulcimer"
+msgstr "цимбалы"
+
+#: Programs/menu_prefs.c:768
+msgid "Eager Sliding Braille Window"
+msgstr "быстрое скольжение окна Брайля"
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "редактировать таблицу."
+
+#: Programs/midi.c:81
+msgid "Electric Bass (finger)"
+msgstr "электробас (палец)"
+
+#: Programs/midi.c:82
+msgid "Electric Bass (pick)"
+msgstr "электробас (щипок)"
+
+#: Programs/midi.c:46
+msgid "Electric Grand Piano"
+msgstr "электрический рояль"
+
+#: Programs/midi.c:74
+msgid "Electric Guitar (clean)"
+msgstr "электрогитара (чистое звучание)"
+
+#: Programs/midi.c:73
+msgid "Electric Guitar (jazz)"
+msgstr "электрогитара (джаз)"
+
+#: Programs/midi.c:75
+msgid "Electric Guitar (muted)"
+msgstr "электрогитара (приглушённый звук)"
+
+#: Programs/midi.c:48
+msgid "Electric Piano 1"
+msgstr "электрическое пианино 1"
+
+#: Programs/midi.c:49
+msgid "Electric Piano 2"
+msgstr "электрическое пианино 2"
+
+#: Programs/menu_prefs.c:1373
+msgid "Emergency"
+msgstr "сигнализация экстренных служб"
+
+#: Programs/menu_prefs.c:455
+msgid "End"
+msgstr "конец"
+
+#: Programs/menu_prefs.c:754
+msgid "End of Line"
+msgstr "конец строки"
+
+#: Programs/midi.c:121
+msgid "English Horn"
+msgstr "английский рожок"
+
+#: Programs/menu_prefs.c:1067
+msgid "Enqueue"
+msgstr "поставить в очередь"
+
+#: Programs/midi.c:30
+msgid "Ensemble"
+msgstr "ансамбль"
+
+#: Programs/menu_prefs.c:1376
+msgid "Error"
+msgstr "ошибка"
+
+#: Programs/midi.c:37
+msgid "Ethnic Instruments"
+msgstr "народные инструменты"
+
+#: Programs/menu_prefs.c:921
+msgid "Event Alerts"
+msgstr "уведомление о событиях"
+
+#: Programs/menu_prefs.c:626
+msgid "Expand Current Word"
+msgstr "Развернуть текущее слово"
+
+#: Programs/config.c:399
+msgid "Explicit preference settings."
+msgstr "расширенные настройки"
+
+#: Programs/menu_prefs.c:948
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:986
+msgid "FM Volume"
+msgstr "громкость FM"
+
+#. Synth FM
+#: Programs/midi.c:152
+msgid "FX 1 (rain)"
+msgstr "звуковая схема 1 (дождь)"
+
+#: Programs/midi.c:153
+msgid "FX 2 (soundtrack)"
+msgstr "звуковая схема 2 (саундтрек)"
+
+#: Programs/midi.c:154
+msgid "FX 3 (crystal)"
+msgstr "звуковая схема 3 (кристалл)"
+
+#: Programs/midi.c:155
+msgid "FX 4 (atmosphere)"
+msgstr "звуковая схема 4 (атмосфера)"
+
+#: Programs/midi.c:156
+msgid "FX 5 (brightness)"
+msgstr "звуковая схема 5 (яркость)"
+
+#: Programs/midi.c:157
+msgid "FX 6 (goblins)"
+msgstr "звуковая схема 6 (гоблины)"
+
+#: Programs/midi.c:158
+msgid "FX 7 (echoes)"
+msgstr "звуковая схема 7 (множественное эхо)"
+
+#: Programs/midi.c:159
+msgid "FX 8 (sci-fi)"
+msgstr "звуковая схема 8 (научная фантастика)"
+
+#: Programs/cmd_miscellaneous.c:58
+msgid "February"
+msgstr "февраль"
+
+#: Programs/midi.c:167
+msgid "Fiddle"
+msgstr "скрипка"
+
+#: Programs/midi.c:126
+msgid "Flute"
+msgstr "флейта"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr "запросить немедленный вывод"
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr "формат каждой выводимой строки"
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr "формат входного файла"
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr "формат выходного файла"
+
+#: Programs/midi.c:111
+msgid "French Horn"
+msgstr "французский рожок"
+
+#: Programs/midi.c:83
+msgid "Fretless Bass"
+msgstr "безладовая бас-гитара"
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr "Приостановлено"
+
+#: Programs/menu_prefs.c:470
+msgid "Generic"
+msgstr "основное"
+
+#: Programs/log.c:62
+msgid "Generic Input"
+msgstr "основной ввод"
+
+#: Programs/midi.c:54
+msgid "Glockenspiel"
+msgstr "куранты"
+
+#: Programs/midi.c:27
+msgid "Guitar"
+msgstr "гитара"
+
+#. Sound Effects
+#: Programs/midi.c:179
+msgid "Guitar Fret Noise"
+msgstr "гитарные переборы"
+
+#: Programs/midi.c:78
+msgid "Guitar Harmonics"
+msgstr "гитарная гармоника"
+
+#: Programs/midi.c:186
+msgid "Gunshot"
+msgstr "пушечный выстрел"
+
+#: Programs/midi.c:68
+msgid "Harmonica"
+msgstr "гармоника"
+
+#: Programs/midi.c:50
+msgid "Harpsichord"
+msgstr "клавесин"
+
+#: Programs/midi.c:184
+msgid "Helicopter"
+msgstr "вертолёт"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "экран справки"
+
+#: Programs/menu_prefs.c:649 Programs/menu_prefs.c:902
+msgid "High"
+msgstr "высоко"
+
+#: Programs/menu_prefs.c:810
+msgid "Highlight Braille Window Location"
+msgstr "выделить расположение окна Брайля"
+
+#: Programs/midi.c:47
+msgid "Honkytonk Piano"
+msgstr "расстроенное пианино"
+
+#: Programs/menu_prefs.c:1066
+msgid "Immediate"
+msgstr "немедленно"
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr "Несовместимая библиотека XKB\n"
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr "Несовместимая поддержка сервера XKB\n"
+
+#: Programs/menu_prefs.c:1379
+msgid "Information"
+msgstr "информация"
+
+#: Programs/menu_prefs.c:845
+msgid "Input Options"
+msgstr "опции ввода"
+
+#: Programs/log.c:68
+msgid "Input Packets"
+msgstr "пакетный ввод"
+
+#: Programs/config.c:330
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr "Установить сервис %s и выйти."
+
+#: Programs/menu_prefs.c:1383
+msgid "Internal Parameters"
+msgstr "внутренние параметры"
+
+#: Programs/cmd_miscellaneous.c:57
+msgid "January"
+msgstr "январь"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr "класс Java не найден"
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr "произошло исключение Java"
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr "метод Java не найден"
+
+#: Programs/cmd_miscellaneous.c:63
+msgid "July"
+msgstr "июль"
+
+#: Programs/cmd_miscellaneous.c:62
+msgid "June"
+msgstr "июнь"
+
+#: Programs/midi.c:165
+msgid "Kalimba"
+msgstr "калимба"
+
+#: Programs/config.c:1510
+msgid "Key Bindings"
+msgstr "привязки клавиш"
+
+#: Programs/config.c:1054
+msgid "Key Help"
+msgstr "справка по клавишам"
+
+#: Programs/config.c:1515 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr "таблица клавиш"
+
+#: Programs/menu_prefs.c:824
+msgid "Keyboard Enabled"
+msgstr "клавиатура включена"
+
+#: Programs/log.c:86
+msgid "Keyboard Key Events"
+msgstr "события клавиатурных команд"
+
+#: Programs/menu_prefs.c:931
+msgid "Keyboard LED Alerts"
+msgstr "световые клавиатурные уведомления"
+
+#: Programs/config.c:2826 Programs/menu_prefs.c:913
+msgid "Keyboard Table"
+msgstr "клавиатурная таблица"
+
+#: Programs/midi.c:164
+msgid "Koto"
+msgstr "кото"
+
+#: Programs/config.c:2958
+msgid "Language"
+msgstr "язык"
+
+#. Synth Lead
+#: Programs/midi.c:134
+msgid "Lead 1 (square)"
+msgstr "волна 1 (квадратная)"
+
+#: Programs/midi.c:135
+msgid "Lead 2 (sawtooth)"
+msgstr "волна 2 (пилообразная)"
+
+#: Programs/midi.c:136
+msgid "Lead 3 (calliope)"
+msgstr "волна 3 (каллиопа)"
+
+#: Programs/midi.c:137
+msgid "Lead 4 (chiff)"
+msgstr "волна 4 (основная)"
+
+#: Programs/midi.c:138
+msgid "Lead 5 (charang)"
+msgstr "волна 5 (чаран)"
+
+#: Programs/midi.c:139
+msgid "Lead 6 (voice)"
+msgstr "волна 6 (голос)"
+
+#: Programs/midi.c:140
+msgid "Lead 7 (fifths)"
+msgstr "волна 7 (пятая)"
+
+#: Programs/midi.c:141
+msgid "Lead 8 (bass + lead)"
+msgstr "волна 8 (бас + свинец)"
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "режим обучения"
+
+#: Programs/menu_prefs.c:1222
+msgid "Left"
+msgstr "лево"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr "вывести названия клавиш в списке."
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr "вывести таблицу клавиш списком в формате справочного окна."
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr "вывести таблицу клавиш списком в формате текста с изменённой структурой."
+
+#: Programs/menu_prefs.c:616
+msgid "Literary Braille"
+msgstr "литературный Брайль"
+
+#: Programs/menu_prefs.c:1366
+msgid "Locale Directory"
+msgstr "локальный каталог"
+
+#: Programs/menu_prefs.c:1402
+msgid "Log Categories"
+msgstr "категории журнала"
+
+#: Programs/config.c:824
+msgid "Log Level"
+msgstr "уровень ведения журнала"
+
+#: Programs/menu_prefs.c:1463
+msgid "Log Messages"
+msgstr "сообщения журнала"
+
+#: Programs/config.c:677
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr "сохранить в журнале версии процессора, API и встроенных драйверов, а затем выйти."
+
+#: Programs/config.c:642
+msgid "Log to standard error rather than to the system log."
+msgstr "предпочитать журнал стандартных ошибок системному журналу"
+
+#: Programs/config.c:656
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)"
+msgstr "уровень ведения журнала (%s или один из {%s}) и/или категории журнала для предоставления доступа (к любым сочетаниям {%s}, каждое из которых опционально предваряется %s для отключения)"
+
+#: Programs/menu_prefs.c:867
+msgid "Long Press Time"
+msgstr "время долгого нажатия"
+
+#: Programs/menu_prefs.c:647 Programs/menu_prefs.c:900
+msgid "Low"
+msgstr "низко"
+
+#: Programs/menu_prefs.c:577
+msgid "Lower Left Dot"
+msgstr "понизить левую точку"
+
+#: Programs/menu_prefs.c:578
+msgid "Lower Right Dot"
+msgstr "понизить правую точку"
+
+#: Programs/menu_prefs.c:947
+msgid "MIDI"
+msgstr "MIDI"
+
+#: Programs/config.c:626
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr "спецификатор устройств MIDI (Musical Instrument Digital Interface)."
+
+#: Programs/menu_prefs.c:977
+msgid "MIDI Instrument"
+msgstr "инструмент MIDI"
+
+#: Programs/menu_prefs.c:967
+msgid "MIDI Volume"
+msgstr "громкость MIDI"
+
+#: Programs/cmd_miscellaneous.c:59
+msgid "March"
+msgstr "март"
+
+#: Programs/midi.c:57
+msgid "Marimba"
+msgstr "Маримба"
+
+#: Programs/menu_prefs.c:650 Programs/menu_prefs.c:903
+msgid "Maximum"
+msgstr "максимальное"
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr "максимальная длина выводимой строки."
+
+#: Programs/cmd_miscellaneous.c:61
+msgid "May"
+msgstr "май"
+
+#: Programs/menu_prefs.c:648 Programs/menu_prefs.c:901
+msgid "Medium"
+msgstr "среднее"
+
+#: Programs/midi.c:175
+msgid "Melodic Tom"
+msgstr "мелодичный Том"
+
+#: Programs/menu_prefs.c:590
+msgid "Menu Options"
+msgstr "меню опций"
+
+#: Programs/config.c:635
+msgid "Message hold timeout (in 10ms units)."
+msgstr "сообщение время задержки (шаг в 10ms)."
+
+#: Programs/menu_prefs.c:646 Programs/menu_prefs.c:899
+msgid "Minimum"
+msgstr "минимальное"
+
+#: Programs/menu_prefs.c:1194
+msgid "Month Day Year"
+msgstr "месяц день год"
+
+#: Programs/midi.c:55
+msgid "Music Box"
+msgstr "музыкальная шкатулка"
+
+#: Programs/menu_prefs.c:947
+msgid "Musical Instrument Digital Interface"
+msgstr "цифровой интерфейс музыкальных инструментов"
+
+#: Programs/midi.c:110
+msgid "Muted Trumpet"
+msgstr "приглушённая труба"
+
+#: Programs/config.c:526
+msgid "Name of or path to attributes table."
+msgstr "имя или путь к таблице атрибутов."
+
+#: Programs/config.c:535
+msgid "Name of or path to contraction table."
+msgstr "имя или путь к таблице сокращений."
+
+#: Programs/config.c:391
+msgid "Name of or path to default preferences file."
+msgstr "имя или путь к файлу настроек по умолчанию."
+
+#: Programs/config.c:544
+msgid "Name of or path to keyboard table."
+msgstr "имя или путь к клавиатурной таблице."
+
+#: Programs/config.c:580
+msgid "Name of or path to speech input object."
+msgstr "имя или путь к объекту речевого ввода"
+
+#: Programs/config.c:517
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr "имя или путь к таблице текстов (или %s)."
+
+#: Programs/menu_prefs.c:734
+msgid "Navigation Options"
+msgstr "опции навигации"
+
+#: Programs/menu.c:449
+msgid "No"
+msgstr "нет"
+
+#: Programs/menu_prefs.c:633
+msgid "No Capitalization"
+msgstr "все буквы строчные"
+
+#: Programs/menu_prefs.c:786 Programs/menu_prefs.c:1030
+#: Programs/menu_prefs.c:1043 Programs/menu_prefs.c:1056
+#: Programs/menu_prefs.c:1182 Programs/menu_prefs.c:1221
+#: Programs/menu_prefs.c:1241
+msgid "None"
+msgstr "никакие"
+
+#: Programs/menu_prefs.c:1378
+msgid "Notice"
+msgstr "заметка"
+
+#: Programs/cmd_miscellaneous.c:67
+msgid "November"
+msgstr "ноябрь"
+
+#: Programs/midi.c:120
+msgid "Oboe"
+msgstr "гобой"
+
+#: Programs/midi.c:132
+msgid "Ocarina"
+msgstr "окарина"
+
+#: Programs/cmd_miscellaneous.c:66
+msgid "October"
+msgstr "октябрь"
+
+#: Programs/menu_prefs.c:849
+msgid "Off"
+msgstr "выкл."
+
+#: Programs/config.c:1601
+msgid "Old Preferences File"
+msgstr "файл старых настроек"
+
+#: Programs/menu_prefs.c:862
+msgid "On First Release"
+msgstr "при первом запуске"
+
+#: Programs/midi.c:105
+msgid "Orchestra Hit"
+msgstr "оркестровый хит"
+
+#: Programs/midi.c:95
+msgid "Orchestral Harp"
+msgstr "оркестровая арфа"
+
+#: Programs/midi.c:26
+msgid "Organ"
+msgstr "орган"
+
+#: Programs/log.c:74
+msgid "Output Packets"
+msgstr "пакетный выход"
+
+#: Programs/midi.c:76
+msgid "Overdriven Guitar"
+msgstr "перегруженная гитара"
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr "режим ПК"
+
+#: Programs/menu_prefs.c:946
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:616
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr "PCM спецификатор устройства (звуковая карта)."
+
+#: Programs/menu_prefs.c:959
+msgid "PCM Volume"
+msgstr "громкость PCM"
+
+#: Programs/cmds.auto.h:1266
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr "PS/2 (set 3) клавиатурный скан-код"
+
+#: Programs/menu_prefs.c:1316
+msgid "Package Revision"
+msgstr "ревизия сборки"
+
+#: Programs/menu_prefs.c:1311
+msgid "Package Version"
+msgstr "версия сборки"
+
+#. Synth Pad
+#: Programs/midi.c:143
+msgid "Pad 1 (new age)"
+msgstr "заслонка 1 (нью-эйдж)"
+
+#: Programs/midi.c:144
+msgid "Pad 2 (warm)"
+msgstr "заслонка 2 (тёплый)"
+
+#: Programs/midi.c:145
+msgid "Pad 3 (polysynth)"
+msgstr "заслонка 3 (многотембровый глухой звук)"
+
+#: Programs/midi.c:146
+msgid "Pad 4 (choir)"
+msgstr "заслонка 4 (хор)"
+
+#: Programs/midi.c:147
+msgid "Pad 5 (bowed)"
+msgstr "заслонка 5 (кривизна)"
+
+#: Programs/midi.c:148
+msgid "Pad 6 (metallic)"
+msgstr "заслонка 6 (металлик)"
+
+#: Programs/midi.c:149
+msgid "Pad 7 (halo)"
+msgstr "заслонка 7 (ореол)"
+
+#: Programs/midi.c:150
+msgid "Pad 8 (sweep)"
+msgstr "заслонка 8 (метла)"
+
+#: Programs/midi.c:128
+msgid "Pan Flute"
+msgstr "пан флейта"
+
+#: Programs/config.c:453
+msgid "Parameters for the application programming interface."
+msgstr "параметры для программирования интерфейса приложения."
+
+#: Programs/config.c:475
+msgid "Parameters for the braille driver."
+msgstr "параметры драйвера Брайля."
+
+#: Programs/config.c:348
+msgid "Parameters for the privilege establishment stage."
+msgstr "параметры для преимущественной установки"
+
+#: Programs/config.c:607
+msgid "Parameters for the screen driver."
+msgstr "параметры для экранного драйвера"
+
+#: Programs/config.c:572
+msgid "Parameters for the speech driver."
+msgstr "параметры для речевого драйвера"
+
+#: Programs/config.c:382
+msgid "Path to default settings file."
+msgstr "путь к файлу настроек по умолчанию"
+
+#: Programs/config.c:436
+msgid "Path to directory containing drivers."
+msgstr "путь к каталогу с драйверами"
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:507
+msgid "Path to directory containing tables."
+msgstr "путь к каталогу с таблицами"
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr "путь к каталогу с таблицами текстов"
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr "путь к каталогу загрузки драйверов"
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr "путь к каталогу загрузки таблиц текстов"
+
+#: Programs/brltest.c:78 Programs/config.c:426
+msgid "Path to directory which can be written to."
+msgstr "путь к каталогу для адресации."
+
+#: Programs/config.c:416
+msgid "Path to directory which contains files that can be updated."
+msgstr "путь к каталогу с файлами, доступными для обновления."
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr "путь к таблице ввода текста."
+
+#: Programs/config.c:665
+msgid "Path to log file."
+msgstr "путь к файлу журнала."
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr "путь к таблице вывода текста."
+
+#: Programs/config.c:372
+msgid "Path to process identifier file."
+msgstr "путь к файлу идентификации процесса."
+
+#: Programs/config.c:406
+msgid "Patterns that match command prompts."
+msgstr "образцы, подходящие  для подсказок."
+
+#: Programs/midi.c:38
+msgid "Percussive Instruments"
+msgstr "перкуссионные инструменты"
+
+#: Programs/midi.c:63
+msgid "Percussive Organ"
+msgstr "перкуссионный орган"
+
+#: Programs/midi.c:24
+msgid "Piano"
+msgstr "пианино"
+
+#. Pipe
+#: Programs/midi.c:125
+msgid "Piccolo"
+msgstr "маленькая флейта пикколо"
+
+#: Programs/midi.c:33
+msgid "Pipe"
+msgstr "труба"
+
+#: Programs/midi.c:94
+msgid "Pizzicato Strings"
+msgstr "защемлённые струны"
+
+#: Drivers/Braille/Baum/braille.c:1119
+msgid "Powerdown"
+msgstr "выключить"
+
+#: Programs/config.c:2756 Programs/menu_prefs.c:1346
+msgid "Preferences File"
+msgstr "файл настроек"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr "меню настроек"
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr "Распечатать отчёт по использованию (всех опций) и выйти."
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr "Распечатать отчёт по использованию (только  часто   применяемых опций) и выйти."
+
+#: Programs/config.c:779
+msgid "Privilege Parameter"
+msgstr "параметр преимущественного использования"
+
+#: Programs/menu_prefs.c:1297
+msgid "Profiles"
+msgstr "профили"
+
+#: Programs/config.c:552
+msgid "Properties of eligible keyboards."
+msgstr "свойства допустимых к использованию клавиатур."
+
+#: Programs/menu_prefs.c:839
+msgid "Quick Space"
+msgstr "быстрый пробел"
+
+#: Programs/menu_prefs.c:1047
+msgid "Raise Pitch"
+msgstr "повысить тон"
+
+#: Programs/config.c:316
+msgid "Recognize environment variables."
+msgstr "распознать переменные  системной среды"
+
+#: Programs/midi.c:127
+msgid "Recorder"
+msgstr "диктофон"
+
+#: Programs/midi.c:32
+msgid "Reed"
+msgstr "свирель"
+
+#: Programs/midi.c:66
+msgid "Reed Organ"
+msgstr "Язычковый орган"
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr "переформатировать ввод"
+
+#: Programs/config.c:497
+msgid "Release braille device when screen or window is unreadable."
+msgstr "запустить устройство Брайля, если экран или окно не читается."
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr "Процесс остаётся в приоретете"
+
+#: Programs/config.c:323
+msgid "Remain a foreground process."
+msgstr "Процесс остаётся в приоретете."
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr "убрать точки семь и восемь."
+
+#: Programs/config.c:338
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr "Убрать сервис %s и выйти."
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr "Сообщить о проблемах с таблицей клавиш."
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr "Сообщить о символах, неопределяемых в данной таблице текстов текущего экранного шрифта."
+
+#: Programs/menu_prefs.c:755
+msgid "Rest of Line"
+msgstr "остаток строки"
+
+#: Programs/menu_prefs.c:1445
+msgid "Restart Braille Driver"
+msgstr "перезапустить драйвер Брайля"
+
+#: Programs/menu_prefs.c:1457
+msgid "Restart Screen Driver"
+msgstr "Перезапустить экранный драйвер"
+
+#: Programs/menu_prefs.c:1451
+msgid "Restart Speech Driver"
+msgstr "Перезапустить речевой драйвер"
+
+#: Programs/midi.c:177
+msgid "Reverse Cymbal"
+msgstr "тарелка реверс"
+
+#: Programs/menu_prefs.c:1223
+msgid "Right"
+msgstr "право"
+
+#: Programs/midi.c:64
+msgid "Rock Organ"
+msgstr "Рок-орган"
+
+#: Programs/menu_prefs.c:585
+msgid "Save on Exit"
+msgstr "Сохранить при выходе"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1046
+msgid "Say Cap"
+msgstr "Проговаривать заглавную букву"
+
+#: Programs/menu_prefs.c:1070
+msgid "Say Line Mode"
+msgstr "проговаривать режим строки"
+
+#: Programs/menu_prefs.c:1057
+msgid "Say Space"
+msgstr "проговаривать пробел"
+
+#: Programs/menu_prefs.c:460
+msgid "Screen Cursor Column"
+msgstr "Вертикальный экранный курсор"
+
+#: Programs/menu_prefs.c:459
+msgid "Screen Cursor Coordinates"
+msgstr "Координаты экранного курсора"
+
+#: Programs/menu_prefs.c:687
+msgid "Screen Cursor Invisible Time"
+msgstr "время скрытия экранного курсора"
+
+#: Programs/menu_prefs.c:461
+msgid "Screen Cursor Row"
+msgstr "Горизонтальный экранный курсор"
+
+#: Programs/menu_prefs.c:669
+msgid "Screen Cursor Style"
+msgstr "Стиль экранного курсора"
+
+#: Programs/menu_prefs.c:681
+msgid "Screen Cursor Visible Time"
+msgstr "время видимости экранного курсора"
+
+#: Programs/menu_prefs.c:462
+msgid "Screen Cursor and Braille Window Column"
+msgstr "экранный курсор и окно Брайля по вертикали"
+
+#: Programs/menu_prefs.c:463
+msgid "Screen Cursor and Braille Window Row"
+msgstr "Экранный курсор и окно Брайля по горизонтали"
+
+#: Programs/config.c:2253
+msgid "Screen Driver"
+msgstr "Экранный драйвер"
+
+#: Programs/log.c:158
+msgid "Screen Driver Events"
+msgstr "События экранного драйвера"
+
+#: Programs/menu_prefs.c:464
+msgid "Screen Number"
+msgstr "Номер экрана"
+
+#: Programs/config.c:2259
+msgid "Screen Parameter"
+msgstr "Параметр экрана"
+
+#: Programs/config.c:597
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr "Код экранного драйвера (%s, %s, или один из {%s})."
+
+#: Programs/menu_prefs.c:780
+msgid "Scroll-aware Cursor Navigation"
+msgstr "Курсорная навигация с учётом прокрутки"
+
+#: Programs/midi.c:181
+msgid "Seashore"
+msgstr "Морское побережье"
+
+#: Programs/cmd_miscellaneous.c:65
+msgid "September"
+msgstr "Сентябрь"
+
+#: Programs/log.c:128
+msgid "Serial I/O"
+msgstr "Серийно вкл/выкл"
+
+#: Programs/log.c:122
+msgid "Server Events"
+msgstr "События сервера"
+
+#: Programs/midi.c:130
+msgid "Shakuhachi"
+msgstr "сякухати"
+
+#: Programs/midi.c:163
+msgid "Shamisen"
+msgstr "сямисен"
+
+#: Programs/midi.c:168
+msgid "Shanai"
+msgstr "санаи"
+
+#: Programs/menu_prefs.c:598
+msgid "Show Advanced Submenus"
+msgstr "Показать дополнительные подменю"
+
+#: Programs/menu_prefs.c:603
+msgid "Show All Items"
+msgstr "Показать все элементы"
+
+#: Programs/menu_prefs.c:693
+msgid "Show Attributes"
+msgstr "Показать атрибуты"
+
+#: Programs/menu_prefs.c:664
+msgid "Show Screen Cursor"
+msgstr "Показать экранный курсор"
+
+#: Programs/menu_prefs.c:1176
+msgid "Show Seconds"
+msgstr "Показать секунды"
+
+#: Programs/menu_prefs.c:1122
+msgid "Show Speech Cursor"
+msgstr "Показать речевой курсор"
+
+#: Programs/menu_prefs.c:593
+msgid "Show Submenu Sizes"
+msgstr "Показать масштабы подменю"
+
+#. Ethnic Instruments
+#: Programs/midi.c:161
+msgid "Sitar"
+msgstr "ситар"
+
+#: Programs/menu_prefs.c:747
+msgid "Skip Blank Braille Windows"
+msgstr "Пропустить пустые окна Брайля"
+
+#: Programs/menu_prefs.c:741
+msgid "Skip Identical Lines"
+msgstr "Пропустить одинаковые строки"
+
+#: Programs/menu_prefs.c:758
+msgid "Skip Which Blank Braille Windows"
+msgstr "Пропустить пустые окна Брайля"
+
+#: Programs/midi.c:84
+msgid "Slap Bass 1"
+msgstr "слэп-бас 1"
+
+#: Programs/midi.c:85
+msgid "Slap Bass 2"
+msgstr "слэп-бас 2"
+
+#: Programs/menu_prefs.c:1206
+msgid "Slash"
+msgstr "косая черта"
+
+#: Programs/menu_prefs.c:763
+msgid "Sliding Braille Window"
+msgstr "скользящее окно Брайля"
+
+#: Programs/menu_prefs.c:1031
+msgid "Some"
+msgstr "некоторые"
+
+#. Reed
+#: Programs/midi.c:116
+msgid "Soprano Sax"
+msgstr "саксофон-сопрано"
+
+#: Programs/midi.c:39
+msgid "Sound Effects"
+msgstr "звуковые эффекты"
+
+#: Programs/menu_prefs.c:1242
+msgid "Space"
+msgstr "Пробел"
+
+#: Programs/menu_prefs.c:1110
+msgid "Speak Completed Words"
+msgstr "Проговаривать законченные слова"
+
+#: Programs/menu_prefs.c:1098
+msgid "Speak Deleted Characters"
+msgstr "Проговаривать удалённые символы"
+
+#: Programs/menu_prefs.c:1092
+msgid "Speak Inserted Characters"
+msgstr "Проговаривать вставленные символы"
+
+#: Programs/menu_prefs.c:1116
+msgid "Speak Line Indent"
+msgstr "Проговаривать отступ"
+
+#: Programs/menu_prefs.c:1104
+msgid "Speak Replaced Characters"
+msgstr "проговаривать заменённые символы"
+
+#: Programs/menu_prefs.c:1086
+msgid "Speak Selected Character"
+msgstr "проговаривать выбранный символ"
+
+#: Programs/menu_prefs.c:1080
+msgid "Speak Selected Line"
+msgstr "проговаривать выбранную строку"
+
+#: Programs/menu_prefs.c:1145
+msgid "Speech Cursor Invisible Time"
+msgstr "время скрытия речевого курсора"
+
+#: Programs/menu_prefs.c:1127
+msgid "Speech Cursor Style"
+msgstr "стиль речевого курсора"
+
+#: Programs/menu_prefs.c:1139
+msgid "Speech Cursor Visible Time"
+msgstr "время видимости речевого курсора"
+
+#: Programs/config.c:2028
+msgid "Speech Driver"
+msgstr "речевой драйвер"
+
+#: Programs/log.c:152
+msgid "Speech Driver Events"
+msgstr "события речевого драйвера"
+
+#: Programs/log.c:110
+msgid "Speech Events"
+msgstr "речевые события"
+
+#. Create the file system object for speech input.
+#: Programs/config.c:2867
+msgid "Speech Input"
+msgstr "речевой ввод"
+
+#: Programs/menu_prefs.c:1005
+msgid "Speech Options"
+msgstr "свойства речи"
+
+#: Programs/config.c:2031
+msgid "Speech Parameter"
+msgstr "параметр речи"
+
+#: Programs/menu_prefs.c:1022
+msgid "Speech Pitch"
+msgstr "высота тона"
+
+#: Programs/menu_prefs.c:1035
+msgid "Speech Punctuation"
+msgstr "Речевая пунктуация"
+
+#: Programs/menu_prefs.c:1015
+msgid "Speech Rate"
+msgstr "Скорость речи"
+
+#: Programs/menu_prefs.c:1050
+msgid "Speech Uppercase Indicator"
+msgstr "речевой индикатор заглавных букв"
+
+#: Programs/menu_prefs.c:1008
+msgid "Speech Volume"
+msgstr "Громкость речи"
+
+#: Programs/menu_prefs.c:1060
+msgid "Speech Whitespace Indicator"
+msgstr "Речевой индикатор пробела"
+
+#: Programs/config.c:562
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr "Код речевого драйвера (%s, %s, или один из {%s})."
+
+#: Programs/menu_prefs.c:1392
+msgid "Standard Error Log Level"
+msgstr "Стандартный способ  ведения журнала  ошибок"
+
+#: Programs/menu_prefs.c:815
+msgid "Start Selection with Routing Key"
+msgstr "начать выделение, используя  клавишу маршрутизации"
+
+#: Programs/menu_prefs.c:465
+msgid "State Dots"
+msgstr "зафиксировать точки"
+
+#: Programs/menu_prefs.c:466
+msgid "State Letter"
+msgstr "зафиксировать букву"
+
+#: Programs/menu_prefs.c:1217
+msgid "Status Cells"
+msgstr "ячейки с информацией о статусе"
+
+#: Programs/menu_prefs.c:1233
+msgid "Status Count"
+msgstr "счётчик статуса"
+
+#: Programs/menu_prefs.c:474
+msgid "Status Field"
+msgstr "область статуса"
+
+#: Programs/menu_prefs.c:1226
+msgid "Status Position"
+msgstr "положение статуса"
+
+#: Programs/menu_prefs.c:1248
+msgid "Status Separator"
+msgstr "разделитель статуса"
+
+#: Programs/menu_prefs.c:1244
+msgid "Status Side"
+msgstr "граница строки статуса"
+
+#: Programs/midi.c:172
+msgid "Steel Drums"
+msgstr "стальные барабаны"
+
+#: Programs/config.c:362
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr "Остановить текущее действие по %s и выйти."
+
+#. Ensemble
+#: Programs/midi.c:98
+msgid "String Ensemble 1"
+msgstr "Струнный ансамбль 1"
+
+#: Programs/midi.c:99
+msgid "String Ensemble 2"
+msgstr "Струнный ансамбль 2"
+
+#: Programs/midi.c:29
+msgid "Strings"
+msgstr "Струнные инструменты"
+
+#: Programs/menu_prefs.c:635
+msgid "Superimpose Dot 7"
+msgstr "Наложить точку 7 поверх"
+
+#: Programs/config.c:648
+msgid "Suppress start-up messages."
+msgstr "Сократить стартовые сообщения"
+
+#: Programs/midi.c:86
+msgid "Synth Bass 1"
+msgstr "синтезаторный бас 1"
+
+#: Programs/midi.c:87
+msgid "Synth Bass 2"
+msgstr "синтезаторный бас 2"
+
+#: Programs/midi.c:176
+msgid "Synth Drum"
+msgstr "синтезаторный барабан"
+
+#: Programs/midi.c:36
+msgid "Synth FM"
+msgstr "синтезатор FM"
+
+#: Programs/midi.c:34
+msgid "Synth Lead"
+msgstr "синтезаторный свинец"
+
+#: Programs/midi.c:35
+msgid "Synth Pad"
+msgstr "синтезаторная заслонка"
+
+#: Programs/midi.c:104
+msgid "Synth Voice"
+msgstr "синтезированный голос"
+
+#: Programs/midi.c:113
+msgid "SynthBrass 1"
+msgstr "синтезаторные духовые инструменты 1"
+
+#: Programs/midi.c:114
+msgid "SynthBrass 2"
+msgstr "синтезаторные духовые инструменты 2"
+
+#: Programs/midi.c:100
+msgid "SynthStrings 1"
+msgstr "синтезаторные струнные инструменты 1"
+
+#: Programs/midi.c:101
+msgid "SynthStrings 2"
+msgstr "синтезаторные струнные инструменты 2"
+
+#: Programs/menu_prefs.c:1387
+msgid "System Log Level"
+msgstr "уровень ведения системного журнала"
+
+#: Programs/config.c:2777 Programs/menu_prefs.c:1361
+msgid "Tables Directory"
+msgstr "каталог таблиц"
+
+#: Programs/midi.c:174
+msgid "Taiko Drum"
+msgstr "барабан тайко"
+
+#: Programs/midi.c:69
+msgid "Tango Accordion"
+msgstr "танго-аккордеон"
+
+#: Programs/midi.c:183
+msgid "Telephone Ring"
+msgstr "телефонный звонок"
+
+#: Programs/midi.c:118
+msgid "Tenor Sax"
+msgstr "саксофон-тенор"
+
+#: Programs/menu_prefs.c:661
+msgid "Text Indicators"
+msgstr "индикаторы текста"
+
+#: Programs/menu_prefs.c:1245
+msgid "Text Side"
+msgstr "строка текста"
+
+#: Programs/menu_prefs.c:619
+msgid "Text Style"
+msgstr "стиль текста"
+
+#: Programs/config.c:2798 Programs/menu_prefs.c:1273
+msgid "Text Table"
+msgstr "таблица текстов"
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr "таблица текстов."
+
+#: Programs/config.c:302
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr "текст, выводимый при запуске драйвера Брайля и произносимый при запуске речевого драйвера."
+
+#: Programs/config.c:309
+msgid "The text to be shown when the braille driver stops."
+msgstr "текст, выводимый при остановке драйвера Брайля."
+
+#: Programs/menu_prefs.c:467
+msgid "Time"
+msgstr "время"
+
+#: Programs/menu_prefs.c:1161
+msgid "Time Format"
+msgstr "формат времени"
+
+#: Programs/menu_prefs.c:1153
+msgid "Time Presentation"
+msgstr "представление времени"
+
+#: Programs/menu_prefs.c:1171
+msgid "Time Separator"
+msgstr "разделитель времени"
+
+#: Programs/midi.c:96
+msgid "Timpani"
+msgstr "литавры"
+
+#. Percussive Instruments
+#: Programs/midi.c:170
+msgid "Tinkle Bell"
+msgstr "звонкий колокольчик"
+
+#: Programs/menu_prefs.c:1441
+msgid "Tools"
+msgstr "инструменты"
+
+#: Programs/menu_prefs.c:892
+msgid "Touch Navigation"
+msgstr "Жестовая навигация "
+
+#: Programs/menu_prefs.c:906
+msgid "Touch Sensitivity"
+msgstr "чувствительность к выполнению жестов"
+
+#: Programs/menu_prefs.c:804
+msgid "Track Screen Pointer"
+msgstr "отслеживать экранный указатель"
+
+#: Programs/menu_prefs.c:798
+msgid "Track Screen Scroll"
+msgstr "отслеживать прокрутку экрана"
+
+#: Programs/menu_prefs.c:830
+msgid "Translated via Text Table"
+msgstr "переведено посредством таблицы текстов"
+
+#: Programs/midi.c:93
+msgid "Tremolo Strings"
+msgstr "дрожание струн"
+
+#: Programs/midi.c:108
+msgid "Trombone"
+msgstr "тромбон"
+
+#. Brass
+#: Programs/midi.c:107
+msgid "Trumpet"
+msgstr "труба"
+
+#: Programs/midi.c:109
+msgid "Tuba"
+msgstr "туба"
+
+#: Programs/midi.c:59
+msgid "Tubular Bells"
+msgstr "трубчатые колокола"
+
+#: Programs/menu_prefs.c:951
+msgid "Tune Device"
+msgstr "настроить устройство"
+
+#: Programs/menu_prefs.c:834
+msgid "Typing Mode"
+msgstr "режим набора"
+
+#: Programs/log.c:134
+msgid "USB I/O"
+msgstr "USB вкл/выкл"
+
+#: Programs/menu_prefs.c:575
+msgid "Underline"
+msgstr "подчёркнутый"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr "возобновлено"
+
+#: Programs/config.c:2774 Programs/menu_prefs.c:1341
+msgid "Updatable Directory"
+msgstr "Обновляемый каталог"
+
+#: Programs/log.c:104
+msgid "Update Events"
+msgstr "Обновить события"
+
+#: Programs/options.c:151
+msgid "Usage"
+msgstr "Использование"
+
+#: Programs/menu_prefs.c:634
+msgid "Use Capital Sign"
+msgstr "использовать знак заглавной буквы"
+
+#: Programs/midi.c:56
+msgid "Vibraphone"
+msgstr "Вибрафон"
+
+#: Programs/midi.c:90
+msgid "Viola"
+msgstr "виола"
+
+#. Strings
+#: Programs/midi.c:89
+msgid "Violin"
+msgstr "скрипка"
+
+#: Programs/midi.c:103
+msgid "Voice Oohs"
+msgstr "звуки \"ох!\""
+
+#: Programs/menu_prefs.c:1377
+msgid "Warning"
+msgstr "предупреждение"
+
+#: Programs/menu_prefs.c:1321
+msgid "Web Site"
+msgstr "вебсайт"
+
+#: Programs/midi.c:131
+msgid "Whistle"
+msgstr "свист"
+
+#: Programs/midi.c:173
+msgid "Woodblock"
+msgstr "колода"
+
+#: Programs/menu_prefs.c:737
+msgid "Word Wrap"
+msgstr "перенос слов"
+
+#: Programs/config.c:2748
+msgid "Working Directory"
+msgstr "активный каталог"
+
+#: Programs/config.c:2775 Programs/menu_prefs.c:1351
+msgid "Writable Directory"
+msgstr "каталог с доступом для записи"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr "Записать результат отладки в stdout"
+
+#: Programs/config.c:671
+msgid "Write the start-up logs, and then exit."
+msgstr "Записать стартовые сообщения в журнал и выйти."
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr "X дисплей для подключения"
+
+#: Programs/pgmprivs_linux.c:1925
+msgid "XDG runtime directory access problem"
+msgstr "проблема доступа к каталогу выполнения XDG"
+
+#: Programs/pgmprivs_linux.c:1907
+msgid "XDG runtime directory created"
+msgstr "каталог выполнения XDG создан"
+
+#: Programs/pgmprivs_linux.c:1902
+msgid "XDG runtime directory exists"
+msgstr "каталог выполнения XDG уже существует"
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr "XFree(wm_name) для изменения"
+
+#: Programs/cmds.auto.h:1259
+msgid "XT (set 1) keyboard scan code"
+msgstr "клавиатурный скан-код XT (set 1)"
+
+#: Programs/midi.c:58
+msgid "Xylophone"
+msgstr "ксилофон"
+
+#: Programs/menu_prefs.c:1193
+msgid "Year Month Day"
+msgstr "год месяц день"
+
+#: Programs/menu.c:450
+msgid "Yes"
+msgstr "Да"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr "[хост][:порт]"
+
+#: Programs/menu_prefs.c:576
+msgid "all dots"
+msgstr "все точки"
+
+#: Programs/cmd_miscellaneous.c:121
+msgid "and"
+msgstr "и"
+
+#: Programs/cmds.auto.h:1142
+msgid "append characters to clipboard"
+msgstr "Присоединить символы к буферу обмена"
+
+#: Programs/cmds.auto.h:1033
+msgid "append to clipboard from character"
+msgstr "присоединить содержимое символа к буферу обмена"
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr "Под курсором"
+
+#: Programs/cmds.auto.h:1301
+msgid "backspace key"
+msgstr "Клавиша Пробел"
+
+#: Drivers/Braille/Baum/braille.c:1110 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr "Низкий заряд батареи"
+
+#: Programs/cmds.auto.h:1222
+msgid "bind to specific virtual terminal"
+msgstr "связать с выбранным виртуальным терминалом"
+
+#: Programs/cmds.auto.h:835
+msgid "bind to the next virtual terminal"
+msgstr "связать со следующим виртуальным терминалом"
+
+#: Programs/cmds.auto.h:829
+msgid "bind to the previous virtual terminal"
+msgstr "Связать с предыдущим виртуальным терминалом"
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "чёрный"
+
+#: Programs/core.c:1120
+msgid "blank line"
+msgstr "пустая строка"
+
+#: Programs/cmd_utils.c:168
+msgid "blink"
+msgstr "мигание"
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "синий"
+
+#: Programs/config.c:2838
+#, c-format
+msgid "braille device not specified"
+msgstr "устройство Брайля не определено"
+
+#: Programs/cmds.auto.h:544
+msgid "braille display temporarily unavailable"
+msgstr "Дисплей Брайля временно недоступен"
+
+#: Programs/config.c:1538
+msgid "braille driver initialization failed"
+msgstr "Ошибка инициализации драйвера Брайля"
+
+#: Programs/config.c:1617
+msgid "braille driver not loadable"
+msgstr "Драйвер Брайля недоступен для загрузки"
+
+#: Programs/config.c:1877
+msgid "braille driver restarting"
+msgstr "драйвер Брайля перезапускается"
+
+#: Programs/cmd_miscellaneous.c:178
+msgid "braille driver stopped"
+msgstr "Драйвер Брайля остановлен"
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr "Брайль запущен"
+
+#: Programs/cmds.auto.h:1019
+msgid "bring screen cursor to character"
+msgstr "Привести экранный курсор к символу"
+
+#: Programs/cmds.auto.h:520
+msgid "bring screen cursor to current line"
+msgstr "Привести экранный курсор к текущей строке"
+
+#: Programs/cmds.auto.h:1193
+msgid "bring screen cursor to line"
+msgstr "Привести экранный курсор к строке"
+
+#: Programs/cmds.auto.h:730
+msgid "bring screen cursor to speech cursor"
+msgstr "привести экранный курсор к речевому курсору"
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "коричневый"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr "Невозможно определить состояние панели управления"
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr "Невозможно открыть панель управления"
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr "Невозможно прочитать содержимое экрана"
+
+#: Drivers/Screen/Linux/screen.c:1601
+msgid "can't read screen header"
+msgstr "Невозможно прочитать заголовок экрана"
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr "Невозможно скомпилировать таблицу атрибутов"
+
+#: Programs/ctb_translate.c:365
+msgid "cannot compile contraction table"
+msgstr "Невозможно скомпилировать таблицу сокращений"
+
+#: Programs/config.c:1521
+msgid "cannot compile key table"
+msgstr "Невозможно скомпилировать таблицу клавиш"
+
+#: Programs/config.c:1098
+msgid "cannot compile keyboard table"
+msgstr "Невозможно скомпилировать клавиатурную таблицу"
+
+#: Programs/ttb_translate.c:256
+msgid "cannot compile text table"
+msgstr "Невозможно скомпилировать таблицу текстов"
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr "не могу подключиться к устройствам Брайля daemon brltty в течение %s\n"
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr "Невозможно  подключиться к дисплею %s\n"
+
+#: Programs/file.c:350
+msgid "cannot create directory"
+msgstr "Каталог не может быть создан"
+
+#: Programs/program.c:162
+#, c-format
+msgid "cannot determine program directory"
+msgstr "Каталог программ не обнаружен"
+
+#: Programs/config.c:2751 Programs/menu.c:544
+msgid "cannot determine working directory"
+msgstr "Активный каталог не обнаружен"
+
+#: Programs/system_windows.c:60
+msgid "cannot find procedure"
+msgstr "Выполняемое действие не обнаружено"
+
+#: Programs/program.c:170
+msgid "cannot fix install path"
+msgstr "не могу скорректировать путь установки"
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr "не могу получить tty\n"
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr "не могу получить tty %d\n"
+
+#: Programs/file.c:487
+msgid "cannot get working directory"
+msgstr "не могу получить активный каталог"
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr "не могу захватить окна на экране%d\n"
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr "не могу проигнорировать клавиши\n"
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr "не могу загрузить таблицу атрибутов"
+
+#: Programs/system_windows.c:53
+msgid "cannot load library"
+msgstr "не могу загрузить библиотеку"
+
+#: Programs/ttb_translate.c:276
+msgid "cannot load text table"
+msgstr "не могу загрузить таблицу текстов"
+
+#: Programs/file.c:339
+msgid "cannot make world writable"
+msgstr "не могу открыть среду для записи"
+
+#: Programs/config.c:1056
+msgid "cannot open key help"
+msgstr "не могу открыть справку по клавишам"
+
+#: Programs/program.c:274
+msgid "cannot open process identifier file"
+msgstr "не могу открыть файл идентификации процесса"
+
+#: Programs/menu.c:542
+msgid "cannot open working directory"
+msgstr "не могу открыть активный каталог"
+
+#: Programs/prefs.c:350
+msgid "cannot read preferences file"
+msgstr "не могу прочитать файл настроек"
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr "не могу установить фокус на %#010x\n"
+
+#: Programs/file.c:501 Programs/menu.c:531
+msgid "cannot set working directory"
+msgstr "не могу установить активный каталог"
+
+#: Programs/prefs.c:525
+msgid "cannot write to preferences file"
+msgstr "не могу сделать запись в файле настроек"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1065
+msgid "cap"
+msgstr "заглавная буква"
+
+#: Programs/menu_prefs.c:775 Programs/menu_prefs.c:1234
+msgid "cells"
+msgstr "ячейки"
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr "изменения отменены"
+
+#: Programs/cmds.auto.h:775
+msgid "clear all sticky input modifiers"
+msgstr "убрать все залипающие модификаторы ввода"
+
+#: Programs/cmds.auto.h:889
+msgid "clear the text selection"
+msgstr "убрать выделение текста"
+
+#: Programs/cmd_speech.c:475
+msgid "column"
+msgstr "столбец"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr "конфигурация"
+
+#: Programs/options.c:806
+msgid "configuration directive specified more than once"
+msgstr "свойство конфигурации определено более одного раза"
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr "консоль не используется"
+
+#: Programs/menu_prefs.c:945
+msgid "console tone generator"
+msgstr "Консоль генерирования звуков"
+
+#: Programs/cmds.auto.h:1135
+msgid "copy characters to clipboard"
+msgstr "скопировать символы в буфер обмена"
+
+#: Programs/cmds.auto.h:901
+msgid "copy selected text to host clipboard"
+msgstr "скопировать выделенный текст в основной буфер обмена"
+
+#: Programs/config.c:633 Programs/menu_prefs.c:503
+msgid "csecs"
+msgstr "csecs"
+
+#: Programs/cmds.auto.h:1280
+msgid "current reading location"
+msgstr "текущее место чтения"
+
+#: Programs/cmds.auto.h:1336
+msgid "cursor-down key"
+msgstr "клавиша курсор вниз"
+
+#: Programs/cmds.auto.h:1315
+msgid "cursor-left key"
+msgstr "клавиша курсор влево"
+
+#: Programs/cmds.auto.h:1322
+msgid "cursor-right key"
+msgstr "клавиша курсор вправо"
+
+#: Programs/cmds.auto.h:1329
+msgid "cursor-up key"
+msgstr "клавиша курсор вверх"
+
+#: Programs/cmds.auto.h:907
+msgid "cut selected text to host clipboard"
+msgstr "вырезать выделенный текст в основной буфер обмена"
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr "голубой"
+
+#: Programs/cmds.auto.h:781
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr "Цикличное залипание/разлипание клавиши (правый Alt)"
+
+#: Programs/cmds.auto.h:562
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr "Цикличное залипание/разлипание клавиши Control"
+
+#: Programs/cmds.auto.h:787
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr "Цикличное залипание/разлипание клавиши   (Windows)"
+
+#: Programs/cmds.auto.h:568
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr "Цикличное залипание/разлипание клавиши  (левый Alt"
+
+#: Programs/cmds.auto.h:550
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr "Цикличное залипание/разлипание клавиши Shift"
+
+#: Programs/cmds.auto.h:556
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr "цикличное  залипание/разлипание верхнего модификатора "
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "тёмно-серый"
+
+#: Programs/cmds.auto.h:483
+msgid "decrease speaking rate"
+msgstr "уменьшить скорость речи"
+
+#: Programs/cmds.auto.h:495
+msgid "decrease speaking volume"
+msgstr "уменьшить громкость речи"
+
+#: Programs/cmds.auto.h:1378
+msgid "delete key"
+msgstr "клавиша Del"
+
+#: Programs/cmds.auto.h:1079
+msgid "describe character"
+msgstr "описать символ"
+
+#: Programs/cmds.auto.h:717
+msgid "describe current character"
+msgstr "описать текущий символ"
+
+#: Programs/config.c:614 Programs/config.c:624
+msgid "device"
+msgstr "устройство"
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:412 Programs/config.c:422
+#: Programs/config.c:432 Programs/config.c:503
+msgid "directory"
+msgstr "каталог"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr "дисплей"
+
+#: Programs/cmds.auto.h:4
+msgid "do nothing"
+msgstr "не делать ничего"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr "готово"
+
+#: Programs/menu_prefs.c:577
+msgid "dot 7"
+msgstr "точка 7"
+
+#: Programs/menu_prefs.c:578
+msgid "dot 8"
+msgstr "точка 8"
+
+#: Programs/menu_prefs.c:575
+msgid "dots 7 and 8"
+msgstr "точки 7 и 8"
+
+#: Drivers/Braille/Baum/braille.c:1107
+msgid "driver request"
+msgstr "запрос драйвера"
+
+#: Programs/config.c:461 Programs/config.c:559 Programs/config.c:594
+msgid "driver,..."
+msgstr "драйвер,..."
+
+#: Programs/cmds.auto.h:1364
+msgid "end key"
+msgstr "клавиша End"
+
+#: Programs/cmds.auto.h:1287
+msgid "enter key"
+msgstr "клавиша Ввод"
+
+#: Programs/cmds.auto.h:384
+msgid "enter/leave command learn mode"
+msgstr "открыть/закрыть режим изучения команд"
+
+#: Programs/cmds.auto.h:372
+msgid "enter/leave help display"
+msgstr "открыть/закрыть экран справки"
+
+#: Programs/cmds.auto.h:390
+msgid "enter/leave preferences menu"
+msgstr "открыть/закрыть меню настроек"
+
+#: Programs/cmds.auto.h:378
+msgid "enter/leave status display"
+msgstr "открыть/закрыть строку состояния дисплея"
+
+#: Programs/cmds.auto.h:1308
+msgid "escape key"
+msgstr "клавиша Escape"
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "европейский Брайль"
+
+#: Programs/cmd_miscellaneous.c:118
+msgid "exactly"
+msgstr "точно"
+
+#: Programs/config.c:808
+msgid "excess argument"
+msgstr "добавочный аргумент"
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr "выполняется \"%s\" (из \"%s\")\n"
+
+#: Programs/pgmprivs_linux.c:2010
+msgid "executing as the invoking user"
+msgstr "выполняется от вызывающего пользователя"
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr "выполнение \"%s\" не удалось: %s\n"
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr "не удалось получить первичный фокус\n"
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:369
+#: Programs/config.c:378 Programs/config.c:388 Programs/config.c:514
+#: Programs/config.c:524 Programs/config.c:533 Programs/config.c:542
+#: Programs/config.c:578 Programs/config.c:663
+msgid "file"
+msgstr "файл"
+
+#: Programs/options.c:992
+#, c-format
+msgid "file '%s' processing error."
+msgstr "ошибка обработки файла '%s' ."
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr "создание обходного пути для \"%s\" не удалось: %s\n"
+
+#: Programs/cmds.auto.h:1386
+msgid "function key"
+msgstr "клавиша Function"
+
+#: Programs/cmds.auto.h:240
+msgid "go back after cursor tracking"
+msgstr "Вернуться назад  на место  слежения курсора"
+
+#: Programs/cmds.auto.h:939
+msgid "go back to the previous screen"
+msgstr "Вернуться назад на предыдущий экран"
+
+#: Programs/cmds.auto.h:186
+msgid "go backward one braille window"
+msgstr "Вернуться назад на одно окно Брайля"
+
+#: Programs/cmds.auto.h:202
+msgid "go backward skipping blank braille windows"
+msgstr "Вернуться назад, пропуская пустые окна Брайля"
+
+#: Programs/cmds.auto.h:843
+msgid "go backward to nearest non-blank braille window"
+msgstr "Вернуться назад, на ближайшее заполненное  окно Брайля"
+
+#: Programs/cmds.auto.h:20
+msgid "go down one line"
+msgstr "Перейти на одну строку вниз"
+
+#: Programs/cmds.auto.h:36
+msgid "go down several lines"
+msgstr "Перейти на несколько строк вниз"
+
+#: Programs/cmds.auto.h:118
+msgid "go down to first line of next paragraph"
+msgstr "Перейти вниз на первую строку следующего абзаца"
+
+#: Programs/cmds.auto.h:418
+msgid "go down to last item"
+msgstr "Перейти вниз к последнему элементу"
+
+#: Programs/cmds.auto.h:1128
+msgid "go down to nearest line with different character"
+msgstr "Перейти вниз к ближайшей строке с другим символом"
+
+#: Programs/cmds.auto.h:52
+msgid "go down to nearest line with different content"
+msgstr "Перейти вниз к ближайшей строке с другим содержимым"
+
+#: Programs/cmds.auto.h:68
+msgid "go down to nearest line with different highlighting"
+msgstr "перейти вниз к ближайшей строке с другим выделением"
+
+#: Programs/cmds.auto.h:1072
+msgid "go down to nearest line with less indent than character"
+msgstr "перейти вниз к ближайшей строке с отступом менее чем в один символ"
+
+#: Programs/cmds.auto.h:134
+msgid "go down to next command prompt"
+msgstr "перейти  к следующей  подсказке"
+
+#: Programs/cmds.auto.h:434
+msgid "go down to next item"
+msgstr "перейти  к следующему элементу"
+
+#: Programs/cmds.auto.h:194
+msgid "go forward one braille window"
+msgstr "Перейти вперёд на одно окно Брайля"
+
+#: Programs/cmds.auto.h:210
+msgid "go forward skipping blank braille windows"
+msgstr "Перейти вперёд, пропуская пустые окна Брайля"
+
+#: Programs/cmds.auto.h:851
+msgid "go forward to nearest non-blank braille window"
+msgstr "Перейти вперёд, к ближайшему заполненному окну Брайля"
+
+#: Programs/cmds.auto.h:170
+msgid "go left half a braille window"
+msgstr "Перейти влево на половину окна Брайля"
+
+#: Programs/cmds.auto.h:154
+msgid "go left one character"
+msgstr "Перейти влево на один символ"
+
+#: Programs/cmds.auto.h:178
+msgid "go right half a braille window"
+msgstr "Перейти вправо на половину окна Брайля"
+
+#: Programs/cmds.auto.h:162
+msgid "go right one character"
+msgstr "Перейти вправо на один символ"
+
+#: Programs/cmds.auto.h:690
+msgid "go to and speak first non-blank character on line"
+msgstr "Перейти к ближайшему незаполненному символу в строке и проговорить его"
+
+#: Programs/cmds.auto.h:704
+msgid "go to and speak first non-blank line on screen"
+msgstr "Перейти к ближайшей незаполненной строке на экране и проговорить её"
+
+#: Programs/cmds.auto.h:697
+msgid "go to and speak last non-blank character on line"
+msgstr "Перейти к последнему незаполненному символу в строке и проговорить его"
+
+#: Programs/cmds.auto.h:711
+msgid "go to and speak last non-blank line on screen"
+msgstr "Перейти к последней незаполненной строке на экране и проговорить её"
+
+#: Programs/cmds.auto.h:643
+msgid "go to and speak next character"
+msgstr "Перейти к следующему символу и проговорить его"
+
+#: Programs/cmds.auto.h:683
+msgid "go to and speak next line"
+msgstr "Перейти к следующей строке и проговорить её"
+
+#: Programs/cmds.auto.h:663
+msgid "go to and speak next word"
+msgstr "Перейти к следующему слову и проговорить его"
+
+#: Programs/cmds.auto.h:636
+msgid "go to and speak previous character"
+msgstr "Перейти к предыдущему символу и проговорить его"
+
+#: Programs/cmds.auto.h:676
+msgid "go to and speak previous line"
+msgstr "Перейти к предыдущей строке и проговорить её"
+
+#: Programs/cmds.auto.h:656
+msgid "go to and speak previous word"
+msgstr "Перейти к предыдущему слову и проговорить его"
+
+#: Programs/cmds.auto.h:102
+msgid "go to beginning of bottom line"
+msgstr "Перейти к началу нижней строки"
+
+#: Programs/cmds.auto.h:218
+msgid "go to beginning of line"
+msgstr "Перейти к началу строки"
+
+#: Programs/cmds.auto.h:93
+msgid "go to beginning of top line"
+msgstr "Перейти к началу верхней строки"
+
+#: Programs/cmds.auto.h:84
+msgid "go to bottom line"
+msgstr "Перейти вниз"
+
+#: Programs/cmds.auto.h:459
+msgid "go to current speaking position"
+msgstr "Перейти к текущему месту проговаривания"
+
+#: Programs/cmds.auto.h:226
+msgid "go to end of line"
+msgstr "Перейти в конец строки"
+
+#: Programs/cmds.auto.h:581
+msgid "go to previous menu level"
+msgstr "Перейти на предыдущий уровень меню"
+
+#: Programs/cmds.auto.h:1101
+msgid "go to remembered braille window position"
+msgstr "Перейти к сохраненной позиции окна Брайля"
+
+#: Programs/cmds.auto.h:233
+msgid "go to screen cursor"
+msgstr "Перейти к экранному курсору"
+
+#: Programs/cmds.auto.h:247
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr "Перейти к экранному курсору или вернуться назад к месту слежения курсора "
+
+#: Programs/cmds.auto.h:1110
+msgid "go to selected line"
+msgstr "Перейти к выделенной строке"
+
+#: Programs/cmds.auto.h:932
+msgid "go to the home screen"
+msgstr "Перейти на главный экран"
+
+#: Programs/cmds.auto.h:76
+msgid "go to top line"
+msgstr "Перейти к началу  "
+
+#: Programs/cmds.auto.h:12
+msgid "go up one line"
+msgstr "Перейти  на одну строку вверх"
+
+#: Programs/cmds.auto.h:28
+msgid "go up several lines"
+msgstr "перейти  на несколько строк"
+
+#: Programs/cmds.auto.h:410
+msgid "go up to first item"
+msgstr "перейти  к первому элементу"
+
+#: Programs/cmds.auto.h:110
+msgid "go up to first line of paragraph"
+msgstr "Перейти  к первой строке абзаца"
+
+#: Programs/cmds.auto.h:1119
+msgid "go up to nearest line with different character"
+msgstr "Перейти  к ближайшей строке с другим символом"
+
+#: Programs/cmds.auto.h:44
+msgid "go up to nearest line with different content"
+msgstr "Перейти  к ближайшей строке с другим содержимым"
+
+#: Programs/cmds.auto.h:60
+msgid "go up to nearest line with different highlighting"
+msgstr "Перейти  к ближайшей строке с другим выделением"
+
+#: Programs/cmds.auto.h:1063
+msgid "go up to nearest line with less indent than character"
+msgstr "Перейти  к ближайшей строке с отступом менее чем в 1 символ"
+
+#: Programs/cmds.auto.h:126
+msgid "go up to previous command prompt"
+msgstr "Перейти  к предыдущей подсказке"
+
+#: Programs/cmds.auto.h:426
+msgid "go up to previous item"
+msgstr "Перейти  к предыдущему элементу"
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "зелёный"
+
+#: Programs/pgmprivs_linux.c:2132
+msgid "group permissions added"
+msgstr "Групповые разрешения добавлены"
+
+#: Programs/cmd_miscellaneous.c:228
+msgid "help not available"
+msgstr "Справка недоступна"
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr "Невозможно прочитать экран справки"
+
+#: Programs/cmds.auto.h:1357
+msgid "home key"
+msgstr "Клавиша Home"
+
+#: Programs/config.c:482
+msgid "identifier,..."
+msgstr "идентификатор,..."
+
+#: Drivers/Braille/Baum/braille.c:1109
+msgid "idle timeout"
+msgstr " Нет активности -  перерыв"
+
+#: Programs/cmds.auto.h:489
+msgid "increase speaking rate"
+msgstr "Увеличить скорость речи"
+
+#: Programs/cmds.auto.h:501
+msgid "increase speaking volume"
+msgstr "Увеличить громкость речи"
+
+#: Programs/core.c:1123
+msgid "indent"
+msgstr "Отступ"
+
+#: Programs/cmds.auto.h:1149
+msgid "insert clipboard history entry after screen cursor"
+msgstr "Вставить историю  поисковых запросов к буферу обмена после экранного курсора"
+
+#: Programs/cmds.auto.h:526
+msgid "insert clipboard text after screen cursor"
+msgstr "Вставить текст из буфера обмена после экранного курсора"
+
+#: Programs/cmds.auto.h:913
+msgid "insert host clipboard text after screen cursor"
+msgstr "Вставить текст из главного буфера обмена после экранного курсора"
+
+#: Programs/cmds.auto.h:1371
+msgid "insert key"
+msgstr "Вставить команду"
+
+#: Programs/program.c:178
+msgid "install path not absolute"
+msgstr "Путь установки не уточнён"
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr "Некорректная установка счётчика"
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr "Недопустимая последовательность выхода"
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr "Некорректная установка флага"
+
+#: Programs/config.c:2663
+msgid "invalid message hold timeout"
+msgstr "недопустимое время задержки сообщения"
+
+#. the operand for an option is invalid
+#: Programs/options.c:588
+msgid "invalid operand"
+msgstr "Недопустимый оперант"
+
+#: Programs/brlapi_server.c:4413
+msgid "invalid thread stack size"
+msgstr "Недопустимый размер хранилища цепочек"
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr "Эмулятор клавиатуры выключен"
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr "Эмулятор клавиатуры включён"
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr "светло-синий"
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr "небесно-голубой"
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr "светло-зелёный"
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr "светло-серый"
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr "Светло-пурпурный"
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr "Ярко-красный"
+
+#: Programs/cmd_speech.c:474
+msgid "line"
+msgstr "Строка"
+
+#: Programs/cmds.auto.h:1047
+msgid "linear copy to character"
+msgstr "Построчно копировать в кодировку"
+
+#: Programs/config.c:654
+msgid "lvl|cat,..."
+msgstr "lvl|cat,..."
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "Пурпурный"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:582
+msgid "missing operand"
+msgstr "Отсутствует оперант"
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr "Отсутствует имя параметра"
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr "Отсутствует спецификатор параметра"
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr "Отсутствует значение параметра"
+
+#: Programs/cmds.auto.h:993
+msgid "move to the first item in the screen area"
+msgstr "Перейти к первому элементу в экранной области"
+
+#: Programs/cmds.auto.h:1011
+msgid "move to the last item in the screen area"
+msgstr "Перейти к последнему элементу в экранной области"
+
+#: Programs/cmds.auto.h:1005
+msgid "move to the next item in the screen area"
+msgstr "Перейти к следующему элементу в экранной области"
+
+#: Programs/cmds.auto.h:999
+msgid "move to the previous item in the screen area"
+msgstr "Перейти к предыдущему элементу в экранной области"
+
+#: Programs/config.c:345 Programs/config.c:397 Programs/config.c:450
+#: Programs/config.c:472 Programs/config.c:550 Programs/config.c:569
+#: Programs/config.c:604
+msgid "name=value,..."
+msgstr "имя=значение,..."
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr "исходное"
+
+#: Programs/config.c:1065
+msgid "no key bindings"
+msgstr "нет привязки клавиш"
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr "нет экрана"
+
+#: Programs/config.c:706
+msgid "none"
+msgstr "никакие"
+
+#: Programs/config.c:1338
+msgid "not saved"
+msgstr "не сохранено"
+
+#: Programs/pgmprivs_linux.c:1983
+msgid "not switching to an unprivileged user"
+msgstr "нет перехода в статус обычного пользователя"
+
+#: Programs/cmds.auto.h:969
+msgid "open the application alerts window"
+msgstr "открыть окно оповещений"
+
+#: Programs/cmds.auto.h:957
+msgid "open the application list window"
+msgstr "открыть окно списка приложений"
+
+#: Programs/cmds.auto.h:963
+msgid "open the application-specific menu"
+msgstr "открыть контекстное меню для данного приложения"
+
+#: Programs/cmds.auto.h:925
+msgid "open the braille actions window"
+msgstr "открыть окно действий Брайля"
+
+#: Programs/cmds.auto.h:951
+msgid "open the device options window"
+msgstr "Открыть окно свойств устройства"
+
+#: Programs/cmds.auto.h:945
+msgid "open the device settings window"
+msgstr "Открыть окно настроек устройства"
+
+#: Programs/options.c:155
+msgid "option"
+msgstr "свойство"
+
+#: Programs/pgmprivs_linux.c:2117
+msgid "ownership claimed"
+msgstr "заявление о правах собственности"
+
+#: Programs/cmds.auto.h:1350
+msgid "page-down key"
+msgstr "клавиша Страница вниз"
+
+#: Programs/cmds.auto.h:1343
+msgid "page-up key"
+msgstr "клавиша Страница вверх"
+
+#: Programs/menu_prefs.c:509
+msgid "percentage"
+msgstr "Процентное соотношение"
+
+#: Programs/config.c:2636
+msgid "pid file not specified"
+msgstr "pid-файл не выбран"
+
+#: Programs/program.c:320
+msgid "pid file open error"
+msgstr "ошибка открытия pid-файла"
+
+#: Programs/spk.c:200
+msgid "pitch"
+msgstr "высота тона"
+
+#: Programs/cmds.auto.h:1086
+msgid "place left end of braille window at character"
+msgstr "Поместить левый конец окна Брайля возле символа"
+
+#: Drivers/Braille/Baum/braille.c:1108
+msgid "power switch"
+msgstr "включить электропитание"
+
+#: Programs/spk.c:186
+msgid "rate"
+msgstr "скорость"
+
+#: Programs/cmds.auto.h:1040
+msgid "rectangular copy to character"
+msgstr "Превращение   символа в прямоугольник"
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "красный"
+
+#: Programs/cmds.auto.h:877
+msgid "refresh braille display"
+msgstr "обновить дисплей Брайля"
+
+#: Programs/cmds.auto.h:1201
+msgid "refresh braille line"
+msgstr "обновить строку Брайля"
+
+#: Programs/config.c:404
+msgid "regexp,..."
+msgstr "regexp,..."
+
+#: Programs/config.c:1881
+#, c-format
+msgid "reinitializing braille driver"
+msgstr "Повторная инициализация драйвера Брайля"
+
+#: Programs/config.c:2394
+#, c-format
+msgid "reinitializing screen driver"
+msgstr "Повторная инициализация экранного драйвера"
+
+#: Programs/config.c:2194
+#, c-format
+msgid "reinitializing speech driver"
+msgstr "Повторная инициализация речевого драйвера"
+
+#: Programs/cmds.auto.h:1093
+msgid "remember current braille window position"
+msgstr "Запомнить текущую позицию окна Брайля"
+
+#: Programs/cmds.auto.h:1229
+msgid "render an alert"
+msgstr "Вывести оповещение на экран"
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr "повторить отсчёт"
+
+#: Programs/cmds.auto.h:532
+msgid "restart braille driver"
+msgstr "перезапустить драйвер Брайля"
+
+#: Programs/cmds.auto.h:538
+msgid "restart speech driver"
+msgstr "Перезапустить речевой драйвер"
+
+#: Programs/cmds.auto.h:755
+msgid "restore clipboard from disk"
+msgstr "восстановить буфер обмена с диска"
+
+#: Programs/cmds.auto.h:402
+msgid "restore preferences from disk"
+msgstr "восстановить настройки с диска"
+
+#: Programs/cmds.auto.h:975
+msgid "return to the active screen area"
+msgstr "вернуться в активную область экрана"
+
+#: Programs/cmds.auto.h:749
+msgid "save clipboard to disk"
+msgstr "сохранить буфер обмена на диск"
+
+#: Programs/cmds.auto.h:396
+msgid "save preferences to disk"
+msgstr "сохранить настройки на диск"
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr "схема+..."
+
+#: Programs/config.c:2270
+msgid "screen driver not loadable"
+msgstr "экранный драйвер недоступен для загрузки"
+
+#: Programs/config.c:2391
+msgid "screen driver restarting"
+msgstr "перезапуск экранного драйвера"
+
+#: Programs/cmd_miscellaneous.c:186
+msgid "screen driver stopped"
+msgstr "экранный драйвер остановлен"
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr "экран заблокирован"
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr "экран не в текстовом режиме"
+
+#: Programs/cmds.auto.h:140
+msgid "search backward for clipboard text"
+msgstr "обратный поиск текста в буфере обмена"
+
+#: Programs/cmds.auto.h:146
+msgid "search forward for clipboard text"
+msgstr "прямой поиск текста в буфере обмена"
+
+#: Programs/cmd_miscellaneous.c:122
+msgid "second"
+msgid_plural "seconds"
+msgstr[0] "секунд"
+msgstr[1] "секунда"
+
+#: Programs/cmds.auto.h:895
+msgid "select all of the text"
+msgstr "выделить весь текст"
+
+#: Programs/cmds.auto.h:446
+msgid "select next choice"
+msgstr "выделить следующий выбранный"
+
+#: Programs/cmds.auto.h:440
+msgid "select previous choice"
+msgstr "выделить предыдущий выбранный"
+
+#: Programs/cmds.auto.h:352
+msgid "set alert tunes on/off"
+msgstr "включить/выключить звуковые оповещения"
+
+#: Programs/cmds.auto.h:338
+msgid "set attribute blinking on/off"
+msgstr "включить/выключить атрибут мигания"
+
+#: Programs/cmds.auto.h:331
+msgid "set attribute underlining on/off"
+msgstr "включить/выключить атрибут подчёркивания"
+
+#: Programs/cmds.auto.h:1163
+msgid "set attributes table"
+msgstr "настроить таблицу атрибутов"
+
+#: Programs/cmds.auto.h:359
+msgid "set autorepeat on/off"
+msgstr "включить/выключить автоповтор"
+
+#: Programs/cmds.auto.h:623
+msgid "set autospeak completed words on/off"
+msgstr "включить/выключить автопроговаривание введённых слов"
+
+#: Programs/cmds.auto.h:609
+msgid "set autospeak deleted characters on/off"
+msgstr "включить/выключить автопроговаривание удалённых символов"
+
+#: Programs/cmds.auto.h:871
+msgid "set autospeak indent of current line on/off"
+msgstr "включить/выключить автопроговаривание отступа текущей строки"
+
+#: Programs/cmds.auto.h:602
+msgid "set autospeak inserted characters on/off"
+msgstr "включить/выключить автопроговаривание вставленных символов"
+
+#: Programs/cmds.auto.h:366
+msgid "set autospeak on/off"
+msgstr "включить/выключить автопроговаривание"
+
+#: Programs/cmds.auto.h:616
+msgid "set autospeak replaced characters on/off"
+msgstr "включить/выключить автопроговаривание заменённых символов"
+
+#: Programs/cmds.auto.h:595
+msgid "set autospeak selected character on/off"
+msgstr "включить/выключить автопроговаривание выделенного символа"
+
+#: Programs/cmds.auto.h:588
+msgid "set autospeak selected line on/off"
+msgstr "включить/выключить автопроговаривание выделенной строки"
+
+#: Programs/cmds.auto.h:769
+msgid "set braille keyboard enabled/disabled"
+msgstr "включить/отключить клавиатуру Брайля"
+
+#: Programs/cmds.auto.h:762
+msgid "set braille typing mode dots/text"
+msgstr "режим брайлевского ввода: точки/текст"
+
+#: Programs/cmds.auto.h:345
+msgid "set capital letter blinking on/off"
+msgstr "включить/выключить мигание заглавной буквы"
+
+#: Programs/cmds.auto.h:1170
+msgid "set contraction table"
+msgstr "настроить таблицу сокращений"
+
+#: Programs/cmds.auto.h:261
+msgid "set display mode attributes/text"
+msgstr "режим дисплея: атрибуты/текст"
+
+#: Programs/cmds.auto.h:303
+msgid "set hidden screen cursor on/off"
+msgstr "включить/отключить скрытый экранный курсор"
+
+#: Programs/cmds.auto.h:1177
+msgid "set keyboard table"
+msgstr "настроить клавиатурную таблицу"
+
+#: Programs/cmds.auto.h:1184
+msgid "set language profile"
+msgstr "настроить языковой профиль"
+
+#: Programs/cmds.auto.h:324
+msgid "set screen cursor blinking on/off"
+msgstr "включить/выключить мигание экранного курсора"
+
+#: Programs/cmds.auto.h:317
+msgid "set screen cursor style block/underline"
+msgstr "Перевести экранный курсор в режим подчёркивания"
+
+#: Programs/cmds.auto.h:296
+msgid "set screen cursor visibility on/off"
+msgstr "включить/выключить видимость экранного курсора"
+
+#: Programs/cmds.auto.h:254
+msgid "set screen image frozen/unfrozen"
+msgstr "изображение на экране: приостановить/возобновить"
+
+#: Programs/cmds.auto.h:289
+msgid "set skipping of blank braille windows on/off"
+msgstr "включить/выключить пропуск пустых окон Брайля"
+
+#: Programs/cmds.auto.h:282
+msgid "set skipping of lines with identical content on/off"
+msgstr "включить/выключить пропуск строк с одинаковым содержимым"
+
+#: Programs/cmds.auto.h:275
+msgid "set sliding braille window on/off"
+msgstr "включить/выключить скольжение окна Брайля"
+
+#: Programs/cmds.auto.h:743
+msgid "set speech cursor visibility on/off"
+msgstr "включить/выключить видимость речевого курсора"
+
+#: Programs/cmds.auto.h:1215
+msgid "set text selection"
+msgstr "настроить выделение текста"
+
+#: Programs/cmds.auto.h:268
+msgid "set text style 6-dot/8-dot"
+msgstr "стиль текста: шеститочечный/восьмиточечный"
+
+#: Programs/cmds.auto.h:1156
+msgid "set text table"
+msgstr "настроить таблицу текстов"
+
+#: Programs/cmds.auto.h:858
+msgid "set touch navigation on/off"
+msgstr "включить/выключить жестовую навигацию"
+
+#: Programs/cmds.auto.h:310
+msgid "set track screen cursor on/off"
+msgstr "включить/выключить отслеживание экранного курсора"
+
+#: Programs/cmds.auto.h:574
+msgid "show current date and time"
+msgstr "показать текущие дату и время"
+
+#: Programs/cmds.auto.h:919
+msgid "show the window title"
+msgstr "показать заголовок окна"
+
+#: Programs/cmds.auto.h:883
+msgid "show various device status indicators"
+msgstr "показать индикаторы состояния устройства"
+
+#: Programs/menu_prefs.c:946
+msgid "soundcard digital audio"
+msgstr "звуковая карта - цифровое аудио"
+
+#: Programs/menu_prefs.c:948
+msgid "soundcard synthesizer"
+msgstr "звуковая карта синтезатор"
+
+#: Programs/cmd.c:277 Programs/core.c:1046
+msgid "space"
+msgstr "пробел"
+
+#: Programs/cmds.auto.h:629
+msgid "speak current character"
+msgstr "проговаривать текущий символ"
+
+#: Programs/cmds.auto.h:465 Programs/cmds.auto.h:669
+msgid "speak current line"
+msgstr "проговаривать текущую строку"
+
+#: Programs/cmds.auto.h:649
+msgid "speak current word"
+msgstr "проговаривать текущее слово"
+
+#: Programs/cmds.auto.h:477
+msgid "speak from current line through bottom of screen"
+msgstr "проговаривать от текущей строки до конца экрана"
+
+#: Programs/cmds.auto.h:471
+msgid "speak from top of screen through current line"
+msgstr "проговаривать от начала экрана до текущей строки"
+
+#: Programs/cmds.auto.h:864
+msgid "speak indent of current line"
+msgstr "проговаривать отступ текущей строки"
+
+#: Programs/cmds.auto.h:736
+msgid "speak speech cursor location"
+msgstr "проговаривать положение речевого курсора"
+
+#: Programs/config.c:2042
+msgid "speech driver not loadable"
+msgstr "невозможно загрузить речевой драйвер"
+
+#: Programs/config.c:2191
+msgid "speech driver restarting"
+msgstr "перезапуск речевого драйвера"
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr "речевой драйвер остановлен"
+
+#: Programs/cmds.auto.h:723
+msgid "spell current word"
+msgstr "произнести текущее слово по буквам"
+
+#: Programs/cmds.auto.h:1026
+msgid "start new clipboard at character"
+msgstr "создать новый буфер обмена после символа"
+
+#: Programs/cmds.auto.h:1208
+msgid "start text selection"
+msgstr "начать выделение текста"
+
+#: Programs/cmds.auto.h:799
+msgid "start the braille driver"
+msgstr "запустить драйвер Брайля"
+
+#: Programs/cmds.auto.h:823
+msgid "start the screen driver"
+msgstr "запустить экранный драйвер"
+
+#: Programs/cmds.auto.h:811
+msgid "start the speech driver"
+msgstr "запустить речевой драйвер"
+
+#: Programs/cmds.auto.h:452
+msgid "stop speaking"
+msgstr "остановить проговаривание"
+
+#: Programs/cmds.auto.h:793
+msgid "stop the braille driver"
+msgstr "остановить драйвер Брайля"
+
+#: Programs/cmds.auto.h:817
+msgid "stop the screen driver"
+msgstr "остановить экранный драйвер"
+
+#: Programs/cmds.auto.h:805
+msgid "stop the speech driver"
+msgstr "остановить речевой драйвер"
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr "мастер по исправлению  старых ошибок\n"
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr "цепочка"
+
+#: Programs/cmds.auto.h:1273
+msgid "switch to command context"
+msgstr "переключиться в командную строку"
+
+#: Programs/cmds.auto.h:1054
+msgid "switch to specific virtual terminal"
+msgstr "переключиться на выбранный виртуальный терминал"
+
+#: Programs/cmds.auto.h:987
+msgid "switch to the next screen area"
+msgstr "перейти к следующей экранной области"
+
+#: Programs/cmds.auto.h:513
+msgid "switch to the next virtual terminal"
+msgstr "переключиться на следующий виртуальный терминал"
+
+#: Programs/cmds.auto.h:981
+msgid "switch to the previous screen area"
+msgstr "перейти к предыдущей экранной области"
+
+#: Programs/cmds.auto.h:507
+msgid "switch to the previous virtual terminal"
+msgstr "переключиться на предыдущий виртуальный терминал"
+
+#: Programs/pgmprivs_linux.c:1968
+msgid "switched to unprivileged user"
+msgstr " переход к  обычному пользователю "
+
+#: Programs/cmds.auto.h:1294
+msgid "tab key"
+msgstr "клавиша табуляции"
+
+#: Programs/config.c:300 Programs/config.c:307
+msgid "text"
+msgstr "текст"
+
+#: Programs/cmds.auto.h:1245
+msgid "type braille dots"
+msgstr "ввести точки Брайля"
+
+#: Programs/cmds.auto.h:1237
+msgid "type unicode character"
+msgstr "ввести символ в формате Unicode"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr "внезапная блокировка ввода"
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr "непредвиденное cmd"
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr "необработанная команда"
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr "неизвестная команда"
+
+#: Programs/options.c:822
+msgid "unknown configuration directive"
+msgstr "неизвестная директива  конфигурации"
+
+#: Programs/config.c:726
+msgid "unknown log level or category"
+msgstr "неизвестная запись в журнале или категория"
+
+#. an unknown option has been specified
+#: Programs/options.c:569
+msgid "unknown option"
+msgstr "неизвестная опция"
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr "неподдерживаемый параметр"
+
+#: Programs/spk.c:172
+msgid "volume"
+msgstr "громкость"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "белый"
+
+#: Programs/pgmprivs_linux.c:1830
+msgid "working directory changed"
+msgstr "активный каталог изменён"
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound  keysym %08X\n"
+msgstr "xbrlapi: Не могу найти код для перезаписи   и копирования  значений отдельных клавиш %08X\n"
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr "xbrlapi: Не удалось найти годные к применению модификаторы %d для получения значения клавиши %08X\n"
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr "xbrlapi: Не удалось перевести значение клавиши %08X в kкодовый ключ.\n"
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr "xbrlapi: неизвестная Ошибка %d, %s на дисплее %s\n"
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr "xbrlapi: неподходящий формат числа VT\n"
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr "xbrlapi: неподходящий вид числа VT\n"
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr "xbrlapi: не могу получить исходное окно XFree86_VT свойство\n"
+
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr "xbrlapi: не могу записать имя окна %s\n"
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr "xbrlapi: не удалось перехватить источник %#010lx\n"
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr "xbrlapi: не удалось перехватить окно %#010lx\n"
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr "xbrlapi: не удалось перехватить окно %#010lx однако удалось получить фокус\n"
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr "xbrlapi: более одного элемента для числа VT\n"
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr "xbrlapi: нет XFree86_VT atom\n"
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr "xbrlapi: нет элементов для числа VT\n"
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr "xbrlapi: источник %#010lx, req %u:%u\n"
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr "xbrlapi: необработанный вид события: %d\n"
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr "xbrlapi: окно %#010lx изменено на нулевое имя\n"
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "жёлтый"
diff --git a/Messages/zh.po b/Messages/zh.po
new file mode 100644
index 0000000..7bc1d5f
--- /dev/null
+++ b/Messages/zh.po
@@ -0,0 +1,4331 @@
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: brltty 4.3\n"
+"Report-Msgid-Bugs-To: BRLTTY@brltty.app\n"
+"POT-Creation-Date: 2021-09-04 20:26+0200\n"
+"PO-Revision-Date: 2021-09-04 21:27+0000\n"
+"Last-Translator: 高生旺 <coscell@molerat.net>\n"
+"Language-Team: Friends of BRLTTY <BRLTTY@brlttY.app>\n"
+"Language: zh\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: UTF-8\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: Programs/brltty.c:167
+#, c-format
+msgid "\"%s\" started as \"%s\"\n"
+msgstr "\"%s\" 啟動為 \"%s\"\n"
+
+#. xgettext: This phrase describes the colour of a character on the screen.
+#. xgettext: %1$s is the (already translated) foreground colour.
+#. xgettext: %2$s is the (already translated) background colour.
+#: Programs/cmd_utils.c:169
+#, c-format
+msgid "%1$s on %2$s"
+msgstr "%1$s 在 %2$s"
+
+#. xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+#. xgettext: (%u represents the number of hours)
+#: Programs/cmd_miscellaneous.c:89
+#, c-format
+msgid "%u o'clock"
+msgid_plural "%u o'clock"
+msgstr[0] "%u 點鐘"
+
+#. xgettext: This is a number (%u) of seconds (time units).
+#: Programs/cmd_miscellaneous.c:107
+#, c-format
+msgid "%u second"
+msgid_plural "%u seconds"
+msgstr[0] "%u 秒鐘"
+
+#: Programs/menu_prefs.c:543 Programs/menu_prefs.c:544
+#: Programs/menu_prefs.c:546 Programs/menu_prefs.c:547
+#: Programs/menu_prefs.c:550 Programs/menu_prefs.c:551
+#: Programs/menu_prefs.c:552 Programs/menu_prefs.c:554
+#: Programs/menu_prefs.c:555 Programs/menu_prefs.c:561
+msgid "1 cell"
+msgstr "1 方"
+
+#: Programs/menu_prefs.c:900
+msgid "1 second"
+msgstr "1 秒"
+
+#: Programs/menu_prefs.c:962
+msgid "10 seconds"
+msgstr "10 秒"
+
+#: Programs/menu_prefs.c:1273
+msgid "12 Hour"
+msgstr "12 小時"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:545
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:549
+#: Programs/menu_prefs.c:553
+msgid "2 cells"
+msgstr "2 方"
+
+#: Programs/menu_prefs.c:901
+msgid "2 seconds"
+msgstr "2 秒"
+
+#: Programs/menu_prefs.c:963
+msgid "20 seconds"
+msgstr "20 秒"
+
+#: Programs/menu_prefs.c:1272
+msgid "24 Hour"
+msgstr "24 小時"
+
+#: Programs/menu_prefs.c:898
+msgid "250 milliseconds"
+msgstr "250 毫秒"
+
+#: Programs/menu_prefs.c:557 Programs/menu_prefs.c:558
+#: Programs/menu_prefs.c:559 Programs/menu_prefs.c:560
+msgid "3 cells"
+msgstr "3 方"
+
+#: Programs/menu_prefs.c:964
+msgid "40 seconds"
+msgstr "40 秒"
+
+#: Programs/menu_prefs.c:961
+msgid "5 seconds"
+msgstr "5 秒"
+
+#: Programs/menu_prefs.c:899
+msgid "500 milliseconds"
+msgstr "500 毫秒"
+
+#: Programs/menu_prefs.c:733
+msgid "6-dot"
+msgstr "6-點"
+
+#: Programs/brltty-ttb.c:179
+msgid "8-bit character set to use."
+msgstr "要用的八位元字元集"
+
+#: Programs/menu_prefs.c:732
+msgid "8-dot"
+msgstr "8-點"
+
+#: Programs/scr_menu.c:104
+msgid "<off>"
+msgstr "<關>"
+
+#: Programs/config.c:1506
+msgid "API Parameter"
+msgstr "API 參數"
+
+#. xgettext: This is the description of the PASSAT command.
+#: Programs/cmds.auto.h:1471
+msgid "AT (set 2) keyboard scan code"
+msgstr "AT (set 2) 鍵盤掃描碼"
+
+#. xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+#: Programs/midi.c:145
+msgid "Accordion"
+msgstr "風琴"
+
+#. Bass
+#. xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+#: Programs/midi.c:180
+msgid "Acoustic Bass"
+msgstr "低音被私"
+
+#. Piano
+#. xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+#: Programs/midi.c:80
+msgid "Acoustic Grand Piano"
+msgstr "平臺鋼琴"
+
+#. Guitar
+#. xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+#: Programs/midi.c:155
+msgid "Acoustic Guitar (nylon)"
+msgstr "尼龍吉他"
+
+#. xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+#: Programs/midi.c:158
+msgid "Acoustic Guitar (steel)"
+msgstr "鋼弦吉他"
+
+#: Programs/menu_prefs.c:1299
+msgid "After Time"
+msgstr "後時間"
+
+#. xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+#: Programs/midi.c:434
+msgid "Agogo"
+msgstr "Agogo"
+
+#: Programs/menu_prefs.c:1487
+msgid "Alert"
+msgstr "警示"
+
+#: Programs/menu_prefs.c:1104
+msgid "Alert Dots"
+msgstr "警示點位"
+
+#: Programs/menu_prefs.c:1109
+msgid "Alert Messages"
+msgstr "警示訊息"
+
+#: Programs/menu_prefs.c:1049
+msgid "Alert Tunes"
+msgstr "警示音"
+
+#: Programs/menu_prefs.c:864 Programs/menu_prefs.c:1143
+msgid "All"
+msgstr "全部"
+
+#: Programs/menu_prefs.c:555
+msgid "Alphabetic Cursor Coordinates"
+msgstr "字母游標座標"
+
+#: Programs/menu_prefs.c:554
+msgid "Alphabetic Window Coordinates"
+msgstr "字母視窗座標"
+
+#. xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+#: Programs/midi.c:283
+msgid "Alto Saxophone"
+msgstr "中音薩克斯風"
+
+#. xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+#: Programs/midi.c:474
+msgid "Applause"
+msgstr "掌聲"
+
+#: Programs/log.c:118
+msgid "Async Events"
+msgstr "非同步事件"
+
+#: Programs/menu_prefs.c:807
+msgid "Attributes Blink Period"
+msgstr "屬性跳動其"
+
+#: Programs/menu_prefs.c:815
+msgid "Attributes Percent Visible"
+msgstr "可見的屬性百分比"
+
+#: Programs/config.c:999 Programs/menu_prefs.c:1402
+msgid "Attributes Table"
+msgstr "屬性表"
+
+#: Programs/alert.c:168
+msgid "Autorelease"
+msgstr "自動放開"
+
+#: Programs/menu_prefs.c:967
+msgid "Autorelease Time"
+msgstr "自動放開時間"
+
+#: Programs/menu_prefs.c:984
+msgid "Autorepeat Enabled"
+msgstr "自動重複啟用"
+
+#: Programs/menu_prefs.c:990
+msgid "Autorepeat Interval"
+msgstr "自動重複間隔"
+
+#: Programs/menu_prefs.c:997
+msgid "Autorepeat Panning"
+msgstr "自動重複移動"
+
+#: Programs/menu_prefs.c:1186
+msgid "Autospeak"
+msgstr "自動朗讀"
+
+#: Programs/config.c:1952
+msgid "BRLTTY stopped"
+msgstr "BRLTTY 停止"
+
+#. xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+#: Programs/midi.c:421
+msgid "Bag Pipe"
+msgstr "水管"
+
+#. xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+#: Programs/midi.c:409
+msgid "Banjo"
+msgstr "斑鳩"
+
+#. xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+#: Programs/midi.c:289
+msgid "Baritone Saxophone"
+msgstr "男中音薩克斯風"
+
+#. xgettext: This is the name of MIDI musical instrument group #5.
+#: Programs/midi.c:37
+msgid "Bass"
+msgstr "被私"
+
+#. xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+#: Programs/midi.c:298
+msgid "Bassoon"
+msgstr "低音管"
+
+#: Programs/menu_prefs.c:1056
+msgid "Beeper"
+msgstr "翁鳴器"
+
+#: Programs/menu_prefs.c:1298
+msgid "Before Time"
+msgstr "前時間"
+
+#. xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+#: Programs/midi.c:465
+msgid "Bird Tweet"
+msgstr "鳥鳴"
+
+#: Programs/menu_prefs.c:801
+msgid "Blinking Attributes"
+msgstr "跳動屬性"
+
+#: Programs/menu_prefs.c:823
+msgid "Blinking Capitals"
+msgstr "跳動大寫字母"
+
+#: Programs/menu_prefs.c:774
+msgid "Blinking Screen Cursor"
+msgstr "跳動游標"
+
+#: Programs/menu_prefs.c:1244
+msgid "Blinking Speech Cursor"
+msgstr "跳動語音游標"
+
+#: Programs/menu_prefs.c:665 Programs/menu_prefs.c:1358
+msgid "Block"
+msgstr "區快"
+
+#. xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+#: Programs/midi.c:317
+msgid "Blown Bottle"
+msgstr "瓶子"
+
+#: Programs/log.c:142
+msgid "Bluetooth I/O"
+msgstr "藍芽輸入/輸出"
+
+#: Programs/config.c:1727
+msgid "Braille Device"
+msgstr "點字設備"
+
+#: Programs/config.c:1723
+msgid "Braille Driver"
+msgstr "點字驅動"
+
+#: Programs/log.c:148
+msgid "Braille Driver Events"
+msgstr "點字驅動事件"
+
+#: Programs/menu_prefs.c:752
+msgid "Braille Firmness"
+msgstr "點字強度"
+
+#: Programs/log.c:82
+msgid "Braille Key Events"
+msgstr "點字按鍵事件"
+
+#: Programs/config.c:1726
+msgid "Braille Parameter"
+msgstr "點字參數"
+
+#: Programs/menu_prefs.c:698
+msgid "Braille Presentation"
+msgstr "點字顯示"
+
+#: Programs/menu_prefs.c:1385
+msgid "Braille Tables"
+msgstr "點字表"
+
+#: Programs/menu_prefs.c:932
+msgid "Braille Typing"
+msgstr "點字輸入"
+
+#: Programs/menu_prefs.c:706
+msgid "Braille Variant"
+msgstr "點字種類"
+
+#: Programs/menu_prefs.c:885
+msgid "Braille Window Overlap"
+msgstr "點字視窗重疊"
+
+#: Programs/config.c:473
+#, c-format
+msgid "Braille driver code (%s, %s, or one of {%s})."
+msgstr "點字驅動碼 (%s, %s, 或 {%s} 之一"
+
+#. xgettext: This is the name of MIDI musical instrument group #8.
+#: Programs/midi.c:46
+msgid "Brass"
+msgstr "銅管"
+
+#. xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+#: Programs/midi.c:270
+msgid "Brass Section"
+msgstr "銅管組"
+
+#. xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+#: Programs/midi.c:459
+msgid "Breath Noise"
+msgstr "呼吸聲"
+
+#. xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+#: Programs/midi.c:83
+msgid "Bright Acoustic Piano"
+msgstr "明亮鋼琴"
+
+#: Programs/xbrlapi.c:93
+msgid "BrlAPI authorization/authentication schemes"
+msgstr "BrlAPI 授權/身份驗證方案"
+
+#: Programs/xbrlapi.c:86
+msgid "BrlAPI host and/or port to connect to"
+msgstr "要連接的 BrlAPI 主機和/或埠口"
+
+#: Programs/menu_prefs.c:1420
+msgid "Build Information"
+msgstr "建立資訊"
+
+#: Programs/menu_prefs.c:725
+msgid "Capitalization Mode"
+msgstr "大寫模式"
+
+#: Programs/menu_prefs.c:828
+msgid "Capitals Blink Period"
+msgstr "大寫跳動週期"
+
+#: Programs/menu_prefs.c:836
+msgid "Capitals Percent Visible"
+msgstr "可見大寫百分比"
+
+#: Programs/menu_prefs.c:1510
+msgid "Category Log Level"
+msgstr "分類記錄等級"
+
+#. Chromatic Percussion
+#. xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+#: Programs/midi.c:105
+msgid "Celesta"
+msgstr "鋼片琴"
+
+#. xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+#: Programs/midi.c:211
+msgid "Cello"
+msgstr "大提琴"
+
+#. xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+#: Programs/midi.c:242
+msgid "Choir Aahs"
+msgstr "和聲阿"
+
+#. xgettext: This is the name of MIDI musical instrument group #2.
+#: Programs/midi.c:28
+msgid "Chromatic Percussion"
+msgstr "有音調的打擊樂"
+
+#. xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+#: Programs/midi.c:139
+msgid "Church Organ"
+msgstr "教堂風琴"
+
+#. xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+#: Programs/midi.c:301
+msgid "Clarinet"
+msgstr "單簧管"
+
+#. xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+#: Programs/midi.c:101
+msgid "Clavinet"
+msgstr "單簧管"
+
+#: Programs/menu.c:894
+msgid "Close"
+msgstr "關閉"
+
+#: Programs/menu_prefs.c:1282
+msgid "Colon"
+msgstr "冒號"
+
+#: Programs/menu_prefs.c:702
+msgid "Computer Braille"
+msgstr "電腦點字"
+
+#: Programs/menu_prefs.c:736
+msgid "Computer Braille Cell Type"
+msgstr "電腦點字方類型"
+
+#: Programs/menu_prefs.c:1444
+msgid "Configuration Directory"
+msgstr "設定檔目錄"
+
+#: Programs/config.c:2895 Programs/menu_prefs.c:1449
+msgid "Configuration File"
+msgstr "設定檔"
+
+#: Programs/alert.c:163
+msgid "Console Bell"
+msgstr "主控台嗶聲"
+
+#: Programs/menu_prefs.c:1035
+msgid "Console Bell Alert"
+msgstr "主控台嗶聲警示"
+
+#. xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+#: Programs/midi.c:214
+msgid "Contrabass"
+msgstr "倍低音"
+
+#: Programs/menu_prefs.c:703
+msgid "Contracted Braille"
+msgstr "縮寫點字"
+
+#: Programs/config.c:967 Programs/menu_prefs.c:1395
+msgid "Contraction Table"
+msgstr "縮寫表"
+
+#: Programs/brltty-ctb.c:62
+msgid "Contraction table."
+msgstr "縮寫表"
+
+#: Programs/brltty-ctb.c:76
+msgid "Contraction verification table."
+msgstr "縮寫檢查表"
+
+#: Programs/menu_prefs.c:1488
+msgid "Critical"
+msgstr "關鍵"
+
+#: Programs/menu_prefs.c:546
+msgid "Cursor Column"
+msgstr "游標欄位"
+
+#: Programs/menu_prefs.c:545 Programs/menu_prefs.c:557
+msgid "Cursor Coordinates"
+msgstr "游標座標"
+
+#: Programs/log.c:100
+msgid "Cursor Routing"
+msgstr "游標定位"
+
+#: Programs/menu_prefs.c:547
+msgid "Cursor Row"
+msgstr "游標行"
+
+#: Programs/log.c:94
+msgid "Cursor Tracking"
+msgstr "游標追蹤"
+
+#: Programs/menu_prefs.c:904
+msgid "Cursor Tracking Delay"
+msgstr "游標追蹤延遲"
+
+#: Programs/menu_prefs.c:548 Programs/menu_prefs.c:559
+msgid "Cursor and Window Column"
+msgstr "游標與視窗欄位"
+
+#: Programs/menu_prefs.c:549 Programs/menu_prefs.c:560
+msgid "Cursor and Window Row"
+msgstr "游標與視窗行"
+
+#: Programs/menu_prefs.c:1320
+msgid "Dash"
+msgstr "聯字符號"
+
+#: Programs/menu_prefs.c:1313
+msgid "Date Format"
+msgstr "日期格式"
+
+#: Programs/menu_prefs.c:1302
+msgid "Date Position"
+msgstr "日期位置"
+
+#: Programs/menu_prefs.c:1325
+msgid "Date Separator"
+msgstr "日期分隔符號"
+
+#: Programs/menu_prefs.c:1310
+msgid "Day Month Year"
+msgstr "日 月 年"
+
+#: Programs/menu_prefs.c:1493
+msgid "Debug"
+msgstr "除錯"
+
+#: Programs/config.c:494
+msgid "Device for accessing braille display."
+msgstr "處理點字顯示的設備"
+
+#: Programs/config.c:453
+msgid "Disable the application programming interface."
+msgstr "取消程式設計界面"
+
+#. xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+#: Programs/midi.c:173
+msgid "Distortion Guitar"
+msgstr "噪音吉他"
+
+#: Programs/config.c:596
+msgid "Do not autospeak when braille is not being used."
+msgstr "點字未啟用十不自動朗讀"
+
+#: Programs/xbrlapi.c:112
+msgid "Do not write any text to the braille device"
+msgstr "不寫入任何文字到點字設備"
+
+#: Programs/brltty-trtxt.c:79
+msgid "Don't fall back to the Unicode base character."
+msgstr "不返回 Unicode 字元"
+
+#: Programs/config.c:364
+msgid "Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc)."
+msgstr "不切換為未授權的使用者或放棄權限(群組成員身份、功能等等)"
+
+#: Programs/alert.c:52
+msgid "Done"
+msgstr "完成"
+
+#: Programs/menu_prefs.c:1283 Programs/menu_prefs.c:1322
+msgid "Dot"
+msgstr "點"
+
+#: Programs/menu_prefs.c:942
+msgid "Dots via Unicode Braille"
+msgstr "Unicode 點字碼的點"
+
+#. Organ
+#. xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+#: Programs/midi.c:130
+msgid "Drawbar Organ"
+msgstr "拉把風琴"
+
+#: Programs/config.c:2916 Programs/menu_prefs.c:1469
+msgid "Drivers Directory"
+msgstr "驅動程式目錄"
+
+#. xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+#: Programs/midi.c:126
+msgid "Dulcimer"
+msgstr "洋琴"
+
+#: Programs/menu_prefs.c:879
+msgid "Eager Sliding Braille Window"
+msgstr "即時拉動點字視窗"
+
+#: Programs/brltty-ttb.c:158
+msgid "Edit table."
+msgstr "編輯表"
+
+#. xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+#: Programs/midi.c:183
+msgid "Electric Bass (finger)"
+msgstr "電被私(指彈)"
+
+# Elektronischer Bass (Pick)
+#. xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+#: Programs/midi.c:186
+msgid "Electric Bass (pick)"
+msgstr "電被私(玻片)"
+
+#. xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+#: Programs/midi.c:86
+msgid "Electric Grand Piano"
+msgstr "電鋼琴"
+
+#. xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+#: Programs/midi.c:164
+msgid "Electric Guitar (clean)"
+msgstr "電吉他(清澈)"
+
+#. xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+#: Programs/midi.c:161
+msgid "Electric Guitar (jazz)"
+msgstr "電吉他(爵士)"
+
+#. xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+#: Programs/midi.c:167
+msgid "Electric Guitar (muted)"
+msgstr "電吉他(悶音)"
+
+#. xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+#: Programs/midi.c:92
+msgid "Electric Piano 1"
+msgstr "電鋼琴 1"
+
+#. xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+#: Programs/midi.c:95
+msgid "Electric Piano 2"
+msgstr "電鋼琴 2"
+
+#: Programs/menu_prefs.c:1486
+msgid "Emergency"
+msgstr "緊急"
+
+#: Programs/menu_prefs.c:541
+msgid "End"
+msgstr "結尾"
+
+#: Programs/menu_prefs.c:865
+msgid "End of Line"
+msgstr "行尾"
+
+#. xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+#: Programs/midi.c:295
+msgid "English Horn"
+msgstr "英國管"
+
+#: Programs/menu_prefs.c:1178
+msgid "Enqueue"
+msgstr "排隊"
+
+#. xgettext: This is the name of MIDI musical instrument group #7.
+#: Programs/midi.c:43
+msgid "Ensemble"
+msgstr "弦樂組"
+
+#: Programs/menu_prefs.c:1489
+msgid "Error"
+msgstr "錯誤"
+
+#. xgettext: This is the name of MIDI musical instrument group #14.
+#: Programs/midi.c:68
+msgid "Ethnic Instruments"
+msgstr "民族樂器"
+
+#: Programs/menu_prefs.c:1032
+msgid "Event Alerts"
+msgstr "事件警示"
+
+#: Programs/menu_prefs.c:713
+msgid "Expand Current Word"
+msgstr "展開目前單字"
+
+#: Programs/config.c:408
+msgid "Explicit preference settings."
+msgstr "顯示偏好設定"
+
+#: Programs/menu_prefs.c:1059
+msgid "FM"
+msgstr "FM"
+
+#: Programs/menu_prefs.c:1097
+msgid "FM Volume"
+msgstr "FM 音量"
+
+#. Synth FM
+#. xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+#: Programs/midi.c:380
+msgid "FX 1 (rain)"
+msgstr "FX 1 (雨聲)"
+
+#. xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+#: Programs/midi.c:383
+msgid "FX 2 (soundtrack)"
+msgstr "FX 2 (音軌)"
+
+#. xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+#: Programs/midi.c:386
+msgid "FX 3 (crystal)"
+msgstr "FX 3 (水晶)"
+
+#. xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+#: Programs/midi.c:389
+msgid "FX 4 (atmosphere)"
+msgstr "FX 4 (原子)"
+
+#. xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+#: Programs/midi.c:392
+msgid "FX 5 (brightness)"
+msgstr "FX 5 (明亮)"
+
+#. xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+#: Programs/midi.c:395
+msgid "FX 6 (goblins)"
+msgstr "FX 6 (小妖)"
+
+#. xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+#: Programs/midi.c:398
+msgid "FX 7 (echoes)"
+msgstr "FX 7 (回音)"
+
+#. xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+#. xgettext: (sci-fi is a common short form for science fiction)
+#: Programs/midi.c:402
+msgid "FX 8 (sci-fi)"
+msgstr "FX 8 (科幻)"
+
+#. xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+#: Programs/midi.c:424
+msgid "Fiddle"
+msgstr "提琴"
+
+#. xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+#: Programs/midi.c:308
+msgid "Flute"
+msgstr "長笛"
+
+#: Programs/brltty-ctb.c:96
+msgid "Force immediate output."
+msgstr "強迫立即輸出"
+
+#: Programs/brltty-cldr.c:42
+msgid "Format of each output line."
+msgstr "每一輸出行的格式"
+
+#: Programs/brltty-ttb.c:165
+msgid "Format of input file."
+msgstr "輸入檔案格式"
+
+#: Programs/brltty-ttb.c:172
+msgid "Format of output file."
+msgstr "輸出檔案格式"
+
+#. xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+#: Programs/midi.c:267
+msgid "French Horn"
+msgstr "法國號"
+
+#. xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+#: Programs/midi.c:189
+msgid "Fretless Bass"
+msgstr "純被私"
+
+#: Programs/alert.c:97
+msgid "Frozen"
+msgstr "凍結"
+
+#: Programs/menu_prefs.c:556
+msgid "Generic"
+msgstr "一般"
+
+#: Programs/log.c:64
+msgid "Generic Input"
+msgstr "一般輸入"
+
+#. xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+#: Programs/midi.c:108
+msgid "Glockenspiel"
+msgstr "鐘琴"
+
+#. xgettext: This is the name of MIDI musical instrument group #4.
+#: Programs/midi.c:34
+msgid "Guitar"
+msgstr "吉他"
+
+#. Sound Effects
+#. xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+#: Programs/midi.c:456
+msgid "Guitar Fret Noise"
+msgstr "吉他換把聲"
+
+#. xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+#: Programs/midi.c:176
+msgid "Guitar Harmonics"
+msgstr "吉他和絃"
+
+#. xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+#: Programs/midi.c:477
+msgid "Gunshot"
+msgstr "槍聲"
+
+#. xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+#: Programs/midi.c:148
+msgid "Harmonica"
+msgstr "口琴"
+
+#. xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+#: Programs/midi.c:98
+msgid "Harpsichord"
+msgstr "大鍵琴"
+
+#. xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+#: Programs/midi.c:471
+msgid "Helicopter"
+msgstr "直升機"
+
+#: Programs/scr_help.c:215
+msgid "Help Screen"
+msgstr "說明畫面"
+
+#: Programs/menu_prefs.c:748 Programs/menu_prefs.c:1013
+msgid "High"
+msgstr "高"
+
+#: Programs/menu_prefs.c:921
+msgid "Highlight Braille Window Location"
+msgstr "反白點字視窗位置"
+
+#. xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+#: Programs/midi.c:89
+msgid "Honkytonk Piano"
+msgstr "酒吧鋼琴"
+
+#: Programs/menu_prefs.c:1177
+msgid "Immediate"
+msgstr "立刻"
+
+#: Programs/xbrlapi.c:665
+msgid "Incompatible XKB library\n"
+msgstr "不相容的 XKB 含式庫\n"
+
+#: Programs/xbrlapi.c:667
+msgid "Incompatible XKB server support\n"
+msgstr "不相容的 XKB 服務支援\n"
+
+#: Programs/menu_prefs.c:1492
+msgid "Information"
+msgstr "資訊"
+
+#: Programs/menu_prefs.c:956
+msgid "Input Options"
+msgstr "輸入選向"
+
+#: Programs/log.c:70
+msgid "Input Packets"
+msgstr "輸入風包"
+
+#: Programs/config.c:339
+#, c-format
+msgid "Install the %s service, and then exit."
+msgstr "安裝 %s 服務然後退出"
+
+#: Programs/menu_prefs.c:1496
+msgid "Internal Parameters"
+msgstr "內部參數"
+
+#: Drivers/Screen/Android/screen.c:197
+msgid "Java class not found"
+msgstr "沒有 Java 類別"
+
+#: Drivers/Screen/Android/screen.c:181
+msgid "Java exception occurred"
+msgstr "發生 Java 錯誤"
+
+#: Drivers/Screen/Android/screen.c:194
+msgid "Java method not found"
+msgstr "沒有 Java 函數"
+
+#. xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+#: Programs/midi.c:418
+msgid "Kalimba"
+msgstr "卡淋巴"
+
+#: Programs/config.c:1649
+msgid "Key Bindings"
+msgstr "按鍵榜定"
+
+#: Programs/config.c:1177
+msgid "Key Help"
+msgstr "按鍵說明"
+
+#: Programs/config.c:1654 Programs/ktb_list.c:737
+msgid "Key Table"
+msgstr "按鍵表"
+
+#: Programs/menu_prefs.c:935
+msgid "Keyboard Enabled"
+msgstr "鍵盤啟用"
+
+#: Programs/log.c:88
+msgid "Keyboard Key Events"
+msgstr "鍵盤按鍵事件"
+
+#: Programs/menu_prefs.c:1042
+msgid "Keyboard LED Alerts"
+msgstr "鍵盤燈號警示"
+
+#: Programs/config.c:1262 Programs/menu_prefs.c:1024
+msgid "Keyboard Table"
+msgstr "按鍵表"
+
+#. xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+#: Programs/midi.c:415
+msgid "Koto"
+msgstr "日本十三弦箏"
+
+#: Programs/config.c:3052
+msgid "Language"
+msgstr "語言"
+
+#. Synth Lead
+#. xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+#: Programs/midi.c:330
+msgid "Lead 1 (square)"
+msgstr "Lead 1 (方波)"
+
+#. xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+#: Programs/midi.c:333
+msgid "Lead 2 (sawtooth)"
+msgstr "Lead 2 (鋸齒)"
+
+#. xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+#: Programs/midi.c:336
+msgid "Lead 3 (calliope)"
+msgstr "Lead 3 (氣笛風琴)"
+
+#. xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+#: Programs/midi.c:339
+msgid "Lead 4 (chiff)"
+msgstr "Lead 4 (chiff)"
+
+#. xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+#: Programs/midi.c:342
+msgid "Lead 5 (charang)"
+msgstr "Lead 5 (charang)"
+
+#. xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+#: Programs/midi.c:345
+msgid "Lead 6 (voice)"
+msgstr "Lead 6 (人聲)"
+
+#. xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+#: Programs/midi.c:348
+msgid "Lead 7 (fifths)"
+msgstr "Lead 7 (fifths)"
+
+#. xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+#: Programs/midi.c:351
+msgid "Lead 8 (bass + lead)"
+msgstr "Lead 8 (bass + lead)"
+
+#: Programs/learn.c:101
+msgid "Learn Mode"
+msgstr "學習模式"
+
+#: Programs/menu_prefs.c:1337
+msgid "Left"
+msgstr "左"
+
+#: Programs/brltty-ktb.c:51
+msgid "List key names."
+msgstr "列出按鍵名稱"
+
+#: Programs/brltty-ktb.c:57
+msgid "List key table in help screen format."
+msgstr "以說明畫面格式列出按鍵表"
+
+#: Programs/brltty-ktb.c:63
+msgid "List key table in reStructuredText format."
+msgstr "以重新架構文字格式列出按鍵表"
+
+#: Programs/menu_prefs.c:1479
+msgid "Locale Directory"
+msgstr "本土化目錄"
+
+#: Programs/menu_prefs.c:1515
+msgid "Log Categories"
+msgstr "紀錄分類"
+
+#: Programs/config.c:834
+msgid "Log Level"
+msgstr "記錄等級"
+
+#: Programs/menu_prefs.c:1576
+msgid "Log Messages"
+msgstr "記錄訊息"
+
+#: Programs/config.c:686
+msgid "Log the versions of the core, API, and built-in drivers, and then exit."
+msgstr "記錄核心、API, 與內見驅動程式等的版本然後退出"
+
+#: Programs/config.c:651
+msgid "Log to standard error rather than to the system log."
+msgstr "輸出到標準錯誤而非系統記錄"
+
+#: Programs/config.c:665
+#, c-format
+msgid "Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)"
+msgstr "啟用紀錄等級 (%s 或 {%s} 之一)和/或紀錄分類(任何 {%s} 的組合或每個以 %s 開頭的都可停用)"
+
+#: Programs/menu_prefs.c:978
+msgid "Long Press Time"
+msgstr "長按時間"
+
+#: Programs/menu_prefs.c:746 Programs/menu_prefs.c:1011
+msgid "Low"
+msgstr "低"
+
+#: Programs/menu_prefs.c:666
+msgid "Lower Left Dot"
+msgstr "左下方的點"
+
+#: Programs/menu_prefs.c:667
+msgid "Lower Right Dot"
+msgstr "右下方的點"
+
+#: Programs/menu_prefs.c:1058
+msgid "MIDI"
+msgstr "MIDI"
+
+#: Programs/config.c:635
+msgid "MIDI (Musical Instrument Digital Interface) device specifier."
+msgstr "MIDI (樂器數位界面)設備指定"
+
+#: Programs/menu_prefs.c:1088
+msgid "MIDI Instrument"
+msgstr "MIDI 樂器"
+
+#: Programs/menu_prefs.c:1078
+msgid "MIDI Volume"
+msgstr "MIDI 音量"
+
+#: Programs/menu_prefs.c:1439
+msgid "Mailing List"
+msgstr "郵件論壇"
+
+#. xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+#: Programs/midi.c:117
+msgid "Marimba"
+msgstr "碼淋巴"
+
+#: Programs/menu_prefs.c:749 Programs/menu_prefs.c:1014
+msgid "Maximum"
+msgstr "最大"
+
+#: Programs/brltty-ctb.c:90
+msgid "Maximum length of an output line."
+msgstr "一行顯示的最大長度"
+
+#: Programs/menu_prefs.c:747 Programs/menu_prefs.c:1012
+msgid "Medium"
+msgstr "中等"
+
+#. xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+#: Programs/midi.c:446
+msgid "Melodic Tom"
+msgstr "定音鼓"
+
+#: Programs/menu_prefs.c:679
+msgid "Menu Options"
+msgstr "選單選向"
+
+#: Programs/config.c:644
+msgid "Message hold timeout (in 10ms units)."
+msgstr "訊息保留時間(以 10ms 為單位)"
+
+#: Programs/config.c:837
+msgid "Messages Directory"
+msgstr "訊息目錄"
+
+#: Programs/config.c:836
+msgid "Messages Domain"
+msgstr "訊息種類"
+
+#: Programs/config.c:835
+msgid "Messages Locale"
+msgstr "訊息語言"
+
+#: Programs/menu_prefs.c:745 Programs/menu_prefs.c:1010
+msgid "Minimum"
+msgstr "最小"
+
+#: Programs/menu_prefs.c:1309
+msgid "Month Day Year"
+msgstr "月 日 年"
+
+#. xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+#: Programs/midi.c:111
+msgid "Music Box"
+msgstr "音樂盒"
+
+#: Programs/menu_prefs.c:1058
+msgid "Musical Instrument Digital Interface"
+msgstr "樂器數位界面"
+
+#. xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+#: Programs/midi.c:264
+msgid "Muted Trumpet"
+msgstr "加若音器的小喇叭"
+
+#: Programs/config.c:544
+msgid "Name of or path to attributes table."
+msgstr "屬性表的名稱或路徑"
+
+#: Programs/config.c:536
+msgid "Name of or path to contraction table."
+msgstr "縮寫表的名稱或路徑"
+
+#: Programs/config.c:400
+msgid "Name of or path to default preferences file."
+msgstr "預設偏好設定檔的名稱或路徑"
+
+#: Programs/config.c:553
+msgid "Name of or path to keyboard table."
+msgstr "按鍵表的名稱或路徑"
+
+#: Programs/config.c:589
+msgid "Name of or path to speech input object."
+msgstr "語音輸入物件的名稱或路徑"
+
+#: Programs/config.c:526
+#, c-format
+msgid "Name of or path to text table (or %s)."
+msgstr "文字表 (或 %s) 的名稱或路徑"
+
+#: Programs/menu_prefs.c:845
+msgid "Navigation Options"
+msgstr "導覽選向"
+
+#: Programs/menu.c:523
+msgid "No"
+msgstr "否"
+
+#: Programs/menu_prefs.c:720
+msgid "No Capitalization"
+msgstr "沒有大寫"
+
+#: Programs/menu_prefs.c:897 Programs/menu_prefs.c:1141
+#: Programs/menu_prefs.c:1154 Programs/menu_prefs.c:1167
+#: Programs/menu_prefs.c:1297 Programs/menu_prefs.c:1336
+#: Programs/menu_prefs.c:1356
+msgid "None"
+msgstr "沒有"
+
+#: Programs/menu_prefs.c:1491
+msgid "Notice"
+msgstr "注意"
+
+#. xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+#: Programs/midi.c:292
+msgid "Oboe"
+msgstr "雙簧管"
+
+#. xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+#: Programs/midi.c:326
+msgid "Ocarina"
+msgstr "陶笛"
+
+#: Programs/menu_prefs.c:960
+msgid "Off"
+msgstr "關"
+
+#: Programs/config.c:1740
+msgid "Old Preferences File"
+msgstr "舊的偏好設定檔"
+
+#: Programs/menu_prefs.c:973
+msgid "On First Release"
+msgstr "第一個按鍵放開"
+
+#. xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+#: Programs/midi.c:251
+msgid "Orchestra Hit"
+msgstr "管絃樂其驟"
+
+#. xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+#: Programs/midi.c:223
+msgid "Orchestral Harp"
+msgstr "豎琴"
+
+#. xgettext: This is the name of MIDI musical instrument group #3.
+#: Programs/midi.c:31
+msgid "Organ"
+msgstr "管風琴"
+
+#: Programs/log.c:76
+msgid "Output Packets"
+msgstr "輸出風包"
+
+#. xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+#: Programs/midi.c:170
+msgid "Overdriven Guitar"
+msgstr "超音吉他"
+
+#: Drivers/Braille/Iris/braille.c:1545
+msgid "PC mode"
+msgstr "PC 模式"
+
+#: Programs/menu_prefs.c:1057
+msgid "PCM"
+msgstr "PCM"
+
+#: Programs/config.c:625
+msgid "PCM (soundcard digital audio) device specifier."
+msgstr "PCM (音效卡數位音效)設備指定"
+
+#: Programs/menu_prefs.c:1070
+msgid "PCM Volume"
+msgstr "PCM 音量"
+
+#. xgettext: This is the description of the PASSPS2 command.
+#: Programs/cmds.auto.h:1487
+msgid "PS/2 (set 3) keyboard scan code"
+msgstr "PS/2 (set 3) 鍵盤掃描碼"
+
+#: Programs/menu_prefs.c:1429
+msgid "Package Revision"
+msgstr "套件修訂版本"
+
+#: Programs/menu_prefs.c:1424
+msgid "Package Version"
+msgstr "套件版本"
+
+#. Synth Pad
+#. xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+#: Programs/midi.c:355
+msgid "Pad 1 (new age)"
+msgstr "pad 1 (新世紀)"
+
+#. xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+#: Programs/midi.c:358
+msgid "Pad 2 (warm)"
+msgstr "pad 2 (溫暖)"
+
+#. xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+#: Programs/midi.c:361
+msgid "Pad 3 (polysynth)"
+msgstr "pad 3 (多音合成)"
+
+#. xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+#: Programs/midi.c:364
+msgid "Pad 4 (choir)"
+msgstr "pad 4 (合唱)"
+
+#. xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+#: Programs/midi.c:367
+msgid "Pad 5 (bowed)"
+msgstr "pad 5 (bowed)"
+
+#. xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+#: Programs/midi.c:370
+msgid "Pad 6 (metallic)"
+msgstr "pad 6 (金屬)"
+
+#. xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+#: Programs/midi.c:373
+msgid "Pad 7 (halo)"
+msgstr "pad 7 (halo)"
+
+#. xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+#: Programs/midi.c:376
+msgid "Pad 8 (sweep)"
+msgstr "pad 8 (Sweep)"
+
+#. xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+#: Programs/midi.c:314
+msgid "Pan Flute"
+msgstr "排笛"
+
+#: Programs/config.c:462
+msgid "Parameters for the application programming interface."
+msgstr "程式設計界面參數"
+
+#: Programs/config.c:484
+msgid "Parameters for the braille driver."
+msgstr "點字驅動參數"
+
+#: Programs/config.c:357
+msgid "Parameters for the privilege establishment stage."
+msgstr "權限建立階段的參數"
+
+#: Programs/config.c:616
+msgid "Parameters for the screen driver."
+msgstr "螢幕驅動參數"
+
+#: Programs/config.c:581
+msgid "Parameters for the speech driver."
+msgstr "語音驅動參數"
+
+#: Programs/config.c:391
+msgid "Path to default settings file."
+msgstr "預設設定檔案路徑"
+
+#: Programs/config.c:445
+msgid "Path to directory containing drivers."
+msgstr "包含驅動的目錄路徑"
+
+#: Programs/brltest.c:68 Programs/brltty-atb.c:36 Programs/brltty-ctb.c:54
+#: Programs/brltty-ktb.c:73 Programs/config.c:516
+msgid "Path to directory containing tables."
+msgstr "對照表所在目錄路徑"
+
+#: Programs/brltty-ttb.c:152
+msgid "Path to directory containing text tables."
+msgstr "包含文字表的目錄路徑"
+
+#: Programs/brltty-ktb.c:83
+msgid "Path to directory for loading drivers."
+msgstr "驅動程式所在目錄路徑"
+
+#: Programs/brltty-trtxt.c:51
+msgid "Path to directory for text tables."
+msgstr "文字表所在目錄路徑"
+
+#: Programs/brltest.c:78 Programs/config.c:435
+msgid "Path to directory which can be written to."
+msgstr "可寫入的目錄路徑"
+
+#: Programs/config.c:425
+msgid "Path to directory which contains files that can be updated."
+msgstr "包含可升級檔案的目錄路徑"
+
+#: Programs/config.c:325
+msgid "Path to directory which contains message localizations."
+msgstr "包含訊息本地化的目錄路徑。"
+
+#: Programs/brltty-trtxt.c:59
+msgid "Path to input text table."
+msgstr "輸入文字表路徑"
+
+#: Programs/config.c:674
+msgid "Path to log file."
+msgstr "記錄檔路徑"
+
+#: Programs/brltty-trtxt.c:67
+msgid "Path to output text table."
+msgstr "輸出文字表路徑"
+
+#: Programs/config.c:381
+msgid "Path to process identifier file."
+msgstr "處理程序代號檔的路徑"
+
+#: Programs/config.c:415
+msgid "Patterns that match command prompts."
+msgstr "符合命令提示的樣式"
+
+#. xgettext: This is the name of MIDI musical instrument group #15.
+#: Programs/midi.c:71
+msgid "Percussive Instruments"
+msgstr "打擊樂器"
+
+#. xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+#: Programs/midi.c:133
+msgid "Percussive Organ"
+msgstr "打擊管風琴"
+
+#. xgettext: This is the name of MIDI musical instrument group #1.
+#: Programs/midi.c:25
+msgid "Piano"
+msgstr "鋼琴"
+
+#. Pipe
+#. xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+#: Programs/midi.c:305
+msgid "Piccolo"
+msgstr "短笛"
+
+#. xgettext: This is the name of MIDI musical instrument group #10.
+#: Programs/midi.c:52
+msgid "Pipe"
+msgstr "管線"
+
+#. xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+#: Programs/midi.c:220
+msgid "Pizzicato Strings"
+msgstr "玻弦"
+
+#: Drivers/Braille/Baum/braille.c:1158
+msgid "Powerdown"
+msgstr "降壓"
+
+#: Programs/config.c:2896 Programs/menu_prefs.c:1459
+msgid "Preferences File"
+msgstr "偏好設定檔"
+
+#: Programs/scr_menu.c:271
+msgid "Preferences Menu"
+msgstr "偏好設定"
+
+#: Headers/options.h:75
+msgid "Print a usage summary (all options), and then exit."
+msgstr "印出用法摘要(所有選向)alle Optionen) 然後退出"
+
+#: Headers/options.h:70
+msgid "Print a usage summary (commonly used options only), and then exit."
+msgstr "印出用法摘要(只有共用選向)然後跳出"
+
+#: Programs/config.c:788
+msgid "Privilege Parameter"
+msgstr "權限參數"
+
+#: Programs/menu_prefs.c:1410
+msgid "Profiles"
+msgstr "設定檔"
+
+#: Programs/config.c:561
+msgid "Properties of eligible keyboards."
+msgstr "合格鍵盤的屬性"
+
+#: Programs/menu_prefs.c:950
+msgid "Quick Space"
+msgstr "空白"
+
+#: Programs/menu_prefs.c:1158
+msgid "Raise Pitch"
+msgstr "提高音調"
+
+#: Programs/config.c:316
+msgid "Recognize environment variables."
+msgstr "認識環境變數"
+
+#. xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+#: Programs/midi.c:311
+msgid "Recorder"
+msgstr "錄音機"
+
+#. xgettext: This is the name of MIDI musical instrument group #9.
+#: Programs/midi.c:49
+msgid "Reed"
+msgstr "簧片"
+
+#. xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+#: Programs/midi.c:142
+msgid "Reed Organ"
+msgstr "簧片風琴"
+
+#: Programs/brltty-ctb.c:82
+msgid "Reformat input."
+msgstr "重新格式輸入"
+
+#: Programs/config.c:506
+msgid "Release braille device when screen or window is unreadable."
+msgstr "當螢幕或視窗無法閱讀時釋放點字設備"
+
+#: Programs/xbrlapi.c:106
+msgid "Remain a foreground process"
+msgstr "保留前景處理程序"
+
+#: Programs/config.c:332
+msgid "Remain a foreground process."
+msgstr "保留前景處理程序"
+
+#: Programs/brltty-trtxt.c:73
+msgid "Remove dots seven and eight."
+msgstr "宜除七八點"
+
+#: Programs/config.c:347
+#, c-format
+msgid "Remove the %s service, and then exit."
+msgstr "宜除 %s 服務然後退出"
+
+#: Programs/brltty-ktb.c:45
+msgid "Report problems with the key table."
+msgstr "回報按鍵表問題"
+
+#: Programs/brltty-ttb.c:186
+msgid "Report the characters within the current screen font that aren't defined within the text table."
+msgstr "回報目前螢幕字形李的字元在文字表李沒有定義"
+
+#: Programs/menu_prefs.c:866
+msgid "Rest of Line"
+msgstr "盛夏的行"
+
+#: Programs/menu_prefs.c:1558
+msgid "Restart Braille Driver"
+msgstr "重新啟動點字驅動"
+
+#: Programs/menu_prefs.c:1570
+msgid "Restart Screen Driver"
+msgstr "重新啟動螢幕驅動"
+
+#: Programs/menu_prefs.c:1564
+msgid "Restart Speech Driver"
+msgstr "重新啟動語音驅動"
+
+#. xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+#: Programs/midi.c:452
+msgid "Reverse Cymbal"
+msgstr "Reverse Cymbal"
+
+#: Programs/menu_prefs.c:1338
+msgid "Right"
+msgstr "右"
+
+#. xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+#: Programs/midi.c:136
+msgid "Rock Organ"
+msgstr "搖滾管風琴"
+
+#: Programs/menu_prefs.c:674
+msgid "Save on Exit"
+msgstr "退出十存檔"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/menu_prefs.c:1157
+msgid "Say Cap"
+msgstr "說大寫"
+
+#: Programs/menu_prefs.c:1181
+msgid "Say Line Mode"
+msgstr "說行模式"
+
+#: Programs/menu_prefs.c:1168
+msgid "Say Space"
+msgstr "說空白"
+
+#: Programs/menu_prefs.c:780
+msgid "Screen Cursor Blink Period"
+msgstr "螢幕游標跳動週期"
+
+#: Programs/menu_prefs.c:788
+msgid "Screen Cursor Percent Visible"
+msgstr "可見螢幕游標百分比"
+
+#: Programs/menu_prefs.c:768
+msgid "Screen Cursor Style"
+msgstr "螢幕游標形狀"
+
+#: Programs/config.c:2393
+msgid "Screen Driver"
+msgstr "螢幕驅動"
+
+#: Programs/log.c:160
+msgid "Screen Driver Events"
+msgstr "螢幕驅動事件"
+
+#: Programs/menu_prefs.c:550
+msgid "Screen Number"
+msgstr "螢幕編號"
+
+#: Programs/config.c:2399
+msgid "Screen Parameter"
+msgstr "螢幕參數"
+
+#: Programs/config.c:606
+#, c-format
+msgid "Screen driver code (%s, %s, or one of {%s})."
+msgstr "螢幕驅動碼: (%s, %s, 或 {%s} 之一"
+
+#: Programs/menu_prefs.c:891
+msgid "Scroll-aware Cursor Navigation"
+msgstr "滾動式游標導覽"
+
+#. xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+#: Programs/midi.c:462
+msgid "Seashore"
+msgstr "海浪"
+
+#: Programs/log.c:130
+msgid "Serial I/O"
+msgstr "序列輸入/輸出"
+
+#: Programs/log.c:124
+msgid "Server Events"
+msgstr "服務程序事件"
+
+#. xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+#: Programs/midi.c:320
+msgid "Shakuhachi"
+msgstr "尺八"
+
+#. xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+#: Programs/midi.c:412
+msgid "Shamisen"
+msgstr "三弦"
+
+#. xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+#: Programs/midi.c:427
+msgid "Shanai"
+msgstr "山奈琴"
+
+#: Programs/menu_prefs.c:687
+msgid "Show Advanced Submenus"
+msgstr "顯示進階子選單"
+
+#: Programs/menu_prefs.c:692
+msgid "Show All Items"
+msgstr "顯示所有項目"
+
+#: Programs/menu_prefs.c:796
+msgid "Show Attributes"
+msgstr "顯示屬性"
+
+#: Programs/menu_prefs.c:763
+msgid "Show Screen Cursor"
+msgstr "顯示螢幕游標"
+
+#: Programs/menu_prefs.c:1291
+msgid "Show Seconds"
+msgstr "顯示秒數"
+
+#: Programs/menu_prefs.c:1233
+msgid "Show Speech Cursor"
+msgstr "顯示語音游標"
+
+#: Programs/menu_prefs.c:682
+msgid "Show Submenu Sizes"
+msgstr "顯示子選單項數"
+
+#. Ethnic Instruments
+#. xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+#: Programs/midi.c:406
+msgid "Sitar"
+msgstr "西塔琴"
+
+#: Programs/menu_prefs.c:858
+msgid "Skip Blank Braille Windows"
+msgstr "跳過空白點字視窗"
+
+#: Programs/menu_prefs.c:852
+msgid "Skip Identical Lines"
+msgstr "跳過相同的行"
+
+#: Programs/menu_prefs.c:869
+msgid "Skip Which Blank Braille Windows"
+msgstr "跳過何種空白點字視窗"
+
+#. xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+#: Programs/midi.c:192
+msgid "Slap Bass 1"
+msgstr "Slap Bass 1"
+
+#. xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+#: Programs/midi.c:195
+msgid "Slap Bass 2"
+msgstr "Slap Bass 2"
+
+#: Programs/menu_prefs.c:1321
+msgid "Slash"
+msgstr "削減"
+
+#: Programs/menu_prefs.c:874
+msgid "Sliding Braille Window"
+msgstr "滑動點字視窗"
+
+#: Programs/menu_prefs.c:1142
+msgid "Some"
+msgstr "一些"
+
+#. Reed
+#. xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+#: Programs/midi.c:280
+msgid "Soprano Saxophone"
+msgstr "高音薩克斯風"
+
+#. xgettext: This is the name of MIDI musical instrument group #16.
+#: Programs/midi.c:74
+msgid "Sound Effects"
+msgstr "音效"
+
+#: Programs/menu_prefs.c:561 Programs/menu_prefs.c:1357
+msgid "Space"
+msgstr "空白"
+
+#: Programs/menu_prefs.c:1221
+msgid "Speak Completed Words"
+msgstr "朗讀完整的單字"
+
+#: Programs/menu_prefs.c:1209
+msgid "Speak Deleted Characters"
+msgstr "朗讀刪除的字元"
+
+#: Programs/menu_prefs.c:1203
+msgid "Speak Inserted Characters"
+msgstr "朗讀插入的字元"
+
+#: Programs/menu_prefs.c:1227
+msgid "Speak Line Indent"
+msgstr "朗讀內縮行"
+
+#: Programs/menu_prefs.c:1215
+msgid "Speak Replaced Characters"
+msgstr "朗讀取代的字元"
+
+#: Programs/menu_prefs.c:1197
+msgid "Speak Selected Character"
+msgstr "朗讀選取的字元"
+
+#: Programs/menu_prefs.c:1191
+msgid "Speak Selected Line"
+msgstr "朗讀選取的行"
+
+#: Programs/menu_prefs.c:1250
+msgid "Speech Cursor Blink Period"
+msgstr "語音游標跳動週期"
+
+#: Programs/menu_prefs.c:1258
+msgid "Speech Cursor Percent Visible"
+msgstr "可見語音游標百分比"
+
+#: Programs/menu_prefs.c:1238
+msgid "Speech Cursor Style"
+msgstr "語音游標形狀"
+
+#: Programs/config.c:2168
+msgid "Speech Driver"
+msgstr "語音驅動"
+
+#: Programs/log.c:154
+msgid "Speech Driver Events"
+msgstr "語音驅動事件"
+
+#: Programs/log.c:112
+msgid "Speech Events"
+msgstr "語音事件"
+
+#. Create the file system object for speech input.
+#: Programs/config.c:2963
+msgid "Speech Input"
+msgstr "語音輸入"
+
+#: Programs/menu_prefs.c:1116
+msgid "Speech Options"
+msgstr "語音選向"
+
+#: Programs/config.c:2171
+msgid "Speech Parameter"
+msgstr "語音參數"
+
+#: Programs/menu_prefs.c:1133
+msgid "Speech Pitch"
+msgstr "語音音調"
+
+#: Programs/menu_prefs.c:1146
+msgid "Speech Punctuation"
+msgstr "朗讀標點"
+
+#: Programs/menu_prefs.c:1126
+msgid "Speech Rate"
+msgstr "語音速率"
+
+#: Programs/menu_prefs.c:1161
+msgid "Speech Uppercase Indicator"
+msgstr "說出大寫符號"
+
+#: Programs/menu_prefs.c:1119
+msgid "Speech Volume"
+msgstr "語音音量"
+
+#: Programs/menu_prefs.c:1171
+msgid "Speech Whitespace Indicator"
+msgstr "說出空白記號"
+
+#: Programs/config.c:571
+#, c-format
+msgid "Speech driver code (%s, %s, or one of {%s})."
+msgstr "語音驅動碼: (%s, %s 或 {%s} 之一)"
+
+#: Programs/menu_prefs.c:1505
+msgid "Standard Error Log Level"
+msgstr "標準錯誤記錄等級"
+
+#: Programs/menu_prefs.c:926
+msgid "Start Selection with Routing Key"
+msgstr "用定位鍵開始選取"
+
+#: Programs/menu_prefs.c:551
+msgid "State Dots"
+msgstr "狀態點"
+
+#: Programs/menu_prefs.c:552
+msgid "State Letter"
+msgstr "狀態字母"
+
+#: Programs/menu_prefs.c:1332
+msgid "Status Cells"
+msgstr "狀態方"
+
+#: Programs/menu_prefs.c:1348
+msgid "Status Count"
+msgstr "狀態計數"
+
+#: Programs/menu_prefs.c:565
+msgid "Status Field"
+msgstr "狀態欄位"
+
+#: Programs/menu_prefs.c:1341
+msgid "Status Position"
+msgstr "狀態位置"
+
+#: Programs/menu_prefs.c:1363
+msgid "Status Separator"
+msgstr "狀態分隔符號"
+
+#: Programs/menu_prefs.c:1359
+msgid "Status Side"
+msgstr "狀態邊側"
+
+#. xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+#: Programs/midi.c:437
+msgid "Steel Drums"
+msgstr "鋼鼓"
+
+#: Programs/config.c:371
+#, c-format
+msgid "Stop an existing instance of %s, and then exit."
+msgstr "停止存在的 %s 程序然後退出"
+
+#. Ensemble
+#. xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+#: Programs/midi.c:230
+msgid "String Ensemble 1"
+msgstr "弦樂組 1"
+
+#. xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+#: Programs/midi.c:233
+msgid "String Ensemble 2"
+msgstr "弦樂組 2"
+
+#. xgettext: This is the name of MIDI musical instrument group #6.
+#: Programs/midi.c:40
+msgid "Strings"
+msgstr "弦樂"
+
+#: Programs/menu_prefs.c:722
+msgid "Superimpose Dot 7"
+msgstr "加上第七點"
+
+#: Programs/config.c:657
+msgid "Suppress start-up messages."
+msgstr "取消啟動訊息"
+
+#. xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+#: Programs/midi.c:198
+msgid "Synth Bass 1"
+msgstr "合成被司 1"
+
+#. xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+#: Programs/midi.c:201
+msgid "Synth Bass 2"
+msgstr "合成被司 2"
+
+#. xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+#: Programs/midi.c:449
+msgid "Synth Drum"
+msgstr "合成鼓"
+
+#. xgettext: This is the name of MIDI musical instrument group #13.
+#. xgettext: (synth is a common short form for synthesizer)
+#. xgettext: (FM is the acronym for Frequency Modulation)
+#: Programs/midi.c:65
+msgid "Synth FM"
+msgstr "合成 FM"
+
+#. xgettext: This is the name of MIDI musical instrument group #11.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:56
+msgid "Synth Lead"
+msgstr "合成 Lead"
+
+#. xgettext: This is the name of MIDI musical instrument group #12.
+#. xgettext: (synth is a common short form for synthesizer)
+#: Programs/midi.c:60
+msgid "Synth Pad"
+msgstr "合成 pad"
+
+#. xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+#: Programs/midi.c:248
+msgid "Synth Voice"
+msgstr "合成人聲"
+
+#. xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+#: Programs/midi.c:273
+msgid "SynthBrass 1"
+msgstr "合成銅管 1"
+
+#. xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+#: Programs/midi.c:276
+msgid "SynthBrass 2"
+msgstr "合成銅管 2"
+
+#. xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+#: Programs/midi.c:236
+msgid "SynthStrings 1"
+msgstr "合成弦樂 1"
+
+#. xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+#: Programs/midi.c:239
+msgid "SynthStrings 2"
+msgstr "合成弦樂 2"
+
+#: Programs/menu_prefs.c:1500
+msgid "System Log Level"
+msgstr "系統記錄等級"
+
+#: Programs/config.c:2917 Programs/menu_prefs.c:1474
+msgid "Tables Directory"
+msgstr "對照表目錄"
+
+#. xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+#: Programs/midi.c:443
+msgid "Taiko Drum"
+msgstr "太鼓"
+
+#. xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+#: Programs/midi.c:151
+msgid "Tango Accordion"
+msgstr "探戈手風琴"
+
+#. xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+#: Programs/midi.c:468
+msgid "Telephone Ring"
+msgstr "電話鈴生"
+
+#. xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+#: Programs/midi.c:286
+msgid "Tenor Saxophone"
+msgstr "次中音薩克斯風"
+
+#: Programs/menu_prefs.c:760
+msgid "Text Indicators"
+msgstr "文字標記"
+
+#: Programs/menu_prefs.c:1360
+msgid "Text Side"
+msgstr "文字邊側"
+
+#: Programs/config.c:943 Programs/menu_prefs.c:1388
+msgid "Text Table"
+msgstr "文字表"
+
+#: Programs/brltty-ctb.c:69
+msgid "Text table."
+msgstr "文字表"
+
+#: Programs/config.c:302
+msgid "The text to be shown when the braille driver starts and to be spoken when the speech driver starts."
+msgstr "點字啟動顯示何語音啟動朗讀的文字"
+
+#: Programs/config.c:309
+msgid "The text to be shown when the braille driver stops."
+msgstr "點字停用顯示的文字"
+
+#: Programs/menu_prefs.c:553
+msgid "Time"
+msgstr "時間"
+
+#: Programs/menu_prefs.c:1276
+msgid "Time Format"
+msgstr "時間格式"
+
+#: Programs/menu_prefs.c:1268
+msgid "Time Presentation"
+msgstr "時間表示法"
+
+#: Programs/menu_prefs.c:1286
+msgid "Time Separator"
+msgstr "時間分隔符號"
+
+#. xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+#: Programs/midi.c:226
+msgid "Timpani"
+msgstr "定音鼓"
+
+#. Percussive Instruments
+#. xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+#: Programs/midi.c:431
+msgid "Tinkle Bell"
+msgstr "牛零"
+
+#: Programs/menu_prefs.c:1554
+msgid "Tools"
+msgstr "工具"
+
+#: Programs/menu_prefs.c:1003
+msgid "Touch Navigation"
+msgstr "觸控導覽"
+
+#: Programs/menu_prefs.c:1017
+msgid "Touch Sensitivity"
+msgstr "觸控靈敏度"
+
+#: Programs/menu_prefs.c:915
+msgid "Track Screen Pointer"
+msgstr "追蹤螢幕指標"
+
+#: Programs/menu_prefs.c:909
+msgid "Track Screen Scroll"
+msgstr "追蹤螢幕捲動"
+
+#: Programs/menu_prefs.c:941
+msgid "Translated via Text Table"
+msgstr "由文字表翻譯"
+
+#. xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+#: Programs/midi.c:217
+msgid "Tremolo Strings"
+msgstr "陣音弦樂"
+
+#. xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+#: Programs/midi.c:258
+msgid "Trombone"
+msgstr "伸縮喇叭"
+
+#. Brass
+#. xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+#: Programs/midi.c:255
+msgid "Trumpet"
+msgstr "小喇叭"
+
+#. xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+#: Programs/midi.c:261
+msgid "Tuba"
+msgstr "大號"
+
+#. xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+#: Programs/midi.c:123
+msgid "Tubular Bells"
+msgstr "管中"
+
+#: Programs/menu_prefs.c:1062
+msgid "Tune Device"
+msgstr "聲音設備"
+
+#: Programs/menu_prefs.c:945
+msgid "Typing Mode"
+msgstr "輸入模式"
+
+#: Programs/log.c:136
+msgid "USB I/O"
+msgstr "USB 輸入/輸出"
+
+#: Programs/menu_prefs.c:664
+msgid "Underline"
+msgstr "底線"
+
+#: Programs/alert.c:102
+msgid "Unfrozen"
+msgstr "解凍"
+
+#: Programs/config.c:2914 Programs/menu_prefs.c:1454
+msgid "Updatable Directory"
+msgstr "可更新的目錄"
+
+#: Programs/log.c:106
+msgid "Update Events"
+msgstr "更新事件"
+
+#: Programs/options.c:151
+msgid "Usage"
+msgstr "用法"
+
+#: Programs/menu_prefs.c:721
+msgid "Use Capital Sign"
+msgstr "使用大寫記號"
+
+#. xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+#: Programs/midi.c:114
+msgid "Vibraphone"
+msgstr "顫音"
+
+#. xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+#: Programs/midi.c:208
+msgid "Viola"
+msgstr "中提琴"
+
+#. Strings
+#. xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+#: Programs/midi.c:205
+msgid "Violin"
+msgstr "小提琴"
+
+#. xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+#: Programs/midi.c:245
+msgid "Voice Oohs"
+msgstr "人聲喔"
+
+#: Programs/menu_prefs.c:1490
+msgid "Warning"
+msgstr "警告"
+
+#: Programs/menu_prefs.c:1434
+msgid "Web Site"
+msgstr "網站"
+
+#. xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+#: Programs/midi.c:323
+msgid "Whistle"
+msgstr "口哨"
+
+#: Programs/menu_prefs.c:543
+msgid "Window Column"
+msgstr "視窗欄位"
+
+#: Programs/menu_prefs.c:542 Programs/menu_prefs.c:558
+msgid "Window Coordinates"
+msgstr "視窗座標"
+
+#: Programs/menu_prefs.c:544
+msgid "Window Row"
+msgstr "視窗行"
+
+#. xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+#: Programs/midi.c:440
+msgid "Woodblock"
+msgstr "木魚"
+
+#: Programs/menu_prefs.c:848
+msgid "Word Wrap"
+msgstr "段字"
+
+#: Programs/config.c:2888
+msgid "Working Directory"
+msgstr "工作目錄"
+
+#: Programs/config.c:2915 Programs/menu_prefs.c:1464
+msgid "Writable Directory"
+msgstr "可寫入的目錄"
+
+#: Programs/xbrlapi.c:118
+msgid "Write debugging output to stdout"
+msgstr "把除錯訊息顯示到標準輸出"
+
+#: Programs/config.c:680
+msgid "Write the start-up logs, and then exit."
+msgstr "寫好啟動記錄然後退出"
+
+#: Programs/xbrlapi.c:100
+msgid "X display to connect to"
+msgstr "X 顯示連接到"
+
+#: Programs/pgmprivs_linux.c:1951
+msgid "XDG runtime directory access problem"
+msgstr "XDG 執行時期目錄處理問題"
+
+#: Programs/pgmprivs_linux.c:1933
+msgid "XDG runtime directory created"
+msgstr "XDG 執行時期目錄建立"
+
+#: Programs/pgmprivs_linux.c:1928
+msgid "XDG runtime directory exists"
+msgstr "XDG 執行時期目錄存在"
+
+#: Programs/xbrlapi.c:786
+msgid "XFree(wm_name) for change"
+msgstr "XFree(wm_name) 變更"
+
+#. xgettext: This is the description of the PASSXT command.
+#: Programs/cmds.auto.h:1479
+msgid "XT (set 1) keyboard scan code"
+msgstr "XT (set 1) 鍵盤掃描碼"
+
+#. xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+#: Programs/midi.c:120
+msgid "Xylophone"
+msgstr "木琴"
+
+#: Programs/menu_prefs.c:1308
+msgid "Year Month Day"
+msgstr "年 月 日"
+
+#: Programs/menu.c:524
+msgid "Yes"
+msgstr "是"
+
+#: Programs/xbrlapi.c:84
+msgid "[host][:port]"
+msgstr "[Host][:Port]"
+
+#: Programs/menu_prefs.c:665
+msgid "all dots"
+msgstr "滿點"
+
+#: Programs/cmd_miscellaneous.c:104
+msgid "and"
+msgstr "與"
+
+#. xgettext: This is the description of the CLIP_APPEND command.
+#: Programs/cmds.auto.h:1346
+msgid "append characters to clipboard"
+msgstr "附加字元到剪貼不"
+
+#. xgettext: This is the description of the CLIP_ADD command.
+#: Programs/cmds.auto.h:1223
+msgid "append to clipboard from character"
+msgstr "從字元附加到剪貼不"
+
+#: Programs/cmd.c:290
+msgid "at cursor"
+msgstr "在游標"
+
+#. xgettext: This is the description of the KEY_BACKSPACE command.
+#: Programs/cmds.auto.h:1527
+msgid "backspace key"
+msgstr "倒除鍵"
+
+#: Drivers/Braille/Baum/braille.c:1149 Drivers/Braille/TSI/braille.c:869
+msgid "battery low"
+msgstr "電量低"
+
+#. xgettext: This is the description of the SELECTVT command.
+#: Programs/cmds.auto.h:1437
+msgid "bind to specific virtual terminal"
+msgstr "榜定到特定的虛擬終端機"
+
+#. xgettext: This is the description of the SELECTVT_NEXT command.
+#: Programs/cmds.auto.h:957
+msgid "bind to the next virtual terminal"
+msgstr "榜定到下一個虛擬終端機"
+
+#. xgettext: This is the description of the SELECTVT_PREV command.
+#: Programs/cmds.auto.h:950
+msgid "bind to the previous virtual terminal"
+msgstr "榜定到前一個虛擬終端機"
+
+#.
+#: Programs/cmd_utils.c:144
+msgid "black"
+msgstr "黑"
+
+#: Programs/core.c:1125
+msgid "blank line"
+msgstr "空白行"
+
+#: Programs/cmd_utils.c:173
+msgid "blinking"
+msgstr "跳動"
+
+#. B
+#: Programs/cmd_utils.c:145
+msgid "blue"
+msgstr "藍"
+
+#: Programs/config.c:2933
+#, c-format
+msgid "braille device not specified"
+msgstr "點字設備未指定"
+
+#. xgettext: This is the description of the OFFLINE command.
+#: Programs/cmds.auto.h:621
+msgid "braille display temporarily unavailable"
+msgstr "點字設備暫時無作用"
+
+#: Programs/config.c:1677
+msgid "braille driver initialization failed"
+msgstr "點字驅動出使化失敗"
+
+#: Programs/config.c:1756
+msgid "braille driver not loadable"
+msgstr "點字驅動無法載入"
+
+#: Programs/config.c:2017
+msgid "braille driver restarting"
+msgstr "點字驅動正在啟動"
+
+#: Programs/cmd_miscellaneous.c:163
+msgid "braille driver stopped"
+msgstr "點字停止"
+
+#: Drivers/Screen/Android/screen.c:189
+msgid "braille released"
+msgstr "點字停止"
+
+#. xgettext: This is the description of the ROUTE command.
+#: Programs/cmds.auto.h:1207
+msgid "bring screen cursor to character"
+msgstr "帶動螢幕游標到字元"
+
+#. xgettext: This is the description of the CSRJMP_VERT command.
+#: Programs/cmds.auto.h:593
+msgid "bring screen cursor to current line"
+msgstr "帶動螢幕游標到目前行"
+
+#. xgettext: This is the description of the ROUTE_LINE command.
+#: Programs/cmds.auto.h:1404
+msgid "bring screen cursor to line"
+msgstr "帶動螢幕游標到行"
+
+#. xgettext: This is the description of the ROUTE_CURR_LOCN command.
+#: Programs/cmds.auto.h:835
+msgid "bring screen cursor to speech cursor"
+msgstr "帶動螢幕游標到語音游標"
+
+#. RG
+#: Programs/cmd_utils.c:150
+msgid "brown"
+msgstr "棕"
+
+#: Drivers/Screen/Linux/screen.c:1432
+msgid "can't get console state"
+msgstr "無法取得主控台狀態"
+
+#: Drivers/Screen/Linux/screen.c:1510
+msgid "can't open console"
+msgstr "無法打開主控台"
+
+#: Drivers/Screen/Linux/screen.c:1556
+msgid "can't read screen content"
+msgstr "無法讀取螢幕內容"
+
+#: Drivers/Screen/Linux/screen.c:1601
+msgid "can't read screen header"
+msgstr "無法讀取畫面表頭"
+
+#: Programs/ctb_translate.c:393
+msgid "cannot access internal contraction table"
+msgstr "無法存取內部縮寫表"
+
+#: Programs/atb_translate.c:70
+msgid "cannot compile attributes table"
+msgstr "無法編譯屬性表"
+
+#: Programs/ctb_translate.c:387
+msgid "cannot compile contraction table"
+msgstr "無法編譯縮寫表"
+
+#: Programs/config.c:1660
+msgid "cannot compile key table"
+msgstr "無法編譯按鍵表"
+
+#: Programs/config.c:1224
+msgid "cannot compile keyboard table"
+msgstr "無法編譯鍵盤表"
+
+#: Programs/ttb_translate.c:275
+msgid "cannot compile text table"
+msgstr "無法編譯文字表"
+
+#. This is the first attempt to connect to BRLTTY, and it failed.
+#. * Return the error immediately to the user, to provide feedback to users
+#. * running xbrlapi by hand, but not fill logs, eat battery, spam
+#. * 127.0.0.1 with reconnection attempts.
+#.
+#: Programs/xbrlapi.c:204
+#, c-format
+msgid "cannot connect to braille devices daemon brltty at %s\n"
+msgstr "無法連接點字設備服務程序 brltty 於 %s\n"
+
+#: Programs/xbrlapi.c:654
+#, c-format
+msgid "cannot connect to display %s\n"
+msgstr "無法連接點顯器 %s\n"
+
+#: Programs/file.c:381
+msgid "cannot create directory"
+msgstr "無法建立目錄"
+
+#: Programs/program.c:150
+#, c-format
+msgid "cannot determine program directory"
+msgstr "無法定義程式目錄"
+
+#: Programs/config.c:2891 Programs/menu.c:618
+msgid "cannot determine working directory"
+msgstr "無法定義工作目錄"
+
+#: Programs/system_windows.c:61
+msgid "cannot find procedure"
+msgstr "無法找到程序"
+
+#: Programs/program.c:158
+msgid "cannot fix install path"
+msgstr "無法修復安裝路徑"
+
+#: Programs/xbrlapi.c:259
+msgid "cannot get tty\n"
+msgstr "無法取得 tty\n"
+
+#: Programs/xbrlapi.c:265
+#, c-format
+msgid "cannot get tty %d\n"
+msgstr "無法取得 tty %d\n"
+
+#: Programs/file.c:518
+msgid "cannot get working directory"
+msgstr "無法取得工作目錄"
+
+#: Programs/xbrlapi.c:702
+#, c-format
+msgid "cannot grab windows on screen %d\n"
+msgstr "無法擷取視窗在螢幕 %d\n"
+
+#: Programs/xbrlapi.c:272
+msgid "cannot ignore keys\n"
+msgstr "無法忽略按鍵\n"
+
+#: Programs/atb_translate.c:90
+msgid "cannot load attributes table"
+msgstr "無法載入屬性表"
+
+#: Programs/ctb_translate.c:407
+msgid "cannot load contraction table"
+msgstr "無法載入縮寫表"
+
+#: Programs/system_windows.c:54
+msgid "cannot load library"
+msgstr "無法載入含式庫"
+
+#: Programs/ttb_translate.c:295
+msgid "cannot load text table"
+msgstr "無法載入文字表"
+
+#: Programs/file.c:370
+msgid "cannot make world writable"
+msgstr "無法讓環境可寫入"
+
+#: Programs/config.c:1179
+msgid "cannot open key help"
+msgstr "無法開啟按鍵說明"
+
+#: Programs/program.c:262
+msgid "cannot open process identifier file"
+msgstr "無法開啟程序編號檔"
+
+#: Programs/menu.c:616
+msgid "cannot open working directory"
+msgstr "無法開啟工作目錄"
+
+#: Programs/prefs.c:363
+msgid "cannot read preferences file"
+msgstr "無法讀取偏好設定檔"
+
+#: Programs/xbrlapi.c:337
+#, c-format
+msgid "cannot set focus to %#010x\n"
+msgstr "無法設定焦點到 %#010x\n"
+
+#: Programs/file.c:532 Programs/menu.c:605
+msgid "cannot set working directory"
+msgstr "無法設定工作目錄"
+
+#: Programs/prefs.c:576
+msgid "cannot write to preferences file"
+msgstr "無法寫入偏好設定檔"
+
+#. "cap" here, used during speech output, is short for "capital".
+#. It is spoken just before an uppercase letter, e.g. "cap A".
+#: Programs/core.c:1070
+msgid "cap"
+msgstr "大寫"
+
+#: Programs/menu_prefs.c:886 Programs/menu_prefs.c:1349
+msgid "cells"
+msgstr "點字方"
+
+#: Programs/cmd_preferences.c:87
+msgid "changes discarded"
+msgstr "放棄變更"
+
+#. xgettext: This is the description of the UNSTICK command.
+#: Programs/cmds.auto.h:887
+msgid "clear all sticky input modifiers"
+msgstr "清除所有相黏的輸入設定"
+
+#. xgettext: This is the description of the TXTSEL_CLEAR command.
+#: Programs/cmds.auto.h:1019
+msgid "clear the text selection"
+msgstr "清除文字選取"
+
+#: Programs/cmd_speech.c:501
+msgid "column"
+msgstr "欄"
+
+#. configuration menu
+#: Drivers/Braille/BrailleLite/braille.c:607
+msgid "config"
+msgstr "設定"
+
+#: Programs/options.c:807
+msgid "configuration directive specified more than once"
+msgstr "設定敘述指定超過一次"
+
+#: Drivers/Screen/Linux/screen.c:1508
+msgid "console not in use"
+msgstr "主控台未使用"
+
+#: Programs/menu_prefs.c:1056
+msgid "console tone generator"
+msgstr "主控台音響產生器"
+
+#. xgettext: This is the description of the CLIP_COPY command.
+#: Programs/cmds.auto.h:1338
+msgid "copy characters to clipboard"
+msgstr "複製字元到剪貼不"
+
+#. xgettext: This is the description of the HOST_COPY command.
+#: Programs/cmds.auto.h:1033
+msgid "copy selected text to host clipboard"
+msgstr "複製選取的文字到主機剪貼不"
+
+#: Programs/config.c:642
+msgid "csecs"
+msgstr "csecs"
+
+#. xgettext: This is the description of the TOUCH_AT command.
+#: Programs/cmds.auto.h:1503
+msgid "current reading location"
+msgstr "目前的閱讀位置"
+
+#. xgettext: This is the description of the KEY_CURSOR_DOWN command.
+#: Programs/cmds.auto.h:1567
+msgid "cursor-down key"
+msgstr "游標往下鍵"
+
+#. xgettext: This is the description of the KEY_CURSOR_LEFT command.
+#: Programs/cmds.auto.h:1543
+msgid "cursor-left key"
+msgstr "游標往左鍵"
+
+#. xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+#: Programs/cmds.auto.h:1551
+msgid "cursor-right key"
+msgstr "游標往右鍵"
+
+#. xgettext: This is the description of the KEY_CURSOR_UP command.
+#: Programs/cmds.auto.h:1559
+msgid "cursor-up key"
+msgstr "游標往上鍵"
+
+#. xgettext: This is the description of the HOST_CUT command.
+#: Programs/cmds.auto.h:1040
+msgid "cut selected text to host clipboard"
+msgstr "剪下選取的文字到主機剪貼簿"
+
+#. GB
+#: Programs/cmd_utils.c:147
+msgid "cyan"
+msgstr "青"
+
+#. xgettext: This is the description of the ALTGR command.
+#: Programs/cmds.auto.h:894
+msgid "cycle the AltGr (Right Alt) sticky input modifier (next, on, off)"
+msgstr "循環 AltGr (右 Alt) 相黏輸入設定 (下一個, 開, 關)"
+
+#. xgettext: This is the description of the CONTROL command.
+#: Programs/cmds.auto.h:642
+msgid "cycle the Control sticky input modifier (next, on, off)"
+msgstr "循環 Control 相黏輸入設定 (下一個, 開, 關)"
+
+#. xgettext: This is the description of the GUI command.
+#: Programs/cmds.auto.h:901
+msgid "cycle the GUI (Windows) sticky input modifier (next, on, off)"
+msgstr "循環 GUI (Windows) 相黏輸入設定 (下一個, 開, 關)"
+
+#. xgettext: This is the description of the META command.
+#: Programs/cmds.auto.h:649
+msgid "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"
+msgstr "循環 Meta (左 Alt) 相黏輸入設定 (下一個, 開, 關)"
+
+#. xgettext: This is the description of the SHIFT command.
+#: Programs/cmds.auto.h:628
+msgid "cycle the Shift sticky input modifier (next, on, off)"
+msgstr "循環 Shift 相黏輸入設定 (下一個, 開, 關)"
+
+#. xgettext: This is the description of the UPPER command.
+#: Programs/cmds.auto.h:635
+msgid "cycle the Upper sticky input modifier (next, on, off)"
+msgstr "循環 Upper 相黏輸入設定 (下一個, 開, 關)"
+
+#. L
+#: Programs/cmd_utils.c:152
+msgid "dark grey"
+msgstr "暗灰"
+
+#. xgettext: This is the description of the SAY_LOWER command.
+#: Programs/cmds.auto.h:1168
+msgid "decrease speaking pitch"
+msgstr "降低語音音調"
+
+#. xgettext: This is the description of the SAY_SLOWER command.
+#: Programs/cmds.auto.h:550
+msgid "decrease speaking rate"
+msgstr "降低與因素率"
+
+#. xgettext: This is the description of the SAY_SOFTER command.
+#: Programs/cmds.auto.h:564
+msgid "decrease speaking volume"
+msgstr "降低語音音量"
+
+#. xgettext: This is the description of the KEY_DELETE command.
+#: Programs/cmds.auto.h:1615
+msgid "delete key"
+msgstr "刪除鍵"
+
+#. xgettext: This is the description of the DESCCHAR command.
+#: Programs/cmds.auto.h:1275
+msgid "describe character"
+msgstr "描述字元"
+
+#. xgettext: This is the description of the DESC_CURR_CHAR command.
+#: Programs/cmds.auto.h:820
+msgid "describe current character"
+msgstr "描述目前字元"
+
+#: Programs/config.c:623 Programs/config.c:633
+msgid "device"
+msgstr "設備"
+
+#: Programs/brltest.c:64 Programs/brltest.c:74 Programs/brltty-atb.c:32
+#: Programs/brltty-ctb.c:50 Programs/brltty-ktb.c:69 Programs/brltty-ktb.c:79
+#: Programs/brltty-trtxt.c:47 Programs/config.c:321 Programs/config.c:421
+#: Programs/config.c:431 Programs/config.c:441 Programs/config.c:512
+msgid "directory"
+msgstr "目錄"
+
+#: Programs/xbrlapi.c:98
+msgid "display"
+msgstr "顯示"
+
+#. xgettext: This is the description of the NOOP command.
+#: Programs/cmds.auto.h:5
+msgid "do nothing"
+msgstr "無作為"
+
+#: Programs/learn.c:108
+msgid "done"
+msgstr "完成"
+
+#: Programs/menu_prefs.c:666
+msgid "dot 7"
+msgstr "第七點"
+
+#: Programs/menu_prefs.c:667
+msgid "dot 8"
+msgstr "地八點"
+
+#: Programs/menu_prefs.c:664
+msgid "dots 7 and 8"
+msgstr "七八點"
+
+#: Drivers/Braille/Baum/braille.c:1146
+msgid "driver request"
+msgstr "驅動要求"
+
+#: Programs/config.c:470 Programs/config.c:568 Programs/config.c:603
+msgid "driver,..."
+msgstr "驅動,..."
+
+#. xgettext: This is the description of the KEY_END command.
+#: Programs/cmds.auto.h:1599
+msgid "end key"
+msgstr "End 鍵"
+
+#. xgettext: This is the description of the KEY_ENTER command.
+#: Programs/cmds.auto.h:1511
+msgid "enter key"
+msgstr "Enter 鍵"
+
+#. xgettext: This is the description of the LEARN command.
+#: Programs/cmds.auto.h:436
+msgid "enter/leave command learn mode"
+msgstr "進入/離開指令學習模式"
+
+#. xgettext: This is the description of the HELP command.
+#: Programs/cmds.auto.h:422
+msgid "enter/leave help display"
+msgstr "進入/離開說明畫面"
+
+#. xgettext: This is the description of the PREFMENU command.
+#: Programs/cmds.auto.h:443
+msgid "enter/leave preferences menu"
+msgstr "進入/離開偏好設定"
+
+#. xgettext: This is the description of the INFO command.
+#: Programs/cmds.auto.h:429
+msgid "enter/leave status display"
+msgstr "進入/離開狀態畫面"
+
+#. xgettext: This is the description of the KEY_ESCAPE command.
+#: Programs/cmds.auto.h:1535
+msgid "escape key"
+msgstr "Escape 鍵"
+
+#: Drivers/Braille/Iris/braille.c:1494
+msgid "eurobraille"
+msgstr "Eurobraille"
+
+#. xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+#: Programs/cmd_miscellaneous.c:102
+msgid "exactly"
+msgstr "精確的"
+
+#: Programs/config.c:817
+msgid "excess argument"
+msgstr "過多引述"
+
+#. parent
+#: Programs/brltty.c:177
+#, c-format
+msgid "executing \"%s\" (from \"%s\")\n"
+msgstr "(從 \"%s\")執行 \"%s\"\n"
+
+#: Programs/pgmprivs_linux.c:2045
+msgid "executing as the invoking user"
+msgstr "以調用的使用者身份執行"
+
+#. execv() shouldn't return
+#: Programs/brltty.c:184
+#, c-format
+msgid "execution of \"%s\" failed: %s\n"
+msgstr "\"%s\" 執行失敗: %s\n"
+
+#: Programs/xbrlapi.c:709
+msgid "failed to get first focus\n"
+msgstr "或取締一個焦點失敗\n"
+
+#: Programs/brltty-trtxt.c:56 Programs/brltty-trtxt.c:64 Programs/config.c:378
+#: Programs/config.c:387 Programs/config.c:397 Programs/config.c:523
+#: Programs/config.c:533 Programs/config.c:542 Programs/config.c:550
+#: Programs/config.c:587 Programs/config.c:672
+msgid "file"
+msgstr "檔案"
+
+#: Programs/options.c:993
+#, c-format
+msgid "file '%s' processing error."
+msgstr "檔案 '%s' 處理錯誤"
+
+#. failed
+#: Programs/brltty.c:172
+#, c-format
+msgid "fork of \"%s\" failed: %s\n"
+msgstr "\"%s\" 分叉失敗: %s\n"
+
+#. xgettext: This is the description of the KEY_FUNCTION command.
+#: Programs/cmds.auto.h:1624
+msgid "function key"
+msgstr "功能鍵"
+
+#. xgettext: This is the description of the BACK command.
+#: Programs/cmds.auto.h:271
+msgid "go back after cursor tracking"
+msgstr "游標追蹤後返回"
+
+#. xgettext: This is the description of the GUI_BACK command.
+#: Programs/cmds.auto.h:1077
+msgid "go back to the previous screen"
+msgstr "跳回上一個畫面"
+
+#. xgettext: This is the description of the FWINLT command.
+#: Programs/cmds.auto.h:210
+msgid "go backward one braille window"
+msgstr "倒退一個點字視窗"
+
+#. xgettext: This is the description of the FWINLTSKIP command.
+#: Programs/cmds.auto.h:228
+msgid "go backward skipping blank braille windows"
+msgstr "倒退跳過空白點字視窗"
+
+#. xgettext: This is the description of the PRNBWIN command.
+#: Programs/cmds.auto.h:966
+msgid "go backward to nearest non-blank braille window"
+msgstr "倒退道最近的非空白點字視窗"
+
+#. xgettext: This is the description of the LNDN command.
+#: Programs/cmds.auto.h:23
+msgid "go down one line"
+msgstr "下宜一行"
+
+#. xgettext: This is the description of the WINDN command.
+#: Programs/cmds.auto.h:41
+msgid "go down several lines"
+msgstr "下宜若干行"
+
+#. xgettext: This is the description of the NXPGRPH command.
+#: Programs/cmds.auto.h:133
+msgid "go down to first line of next paragraph"
+msgstr "移到下一段第一行"
+
+#. xgettext: This is the description of the MENU_LAST_ITEM command.
+#: Programs/cmds.auto.h:475
+msgid "go down to last item"
+msgstr "跳到最後一項"
+
+#. xgettext: This is the description of the NXDIFCHAR command.
+#: Programs/cmds.auto.h:1330
+msgid "go down to nearest line with different character"
+msgstr "下移到最近的不同字元行"
+
+#. xgettext: This is the description of the NXDIFLN command.
+#: Programs/cmds.auto.h:59
+msgid "go down to nearest line with different content"
+msgstr "下移到最近的不同內容行"
+
+#. xgettext: This is the description of the ATTRDN command.
+#: Programs/cmds.auto.h:77
+msgid "go down to nearest line with different highlighting"
+msgstr "下移到最近的不同反白行"
+
+#. xgettext: This is the description of the NXINDENT command.
+#: Programs/cmds.auto.h:1267
+msgid "go down to nearest line with less indent than character"
+msgstr "下移到最近的內縮少於字元的行"
+
+#. xgettext: This is the description of the NXPROMPT command.
+#: Programs/cmds.auto.h:151
+msgid "go down to next command prompt"
+msgstr "移到下一個命令提示"
+
+#. xgettext: This is the description of the MENU_NEXT_ITEM command.
+#: Programs/cmds.auto.h:493
+msgid "go down to next item"
+msgstr "跳到下一項"
+
+#. xgettext: This is the description of the FWINRT command.
+#: Programs/cmds.auto.h:219
+msgid "go forward one braille window"
+msgstr "前進一個點字視窗"
+
+#. xgettext: This is the description of the FWINRTSKIP command.
+#: Programs/cmds.auto.h:237
+msgid "go forward skipping blank braille windows"
+msgstr "往前跳過空白點字視窗"
+
+#. xgettext: This is the description of the NXNBWIN command.
+#: Programs/cmds.auto.h:975
+msgid "go forward to nearest non-blank braille window"
+msgstr "前進道最近的非空白點字視窗"
+
+#. xgettext: This is the description of the HWINLT command.
+#: Programs/cmds.auto.h:192
+msgid "go left half a braille window"
+msgstr "左宜半個點字視窗"
+
+#. xgettext: This is the description of the CHRLT command.
+#: Programs/cmds.auto.h:174
+msgid "go left one character"
+msgstr "左宜一個字元"
+
+#. xgettext: This is the description of the HWINRT command.
+#: Programs/cmds.auto.h:201
+msgid "go right half a braille window"
+msgstr "右移半個點字視窗"
+
+#. xgettext: This is the description of the CHRRT command.
+#: Programs/cmds.auto.h:183
+msgid "go right one character"
+msgstr "右移一個字元"
+
+#. xgettext: This is the description of the SPEAK_FRST_CHAR command.
+#: Programs/cmds.auto.h:789
+msgid "go to and speak first non-blank character on line"
+msgstr "跳到並朗讀本行第一個非空白字元"
+
+#. xgettext: This is the description of the SPEAK_FRST_LINE command.
+#: Programs/cmds.auto.h:805
+msgid "go to and speak first non-blank line on screen"
+msgstr "跳到並朗讀螢幕上帝一非空白行"
+
+#. xgettext: This is the description of the SPEAK_LAST_CHAR command.
+#: Programs/cmds.auto.h:797
+msgid "go to and speak last non-blank character on line"
+msgstr "跳到並朗讀本行最後一個非空白字元"
+
+#. xgettext: This is the description of the SPEAK_LAST_LINE command.
+#: Programs/cmds.auto.h:813
+msgid "go to and speak last non-blank line on screen"
+msgstr "跳到並朗讀螢幕最後一非空白行"
+
+#. xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+#: Programs/cmds.auto.h:735
+msgid "go to and speak next character"
+msgstr "跳到並朗讀下個字元"
+
+#. xgettext: This is the description of the SPEAK_NEXT_LINE command.
+#: Programs/cmds.auto.h:781
+msgid "go to and speak next line"
+msgstr "跳到並朗讀下一行"
+
+#. xgettext: This is the description of the SPEAK_NEXT_WORD command.
+#: Programs/cmds.auto.h:758
+msgid "go to and speak next word"
+msgstr "跳到並朗讀下個單字"
+
+#. xgettext: This is the description of the SPEAK_PREV_CHAR command.
+#: Programs/cmds.auto.h:727
+msgid "go to and speak previous character"
+msgstr "跳到並朗讀上一字元"
+
+#. xgettext: This is the description of the SPEAK_PREV_LINE command.
+#: Programs/cmds.auto.h:773
+msgid "go to and speak previous line"
+msgstr "跳到並朗讀上一行"
+
+#. xgettext: This is the description of the SPEAK_PREV_WORD command.
+#: Programs/cmds.auto.h:750
+msgid "go to and speak previous word"
+msgstr "跳到並朗讀上一單字"
+
+#. xgettext: This is the description of the BOT_LEFT command.
+#: Programs/cmds.auto.h:115
+msgid "go to beginning of bottom line"
+msgstr "跳到最後一行開頭"
+
+#. xgettext: This is the description of the LNBEG command.
+#: Programs/cmds.auto.h:246
+msgid "go to beginning of line"
+msgstr "跳到行首"
+
+#. xgettext: This is the description of the TOP_LEFT command.
+#: Programs/cmds.auto.h:105
+msgid "go to beginning of top line"
+msgstr "跳道地一行開頭"
+
+#. xgettext: This is the description of the BOT command.
+#: Programs/cmds.auto.h:95
+msgid "go to bottom line"
+msgstr "跳到最後一行"
+
+#. xgettext: This is the description of the SPKHOME command.
+#: Programs/cmds.auto.h:522
+msgid "go to current speaking position"
+msgstr "跳到目前與因為至"
+
+#. xgettext: This is the description of the LNEND command.
+#: Programs/cmds.auto.h:255
+msgid "go to end of line"
+msgstr "跳到行尾"
+
+#. xgettext: This is the description of the MENU_PREV_LEVEL command.
+#: Programs/cmds.auto.h:664
+msgid "go to previous menu level"
+msgstr "跳道上一曾選單"
+
+#. xgettext: This is the description of the GOTOMARK command.
+#: Programs/cmds.auto.h:1300
+msgid "go to remembered braille window position"
+msgstr "跳到濟助的點字視窗位置"
+
+#. xgettext: This is the description of the HOME command.
+#: Programs/cmds.auto.h:263
+msgid "go to screen cursor"
+msgstr "跳到螢幕游標"
+
+#. xgettext: This is the description of the RETURN command.
+#: Programs/cmds.auto.h:279
+msgid "go to screen cursor or go back after cursor tracking"
+msgstr "跳到螢幕游標或游標追蹤後反回"
+
+#. xgettext: This is the description of the GOTOLINE command.
+#: Programs/cmds.auto.h:1310
+msgid "go to selected line"
+msgstr "跳到選取的行"
+
+#. xgettext: This is the description of the GUI_HOME command.
+#: Programs/cmds.auto.h:1069
+msgid "go to the home screen"
+msgstr "跳到主畫面"
+
+#. xgettext: This is the description of the TOP command.
+#: Programs/cmds.auto.h:86
+msgid "go to top line"
+msgstr "跳道地一行"
+
+#. xgettext: This is the description of the LNUP command.
+#: Programs/cmds.auto.h:14
+msgid "go up one line"
+msgstr "跳上一行"
+
+#. xgettext: This is the description of the WINUP command.
+#: Programs/cmds.auto.h:32
+msgid "go up several lines"
+msgstr "往上跳若干行"
+
+#. xgettext: This is the description of the MENU_FIRST_ITEM command.
+#: Programs/cmds.auto.h:466
+msgid "go up to first item"
+msgstr "跳到第一項"
+
+#. xgettext: This is the description of the PRPGRPH command.
+#: Programs/cmds.auto.h:124
+msgid "go up to first line of paragraph"
+msgstr "跳到本段第一行"
+
+#. xgettext: This is the description of the PRDIFCHAR command.
+#: Programs/cmds.auto.h:1320
+msgid "go up to nearest line with different character"
+msgstr "往上跳到最近的不同字元行"
+
+#. xgettext: This is the description of the PRDIFLN command.
+#: Programs/cmds.auto.h:50
+msgid "go up to nearest line with different content"
+msgstr "往上跳到最近的不同內容行"
+
+#. xgettext: This is the description of the ATTRUP command.
+#: Programs/cmds.auto.h:68
+msgid "go up to nearest line with different highlighting"
+msgstr "往上跳到最近的不同反白行"
+
+#. xgettext: This is the description of the PRINDENT command.
+#: Programs/cmds.auto.h:1257
+msgid "go up to nearest line with less indent than character"
+msgstr "往上跳到最近的內縮內縮少於字元的行"
+
+#. xgettext: This is the description of the PRPROMPT command.
+#: Programs/cmds.auto.h:142
+msgid "go up to previous command prompt"
+msgstr "跳到上一個命令提示"
+
+#. xgettext: This is the description of the MENU_PREV_ITEM command.
+#: Programs/cmds.auto.h:484
+msgid "go up to previous item"
+msgstr "跳到上一項"
+
+#. G
+#: Programs/cmd_utils.c:146
+msgid "green"
+msgstr "綠"
+
+#: Programs/pgmprivs_linux.c:2167
+msgid "group permissions added"
+msgstr "以加入群組權限"
+
+#: Programs/cmd_miscellaneous.c:213
+msgid "help not available"
+msgstr "沒有說明"
+
+#: Programs/scr_help.c:229
+msgid "help screen not readable"
+msgstr "說明畫面無法讀取"
+
+#. xgettext: This is the description of the KEY_HOME command.
+#: Programs/cmds.auto.h:1591
+msgid "home key"
+msgstr "home 鍵"
+
+#: Programs/config.c:491
+msgid "identifier,..."
+msgstr "相同,..."
+
+#: Drivers/Braille/Baum/braille.c:1148
+msgid "idle timeout"
+msgstr "閒置逾時"
+
+#. xgettext: This is the description of the SAY_HIGHER command.
+#: Programs/cmds.auto.h:1175
+msgid "increase speaking pitch"
+msgstr "提高語音音調"
+
+#. xgettext: This is the description of the SAY_FASTER command.
+#: Programs/cmds.auto.h:557
+msgid "increase speaking rate"
+msgstr "增加與因素率"
+
+#. xgettext: This is the description of the SAY_LOUDER command.
+#: Programs/cmds.auto.h:571
+msgid "increase speaking volume"
+msgstr "增加語音音量"
+
+#: Programs/core.c:1128
+msgid "indent"
+msgstr "內縮"
+
+#. xgettext: This is the description of the PASTE_HISTORY command.
+#: Programs/cmds.auto.h:1354
+msgid "insert clipboard history entry after screen cursor"
+msgstr "在螢幕游標處插入剪貼不的先前內容"
+
+#. xgettext: This is the description of the PASTE command.
+#: Programs/cmds.auto.h:600
+msgid "insert clipboard text after screen cursor"
+msgstr "在螢幕游標處插入剪貼不的文字"
+
+#. xgettext: This is the description of the HOST_PASTE command.
+#: Programs/cmds.auto.h:1047
+msgid "insert host clipboard text after screen cursor"
+msgstr "在螢幕游標處插入主機剪貼不的文字"
+
+#. xgettext: This is the description of the KEY_INSERT command.
+#: Programs/cmds.auto.h:1607
+msgid "insert key"
+msgstr "insert 鍵"
+
+#: Programs/program.c:166
+msgid "install path not absolute"
+msgstr "安裝路徑不是絕對"
+
+#: Programs/options.c:104
+msgid "invalid counter setting"
+msgstr "無效的計數器設定"
+
+#: Programs/datafile.c:318
+msgid "invalid escape sequence"
+msgstr "無效的跳脫序列"
+
+#: Programs/options.c:113
+msgid "invalid flag setting"
+msgstr "無效的旗標設定"
+
+#: Programs/config.c:2803
+msgid "invalid message hold timeout"
+msgstr "無效的訊息保留時間"
+
+#. the operand for an option is invalid
+#: Programs/options.c:589
+msgid "invalid operand"
+msgstr "無效的運算員"
+
+#: Programs/brlapi_server.c:4516
+msgid "invalid thread stack size"
+msgstr "無效的線程堆疊大小"
+
+#: Drivers/Braille/BrailleLite/braille.c:590
+#: Drivers/Braille/BrailleLite/braille.c:668
+msgid "keyboard emu off"
+msgstr "鍵盤模擬關"
+
+#: Drivers/Braille/BrailleLite/braille.c:589
+msgid "keyboard emu on"
+msgstr "鍵盤模擬開"
+
+#. L  B
+#: Programs/cmd_utils.c:153
+msgid "light blue"
+msgstr "淡藍"
+
+#. L GB
+#: Programs/cmd_utils.c:155
+msgid "light cyan"
+msgstr "淡青"
+
+#. L G
+#: Programs/cmd_utils.c:154
+msgid "light green"
+msgstr "淡率"
+
+#. RGB
+#: Programs/cmd_utils.c:151
+msgid "light grey"
+msgstr "淡灰"
+
+#. LR B
+#: Programs/cmd_utils.c:157
+msgid "light magenta"
+msgstr "淡紫"
+
+#. LR
+#: Programs/cmd_utils.c:156
+msgid "light red"
+msgstr "淡紅"
+
+#: Programs/cmd_speech.c:500
+msgid "line"
+msgstr "行"
+
+#. xgettext: This is the description of the COPY_LINE command.
+#: Programs/cmds.auto.h:1239
+msgid "linear copy to character"
+msgstr "直線拷貝字元"
+
+#: Programs/config.c:663
+msgid "lvl|cat,..."
+msgstr "lvl|cat,..."
+
+#. R B
+#: Programs/cmd_utils.c:149
+msgid "magenta"
+msgstr "紫紅"
+
+#. the operand for a string option hasn't been specified
+#: Programs/options.c:583
+msgid "missing operand"
+msgstr "缺少運算員"
+
+#: Programs/parse.c:472
+msgid "missing parameter name"
+msgstr "缺少參數名稱"
+
+#: Programs/parse.c:458
+msgid "missing parameter qualifier"
+msgstr "缺少參數指定符號"
+
+#: Programs/parse.c:434
+msgid "missing parameter value"
+msgstr "缺少餐數值"
+
+#. xgettext: This is the description of the GUI_ITEM_FRST command.
+#: Programs/cmds.auto.h:1140
+msgid "move to the first item in the screen area"
+msgstr "移到畫面中的第一個項目"
+
+#. xgettext: This is the description of the GUI_ITEM_LAST command.
+#: Programs/cmds.auto.h:1161
+msgid "move to the last item in the screen area"
+msgstr "移到畫面中的最後一個項目"
+
+#. xgettext: This is the description of the GUI_ITEM_NEXT command.
+#: Programs/cmds.auto.h:1154
+msgid "move to the next item in the screen area"
+msgstr "移到畫面中的下一個項目"
+
+#. xgettext: This is the description of the GUI_ITEM_PREV command.
+#: Programs/cmds.auto.h:1147
+msgid "move to the previous item in the screen area"
+msgstr "移到畫面中的上一個項目"
+
+#: Programs/msgtest.c:57
+msgid "name"
+msgstr "名稱"
+
+#: Programs/config.c:354 Programs/config.c:406 Programs/config.c:459
+#: Programs/config.c:481 Programs/config.c:559 Programs/config.c:578
+#: Programs/config.c:613
+msgid "name=value,..."
+msgstr "名稱=數值,..."
+
+#: Drivers/Braille/Iris/braille.c:1510
+msgid "native"
+msgstr "內部"
+
+#: Programs/config.c:1188
+msgid "no key bindings"
+msgstr "沒有按鍵綁定"
+
+#: Programs/scr_driver.c:39
+msgid "no screen"
+msgstr "沒有螢幕"
+
+#: Programs/config.c:715
+msgid "none"
+msgstr "沒有"
+
+#: Programs/config.c:1477
+msgid "not saved"
+msgstr "未存檔"
+
+#: Programs/pgmprivs_linux.c:2018
+msgid "not switching to an unprivileged user"
+msgstr "未切換到未授權的使用者"
+
+#. xgettext: This is the description of the GUI_APP_ALERTS command.
+#: Programs/cmds.auto.h:1112
+msgid "open the application alerts window"
+msgstr "打開軟體警示視窗"
+
+#. xgettext: This is the description of the GUI_APP_LIST command.
+#: Programs/cmds.auto.h:1098
+msgid "open the application list window"
+msgstr "打開軟體列表視窗"
+
+#. xgettext: This is the description of the GUI_APP_MENU command.
+#: Programs/cmds.auto.h:1105
+msgid "open the application-specific menu"
+msgstr "打開軟體指定的選單"
+
+#. xgettext: This is the description of the GUI_BRL_ACTIONS command.
+#: Programs/cmds.auto.h:1061
+msgid "open the braille actions window"
+msgstr "打開點字活動視窗"
+
+#. xgettext: This is the description of the GUI_DEV_OPTIONS command.
+#: Programs/cmds.auto.h:1091
+msgid "open the device options window"
+msgstr "打開設備選項視窗"
+
+#. xgettext: This is the description of the GUI_DEV_SETTINGS command.
+#: Programs/cmds.auto.h:1084
+msgid "open the device settings window"
+msgstr "打開設備設定視窗"
+
+#: Programs/options.c:155
+msgid "option"
+msgstr "選向"
+
+#: Programs/pgmprivs_linux.c:2152
+msgid "ownership claimed"
+msgstr "所有權主張"
+
+#. xgettext: This is the description of the KEY_PAGE_DOWN command.
+#: Programs/cmds.auto.h:1583
+msgid "page-down key"
+msgstr "page-down 鍵"
+
+#. xgettext: This is the description of the KEY_PAGE_UP command.
+#: Programs/cmds.auto.h:1575
+msgid "page-up key"
+msgstr "page-up 鍵"
+
+#: Programs/msgtest.c:42
+msgid "path"
+msgstr "路徑"
+
+#: Programs/config.c:2776
+msgid "pid file not specified"
+msgstr "pid 檔案未指定"
+
+#: Programs/program.c:308
+msgid "pid file open error"
+msgstr "pid 檔案開啟錯誤"
+
+#: Programs/spk.c:232
+msgid "pitch"
+msgstr "音調"
+
+#. xgettext: This is the description of the SETLEFT command.
+#: Programs/cmds.auto.h:1283
+msgid "place left end of braille window at character"
+msgstr "把點字視窗的最左端對齊字元"
+
+#: Drivers/Braille/Baum/braille.c:1147
+msgid "power switch"
+msgstr "電源開關"
+
+#: Programs/spk.c:206
+msgid "rate"
+msgstr "速率"
+
+#. xgettext: This is the description of the COPY_RECT command.
+#: Programs/cmds.auto.h:1231
+msgid "rectangular copy to character"
+msgstr "區塊複製字元"
+
+#. R
+#: Programs/cmd_utils.c:148
+msgid "red"
+msgstr "紅"
+
+#. xgettext: This is the description of the REFRESH command.
+#: Programs/cmds.auto.h:1005
+msgid "refresh braille display"
+msgstr "刷新點字顯示"
+
+#. xgettext: This is the description of the REFRESH_LINE command.
+#: Programs/cmds.auto.h:1413
+msgid "refresh braille line"
+msgstr "刷新點字顯示行"
+
+#: Programs/config.c:413
+msgid "regexp,..."
+msgstr "regexp,..."
+
+#: Programs/config.c:2021
+#, c-format
+msgid "reinitializing braille driver"
+msgstr "重新出使化點字驅動"
+
+#: Programs/config.c:2534
+#, c-format
+msgid "reinitializing screen driver"
+msgstr "重新出使化螢幕驅動"
+
+#: Programs/config.c:2334
+#, c-format
+msgid "reinitializing speech driver"
+msgstr "重新出使化語音驅動"
+
+#. xgettext: This is the description of the SETMARK command.
+#: Programs/cmds.auto.h:1291
+msgid "remember current braille window position"
+msgstr "濟助目前點字視窗位置"
+
+#. xgettext: This is the description of the ALERT command.
+#: Programs/cmds.auto.h:1445
+msgid "render an alert"
+msgstr "發出警報"
+
+#: Drivers/Braille/BrailleLite/braille.c:601
+#: Drivers/Braille/BrailleLite/braille.c:780
+#: Drivers/Braille/BrailleLite/braille.c:782
+#: Drivers/Braille/BrailleLite/braille.c:791
+msgid "repeat count"
+msgstr "重複計數"
+
+#. xgettext: This is the description of the RESTARTBRL command.
+#: Programs/cmds.auto.h:607
+msgid "restart braille driver"
+msgstr "重新啟動點字驅動"
+
+#. xgettext: This is the description of the RESTARTSPEECH command.
+#: Programs/cmds.auto.h:614
+msgid "restart speech driver"
+msgstr "重新啟動語音驅動"
+
+#. xgettext: This is the description of the CLIP_RESTORE command.
+#: Programs/cmds.auto.h:864
+msgid "restore clipboard from disk"
+msgstr "從磁碟還原剪貼簿"
+
+#. xgettext: This is the description of the PREFLOAD command.
+#: Programs/cmds.auto.h:457
+msgid "restore preferences from disk"
+msgstr "從磁碟還原偏好設定"
+
+#. xgettext: This is the description of the GUI_AREA_ACTV command.
+#: Programs/cmds.auto.h:1119
+msgid "return to the active screen area"
+msgstr "回歸運作中的畫面"
+
+#. xgettext: This is the description of the CLIP_SAVE command.
+#: Programs/cmds.auto.h:857
+msgid "save clipboard to disk"
+msgstr "儲存剪貼簿到磁碟"
+
+#. xgettext: This is the description of the PREFSAVE command.
+#: Programs/cmds.auto.h:450
+msgid "save preferences to disk"
+msgstr "儲存偏好設定到磁碟"
+
+#: Programs/xbrlapi.c:91
+msgid "scheme+..."
+msgstr "scheme+..."
+
+#: Programs/config.c:2410
+msgid "screen driver not loadable"
+msgstr "螢幕驅動無法載入"
+
+#: Programs/config.c:2531
+msgid "screen driver restarting"
+msgstr "螢幕驅動重新啟動"
+
+#: Programs/cmd_miscellaneous.c:171
+msgid "screen driver stopped"
+msgstr "螢幕驅動停止"
+
+#: Drivers/Screen/Android/screen.c:184
+msgid "screen locked"
+msgstr "螢幕鎖定"
+
+#: Drivers/Screen/Linux/screen.c:1531
+msgid "screen not in text mode"
+msgstr "螢幕不在文字模式"
+
+#. xgettext: This is the description of the PRSEARCH command.
+#: Programs/cmds.auto.h:158
+msgid "search backward for clipboard text"
+msgstr "往回尋找剪貼不文字"
+
+#. xgettext: This is the description of the NXSEARCH command.
+#: Programs/cmds.auto.h:165
+msgid "search forward for clipboard text"
+msgstr "往前尋找剪貼不文字"
+
+#: Programs/menu.c:469
+msgid "seconds"
+msgstr "秒"
+
+#. xgettext: This is the description of the TXTSEL_ALL command.
+#: Programs/cmds.auto.h:1026
+msgid "select all of the text"
+msgstr "全選文字"
+
+#. xgettext: This is the description of the MENU_NEXT_SETTING command.
+#: Programs/cmds.auto.h:507
+msgid "select next choice"
+msgstr "選取下一向選擇"
+
+#. xgettext: This is the description of the MENU_PREV_SETTING command.
+#: Programs/cmds.auto.h:500
+msgid "select previous choice"
+msgstr "選取前一向選擇"
+
+#. xgettext: This is the description of the TUNES command.
+#: Programs/cmds.auto.h:399
+msgid "set alert tunes on/off"
+msgstr "設定警示音開關"
+
+#. xgettext: This is the description of the ATTRBLINK command.
+#: Programs/cmds.auto.h:383
+msgid "set attribute blinking on/off"
+msgstr "設定屬性跳動開關"
+
+#. xgettext: This is the description of the ATTRVIS command.
+#: Programs/cmds.auto.h:375
+msgid "set attribute underlining on/off"
+msgstr "設定屬性家底線開關"
+
+#. xgettext: This is the description of the SET_ATTRIBUTES_TABLE command.
+#: Programs/cmds.auto.h:1370
+msgid "set attributes table"
+msgstr "設定屬性表"
+
+#. xgettext: This is the description of the AUTOREPEAT command.
+#: Programs/cmds.auto.h:407
+msgid "set autorepeat on/off"
+msgstr "設定自動重複開關"
+
+#. xgettext: This is the description of the ASPK_CMP_WORDS command.
+#: Programs/cmds.auto.h:712
+msgid "set autospeak completed words on/off"
+msgstr "設定自動朗讀完成的單字開關"
+
+#. xgettext: This is the description of the ASPK_DEL_CHARS command.
+#: Programs/cmds.auto.h:696
+msgid "set autospeak deleted characters on/off"
+msgstr "設定自動朗讀刪除的字元開關"
+
+#. xgettext: This is the description of the ASPK_INDENT command.
+#: Programs/cmds.auto.h:998
+msgid "set autospeak indent of current line on/off"
+msgstr "設定自動朗讀內縮行開關"
+
+#. xgettext: This is the description of the ASPK_INS_CHARS command.
+#: Programs/cmds.auto.h:688
+msgid "set autospeak inserted characters on/off"
+msgstr "設定自動朗讀插入的字元開關"
+
+#. xgettext: This is the description of the AUTOSPEAK command.
+#: Programs/cmds.auto.h:415
+msgid "set autospeak on/off"
+msgstr "設定自動朗讀開關"
+
+#. xgettext: This is the description of the ASPK_REP_CHARS command.
+#: Programs/cmds.auto.h:704
+msgid "set autospeak replaced characters on/off"
+msgstr "設定自動朗讀重複的字元開關"
+
+#. xgettext: This is the description of the ASPK_SEL_CHAR command.
+#: Programs/cmds.auto.h:680
+msgid "set autospeak selected character on/off"
+msgstr "設定自動朗讀選取的字元開關"
+
+#. xgettext: This is the description of the ASPK_SEL_LINE command.
+#: Programs/cmds.auto.h:672
+msgid "set autospeak selected line on/off"
+msgstr "設定自動朗讀選取的行開關"
+
+#. xgettext: This is the description of the BRLKBD command.
+#: Programs/cmds.auto.h:880
+msgid "set braille keyboard enabled/disabled"
+msgstr "設定點字鍵盤 啟用/停用"
+
+#. xgettext: This is the description of the BRLUCDOTS command.
+#: Programs/cmds.auto.h:872
+msgid "set braille typing mode dots/text"
+msgstr "設定點字輸入模式 點位/文字"
+
+#. xgettext: This is the description of the CAPBLINK command.
+#: Programs/cmds.auto.h:391
+msgid "set capital letter blinking on/off"
+msgstr "設定大寫字母跳動開關"
+
+#. xgettext: This is the description of the CONTRACTED command.
+#: Programs/cmds.auto.h:1190
+msgid "set contracted/computer braille"
+msgstr "設定縮寫或電腦點字"
+
+#. xgettext: This is the description of the SET_CONTRACTION_TABLE command.
+#: Programs/cmds.auto.h:1378
+msgid "set contraction table"
+msgstr "設定縮寫表"
+
+#. xgettext: This is the description of the DISPMD command.
+#: Programs/cmds.auto.h:295
+msgid "set display mode attributes/text"
+msgstr "設定顯示模式 屬性/文字"
+
+#. xgettext: This is the description of the CSRHIDE command.
+#: Programs/cmds.auto.h:343
+msgid "set hidden screen cursor on/off"
+msgstr "設定隱藏螢幕游標開關"
+
+#. xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+#: Programs/cmds.auto.h:1386
+msgid "set keyboard table"
+msgstr "設定按鍵表"
+
+#. xgettext: This is the description of the SET_LANGUAGE_PROFILE command.
+#: Programs/cmds.auto.h:1394
+msgid "set language profile"
+msgstr "設定語言設定檔"
+
+#. xgettext: This is the description of the CSRBLINK command.
+#: Programs/cmds.auto.h:367
+msgid "set screen cursor blinking on/off"
+msgstr "設定螢幕游標跳動開關"
+
+#. xgettext: This is the description of the CSRSIZE command.
+#: Programs/cmds.auto.h:359
+msgid "set screen cursor style block/underline"
+msgstr "設定螢幕游標形狀 區快/底線"
+
+#. xgettext: This is the description of the CSRVIS command.
+#: Programs/cmds.auto.h:335
+msgid "set screen cursor visibility on/off"
+msgstr "設定螢幕游標顯示開關"
+
+#. xgettext: This is the description of the FREEZE command.
+#: Programs/cmds.auto.h:287
+msgid "set screen image frozen/unfrozen"
+msgstr "設定畫面圖像 凍結/不動節"
+
+#. xgettext: This is the description of the COMPBRL6 command.
+#: Programs/cmds.auto.h:1198
+msgid "set six/eight dot computer braille"
+msgstr "設定六點或八點電腦點字"
+
+#. xgettext: This is the description of the SKPBLNKWINS command.
+#: Programs/cmds.auto.h:327
+msgid "set skipping of blank braille windows on/off"
+msgstr "設定跳過空白點字視窗開關"
+
+#. xgettext: This is the description of the SKPIDLNS command.
+#: Programs/cmds.auto.h:319
+msgid "set skipping of lines with identical content on/off"
+msgstr "設定跳過相同內容的行開關"
+
+#. xgettext: This is the description of the SLIDEWIN command.
+#: Programs/cmds.auto.h:311
+msgid "set sliding braille window on/off"
+msgstr "設定滑動點字視窗開關"
+
+#. xgettext: This is the description of the SHOW_CURR_LOCN command.
+#: Programs/cmds.auto.h:850
+msgid "set speech cursor visibility on/off"
+msgstr "設定語音游標顯示開關"
+
+#. xgettext: This is the description of the TXTSEL_SET command.
+#: Programs/cmds.auto.h:1429
+msgid "set text selection"
+msgstr "設定文字選取"
+
+#. xgettext: This is the description of the SIXDOTS command.
+#: Programs/cmds.auto.h:303
+msgid "set text style 6-dot/8-dot"
+msgstr "設定文自行太 六點/八點"
+
+#. xgettext: This is the description of the SET_TEXT_TABLE command.
+#: Programs/cmds.auto.h:1362
+msgid "set text table"
+msgstr "設定文字表"
+
+#. xgettext: This is the description of the TOUCH_NAV command.
+#: Programs/cmds.auto.h:983
+msgid "set touch navigation on/off"
+msgstr "設定觸控導覽開關"
+
+#. xgettext: This is the description of the CSRTRK command.
+#: Programs/cmds.auto.h:351
+msgid "set track screen cursor on/off"
+msgstr "設定追蹤螢幕游標開關"
+
+#. xgettext: This is the description of the TIME command.
+#: Programs/cmds.auto.h:656
+msgid "show current date and time"
+msgstr "顯示目前日期與時間"
+
+#. xgettext: This is the description of the GUI_TITLE command.
+#: Programs/cmds.auto.h:1054
+msgid "show the window title"
+msgstr "顯示視窗標題"
+
+#. xgettext: This is the description of the INDICATORS command.
+#: Programs/cmds.auto.h:1012
+msgid "show various device status indicators"
+msgstr "顯示各種設備狀態指示燈"
+
+#: Programs/menu_prefs.c:1057
+msgid "soundcard digital audio"
+msgstr "音效卡數位音效"
+
+#: Programs/menu_prefs.c:1059
+msgid "soundcard synthesizer"
+msgstr "音效卡合成器"
+
+#: Programs/cmd.c:277 Programs/core.c:1051
+msgid "space"
+msgstr "空白"
+
+#. xgettext: This is the description of the SPEAK_CURR_CHAR command.
+#: Programs/cmds.auto.h:719
+msgid "speak current character"
+msgstr "朗讀目前字元"
+
+#. xgettext: This is the description of the SAY_LINE command.
+#. xgettext: This is the description of the SPEAK_CURR_LINE command.
+#: Programs/cmds.auto.h:529 Programs/cmds.auto.h:765
+msgid "speak current line"
+msgstr "朗讀本行"
+
+#. xgettext: This is the description of the SPEAK_CURR_WORD command.
+#: Programs/cmds.auto.h:742
+msgid "speak current word"
+msgstr "朗讀目前單字"
+
+#. xgettext: This is the description of the SAY_BELOW command.
+#: Programs/cmds.auto.h:543
+msgid "speak from current line through bottom of screen"
+msgstr "從本行開始朗讀到畫面底部"
+
+#. xgettext: This is the description of the SAY_ALL command.
+#: Programs/cmds.auto.h:1182
+msgid "speak from top of screen through bottom of screen"
+msgstr "從畫面開頭報讀到畫面結束"
+
+#. xgettext: This is the description of the SAY_ABOVE command.
+#: Programs/cmds.auto.h:536
+msgid "speak from top of screen through current line"
+msgstr "從畫面頂端開始朗獨到本行"
+
+#. xgettext: This is the description of the SPEAK_INDENT command.
+#: Programs/cmds.auto.h:990
+msgid "speak indent of current line"
+msgstr "朗讀內縮行"
+
+#. xgettext: This is the description of the SPEAK_CURR_LOCN command.
+#: Programs/cmds.auto.h:842
+msgid "speak speech cursor location"
+msgstr "朗讀語音游標位置"
+
+#: Programs/msgtest.c:50
+msgid "specifier"
+msgstr "指定符號"
+
+#: Programs/config.c:2182
+msgid "speech driver not loadable"
+msgstr "語音驅動無法載入"
+
+#: Programs/config.c:2331
+msgid "speech driver restarting"
+msgstr "語音驅動重新啟動"
+
+#: Programs/cmd_speech.c:104
+msgid "speech driver stopped"
+msgstr "語音驅動停止"
+
+#. xgettext: This is the description of the SPELL_CURR_WORD command.
+#: Programs/cmds.auto.h:827
+msgid "spell current word"
+msgstr "拼出目前的單字"
+
+#. xgettext: This is the description of the CLIP_NEW command.
+#: Programs/cmds.auto.h:1215
+msgid "start new clipboard at character"
+msgstr "以字元開始新的剪貼不"
+
+#. xgettext: This is the description of the TXTSEL_START command.
+#: Programs/cmds.auto.h:1421
+msgid "start text selection"
+msgstr "開始文字選取"
+
+#. xgettext: This is the description of the BRL_START command.
+#: Programs/cmds.auto.h:915
+msgid "start the braille driver"
+msgstr "啟動點字驅動"
+
+#. xgettext: This is the description of the SCR_START command.
+#: Programs/cmds.auto.h:943
+msgid "start the screen driver"
+msgstr "啟動螢幕驅動"
+
+#. xgettext: This is the description of the SPK_START command.
+#: Programs/cmds.auto.h:929
+msgid "start the speech driver"
+msgstr "啟動語音驅動"
+
+#. xgettext: This is the description of the MUTE command.
+#: Programs/cmds.auto.h:514
+msgid "stop speaking"
+msgstr "停止朗讀"
+
+#. xgettext: This is the description of the BRL_STOP command.
+#: Programs/cmds.auto.h:908
+msgid "stop the braille driver"
+msgstr "停止點字驅動"
+
+#. xgettext: This is the description of the SCR_STOP command.
+#: Programs/cmds.auto.h:936
+msgid "stop the screen driver"
+msgstr "停止螢幕驅動"
+
+#. xgettext: This is the description of the SPK_STOP command.
+#: Programs/cmds.auto.h:922
+msgid "stop the speech driver"
+msgstr "停止語音驅動"
+
+#: Programs/xbrlapi.c:656
+msgid "strange old error handler\n"
+msgstr "奇怪的老舊錯誤處理程序\n"
+
+#: Programs/brltty-cldr.c:39
+msgid "string"
+msgstr "弦樂"
+
+#. xgettext: This is the description of the CONTEXT command.
+#: Programs/cmds.auto.h:1495
+msgid "switch to command context"
+msgstr "切換到命令關聯"
+
+#. xgettext: This is the description of the SWITCHVT command.
+#: Programs/cmds.auto.h:1247
+msgid "switch to specific virtual terminal"
+msgstr "切換到特定的虛擬終端機"
+
+#. xgettext: This is the description of the GUI_AREA_NEXT command.
+#: Programs/cmds.auto.h:1133
+msgid "switch to the next screen area"
+msgstr "切換到下一個畫面"
+
+#. xgettext: This is the description of the SWITCHVT_NEXT command.
+#: Programs/cmds.auto.h:585
+msgid "switch to the next virtual terminal"
+msgstr "切換到下一個虛擬終端機"
+
+#. xgettext: This is the description of the GUI_AREA_PREV command.
+#: Programs/cmds.auto.h:1126
+msgid "switch to the previous screen area"
+msgstr "切換到上一個畫面"
+
+#. xgettext: This is the description of the SWITCHVT_PREV command.
+#: Programs/cmds.auto.h:578
+msgid "switch to the previous virtual terminal"
+msgstr "切換到上一個虛擬終端機"
+
+#: Programs/pgmprivs_linux.c:1994
+msgid "switched to unprivileged user"
+msgstr "切換到未授權使用者"
+
+#. xgettext: This is the description of the KEY_TAB command.
+#: Programs/cmds.auto.h:1519
+msgid "tab key"
+msgstr "tab 鍵"
+
+#: Programs/config.c:300 Programs/config.c:307
+msgid "text"
+msgstr "文字"
+
+#: Programs/msgtest.c:45
+msgid "the locale directory containing the translations"
+msgstr "含有翻譯的語言目錄"
+
+#: Programs/msgtest.c:52
+msgid "the locale in which to look up a translation"
+msgstr "查詢翻譯的語言"
+
+#: Programs/msgtest.c:59
+msgid "the name of the domain containing the translations"
+msgstr "含有翻譯的種類名稱"
+
+#. xgettext: This is the description of the PASSDOTS command.
+#: Programs/cmds.auto.h:1463
+msgid "type braille dots"
+msgstr "輸入點字點"
+
+#. xgettext: This is the description of the PASSCHAR command.
+#: Programs/cmds.auto.h:1454
+msgid "type unicode character"
+msgstr "輸入 unicode 字元"
+
+#: Programs/xbrlapi.c:974
+msgid "unexpected block type"
+msgstr "未預期的阻擋輸入"
+
+#: Programs/xbrlapi.c:864
+msgid "unexpected cmd"
+msgstr "不預期的指令"
+
+#: Programs/cmd_queue.c:157
+msgid "unhandled command"
+msgstr "無法掌控的命令"
+
+#: Programs/cmd.c:198
+msgid "unknown command"
+msgstr "未知的命令"
+
+#: Programs/options.c:823
+msgid "unknown configuration directive"
+msgstr "未知的設定敘述"
+
+#: Programs/config.c:735
+msgid "unknown log level or category"
+msgstr "未知的記錄等級或分類"
+
+#. an unknown option has been specified
+#: Programs/options.c:570
+msgid "unknown option"
+msgstr "未知的選向"
+
+#: Programs/parse.c:501
+msgid "unsupported parameter"
+msgstr "不支援的參數"
+
+#: Programs/spk.c:180
+msgid "volume"
+msgstr "音量"
+
+#. LRGB
+#: Programs/cmd_utils.c:159
+msgid "white"
+msgstr "白"
+
+#: Programs/pgmprivs_linux.c:1856
+msgid "working directory changed"
+msgstr "工作目錄變更"
+
+#: Programs/msgtest.c:65
+msgid "write the translations using UTF-8"
+msgstr "用 UTF-8 撰寫翻譯"
+
+#: Programs/xbrlapi.c:911
+#, c-format
+msgid "xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"
+msgstr "xbrlapi: 無法找到按鍵碼來重設模擬未綁定的按鍵符號 %08X\n"
+
+#: Programs/xbrlapi.c:898
+#, c-format
+msgid "xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"
+msgstr "xbrlapi: 無法找到組合見來套用到 %d 以取得按鍵符號 %08X\n"
+
+#: Programs/xbrlapi.c:874
+#, c-format
+msgid "xbrlapi: Couldn't translate keysym %08X to keycode.\n"
+msgstr "xbrlapi: 無法翻譯按鍵符號 %08X 到按鍵碼\n"
+
+#: Programs/xbrlapi.c:415
+#, c-format
+msgid "xbrlapi: X Error %d, %s on display %s\n"
+msgstr "xbrlapi: X 錯誤 %d, %s 在顯示 %s\n"
+
+#: Programs/xbrlapi.c:457
+#, c-format
+msgid "xbrlapi: bad format for VT number\n"
+msgstr "xbrlapi: 不良的 VT 編號格式\n"
+
+#: Programs/xbrlapi.c:460
+#, c-format
+msgid "xbrlapi: bad type for VT number\n"
+msgstr "xbrlapi: 不良的 VT 編號類型\n"
+
+#: Programs/xbrlapi.c:439
+#, c-format
+msgid "xbrlapi: cannot get root window XFree86_VT property\n"
+msgstr "xbrlapi: 無法獲取跟視窗 XFree86_VT 內容\n"
+
+#: Programs/xbrlapi.c:316
+#, c-format
+msgid "xbrlapi: cannot write window name %s\n"
+msgstr "xbrlapi: 無法寫入視窗名稱 %s\n"
+
+#: Programs/xbrlapi.c:764
+#, c-format
+msgid "xbrlapi: didn't grab parent of %#010lx\n"
+msgstr "xbrlapi: 無法擷取 %#010lx 的上層\n"
+
+#: Programs/xbrlapi.c:782
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx\n"
+msgstr "xbrlapi: 沒有擷取視窗 %#010lx\n"
+
+#: Programs/xbrlapi.c:582
+#, c-format
+msgid "xbrlapi: didn't grab window %#010lx but got focus\n"
+msgstr "xbrlapi: 沒有擷取視窗 %#010lx 不過得到焦點\n"
+
+#: Programs/xbrlapi.c:448
+#, c-format
+msgid "xbrlapi: more than one item for VT number\n"
+msgstr "xbrlapi: 多了一個 VT 編號\n"
+
+#: Programs/xbrlapi.c:433
+#, c-format
+msgid "xbrlapi: no XFree86_VT atom\n"
+msgstr "xbrlapi: 沒有 XFree86_VT atom\n"
+
+#: Programs/xbrlapi.c:444
+#, c-format
+msgid "xbrlapi: no items for VT number\n"
+msgstr "xbrlapi: 沒有 VT 編號\n"
+
+#: Programs/xbrlapi.c:416
+#, c-format
+msgid "xbrlapi: resource %#010lx, req %u:%u\n"
+msgstr "xbrlapi: 資源 %#010lx, req %u:%u\n"
+
+#. "shouldn't happen" events
+#: Programs/xbrlapi.c:811
+#, c-format
+msgid "xbrlapi: unhandled event type: %d\n"
+msgstr "xbrlapi: 無法處理的事件型態: %d\n"
+
+#: Programs/xbrlapi.c:790
+#, c-format
+msgid "xbrlapi: window %#010lx changed to NULL name\n"
+msgstr "xbrlapi: 視窗 %#010lx 變更為沒有名稱\n"
+
+#. LRG
+#: Programs/cmd_utils.c:158
+msgid "yellow"
+msgstr "黃"
+
+#~ msgid "6-Dot Computer"
+#~ msgstr "六點電腦點字"
+
+#~ msgid "6-Dot Contracted"
+#~ msgstr "六點縮寫點字"
+
+#~ msgid "8-Dot Computer"
+#~ msgstr "八點電腦點字"
+
+#~ msgid "8-Dot Contracted"
+#~ msgstr "八點縮寫點字"
+
+#~ msgid "Alphabetic Braille Window Coordinates"
+#~ msgstr "字母點字視窗座標"
+
+#~ msgid "Alphabetic Screen Cursor Coordinates"
+#~ msgstr "字母游標位置"
+
+#~ msgid "Alto Sax"
+#~ msgstr "中音薩克斯風"
+
+#~ msgid "April"
+#~ msgstr "四月"
+
+#~ msgid "Attributes Invisible Time"
+#~ msgstr "屬性隱藏時間"
+
+#~ msgid "Attributes Visible Time"
+#~ msgstr "屬性顯示時間"
+
+#~ msgid "August"
+#~ msgstr "八月"
+
+#~ msgid "Baritone Sax"
+#~ msgstr "低音薩克斯風"
+
+#~ msgid "Braille Window Column"
+#~ msgstr "點字視窗欄位"
+
+#~ msgid "Braille Window Coordinates"
+#~ msgstr "點字視窗座標"
+
+#~ msgid "Braille Window Row"
+#~ msgstr "點字視窗行"
+
+#~ msgid "Capitals Invisible Time"
+#~ msgstr "大寫隱藏時間"
+
+#~ msgid "Capitals Visible Time"
+#~ msgstr "大寫顯示時間"
+
+#~ msgid "Clavi"
+#~ msgstr "大鍵琴"
+
+#~ msgid "December"
+#~ msgstr "十二月"
+
+#~ msgid "February"
+#~ msgstr "二月"
+
+#~ msgid "January"
+#~ msgstr "一月"
+
+#~ msgid "July"
+#~ msgstr "七月"
+
+#~ msgid "June"
+#~ msgstr "六月"
+
+#~ msgid "March"
+#~ msgstr "三月"
+
+#~ msgid "May"
+#~ msgstr "五嶽"
+
+#~ msgid "November"
+#~ msgstr "十一月"
+
+#~ msgid "October"
+#~ msgstr "十月"
+
+#~ msgid "Path to directory which contains message translations."
+#~ msgstr "包含可升級檔案的目錄路徑"
+
+#~ msgid "Screen Cursor Column"
+#~ msgstr "螢幕游標欄位"
+
+#~ msgid "Screen Cursor Coordinates"
+#~ msgstr "螢幕游標座標"
+
+#~ msgid "Screen Cursor Invisible Time"
+#~ msgstr "螢幕游標隱藏時間"
+
+#~ msgid "Screen Cursor Row"
+#~ msgstr "螢幕游標行"
+
+#~ msgid "Screen Cursor Visible Time"
+#~ msgstr "螢幕游標顯示時間"
+
+#~ msgid "Screen Cursor and Braille Window Column"
+#~ msgstr "螢幕游標與點字視窗欄位"
+
+#~ msgid "Screen Cursor and Braille Window Row"
+#~ msgstr "螢幕游標與點字視窗行"
+
+#~ msgid "September"
+#~ msgstr "九月"
+
+#~ msgid "Soprano Sax"
+#~ msgstr "高音薩克斯風"
+
+#~ msgid "Speech Cursor Invisible Time"
+#~ msgstr "語音游標隱藏時間"
+
+#~ msgid "Speech Cursor Visible Time"
+#~ msgstr "語音游標顯示時間"
+
+#~ msgid "Tenor Sax"
+#~ msgstr "中音薩克斯風"
+
+#~ msgid "blink"
+#~ msgstr "跳動"
+
+#~ msgid "list all of the translations (the default)"
+#~ msgstr "列出所有翻譯(預設)"
+
+#~ msgid "percentage"
+#~ msgstr "百分比"
+
+#~ msgid "second"
+#~ msgid_plural "seconds"
+#~ msgstr[0] "秒"
+#~ msgstr[1] "秒"
+
+#~ msgid "show the message count"
+#~ msgstr "顯示訊息數量"
+
+#~ msgid "show the translation metadata"
+#~ msgstr "顯示翻譯原始資料"
+
+#~ msgid "6-Dot Computer Braille"
+#~ msgstr "六點電腦點字"
+
+#~ msgid "8-Dot Computer Braille"
+#~ msgstr "八點電腦點字"
+
+#~ msgid "Literary Braille"
+#~ msgstr "書面點字"
+
+#~ msgid "Text Style"
+#~ msgstr "文自行太"
+
+#~ msgid "Bug Reports"
+#~ msgstr "錯誤報告"
+
+#~ msgid "Braille Display Orientation"
+#~ msgstr "點字顯示器導向"
+
+#~ msgid "Braille Input Mode"
+#~ msgstr "點字輸入模式"
+
+#~ msgid "Braille Keyboard Enabled"
+#~ msgstr "點字鍵盤啟用"
+
+#~ msgid "Normal"
+#~ msgstr "正常"
+
+#~ msgid "Rotated"
+#~ msgstr "循環"
+
+#~ msgid "合成銅管 2"
+#~ msgstr "SynthBrass 2"
+
+#~ msgid "小提琴"
+#~ msgstr "小提琴"
+
+#~ msgid "Check for undefined characters."
+#~ msgstr "檢查未定義的字元"
+
+#~ msgid "Blinking Cursor"
+#~ msgstr "游標跳動"
+
+#~ msgid "Cursor Invisible Time"
+#~ msgstr "游標隱藏時間"
+
+#~ msgid "Cursor Style"
+#~ msgstr "游標形狀"
+
+#~ msgid "Cursor Visible Time"
+#~ msgstr "游標顯示時間"
+
+#~ msgid "Dirvers Directory"
+#~ msgstr "驅動程式目錄"
+
+#~ msgid "Show Cursor"
+#~ msgstr "顯示游標"
+
+#~ msgid "State Directory"
+#~ msgstr "狀態目錄"
+
+#~ msgid "Window Follows Pointer"
+#~ msgstr "視窗跟隨指標"
+
+#~ msgid "drag cursor"
+#~ msgstr "拖曳游標"
+
+#~ msgid "go up to last line of previous paragraph"
+#~ msgstr "跳道上一段最後一行"
+
+#~ msgid "left margin"
+#~ msgstr "左邊界"
+
+#~ msgid "normalized position"
+#~ msgstr "正常化位置"
+
+#~ msgid "set control modifier of next typed character or emulated key on/off"
+#~ msgstr "設定為輸入字元家 control 或模擬鍵盤開關"
+
+#~ msgid "set cursor tracking on/off"
+#~ msgstr "設定游標追蹤開關"
+
+#~ msgid "set meta modifier of next typed character or emulated key on/off"
+#~ msgstr "設定輸入字元家 alt 或模擬鍵盤開關"
+
+#~ msgid "set shift modifier of next typed character or emulated key on/off"
+#~ msgstr "設定輸入字元家 shift 或模擬鍵盤開關"
+
+#~ msgid "set speech location visibility on/off"
+#~ msgstr "設定與因為至顯示開關"
+
+#~ msgid "set upper modifier of next typed character or emulated key on/off"
+#~ msgstr "將將輸入字元設為上位符號或模擬鍵盤開關"
+
+#~ msgid "switch to virtual terminal"
+#~ msgstr "切換到虛擬終端機"
+
+#~ msgid "Help Path"
+#~ msgstr "求助管道"
+
+#~ msgid "emulate special key"
+#~ msgstr "模擬特殊鍵"
+
+#~ msgid "Braille window update interval [4]."
+#~ msgstr "點字視窗更新間隔 [4]."
+
+#~ msgid "Command Learning mode"
+#~ msgstr "指令學習模式"
+
+#~ msgid "Device specifier for soundcard digital audio."
+#~ msgstr "音效卡數位音效的設備指定碼"
+
+#~ msgid "Device specifier for the Musical Instrument Digital Interface."
+#~ msgstr "樂器數位界面的設備指定碼"
+
+#~ msgid "Diagnostic logging level: %s, or one of {%s}"
+#~ msgstr "診斷記錄等級: %s, 或 {%s} 之一"
+
+#~ msgid "Log cursor routine"
+#~ msgstr "記錄游標定位"
+
+#~ msgid "Path to attributes translation table file."
+#~ msgstr "屬性翻譯表檔案路徑"
+
+#~ msgid "Path to directory for text and attributes tables."
+#~ msgstr "文字與屬性表所在目錄路徑"
+
+#~ msgid "Path to text translation table file."
+#~ msgstr "文字翻譯檔路徑"
+
+#~ msgid "freeze/unfreeze screen image"
+#~ msgstr "凍結/解動螢幕映像"
+
+#~ msgid "invalid retain dots setting"
+#~ msgstr "無效的維持點設定"
+
+#~ msgid "invalid update interval"
+#~ msgstr "無效的更新間隔"
+
+#~ msgid "level"
+#~ msgstr "等級"
+
+#~ msgid "operand not supplied for configuration directive"
+#~ msgstr "設定敘述未提供運算員"
+
+#~ msgid "put random password into clipboard"
+#~ msgstr "置放隨機密碼到剪貼不"
+
+#~ msgid "set shift modifier next character or emulate key on/off"
+#~ msgstr "設定輸入字元換位或模擬鍵盤開關"
+
+#~ msgid "too many operands for configuration directive"
+#~ msgstr "過多的設定敘述運算員"
diff --git a/Patches/screen-4.0.1.patch b/Patches/screen-4.0.1.patch
new file mode 100644
index 0000000..b57caf2
--- /dev/null
+++ b/Patches/screen-4.0.1.patch
@@ -0,0 +1,379 @@
+--- configure.in.orig	2003-06-03 07:58:24.000000000 -0400
++++ configure.in	2006-07-24 01:21:24.000000000 -0400
+@@ -1193,17 +1193,17 @@
+   exit(0); /* libc version works properly.  */
+ }], AC_DEFINE(USEMEMCPY))
+ 
+-AC_MSG_CHECKING(long file names)
+-(echo 1 > /tmp/conftest9012345) 2>/dev/null
+-(echo 2 > /tmp/conftest9012346) 2>/dev/null
+-val=`cat /tmp/conftest9012345 2>/dev/null`
+-if test -f /tmp/conftest9012345 && test "$val" = 1; then
+-AC_MSG_RESULT(yes)
+-else
+-AC_MSG_RESULT(no)
+-AC_DEFINE(NAME_MAX, 14)
+-fi
+-rm -f /tmp/conftest*
++#AC_MSG_CHECKING(long file names)
++#(echo 1 > /tmp/conftest9012345) 2>/dev/null
++#(echo 2 > /tmp/conftest9012346) 2>/dev/null
++#val=`cat /tmp/conftest9012345 2>/dev/null`
++#if test -f /tmp/conftest9012345 && test "$val" = 1; then
++#AC_MSG_RESULT(yes)
++#else
++#AC_MSG_RESULT(no)
++#AC_DEFINE(NAME_MAX, 14)
++#fi
++#rm -f /tmp/conftest*
+ 
+ AC_MSG_CHECKING(for vsprintf)
+ AC_TRY_LINK(,[vsprintf(0,0,0);], AC_MSG_RESULT(yes);AC_DEFINE(USEVARARGS), AC_MSG_RESULT(no))
+--- Makefile.in.orig	2003-12-05 08:59:39.000000000 -0500
++++ Makefile.in	2006-07-24 01:22:51.000000000 -0400
+@@ -46,7 +46,12 @@
+ # -DDUMPSHADOW
+ #	With shadow-pw screen would never dump core. Use this option if
+ #	you still want to have a core. Use only for debugging.
+-OPTIONS=
++# -DIPC_EXPORT_IMAGE
++#	Allows an other program to get the screen content through shared mem 
++#	 and ipc. This is used e.g. for braille and speech software.
++
++OPTIONS=-DIPC_EXPORT_IMAGE
++#OPTIONS=
+ #OPTIONS= -DDEBUG
+ 
+ SHELL=/bin/sh
+--- extern.h.orig	2003-08-22 08:27:57.000000000 -0400
++++ extern.h	2006-08-09 11:35:42.000000000 -0400
+@@ -139,6 +139,15 @@
+ extern void  FreePseudowin __P((struct win *));
+ #endif
+ extern void  nwin_compose __P((struct NewWindow *, struct NewWindow *, struct NewWindow *));
++
++#ifdef IPC_EXPORT_IMAGE
++extern void SetWinImage __P((const char *, unsigned char *));
++extern void CopyWinImage __P((struct win *, unsigned char *));
++extern int IsInputLayer __P((struct layer *));
++extern int GetInputPosition __P((struct layer *));
++extern void CopyInputLine __P((struct layer *, char *, int));
++#endif /* IPC_EXPORT_IMAGE */
++
+ extern int   DoStartLog __P((struct win *, char *, int));
+ extern int   ReleaseAutoWritelock __P((struct display *, struct win *));
+ extern int   ObtainAutoWritelock __P((struct display *, struct win *));
+--- screen.h.orig	2003-08-22 08:28:43.000000000 -0400
++++ screen.h	2006-07-24 02:04:06.000000000 -0400
+@@ -288,6 +288,10 @@
+   int sym;	/* symbol defined in ttydev.h */
+ };
+ 
++#ifdef IPC_EXPORT_IMAGE
++extern unsigned char *shm;		  /* pointer to shared memory segment */
++#endif
++
+ /*
+  * windowlist orders
+  */
+--- screen.c.orig	2003-09-08 10:26:41.000000000 -0400
++++ screen.c	2006-07-25 10:55:24.000000000 -0400
+@@ -71,6 +71,14 @@
+ #if (defined(AUX) || defined(_AUX_SOURCE)) && defined(POSIX)
+ # include <compat.h>
+ #endif
++
++#ifdef IPC_EXPORT_IMAGE
++# include <sys/ipc.h>
++# include <sys/shm.h>
++#endif
++
++
++
+ #if defined(USE_LOCALE) || defined(ENCODINGS)
+ # include <locale.h>
+ #endif
+@@ -78,6 +86,11 @@
+ # include <langinfo.h>
+ #endif
+ 
++#ifdef IPC_EXPORT_IMAGE
++# include <sys/ipc.h>
++# include <sys/shm.h>
++#endif
++
+ #include "screen.h"
+ #ifdef HAVE_BRAILLE
+ # include "braille.h"
+@@ -234,6 +247,12 @@
+ 
+ 
+ 
++
++#ifdef IPC_EXPORT_IMAGE
++unsigned char *shm;              /* pointer to shared memory segment */
++#endif
++
++
+ /*
+  * Do this last
+  */
+@@ -461,6 +480,40 @@
+   zmodem_recvcmd = SaveStr("!!! rz -vv -b -E");
+ #endif
+ 
++#ifdef IPC_EXPORT_IMAGE
++  {
++    const char *path;
++    key_t key;
++    int shmid;
++
++    /* allow for the header, text, attributes, and auxiliary data
++     * (assuming no screen will be bigger than 132x66)
++     */
++    size_t size = 18000; /*  */
++
++    path = getenv("HOME");
++    if (!path || !*path) path = "/";
++    if ((key = ftok(path, 'b')) == -1) key = 0XBACD072F;
++
++    shmid = shmget( key, size, IPC_CREAT | S_IRWXU );
++    if( shmid < 0 )
++      {
++        Panic( errno, "shmget" );
++        /* NOTREACHED */
++      }
++
++    shm = shmat( shmid, 0, 0);
++    if ( shm == (void*)-1 )
++      {
++        Panic( errno, "shmat" );
++        /* NOTREACHED */
++      }
++
++    /* put valid data into the image */
++    SetWinImage( "screen is initializing...", shm );
++  }
++#endif
++
+ #ifdef COPY_PASTE
+   CompileKeys((char *)0, 0, mark_key_tab);
+ #endif
+--- sched.c.orig	2003-09-08 10:26:36.000000000 -0400
++++ sched.c	2006-07-24 01:33:39.000000000 -0400
+@@ -110,6 +110,10 @@
+   return min;
+ }
+ 
++#ifdef IPC_EXPORT_IMAGE
++ extern struct win *windows;
++#endif
++
+ void
+ sched()
+ {
+@@ -121,6 +125,11 @@
+ 
+   for (;;)
+     {
++#ifdef IPC_EXPORT_IMAGE
++      /* export image from last used window which is on top of the list */
++      CopyWinImage( windows, shm );
++#endif
++
+       if (calctimeout)
+ 	timeoutev = calctimo();
+       if (timeoutev)
+--- input.c.orig	2006-08-09 10:40:10.000000000 -0400
++++ input.c	2006-08-09 11:42:39.000000000 -0400
+@@ -400,3 +400,49 @@
+   return 0;
+ }
+ 
++#ifdef IPC_EXPORT_IMAGE
++int
++IsInputLayer(l)
++struct layer *l;
++{
++  return l->l_layfn == &InpLf;
++}
++
++int
++GetInputPosition(l)
++struct layer *l;
++{
++  struct inpdata *inpdata = (struct inpdata *)l->l_data;
++  return inpdata->inpstringlen + inpdata->inp.pos;
++}
++
++void
++CopyInputLine(l, dest, width)
++struct layer *l;
++char *dest;
++int width;
++{
++  struct inpdata *inpdata = (struct inpdata *)l->l_data;
++  char *src;
++  int len;
++
++  for
++  ( 
++    src = inpdata->inpstring, len = inpdata->inpstringlen;
++    width && len;
++    *dest++ = *src++, len--, width--
++  );
++
++  if( !(inpdata->inpmode & INP_NOECHO) )
++    {
++      for
++      ( 
++        src = inpdata->inp.buf, len = inpdata->inp.len;
++        width && len;
++        *dest++ = *src++, len--, width--
++      );
++    }
++
++  while( width ) *dest++ = ' ', width--;
++}
++#endif	/* IPC_EXPORT_IMAGE */
+--- window.c.orig	2003-12-05 08:45:41.000000000 -0500
++++ window.c	2006-08-09 11:34:20.000000000 -0400
+@@ -1993,6 +1993,138 @@
+     }
+ }
+ 
++
++#ifdef IPC_EXPORT_IMAGE
++
++void
++SetWinImage( msg, dest )
++const char *msg;
++unsigned char *dest;
++{
++  unsigned char *d = dest;
++
++  *d++ = 80;   /* screen width */
++  *d++ = 1;    /* screen height */
++  *d++ = 0;    /* cursor column */
++  *d++ = 0;    /* cursor row */
++
++  {
++    size_t count = dest[0] * dest[1];
++
++    memset( d, ' ', count );
++    strcpy( d, msg );
++    d[strlen(d)] = ' ';
++    d += count;
++
++    memset( d, 0X07, count );
++    d += count;
++  }
++
++  *d++ = 0;    /* window number */
++  *d++ = 0;    /* flags */
++}
++
++
++void
++CopyWinImage( p, dest )
++struct win *p;
++unsigned char *dest;
++{
++  register unsigned char *s, *d = dest, *m;
++  register int x, y;
++  struct display *display = p->w_lastdisp;
++  int st = (display && D_status) ? 1 : 0;
++  int in = IsInputLayer(p->w_savelayer) ? 1 : 0;
++
++  if( p && p->w_mlines )
++    {
++      *d++ = p->w_width;                        /* screen width */
++      *d++ = p->w_height + (st | in);                  /* screen height */
++      *d++ = st? D_status_len:                  /* cursor column */
++             in? GetInputPosition(p->w_savelayer):
++                 p->w_x;
++      *d++ = (st || in)? p->w_height: p->w_y;       /* cursor row */
++
++      /* copy window image to buffer */
++      for( y = 0; y < p->w_height; y++ )
++        {
++          s = p->w_mlines[y].image;
++          x = p->w_width;
++          for( ; x; *d++ = *s++, x-- );
++        }
++
++      /* copy status line to buffer */
++      if( st )
++        {
++          s = D_status_lastmsg;
++          x = p->w_width;
++          for( ; *s && x; *d++ = *s++, x-- );
++          for( ; x; *d++ = ' ', x-- );
++        }
++      else if (in)
++        {
++          CopyInputLine(p->w_savelayer, d, p->w_width);
++          d += p->w_width;
++        }
++
++      /* copy attributes from window image to buffer */
++#ifdef COLOR
++      for( y = 0; y < p->w_height; y++ )
++        {
++          struct mline *ml = &p->w_mlines[y];
++          x = 0;
++          for( ; x<p->w_width; x++ )
++            {
++              static const unsigned char tr[] =
++                {
++                  0X0, 0X4, 0X2, 0X6, 0X1, 0X5, 0X3, 0X7,
++                  0X8, 0XC, 0XA, 0XE, 0X9, 0XD, 0XB, 0XF
++                };
++
++              struct mchar mc;
++              int fg;
++              int bg;
++
++              copy_mline2mchar( &mc, ml, x );
++              fg = rend_getfg(&mc);
++              bg = rend_getbg(&mc);
++
++              fg = fg? tr[coli2e(fg) & 0XF]: 0X7;
++              bg = bg? tr[coli2e(bg) & 0XF]: 0X0;
++              *d++ = fg | (bg << 4);
++            }
++        }
++
++      if( st || in )
++        {
++          memset(d, (st ? 0X70 : 0X07), p->w_width);
++          d += p->w_width;
++        }
++#else /* COLOR */
++      {
++        int count = dest[0] * dest[1];
++        memset(d, 0X07, count);
++        d += count;
++      }
++#endif /* COLOR */
++
++      *d++ = p->w_number;  /* window number */
++
++      *d = 0;  /* flags */
++      if (p->w_cursorkeys) *d |= 0X01; /* cursor keys are in application mode */
++      if (p->w_keypad) *d |= 0X02; /* keypad is in application mode */
++      d++;
++    }
++  else
++    {
++      /* no window pointer */
++      SetWinImage( "no active scren", dest );
++    }
++}
++
++#endif	/* IPC_EXPORT_IMAGE */
++ 
++
+ #ifdef ZMODEM
+ 
+ static int
diff --git a/Patches/screen-4.0.1.txt b/Patches/screen-4.0.1.txt
new file mode 100644
index 0000000..a3696d5
--- /dev/null
+++ b/Patches/screen-4.0.1.txt
@@ -0,0 +1,66 @@
+Introduction
+============
+
+Shared memory is another method for BRLTTY to get the content of the screen.
+For this method you need two components:
+
+*  Some other application needs to maintain the shared screen image. This can 
+   be done via the screen program when augmented by a BRLTTY-supplied patch.
+*  BRLTTY needs to be able to view the shared screen image. This is done via
+   its Screen screen driver.
+
+The original purpose of screen was to run different "screens" on a single
+terminal. It also supports features like copy-and-paste, a scrollback buffer,
+etc. Screen supports lots of terminal types, especially XTERM. This is
+important because it runs under X-Windows which is part of most unixes.
+
+
+Instructions
+============
+
+1) Build and install BRLTTY:
+
+      ./configure
+      make
+      make install
+   
+   BRLTTY's Screen screen driver will be built by default. Be sure not to 
+   explicitly exclude it via, for example, a configure option like:
+
+      --with-screen-driver=-sc
+
+2) Get the source for screen from: 
+
+      ftp://ftp.uni-erlangen.de/pub/utilities/screen/screen-4.0.1.tar.gz
+
+3) Unpack the source:
+
+      tar xzf /path/to/screen-4.0.1.tar.gz
+
+4) Change to the source tree:
+
+      cd screen-4.0.1
+
+5) Apply the patch: 
+
+      patch -p0 </path/to/brltty/Patches/screen-4.0.1.patch
+
+6) Build screen:
+
+      ./configure
+      make
+      make install
+
+7) Run screen, and then brltty (specifying its Screen screen driver): 
+
+      screen
+      brltty -x sc
+
+   The only order dependency is that screen must be run first the first time in
+   order to get the shared memory segment created. Although brltty can be run
+   first from then on, the shared memory image will, of course, be stale until
+   screen is started.
+
+
+BRLTTY's screen patch was originally developed by Rudolf Weeber
+<rudolf.weeber@gmx.de>.
diff --git a/Patches/screen-4.2.1.patch b/Patches/screen-4.2.1.patch
new file mode 100644
index 0000000..a99f9da
--- /dev/null
+++ b/Patches/screen-4.2.1.patch
@@ -0,0 +1,290 @@
+--- extern.h.org	2014-09-15 21:02:12.000000000 +0200
++++ extern.h	2014-09-15 21:11:27.000000000 +0200
+@@ -33,12 +33,19 @@
+ #endif
+ 
+ /* screen.c */
++
++extern void SetWinImage __P((const char *, unsigned char *));
++extern void CopyWinImage __P((struct win *, unsigned char *));
++extern int IsInputLayer __P((struct layer *));
++extern int GetInputPosition __P((struct layer *));
++extern void CopyInputLine __P((struct layer *, char *, int));
+ extern int   main __P((int, char **));
+ extern sigret_t SigHup __P(SIGPROTOARG);
+ extern void  eexit __P((int)) __attribute__((__noreturn__));
+ extern void  Detach __P((int));
+ extern void  Hangup __P((void));
+ extern void  Kill __P((int, int));
++
+ #ifdef USEVARARGS
+ extern void  Msg __P((int, const char *, ...)) __attribute__((format(printf, 2, 3)));
+ extern void  Panic __P((int, const char *, ...)) __attribute__((format(printf, 2, 3))) __attribute__((__noreturn__));
+--- screen.c.org	2014-09-15 21:04:33.000000000 +0200
++++ screen.c	2014-09-15 22:20:51.000000000 +0200
+@@ -52,6 +52,11 @@
+ # include <sys/ioctl.h>
+ #endif
+ 
++/* export ipc image */
++#include <sys/ipc.h>
++#include <sys/shm.h>
++unsigned char *shm;
++/* end export ipc image */
+ #ifndef SIGINT
+ # include <signal.h>
+ #endif
+@@ -489,6 +494,40 @@
+ #ifdef HAVE_BRAILLE
+   InitBraille();
+ #endif
++
++/* export ipc image */
++  {
++    const char *path;
++    key_t key;
++    int shmid;
++
++    /* allow for the header, text, attributes, and auxiliary data
++     * (assuming no screen will be bigger than 132x66)
++     */
++    size_t size = 18000; /*  */
++
++    path = getenv("HOME");
++    if (!path || !*path) path = "/";
++    if ((key = ftok(path, 'b')) == -1) key = 0XBACD072F;
++
++    shmid = shmget( key, size, IPC_CREAT | S_IRWXU );
++    if( shmid < 0 )
++      {
++        Panic( errno, "shmget" );
++        /* NOTREACHED */
++      }
++
++    shm = shmat( shmid, 0, 0);
++    if ( shm == (void*)-1 )
++      {
++        Panic( errno, "shmat" );
++        /* NOTREACHED */
++      }
++
++    /* put valid data into the image */
++    SetWinImage( "screen is initializing...", shm );
++  }
++/* end export ipc image */
+ #ifdef ZMODEM
+   zmodem_sendcmd = SaveStr("!!! sz -vv -b ");
+   zmodem_recvcmd = SaveStr("!!! rz -vv -b -E");
+--- sched.c.org	2014-09-15 21:04:07.000000000 +0200
++++ sched.c	2014-09-15 22:54:07.000000000 +0200
+@@ -115,6 +115,12 @@
+   return min;
+ }
+ 
++/* export ipc image */
++#include <sys/shm.h>
++unsigned char *shm;
++extern struct win *windows;
++/* end export ipc image */
++
+ void
+ sched()
+ {
+@@ -126,6 +132,9 @@
+ 
+   for (;;)
+     {
++/* export image from last used window which is on top of the list */
++      CopyWinImage( windows, shm );
++/* end export ipc image */
+       if (calctimeout)
+ 	timeoutev = calctimo();
+       if (timeoutev)
+--- window.c.org	2014-09-15 21:05:18.000000000 +0200
++++ window.c	2014-09-15 22:33:15.000000000 +0200
+@@ -2082,6 +2082,134 @@
+     }
+ }
+ 
++/* export ipc image */
++void
++SetWinImage( msg, dest )
++const char *msg;
++unsigned char *dest;
++{
++  unsigned char *d = dest;
++
++  *d++ = 80;   /* screen width */
++  *d++ = 1;    /* screen height */
++  *d++ = 0;    /* cursor column */
++  *d++ = 0;    /* cursor row */
++
++  {
++    size_t count = dest[0] * dest[1];
++
++    memset( d, ' ', count );
++    strcpy( d, msg );
++    d[strlen(d)] = ' ';
++    d += count;
++
++    memset( d, 0X07, count );
++    d += count;
++  }
++
++  *d++ = 0;    /* window number */
++  *d++ = 0;    /* flags */
++}
++
++
++void
++CopyWinImage( p, dest )
++struct win *p;
++unsigned char *dest;
++{
++  register unsigned char *s, *d = dest, *m;
++  register int x, y;
++  struct display *display = p->w_lastdisp;
++  int st = (display && D_status) ? 1 : 0;
++  int in = IsInputLayer(p->w_savelayer) ? 1 : 0;
++
++  if( p && p->w_mlines )
++    {
++      *d++ = p->w_width;                        /* screen width */
++      *d++ = p->w_height + (st | in);                  /* screen height */
++      *d++ = st? D_status_len:                  /* cursor column */
++             in? GetInputPosition(p->w_savelayer):
++                 p->w_x;
++      *d++ = (st || in)? p->w_height: p->w_y;       /* cursor row */
++
++      /* copy window image to buffer */
++      for( y = 0; y < p->w_height; y++ )
++        {
++          s = p->w_mlines[y].image;
++          x = p->w_width;
++          for( ; x; *d++ = *s++, x-- );
++        }
++
++      /* copy status line to buffer */
++      if( st )
++        {
++          s = D_status_lastmsg;
++          x = p->w_width;
++          for( ; *s && x; *d++ = *s++, x-- );
++          for( ; x; *d++ = ' ', x-- );
++        }
++      else if (in)
++        {
++          CopyInputLine(p->w_savelayer, d, p->w_width);
++          d += p->w_width;
++        }
++
++      /* copy attributes from window image to buffer */
++#ifdef COLOR
++      for( y = 0; y < p->w_height; y++ )
++        {
++          struct mline *ml = &p->w_mlines[y];
++          x = 0;
++          for( ; x<p->w_width; x++ )
++            {
++              static const unsigned char tr[] =
++                {
++                  0X0, 0X4, 0X2, 0X6, 0X1, 0X5, 0X3, 0X7,
++                  0X8, 0XC, 0XA, 0XE, 0X9, 0XD, 0XB, 0XF
++                };
++
++              struct mchar mc;
++              int fg;
++              int bg;
++
++              copy_mline2mchar( &mc, ml, x );
++              fg = rend_getfg(&mc);
++              bg = rend_getbg(&mc);
++
++              fg = fg? tr[coli2e(fg) & 0XF]: 0X7;
++              bg = bg? tr[coli2e(bg) & 0XF]: 0X0;
++              *d++ = fg | (bg << 4);
++            }
++        }
++
++      if( st || in )
++        {
++          memset(d, (st ? 0X70 : 0X07), p->w_width);
++          d += p->w_width;
++        }
++#else /* COLOR */
++      {
++        int count = dest[0] * dest[1];
++        memset(d, 0X07, count);
++        d += count;
++      }
++#endif /* COLOR */
++
++      *d++ = p->w_number;  /* window number */
++
++      *d = 0;  /* flags */
++      if (p->w_cursorkeys) *d |= 0X01; /* cursor keys are in application mode */
++      if (p->w_keypad) *d |= 0X02; /* keypad is in application mode */
++      d++;
++    }
++  else
++    {
++      /* no window pointer */
++      SetWinImage( "no active scren", dest );
++    }
++}
++/* end export ipc image */
++
+ static void
+ win_destroyev_fn(ev, data)
+ struct event *ev;
+--- input.c.org	2014-09-15 21:03:06.000000000 +0200
++++ input.c	2014-09-15 23:09:43.000000000 +0200
+@@ -525,4 +525,49 @@
+       q += l;
+     }
+ }
++/* add export ipc shared image */
++int
++IsInputLayer(l)
++struct layer *l;
++{
++  return l->l_layfn == &InpLf;
++}
++
++int
++GetInputPosition(l)
++struct layer *l;
++{
++  struct inpdata *inpdata = (struct inpdata *)l->l_data;
++  return inpdata->inpstringlen + inpdata->inp.pos;
++}
++
++void
++CopyInputLine(l, dest, width)
++struct layer *l;
++char *dest;
++int width;
++{
++  struct inpdata *inpdata = (struct inpdata *)l->l_data;
++  char *src;
++  int len;
++
++  for
++  ( 
++    src = inpdata->inpstring, len = inpdata->inpstringlen;
++    width && len;
++    *dest++ = *src++, len--, width--
++  );
++
++  if( !(inpdata->inpmode & INP_NOECHO) )
++    {
++      for
++      ( 
++        src = inpdata->inp.buf, len = inpdata->inp.len;
++        width && len;
++        *dest++ = *src++, len--, width--
++      );
++    }
++
++  while( width ) *dest++ = ' ', width--;
++}
diff --git a/Patches/screen-pty.patch b/Patches/screen-pty.patch
new file mode 100644
index 0000000..335412e
--- /dev/null
+++ b/Patches/screen-pty.patch
@@ -0,0 +1,11 @@
+--- ./pty.c.orig	2003-09-08 16:26:18.000000000 +0200
++++ ./pty.c	2007-10-28 16:27:56.000000000 +0100
+@@ -34,7 +34,7 @@
+ #endif
+ 
+ /* for solaris 2.1, Unixware (SVR4.2) and possibly others */
+-#ifdef HAVE_SVR4_PTYS
++#if defined(HAVE_SVR4_PTYS) && !defined(__APPLE__)
+ # include <sys/stropts.h>
+ #endif
+ 
diff --git a/Patches/screen-pty.txt b/Patches/screen-pty.txt
new file mode 100644
index 0000000..d77fc81
--- /dev/null
+++ b/Patches/screen-pty.txt
@@ -0,0 +1,17 @@
+If you compile screen on Mac OS X and encounter the following error:
+
+   pty.c:38:26: error: sys/stropts.h: No such file or directory
+
+then try applying the included screen pty.c patch for this problem as follows:
+
+   cd /path/to/screen-build-tree
+   patch -p0 </path/to/brltty/Patches/screen-pty.patch
+
+This patch was downloaded from the following location:
+
+   https://trac.macports.org/raw-attachment/ticket/13009/patch-pty.c
+
+It is documented as MacPorts ticket 13009, which can be read at the following 
+location:
+
+   https://trac.macports.org/ticket/13009
diff --git a/Programs/Makefile.in b/Programs/Makefile.in
new file mode 100644
index 0000000..0bf4cc4
--- /dev/null
+++ b/Programs/Makefile.in
@@ -0,0 +1,1375 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+all: all-commands all-tools $(ALL_API) $(ALL_API_BINDINGS)
+
+all-commands: all-brltty all-brltty-trtxt all-brltty-ttb all-brltty-ctb all-brltty-atb all-brltty-ktb all-brltty-tune all-brltty-morse all-brltty-lscmds all-brltty-hid $(ALL_BRLTTY_PTY)
+all-brltty: brltty$X | $(BRAILLE_DRIVERS) $(SPEECH_DRIVERS) $(SCREEN_DRIVERS)
+all-brltty-trtxt: brltty-trtxt$X
+all-brltty-ttb: brltty-ttb$X
+all-brltty-ctb: brltty-ctb$X
+all-brltty-atb: brltty-atb$X
+all-brltty-ktb: brltty-ktb$X | $(BRAILLE_DRIVERS)
+all-brltty-tune: brltty-tune$X
+all-brltty-morse: brltty-morse$X
+all-brltty-lscmds: brltty-lscmds$X
+all-brltty-hid: brltty-hid$X
+all-brltty-pty: brltty-pty$X
+
+all-tools: all-brltty-cldr all-brltty-lsinc
+all-brltty-cldr: brltty-cldr$X
+all-brltty-lsinc: brltty-lsinc$X
+
+everything: all all-brltest all-spktest all-scrtest all-crctest all-msgtest
+all-brltest: brltest$X | $(BRAILLE_DRIVERS)
+all-spktest: spktest$X | $(SPEECH_DRIVERS)
+all-scrtest: scrtest$X | $(SCREEN_DRIVERS)
+all-crctest: crctest$X
+all-msgtest: msgtest$X
+
+all-api: $(ALL_XBRLAPI) all-brltty-clip all-apitest brlapi_brldefs.auto.h
+all-xbrlapi: xbrlapi$X
+all-brltty-clip: brltty-clip$X
+all-apitest: apitest$X
+
+###############################################################################
+
+REVISION_HEADER = revision_identifier.auto.h
+$(REVISION_HEADER):
+	-$(SRC_TOP)getrevid -s -f $@ -d unknown $(SRC_TOP)
+
+revision.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/revision.c
+
+###############################################################################
+
+program.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/program.c
+
+$(PGMPATH_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(PGMPATH_INCLUDES) -c $(SRC_DIR)/$(PGMPATH_OBJECT).c
+
+$(PGMPRIVS_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(PGMPRIVS_INCLUDES) -c $(SRC_DIR)/$(PGMPRIVS_OBJECT).c
+
+###############################################################################
+
+$(SERVICE_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(SERVICE_INCLUDES) -c $(SRC_DIR)/$(SERVICE_OBJECT).c
+
+###############################################################################
+
+cmdline.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmdline.c
+
+$(PARAMS_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(PARAMS_INCLUDES) -c $(SRC_DIR)/$(PARAMS_OBJECT).c
+
+###############################################################################
+
+$(DYNLD_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(DYNLD_INCLUDES) -c $(SRC_DIR)/$(DYNLD_OBJECT).c
+
+###############################################################################
+
+charset.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/charset.c
+
+$(CHARSET_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(CHARSET_INCLUDES) -c $(SRC_DIR)/$(CHARSET_OBJECT).c
+
+###############################################################################
+
+hostcmd.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hostcmd.c
+
+$(HOSTCMD_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(HOSTCMD_INCLUDES) -c $(SRC_DIR)/$(HOSTCMD_OBJECT).c
+
+###############################################################################
+
+mntpt.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/mntpt.c
+
+$(MNTPT_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(MNTPT_INCLUDES) -c $(SRC_DIR)/$(MNTPT_OBJECT).c
+
+###############################################################################
+
+$(MNTFS_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(MNTFS_INCLUDES) -c $(SRC_DIR)/$(MNTFS_OBJECT).c
+
+###############################################################################
+
+kbd.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/kbd.c
+
+$(KBD_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(KBD_INCLUDES) -c $(SRC_DIR)/$(KBD_OBJECT).c
+
+kbd_keycodes.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/kbd_keycodes.c
+
+###############################################################################
+
+bell.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/bell.c
+
+$(BELL_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(BELL_INCLUDES) -c $(SRC_DIR)/$(BELL_OBJECT).c
+
+###############################################################################
+
+leds.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/leds.c
+
+$(LEDS_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(LEDS_INCLUDES) -c $(SRC_DIR)/$(LEDS_OBJECT).c
+
+###############################################################################
+
+beep.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/beep.c
+
+$(BEEP_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(BEEP_INCLUDES) -c $(SRC_DIR)/$(BEEP_OBJECT).c
+
+###############################################################################
+
+pcm.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/pcm.c
+
+$(PCM_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(PCM_INCLUDES) -c $(SRC_DIR)/$(PCM_OBJECT).c
+
+###############################################################################
+
+midi.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/midi.c
+
+$(MIDI_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(MIDI_INCLUDES) -c $(SRC_DIR)/$(MIDI_OBJECT).c
+
+###############################################################################
+
+$(FM_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(FM_INCLUDES) -c $(SRC_DIR)/$(FM_OBJECT).c
+
+###############################################################################
+
+serial.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/serial.c
+
+$(SERIAL_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(SERIAL_INCLUDES) -c $(SRC_DIR)/$(SERIAL_OBJECT).c
+
+###############################################################################
+
+usb.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb.c
+
+usb_hid.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_hid.c
+
+usb_devices.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_devices.c
+
+usb_serial.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_serial.c
+
+usb_adapters.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_adapters.c
+
+usb_cdc_acm.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_cdc_acm.c
+
+usb_belkin.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_belkin.c
+
+usb_ch341.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_ch341.c
+
+usb_cp2101.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_cp2101.c
+
+usb_cp2110.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_cp2110.c
+
+usb_ftdi.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/usb_ftdi.c
+
+$(USB_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(USB_INCLUDES) -c $(SRC_DIR)/$(USB_OBJECT).c
+
+###############################################################################
+
+bluetooth.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/bluetooth.c
+
+bluetooth_names.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/bluetooth_names.c
+
+$(BLUETOOTH_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(BLUETOOTH_INCLUDES) -c $(SRC_DIR)/$(BLUETOOTH_OBJECT).c
+
+###############################################################################
+
+$(PORTS_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(PORTS_INCLUDES) -c $(SRC_DIR)/$(PORTS_OBJECT).c
+
+###############################################################################
+
+$(SYSTEM_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(SYSTEM_INCLUDES) -c $(SRC_DIR)/$(SYSTEM_OBJECT).c
+
+###############################################################################
+
+io_misc.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/io_misc.c
+
+io_log.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/io_log.c
+
+gio.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio.c
+
+gio_null.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio_null.c
+
+gio_serial.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio_serial.c
+
+gio_usb.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio_usb.c
+
+gio_bluetooth.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio_bluetooth.c
+
+gio_hid.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/gio_hid.c
+
+###############################################################################
+
+async_handle.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_handle.c
+
+async_data.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_data.c
+
+async_wait.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_wait.c
+
+async_alarm.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_alarm.c
+
+async_task.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_task.c
+
+async_io.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_io.c
+
+async_event.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_event.c
+
+async_signal.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/async_signal.c
+
+thread.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/thread.c
+
+###############################################################################
+
+messages.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/messages.c
+
+log.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/log.c
+
+log_history.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/log_history.c
+
+addresses.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/addresses.c
+
+report.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/report.c
+
+file.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/file.c
+
+device.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/device.c
+
+pipe.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/pipe.c
+
+parse.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/parse.c
+
+timing.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/timing.c
+
+queue.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/queue.c
+
+datafile.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/datafile.c
+
+variables.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/variables.c
+
+unicode.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/unicode.c
+
+utf8.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/utf8.c
+
+lock.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/lock.c
+
+###############################################################################
+
+pid.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/pid.c
+
+###############################################################################
+
+auth.$O:
+	$(CC) $(LIBCFLAGS) $(POLKIT_INCLUDES) -c $(SRC_DIR)/auth.c
+
+dataarea.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/dataarea.c
+
+###############################################################################
+
+PREFS_OBJECTS = prefs.$O pref_tables.$O
+
+prefs.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/prefs.c
+
+pref_tables.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/pref_tables.c
+
+###############################################################################
+
+CRCTEST_OBJECTS = crctest.$O $(PROGRAM_OBJECTS) crc_algorithms.$O crc_generate.$O crc_verify.$O
+
+crctest$X: $(CRCTEST_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(CRCTEST_OBJECTS) $(LDLIBS)
+
+crctest.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/crctest.c
+
+crc_algorithms.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/crc_algorithms.c
+
+crc_generate.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/crc_generate.c
+
+crc_verify.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/crc_verify.c
+
+###############################################################################
+
+MSGTEST_OBJECTS = msgtest.$O $(PROGRAM_OBJECTS)
+
+msgtest$X: $(MSGTEST_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(MSGTEST_OBJECTS) $(LDLIBS)
+
+msgtest.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/msgtest.c
+
+###############################################################################
+
+hid_items.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hid_items.c
+
+hid_braille.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hid_braille.c
+
+hid.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hid.c
+
+$(HID_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(HID_INCLUDES) -c $(SRC_DIR)/$(HID_OBJECT).c
+
+BRLTTY_HID_OBJECTS = brltty-hid.$O $(PROGRAM_OBJECTS) $(HID_OBJECTS) hid_inspect.$O hid_tables.$O
+
+brltty-hid$X: $(BRLTTY_HID_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_HID_OBJECTS) $(HID_LIBS) $(LDLIBS)
+
+brltty-hid.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brltty-hid.c
+
+hid_inspect.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hid_inspect.c
+
+hid_tables.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hid_tables.c
+
+###############################################################################
+
+FIRMWARE_OBJECTS = ihex.$O ezusb.$O
+
+ihex.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ihex.c
+
+ezusb.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ezusb.c
+
+###############################################################################
+
+BRAILLE_OBJECTS = brl.$O brl_utils.$O brl_input.$O brl_driver.$O brl_base.$O $(BRAILLE_DRIVER_OBJECTS) $(IO_OBJECTS) crc_generate.$O $(FIRMWARE_OBJECTS)
+
+brl.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brl.c
+
+brl_utils.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brl_utils.c
+
+brl_input.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brl_input.c
+
+brl_driver.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brl_driver.c
+
+brl_base.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brl_base.c
+
+###############################################################################
+
+SPEECH_OBJECTS = $(SPEECH_OBJECT) spk_thread.$O spk_driver.$O spk_base.$O $(SPEECH_DRIVER_OBJECTS)
+
+spk.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/spk.c
+
+spk_thread.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/spk_thread.c
+
+spk_input.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/spk_input.c
+
+spk_driver.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/spk_driver.c
+
+spk_base.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/spk_base.c
+
+###############################################################################
+
+SCREEN_OBJECTS = scr.$O scr_utils.$O scr_base.$O scr_main.$O scr_real.$O scr_gpm.$O scr_driver.$O routing.$O $(SCREEN_DRIVER_OBJECTS)
+
+scr.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr.c
+
+scr_utils.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_utils.c
+
+scr_base.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_base.c
+
+scr_main.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_main.c
+
+scr_real.$O: scr.auto.h
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_real.c
+
+scr_gpm.$O: scr.auto.h
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_gpm.c
+
+scr_driver.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_driver.c
+
+routing.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/routing.c
+
+###############################################################################
+
+SPECIAL_SCREEN_OBJECTS = scr_special.$O scr_frozen.$O scr_help.$O scr_menu.$O
+
+scr_special.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_special.c
+
+scr_frozen.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_frozen.c
+
+scr_help.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_help.c
+
+scr_menu.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/scr_menu.c
+
+###############################################################################
+
+ALERT_OBJECTS = alert.$O tune_builder.$O message.$O $(TUNE_OBJECTS)
+
+alert.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/alert.c
+
+message.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/message.c
+
+tune.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/tune.c
+
+notes.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/notes.c
+
+notes_beep.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/notes_beep.c
+
+notes_pcm.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/notes_pcm.c
+
+notes_midi.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/notes_midi.c
+
+notes_fm.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/notes_fm.c
+
+###############################################################################
+
+TTB_OBJECTS = ttb_translate.$O ttb_compile.$O ttb_native.$O
+
+ttb_translate.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ttb_translate.c
+
+ttb_compile.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ttb_compile.c
+
+ttb_native.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ttb_native.c
+
+ttb_gnome.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ttb_gnome.c
+
+ttb_louis.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ttb_louis.c
+
+BRLTTY_TTB_OBJECTS = brltty-ttb.$O $(PROGRAM_OBJECTS) dataarea.$O $(TTB_OBJECTS) ttb_gnome.$O ttb_louis.$O $(PREFS_OBJECTS) $(CHARSET_OBJECTS)
+
+brltty-ttb$X: $(BRLTTY_TTB_OBJECTS) | $(BUILD_API)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_TTB_OBJECTS) $(API_REF) $(CURSES_LIBS) $(LDLIBS)
+
+brltty-ttb.$O:
+	$(CC) $(CFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/brltty-ttb.c
+
+check-text-tables: brltty-ttb$X $(API_LIB_VERSIONED)
+	@echo checking text tables
+	set -- $(SRC_TOP)$(TBL_DIR)/$(TEXT_TABLES_SUBDIRECTORY)/*$(TEXT_TABLE_EXTENSION) && \
+	for file; do \
+	LD_LIBRARY_PATH=$(BLD_DIR) \
+	./brltty-ttb$X -T$(SRC_TOP)$(TBL_DIR) $${file##*/}; \
+	done
+
+###############################################################################
+
+ctb_compile.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/ctb_compile.c
+
+ctb_translate.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/ctb_translate.c
+
+ctb_native.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ctb_native.c
+
+ctb_external.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ctb_external.c
+
+ctb_louis.$O:
+	$(CC) $(LIBCFLAGS) $(LOUIS_INCLUDES) -c $(SRC_DIR)/ctb_louis.c
+
+BRLTTY_CTB_OBJECTS = brltty-ctb.$O $(PROGRAM_OBJECTS) $(TTB_OBJECTS) $(CTB_OBJECTS) $(PREFS_OBJECTS) $(CHARSET_OBJECTS) dataarea.$O
+
+brltty-ctb$X: $(BRLTTY_CTB_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_CTB_OBJECTS) $(LOUIS_LIBS) $(EXPAT_LIBS) $(LDLIBS)
+
+brltty-ctb.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-ctb.c
+
+check-contraction-tables: brltty-ctb$X
+	@echo checking contraction tables
+	set -- $(SRC_TOP)$(TBL_DIR)/$(CONTRACTION_TABLES_SUBDIRECTORY)/*$(CONTRACTION_TABLE_EXTENSION) && \
+	for file; do \
+	test -x $${file} || \
+	./brltty-ctb$X -T$(SRC_TOP)$(TBL_DIR) -c$${file##*/} </dev/null; \
+	done
+
+###############################################################################
+
+ATB_OBJECTS = atb_translate.$O atb_compile.$O
+
+atb_translate.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/atb_translate.c
+
+atb_compile.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/atb_compile.c
+
+BRLTTY_ATB_OBJECTS = brltty-atb.$O $(PROGRAM_OBJECTS) $(ATB_OBJECTS) dataarea.$O
+
+brltty-atb$X: $(BRLTTY_ATB_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_ATB_OBJECTS) $(LDLIBS)
+
+brltty-atb.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-atb.c
+
+check-attributes-tables: brltty-atb$X
+	@echo checking attributes tables
+	set -- $(SRC_TOP)$(TBL_DIR)/$(ATTRIBUTES_TABLES_SUBDIRECTORY)/*$(ATTRIBUTES_TABLE_EXTENSION) && \
+	for file; do \
+	./brltty-atb$X -T$(SRC_TOP)$(TBL_DIR) $${file##*/}; \
+	done
+
+###############################################################################
+
+KTB_OBJECTS = ktb_translate.$O ktb_compile.$O ktb_list.$O ktb_cmds.$O
+
+ktb_translate.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_translate.c
+
+ktb_compile.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_compile.c
+
+ktb_list.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_list.c
+
+ktb_audit.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_audit.c
+
+ktb_cmds.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_cmds.c
+
+ktb_keyboard.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ktb_keyboard.c
+
+BRLTTY_KTB_OBJECTS = brltty-ktb.$O $(PROGRAM_OBJECTS) $(KTB_OBJECTS) ktb_audit.$O ktb_keyboard.$O $(TTB_OBJECTS) $(PREFS_OBJECTS) $(CHARSET_OBJECTS) dataarea.$O drivers.$O driver.$O brl_utils.$O brl_driver.$O brl_base.$O $(BRAILLE_DRIVER_OBJECTS) $(IO_OBJECTS) cmd.$O cmd_queue.$O hidkeys.$O report.$O cmd_brlapi.$O crc_generate.$O $(FIRMWARE_OBJECTS)
+
+brltty-ktb$X: $(BRLTTY_KTB_OBJECTS) | $(BRAILLE_DRIVERS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_KTB_OBJECTS) $(BRAILLE_DRIVER_LIBRARIES) $(USB_LIBS) $(BLUETOOTH_LIBS) $(HID_LIBS) $(LDLIBS)
+
+brltty-ktb.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-ktb.c
+
+check-keyboard-tables: all-brltty-ktb
+	@echo checking keyboard tables
+	set -- $(SRC_TOP)$(TBL_DIR)/$(KEYBOARD_TABLES_SUBDIRECTORY)/*$(KEY_TABLE_EXTENSION) && \
+	for file; do \
+	name=$${file##*/}; \
+	name=$${name%.*}; \
+	./brltty-ktb$X -a -D$(BLD_TOP)$(DRV_DIR) -T$(SRC_TOP)$(TBL_DIR) $$name; \
+	done
+
+check-input-tables: all-brltty-ktb
+	@echo checking input tables
+	find $(SRC_TOP)$(TBL_DIR)/$(INPUT_TABLES_SUBDIRECTORY) -name '*$(KEY_TABLE_EXTENSION)' -print | \
+	while read file; do \
+	driver=$${file%/*}; \
+	driver=$${driver##*/}; \
+	name=$${file##*/}; \
+	name=$${name%.*}; \
+	./brltty-ktb$X -a -D$(BLD_TOP)$(DRV_DIR) -T$(SRC_TOP)$(TBL_DIR) -b $$driver $$name; \
+	done
+
+###############################################################################
+
+api_control.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/api_control.c
+
+brlapi_server.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brlapi_server.c
+
+brlapi_keyranges.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brlapi_keyranges.c
+
+###############################################################################
+
+API_CLIENT_OBJECTS = brlapi_client.$O
+
+api: $(API_DYNAMIC_LIBRARY) $(API_ARC)
+
+api-dynamic-library: $(API_LIB_VERSIONED)
+$(API_LIB_VERSIONED): $(API_LIB)
+	test -e "$(API_LIB_VERSIONED)" || $(SYMLINK) $(API_LIB) $(API_LIB_VERSIONED)
+
+$(API_LIB): $(API_CLIENT_OBJECTS)
+	$(MKLIB:<name>=${API_LIB_VERSIONED}) $@ $(API_CLIENT_OBJECTS) $(API_LIBRARIES)
+
+api-dynamic-library-windows: $(API_DLL)
+$(API_DLL): $(API_CLIENT_OBJECTS)
+	-rm -f implib.a lib.def
+	$(MKLIB:<name>=${API_LIB_VERSIONED}) $@ $(API_CLIENT_OBJECTS) $(API_LIBRARIES)
+	[ ! -f implib.a ] || mv implib.a $(API_IMPLIB)
+	[ ! -f lib.def ] || mv lib.def $(API_DEF)
+
+$(API_ARC): $(API_CLIENT_OBJECTS)
+	ar rc $@ $(API_CLIENT_OBJECTS)
+	$(RANLIB) $@
+
+brlapi_client.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brlapi_client.c
+
+brlapi_constants.h: $(SRC_DIR)/brlapi_constants.awk $(COMMANDS_DEPENDENCIES)
+	$(AWK) -f $(SRC_DIR)/brlapi_constants.awk $(COMMANDS_ARGUMENTS) >$@
+
+brlapi_keytab.auto.h: $(SRC_DIR)/brlapi_keytab.awk $(COMMANDS_DEPENDENCIES)
+	$(AWK) -f $(SRC_DIR)/brlapi_keytab.awk $(COMMANDS_ARGUMENTS) >$@
+
+brlapi_brldefs.auto.h: $(SRC_DIR)/brlapi_brldefs.awk $(COMMANDS_DEPENDENCIES)
+	$(AWK) -f $(SRC_DIR)/brlapi_brldefs.awk $(COMMANDS_ARGUMENTS) >$@
+
+all-api-bindings: | api
+	set -- $(API_BINDINGS); \
+	for language \
+	do (cd $(BLD_TOP)$(BND_DIR)/$$language && $(MAKE) all) || exit 1; \
+	done
+
+install-api-bindings: all-api-bindings
+	set -- $(API_BINDINGS); \
+	for language \
+	do (cd $(BLD_TOP)$(BND_DIR)/$$language && $(MAKE) install) || exit 1; \
+	done
+
+uninstall-api-bindings:
+	-set -- $(API_BINDINGS); \
+	for language \
+	do (cd $(BLD_TOP)$(BND_DIR)/$$language && $(MAKE) uninstall); \
+	done
+
+###############################################################################
+
+BRLTTY_CLDR_OBJECTS = brltty-cldr.$O cldr.$O $(PROGRAM_OBJECTS)
+
+brltty-cldr$X: $(BRLTTY_CLDR_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_CLDR_OBJECTS) $(EXPAT_LIBS) $(LDLIBS)
+
+brltty-cldr.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brltty-cldr.c
+
+cldr.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cldr.c
+
+###############################################################################
+
+rgx.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/rgx.c
+
+$(RGX_OBJECT).$O:
+	$(CC) $(LIBCFLAGS) $(RGX_INCLUDES) -c $(SRC_DIR)/$(RGX_OBJECT).c
+
+###############################################################################
+
+CORE_OBJECTS = core.$O $(PROGRAM_OBJECTS) revision.$O $(PGMPRIVS_OBJECTS) report.$O config.$O $(RGX_OBJECTS) $(SERVICE_OBJECTS) activity.$O $(PREFS_OBJECTS) profile.$O menu.$O menu_prefs.$O ses.$O status.$O update.$O blink.$O dataarea.$O $(CMD_OBJECTS) pipe.$O $(TTB_OBJECTS) $(CHARSET_OBJECTS) $(CTB_OBJECTS) $(ATB_OBJECTS) $(KTB_OBJECTS) ktb_keyboard.$O $(KBD_OBJECTS) kbd_keycodes.$O $(BELL_OBJECTS) $(LEDS_OBJECTS) $(ALERT_OBJECTS) hidkeys.$O drivers.$O driver.$O $(SCREEN_OBJECTS) $(SPECIAL_SCREEN_OBJECTS) $(BRAILLE_OBJECTS) $(SPEECH_OBJECTS) spk_input.$O api_control.$O $(API_SERVER_OBJECTS)
+CORE_NAME = brltty
+
+brltty-core: $(CORE_OBJECTS)
+
+core.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/core.c
+
+config.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/config.c
+
+activity.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/activity.c
+
+profile.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/profile.c
+
+menu.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/menu.c
+
+menu_prefs.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/menu_prefs.c
+
+ses.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/ses.c
+
+status.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/status.c
+
+update.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/update.c
+
+blink.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/blink.c
+
+hidkeys.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/hidkeys.c
+
+drivers.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/drivers.c
+
+driver.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/driver.c
+
+###############################################################################
+
+CMD_OBJECTS = cmd.$O cmd_brlapi.$O cmd_queue.$O cmd_utils.$O cmd_clipboard.$O cmd_custom.$O cmd_input.$O cmd_keycodes.$O cmd_learn.$O cmd_miscellaneous.$O cmd_navigation.$O cmd_override.$O cmd_preferences.$O cmd_speech.$O cmd_toggle.$O cmd_touch.$O clipboard.$O learn.$O
+
+cmd.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd.c
+
+cmd_brlapi.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_brlapi.c
+
+cmd_queue.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_queue.c
+
+cmd_utils.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/cmd_utils.c
+
+cmd_clipboard.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_clipboard.c
+
+cmd_custom.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/cmd_custom.c
+
+cmd_input.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_input.c
+
+cmd_keycodes.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_keycodes.c
+
+cmd_learn.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_learn.c
+
+cmd_miscellaneous.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_miscellaneous.c
+
+cmd_navigation.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/cmd_navigation.c
+
+cmd_override.$O:
+	$(CC) $(LIBCFLAGS) $(ICU_INCLUDES) -c $(SRC_DIR)/cmd_override.c
+
+cmd_preferences.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_preferences.c
+
+cmd_speech.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_speech.c
+
+cmd_toggle.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_toggle.c
+
+cmd_touch.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/cmd_touch.c
+
+clipboard.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/clipboard.c
+
+learn.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/learn.c
+
+###############################################################################
+
+BRLTTY_OBJECTS = brltty.$O $(CORE_OBJECTS)
+BRLTTY_LIBRARIES = $(BRAILLE_DRIVER_LIBRARIES) $(SPEECH_DRIVER_LIBRARIES) $(SCREEN_DRIVER_LIBRARIES) $(SERVICE_LIBS) $(PCM_LIBS) $(MIDI_LIBS) $(USB_LIBS) $(BLUETOOTH_LIBS) $(HID_LIBS) $(LOUIS_LIBS) $(EXPAT_LIBS) $(RGX_LIBS) $(POLKIT_LIBS) $(LDLIBS)
+
+brltty$X: $(BRLTTY_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_OBJECTS) $(BRLTTY_LIBRARIES)
+
+brltty.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty.c
+
+###############################################################################
+
+BRLTTY_CORE_LIB = $(LIB_PFX)$(CORE_NAME)_core.$(LIB_EXT)
+
+core-library: $(BRLTTY_CORE_LIB)
+
+$(BRLTTY_CORE_LIB): $(CORE_OBJECTS)
+	$(MKLIB:<name>=$@.$(PACKAGE_VERSION)) $@ $(CORE_OBJECTS) $(BRLTTY_LIBRARIES)
+
+###############################################################################
+
+BRLTTY_JNI_JAR = $(CORE_NAME).jar
+BRLTTY_JNI_LIB = $(LIB_PFX)$(CORE_NAME)_jni.$(LIB_EXT)
+
+brltty-jni: $(BRLTTY_JNI_JAR) $(BRLTTY_JNI_LIB)
+
+$(BRLTTY_JNI_JAR): brltty.class
+	jar cf $@ brltty.class
+
+brltty.class: $(SRC_DIR)/brltty.java
+	javac $(SRC_DIR)/brltty.java
+
+$(BRLTTY_JNI_LIB): brltty_jni.$O $(BRLTTY_CORE_LIB)
+	$(MKLIB:<name>=$@.$(PACKAGE_VERSION)) $@ brltty_jni.$O $(LDLIBS)
+
+brltty_jni.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/brltty_jni.c
+
+###############################################################################
+
+BRLTTY_TRTXT_OBJECTS = brltty-trtxt.$O $(PROGRAM_OBJECTS) $(TTB_OBJECTS) $(PREFS_OBJECTS) $(CHARSET_OBJECTS) dataarea.$O
+
+brltty-trtxt$X: $(BRLTTY_TRTXT_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_TRTXT_OBJECTS) $(LDLIBS)
+
+brltty-trtxt.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-trtxt.c
+
+###############################################################################
+
+BRLTTY_LSCMDS_OBJECTS = brltty-lscmds.$O $(PROGRAM_OBJECTS) ktb_cmds.$O cmd.$O
+
+brltty-lscmds$X: $(BRLTTY_LSCMDS_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_LSCMDS_OBJECTS) $(LDLIBS)
+
+brltty-lscmds.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-lscmds.c
+
+###############################################################################
+
+BRLTTY_LSINC_OBJECTS = brltty-lsinc.$O $(PROGRAM_OBJECTS)
+
+brltty-lsinc$X: $(BRLTTY_LSINC_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_LSINC_OBJECTS) $(LDLIBS)
+
+brltty-lsinc.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-lsinc.c
+
+###############################################################################
+
+BRLTEST_OBJECTS = brltest.$O $(PROGRAM_OBJECTS) report.$O $(TTB_OBJECTS) $(KTB_OBJECTS) $(PREFS_OBJECTS) $(CHARSET_OBJECTS) dataarea.$O cmd.$O cmd_queue.$O drivers.$O driver.$O $(BRAILLE_OBJECTS) hidkeys.$O learn.$O
+
+brltest$X: $(BRLTEST_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTEST_OBJECTS) $(BRAILLE_DRIVER_LIBRARIES) $(USB_LIBS) $(BLUETOOTH_LIBS) $(HID_LIBS) $(LDLIBS)
+
+brltest.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltest.c
+
+###############################################################################
+
+SPKTEST_OBJECTS = spktest.$O $(PROGRAM_OBJECTS) drivers.$O driver.$O $(SPEECH_OBJECTS) $(PREFS_OBJECTS)
+
+spktest$X: $(SPKTEST_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(SPKTEST_OBJECTS) $(SPEECH_DRIVER_LIBRARIES) $(LDLIBS)
+
+spktest.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/spktest.c
+
+###############################################################################
+
+SCRTEST_OBJECTS = scrtest.$O $(PROGRAM_OBJECTS) $(CHARSET_OBJECTS) drivers.$O driver.$O $(SCREEN_OBJECTS) report.$O
+
+scrtest$X: $(SCRTEST_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(SCRTEST_OBJECTS) $(SCREEN_DRIVER_LIBRARIES) $(LDLIBS)
+
+scrtest.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/scrtest.c
+
+###############################################################################
+
+TERMINAL_EMULATOR_OBJECTS = scr_terminal.$O scr_emulator.$O msg_queue.$O
+
+scr_terminal.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/scr_terminal.c
+
+scr_emulator.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/scr_emulator.c
+
+msg_queue.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/msg_queue.c
+
+BRLTTY_PTY_OBJECTS = brltty-pty.$O $(PROGRAM_OBJECTS) pty_object.$O pty_terminal.$O pty_screen.$O $(TERMINAL_EMULATOR_OBJECTS)
+
+brltty-pty$X: $(BRLTTY_PTY_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_PTY_OBJECTS) $(CURSES_LIBS) $(LDLIBS)
+
+brltty-pty.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-pty.c
+
+pty_object.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/pty_object.c
+
+pty_terminal.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/pty_terminal.c
+
+pty_screen.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/pty_screen.c
+
+###############################################################################
+
+BRLTTY_TUNE_OBJECTS = brltty-tune.$O tune_utils.$O tune_builder.$O $(PROGRAM_OBJECTS) $(PREFS_OBJECTS) $(TUNE_OBJECTS)
+
+brltty-tune$X: $(BRLTTY_TUNE_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_TUNE_OBJECTS) $(PCM_LIBS) $(MIDI_LIBS) $(LDLIBS)
+
+brltty-tune.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-tune.c
+
+tune_utils.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/tune_utils.c
+
+tune_builder.$O:
+	$(CC) $(LIBCFLAGS) -c $(SRC_DIR)/tune_builder.c
+
+###############################################################################
+
+BRLTTY_MORSE_OBJECTS = brltty-morse.$O morse.$O tune_utils.$O $(PROGRAM_OBJECTS) $(PREFS_OBJECTS) $(TUNE_OBJECTS)
+
+brltty-morse$X: $(BRLTTY_MORSE_OBJECTS)
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_MORSE_OBJECTS) $(PCM_LIBS) $(MIDI_LIBS) $(LDLIBS)
+
+brltty-morse.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-morse.c
+
+morse.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/morse.c
+
+###############################################################################
+
+APITEST_OBJECTS = apitest.$O $(PROGRAM_OBJECTS) cmd.$O cmd_brlapi.$O $(TTB_OBJECTS) $(PREFS_OBJECTS) $(CHARSET_OBJECTS) dataarea.$O
+
+apitest$X: $(APITEST_OBJECTS) | api
+	$(CC) $(LDFLAGS) -o $@ $(APITEST_OBJECTS) $(API_LIBS) $(LDLIBS)
+
+apitest.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/apitest.c
+
+###############################################################################
+
+braille-drivers: $(BUILD_API)
+	for driver in $(BRAILLE_EXTERNAL_DRIVER_NAMES); \
+	do (cd $(BLD_TOP)$(BRL_DIR)/$$driver && $(MAKE) braille-driver) || exit 1; \
+	done
+
+###############################################################################
+
+speech-drivers:
+	for driver in $(SPEECH_EXTERNAL_DRIVER_NAMES); \
+	do (cd $(BLD_TOP)$(SPK_DIR)/$$driver && $(MAKE) speech-driver) || exit 1; \
+	done
+
+###############################################################################
+
+screen-drivers:
+	for driver in $(SCREEN_EXTERNAL_DRIVER_NAMES); \
+	do (cd $(BLD_TOP)$(SCR_DIR)/$$driver && $(MAKE) screen-driver) || exit 1; \
+	done
+
+###############################################################################
+
+TEXT_TABLE_FILE = $(SRC_TOP)$(TBL_DIR)/$(TEXT_TABLES_SUBDIRECTORY)/$(TEXT_TABLE)$(TEXT_TABLE_EXTENSION)
+ttb.auto.h: $(TEXT_TABLE_FILE) tbl2hex$(X_FOR_BUILD)
+	./tbl2hex$(X_FOR_BUILD) -- $(TEXT_TABLE_FILE) >$@
+
+CONTRACTION_TABLE_FILE = $(SRC_TOP)$(TBL_DIR)/$(CONTRACTION_TABLES_SUBDIRECTORY)/$(CONTRACTION_TABLE)$(CONTRACTION_TABLE_EXTENSION)
+ctb.auto.h: $(CONTRACTION_TABLE_FILE) tbl2hex$(X_FOR_BUILD)
+	./tbl2hex$(X_FOR_BUILD) -- $(CONTRACTION_TABLE_FILE) >$@
+
+ATTRIBUTES_TABLE_FILE = $(SRC_TOP)$(TBL_DIR)/$(ATTRIBUTES_TABLES_SUBDIRECTORY)/$(ATTRIBUTES_TABLE)$(ATTRIBUTES_TABLE_EXTENSION)
+atb.auto.h: $(ATTRIBUTES_TABLE_FILE) tbl2hex$(X_FOR_BUILD)
+	./tbl2hex$(X_FOR_BUILD) -- $(ATTRIBUTES_TABLE_FILE) >$@
+
+cmds.auto.h: $(SRC_DIR)/cmds.awk $(COMMANDS_DEPENDENCIES)
+	$(AWK) -f $(SRC_DIR)/cmds.awk $(COMMANDS_ARGUMENTS) >$@
+
+brl.auto.h: $(SRC_DIR)/mkdrvtab
+	$(SRC_DIR)/mkdrvtab BrailleDriver brl_driver_ $(BRAILLE_INTERNAL_DRIVER_CODES) >$@
+
+spk.auto.h: $(SRC_DIR)/mkdrvtab
+	$(SRC_DIR)/mkdrvtab SpeechDriver spk_driver_ $(SPEECH_INTERNAL_DRIVER_CODES) >$@
+
+scr.auto.h: $(SRC_DIR)/mkdrvtab
+	$(SRC_DIR)/mkdrvtab ScreenDriver scr_driver_ $(SCREEN_INTERNAL_DRIVER_CODES) >$@
+
+###############################################################################
+
+XBRLAPI_OBJECTS = xbrlapi.$O $(XSEL_OBJECT) $(PROGRAM_OBJECTS)
+
+xbrlapi$X: $(XBRLAPI_OBJECTS) | api
+	$(CC) $(LDFLAGS) -o $@ $(XBRLAPI_OBJECTS) $(API_LIBS) $(XKB_LIBS) $(XFIXES_LIBS) $(X11_LIBS) $(LDLIBS)
+
+xbrlapi.$O:
+	$(CC) $(CFLAGS) $(X11_INCLUDES) -c $(SRC_DIR)/xbrlapi.c
+
+xsel.$O:
+	$(CC) $(LIBCFLAGS) $(X11_INCLUDES) -c $(SRC_DIR)/xsel.c
+
+###############################################################################
+
+BRLTTY_CLIP_OBJECTS = brltty-clip.$O $(PROGRAM_OBJECTS)
+
+brltty-clip$X: $(BRLTTY_CLIP_OBJECTS) | api
+	$(CC) $(LDFLAGS) -o $@ $(BRLTTY_CLIP_OBJECTS) $(API_LIBS) $(LDLIBS)
+
+brltty-clip.$O:
+	$(CC) $(CFLAGS) -c $(SRC_DIR)/brltty-clip.c
+
+###############################################################################
+
+TBL2HEX_OBJECTS_FOR_BUILD = tbl2hex.$(O_FOR_BUILD) $(PROGRAM_OBJECTS_FOR_BUILD) dataarea.$(O_FOR_BUILD) ttb_compile.$(O_FOR_BUILD) ttb_native.$(O_FOR_BUILD) $(CHARSET_OBJECTS_FOR_BUILD) ctb_compile.$(O_FOR_BUILD) cldr.$(O_FOR_BUILD) atb_compile.$(O_FOR_BUILD)
+TBL2HEX_OBJECTS = $(TBL2HEX_OBJECTS_FOR_BUILD:.$(O_FOR_BUILD)=.$B)
+
+tbl2hex$(X_FOR_BUILD): $(TBL2HEX_OBJECTS)
+	$(CC_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -o $@ $(TBL2HEX_OBJECTS) $(EXPAT_LIBS_FOR_BUILD) $(LDLIBS_FOR_BUILD)
+
+###############################################################################
+
+check-braille-drivers: brltty$X $(API_LIB_VERSIONED) | braille-drivers
+	@echo checking braille drivers
+	set -- $(BRAILLE_DRIVER_CODES) && \
+	for code; do \
+	LD_LIBRARY_PATH=$(BLD_DIR) \
+	./brltty -v -lwarning -N -e -f /dev/null -b $$code -s no -D "$(BLD_TOP)$(DRV_DIR)" -T "$(BLD_TOP)$(TBL_DIR)" 2>&1 || exit 11; \
+	done
+
+check-speech-drivers: brltty$X | speech-drivers
+	@echo checking speech drivers
+	set -- $(SPEECH_DRIVER_CODES) && \
+	for code; do \
+	./brltty -v -lwarning -N -e -f /dev/null -b no -s $$code -D "$(BLD_TOP)$(DRV_DIR)" -T "$(BLD_TOP)$(TBL_DIR)" 2>&1 || exit 11; \
+	done
+
+###############################################################################
+
+check-public-headers:
+	@echo checking public headers
+	$(SRC_TOP)chkhdrs $(SRC_TOP)$(HDR_DIR)
+
+check-all: check-text-tables check-contraction-tables check-attributes-tables check-keyboard-tables check-input-tables check-braille-drivers check-speech-drivers check-public-headers
+
+###############################################################################
+
+install:: install-programs install-tables $(INSTALL_DRIVERS) install-core-headers $(INSTALL_MESSAGES) install-manpages install-pkgconfig-file $(INSTALL_API)
+
+install-programs: install-commands install-tools
+
+install-program-directories: install-program-directory
+
+install-commands: all-commands install-program-directories $(INSTALL_BRLTTY_PTY)
+	$(INSTALL_PROGRAM) brltty$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-trtxt$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-ttb$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-ctb$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-atb$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-ktb$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-tune$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-morse$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-lscmds$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-hid$X $(INSTALL_PROGRAM_DIRECTORY) 
+
+install-brltty-pty: install-commands-directory
+	$(INSTALL_PROGRAM) brltty-pty$X $(INSTALL_COMMANDS_DIRECTORY) 
+
+install-tools: all-tools install-program-directories
+	$(INSTALL_PROGRAM) brltty-cldr$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_PROGRAM) brltty-lsinc$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_DATA) $(BLD_TOP)brltty-config.sh $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_TOP)brltty-prologue.* $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_SCRIPT) $(SRC_TOP)brltty-mkuser $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_SCRIPT) $(SRC_TOP)brltty-setcaps $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_SCRIPT) $(SRC_TOP)brltty-genkey $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_SCRIPT) $(SRC_TOP)brltty-term $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_SCRIPT) $(SRC_TOP)brltty-ttysize $(INSTALL_PROGRAM_DIRECTORY)
+
+XBRLAPI_X11_AUTOSTART_FILE = 90xbrlapi
+XBRLAPI_GDM_AUTOSTART_FILE = xbrlapi.desktop
+
+install-xbrlapi: xbrlapi$X install-program-directory install-x11-autostart-directory install-gdm-autostart-directory
+	$(INSTALL_PROGRAM) xbrlapi$X $(INSTALL_PROGRAM_DIRECTORY) 
+	$(INSTALL_DATA) $(BLD_TOP)$(DOC_DIR)/xbrlapi.1 $(INSTALL_MAN1_DIRECTORY)
+	$(INSTALL_DATA) $(BLD_TOP)Autostart/X11/$(XBRLAPI_X11_AUTOSTART_FILE) $(INSTALL_X11_AUTOSTART_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_TOP)Autostart/gdm/$(XBRLAPI_GDM_AUTOSTART_FILE) $(INSTALL_GDM_AUTOSTART_DIRECTORY)
+
+install-tables: $(INSTALL_TEXT_TABLES) $(INSTALL_CONTRACTION_TABLES) $(INSTALL_ATTRIBUTES_TABLES) install-keyboard-tables install-input-tables
+
+install-text-tables: install-text-tables-directory
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(TEXT_TABLES_SUBDIRECTORY)/*$(TEXT_TABLE_EXTENSION) $(INSTALL_TEXT_TABLES_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(TEXT_TABLES_SUBDIRECTORY)/*$(TEXT_SUBTABLE_EXTENSION) $(INSTALL_TEXT_TABLES_DIRECTORY)
+
+install-contraction-tables: install-contraction-tables-directory
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(CONTRACTION_TABLES_SUBDIRECTORY)/*$(CONTRACTION_TABLE_EXTENSION) $(INSTALL_CONTRACTION_TABLES_DIRECTORY)
+	for table in latex-access; do \
+	$(INSTALL_SCRIPT) $(SRC_TOP)$(TBL_DIR)/$(CONTRACTION_TABLES_SUBDIRECTORY)/$$table$(CONTRACTION_TABLE_EXTENSION) $(INSTALL_CONTRACTION_TABLES_DIRECTORY); \
+	done
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(CONTRACTION_TABLES_SUBDIRECTORY)/*$(CONTRACTION_SUBTABLE_EXTENSION) $(INSTALL_CONTRACTION_TABLES_DIRECTORY)
+
+install-attributes-tables: install-attributes-tables-directory
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(ATTRIBUTES_TABLES_SUBDIRECTORY)/*$(ATTRIBUTES_TABLE_EXTENSION) $(INSTALL_ATTRIBUTES_TABLES_DIRECTORY)
+	#$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(ATTRIBUTES_TABLES_SUBDIRECTORY)/*$(ATTRIBUTES_SUBTABLE_EXTENSION) $(INSTALL_ATTRIBUTES_TABLES_DIRECTORY)
+
+install-keyboard-tables: install-keyboard-tables-directory
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(KEYBOARD_TABLES_SUBDIRECTORY)/*$(KEY_TABLE_EXTENSION) $(INSTALL_KEYBOARD_TABLES_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_TOP)$(TBL_DIR)/$(KEYBOARD_TABLES_SUBDIRECTORY)/*$(KEY_SUBTABLE_EXTENSION) $(INSTALL_KEYBOARD_TABLES_DIRECTORY)
+
+install-input-tables: install-input-tables-directory
+	cd $(SRC_TOP)$(TBL_DIR)/$(INPUT_TABLES_SUBDIRECTORY) && \
+	set -- *$(KEY_SUBTABLE_EXTENSION) */*$(KEY_TABLE_EXTENSION) */*$(KEY_SUBTABLE_EXTENSION) */*$(KEY_HELP_EXTENSION) && \
+	for file; do \
+	driver=`dirname $$file` && \
+	target=$(INSTALL_INPUT_TABLES_DIRECTORY)/$$driver && \
+	$(INSTALL_DIRECTORY) $$target && \
+	$(INSTALL_DATA) $$file $$target; \
+	done
+
+install-drivers: $(BRAILLE_DRIVERS) $(SPEECH_DRIVERS) $(SCREEN_DRIVERS) install-drivers-directory
+	$(INSTALL_PROGRAM) $(BLD_TOP)$(DRV_DIR)/*.$(MOD_EXT) $(INSTALL_DRIVERS_DIRECTORY)
+
+CORE_HEADERS = $(SRC_TOP)Headers/*.h $(BLD_TOP)config.h $(BLD_TOP)forbuild.h
+
+install-core-headers: install-include-directory
+	$(INSTALL_DATA) $(CORE_HEADERS) $(INSTALL_INCLUDE_DIRECTORY)
+
+install-messages:
+	cd $(BLD_TOP)$(MSG_DIR) && $(MAKE) install
+
+install-manpages: install-man1-directory
+	$(INSTALL_DATA) $(BLD_TOP)$(DOC_DIR)/brltty.1 $(INSTALL_MAN1_DIRECTORY)
+
+PKGCONFIG_FILE_NAME = brltty.pc
+PKGCONFIG_FILE_PATH = $(INSTALL_PKGCONFIG_DIRECTORY)/$(PKGCONFIG_FILE_NAME)
+install-pkgconfig-file: install-pkgconfig-directory
+	$(INSTALL_DATA) $(BLD_TOP)$(PKGCONFIG_FILE_NAME) $(PKGCONFIG_FILE_PATH)
+	-$(SRC_TOP)getrevid -p "revision_identifier=" $(SRC_TOP) >>$(PKGCONFIG_FILE_PATH)
+
+install-documents: install-document-directory brltty-ktb$X $(BRAILLE_DRIVERS)
+	cd $(BLD_TOP)$(DOC_DIR) && $(MAKE) all
+	$(SRC_TOP)mkdocs -o $(INSTALL_DOCUMENT_DIRECTORY) -s $(SRC_TOP) -b $(BLD_TOP)
+
+install-api: $(INSTALL_API_LIBRARIES) install-api-headers install-api-manpages install-api-key install-api-commands $(INSTALL_XBRLAPI) $(INSTALL_API_BINDINGS)
+
+install-api-libraries: $(API_LIB) $(API_ARC) install-apilib-directory install-apisoc-directory
+	$(INSTALL_PROGRAM) $(API_LIB) $(INSTALL_APILIB_DIRECTORY)/$(API_LIB).$(API_RELEASE)
+	$(SYMLINK) -f $(API_LIB).$(API_RELEASE) $(INSTALL_APILIB_DIRECTORY)/$(API_LIB)
+	$(SYMLINK) -f $(API_LIB).$(API_RELEASE) $(INSTALL_APILIB_DIRECTORY)/$(API_LIB).$(API_VERSION)
+	$(CONFLIBDIR) $(INSTALL_APILIB_DIRECTORY)
+	$(INSTALL_DATA) $(API_ARC) $(INSTALL_APILIB_DIRECTORY)
+
+install-api-libraries-windows: $(API_DLL) $(API_ARC) install-program-directory install-apilib-directory
+	$(INSTALL_PROGRAM) $(API_DLL) $(INSTALL_PROGRAM_DIRECTORY)
+	$(INSTALL_DATA) $(API_IMPLIB) $(INSTALL_APILIB_DIRECTORY)/$(API_IMPLIB_VERSIONED)
+	cd $(INSTALL_APILIB_DIRECTORY) && $(SYMLINK) -f $(API_IMPLIB_VERSIONED) $(API_IMPLIB)
+	$(INSTALL_DATA) $(API_ARC) $(INSTALL_APILIB_DIRECTORY)
+	$(INSTALL_DATA) $(API_DEF) $(INSTALL_APILIB_DIRECTORY)
+
+install-api-headers: brlapi_constants.h brlapi_brldefs.auto.h install-apihdr-directory install-include-directory
+	$(INSTALL_DATA) brlapi.h $(INSTALL_APIHDR_DIRECTORY)
+	$(INSTALL_DATA) brlapi_constants.h $(INSTALL_APIHDR_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brlapi_keycodes.h $(INSTALL_APIHDR_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brlapi_param.h $(INSTALL_APIHDR_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brlapi_protocol.h $(INSTALL_APIHDR_DIRECTORY)
+	$(INSTALL_DATA) brlapi_brldefs.auto.h $(INSTALL_APIHDR_DIRECTORY)/brlapi_brldefs.h
+	$(INSTALL_DATA) $(SRC_DIR)/api.h $(INSTALL_INCLUDE_DIRECTORY)
+	$(INSTALL_DATA) $(SRC_DIR)/brldefs.h $(INSTALL_INCLUDE_DIRECTORY)
+
+install-api-manpages: install-man3-directory
+	cd $(BLD_TOP)$(DOC_DIR) && $(MAKE) man3
+	set -- $(BLD_TOP)$(DOC_DIR)/BrlAPIref/man/man3/brlapi_*.3 && [ -f $$1 ] || exit 0 && \
+	$(INSTALL_DATA) $$* $(INSTALL_MAN3_DIRECTORY)
+
+install-api-key:
+	file=$(sysconfdir)/$(API_AUTHFILE) && \
+	if test ! -f $$file -a -w $(sysconfdir) -a -z "$(INSTALL_ROOT)"; \
+	then $(SRC_TOP)brltty-genkey -f $$file; fi
+
+install-api-commands: all-brltty-clip
+	$(INSTALL_PROGRAM) brltty-clip$X $(INSTALL_PROGRAM_DIRECTORY) 
+
+###############################################################################
+
+uninstall:: uninstall-programs uninstall-tables uninstall-drivers uninstall-messages uninstall-manpages uninstall-pkgconfig-file uninstall-documents uninstall-headers uninstall-api-bindings
+
+uninstall-programs:
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/brltty$X
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/brltty-*
+	-rm -f $(INSTALL_COMMANDS_DIRECTORY)/brltty-*
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/xbrlapi$X
+	-rm -f $(INSTALL_X11_AUTOSTART_DIRECTORY)/$(XBRLAPI_X11_AUTOSTART_FILE)
+	-rm -f $(INSTALL_GDM_AUTOSTART_DIRECTORY)/$(XBRLAPI_GDM_AUTOSTART_FILE)
+
+uninstall-tables:
+	-rm -f -r $(INSTALL_TEXT_TABLES_DIRECTORY)
+	-rm -f -r $(INSTALL_CONTRACTION_TABLES_DIRECTORY)
+	-rm -f -r $(INSTALL_ATTRIBUTES_TABLES_DIRECTORY)
+	-rm -f -r $(INSTALL_KEYBOARD_TABLES_DIRECTORY)
+	-rm -f -r $(INSTALL_INPUT_TABLES_DIRECTORY)
+	-[ ! -d $(INSTALL_TABLES_DIRECTORY) ] || rmdir $(INSTALL_TABLES_DIRECTORY)
+
+uninstall-drivers: $(UNINSTALL_API_LIBRARIES)
+	-rm -f $(INSTALL_DRIVERS_DIRECTORY)/$(LIB_NAME)*
+	-[ ! -d $(INSTALL_DRIVERS_DIRECTORY) ] || rmdir $(INSTALL_DRIVERS_DIRECTORY)
+
+uninstall-messages:
+	cd $(BLD_TOP)$(MSG_DIR) && $(MAKE) uninstall
+
+uninstall-manpages:
+	-rm -f $(INSTALL_ROOT)$(MANPAGE_DIRECTORY)/man1/brltty.1
+	-rm -f $(INSTALL_ROOT)$(MANPAGE_DIRECTORY)/man1/xbrlapi.1
+	-rm -f $(INSTALL_MAN3_DIRECTORY)/brlapi_*.3
+
+uninstall-pkgconfig-file:
+	-rm -f $(INSTALL_PKGCONFIG_DIRECTORY)/brltty.*
+
+uninstall-documents:
+	-rm -r -f $(INSTALL_DOCUMENT_DIRECTORY)
+
+uninstall-headers:
+	-rm -f $(INSTALL_APIHDR_DIRECTORY)/brlapi.h
+	-rm -f $(INSTALL_APIHDR_DIRECTORY)/brlapi_*.h
+	-[ ! -d $(INSTALL_INCLUDE_DIRECTORY) ] || rm -f -r $(INSTALL_INCLUDE_DIRECTORY)
+
+uninstall-api-libraries:
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_ARC)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_LIB)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_LIB).$(API_VERSION)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_LIB).$(API_RELEASE)
+	$(CONFLIBDIR) $(INSTALL_APILIB_DIRECTORY)
+	-rm -f -r $(INSTALL_APISOC_DIRECTORY)
+
+uninstall-api-libraries-windows:
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_ARC)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_IMPLIB)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_IMPLIB_VERSIONED)
+	-rm -f $(INSTALL_APILIB_DIRECTORY)/$(API_DEF)
+	-rm -f $(INSTALL_PROGRAM_DIRECTORY)/$(API_DLL)
+
+###############################################################################
+
+install uninstall::
+	-set -- $(BRAILLE_EXTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(BRL_DIR)/$$driver && $(MAKE) $@); done
+	-set -- $(SPEECH_EXTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(SPK_DIR)/$$driver && $(MAKE) $@); done
+	-set -- $(SCREEN_EXTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(SCR_DIR)/$$driver && $(MAKE) $@); done
+
+distclean clean::
+	-set -- $(BRAILLE_EXTERNAL_DRIVER_NAMES) $(BRAILLE_INTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(BRL_DIR)/$$driver && $(MAKE) $@); done
+	-set -- $(SPEECH_EXTERNAL_DRIVER_NAMES) $(SPEECH_INTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(SPK_DIR)/$$driver && $(MAKE) $@); done
+	-set -- $(SCREEN_EXTERNAL_DRIVER_NAMES) $(SCREEN_INTERNAL_DRIVER_NAMES); \
+	for driver do (cd $(BLD_TOP)$(SCR_DIR)/$$driver && $(MAKE) $@); done
+	-set -- $(API_BINDINGS); \
+	for language do (cd $(BLD_TOP)$(BND_DIR)/$$language && $(MAKE) $@); done
+
+clean::
+	-rm -f brltty$X
+	-rm -f brltty-trtxt$X brltty-ttb$X brltty-ctb$X brltty-atb$X brltty-ktb$X
+	-rm -f brltty-tune$X brltty-morse$X brltty-pty$X
+	-rm -f brltty-cldr$X brltty-hid$X brltty-lscmds$X brltty-lsinc$X
+	-rm -f brltty-clip$X xbrlapi$X
+	-rm -f tbl2hex$(X_FOR_BUILD) *test$X *-static$X
+	-rm -f brlapi_constants.h *.$(LIB_EXT) *.$(LIB_EXT).* *.$(ARC_EXT) *.def *.class *.jar
+	-rm -f $(BLD_TOP)$(DRV_DIR)/*
+
+distclean::
+	-rm -f brlapi.h
diff --git a/Programs/activity.c b/Programs/activity.c
new file mode 100644
index 0000000..1b50156
--- /dev/null
+++ b/Programs/activity.c
@@ -0,0 +1,406 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "activity.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "async_wait.h"
+
+typedef enum {
+  ACT_STOPPED,
+  ACT_PREPARED,
+  ACT_SCHEDULED,
+  ACT_STARTED,
+
+  ACT_PREPARING,
+  ACT_PREPARING_STOP,
+
+  ACT_STARTING,
+  ACT_STARTING_STOP,
+  ACT_STARTING_RESTART,
+
+  ACT_STOPPING,
+  ACT_STOPPING_START
+} ActivityState;
+
+typedef struct {
+  const char *name;
+} ActivityStateDescriptor;
+
+static const ActivityStateDescriptor activityStateDescriptors[] = {
+  [ACT_STOPPED] = {
+    .name = "stopped"
+  },
+
+  [ACT_PREPARED] = {
+    .name = "prepared"
+  },
+
+  [ACT_SCHEDULED] = {
+    .name = "scheduled"
+  },
+
+  [ACT_STARTED] = {
+    .name = "started"
+  },
+
+  [ACT_PREPARING] = {
+    .name = "preparing"
+  },
+
+  [ACT_PREPARING_STOP] = {
+    .name = "preparing+stop"
+  },
+
+  [ACT_STARTING] = {
+    .name = "starting"
+  },
+
+  [ACT_STARTING_STOP] = {
+    .name = "starting+stop"
+  },
+
+  [ACT_STARTING_RESTART] = {
+    .name = "starting+restart"
+  },
+
+  [ACT_STOPPING] = {
+    .name = "stopping"
+  },
+
+  [ACT_STOPPING_START] = {
+    .name = "stopping+start"
+  },
+};
+
+struct ActivityObjectStruct {
+  const ActivityMethods *methods;
+  void *data;
+
+  ActivityState state;
+  AsyncHandle startAlarm;
+};
+
+static const char *
+getActivityStateName (ActivityState state) {
+  if (state < ARRAY_COUNT(activityStateDescriptors)) {
+    const char *name = activityStateDescriptors[state].name;
+
+    if (name && *name) return name;
+  }
+
+  return "unknown";
+}
+
+static void
+logUnexpectedActivityState (ActivityObject *activity, const char *action) {
+  ActivityState state = activity->state;
+
+  logMessage(LOG_WARNING, "unexpected activity state: %s: %s: %u[%s]",
+             activity->methods->activityName, action, state, getActivityStateName(state));
+}
+
+static void
+setActivityState (ActivityObject *activity, ActivityState state) {
+  logMessage(LOG_DEBUG, "activity state change: %s: %u[%s]",
+             activity->methods->activityName, state, getActivityStateName(state));
+
+  activity->state = state;
+}
+
+static void
+logActivityActionRequest (ActivityObject *activity, const char *action) {
+  logMessage(LOG_DEBUG, "activity action request: %s: %s",
+             activity->methods->activityName, action);
+}
+
+static void
+logActivityActionFailed (ActivityObject *activity, const char *action) {
+  logMessage(LOG_DEBUG, "activity action failed: %s: %s",
+             activity->methods->activityName, action);
+}
+
+static void
+logActivityActionTimeout (ActivityObject *activity, const char *action) {
+  logMessage(LOG_DEBUG, "activity action timeout: %s: %s",
+             activity->methods->activityName, action);
+}
+
+static void
+cancelActivityStartAlarm (ActivityObject *activity) {
+  asyncCancelRequest(activity->startAlarm);
+  activity->startAlarm = NULL;
+}
+
+ASYNC_ALARM_CALLBACK(handleActivityStartAlarm) {
+  ActivityObject *activity = parameters->data;
+  ActivityStartMethod *start = activity->methods->start;
+  ActivityState oldState = activity->state;
+  int started;
+  ActivityState newState;
+
+  setActivityState(activity, ACT_STARTING);
+  started = !start || start(activity->data);
+
+  if (started) {
+    cancelActivityStartAlarm(activity);
+  } else {
+    logActivityActionFailed(activity, "start");
+  }
+
+  newState = activity->state;
+  setActivityState(activity, (started? ACT_STARTED: oldState));
+
+  if (newState == ACT_STARTING_STOP) {
+    stopActivity(activity);
+  } else if (newState == ACT_STARTING_RESTART) {
+    stopActivity(activity);
+    startActivity(activity);
+  } else if (newState != ACT_STARTING) {
+    logUnexpectedActivityState(activity, "starting");
+  }
+}
+
+static int
+prepareActivity (ActivityObject *activity) {
+  ActivityPrepareMethod *prepare = activity->methods->prepare;
+  ActivityState oldState = activity->state;
+
+  if (!prepare) {
+    setActivityState(activity, ACT_PREPARED);
+    return 1;
+  }
+
+  setActivityState(activity, ACT_PREPARING);
+
+  if (!prepare(activity->data)) {
+    setActivityState(activity, oldState);
+    return 0;
+  }
+
+  if (activity->state == ACT_PREPARING) {
+    setActivityState(activity, ACT_PREPARED);
+    return 1;
+  }
+
+  if (activity->state == ACT_PREPARING_STOP) {
+    setActivityState(activity, ACT_STOPPED);
+    return 0;
+  }
+
+  logUnexpectedActivityState(activity, "preparing");
+  return 0;
+}
+
+static int
+scheduleActivity (ActivityObject *activity) {
+  if (asyncNewRelativeAlarm(&activity->startAlarm, 0, handleActivityStartAlarm, activity)) {
+    if (asyncResetAlarmInterval(activity->startAlarm, activity->methods->retryInterval)) {
+      setActivityState(activity, ACT_SCHEDULED);
+      return 1;
+    }
+
+    cancelActivityStartAlarm(activity);
+  }
+
+  return 0;
+}
+
+void
+startActivity (ActivityObject *activity) {
+  logActivityActionRequest(activity, "start");
+
+  while (1) {
+    switch (activity->state) {
+      case ACT_STOPPED:
+        if (prepareActivity(activity)) continue;
+        return;
+
+      case ACT_PREPARING_STOP:
+        setActivityState(activity, ACT_PREPARING);
+        continue;
+
+      case ACT_PREPARED:
+        if (scheduleActivity(activity)) continue;
+        return;
+
+      case ACT_SCHEDULED:
+        asyncResetAlarmIn(activity->startAlarm, 0);
+        return;
+
+      case ACT_STARTING_STOP:
+        setActivityState(activity, ACT_STARTING_RESTART);
+        continue;
+
+      case ACT_STOPPING:
+        setActivityState(activity, ACT_STOPPING_START);
+        continue;
+
+      case ACT_PREPARING:
+      case ACT_STARTING:
+      case ACT_STARTING_RESTART:
+      case ACT_STARTED:
+      case ACT_STOPPING_START:
+        return;
+    }
+
+    logUnexpectedActivityState(activity, "start");
+    break;
+  }
+}
+
+void
+stopActivity (ActivityObject *activity) {
+  logActivityActionRequest(activity, "stop");
+
+  while (1) {
+    switch (activity->state) {
+      case ACT_PREPARING:
+        setActivityState(activity, ACT_PREPARING_STOP);
+        continue;
+
+      case ACT_PREPARED:
+        setActivityState(activity, ACT_STOPPED);
+        continue;
+
+      case ACT_SCHEDULED:
+        cancelActivityStartAlarm(activity);
+        setActivityState(activity, ACT_PREPARED);
+        continue;
+
+      case ACT_STARTING:
+      case ACT_STARTING_RESTART:
+        setActivityState(activity, ACT_STARTING_STOP);
+        continue;
+
+      case ACT_STARTED: {
+        ActivityStopMethod *stop = activity->methods->stop;
+
+        if (stop) {
+          ActivityState newState;
+
+          setActivityState(activity, ACT_STOPPING);
+          stop(activity->data);
+          newState = activity->state;
+          setActivityState(activity, ACT_STOPPED);
+
+          if (newState == ACT_STOPPING_START) {
+            startActivity(activity);
+          } else if (newState != ACT_STOPPING) {
+            logUnexpectedActivityState(activity, "stopping");
+          }
+        } else {
+          setActivityState(activity, ACT_STOPPED);
+        }
+
+        return;
+      }
+
+      case ACT_STOPPING_START:
+        setActivityState(activity, ACT_STOPPING);
+        continue;
+
+      case ACT_PREPARING_STOP:
+      case ACT_STARTING_STOP:
+      case ACT_STOPPING:
+      case ACT_STOPPED:
+        return;
+    }
+
+    logUnexpectedActivityState(activity, "stop");
+    break;
+  }
+}
+
+ActivityObject *
+newActivity (const ActivityMethods *methods, void *data) {
+  ActivityObject *activity;
+
+  if ((activity = malloc(sizeof(*activity)))) {
+    memset(activity, 0, sizeof(*activity));
+
+    activity->methods = methods;
+    activity->data = data;
+
+    activity->state = ACT_STOPPED;
+    activity->startAlarm = NULL;
+
+    return activity;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyActivity (ActivityObject *activity) {
+  stopActivity(activity);
+  awaitActivityStopped(activity);
+  free(activity);
+}
+
+int
+isActivityStarted (const ActivityObject *activity) {
+  return activity->state == ACT_STARTED;
+}
+
+int
+isActivityStopped (const ActivityObject *activity) {
+  return activity->state == ACT_STOPPED;
+}
+
+ASYNC_CONDITION_TESTER(testActivityStarted) {
+  const ActivityObject *activity = data;
+
+  return isActivityStarted(activity);
+}
+
+int
+awaitActivityStarted (ActivityObject *activity) {
+  int timeout = activity->methods->startTimeout;
+
+  if (!timeout) timeout = DEFAULT_ACTIVITY_START_TIMEOUT;
+  if (asyncAwaitCondition(timeout, testActivityStarted, activity)) return 1;
+
+  logActivityActionTimeout(activity, "start");
+  return 0;
+}
+
+ASYNC_CONDITION_TESTER(testActivityStopped) {
+  const ActivityObject *activity = data;
+
+  return isActivityStopped(activity);
+}
+
+int
+awaitActivityStopped (ActivityObject *activity) {
+  int timeout = activity->methods->stopTimeout;
+
+  if (!timeout) timeout = DEFAULT_ACTIVITY_STOP_TIMEOUT;
+  if (asyncAwaitCondition(timeout, testActivityStopped, activity)) return 1;
+
+  logActivityActionTimeout(activity, "stop");
+  return 0;
+}
diff --git a/Programs/activity.h b/Programs/activity.h
new file mode 100644
index 0000000..43f3341
--- /dev/null
+++ b/Programs/activity.h
@@ -0,0 +1,59 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ACTIVITY
+#define BRLTTY_INCLUDED_ACTIVITY
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef int ActivityPrepareMethod (void *data);
+typedef int ActivityStartMethod (void *data);
+typedef void ActivityStopMethod (void *data);
+
+typedef struct {
+  const char *activityName;
+  int retryInterval;
+  int startTimeout;
+  int stopTimeout;
+
+  ActivityPrepareMethod *prepare;
+  ActivityStartMethod *start;
+  ActivityStopMethod *stop;
+} ActivityMethods;
+
+typedef struct ActivityObjectStruct ActivityObject;
+
+extern ActivityObject *newActivity (const ActivityMethods *methods, void *data);
+extern void destroyActivity (ActivityObject *activity);
+
+extern void startActivity (ActivityObject *activity);
+extern void stopActivity (ActivityObject *activity);
+
+extern int isActivityStarted (const ActivityObject *activity);
+extern int isActivityStopped (const ActivityObject *activity);
+
+extern int awaitActivityStarted (ActivityObject *activity);
+extern int awaitActivityStopped (ActivityObject *activity);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ACTIVITY */
diff --git a/Programs/addresses.c b/Programs/addresses.c
new file mode 100644
index 0000000..2a49316
--- /dev/null
+++ b/Programs/addresses.c
@@ -0,0 +1,168 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "addresses.h"
+#include "log.h"
+#include "dynld.h"
+#include "program.h"
+
+typedef struct {
+  void *address;
+  char name[0];
+} AddressEntry;
+
+static AddressEntry **addressTable = NULL;
+static int addressCount = 0;
+static int addressLimit = 0;
+
+static void
+exitAddressTable (void *data) {
+  if (addressTable) free(addressTable);
+
+  addressTable = NULL;
+  addressCount = 0;
+  addressLimit = 0;
+}
+
+static int
+findAddressIndex (int *index, void *address) {
+  int first = 0;
+  int last = addressCount - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    AddressEntry *entry = addressTable[current];
+
+    if (entry->address == address) {
+      *index = current;
+      return 1;
+    }
+
+    if (address < entry->address) {
+      last = current - 1;
+    } else {
+      first = current + 1;
+    }
+  }
+
+  *index = first;
+  return 0;
+}
+
+static void
+moveAddressSlice (int to, int from, int count) {
+  memmove(&addressTable[to], &addressTable[from], ARRAY_SIZE(addressTable, count));
+}
+
+static int
+insertAddressEntry (int index, AddressEntry *entry) {
+  if (addressCount == addressLimit) {
+    int newLimit = addressLimit + 1;
+    AddressEntry **newTable = realloc(addressTable, ARRAY_SIZE(newTable, newLimit));
+
+    if (!newTable) {
+      logMallocError();
+      return 0;
+    }
+
+    if (!addressTable) {
+      onProgramExit("address-table", exitAddressTable, NULL);
+    }
+
+    addressTable = newTable;
+    addressLimit = newLimit;
+  }
+
+  moveAddressSlice(index+1, index, (addressCount++ - index));
+  addressTable[index] = entry;
+  return 1;
+}
+
+static void
+removeAddressEntry (int index) {
+  free(addressTable[index]);
+  moveAddressSlice(index, index+1, (--addressCount - index));
+}
+
+int
+setAddressName (void *address, const char *format, ...) {
+  char name[0X1000];
+  AddressEntry *entry;
+  size_t size;
+
+  {
+    va_list arguments;
+
+    va_start(arguments, format);
+    vsnprintf(name, sizeof(name), format, arguments);
+    va_end(arguments);
+  }
+
+  size = sizeof(*entry) + strlen(name) + 1;
+
+  if ((entry = malloc(size))) {
+    int index;
+
+    memset(entry, 0, sizeof(*entry));
+    entry->address = address;
+    strcpy(entry->name, name);
+
+    if (findAddressIndex(&index, address)) {
+      free(addressTable[index]);
+      addressTable[index] = entry;
+      return 1;
+    }
+
+    if (insertAddressEntry(index, entry)) return 1;
+
+    free(entry);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+void
+unsetAddressName (void *address) {
+  int index;
+
+  if (findAddressIndex(&index, address)) {
+    removeAddressEntry(index);
+  }
+}
+
+const char *
+getAddressName (void *address, ptrdiff_t *offset) {
+  {
+    int index;
+
+    if (findAddressIndex(&index, address)) {
+      if (offset) *offset = 0;
+      return addressTable[index]->name;
+    }
+  }
+
+  return getSharedSymbolName(address, offset);
+}
diff --git a/Programs/alert.c b/Programs/alert.c
new file mode 100644
index 0000000..a317b1f
--- /dev/null
+++ b/Programs/alert.c
@@ -0,0 +1,287 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "alert.h"
+#include "program.h"
+#include "prefs.h"
+#include "tune.h"
+#include "tune_builder.h"
+#include "message.h"
+#include "brl_dots.h"
+#include "utf8.h"
+#include "core.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+#include "spk.h"
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+typedef struct {
+  unsigned char duration;
+  BrlDots pattern;
+} TactileAlert;
+
+typedef struct {
+  const char *tune;
+  const char *message;
+  TactileAlert tactile;
+} AlertEntry;
+
+#define ALERT_TACTILE(d,p) {.duration=(d), .pattern=(p)}
+
+static const AlertEntry alertTable[] = {
+  [ALERT_BRAILLE_ON] = {
+    .tune = "m64@60 m69@100"
+  },
+
+  [ALERT_BRAILLE_OFF] = {
+    .tune = "m64@60 m57@60"
+  },
+
+  [ALERT_COMMAND_DONE] = {
+    .message = strtext("Done"),
+    .tune = "m74@40 r@30 m74@40 r@40 m74@140 r@20 m79@50"
+  },
+
+  [ALERT_COMMAND_REJECTED] = {
+    .tactile = ALERT_TACTILE(50, BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6),
+    .tune = "m78@100"
+  },
+
+  [ALERT_MARK_SET] = {
+    .tune = "m83@20 m81@15 m79@15 m84@25"
+  },
+
+  [ALERT_CLIPBOARD_BEGIN] = {
+    .tune = "m74@40 m86@20"
+  },
+
+  [ALERT_CLIPBOARD_END] = {
+    .tune = "m86@50 m74@30"
+  },
+
+  [ALERT_NO_CHANGE] = {
+    .tactile = ALERT_TACTILE(30, BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6),
+    .tune = "m79@30 r@30 m79@30 r@30 m79@30"
+  },
+
+  [ALERT_TOGGLE_ON] = {
+    .tactile = ALERT_TACTILE(30, BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5),
+    .tune = "m74@30 r@30 m79@30 r@30 m86@30"
+  },
+
+  [ALERT_TOGGLE_OFF] = {
+    .tactile = ALERT_TACTILE(30, BRL_DOT_3 | BRL_DOT_7 | BRL_DOT_6 | BRL_DOT_8),
+    .tune = "m86@30 r@30 m79@30 r@30 m74@30"
+  },
+
+  [ALERT_CURSOR_LINKED] = {
+    .tune = "m80@7 m79@7 m76@12"
+  },
+
+  [ALERT_CURSOR_UNLINKED] = {
+    .tune = "m78@7 m79@7 m83@20"
+  },
+
+  [ALERT_SCREEN_FROZEN] = {
+    .message = strtext("Frozen"),
+    .tune = "m58@5 m59 m60 m61 m62 m63 m64 m65 m66 m67 m68 m69 m70 m71 m72 m73 m74 m76 m78 m80 m83 m86 m90 m95"
+  },
+
+  [ALERT_SCREEN_UNFROZEN] = {
+    .message = strtext("Unfrozen"),
+    .tune = "m95@5 m90 m86 m83 m80 m78 m76 m74 m73 m72 m71 m70 m69 m68 m67 m66 m65 m64 m63 m62 m61 m60 m59 m58"
+  },
+
+  [ALERT_FREEZE_REMINDER] = {
+    .tune = "m60@50 r@30 m60@50"
+  },
+
+  [ALERT_WRAP_DOWN] = {
+    .tactile = ALERT_TACTILE(20, BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_8),
+    .tune = "m86@6 m74@6 m62@6 m50@10"
+  },
+
+  [ALERT_WRAP_UP] = {
+    .tactile = ALERT_TACTILE(20, BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_7),
+    .tune = "m50@6 m62@6 m74@6 m86@10"
+  },
+
+  [ALERT_SKIP_FIRST] = {
+    .tactile = ALERT_TACTILE(30, BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_7 | BRL_DOT_8),
+    .tune = "r@40 m62@4 m67@6 m74@8 r@25"
+  },
+
+  [ALERT_SKIP_ONE] = {
+    .tune = "m74@10 r@18"
+  },
+
+  [ALERT_SKIP_SEVERAL] = {
+    .tune = "m73@20 r@1"
+  },
+
+  [ALERT_BOUNCE] = {
+    .tactile = ALERT_TACTILE(50, BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_7 | BRL_DOT_8),
+    .tune = "m98@6 m86@6 m74@6 m62@6 m50@10"
+  },
+
+  [ALERT_ROUTING_STARTED] = {
+    .tune = "m55@10 r@60 m60@15"
+  },
+
+  [ALERT_ROUTING_SUCCEEDED] = {
+    .tune = "m64@60 m76@20"
+  },
+
+  [ALERT_ROUTING_FAILED] = {
+    .tune = "m80@80 m79@90 m78@100 m77@100 r@20 m77@100 r@20 m77@150"
+  },
+
+  [ALERT_MODIFIER_ONCE] = {
+    .tune = "m70@60 m74@60 m77@90"
+  },
+
+  [ALERT_MODIFIER_LOCK] = {
+    .tune = "m70@60 m74@60 m77@60 m82@90"
+  },
+
+  [ALERT_MODIFIER_OFF] = {
+    .tune = "m82@60 m77@60 m74@60 m70@90"
+  },
+
+  [ALERT_CONSOLE_BELL] = {
+    .message = strtext("Console Bell"),
+    .tune = "m78@100"
+  },
+
+  [ALERT_KEYS_AUTORELEASED] = {
+    .message = strtext("Autorelease"),
+    .tune = "c6@50 b- g e- p50 c@100 c c"
+  },
+
+  [ALERT_SCROLL_UP] = {
+    .tune = "b6@10 d7"
+  },
+
+  [ALERT_CONTEXT_DEFAULT] = {
+    .tune = "m76@60 m73@60 m69@60 m66@90"
+  },
+
+  [ALERT_CONTEXT_PERSISTENT] = {
+    .tune = "m66@60 m69@60 m73@60 m76@90"
+  },
+
+  [ALERT_CONTEXT_TEMPORARY] = {
+    .tune = "m66@60 m69@60 m73@90"
+  },
+};
+
+static ToneElement *tuneTable[ARRAY_COUNT(alertTable)] = {NULL};
+static TuneBuilder *tuneBuilder = NULL;
+static ToneElement emptyTune[] = {TONE_STOP()};
+
+static void
+exitAlertTunes (void *data) {
+  tuneSynchronize();
+
+  {
+    ToneElement **tune = tuneTable;
+    ToneElement **end = tune + ARRAY_COUNT(tuneTable);
+
+    while (tune < end) {
+      if (*tune) {
+        if (*tune != emptyTune) free(*tune);
+        *tune = NULL;
+      }
+
+      tune += 1;
+    }
+  }
+
+  if (tuneBuilder) {
+    destroyTuneBuilder(tuneBuilder);
+    tuneBuilder = NULL;
+  }
+}
+
+static TuneBuilder *
+getTuneBuilder (void) {
+  if (!tuneBuilder) {
+    if (!(tuneBuilder = newTuneBuilder())) {
+      return NULL;
+    }
+
+    onProgramExit("alert-tunes", exitAlertTunes, NULL);
+  }
+
+  return tuneBuilder;
+}
+
+void
+alert (AlertIdentifier identifier) {
+  if (identifier < ARRAY_COUNT(alertTable)) {
+    const AlertEntry *alert = &alertTable[identifier];
+
+    if (prefs.alertTunes && alert->tune && *alert->tune) {
+      ToneElement **tune = &tuneTable[identifier];
+
+      if (!*tune) {
+        TuneBuilder *tb = getTuneBuilder();
+
+        if (tb) {
+          setTuneSourceName(tuneBuilder, "alert");
+          setTuneSourceIndex(tb, identifier);
+
+          if (parseTuneString(tb, "p100")) {
+            if (parseTuneString(tb, alert->tune)) {
+              *tune = getTune(tb);
+            }
+          }
+
+          resetTuneBuilder(tb);
+        }
+
+        if (!*tune) *tune = emptyTune;
+      }
+
+      tunePlayTones(*tune);
+    } else if (prefs.alertDots && alert->tactile.duration) {
+      showDotPattern(alert->tactile.pattern, alert->tactile.duration);
+    } else if (prefs.alertMessages && alert->message) {
+      message(NULL, gettext(alert->message), 0);
+    }
+  }
+}
+
+void
+speakAlertMessage (const char *message) {
+#ifdef ENABLE_SPEECH_SUPPORT
+  sayString(&spk, message, SAY_OPT_MUTE_FIRST);
+#endif /* ENABLE_SPEECH_SUPPORT */
+}
+
+void
+speakAlertText (const wchar_t *text) {
+  char *message = getUtf8FromWchars(text, wcslen(text), NULL);
+
+  if (message) {
+    speakAlertMessage(message);
+    free(message);
+  }
+}
diff --git a/Programs/api.h b/Programs/api.h
new file mode 100644
index 0000000..5583506
--- /dev/null
+++ b/Programs/api.h
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_BRLTTY_API
+#define BRLAPI_INCLUDED_BRLTTY_API
+
+#warning include <brlapi.h> instead of <brltty/api.h> (see man brlapi_deprecated)
+#include <brlapi.h>
+
+#endif /* BRLAPI_INCLUDED_BRLTTY_API */
diff --git a/Programs/api_control.c b/Programs/api_control.c
new file mode 100644
index 0000000..7548650
--- /dev/null
+++ b/Programs/api_control.c
@@ -0,0 +1,235 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "api_control.h"
+#include "api_server.h"
+#include "report.h"
+#include "core.h"
+
+#ifndef ENABLE_API
+const char *const api_serverParameters[] = {NULL};
+
+void
+api_logServerIdentity (int full) {
+}
+
+int
+api_startServer (BrailleDisplay *brl, char **parameters) {
+  return 0;
+}
+
+void
+api_stopServer (BrailleDisplay *brl) {
+}
+
+void
+api_linkServer (BrailleDisplay *brl) {
+}
+
+void
+api_unlinkServer (BrailleDisplay *brl) {
+}
+
+void
+api_suspendDriver (BrailleDisplay *brl) {
+}
+
+int
+api_resumeDriver (BrailleDisplay *brl) {
+  return 0;
+}
+
+int
+api_claimDriver (BrailleDisplay *brl) {
+  return 0;
+}
+
+void
+api_releaseDriver (BrailleDisplay *brl) {
+}
+
+int
+api_handleCommand (int command) {
+  return 0;
+}
+
+int
+api_handleKeyEvent (KeyGroup group, KeyNumber number, int press) {
+  return 0;
+}
+
+int
+api_flushOutput (BrailleDisplay *brl) {
+  return 0;
+}
+
+void
+api_updateParameter (brlapi_param_t parameter, brlapi_param_subparam_t subparam) {
+}
+#endif /* ENABLE_API */
+
+static int isRunning = 0;
+static int isLinked = 0;
+static int isClaimed = 0;
+
+static void
+apiLogServerIdentity (int full) {
+  api_logServerIdentity(full);
+}
+
+static const char *const *
+apiGetServerParameters (void) {
+  return api_serverParameters;
+}
+
+static int
+apiStartServer (char **parameters) {
+  if (api_startServer(&brl, parameters)) {
+    isRunning = 1;
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+apiStopServer (void) {
+  api_stopServer(&brl);
+  isRunning = 0;
+}
+
+static int
+apiIsServerRunning (void) {
+  return isRunning;
+}
+
+static void
+apiLinkServer (void) {
+  if (isRunning) {
+    api_linkServer(&brl);
+    isLinked = 1;
+  }
+}
+
+static void
+apiUnlinkServer (void) {
+  if (isRunning) {
+    api_unlinkServer(&brl);
+    isLinked = 0;
+  }
+}
+
+static int
+apiIsServerLinked (void) {
+  return isLinked;
+}
+
+static void
+apiSuspendDriver (void) {
+#ifdef ENABLE_API
+  if (isRunning) {
+    api_suspendDriver(&brl);
+  } else
+#endif /* ENABLE_API */
+
+  {
+    destructBrailleDriver();
+  }
+}
+
+static int
+apiResumeDriver (void) {
+#ifdef ENABLE_API
+  if (isRunning) return api_resumeDriver(&brl);
+#endif /* ENABLE_API */
+
+  return constructBrailleDriver();
+}
+
+static int
+apiClaimDriver (void) {
+  if (!isClaimed && isRunning) {
+    if (!api_claimDriver(&brl)) return 0;
+    isClaimed = 1;
+  }
+
+  return 1;
+}
+
+static void
+apiReleaseDriver (void) {
+  if (isClaimed) {
+    api_releaseDriver(&brl);
+    isClaimed = 0;
+  }
+}
+
+static int
+apiHandleCommand (int command) {
+  if (!isRunning) return 0;
+  return api_handleCommand(command);
+}
+
+static int
+apiHandleKeyEvent (KeyGroup group, KeyNumber number, int press) {
+  if (!isRunning) return 0;
+  return api_handleKeyEvent(group, number, press);
+}
+
+static int
+apiFlushOutput (void) {
+  if (!isRunning) return 1;
+  return api_flushOutput(&brl);
+}
+
+static void
+apiUpdateParameter (brlapi_param_t parameter, brlapi_param_subparam_t subparam) {
+  if (isRunning) {
+    api_updateParameter(parameter, subparam);
+  } else {
+    reportParameterUpdated(parameter, subparam);
+  }
+}
+
+const ApiMethods api = {
+  .logServerIdentity = apiLogServerIdentity,
+  .getServerParameters = apiGetServerParameters,
+
+  .startServer = apiStartServer,
+  .stopServer = apiStopServer,
+  .isServerRunning = apiIsServerRunning,
+
+  .linkServer = apiLinkServer,
+  .unlinkServer = apiUnlinkServer,
+  .isServerLinked = apiIsServerLinked,
+
+  .suspendDriver = apiSuspendDriver,
+  .resumeDriver = apiResumeDriver,
+
+  .claimDriver = apiClaimDriver,
+  .releaseDriver = apiReleaseDriver,
+
+  .handleCommand = apiHandleCommand,
+  .handleKeyEvent = apiHandleKeyEvent,
+
+  .flushOutput = apiFlushOutput,
+  .updateParameter = apiUpdateParameter
+};
diff --git a/Programs/api_control.h b/Programs/api_control.h
new file mode 100644
index 0000000..46d9439
--- /dev/null
+++ b/Programs/api_control.h
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_API_CONTROL
+#define BRLTTY_INCLUDED_API_CONTROL
+
+#include "brlapi_param.h"
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  void (*logServerIdentity) (int full);
+  const char *const * (*getServerParameters) (void);
+
+  int (*startServer) (char **parameters);
+  void (*stopServer) (void);
+  int (*isServerRunning) (void);
+
+  void (*linkServer) (void);
+  void (*unlinkServer) (void);
+  int (*isServerLinked) (void);
+
+  void (*suspendDriver) (void);
+  int (*resumeDriver) (void);
+
+  int (*claimDriver) (void);
+  void (*releaseDriver) (void);
+
+  int (*handleCommand) (int command);
+  int (*handleKeyEvent) (KeyGroup group, KeyNumber number, int press);
+
+  int (*flushOutput) (void);
+  void (*updateParameter) (brlapi_param_t parameter, brlapi_param_subparam_t subparam);
+} ApiMethods;
+
+extern const ApiMethods api;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_API_CONTROL */
diff --git a/Programs/api_server.h b/Programs/api_server.h
new file mode 100644
index 0000000..fb3c099
--- /dev/null
+++ b/Programs/api_server.h
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_API_SERVER
+#define BRLTTY_INCLUDED_API_SERVER
+
+#include "brl_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void api_logServerIdentity (int full);
+extern const char *const api_serverParameters[];
+
+extern int api_startServer (BrailleDisplay *brl, char **parameters);
+extern void api_stopServer (BrailleDisplay *brl);
+
+extern void api_linkServer (BrailleDisplay *brl);
+extern void api_unlinkServer (BrailleDisplay *brl);
+
+extern void api_suspendDriver (BrailleDisplay *brl);
+extern int api_resumeDriver (BrailleDisplay *brl);
+
+extern int api_claimDriver (BrailleDisplay *brl);
+extern void api_releaseDriver (BrailleDisplay *brl);
+
+extern int api_handleCommand (int command);
+extern int api_handleKeyEvent (KeyGroup group, KeyNumber number, int press);
+
+extern int api_flushOutput (BrailleDisplay *brl);
+
+extern void api_updateParameter (brlapi_param_t parameter, brlapi_param_subparam_t subparam);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_API_SERVER */
diff --git a/Programs/apitest.c b/Programs/apitest.c
new file mode 100644
index 0000000..da70b16
--- /dev/null
+++ b/Programs/apitest.c
@@ -0,0 +1,540 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* apitest provides a small test utility for BRLTTY's API */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#ifdef __MINGW32__
+#include "win_pthread.h"
+#else
+#include <pthread.h>
+#endif
+
+#include "cmdline.h"
+#include "pid.h"
+#include "brl_cmds.h"
+#include "brl_dots.h"
+#include "cmd.h"
+#include "cmd_brlapi.h"
+#include "async_wait.h"
+
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+
+static brlapi_connectionSettings_t settings;
+static char *opt_host;
+static char *opt_auth;
+
+static int opt_showName;
+static int opt_showModelIdentifier;
+static int opt_showSize;
+
+static int opt_showDots;
+static int opt_showKeyCodes;
+static int opt_learnMode;
+static int opt_parameters;
+
+static int opt_suspendMode;
+static int opt_threadMode;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "brlapi",
+    .letter = 'b',
+    .argument = "[host][:port]",
+    .setting.string = &opt_host,
+    .description = "BrlAPIa host and/or port to connect to."
+  },
+
+  { .word = "auth",
+    .letter = 'a',
+    .argument = "scheme+...",
+    .setting.string = &opt_auth,
+    .description = "BrlAPI authorization/authentication schemes."
+  },
+
+  { .word = "name",
+    .letter = 'n',
+    .setting.flag = &opt_showName,
+    .description = "Show the name of the braille driver."
+  },
+
+  { .word = "model",
+    .letter = 'm',
+    .setting.flag = &opt_showModelIdentifier,
+    .description = "Show the model identifier of the braille device."
+  },
+
+  { .word = "window",
+    .letter = 'w',
+    .setting.flag = &opt_showSize,
+    .description = "Show the dimensions of the braille window."
+  },
+
+  { .word = "dots",
+    .letter = 'd',
+    .setting.flag = &opt_showDots,
+    .description = "Show dot pattern."
+  },
+
+  { .word = "keycodes",
+    .letter = 'k',
+    .setting.flag = &opt_showKeyCodes,
+    .description = "Enter interactive keycode learn mode."
+  },
+
+  { .word = "learn",
+    .letter = 'l',
+    .setting.flag = &opt_learnMode,
+    .description = "Enter interactive command learn mode."
+  },
+
+  { .word = "parameters",
+    .letter = 'p',
+    .setting.flag = &opt_parameters,
+    .description = "Test parameters"
+  },
+
+  { .word = "suspend",
+    .letter = 's',
+    .setting.flag = &opt_suspendMode,
+    .description = "Suspend the braille driver (press ^C or send SIGUSR1 to resume)."
+  },
+
+  { .word = "thread",
+    .letter = 't',
+    .setting.flag = &opt_threadMode,
+    .description = "Exercise threaded use"
+  },
+END_OPTION_TABLE(programOptions)
+
+static void showDisplaySize(void)
+{
+  unsigned int x, y;
+  fprintf(stderr,"Getting display size: ");
+  if (brlapi_getDisplaySize(&x, &y)<0) {
+    brlapi_perror("failed");
+    exit(PROG_EXIT_FATAL);
+  }
+  fprintf(stderr, "%dX%d\n", x, y);
+}
+
+static void showDriverName(void)
+{
+  char name[30];
+  fprintf(stderr, "Getting driver name: ");
+  if (brlapi_getDriverName(name, sizeof(name))<0) {
+    brlapi_perror("failed");
+    exit(PROG_EXIT_FATAL);
+  }
+  fprintf(stderr, "%s\n", name);
+}
+
+static void showModelIdentifier(void)
+{
+  char identifier[30];
+  fprintf(stderr, "Getting model identifier: ");
+  if (brlapi_getModelIdentifier(identifier, sizeof(identifier))<0) {
+    brlapi_perror("failed");
+    exit(PROG_EXIT_FATAL);
+  }
+  fprintf(stderr, "%s\n", identifier);
+}
+
+#define DOTS_TEXT "dots: "
+#define DOTS_TEXTLEN (strlen(DOTS_TEXT))
+#define DOTS_LEN 8
+#define DOTS_TOTALLEN (DOTS_TEXTLEN + DOTS_LEN)
+static void showDots(void)
+{
+  unsigned int size;
+
+  {
+    unsigned int columns, rows;
+
+    if (brlapi_getDisplaySize(&columns, &rows) < 0) {
+      brlapi_perror("failed");
+      exit(PROG_EXIT_FATAL);
+    }
+
+    size = columns * rows;
+    unsigned int minimum = DOTS_TOTALLEN;
+
+    if (size < minimum) {
+      fprintf(stderr, "can't show dots on a braille display with less than %u cells\n", minimum);
+      exit(PROG_EXIT_SEMANTIC);
+    }
+  }
+
+  if (brlapi_enterTtyMode(-1, NULL) < 0) {
+    brlapi_perror("enterTtyMode");
+    exit(PROG_EXIT_FATAL);
+  }
+
+  {
+    fprintf(stderr, "Showing dot patterns\n");
+
+    char text[size + 1];
+    memset(text, ' ', size);
+    text[size] = 0;
+    memcpy(text, DOTS_TEXT, DOTS_TEXTLEN);
+
+    unsigned char or[size];
+    memset(or, 0, size);
+    or[DOTS_TEXTLEN+0] = BRL_DOT_1;
+    or[DOTS_TEXTLEN+1] = BRL_DOT_2;
+    or[DOTS_TEXTLEN+2] = BRL_DOT_3;
+    or[DOTS_TEXTLEN+3] = BRL_DOT_4;
+    or[DOTS_TEXTLEN+4] = BRL_DOT_5;
+    or[DOTS_TEXTLEN+5] = BRL_DOT_6;
+    or[DOTS_TEXTLEN+6] = BRL_DOT_7;
+    or[DOTS_TEXTLEN+7] = BRL_DOT_8;
+
+    brlapi_writeArguments_t wa = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+    wa.regionBegin = 1;
+    wa.regionSize = size;
+    wa.text = text;
+    wa.orMask = or;
+
+    if (brlapi_write(&wa) < 0) {
+      brlapi_perror("brlapi_write");
+      exit(PROG_EXIT_FATAL);
+    }
+  }
+
+  {
+    brlapi_keyCode_t key;
+    brlapi_readKey(1, &key);
+  }
+}
+
+static char *getKeyName (brlapi_keyCode_t key)
+{
+  return brlapi_getParameterAlloc(BRLAPI_PARAM_KEY_SHORT_NAME, (key & ~BRLAPI_DRV_KEY_PRESS), BRLAPI_PARAMF_GLOBAL, NULL);
+}
+
+static void listKeys(void)
+{
+  size_t length;
+  brlapi_param_driverKeycode_t *keys = brlapi_getParameterAlloc(BRLAPI_PARAM_DEVICE_KEY_CODES, 0, BRLAPI_PARAMF_GLOBAL, &length);
+
+  if (keys) {
+    length /= sizeof(*keys);
+    printf("%zu keys\n", length);
+
+    for (int i=0; i<length; i+=1) {
+      printf("key %04"BRLAPI_PRIxKEYCODE":", keys[i]);
+
+      {
+        char *name = getKeyName(keys[i]);
+
+        if (name) {
+          printf(" name %s", name);
+          free(name);
+        }
+      }
+
+      printf("\n");
+    }
+
+    free(keys);
+  }
+}
+
+static void showKeyCodes(void)
+{
+  char buf[0X100];
+
+  fprintf(stderr, "Entering keycode learn mode\n");
+
+  if (brlapi_getDriverName(buf, sizeof(buf))==-1) {
+    brlapi_perror("getDriverName");
+    return;
+  }
+
+  if (brlapi_enterTtyMode(-1, buf)<0) {
+    brlapi_perror("enterTtyMode");
+    return;
+  }
+
+  if (brlapi_acceptAllKeys()==-1) {
+    brlapi_perror("acceptAllKeys");
+    return;
+  }
+
+  if (brlapi_writeText(BRLAPI_CURSOR_OFF, "showing key codes")<0) {
+    brlapi_perror("brlapi_writeText");
+    exit(PROG_EXIT_FATAL);
+  }
+
+  int res;
+  brlapi_keyCode_t key;
+
+  while ((res = brlapi_readKeyWithTimeout(10000, &key)) > 0) {
+    const char *action = (key & BRLAPI_DRV_KEY_PRESS)? "press": "release";
+    size_t length = snprintf(buf, sizeof(buf), "%04" BRLAPI_PRIxKEYCODE " (%" BRLAPI_PRIuKEYCODE ") %s", key, key, action);
+
+    {
+      char *name = getKeyName(key);
+
+      if (name) {
+        snprintf(&buf[length], (sizeof(buf) - length), ": %s", name);
+        free(name);
+      }
+    }
+
+    brlapi_writeText(BRLAPI_CURSOR_OFF, buf);
+    fprintf(stderr, "%s\n", buf);
+  }
+
+  if (res < 0) brlapi_perror("brlapi_readKey");
+}
+
+static void enterLearnMode(void)
+{
+  int res;
+  brlapi_keyCode_t code;
+  int cmd;
+  char buf[0X100], *val;
+
+  fprintf(stderr,"Entering command learn mode\n");
+  if (brlapi_enterTtyMode(-1, NULL)<0) {
+    brlapi_perror("enterTtyMode");
+    return;
+  }
+
+  if (brlapi_writeText(BRLAPI_CURSOR_OFF, "command learn mode")<0) {
+    brlapi_perror("brlapi_writeText");
+    exit(PROG_EXIT_FATAL);
+  }
+
+  while ((res = brlapi_readKey(1, &code)) != -1) {
+    fprintf(stderr, "got key %016"BRLAPI_PRIxKEYCODE"\n",code);
+    cmd = cmdBrlapiToBrltty(code);
+    describeCommand(buf, sizeof(buf), cmd,
+                    (CDO_IncludeName | CDO_IncludeOperand));
+    brlapi_writeText(BRLAPI_CURSOR_OFF, buf);
+    fprintf(stderr, "%s\n", buf);
+    val = brlapi_getParameterAlloc(BRLAPI_PARAM_COMMAND_LONG_NAME, cmd, BRLAPI_PARAMF_GLOBAL, NULL);
+    fprintf(stderr, "%s\n", val);
+    free(val);
+    if (cmd==BRL_CMD_LEARN) return;
+  }
+  brlapi_perror("brlapi_readKey");
+}
+
+static void brailleRetainDotsChanged(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len)
+{
+  const brlapi_param_retainDots_t *d = data;
+  if (parameter != BRLAPI_PARAM_RETAIN_DOTS)
+  {
+    printf("handler called for %x, another parameter than retaindot parameter?!\n", parameter);
+    return;
+  }
+  printf("new retain dots %zd: %d\n", len, *d);
+}
+
+static void testParameters(void)
+{
+  brlapi_param_retainDots_t val;
+  if (brlapi_getParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &val, sizeof(val)) < 0) {
+    brlapi_perror("getParameter");
+  }
+  printf("retain dots was %d\n", val);
+  printf("now watching retain dots parameter\n");
+  if (brlapi_watchParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, brailleRetainDotsChanged, NULL, NULL, 0) == 0) {
+    brlapi_perror("watchParameter");
+  }
+  val = 0;
+  printf("setting retain dots parameter to %d\n", val);
+  if (brlapi_setParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &val, sizeof(val)) < 0) {
+    brlapi_perror("setParameter");
+  }
+  if (brlapi_getParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &val, sizeof(val)) < 0) {
+    brlapi_perror("getParameter");
+  }
+  printf("retain dots now %d\n", val);
+  val = 1;
+  printf("setting retain dots parameter to %d\n", val);
+  if (brlapi_setParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &val, sizeof(val)) < 0) {
+    brlapi_perror("setParameter");
+  }
+  if (brlapi_getParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &val, sizeof(val)) < 0) {
+    brlapi_perror("getParameter");
+  }
+  printf("retain dots now %d\n", val);
+
+  listKeys();
+}
+
+#ifdef SIGUSR1
+static void emptySignalHandler(int sig) { }
+#endif /* SIGUSR1 */
+
+static void suspendDriver(void)
+{
+  char driver[30];
+  fprintf(stderr, "Getting driver name: ");
+
+  if (brlapi_getDriverName(driver, sizeof(driver))<0) {
+    brlapi_perror("failed");
+    exit(PROG_EXIT_FATAL);
+  }
+  fprintf(stderr, "%s\n", driver);
+
+  fprintf(stderr, "Suspending driver\n");
+  if (brlapi_suspendDriver(driver)) {
+    brlapi_perror("suspend");
+  } else {
+#ifdef SIGUSR1
+    signal(SIGUSR1,emptySignalHandler);
+#endif /* SIGUSR1 */
+
+    {
+      ProcessIdentifier pid = getProcessIdentifier();
+      fprintf(stderr, "Waiting (to resume, send SIGUSR1 to process %"PRIpid")\n", pid);
+    }
+
+    brlapi_pause(-1);
+
+#ifdef SIGUSR1
+    signal(SIGUSR1,SIG_DFL);
+#endif /* SIGUSR1 */
+
+    fprintf(stderr, "Resuming driver\n");
+    if (brlapi_resumeDriver()) {
+      brlapi_perror("resumeDriver");
+    }
+  }
+}
+
+volatile int thread_done;
+static void *thread_fun(void *foo)
+{
+  brlapi_keyCode_t code;
+  do {
+    brlapi_readKey(1, &code);
+    printf("got key %"PRIx64"\n", code);
+    if (brlapi_readKeyWithTimeout(1000, &code) != 1) {
+      printf("didn't get a key within the 1s delay\n");
+    } else {
+      printf("got key %"PRIx64" within the 1s delay\n", code);
+    }
+  } while (code != (BRLAPI_KEY_TYPE_CMD | BRL_CMD_HOME));
+  thread_done = 1;
+  return NULL;
+}
+
+static void exerciseThreads(void)
+{
+  pthread_t thread;
+  unsigned x, y;
+  unsigned i = 0;
+  if (brlapi_getDisplaySize(&x, &y)<0) {
+    brlapi_perror("failed");
+    exit(PROG_EXIT_FATAL);
+  }
+  if (brlapi_enterTtyMode(-1, NULL)<0) {
+    brlapi_perror("enterTtyMode");
+    exit(PROG_EXIT_FATAL);
+  }
+  pthread_create(&thread, NULL, thread_fun, NULL);
+  while (!thread_done) {
+    char buf[x+1];
+    snprintf(buf, sizeof(buf), "counting %d", i);
+    buf[x] = 0;
+    brlapi_writeText(BRLAPI_CURSOR_OFF, buf);
+    asyncWait(1000);
+    i++;
+  }
+  pthread_join(thread, NULL);
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_SUCCESS;
+  brlapi_fileDescriptor fd;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "apitest",
+
+      .usage = {
+        .purpose = strtext("Test BrlAPI functions."),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  settings.host = opt_host;
+  settings.auth = opt_auth;
+  fprintf(stderr, "Connecting to BrlAPI... ");
+  if ((fd=brlapi_openConnection(&settings, &settings)) != (brlapi_fileDescriptor)(-1)) {
+    fprintf(stderr, "done (fd=%"PRIfd")\n", fd);
+    fprintf(stderr,"Connected to %s using auth %s\n", settings.host, settings.auth);
+
+    if (opt_showName) {
+      showDriverName();
+    }
+
+    if (opt_showModelIdentifier) {
+      showModelIdentifier();
+    }
+
+    if (opt_showSize) {
+      showDisplaySize();
+    }
+
+    if (opt_showDots) {
+      showDots();
+    }
+
+    if (opt_showKeyCodes) {
+      showKeyCodes();
+    }
+
+    if (opt_learnMode) {
+      enterLearnMode();
+    }
+
+    if (opt_parameters) {
+      testParameters();
+    }
+
+    if (opt_suspendMode) {
+      suspendDriver();
+    }
+
+    if (opt_threadMode) {
+      exerciseThreads();
+    }
+
+    brlapi_closeConnection();
+    fprintf(stderr, "Disconnected\n");
+  } else {
+    fprintf(stderr, "failed to connect to %s using auth %s",settings.host, settings.auth);
+    brlapi_perror("");
+    exitStatus = PROG_EXIT_FATAL;
+  }
+  return exitStatus;
+}
diff --git a/Programs/async_alarm.c b/Programs/async_alarm.c
new file mode 100644
index 0000000..075ca21
--- /dev/null
+++ b/Programs/async_alarm.c
@@ -0,0 +1,292 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_alarm.h"
+#include "async_internal.h"
+#include "timing.h"
+
+typedef struct {
+  TimeValue time;
+  int interval;
+
+  AsyncAlarmCallback *callback;
+  void *data;
+
+  unsigned active:1;
+  unsigned cancel:1;
+  unsigned reschedule:1;
+} AlarmEntry;
+
+struct AsyncAlarmDataStruct {
+  Queue *alarmQueue;
+};
+
+void
+asyncDeallocateAlarmData (AsyncAlarmData *ad) {
+  if (ad) {
+    if (ad->alarmQueue) deallocateQueue(ad->alarmQueue);
+    free(ad);
+  }
+}
+
+static AsyncAlarmData *
+getAlarmData (void) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+  if (!tsd) return NULL;
+
+  if (!tsd->alarmData) {
+    AsyncAlarmData *ad;
+
+    if (!(ad = malloc(sizeof(*ad)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(ad, 0, sizeof(*ad));
+    ad->alarmQueue = NULL;
+    tsd->alarmData = ad;
+  }
+
+  return tsd->alarmData;
+}
+
+static void
+cancelAlarm (Element *element) {
+  AlarmEntry *alarm = getElementItem(element);
+
+  if (alarm->active) {
+    alarm->cancel = 1;
+  } else {
+    deleteElement(element);
+  }
+}
+
+static void
+deallocateAlarmEntry (void *item, void *data) {
+  AlarmEntry *alarm = item;
+
+  free(alarm);
+}
+
+static int
+compareAlarmEntries (const void *newItem, const void *existingItem, void *queueData) {
+  const AlarmEntry *newAlarm = newItem;
+  const AlarmEntry *existingAlarm = existingItem;
+
+  return compareTimeValues(&newAlarm->time, &existingAlarm->time) < 0;
+}
+
+static Queue *
+getAlarmQueue (int create) {
+  AsyncAlarmData *ad = getAlarmData();
+  if (!ad) return NULL;
+
+  if (!ad->alarmQueue && create) {
+    if ((ad->alarmQueue = newQueue(deallocateAlarmEntry, compareAlarmEntries))) {
+      static AsyncQueueMethods methods = {
+        .cancelRequest = cancelAlarm
+      };
+
+      setQueueData(ad->alarmQueue, &methods);
+    }
+  }
+
+  return ad->alarmQueue;
+}
+
+typedef struct {
+  const TimeValue *time;
+  AsyncAlarmCallback *callback;
+  void *data;
+} AlarmElementParameters;
+
+static Element *
+newAlarmElement (const void *parameters) {
+  const AlarmElementParameters *aep = parameters;
+  Queue *alarms = getAlarmQueue(1);
+
+  if (alarms) {
+    AlarmEntry *alarm;
+
+    if ((alarm = malloc(sizeof(*alarm)))) {
+      memset(alarm, 0, sizeof(*alarm));
+
+      alarm->time = *aep->time;
+
+      alarm->callback = aep->callback;
+      alarm->data = aep->data;
+
+      alarm->active = 0;
+      alarm->cancel = 0;
+      alarm->reschedule = 0;
+
+      {
+        Element *element = enqueueItem(alarms, alarm);
+
+        if (element) {
+          logSymbol(LOG_CATEGORY(ASYNC_EVENTS), aep->callback, "alarm added");
+          return element;
+        }
+      }
+
+      free(alarm);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+int
+asyncNewAbsoluteAlarm (
+  AsyncHandle *handle,
+  const TimeValue *time,
+  AsyncAlarmCallback *callback,
+  void *data
+) {
+  const AlarmElementParameters aep = {
+    .time = time,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newAlarmElement, &aep);
+}
+
+int
+asyncNewRelativeAlarm (
+  AsyncHandle *handle,
+  int milliseconds,
+  AsyncAlarmCallback *callback,
+  void *data
+) {
+  TimeValue time;
+
+  getMonotonicTime(&time);
+  adjustTimeValue(&time, milliseconds);
+  return asyncNewAbsoluteAlarm(handle, &time, callback, data);
+}
+
+static Element *
+getAlarmElement (AsyncHandle handle) {
+  return asyncGetHandleElement(handle, getAlarmQueue(0));
+}
+
+int
+asyncResetAlarmTo (AsyncHandle handle, const TimeValue *time) {
+  Element *element = getAlarmElement(handle);
+
+  if (element) {
+    AlarmEntry *alarm = getElementItem(element);
+
+    alarm->time = *time;
+    requeueElement(element);
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+asyncResetAlarmIn (AsyncHandle handle, int milliseconds) {
+  TimeValue time;
+
+  getMonotonicTime(&time);
+  adjustTimeValue(&time, milliseconds);
+  return asyncResetAlarmTo(handle, &time);
+}
+
+int
+asyncResetAlarmInterval (AsyncHandle handle, int milliseconds) {
+  Element *element = getAlarmElement(handle);
+
+  if (element) {
+    AlarmEntry *alarm = getElementItem(element);
+
+    alarm->interval = milliseconds;
+    alarm->reschedule = milliseconds > 0;
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+testInactiveAlarm (void *item, void *data) {
+  const AlarmEntry *alarm = item;
+
+  return !alarm->active;
+}
+
+int
+asyncExecuteAlarmCallback (AsyncAlarmData *ad, long int *timeout) {
+  if (ad) {
+    Queue *alarms = ad->alarmQueue;
+
+    if (alarms) {
+      Element *element = processQueue(alarms, testInactiveAlarm, NULL);
+
+      if (element) {
+        AlarmEntry *alarm = getElementItem(element);
+        TimeValue now;
+        long int milliseconds;
+
+        getMonotonicTime(&now);
+        milliseconds = millisecondsBetween(&now, &alarm->time);
+
+        if (milliseconds <= 0) {
+          AsyncAlarmCallback *callback = alarm->callback;
+          const AsyncAlarmCallbackParameters parameters = {
+            .now = &now,
+            .data = alarm->data
+          };
+
+          logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "alarm starting");
+          alarm->active = 1;
+          if (callback) callback(&parameters);
+          alarm->active = 0;
+
+          if (alarm->reschedule) {
+            adjustTimeValue(&alarm->time, alarm->interval);
+            getMonotonicTime(&now);
+            if (compareTimeValues(&alarm->time, &now) < 0) alarm->time = now;
+            requeueElement(element);
+          } else {
+            alarm->cancel = 1;
+          }
+
+          if (alarm->cancel) deleteElement(element);
+          return 1;
+        }
+
+        if (milliseconds < *timeout) {
+          *timeout = milliseconds;
+          logSymbol(LOG_CATEGORY(ASYNC_EVENTS), alarm->callback, "next alarm: %ld", *timeout);
+        }
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/Programs/async_data.c b/Programs/async_data.c
new file mode 100644
index 0000000..20edb36
--- /dev/null
+++ b/Programs/async_data.c
@@ -0,0 +1,69 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "thread.h"
+#include "async_internal.h"
+
+static THREAD_SPECIFIC_DATA_NEW(tsdAsync) {
+  AsyncThreadSpecificData *tsd;
+
+  if ((tsd = malloc(sizeof(*tsd)))) {
+    memset(tsd, 0, sizeof(*tsd));
+
+    tsd->waitData = NULL;
+    tsd->alarmData = NULL;
+    tsd->taskData = NULL;
+    tsd->ioData = NULL;
+    tsd->signalData = NULL;
+
+    return tsd;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static THREAD_SPECIFIC_DATA_DESTROY(tsdAsync) {
+  AsyncThreadSpecificData *tsd = data;
+
+  if (tsd) {
+    asyncDeallocateWaitData(tsd->waitData);
+    asyncDeallocateAlarmData(tsd->alarmData);
+    asyncDeallocateTaskData(tsd->taskData);
+    asyncDeallocateIoData(tsd->ioData);
+
+#ifdef ASYNC_CAN_HANDLE_SIGNALS
+    asyncDeallocateSignalData(tsd->signalData);
+#endif /* ASYNC_CAN_HANDLE_SIGNALS */
+
+    free(tsd);
+  }
+}
+
+THREAD_SPECIFIC_DATA_CONTROL(tsdAsync);
+
+AsyncThreadSpecificData *
+asyncGetThreadSpecificData (void) {
+  return getThreadSpecificData(&tsdAsync);
+}
diff --git a/Programs/async_event.c b/Programs/async_event.c
new file mode 100644
index 0000000..0099550
--- /dev/null
+++ b/Programs/async_event.c
@@ -0,0 +1,163 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_io.h"
+#include "async_event.h"
+#include "async_internal.h"
+#include "file.h"
+#include "io_misc.h"
+
+struct AsyncEventStruct {
+  AsyncEventCallback *callback;
+  void *data;
+
+  FileDescriptor pipeInput;
+  FileDescriptor pipeOutput;
+
+  FileDescriptor monitorDescriptor;
+  AsyncHandle monitorHandle;
+
+#ifdef __MINGW32__
+  CRITICAL_SECTION criticalSection;
+  unsigned int pendingCount;
+#endif /* __MINGW32__ */
+};
+
+ASYNC_MONITOR_CALLBACK(asyncMonitorEventPipe) {
+  AsyncEvent *event = parameters->data;
+  void *data;
+  const size_t size = sizeof(data);
+
+  if (readFileDescriptor(event->pipeOutput, &data, size) == size) {
+#ifdef __MINGW32__
+    EnterCriticalSection(&event->criticalSection);
+    if (!--event->pendingCount) ResetEvent(event->monitorDescriptor);
+    LeaveCriticalSection(&event->criticalSection);
+#endif /* __MINGW32__ */
+
+    {
+      AsyncEventCallback *callback = event->callback;
+
+      const AsyncEventCallbackParameters parameters = {
+        .eventData = event->data,
+        .signalData = data
+      };
+
+      logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "event starting");
+      if (callback) callback(&parameters);
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+asyncSignalEvent (AsyncEvent *event, void *data) {
+  const size_t size = sizeof(data);
+  ssize_t result = writeFileDescriptor(event->pipeInput, &data, size);
+
+  if (result == size) {
+#ifdef __MINGW32__
+    EnterCriticalSection(&event->criticalSection);
+    if (!event->pendingCount++) SetEvent(event->monitorDescriptor);
+    LeaveCriticalSection(&event->criticalSection);
+#endif /* __MINGW32__ */
+
+    return 1;
+  }
+
+  if (result == -1) {
+    logSystemError("write");
+  } else {
+    logMessage(LOG_ERR, "short write"); 
+  }
+
+  return 0;
+}
+
+AsyncEvent *
+asyncNewEvent (AsyncEventCallback *callback, void *data) {
+  AsyncEvent *event;
+
+  if ((event = malloc(sizeof(*event)))) {
+    memset(event, 0, sizeof(*event));
+    event->callback = callback;
+    event->data = data;
+
+    if (createAnonymousPipe(&event->pipeInput, &event->pipeOutput)) {
+#ifdef __MINGW32__
+      if (!(event->monitorDescriptor = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+        logWindowsSystemError("CreateEvent");
+        event->monitorDescriptor = INVALID_FILE_DESCRIPTOR;
+      }
+#else /* __MINGW32__ */
+      setCloseOnExec(event->pipeInput, 1);
+      setCloseOnExec(event->pipeOutput, 1);
+      event->monitorDescriptor = event->pipeOutput;
+#endif /* __MINGW32__ */
+
+      if (event->monitorDescriptor != INVALID_FILE_DESCRIPTOR) {
+        if (asyncMonitorFileInput(&event->monitorHandle, event->monitorDescriptor,
+                                  asyncMonitorEventPipe, event)) {
+#ifdef __MINGW32__
+          InitializeCriticalSection(&event->criticalSection);
+          event->pendingCount = 0;
+#endif /* __MINGW32__ */
+
+          logSymbol(LOG_CATEGORY(ASYNC_EVENTS), event->callback, "event added");
+          return event;
+        }
+
+        closeFileDescriptor(event->monitorDescriptor);
+      }
+
+      closeFileDescriptor(event->pipeInput);
+      closeFileDescriptor(event->pipeOutput);
+    }
+
+    free(event);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+asyncDiscardEvent (AsyncEvent *event) {
+  asyncCancelRequest(event->monitorHandle);
+
+  closeFileDescriptor(event->pipeInput);
+  closeFileDescriptor(event->pipeOutput);
+
+#ifdef __MINGW32__
+  CloseHandle(event->monitorDescriptor);
+  DeleteCriticalSection(&event->criticalSection);
+#endif /* __MINGW32__ */
+
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS), event->callback, "event removed");
+  free(event);
+}
+
diff --git a/Programs/async_handle.c b/Programs/async_handle.c
new file mode 100644
index 0000000..5245f71
--- /dev/null
+++ b/Programs/async_handle.c
@@ -0,0 +1,134 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_internal.h"
+
+struct AsyncHandleStruct {
+  Element *element;
+  int identifier;
+  AsyncThreadSpecificData *tsd;
+};
+
+int
+asyncMakeHandle (
+  AsyncHandle *handle,
+  Element *(*newElement) (const void *parameters),
+  const void *parameters
+) {
+  if (handle) {
+    if (!(*handle = malloc(sizeof(**handle)))) {
+      logMallocError();
+      return 0;
+    }
+  }
+
+  {
+    Element *element = newElement(parameters);
+
+    if (element) {
+      if (handle) {
+        memset(*handle, 0, sizeof(**handle));
+        (*handle)->element = element;
+        (*handle)->identifier = getElementIdentifier(element);
+        (*handle)->tsd = asyncGetThreadSpecificData();
+      }
+
+      return 1;
+    }
+  }
+
+  if (handle) free(*handle);
+  return 0;
+}
+
+int
+asyncTestHandle (AsyncHandle handle) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+
+  if (handle->tsd == tsd) return 1;
+  logMessage(LOG_WARNING, "invalid async handle");
+  return 0;
+}
+
+Element *
+asyncGetHandleElement (AsyncHandle handle, const Queue *queue) {
+  if (asyncTestHandle(handle)) {
+    if (queue) {
+      Element *element = handle->element;
+
+      if (handle->identifier == getElementIdentifier(element)) {
+        if ((queue == ASYNC_ANY_QUEUE) || (queue == getElementQueue(element))) {
+          return element;
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+void
+asyncDiscardHandle (AsyncHandle handle) {
+  free(handle);
+}
+
+void
+asyncCancelRequest (AsyncHandle handle) {
+  Element *element = asyncGetHandleElement(handle, ASYNC_ANY_QUEUE);
+
+  asyncDiscardHandle(handle);
+
+  if (element) {
+    Queue *queue = getElementQueue(element);
+    const AsyncQueueMethods *methods = getQueueData(queue);
+
+    if (methods) {
+      if (methods->cancelRequest) {
+        methods->cancelRequest(element);
+        return;
+      }
+    }
+
+    deleteElement(element);
+  }
+}
+
+typedef struct {
+  Element *element;
+} ElementHandleParameters;
+
+static Element *
+newElementHandle (const void *parameters) {
+  const ElementHandleParameters *ehp = parameters;
+
+  return ehp->element;
+}
+
+int
+asyncMakeElementHandle (AsyncHandle *handle, Element *element) {
+  const ElementHandleParameters ehp = {
+    .element = element
+  };
+
+  return asyncMakeHandle(handle, newElementHandle, &ehp);
+}
diff --git a/Programs/async_internal.h b/Programs/async_internal.h
new file mode 100644
index 0000000..3e62dc9
--- /dev/null
+++ b/Programs/async_internal.h
@@ -0,0 +1,76 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ASYNC_INTERNAL
+#define BRLTTY_INCLUDED_ASYNC_INTERNAL
+
+#include "async_handle.h"
+#include "queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct AsyncWaitDataStruct AsyncWaitData;
+extern void asyncDeallocateWaitData (AsyncWaitData *waitData);
+
+typedef struct AsyncAlarmDataStruct AsyncAlarmData;
+extern void asyncDeallocateAlarmData (AsyncAlarmData *alarmData);
+extern int asyncExecuteAlarmCallback (AsyncAlarmData *ad, long int *timeout);
+
+typedef struct AsyncTaskDataStruct AsyncTaskData;
+extern void asyncDeallocateTaskData (AsyncTaskData *taskData);
+extern int asyncExecuteTaskCallback (AsyncTaskData *td);
+
+typedef struct AsyncIoDataStruct AsyncIoData;
+extern void asyncDeallocateIoData (AsyncIoData *ioData);
+extern int asyncExecuteIoCallback (AsyncIoData *iod, long int timeout);
+
+typedef struct AsyncSignalDataStruct AsyncSignalData;
+extern void asyncDeallocateSignalData (AsyncSignalData *sd);
+
+typedef struct {
+  AsyncWaitData *waitData;
+  AsyncAlarmData *alarmData;
+  AsyncTaskData *taskData;
+  AsyncIoData *ioData;
+  AsyncSignalData *signalData;
+} AsyncThreadSpecificData;
+
+extern AsyncThreadSpecificData *asyncGetThreadSpecificData (void);
+
+extern int asyncMakeHandle (
+  AsyncHandle *handle,
+  Element *(*newElement) (const void *parameters),
+  const void *parameters
+);
+
+extern int asyncMakeElementHandle (AsyncHandle *handle, Element *element);
+
+#define ASYNC_ANY_QUEUE ((const Queue *)1)
+extern Element *asyncGetHandleElement (AsyncHandle handle, const Queue *queue);
+
+typedef struct {
+  void (*cancelRequest) (Element *element);
+} AsyncQueueMethods;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ASYNC_INTERNAL */
diff --git a/Programs/async_io.c b/Programs/async_io.c
new file mode 100644
index 0000000..34243d8
--- /dev/null
+++ b/Programs/async_io.c
@@ -0,0 +1,1325 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "get_select.h"
+
+#ifdef __MINGW32__
+#if _WIN32_WINNT < _WIN32_WINNT_VISTA
+#define CancelIoEx(handle, ol) CancelIo((handle))
+#endif /* _WIN32_WINNT < _WIN32_WINNT_VISTA */
+#endif /* __MINGW32__ */
+
+#ifdef __MSDOS__
+#include "system_msdos.h"
+#endif /* __MSDOS__ */
+
+#undef ASYNC_CAN_MONITOR_IO
+#if defined(__MINGW32__)
+#define ASYNC_CAN_MONITOR_IO
+
+typedef HANDLE MonitorEntry;
+
+#elif defined(HAVE_SYS_POLL_H)
+#define ASYNC_CAN_MONITOR_IO
+
+#include <poll.h>
+typedef struct pollfd MonitorEntry;
+
+#elif defined(GOT_SELECT)
+#define ASYNC_CAN_MONITOR_IO
+
+typedef struct {
+  int size;
+  fd_set set;
+} SelectDescriptor;
+
+static SelectDescriptor selectDescriptor_read;
+static SelectDescriptor selectDescriptor_write;
+static SelectDescriptor selectDescriptor_exception;
+
+typedef struct {
+  fd_set *selectSet;
+  FileDescriptor fileDescriptor;
+} MonitorEntry;
+
+#endif /* monitor definitions */
+
+#include "log.h"
+#include "async_io.h"
+#include "async_internal.h"
+#include "timing.h"
+
+typedef struct FunctionEntryStruct FunctionEntry;
+
+typedef struct {
+  AsyncMonitorCallback *callback;
+} MonitorExtension;
+
+typedef union {
+  struct {
+    AsyncInputCallback *callback;
+    unsigned end:1;
+  } input;
+
+  struct {
+    AsyncOutputCallback *callback;
+  } output;
+} TransferDirectionUnion;
+
+typedef struct {
+  TransferDirectionUnion direction;
+  size_t size;
+  size_t length;
+  unsigned char buffer[];
+} TransferExtension;
+
+typedef struct {
+  FunctionEntry *function;
+  void *extension;
+  void *data;
+
+  MonitorEntry *monitor;
+  int error;
+
+  unsigned active:1;
+  unsigned cancel:1;
+  unsigned finished:1;
+} OperationEntry;
+
+typedef struct {
+  const char *functionName;
+
+  void (*beginFunction) (FunctionEntry *function);
+  void (*endFunction) (FunctionEntry *function);
+
+  void (*startOperation) (OperationEntry *operation);
+  void (*finishOperation) (OperationEntry *operation);
+  void (*cancelOperation) (OperationEntry *operation);
+
+  int (*invokeCallback) (OperationEntry *operation);
+} FunctionMethods;
+
+struct FunctionEntryStruct {
+  FileDescriptor fileDescriptor;
+  const FunctionMethods *methods;
+  Queue *operations;
+
+#if defined(__MINGW32__)
+  struct {
+    OVERLAPPED overlapped;
+  } windows;
+
+#elif defined(HAVE_SYS_POLL_H)
+  struct {
+    short int events;
+  } poll;
+
+#elif defined(HAVE_SELECT)
+  struct {
+    SelectDescriptor *descriptor;
+  } select;
+
+#endif /* monitor paradigms */
+};
+
+typedef struct {
+  FileDescriptor fileDescriptor;
+  const FunctionMethods *methods;
+} FunctionKey;
+
+typedef struct {
+  MonitorEntry *const array;
+  unsigned int count;
+} MonitorGroup;
+
+struct AsyncIoDataStruct {
+  Queue *functionQueue;
+};
+
+void
+asyncDeallocateIoData (AsyncIoData *iod) {
+  if (iod) {
+    if (iod->functionQueue) deallocateQueue(iod->functionQueue);
+    free(iod);
+  }
+}
+
+static AsyncIoData *
+getIoData (void) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+  if (!tsd) return NULL;
+
+  if (!tsd->ioData) {
+    AsyncIoData *iod;
+
+    if (!(iod = malloc(sizeof(*iod)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(iod, 0, sizeof(*iod));
+    iod->functionQueue = NULL;
+    tsd->ioData = iod;
+  }
+
+  return tsd->ioData;
+}
+
+static void
+logOperation (const OperationEntry *operation, void *callback) {
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS),
+            callback,
+            "%s", operation->function->methods->functionName);
+}
+
+#ifdef __MINGW32__
+static void
+prepareMonitors (void) {
+}
+
+static int
+awaitMonitors (const MonitorGroup *monitors, int timeout) {
+  if (monitors->count) {
+    DWORD result = WaitForMultipleObjects(monitors->count, monitors->array, FALSE, timeout);
+    if ((result >= WAIT_OBJECT_0) && (result < (WAIT_OBJECT_0 + monitors->count))) return 1;
+
+    if (result == WAIT_FAILED) {
+      logWindowsSystemError("WaitForMultipleObjects");
+    }
+  } else {
+    approximateDelay(timeout);
+  }
+
+  return 0;
+}
+
+static void
+initializeMonitor (MonitorEntry *monitor, const FunctionEntry *function, const OperationEntry *operation) {
+  *monitor = function->windows.overlapped.hEvent;
+  if (*monitor == INVALID_HANDLE_VALUE) *monitor = function->fileDescriptor;
+}
+
+static int
+testMonitor (const MonitorEntry *monitor, int *error) {
+  DWORD result = WaitForSingleObject(*monitor, 0);
+  if (result == WAIT_OBJECT_0) return 1;
+
+  if (result == WAIT_FAILED) {
+    logWindowsSystemError("WaitForSingleObject");
+  }
+
+  return 0;
+}
+
+static int
+allocateWindowsEvent (HANDLE *event) {
+  if (*event == INVALID_HANDLE_VALUE) {
+    HANDLE handle = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (!handle) return 0;
+    *event = handle;
+  }
+
+  return ResetEvent(*event);
+}
+
+static void
+deallocateWindowsEvent (HANDLE *event) {
+  if (*event != INVALID_HANDLE_VALUE) {
+    CloseHandle(*event);
+    *event = INVALID_HANDLE_VALUE;
+  }
+}
+
+static int
+allocateWindowsResources (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+
+  if (allocateWindowsEvent(&function->windows.overlapped.hEvent)) {
+    return 1;
+  }
+
+  operation->finished = 1;
+  operation->error = GetLastError();
+  return 0;
+}
+
+static void
+setWindowsTransferResult (OperationEntry *operation, DWORD success, DWORD count) {
+  TransferExtension *extension = operation->extension;
+
+  if (success) {
+    extension->length += count;
+  } else {
+    DWORD error = GetLastError();
+
+    if ((error == ERROR_HANDLE_EOF) || (error == ERROR_BROKEN_PIPE)) {
+      extension->direction.input.end = 1;
+    } else {
+      setErrno(error);
+      operation->error = errno;
+
+      if (error == ERROR_IO_PENDING) return;
+      if (error == ERROR_IO_INCOMPLETE) return;
+    }
+  }
+
+  operation->finished = 1;
+}
+
+static void
+beginWindowsFunction (FunctionEntry *function) {
+  ZeroMemory(&function->windows.overlapped, sizeof(function->windows.overlapped));
+  function->windows.overlapped.hEvent = INVALID_HANDLE_VALUE;
+}
+
+static void
+endWindowsFunction (FunctionEntry *function) {
+  deallocateWindowsEvent(&function->windows.overlapped.hEvent);
+}
+
+static void
+startWindowsRead (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  TransferExtension *extension = operation->extension;
+
+  if (allocateWindowsResources(operation)) {
+    DWORD count;
+    BOOL success = ReadFile(function->fileDescriptor,
+                            &extension->buffer[extension->length],
+                            extension->size - extension->length,
+                            &count, &function->windows.overlapped);
+
+    setWindowsTransferResult(operation, success, count);
+  }
+}
+
+static void
+startWindowsWrite (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  TransferExtension *extension = operation->extension;
+
+  if (allocateWindowsResources(operation)) {
+    DWORD count;
+    BOOL success = WriteFile(function->fileDescriptor,
+                             &extension->buffer[extension->length],
+                             extension->size - extension->length,
+                             &count, &function->windows.overlapped);
+
+    setWindowsTransferResult(operation, success, count);
+  }
+}
+
+static void
+finishWindowsTransferOperation (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  DWORD count;
+  BOOL success = GetOverlappedResult(function->fileDescriptor, &function->windows.overlapped, &count, FALSE);
+
+  setWindowsTransferResult(operation, success, count);
+}
+
+static void
+cancelWindowsTransferOperation (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  DWORD count;
+
+  if (CancelIoEx(function->fileDescriptor, &function->windows.overlapped)) {
+    GetOverlappedResult(function->fileDescriptor, &function->windows.overlapped, &count, TRUE);
+  }
+}
+
+#else /* __MINGW32__ */
+
+#ifdef HAVE_SYS_POLL_H
+static void
+prepareMonitors (void) {
+}
+
+static int
+awaitMonitors (const MonitorGroup *monitors, int timeout) {
+  int result = poll(monitors->array, monitors->count, timeout);
+  if (result > 0) return 1;
+
+  if (result == -1) {
+    if (errno != EINTR) logSystemError("poll");
+  }
+
+  return 0;
+}
+
+static void
+initializeMonitor (MonitorEntry *monitor, const FunctionEntry *function, const OperationEntry *operation) {
+  monitor->fd = function->fileDescriptor;
+  monitor->events = function->poll.events;
+  monitor->revents = 0;
+}
+
+static int
+testMonitor (const MonitorEntry *monitor, int *error) {
+  if (!monitor->revents) return 0;
+
+  if (!(monitor->revents & monitor->events)) {
+    if (monitor->revents & POLLHUP) {
+      *error = ENODEV;
+    } else {
+      *error = EIO;
+    }
+  }
+
+  return 1;
+}
+
+static void
+beginUnixInputFunction (FunctionEntry *function) {
+  function->poll.events = POLLIN;
+}
+
+static void
+beginUnixOutputFunction (FunctionEntry *function) {
+  function->poll.events = POLLOUT;
+}
+
+static void
+beginUnixAlertFunction (FunctionEntry *function) {
+  function->poll.events = POLLPRI;
+}
+
+#elif defined(HAVE_SELECT)
+
+static void
+prepareSelectDescriptor (SelectDescriptor *descriptor) {
+  FD_ZERO(&descriptor->set);
+  descriptor->size = 0;
+}
+
+static void
+prepareMonitors (void) {
+  prepareSelectDescriptor(&selectDescriptor_read);
+  prepareSelectDescriptor(&selectDescriptor_write);
+  prepareSelectDescriptor(&selectDescriptor_exception);
+}
+
+static fd_set *
+getSelectSet (SelectDescriptor *descriptor) {
+  return descriptor->size? &descriptor->set: NULL;
+}
+
+static int
+doSelect (int setSize, fd_set *readSet, fd_set *writeSet, fd_set *exceptionSet, int timeout) {
+  struct timeval time = {
+    .tv_sec = timeout / MSECS_PER_SEC,
+    .tv_usec = (timeout % MSECS_PER_SEC) * USECS_PER_MSEC
+  };
+
+  {
+    int result = select(setSize, readSet, writeSet, exceptionSet, &time);
+    if (result > 0) return 1;
+
+    if (result == -1) {
+      if (errno != EINTR) logSystemError("select");
+    }
+
+    return 0;
+  }
+}
+
+static int
+awaitMonitors (const MonitorGroup *monitors, int timeout) {
+  fd_set *readSet = getSelectSet(&selectDescriptor_read);
+  fd_set *writeSet = getSelectSet(&selectDescriptor_write);
+  fd_set *exceptionSet = getSelectSet(&selectDescriptor_exception);
+
+  int setSize = selectDescriptor_read.size;
+  setSize = MAX(setSize, selectDescriptor_write.size);
+  setSize = MAX(setSize, selectDescriptor_exception.size);
+
+#ifdef __MSDOS__
+  int elapsed = 0;
+
+  do {
+    fd_set readSet1, writeSet1, exceptionSet1;
+
+    if (readSet) readSet1 = *readSet;
+    if (writeSet) writeSet1 = *writeSet;
+    if (exceptionSet) exceptionSet1 = *exceptionSet;
+
+    if (doSelect(setSize,
+                 (readSet? &readSet1: NULL),
+                 (writeSet? &writeSet1: NULL),
+                 (exceptionSet? &exceptionSet1: NULL),
+                 0)) {
+      if (readSet) *readSet = readSet1;
+      if (writeSet) *writeSet = writeSet1;
+      if (exceptionSet) *exceptionSet = exceptionSet1;
+      return 1;
+    }
+  } while ((elapsed += msdosUSleep(USECS_PER_MSEC)) < timeout);
+#else /* __MSDOS__ */
+  if (doSelect(setSize, readSet, writeSet, exceptionSet, timeout)) return 1;
+#endif /* __MSDOS__ */
+
+  return 0;
+}
+
+static void
+initializeMonitor (MonitorEntry *monitor, const FunctionEntry *function, const OperationEntry *operation) {
+  monitor->selectSet = &function->select.descriptor->set;
+  monitor->fileDescriptor = function->fileDescriptor;
+  FD_SET(function->fileDescriptor, &function->select.descriptor->set);
+
+  if (function->fileDescriptor >= function->select.descriptor->size) {
+    function->select.descriptor->size = function->fileDescriptor + 1;
+  }
+}
+
+static int
+testMonitor (const MonitorEntry *monitor, int *error) {
+  return FD_ISSET(monitor->fileDescriptor, monitor->selectSet);
+}
+
+static void
+beginUnixInputFunction (FunctionEntry *function) {
+  function->select.descriptor = &selectDescriptor_read;
+}
+
+static void
+beginUnixOutputFunction (FunctionEntry *function) {
+  function->select.descriptor = &selectDescriptor_write;
+}
+
+static void
+beginUnixAlertFunction (FunctionEntry *function) {
+  function->select.descriptor = &selectDescriptor_exception;
+}
+
+#endif /* Unix I/O monitoring capabilities */
+
+#ifdef ASYNC_CAN_MONITOR_IO
+static void
+setUnixTransferResult (OperationEntry *operation, ssize_t result) {
+  TransferExtension *extension = operation->extension;
+
+  if (result == -1) {
+    operation->error = errno;
+  } else if (result == 0) {
+    extension->direction.input.end = 1;
+  } else {
+    extension->length += result;
+  }
+
+  operation->finished = 1;
+}
+
+static void
+finishUnixRead (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  TransferExtension *extension = operation->extension;
+  ssize_t result = read(function->fileDescriptor,
+                        &extension->buffer[extension->length],
+                        extension->size - extension->length);
+
+  setUnixTransferResult(operation, result);
+}
+
+static void
+finishUnixWrite (OperationEntry *operation) {
+  FunctionEntry *function = operation->function;
+  TransferExtension *extension = operation->extension;
+  ssize_t result = write(function->fileDescriptor,
+                         &extension->buffer[extension->length],
+                         extension->size - extension->length);
+
+  setUnixTransferResult(operation, result);
+}
+#endif /* ASYNC_CAN_MONITOR_IO */
+#endif /* __MINGW32__ */
+
+#ifdef ASYNC_CAN_MONITOR_IO
+static void
+deallocateFunctionEntry (void *item, void *data) {
+  FunctionEntry *function = item;
+
+  if (function->operations) deallocateQueue(function->operations);
+  if (function->methods->endFunction) function->methods->endFunction(function);
+  free(function);
+}
+
+static Queue *
+getFunctionQueue (int create) {
+  AsyncIoData *iod = getIoData();
+  if (!iod) return NULL;
+
+  if (!iod->functionQueue && create) {
+    iod->functionQueue = newQueue(deallocateFunctionEntry, NULL);
+  }
+
+  return iod->functionQueue;
+}
+
+static int
+invokeMonitorCallback (OperationEntry *operation) {
+  MonitorExtension *extension = operation->extension;
+  AsyncMonitorCallback *callback = extension->callback;
+
+  logOperation(operation, callback);
+
+  if (callback) {
+    const AsyncMonitorCallbackParameters parameters = {
+      .error = operation->error,
+      .data = operation->data
+    };
+
+    if (callback(&parameters)) {
+      if (!operation->error) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+invokeInputCallback (OperationEntry *operation) {
+  TransferExtension *extension = operation->extension;
+  AsyncInputCallback *callback = extension->direction.input.callback;
+  size_t count;
+
+  logOperation(operation, callback);
+
+  if (!callback) return 0;
+
+  {
+    const AsyncInputCallbackParameters parameters = {
+      .data = operation->data,
+      .buffer = extension->buffer,
+      .size = extension->size,
+      .length = extension->length,
+      .error = operation->error,
+      .end = extension->direction.input.end
+    };
+
+    count = callback(&parameters);
+  }
+
+  if (operation->error) return 0;
+  if (extension->direction.input.end) return 0;
+
+  operation->finished = 0;
+  if (count) {
+    memmove(extension->buffer, &extension->buffer[count],
+            extension->length -= count);
+    if (extension->length > 0) operation->finished = 1;
+  }
+
+  return 1;
+}
+
+static int
+invokeOutputCallback (OperationEntry *operation) {
+  TransferExtension *extension = operation->extension;
+  AsyncOutputCallback *callback = extension->direction.output.callback;
+
+  logOperation(operation, callback);
+
+  if (!operation->error && (extension->length < extension->size)) {
+    operation->finished = 0;
+    return 1;
+  }
+
+  if (callback) {
+    const AsyncOutputCallbackParameters parameters = {
+      .data = operation->data,
+      .buffer = extension->buffer,
+      .size = extension->size,
+      .error = operation->error
+    };
+
+    callback(&parameters);
+  }
+
+  return 0;
+}
+
+static Element *
+getActiveOperationElement (const FunctionEntry *function) {
+  Queue *queue = function->operations;
+
+  if (function->methods->invokeCallback == invokeMonitorCallback) return getStackHead(queue);
+  return getQueueHead(queue);
+}
+
+static OperationEntry *
+getActiveOperation (const FunctionEntry *function) {
+  Element *element = getActiveOperationElement(function);
+
+  if (element) return getElementItem(element);
+  return NULL;
+}
+
+static void
+startOperation (OperationEntry *operation) {
+  if (operation->function->methods->startOperation) {
+    operation->function->methods->startOperation(operation);
+  }
+}
+
+static void
+finishOperation (OperationEntry *operation) {
+  if (operation->function->methods->finishOperation) {
+    operation->function->methods->finishOperation(operation);
+  }
+}
+
+static int
+addFunctionMonitor (void *item, void *data) {
+  const FunctionEntry *function = item;
+  MonitorGroup *monitors = data;
+  OperationEntry *operation = getActiveOperation(function);
+
+  if (operation) {
+    operation->monitor = NULL;
+
+    if (!operation->active) {
+      if (operation->finished) return 1;
+
+      operation->monitor = &monitors->array[monitors->count++];
+      initializeMonitor(operation->monitor, function, operation);
+    }
+  }
+
+  return 0;
+}
+
+static int
+testFunctionMonitor (void *item, void *data) {
+  FunctionEntry *function = item;
+  OperationEntry *operation = getActiveOperation(function);
+
+  if (operation && operation->monitor) {
+    int *error = &operation->error;
+
+    *error = 0;
+    if (testMonitor(operation->monitor, error)) return 1;
+  }
+
+  return 0;
+}
+
+int
+asyncExecuteIoCallback (AsyncIoData *iod, long int timeout) {
+  if (iod) {
+    Queue *functions = iod->functionQueue;
+    unsigned int functionCount = functions? getQueueSize(functions): 0;
+
+    prepareMonitors();
+
+    if (functionCount) {
+      MonitorEntry monitorArray[functionCount];
+      MonitorGroup monitors = {
+        .array = monitorArray,
+        .count = 0
+      };
+
+      int executed = 0;
+      Element *functionElement = processQueue(functions, addFunctionMonitor, &monitors);
+
+      if (!functionElement) {
+        if (!monitors.count) {
+          approximateDelay(timeout);
+        } else if (awaitMonitors(&monitors, timeout)) {
+          functionElement = processQueue(functions, testFunctionMonitor, NULL);
+        }
+      }
+
+      if (functionElement) {
+        FunctionEntry *function = getElementItem(functionElement);
+        Element *operationElement = getActiveOperationElement(function);
+        OperationEntry *operation = getElementItem(operationElement);
+
+        if (!operation->finished) finishOperation(operation);
+
+        operation->active = 1;
+        if (!function->methods->invokeCallback(operation)) operation->cancel = 1;
+        operation->active = 0;
+        executed = 1;
+
+        if (operation->cancel) {
+          deleteElement(operationElement);
+        } else {
+          operation->error = 0;
+        }
+
+        if ((operationElement = getActiveOperationElement(function))) {
+          operation = getElementItem(operationElement);
+          if (!operation->finished) startOperation(operation);
+          requeueElement(functionElement);
+        } else {
+          deleteElement(functionElement);
+        }
+      }
+
+      return executed;
+    }
+  }
+
+  approximateDelay(timeout);
+  return 0;
+}
+
+static void
+deallocateOperationEntry (void *item, void *data) {
+  OperationEntry *operation = item;
+  if (operation->extension) free(operation->extension);
+  free(operation);
+}
+
+static void
+cancelOperation (Element *operationElement) {
+  OperationEntry *operation = getElementItem(operationElement);
+
+  if (operation->active) {
+    operation->cancel = 1;
+  } else {
+    FunctionEntry *function = operation->function;
+    int isFirstOperation = operationElement == getActiveOperationElement(function);
+
+    if (isFirstOperation) {
+      if (!operation->finished) {
+        if (operation->function->methods->cancelOperation) {
+          operation->function->methods->cancelOperation(operation);
+        }
+      }
+    }
+
+    if (getQueueSize(function->operations) == 1) {
+      deleteElement(findElementWithItem(getFunctionQueue(0), function));
+    } else {
+      deleteElement(operationElement);
+
+      if (isFirstOperation) {
+        operationElement = getActiveOperationElement(function);
+        operation = getElementItem(operationElement);
+
+        if (!operation->finished) startOperation(operation);
+      }
+    }
+  }
+}
+
+static int
+testFunctionEntry (const void *item, void *data) {
+  const FunctionEntry *function = item;
+  const FunctionKey *key = data;
+  return (function->fileDescriptor == key->fileDescriptor) &&
+         (function->methods == key->methods);
+}
+
+static Element *
+getFunctionElement (FileDescriptor fileDescriptor, const FunctionMethods *methods, int create) {
+  Queue *functions = getFunctionQueue(create);
+
+  if (functions) {
+    {
+      FunctionKey key = {
+        .fileDescriptor = fileDescriptor,
+        .methods = methods
+      };
+
+      {
+        Element *element = findElement(functions, testFunctionEntry, &key);
+        if (element) return element;
+      }
+    }
+
+    if (create) {
+      FunctionEntry *function;
+
+      if ((function = malloc(sizeof(*function)))) {
+        function->fileDescriptor = fileDescriptor;
+        function->methods = methods;
+
+        if ((function->operations = newQueue(deallocateOperationEntry, NULL))) {
+          {
+            static AsyncQueueMethods methods = {
+              .cancelRequest = cancelOperation
+            };
+
+            setQueueData(function->operations, &methods);
+          }
+
+          if (methods->beginFunction) methods->beginFunction(function);
+
+          {
+            Element *element = enqueueItem(functions, function);
+            if (element) return element;
+          }
+
+          deallocateQueue(function->operations);
+        }
+
+        free(function);
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static Element *
+newOperation (
+  FileDescriptor fileDescriptor,
+  const FunctionMethods *methods,
+  void *extension,
+  void *data
+) {
+  OperationEntry *operation;
+
+  if ((operation = malloc(sizeof(*operation)))) {
+    Element *functionElement;
+
+    if ((functionElement = getFunctionElement(fileDescriptor, methods, 1))) {
+      FunctionEntry *function = getElementItem(functionElement);
+      int isFirstOperation = !getQueueSize(function->operations);
+      Element *operationElement = enqueueItem(function->operations, operation);
+
+      if (operationElement) {
+        operation->function = function;
+        operation->extension = extension;
+        operation->data = data;
+
+        operation->monitor = NULL;
+        operation->error = 0;
+
+        operation->active = 0;
+        operation->cancel = 0;
+        operation->finished = 0;
+
+        if (isFirstOperation) startOperation(operation);
+        return operationElement;
+      }
+
+      if (isFirstOperation) deleteElement(functionElement);
+    }
+
+    free(operation);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+typedef struct {
+  FileDescriptor fileDescriptor;
+  const FunctionMethods *methods;
+  AsyncMonitorCallback *callback;
+  void *data;
+} MonitorFileOperationParameters;
+
+static Element *
+newFileMonitorOperation (const void *parameters) {
+  const MonitorFileOperationParameters *mop = parameters;
+  MonitorExtension *extension;
+
+  if ((extension = malloc(sizeof(*extension)))) {
+    extension->callback = mop->callback;
+
+    {
+      Element *element = newOperation(mop->fileDescriptor, mop->methods, extension, mop->data);
+
+      if (element) return element;
+    }
+
+    free(extension);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static Element *
+newTransferOperation (
+  FileDescriptor fileDescriptor,
+  const FunctionMethods *methods,
+  const TransferDirectionUnion *direction,
+  size_t size, const void *buffer,
+  void *data
+) {
+  TransferExtension *extension;
+
+  if ((extension = malloc(sizeof(*extension) + size))) {
+    extension->direction = *direction;
+    extension->size = size;
+    extension->length = 0;
+    if (buffer) memcpy(extension->buffer, buffer, size);
+
+    {
+      Element *element = newOperation(fileDescriptor, methods, extension, data);
+
+      if (element) return element;
+    }
+
+    free(extension);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+typedef struct {
+  FileDescriptor fileDescriptor;
+  size_t size;
+  AsyncInputCallback *callback;
+  void *data;
+} InputOperationParameters;
+
+static Element *
+newInputOperation (const void *parameters) {
+  const InputOperationParameters *iop = parameters;
+
+  TransferDirectionUnion direction = {
+    .input = {
+      .callback = iop->callback,
+      .end = 0
+    }
+  };
+
+  static const FunctionMethods methods = {
+    .functionName = "input transferred",
+
+#ifdef __MINGW32__
+    .beginFunction = beginWindowsFunction,
+    .endFunction = endWindowsFunction,
+    .startOperation = startWindowsRead,
+    .finishOperation = finishWindowsTransferOperation,
+    .cancelOperation = cancelWindowsTransferOperation,
+#else /* __MINGW32__ */
+    .beginFunction = beginUnixInputFunction,
+    .finishOperation = finishUnixRead,
+#endif /* __MINGW32__ */
+
+    .invokeCallback = invokeInputCallback
+  };
+
+  return newTransferOperation(iop->fileDescriptor, &methods, &direction, iop->size, NULL, iop->data);
+}
+
+typedef struct {
+  FileDescriptor fileDescriptor;
+  size_t size;
+  const void *buffer;
+  AsyncOutputCallback *callback;
+  void *data;
+} OutputOperationParameters;
+
+static Element *
+newOutputOperation (const void *parameters) {
+  const OutputOperationParameters *oop = parameters;
+
+  TransferDirectionUnion direction = {
+    .output = {
+      .callback = oop->callback
+    }
+  };
+
+  static const FunctionMethods methods = {
+    .functionName = "output transferred",
+
+#ifdef __MINGW32__
+    .beginFunction = beginWindowsFunction,
+    .endFunction = endWindowsFunction,
+    .startOperation = startWindowsWrite,
+    .finishOperation = finishWindowsTransferOperation,
+    .cancelOperation = cancelWindowsTransferOperation,
+#else /* __MINGW32__ */
+    .beginFunction = beginUnixOutputFunction,
+    .finishOperation = finishUnixWrite,
+#endif /* __MINGW32__ */
+
+    .invokeCallback = invokeOutputCallback
+  };
+
+  return newTransferOperation(oop->fileDescriptor, &methods, &direction, oop->size, oop->buffer, oop->data);
+}
+
+#else /* ASYNC_CAN_MONITOR_IO */
+int
+asyncHandleOperation (AsyncThreadSpecificData *tsd, long int timeout) {
+  approximateDelay(timeout);
+  return 0;
+}
+#endif /* ASYNC_CAN_MONITOR_IO */
+
+int
+asyncMonitorFileInput (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+#ifdef ASYNC_CAN_MONITOR_IO
+  static const FunctionMethods methods = {
+    .functionName = "file input monitor",
+
+#ifdef __MINGW32__
+    .beginFunction = beginWindowsFunction,
+    .endFunction = endWindowsFunction,
+#else /* __MINGW32__ */
+    .beginFunction = beginUnixInputFunction,
+#endif /* __MINGW32__ */
+
+    .invokeCallback = invokeMonitorCallback
+  };
+
+  const MonitorFileOperationParameters mop = {
+    .fileDescriptor = fileDescriptor,
+    .methods = &methods,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newFileMonitorOperation, &mop);
+#else /* ASYNC_CAN_MONITOR_IO */
+  logUnsupportedFunction();
+  return 0;
+#endif /* ASYNC_CAN_MONITOR_IO */
+}
+
+int
+asyncMonitorFileOutput (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+#ifdef ASYNC_CAN_MONITOR_IO
+  static const FunctionMethods methods = {
+    .functionName = "file output monitor",
+
+#ifdef __MINGW32__
+    .beginFunction = beginWindowsFunction,
+    .endFunction = endWindowsFunction,
+#else /* __MINGW32__ */
+    .beginFunction = beginUnixOutputFunction,
+#endif /* __MINGW32__ */
+
+    .invokeCallback = invokeMonitorCallback
+  };
+
+  const MonitorFileOperationParameters mop = {
+    .fileDescriptor = fileDescriptor,
+    .methods = &methods,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newFileMonitorOperation, &mop);
+#else /* ASYNC_CAN_MONITOR_IO */
+  logUnsupportedFunction();
+  return 0;
+#endif /* ASYNC_CAN_MONITOR_IO */
+}
+
+int
+asyncMonitorFileAlert (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+#ifdef ASYNC_CAN_MONITOR_IO
+  static const FunctionMethods methods = {
+    .functionName = "file alert monitor",
+
+#ifdef __MINGW32__
+    .beginFunction = beginWindowsFunction,
+    .endFunction = endWindowsFunction,
+#else /* __MINGW32__ */
+    .beginFunction = beginUnixAlertFunction,
+#endif /* __MINGW32__ */
+
+    .invokeCallback = invokeMonitorCallback
+  };
+
+  const MonitorFileOperationParameters mop = {
+    .fileDescriptor = fileDescriptor,
+    .methods = &methods,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newFileMonitorOperation, &mop);
+#else /* ASYNC_CAN_MONITOR_IO */
+  logUnsupportedFunction();
+  return 0;
+#endif /* ASYNC_CAN_MONITOR_IO */
+}
+
+int
+asyncReadFile (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  size_t size,
+  AsyncInputCallback *callback, void *data
+) {
+#ifdef ASYNC_CAN_MONITOR_IO
+  const InputOperationParameters iop = {
+    .fileDescriptor = fileDescriptor,
+    .size = size,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newInputOperation, &iop);
+#else /* ASYNC_CAN_MONITOR_IO */
+  logUnsupportedFunction();
+  return 0;
+#endif /* ASYNC_CAN_MONITOR_IO */
+}
+
+int
+asyncWriteFile (
+  AsyncHandle *handle,
+  FileDescriptor fileDescriptor,
+  const void *buffer, size_t size,
+  AsyncOutputCallback *callback, void *data
+) {
+#ifdef ASYNC_CAN_MONITOR_IO
+  const OutputOperationParameters oop = {
+    .fileDescriptor = fileDescriptor,
+    .size = size,
+    .buffer = buffer,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newOutputOperation, &oop);
+#else /* ASYNC_CAN_MONITOR_IO */
+  logUnsupportedFunction();
+  return 0;
+#endif /* ASYNC_CAN_MONITOR_IO */
+}
+
+#ifdef __MINGW32__
+int
+asyncMonitorSocketInput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+asyncMonitorSocketOutput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+asyncMonitorSocketAlert (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+asyncReadSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  size_t size,
+  AsyncInputCallback *callback, void *data
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+asyncWriteSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  const void *buffer, size_t size,
+  AsyncOutputCallback *callback, void *data
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+#else /* __MINGW32__ */
+int
+asyncMonitorSocketInput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return asyncMonitorFileInput(handle, socketDescriptor, callback, data);
+}
+
+int
+asyncMonitorSocketOutput (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return asyncMonitorFileOutput(handle, socketDescriptor, callback, data);
+}
+
+int
+asyncMonitorSocketAlert (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return asyncMonitorFileAlert(handle, socketDescriptor, callback, data);
+}
+
+int
+asyncReadSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  size_t size,
+  AsyncInputCallback *callback, void *data
+) {
+  return asyncReadFile(handle, socketDescriptor, size, callback, data);
+}
+
+int
+asyncWriteSocket (
+  AsyncHandle *handle,
+  SocketDescriptor socketDescriptor,
+  const void *buffer, size_t size,
+  AsyncOutputCallback *callback, void *data
+) {
+  return asyncWriteFile(handle, socketDescriptor, buffer, size, callback, data);
+}
+#endif /* __MINGW32__ */
diff --git a/Programs/async_signal.c b/Programs/async_signal.c
new file mode 100644
index 0000000..a11f4ef
--- /dev/null
+++ b/Programs/async_signal.c
@@ -0,0 +1,834 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_event.h"
+#include "async_signal.h"
+#include "async_internal.h"
+#include "get_thread.h"
+
+#ifdef ASYNC_CAN_HANDLE_SIGNALS
+#if defined(HAVE_SYS_SIGNALFD_H)
+#include <sys/signalfd.h>
+#include "async_io.h"
+
+#else /* paradigm-specific signal monitoring definitions */
+#endif /* paradigm-specific signal monitoring definitions */
+
+struct AsyncSignalDataStruct {
+#ifdef ASYNC_CAN_MONITOR_SIGNALS
+  Queue *signalQueue;
+#endif /* ASYNC_CAN_MONITOR_SIGNALS */
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+  sigset_t obtainableSignals;
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+#ifdef ASYNC_CAN_OBTAIN_SIGNALS
+  sigset_t claimedSignals;
+  sigset_t obtainedSignals;
+
+  int firstObtainableSignal;
+  int lastObtainableSignal;
+#endif /* ASYNC_CAN_OBTAIN_SIGNALS */
+};
+
+void
+asyncDeallocateSignalData (AsyncSignalData *sd) {
+  if (sd) {
+#ifdef ASYNC_CAN_MONITOR_SIGNALS
+    if (sd->signalQueue) deallocateQueue(sd->signalQueue);
+#endif /* ASYNC_CAN_MONITOR_SIGNALS */
+
+    free(sd);
+  }
+}
+
+#if defined(ASYNC_CAN_BLOCK_SIGNALS) || defined(ASYNC_CAN_MONITOR_SIGNALS) || defined(ASYNC_CAN_OBTAIN_SIGNALS)
+static AsyncSignalData *
+getSignalData (void) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+  if (!tsd) return NULL;
+
+  if (!tsd->signalData) {
+    AsyncSignalData *sd;
+
+    if (!(sd = malloc(sizeof(*sd)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(sd, 0, sizeof(*sd));
+
+#ifdef ASYNC_CAN_MONITOR_SIGNALS
+    sd->signalQueue = NULL;
+#endif /* ASYNC_CAN_MONITOR_SIGNALS */
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+    sigemptyset(&sd->obtainableSignals);
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+#ifdef ASYNC_CAN_OBTAIN_SIGNALS
+    sigemptyset(&sd->claimedSignals);
+    sigemptyset(&sd->obtainedSignals);
+
+    sd->firstObtainableSignal = SIGRTMIN;
+    sd->lastObtainableSignal = SIGRTMAX;
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+    {
+      int signalNumber;
+
+      for (signalNumber=sd->firstObtainableSignal; signalNumber<=sd->lastObtainableSignal; signalNumber+=1) {
+        sigaddset(&sd->obtainableSignals, signalNumber);
+      }
+    }
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+#endif /* ASYNC_CAN_OBTAIN_SIGNALS */
+
+    tsd->signalData = sd;
+  }
+
+  return tsd->signalData;
+}
+#endif /* need signal data */
+
+int
+asyncHandleSignal (int signalNumber, AsyncSignalHandler *newHandler, AsyncSignalHandler **oldHandler) {
+#if defined(HAVE_SIGACTION)
+  struct sigaction newAction;
+  struct sigaction oldAction;
+
+  memset(&newAction, 0, sizeof(newAction));
+  sigemptyset(&newAction.sa_mask);
+  newAction.sa_handler = newHandler;
+
+  if (sigaction(signalNumber, &newAction, &oldAction) != -1) {
+    if (oldHandler) *oldHandler = oldAction.sa_handler;
+    return 1;
+  }
+
+  logSystemError("sigaction");
+#else /* set signal handler */
+  AsyncSignalHandler *result = signal(signalNumber, newHandler);
+
+  if (result != SIG_ERR) {
+    if (oldHandler) *oldHandler = result;
+    return 1;
+  }
+
+  logSystemError("signal");
+#endif /* set signal handler */
+
+  return 0;
+}
+
+int
+asyncIgnoreSignal (int signalNumber, AsyncSignalHandler **oldHandler) {
+  return asyncHandleSignal(signalNumber, SIG_IGN, oldHandler);
+}
+
+int
+asyncRevertSignal (int signalNumber, AsyncSignalHandler **oldHandler) {
+  return asyncHandleSignal(signalNumber, SIG_DFL, oldHandler);
+}
+
+ASYNC_SIGNAL_HANDLER(asyncEmptySignalHandler) {
+}
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+static int
+setSignalMask (int how, const sigset_t *newMask, sigset_t *oldMask) {
+#ifdef GOT_PTHREADS
+  int error = pthread_sigmask(how, newMask, oldMask);
+
+  if (!error) return 1;
+  logActionError(error, "pthread_setmask");
+#else /* GOT_PTHREADS */
+  if (sigprocmask(how, newMask, oldMask) != -1) return 1;
+  logSystemError("sigprocmask");
+#endif /* GOT_PTHREADS */
+
+  return 0;
+}
+
+static int
+makeSignalMask (sigset_t *signalMask, int signalNumber) {
+  if (sigemptyset(signalMask) != -1) {
+    if (sigaddset(signalMask, signalNumber) != -1) {
+      return 1;
+    } else {
+      logSystemError("sigaddset");
+    }
+  } else {
+    logSystemError("sigemptyset");
+  }
+
+  return 0;
+}
+
+int
+asyncSetSignalBlocked (int signalNumber, int state) {
+  sigset_t mask;
+
+  if (makeSignalMask(&mask, signalNumber)) {
+    if (setSignalMask((state? SIG_BLOCK: SIG_UNBLOCK), &mask, NULL)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+getSignalMask (sigset_t *mask) {
+  return setSignalMask(SIG_SETMASK, NULL, mask);
+}
+
+int
+asyncIsSignalBlocked (int signalNumber) {
+  sigset_t signalMask;
+
+  if (getSignalMask(&signalMask)) {
+    int result = sigismember(&signalMask, signalNumber);
+
+    if (result != -1) return result;
+    logSystemError("sigismember");
+  }
+
+  return 0;
+}
+
+int
+asyncWithSignalsBlocked (
+  const sigset_t *mask,
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+) {
+  sigset_t oldMask;
+
+  if (setSignalMask(SIG_BLOCK, mask, &oldMask)) {
+    function(data);
+    setSignalMask(SIG_SETMASK, &oldMask, NULL);
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+asyncWithSignalBlocked (
+  int number,
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+) {
+  sigset_t mask;
+
+  if (makeSignalMask(&mask, number)) {
+    if (asyncWithSignalsBlocked(&mask, function, data)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+asyncWithAllSignalsBlocked (
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+) {
+  sigset_t mask;
+
+  if (sigfillset(&mask) != -1) {
+    if (asyncWithSignalsBlocked(&mask, function, data)) {
+      return 1;
+    }
+  } else {
+    logSystemError("sigfillset");
+  }
+
+  return 0;
+}
+
+int
+asyncWithObtainableSignalsBlocked (
+  AsyncWithSignalsBlockedFunction *function,
+  void *data
+) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    if (asyncWithSignalsBlocked(&sd->obtainableSignals, function, data)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+asyncBlockObtainableSignals (void) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    if (setSignalMask(SIG_BLOCK, &sd->obtainableSignals, NULL)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+#ifdef ASYNC_CAN_MONITOR_SIGNALS
+typedef struct {
+  int number;
+  Queue *monitors;
+  unsigned wasBlocked:1;
+
+#if defined(HAVE_SYS_SIGNALFD_H)
+  struct {
+    int fileDescriptor;
+    AsyncHandle asyncMonitor;
+  } signalfd;
+
+#else /* paradigm-specific signal monitoring fields */
+  struct {
+    AsyncEvent *event;
+    AsyncSignalHandler *old;
+  } handler;
+#endif /* paradigm-specific signal monitoring fields */
+} SignalEntry;
+
+typedef struct {
+  SignalEntry *signal;
+  AsyncSignalCallback *callback;
+  void *data;
+  unsigned active:1;
+  unsigned delete:1;
+} MonitorEntry;
+
+#if defined(HAVE_SYS_SIGNALFD_H)
+static void
+logSignalfdAction (const SignalEntry *sig, const char *action) {
+  logMessage(LOG_CATEGORY(ASYNC_EVENTS), "%s signalfd monitor: sig=%d fd=%d",
+             action, sig->number, sig->signalfd.fileDescriptor);
+}
+
+static void
+initializeSignalfdFileDescriptor (SignalEntry *sig) {
+  sig->signalfd.fileDescriptor = -1;
+}
+
+static void
+initializeSignalfdAsyncMonitor (SignalEntry *sig) {
+  sig->signalfd.asyncMonitor = NULL;
+}
+
+static void
+initializeSignalMonitoring (SignalEntry *sig) {
+  initializeSignalfdFileDescriptor(sig);
+  initializeSignalfdAsyncMonitor(sig);
+}
+
+static void
+closeSignalfdFileDescriptor (SignalEntry *sig) {
+  struct signalfd_siginfo buffer;
+
+  while (read(sig->signalfd.fileDescriptor, &buffer, sizeof(buffer)) != -1);
+  close(sig->signalfd.fileDescriptor);
+  initializeSignalfdFileDescriptor(sig);
+}
+
+static void
+cancelSignalfdAsyncMonitor (SignalEntry *sig) {
+  asyncCancelRequest(sig->signalfd.asyncMonitor);
+  initializeSignalfdAsyncMonitor(sig);
+}
+
+static void
+deactivateSignalMonitoring (SignalEntry *sig) {
+  logSignalfdAction(sig, "destroying");
+  cancelSignalfdAsyncMonitor(sig);
+  closeSignalfdFileDescriptor(sig);
+}
+
+#else /* paradigm-specific signal monitoring functions */
+static void
+initializeHandlerEvent (SignalEntry *sig) {
+  sig->handler.event = NULL;
+}
+
+static void
+initializeOldHandler (SignalEntry *sig) {
+  sig->handler.old = NULL;
+}
+
+static void
+initializeSignalMonitoring (SignalEntry *sig) {
+  initializeHandlerEvent(sig);
+  initializeOldHandler(sig);
+}
+
+static void
+discardHandlerEvent (SignalEntry *sig) {
+  asyncDiscardEvent(sig->handler.event);
+  initializeHandlerEvent(sig);
+}
+
+static void
+restoreOldHandler (SignalEntry *sig) {
+  asyncHandleSignal(sig->number, sig->handler.old, NULL);
+  initializeOldHandler(sig);
+}
+
+static void
+deactivateSignalMonitoring (SignalEntry *sig) {
+  restoreOldHandler(sig);
+  discardHandlerEvent(sig);
+}
+#endif /* paradigm-specific signal monitoring functions */
+
+static void
+deallocateMonitorEntry (void *item, void *data) {
+  MonitorEntry *mon = item;
+
+  free(mon);
+}
+
+static void
+deallocateSignalEntry (void *item, void *data) {
+  SignalEntry *sig = item;
+
+  deallocateQueue(sig->monitors);
+  free(sig);
+}
+
+static Queue *
+getSignalQueue (int create) {
+  AsyncSignalData *sd = getSignalData();
+  if (!sd) return NULL;
+
+  if (!sd->signalQueue && create) {
+    sd->signalQueue = newQueue(deallocateSignalEntry, NULL);
+  }
+
+  return sd->signalQueue;
+}
+
+typedef struct {
+  SignalEntry *const signalEntry;
+} DeleteSignalEntryParameters;
+
+ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(asyncDeleteSignalEntry) {
+  DeleteSignalEntryParameters *parameters = data;
+  Queue *signals = getSignalQueue(0);
+  Element *signalElement = findElementWithItem(signals, parameters->signalEntry);
+
+  deleteElement(signalElement);
+}
+
+static void
+deleteMonitor (Element *monitorElement) {
+  MonitorEntry *mon = getElementItem(monitorElement);
+  SignalEntry *sig = mon->signal;
+
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS), mon->callback, "signal monitor removed: %d", sig->number);
+  deleteElement(monitorElement);
+
+  if (getQueueSize(sig->monitors) == 0) {
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "deactivating signal monitoring: %d", sig->number);
+    asyncSetSignalBlocked(sig->number, sig->wasBlocked);
+    deactivateSignalMonitoring(sig);
+
+    {
+      DeleteSignalEntryParameters parameters = {
+        .signalEntry = sig
+      };
+
+      asyncWithAllSignalsBlocked(asyncDeleteSignalEntry, &parameters);
+    }
+  }
+}
+
+static void
+cancelMonitor (Element *monitorElement) {
+  MonitorEntry *mon = getElementItem(monitorElement);
+
+  if (mon->active) {
+    mon->delete = 1;
+  } else {
+    deleteMonitor(monitorElement);
+  }
+}
+
+static void
+handlePendingSignal (const SignalEntry *sig) {
+  Element *monitorElement = getStackHead(sig->monitors);
+
+  if (monitorElement) {
+    MonitorEntry *mon = getElementItem(monitorElement);
+    AsyncSignalCallback *callback = mon->callback;
+
+    const AsyncSignalCallbackParameters parameters = {
+      .signal = sig->number,
+      .data = mon->data
+    };
+
+    logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "signal %d starting", sig->number);
+    mon->active = 1;
+    if (!callback(&parameters)) mon->delete = 1;
+    mon->active = 0;
+    logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "signal %d finished", sig->number);
+    if (mon->delete) deleteMonitor(monitorElement);
+  }
+}
+
+typedef struct {
+  Queue *const signalQueue;
+  SignalEntry *const signalEntry;
+
+  Element *signalElement;
+} AddSignalEntryParameters;
+
+ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(asyncAddSignalEntry) {
+  AddSignalEntryParameters *parameters = data;
+
+  parameters->signalElement = enqueueItem(parameters->signalQueue, parameters->signalEntry);
+}
+
+typedef struct {
+  int signalNumber;
+} TestMonitoredSignalKey;
+
+static int
+testMonitoredSignal (const void *item, void *data) {
+  const SignalEntry *sig = item;
+  const TestMonitoredSignalKey *key = data;
+
+  return sig->number == key->signalNumber;
+}
+
+static Element *
+getSignalElement (int signalNumber, int create) {
+  Queue *signals = getSignalQueue(create);
+
+  if (signals) {
+    {
+      TestMonitoredSignalKey key = {
+        .signalNumber = signalNumber
+      };
+
+      {
+        Element *element = findElement(signals, testMonitoredSignal, &key);
+
+        if (element) return element;
+      }
+    }
+
+    if (create) {
+      SignalEntry *sig;
+
+      if ((sig = malloc(sizeof(*sig)))) {
+        memset(sig, 0, sizeof(*sig));
+        sig->number = signalNumber;
+        initializeSignalMonitoring(sig);
+
+        if ((sig->monitors = newQueue(deallocateMonitorEntry, NULL))) {
+          {
+            static AsyncQueueMethods methods = {
+              .cancelRequest = cancelMonitor
+            };
+
+            setQueueData(sig->monitors, &methods);
+          }
+
+          {
+            AddSignalEntryParameters parameters = {
+              .signalQueue = signals,
+              .signalEntry = sig,
+
+              .signalElement = NULL
+            };
+
+            asyncWithAllSignalsBlocked(asyncAddSignalEntry, &parameters);
+            if (parameters.signalElement) return parameters.signalElement;
+          }
+
+          deallocateQueue(sig->monitors);
+        }
+
+        free(sig);
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return NULL;
+}
+
+#if defined(HAVE_SYS_SIGNALFD_H)
+ASYNC_INPUT_CALLBACK(asyncHandleSignalfdInput) {
+  static const char label[] = "signalfd";
+  const SignalEntry *sig = parameters->data;
+
+  if (parameters->error) {
+    logMessage(LOG_WARNING, "%s read error: fd=%d sig=%d: %s",
+               label, sig->signalfd.fileDescriptor, sig->number, strerror(parameters->error));
+  } else if (parameters->end) {
+    logMessage(LOG_WARNING, "%s end-of-file: fd=%d sig=%d",
+               label, sig->signalfd.fileDescriptor, sig->number);
+  } else {
+    const struct signalfd_siginfo *info = parameters->buffer;
+
+    handlePendingSignal(sig);
+    return sizeof(*info);
+  }
+
+  return 0;
+}
+
+static int
+activateSignalMonitoring (SignalEntry *sig) {
+  sigset_t mask;
+
+  if (makeSignalMask(&mask, sig->number)) {
+    int flags = 0;
+
+#ifdef SFD_NONBLOCK
+    flags |= SFD_NONBLOCK;
+#endif /* SFD_NONBLOCK */
+
+#ifdef SFD_CLOEXEC
+    flags |= SFD_CLOEXEC;
+#endif /* SFD_CLOEXEC */
+
+    if ((sig->signalfd.fileDescriptor = signalfd(-1, &mask, flags)) != -1) {
+      if (asyncReadFile(&sig->signalfd.asyncMonitor, sig->signalfd.fileDescriptor,
+                        sizeof(struct signalfd_siginfo),
+                        asyncHandleSignalfdInput, sig)) {
+        if (sig->wasBlocked || asyncSetSignalBlocked(sig->number, 1)) {
+          logSignalfdAction(sig, "created");
+          return 1;
+        }
+
+        cancelSignalfdAsyncMonitor(sig);
+      }
+
+      closeSignalfdFileDescriptor(sig);
+    } else {
+      logSystemError("signalfd");
+    }
+  }
+
+  return 0;
+}
+
+#else /* paradigm-specific signal monitoring handlers */
+ASYNC_EVENT_CALLBACK(asyncHandlePendingSignal) {
+  SignalEntry *sig = parameters->eventData;
+
+  handlePendingSignal(sig);
+}
+
+ASYNC_SIGNAL_HANDLER(asyncHandleMonitoredSignal) {
+  Element *signalElement = getSignalElement(signalNumber, 0);
+
+  if (signalElement) {
+    SignalEntry *sig = getElementItem(signalElement);
+
+    asyncSignalEvent(sig->handler.event, NULL);
+  }
+}
+
+static int
+activateSignalMonitoring (SignalEntry *sig) {
+  if ((sig->handler.event = asyncNewEvent(asyncHandlePendingSignal, sig))) {
+    if (asyncHandleSignal(sig->number, asyncHandleMonitoredSignal, &sig->handler.old)) {
+      if (!sig->wasBlocked || asyncSetSignalBlocked(sig->number, 0)) {
+        return 1;
+      }
+
+      restoreOldHandler(sig);
+    }
+
+    discardHandlerEvent(sig);
+  }
+
+  return 0;
+}
+#endif /* paradigm-specific signal monitoring handlers */
+
+typedef struct {
+  int signal;
+  AsyncSignalCallback *callback;
+  void *data;
+} MonitorElementParameters;
+
+static Element *
+newMonitorElement (const void *parameters) {
+  const MonitorElementParameters *mep = parameters;
+  Element *signalElement = getSignalElement(mep->signal, 1);
+
+  if (signalElement) {
+    SignalEntry *sig = getElementItem(signalElement);
+    int newSignal = getQueueSize(sig->monitors) == 0;
+    MonitorEntry *mon;
+
+    if ((mon = malloc(sizeof(*mon)))) {
+      memset(mon, 0, sizeof(*mon));
+
+      mon->signal = sig;
+      mon->callback = mep->callback;
+      mon->data = mep->data;
+
+      mon->active = 0;
+      mon->delete = 0;
+
+      {
+        Element *monitorElement = enqueueItem(sig->monitors, mon);
+
+        if (monitorElement) {
+          int added = !newSignal;
+
+          if (!added) {
+            logMessage(LOG_CATEGORY(ASYNC_EVENTS), "activating signal monitoring: %d", sig->number);
+            sig->wasBlocked = asyncIsSignalBlocked(sig->number);
+            if (activateSignalMonitoring(sig)) added = 1;
+          }
+
+          if (added) {
+            logSymbol(LOG_CATEGORY(ASYNC_EVENTS), mon->callback, "signal monitor added: %d", sig->number);
+            return monitorElement;
+          }
+
+          deleteElement(monitorElement);
+        }
+      }
+
+      free(mon);
+    } else {
+      logMallocError();
+    }
+
+    if (newSignal) deleteElement(signalElement);
+  }
+
+  return NULL;
+}
+
+int
+asyncMonitorSignal (
+  AsyncHandle *handle, int signal,
+  AsyncSignalCallback *callback, void *data
+) {
+  const MonitorElementParameters mep = {
+    .signal = signal,
+    .callback = callback,
+    .data = data
+  };
+
+  return asyncMakeHandle(handle, newMonitorElement, &mep);
+}
+#endif /* ASYNC_CAN_MONITOR_SIGNALS */
+
+#ifdef ASYNC_CAN_OBTAIN_SIGNALS
+int
+asyncClaimSignalNumber (int signal) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    const char *reason = "signal number not claimable";
+
+    if (sigismember(&sd->obtainableSignals, signal)) {
+      if (sigismember(&sd->claimedSignals, signal)) {
+        reason = "signal number already claimed";
+      } else if (sigismember(&sd->obtainedSignals, signal)) {
+        reason = "signal number in use";
+      } else {
+        sigaddset(&sd->claimedSignals, signal);
+        return 1;
+      }
+    }
+
+    logMessage(LOG_ERR, "%s: %d", reason, signal);
+  }
+
+  return 0;
+}
+
+int
+asyncReleaseSignalNumber (int signal) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    if (sigismember(&sd->claimedSignals, signal)) {
+      sigdelset(&sd->claimedSignals, signal);
+      return 1;
+    }
+  }
+
+  logMessage(LOG_ERR, "signal number not claimed: %d", signal);
+  return 0;
+}
+
+int
+asyncObtainSignalNumber (void) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    int signal;
+
+    for (signal=sd->firstObtainableSignal; signal<=sd->lastObtainableSignal; signal+=1) {
+      if (sigismember(&sd->obtainableSignals, signal)) {
+        if (!sigismember(&sd->claimedSignals, signal)) {
+          if (!sigismember(&sd->obtainedSignals, signal)) {
+            sigaddset(&sd->obtainedSignals, signal);
+            return signal;
+          }
+        }
+      }
+    }
+  }
+
+  logMessage(LOG_ERR, "no obtainable signal number");
+  return 0;
+}
+
+int
+asyncRelinquishSignalNumber (int signal) {
+  AsyncSignalData *sd = getSignalData();
+
+  if (sd) {
+    if (sigismember(&sd->obtainedSignals, signal)) {
+      sigdelset(&sd->obtainedSignals, signal);
+      return 1;
+    }
+  }
+
+  logMessage(LOG_ERR, "signal number not obtained: %d", signal);
+  return 0;
+}
+#endif /* ASYNC_CAN_OBTAIN_SIGNALS */
+#endif /* ASYNC_CAN_HANDLE_SIGNALS */
diff --git a/Programs/async_task.c b/Programs/async_task.c
new file mode 100644
index 0000000..8108f84
--- /dev/null
+++ b/Programs/async_task.c
@@ -0,0 +1,151 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_task.h"
+#include "async_internal.h"
+#include "async_event.h"
+
+typedef struct {
+  AsyncTaskCallback *callback;
+  void *data;
+} TaskDefinition;
+
+struct AsyncTaskDataStruct {
+  Queue *taskQueue;
+};
+
+void
+asyncDeallocateTaskData (AsyncTaskData *td) {
+  if (td) {
+    if (td->taskQueue) deallocateQueue(td->taskQueue);
+    free(td);
+  }
+}
+
+static AsyncTaskData *
+getTaskData (void) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+  if (!tsd) return NULL;
+
+  if (!tsd->taskData) {
+    AsyncTaskData *td;
+
+    if (!(td = malloc(sizeof(*td)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(td, 0, sizeof(*td));
+    td->taskQueue = NULL;
+    tsd->taskData = td;
+  }
+
+  return tsd->taskData;
+}
+
+static void
+deallocateTaskDefinition (void *item, void *data) {
+  TaskDefinition *task = item;
+
+  free(task);
+}
+
+static Queue *
+getTaskQueue (int create) {
+  AsyncTaskData *td = getTaskData();
+  if (!td) return NULL;
+
+  if (!td->taskQueue && create) {
+    td->taskQueue = newQueue(deallocateTaskDefinition, NULL);
+  }
+
+  return td->taskQueue;
+}
+
+static int
+addTask (TaskDefinition *task) {
+  Queue *queue = getTaskQueue(1);
+
+  if (queue) {
+    if (enqueueItem(queue, task)) {
+      logSymbol(LOG_CATEGORY(ASYNC_EVENTS), task->callback, "task added");
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+asyncAddTask (AsyncEvent *event, AsyncTaskCallback *callback, void *data) {
+  TaskDefinition *task;
+
+  if ((task = malloc(sizeof(*task)))) {
+    memset(task, 0, sizeof(*task));
+    task->callback = callback;
+    task->data = data;
+
+    if (event) {
+      if (asyncSignalEvent(event, task)) return 1;
+    } else if (addTask(task)) {
+      return 1;
+    }
+
+    free(task);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+ASYNC_EVENT_CALLBACK(asyncHandleAddTaskEvent) {
+  addTask(parameters->signalData);
+}
+
+AsyncEvent *
+asyncNewAddTaskEvent (void) {
+  return asyncNewEvent(asyncHandleAddTaskEvent, NULL);
+}
+
+int
+asyncExecuteTaskCallback (AsyncTaskData *td) {
+  if (td) {
+    Queue *queue = td->taskQueue;
+
+    if (queue) {
+      TaskDefinition *task = dequeueItem(queue);
+
+      if (task) {
+        AsyncTaskCallback *callback = task->callback;
+
+        logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "task starting");
+        if (callback) callback(task->data);
+        free(task);
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/Programs/async_wait.c b/Programs/async_wait.c
new file mode 100644
index 0000000..4fa2cd8
--- /dev/null
+++ b/Programs/async_wait.c
@@ -0,0 +1,174 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "async_wait.h"
+#include "async_internal.h"
+#include "timing.h"
+
+typedef struct {
+  AsyncThreadSpecificData *tsd;
+  long int timeout;
+} CallbackExecuterParameters;
+
+typedef int CallbackExecuter (CallbackExecuterParameters *parameters);
+
+typedef struct {
+  CallbackExecuter *execute;
+  const char *action;
+} CallbackExecuterEntry;
+
+struct AsyncWaitDataStruct {
+  AsyncThreadSpecificData *tsd;
+  unsigned int waitDepth;
+};
+
+static int
+alarmCallbackExecuter (CallbackExecuterParameters *parameters) {
+  return asyncExecuteAlarmCallback(parameters->tsd->alarmData, &parameters->timeout);
+}
+
+static int
+taskCallbackExecuter (CallbackExecuterParameters *parameters) {
+  AsyncThreadSpecificData *tsd = parameters->tsd;
+
+  if (tsd->waitData->waitDepth != 1) return 0;
+  return asyncExecuteTaskCallback(tsd->taskData);
+}
+
+static int
+ioCallbackExecuter (CallbackExecuterParameters *parameters) {
+  return asyncExecuteIoCallback(parameters->tsd->ioData, parameters->timeout);
+}
+
+static const CallbackExecuterEntry callbackExecuterTable[] = {
+  { .execute = alarmCallbackExecuter,
+    .action = "alarm handled"
+  },
+
+  { .execute = taskCallbackExecuter,
+    .action = "task performed"
+  },
+
+  { .execute = ioCallbackExecuter,
+    .action = "I/O operation handled"
+  },
+
+  { .execute = NULL,
+    .action = "wait timed out"
+  }
+};
+
+void
+asyncDeallocateWaitData (AsyncWaitData *wd) {
+  if (wd) {
+    free(wd);
+  }
+}
+
+static AsyncWaitData *
+getWaitData (void) {
+  AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
+  if (!tsd) return NULL;
+
+  if (!tsd->waitData) {
+    AsyncWaitData *wd;
+
+    if (!(wd = malloc(sizeof(*wd)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(wd, 0, sizeof(*wd));
+    wd->tsd = tsd;
+    wd->waitDepth = 0;
+    tsd->waitData = wd;
+  }
+
+  return tsd->waitData;
+}
+
+static void
+awaitAction (long int timeout) {
+  AsyncWaitData *wd = getWaitData();
+
+  if (wd) {
+    const CallbackExecuterEntry *cbx = callbackExecuterTable;
+
+    CallbackExecuterParameters parameters = {
+      .tsd = wd->tsd,
+      .timeout = timeout
+    };
+
+    wd->waitDepth += 1;
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS),
+               "begin: level %u: timeout %ld",
+               wd->waitDepth, timeout);
+
+    while (cbx->execute) {
+      if (cbx->execute(&parameters)) break;
+      cbx += 1;
+    }
+
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS),
+               "end: level %u: %s",
+               wd->waitDepth, cbx->action);
+
+    wd->waitDepth -= 1;
+  } else {
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "waiting: %ld", timeout);
+    approximateDelay(timeout);
+  }
+}
+
+int
+asyncAwaitCondition (int timeout, AsyncConditionTester *testCondition, void *data) {
+  int first = 1;
+  TimePeriod period;
+  startTimePeriod(&period, timeout);
+
+  while (!(testCondition && testCondition(data))) {
+    long int elapsed;
+
+    if (first) {
+      first = 0;
+      elapsed = 0;
+    } else if (afterTimePeriod(&period, &elapsed)) {
+      return 0;
+    }
+
+    awaitAction(timeout - elapsed);
+  }
+
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS), testCondition, "condition satisfied");
+  return 1;
+}
+
+void
+asyncWait (int duration) {
+  asyncAwaitCondition(duration, NULL, NULL);
+}
+
+void
+asyncWaitFor (AsyncConditionTester *testCondition, void *data) {
+  while (!asyncAwaitCondition(1000000, testCondition, data));
+}
diff --git a/Programs/atb.auto.h b/Programs/atb.auto.h
new file mode 100644
index 0000000..7a4e801
--- /dev/null
+++ b/Programs/atb.auto.h
@@ -0,0 +1,32 @@
+[0X01] = 0X01, 0X02, 0X03, 0X04, 0X05, 0X06, 0X07, 0X40,
+[0X09] = 0X41, 0X42, 0X43, 0X44, 0X45, 0X46, 0X47, 0X08,
+[0X11] = 0X09, 0X0A, 0X0B, 0X0C, 0X0D, 0X0E, 0X0F, 0X48,
+[0X19] = 0X49, 0X4A, 0X4B, 0X4C, 0X4D, 0X4E, 0X4F, 0X10,
+[0X21] = 0X11, 0X12, 0X13, 0X14, 0X15, 0X16, 0X17, 0X50,
+[0X29] = 0X51, 0X52, 0X53, 0X54, 0X55, 0X56, 0X57, 0X18,
+[0X31] = 0X19, 0X1A, 0X1B, 0X1C, 0X1D, 0X1E, 0X1F, 0X58,
+[0X39] = 0X59, 0X5A, 0X5B, 0X5C, 0X5D, 0X5E, 0X5F, 0X20,
+[0X41] = 0X21, 0X22, 0X23, 0X24, 0X25, 0X26, 0X27, 0X60,
+[0X49] = 0X61, 0X62, 0X63, 0X64, 0X65, 0X66, 0X67, 0X28,
+[0X51] = 0X29, 0X2A, 0X2B, 0X2C, 0X2D, 0X2E, 0X2F, 0X68,
+[0X59] = 0X69, 0X6A, 0X6B, 0X6C, 0X6D, 0X6E, 0X6F, 0X30,
+[0X61] = 0X31, 0X32, 0X33, 0X34, 0X35, 0X36, 0X37, 0X70,
+[0X69] = 0X71, 0X72, 0X73, 0X74, 0X75, 0X76, 0X77, 0X38,
+[0X71] = 0X39, 0X3A, 0X3B, 0X3C, 0X3D, 0X3E, 0X3F, 0X78,
+[0X79] = 0X79, 0X7A, 0X7B, 0X7C, 0X7D, 0X7E, 0X7F, 0X80,
+[0X81] = 0X81, 0X82, 0X83, 0X84, 0X85, 0X86, 0X87, 0XC0,
+[0X89] = 0XC1, 0XC2, 0XC3, 0XC4, 0XC5, 0XC6, 0XC7, 0X88,
+[0X91] = 0X89, 0X8A, 0X8B, 0X8C, 0X8D, 0X8E, 0X8F, 0XC8,
+[0X99] = 0XC9, 0XCA, 0XCB, 0XCC, 0XCD, 0XCE, 0XCF, 0X90,
+[0XA1] = 0X91, 0X92, 0X93, 0X94, 0X95, 0X96, 0X97, 0XD0,
+[0XA9] = 0XD1, 0XD2, 0XD3, 0XD4, 0XD5, 0XD6, 0XD7, 0X98,
+[0XB1] = 0X99, 0X9A, 0X9B, 0X9C, 0X9D, 0X9E, 0X9F, 0XD8,
+[0XB9] = 0XD9, 0XDA, 0XDB, 0XDC, 0XDD, 0XDE, 0XDF, 0XA0,
+[0XC1] = 0XA1, 0XA2, 0XA3, 0XA4, 0XA5, 0XA6, 0XA7, 0XE0,
+[0XC9] = 0XE1, 0XE2, 0XE3, 0XE4, 0XE5, 0XE6, 0XE7, 0XA8,
+[0XD1] = 0XA9, 0XAA, 0XAB, 0XAC, 0XAD, 0XAE, 0XAF, 0XE8,
+[0XD9] = 0XE9, 0XEA, 0XEB, 0XEC, 0XED, 0XEE, 0XEF, 0XB0,
+[0XE1] = 0XB1, 0XB2, 0XB3, 0XB4, 0XB5, 0XB6, 0XB7, 0XF0,
+[0XE9] = 0XF1, 0XF2, 0XF3, 0XF4, 0XF5, 0XF6, 0XF7, 0XB8,
+[0XF1] = 0XB9, 0XBA, 0XBB, 0XBC, 0XBD, 0XBE, 0XBF, 0XF8,
+[0XF9] = 0XF9, 0XFA, 0XFB, 0XFC, 0XFD, 0XFE, 0XFF
diff --git a/Programs/atb_compile.c b/Programs/atb_compile.c
new file mode 100644
index 0000000..8d7c0ba
--- /dev/null
+++ b/Programs/atb_compile.c
@@ -0,0 +1,222 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "file.h"
+#include "datafile.h"
+#include "dataarea.h"
+#include "atb.h"
+#include "atb_internal.h"
+
+typedef struct {
+  unsigned char attribute;
+  unsigned char operation;
+} DotData;
+
+typedef struct {
+  DataArea *area;
+  DotData dots[8];
+} AttributesTableData;
+
+static inline AttributesTableHeader *
+getAttributesTableHeader (AttributesTableData *atd) {
+  return getDataItem(atd->area, 0);
+}
+
+static int
+makeAttributesToDots (AttributesTableData *atd) {
+  unsigned char *table = getAttributesTableHeader(atd)->attributesToDots;
+  unsigned char bits = 0;
+
+  do {
+    unsigned char *cell = &table[bits];
+    unsigned char dotIndex;
+
+    *cell = 0;
+    for (dotIndex=0; dotIndex<BRL_DOT_COUNT; dotIndex+=1) {
+      const DotData *dot = &atd->dots[dotIndex];
+      int isSet = bits & dot->attribute;
+      if (!isSet == !dot->operation) *cell |= brlDotBits[dotIndex];
+    }
+  } while ((bits += 1));
+
+  return 1;
+}
+
+static int
+parseAttributeOperand (DataFile *file, unsigned char *bit, unsigned char *operation, const wchar_t *characters, int length) {
+  if (length > 1) {
+    static const wchar_t operators[] = {'~', '='};
+    const wchar_t *operator = wmemchr(operators, characters[0], ARRAY_COUNT(operators));
+
+    if (operator) {
+      typedef struct {
+        const wchar_t *name;
+        unsigned char bit;
+      } AttributeEntry;
+
+      static const AttributeEntry attributeTable[] = {
+        {WS_C("fg-blue")  , 0X01},
+        {WS_C("fg-green") , 0X02},
+        {WS_C("fg-red")   , 0X04},
+        {WS_C("fg-bright"), 0X08},
+        {WS_C("bg-blue")  , 0X10},
+        {WS_C("bg-green") , 0X20},
+        {WS_C("bg-red")   , 0X40},
+        {WS_C("blink")    , 0X80},
+
+        {WS_C("bit0"), 0X01},
+        {WS_C("bit1"), 0X02},
+        {WS_C("bit2"), 0X04},
+        {WS_C("bit3"), 0X08},
+        {WS_C("bit4"), 0X10},
+        {WS_C("bit5"), 0X20},
+        {WS_C("bit6"), 0X40},
+        {WS_C("bit7"), 0X80},
+
+        {WS_C("bit01"), 0X01},
+        {WS_C("bit02"), 0X02},
+        {WS_C("bit04"), 0X04},
+        {WS_C("bit08"), 0X08},
+        {WS_C("bit10"), 0X10},
+        {WS_C("bit20"), 0X20},
+        {WS_C("bit40"), 0X40},
+        {WS_C("bit80"), 0X80},
+
+        {NULL, 0X00}
+      };
+
+      const AttributeEntry *attribute = attributeTable;
+
+      characters += 1, length -= 1;
+
+      while (attribute->name) {
+        if ((length == wcslen(attribute->name)) &&
+            (wmemcmp(characters, attribute->name, length) == 0)) {
+          *bit = attribute->bit;
+          *operation = operator - operators;
+          return 1;
+        }
+
+        attribute += 1;
+      }
+
+      reportDataError(file, "invalid attribute name: %.*" PRIws, length, characters);
+    } else {
+      reportDataError(file, "invalid attribute operator: %.1" PRIws, &characters[0]);
+    }
+  }
+
+  return 0;
+}
+
+static int
+getAttributeOperand (DataFile *file, unsigned char *bit, unsigned char *operation) {
+  DataOperand attribute;
+
+  if (getDataOperand(file, &attribute, "attribute"))
+    if (parseAttributeOperand(file, bit, operation, attribute.characters, attribute.length))
+      return 1;
+
+  return 0;
+}
+
+static DATA_OPERANDS_PROCESSOR(processDotOperands) {
+  AttributesTableData *atd = data;
+  int dotIndex;
+
+  if (getDotOperand(file, &dotIndex)) {
+    DotData *dot = &atd->dots[dotIndex];
+
+    if (getAttributeOperand(file, &dot->attribute, &dot->operation)) return 1;
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processAttributesTableOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    {.name=WS_C("dot"), .processor=processDotOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "attributes table directive", data);
+}
+
+AttributesTable *
+compileAttributesTable (const char *name) {
+  AttributesTable *table = NULL;
+
+  if (setTableDataVariables(ATTRIBUTES_TABLE_EXTENSION, ATTRIBUTES_SUBTABLE_EXTENSION)) {
+    AttributesTableData atd;
+    memset(&atd, 0, sizeof(atd));
+
+    if ((atd.area = newDataArea())) {
+      if (allocateDataItem(atd.area, NULL, sizeof(AttributesTableHeader), __alignof__(AttributesTableHeader))) {
+        const DataFileParameters parameters = {
+          .processOperands = processAttributesTableOperands,
+          .data = &atd
+        };
+
+        if (processDataFile(name, &parameters)) {
+          if (makeAttributesToDots(&atd)) {
+            if ((table = malloc(sizeof(*table)))) {
+              table->header.fields = getAttributesTableHeader(&atd);
+              table->size = getDataSize(atd.area);
+              resetDataArea(atd.area);
+            }
+          }
+        }
+      }
+
+      destroyDataArea(atd.area);
+    }
+  }
+
+  return table;
+}
+
+void
+destroyAttributesTable (AttributesTable *table) {
+  if (table->size) {
+    free(table->header.fields);
+    free(table);
+  }
+}
+
+char *
+ensureAttributesTableExtension (const char *path) {
+  return ensureFileExtension(path, ATTRIBUTES_TABLE_EXTENSION);
+}
+
+char *
+makeAttributesTablePath (const char *directory, const char *name) {
+  char *subdirectory = makePath(directory, ATTRIBUTES_TABLES_SUBDIRECTORY);
+
+  if (subdirectory) {
+    char *file = makeFilePath(subdirectory, name, ATTRIBUTES_TABLE_EXTENSION);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
diff --git a/Programs/atb_internal.h b/Programs/atb_internal.h
new file mode 100644
index 0000000..deb7d13
--- /dev/null
+++ b/Programs/atb_internal.h
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ATB_INTERNAL
+#define BRLTTY_INCLUDED_ATB_INTERNAL
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint32_t AttributesTableOffset;
+
+typedef struct {
+  unsigned char attributesToDots[0X100];
+} AttributesTableHeader;
+
+struct AttributesTableStruct {
+  union {
+    AttributesTableHeader *fields;
+    const unsigned char *bytes;
+  } header;
+
+  size_t size;
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ATB_INTERNAL */
diff --git a/Programs/atb_translate.c b/Programs/atb_translate.c
new file mode 100644
index 0000000..26cb06c
--- /dev/null
+++ b/Programs/atb_translate.c
@@ -0,0 +1,92 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "lock.h"
+#include "file.h"
+#include "atb.h"
+#include "atb_internal.h"
+
+static const unsigned char internalAttributesTableBytes[] = {
+#include "atb.auto.h"
+};
+
+static AttributesTable internalAttributesTable = {
+  .header.bytes = internalAttributesTableBytes,
+  .size = 0
+};
+
+AttributesTable *attributesTable = &internalAttributesTable;
+
+static LockDescriptor *
+getAttributesTableLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "attributes-table");
+}
+
+void
+lockAttributesTable (void) {
+  obtainExclusiveLock(getAttributesTableLock());
+}
+
+void
+unlockAttributesTable (void) {
+  releaseLock(getAttributesTableLock());
+}
+
+unsigned char
+convertAttributesToDots (AttributesTable *table, unsigned char attributes) {
+  return table->header.fields->attributesToDots[attributes];
+}
+
+int
+replaceAttributesTable (const char *directory, const char *name) {
+  AttributesTable *newTable = NULL;
+
+  if (*name) {
+    char *path;
+
+    if ((path = makeAttributesTablePath(directory, name))) {
+      logMessage(LOG_DEBUG, "compiling attributes table: %s", path);
+
+      if (!(newTable = compileAttributesTable(path))) {
+        logMessage(LOG_ERR, "%s: %s", gettext("cannot compile attributes table"), path);
+      }
+
+      free(path);
+    }
+  } else {
+    newTable = &internalAttributesTable;
+  }
+
+  if (newTable) {
+    AttributesTable *oldTable = attributesTable;
+
+    lockAttributesTable();
+      attributesTable = newTable;
+    unlockAttributesTable();
+
+    destroyAttributesTable(oldTable);
+    return 1;
+  }
+
+  logMessage(LOG_ERR, "%s: %s", gettext("cannot load attributes table"), name);
+  return 0;
+}
diff --git a/Programs/auth.c b/Programs/auth.c
new file mode 100644
index 0000000..39aae96
--- /dev/null
+++ b/Programs/auth.c
@@ -0,0 +1,851 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#if defined(__MINGW32__)
+#include <ws2tcpip.h>
+
+#elif defined(__MSDOS__)
+
+#elif defined(GRUB_RUNTIME)
+typedef unsigned int uid_t;
+typedef unsigned int gid_t;
+
+#else /* Unix */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <pwd.h>
+#include <grp.h>
+#endif /* platform-specific includes */
+
+#if !defined(AF_LOCAL) && defined(AF_UNIX)
+#define AF_LOCAL AF_UNIX
+#endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */
+
+#if !defined(PF_LOCAL) && defined(PF_UNIX)
+#define PF_LOCAL PF_UNIX
+#endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */
+
+#include "log.h"
+#include "strfmt.h"
+#include "parse.h"
+#include "auth.h"
+#include "async_wait.h"
+
+/* peer credentials */
+#undef CAN_CHECK_CREDENTIALS
+
+typedef struct {
+#ifdef __MINGW32__
+  const char *name;
+#else /* __MINGW32__ */
+  uid_t id;
+#endif /* __MINGW32__ */
+} MethodDescriptor_user;
+
+typedef struct {
+#ifdef __MINGW32__
+  const char *name;
+#else /* __MINGW32__ */
+  gid_t id;
+#endif /* __MINGW32__ */
+} MethodDescriptor_group;
+
+#if defined(__MINGW32__)
+#define CAN_CHECK_CREDENTIALS
+
+typedef struct {
+  char *user;
+} PeerCredentials;
+
+static int
+retrievePeerCredentials (PeerCredentials *credentials, FileDescriptor fd) {
+  char buffer[0X100+1];
+
+  if (GetNamedPipeHandleState(fd, NULL, NULL, NULL, NULL, buffer, sizeof(buffer))) {
+    buffer[sizeof(buffer) - 1] = 0;
+
+    if ((credentials->user = strdup(buffer))) {
+      return 1;
+    }
+  } else {
+    switch (GetLastError()) {
+      default:
+        logWindowsSystemError("GetNamedPipeHandleState");
+
+      case ERROR_INSUFFICIENT_BUFFER: /* buffer too small */
+      case ERROR_INVALID_HANDLE: /* not a named pipe */
+      case ERROR_CANNOT_IMPERSONATE: /* no data transferred yet */
+        break;
+    }
+  }
+
+  return 0;
+}
+
+static void
+releasePeerCredentials (PeerCredentials *credentials) {
+  free(credentials->user);
+}
+
+static int
+checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
+  return strcmp(user->name, credentials->user) == 0;
+}
+
+static int
+checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
+  return 0;
+}
+
+#elif defined(HAVE_GETPEERUCRED)
+#define CAN_CHECK_CREDENTIALS
+
+#include <ucred.h>
+
+#ifdef HAVE_GETZONEID
+#include <zone.h>
+#endif /* HAVE_GETZONEID */
+
+typedef ucred_t *PeerCredentials;
+
+static int
+retrievePeerCredentials (PeerCredentials *credentials, int fd) {
+  *credentials = NULL;
+  if (getpeerucred(fd, credentials) == -1) {
+    logSystemError("getpeerucred");
+    return 0;
+  }
+
+#ifdef HAVE_GETZONEID
+  if (ucred_getzoneid(*credentials) != getzoneid()) {
+    ucred_free(*credentials);
+    return 0;
+  }
+#endif /* HAVE_GETZONEID */
+
+  return 1;
+}
+
+static void
+releasePeerCredentials (PeerCredentials *credentials) {
+  ucred_free(*credentials);
+}
+
+static int
+checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
+  if (user->id == ucred_geteuid(*credentials)) return 1;
+  return 0;
+}
+
+static int
+checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
+  if (group->id == ucred_getegid(*credentials)) return 1;
+
+  {
+    const gid_t *groups;
+    int count = ucred_getgroups(*credentials, &groups);
+    while (count > 0)
+      if (group->id == groups[--count])
+        return 1;
+  }
+
+  return 0;
+}
+
+#elif defined(HAVE_GETPEEREID)
+#define CAN_CHECK_CREDENTIALS
+
+typedef struct {
+  uid_t euid;
+  gid_t egid;
+} PeerCredentials;
+
+static int
+retrievePeerCredentials (PeerCredentials *credentials, int fd) {
+  if (getpeereid(fd, &credentials->euid, &credentials->egid) != -1) return 1;
+  logSystemError("getpeereid");
+  return 0;
+}
+
+static void
+releasePeerCredentials (PeerCredentials *credentials) {
+}
+
+static int
+checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
+  return user->id == credentials->euid;
+}
+
+static int
+checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
+  return group->id == credentials->egid;
+}
+
+#elif defined(SO_PEERCRED)
+#define CAN_CHECK_CREDENTIALS
+
+typedef struct ucred PeerCredentials;
+
+static int
+retrievePeerCredentials (PeerCredentials *credentials, int fd) {
+  socklen_t length = sizeof(*credentials);
+  if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, credentials, &length) != -1) return 1;
+  logSystemError("getsockopt[SO_PEERCRED]");
+  return 0;
+}
+
+static void
+releasePeerCredentials (PeerCredentials *credentials) {
+}
+
+static int
+checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) {
+  return user->id == credentials->uid;
+}
+
+static int
+checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) {
+  return group->id == credentials->gid;
+}
+
+#else /* peer credentials method */
+#warning peer credentials support not available on this platform
+#endif /* peer credentials method */
+
+/* general type definitions */
+
+typedef int (*MethodPerform) (AuthDescriptor *auth, FileDescriptor fd, void *data);
+
+typedef struct {
+  const char *name;
+  void *(*initialize) (const char *parameter);
+  void (*release) (void *data);
+  MethodPerform client;
+  MethodPerform server;
+} MethodDefinition;
+
+typedef struct {
+  const MethodDefinition *definition;
+  void *data;
+} MethodDescriptor;
+
+#ifdef CAN_CHECK_CREDENTIALS
+typedef enum {
+  PCS_NEED,
+  PCS_CANT,
+  PCS_HAVE
+} PeerCredentialsState;
+#endif /* CAN_CHECK_CREDENTIALS */
+
+typedef int (*AuthPerform) (AuthDescriptor *auth, FileDescriptor fd);
+
+struct AuthDescriptorStruct {
+  int count;
+  char **parameters;
+  MethodDescriptor *methods;
+  AuthPerform perform;
+
+#ifdef CAN_CHECK_CREDENTIALS
+  PeerCredentialsState peerCredentialsState;
+  PeerCredentials peerCredentials;
+#endif /* CAN_CHECK_CREDENTIALS */
+};
+
+/* the keyfile method */
+
+typedef struct {
+  const char *path;
+} MethodDescriptor_keyfile;
+
+static void *
+authKeyfile_initialize (const char *parameter) {
+  MethodDescriptor_keyfile *keyfile;
+
+  if ((keyfile = malloc(sizeof(*keyfile)))) {
+    if (*parameter) {
+      keyfile->path = parameter;
+      return keyfile;
+    } else {
+      logMessage(LOG_ERR, "path to key file not specified");
+    }
+
+    free(keyfile);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+authKeyfile_release (void *data) {
+  MethodDescriptor_keyfile *keyfile = data;
+  free(keyfile);
+}
+
+static int
+authKeyfile_client (AuthDescriptor *auth, FileDescriptor fd, void *data) {
+  return 1;
+}
+
+static int
+authKeyfile_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
+  MethodDescriptor_keyfile *keyfile = data;
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "checking key file: %s", keyfile->path);
+  return 1;
+}
+
+#ifdef CAN_CHECK_CREDENTIALS
+static int
+getPeerCredentials (AuthDescriptor *auth, FileDescriptor fd) {
+  if (auth->peerCredentialsState == PCS_NEED) {
+    auth->peerCredentialsState = retrievePeerCredentials(&auth->peerCredentials, fd)? PCS_HAVE: PCS_CANT;
+  }
+  return auth->peerCredentialsState == PCS_HAVE;
+}
+
+/* the user method */
+
+static void *
+authUser_initialize (const char *parameter) {
+  MethodDescriptor_user *user;
+
+  if ((user = malloc(sizeof(*user)))) {
+#ifdef __MINGW32__
+    user->name = parameter;
+    return user;
+#else /* __MINGW32__ */
+    if (!*parameter) {
+      user->id = geteuid();
+      return user;
+    }
+
+    {
+      int value;
+      if (isInteger(&value, parameter)) {
+        user->id = value;
+        return user;
+      }
+    }
+
+    {
+      const struct passwd *p = getpwnam(parameter);
+      if (p) {
+        user->id = p->pw_uid;
+        return user;
+      }
+    }
+
+    logMessage(LOG_ERR, "unknown user: %s", parameter);
+    free(user);
+#endif /* __MINGW32__ */
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+authUser_release (void *data) {
+  MethodDescriptor_user *user = data;
+  free(user);
+}
+
+static int
+authUser_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
+  MethodDescriptor_user *user = data;
+  return getPeerCredentials(auth, fd) &&
+         checkPeerUser(&auth->peerCredentials, user);
+}
+
+/* the group method */
+
+static void *
+authGroup_initialize (const char *parameter) {
+  MethodDescriptor_group *group;
+
+  if ((group = malloc(sizeof(*group)))) {
+#ifdef __MINGW32__
+    group->name = parameter;
+    return group;
+#else /* __MINGW32__ */
+    if (!*parameter) {
+      group->id = getegid();
+      return group;
+    }
+
+    {
+      int value;
+      if (isInteger(&value, parameter)) {
+        group->id = value;
+        return group;
+      }
+    }
+
+    {
+      const struct group *g = getgrnam(parameter);
+      if (g) {
+        group->id = g->gr_gid;
+        return group;
+      }
+    }
+
+    logMessage(LOG_ERR, "unknown group: %s", parameter);
+    free(group);
+#endif /* __MINGW32__ */
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+authGroup_release (void *data) {
+  MethodDescriptor_group *group = data;
+  free(group);
+}
+
+static int
+authGroup_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
+  MethodDescriptor_group *group = data;
+  return getPeerCredentials(auth, fd) &&
+         checkPeerGroup(&auth->peerCredentials, group);
+}
+
+#ifdef HAVE_POLKIT
+#include <polkit/polkit.h>
+
+typedef struct {
+  PolkitAuthority *authority;
+} MethodDescriptor_polkit;
+
+static void *
+authPolkit_initialize (const char *parameter) {
+  MethodDescriptor_polkit *polkit;
+
+  if ((polkit = malloc(sizeof(*polkit)))) {
+    memset(polkit, 0, sizeof(*polkit));
+
+    while (1) {
+      GError *error = NULL;
+      polkit->authority = polkit_authority_get_sync(NULL, &error);
+      if (polkit->authority) return polkit;
+
+      int wait = 0;
+      char message[0X100];
+
+      STR_BEGIN(message, sizeof(message));
+      STR_PRINTF("unable to connect to polkit");
+
+      if (error) {
+        GQuark domain = error->domain;
+        gint code = error->code;
+
+        STR_PRINTF(": %s (%d) %s (%d)",
+                   g_quark_to_string(domain), (int)domain,
+                   error->message, code);
+
+        g_error_free(error);
+        error = NULL;
+
+        if ((domain == G_IO_ERROR) || (code == G_IO_ERROR_NOT_FOUND)) wait = 1;
+      }
+
+      STR_END;
+      logMessage(LOG_WARNING, "%s", message);
+
+      if (!wait) break;
+      asyncWait(1000);
+    }
+
+    g_free(polkit);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+authPolkit_release (void *data) {
+  MethodDescriptor_polkit *polkit = data;
+  g_object_unref (polkit->authority);
+  free(polkit);
+}
+
+static int
+authPolkit_server (AuthDescriptor *auth, FileDescriptor fd, void *data) {
+  MethodDescriptor_polkit *polkit = data;
+
+  struct ucred cred;
+  socklen_t length = sizeof(cred);
+
+  if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &length) != -1) {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS),
+      "attempting to authorize client (pid %d, uid %d) via polkit",
+      cred.pid, cred.uid
+    );
+
+    if (cred.uid == -1) {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS),
+        "user not specified in credentials"
+      );
+    } else {
+      PolkitSubject *subject = polkit_unix_process_new_for_owner(cred.pid, 0, cred.uid);
+
+      if (subject) {
+        GError *error_local = NULL;
+
+        PolkitAuthorizationResult *result = polkit_authority_check_authorization_sync(
+          polkit->authority,			/* authority */
+          subject,				/* PolkitSubject for client */
+          "org.a11y.brlapi.write-display",		/* name of polkit action */
+          NULL,					/* details */
+          POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE,	/* disallow interaction */
+          NULL,					/* GCancellable */
+          &error_local				/* returned error */
+        );
+
+        if (result) {
+          int isAuthorized = polkit_authorization_result_get_is_authorized(result);
+          g_object_unref(result);
+
+          logMessage(LOG_CATEGORY(SERVER_EVENTS),
+            "polkit_authority_check_authorization_sync returned %d",
+            isAuthorized
+          );
+
+          return isAuthorized;
+        } else {
+          logMessage(LOG_ERR, "polkit_authority_check_authorization_sync error: %s", error_local->message);
+          g_error_free(error_local);
+        }
+
+        g_object_unref(subject);
+      } else {
+        logSystemError("polkit_unix_process_new_for_owner");
+      }
+    }
+  } else {
+    logSystemError("getsockopt[SO_PEERCRED]");
+  }
+
+  return 0;
+}
+#endif /* HAVE_POLKIT */
+#endif /* CAN_CHECK_CREDENTIALS */
+
+/* general functions */
+
+static const MethodDefinition methodDefinitions[] = {
+  { .name = "keyfile",
+    .initialize = authKeyfile_initialize,
+    .release = authKeyfile_release,
+    .client = authKeyfile_client,
+    .server = authKeyfile_server
+  },
+
+#ifdef CAN_CHECK_CREDENTIALS
+  { .name = "user",
+    .initialize = authUser_initialize,
+    .release = authUser_release,
+    .client = NULL,
+    .server = authUser_server
+  },
+
+  { .name = "group",
+    .initialize = authGroup_initialize,
+    .release = authGroup_release,
+    .client = NULL,
+    .server = authGroup_server
+  },
+
+#ifdef HAVE_POLKIT
+  { .name = "polkit",
+    .initialize = authPolkit_initialize,
+    .release = authPolkit_release,
+    .client = NULL,
+    .server = authPolkit_server
+  },
+#endif /* HAVE_POLKIT */
+#endif /* CAN_CHECK_CREDENTIALS */
+
+  {.name = NULL}
+};
+
+static void
+releaseMethodDescriptor (MethodDescriptor *method) {
+  if (method->data) {
+    if (method->definition->release) method->definition->release(method->data);
+    method->data = NULL;
+  }
+}
+
+static void
+releaseMethodDescriptors (AuthDescriptor *auth, int count) {
+  while (count > 0) releaseMethodDescriptor(&auth->methods[--count]);
+}
+
+static int
+initializeMethodDescriptor (MethodDescriptor *method, const char *parameter) {
+  const char *name;
+  int nameLength;
+
+  if ((parameter = strchr(name=parameter, ':'))) {
+    nameLength = parameter++ - name;
+  } else {
+    parameter = name + (nameLength = strlen(name));
+  }
+
+  {
+    const MethodDefinition *definition = methodDefinitions;
+    while (definition->name) {
+      if ((nameLength == strlen(definition->name)) &&
+          (strncmp(name, definition->name, nameLength) == 0)) {
+        void *data = definition->initialize(parameter);
+        if (!data) return 0;
+
+        method->definition = definition;
+        method->data = data;
+        return 1;
+      }
+
+      ++definition;
+    }
+  }
+
+  logMessage(LOG_WARNING, "unknown authorization method: %.*s", nameLength, name);
+  return 0;
+}
+
+static int
+initializeMethodDescriptors (AuthDescriptor *auth) {
+  int index = 0;
+  while (index < auth->count) {
+    if (!initializeMethodDescriptor(&auth->methods[index], auth->parameters[index])) {
+      releaseMethodDescriptors(auth, index);
+      return 0;
+    }
+    ++index;
+  }
+  return 1;
+}
+
+static AuthDescriptor *
+authBegin (const char *parameter, const char *defaultParameter, AuthPerform perform) {
+  AuthDescriptor *auth;
+
+  if ((auth = malloc(sizeof(*auth)))) {
+    auth->perform = perform;
+
+    if (!parameter) parameter = "";
+    if (!*parameter) {
+      parameter = defaultParameter;
+    } else if (strcmp(parameter, "none") == 0) {
+      parameter = "";
+    }
+
+    if ((auth->parameters = splitString(parameter, '+', &auth->count))) {
+      if (!auth->count) {
+        auth->methods = NULL;
+        return auth;
+      }
+
+      if ((auth->methods = malloc(ARRAY_SIZE(auth->methods, auth->count)))) {
+        if (initializeMethodDescriptors(auth)) return auth;
+
+        free(auth->methods);
+      } else {
+        logMallocError();
+      }
+
+      deallocateStrings(auth->parameters);
+    }
+
+    free(auth);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+authPerformClient (AuthDescriptor *auth, FileDescriptor fd) {
+  return 1;
+}
+
+AuthDescriptor *
+authBeginClient (const char *parameter) {
+  return authBegin(parameter, "", authPerformClient);
+}
+
+static int
+authPerformServer (AuthDescriptor *auth, FileDescriptor fd) {
+  int ok = 0;
+
+  if (!auth->count) return 1;
+
+#ifdef CAN_CHECK_CREDENTIALS
+  auth->peerCredentialsState = PCS_NEED;
+#endif /* CAN_CHECK_CREDENTIALS */
+
+  {
+    int index;
+    for (index=0; index<auth->count; ++index) {
+      const MethodDescriptor *method = &auth->methods[index];
+
+      if (!method->definition->server) continue;
+      if (method->definition->client) continue;
+
+      if (method->definition->server(auth, fd, method->data)) {
+        ok = 1;
+        break;
+      }
+    }
+  }
+
+#ifdef CAN_CHECK_CREDENTIALS
+  if (auth->peerCredentialsState != PCS_NEED) {
+    if (auth->peerCredentialsState == PCS_HAVE) releasePeerCredentials(&auth->peerCredentials);
+    if (!ok) logMessage(LOG_ERR, "no matching user or group");
+  }
+#endif /* CAN_CHECK_CREDENTIALS */
+
+  if (!ok) {
+    int index;
+    for (index=0; index<auth->count; ++index) {
+      const MethodDescriptor *method = &auth->methods[index];
+
+      if (!method->definition->server) continue;
+      if (!method->definition->client) continue;
+    }
+  }
+
+  return ok;
+}
+
+AuthDescriptor *
+authBeginServer (const char *parameter) {
+  return authBegin(parameter, "user", authPerformServer);
+}
+
+void
+authEnd (AuthDescriptor *auth) {
+  releaseMethodDescriptors(auth, auth->count);
+  if (auth->methods) free(auth->methods);
+  deallocateStrings(auth->parameters);
+  free(auth);
+}
+
+int
+authPerform (AuthDescriptor *auth, FileDescriptor fd) {
+  return auth->perform(auth, fd);
+}
+
+void
+formatAddress (
+  char *buffer, size_t bufferSize,
+  const void *address, socklen_t addressSize
+) {
+#ifdef AF_INET
+  const struct sockaddr *sa = address;
+
+  switch (sa->sa_family) {
+#ifndef __MINGW32__
+    case AF_LOCAL: {
+      const struct sockaddr_un *local = address;
+
+      if (addressSize <= sizeof(sa_family_t)) {
+        snprintf(buffer, bufferSize, "local <unnamed>");
+      } else {
+        snprintf(buffer, bufferSize, "local %s", local->sun_path);
+      }
+      break;
+    }
+#endif /* __MINGW32__ */
+
+    case AF_INET: {
+      const struct sockaddr_in *inet = address;
+
+      snprintf(buffer, bufferSize, "inet %s:%d", inet_ntoa(inet->sin_addr), ntohs(inet->sin_port));
+      break;
+    }
+
+    default:
+#if defined(HAVE_GETNAMEINFO) && !defined(WINDOWS)
+      {
+        char host[NI_MAXHOST];
+        char service[NI_MAXSERV];
+        int err;
+
+        if (!(err = getnameinfo(address, addressSize,
+                                host, sizeof(host), service, sizeof(service),
+                                NI_NUMERICHOST | NI_NUMERICSERV))) {
+          snprintf(buffer, bufferSize, "af=%d %s:%s", sa->sa_family, host, service);
+          break;
+        }
+
+        if (err != EAI_FAMILY) {
+#ifdef HAVE_GAI_STRERROR
+          snprintf(buffer, bufferSize, "reverse lookup error for address family %d: %s", 
+                   sa->sa_family,
+#ifdef EAI_SYSTEM
+                   (err == EAI_SYSTEM)? strerror(errno):
+#endif /* EAI_SYSTEM */
+                   gai_strerror(err));
+#else /* HAVE_GAI_STRERROR */
+          snprintf(buffer, bufferSize, "reverse lookup error %d for address family %d",
+                   err, sa->sa_family);
+#endif /* HAVE_GAI_STRERROR */
+          break;
+        }
+      }
+#endif /* GETNAMEINFO */
+
+      {
+        STR_BEGIN(buffer, bufferSize);
+        STR_PRINTF("address family %d:", sa->sa_family);
+
+        {
+          const unsigned char *byte = address;
+          const unsigned char *end = byte + addressSize;
+
+          while (byte < end) STR_PRINTF(" %02X", *byte++);
+        }
+
+        STR_END;
+      }
+      break;
+  }
+#else /* AF_INET */
+  snprintf(buffer, bufferSize, "unknown");
+#endif /* AF_INET */
+}
diff --git a/Programs/beep.c b/Programs/beep.c
new file mode 100644
index 0000000..6376a79
--- /dev/null
+++ b/Programs/beep.c
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "async_wait.h"
+#include "beep.h"
+
+int
+playBeep (BeepFrequency frequency, BeepDuration duration) {
+  if (asynchronousBeep(frequency, duration*4)) {
+    asyncWait(duration);
+    stopBeep();
+    return 1;
+  }
+
+  if (startBeep(frequency)) {
+    asyncWait(duration);
+    stopBeep();
+    return 1;
+  }
+
+  return synchronousBeep(frequency, duration);
+}
diff --git a/Programs/beep_linux.c b/Programs/beep_linux.c
new file mode 100644
index 0000000..2063bc5
--- /dev/null
+++ b/Programs/beep_linux.c
@@ -0,0 +1,95 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+
+#include "log.h"
+#include "beep.h"
+#include "system_linux.h"
+
+#define BEEP_DEVICE_PATH "/dev/tty0"
+#define TICKS_PER_SECOND 1193180
+
+static int beepDevice = INVALID_FILE_DESCRIPTOR;
+
+static inline BeepFrequency
+getTicksPerWave (BeepFrequency frequency) {
+  return frequency? (TICKS_PER_SECOND / frequency): 0;
+}
+
+int
+canBeep (void) {
+  if (beepDevice == INVALID_FILE_DESCRIPTOR) {
+    const char *path = BEEP_DEVICE_PATH;
+    int device = open(path, O_WRONLY);
+
+    if (device == -1) {
+      logMessage(LOG_WARNING, "can't open beep device: %s: %s", path, strerror(errno));
+      return 0;
+    }
+
+    beepDevice = device;
+    installSpeakerModule();
+  }
+
+  return 1;
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  if (beepDevice != INVALID_FILE_DESCRIPTOR) {
+    if (ioctl(beepDevice, KDMKTONE, ((duration << 0X10) | getTicksPerWave(frequency))) != -1) return 1;
+    logSystemError("ioctl[KDMKTONE]");
+  }
+
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  if (beepDevice != INVALID_FILE_DESCRIPTOR) {
+    if (ioctl(beepDevice, KIOCSOUND, getTicksPerWave(frequency)) != -1) return 1;
+    logSystemError("ioctl[KIOCSOUND]");
+  }
+
+  return 0;
+}
+
+int
+stopBeep (void) {
+  return startBeep(0);
+}
+
+void
+endBeep (void) {
+  if (beepDevice != INVALID_FILE_DESCRIPTOR) {
+    close(beepDevice);
+    beepDevice = INVALID_FILE_DESCRIPTOR;
+  }
+}
diff --git a/Programs/beep_msdos.c b/Programs/beep_msdos.c
new file mode 100644
index 0000000..8f400d4
--- /dev/null
+++ b/Programs/beep_msdos.c
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <pc.h>
+
+#include "beep.h"
+
+int
+canBeep (void) {
+  return 1;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  sound(frequency);
+  return 1;
+}
+
+int
+stopBeep (void) {
+  nosound();
+  return 1;
+}
+
+void
+endBeep (void) {
+}
diff --git a/Programs/beep_none.c b/Programs/beep_none.c
new file mode 100644
index 0000000..5a4a53c
--- /dev/null
+++ b/Programs/beep_none.c
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "beep.h"
+
+int
+canBeep (void) {
+  return 0;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  return 0;
+}
+
+int
+stopBeep (void) {
+  return 0;
+}
+
+void
+endBeep (void) {
+}
diff --git a/Programs/beep_solaris.c b/Programs/beep_solaris.c
new file mode 100644
index 0000000..1e4c3c6
--- /dev/null
+++ b/Programs/beep_solaris.c
@@ -0,0 +1,80 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <fcntl.h>
+#include <sys/kbio.h>
+#include <sys/kbd.h>
+
+#include "log.h"
+#include "beep.h"
+
+static int
+getKeyboard (void) {
+  static int keyboard = -1;
+  if (keyboard == -1) {
+    if ((keyboard = open("/dev/kbd", O_WRONLY)) != -1) {
+      logMessage(LOG_DEBUG, "keyboard opened: fd=%d", keyboard);
+    } else {
+      logSystemError("keyboard open");
+    }
+  }
+  return keyboard;
+}
+
+int
+canBeep (void) {
+  return getKeyboard() != -1;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  int keyboard = getKeyboard();
+  if (keyboard != -1) {
+    int command = KBD_CMD_BELL;
+    if (ioctl(keyboard, KIOCCMD, &command) != -1) return 1;
+    logSystemError("ioctl KIOCCMD KBD_CMD_BELL");
+  }
+  return 0;
+}
+
+int
+stopBeep (void) {
+  int keyboard = getKeyboard();
+  if (keyboard != -1) {
+    int command = KBD_CMD_NOBELL;
+    if (ioctl(keyboard, KIOCCMD, &command) != -1) return 1;
+    logSystemError("ioctl KIOCCMD KBD_CMD_NOBELL");
+  }
+  return 0;
+}
+
+void
+endBeep (void) {
+}
diff --git a/Programs/beep_spkr.c b/Programs/beep_spkr.c
new file mode 100644
index 0000000..28ff266
--- /dev/null
+++ b/Programs/beep_spkr.c
@@ -0,0 +1,105 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <fcntl.h>
+
+#undef CAN_BEEP
+
+#if defined(HAVE_DEV_SPEAKER_SPEAKER_H)
+#include <dev/speaker/speaker.h>
+#define CAN_BEEP
+
+#elif defined(HAVE_MACHINE_SPEAKER_H)
+#include <machine/speaker.h>
+#define CAN_BEEP
+
+#else /* speaker.h */
+#warning beeps not available on this installation: no speaker.h
+#endif /* speaker.h */
+
+#ifdef CAN_BEEP
+#include "log.h"
+#include "beep.h"
+
+static int speaker = -1;
+
+static int
+getSpeaker (void) {
+  if (speaker == -1) {
+    if ((speaker = open("/dev/speaker", O_WRONLY)) != -1) {
+      logMessage(LOG_DEBUG, "Speaker opened: fd=%d", speaker);
+    } else {
+      logSystemError("speaker open");
+    }
+  }
+  return speaker;
+}
+#endif /* CAN_BEEP */
+
+int
+canBeep (void) {
+#ifdef CAN_BEEP
+  return 1;
+#else /* CAN_BEEP */
+  return 0;
+#endif /* CAN_BEEP */
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+#ifdef CAN_BEEP
+  int speaker = getSpeaker();
+  if (speaker != -1) {
+    tone_t tone;
+    memset(&tone, 0, sizeof(tone));
+    tone.frequency = frequency;
+    tone.duration = (duration + 9) / 10;
+    if (ioctl(speaker, SPKRTONE, &tone) != -1) return 1;
+    logSystemError("speaker tone");
+  }
+#endif /* CAN_BEEP */
+  return 0;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  return 0;
+}
+
+int
+stopBeep (void) {
+  return 0;
+}
+
+void
+endBeep (void) {
+#ifdef CAN_BEEP
+  if (speaker != -1) {
+    close(speaker);
+    speaker = -1;
+  }
+#endif /* CAN_BEEP */
+}
diff --git a/Programs/beep_windows.c b/Programs/beep_windows.c
new file mode 100644
index 0000000..b8def6e
--- /dev/null
+++ b/Programs/beep_windows.c
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "beep.h"
+
+int
+canBeep (void) {
+  return 1;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return Beep(frequency, duration);
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  return 0;
+}
+
+int
+stopBeep (void) {
+  return 0;
+}
+
+void
+endBeep (void) {
+}
diff --git a/Programs/beep_wskbd.c b/Programs/beep_wskbd.c
new file mode 100644
index 0000000..a9bb61a
--- /dev/null
+++ b/Programs/beep_wskbd.c
@@ -0,0 +1,75 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <time.h>
+#include <sys/ioctl.h>
+#include <dev/wscons/wsconsio.h>
+
+#include "log.h"
+#include "device.h"
+#include "beep.h"
+
+int
+canBeep (void) {
+  return !!getConsole();
+}
+
+int
+synchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  return 0;
+}
+
+int
+asynchronousBeep (BeepFrequency frequency, BeepDuration duration) {
+  FILE *console = getConsole();
+  if (console) {
+    struct wskbd_bell_data bell;
+    if (!(bell.period = duration)) return 1;
+    bell.pitch = frequency;
+    bell.volume = 100;
+    bell.which = WSKBD_BELL_DOALL;
+    if (ioctl(fileno(console), WSKBDIO_COMPLEXBELL, &bell) != -1) return 1;
+    logSystemError("ioctl WSKBDIO_COMPLEXBELL");
+  }
+  return 0;
+}
+
+int
+startBeep (BeepFrequency frequency) {
+  return 0;
+}
+
+int
+stopBeep (void) {
+  FILE *console = getConsole();
+  if (console) {
+    struct wskbd_bell_data bell;
+    bell.which = WSKBD_BELL_DOVOLUME | WSKBD_BELL_DOPERIOD;
+    bell.volume = 0;
+    bell.period = 0;
+    if (ioctl(fileno(console), WSKBDIO_COMPLEXBELL, &bell) != -1) return 1;
+    logSystemError("ioctl WSKBDIO_COMPLEXBELL");
+  }
+  return 0;
+}
+
+void
+endBeep (void) {
+}
diff --git a/Programs/bell.c b/Programs/bell.c
new file mode 100644
index 0000000..4fc9d1f
--- /dev/null
+++ b/Programs/bell.c
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "bell.h"
+#include "alert.h"
+#include "prefs.h"
+#include "tune_types.h"
+
+int
+setConsoleBellMonitoring (int on) {
+  if (on) return startMonitoringConsoleBell();
+
+  stopMonitoringConsoleBell();
+  return 1;
+}
+
+void
+alertConsoleBell (void) {
+  if (prefs.tuneDevice != tdBeeper) alert(ALERT_CONSOLE_BELL);
+}
diff --git a/Programs/bell_linux.c b/Programs/bell_linux.c
new file mode 100644
index 0000000..b627e7c
--- /dev/null
+++ b/Programs/bell_linux.c
@@ -0,0 +1,101 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "bell.h"
+
+#ifdef HAVE_LINUX_INPUT_H
+#include <linux/input.h>
+
+#include "system_linux.h"
+
+static InputEventMonitor *inputEventMonitor = NULL;
+
+static int
+prepareUinputObject (UinputObject *uinput) {
+  if (!enableUinputEventType(uinput, EV_SND)) return 0;
+  if (!enableUinputSound(uinput, SND_BELL)) return 0;
+  return 1;
+}
+
+static void
+handleInputEvent (const InputEvent *event) {
+  switch (event->type) {
+    case EV_SND: {
+      int value = event->value;
+
+      switch (event->code) {
+        case SND_BELL:
+          if (value) alertConsoleBell();
+          break;
+
+        default:
+          break;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+}
+
+int
+canMonitorConsoleBell (void) {
+  return 1;
+}
+
+int
+startMonitoringConsoleBell (void) {
+  if (!inputEventMonitor) {
+    InputEventMonitor *monitor = newInputEventMonitor(
+      "Console Bell Monitor", prepareUinputObject, handleInputEvent
+    );
+
+    if (!monitor) return 0;
+    inputEventMonitor = monitor;
+  }
+
+  return 1;
+}
+
+void
+stopMonitoringConsoleBell (void) {
+  if (inputEventMonitor) {
+    destroyInputEventMonitor(inputEventMonitor);
+    inputEventMonitor = NULL;
+  }
+}
+
+#else /* HAVE_LINUX_INPUT_H */
+int
+canMonitorConsoleBell (void) {
+  return 0;
+}
+
+int
+startMonitoringConsoleBell (void) {
+  return 0;
+}
+
+void
+stopMonitoringConsoleBell (void) {
+}
+#endif /* HAVE_LINUX_INPUT_H */
diff --git a/Programs/bell_none.c b/Programs/bell_none.c
new file mode 100644
index 0000000..9e9e181
--- /dev/null
+++ b/Programs/bell_none.c
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "bell.h"
+
+int
+canMonitorConsoleBell (void) {
+  return 0;
+}
+
+int
+startMonitoringConsoleBell (void) {
+  return 0;
+}
+
+void
+stopMonitoringConsoleBell (void) {
+}
diff --git a/Programs/blink.c b/Programs/blink.c
new file mode 100644
index 0000000..e591c3d
--- /dev/null
+++ b/Programs/blink.c
@@ -0,0 +1,238 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "parameters.h"
+#include "blink.h"
+#include "prefs.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "update.h"
+#include "core.h"
+
+struct BlinkDescriptorStruct {
+  const char *const name;
+  const unsigned char *const isEnabled;
+  unsigned char *const visibleTime;
+  unsigned char *const invisibleTime;
+
+  unsigned isRequired:1;
+  unsigned isVisible:1;
+  AsyncHandle alarmHandle;
+};
+
+BlinkDescriptor screenCursorBlinkDescriptor = {
+  .name = "Screen Cursor",
+  .isEnabled = &prefs.blinkingScreenCursor,
+  .visibleTime = &prefs.screenCursorVisibleTime,
+  .invisibleTime = &prefs.screenCursorInvisibleTime
+};
+
+BlinkDescriptor attributesUnderlineBlinkDescriptor = {
+  .name = "Attributes Underline",
+  .isEnabled = &prefs.blinkingAttributes,
+  .visibleTime = &prefs.attributesVisibleTime,
+  .invisibleTime = &prefs.attributesInvisibleTime
+};
+
+BlinkDescriptor uppercaseLettersBlinkDescriptor = {
+  .name = "Uppercase Letters",
+  .isEnabled = &prefs.blinkingCapitals,
+  .visibleTime = &prefs.capitalsVisibleTime,
+  .invisibleTime = &prefs.capitalsInvisibleTime
+};
+
+BlinkDescriptor speechCursorBlinkDescriptor = {
+  .name = "Speech Cursor",
+  .isEnabled = &prefs.blinkingSpeechCursor,
+  .visibleTime = &prefs.speechCursorVisibleTime,
+  .invisibleTime = &prefs.speechCursorInvisibleTime
+};
+
+static BlinkDescriptor *const blinkDescriptors[] = {
+  &screenCursorBlinkDescriptor,
+  &attributesUnderlineBlinkDescriptor,
+  &uppercaseLettersBlinkDescriptor,
+  &speechCursorBlinkDescriptor,
+  NULL
+};
+
+static inline int
+toPercentage (int numerator, int denominator) {
+  return (numerator * 100) / denominator;
+}
+
+const char *
+getBlinkName (BlinkDescriptor *blink) {
+  return blink->name;
+}
+
+int
+getBlinkVisibleTime (BlinkDescriptor *blink) {
+  return PREFS2MSECS(*blink->visibleTime);
+}
+
+int
+getBlinkInvisibleTime (BlinkDescriptor *blink) {
+  return PREFS2MSECS(*blink->invisibleTime);
+}
+
+int
+isBlinkEnabled (BlinkDescriptor *blink) {
+  return *blink->isEnabled;
+}
+
+int
+getBlinkPeriod (BlinkDescriptor *blink) {
+  return getBlinkVisibleTime(blink) + getBlinkInvisibleTime(blink);
+}
+
+int
+getBlinkPercentVisible (BlinkDescriptor *blink) {
+  return toPercentage(getBlinkVisibleTime(blink), getBlinkPeriod(blink));
+}
+
+int
+setBlinkProperties (BlinkDescriptor *blink, int period, int percentVisible) {
+  if (period < 1) return 0;
+
+  if (percentVisible < 1) return 0;
+  if (percentVisible > 99) return 0;
+
+  const int minimumTime = SCREEN_UPDATE_SCHEDULE_DELAY;
+  period = MAX(period, (minimumTime * 2));
+
+  int visibleTime;
+  int invisibleTime;
+
+  // let the visible time round toward 50%
+  if (percentVisible < 50) {
+    invisibleTime = (period * (100 - percentVisible)) / 100;
+    visibleTime = period - invisibleTime;
+  } else {
+    visibleTime = (period * percentVisible) / 100;
+    invisibleTime = period - visibleTime;
+  }
+
+  if (visibleTime && (visibleTime < minimumTime)) {
+    visibleTime = minimumTime;
+    invisibleTime = period - visibleTime;
+  } else if (invisibleTime && (invisibleTime < minimumTime)) {
+    invisibleTime = minimumTime;
+    visibleTime = period - invisibleTime;
+  }
+
+  visibleTime = MSECS2PREFS(visibleTime);
+  invisibleTime = MSECS2PREFS(invisibleTime);
+  if ((visibleTime + invisibleTime) > UINT8_MAX) return 0;
+
+  *blink->visibleTime = visibleTime;
+  *blink->invisibleTime = invisibleTime;
+  return 1;
+}
+
+int
+isBlinkVisible (const BlinkDescriptor *blink) {
+  if (!*blink->isEnabled) return 1;
+  return blink->isVisible;
+}
+
+static int
+getBlinkDuration (const BlinkDescriptor *blink) {
+  return PREFS2MSECS(blink->isVisible? *blink->visibleTime: *blink->invisibleTime);
+}
+
+void
+setBlinkState (BlinkDescriptor *blink, int visible) {
+  int changed = visible != blink->isVisible;
+
+  blink->isVisible = visible;
+
+  if (blink->alarmHandle) {
+    asyncResetAlarmIn(blink->alarmHandle, getBlinkDuration(blink));
+    if (changed) scheduleUpdate("blink state set");
+  }
+}
+
+static void setBlinkAlarm (BlinkDescriptor *blink);
+
+ASYNC_ALARM_CALLBACK(handleBlinkAlarm) {
+  BlinkDescriptor *blink = parameters->data;
+
+  asyncDiscardHandle(blink->alarmHandle);
+  blink->alarmHandle = NULL;
+
+  blink->isVisible = !blink->isVisible;
+  setBlinkAlarm(blink);
+  scheduleUpdate("blink state changed");
+}
+
+static void
+setBlinkAlarm (BlinkDescriptor *blink) {
+  asyncNewRelativeAlarm(&blink->alarmHandle, getBlinkDuration(blink), handleBlinkAlarm, blink);
+}
+
+static void
+forEachBlinkDescriptor (void (*handleBlinkDescriptor) (BlinkDescriptor *blink)) {
+  BlinkDescriptor *const *blink = blinkDescriptors;
+
+  while (*blink) handleBlinkDescriptor(*blink++);
+}
+
+static void
+unrequireBlinkDescriptor (BlinkDescriptor *blink) {
+  blink->isRequired = 0;
+}
+
+void
+unrequireAllBlinkDescriptors (void) {
+  forEachBlinkDescriptor(unrequireBlinkDescriptor);
+}
+
+void
+requireBlinkDescriptor (BlinkDescriptor *blink) {
+  blink->isRequired = 1;
+}
+
+static void
+stopBlinkDescriptor (BlinkDescriptor *blink) {
+  if (blink->alarmHandle) {
+    asyncCancelRequest(blink->alarmHandle);
+    blink->alarmHandle = NULL;
+  }
+}
+
+void
+stopAllBlinkDescriptors (void) {
+  forEachBlinkDescriptor(stopBlinkDescriptor);
+}
+
+static void
+resetBlinkDescriptor (BlinkDescriptor *blink) {
+  if (!(*blink->isEnabled && blink->isRequired)) {
+    stopBlinkDescriptor(blink);
+  } else if (!blink->alarmHandle) {
+    setBlinkAlarm(blink);
+  }
+}
+
+void
+resetAllBlinkDescriptors (void) {
+  forEachBlinkDescriptor(resetBlinkDescriptor);
+}
diff --git a/Programs/blink.h b/Programs/blink.h
new file mode 100644
index 0000000..739261a
--- /dev/null
+++ b/Programs/blink.h
@@ -0,0 +1,62 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BLINK
+#define BRLTTY_INCLUDED_BLINK
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct BlinkDescriptorStruct BlinkDescriptor;
+extern BlinkDescriptor screenCursorBlinkDescriptor;
+extern BlinkDescriptor attributesUnderlineBlinkDescriptor;
+extern BlinkDescriptor uppercaseLettersBlinkDescriptor;
+extern BlinkDescriptor speechCursorBlinkDescriptor;
+
+extern int getBlinkPeriod (BlinkDescriptor *blink);
+extern int getBlinkPercentVisible (BlinkDescriptor *blink);
+extern int setBlinkProperties (BlinkDescriptor *blink, int period, int percentVisible);
+
+static inline int setBlinkPeriod (BlinkDescriptor *blink, int period) {
+  return setBlinkProperties(blink, period, getBlinkPercentVisible(blink));
+}
+
+static inline int setBlinkPercentVisible (BlinkDescriptor *blink, int percentVisible) {
+  return setBlinkProperties(blink, getBlinkPeriod(blink), percentVisible);
+}
+
+extern const char *getBlinkName (BlinkDescriptor *blink);
+extern int getBlinkVisibleTime (BlinkDescriptor *blink);
+extern int getBlinkInvisibleTime (BlinkDescriptor *blink);
+extern int isBlinkEnabled (BlinkDescriptor *blink);
+
+extern int isBlinkVisible (const BlinkDescriptor *blink);
+extern void setBlinkState (BlinkDescriptor *blink, int visible);
+
+extern void unrequireAllBlinkDescriptors (void);
+extern void requireBlinkDescriptor (BlinkDescriptor *blink);
+
+extern void resetAllBlinkDescriptors (void);
+extern void stopAllBlinkDescriptors (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BLINK */
diff --git a/Programs/bluetooth.c b/Programs/bluetooth.c
new file mode 100644
index 0000000..6aad957
--- /dev/null
+++ b/Programs/bluetooth.c
@@ -0,0 +1,787 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "parameters.h"
+#include "timing.h"
+#include "async_wait.h"
+#include "parse.h"
+#include "device.h"
+#include "queue.h"
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+
+static int
+bthDiscoverSerialPortChannel (uint8_t *channel, BluetoothConnectionExtension *bcx, int timeout) {
+  static const uint8_t uuid[] = {
+    0X00, 0X00, 0X11, 0X01,
+    0X00, 0X00,
+    0X10, 0X00,
+    0X80, 0X00,
+    0X00, 0X80, 0X5F, 0X9B, 0X34, 0XFB
+  };
+
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO), "discovering serial port channel");
+  int discovered = bthDiscoverChannel(channel, bcx, uuid, sizeof(uuid), timeout);
+
+  if (discovered) {
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "serial port channel discovered: %u", *channel);
+  } else {
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "serial port channel not discovered");
+  }
+
+  return discovered;
+}
+
+static void
+bthLogChannel (uint8_t channel) {
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO), "RFCOMM channel: %u", channel);
+}
+
+typedef struct {
+  uint64_t address;
+  char *name;
+  int error;
+  unsigned paired:1;
+} BluetoothDeviceEntry;
+
+static void
+bthDeallocateDeviceEntry (void *item, void *data) {
+  BluetoothDeviceEntry *entry = item;
+
+  if (entry->name) free(entry->name);
+  free(entry);
+}
+
+static Queue *
+bthCreateDeviceQueue (void *data) {
+  return newQueue(bthDeallocateDeviceEntry, NULL);
+}
+
+static Queue *
+bthGetDeviceQueue (int create) {
+  static Queue *devices = NULL;
+
+  return getProgramQueue(&devices, "bluetooth-device-queue", create,
+                         bthCreateDeviceQueue, NULL);
+}
+
+static int
+bthTestDeviceAddress (const void *item, void *data) {
+  const BluetoothDeviceEntry *device = item;
+  const uint64_t *address = data;
+
+  return device->address == *address;
+}
+
+static BluetoothDeviceEntry *
+bthGetDeviceEntry (uint64_t address, int add) {
+  Queue *devices = bthGetDeviceQueue(add);
+
+  if (devices) {
+    BluetoothDeviceEntry *entry = findItem(devices, bthTestDeviceAddress, &address);
+    if (entry) return entry;
+
+    if (add) {
+      if ((entry = malloc(sizeof(*entry)))) {
+        entry->address = address;
+        entry->name = NULL;
+        entry->error = 0;
+        entry->paired = 0;
+
+        if (enqueueItem(devices, entry)) return entry;
+        free(entry);
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static int
+bthRememberDeviceName (BluetoothDeviceEntry *entry, const char *name) {
+  if (name && *name) {
+    char *copy = strdup(name);
+
+    if (copy) {
+      if (entry->name) free(entry->name);
+      entry->name = copy;
+      return 1;
+    } else {
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+static inline const char *
+bthGetPairedKeyword (int state) {
+  return getFlagKeywordYesNo(state);
+}
+
+static int
+bthRememberDiscoveredDevice (const DiscoveredBluetoothDevice *device, void *data) {
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO),
+             "remember discovered device: "
+             "Addr:%012" PRIX64 " Paired:%s Name:%s",
+             device->address, bthGetPairedKeyword(device->paired), device->name);
+
+  BluetoothDeviceEntry *entry = bthGetDeviceEntry(device->address, 1);
+
+  if (entry) {
+    bthRememberDeviceName(entry, device->name);
+    entry->paired = device->paired;
+  }
+
+  return 0;
+}
+
+static int bluetoothDevicesDiscovered = 0;
+
+static void
+bthDiscoverDevices (void) {
+  if (!bluetoothDevicesDiscovered) {
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "begin device discovery");
+    bthProcessDiscoveredDevices(bthRememberDiscoveredDevice, NULL);
+    bluetoothDevicesDiscovered = 1;
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "end device discovery");
+  }
+}
+
+void
+bthForgetDevices (void) {
+  Queue *devices = bthGetDeviceQueue(0);
+
+  if (devices) deleteElements(devices);
+  bluetoothDevicesDiscovered = 0;
+}
+
+static int
+bthRememberConnectError (uint64_t address, int value) {
+  BluetoothDeviceEntry *entry = bthGetDeviceEntry(address, 1);
+  if (!entry) return 0;
+
+  entry->error = value;
+  return 1;
+}
+
+static int
+bthRecallConnectError (uint64_t address, int *value) {
+  BluetoothDeviceEntry *entry = bthGetDeviceEntry(address, 0);
+  if (!entry) return 0;
+  if (!entry->error) return 0;
+
+  *value = entry->error;
+  return 1;
+}
+
+void
+bthInitializeConnectionRequest (BluetoothConnectionRequest *request) {
+  memset(request, 0, sizeof(*request));
+  request->driver = NULL;
+  request->address = 0;
+  request->timeout = BLUETOOTH_CHANNEL_CONNECT_TIMEOUT;
+  request->channel = 0;
+  request->discover = 0;
+}
+
+typedef enum {
+  BTH_PARM_ADDRESS,
+  BTH_PARM_NAME,
+  BTH_PARM_CHANNEL,
+  BTH_PARM_DISCOVER,
+  BTH_PARM_TIMEOUT
+} BluetoothDeviceParameter;
+
+static const char *const bthDeviceParameterNames[] = {
+  "address",
+  "name",
+  "channel",
+  "discover",
+  "timeout",
+  NULL
+};
+
+static char **
+bthGetDeviceParameters (const char *identifier) {
+  if (!identifier) identifier = "";
+  return getDeviceParameters(bthDeviceParameterNames, identifier);
+}
+
+int
+bthParseAddress (uint64_t *address, const char *string) {
+  const char *character = string;
+  unsigned int counter = BDA_SIZE;
+  char delimiter = 0;
+  *address = 0;
+
+  while (*character) {
+    long unsigned int value;
+
+    {
+      const char *start = character;
+      char *end;
+
+      value = strtoul(character, &end, 0X10);
+      if ((end - start) != 2) break;
+      character = end;
+    }
+
+    if (value > UINT8_MAX) break;
+    *address <<= 8;
+    *address |= value;
+
+    if (!--counter) {
+      if (*character) break;
+      return 1;
+    }
+
+    if (!*character) break;
+
+    if (!delimiter) {
+      delimiter = *character;
+      if ((delimiter != ':') && (delimiter != '-')) break;
+    } else if (*character != delimiter) {
+      break;
+    }
+
+    character += 1;
+  }
+
+  logMessage(LOG_ERR, "invalid Bluetooth device address: %s", string);
+  errno = EINVAL;
+  return 0;
+}
+
+int
+bthParseChannelNumber (uint8_t *channel, const char *string) {
+  if (*string) {
+    unsigned int value;
+
+    if (isUnsignedInteger(&value, string)) {
+      if ((value > 0) && (value < 0X1F)) {
+        *channel = value;
+        return 1;
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "invalid RFCOMM channel number: %s", string);
+  return 0;
+}
+
+STR_BEGIN_FORMATTER(bthFormatAddress, uint64_t address)
+  uint8_t bytes[6];
+  size_t count = ARRAY_COUNT(bytes);
+  unsigned int index = count;
+
+  while (index > 0) {
+    bytes[--index] = address & 0XFF;
+    address >>= 8;
+  }
+
+  while (index < count) {
+    if (index > 0) STR_PRINTF("%c", ':');
+    STR_PRINTF("%02X", bytes[index++]);
+  }
+STR_END_FORMATTER
+
+static const BluetoothNameEntry *
+bthGetNameEntry (const char *name) {
+  if (name && *name) {
+    const BluetoothNameEntry *entry = bluetoothNameTable;
+
+    while (entry->namePrefix) {
+      if (strncmp(name, entry->namePrefix, strlen(entry->namePrefix)) == 0) {
+        return entry;
+      }
+
+      entry += 1;
+    }
+  }
+
+  return NULL;
+}
+
+typedef struct {
+  struct {
+    const char *address;
+    size_t length;
+  } name;
+
+  struct {
+    const char *address;
+    size_t length;
+  } driver;
+} GetDeviceAddressData;
+
+static int
+bthTestDeviceName (const void *item, void *data) {
+  const BluetoothDeviceEntry *device = item;
+  const GetDeviceAddressData *gda = data;
+
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO),
+             "testing device: Addr:%012" PRIX64 " Paired:%s Name:%s",
+             device->address, bthGetPairedKeyword(device->paired), device->name
+  );
+
+  if (!device->paired) {
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "not paired");
+    return 0;
+  }
+
+  if (gda->name.length) {
+    if (strncmp(device->name, gda->name.address, gda->name.length) != 0) {
+      logMessage(LOG_CATEGORY(BLUETOOTH_IO), "ineligible name");
+      return 0;
+    }
+  }
+
+  const BluetoothNameEntry *name = bthGetNameEntry(device->name);
+  if (!name) {
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "unrecognized name");
+    return 0;
+  }
+
+  if (gda->driver.length) {
+    const char *const *code = name->driverCodes;
+
+    while (*code) {
+      if (strncmp(*code, gda->driver.address, gda->driver.length) == 0) {
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "found");
+        return 1;
+      }
+
+      code += 1;
+    }
+
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "ineligible driver");
+    return 0;
+  }
+
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO), "driver not specified");
+  return 0;
+}
+
+static int
+bthGetDeviceAddress (uint64_t *address, char **parameters, const char *driver) {
+  {
+    const char *parameter = parameters[BTH_PARM_ADDRESS];
+
+    if (parameter && *parameter) {
+      return bthParseAddress(address, parameter);
+    }
+  }
+
+  {
+    bthDiscoverDevices();
+    Queue *devices = bthGetDeviceQueue(0);
+
+    if (devices) {
+      const char *name = parameters[BTH_PARM_NAME];
+
+      GetDeviceAddressData gda = {
+        .name = {
+          .address = name,
+          .length = name? strlen(name): 0
+        },
+
+        .driver = {
+          .address = driver,
+          .length = driver? strlen(driver): 0
+        }
+      };
+
+      if (gda.driver.length) {
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "begin device search");
+        const BluetoothDeviceEntry *device = findItem(devices, bthTestDeviceName, &gda);
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "end device search");
+
+        if (device) {
+          *address = device->address;
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+bthProcessTimeoutParameter (BluetoothConnectionRequest *request, const char *parameter) {
+  int seconds;
+
+  if (!parameter) return 1;
+  if (!*parameter) return 1;
+
+  if (isInteger(&seconds, parameter)) {
+    if ((seconds > 0) && (seconds < 60)) {
+      request->timeout = seconds * MSECS_PER_SEC;
+      return 1;
+    }
+  }
+
+  logMessage(LOG_ERR, "invalid Bluetooth connection timeout: %s", parameter);
+  return 0;
+}
+
+static int
+bthProcessChannelParameter (BluetoothConnectionRequest *request, const char *parameter) {
+  uint8_t channel;
+
+  if (!parameter) return 1;
+  if (!*parameter) return 1;
+
+  if (bthParseChannelNumber(&channel, parameter)) {
+    request->channel = channel;
+    request->discover = 0;
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+bthProcessDiscoverParameter (BluetoothConnectionRequest *request, const char *parameter) {
+  unsigned int flag;
+
+  if (!parameter) return 1;
+  if (!*parameter) return 1;
+
+  if (validateYesNo(&flag, parameter)) {
+    request->discover = flag;
+    return 1;
+  }
+
+  logMessage(LOG_ERR, "invalid discover option: %s", parameter);
+  return 0;
+}
+
+int
+bthApplyParameters (BluetoothConnectionRequest *request, const char *identifier) {
+  char **parameters = bthGetDeviceParameters(identifier);
+  if (!parameters) return 0;
+
+  int ok = 1;
+  if (!bthProcessChannelParameter(request, parameters[BTH_PARM_CHANNEL])) ok = 0;
+  if (!bthProcessDiscoverParameter(request, parameters[BTH_PARM_DISCOVER])) ok = 0;
+  if (!bthProcessTimeoutParameter(request, parameters[BTH_PARM_TIMEOUT])) ok = 0;
+  if (!bthGetDeviceAddress(&request->address, parameters, request->driver)) ok = 0;
+
+  deallocateStrings(parameters);
+  return ok;
+}
+
+BluetoothConnection *
+bthOpenConnection (const BluetoothConnectionRequest *request) {
+  BluetoothConnection *connection;
+
+  if ((connection = malloc(sizeof(*connection)))) {
+    memset(connection, 0, sizeof(*connection));
+    connection->address = request->address;
+    connection->channel = request->channel;
+
+    if ((connection->extension = bthNewConnectionExtension(connection->address))) {
+      int alreadyTried = 0;
+
+      {
+        int value;
+
+        if (bthRecallConnectError(connection->address, &value)) {
+          errno = value;
+          alreadyTried = 1;
+        }
+      }
+
+      if (!alreadyTried) {
+        if (request->discover) bthDiscoverSerialPortChannel(&connection->channel, connection->extension, request->timeout);
+        bthLogChannel(connection->channel);
+
+        {
+          TimePeriod period;
+          startTimePeriod(&period, BLUETOOTH_CHANNEL_BUSY_RETRY_TIMEOUT);
+
+          while (1) {
+            if (bthOpenChannel(connection->extension, connection->channel, request->timeout)) {
+              return connection;
+            }
+
+            if (afterTimePeriod(&period, NULL)) break;
+            if (errno != EBUSY) break;
+            asyncWait(BLUETOOTH_CHANNEL_BUSY_RETRY_INTERVAL);
+          }
+        }
+
+        bthRememberConnectError(connection->address, errno);
+      }
+
+      bthReleaseConnectionExtension(connection->extension);
+    }
+
+    free(connection);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+bthCloseConnection (BluetoothConnection *connection) {
+  bthReleaseConnectionExtension(connection->extension);
+  free(connection);
+}
+
+const char *
+bthMakeConnectionIdentifier (BluetoothConnection *connection, char *buffer, size_t size) {
+  size_t length;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s%c", BLUETOOTH_DEVICE_QUALIFIER, PARAMETER_QUALIFIER_CHARACTER);
+
+  {
+    uint64_t address = bthGetAddress(connection);
+    STR_PRINTF("%s%c", bthDeviceParameterNames[BTH_PARM_ADDRESS], PARAMETER_ASSIGNMENT_CHARACTER);
+    STR_FORMAT(bthFormatAddress, address);
+    STR_PRINTF("%c", DEVICE_PARAMETER_SEPARATOR);
+  }
+
+  {
+    uint8_t channel = bthGetChannel(connection);
+
+    if (channel) {
+      STR_PRINTF(
+        "%s%c%u%c",
+        bthDeviceParameterNames[BTH_PARM_CHANNEL],
+        PARAMETER_ASSIGNMENT_CHARACTER,
+        channel,
+        DEVICE_PARAMETER_SEPARATOR
+      );
+    }
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+
+  {
+    char *last = &buffer[length] - 1;
+    if (*last == DEVICE_PARAMETER_SEPARATOR) *last = 0;
+  }
+
+  return buffer;
+}
+
+uint64_t
+bthGetAddress (BluetoothConnection *connection) {
+  return connection->address;
+}
+
+uint8_t
+bthGetChannel (BluetoothConnection *connection) {
+  return connection->channel;
+}
+
+int
+bthAwaitInput (BluetoothConnection *connection, int timeout) {
+  return bthPollInput(connection->extension, timeout);
+}
+
+ssize_t
+bthReadData (
+  BluetoothConnection *connection, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  ssize_t result = bthGetData(connection->extension, buffer, size, initialTimeout, subsequentTimeout);
+
+  if (result > 0) logBytes(LOG_CATEGORY(BLUETOOTH_IO), "input", buffer, result);
+  return result;
+}
+
+ssize_t
+bthWriteData (BluetoothConnection *connection, const void *buffer, size_t size) {
+  if (size > 0) logBytes(LOG_CATEGORY(BLUETOOTH_IO), "output", buffer, size);
+  return bthPutData(connection->extension, buffer, size);
+}
+
+static const char *
+bthGetDeviceName (uint64_t address, int timeout) {
+  bthDiscoverDevices();
+  BluetoothDeviceEntry *entry = bthGetDeviceEntry(address, 1);
+
+  if (entry) {
+    if (!entry->name) {
+      logMessage(LOG_CATEGORY(BLUETOOTH_IO), "obtaining device name");
+
+      if ((entry->name = bthObtainDeviceName(address, timeout))) {
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "device name: %s", entry->name);
+      } else {
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "device name not obtained");
+      }
+    }
+
+    return entry->name;
+  }
+
+  return NULL;
+}
+
+const char *
+bthGetNameOfDevice (BluetoothConnection *connection, int timeout) {
+  return bthGetDeviceName(connection->address, timeout);
+}
+
+const char *
+bthGetNameAtAddress (const char *address, int timeout) {
+  uint64_t bda;
+
+  if (bthParseAddress(&bda, address)) {
+    return bthGetDeviceName(bda, timeout);
+  }
+
+  return NULL;
+}
+
+static struct {
+  const char **table;
+  unsigned int count;
+  unsigned int size;
+} driverCodes = {
+  .table = NULL,
+  .count = 0,
+  .size = 0
+};
+
+static int
+bthFindDriverCode (unsigned int *position, const char *code) {
+  int first = 0;
+  int last = driverCodes.count - 1;
+
+  while (first <= last) {
+    unsigned int current = (first + last) / 2;
+    int relation = strcmp(code, driverCodes.table[current]);
+
+    if (relation < 0) {
+      last = current - 1;
+    } else if (relation > 0) {
+      first = current + 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+static int
+bthEnsureDriverCodeTableSize (void) {
+  if (driverCodes.count < driverCodes.size) return 1;
+
+  unsigned int newSize = driverCodes.size + 0X10;
+  const char **newTable = realloc(driverCodes.table, ARRAY_SIZE(driverCodes.table, newSize));
+
+  if (!newTable) {
+    logMallocError();
+    return 0;
+  }
+
+  driverCodes.table = newTable;
+  driverCodes.size = newSize;
+  return 1;
+}
+
+static int
+bthAddDriverCode (const char *code) {
+  unsigned int position;
+  if (bthFindDriverCode(&position, code)) return 1;
+  if (!bthEnsureDriverCodeTableSize()) return 0;
+
+  memmove(&driverCodes.table[position+1], &driverCodes.table[position],
+          ARRAY_SIZE(driverCodes.table, (driverCodes.count - position)));
+
+  driverCodes.count += 1;
+  driverCodes.table[position] = code;
+  return 1;
+}
+
+static const char *const *
+bthGetAllDriverCodes (void) {
+  if (driverCodes.table) return driverCodes.table;
+  const BluetoothNameEntry *entry = bluetoothNameTable;
+
+  while (entry->namePrefix) {
+    const char *const *code = entry->driverCodes;
+
+    while (*code) {
+      if (!bthAddDriverCode(*code)) goto failure;
+      code += 1;
+    }
+
+    entry += 1;
+  }
+
+  if (bthEnsureDriverCodeTableSize()) {
+    driverCodes.table[driverCodes.count] = NULL;
+    return driverCodes.table;
+  }
+
+failure:
+  if (driverCodes.table) {
+    free(driverCodes.table);
+    memset(&driverCodes, 0, sizeof(driverCodes));
+  }
+
+  static const char *const *noDriverCodes = {NULL};
+  return noDriverCodes;
+}
+
+const char *const *
+bthGetDriverCodes (const char *identifier, int timeout) {
+  const char *const *codes = NULL;
+  char **parameters = bthGetDeviceParameters(identifier);
+
+  if (parameters) {
+    uint64_t address;
+
+    if (bthGetDeviceAddress(&address, parameters, NULL)) {
+      const char *name = bthGetDeviceName(address, timeout);
+      const BluetoothNameEntry *entry = bthGetNameEntry(name);
+      if (entry) codes = entry->driverCodes;
+    }
+
+    deallocateStrings(parameters);
+  }
+
+  if (!codes) codes = bthGetAllDriverCodes();
+  return codes;
+}
+
+int
+isBluetoothDeviceIdentifier (const char **identifier) {
+  return hasQualifier(identifier, BLUETOOTH_DEVICE_QUALIFIER);
+}
diff --git a/Programs/bluetooth_android.c b/Programs/bluetooth_android.c
new file mode 100644
index 0000000..5cd23f5
--- /dev/null
+++ b/Programs/bluetooth_android.c
@@ -0,0 +1,448 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+#include "async_handle.h"
+#include "async_io.h"
+#include "io_misc.h"
+#include "thread.h"
+#include "system_java.h"
+
+static jclass connectionClass = NULL;
+static jmethodID connectionConstructor = 0;
+static jmethodID canDiscoverMethod = 0;
+static jmethodID openMethod = 0;
+static jmethodID closeMethod = 0;
+static jmethodID writeMethod = 0;
+
+static int
+bthGetConnectionClass (JNIEnv *env) {
+  return findJavaClass(
+    env, &connectionClass, JAVA_OBJ_BRLTTY("BluetoothConnection")
+  );
+}
+
+static int
+bthGetConnectionConstructor (JNIEnv *env) {
+  return findJavaConstructor(
+    env, &connectionConstructor, connectionClass,
+    JAVA_SIG_CONSTRUCTOR(
+      JAVA_SIG_LONG // address
+    )
+  );
+}
+
+static int
+bthGetCanDiscoverMethod (JNIEnv *env) {
+  return findJavaInstanceMethod(
+    env, &canDiscoverMethod, connectionClass, "canDiscover",
+    JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+    )
+  );
+}
+
+static int
+bthGetOpenMethod (JNIEnv *env) {
+  return findJavaInstanceMethod(
+    env, &openMethod, connectionClass, "open",
+    JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+      JAVA_SIG_INT // inputPipe
+      JAVA_SIG_INT // channel
+      JAVA_SIG_BOOLEAN // secure
+    )
+  );
+}
+
+static int
+bthGetCloseMethod (JNIEnv *env) {
+  return findJavaInstanceMethod(
+    env, &closeMethod, connectionClass, "close",
+    JAVA_SIG_METHOD(JAVA_SIG_VOID,
+    )
+  );
+}
+
+static int
+bthGetWriteMethod (JNIEnv *env) {
+  return (findJavaInstanceMethod(
+    env, &writeMethod, connectionClass, "write",
+    JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+      JAVA_SIG_ARRAY(JAVA_SIG_BYTE)) // bytes
+    )
+  );
+}
+
+struct BluetoothConnectionExtensionStruct {
+  JNIEnv *env;
+
+  jobject connection;
+  AsyncHandle inputMonitor;
+  int inputPipe[2];
+};
+
+BluetoothConnectionExtension *
+bthNewConnectionExtension (uint64_t bda) {
+  BluetoothConnectionExtension *bcx;
+
+  if ((bcx = malloc(sizeof(*bcx)))) {
+    memset(bcx, 0, sizeof(*bcx));
+
+    bcx->inputPipe[0] = INVALID_FILE_DESCRIPTOR;
+    bcx->inputPipe[1] = INVALID_FILE_DESCRIPTOR;
+
+    if ((bcx->env = getJavaNativeInterface())) {
+      if (bthGetConnectionClass(bcx->env)) {
+        if (bthGetConnectionConstructor(bcx->env)) {
+          jobject localReference = (*bcx->env)->NewObject(bcx->env, connectionClass, connectionConstructor, bda);
+
+          if (!clearJavaException(bcx->env, 1)) {
+            jobject globalReference = (*bcx->env)->NewGlobalRef(bcx->env, localReference);
+
+            (*bcx->env)->DeleteLocalRef(bcx->env, localReference);
+            localReference = NULL;
+
+            if (globalReference) {
+              bcx->connection = globalReference;
+              return bcx;
+            } else {
+              logMallocError();
+              clearJavaException(bcx->env, 0);
+            }
+          }
+        }
+      }
+    }
+
+    free(bcx);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+bthCancelInputMonitor (BluetoothConnectionExtension *bcx) {
+  if (bcx->inputMonitor) {
+    asyncCancelRequest(bcx->inputMonitor);
+    bcx->inputMonitor = NULL;
+  }
+}
+
+void
+bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) {
+  bthCancelInputMonitor(bcx);
+
+  if (bcx->connection) {
+    if (bthGetCloseMethod(bcx->env)) {
+      (*bcx->env)->CallVoidMethod(bcx->env, bcx->connection, closeMethod);
+    }
+
+    (*bcx->env)->DeleteGlobalRef(bcx->env, bcx->connection);
+    clearJavaException(bcx->env, 1);
+  }
+
+  closeFile(&bcx->inputPipe[0]);
+  closeFile(&bcx->inputPipe[1]);
+
+  free(bcx);
+}
+
+typedef struct {
+  BluetoothConnectionExtension *const bcx;
+  uint8_t const channel;
+  int const timeout;
+
+  int error;
+} OpenBluetoothConnectionData;
+
+THREAD_FUNCTION(runOpenBluetoothConnection) {
+  OpenBluetoothConnectionData *obc = argument;
+  JNIEnv *env;
+
+  if ((env = getJavaNativeInterface())) {
+    if (pipe(obc->bcx->inputPipe) != -1) {
+      if (setBlockingIo(obc->bcx->inputPipe[0], 0)) {
+        if (bthGetOpenMethod(env)) {
+          jboolean result = (*env)->CallBooleanMethod(env, obc->bcx->connection, openMethod,
+                                                      obc->bcx->inputPipe[1], obc->channel, JNI_FALSE);
+
+          if (!clearJavaException(env, 1)) {
+            if (result == JNI_TRUE) {
+              closeFile(&obc->bcx->inputPipe[1]);
+              obc->error = 0;
+              goto done;
+            }
+          }
+
+          errno = EIO;
+        }
+      }
+
+      closeFile(&obc->bcx->inputPipe[0]);
+      closeFile(&obc->bcx->inputPipe[1]);
+    } else {
+      logSystemError("pipe");
+    }
+  }
+
+  obc->error = errno;
+done:
+  return NULL;
+}
+
+int
+bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) {
+  OpenBluetoothConnectionData obc = {
+    .bcx = bcx,
+    .channel = channel,
+    .timeout = timeout,
+
+    .error = EIO
+  };
+
+  if (callThreadFunction("bluetooth-open", runOpenBluetoothConnection, &obc, NULL)) {
+    if (!obc.error) return 1;
+    errno = obc.error;
+  }
+
+  return 0;
+}
+
+int
+bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+) {
+  JNIEnv *env = bcx->env;
+
+  if (bthGetCanDiscoverMethod(env)) {
+    jboolean result = (*env)->CallBooleanMethod(env, bcx->connection, canDiscoverMethod);
+
+    if (!clearJavaException(env, 1)) {
+      int yes = result == JNI_TRUE;
+
+      if (yes) {
+        logMessage(LOG_CATEGORY(BLUETOOTH_IO), "can discover serial port channel");
+        *channel = 0;
+      } else {
+        errno = ENOENT;
+      }
+
+      return yes;
+    }
+  }
+
+  errno = EIO;
+  return 0;
+}
+
+int
+bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) {
+  BluetoothConnectionExtension *bcx = connection->extension;
+
+  bthCancelInputMonitor(bcx);
+  if (!callback) return 1;
+  return asyncMonitorFileInput(&bcx->inputMonitor, bcx->inputPipe[0], callback, data);
+}
+
+int
+bthPollInput (BluetoothConnectionExtension *bcx, int timeout) {
+  return awaitFileInput(bcx->inputPipe[0], timeout);
+}
+
+ssize_t
+bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return readFile(bcx->inputPipe[0], buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) {
+  if (bthGetWriteMethod(bcx->env)) {
+    jbyteArray bytes = (*bcx->env)->NewByteArray(bcx->env, size);
+
+    if (bytes) {
+      jboolean result;
+
+      (*bcx->env)->SetByteArrayRegion(bcx->env, bytes, 0, size, buffer);
+      result = (*bcx->env)->CallBooleanMethod(bcx->env, bcx->connection, writeMethod, bytes);
+      (*bcx->env)->DeleteLocalRef(bcx->env, bytes);
+
+      if (!clearJavaException(bcx->env, 1)) {
+        if (result == JNI_TRUE) {
+          return size;
+        }
+      }
+
+      errno = EIO;
+    } else {
+      errno = ENOMEM;
+    }
+  } else {
+    errno = ENOSYS;
+  }
+
+  logSystemError("Bluetooth write");
+  return -1;
+}
+
+char *
+bthObtainDeviceName (uint64_t bda, int timeout) {
+  char *name = NULL;
+  JNIEnv *env = getJavaNativeInterface();
+
+  if (env) {
+    if (bthGetConnectionClass(env)) {
+      static jmethodID method = 0;
+
+      if (findJavaStaticMethod(env, &method, connectionClass, "getName",
+                               JAVA_SIG_METHOD(JAVA_SIG_STRING,
+                                               JAVA_SIG_LONG // address
+                                              ))) {
+        jstring jName = (*env)->CallStaticObjectMethod(env, connectionClass, method, bda);
+
+        if (jName) {
+          const char *cName = (*env)->GetStringUTFChars(env, jName, NULL);
+
+          if (cName) {
+            if (!(name = strdup(cName))) logMallocError();
+            (*env)->ReleaseStringUTFChars(env, jName, cName);
+          } else {
+            logMallocError();
+            clearJavaException(env, 0);
+          }
+
+          (*env)->DeleteLocalRef(env, jName);
+        } else {
+          logMallocError();
+          clearJavaException(env, 0);
+        }
+      }
+    }
+  }
+
+  return name;
+}
+
+static jmethodID getPairedDeviceCountMethod = 0;
+static jmethodID getPairedDeviceAddressMethod = 0;
+static jmethodID getPairedDeviceNameMethod = 0;
+
+static int
+bthGetPairedDeviceCountMethod (JNIEnv *env) {
+  return findJavaStaticMethod(
+    env, &getPairedDeviceCountMethod, connectionClass, "getPairedDeviceCount",
+    JAVA_SIG_METHOD(JAVA_SIG_INT,
+    )
+  );
+}
+
+static int
+bthGetPairedDeviceAddressMethod (JNIEnv *env) {
+  return findJavaStaticMethod(
+    env, &getPairedDeviceAddressMethod, connectionClass, "getPairedDeviceAddress",
+    JAVA_SIG_METHOD(JAVA_SIG_STRING,
+      JAVA_SIG_INT // index
+    )
+  );
+}
+
+static int
+bthGetPairedDeviceNameMethod (JNIEnv *env) {
+  return findJavaStaticMethod(
+    env, &getPairedDeviceNameMethod, connectionClass, "getPairedDeviceName",
+    JAVA_SIG_METHOD(JAVA_SIG_STRING,
+      JAVA_SIG_INT // index
+    )
+  );
+}
+
+static JNIEnv *
+bthGetPairedDeviceMethods (void) {
+  JNIEnv *env = getJavaNativeInterface();
+
+  if (env) {
+    if (bthGetConnectionClass(env)) {
+      if (bthGetPairedDeviceCountMethod(env)) {
+        if (bthGetPairedDeviceAddressMethod(env)) {
+          if (bthGetPairedDeviceNameMethod(env)) {
+            return env;
+          }
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+void
+bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+) {
+  JNIEnv *env = bthGetPairedDeviceMethods();
+
+  if (env) {
+    jint count = (*env)->CallStaticIntMethod(env, connectionClass, getPairedDeviceCountMethod);
+
+    for (jint index=0; index<count; index+=1) {
+      int found = 0;
+      jstring jAddress = (*env)->CallStaticObjectMethod(env, connectionClass, getPairedDeviceAddressMethod, index);
+
+      if (jAddress) {
+        const char *cAddress = (*env)->GetStringUTFChars(env, jAddress, NULL);
+
+        if (cAddress) {
+          uint64_t address;
+
+          if (bthParseAddress(&address, cAddress)) {
+            jstring jName = (*env)->CallStaticObjectMethod(env, connectionClass, getPairedDeviceNameMethod, index);
+            const char *cName = jName? (*env)->GetStringUTFChars(env, jName, NULL): NULL;
+
+            const DiscoveredBluetoothDevice device = {
+              .address = address,
+              .name = cName,
+              .paired = 1
+            };
+
+            if (testDevice(&device, data)) found = 1;
+            if (cName) (*env)->ReleaseStringUTFChars(env, jName, cName);
+            if (jName) (*env)->DeleteLocalRef(env, jName);
+          }
+
+          (*env)->ReleaseStringUTFChars(env, jAddress, cAddress);
+        }
+
+        (*env)->DeleteLocalRef(env, jAddress);
+      }
+
+      if (found) break;
+    }
+  }
+}
diff --git a/Programs/bluetooth_darwin.c b/Programs/bluetooth_darwin.c
new file mode 100644
index 0000000..91ad36e
--- /dev/null
+++ b/Programs/bluetooth_darwin.c
@@ -0,0 +1,373 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#import <IOBluetooth/objc/IOBluetoothDevice.h>
+#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
+#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>
+#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+#include "system_darwin.h"
+
+@interface ServiceQueryResult: AsynchronousResult
+- (void) sdpQueryComplete
+  : (IOBluetoothDevice *) device
+  status: (IOReturn) status;
+@end
+
+@interface BluetoothConnectionDelegate: AsynchronousTask
+@property (assign) BluetoothConnectionExtension *bluetoothConnectionExtension;
+@end
+
+@interface RfcommChannelDelegate: BluetoothConnectionDelegate
+- (void) rfcommChannelData
+  : (IOBluetoothRFCOMMChannel *) rfcommChannel
+  data: (void *) dataPointer
+  length: (size_t) dataLength;
+
+- (void) rfcommChannelClosed
+  : (IOBluetoothRFCOMMChannel*) rfcommChannel;
+
+- (IOReturn) run;
+@end
+
+struct BluetoothConnectionExtensionStruct {
+  BluetoothDeviceAddress bluetoothAddress;
+  IOBluetoothDevice *bluetoothDevice;
+
+  IOBluetoothRFCOMMChannel *rfcommChannel;
+  RfcommChannelDelegate *rfcommDelegate;
+
+  int inputPipe[2];
+};
+
+static void
+bthSetError (IOReturn result, const char *action) {
+  setDarwinSystemError(result);
+  logSystemError(action);
+}
+
+static void
+bthInitializeRfcommChannel (BluetoothConnectionExtension *bcx) {
+  bcx->rfcommChannel = nil;
+}
+
+static void
+bthDestroyRfcommChannel (BluetoothConnectionExtension *bcx) {
+  if (bcx->rfcommChannel) {
+    [bcx->rfcommChannel closeChannel];
+    [bcx->rfcommChannel release];
+    bthInitializeRfcommChannel(bcx);
+  }
+}
+
+static void
+bthInitializeRfcommDelegate (BluetoothConnectionExtension *bcx) {
+  bcx->rfcommDelegate = nil;
+}
+
+static void
+bthDestroyRfcommDelegate (BluetoothConnectionExtension *bcx) {
+  if (bcx->rfcommDelegate) {
+    [bcx->rfcommDelegate stop];
+    [bcx->rfcommDelegate wait:5];
+    [bcx->rfcommDelegate release];
+    bthInitializeRfcommDelegate(bcx);
+  }
+}
+
+static void
+bthInitializeBluetoothDevice (BluetoothConnectionExtension *bcx) {
+  bcx->bluetoothDevice = nil;
+}
+
+static void
+bthDestroyBluetoothDevice (BluetoothConnectionExtension *bcx) {
+  if (bcx->bluetoothDevice) {
+    [bcx->bluetoothDevice closeConnection];
+    [bcx->bluetoothDevice release];
+    bthInitializeBluetoothDevice(bcx);
+  }
+}
+
+static void
+bthInitializeInputPipe (BluetoothConnectionExtension *bcx) {
+  bcx->inputPipe[0] = bcx->inputPipe[1] = INVALID_FILE_DESCRIPTOR;
+}
+
+static void
+bthDestroyInputPipe (BluetoothConnectionExtension *bcx) {
+  int *fileDescriptor = bcx->inputPipe;
+  const int *end = fileDescriptor + ARRAY_COUNT(bcx->inputPipe);
+
+  while (fileDescriptor < end) {
+    closeFile(fileDescriptor);
+    fileDescriptor += 1;
+  }
+}
+
+static void
+bthMakeAddress (BluetoothDeviceAddress *address, uint64_t bda) {
+  unsigned int index = sizeof(address->data);
+
+  while (index > 0) {
+    address->data[--index] = bda & 0XFF;
+    bda >>= 8;
+  }
+}
+
+BluetoothConnectionExtension *
+bthNewConnectionExtension (uint64_t bda) {
+  BluetoothConnectionExtension *bcx;
+
+  if ((bcx = malloc(sizeof(*bcx)))) {
+    memset(bcx, 0, sizeof(*bcx));
+    bthInitializeInputPipe(bcx);
+    bthMakeAddress(&bcx->bluetoothAddress, bda);
+
+    if ((bcx->bluetoothDevice = [IOBluetoothDevice deviceWithAddress:&bcx->bluetoothAddress])) {
+      [bcx->bluetoothDevice retain];
+
+      return bcx;
+    }
+
+    free(bcx);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) {
+  bthDestroyRfcommChannel(bcx);
+  bthDestroyRfcommDelegate(bcx);
+  bthDestroyBluetoothDevice(bcx);
+  bthDestroyInputPipe(bcx);
+  free(bcx);
+}
+
+int
+bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) {
+  IOReturn result;
+
+  if (pipe(bcx->inputPipe) != -1) {
+    if (setBlockingIo(bcx->inputPipe[0], 0)) {
+      if ((bcx->rfcommDelegate = [RfcommChannelDelegate new])) {
+        bcx->rfcommDelegate.bluetoothConnectionExtension = bcx;
+
+        if ((result = [bcx->bluetoothDevice openRFCOMMChannelSync:&bcx->rfcommChannel withChannelID:channel delegate:nil]) == kIOReturnSuccess) {
+          if ([bcx->rfcommDelegate start]) {
+            return 1;
+          }
+
+          bthDestroyRfcommChannel(bcx);
+        } else {
+          bthSetError(result, "RFCOMM channel open");
+        }
+
+        bthDestroyRfcommDelegate(bcx);
+      }
+    }
+
+    bthDestroyInputPipe(bcx);
+  } else {
+    logSystemError("pipe");
+  }
+
+  return 0;
+}
+
+static int
+bthPerformServiceQuery (BluetoothConnectionExtension *bcx) {
+  int ok = 0;
+  IOReturn result;
+  ServiceQueryResult *target = [ServiceQueryResult new];
+
+  if (target) {
+    if ((result = [bcx->bluetoothDevice performSDPQuery:target]) == kIOReturnSuccess) {
+      if ([target wait:10]) {
+        if ((result = target.finalStatus) == kIOReturnSuccess) {
+          ok = 1;
+        } else {
+          bthSetError(result, "service discovery response");
+        }
+      }
+    } else {
+      bthSetError(result, "service discovery request");
+    }
+
+    [target release];
+  }
+
+  return ok;
+}
+
+int
+bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+) {
+  IOReturn result;
+
+  if (bthPerformServiceQuery(bcx)) {
+    IOBluetoothSDPUUID *uuid = [IOBluetoothSDPUUID uuidWithBytes:uuidBytes length:uuidLength];
+
+    if (uuid) {
+      IOBluetoothSDPServiceRecord *record = [bcx->bluetoothDevice getServiceRecordForUUID:uuid];
+
+      if (record) {
+        if ((result = [record getRFCOMMChannelID:channel]) == kIOReturnSuccess) {
+          return 1;
+        } else {
+          bthSetError(result, "RFCOMM channel lookup");
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+int
+bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+bthPollInput (BluetoothConnectionExtension *bcx, int timeout) {
+  return awaitFileInput(bcx->inputPipe[0], timeout);
+}
+
+ssize_t
+bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return readFile(bcx->inputPipe[0], buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) {
+  IOReturn result = [bcx->rfcommChannel writeSync:(void *)buffer length:size];
+
+  if (result == kIOReturnSuccess) return size;
+  bthSetError(result, "RFCOMM channel write");
+  return -1;
+}
+
+char *
+bthObtainDeviceName (uint64_t bda, int timeout) {
+  IOReturn result;
+  BluetoothDeviceAddress address;
+
+  bthMakeAddress(&address, bda);
+
+  {
+    IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddress:&address];
+
+    if (device != nil) {
+      if ((result = [device remoteNameRequest:nil]) == kIOReturnSuccess) {
+        NSString *nsName = device.name;
+
+        if (nsName != nil) {
+          const char *utf8Name = [nsName UTF8String];
+
+          if (utf8Name != NULL) {
+            char *name = strdup(utf8Name);
+
+            if (name != NULL) {
+              return name;
+            }
+          }
+        }
+      } else {
+        bthSetError(result, "device name query");
+      }
+
+      [device closeConnection];
+    }
+  }
+
+  return NULL;
+}
+
+@implementation ServiceQueryResult
+- (void) sdpQueryComplete
+  : (IOBluetoothDevice *) device
+  status: (IOReturn) status
+  {
+    [self setStatus:status];
+  }
+@end
+
+@implementation BluetoothConnectionDelegate
+@synthesize bluetoothConnectionExtension;
+@end
+
+@implementation RfcommChannelDelegate
+- (void) rfcommChannelData
+  : (IOBluetoothRFCOMMChannel *) rfcommChannel
+  data: (void *) dataPointer
+  length: (size_t) dataLength
+  {
+    writeFile(self.bluetoothConnectionExtension->inputPipe[1], dataPointer, dataLength);
+  }
+
+- (void) rfcommChannelClosed
+  : (IOBluetoothRFCOMMChannel*) rfcommChannel
+  {
+    logMessage(LOG_NOTICE, "RFCOMM channel closed");
+  }
+
+- (IOReturn) run
+  {
+    IOReturn result;
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "RFCOMM channel delegate started");
+
+    {
+      BluetoothConnectionExtension *bcx = self.bluetoothConnectionExtension;
+
+      if ((result = [bcx->rfcommChannel setDelegate:self]) == kIOReturnSuccess) {
+        CFRunLoopRun();
+        result = kIOReturnSuccess;
+      } else {
+        bthSetError(result, "RFCOMM channel delegate set");
+      }
+    }
+
+    logMessage(LOG_CATEGORY(BLUETOOTH_IO), "RFCOMM channel delegate finished");
+    return result;
+  }
+@end
+
+void
+bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+) {
+}
diff --git a/Programs/bluetooth_internal.h b/Programs/bluetooth_internal.h
new file mode 100644
index 0000000..f0b02a5
--- /dev/null
+++ b/Programs/bluetooth_internal.h
@@ -0,0 +1,83 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BLUETOOTH_INTERNAL
+#define BRLTTY_INCLUDED_BLUETOOTH_INTERNAL
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define BDA_SIZE 6
+
+extern char *bthObtainDeviceName (uint64_t bda, int timeout);
+
+typedef struct BluetoothConnectionExtensionStruct BluetoothConnectionExtension;
+
+extern BluetoothConnectionExtension *bthNewConnectionExtension (uint64_t bda);
+extern void bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx);
+
+extern int bthPollInput (BluetoothConnectionExtension *bcx, int timeout);
+
+extern ssize_t bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size);
+
+extern int bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+);
+
+extern int bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout);
+
+struct BluetoothConnectionStruct {
+  uint64_t address;
+  uint8_t channel;
+  BluetoothConnectionExtension *extension;
+};
+
+typedef struct {
+  const char *namePrefix;
+  const char *const *driverCodes;
+} BluetoothNameEntry;
+
+extern const BluetoothNameEntry bluetoothNameTable[];
+
+typedef struct {
+  const char *name;
+  uint64_t address;
+  unsigned paired:1;
+} DiscoveredBluetoothDevice;
+
+typedef int DiscoveredBluetoothDeviceTester (
+  const DiscoveredBluetoothDevice *device, void *data
+);
+
+extern void bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BLUETOOTH_INTERNAL */
diff --git a/Programs/bluetooth_linux.c b/Programs/bluetooth_linux.c
new file mode 100644
index 0000000..2b24626
--- /dev/null
+++ b/Programs/bluetooth_linux.c
@@ -0,0 +1,792 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/rfcomm.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+#include "async_handle.h"
+#include "async_io.h"
+#include "io_misc.h"
+#include "timing.h"
+
+struct BluetoothConnectionExtensionStruct {
+  SocketDescriptor socketDescriptor;
+  struct sockaddr_rc localAddress;
+  struct sockaddr_rc remoteAddress;
+  AsyncHandle inputMonitor;
+};
+
+typedef union {
+  unsigned char hciEvent[HCI_MAX_EVENT_SIZE];
+
+  struct {
+    unsigned char type;
+
+    union {
+      struct {
+        hci_event_hdr header;
+
+        union {
+          evt_remote_name_req_complete rn;
+          evt_cmd_complete cc;
+          evt_cmd_status cs;
+        } data;
+      } PACKED hciEvent;
+    } data;
+  } PACKED fields;
+} BluetoothPacket;
+
+static void
+bthMakeAddress (bdaddr_t *address, uint64_t bda) {
+  unsigned int index;
+
+  for (index=0; index<BDA_SIZE; index+=1) {
+    address->b[index] = bda & 0XFF;
+    bda >>= 8;
+  }
+}
+
+BluetoothConnectionExtension *
+bthNewConnectionExtension (uint64_t bda) {
+  BluetoothConnectionExtension *bcx;
+
+  if ((bcx = malloc(sizeof(*bcx)))) {
+    memset(bcx, 0, sizeof(*bcx));
+
+    bcx->localAddress.rc_family = AF_BLUETOOTH;
+    bcx->localAddress.rc_channel = 0;
+    bacpy(&bcx->localAddress.rc_bdaddr, BDADDR_ANY); /* Any HCI. No support for explicit
+                                                      * interface specification yet.
+                                                      */
+
+    bcx->remoteAddress.rc_family = AF_BLUETOOTH;
+    bcx->remoteAddress.rc_channel = 0;
+    bthMakeAddress(&bcx->remoteAddress.rc_bdaddr, bda);
+
+    bcx->socketDescriptor = INVALID_SOCKET_DESCRIPTOR;
+    return bcx;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+bthCancelInputMonitor (BluetoothConnectionExtension *bcx) {
+  if (bcx->inputMonitor) {
+    asyncCancelRequest(bcx->inputMonitor);
+    bcx->inputMonitor = NULL;
+  }
+}
+
+void
+bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) {
+  bthCancelInputMonitor(bcx);
+  closeSocket(&bcx->socketDescriptor);
+  free(bcx);
+}
+
+static int
+bthGetConnectLogLevel (int error) {
+  switch (error) {
+    case EHOSTUNREACH:
+    case EHOSTDOWN:
+      return LOG_CATEGORY(BLUETOOTH_IO);
+
+    default:
+      return LOG_ERR;
+  }
+}
+
+int
+bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) {
+  bcx->remoteAddress.rc_channel = channel;
+
+  if ((bcx->socketDescriptor = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) != -1) {
+    setCloseOnExec(bcx->socketDescriptor, 1);
+
+    if (bind(bcx->socketDescriptor, (struct sockaddr *)&bcx->localAddress, sizeof(bcx->localAddress)) != -1) {
+      if (setBlockingIo(bcx->socketDescriptor, 0)) {
+        int connectResult = LINUX_BLUETOOTH_CHANNEL_CONNECT_ASYNCHRONOUS?
+                              connectSocket(bcx->socketDescriptor,
+                                            (struct sockaddr *)&bcx->remoteAddress,
+                                            sizeof(bcx->remoteAddress),
+                                            timeout):
+                              connect(bcx->socketDescriptor,
+                                      (struct sockaddr *)&bcx->remoteAddress,
+                                      sizeof(bcx->remoteAddress));
+
+        if (connectResult != -1) return 1;
+        logSystemProblem(bthGetConnectLogLevel(errno), "RFCOMM connect");
+      }
+    } else {
+      logSystemError("RFCOMM bind");
+    }
+
+    setSocketNoLinger(bcx->socketDescriptor);
+    close(bcx->socketDescriptor);
+    bcx->socketDescriptor = INVALID_SOCKET_DESCRIPTOR;
+  } else {
+    logSystemError("RFCOMM socket");
+  }
+
+  return 0;
+}
+
+static int
+bthFindChannel (uint8_t *channel, sdp_record_t *record) {
+  int foundChannel = 0;
+  int stopSearching = 0;
+  sdp_list_t *protocolsList;
+
+  if (!(sdp_get_access_protos(record, &protocolsList))) {
+    sdp_list_t *protocolsElement = protocolsList;
+
+    while (protocolsElement) {
+      sdp_list_t *protocolList = (sdp_list_t *)protocolsElement->data;
+      sdp_list_t *protocolElement = protocolList;
+
+      while (protocolElement) {
+        sdp_data_t *dataList = (sdp_data_t *)protocolElement->data;
+        sdp_data_t *dataElement = dataList;
+        int uuidProtocol = 0;
+
+        while (dataElement) {
+          if (SDP_IS_UUID(dataElement->dtd)) {
+            uuidProtocol = sdp_uuid_to_proto(&dataElement->val.uuid);
+          } else if (dataElement->dtd == SDP_UINT8) {
+            if (uuidProtocol == RFCOMM_UUID) {
+              *channel = dataElement->val.uint8;
+              foundChannel = 1;
+              stopSearching = 1;
+            }
+          }
+
+          if (stopSearching) break;
+          dataElement = dataElement->next;
+        }
+
+        if (stopSearching) break;
+        protocolElement = protocolElement->next;
+      }
+
+      sdp_list_free(protocolList, NULL);
+      if (stopSearching) break;
+      protocolsElement = protocolsElement->next;
+    }
+
+    sdp_list_free(protocolsList, NULL);
+  } else {
+    logSystemError("sdp_get_access_protos");
+  }
+
+  return foundChannel;
+}
+
+static SocketDescriptor
+bthNewL2capConnection (const bdaddr_t *address, int timeout) {
+  SocketDescriptor socketDescriptor = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+
+  if (socketDescriptor != -1) {
+    setCloseOnExec(socketDescriptor, 1);
+
+    if (setBlockingIo(socketDescriptor, 0)) {
+      struct sockaddr_l2 socketAddress = {
+        .l2_family = AF_BLUETOOTH,
+        .l2_bdaddr = *address,
+        .l2_psm = htobs(SDP_PSM)
+      };
+
+      int connectResult = connectSocket(socketDescriptor,
+                                        (struct sockaddr *)&socketAddress,
+                                        sizeof(socketAddress),
+                                        timeout);
+
+      if (connectResult != -1) return socketDescriptor;
+      logSystemProblem(bthGetConnectLogLevel(errno), "L2CAP connect");
+    }
+
+    setSocketNoLinger(socketDescriptor);
+    close(socketDescriptor);
+  } else {
+    logSystemError("L2CAP socket");
+  }
+
+  return INVALID_SOCKET_DESCRIPTOR;
+}
+
+typedef struct {
+  sdp_session_t *session;
+  int *found;
+  uint8_t *channel;
+} BluetoothChannelDiscoveryData;
+
+static void
+bthHandleChannelDiscoveryResponse (
+  uint8_t type, uint16_t status,
+  uint8_t *response, size_t size,
+  void *data
+) {
+  BluetoothChannelDiscoveryData *bcd = data;
+
+  switch (status) {
+    case 0:
+      switch (type) {
+        case SDP_SVC_SEARCH_ATTR_RSP: {
+          uint8_t *nextByte = response;
+          int bytesLeft = size;
+
+          uint8_t dtd = 0;
+          int dataLeft = 0;
+          int headerLength = sdp_extract_seqtype(nextByte, bytesLeft, &dtd, &dataLeft);
+
+          if (headerLength > 0) {
+            nextByte += headerLength;
+            bytesLeft -= headerLength;
+
+            while (dataLeft > 0) {
+              int stop = 0;
+              int recordLength = 0;
+              sdp_record_t *record = sdp_extract_pdu(nextByte, bytesLeft, &recordLength);
+
+              if (record) {
+                if (bthFindChannel(bcd->channel, record)) {
+                  *bcd->found = 1;
+                  stop = 1;
+                }
+
+                nextByte += recordLength;
+                bytesLeft -= recordLength;
+                dataLeft -= recordLength;
+
+                sdp_record_free(record);
+              } else {
+                logSystemError("sdp_extract_pdu");
+                stop = 1;
+              }
+
+              if (stop) break;
+            }
+          }
+
+          break;
+        }
+
+        default:
+          logMessage(LOG_ERR, "unexpected channel discovery response type: %u", type);
+          break;
+      }
+      break;
+
+    case 0XFFFF:
+      errno = sdp_get_error(bcd->session);
+      if (errno < 0) errno = EINVAL;
+      logSystemError("channel discovery response");
+      break;
+
+    default:
+      logMessage(LOG_ERR, "unexpected channel discovery response status: %u", status);
+      break;
+  }
+}
+
+int
+bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+) {
+  int foundChannel = 0;
+
+  uuid_t uuid;
+  sdp_list_t *searchList;
+
+  sdp_uuid128_create(&uuid, uuidBytes);
+  searchList = sdp_list_append(NULL, &uuid);
+
+  if (searchList) {
+    uint32_t attributesRange = 0X0000FFFF;
+    sdp_list_t *attributesList = sdp_list_append(NULL, &attributesRange);
+
+    if (attributesList) {
+      if (LINUX_BLUETOOTH_CHANNEL_DISCOVER_ASYNCHRONOUS) {
+        SocketDescriptor l2capSocket;
+        TimePeriod period;
+        startTimePeriod(&period, timeout);
+
+        if ((l2capSocket = bthNewL2capConnection(&bcx->remoteAddress.rc_bdaddr, timeout)) != INVALID_SOCKET_DESCRIPTOR) {
+          sdp_session_t *session = sdp_create(l2capSocket, 0);
+
+          if (session) {
+            BluetoothChannelDiscoveryData bcd = {
+              .session = session,
+              .found = &foundChannel,
+              .channel = channel
+            };
+
+            if (sdp_set_notify(session, bthHandleChannelDiscoveryResponse, &bcd) != -1) {
+              int queryStatus = sdp_service_search_attr_async(session, searchList,
+                                                              SDP_ATTR_REQ_RANGE, attributesList);
+
+              if (!queryStatus) {
+                long int elapsed;
+
+                while (!afterTimePeriod(&period, &elapsed)) {
+                  if (!awaitSocketInput(l2capSocket, (timeout - elapsed))) break;
+                  if (sdp_process(session) == -1) break;
+                }
+              } else {
+                logSystemError("sdp_service_search_attr_async");
+              }
+            } else {
+              logSystemError("sdp_set_notify");
+            }
+
+            sdp_close(session);
+          } else {
+            logSystemError("sdp_create");
+          }
+
+          close(l2capSocket);
+        }
+      } else {
+        sdp_session_t *session = sdp_connect(BDADDR_ANY, &bcx->remoteAddress.rc_bdaddr, SDP_RETRY_IF_BUSY);
+
+        if (session) {
+          sdp_list_t *recordList = NULL;
+          int queryStatus = sdp_service_search_attr_req(session, searchList,
+                                                        SDP_ATTR_REQ_RANGE, attributesList,
+                                                        &recordList);
+
+          if (!queryStatus) {
+            int stopSearching = 0;
+            sdp_list_t *recordElement = recordList;
+
+            while (recordElement) {
+              sdp_record_t *record = (sdp_record_t *)recordElement->data;
+
+              if (record) {
+                if (bthFindChannel(channel, record)) {
+                  foundChannel = 1;
+                  stopSearching = 1;
+                }
+
+                sdp_record_free(record);
+              } else {
+                logMallocError();
+                stopSearching = 1;
+              }
+
+              if (stopSearching) break;
+              recordElement = recordElement->next;
+            }
+
+            sdp_list_free(recordList, NULL);
+          } else {
+            logSystemError("sdp_service_search_attr_req");
+          }
+
+          sdp_close(session);
+        } else {
+          logSystemError("sdp_connect");
+        }
+      }
+
+      sdp_list_free(attributesList, NULL);
+    } else {
+      logMallocError();
+    }
+
+    sdp_list_free(searchList, NULL);
+  } else {
+    logMallocError();
+  }
+
+  return foundChannel;
+}
+
+int
+bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) {
+  BluetoothConnectionExtension *bcx = connection->extension;
+
+  bthCancelInputMonitor(bcx);
+  if (!callback) return 1;
+  return asyncMonitorSocketInput(&bcx->inputMonitor, bcx->socketDescriptor, callback, data);
+}
+
+int
+bthPollInput (BluetoothConnectionExtension *bcx, int timeout) {
+  return awaitSocketInput(bcx->socketDescriptor, timeout);
+}
+
+ssize_t
+bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return readSocket(bcx->socketDescriptor, buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) {
+  return writeSocket(bcx->socketDescriptor, buffer, size);
+}
+
+char *
+bthObtainDeviceName (uint64_t bda, int timeout) {
+  char *name = NULL;
+  int device = hci_get_route(NULL);
+
+  if (device >= 0) {
+    SocketDescriptor socketDescriptor = hci_open_dev(device);
+
+    if (socketDescriptor >= 0) {
+      int obtained = 0;
+      bdaddr_t address;
+      char buffer[HCI_MAX_NAME_LENGTH];
+
+      bthMakeAddress(&address, bda);
+      memset(buffer, 0, sizeof(buffer));
+
+      if (LINUX_BLUETOOTH_NAME_OBTAIN_ASYNCHRONOUS) {
+        if (setBlockingIo(socketDescriptor, 0)) {
+          struct hci_filter oldFilter;
+          socklen_t oldLength = sizeof(oldFilter);
+
+          if (getsockopt(socketDescriptor, SOL_HCI, HCI_FILTER, &oldFilter, &oldLength) != -1) {
+            uint16_t ogf = OGF_LINK_CTL;
+            uint16_t ocf = OCF_REMOTE_NAME_REQ;
+            uint16_t opcode = htobs(cmd_opcode_pack(ogf, ocf));
+            struct hci_filter newFilter;
+
+            hci_filter_clear(&newFilter);
+            hci_filter_set_ptype(HCI_EVENT_PKT, &newFilter);
+            hci_filter_set_event(EVT_CMD_STATUS, &newFilter);
+            hci_filter_set_event(EVT_CMD_COMPLETE, &newFilter);
+            hci_filter_set_event(EVT_REMOTE_NAME_REQ_COMPLETE, &newFilter);
+            hci_filter_set_opcode(opcode, &newFilter);
+
+            if (setsockopt(socketDescriptor, SOL_HCI, HCI_FILTER, &newFilter, sizeof(newFilter)) != -1) {
+              remote_name_req_cp parameters;
+
+              memset(&parameters, 0, sizeof(parameters));
+              bacpy(&parameters.bdaddr, &address);
+              parameters.pscan_rep_mode = 0X02;
+              parameters.clock_offset = 0;
+
+              if (hci_send_cmd(socketDescriptor, ogf, ocf, sizeof(parameters), &parameters) != -1) {
+                long int elapsed = 0;
+                TimePeriod period;
+                startTimePeriod(&period, timeout);
+
+                while (awaitSocketInput(socketDescriptor, (timeout - elapsed))) {
+                  enum {
+                    UNEXPECTED,
+                    HANDLED,
+                    DONE
+                  } state = UNEXPECTED;
+
+                  BluetoothPacket packet;
+                  int result = read(socketDescriptor, &packet, sizeof(packet));
+
+                  if (result == -1) {
+                    if (errno == EAGAIN) continue;
+                    if (errno == EINTR) continue;
+
+                    logSystemError("read");
+                    break;
+                  }
+
+                  switch (packet.fields.type) {
+                    case HCI_EVENT_PKT: {
+                      hci_event_hdr *header = &packet.fields.data.hciEvent.header;
+
+                      switch (header->evt) {
+                        case EVT_REMOTE_NAME_REQ_COMPLETE: {
+                          evt_remote_name_req_complete *rn = &packet.fields.data.hciEvent.data.rn;
+
+                          if (bacmp(&rn->bdaddr, &address) == 0) {
+                            state = DONE;
+
+                            if (!rn->status) {
+                              size_t length = header->plen;
+
+                              length -= rn->name - (unsigned char *)rn;
+                              length = MIN(length, sizeof(rn->name));
+                              length = MIN(length, sizeof(buffer)-1);
+
+                              memcpy(buffer, rn->name, length);
+                              buffer[length] = 0;
+                              obtained = 1;
+                            }
+                          }
+
+                          break;
+                        }
+
+                        case EVT_CMD_STATUS: {
+                          evt_cmd_status *cs = &packet.fields.data.hciEvent.data.cs;
+
+                          if (cs->opcode == opcode) {
+                            state = HANDLED;
+
+                            if (cs->status) {
+                            }
+                          }
+
+                          break;
+                        }
+
+                        default:
+                          logMessage(LOG_CATEGORY(BLUETOOTH_IO), "unexpected HCI event type: %u", header->evt);
+                          break;
+                      }
+
+                      break;
+                    }
+
+                    default:
+                      logMessage(LOG_CATEGORY(BLUETOOTH_IO), "unexpected Bluetooth packet type: %u", packet.fields.type);
+                      break;
+                  }
+
+                  if (state == DONE) break;
+                  if (state == UNEXPECTED) logBytes(LOG_WARNING, "unexpected Bluetooth packet", &packet, result);
+                  if (afterTimePeriod(&period, &elapsed)) break;
+                }
+              } else {
+                logSystemError("hci_send_cmd");
+              }
+
+              if (setsockopt(socketDescriptor, SOL_HCI, HCI_FILTER, &oldFilter, oldLength) == -1) {
+                logSystemError("setsockopt[SOL_HCI,HCI_FILTER]");
+              }
+            } else {
+              logSystemError("setsockopt[SOL_HCI,HCI_FILTER]");
+            }
+          } else {
+            logSystemError("getsockopt[SOL_HCI,HCI_FILTER]");
+          }
+        }
+      } else {
+        int result = hci_read_remote_name(socketDescriptor, &address, sizeof(buffer), buffer, timeout);
+
+        if (result >= 0) {
+          obtained = 1;
+        } else {
+          logSystemError("hci_read_remote_name");
+        }
+      }
+
+      if (obtained) {
+        if (!(name = strdup(buffer))) {
+          logMallocError();
+        }
+      }
+
+      close(socketDescriptor);
+    } else {
+      logSystemError("hci_open_dev");
+    }
+  } else {
+    logSystemError("hci_get_route");
+  }
+
+  return name;
+}
+
+#ifdef HAVE_PKG_DBUS
+#include <dbus/dbus.h>
+
+static void
+logDBusError (const char *action, const DBusError *error) {
+  const char *message = error->message;
+  int length = strlen(message);
+
+  while (length > 0) {
+    char character = message[--length];
+    if (character != '\n') break;
+  }
+
+  logMessage(LOG_CATEGORY(BLUETOOTH_IO),
+             "DBus error: %s: %s: %.*s",
+             action, error->name, length, message);
+}
+#endif /* HAVE_PKG_DBUS */
+
+void
+bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+) {
+#ifdef HAVE_PKG_DBUS
+  int found = 0;
+
+  DBusError error;
+  DBusConnection *bus;
+ 
+  dbus_error_init(&error);
+  bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+
+  if (dbus_error_is_set(&error)) {
+    logDBusError("get bus", &error);
+    dbus_error_free(&error);
+  } else if (!bus) {
+    logMallocError();
+  } else {
+    DBusMessage *getManagedObjects = dbus_message_new_method_call(
+      "org.bluez", "/",
+      "org.freedesktop.DBus.ObjectManager",
+      "GetManagedObjects"
+    );
+
+    if (!getManagedObjects) {
+      logMallocError();
+    } else {
+      DBusMessage *managedObjects = dbus_connection_send_with_reply_and_block(
+        bus, getManagedObjects, -1, &error
+      );
+
+      dbus_message_unref(getManagedObjects);
+      getManagedObjects = NULL;
+
+      if (dbus_error_is_set(&error)) {
+        logDBusError("send message", &error);
+        dbus_error_free(&error);
+      } else if (!managedObjects) {
+        logMallocError();
+      } else {
+        DBusMessageIter args;
+
+        if (dbus_message_iter_init(managedObjects, &args) == FALSE) {
+          logMessage(LOG_ERR, "reply has no arguments");
+        } else if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) {
+          logMessage(LOG_ERR, "expecting an array");
+        } else {
+          DBusMessageIter objects;
+          dbus_message_iter_recurse(&args, &objects);
+
+          while (dbus_message_iter_get_arg_type(&objects) == DBUS_TYPE_DICT_ENTRY) {
+            DBusMessageIter object;
+            dbus_message_iter_recurse(&objects, &object);
+
+            if (dbus_message_iter_get_arg_type(&object) == DBUS_TYPE_OBJECT_PATH) {
+              // DBus path to talk to this object
+              dbus_message_iter_next(&object);
+
+              if (dbus_message_iter_get_arg_type(&object) == DBUS_TYPE_ARRAY) {
+                DBusMessageIter interfaces;
+                dbus_message_iter_recurse(&object, &interfaces);
+
+                while (dbus_message_iter_get_arg_type(&interfaces) == DBUS_TYPE_DICT_ENTRY) {
+                  DBusMessageIter interface;
+                  dbus_message_iter_recurse(&interfaces, &interface);
+
+                  if (dbus_message_iter_get_arg_type(&interface) == DBUS_TYPE_STRING) {
+                    const char *interfaceName;
+                    dbus_message_iter_get_basic(&interface, &interfaceName);
+
+                    if (strcmp(interfaceName, "org.bluez.Device1") == 0) {
+                      dbus_message_iter_next(&interface);
+
+                      if (dbus_message_iter_get_arg_type(&interface) == DBUS_TYPE_ARRAY) {
+                        DiscoveredBluetoothDevice device;
+                        memset(&device, 0, sizeof(device));
+
+                        DBusMessageIter properties;
+                        dbus_message_iter_recurse(&interface, &properties);
+
+                        while (DBUS_TYPE_DICT_ENTRY == dbus_message_iter_get_arg_type(&properties)) {
+                          DBusMessageIter property;
+                          dbus_message_iter_recurse(&properties, &property);
+
+                          if (dbus_message_iter_get_arg_type(&property) == DBUS_TYPE_STRING) {
+                            const char *propertyName;
+
+                            dbus_message_iter_get_basic(&property, &propertyName);
+                            dbus_message_iter_next(&property);
+
+                            if (dbus_message_iter_get_arg_type(&property) == DBUS_TYPE_VARIANT) {
+                              DBusMessageIter variant;
+                              dbus_message_iter_recurse(&property, &variant);
+
+                              if ((strcmp(propertyName, "Address") == 0) &&
+                                  (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_STRING)) {
+                                const char *address;
+                                dbus_message_iter_get_basic(&variant, &address);
+                                bthParseAddress(&device.address, address);
+                              } else if ((strcmp(propertyName, "Name") == 0) &&
+                                         (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_STRING)) {
+                                const char *name;
+                                dbus_message_iter_get_basic(&variant, &name);
+                                device.name = name;
+                              } else if ((strcmp(propertyName, "Paired") == 0) &&
+                                         (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_BOOLEAN)) {
+                                dbus_bool_t paired;
+                                dbus_message_iter_get_basic(&variant, &paired);
+                                device.paired = paired == TRUE;
+                              }
+                            }
+                          }
+
+                          dbus_message_iter_next(&properties);
+                        }
+
+                        if (device.address) {
+                          if (testDevice(&device, data)) {
+                            found = 1;
+                          }
+                        }
+                      }
+                    }
+                  }
+
+                  if (found) break;
+                  dbus_message_iter_next(&interfaces);
+                }
+              }
+            }
+
+            if (found) break;
+            dbus_message_iter_next(&objects);
+          }
+        }
+
+        dbus_message_unref(managedObjects);
+        managedObjects = NULL;
+      }
+    }
+
+    dbus_connection_unref(bus);
+    bus = NULL;
+  }
+#endif /* HAVE_PKG_DBUS */
+}
diff --git a/Programs/bluetooth_names.c b/Programs/bluetooth_names.c
new file mode 100644
index 0000000..d851b2c
--- /dev/null
+++ b/Programs/bluetooth_names.c
@@ -0,0 +1,172 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "bluetooth_internal.h"
+
+#define BLUETOOTH_NAME_ENTRY(name,...) { \
+  .namePrefix = name, \
+  .driverCodes = NULL_TERMINATED_STRING_ARRAY(__VA_ARGS__) \
+}
+
+const BluetoothNameEntry bluetoothNameTable[] = {
+  // HandyTech: Actilino
+  BLUETOOTH_NAME_ENTRY("Actilino ALO", "ht"),
+
+  // HandyTech: Activator
+  BLUETOOTH_NAME_ENTRY("Activator AC4", "ht"),
+
+  // HandyTech: Active Braille
+  BLUETOOTH_NAME_ENTRY("Active Braille AB", "ht"),
+
+  // HandyTech: Active Star
+  BLUETOOTH_NAME_ENTRY("Active Star AS", "ht"),
+
+  // Alva: Basic Controller (6nn)
+  BLUETOOTH_NAME_ENTRY("ALVA BC", "al"),
+
+  // HumanWare: APH Chameleon
+  BLUETOOTH_NAME_ENTRY("APH Chameleon", "hw"),
+
+  // HumanWare: APH Mantis
+  BLUETOOTH_NAME_ENTRY("APH Mantis", "hw"),
+
+  // HandyTech: Basic Braille
+  BLUETOOTH_NAME_ENTRY("Basic Braille BB", "ht"),
+
+  // HandyTech: Basic Braille Plus
+  BLUETOOTH_NAME_ENTRY("Basic Braille Plus BP", "ht"),
+
+  // Baum: Conny
+  BLUETOOTH_NAME_ENTRY("BAUM Conny", "bm"),
+
+  // Baum: Pocket Vario
+  BLUETOOTH_NAME_ENTRY("Baum PocketVario", "bm"),
+
+  // Baum: Super Vario
+  BLUETOOTH_NAME_ENTRY("Baum SuperVario", "bm"),
+
+  // Baum: Super Vario
+  BLUETOOTH_NAME_ENTRY("Baum SVario", "bm"),
+
+  // Baum: Braille Connect
+  BLUETOOTH_NAME_ENTRY("BrailleConnect", "bm"),
+
+  // HIMS: Braille Edge
+  BLUETOOTH_NAME_ENTRY("BrailleEDGE", "hm"),
+
+  // Inceptor: Braille Me
+  BLUETOOTH_NAME_ENTRY("BrailleMe", "ic"),
+
+  // KGS: Braille Memo Pocket
+  BLUETOOTH_NAME_ENTRY("BMpk", "mm"),
+
+  // KGS: Braille Memo Smart
+  BLUETOOTH_NAME_ENTRY("BMsmart", "mm"),
+
+  // KGS: Braille Memo 32
+  BLUETOOTH_NAME_ENTRY("BM32", "mm"),
+
+  // HumanWare: Braille Note Touch
+  BLUETOOTH_NAME_ENTRY("BrailleNote Touch", "hw"),
+
+  // HIMS: Braille Sense
+  BLUETOOTH_NAME_ENTRY("BrailleSense", "hm"),
+
+  // HandyTech: Braille Star
+  BLUETOOTH_NAME_ENTRY("Braille Star", "ht"),
+
+  // Papenmeier
+  BLUETOOTH_NAME_ENTRY("Braillex", "pm"),
+
+  // HumanWare: Brailliant BI
+  BLUETOOTH_NAME_ENTRY("Brailliant BI", "hw"),
+
+  // HumanWare: Brailliant BI 14
+  BLUETOOTH_NAME_ENTRY("Brailliant 14", "hw"),
+
+  // HumanWare: Brailliant B 80
+  BLUETOOTH_NAME_ENTRY("Brailliant 80", "hw"),
+
+  // HandyTech: Braillino
+  BLUETOOTH_NAME_ENTRY("Braillino BL", "ht"),
+
+  // National Braille Press: B2G
+  BLUETOOTH_NAME_ENTRY("B2G", "bm"),
+
+  // Baum: Conny
+  BLUETOOTH_NAME_ENTRY("Conny", "bm"),
+
+  // DotPad
+  BLUETOOTH_NAME_ENTRY("DotPad", "dp"),
+
+  // HandyTech: Easy Braille
+  BLUETOOTH_NAME_ENTRY("Easy Braille EBR", "ht"),
+
+  // Alva: EL12
+  // Harpo: Braille Pen
+  BLUETOOTH_NAME_ENTRY("EL12-", "al", "vo"),
+
+  // EuroBraille
+  BLUETOOTH_NAME_ENTRY("Esys-", "eu"),
+
+  // Freedom Scientific: Focus
+  BLUETOOTH_NAME_ENTRY("Focus", "fs"),
+
+  // HumanWare: BrailleOne
+  BLUETOOTH_NAME_ENTRY("Humanware BrailleOne", "hw"),
+
+  // HumanWare: Brailliant
+  BLUETOOTH_NAME_ENTRY("HWG Brailliant", "bm"),
+
+  // MDV
+  BLUETOOTH_NAME_ENTRY("MB248", "md"),
+
+  // NLS eReader: HumanWare
+  BLUETOOTH_NAME_ENTRY("NLS eReader H", "hw"),
+
+  // NLS eReader: Zoomax
+  BLUETOOTH_NAME_ENTRY("NLS eReader Z", "bm"),
+
+  // American Printing House: Orbit Reader
+  BLUETOOTH_NAME_ENTRY("Orbit Reader", "bm"),
+
+  // Baum: Pronto!
+  BLUETOOTH_NAME_ENTRY("Pronto!", "bm"),
+
+  // American Printing House: Refreshabraille
+  BLUETOOTH_NAME_ENTRY("Refreshabraille", "bm"),
+
+  // HIMS: Smart Beetle
+  BLUETOOTH_NAME_ENTRY("SmartBeetle", "hm"),
+
+  // Baum: Super Vario
+  BLUETOOTH_NAME_ENTRY("SuperVario", "bm"),
+
+  // Seika: Note Taker
+  BLUETOOTH_NAME_ENTRY("TSM", "sk"),
+
+  // Baum: Vario Connect
+  BLUETOOTH_NAME_ENTRY("VarioConnect", "bm"),
+
+  // Baum: Vario Ultra
+  BLUETOOTH_NAME_ENTRY("VarioUltra", "bm"),
+
+  { .namePrefix = NULL }
+};
diff --git a/Programs/bluetooth_none.c b/Programs/bluetooth_none.c
new file mode 100644
index 0000000..72ff9e5
--- /dev/null
+++ b/Programs/bluetooth_none.c
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+#include "log.h"
+
+BluetoothConnectionExtension *
+bthNewConnectionExtension (uint64_t bda) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+void
+bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) {
+}
+
+int
+bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+BluetoothConnectionExtension *
+bthConnect (uint64_t bda, uint8_t channel, int discover, int timeout) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+void
+bthDisconnect (BluetoothConnectionExtension *bcx) {
+}
+
+int
+bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+bthPollInput (BluetoothConnectionExtension *bcx, int timeout) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+ssize_t
+bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+char *
+bthObtainDeviceName (uint64_t bda, int timeout) {
+  errno = ENOSYS;
+  return NULL;
+}
+
+void
+bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+) {
+}
diff --git a/Programs/bluetooth_windows.c b/Programs/bluetooth_windows.c
new file mode 100644
index 0000000..7124355
--- /dev/null
+++ b/Programs/bluetooth_windows.c
@@ -0,0 +1,479 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#define _WIN32_WINNT _WIN32_WINNT_VISTA
+#define __USE_W32_SOCKETS
+
+#pragma pack(push,2)
+#include <winsock2.h>
+#include <ws2bth.h>
+#include <nspapi.h>
+#pragma pack(pop)
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#if defined(AF_BTH)
+#ifndef PF_BTH
+#define PF_BTH AF_BTH
+#endif /* PF_BTH */
+
+#elif defined(PF_BTH)
+#ifndef AF_BTH
+#define AF_BTH PF_BTH
+#endif /* AF_BTH */
+
+#else /* bluetooth address/protocol family definitions */
+#define AF_BTH 32
+#define PF_BTH AF_BTH
+#warning using hard-coded value for Bluetooth socket address and protocol family constants
+#endif /* bluetooth address/protocol family definitions */
+
+#ifndef NS_BTH
+#define NS_BTH 16
+#warning using hard-coded value for Bluetooth namespace constant
+#endif /* NS_BTH */
+
+#if defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#define IOCTLSOCKET_ARGUMENT_TYPE unsigned int
+#else /* ioctlcontrol argument type */
+#define IOCTLSOCKET_ARGUMENT_TYPE u_long
+#endif /* ioctlcontrol argument type */
+
+#include "log.h"
+#include "timing.h"
+#include "bitfield.h"
+#include "io_bluetooth.h"
+#include "bluetooth_internal.h"
+
+struct BluetoothConnectionExtensionStruct {
+  SOCKET socket;
+  SOCKADDR_BTH local;
+  SOCKADDR_BTH remote;
+};
+
+static void
+bthSetErrno (DWORD error, const char *action, const DWORD *exceptions) {
+  if (exceptions) {
+    const DWORD *exception = exceptions;
+
+    while (*exception != NO_ERROR) {
+      if (error == *exception) goto isException;
+      exception += 1;
+    }
+  }
+
+  logWindowsError(error, action);
+isException:
+  setErrno(error);
+}
+
+static DWORD
+bthSocketError (const char *action, const DWORD *exceptions) {
+  DWORD error = WSAGetLastError();
+
+  bthSetErrno(error, action, exceptions);
+  return error;
+}
+
+static int
+bthStartSockets (void) {
+  WSADATA wsa;
+  int result = WSAStartup(MAKEWORD(2, 2), &wsa);
+
+  if (!result) return 1;
+  bthSetErrno(result, "WSA startup", NULL);
+  return 0;
+}
+
+BluetoothConnectionExtension *
+bthNewConnectionExtension (uint64_t bda) {
+  BluetoothConnectionExtension *bcx;
+
+  if ((bcx = malloc(sizeof(*bcx)))) {
+    memset(bcx, 0, sizeof(*bcx));
+
+    bcx->local.addressFamily = AF_BTH;
+    bcx->local.btAddr = BTH_ADDR_NULL;
+    bcx->local.port = 0;
+
+    bcx->remote.addressFamily = AF_BTH;
+    bcx->remote.btAddr = bda;
+    bcx->remote.port = 0;
+
+    bcx->socket = INVALID_SOCKET;
+    return bcx;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+bthReleaseConnectionExtension (BluetoothConnectionExtension *bcx) {
+  if (bcx->socket != INVALID_SOCKET) {
+    closesocket(bcx->socket);
+    bcx->socket = INVALID_SOCKET;
+  }
+
+  free(bcx);
+}
+
+int
+bthOpenChannel (BluetoothConnectionExtension *bcx, uint8_t channel, int timeout) {
+  bcx->remote.port = channel;
+
+  if (bthStartSockets()) {
+    if ((bcx->socket = socket(PF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM)) != INVALID_SOCKET) {
+      if (bind(bcx->socket, (SOCKADDR *)&bcx->local, sizeof(bcx->local)) != SOCKET_ERROR) {
+        if (connect(bcx->socket, (SOCKADDR *)&bcx->remote, sizeof(bcx->remote)) != SOCKET_ERROR) {
+          IOCTLSOCKET_ARGUMENT_TYPE nonblocking = 1;
+
+          if (ioctlsocket(bcx->socket, FIONBIO, &nonblocking) != SOCKET_ERROR) {
+            return 1;
+          } else {
+            bthSocketError("RFCOMM nonblocking", NULL);
+          }
+        } else {
+          static const DWORD exceptions[] = {
+            ERROR_HOST_DOWN,
+            ERROR_HOST_UNREACHABLE,
+            NO_ERROR
+          };
+
+          bthSocketError("RFCOMM connect", exceptions);
+        }
+      } else {
+        bthSocketError("RFCOMM bind", NULL);
+      }
+
+      closesocket(bcx->socket);
+      bcx->socket = INVALID_SOCKET;
+    } else {
+      bthSocketError("RFCOMM socket", NULL);
+    }
+  }
+
+  return 0;
+}
+
+typedef union {
+  double ensureCorrectAlignment;
+  unsigned char ensureAdequateSize[0X1000];
+  WSAQUERYSET querySet;
+} BluetoothServiceLookupResult;
+
+static int
+bthPerformServiceLookup (
+  BluetoothServiceLookupResult *result,
+  ULONGLONG address, GUID *guid,
+  DWORD beginFlags, DWORD nextFlags
+) {
+  int found = 0;
+
+  if (bthStartSockets()) {
+    SOCKADDR_BTH socketAddress = {
+      .addressFamily = AF_BTH,
+      .btAddr = address
+    };
+
+    char addressString[0X100];
+    DWORD addressLength = sizeof(addressString);
+
+    if (WSAAddressToString((SOCKADDR *)&socketAddress, sizeof(socketAddress),
+                            NULL,
+                            addressString, &addressLength) != SOCKET_ERROR) {
+      HANDLE handle;
+
+      CSADDR_INFO csa[] = {
+        {
+          .RemoteAddr = {
+            .lpSockaddr = (SOCKADDR *)&socketAddress,
+            .iSockaddrLength = sizeof(socketAddress)
+          }
+        }
+      };
+
+      WSAQUERYSET restrictions = {
+        .dwNameSpace = NS_BTH,
+        .lpszContext = addressString,
+        .lpcsaBuffer = csa,
+        .dwNumberOfCsAddrs = ARRAY_COUNT(csa),
+        .lpServiceClassId = guid,
+        .dwSize = sizeof(restrictions)
+      };
+
+      if (WSALookupServiceBegin(&restrictions, (LUP_FLUSHCACHE | beginFlags), &handle) != SOCKET_ERROR) {
+        DWORD resultLength = sizeof(*result);
+
+        if (WSALookupServiceNext(handle, nextFlags, &resultLength, &result->querySet) != SOCKET_ERROR) {
+          found = 1;
+        } else {
+          static const DWORD exceptions[] = {
+#ifdef WSA_E_NO_MORE
+            WSA_E_NO_MORE,
+#endif /* WSA_E_NO_MORE */
+
+#ifdef WSAENOMORE
+            WSAENOMORE,
+#endif /* WSAENOMORE */
+
+            NO_ERROR
+          };
+
+          bthSocketError("WSALookupServiceNext", exceptions);
+        }
+
+        if (WSALookupServiceEnd(handle) == SOCKET_ERROR) {
+          bthSocketError("WSALookupServiceEnd", NULL);
+        }
+      } else {
+        static const DWORD exceptions[] = {
+          WSASERVICE_NOT_FOUND,
+          NO_ERROR
+        };
+
+        bthSocketError("WSALookupServiceBegin", exceptions);
+      }
+    } else {
+      bthSocketError("WSAAddressToString", NULL);
+    }
+  }
+
+  return found;
+}
+
+int
+bthDiscoverChannel (
+  uint8_t *channel, BluetoothConnectionExtension *bcx,
+  const void *uuidBytes, size_t uuidLength,
+  int timeout
+) {
+  GUID guid;
+
+  memcpy(&guid, uuidBytes, sizeof(guid));
+  putNativeEndian32((uint32_t *)&guid.Data1, getBigEndian32(guid.Data1));
+  putNativeEndian16((uint16_t *)&guid.Data2, getBigEndian16(guid.Data2));
+  putNativeEndian16((uint16_t *)&guid.Data3, getBigEndian16(guid.Data3));
+
+#if 1
+  {
+    BluetoothServiceLookupResult result;
+
+    if (bthPerformServiceLookup(&result, bcx->remote.btAddr, &guid, 0, LUP_RETURN_ADDR)) {
+      SOCKADDR_BTH *bth = (SOCKADDR_BTH *)result.querySet.lpcsaBuffer[0].RemoteAddr.lpSockaddr;
+
+      if (bth->port) {
+        *channel = bth->port;
+        return 1;
+      }
+    }
+
+    return 0;
+  }
+#else /* discover channel */
+  memcpy(&bcx->remote.serviceClassId, &guid, sizeof(bcx->remote.serviceClassId));
+  *channel = 0;
+  return 1;
+#endif /* discover channel */
+}
+
+int
+bthMonitorInput (BluetoothConnection *connection, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+bthPollInput (BluetoothConnectionExtension *bcx, int timeout) {
+  fd_set input;
+  TIMEVAL time;
+
+  FD_ZERO(&input);
+  FD_SET(bcx->socket, &input);
+
+  memset(&time, 0, sizeof(time));
+  time.tv_sec = timeout / MSECS_PER_SEC;
+  time.tv_usec = (timeout % MSECS_PER_SEC) * USECS_PER_MSEC;
+
+  switch (select(bcx->socket+1, &input, NULL, NULL, &time)) {
+    case SOCKET_ERROR:
+      bthSocketError("RFCOMM wait", NULL);
+      break;
+
+    case 0:
+      errno = EAGAIN;
+      break;
+
+    default:
+      return 1;
+  }
+
+  return 0;
+}
+
+ssize_t
+bthGetData (
+  BluetoothConnectionExtension *bcx, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  size_t count = size;
+
+  if (count) {
+    char *to = buffer;
+
+    while (1) {
+      int result = recv(bcx->socket, to, count, 0);
+
+      if (result != SOCKET_ERROR) {
+        to += result;
+        if (!(count -= result)) break;
+      } else {
+        static const DWORD exceptions[] = {
+          WSAEWOULDBLOCK,
+          NO_ERROR
+        };
+
+        DWORD error = bthSocketError("RFCOMM read", exceptions);
+        if (error != WSAEWOULDBLOCK) return -1;
+      }
+
+      {
+        int timeout = (to == buffer)? initialTimeout: subsequentTimeout;
+
+        if (!timeout) break;
+        if (!bthPollInput(bcx, timeout)) return -1;
+      }
+    }
+  }
+
+  if (size == count) errno = EAGAIN;
+  return size - count;
+}
+
+ssize_t
+bthPutData (BluetoothConnectionExtension *bcx, const void *buffer, size_t size) {
+  const char *from = buffer;
+  size_t count = size;
+
+  while (count) {
+    int result = send(bcx->socket, from, count, 0);
+
+    if (result == SOCKET_ERROR) {
+      bthSocketError("RFCOMM write", NULL);
+      return -1;
+    }
+
+    from += result;
+    count -= result;
+  }
+
+  return size;
+}
+
+char *
+bthObtainDeviceName (uint64_t bda, int timeout) {
+  BluetoothServiceLookupResult result;
+
+  if (bthPerformServiceLookup(&result, bda, NULL, LUP_CONTAINERS, LUP_RETURN_NAME)) {
+    char *name = strdup(result.querySet.lpszServiceInstanceName);
+
+    if (name) {
+      return name;
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+void
+bthProcessDiscoveredDevices (
+  DiscoveredBluetoothDeviceTester *testDevice, void *data
+) {
+  if (bthStartSockets()) {
+    HANDLE handle;
+
+    WSAQUERYSET restrictions = {
+      .dwNameSpace = NS_BTH,
+      .dwSize = sizeof(restrictions)
+    };
+
+    if (WSALookupServiceBegin(&restrictions, LUP_CONTAINERS, &handle) != SOCKET_ERROR) {
+      while (1) {
+        BluetoothServiceLookupResult result;
+        DWORD resultLength = sizeof(result);
+
+        if (WSALookupServiceNext(handle,
+                                 (LUP_RETURN_ADDR | LUP_RETURN_NAME | LUP_RETURN_BLOB),
+                                 &resultLength, &result.querySet) == SOCKET_ERROR) {
+          static const DWORD exceptions[] = {
+#ifdef WSA_E_NO_MORE
+            WSA_E_NO_MORE,
+#endif /* WSA_E_NO_MORE */
+
+#ifdef WSAENOMORE
+            WSAENOMORE,
+#endif /* WSAENOMORE */
+
+            NO_ERROR
+          };
+
+          bthSocketError("WSALookupServiceNext", exceptions);
+          break;
+        }
+
+        if (result.querySet.dwNumberOfCsAddrs == 1) {
+          DiscoveredBluetoothDevice device;
+          memset(&device, 0, sizeof(device));
+
+          {
+            BTH_ADDR address = ((SOCKADDR_BTH *)result.querySet.lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
+            device.address = ((uint64_t)GET_NAP(address) << 0X20) | (uint64_t)GET_SAP(address);
+          }
+
+          {
+            char *name = result.querySet.lpszServiceInstanceName;
+            if (name && *name) device.name = name;
+          }
+
+          {
+            BTH_DEVICE_INFO *info = (BTH_DEVICE_INFO *)result.querySet.lpBlob->pBlobData;
+            device.paired = !!(info->flags & BDIF_PAIRED);
+          }
+
+          if (testDevice(&device, data)) break;
+        }
+      }
+
+      if (WSALookupServiceEnd(handle) == SOCKET_ERROR) {
+        bthSocketError("WSALookupServiceEnd", NULL);
+      }
+    } else {
+      static const DWORD exceptions[] = {
+        WSASERVICE_NOT_FOUND,
+        NO_ERROR
+      };
+
+      bthSocketError("WSALookupServiceBegin", exceptions);
+    }
+  }
+}
diff --git a/Programs/brl.auto.h b/Programs/brl.auto.h
new file mode 100644
index 0000000..75873ff
--- /dev/null
+++ b/Programs/brl.auto.h
@@ -0,0 +1,24 @@
+extern const BrailleDriver brl_driver_al;
+extern const BrailleDriver brl_driver_bm;
+extern const BrailleDriver brl_driver_eu;
+extern const BrailleDriver brl_driver_fs;
+extern const BrailleDriver brl_driver_hid;
+extern const BrailleDriver brl_driver_hm;
+extern const BrailleDriver brl_driver_ht;
+extern const BrailleDriver brl_driver_hw;
+extern const BrailleDriver brl_driver_pm;
+extern const BrailleDriver brl_driver_sk;
+extern const BrailleDriver brl_driver_vo;
+static const DriverEntry driverTable[] = {
+    {&brl_driver_al, &brl_driver_al.definition},
+    {&brl_driver_bm, &brl_driver_bm.definition},
+    {&brl_driver_eu, &brl_driver_eu.definition},
+    {&brl_driver_fs, &brl_driver_fs.definition},
+    {&brl_driver_hid, &brl_driver_hid.definition},
+    {&brl_driver_hm, &brl_driver_hm.definition},
+    {&brl_driver_ht, &brl_driver_ht.definition},
+    {&brl_driver_hw, &brl_driver_hw.definition},
+    {&brl_driver_pm, &brl_driver_pm.definition},
+    {&brl_driver_sk, &brl_driver_sk.definition},
+    {&brl_driver_vo, &brl_driver_vo.definition},
+    {NULL, NULL}};
diff --git a/Programs/brl.c b/Programs/brl.c
new file mode 100644
index 0000000..d4e13e8
--- /dev/null
+++ b/Programs/brl.c
@@ -0,0 +1,349 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "charset.h"
+#include "unicode.h"
+#include "brl.h"
+#include "ttb.h"
+#include "ktb.h"
+#include "queue.h"
+#include "async_handle.h"
+
+void
+constructBrailleDisplay (BrailleDisplay *brl) {
+  brl->data = NULL;
+
+  brl->refreshBrailleDisplay = NULL;
+  brl->refreshBrailleRow = NULL;
+
+  brl->setBrailleFirmness = NULL;
+  brl->setTouchSensitivity = NULL;
+  brl->setAutorepeatProperties = NULL;
+
+  brl->textColumns = 0;
+  brl->textRows = 1;
+  brl->statusColumns = 0;
+  brl->statusRows = 0;
+  brl->cellSize = 8;
+
+  brl->keyBindings = "all";
+  brl->keyNames = NULL;
+  brl->keyTable = NULL;
+
+  brl->gioEndpoint = NULL;
+  brl->writeDelay = 0;
+
+  brl->buffer = NULL;
+  brl->bufferResized = NULL;
+
+  brl->rowDescriptors.array = NULL;
+  brl->rowDescriptors.size = 0;
+
+  brl->cursor = BRL_NO_CURSOR;
+  brl->quality = 0;
+
+  brl->noDisplay = 0;
+  brl->hasFailed = 0;
+  brl->isOffline = 0;
+  brl->isSuspended = 0;
+  brl->isCoreBuffer = 0;
+  brl->resizeRequired = 0;
+  brl->hideCursor = 0;
+
+  brl->acknowledgements.messages = NULL;
+  brl->acknowledgements.alarm = NULL;
+  brl->acknowledgements.missing.timeout = BRAILLE_MESSAGE_ACKNOWLEDGEMENT_TIMEOUT;
+  brl->acknowledgements.missing.count = 0;
+  brl->acknowledgements.missing.limit = BRAILLE_MESSAGE_UNACKNOWLEDGEED_LIMIT;
+}
+
+static void
+destructContractionCache (ContractionCache *cache) {
+  if (cache->input.characters) {
+    free(cache->input.characters);
+    cache->input.characters = NULL;
+  }
+
+  if (cache->output.cells) {
+    free(cache->output.cells);
+    cache->output.cells = NULL;
+  }
+
+  if (cache->offsets.array) {
+    free(cache->offsets.array);
+    cache->offsets.array = NULL;
+  }
+}
+
+static void
+destructBrailleRowDescriptor (BrailleRowDescriptor *brd) {
+  destructContractionCache(&brd->contracted.cache);
+  if (brd->contracted.offsets.array) free(brd->contracted.offsets.array);
+}
+
+void
+destructBrailleDisplay (BrailleDisplay *brl) {
+  if (brl->acknowledgements.alarm) {
+    asyncCancelRequest(brl->acknowledgements.alarm);
+    brl->acknowledgements.alarm = NULL;
+  }
+
+  if (brl->acknowledgements.messages) {
+    deallocateQueue(brl->acknowledgements.messages);
+    brl->acknowledgements.messages = NULL;
+  }
+
+  if (brl->keyTable) {
+    destroyKeyTable(brl->keyTable);
+    brl->keyTable = NULL;
+  }
+
+  if (brl->rowDescriptors.array) {
+    while (brl->rowDescriptors.size > 0) {
+      BrailleRowDescriptor *brd = &brl->rowDescriptors.array[--brl->rowDescriptors.size];
+      destructBrailleRowDescriptor(brd);
+    }
+
+    free(brl->rowDescriptors.array);
+    brl->rowDescriptors.array = NULL;
+  }
+
+  if (brl->buffer) {
+    if (brl->isCoreBuffer) free(brl->buffer);
+    brl->buffer = NULL;
+  }
+}
+
+static void
+fillRegion (
+  wchar_t *text, unsigned char *dots,
+  unsigned int start, unsigned int count,
+  unsigned int columns, unsigned int rows,
+  void *data, unsigned int length,
+  void (*fill) (wchar_t *text, unsigned char *dots, void *data)
+) {
+  text += start;
+  dots += start;
+
+  while (rows > 0) {
+    unsigned int index = 0;
+    size_t amount = length;
+    if (amount > count) amount = count;
+
+    while (index < amount) {
+      fill(&text[index], &dots[index], data);
+      index += 1;
+    }
+    length -= amount;
+
+    amount = count - index;
+    wmemset(&text[index], WC_C(' '), amount);
+    memset(&dots[index], 0, amount);
+
+    text += columns;
+    dots += columns;
+    rows -= 1;
+  }
+}
+
+static void
+fillText (wchar_t *text, unsigned char *dots, void *data) {
+  wchar_t **character = data;
+  *dots = convertCharacterToDots(textTable, (*text = *(*character)++));
+}
+
+void
+fillTextRegion (
+  wchar_t *text, unsigned char *dots,
+  unsigned int start, unsigned int count,
+  unsigned int columns, unsigned int rows,
+  const wchar_t *characters, size_t length
+) {
+  fillRegion(text, dots, start, count, columns, rows, &characters, length, fillText);
+}
+
+static void
+fillDots (wchar_t *text, unsigned char *dots, void *data) {
+  unsigned char **cell = data;
+  *text = UNICODE_BRAILLE_ROW | (*dots = *(*cell)++);
+}
+
+void
+fillDotsRegion (
+  wchar_t *text, unsigned char *dots,
+  unsigned int start, unsigned int count,
+  unsigned int columns, unsigned int rows,
+  const unsigned char *cells, size_t length
+) {
+  fillRegion(text, dots, start, count, columns, rows, &cells, length, fillDots);
+}
+
+
+int
+setStatusText (BrailleDisplay *brl, const char *text) {
+  unsigned int length = brl->statusColumns * brl->statusRows;
+
+  if (braille->writeStatus && (length > 0)) {
+    unsigned char cells[length];
+
+    {
+      unsigned int index = 0;
+
+      while (index < length) {
+        char c;
+        wint_t wc;
+
+        if (!(c = text[index])) break;
+        if ((wc = convertCharToWchar(c)) == WEOF) wc = WC_C('?');
+        cells[index++] = convertCharacterToDots(textTable, wc);
+      }
+
+      memset(&cells[index], 0, length-index);
+    }
+
+    if (!braille->writeStatus(brl, cells)) return 0;
+  }
+
+  return 1;
+}
+
+int
+clearStatusCells (BrailleDisplay *brl) {
+  return setStatusText(brl, "");
+}
+
+static void
+brailleBufferResized (BrailleDisplay *brl, int infoLevel) {
+  logMessage(infoLevel,
+    "Braille Display Dimensions: %d %s, %d %s",
+    brl->textColumns, ((brl->textColumns == 1)? "column": "columns"),
+    brl->textRows, ((brl->textRows == 1)? "row": "rows")
+  );
+
+  memset(brl->buffer, 0, brl->textColumns*brl->textRows);
+  if (brl->bufferResized) brl->bufferResized(brl->textRows, brl->textColumns);
+}
+
+static int
+resizeBrailleBuffer (BrailleDisplay *brl, int resized, int infoLevel) {
+  if (brl->resizeRequired) {
+    brl->resizeRequired = 0;
+    resized = 1;
+
+    if (brl->isCoreBuffer) {
+      size_t newSize = brl->textColumns * brl->textRows;
+      unsigned char *newAddress = malloc(newSize);
+
+      if (!newAddress) {
+        logMallocError();
+        return 0;
+      }
+
+      if (brl->buffer) free(brl->buffer);
+      brl->buffer = newAddress;
+    }
+  }
+
+  if (resized) brailleBufferResized(brl, infoLevel);
+  return 1;
+}
+
+int
+ensureBrailleBuffer (BrailleDisplay *brl, int infoLevel) {
+  brl->resizeRequired = brl->isCoreBuffer = !brl->buffer;
+  if ((brl->noDisplay = !brl->textColumns)) brl->textColumns = 1;
+  return resizeBrailleBuffer(brl, 1, infoLevel);
+}
+
+int
+readBrailleCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
+  int command = braille->readCommand(brl, context);
+
+  resizeBrailleBuffer(brl, 0, LOG_INFO);
+  return command;
+}
+
+int
+canRefreshBrailleDisplay (BrailleDisplay *brl) {
+  return brl->refreshBrailleDisplay != NULL;
+}
+
+int
+refreshBrailleDisplay (BrailleDisplay *brl) {
+  if (!canRefreshBrailleDisplay(brl)) return 0;
+  logMessage(LOG_DEBUG, "refreshing braille display");
+  return brl->refreshBrailleDisplay(brl);
+}
+
+int
+canRefreshBrailleRow (BrailleDisplay *brl) {
+  return brl->refreshBrailleRow != NULL;
+}
+
+int
+refreshBrailleRow (BrailleDisplay *brl, int row) {
+  if (!canRefreshBrailleRow(brl)) return 0;
+  logMessage(LOG_DEBUG, "refreshing braille row: %d", row);
+  return brl->refreshBrailleRow(brl, row);
+}
+
+int
+canSetBrailleFirmness (BrailleDisplay *brl) {
+  return brl->setBrailleFirmness != NULL;
+}
+
+int
+setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting) {
+  if (!canSetBrailleFirmness(brl)) return 0;
+  logMessage(LOG_DEBUG, "setting braille firmness: %d", setting);
+  return brl->setBrailleFirmness(brl, setting);
+}
+
+int
+canSetTouchSensitivity (BrailleDisplay *brl) {
+  return brl->setTouchSensitivity != NULL;
+}
+
+int
+setTouchSensitivity (BrailleDisplay *brl, TouchSensitivity setting) {
+  if (!canSetTouchSensitivity(brl)) return 0;
+  logMessage(LOG_DEBUG, "setting touch sensitivity: %d", setting);
+  return brl->setTouchSensitivity(brl, setting);
+}
+
+int
+canSetAutorepeatProperties (BrailleDisplay *brl) {
+  return brl->setAutorepeatProperties != NULL;
+}
+
+int
+setAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval) {
+  if (!canSetAutorepeatProperties(brl)) return 0;
+  logMessage(LOG_DEBUG, "setting autorepeat properties: %s Delay:%d Interval:%d", 
+             (on? "on": "off"), delay, interval);
+  return brl->setAutorepeatProperties(brl, on, delay, interval);
+}
diff --git a/Programs/brl.h b/Programs/brl.h
new file mode 100644
index 0000000..17529c9
--- /dev/null
+++ b/Programs/brl.h
@@ -0,0 +1,81 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL
+#define BRLTTY_INCLUDED_BRL
+
+#include "prologue.h"
+
+#include "brl_types.h"
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void constructBrailleDisplay (BrailleDisplay *brl);
+extern int ensureBrailleBuffer (BrailleDisplay *brl, int infoLevel);
+extern void destructBrailleDisplay (BrailleDisplay *brl);
+
+extern void fillTextRegion (
+  wchar_t *text, unsigned char *dots,
+  unsigned int start, unsigned int count,
+  unsigned int columns, unsigned int rows,
+  const wchar_t *characters, size_t length
+);
+
+extern void fillDotsRegion (
+  wchar_t *text, unsigned char *dots,
+  unsigned int start, unsigned int count,
+  unsigned int columns, unsigned int rows,
+  const unsigned char *cells, size_t length
+);
+
+extern int clearStatusCells (BrailleDisplay *brl);
+extern int setStatusText (BrailleDisplay *brl, const char *text);
+
+extern int readBrailleCommand (BrailleDisplay *, KeyTableCommandContext);
+
+extern int canRefreshBrailleDisplay (BrailleDisplay *brl);
+extern int refreshBrailleDisplay (BrailleDisplay *brl);
+
+extern int canRefreshBrailleRow (BrailleDisplay *brl);
+extern int refreshBrailleRow (BrailleDisplay *brl, int row);
+
+extern int canSetBrailleFirmness (BrailleDisplay *brl);
+extern int setBrailleFirmness (BrailleDisplay *brl, BrailleFirmness setting);
+
+extern int canSetTouchSensitivity (BrailleDisplay *brl);
+extern int setTouchSensitivity (BrailleDisplay *brl, TouchSensitivity setting);
+
+extern int canSetAutorepeatProperties (BrailleDisplay *brl);
+extern int setAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval);
+
+extern int haveBrailleDriver (const char *code);
+extern const char *getDefaultBrailleDriver (void);
+extern const BrailleDriver *loadBrailleDriver (const char *code, void **driverObject, const char *driverDirectory);
+extern void identifyBrailleDriver (const BrailleDriver *driver, int full);
+extern void identifyBrailleDrivers (int full);
+extern const BrailleDriver *braille;
+extern const BrailleDriver noBraille;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL */
diff --git a/Programs/brl_base.c b/Programs/brl_base.c
new file mode 100644
index 0000000..489ce96
--- /dev/null
+++ b/Programs/brl_base.c
@@ -0,0 +1,821 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "report.h"
+#include "api_control.h"
+#include "queue.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "brl_base.h"
+#include "brl_utils.h"
+#include "brl_dots.h"
+#include "kbd_keycodes.h"
+#include "io_generic.h"
+#include "cmd_queue.h"
+#include "ktb.h"
+
+const DotsTable dotsTable_ISO11548_1 = {
+  BRL_DOT_1, BRL_DOT_2, BRL_DOT_3, BRL_DOT_4,
+  BRL_DOT_5, BRL_DOT_6, BRL_DOT_7, BRL_DOT_8
+};
+
+const DotsTable dotsTable_rotated = {
+  BRL_DOT_8, BRL_DOT_6, BRL_DOT_5, BRL_DOT_7,
+  BRL_DOT_3, BRL_DOT_2, BRL_DOT_4, BRL_DOT_1
+};
+
+void
+makeTranslationTable (const DotsTable dots, TranslationTable table) {
+  int byte;
+
+  for (byte=0; byte<TRANSLATION_TABLE_SIZE; byte+=1) {
+    unsigned char cell = 0;
+    int dot;
+
+    for (dot=0; dot<DOTS_TABLE_SIZE; dot+=1) {
+      if (byte & dotsTable_ISO11548_1[dot]) {
+        cell |= dots[dot];
+      }
+    }
+
+    table[byte] = cell;
+  }
+}
+
+void
+reverseTranslationTable (const TranslationTable from, TranslationTable to) {
+  int byte;
+  memset(to, 0, sizeof(TranslationTable));
+  for (byte=TRANSLATION_TABLE_SIZE-1; byte>=0; byte--) to[from[byte]] = byte;
+}
+
+static inline void *
+translateCells (
+  const TranslationTable table,
+  unsigned char *target, const unsigned char *source, size_t count
+) {
+  if (table) {
+    while (count--) *target++ = table[*source++];
+    return target;
+  }
+
+  if (target == source) return target + count;
+  return mempcpy(target, source, count);
+}
+
+static inline unsigned char
+translateCell (const TranslationTable table, unsigned char cell) {
+  return table? table[cell]: cell;
+}
+
+static TranslationTable internalOutputTable;
+static const unsigned char *outputTable;
+
+void
+setOutputTable (const TranslationTable table) {
+  outputTable = table;
+}
+
+void
+makeOutputTable (const DotsTable dots) {
+  if (memcmp(dots, dotsTable_ISO11548_1, DOTS_TABLE_SIZE) == 0) {
+    outputTable = NULL;
+  } else {
+    makeTranslationTable(dots, internalOutputTable);
+    outputTable = internalOutputTable;
+  }
+}
+
+void *
+translateOutputCells (unsigned char *target, const unsigned char *source, size_t count) {
+  return translateCells(outputTable, target, source, count);
+}
+
+unsigned char
+translateOutputCell (unsigned char cell) {
+  return translateCell(outputTable, cell);
+}
+
+static TranslationTable internalInputTable;
+static const unsigned char *inputTable;
+
+void
+makeInputTable (void) {
+  if (outputTable) {
+    reverseTranslationTable(outputTable, internalInputTable);
+    inputTable = internalInputTable;
+  } else {
+    inputTable = NULL;
+  }
+}
+
+void *
+translateInputCells (unsigned char *target, const unsigned char *source, size_t count) {
+  return translateCells(inputTable, target, source, count);
+}
+
+unsigned char
+translateInputCell (unsigned char cell) {
+  return translateCell(inputTable, cell);
+}
+
+int
+awaitBrailleInput (BrailleDisplay *brl, int timeout) {
+  return gioAwaitInput(brl->gioEndpoint, timeout);
+}
+
+int
+connectBrailleResource (
+  BrailleDisplay *brl,
+  const char *identifier,
+  const GioDescriptor *descriptor,
+  BrailleSessionInitializer *initializeSession
+) {
+  if ((brl->gioEndpoint = gioConnectResource(identifier, descriptor))) {
+    if (!initializeSession || initializeSession(brl)) {
+      if (gioDiscardInput(brl->gioEndpoint)) {
+        return 1;
+      }
+    }
+
+    gioDisconnectResource(brl->gioEndpoint);
+    brl->gioEndpoint = NULL;
+  }
+
+  return 0;
+}
+
+void
+disconnectBrailleResource (
+  BrailleDisplay *brl,
+  BrailleSessionEnder *endSession
+) {
+  if (brl->gioEndpoint) {
+    if (endSession) endSession(brl);
+    drainBrailleOutput(brl, 0);
+    gioDisconnectResource(brl->gioEndpoint);
+    brl->gioEndpoint = NULL;
+  }
+}
+
+size_t
+readBraillePacket (
+  BrailleDisplay *brl,
+  GioEndpoint *endpoint,
+  void *packet, size_t size,
+  BraillePacketVerifier *verifyPacket, void *data
+) {
+  if (!endpoint) endpoint = brl->gioEndpoint;
+
+  unsigned char *bytes = packet;
+  size_t count = 0;
+  size_t length = 1;
+  int started = 0;
+
+  while (1) {
+    unsigned char byte;
+
+    if (!gioReadByte(endpoint, &byte, started)) {
+      if (count > 0) logPartialPacket(bytes, count);
+      return 0;
+    }
+
+  gotByte:
+    if (count < size) {
+      bytes[count++] = byte;
+      BraillePacketVerifierResult result = verifyPacket(brl, bytes, count, &length, data);
+
+      switch (result) {
+        case BRL_PVR_EXCLUDE:
+          count -= 1;
+          /* fall through */
+        case BRL_PVR_INCLUDE:
+          started = 1;
+          break;
+
+        case BRL_PVR_IGNORE:
+          count -= 1;
+          continue;
+
+        default:
+          logMessage(LOG_WARNING, "unimplemented braille packet verifier result: %u", result);
+          /* fall through */
+        case BRL_PVR_INVALID:
+          started = 0;
+          length = 1;
+
+          if (--count) {
+            logShortPacket(bytes, count);
+            count = 0;
+            goto gotByte;
+          }
+
+          logIgnoredByte(byte);
+          continue;
+      }
+
+      if (count >= length) {
+        logInputPacket(bytes, length);
+        return length;
+      }
+    } else {
+      if (count++ == size) logTruncatedPacket(bytes, size);
+      logDiscardedByte(byte);
+    }
+  }
+}
+
+int
+writeBraillePacket (
+  BrailleDisplay *brl, GioEndpoint *endpoint,
+  const void *packet, size_t size
+) {
+  if (!endpoint) endpoint = brl->gioEndpoint;
+  logOutputPacket(packet, size);
+  if (gioWriteData(endpoint, packet, size) == -1) return 0;
+
+  if (endpoint == brl->gioEndpoint) {
+    brl->writeDelay += gioGetMillisecondsToTransfer(endpoint, size);
+  }
+
+  return 1;
+}
+
+typedef struct {
+  GioEndpoint *endpoint;
+  unsigned int type;
+  size_t size;
+  unsigned char packet[];
+} BrailleMessage;
+
+static void
+logBrailleMessage (BrailleMessage *msg, const char *action) {
+  logBytes(LOG_CATEGORY(OUTPUT_PACKETS), "%s", msg->packet, msg->size, action);
+}
+
+static void
+deallocateBrailleMessage (BrailleMessage *msg) {
+  free(msg);
+}
+
+static void
+cancelBrailleMessageAlarm (BrailleDisplay *brl) {
+  if (brl->acknowledgements.alarm) {
+    asyncCancelRequest(brl->acknowledgements.alarm);
+    brl->acknowledgements.alarm = NULL;
+  }
+}
+
+static void setBrailleMessageAlarm (BrailleDisplay *brl);
+
+static int
+writeNextBrailleMessage (BrailleDisplay *brl) {
+  int ok = 1;
+
+  if (brl->acknowledgements.messages) {
+    BrailleMessage *msg = dequeueItem(brl->acknowledgements.messages);
+
+    if (msg) {
+      int written;
+
+      logBrailleMessage(msg, "dequeued");
+      written = writeBraillePacket(brl, msg->endpoint, msg->packet, msg->size);
+
+      deallocateBrailleMessage(msg);
+      msg = NULL;
+
+      if (written) {
+        setBrailleMessageAlarm(brl);
+        return 1;
+      }
+
+      ok = 0;
+    }
+  }
+
+  cancelBrailleMessageAlarm(brl);
+  return ok;
+}
+
+int
+acknowledgeBrailleMessage (BrailleDisplay *brl) {
+  logMessage(LOG_CATEGORY(OUTPUT_PACKETS), "acknowledged");
+  brl->acknowledgements.missing.count = 0;
+  return writeNextBrailleMessage(brl);
+}
+
+ASYNC_ALARM_CALLBACK(handleBrailleMessageTimeout) {
+  BrailleDisplay *brl = parameters->data;
+
+  asyncDiscardHandle(brl->acknowledgements.alarm);
+  brl->acknowledgements.alarm = NULL;
+
+  if ((brl->acknowledgements.missing.count += 1) < brl->acknowledgements.missing.limit) {
+    logMessage(LOG_WARNING, "missing braille message acknowledgement");
+    writeNextBrailleMessage(brl);
+  } else {
+    logMessage(LOG_WARNING, "too many missing braille message acknowledgements");
+    brl->hasFailed = 1;
+  }
+}
+
+static void
+setBrailleMessageAlarm (BrailleDisplay *brl) {
+  if (brl->acknowledgements.alarm) {
+    asyncResetAlarmIn(brl->acknowledgements.alarm, brl->acknowledgements.missing.timeout);
+  } else {
+    asyncNewRelativeAlarm(&brl->acknowledgements.alarm, brl->acknowledgements.missing.timeout,
+                          handleBrailleMessageTimeout, brl);
+  }
+}
+
+static int
+findOldBrailleMessage (const void *item, void *data) {
+  const BrailleMessage *old = item;
+  const BrailleMessage *new = data;
+
+  return old->type == new->type;
+}
+
+static void
+deallocateBrailleMessageItem (void *item, void *data) {
+  BrailleMessage *msg = item;
+
+  deallocateBrailleMessage(msg);
+}
+
+int
+writeBrailleMessage (
+  BrailleDisplay *brl, GioEndpoint *endpoint,
+  unsigned int type,
+  const void *packet, size_t size
+) {
+  if (brl->acknowledgements.alarm) {
+    BrailleMessage *msg;
+
+    if (!brl->acknowledgements.messages) {
+      if (!(brl->acknowledgements.messages = newQueue(deallocateBrailleMessageItem, NULL))) {
+        return 0;
+      }
+    }
+
+    if ((msg = malloc(sizeof(*msg) + size))) {
+      memset(msg, 0, sizeof(*msg));
+      msg->endpoint = endpoint;
+      msg->type = type;
+      msg->size = size;
+      memcpy(msg->packet, packet, size);
+
+      {
+        Element *element = findElement(brl->acknowledgements.messages, findOldBrailleMessage, msg);
+
+        if (element) {
+          logBrailleMessage(getElementItem(element), "unqueued");
+          deleteElement(element);
+        }
+      }
+
+      if (enqueueItem(brl->acknowledgements.messages, msg)) {
+        logBrailleMessage(msg, "enqueued");
+        return 1;
+      }
+
+      logBrailleMessage(msg, "discarded");
+      free(msg);
+    } else {
+      logMallocError();
+    }
+  } else if (writeBraillePacket(brl, endpoint, packet, size)) {
+    setBrailleMessageAlarm(brl);
+    return 1;
+  }
+
+  return 0;
+}
+
+void
+endBrailleMessages (BrailleDisplay *brl) {
+  cancelBrailleMessageAlarm(brl);
+
+  if (brl->acknowledgements.messages) {
+    deallocateQueue(brl->acknowledgements.messages);
+    brl->acknowledgements.messages = NULL;
+  }
+}
+
+int
+getBrailleReportSize (BrailleDisplay *brl, unsigned char identifier, HidReportSize *size) {
+  return gioGetHidReportSize(brl->gioEndpoint, identifier, size);
+}
+
+int
+getBrailleReportSizes (BrailleDisplay *brl, const BrailleReportSizeEntry *table) {
+  const BrailleReportSizeEntry *entry = table;
+
+  while (entry->identifier) {
+    HidReportSize size;
+    if (!getBrailleReportSize(brl, entry->identifier, &size)) return 0;
+
+    if (entry->input) *entry->input = size.input;
+    if (entry->output) *entry->output = size.output;
+    if (entry->feature) *entry->feature = size.feature;
+
+    entry += 1;
+  }
+
+  return 1;
+}
+
+int
+probeBrailleDisplay (
+  BrailleDisplay *brl, unsigned int retryLimit,
+  GioEndpoint *endpoint, int inputTimeout,
+  BrailleRequestWriter *writeRequest,
+  BraillePacketReader *readPacket, void *responsePacket, size_t responseSize,
+  BrailleResponseHandler *handleResponse
+) {
+  unsigned int retryCount = 0;
+
+  if (!endpoint) endpoint = brl->gioEndpoint;
+
+  while (writeRequest(brl)) {
+    drainBrailleOutput(brl, 0);
+
+    while (gioAwaitInput(endpoint, inputTimeout)) {
+      size_t size = readPacket(brl, responsePacket, responseSize);
+      if (!size) break;
+
+      {
+        BrailleResponseResult result = handleResponse(brl, responsePacket, size);
+
+        switch (result) {
+          case BRL_RSP_DONE:
+            return 1;
+
+          case BRL_RSP_UNEXPECTED:
+            logUnexpectedPacket(responsePacket, size);
+          case BRL_RSP_CONTINUE:
+            break;
+
+          default:
+            logMessage(LOG_WARNING, "unimplemented braille response result: %u", result);
+            /* fall through */
+          case BRL_RSP_FAIL:
+            return 0;
+        }
+      }
+    }
+
+    if (errno != EAGAIN)
+#ifdef ETIMEDOUT
+      if (errno != ETIMEDOUT)
+#endif /* ETIMEDOUT */
+        break;
+
+    if (retryCount == retryLimit) break;
+    retryCount += 1;
+  }
+
+  return 0;
+}
+
+void
+releaseBrailleKeys (BrailleDisplay *brl) {
+  releaseAllKeys(brl->keyTable);
+}
+
+KeyNumberSet
+mapKeyNumbers (KeyNumberSet fromKeys, const KeyNumberMapEntry *map, size_t count) {
+  KeyNumberSet toKeys = fromKeys;
+  const KeyNumberMapEntry *end = map + count;
+
+  while (map < end) {
+    KeyNumber key = map->from;
+    int yes = (key == KTB_KEY_ANY)? 0: isKeyNumberIncluded(fromKeys, key);
+
+    setKeyNumberIncluded(&toKeys, map->to, yes);
+    map += 1;
+  }
+
+  return toKeys;
+}
+
+void
+remapKeyNumbers (KeyNumberSet *keys, const KeyNumberMapEntry *map, size_t count) {
+  *keys = mapKeyNumbers(*keys, map, count);
+}
+
+struct KeyNumberSetMapStruct {
+  const KeyNumberSetMapEntry *linear;
+  const KeyNumberSetMapEntry **sorted;
+  size_t count;
+};
+
+static int
+compareKeyNumberSets (KeyNumberSet keys1, KeyNumberSet keys2) {
+  if (keys1 < keys2) return -1;
+  if (keys1 > keys2) return 1;
+  return 0;
+}
+
+static int
+sortKeyNumberSets (const void *element1, const void *element2) {
+  const KeyNumberSetMapEntry *const *entry1 = element1;
+  const KeyNumberSetMapEntry *const *entry2 = element2;
+  return compareKeyNumberSets((*entry1)->from, (*entry2)->from);
+}
+
+static int
+searchKeyNumberSetMapEntry (const void *target, const void *element) {
+  const KeyNumberSet *reference = target;
+  const KeyNumberSetMapEntry *const *entry = element;
+  return compareKeyNumberSets(*reference, (*entry)->from);
+}
+
+KeyNumberSetMap *
+newKeyNumberSetMap (const KeyNumberSetMapEntry *entries, size_t count) {
+  KeyNumberSetMap *map;
+
+  if ((map = malloc(sizeof(*map)))) {
+    memset(map, 0, sizeof(*map));
+
+    map->linear = entries;
+    map->count = count;
+
+    if (count < 4) {
+      map->sorted = NULL;
+      return map;
+    }
+
+    if ((map->sorted = malloc(ARRAY_SIZE(map->sorted, count)))) {
+      for (size_t i=0; i<count; i+=1) {
+        map->sorted[i] = &entries[i];
+      }
+
+      qsort(map->sorted, map->count, sizeof(*map->sorted), sortKeyNumberSets);
+      return map;
+    }
+
+    free(map);
+  }
+
+  logMallocError();
+  return NULL;
+}
+
+void
+destroyKeyNumberSetMap (KeyNumberSetMap *map) {
+  if (map->sorted) free(map->sorted);
+  free(map);
+}
+
+KeyNumberSet
+mapKeyNumberSet (KeyNumberSet keys, const KeyNumberSetMap *map) {
+  if (map) {
+    if (map->sorted) {
+      const KeyNumberSetMapEntry *const *entry = bsearch(
+        &keys, map->sorted, map->count,
+        sizeof(*map->sorted), searchKeyNumberSetMapEntry
+      );
+
+      if (entry) return (*entry)->to;
+    } else {
+      const KeyNumberSetMapEntry *entry = map->linear;
+      const KeyNumberSetMapEntry *end = entry + map->count;
+
+      while (entry < end) {
+        if (keys == entry->from) return entry->to;
+        entry += 1;
+      }
+    }
+  }
+
+  return keys;
+}
+
+void
+remapKeyNumberSet (KeyNumberSet *keys, const KeyNumberSetMap *map) {
+  *keys = mapKeyNumberSet(*keys, map);
+}
+
+typedef struct {
+  const KeyGroup group;
+
+  KeyNumberSet set;
+} IncludeKeyNumberData;
+
+static int
+includeKeyNumber (const KeyNameEntry *kne, void *data) {
+  if (kne) {
+    IncludeKeyNumberData *ikn = data;
+
+    if (kne->value.group == ikn->group) ikn->set |= KEY_NUMBER_BIT(kne->value.number);
+  }
+
+  return 1;
+}
+
+KeyNumberSet
+makeKeyNumberSet (KEY_NAME_TABLES_REFERENCE keys, KeyGroup group) {
+  IncludeKeyNumberData ikn = {
+    .group = group,
+
+    .set = 0
+  };
+
+  forEachKeyName(keys, includeKeyNumber, &ikn);
+  return ikn.set;
+}
+
+int
+enqueueKeyEvent (
+  BrailleDisplay *brl,
+  KeyGroup group, KeyNumber number, int press
+) {
+  report(REPORT_BRAILLE_KEY_EVENT, NULL);
+  if (api.handleKeyEvent(group, number, press)) return 1;
+
+  if (brl->keyTable) {
+    processKeyEvent(brl->keyTable, getCurrentCommandContext(), group, number, press);
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+enqueueKeyEvents (
+  BrailleDisplay *brl,
+  KeyNumberSet set, KeyGroup group, KeyNumber number, int press
+) {
+  while (set) {
+    if (set & 0X1) {
+      if (!enqueueKeyEvent(brl, group, number, press)) {
+        return 0;
+      }
+    }
+
+    set >>= 1;
+    number += 1;
+  }
+
+  return 1;
+}
+
+int
+enqueueKey (
+  BrailleDisplay *brl,
+  KeyGroup group, KeyNumber number
+) {
+  if (enqueueKeyEvent(brl, group, number, 1)) {
+    if (enqueueKeyEvent(brl, group, number, 0)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+enqueueKeys (
+  BrailleDisplay *brl,
+  KeyNumberSet set, KeyGroup group, KeyNumber number
+) {
+  KeyNumber stack[UINT8_MAX + 1];
+  unsigned char count = 0;
+
+  while (set) {
+    if (set & 0X1) {
+      if (!enqueueKeyEvent(brl, group, number, 1)) return 0;
+      stack[count++] = number;
+    }
+
+    set >>= 1;
+    number += 1;
+  }
+
+  while (count) {
+    if (!enqueueKeyEvent(brl, group, stack[--count], 0)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int
+enqueueUpdatedKeys (
+  BrailleDisplay *brl,
+  KeyNumberSet new, KeyNumberSet *old, KeyGroup group, KeyNumber number
+) {
+  KeyNumberSet bit = KEY_NUMBER_BIT(0);
+  KeyNumber stack[UINT8_MAX + 1];
+  unsigned char count = 0;
+
+  while (*old != new) {
+    if ((new & bit) && !(*old & bit)) {
+      stack[count++] = number;
+      *old |= bit;
+    } else if (!(new & bit) && (*old & bit)) {
+      if (!enqueueKeyEvent(brl, group, number, 0)) return 0;
+      *old &= ~bit;
+    }
+
+    number += 1;
+    bit <<= 1;
+  }
+
+  while (count) {
+    if (!enqueueKeyEvent(brl, group, stack[--count], 1)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int
+enqueueUpdatedKeyGroup (
+  BrailleDisplay *brl,
+  unsigned int count,
+  const unsigned char *new,
+  unsigned char *old,
+  KeyGroup group
+) {
+  KeyNumber pressStack[count];
+  unsigned char pressCount = 0;
+  KeyNumber base = 0;
+
+  while (base < count) {
+    KeyNumber number = base;
+    unsigned char bit = 1;
+
+    while (*old != *new) {
+      unsigned char isPressed = *new & bit;
+      unsigned char wasPressed = *old & bit;
+
+      if (isPressed && !wasPressed) {
+        *old |= bit;
+        pressStack[pressCount++] = number;
+      } else if (wasPressed && !isPressed) {
+        *old &= ~bit;
+        enqueueKeyEvent(brl, group, number, 0);
+      }
+
+      if (++number == count) break;
+      bit <<= 1;
+    }
+
+    old += 1;
+    new += 1;
+    base += 8;
+  }
+
+  while (pressCount > 0) {
+    enqueueKeyEvent(brl, group, pressStack[--pressCount], 1);
+  }
+
+  return 1;
+}
+
+int
+enqueueXtScanCode (
+  BrailleDisplay *brl,
+  unsigned char key, unsigned char escape,
+  KeyGroup group00, KeyGroup groupE0, KeyGroup groupE1
+) {
+  KeyGroup group;
+
+  switch (escape) {
+    case XT_MOD_00: group = group00; break;
+    case XT_MOD_E0: group = groupE0; break;
+    case XT_MOD_E1: group = groupE1; break;
+
+    default:
+      logMessage(LOG_WARNING, "unsupported XT scan code: %02X %02X", escape, key);
+      return 0;
+  }
+
+  return enqueueKey(brl, group, key);
+}
diff --git a/Programs/brl_cmds.awk b/Programs/brl_cmds.awk
new file mode 100644
index 0000000..4a21097
--- /dev/null
+++ b/Programs/brl_cmds.awk
@@ -0,0 +1,185 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  brlCommandValue = 0
+  brlBlockValue = 0
+  brlKeyValue = 0
+
+  brlBlockDeprecations["CLIP_NEW"] = "CUTBEGIN"
+  brlBlockDeprecations["CLIP_ADD"] = "CUTAPPEND"
+  brlBlockDeprecations["COPY_LINE"] = "CUTLINE"
+  brlBlockDeprecations["COPY_RECT"] = "CUTRECT"
+  brlBlockDeprecations["CLIP_COPY"] = "COPYCHARS"
+  brlBlockDeprecations["CLIP_APPEND"] = "APNDCHARS"
+}
+
+/^[[:space:]]*BRL_CMD_/ {
+  brlCommand(substr($1, 9), $1, brlCommandValue++, getComment($0))
+  next
+}
+
+/^[[:space:]]*BRL_BLK_/ {
+  prefix = substr($1, 1, 8)
+  name = substr($1, 9)
+  value = brlBlockValue++
+  help = getComment($0)
+
+  if (help !~ /^\(/) {
+    brlBlock(name, $1, value, help)
+
+    if (name in brlBlockDeprecations) {
+      alias = brlBlockDeprecations[name]
+      brlBlock(alias, prefix alias, value, "deprecated definition of " name " - " help)
+    }
+  }
+
+  next
+}
+
+/^[[:space:]]*BRL_KEY_/ {
+  gsub(",", "", $1)
+  key = tolower(substr($1, 9))
+  gsub("_", "-", key)
+  brlKey(substr($1, 9), $1, brlKeyValue++, key " key")
+  next
+}
+
+/#define[[:space:]]+BRL_FLG_/ {
+  brlFlag(substr($2, 9), $2, getDefineValue(), getComment($0))
+  next
+}
+
+/#define[[:space:]]+BRL_DOT/ {
+  value = getDefineValue()
+  sub("^.*\\(", "", value)
+  sub("\\).*$", "", value)
+  value = 2 ^ (value - 1)
+  brlDot(substr($2, 8), $2, value, getComment($0))
+  next
+}
+
+function writeHeaderPrologue(symbol, copyrightFile) {
+  writeCopyright(copyrightFile)
+
+  headerSymbol = symbol
+  print "#ifndef " headerSymbol
+  writeMacroDefinition(headerSymbol)
+  print ""
+
+  print "#ifdef __cplusplus"
+  print "extern \"C\" {"
+  print "#endif /* __cplusplus */"
+  print ""
+}
+
+function writeCopyright(file) {
+  if (length(file) > 0) {
+    while (getline line <file == 1) {
+      if (match(line, "^ */\\* *$")) {
+        print line
+
+        while (getline line <file == 1) {
+          print line
+          if (match(line, "^ *\\*/ *$")) break
+        }
+
+        print ""
+        break
+      }
+    }
+  }
+}
+
+function writeHeaderEpilogue() {
+  print ""
+  print "#ifdef __cplusplus"
+  print "}"
+  print "#endif /* __cplusplus */"
+
+  print ""
+  print "#endif /* " headerSymbol " */"
+}
+
+function writeMacroDefinition(name, definition, help) {
+  statement = "#define " name
+  if (length(definition) > 0) statement = statement " " definition
+
+  if (length(help) > 0) print makeDoxygenComment(help)
+  print statement
+}
+
+function getComment(line,     comment, last) {
+  comment = ""
+
+  if (match(line, "/\\*")) {
+    line = substr(line, RSTART+2)
+    gsub("^\\**<? *", "", line)
+
+    while (1) {
+      last = gsub("\\*/.*$", "", line)
+      gsub(" *$", "", line)
+
+      if (length(comment) > 0) comment = comment " "
+      comment = comment line
+      if (last) break
+
+      getline line
+      gsub("^[[:space:]]*\\**[[:space:]]*", "", line)
+    }
+  } else if (match(line, "//")) {
+    comment = substr(line, RSTART+2);
+    gsub("^ *", "", comment)
+
+    gsub("\\*/.*$", "", comment)
+    gsub(" *$", "", comment)
+  }
+
+  return comment
+}
+
+function getDefineValue() {
+  if ($3 !~ "^\\(") return getNormalizedConstant($3)
+  if (match($0, "\\([^)]*\\)")) return substr($0, RSTART, RLENGTH)
+  return ""
+}
+
+function getNormalizedConstant(value) {
+   if (value ~ "^UINT64_C\\(.+\\)$") value = substr(value, 10, length(value)-10)
+   return value
+}
+
+function makeDoxygenComment(text) {
+  return "/** " text " */"
+}
+
+function makeTranslatorNote(text) {
+  return "// xgettext: " text
+}
+
+function beginDoxygenFile() {
+  print "/** \\file"
+  print " */"
+  print ""
+}
+
+function getBrlapiKeyName(name) {
+  if (name == "ENTER") return "LINEFEED"
+  if (name ~ /^CURSOR_/) return substr(name, 8)
+  return name
+}
diff --git a/Programs/brl_driver.c b/Programs/brl_driver.c
new file mode 100644
index 0000000..60a137d
--- /dev/null
+++ b/Programs/brl_driver.c
@@ -0,0 +1,102 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "drivers.h"
+#include "brl.h"
+
+#define BRLSYMBOL noBraille
+#define DRIVER_NAME NoBraille
+#define DRIVER_CODE no
+#define DRIVER_COMMENT "no braille support"
+#define DRIVER_VERSION ""
+#define DRIVER_DEVELOPERS ""
+#include "brl_driver.h"
+#include "brl.auto.h"
+
+static int
+connectResource (BrailleDisplay *brl, const char *identifier) {
+  GioDescriptor descriptor;
+  gioInitializeDescriptor(&descriptor);
+
+  if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
+  if (connectResource(brl, "null:")) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+brl_destruct (BrailleDisplay *brl UNUSED) {
+  disconnectBrailleResource(brl, NULL);
+}
+
+static int
+brl_readCommand (BrailleDisplay *brl UNUSED, KeyTableCommandContext context UNUSED) {
+  return EOF;
+}
+
+static int
+brl_writeWindow (BrailleDisplay *brl UNUSED, const wchar_t *characters UNUSED) {
+  return 1;
+}
+
+const BrailleDriver *braille = &noBraille;
+
+int
+haveBrailleDriver (const char *code) {
+  return haveDriver(code, BRAILLE_DRIVER_CODES, driverTable);
+}
+
+const char *
+getDefaultBrailleDriver (void) {
+  return getDefaultDriver(driverTable);
+}
+
+const BrailleDriver *
+loadBrailleDriver (const char *code, void **driverObject, const char *driverDirectory) {
+  return loadDriver(code, driverObject,
+                    driverDirectory, driverTable,
+                    "braille", 'b', "brl",
+                    &noBraille, &noBraille.definition);
+}
+
+void
+identifyBrailleDriver (const BrailleDriver *driver, int full) {
+  identifyDriver("Braille", &driver->definition, full);
+}
+
+void
+identifyBrailleDrivers (int full) {
+  const DriverEntry *entry = driverTable;
+  while (entry->address) {
+    const BrailleDriver *driver = entry++->address;
+    identifyBrailleDriver(driver, full);
+  }
+}
diff --git a/Programs/brl_input.c b/Programs/brl_input.c
new file mode 100644
index 0000000..899c372
--- /dev/null
+++ b/Programs/brl_input.c
@@ -0,0 +1,111 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "log.h"
+#include "embed.h"
+#include "parameters.h"
+#include "api_control.h"
+#include "brl_input.h"
+#include "brl_utils.h"
+#include "brl_cmds.h"
+#include "cmd_queue.h"
+#include "cmd_enqueue.h"
+#include "io_generic.h"
+#include "core.h"
+
+static int
+processInput (void) {
+  int command = readBrailleCommand(&brl, getCurrentCommandContext());
+
+  if (command != EOF) {
+    switch (command & BRL_MSK_CMD) {
+      case BRL_CMD_OFFLINE:
+        setBrailleOffline(&brl);
+        return 0;
+
+      default:
+        break;
+    }
+  }
+
+  setBrailleOnline(&brl);
+  if (command == EOF) return 0;
+  enqueueCommand(command);
+  return 1;
+}
+
+static
+GIO_INPUT_HANDLER(handleBrailleInput) {
+  int processed = 0;
+
+  {
+    int error = parameters->error;
+
+    if (error) {
+      logActionError(error, "braille input monitor");
+      brl.hasFailed = 1;
+      return 0;
+    }
+  }
+
+  suspendCommandQueue();
+
+  if (!brl.isSuspended) {
+    api.claimDriver();
+    if (processInput()) processed = 1;
+    api.releaseDriver();
+  }
+
+#ifdef ENABLE_API
+  else if (api.isServerRunning()) {
+    switch (readBrailleCommand(&brl, KTB_CTX_DEFAULT)) {
+      case BRL_CMD_RESTARTBRL:
+        brl.hasFailed = 1;
+        break;
+
+      default:
+        processed = 1;
+      case EOF:
+        break;
+    }
+  }
+#endif /* ENABLE_API */
+
+  resumeCommandQueue();
+  return processed;
+}
+
+static GioHandleInputObject *handleBrailleInputObject = NULL;
+
+void
+stopBrailleInput (void) {
+  if (handleBrailleInputObject) {
+    gioDestroyHandleInputObject(handleBrailleInputObject);
+    handleBrailleInputObject = NULL;
+  }
+}
+
+void
+startBrailleInput (void) {
+  stopBrailleInput();
+  handleBrailleInputObject = gioNewHandleInputObject(brl.gioEndpoint, BRAILLE_DRIVER_INPUT_POLL_INTERVAL, handleBrailleInput, &brl);
+}
diff --git a/Programs/brl_input.h b/Programs/brl_input.h
new file mode 100644
index 0000000..2d9866e
--- /dev/null
+++ b/Programs/brl_input.h
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_BRL_INPUT
+#define BRLTTY_INCLUDED_BRL_INPUT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void startBrailleInput (void);
+extern void stopBrailleInput (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_BRL_INPUT */
diff --git a/Programs/brl_utils.c b/Programs/brl_utils.c
new file mode 100644
index 0000000..be39b90
--- /dev/null
+++ b/Programs/brl_utils.c
@@ -0,0 +1,255 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "report.h"
+#include "api_control.h"
+#include "brl_utils.h"
+#include "brl_dots.h"
+#include "async_wait.h"
+#include "ktb.h"
+
+void
+drainBrailleOutput (BrailleDisplay *brl, int minimumDelay) {
+  int duration = brl->writeDelay + 1;
+
+  brl->writeDelay = 0;
+  if (duration < minimumDelay) duration = minimumDelay;
+  asyncWait(duration);
+}
+
+void
+announceBrailleOffline (void) {
+  logMessage(LOG_DEBUG, "braille is offline");
+  api.updateParameter(BRLAPI_PARAM_DEVICE_ONLINE, 0);
+  report(REPORT_BRAILLE_DEVICE_OFFLINE, NULL);
+}
+
+void
+announceBrailleOnline (void) {
+  logMessage(LOG_DEBUG, "braille is online");
+  api.updateParameter(BRLAPI_PARAM_DEVICE_ONLINE, 0);
+  report(REPORT_BRAILLE_DEVICE_ONLINE, NULL);
+}
+
+void
+setBrailleOffline (BrailleDisplay *brl) {
+  if (!brl->isOffline) {
+    brl->isOffline = 1;
+    announceBrailleOffline();
+
+    {
+      KeyTable *keyTable = brl->keyTable;
+
+      if (keyTable) releaseAllKeys(keyTable);
+    }
+  }
+}
+
+void
+setBrailleOnline (BrailleDisplay *brl) {
+  if (brl->isOffline) {
+    brl->isOffline = 0;
+    announceBrailleOnline();
+
+    brl->writeDelay = 0;
+  }
+}
+
+int
+cellsHaveChanged (
+  unsigned char *cells, const unsigned char *new, unsigned int count,
+  unsigned int *from, unsigned int *to, unsigned char *force
+) {
+  unsigned int first = 0;
+
+  if (force && *force) {
+    *force = 0;
+  } else if (memcmp(cells, new, count) != 0) {
+    if (to) {
+      while (count) {
+        unsigned int last = count - 1;
+        if (cells[last] != new[last]) break;
+        count = last;
+      }
+    }
+
+    if (from) {
+      while (first < count) {
+        if (cells[first] != new[first]) break;
+        first += 1;
+      }
+    }
+  } else {
+    return 0;
+  }
+
+  if (from) *from = first;
+  if (to) *to = count;
+
+  memcpy(cells+first, new+first, count-first);
+  return 1;
+}
+
+int
+textHasChanged (
+  wchar_t *text, const wchar_t *new, unsigned int count,
+  unsigned int *from, unsigned int *to, unsigned char *force
+) {
+  unsigned int first = 0;
+
+  if (force && *force) {
+    *force = 0;
+  } else if (wmemcmp(text, new, count) != 0) {
+    if (to) {
+      while (count) {
+        unsigned int last = count - 1;
+        if (text[last] != new[last]) break;
+        count = last;
+      }
+    }
+
+    if (from) {
+      while (first < count) {
+        if (text[first] != new[first]) break;
+        first += 1;
+      }
+    }
+  } else {
+    return 0;
+  }
+
+  if (from) *from = first;
+  if (to) *to = count;
+
+  wmemcpy(text+first, new+first, count-first);
+  return 1;
+}
+
+int
+cursorHasChanged (int *cursor, int new, unsigned char *force) {
+  if (force && *force) {
+    *force = 0;
+  } else if (new == *cursor) {
+    return 0;
+  }
+
+  *cursor = new;
+  return 1;
+}
+
+unsigned char
+toLowerDigit (unsigned char upper) {
+  unsigned char lower = 0;
+  if (upper & BRL_DOT_1) lower |= BRL_DOT_3;
+  if (upper & BRL_DOT_2) lower |= BRL_DOT_7;
+  if (upper & BRL_DOT_4) lower |= BRL_DOT_6;
+  if (upper & BRL_DOT_5) lower |= BRL_DOT_8;
+  return lower;
+}
+
+/* Dots for landscape (counterclockwise-rotated) digits. */
+const DigitsTable landscapeDigits = {
+  [ 0] = BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_2,
+  [ 1] = BRL_DOT_4,
+  [ 2] = BRL_DOT_4 | BRL_DOT_1,
+  [ 3] = BRL_DOT_4 | BRL_DOT_5,
+  [ 4] = BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_2,
+  [ 5] = BRL_DOT_4 | BRL_DOT_2,
+  [ 6] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_5,
+  [ 7] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_2,
+  [ 8] = BRL_DOT_4 | BRL_DOT_1 | BRL_DOT_2,
+  [ 9] = BRL_DOT_1 | BRL_DOT_5,
+  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
+};
+
+/* Format landscape representation of numbers 0 through 99. */
+unsigned char
+makeLandscapeNumber (int x) {
+  return landscapeDigits[(x / 10) % 10] | toLowerDigit(landscapeDigits[x % 10]);  
+}
+
+/* Format landscape flag state indicator. */
+unsigned char
+makeLandscapeFlag (int number, int on) {
+  unsigned char dots = landscapeDigits[number % 10];
+  if (on) dots |= toLowerDigit(landscapeDigits[10]);
+  return dots;
+}
+
+/* Dots for seascape (clockwise-rotated) digits. */
+const DigitsTable seascapeDigits = {
+  [ 0] = BRL_DOT_5 | BRL_DOT_1 | BRL_DOT_4,
+  [ 1] = BRL_DOT_2,
+  [ 2] = BRL_DOT_2 | BRL_DOT_5,
+  [ 3] = BRL_DOT_2 | BRL_DOT_1,
+  [ 4] = BRL_DOT_2 | BRL_DOT_1 | BRL_DOT_4,
+  [ 5] = BRL_DOT_2 | BRL_DOT_4,
+  [ 6] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_1,
+  [ 7] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_1 | BRL_DOT_4,
+  [ 8] = BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_4,
+  [ 9] = BRL_DOT_5 | BRL_DOT_1,
+  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
+};
+
+/* Format seascape representation of numbers 0 through 99. */
+unsigned char
+makeSeascapeNumber (int x) {
+  return toLowerDigit(seascapeDigits[(x / 10) % 10]) | seascapeDigits[x % 10];  
+}
+
+/* Format seascape flag state indicator. */
+unsigned char
+makeSeascapeFlag (int number, int on) {
+  unsigned char dots = toLowerDigit(seascapeDigits[number % 10]);
+  if (on) dots |= seascapeDigits[10];
+  return dots;
+}
+
+/* Dots for portrait digits - 2 numbers in one cells */
+const DigitsTable portraitDigits = {
+  [ 0] = BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
+  [ 1] = BRL_DOT_1,
+  [ 2] = BRL_DOT_1 | BRL_DOT_2,
+  [ 3] = BRL_DOT_1 | BRL_DOT_4,
+  [ 4] = BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5,
+  [ 5] = BRL_DOT_1 | BRL_DOT_5,
+  [ 6] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4,
+  [ 7] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
+  [ 8] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5,
+  [ 9] = BRL_DOT_2 | BRL_DOT_4,
+  [10] = BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5
+};
+
+/* Format portrait representation of numbers 0 through 99. */
+unsigned char
+makePortraitNumber (int x) {
+  return portraitDigits[(x / 10) % 10] | toLowerDigit(portraitDigits[x % 10]);  
+}
+
+/* Format portrait flag state indicator. */
+unsigned char
+makePortraitFlag (int number, int on) {
+  unsigned char dots = toLowerDigit(portraitDigits[number % 10]);
+  if (on) dots |= portraitDigits[10];
+  return dots;
+}
diff --git a/Programs/brlapi.awk b/Programs/brlapi.awk
new file mode 100644
index 0000000..d33dad0
--- /dev/null
+++ b/Programs/brlapi.awk
@@ -0,0 +1,91 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  apiRangeTypeCount = 0
+  apiParameterTypeCount = 0
+}
+
+/#define[[:space:]]+BRLAPI_(CURSOR|DISPLAY|TTY)_/ {
+  apiConstant(substr($2, 8), $2, getDefineValue(), "")
+  next
+}
+
+/^[[:space:]]*BRLAPI_(ERROR)_[^[:space:]]+[[:space:]]+=[[:space:]]/ {
+  gsub(",", "", $3)
+  apiConstant(substr($1, 8), $1, $3, getComment($0))
+  next
+}
+
+/#define[[:space:]]+BRLAPI_KEY_MAX/ {
+  apiMask(substr($2, 12), $2, getDefineValue(), "")
+  next
+}
+
+/#define[[:space:]]+BRLAPI_KEY_[A-Z_]+_MASK/ {
+  apiMask(substr($2, 12), $2, getDefineValue(), "")
+  next
+}
+
+/#define[[:space:]]+BRLAPI_KEY_[A-Z_]+_SHIFT/ {
+  apiShift(substr($2, 12), $2, getDefineValue(), "")
+  next
+}
+
+/#define[[:space:]]+BRLAPI_KEY_TYPE_/ {
+  apiType(substr($2, 17), $2, getDefineValue(), "")
+  next
+}
+
+/#define[[:space:]]+BRLAPI_KEY_SYM_/ {
+  apiKey(substr($2, 16), $2, getDefineValue(), "")
+  next
+}
+
+/^[[:space:]]*brlapi_rangeType_/ {
+  gsub(",", "", $1)
+  apiRangeType(substr($1, 18), $1, apiRangeTypeCount++, "")
+  next
+}
+
+/#define[[:space:]]+BRLAPI_PARAMF_/ {
+  apiConstant(substr($2, 8), $2, getDefineValue(), "")
+  next
+}
+
+/^[[:space:]]*BRLAPI_PARAM_TYPE_[^[:space:],]+,?/ {
+  gsub(",", "", $1)
+  name = substr($1, 8)
+
+  if ($2 == "=" ) {
+    gsub("^BRLAPI_", "", $3)
+    gsub(",", "", $3)
+    apiConstant(name, $1, apiParameterTypeValues[$3], getComment($0))
+  } else {
+    apiParameterTypeValues[name] = apiParameterTypeCount
+    apiConstant(name, $1, apiParameterTypeCount++, getComment($0))
+  }
+
+  next
+}
+
+/^[[:space:]]*BRLAPI_PARAM_[^[:space:]]+[[:space:]]+=[[:space:]]+[0-9]+,?/ {
+  gsub(",", "", $3)
+  apiConstant(substr($1, 8), $1, $3, getComment($0))
+  next
+}
diff --git a/Programs/brlapi.h b/Programs/brlapi.h
new file mode 100644
index 0000000..031a9eb
--- /dev/null
+++ b/Programs/brlapi.h
@@ -0,0 +1,1583 @@
+/* Programs/brlapi.h.  Generated from brlapi.h.in by configure.  */
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** \file
+ * \brief Types, defines and functions prototypes for \e BrlAPI's library
+ */
+
+#ifndef BRLAPI_INCLUDED
+#define BRLAPI_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* #undef BRLAPI_WIN32 */
+
+/** \defgroup brlapi_version Version of the BrlAPI library
+ * @{ */
+
+/** Library version. */
+#define BRLAPI_RELEASE "0.8.5"
+
+/** Library major version. */
+#define BRLAPI_MAJOR 0
+
+/** Library minor version. */
+#define BRLAPI_MINOR 8
+
+/** Library revision. */
+#define BRLAPI_REVISION 5
+
+/** Returns the version of the library */
+void brlapi_getLibraryVersion(int *major, int *minor, int *revision);
+
+/** @} */
+
+/* Types are defined there */
+#include <sys/types.h>
+
+#ifdef BRLAPI_WIN32
+#include <windows.h>
+#define BRLAPI_STDCALL __stdcall
+#else /* BRLAPI_WIN32 */
+#define BRLAPI_STDCALL
+#endif /* BRLAPI_WIN32 */
+
+#ifdef _MSC_VER
+typedef __int64 uint64_t;
+typedef __int32 uint32_t;
+#define UINT64_C(x) (x ## Ui64)
+#define PRIx64 "I64x"
+typedef signed int ssize_t;
+#else /* _MSC_VER */
+
+/* this is for uint*_t */
+#include <stdint.h>
+
+/* NULL is defined there */
+#include <unistd.h>
+
+#include <inttypes.h> /* For PRIx64 */
+#endif /* _MSC_VER */
+
+#include <wchar.h>
+
+/** \defgroup brlapi_handles BrlAPI handles
+ *
+ * Each function provided by BrlAPI comes in two versions.
+ *
+ * 1. A version whose name is prefixed by brlapi_ for clients opening only
+ * one simultaneous connection with BrlAPI (most frequen case)
+ *
+ * 2. A version whose name is prefixed by brlapi__ for use by clients
+ * wishing to open more than one connection to BrlAPI.
+ *
+ * A function called brlapi__foo is used in exactly the same way as its
+ * brlapi_foo counterpart, except that it takes an additional argument
+ * (the first one), which is a handle letting the client refer to a given
+ * connection in a similar manner to what file descriptors do.
+ *
+ * In case you want to check that your code is not erroneously using brlapi_foo
+ * functions, define BRLAPI_NO_SINGLE_SESSION before including <brlapi.h>: that
+ * will disable the declaration of all single session functions.
+ *
+ * @{ */
+
+/** Type for BrlAPI hanles */
+typedef struct brlapi_handle_t brlapi_handle_t;
+
+/** Returns the size of an object of type brlapi_handle_t in bytes */
+size_t BRLAPI_STDCALL brlapi_getHandleSize(void);
+
+/** @} */
+
+/** \defgroup brlapi_connection Connecting to BrlAPI
+ *
+ * Before calling any other function of the library, calling
+ * brlapi_openConnection() is needed to establish a connection to
+ * \e BrlAPI 's server.
+ * When the connection is not needed any more, brlapi_closeConnection() must be
+ * called to close the connection.
+ *
+ * @{ */
+
+/** Default port number on which connections to \e BrlAPI can be established */
+#define BRLAPI_SOCKETPORTNUM 4101
+#define BRLAPI_SOCKETPORT "4101"
+
+/** Default unix path on which connections to \e BrlAPI can be established */
+#define BRLAPI_SOCKETPATH "/var/lib/BrlAPI"
+
+/** \e brltty 's settings directory
+ *
+ * This is where authorization key and driver-dependent key names are found
+ * for instance. */
+#define BRLAPI_ETCDIR "/etc"
+
+/** Default name of the file containing \e BrlAPI 's authorization key
+ *
+ * This name is relative to BRLAPI_ETCDIR */
+#define BRLAPI_AUTHKEYFILE "brlapi.key"
+
+/** Default authorization setting */
+#ifdef BRLAPI_WIN32
+/* No authentication by default on Windows */
+#define BRLAPI_DEFAUTH "none"
+#else /* BRLAPI_WIN32 */
+#define BRLAPI_DEFAUTH_KEYFILE "keyfile:" BRLAPI_ETCDIR "/" BRLAPI_AUTHKEYFILE
+
+#ifdef HAVE_POLKIT
+#define BRLAPI_DEFAUTH_POLKIT "+polkit"
+#else /* HAVE_POLKIT */
+#define BRLAPI_DEFAUTH_POLKIT ""
+#endif /* HAVE_POLKIT */
+
+#define BRLAPI_DEFAUTH BRLAPI_DEFAUTH_KEYFILE BRLAPI_DEFAUTH_POLKIT
+#endif /* BRLAPI_WIN32 */
+
+/** OS-dependent file descriptor type
+ *
+ * This is the type for file descriptors returned by brlapi_openConnection() and
+ * brlapi_getFileDescriptor().
+ */
+#ifdef __MINGW32__
+typedef HANDLE brlapi_fileDescriptor;
+#else /* __MINGW32__ */
+typedef int brlapi_fileDescriptor;
+#endif /* __MINGW32__ */
+
+/** Invalid value for brlapi_fileDescriptor
+ *
+ * This is returned by brlapi_getFileDescriptor() when the connection is closed.
+ */
+#ifdef __MINGW32__
+#define BRLAPI_INVALID_FILE_DESCRIPTOR INVALID_HANDLE_VALUE
+#else /* __MINGW32__ */
+#define BRLAPI_INVALID_FILE_DESCRIPTOR -1
+#endif /* __MINGW32__ */
+
+/** \brief Settings structure for \e BrlAPI connection
+ *
+ * This structure holds every parameter needed to connect to \e BrlAPI: which
+ * file the authorization key can be found in and which computer to connect to.
+ *
+ * \par Examples:
+ * \code
+ * brlapi_connectionSettings_t settings;
+ *
+ * settings.auth="/etc/brlapi.key";
+ * settings.host="foo";
+ * \endcode
+ *
+ * \e libbrlapi will read authorization key from file \p /etc/brlapi.key
+ * and connect to the machine called "foo", on the default TCP port.
+ *
+ * \code
+ * settings.host="10.1.0.2";
+ * \endcode
+ *
+ * lets directly enter an IP address instead of a machine name.
+ *
+ * \code
+ * settings.host=":1";
+ * \endcode
+ *
+ * lets \e libbrlapi connect to the local computer, on port BRLAPI_SOCKETPORTNUM+1
+ *
+ * \sa brlapi_openConnection()
+ */
+typedef struct {
+  /** For security reasons, \e libbrlapi has to get authorized to connect to the
+   * \e BrlAPI server. This can be done via a secret key, for instance. This is
+   * the path to the file which holds it; it will hence have to be readable by
+   * the application.
+   *
+   * Setting \c NULL defaults it to local installation setup or to the content
+   * of the BRLAPI_AUTH environment variable, if it exists. */
+  const char *auth;
+
+  /** This tells where the \e BrlAPI server resides: it might be listening on
+   * another computer, on any TCP port. It should look like "foo:1", which
+   * means TCP port number BRLAPI_SOCKETPORTNUM+1 on computer called "foo".
+   * \note Please check that resolving this name works before complaining
+   *
+   * Settings \c NULL defaults it to localhost, using the local installation's
+   * default TCP port, or to the content of the BRLAPI_HOST environment
+   * variable, if it exists. */
+  const char *host;
+} brlapi_connectionSettings_t;
+
+/* BRLAPI_SETTINGS_INITIALIZER */
+/** Allows to initialize a structure of type \e brlapi_connectionSettings_t *
+ * with default values. */
+#define BRLAPI_SETTINGS_INITIALIZER { NULL, NULL }
+
+/* brlapi_openConnection */
+/** Open a socket and connect it to \e BrlAPI 's server
+ *
+ * This function first loads an authorization key as specified in settings.
+ * It then creates a TCP socket and connects it to the specified machine, on
+ * the specified port. It writes the authorization key on the socket and
+ * waits for acknowledgement.
+ *
+ * \return the file descriptor, or BRLAPI_INVALID_FILE_DESCRIPTOR on error
+ *
+ * \note The file descriptor is returned in case the client wants to
+ * communicate with the server without using \e libbrlapi functions. If it uses
+ * them however, it won't have to pass the file descriptor later, since the
+ * library keeps a copy of it. But that also means that
+ * brlapi_openConnection() may be called several times, but \e libbrlapi
+ * functions will always work with the last call's descriptor
+ *
+ * \par Example:
+ * \code
+ * if (brlapi_openConnection(&settings,&settings)<0) {
+ *  fprintf(stderr,"couldn't connect to BrlAPI at %s: %s\n",
+ *   settings.host, brlapi_strerror(&brlapi_error));
+ *  exit(1);
+ * }
+ * \endcode
+ *
+ * \par Errors:
+ * \e BrlAPI might not be on this TCP port, the host name might not be
+ * resolvable, the authorization may fail,...
+ *
+ * \param desiredSettings this gives the desired connection parameters, as described
+ * in brlapi_connectionSettings_t. If \c NULL, defaults values are used, so that it is
+ * generally a good idea to give \c NULL as default, and only fill a
+ * brlapi_connectionSettings_t structure when the user gave parameters to the program
+ * for instance;
+ * \param actualSettings if not \c NULL, parameters which were actually used are
+ * stored here, if the application ever needs them. Since they are constant
+ * strings, or come from a getenv call, these must not be freed by the
+ * application.
+ *
+ * \sa
+ * brlapi_connectionSettings_t
+ * brlapi_writePacket()
+ * brlapi_readPacketHeader()
+ * brlapi_readPacketContent()
+ * brlapi_readPacket()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_openConnection(const brlapi_connectionSettings_t *desiredSettings, brlapi_connectionSettings_t *actualSettings);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__openConnection(brlapi_handle_t *handle, const brlapi_connectionSettings_t *desiredSettings, brlapi_connectionSettings_t *actualSettings);
+
+/* brlapi_fileDescriptor */
+/** Return the file descriptor used by the BrlAPI connection
+ *
+ * This allows to use it with \c select(), \c g_io_add_watch(), \c
+ * XtAppAddInput(), etc.
+ *
+ * BRLAPI_INVALID_FILE_DESCRIPTOR is returned when the connection was closed.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_getFileDescriptor(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__getFileDescriptor(brlapi_handle_t *handle);
+
+
+/* brlapi_closeConnection */
+/** Cleanly close the socket
+ *
+ * This function locks until a closing acknowledgement is received from the
+ * server. The socket is then freed, so the file descriptor
+ * brlapi_openConnection() gave has no meaning any more
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_closeConnection(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__closeConnection(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_clientData Setting and getting client data
+ *
+ * Clients can register a pointer to data that can then be used
+ * e.g. in exception handlers.
+ * @{ */
+
+/* brlapi__setClientData */
+/** Register a pointer to client data
+ *
+ * \param data The pointer to the private client data
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_setClientData(void *data);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__setClientData(brlapi_handle_t *handle, void *data);
+
+/* brlapi__getClientData */
+/** Retrieves the pointer to the private client data
+ *
+ * \return the pointer to the private client data
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void * BRLAPI_STDCALL brlapi_getClientData(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void * BRLAPI_STDCALL brlapi__getClientData(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_info Getting Terminal information
+ * \brief How to get information about the connected Terminal
+ *
+ * Before using Raw mode or key codes, the application should always check the
+ * type of the connected terminal, to be sure it is really the one it expects.
+ *
+ * One should also check for display size, so as to adjust further displaying
+ * on it.
+ * @{
+ */
+
+/** Maximum name length for driver names embeded in BrlAPI packets, not counting
+ * any termination \\0 character */
+#define BRLAPI_MAXNAMELENGTH 31
+
+/* brlapi_getDriverName */
+/** Return the complete name of the driver used by \e brltty
+ *
+ * This function fills its argument with the whole name of the braille
+ * driver if available, terminated with a '\\0'.
+ *
+ * \param buffer is the buffer provided by the application;
+ * \param size is the maximum size for the name buffer.
+ *
+ * \return -1 on error, otherwise a positive value giving the size of the needed
+ * buffer (including '\\0'). If that value is bigger than \p size, the value was
+ * truncated and the caller should retry with a bigger buffer accordingly.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getDriverName(char *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getDriverName(brlapi_handle_t *handle, char *buffer, size_t size);
+
+/* brlapi_getModelIdentifier */
+/** Return an identifier for the device model used by \e brltty
+ *
+ * This function fills its argument with the whole identifier of the braille
+ * device model if available, terminated with a '\\0'.
+ *
+ * \param buffer is the buffer given by the application;
+ * \param size is the maximum size for the identifier buffer.
+ *
+ * \return -1 on error, otherwise a positive value giving the size of the needed
+ * buffer (including '\\0'). If that value is bigger than \p size, the value was
+ * truncated and the caller should retry with a bigger buffer accordingly.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getModelIdentifier(char *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getModelIdentifier(brlapi_handle_t *handle, char *buffer, size_t size);
+
+/* brlapi_getDisplaySize */
+/** Return the size of the braille display */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getDisplaySize(unsigned int *x, unsigned int *y);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getDisplaySize(brlapi_handle_t *handle, unsigned int *x, unsigned int *y);
+
+/** @} */
+
+/** \defgroup brlapi_tty Entering & leaving tty mode
+ * \brief How to take control of ttys for direct braille display / read
+ *
+ * Before being able to write on the braille display, the application must tell
+ * the server which tty it will handle.
+ *
+ * The application must also specify how braille keys will be delivered to it.
+ * Two ways are possible:
+ *
+ * - driver-specific keycodes: applications will get values specific to each braille driver, since the keycode, as
+ *   defined in the driver will be given for each key event (press or release).
+ *   Using them leads to building highly driver-dependent applications, which
+ *   can yet sometimes be useful to mimic existing proprietary applications
+ *   for instance.
+ * - commands: applications will get exactly the same values as
+ *   \e brltty. This allows driver-independent clients, which will hopefully
+ *   be nice to use with a lot of different terminals.
+ * \sa brlapi_readKey()
+ * @{
+ */
+
+/* brlapi_enterTtyMode */
+/** Ask for some tty, with some key mechanism
+ *
+ * \param tty
+ * - If tty>=0 then take control of the specified tty.
+ * - If tty==::BRLAPI_TTY_DEFAULT then take control of the default tty.
+ *
+ * \param driver tells how the application wants brlapi_readKey() to return
+ * key presses. NULL or "" means BRLTTY commands are required,
+ * whereas a driver name means that driver-specific keycodes are expected.
+ *
+ * \return the used tty number on success, -1 on error
+ *
+ * WINDOWPATH and WINDOWID should be propagated when running remote applications
+ * via ssh, for instance, along with BRLAPI_HOST and the authorization key (see
+ * SendEnv in ssh_config(5) and AcceptEnv in sshd_config(5))
+ *
+ * Once brlapi_enterTtyMode() is called, brlapi_leaveTtyMode() has to be called
+ * before calling brlapi_enterTtyMode() again.
+ *
+ * TODO: document which functions work in TTY mode only.
+ *
+ * \sa brlapi_leaveTtyMode() brlapi_readKey()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterTtyMode(int tty, const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterTtyMode(brlapi_handle_t *handle, int tty, const char *driver);
+
+/** Select the default tty.
+ *
+ * The library takes the following steps:
+ * -# Try to get the tty number from the \c WINDOWID environment variable (for the \c xterm case).
+ * -# Try to get the tty number from the \c CONTROLVT environment variable.
+ * -# Read \c /proc/self/stat (on \c Linux).
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#define BRLAPI_TTY_DEFAULT -1
+
+/* brlapi_enterTtyModeWithPath */
+/** Ask for some tty specified by its path in the tty tree, with some key mechanism
+ *
+ * \param ttys points on the array of ttys representing the tty path to be got.
+ * Can be NULL if nttys is 0.
+ * \param count gives the number of elements in ttys.
+ * \param driver has the same meaning as in brlapi_enterTtyMode()
+ *
+ * Providing nttys == 0 means to get the root, which is usually what a screen
+ * readers wants to use.
+ *
+ * The content of WINDOWPATH or XDG_VTNR will always be prepended to the given
+ * \p ttys array, so the application does not need to determine by itself where
+ * it is running.
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterTtyModeWithPath(const int *ttys, int count, const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterTtyModeWithPath(brlapi_handle_t *handle, const int *ttys, int count, const char *driver);
+
+/* brlapi_leaveTtyMode */
+/** Stop controlling the tty
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_leaveTtyMode(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__leaveTtyMode(brlapi_handle_t *handle);
+
+/* brlapi_setFocus */
+/** Tell the current tty to brltty
+ *
+ * This is intended for focus tellers, such as brltty, xbrlapi, screen, ...
+ * brlapi_enterTtyMode() must have been called beforehand to tell where this focus
+ * applies in the tty tree.
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode() brlapi_leaveTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_setFocus(int tty);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__setFocus(brlapi_handle_t *handle, int tty);
+
+/** @} */
+
+/** \defgroup brlapi_write Writing on the braille display
+ * \brief Write text to the braille display
+ *
+ * After brlapi_enterTtyMode() has been called, the application can
+ * call one of these functions to write things on the braille display.
+ *
+ * \note Be sure to call brlapi_enterTtyMode() \e before calling brlapi_write(),
+ * or else you'll get an error. This is particularly not always trivial when
+ * writing multithreaded applications.
+ *
+ * \note Dots are coded as described in ISO/TR 11548-1: a dot pattern is coded
+ * by a byte in which bit 0 is set iff dot 1 is up, bit 1 is set iff dot 2 is
+ * up, ... bit 7 is set iff dot 8 is up. This also corresponds to the low-order
+ * byte of the coding of unicode's braille row U+2800.
+ *
+ * \note Text is translated by the server one to one, by just using a simple
+ * wchar_t to pattern table, i.e. no contraction/expansion is performed, because
+ * the client would then have no way to know how wide the output would be and
+ * thus the quantity of text to feed.  If contraction/expansion is desired, the
+ * client should perform it itself (e.g. thanks to liblouis or gnome-braille)
+ * and send the resulting dot patterns.  This is actually exactly the same
+ * problem as font rendering on a graphical display: for better control,
+ * nowadays all font rasterization is performed on the client side, and mere
+ * pixmaps are sent to the X server.
+ *
+ * \note For braille displays with multiple lines, text will be wrapped over the
+ * lines.
+ *
+ * @{ */
+
+/* brlapi_writeText */
+/** Write the given \\0-terminated string to the braille display
+ *
+ * If the string is too long, it is truncated. If it's too short,
+ * it is padded with spaces. The text is assumed to be in the current application
+ * locale charset set by setlocale(3) if it was called, or the locale charset
+ * from the application locale environment variables if setlocale(3) was not called.
+ *
+ * \param cursor gives the cursor position; if equal to ::BRLAPI_CURSOR_OFF, no cursor is shown at
+ * all; if cursor==::BRLAPI_CURSOR_LEAVE, the cursor is left where it is
+ *
+ * \param text points to the string to be displayed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeText(int cursor, const char *text);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeText(brlapi_handle_t *handle, int cursor, const char *text);
+
+/* brlapi_writeWText */
+/** Write the given \\0-terminated unicode string to the braille display
+ *
+ * If the string is too long, it is truncated. If it's too short,
+ * it is padded with spaces.
+ *
+ * \param cursor gives the cursor position; if equal to ::BRLAPI_CURSOR_OFF, no cursor is shown at
+ * all; if cursor==::BRLAPI_CURSOR_LEAVE, the cursor is left where it is
+ *
+ * \param text points to the string to be displayed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeWText(int cursor, const wchar_t *text);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeWText(brlapi_handle_t *handle, int cursor, const wchar_t *text);
+
+/* brlapi_writeDots */
+/** Write the given dots array to the display
+ *
+ * \param dots points on an array of dot information, one per character. Its
+ * size must hence be the same as what brlapi_getDisplaySize() returns.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeDots(const unsigned char *dots);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeDots(brlapi_handle_t *handle, const unsigned char *dots);
+
+/* brlapi_writeArguments_t */
+/** Structure containing arguments to be given to brlapi_write() */
+typedef struct {
+  int displayNumber /** Display number ::BRLAPI_DISPLAY_DEFAULT == unspecified */;
+  unsigned int
+      regionBegin /** Region of display to update, 1st character of display is
+                     1, 1st character of the second line of display is the width
+                     of a line plus 1, etc. Regions are always a linear wrap
+                     over the different lines. */
+      ;
+  int regionSize /** Number of characters held in andMask and orMask. If negative, its absolute value is taken into account, and the output is padded or truncated to fill the rest of the display. */;
+  const char *text /** Text to display, must hold as many characters as the region fields expresses. */;
+  int textSize /** Size of text in bytes. If -1, strlen() is used for computing it. */;
+  const unsigned char *andMask /** And attributes; applied first */;
+  const unsigned char *orMask /** Or attributes; applied \e after ANDing */;
+  int cursor /** ::BRLAPI_CURSOR_LEAVE == don't touch, ::BRLAPI_CURSOR_OFF == turn off, 1 = 1st char of display, ... */;
+  const char *charset /** Text charset. NULL means it is assumed to be 8bits, and the same as the server's. "" means current locale's charset. If no locale was selected, defaults to NULL's meaning. */;
+} brlapi_writeArguments_t;
+
+/** Write to the default display on the braille device.
+ *
+ * \sa brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_DISPLAY_DEFAULT -1
+
+/** Do not change the cursor's state or position.
+ *
+ * \sa brlapi_writeText() brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_CURSOR_LEAVE -1
+
+/** Do not display the cursor.
+ *
+ * \sa brlapi_writeText() brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_CURSOR_OFF 0
+
+/* BRLAPI_WRITEARGUMENTS_INITIALIZER */
+/** Allows to initialize a structure of type \e brlapi_writeArguments_t *
+ * with default values:
+ * displayNumber = ::BRLAPI_DISPLAY_DEFAULT; (unspecified)
+ * regionBegin = regionSize = 0; (update the whole display, DEPRECATED and will
+ * be forbidden in next release. You must always express the region you wish to
+ * update)
+ * text = andMask = orMask = NULL; (no text, no attribute)
+ * cursor = ::BRLAPI_CURSOR_LEAVE; (don't touch cursor)
+*/
+#define BRLAPI_WRITEARGUMENTS_INITIALIZER { \
+  .displayNumber = BRLAPI_DISPLAY_DEFAULT, \
+  .regionBegin = 0, \
+  .regionSize = 0, \
+  .text = NULL, \
+  .textSize = -1, \
+  .andMask = NULL, \
+  .orMask = NULL, \
+  .cursor = BRLAPI_CURSOR_LEAVE, \
+  .charset = NULL \
+}
+
+/* brlapi_write */
+/** Update a specific region of the braille display and apply and/or masks
+ *
+ * \param arguments gives information necessary for the update
+ *
+ * regionBegin and regionSize must be filled for specifying which part of the
+ * display will be updated, as well as the size (in characters, not bytes) of
+ * the text, andMask and orMask members.
+ *
+ * If given, the "text" field holds the text that will be displayed in the
+ * region.  The char string must hold exactly as many characters as the region
+ * fields express.  For multibyte text, this is the number of \e multibyte
+ * caracters.  Notably, combining and double-width caracters count for 1.
+ *
+ * The actual length of the text in \e bytes may be specified thanks to
+ * textSize.  If -1 is given, it will be computed thanks to strlen(), so "text"
+ * must then be a NUL-terminated string.
+ *
+ * The "andMask" and "orMask" masks, if present, are then applied on top of
+ * the text, one byte per character.  This hence permits the superimposing of
+ * attributes over the text.  For instance, setting an andMask mask full of
+ * BRLAPI_DOTS(1,1,1,1,1,1,0,0) will only keep (logical AND) dots 1-6,
+ * hence dropping dots 7 and 8.  On the contrary, setting an orMask full of
+ * BRLAPI_DOT7|BRLAPI_DOT8 will add (logical OR) dots 7 and 8.
+ *
+ * The "charset" field, if present, specifies the charset of the "text" field.
+ * If it is "", the current locale's charset (if any) is assumed.  Else, the
+ * 8-bit charset of the server is assumed.
+ *
+ * A special invocation is with an unmodified initialized structure: this clears
+ * the client's whole display, letting the display of other applications on
+ * the same tty or of applications "under" the tty appear. See Concurrency
+ * management section of the BrlAPI documentation for more details.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_write(const brlapi_writeArguments_t *arguments);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__write(brlapi_handle_t *handle, const brlapi_writeArguments_t *arguments);
+
+/** @} */
+
+#include "brlapi_keycodes.h"
+
+/** \defgroup brlapi_keys Reading key presses
+ * \brief How to read key presses from the braille terminal
+ *
+ * Once brlapi_enterTtyMode() has been called, the application can call brlapi_readKey() to
+ * read key presses. Either key codes (see \ref brlapi_keycodes) or commands will be returned, depending
+ * on parameters given to brlapi_enterTtyMode().
+ *
+ * Key presses are buffered, so that calling brlapi_readKey() in non-blocking
+ * mode from time to time should suffice.
+ *
+ * @{
+ */
+
+/* brlapi_expandedKeyCode_t */
+/** Structure holding the components of a key code as returned by brlapi_expandKeyCode() */
+typedef struct {
+  unsigned int type /** the type value */;
+  unsigned int command /** the command value */;
+  unsigned int argument /** the argument value */;
+  unsigned int flags /** the flags value */;
+} brlapi_expandedKeyCode_t;
+
+/* brlapi_expandKeyCode */
+/** Expand the components of a key code
+ *
+ * \param code the key code to be expanded
+ * \param expansion pointer to the structure that receives the components
+ *
+ * \return 0 on success, -1 on error
+ */
+int BRLAPI_STDCALL brlapi_expandKeyCode (brlapi_keyCode_t code, brlapi_expandedKeyCode_t *expansion);
+
+/* brlapi_describedKeyCode_t */
+/** Structure holding the components of a key code as returned by brlapi_describeKeyCode() */
+typedef struct {
+  const char *type /** the  type name */;
+  const char *command /** the command name */;
+  unsigned int argument /** the argument value */;
+  unsigned int flags /** the flag count */;
+  const char *flag[64 - BRLAPI_KEY_FLAGS_SHIFT] /** the flag names */;
+  brlapi_expandedKeyCode_t values /** the actual values of the components */;
+} brlapi_describedKeyCode_t;
+
+/* brlapi_describeKeyCode */
+/** Describe the components of a key code.
+ *
+ * \param code the keycode to be described
+ * \param description pointer to the structure that receives the description
+ *
+ * \return 0 on success, -1 on error
+ */
+int BRLAPI_STDCALL brlapi_describeKeyCode (brlapi_keyCode_t code, brlapi_describedKeyCode_t *description);
+
+/** Unicode braille row */
+#define BRLAPI_UC_ROW	0x2800UL
+
+/* brlapi_readKey */
+/** Read a key from the braille keyboard
+ *
+ * This function returns one key press's code.
+ *
+ * If NULL or "" was given to brlapi_enterTtyMode(), a \e brltty command is returned,
+ * as described in the documentation for ::brlapi_keyCode_t . It is hence pretty
+ * driver-independent, and should be used by default when no other option is
+ * possible.
+ *
+ * By default, all commands but those which restart drivers and switch
+ * virtual terminals are returned to the application and not to brltty.
+ * If the application doesn't want to see some command events,
+ * it should call brlapi_ignoreKeys()
+ *
+ * If a driver name was given to brlapi_enterTtyMode(), a driver-specific keycode is returned,
+ * as specified by the braille driver, usually in <brltty/brldefs-xy> where xy
+ * is the driver's code. It generally corresponds to the very code that the
+ * terminal tells to the driver.
+ * This should only be used by applications which are dedicated to a particular
+ * braille terminal. Hence, checking the terminal type thanks to a call to
+ * brlapi_getDriverName() before getting tty control is a pretty good idea.
+ *
+ * By default, all the keypresses will be passed to the client, none will go
+ * through brltty, so the application will have to handle console switching
+ * itself for instance.
+ *
+ * \param wait tells whether the call should block until a key is pressed (1)
+ *  or should only probe key presses (0);
+ * \param code holds the key code if a key press is indeed read.
+ *
+ * \return -1 on error or signal interrupt and *code is then undefined, 0 if
+ * block was 0 and no key was pressed so far, or 1 and *code holds the key code.
+ *
+ * Programming hints:
+ *
+ * If your application is only driven by braille command keypresses, you can
+ * just call brlapi_readKey(1, &code) so that it keeps blocking, waiting for
+ * keypresses.
+ *
+ * Else, you'll probably want to use the file descriptor returned by
+ * brlapi_openConnection() or brlapi_getFileDescriptor() in your "big polling loop".
+ * For instance:
+ *
+ * - in a \c select() loop, just add it to the \c readfds and \c exceptfds file
+ *   descriptor sets;
+ * - in a gtk or atspi application, use
+ *   \c g_io_add_watch(fileDescriptor, \c G_IO_IN|G_IO_ERR|G_IO_HUP, \c f, \c data)
+ *   for adding a callback called \c f;
+ * - in an Xt/Xaw/motif-based application, use
+ *   \c XtAppAddInput(app_context, \c fileDescriptor, \c XtInputReadMask|XtInputExceptMask, \c f, \c data)
+ * - etc.
+ *
+ * and then, when you detect inbound trafic on the file descriptor, do something
+ * like this:
+ *
+ * while (brlapi_readKey(0, &code) {
+ *    // process keycode code
+ *    // ...
+ * }
+ *
+ * The \c while loop is needed for processing \e all pending key presses, else
+ * some of them may be left in libbrlapi's internal key buffer and you wouldn't
+ * get them immediately.
+ *
+ * \note If the read is interrupted by a signal or by a parameter change
+ * notification, brlapi_readKey() will return -1, brlapi_errno will be
+ * BRLAPI_ERROR_LIBCERR and errno will be EINTR.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_readKey(int wait, brlapi_keyCode_t *code);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__readKey(brlapi_handle_t *handle, int wait, brlapi_keyCode_t *code);
+
+/* brlapi_readKeyWithTimeout */
+/** Read a key from the braille keyboard, unless a timeout expires
+ *
+ * This function works like brlapi_readKey, except that parameter \e wait is
+ * replaced by a \e timeout_ms parameter
+ *
+ * \param timeout_ms specifies how long the function should wait for a keypress.
+ * \param code holds the key code if a key press is indeed read.
+ *
+ * \return -1 on error, signal interrupt or parameter change notification and
+ * *code is then undefined, 0 if the timeout expired and no key was pressed, or
+ * 1 and *code holds the key code.
+ *
+ * If the timeout expires without any key being pressed, 0 is returned.
+ *
+ * If timeout_ms is set to 0, this function looks for key events that have been
+ * already received, but does not wait at all if no event was received.
+ *
+ * If timeout_ms is set to a negative value, this function behaves like
+ * brlapi_readKey, i.e. it uses an infinite timeout.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_readKeyWithTimeout(int timeout_ms, brlapi_keyCode_t *code);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__readKeyWithTimeout(brlapi_handle_t *handle, int timeout_ms, brlapi_keyCode_t *code);
+
+/** types of key ranges */
+typedef enum {
+  brlapi_rangeType_all,	/**< all keys, code must be 0 */
+  brlapi_rangeType_type,	/**< all keys of a given type */
+  brlapi_rangeType_command,	/**< all keys of a given command block, i.e. matching the key type and command parts */
+  brlapi_rangeType_key,	/**< a given key with any flags */
+  brlapi_rangeType_code,	/**< a given key code */
+} brlapi_rangeType_t;
+
+/* brlapi_ignoreKeys */
+/** Ignore some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided keys to \e brltty, rather
+ * than returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param type type of keys to be ignored
+ * \param keys array of keys to be ignored
+ * \param count number of keys
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreKeys(brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreKeys(brlapi_handle_t *handle, brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+
+/* brlapi_acceptKeys */
+/** Accept some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided keys to the application,
+ * and not give them to \e brltty.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param type type of keys to be ignored
+ * \param keys array of keys to be ignored
+ * \param count number of keys
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptKeys(brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptKeys(brlapi_handle_t *handle, brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+
+/* brlapi_ignoreAllKeys */
+/** Ignore all key presses from the braille keyboard
+ *
+ * This function asks the server to give all keys to \e brltty, rather than
+ * returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreAllKeys(void);
+#define brlapi_ignoreAllKeys() brlapi_ignoreKeys(brlapi_rangeType_all, NULL, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreAllKeys(brlapi_handle_t *handle);
+#define brlapi__ignoreAllKeys(handle) brlapi__ignoreKeys(handle, brlapi_rangeType_all, NULL, 0)
+
+/* brlapi_acceptAllKeys */
+/** Accept all key presses from the braille keyboard
+ *
+ * This function asks the server to give all keys to the application, and not
+ * give them to \e brltty.
+ *
+ * Warning: after calling this function, make sure to call brlapi_ignoreKeys()
+ * for ignoring important keys like BRL_CMD_SWITCHVT_PREV/NEXT and such.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptAllKeys(void);
+#define brlapi_acceptAllKeys() brlapi_acceptKeys(brlapi_rangeType_all, NULL, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptAllKeys(brlapi_handle_t *handle);
+#define brlapi__acceptAllKeys(handle) brlapi__acceptKeys(handle, brlapi_rangeType_all, NULL, 0)
+
+/** Type for keycode ranges
+ *
+ * Denotes the set of keycodes between \e first and \e last (inclusive)
+ */
+typedef struct {
+	brlapi_keyCode_t first;	/**< first key of the range */
+	brlapi_keyCode_t last;	/**< last key of the range */
+} brlapi_range_t;
+
+/* brlapi_ignoreKeyRanges */
+/** Ignore some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided key ranges to \e brltty,
+ * rather than returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param ranges key ranges, which are inclusive, see \ref brlapi_keycodes about key ranges
+ * \param count number of ranges
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreKeyRanges(const brlapi_range_t ranges[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int count);
+
+/* brlapi_acceptKeyRanges */
+/** Accept some key presses from the braille keyboard
+ *
+ * This function asks the server to return the provided key ranges (inclusive)
+ * to the application, and not give them to \e brltty.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param ranges key ranges, which are inclusive, see \ref brlapi_keycodes about key ranges
+ * \param count number of ranges
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptKeyRanges(const brlapi_range_t ranges[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int count);
+/** @} */
+
+/** \defgroup brlapi_driverspecific Driver-Specific modes
+ * \brief Raw and Suspend Modes mechanism
+ *
+ * If the application wants to directly talk to the braille terminal, it should
+ * use Raw Mode. In this special mode, the driver gives the whole control of the
+ * terminal to it: \e brltty doesn't work any more.
+ *
+ * For this, it simply has to call brlapi_enterRawMode(), then brlapi_sendRaw()
+ * and brlapi_recvRaw(), and finally give control back thanks to
+ * brlapi_leaveRawMode().
+ *
+ * Special care of the terminal should be taken, since one might completely
+ * trash the terminal's data, or even lock it! The application should always
+ * check for terminal's type thanks to brlapi_getDriverName().
+ *
+ * The client can also make brltty close the driver by using brlapi_suspendDriver(),
+ * and resume it again with brlapi_resumeDriver().  This should not be used if
+ * possible: raw mode should be sufficient for any use.  If not, please ask for
+ * features :)
+ *
+ * @{
+ */
+
+/* brlapi_enterRawMode */
+/** Switch to Raw mode
+ *
+ * TODO: document which functions work in raw mode.
+ *
+ * \param driver Specifies the name of the driver for which the raw
+ * communication will be established.
+ * \return 0 on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterRawMode(const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterRawMode(brlapi_handle_t *handle, const char *driver);
+
+/* brlapi_leaveRawMode */
+/** Leave Raw mode
+ * \return 0 on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_leaveRawMode(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__leaveRawMode(brlapi_handle_t *handle);
+
+/* brlapi_sendRaw */
+/** Send Raw data
+ *
+ * \param buffer points on the data;
+ * \param size holds the packet size.
+ * \return size on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_sendRaw(const void *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+ssize_t BRLAPI_STDCALL brlapi__sendRaw(brlapi_handle_t *handle, const void *buffer, size_t size);
+
+/* brlapi_recvRaw */
+/** Get Raw data
+ *
+ * \param buffer points on a buffer where the function will store the received
+ * data;
+ * \param size holds the buffer size.
+ * \return its size, -1 on error, or on interruption by a signal or a parameter
+ * change notification, in which case brlapi_errno will be BRLAPI_ERROR_LIBCERR
+ * and errno will be EINTR. */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_recvRaw(void *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+ssize_t BRLAPI_STDCALL brlapi__recvRaw(brlapi_handle_t *handle, void *buffer, size_t size);
+
+/* brlapi_suspendDriver */
+/** Suspend braille driver
+ * \param driver Specifies the name of the driver which will be suspended.
+ * \return -1 on error
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_suspendDriver(const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__suspendDriver(brlapi_handle_t *handle, const char *driver);
+
+/* brlapi_resumeDriver */
+/** Resume braille driver
+ * \return -1 on error
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_resumeDriver(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__resumeDriver(brlapi_handle_t *handle);
+/** @} */
+
+#include "brlapi_param.h"
+
+/** \defgroup brlapi_parameterManagement Parameter management
+ * \brief How to manage BrlAPI parameters
+ *
+ * There are several kinds of parameters:
+ * - states associated with the braille device itself, such as its size or
+ * parameters of the device port
+ * - states of the BrlAPI connection itself, such as the displaying level or
+ * key passing preferences.
+ * - general states such as the cut buffer,
+ * - braille parameters: braille table, contraction, cursor shape, etc,
+ * - browse parameters: line skip, beep, etc.
+ *
+ * Some of them are subdivided in subparameters.  Others have only subparameter 0.
+ *
+ * Some of them are read-only, others are read/write.
+ *
+ * A client can either request the immediate content of a parameter by
+ * using brlapi_getParameter(); set the content of a parameter by using
+ * brlapi_setParameter(); or register a callback that may be called immediately
+ * and on each change of a given parameter, by using brlapi_watchParameter().
+ *
+ * @{ */
+
+/** Flags for parameter requests */
+typedef uint32_t brlapi_param_flags_t;
+#define BRLAPI_PARAMF_LOCAL          0X00    /**< Refer to the value local to the connection instead of the global value */
+#define BRLAPI_PARAMF_GLOBAL         0X01    /**< Refer to the global value instead of the value local to the connection */
+#define BRLAPI_PARAMF_SELF           0X02    /**< Specify whether to receive notifications of value self-changes */
+
+/* brlapi_getParameter */
+/** Get the content of a parameter
+ *
+ * brlapi_getParameter gets the current content of a parameter
+ *
+ * \param parameter is the parameter whose content shall be gotten;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be returned;
+ * \param data is a buffer where content of the parameter shall be stored;
+ * \param len is the size of the buffer.
+ *
+ * \return the real size of the parameter's content. If the parameter does not fit in the provided buffer, it is truncated to len bytes (but the real size of the parameter is still returned). In that case, the client must call brlapi_getParameter again with a big enough buffer.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_getParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len);
+#endif
+ssize_t BRLAPI_STDCALL brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len);
+
+/* brlapi_getParameterAlloc */
+/** Return the content of a parameter
+ *
+ * brlapi_getParameterAlloc gets the current content of a parameter, by returning it as a newly-allocated buffer.
+ * The buffer is allocated to one byte more than the parameter value. This byte is set to zero. This allows, for string parameters, to be able to immediately use it as a C string.
+ *
+ * \param parameter is the parameter whose content shall be gotten;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be returned;
+ * \param len is the address where to store the size of the parameter value.
+ *
+ * \return a newly-allocated buffer that contains the value of the parameter. The caller must call free() on it after use. NULL is returned on errors
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void * BRLAPI_STDCALL brlapi_getParameterAlloc(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len);
+#endif
+void * BRLAPI_STDCALL brlapi__getParameterAlloc(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len);
+
+/* brlapi_setParameter */
+/** Set the content of a parameter
+ *
+ * brlapi_setParameter sets the content of a parameter
+ *
+ * \param parameter is the parameter to set;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be set;
+ * \param data is a buffer containing the data to store in the parameter;
+ * \param len is the size of the data.
+ *
+ * \return 0 on success, -1 on error (read-only parameter for instance).
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_setParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len);
+#endif
+int BRLAPI_STDCALL brlapi__setParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len);
+
+/* brlapi_paramCallback_t */
+/** Callback for parameter changes
+ *
+ * When a parameter gets changed, application-defined callbacks set by the
+ * brlapi_watchParameter() function are called.
+ *
+ * \param parameter is the parameter that changed;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it was changed;
+ * \param priv is the void pointer that was passed to the brlapi_watchParameter call which registered the callback;
+ * \param data is a buffer containing the new value of the parameter;
+ * \param len is the size of the data.
+ *
+ * This callback only gets called when the application calls some brlapi_
+ * function (i.e. BrlAPI gets direct control of the execution).
+ */
+typedef void (*brlapi_paramCallback_t)(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len);
+
+/* brlapi_paramCallbackDescriptor_t */
+/** Type for callback descriptors
+ * This is returned by brlapi_watchParameter, to be passed to
+ * brlapi_unwatchParameter.
+ */
+typedef void *brlapi_paramCallbackDescriptor_t;
+
+/* brlapi_watchParameter */
+/** Set a parameter change callback
+ *
+ * brlapi_watchParameter registers a parameter change callback: whenever the
+ * given parameter changes, the given function is called.
+ *
+ * \param parameter is the parameter to watch;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be monitored;
+ * \param func is the function to call on parameter change;
+ * \param priv is a void pointer which will be passed as such to the function;
+ * \param data is a buffer where the current content of the parameter shall be
+ * stored;
+ * \param len is the size of the buffer.
+ *
+ * \return the callback descriptor (to be passed to brlapi_unwatchParameter to
+ * unregister the callback), or NULL on error.
+ *
+ * \note Default parameter callbacks don't do anything, except the ones for
+ * display size which just raise SIGWINCH.
+ * \note If data is NULL, the callback will be called immediately by
+ * brlapi_watchParameter, for providing the initial value
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi_watchParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len);
+#endif
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi__watchParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len);
+
+/* brlapi_unwatchParameter */
+/** Clear a parameter change callback
+ *
+ * brlapi_unwatchParameter unregisters a parameter change callback: the
+ * callback function previously registered with brlapi_watchParameter will
+ * not be called any longer.
+ *
+ * \param descriptor refers to the callback to be removed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_unwatchParameter(brlapi_paramCallbackDescriptor_t descriptor);
+#endif
+int BRLAPI_STDCALL brlapi__unwatchParameter(brlapi_handle_t *handle, brlapi_paramCallbackDescriptor_t descriptor);
+
+/** @} */
+
+/** \defgroup brlapi_misc Miscellaneous functions
+ * @{ */
+
+/* brlapi_pause */
+/**
+ * Waits until an event is received from the BrlAPI server
+ * \param timeout_ms specifies an optional timeout which can be zero for polling, or -1 for infinite wait
+ * \return 0 on timeout, -1 on error, or on interruption by a signal or a parameter
+ * change notification, in which case brlapi_errno will be BRLAPI_ERROR_LIBCERR
+ * and errno will be EINTR. */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_pause(int timeout_ms);
+#endif
+int BRLAPI_STDCALL brlapi__pause(brlapi_handle_t *handle, int timeout_ms);
+
+/* brlapi_sync */
+/**
+ * Synchronize against any pending exception, and returns it. This allows to
+ * synchronously catch exception raised by brlapi_write calls.
+ * This works by temporarily replacing the current exception handler by its own
+ * handler.
+ * \return 0 if no exception was pending, -1 if an exception was caught.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_sync(void);
+#endif
+int BRLAPI_STDCALL brlapi__sync(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_error Error handling
+ * \brief How to handle errors
+ *
+ * When a function fails, ::brlapi_errno will hold an error
+ * code to explain why it failed. It should always be reported somehow.
+ *
+ * Although most errors are reported that way, some (called exceptions)
+ * are reported asynchronously for efficiency reasons, because they always
+ * just report a programming error. The affected functions are: brlapi_setFocus,
+ * brlapi_write* and brlapi_sendRaw.  When they happen, the next call to
+ * brlapi_something will close the connection and call the \e exception
+ * handler. If the exception handler returns, the brlapi_something function will
+ * return an end-of-file error.
+ *
+ * The default exception handler (brlapi_defaultExceptionHandler()) dumps
+ * the guilty packet before abort()ing.  It can be replaced by calling
+ * brlapi_setExceptionHandler().  For instance, the Java and Python bindings use
+ * this for raising a Java or Python exception that may be caught.
+ *
+ * @{ */
+
+/** BrlAPI Error codes */
+enum brlapi_error {
+  BRLAPI_ERROR_SUCCESS             =  0,  /**< Success */
+#define  BRLAPI_ERROR_SUCCESS              0
+  BRLAPI_ERROR_NOMEM               =  1,  /**< Not enough memory */
+#define BRLAPI_ERROR_NOMEM                BRLAPI_ERROR_NOMEM
+  BRLAPI_ERROR_TTYBUSY             =  2,  /**< A connection is already running in this tty */
+#define BRLAPI_ERROR_TTYBUSY              BRLAPI_ERROR_TTYBUSY
+  BRLAPI_ERROR_DEVICEBUSY          =  3,  /**< A connection is already using RAW or suspend mode */
+#define BRLAPI_ERROR_DEVICEBUSY           BRLAPI_ERROR_DEVICEBUSY
+  BRLAPI_ERROR_UNKNOWN_INSTRUCTION =  4,  /**< Not implemented in protocol */
+#define BRLAPI_ERROR_UNKNOWN_INSTRUCTION  BRLAPI_ERROR_UNKNOWN_INSTRUCTION
+  BRLAPI_ERROR_ILLEGAL_INSTRUCTION =  5,  /**< Forbiden in current mode */
+#define BRLAPI_ERROR_ILLEGAL_INSTRUCTION  BRLAPI_ERROR_ILLEGAL_INSTRUCTION
+  BRLAPI_ERROR_INVALID_PARAMETER   =  6,  /**< Out of range or have no sense */
+#define BRLAPI_ERROR_INVALID_PARAMETER    BRLAPI_ERROR_INVALID_PARAMETER
+  BRLAPI_ERROR_INVALID_PACKET      =  7,  /**< Invalid size */
+#define BRLAPI_ERROR_INVALID_PACKET       BRLAPI_ERROR_INVALID_PACKET
+  BRLAPI_ERROR_CONNREFUSED         =  8,  /**< Connection refused */
+#define BRLAPI_ERROR_CONNREFUSED          BRLAPI_ERROR_CONNREFUSED
+  BRLAPI_ERROR_OPNOTSUPP           =  9,  /**< Operation not supported */
+#define BRLAPI_ERROR_OPNOTSUPP            BRLAPI_ERROR_OPNOTSUPP
+  BRLAPI_ERROR_GAIERR              = 10,  /**< Getaddrinfo error */
+#define BRLAPI_ERROR_GAIERR               BRLAPI_ERROR_GAIERR
+  BRLAPI_ERROR_LIBCERR             = 11,  /**< Libc error */
+#define BRLAPI_ERROR_LIBCERR              BRLAPI_ERROR_LIBCERR
+  BRLAPI_ERROR_UNKNOWNTTY          = 12,  /**< Couldn't find out the tty number */
+#define BRLAPI_ERROR_UNKNOWNTTY           BRLAPI_ERROR_UNKNOWNTTY
+  BRLAPI_ERROR_PROTOCOL_VERSION    = 13,  /**< Bad protocol version */
+#define BRLAPI_ERROR_PROTOCOL_VERSION     BRLAPI_ERROR_PROTOCOL_VERSION
+  BRLAPI_ERROR_EOF                 = 14,  /**< Unexpected end of file */
+#define BRLAPI_ERROR_EOF                  BRLAPI_ERROR_EOF
+  BRLAPI_ERROR_EMPTYKEY            = 15,  /**< Key file empty */
+#define BRLAPI_ERROR_EMPTYKEY             BRLAPI_ERROR_EMPTYKEY
+  BRLAPI_ERROR_DRIVERERROR         = 16,  /**< Packet returned by driver too large */
+#define BRLAPI_ERROR_DRIVERERROR          BRLAPI_ERROR_DRIVERERROR
+  BRLAPI_ERROR_AUTHENTICATION      = 17,  /**< Authentication failed */
+#define BRLAPI_ERROR_AUTHENTICATION       BRLAPI_ERROR_AUTHENTICATION
+  BRLAPI_ERROR_READONLY_PARAMETER  = 18,  /**< Parameter can not be changed */
+#define BRLAPI_ERROR_READONLY_PARAMETER   BRLAPI_ERROR_READONLY_PARAMETER
+};
+
+/* brlapi_errlist */
+/** Error message list
+ *
+ * These are the string constants used by brlapi_perror().
+ */
+extern const char *brlapi_errlist[];
+
+/* brlapi_nerr */
+/** Number of error messages */
+extern const int brlapi_nerr;
+
+/* brlapi_perror */
+/** Print a BrlAPI error message
+ *
+ * brlapi_perror() reads ::brlapi_error, and acts just like perror().
+ */
+void BRLAPI_STDCALL brlapi_perror(const char *s);
+
+/* brlapi_error_t */
+/** All information that is needed to describe brlapi errors */
+typedef struct {
+  enum brlapi_error brlerrno;
+  int libcerrno;
+  int gaierrno;
+  const char *errfun;
+} brlapi_error_t;
+
+/** Get per-thread error location
+ *
+ * In multithreaded software, ::brlapi_error is thread-specific, so api.h
+ * cheats about the brlapi_error token and actually calls
+ * brlapi_error_location().
+ *
+ * This gets the thread specific location of global variable ::brlapi_error
+ */
+brlapi_error_t * BRLAPI_STDCALL brlapi_error_location(void);
+
+/** Global variable brlapi_error
+ *
+ * ::brlapi_error is a global left-value containing the last error information.
+ * Its errno field is not reset to BRLAPI_ERROR_SUCCESS on success.
+ *
+ * This information may be copied in brlapi_error_t variables for later use
+ * with the brlapi_strerror function.
+ */
+extern brlapi_error_t brlapi_error;
+
+/** Shorthand for brlapi_error.errno */
+extern enum brlapi_error brlapi_errno;
+/** Shorthand for brlapi_error.libcerrno */
+extern int brlapi_libcerrno;
+/** Shorthand for brlapi_error.gaierrno */
+extern int brlapi_gaierrno;
+/** Shorthand for brlapi_error.errfun */
+extern const char *brlapi_errfun;
+
+/** Cheat about the brlapi_error C token */
+#define brlapi_error (*brlapi_error_location())
+/** Cheat about the brlapi_errno C token */
+#define brlapi_errno (brlapi_error.brlerrno)
+/** Cheat about the brlapi_libcerrno C token */
+#define brlapi_libcerrno (brlapi_error.libcerrno)
+/** Cheat about the brlapi_gaierrno C token */
+#define brlapi_gaierrno (brlapi_error.gaierrno)
+/** Cheat about the brlapi_errfun C token */
+#define brlapi_errfun (brlapi_error.errfun)
+
+/* brlapi_strerror */
+/** Get plain error message
+ *
+ * brlapi_strerror() returns the plain error message corresponding to its
+ * argument. Since the message is a constant string, the application must not
+ * free it. Also, this makes it unsafe in threaded environments,
+ * brlapi_strerror_r() should be used instead in that case.
+ */
+const char * BRLAPI_STDCALL brlapi_strerror(const brlapi_error_t *error);
+
+/* brlapi_strerror_r */
+/** Store plain error message
+ *
+ * brlapi_strerror_r() stores the plain error message corresponding to its
+ * \p error argument. \p buflen has to be set to the size of \p buf, and
+ * brlapi_strerror_r() will store at most \p buflen bytes in \p buf. If \p
+ * buflen is not large enough for the whole error message, it will be truncated,
+ * but a trailing \0 character will still be set at buf[buflen-1].
+ *
+ * \returns the number of characters that should have been stored in \p buf
+ * (without the trailing \0 character). A value greater or equal to \p buflen
+ * thus means that the output was truncated.
+ *
+ * If \p buflen is set to 0, \p buf can be set to NULL, and brlapi_strerror_r
+ * will thus only return the number of characters that would have been stored
+ * (without the trailing \0 character).
+ */
+size_t BRLAPI_STDCALL brlapi_strerror_r(const brlapi_error_t *error, char *buf, size_t buflen);
+
+/** Type for packet type. Only unsigned can cross networks, 32bits */
+typedef uint32_t brlapi_packetType_t;
+
+/* brlapi_getPacketTypeName */
+/** Get plain packet type
+ *
+ * brlapi_getPacketTypeName() returns the plain packet type name corresponding to
+ * its argument.
+ */
+const char * BRLAPI_STDCALL brlapi_getPacketTypeName(brlapi_packetType_t type);
+
+/* brlapi_exceptionHandler_t */
+/** Types for exception handlers
+ *
+ * Types of exception handlers which are to be given to
+ * brlapi_setExceptionHandler() and brlapi__setExceptionHandler().
+ *
+ * \param error is a BRLAPI_ERROR_ error code;
+ * \param type is the type of the guilty packet;
+ * \param packet points to the content of the guilty packet (might be a little bit truncated);
+ * \param size gives the guilty packet's size.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+typedef void (BRLAPI_STDCALL *brlapi_exceptionHandler_t)(int error, brlapi_packetType_t type, const void *packet, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+typedef void (BRLAPI_STDCALL *brlapi__exceptionHandler_t)(brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size);
+
+/* brlapi_strexception */
+/** Describes an exception
+ *
+ * brlapi_strexception() puts a text describing the given exception in buffer.
+ *
+ * The beginning of the guilty packet is dumped as a sequence of hex bytes.
+ *
+ * \return the size of the text describing the exception, following
+ * snprintf()'s semantics.
+*/
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_strexception(char *buffer, size_t bufferSize, int error, brlapi_packetType_t type, const void *packet, size_t packetSize);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__strexception(brlapi_handle_t *handle, char *buffer, size_t bufferSize, int error, brlapi_packetType_t type, const void *packet, size_t packetSize);
+
+/* brlapi_setExceptionHandler */
+/** Set a new exception handler
+ *
+ * brlapi_setExceptionHandler() replaces the previous exception handler with
+ * the handler parameter. The previous exception handler is returned to make
+ * chaining error handlers possible.
+ *
+ * The default handler just prints the exception and abort()s.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_exceptionHandler_t BRLAPI_STDCALL brlapi_setExceptionHandler(brlapi_exceptionHandler_t handler);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi__exceptionHandler_t BRLAPI_STDCALL brlapi__setExceptionHandler(brlapi_handle_t *handle, brlapi__exceptionHandler_t handler);
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_defaultExceptionHandler(int error, brlapi_packetType_t type, const void *packet, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__defaultExceptionHandler(brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size);
+
+/** @} */
+
+/* Windows-specific tricks - don't look at this */
+#ifdef BRLAPI_WIN32
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeTextWin(int cursor, const void *str, int wide);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeTextWin(brlapi_handle_t *handle, int cursor, const void *str, int wide);
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeWin(const brlapi_writeArguments_t *s, int wide);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeWin(brlapi_handle_t *handle, const brlapi_writeArguments_t *s, int wide);
+
+#ifdef UNICODE
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_writeText(cursor, str) brlapi_writeTextWin(cursor, str, 1)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__writeText(handle, cursor, str) brlapi__writeTextWin(handle, cursor, str, 1)
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_write(s) brlapi_writeWin(s, 1)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__write(handle, s) brlapi__writeWin(handle, s, 1)
+
+#else /* UNICODE */
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_writeText(cursor, str) brlapi_writeTextWin(cursor, str, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__writeText(handle, cursor, str) brlapi__writeTextWin(handle, cursor, str, 0)
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_write(s) brlapi_writeWin(s, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__write(handle, s) brlapi__writeWin(handle, s, 0)
+
+#endif /* UNICODE */
+#endif /* BRLAPI_WIN32 */
+
+#ifndef BRLAPI_NO_DEPRECATED
+/** \defgroup brlapi_deprecated Deprecated names
+ *
+ * With version 0.5.0, BrlAPI is now provided through including <brlapi.h> and
+ * got a big renaming pass. Old names are still available through macros, but
+ * they are deprecated since they will get dropped in the next release. This
+ * documentation is for you to know the new names.
+ *
+ * For checking that you have completely switched to new names, just define
+ * BRLAPI_NO_DEPRECATED: that will disable compatibility macros.
+ *
+ * @{ */
+
+#define brlapi_settings_t brlapi_connectionSettings_t
+
+/** brlapi_writeStruct, replaced by brlapi_writeArguments_t */
+typedef struct {
+  int displayNumber;
+  unsigned int regionBegin;
+  unsigned int regionSize;
+  const char *text;
+  int textSize;
+  const unsigned char *attrAnd;
+  const unsigned char *attrOr;
+  int cursor;
+  const char *charset;
+} brlapi_writeStruct;
+#define BRLAPI_WRITESTRUCT_INITIALIZER BRLAPI_WRITEARGUMENTS_INITIALIZER
+
+#define brl_keycode_t brlapi_keyCode_t
+#define brl_type_t brlapi_packetType_t
+
+#define BRLCOMMANDS NULL
+#define BRL_KEYCODE_MAX BRLAPI_KEY_MAX
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_initializeConnection brlapi_openConnection
+#define brlapi_getTty brlapi_enterTtyMode
+#define brlapi_getTtyPath brlapi_enterTtyModeWithPath
+#define brlapi_leaveTty brlapi_leaveTtyMode
+#define brlapi_unignoreKeyRange brlapi_acceptKeyRange
+#define brlapi_unignoreKeySet brlapi_acceptKeySet
+#define brlapi_getRaw brlapi_enterRawMode
+#define brlapi_leaveRaw brlapi_leaveRawMode
+#define brlapi_suspend brlapi_suspendDriver
+#define brlapi_resume brlapi_resumeDriver
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+
+#define BRLERR_SUCCESS                 BRLAPI_ERROR_SUCCESS
+#define BRLERR_NOMEM                   BRLAPI_ERROR_NOMEM
+#define BRLERR_TTYBUSY                 BRLAPI_ERROR_TTYBUSY
+#define BRLERR_DEVICEBUSY              BRLAPI_ERROR_DEVICEBUSY
+#define BRLERR_UNKNOWN_INSTRUCTION     BRLAPI_ERROR_UNKNOWN_INSTRUCTION
+#define BRLERR_ILLEGAL_INSTRUCTION     BRLAPI_ERROR_ILLEGAL_INSTRUCTION
+#define BRLERR_INVALID_PARAMETER       BRLAPI_ERROR_INVALID_PARAMETER
+#define BRLERR_INVALID_PACKET          BRLAPI_ERROR_INVALID_PACKET
+#define BRLERR_CONNREFUSED             BRLAPI_ERROR_CONNREFUSED
+#define BRLERR_OPNOTSUPP               BRLAPI_ERROR_OPNOTSUPP
+#define BRLERR_GAIERR                  BRLAPI_ERROR_GAIERR
+#define BRLERR_LIBCERR                 BRLAPI_ERROR_LIBCERR
+#define BRLERR_UNKNOWNTTY              BRLAPI_ERROR_UNKNOWNTTY
+#define BRLERR_PROTOCOL_VERSION        BRLAPI_ERROR_PROTOCOL_VERSION
+#define BRLERR_EOF                     BRLAPI_ERROR_EOF
+#define BRLERR_EMPTYKEY                BRLAPI_ERROR_EMPTYKEY
+#define BRLERR_DRIVERERROR             BRLAPI_ERROR_DRIVERERROR
+
+/** @} */
+#endif /* BRLAPI_NO_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED */
diff --git a/Programs/brlapi.h.in b/Programs/brlapi.h.in
new file mode 100644
index 0000000..2b49ed8
--- /dev/null
+++ b/Programs/brlapi.h.in
@@ -0,0 +1,1577 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** \file
+ * \brief Types, defines and functions prototypes for \e BrlAPI's library
+ */
+
+#ifndef BRLAPI_INCLUDED
+#define BRLAPI_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#undef BRLAPI_WIN32
+
+/** \defgroup brlapi_version Version of the BrlAPI library
+ * @{ */
+
+/** Library version. */
+#undef BRLAPI_RELEASE
+
+/** Library major version. */
+#undef BRLAPI_MAJOR
+
+/** Library minor version. */
+#undef BRLAPI_MINOR
+
+/** Library revision. */
+#undef BRLAPI_REVISION
+
+/** Returns the version of the library */
+void brlapi_getLibraryVersion(int *major, int *minor, int *revision);
+
+/** @} */
+
+/* Types are defined there */
+#include <sys/types.h>
+
+#ifdef BRLAPI_WIN32
+#include <windows.h>
+#define BRLAPI_STDCALL __stdcall
+#else /* BRLAPI_WIN32 */
+#define BRLAPI_STDCALL
+#endif /* BRLAPI_WIN32 */
+
+#ifdef _MSC_VER
+typedef __int64 uint64_t;
+typedef __int32 uint32_t;
+#define UINT64_C(x) (x ## Ui64)
+#define PRIx64 "I64x"
+typedef signed int ssize_t;
+#else /* _MSC_VER */
+
+/* this is for uint*_t */
+#include <stdint.h>
+
+/* NULL is defined there */
+#include <unistd.h>
+
+#include <inttypes.h> /* For PRIx64 */
+#endif /* _MSC_VER */
+
+#include <wchar.h>
+
+/** \defgroup brlapi_handles BrlAPI handles
+ *
+ * Each function provided by BrlAPI comes in two versions.
+ *
+ * 1. A version whose name is prefixed by brlapi_ for clients opening only
+ * one simultaneous connection with BrlAPI (most frequen case)
+ *
+ * 2. A version whose name is prefixed by brlapi__ for use by clients
+ * wishing to open more than one connection to BrlAPI.
+ *
+ * A function called brlapi__foo is used in exactly the same way as its
+ * brlapi_foo counterpart, except that it takes an additional argument
+ * (the first one), which is a handle letting the client refer to a given
+ * connection in a similar manner to what file descriptors do.
+ *
+ * In case you want to check that your code is not erroneously using brlapi_foo
+ * functions, define BRLAPI_NO_SINGLE_SESSION before including <brlapi.h>: that
+ * will disable the declaration of all single session functions.
+ *
+ * @{ */
+
+/** Type for BrlAPI hanles */
+typedef struct brlapi_handle_t brlapi_handle_t;
+
+/** Returns the size of an object of type brlapi_handle_t in bytes */
+size_t BRLAPI_STDCALL brlapi_getHandleSize(void);
+
+/** @} */
+
+/** \defgroup brlapi_connection Connecting to BrlAPI
+ *
+ * Before calling any other function of the library, calling
+ * brlapi_openConnection() is needed to establish a connection to
+ * \e BrlAPI 's server.
+ * When the connection is not needed any more, brlapi_closeConnection() must be
+ * called to close the connection.
+ *
+ * @{ */
+
+/** Default port number on which connections to \e BrlAPI can be established */
+#define BRLAPI_SOCKETPORTNUM 4101
+#define BRLAPI_SOCKETPORT "4101"
+
+/** Default unix path on which connections to \e BrlAPI can be established */
+#undef BRLAPI_SOCKETPATH
+
+/** \e brltty 's settings directory
+ *
+ * This is where authorization key and driver-dependent key names are found
+ * for instance. */
+#undef BRLAPI_ETCDIR
+
+/** Default name of the file containing \e BrlAPI 's authorization key
+ *
+ * This name is relative to BRLAPI_ETCDIR */
+#undef BRLAPI_AUTHKEYFILE
+
+/** Default authorization setting */
+#ifdef BRLAPI_WIN32
+/* No authentication by default on Windows */
+#define BRLAPI_DEFAUTH "none"
+#else /* BRLAPI_WIN32 */
+#define BRLAPI_DEFAUTH_KEYFILE "keyfile:" BRLAPI_ETCDIR "/" BRLAPI_AUTHKEYFILE
+
+#ifdef HAVE_POLKIT
+#define BRLAPI_DEFAUTH_POLKIT "+polkit"
+#else /* HAVE_POLKIT */
+#define BRLAPI_DEFAUTH_POLKIT ""
+#endif /* HAVE_POLKIT */
+
+#define BRLAPI_DEFAUTH BRLAPI_DEFAUTH_KEYFILE BRLAPI_DEFAUTH_POLKIT
+#endif /* BRLAPI_WIN32 */
+
+/** OS-dependent file descriptor type
+ *
+ * This is the type for file descriptors returned by brlapi_openConnection() and
+ * brlapi_getFileDescriptor().
+ */
+#ifdef __MINGW32__
+typedef HANDLE brlapi_fileDescriptor;
+#else /* __MINGW32__ */
+typedef int brlapi_fileDescriptor;
+#endif /* __MINGW32__ */
+
+/** Invalid value for brlapi_fileDescriptor
+ *
+ * This is returned by brlapi_getFileDescriptor() when the connection is closed.
+ */
+#ifdef __MINGW32__
+#define BRLAPI_INVALID_FILE_DESCRIPTOR INVALID_HANDLE_VALUE
+#else /* __MINGW32__ */
+#define BRLAPI_INVALID_FILE_DESCRIPTOR -1
+#endif /* __MINGW32__ */
+
+/** \brief Settings structure for \e BrlAPI connection
+ *
+ * This structure holds every parameter needed to connect to \e BrlAPI: which
+ * file the authorization key can be found in and which computer to connect to.
+ *
+ * \par Examples:
+ * \code
+ * brlapi_connectionSettings_t settings;
+ *
+ * settings.auth="/etc/brlapi.key";
+ * settings.host="foo";
+ * \endcode
+ *
+ * \e libbrlapi will read authorization key from file \p /etc/brlapi.key
+ * and connect to the machine called "foo", on the default TCP port.
+ *
+ * \code
+ * settings.host="10.1.0.2";
+ * \endcode
+ *
+ * lets directly enter an IP address instead of a machine name.
+ *
+ * \code
+ * settings.host=":1";
+ * \endcode
+ *
+ * lets \e libbrlapi connect to the local computer, on port BRLAPI_SOCKETPORTNUM+1
+ *
+ * \sa brlapi_openConnection()
+ */
+typedef struct {
+  /** For security reasons, \e libbrlapi has to get authorized to connect to the
+   * \e BrlAPI server. This can be done via a secret key, for instance. This is
+   * the path to the file which holds it; it will hence have to be readable by
+   * the application.
+   *
+   * Setting \c NULL defaults it to local installation setup or to the content
+   * of the BRLAPI_AUTH environment variable, if it exists. */
+  const char *auth;
+
+  /** This tells where the \e BrlAPI server resides: it might be listening on
+   * another computer, on any TCP port. It should look like "foo:1", which
+   * means TCP port number BRLAPI_SOCKETPORTNUM+1 on computer called "foo".
+   * \note Please check that resolving this name works before complaining
+   *
+   * Settings \c NULL defaults it to localhost, using the local installation's
+   * default TCP port, or to the content of the BRLAPI_HOST environment
+   * variable, if it exists. */
+  const char *host;
+} brlapi_connectionSettings_t;
+
+/* BRLAPI_SETTINGS_INITIALIZER */
+/** Allows to initialize a structure of type \e brlapi_connectionSettings_t *
+ * with default values. */
+#define BRLAPI_SETTINGS_INITIALIZER { NULL, NULL }
+
+/* brlapi_openConnection */
+/** Open a socket and connect it to \e BrlAPI 's server
+ *
+ * This function first loads an authorization key as specified in settings.
+ * It then creates a TCP socket and connects it to the specified machine, on
+ * the specified port. It writes the authorization key on the socket and
+ * waits for acknowledgement.
+ *
+ * \return the file descriptor, or BRLAPI_INVALID_FILE_DESCRIPTOR on error
+ *
+ * \note The file descriptor is returned in case the client wants to
+ * communicate with the server without using \e libbrlapi functions. If it uses
+ * them however, it won't have to pass the file descriptor later, since the
+ * library keeps a copy of it. But that also means that
+ * brlapi_openConnection() may be called several times, but \e libbrlapi
+ * functions will always work with the last call's descriptor
+ *
+ * \par Example:
+ * \code
+ * if (brlapi_openConnection(&settings,&settings)<0) {
+ *  fprintf(stderr,"couldn't connect to BrlAPI at %s: %s\n",
+ *   settings.host, brlapi_strerror(&brlapi_error));
+ *  exit(1);
+ * }
+ * \endcode
+ *
+ * \par Errors:
+ * \e BrlAPI might not be on this TCP port, the host name might not be
+ * resolvable, the authorization may fail,...
+ *
+ * \param desiredSettings this gives the desired connection parameters, as described
+ * in brlapi_connectionSettings_t. If \c NULL, defaults values are used, so that it is
+ * generally a good idea to give \c NULL as default, and only fill a
+ * brlapi_connectionSettings_t structure when the user gave parameters to the program
+ * for instance;
+ * \param actualSettings if not \c NULL, parameters which were actually used are
+ * stored here, if the application ever needs them. Since they are constant
+ * strings, or come from a getenv call, these must not be freed by the
+ * application.
+ *
+ * \sa
+ * brlapi_connectionSettings_t
+ * brlapi_writePacket()
+ * brlapi_readPacketHeader()
+ * brlapi_readPacketContent()
+ * brlapi_readPacket()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_openConnection(const brlapi_connectionSettings_t *desiredSettings, brlapi_connectionSettings_t *actualSettings);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__openConnection(brlapi_handle_t *handle, const brlapi_connectionSettings_t *desiredSettings, brlapi_connectionSettings_t *actualSettings);
+
+/* brlapi_fileDescriptor */
+/** Return the file descriptor used by the BrlAPI connection
+ *
+ * This allows to use it with \c select(), \c g_io_add_watch(), \c
+ * XtAppAddInput(), etc.
+ *
+ * BRLAPI_INVALID_FILE_DESCRIPTOR is returned when the connection was closed.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_getFileDescriptor(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__getFileDescriptor(brlapi_handle_t *handle);
+
+
+/* brlapi_closeConnection */
+/** Cleanly close the socket
+ *
+ * This function locks until a closing acknowledgement is received from the
+ * server. The socket is then freed, so the file descriptor
+ * brlapi_openConnection() gave has no meaning any more
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_closeConnection(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__closeConnection(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_clientData Setting and getting client data
+ *
+ * Clients can register a pointer to data that can then be used
+ * e.g. in exception handlers.
+ * @{ */
+
+/* brlapi__setClientData */
+/** Register a pointer to client data
+ *
+ * \param data The pointer to the private client data
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_setClientData(void *data);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__setClientData(brlapi_handle_t *handle, void *data);
+
+/* brlapi__getClientData */
+/** Retrieves the pointer to the private client data
+ *
+ * \return the pointer to the private client data
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void * BRLAPI_STDCALL brlapi_getClientData(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void * BRLAPI_STDCALL brlapi__getClientData(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_info Getting Terminal information
+ * \brief How to get information about the connected Terminal
+ *
+ * Before using Raw mode or key codes, the application should always check the
+ * type of the connected terminal, to be sure it is really the one it expects.
+ *
+ * One should also check for display size, so as to adjust further displaying
+ * on it.
+ * @{
+ */
+
+/** Maximum name length for driver names embeded in BrlAPI packets, not counting
+ * any termination \\0 character */
+#define BRLAPI_MAXNAMELENGTH 31
+
+/* brlapi_getDriverName */
+/** Return the complete name of the driver used by \e brltty
+ *
+ * This function fills its argument with the whole name of the braille
+ * driver if available, terminated with a '\\0'.
+ *
+ * \param buffer is the buffer provided by the application;
+ * \param size is the maximum size for the name buffer.
+ *
+ * \return -1 on error, otherwise a positive value giving the size of the needed
+ * buffer (including '\\0'). If that value is bigger than \p size, the value was
+ * truncated and the caller should retry with a bigger buffer accordingly.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getDriverName(char *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getDriverName(brlapi_handle_t *handle, char *buffer, size_t size);
+
+/* brlapi_getModelIdentifier */
+/** Return an identifier for the device model used by \e brltty
+ *
+ * This function fills its argument with the whole identifier of the braille
+ * device model if available, terminated with a '\\0'.
+ *
+ * \param buffer is the buffer given by the application;
+ * \param size is the maximum size for the identifier buffer.
+ *
+ * \return -1 on error, otherwise a positive value giving the size of the needed
+ * buffer (including '\\0'). If that value is bigger than \p size, the value was
+ * truncated and the caller should retry with a bigger buffer accordingly.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getModelIdentifier(char *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getModelIdentifier(brlapi_handle_t *handle, char *buffer, size_t size);
+
+/* brlapi_getDisplaySize */
+/** Return the size of the braille display */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_getDisplaySize(unsigned int *x, unsigned int *y);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__getDisplaySize(brlapi_handle_t *handle, unsigned int *x, unsigned int *y);
+
+/** @} */
+
+/** \defgroup brlapi_tty Entering & leaving tty mode
+ * \brief How to take control of ttys for direct braille display / read
+ *
+ * Before being able to write on the braille display, the application must tell
+ * the server which tty it will handle.
+ *
+ * The application must also specify how braille keys will be delivered to it.
+ * Two ways are possible:
+ *
+ * - driver-specific keycodes: applications will get values specific to each braille driver, since the keycode, as
+ *   defined in the driver will be given for each key event (press or release).
+ *   Using them leads to building highly driver-dependent applications, which
+ *   can yet sometimes be useful to mimic existing proprietary applications
+ *   for instance.
+ * - commands: applications will get exactly the same values as
+ *   \e brltty. This allows driver-independent clients, which will hopefully
+ *   be nice to use with a lot of different terminals.
+ * \sa brlapi_readKey()
+ * @{
+ */
+
+/* brlapi_enterTtyMode */
+/** Ask for some tty, with some key mechanism
+ *
+ * \param tty
+ * - If tty>=0 then take control of the specified tty.
+ * - If tty==::BRLAPI_TTY_DEFAULT then take control of the default tty.
+ *
+ * \param driver tells how the application wants brlapi_readKey() to return
+ * key presses. NULL or "" means BRLTTY commands are required,
+ * whereas a driver name means that driver-specific keycodes are expected.
+ *
+ * \return the used tty number on success, -1 on error
+ *
+ * WINDOWPATH and WINDOWID should be propagated when running remote applications
+ * via ssh, for instance, along with BRLAPI_HOST and the authorization key (see
+ * SendEnv in ssh_config(5) and AcceptEnv in sshd_config(5))
+ *
+ * Once brlapi_enterTtyMode() is called, brlapi_leaveTtyMode() has to be called
+ * before calling brlapi_enterTtyMode() again.
+ *
+ * TODO: document which functions work in TTY mode only.
+ *
+ * \sa brlapi_leaveTtyMode() brlapi_readKey()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterTtyMode(int tty, const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterTtyMode(brlapi_handle_t *handle, int tty, const char *driver);
+
+/** Select the default tty.
+ *
+ * The library takes the following steps:
+ * -# Try to get the tty number from the \c WINDOWID environment variable (for the \c xterm case).
+ * -# Try to get the tty number from the \c CONTROLVT environment variable.
+ * -# Read \c /proc/self/stat (on \c Linux).
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#define BRLAPI_TTY_DEFAULT -1
+
+/* brlapi_enterTtyModeWithPath */
+/** Ask for some tty specified by its path in the tty tree, with some key mechanism
+ *
+ * \param ttys points on the array of ttys representing the tty path to be got.
+ * Can be NULL if nttys is 0.
+ * \param count gives the number of elements in ttys.
+ * \param driver has the same meaning as in brlapi_enterTtyMode()
+ *
+ * Providing nttys == 0 means to get the root, which is usually what a screen
+ * readers wants to use.
+ *
+ * The content of WINDOWPATH or XDG_VTNR will always be prepended to the given
+ * \p ttys array, so the application does not need to determine by itself where
+ * it is running.
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterTtyModeWithPath(const int *ttys, int count, const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterTtyModeWithPath(brlapi_handle_t *handle, const int *ttys, int count, const char *driver);
+
+/* brlapi_leaveTtyMode */
+/** Stop controlling the tty
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_leaveTtyMode(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__leaveTtyMode(brlapi_handle_t *handle);
+
+/* brlapi_setFocus */
+/** Tell the current tty to brltty
+ *
+ * This is intended for focus tellers, such as brltty, xbrlapi, screen, ...
+ * brlapi_enterTtyMode() must have been called beforehand to tell where this focus
+ * applies in the tty tree.
+ *
+ * \return 0 on success, -1 on error.
+ *
+ * \sa brlapi_enterTtyMode() brlapi_leaveTtyMode()
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_setFocus(int tty);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__setFocus(brlapi_handle_t *handle, int tty);
+
+/** @} */
+
+/** \defgroup brlapi_write Writing on the braille display
+ * \brief Write text to the braille display
+ *
+ * After brlapi_enterTtyMode() has been called, the application can
+ * call one of these functions to write things on the braille display.
+ *
+ * \note Be sure to call brlapi_enterTtyMode() \e before calling brlapi_write(), or
+ * else you'll get an error. This is particularly not always trivial when
+ * writing multithreaded applications.
+ *
+ * \note Dots are coded as described in ISO/TR 11548-1: a dot pattern is coded
+ * by a byte in which bit 0 is set iff dot 1 is up, bit 1 is set iff dot 2 is
+ * up, ... bit 7 is set iff dot 8 is up. This also corresponds to the low-order
+ * byte of the coding of unicode's braille row U+2800.
+ *
+ * \note Text is translated by the server one to one, by just using a simple
+ * wchar_t to pattern table, i.e. no contraction/expansion is performed, because
+ * the client would then have no way to know how wide the output would be and
+ * thus the quantity of text to feed.  If contraction/expansion is desired, the
+ * client should perform it itself (e.g. thanks to liblouis or gnome-braille)
+ * and send the resulting dot patterns.  This is actually exactly the same
+ * problem as font rendering on a graphical display: for better control,
+ * nowadays all font rasterization is performed on the client side, and mere
+ * pixmaps are sent to the X server.
+ *
+ * \note For braille displays with multiple lines, text will be wrapped over the
+ * lines.
+ *
+ * @{ */
+
+/* brlapi_writeText */
+/** Write the given \\0-terminated string to the braille display
+ *
+ * If the string is too long, it is truncated. If it's too short,
+ * it is padded with spaces. The text is assumed to be in the current application
+ * locale charset set by setlocale(3) if it was called, or the locale charset
+ * from the application locale environment variables if setlocale(3) was not called.
+ *
+ * \param cursor gives the cursor position; if equal to ::BRLAPI_CURSOR_OFF, no cursor is shown at
+ * all; if cursor==::BRLAPI_CURSOR_LEAVE, the cursor is left where it is
+ *
+ * \param text points to the string to be displayed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeText(int cursor, const char *text);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeText(brlapi_handle_t *handle, int cursor, const char *text);
+
+/* brlapi_writeWText */
+/** Write the given \\0-terminated unicode string to the braille display
+ *
+ * If the string is too long, it is truncated. If it's too short,
+ * it is padded with spaces.
+ *
+ * \param cursor gives the cursor position; if equal to ::BRLAPI_CURSOR_OFF, no cursor is shown at
+ * all; if cursor==::BRLAPI_CURSOR_LEAVE, the cursor is left where it is
+ *
+ * \param text points to the string to be displayed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeWText(int cursor, const wchar_t *text);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeWText(brlapi_handle_t *handle, int cursor, const wchar_t *text);
+
+/* brlapi_writeDots */
+/** Write the given dots array to the display
+ *
+ * \param dots points on an array of dot information, one per character. Its
+ * size must hence be the same as what brlapi_getDisplaySize() returns.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeDots(const unsigned char *dots);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeDots(brlapi_handle_t *handle, const unsigned char *dots);
+
+/* brlapi_writeArguments_t */
+/** Structure containing arguments to be given to brlapi_write() */
+typedef struct {
+  int displayNumber /** Display number ::BRLAPI_DISPLAY_DEFAULT == unspecified */;
+  unsigned int regionBegin /** Region of display to update, 1st character of display is 1, 1st character of the second line of display is the width of a line plus 1, etc. Regions are always a linear wrap over the different lines. */;
+  int regionSize /** Number of characters held in andMask and orMask. If negative, its absolute value is taken into account, and the output is padded or truncated to fill the rest of the display. */;
+  const char *text /** Text to display, must hold as many characters as the region fields expresses. */;
+  int textSize /** Size of text in bytes. If -1, strlen() is used for computing it. */;
+  const unsigned char *andMask /** And attributes; applied first */;
+  const unsigned char *orMask /** Or attributes; applied \e after ANDing */;
+  int cursor /** ::BRLAPI_CURSOR_LEAVE == don't touch, ::BRLAPI_CURSOR_OFF == turn off, 1 = 1st char of display, ... */;
+  const char *charset /** Text charset. NULL means it is assumed to be 8bits, and the same as the server's. "" means current locale's charset. If no locale was selected, defaults to NULL's meaning. */;
+} brlapi_writeArguments_t;
+
+/** Write to the default display on the braille device.
+ *
+ * \sa brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_DISPLAY_DEFAULT -1
+
+/** Do not change the cursor's state or position.
+ *
+ * \sa brlapi_writeText() brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_CURSOR_LEAVE -1
+
+/** Do not display the cursor.
+ *
+ * \sa brlapi_writeText() brlapi_write() brlapi_writeArguments_t
+ */
+#define BRLAPI_CURSOR_OFF 0
+
+/* BRLAPI_WRITEARGUMENTS_INITIALIZER */
+/** Allows to initialize a structure of type \e brlapi_writeArguments_t *
+ * with default values:
+ * displayNumber = ::BRLAPI_DISPLAY_DEFAULT; (unspecified)
+ * regionBegin = regionSize = 0; (update the whole display, DEPRECATED and will
+ * be forbidden in next release. You must always express the region you wish to
+ * update)
+ * text = andMask = orMask = NULL; (no text, no attribute)
+ * cursor = ::BRLAPI_CURSOR_LEAVE; (don't touch cursor)
+*/
+#define BRLAPI_WRITEARGUMENTS_INITIALIZER { \
+  .displayNumber = BRLAPI_DISPLAY_DEFAULT, \
+  .regionBegin = 0, \
+  .regionSize = 0, \
+  .text = NULL, \
+  .textSize = -1, \
+  .andMask = NULL, \
+  .orMask = NULL, \
+  .cursor = BRLAPI_CURSOR_LEAVE, \
+  .charset = NULL \
+}
+
+/* brlapi_write */
+/** Update a specific region of the braille display and apply and/or masks
+ *
+ * \param arguments gives information necessary for the update
+ *
+ * regionBegin and regionSize must be filled for specifying which part of the
+ * display will be updated, as well as the size (in characters, not bytes) of
+ * the text, andMask and orMask members.
+ *
+ * If given, the "text" field holds the text that will be displayed in the
+ * region.  The char string must hold exactly as many characters as the region
+ * fields express.  For multibyte text, this is the number of \e multibyte
+ * caracters.  Notably, combining and double-width caracters count for 1.
+ *
+ * The actual length of the text in \e bytes may be specified thanks to
+ * textSize.  If -1 is given, it will be computed thanks to strlen(), so "text"
+ * must then be a NUL-terminated string.
+ *
+ * The "andMask" and "orMask" masks, if present, are then applied on top of
+ * the text, one byte per character.  This hence permits the superimposing of
+ * attributes over the text.  For instance, setting an andMask mask full of
+ * BRLAPI_DOTS(1,1,1,1,1,1,0,0) will only keep (logical AND) dots 1-6,
+ * hence dropping dots 7 and 8.  On the contrary, setting an orMask full of
+ * BRLAPI_DOT7|BRLAPI_DOT8 will add (logical OR) dots 7 and 8.
+ *
+ * The "charset" field, if present, specifies the charset of the "text" field.
+ * If it is "", the current locale's charset (if any) is assumed.  Else, the
+ * 8-bit charset of the server is assumed.
+ *
+ * A special invocation is with an unmodified initialized structure: this clears
+ * the client's whole display, letting the display of other applications on
+ * the same tty or of applications "under" the tty appear. See Concurrency
+ * management section of the BrlAPI documentation for more details.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_write(const brlapi_writeArguments_t *arguments);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__write(brlapi_handle_t *handle, const brlapi_writeArguments_t *arguments);
+
+/** @} */
+
+#include "brlapi_keycodes.h"
+
+/** \defgroup brlapi_keys Reading key presses
+ * \brief How to read key presses from the braille terminal
+ *
+ * Once brlapi_enterTtyMode() has been called, the application can call brlapi_readKey() to
+ * read key presses. Either key codes (see \ref brlapi_keycodes) or commands will be returned, depending
+ * on parameters given to brlapi_enterTtyMode().
+ *
+ * Key presses are buffered, so that calling brlapi_readKey() in non-blocking
+ * mode from time to time should suffice.
+ *
+ * @{
+ */
+
+/* brlapi_expandedKeyCode_t */
+/** Structure holding the components of a key code as returned by brlapi_expandKeyCode() */
+typedef struct {
+  unsigned int type /** the type value */;
+  unsigned int command /** the command value */;
+  unsigned int argument /** the argument value */;
+  unsigned int flags /** the flags value */;
+} brlapi_expandedKeyCode_t;
+
+/* brlapi_expandKeyCode */
+/** Expand the components of a key code
+ *
+ * \param code the key code to be expanded
+ * \param expansion pointer to the structure that receives the components
+ *
+ * \return 0 on success, -1 on error
+ */
+int BRLAPI_STDCALL brlapi_expandKeyCode (brlapi_keyCode_t code, brlapi_expandedKeyCode_t *expansion);
+
+/* brlapi_describedKeyCode_t */
+/** Structure holding the components of a key code as returned by brlapi_describeKeyCode() */
+typedef struct {
+  const char *type /** the  type name */;
+  const char *command /** the command name */;
+  unsigned int argument /** the argument value */;
+  unsigned int flags /** the flag count */;
+  const char *flag[64 - BRLAPI_KEY_FLAGS_SHIFT] /** the flag names */;
+  brlapi_expandedKeyCode_t values /** the actual values of the components */;
+} brlapi_describedKeyCode_t;
+
+/* brlapi_describeKeyCode */
+/** Describe the components of a key code.
+ *
+ * \param code the keycode to be described
+ * \param description pointer to the structure that receives the description
+ *
+ * \return 0 on success, -1 on error
+ */
+int BRLAPI_STDCALL brlapi_describeKeyCode (brlapi_keyCode_t code, brlapi_describedKeyCode_t *description);
+
+/** Unicode braille row */
+#define BRLAPI_UC_ROW	0x2800UL
+
+/* brlapi_readKey */
+/** Read a key from the braille keyboard
+ *
+ * This function returns one key press's code.
+ *
+ * If NULL or "" was given to brlapi_enterTtyMode(), a \e brltty command is returned,
+ * as described in the documentation for ::brlapi_keyCode_t . It is hence pretty
+ * driver-independent, and should be used by default when no other option is
+ * possible.
+ *
+ * By default, all commands but those which restart drivers and switch
+ * virtual terminals are returned to the application and not to brltty.
+ * If the application doesn't want to see some command events,
+ * it should call brlapi_ignoreKeys()
+ *
+ * If a driver name was given to brlapi_enterTtyMode(), a driver-specific keycode is returned,
+ * as specified by the braille driver, usually in <brltty/brldefs-xy> where xy
+ * is the driver's code. It generally corresponds to the very code that the
+ * terminal tells to the driver.
+ * This should only be used by applications which are dedicated to a particular
+ * braille terminal. Hence, checking the terminal type thanks to a call to
+ * brlapi_getDriverName() before getting tty control is a pretty good idea.
+ *
+ * By default, all the keypresses will be passed to the client, none will go
+ * through brltty, so the application will have to handle console switching
+ * itself for instance.
+ *
+ * \param wait tells whether the call should block until a key is pressed (1)
+ *  or should only probe key presses (0);
+ * \param code holds the key code if a key press is indeed read.
+ *
+ * \return -1 on error or signal interrupt and *code is then undefined, 0 if
+ * block was 0 and no key was pressed so far, or 1 and *code holds the key code.
+ *
+ * Programming hints:
+ *
+ * If your application is only driven by braille command keypresses, you can
+ * just call brlapi_readKey(1, &code) so that it keeps blocking, waiting for
+ * keypresses.
+ *
+ * Else, you'll probably want to use the file descriptor returned by
+ * brlapi_openConnection() or brlapi_getFileDescriptor() in your "big polling loop".
+ * For instance:
+ *
+ * - in a \c select() loop, just add it to the \c readfds and \c exceptfds file
+ *   descriptor sets;
+ * - in a gtk or atspi application, use
+ *   \c g_io_add_watch(fileDescriptor, \c G_IO_IN|G_IO_ERR|G_IO_HUP, \c f, \c data)
+ *   for adding a callback called \c f;
+ * - in an Xt/Xaw/motif-based application, use
+ *   \c XtAppAddInput(app_context, \c fileDescriptor, \c XtInputReadMask|XtInputExceptMask, \c f, \c data)
+ * - etc.
+ *
+ * and then, when you detect inbound trafic on the file descriptor, do something
+ * like this:
+ *
+ * while (brlapi_readKey(0, &code) {
+ *    // process keycode code
+ *    // ...
+ * }
+ *
+ * The \c while loop is needed for processing \e all pending key presses, else
+ * some of them may be left in libbrlapi's internal key buffer and you wouldn't
+ * get them immediately.
+ *
+ * \note If the read is interrupted by a signal or by a parameter change
+ * notification, brlapi_readKey() will return -1, brlapi_errno will be
+ * BRLAPI_ERROR_LIBCERR and errno will be EINTR.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_readKey(int wait, brlapi_keyCode_t *code);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__readKey(brlapi_handle_t *handle, int wait, brlapi_keyCode_t *code);
+
+/* brlapi_readKeyWithTimeout */
+/** Read a key from the braille keyboard, unless a timeout expires
+ *
+ * This function works like brlapi_readKey, except that parameter \e wait is
+ * replaced by a \e timeout_ms parameter
+ *
+ * \param timeout_ms specifies how long the function should wait for a keypress.
+ * \param code holds the key code if a key press is indeed read.
+ *
+ * \return -1 on error, signal interrupt or parameter change notification and
+ * *code is then undefined, 0 if the timeout expired and no key was pressed, or
+ * 1 and *code holds the key code.
+ *
+ * If the timeout expires without any key being pressed, 0 is returned.
+ *
+ * If timeout_ms is set to 0, this function looks for key events that have been
+ * already received, but does not wait at all if no event was received.
+ *
+ * If timeout_ms is set to a negative value, this function behaves like
+ * brlapi_readKey, i.e. it uses an infinite timeout.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_readKeyWithTimeout(int timeout_ms, brlapi_keyCode_t *code);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__readKeyWithTimeout(brlapi_handle_t *handle, int timeout_ms, brlapi_keyCode_t *code);
+
+/** types of key ranges */
+typedef enum {
+  brlapi_rangeType_all,	/**< all keys, code must be 0 */
+  brlapi_rangeType_type,	/**< all keys of a given type */
+  brlapi_rangeType_command,	/**< all keys of a given command block, i.e. matching the key type and command parts */
+  brlapi_rangeType_key,	/**< a given key with any flags */
+  brlapi_rangeType_code,	/**< a given key code */
+} brlapi_rangeType_t;
+
+/* brlapi_ignoreKeys */
+/** Ignore some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided keys to \e brltty, rather
+ * than returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param type type of keys to be ignored
+ * \param keys array of keys to be ignored
+ * \param count number of keys
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreKeys(brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreKeys(brlapi_handle_t *handle, brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+
+/* brlapi_acceptKeys */
+/** Accept some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided keys to the application,
+ * and not give them to \e brltty.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param type type of keys to be ignored
+ * \param keys array of keys to be ignored
+ * \param count number of keys
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptKeys(brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptKeys(brlapi_handle_t *handle, brlapi_rangeType_t type, const brlapi_keyCode_t keys[], unsigned int count);
+
+/* brlapi_ignoreAllKeys */
+/** Ignore all key presses from the braille keyboard
+ *
+ * This function asks the server to give all keys to \e brltty, rather than
+ * returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreAllKeys(void);
+#define brlapi_ignoreAllKeys() brlapi_ignoreKeys(brlapi_rangeType_all, NULL, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreAllKeys(brlapi_handle_t *handle);
+#define brlapi__ignoreAllKeys(handle) brlapi__ignoreKeys(handle, brlapi_rangeType_all, NULL, 0)
+
+/* brlapi_acceptAllKeys */
+/** Accept all key presses from the braille keyboard
+ *
+ * This function asks the server to give all keys to the application, and not
+ * give them to \e brltty.
+ *
+ * Warning: after calling this function, make sure to call brlapi_ignoreKeys()
+ * for ignoring important keys like BRL_CMD_SWITCHVT_PREV/NEXT and such.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptAllKeys(void);
+#define brlapi_acceptAllKeys() brlapi_acceptKeys(brlapi_rangeType_all, NULL, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptAllKeys(brlapi_handle_t *handle);
+#define brlapi__acceptAllKeys(handle) brlapi__acceptKeys(handle, brlapi_rangeType_all, NULL, 0)
+
+/** Type for keycode ranges
+ *
+ * Denotes the set of keycodes between \e first and \e last (inclusive)
+ */
+typedef struct {
+	brlapi_keyCode_t first;	/**< first key of the range */
+	brlapi_keyCode_t last;	/**< last key of the range */
+} brlapi_range_t;
+
+/* brlapi_ignoreKeyRanges */
+/** Ignore some key presses from the braille keyboard
+ *
+ * This function asks the server to give the provided key ranges to \e brltty,
+ * rather than returning them to the application via brlapi_readKey().
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param ranges key ranges, which are inclusive, see \ref brlapi_keycodes about key ranges
+ * \param count number of ranges
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_ignoreKeyRanges(const brlapi_range_t ranges[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__ignoreKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int count);
+
+/* brlapi_acceptKeyRanges */
+/** Accept some key presses from the braille keyboard
+ *
+ * This function asks the server to return the provided key ranges (inclusive)
+ * to the application, and not give them to \e brltty.
+ *
+ * The sets of such keys are reset on brlapi_enterTtyMode call.
+ *
+ * \param ranges key ranges, which are inclusive, see \ref brlapi_keycodes about key ranges
+ * \param count number of ranges
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_acceptKeyRanges(const brlapi_range_t ranges[], unsigned int count);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__acceptKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int count);
+/** @} */
+
+/** \defgroup brlapi_driverspecific Driver-Specific modes
+ * \brief Raw and Suspend Modes mechanism
+ *
+ * If the application wants to directly talk to the braille terminal, it should
+ * use Raw Mode. In this special mode, the driver gives the whole control of the
+ * terminal to it: \e brltty doesn't work any more.
+ *
+ * For this, it simply has to call brlapi_enterRawMode(), then brlapi_sendRaw()
+ * and brlapi_recvRaw(), and finally give control back thanks to
+ * brlapi_leaveRawMode().
+ *
+ * Special care of the terminal should be taken, since one might completely
+ * trash the terminal's data, or even lock it! The application should always
+ * check for terminal's type thanks to brlapi_getDriverName().
+ *
+ * The client can also make brltty close the driver by using brlapi_suspendDriver(),
+ * and resume it again with brlapi_resumeDriver().  This should not be used if
+ * possible: raw mode should be sufficient for any use.  If not, please ask for
+ * features :)
+ *
+ * @{
+ */
+
+/* brlapi_enterRawMode */
+/** Switch to Raw mode
+ *
+ * TODO: document which functions work in raw mode.
+ *
+ * \param driver Specifies the name of the driver for which the raw
+ * communication will be established.
+ * \return 0 on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_enterRawMode(const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__enterRawMode(brlapi_handle_t *handle, const char *driver);
+
+/* brlapi_leaveRawMode */
+/** Leave Raw mode
+ * \return 0 on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_leaveRawMode(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__leaveRawMode(brlapi_handle_t *handle);
+
+/* brlapi_sendRaw */
+/** Send Raw data
+ *
+ * \param buffer points on the data;
+ * \param size holds the packet size.
+ * \return size on success, -1 on error */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_sendRaw(const void *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+ssize_t BRLAPI_STDCALL brlapi__sendRaw(brlapi_handle_t *handle, const void *buffer, size_t size);
+
+/* brlapi_recvRaw */
+/** Get Raw data
+ *
+ * \param buffer points on a buffer where the function will store the received
+ * data;
+ * \param size holds the buffer size.
+ * \return its size, -1 on error, or on interruption by a signal or a parameter
+ * change notification, in which case brlapi_errno will be BRLAPI_ERROR_LIBCERR
+ * and errno will be EINTR. */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_recvRaw(void *buffer, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+ssize_t BRLAPI_STDCALL brlapi__recvRaw(brlapi_handle_t *handle, void *buffer, size_t size);
+
+/* brlapi_suspendDriver */
+/** Suspend braille driver
+ * \param driver Specifies the name of the driver which will be suspended.
+ * \return -1 on error
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_suspendDriver(const char *driver);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__suspendDriver(brlapi_handle_t *handle, const char *driver);
+
+/* brlapi_resumeDriver */
+/** Resume braille driver
+ * \return -1 on error
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_resumeDriver(void);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__resumeDriver(brlapi_handle_t *handle);
+/** @} */
+
+#include "brlapi_param.h"
+
+/** \defgroup brlapi_parameterManagement Parameter management
+ * \brief How to manage BrlAPI parameters
+ *
+ * There are several kinds of parameters:
+ * - states associated with the braille device itself, such as its size or
+ * parameters of the device port
+ * - states of the BrlAPI connection itself, such as the displaying level or
+ * key passing preferences.
+ * - general states such as the cut buffer,
+ * - braille parameters: braille table, contraction, cursor shape, etc,
+ * - browse parameters: line skip, beep, etc.
+ *
+ * Some of them are subdivided in subparameters.  Others have only subparameter 0.
+ *
+ * Some of them are read-only, others are read/write.
+ *
+ * A client can either request the immediate content of a parameter by
+ * using brlapi_getParameter(); set the content of a parameter by using
+ * brlapi_setParameter(); or register a callback that may be called immediately
+ * and on each change of a given parameter, by using brlapi_watchParameter().
+ *
+ * @{ */
+
+/** Flags for parameter requests */
+typedef uint32_t brlapi_param_flags_t;
+#define BRLAPI_PARAMF_LOCAL          0X00    /**< Refer to the value local to the connection instead of the global value */
+#define BRLAPI_PARAMF_GLOBAL         0X01    /**< Refer to the global value instead of the value local to the connection */
+#define BRLAPI_PARAMF_SELF           0X02    /**< Specify whether to receive notifications of value self-changes */
+
+/* brlapi_getParameter */
+/** Get the content of a parameter
+ *
+ * brlapi_getParameter gets the current content of a parameter
+ *
+ * \param parameter is the parameter whose content shall be gotten;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be returned;
+ * \param data is a buffer where content of the parameter shall be stored;
+ * \param len is the size of the buffer.
+ *
+ * \return the real size of the parameter's content. If the parameter does not fit in the provided buffer, it is truncated to len bytes (but the real size of the parameter is still returned). In that case, the client must call brlapi_getParameter again with a big enough buffer.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+ssize_t BRLAPI_STDCALL brlapi_getParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len);
+#endif
+ssize_t BRLAPI_STDCALL brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len);
+
+/* brlapi_getParameterAlloc */
+/** Return the content of a parameter
+ *
+ * brlapi_getParameterAlloc gets the current content of a parameter, by returning it as a newly-allocated buffer.
+ * The buffer is allocated to one byte more than the parameter value. This byte is set to zero. This allows, for string parameters, to be able to immediately use it as a C string.
+ *
+ * \param parameter is the parameter whose content shall be gotten;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be returned;
+ * \param len is the address where to store the size of the parameter value.
+ *
+ * \return a newly-allocated buffer that contains the value of the parameter. The caller must call free() on it after use. NULL is returned on errors
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void * BRLAPI_STDCALL brlapi_getParameterAlloc(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len);
+#endif
+void * BRLAPI_STDCALL brlapi__getParameterAlloc(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len);
+
+/* brlapi_setParameter */
+/** Set the content of a parameter
+ *
+ * brlapi_setParameter sets the content of a parameter
+ *
+ * \param parameter is the parameter to set;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be set;
+ * \param data is a buffer containing the data to store in the parameter;
+ * \param len is the size of the data.
+ *
+ * \return 0 on success, -1 on error (read-only parameter for instance).
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_setParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len);
+#endif
+int BRLAPI_STDCALL brlapi__setParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len);
+
+/* brlapi_paramCallback_t */
+/** Callback for parameter changes
+ *
+ * When a parameter gets changed, application-defined callbacks set by the
+ * brlapi_watchParameter() function are called.
+ *
+ * \param parameter is the parameter that changed;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it was changed;
+ * \param priv is the void pointer that was passed to the brlapi_watchParameter call which registered the callback;
+ * \param data is a buffer containing the new value of the parameter;
+ * \param len is the size of the data.
+ *
+ * This callback only gets called when the application calls some brlapi_
+ * function (i.e. BrlAPI gets direct control of the execution).
+ */
+typedef void (*brlapi_paramCallback_t)(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len);
+
+/* brlapi_paramCallbackDescriptor_t */
+/** Type for callback descriptors
+ * This is returned by brlapi_watchParameter, to be passed to
+ * brlapi_unwatchParameter.
+ */
+typedef void *brlapi_paramCallbackDescriptor_t;
+
+/* brlapi_watchParameter */
+/** Set a parameter change callback
+ *
+ * brlapi_watchParameter registers a parameter change callback: whenever the
+ * given parameter changes, the given function is called.
+ *
+ * \param parameter is the parameter to watch;
+ * \param subparam is a specific instance of the parameter;
+ * \param flags specify which value and how it should be monitored;
+ * \param func is the function to call on parameter change;
+ * \param priv is a void pointer which will be passed as such to the function;
+ * \param data is a buffer where the current content of the parameter shall be
+ * stored;
+ * \param len is the size of the buffer.
+ *
+ * \return the callback descriptor (to be passed to brlapi_unwatchParameter to
+ * unregister the callback), or NULL on error.
+ *
+ * \note Default parameter callbacks don't do anything, except the ones for
+ * display size which just raise SIGWINCH.
+ * \note If data is NULL, the callback will be called immediately by
+ * brlapi_watchParameter, for providing the initial value
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi_watchParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len);
+#endif
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi__watchParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len);
+
+/* brlapi_unwatchParameter */
+/** Clear a parameter change callback
+ *
+ * brlapi_unwatchParameter unregisters a parameter change callback: the
+ * callback function previously registered with brlapi_watchParameter will
+ * not be called any longer.
+ *
+ * \param descriptor refers to the callback to be removed.
+ *
+ * \return 0 on success, -1 on error.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_unwatchParameter(brlapi_paramCallbackDescriptor_t descriptor);
+#endif
+int BRLAPI_STDCALL brlapi__unwatchParameter(brlapi_handle_t *handle, brlapi_paramCallbackDescriptor_t descriptor);
+
+/** @} */
+
+/** \defgroup brlapi_misc Miscellaneous functions
+ * @{ */
+
+/* brlapi_pause */
+/**
+ * Waits until an event is received from the BrlAPI server
+ * \param timeout_ms specifies an optional timeout which can be zero for polling, or -1 for infinite wait
+ * \return 0 on timeout, -1 on error, or on interruption by a signal or a parameter
+ * change notification, in which case brlapi_errno will be BRLAPI_ERROR_LIBCERR
+ * and errno will be EINTR. */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_pause(int timeout_ms);
+#endif
+int BRLAPI_STDCALL brlapi__pause(brlapi_handle_t *handle, int timeout_ms);
+
+/* brlapi_sync */
+/**
+ * Synchronize against any pending exception, and returns it. This allows to
+ * synchronously catch exception raised by brlapi_write calls.
+ * This works by temporarily replacing the current exception handler by its own
+ * handler.
+ * \return 0 if no exception was pending, -1 if an exception was caught.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_sync(void);
+#endif
+int BRLAPI_STDCALL brlapi__sync(brlapi_handle_t *handle);
+
+/** @} */
+
+/** \defgroup brlapi_error Error handling
+ * \brief How to handle errors
+ *
+ * When a function fails, ::brlapi_errno will hold an error
+ * code to explain why it failed. It should always be reported somehow.
+ *
+ * Although most errors are reported that way, some (called exceptions)
+ * are reported asynchronously for efficiency reasons, because they always
+ * just report a programming error. The affected functions are: brlapi_setFocus,
+ * brlapi_write* and brlapi_sendRaw.  When they happen, the next call to
+ * brlapi_something will close the connection and call the \e exception
+ * handler. If the exception handler returns, the brlapi_something function will
+ * return an end-of-file error.
+ *
+ * The default exception handler (brlapi_defaultExceptionHandler()) dumps
+ * the guilty packet before abort()ing.  It can be replaced by calling
+ * brlapi_setExceptionHandler().  For instance, the Java and Python bindings use
+ * this for raising a Java or Python exception that may be caught.
+ *
+ * @{ */
+
+/** BrlAPI Error codes */
+enum brlapi_error {
+  BRLAPI_ERROR_SUCCESS             =  0,  /**< Success */
+#define  BRLAPI_ERROR_SUCCESS              0
+  BRLAPI_ERROR_NOMEM               =  1,  /**< Not enough memory */
+#define BRLAPI_ERROR_NOMEM                BRLAPI_ERROR_NOMEM
+  BRLAPI_ERROR_TTYBUSY             =  2,  /**< A connection is already running in this tty */
+#define BRLAPI_ERROR_TTYBUSY              BRLAPI_ERROR_TTYBUSY
+  BRLAPI_ERROR_DEVICEBUSY          =  3,  /**< A connection is already using RAW or suspend mode */
+#define BRLAPI_ERROR_DEVICEBUSY           BRLAPI_ERROR_DEVICEBUSY
+  BRLAPI_ERROR_UNKNOWN_INSTRUCTION =  4,  /**< Not implemented in protocol */
+#define BRLAPI_ERROR_UNKNOWN_INSTRUCTION  BRLAPI_ERROR_UNKNOWN_INSTRUCTION
+  BRLAPI_ERROR_ILLEGAL_INSTRUCTION =  5,  /**< Forbiden in current mode */
+#define BRLAPI_ERROR_ILLEGAL_INSTRUCTION  BRLAPI_ERROR_ILLEGAL_INSTRUCTION
+  BRLAPI_ERROR_INVALID_PARAMETER   =  6,  /**< Out of range or have no sense */
+#define BRLAPI_ERROR_INVALID_PARAMETER    BRLAPI_ERROR_INVALID_PARAMETER
+  BRLAPI_ERROR_INVALID_PACKET      =  7,  /**< Invalid size */
+#define BRLAPI_ERROR_INVALID_PACKET       BRLAPI_ERROR_INVALID_PACKET
+  BRLAPI_ERROR_CONNREFUSED         =  8,  /**< Connection refused */
+#define BRLAPI_ERROR_CONNREFUSED          BRLAPI_ERROR_CONNREFUSED
+  BRLAPI_ERROR_OPNOTSUPP           =  9,  /**< Operation not supported */
+#define BRLAPI_ERROR_OPNOTSUPP            BRLAPI_ERROR_OPNOTSUPP
+  BRLAPI_ERROR_GAIERR              = 10,  /**< Getaddrinfo error */
+#define BRLAPI_ERROR_GAIERR               BRLAPI_ERROR_GAIERR
+  BRLAPI_ERROR_LIBCERR             = 11,  /**< Libc error */
+#define BRLAPI_ERROR_LIBCERR              BRLAPI_ERROR_LIBCERR
+  BRLAPI_ERROR_UNKNOWNTTY          = 12,  /**< Couldn't find out the tty number */
+#define BRLAPI_ERROR_UNKNOWNTTY           BRLAPI_ERROR_UNKNOWNTTY
+  BRLAPI_ERROR_PROTOCOL_VERSION    = 13,  /**< Bad protocol version */
+#define BRLAPI_ERROR_PROTOCOL_VERSION     BRLAPI_ERROR_PROTOCOL_VERSION
+  BRLAPI_ERROR_EOF                 = 14,  /**< Unexpected end of file */
+#define BRLAPI_ERROR_EOF                  BRLAPI_ERROR_EOF
+  BRLAPI_ERROR_EMPTYKEY            = 15,  /**< Key file empty */
+#define BRLAPI_ERROR_EMPTYKEY             BRLAPI_ERROR_EMPTYKEY
+  BRLAPI_ERROR_DRIVERERROR         = 16,  /**< Packet returned by driver too large */
+#define BRLAPI_ERROR_DRIVERERROR          BRLAPI_ERROR_DRIVERERROR
+  BRLAPI_ERROR_AUTHENTICATION      = 17,  /**< Authentication failed */
+#define BRLAPI_ERROR_AUTHENTICATION       BRLAPI_ERROR_AUTHENTICATION
+  BRLAPI_ERROR_READONLY_PARAMETER  = 18,  /**< Parameter can not be changed */
+#define BRLAPI_ERROR_READONLY_PARAMETER   BRLAPI_ERROR_READONLY_PARAMETER
+};
+
+/* brlapi_errlist */
+/** Error message list
+ *
+ * These are the string constants used by brlapi_perror().
+ */
+extern const char *brlapi_errlist[];
+
+/* brlapi_nerr */
+/** Number of error messages */
+extern const int brlapi_nerr;
+
+/* brlapi_perror */
+/** Print a BrlAPI error message
+ *
+ * brlapi_perror() reads ::brlapi_error, and acts just like perror().
+ */
+void BRLAPI_STDCALL brlapi_perror(const char *s);
+
+/* brlapi_error_t */
+/** All information that is needed to describe brlapi errors */
+typedef struct {
+  enum brlapi_error brlerrno;
+  int libcerrno;
+  int gaierrno;
+  const char *errfun;
+} brlapi_error_t;
+
+/** Get per-thread error location
+ *
+ * In multithreaded software, ::brlapi_error is thread-specific, so api.h
+ * cheats about the brlapi_error token and actually calls
+ * brlapi_error_location().
+ *
+ * This gets the thread specific location of global variable ::brlapi_error
+ */
+brlapi_error_t * BRLAPI_STDCALL brlapi_error_location(void);
+
+/** Global variable brlapi_error
+ *
+ * ::brlapi_error is a global left-value containing the last error information.
+ * Its errno field is not reset to BRLAPI_ERROR_SUCCESS on success.
+ *
+ * This information may be copied in brlapi_error_t variables for later use
+ * with the brlapi_strerror function.
+ */
+extern brlapi_error_t brlapi_error;
+
+/** Shorthand for brlapi_error.errno */
+extern enum brlapi_error brlapi_errno;
+/** Shorthand for brlapi_error.libcerrno */
+extern int brlapi_libcerrno;
+/** Shorthand for brlapi_error.gaierrno */
+extern int brlapi_gaierrno;
+/** Shorthand for brlapi_error.errfun */
+extern const char *brlapi_errfun;
+
+/** Cheat about the brlapi_error C token */
+#define brlapi_error (*brlapi_error_location())
+/** Cheat about the brlapi_errno C token */
+#define brlapi_errno (brlapi_error.brlerrno)
+/** Cheat about the brlapi_libcerrno C token */
+#define brlapi_libcerrno (brlapi_error.libcerrno)
+/** Cheat about the brlapi_gaierrno C token */
+#define brlapi_gaierrno (brlapi_error.gaierrno)
+/** Cheat about the brlapi_errfun C token */
+#define brlapi_errfun (brlapi_error.errfun)
+
+/* brlapi_strerror */
+/** Get plain error message
+ *
+ * brlapi_strerror() returns the plain error message corresponding to its
+ * argument. Since the message is a constant string, the application must not
+ * free it. Also, this makes it unsafe in threaded environments,
+ * brlapi_strerror_r() should be used instead in that case.
+ */
+const char * BRLAPI_STDCALL brlapi_strerror(const brlapi_error_t *error);
+
+/* brlapi_strerror_r */
+/** Store plain error message
+ *
+ * brlapi_strerror_r() stores the plain error message corresponding to its
+ * \p error argument. \p buflen has to be set to the size of \p buf, and
+ * brlapi_strerror_r() will store at most \p buflen bytes in \p buf. If \p
+ * buflen is not large enough for the whole error message, it will be truncated,
+ * but a trailing \0 character will still be set at buf[buflen-1].
+ *
+ * \returns the number of characters that should have been stored in \p buf
+ * (without the trailing \0 character). A value greater or equal to \p buflen
+ * thus means that the output was truncated.
+ *
+ * If \p buflen is set to 0, \p buf can be set to NULL, and brlapi_strerror_r
+ * will thus only return the number of characters that would have been stored
+ * (without the trailing \0 character).
+ */
+size_t BRLAPI_STDCALL brlapi_strerror_r(const brlapi_error_t *error, char *buf, size_t buflen);
+
+/** Type for packet type. Only unsigned can cross networks, 32bits */
+typedef uint32_t brlapi_packetType_t;
+
+/* brlapi_getPacketTypeName */
+/** Get plain packet type
+ *
+ * brlapi_getPacketTypeName() returns the plain packet type name corresponding to
+ * its argument.
+ */
+const char * BRLAPI_STDCALL brlapi_getPacketTypeName(brlapi_packetType_t type);
+
+/* brlapi_exceptionHandler_t */
+/** Types for exception handlers
+ *
+ * Types of exception handlers which are to be given to
+ * brlapi_setExceptionHandler() and brlapi__setExceptionHandler().
+ *
+ * \param error is a BRLAPI_ERROR_ error code;
+ * \param type is the type of the guilty packet;
+ * \param packet points to the content of the guilty packet (might be a little bit truncated);
+ * \param size gives the guilty packet's size.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+typedef void (BRLAPI_STDCALL *brlapi_exceptionHandler_t)(int error, brlapi_packetType_t type, const void *packet, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+typedef void (BRLAPI_STDCALL *brlapi__exceptionHandler_t)(brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size);
+
+/* brlapi_strexception */
+/** Describes an exception
+ *
+ * brlapi_strexception() puts a text describing the given exception in buffer.
+ *
+ * The beginning of the guilty packet is dumped as a sequence of hex bytes.
+ *
+ * \return the size of the text describing the exception, following
+ * snprintf()'s semantics.
+*/
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_strexception(char *buffer, size_t bufferSize, int error, brlapi_packetType_t type, const void *packet, size_t packetSize);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__strexception(brlapi_handle_t *handle, char *buffer, size_t bufferSize, int error, brlapi_packetType_t type, const void *packet, size_t packetSize);
+
+/* brlapi_setExceptionHandler */
+/** Set a new exception handler
+ *
+ * brlapi_setExceptionHandler() replaces the previous exception handler with
+ * the handler parameter. The previous exception handler is returned to make
+ * chaining error handlers possible.
+ *
+ * The default handler just prints the exception and abort()s.
+ */
+#ifndef BRLAPI_NO_SINGLE_SESSION
+brlapi_exceptionHandler_t BRLAPI_STDCALL brlapi_setExceptionHandler(brlapi_exceptionHandler_t handler);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+brlapi__exceptionHandler_t BRLAPI_STDCALL brlapi__setExceptionHandler(brlapi_handle_t *handle, brlapi__exceptionHandler_t handler);
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+void BRLAPI_STDCALL brlapi_defaultExceptionHandler(int error, brlapi_packetType_t type, const void *packet, size_t size);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+void BRLAPI_STDCALL brlapi__defaultExceptionHandler(brlapi_handle_t *handle, int error, brlapi_packetType_t type, const void *packet, size_t size);
+
+/** @} */
+
+/* Windows-specific tricks - don't look at this */
+#ifdef BRLAPI_WIN32
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeTextWin(int cursor, const void *str, int wide);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeTextWin(brlapi_handle_t *handle, int cursor, const void *str, int wide);
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+int BRLAPI_STDCALL brlapi_writeWin(const brlapi_writeArguments_t *s, int wide);
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+int BRLAPI_STDCALL brlapi__writeWin(brlapi_handle_t *handle, const brlapi_writeArguments_t *s, int wide);
+
+#ifdef UNICODE
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_writeText(cursor, str) brlapi_writeTextWin(cursor, str, 1)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__writeText(handle, cursor, str) brlapi__writeTextWin(handle, cursor, str, 1)
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_write(s) brlapi_writeWin(s, 1)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__write(handle, s) brlapi__writeWin(handle, s, 1)
+
+#else /* UNICODE */
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_writeText(cursor, str) brlapi_writeTextWin(cursor, str, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__writeText(handle, cursor, str) brlapi__writeTextWin(handle, cursor, str, 0)
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_write(s) brlapi_writeWin(s, 0)
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+#define brlapi__write(handle, s) brlapi__writeWin(handle, s, 0)
+
+#endif /* UNICODE */
+#endif /* BRLAPI_WIN32 */
+
+#ifndef BRLAPI_NO_DEPRECATED
+/** \defgroup brlapi_deprecated Deprecated names
+ *
+ * With version 0.5.0, BrlAPI is now provided through including <brlapi.h> and
+ * got a big renaming pass. Old names are still available through macros, but
+ * they are deprecated since they will get dropped in the next release. This
+ * documentation is for you to know the new names.
+ *
+ * For checking that you have completely switched to new names, just define
+ * BRLAPI_NO_DEPRECATED: that will disable compatibility macros.
+ *
+ * @{ */
+
+#define brlapi_settings_t brlapi_connectionSettings_t
+
+/** brlapi_writeStruct, replaced by brlapi_writeArguments_t */
+typedef struct {
+  int displayNumber;
+  unsigned int regionBegin;
+  unsigned int regionSize;
+  const char *text;
+  int textSize;
+  const unsigned char *attrAnd;
+  const unsigned char *attrOr;
+  int cursor;
+  const char *charset;
+} brlapi_writeStruct;
+#define BRLAPI_WRITESTRUCT_INITIALIZER BRLAPI_WRITEARGUMENTS_INITIALIZER
+
+#define brl_keycode_t brlapi_keyCode_t
+#define brl_type_t brlapi_packetType_t
+
+#define BRLCOMMANDS NULL
+#define BRL_KEYCODE_MAX BRLAPI_KEY_MAX
+
+#ifndef BRLAPI_NO_SINGLE_SESSION
+#define brlapi_initializeConnection brlapi_openConnection
+#define brlapi_getTty brlapi_enterTtyMode
+#define brlapi_getTtyPath brlapi_enterTtyModeWithPath
+#define brlapi_leaveTty brlapi_leaveTtyMode
+#define brlapi_unignoreKeyRange brlapi_acceptKeyRange
+#define brlapi_unignoreKeySet brlapi_acceptKeySet
+#define brlapi_getRaw brlapi_enterRawMode
+#define brlapi_leaveRaw brlapi_leaveRawMode
+#define brlapi_suspend brlapi_suspendDriver
+#define brlapi_resume brlapi_resumeDriver
+#endif /* BRLAPI_NO_SINGLE_SESSION */
+
+#define BRLERR_SUCCESS                 BRLAPI_ERROR_SUCCESS
+#define BRLERR_NOMEM                   BRLAPI_ERROR_NOMEM
+#define BRLERR_TTYBUSY                 BRLAPI_ERROR_TTYBUSY
+#define BRLERR_DEVICEBUSY              BRLAPI_ERROR_DEVICEBUSY
+#define BRLERR_UNKNOWN_INSTRUCTION     BRLAPI_ERROR_UNKNOWN_INSTRUCTION
+#define BRLERR_ILLEGAL_INSTRUCTION     BRLAPI_ERROR_ILLEGAL_INSTRUCTION
+#define BRLERR_INVALID_PARAMETER       BRLAPI_ERROR_INVALID_PARAMETER
+#define BRLERR_INVALID_PACKET          BRLAPI_ERROR_INVALID_PACKET
+#define BRLERR_CONNREFUSED             BRLAPI_ERROR_CONNREFUSED
+#define BRLERR_OPNOTSUPP               BRLAPI_ERROR_OPNOTSUPP
+#define BRLERR_GAIERR                  BRLAPI_ERROR_GAIERR
+#define BRLERR_LIBCERR                 BRLAPI_ERROR_LIBCERR
+#define BRLERR_UNKNOWNTTY              BRLAPI_ERROR_UNKNOWNTTY
+#define BRLERR_PROTOCOL_VERSION        BRLAPI_ERROR_PROTOCOL_VERSION
+#define BRLERR_EOF                     BRLAPI_ERROR_EOF
+#define BRLERR_EMPTYKEY                BRLAPI_ERROR_EMPTYKEY
+#define BRLERR_DRIVERERROR             BRLAPI_ERROR_DRIVERERROR
+
+/** @} */
+#endif /* BRLAPI_NO_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED */
diff --git a/Programs/brlapi_brldefs.awk b/Programs/brlapi_brldefs.awk
new file mode 100644
index 0000000..8bf4567
--- /dev/null
+++ b/Programs/brlapi_brldefs.awk
@@ -0,0 +1,72 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  writeHeaderPrologue("BRLAPI_INCLUDED_BRLDEFS", "api.h")
+}
+
+END {
+  writeMacroDefinition("BRL_MSK_BLK", "(BRLAPI_KEY_TYPE_MASK | BRLAPI_KEY_CMD_BLK_MASK)", "mask for command type")
+  writeMaskDefinition("ARG", "mask for command value/argument")
+  writeMacroDefinition("BRL_MSK_FLG", "BRLAPI_KEY_FLAGS_MASK", "mask for command flags")
+  writeMacroDefinition("BRL_MSK_CMD", "(BRL_MSK_BLK | BRL_MSK_ARG)", "mask for command")
+
+  writeHeaderEpilogue()
+}
+
+function writeMaskDefinition(name, help) {
+  writeSymbolDefinition(name, "BRL_MSK_", "", "BRLAPI_KEY_CMD_", "_MASK", help)
+}
+
+function writeSymbolDefinition(name, brlPrefix, brlSuffix, apiPrefix, apiSuffix, help) {
+  writeMacroDefinition(brlPrefix name brlSuffix, apiPrefix name apiSuffix, help)
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeSymbolDefinition(name, "BRL_CMD_", "", "(BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_", ")", help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  if (name == "PASSCHAR") {
+    writeMacroDefinition("BRL_KEY_" name, "(BRLAPI_KEY_TYPE_SYM | 0X0000)", help)
+  } else if (name == "PASSKEY") {
+    writeMacroDefinition("BRL_KEY_" name, "(BRLAPI_KEY_TYPE_SYM | 0XFF00)", help)
+  } else {
+    writeSymbolDefinition(name, "BRL_BLK_", "", "(BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_", ")", help)
+  }
+}
+
+function brlKey(name, symbol, value, help) {
+  writeMacroDefinition("BRL_KEY_" name, "(BRLAPI_KEY_SYM_" getBrlapiKeyName(name) " & 0XFF)", help)
+}
+
+function brlFlag(name, symbol, value, help) {
+  if (match(name, "^[^_]*_")) {
+    type = substr(name, 1, RLENGTH-1)
+    if (type == "CHAR") {
+      name = substr(name, RLENGTH+1)
+      writeSymbolDefinition(name, "BRL_FLG_" type "_", "", "BRLAPI_KEY_FLG_", "", help)
+      return
+    }
+  }
+  writeSymbolDefinition(name, "BRL_FLG_", "", "BRLAPI_KEY_FLG_", "", help)
+}
+
+function brlDot(number, symbol, value, help) {
+  writeSymbolDefinition(number, "BRL_DOT", "", "BRLAPI_DOT", "", help)
+}
diff --git a/Programs/brlapi_client.c b/Programs/brlapi_client.c
new file mode 100644
index 0000000..82c1520
--- /dev/null
+++ b/Programs/brlapi_client.c
@@ -0,0 +1,2783 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* api_client.c handles connection with BrlApi */
+
+#ifdef __ANDROID__
+#define __ANDROID_API__ 21
+#endif /* __ANDROID__ */
+
+#define WIN_ERRNO_STORAGE_CLASS static
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <locale.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+
+#ifndef __MINGW32__
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+#endif /* __MINGW32__ */
+
+#ifdef __MINGW32__
+#include <ws2tcpip.h>
+#include "win_pthread.h"
+
+#include "win_errno.h"
+
+#define syslog(level,fmt,...) fprintf(stderr,#level ": " fmt, ## __VA_ARGS__)
+
+#else /* __MINGW32__ */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <pthread.h>
+#include <syslog.h>
+
+#if defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/semaphore.h>
+
+#else /* semaphore support */
+#include <semaphore.h>
+#endif /* semaphore support */
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#ifdef HAVE_SYS_POLL_H
+#include <poll.h>
+#endif /* HAVE_SYS_POLL_H */
+#endif /* __MINGW32__ */
+
+#include <sys/time.h>
+#include "gettime.h"
+
+#ifdef linux
+#include <sys/sysmacros.h>
+#include <linux/major.h>
+#include <linux/tty.h>
+#include <linux/vt.h>
+#define MAXIMUM_VIRTUAL_CONSOLE MAX_NR_CONSOLES
+#endif /* linux */
+
+#ifdef __OpenBSD__
+#define MAXIMUM_VIRTUAL_CONSOLE 16
+#endif /* __OpenBSD__ */
+
+#ifndef MAXIMUM_VIRTUAL_CONSOLE
+#define MAXIMUM_VIRTUAL_CONSOLE 1
+#endif /* MAXIMUM_VIRTUAL_CONSOLE */
+
+#ifdef HAVE_SD_SESSION_GET_VT
+#include <systemd/sd-login.h>
+#endif /* HAVE_SD_SESSION_GET_VT */
+
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+#include "brlapi_protocol.h"
+
+#define BRLAPI(fun) brlapi_ ## fun
+#include "brlapi_common.h"
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b))? (a): (b))
+#endif /* MIN */
+
+#ifndef MAX
+#define MAX(a, b) (((a) > (b))? (a): (b))
+#endif /* MAX */
+
+/* API states */
+#define STCONNECTED 1
+#define STRAW 2
+#define STSUSPEND 4
+#define STCONTROLLINGTTY 8
+
+#ifdef WINDOWS
+#ifdef __MINGW32__
+static WSADATA wsadata;
+#endif /* __MINGW32__ */
+
+static void* GetProc(const char *library, const char *fun) {
+  HMODULE module;
+  void *ret;
+  if (!(module = LoadLibrary(library))) return (void*)(-1);
+  if ((ret = GetProcAddress(module, fun))) return ret;
+  FreeLibrary(module);
+  return (void*)(-1);
+}
+
+#define CHECKPROC(library, name) \
+  (name##Proc && name##Proc != (void*)(-1))
+#define CHECKGETPROC(library, name) \
+  (name##Proc != (void*)(-1) && (name##Proc || (name##Proc = GetProc(library,#name)) != (void*)(-1)))
+#define WIN_PROC_STUB(name) typeof(name) (*name##Proc);
+
+
+static WIN_PROC_STUB(GetConsoleWindow);
+
+#ifdef __MINGW32__
+static WIN_PROC_STUB(wcslen);
+
+static WIN_PROC_STUB(getaddrinfo);
+#define getaddrinfo(host,port,hints,res) getaddrinfoProc(host,port,hints,res)
+static WIN_PROC_STUB(freeaddrinfo);
+#define freeaddrinfo(res) freeaddrinfoProc(res)
+#endif /* __MINGW32__ */
+#endif /* WINDOWS */
+
+/* We need to declare these as weak external references to determine
+ * at runtime whether libpthread is used or not.
+ * We also can't rely on the functions prototypes.
+ */
+#if defined(WINDOWS)
+
+#elif defined(__APPLE__)
+#pragma weak pthread_key_create
+#pragma weak pthread_once
+#pragma weak pthread_getspecific
+#pragma weak pthread_setspecific
+
+typedef semaphore_t sem_t;
+
+static int
+sem_init (sem_t *sem, int shared, int value) {
+  return semaphore_create(mach_task_self(), sem, SYNC_POLICY_FIFO, value);
+}
+
+static int
+sem_post (sem_t *sem) {
+  return semaphore_signal(*sem);
+}
+
+static int
+sem_wait (sem_t *sem) {
+  return semaphore_wait(*sem);
+}
+
+static int
+sem_timedwait (sem_t *sem, const struct timespec *abs) {
+  struct timeval now;
+  getRealTime(&now);
+
+  mach_timespec_t rel = {
+    .tv_sec = abs->tv_sec - now.tv_sec,
+    .tv_nsec = abs->tv_nsec - (now.tv_usec * 1000)
+  };
+
+  if (rel.tv_nsec < 0) rel.tv_sec -= 1, rel.tv_nsec += 1000000000;
+  if (rel.tv_sec < 0) rel.tv_sec = 0, rel.tv_nsec = 0;
+
+  return semaphore_timedwait(*sem, rel);
+}
+
+static int
+sem_trywait (sem_t *sem) {
+  mach_timespec_t rel = { .tv_sec=0, .tv_nsec=0 };
+  return semaphore_timedwait(*sem, rel);
+}
+
+static int
+sem_destroy (sem_t *sem) {
+  return semaphore_destroy(mach_task_self(), *sem);
+}
+
+#elif defined(__GNUC__) || defined(__sun__)
+#pragma weak pthread_key_create
+#pragma weak pthread_once
+#pragma weak pthread_getspecific
+#pragma weak pthread_setspecific
+#pragma weak sem_init
+#pragma weak sem_post
+#pragma weak sem_wait
+#pragma weak sem_destroy
+#endif /* weak external references */
+
+/** key presses buffer size
+ *
+ * key presses won't be lost provided no more than BRL_KEYBUF_SIZE key presses
+ * are done between two calls to brlapi_read* if a call to another function is
+ * done in the meanwhile (which needs somewhere to put them before being able
+ * to get responses from the server)
+*/
+#define BRL_KEYBUF_SIZE 256
+
+struct brlapi_parameterCallback_t {
+  brlapi_param_t parameter;
+  brlapi_param_subparam_t subparam;
+  brlapi_param_flags_t flags;
+  brlapi_paramCallback_t func;
+  void *priv;
+  struct brlapi_parameterCallback_t *prev, *next;
+};
+
+struct brlapi_handle_t { /* Connection-specific information */
+  uint32_t serverVersion;
+  unsigned int brlx;
+  unsigned int brly;
+  brlapi_fileDescriptor fileDescriptor; /* Descriptor of the socket connected to BrlApi */
+  int addrfamily; /* Address family of the socket */
+  /* to protect concurrent fd write operations */
+  pthread_mutex_t fileDescriptor_mutex;
+  /* to protect concurrent fd requests */
+  pthread_mutex_t req_mutex;
+  /* to protect concurrent key reading */
+  pthread_mutex_t key_mutex;
+  /* to protect concurrent fd key events and request answers, also protects the
+   * key buffer */
+  /* Only two threads might want to take it: one that already got
+  * brlapi_req_mutex, or one that got key_mutex */
+  pthread_mutex_t read_mutex;
+  Packet packet;
+  /* when someone is already reading (reading==1), put expected type,
+   * address/size of buffer, and address of return value, then wait on semaphore,
+   * the buffer gets filled, altRes too */
+  int reading;
+  brlapi_packetType_t altExpectedPacketType;
+  unsigned char *altPacket;
+  size_t altSize;
+  ssize_t *altRes;
+  sem_t *altSem;
+  int state;
+  pthread_mutex_t state_mutex;
+
+#ifdef LC_GLOBAL_LOCALE
+  locale_t default_locale;
+#endif /* LC_GLOBAL_LOCALE */
+
+  /* key presses buffer, for when key presses are received instead of
+   * acknowledgements for instance
+   *
+   * every function must hence be able to read at least sizeof(brlapi_keyCode_t) */
+  brlapi_keyCode_t keybuf[BRL_KEYBUF_SIZE];
+  unsigned keybuf_next;
+  unsigned keybuf_nb;
+  union {
+    brlapi_exceptionHandler_t withoutHandle;
+    brlapi__exceptionHandler_t withHandle;
+  } exceptionHandler;
+  int exception_sync;
+  int exception_error;
+  pthread_mutex_t exceptionHandler_mutex;
+
+  struct brlapi_parameterCallback_t *parameterCallbacks;
+  /* This protects the callback list, but also callback calling conventions in
+   * general:
+   * callbacks are called in the update order, and not after unregistration */
+  pthread_mutex_t callbacks_mutex;
+  /* The callback might want to unregister itself, while we are processing the
+   * list of callbacks. This records where we are in the list, to skip the
+   * deleted item. */
+  struct brlapi_parameterCallback_t *nextCallback;
+
+  void *clientData; /* Private client data */
+};
+
+/* Function brlapi_getLibraryVersion */
+void brlapi_getLibraryVersion(int *major, int *minor, int *revision)
+{
+  *major = BRLAPI_MAJOR;
+  *minor = BRLAPI_MINOR;
+  *revision = BRLAPI_REVISION;
+}
+
+/* Function brlapi_getHandleSize */
+size_t BRLAPI_STDCALL brlapi_getHandleSize(void)
+{
+  return sizeof(brlapi_handle_t);
+}
+
+static brlapi_handle_t defaultHandle;
+
+/* brlapi_initializeHandle */
+/* Initialize a BrlAPI handle */
+static void brlapi_initializeHandle(brlapi_handle_t *handle)
+{
+  handle->brlx = 0;
+  handle->brly = 0;
+  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
+  handle->addrfamily = 0;
+  pthread_mutex_init(&handle->fileDescriptor_mutex, NULL);
+  pthread_mutex_init(&handle->req_mutex, NULL);
+  pthread_mutex_init(&handle->key_mutex, NULL);
+  pthread_mutex_init(&handle->read_mutex, NULL);
+  brlapi_initializePacket(&handle->packet);
+  handle->reading = 0;
+  handle->altExpectedPacketType = 0;
+  handle->altPacket = NULL;
+  handle->altSize = 0;
+  handle->altRes = NULL;
+  handle->altSem = NULL;
+  handle->state = 0;
+  pthread_mutex_init(&handle->state_mutex, NULL);
+
+#ifdef LC_GLOBAL_LOCALE
+  handle->default_locale = LC_GLOBAL_LOCALE;
+#endif /* LC_GLOBAL_LOCALE */
+
+  memset(handle->keybuf, 0, sizeof(handle->keybuf));
+  handle->keybuf_next = 0;
+  handle->keybuf_nb = 0;
+  if (handle == &defaultHandle)
+    handle->exceptionHandler.withoutHandle = brlapi_defaultExceptionHandler;
+  else
+    handle->exceptionHandler.withHandle = brlapi__defaultExceptionHandler;
+  handle->exception_sync = 0;
+  handle->exception_error = BRLAPI_ERROR_SUCCESS;
+  pthread_mutex_init(&handle->exceptionHandler_mutex, NULL);
+  handle->parameterCallbacks = NULL;
+  {
+    pthread_mutexattr_t mattr;
+    pthread_mutexattr_init(&mattr);
+    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
+    pthread_mutex_init(&handle->callbacks_mutex, &mattr);
+  }
+  handle->nextCallback = NULL;
+  handle->clientData = NULL;
+}
+
+/* brlapi_doWaitForPacket */
+/* Waits for the specified type of packet: must be called with brlapi_req_mutex locked */
+/* deadline can be used to stop waiting after a given date, or wait forever (NULL) */
+/* If the right packet type arrives, returns its size */
+/* Returns -1 if a non-fatal error is encountered */
+/* Returns -2 on end of file */
+/* Returns -3 if the available packet was not for us */
+/* Returns -4 on timeout (if deadline is not NULL) */
+/* Calls the exception handler if an exception is encountered */
+static ssize_t brlapi__doWaitForPacket(brlapi_handle_t *handle, brlapi_packetType_t expectedPacketType, void *packet, size_t packetSize, struct timeval *deadline)
+{
+  uint32_t *uint32Packet = handle->packet.content;
+  const brlapi_packet_t *localPacket = (brlapi_packet_t *) uint32Packet;
+  const brlapi_errorPacket_t *errorPacket = &localPacket->error;
+
+  uint32_t size;
+  brlapi_packetType_t type;
+  int ret = 0;
+
+  struct timeval now;
+  long delay = 0;
+  int polled = 0; /* Whether we have polled at least once */
+
+  do {
+    if (deadline) {
+      getRealTime(&now);
+      delay = (deadline->tv_sec  - now.tv_sec ) * 1000 +
+	      (deadline->tv_usec - now.tv_usec) / 1000;
+      if (delay < 0) {
+	if (polled) {
+	  /* The deadline has expired, don't wait more */
+	  return -4;
+	}
+	/* Poll at least once */
+	delay = 0;
+      }
+    }
+    polled = 1;
+#ifdef __MINGW32__
+    DWORD dw;
+    dw = WaitForSingleObject(handle->packet.overl.hEvent, deadline ? delay : INFINITE);
+    if (dw == WAIT_FAILED) {
+      setSystemErrno();
+      LibcError("waiting for packet");
+      return -2;
+    }
+
+    if (dw == WAIT_OBJECT_0)
+#else /* __MINGW32__ */
+#ifdef HAVE_POLL
+    struct pollfd pollfd;
+
+    pollfd.fd = handle->fileDescriptor;
+    pollfd.events = POLLIN;
+    pollfd.revents = 0;
+
+    if (poll(&pollfd, 1, deadline ? delay : -1) < 0) {
+      LibcError("waiting for packet");
+      return -2;
+    }
+
+    if (pollfd.revents & POLLIN)
+#else /* HAVE_POLL */
+    fd_set sockset;
+    struct timeval timeout, *ptimeout = NULL;
+    if (deadline) {
+      timeout.tv_sec = delay / 1000;
+      timeout.tv_usec = (delay % 1000) * 1000;
+      ptimeout = &timeout;
+    }
+
+    FD_ZERO(&sockset);
+    FD_SET(handle->fileDescriptor, &sockset);
+    if (select(handle->fileDescriptor+1, &sockset, NULL, NULL, ptimeout) < 0) {
+      LibcError("waiting for packet");
+      return -2;
+    }
+
+    if (FD_ISSET(handle->fileDescriptor, &sockset))
+#endif /* !HAVE_POLL */
+#endif /* __MINGW32__ */
+    {
+      /* Some data is here, read it */
+      ret = brlapi__readPacket(&handle->packet, handle->fileDescriptor);
+      if (ret == -1) {
+	LibcError("reading packet");
+      }
+      if (ret < 0) {
+	/* error or end of file */
+	return -2;
+      }
+    }
+  } while (ret == 0);
+
+  /* Got a packet, process it.  */
+  size = handle->packet.header.size;
+  type = handle->packet.header.type;
+
+  if (type==expectedPacketType)
+  {
+    /* For us, just copy */
+    memcpy(packet, handle->packet.content, MIN(packetSize, size));
+    return size;
+  }
+
+  /* Not for us. For alternate reader? */
+  pthread_mutex_lock(&handle->read_mutex);
+  if (handle->altSem && type==handle->altExpectedPacketType) {
+    /* Yes, put packet content there */
+    memcpy(handle->altPacket, handle->packet.content, MIN(handle->altSize, size));
+    *handle->altRes = size;
+#ifndef WINDOWS
+    if (sem_post)
+#endif /* WINDOWS */
+      sem_post(handle->altSem);
+    handle->altSem = NULL;
+    pthread_mutex_unlock(&handle->read_mutex);
+    return -3;
+  }
+  /* No alternate reader, read it locally... */
+  if ((type==BRLAPI_PACKET_KEY) && (handle->state & STCONTROLLINGTTY) && (size==sizeof(brlapi_keyCode_t))) {
+    /* keypress, buffer it */
+    if (handle->keybuf_nb>=BRL_KEYBUF_SIZE) {
+      syslog(LOG_WARNING,"lost key: 0X%8lx%8lx\n",(unsigned long)ntohl(uint32Packet[0]),(unsigned long)ntohl(uint32Packet[1]));
+    } else {
+      handle->keybuf[(handle->keybuf_next+handle->keybuf_nb++)%BRL_KEYBUF_SIZE]
+          = brlapi_packetToKeyCode(uint32Packet);
+    }
+    pthread_mutex_unlock(&handle->read_mutex);
+    return -3;
+  }
+  if (type==BRLAPI_PACKET_PARAM_UPDATE) {
+    /* Parameter update, find handler */
+    brlapi_paramValuePacket_t *value = (void*) handle->packet.content;
+    brlapi_param_flags_t flags = ntohl(value->flags);
+    brlapi_param_t param = ntohl(value->param);
+    brlapi_param_subparam_t subparam = ((brlapi_param_subparam_t)ntohl(value->subparam_hi) << 32) | ntohl(value->subparam_lo);
+    size_t rlen = size - sizeof(flags) - sizeof(param) - sizeof(subparam);
+    _brlapi_ntohParameter(param, value, rlen);
+    pthread_mutex_lock(&handle->callbacks_mutex);
+    for(handle->nextCallback = handle->parameterCallbacks;
+	handle->nextCallback; ) {
+      struct brlapi_parameterCallback_t *callback = handle->nextCallback;
+      handle->nextCallback = handle->nextCallback->next;
+      if (callback->parameter == param &&
+	  callback->subparam == subparam &&
+	  (callback->flags & BRLAPI_PARAMF_GLOBAL) == (flags & BRLAPI_PARAMF_GLOBAL)) {
+	callback->func(param, subparam, callback->flags, callback->priv, value->data, rlen);
+	/* Note: the callback might have removed this entry */
+      }
+    }
+    handle->nextCallback = NULL;
+    pthread_mutex_unlock(&handle->callbacks_mutex);
+  }
+  pthread_mutex_unlock(&handle->read_mutex);
+
+  /* otherwise this is an error */
+
+  if (type==BRLAPI_PACKET_ERROR) {
+    brlapi_errno = ntohl(errorPacket->code);
+    return -1;
+  }
+
+  if (type==BRLAPI_PACKET_EXCEPTION) {
+    size_t esize;
+    int hdrSize = sizeof(errorPacket->code)+sizeof(errorPacket->type);
+    int err = ntohl(errorPacket->code);
+
+    if (size<hdrSize)
+      esize = 0;
+    else
+      esize = size-hdrSize;
+
+    pthread_mutex_lock(&handle->exceptionHandler_mutex);
+    if (handle->exception_sync) {
+      handle->exception_error = err;
+      pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+      return -3;
+    }
+    if (handle==&defaultHandle)
+      defaultHandle.exceptionHandler.withoutHandle(err, ntohl(errorPacket->type), &errorPacket->packet, esize);
+    else
+      handle->exceptionHandler.withHandle(handle, err, ntohl(errorPacket->type), &errorPacket->packet, esize);
+    pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+
+    pthread_mutex_lock(&handle->fileDescriptor_mutex);
+    closeFileDescriptor(handle->fileDescriptor);
+    handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
+    pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+
+    return -2;
+  }
+
+  syslog(LOG_ERR,"(brlapi_waitForPacket) Received unexpected packet of type %s and size %ld\n",brlapi_getPacketTypeName(type),(long)size);
+  return -3;
+}
+
+#define TRY_WAIT_FOR_EXPECTED_PACKET 0
+#define WAIT_FOR_EXPECTED_PACKET 1
+
+#define POLL 0
+#define WAIT_FOREVER (-1)
+
+/* brlapi_waitForPacket */
+/* same as brlapi_doWaitForPacket, but sleeps instead of reading if another
+ * thread is already reading, and timeout_ms expressed as relative ms delay
+ * instead of absolute deadline.
+ * timeout_ms set to WAIT_FOREVER means no deadline.
+ * Never returns -2. If loop is WAIT_FOR_EXPECTED_PACKET, never returns -3.
+ */
+static ssize_t brlapi__waitForPacket(brlapi_handle_t *handle, brlapi_packetType_t expectedPacketType, void *packet, size_t size, int loop, int timeout_ms) {
+  int doread;
+  ssize_t res;
+  sem_t sem;
+  struct timeval deadline, *pdeadline = NULL;
+  if (timeout_ms >= 0) {
+    getRealTime(&deadline);
+    deadline.tv_sec += timeout_ms / 1000;
+    deadline.tv_usec += (timeout_ms % 1000) * 1000;
+    if (deadline.tv_usec >= 1000000) {
+      deadline.tv_sec++;
+      deadline.tv_usec -= 1000000;
+    }
+    pdeadline = &deadline;
+  }
+again:
+  doread = 0;
+  pthread_mutex_lock(&handle->read_mutex);
+  if (!handle->reading) {
+    doread = handle->reading = 1;
+  } else {
+    if (
+#ifndef WINDOWS
+      !sem_init || !sem_post || !sem_wait || !sem_destroy ||
+#endif /* WINDOWS */
+	handle->altSem) {
+      /* This can't happen without threads */
+      pthread_mutex_unlock(&handle->read_mutex);
+      syslog(LOG_ERR,"third call to brlapi_waitForPacket !");
+      brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+      return -1;
+    }
+
+    handle->altExpectedPacketType = expectedPacketType;
+    handle->altPacket = packet;
+    handle->altSize = size;
+    handle->altRes = &res;
+    sem_init(&sem, 0, 0);
+    handle->altSem = &sem;
+  }
+  pthread_mutex_unlock(&handle->read_mutex);
+  if (doread) {
+    do {
+      res = brlapi__doWaitForPacket(handle, expectedPacketType, packet, size, pdeadline);
+    } while (loop && (res == -3 || (res == -1 && brlapi_errno == BRLAPI_ERROR_LIBCERR && (
+	  brlapi_libcerrno == EINTR ||
+#ifdef EWOULDBLOCK
+	  brlapi_libcerrno == EWOULDBLOCK ||
+#endif /* EWOULDBLOCK */
+	  brlapi_libcerrno == EAGAIN))));
+    pthread_mutex_lock(&handle->read_mutex);
+    if (handle->altSem) {
+      *handle->altRes = -3; /* no packet for him */
+#ifndef WINDOWS
+      if (sem_post)
+#endif /* WINDOWS */
+        sem_post(handle->altSem);
+      handle->altSem = NULL;
+    }
+    handle->reading = 0;
+    pthread_mutex_unlock(&handle->read_mutex);
+  } else {
+    int ret;
+    if (timeout_ms == 0) {
+      ret = sem_trywait(&sem);
+    } else if (timeout_ms > 0) {
+      struct timespec abs_timeout;
+      abs_timeout.tv_sec = deadline.tv_sec;
+      abs_timeout.tv_nsec = deadline.tv_usec * 1000;
+      ret = sem_timedwait(&sem, &abs_timeout);
+    } else {
+      do {
+	ret = sem_wait(&sem);
+      } while (ret && errno == EINTR);
+    }
+    if (ret) {
+      /* Timeout, drop our semaphore */
+      pthread_mutex_lock(&handle->read_mutex);
+      handle->altSem = NULL;
+      pthread_mutex_unlock(&handle->read_mutex);
+      res = -4;
+    }
+    sem_destroy(&sem);
+    if (res == -1 /* He got an error, we want to read it too */
+	|| (res == -3 && loop)) /* reader hadn't any packet for us */
+      goto again;
+  }
+  if (res==-2) {
+    res = -1;
+    brlapi_errno = BRLAPI_ERROR_EOF;
+  }
+  return res;
+}
+
+/* brlapi_waitForAck */
+/* Wait for an acknowledgement, must be called with brlapi_req_mutex locked */
+static int brlapi__waitForAck(brlapi_handle_t *handle)
+{
+  return brlapi__waitForPacket(handle, BRLAPI_PACKET_ACK, NULL, 0, WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
+}
+
+/* brlapi_writePacketWaitForAck */
+/* write a packet and wait for an acknowledgement */
+static int brlapi__writePacketWaitForAck(brlapi_handle_t *handle, brlapi_packetType_t type, const void *buf, size_t size)
+{
+  ssize_t res;
+  pthread_mutex_lock(&handle->req_mutex);
+  if ((res=brlapi_writePacket(handle->fileDescriptor, type,buf,size))<0) {
+    pthread_mutex_unlock(&handle->req_mutex);
+    return res;
+  }
+  res=brlapi__waitForAck(handle);
+  pthread_mutex_unlock(&handle->req_mutex);
+  return res;
+}
+
+/* brlapi__pause */
+/* Wait for an event to be received */
+int BRLAPI_STDCALL brlapi__pause(brlapi_handle_t *handle, int timeout_ms) {
+  ssize_t res = brlapi__waitForPacket(handle, 0, NULL, 0, TRY_WAIT_FOR_EXPECTED_PACKET, timeout_ms);
+  if (res == -3) {
+    brlapi_libcerrno = EINTR;
+    brlapi_errno = BRLAPI_ERROR_LIBCERR;
+    brlapi_errfun = "waitForPacket";
+    res = -1;
+  }
+  if (res == -4) {
+    /* Timeout */
+    res = 0;
+  }
+  return res;
+}
+
+/* brlapi_pause */
+int BRLAPI_STDCALL brlapi_pause(int timeout_ms) {
+  return brlapi__pause(&defaultHandle, timeout_ms);
+}
+
+
+/* brlapi__sync */
+int BRLAPI_STDCALL brlapi__sync(brlapi_handle_t *handle) {
+  int res, error;
+
+  pthread_mutex_lock(&handle->exceptionHandler_mutex);
+  handle->exception_sync++;
+  error = handle->exception_error;
+  handle->exception_error = BRLAPI_ERROR_SUCCESS;
+  pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+
+  if (error != BRLAPI_ERROR_SUCCESS) {
+    pthread_mutex_lock(&handle->exceptionHandler_mutex);
+    handle->exception_sync--;
+    pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+    brlapi_errno = error;
+    return -1;
+  }
+
+  res = brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_SYNCHRONIZE,NULL,0);
+
+  if (res == -1) {
+    pthread_mutex_lock(&handle->exceptionHandler_mutex);
+    handle->exception_sync--;
+    pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+    return -1;
+  }
+
+  pthread_mutex_lock(&handle->exceptionHandler_mutex);
+  handle->exception_sync--;
+  error = handle->exception_error;
+  handle->exception_error = BRLAPI_ERROR_SUCCESS;
+  pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+
+  if (error != BRLAPI_ERROR_SUCCESS) {
+    brlapi_errno = error;
+    return -1;
+  }
+  return 0;
+}
+
+/* brlapi_sync */
+int BRLAPI_STDCALL brlapi_sync(void) {
+  return brlapi__sync(&defaultHandle);
+}
+
+/* Function: tryHost */
+/* Tries to connect to the given host. */
+static int tryHost(brlapi_handle_t *handle, const char *hostAndPort) {
+  char *host = NULL;
+  char *port;
+  SocketDescriptor sockfd = -1;
+
+#ifdef __MINGW32__
+  if (WSAStartup(
+#ifdef HAVE_GETADDRINFO
+	  MAKEWORD(2,0),
+#else /* HAVE_GETADDRINFO */
+	  MAKEWORD(1,1),
+#endif /* HAVE_GETADDRINFO */
+	  &wsadata))
+    return -1;
+#endif /* __MINGW32__ */
+
+  handle->addrfamily = brlapi_expandHost(hostAndPort,&host,&port);
+
+#if defined(PF_LOCAL)
+  if (handle->addrfamily == PF_LOCAL) {
+    int lpath = strlen(BRLAPI_SOCKETPATH),lport;
+    lport = strlen(port);
+
+#ifdef __MINGW32__
+    {
+      HANDLE pipefd;
+      char path[lpath+lport+1];
+
+      memcpy(path, BRLAPI_SOCKETPATH, lpath);
+      memcpy(path+lpath, port, lport+1);
+
+      while ((pipefd = CreateFile(path,GENERIC_READ|GENERIC_WRITE,
+	      FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,SECURITY_IMPERSONATION|FILE_FLAG_OVERLAPPED,NULL))
+	  == INVALID_HANDLE_VALUE) {
+	if (GetLastError() != ERROR_PIPE_BUSY) {
+	  brlapi_errfun = "CreateFile";
+	  goto outlibc;
+	}
+
+	WaitNamedPipe(path, NMPWAIT_WAIT_FOREVER);
+      }
+
+      sockfd = (SocketDescriptor)pipefd;
+    }
+#else /* __MINGW32__ */
+    {
+      struct sockaddr_un sa;
+
+      if (lpath+1+lport+1 > sizeof(sa.sun_path)) {
+	brlapi_libcerrno=ENAMETOOLONG;
+	brlapi_errfun="path";
+	brlapi_errno = BRLAPI_ERROR_LIBCERR;
+	goto out;
+      }
+
+      if ((sockfd = socket(PF_LOCAL, SOCK_STREAM, 0))<0) {
+        brlapi_errfun="socket";
+        setSocketErrno();
+        goto outlibc;
+      }
+
+#if !defined(HAVE_POLL)
+      if (sockfd >= FD_SETSIZE) {
+	/* Will not be able to call select() on this */
+	closeFileDescriptor(sockfd);
+	brlapi_errfun="socket";
+	setErrno(EMFILE);
+	goto outlibc;
+      }
+#endif /* !HAVE_POLL */
+
+      sa.sun_family = AF_LOCAL;
+      memcpy(sa.sun_path,BRLAPI_SOCKETPATH "/",lpath+1);
+      memcpy(sa.sun_path+lpath+1,port,lport+1);
+
+      if (connect(sockfd, (struct sockaddr *) &sa, sizeof(sa))<0) {
+        brlapi_errfun="connect";
+        setSocketErrno();
+        goto outlibc;
+      }
+    }
+#endif /* __MINGW32__ */
+
+    handle->fileDescriptor = (FileDescriptor) sockfd;
+  } else {
+#else /* PF_LOCAL */
+  if (0) {} else {
+#endif /* PF_LOCAL */
+
+#ifdef __MINGW32__
+    if (CHECKGETPROC("ws2_32.dll",getaddrinfo)
+	&& CHECKGETPROC("ws2_32.dll",freeaddrinfo)) {
+#endif /* __MINGW32__ */
+#if defined(HAVE_GETADDRINFO) || defined(__MINGW32__)
+
+    struct addrinfo *res,*cur;
+    struct addrinfo hints;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+
+    brlapi_gaierrno = getaddrinfo(host, port, &hints, &res);
+    if (brlapi_gaierrno) {
+      brlapi_errno=BRLAPI_ERROR_GAIERR;
+      brlapi_libcerrno=errno;
+      goto out;
+    }
+    for(cur = res; cur; cur = cur->ai_next) {
+      sockfd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
+      if (sockfd<0) continue;
+
+#if !defined(__MINGW32__) && !defined(HAVE_POLL)
+      if (sockfd >= FD_SETSIZE) {
+	/* Will not be able to call select() on this */
+	closeFileDescriptor(sockfd);
+	brlapi_errfun="socket";
+	setErrno(EMFILE);
+	goto outlibc;
+      }
+#endif /* !__MINGW32__ && !HAVE_POLL */
+
+      if (connect(sockfd, cur->ai_addr, cur->ai_addrlen)<0) {
+        closeSocketDescriptor(sockfd);
+        continue;
+      }
+      break;
+    }
+    freeaddrinfo(res);
+    if (!cur) {
+      brlapi_errno=BRLAPI_ERROR_CONNREFUSED;
+      goto out;
+    }
+
+#endif /* HAVE_GETADDRINFO */
+#ifdef __MINGW32__
+    } else {
+#endif /* __MINGW32__ */
+#if !defined(HAVE_GETADDRINFO) || defined(__MINGW32__)
+
+    struct sockaddr_in addr;
+    struct hostent *he;
+
+    memset(&addr,0,sizeof(addr));
+    addr.sin_family = AF_INET;
+    if (!port)
+      addr.sin_port = htons(BRLAPI_SOCKETPORTNUM);
+    else {
+      char *c;
+      addr.sin_port = htons(strtol(port, &c, 0));
+      if (*c) {
+	struct servent *se;
+
+	if (!(se = getservbyname(port,"tcp"))) {
+	  brlapi_gaierrno = h_errno;
+          brlapi_errno=BRLAPI_ERROR_GAIERR;
+	  goto out;
+	}
+	addr.sin_port = se->s_port;
+      }
+    }
+
+    if (!host) {
+      addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    } else if ((addr.sin_addr.s_addr = inet_addr(host)) == htonl(INADDR_NONE)) {
+      if (!(he = gethostbyname(host))) {
+	brlapi_gaierrno = h_errno;
+	brlapi_errno = BRLAPI_ERROR_GAIERR;
+	goto out;
+      }
+      if (he->h_addrtype != AF_INET) {
+#ifdef EAFNOSUPPORT
+        errno = EAFNOSUPPORT;
+#else /* EAFNOSUPPORT */
+        errno = EINVAL;
+#endif /* EAFNOSUPPORT */
+	brlapi_errfun = "gethostbyname";
+	goto outlibc;
+      }
+      if (he->h_length > sizeof(addr.sin_addr)) {
+        errno = EINVAL;
+	brlapi_errfun = "gethostbyname";
+	goto outlibc;
+      }
+      memcpy(&addr.sin_addr,he->h_addr,he->h_length);
+    }
+
+    sockfd = socket(addr.sin_family, SOCK_STREAM, 0);
+    if (sockfd<0) {
+      brlapi_errfun = "socket";
+      setSocketErrno();
+      goto outlibc;
+    }
+
+#if !defined(__MINGW32__) && !defined(HAVE_POLL)
+    if (sockfd >= FD_SETSIZE) {
+      /* Will not be able to call select() on this */
+      closeFileDescriptor(sockfd);
+      brlapi_errfun="socket";
+      setErrno(EMFILE);
+      goto outlibc;
+    }
+#endif /* !__MINGW32__ && !HAVE_POLL */
+
+    if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr))<0) {
+      brlapi_errfun = "connect";
+      setSocketErrno();
+      goto outlibc;
+    }
+
+#endif /* !HAVE_GETADDRINFO */
+#ifdef __MINGW32__
+    }
+#endif /* __MINGW32__ */
+
+#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
+    {
+      int yes=1;
+      setsockopt(sockfd,IPPROTO_TCP,TCP_NODELAY,(void*)&yes,sizeof(yes));
+    }
+#endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */
+    handle->fileDescriptor = (FileDescriptor) sockfd;
+  }
+  free(host);
+  free(port);
+  return 0;
+
+outlibc:
+  brlapi_errno = BRLAPI_ERROR_LIBCERR;
+  brlapi_libcerrno = errno;
+  if (sockfd>=0)
+    closeSocketDescriptor(sockfd);
+out:
+  free(host);
+  free(port);
+  return -1;
+}
+
+/* Function : updateSettings */
+/* Updates the content of a brlapi_connectionSettings_t structure according to */
+/* another structure of the same type */
+static void updateSettings(brlapi_connectionSettings_t *s1, const brlapi_connectionSettings_t *s2)
+{
+  if (s2==NULL) return;
+  if ((s2->auth) && (*s2->auth))
+    s1->auth = s2->auth;
+  if ((s2->host) && (*s2->host))
+    s1->host = s2->host;
+}
+
+/* Function: brlapi_openConnection
+ * Creates a socket to connect to BrlApi */
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__openConnection(brlapi_handle_t *handle, const brlapi_connectionSettings_t *clientSettings, brlapi_connectionSettings_t *usedSettings)
+{
+  brlapi_packet_t packet;
+  brlapi_packet_t serverPacket;
+  brlapi_authClientPacket_t *auth = &packet.authClient;
+  brlapi_authServerPacket_t *authServer = &serverPacket.authServer;
+  brlapi_versionPacket_t *version = &serverPacket.version;
+  uint32_t *type;
+  int len;
+
+  brlapi_connectionSettings_t settings = { BRLAPI_DEFAUTH, ":0" };
+  brlapi_connectionSettings_t envsettings = { getenv("BRLAPI_AUTH"), getenv("BRLAPI_HOST") };
+
+  /* Here update settings with the parameters from misc sources (files, env...) */
+  updateSettings(&settings, &envsettings);
+  updateSettings(&settings, clientSettings);
+  if (usedSettings!=NULL) updateSettings(usedSettings, &settings);
+  brlapi_initializeHandle(handle);
+
+  if (tryHost(handle, settings.host)<0) {
+    const char *port = strrchr(settings.host, ':');
+    if (port != settings.host) goto out;
+    if (!isPortNumber(port+1, NULL)) goto out;
+
+    brlapi_error_t originalError = brlapi_error;
+    size_t portLength = strlen(port);
+
+    static const char *const localhostAddressTable[] = {
+      LOCALHOST_ADDRESS_IPV4,
+
+      #ifdef AF_INET6
+      LOCALHOST_ADDRESS_IPV6,
+      #endif /* AF_INET6 */
+    };
+
+    const char *const *lha = localhostAddressTable;
+    const char *const *end = lha + ARRAY_COUNT(localhostAddressTable);
+
+    while (lha < end) {
+      size_t hostLength = strlen(*lha);
+      char host[hostLength + portLength + 1];
+      snprintf(host, sizeof(host), "%s%s", *lha, port);
+
+      if (tryHost(handle, host) != -1) {
+        if (usedSettings) usedSettings->host = strdup(host);
+        break;
+      }
+
+      lha += 1;
+    }
+
+    if (lha == end) {
+      brlapi_error = originalError;
+      goto out;
+    }
+  }
+
+  if ((len = brlapi__waitForPacket(handle, BRLAPI_PACKET_VERSION, &serverPacket, sizeof(serverPacket), WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER)) < 0)
+    goto outfd;
+
+  handle->serverVersion = ntohl(version->protocolVersion);
+  if (handle->serverVersion < 8) {
+    /* We only provide compatibility with version 8 and later. */
+    brlapi_errno = BRLAPI_ERROR_PROTOCOL_VERSION;
+    goto outfd;
+  }
+
+  version->protocolVersion = htonl(BRLAPI_PROTOCOL_VERSION);
+  if (brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_VERSION, version, sizeof(*version)) < 0)
+    goto outfd;
+
+  if ((len = brlapi__waitForPacket(handle, BRLAPI_PACKET_AUTH, &serverPacket, sizeof(serverPacket), WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER)) < 0)
+    return BRLAPI_INVALID_FILE_DESCRIPTOR;
+
+  for (type = &authServer->type[0]; type < &authServer->type[len / sizeof(authServer->type[0])]; type++) {
+    auth->type = *type;
+    switch (ntohl(*type)) {
+      case BRLAPI_AUTH_NONE:
+	if (usedSettings) usedSettings->auth = "none";
+	goto done;
+      case BRLAPI_AUTH_KEY: {
+        size_t authKeyLength;
+	int res;
+	char *keyfile = brlapi_getKeyFile(settings.auth);
+	if (!keyfile)
+	  continue;
+	res = brlapi_loadAuthKey(keyfile, &authKeyLength, (void *) &auth->key);
+	free(keyfile);
+        if (res < 0)
+	  continue;
+        res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_AUTH, auth,
+	  sizeof(auth->type)+authKeyLength);
+	memset(&auth->key, 0, authKeyLength);
+	if (res < 0)
+	  goto outfd;
+	if (usedSettings) usedSettings->auth = settings.auth;
+	break;
+      }
+      default:
+        /* unsupported authorization type */
+	continue;
+    }
+    if ((brlapi__waitForAck(handle)) == 0) goto done;
+  }
+
+  /* no suitable authorization method */
+  brlapi_errno = BRLAPI_ERROR_AUTHENTICATION;
+outfd:
+  closeFileDescriptor(handle->fileDescriptor);
+  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
+out:
+  return BRLAPI_INVALID_FILE_DESCRIPTOR;
+
+done:
+  pthread_mutex_lock(&handle->state_mutex);
+  handle->state = STCONNECTED;
+  pthread_mutex_unlock(&handle->state_mutex);
+  return handle->fileDescriptor;
+}
+
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_openConnection(const brlapi_connectionSettings_t *clientSettings, brlapi_connectionSettings_t *usedSettings)
+{
+  return brlapi__openConnection(&defaultHandle, clientSettings, usedSettings);
+}
+
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi__getFileDescriptor(brlapi_handle_t *handle)
+{
+  return handle->fileDescriptor;
+}
+
+brlapi_fileDescriptor BRLAPI_STDCALL brlapi_getFileDescriptor(void)
+{
+  return brlapi__getFileDescriptor(&defaultHandle);
+}
+
+/* brlapi_closeConnection */
+/* Cleanly close the socket */
+void BRLAPI_STDCALL brlapi__closeConnection(brlapi_handle_t *handle)
+{
+  pthread_mutex_lock(&handle->state_mutex);
+  handle->state = 0;
+  pthread_mutex_unlock(&handle->state_mutex);
+  pthread_mutex_lock(&handle->fileDescriptor_mutex);
+  closeFileDescriptor(handle->fileDescriptor);
+  handle->fileDescriptor = BRLAPI_INVALID_FILE_DESCRIPTOR;
+  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+
+#ifdef LC_GLOBAL_LOCALE
+  if (handle->default_locale != LC_GLOBAL_LOCALE) {
+    freelocale(handle->default_locale);
+  }
+#endif /* LC_GLOBAL_LOCALE */
+
+#ifdef __MINGW32__
+  WSACleanup();
+#endif /* __MINGW32__ */
+}
+
+void BRLAPI_STDCALL brlapi_closeConnection(void)
+{
+  brlapi__closeConnection(&defaultHandle);
+}
+
+/* brlapi__setClientData */
+void BRLAPI_STDCALL brlapi__setClientData(brlapi_handle_t *handle, void *data)
+{
+  handle->clientData = data;
+}
+
+void BRLAPI_STDCALL brlapi_setClientData(void *data)
+{
+  brlapi__setClientData(&defaultHandle, data);
+}
+
+/* brlapi__getClientData */
+void * BRLAPI_STDCALL brlapi__getClientData(brlapi_handle_t *handle)
+{
+  return handle->clientData;
+}
+
+void * BRLAPI_STDCALL brlapi_getClientData(void)
+{
+  return brlapi__getClientData(&defaultHandle);
+}
+
+/* brlapi_getDriverSpecific */
+/* Switch to device specific mode */
+static int brlapi__getDriverSpecific(brlapi_handle_t *handle, const char *driver, brlapi_packetType_t type, int st)
+{
+  int res;
+  brlapi_packet_t packet;
+  brlapi_getDriverSpecificModePacket_t *driverPacket = &packet.getDriverSpecificMode;
+  unsigned int n = strlen(driver);
+  if (n>BRLAPI_MAXNAMELENGTH) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+  pthread_mutex_lock(&defaultHandle.state_mutex);
+  if ((defaultHandle.state & st)) {
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    res = -1;
+    goto out;
+  }
+  driverPacket->magic = htonl(BRLAPI_DEVICE_MAGIC);
+  driverPacket->nameLength = n;
+  memcpy(&driverPacket->name, driver, n);
+  res = brlapi__writePacketWaitForAck(handle, type, &packet, sizeof(uint32_t)+1+n);
+  if (res!=-1)
+    handle->state |= st;
+out:
+  pthread_mutex_unlock(&handle->state_mutex);
+  return res;
+}
+
+/* brlapi_leaveDriverSpecific */
+/* Leave device specific mode */
+static int brlapi__leaveDriverSpecific(brlapi_handle_t *handle, brlapi_packetType_t type, int st)
+{
+  int res;
+  pthread_mutex_lock(&handle->state_mutex);
+  if (!(handle->state & st)) {
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    res = -1;
+    goto out;
+  }
+  res = brlapi__writePacketWaitForAck(handle, type, NULL, 0);
+  if (!res)
+    handle->state &= ~st;
+out:
+  pthread_mutex_unlock(&handle->state_mutex);
+  return res;
+}
+
+/* brlapi_enterRawMode */
+/* Switch to Raw mode */
+int BRLAPI_STDCALL brlapi__enterRawMode(brlapi_handle_t *handle, const char *driver)
+{
+  return brlapi__getDriverSpecific(handle, driver, BRLAPI_PACKET_ENTERRAWMODE, STRAW);
+}
+
+int BRLAPI_STDCALL brlapi_enterRawMode(const char *driver)
+{
+  return brlapi__enterRawMode(&defaultHandle, driver);
+}
+
+/* brlapi_leaveRawMode */
+/* Leave Raw mode */
+int BRLAPI_STDCALL brlapi__leaveRawMode(brlapi_handle_t *handle)
+{
+  return brlapi__leaveDriverSpecific(handle, BRLAPI_PACKET_LEAVERAWMODE, STRAW);
+}
+
+int BRLAPI_STDCALL brlapi_leaveRawMode(void)
+{
+  return brlapi__leaveRawMode(&defaultHandle);
+}
+
+/* brlapi_sendRaw */
+/* Send a Raw Packet */
+ssize_t BRLAPI_STDCALL brlapi__sendRaw(brlapi_handle_t *handle, const void *buf, size_t size)
+{
+  ssize_t res;
+  pthread_mutex_lock(&handle->fileDescriptor_mutex);
+  res=brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_PACKET, buf, size);
+  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+  return res;
+}
+
+ssize_t BRLAPI_STDCALL brlapi_sendRaw(const void *buf, size_t size)
+{
+  return brlapi__sendRaw(&defaultHandle, buf, size);
+}
+
+/* brlapi_recvRaw */
+/* Get a Raw packet */
+ssize_t BRLAPI_STDCALL brlapi__recvRaw(brlapi_handle_t *handle, void *buf, size_t size)
+{
+  ssize_t res;
+  if (!(handle->state & STRAW)) {
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    return -1;
+  }
+  res = brlapi__waitForPacket(handle, BRLAPI_PACKET_PACKET, buf, size, TRY_WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
+  if (res == -3) {
+    brlapi_libcerrno = EINTR;
+    brlapi_errno = BRLAPI_ERROR_LIBCERR;
+    brlapi_errfun = "waitForPacket";
+    res = -1;
+  }
+  return res;
+}
+
+ssize_t BRLAPI_STDCALL brlapi_recvRaw(void *buf, size_t size)
+{
+  return brlapi__recvRaw(&defaultHandle, buf, size);
+}
+
+/* brlapi_suspendDriver */
+int BRLAPI_STDCALL brlapi__suspendDriver(brlapi_handle_t *handle, const char *driver)
+{
+  return brlapi__getDriverSpecific(handle, driver, BRLAPI_PACKET_SUSPENDDRIVER, STSUSPEND);
+}
+
+int BRLAPI_STDCALL brlapi_suspendDriver(const char *driver)
+{
+  return brlapi__suspendDriver(&defaultHandle, driver);
+}
+
+/* brlapi_resumeDriver */
+int BRLAPI_STDCALL brlapi__resumeDriver(brlapi_handle_t *handle)
+{
+  return brlapi__leaveDriverSpecific(handle, BRLAPI_PACKET_RESUMEDRIVER, STSUSPEND);
+}
+
+int BRLAPI_STDCALL brlapi_resumeDriver(void)
+{
+  return brlapi__resumeDriver(&defaultHandle);
+}
+
+/* Function brlapi_request */
+/* Sends a request to the API and waits for the answer */
+/* The answer is put in the given packet */
+static ssize_t brlapi__request(brlapi_handle_t *handle, brlapi_packetType_t request, void *packet, size_t size)
+{
+  ssize_t res;
+  pthread_mutex_lock(&handle->req_mutex);
+  res = brlapi_writePacket(handle->fileDescriptor, request, NULL, 0);
+  if (res==-1) {
+    pthread_mutex_unlock(&handle->req_mutex);
+    return -1;
+  }
+  res = brlapi__waitForPacket(handle, request, packet, size, WAIT_FOR_EXPECTED_PACKET, WAIT_FOREVER);
+  pthread_mutex_unlock(&handle->req_mutex);
+  return res;
+}
+
+/* Function : brlapi_getDriverName */
+/* Name of the driver used by brltty */
+int BRLAPI_STDCALL brlapi__getDriverName(brlapi_handle_t *handle, char *name, size_t n)
+{
+  ssize_t res = brlapi__request(handle, BRLAPI_PACKET_GETDRIVERNAME, name, n);
+  if ((res>0) && (res<=n)) name[res-1] = '\0';
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_getDriverName(char *name, size_t n)
+{
+  return brlapi__getDriverName(&defaultHandle, name, n);
+}
+
+/* Function : brlapi_getModelIdentifier */
+/* An identifier for the model of the device used by brltty */
+int BRLAPI_STDCALL brlapi__getModelIdentifier(brlapi_handle_t *handle, char *identifier, size_t n)
+{
+  ssize_t res = brlapi__request(handle, BRLAPI_PACKET_GETMODELID, identifier, n);
+  if ((res>0) && (res<=n)) identifier[res-1] = '\0';
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_getModelIdentifier(char *identifier, size_t n)
+{
+  return brlapi__getModelIdentifier(&defaultHandle, identifier, n);
+}
+
+/* Function : brlapi_getDisplaySize */
+/* Returns the size of the braille display */
+int BRLAPI_STDCALL brlapi__getDisplaySize(brlapi_handle_t *handle, unsigned int *x, unsigned int *y)
+{
+  uint32_t displaySize[2];
+  ssize_t res;
+
+  if (handle->brlx&&handle->brly) { *x = handle->brlx; *y = handle->brly; return 0; }
+  res = brlapi__request(handle, BRLAPI_PACKET_GETDISPLAYSIZE, displaySize, sizeof(displaySize));
+  if (res==-1) { return -1; }
+  handle->brlx = ntohl(displaySize[0]);
+  handle->brly = ntohl(displaySize[1]);
+  *x = handle->brlx; *y = handle->brly;
+  return 0;
+}
+
+int BRLAPI_STDCALL brlapi_getDisplaySize(unsigned int *x, unsigned int *y)
+{
+  return brlapi__getDisplaySize(&defaultHandle, x, y);
+}
+
+/* Function: brlapi_getParameter */
+
+/* Internal version, returns the reply value packet and the length of the value */
+static ssize_t _brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramValuePacket_t *reply)
+{
+  brlapi_paramRequestPacket_t request;
+  int res;
+  ssize_t rlen;
+
+  request.flags = htonl(flags);
+  request.param = htonl(parameter);
+  request.subparam_hi = htonl(subparam >> 32);
+  request.subparam_lo = htonl(subparam & 0xfffffffful);
+
+  pthread_mutex_lock(&handle->req_mutex);
+  res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_PARAM_REQUEST, &request, sizeof(request));
+  if (res < 0) {
+    pthread_mutex_unlock(&handle->req_mutex);
+    return -1;
+  }
+  if (flags & BRLAPI_PARAMF_GET)
+    rlen = brlapi__waitForPacket(handle, BRLAPI_PACKET_PARAM_VALUE, reply, sizeof(*reply), 1, -1);
+  else
+    rlen = brlapi__waitForAck(handle);
+  pthread_mutex_unlock(&handle->req_mutex);
+
+  if (rlen < 0) {
+    return -1;
+  }
+
+  if (flags & BRLAPI_PARAMF_GET) {
+    rlen -= sizeof(brlapi_param_flags_t) + sizeof(brlapi_param_t) + sizeof(brlapi_param_subparam_t);
+    if (rlen < 0) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+  }
+
+  return rlen;
+}
+
+ssize_t BRLAPI_STDCALL brlapi__getParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len)
+{
+  brlapi_paramValuePacket_t reply;
+  ssize_t rlen;
+
+  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+
+  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET, &reply);
+  if (rlen < 0)
+    return -1;
+
+  if (rlen < len) {
+    len = rlen;
+  }
+  _brlapi_ntohParameter(parameter, &reply, rlen);
+  memcpy(data, &reply.data, len);
+
+  return rlen;
+}
+
+ssize_t BRLAPI_STDCALL brlapi_getParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void* data, size_t len)
+{
+  return brlapi__getParameter(&defaultHandle, parameter, subparam, flags, data, len);
+}
+
+void* BRLAPI_STDCALL brlapi__getParameterAlloc(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len)
+{
+  brlapi_paramValuePacket_t reply;
+  ssize_t rlen;
+  void *data;
+
+  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return NULL;
+  }
+
+  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET, &reply);
+  if (rlen < 0)
+    return NULL;
+
+  data = malloc(rlen + 1);
+  if (!data)
+    return NULL;
+
+  _brlapi_ntohParameter(parameter, &reply, rlen);
+  memcpy(data, &reply.data, rlen);
+  ((char*)data)[rlen] = 0;
+  if (len)
+    *len = rlen;
+  return data;
+}
+
+void* BRLAPI_STDCALL brlapi_getParameterAlloc(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, size_t *len)
+{
+  return brlapi__getParameterAlloc(&defaultHandle, parameter, subparam, flags, len);
+}
+
+/* Function: brlapi_setParameter */
+int BRLAPI_STDCALL brlapi__setParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len)
+{
+  brlapi_paramValuePacket_t packet;
+  int res;
+
+  if (flags & ~BRLAPI_PARAMF_GLOBAL) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+
+  if (len > sizeof(packet.data)) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+
+  packet.flags = htonl(flags);
+  packet.param = htonl(parameter);
+  packet.subparam_hi = htonl(subparam >> 32);
+  packet.subparam_lo = htonl(subparam & 0xfffffffful);
+  memcpy(packet.data, data, len);
+  _brlapi_htonParameter(parameter, &packet, len);
+
+  res = brlapi__writePacketWaitForAck(handle, BRLAPI_PACKET_PARAM_VALUE, &packet, sizeof(packet.flags) + sizeof(parameter) + sizeof(subparam) + len);
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_setParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void* data, size_t len)
+{
+  return brlapi__setParameter(&defaultHandle, parameter, subparam, flags, data, len);
+}
+
+/* Function: brlapi_watchParameter */
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi__watchParameter(brlapi_handle_t *handle, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len)
+{
+  brlapi_paramValuePacket_t reply;
+  ssize_t rlen;
+  struct brlapi_parameterCallback_t *callback;
+
+  if (flags & ~(BRLAPI_PARAMF_GLOBAL|BRLAPI_PARAMF_SELF)) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return NULL;
+  }
+
+  pthread_mutex_lock(&handle->callbacks_mutex);
+  rlen = _brlapi__getParameter(handle, parameter, subparam, flags | BRLAPI_PARAMF_GET | BRLAPI_PARAMF_SUBSCRIBE, &reply);
+  if (rlen < 0) {
+    pthread_mutex_unlock(&handle->callbacks_mutex);
+    return NULL;
+  }
+
+  callback = malloc(sizeof(*callback));
+  if (callback == NULL) {
+    brlapi_errno = BRLAPI_ERROR_NOMEM;
+    return NULL;
+  }
+  callback->parameter = parameter;
+  callback->subparam = subparam;
+  callback->flags = flags;
+  callback->func = func;
+  callback->priv = priv;
+
+  callback->next = handle->parameterCallbacks;
+  if (callback->next)
+    callback->next->prev = callback;
+  callback->prev = NULL;
+  handle->parameterCallbacks = callback;
+
+  _brlapi_ntohParameter(parameter, &reply, rlen);
+  if (data) {
+    if (rlen < len) {
+      len = rlen;
+    }
+    memcpy(data, &reply.data, len);
+  } else {
+    func(parameter, subparam, flags, priv, &reply.data, rlen);
+  }
+  pthread_mutex_unlock(&handle->callbacks_mutex);
+
+  return callback;
+}
+
+brlapi_paramCallbackDescriptor_t BRLAPI_STDCALL brlapi_watchParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramCallback_t func, void *priv, void* data, size_t len)
+{
+  return brlapi__watchParameter(&defaultHandle, parameter, subparam, flags, func, priv, data, len);
+}
+
+/* Function: brlapi_unwatchParameter */
+int BRLAPI_STDCALL brlapi__unwatchParameter(brlapi_handle_t *handle, brlapi_paramCallbackDescriptor_t descriptor)
+{
+  brlapi_paramValuePacket_t reply;
+  ssize_t rlen;
+  struct brlapi_parameterCallback_t *callback = descriptor;
+
+  pthread_mutex_lock(&handle->callbacks_mutex);
+  rlen = _brlapi__getParameter(handle, callback->parameter, callback->subparam, callback->flags | BRLAPI_PARAMF_UNSUBSCRIBE, &reply);
+  if (rlen < 0) {
+    pthread_mutex_unlock(&handle->callbacks_mutex);
+    return -1;
+  }
+
+  if (handle->nextCallback == callback) {
+    /* We might be removing an entry which brlapi__doWaitForPacket was planning
+     * to have a look at next, make it skip it. */
+    handle->nextCallback = handle->nextCallback->next;
+  }
+
+  if (callback->next) {
+    callback->next->prev = callback->prev;
+  }
+  if (callback->prev) {
+    callback->prev->next = callback->next;
+  } else {
+    handle->parameterCallbacks = callback->next;
+  }
+  pthread_mutex_unlock(&handle->callbacks_mutex);
+  free(callback);
+  return 0;
+}
+
+int BRLAPI_STDCALL brlapi_unwatchParameter(brlapi_paramCallbackDescriptor_t descriptor)
+{
+  return brlapi__unwatchParameter(&defaultHandle, descriptor);
+}
+
+
+/* Function : getControllingTty */
+/* Returns the number of the caller's controlling terminal */
+/* -1 if error or unknown */
+static int getControllingTty(void)
+{
+  int tty;
+  const char *env;
+
+  /*if ((env = getenv("WINDOW")) && sscanf(env, "%u", &tty) == 1) return tty;*/
+  if ((env = getenv("WINDOWID")) && sscanf(env, "%u", &tty) == 1) return tty;
+  if ((env = getenv("CONTROLVT")) && sscanf(env, "%u", &tty) == 1) return tty;
+  if (!getenv("DISPLAY") && !getenv("WAYLAND_DISPLAY"))
+    if ((env = getenv("XDG_VTNR")) && sscanf(env, "%u", &tty) == 1) return tty;
+
+#ifdef WINDOWS
+  if (CHECKGETPROC("kernel32.dll", GetConsoleWindow))
+    /* really good guess */
+    if ((tty = (LONG_PTR) GetConsoleWindowProc())) return tty;
+  if ((tty = (LONG_PTR) GetActiveWindow()) || (tty = (LONG_PTR) GetFocus())) {
+    /* good guess, but need to get back up to parent window */
+    HWND root = GetDesktopWindow();
+    HWND tmp = (HWND) (LONG_PTR) tty;
+    while (1) {
+      tmp = GetParent(tmp);
+      if (!tmp || tmp == root) return tty;
+      tty = (LONG_PTR) tmp;
+    }
+  }
+  /* poor guess: assumes that focus is here */
+  if ((tty = (LONG_PTR) GetForegroundWindow())) return tty;
+#endif /* WINDOWS */
+
+#ifdef linux
+  do {
+    int fd = open("/proc/self/stat", O_RDONLY, 0);
+    if (fd < 0) {
+      break;
+    }
+
+    char buf[256];
+    ssize_t read_bytes = read(fd, buf, sizeof(buf) - 1);
+    close(fd);
+    if (read_bytes < 0) {
+      break;
+    }
+    /* Append null at the end of buffer to ensure that sscanf doesn't
+     * overflows.*/
+    buf[read_bytes] = '\0';
+
+    /* The line format is "$pid ($comm) $state ...
+     * where comm may contain space and `)` as part of its name, ex:
+     * 12345 (some proc (foo)) R ...
+     * So to process valid identified after comm, we should skip comm by
+     * getting the 1st `)` from end.
+     */
+    char *ptr = buf + read_bytes - 1;
+    while (ptr > buf && *ptr != ')') {
+      ptr -= 1;
+    }
+    if (*ptr != ')') {
+      break;
+    }
+    /* Point to space after ($comm). */
+    ptr += 1;
+
+    int vt;
+    int ok = sscanf(ptr, " %*c %*d %*d %*d %d", &tty) == 1;
+
+    if (ok && (major(tty) == TTY_MAJOR)) {
+      vt = minor(tty);
+      if ((vt >= 1) && (vt <= MAXIMUM_VIRTUAL_CONSOLE)) return vt;
+    }
+  } while (0);
+#endif /* linux */
+
+  return -1;
+}
+
+/* Function : brlapi_enterTtyMode */
+/* Takes control of a tty */
+int BRLAPI_STDCALL brlapi__enterTtyMode(brlapi_handle_t *handle, int tty, const char *driverName)
+{
+  /* Determine which tty to take control of */
+  if (tty<0) tty = getControllingTty();
+  /* 0 can be a valid screen WINDOW
+  0xffffffff can not be a valid WINDOWID (top 3 bits guaranteed to be zero) */
+  if (tty<0) { brlapi_errno=BRLAPI_ERROR_UNKNOWNTTY; return -1; }
+
+  if (brlapi__enterTtyModeWithPath(handle, &tty, 1, driverName)) return -1;
+
+  return tty;
+}
+
+int BRLAPI_STDCALL brlapi_enterTtyMode(int tty, const char *how)
+{
+  return brlapi__enterTtyMode(&defaultHandle, tty, how);
+}
+
+/* Function : brlapi_enterTtyModeWithPath */
+/* Takes control of a tty path */
+int BRLAPI_STDCALL brlapi__enterTtyModeWithPath(brlapi_handle_t *handle, const int *ttys, int nttys, const char *driverName)
+{
+  int res;
+  void *packet;
+  unsigned char *p;
+  uint32_t *nbTtys, *t;
+  char *ttytreepath,*cur,*next;
+  int ttypath;
+  unsigned int n, nttytreepath;
+  size_t size;
+
+  pthread_mutex_lock(&handle->state_mutex);
+  if ((handle->state & STCONTROLLINGTTY)) {
+    pthread_mutex_unlock(&handle->state_mutex);
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    return -1;
+  }
+
+  if (brlapi__getDisplaySize(handle, &handle->brlx, &handle->brly)<0){
+    pthread_mutex_unlock(&handle->state_mutex);
+    return -1;
+  }
+
+  /* Clear key buffer before taking the tty, just in case... */
+  pthread_mutex_lock(&handle->read_mutex);
+  handle->keybuf_next = handle->keybuf_nb = 0;
+  pthread_mutex_unlock(&handle->read_mutex);
+
+  /* OK, Now we know where we are, so get the effective control of the terminal! */
+  ttytreepath = getenv("WINDOWPATH");
+  if (!ttytreepath && (getenv("DISPLAY") || getenv("WAYLAND_DISPLAY"))) {
+    /* Cope with some DMs which are not setting WINDOWPATH (e.g. gdm 3.12) */
+    ttytreepath = getenv("XDG_VTNR");
+
+#ifdef HAVE_SD_SESSION_GET_VT
+    if (!ttytreepath) {
+      /* Fallback to asking logind */
+      char *id;
+      unsigned vtnr = 0;
+      int ret;
+
+      ret = sd_pid_get_session(0, &id);
+      if (ret == 0 && id != NULL) {
+	sd_session_get_vt(id, &vtnr);
+	free(id);
+      } else {
+	char **sessions;
+	/* Not even logind knows :/ we are probably logged in from gdm */
+	int nsessions = sd_uid_get_sessions(getuid(), 0, &sessions);
+
+	if (nsessions > 0) {
+	  int i, chosen = -1;
+
+	  for (i = 0; i < nsessions; i++) {
+	    char *type;
+
+	    ret = sd_session_get_type(sessions[i], &type);
+	    if (ret == 0) {
+	      if (strcmp(type, "tty") != 0 &&
+	          strcmp(type, "unspecified") != 0) {
+		/* x11, wayland or mir, so graphical.
+		 * User normally has only one of them */
+		if (chosen >= 0) {
+		  /* Oops, several sessions? That is not supposed to happen, better
+		   * choose none of them. */
+		  chosen = -1;
+		  break;
+		}
+		chosen = i;
+	      }
+	    }
+	  }
+
+	  if (chosen >= 0) sd_session_get_vt(sessions[i], &vtnr);
+
+	  for (i = 0; i < nsessions; i++) free(sessions[i]);
+	  free(sessions);
+	}
+      }
+
+      if (vtnr) {
+	size_t size = sizeof(vtnr)*3 + 1;
+	ttytreepath=alloca(size);
+	snprintf(ttytreepath, size, "%u", vtnr);
+      }
+    }
+#endif /* HAVE_SD_SESSION_GET_VT */
+  }
+
+  nttytreepath = 0;
+  if (ttytreepath) {
+    /* Count ttys from path */
+    for(cur = ttytreepath; *cur; nttytreepath++, cur = next+1) {
+      strtol(cur,&next,0);
+      if (next==cur) {
+        syslog(LOG_WARNING, "Erroneous window path %s", ttytreepath);
+        pthread_mutex_unlock(&handle->state_mutex);
+        brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+        return -1;
+      }
+      if (*next==0) { nttytreepath++; break; }
+    }
+  }
+
+  size = (1 + nttytreepath + nttys) * sizeof(uint32_t);
+  size += 1 + (driverName ? strlen(driverName) : 0);
+  packet = alloca(size);
+  nbTtys = packet;
+  t = nbTtys+1;
+  *nbTtys = htonl(nttytreepath + nttys);
+
+  if (ttytreepath) {
+    /* First add ttys from environment path */
+    for(cur=ttytreepath; *cur; nttytreepath++, cur=next+1) {
+      ttypath=strtol(cur,&next,0);
+      *t++ = htonl(ttypath);
+      if (*next==0) break;
+    }
+  }
+
+  /* Then add ttys from caller path */
+  for (n=0; n<nttys; n++) *t++ = htonl(ttys[n]);
+
+  p = (unsigned char *) t;
+  if (driverName==NULL) n = 0; else n = strlen(driverName);
+  if (n>BRLAPI_MAXNAMELENGTH) {
+    pthread_mutex_unlock(&handle->state_mutex);
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+  *p = n;
+  p++;
+  if (n) p = mempcpy(p, driverName, n);
+
+  assert(p-(unsigned char *)packet == size);
+  if ((res=brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_ENTERTTYMODE,packet,size)) == 0) {
+    handle->state |= STCONTROLLINGTTY;
+  }
+
+  pthread_mutex_unlock(&handle->state_mutex);
+
+  /* Determine default charset if application did not call setlocale.  */
+#ifdef LC_GLOBAL_LOCALE
+  const char *locale = setlocale(LC_CTYPE, NULL);
+
+  if (!locale || !strcmp(locale, "C")) {
+    /* Application did not call setlocale, try to load the current locale.  */
+    locale_t default_locale = newlocale(LC_CTYPE_MASK, "", 0);
+    if (default_locale) handle->default_locale = default_locale;
+  }
+#endif /* LC_GLOBAL_LOCALE */
+
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_enterTtyModeWithPath(const int *ttys, int nttys, const char *how)
+{
+  return brlapi__enterTtyModeWithPath(&defaultHandle, ttys, nttys, how);
+}
+
+/* Function : brlapi_leaveTtyMode */
+/* Gives back control of our tty to brltty */
+int BRLAPI_STDCALL brlapi__leaveTtyMode(brlapi_handle_t *handle)
+{
+  int res;
+  pthread_mutex_lock(&handle->state_mutex);
+  if (!(handle->state & STCONTROLLINGTTY)) {
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    res = -1;
+    goto out;
+  }
+  handle->brlx = 0; handle->brly = 0;
+  res = brlapi__writePacketWaitForAck(handle,BRLAPI_PACKET_LEAVETTYMODE,NULL,0);
+  handle->state &= ~STCONTROLLINGTTY;
+out:
+  pthread_mutex_unlock(&handle->state_mutex);
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_leaveTtyMode(void)
+{
+  return brlapi__leaveTtyMode(&defaultHandle);
+}
+
+/* Function : brlapi_setFocus */
+/* sends the current focus to brltty */
+int BRLAPI_STDCALL brlapi__setFocus(brlapi_handle_t *handle, int tty)
+{
+  uint32_t utty;
+  int res;
+  utty = htonl(tty);
+  pthread_mutex_lock(&handle->fileDescriptor_mutex);
+  res = brlapi_writePacket(handle->fileDescriptor, BRLAPI_PACKET_SETFOCUS, &utty, sizeof(utty));
+  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+  return res;
+}
+
+int BRLAPI_STDCALL brlapi_setFocus(int tty)
+{
+  return brlapi__setFocus(&defaultHandle, tty);
+}
+
+static size_t getCharset(brlapi_handle_t *handle, void *buffer, int wide) {
+  char *p = buffer;
+  const char *start = p;
+  const char *locale = setlocale(LC_CTYPE, NULL);
+
+  if (wide
+#ifdef __MINGW32__
+      && CHECKPROC("ntdll.dll", wcslen)
+#endif /* __MINGW32__ */
+  ) {
+    size_t length = strlen(WCHAR_CHARSET);
+    *p++ = length;
+    p = mempcpy(p, WCHAR_CHARSET, length);
+  } else if ((locale && strcmp(locale, "C"))
+#ifdef LC_GLOBAL_LOCALE
+             || (handle->default_locale != LC_GLOBAL_LOCALE)
+#endif /* LC_GLOBAL_LOCALE */
+            ) {
+    /* not default locale, tell charset to server */
+#ifdef WINDOWS
+    UINT CP = GetACP();
+    if (!CP) CP = GetOEMCP();
+
+    if (CP) {
+      size_t length = sprintf(p+1, "CP%u", CP);
+      *p++ = length;
+      p += length;
+    }
+#elif defined(HAVE_NL_LANGINFO)
+    char *language = nl_langinfo(CODESET);
+    size_t length = strlen(language);
+
+    *p++ = length;
+    p = mempcpy(p, language, length);
+#endif /* get non-wide character set */
+  }
+
+  return p-start;
+}
+
+/* Function : brlapi_writeText */
+/* Writes a string to the braille display */
+static int brlapi___writeText(brlapi_handle_t *handle, int cursor, const void *str, int wide)
+{
+  int dispSize = handle->brlx * handle->brly;
+  brlapi_packet_t packet;
+  brlapi_writeArgumentsPacket_t *wa = &packet.writeArguments;
+  unsigned char *p = &wa->data;
+  int res;
+  size_t len;
+
+#ifdef LC_GLOBAL_LOCALE
+  locale_t old_locale = 0;
+
+  if (handle->default_locale != LC_GLOBAL_LOCALE) {
+    /* Temporarily load the default locale.  */
+    old_locale = uselocale(handle->default_locale);
+  }
+
+#else /* LC_GLOBAL_LOCALE */
+  setlocale(LC_CTYPE,NULL);
+#endif /* LC_GLOBAL_LOCALE */
+
+  wa->flags = BRLAPI_WF_REGION;
+  *((uint32_t *) p) = htonl(1); p += sizeof(uint32_t);
+  *((uint32_t *) p) = htonl(-dispSize); p += sizeof(uint32_t);
+
+  if (str) {
+    wa->flags |= BRLAPI_WF_TEXT;
+#if defined(__MINGW32__)
+    if (CHECKGETPROC("ntdll.dll", wcslen) && wide)
+      len = sizeof(wchar_t) * wcslenProc(str);
+#else /* __MINGW32__ */
+    if (wide)
+      len = sizeof(wchar_t) * wcslen(str);
+#endif /* __MINGW32__ */
+    else
+      len = strlen(str);
+    *((uint32_t *) p) = htonl(len); p += sizeof(uint32_t);
+    memcpy(p, str, len);
+    p += len;
+  }
+  if (cursor!=BRLAPI_CURSOR_LEAVE) {
+    wa->flags |= BRLAPI_WF_CURSOR;
+    *((uint32_t *) p) = htonl(cursor);
+    p += sizeof(uint32_t);
+  }
+
+  if ((len = getCharset(handle, p , wide))) {
+    wa->flags |= BRLAPI_WF_CHARSET;
+    p += len;
+  }
+
+  wa->flags = htonl(wa->flags);
+  pthread_mutex_lock(&handle->fileDescriptor_mutex);
+  res = brlapi_writePacket(handle->fileDescriptor,BRLAPI_PACKET_WRITE,&packet,sizeof(wa->flags)+(p-&wa->data));
+  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+
+#ifdef LC_GLOBAL_LOCALE
+  if (handle->default_locale != LC_GLOBAL_LOCALE) {
+    /* Restore application locale */
+    uselocale(old_locale);
+  }
+#endif /* LC_GLOBAL_LOCALE */
+
+  return res;
+}
+
+#ifdef WINDOWS
+int BRLAPI_STDCALL brlapi__writeTextWin(brlapi_handle_t *handle, int cursor, const void *str, int wide)
+{
+  return brlapi___writeText(handle, cursor, str, wide);
+}
+
+int BRLAPI_STDCALL brlapi_writeTextWin(int cursor, const void *str, int wide)
+{
+  return brlapi___writeText(&defaultHandle, cursor, str, wide);
+}
+#else /* WINDOWS */
+int BRLAPI_STDCALL brlapi__writeText(brlapi_handle_t *handle, int cursor, const char *str)
+{
+  return brlapi___writeText(handle, cursor, str, 0);
+}
+
+int BRLAPI_STDCALL brlapi__writeWText(brlapi_handle_t *handle, int cursor, const wchar_t *str)
+{
+  return brlapi___writeText(handle, cursor, str, 1);
+}
+
+int BRLAPI_STDCALL brlapi_writeText(int cursor, const char *str)
+{
+  return brlapi__writeText(&defaultHandle, cursor, str);
+}
+
+int BRLAPI_STDCALL brlapi_writeWText(int cursor, const wchar_t *str)
+{
+  return brlapi__writeWText(&defaultHandle, cursor, str);
+}
+#endif /* WINDOWS */
+
+/* Function : brlapi_writeDots */
+/* Writes dot-matrix to the braille display */
+int BRLAPI_STDCALL brlapi__writeDots(brlapi_handle_t *handle, const unsigned char *dots)
+{
+  brlapi_writeArguments_t wa = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+  unsigned int size = handle->brlx * handle->brly;
+
+  if (size == 0) {
+    brlapi_errno=BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+
+  unsigned char andMask[size];
+  memset(andMask, 0, size);
+  wa.andMask = andMask;
+
+  unsigned char orMask[size];
+  memcpy(orMask, dots, size);
+  wa.orMask = orMask;
+
+  char text[(size * 3) + 1];
+  wa.text = text;
+  wa.charset = "utf-8";
+
+  {
+    /* Pass a UTF8-encoded string of the braille characters as the text.
+     *
+     * The Unicode row for the braille pattern characters is U+2800.
+     * Each of the eight dots is represented by a bit in the low-order byte:
+     * Dot1 by 0X01, Dot2 by 0X02, ..., Dot7 by 0X40, and Dot8 by 0X80.
+     *
+     * The UTF-8 template for the Unicode braille row is: 0XE2, 0XA0, 0X80.
+     * Dots 1-6 are the low-order six bits of the last (0X80) byte.
+     * Dots 7-8 are the low-order two bits of the middle (0XA0) byte.
+     */
+
+    char *byte = text;
+    const unsigned char *cell = dots;
+    const unsigned char *end = cell + size;
+
+    while (cell < end) {
+      *byte++ = 0XE2;
+      *byte++ = 0XA0 | ((*cell >> 6) & 0X3); // dots 7-8
+      *byte++ = 0X80 | (*cell & 0X3F); // dots 1-6
+      cell += 1;
+    }
+
+    *byte = 0;
+    wa.textSize = byte - text;
+  }
+
+  wa.regionBegin = 1;
+  wa.regionSize = -size;
+  wa.cursor = BRLAPI_CURSOR_OFF;
+
+  return brlapi__write(handle, &wa);
+}
+
+int BRLAPI_STDCALL brlapi_writeDots(const unsigned char *dots)
+{
+  return brlapi__writeDots(&defaultHandle, dots);
+}
+
+/* Function : brlapi_write */
+/* Extended writes on braille displays */
+#ifdef WINDOWS
+int BRLAPI_STDCALL brlapi__writeWin(brlapi_handle_t *handle, const brlapi_writeArguments_t *s, int wide)
+#else /* WINDOWS */
+int brlapi__write(brlapi_handle_t *handle, const brlapi_writeArguments_t *s)
+#endif /* WINDOWS */
+{
+  int dispSize = handle->brlx * handle->brly;
+  unsigned int rbeg, strLen;
+  int rsiz;
+  brlapi_packet_t packet;
+  brlapi_writeArgumentsPacket_t *wa = &packet.writeArguments;
+  unsigned char *p = &wa->data;
+  unsigned char *end = (unsigned char*) &packet.data[sizeof(packet)];
+  int res;
+#ifndef WINDOWS
+  int wide = 0;
+#endif /* WINDOWS */
+  wa->flags = 0;
+  if (s==NULL) goto send;
+  rbeg = s->regionBegin;
+  rsiz = s->regionSize;
+  if (rbeg || rsiz) {
+    if (rsiz == 0) return 0;
+    wa->flags |= BRLAPI_WF_REGION;
+    *((uint32_t *) p) = htonl(rbeg); p += sizeof(uint32_t);
+    *((uint32_t *) p) = htonl((int32_t) rsiz); p += sizeof(uint32_t);
+  } else {
+    /* DEPRECATED */
+    rsiz = -dispSize;
+  }
+  if (rsiz < 0)
+    rsiz = -rsiz;
+  if (s->text) {
+    if (s->textSize != -1)
+      strLen = s->textSize;
+    else
+#if defined(__CYGWIN__)
+      if (wide)
+	strLen = sizeof(wchar_t) * wcslen((wchar_t *) s->text);
+      else
+#elif defined(__MINGW32__)
+      if (CHECKGETPROC("ntdll.dll", wcslen) && wide)
+	strLen = sizeof(wchar_t) * wcslenProc((wchar_t *) s->text);
+      else
+#endif /* windows wide string length */
+	strLen = strlen(s->text);
+    *((uint32_t *) p) = htonl(strLen); p += sizeof(uint32_t);
+    wa->flags |= BRLAPI_WF_TEXT;
+    if (p + strLen > end) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+    memcpy(p, s->text, strLen);
+    p += strLen;
+  }
+  if (s->andMask) {
+    wa->flags |= BRLAPI_WF_ATTR_AND;
+    if (p + rsiz > end) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+    memcpy(p, s->andMask, rsiz);
+    p += rsiz;
+  }
+  if (s->orMask) {
+    wa->flags |= BRLAPI_WF_ATTR_OR;
+    if (p + rsiz > end) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+    memcpy(p, s->orMask, rsiz);
+    p += rsiz;
+  }
+  if ((s->cursor>=0) && (s->cursor<=dispSize)) {
+    wa->flags |= BRLAPI_WF_CURSOR;
+    if (p + sizeof(uint32_t) > end) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+    *((uint32_t *) p) = htonl(s->cursor);
+    p += sizeof(uint32_t);
+  } else if (s->cursor != BRLAPI_CURSOR_LEAVE) {
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    return -1;
+  }
+
+  if (s->charset) {
+    if (!*s->charset) {
+#ifdef LC_GLOBAL_LOCALE
+      locale_t old_locale = 0;
+
+      if (handle->default_locale != LC_GLOBAL_LOCALE) {
+	/* Temporarily load the default locale.  */
+	old_locale = uselocale(handle->default_locale);
+      }
+#endif /* LC_GLOBAL_LOCALE */
+
+      if ((strLen = getCharset(handle, p, wide))) {
+	wa->flags |= BRLAPI_WF_CHARSET;
+	p += strLen;
+      }
+
+#ifdef LC_GLOBAL_LOCALE
+      if (handle->default_locale != LC_GLOBAL_LOCALE) {
+	/* Restore application locale */
+	uselocale(old_locale);
+      }
+#endif /* LC_GLOBAL_LOCALE */
+    } else {
+      strLen = strlen(s->charset);
+      *p++ = strLen;
+      wa->flags |= BRLAPI_WF_CHARSET;
+      if (p + strLen > end) {
+	brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+	return -1;
+      }
+
+      memcpy(p, s->charset, strLen);
+      p += strLen;
+    }
+  }
+
+send:
+  wa->flags = htonl(wa->flags);
+  pthread_mutex_lock(&handle->fileDescriptor_mutex);
+  res = brlapi_writePacket(handle->fileDescriptor,BRLAPI_PACKET_WRITE,&packet,sizeof(wa->flags)+(p-&wa->data));
+  pthread_mutex_unlock(&handle->fileDescriptor_mutex);
+  return res;
+}
+
+#ifdef WINDOWS
+int BRLAPI_STDCALL brlapi_writeWin(const brlapi_writeArguments_t *s, int wide)
+{
+  return brlapi__writeWin(&defaultHandle, s, wide);
+}
+#else /* WINDOWS */
+int brlapi_write(const brlapi_writeArguments_t *s)
+{
+  return brlapi__write(&defaultHandle, s);
+}
+#endif /* WINDOWS */
+
+/* Function : brlapi_readKey */
+/* Reads a key from the braille keyboard */
+int BRLAPI_STDCALL brlapi__readKeyWithTimeout(brlapi_handle_t *handle, int timeout_ms, brlapi_keyCode_t *code)
+{
+  ssize_t res;
+  uint32_t buf[2];
+
+  pthread_mutex_lock(&handle->state_mutex);
+  if (!(handle->state & STCONTROLLINGTTY)) {
+    pthread_mutex_unlock(&handle->state_mutex);
+    brlapi_errno = BRLAPI_ERROR_ILLEGAL_INSTRUCTION;
+    return -1;
+  }
+  pthread_mutex_unlock(&handle->state_mutex);
+
+  pthread_mutex_lock(&handle->read_mutex);
+  if (handle->keybuf_nb>0) {
+    *code=handle->keybuf[handle->keybuf_next];
+    handle->keybuf_next=(handle->keybuf_next+1)%BRL_KEYBUF_SIZE;
+    handle->keybuf_nb--;
+    pthread_mutex_unlock(&handle->read_mutex);
+    return 1;
+  }
+  pthread_mutex_unlock(&handle->read_mutex);
+
+  pthread_mutex_lock(&handle->key_mutex);
+  res = brlapi__waitForPacket(handle,BRLAPI_PACKET_KEY, buf, sizeof(buf), TRY_WAIT_FOR_EXPECTED_PACKET, timeout_ms);
+  pthread_mutex_unlock(&handle->key_mutex);
+  if (res == -3) {
+    if (timeout_ms == 0) return 0;
+    brlapi_libcerrno = EINTR;
+    brlapi_errno = BRLAPI_ERROR_LIBCERR;
+    brlapi_errfun = "waitForPacket";
+    return -1;
+  }
+  if (res == -4) {
+    /* Timeout */
+    return 0;
+  }
+  if (res < 0) return -1;
+  *code = brlapi_packetToKeyCode(buf);
+  return 1;
+}
+
+int BRLAPI_STDCALL brlapi_readKeyWithTimeout(int timeout_ms, brlapi_keyCode_t *code)
+{
+  return brlapi__readKeyWithTimeout(&defaultHandle, timeout_ms, code);
+}
+
+int BRLAPI_STDCALL brlapi__readKey(brlapi_handle_t *handle, int block, brlapi_keyCode_t *code)
+{
+  return brlapi__readKeyWithTimeout(handle, block ? -1 : 0, code);
+}
+
+int BRLAPI_STDCALL brlapi_readKey(int block, brlapi_keyCode_t *code)
+{
+  return brlapi__readKeyWithTimeout(&defaultHandle, block ? -1 : 0, code);
+}
+
+typedef struct {
+  brlapi_keyCode_t code;
+  const char *name;
+} brlapi_keyEntry_t;
+
+static const brlapi_keyEntry_t brlapi_keyTable[] = {
+#include "brlapi_keytab.auto.h"
+
+  {.code=0X0000U, .name="LATIN1"},
+  {.code=0X0100U, .name="LATIN2"},
+  {.code=0X0200U, .name="LATIN3"},
+  {.code=0X0300U, .name="LATIN4"},
+  {.code=0X0400U, .name="KATAKANA"},
+  {.code=0X0500U, .name="ARABIC"},
+  {.code=0X0600U, .name="CYRILLIC"},
+  {.code=0X0700U, .name="GREEK"},
+  {.code=0X0800U, .name="TECHNICAL"},
+  {.code=0X0900U, .name="SPECIAL"},
+  {.code=0X0A00U, .name="PUBLISHING"},
+  {.code=0X0B00U, .name="APL"},
+  {.code=0X0C00U, .name="HEBREW"},
+  {.code=0X0D00U, .name="THAI"},
+  {.code=0X0E00U, .name="KOREAN"},
+  {.code=0X1200U, .name="LATIN8"},
+  {.code=0X1300U, .name="LATIN9"},
+  {.code=0X1400U, .name="ARMENIAN"},
+  {.code=0X1500U, .name="GEORGIAN"},
+  {.code=0X1600U, .name="CAUCASUS"},
+  {.code=0X1E00U, .name="VIETNAMESE"},
+  {.code=0X2000U, .name="CURRENCY"},
+  {.code=0XFD00U, .name="3270"},
+  {.code=0XFE00U, .name="XKB"},
+  {.code=0XFF00U, .name="MISCELLANY"},
+  {.code=0X01000000U, .name="UNICODE"},
+
+  {.name=NULL}
+};
+
+int BRLAPI_STDCALL
+brlapi_expandKeyCode (brlapi_keyCode_t keyCode, brlapi_expandedKeyCode_t *ekc) {
+  int argumentWidth = brlapi_getArgumentWidth(keyCode);
+
+  if (argumentWidth != -1) {
+    unsigned int argumentMask = (1 << argumentWidth) - 1;
+    brlapi_keyCode_t type = keyCode & BRLAPI_KEY_TYPE_MASK;
+    brlapi_keyCode_t code = keyCode & BRLAPI_KEY_CODE_MASK;
+
+    ekc->type = type;
+    ekc->command = (code & ~argumentMask);
+    ekc->argument = code & argumentMask;
+    ekc->flags = (keyCode & BRLAPI_KEY_FLAGS_MASK) >> BRLAPI_KEY_FLAGS_SHIFT;
+
+    return 0;
+  }
+
+  return -1;
+}
+
+int BRLAPI_STDCALL
+brlapi_describeKeyCode (brlapi_keyCode_t keyCode, brlapi_describedKeyCode_t *dkc) {
+  brlapi_expandedKeyCode_t ekc;
+  int result = brlapi_expandKeyCode(keyCode, &ekc);
+
+  if (result != -1) {
+    unsigned int argument = ekc.argument;
+    unsigned int codeWithoutArgument = ekc.type | ekc.command;
+    unsigned int codeWithArgument = codeWithoutArgument | argument;
+    const brlapi_keyEntry_t *keyWithoutArgument = NULL;
+    const brlapi_keyEntry_t *key = brlapi_keyTable;
+
+    while (key->name) {
+      if (codeWithArgument == key->code) {
+        argument = 0;
+        goto found;
+      }
+
+      if (codeWithoutArgument == key->code)
+        if (!keyWithoutArgument)
+          keyWithoutArgument = key;
+
+      ++key;
+    }
+
+    if (keyWithoutArgument) {
+      key = keyWithoutArgument;
+      goto found;
+    }
+
+    brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+    result = -1;
+    goto done;
+
+  found:
+    dkc->command = key->name;
+    dkc->argument = argument;
+    dkc->values = ekc;
+
+    switch (ekc.type) {
+      case BRLAPI_KEY_TYPE_SYM: dkc->type = "SYM";     break;
+      case BRLAPI_KEY_TYPE_CMD: dkc->type = "CMD";     break;
+      default:                  dkc->type = "UNKNOWN"; break;
+    }
+
+#define FLAG(name) if (keyCode & BRLAPI_KEY_FLG_##name) dkc->flag[dkc->flags++] = #name
+    dkc->flags = 0;
+
+    FLAG(SHIFT);
+    FLAG(UPPER);
+    FLAG(CONTROL);
+    FLAG(META);
+    FLAG(ALTGR);
+    FLAG(GUI);
+
+    switch (ekc.type) {
+      case BRLAPI_KEY_TYPE_CMD:
+        switch (ekc.command & BRLAPI_KEY_CMD_BLK_MASK) {
+          case BRLAPI_KEY_CMD_PASSDOTS:
+            break;
+
+          case BRLAPI_KEY_CMD_PASSXT:
+          case BRLAPI_KEY_CMD_PASSAT:
+          case BRLAPI_KEY_CMD_PASSPS2:
+            FLAG(KBD_RELEASE);
+            FLAG(KBD_EMUL0);
+            FLAG(KBD_EMUL1);
+            break;
+
+          default:
+            FLAG(TOGGLE_ON);
+            FLAG(TOGGLE_OFF);
+            FLAG(MOTION_ROUTE);
+            FLAG(MOTION_SCALED);
+            FLAG(MOTION_TOLEFT);
+            break;
+        }
+        break;
+    }
+#undef FLAG
+  }
+
+done:
+  return result;
+}
+
+/* Function : ignore_accept_key_range */
+/* Common tasks for ignoring and unignoring key ranges */
+/* what = 0 for ignoring !0 for unignoring */
+static int ignore_accept_key_ranges(brlapi_handle_t *handle, int what, const brlapi_range_t ranges[], unsigned int n)
+{
+  uint32_t ints[n][4];
+  unsigned int i;
+
+  for (i=0; i<n; i++) {
+    ints[i][0] = htonl(ranges[i].first >> 32);
+    ints[i][1] = htonl(ranges[i].first & 0xffffffff);
+    ints[i][2] = htonl(ranges[i].last >> 32);
+    ints[i][3] = htonl(ranges[i].last & 0xffffffff);
+  };
+
+  if (brlapi__writePacketWaitForAck(handle,(what ? BRLAPI_PACKET_ACCEPTKEYRANGES : BRLAPI_PACKET_IGNOREKEYRANGES),ints,n*2*sizeof(brlapi_keyCode_t)))
+    return -1;
+  return 0;
+}
+
+/* Function : ignore_accept_keys */
+/* Common tasks for ignoring and unignoring keys */
+/* what = 0 for ignoring !0 for unignoring */
+static int ignore_accept_keys(brlapi_handle_t *handle, int what, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
+{
+  if (!n) {
+    if (r != brlapi_rangeType_all) {
+      brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+      return -1;
+    }
+    brlapi_range_t range = { .first = 0, .last = BRLAPI_KEY_MAX };
+    return ignore_accept_key_ranges(handle, what, &range, 1);
+  } else {
+    brlapi_range_t ranges[n];
+    unsigned int i;
+    brlapi_keyCode_t mask;
+
+    for (i=0; i<n; i++) {
+      if (brlapi_getKeyrangeMask(r, code[i], &mask))
+	return -1;
+      if (code[i] & mask) {
+	brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+	return -1;
+      }
+      ranges[i].first = code[i];
+      ranges[i].last = code[i] | mask;
+    }
+    return ignore_accept_key_ranges(handle, what, ranges, n);
+  }
+}
+
+/* Function : brlapi_acceptKeyRanges */
+int BRLAPI_STDCALL brlapi__acceptKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int n)
+{
+  return ignore_accept_key_ranges(handle, !0, ranges, n);
+}
+
+int BRLAPI_STDCALL brlapi_acceptKeyRanges(const brlapi_range_t ranges[], unsigned int n)
+{
+  return brlapi__acceptKeyRanges(&defaultHandle, ranges, n);
+}
+
+/* Function : brlapi_acceptKeys */
+int BRLAPI_STDCALL brlapi__acceptKeys(brlapi_handle_t *handle, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
+{
+  return ignore_accept_keys(handle, !0, r, code, n);
+}
+
+int BRLAPI_STDCALL brlapi_acceptKeys(brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
+{
+  return brlapi__acceptKeys(&defaultHandle, r, code, n);
+}
+
+/* Function : brlapi_ignoreKeyRanges */
+int BRLAPI_STDCALL brlapi__ignoreKeyRanges(brlapi_handle_t *handle, const brlapi_range_t ranges[], unsigned int n)
+{
+  return ignore_accept_key_ranges(handle, 0, ranges, n);
+}
+
+int BRLAPI_STDCALL brlapi_ignoreKeyRanges(const brlapi_range_t ranges[], unsigned int n)
+{
+  return brlapi__ignoreKeyRanges(&defaultHandle, ranges, n);
+}
+
+/* Function : brlapi_ignoreKeys */
+int BRLAPI_STDCALL brlapi__ignoreKeys(brlapi_handle_t *handle, brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
+{
+  return ignore_accept_keys(handle, 0, r, code, n);
+}
+
+int BRLAPI_STDCALL brlapi_ignoreKeys(brlapi_rangeType_t r, const brlapi_keyCode_t *code, unsigned int n)
+{
+  return brlapi__ignoreKeys(&defaultHandle, r, code, n);
+}
+
+/* Error code handling */
+
+/* brlapi_errlist: error messages */
+const char *brlapi_errlist[] = {
+  [0]                                = "Success",
+  [BRLAPI_ERROR_NOMEM]               = "Insufficient memory",
+  [BRLAPI_ERROR_TTYBUSY]             = "Tty is busy",
+  [BRLAPI_ERROR_DEVICEBUSY]          = "Device is busy",
+  [BRLAPI_ERROR_UNKNOWN_INSTRUCTION] = "Unknown instruction",
+  [BRLAPI_ERROR_ILLEGAL_INSTRUCTION] = "Illegal instruction",
+  [BRLAPI_ERROR_INVALID_PARAMETER]   = "Invalid parameter",
+  [BRLAPI_ERROR_INVALID_PACKET]      = "Invalid packet",
+  [BRLAPI_ERROR_CONNREFUSED]         = "Connection refused",
+  [BRLAPI_ERROR_OPNOTSUPP]           = "Operation not supported",
+  [BRLAPI_ERROR_GAIERR]              = "getaddrinfo() error",
+  [BRLAPI_ERROR_LIBCERR]             = "libc error",
+  [BRLAPI_ERROR_UNKNOWNTTY]          = "Can't determine tty number",
+  [BRLAPI_ERROR_PROTOCOL_VERSION]    = "Bad protocol version",
+  [BRLAPI_ERROR_EOF]                 = "Unexpected end of file",
+  [BRLAPI_ERROR_EMPTYKEY]            = "Key file is empty",
+  [BRLAPI_ERROR_DRIVERERROR]         = "Driver error",
+  [BRLAPI_ERROR_AUTHENTICATION]      = "Authentication failed",
+  [BRLAPI_ERROR_READONLY_PARAMETER]  = "Parameter can not be changed",
+};
+
+/* brlapi_nerr: number of error codes */
+const int brlapi_nerr = (sizeof(brlapi_errlist)/sizeof(char*));
+
+/* _brlapi_strerror_cpy: store constant-string error message */
+static size_t _brlapi_strerror_cpy(char *buf, size_t buflen, const char *msg)
+{
+  size_t totsize = strlen(msg);
+
+  if (buflen == 0)
+    return totsize;
+
+  if (buflen > totsize+1) {
+    buflen = totsize+1;
+  }
+  memcpy(buf, msg, buflen-1);
+  buf[buflen-1] = '\0';
+
+  return totsize;
+}
+
+/* _brlapi_strerror_snprintf: store formatted error message */
+static size_t _brlapi_strerror_snprintf(char *buf, size_t buflen, const char *fmt, ...)
+{
+  va_list va;
+  int totsize;
+
+  va_start(va, fmt);
+  totsize = vsnprintf(buf, buflen, fmt, va);
+  va_end(va);
+
+  return totsize;
+}
+
+/* brlapi_strerror_r: store error message */
+size_t BRLAPI_STDCALL brlapi_strerror_r(const brlapi_error_t *error, char *buf, size_t buflen)
+{
+  if (error->brlerrno>=brlapi_nerr)
+    return _brlapi_strerror_cpy(buf, buflen, "Unknown error");
+  else if (error->brlerrno==BRLAPI_ERROR_GAIERR) {
+#if defined(EAI_SYSTEM)
+    if (error->gaierrno == EAI_SYSTEM)
+      return _brlapi_strerror_snprintf(buf,buflen,"resolve: %s", strerror(error->libcerrno));
+    else
+#endif /* EAI_SYSTEM */
+      return _brlapi_strerror_snprintf(buf,buflen,"resolve: "
+#if defined(HAVE_GETADDRINFO)
+#if defined(HAVE_GAI_STRERROR)
+	"%s", gai_strerror(error->gaierrno)
+#else
+	"%d", error->gaierrno
+#endif
+#elif defined(HAVE_HSTRERROR) && !defined(__MINGW32__)
+	"%s", hstrerror(error->gaierrno)
+#else
+	"%x", error->gaierrno
+#endif
+	);
+  }
+  else if (error->brlerrno==BRLAPI_ERROR_LIBCERR) {
+    return _brlapi_strerror_snprintf(buf,buflen,"%s: %s", error->errfun?error->errfun:"(null)", strerror(error->libcerrno));
+  } else
+    return _brlapi_strerror_cpy(buf, buflen, brlapi_errlist[error->brlerrno]);
+}
+
+/* brlapi_strerror: return error message */
+const char * BRLAPI_STDCALL brlapi_strerror(const brlapi_error_t *error)
+{
+  static char errmsg[128];
+  brlapi_strerror_r(error, errmsg, sizeof(errmsg));
+  return errmsg;
+}
+
+/* brlapi_perror: error message printing */
+void BRLAPI_STDCALL brlapi_perror(const char *s)
+{
+  fprintf(stderr,"%s: %s\n",s,brlapi_strerror(&brlapi_error));
+}
+
+/* XXX functions mustn't use brlapi_errno after this since it #undefs it XXX */
+
+#ifdef brlapi_error
+#undef brlapi_error
+#endif /* brlapi_error */
+
+brlapi_error_t brlapi_error;
+static int pthread_error_ok;
+
+/* we need a per-thread errno variable, thanks to pthread_keys */
+static pthread_key_t error_key;
+
+/* the key must be created at most once */
+static pthread_once_t error_key_once = PTHREAD_ONCE_INIT;
+
+static void error_key_free(void *key)
+{
+  free(key);
+}
+
+static void error_key_alloc(void)
+{
+  pthread_error_ok=!pthread_key_create(&error_key, error_key_free);
+}
+
+/* how to get per-thread errno variable. This will be called by the macro
+ * brlapi_errno */
+brlapi_error_t * BRLAPI_STDCALL brlapi_error_location(void)
+{
+  brlapi_error_t *errorp;
+#ifndef WINDOWS
+  if (pthread_once && pthread_key_create) {
+#endif /* WINDOWS */
+    pthread_once(&error_key_once, error_key_alloc);
+    if (pthread_error_ok) {
+      if ((errorp=(brlapi_error_t *) pthread_getspecific(error_key)))
+        /* normal case */
+        return errorp;
+      else
+        /* on the first time, must allocate it */
+        if ((errorp=malloc(sizeof(*errorp))) && !pthread_setspecific(error_key,errorp)) {
+	  memset(errorp,0,sizeof(*errorp));
+          return errorp;
+	}
+    }
+#ifndef WINDOWS
+  }
+#endif /* WINDOWS */
+  /* fall-back: shared errno :/ */
+  return &brlapi_error;
+}
+
+brlapi__exceptionHandler_t BRLAPI_STDCALL brlapi__setExceptionHandler(brlapi_handle_t *handle, brlapi__exceptionHandler_t new)
+{
+  brlapi__exceptionHandler_t tmp;
+  pthread_mutex_lock(&handle->exceptionHandler_mutex);
+  tmp = handle->exceptionHandler.withHandle;
+  if (new!=NULL) handle->exceptionHandler.withHandle = new;
+  pthread_mutex_unlock(&handle->exceptionHandler_mutex);
+  return tmp;
+}
+
+brlapi_exceptionHandler_t BRLAPI_STDCALL brlapi_setExceptionHandler(brlapi_exceptionHandler_t new)
+{
+  brlapi_exceptionHandler_t tmp;
+  pthread_mutex_lock(&defaultHandle.exceptionHandler_mutex);
+  tmp = defaultHandle.exceptionHandler.withoutHandle;
+  if (new!=NULL) defaultHandle.exceptionHandler.withoutHandle = new;
+  pthread_mutex_unlock(&defaultHandle.exceptionHandler_mutex);
+  return tmp;
+}
+
+int BRLAPI_STDCALL brlapi__strexception(brlapi_handle_t *handle, char *buf, size_t n, int err, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  int chars = 128; /* Number of bytes to dump */
+  char hexString[3*chars+1];
+  int i, nbChars = MIN(chars, size);
+  char *p = hexString;
+  brlapi_error_t error = { .brlerrno = err };
+  for (i=0; i<nbChars; i++)
+    p += sprintf(p, "%02x ", ((unsigned char *) packet)[i]);
+  p--; /* Don't keep last space */
+  *p = '\0';
+  return snprintf(buf, n, "%s on %s request of size %d (%s)",
+    brlapi_strerror(&error), brlapi_getPacketTypeName(type), (int)size, hexString);
+}
+
+int BRLAPI_STDCALL brlapi_strexception(char *buf, size_t n, int err, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  return brlapi__strexception(&defaultHandle, buf, n, err, type, packet, size);
+}
+
+void BRLAPI_STDCALL brlapi__defaultExceptionHandler(brlapi_handle_t *handle, int err, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  char str[0X100];
+  brlapi_strexception(str,0X100, err, type, packet, size);
+  fprintf(stderr, "BrlAPI exception: %s\nYou may wish to add the -ldebug option to the brltty command line in order to get additional information in the system log\n", str);
+  fprintf(stderr, "Crashing the client now. You may want to use brlapi_setExceptionHandler to define your own exception handling.\n");
+  abort();
+}
+
+void BRLAPI_STDCALL brlapi_defaultExceptionHandler(int err, brlapi_packetType_t type, const void *packet, size_t size)
+{
+  brlapi__defaultExceptionHandler(&defaultHandle, err, type, packet, size);
+}
diff --git a/Programs/brlapi_common.h b/Programs/brlapi_common.h
new file mode 100644
index 0000000..e02c389
--- /dev/null
+++ b/Programs/brlapi_common.h
@@ -0,0 +1,951 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* api_common.h - private definitions shared by both server & client */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef __MINGW32__
+#include <io.h>
+#else /* __MINGW32__ */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif /* __MINGW32__ */
+
+#include "brlapi_protocol.h"
+
+#if !defined(AF_LOCAL) && defined(AF_UNIX)
+#define AF_LOCAL AF_UNIX
+#endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */
+
+#if !defined(PF_LOCAL) && defined(PF_UNIX)
+#define PF_LOCAL PF_UNIX
+#endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b))? (a): (b))
+#endif /* MIN */
+
+#ifndef MAX
+#define MAX(a, b) (((a) > (b))? (a): (b))
+#endif /* MAX */
+
+#ifdef __MINGW32__
+#define get_osfhandle(fd) _get_osfhandle(fd)
+#endif /* __MINGW32__ */
+
+#define LibcError(function) \
+  brlapi_errno=BRLAPI_ERROR_LIBCERR; \
+  brlapi_libcerrno = errno; \
+  brlapi_errfun = function;
+
+/* brlapi_writeFile */
+/* Writes a buffer to a file */
+static ssize_t brlapi_writeFile(brlapi_fileDescriptor fd, const void *buffer, size_t size)
+{
+  const unsigned char *buf = buffer;
+  size_t n;
+#ifdef __MINGW32__
+  DWORD res=0;
+#else /* __MINGW32__ */
+  ssize_t res=0;
+#endif /* __MINGW32__ */
+  for (n=0;n<size;n+=res) {
+#ifdef __MINGW32__
+    OVERLAPPED overl = {0, 0, {{0, 0}}, CreateEvent(NULL, TRUE, FALSE, NULL)};
+    if ((!WriteFile(fd,buf+n,size-n,&res,&overl)
+      && GetLastError() != ERROR_IO_PENDING) ||
+      !GetOverlappedResult(fd, &overl, &res, TRUE)) {
+      res = GetLastError();
+      CloseHandle(overl.hEvent);
+      setErrno(res);
+      return -1;
+    }
+    CloseHandle(overl.hEvent);
+#else /* __MINGW32__ */
+    res=send(fd,buf+n,size-n,0);
+    if ((res<0) &&
+        (errno!=EINTR) &&
+#ifdef EWOULDBLOCK
+        (errno!=EWOULDBLOCK) &&
+#endif /* EWOULDBLOCK */
+        (errno!=EAGAIN)) { /* EAGAIN shouldn't happen, but who knows... */
+      return res;
+    }
+#endif /* __MINGW32__ */
+  }
+  return n;
+}
+
+/* brlapi_readFile */
+/* Reads a buffer from a file */
+static ssize_t brlapi_readFile(brlapi_fileDescriptor fd, void *buffer, size_t size, int loop)
+{
+  unsigned char *buf = buffer;
+  size_t n;
+#ifdef __MINGW32__
+  DWORD res=0;
+#else /* __MINGW32__ */
+  ssize_t res=0;
+#endif /* __MINGW32__ */
+  for (n=0;n<size && res>=0;n+=res) {
+#ifdef __MINGW32__
+    OVERLAPPED overl = {0, 0, {{0, 0}}, CreateEvent(NULL, TRUE, FALSE, NULL)};
+    if ((!ReadFile(fd,buf+n,size-n,&res,&overl)
+      && GetLastError() != ERROR_IO_PENDING) ||
+      !GetOverlappedResult(fd, &overl, &res, TRUE)) {
+      res = GetLastError();
+      CloseHandle(overl.hEvent);
+      if (res == ERROR_HANDLE_EOF) return n;
+      setErrno(res);
+      return -1;
+    }
+    CloseHandle(overl.hEvent);
+#else /* __MINGW32__ */
+    res=read(fd,buf+n,size-n);
+    if (res<0) {
+      if ((errno!=EINTR) &&
+#ifdef EWOULDBLOCK
+        (errno!=EWOULDBLOCK) &&
+#endif /* EWOULDBLOCK */
+        (errno!=EAGAIN)) { /* EAGAIN shouldn't happen, but who knows... */
+	return -1;
+      }
+      if (!loop && !n) return -1; /* Nothing read yet, report EINTR */
+      /* else, continue reading */
+    }
+#endif /* __MINGW32__ */
+    if (res==0)
+      /* Unexpected end of file ! */
+      break;
+  }
+  return n;
+}
+
+typedef enum {
+#ifdef __MINGW32__
+  READY, /* but no pending ReadFile */
+#endif /* __MINGW32__ */
+  READING_HEADER,
+  READING_CONTENT,
+  DISCARDING
+} PacketState;
+
+typedef struct {
+  brlapi_header_t header;
+  uint32_t content[BRLAPI_MAXPACKETSIZE/sizeof(uint32_t)+1]; /* +1 for additional \0 */
+  PacketState state;
+  int readBytes; /* Already read bytes */
+  unsigned char *p; /* Where read() should load datas */
+  int n; /* Value to give so read() */
+#ifdef __MINGW32__
+  OVERLAPPED overl;
+#endif /* __MINGW32__ */
+} Packet;
+
+/* Function: brlapi_resetPacket */
+/* Resets a Packet structure */
+static void brlapi_resetPacket(Packet *packet)
+{
+#ifdef __MINGW32__
+  packet->state = READY;
+#else /* __MINGW32__ */
+  packet->state = READING_HEADER;
+#endif /* __MINGW32__ */
+  packet->readBytes = 0;
+  packet->p = (unsigned char *) &packet->header;
+  packet->n = sizeof(packet->header);
+#ifdef __MINGW32__
+  SetEvent(packet->overl.hEvent);
+#endif /* __MINGW32__ */
+}
+
+/* Function: brlapi_initializePacket */
+/* Prepares a Packet structure */
+/* returns 0 on success, -1 on failure */
+static int brlapi_initializePacket(Packet *packet)
+{
+#ifdef __MINGW32__
+  memset(&packet->overl,0,sizeof(packet->overl));
+  if (!(packet->overl.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL))) {
+    setSystemErrno();
+    LibcError("CreateEvent for readPacket");
+    return -1;
+  }
+#endif /* __MINGW32__ */
+  brlapi_resetPacket(packet);
+  return 0;
+}
+
+/* Function : readPacket */
+/* Reads a packet for the given connection */
+/* Returns -2 on EOF, -1 on error, 0 if the reading is not complete, */
+/* 1 if the packet has been read. */
+static int brlapi__readPacket(Packet *packet, brlapi_fileDescriptor descriptor)
+{
+#ifdef __MINGW32__
+  DWORD res;
+  if (packet->state!=READY) {
+    /* pending read */
+    if (!GetOverlappedResult(descriptor,&packet->overl,&res,FALSE)) {
+      switch (GetLastError()) {
+        case ERROR_IO_PENDING: return 0;
+        case ERROR_HANDLE_EOF:
+        case ERROR_BROKEN_PIPE: return -2;
+        default: setSystemErrno(); LibcError("GetOverlappedResult"); return -1;
+      }
+    }
+read:
+#else /* __MINGW32__ */
+  int res;
+read:
+  res = read(descriptor, packet->p, packet->n);
+  if (res==-1) {
+    switch (errno) {
+      case EINTR: goto read;
+      case EAGAIN: return 0;
+      default: return -1;
+    }
+  }
+#endif /* __MINGW32__ */
+  if (res==0) return -2; /* EOF */
+  packet->readBytes += res;
+  if ((packet->state==READING_HEADER) && (packet->readBytes==BRLAPI_HEADERSIZE)) {
+    packet->header.size = ntohl(packet->header.size);
+    packet->header.type = ntohl(packet->header.type);
+    if (packet->header.size==0) goto out;
+    packet->readBytes = 0;
+    if (packet->header.size<=BRLAPI_MAXPACKETSIZE) {
+      packet->state = READING_CONTENT;
+      packet->n = packet->header.size;
+    } else {
+      packet->state = DISCARDING;
+      packet->n = BRLAPI_MAXPACKETSIZE;
+    }
+    packet->p = (unsigned char*) packet->content;
+  } else if ((packet->state == READING_CONTENT) && (packet->readBytes==packet->header.size)) goto out;
+  else if (packet->state==DISCARDING) {
+    packet->p = (unsigned char *) packet->content;
+    packet->n = MIN(packet->header.size-packet->readBytes, BRLAPI_MAXPACKETSIZE);
+  } else {
+    packet->n -= res;
+    packet->p += res;
+  }
+#ifdef __MINGW32__
+  } else packet->state = READING_HEADER;
+  if (!ResetEvent(packet->overl.hEvent))
+  {
+    setSystemErrno();
+    LibcError("ResetEvent in readPacket");
+  }
+  if (!ReadFile(descriptor, packet->p, packet->n, &res, &packet->overl)) {
+    switch (GetLastError()) {
+      case ERROR_IO_PENDING: return 0;
+      case ERROR_HANDLE_EOF:
+      case ERROR_BROKEN_PIPE: return -2;
+      default: setSystemErrno(); LibcError("ReadFile"); return -1;
+    }
+  }
+#endif /* __MINGW32__ */
+  goto read;
+
+out:
+  brlapi_resetPacket(packet);
+  return 1;
+}
+
+/* brlapi_writePacket */
+/* Write a packet on the socket */
+ssize_t BRLAPI(writePacket)(brlapi_fileDescriptor fd, brlapi_packetType_t type, const void *buf, size_t size)
+{
+  uint32_t header[2] = { htonl(size), htonl(type) };
+  ssize_t res;
+
+  /* first send packet header (size+type) */
+  if ((res=brlapi_writeFile(fd,&header[0],sizeof(header)))<0) {
+    LibcError("write in writePacket");
+    return res;
+  }
+
+  /* eventually data */
+  if (size && buf)
+    if ((res=brlapi_writeFile(fd,buf,size))<0) {
+      LibcError("write in writePacket");
+      return res;
+    }
+
+  return 0;
+}
+
+/* brlapi_readPacketHeader */
+/* Read a packet's header and return packet's size */
+ssize_t BRLAPI(readPacketHeader)(brlapi_fileDescriptor fd, brlapi_packetType_t *packetType)
+{
+  uint32_t header[2];
+  ssize_t res;
+  if ((res=brlapi_readFile(fd,header,sizeof(header),0)) != sizeof(header)) {
+    if (res<0) {
+      /* reports EINTR too */
+      LibcError("read in brlapi_readPacketHeader");
+      return -1;
+    } else return -2;
+  }
+  *packetType = ntohl(header[1]);
+  return ntohl(header[0]);
+}
+
+/* brlapi_readPacketContent */
+/* Read a packet's content into the given buffer */
+/* If the packet is too large, the buffer is filled with the */
+/* beginning of the packet, the rest of the packet being discarded */
+/* Returns packet size, -1 on failure, -2 on EOF */
+ssize_t BRLAPI(readPacketContent)(brlapi_fileDescriptor fd, size_t packetSize, void *buf, size_t bufSize)
+{
+  ssize_t res;
+  char foo[BRLAPI_MAXPACKETSIZE];
+  while (1) {
+    res = brlapi_readFile(fd,buf,MIN(bufSize,packetSize),1);
+    if (res >= 0) break;
+    if (errno != EINTR
+#ifdef EWOULDBLOCK
+	&& errno != EWOULDBLOCK
+#endif /* EWOULDBLOCK */
+	&& errno != EAGAIN)
+      goto out;
+  }
+  if (res<MIN(bufSize,packetSize)) return -2; /* pkt smaller than announced => EOF */
+  if (packetSize>bufSize) {
+    size_t discard = packetSize-bufSize;
+    for (res=0; res<discard / sizeof(foo); res++)
+      brlapi_readFile(fd,foo,sizeof(foo),1);
+    brlapi_readFile(fd,foo,discard % sizeof(foo),1);
+  }
+  return packetSize;
+
+out:
+  LibcError("read in brlapi_readPacket");
+  return -1;
+}
+
+/* brlapi_readPacket */
+/* Read a packet */
+/* Returns packet's size, -2 if EOF, -1 on error */
+/* If the packet is larger than the supplied buffer, then */
+/* the packet is truncated to buffer's size, like in the recv system call */
+/* with option MSG_TRUNC (rest of the pcket is read but discarded) */
+ssize_t BRLAPI(readPacket)(brlapi_fileDescriptor fd, brlapi_packetType_t *packetType, void *buf, size_t size)
+{
+  ssize_t res = BRLAPI(readPacketHeader)(fd, packetType);
+  if (res<0) return res; /* reports EINTR too */
+  return BRLAPI(readPacketContent)(fd, res, buf, size);
+}
+
+/* Function : brlapi_loadAuthKey */
+/* Loads an authorization key from the given file */
+/* It is stored in auth, and its size in authLength */
+/* If the file is non-existant or unreadable, returns -1 */
+static int BRLAPI(loadAuthKey)(const char *filename, size_t *authlength, void *auth)
+{
+  int fd;
+  off_t stsize;
+  struct stat statbuf;
+  if (stat(filename, &statbuf)<0) {
+    LibcError("stat in loadAuthKey");
+    return -1;
+  }
+
+  if (statbuf.st_size==0) {
+    brlapi_errno = BRLAPI_ERROR_EMPTYKEY;
+    brlapi_errfun = "brlapi_laudAuthKey";
+    return -1;
+  }
+
+  stsize = MIN(statbuf.st_size, BRLAPI_MAXPACKETSIZE-2*sizeof(uint32_t));
+
+  if ((fd = open(filename, O_RDONLY)) <0) {
+    LibcError("open in loadAuthKey");
+    return -1;
+  }
+
+  *authlength = brlapi_readFile(
+#ifdef __MINGW32__
+		  (HANDLE) get_osfhandle(fd),
+#else /* __MINGW32__ */
+		  fd,
+#endif /* __MINGW32__ */
+		  auth, stsize, 1);
+
+  if (*authlength!=(size_t)stsize) {
+    LibcError("read in loadAuthKey");
+    close(fd);
+    return -1;
+  }
+
+  close(fd);
+  return 0;
+}
+
+static char *
+intToString (int64_t value) {
+  char buffer[0X20];
+  snprintf(buffer, sizeof(buffer), "%"PRId64, value);
+  return strdup(buffer);
+}
+
+#define LOCALHOST_ADDRESS_IPV4 "127.0.0.1"
+#define LOCALHOST_ADDRESS_IPV6 "::1"
+
+static int
+isPortNumber (const char *number, uint16_t *port) {
+  if (!number) return 0;
+  if (*number < '0') return 0;
+  if (*number > '9') return 0;
+
+  char *end;
+  long int value = strtol(number, &end, 10);
+  if (*end) return 0;
+
+  if (value < 0) return 0;
+  if (value > (UINT16_MAX - BRLAPI_SOCKETPORTNUM)) return 0;
+
+  if (port) *port = value + BRLAPI_SOCKETPORTNUM;
+  return 1;
+}
+
+/* Function: brlapi_expandHost
+ * splits host into host & port */
+static int BRLAPI(expandHost)(const char *hostAndPort, char **host, char **port) {
+  const char *c;
+  if (!hostAndPort || !*hostAndPort) {
+#if defined(PF_LOCAL)
+    *host = NULL;
+    *port = strdup("0");
+    return PF_LOCAL;
+#else /* PF_LOCAL */
+    *host = strdup(LOCALHOST_ADDRESS_IPV4);
+    *port = strdup(BRLAPI_SOCKETPORT);
+    return PF_UNSPEC;
+#endif /* PF_LOCAL */
+  } else if ((c = strrchr(hostAndPort,':'))) {
+    if (c != hostAndPort) {
+      uint16_t porti = BRLAPI_SOCKETPORTNUM;
+      isPortNumber(c+1, &porti);
+      *host = malloc(c-hostAndPort+1);
+      memcpy(*host, hostAndPort, c-hostAndPort);
+      (*host)[c-hostAndPort] = 0;
+      *port = intToString(porti);
+      return PF_UNSPEC;
+    } else {
+#if defined(PF_LOCAL)
+      *host = NULL;
+      *port = strdup(c+1);
+      return PF_LOCAL;
+#else /* PF_LOCAL */
+      uint16_t porti = BRLAPI_SOCKETPORTNUM;
+      isPortNumber(c+1, &porti);
+      *host = strdup(LOCALHOST_ADDRESS_IPV4);
+      *port = intToString(porti);
+      return PF_UNSPEC;
+#endif /* PF_LOCAL */
+    }
+  } else {
+    *host = strdup(hostAndPort);
+    *port = strdup(BRLAPI_SOCKETPORT);
+    return PF_UNSPEC;
+  }
+}
+
+typedef struct {
+  brlapi_packetType_t type;
+  const char *name;
+} brlapi_packetTypeEntry_t;
+
+static const brlapi_packetTypeEntry_t brlapi_packetTypeTable[] = {
+  { BRLAPI_PACKET_VERSION, "Version" },
+  { BRLAPI_PACKET_AUTH, "Auth" },
+  { BRLAPI_PACKET_GETDRIVERNAME, "GetDriverName" },
+  { BRLAPI_PACKET_GETDISPLAYSIZE, "GetDisplaySize" },
+  { BRLAPI_PACKET_ENTERTTYMODE, "EnterTtyMode" },
+  { BRLAPI_PACKET_SETFOCUS, "SetFocus" },
+  { BRLAPI_PACKET_LEAVETTYMODE, "LeaveTtyMode" },
+  { BRLAPI_PACKET_KEY, "Key" },
+  { BRLAPI_PACKET_IGNOREKEYRANGES, "IgnoreKeyRanges" },
+  { BRLAPI_PACKET_ACCEPTKEYRANGES, "AcceptKeyRanges" },
+  { BRLAPI_PACKET_WRITE, "Write" },
+  { BRLAPI_PACKET_ENTERRAWMODE, "EnterRawMode" },
+  { BRLAPI_PACKET_LEAVERAWMODE, "LeaveRawMode" },
+  { BRLAPI_PACKET_PACKET, "Packet" },
+  { BRLAPI_PACKET_SUSPENDDRIVER, "SuspendDriver" },
+  { BRLAPI_PACKET_RESUMEDRIVER, "ResumeDriver" },
+  { BRLAPI_PACKET_PARAM_VALUE, "ParameterValue" },
+  { BRLAPI_PACKET_PARAM_REQUEST, "ParameterRequest" },
+  { BRLAPI_PACKET_SYNCHRONIZE, "Synchronize" },
+  { BRLAPI_PACKET_ACK, "Ack" },
+  { BRLAPI_PACKET_ERROR, "Error" },
+  { BRLAPI_PACKET_EXCEPTION, "Exception" },
+  { 0, NULL }
+};
+
+const char * BRLAPI_STDCALL BRLAPI(getPacketTypeName)(brlapi_packetType_t type)
+{
+  const brlapi_packetTypeEntry_t *p;
+  for (p = brlapi_packetTypeTable; p->type; p++)
+    if (type==p->type) return p->name;
+  return "Unknown";
+}
+
+static int
+BRLAPI(getArgumentWidth) (brlapi_keyCode_t keyCode) {
+  brlapi_keyCode_t code = keyCode & BRLAPI_KEY_CODE_MASK;
+
+  switch (keyCode & BRLAPI_KEY_TYPE_MASK) {
+    default: break;
+
+    case BRLAPI_KEY_TYPE_SYM:
+      switch (code & 0XFF000000U) {
+        default: break;
+
+        case 0X00000000U:
+          switch (code & 0XFF0000U) {
+            default: break;
+            case 0X000000U: return 8;
+          }
+          break;
+
+        case 0X01000000U: return 24;
+      }
+      break;
+
+    case BRLAPI_KEY_TYPE_CMD:
+      switch (code & BRLAPI_KEY_CMD_BLK_MASK) {
+        default: return 16;
+        case 0: return 0;
+      }
+      break;
+  }
+
+  brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+  return -1;
+}
+
+/* Function brlapi_uint32ToKeyCode */
+/* Translate keycodes stored in a packet to a keycode */
+static brlapi_keyCode_t
+BRLAPI(packetToKeyCode)(uint32_t t[2])
+{
+  return (((brlapi_keyCode_t)ntohl(t[0])) << 32) | ntohl(t[1]);
+}
+
+/* Function : brlapi_getKeyrangeMask */
+/* returns the keyCode mask for a given range type */
+static int
+BRLAPI(getKeyrangeMask) (brlapi_rangeType_t r, brlapi_keyCode_t code, brlapi_keyCode_t *mask)
+{
+  switch(r) {
+    case brlapi_rangeType_all:
+      *mask = BRLAPI_KEY_MAX;
+      return 0;
+    case brlapi_rangeType_type:
+      *mask = BRLAPI_KEY_CODE_MASK|BRLAPI_KEY_FLAGS_MASK;
+      return 0;
+    case brlapi_rangeType_command: {
+      int width = BRLAPI(getArgumentWidth)(code);
+      if (width == -1) return -1;
+      *mask = ((1 << width) - 1) | BRLAPI_KEY_FLAGS_MASK;
+      return 0;
+    }
+    case brlapi_rangeType_key:
+      *mask = BRLAPI_KEY_FLAGS_MASK;
+      return 0;
+    case brlapi_rangeType_code:
+      *mask = 0;
+      return 0;
+  }
+  brlapi_errno = BRLAPI_ERROR_INVALID_PARAMETER;
+  return -1;
+}
+
+static char *
+BRLAPI(getKeyFile)(const char *auth)
+{
+  const char *path;
+  char *ret, *delim;
+  if (!strncmp(auth,"keyfile:",8))
+    path=auth+8;
+  else {
+    path=strstr(auth,"+keyfile:");
+    if (path) path+=9;
+    else path=auth;
+  }
+  ret=strdup(path);
+  delim=strchr(ret,'+');
+  if (delim)
+    *delim = 0;
+  return ret;
+}
+
+static const brlapi_param_properties_t brlapi_param_properties[BRLAPI_PARAM_COUNT] = {
+//Connection Parameters
+  [BRLAPI_PARAM_SERVER_VERSION] = {
+    .type = BRLAPI_PARAM_TYPE_UINT32,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_CLIENT_PRIORITY] = {
+    .type = BRLAPI_PARAM_TYPE_UINT32,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+//Device Parameters
+  [BRLAPI_PARAM_DRIVER_NAME] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DRIVER_CODE] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DRIVER_VERSION] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DEVICE_MODEL] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DEVICE_CELL_SIZE] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DISPLAY_SIZE] = {
+    .type = BRLAPI_PARAM_TYPE_UINT32,
+    .canRead = 1,
+    .canWatch = 1,
+    .isArray = 1,
+    .arraySize = 2,
+  },
+
+  [BRLAPI_PARAM_DEVICE_IDENTIFIER] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DEVICE_SPEED] = {
+    .type = BRLAPI_PARAM_TYPE_UINT32,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+  [BRLAPI_PARAM_DEVICE_ONLINE] = {
+    .type = BRLAPI_PARAM_TYPE_BOOLEAN,
+    .canRead = 1,
+    .canWatch = 1,
+  },
+
+//Input Parameters
+  [BRLAPI_PARAM_RETAIN_DOTS] = {
+    .type = BRLAPI_PARAM_TYPE_BOOLEAN,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+//Braille Rendering Parameters
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_LITERARY_BRAILLE] = {
+    .type = BRLAPI_PARAM_TYPE_BOOLEAN,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_CURSOR_DOTS] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_CURSOR_BLINK_PERIOD] = {
+    .type = BRLAPI_PARAM_TYPE_UINT32,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_CURSOR_BLINK_PERCENTAGE] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_RENDERED_CELLS] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .canWatch = 1,
+    .isArray = 1,
+  },
+
+//Navigation Parameters
+  [BRLAPI_PARAM_SKIP_IDENTICAL_LINES] = {
+    .type = BRLAPI_PARAM_TYPE_BOOLEAN,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_AUDIBLE_ALERTS] = {
+    .type = BRLAPI_PARAM_TYPE_BOOLEAN,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+//Clipboard Parameters
+  [BRLAPI_PARAM_CLIPBOARD_CONTENT] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+//TTY Mode Parameters
+  [BRLAPI_PARAM_BOUND_COMMAND_KEYCODES] = {
+    .type = BRLAPI_PARAM_TYPE_KEYCODE,
+    .canRead = 1,
+    .canWatch = 1,
+    .isArray = 1,
+  },
+
+  [BRLAPI_PARAM_COMMAND_KEYCODE_NAME] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .hasSubparam = 1,
+  },
+
+  [BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .hasSubparam = 1,
+  },
+
+  [BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES] = {
+    .type = BRLAPI_PARAM_TYPE_KEYCODE,
+    .canRead = 1,
+    .canWatch = 1,
+    .isArray = 1,
+  },
+
+  [BRLAPI_PARAM_DRIVER_KEYCODE_NAME] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .hasSubparam = 1,
+  },
+
+  [BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .hasSubparam = 1,
+  },
+
+//Braille Translation Parameters
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_ROWS_MASK] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .isArray = 1,
+    .arraySize = (0X10FFFF + 1) / 0X100 / 8,
+  },
+
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_ROW_CELLS] = {
+    .type = BRLAPI_PARAM_TYPE_UINT8,
+    .canRead = 1,
+    .isArray = 1,
+    .arraySize = 0X100 + (0X100 / 8),
+    .hasSubparam = 1,
+  },
+
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_LITERARY_BRAILLE_TABLE] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+
+  [BRLAPI_PARAM_MESSAGE_LOCALE] = {
+    .type = BRLAPI_PARAM_TYPE_STRING,
+    .canRead = 1,
+    .canWatch = 1,
+    .canWrite = 1,
+  },
+};
+
+const brlapi_param_properties_t *brlapi_getParameterProperties(brlapi_param_t parameter) {
+  if (parameter >= (sizeof(brlapi_param_properties) / sizeof(*brlapi_param_properties)))
+    return NULL;
+
+  return &brlapi_param_properties[parameter];
+}
+
+typedef void IntegerConverter (void *v);
+
+static void convertInteger_hton16 (void *v) {
+  uint16_t *u = v;
+  *u = htons(*u);
+}
+
+static void convertInteger_ntoh16 (void *v) {
+  uint16_t *u = v;
+  *u = ntohs(*u);
+}
+
+static void convertInteger_hton32 (void *v) {
+  uint32_t *u = v;
+  *u = htonl(*u);
+}
+
+static void convertInteger_ntoh32 (void *v) {
+  uint32_t *u = v;
+  *u = ntohl(*u);
+}
+
+#define UINT64_SHIFT ((sizeof(uint64_t) / 2) * 8)
+#define UINT64_LOW ((UINT64_C(1) << UINT64_SHIFT) - 1)
+
+static void convertInteger_hton64 (void *v) {
+#ifndef WORDS_BIGENDIAN
+  uint64_t *u = v;
+  uint32_t low = *u & UINT64_LOW;
+  uint32_t high = *u >> UINT64_SHIFT;
+  *u = ((uint64_t)htonl(low) << UINT64_SHIFT) | htonl(high);
+#endif /* WORDS_BIGENDIAN */
+}
+
+static void convertInteger_ntoh64 (void *v) {
+#ifndef WORDS_BIGENDIAN
+  uint64_t *u = v;
+  uint32_t low = *u & UINT64_LOW;
+  uint32_t high = *u >> UINT64_SHIFT;
+  *u = ((uint64_t)ntohl(low) << UINT64_SHIFT) | ntohl(high);
+#endif /* WORDS_BIGENDIAN */
+}
+
+static void convertIntegers (void *data, size_t length, size_t size, IntegerConverter *convert) {
+  length /= size;
+  length *= size;
+  void *end = data + length;
+
+  while (data < end) {
+    convert(data);
+    data += size;
+  }
+}
+
+/* Function: _brlapi_htonParameter */
+/* swap uint32 values of the parameter from host to network */
+void _brlapi_htonParameter(brlapi_param_t parameter, brlapi_paramValuePacket_t *value, size_t len)
+{
+  const brlapi_param_properties_t *properties = brlapi_getParameterProperties(parameter);
+
+  if (properties) {
+    void *p =  value->data;
+
+    switch (properties->type) {
+      case BRLAPI_PARAM_TYPE_UINT16:
+        convertIntegers(p, len, sizeof(uint16_t), convertInteger_hton16);
+        return;
+
+      case BRLAPI_PARAM_TYPE_UINT32:
+        convertIntegers(p, len, sizeof(uint32_t), convertInteger_hton32);
+        return;
+
+      case BRLAPI_PARAM_TYPE_UINT64:
+        convertIntegers(p, len, sizeof(uint64_t), convertInteger_hton64);
+        return;
+
+      default:
+        /* no conversion needed */
+        return;
+    }
+  }
+}
+
+/* Function: _brlapi_ntohParameter */
+/* swap uint32 values of the parameter from network to host */
+void _brlapi_ntohParameter(brlapi_param_t parameter, brlapi_paramValuePacket_t *value, size_t len)
+{
+  const brlapi_param_properties_t *properties = brlapi_getParameterProperties(parameter);
+
+  if (properties) {
+    void *p =  value->data;
+
+    switch (properties->type) {
+      case BRLAPI_PARAM_TYPE_UINT16:
+        convertIntegers(p, len, sizeof(uint16_t), convertInteger_ntoh16);
+        return;
+
+      case BRLAPI_PARAM_TYPE_UINT32:
+        convertIntegers(p, len, sizeof(uint32_t), convertInteger_ntoh32);
+        return;
+
+      case BRLAPI_PARAM_TYPE_UINT64:
+        convertIntegers(p, len, sizeof(uint64_t), convertInteger_ntoh64);
+        return;
+
+      default:
+        /* no conversion needed */
+        return;
+    }
+  }
+}
+
diff --git a/Programs/brlapi_constants.awk b/Programs/brlapi_constants.awk
new file mode 100644
index 0000000..061d912
--- /dev/null
+++ b/Programs/brlapi_constants.awk
@@ -0,0 +1,93 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  writeHeaderPrologue("BRLAPI_INCLUDED_CONSTANTS", "brlapi.h")
+  beginDoxygenFile()
+  print "/** \\ingroup brlapi_keycodes"
+  print " * @{ */"
+  print ""
+}
+
+END {
+  writeBrlapiDots()
+  print "/** @} */"
+  print ""
+  writeHeaderEpilogue()
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeMacroDefinition("BRLAPI_KEY_CMD_" name, "(BRLAPI_KEY_CMD(0) + " value ")", help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  if (name == "PASSCHAR") return
+  if (name == "PASSKEY") return
+
+  writeMacroDefinition("BRLAPI_KEY_CMD_" name, "BRLAPI_KEY_CMD(" value ")", help)
+}
+
+function brlKey(name, symbol, value, help) {
+}
+
+function brlFlag(name, symbol, value, help) {
+  if (value ~ /^0[xX][0-9a-fA-F]+0000$/) {
+    value = substr(value, 1, length(value)-4)
+    if (name ~ /^INPUT_/) {
+      name = substr(name, 7)
+    } else {
+      value = value "00"
+    }
+    value = "BRLAPI_KEY_FLG(" value ")"
+  } else if (value ~ /^\(/) {
+    gsub("BRL_FLG_", "BRLAPI_KEY_FLG_", value)
+  } else {
+    return
+  }
+  writeMacroDefinition("BRLAPI_KEY_FLG_" name, value, help)
+}
+
+function brlDot(number, symbol, value, help) {
+  writeMacroDefinition("BRLAPI_DOT" number, value, help)
+}
+
+function writeBrlapiDots() {
+  print ""
+  print "/** Helper macro to easily produce braille patterns */"
+
+  arguments = ""
+  argumentDelimiter = ""
+  expression = ""
+  subexpressionDelimiter = ""
+
+  for (dotNumber=1; dotNumber<=8; ++dotNumber) {
+    argumentName = "dot" dotNumber
+
+    arguments = arguments argumentDelimiter argumentName
+    argumentDelimiter = ", "
+
+    subexpression = "((" argumentName ")? BRLAPI_DOT" dotNumber ": 0)"
+    expression = expression subexpressionDelimiter "  " subexpression
+    subexpressionDelimiter = " | \\\n"
+  }
+
+  print "#define BRLAPI_DOTS(" arguments ") (\\\n" expression " \\\n)"
+
+  print ""
+  writeMacroDefinition("BRLAPI_DOT_CHORD", 256, "space key")
+}
diff --git a/Programs/brlapi_constants.h b/Programs/brlapi_constants.h
new file mode 100644
index 0000000..1bc811a
--- /dev/null
+++ b/Programs/brlapi_constants.h
@@ -0,0 +1,505 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_CONSTANTS
+#define BRLAPI_INCLUDED_CONSTANTS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** \file
+ */
+
+/** \ingroup brlapi_keycodes
+ * @{ */
+
+/** do nothing */
+#define BRLAPI_KEY_CMD_NOOP (BRLAPI_KEY_CMD(0) + 0)
+/** go up one line */
+#define BRLAPI_KEY_CMD_LNUP (BRLAPI_KEY_CMD(0) + 1)
+/** go down one line */
+#define BRLAPI_KEY_CMD_LNDN (BRLAPI_KEY_CMD(0) + 2)
+/** go up several lines */
+#define BRLAPI_KEY_CMD_WINUP (BRLAPI_KEY_CMD(0) + 3)
+/** go down several lines */
+#define BRLAPI_KEY_CMD_WINDN (BRLAPI_KEY_CMD(0) + 4)
+/** go up to nearest line with different content */
+#define BRLAPI_KEY_CMD_PRDIFLN (BRLAPI_KEY_CMD(0) + 5)
+/** go down to nearest line with different content */
+#define BRLAPI_KEY_CMD_NXDIFLN (BRLAPI_KEY_CMD(0) + 6)
+/** go up to nearest line with different highlighting */
+#define BRLAPI_KEY_CMD_ATTRUP (BRLAPI_KEY_CMD(0) + 7)
+/** go down to nearest line with different highlighting */
+#define BRLAPI_KEY_CMD_ATTRDN (BRLAPI_KEY_CMD(0) + 8)
+/** go to top line */
+#define BRLAPI_KEY_CMD_TOP (BRLAPI_KEY_CMD(0) + 9)
+/** go to bottom line */
+#define BRLAPI_KEY_CMD_BOT (BRLAPI_KEY_CMD(0) + 10)
+/** go to beginning of top line */
+#define BRLAPI_KEY_CMD_TOP_LEFT (BRLAPI_KEY_CMD(0) + 11)
+/** go to beginning of bottom line */
+#define BRLAPI_KEY_CMD_BOT_LEFT (BRLAPI_KEY_CMD(0) + 12)
+/** go up to first line of paragraph */
+#define BRLAPI_KEY_CMD_PRPGRPH (BRLAPI_KEY_CMD(0) + 13)
+/** go down to first line of next paragraph */
+#define BRLAPI_KEY_CMD_NXPGRPH (BRLAPI_KEY_CMD(0) + 14)
+/** go up to previous command prompt */
+#define BRLAPI_KEY_CMD_PRPROMPT (BRLAPI_KEY_CMD(0) + 15)
+/** go down to next command prompt */
+#define BRLAPI_KEY_CMD_NXPROMPT (BRLAPI_KEY_CMD(0) + 16)
+/** search backward for clipboard text */
+#define BRLAPI_KEY_CMD_PRSEARCH (BRLAPI_KEY_CMD(0) + 17)
+/** search forward for clipboard text */
+#define BRLAPI_KEY_CMD_NXSEARCH (BRLAPI_KEY_CMD(0) + 18)
+/** go left one character */
+#define BRLAPI_KEY_CMD_CHRLT (BRLAPI_KEY_CMD(0) + 19)
+/** go right one character */
+#define BRLAPI_KEY_CMD_CHRRT (BRLAPI_KEY_CMD(0) + 20)
+/** go left half a braille window */
+#define BRLAPI_KEY_CMD_HWINLT (BRLAPI_KEY_CMD(0) + 21)
+/** go right half a braille window */
+#define BRLAPI_KEY_CMD_HWINRT (BRLAPI_KEY_CMD(0) + 22)
+/** go backward one braille window */
+#define BRLAPI_KEY_CMD_FWINLT (BRLAPI_KEY_CMD(0) + 23)
+/** go forward one braille window */
+#define BRLAPI_KEY_CMD_FWINRT (BRLAPI_KEY_CMD(0) + 24)
+/** go backward skipping blank braille windows */
+#define BRLAPI_KEY_CMD_FWINLTSKIP (BRLAPI_KEY_CMD(0) + 25)
+/** go forward skipping blank braille windows */
+#define BRLAPI_KEY_CMD_FWINRTSKIP (BRLAPI_KEY_CMD(0) + 26)
+/** go to beginning of line */
+#define BRLAPI_KEY_CMD_LNBEG (BRLAPI_KEY_CMD(0) + 27)
+/** go to end of line */
+#define BRLAPI_KEY_CMD_LNEND (BRLAPI_KEY_CMD(0) + 28)
+/** go to screen cursor */
+#define BRLAPI_KEY_CMD_HOME (BRLAPI_KEY_CMD(0) + 29)
+/** go back after cursor tracking */
+#define BRLAPI_KEY_CMD_BACK (BRLAPI_KEY_CMD(0) + 30)
+/** go to screen cursor or go back after cursor tracking */
+#define BRLAPI_KEY_CMD_RETURN (BRLAPI_KEY_CMD(0) + 31)
+/** set screen image frozen/unfrozen */
+#define BRLAPI_KEY_CMD_FREEZE (BRLAPI_KEY_CMD(0) + 32)
+/** set display mode attributes/text */
+#define BRLAPI_KEY_CMD_DISPMD (BRLAPI_KEY_CMD(0) + 33)
+/** set text style 6-dot/8-dot */
+#define BRLAPI_KEY_CMD_SIXDOTS (BRLAPI_KEY_CMD(0) + 34)
+/** set sliding braille window on/off */
+#define BRLAPI_KEY_CMD_SLIDEWIN (BRLAPI_KEY_CMD(0) + 35)
+/** set skipping of lines with identical content on/off */
+#define BRLAPI_KEY_CMD_SKPIDLNS (BRLAPI_KEY_CMD(0) + 36)
+/** set skipping of blank braille windows on/off */
+#define BRLAPI_KEY_CMD_SKPBLNKWINS (BRLAPI_KEY_CMD(0) + 37)
+/** set screen cursor visibility on/off */
+#define BRLAPI_KEY_CMD_CSRVIS (BRLAPI_KEY_CMD(0) + 38)
+/** set hidden screen cursor on/off */
+#define BRLAPI_KEY_CMD_CSRHIDE (BRLAPI_KEY_CMD(0) + 39)
+/** set track screen cursor on/off */
+#define BRLAPI_KEY_CMD_CSRTRK (BRLAPI_KEY_CMD(0) + 40)
+/** set screen cursor style block/underline */
+#define BRLAPI_KEY_CMD_CSRSIZE (BRLAPI_KEY_CMD(0) + 41)
+/** set screen cursor blinking on/off */
+#define BRLAPI_KEY_CMD_CSRBLINK (BRLAPI_KEY_CMD(0) + 42)
+/** set attribute underlining on/off */
+#define BRLAPI_KEY_CMD_ATTRVIS (BRLAPI_KEY_CMD(0) + 43)
+/** set attribute blinking on/off */
+#define BRLAPI_KEY_CMD_ATTRBLINK (BRLAPI_KEY_CMD(0) + 44)
+/** set capital letter blinking on/off */
+#define BRLAPI_KEY_CMD_CAPBLINK (BRLAPI_KEY_CMD(0) + 45)
+/** set alert tunes on/off */
+#define BRLAPI_KEY_CMD_TUNES (BRLAPI_KEY_CMD(0) + 46)
+/** set autorepeat on/off */
+#define BRLAPI_KEY_CMD_AUTOREPEAT (BRLAPI_KEY_CMD(0) + 47)
+/** set autospeak on/off */
+#define BRLAPI_KEY_CMD_AUTOSPEAK (BRLAPI_KEY_CMD(0) + 48)
+/** enter/leave help display */
+#define BRLAPI_KEY_CMD_HELP (BRLAPI_KEY_CMD(0) + 49)
+/** enter/leave status display */
+#define BRLAPI_KEY_CMD_INFO (BRLAPI_KEY_CMD(0) + 50)
+/** enter/leave command learn mode */
+#define BRLAPI_KEY_CMD_LEARN (BRLAPI_KEY_CMD(0) + 51)
+/** enter/leave preferences menu */
+#define BRLAPI_KEY_CMD_PREFMENU (BRLAPI_KEY_CMD(0) + 52)
+/** save preferences to disk */
+#define BRLAPI_KEY_CMD_PREFSAVE (BRLAPI_KEY_CMD(0) + 53)
+/** restore preferences from disk */
+#define BRLAPI_KEY_CMD_PREFLOAD (BRLAPI_KEY_CMD(0) + 54)
+/** go up to first item */
+#define BRLAPI_KEY_CMD_MENU_FIRST_ITEM (BRLAPI_KEY_CMD(0) + 55)
+/** go down to last item */
+#define BRLAPI_KEY_CMD_MENU_LAST_ITEM (BRLAPI_KEY_CMD(0) + 56)
+/** go up to previous item */
+#define BRLAPI_KEY_CMD_MENU_PREV_ITEM (BRLAPI_KEY_CMD(0) + 57)
+/** go down to next item */
+#define BRLAPI_KEY_CMD_MENU_NEXT_ITEM (BRLAPI_KEY_CMD(0) + 58)
+/** select previous choice */
+#define BRLAPI_KEY_CMD_MENU_PREV_SETTING (BRLAPI_KEY_CMD(0) + 59)
+/** select next choice */
+#define BRLAPI_KEY_CMD_MENU_NEXT_SETTING (BRLAPI_KEY_CMD(0) + 60)
+/** stop speaking */
+#define BRLAPI_KEY_CMD_MUTE (BRLAPI_KEY_CMD(0) + 61)
+/** go to current speaking position */
+#define BRLAPI_KEY_CMD_SPKHOME (BRLAPI_KEY_CMD(0) + 62)
+/** speak current line */
+#define BRLAPI_KEY_CMD_SAY_LINE (BRLAPI_KEY_CMD(0) + 63)
+/** speak from top of screen through current line */
+#define BRLAPI_KEY_CMD_SAY_ABOVE (BRLAPI_KEY_CMD(0) + 64)
+/** speak from current line through bottom of screen */
+#define BRLAPI_KEY_CMD_SAY_BELOW (BRLAPI_KEY_CMD(0) + 65)
+/** decrease speaking rate */
+#define BRLAPI_KEY_CMD_SAY_SLOWER (BRLAPI_KEY_CMD(0) + 66)
+/** increase speaking rate */
+#define BRLAPI_KEY_CMD_SAY_FASTER (BRLAPI_KEY_CMD(0) + 67)
+/** decrease speaking volume */
+#define BRLAPI_KEY_CMD_SAY_SOFTER (BRLAPI_KEY_CMD(0) + 68)
+/** increase speaking volume */
+#define BRLAPI_KEY_CMD_SAY_LOUDER (BRLAPI_KEY_CMD(0) + 69)
+/** switch to the previous virtual terminal */
+#define BRLAPI_KEY_CMD_SWITCHVT_PREV (BRLAPI_KEY_CMD(0) + 70)
+/** switch to the next virtual terminal */
+#define BRLAPI_KEY_CMD_SWITCHVT_NEXT (BRLAPI_KEY_CMD(0) + 71)
+/** bring screen cursor to current line */
+#define BRLAPI_KEY_CMD_CSRJMP_VERT (BRLAPI_KEY_CMD(0) + 72)
+/** insert clipboard text after screen cursor */
+#define BRLAPI_KEY_CMD_PASTE (BRLAPI_KEY_CMD(0) + 73)
+/** restart braille driver */
+#define BRLAPI_KEY_CMD_RESTARTBRL (BRLAPI_KEY_CMD(0) + 74)
+/** restart speech driver */
+#define BRLAPI_KEY_CMD_RESTARTSPEECH (BRLAPI_KEY_CMD(0) + 75)
+/** braille display temporarily unavailable */
+#define BRLAPI_KEY_CMD_OFFLINE (BRLAPI_KEY_CMD(0) + 76)
+/** cycle the Shift sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_SHIFT (BRLAPI_KEY_CMD(0) + 77)
+/** cycle the Upper sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_UPPER (BRLAPI_KEY_CMD(0) + 78)
+/** cycle the Control sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_CONTROL (BRLAPI_KEY_CMD(0) + 79)
+/** cycle the Meta (Left Alt) sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_META (BRLAPI_KEY_CMD(0) + 80)
+/** show current date and time */
+#define BRLAPI_KEY_CMD_TIME (BRLAPI_KEY_CMD(0) + 81)
+/** go to previous menu level */
+#define BRLAPI_KEY_CMD_MENU_PREV_LEVEL (BRLAPI_KEY_CMD(0) + 82)
+/** set autospeak selected line on/off */
+#define BRLAPI_KEY_CMD_ASPK_SEL_LINE (BRLAPI_KEY_CMD(0) + 83)
+/** set autospeak selected character on/off */
+#define BRLAPI_KEY_CMD_ASPK_SEL_CHAR (BRLAPI_KEY_CMD(0) + 84)
+/** set autospeak inserted characters on/off */
+#define BRLAPI_KEY_CMD_ASPK_INS_CHARS (BRLAPI_KEY_CMD(0) + 85)
+/** set autospeak deleted characters on/off */
+#define BRLAPI_KEY_CMD_ASPK_DEL_CHARS (BRLAPI_KEY_CMD(0) + 86)
+/** set autospeak replaced characters on/off */
+#define BRLAPI_KEY_CMD_ASPK_REP_CHARS (BRLAPI_KEY_CMD(0) + 87)
+/** set autospeak completed words on/off */
+#define BRLAPI_KEY_CMD_ASPK_CMP_WORDS (BRLAPI_KEY_CMD(0) + 88)
+/** speak current character */
+#define BRLAPI_KEY_CMD_SPEAK_CURR_CHAR (BRLAPI_KEY_CMD(0) + 89)
+/** go to and speak previous character */
+#define BRLAPI_KEY_CMD_SPEAK_PREV_CHAR (BRLAPI_KEY_CMD(0) + 90)
+/** go to and speak next character */
+#define BRLAPI_KEY_CMD_SPEAK_NEXT_CHAR (BRLAPI_KEY_CMD(0) + 91)
+/** speak current word */
+#define BRLAPI_KEY_CMD_SPEAK_CURR_WORD (BRLAPI_KEY_CMD(0) + 92)
+/** go to and speak previous word */
+#define BRLAPI_KEY_CMD_SPEAK_PREV_WORD (BRLAPI_KEY_CMD(0) + 93)
+/** go to and speak next word */
+#define BRLAPI_KEY_CMD_SPEAK_NEXT_WORD (BRLAPI_KEY_CMD(0) + 94)
+/** speak current line */
+#define BRLAPI_KEY_CMD_SPEAK_CURR_LINE (BRLAPI_KEY_CMD(0) + 95)
+/** go to and speak previous line */
+#define BRLAPI_KEY_CMD_SPEAK_PREV_LINE (BRLAPI_KEY_CMD(0) + 96)
+/** go to and speak next line */
+#define BRLAPI_KEY_CMD_SPEAK_NEXT_LINE (BRLAPI_KEY_CMD(0) + 97)
+/** go to and speak first non-blank character on line */
+#define BRLAPI_KEY_CMD_SPEAK_FRST_CHAR (BRLAPI_KEY_CMD(0) + 98)
+/** go to and speak last non-blank character on line */
+#define BRLAPI_KEY_CMD_SPEAK_LAST_CHAR (BRLAPI_KEY_CMD(0) + 99)
+/** go to and speak first non-blank line on screen */
+#define BRLAPI_KEY_CMD_SPEAK_FRST_LINE (BRLAPI_KEY_CMD(0) + 100)
+/** go to and speak last non-blank line on screen */
+#define BRLAPI_KEY_CMD_SPEAK_LAST_LINE (BRLAPI_KEY_CMD(0) + 101)
+/** describe current character */
+#define BRLAPI_KEY_CMD_DESC_CURR_CHAR (BRLAPI_KEY_CMD(0) + 102)
+/** spell current word */
+#define BRLAPI_KEY_CMD_SPELL_CURR_WORD (BRLAPI_KEY_CMD(0) + 103)
+/** bring screen cursor to speech cursor */
+#define BRLAPI_KEY_CMD_ROUTE_CURR_LOCN (BRLAPI_KEY_CMD(0) + 104)
+/** speak speech cursor location */
+#define BRLAPI_KEY_CMD_SPEAK_CURR_LOCN (BRLAPI_KEY_CMD(0) + 105)
+/** set speech cursor visibility on/off */
+#define BRLAPI_KEY_CMD_SHOW_CURR_LOCN (BRLAPI_KEY_CMD(0) + 106)
+/** save clipboard to disk */
+#define BRLAPI_KEY_CMD_CLIP_SAVE (BRLAPI_KEY_CMD(0) + 107)
+/** restore clipboard from disk */
+#define BRLAPI_KEY_CMD_CLIP_RESTORE (BRLAPI_KEY_CMD(0) + 108)
+/** set braille typing mode dots/text */
+#define BRLAPI_KEY_CMD_BRLUCDOTS (BRLAPI_KEY_CMD(0) + 109)
+/** set braille keyboard enabled/disabled */
+#define BRLAPI_KEY_CMD_BRLKBD (BRLAPI_KEY_CMD(0) + 110)
+/** clear all sticky input modifiers */
+#define BRLAPI_KEY_CMD_UNSTICK (BRLAPI_KEY_CMD(0) + 111)
+/** cycle the AltGr (Right Alt) sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_ALTGR (BRLAPI_KEY_CMD(0) + 112)
+/** cycle the GUI (Windows) sticky input modifier (next, on, off) */
+#define BRLAPI_KEY_CMD_GUI (BRLAPI_KEY_CMD(0) + 113)
+/** stop the braille driver */
+#define BRLAPI_KEY_CMD_BRL_STOP (BRLAPI_KEY_CMD(0) + 114)
+/** start the braille driver */
+#define BRLAPI_KEY_CMD_BRL_START (BRLAPI_KEY_CMD(0) + 115)
+/** stop the speech driver */
+#define BRLAPI_KEY_CMD_SPK_STOP (BRLAPI_KEY_CMD(0) + 116)
+/** start the speech driver */
+#define BRLAPI_KEY_CMD_SPK_START (BRLAPI_KEY_CMD(0) + 117)
+/** stop the screen driver */
+#define BRLAPI_KEY_CMD_SCR_STOP (BRLAPI_KEY_CMD(0) + 118)
+/** start the screen driver */
+#define BRLAPI_KEY_CMD_SCR_START (BRLAPI_KEY_CMD(0) + 119)
+/** bind to the previous virtual terminal */
+#define BRLAPI_KEY_CMD_SELECTVT_PREV (BRLAPI_KEY_CMD(0) + 120)
+/** bind to the next virtual terminal */
+#define BRLAPI_KEY_CMD_SELECTVT_NEXT (BRLAPI_KEY_CMD(0) + 121)
+/** go backward to nearest non-blank braille window */
+#define BRLAPI_KEY_CMD_PRNBWIN (BRLAPI_KEY_CMD(0) + 122)
+/** go forward to nearest non-blank braille window */
+#define BRLAPI_KEY_CMD_NXNBWIN (BRLAPI_KEY_CMD(0) + 123)
+/** set touch navigation on/off */
+#define BRLAPI_KEY_CMD_TOUCH_NAV (BRLAPI_KEY_CMD(0) + 124)
+/** speak indent of current line */
+#define BRLAPI_KEY_CMD_SPEAK_INDENT (BRLAPI_KEY_CMD(0) + 125)
+/** set autospeak indent of current line on/off */
+#define BRLAPI_KEY_CMD_ASPK_INDENT (BRLAPI_KEY_CMD(0) + 126)
+/** refresh braille display */
+#define BRLAPI_KEY_CMD_REFRESH (BRLAPI_KEY_CMD(0) + 127)
+/** show various device status indicators */
+#define BRLAPI_KEY_CMD_INDICATORS (BRLAPI_KEY_CMD(0) + 128)
+/** clear the text selection */
+#define BRLAPI_KEY_CMD_TXTSEL_CLEAR (BRLAPI_KEY_CMD(0) + 129)
+/** select all of the text */
+#define BRLAPI_KEY_CMD_TXTSEL_ALL (BRLAPI_KEY_CMD(0) + 130)
+/** copy selected text to host clipboard */
+#define BRLAPI_KEY_CMD_HOST_COPY (BRLAPI_KEY_CMD(0) + 131)
+/** cut selected text to host clipboard */
+#define BRLAPI_KEY_CMD_HOST_CUT (BRLAPI_KEY_CMD(0) + 132)
+/** insert host clipboard text after screen cursor */
+#define BRLAPI_KEY_CMD_HOST_PASTE (BRLAPI_KEY_CMD(0) + 133)
+/** show the window title */
+#define BRLAPI_KEY_CMD_GUI_TITLE (BRLAPI_KEY_CMD(0) + 134)
+/** open the braille actions window */
+#define BRLAPI_KEY_CMD_GUI_BRL_ACTIONS (BRLAPI_KEY_CMD(0) + 135)
+/** go to the home screen */
+#define BRLAPI_KEY_CMD_GUI_HOME (BRLAPI_KEY_CMD(0) + 136)
+/** go back to the previous screen */
+#define BRLAPI_KEY_CMD_GUI_BACK (BRLAPI_KEY_CMD(0) + 137)
+/** open the device settings window */
+#define BRLAPI_KEY_CMD_GUI_DEV_SETTINGS (BRLAPI_KEY_CMD(0) + 138)
+/** open the device options window */
+#define BRLAPI_KEY_CMD_GUI_DEV_OPTIONS (BRLAPI_KEY_CMD(0) + 139)
+/** open the application list window */
+#define BRLAPI_KEY_CMD_GUI_APP_LIST (BRLAPI_KEY_CMD(0) + 140)
+/** open the application-specific menu */
+#define BRLAPI_KEY_CMD_GUI_APP_MENU (BRLAPI_KEY_CMD(0) + 141)
+/** open the application alerts window */
+#define BRLAPI_KEY_CMD_GUI_APP_ALERTS (BRLAPI_KEY_CMD(0) + 142)
+/** return to the active screen area */
+#define BRLAPI_KEY_CMD_GUI_AREA_ACTV (BRLAPI_KEY_CMD(0) + 143)
+/** switch to the previous screen area */
+#define BRLAPI_KEY_CMD_GUI_AREA_PREV (BRLAPI_KEY_CMD(0) + 144)
+/** switch to the next screen area */
+#define BRLAPI_KEY_CMD_GUI_AREA_NEXT (BRLAPI_KEY_CMD(0) + 145)
+/** move to the first item in the screen area */
+#define BRLAPI_KEY_CMD_GUI_ITEM_FRST (BRLAPI_KEY_CMD(0) + 146)
+/** move to the previous item in the screen area */
+#define BRLAPI_KEY_CMD_GUI_ITEM_PREV (BRLAPI_KEY_CMD(0) + 147)
+/** move to the next item in the screen area */
+#define BRLAPI_KEY_CMD_GUI_ITEM_NEXT (BRLAPI_KEY_CMD(0) + 148)
+/** move to the last item in the screen area */
+#define BRLAPI_KEY_CMD_GUI_ITEM_LAST (BRLAPI_KEY_CMD(0) + 149)
+/** decrease speaking pitch */
+#define BRLAPI_KEY_CMD_SAY_LOWER (BRLAPI_KEY_CMD(0) + 150)
+/** increase speaking pitch */
+#define BRLAPI_KEY_CMD_SAY_HIGHER (BRLAPI_KEY_CMD(0) + 151)
+/** speak from top of screen through bottom of screen */
+#define BRLAPI_KEY_CMD_SAY_ALL (BRLAPI_KEY_CMD(0) + 152)
+/** set contracted/computer braille */
+#define BRLAPI_KEY_CMD_CONTRACTED (BRLAPI_KEY_CMD(0) + 153)
+/** set six/eight dot computer braille */
+#define BRLAPI_KEY_CMD_COMPBRL6 (BRLAPI_KEY_CMD(0) + 154)
+/** reset preferences to defaults */
+#define BRLAPI_KEY_CMD_PREFRESET (BRLAPI_KEY_CMD(0) + 155)
+/** bring screen cursor to character */
+#define BRLAPI_KEY_CMD_ROUTE BRLAPI_KEY_CMD(1)
+/** start new clipboard at character */
+#define BRLAPI_KEY_CMD_CLIP_NEW BRLAPI_KEY_CMD(2)
+/** deprecated definition of CLIP_NEW - start new clipboard at character */
+#define BRLAPI_KEY_CMD_CUTBEGIN BRLAPI_KEY_CMD(2)
+/** append to clipboard from character */
+#define BRLAPI_KEY_CMD_CLIP_ADD BRLAPI_KEY_CMD(3)
+/** deprecated definition of CLIP_ADD - append to clipboard from character */
+#define BRLAPI_KEY_CMD_CUTAPPEND BRLAPI_KEY_CMD(3)
+/** rectangular copy to character */
+#define BRLAPI_KEY_CMD_COPY_RECT BRLAPI_KEY_CMD(4)
+/** deprecated definition of COPY_RECT - rectangular copy to character */
+#define BRLAPI_KEY_CMD_CUTRECT BRLAPI_KEY_CMD(4)
+/** linear copy to character */
+#define BRLAPI_KEY_CMD_COPY_LINE BRLAPI_KEY_CMD(5)
+/** deprecated definition of COPY_LINE - linear copy to character */
+#define BRLAPI_KEY_CMD_CUTLINE BRLAPI_KEY_CMD(5)
+/** switch to specific virtual terminal */
+#define BRLAPI_KEY_CMD_SWITCHVT BRLAPI_KEY_CMD(6)
+/** go up to nearest line with less indent than character */
+#define BRLAPI_KEY_CMD_PRINDENT BRLAPI_KEY_CMD(7)
+/** go down to nearest line with less indent than character */
+#define BRLAPI_KEY_CMD_NXINDENT BRLAPI_KEY_CMD(8)
+/** describe character */
+#define BRLAPI_KEY_CMD_DESCCHAR BRLAPI_KEY_CMD(9)
+/** place left end of braille window at character */
+#define BRLAPI_KEY_CMD_SETLEFT BRLAPI_KEY_CMD(10)
+/** remember current braille window position */
+#define BRLAPI_KEY_CMD_SETMARK BRLAPI_KEY_CMD(11)
+/** go to remembered braille window position */
+#define BRLAPI_KEY_CMD_GOTOMARK BRLAPI_KEY_CMD(12)
+/** go to selected line */
+#define BRLAPI_KEY_CMD_GOTOLINE BRLAPI_KEY_CMD(13)
+/** go up to nearest line with different character */
+#define BRLAPI_KEY_CMD_PRDIFCHAR BRLAPI_KEY_CMD(14)
+/** go down to nearest line with different character */
+#define BRLAPI_KEY_CMD_NXDIFCHAR BRLAPI_KEY_CMD(15)
+/** copy characters to clipboard */
+#define BRLAPI_KEY_CMD_CLIP_COPY BRLAPI_KEY_CMD(16)
+/** deprecated definition of CLIP_COPY - copy characters to clipboard */
+#define BRLAPI_KEY_CMD_COPYCHARS BRLAPI_KEY_CMD(16)
+/** append characters to clipboard */
+#define BRLAPI_KEY_CMD_CLIP_APPEND BRLAPI_KEY_CMD(17)
+/** deprecated definition of CLIP_APPEND - append characters to clipboard */
+#define BRLAPI_KEY_CMD_APNDCHARS BRLAPI_KEY_CMD(17)
+/** insert clipboard history entry after screen cursor */
+#define BRLAPI_KEY_CMD_PASTE_HISTORY BRLAPI_KEY_CMD(18)
+/** set text table */
+#define BRLAPI_KEY_CMD_SET_TEXT_TABLE BRLAPI_KEY_CMD(19)
+/** set attributes table */
+#define BRLAPI_KEY_CMD_SET_ATTRIBUTES_TABLE BRLAPI_KEY_CMD(20)
+/** set contraction table */
+#define BRLAPI_KEY_CMD_SET_CONTRACTION_TABLE BRLAPI_KEY_CMD(21)
+/** set keyboard table */
+#define BRLAPI_KEY_CMD_SET_KEYBOARD_TABLE BRLAPI_KEY_CMD(22)
+/** set language profile */
+#define BRLAPI_KEY_CMD_SET_LANGUAGE_PROFILE BRLAPI_KEY_CMD(23)
+/** bring screen cursor to line */
+#define BRLAPI_KEY_CMD_ROUTE_LINE BRLAPI_KEY_CMD(24)
+/** refresh braille line */
+#define BRLAPI_KEY_CMD_REFRESH_LINE BRLAPI_KEY_CMD(25)
+/** start text selection */
+#define BRLAPI_KEY_CMD_TXTSEL_START BRLAPI_KEY_CMD(26)
+/** set text selection */
+#define BRLAPI_KEY_CMD_TXTSEL_SET BRLAPI_KEY_CMD(27)
+/** bring speech cursor to character */
+#define BRLAPI_KEY_CMD_ROUTE_SPEECH BRLAPI_KEY_CMD(28)
+/** bind to specific virtual terminal */
+#define BRLAPI_KEY_CMD_SELECTVT BRLAPI_KEY_CMD(30)
+/** render an alert */
+#define BRLAPI_KEY_CMD_ALERT BRLAPI_KEY_CMD(31)
+/** type braille dots */
+#define BRLAPI_KEY_CMD_PASSDOTS BRLAPI_KEY_CMD(34)
+/** AT (set 2) keyboard scan code */
+#define BRLAPI_KEY_CMD_PASSAT BRLAPI_KEY_CMD(35)
+/** XT (set 1) keyboard scan code */
+#define BRLAPI_KEY_CMD_PASSXT BRLAPI_KEY_CMD(36)
+/** PS/2 (set 3) keyboard scan code */
+#define BRLAPI_KEY_CMD_PASSPS2 BRLAPI_KEY_CMD(37)
+/** switch to command context */
+#define BRLAPI_KEY_CMD_CONTEXT BRLAPI_KEY_CMD(38)
+/** current reading location */
+#define BRLAPI_KEY_CMD_TOUCH_AT BRLAPI_KEY_CMD(39)
+/** execute command macro */
+#define BRLAPI_KEY_CMD_MACRO BRLAPI_KEY_CMD(40)
+/** run host command */
+#define BRLAPI_KEY_CMD_HOSTCMD BRLAPI_KEY_CMD(41)
+/** enable feature */
+#define BRLAPI_KEY_FLG_TOGGLE_ON BRLAPI_KEY_FLG(0X0100)
+/** disable feature */
+#define BRLAPI_KEY_FLG_TOGGLE_OFF BRLAPI_KEY_FLG(0X0200)
+/** mask for all toggle flags */
+#define BRLAPI_KEY_FLG_TOGGLE_MASK (BRLAPI_KEY_FLG_TOGGLE_ON | BRLAPI_KEY_FLG_TOGGLE_OFF)
+/** bring screen cursor into braille window after function */
+#define BRLAPI_KEY_FLG_MOTION_ROUTE BRLAPI_KEY_FLG(0X0400)
+/** scale arg=0X00-0XFF to screen height */
+#define BRLAPI_KEY_FLG_MOTION_SCALED BRLAPI_KEY_FLG(0X0800)
+/** go to beginning of line */
+#define BRLAPI_KEY_FLG_MOTION_TOLEFT BRLAPI_KEY_FLG(0X1000)
+/** shift key pressed */
+#define BRLAPI_KEY_FLG_SHIFT BRLAPI_KEY_FLG(0X01)
+/** convert to uppercase */
+#define BRLAPI_KEY_FLG_UPPER BRLAPI_KEY_FLG(0X02)
+/** control key pressed */
+#define BRLAPI_KEY_FLG_CONTROL BRLAPI_KEY_FLG(0X04)
+/** meta (left alt) key pressed */
+#define BRLAPI_KEY_FLG_META BRLAPI_KEY_FLG(0X08)
+/** altgr (right alt) key pressed */
+#define BRLAPI_KEY_FLG_ALTGR BRLAPI_KEY_FLG(0X10)
+/** gui (windows) key pressed */
+#define BRLAPI_KEY_FLG_GUI BRLAPI_KEY_FLG(0X20)
+/** prefix with escape */
+#define BRLAPI_KEY_FLG_ESCAPED BRLAPI_KEY_FLG(0X40)
+/** it is a release scan code */
+#define BRLAPI_KEY_FLG_KBD_RELEASE BRLAPI_KEY_FLG(0X0100)
+/** it is an emulation 0 scan code */
+#define BRLAPI_KEY_FLG_KBD_EMUL0 BRLAPI_KEY_FLG(0X0200)
+/** it is an emulation 1 scan code */
+#define BRLAPI_KEY_FLG_KBD_EMUL1 BRLAPI_KEY_FLG(0X0400)
+/** upper-left dot of standard braille cell */
+#define BRLAPI_DOT1 1
+/** middle-left dot of standard braille cell */
+#define BRLAPI_DOT2 2
+/** lower-left dot of standard braille cell */
+#define BRLAPI_DOT3 4
+/** upper-right dot of standard braille cell */
+#define BRLAPI_DOT4 8
+/** middle-right dot of standard braille cell */
+#define BRLAPI_DOT5 16
+/** lower-right dot of standard braille cell */
+#define BRLAPI_DOT6 32
+/** lower-left dot of computer braille cell */
+#define BRLAPI_DOT7 64
+/** lower-right dot of computer braille cell */
+#define BRLAPI_DOT8 128
+/** chord (space bar on braille keyboard) */
+#define BRLAPI_DOTC 256
+
+/** Helper macro to easily produce braille patterns */
+#define BRLAPI_DOTS(dot1, dot2, dot3, dot4, dot5, dot6, dot7, dot8) (\
+  ((dot1)? BRLAPI_DOT1: 0) | \
+  ((dot2)? BRLAPI_DOT2: 0) | \
+  ((dot3)? BRLAPI_DOT3: 0) | \
+  ((dot4)? BRLAPI_DOT4: 0) | \
+  ((dot5)? BRLAPI_DOT5: 0) | \
+  ((dot6)? BRLAPI_DOT6: 0) | \
+  ((dot7)? BRLAPI_DOT7: 0) | \
+  ((dot8)? BRLAPI_DOT8: 0) \
+)
+
+/** space key */
+#define BRLAPI_DOT_CHORD 256
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_CONSTANTS */
diff --git a/Programs/brlapi_keycodes.h b/Programs/brlapi_keycodes.h
new file mode 100644
index 0000000..5dec391
--- /dev/null
+++ b/Programs/brlapi_keycodes.h
@@ -0,0 +1,234 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** \file
+ */
+
+#ifndef BRLAPI_INCLUDED_KEYCODES
+#define BRLAPI_INCLUDED_KEYCODES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** \defgroup brlapi_keycodes Types and Defines for \e BrlAPI Key Codes
+ *
+ * Key codes are unsigned 64 bit integers.  This 64-bit space is split into 3
+ * parts:
+ *
+ * - bits 63-32 (BRLAPI_KEY_FLAGS_MASK), flags: bits 39-32 are standard X
+ * modifiers (shift, control, meta, ...). Other flags are used for some commands,
+ * see documentation of BRLAPI_KEY_FLG_* for their respective uses.
+ * - bits 31-29 (BRLAPI_KEY_TYPE_MASK), key type: either BRLAPI_KEY_TYPE_CMD for
+ * braille commands, or BRLAPI_KEY_TYPE_SYM for standard X keysyms.
+ * - bits 28-0 (BRLAPI_KEY_CODE_MASK), key code: for braille commands, see
+ * BRLAPI_KEY_CMD_* ; for standard X keysyms, this is the keysym value, see
+ * X11 documentation, a complete list is probably available on your system in
+ * /usr/include/X11/keysymdef.h
+ *
+ * The second and third part are thus mandatory to tell the type of keycode and
+ * the value of the keycode, and the first part contains optional flags.
+ *
+ * The third part is itself split into two parts: a command number and a command
+ * value.  The relative sizes of these parts vary according to the key type.
+ *
+ * For a braille command, bits 28-16 (BRLAPI_KEY_CMD_BLK_MASK) hold the braille
+ * command number, while bits 15-0 (BRLAPI_KEY_CMD_ARG_MASK) hold the command
+ * value.
+ *
+ * The brlapi_expandKeyCode() function may be used for splitting key codes into
+ * these parts.
+ *
+ * For a X keysym, if it is a unicode keysym (0x1uvwxyz), then the command
+ * number part is 0x1000000 and the value part is 0xuvwxyz. Else, the command
+ * part is held by bits 28-8 and the value part is held by bits 7-0. This
+ * permits to easily handle usual cases like 0x00xy (latin1), 0x01xy (latin2),
+ * XK_Backspace (0xff08, backspace), XK_Tab (0xff09, tab), ...
+ *
+ * For instance, if key == 0x0000000020010008,
+ * - (key & BRLAPI_KEY_TYPE_MASK) == BRLAPI_KEY_TYPE_CMD, so it's a braille
+ * command
+ * - (key & BRLAPI_KEY_CMD_BLK_MASK) == BRLAPI_KEY_CMD_ROUTE, so it's the
+ * braille route command.
+ * - (key & BRLAPI_KEY_CMD_ARG_MASK) == 8, so the highlighted cell is the 9th
+ * one (cells are numbered from 0)
+ * - (key & BRLAPI_KEY_FLAGS_MASK) == 0, so no modifier key was pressed during
+ * the command, and no particular flag applies to the command.
+ *
+ * if key == 0x000000010000FF09,
+ * - (key & BRLAPI_KEY_TYPE_MASK) == BRLAPI_KEY_TYPE_SYM, so it's a keysym
+ * - (key & BRLAPI_KEY_CODE_MASK) == XK_Tab, so it's the tab key.
+ * BRLAPI_KEY_SYM_TAB can also be used here, as well as a few other
+ * BRLAPI_KEY_SYM_* constants which are provided to avoid having to include
+ * X11/keysymdef.h
+ * - (key & BRLAPI_KEY_FLAGS_MASK) == BRLAPI_KEY_FLG_SHIFT, so the shift
+ * modifier was pressed during the command.
+ *
+ * in the X11 standard some keysyms are directly unicode, for instance if
+ * key == 0x0000000001001EA0,
+ * - (key & BRLAPI_KEY_TYPE_MASK) == BRLAPI_KEY_TYPE_SYM, so it's a keysym
+ * - (key & BRLAPI_KEY_SYM_UNICODE) != 0 so it's a unicode keysym, whose value
+ * is key & (BRLAPI_KEY_SYM_UNICODE-1).  Of course, one can also consider
+ * (key & BRLAPI_KEY_CODE_MASK) == XK_Abelowdot
+ * - (key & BRLAPI_KEY_FLAGS_MASK) == 0, so no modifier key was pressed during
+ * the command, and no particular flag applies to the command.
+ *
+ * brlapi_ignoreKeyRanges() and brlapi_acceptKeyRanges() manipulate keycode
+ * ranges. They are composed of 2 keycodes: the "first" and the "last"
+ * boundaries. The range expressed by these two keycodes is the set of keycodes
+ * whose lower part (bits 31-0) is between the lower part of the "first" keycode
+ * and the "last" keycode (inclusive), and whose high part (bits 63-32), the
+ * flags, contains at least the flags of the "first" keycode, and at most the
+ * flags of the "last" keycode. Setting the "first" and "last" keycode to the
+ * same value express only one keycode, for instance. Setting the first and last
+ * keycode to the same command code but setting no flags in the "first" keycode
+ * and setting one flag in the "last" keycode expresses only two keycode, with
+ * the same lower part and no flags set except possibly the flag that is set in
+ * the "last" keycode. Setting one flag i in the "first" keycode and setting
+ * the same flag plus another flag j in the "last" keycode expresses that the
+ * keycodes in the range have flag i set and possibly flag j set, but no other
+ * flag.
+ * @{
+ */
+typedef uint64_t brlapi_keyCode_t;
+
+/** Define a brlapi_keyCode_t constant */
+#define BRLAPI_KEYCODE_C(value) UINT64_C(value)
+
+/** Hexadecimal print format for brlapi_keyCode_t */
+#define BRLAPI_PRIxKEYCODE PRIx64
+
+/** Unsigned decimal print format for brlapi_keyCode_t */
+#define BRLAPI_PRIuKEYCODE PRIu64
+
+/** Brlapi_keyCode_t's biggest value
+ *
+ * As defined in \c <stdint.h> */
+#define BRLAPI_KEY_MAX UINT64_C(0XFFFFFFFFFFFFFFFF)
+
+/**
+ * Mask for flags of brlapi_keyCode_t
+ */
+#define BRLAPI_KEY_FLAGS_MASK		UINT64_C(0XFFFFFFFF00000000)
+/** Shift for flags of brlapi_keyCode_t */
+#define BRLAPI_KEY_FLAGS_SHIFT		32
+
+#define BRLAPI_KEY_FLG(v)		((brlapi_keyCode_t)(v) << BRLAPI_KEY_FLAGS_SHIFT)
+/** Standard X modifiers */
+/** Mod1 modifier (AKA meta) */
+#define BRLAPI_KEY_FLG_MOD1		BRLAPI_KEY_FLG(0x00000008)
+/** Mod2 modifier (usually numlock) */
+#define BRLAPI_KEY_FLG_MOD2		BRLAPI_KEY_FLG(0x00000010)
+/** Mod3 modifier */
+#define BRLAPI_KEY_FLG_MOD3		BRLAPI_KEY_FLG(0x00000020)
+/** Mod4 modifier */
+#define BRLAPI_KEY_FLG_MOD4		BRLAPI_KEY_FLG(0x00000040)
+/** Mod5 modifier (usually Alt-Gr) */
+#define BRLAPI_KEY_FLG_MOD5		BRLAPI_KEY_FLG(0x00000080)
+
+
+/**
+ * Mask for type of brlapi_keyCode_t
+ */
+#define BRLAPI_KEY_TYPE_MASK		UINT64_C(0X00000000E0000000)
+/** Shift for type of brlapi_keyCode_t */
+#define BRLAPI_KEY_TYPE_SHIFT		29
+/** Braille command brlapi_keyCode_t */
+#define BRLAPI_KEY_TYPE_CMD		UINT64_C(0X0000000020000000)
+/** X Keysym brlapi_keyCode_t */
+#define BRLAPI_KEY_TYPE_SYM		UINT64_C(0X0000000000000000)
+
+/**
+ * Mask for code of brlapi_keyCode_t
+ */
+#define BRLAPI_KEY_CODE_MASK		UINT64_C(0X000000001FFFFFFF)
+/** Shift for code of brlapi_keyCode_t */
+#define BRLAPI_KEY_CODE_SHIFT		0
+
+/** Mask for braille command type */
+#define BRLAPI_KEY_CMD_BLK_MASK		UINT64_C(0X1FFF0000)
+/** Shift for braille command type */
+#define BRLAPI_KEY_CMD_BLK_SHIFT	16
+/** Mask for braille command value */
+#define BRLAPI_KEY_CMD_ARG_MASK		UINT64_C(0X0000FFFF)
+/** Shift for braille command value */
+#define BRLAPI_KEY_CMD_ARG_SHIFT	0
+#define BRLAPI_KEY_CMD(v)		((v) << BRLAPI_KEY_CMD_BLK_SHIFT)
+
+/** Standard X keysyms */
+#define BRLAPI_KEY_SYM_BACKSPACE	UINT64_C(0X0000FF08)
+#define BRLAPI_KEY_SYM_TAB		UINT64_C(0X0000FF09)
+#define BRLAPI_KEY_SYM_LINEFEED		UINT64_C(0X0000FF0D)
+#define BRLAPI_KEY_SYM_ESCAPE		UINT64_C(0X0000FF1B)
+#define BRLAPI_KEY_SYM_HOME		UINT64_C(0X0000FF50)
+#define BRLAPI_KEY_SYM_LEFT		UINT64_C(0X0000FF51)
+#define BRLAPI_KEY_SYM_UP		UINT64_C(0X0000FF52)
+#define BRLAPI_KEY_SYM_RIGHT		UINT64_C(0X0000FF53)
+#define BRLAPI_KEY_SYM_DOWN		UINT64_C(0X0000FF54)
+#define BRLAPI_KEY_SYM_PAGE_UP		UINT64_C(0X0000FF55)
+#define BRLAPI_KEY_SYM_PAGE_DOWN	UINT64_C(0X0000FF56)
+#define BRLAPI_KEY_SYM_END		UINT64_C(0X0000FF57)
+#define BRLAPI_KEY_SYM_INSERT		UINT64_C(0X0000FF63)
+#define BRLAPI_KEY_SYM_FUNCTION		UINT64_C(0X0000FFBE)
+#define BRLAPI_KEY_SYM_DELETE		UINT64_C(0X0000FFFF)
+#define BRLAPI_KEY_SYM_UNICODE		UINT64_C(0X01000000)
+
+/**
+ * When brlapi_enterTtyMode() or brlapi_entg$erTtyModeWithPath() is called
+ * with a driver name, brlapi_readKey() and brlapi_readKeyWithTimeout()
+ * will return driver-specific key codes. From most- to least-significant,
+ * their eight bytes are: F 0 0 0 0 0 G N. F is a byte that contains flag
+ * bits which are common for all drivers. BRLAPI_DRV_KEY_PRESS, which
+ * indicates that it's a key press (as opposed to a release) event, is the
+ * only currently defined flag. The other flag bits are always 0.
+ * G is the key's group, and N is the key's number within that group.
+ */
+
+/** Flag for a driver-specific keycode press (not set means a release) */
+#define BRLAPI_DRV_KEY_PRESS BRLAPI_KEYCODE_C(0X8000000000000000)
+
+/** Shift for key number of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_NUMBER_SHIFT 0
+/** Mask for key number of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_NUMBER_MASK 0XFF
+/** Get key number of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_NUMBER(code) (((code) & BRLAPI_DRV_KEY_NUMBER_MASK) >> BRLAPI_DRV_KEY_NUMBER_SHIFT)
+
+/** Shift for key group of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_GROUP_SHIFT 8
+/** Mask for key group of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_GROUP_MASK 0XFF00
+/** Get key group of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_GROUP(code) (((code) & BRLAPI_DRV_KEY_GROUP_MASK) >> BRLAPI_DRV_KEY_GROUP_SHIFT)
+
+/** Mask for key value (group and number) of brlapi_keyCode_t */
+#define BRLAPI_DRV_KEY_VALUE_MASK (BRLAPI_DRV_KEY_GROUP_MASK | BRLAPI_DRV_KEY_NUMBER_MASK)
+
+/** Key number representing any key in the group */
+#define BRLAPI_DRV_KEY_NUMBER_ANY 0XFF
+
+/** @} */
+
+#include "brlapi_constants.h"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_KEYCODES */
diff --git a/Programs/brlapi_keyranges.c b/Programs/brlapi_keyranges.c
new file mode 100644
index 0000000..f885169
--- /dev/null
+++ b/Programs/brlapi_keyranges.c
@@ -0,0 +1,210 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+/* Source file for range list management module */
+/* For a description of what each function does, see rangelist.h */
+
+#include <stdio.h>
+
+#include "brlapi_keyranges.h"
+#include "log.h"
+
+static int inKeyrange(KeyrangeList *l, KeyrangeElem e)
+{
+  uint32_t flags = KeyrangeFlags(e);
+  uint32_t val = KeyrangeVal(e);
+  return (l->minVal <= val && val <= l->maxVal && (flags | l->minFlags) == flags && ((flags & ~l->maxFlags) == 0));
+}
+
+/* Function : createKeyrange */
+static KeyrangeList **createKeyrange(KeyrangeList **p, uint32_t minFlags, uint32_t minVal, uint32_t maxFlags, uint32_t maxVal, KeyrangeList *n)
+{
+  KeyrangeList *c = malloc(sizeof(KeyrangeList));
+  if (c==NULL) return NULL;
+  c->minFlags = minFlags; c->minVal = minVal;
+  c->maxFlags = maxFlags; c->maxVal = maxVal;
+  c->next = n;
+  *p = c;
+  return &c->next;
+}
+
+/* Function : freeKeyrange */
+static void freeKeyrange(KeyrangeList **p, KeyrangeList *c)
+{
+  if (c==NULL) return;
+  *p = c->next;
+  free(c);
+}
+
+/* Function : freeKeyrangeList */
+void freeKeyrangeList(KeyrangeList **l)
+{
+  KeyrangeList *p1, *p2;
+  if (l==NULL) return;
+  p2 = *l;
+  while (p2!=NULL) {
+    p1 = p2;
+    p2 = p1->next;
+    free(p1);
+  }
+  *l = NULL;
+}
+
+/* Function : inKeyrangeList */
+KeyrangeList *inKeyrangeList(KeyrangeList *l, KeyrangeElem n)
+{
+  KeyrangeList *c = l;
+  while (c!=NULL) {
+    if (inKeyrange(c, n)) return c;
+    c = c->next;
+  }
+  return NULL;
+}
+
+/* Function : DisplayKeyrangeList */
+void DisplayKeyrangeList(KeyrangeList *l)
+{
+  if (l==NULL) printf("emptyset");
+  else {
+    KeyrangeList *c = l;
+    while (1) {
+      printf("[%lx(%lx)..%lx(%lx)]",(unsigned long)c->minVal,(unsigned long)c->minFlags,(unsigned long)c->maxVal,(unsigned long)c->maxFlags);
+      if (c->next==NULL) break;
+      printf(",");
+      c = c->next;
+    }
+  }
+  printf("\n");
+}
+
+/* Function : addKeyrange */
+int addKeyrange(KeyrangeElem x0, KeyrangeElem y0, KeyrangeList **l)
+{
+  KeyrangeList *c;
+  uint32_t minFlags = KeyrangeFlags(x0) & KeyrangeFlags(y0);
+  uint32_t maxFlags = KeyrangeFlags(x0) | KeyrangeFlags(y0);
+  uint32_t minVal   = MIN(KeyrangeVal(x0), KeyrangeVal(y0));
+  uint32_t maxVal   = MAX(KeyrangeVal(x0), KeyrangeVal(y0));
+  KeyrangeElem min = KeyrangeElem(minFlags, minVal);
+  KeyrangeElem max = KeyrangeElem(maxFlags, maxVal);
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS) | LOG_DEBUG,
+    "adding range [%"PRIx32"(%"PRIx32")..%"PRIx32"(%"PRIx32")]",
+    minVal, minFlags, maxVal, maxFlags
+  );
+
+  c = *l;
+  while (c) {
+    if (inKeyrange(c, min) && inKeyrange(c, max))
+      /* Falls completely within an existing range */
+      return 0;
+
+    if (c->minVal <= maxVal && maxVal <= c->maxVal && minFlags == c->minFlags && maxFlags == c->maxFlags) {
+      /* May just change lower bound */
+      /* Note that minVal can't be >= c->minVal */
+      c->minVal = minVal;
+      return 0;
+    }
+
+    if (c->minVal <= minVal && minVal <= c->maxVal && minFlags == c->minFlags && maxFlags == c->maxFlags) {
+      /* May just change upper bound */
+      /* Note that maxVal can't be <= c->maxVal */
+      c->maxVal = maxVal;
+      return 0;
+    }
+
+    c = c->next;
+  }
+
+  /* Else things are not easy, just add */
+  if ((createKeyrange(l,minFlags,minVal,maxFlags,maxVal,*l)) == NULL) return -1;
+  return 0;
+}
+
+int removeKeyrange(KeyrangeElem x0, KeyrangeElem y0, KeyrangeList **l)
+{
+  uint32_t minFlags = KeyrangeFlags(x0) & KeyrangeFlags(y0);
+  uint32_t maxFlags = KeyrangeFlags(x0) | KeyrangeFlags(y0);
+  uint32_t minVal   = MIN(KeyrangeVal(x0), KeyrangeVal(y0));
+  uint32_t maxVal   = MAX(KeyrangeVal(x0), KeyrangeVal(y0));
+  KeyrangeList *c, **p, *tmp;
+
+  if ((l==NULL) || (*l==NULL)) return 0;
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS) | LOG_DEBUG,
+    "removing range [%"PRIx32"(%"PRIx32")..%"PRIx32"(%"PRIx32")]",
+    minVal, minFlags, maxVal, maxFlags
+  );
+
+  /* Need to intersect with every range */
+  p = l; c = *p;
+  while (c) {
+    if (c->minVal > maxVal || c->maxVal < minVal ||
+        !(c->maxFlags | ~minFlags) || !(~c->minFlags | maxFlags)) {
+      /* don't intersect */
+      p = &c->next;
+      c = *p;
+      continue;
+    }
+
+    if (minVal <= c->minVal && maxVal >= c->maxVal &&
+        (c->minFlags | minFlags) == c->minFlags &&
+        (c->maxFlags & ~maxFlags) == 0) {
+      /* range falls completely in deletion range, just drop it */
+      tmp = c; c = c->next;
+      freeKeyrange(p,tmp);
+      continue;
+    }
+
+    /* Partly intersect */
+
+    if (c->minVal < minVal) {
+      /* lower part should be kept intact, save it. */
+      p = createKeyrange(p, c->minFlags, c->minVal, c->maxFlags, minVal - 1, c);
+      if (p == NULL) return -1;
+      c->minVal = minVal;
+    }
+
+    if (c->maxVal > maxVal) {
+      /* upper part should be kept intact, save it. */
+      p = createKeyrange(p, c->minFlags, maxVal + 1, c->maxFlags, c->maxVal, c);
+      if (p == NULL) return -1;
+      c->maxVal = maxVal;
+    }
+
+    /* Now values are contained in suppression, intersect against flags */
+
+    if (~minFlags & maxFlags) {
+      /* At least some flag must now be neither cleared nor set, drop range */
+      tmp = c; c = c->next;
+      freeKeyrange(p,tmp);
+      continue;
+    }
+
+    /* Clamp flags on the value interval */
+    c->minFlags |= ~maxFlags;
+    c->maxFlags &= ~minFlags;
+
+    p = &c->next;
+    c = *p;
+  }
+
+  return 0;
+}
diff --git a/Programs/brlapi_keyranges.h b/Programs/brlapi_keyranges.h
new file mode 100644
index 0000000..c9fcf37
--- /dev/null
+++ b/Programs/brlapi_keyranges.h
@@ -0,0 +1,76 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_KEYRANGES
+#define BRLAPI_INCLUDED_KEYRANGES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Header file for the range list management module */
+
+#include "prologue.h"
+
+typedef uint64_t KeyrangeElem;
+
+#define KeyrangeFlags(v) (((v) >> 32) & 0xffffffffull)
+#define KeyrangeVal(v) ((v) & 0xffffffffull)
+
+#define KeyrangeElem(flags,val) (((KeyrangeElem)(flags) << 32) | (val))
+
+
+typedef struct KeyrangeList {
+  uint32_t minFlags, maxFlags;
+  uint32_t minVal, maxVal;
+  struct KeyrangeList *next;
+} KeyrangeList;
+
+/* Function : freeKeyrangeList */
+/* Frees a whole list */
+/* If you want to destroy a whole list, call this function, rather than */
+/* calling freeKeyrange on each element, since th latter cares about links */
+/* and hence is slower */
+extern void freeKeyrangeList(KeyrangeList **l);
+
+/* Function : inKeyrangeList */
+/* Determines if the range list l contains x */
+/* If yes, returns the adress of the cell [a..b] such that a<=x<=b */
+/* If no, returns NULL */
+extern KeyrangeList *inKeyrangeList(KeyrangeList *l, KeyrangeElem n);
+
+/* Function : displayKeyrangeList */
+/* Prints a range list on stdout */
+/* This is for debugging only */
+extern void displayKeyrangeList(KeyrangeList *l);
+
+/* Function : addKeyrange */
+/* Adds a range to a range list */
+/* Return 0 if success, -1 if an error occurs */
+extern int addKeyrange(KeyrangeElem x0, KeyrangeElem y0, KeyrangeList **l);
+
+/* Function : removeKeyrange */
+/* Removes a range from a range list */
+/* Returns 0 if success, -1 if failure */
+extern int removeKeyrange(KeyrangeElem x0, KeyrangeElem y0, KeyrangeList **l);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_KEYRANGES */
diff --git a/Programs/brlapi_keytab.awk b/Programs/brlapi_keytab.awk
new file mode 100644
index 0000000..74d90af
--- /dev/null
+++ b/Programs/brlapi_keytab.awk
@@ -0,0 +1,62 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+BEGIN {
+  writeHeaderPrologue("BRLAPI_INCLUDED_KEYTAB", "api.h")
+}
+
+END {
+  writeHeaderEpilogue()
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeSimpleKey("CMD", name)
+}
+
+function brlBlock(name, symbol, value, help) {
+  if (name == "PASSCHAR") return
+  if (name == "PASSKEY") return
+  writeSimpleKey("CMD", name)
+}
+
+function brlKey(name, symbol, value, help) {
+  if (name == "FUNCTION") {
+    for (functionNumber=1; functionNumber<=35; ++functionNumber) {
+      writeComplexKey("F" functionNumber, "BRLAPI_KEY_SYM_FUNCTION+" (functionNumber - 1))
+    }
+  } else {
+    writeSimpleKey("SYM", getBrlapiKeyName(name))
+  }
+}
+
+function brlFlag(name, symbol, value, help) {
+}
+
+function brlDot(number, symbol, value, help) {
+}
+
+function writeSimpleKey(type, name) {
+  writeComplexKey(name, "(BRLAPI_KEY_TYPE_" type " | BRLAPI_KEY_" type "_" name ")")
+}
+
+function writeComplexKey(name, code) {
+  print "{"
+  print "  .name = \"" name "\","
+  print "  .code = " code
+  print "},"
+}
diff --git a/Programs/brlapi_param.h b/Programs/brlapi_param.h
new file mode 100644
index 0000000..03acfeb
--- /dev/null
+++ b/Programs/brlapi_param.h
@@ -0,0 +1,328 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** \file
+ */
+
+#ifndef BRLAPI_INCLUDED_PARAM
+#define BRLAPI_INCLUDED_PARAM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "brlapi_keycodes.h"
+
+/** \ingroup brlapi_parameterManagement
+ *
+ * @{ */
+
+typedef enum {
+//Connection Parameters
+  BRLAPI_PARAM_SERVER_VERSION = 0,		/**< Version of the server: uint32_t */
+  BRLAPI_PARAM_CLIENT_PRIORITY = 1,		/**< Priority of the client: uint32_t (from 0 through 100, default is 50) */
+
+//Device Parameters
+  BRLAPI_PARAM_DRIVER_NAME = 2,			/**< Full name of the driver: string */
+  BRLAPI_PARAM_DRIVER_CODE = 3,			/**< Code (short name) of the driver: string */
+  BRLAPI_PARAM_DRIVER_VERSION = 4,		/**< Version of the driver: string */
+  BRLAPI_PARAM_DEVICE_MODEL = 5,		/**< Model of the device: string */
+  BRLAPI_PARAM_DEVICE_CELL_SIZE = 31,		/**< Number of dots in a cell: uint8_t */
+  BRLAPI_PARAM_DISPLAY_SIZE = 6,		/**< Dimensions of the braille display: { uint32_t columns; uint32_t rows; } */
+  BRLAPI_PARAM_DEVICE_IDENTIFIER = 7,		/**< Identifier of the device: string */
+  BRLAPI_PARAM_DEVICE_SPEED = 8,		/**< Speed of the device: uint32_t */
+  BRLAPI_PARAM_DEVICE_ONLINE = 9,		/**< Device is online: boolean */
+/* TODO: status area */
+
+//Input Parameters
+  BRLAPI_PARAM_RETAIN_DOTS = 10,		/**< Pass dot combinations (rather than characters): boolean */
+
+//Braille Rendering Parameters
+  BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE = 11,	/**< Number of dots used to render a computer braille character: uint8_t (8 or 6) */
+  BRLAPI_PARAM_LITERARY_BRAILLE = 12,		/**< Whether braille is literary (rather than computer): boolean */
+  BRLAPI_PARAM_CURSOR_DOTS = 13,		/**< Representation of the cursor: uint8_t (ISO 11548-1) */
+  BRLAPI_PARAM_CURSOR_BLINK_PERIOD = 14,	/**< Blinking period of the cursor: uint32_t (milliseconds) */
+  BRLAPI_PARAM_CURSOR_BLINK_PERCENTAGE = 15,	/**< Portion of the blinking period that the cursor is visible: uint8_t (from 0 through 100) */
+  BRLAPI_PARAM_RENDERED_CELLS = 16,		/**< Cells rendered by the client: uint8_t[] (ISO 11548-1), one cell per element */
+
+//Navigation Parameters
+  BRLAPI_PARAM_SKIP_IDENTICAL_LINES = 17,	/**< Whether to skip identical screen lines: boolean */
+  BRLAPI_PARAM_AUDIBLE_ALERTS = 18,		/**< Whether to use audible alerts: boolean */
+
+//Clipboard Parameters
+  BRLAPI_PARAM_CLIPBOARD_CONTENT = 19,		/**< Content of the clipboard: UTF-8 string */
+
+//TTY Mode Parameters
+  BRLAPI_PARAM_BOUND_COMMAND_KEYCODES = 20,	/**< Commands bound by the driver:
+						  * uint64_t[], one command keycode per element */
+  BRLAPI_PARAM_COMMAND_KEYCODE_NAME = 21,	/**< Name for a command keycode
+						  * (specified via the subparam argument):
+						  * string (usually a few characters) */
+  BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY = 22,	/**< Description for a command keycode
+						  * (specified via the subparam argument):
+						  * string (usually a few words) */
+  BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES = 23,	/**< Keycodes defined by the driver:
+						  * uint64_t[], one keycode per element */
+  BRLAPI_PARAM_DRIVER_KEYCODE_NAME = 24,	/**< Name for a driver keycode
+						  * (specified via the subparam argument):
+						  * string (usually a few characters) */
+  BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY = 25,	/**< Description for a driver keycode
+						  * (specified via the subparam argument):
+						  * string (usually a few words) */
+
+//Braille Translation Parameters
+  BRLAPI_PARAM_COMPUTER_BRAILLE_ROWS_MASK = 26,	/**< Set of Unicode rows that are defined for computer braille
+						  * (from U+0000 through U+10FFFF):
+						  * uint8_t[544], one bit per row, eight rows per element */
+  BRLAPI_PARAM_COMPUTER_BRAILLE_ROW_CELLS = 27,	/**< Computer braille cells for a Unicode row
+						  * (specified via the subparam argument):
+						  * uint8_t[256] (ISO 11548-1), one cell per element */
+  BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE = 28,	/**< Name of the computer braille table: string */
+  BRLAPI_PARAM_LITERARY_BRAILLE_TABLE = 29,	/**< Name of the literary braille table: string */
+  BRLAPI_PARAM_MESSAGE_LOCALE = 30,		/**< Locale to use for messages: string */
+/* TODO: dot-to-unicode as well */
+
+ /* TODO: help strings */
+
+  BRLAPI_PARAM_COUNT = 32 /** Number of parameters */
+} brlapi_param_t;
+
+/* brlapi_param_subparam_t */
+/** Type to be used for specifying a sub-parameter */
+typedef uint64_t brlapi_param_subparam_t;
+
+/* brlapi_param_bool_t */
+/** Type to be used for boolean parameters */
+typedef uint8_t brlapi_param_bool_t;
+
+/* brlapi_param_serverVersion_t */
+/** Type to be used for BRLAPI_PARAM_SERVER_VERSION */
+typedef uint32_t brlapi_param_serverVersion_t;
+
+/* brlapi_param_clientPriority_t */
+/** Type to be used for BRLAPI_PARAM_CLIENT_PRIORITY */
+typedef uint32_t brlapi_param_clientPriority_t;
+
+/* BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT */
+/** Default value for BRLAPI_PARAM_CLIENT_PRIORITY */
+#define BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT 50
+
+/* BRLAPI_PARAM_CLIENT_PRIORITY_DISABLE */
+/** Value for BRLAPI_PARAM_CLIENT_PRIORITY which actually disables input and
+ * output */
+#define BRLAPI_PARAM_CLIENT_PRIORITY_DISABLE 0
+
+/* brlapi_param_driverName_t */
+/** Type to be used for BRLAPI_PARAM_DRIVER_NAME */
+typedef char *brlapi_param_driverName_t;
+
+/* brlapi_param_driverCode_t */
+/** Type to be used for BRLAPI_PARAM_DRIVER_CODE */
+typedef char *brlapi_param_driverCode_t;
+
+/* brlapi_param_driverVersion_t */
+/** Type to be used for BRLAPI_PARAM_DRIVER_VERSION */
+typedef char *brlapi_param_driverVersion_t;
+
+/* brlapi_param_deviceModel_t */
+/** Type to be used for BRLAPI_PARAM_DEVICE_MODEL */
+typedef char *brlapi_param_deviceModel_t;
+
+/* brlapi_param_deviceCellSize_t */
+/** Type to be used for BRLAPI_PARAM_DEVICE_CELL_SIZE */
+typedef uint8_t brlapi_param_deviceCellSize_t;
+
+/* brlapi_param_displaySize_t */
+/** Type to be used for BRLAPI_PARAM_DISPLAY_SIZE */
+typedef struct {
+  uint32_t columns;
+  uint32_t rows;
+} brlapi_param_displaySize_t;
+
+/* brlapi_param_deviceIdentifier_t */
+/** Type to be used for BRLAPI_PARAM_DEVICE_IDENTIFIER */
+typedef char *brlapi_param_deviceIdentifier_t;
+
+/* brlapi_param_deviceSpeed_t */
+/** Type to be used for BRLAPI_PARAM_DEVICE_SPEED */
+typedef uint32_t brlapi_param_deviceSpeed_t;
+
+/* brlapi_param_deviceOnline_t */
+/** Type to be used for BRLAPI_PARAM_DEVICE_ONLINE */
+typedef brlapi_param_bool_t brlapi_param_deviceOnline_t;
+
+/* brlapi_param_retainDots_t */
+/** Type to be used for BRLAPI_PARAM_RETAIN_DOTS */
+typedef brlapi_param_bool_t brlapi_param_retainDots_t;
+
+/* brlapi_param_computerBrailleCellSize_t */
+/** Type to be used for BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE */
+typedef uint8_t brlapi_param_computerBrailleCellSize_t;
+
+/* brlapi_param_literaryBraille_t */
+/** Type to be used for BRLAPI_PARAM_LITERARY_BRAILLE */
+typedef brlapi_param_bool_t brlapi_param_literaryBraille_t;
+
+/* brlapi_param_cursorDots_t */
+/** Type to be used for BRLAPI_PARAM_CURSOR_DOTS */
+typedef uint8_t brlapi_param_cursorDots_t;
+
+/* brlapi_param_cursorBlinkPeriod_t */
+/** Type to be used for BRLAPI_PARAM_CURSOR_BLINK_PERIOD */
+typedef uint32_t brlapi_param_cursorBlinkPeriod_t;
+
+/* brlapi_param_cursorBlinkPercentage_t */
+/** Type to be used for BRLAPI_PARAM_CURSOR_BLINK_PERCENTAGE */
+typedef uint8_t brlapi_param_cursorBlinkPercentage_t;
+
+/* brlapi_param_renderedCells_t */
+/** Type to be used for BRLAPI_PARAM_RENDERED_CELLS */
+typedef uint8_t *brlapi_param_renderedCells_t;
+
+/* brlapi_param_skipIdenticalLines_t */
+/** Type to be used for BRLAPI_PARAM_SKIP_IDENTICAL_LINES */
+typedef brlapi_param_bool_t brlapi_param_skipIdenticalLines_t;
+
+/* brlapi_param_audibleAlerts_t */
+/** Type to be used for BRLAPI_PARAM_AUDIBLE_ALERTS */
+typedef brlapi_param_bool_t brlapi_param_audibleAlerts_t;
+
+/* brlapi_param_clipboardContent_t */
+/** Type to be used for BRLAPI_PARAM_CLIPBOARD_CONTENT */
+typedef char *brlapi_param_clipboardContent_t;
+
+/* brlapi_param_commandKeycode_t */
+/** Type to be used for BRLAPI_PARAM_BOUND_COMMAND_KEYCODES */
+typedef brlapi_keyCode_t brlapi_param_commandKeycode_t;
+
+/* brlapi_param_commandKeycodeName_t */
+/** Type to be used for BRLAPI_PARAM_COMMAND_KEYCODE_NAME */
+typedef char *brlapi_param_commandKeycodeName_t;
+
+/* brlapi_param_commandKeycodeSummary_t */
+/** Type to be used for BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY */
+typedef char *brlapi_param_commandKeycodeSummary_t;
+
+/* brlapi_param_driverKeycode_t */
+/** Type to be used for BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES */
+typedef brlapi_keyCode_t brlapi_param_driverKeycode_t;
+
+/* brlapi_param_driverKeycodeName_t */
+/** Type to be used for BRLAPI_PARAM_DRIVER_KEYCODE_NAME */
+typedef char *brlapi_param_driverKeycodeName_t;
+
+/* brlapi_param_driverKeycodeSummary_t */
+/** Type to be used for BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY */
+typedef char *brlapi_param_driverKeycodeSummary_t;
+
+/* brlapi_param_computerBrailleRowsMask_t */
+/** Type to be used for BRLAPI_PARAM_COMPUTER_BRAILLE_ROWS_MASK */
+typedef uint8_t brlapi_param_computerBrailleRowsMask_t[544];
+
+/* brlapi_param_computerBrailleRowCells_t */
+/** Type to be used for BRLAPI_PARAM_COMPUTER_BRAILLE_ROW_CELLS */
+typedef struct {
+  uint8_t cells[0X100];
+  uint8_t defined[0X100 / 8];
+} brlapi_param_computerBrailleRowCells_t;
+
+/* brlapi_param_computerBrailleTable_t */
+/** Type to be used for BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE */
+typedef char *brlapi_param_computerBrailleTable_t;
+
+/* brlapi_param_literaryBrailleTable_t */
+/** Type to be used for BRLAPI_PARAM_LITERARY_BRAILLE_TABLE */
+typedef char *brlapi_param_literaryBrailleTable_t;
+
+/* brlapi_param_messageLocale_t */
+/** Type to be used for BRLAPI_PARAM_MESSAGE_LOCALE      */
+typedef char *brlapi_param_messageLocale_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_BOUND_COMMAND_KEYCODES */
+#define BRLAPI_PARAM_BOUND_COMMAND_CODES BRLAPI_PARAM_BOUND_COMMAND_KEYCODES
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_commandKeycode_t */
+typedef brlapi_param_commandKeycode_t brlapi_param_commandCode_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_COMMAND_KEYCODE_NAME */
+#define BRLAPI_PARAM_COMMAND_SHORT_NAME BRLAPI_PARAM_COMMAND_KEYCODE_NAME
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_commandKeycodeName_t */
+typedef brlapi_param_commandKeycodeName_t brlapi_param_commandShortName_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY */
+#define BRLAPI_PARAM_COMMAND_LONG_NAME BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_commandKeycodeSummary_t */
+typedef brlapi_param_commandKeycodeSummary_t brlapi_param_commandLongName_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES */
+#define BRLAPI_PARAM_DEVICE_KEY_CODES BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_driverKeycode_t */
+typedef brlapi_param_driverKeycode_t brlapi_param_keyCode_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_DRIVER_KEYCODE_NAME */
+#define BRLAPI_PARAM_KEY_SHORT_NAME BRLAPI_PARAM_DRIVER_KEYCODE_NAME
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_driverKeycodeName_t */
+typedef brlapi_param_driverKeycodeName_t brlapi_param_keyShortName_t;
+
+/** Deprecated in BRLTTY-6.2 - use BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY */
+#define BRLAPI_PARAM_KEY_LONG_NAME BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY
+/** Deprecated in BRLTTY-6.2 - use brlapi_param_driverKeycodeSummary_t */
+typedef brlapi_param_driverKeycodeSummary_t brlapi_param_keyLongName_t;
+
+/** Enumeration of parameter value types */
+typedef enum {
+  BRLAPI_PARAM_TYPE_STRING,	/**< Parameter is a string of UTF-8 characters */
+  BRLAPI_PARAM_TYPE_BOOLEAN,	/**< Parameter is one or more booleans represented by a uint8_t */
+  BRLAPI_PARAM_TYPE_UINT8,	/**< Parameter is one or more 8-bit unsigned integers */
+  BRLAPI_PARAM_TYPE_UINT16,	/**< Parameter is one or more 16-bit unsigned integers */
+  BRLAPI_PARAM_TYPE_UINT32,	/**< Parameter is one or more 32-bit unsigned integers */
+  BRLAPI_PARAM_TYPE_UINT64,	/**< Parameter is one or more 64-bit unsigned integers */
+  BRLAPI_PARAM_TYPE_KEYCODE = BRLAPI_PARAM_TYPE_UINT64,	/**< Parameter is one or more key codes */
+} brlapi_param_type_t;
+
+/** Structure that describes the properties of a parameter */
+typedef struct {
+  brlapi_param_type_t type;	/**< Type of the parameter's value */
+  uint16_t arraySize;		/**< If .isArray is true, the number of elements in the parameter's value;
+				  * if .isArray is false then the number of elements in the parameter's value is always exactly one */
+  uint16_t isArray:1;		/**< True if the parameter's value contains several values;
+				  *< False means always axactly one */
+  uint16_t canRead:1;		/**< True if the parameter is readable */
+  uint16_t canWrite:1;		/**< True if the parameter is writable */
+  uint16_t canWatch:1;		/**< True if the parameter can be watched */
+  uint16_t abiPadding1:4;
+  uint16_t hasSubparam:1;	/**< whether the Parameter uses the subparam argument */
+} brlapi_param_properties_t;
+
+/** Enumeration of parameter types */
+/* brlapi_getParameterProperties */
+/** Return a description of the properties of a parameter
+ *
+ * \param parameter is the parameter whose properties describion shall be returned.
+ *
+ * \return a pointer to the description of the properties of the parameter.
+ */
+extern const brlapi_param_properties_t *brlapi_getParameterProperties(brlapi_param_t parameter);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_PARAM */
diff --git a/Programs/brlapi_protocol.h b/Programs/brlapi_protocol.h
new file mode 100644
index 0000000..820ba46
--- /dev/null
+++ b/Programs/brlapi_protocol.h
@@ -0,0 +1,309 @@
+/*
+ * libbrlapi - A library providing access to braille terminals for applications.
+ *
+ * Copyright (C) 2002-2023 by
+ *   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *   Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+ *
+ * libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/** \file
+ * \brief types and constants for \e BrlAPI's protocol
+ */
+
+#ifndef BRLAPI_INCLUDED_PROTOCOL
+#define BRLAPI_INCLUDED_PROTOCOL
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "brlapi.h"
+
+/* this is for UINT32_MAX */
+#include <inttypes.h>
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif /* UINT32_MAX */
+
+/* The type size_t is defined there! */
+#include <unistd.h>
+
+/** \defgroup brlapi_protocol BrlAPI's protocol
+ * \brief Instructions and constants for \e BrlAPI 's protocol
+ *
+ * These are defines for the protocol between \e BrlAPI 's server and clients.
+ * Understanding is not needed to use the \e BrlAPI library, so reading this
+ * is not needed unless really wanting to connect to \e BrlAPI without
+ * \e BrlAPI 's library.
+ *
+ * @{ */
+
+#define BRLAPI_PROTOCOL_VERSION ((uint32_t) 8) /** Communication protocol version */
+
+/** Maximum packet size for packets exchanged on sockets and with braille
+ * terminal */
+#define BRLAPI_MAXPACKETSIZE 4096
+
+#define BRLAPI_PACKET_VERSION         'v'   /**< Version                     */
+#define BRLAPI_PACKET_AUTH            'a'   /**< Authorization               */
+#define BRLAPI_PACKET_GETDRIVERNAME   'n'   /**< Ask which driver is used    */
+#define BRLAPI_PACKET_GETMODELID      'd'   /**< Ask which model is used     */
+#define BRLAPI_PACKET_GETDISPLAYSIZE  's'   /**< Dimensions of brl display   */
+#define BRLAPI_PACKET_ENTERTTYMODE    't'   /**< Asks for a specified tty    */
+#define BRLAPI_PACKET_SETFOCUS        'F'   /**< Set current tty focus       */
+#define BRLAPI_PACKET_LEAVETTYMODE    'L'   /**< Release the tty             */
+#define BRLAPI_PACKET_KEY             'k'   /**< Braille key                 */
+#define BRLAPI_PACKET_IGNOREKEYRANGES 'm'   /**< Mask key ranges             */
+#define BRLAPI_PACKET_ACCEPTKEYRANGES 'u'   /**< Unmask key ranges           */
+#define BRLAPI_PACKET_WRITE           'w'   /**< Write                       */
+#define BRLAPI_PACKET_ENTERRAWMODE    '*'   /**< Enter in raw mode           */
+#define BRLAPI_PACKET_LEAVERAWMODE    '#'   /**< Leave raw mode              */
+#define BRLAPI_PACKET_PACKET          'p'   /**< Raw packets                 */
+#define BRLAPI_PACKET_ACK             'A'   /**< Acknowledgement             */
+#define BRLAPI_PACKET_ERROR           'e'   /**< non-fatal error             */
+#define BRLAPI_PACKET_EXCEPTION       'E'   /**< Exception                   */
+#define BRLAPI_PACKET_SUSPENDDRIVER   'S'   /**< Suspend driver              */
+#define BRLAPI_PACKET_RESUMEDRIVER    'R'   /**< Resume driver               */
+#define BRLAPI_PACKET_SYNCHRONIZE     'Z'   /**< Synchronize exceptions      */
+#define BRLAPI_PACKET_PARAM_VALUE     (('P'<<8) + 'V') /**< Parameter value  */
+#define BRLAPI_PACKET_PARAM_REQUEST   (('P'<<8) + 'R') /**< Parameter request*/
+#define BRLAPI_PACKET_PARAM_UPDATE    (('P'<<8) + 'U') /**< Parameter update */
+
+/** Magic number to give when sending a BRLPACKET_ENTERRAWMODE or BRLPACKET_SUSPEND packet */
+#define BRLAPI_DEVICE_MAGIC (0xdeadbeefL)
+
+/** Structure of packets headers */
+typedef struct {
+  uint32_t size;
+  brlapi_packetType_t type;
+} brlapi_header_t;
+
+/** Size of packet headers */
+#define BRLAPI_HEADERSIZE sizeof(brlapi_header_t)
+
+/** Structure of version packets */
+typedef struct {
+  uint32_t protocolVersion;
+} brlapi_versionPacket_t;
+
+/** Structure of authorization packets */
+typedef struct {
+  uint32_t type;
+  unsigned char key;
+} brlapi_authClientPacket_t;
+
+typedef struct {
+  uint32_t type[1];
+} brlapi_authServerPacket_t;
+
+#define BRLAPI_AUTH_NONE 'N' /**< No or implicit authorization              */
+#define BRLAPI_AUTH_KEY  'K' /**< Key authorization                         */
+#define BRLAPI_AUTH_CRED 'C' /**< Explicit socket credentials authorization */
+
+/** Structure of error packets */
+typedef struct {
+  uint32_t code;
+  brlapi_packetType_t type;
+  unsigned char packet;
+} brlapi_errorPacket_t;
+
+/** Structure of enterRawMode / suspend packets */
+typedef struct {
+  uint32_t magic;
+  unsigned char nameLength;
+  char name;
+} brlapi_getDriverSpecificModePacket_t;
+
+/** Flags for writing */
+#define BRLAPI_WF_DISPLAYNUMBER 0X01    /**< Display number                 */
+#define BRLAPI_WF_REGION        0X02    /**< Region parameter               */
+#define BRLAPI_WF_TEXT          0X04    /**< Contains some text             */
+#define BRLAPI_WF_ATTR_AND      0X08    /**< And attributes                 */
+#define BRLAPI_WF_ATTR_OR       0X10    /**< Or attributes                  */
+#define BRLAPI_WF_CURSOR        0X20    /**< Cursor position                */
+#define BRLAPI_WF_CHARSET       0X40    /**< Charset                        */
+
+/** Structure of extended write packets */
+typedef struct {
+  uint32_t flags; /** Flags to tell which fields are present */
+  unsigned char data; /** Fields in the same order as flag weight */
+} brlapi_writeArgumentsPacket_t;
+
+/** Flags for parameter values */
+#define BRLAPI_PVF_GLOBAL            0X01    /** Value is the global value */
+
+#define BRLAPI_MAXPARAMSIZE (BRLAPI_MAXPACKETSIZE - (sizeof(uint32_t) + sizeof(brlapi_param_t) + 2*sizeof(uint32_t)))
+
+/** Structure of Parameter value or update */
+typedef struct {
+  uint32_t flags; /** Flags to tell how value was gotten */
+  brlapi_param_t param; /** Which parameter being transmitted */
+  uint32_t subparam_hi; /** Which sub-parameter being transmitted, hi 32bits */
+  uint32_t subparam_lo; /** Which sub-parameter being transmitted, lo 32bits */
+  unsigned char data[BRLAPI_MAXPARAMSIZE]; /** Content of the parameter */
+} brlapi_paramValuePacket_t;
+
+/** Flags for parameter requests */
+#define BRLAPI_PARAMF_GET            0X100    /** Get current parameter value    */
+#define BRLAPI_PARAMF_SUBSCRIBE      0X200    /** Subscribe to parameter updates */
+#define BRLAPI_PARAMF_UNSUBSCRIBE    0X400    /** Unsubscribe from parameter updates */
+
+/** Structure of Parameter request */
+typedef struct {
+  uint32_t flags; /** Flags to tell whether/how to get values */
+  brlapi_param_t param; /** Which parameter to be transmitted */
+  uint32_t subparam_hi; /** Which sub-parameter being transmitted, hi 32bits */
+  uint32_t subparam_lo; /** Which sub-parameter being transmitted, lo 32bits */
+} brlapi_paramRequestPacket_t;
+
+/** Type for packets.  Should be used instead of a mere char[], since it has
+ * correct alignment requirements. */
+typedef union {
+	unsigned char data[BRLAPI_MAXPACKETSIZE];
+	brlapi_versionPacket_t version;
+	brlapi_authClientPacket_t authClient;
+	brlapi_authServerPacket_t authServer;
+	brlapi_errorPacket_t error;
+	brlapi_getDriverSpecificModePacket_t getDriverSpecificMode;
+	brlapi_writeArgumentsPacket_t writeArguments;
+	brlapi_paramValuePacket_t paramValue;
+	brlapi_paramRequestPacket_t paramRequest;
+	uint32_t uint32;
+} brlapi_packet_t;
+
+/* brlapi_writePacket */
+/** Send a packet to \e BrlAPI server
+ *
+ * This function is for internal use, but one might use it if one really knows
+ * what one is doing...
+ *
+ * \e type should only be one of the above defined BRLPACKET_*.
+ *
+ * The syntax is the same as write()'s.
+ *
+ * \return 0 on success, -1 on failure.
+ *
+ * \sa brlapi_readPacketHeader()
+ * brlapi_readPacketContent()
+ * brlapi_readPacket()
+ */
+ssize_t brlapi_writePacket(brlapi_fileDescriptor fd, brlapi_packetType_t type, const void *buf, size_t size);
+
+/* brlapi_readPacketHeader */
+/** Read the header (type+size) of a packet from \e BrlAPI server
+ *
+ * This function is for internal use, but one might use it if one really knows
+ * what one is doing...
+ *
+ * \e type is where the function will store the packet type; it should always
+ * be one of the above defined BRLPACKET_* (or else something very nasty must
+ * have happened :/).
+ *
+ * \return packet's size, -2 if \c EOF occurred, -1 on error or signal
+ * interruption.
+ *
+ * \sa brlapi_writePacket()
+ * brlapi_readPacketContent
+ * brlapi_readPacket
+ */
+ssize_t brlapi_readPacketHeader(brlapi_fileDescriptor fd, brlapi_packetType_t *packetType);
+
+/* brlapi_readPacketContent */
+/** Read the content of a packet from \e BrlAPI server
+ *
+ * This function is for internal use, but one might use it if one really knows
+ * what one is doing...
+ *
+ * \e packetSize is the size announced by \e brlapi_readPacketHeader()
+ *
+ * \e bufSize is the size of \e buf
+ *
+ * \return packetSize, -2 if \c EOF occurred, -1 on error.
+ *
+ * If the packet is larger than the supplied buffer, the buffer will be
+ * filled with the beginning of the packet, the rest of the packet being
+ * discarded. This follows the semantics of the recv system call when the
+ * MSG_TRUNC option is given.
+ *
+ * \sa brlapi_writePacket()
+ * brlapi_readPacketHeader()
+ * brlapi_readPacket()
+ */
+ssize_t brlapi_readPacketContent(brlapi_fileDescriptor fd, size_t packetSize, void *buf, size_t bufSize);
+
+/* brlapi_readPacket */
+/** Read a packet from \e BrlAPI server
+ *
+ * This function is for internal use, but one might use it if one really knows
+ * what one is doing...
+ *
+ * \e type is where the function will store the packet type; it should always
+ * be one of the above defined BRLPACKET_* (or else something very nasty must
+ * have happened :/).
+ *
+ * The syntax is the same as read()'s.
+ *
+ * \return packet's size, -2 if \c EOF occurred, -1 on error or signal
+ * interruption.
+ *
+ * If the packet is larger than the supplied buffer, the buffer will be
+ * filled with the beginning of the packet, the rest of the packet being
+ * discarded. This follows the semantics of the recv system call when the
+ * MSG_TRUNC option is given.
+ *
+ * \sa brlapi_writePacket()
+ */
+ssize_t brlapi_readPacket(brlapi_fileDescriptor fd, brlapi_packetType_t *type, void *buf, size_t size);
+
+/* brlapi_fd_mutex */
+/** Mutex for protecting concurrent fd access
+ *
+ * In order to regulate concurrent access to the library's file descriptor and
+ * requests to / answers from \e BrlAPI server, every function of the library
+ * locks this mutex, namely
+ *
+ * - brlapi_openConnection()
+ * - brlapi_closeConnection()
+ * - brlapi_enterRawMode()
+ * - brlapi_leaveRawMode()
+ * - brlapi_sendRaw()
+ * - brlapi_recvRaw()
+ * - brlapi_getDriverName()
+ * - brlapi_getDisplaySize()
+ * - brlapi_enterTtyMode()
+ * - brlapi_enterTtyModeWithPath()
+ * - brlapi_leaveTtyMode()
+ * - brlapi_*write*()
+ * - brlapi_(un)?ignorekey(Range|Set)()
+ * - brlapi_readKey()
+ *
+ * If both these functions and brlapi_writePacket() or brlapi_readPacket() are
+ * used in a multithreaded application, this mutex must be locked before calling
+ * brlapi_writePacket() or brlapi_readPacket(), and unlocked afterwards.
+ */
+#ifdef __MINGW32__
+#include <windows.h>
+extern HANDLE brlapi_fd_mutex;
+#else /* __MINGW32__ */
+#include <pthread.h>
+extern pthread_mutex_t brlapi_fd_mutex;
+#endif /* __MINGW32__ */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLAPI_INCLUDED_PROTOCOL */
diff --git a/Programs/brlapi_server.c b/Programs/brlapi_server.c
new file mode 100644
index 0000000..4958025
--- /dev/null
+++ b/Programs/brlapi_server.c
@@ -0,0 +1,4892 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* api_server.c : Main file for BrlApi server */
+
+#define SERVER_SOCKET_LIMIT 4
+#define SERVER_SELECT_TIMEOUT 1
+#define UNAUTH_LIMIT 5
+#define UNAUTH_TIMEOUT 30
+
+#define RELEASE "BrlAPI Server: release " BRLAPI_RELEASE
+#define COPYRIGHT "   Copyright (C) 2002-2023 by Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>, \
+Samuel Thibault <samuel.thibault@ens-lyon.org>"
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <locale.h>
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+#endif /* HAVE_ICONV_H */
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif /* HAVE_ALLOCA_H */
+
+#ifdef __MINGW32__
+#include "system_windows.h"
+#include "win_pthread.h"
+
+#else /* __MINGW32__ */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <pthread.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else /* HAVE_SYS_SELECT_H */
+#include <sys/time.h>
+#endif /* HAVE_SYS_SELECT_H */
+#endif /* __MINGW32__ */
+
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+#include "brlapi_protocol.h"
+#include "brlapi_keyranges.h"
+
+#include "cmd_brlapi.h"
+#include "brl_cmds.h"
+#include "brl_utils.h"
+#include "embed.h"
+#include "clipboard.h"
+#include "ttb.h"
+#include "core.h"
+#include "api_server.h"
+#include "report.h"
+#include "log.h"
+#include "addresses.h"
+#include "prefs.h"
+#include "file.h"
+#include "parse.h"
+#include "timing.h"
+#include "auth.h"
+#include "io_generic.h"
+#include "io_misc.h"
+#include "scr.h"
+#include "charset.h"
+#include "async_signal.h"
+#include "thread.h"
+#include "blink.h"
+
+#ifdef __MINGW32__
+#define LogSocketError(msg) logWindowsSocketError(msg)
+#else /* __MINGW32__ */
+#define LogSocketError(msg) logSystemError(msg)
+#endif /* __MINGW32__ */
+
+#ifdef __CYGWIN__
+#undef PF_LOCAL
+#endif /* __CYGWIN__ */
+
+typedef enum {
+  PARM_AUTH,
+  PARM_HOST,
+#ifdef ENABLE_API_FUZZING
+  PARM_FUZZ,
+  PARM_FUZZSEED,
+  PARM_FUZZHEAD,
+  PARM_FUZZWRITE,
+  PARM_FUZZWRITEUTF8,
+  PARM_CRASH,
+#endif
+} Parameters;
+
+const char *const api_serverParameters[] = {
+  "auth", "host",
+#ifdef ENABLE_API_FUZZING
+  "fuzz", "fuzzseed", "fuzzhead", "fuzzwrite", "fuzzwriteutf8", "crash",
+#endif /* ENABLE_API_FUZZING */
+  NULL
+};
+
+#ifdef ENABLE_API_FUZZING
+static int fuzz_runs;
+static int fuzz_seed;
+static unsigned int fuzz_head;
+static unsigned int fuzz_write;
+static unsigned int fuzz_writeutf8;
+#endif /* ENABLE_API_FUZZING */
+
+#define WERR(x, y, ...) do { \
+  logMessage(LOG_ERR, "writing error %d to %"PRIfd, y, x); \
+  logMessage(LOG_ERR, __VA_ARGS__); \
+  writeError(x, y); \
+} while(0)
+#define WEXC(fd, err, type, packet, size, ...) do { \
+  logMessage(LOG_ERR, "writing exception %d to fd %"PRIfd, err, fd); \
+  logMessage(LOG_ERR, __VA_ARGS__); \
+  writeException(fd, err, type, packet, size); \
+} while(0)
+
+/* These CHECK* macros check whether a condition is true, and, if not, */
+/* send back either a non-fatal error, or an exception */
+#define CHECKERR(condition, error, msg, ...) \
+if (!( condition )) { \
+  WERR(c->fd, error, "%s not met: " msg, #condition, ## __VA_ARGS__); \
+  return 0; \
+} else { }
+#define CHECKEXC(condition, error, msg, ...) \
+if (!( condition )) { \
+  WEXC(c->fd, error, type, packet, size, "%s not met: " msg, #condition, ## __VA_ARGS__); \
+  return 0; \
+} else { }
+
+#ifdef brlapi_error
+#undef brlapi_error
+#endif
+
+static brlapi_error_t brlapiserver_error;
+#define brlapi_error brlapiserver_error
+
+#define BRLAPI(fun) brlapiserver_ ## fun
+#include "brlapi_common.h"
+
+/** ask for \e brltty commands */
+#define BRL_COMMANDS 0
+/** ask for raw driver keycodes */
+#define BRL_KEYCODES 1
+
+/****************************************************************************/
+/** GLOBAL TYPES AND VARIABLES                                              */
+/****************************************************************************/
+
+extern char *opt_brailleParameters;
+extern char *cfg_brailleParameters;
+
+typedef struct {
+  unsigned int cursor;
+  wchar_t *text;
+  unsigned char *andAttr;
+  unsigned char *orAttr;
+} BrailleWindow;
+
+typedef enum { TODISPLAY, EMPTY } BrlBufState;
+
+typedef struct Subscription {
+  brlapi_param_t parameter;
+  brlapi_param_subparam_t subparam;
+  brlapi_param_flags_t flags;
+  struct Subscription *prev, *next;
+} Subscription;
+
+typedef struct Connection {
+  uint32_t clientVersion;
+  struct Connection *prev, *next;
+  FileDescriptor fd;
+  int auth;
+  struct Tty *tty;
+  brlapi_param_clientPriority_t client_priority;
+  int raw, suspend;
+  unsigned int how; /* how keys must be delivered to clients */
+  uint8_t retainDots; /* whether client wants dots instead of translating to chars */
+  BrailleWindow brailleWindow;
+  BrlBufState brlbufstate;
+  pthread_mutex_t brailleWindowMutex;
+  KeyrangeList *acceptedKeys;
+  pthread_mutex_t acceptedKeysMutex;
+  time_t upTime;
+  Packet packet;
+  struct Subscription subscriptions;
+} Connection;
+
+typedef struct Tty {
+  int focus;
+  int number;
+  struct Connection *connections;
+  struct Tty *father; /* father */
+  struct Tty **prevnext,*next; /* siblings */
+  struct Tty *subttys; /* children */
+} Tty;
+
+typedef struct {
+  unsigned local_subscriptions;
+  unsigned global_subscriptions;
+} ParamState;
+
+static ParamState paramState[BRLAPI_PARAM_COUNT];
+
+/* Pointer to the connection accepter thread */
+static pthread_t serverThread; /* server */
+#ifdef ENABLE_API_FUZZING
+static pthread_t fuzzerThread;                       /* fuzzer */
+static pthread_t crasherThread;                      /* crash reproducer */
+#endif /* ENABLE_API_FUZZING */
+static pthread_t socketThreads[SERVER_SOCKET_LIMIT]; /* socket binding threads */
+static int running; /* should threads be running? */
+static char **socketHosts = NULL; /* socket local hosts */
+static struct socketInfo {
+  int addrfamily;
+  FileDescriptor fd;
+  char *host;
+  char *port;
+#ifdef __MINGW32__
+  OVERLAPPED overl;
+#endif /* __MINGW32__ */
+} socketInfo[SERVER_SOCKET_LIMIT]; /* information for cleaning sockets */
+
+static int serverSocketCount; /* number of sockets */
+static int serverSocketsPending; /* number of sockets not opened yet */
+pthread_mutex_t apiSocketsMutex;
+
+/* Protects from connection addition / remove from the server thread */
+pthread_mutex_t apiConnectionsMutex;
+
+/* Protects the real driver's functions */
+pthread_mutex_t apiDriverMutex;
+
+/* Which connection currently has raw mode or requested device suspension */
+pthread_mutex_t apiRawMutex;
+static Connection *rawConnection = NULL;
+static Connection *suspendConnection = NULL;
+
+pthread_mutex_t apiParamMutex;
+/* Which connection is currently modifying a parameter */
+static Connection *paramUpdateConnection;
+
+/* mutex lock order is as follows:
+ * 1. apiParamMutex
+ * 2. apiConnectionsMutex
+ * 3. apiRawMutex
+ * 4. acceptedKeysMutex or brailleWindowMutex
+ * 5. apiDriverMutex
+*/
+
+static Tty notty;
+static Tty ttys;
+
+static unsigned int unauthConnections;
+static unsigned int unauthConnLog = 0;
+
+/*
+ * API states are
+ * - stopped: No thread is running (hence no connection allowed).
+ *   started: The server thread is running, accepting connections.
+ * - unlinked: TrueBraille == &noBraille: API has no control on the driver.
+ *   linked: TrueBraille != &noBraille: API controls the driver.
+ * - core suspended: The core asked to keep the device closed.
+ *   core active: The core asked has opened the device.
+ * - device closed: API keeps the device closed.
+ *   device opened: API has really opened the device.
+ *
+ * Combinations can be:
+ * - initial: API stopped, unlinked, core suspended and device closed.
+ * - started: API started, unlinked, core suspended and device closed.
+ * - normal: API started, linked, core active and device opened.
+ * - core suspend: API started, linked, core suspended but device opened.
+ *   (BrlAPI-only output).
+ * - full suspend: API started, linked, core suspended and device closed.
+ * - brltty control: API started, core active and device opened, but unlinked.
+ *
+ * Other states don't make sense, since
+ * - api needs to be started before being linked,
+ * - the device can't remain closed if core is active,
+ * - the core must resume before unlinking api (so as to let the api re-open
+ *   the driver if necessary)
+ */
+
+/* Pointer to subroutines of the real braille driver, &noBraille when API is
+ * unlinked  */
+static const BrailleDriver *trueBraille;
+static BrailleDriver ApiBraille;
+
+/* Identication of the REAL braille driver currently used */
+
+/* The following variable contains the size of the braille display */
+/* stored as a pair of _network_-formatted integers */
+static uint32_t displayDimensions[2] = { 0, 0 };
+static unsigned int displaySize = 0;
+
+static BrailleDisplay *disp; /* Parameter to pass to braille drivers */
+
+static int coreActive; /* Whether core is active */
+static int offline; /* Whether device is offline */
+static int driverConstructed; /* Whether device is really opened, protected by apiDriverMutex */
+static int driverConstructing; /* Whether device being constructed, protected by apiDriverMutex */
+static wchar_t *coreWindowText; /* Last text written by the core */
+static unsigned char *coreWindowDots; /* Last dots written by the core */
+static int coreWindowCursor; /* Last cursor position set by the core */
+pthread_mutex_t apiSuspendMutex; /* Protects use of driverConstructed state */
+
+static const char *auth = BRLAPI_DEFAUTH;
+static AuthDescriptor *authDescriptor;
+
+#ifdef __MINGW32__
+static WSADATA wsadata;
+#endif /* __MINGW32__ */
+
+static unsigned char cursorOverlay = 0;
+
+/****************************************************************************/
+/** SOME PROTOTYPES                                                        **/
+/****************************************************************************/
+
+extern void processParameters(char ***values, const char *const *names, const char *description, char *optionParameters, char *configuredParameters, const char *environmentVariable);
+static int initializeAcceptedKeys(Connection *c, int how);
+static void brlResize(BrailleDisplay *brl);
+static void handleParamUpdate(Connection *source, Connection *dest, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void *data, size_t size);
+
+/****************************************************************************/
+/** DRIVER CAPABILITIES                                                    **/
+/****************************************************************************/
+
+/* Function : isRawCapable */
+/* Returns !0 if the specified driver is raw capable, 0 if it is not. */
+static int isRawCapable(const BrailleDriver *brl)
+{
+  return ((brl->readPacket!=NULL) && (brl->writePacket!=NULL) && (brl->reset!=NULL));
+}
+
+/* Function : isKeyCapable */
+/* Returns !0 if driver can return specific keycodes, 0 if not. */
+static int isKeyCapable(const BrailleDriver *brl)
+{
+  int ret;
+  lockMutex(&apiDriverMutex);
+  ret = (disp && (disp->keyNames != NULL));
+  unlockMutex(&apiDriverMutex);
+  return ret;
+}
+
+/* Function : suspendBrailleDriver */
+/* Really suspend the braille driver. Assumes that apiDriverMutex is locked */
+static void suspendBrailleDriver(void) {
+  if (trueBraille == &noBraille) return; /* core unlinked api */
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "driver suspended");
+  lockMutex(&apiSuspendMutex);
+  driverConstructed = 0;
+  destructBrailleDriver();
+  unlockMutex(&apiSuspendMutex);
+}
+
+
+CORE_TASK_CALLBACK(apiCoreTask_destructBrailleDriver) {
+  lockMutex(&apiDriverMutex);
+  if (driverConstructed) {
+    suspendBrailleDriver();
+  }
+  unlockMutex(&apiDriverMutex);
+}
+
+/* Function : suspendDriver */
+/* Requests suspending the driver */
+static void suspendDriver(void) {
+  runCoreTask(apiCoreTask_destructBrailleDriver, NULL, 1);
+}
+
+/* Function : resumeBrailleDriver */
+/* Really resume the braille driver. Assumes that apiDriverMutex is locked */
+static int resumeBrailleDriver(BrailleDisplay *brl) {
+  if (trueBraille == &noBraille) return 0; /* core unlinked api */
+  driverConstructing = 1;
+  lockMutex(&apiSuspendMutex);
+  driverConstructed = constructBrailleDriver();
+  if (driverConstructed) {
+    disp = brl;
+  }
+  unlockMutex(&apiSuspendMutex);
+  if (driverConstructed) {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "driver resumed");
+    handleParamUpdate(NULL, NULL, BRLAPI_PARAM_DRIVER_NAME, 0, BRLAPI_PARAMF_GLOBAL, braille->definition.name, strlen(braille->definition.name));
+    handleParamUpdate(NULL, NULL, BRLAPI_PARAM_DRIVER_CODE, 0, BRLAPI_PARAMF_GLOBAL, braille->definition.code, strlen(braille->definition.code));
+    handleParamUpdate(NULL, NULL, BRLAPI_PARAM_DRIVER_VERSION, 0, BRLAPI_PARAMF_GLOBAL, braille->definition.version, strlen(braille->definition.version));
+    handleParamUpdate(NULL, NULL, BRLAPI_PARAM_DEVICE_MODEL, 0, BRLAPI_PARAMF_GLOBAL, disp->keyBindings, strlen(disp->keyBindings));
+    brlResize(brl);
+  }
+  driverConstructing = 0;
+  return driverConstructed;
+}
+
+typedef struct {
+  unsigned resumed:1;
+} CoreTaskData_resumeBrailleDriver;
+
+CORE_TASK_CALLBACK(apiCoreTask_resumeBrailleDriver) {
+  CoreTaskData_resumeBrailleDriver *rbd = data;
+  lockMutex(&apiDriverMutex);
+  if (!disp) {
+    rbd->resumed = 0;
+  } else if (driverConstructed || driverConstructing) {
+    rbd->resumed = 1;
+  } else {
+    rbd->resumed = resumeBrailleDriver(disp);
+  }
+  unlockMutex(&apiDriverMutex);
+}
+
+/* Function : resumeDriver */
+/* Requests resuming the driver */
+static int resumeDriver(void) {
+  CoreTaskData_resumeBrailleDriver rbd = {
+    .resumed = 0
+  };
+
+  runCoreTask(apiCoreTask_resumeBrailleDriver, &rbd, 1);
+  return rbd.resumed;
+}
+
+/* Function : resetBrailleDriver */
+/* Really reset the braille driver. Assumes that apiDriverMutex is locked */
+static void resetBrailleDevice(void) {
+  logMessage(LOG_WARNING, "trying to reset the braille device");
+
+  if (!trueBraille->reset || !disp || !trueBraille->reset(disp)) {
+    if (trueBraille->reset) {
+      logMessage(LOG_WARNING, "reset failed - restarting the braille driver");
+    }
+
+    restartBrailleDriver();
+  }
+}
+
+CORE_TASK_CALLBACK(apiCoreTask_resetBrailleDevice) {
+  lockMutex(&apiDriverMutex);
+  resetBrailleDevice();
+  unlockMutex(&apiDriverMutex);
+}
+
+/* Function : resetDriver */
+/* Requests resetting the driver */
+static void resetDevice(void) {
+  runCoreTask(apiCoreTask_resetBrailleDevice, NULL, 1);
+}
+
+static int flushBrailleOutput(BrailleDisplay *brl) {
+  int flushed = api_flushOutput(brl);
+  if (!flushed) brl->hasFailed = 1;
+  resetAllBlinkDescriptors();
+  return flushed;
+}
+
+typedef struct {
+  unsigned flushed:1;
+} CoreTaskData_flushBrailleOutput;
+
+CORE_TASK_CALLBACK(apiCoreTask_flushBrailleOutput) {
+  CoreTaskData_flushBrailleOutput *fbo = data;
+  fbo->flushed = flushBrailleOutput(&brl);
+}
+
+static int flushOutput(void) {
+  CoreTaskData_flushBrailleOutput fbo = {
+    .flushed = 0
+  };
+
+  runCoreTask(apiCoreTask_flushBrailleOutput, &fbo, 1);
+  return fbo.flushed;
+}
+
+/****************************************************************************/
+/** PACKET HANDLING                                                        **/
+/****************************************************************************/
+
+/* Function : writeAck */
+/* Sends an acknowledgement on the given socket */
+static inline void writeAck(FileDescriptor fd)
+{
+  brlapiserver_writePacket(fd,BRLAPI_PACKET_ACK,NULL,0);
+}
+
+/* Function : writeError */
+/* Sends the given non-fatal error on the given socket */
+static void writeError(FileDescriptor fd, unsigned int err)
+{
+  uint32_t code = htonl(err);
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "error %u on fd %"PRIfd, err, fd);
+  brlapiserver_writePacket(fd,BRLAPI_PACKET_ERROR,&code,sizeof(code));
+}
+
+/* Function : writeException */
+/* Sends the given error code on the given socket */
+static void writeException(FileDescriptor fd, unsigned int err, brlapi_packetType_t type, const brlapi_packet_t *packet, size_t size)
+{
+  int hdrsize, esize;
+  brlapi_packet_t epacket;
+  brlapi_errorPacket_t * errorPacket = &epacket.error;
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "exception %u for packet type %lu on fd %"PRIfd, err, (unsigned long)type, fd);
+  hdrsize = sizeof(errorPacket->code)+sizeof(errorPacket->type);
+  errorPacket->code = htonl(err);
+  errorPacket->type = htonl(type);
+  esize = MIN(size, BRLAPI_MAXPACKETSIZE-hdrsize);
+  if ((packet!=NULL) && (size!=0)) memcpy(&errorPacket->packet, &packet->data, esize);
+  brlapiserver_writePacket(fd,BRLAPI_PACKET_EXCEPTION,&epacket.data, hdrsize+esize);
+}
+
+static void writeKey(FileDescriptor fd, brlapi_keyCode_t key) {
+  uint32_t buf[2];
+  buf[0] = htonl(key >> 32);
+  buf[1] = htonl(key & 0xffffffff);
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "writing key %08"PRIx32" %08"PRIx32" to fd %"PRIfd,buf[0],buf[1],fd);
+  brlapiserver_writePacket(fd,BRLAPI_PACKET_KEY,&buf,sizeof(buf));
+}
+
+typedef int(*PacketHandler)(Connection *, brlapi_packetType_t, brlapi_packet_t *, size_t);
+
+typedef struct { /* packet handlers */
+  PacketHandler getDriverName;
+  PacketHandler getModelIdentifier;
+  PacketHandler getDisplaySize;
+  PacketHandler enterTtyMode;
+  PacketHandler setFocus;
+  PacketHandler leaveTtyMode;
+  PacketHandler ignoreKeyRanges;
+  PacketHandler acceptKeyRanges;
+  PacketHandler write;
+  PacketHandler enterRawMode;
+  PacketHandler leaveRawMode;
+  PacketHandler packet;
+  PacketHandler suspendDriver;
+  PacketHandler resumeDriver;
+  PacketHandler parameterValue;
+  PacketHandler parameterRequest;
+  PacketHandler sync;
+} PacketHandlers;
+
+/****************************************************************************/
+/** BRAILLE WINDOWS MANAGING                                               **/
+/****************************************************************************/
+
+/* Function : allocBrailleWindow */
+/* Allocates and initializes the members of a BrailleWindow structure */
+/* Uses displaySize to determine size of allocated buffers */
+/* Returns to report success, -1 on errors */
+static int allocBrailleWindow(BrailleWindow *brailleWindow)
+{
+  if (!(brailleWindow->text = malloc(displaySize*sizeof(wchar_t)))) goto out;
+  if (!(brailleWindow->andAttr = malloc(displaySize))) goto outText;
+  if (!(brailleWindow->orAttr = malloc(displaySize))) goto outAnd;
+
+  wmemset(brailleWindow->text, WC_C(' '), displaySize);
+  memset(brailleWindow->andAttr, 0xFF, displaySize);
+  memset(brailleWindow->orAttr, 0x00, displaySize);
+  brailleWindow->cursor = 0;
+  return 0;
+
+outAnd:
+  free(brailleWindow->andAttr);
+
+outText:
+  free(brailleWindow->text);
+
+out:
+  return -1;
+}
+
+/* Function: freeBrailleWindow */
+/* Frees the fields of a BrailleWindow structure */
+static void freeBrailleWindow(BrailleWindow *brailleWindow)
+{
+  free(brailleWindow->text); brailleWindow->text = NULL;
+  free(brailleWindow->andAttr); brailleWindow->andAttr = NULL;
+  free(brailleWindow->orAttr); brailleWindow->orAttr = NULL;
+}
+
+static unsigned char
+getCursorOverlay (BrailleDisplay *brl) {
+  if (prefs.showScreenCursor && !brl->hideCursor) {
+    BlinkDescriptor *blink = &screenCursorBlinkDescriptor;
+    requireBlinkDescriptor(blink);
+
+    if (isBlinkVisible(blink)) {
+      return mapCursorDots(getScreenCursorDots());
+    }
+  }
+
+  return 0;
+}
+
+/* Function: getDots */
+/* Returns the braille dots corresponding to a BrailleWindow structure */
+/* No allocation of buf is performed */
+static void getDots(const BrailleWindow *brailleWindow, unsigned char *buf)
+{
+  int i;
+  unsigned char c;
+  for (i=0; i<displaySize; i++) {
+    c = convertCharacterToDots(textTable, brailleWindow->text[i]);
+    buf[i] = (c & brailleWindow->andAttr[i]) | brailleWindow->orAttr[i];
+  }
+
+  if (brailleWindow->cursor) {
+    buf[brailleWindow->cursor-1] |= cursorOverlay;
+  }
+}
+
+/****************************************************************************/
+/** CONNECTIONS MANAGING                                                   **/
+/****************************************************************************/
+
+/* Function : createConnection */
+/* Creates a connection */
+static Connection *createConnection(FileDescriptor fd, time_t currentTime)
+{
+  Connection *c =  malloc(sizeof(Connection));
+  if (c==NULL) goto out;
+
+  c->auth = -1;
+  c->fd = fd;
+  c->tty = NULL;
+  c->client_priority = BRLAPI_PARAM_CLIENT_PRIORITY_DEFAULT;
+  c->raw = 0;
+  c->suspend = 0;
+  c->brlbufstate = EMPTY;
+
+  {
+    pthread_mutexattr_t mattr;
+
+    pthread_mutexattr_init(&mattr);
+    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
+
+    pthread_mutex_init(&c->brailleWindowMutex,&mattr);
+    setAddressName(&c->brailleWindowMutex, "apiBrailleWindowMutex[" PRIfd "]", fd);
+
+    pthread_mutex_init(&c->acceptedKeysMutex,&mattr);
+    setAddressName(&c->acceptedKeysMutex, "apiAcceptedKeysMutex[" PRIfd "]", fd);
+  }
+
+  c->how = 0;
+  c->retainDots = 1;
+  c->acceptedKeys = NULL;
+  c->upTime = currentTime;
+  c->brailleWindow.text = NULL;
+  c->brailleWindow.andAttr = NULL;
+  c->brailleWindow.orAttr = NULL;
+  if (brlapi_initializePacket(&c->packet))
+    goto outmalloc;
+  c->subscriptions.next = &c->subscriptions;
+  c->subscriptions.prev = &c->subscriptions;
+  return c;
+
+outmalloc:
+  free(c);
+out:
+  if (fd != INVALID_FILE_DESCRIPTOR) {
+    writeError(fd,BRLAPI_ERROR_NOMEM);
+    closeFileDescriptor(fd);
+  }
+  return NULL;
+}
+
+/* Function : freeConnection */
+/* Frees all resources associated to a connection */
+static void freeConnection(Connection *c)
+{
+  struct Subscription *s, *next;
+
+  if (c->fd != INVALID_FILE_DESCRIPTOR) {
+    lockMutex(&apiParamMutex);
+    for (s=c->subscriptions.next; s!=&c->subscriptions; s=next) {
+      if (s->flags & BRLAPI_PARAMF_GLOBAL)
+	paramState[s->parameter].global_subscriptions--;
+      else
+	paramState[s->parameter].local_subscriptions--;
+      next = s->next;
+      free(s);
+    }
+    unlockMutex(&apiParamMutex);
+
+    if (c->auth != 1) unauthConnections--;
+    closeFileDescriptor(c->fd);
+  }
+
+  pthread_mutex_destroy(&c->brailleWindowMutex);
+  unsetAddressName(&c->brailleWindowMutex);
+
+  pthread_mutex_destroy(&c->acceptedKeysMutex);
+  unsetAddressName(&c->acceptedKeysMutex);
+
+  freeBrailleWindow(&c->brailleWindow);
+  freeKeyrangeList(&c->acceptedKeys);
+  free(c);
+}
+
+/* Function : addConnection */
+/* Creates a connection and adds it to the connection list */
+static void __addConnection(Connection *c, Connection *connections)
+{
+  c->next = connections->next;
+  c->prev = connections;
+  connections->next->prev = c;
+  connections->next = c;
+}
+static void __addConnectionSorted(Connection *c, Connection *head)
+{
+  Connection *cur = head;
+  while (cur->next != head && cur->next->client_priority > c->client_priority)
+    cur = cur->next;
+  __addConnection(c, cur);
+}
+static void addConnection(Connection *c, Connection *connections)
+{
+  lockMutex(&apiConnectionsMutex);
+  __addConnection(c,connections);
+  unlockMutex(&apiConnectionsMutex);
+}
+
+/* Function : removeConnection */
+/* Removes the connection from the list */
+static void __removeConnection(Connection *c)
+{
+  c->prev->next = c->next;
+  c->next->prev = c->prev;
+}
+static void removeConnection(Connection *c)
+{
+  lockMutex(&apiConnectionsMutex);
+  __removeConnection(c);
+  unlockMutex(&apiConnectionsMutex);
+}
+
+/* Function: removeFreeConnection */
+/* Removes the connection from the list and frees its resources */
+static void removeFreeConnection(Connection *c)
+{
+  removeConnection(c);
+  freeConnection(c);
+}
+
+/****************************************************************************/
+/** TTYs MANAGING                                                          **/
+/****************************************************************************/
+
+/* Function: newTty */
+/* creates a new tty and inserts it in the hierarchy */
+static inline Tty *newTty(Tty *father, int number)
+{
+  Tty *tty;
+  if (!(tty = calloc(1,sizeof(*tty)))) goto out;
+  if (!(tty->connections = createConnection(INVALID_FILE_DESCRIPTOR,0))) goto outtty;
+  tty->connections->next = tty->connections->prev = tty->connections;
+  tty->number = number;
+  tty->focus = SCR_NO_VT;
+  tty->father = father;
+  tty->prevnext = &father->subttys;
+  if ((tty->next = father->subttys))
+    tty->next->prevnext = &tty->next;
+  father->subttys = tty;
+  return tty;
+
+outtty:
+  free(tty);
+out:
+  return NULL;
+}
+
+/* Function: removeTty */
+/* removes an unused tty from the hierarchy */
+static inline void removeTty(Tty *toremove)
+{
+  if (toremove->next)
+    toremove->next->prevnext = toremove->prevnext;
+  *(toremove->prevnext) = toremove->next;
+}
+
+/* Function: freeTty */
+/* frees a tty */
+static inline void freeTty(Tty *tty)
+{
+  freeConnection(tty->connections);
+  free(tty);
+}
+
+/****************************************************************************/
+/** COMMUNICATION PROTOCOL HANDLING                                        **/
+/****************************************************************************/
+
+/* Function logRequest */
+/* Logs the given request */
+static inline void logRequest(brlapi_packetType_t type, FileDescriptor fd)
+{
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "received %s request on fd %"PRIfd, brlapiserver_getPacketTypeName(type), fd);
+}
+
+static int handleGetDriver(Connection *c, brlapi_packetType_t type, size_t size, const char *str)
+{
+  int len = strlen(str);
+  CHECKERR(size==0,BRLAPI_ERROR_INVALID_PACKET,"packet should be empty");
+  CHECKERR(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  brlapiserver_writePacket(c->fd, type, str, len+1);
+  return 0;
+}
+
+static int handleGetDriverName(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  return handleGetDriver(c, type, size, braille->definition.name);
+}
+
+static int handleGetModelIdentifier(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  return handleGetDriver(c, type, size, disp && disp->keyBindings ? disp->keyBindings : "");
+}
+
+static int handleGetDisplaySize(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKERR(size==0,BRLAPI_ERROR_INVALID_PACKET,"packet should be empty");
+  CHECKERR(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  brlapiserver_writePacket(c->fd,BRLAPI_PACKET_GETDISPLAYSIZE,&displayDimensions[0],sizeof(displayDimensions));
+  return 0;
+}
+
+static int handleEnterTtyMode(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  uint32_t * ints = &packet->uint32;
+  uint32_t nbTtys;
+  int how;
+  unsigned int n;
+  unsigned char *p = packet->data;
+  char name[BRLAPI_MAXNAMELENGTH+1];
+  Tty *tty,*tty2,*tty3;
+  uint32_t *ptty;
+  size_t remaining = size;
+  CHECKERR((!c->raw),BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  CHECKERR(remaining>=sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small");
+  p += sizeof(uint32_t); remaining -= sizeof(uint32_t);
+  nbTtys = ntohl(ints[0]);
+  CHECKERR(remaining>=nbTtys*sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small for provided number of ttys");
+  p += nbTtys*sizeof(uint32_t); remaining -= nbTtys*sizeof(uint32_t);
+  CHECKERR(*p<=BRLAPI_MAXNAMELENGTH, BRLAPI_ERROR_INVALID_PARAMETER, "driver name too long");
+  n = *p; p++; remaining--;
+  CHECKERR(remaining==n, BRLAPI_ERROR_INVALID_PACKET,"packet size doesn't match format");
+  memcpy(name, p, n);
+  name[n] = '\0';
+  if (!*name) how = BRL_COMMANDS; else {
+    CHECKERR(!strcmp(name, trueBraille->definition.name), BRLAPI_ERROR_INVALID_PARAMETER, "wrong driver name");
+    CHECKERR(isKeyCapable(trueBraille), BRLAPI_ERROR_OPNOTSUPP, "driver doesn't support raw keycodes");
+    how = BRL_KEYCODES;
+  }
+  freeBrailleWindow(&c->brailleWindow); /* In case of multiple enterTtyMode requests */
+
+  if ((initializeAcceptedKeys(c, how)==-1) || (allocBrailleWindow(&c->brailleWindow)==-1)) {
+    logMessage(LOG_WARNING,"Failed to allocate some resources");
+    freeKeyrangeList(&c->acceptedKeys);
+    WERR(c->fd,BRLAPI_ERROR_NOMEM, "no memory for accepted keys");
+    return 0;
+  }
+
+  lockMutex(&apiConnectionsMutex);
+  tty = tty2 = &ttys;
+
+  for (ptty=ints+1; ptty<=ints+nbTtys; ptty++) {
+    for (tty2=tty->subttys; tty2; tty2=tty2->next) {
+      if (tty2->number == ntohl(*ptty)) break;
+    }
+
+    if (!tty2) break;
+    tty = tty2;
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "tty %#010lx ok",(unsigned long)ntohl(*ptty));
+  }
+
+  if (!tty2) {
+    /* we were stopped at some point because the path doesn't exist yet */
+    if (c->tty) {
+      /* uhu, we already got a tty, but not this one, since the path
+       * doesn't exist yet. This is forbidden. */
+      unlockMutex(&apiConnectionsMutex);
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "already having another tty");
+      return 0;
+    }
+    /* ok, allocate path */
+    /* we lock the entire subtree for easier cleanup */
+    if (!(tty2 = newTty(tty,ntohl(*ptty)))) {
+      unlockMutex(&apiConnectionsMutex);
+      WERR(c->fd,BRLAPI_ERROR_NOMEM, "no memory for new tty");
+      freeBrailleWindow(&c->brailleWindow);
+      return 0;
+    }
+    ptty++;
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "allocated tty %#010lx",(unsigned long)ntohl(*(ptty-1)));
+    for (; ptty<=ints+nbTtys; ptty++) {
+      if (!(tty2 = newTty(tty2,ntohl(*ptty)))) {
+        /* gasp, couldn't allocate :/, clean tree */
+        for (tty2 = tty->subttys; tty2; tty2 = tty3) {
+          tty3 = tty2->subttys;
+          freeTty(tty2);
+        }
+        unlockMutex(&apiConnectionsMutex);
+        WERR(c->fd,BRLAPI_ERROR_NOMEM, "no memory for new tty");
+        freeBrailleWindow(&c->brailleWindow);
+        return 0;
+      }
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "allocated tty %#010lx",(unsigned long)ntohl(*ptty));
+    }
+    tty = tty2;
+  }
+  if (c->tty) {
+    unlockMutex(&apiConnectionsMutex);
+    if (c->tty == tty) {
+      if (c->how==how) {
+	WERR(c->fd, BRLAPI_ERROR_ILLEGAL_INSTRUCTION, "already controlling tty %#010x", c->tty->number);
+      } else {
+        /* Here one is in the case where the client tries to change */
+        /* from BRL_KEYCODES to BRL_COMMANDS, or something like that */
+        /* For the moment this operation is not supported */
+        /* A client that wants to do that should first LeaveTty() */
+        /* and then get it again, risking to lose it */
+        WERR(c->fd,BRLAPI_ERROR_OPNOTSUPP, "Switching from BRL_KEYCODES to BRL_COMMANDS not supported yet");
+      }
+      return 0;
+    } else {
+      /* uhu, we already got a tty, but not this one: this is forbidden. */
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "already having a tty");
+      return 0;
+    }
+  }
+  c->tty = tty;
+  c->how = how;
+  __removeConnection(c);
+  __addConnectionSorted(c,tty->connections);
+  unlockMutex(&apiConnectionsMutex);
+  writeAck(c->fd);
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "fd %"PRIfd" taking control of tty %#010x (how=%d)",c->fd,tty->number,how);
+  return 0;
+}
+
+static int handleSetFocus(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  uint32_t * ints = &packet->uint32;
+  CHECKEXC(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  CHECKEXC(c->tty,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of tty mode");
+  c->tty->focus = ntohl(ints[0]);
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "focus on window %#010x from fd%"PRIfd,c->tty->focus,c->fd);
+  flushOutput();
+  return 0;
+}
+
+/* Function doLeaveTty */
+/* handles a connection leaving its tty */
+static void doLeaveTty(Connection *c)
+{
+  Tty *tty = c->tty;
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "fd %"PRIfd" releasing tty %#010x",c->fd,tty->number);
+  c->tty = NULL;
+  lockMutex(&apiConnectionsMutex);
+  __removeConnection(c);
+  __addConnection(c,notty.connections);
+  unlockMutex(&apiConnectionsMutex);
+  freeKeyrangeList(&c->acceptedKeys);
+  freeBrailleWindow(&c->brailleWindow);
+}
+
+static int handleLeaveTtyMode(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKERR(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  CHECKERR(c->tty,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of tty mode");
+  doLeaveTty(c);
+  writeAck(c->fd);
+  return 0;
+}
+
+static int handleKeyRanges(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  int res = 0;
+  brlapi_keyCode_t x,y;
+  uint32_t (*ints)[4] = (uint32_t (*)[4]) &packet->uint32;
+  unsigned int i;
+  CHECKERR(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  CHECKERR(c->tty,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of tty mode");
+  CHECKERR(!(size%(2*sizeof(brlapi_keyCode_t))),BRLAPI_ERROR_INVALID_PACKET,"wrong packet size");
+  lockMutex(&c->acceptedKeysMutex);
+  for (i=0; i<size/(2*sizeof(brlapi_keyCode_t)); i++) {
+    x = brlapiserver_packetToKeyCode(&ints[i][0]);
+    y = brlapiserver_packetToKeyCode(&ints[i][2]);
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "fd %"PRIfd" range: [%016"BRLAPI_PRIxKEYCODE"..%016"BRLAPI_PRIxKEYCODE"]",c->fd,x,y);
+    if (type==BRLAPI_PACKET_IGNOREKEYRANGES) res = removeKeyrange(x,y,&c->acceptedKeys);
+    else res = addKeyrange(x,y,&c->acceptedKeys);
+    if (res==-1) {
+      /* XXX: humf, in the middle of keycode updates :( */
+      WERR(c->fd,BRLAPI_ERROR_NOMEM,"no memory for key range");
+      break;
+    }
+  }
+  unlockMutex(&c->acceptedKeysMutex);
+  if (!res) writeAck(c->fd);
+  return 0;
+}
+
+static void
+logConversionDecision (Connection *connection, const char *charset, const char *decision) {
+  logMessage(LOG_CATEGORY(SERVER_EVENTS),
+    "fd %"PRIfd" charset %s %s",
+    connection->fd, charset, decision
+  );
+}
+
+static void
+logConversionResult (Connection *connection, unsigned int chars, unsigned int bytes) {
+  logMessage(LOG_CATEGORY(SERVER_EVENTS),
+    "fd %"PRIfd" wrote %d characters %d bytes",
+    connection->fd, chars, bytes
+  );
+}
+
+static void
+convertFromLatin1 (Connection *c, unsigned int rbeg, unsigned int rsiz, const unsigned char *text) {
+  for (int i=0; i<rsiz; i+=1) {
+    c->brailleWindow.text[rbeg-1+i] = text[i];
+  }
+
+  logConversionResult(c, rsiz, rsiz);
+}
+
+static int
+convertFromUTF8 (Connection *c, wchar_t *outBuff, size_t *outLeft, const char *inBuff, size_t *inLeft) {
+  wchar_t *out = outBuff;
+  const char *in = inBuff;
+
+  while ((*outLeft > 0) && (*inLeft > 0)) {
+    wint_t wc = convertUtf8ToWchar(&in, inLeft);
+    if (wc == WEOF) return 0;
+
+    *out++ = wc;
+    *outLeft -= 1;
+  }
+
+  logConversionResult(c, (out - outBuff), (in - inBuff));
+  return 1;
+}
+
+static int handleWrite(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  brlapi_writeArgumentsPacket_t *wa = &packet->writeArguments;
+  unsigned char *text = NULL, *orAttr = NULL, *andAttr = NULL;
+  unsigned int rbeg, rsiz, textLen = 0;
+  bool fill;
+  int cursor = -1;
+  unsigned char *p = &wa->data;
+  int remaining = size;
+  char *charset = NULL;
+  unsigned int charsetLen = 0;
+  CHECKEXC(remaining>=sizeof(wa->flags), BRLAPI_ERROR_INVALID_PACKET, "packet too small for flags");
+  CHECKEXC(!c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  CHECKEXC(c->tty,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of tty mode");
+  wa->flags = ntohl(wa->flags);
+  if ((remaining==sizeof(wa->flags))&&(wa->flags==0)) {
+    c->brlbufstate = EMPTY;
+    return 0;
+  }
+  remaining -= sizeof(wa->flags); /* flags */
+  CHECKEXC((wa->flags & BRLAPI_WF_DISPLAYNUMBER)==0, BRLAPI_ERROR_OPNOTSUPP, "display number not yet supported");
+  if (wa->flags & BRLAPI_WF_REGION) {
+    int regionSize;
+    CHECKEXC(remaining>2*sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small for region");
+    rbeg = ntohl( *((uint32_t *) p) );
+    p += sizeof(uint32_t); remaining -= sizeof(uint32_t); /* region begin */
+    regionSize = (int32_t) ntohl( *((uint32_t *) p) );
+    p += sizeof(uint32_t); remaining -= sizeof(uint32_t); /* region size */
+    if (regionSize < 0) {
+      CHECKEXC(regionSize != INT32_MIN, BRLAPI_ERROR_INVALID_PARAMETER, "invalid region size");
+      rsiz = -regionSize;
+      fill = true;
+    } else {
+      rsiz = regionSize;
+      fill = false;
+    }
+
+    CHECKEXC(
+      (rbeg >= 1) && (rbeg <= displaySize),
+      BRLAPI_ERROR_INVALID_PARAMETER, "invalid region start"
+    );
+
+    CHECKEXC(
+      (rsiz >= 1) && (rsiz <= displaySize),
+      BRLAPI_ERROR_INVALID_PARAMETER, "invalid region size"
+    );
+
+    CHECKEXC(
+      (rbeg + rsiz - 1 <= displaySize),
+      BRLAPI_ERROR_INVALID_PARAMETER, "invalid region"
+    );
+  } else {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "warning: fd %"PRIfd" uses deprecated regionBegin=0 and regionSize = 0",c->fd);
+    rbeg = 1;
+    rsiz = displaySize;
+    fill = true;
+  }
+  if (wa->flags & BRLAPI_WF_TEXT) {
+    CHECKEXC(remaining>=sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small for text length");
+    textLen = ntohl( *((uint32_t *) p) );
+    p += sizeof(uint32_t); remaining -= sizeof(uint32_t); /* text size */
+    CHECKEXC(remaining>=textLen, BRLAPI_ERROR_INVALID_PACKET, "packet too small for text");
+    text = p;
+    p += textLen; remaining -= textLen; /* text */
+  }
+  if (wa->flags & BRLAPI_WF_ATTR_AND) {
+    CHECKEXC(remaining>=rsiz, BRLAPI_ERROR_INVALID_PACKET, "packet too small for And mask");
+    andAttr = p;
+    p += rsiz; remaining -= rsiz; /* and attributes */
+  }
+  if (wa->flags & BRLAPI_WF_ATTR_OR) {
+    CHECKEXC(remaining>=rsiz, BRLAPI_ERROR_INVALID_PACKET, "packet too small for Or mask");
+    orAttr = p;
+    p += rsiz; remaining -= rsiz; /* or attributes */
+  }
+  if (wa->flags & BRLAPI_WF_CURSOR) {
+    uint32_t u32;
+    CHECKEXC(remaining>=sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small for cursor");
+    memcpy(&u32, p, sizeof(uint32_t));
+    cursor = ntohl(u32);
+    p += sizeof(uint32_t); remaining -= sizeof(uint32_t); /* cursor */
+    CHECKEXC(cursor<=displaySize, BRLAPI_ERROR_INVALID_PACKET, "wrong cursor");
+  }
+  if (wa->flags & BRLAPI_WF_CHARSET) {
+    CHECKEXC(wa->flags & BRLAPI_WF_TEXT, BRLAPI_ERROR_INVALID_PACKET, "charset requires text");
+    CHECKEXC(remaining>=1, BRLAPI_ERROR_INVALID_PACKET, "packet too small for charset length");
+    charsetLen = *p++; remaining--; /* charset length */
+    CHECKEXC(remaining>=charsetLen, BRLAPI_ERROR_INVALID_PACKET, "packet too small for charset");
+    charset = (char *) p;
+    p += charsetLen; remaining -= charsetLen; /* charset name */
+  }
+  CHECKEXC(remaining==0, BRLAPI_ERROR_INVALID_PACKET, "packet too big");
+  /* Here the whole packet has been checked */
+
+  unsigned int rsiz_filled = fill ? displaySize - rbeg + 1 : rsiz;
+
+  if (text) {
+    int isUTF8 = 0;
+    int isLatin1 = 0;
+
+#ifndef HAVE_ALLOCA_H
+    char charsetBuffer[0X20];
+#endif /* HAVE_ALLOCA_H */
+
+    if (charset) {
+      charset[charsetLen] = 0; /* we have room for this */
+    } else {
+      lockCharset(0);
+      const char *name = getCharset();
+
+      if (name) {
+        size_t length = strlen(name);
+
+#ifdef HAVE_ALLOCA_H
+        size_t size = length + 1;
+        charset = alloca(size);
+        strcpy(charset, name);
+#else /* HAVE_ALLOCA_H */
+        if (length < sizeof(charsetBuffer)) {
+          strcpy(charsetBuffer, name);
+          charset = charsetBuffer;
+        }
+#endif /* HAVE_ALLOCA_H */
+
+        if (charset) charsetLen = length;
+      }
+
+      unlockCharset();
+    }
+
+    if (charset) {
+      if (isCharsetUTF8(charset)) {
+        isUTF8 = 1;
+      } else if (isCharsetLatin1(charset)) {
+        isLatin1 = 1;
+      } else {
+#ifndef HAVE_ICONV_H
+        CHECKEXC(0, BRLAPI_ERROR_OPNOTSUPP, "charset conversion not supported (enable iconv?)");
+#endif /* !HAVE_ICONV_H */
+      }
+    }
+
+    size_t end;
+    if (isUTF8) {
+      logConversionDecision(c, "UTF-8", "internal conversion");
+
+      size_t outLeft = rsiz_filled;
+      wchar_t outBuff[outLeft];
+
+      size_t inLeft = textLen;
+      const char *inBuff = (char *)text;
+
+      CHECKEXC(
+        convertFromUTF8(c, outBuff, &outLeft, inBuff, &inLeft),
+        BRLAPI_ERROR_INVALID_PACKET, "invalid charset conversion"
+      );
+
+      if (!fill) {
+	CHECKEXC(!inLeft, BRLAPI_ERROR_INVALID_PACKET, "text too big");
+	CHECKEXC(!outLeft, BRLAPI_ERROR_INVALID_PACKET, "text too small");
+      } else {
+	CHECKEXC(inLeft || (!andAttr && !orAttr) || rsiz_filled - outLeft == rsiz, BRLAPI_ERROR_INVALID_PACKET, "text length does not match and/or mask length");
+      }
+
+      lockMutex(&c->brailleWindowMutex);
+      wmemcpy(c->brailleWindow.text+rbeg-1, outBuff, rsiz_filled - outLeft);
+      end = rbeg-1 + rsiz_filled - outLeft;
+    }
+
+#ifdef HAVE_ICONV_H
+    else if (charset && !isLatin1) {
+      logConversionDecision(c, charset, "iconv conversion");
+
+      size_t len = fill ? textLen : rsiz;
+      wchar_t textBuf[len ? len : 1];
+      char *in = (char *) text, *out = (char *) textBuf;
+      size_t sin = textLen, sout = len * sizeof(wchar_t);
+
+      iconv_t conv = iconv_open(getWcharCharset(), charset);
+      CHECKEXC((conv != (iconv_t)(-1)), BRLAPI_ERROR_INVALID_PACKET, "invalid charset");
+      size_t res = iconv(conv,&in,&sin,&out,&sout);
+      iconv_close(conv);
+
+      CHECKEXC((res != (size_t)-1), BRLAPI_ERROR_INVALID_PACKET, "invalid charset conversion");
+      if (!fill) {
+	CHECKEXC(!sin, BRLAPI_ERROR_INVALID_PACKET, "text too big");
+	CHECKEXC(!sout, BRLAPI_ERROR_INVALID_PACKET, "text too small");
+      } else {
+	CHECKEXC(sin || (!andAttr && !orAttr) || len - sout / sizeof(wchar_t) == rsiz, BRLAPI_ERROR_INVALID_PACKET, "text length does not match and/or mask length");
+      }
+      logConversionResult(c, rsiz, textLen);
+      len -= sout / sizeof(wchar_t);
+      if (len > displaySize - rbeg + 1)
+	len = displaySize - rbeg + 1;
+
+      lockMutex(&c->brailleWindowMutex);
+      wmemcpy(c->brailleWindow.text+rbeg-1, textBuf, len);
+      end = rbeg-1 + len;
+    }
+#endif /* HAVE_ICONV_H */
+
+    else {
+      logConversionDecision(c, "ISO_8859-1", isLatin1 ? "internal conversion" : "assumed");
+      size_t len = textLen;
+      if (!fill) {
+	CHECKEXC(len <= rsiz, BRLAPI_ERROR_INVALID_PACKET, "text too big");
+	CHECKEXC(len >= rsiz, BRLAPI_ERROR_INVALID_PACKET, "text too small");
+      } else {
+	if (len > rsiz_filled)
+	  len = rsiz_filled;
+	else
+	  CHECKEXC((!andAttr && !orAttr) || len == rsiz, BRLAPI_ERROR_INVALID_PACKET, "text length does not match and/or mask length");
+      }
+      lockMutex(&c->brailleWindowMutex);
+      convertFromLatin1(c, rbeg, len, text);
+      end = rbeg-1 + len;
+    }
+    if (fill)
+      wmemset(c->brailleWindow.text+end, L' ', displaySize - end);
+
+    // Forget the charset in case it's pointing to a local buffer.
+    // This occurs when getCharset() is saved and alloca() isn't available.
+    charset = NULL;
+    charsetLen = 0;
+
+    if (!andAttr) memset(c->brailleWindow.andAttr+rbeg-1,0xFF,rsiz_filled);
+    if (!orAttr)  memset(c->brailleWindow.orAttr+rbeg-1,0x00,rsiz_filled);
+    if (fill)     memset(c->brailleWindow.andAttr+rbeg-1+rsiz,0x00,rsiz_filled-rsiz);
+  } else {
+    lockMutex(&c->brailleWindowMutex);
+  }
+
+  if (andAttr) {
+    memcpy(c->brailleWindow.andAttr+rbeg-1,andAttr,rsiz);
+    memset(c->brailleWindow.andAttr+rbeg-1+rsiz,0X00,rsiz_filled-rsiz);
+  }
+  if (orAttr) {
+    memcpy(c->brailleWindow.orAttr+rbeg-1,orAttr,rsiz);
+    memset(c->brailleWindow.orAttr+rbeg-1+rsiz,0X00,rsiz_filled-rsiz);
+  }
+  if (cursor >= 0) c->brailleWindow.cursor = cursor;
+
+  c->brlbufstate = TODISPLAY;
+  unlockMutex(&c->brailleWindowMutex);
+  flushOutput();
+  return 0;
+}
+
+static int checkDriverSpecificModePacket(Connection *c, brlapi_packet_t *packet, size_t size)
+{
+  brlapi_getDriverSpecificModePacket_t *getDevicePacket = &packet->getDriverSpecificMode;
+  int remaining = size;
+  CHECKERR(remaining>sizeof(uint32_t), BRLAPI_ERROR_INVALID_PACKET, "packet too small");
+  remaining -= sizeof(uint32_t);
+  CHECKERR(ntohl(getDevicePacket->magic)==BRLAPI_DEVICE_MAGIC,BRLAPI_ERROR_INVALID_PARAMETER, "wrong magic number");
+  remaining--;
+  CHECKERR(getDevicePacket->nameLength<=BRLAPI_MAXNAMELENGTH && getDevicePacket->nameLength == strlen(trueBraille->definition.name), BRLAPI_ERROR_INVALID_PARAMETER, "wrong driver length");
+  CHECKERR(remaining==getDevicePacket->nameLength, BRLAPI_ERROR_INVALID_PACKET, "wrong packet size");
+  CHECKERR(((!strncmp(&getDevicePacket->name, trueBraille->definition.name, remaining))), BRLAPI_ERROR_INVALID_PARAMETER, "wrong driver name");
+  return 1;
+}
+
+static int handleEnterRawMode(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKERR(!c->raw, BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed in raw mode");
+  if (!checkDriverSpecificModePacket(c, packet, size)) return 0;
+  CHECKERR(isRawCapable(trueBraille), BRLAPI_ERROR_OPNOTSUPP, "driver doesn't support Raw mode");
+  lockMutex(&apiRawMutex);
+  if (rawConnection || suspendConnection) {
+    WERR(c->fd,BRLAPI_ERROR_DEVICEBUSY,"driver busy (%s)", rawConnection?"raw":"suspend");
+    unlockMutex(&apiRawMutex);
+    return 0;
+  }
+  rawConnection = c;
+  unlockMutex(&apiRawMutex);
+  if (!resumeDriver()) {
+    WERR(c->fd, BRLAPI_ERROR_DRIVERERROR,"driver resume error");
+    return 0;
+  }
+  c->raw = 1;
+  writeAck(c->fd);
+  return 0;
+}
+
+static int handleLeaveRawMode(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKERR(c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of raw mode");
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "fd %"PRIfd" going out of raw mode",c->fd);
+  c->raw = 0;
+  lockMutex(&apiRawMutex);
+  rawConnection = NULL;
+  unlockMutex(&apiRawMutex);
+  writeAck(c->fd);
+  return 0;
+}
+
+static int handlePacket(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKEXC(c->raw,BRLAPI_ERROR_ILLEGAL_INSTRUCTION,"not allowed out of raw mode");
+  lockMutex(&apiDriverMutex);
+  trueBraille->writePacket(disp,&packet->data,size);
+  unlockMutex(&apiDriverMutex);
+  return 0;
+}
+
+static int handleSuspendDriver(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  if (!checkDriverSpecificModePacket(c, packet, size)) return 0;
+  CHECKERR(!c->suspend,BRLAPI_ERROR_ILLEGAL_INSTRUCTION, "not allowed in suspend mode");
+  lockMutex(&apiRawMutex);
+  if (suspendConnection || rawConnection) {
+    WERR(c->fd, BRLAPI_ERROR_DEVICEBUSY,"driver busy (%s)", rawConnection?"raw":"suspend");
+    unlockMutex(&apiRawMutex);
+    return 0;
+  }
+  suspendConnection = c;
+  unlockMutex(&apiRawMutex);
+  c->suspend = 1;
+  suspendDriver();
+  writeAck(c->fd);
+  return 0;
+}
+
+static int handleResumeDriver(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  CHECKERR(c->suspend,BRLAPI_ERROR_ILLEGAL_INSTRUCTION, "not allowed out of suspend mode");
+  c->suspend = 0;
+  lockMutex(&apiRawMutex);
+  suspendConnection = NULL;
+  unlockMutex(&apiRawMutex);
+  resumeDriver();
+  writeAck(c->fd);
+  return 0;
+}
+
+static brlapi_keyCode_t makeDriverKeyCode (KeyGroup group, KeyNumber number, int press)
+{
+  brlapi_keyCode_t code =
+    ((group << BRLAPI_DRV_KEY_GROUP_SHIFT) & BRLAPI_DRV_KEY_GROUP_MASK) |
+    ((number << BRLAPI_DRV_KEY_NUMBER_SHIFT) & BRLAPI_DRV_KEY_NUMBER_MASK);
+
+  if (press) code |= BRLAPI_DRV_KEY_PRESS;
+  return code;
+}
+
+/* On success, this should fill 'data', adjust 'size', and return NULL.
+ * On failure, this should return a non-allocated error message string. */
+#define PARAM_READER_DECLARATION(name) const char *name (Connection *c, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *data, size_t *size)
+typedef PARAM_READER_DECLARATION(ParamReader);
+#define PARAM_READER(name) static PARAM_READER_DECLARATION(param_ ## name ## _read)
+
+/* On success, this should return NULL.
+ * On failure, this should return a non-allocated error message string. */
+#define PARAM_WRITER_DECLARATION(name) const char *name (Connection *c, brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void *data, size_t size)
+typedef PARAM_WRITER_DECLARATION(ParamWriter);
+#define PARAM_WRITER(name) static PARAM_WRITER_DECLARATION(param_ ## name ## _write)
+
+#define PARAM_ASSERT(condition) \
+do { \
+  if (!(condition)) return "assertion failed: " #condition; \
+} while (0)
+
+#define PARAM_ASSERT_SIZE(pointer) PARAM_ASSERT(size == sizeof(*pointer))
+
+static void param_readString(const char *string, void *data, size_t *size)
+{
+  if (!string) string = "";
+  size_t length = strlen(string);
+  if (length < *size) *size = length;
+  memcpy(data, string, *size);
+}
+
+static const char *param_writeString(int (*handler) (const char *string), const void *data, size_t size)
+{
+  char string[size + 1];
+  memcpy(string, data, size);
+  string[size] = 0;
+
+  if (handler(string)) return NULL;
+  return "set string parameter failed";
+}
+
+/* BRLAPI_PARAM_SERVER_VERSION */
+PARAM_READER(serverVersion)
+{
+  brlapi_param_serverVersion_t *serverVersion = data;
+  *size = sizeof(*serverVersion);
+  *serverVersion = BRLAPI_PROTOCOL_VERSION;
+  return NULL;
+}
+
+/* BRLAPI_PARAM_CLIENT_PRIORITY */
+PARAM_READER(clientPriority)
+{
+  brlapi_param_clientPriority_t *clientPriority = data;
+  *size = sizeof(*clientPriority);
+  *clientPriority = c->client_priority;
+  return NULL;
+}
+
+PARAM_WRITER(clientPriority)
+{
+  const brlapi_param_clientPriority_t *clientPriority = data;
+  PARAM_ASSERT_SIZE(clientPriority);
+
+  lockMutex(&apiConnectionsMutex);
+    c->client_priority = *clientPriority;
+
+    if (c->tty) {
+      __removeConnection(c);
+      __addConnectionSorted(c,c->tty->connections);
+    }
+  unlockMutex(&apiConnectionsMutex);
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DRIVER_NAME */
+PARAM_READER(driverName)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      param_readString(braille->definition.name, data, size);
+    } else {
+      *size = 0;
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DRIVER_CODE */
+PARAM_READER(driverCode)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      param_readString(braille->definition.code, data, size);
+    } else {
+      *size = 0;
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DRIVER_VERSION */
+PARAM_READER(driverVersion)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      param_readString(braille->definition.version, data, size);
+    } else {
+      *size = 0;
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEVICE_MODEL */
+PARAM_READER(deviceModel)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      const char *deviceModel = brl.keyBindings;
+
+      if (deviceModel) {
+        param_readString(deviceModel, data, size);
+        goto unlock;
+      }
+    }
+
+    *size = 0;
+unlock:
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEVICE_CELL_SIZE */
+PARAM_READER(deviceCellSize)
+{
+  brlapi_param_deviceCellSize_t *deviceCellSize = data;
+  *size = sizeof(*deviceCellSize);
+
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      *deviceCellSize = brl.cellSize;
+    } else {
+      *deviceCellSize = 0;
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DISPLAY_SIZE */
+PARAM_READER(displaySize)
+{
+  brlapi_param_displaySize_t *displaySize = data;
+  *size = sizeof(*displaySize);
+
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      displaySize->columns = brl.textColumns;
+      displaySize->rows = brl.textRows;
+    } else {
+      displaySize->columns = 0;
+      displaySize->rows = 0;
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEVICE_IDENTIFIER */
+PARAM_READER(deviceIdentifier)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      GioEndpoint *endpoint = brl.gioEndpoint;
+
+      if (endpoint) {
+        gioMakeResourceIdentifier(endpoint, data, *size);
+        *size = strlen(data);
+        goto unlock;
+      }
+    }
+
+    *size = 0;
+unlock:
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEVICE_SPEED */
+PARAM_READER(deviceSpeed)
+{
+  brlapi_param_deviceSpeed_t *deviceSpeed = data;
+  *size = sizeof(*deviceSpeed);
+  *deviceSpeed = 0;
+
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      GioEndpoint *endpoint = brl.gioEndpoint;
+
+      if (endpoint) {
+        *deviceSpeed = gioGetBytesPerSecond(endpoint);
+      }
+    }
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEVICE_ONLINE */
+PARAM_READER(deviceOnline)
+{
+  brlapi_param_deviceOnline_t *deviceOnline = data;
+  *size = sizeof(*deviceOnline);
+
+  lockBrailleDriver();
+    *deviceOnline = isBrailleOnline();
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_RETAIN_DOTS */
+PARAM_READER(retainDots)
+{
+  brlapi_param_retainDots_t *retainDots = data;
+  *size = sizeof(*retainDots);
+  *retainDots = c->retainDots;
+  return NULL;
+}
+
+PARAM_WRITER(retainDots)
+{
+  const brlapi_param_retainDots_t *retainDots = data;
+  PARAM_ASSERT_SIZE(retainDots);
+  c->retainDots = *retainDots;
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE */
+PARAM_READER(computerBrailleCellSize)
+{
+  brlapi_param_computerBrailleCellSize_t *computerBrailleCellSize = data;
+  *size = sizeof(*computerBrailleCellSize);
+  *computerBrailleCellSize = isSixDotComputerBraille()? 6: 8;
+  return NULL;
+}
+
+PARAM_WRITER(computerBrailleCellSize)
+{
+  const brlapi_param_computerBrailleCellSize_t *computerBrailleCellSize = data;
+  PARAM_ASSERT_SIZE(computerBrailleCellSize);
+
+  setSixDotComputerBraille(*computerBrailleCellSize <= 6);
+  return NULL;
+}
+
+/* BRLAPI_PARAM_LITERARY_BRAILLE */
+PARAM_READER(literaryBraille)
+{
+  brlapi_param_literaryBraille_t *literaryBraille = data;
+  *size = sizeof(*literaryBraille);
+  *literaryBraille = isContractedBraille();
+  return NULL;
+}
+
+PARAM_WRITER(literaryBraille)
+{
+  const brlapi_param_literaryBraille_t *literaryBraille = data;
+  PARAM_ASSERT_SIZE(literaryBraille);
+  setContractedBraille(*literaryBraille);
+  return NULL;
+}
+
+/* BRLAPI_PARAM_CURSOR_DOTS */
+PARAM_READER(cursorDots)
+{
+  brlapi_param_cursorDots_t *cursorDots = data;
+  *size = sizeof(*cursorDots);
+  *cursorDots = getScreenCursorDots();
+  return NULL;
+}
+
+PARAM_WRITER(cursorDots)
+{
+  const brlapi_param_cursorDots_t *cursorDots = data;
+  PARAM_ASSERT_SIZE(cursorDots);
+  if (!setScreenCursorDots(*cursorDots)) return "unsupported cursor style";
+  return NULL;
+}
+
+/* BRLAPI_PARAM_CURSOR_BLINK_PERIOD */
+PARAM_READER(cursorBlinkPeriod)
+{
+  brlapi_param_cursorBlinkPeriod_t *cursorBlinkPeriod = data;
+  *size = sizeof(*cursorBlinkPeriod);
+  *cursorBlinkPeriod = getBlinkPeriod(&screenCursorBlinkDescriptor);
+  return NULL;
+}
+
+PARAM_WRITER(cursorBlinkPeriod)
+{
+  const brlapi_param_cursorBlinkPeriod_t *cursorBlinkPeriod = data;
+  PARAM_ASSERT_SIZE(cursorBlinkPeriod);
+  if (!setBlinkPeriod(&screenCursorBlinkDescriptor, *cursorBlinkPeriod)) return "unsupported cursor blink period";
+  return NULL;
+}
+
+/* BRLAPI_PARAM_CURSOR_BLINK_PERCENTAGE */
+PARAM_READER(cursorBlinkPercentage)
+{
+  brlapi_param_cursorBlinkPercentage_t *cursorBlinkPercentage = data;
+  *size = sizeof(*cursorBlinkPercentage);
+  *cursorBlinkPercentage = getBlinkPercentVisible(&screenCursorBlinkDescriptor);
+  return NULL;
+}
+
+PARAM_WRITER(cursorBlinkPercentage)
+{
+  const brlapi_param_cursorBlinkPercentage_t *cursorBlinkPercentage = data;
+  PARAM_ASSERT_SIZE(cursorBlinkPercentage);
+  if (!setBlinkPercentVisible(&screenCursorBlinkDescriptor, *cursorBlinkPercentage)) return "unsupported cursor blink percentage";
+  return NULL;
+}
+
+/* BRLAPI_PARAM_RENDERED_CELLS */
+PARAM_READER(renderedCells)
+{
+  lockMutex(&apiDriverMutex);
+    if (disp && c->brailleWindow.text) {
+      unsigned char buffer[displaySize];
+      getDots(&c->brailleWindow, buffer);
+
+      if (displaySize < *size) *size = displaySize;
+      memcpy(data, buffer, *size);
+    } else {
+      *size = 0;
+    }
+  unlockMutex(&apiDriverMutex);
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_SKIP_IDENTICAL_LINES */
+PARAM_READER(skipIdenticalLines)
+{
+  brlapi_param_skipIdenticalLines_t *skipIdenticalLines = data;
+  *size = sizeof(*skipIdenticalLines);
+  *skipIdenticalLines = !!prefs.skipIdenticalLines;
+  return NULL;
+}
+
+PARAM_WRITER(skipIdenticalLines)
+{
+  const brlapi_param_skipIdenticalLines_t *skipIdenticalLines = data;
+  PARAM_ASSERT_SIZE(skipIdenticalLines);
+  prefs.skipIdenticalLines = !!*skipIdenticalLines;
+  api_updateParameter(BRLAPI_PARAM_SKIP_IDENTICAL_LINES, 0);
+  return NULL;
+}
+
+/* BRLAPI_PARAM_AUDIBLE_ALERTS */
+PARAM_READER(audibleAlerts)
+{
+  brlapi_param_audibleAlerts_t *audibleAlerts = data;
+  *size = sizeof(*audibleAlerts);
+  *audibleAlerts = !!prefs.alertTunes;
+  return NULL;
+}
+
+PARAM_WRITER(audibleAlerts)
+{
+  const brlapi_param_audibleAlerts_t *audibleAlerts = data;
+  PARAM_ASSERT_SIZE(audibleAlerts);
+  prefs.alertTunes = !!*audibleAlerts;
+  api_updateParameter(BRLAPI_PARAM_AUDIBLE_ALERTS, 0);
+  return NULL;
+}
+
+/* BRLAPI_PARAM_CLIPBOARD_CONTENT */
+PARAM_READER(clipboardContent)
+{
+  char *content = getMainClipboardContent();
+  if (!content) return "no memory";
+
+  param_readString(content, data, size);
+  free(content);
+  return NULL;
+}
+
+PARAM_WRITER(clipboardContent)
+{
+  return param_writeString(setMainClipboardContent, data, size);
+}
+
+/* BRLAPI_PARAM_BOUND_COMMAND_KEYCODES */
+PARAM_READER(boundCommandKeycodes)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      KeyTable *table = brl.keyTable;
+
+      if (table) {
+        unsigned int count;
+        int *commands = getBoundCommands(table, &count);
+
+        if (commands) {
+          *size /= sizeof(brlapi_param_commandKeycode_t);
+          *size *= sizeof(brlapi_param_commandKeycode_t);
+
+          void *next = data;
+          const void *end = next + *size;
+
+          for (unsigned int i=0; i<count; i+=1) {
+            if (next == end) break;
+            brlapi_keyCode_t keyCode;
+
+            if (cmdBrlttyToBrlapi(&keyCode, commands[i], c->retainDots)) {
+              brlapi_param_commandKeycode_t *commandCode = next;
+              *commandCode = keyCode;
+              next += sizeof(*commandCode);
+            }
+          }
+
+          *size = next - data;
+          free(commands);
+          goto unlock;
+        }
+      }
+    }
+
+    *size = 0;
+unlock:
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMMAND_KEYCODE_NAME */
+PARAM_READER(commandKeycodeName)
+{
+  int command = cmdBrlapiToBrltty(subparam);
+
+  if (command == EOF) {
+    *size = 0;
+  } else {
+    describeCommand(data, *size, command, CDO_IncludeName);
+    char *colon = strchr(data, ':');
+    if (colon) *colon = 0;
+    *size = strlen(data);
+  }
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY */
+PARAM_READER(commandKeycodeSummary)
+{
+  int command = cmdBrlapiToBrltty(subparam);
+
+  if (command == EOF) {
+    *size = 0;
+  } else {
+    describeCommand(data, *size, command, 0);
+    *size = strlen(data);
+  }
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES */
+typedef struct {
+  void *next;
+  const void *end;
+} param_addKeyCode_t;
+
+static int param_addKeyCode (const KeyNameEntry *kne, void *data)
+{
+  if (kne) {
+    param_addKeyCode_t *akc = data;
+
+    if (akc->next < akc->end) {
+      brlapi_param_driverKeycode_t *key = akc->next;
+      *key = makeDriverKeyCode(kne->value.group, kne->value.number, 0);
+      akc->next += sizeof(*key);
+    }
+  }
+
+  return 1;
+}
+
+PARAM_READER(definedDriverKeycodes)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      KEY_NAME_TABLES_REFERENCE keys = brl.keyNames;
+
+      if (keys) {
+        *size /= sizeof(brlapi_param_driverKeycode_t);
+        *size *= sizeof(brlapi_param_driverKeycode_t);
+
+        param_addKeyCode_t akc = {
+          .next = data,
+          .end = data + *size
+        };
+
+        forEachKeyName(keys, param_addKeyCode, &akc);
+        *size = akc.next - data;
+        goto unlock;
+      }
+    }
+
+    *size = 0;
+unlock:
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DRIVER_KEYCODE_NAME */
+PARAM_READER(driverKeycodeName)
+{
+  lockBrailleDriver();
+    if (isBrailleDriverConstructed()) {
+      KeyTable *table = brl.keyTable;
+
+      if (table) {
+        if (subparam <= UINT16_MAX) {
+          const KeyValue keyValue = {
+            .group = subparam >> 8,
+            .number = subparam & 0XFF
+          };
+
+          const KeyNameEntry *kne = findKeyNameEntry(table, &keyValue);
+          if (kne) {
+            param_readString(kne->name, data, size);
+            goto unlock;
+          }
+        }
+      }
+    }
+
+    *size = 0;
+unlock:
+  unlockBrailleDriver();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY */
+PARAM_READER(driverKeycodeSummary)
+{
+  *size = 0;
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMPUTER_BRAILLE_ROWS_MASK */
+PARAM_READER(computerBrailleRowsMask)
+{
+  lockTextTable();
+    *size = getTextTableRowsMask(textTable, data, *size);
+  unlockTextTable();
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMPUTER_BRAILLE_ROW_CELLS */
+PARAM_READER(computerBrailleRowCells)
+{
+  brlapi_param_computerBrailleRowCells_t *computerBrailleRowCells = data;
+  int rowDefined;
+
+  lockTextTable();
+    rowDefined = getTextTableRowCells(
+      textTable, subparam,
+      computerBrailleRowCells->cells,
+      computerBrailleRowCells->defined
+    );
+  unlockTextTable();
+
+  if (rowDefined) {
+    *size = sizeof(*computerBrailleRowCells);
+  } else {
+    *size = 0;
+  }
+
+  return NULL;
+}
+
+/* BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE */
+PARAM_READER(computerBrailleTable)
+{
+  param_readString(opt_textTable, data, size);
+  return NULL;
+}
+
+PARAM_WRITER(computerBrailleTable)
+{
+  return param_writeString(changeTextTable, data, size);
+}
+
+/* BRLAPI_PARAM_LITERARY_BRAILLE_TABLE */
+PARAM_READER(literaryBrailleTable)
+{
+  param_readString(opt_contractionTable, data, size);
+  return NULL;
+}
+
+PARAM_WRITER(literaryBrailleTable)
+{
+  return param_writeString(changeContractionTable, data, size);
+}
+
+/* BRLAPI_PARAM_MESSAGE_LOCALE */
+PARAM_READER(messageLocale)
+{
+  param_readString(setlocale(LC_ALL, NULL), data, size);
+  return NULL;
+}
+
+PARAM_WRITER(messageLocale)
+{
+  return param_writeString(changeMessageLocale, data, size);
+}
+
+typedef struct {
+  unsigned local:1;
+  unsigned global:1;
+  brlapi_param_t rootParameter;
+  ParamReader *read;
+  ParamWriter *write;
+} ParamDispatch;
+
+static const ParamDispatch paramDispatch[BRLAPI_PARAM_COUNT] = {
+//Connection Parameters
+  [BRLAPI_PARAM_SERVER_VERSION] = {
+    .global = 1,
+    .read = param_serverVersion_read,
+  },
+
+  [BRLAPI_PARAM_CLIENT_PRIORITY] = {
+    .local = 1,
+    .read = param_clientPriority_read,
+    .write = param_clientPriority_write,
+  },
+
+//Device Parameters
+  [BRLAPI_PARAM_DRIVER_NAME] = {
+    .global = 1,
+    .read = param_driverName_read,
+  },
+
+  [BRLAPI_PARAM_DRIVER_CODE] = {
+    .global = 1,
+    .read = param_driverCode_read,
+  },
+
+  [BRLAPI_PARAM_DRIVER_VERSION] = {
+    .global = 1,
+    .read = param_driverVersion_read,
+  },
+
+  [BRLAPI_PARAM_DEVICE_MODEL] = {
+    .global = 1,
+    .read = param_deviceModel_read,
+  },
+
+  [BRLAPI_PARAM_DEVICE_CELL_SIZE] = {
+    .global = 1,
+    .read = param_deviceCellSize_read,
+  },
+
+  [BRLAPI_PARAM_DISPLAY_SIZE] = {
+    .global = 1,
+    .read = param_displaySize_read,
+  },
+
+  [BRLAPI_PARAM_DEVICE_IDENTIFIER] = {
+    .global = 1,
+    .read = param_deviceIdentifier_read,
+  },
+
+  [BRLAPI_PARAM_DEVICE_SPEED] = {
+    .global = 1,
+    .read = param_deviceSpeed_read,
+  },
+
+  [BRLAPI_PARAM_DEVICE_ONLINE] = {
+    .global = 1,
+    .read = param_deviceOnline_read,
+  },
+
+//Input Parameters
+  [BRLAPI_PARAM_RETAIN_DOTS] = {
+    .local = 1,
+    .read = param_retainDots_read,
+    .write = param_retainDots_write,
+  },
+
+//Braille Rendering Parameters
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE] = {
+    .global = 1,
+    .read = param_computerBrailleCellSize_read,
+    .write = param_computerBrailleCellSize_write,
+  },
+
+  [BRLAPI_PARAM_LITERARY_BRAILLE] = {
+    .global = 1,
+    .read = param_literaryBraille_read,
+    .write = param_literaryBraille_write,
+  },
+
+  [BRLAPI_PARAM_CURSOR_DOTS] = {
+    .global = 1,
+    .read = param_cursorDots_read,
+    .write = param_cursorDots_write,
+  },
+
+  [BRLAPI_PARAM_CURSOR_BLINK_PERIOD] = {
+    .global = 1,
+    .read = param_cursorBlinkPeriod_read,
+    .write = param_cursorBlinkPeriod_write,
+  },
+
+  [BRLAPI_PARAM_CURSOR_BLINK_PERCENTAGE] = {
+    .global = 1,
+    .read = param_cursorBlinkPercentage_read,
+    .write = param_cursorBlinkPercentage_write,
+  },
+
+  [BRLAPI_PARAM_RENDERED_CELLS] = {
+    .local = 1,
+    .read = param_renderedCells_read,
+  },
+
+//Navigation Parameters
+  [BRLAPI_PARAM_SKIP_IDENTICAL_LINES] = {
+    .global = 1,
+    .read = param_skipIdenticalLines_read,
+    .write = param_skipIdenticalLines_write,
+  },
+
+  [BRLAPI_PARAM_AUDIBLE_ALERTS] = {
+    .global = 1,
+    .read = param_audibleAlerts_read,
+    .write = param_audibleAlerts_write,
+  },
+
+//Clipboard Parameters
+  [BRLAPI_PARAM_CLIPBOARD_CONTENT] = {
+    .global = 1,
+    .read = param_clipboardContent_read,
+    .write = param_clipboardContent_write,
+  },
+
+//TTY Mode Parameters
+  [BRLAPI_PARAM_BOUND_COMMAND_KEYCODES] = {
+    .global = 1,
+    .read = param_boundCommandKeycodes_read,
+  },
+
+  [BRLAPI_PARAM_COMMAND_KEYCODE_NAME] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_BOUND_COMMAND_KEYCODES,
+    .read = param_commandKeycodeName_read,
+  },
+
+  [BRLAPI_PARAM_COMMAND_KEYCODE_SUMMARY] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_BOUND_COMMAND_KEYCODES,
+    .read = param_commandKeycodeSummary_read,
+  },
+
+  [BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES] = {
+    .global = 1,
+    .read = param_definedDriverKeycodes_read,
+  },
+
+  [BRLAPI_PARAM_DRIVER_KEYCODE_NAME] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES,
+    .read = param_driverKeycodeName_read,
+  },
+
+  [BRLAPI_PARAM_DRIVER_KEYCODE_SUMMARY] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_DEFINED_DRIVER_KEYCODES,
+    .read = param_driverKeycodeSummary_read,
+  },
+
+//Braille Translation Parameters
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_ROWS_MASK] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE,
+    .read = param_computerBrailleRowsMask_read,
+  },
+
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_ROW_CELLS] = {
+    .global = 1,
+    .rootParameter = BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE,
+    .read = param_computerBrailleRowCells_read,
+  },
+
+  [BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE] = {
+    .global = 1,
+    .read = param_computerBrailleTable_read,
+    .write = param_computerBrailleTable_write,
+  },
+
+  [BRLAPI_PARAM_LITERARY_BRAILLE_TABLE] = {
+    .global = 1,
+    .read = param_literaryBrailleTable_read,
+    .write = param_literaryBrailleTable_write,
+  },
+
+  [BRLAPI_PARAM_MESSAGE_LOCALE] = {
+    .global = 1,
+    .read = param_messageLocale_read,
+    .write = param_messageLocale_write,
+  },
+};
+
+static inline const ParamDispatch *param_getDispatch(brlapi_param_t parameter)
+{
+  if (parameter >= ARRAY_COUNT(paramDispatch)) return NULL;
+  return &paramDispatch[parameter];
+}
+
+static int checkParamLocalGlobal(Connection *c, brlapi_param_t param, brlapi_param_flags_t flags)
+{
+  if (flags & BRLAPI_PARAMF_GLOBAL) {
+    if (!paramDispatch[param].global) {
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u does not make sense globally", param);
+      return 0;
+    }
+  } else {
+    if (!paramDispatch[param].local) {
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u does not make sense locally", param);
+      return 0;
+    }
+  }
+  return 1;
+}
+
+static int handleParamValue(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  brlapi_paramValuePacket_t *paramValue = &packet->paramValue;
+  brlapi_param_t param;
+  brlapi_param_subparam_t subparam;
+  brlapi_param_flags_t flags;
+
+  CHECKERR( (size >= sizeof(flags) + sizeof(param) + sizeof(subparam)), BRLAPI_ERROR_INVALID_PACKET, "wrong size for paramValue packet");
+  flags = ntohl(paramValue->flags);
+  param = ntohl(paramValue->param);
+
+  if (param >= sizeof(paramDispatch) / sizeof(*paramDispatch)) {
+    WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "unknown parameter %u", param);
+    return 0;
+  }
+
+  ParamWriter *writeHandler = paramDispatch[param].write;
+  /* Check against read-only parameters */
+  if (!writeHandler) {
+    WERR(c->fd, BRLAPI_ERROR_READONLY_PARAMETER, "parameter %u not available for writing", param);
+    return 0;
+  }
+
+  if (!checkParamLocalGlobal(c, param, flags))
+    return 0;
+
+  subparam = ((brlapi_param_subparam_t)ntohl(paramValue->subparam_hi) << 32) || ntohl(paramValue->subparam_lo);
+  size -= sizeof(flags) + sizeof(param) + sizeof(subparam);
+  _brlapi_ntohParameter(param, paramValue, size);
+
+  {
+    const char *error;
+
+    lockMutex(&apiParamMutex);
+    paramUpdateConnection = c;
+    error = writeHandler(c, param, subparam, flags, paramValue->data, size);
+    paramUpdateConnection = NULL;
+    unlockMutex(&apiParamMutex);
+
+    if (error) {
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u write error: %s", param, error);
+      return 0;
+    }
+  }
+
+  if (!(flags & BRLAPI_PARAMF_GLOBAL)) {
+    handleParamUpdate(c, c, param, subparam, flags, paramValue->data, size);
+  }
+  writeAck(c->fd);
+  return 0;
+}
+
+
+/* sendConnectionParamUpdate: Send the parameter update to a connection */
+static void sendConnectionParamUpdate(Connection *c, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramValuePacket_t *paramValue, size_t size)
+{
+  struct Subscription *s;
+
+  for (s=c->subscriptions.next; s!=&c->subscriptions; s=s->next) {
+    if (s->parameter == param
+	&& s->subparam == subparam
+	&& (s->flags & BRLAPI_PARAMF_GLOBAL) == (flags & BRLAPI_PARAMF_GLOBAL)
+	&& ((s->flags & BRLAPI_PARAMF_SELF) || (paramUpdateConnection != c)))
+    {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "writing parameter %"PRIx32" update to fd %"PRIfd,param,c->fd);
+      brlapiserver_writePacket(c->fd,BRLAPI_PACKET_PARAM_UPDATE,paramValue,size);
+      break;
+    }
+  }
+}
+
+/* sendParamUpdate: Send the parameter update to connections bound to a given tree of ttys */
+static void sendParamUpdate(Tty *tty, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, brlapi_paramValuePacket_t *paramValue, size_t size)
+{
+  Connection *c;
+  Tty *t;
+
+  for (c = tty->connections->next; c!=tty->connections; c = c->next)
+    sendConnectionParamUpdate(c, param, subparam, flags, paramValue, size);
+  for (t = tty->subttys; t; t = t->next)
+    sendParamUpdate(t, param, subparam, flags, paramValue, size);
+}
+
+/* handleParamUpdate: Prepare and send the parameter update to all connections */
+static void __handleParamUpdate(Connection *dest, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void *data, size_t size)
+{
+  brlapi_packet_t response;
+  brlapi_paramValuePacket_t *paramValue = &response.paramValue;
+  unsigned char *p = paramValue->data;
+  paramValue->flags = htonl(flags);
+  paramValue->param = htonl(param);
+  paramValue->subparam_hi = htonl(subparam >> 32);
+  paramValue->subparam_lo = htonl(subparam & 0xfffffffful);
+  memcpy(p, data, size);
+  _brlapi_htonParameter(param, paramValue, size);
+  size += sizeof(flags) + sizeof(param) + sizeof(subparam);
+  if (!(flags & BRLAPI_PARAMF_GLOBAL)) {
+    sendConnectionParamUpdate(dest,param,subparam,flags,paramValue,size);
+  } else {
+    lockMutex(&apiConnectionsMutex);
+    sendParamUpdate(&ttys,param,subparam,flags,paramValue,size);
+    sendParamUpdate(&notty,param,subparam,flags,paramValue,size);
+    unlockMutex(&apiConnectionsMutex);
+  }
+}
+
+static void handleParamUpdate(Connection *source, Connection *dest, brlapi_param_t param, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, const void *data, size_t size)
+{
+  lockMutex(&apiParamMutex);
+  paramUpdateConnection = source;
+  if (((flags & BRLAPI_PARAMF_GLOBAL) && paramState[param].global_subscriptions)
+  || (!(flags & BRLAPI_PARAMF_GLOBAL) && paramState[param].local_subscriptions))
+    __handleParamUpdate(dest, param, subparam, flags, data, size);
+  paramUpdateConnection = NULL;
+  unlockMutex(&apiParamMutex);
+}
+
+void api_updateParameter(brlapi_param_t parameter, brlapi_param_subparam_t subparam)
+{
+  const ParamDispatch *pd = param_getDispatch(parameter);
+
+  if (pd) {
+    if (pd->global) {
+      ParamReader *readHandler = pd->read;
+
+      if (readHandler) {
+        lockMutex(&apiParamMutex);
+        {
+          if (paramState[parameter].global_subscriptions) {
+            unsigned char data[BRLAPI_MAXPARAMSIZE];
+            size_t size = sizeof(data);
+            const char *error = readHandler(NULL, parameter, subparam, BRLAPI_PARAMF_GLOBAL, data, &size);
+
+            if (error) {
+              logMessage(LOG_CATEGORY(SERVER_EVENTS), "parameter %u read error: %s", parameter, error);
+            } else {
+              __handleParamUpdate(NULL, parameter, subparam, BRLAPI_PARAMF_GLOBAL, data, size);
+            }
+          }
+        }
+        unlockMutex(&apiParamMutex);
+
+        reportParameterUpdated(parameter, subparam);
+      } else {
+        logMessage(LOG_CATEGORY(SERVER_EVENTS), "parameter %u is not readable", parameter);
+      }
+    } else {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "parameter %u is not global", parameter);
+    }
+  } else {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "parameter %u is out of range", parameter);
+  }
+}
+
+static int handleParamRequest(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  brlapi_paramRequestPacket_t *paramRequest = &packet->paramRequest;
+  brlapi_param_t param;
+  brlapi_param_subparam_t subparam;
+  brlapi_param_flags_t flags;
+
+  CHECKERR( (size == sizeof(brlapi_paramRequestPacket_t)), BRLAPI_ERROR_INVALID_PACKET, "wrong size for paramRequest packet: %lu vs %lu", (unsigned long) size, (unsigned long) sizeof(brlapi_paramRequestPacket_t));
+  flags = ntohl(paramRequest->flags);
+  param = ntohl(paramRequest->param);
+
+  if (param >= sizeof(paramDispatch) / sizeof(*paramDispatch)) {
+    WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "unknown parameter %u", param);
+    return 0;
+  }
+
+  ParamReader *readHandler = paramDispatch[param].read;
+  /* Check against non-readable parameters */
+  if (!readHandler) {
+    WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u not available for reading", param);
+    return 0;
+  }
+
+  if (!checkParamLocalGlobal(c, param, flags))
+    return 0;
+
+  subparam = (brlapi_param_subparam_t)ntohl(paramRequest->subparam_hi) << 32 | ntohl(paramRequest->subparam_lo);
+  if ((flags & BRLAPI_PARAMF_SUBSCRIBE) &&
+      (flags & BRLAPI_PARAMF_UNSUBSCRIBE)) {
+    WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "subscribe and unsubscribe flags both set");
+    return 0;
+  }
+  lockMutex(&apiParamMutex);
+  if (flags & BRLAPI_PARAMF_SUBSCRIBE) {
+    /* subscribe to parameter updates */
+
+    {
+      brlapi_param_t root = paramDispatch[param].rootParameter;
+
+      if (root) {
+        WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u not available for watching - %u should be watched instead", param, root);
+        unlockMutex(&apiParamMutex);
+        return 0;
+      }
+    }
+
+    struct Subscription *s;
+    lockMutex(&apiConnectionsMutex);
+    if (flags & BRLAPI_PARAMF_GLOBAL)
+      paramState[param].global_subscriptions++;
+    else
+      paramState[param].local_subscriptions++;
+    s = malloc(sizeof(*s));
+    s->parameter = param;
+    s->subparam = subparam;
+    s->flags = flags;
+    s->next = c->subscriptions.next;
+    s->prev = &c->subscriptions;
+    s->next->prev = s;
+    s->prev->next = s;
+    unlockMutex(&apiConnectionsMutex);
+  } else if (flags & BRLAPI_PARAMF_UNSUBSCRIBE) {
+    /* unsubscribe from parameter updates */
+    struct Subscription *s;
+    lockMutex(&apiConnectionsMutex);
+    for (s = c->subscriptions.next; s!=&c->subscriptions; s=s->next) {
+      if (s->parameter == param
+	  && (s->flags & BRLAPI_PARAMF_GLOBAL) == (flags & BRLAPI_PARAMF_GLOBAL))
+	break;
+    }
+    if (s != &c->subscriptions) {
+      if (flags & BRLAPI_PARAMF_GLOBAL)
+        paramState[param].global_subscriptions--;
+      else
+        paramState[param].local_subscriptions--;
+      s->next->prev = s->prev;
+      s->prev->next = s->next;
+      free(s);
+    } else {
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "was not subscribed");
+      unlockMutex(&apiParamMutex);
+      unlockMutex(&apiConnectionsMutex);
+      return 0;
+    }
+    unlockMutex(&apiConnectionsMutex);
+  }
+  if (flags & BRLAPI_PARAMF_GET) { /* Ack by sending parameter value */
+    brlapi_packet_t response;
+    brlapi_paramValuePacket_t *paramValue = &response.paramValue;
+    paramValue->flags = htonl(flags & BRLAPI_PARAMF_GLOBAL);
+    paramValue->param = paramRequest->param;
+    paramValue->subparam_hi = paramRequest->subparam_hi;
+    paramValue->subparam_lo = paramRequest->subparam_lo;
+    size = sizeof(paramValue->data);
+    const char *error = readHandler(c, param, subparam, flags, paramValue->data, &size);
+
+    if (error) {
+      WERR(c->fd, BRLAPI_ERROR_INVALID_PARAMETER, "parameter %u read error: %s", param, error);
+    } else {
+      _brlapi_htonParameter(param, paramValue, size);
+      size += sizeof(flags) + sizeof(param) + sizeof(subparam);
+      brlapiserver_writePacket(c->fd,BRLAPI_PACKET_PARAM_VALUE,paramValue,size);
+    }
+  } else { /* Ack with ack */
+    writeAck(c->fd);
+  }
+  unlockMutex(&apiParamMutex);
+  return 0;
+}
+
+static int handleSync(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  writeAck(c->fd);
+  return 0;
+}
+
+static PacketHandlers packetHandlers = {
+  handleGetDriverName, handleGetModelIdentifier, handleGetDisplaySize,
+  handleEnterTtyMode, handleSetFocus, handleLeaveTtyMode,
+  handleKeyRanges, handleKeyRanges, handleWrite,
+  handleEnterRawMode, handleLeaveRawMode, handlePacket,
+  handleSuspendDriver, handleResumeDriver,
+  handleParamValue, handleParamRequest,
+  handleSync,
+};
+
+static void handleNewConnection(Connection *c)
+{
+  brlapi_packet_t versionPacket;
+  versionPacket.version.protocolVersion = htonl(BRLAPI_PROTOCOL_VERSION);
+
+  brlapiserver_writePacket(c->fd,BRLAPI_PACKET_VERSION,&versionPacket.data,sizeof(versionPacket.version));
+}
+
+static int
+hasKeyFile(const char *auth)
+{
+  if (isAbsolutePath(auth))
+    return 1;
+  if (!strncmp(auth,"keyfile:", 8))
+    return 1;
+  if (strstr(auth,"+keyfile:"))
+    return 1;
+  return 0;
+}
+
+/* Function : handleUnauthorizedConnection */
+/* Returns 1 if connection has to be removed */
+static int handleUnauthorizedConnection(Connection *c, brlapi_packetType_t type, brlapi_packet_t *packet, size_t size)
+{
+  if (c->auth == -1) {
+    if (type != BRLAPI_PACKET_VERSION) {
+      WERR(c->fd, BRLAPI_ERROR_PROTOCOL_VERSION, "wrong packet type (should be version)");
+      return 1;
+    }
+
+    {
+      brlapi_versionPacket_t *versionPacket = &packet->version;
+      brlapi_packet_t serverPacket;
+      brlapi_authServerPacket_t *authPacket = &serverPacket.authServer;
+      int nbmethods = 0;
+
+      if (size<sizeof(*versionPacket)) {
+	WERR(c->fd, BRLAPI_ERROR_PROTOCOL_VERSION, "wrong protocol version");
+	return 1;
+      }
+
+      c->clientVersion = ntohl(versionPacket->protocolVersion);
+      if (c->clientVersion < 8) {
+	/* We only provide compatibility with version 8 and later. */
+	WERR(c->fd, BRLAPI_ERROR_PROTOCOL_VERSION, "protocol version %"PRIu32" < 8 is not supported", c->clientVersion);
+	return 1;
+      }
+
+      /* TODO: move this inside auth.c */
+      if (authDescriptor && authPerform(authDescriptor, c->fd)) {
+	authPacket->type[nbmethods++] = htonl(BRLAPI_AUTH_NONE);
+	unauthConnections--;
+	c->auth = 1;
+      } else {
+	if (hasKeyFile(auth))
+	  authPacket->type[nbmethods++] = htonl(BRLAPI_AUTH_KEY);
+	c->auth = 0;
+      }
+
+      brlapiserver_writePacket(c->fd,BRLAPI_PACKET_AUTH,&serverPacket,nbmethods*sizeof(authPacket->type));
+
+      return 0;
+    }
+  }
+
+  if (type!=BRLAPI_PACKET_AUTH) {
+    WERR(c->fd, BRLAPI_ERROR_PROTOCOL_VERSION, "wrong packet type (should be auth)");
+    return 1;
+  }
+
+  {
+    size_t authKeyLength = 0;
+    brlapi_packet_t authKey;
+    int authCorrect = 0;
+    brlapi_authClientPacket_t *authPacket = &packet->authClient;
+    int remaining = size;
+    uint32_t authType;
+
+    if (!strcmp(auth,"none"))
+      authCorrect = 1;
+    else {
+      authType = ntohl(authPacket->type);
+      remaining -= sizeof(authPacket->type);
+
+    /* TODO: move this inside auth.c */
+      switch(authType) {
+	case BRLAPI_AUTH_NONE:
+	  if (authDescriptor) authCorrect = authPerform(authDescriptor, c->fd);
+	  break;
+	case BRLAPI_AUTH_KEY:
+	  if (hasKeyFile(auth)) {
+	    char *path = brlapiserver_getKeyFile(auth);
+	    int ret = brlapiserver_loadAuthKey(path,&authKeyLength,&authKey);
+	    if (ret==-1) {
+	      logMessage(LOG_WARNING,"Unable to load API authorization key from %s: %s in %s. You may use parameter auth=none if you don't want any authorization (dangerous)", path, strerror(brlapi_libcerrno), brlapi_errfun);
+	      free(path);
+	      break;
+	    }
+	    free(path);
+	    logMessage(LOG_CATEGORY(SERVER_EVENTS), "authorization key loaded");
+	    authCorrect = (remaining==authKeyLength) && (!memcmp(&authPacket->key, &authKey, authKeyLength));
+	    memset(&authKey, 0, authKeyLength);
+	    memset(&authPacket->key, 0, remaining);
+	  }
+	  break;
+	default:
+	  logMessage(LOG_CATEGORY(SERVER_EVENTS), "unsupported authorization method %"PRId32, authType);
+	  break;
+      }
+    }
+
+    if (!authCorrect) {
+      writeError(c->fd, BRLAPI_ERROR_AUTHENTICATION);
+      logMessage(LOG_WARNING, "BrlAPI connection fd=%"PRIfd" failed authorization", c->fd);
+      return 0;
+    }
+
+    unauthConnections--;
+    writeAck(c->fd);
+    c->auth = 1;
+    return 0;
+  }
+}
+
+/* Function : processRequest */
+/* Reads a packet fro c->fd and processes it */
+/* Returns 1 if connection has to be removed */
+/* If EOF is reached, closes fd and frees all associated resources */
+static int processRequest(Connection *c, PacketHandlers *handlers)
+{
+  PacketHandler p = NULL;
+  int res;
+  ssize_t size;
+  brlapi_packet_t *packet = (brlapi_packet_t *) c->packet.content;
+  brlapi_packetType_t type;
+  res = brlapi__readPacket(&c->packet, c->fd);
+  if (res==0) return 0; /* No packet ready */
+  if (res<0) {
+    if (res==-1) {
+      logMessage(LOG_WARNING,"read : %s (connection on fd %"PRIfd")",strerror(errno),c->fd);
+    } else {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "closing connection on fd %"PRIfd,c->fd);
+    }
+    if (c->raw) {
+      c->raw = 0;
+      lockMutex(&apiRawMutex);
+      rawConnection = NULL;
+      unlockMutex(&apiRawMutex);
+      logMessage(LOG_WARNING,"Client on fd %"PRIfd" did not give up raw mode properly",c->fd);
+      resetDevice();
+    } else if (c->suspend) {
+      c->suspend = 0;
+      lockMutex(&apiRawMutex);
+      suspendConnection = NULL;
+      unlockMutex(&apiRawMutex);
+      logMessage(LOG_WARNING,"Client on fd %"PRIfd" did not give up suspended mode properly",c->fd);
+      if (!resumeDriver())
+	logMessage(LOG_WARNING,"Couldn't resume braille driver");
+    }
+    if (c->tty) {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "client on fd %"PRIfd" did not give up control of tty %#010x properly",c->fd,c->tty->number);
+      doLeaveTty(c);
+    }
+    return 1;
+  }
+  size = c->packet.header.size;
+  type = c->packet.header.type;
+
+  if (c->auth!=1) return handleUnauthorizedConnection(c, type, packet, size);
+
+  if (size>BRLAPI_MAXPACKETSIZE) {
+    logMessage(LOG_WARNING, "Discarding too large packet of type %s on fd %"PRIfd,brlapiserver_getPacketTypeName(type), c->fd);
+    return 0;
+  }
+  switch (type) {
+    case BRLAPI_PACKET_GETDRIVERNAME: p = handlers->getDriverName; break;
+    case BRLAPI_PACKET_GETMODELID: p = handlers->getModelIdentifier; break;
+    case BRLAPI_PACKET_GETDISPLAYSIZE: p = handlers->getDisplaySize; break;
+    case BRLAPI_PACKET_ENTERTTYMODE: p = handlers->enterTtyMode; break;
+    case BRLAPI_PACKET_SETFOCUS: p = handlers->setFocus; break;
+    case BRLAPI_PACKET_LEAVETTYMODE: p = handlers->leaveTtyMode; break;
+    case BRLAPI_PACKET_IGNOREKEYRANGES: p = handlers->ignoreKeyRanges; break;
+    case BRLAPI_PACKET_ACCEPTKEYRANGES: p = handlers->acceptKeyRanges; break;
+    case BRLAPI_PACKET_WRITE: p = handlers->write; break;
+    case BRLAPI_PACKET_ENTERRAWMODE: p = handlers->enterRawMode; break;
+    case BRLAPI_PACKET_LEAVERAWMODE: p = handlers->leaveRawMode; break;
+    case BRLAPI_PACKET_PACKET: p = handlers->packet; break;
+    case BRLAPI_PACKET_SUSPENDDRIVER: p = handlers->suspendDriver; break;
+    case BRLAPI_PACKET_RESUMEDRIVER: p = handlers->resumeDriver; break;
+    case BRLAPI_PACKET_PARAM_VALUE: p = handlers->parameterValue; break;
+    case BRLAPI_PACKET_PARAM_REQUEST: p = handlers->parameterRequest; break;
+    case BRLAPI_PACKET_SYNCHRONIZE: p = handlers->sync; break;
+  }
+  if (p!=NULL) {
+    logRequest(type, c->fd);
+    p(c, type, packet, size);
+  } else {
+    WEXC(c->fd,BRLAPI_ERROR_UNKNOWN_INSTRUCTION, type, packet, size, "unknown packet type %x", type);
+  }
+  return 0;
+}
+
+/****************************************************************************/
+/** SOCKETS AND CONNECTIONS MANAGING                                       **/
+/****************************************************************************/
+
+/*
+ * There is one server thread which first launches binding threads and then
+ * enters infinite loop trying to accept connections, read packets, etc.
+ *
+ * Binding threads loop trying to establish some socket, waiting for
+ * filesystems to be read/write or network to be configured.
+ *
+ * On windows, WSAEventSelect() is emulated by a standalone thread.
+ */
+
+/* Function: loopBind */
+/* tries binding while temporary errors occur */
+static int loopBind(SocketDescriptor fd, const struct sockaddr *address, socklen_t length)
+{
+  char buffer[0X100] = {0};
+  const int maximum = 100;
+  int delay = 1;
+  int res;
+
+  while (1) {
+    {
+      lockUmask();
+      mode_t originalMask = umask(0);
+      res = bind(fd, address, length);
+      umask(originalMask);
+      unlockUmask();
+    }
+
+    if (res != -1) break;
+    if (!running) break;
+
+    if (
+#ifdef EADDRNOTAVAIL
+        (errno != EADDRNOTAVAIL) &&
+#endif /* EADDRNOTAVAIL */
+#ifdef EADDRINUSE
+        (errno != EADDRINUSE) &&
+#endif /* EADDRINUSE */
+        (errno != EROFS)) {
+      break;
+    }
+
+    if (!buffer[0]) {
+      formatAddress(buffer, sizeof(buffer), address, length);
+    }
+
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "bind waiting: %s: %s", buffer, strerror(errno));
+    approximateDelay(delay * MSECS_PER_SEC);
+
+    delay <<= 1;
+    delay = MIN(delay, maximum);
+  }
+
+  return res;
+}
+
+static SocketDescriptor newTcpSocket(int family, int type, int protocol, const struct sockaddr *addr, socklen_t len)
+{
+  static const int yes = 1;
+  SocketDescriptor fd = socket(family, type, protocol);
+
+  if (fd != INVALID_SOCKET_DESCRIPTOR) {
+  //setCloseOnExec(fd, 1);
+
+#ifdef SO_REUSEADDR
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+                   (const void *)&yes, sizeof(yes)) == -1) {
+      LogSocketError("setsockopt[SOCKET,REUSEADDR]");
+    }
+#endif /* SO_REUSEADDR */
+
+#ifdef SOL_TCP
+#ifdef TCP_NODELAY
+    if (setsockopt(fd, SOL_TCP, TCP_NODELAY,
+                   (const void *)&yes, sizeof(yes)) == -1) {
+      LogSocketError("setsockopt[TCP,NODELAY]");
+    }
+#endif /* TCP_NODELAY */
+#endif /* SOL_TCP */
+
+    if (loopBind(fd, addr, len) != -1) {
+      if (listen(fd, 1) != -1) {
+        return fd;
+      } else {
+        LogSocketError("listen");
+      }
+    } else {
+      LogSocketError("bind");
+    }
+
+    closeSocketDescriptor(fd);
+  } else {
+    setSocketErrno();
+
+#ifdef EAFNOSUPPORT
+    if (errno != EAFNOSUPPORT)
+#endif /* EAFNOSUPPORT */
+      logMessage(LOG_WARNING, "socket allocation error: %s", strerror(errno));
+  }
+
+  return INVALID_SOCKET_DESCRIPTOR;
+}
+
+/* Function : createTcpSocket */
+/* Creates the listening socket for in-connections */
+/* Returns the descriptor, or -1 if an error occurred */
+static FileDescriptor createTcpSocket(struct socketInfo *info)
+{
+  SocketDescriptor fd = INVALID_SOCKET_DESCRIPTOR;
+
+#ifdef __MINGW32__
+  if (getaddrinfoProc) {
+#endif /* __MINGW32__ */
+
+#if defined(HAVE_GETADDRINFO) || defined(__MINGW32__)
+  static const struct addrinfo hints = {
+    .ai_flags = AI_PASSIVE,
+    .ai_family = PF_UNSPEC,
+    .ai_socktype = SOCK_STREAM
+  };
+
+  struct addrinfo *res, *cur;
+  int err = getaddrinfo(info->host, info->port, &hints, &res);
+
+  if (err) {
+    logMessage(LOG_WARNING,
+               "getaddrinfo(%s,%s): "
+#ifdef HAVE_GAI_STRERROR
+               "%s"
+#else /* HAVE_GAI_STRERROR */
+               "%d"
+#endif /* HAVE_GAI_STRERROR */
+               , info->host
+               , info->port
+#ifdef HAVE_GAI_STRERROR
+               ,
+#ifdef EAI_SYSTEM
+                 (err == EAI_SYSTEM)? strerror(errno):
+#endif /* EAI_SYSTEM */
+                 gai_strerror(err)
+#else /* HAVE_GAI_STRERROR */
+               , err
+#endif /* HAVE_GAI_STRERROR */
+    );
+    return INVALID_FILE_DESCRIPTOR;
+  }
+
+  for (cur = res; cur; cur = cur->ai_next) {
+    fd = newTcpSocket(cur->ai_family, cur->ai_socktype, cur->ai_protocol,
+                      cur->ai_addr, cur->ai_addrlen);
+
+    if (fd != INVALID_SOCKET_DESCRIPTOR) break;
+  }
+
+  if (!cur) fd = INVALID_SOCKET_DESCRIPTOR;
+  freeaddrinfo(res);
+#endif /* HAVE_GETADDRINFO */
+
+#ifdef __MINGW32__
+  } else {
+#endif /* __MINGW32__ */
+
+#if !defined(HAVE_GETADDRINFO) || defined(__MINGW32__)
+  struct hostent *he;
+
+  struct sockaddr_in addr = {
+    .sin_family = AF_INET
+  };
+
+  if (!info->port) {
+    addr.sin_port = htons(BRLAPI_SOCKETPORTNUM);
+  } else {
+    unsigned int port;
+
+    if (!isUnsignedInteger(&port, info->port)) {
+      struct servent *se;
+
+      if (!(se = getservbyname(info->port, "tcp"))) {
+        logMessage(LOG_ERR,
+                   "TCP port %s: "
+#ifdef __MINGW32__
+                   "%d"
+#else /* __MINGW32__ */
+                   "%s"
+#endif /* __MINGW32__ */
+                   , info->port
+#ifdef __MINGW32__
+                   , WSAGetLastError()
+#else /* __MINGW32__ */
+                   , hstrerror(h_errno)
+#endif /* __MINGW32__ */
+	);
+
+	return INVALID_FILE_DESCRIPTOR;
+      }
+
+      addr.sin_port = se->s_port;
+    } else if ((port > 0) && (port <= UINT16_MAX)) {
+      addr.sin_port = htons(port);
+    } else {
+      logMessage(LOG_ERR, "invalid TCP port: %s", info->port);
+      return INVALID_FILE_DESCRIPTOR;
+    }
+  }
+
+  if (!info->host) {
+    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+  } else if ((addr.sin_addr.s_addr = inet_addr(info->host)) == htonl(INADDR_NONE)) {
+    if (!(he = gethostbyname(info->host))) {
+      logMessage(LOG_ERR,
+                 "gethostbyname(%s): "
+#ifdef __MINGW32__
+                 "%d"
+#else /* __MINGW32__ */
+                 "%s"
+#endif /* __MINGW32__ */
+                 , info->host
+#ifdef __MINGW32__
+                 , WSAGetLastError()
+#else /* __MINGW32__ */
+                 , hstrerror(h_errno)
+#endif /* __MINGW32__ */
+      );
+
+      return INVALID_FILE_DESCRIPTOR;
+    }
+
+    if (he->h_addrtype != AF_INET) {
+#ifdef EAFNOSUPPORT
+      errno = EAFNOSUPPORT;
+#else /* EAFNOSUPPORT */
+      errno = EINVAL;
+#endif /* EAFNOSUPPORT */
+
+      logMessage(LOG_ERR, "unknown address type %d", he->h_addrtype);
+      return INVALID_FILE_DESCRIPTOR;
+    }
+
+    if (he->h_length > sizeof(addr.sin_addr)) {
+      errno = EINVAL;
+      logMessage(LOG_ERR, "address too big: %d", he->h_length);
+      return INVALID_FILE_DESCRIPTOR;
+    }
+
+    memcpy(&addr.sin_addr, he->h_addr, he->h_length);
+  }
+
+  fd = newTcpSocket(PF_INET, SOCK_STREAM, IPPROTO_TCP,
+                    (struct sockaddr *)&addr, sizeof(addr));
+#endif /* !HAVE_GETADDRINFO */
+
+#ifdef __MINGW32__
+  }
+#endif /* __MINGW32__ */
+
+  if (fd == INVALID_SOCKET_DESCRIPTOR) {
+    logMessage(LOG_WARNING,"unable to find a local TCP port %s:%s !",info->host,info->port);
+  }
+
+  if (info->host) {
+    free(info->host);
+    info->host = NULL;
+  }
+
+  if (info->port) {
+    free(info->port);
+    info->port = NULL;
+  }
+
+  if (fd == INVALID_SOCKET_DESCRIPTOR) {
+    return INVALID_FILE_DESCRIPTOR;
+  }
+
+#ifdef __MINGW32__
+  if (!(info->overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+    logWindowsSystemError("CreateEvent");
+    closeSocketDescriptor(fd);
+    return INVALID_FILE_DESCRIPTOR;
+  }
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "event -> %p", info->overl.hEvent);
+  WSAEventSelect(fd, info->overl.hEvent, FD_ACCEPT);
+#endif /* __MINGW32__ */
+
+  return (FileDescriptor)fd;
+}
+
+#if defined(PF_LOCAL)
+
+#ifndef __MINGW32__
+static int readPid(char *path)
+  /* read pid from specified file. Return 0 on any error */
+{
+  char pids[16], *ptr;
+  pid_t pid;
+  int n;
+  FileDescriptor fd;
+
+  {
+    int openFlags = O_RDONLY;
+
+    #ifdef O_CLOEXEC
+    openFlags |= O_CLOEXEC;
+    #endif /* O_CLOEXEC */
+
+    fd = open(path, openFlags);
+  }
+
+  if (fd == -1) return 0;
+  n = read(fd, pids, sizeof(pids)-1);
+  closeFileDescriptor(fd);
+  if (n == -1) return 0;
+  pids[n] = 0;
+  pid = strtol(pids, &ptr, 10);
+  if (ptr != &pids[n]) return 0;
+  return pid;
+}
+
+static int
+adjustPermissions (
+  const void *object, const char *container,
+  int (*getStatus) (const void *object, struct stat *status),
+  int (*setPermissions) (const void *object, mode_t permissions)
+) {
+  uid_t user = geteuid();
+  int adjust = !user;
+
+  if (!adjust) {
+    struct stat status;
+
+    if (stat(container, &status) == -1) {
+      logSystemError("stat");
+    } else if (status.st_uid == user) {
+      adjust = 1;
+    }
+  }
+
+  if (adjust) {
+    struct stat status;
+    if (!getStatus(object, &status)) return 0;
+
+    {
+      mode_t oldPermissions = status.st_mode & ~S_IFMT;
+      mode_t newPermissions = oldPermissions;
+
+      #ifdef S_IRGRP
+      if (oldPermissions & S_IRUSR) newPermissions |= S_IRGRP | S_IROTH;
+      if (oldPermissions & S_IWUSR) newPermissions |= S_IWGRP | S_IWOTH;
+      if (oldPermissions & S_IXUSR) newPermissions |= S_IXGRP | S_IXOTH;
+      if (S_ISDIR(status.st_mode)) newPermissions |= S_ISVTX | S_IXOTH;
+      #endif
+
+      if (newPermissions != oldPermissions) {
+        if (!setPermissions(object, newPermissions)) {
+          return 0;
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+getPathStatus (const void *object, struct stat *status) {
+  const char *path = object;
+  if (stat(path, status) != -1) return 1;
+  logSystemError("stat");
+  return 0;
+}
+
+static int
+setPathPermissions (const void *object, mode_t permissions) {
+  const char *path = object;
+
+  lockUmask();
+  int changed = chmod(path, permissions) != -1;
+  unlockUmask();
+
+  if (!changed) logSystemError("chmod");
+  return changed;
+}
+
+static int
+adjustPathPermissions (const char *path) {
+  int ok = 0;
+  char *parent = getPathDirectory(path);
+
+  if (parent) {
+    if (adjustPermissions(path, parent, getPathStatus, setPathPermissions)) ok = 1;
+    free(parent);
+  }
+
+  return ok;
+}
+
+static int
+getFileStatus (const void *object, struct stat *status) {
+  const int *fd = object;
+  if (fstat(*fd, status) != -1) return 1;
+  logSystemError("fstat");
+  return 0;
+}
+
+static int
+setFilePermissions (const void *object, mode_t permissions) {
+  const int *fd = object;
+
+  lockUmask();
+  int changed = fchmod(*fd, permissions) != -1;
+  unlockUmask();
+
+  if (!changed) logSystemError("fchmod");
+  return changed;
+}
+
+static int
+adjustFilePermissions (int fd, const char *directory) {
+  return adjustPermissions(
+    &fd, directory,
+    getFileStatus,
+    setFilePermissions
+  );
+}
+#endif /* __MINGW32__ */
+
+/* Function : createLocalSocket */
+/* Creates the listening socket for in-connections */
+/* Returns 1, or 0 if an error occurred */
+static FileDescriptor createLocalSocket(struct socketInfo *info)
+{
+  int lpath=strlen(BRLAPI_SOCKETPATH),lport=strlen(info->port);
+  FileDescriptor fd;
+#ifdef __MINGW32__
+  char path[lpath+lport+1];
+  memcpy(path,BRLAPI_SOCKETPATH,lpath);
+  memcpy(path+lpath,info->port,lport+1);
+  if ((fd = CreateNamedPipe(path,
+	  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+	  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
+	  PIPE_UNLIMITED_INSTANCES,
+	  BRLAPI_MAXPACKETSIZE + sizeof(brlapi_header_t),
+	  BRLAPI_MAXPACKETSIZE + sizeof(brlapi_header_t),
+	  0, NULL)) == INVALID_HANDLE_VALUE) {
+    if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
+      logWindowsSystemError("CreateNamedPipe");
+    goto out;
+  }
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "CreateFile -> %"PRIfd,fd);
+  if (!info->overl.hEvent) {
+    if (!(info->overl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+      logWindowsSystemError("CreateEvent");
+      goto outfd;
+    }
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "event -> %p",info->overl.hEvent);
+  }
+  if (!(ResetEvent(info->overl.hEvent))) {
+    logWindowsSystemError("ResetEvent");
+    goto outfd;
+  }
+  if (ConnectNamedPipe(fd, &info->overl)) {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "already connected !");
+    return fd;
+  }
+
+  switch(GetLastError()) {
+    case ERROR_IO_PENDING: return fd;
+    case ERROR_PIPE_CONNECTED: SetEvent(info->overl.hEvent); return fd;
+    default: logWindowsSystemError("ConnectNamedPipe");
+  }
+  CloseHandle(info->overl.hEvent);
+#else /* __MINGW32__ */
+  struct sockaddr_un sa;
+  char tmppath[lpath+lport+4];
+  char lockpath[lpath+lport+3];
+  struct stat st;
+  char pids[16];
+  pid_t pid;
+  int lock,n,done,res;
+  mode_t permissions = S_IRWXU | S_IRWXG | S_IRWXO;
+
+  if ((fd = socket(PF_LOCAL, SOCK_STREAM, 0))==-1) {
+    logSystemError("socket");
+    goto out;
+  }
+
+  setCloseOnExec(fd, 1);
+  sa.sun_family = AF_LOCAL;
+
+  if (lpath+lport+1>sizeof(sa.sun_path)) {
+    logMessage(LOG_ERR, "Unix path too long");
+    goto outfd;
+  }
+
+  while (1) {
+    {
+      lockUmask();
+      int mkdirResult = mkdir(BRLAPI_SOCKETPATH, (permissions | S_ISVTX));
+      unlockUmask();
+      if (mkdirResult != -1) break;
+    }
+
+    if (!running) goto outfd;
+    if (errno == EEXIST) break;
+
+    if ((errno != EROFS) && (errno != ENOENT)) {
+      logSystemError("making socket directory");
+      goto outfd;
+    }
+
+    /* read-only, or not mounted yet, wait */
+    approximateDelay(1000);
+  }
+
+  if (!adjustPathPermissions(BRLAPI_SOCKETPATH)) {
+    goto outfd;
+  }
+
+  memcpy(sa.sun_path,BRLAPI_SOCKETPATH "/",lpath+1);
+  memcpy(sa.sun_path+lpath+1,info->port,lport+1);
+  memcpy(tmppath, BRLAPI_SOCKETPATH "/", lpath+1);
+  tmppath[lpath+1]='.';
+  memcpy(tmppath+lpath+2, info->port, lport);
+  memcpy(lockpath, tmppath, lpath+2+lport);
+  tmppath[lpath+2+lport]='_';
+  tmppath[lpath+2+lport+1]=0;
+  lockpath[lpath+2+lport]=0;
+
+  while (1) {
+    {
+      lockUmask();
+
+      {
+        int openFlags = O_WRONLY | O_CREAT | O_EXCL;
+
+        #ifdef O_CLOEXEC
+        openFlags |= O_CLOEXEC;
+        #endif /* O_CLOEXEC */
+
+        lock = open(
+          tmppath, openFlags,
+          (permissions & (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))
+        );
+      }
+
+      unlockUmask();
+    }
+
+    if (lock != -1) break;
+    if (!running) goto outfd;
+
+    if (errno == EROFS) {
+      approximateDelay(1000);
+      continue;
+    }
+
+    if (errno != EEXIST) {
+      logSystemError("opening local socket lock");
+      goto outfd;
+    }
+
+    if ((pid = readPid(tmppath)) && pid != getpid()
+	&& (kill(pid, 0) != -1 || errno != ESRCH)) {
+      logMessage(LOG_ERR,"another BrlAPI server is already listening on %s (file %s exists)",info->port, tmppath);
+      goto outfd;
+    }
+
+    /* bogus file, myself or non-existent process, remove */
+    while (unlink(tmppath)) {
+      if (errno != EROFS) {
+	logSystemError("removing stale local socket lock");
+	goto outfd;
+      }
+
+      approximateDelay(1000);
+    }
+  }
+
+  n = snprintf(pids,sizeof(pids),"%d",getpid());
+  done = 0;
+
+  while ((res = write(lock,pids+done,n)) < n) {
+    if (!running) goto outlockfd;
+
+    if (res == -1) {
+      if (errno != ENOSPC) {
+	logSystemError("writing pid in local socket lock");
+	goto outlockfd;
+      }
+
+      approximateDelay(1000);
+    } else {
+      done += res;
+      n -= res;
+    }
+  }
+
+  while (1) {
+    if (!running) goto outtmp;
+
+    if (link(tmppath, lockpath) == -1) {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "linking local socket lock: %s", strerror(errno));
+      /* but no action: link() might erroneously return errors, see manpage */
+    }
+
+    if (fstat(lock, &st) == -1) {
+      logSystemError("checking local socket lock");
+      goto outtmp;
+    }
+
+    if (st.st_nlink == 2) {
+      /* success */
+      break;
+    }
+
+    /* failed to link */
+    if ((pid = readPid(lockpath)) && pid != getpid()
+	&& (kill(pid, 0) != -1 || errno != ESRCH)) {
+      logMessage(LOG_ERR,"another BrlAPI server is already listening on %s (file %s exists)",info->port, lockpath);
+      goto outtmp;
+    }
+
+    /* bogus file, myself or non-existent process, remove */
+    if (unlink(lockpath)) {
+      logSystemError("removing stale local socket lock");
+      goto outtmp;
+    }
+  }
+
+  closeFileDescriptor(lock);
+
+  if (unlink(tmppath) == -1) {
+    logSystemError("removing temp local socket lock");
+  }
+
+  if (unlink(sa.sun_path) && errno != ENOENT) {
+    logSystemError("removing old socket");
+    goto outfd;
+  }
+
+  if (loopBind(fd, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
+    logMessage(LOG_WARNING, "bind: %s", strerror(errno));
+    goto outfd;
+  }
+
+  if (!adjustFilePermissions(fd, BRLAPI_SOCKETPATH)) {
+    goto outfd;
+  }
+
+  if (listen(fd,1)<0) {
+    logSystemError("listen");
+    goto outfd;
+  }
+  return fd;
+
+outtmp:
+  unlink(tmppath);
+outlockfd:
+  closeFileDescriptor(lock);
+#endif /* __MINGW32__ */
+outfd:
+  closeFileDescriptor(fd);
+out:
+  return INVALID_FILE_DESCRIPTOR;
+}
+#endif /* PF_LOCAL */
+
+static void createSocket(int num)
+{
+  struct socketInfo *cinfo = &socketInfo[num];
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "creating socket: %d (%s:%s)",
+             num,
+             (cinfo->host? cinfo->host: "LOCAL"),
+             (cinfo->port? cinfo->port: "DEFAULT")
+  );
+
+  cinfo->fd =
+#if defined(PF_LOCAL)
+    (cinfo->addrfamily == PF_LOCAL)? createLocalSocket(cinfo):
+#endif /* PF_LOCAL */
+    createTcpSocket(cinfo);
+
+  if (cinfo->fd == INVALID_FILE_DESCRIPTOR) {
+    logMessage(LOG_WARNING, "error while creating socket %d", num);
+  } else {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "socket %d created (fd %"PRIfd")", num, cinfo->fd);
+  }
+}
+
+static void closeSockets(void *arg)
+{
+  int i;
+  struct socketInfo *info;
+
+  for (i=0;i<serverSocketCount;i++) {
+#ifdef __MINGW32__
+    pthread_cancel(socketThreads[i]);
+#else /* __MINGW32__ */
+    pthread_kill(socketThreads[i], SIGUSR2);
+#endif /* __MINGW32__ */
+    pthread_join(socketThreads[i], NULL);
+
+    info=&socketInfo[i];
+
+    if (info->fd>=0) {
+      if (closeFileDescriptor(info->fd)) {
+        logSystemError("closing socket");
+      }
+      info->fd=INVALID_FILE_DESCRIPTOR;
+
+#ifdef __MINGW32__
+      if ((info->overl.hEvent)) {
+	CloseHandle(info->overl.hEvent);
+	info->overl.hEvent = NULL;
+      }
+#else /* __MINGW32__ */
+
+#if defined(PF_LOCAL)
+      if (info->addrfamily==PF_LOCAL) {
+	char *path;
+	int lpath=strlen(BRLAPI_SOCKETPATH),lport=strlen(info->port);
+
+	if ((path=malloc(lpath+lport+3))) {
+	  memcpy(path,BRLAPI_SOCKETPATH "/",lpath+1);
+	  memcpy(path+lpath+1,info->port,lport+1);
+
+	  if (unlink(path) == -1) {
+	    logSystemError("unlinking local socket");
+          }
+
+	  path[lpath+1]='.';
+	  memcpy(path+lpath+2,info->port,lport+1);
+
+	  if (unlink(path) == -1) {
+	    logSystemError("unlinking local socket lock");
+          }
+
+	  free(path);
+	}
+      }
+#endif /* PF_LOCAL */
+#endif /* __MINGW32__ */
+    }
+
+    free(info->port);
+    info->port = NULL;
+
+    free(info->host);
+    info->host = NULL;
+  }
+}
+
+/* Function: addTtyFds */
+/* recursively add fds of ttys */
+#ifdef __MINGW32__
+static void addTtyFds(HANDLE **lpHandles, int *nbAlloc, int *nbHandles, Tty *tty) {
+#else /* __MINGW32__ */
+static void addTtyFds(fd_set *fds, int *fdmax, Tty *tty) {
+#endif /* __MINGW32__ */
+  {
+    Connection *c;
+    for (c = tty->connections->next; c != tty->connections; c = c -> next) {
+#ifdef __MINGW32__
+      if (*nbHandles == *nbAlloc) {
+	*nbAlloc *= 2;
+	*lpHandles = realloc(*lpHandles,*nbAlloc*sizeof(**lpHandles));
+      }
+      (*lpHandles)[(*nbHandles)++] = c->packet.overl.hEvent;
+#else /* __MINGW32__ */
+      if (c->fd>*fdmax) *fdmax = c->fd;
+      FD_SET(c->fd,fds);
+#endif /* __MINGW32__ */
+    }
+  }
+  {
+    Tty *t;
+    for (t = tty->subttys; t; t = t->next)
+#ifdef __MINGW32__
+      addTtyFds(lpHandles, nbAlloc, nbHandles, t);
+#else /* __MINGW32__ */
+      addTtyFds(fds,fdmax,t);
+#endif /* __MINGW32__ */
+  }
+}
+
+/* Function: handleTtyFds */
+/* recursively handle ttys' fds */
+static void handleTtyFds(fd_set *fds, time_t currentTime, Tty *tty) {
+  {
+    Connection *c,*next;
+    c = tty->connections->next;
+
+    while (c != tty->connections) {
+      int remove = 0;
+      next = c->next;
+
+#ifdef __MINGW32__
+      if (WaitForSingleObject(c->packet.overl.hEvent, 0) == WAIT_OBJECT_0)
+#else /* __MINGW32__ */
+      if (FD_ISSET(c->fd, fds))
+#endif /* __MINGW32__ */
+      {
+	remove = processRequest(c, &packetHandlers);
+      } else {
+        remove = (c->auth != 1) && ((currentTime - c->upTime) > UNAUTH_TIMEOUT);
+      }
+
+#ifndef __MINGW32__
+      FD_CLR(c->fd,fds);
+#endif /* __MINGW32__ */
+
+      if (remove) removeFreeConnection(c);
+      c = next;
+    }
+  }
+
+  {
+    Tty *t,*next;
+    for (t = tty->subttys; t; t = next) {
+      next = t->next;
+      handleTtyFds(fds,currentTime,t);
+    }
+  }
+  if (tty!=&ttys && tty!=&notty
+      && tty->connections->next == tty->connections && !tty->subttys) {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "freeing tty %#010x",tty->number);
+    lockMutex(&apiConnectionsMutex);
+    removeTty(tty);
+    freeTty(tty);
+    unlockMutex(&apiConnectionsMutex);
+  }
+}
+
+#ifndef __MINGW32__
+static sigset_t blockedSignalsMask;
+
+static void initializeBlockedSignalsMask(void)
+{
+  sigemptyset(&blockedSignalsMask);
+
+  sigaddset(&blockedSignalsMask, SIGTERM);
+  sigaddset(&blockedSignalsMask, SIGINT);
+  sigaddset(&blockedSignalsMask, SIGPIPE);
+  sigaddset(&blockedSignalsMask, SIGCHLD);
+  sigaddset(&blockedSignalsMask, SIGUSR1);
+}
+#endif /* __MINGW32__ */
+
+static int prepareThread(void)
+{
+#ifndef __MINGW32__
+  if (pthread_sigmask(SIG_BLOCK, &blockedSignalsMask, NULL) != 0) {
+    logSystemError("pthread_sigmask[SIG_BLOCK]");
+    return 0;
+  }
+#endif /* __MINGW32__ */
+
+  return 1;
+}
+
+THREAD_FUNCTION(createServerSocket) {
+  intptr_t num = (intptr_t) argument;
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "socket creation started: %"PRIdPTR, num);
+
+  if (prepareThread()) {
+    createSocket(num);
+  }
+
+  lockMutex(&apiSocketsMutex);
+    serverSocketsPending -= 1;
+  unlockMutex(&apiSocketsMutex);
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "socket creation finished: %"PRIdPTR, num);
+  return NULL;
+}
+
+/* Function : runServer */
+/* The server thread */
+/* Returns NULL in any case */
+THREAD_FUNCTION(runServer) {
+  char *hosts = (char *)argument;
+  int i;
+  int res;
+  struct sockaddr_storage addr;
+  socklen_t addrlen;
+  Connection *c;
+  time_t currentTime;
+  fd_set sockset;
+  FileDescriptor resfd;
+
+#ifdef __MINGW32__
+  HANDLE *lpHandles;
+  int nbAlloc;
+  int nbHandles = 0;
+#else /* __MINGW32__ */
+  int fdmax;
+#endif /* __MINGW32__ */
+
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "server thread started");
+  if (!prepareThread()) goto finished;
+
+  if (auth && !isAbsolutePath(auth)) {
+    if (!(authDescriptor = authBeginServer(auth))) {
+      logMessage(LOG_WARNING, "Unable to start auth server");
+      goto finished;
+    }
+  }
+
+  socketHosts = splitString(hosts,'+',&serverSocketCount);
+  if (serverSocketCount > SERVER_SOCKET_LIMIT) {
+    logMessage(LOG_ERR, "too many hosts specified: %d > %d)",
+               serverSocketCount, SERVER_SOCKET_LIMIT);
+    goto finished;
+  }
+  if (serverSocketCount == 0) {
+    logMessage(LOG_INFO,"no hosts specified");
+    goto finished;
+  }
+#ifdef __MINGW32__
+  nbAlloc = serverSocketCount;
+#endif /* __MINGW32__ */
+
+  for (i=0;i<serverSocketCount;i++)
+    socketInfo[i].fd = INVALID_FILE_DESCRIPTOR;
+
+#ifdef __MINGW32__
+  if ((getaddrinfoProc && WSAStartup(MAKEWORD(2,0), &wsadata))
+	|| (!getaddrinfoProc && WSAStartup(MAKEWORD(1,1), &wsadata))) {
+    logWindowsSocketError("Starting socket library");
+    goto finished;
+  }
+#endif /* __MINGW32__ */
+
+#ifdef __MINGW32__
+  pthread_cleanup_push(closeSockets,NULL);
+#endif /* __MINGW32__ */
+
+  {
+    pthread_mutexattr_t attributes;
+    pthread_mutexattr_init(&attributes);
+    pthread_mutex_init(&apiSocketsMutex, &attributes);
+    serverSocketsPending = serverSocketCount;
+  }
+
+  for (i=0;i<serverSocketCount;i++) {
+    socketInfo[i].addrfamily=brlapiserver_expandHost(socketHosts[i],&socketInfo[i].host,&socketInfo[i].port);
+
+#ifdef __MINGW32__
+    if (socketInfo[i].addrfamily != PF_LOCAL) {
+#endif /* __MINGW32__ */
+      {
+        char name[0X100];
+        snprintf(name, sizeof(name), "server-socket-create-%d", i);
+
+        res = createThread(name, &socketThreads[i], NULL,
+                           createServerSocket, (void *)(intptr_t)i);
+      }
+
+      if (res != 0) {
+	logMessage(LOG_WARNING,"pthread_create: %s",strerror(res));
+
+	for (i--;i>=0;i--) {
+#ifdef __MINGW32__
+	  pthread_cancel(socketThreads[i]);
+#else /* __MINGW32__ */
+	  pthread_kill(socketThreads[i], SIGUSR2);
+#endif /* __MINGW32__ */
+	  pthread_join(socketThreads[i], NULL);
+        }
+
+	goto finished;
+      }
+
+#ifdef __MINGW32__
+    } else {
+      /* Windows doesn't have trouble with local sockets on read-only
+       * filesystems, but it has with inter-thread overlapped operations,
+       * so call from here
+       */
+      createSocket(i);
+    }
+#endif /* __MINGW32__ */
+  }
+
+  unauthConnections = 0;
+  unauthConnLog = 0;
+
+  while (running) {
+#ifdef __MINGW32__
+    lpHandles = malloc(nbAlloc * sizeof(*lpHandles));
+    nbHandles = 0;
+
+    for (i=0;i<serverSocketCount;i++) {
+      if (socketInfo[i].fd != INVALID_HANDLE_VALUE) {
+	lpHandles[nbHandles++] = socketInfo[i].overl.hEvent;
+      }
+    }
+
+    lockMutex(&apiConnectionsMutex);
+    addTtyFds(&lpHandles, &nbAlloc, &nbHandles, &notty);
+    addTtyFds(&lpHandles, &nbAlloc, &nbHandles, &ttys);
+    unlockMutex(&apiConnectionsMutex);
+
+    if (!nbHandles) {
+      free(lpHandles);
+      approximateDelay(1000);
+      continue;
+    }
+
+    switch (WaitForMultipleObjects(nbHandles, lpHandles, FALSE, 1000)) {
+      case WAIT_TIMEOUT:
+        continue;
+
+      case WAIT_FAILED:
+        logWindowsSystemError("WaitForMultipleObjects");
+        break;
+    }
+
+    free(lpHandles);
+#else /* __MINGW32__ */
+    /* Compute sockets set and fdmax */
+    FD_ZERO(&sockset);
+    fdmax=0;
+
+    lockMutex(&apiConnectionsMutex);
+    addTtyFds(&sockset, &fdmax, &notty);
+    addTtyFds(&sockset, &fdmax, &ttys);
+    unlockMutex(&apiConnectionsMutex);
+
+    {
+      struct timeval tv, *timeout;
+
+      lockMutex(&apiSocketsMutex);
+	for (i=0;i<serverSocketCount;i++) {
+	  if (socketInfo[i].fd>=0) {
+	    FD_SET(socketInfo[i].fd, &sockset);
+
+	    if (socketInfo[i].fd>fdmax) {
+	      fdmax = socketInfo[i].fd;
+	    }
+	  }
+	}
+
+        if (unauthConnections || serverSocketsPending) {
+          memset(&tv, 0, sizeof(tv));
+          tv.tv_sec = SERVER_SELECT_TIMEOUT;
+          timeout = &tv;
+        } else {
+          timeout = NULL;
+        }
+      unlockMutex(&apiSocketsMutex);
+
+      if (select(fdmax+1, &sockset, NULL, NULL, timeout) < 0) {
+        if (fdmax==0) continue; /* still no server socket */
+        logMessage(LOG_WARNING,"select: %s",strerror(errno));
+        break;
+      }
+    }
+#endif /* __MINGW32__ */
+
+    time(&currentTime);
+
+    for (i=0;i<serverSocketCount;i++) {
+      char source[0X100];
+
+#ifdef __MINGW32__
+      if (socketInfo[i].fd != INVALID_FILE_DESCRIPTOR &&
+          WaitForSingleObject(socketInfo[i].overl.hEvent, 0) == WAIT_OBJECT_0) {
+        if (socketInfo[i].addrfamily == PF_LOCAL) {
+          DWORD foo;
+
+          if (!(GetOverlappedResult(socketInfo[i].fd, &socketInfo[i].overl, &foo, FALSE))) {
+            logWindowsSystemError("GetOverlappedResult");
+          }
+
+          resfd = socketInfo[i].fd;
+          if ((socketInfo[i].fd = createLocalSocket(&socketInfo[i])) != INVALID_FILE_DESCRIPTOR) {
+            logMessage(LOG_CATEGORY(SERVER_EVENTS), "socket %d re-established (fd %"PRIfd", was %"PRIfd")",i,socketInfo[i].fd,resfd);
+          }
+
+          snprintf(source, sizeof(source), BRLAPI_SOCKETPATH "%s", socketInfo[i].port);
+        } else {
+          if (!ResetEvent(socketInfo[i].overl.hEvent)) {
+            logWindowsSystemError("ResetEvent in server loop");
+          }
+#else /* __MINGW32__ */
+      if (socketInfo[i].fd>=0 && FD_ISSET(socketInfo[i].fd, &sockset)) {
+#endif /* __MINGW32__ */
+          addrlen = sizeof(addr);
+          resfd = (FileDescriptor)accept((SocketDescriptor)socketInfo[i].fd, (struct sockaddr *) &addr, &addrlen);
+
+          if (resfd == INVALID_FILE_DESCRIPTOR) {
+            setSocketErrno();
+            logMessage(LOG_WARNING,"accept(%"PRIfd"): %s",socketInfo[i].fd,strerror(errno));
+            continue;
+          }
+
+#ifndef __MINGW32__
+          if (resfd >= FD_SETSIZE) {
+            /* Will not be able to call select() on this */
+            setErrno(EMFILE);
+            logMessage(LOG_WARNING,"accept(%"PRIfd"): %s",socketInfo[i].fd,strerror(errno));
+            continue;
+          }
+
+#endif /* !__MINGW32__ */
+
+          formatAddress(source, sizeof(source), &addr, addrlen);
+#ifdef __MINGW32__
+        }
+#endif /* __MINGW32__ */
+
+        logMessage(LOG_CATEGORY(SERVER_EVENTS),
+          "BrlAPI connection fd=%"PRIfd" accepted: %s", resfd, source
+        );
+
+        if (unauthConnections >= UNAUTH_LIMIT) {
+          writeError(resfd, BRLAPI_ERROR_CONNREFUSED);
+          closeFileDescriptor(resfd);
+
+          if (unauthConnLog==0) {
+            logMessage(LOG_WARNING, "Too many simultaneous unauthorized connections");
+          }
+
+          unauthConnLog++;
+        } else {
+#ifndef __MINGW32__
+          if (!setBlockingIo(resfd, 0)) {
+            logMessage(LOG_WARNING, "Failed to switch to non-blocking mode: %s",strerror(errno));
+            break;
+          }
+#endif /* __MINGW32__ */
+
+          c = createConnection(resfd, currentTime);
+          if (c==NULL) {
+            logMessage(LOG_WARNING,"Failed to create connection structure");
+            closeFileDescriptor(resfd);
+          } else {
+	    unauthConnections++;
+	    addConnection(c, notty.connections);
+	    handleNewConnection(c);
+	  }
+        }
+      }
+    }
+
+    handleTtyFds(&sockset,currentTime,&notty);
+    handleTtyFds(&sockset,currentTime,&ttys);
+  }
+
+  running = 0;
+#ifdef __MINGW32__
+  pthread_cleanup_pop(1);
+#else /* __MINGW32__ */
+  closeSockets(NULL);
+#endif /* __MINGW32__ */
+
+finished:
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "server thread finished");
+  return NULL;
+}
+
+/****************************************************************************/
+/** MISCELLANEOUS FUNCTIONS                                                **/
+/****************************************************************************/
+
+/* Function : initializeAcceptedKeys */
+/* Specify which keys should be passed to the client by default, as soon */
+/* as it controls the tty */
+/* If client asked for commands, one lets it process routing cursor */
+/* and screen-related commands */
+/* If the client is interested in braille codes, one passes it nothing */
+/* to let the user read the screen in case theree is an error */
+static int initializeAcceptedKeys(Connection *c, int how)
+{
+  if (how == BRL_KEYCODES) {
+    if (c && addKeyrange(0, BRLAPI_KEY_MAX, &c->acceptedKeys) == -1) return -1;
+  } else {
+    if (c) {
+      typedef struct {
+        int (*action) (brlapi_keyCode_t first, brlapi_keyCode_t last, KeyrangeList **list);
+        brlapi_rangeType_t type;
+        brlapi_keyCode_t code;
+      } KeyrangeEntry;
+
+      static const KeyrangeEntry keyrangeTable[] = {
+        { .action = addKeyrange,
+          .type = brlapi_rangeType_all,
+          .code = 0
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_OFFLINE
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_NOOP
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_RESTARTBRL
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_BRL_START
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_BRL_STOP
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_RESTARTSPEECH
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SPK_START
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SPK_STOP
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SCR_START
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SCR_STOP
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SWITCHVT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SWITCHVT_PREV
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SWITCHVT_NEXT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SELECTVT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SELECTVT_PREV
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SELECTVT_NEXT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSXT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSAT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSPS2
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_CONTEXT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ALERT
+        },
+
+        { .action = removeKeyrange,
+          .type = brlapi_rangeType_command,
+          .code = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_PASSDOTS
+        },
+
+        { .action = NULL }
+      };
+
+      const KeyrangeEntry *keyrange = keyrangeTable;
+
+      while (keyrange->action) {
+        brlapi_keyCode_t first;
+        brlapi_keyCode_t mask;
+        brlapi_keyCode_t last;
+
+        first = keyrange->code;
+        if (brlapiserver_getKeyrangeMask(keyrange->type, first, &mask) == -1) return -1;
+        last = first | mask;
+
+        if (keyrange->action(first, last, &c->acceptedKeys) == -1) return -1;
+        keyrange += 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/* Function: ttyTerminationHandler */
+/* Recursively removes connections */
+static void ttyTerminationHandler(Tty *tty)
+{
+  while (tty->connections->next != tty->connections) {
+    removeFreeConnection(tty->connections->next);
+  }
+  freeConnection(tty->connections);
+
+  {
+    Tty *t = tty->subttys;
+
+    while (t) {
+      ttyTerminationHandler(t);
+      t = t->next;
+    }
+  }
+}
+/* Function : terminationHandler */
+/* Terminates driver */
+static void terminationHandler(void)
+{
+  int res;
+  running = 0;
+
+#ifdef __MINGW32__
+  res = pthread_cancel(serverThread);
+#else /* __MINGW32__ */
+  res = pthread_kill(serverThread, SIGUSR2);
+#endif /* __MINGW32__ */
+  pthread_join(serverThread, NULL);
+
+  if (res != 0) {
+    logMessage(LOG_WARNING,"pthread_cancel: %s",strerror(res));
+  }
+
+  ttyTerminationHandler(&notty);
+  ttyTerminationHandler(&ttys);
+
+  if (authDescriptor) {
+    authEnd(authDescriptor);
+    authDescriptor = NULL;
+  }
+
+#ifdef __MINGW32__
+  WSACleanup();
+#endif /* __MINGW32__ */
+
+  if (socketHosts) {
+    deallocateStrings(socketHosts);
+    socketHosts = NULL;
+  }
+}
+
+/* Function: whoFillsTty */
+/* Returns the connection which fills the tty */
+static Connection *whoFillsTty(Tty *tty) {
+  Connection *c;
+  Tty *t;
+  for (c=tty->connections->next; c!=tty->connections; c = c->next)
+    if (c->brlbufstate!=EMPTY
+        && c->client_priority != BRLAPI_PARAM_CLIENT_PRIORITY_DISABLE) {
+      goto found;
+    }
+
+  c = NULL;
+found:
+  for (t = tty->subttys; t; t = t->next)
+    if (tty->focus==SCR_NO_VT || t->number == tty->focus) {
+      Connection *recur_c = whoFillsTty(t);
+      return recur_c ? recur_c : c;
+    }
+  return c;
+}
+
+static inline void setCurrentRootTty(void) {
+  ttys.focus = currentVirtualTerminal();
+}
+
+/* Function : api_writeWindow */
+static int api_writeWindow(BrailleDisplay *brl, const wchar_t *text)
+{
+  int ok = 1;
+  if (text)
+    memcpy(coreWindowText, text, displaySize * sizeof(*coreWindowText));
+  else
+    memset(coreWindowText, 0, displaySize * sizeof(*coreWindowText));
+  memcpy(coreWindowDots, brl->buffer, displaySize * sizeof(*coreWindowDots));
+  coreWindowCursor = brl->cursor;
+  setCurrentRootTty();
+  lockMutex(&apiConnectionsMutex);
+  lockMutex(&apiRawMutex);
+  if (!offline && !suspendConnection && !rawConnection && !whoFillsTty(&ttys)) {
+    lockMutex(&apiDriverMutex);
+    if (!trueBraille->writeWindow(brl, text)) ok = 0;
+    unlockMutex(&apiDriverMutex);
+  }
+  unlockMutex(&apiRawMutex);
+  unlockMutex(&apiConnectionsMutex);
+  return ok;
+}
+
+/* Function: whoGetsKey */
+/* Returns the connection which gets that key */
+static Connection *whoGetsKey(Tty *tty, brlapi_keyCode_t code, unsigned int how, unsigned int retainDots)
+{
+  Connection *c;
+  Tty *t;
+  int passKey;
+  for (c=tty->connections->next; c!=tty->connections; c = c->next) {
+    lockMutex(&c->acceptedKeysMutex);
+    passKey = (c->how==how) && (inKeyrangeList(c->acceptedKeys,code) != NULL)
+      && (how != BRL_COMMANDS || (!retainDots || c->retainDots));
+    unlockMutex(&c->acceptedKeysMutex);
+    if (passKey) goto found;
+  }
+  c = NULL;
+found:
+  for (t = tty->subttys; t; t = t->next)
+    if (tty->focus==SCR_NO_VT || t->number == tty->focus) {
+      Connection *recur_c = whoGetsKey(t, code, how, retainDots);
+      return recur_c ? recur_c : c;
+    }
+  return c;
+}
+
+/* Temporary function, until we implement proper generic support for variables.
+ */
+static void broadcastKey(Tty *tty, brlapi_keyCode_t code, unsigned int how) {
+  Connection *c;
+  Tty *t;
+  for (c=tty->connections->next; c!=tty->connections; c = c->next) {
+    lockMutex(&c->acceptedKeysMutex);
+    if ((c->how==how) && (inKeyrangeList(c->acceptedKeys,code) != NULL))
+      writeKey(c->fd,code);
+    unlockMutex(&c->acceptedKeysMutex);
+  }
+  for (t = tty->subttys; t; t = t->next)
+    broadcastKey(t, code, how);
+}
+
+/* The core produced a key event, try to send it to a brlapi client. */
+static int api__handleKeyEvent(brlapi_keyCode_t clientCode) {
+  Connection *c;
+
+  if (offline) {
+    broadcastKey(&ttys, BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_NOOP, BRL_COMMANDS);
+    offline = 0;
+  }
+  /* somebody gets the raw code */
+  if ((c = whoGetsKey(&ttys, clientCode, BRL_KEYCODES, 0))) {
+    logMessage(LOG_CATEGORY(SERVER_EVENTS), "transmitting accepted key %016"BRLAPI_PRIxKEYCODE" to fd %"PRIfd,clientCode,c->fd);
+    writeKey(c->fd,clientCode);
+    return 1;
+  }
+  return 0;
+}
+
+int api_handleKeyEvent(KeyGroup group, KeyNumber number, int press) {
+  brlapi_keyCode_t clientCode = makeDriverKeyCode(group, number, press);
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "API got key %02x %02x (press %d), thus client code %016"BRLAPI_PRIxKEYCODE, group, number, press, clientCode);
+
+  int ret;
+  lockMutex(&apiConnectionsMutex);
+  ret = api__handleKeyEvent(clientCode);
+  unlockMutex(&apiConnectionsMutex);
+  return ret;
+}
+
+/* The core produced a command, try to send it to a brlapi client.
+ * Return true if handled and false otherwise.  */
+static int api__handleCommand(int command) {
+  if (command == BRL_CMD_OFFLINE) {
+    if (!offline) {
+      broadcastKey(&ttys, BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_OFFLINE, BRL_COMMANDS);
+      offline = 1;
+    }
+
+    return 0;
+  }
+
+  if (offline) {
+    broadcastKey(&ttys, BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_NOOP, BRL_COMMANDS);
+    offline = 0;
+  }
+
+  if (command != EOF) {
+    Connection *c = NULL;
+    brlapi_keyCode_t code;
+    if (!cmdBrlttyToBrlapi(&code, command, 1)) {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "command %08x could not be converted to BrlAPI with retainDots", command);
+    } else {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "command %08x -> client code %016"BRLAPI_PRIxKEYCODE, command, code);
+      c = whoGetsKey(&ttys, code, BRL_COMMANDS, 1);
+    }
+
+    if (!c) {
+      brlapi_keyCode_t alternate;
+      if (!cmdBrlttyToBrlapi(&alternate, command, 0)) {
+	logMessage(LOG_CATEGORY(SERVER_EVENTS), "command %08x could not be converted to BrlAPI without retainDots", command);
+      } else {
+	logMessage(LOG_CATEGORY(SERVER_EVENTS), "command %08x -> client code %016"BRLAPI_PRIxKEYCODE, command, alternate);
+	c = whoGetsKey(&ttys, alternate, BRL_COMMANDS, 0);
+	if (c) code = alternate;
+      }
+    }
+
+    if (c) {
+      logMessage(LOG_CATEGORY(SERVER_EVENTS), "transmitting accepted command %lx as client code %016"BRLAPI_PRIxKEYCODE" to fd %"PRIfd,(unsigned long)command,code,c->fd);
+      writeKey(c->fd, code);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int api_handleCommand(int command) {
+  int handled;
+
+  lockMutex(&apiConnectionsMutex);
+  handled = api__handleCommand(command);
+  unlockMutex(&apiConnectionsMutex);
+
+  return handled;
+}
+
+/* Function : api_readCommand
+ * Call driver->readCommand unless the driver is suspended.
+ */
+static int api_readCommand(BrailleDisplay *brl, KeyTableCommandContext context) {
+  ssize_t size;
+  brlapi_packet_t packet;
+  int res;
+  int command = EOF;
+
+  lockMutex(&apiConnectionsMutex);
+  lockMutex(&apiRawMutex);
+  if (suspendConnection || !driverConstructed) {
+    unlockMutex(&apiRawMutex);
+    goto out;
+  }
+  if (rawConnection!=NULL) {
+    lockMutex(&apiDriverMutex);
+    size = trueBraille->readPacket(brl, &packet.data, BRLAPI_MAXPACKETSIZE);
+    unlockMutex(&apiDriverMutex);
+    if (size<0)
+      writeException(rawConnection->fd, BRLAPI_ERROR_DRIVERERROR, BRLAPI_PACKET_PACKET, NULL, 0);
+    else if (size)
+      brlapiserver_writePacket(rawConnection->fd,BRLAPI_PACKET_PACKET,&packet.data,size);
+    unlockMutex(&apiRawMutex);
+    goto out;
+  }
+
+  lockMutex(&apiDriverMutex);
+  res = trueBraille->readCommand(brl,context);
+  unlockMutex(&apiDriverMutex);
+  if (brl->resizeRequired)
+    brlResize(brl);
+  command = res;
+  /* some client may get raw mode only from now */
+  unlockMutex(&apiRawMutex);
+out:
+  unlockMutex(&apiConnectionsMutex);
+  return command;
+}
+
+/* Function : api_flushOutput
+ * Flush writes to the braille device.
+ */
+int api_flushOutput(BrailleDisplay *brl) {
+  Connection *c;
+  static Connection *displayed_last;
+  int ok = 1;
+  int drain = 0;
+  int update = 0;
+
+  lockMutex(&apiParamMutex);
+  lockMutex(&apiConnectionsMutex);
+  lockMutex(&apiRawMutex);
+  if (suspendConnection) {
+    unlockMutex(&apiRawMutex);
+    goto out;
+  }
+  setCurrentRootTty();
+  c = whoFillsTty(&ttys);
+  if (!offline && c) {
+    lockMutex(&c->brailleWindowMutex);
+    lockMutex(&apiDriverMutex);
+    if (!driverConstructed && !driverConstructing) {
+      if (!resumeBrailleDriver(brl)) {
+	unlockMutex(&apiDriverMutex);
+	unlockMutex(&c->brailleWindowMutex);
+        unlockMutex(&apiRawMutex);
+	goto out;
+      }
+    }
+
+    if (c->brailleWindow.cursor) {
+      unsigned char newCursorOverlay = getCursorOverlay(brl);
+
+      if (newCursorOverlay != cursorOverlay) {
+        cursorOverlay = newCursorOverlay;
+        update = 1;
+      }
+    }
+
+    if (c != displayed_last || c->brlbufstate==TODISPLAY || update) {
+      unsigned char *oldbuf = disp->buffer, buf[displaySize];
+      disp->buffer = buf;
+      getDots(&c->brailleWindow, buf);
+      brl->cursor = c->brailleWindow.cursor-1;
+      if (!trueBraille->writeWindow(brl, c->brailleWindow.text)) ok = 0;
+      /* FIXME: the client should have gotten the notification when the write
+       * was received, rather than only when it eventually gets displayed
+       * (possibly only because of focus change) */
+      if (ok) handleParamUpdate(c, c, BRLAPI_PARAM_RENDERED_CELLS, 0, 0, disp->buffer, displaySize);
+      drain = 1;
+      disp->buffer = oldbuf;
+      displayed_last = c;
+    }
+    unlockMutex(&apiDriverMutex);
+    unlockMutex(&c->brailleWindowMutex);
+  } else {
+    /* no RAW, no connection filling tty, hence suspend if needed */
+    lockMutex(&apiDriverMutex);
+    if (!coreActive) {
+      if (driverConstructed) {
+	/* Put back core output before suspending */
+	unsigned char *oldbuf = disp->buffer;
+	disp->buffer = coreWindowDots;
+	brl->cursor = coreWindowCursor;
+	if (!trueBraille->writeWindow(brl, coreWindowText)) ok = 0;
+	disp->buffer = oldbuf;
+	suspendBrailleDriver();
+      }
+      unlockMutex(&apiDriverMutex);
+      unlockMutex(&apiRawMutex);
+      goto out;
+    }
+    unlockMutex(&apiDriverMutex);
+  }
+  if (!ok) {
+    unlockMutex(&apiRawMutex);
+    goto out;
+  }
+  if (drain)
+    drainBrailleOutput(brl, 0);
+  unlockMutex(&apiRawMutex);
+out:
+  unlockMutex(&apiConnectionsMutex);
+  unlockMutex(&apiParamMutex);
+  return ok;
+}
+
+int api_resumeDriver(BrailleDisplay *brl) {
+  /* core is resuming or opening the device for the first time, let's try to go
+   * to normal state */
+  lockMutex(&apiRawMutex);
+  lockMutex(&apiDriverMutex);
+  if (!suspendConnection && !driverConstructed)
+    resumeBrailleDriver(brl);
+  unlockMutex(&apiDriverMutex);
+  unlockMutex(&apiRawMutex);
+  return (coreActive = driverConstructed);
+}
+
+/* try to get access to device. If suspended, returns 0 */
+int api_claimDriver (BrailleDisplay *brl)
+{
+  int ret;
+  lockMutex(&apiSuspendMutex);
+  ret = driverConstructed;
+  return ret;
+}
+
+void api_releaseDriver(BrailleDisplay *brl)
+{
+  unlockMutex(&apiSuspendMutex);
+}
+
+void api_suspendDriver(BrailleDisplay *brl) {
+  /* core is suspending, going to core suspend state */
+  coreActive = 0;
+  flushBrailleOutput(brl);
+}
+
+static void brlResize(BrailleDisplay *brl)
+{
+  displayDimensions[0] = htonl(brl->textColumns);
+  displayDimensions[1] = htonl(brl->textRows);
+  displaySize = brl->textColumns * brl->textRows;
+  coreWindowText = realloc(coreWindowText, displaySize * sizeof(*coreWindowText));
+  coreWindowDots = realloc(coreWindowDots, displaySize * sizeof(*coreWindowDots));
+  coreWindowCursor = 0;
+  handleParamUpdate(NULL, NULL, BRLAPI_PARAM_DISPLAY_SIZE, 0, BRLAPI_PARAMF_GLOBAL, displayDimensions, sizeof(displayDimensions));
+}
+
+REPORT_LISTENER(brlapi_handleReports)
+{
+  if (parameters->reportIdentifier == REPORT_BRAILLE_DEVICE_ONLINE) {
+    BrailleDisplay *brl = parameters->listenerData;
+    flushBrailleOutput(brl);
+  }
+}
+
+static ReportListenerInstance *api_reportListener;
+
+/* Function : api_linkServer */
+/* Does all the link stuff to let api get events from the driver and */
+/* writes from brltty */
+void api_linkServer(BrailleDisplay *brl)
+{
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "api link");
+  trueBraille=braille;
+  memcpy(&ApiBraille,braille,sizeof(BrailleDriver));
+  ApiBraille.writeWindow=api_writeWindow;
+  ApiBraille.readCommand=api_readCommand;
+  ApiBraille.readPacket = NULL;
+  ApiBraille.writePacket = NULL;
+  braille=&ApiBraille;
+  lockMutex(&apiDriverMutex);
+  disp = brl;
+  driverConstructed=1;
+  unlockMutex(&apiDriverMutex);
+  brlResize(brl);
+  lockMutex(&apiConnectionsMutex);
+  broadcastKey(&ttys, BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_NOOP, BRL_COMMANDS);
+  unlockMutex(&apiConnectionsMutex);
+  api_reportListener = registerReportListener(REPORT_BRAILLE_DEVICE_ONLINE, brlapi_handleReports, brl);
+}
+
+/* Function : api_unlinkServer */
+/* Does all the unlink stuff to remove api from the picture */
+void api_unlinkServer(BrailleDisplay *brl)
+{
+  logMessage(LOG_CATEGORY(SERVER_EVENTS), "api unlink");
+  unregisterReportListener(api_reportListener);
+  lockMutex(&apiConnectionsMutex);
+  broadcastKey(&ttys, BRLAPI_KEY_TYPE_CMD|BRLAPI_KEY_CMD_OFFLINE, BRL_COMMANDS);
+  unlockMutex(&apiConnectionsMutex);
+  free(coreWindowText);
+  coreWindowText = NULL;
+  free(coreWindowDots);
+  coreWindowDots = NULL;
+  braille=trueBraille;
+  trueBraille=&noBraille;
+  lockMutex(&apiDriverMutex);
+  if (!coreActive && driverConstructed)
+    suspendBrailleDriver();
+  driverConstructed=0;
+  disp = NULL;
+  unlockMutex(&apiDriverMutex);
+}
+
+/* Function : api_logServerIdentity */
+/* Identifies BrlApi */
+void api_logServerIdentity(int full)
+{
+  logMessage(LOG_NOTICE, RELEASE);
+  if (full) {
+    logMessage(LOG_INFO, COPYRIGHT);
+  }
+}
+
+#ifdef ENABLE_API_FUZZING
+/* Function : api_connect */
+/* Connect to our own API server */
+static int api_connect(void){
+  int s;
+  struct sockaddr_storage ss;
+  size_t ss_size;
+
+#ifdef PF_LOCAL
+  if (socketInfo[0].addrfamily == PF_LOCAL) {
+    struct sockaddr_un *sun = (struct sockaddr_un *) &ss;
+    s = socket(PF_LOCAL, SOCK_STREAM, 0);
+    ss_size = sizeof(*sun);
+    memset(sun, 0, ss_size);
+    sun->sun_family = AF_LOCAL;
+    sprintf(sun->sun_path, BRLAPI_SOCKETPATH "/%s", socketInfo[0].port);
+  } else
+#endif /* PF_LOCAL */
+  {
+    struct sockaddr_in *sin = (struct sockaddr_in *) &ss;
+    s = socket(AF_INET, SOCK_STREAM, 0);
+    ss_size = sizeof(*sin);
+    memset(sin, 0, ss_size);
+    sin->sin_family = AF_INET;
+    sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    sin->sin_port = htons(BRLAPI_SOCKETPORTNUM+atoi(socketInfo[0].port));
+  }
+
+  while (true) {
+    int c = connect(s, (struct sockaddr *) &ss, ss_size);
+    if (!c) break;
+    if (errno != ECONNREFUSED)
+      logMessage(LOG_WARNING, "Could not connect to API: %s", strerror(errno));
+    sleep(1);
+  }
+  return s;
+}
+
+/* Function : api_writeData */
+/* Write to API socket some data */
+static int api_writeData(int s, const void* data, size_t size) {
+  size_t remaining = size;
+  while (remaining) {
+    ssize_t done = write(s, data, remaining);
+    if (done < 0) {
+      if (errno != EINTR && errno != EAGAIN)
+	return 0;
+      done = 0;
+    }
+    remaining -= done;
+    data += done;
+  }
+  return 1;
+}
+
+/* Function : api_writeFile */
+/* Write to API socket the content of a file */
+static int api_writeFile(int s, const char* filename) {
+  struct stat st;
+  int fd;
+  size_t size;
+  fd = open(filename, O_RDONLY);
+  if (fd < 0) return 0;
+  if (fstat(fd, &st) < 0) {
+    close(fd);
+    return 0;
+  }
+  size = st.st_size;
+  if (size == 0) return 0;
+  {
+    uint8_t buff[size];
+    ssize_t ret;
+    ret = read(fd, buff, size);
+    close(fd);
+    if (ret < size) return 0;
+    return api_writeData(s, buff, size);
+  }
+}
+
+/* Function : api_writeHead */
+/* Write to API socket the version + auth head, so the fuzzer does not have to guess these */
+static int api_writeHead(int s) {
+  const uint32_t versionPacket[] = {
+    htonl(4), htonl(BRLAPI_PACKET_VERSION),
+    htonl(BRLAPI_PROTOCOL_VERSION)
+  };
+  if (!api_writeData(s, versionPacket, sizeof(versionPacket))) return 0;
+  return 1;
+}
+
+/* Function : api_writeWrite */
+/* Write to API socket the write header, so the fuzzer does not have to guess it */
+static int api_writeWrite(int s, int utf8) {
+  {
+    const uint32_t enterTtyModePacket[] = { htonl(2*4), htonl(BRLAPI_PACKET_ENTERTTYMODE), htonl(1), htonl(1) };
+    if (!api_writeData(s, enterTtyModePacket, sizeof(enterTtyModePacket))) return 0;
+  }
+  {
+    const uint32_t writePacket[] = {
+      htonl(3 * 4 + displaySize + (utf8 ? 6 : 11)), htonl(BRLAPI_PACKET_WRITE),
+      htonl(BRLAPI_WF_REGION | BRLAPI_WF_TEXT | BRLAPI_WF_CHARSET),
+      htonl(1), htonl(displaySize),
+    };
+    if (!api_writeData(s, writePacket, sizeof(writePacket))) return 0;
+  }
+  return 1;
+}
+
+/* Function : api_writeLatin1Charset */
+/* Write to API socket the write footer for latin1, so the fuzzer does not have to guess it */
+static int api_writeLatin1Charset(int s) {
+  return api_writeData(s, "\x0aiso-8859-1", 11);
+}
+
+/* Function : api_writeUtf8Charset */
+/* Write to API socket the write footer for UTF-8, so the fuzzer does not have to guess it */
+static int api_writeUtf8Charset(int s) {
+  return api_writeData(s, "\x05UTF-8", 6);
+}
+
+/* Function : api_writeHeading */
+/* Write to API socket the heading for a test */
+static int api_writeHeading(int s) {
+  if (!fuzz_head && !api_writeHead(s)) return 0;
+  if (fuzz_write && !api_writeWrite(s, fuzz_writeutf8)) return 0;
+  return 1;
+}
+
+/* Function : api_writeTrailing */
+/* Write to API socket the trailing for a test */
+static int api_writeTrailing(int s) {
+  if (fuzz_write) {
+    if (fuzz_writeutf8) {
+      if (!api_writeUtf8Charset(s)) return 0;
+    } else {
+      if (!api_writeLatin1Charset(s)) return 0;
+    }
+  }
+  return 1;
+}
+
+/* Function : api_writeInput */
+/* Write to API socket some data set */
+static int api_writeInput(int s, const uint8_t *data, size_t size) {
+  if (fuzz_write) {
+    /* Reject sizes that don't match display size */
+    if (fuzz_writeutf8) {
+      char buf[size+1];
+      memcpy(buf, data, size);
+      buf[size] = 0;
+      if (countUtf8Characters((const char*) data) != displaySize) return -1;
+    } else {
+      if (size != displaySize) return -1;
+    }
+  }
+  if (!api_writeHeading(s)) return 0;
+  if (!api_writeData(s, data, size)) return 0;
+  if (!api_writeTrailing(s)) return 0;
+  return 1;
+}
+
+/* Function: api_readData */
+/* Read all data coming from API socket */
+static int api_readData(int s){
+  while (1) {
+    char buff[4096];
+    ssize_t r = read(s, buff, sizeof(buff));
+    if (r <= 0) {
+      close(s);
+      return r == 0;
+    }
+  }
+}
+
+/* Function: api_testOneInput */
+/* Test feeding API with feeding one input set */
+static int api_testOneInput(const uint8_t *data, size_t size)
+{
+  int s = api_connect();
+  int r = api_writeInput(s, data, size);
+  if (r == -1) return -1; /* Reject this data */
+  if (r == 0) goto out;
+  shutdown(s, SHUT_WR); /* We are finished speaking */
+  api_readData(s);
+out:
+  close(s);
+  return 0;
+}
+
+/* Prototype for LLVM fuzzer runner */
+extern int LLVMFuzzerRunDriver(int *argc, char ***argv,
+                               int (*UserCb)(const uint8_t *data, size_t size));
+
+/* Function: fuzzerFunction */
+/* Runs the LLVM fuzzer */
+THREAD_FUNCTION(fuzzerFunction)
+{
+  char runs[18];
+  char seed[18];
+  char *argv[] = { "brltty", "-max_len=1000", "-dict=Tools/brlapi.d/fuzz-dict", "-detect_leaks=0", "-rss_limit_mb=1000", runs, seed, NULL };
+  int a = sizeof(argv) / sizeof(argv[0]) - 1;
+  char **a_2 = argv;
+  snprintf(runs, sizeof(runs), "-runs=%d", fuzz_runs);
+  snprintf(seed, sizeof(seed), "-seed=%d", fuzz_seed);
+  sleep(1);
+  LLVMFuzzerRunDriver(&a, &a_2, &api_testOneInput);
+  _exit(EXIT_SUCCESS);
+  return 0;
+}
+
+/* Function: crasherFunction */
+/* Runs one crasher reproducer */
+THREAD_FUNCTION(crasherFunction)
+{
+  int s, r = 0;
+  sleep(1);
+  s = api_connect();
+  if (!api_writeHeading(s)) goto out;
+  if (!api_writeFile(s, argument)) goto out;
+  if (!api_writeTrailing(s)) goto out;
+  shutdown(s, SHUT_WR);
+  api_readData(s);
+  r = 1;
+out:
+  close(s);
+  if (r)
+    logMessage(LOG_ALERT, "crash test passed");
+  _exit(EXIT_SUCCESS);
+  return NULL;
+}
+#endif /* ENABLE_API_FUZZING */
+
+/* Function : api_startServer */
+/* Initializes BrlApi */
+/* One first initialize the driver */
+/* Then one creates the communication socket */
+int api_startServer(BrailleDisplay *brl, char **parameters)
+{
+  int res,i;
+
+  char *hosts =
+#if defined(PF_LOCAL)
+	":0+127.0.0.1:0+::1:0"
+#else /* PF_LOCAL */
+	"127.0.0.1:0+::1:0"
+#endif /* PF_LOCAL */
+	;
+
+  {
+    char *operand = parameters[PARM_HOST];
+
+    if (*operand) hosts = operand;
+  }
+
+  auth = BRLAPI_DEFAUTH;
+  {
+    const char *operand = parameters[PARM_AUTH];
+
+    if (*operand) auth = operand;
+  }
+
+  pthread_mutexattr_t mattr;
+
+  coreActive=1;
+
+  if ((notty.connections = createConnection(INVALID_FILE_DESCRIPTOR,0)) == NULL) {
+    logMessage(LOG_WARNING, "Unable to create connections list");
+    goto noNottyConnections;
+  }
+  notty.connections->prev = notty.connections->next = notty.connections;
+  if ((ttys.connections = createConnection(INVALID_FILE_DESCRIPTOR, 0)) == NULL) {
+    logMessage(LOG_WARNING, "Unable to create ttys' connections list");
+    goto noTtysConnections;
+  }
+  ttys.connections->prev = ttys.connections->next = ttys.connections;
+  ttys.focus = SCR_NO_VT;
+
+  pthread_mutexattr_init(&mattr);
+  pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
+
+  pthread_mutex_init(&apiConnectionsMutex,&mattr);
+  pthread_mutex_init(&apiDriverMutex,&mattr);
+  pthread_mutex_init(&apiRawMutex,&mattr);
+  pthread_mutex_init(&apiSuspendMutex,&mattr);
+  pthread_mutex_init(&apiParamMutex,&mattr);
+
+#ifndef __MINGW32__
+  initializeBlockedSignalsMask();
+  asyncHandleSignal(SIGUSR2, asyncEmptySignalHandler, NULL);
+#endif /* __MINGW32__ */
+
+  running = 1;
+  trueBraille=&noBraille;
+
+  if ((res = createThread("server-main", &serverThread, NULL,
+                          runServer, hosts)) != 0) {
+    logMessage(LOG_WARNING,"pthread_create: %s",strerror(res));
+    running = 0;
+
+    for (i=0;i<serverSocketCount;i++) {
+#ifdef __MINGW32__
+      pthread_cancel(socketThreads[i]);
+#else /* __MINGW32__ */
+      pthread_kill(socketThreads[i], SIGUSR2);
+#endif /* __MINGW32__ */
+      pthread_join(socketThreads[i], NULL);
+    }
+
+    goto noServerThread;
+  }
+
+#ifdef ENABLE_API_FUZZING
+  validateOnOff(&fuzz_head, parameters[PARM_FUZZHEAD]);
+  validateOnOff(&fuzz_write, parameters[PARM_FUZZWRITE]);
+  validateOnOff(&fuzz_writeutf8, parameters[PARM_FUZZWRITEUTF8]);
+
+  const char *fuzz = parameters[PARM_FUZZ];
+  if (fuzz) {
+    validateInteger(&fuzz_runs, fuzz, NULL, NULL);
+    if (fuzz_runs) {
+      if (parameters[PARM_FUZZSEED])
+        validateInteger(&fuzz_seed, parameters[PARM_FUZZSEED], NULL, NULL);
+      createThread("fuzzer", &fuzzerThread, NULL, fuzzerFunction, NULL);
+    }
+  }
+
+  const char *crash = parameters[PARM_CRASH];
+  if (crash && crash[0])
+    createThread("crasher", &crasherThread, NULL, crasherFunction, (void*) crash);
+#endif /* ENABLE_API_FUZZING */
+
+  return 1;
+
+noServerThread:
+  freeConnection(ttys.connections);
+noTtysConnections:
+  freeConnection(notty.connections);
+noNottyConnections:
+  authEnd(authDescriptor);
+  authDescriptor = NULL;
+  return 0;
+}
+
+/* Function : api_stopServer */
+/* End of BrlApi session. Closes the listening socket */
+/* destroys opened connections and associated resources */
+/* Closes the driver */
+void api_stopServer(BrailleDisplay *brl)
+{
+  terminationHandler();
+}
diff --git a/Programs/brldefs.h b/Programs/brldefs.h
new file mode 100644
index 0000000..35b5d69
--- /dev/null
+++ b/Programs/brldefs.h
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLAPI_INCLUDED_BRLTTY_BRLDEFS
+#define BRLAPI_INCLUDED_BRLTTY_BRLDEFS
+
+#warning include <brlapi_brldefs.h> instead of <brltty/brldefs.h> (see man brlapi_deprecated)
+#include <brlapi_brldefs.h>
+
+#endif /* BRLAPI_INCLUDED_BRLTTY_BRLDEFS */
diff --git a/Programs/brltest.c b/Programs/brltest.c
new file mode 100644
index 0000000..ca0f202
--- /dev/null
+++ b/Programs/brltest.c
@@ -0,0 +1,311 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "parameters.h"
+#include "log.h"
+#include "parse.h"
+#include "file.h"
+#include "cmd_queue.h"
+#include "brl.h"
+#include "brl_input.h"
+#include "brl_utils.h"
+#include "ttb.h"
+#include "ktb.h"
+#include "message.h"
+#include "utf8.h"
+#include "async_wait.h"
+#include "learn.h"
+
+BrailleDisplay brl;
+
+static char *opt_brailleDevice;
+char *opt_driversDirectory;
+static char *opt_tablesDirectory;
+static char *opt_writableDirectory;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "device",
+    .letter = 'd',
+    .argument = "device",
+    .setting.string = &opt_brailleDevice,
+    .internal.setting = BRAILLE_DEVICE,
+    .description = "Path to device for accessing braille display."
+  },
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing tables.")
+  },
+
+  { .word = "drivers-directory",
+    .letter = 'D',
+    .argument = "directory",
+    .setting.string = &opt_driversDirectory,
+    .internal.setting = DRIVERS_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = "Path to directory for loading drivers."
+  },
+
+  { .word = "writable-directory",
+    .letter = 'W',
+    .argument = strtext("directory"),
+    .setting.string = &opt_writableDirectory,
+    .internal.setting = WRITABLE_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory which can be written to.")
+  },
+END_OPTION_TABLE(programOptions)
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+
+  const char *driver = NULL;
+  void *object;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltest",
+
+      .usage = {
+        .purpose = strtext("Test a braille driver."),
+        .parameters = "[driver [parameter=value ...]]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  setWritableDirectory(opt_writableDirectory);
+
+  if (argc) {
+    driver = *argv++, --argc;
+  }
+
+  if (!*opt_brailleDevice) {
+    changeStringSetting(&opt_brailleDevice, BRAILLE_DEVICE);
+  }
+
+  if ((braille = loadBrailleDriver(driver, &object, opt_driversDirectory))) {
+    const char *const *parameterNames = braille->parameters;
+    char **parameterSettings;
+
+    if (!parameterNames) {
+      static const char *const noNames[] = {NULL};
+
+      parameterNames = noNames;
+    }
+
+    {
+      const char *const *name = parameterNames;
+      unsigned int count;
+      char **setting;
+
+      while (*name) name += 1;
+      count = name - parameterNames;
+
+      if (!(parameterSettings = malloc((count + 1) * sizeof(*parameterSettings)))) {
+        logMallocError();
+        return PROG_EXIT_FATAL;
+      }
+
+      setting = parameterSettings;
+      while (count--) *setting++ = "";
+      *setting = NULL;
+    }
+
+    while (argc) {
+      char *assignment = *argv++;
+      int ok = 0;
+      char *delimiter = strchr(assignment, '=');
+
+      if (!delimiter) {
+        logMessage(LOG_ERR, "missing braille driver parameter value: %s", assignment);
+      } else if (delimiter == assignment) {
+        logMessage(LOG_ERR, "missing braille driver parameter name: %s", assignment);
+      } else {
+        size_t nameLength = delimiter - assignment;
+        const char *const *name = parameterNames;
+
+        while (*name) {
+          if (strncasecmp(assignment, *name, nameLength) == 0) {
+            parameterSettings[name - parameterNames] = delimiter + 1;
+            ok = 1;
+            break;
+          }
+
+          name += 1;
+        }
+
+        if (!ok) logMessage(LOG_ERR, "invalid braille driver parameter: %s", assignment);
+      }
+
+      if (!ok) return PROG_EXIT_SYNTAX;
+      argc -= 1;
+    }
+
+    constructBrailleDisplay(&brl);
+    identifyBrailleDriver(braille, 0);		/* start-up messages */
+
+    if (braille->construct(&brl, parameterSettings, opt_brailleDevice)) {
+      if (ensureBrailleBuffer(&brl, LOG_INFO)) {
+        if (brl.keyNames) {
+          char *path = makeInputTablePath(opt_tablesDirectory, braille->definition.code, brl.keyBindings);
+
+          if (path) {
+            brl.keyTable = compileKeyTable(path, brl.keyNames);
+
+            free(path);
+          }
+        }
+
+        beginCommandQueue();
+        startBrailleInput();
+        learnMode(10000);
+        stopBrailleInput();
+
+        if (brl.keyTable) {
+          KeyTable *table = brl.keyTable;
+
+          brl.keyTable = NULL;
+          destroyKeyTable(table);
+        }
+
+        braille->destruct(&brl);		/* finish with the display */
+        exitStatus = PROG_EXIT_SUCCESS;
+      } else {
+        logMessage(LOG_ERR, "can't allocate braille buffer");
+        exitStatus = PROG_EXIT_FATAL;
+      }
+    } else {
+      logMessage(LOG_ERR, "can't initialize braille driver");
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  } else {
+    logMessage(LOG_ERR, "can't load braille driver");
+    exitStatus = PROG_EXIT_FATAL;
+  }
+
+  return exitStatus;
+}
+
+typedef struct {
+  unsigned endWait:1;
+} MessageData;
+
+static int
+handleMessageCommands (int command, void *data) {
+  MessageData *mgd = data;
+
+  mgd->endWait = 1;
+  return 1;
+}
+
+ASYNC_CONDITION_TESTER(testEndMessageWait) {
+  const MessageData *mgd = data;
+
+  return mgd->endWait;
+}
+
+int
+message (const char *mode, const char *text, MessageOptions options) {
+  MessageData mgd;
+
+  size_t size = brl.textColumns * brl.textRows;
+  wchar_t buffer[size];
+
+  size_t length = countUtf8Characters(text);
+  wchar_t characters[length + 1];
+  const wchar_t *character = characters;
+
+  clearStatusCells(&brl);
+  makeWcharsFromUtf8(text, characters, ARRAY_COUNT(characters));
+
+  pushCommandEnvironment("message", NULL, NULL);
+  pushCommandHandler("message", KTB_CTX_WAITING,
+                     handleMessageCommands, NULL, &mgd);
+
+  while (length) {
+    size_t count = (length <= size)? length: (size - 1);
+
+    wmemcpy(buffer, character, count);
+    character += count;
+    length -= count;
+
+    if (length) {
+      buffer[(count = size) - 1] = WC_C('-');
+    }
+    wmemset(&buffer[count], WC_C(' '), (size - count));
+
+    {
+      unsigned int i;
+
+      for (i=0; i<size; i+=1) {
+        brl.buffer[i] = convertCharacterToDots(textTable, buffer[i]);
+      }
+    }
+
+    if (!braille->writeWindow(&brl, buffer)) return 0;
+
+    if (length) {
+      int delay = MESSAGE_HOLD_TIMEOUT - brl.writeDelay;
+
+      mgd.endWait = 0;
+      drainBrailleOutput(&brl, 0);
+      asyncAwaitCondition(delay, testEndMessageWait, &mgd);
+    }
+  }
+
+  popCommandEnvironment();
+  return 1;
+}
+
+#include "scr.h"
+
+KeyTableCommandContext
+getScreenCommandContext (void) {
+  return KTB_CTX_DEFAULT;
+}
+
+#include "alert.h"
+
+void
+alert (AlertIdentifier identifier) {
+}
+
+void
+speakAlertText (const wchar_t *text) {
+}
+
+#include "api_control.h"
+
+const ApiMethods api;
diff --git a/Programs/brltty-atb.c b/Programs/brltty-atb.c
new file mode 100644
index 0000000..c595a50
--- /dev/null
+++ b/Programs/brltty-atb.c
@@ -0,0 +1,79 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "atb.h"
+
+static char *opt_tablesDirectory;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing tables.")
+  },
+END_OPTION_TABLE(programOptions)
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_SUCCESS;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-atb",
+
+      .usage = {
+        .purpose = strtext("Check an attributes table."),
+        .parameters = "attributes-table",
+      }
+    };
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (argc) {
+    const char *tableName = (argc--, *argv++);
+    char *tablePath = makeAttributesTablePath(opt_tablesDirectory, tableName);
+
+    if (tablePath) {
+      if ((attributesTable = compileAttributesTable(tablePath))) {
+        exitStatus = PROG_EXIT_SUCCESS;
+
+        destroyAttributesTable(attributesTable);
+      } else {
+        exitStatus = PROG_EXIT_FATAL;
+      }
+
+      free(tablePath);
+    } else {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  } else {
+    logMessage(LOG_ERR, "missing attributes table name");
+    exitStatus = PROG_EXIT_SYNTAX;
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-cldr.c b/Programs/brltty-cldr.c
new file mode 100644
index 0000000..6887ef5
--- /dev/null
+++ b/Programs/brltty-cldr.c
@@ -0,0 +1,262 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "cldr.h"
+#include "datafile.h"
+#include "utf8.h"
+
+#define DEFAULT_OUTPUT_FORMAT "%s\\t%n\\n"
+
+static char *opt_outputFormat;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "output-format",
+    .letter = 'f',
+    .argument = strtext("string"),
+    .setting.string = &opt_outputFormat,
+    .internal.setting = DEFAULT_OUTPUT_FORMAT,
+    .description = strtext("The format of each output line.")
+  },
+END_OPTION_TABLE(programOptions)
+
+static
+BEGIN_USAGE_NOTES(usageNotes)
+  "The output format is printf-like -",
+  "arbitrary text which may contain",
+  "field specifiers (introduced via a percent sign [%])",
+  "and/or special characters (introduced via a backslash [\\]).",
+  "The default format, excluding the quotes, is \"" DEFAULT_OUTPUT_FORMAT "\".",
+  "",
+  "These field specifiers are recognized:",
+  "  %n  the name of the character sequence",
+  "  %s  the character sequence itself",
+  "  %x  the character sequence in hexadecimal",
+  "  %%  a literal percent sign",
+  "",
+  "These special characters are recognized:",
+  "  \\a  alert (bell)",
+  "  \\b  backspace",
+  "  \\e  escape",
+  "  \\f  form feed",
+  "  \\n  new line",
+  "  \\r  carriage return",
+  "  \\t  horizontal tab",
+  "  \\v  vertical tab",
+  "  \\\\  literal backslasha  ",
+END_USAGE_NOTES
+
+static void
+onFormatError (void) {
+  exit(PROG_EXIT_SYNTAX);
+}
+
+static void
+onMissingCharacter (const char *type) {
+  logMessage(LOG_ERR, "missing %s character", type);
+  onFormatError();
+}
+
+static void
+onUnrecognizedCharacter (const char *type, int byte) {
+  logMessage(LOG_ERR, "unrecognized %s character: %c", type, byte);
+  onFormatError();
+}
+
+static void
+onOutputError (void) {
+  logMessage(LOG_ERR, "output error %d: %s", errno, strerror(errno));
+  exit(PROG_EXIT_FATAL);
+}
+
+static void
+putByte (int byte) {
+  if (fputc(byte, stdout) == EOF) onOutputError();
+}
+
+static void
+putString (const char *string) {
+  if (fputs(string, stdout) == EOF) onOutputError();
+}
+
+static void
+putHexadecimal (const char *string) {
+  size_t size = strlen(string) + 1;
+  wchar_t characters[size];
+
+  const char *byte = string;
+  wchar_t *character = characters;
+  wchar_t *end = character;
+  convertUtf8ToWchars(&byte, &end, size);
+
+  while (character < end) {
+    if (writeHexadecimalCharacter(stdout, *character) == EOF) onOutputError();
+    character += 1;
+  }
+}
+
+static
+CLDR_ANNOTATION_HANDLER(handleAnnotation) {
+  typedef enum {LITERAL, FORMAT, ESCAPE} State;
+  State state = LITERAL;
+  const char *format = opt_outputFormat;
+
+  while (*format) {
+    int byte = *format & 0XFF;
+
+    switch (state) {
+      case LITERAL: {
+        switch (byte) {
+          case '%':
+            state = FORMAT;
+            break;
+
+          case '\\':
+            state = ESCAPE;
+            break;
+
+          default:
+            putByte(byte);
+            break;
+        }
+
+        break;
+      }
+
+      case FORMAT: {
+        switch (byte) {
+          case 'n':
+            putString(parameters->name);
+            break;
+
+          case 's':
+            putString(parameters->sequence);
+            break;
+
+          case 'x':
+            putHexadecimal(parameters->sequence);
+            break;
+
+          case '%':
+            putByte(byte);
+            break;
+
+          default:
+            onUnrecognizedCharacter("format", byte);
+            return 0;
+        }
+
+        state = LITERAL;
+        break;
+      }
+
+      case ESCAPE: {
+        static const char escapes[] = {
+          ['a'] = '\a',
+          ['b'] = '\b',
+          ['e'] = '\e',
+          ['f'] = '\f',
+          ['n'] = '\n',
+          ['r'] = '\r',
+          ['t'] = '\t',
+          ['v'] = '\v',
+          ['\\'] = '\\'
+        };
+
+        switch (byte) {
+          default: {
+            if (byte < ARRAY_COUNT(escapes)) {
+              char escape = escapes[byte];
+
+              if (escape) {
+                putByte(escape);
+                break;
+              }
+            }
+
+            onUnrecognizedCharacter("escape", byte);
+            return 0;
+          }
+        }
+
+        state = LITERAL;
+        break;
+      }
+    }
+
+    format += 1;
+  }
+
+  switch (state) {
+    case LITERAL:
+      return 1;
+
+    case FORMAT:
+      onMissingCharacter("format");
+      break;
+
+    case ESCAPE:
+      onMissingCharacter("escape");
+      break;
+  }
+
+  return 0;
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-cldr",
+
+      .usage = {
+        .purpose = strtext("List the characters defined within a CLDR (Common Locale Data Repository Project) annotations file."),
+        .parameters = "input-file",
+        .notes = USAGE_NOTES(usageNotes),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (argc < 1) {
+    logMessage(LOG_ERR, "missing annotations file name");
+    return PROG_EXIT_SYNTAX;
+  }
+
+  const char *inputFile = *argv++;
+  argc -= 1;
+
+  if (argc > 0) {
+    logMessage(LOG_ERR, "too many parameters");
+    return PROG_EXIT_SYNTAX;
+  }
+
+  return cldrParseFile(inputFile, handleAnnotation, NULL)?
+         PROG_EXIT_SUCCESS:
+         PROG_EXIT_FATAL;
+}
diff --git a/Programs/brltty-clip.c b/Programs/brltty-clip.c
new file mode 100644
index 0000000..10e9819
--- /dev/null
+++ b/Programs/brltty-clip.c
@@ -0,0 +1,264 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "cmdline.h"
+#include "datafile.h"
+#include "utf8.h"
+#include "brlapi.h"
+
+static char *opt_apiHost;
+static char *opt_authSchemes;
+static int opt_getContent;
+static char *opt_setContent;
+static int opt_removeNewline;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "brlapi",
+    .letter = 'b',
+    .argument = "[host][:port]",
+    .setting.string = &opt_apiHost,
+    .description = "BrlAPIa host and/or port to connect to."
+  },
+
+  { .word = "auth",
+    .letter = 'a',
+    .argument = "scheme+...",
+    .setting.string = &opt_authSchemes,
+    .description = "BrlAPI authorization/authentication schemes."
+  },
+
+  { .word = "get-content",
+    .letter = 'g',
+    .setting.flag = &opt_getContent,
+    .description = "Write the content of the clipboard to standard output."
+  },
+
+  { .word = "set-content",
+    .letter = 's',
+    .argument = "content",
+    .setting.string = &opt_setContent,
+    .description = "Set the content of the clipboard."
+  },
+
+  { .word = "remove-newline",
+    .letter = 'r',
+    .setting.flag = &opt_removeNewline,
+    .description = "Remove a trailing newline."
+  },
+END_OPTION_TABLE(programOptions)
+
+static const brlapi_param_t apiParameter = BRLAPI_PARAM_CLIPBOARD_CONTENT;
+static const brlapi_param_subparam_t apiSubparam = 0;
+static const brlapi_param_flags_t apiFlags = BRLAPI_PARAMF_GLOBAL;
+
+static char *
+getClipboardContent (void) {
+  return brlapi_getParameterAlloc(apiParameter, apiSubparam, apiFlags, NULL);
+}
+
+static int
+setClipboardContent (const char *content, size_t length) {
+  if (opt_removeNewline) {
+    if (length > 0) {
+      size_t newLength = length - 1;
+      if (content[newLength] == '\n') length = newLength;
+    }
+  }
+
+  return brlapi_setParameter(apiParameter, apiSubparam, apiFlags, content, length) >= 0;
+}
+
+typedef struct {
+  struct {
+    wchar_t *characters;
+    size_t size;
+    size_t count;
+  } content;
+} LineProcessingData;
+
+static int
+addContent (LineProcessingData *lpd, const wchar_t *characters, size_t count) {
+  {
+    size_t newSize = lpd->content.count + count;
+
+    if (newSize > lpd->content.size) {
+      newSize |= 0XFFF;
+      newSize += 1;
+      wchar_t *newCharacters = allocateCharacters(newSize);
+
+      if (!newCharacters) {
+        logMallocError();
+        return 0;
+      }
+
+      if (lpd->content.characters) {
+        wmemcpy(newCharacters, lpd->content.characters, lpd->content.count);
+      }
+
+      lpd->content.characters = newCharacters;
+      lpd->content.size = newSize;
+    }
+  }
+
+  wmemcpy(&lpd->content.characters[lpd->content.count], characters, count);
+  lpd->content.count += count;
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processInputLine) {
+  LineProcessingData *lpd = data;
+
+  {
+    DataOperand line;
+    getTextRemaining(file, &line);
+    if (!addContent(lpd, line.characters, line.length)) return 0;
+  }
+
+  {
+    static const wchar_t delimiter[] = {WC_C('\n')};
+    return addContent(lpd, delimiter, ARRAY_COUNT(delimiter));
+  }
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_FATAL;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-clip",
+
+      .usage = {
+        .purpose = strtext("Manage brltty's clipboard from the command line."),
+        .parameters = "[{input-file | -} ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  brlapi_connectionSettings_t settings = {
+    .host = opt_apiHost,
+    .auth = opt_authSchemes
+  };
+
+  brlapi_fileDescriptor fileDescriptor = brlapi_openConnection(&settings, &settings);
+
+  if (fileDescriptor != (brlapi_fileDescriptor)(-1)) {
+    char *oldContent = NULL;
+
+    LineProcessingData lpd = {
+      .content = {
+        .characters = NULL,
+        .size = 0,
+        .count = 0
+      }
+    };
+
+    int getContent = !!opt_getContent;
+    int setContent = !!*opt_setContent;
+
+    if (!(getContent || setContent)) {
+      const InputFilesProcessingParameters parameters = {
+        .dataFileParameters = {
+          .options = DFO_NO_COMMENTS,
+          .processOperands = processInputLine,
+          .data = &lpd
+        }
+      };
+
+      exitStatus = processInputFiles(argv, argc, &parameters);
+    } else if (argc > 0) {
+      logMessage(LOG_ERR, "too many arguments");
+      exitStatus = PROG_EXIT_SYNTAX;
+    } else {
+      exitStatus = PROG_EXIT_SUCCESS;
+    }
+
+    if (exitStatus == PROG_EXIT_SUCCESS) {
+      if (getContent) {
+        oldContent = getClipboardContent();
+        if (!oldContent) exitStatus = PROG_EXIT_FATAL;
+      }
+    }
+
+    if (exitStatus == PROG_EXIT_SUCCESS) {
+      if (setContent) {
+        if (!setClipboardContent(opt_setContent, strlen(opt_setContent))) {
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      }
+    }
+
+    if (lpd.content.characters) {
+      if (exitStatus == PROG_EXIT_SUCCESS) {
+        exitStatus = PROG_EXIT_FATAL;
+
+        size_t length;
+        char *content = getUtf8FromWchars(lpd.content.characters, lpd.content.count, &length);
+
+        if (content) {
+          if (setClipboardContent(content, length)) {
+            exitStatus = PROG_EXIT_SUCCESS;
+          }
+
+          free(content);
+        }
+      }
+
+      free(lpd.content.characters);
+    }
+
+    if (oldContent) {
+      if (exitStatus == PROG_EXIT_SUCCESS) {
+        if (opt_removeNewline) {
+          size_t length = strlen(oldContent);
+
+          if (length > 0) {
+            if (oldContent[--length] == '\n') {
+              oldContent[length] = 0;
+            }
+          }
+        }
+
+        printf("%s", oldContent);
+
+        if (ferror(stdout)) {
+          logMessage(LOG_ERR, "standard output write error: %s", strerror(errno));
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      }
+
+      free(oldContent);
+    }
+
+    brlapi_closeConnection();
+  } else {
+    logMessage(LOG_ERR, "failed to connect to %s using auth %s: %s",
+               settings.host, settings.auth, brlapi_strerror(&brlapi_error));
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-ctb.c b/Programs/brltty-ctb.c
new file mode 100644
index 0000000..9f7300c
--- /dev/null
+++ b/Programs/brltty-ctb.c
@@ -0,0 +1,564 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "prefs.h"
+#include "log.h"
+#include "file.h"
+#include "datafile.h"
+#include "parse.h"
+#include "utf8.h"
+#include "unicode.h"
+#include "ascii.h"
+#include "ttb.h"
+#include "ctb.h"
+
+static char *opt_tablesDirectory;
+static char *opt_contractionTable;
+static char *opt_textTable;
+static char *opt_verificationTable;
+
+static char *opt_outputWidth;
+static int opt_reformatText;
+static int opt_forceOutput;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "output-width",
+    .letter = 'w',
+    .argument = "columns",
+    .setting.string = &opt_outputWidth,
+    .internal.setting = "",
+    .description = strtext("Maximum length of an output line.")
+  },
+
+  { .word = "reformat-text",
+    .letter = 'r',
+    .setting.flag = &opt_reformatText,
+    .description = strtext("Reformat input.")
+  },
+
+  { .word = "force-output",
+    .letter = 'f',
+    .setting.flag = &opt_forceOutput,
+    .description = strtext("Force immediate output.")
+  },
+
+  { .word = "contraction-table",
+    .letter = 'c',
+    .argument = "file",
+    .setting.string = &opt_contractionTable,
+    .internal.setting = "en-us-g2",
+    .description = strtext("Contraction table.")
+  },
+
+  { .word = "text-table",
+    .letter = 't',
+    .argument = "file",
+    .setting.string = &opt_textTable,
+    .description = strtext("Text table.")
+  },
+
+  { .word = "verification-table",
+    .letter = 'v',
+    .argument = "file",
+    .setting.string = &opt_verificationTable,
+    .description = strtext("Contraction verification table.")
+  },
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing tables.")
+  },
+END_OPTION_TABLE(programOptions)
+
+static wchar_t *inputBuffer;
+static size_t inputSize;
+static size_t inputLength;
+
+static FILE *outputStream;
+static unsigned char *outputBuffer;
+static int outputWidth;
+static int outputExtend;
+
+#define VERIFICATION_TABLE_EXTENSION ".cvb"
+#define VERIFICATION_SUBTABLE_EXTENSION ".cvi"
+
+static char *verificationTablePath;
+static FILE *verificationTableStream;
+
+static int (*processInputCharacters) (const wchar_t *characters, size_t length, void *data);
+static int (*putCell) (unsigned char cell, void *data);
+
+typedef struct {
+  ProgramExitStatus exitStatus;
+} LineProcessingData;
+
+static void
+noMemory (void *data) {
+  LineProcessingData *lpd = data;
+
+  logMallocError();
+  lpd->exitStatus = PROG_EXIT_FATAL;
+}
+
+static int
+checkOutputStream (void *data) {
+  LineProcessingData *lpd = data;
+
+  if (ferror(outputStream)) {
+    logSystemError("output");
+    lpd->exitStatus = PROG_EXIT_FATAL;
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+flushOutputStream (void *data) {
+  fflush(outputStream);
+  return checkOutputStream(data);
+}
+
+static int
+putCharacter (unsigned char character, void *data) {
+  fputc(character, outputStream);
+  return checkOutputStream(data);
+}
+
+static int
+putCellCharacter (wchar_t character, void *data) {
+  Utf8Buffer utf8;
+  size_t utfs = convertWcharToUtf8(character, utf8);
+
+  fprintf(outputStream, "%.*s", (int)utfs, utf8);
+  return checkOutputStream(data);
+}
+
+static int
+putTextCell (unsigned char cell, void *data) {
+  return putCellCharacter(convertDotsToCharacter(textTable, cell), data);
+}
+
+static int
+putBrailleCell (unsigned char cell, void *data) {
+  return putCellCharacter((UNICODE_BRAILLE_ROW | cell), data);
+}
+
+static int
+writeCharacters (const wchar_t *inputLine, size_t inputLength, void *data) {
+  const wchar_t *inputBuffer = inputLine;
+
+  while (inputLength) {
+    int inputCount = inputLength;
+    int outputCount = outputWidth;
+
+    if (!outputBuffer) {
+      if (!(outputBuffer = malloc(outputWidth))) {
+        noMemory(data);
+        return 0;
+      }
+    }
+
+    contractText(contractionTable, NULL,
+                 inputBuffer, &inputCount,
+                 outputBuffer, &outputCount,
+                 NULL, CTB_NO_CURSOR);
+
+    if ((inputCount < inputLength) && outputExtend) {
+      free(outputBuffer);
+      outputBuffer = NULL;
+      outputWidth <<= 1;
+    } else {
+      {
+        int index;
+
+        for (index=0; index<outputCount; index+=1)
+          if (!putCell(outputBuffer[index], data))
+            return 0;
+      }
+
+      inputBuffer += inputCount;
+      inputLength -= inputCount;
+
+      if (inputLength)
+        if (!putCharacter('\n', data))
+          return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+flushCharacters (wchar_t end, void *data) {
+  if (inputLength) {
+    if (!writeCharacters(inputBuffer, inputLength, data)) return 0;
+    inputLength = 0;
+
+    if (end)
+      if (!putCharacter(end, data))
+        return 0;
+  }
+
+  return 1;
+}
+
+static int
+processCharacters (const wchar_t *characters, size_t count, wchar_t end, void *data) {
+  if (opt_reformatText && count) {
+    if (iswspace(characters[0]))
+      if (!flushCharacters('\n', data))
+        return 0;
+
+    {
+      unsigned int spaces = !inputLength? 0: 1;
+      size_t newLength = inputLength + spaces + count;
+
+      if (newLength > inputSize) {
+        size_t newSize = newLength | 0XFF;
+        wchar_t *newBuffer = calloc(newSize, sizeof(*newBuffer));
+
+        if (!newBuffer) {
+          noMemory(data);
+          return 0;
+        }
+
+        wmemcpy(newBuffer, inputBuffer, inputLength);
+        free(inputBuffer);
+
+        inputBuffer = newBuffer;
+        inputSize = newSize;
+      }
+
+      while (spaces) {
+        inputBuffer[inputLength++] = WC_C(' ');
+        spaces -= 1;
+      }
+
+      wmemcpy(&inputBuffer[inputLength], characters, count);
+      inputLength += count;
+    }
+
+    if (end != '\n') {
+      if (!flushCharacters(0, data)) return 0;
+      if (!putCharacter(end, data)) return 0;
+    }
+  } else {
+    if (!flushCharacters('\n', data)) return 0;
+    if (!writeCharacters(characters, count, data)) return 0;
+    if (!putCharacter(end, data)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+writeContractedBraille (const wchar_t *characters, size_t length, void *data) {
+  const wchar_t *character = characters;
+
+  while (1) {
+    const wchar_t *end = wmemchr(character, ASCII_FF, length);
+    size_t count;
+
+    if (!end) break;
+    count = end - character;
+    if (!processCharacters(character, count, *end, data)) return 0;
+
+    count += 1;
+    character += count;
+    length -= count;
+  }
+  if (!processCharacters(character, length, '\n', data)) return 0;
+
+  if (opt_forceOutput)
+    if (!flushOutputStream(data))
+      return 0;
+
+  return 1;
+}
+
+static char *
+makeUtf8FromCells (unsigned char *cells, size_t count) {
+  char *text = malloc((count * UTF8_LEN_MAX) + 1);
+
+  if (text) {
+    char *ch = text;
+    size_t i;
+
+    for (i=0; i<count; i+=1) {
+      Utf8Buffer utf8;
+      size_t utfs = convertWcharToUtf8(cells[i]|UNICODE_BRAILLE_ROW, utf8);
+
+      if (utfs) {
+        ch = mempcpy(ch, utf8, utfs);
+      } else {
+        *ch++ = ' ';
+      }
+    }
+
+    *ch = 0;
+
+    return text;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+writeVerificationTableLine (const wchar_t *characters, size_t length, void *data) {
+  int inputCount = length;
+  int outputCount = length << 2;
+  unsigned char outputBuffer[outputCount];
+
+  contractText(contractionTable, NULL,
+               characters, &inputCount,
+               outputBuffer, &outputCount,
+               NULL, CTB_NO_CURSOR);
+
+  if (fprintf(verificationTableStream, "contracts ") == EOF) goto outputError;
+  if (!writeEscapedCharacters(verificationTableStream, characters, length)) goto outputError;
+  if (fprintf(verificationTableStream, " ") == EOF) goto outputError;
+  if (!writeDotsCells(verificationTableStream, outputBuffer, outputCount)) goto outputError;
+  if (fprintf(verificationTableStream, " ") == EOF) goto outputError;
+  if (!writeUtf8Cells(verificationTableStream, outputBuffer, outputCount)) goto outputError;
+  if (fprintf(verificationTableStream, "\n") == EOF) goto outputError;
+  return 1;
+
+outputError:
+  logMessage(LOG_ERR, "output error: %s", strerror(errno));
+  return 0;
+}
+
+static DATA_OPERANDS_PROCESSOR(processContractsOperands) {
+  DataString text;
+
+  if (getDataString(file, &text, 1, "uncontracted text")) {
+    ByteOperand cells;
+
+    if (getCellsOperand(file, &cells, "contracted braille")) {
+      int inputCount = text.length;
+      int outputCount = inputCount << 3;
+      unsigned char outputBuffer[outputCount];
+
+      contractText(contractionTable, NULL,
+		   text.characters, &inputCount,
+		   outputBuffer, &outputCount,
+		   NULL, CTB_NO_CURSOR);
+
+      if ((outputCount != cells.length) ||
+	  (memcmp(cells.bytes, outputBuffer, outputCount) != 0)) {
+	char *expected;
+
+	if ((expected = makeUtf8FromCells(cells.bytes, cells.length))) {
+           char *actual;
+
+           if ((actual = makeUtf8FromCells(outputBuffer, outputCount))) {
+              reportDataError(file,
+                              "%" PRIws ": expected %s, got %s",
+                              text.characters, expected, actual); 
+              free(actual);
+           }
+
+           free(expected);
+        }
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static  DATA_OPERANDS_PROCESSOR(processVerificationOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    {.name=WS_C("contracts"), .processor=processContractsOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "contraction verification directive", data);
+}
+
+static ProgramExitStatus
+processVerificationTable (void) {
+  if (setTableDataVariables(VERIFICATION_TABLE_EXTENSION, VERIFICATION_SUBTABLE_EXTENSION)) {
+    const DataFileParameters parameters = {
+      .processOperands = processVerificationOperands,
+      .data = NULL
+    };
+
+    if (processDataStream(NULL, verificationTableStream, verificationTablePath, &parameters)) {
+      return PROG_EXIT_SUCCESS;
+    }
+  }
+
+  return PROG_EXIT_FATAL;
+}
+
+static DATA_OPERANDS_PROCESSOR(processInputLine) {
+  DataOperand line;
+  getTextRemaining(file, &line);
+  return processInputCharacters(line.characters, line.length, data);
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_FATAL;
+
+  verificationTablePath = NULL;
+  verificationTableStream = NULL;
+  processInputCharacters = writeContractedBraille;
+
+  resetPreferences();
+  prefs.expandCurrentWord = 0;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-ctb",
+
+      .usage = {
+        .purpose = strtext("Check/validate a contraction (literary braille) table, or translate text into contracted braille."),
+        .parameters = "[{input-file | -} ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  inputBuffer = NULL;
+  inputSize = 0;
+  inputLength = 0;
+
+  outputStream = stdout;
+  outputBuffer = NULL;
+
+  if ((outputExtend = !*opt_outputWidth)) {
+    outputWidth = 0X80;
+  } else {
+    static const int minimum = 1;
+
+    if (!validateInteger(&outputWidth, opt_outputWidth, &minimum, NULL)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid output width", opt_outputWidth);
+      return PROG_EXIT_SYNTAX;
+    }
+  }
+
+  {
+    char *contractionTablePath;
+
+    if ((contractionTablePath = makeContractionTablePath(opt_tablesDirectory, opt_contractionTable))) {
+      if ((contractionTable = compileContractionTable(contractionTablePath))) {
+        if (*opt_textTable) {
+          putCell = putTextCell;
+          char *textTablePath;
+
+          if ((textTablePath = makeTextTablePath(opt_tablesDirectory, opt_textTable))) {
+            textTable = compileTextTable(textTablePath);
+            exitStatus = textTable? PROG_EXIT_SUCCESS: PROG_EXIT_FATAL;
+            free(textTablePath);
+          } else {
+            exitStatus = PROG_EXIT_FATAL;
+          }
+        } else {
+          putCell = putBrailleCell;
+          textTable = NULL;
+          exitStatus = PROG_EXIT_SUCCESS;
+        }
+
+        if (exitStatus == PROG_EXIT_SUCCESS) {
+          if (opt_verificationTable && *opt_verificationTable) {
+            if ((verificationTablePath = makeFilePath(NULL, opt_verificationTable, VERIFICATION_TABLE_EXTENSION))) {
+              const char *verificationTableMode = (argc > 0)? "w": "r";
+
+              if ((verificationTableStream = openDataFile(verificationTablePath, verificationTableMode, 0))) {
+                processInputCharacters = writeVerificationTableLine;
+              } else {
+                exitStatus = PROG_EXIT_FATAL;
+              }
+            } else {
+              exitStatus = PROG_EXIT_FATAL;
+            }
+          }
+        }
+
+        if (exitStatus == PROG_EXIT_SUCCESS) {
+          if (verificationTableStream && !argc) {
+            exitStatus = processVerificationTable();
+          } else {
+            LineProcessingData lpd = {
+              .exitStatus = PROG_EXIT_SUCCESS
+            };
+
+            const InputFilesProcessingParameters parameters = {
+              .dataFileParameters = {
+                .options = DFO_NO_COMMENTS,
+                .processOperands = processInputLine,
+                .data = &lpd
+              }
+            };
+
+            if ((exitStatus = processInputFiles(argv, argc, &parameters)) == PROG_EXIT_SUCCESS) {
+              if (!(flushCharacters('\n', &lpd) && flushOutputStream(&lpd))) {
+                exitStatus = lpd.exitStatus;
+              }
+            }
+          }
+
+          if (textTable) destroyTextTable(textTable);
+        }
+
+        destroyContractionTable(contractionTable);
+      } else {
+        exitStatus = PROG_EXIT_FATAL;
+      }
+
+      free(contractionTablePath);
+    }
+  }
+
+  if (verificationTableStream) {
+    if (fclose(verificationTableStream) == EOF) {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+
+    verificationTableStream = NULL;
+  }
+
+  if (verificationTablePath) {
+    free(verificationTablePath);
+    verificationTablePath = NULL;
+  }
+
+  if (outputBuffer) free(outputBuffer);
+  if (inputBuffer) free(inputBuffer);
+  return exitStatus;
+}
diff --git a/Programs/brltty-hid.c b/Programs/brltty-hid.c
new file mode 100644
index 0000000..d242aeb
--- /dev/null
+++ b/Programs/brltty-hid.c
@@ -0,0 +1,1101 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "strfmt.h"
+#include "parse.h"
+#include "io_hid.h"
+#include "hid_items.h"
+#include "hid_inspect.h"
+
+static int opt_matchUSBDevices;
+static int opt_matchBluetoothDevices;
+
+static char *opt_matchVendorIdentifier;
+static char *opt_matchProductIdentifier;
+
+static char *opt_matchManufacturerName;
+static char *opt_matchProductDescription;
+static char *opt_matchSerialNumber;
+
+static char *opt_matchDeviceAddress;
+static char *opt_matchDeviceName;
+
+static int opt_showDeviceIdentifiers;
+static int opt_showDeviceAddress;
+static int opt_showDeviceName;
+static int opt_showHostPath;
+static int opt_showHostDevice;
+
+static int opt_listItems;
+static int opt_listReports;
+
+static char *opt_readReport;
+static char *opt_readFeature;
+
+static char *opt_writeReport;
+static char *opt_writeFeature;
+
+static int opt_echoInput;
+static char *opt_inputTimeout;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "match-usb-devices",
+    .letter = 'u',
+    .setting.flag = &opt_matchUSBDevices,
+    .description = strtext("Filter for a USB device (the default if not ambiguous).")
+  },
+
+  { .word = "match-bluetooth-devices",
+    .letter = 'b',
+    .setting.flag = &opt_matchBluetoothDevices,
+    .description = strtext("Filter for a Bluetooth device.")
+  },
+
+  { .word = "match-vendor-identifier",
+    .letter = 'v',
+    .argument = strtext("identifier"),
+    .setting.string = &opt_matchVendorIdentifier,
+    .description = strtext("Match the vendor identifier (four hexadecimal digits).")
+  },
+
+  { .word = "match-product-identifier",
+    .letter = 'p',
+    .argument = strtext("identifier"),
+    .setting.string = &opt_matchProductIdentifier,
+    .description = strtext("Match the product identifier (four hexadecimal digits).")
+  },
+
+  { .word = "match-manufacturer-name",
+    .letter = 'm',
+    .argument = strtext("string"),
+    .setting.string = &opt_matchManufacturerName,
+    .description = strtext("Match the start of the manufacturer name (USB only).")
+  },
+
+  { .word = "match-product-description",
+    .letter = 'd',
+    .argument = strtext("string"),
+    .setting.string = &opt_matchProductDescription,
+    .description = strtext("Match the start of the product description (USB only).")
+  },
+
+  { .word = "match-serial-number",
+    .letter = 's',
+    .argument = strtext("string"),
+    .setting.string = &opt_matchSerialNumber,
+    .description = strtext("Match the start of the serial number (USB only).")
+  },
+
+  { .word = "match-device-address",
+    .letter = 'a',
+    .argument = strtext("octets"),
+    .setting.string = &opt_matchDeviceAddress,
+    .description = strtext("Match the full device address (Bluetooth only - all six two-digit, hexadecimal octets separated by a colon [:]).")
+  },
+
+  { .word = "match-device-name",
+    .letter = 'n',
+    .argument = strtext("string"),
+    .setting.string = &opt_matchDeviceName,
+    .description = strtext("Match the start of the device name (Bluetooth only).")
+  },
+
+  { .word = "show-device-identifiers",
+    .letter = 'I',
+    .setting.flag = &opt_showDeviceIdentifiers,
+    .description = strtext("Show the vendor and product identifiers.")
+  },
+
+  { .word = "show-device-address",
+    .letter = 'A',
+    .setting.flag = &opt_showDeviceAddress,
+    .description = strtext("Show the device address (USB serial number, Bluetooth device address, etc).")
+  },
+
+  { .word = "show-device-name",
+    .letter = 'N',
+    .setting.flag = &opt_showDeviceName,
+    .description = strtext("Show the device name (USB manufacturer and/or product strings, Bluetooth device name, etc).")
+  },
+
+  { .word = "show-host-path",
+    .letter = 'P',
+    .setting.flag = &opt_showHostPath,
+    .description = strtext("Show the host path (USB topology, Bluetooth host controller address, etc).")
+  },
+
+  { .word = "show-host-device",
+    .letter = 'D',
+    .setting.flag = &opt_showHostDevice,
+    .description = strtext("Show the host device (usually its absolute path).")
+  },
+
+  { .word = "list-items",
+    .letter = 'l',
+    .setting.flag = &opt_listItems,
+    .description = strtext("List the HID report descriptor's items.")
+  },
+
+  { .word = "list-reports",
+    .letter = 'L',
+    .setting.flag = &opt_listReports,
+    .description = strtext("List each report's identifier and sizes.")
+  },
+
+  { .word = "read-report",
+    .letter = 'r',
+    .argument = strtext("identifier"),
+    .setting.string = &opt_readReport,
+    .description = strtext("Read (get) an input report (two hexadecimal digits).")
+  },
+
+  { .word = "read-feature",
+    .letter = 'R',
+    .argument = strtext("identifier"),
+    .setting.string = &opt_readFeature,
+    .description = strtext("Read (get) a feature report (two hexadecimal digits).")
+  },
+
+  { .word = "write-report",
+    .letter = 'w',
+    .argument = strtext("bytes"),
+    .setting.string = &opt_writeReport,
+    .description = strtext("Write (set) an output report (see below)."),
+  },
+
+  { .word = "write-feature",
+    .letter = 'W',
+    .argument = strtext("bytes"),
+    .setting.string = &opt_writeFeature,
+    .description = strtext("Write (set) a feature report (see below)."),
+  },
+
+  { .word = "echo-input",
+    .letter = 'e',
+    .setting.flag = &opt_echoInput,
+    .description = strtext("Echo (in hexadecimal) input received from the device.")
+  },
+
+  { .word = "input-timeout",
+    .letter = 't',
+    .argument = strtext("integer"),
+    .setting.string = &opt_inputTimeout,
+    .description = strtext("The input timeout (in seconds).")
+  },
+END_OPTION_TABLE(programOptions)
+
+static
+BEGIN_USAGE_NOTES(usageNotes)
+  "When writing a report or feature, the bytes don't need to be, but can be, separated from one another by whitespace.",
+  "Each byte is either two hexadecimal digits or zero or more braille dot numbers within [square brackets].",
+  "A byte may optionally be followed by an asterisk [*] and a decimal count - if not specified, 1 is assumed.",
+  "The first byte is the report number - specify 00 for no report number.",
+END_USAGE_NOTES
+
+static FILE *outputStream;
+static int outputError;
+
+static int
+canWriteOutput (void) {
+  if (outputError) return 0;
+  if (!ferror(outputStream)) return 1;
+
+  outputError = errno;
+  return 0;
+}
+
+static int writeBytesLine (
+  const char *format,
+  const unsigned char *from, size_t count,
+  ...
+) PRINTF(1, 4);
+
+static int
+writeBytesLine (const char *format, const unsigned char *from, size_t count, ...) {
+  const unsigned char *to = from + count;
+
+  char bytes[(count * 3) + 1];
+  STR_BEGIN(bytes, sizeof(bytes));
+
+  while (from < to) {
+    STR_PRINTF(" %02X", *from++);
+  }
+  STR_END;
+
+  char label[0X100];
+  {
+    va_list arguments;
+    va_start(arguments, count);
+    vsnprintf(label, sizeof(label), format, arguments);
+    va_end(arguments);
+  }
+
+  fprintf(outputStream, "%s:%s\n", label, bytes);
+  if (!canWriteOutput()) return 0;
+
+  fflush(outputStream);
+  return canWriteOutput();
+}
+
+static int
+openDevice (HidDevice **device) {
+  HidFilter filter = {
+    .usb = {
+      .manufacturerName = opt_matchManufacturerName,
+      .productDescription = opt_matchProductDescription,
+      .serialNumber = opt_matchSerialNumber,
+    },
+
+    .bluetooth = {
+      .macAddress = opt_matchDeviceAddress,
+      .deviceName = opt_matchDeviceName,
+    },
+
+    .flags = {
+      .wantUSB = opt_matchUSBDevices,
+      .wantBluetooth = opt_matchBluetoothDevices,
+    },
+  };
+
+  int ok = hidSetFilterIdentifiers(
+    &filter, opt_matchVendorIdentifier, opt_matchProductIdentifier
+  );
+
+  if (!ok) return 0;
+  return hidOpenDeviceWithFilter(device, &filter);
+}
+
+static const HidItemsDescriptor *
+getItems (HidDevice *device) {
+  const HidItemsDescriptor *items = hidGetItems(device);
+  if (!items) logMessage(LOG_ERR, "HID items not available");
+  return items;
+}
+
+static int
+getReportSize (HidDevice *device, HidReportIdentifier identifier, HidReportSize *size) {
+  const HidItemsDescriptor *items = getItems(device);
+  if (!items) return 0;
+  return hidReportSize(items, identifier, size);
+}
+
+static void
+logUnexpectedLength (
+  const char *what, HidReportIdentifier identifier,
+  size_t expected, size_t actual
+) {
+  logMessage(LOG_WARNING,
+    "unexpected %s length: %02X:"
+    " Expected:%"PRIsize " Actual:%"PRIsize,
+    what, identifier, expected, actual
+  );
+}
+
+static int
+performShowDeviceIdentifiers (HidDevice *device) {
+  HidDeviceIdentifier vendor;
+  HidDeviceIdentifier product;
+
+  if (!hidGetDeviceIdentifiers(device, &vendor, &product)) {
+    logMessage(LOG_WARNING, "vendor/product identifiers not available");
+    return 0;
+  }
+
+  fprintf(outputStream,
+    "Device Identifiers: %04X:%04X\n",
+    vendor, product
+  );
+
+  return 1;
+}
+
+static int
+performShowDeviceAddress (HidDevice *device) {
+  const char *address = hidGetDeviceAddress(device);
+
+  if (!address) {
+    logMessage(LOG_WARNING, "device address not available");
+    return 0;
+  }
+
+  fprintf(outputStream, "Device Address: %s\n", address);
+  return 1;
+}
+
+static int
+performShowDeviceName (HidDevice *device) {
+  const char *name = hidGetDeviceName(device);
+
+  if (!name) {
+    logMessage(LOG_WARNING, "device name not available");
+    return 0;
+  }
+
+  fprintf(outputStream, "Device Name: %s\n", name);
+  return 1;
+}
+
+static int
+performShowHostPath (HidDevice *device) {
+  const char *path = hidGetHostPath(device);
+
+  if (!path) {
+    logMessage(LOG_WARNING, "host path not available");
+    return 0;
+  }
+
+  fprintf(outputStream, "Host Path: %s\n", path);
+  return 1;
+}
+
+static int
+performShowHostDevice (HidDevice *device) {
+  const char *hostDevice = hidGetHostDevice(device);
+
+  if (!hostDevice) {
+    logMessage(LOG_WARNING, "host device not available");
+    return 0;
+  }
+
+  fprintf(outputStream, "Host Device: %s\n", hostDevice);
+  return 1;
+}
+
+static int
+listItem (const char *line, void *data) {
+  fprintf(outputStream, "%s\n", line);
+  return canWriteOutput();
+}
+
+static int
+performListItems (HidDevice *device) {
+  const HidItemsDescriptor *items = getItems(device);
+  if (!items) return 0;
+
+  hidListItems(items, listItem, NULL);
+  return 1;
+}
+
+static int
+performListReports (HidDevice *device) {
+  const HidItemsDescriptor *items = getItems(device);
+  if (!items) return 0;
+
+  HidReports *reports = hidGetReports(items);
+  if (!reports) return 0;
+
+  for (unsigned int index=0; index<reports->count; index+=1) {
+    unsigned char identifier = reports->identifiers[index];
+    HidReportSize size;
+
+    char line[0X40];
+    STR_BEGIN(line, sizeof(line));
+    STR_PRINTF("Report %02X:", identifier);
+
+    if (hidReportSize(items, identifier, &size)) {
+      typedef struct {
+        const char *label;
+        const size_t value;
+      } SizeEntry;
+
+      const SizeEntry sizeTable[] = {
+        { .value = size.input,
+          .label = "In",
+        },
+
+        { .value = size.output,
+          .label = "Out",
+        },
+
+        { .value = size.feature,
+          .label = "Ftr",
+        },
+      };
+
+      const SizeEntry *size = sizeTable;
+      const SizeEntry *end = size + ARRAY_COUNT(sizeTable);
+
+      while (size < end) {
+        if (size->value) {
+          STR_PRINTF(" %s:%"PRIsize, size->label, size->value);
+        }
+
+        size += 1;
+      }
+    }
+
+    STR_END;
+    fprintf(outputStream, "%s\n", line);
+    if (!canWriteOutput()) return 0;
+  }
+
+  free(reports);
+  return 1;
+}
+
+static int
+isReportIdentifier (HidReportIdentifier *identifier, const char *string, unsigned char minimum) {
+  if (strlen(string) != 2) return 0;
+
+  char *end;
+  unsigned long int value = strtoul(string, &end, 0X10);
+  if (*end) return 0;
+
+  if (value > UINT8_MAX) return 0;
+  *identifier = value;
+  return 1;
+}
+
+static int
+isReportDefined (
+  HidDevice *device, const char *what, HidReportIdentifier identifier,
+  HidReportSize *reportSize, size_t *size
+) {
+  if (getReportSize(device, identifier, reportSize)) {
+    if (*size) {
+      return 1;
+    }
+  }
+
+  logMessage(LOG_ERR, "%s report not defined: %02X", what, identifier);
+  return 0;
+}
+
+static int
+verifyRead (
+  HidDevice *device, const char *what, HidReportIdentifier identifier,
+  HidReportSize *reportSize, size_t *size
+) {
+  int isDefined = isReportDefined(
+    device, what, identifier,
+    reportSize, size
+  );
+
+  return isDefined;
+}
+
+static HidReportIdentifier readReportIdentifier;
+
+static int
+parseReadReport (void) {
+  const char *operand = opt_readReport;
+  if (!*operand) return 1;
+  if (isReportIdentifier(&readReportIdentifier, operand, 0)) return 1;
+
+  logMessage(LOG_ERR, "invalid input report identifier: %s", operand);
+  return 0;
+}
+
+static int
+performReadReport (HidDevice *device) {
+  HidReportIdentifier identifier = readReportIdentifier;
+
+  HidReportSize reportSize;
+  size_t *size = &reportSize.input;
+
+  int verified = verifyRead(
+    device, "input", identifier,
+    &reportSize, size
+  );
+
+  if (verified) {
+    size_t length = *size;
+    unsigned char report[length];
+
+    report[0] = identifier;
+    ssize_t result = hidGetReport(device, report, length);
+
+    if (result == -1) {
+      logSystemError("hidGetReport");
+    } else {
+      writeBytesLine("Input Report: %02X", report, result, identifier);
+      if (result == length) return 1;
+      logUnexpectedLength("report read", identifier, length, result);
+    }
+  }
+
+  return 0;
+}
+
+static HidReportIdentifier readFeatureIdentifier;
+
+static int
+parseReadFeature (void) {
+  const char *operand = opt_readFeature;
+  if (!*operand) return 1;
+  if (isReportIdentifier(&readFeatureIdentifier, operand, 1)) return 1;
+
+  logMessage(LOG_ERR, "invalid feature report identifier: %s", operand);
+  return 0;
+}
+
+static int
+performReadFeature (HidDevice *device) {
+  HidReportIdentifier identifier = readFeatureIdentifier;
+
+  HidReportSize reportSize;
+  size_t *size = &reportSize.feature;
+
+  int verified = verifyRead(
+    device, "feature", identifier,
+    &reportSize, size
+  );
+
+  if (verified) {
+    size_t length = *size;
+    unsigned char feature[length];
+
+    feature[0] = identifier;
+    ssize_t result = hidGetFeature(device, feature, length);
+
+    if (result == -1) {
+      logSystemError("hidGetFeature");
+    } else {
+      writeBytesLine("Feature Report: %02X", feature, result, identifier);
+      if (result == length) return 1;
+      logUnexpectedLength("feature read", identifier, length, result);
+    }
+  }
+
+  return 0;
+}
+
+static int
+isHexadecimal (unsigned char *digit, char character) {
+  const char string[] = {character, 0};
+  char *end;
+  long int value = strtol(string, &end, 0X10);
+
+  if (*end) {
+    logMessage(LOG_ERR, "invalid hexadecimal digit: %c", character);
+    return 0;
+  }
+
+  *digit = value;
+  return 1;
+}
+
+static int
+parseBytes (
+  const char *bytes, const char *what, unsigned char *buffer,
+  size_t bufferSize, size_t *bufferUsed
+) {
+  unsigned char *out = buffer;
+  const unsigned char *end = out + bufferSize;
+
+  if (*bytes) {
+    const char *in = bytes;
+    unsigned char byte = 0;
+    unsigned int count = 1;
+
+    enum {NEXT, HEX, DOTS, COUNT};
+    unsigned int state = NEXT;
+
+    while (*in) {
+      char character = *in++;
+
+      switch (state) {
+        case NEXT: {
+          if (iswspace(character)) continue;
+
+          if (character == '[') {
+            state = DOTS;
+            continue;
+          }
+
+          unsigned char digit;
+          if (!isHexadecimal(&digit, character)) return 0;
+
+          byte = digit << 4;
+          state = HEX;
+          continue;
+        }
+
+        case HEX: {
+          unsigned char digit;
+          if (!isHexadecimal(&digit, character)) return 0;
+
+          byte |= digit;
+          state = NEXT;
+          break;
+        }
+
+        case DOTS: {
+          if (character == ']') {
+            state = NEXT;
+            break;
+          }
+
+          if ((character < '1') || (character > '8')) {
+            logMessage(LOG_ERR, "invalid dot number: %c", character);
+            return 0;
+          }
+          unsigned char bit = 1 << (character - '1');
+
+          if (byte & bit) {
+            logMessage(LOG_ERR, "duplicate dot number: %c", character);
+            return 0;
+          }
+
+          byte |= bit;
+          continue;
+        }
+
+        case COUNT: {
+          if (iswspace(character)) break;
+          int digit = character - '0';
+
+          if ((digit < 0) || (digit > 9)) {
+            logMessage(LOG_ERR, "invalid count digit: %c", character);
+            return 0;
+          }
+
+          if (!digit) {
+            if (!count) {
+              logMessage(LOG_ERR, "first digit of count can't be 0");
+              return 0;
+            }
+          }
+
+          count *= 10;
+          count += digit;
+
+          if (!*in) break;
+          continue;
+        }
+
+        default:
+          logMessage(LOG_ERR, "unexpected bytes parser state: %u", state);
+          return 0;
+      }
+
+      if (state == COUNT) {
+        if (!count) {
+          logMessage(LOG_ERR, "missing count");
+          return 0;
+        }
+
+        state = NEXT;
+      } else if (*in == '*') {
+        in += 1;
+        state = COUNT;
+        count = 0;
+        continue;
+      }
+
+      while (count--) {
+        if (out == end) {
+          logMessage(LOG_ERR, "%s buffer too small", what);
+          return 0;
+        }
+
+        *out++ = byte;
+      }
+
+      byte = 0;
+      count = 1;
+    }
+
+    if (state != NEXT) {
+      logMessage(LOG_ERR, "incomplete %s specification", what);
+      return 0;
+    }
+  }
+
+  *bufferUsed = out - buffer;
+  return 1;
+}
+
+static int
+verifyWrite (
+  HidDevice *device, const char *what, unsigned char *identifier,
+  HidReportSize *reportSize, size_t *expectedSize,
+  const unsigned char *buffer, size_t actualSize
+) {
+  *identifier = buffer[0];
+
+  int isDefined = isReportDefined(
+    device, what, *identifier,
+    reportSize, expectedSize
+  );
+
+  if (!isDefined) return 0;
+  if (!*identifier) *expectedSize += 1;
+
+  if (actualSize != *expectedSize) {
+    logMessage(LOG_ERR,
+      "incorrect %s report size: %02X:"
+      " Expected:%"PRIsize " Actual:%"PRIsize,
+      what, *identifier, *expectedSize, actualSize
+    );
+
+    return 0;
+  }
+
+  return 1;
+}
+
+static unsigned char writeReportBuffer[0X1000];
+static size_t writeReportLength;
+
+static int
+parseWriteReport (void) {
+  return parseBytes(
+    opt_writeReport, "output report", writeReportBuffer,
+    ARRAY_COUNT(writeReportBuffer), &writeReportLength
+  );
+}
+
+static int
+performWriteReport (HidDevice *device) {
+  const unsigned char *report = writeReportBuffer;
+  size_t length = writeReportLength;
+
+  unsigned char identifier;
+  HidReportSize reportSize;
+
+  int verified = verifyWrite(
+    device, "output", &identifier,
+    &reportSize, &reportSize.output,
+    report, length
+  );
+
+  if (verified) {
+    writeBytesLine("Writing Report: %02X", report, length, identifier);
+    ssize_t result = hidSetReport(device, report, length);
+
+    if (result == -1) {
+      logSystemError("hidSetReport");
+    } else if (result == length) {
+      return 1;
+    } else {
+      logUnexpectedLength("report write", identifier, length, result);
+    }
+  }
+
+  return 0;
+}
+
+static unsigned char writeFeatureBuffer[0X1000];
+static size_t writeFeatureLength;
+
+static int
+parseWriteFeature (void) {
+  return parseBytes(
+    opt_writeFeature, "feature report", writeFeatureBuffer,
+    ARRAY_COUNT(writeFeatureBuffer), &writeFeatureLength
+  );
+}
+
+static int
+performWriteFeature (HidDevice *device) {
+  const unsigned char *feature = writeFeatureBuffer;
+  size_t length = writeFeatureLength;
+
+  unsigned char identifier;
+  HidReportSize reportSize;
+
+  int verified = verifyWrite(
+    device, "feature", &identifier,
+    &reportSize, &reportSize.feature,
+    feature, length
+  );
+
+  if (verified) {
+    writeBytesLine("Writing Feature: %02X", feature, length, identifier);
+    ssize_t result = hidSetFeature(device, feature, length);
+
+    if (result == -1) {
+      logSystemError("hidSetFeature");
+    } else if (result == length) {
+      return 1;
+    } else {
+      logUnexpectedLength("feature write", identifier, length, result);
+    }
+  }
+
+  return 0;
+}
+
+static int inputTimeout;
+
+static int
+parseInputTimeout (void) {
+  inputTimeout = 10;
+
+  static const int minimum = 1;
+  static const int maximum = 99;
+
+  if (!validateInteger(&inputTimeout, opt_inputTimeout, &minimum, &maximum)) {
+    logMessage(LOG_ERR, "invalid input timeout: %s", opt_inputTimeout);
+    return 0;
+  }
+
+  inputTimeout *= 1000;
+  return 1;
+}
+
+static int
+performEchoInput (HidDevice *device) {
+  HidReportSize reportSize;
+  const size_t *inputSize = &reportSize.input;
+
+  unsigned char reportIdentifier = 0;
+  int hasReportIdentifiers = !getReportSize(device, reportIdentifier, &reportSize);
+
+  unsigned char buffer[0X1000];
+  size_t bufferSize = sizeof(buffer);
+
+  unsigned char *from = buffer;
+  unsigned char *to = from;
+  const unsigned char *end = from + bufferSize;
+
+  while (hidAwaitInput(device, inputTimeout)) {
+    ssize_t result = hidReadData(device, to, (end - to), 1000, 100);
+
+    if (result == -1) {
+      logMessage(LOG_ERR, "input error: %s", strerror(errno));
+      return 0;
+    }
+
+    to += result;
+
+    while (from < to) {
+      if (hasReportIdentifiers) {
+        reportIdentifier = *from;
+
+        if (!getReportSize(device, reportIdentifier, &reportSize)) {
+          logMessage(LOG_ERR, "input report not defined: %02X", reportIdentifier);
+          return 0;
+        }
+      }
+
+      if (!*inputSize) {
+        logMessage(LOG_ERR, "input report size is zero: %02X", reportIdentifier);
+        return 0;
+      }
+
+      size_t count = to - from;
+
+      if (*inputSize > count) {
+        if (from == buffer) {
+          logMessage(LOG_ERR,
+            "input report too large: %02X: %"PRIsize " > %"PRIsize,
+            reportIdentifier, *inputSize, count
+          );
+
+          return 0;
+        }
+
+        memmove(buffer, from, count);
+        from = buffer;
+        to = from + count;
+
+        break;
+      }
+
+      if (!writeBytesLine("Input Report", from, *inputSize)) return 0;
+      from += *inputSize;
+    }
+  }
+
+  return 1;
+}
+
+static int
+parseOperands (void) {
+  typedef struct {
+    int (*parse) (void);
+  } OperandEntry;
+
+  static const OperandEntry operandTable[] = {
+    { .parse = parseReadReport,
+    },
+
+    { .parse = parseReadFeature,
+    },
+
+    { .parse = parseWriteReport,
+    },
+
+    { .parse = parseWriteFeature,
+    },
+
+    { .parse = parseInputTimeout,
+    },
+  };
+
+  const OperandEntry *operand = operandTable;
+  const OperandEntry *end = operand + ARRAY_COUNT(operandTable);
+
+  while (operand < end) {
+    if (!operand->parse()) return 0;
+    operand += 1;
+  }
+
+  return 1;
+}
+
+static int
+performActions (HidDevice *device) {
+  typedef struct {
+    int (*perform) (HidDevice *device);
+    unsigned char isFlag:1;
+
+    union {
+      char **string;
+      int *flag;
+    } option;
+  } ActionEntry;
+
+  static const ActionEntry actionTable[] = {
+    { .perform = performShowDeviceIdentifiers,
+      .isFlag = 1,
+      .option.flag = &opt_showDeviceIdentifiers,
+    },
+
+    { .perform = performShowDeviceAddress,
+      .isFlag = 1,
+      .option.flag = &opt_showDeviceAddress,
+    },
+
+    { .perform = performShowDeviceName,
+      .isFlag = 1,
+      .option.flag = &opt_showDeviceName,
+    },
+
+    { .perform = performShowHostPath,
+      .isFlag = 1,
+      .option.flag = &opt_showHostPath,
+    },
+
+    { .perform = performShowHostDevice,
+      .isFlag = 1,
+      .option.flag = &opt_showHostDevice,
+    },
+
+    { .perform = performListItems,
+      .isFlag = 1,
+      .option.flag = &opt_listItems,
+    },
+
+    { .perform = performListReports,
+      .isFlag = 1,
+      .option.flag = &opt_listReports,
+    },
+
+    { .perform = performReadReport,
+      .option.string = &opt_readReport,
+    },
+
+    { .perform = performReadFeature,
+      .option.string = &opt_readFeature,
+    },
+
+    { .perform = performWriteReport,
+      .option.string = &opt_writeReport,
+    },
+
+    { .perform = performWriteFeature,
+      .option.string = &opt_writeFeature,
+    },
+
+    { .perform = performEchoInput,
+      .isFlag = 1,
+      .option.flag = &opt_echoInput,
+    },
+  };
+
+  const ActionEntry *action = actionTable;
+  const ActionEntry *end = action + ARRAY_COUNT(actionTable);
+
+  while (action < end) {
+    int perform = 0;
+
+    if (action->isFlag) {
+      if (*action->option.flag) perform = 1;
+    } else {
+      if (**action->option.string) perform = 1;
+    }
+
+    if (perform) {
+      if (!action->perform(device)) return 0;
+      if (!canWriteOutput()) return 0;
+    }
+
+    action += 1;
+  }
+
+  return 1;
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-hid",
+
+      .usage = {
+        .purpose = strtext("Find HID devices, list report descriptors, read/write reports/features, or monitor input from a HID device."),
+        .notes = USAGE_NOTES(usageNotes),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  outputStream = stdout;
+  outputError = 0;
+
+  if (argc) {
+    logMessage(LOG_ERR, "too many parameters");
+    return PROG_EXIT_SYNTAX;
+  }
+
+  if (!parseOperands()) return PROG_EXIT_SYNTAX;
+  ProgramExitStatus exitStatus = PROG_EXIT_SUCCESS;
+  HidDevice *device = NULL;
+
+  if (!openDevice(&device)) {
+    exitStatus = PROG_EXIT_SYNTAX;
+  } else if (!device) {
+    logMessage(LOG_ERR, "device not found");
+    exitStatus = PROG_EXIT_SEMANTIC;
+  } else {
+    if (!performActions(device)) exitStatus = PROG_EXIT_FATAL;
+    hidCloseDevice(device);
+  }
+
+  if (outputError) {
+    logMessage(LOG_ERR, "output error: %s", strerror(outputError));
+    exitStatus = PROG_EXIT_FATAL;
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-ktb.c b/Programs/brltty-ktb.c
new file mode 100644
index 0000000..7450628
--- /dev/null
+++ b/Programs/brltty-ktb.c
@@ -0,0 +1,403 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "file.h"
+#include "parse.h"
+#include "dynld.h"
+#include "ktb.h"
+#include "ktb_keyboard.h"
+#include "brl.h"
+
+static char *opt_brailleDriver;
+static int opt_audit;
+static int opt_listKeyNames;
+static int opt_listHelpScreen;
+static int opt_listRestructuredText;
+static char *opt_tablesDirectory;
+char *opt_driversDirectory;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "braille-driver",
+    .letter = 'b',
+    .argument = strtext("driver"),
+    .setting.string = &opt_brailleDriver,
+    .description = strtext("Braille driver code."),
+  },
+
+  { .word = "audit",
+    .letter = 'a',
+    .setting.flag = &opt_audit,
+    .description = strtext("Report problems with the key table.")
+  },
+
+  { .word = "keys",
+    .letter = 'k',
+    .setting.flag = &opt_listKeyNames,
+    .description = strtext("List key names.")
+  },
+
+  { .word = "list",
+    .letter = 'l',
+    .setting.flag = &opt_listHelpScreen,
+    .description = strtext("List key table in help screen format.")
+  },
+
+  { .word = "reStructuredText",
+    .letter = 'r',
+    .setting.flag = &opt_listRestructuredText,
+    .description = strtext("List key table in reStructuredText format.")
+  },
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing tables.")
+  },
+
+  { .word = "drivers-directory",
+    .letter = 'D',
+    .argument = strtext("directory"),
+    .setting.string = &opt_driversDirectory,
+    .internal.setting = DRIVERS_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory for loading drivers.")
+  },
+END_OPTION_TABLE(programOptions)
+
+static void *driverObject;
+
+typedef struct {
+  KEY_NAME_TABLES_REFERENCE names;
+  char *path;
+} KeyTableDescriptor;
+
+static int
+getKeyTableDescriptor (KeyTableDescriptor *ktd, const char *tableName) {
+  int ok = 0;
+
+  memset(ktd, 0, sizeof(*ktd));
+  ktd->names = NULL;
+  ktd->path = NULL;
+
+  if (*opt_brailleDriver) {
+    if (loadBrailleDriver(opt_brailleDriver, &driverObject, opt_driversDirectory)) {
+      char *keyTablesSymbol;
+
+      {
+        const char *strings[] = {"brl_ktb_", opt_brailleDriver};
+        keyTablesSymbol = joinStrings(strings, ARRAY_COUNT(strings));
+      }
+
+      if (keyTablesSymbol) {
+        const KeyTableDefinition *const *keyTableDefinitions;
+
+        if (findSharedSymbol(driverObject, keyTablesSymbol, &keyTableDefinitions)) {
+          const KeyTableDefinition *const *currentDefinition = keyTableDefinitions;
+
+          while (*currentDefinition) {
+            if (strcmp(tableName, (*currentDefinition)->bindings) == 0) {
+              ktd->names = (*currentDefinition)->names;
+              if ((ktd->path = makeInputTablePath(opt_tablesDirectory, opt_brailleDriver, tableName))) ok = 1;
+              break;
+            }
+
+            currentDefinition += 1;
+          }
+
+          if (!ktd->names) {
+            logMessage(LOG_ERR,
+              "unknown braille device model: %s-%s",
+              opt_brailleDriver, tableName
+            );
+          }
+        }
+
+        free(keyTablesSymbol);
+      } else {
+        logMallocError();
+      }
+    }
+  } else {
+    ktd->names = KEY_NAME_TABLES(keyboard);
+    if ((ktd->path = makeKeyboardTablePath(opt_tablesDirectory, tableName))) ok = 1;
+  }
+
+  if (!ok) {
+    if (ktd->path) free(ktd->path);
+    memset(ktd, 0, sizeof(*ktd));
+  }
+
+  return ok;
+}
+
+static int
+writeLine (const wchar_t *line) {
+  FILE *stream = stdout;
+
+  fprintf(stream, "%" PRIws "\n", line);
+  return !ferror(stream);
+}
+
+static int
+hlpWriteLine (const wchar_t *line, void *data) {
+  return writeLine(line);
+}
+
+typedef struct {
+  unsigned int headerLevel;
+  unsigned int elementLevel;
+  wchar_t elementBullet;
+  unsigned blankLine:1;
+} RestructuredTextData;
+
+static int
+rstAddLine (const wchar_t *line, RestructuredTextData *rst) {
+  if (*line) {
+    rst->blankLine = 0;
+  } else if (rst->blankLine) {
+    return 1;
+  } else {
+    rst->blankLine = 1;
+  }
+
+  return writeLine(line);
+}
+
+static int
+rstAddBlankLine (RestructuredTextData *rst) {
+  return rstAddLine(WS_C(""), rst);
+}
+
+static int
+rstWriteLine (const wchar_t *line, void *data) {
+  RestructuredTextData *rst = data;
+
+  const unsigned int indent = 2;
+  const unsigned int count = indent * rst->elementLevel;
+  size_t length = wcslen(line);
+  wchar_t buffer[count + length + 1];
+
+  if (rst->elementLevel > 0) {
+    wmemset(buffer, WC_C(' '), count);
+    buffer[count - indent] = rst->elementBullet;
+    rst->elementBullet = WC_C(' ');
+
+    wmemcpy(&buffer[count], line, (length + 1));
+    line = buffer;
+  }
+
+  return rstAddLine(line, rst);
+}
+
+static int
+rstWriteHeader (const wchar_t *text, unsigned int level, void *data) {
+  RestructuredTextData *rst = data;
+
+  if (level > (rst->headerLevel + 1)) {
+    level = rst->headerLevel + 1;
+  } else {
+    rst->headerLevel = level;
+  }
+
+  static const wchar_t characters[] = {
+    WC_C('='), WC_C('-'), WC_C('~')
+  };
+
+  size_t length = wcslen(text);
+  wchar_t underline[length + 1];
+
+  wmemset(underline, characters[level], length);
+  underline[length] = 0;
+
+  if (!rstAddLine(text, rst)) return 0;
+  if (!rstAddLine(underline, rst)) return 0;
+  if (!rstAddBlankLine(rst)) return 0;
+
+  if (level == 0) {
+    if (!rstAddLine(WS_C(".. contents::"), rst)) return 0;
+    if (!rstAddBlankLine(rst)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+rstBeginElement (unsigned int level, void *data) {
+  RestructuredTextData *rst = data;
+
+  static const wchar_t bullets[] = {
+    WC_C('*'), WC_C('+'), WC_C('o')
+  };
+
+  rst->elementLevel = level;
+  rst->elementBullet = bullets[level - 1];
+
+  if (!rstAddBlankLine(rst)) return 0;
+  return 1;
+}
+
+static int
+rstEndList (void *data) {
+  RestructuredTextData *rst = data;
+
+  rst->elementLevel = 0;
+  if (!rstAddBlankLine(rst)) return 0;
+  return 1;
+}
+
+static const KeyTableListMethods rstMethods = {
+  .writeHeader = rstWriteHeader,
+  .beginElement = rstBeginElement,
+  .endList = rstEndList
+};
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_SUCCESS;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-ktb",
+
+      .usage = {
+        .purpose = strtext("check a key table, list the key naems it can use, or write the key bindings it defines in useful formats."),
+        .parameters = "table-name",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  driverObject = NULL;
+
+  if (argc) {
+    const char *tableName = (argc--, *argv++);
+    KeyTableDescriptor ktd;
+    int gotKeyTableDescriptor;
+
+    {
+      const char *file = locatePathName(tableName);
+      const char *delimiter = strrchr(file, '.');
+      size_t length = delimiter? (delimiter - file): strlen(file);
+      char name[length + 1];
+
+      memcpy(name, file, length);
+      name[length] = 0;
+
+      gotKeyTableDescriptor = getKeyTableDescriptor(&ktd, name);
+    }
+
+    if (gotKeyTableDescriptor) {
+      if (opt_listKeyNames) {
+        if (!listKeyNames(ktd.names, hlpWriteLine, NULL)) {
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      }
+
+      if (exitStatus == PROG_EXIT_SUCCESS) {
+        KeyTable *keyTable = compileKeyTable(ktd.path, ktd.names);
+
+        if (keyTable) {
+          if (opt_audit) {
+            if (!auditKeyTable(keyTable, ktd.path)) {
+              exitStatus = PROG_EXIT_FATAL;
+            }
+          }
+
+          if (opt_listHelpScreen) {
+            if (!listKeyTable(keyTable, NULL, hlpWriteLine, NULL)) {
+              exitStatus = PROG_EXIT_FATAL;
+            }
+          }
+
+          if (opt_listRestructuredText) {
+            RestructuredTextData rst = {
+              .headerLevel = 0,
+              .elementLevel = 0,
+              .elementBullet = WC_C(' '),
+              .blankLine = 0
+            };
+
+            if (!listKeyTable(keyTable, &rstMethods, rstWriteLine, &rst)) {
+              exitStatus = PROG_EXIT_FATAL;
+            }
+          }
+
+          destroyKeyTable(keyTable);
+        } else {
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      }
+
+      free(ktd.path);
+    } else {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  } else {
+    logMessage(LOG_ERR, "missing key table name");
+    exitStatus = PROG_EXIT_SYNTAX;
+  }
+
+  if (driverObject) unloadSharedObject(driverObject);
+  return exitStatus;
+}
+
+#include "scr.h"
+
+KeyTableCommandContext
+getScreenCommandContext (void) {
+  return KTB_CTX_DEFAULT;
+}
+
+int
+currentVirtualTerminal (void) {
+  return 0;
+}
+
+#include "alert.h"
+
+void
+alert (AlertIdentifier identifier) {
+}
+
+void
+speakAlertText (const wchar_t *text) {
+}
+
+#include "api_control.h"
+
+const ApiMethods api;
+
+#include "message.h"
+
+int
+message (const char *mode, const char *text, MessageOptions options) {
+  return 1;
+}
diff --git a/Programs/brltty-lscmds.c b/Programs/brltty-lscmds.c
new file mode 100644
index 0000000..b1a3723
--- /dev/null
+++ b/Programs/brltty-lscmds.c
@@ -0,0 +1,267 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "ktb_cmds.h"
+#include "cmd.h"
+
+BEGIN_OPTION_TABLE(programOptions)
+END_OPTION_TABLE(programOptions)
+
+static void
+writeCharacter (char character) {
+  putc(character, stdout);
+}
+
+static void
+writeCharacters (char character, size_t count) {
+  while (count > 0) {
+    writeCharacter(character);
+    count -= 1;
+  }
+}
+
+static void
+endLine (void) {
+  writeCharacter('\n');
+}
+
+static void
+writeString (const char *string) {
+  while (*string) writeCharacter(*string++);
+}
+
+static const char headerCharacters[] = {'~', '=', '-'};
+static unsigned char headerLevel = 0;
+
+static void
+incrementHeaderLevel (void) {
+  headerLevel += 1;
+}
+
+static void
+decrementHeaderLevel (void) {
+  headerLevel -= 1;
+}
+
+static void
+writeHeader (const char *header) {
+  char headerCharacter = headerCharacters[headerLevel];
+  size_t headerLength = strlen(header);
+
+  if (headerLevel == 0) {
+    writeCharacters(headerCharacter, headerLength);
+    endLine();
+  }
+
+  writeString(header);
+  endLine();
+
+  writeCharacters(headerCharacter, headerLength);
+  endLine();
+  endLine();
+}
+
+void
+commandGroupHook_hotkeys (CommandGroupHookData *data) {
+}
+
+void
+commandGroupHook_keyboardFunctions (CommandGroupHookData *data) {
+}
+
+static void
+listModifiers (int include, const char *type, int *started, const CommandModifierEntry *modifiers) {
+  if (include) {
+    if (!*started) {
+      *started = 1;
+      printf("The following modifiers may be specified:\n\n");
+    }
+
+    printf("* %s", type);
+
+    if (modifiers) {
+      const CommandModifierEntry *modifier = modifiers;
+      char punctuation = ':';
+
+      while (modifier->name) {
+        printf("%c %s", punctuation, modifier->name);
+        punctuation = ',';
+        modifier += 1;
+      }
+    }
+
+    endLine();
+  }
+}
+
+static void
+putCommand (const CommandEntry *command) {
+  {
+    const char *description = command->description;
+
+    printf(".. _%s:\n\n", command->name);
+    printf("**%s** - ", command->name);
+
+    writeCharacter(toupper(*description++));
+    printf("%s.\n\n", description);
+  }
+
+  {
+    int started = 0;
+
+    listModifiers(command->isOffset, "an offset", &started, NULL);
+    listModifiers(command->isColumn, "a column number", &started, NULL);
+    listModifiers(command->isCharacter, "a single character", &started, NULL);
+
+    listModifiers(
+      command->isToggle, "Toggle", &started,
+      commandModifierTable_toggle
+    );
+
+    listModifiers(
+      command->isMotion, "Motion", &started,
+      commandModifierTable_motion
+    );
+
+    listModifiers(
+      command->isRow, "Row", &started,
+      commandModifierTable_row
+    );
+
+    listModifiers(
+      command->isVertical, "Vertical", &started,
+      commandModifierTable_vertical
+    );
+
+    listModifiers(
+      command->isInput, "Input", &started,
+      commandModifierTable_input
+    );
+
+    listModifiers(
+      (command->isCharacter || command->isBraille), "Character", &started,
+      commandModifierTable_character
+    );
+
+    listModifiers(
+      command->isBraille, "Braille", &started,
+      commandModifierTable_braille
+    );
+
+    listModifiers(
+      command->isKeyboard, "Keyboard", &started,
+      commandModifierTable_keyboard
+    );
+
+    if (started) endLine();
+  }
+}
+
+static void
+putGroup (const CommandGroupEntry *group) {
+  incrementHeaderLevel();
+  writeHeader(group->name);
+
+  size_t count = group->commands.count;
+  const CommandEntry *commands[count];
+
+  for (unsigned int index=0; index<count; index+=1) {
+    commands[index] = findCommandEntry(group->commands.table[index].code);
+  }
+
+  for (unsigned int index=0; index<count; index+=1) {
+    printf("* `%s`_\n", commands[index]->name);
+  }
+  printf("\n");
+
+  for (unsigned int index=0; index<count; index+=1) {
+    putCommand(commands[index]);
+  }
+
+  decrementHeaderLevel();
+}
+
+static void
+putGroups (void) {
+  const CommandGroupEntry *group = commandGroupTable;
+  const CommandGroupEntry *end = group + commandGroupCount;
+
+  while (group < end) {
+    putGroup(group);
+    group += 1;
+  }
+}
+
+static int
+compareCommands (const void *element1, const void *element2) {
+  const CommandEntry *const *command1 = element1;
+  const CommandEntry *const *command2 = element2;
+  return strcmp((*command1)->name, (*command2)->name);
+}
+
+static void
+putCommandIndex (void) {
+  incrementHeaderLevel();
+  writeHeader("Alphabetical Command Index");
+
+  int count = getCommandCount();
+  const CommandEntry *commands[count];
+
+  for (int index=0; index<count; index+=1) {
+    commands[index] = &commandTable[index];
+  }
+  qsort(commands, count, sizeof(commands[0]), compareCommands);
+
+  for (int index=0; index<count; index+=1) {
+    printf("* `%s`_\n", commands[index]->name);
+  }
+
+  printf("\n");
+  decrementHeaderLevel();
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-lscmds",
+
+      .usage = {
+        .purpose = strtext("Write a brltty command reference in reStructuredText."),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  writeHeader("The BRLTTY Command Reference");
+  writeString(".. contents::\n\n");
+
+  putCommandIndex();
+  putGroups();
+  return PROG_EXIT_SUCCESS;
+}
diff --git a/Programs/brltty-lsinc.c b/Programs/brltty-lsinc.c
new file mode 100644
index 0000000..2cf0d6c
--- /dev/null
+++ b/Programs/brltty-lsinc.c
@@ -0,0 +1,127 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <search.h>
+
+#include "log.h"
+#include "program.h"
+#include "cmdline.h"
+#include "file.h"
+
+BEGIN_OPTION_TABLE(programOptions)
+END_OPTION_TABLE(programOptions)
+
+static void
+noMemory (void) {
+  fprintf(stderr, "%s: insufficient memory\n", programName);
+  exit(PROG_EXIT_FATAL);
+}
+
+static int
+compareStrings (const void *string1, const void *string2) {
+  return strcmp(string1, string2);
+}
+
+static void
+logFileName (const char *name, void *data) {
+  static void *namesTree = NULL;
+
+  if (!tfind(name, &namesTree, compareStrings)) {
+    name = strdup(name);
+    if (!name) noMemory();
+    if (!tsearch(name, &namesTree, compareStrings)) noMemory();
+    printf("%s\n", name);
+  }
+}
+
+static DATA_CONDITION_TESTER(testConditionOperand) {
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processUnknownDirective) {
+  DataOperand directive;
+
+  if (getDataOperand(file, &directive, NULL)) {
+    if (directive.length >= 2) {
+      if (isKeyword(WS_C("if"), directive.characters, 2)) {
+        return processConditionOperands(file, testConditionOperand, 0, NULL, data);
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    DATA_VARIABLE_DIRECTIVES,
+    {NULL, processUnknownDirective},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "attributes table directive", data);
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-lsinc",
+
+      .usage = {
+        .purpose = strtext("List the paths to a data file and those which it recursively includes."),
+        .parameters = "file ...",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (argc == 0) {
+    logMessage(LOG_ERR, "missing file");
+    exitStatus = PROG_EXIT_SYNTAX;
+  } else {
+    exitStatus = PROG_EXIT_SUCCESS;
+
+    do {
+      const char *path = *argv++;
+      argc -= 1;
+
+      const DataFileParameters parameters = {
+        .processOperands = processOperands,
+        .logFileName = logFileName
+      };
+
+      if (testProgramPath(path)) {
+        logFileName(path, parameters.data);
+      } else if (!processDataFile(path, &parameters)) {
+        exitStatus = PROG_EXIT_SEMANTIC;
+      }
+    } while (argc);
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-morse.c b/Programs/brltty-morse.c
new file mode 100644
index 0000000..621d096
--- /dev/null
+++ b/Programs/brltty-morse.c
@@ -0,0 +1,251 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "log.h"
+#include "cmdline.h"
+#include "prefs.h"
+#include "parse.h"
+#include "tune_utils.h"
+#include "notes.h"
+#include "datafile.h"
+#include "morse.h"
+
+static int opt_fromFiles;
+static char *opt_morsePitch;
+static char *opt_morseSpeed;
+static int opt_morseGroups;
+static char *opt_outputVolume;
+static char *opt_tuneDevice;
+
+#ifdef HAVE_MIDI_SUPPORT
+static char *opt_midiInstrument;
+#endif /* HAVE_MIDI_SUPPORT */
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "files",
+    .letter = 'f',
+    .setting.flag = &opt_fromFiles,
+    .description = "Use files rather than command line arguments."
+  },
+
+  { .word = "tone",
+    .letter = 't',
+    .argument = "frequency",
+    .setting.string = &opt_morsePitch,
+    .description = "The pitch of the tone."
+  },
+
+  { .word = "speed",
+    .letter = 's',
+    .argument = "wordsPerMinute",
+    .setting.string = &opt_morseSpeed,
+    .description = "Morse speed (words per minute)."
+  },
+
+  { .word = "groups",
+    .letter = 'g',
+    .setting.flag = &opt_morseGroups,
+    .description = "Speed is in groups (rather than words) per minute."
+  },
+
+  { .word = "volume",
+    .letter = 'v',
+    .argument = "loudness",
+    .setting.string = &opt_outputVolume,
+    .description = "Output volume (percentage)."
+  },
+
+  { .word = "device",
+    .letter = 'd',
+    .argument = "device",
+    .setting.string = &opt_tuneDevice,
+    .description = "Name of tune device."
+  },
+
+#ifdef HAVE_PCM_SUPPORT
+  { .word = "pcm-device",
+    .letter = 'p',
+    .argument = "device",
+    .setting.string = &opt_pcmDevice,
+    .description = "Device specifier for soundcard digital audio."
+  },
+#endif /* HAVE_PCM_SUPPORT */
+
+#ifdef HAVE_MIDI_SUPPORT
+  { .word = "midi-device",
+    .letter = 'm',
+    .argument = "device",
+    .setting.string = &opt_midiDevice,
+    .description = "Device specifier for the Musical Instrument Digital Interface."
+  },
+
+  { .word = "instrument",
+    .letter = 'i',
+    .argument = "instrument",
+    .setting.string = &opt_midiInstrument,
+    .description = "Name of MIDI instrument."
+  },
+#endif /* HAVE_MIDI_SUPPORT */
+END_OPTION_TABLE(programOptions)
+
+static
+DATA_OPERANDS_PROCESSOR(processMorseLine) {
+  MorseObject *morse = data;
+
+  DataOperand text;
+  getTextRemaining(file, &text);
+
+  if (!addMorseCharacters(morse, text.characters, text.length)) return 0;
+  if (!addMorseSpace(morse)) return 0;
+  return 1;
+}
+
+static void
+exitMorseObject (void *data) {
+  MorseObject *morse = data;
+  destroyMorseObject(morse);
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-morse",
+
+      .usage = {
+        .purpose = strtext("Translate text into Morse Code tones."),
+        .parameters = "text ... | -f [{file | -} ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  resetPreferences();
+  if (!parseTuneDevice(opt_tuneDevice)) return PROG_EXIT_SYNTAX;
+  if (!parseTuneVolume(opt_outputVolume)) return PROG_EXIT_SYNTAX;
+
+#ifdef HAVE_MIDI_SUPPORT
+  if (!parseTuneInstrument(opt_midiInstrument)) return PROG_EXIT_SYNTAX;
+#endif /* HAVE_MIDI_SUPPORT */
+
+  MorseObject *morse = newMorseObject();
+  if (!morse) return PROG_EXIT_FATAL;
+  onProgramExit("morse-object", exitMorseObject, morse);
+
+  {
+    int ok = 0;
+
+    {
+      int pitch = getMorsePitch(morse);
+      static int minimum = 1;
+      static int maximum = 0XFFFF;
+
+      if (validateInteger(&pitch, opt_morsePitch, &minimum, &maximum)) {
+        if (setMorsePitch(morse, pitch)) {
+          ok = 1;
+        }
+      }
+    }
+
+    if (!ok) {
+      logMessage(LOG_WARNING, "unsupported Morse pitch: %s (Hz)", opt_morsePitch);
+      return PROG_EXIT_SYNTAX;
+    }
+  }
+
+  {
+    int ok = 0;
+
+    int speed;
+    const char *unit;
+
+    if (opt_morseGroups) {
+      speed = getMorseGroupsPerMinute(morse);
+      unit = "groups";
+    } else {
+      speed = getMorseWordsPerMinute(morse);
+      unit = "words";
+    }
+
+    {
+      static int minimum = 1;
+      static int maximum = 100;
+
+      if (validateInteger(&speed, opt_morseSpeed, &minimum, &maximum)) {
+        if (opt_morseGroups) {
+          if (setMorseGroupsPerMinute(morse, speed)) ok = 1;
+        } else {
+          if (setMorseWordsPerMinute(morse, speed)) ok = 1;
+        }
+      }
+    }
+
+    if (!ok) {
+      logMessage(LOG_WARNING, "unsupported Morse speed: %s (%s per minute)", opt_morseSpeed, unit);
+      return PROG_EXIT_SYNTAX;
+    }
+  }
+
+  if (!setTuneDevice()) return PROG_EXIT_SEMANTIC;
+  ProgramExitStatus exitStatus = PROG_EXIT_FATAL;
+
+  if (opt_fromFiles) {
+    const InputFilesProcessingParameters parameters = {
+      .dataFileParameters = {
+        .processOperands = processMorseLine,
+        .data = &morse
+      }
+    };
+
+    exitStatus = processInputFiles(argv, argc, &parameters);
+  } else if (argc) {
+    exitStatus = PROG_EXIT_SUCCESS;
+
+    do {
+      if (!(addMorseString(morse, *argv) && addMorseSpace(morse))) {
+        exitStatus = PROG_EXIT_FATAL;
+        break;
+      }
+
+      argv += 1;
+    } while (argc -= 1);
+  } else {
+    logMessage(LOG_ERR, "missing text");
+    exitStatus = PROG_EXIT_SYNTAX;
+  }
+
+  if (exitStatus == PROG_EXIT_SUCCESS) {
+    if (!playMorseSequence(morse)) {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  }
+
+  return exitStatus;
+}
+
+#include "alert.h"
+
+void
+alert (AlertIdentifier identifier) {
+}
diff --git a/Programs/brltty-pty.c b/Programs/brltty-pty.c
new file mode 100644
index 0000000..b126ef7
--- /dev/null
+++ b/Programs/brltty-pty.c
@@ -0,0 +1,486 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* not done yet:
+ * parent: terminal type list
+ * screen: resize
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/wait.h>
+
+#include "log.h"
+#include "cmdline.h"
+#include "pty_object.h"
+#include "pty_terminal.h"
+#include "parse.h"
+#include "file.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_io.h"
+#include "async_signal.h"
+
+static int opt_driverDirectives;
+static int opt_showPath;
+static char *opt_asUser;
+static char *opt_asGroup;
+static char *opt_workingDirectory;
+static char *opt_homeDirectory;
+
+static int opt_logInput;
+static int opt_logOutput;
+static int opt_logSequences;
+static int opt_logUnexpected;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "driver-directives",
+    .letter = 'x',
+    .setting.flag = &opt_driverDirectives,
+    .description = strtext("write driver directives to standard error")
+  },
+
+  { .word = "show-path",
+    .letter = 'p',
+    .setting.flag = &opt_showPath,
+    .description = strtext("show the absolute path to the pty slave")
+  },
+
+  { .word = "user",
+    .letter = 'u',
+    .argument = "user",
+    .setting.string = &opt_asUser,
+    .description = strtext("the name or number of the user to run as")
+  },
+
+  { .word = "group",
+    .letter = 'g',
+    .argument = "group",
+    .setting.string = &opt_asGroup,
+    .description = strtext("the name or number of the group to run as")
+  },
+
+  { .word = "working-directory",
+    .letter = 'd',
+    .argument = "path",
+    .setting.string = &opt_workingDirectory,
+    .description = strtext("the directory to change to")
+  },
+
+  { .word = "home-directory",
+    .letter = 'D',
+    .argument = "path",
+    .setting.string = &opt_homeDirectory,
+    .description = strtext("the home directory to use")
+  },
+
+  { .word = "log-input",
+    .letter = 'I',
+    .setting.flag = &opt_logInput,
+    .description = strtext("log input written to the pty slave")
+  },
+
+  { .word = "log-output",
+    .letter = 'O',
+    .setting.flag = &opt_logOutput,
+    .description = strtext("log output received from the pty slave that isn't an escape sequence or a special character")
+  },
+
+  { .word = "log-sequences",
+    .letter = 'S',
+    .setting.flag = &opt_logSequences,
+    .description = strtext("log escape sequences and special characters received from the pty slave")
+  },
+
+  { .word = "log-unexpected",
+    .letter = 'U',
+    .setting.flag = &opt_logUnexpected,
+    .description = strtext("log unexpected input/output")
+  },
+END_OPTION_TABLE(programOptions)
+
+static void writeDriverDirective (const char *format, ...) PRINTF(1, 2);
+
+static void
+writeDriverDirective (const char *format, ...) {
+  if (opt_driverDirectives) {
+    va_list args;
+    va_start(args, format);
+
+    {
+      FILE *stream = stderr;
+      vfprintf(stream, format, args);
+      fputc('\n', stream);
+      fflush(stream);
+    }
+
+    va_end(args);
+  }
+}
+
+static int
+setEnvironmentString (const char *variable, const char *string) {
+  int result = setenv(variable, string, 1);
+  if (result != -1) return 1;
+
+  logSystemError("setenv");
+  return 0;
+}
+
+static int
+setEnvironmentInteger (const char *variable, int integer) {
+  char string[0X10];
+  snprintf(string, sizeof(string), "%d", integer);
+  return setEnvironmentString(variable, string);
+}
+
+static int
+setEnvironmentVariables (void) {
+  if (!setEnvironmentString("TERM_PROGRAM", programName)) return 0;
+  if (!setEnvironmentString("TERM_PROGRAM_VERSION", PACKAGE_VERSION)) return 0;
+
+  {
+    static const char *const variables[] = {
+      /* screen */ "STY", "WINDOW",
+      /* tmux   */ "TMUX",
+    };
+
+    const char *const *variable = variables;
+    const char *const *end = variable + ARRAY_COUNT(variables);
+
+    while (variable < end) {
+      if (unsetenv(*variable) == -1) {
+        logSystemError("unsetenv");
+      }
+
+      variable += 1;
+    }
+  }
+
+  {
+    size_t width, height;
+
+    if (getConsoleSize(&width, &height)) {
+      if (!setEnvironmentInteger("COLUMNS", width)) return 0;
+      if (!setEnvironmentInteger("LINES", height)) return 0;
+    }
+  }
+
+  return setEnvironmentString("TERM", ptyGetTerminalType());
+}
+
+static int
+prepareChild (PtyObject *pty) {
+  setsid();
+  ptyCloseMaster(pty);
+
+  if (setEnvironmentVariables()) {
+    int tty;
+    if (!ptyOpenSlave(pty, &tty)) return 0;
+    int keep = 0;
+
+    for (int fd=0; fd<=2; fd+=1) {
+      if (fd == tty) {
+        keep = 1;
+      } else {
+        int result = dup2(tty, fd);
+
+        if (result == -1) {
+          logSystemError("dup2");
+          return 0;
+        }
+      }
+    }
+
+    if (!keep) close(tty);
+  }
+
+  return 1;
+}
+
+static int
+runChild (PtyObject *pty, char **command) {
+  char *defaultCommand[2];
+
+  if (!(command && *command)) {
+    char *shell = getenv("SHELL");
+    if (!(shell && *shell)) shell = "/bin/sh";
+
+    defaultCommand[0] = shell;
+    defaultCommand[1] = NULL;
+    command = defaultCommand;
+  }
+
+  if (prepareChild(pty)) {
+    int result = execvp(*command, command);
+
+    if (result == -1) {
+      switch (errno) {
+        case ENOENT:
+          logMessage(LOG_ERR, "%s: %s", gettext("command not found"), *command);
+          return PROG_EXIT_SEMANTIC;
+
+        default:
+          logSystemError("execvp");
+          break;
+      }
+    } else {
+      logMessage(LOG_ERR, "unexpected return from execvp");
+    }
+  }
+
+  return PROG_EXIT_FATAL;
+}
+
+static unsigned char parentIsQuitting;
+static unsigned char childHasTerminated;
+static unsigned char slaveHasBeenClosed;
+
+static
+ASYNC_CONDITION_TESTER(parentTerminationTester) {
+  if (parentIsQuitting) return 1;
+  return childHasTerminated && slaveHasBeenClosed;
+}
+
+static void
+parentQuitMonitor (int signalNumber) {
+  parentIsQuitting = 1;
+}
+
+static void
+childTerminationMonitor (int signalNumber) {
+  childHasTerminated = 1;
+}
+
+static int
+installSignalHandlers (void) {
+  if (!asyncHandleSignal(SIGTERM, parentQuitMonitor, NULL)) return 0;
+  if (!asyncHandleSignal(SIGINT, parentQuitMonitor, NULL)) return 0;
+  if (!asyncHandleSignal(SIGQUIT, parentQuitMonitor, NULL)) return 0;
+  return asyncHandleSignal(SIGCHLD, childTerminationMonitor, NULL);
+}
+
+static
+ASYNC_MONITOR_CALLBACK(standardInputMonitor) {
+  PtyObject *pty = parameters->data;
+  if (ptyProcessTerminalInput(pty)) return 1;
+
+  parentIsQuitting = 1;
+  return 0;
+}
+
+static
+ASYNC_INPUT_CALLBACK(ptyInputHandler) {
+  if (!(parameters->error || parameters->end)) {
+    size_t length = parameters->length;
+
+    if (!ptyProcessTerminalOutput(parameters->buffer, length)) {
+      parentIsQuitting = 1;
+    }
+
+    return length;
+  }
+
+  slaveHasBeenClosed = 1;
+  return 0;
+}
+
+static int
+reapExitStatus (pid_t pid) {
+  while (1) {
+    int status;
+    pid_t result = waitpid(pid, &status, 0);
+
+    if (result == -1) {
+      if (errno == EINTR) continue;
+      logSystemError("waitpid");
+      break;
+    }
+
+    if (WIFEXITED(status)) return WEXITSTATUS(status);
+    if (WIFSIGNALED(status)) return 0X80 | WTERMSIG(status);
+
+    #ifdef WCOREDUMP
+    if (WCOREDUMP(status)) return 0X80 | WTERMSIG(status);
+    #endif /* WCOREDUMP */
+
+    #ifdef WIFSTOPPED
+    if (WIFSTOPPED(status)) continue;
+    #endif /* WIFSTOPPED */
+
+    #ifdef WIFCONTINUED
+    if (WIFCONTINUED(status)) continue;
+    #endif /* WIFCONTINUED */
+  }
+
+  return PROG_EXIT_FATAL;
+}
+
+static int
+runParent (PtyObject *pty, pid_t child) {
+  int exitStatus = PROG_EXIT_FATAL;
+  AsyncHandle ptyInputHandle;
+
+  parentIsQuitting = 0;
+  childHasTerminated = 0;
+  slaveHasBeenClosed = 0;
+
+  if (asyncReadFile(&ptyInputHandle, ptyGetMaster(pty), 1, ptyInputHandler, NULL)) {
+    AsyncHandle standardInputHandle;
+
+    if (asyncMonitorFileInput(&standardInputHandle, STDIN_FILENO, standardInputMonitor, pty)) {
+      if (installSignalHandlers()) {
+        if (!isatty(2)) {
+          unsigned char level = LOG_NOTICE;
+          ptySetTerminalLogLevel(level);
+          ptySetLogLevel(pty, level);
+        }
+
+        if (ptyBeginTerminal(pty, opt_driverDirectives)) {
+          writeDriverDirective("path %s", ptyGetPath(pty));
+
+          asyncAwaitCondition(INT_MAX, parentTerminationTester, NULL);
+          if (!parentIsQuitting) exitStatus = reapExitStatus(child);
+
+          ptyEndTerminal();
+        }
+      }
+
+      asyncCancelRequest(standardInputHandle);
+    }
+
+    asyncCancelRequest(ptyInputHandle);
+  }
+
+  return exitStatus;
+}
+
+int
+main (int argc, char *argv[]) {
+  int exitStatus = PROG_EXIT_FATAL;
+  PtyObject *pty;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-pty",
+
+      .usage = {
+        .purpose = strtext("Run a shell or terminal manager within a pty (virtual terminal) and export its screen via a shared memory segment so that brltty can read it via its Terminal Emulator screen driver."),
+        .parameters = "[command [arg ...]]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  ptySetLogTerminalInput(opt_logInput);
+  ptySetLogTerminalOutput(opt_logOutput);
+  ptySetLogTerminalSequences(opt_logSequences);
+  ptySetLogUnexpectedTerminalIO(opt_logUnexpected);
+
+  if (!isatty(STDIN_FILENO)) {
+    logMessage(LOG_ERR, "%s", gettext("standard input isn't a terminal"));
+    return PROG_EXIT_SEMANTIC;
+  }
+
+  if (!isatty(STDOUT_FILENO)) {
+    logMessage(LOG_ERR, "%s", gettext("standard output isn't a terminal"));
+    return PROG_EXIT_SEMANTIC;
+  }
+
+  {
+    uid_t user = 0;
+    gid_t group = 0;
+
+    if (*opt_asUser) {
+      if (!validateUser(&user, opt_asUser, &group)) {
+        logMessage(LOG_ERR, "unknown user: %s", opt_asUser);
+        return PROG_EXIT_SEMANTIC;
+      }
+    }
+
+    if (*opt_asGroup) {
+      if (!validateGroup(&group, opt_asGroup)) {
+        logMessage(LOG_ERR, "unknown group: %s", opt_asGroup);
+        return PROG_EXIT_SEMANTIC;
+      }
+    }
+
+    if (group) {
+      if (setregid(group, group) == -1) {
+        logSystemError("setregid");
+        return PROG_EXIT_FATAL;
+      }
+    }
+
+    if (user) {
+      if (setreuid(user, user) == -1) {
+        logSystemError("setreuid");
+        return PROG_EXIT_FATAL;
+      }
+    }
+  }
+
+  if (*opt_workingDirectory) {
+    if (chdir(opt_workingDirectory) == -1) {
+      logMessage(LOG_ERR, "can't change to directory: %s: %s", opt_workingDirectory, strerror(errno));
+      return PROG_EXIT_FATAL;
+    }
+  }
+
+  if (*opt_homeDirectory) {
+    if (!setEnvironmentString("HOME", opt_homeDirectory)) {
+      return PROG_EXIT_FATAL;
+    }
+  }
+
+  if ((pty = ptyNewObject())) {
+    ptySetLogInput(pty, opt_logInput);
+    const char *ttyPath = ptyGetPath(pty);
+
+    if (opt_showPath) {
+      FILE *stream = stderr;
+      fprintf(stream, "%s\n", ttyPath);
+      fflush(stream);
+    }
+
+    pid_t child = fork();
+    switch (child) {
+      case -1:
+        logSystemError("fork");
+        break;
+
+      case 0:
+        _exit(runChild(pty, argv));
+
+      default:
+        exitStatus = runParent(pty, child);
+        break;
+    }
+
+    ptyDestroyObject(pty);
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-trtxt.c b/Programs/brltty-trtxt.c
new file mode 100644
index 0000000..07d72ee
--- /dev/null
+++ b/Programs/brltty-trtxt.c
@@ -0,0 +1,287 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "file.h"
+#include "unicode.h"
+#include "utf8.h"
+#include "brl_dots.h"
+#include "ttb.h"
+
+static char *opt_tablesDirectory;
+static char *opt_inputTable;
+static char *opt_outputTable;
+
+static int opt_sixDots;
+static int opt_noBaseCharacters;
+
+static const char tableName_autoselect[] = "auto";
+static const char tableName_unicode[] = "unicode";
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "input-table",
+    .letter = 'i',
+    .argument = strtext("file"),
+    .setting.string = &opt_inputTable,
+    .internal.setting = tableName_autoselect,
+    .description = strtext("Path to input text table.")
+  },
+
+  { .word = "output-table",
+    .letter = 'o',
+    .argument = strtext("file"),
+    .setting.string = &opt_outputTable,
+    .internal.setting = tableName_unicode,
+    .description = strtext("Path to output text table.")
+  },
+
+  { .word = "six-dots",
+    .letter = '6',
+    .setting.flag = &opt_sixDots,
+    .description = strtext("Remove dots seven and eight.")
+  },
+
+  { .word = "no-base-characters",
+    .letter = 'b',
+    .setting.flag = &opt_noBaseCharacters,
+    .description = strtext("Don't fall back to the Unicode base character.")
+  },
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory for text tables.")
+  },
+END_OPTION_TABLE(programOptions)
+
+static TextTable *inputTable;
+static TextTable *outputTable;
+
+static FILE *outputStream;
+static const char *outputName;
+
+static unsigned char (*toDots) (wchar_t character);
+static wchar_t (*toCharacter) (unsigned char dots);
+
+static unsigned char
+toDots_mapped (wchar_t character) {
+  return convertCharacterToDots(inputTable, character);
+}
+
+static unsigned char
+toDots_unicode (wchar_t character) {
+  return ((character & UNICODE_ROW_MASK) == UNICODE_BRAILLE_ROW)?
+         character & UNICODE_CELL_MASK:
+         (BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_7 | BRL_DOT_8);
+}
+
+static wchar_t
+toCharacter_mapped (unsigned char dots) {
+  return convertDotsToCharacter(outputTable, dots);
+}
+
+static wchar_t
+toCharacter_unicode (unsigned char dots) {
+  return UNICODE_BRAILLE_ROW | dots;
+}
+
+static int
+writeCharacter (const wchar_t *character, mbstate_t *state) {
+  char bytes[0X1000];
+  size_t result = wcrtomb(bytes, (character? *character: WC_C('\0')), state);
+
+  if (result == (size_t)-1) return 0;
+  if (!character) result -= 1;
+
+  fwrite(bytes, 1, result, outputStream);
+  return !ferror(outputStream);
+}
+
+static int
+processStream (FILE *inputStream, const char *inputName) {
+  mbstate_t inputState;
+  memset(&inputState, 0, sizeof(inputState));
+
+  mbstate_t outputState;
+  memset(&outputState, 0, sizeof(outputState));
+
+  while (!feof(inputStream)) {
+    char inputBuffer[0X1000];
+    size_t inputCount = fread(inputBuffer, 1, sizeof(inputBuffer)-1, inputStream);
+
+    if (ferror(inputStream)) goto inputError;
+    if (!inputCount) break;
+    inputBuffer[inputCount] = 0;
+
+    {
+      char *byte = inputBuffer;
+
+      while (inputCount) {
+        wchar_t character;
+
+        {
+          size_t result = mbrtowc(&character, byte, inputCount, &inputState);
+
+          if (result == (size_t)-2) break;
+          if (result == (size_t)-1) goto inputError;
+          if (!result) result = 1;
+
+          byte += result;
+          inputCount -= result;
+        }
+
+        if (!iswcntrl(character)) {
+          unsigned char dots = toDots(character);
+
+          if (dots || !iswspace(character)) {
+            if (opt_sixDots) dots &= ~(BRL_DOT_7 | BRL_DOT_8);
+            character = toCharacter(dots);
+          }
+        }
+
+        if (!writeCharacter(&character, &outputState)) goto outputError;
+      }
+    }
+  }
+
+  if (!writeCharacter(NULL, &outputState)) goto outputError;
+  fflush(outputStream);
+  if (ferror(outputStream)) goto outputError;
+
+  if (!mbsinit(&inputState)) {
+#ifdef EILSEQ
+    errno = EILSEQ;
+#else /* EILSEQ */
+    errno = EINVAL;
+#endif /* EILSEQ */
+    goto inputError;
+  }
+
+  return 1;
+
+inputError:
+  logMessage(LOG_ERR, "input error: %s: %s", inputName, strerror(errno));
+  return 0;
+
+outputError:
+  logMessage(LOG_ERR, "output error: %s: %s", outputName, strerror(errno));
+  return 0;
+}
+
+static int
+getTable (TextTable **table, const char *name) {
+  const char *directory = opt_tablesDirectory;
+
+  *table = NULL;
+
+  if (strcmp(name, tableName_unicode) != 0) {
+    char *allocated = NULL;
+
+    if (strcmp(name, tableName_autoselect) == 0) {
+      if (!(name = allocated = getTextTableForLocale(directory))) {
+        logMessage(LOG_ERR, "cannot find text table for current locale");
+      }
+    }
+
+    if (name) {
+      char *path = makeTextTablePath(directory, name);
+
+      if (path) {
+        *table = compileTextTable(path);
+        free(path);
+      }
+    }
+
+    if (allocated) free(allocated);
+     if (opt_noBaseCharacters) setTryBaseCharacter(*table, 0);
+    if (!*table) return 0;
+  }
+
+  return 1;
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus = PROG_EXIT_FATAL;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-trtxt",
+
+      .usage = {
+        .purpose = strtext("Translate one binary braille representation to another."),
+        .parameters = "[{input-file | -} ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (getTable(&inputTable, opt_inputTable)) {
+    if (getTable(&outputTable, opt_outputTable)) {
+      outputStream = stdout;
+      outputName = standardOutputName;
+
+      toDots = inputTable? toDots_mapped: toDots_unicode;
+      toCharacter = outputTable? toCharacter_mapped: toCharacter_unicode;
+
+      if (argc) {
+        do {
+          const char *file = argv[0];
+          FILE *stream;
+
+          if (strcmp(file, standardStreamArgument) == 0) {
+            if (!processStream(stdin, standardInputName)) break;
+          } else if ((stream = fopen(file, "r"))) {
+            int ok = processStream(stream, file);
+            fclose(stream);
+            if (!ok) break;
+          } else {
+            logMessage(LOG_ERR, "cannot open file: %s: %s", file, strerror(errno));
+            exitStatus = PROG_EXIT_SEMANTIC;
+            break;
+          }
+
+          argv += 1, argc -= 1;
+        } while (argc);
+
+        if (!argc) exitStatus = PROG_EXIT_SUCCESS;
+      } else if (processStream(stdin, standardInputName)) {
+        exitStatus = PROG_EXIT_SUCCESS;
+      }
+
+      if (outputTable) destroyTextTable(outputTable);
+    }
+
+    if (inputTable) destroyTextTable(inputTable);
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-ttb.c b/Programs/brltty-ttb.c
new file mode 100644
index 0000000..80d5933
--- /dev/null
+++ b/Programs/brltty-ttb.c
@@ -0,0 +1,2569 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "program.h"
+#include "cmdline.h"
+#include "file.h"
+#include "get_select.h"
+#include "brl_dots.h"
+#include "charset.h"
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "ttb_compile.h"
+
+#undef HAVE_UNDEFINED_CHARACTERS_SUPPORT
+#if defined(__linux__)
+#define HAVE_UNDEFINED_CHARACTERS_SUPPORT
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/kd.h>
+
+static void
+showUndefinedCharacters (TextTableData *ttd) {
+  char path[0X40];
+  int console;
+
+  {
+    const char *vt = getenv("XDG_VTNR");
+
+    if (!(vt && *vt)) vt = "0";
+    snprintf(path, sizeof(path), "/dev/tty%s", vt);
+  }
+
+  if ((console = open(path, O_RDONLY)) != -1) {
+    struct unimapdesc sfm;
+    unsigned short size = 0X100;
+
+    while (1) {
+      sfm.entry_ct = size;
+
+      if (!(sfm.entries = malloc(ARRAY_SIZE(sfm.entries, sfm.entry_ct)))) {
+        logMallocError();
+        break;
+      }
+
+      if (ioctl(console, GIO_UNIMAP, &sfm) != -1) break;
+      free(sfm.entries);
+      sfm.entries = NULL;
+
+      if (errno != ENOMEM) {
+        logSystemError("ioctl[GIO_UNIMAP]");
+        break;
+      }
+
+      if (!(size <<= 1)) {
+        logMessage(LOG_ERR, "screen font map too big");
+        break;
+      }
+    }
+
+    if (sfm.entries) {
+      const struct unipair *entry = sfm.entries;
+      const struct unipair *end = entry + sfm.entry_ct;
+
+      while (entry < end) {
+        wchar_t character = entry++->unicode;
+
+        if (character == UNICODE_REPLACEMENT_CHARACTER) continue;
+        if ((character & ~UNICODE_CELL_MASK) == UNICODE_BRAILLE_ROW) continue;
+
+        if (!getUnicodeCell(ttd, character)) {
+          const TextTableHeader *header = getTextTableHeader(ttd);
+
+          const TextTableAliasEntry *alias = locateTextTableAlias(
+            character, getTextTableItem(ttd, header->aliasArray), header->aliasCount
+          );
+
+          if (!alias) {
+            char buffer[0X80];
+
+            STR_BEGIN(buffer, sizeof(buffer));
+            STR_PRINTF("undefined character: U+%04"PRIX32, (uint32_t)character);
+
+            {
+              char name[0X40];
+
+              if (getCharacterName(character, name, sizeof(name))) {
+                STR_PRINTF(" [%s]", name);
+              }
+            }
+
+            STR_END;
+            logMessage(LOG_WARNING, "%s", buffer);
+          }
+        }
+      }
+
+      free(sfm.entries);
+    }
+
+    close(console);
+  } else {
+    logMessage(LOG_ERR, "cannot open console: %s: %s", path, strerror(errno));
+  }
+}
+
+#endif /* undefined characters support */
+
+static char *opt_charset;
+static char *opt_inputFormat;
+static char *opt_outputFormat;
+static char *opt_tablesDirectory;
+static int opt_edit;
+
+#ifdef HAVE_UNDEFINED_CHARACTERS_SUPPORT
+static int opt_undefined;
+#endif /* HAVE_UNDEFINED_CHARACTERS_SUPPORT */
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "edit",
+    .letter = 'e',
+    .setting.flag = &opt_edit,
+    .description = strtext("Edit table.")
+  },
+
+  { .word = "input-format",
+    .letter = 'i',
+    .argument = "format",
+    .setting.string = &opt_inputFormat,
+    .description = strtext("Format of input file.")
+  },
+
+  { .word = "output-format",
+    .letter = 'o',
+    .argument = "format",
+    .setting.string = &opt_outputFormat,
+    .description = strtext("Format of output file.")
+  },
+
+  { .word = "charset",
+    .letter = 'c',
+    .argument = "charset",
+    .setting.string = &opt_charset,
+    .description = strtext("8-bit character set to use.")
+  },
+
+#ifdef HAVE_UNDEFINED_CHARACTERS_SUPPORT
+  { .word = "undefined",
+    .letter = 'u',
+    .setting.flag = &opt_undefined,
+    .description = strtext("Report the characters within the current screen font that aren't defined within the text table.")
+  },
+#endif /* HAVE_UNDEFINED_CHARACTERS_SUPPORT */
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .argument = "directory",
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing text tables.")
+  },
+END_OPTION_TABLE(programOptions)
+
+static const BrlDotTable dotsInternal = {
+  BRL_DOT_1, BRL_DOT_2, BRL_DOT_3, BRL_DOT_4,
+  BRL_DOT_5, BRL_DOT_6, BRL_DOT_7, BRL_DOT_8
+};
+
+static const BrlDotTable dots12345678 = {
+  0X01, 0X02, 0X04, 0X08, 0X10, 0X20, 0X40, 0X80
+};
+
+static const BrlDotTable dots14253678 = {
+  0X01, 0X04, 0X10, 0X02, 0X08, 0X20, 0X40, 0X80
+};
+
+static unsigned char
+mapDots (unsigned char input, const BrlDotTable from, const BrlDotTable to) {
+  unsigned char output = 0;
+  {
+    for (int dot=0; dot<BRL_DOT_COUNT; dot+=1) {
+      if (input & from[dot]) output |= to[dot];
+    }
+  }
+  return output;
+}
+
+typedef TextTableData *TableReader (const char *path, FILE *file, const void *data);
+typedef int TableWriter (const char *path, FILE *file, TextTableData *ttd, const void *data);
+typedef int CharacterWriter (FILE *file, const char *directive, wchar_t character, unsigned char dots);
+typedef int AliasWriter (FILE *file, const TextTableAliasEntry *alias, const void *data);
+
+static int
+getDots (TextTableData *ttd, wchar_t character, unsigned char *dots) {
+  const unsigned char *cell = getUnicodeCell(ttd, character);
+  if (!cell) return 0;
+  *dots = *cell;
+  return 1;
+}
+
+static int
+writeString (FILE *file, const char *string) {
+  if (fputs(string, file) != EOF) return 1;
+  logSystemError("output");
+  return 0;
+}
+
+static int
+endLine (FILE *file) {
+  return writeString(file, "\n");
+}
+
+typedef int CommentWriter (FILE *file, const char *text);
+
+static int
+writeHashComment (FILE *file, const char *text) {
+  if (!writeString(file, "# ")) return 0;
+  if (!writeString(file, text)) return 0;
+  if (!endLine(file)) return 0;
+  return 1;
+}
+
+static int
+writeSemicolonComment (FILE *file, const char *text) {
+  if (!writeString(file, "; ")) return 0;
+  if (!writeString(file, text)) return 0;
+  if (!endLine(file)) return 0;
+  return 1;
+}
+
+static int
+writeCComment (FILE *file, const char *text) {
+  if (!writeString(file, "/* ")) return 0;
+  if (!writeString(file, text)) return 0;
+  if (!writeString(file, " */")) return 0;
+  if (!endLine(file)) return 0;
+  return 1;
+}
+
+static int
+defineCMacro (FILE *file, const char *name, const char *args) {
+  if (!writeString(file, "#ifndef ")) return 0;
+  if (!writeString(file, name)) return 0;
+  if (!endLine(file)) return 0;
+
+  if (!writeString(file, "#define ")) return 0;
+  if (!writeString(file, name)) return 0;
+  if (!writeString(file, args)) return 0;
+  if (!endLine(file)) return 0;
+
+  if (!writeString(file, "#endif")) return 0;
+  if (!endLine(file)) return 0;
+
+  if (!endLine(file)) return 0;
+  return 1;
+}
+
+static int
+beginCMacro (FILE *file, const char *name) {
+  if (!writeString(file, name)) return 0;
+  if (!writeString(file, "(")) return 0;
+  return 1;
+}
+
+static int
+endCMacro (FILE *file) {
+  if (!writeString(file, ")")) return 0;
+  return 1;
+}
+
+static int
+nextCArgument (FILE *file) {
+  return fprintf(file, ", ") != EOF;
+}
+
+static int
+writeHeaderComment (FILE *file, CommentWriter *writeComment) {
+  char buffer[0X80];
+
+  STR_BEGIN(buffer, sizeof(buffer));
+  STR_PRINTF("generated by %s", programName);
+  if (*opt_charset) STR_PRINTF(": charset=%s", opt_charset);
+  STR_END;
+
+  return writeComment(file, buffer);
+}
+
+static int
+writeCharacterDescription (FILE *file, wchar_t character) {
+  if (!writeString(file, " ")) return 0;
+  if (!writeUtf8Character(file, ((iswprint(character) && !iswspace(character))? character: WC_C(' ')))) return 0;
+
+  {
+    char name[0X40];
+
+    if (getCharacterName(character, name, sizeof(name))) {
+      if (!writeString(file, " [")) return 0;
+      if (!writeString(file, name)) return 0;
+      if (!writeString(file, "]")) return 0;
+    }
+  }
+
+  return 1;
+}
+
+typedef struct {
+  wchar_t character;
+  unsigned char dots;
+} InputCharacterEntry;
+
+static int
+sortInputCharacterEntries (const void *element1, const void *element2) {
+  const InputCharacterEntry *entry1 = element1;
+  const InputCharacterEntry *entry2 = element2;
+
+  if (entry1->character < entry2->character) return -1;
+  if (entry1->character > entry2->character) return 1;
+  return 0;
+}
+
+typedef struct {
+  const char *in;
+  const char *out;
+  const char *inOut;
+} WriteCharacterDirectives;
+
+#define WRITE_CHARACTER_DIRECTIVES(...) &(const WriteCharacterDirectives){__VA_ARGS__}
+#define WRITE_CHARACTERS_IN_ONLY WRITE_CHARACTER_DIRECTIVES(.in="")
+#define WRITE_CHARACTERS_OUT_ONLY WRITE_CHARACTER_DIRECTIVES(.out="")
+#define WRITE_CHARACTERS_NATIVE WRITE_CHARACTER_DIRECTIVES(.in="input", .out="glyph", .inOut="char")
+#define WRITE_CHARACTERS_LIBLOUIS WRITE_CHARACTER_DIRECTIVES(.in="nofor", .out="noback", .inOut="")
+
+static int
+writeInputCharacters (
+  FILE *file, const wchar_t *character,
+  const InputCharacterEntry **entry,
+  const InputCharacterEntry *const *end,
+  CharacterWriter *writer, const char *directive
+) {
+  while (*entry < *end) {
+    if (character) {
+      if ((*entry)->character >= *character) {
+        break;
+      }
+    }
+
+    if (directive) {
+      if (!writer(file, directive, (*entry)->character, (*entry)->dots)) {
+        return 0;
+      }
+    }
+
+    *entry += 1;
+  }
+
+  return 1;
+}
+
+static int
+writeCharacters (FILE *file, TextTableData *ttd, CharacterWriter writer, const void *data) {
+  const WriteCharacterDirectives *directives = data;
+  const TextTableHeader *header = getTextTableHeader(ttd);
+
+  InputCharacterEntry inputCharactersBuffer[0X100];
+  unsigned int inputCharacterCount = 0;
+
+  for (unsigned int dots=0; dots<0X100; dots+=1) {
+    if (BITMASK_TEST(header->inputCharacterDefined, dots)) {
+      InputCharacterEntry *entry = &inputCharactersBuffer[inputCharacterCount++];
+      entry->character = header->inputCharacters[dots];
+      entry->dots = dots;
+    }
+  }
+
+  qsort(
+    inputCharactersBuffer, inputCharacterCount,
+    sizeof(inputCharactersBuffer[0]),
+    sortInputCharacterEntries
+  );
+
+  const InputCharacterEntry *inputCharacterEntry = inputCharactersBuffer;
+  const InputCharacterEntry *const inputCharacterEnd = inputCharacterEntry + inputCharacterCount;
+
+  for (unsigned int groupNumber=0; groupNumber<UNICODE_GROUP_COUNT; groupNumber+=1) {
+    TextTableOffset groupOffset = header->unicodeGroups[groupNumber];
+
+    if (groupOffset) {
+      const UnicodeGroupEntry *group = getTextTableItem(ttd, groupOffset);
+
+      for (unsigned int planeNumber=0; planeNumber<UNICODE_PLANES_PER_GROUP; planeNumber+=1) {
+        TextTableOffset planeOffset = group->planes[planeNumber];
+
+        if (planeOffset) {
+          const UnicodePlaneEntry *plane = getTextTableItem(ttd, planeOffset);
+
+          for (unsigned int rowNumber=0; rowNumber<UNICODE_ROWS_PER_PLANE; rowNumber+=1) {
+            TextTableOffset rowOffset = plane->rows[rowNumber];
+
+            if (rowOffset) {
+              const UnicodeRowEntry *row = getTextTableItem(ttd, rowOffset);
+
+              for (unsigned int cellNumber=0; cellNumber<UNICODE_CELLS_PER_ROW; cellNumber+=1) {
+                if (BITMASK_TEST(row->cellDefined, cellNumber)) {
+                  wchar_t character = UNICODE_CHARACTER(groupNumber, planeNumber, rowNumber, cellNumber);
+                  unsigned char dots = row->cells[cellNumber];
+
+                  {
+                    int ok = writeInputCharacters(
+                      file, &character,
+                      &inputCharacterEntry, &inputCharacterEnd,
+                      writer, directives->in
+                    );
+
+                    if (!ok) return 0;
+                  }
+
+                  if (inputCharacterEntry < inputCharacterEnd) {
+                    if (character == inputCharacterEntry->character) {
+                      if (dots == inputCharacterEntry->dots) {
+                        const char *directive = directives->inOut;
+
+                        if (directive) {
+                          if (!writer(file, directive, character, dots)) {
+                            return 0;
+                          }
+
+                          inputCharacterEntry += 1;
+                          continue;
+                        }
+                      }
+                    }
+                  }
+
+                  {
+                    const char *directive = directives->out;
+
+                    if (directive) {
+                      if (!writer(file, directive, character, dots)) {
+                        return 0;
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return writeInputCharacters(
+    file, NULL,
+    &inputCharacterEntry, &inputCharacterEnd,
+    writer, directives->in
+  );
+}
+
+static int
+writeAliases (FILE *file, TextTableData *ttd, AliasWriter writer, const void *data) {
+  const TextTableHeader *header = getTextTableHeader(ttd);
+  const TextTableAliasEntry *alias = getTextTableItem(ttd, header->aliasArray);
+  const TextTableAliasEntry *end = alias + header->aliasCount;
+
+  while (alias < end) {
+    if (!writer(file, alias, data)) return 0;
+    alias += 1;
+  }
+
+  return 1;
+}
+
+static TextTableData *
+readTable_native (const char *path, FILE *file, const void *data) {
+  return processTextTableStream(file, path);
+}
+
+static int
+writeDots_native (FILE *file, unsigned char dots) {
+  if (fprintf(file, "(") == EOF) return 0;
+
+  for (unsigned char dot=0X01; dot; dot<<=1) {
+    char number = (dots & dot)? brlDotToNumber(dot): ' ';
+    if (fprintf(file, "%c", number) == EOF) return 0;
+  }
+
+  if (fprintf(file, ")") == EOF) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_native (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  if (fprintf(file, "%s\t", directive) == EOF) return 0;
+  if (!writeHexadecimalCharacter(file, character)) return 0;
+  if (fprintf(file, "\t") == EOF) return 0;
+  if (!writeDots_native(file, dots)) return 0;
+  if (fprintf(file, "\t#") == EOF) return 0;
+
+  if (fprintf(file, " ") == EOF) return 0;
+  if (!writeUtf8Cell(file, dots)) return 0;
+
+  if (!writeCharacterDescription(file, character)) return 0;
+  return endLine(file);
+}
+
+static int
+writeAlias_native (FILE *file, const TextTableAliasEntry *alias, const void *data) {
+  if (fprintf(file, "alias\t") == EOF) return 0;
+  if (!writeHexadecimalCharacter(file, alias->from)) return 0;
+  if (fprintf(file, "\t") == EOF) return 0;
+  if (!writeHexadecimalCharacter(file, alias->to)) return 0;
+  if (fprintf(file, "\t#") == EOF) return 0;
+  if (!writeCharacterDescription(file, alias->from)) return 0;
+  return endLine(file);
+}
+
+static int
+writeTable_native (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_native, data)) return 0;
+  if (!writeAliases(file, ttd, writeAlias_native, data)) return 0;
+  return 1;
+}
+
+static TextTableData *
+readTable_binary (const char *path, FILE *file, const void *data) {
+  TextTableData *ttd;
+
+  if ((ttd = newTextTableData())) {
+    int count = 0X100;
+    int byte;
+
+    for (byte=0; byte<count; byte+=1) {
+      int dots = fgetc(file);
+
+      if (dots == EOF) {
+        if (ferror(file)) {
+          logMessage(LOG_ERR, "input error: %s: %s", path, strerror(errno));
+        } else {
+          logMessage(LOG_ERR, "table too short: %s", path);
+        }
+
+        break;
+      }
+
+      if (data) dots = mapDots(dots, data, dotsInternal);
+      if (!setTextTableByte(ttd, byte, dots)) break;
+    }
+
+    if (byte == count) return ttd;
+    destroyTextTableData(ttd);
+  }
+
+  return NULL;
+}
+
+static int
+writeTable_binary (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  for (unsigned int byte=0; byte<0X100; byte+=1) {
+    unsigned char dots;
+
+    {
+      wint_t character = convertCharToWchar(byte);
+      if (character == WEOF) character = UNICODE_REPLACEMENT_CHARACTER;
+
+      if (!getDots(ttd, character, &dots)) {
+        dots = 0;
+      }
+    }
+
+    if (data) dots = mapDots(dots, dotsInternal, data);
+    if (fputc(dots, file) == EOF) {
+      logMessage(LOG_ERR, "output error: %s: %s", path, strerror(errno));
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static TextTableData *
+readTable_LibLouis (const char *path, FILE *file, const void *data) {
+  return processLibLouisStream(file, path);
+}
+
+static const char *
+getCharacterType_LibLouis (wchar_t character) {
+  if (iswspace(character)) return "space";
+
+  if (iswlower(character)) return "lowercase";
+  if (iswupper(character)) return "uppercase";
+  if (iswalpha(character)) return "letter";
+
+  if (iswdigit(character)) return "digit";
+  if (iswxdigit(character)) return "digit";
+
+  if (iswpunct(character)) return "punctuation";
+  return NULL;
+}
+
+static int
+writeCharacter_LibLouis (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  const char *type = getCharacterType_LibLouis(character);
+  if (!type) return 1;
+
+  if (*directive) {
+    if (fprintf(file, "%s ", directive) == EOF) {
+      return 0;
+    }
+  }
+
+  if (fprintf(file, "%s\t", type) == EOF) return 0;
+
+  if (character == WC_C('\\')) {
+    if (!writeString(file, "\\\\")) return 0;
+  } else if (character == WC_C('\f')) {
+    if (!writeString(file, "\\f")) return 0;
+  } else if (character == WC_C('\n')) {
+    if (!writeString(file, "\\n")) return 0;
+  } else if (character == WC_C('\r')) {
+    if (!writeString(file, "\\r")) return 0;
+  } else if (character == WC_C(' ')) {
+    if (!writeString(file, "\\s")) return 0;
+  } else if (character == WC_C('\t')) {
+    if (!writeString(file, "\\t")) return 0;
+  } else if (character == WC_C('\v')) {
+    if (!writeString(file, "\\v")) return 0;
+  } else if ((character > 0X20) && (character < 0X7F) && (character != '#')) {
+    wint_t value = character;
+    if (fprintf(file, "%" PRIwc, value) == EOF) return 0;
+  } else {
+    unsigned long int value = character;
+    int digits;
+    char format;
+
+    if (value < (1 << 16)) {
+      digits = 4;
+      format = 'x';
+    } else if (value < (1 << 20)) {
+      digits = 5;
+      format = 'y';
+    } else {
+      digits = 8;
+      format = 'z';
+    }
+
+    if (fprintf(file, "\\%c%0*lX", format, digits, value) == EOF) return 0;
+  }
+
+  if (fprintf(file, "\t") == EOF) return 0;
+  if (!dots) {
+    if (fprintf(file, "0") == EOF) return 0;
+  } else {
+    if (!writeDots(file, dots)) return 0;
+  }
+
+  {
+    char name[0X40];
+
+    if (getCharacterName(character, name, sizeof(name))) {
+      if (fprintf(file, "\t# %s", name) == EOF) return 0;
+    }
+  }
+
+  return endLine(file);
+}
+
+static int
+writeTable_LibLouis (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_LibLouis, data)) return 0;
+  return 1;
+}
+
+#ifdef HAVE_ICONV_H
+static TextTableData *
+readTable_gnome (const char *path, FILE *file, const void *data) {
+  return processGnomeBrailleStream(file, path);
+}
+
+static int
+writeCharacter_gnome (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  wchar_t pattern = UNICODE_BRAILLE_ROW | dots;
+
+  if (iswprint(character) && !iswspace(character)) {
+    Utf8Buffer utf8Character;
+    if (!convertWcharToUtf8(character, utf8Character)) return 0;
+
+    Utf8Buffer utf8Pattern;
+    if (!convertWcharToUtf8(pattern, utf8Pattern)) return 0;
+
+    if (fprintf(file, "UCS-CHAR %s %s", utf8Character, utf8Pattern) == EOF) return 0;
+  } else {
+    uint32_t c = character;
+    uint32_t p = pattern;
+    if (fprintf(file, "UNICODE-CHAR U+%02" PRIx32 " U+%04" PRIx32, c, p) == EOF) return 0;
+  }
+
+  {
+    char name[0X40];
+
+    if (getCharacterName(character, name, sizeof(name))) {
+      if (fprintf(file, "  # %s", name) == EOF) return 0;
+    }
+  }
+
+  return endLine(file);
+}
+
+static int
+writeTable_gnome (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  /* TODO UNKNOWN-CHAR %wc all */
+  if (fprintf(file, "ENCODING UTF-8\n") == EOF) return 0;
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_gnome, data)) return 0;
+  return 1;
+}
+#endif /* HAVE_ICONV_H */
+
+static int
+writeCharacterDots_XCompose (FILE *file, unsigned char dots) {
+  if (fprintf(file, "<braille_") == EOF) return 0;
+
+  if (!dots) {
+    if (fprintf(file, "blank") == EOF) return 0;
+  } else {
+    if (fprintf(file, "dots_") == EOF) return 0;
+    if (!writeDots(file, dots)) return 0;
+  }
+
+  if (fprintf(file, ">") == EOF) return 0;
+
+  return 1;
+}
+
+static int
+writeCharacterOutput_XCompose (FILE *file, wchar_t character) {
+  if (fprintf(file, " : \"") == EOF) return 0;
+
+  switch (character) {
+    case WC_C('\n'):
+      if (fprintf(file, "\\n") == EOF) return 0;
+      break;
+
+    case WC_C('\r'):
+      if (fprintf(file, "\\r") == EOF) return 0;
+      break;
+
+    case WC_C('"'):
+      if (fprintf(file, "\\\"") == EOF) return 0;
+      break;
+
+    case WC_C('\\'):
+      if (fprintf(file, "\\\\") == EOF) return 0;
+      break;
+
+    default:
+      if (fprintf(file, "%lc", (wint_t)character) == EOF) return 0;
+      break;
+  }
+
+  if (fprintf(file, "\"") == EOF) return 0;
+
+  {
+    char name[0X40];
+
+    if (getCharacterName(character, name, sizeof(name))) {
+      if (fprintf(file, "  # %s", name) == EOF) return 0;
+    }
+  }
+
+  return endLine(file);
+}
+
+static int
+writeCharacter_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  if (!writeCharacterDots_XCompose (file, dots)) return 0;
+  return writeCharacterOutput_XCompose(file, character);
+}
+
+static int
+writeTable_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_half_XCompose (
+  FILE *file, wchar_t character,
+  unsigned char leftDots, unsigned char rightDots
+) {
+  if (!writeCharacterDots_XCompose (file, leftDots)) return 0;
+  if (fprintf(file, " ") == EOF) return 0;
+  if (!writeCharacterDots_XCompose (file, rightDots)) return 0;
+  return writeCharacterOutput_XCompose(file, character);
+}
+
+static int
+writeCharacter_leftrighthalf_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  unsigned char leftDots = brlGetLeftDots(dots);
+  unsigned char rightDots = brlGetRightDots(dots);
+
+  if (!writeCharacter_half_XCompose(file, character, leftDots, rightDots)) {
+    return 0;
+  }
+
+  if (!leftDots && rightDots) {
+    /* Also add shortcut without blank pattern for left part.  */
+    if (!writeCharacterDots_XCompose (file, rightDots)) return 0;
+    if (!writeCharacterOutput_XCompose(file, character)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+writeTable_leftrighthalf_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_leftrighthalf_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_lefthalf_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  return writeCharacter_half_XCompose(
+    file, character,
+    brlGetLeftDots(dots),
+    brlGetRightDotsToLeftDots(dots)
+  );
+}
+
+static int
+writeTable_lefthalf_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_lefthalf_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_lefthalfalt_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  return writeCharacter_half_XCompose(
+    file, character,
+    brlGetLeftDots(dots),
+    brlGetRightDotsToLeftDotsAlt(dots)
+  );
+}
+
+static int
+writeTable_lefthalfalt_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_lefthalfalt_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_righthalf_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  return writeCharacter_half_XCompose(
+    file, character,
+    brlGetLeftDotsToRightDots(dots),
+    brlGetRightDots(dots)
+  );
+}
+
+static int
+writeTable_righthalf_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_righthalf_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_righthalfalt_XCompose (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  return writeCharacter_half_XCompose(
+    file, character,
+    brlGetLeftDotsToRightDotsAlt(dots),
+    brlGetRightDots(dots)
+  );
+}
+
+static int
+writeTable_righthalfalt_XCompose (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeHashComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_righthalfalt_XCompose, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacter_JAWS (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  uint32_t value = character;
+
+  if (fprintf(file, "U+%04" PRIX32 "=", value) == EOF) return 0;
+  if (!writeDots(file, dots)) return 0;
+  return endLine(file);
+}
+
+static int
+writeTable_JAWS (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeSemicolonComment)) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_JAWS, data)) return 0;
+  return 1;
+}
+
+static int
+writeCharacterValue_CPreprocessor (FILE *file, wchar_t character) {
+  return fprintf(file, "0X%08" PRIX32, (uint32_t)character) != EOF;
+}
+
+static int
+writeCharacterName_CPreprocessor (FILE *file, wchar_t character) {
+  char name[0X40];
+
+  if (getCharacterName(character, name, sizeof(name))) {
+    if (!writeString(file, "\"")) return 0;
+    if (!writeString(file, name)) return 0;
+    if (!writeString(file, "\"")) return 0;
+  } else {
+    if (!beginCMacro(file, "BRLTTY_TEXT_TABLE_NO_NAME")) return 0;
+    if (!writeCharacterValue_CPreprocessor(file, character)) return 0;
+    if (!endCMacro(file)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+writeCharacter_CPreprocessor (
+  FILE *file, const char *directive,
+  wchar_t character, unsigned char dots
+) {
+  if (!beginCMacro(file, "BRLTTY_TEXT_TABLE_CHARACTER")) return 0;
+  if (!writeCharacterValue_CPreprocessor(file, character)) return 0;
+
+  if (!nextCArgument(file)) return 0;
+  if (fprintf(file, "0X%02" PRIX8, dots) == EOF) return 0;
+
+  if (!nextCArgument(file)) return 0;
+  if (fprintf(file, "%s", directive) == EOF) return 0;
+
+  if (!nextCArgument(file)) return 0;
+  if (!writeCharacterName_CPreprocessor(file, character)) return 0;
+
+  if (!endCMacro(file)) return 0;
+  return endLine(file);
+}
+
+static int
+writeAlias_CPreprocessor (FILE *file, const TextTableAliasEntry *alias, const void *data) {
+  if (!beginCMacro(file, "BRLTTY_TEXT_TABLE_ALIAS")) return 0;
+  if (!writeCharacterValue_CPreprocessor(file, alias->from)) return 0;
+
+  if (!nextCArgument(file)) return 0;
+  if (!writeCharacterValue_CPreprocessor(file, alias->to)) return 0;
+
+  if (!nextCArgument(file)) return 0;
+  if (!writeCharacterName_CPreprocessor(file, alias->from)) return 0;
+
+  if (!endCMacro(file)) return 0;
+  if (!endLine(file)) return 0;
+  return 1;
+}
+
+static int
+writeTable_CPreprocessor (
+  const char *path, FILE *file, TextTableData *ttd, const void *data
+) {
+  if (!writeHeaderComment(file, writeCComment)) return 0;
+  if (!endLine(file)) return 0;
+
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_BEGIN_CHARACTERS", "")) return 0;
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_CHARACTER", "(unicode, braille, properties, name)")) return 0;
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_END_CHARACTERS", "")) return 0;
+
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_BEGIN_ALIASES", "")) return 0;
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_ALIAS", "(from, to, name)")) return 0;
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_END_ALIASES", "")) return 0;
+
+  if (!defineCMacro(file, "BRLTTY_TEXT_TABLE_NO_NAME", "(character)")) return 0;
+
+  if (fprintf(file, "BRLTTY_TEXT_TABLE_BEGIN_CHARACTERS\n") == EOF) return 0;
+  if (!writeCharacters(file, ttd, writeCharacter_CPreprocessor, data)) return 0;
+  if (fprintf(file, "BRLTTY_TEXT_TABLE_END_CHARACTERS\n") == EOF) return 0;
+  if (!endLine(file)) return 0;
+
+  if (fprintf(file, "BRLTTY_TEXT_TABLE_BEGIN_ALIASES\n") == EOF) return 0;
+  if (!writeAliases(file, ttd, writeAlias_CPreprocessor, data)) return 0;
+  if (fprintf(file, "BRLTTY_TEXT_TABLE_END_ALIASES\n") == EOF) return 0;
+  if (!endLine(file)) return 0;
+
+  return 1;
+}
+
+typedef struct {
+  const char *name;
+  TableReader *read;
+  TableWriter *write;
+  const void *data;
+} FormatEntry;
+
+static const FormatEntry formatEntries[] = {
+  { .name = "ttb",
+    .read = readTable_native,
+    .write = writeTable_native,
+    .data = WRITE_CHARACTERS_NATIVE,
+  },
+
+  { .name = "a2b",
+    .read = readTable_binary,
+    .write = writeTable_binary,
+    .data = &dots12345678,
+  },
+
+  { .name = "sbl",
+    .read = readTable_binary,
+    .write = writeTable_binary,
+    .data = &dots14253678,
+  },
+
+  { .name = "ctb",
+    .read = readTable_LibLouis,
+    .write = writeTable_LibLouis,
+    .data = WRITE_CHARACTERS_LIBLOUIS,
+  },
+
+  { .name = "utb",
+    .read = readTable_LibLouis,
+    .write = writeTable_LibLouis,
+    .data = WRITE_CHARACTERS_LIBLOUIS,
+  },
+
+#ifdef HAVE_ICONV_H
+  { .name = "gnb",
+    .read = readTable_gnome,
+    .write = writeTable_gnome,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+#endif /* HAVE_ICONV_H */
+
+  { .name = "XCompose",
+    .write = writeTable_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "half-XCompose",
+    .write = writeTable_leftrighthalf_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "lefthalf-XCompose",
+    .write = writeTable_lefthalf_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "lefthalfalt-XCompose",
+    .write = writeTable_lefthalfalt_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "righthalf-XCompose",
+    .write = writeTable_righthalf_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "righthalfalt-XCompose",
+    .write = writeTable_righthalfalt_XCompose,
+    .data = WRITE_CHARACTERS_IN_ONLY,
+  },
+
+  { .name = "jbt",
+    .write = writeTable_JAWS,
+    .data = WRITE_CHARACTERS_OUT_ONLY,
+  },
+
+  { .name = "cpp",
+    .write = writeTable_CPreprocessor,
+    .data = WRITE_CHARACTER_DIRECTIVES(.in="1", .out="2", .inOut="3"),
+  },
+
+  { .name = NULL }
+};
+
+static const FormatEntry *
+findFormatEntry (const char *name) {
+  const FormatEntry *format = formatEntries;
+  while (format->name) {
+    if (strcmp(name, format->name) == 0) return format;
+    format += 1;
+  }
+  return NULL;
+}
+
+static const FormatEntry *
+getFormatEntry (const char *name, const char *path, const char *description) {
+  if (!(name && *name)) {
+    name = locatePathExtension(path);
+
+    if (!(name && *++name)) {
+      logMessage(LOG_ERR, "unspecified %s format.", description);
+      exit(PROG_EXIT_SYNTAX);
+    }
+  }
+
+  {
+    const FormatEntry *format = findFormatEntry(name);
+    if (format) return format;
+  }
+
+  logMessage(LOG_ERR, "unknown %s format: %s", description, name);
+  exit(PROG_EXIT_SYNTAX);
+}
+
+static const char *inputPath;
+static const char *outputPath;
+static const FormatEntry *inputFormat;
+static const FormatEntry *outputFormat;
+
+static FILE *
+openTable (const char **file, const char *mode, const char *directory, FILE *stdStream, const char *stdName) {
+  if (stdStream) {
+    if (strcmp(*file, standardStreamArgument) == 0) {
+      *file = stdName;
+      return stdStream;
+    }
+  }
+
+  if (directory) {
+    const char *path = makeTextTablePath(directory, *file);
+
+    if (!path) return NULL;
+    *file = path;
+  }
+
+  {
+    FILE *stream = fopen(*file, mode);
+
+    if (!stream) logMessage(LOG_ERR, "table open error: %s: %s", *file, strerror(errno));
+    return stream;
+  }
+}
+
+static FILE *
+openInputTable (const char **path, int allowStandardInput) {
+  FILE *stdStream;
+  const char *stdName;
+
+  if (allowStandardInput) {
+    stdStream = stdin;
+    stdName = standardInputName;
+  } else {
+    stdStream = NULL;
+    stdName = NULL;
+  }
+
+  return openTable(path, "r", opt_tablesDirectory, stdStream, stdName);
+}
+
+static FILE *
+openOutputTable (const char **path) {
+  return openTable(path, "w", NULL, stdout, standardOutputName);
+}
+
+static TextTableData *
+readTable (const char *path, FILE *file, const FormatEntry *fmt) {
+  if (fmt->read) return fmt->read(path, file, fmt->data);
+  logMessage(LOG_ERR, "reading not supported: %s", fmt->name);
+  return NULL;
+}
+
+static ProgramExitStatus
+convertTable (void) {
+  ProgramExitStatus exitStatus;
+  FILE *inputFile = openInputTable(&inputPath, 1);
+
+  if (inputFile) {
+    TextTableData *ttd;
+
+    if ((ttd = readTable(inputPath, inputFile, inputFormat))) {
+#ifdef HAVE_UNDEFINED_CHARACTERS_SUPPORT
+      if (opt_undefined) {
+        showUndefinedCharacters(ttd);
+      }
+#endif /* HAVE_UNDEFINED_CHARACTERS_SUPPORT */
+
+      if (outputPath) {
+        FILE *outputFile = openOutputTable(&outputPath);
+
+        if (outputFile) {
+          if (outputFormat->write(outputPath, outputFile, ttd, outputFormat->data)) {
+            exitStatus = PROG_EXIT_SUCCESS;
+          } else {
+            exitStatus = PROG_EXIT_FATAL;
+          }
+
+          fclose(outputFile);
+        } else {
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      } else {
+        exitStatus = PROG_EXIT_SUCCESS;
+      }
+
+      destroyTextTableData(ttd);
+    } else {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+
+    fclose(inputFile);
+  } else {
+    exitStatus = PROG_EXIT_FATAL;
+  }
+
+  return exitStatus;
+}
+
+#include "get_curses.h"
+#ifndef GOT_CURSES
+#define refresh() fflush(stdout)
+#define printw printf
+#define erase() printf("\r\n\v")
+#define beep() printf("\a")
+
+static int inputAttributesChanged;
+
+#if defined(__MINGW32__)
+#define STDIN_HANDLE ((HANDLE)_get_osfhandle(STDIN_FILENO))
+static DWORD inputConsoleMode;
+
+#undef beep
+#define beep() MessageBeep(MB_ICONWARNING)
+
+#else /* termios */
+#include <termios.h>
+static struct termios inputTerminalAttributes;
+
+#ifndef _POSIX_VDISABLE
+#define _POSIX_VDISABLE 0
+#endif /* _POSIX_VDISABLE */
+#endif /* input terminal definitions */
+#endif /* GOT_CURSES */
+
+#ifdef ENABLE_API
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+#endif /* ENABLE_API */
+
+typedef struct {
+  TextTableData *ttd;
+  unsigned updated:1;
+
+  const char *charset;
+  union {
+    wchar_t unicode;
+    unsigned char byte;
+  } character;
+
+#ifdef ENABLE_API
+  brlapi_fileDescriptor brlapiFileDescriptor;
+  const char *brlapiErrorFunction;
+  const char *brlapiErrorMessage;
+#endif /* ENABLE_API */
+
+  unsigned int displayWidth;
+  unsigned int displayHeight;
+} EditTableData;
+
+#ifdef ENABLE_API
+static int
+haveBrailleDisplay (EditTableData *etd) {
+  return etd->brlapiFileDescriptor != (brlapi_fileDescriptor)-1;
+}
+
+static void
+setBrlapiError (EditTableData *etd, const char *function) {
+  if ((etd->brlapiErrorFunction = function)) {
+    etd->brlapiErrorMessage = brlapi_strerror(&brlapi_error);
+  } else {
+    etd->brlapiErrorMessage = NULL;
+  }
+}
+
+static void
+releaseBrailleDisplay (EditTableData *etd) {
+  brlapi_closeConnection();
+  etd->brlapiFileDescriptor = (brlapi_fileDescriptor)-1;
+}
+
+static int
+claimBrailleDisplay (EditTableData *etd) {
+  etd->brlapiFileDescriptor = brlapi_openConnection(NULL, NULL);
+  if (haveBrailleDisplay(etd)) {
+    if (brlapi_getDisplaySize(&etd->displayWidth, &etd->displayHeight) != -1) {
+      if (brlapi_enterTtyMode(BRLAPI_TTY_DEFAULT, NULL) != -1) {
+        setBrlapiError(etd, NULL);
+        return 1;
+      } else {
+        setBrlapiError(etd, "brlapi_enterTtyMode");
+      }
+    } else {
+      setBrlapiError(etd, "brlapi_getDisplaySize");
+    }
+
+    releaseBrailleDisplay(etd);
+  } else {
+    setBrlapiError(etd, "brlapi_openConnection");
+  }
+
+  return 0;
+}
+#endif /* ENABLE_API */
+
+static int
+getCharacter (EditTableData *etd, wchar_t *character) {
+  if (etd->charset) {
+    wint_t wc = convertCharToWchar(etd->character.byte);
+    if (wc == WEOF) return 0;
+    *character = wc;
+  } else {
+    *character = etd->character.unicode;
+  }
+
+  return 1;
+}
+
+static wchar_t *
+makeCharacterDescription (TextTableData *ttd, wchar_t character, size_t *length, int *defined, unsigned char *braille) {
+  char buffer[0X100];
+  size_t characterIndex;
+  size_t brailleIndex;
+  size_t descriptionLength;
+
+  unsigned char dots = 0;
+  int gotDots = getDots(ttd, character, &dots);
+
+  wchar_t printableCharacter;
+  char printablePrefix;
+
+  printableCharacter = character;
+  if (iswLatin1(printableCharacter)) {
+    printablePrefix = '^';
+    if (!(printableCharacter & 0X60)) {
+      printableCharacter |= 0X40;
+      if (printableCharacter & 0X80) printablePrefix = '~';
+    } else if (printableCharacter == 0X7F) {
+      printableCharacter ^= 0X40;       
+    } else if (printableCharacter != printablePrefix) {
+      printablePrefix = ' ';
+    }
+  } else {
+    printablePrefix = ' ';
+  }
+  if (!gotDots) dots = 0;
+
+#define DOT(n) ((dots & BRL_DOT_##n)? ((n) + '0'): ' ')
+  {
+    uint32_t value = character;
+
+    STR_BEGIN(buffer, sizeof(buffer));
+    STR_PRINTF("%04" PRIX32 " %c", value, printablePrefix);
+    characterIndex = STR_LENGTH;
+    STR_PRINTF("x ");
+    brailleIndex = STR_LENGTH;
+    STR_PRINTF("x %c%c%c%c%c%c%c%c%c%c",
+             (gotDots? '[': ' '),
+             DOT(1), DOT(2), DOT(3), DOT(4), DOT(5), DOT(6), DOT(7), DOT(8),
+             (gotDots? ']': ' '));
+    descriptionLength = STR_LENGTH;
+    STR_END;
+  }
+#undef DOT
+
+  {
+    char *name = buffer + descriptionLength + 1;
+    int size = buffer + sizeof(buffer) - name;
+
+    if (getCharacterName(character, name, size)) {
+      descriptionLength += strlen(name) + 1;
+      *--name = ' ';
+    }
+  }
+
+  {
+    wchar_t *description = calloc(descriptionLength+1, sizeof(*description));
+    if (description) {
+      for (unsigned int i=0; i<descriptionLength; i+=1) {
+        wint_t wc = convertCharToWchar(buffer[i]);
+        if (wc == WEOF) wc = WC_C(' ');
+        description[i] = wc;
+      }
+
+      description[characterIndex] = printableCharacter;
+      description[brailleIndex] = gotDots? (UNICODE_BRAILLE_ROW | dots): WC_C(' ');
+      description[descriptionLength] = 0;
+
+      if (length) *length = descriptionLength;
+      if (defined) *defined = gotDots;
+      if (braille) *braille = dots;
+      return description;
+    }
+  }
+
+  return NULL;
+}
+
+static void
+printCharacterString (const wchar_t *wcs) {
+  while (*wcs) {
+    wint_t wc = *wcs++;
+    printw("%" PRIwc, wc);
+  }
+}
+
+static void
+printNavigationPair (
+  const char *key1, const char *preposition1,
+  const char *key2, const char *preposition2,
+  const char *adjective, const char *noun,
+  int width
+) {
+  int length = 0;
+
+  if (*key1 || *key2) {
+    const char *separator = (*key1 && *key2)? "/": "";
+
+    printw("%s%s%s: %s%s%s %s %s%n",
+           key1, separator, key2,
+           (*key1? preposition1: ""), separator, (*key2? preposition2: ""),
+           adjective, noun, &length);
+  }
+
+  if (width > length) printw("%*s", width-length, "");
+}
+
+static int
+updateCharacterDescription (EditTableData *etd) {
+  int ok = 0;
+
+  wchar_t character = UNICODE_REPLACEMENT_CHARACTER;
+  int gotCharacter = getCharacter(etd, &character);
+
+  size_t length;
+  int gotDots;
+  unsigned char dots;
+  wchar_t *description = makeCharacterDescription(etd->ttd, character, &length, &gotDots, &dots);
+
+  if (description) {
+    ok = 1;
+    erase();
+
+#if defined(GOT_CURSES)
+#define KEY_FIRST_ACTUAL_CHARACTER ""
+#define KEY_LAST_ACTUAL_CHARACTER ""
+#define KEY_PREVIOUS_ACTUAL_CHARACTER "Left"
+#define KEY_NEXT_ACTUAL_CHARACTER "Right"
+#define KEY_FIRST_DEFINED_CHARACTER "Home"
+#define KEY_LAST_DEFINED_CHARACTER "End"
+#define KEY_PREVIOUS_DEFINED_CHARACTER "Up"
+#define KEY_NEXT_DEFINED_CHARACTER "Down"
+#define KEY_TOGGLE_DOT1 "F4"
+#define KEY_TOGGLE_DOT2 "F3"
+#define KEY_TOGGLE_DOT3 "F2"
+#define KEY_TOGGLE_DOT4 "F5"
+#define KEY_TOGGLE_DOT5 "F6"
+#define KEY_TOGGLE_DOT6 "F7"
+#define KEY_TOGGLE_DOT7 "F1"
+#define KEY_TOGGLE_DOT8 "F8"
+#define KEY_TOGGLE_CHARACTER "F9"
+#define KEY_ALTERNATE_CHARACTER "F10"
+#define KEY_SAVE_TABLE "F11"
+#define KEY_EXIT_EDITOR "F12"
+#else /* standard input/output */
+#define KEY_FIRST_ACTUAL_CHARACTER "^S"
+#define KEY_LAST_ACTUAL_CHARACTER "^G"
+#define KEY_PREVIOUS_ACTUAL_CHARACTER "^D"
+#define KEY_NEXT_ACTUAL_CHARACTER "^F"
+#define KEY_FIRST_DEFINED_CHARACTER "^H"
+#define KEY_LAST_DEFINED_CHARACTER "^L"
+#define KEY_PREVIOUS_DEFINED_CHARACTER "^J"
+#define KEY_NEXT_DEFINED_CHARACTER "^K"
+#define KEY_TOGGLE_DOT1 "^R"
+#define KEY_TOGGLE_DOT2 "^E"
+#define KEY_TOGGLE_DOT3 "^W"
+#define KEY_TOGGLE_DOT4 "^U"
+#define KEY_TOGGLE_DOT5 "^I"
+#define KEY_TOGGLE_DOT6 "^O"
+#define KEY_TOGGLE_DOT7 "^Q"
+#define KEY_TOGGLE_DOT8 "^P"
+#define KEY_TOGGLE_CHARACTER "^T"
+#define KEY_ALTERNATE_CHARACTER "^Y"
+#define KEY_SAVE_TABLE "^A"
+#define KEY_EXIT_EDITOR "^Z"
+#endif /* key definitions */
+
+    {
+      const char *first = "first";
+      const char *last = "last";
+      const char *previous = "prev";
+      const char *next = "next";
+
+      const char *actual = etd->charset? etd->charset: "unicode";
+      const char *defined = "defined";
+
+      const char *character = "char";
+      const int width = 38;
+
+      printNavigationPair(KEY_PREVIOUS_ACTUAL_CHARACTER, previous,
+                          KEY_NEXT_ACTUAL_CHARACTER, next,
+                          actual, character, width);
+      printNavigationPair(KEY_PREVIOUS_DEFINED_CHARACTER, previous,
+                          KEY_NEXT_DEFINED_CHARACTER, next,
+                          defined, character, 0);
+      printw("\n");
+
+      printNavigationPair(KEY_FIRST_ACTUAL_CHARACTER, first,
+                          KEY_LAST_ACTUAL_CHARACTER, last,
+                          actual, character, width);
+      printNavigationPair(KEY_FIRST_DEFINED_CHARACTER, first,
+                          KEY_LAST_DEFINED_CHARACTER, last,
+                          defined, character, 0);
+      printw("\n");
+    }
+    printw("\n");
+
+#define DOT(n) printw("%s: %s dot %u    ", KEY_TOGGLE_DOT##n, ((dots & BRL_DOT_##n)? "lower": "raise"), n)
+    DOT(1);
+    DOT(4);
+    printw("%s: %s", KEY_TOGGLE_CHARACTER,
+           !gotCharacter? "":
+           !gotDots? "define character (empty cell)":
+           dots? "clear all dots":
+           "undefine character");
+    printw("\n");
+
+    DOT(2);
+    DOT(5);
+    printw("%s:", KEY_ALTERNATE_CHARACTER);
+    {
+      const char *lower = "lowercase";
+      const char *upper = "uppercase";
+      const char *alternate = NULL;
+
+      if (etd->charset) {
+        if (isupper(etd->character.byte)) {
+          alternate = lower;
+        } else if (islower(etd->character.byte)) {
+          alternate = upper;
+        }
+      } else {
+        if (iswupper(etd->character.unicode)) {
+          alternate = lower;
+        } else if (iswlower(etd->character.unicode)) {
+          alternate = upper;
+        }
+      }
+
+      if (alternate) printw(" switch to %s equivalent", alternate);
+    }
+    printw("\n");
+
+    DOT(3);
+    DOT(6);
+    printw("%s: %s", KEY_SAVE_TABLE,
+           etd->updated? "save table": "");
+    printw("\n");
+
+    DOT(7);
+    DOT(8);
+    printw("%s: exit table editor", KEY_EXIT_EDITOR);
+    if (etd->updated) printw(" (unsaved changes)");
+    printw("\n");
+#undef DOT
+
+    printw("\n");
+
+    if (etd->charset) {
+      printw("%02X: %s\n", etd->character.byte, etd->charset);
+    }
+
+    printCharacterString(description);
+    printw("\n");
+
+#define DOT(n) ((dots & BRL_DOT_##n)? '#': ' ')
+    printw(" +---+ \n");
+    printw("1|%c %c|4\n", DOT(1), DOT(4));
+    printw("2|%c %c|5\n", DOT(2), DOT(5));
+    printw("3|%c %c|6\n", DOT(3), DOT(6));
+    printw("7|%c %c|8\n", DOT(7), DOT(8));
+    printw(" +---+ \n");
+#undef DOT
+
+#ifdef ENABLE_API
+    if (etd->brlapiErrorFunction) {
+      printw("BrlAPI error: %s: %s\n",
+             etd->brlapiErrorFunction, etd->brlapiErrorMessage);
+      setBrlapiError(etd, NULL);
+    }
+#endif /* ENABLE_API */
+
+    refresh();
+
+#ifdef ENABLE_API
+    if (haveBrailleDisplay(etd)) {
+      brlapi_writeArguments_t args = BRLAPI_WRITEARGUMENTS_INITIALIZER;
+      wchar_t text[etd->displayWidth];
+
+      {
+        size_t count = MIN(etd->displayWidth, length);
+        wmemcpy(text, description, count);
+        wmemset(&text[count], WC_C(' '), etd->displayWidth-count);
+      }
+
+      args.regionBegin = 1;
+      args.regionSize = etd->displayWidth;
+      args.text = (char *)text;
+      args.textSize = etd->displayWidth * sizeof(text[0]);
+      args.charset = "WCHAR_T";
+
+      if (brlapi_write(&args) == -1) {
+        setBrlapiError(etd, "brlapi_write");
+        releaseBrailleDisplay(etd);
+      }
+    }
+#endif /* ENABLE_API */
+
+    free(description);
+  }
+
+  return ok;
+}
+
+static void
+setPreviousActualCharacter (EditTableData *etd) {
+  if (etd->charset) {
+    etd->character.byte = (etd->character.byte - 1) & CHARSET_BYTE_MAXIMUM;
+  } else {
+    etd->character.unicode = (etd->character.unicode - 1) & WCHAR_MAX;
+  }
+}
+
+static void
+setNextActualCharacter (EditTableData *etd) {
+  if (etd->charset) {
+    etd->character.byte = (etd->character.byte + 1) & CHARSET_BYTE_MAXIMUM;
+  } else {
+    etd->character.unicode = (etd->character.unicode + 1) & WCHAR_MAX;
+  }
+}
+
+static void
+setFirstActualCharacter (EditTableData *etd) {
+  if (etd->charset) {
+    etd->character.byte = 0;
+  } else {
+    etd->character.unicode = 0;
+  }
+}
+
+static void
+setLastActualCharacter (EditTableData *etd) {
+  if (etd->charset) {
+    etd->character.byte = CHARSET_BYTE_MAXIMUM;
+  } else {
+    etd->character.unicode = WCHAR_MAX;
+  }
+}
+
+static int
+findCharacter (EditTableData *etd, int backward) {
+  const int increment = backward? -1: 1;
+
+  if (etd->charset) {
+    const int byteLimit = backward? 0: CHARSET_BYTE_MAXIMUM;
+    const int byteReset = CHARSET_BYTE_MAXIMUM - byteLimit - increment;
+
+    unsigned char byte = etd->character.byte;
+    int counter = CHARSET_BYTE_COUNT;
+
+    do {
+      if (byte == byteLimit) byte = byteReset;
+      byte += increment;
+
+      {
+        wint_t wc = convertCharToWchar(byte);
+
+        if (wc != WEOF) {
+          if (getUnicodeCell(etd->ttd, wc)) {
+            etd->character.byte = byte;
+            return 1;
+          }
+        }       
+      }
+    } while ((counter -= 1) >= 0);
+  } else {
+    const int groupLimit = backward? 0: UNICODE_GROUP_MAXIMUM;
+    const int planeLimit = backward? 0: UNICODE_PLANE_MAXIMUM;
+    const int rowLimit = backward? 0: UNICODE_ROW_MAXIMUM;
+    const int cellLimit = backward? 0: UNICODE_CELL_MAXIMUM;
+
+    const int groupReset = UNICODE_GROUP_MAXIMUM - groupLimit;
+    const int planeReset = UNICODE_PLANE_MAXIMUM - planeLimit;
+    const int rowReset = UNICODE_ROW_MAXIMUM - rowLimit;
+    const int cellReset = UNICODE_CELL_MAXIMUM - cellLimit - increment;
+
+    int groupNumber = UNICODE_GROUP_NUMBER(etd->character.unicode);
+    int planeNumber = UNICODE_PLANE_NUMBER(etd->character.unicode);
+    int rowNumber = UNICODE_ROW_NUMBER(etd->character.unicode);
+    int cellNumber = UNICODE_CELL_NUMBER(etd->character.unicode);
+
+    const TextTableHeader *header = getTextTableHeader(etd->ttd);
+    int groupCounter = UNICODE_GROUP_COUNT;
+
+    do {
+      TextTableOffset groupOffset = header->unicodeGroups[groupNumber];
+
+      if (groupOffset) {
+        const UnicodeGroupEntry *group = getTextTableItem(etd->ttd, groupOffset);
+
+        while (1) {
+          TextTableOffset planeOffset = group->planes[planeNumber];
+
+          if (planeOffset) {
+            const UnicodePlaneEntry *plane = getTextTableItem(etd->ttd, planeOffset);
+
+            while (1) {
+              TextTableOffset rowOffset = plane->rows[rowNumber];
+
+              if (rowOffset) {
+                const UnicodeRowEntry *row = getTextTableItem(etd->ttd, rowOffset);
+
+                while (cellNumber != cellLimit) {
+                  cellNumber += increment;
+
+                  if (BITMASK_TEST(row->cellDefined, cellNumber)) {
+                    etd->character.unicode = UNICODE_CHARACTER(groupNumber, planeNumber, rowNumber, cellNumber);
+                    return 1;
+                  }
+                }
+              }
+
+              cellNumber = cellReset;
+
+              if (rowNumber == rowLimit) break;
+              rowNumber += increment;
+            }
+          }
+
+          rowNumber = rowReset;
+          cellNumber = cellReset;
+
+          if (planeNumber == planeLimit) break;
+          planeNumber += increment;
+        }
+      }
+
+      planeNumber = planeReset;
+      rowNumber = rowReset;
+      cellNumber = cellReset;
+
+      if (groupNumber == groupLimit) {
+        groupNumber = groupReset;
+      } else {
+        groupNumber += increment;
+      }
+    } while ((groupCounter -= 1) >= 0);
+  }
+
+  return 0;
+}
+
+static int
+setPreviousDefinedCharacter (EditTableData *etd) {
+  return findCharacter(etd, 1);
+}
+
+static int
+setNextDefinedCharacter (EditTableData *etd) {
+  return findCharacter(etd, 0);
+}
+
+static int
+setFirstDefinedCharacter (EditTableData *etd) {
+  setLastActualCharacter(etd);
+  if (setNextDefinedCharacter(etd)) return 1;
+
+  setFirstActualCharacter(etd);
+  return 0;
+}
+
+static int
+setLastDefinedCharacter (EditTableData *etd) {
+  setFirstActualCharacter(etd);
+  if (setPreviousDefinedCharacter(etd)) return 1;
+
+  setLastActualCharacter(etd);
+  return 0;
+}
+
+static int
+setAlternateCharacter (EditTableData *etd) {
+  if (etd->charset) {
+    if (isalpha(etd->character.byte)) {
+      if (islower(etd->character.byte)) {
+        etd->character.byte = toupper(etd->character.byte);
+        return 1;
+      }
+
+      if (isupper(etd->character.byte)) {
+        etd->character.byte = tolower(etd->character.byte);
+        return 1;
+      }
+    }
+  } else {
+    if (iswalpha(etd->character.unicode)) {
+      if (iswlower(etd->character.unicode)) {
+        etd->character.unicode = towupper(etd->character.unicode);
+        return 1;
+      }
+
+      if (iswupper(etd->character.unicode)) {
+        etd->character.unicode = towlower(etd->character.unicode);
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+toggleCharacter (EditTableData *etd) {
+  wchar_t character;
+  if (!getCharacter(etd, &character)) return 0;
+
+  {
+    const unsigned char *cell = getUnicodeCell(etd->ttd, character);
+    if (cell && !*cell) {
+      unsetTextTableCharacter(etd->ttd, character);
+    } else if (!setTextTableCharacter(etd->ttd, character, 0)) {
+      return 0;
+    }
+  }
+
+  etd->updated = 1;
+  return 1;
+}
+
+static int
+toggleDot (EditTableData *etd, unsigned char dot) {
+  wchar_t character;
+
+  if (getCharacter(etd, &character)) {
+    const unsigned char *cell = getUnicodeCell(etd->ttd, character);
+    unsigned char dots = cell? *cell: 0;
+
+    if (setTextTableCharacter(etd->ttd, character, dots^dot)) {
+      etd->updated = 1;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+setDots (EditTableData *etd, unsigned char dots) {
+  wchar_t character;
+
+  if (getCharacter(etd, &character)) {
+    if (setTextTableCharacter(etd->ttd, character, dots)) {
+      etd->updated = 1;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+saveTable (EditTableData *etd) {
+  int ok = 0;
+  FILE *outputFile;
+
+  if (!outputPath) outputPath = inputPath;
+  if (!outputFormat) outputFormat = inputFormat;
+
+  if ((outputFile = openOutputTable(&outputPath))) {
+    if (outputFormat->write(outputPath, outputFile, etd->ttd, outputFormat->data)) {
+      ok = 1;
+      etd->updated = 0;
+    }
+
+    fclose(outputFile);
+  }
+
+  return ok;
+}
+
+static int
+doKeyboardCommand (EditTableData *etd) {
+#undef IS_UNICODE_CHARACTER
+#if defined(GOT_CURSES)
+#ifdef GOT_CURSES_WCH
+#define IS_UNICODE_CHARACTER
+  wint_t ch;
+  int ret = get_wch(&ch);
+
+  if (ret == KEY_CODE_YES)
+#else /* GOT_CURSES_WCH */
+  int ch = getch();
+
+  if (ch >= 0X100)
+#endif /* GOT_CURSES_WCH */
+  {
+    switch (ch) {
+      case KEY_LEFT:
+        setPreviousActualCharacter(etd);
+        break;
+
+      case KEY_RIGHT:
+        setNextActualCharacter(etd);
+        break;
+
+      case KEY_UP:
+        if (!setPreviousDefinedCharacter(etd)) beep();
+        break;
+
+      case KEY_DOWN:
+        if (!setNextDefinedCharacter(etd)) beep();
+        break;
+
+      case KEY_HOME:
+        if (!setFirstDefinedCharacter(etd)) beep();
+        break;
+
+      case KEY_END:
+        if (!setLastDefinedCharacter(etd)) beep();
+        break;
+
+      case KEY_F(1):
+        if (!toggleDot(etd, BRL_DOT_7)) beep();
+        break;
+
+      case KEY_F(2):
+        if (!toggleDot(etd, BRL_DOT_3)) beep();
+        break;
+
+      case KEY_F(3):
+        if (!toggleDot(etd, BRL_DOT_2)) beep();
+        break;
+
+      case KEY_F(4):
+        if (!toggleDot(etd, BRL_DOT_1)) beep();
+        break;
+
+      case KEY_F(5):
+        if (!toggleDot(etd, BRL_DOT_4)) beep();
+        break;
+
+      case KEY_F(6):
+        if (!toggleDot(etd, BRL_DOT_5)) beep();
+        break;
+
+      case KEY_F(7):
+        if (!toggleDot(etd, BRL_DOT_6)) beep();
+        break;
+
+      case KEY_F(8):
+        if (!toggleDot(etd, BRL_DOT_8)) beep();
+        break;
+
+      case KEY_F(9):
+        if (!toggleCharacter(etd)) beep();
+        break;
+
+      case KEY_F(10):
+        if (!setAlternateCharacter(etd)) beep();
+        break;
+
+      case KEY_F(11):
+        if (!(etd->updated && saveTable(etd))) beep();
+        break;
+
+      case KEY_F(12):
+        return 0;
+
+      default:
+        beep();
+        break;
+    }
+  } else
+
+#ifdef GOT_CURSES_WCH
+  if (ret == OK)
+#endif /* GOT_CURSES_WCH */
+#else /* standard input/output */
+  int handled = 1;
+
+#ifdef __MSDOS__
+  int ch = fgetc(stdin);
+  if (ch == EOF) return 0;
+#else /* __MSDOS__ */
+#define IS_UNICODE_CHARACTER
+  wint_t ch = fgetwc(stdin);
+  if (ch == WEOF) return 0;
+#endif /* __MSDOS__ */
+
+  switch (ch) {
+    case 0X1B: /* escape */
+      return 0;
+
+    case 0X11: /* CTRL-Q */
+      if (!toggleDot(etd, BRL_DOT_7)) beep();
+      break;
+
+    case 0X17: /* CTRL-W */
+      if (!toggleDot(etd, BRL_DOT_3)) beep();
+      break;
+
+    case 0X05: /* CTRL-E */
+      if (!toggleDot(etd, BRL_DOT_2)) beep();
+      break;
+
+    case 0X12: /* CTRL-R */
+      if (!toggleDot(etd, BRL_DOT_1)) beep();
+      break;
+
+    case 0X14: /* CTRL-T */
+      if (!toggleCharacter(etd)) beep();
+      break;
+
+    case 0X19: /* CTRL-Y */
+      if (!setAlternateCharacter(etd)) beep();
+      break;
+
+    case 0X15: /* CTRL-U */
+      if (!toggleDot(etd, BRL_DOT_4)) beep();
+      break;
+
+    case 0X09: /* CTRL-I */
+      if (!toggleDot(etd, BRL_DOT_5)) beep();
+      break;
+
+    case 0X0F: /* CTRL-O */
+      if (!toggleDot(etd, BRL_DOT_6)) beep();
+      break;
+
+    case 0X10: /* CTRL-P */
+      if (!toggleDot(etd, BRL_DOT_8)) beep();
+      break;
+
+    case 0X01: /* CTRL-A */
+      if (!(etd->updated && saveTable(etd))) beep();
+      break;
+
+    case 0X13: /* CTRL-S */
+      setFirstActualCharacter(etd);
+      break;
+
+    case 0X04: /* CTRL-D */
+      setPreviousActualCharacter(etd);
+      break;
+
+    case 0X06: /* CTRL-F */
+      setNextActualCharacter(etd);
+      break;
+
+    case 0X07: /* CTRL-G */
+      setLastActualCharacter(etd);
+      break;
+
+    case 0X08: /* CTRL-H */
+      if (!setFirstDefinedCharacter(etd)) beep();
+      break;
+
+    case 0X0A: /* CTRL-J */
+      if (!setPreviousDefinedCharacter(etd)) beep();
+      break;
+
+    case 0X0B: /* CTRL-K */
+      if (!setNextDefinedCharacter(etd)) beep();
+      break;
+
+    case 0X0C: /* CTRL-L */
+      if (!setLastDefinedCharacter(etd)) beep();
+      break;
+
+    case 0X1A: /* CTRL-Z */
+      return 0;
+
+    case 0X18: /* CTRL-X */
+      beep();
+      break;
+
+    case 0X03: /* CTRL-C */
+      beep();
+      break;
+
+    case 0X16: /* CTRL-V */
+      beep();
+      break;
+
+    case 0X02: /* CTRL-B */
+      beep();
+      break;
+
+    case 0X0E: /* CTRL-N */
+      beep();
+      break;
+
+    case 0X0D: /* CTRL-M */
+      beep();
+      break;
+
+    default:
+      handled = 0;
+      break;
+  }
+
+  if (!handled)
+#endif /* read character */
+
+  {
+    wint_t character;
+
+#ifdef IS_UNICODE_CHARACTER
+    character = ch;
+#else /* IS_UNICODE_CHARACTER */
+    character = convertCharToWchar(ch);
+#endif /* IS_UNICODE_CHARACTER */
+
+    if ((character >= UNICODE_BRAILLE_ROW) &&
+        (character <= (UNICODE_BRAILLE_ROW | UNICODE_CELL_MASK))) {
+      if (!setDots(etd, character & UNICODE_CELL_MASK)) beep();
+    } else {
+      if (etd->charset) {
+        int c;
+
+#ifdef IS_UNICODE_CHARACTER
+        c = convertWcharToChar(ch);
+#else /* IS_UNICODE_CHARACTER */
+        c = ch;
+#endif /* IS_UNICODE_CHARACTER */
+
+        if (c != EOF) {
+          etd->character.byte = c;
+        } else {
+          beep();
+        }
+      } else if (character != WEOF) {
+        etd->character.unicode = character;
+      } else {
+        beep();
+      }
+    }
+  }
+
+  return 1;
+}
+
+#ifndef __MINGW32__
+#ifdef ENABLE_API
+static int
+doBrailleCommand (EditTableData *etd) {
+  if (haveBrailleDisplay(etd)) {
+    brlapi_keyCode_t key;
+    int ret = brlapi_readKey(0, &key);
+
+    if (ret == 1) {
+      unsigned long code = key & BRLAPI_KEY_CODE_MASK;
+
+      switch (key & BRLAPI_KEY_TYPE_MASK) {
+        case BRLAPI_KEY_TYPE_CMD:
+          switch (code & BRLAPI_KEY_CMD_BLK_MASK) {
+            case 0:
+              switch (code) {
+                case BRLAPI_KEY_CMD_FWINLT:
+                  setPreviousActualCharacter(etd);
+                  break;
+
+                case BRLAPI_KEY_CMD_FWINRT:
+                  setNextActualCharacter(etd);
+                  break;
+
+                case BRLAPI_KEY_CMD_LNUP:
+                  if (!setPreviousDefinedCharacter(etd)) beep();
+                  break;
+
+                case BRLAPI_KEY_CMD_LNDN:
+                  if (!setNextDefinedCharacter(etd)) beep();
+                  break;
+
+                case BRLAPI_KEY_CMD_TOP_LEFT:
+                case BRLAPI_KEY_CMD_TOP:
+                  if (!setFirstDefinedCharacter(etd)) beep();
+                  break;
+
+                case BRLAPI_KEY_CMD_BOT_LEFT:
+                case BRLAPI_KEY_CMD_BOT:
+                  if (!setLastDefinedCharacter(etd)) beep();
+                  break;
+
+                default:
+                  beep();
+                  break;
+              }
+              break;
+
+            case BRLAPI_KEY_CMD_PASSDOTS:
+              if (!setDots(etd, code & BRLAPI_KEY_CMD_ARG_MASK)) beep();
+              break;
+
+            default:
+              beep();
+              break;
+          }
+          break;
+
+        case BRLAPI_KEY_TYPE_SYM: {
+          /* latin1 */
+          if (code < 0X100) code |= BRLAPI_KEY_SYM_UNICODE;
+
+          if ((code & 0X1f000000) == BRLAPI_KEY_SYM_UNICODE) {
+            /* unicode */
+            if ((code & 0Xffff00) == UNICODE_BRAILLE_ROW) {
+              /* Set braille pattern */
+              if (!setDots(etd, code & UNICODE_CELL_MASK)) beep();
+            } else {
+              wchar_t character = code & 0XFFFFFF;
+
+              if (etd->charset) {
+                int c = convertWcharToChar(character);
+
+                if (c != EOF) {
+                  etd->character.byte = c;
+                } else {
+                  beep();
+                }
+              } else {
+                etd->character.unicode = character;
+              }
+            }
+          } else {
+            switch (code) {
+              case BRLAPI_KEY_SYM_LEFT:
+                setPreviousActualCharacter(etd);
+                break;
+
+              case BRLAPI_KEY_SYM_RIGHT:
+                setNextActualCharacter(etd);
+                break;
+
+              case BRLAPI_KEY_SYM_UP:
+                if (!setPreviousDefinedCharacter(etd)) beep();
+                break;
+
+              case BRLAPI_KEY_SYM_DOWN:
+                if (!setNextDefinedCharacter(etd)) beep();
+                break;
+
+              case BRLAPI_KEY_SYM_HOME:
+                if (!setFirstDefinedCharacter(etd)) beep();
+                break;
+
+              case BRLAPI_KEY_SYM_END:
+                if (!setLastDefinedCharacter(etd)) beep();
+                break;
+
+              default:
+                beep();
+                break;
+            }
+          }
+          break;
+        }
+
+        default:
+          beep();
+          break;
+      }
+    } else if (ret == -1) {
+      setBrlapiError(etd, "brlapi_readKey");
+      releaseBrailleDisplay(etd);
+    }
+  }
+
+  return 1;
+}
+#endif /* ENABLE_API */
+#endif /* __MINGW32__ */
+
+static ProgramExitStatus
+editTable (void) {
+  ProgramExitStatus exitStatus;
+  EditTableData etd;
+
+  etd.ttd = NULL;
+  etd.updated = 0;
+
+  {
+    FILE *inputFile = openInputTable(&inputPath, 0);
+
+    if (inputFile) {
+      if ((etd.ttd = readTable(inputPath, inputFile, inputFormat))) {
+        exitStatus = PROG_EXIT_SUCCESS;
+      } else {
+        exitStatus = PROG_EXIT_FATAL;
+      }
+
+      fclose(inputFile);
+    } else {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  }
+
+  if (exitStatus == PROG_EXIT_SUCCESS) {
+#ifdef ENABLE_API
+    claimBrailleDisplay(&etd);
+#endif /* ENABLE_API */
+
+#if defined(GOT_CURSES)
+    initscr();
+    cbreak();
+    keypad(stdscr, TRUE);
+    noecho();
+    nonl();
+    intrflush(stdscr, FALSE);
+#else /* standard input/output */
+    setvbuf(stdin, NULL, _IONBF, 0);
+    inputAttributesChanged = 0;
+
+#if defined(__MINGW32__)
+    if (GetConsoleMode(STDIN_HANDLE, &inputConsoleMode)) {
+      DWORD newConsoleMode = inputConsoleMode;
+      newConsoleMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
+
+      if (SetConsoleMode(STDIN_HANDLE, newConsoleMode)) {
+        inputAttributesChanged = 1;
+      }
+    }
+#else /* termios */
+    if (tcgetattr(STDIN_FILENO, &inputTerminalAttributes) != -1) {
+      struct termios newAttributes = inputTerminalAttributes;
+
+      newAttributes.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+      newAttributes.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+      newAttributes.c_cflag &= ~(CSIZE | PARENB);
+      newAttributes.c_cflag |= CS8;
+
+      {
+        for (int i=0; i<NCCS; i+=1) newAttributes.c_cc[i] = _POSIX_VDISABLE;
+      }
+
+      newAttributes.c_cc[VTIME] = 0;
+      newAttributes.c_cc[VMIN] = 1;
+
+      if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &newAttributes) != -1) {
+        inputAttributesChanged = 1;
+      };
+    }
+#endif /* input terminal initialization */
+#endif /* initialize keyboard and screen */
+
+    etd.charset = *opt_charset? opt_charset: NULL;
+    setFirstDefinedCharacter(&etd);
+
+    while (updateCharacterDescription(&etd)) {
+      fd_set set;
+      FD_ZERO(&set);
+
+#ifndef __MINGW32__
+      {
+        int maximumFileDescriptor = STDIN_FILENO;
+        FD_SET(STDIN_FILENO, &set);
+
+#ifdef ENABLE_API
+        if (haveBrailleDisplay(&etd)) {
+          FD_SET(etd.brlapiFileDescriptor, &set);
+
+          if (etd.brlapiFileDescriptor > maximumFileDescriptor) {
+            maximumFileDescriptor = etd.brlapiFileDescriptor;
+          }
+        }
+#endif /* ENABLE_API */
+
+        select(maximumFileDescriptor+1, &set, NULL, NULL, NULL);
+      }
+
+#ifdef ENABLE_API
+      if (haveBrailleDisplay(&etd) && FD_ISSET(etd.brlapiFileDescriptor, &set)) {
+        if (!doBrailleCommand(&etd)) break;
+      }
+#endif /* ENABLE_API */
+
+      if (FD_ISSET(STDIN_FILENO, &set))
+#endif /* __MINGW32__ */
+      {
+        if (!doKeyboardCommand(&etd)) break;
+      }
+    }
+
+    erase();
+    refresh();
+
+#if defined(GOT_CURSES)
+    endwin();
+#else /* standard input/output */
+    if (inputAttributesChanged) {
+#if defined(__MINGW32__)
+      SetConsoleMode(STDIN_HANDLE, inputConsoleMode);
+#else /* termios */
+      tcsetattr(STDIN_FILENO, TCSAFLUSH, &inputTerminalAttributes);
+#endif /* input terminal restoration */
+    }
+#endif /* restore keyboard and screen */
+
+#ifdef ENABLE_API
+    if (haveBrailleDisplay(&etd)) releaseBrailleDisplay(&etd);
+#endif /* ENABLE_API */
+
+    if (etd.ttd) destroyTextTableData(etd.ttd);
+  }
+
+  return exitStatus;
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-ttb",
+
+      .usage = {
+        .purpose = strtext("Check/edit a text (computer braille) table, or convert it from one format to another."),
+        .parameters = "input-table [output-table]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (argc == 0) {
+    logMessage(LOG_ERR, "missing input table");
+    return PROG_EXIT_SYNTAX;
+  }
+  inputPath = *argv++, argc--;
+
+  if (argc > 0) {
+    outputPath = *argv++, argc--;
+  } else if (opt_outputFormat && *opt_outputFormat) {
+    const char *extension = locatePathExtension(inputPath);
+    int prefix = extension? (extension - inputPath): strlen(inputPath);
+    char buffer[prefix + 1 + strlen(opt_outputFormat) + 1];
+
+    snprintf(buffer, sizeof(buffer), "%.*s.%s", prefix, inputPath, opt_outputFormat);
+
+    if (!(outputPath = strdup(buffer))) {
+      logMallocError();
+      return PROG_EXIT_FATAL;
+    }
+  } else {
+    outputPath = NULL;
+  }
+
+  if (argc > 0) {
+    logMessage(LOG_ERR, "too many parameters");
+    return PROG_EXIT_SYNTAX;
+  }
+
+  inputFormat = getFormatEntry(opt_inputFormat, inputPath, "input");
+
+  if (outputPath) {
+    outputFormat = getFormatEntry(opt_outputFormat, outputPath, "output");
+  } else {
+    outputFormat = NULL;
+  }
+
+  if (*opt_charset && !setCharset(opt_charset)) {
+    logMessage(LOG_ERR, "cannot establish character set: %s", opt_charset);
+    return PROG_EXIT_SEMANTIC;
+  }
+
+  if (opt_edit) {
+    exitStatus = editTable();
+  } else {
+    exitStatus = convertTable();
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/brltty-tune.c b/Programs/brltty-tune.c
new file mode 100644
index 0000000..5fbc971
--- /dev/null
+++ b/Programs/brltty-tune.c
@@ -0,0 +1,236 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "cmdline.h"
+#include "prefs.h"
+#include "tune_utils.h"
+#include "tune_builder.h"
+#include "notes.h"
+#include "datafile.h"
+
+static int opt_fromFiles;
+static char *opt_outputVolume;
+static char *opt_tuneDevice;
+
+#ifdef HAVE_MIDI_SUPPORT
+static char *opt_midiInstrument;
+#endif /* HAVE_MIDI_SUPPORT */
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "files",
+    .letter = 'f',
+    .setting.flag = &opt_fromFiles,
+    .description = "Use files rather than command line arguments."
+  },
+
+  { .word = "volume",
+    .letter = 'v',
+    .argument = "loudness",
+    .setting.string = &opt_outputVolume,
+    .description = "Output volume (percentage)."
+  },
+
+  { .word = "device",
+    .letter = 'd',
+    .argument = "device",
+    .setting.string = &opt_tuneDevice,
+    .description = "Name of tune device."
+  },
+
+#ifdef HAVE_PCM_SUPPORT
+  { .word = "pcm-device",
+    .letter = 'p',
+    .argument = "device",
+    .setting.string = &opt_pcmDevice,
+    .description = "Device specifier for soundcard digital audio."
+  },
+#endif /* HAVE_PCM_SUPPORT */
+
+#ifdef HAVE_MIDI_SUPPORT
+  { .word = "midi-device",
+    .letter = 'm',
+    .argument = "device",
+    .setting.string = &opt_midiDevice,
+    .description = "Device specifier for the Musical Instrument Digital Interface."
+  },
+
+  { .word = "instrument",
+    .letter = 'i',
+    .argument = "instrument",
+    .setting.string = &opt_midiInstrument,
+    .description = "Name of MIDI instrument."
+  },
+#endif /* HAVE_MIDI_SUPPORT */
+END_OPTION_TABLE(programOptions)
+
+static
+BEGIN_USAGE_NOTES(usageNotes)
+  "If the tune is specified on the command line then each argument contains a command group.",
+  "If it's read from a file then each line contains a command group.",
+  "Each specified file contains a different tune.",
+END_USAGE_NOTES
+
+static void
+beginTuneStream (const char *name, void *data) {
+  TuneBuilder *tb = data;
+  resetTuneBuilder(tb);
+  setTuneSourceName(tb, name);
+}
+
+static void
+playTune (TuneBuilder *tb) {
+  ToneElement *tune = getTune(tb);
+
+  if (tune) {
+    tunePlayTones(tune);
+    tuneSynchronize();
+    free(tune);
+  }
+}
+
+static void
+endTuneStream (int incomplete, void *data) {
+  if (!incomplete) {
+    TuneBuilder *tb = data;
+    playTune(tb);
+  }
+}
+
+static
+DATA_OPERANDS_PROCESSOR(processTuneOperands) {
+  DataOperand line;
+
+  if (getTextOperand(file, &line, NULL)) {
+    DataString text;
+
+    if (parseDataString(file, &text, line.characters, line.length, 0)) {
+      return parseTuneText(data, text.characters);
+    }
+  }
+
+  return 1;
+}
+
+static
+DATA_OPERANDS_PROCESSOR(processTuneLine) {
+  TuneBuilder *tb = data;
+  incrementTuneSourceIndex(tb);
+
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    DATA_VARIABLE_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    {.name=NULL, .processor=processTuneOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "tune file directive", tb);
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "brltty-tune",
+
+      .usage = {
+        .purpose = strtext("Compose a tune with the tune builder and play it with the tone generator."),
+        .parameters = "commands ... | -f [{file | -} ...]",
+        .notes = USAGE_NOTES(usageNotes, tuneBuilderUsageNotes),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  resetPreferences();
+  if (!parseTuneDevice(opt_tuneDevice)) return PROG_EXIT_SYNTAX;
+  if (!parseTuneVolume(opt_outputVolume)) return PROG_EXIT_SYNTAX;
+
+#ifdef HAVE_MIDI_SUPPORT
+  if (!parseTuneInstrument(opt_midiInstrument)) return PROG_EXIT_SYNTAX;
+#endif /* HAVE_MIDI_SUPPORT */
+
+  if (!setTuneDevice()) return PROG_EXIT_SEMANTIC;
+  ProgramExitStatus exitStatus = PROG_EXIT_FATAL;
+  TuneBuilder *tb = newTuneBuilder();
+
+  if (tb) {
+    if (opt_fromFiles) {
+      const InputFilesProcessingParameters parameters = {
+        .beginStream = beginTuneStream,
+        .endStream = endTuneStream,
+
+        .dataFileParameters = {
+          .processOperands = processTuneLine,
+          .data = tb
+        }
+      };
+
+      exitStatus = processInputFiles(argv, argc, &parameters);
+    } else if (argc) {
+      exitStatus = PROG_EXIT_SUCCESS;
+      setTuneSourceName(tb, "<command-line>");
+
+      do {
+        incrementTuneSourceIndex(tb);
+        if (!parseTuneString(tb, *argv)) break;
+        argv += 1;
+      } while (argc -= 1);
+
+      playTune(tb);
+    } else {
+      logMessage(LOG_ERR, "missing tune");
+      exitStatus = PROG_EXIT_SYNTAX;
+    }
+
+    if (exitStatus == PROG_EXIT_SUCCESS) {
+      switch (getTuneStatus(tb)) {
+        case TUNE_STATUS_OK:
+          exitStatus = PROG_EXIT_SUCCESS;
+          break;
+
+        case TUNE_STATUS_SYNTAX:
+          exitStatus = PROG_EXIT_SYNTAX;
+          break;
+
+        case TUNE_STATUS_FATAL:
+          exitStatus = PROG_EXIT_FATAL;
+          break;
+      }
+    } else if (exitStatus == PROG_EXIT_FORCE) {
+      exitStatus = PROG_EXIT_SUCCESS;
+    }
+
+    destroyTuneBuilder(tb);
+  }
+
+  return exitStatus;
+}
+
+#include "alert.h"
+
+void
+alert (AlertIdentifier identifier) {
+}
diff --git a/Programs/brltty.c b/Programs/brltty.c
new file mode 100644
index 0000000..e6b8e71
--- /dev/null
+++ b/Programs/brltty.c
@@ -0,0 +1,263 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "embed.h"
+#include "log.h"
+#include "api_control.h"
+#include "core.h"
+
+static ProgramExitStatus
+brlttyRun (void) {
+  while (brlttyWait(INT_MAX));
+  return PROG_EXIT_SUCCESS;
+}
+
+#ifdef __MINGW32__
+#include "utf8.h"
+
+static SERVICE_STATUS_HANDLE serviceStatusHandle;
+static DWORD serviceState;
+static ProgramExitStatus serviceExitStatus;
+
+static BOOL
+setServiceState (DWORD state, DWORD exitStatus, const char *name) {
+  SERVICE_STATUS status = {
+    .dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+    .dwCurrentState = state,
+
+    .dwWin32ExitCode = (exitStatus != PROG_EXIT_SUCCESS)? ERROR_SERVICE_SPECIFIC_ERROR: NO_ERROR,
+    .dwServiceSpecificExitCode = exitStatus
+  };
+
+  switch (status.dwCurrentState) {
+    default:
+      status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
+
+    case SERVICE_START_PENDING:
+    case SERVICE_STOP_PENDING:
+    case SERVICE_STOPPED:
+      break;
+  }
+
+  switch (status.dwCurrentState) {
+    case SERVICE_START_PENDING:
+    case SERVICE_PAUSE_PENDING:
+    case SERVICE_CONTINUE_PENDING:
+    case SERVICE_STOP_PENDING:
+      status.dwWaitHint = 10000;
+      status.dwCheckPoint = 0;
+
+    default:
+      break;
+  }
+
+  serviceState = state;
+  if (SetServiceStatus(serviceStatusHandle, &status)) return 1;
+
+  logWindowsSystemError(name);
+  return 0;
+}
+#define SET_SERVICE_STATE(state,code) setServiceState(state, code, #state)
+
+static void WINAPI
+serviceControlHandler (DWORD code) {
+  switch (code) {
+    case SERVICE_CONTROL_STOP:
+      SET_SERVICE_STATE(SERVICE_STOP_PENDING, PROG_EXIT_SUCCESS);
+      raise(SIGTERM);
+      break;
+
+    case SERVICE_CONTROL_PAUSE:
+      SET_SERVICE_STATE(SERVICE_PAUSE_PENDING, PROG_EXIT_SUCCESS);
+      api.suspendDriver();
+      SET_SERVICE_STATE(SERVICE_PAUSED, PROG_EXIT_SUCCESS);
+      break;
+
+    case SERVICE_CONTROL_CONTINUE:
+      SET_SERVICE_STATE(SERVICE_CONTINUE_PENDING, PROG_EXIT_SUCCESS);
+      if (api.resumeDriver()) {
+        SET_SERVICE_STATE(SERVICE_RUNNING, PROG_EXIT_SUCCESS);
+      } else {
+        SET_SERVICE_STATE(SERVICE_PAUSED, PROG_EXIT_SUCCESS);
+      }
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unexpected service control code: %lu", code);
+      break;
+  }
+}
+
+static void
+exitService (void) {
+  SET_SERVICE_STATE(SERVICE_STOPPED, PROG_EXIT_SUCCESS);
+}
+
+static char **
+getCommandLineArguments (DWORD *argc) {
+  int count;
+  LPWSTR *arguments = CommandLineToArgvW(GetCommandLineW(), &count);
+  char **argv;
+
+  if ((argv = malloc(ARRAY_SIZE(argv, count+1)))) {
+    unsigned int index = 0;
+
+    while (index < count) {
+      const wchar_t *argument = arguments[index];
+
+      if (!(argv[index] = getUtf8FromWchars(argument, wcslen(argument), NULL))) {
+        logMallocError();
+        break;
+      }
+
+      index += 1;
+    }
+
+    LocalFree(arguments);
+    arguments = NULL;
+
+    if (index == count) {
+      argv[count] = NULL;
+      *argc = count;
+      return argv;
+    }
+
+    while (index > 0) free(argv[index-=1]);
+    free(argv);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void WINAPI
+serviceMain (DWORD argc, LPSTR *argv) {
+  atexit(exitService);
+
+  if ((argv = getCommandLineArguments(&argc))) {
+    if ((serviceStatusHandle = RegisterServiceCtrlHandler("", serviceControlHandler))) {
+      if ((SET_SERVICE_STATE(SERVICE_START_PENDING, PROG_EXIT_SUCCESS))) {
+        if ((serviceExitStatus = brlttyConstruct(argc, argv)) == PROG_EXIT_SUCCESS) {
+          if ((SET_SERVICE_STATE(SERVICE_RUNNING, PROG_EXIT_SUCCESS))) {
+            serviceExitStatus = brlttyRun();
+          } else {
+            serviceExitStatus = PROG_EXIT_FATAL;
+          }
+
+          brlttyDestruct();
+        } else if (serviceExitStatus == PROG_EXIT_FORCE) {
+          serviceExitStatus = PROG_EXIT_SUCCESS;
+        }
+
+        SET_SERVICE_STATE(SERVICE_STOPPED, serviceExitStatus);
+      }
+    } else {
+      logWindowsSystemError("RegisterServiceCtrlHandler");
+    }
+  }
+}
+#endif /* __MINGW32__ */
+
+int
+main (int argc, char *argv[]) {
+#ifdef __MINGW32__
+  {
+    static SERVICE_TABLE_ENTRY serviceTable[] = {
+      { .lpServiceName="", .lpServiceProc=serviceMain },
+      {}
+    };
+
+    isWindowsService = 1;
+    if (StartServiceCtrlDispatcher(serviceTable)) return serviceExitStatus;
+    isWindowsService = 0;
+
+    if (GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
+      logWindowsSystemError("StartServiceCtrlDispatcher");
+      return PROG_EXIT_FATAL;
+    }
+  }
+#endif /* __MINGW32__ */
+
+#ifdef INIT_PATH
+#define INIT_NAME "init"
+
+  if ((getpid() == 1) || strstr(argv[0], "linuxrc")) {
+    fprintf(stderr, gettext("\"%s\" started as \"%s\"\n"), PACKAGE_NAME, argv[0]);
+    fflush(stderr);
+
+    switch (fork()) {
+      case -1: /* failed */
+        fprintf(stderr, gettext("fork of \"%s\" failed: %s\n"),
+                PACKAGE_NAME, strerror(errno));
+        fflush(stderr);
+
+      default: /* parent */
+        fprintf(stderr, gettext("executing \"%s\" (from \"%s\")\n"), INIT_NAME, INIT_PATH);
+        fflush(stderr);
+
+      executeInit:
+        execv(INIT_PATH, argv);
+        /* execv() shouldn't return */
+
+        fprintf(stderr, gettext("execution of \"%s\" failed: %s\n"), INIT_NAME, strerror(errno));
+        fflush(stderr);
+        exit(1);
+
+      case 0: { /* child */
+        static char *arguments[] = {"brltty", "-E", "-n", "-e", "-linfo", NULL};
+        argv = arguments;
+        argc = ARRAY_COUNT(arguments) - 1;
+        break;
+      }
+    }
+  } else if (!strstr(argv[0], "brltty")) {
+    /* 
+     * If we are substituting the real init binary, then we may consider
+     * when someone might want to call that binary even when pid != 1.
+     * One example is /sbin/telinit which is a symlink to /sbin/init.
+     */
+    goto executeInit;
+  }
+#endif /* INIT_PATH */
+
+#ifdef STDERR_PATH
+  freopen(STDERR_PATH, "a", stderr);
+#endif /* STDERR_PATH */
+
+  {
+    ProgramExitStatus exitStatus = brlttyConstruct(argc, argv);
+
+    if (exitStatus == PROG_EXIT_SUCCESS) {
+      exitStatus = brlttyRun();
+      brlttyDestruct();
+    } else if (exitStatus == PROG_EXIT_FORCE) {
+      exitStatus = PROG_EXIT_SUCCESS;
+    }
+
+    return exitStatus;
+  }
+}
diff --git a/Programs/brltty.java b/Programs/brltty.java
new file mode 100644
index 0000000..3795872
--- /dev/null
+++ b/Programs/brltty.java
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+// package org.a11y.BRLTTY;
+
+public class brltty {
+  public enum ProgramExitStatus {
+    SUCCESS  (0),
+    FORCE    (1),
+    SYNTAX   (2),
+    SEMANTIC (3),
+    FATAL    (4);
+
+    public final int value;
+
+    ProgramExitStatus (int value) {
+      this.value = value;
+    }
+  }
+
+  public static native int construct (String[] arguments);
+  public static native boolean update ();
+  public static native void destruct ();
+
+  public static void main (String[] arguments) {
+    int exitStatus = construct(arguments);
+
+    if (exitStatus == ProgramExitStatus.SUCCESS.value) {
+      while (update()) {
+      }
+    } else if (exitStatus == ProgramExitStatus.FORCE.value) {
+      exitStatus = ProgramExitStatus.SUCCESS.value;
+    }
+
+    destruct();
+    System.exit(exitStatus);
+  }
+
+  static {
+    System.loadLibrary("brltty_jni");
+  }
+}
diff --git a/Programs/brltty_jni.c b/Programs/brltty_jni.c
new file mode 100644
index 0000000..6105412
--- /dev/null
+++ b/Programs/brltty_jni.c
@@ -0,0 +1,503 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <jni.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "embed.h"
+#include "system_java.h"
+
+#if defined(__ANDROID__)
+#include <android/log.h>
+#define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, PACKAGE_TARNAME, __VA_ARGS__)
+
+#else /* LOG() */
+#warning logging not supported
+#define LOG(...)
+#endif /* LOG() */
+
+SYMBOL_POINTER(brlttyConstruct);
+SYMBOL_POINTER(setJavaClassLoader);
+SYMBOL_POINTER(brlttyDestruct);
+
+SYMBOL_POINTER(brlttyEnableInterrupt);
+SYMBOL_POINTER(brlttyDisableInterrupt);
+
+SYMBOL_POINTER(brlttyInterrupt);
+SYMBOL_POINTER(brlttyWait);
+
+SYMBOL_POINTER(changeLogLevel);
+SYMBOL_POINTER(changeLogCategories);
+
+SYMBOL_POINTER(changeTextTable);
+SYMBOL_POINTER(changeAttributesTable);
+SYMBOL_POINTER(changeContractionTable);
+SYMBOL_POINTER(changeKeyboardTable);
+
+SYMBOL_POINTER(restartBrailleDriver);
+SYMBOL_POINTER(changeBrailleDriver);
+SYMBOL_POINTER(changeBrailleParameters);
+SYMBOL_POINTER(changeBrailleDevice);
+
+SYMBOL_POINTER(restartSpeechDriver);
+SYMBOL_POINTER(changeSpeechDriver);
+SYMBOL_POINTER(changeSpeechParameters);
+
+SYMBOL_POINTER(restartScreenDriver);
+SYMBOL_POINTER(changeScreenDriver);
+SYMBOL_POINTER(changeScreenParameters);
+
+SYMBOL_POINTER(changeMessageLocale);
+SYMBOL_POINTER(showMessage);
+
+typedef struct {
+  const char *name;
+  void *pointer;
+} SymbolEntry;
+
+#define BEGIN_SYMBOL_TABLE static const SymbolEntry symbolTable[] = {
+#define END_SYMBOL_TABLE {.name=NULL} };
+#define SYMBOL_ENTRY(symbol) {.name=#symbol, .pointer=&symbol##_p}
+
+BEGIN_SYMBOL_TABLE
+  SYMBOL_ENTRY(brlttyConstruct),
+  SYMBOL_ENTRY(setJavaClassLoader),
+  SYMBOL_ENTRY(brlttyDestruct),
+
+  SYMBOL_ENTRY(brlttyEnableInterrupt),
+  SYMBOL_ENTRY(brlttyDisableInterrupt),
+
+  SYMBOL_ENTRY(brlttyInterrupt),
+  SYMBOL_ENTRY(brlttyWait),
+
+  SYMBOL_ENTRY(changeLogLevel),
+  SYMBOL_ENTRY(changeLogCategories),
+
+  SYMBOL_ENTRY(changeTextTable),
+  SYMBOL_ENTRY(changeAttributesTable),
+  SYMBOL_ENTRY(changeContractionTable),
+  SYMBOL_ENTRY(changeKeyboardTable),
+
+  SYMBOL_ENTRY(restartBrailleDriver),
+  SYMBOL_ENTRY(changeBrailleDriver),
+  SYMBOL_ENTRY(changeBrailleParameters),
+  SYMBOL_ENTRY(changeBrailleDevice),
+
+  SYMBOL_ENTRY(restartSpeechDriver),
+  SYMBOL_ENTRY(changeSpeechDriver),
+  SYMBOL_ENTRY(changeSpeechParameters),
+
+  SYMBOL_ENTRY(restartScreenDriver),
+  SYMBOL_ENTRY(changeScreenDriver),
+  SYMBOL_ENTRY(changeScreenParameters),
+
+  SYMBOL_ENTRY(changeMessageLocale),
+  SYMBOL_ENTRY(showMessage),
+END_SYMBOL_TABLE
+
+static void *coreHandle = NULL;
+
+static jobject jArgumentArray = NULL;
+static const char **cArgumentArray = NULL;
+static int cArgumentCount;
+
+static void reportProblem (
+  JNIEnv *env, const char *throwable,
+  const char *format, ...
+) PRINTF(3, 4);
+
+static void
+reportProblem (
+  JNIEnv *env, const char *throwable,
+  const char *format, ...
+) {
+  char message[0X100];
+
+  {
+    va_list arguments;
+
+    va_start(arguments, format);
+    vsnprintf(message, sizeof(message), format, arguments);
+    va_end(arguments);
+  }
+
+  if (0) {
+    FILE *stream = stderr;
+
+    fprintf(stream, "%s\n", message);
+    fflush(stream);
+  }
+
+  {
+    jclass object = (*env)->FindClass(env, throwable);
+
+    if (object) {
+      (*env)->ThrowNew(env, object, message);
+      (*env)->DeleteLocalRef(env, object);
+    }
+  }
+}
+
+static void
+reportOutOfMemory (JNIEnv *env, const char *description) {
+  reportProblem(env, JAVA_OBJ_OUT_OF_MEMORY_ERROR, "cannot allocate %s", description);
+}
+
+static int
+prepareProgramArguments (JNIEnv *env, jstring arguments) {
+  jsize count = (*env)->GetArrayLength(env, arguments);
+
+  if ((jArgumentArray = (*env)->NewGlobalRef(env, arguments))) {
+    if ((cArgumentArray = malloc((count + 2) * sizeof(*cArgumentArray)))) {
+      cArgumentArray[0] = PACKAGE_TARNAME;
+      cArgumentArray[count+1] = NULL;
+
+      {
+        unsigned int i;
+
+        for (i=1; i<=count; i+=1) cArgumentArray[i] = NULL;
+
+        for (i=1; i<=count; i+=1) {
+          jstring jArgument = (*env)->GetObjectArrayElement(env, arguments, i-1);
+          jboolean isCopy;
+          const char *cArgument = (*env)->GetStringUTFChars(env, jArgument, &isCopy);
+
+          (*env)->DeleteLocalRef(env, jArgument);
+          jArgument = NULL;
+
+          if (!cArgument) {
+            reportOutOfMemory(env, "C argument string");
+            break;
+          }
+
+          cArgumentArray[i] = cArgument;
+        }
+
+        if (i > count) {
+          cArgumentCount = count + 1;
+          return 1;
+        }
+      }
+    } else {
+      reportOutOfMemory(env, "C argument array");
+    }
+  } else {
+    reportOutOfMemory(env, "Java arguments array global reference");
+  }
+
+  return 0;
+}
+
+static int
+loadCoreLibrary (JNIEnv *env) {
+  if (coreHandle) return 1;
+
+  if ((coreHandle = dlopen("libbrltty_core.so", RTLD_NOW | RTLD_GLOBAL))) {
+    int allFound = 1;
+    const SymbolEntry *symbol = symbolTable;
+
+    while (symbol->name) {
+      const void **pointer = symbol->pointer;
+
+      if ((*pointer = dlsym(coreHandle, symbol->name))) {
+        LOG("core symbol: %s -> %p", symbol->name, *pointer);
+      } else {
+        LOG("core symbol not found: %s", symbol->name);
+        allFound = 0;
+      }
+
+      symbol += 1;
+    }
+
+    if (allFound) return 1;
+  }
+
+  reportProblem(env, JAVA_OBJ_UNSATISFIED_LINK_ERROR, "%s", dlerror());
+  return 0;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreConstruct, jint,
+  jobjectArray arguments, jobject classLoader
+) {
+  if (prepareProgramArguments(env, arguments)) {
+    if (loadCoreLibrary(env)) {
+      setJavaClassLoader_p(env, classLoader);
+      return brlttyConstruct_p(cArgumentCount, (char **)cArgumentArray);
+    }
+  }
+
+  return PROG_EXIT_FATAL;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreDestruct, jboolean
+) {
+  jboolean result = brlttyDestruct_p()? JNI_TRUE: JNI_FALSE;
+
+/*
+  {
+    const SymbolEntry *symbol = symbolTable;
+
+    while (symbol->name) {
+      const void **pointer = symbol->pointer;
+      *pointer = NULL;
+      symbol += 1;
+    }
+  }
+
+  if (coreHandle) {
+    dlclose(coreHandle);
+    coreHandle = NULL;
+  }
+*/
+
+  if (jArgumentArray) {
+    if (cArgumentArray) {
+      unsigned int i = 0;
+
+      while (cArgumentArray[++i]) {
+        jstring jArgument = (*env)->GetObjectArrayElement(env, jArgumentArray, i-1);
+        (*env)->ReleaseStringUTFChars(env, jArgument, cArgumentArray[i]);
+        (*env)->DeleteLocalRef(env, jArgument);
+        jArgument = NULL;
+      }
+
+      free(cArgumentArray);
+      cArgumentArray = NULL;
+    }
+
+    (*env)->DeleteGlobalRef(env, jArgumentArray);
+    jArgumentArray = NULL;
+  }
+
+  return result;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreEnableInterrupt, jboolean
+) {
+  return brlttyEnableInterrupt_p()? JNI_TRUE: JNI_FALSE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreDisableInterrupt, jboolean
+) {
+  return brlttyDisableInterrupt_p()? JNI_TRUE: JNI_FALSE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreInterrupt, jboolean,
+  jboolean stop
+) {
+  return brlttyInterrupt_p((stop != JNI_FALSE)? WAIT_STOP: WAIT_CONTINUE)? JNI_TRUE: JNI_FALSE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, coreWait, jboolean,
+  jint duration
+) {
+  return (brlttyWait_p(duration) != WAIT_STOP)? JNI_TRUE: JNI_FALSE;
+}
+
+static jboolean
+changeStringValue (JNIEnv *env, int (*change) (const char *cValue), jstring jValue) {
+  jboolean result = JNI_FALSE;
+  const char *cValue = (*env)->GetStringUTFChars(env, jValue, NULL);
+
+  if (cValue) {
+    if (change(cValue)) result = JNI_TRUE;
+    (*env)->ReleaseStringUTFChars(env, jValue, cValue);
+  } else {
+    reportOutOfMemory(env, "C new value string");
+  }
+
+  return result;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeLogLevel, jboolean,
+  jstring level
+) {
+  return changeStringValue(env, changeLogLevel_p, level);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeLogCategories, jboolean,
+  jstring categories
+) {
+  return changeStringValue(env, changeLogCategories_p, categories);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeTextTable, jboolean,
+  jstring name
+) {
+  return changeStringValue(env, changeTextTable_p, name);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeAttributesTable, jboolean,
+  jstring name
+) {
+  return changeStringValue(env, changeAttributesTable_p, name);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeContractionTable, jboolean,
+  jstring name
+) {
+  return changeStringValue(env, changeContractionTable_p, name);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeKeyboardTable, jboolean,
+  jstring name
+) {
+  return changeStringValue(env, changeKeyboardTable_p, name);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, restartBrailleDriver, jboolean
+) {
+  restartBrailleDriver_p();
+  return JNI_TRUE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeBrailleDriver, jboolean,
+  jstring driver
+) {
+  return changeStringValue(env, changeBrailleDriver_p, driver);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeBrailleParameters, jboolean,
+  jstring parameters
+) {
+  return changeStringValue(env, changeBrailleParameters_p, parameters);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeBrailleDevice, jboolean,
+  jstring device
+) {
+  return changeStringValue(env, changeBrailleDevice_p, device);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, restartSpeechDriver, jboolean
+) {
+  restartSpeechDriver_p();
+  return JNI_TRUE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeSpeechDriver, jboolean,
+  jstring driver
+) {
+  return changeStringValue(env, changeSpeechDriver_p, driver);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeSpeechParameters, jboolean,
+  jstring parameters
+) {
+  return changeStringValue(env, changeSpeechParameters_p, parameters);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, restartScreenDriver, jboolean
+) {
+  restartScreenDriver_p();
+  return JNI_TRUE;
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeScreenDriver, jboolean,
+  jstring driver
+) {
+  return changeStringValue(env, changeScreenDriver_p, driver);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeScreenParameters, jboolean,
+  jstring parameters
+) {
+  return changeStringValue(env, changeScreenParameters_p, parameters);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, changeMessageLocale, jboolean,
+  jstring locale
+) {
+  return changeStringValue(env, changeMessageLocale_p, locale);
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, showMessage, void,
+  jstring jMessage
+) {
+  const char *cMessage = (*env)->GetStringUTFChars(env, jMessage, NULL);
+
+  if (cMessage) {
+    showMessage_p(cMessage);
+    (*env)->ReleaseStringUTFChars(env, jMessage, cMessage);
+  } else {
+    reportOutOfMemory(env, "C new value string");
+  }
+}
+
+JAVA_STATIC_METHOD (
+  org_a11y_brltty_core_CoreWrapper, setEnvironmentVariable, jboolean,
+  jstring jName, jstring jValue
+) {
+  jboolean isCopy;
+  const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy);
+  const char *cValue = (*env)->GetStringUTFChars(env, jValue, &isCopy);
+
+  int cResult = setenv(cName, cValue, 1) != -1;
+  jboolean jResult = cResult? JNI_TRUE: JNI_FALSE;
+
+  if (cResult) {
+    LOG("environment variable set: %s: %s", cName, cValue);
+  } else {
+    LOG("environment variable not set: %s: %s", cName, strerror(errno));
+  }
+
+  (*env)->ReleaseStringUTFChars(env, jName, cName);
+  (*env)->ReleaseStringUTFChars(env, jValue, cValue);
+  return jResult;
+}
+
+JNIEXPORT jint
+JNI_OnLoad (JavaVM *vm, void *reserved) {
+  JNIEnv *env;
+
+  if ((*vm)->GetEnv(vm, (void **)&env, JAVA_JNI_VERSION) == JNI_OK) {
+    loadCoreLibrary(env);
+  }
+
+  return JAVA_JNI_VERSION;
+}
diff --git a/Programs/charset.c b/Programs/charset.c
new file mode 100644
index 0000000..ce75564
--- /dev/null
+++ b/Programs/charset.c
@@ -0,0 +1,236 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "charset_internal.h"
+#include "lock.h"
+#include "file.h"
+#include "program.h"
+
+int
+isCharsetLatin1 (const char *name) {
+  {
+    const char *substring = "iso";
+    size_t length = strlen(substring);
+    if (strncasecmp(name, substring, length) != 0) return 0;
+    name += length;
+    if ((*name == '-') || (*name == '_')) name += 1;
+  }
+
+  {
+    const char *substring = "8859";
+    size_t length = strlen(substring);
+    if (strncmp(name, substring, length) != 0) return 0;
+    name += length;
+    if (*name == '-') name += 1;
+  }
+
+  return strcmp(name, "1") == 0;
+}
+
+#if defined(__MINGW32__)
+#include "system_windows.h"
+
+#elif defined(__ANDROID__)
+#include "system_java.h"
+
+#else /* unix */
+#include <locale.h>
+#endif /* locale definitions */
+
+const char defaultCharset[] = "ISO-8859-1";
+
+static char *currentCharset = NULL;
+
+char *
+getLocaleName (void) {
+#if defined(__MINGW32__)
+  return getWindowsLocaleName();
+
+#elif defined(__ANDROID__)
+  return getJavaLocaleName();
+
+#else /* unix */
+  char *name = setlocale(LC_CTYPE, NULL);
+
+  if (name) {
+    if (!(name = strdup(name))) {
+      logMallocError();
+    }
+  }
+
+  return name;
+#endif /* text table locale */
+}
+
+int
+isPosixLocale (const char *locale) {
+  if (strcmp(locale, "C") == 0) return 1;
+  if (strcmp(locale, "POSIX") == 0) return 1;
+  return 0;
+}
+
+size_t
+convertCharToUtf8 (char c, Utf8Buffer utf8) {
+  wint_t wc = convertCharToWchar(c);
+  if (wc == WEOF) return 0;
+  return convertWcharToUtf8(wc, utf8);
+}
+
+int
+convertUtf8ToChar (const char **utf8, size_t *utfs) {
+  wint_t wc = convertUtf8ToWchar(utf8, utfs);
+  if (wc == WEOF) return EOF;
+  return convertWcharToChar(wc);
+}
+
+const char *
+getWcharCharset (void) {
+  static const char *wcharCharset = NULL;
+
+  if (!wcharCharset) {
+    char charset[0X10];
+
+    snprintf(charset, sizeof(charset), "UCS-%lu%cE",
+             (unsigned long)sizeof(wchar_t),
+#ifdef WORDS_BIGENDIAN
+             'B'
+#else /* WORDS_BIGENDIAN */
+             'L'
+#endif /* WORDS_BIGENDIAN */
+            );
+
+    if ((wcharCharset = strdup(charset))) {
+      registerProgramMemory("wchar-charset", &wcharCharset);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return wcharCharset;
+}
+
+const char *
+setCharset (const char *name) {
+  char *charset;
+
+  if (name) {
+    if (currentCharset && (strcmp(currentCharset, name) == 0)) return currentCharset;
+  } else if (currentCharset) {
+    return currentCharset;
+  } else {
+    name = getLocaleCharset();
+  }
+
+  if (!(charset = strdup(name))) {
+    logMallocError();
+    return NULL;
+  }
+
+  if (!registerCharacterSet(charset)) {
+    free(charset);
+    return NULL;
+  }
+
+  if (currentCharset) {
+    free(currentCharset);
+  } else {
+    registerProgramMemory("current-charset", &currentCharset);
+  }
+
+  return currentCharset = charset;
+}
+
+const char *
+getCharset (void) {
+  return setCharset(NULL);
+}
+
+static LockDescriptor *
+getCharsetLock (void) {
+  static LockDescriptor *lock = NULL;
+
+  return getLockDescriptor(&lock, "charset");
+}
+
+int
+lockCharset (LockOptions options) {
+  LockDescriptor *lock = getCharsetLock();
+  if (!lock) return 0;
+  return obtainLock(lock, options);
+}
+
+void
+unlockCharset (void) {
+  LockDescriptor *lock = getCharsetLock();
+  if (lock) releaseLock(lock);
+}
+
+static int
+testFileExists (const char *directory, char *name, PathMaker *pathMaker) {
+  int exists = 0;
+  char *path;
+
+  if ((path = pathMaker(directory, name))) {
+    if (testFilePath(path)) exists = 1;
+    free(path);
+  }
+
+  return exists;
+}
+
+char *
+getFileForLocale (const char *directory, PathMaker *pathMaker) {
+  char *locale = getLocaleName();
+
+  if (locale) {
+    char name[strlen(locale) + 1];
+
+    {
+      size_t length = strcspn(locale, ".@");
+      strncpy(name, locale, length);
+      name[length] = 0;
+    }
+
+    free(locale);
+    locale = NULL;
+
+    if (isPosixLocale(name)) {
+      name[0] = 0;
+    } else if (!testFileExists(directory, name, pathMaker)) {
+      char *delimiter = strchr(name, '_');
+
+      if (delimiter) {
+        *delimiter = 0;
+        if (!testFileExists(directory, name, pathMaker)) name[0] = 0;
+      }
+    }
+
+    if (name[0]) {
+      char *file = strdup(name);
+      if (file) return file;
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
diff --git a/Programs/charset_grub.c b/Programs/charset_grub.c
new file mode 100644
index 0000000..105d69e
--- /dev/null
+++ b/Programs/charset_grub.c
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <locale.h>
+#include <wchar.h>
+
+#include "charset_internal.h"
+
+wint_t
+convertCharToWchar (char c) {
+  return (unsigned char)c;
+}
+
+int
+convertWcharToChar (wchar_t wc) {
+  return wc & 0XFF;
+}
+
+const char *
+getLocaleCharset (void) {
+  return nl_langinfo(CODESET);
+}
+
+int
+registerCharacterSet (const char *charset) {
+  return grub_strcasecmp(charset, "UTF-8") == 0;
+}
diff --git a/Programs/charset_iconv.c b/Programs/charset_iconv.c
new file mode 100644
index 0000000..77452bc
--- /dev/null
+++ b/Programs/charset_iconv.c
@@ -0,0 +1,149 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <iconv.h>
+#include <locale.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+
+#include "log.h"
+#include "charset_internal.h"
+#include "program.h"
+
+#define CHARSET_ICONV_NULL ((iconv_t)-1)
+#define CHARSET_ICONV_HANDLE(name) iconv_t iconv##name = CHARSET_ICONV_NULL
+
+static CHARSET_ICONV_HANDLE(CharToWchar);
+static CHARSET_ICONV_HANDLE(WcharToChar);
+
+#define CHARSET_CONVERT_TYPE_TO_TYPE(name, from, to, ret, eof) \
+ret convert##name (from f) { \
+  if (getCharset()) { \
+    from *fp = &f; \
+    size_t fs = sizeof(f); \
+    to t; \
+    to *tp = &t; \
+    size_t ts = sizeof(t); \
+    if (iconv(iconv##name, (void *)&fp, &fs, (void *)&tp, &ts) != (size_t)-1) return t; \
+    logMessage(LOG_DEBUG, "iconv (" #from " -> " #to ") error: %s", strerror(errno)); \
+  } \
+  return eof; \
+}
+CHARSET_CONVERT_TYPE_TO_TYPE(CharToWchar, char, wchar_t, wint_t, WEOF)
+CHARSET_CONVERT_TYPE_TO_TYPE(WcharToChar, wchar_t, unsigned char, int, EOF)
+#undef CHARSET_CONVERT_TYPE_TO_TYPE
+
+const char *
+getLocaleCharset (void) {
+  const char *locale = setlocale(LC_ALL, "");
+
+  if (locale && !isPosixLocale(locale)) {
+#ifdef HAVE_NL_LANGINFO
+    /* some 8-bit locale is set, assume its charset is correct */
+    return nl_langinfo(CODESET);
+#endif /* HAVE_NL_LANGINFO */
+  }
+
+  return defaultCharset;
+}
+
+static void
+exitCharsetIconv (void *data) {
+  static iconv_t *const handles[] = {
+    &iconvCharToWchar,
+    &iconvWcharToChar
+  };
+
+  iconv_t *const *handle = handles;
+  iconv_t *const *const end = handle + ARRAY_COUNT(handles);
+
+  while (handle < end) {
+    if (**handle != CHARSET_ICONV_NULL) {
+      iconv_close(**handle);
+      **handle = CHARSET_ICONV_NULL;
+    }
+
+    handle += 1;
+  }
+}
+
+int
+registerCharacterSet (const char *charset) {
+  int firstTime = 0;
+  const char *const wcharCharset = getWcharCharset();
+
+  typedef struct {
+    iconv_t *const handle;
+    const char *const fromCharset;
+    const char *const toCharset;
+
+    iconv_t newHandle;
+  } ConvEntry;
+
+  ConvEntry convTable[] = {
+    { .handle = &iconvCharToWchar,
+      .fromCharset = charset,
+      .toCharset = wcharCharset
+    },
+
+    { .handle = &iconvWcharToChar,
+      .fromCharset = wcharCharset,
+      .toCharset = charset
+    },
+  };
+
+  ConvEntry *conv = convTable;
+  const ConvEntry *convEnd = conv + ARRAY_COUNT(convTable);
+
+  while (conv < convEnd) {
+    if ((conv->newHandle = iconv_open(conv->toCharset, conv->fromCharset)) == CHARSET_ICONV_NULL) {
+      logSystemError("iconv_open");
+
+      while (conv > convTable) {
+        conv -= 1;
+        iconv_close(conv->newHandle);
+      }
+
+      return 0;
+    }
+
+    conv += 1;
+  }
+
+  while (conv > convTable) {
+    conv -= 1;
+
+    if (*conv->handle == CHARSET_ICONV_NULL) {
+      firstTime = 1;
+    } else {
+      iconv_close(*conv->handle);
+    }
+
+    *conv->handle = conv->newHandle;
+  }
+
+  if (firstTime) onProgramExit("charset-iconv", exitCharsetIconv, NULL);
+  return 1;
+}
diff --git a/Programs/charset_internal.h b/Programs/charset_internal.h
new file mode 100644
index 0000000..0c6c0cc
--- /dev/null
+++ b/Programs/charset_internal.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CHARSET_INTERNAL
+#define BRLTTY_INCLUDED_CHARSET_INTERNAL
+
+#include "charset.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const char defaultCharset[];
+
+extern int registerCharacterSet (const char *charset);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CHARSET_INTERNAL */
diff --git a/Programs/charset_msdos.c b/Programs/charset_msdos.c
new file mode 100644
index 0000000..c3bb6b6
--- /dev/null
+++ b/Programs/charset_msdos.c
@@ -0,0 +1,244 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "charset_internal.h"
+#include "unicode.h"
+#include "system_msdos.h"
+
+#define CHARACTER_SET_SIZE 0X100
+
+static const uint16_t toUnicode_Latin1[CHARACTER_SET_SIZE] = {
+  /* 00 */ 0X0000, 0X0001, 0X0002, 0X0003, 0X0004, 0X0005, 0X0006, 0X0007,
+  /* 08 */ 0X0008, 0X0009, 0X000A, 0X000B, 0X000C, 0X000D, 0X000E, 0X000F,
+  /* 10 */ 0X0010, 0X0011, 0X0012, 0X0013, 0X0014, 0X0015, 0X0016, 0X0017,
+  /* 18 */ 0X0018, 0X0019, 0X001A, 0X001B, 0X001C, 0X001D, 0X001E, 0X001F,
+  /* 20 */ 0X0020, 0X0021, 0X0022, 0X0023, 0X0024, 0X0025, 0X0026, 0X0027,
+  /* 28 */ 0X0028, 0X0029, 0X002A, 0X002B, 0X002C, 0X002D, 0X002E, 0X002F,
+  /* 30 */ 0X0030, 0X0031, 0X0032, 0X0033, 0X0034, 0X0035, 0X0036, 0X0037,
+  /* 38 */ 0X0038, 0X0039, 0X003A, 0X003B, 0X003C, 0X003D, 0X003E, 0X003F,
+  /* 40 */ 0X0040, 0X0041, 0X0042, 0X0043, 0X0044, 0X0045, 0X0046, 0X0047,
+  /* 48 */ 0X0048, 0X0049, 0X004A, 0X004B, 0X004C, 0X004D, 0X004E, 0X004F,
+  /* 50 */ 0X0050, 0X0051, 0X0052, 0X0053, 0X0054, 0X0055, 0X0056, 0X0057,
+  /* 58 */ 0X0058, 0X0059, 0X005A, 0X005B, 0X005C, 0X005D, 0X005E, 0X005F,
+  /* 60 */ 0X0060, 0X0061, 0X0062, 0X0063, 0X0064, 0X0065, 0X0066, 0X0067,
+  /* 68 */ 0X0068, 0X0069, 0X006A, 0X006B, 0X006C, 0X006D, 0X006E, 0X006F,
+  /* 70 */ 0X0070, 0X0071, 0X0072, 0X0073, 0X0074, 0X0075, 0X0076, 0X0077,
+  /* 78 */ 0X0078, 0X0079, 0X007A, 0X007B, 0X007C, 0X007D, 0X007E, 0X007F,
+  /* 80 */ 0X0080, 0X0081, 0X0082, 0X0083, 0X0084, 0X0085, 0X0086, 0X0087,
+  /* 88 */ 0X0088, 0X0089, 0X008A, 0X008B, 0X008C, 0X008D, 0X008E, 0X008F,
+  /* 90 */ 0X0090, 0X0091, 0X0092, 0X0093, 0X0094, 0X0095, 0X0096, 0X0097,
+  /* 98 */ 0X0098, 0X0099, 0X009A, 0X009B, 0X009C, 0X009D, 0X009E, 0X009F,
+  /* A0 */ 0X00A0, 0X00A1, 0X00A2, 0X00A3, 0X00A4, 0X00A5, 0X00A6, 0X00A7,
+  /* A8 */ 0X00A8, 0X00A9, 0X00AA, 0X00AB, 0X00AC, 0X00AD, 0X00AE, 0X00AF,
+  /* B0 */ 0X00B0, 0X00B1, 0X00B2, 0X00B3, 0X00B4, 0X00B5, 0X00B6, 0X00B7,
+  /* B8 */ 0X00B8, 0X00B9, 0X00BA, 0X00BB, 0X00BC, 0X00BD, 0X00BE, 0X00BF,
+  /* C0 */ 0X00C0, 0X00C1, 0X00C2, 0X00C3, 0X00C4, 0X00C5, 0X00C6, 0X00C7,
+  /* C8 */ 0X00C8, 0X00C9, 0X00CA, 0X00CB, 0X00CC, 0X00CD, 0X00CE, 0X00CF,
+  /* D0 */ 0X00D0, 0X00D1, 0X00D2, 0X00D3, 0X00D4, 0X00D5, 0X00D6, 0X00D7,
+  /* D8 */ 0X00D8, 0X00D9, 0X00DA, 0X00DB, 0X00DC, 0X00DD, 0X00DE, 0X00DF,
+  /* E0 */ 0X00E0, 0X00E1, 0X00E2, 0X00E3, 0X00E4, 0X00E5, 0X00E6, 0X00E7,
+  /* E8 */ 0X00E8, 0X00E9, 0X00EA, 0X00EB, 0X00EC, 0X00ED, 0X00EE, 0X00EF,
+  /* F0 */ 0X00F0, 0X00F1, 0X00F2, 0X00F3, 0X00F4, 0X00F5, 0X00F6, 0X00F7,
+  /* F8 */ 0X00F8, 0X00F9, 0X00FA, 0X00FB, 0X00FC, 0X00FD, 0X00FE, 0X00FF,
+};
+
+static const uint16_t toUnicode_CP437[CHARACTER_SET_SIZE] = {
+  /* 00 */ 0X0000, 0X0001, 0X0002, 0X0003, 0X0004, 0X0005, 0X0006, 0X0007,
+  /* 08 */ 0X0008, 0X0009, 0X000A, 0X000B, 0X000C, 0X000D, 0X000E, 0X000F,
+  /* 10 */ 0X0010, 0X0011, 0X0012, 0X0013, 0X0014, 0X0015, 0X0016, 0X0017,
+  /* 18 */ 0X0018, 0X0019, 0X001A, 0X001B, 0X001C, 0X001D, 0X001E, 0X001F,
+  /* 20 */ 0X0020, 0X0021, 0X0022, 0X0023, 0X0024, 0X0025, 0X0026, 0X0027,
+  /* 28 */ 0X0028, 0X0029, 0X002A, 0X002B, 0X002C, 0X002D, 0X002E, 0X002F,
+  /* 30 */ 0X0030, 0X0031, 0X0032, 0X0033, 0X0034, 0X0035, 0X0036, 0X0037,
+  /* 38 */ 0X0038, 0X0039, 0X003A, 0X003B, 0X003C, 0X003D, 0X003E, 0X003F,
+  /* 40 */ 0X0040, 0X0041, 0X0042, 0X0043, 0X0044, 0X0045, 0X0046, 0X0047,
+  /* 48 */ 0X0048, 0X0049, 0X004A, 0X004B, 0X004C, 0X004D, 0X004E, 0X004F,
+  /* 50 */ 0X0050, 0X0051, 0X0052, 0X0053, 0X0054, 0X0055, 0X0056, 0X0057,
+  /* 58 */ 0X0058, 0X0059, 0X005A, 0X005B, 0X005C, 0X005D, 0X005E, 0X005F,
+  /* 60 */ 0X0060, 0X0061, 0X0062, 0X0063, 0X0064, 0X0065, 0X0066, 0X0067,
+  /* 68 */ 0X0068, 0X0069, 0X006A, 0X006B, 0X006C, 0X006D, 0X006E, 0X006F,
+  /* 70 */ 0X0070, 0X0071, 0X0072, 0X0073, 0X0074, 0X0075, 0X0076, 0X0077,
+  /* 78 */ 0X0078, 0X0079, 0X007A, 0X007B, 0X007C, 0X007D, 0X007E, 0X007F,
+  /* 80 */ 0X00C7, 0X00FC, 0X00E9, 0X00E2, 0X00E4, 0X00E0, 0X00E5, 0X00E7,
+  /* 88 */ 0X00EA, 0X00EB, 0X00E8, 0X00EF, 0X00EE, 0X00EC, 0X00C4, 0X00C5,
+  /* 90 */ 0X00C9, 0X00E6, 0X00C6, 0X00F4, 0X00F6, 0X00F2, 0X00FB, 0X00F9,
+  /* 98 */ 0X00FF, 0X00D6, 0X00DC, 0X00A2, 0X00A3, 0X00A5, 0X20A7, 0X0192,
+  /* A0 */ 0X00E1, 0X00ED, 0X00F3, 0X00FA, 0X00F1, 0X00D1, 0X00AA, 0X00BA,
+  /* A8 */ 0X00BF, 0X2310, 0X00AC, 0X00BD, 0X00BC, 0X00A1, 0X00AB, 0X00BB,
+  /* B0 */ 0X2591, 0X2592, 0X2593, 0X2502, 0X2524, 0X2561, 0X2562, 0X2556,
+  /* B8 */ 0X2555, 0X2563, 0X2551, 0X2557, 0X255D, 0X255C, 0X255B, 0X2510,
+  /* C0 */ 0X2514, 0X2534, 0X252C, 0X251C, 0X2500, 0X253C, 0X255E, 0X255F,
+  /* C8 */ 0X255A, 0X2554, 0X2569, 0X2566, 0X2560, 0X2550, 0X256C, 0X2567,
+  /* D0 */ 0X2568, 0X2564, 0X2565, 0X2559, 0X2558, 0X2552, 0X2553, 0X256B,
+  /* D8 */ 0X256A, 0X2518, 0X250C, 0X2588, 0X2584, 0X258C, 0X2590, 0X2580,
+  /* E0 */ 0X03B1, 0X00DF, 0X0393, 0X03C0, 0X03A3, 0X03C3, 0X00B5, 0X03C4,
+  /* E8 */ 0X03A6, 0X0398, 0X03A9, 0X03B4, 0X221E, 0X03C6, 0X03B5, 0X2229,
+  /* F0 */ 0X2261, 0X00B1, 0X2265, 0X2264, 0X2320, 0X2321, 0X00F7, 0X2248,
+  /* F8 */ 0X00B0, 0X2219, 0X00B7, 0X221A, 0X207F, 0X00B2, 0X25A0, 0X00A0
+};
+
+static const uint16_t toUnicode_CP850[CHARACTER_SET_SIZE] = {
+  /* 00 */ 0X0000, 0X0001, 0X0002, 0X0003, 0X0004, 0X0005, 0X0006, 0X0007,
+  /* 08 */ 0X0008, 0X0009, 0X000A, 0X000B, 0X000C, 0X000D, 0X000E, 0X000F,
+  /* 10 */ 0X0010, 0X0011, 0X0012, 0X0013, 0X0014, 0X0015, 0X0016, 0X0017,
+  /* 18 */ 0X0018, 0X0019, 0X001A, 0X001B, 0X001C, 0X001D, 0X001E, 0X001F,
+  /* 20 */ 0X0020, 0X0021, 0X0022, 0X0023, 0X0024, 0X0025, 0X0026, 0X0027,
+  /* 28 */ 0X0028, 0X0029, 0X002A, 0X002B, 0X002C, 0X002D, 0X002E, 0X002F,
+  /* 30 */ 0X0030, 0X0031, 0X0032, 0X0033, 0X0034, 0X0035, 0X0036, 0X0037,
+  /* 38 */ 0X0038, 0X0039, 0X003A, 0X003B, 0X003C, 0X003D, 0X003E, 0X003F,
+  /* 40 */ 0X0040, 0X0041, 0X0042, 0X0043, 0X0044, 0X0045, 0X0046, 0X0047,
+  /* 48 */ 0X0048, 0X0049, 0X004A, 0X004B, 0X004C, 0X004D, 0X004E, 0X004F,
+  /* 50 */ 0X0050, 0X0051, 0X0052, 0X0053, 0X0054, 0X0055, 0X0056, 0X0057,
+  /* 58 */ 0X0058, 0X0059, 0X005A, 0X005B, 0X005C, 0X005D, 0X005E, 0X005F,
+  /* 60 */ 0X0060, 0X0061, 0X0062, 0X0063, 0X0064, 0X0065, 0X0066, 0X0067,
+  /* 68 */ 0X0068, 0X0069, 0X006A, 0X006B, 0X006C, 0X006D, 0X006E, 0X006F,
+  /* 70 */ 0X0070, 0X0071, 0X0072, 0X0073, 0X0074, 0X0075, 0X0076, 0X0077,
+  /* 78 */ 0X0078, 0X0079, 0X007A, 0X007B, 0X007C, 0X007D, 0X007E, 0X007F,
+  /* 80 */ 0X00C7, 0X00FC, 0X00E9, 0X00E2, 0X00E4, 0X00E0, 0X00E5, 0X00E7,
+  /* 88 */ 0X00EA, 0X00EB, 0X00E8, 0X00EF, 0X00EE, 0X00EC, 0X00C4, 0X00C5,
+  /* 90 */ 0X00C9, 0X00E6, 0X00C6, 0X00F4, 0X00F6, 0X00F2, 0X00FB, 0X00F9,
+  /* 98 */ 0X00FF, 0X00D6, 0X00DC, 0X00F8, 0X00A3, 0X00D8, 0X00D7, 0X0192,
+  /* A0 */ 0X00E1, 0X00ED, 0X00F3, 0X00FA, 0X00F1, 0X00D1, 0X00AA, 0X00BA,
+  /* A8 */ 0X00BF, 0X00AE, 0X00AC, 0X00BD, 0X00BC, 0X00A1, 0X00AB, 0X00BB,
+  /* B0 */ 0X2591, 0X2592, 0X2593, 0X2502, 0X2524, 0X00C1, 0X00C2, 0X00C0,
+  /* B8 */ 0X00A9, 0X2563, 0X2551, 0X2557, 0X255D, 0X00A2, 0X00A5, 0X2510,
+  /* C0 */ 0X2514, 0X2534, 0X252C, 0X251C, 0X2500, 0X253C, 0X00E3, 0X00C3,
+  /* C8 */ 0X255A, 0X2554, 0X2569, 0X2566, 0X2560, 0X2550, 0X256C, 0X00A4,
+  /* D0 */ 0X00F0, 0X00D0, 0X00CA, 0X00CB, 0X00C8, 0X0131, 0X00CD, 0X00CE,
+  /* D8 */ 0X00CF, 0X2518, 0X250C, 0X2588, 0X2584, 0X00A6, 0X00CC, 0X2580,
+  /* E0 */ 0X00D3, 0X00DF, 0X00D4, 0X00D2, 0X00F5, 0X00D5, 0X00B5, 0X00FE,
+  /* E8 */ 0X00DE, 0X00DA, 0X00DB, 0X00D9, 0X00FD, 0X00DD, 0X00AF, 0X00B4,
+  /* F0 */ 0X00AD, 0X00B1, 0X2017, 0X00BE, 0X00B6, 0X00A7, 0X00F7, 0X00B8,
+  /* F8 */ 0X00B0, 0X00A8, 0X00B7, 0X00B9, 0X00B3, 0X00B2, 0X25A0, 0X00A0
+};
+
+typedef struct {
+  const char *name;
+  const uint16_t *toUnicode;
+} CharacterSet;
+
+static const CharacterSet characterSets[] = {
+  {.name="iso-8859-1", .toUnicode=toUnicode_Latin1},
+  {.name="cp437", .toUnicode=toUnicode_CP437},
+  {.name="cp850", .toUnicode=toUnicode_CP850},
+  {}
+};
+static const CharacterSet *characterSet;
+
+static char *unicodeRows[UNICODE_ROWS_PER_PLANE];
+
+static void
+clearUnicodeRow (char *row) {
+  memset(row, 0, UNICODE_CELLS_PER_ROW);
+}
+
+static void
+clearUnicodeRows (void) {
+  int rowNumber;
+  for (rowNumber=0; rowNumber<UNICODE_ROWS_PER_PLANE; ++rowNumber) {
+    char *row = unicodeRows[rowNumber];
+    if (row) clearUnicodeRow(row);
+  }
+}
+
+static char *
+getUnicodeCell (uint16_t unicode, int allocate) {
+  unsigned char cellNumber = unicode & 0XFF;
+  unsigned char rowNumber = unicode >> 8;
+  char **rowAddress = &unicodeRows[rowNumber];
+
+  if (!*rowAddress) {
+    if (!allocate) return NULL;
+
+    {
+      char *row = malloc(UNICODE_CELLS_PER_ROW);
+      if (!row) return NULL;
+      clearUnicodeRow(row);
+      *rowAddress = row;
+    }
+  }
+
+  return &(*rowAddress)[cellNumber];
+}
+
+static int
+setCharacterSet (const char *name) {
+  const CharacterSet *set = characterSets;
+
+  while (set->name) {
+    if (strcasecmp(name, set->name) == 0) {
+      clearUnicodeRows();
+      characterSet = NULL;
+
+      {
+        int character;
+        for (character=CHARACTER_SET_SIZE-1; character>=0; --character) {
+          char *cell = getUnicodeCell(set->toUnicode[character], 1);
+          if (!cell) return 0;
+          *cell = character;
+        }
+      }
+
+      characterSet = set;
+      return 1;
+    }
+
+    ++set;
+  }
+
+  return 0;
+}
+
+wint_t
+convertCharToWchar (char c) {
+  if (getCharset()) {
+    uint16_t wc = characterSet->toUnicode[(unsigned char)c & 0XFF];
+    if (wc || !c)
+      if ((sizeof(wchar_t) > 1) || iswLatin1(wc))
+        return wc;
+  }
+  return WEOF;
+}
+
+int
+convertWcharToChar (wchar_t wc) {
+  if (getCharset()) {
+    char *cell = getUnicodeCell(wc, 0);
+    if (cell)
+      if (*cell || !wc)
+        return *cell & 0XFF;
+  }
+  return EOF;
+}
+
+const char *
+getLocaleCharset (void) {
+  static char codepage[8];
+  snprintf(codepage, sizeof(codepage), "CP%03u", msdosGetCodePage());
+  return codepage;
+}
+
+int
+registerCharacterSet (const char *charset) {
+  return setCharacterSet(charset);
+}
diff --git a/Programs/charset_none.c b/Programs/charset_none.c
new file mode 100644
index 0000000..be9bf98
--- /dev/null
+++ b/Programs/charset_none.c
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "charset_internal.h"
+
+wint_t
+convertCharToWchar (char c) {
+  return c & 0XFF;
+}
+
+int
+convertWcharToChar (wchar_t wc) {
+  return wc & 0XFF;
+}
+
+const char *
+getLocaleCharset (void) {
+  return defaultCharset;
+}
+
+int
+registerCharacterSet (const char *charset) {
+  return 1;
+}
diff --git a/Programs/charset_windows.c b/Programs/charset_windows.c
new file mode 100644
index 0000000..c8102c7
--- /dev/null
+++ b/Programs/charset_windows.c
@@ -0,0 +1,73 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <locale.h>
+
+#include "log.h"
+#include "charset_internal.h"
+
+#if defined(CP_THREAD_ACP)
+#define CHARSET_WINDOWS_CODEPAGE CP_THREAD_ACP
+#else /* Windows codepage */
+#define CHARSET_WINDOWS_CODEPAGE CP_ACP
+#endif /* Windows codepage */
+
+wint_t
+convertCharToWchar (char c) {
+  wchar_t wc;
+  int result = MultiByteToWideChar(CHARSET_WINDOWS_CODEPAGE, MB_ERR_INVALID_CHARS,
+                                   &c, 1,  &wc, 1);
+  if (result) return wc;
+  logWindowsSystemError("MultiByteToWideChar[" STRINGIFY(CHARSET_WINDOWS_CODEPAGE) "]");
+  return WEOF;
+}
+
+int
+convertWcharToChar (wchar_t wc) {
+  char c;
+  int result = WideCharToMultiByte(CHARSET_WINDOWS_CODEPAGE, WC_NO_BEST_FIT_CHARS /* WC_ERR_INVALID_CHARS */,
+                                   &wc, 1, &c, 1,
+                                   NULL, NULL);
+  if (result) return c & 0XFF;
+  logWindowsSystemError("WideCharToMultiByte[" STRINGIFY(CHARSET_WINDOWS_CODEPAGE) "]");
+  return EOF;
+}
+
+const char *
+getLocaleCharset (void) {
+  const char *locale = setlocale(LC_ALL, "");
+
+  if (locale && !isPosixLocale(locale)) {
+    /* some 8-bit locale is set, assume its charset is correct */
+    static char codepage[8] = {'C', 'P'};
+
+    GetLocaleInfo(GetThreadLocale(), LOCALE_IDEFAULTANSICODEPAGE, codepage+2, sizeof(codepage)-2);
+    return codepage;
+  }
+
+  return defaultCharset;
+}
+
+int
+registerCharacterSet (const char *charset) {
+  return 1;
+}
diff --git a/Programs/cldr.c b/Programs/cldr.c
new file mode 100644
index 0000000..8225df1
--- /dev/null
+++ b/Programs/cldr.c
@@ -0,0 +1,321 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "cldr.h"
+#include "file.h"
+
+#undef HAVE_XML_PROCESSOR
+
+#if defined(HAVE_EXPAT)
+#define HAVE_XML_PROCESSOR
+#include <expat.h>
+
+#else /* XML processor */
+#endif /* XML processor */
+
+#ifdef HAVE_XML_PROCESSOR
+struct CLDR_DocumentParserObjectStruct {
+  struct {
+    CLDR_AnnotationHandler *handler;
+    void *data;
+  } caller;
+
+  struct {
+    XML_Parser parser;
+    unsigned int depth;
+  } document;
+
+  struct {
+    char *sequence;
+    char *name;
+    unsigned int depth;
+  } annotation;
+};
+
+static void
+abortParser (CLDR_DocumentParserObject *dpo) {
+  XML_StopParser(dpo->document.parser, 0);
+}
+
+static void
+appendAnnotationText (void *userData, const char *characters, int count) {
+  CLDR_DocumentParserObject *dpo = userData;
+
+  if (dpo->document.depth == dpo->annotation.depth) {
+    if (count > 0) {
+      char *name = dpo->annotation.name;
+
+      if (name) {
+        size_t oldLength = strlen(name);
+        size_t newLength = oldLength + count;
+        char *newName = realloc(name, newLength+1);
+
+        if (!newName) {
+          logMallocError();
+          abortParser(dpo);
+          return;
+        }
+
+        memcpy(&newName[oldLength], characters, count);
+        newName[newLength] = 0;
+        name = newName;
+      } else {
+        if (!(name = malloc(count+1))) {
+          logMallocError();
+          abortParser(dpo);
+          return;
+        }
+
+        memcpy(name, characters, count);
+        name[count] = 0;
+      }
+
+      dpo->annotation.name = name;
+    }
+  }
+}
+
+static void
+handleElementStart (void *userData, const char *element, const char **attributes) {
+  CLDR_DocumentParserObject *dpo = userData;
+  dpo->document.depth += 1;
+
+  if (dpo->annotation.depth) {
+    logMessage(LOG_WARNING, "nested annotation");
+    abortParser(dpo);
+    return;
+  }
+
+  if (strcmp(element, "annotation") == 0) {
+    const char *sequence = NULL;
+    int tts = 0;
+
+    while (*attributes) {
+      const char *name = *attributes++;
+      const char *value = *attributes++;
+
+      if (strcmp(name, "type") == 0) {
+        if (strcmp(value, "tts") == 0) tts = 1;
+      } else if (strcmp(name, "cp") == 0) {
+        sequence = value;
+      }
+    }
+
+    if (tts) {
+      if (sequence) {
+        if ((dpo->annotation.sequence = strdup(sequence))) {
+          dpo->annotation.depth = dpo->document.depth;
+        } else {
+          logMallocError();
+          abortParser(dpo);
+        }
+      }
+    }
+  }
+}
+
+static void
+handleElementEnd (void *userData, const char *name) {
+  CLDR_DocumentParserObject *dpo = userData;
+
+  if (dpo->document.depth == dpo->annotation.depth) {
+    if (dpo->annotation.name) {
+      CLDR_AnnotationHandlerParameters parameters = {
+        .sequence = dpo->annotation.sequence,
+        .name = dpo->annotation.name,
+        .data = dpo->caller.data
+      };
+
+      if (!dpo->caller.handler(&parameters)) {
+        abortParser(dpo);
+        return;
+      }
+
+      free(dpo->annotation.name);
+      dpo->annotation.name = NULL;
+    }
+
+    free(dpo->annotation.sequence);
+    dpo->annotation.sequence = NULL;
+
+    dpo->annotation.depth = 0;
+  }
+
+  dpo->document.depth -= 1;
+}
+
+CLDR_DocumentParserObject *
+cldrNewDocumentParser (CLDR_AnnotationHandler *handler, void *data) {
+  CLDR_DocumentParserObject *dpo;
+
+  if ((dpo = malloc(sizeof(*dpo)))) {
+    memset(dpo, 0, sizeof(*dpo));
+
+    dpo->caller.handler = handler;
+    dpo->caller.data = data;
+
+    dpo->document.depth = 0;
+
+    dpo->annotation.sequence = NULL;
+    dpo->annotation.name = NULL;
+    dpo->annotation.depth = 0;
+
+    if ((dpo->document.parser = XML_ParserCreate(NULL))) {
+      XML_SetUserData(dpo->document.parser, dpo);
+      XML_SetElementHandler(dpo->document.parser, handleElementStart, handleElementEnd);
+      XML_SetCharacterDataHandler(dpo->document.parser, appendAnnotationText);
+      return dpo;
+    } else {
+      logMallocError();
+    }
+
+    free(dpo);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+cldrDestroyDocumentParser (CLDR_DocumentParserObject *dpo) {
+  if (dpo->annotation.sequence) {
+    free(dpo->annotation.sequence);
+    dpo->annotation.sequence = NULL;
+  }
+
+  if (dpo->annotation.name) {
+    free(dpo->annotation.name);
+    dpo->annotation.name = NULL;
+  }
+
+  XML_ParserFree(dpo->document.parser);
+  free(dpo);
+}
+
+int
+cldrParseText (CLDR_DocumentParserObject *dpo, const char *text, size_t size, int final) {
+  enum XML_Status status = XML_Parse(dpo->document.parser, text, size, final);
+
+  switch (status) {
+    case XML_STATUS_OK:
+      return 1;
+
+    case XML_STATUS_ERROR:
+      logMessage(LOG_WARNING, "CLDR parse error: %s", XML_ErrorString(XML_GetErrorCode(dpo->document.parser)));
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unrecognized CLDR parse status: %d", status);
+      break;
+  }
+
+  return 0;
+}
+
+int
+cldrParseDocument (
+  const char *document, size_t size,
+  CLDR_AnnotationHandler *handler, void *data
+) {
+  int ok = 0;
+  CLDR_DocumentParserObject *dpo = cldrNewDocumentParser(handler, data);
+
+  if (dpo) {
+    if (cldrParseText(dpo, document, size, 1)) ok = 1;
+    cldrDestroyDocumentParser(dpo);
+  }
+
+  return ok;
+}
+#endif /* HAVE_XML_PROCESSOR */
+
+const char cldrAnnotationsDirectory[] = "/usr/share/unicode/cldr/common/annotations";
+const char cldrAnnotationsExtension[] = ".xml";
+
+int
+cldrParseFile (
+  const char *name,
+  CLDR_AnnotationHandler *handler, void *data
+) {
+  int ok = 0;
+
+#ifdef HAVE_XML_PROCESSOR
+  char *path = makeFilePath(cldrAnnotationsDirectory, name, cldrAnnotationsExtension);
+
+  if (path) {
+    logMessage(LOG_DEBUG, "processing CLDR annotations file: %s", path);
+    int fd = open(path, O_RDONLY);
+
+    if (fd != -1) {
+      CLDR_DocumentParserObject *dpo = cldrNewDocumentParser(handler, data);
+
+      if (dpo) {
+        while (1) {
+          char buffer[0X2000];
+          size_t size = sizeof(buffer);
+          ssize_t count = read(fd, buffer, size);
+
+          if (count == -1) {
+            if (errno == EINTR) continue;
+            logMessage(LOG_WARNING, "CLDR read error: %s: %s", strerror(errno), path);
+            break;
+          }
+
+          int final = count == 0;
+          if (!cldrParseText(dpo, buffer, count, final)) break;
+
+          if (final) {
+            ok = 1;
+            break;
+          }
+        }
+
+        cldrDestroyDocumentParser(dpo);
+      }
+
+      close(fd);
+      fd = -1;
+    } else {
+      logMessage(LOG_WARNING, "CLDR open error: %s: %s", strerror(errno), path);
+
+      if (errno == ENOENT) {
+        if (!isAbsolutePath(name)) {
+          if (!testDirectoryPath(cldrAnnotationsDirectory)) {
+            logPossibleCause("the package that defines the CLDR annotations directory is not installed");
+          }
+        }
+      }
+    }
+
+    free(path);
+  }
+#else /* HAVE_XML_PROCESSOR */
+  logMessage(LOG_WARNING, "CLDR data can't be loaded - no supported XML parser");
+#endif /* HAVE_XML_PROCESSOR */
+
+  return ok;
+}
diff --git a/Programs/clipboard.c b/Programs/clipboard.c
new file mode 100644
index 0000000..38b9183
--- /dev/null
+++ b/Programs/clipboard.c
@@ -0,0 +1,287 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <ctype.h>
+
+#include "log.h"
+#include "clipboard.h"
+#include "utf8.h"
+#include "queue.h"
+#include "lock.h"
+#include "program.h"
+#include "api_control.h"
+
+typedef struct {
+  wchar_t *characters;
+  size_t length;
+} HistoryEntry;
+
+struct ClipboardObjectStruct {
+  struct {
+    wchar_t *characters;
+    size_t size;
+    size_t length;
+  } buffer;
+
+  struct {
+    Queue *queue;
+  } history;
+};
+
+const wchar_t *
+getClipboardHistory (ClipboardObject *cpb, unsigned int index, size_t *length) {
+  Element *element = getStackElement(cpb->history.queue, index);
+  if (!element) return NULL;
+
+  const HistoryEntry *entry = getElementItem(element);
+  *length = entry->length;
+  return entry->characters;
+}
+
+int
+addClipboardHistory (ClipboardObject *cpb, const wchar_t *characters, size_t length) {
+  if (!length) return 1;
+
+  Queue *queue = cpb->history.queue;
+  Element *element = getStackHead(queue);
+
+  if (element) {
+    const HistoryEntry *entry = getElementItem(element);
+
+    if (length == entry->length) {
+      if (wmemcmp(characters, entry->characters, length) == 0) {
+        return 1;
+      }
+    }
+  }
+
+  {
+    HistoryEntry *entry;
+
+    if ((entry = malloc(sizeof(*entry)))) {
+      if ((entry->characters = allocateCharacters(length))) {
+        wmemcpy(entry->characters, characters, length);
+        entry->length = length;
+
+        if (enqueueItem(queue, entry)) {
+          return 1;
+        }
+
+        free(entry->characters);
+      } else {
+        logMallocError();
+      }
+
+      free(entry);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+const wchar_t *
+getClipboardContent (ClipboardObject *cpb, size_t *length) {
+  *length = cpb->buffer.length;
+  return cpb->buffer.characters;
+}
+
+char *
+getClipboardContentUTF8 (ClipboardObject *cpb) {
+  size_t length;
+  const wchar_t *characters = getClipboardContent(cpb, &length);
+  return getUtf8FromWchars(characters, length, NULL);
+}
+
+size_t
+getClipboardContentLength (ClipboardObject *cpb) {
+  return cpb->buffer.length;
+}
+
+int
+truncateClipboardContent (ClipboardObject *cpb, size_t length) {
+  if (length >= cpb->buffer.length) return 0;
+  cpb->buffer.length = length;
+  return 1;
+}
+
+int
+clearClipboardContent (ClipboardObject *cpb) {
+  size_t length;
+  const wchar_t *characters = getClipboardContent(cpb, &length);
+
+  if (!addClipboardHistory(cpb, characters, length)) return 0;
+  truncateClipboardContent(cpb, 0);
+  return 1;
+}
+
+int
+appendClipboardContent (ClipboardObject *cpb, const wchar_t *characters, size_t length) {
+  size_t newLength = cpb->buffer.length + length;
+
+  if (newLength > cpb->buffer.size) {
+    size_t newSize = newLength | 0XFF;
+    wchar_t *newCharacters = allocateCharacters(newSize);
+
+    if (!newCharacters) {
+      logMallocError();
+      return 0;
+    }
+
+    wmemcpy(newCharacters, cpb->buffer.characters, cpb->buffer.length);
+    if (cpb->buffer.characters) free(cpb->buffer.characters);
+    cpb->buffer.characters = newCharacters;
+    cpb->buffer.size = newSize;
+  }
+
+  wmemcpy(&cpb->buffer.characters[cpb->buffer.length], characters, length);
+  cpb->buffer.length += length;
+  return 1;
+}
+
+int
+setClipboardContent (ClipboardObject *cpb, const wchar_t *characters, size_t length) {
+  int truncated = truncateClipboardContent(cpb, 0);
+  int appended = appendClipboardContent(cpb, characters, length);
+  return truncated || appended;
+}
+
+int
+appendClipboardContentUTF8 (ClipboardObject *cpb, const char *text) {
+  size_t length = strlen(text);
+  wchar_t characters[length + 1];
+  size_t count = makeWcharsFromUtf8(text, characters, ARRAY_COUNT(characters));
+  return appendClipboardContent(cpb, characters, count);
+}
+
+int
+setClipboardContentUTF8 (ClipboardObject *cpb, const char *text) {
+  int truncated = truncateClipboardContent(cpb, 0);
+  int appended = appendClipboardContentUTF8(cpb, text);
+  return truncated || appended;
+}
+
+static void
+deallocateClipboardHistoryEntry (void *item, void *data) {
+  HistoryEntry *entry = item;
+  if (entry->characters) free(entry->characters);
+  free(entry);
+}
+
+ClipboardObject *
+newClipboard (void) {
+  ClipboardObject *cpb;
+
+  if ((cpb = malloc(sizeof(*cpb)))) {
+    memset(cpb, 0, sizeof(*cpb));
+
+    cpb->buffer.characters = NULL;
+    cpb->buffer.size = 0;
+    cpb->buffer.length = 0;
+
+    if ((cpb->history.queue = newQueue(deallocateClipboardHistoryEntry, NULL))) {
+      return cpb;
+    }
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyClipboard (ClipboardObject *cpb) {
+  if (cpb->buffer.characters) free(cpb->buffer.characters);
+  deallocateQueue(cpb->history.queue);
+  free(cpb);
+}
+
+static LockDescriptor *
+getMainClipboardLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "main-clipboard");
+}
+
+void
+lockMainClipboard (void) {
+  obtainExclusiveLock(getMainClipboardLock());
+}
+
+void
+unlockMainClipboard (void) {
+  releaseLock(getMainClipboardLock());
+}
+
+static void
+exitMainClipboard (void *data) {
+  ClipboardObject **clipboard = data;
+
+  lockMainClipboard();
+    destroyClipboard(*clipboard);
+    *clipboard = NULL;
+  unlockMainClipboard();
+}
+
+ClipboardObject *
+getMainClipboard (void) {
+  static ClipboardObject *clipboard = NULL;
+
+  lockMainClipboard();
+    if (!clipboard) {
+      if ((clipboard = newClipboard())) {
+        onProgramExit("main-clipboard", exitMainClipboard, &clipboard);
+      }
+    }
+  unlockMainClipboard();
+
+  return clipboard;
+}
+
+void
+onMainClipboardUpdated (void) {
+  api.updateParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0);
+}
+
+int
+setMainClipboardContent (const char *content) {
+  ClipboardObject *cpb = getMainClipboard();
+  int updated;
+
+  lockMainClipboard();
+    updated = setClipboardContentUTF8(cpb, content);
+  unlockMainClipboard();
+
+  if (updated) onMainClipboardUpdated();
+  return updated;
+}
+
+char *
+getMainClipboardContent (void) {
+  ClipboardObject *cpb = getMainClipboard();
+  char *content;
+
+  lockMainClipboard();
+    content = getClipboardContentUTF8(cpb);
+  unlockMainClipboard();
+
+  return content;
+}
diff --git a/Programs/cmd.c b/Programs/cmd.c
new file mode 100644
index 0000000..ad11ae2
--- /dev/null
+++ b/Programs/cmd.c
@@ -0,0 +1,377 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "brl_cmds.h"
+#include "brl_custom.h"
+#include "cmd.h"
+#include "program.h"
+
+const CommandEntry commandTable[] = {
+  #include "cmds.auto.h"
+  {.name=NULL, .code=EOF, .description=NULL}
+};
+
+const CommandModifierEntry commandModifierTable_toggle[] = {
+  {.name="on" , .bit=BRL_FLG_TOGGLE_ON },
+  {.name="off", .bit=BRL_FLG_TOGGLE_OFF},
+  {.name=NULL , .bit=0                 }
+};
+
+const CommandModifierEntry commandModifierTable_motion[] = {
+  {.name="route", .bit=BRL_FLG_MOTION_ROUTE},
+  {.name=NULL   , .bit=0                   }
+};
+
+const CommandModifierEntry commandModifierTable_row[] = {
+  {.name="scaled", .bit=BRL_FLG_MOTION_SCALED},
+  {.name=NULL    , .bit=0                    }
+};
+
+const CommandModifierEntry commandModifierTable_vertical[] = {
+  {.name="toleft", .bit=BRL_FLG_MOTION_TOLEFT},
+  {.name=NULL    , .bit=0                    }
+};
+
+const CommandModifierEntry commandModifierTable_input[] = {
+  {.name="shift"  , .bit=BRL_FLG_INPUT_SHIFT  },
+  {.name="control", .bit=BRL_FLG_INPUT_CONTROL},
+  {.name="meta"   , .bit=BRL_FLG_INPUT_META   },
+  {.name="altgr"  , .bit=BRL_FLG_INPUT_ALTGR  },
+  {.name="gui"    , .bit=BRL_FLG_INPUT_GUI    },
+  {.name=NULL     , .bit=0                    }
+};
+
+const CommandModifierEntry commandModifierTable_character[] = {
+  {.name="upper"  , .bit=BRL_FLG_INPUT_UPPER  },
+  {.name="escaped", .bit=BRL_FLG_INPUT_ESCAPED},
+  {.name=NULL     , .bit=0                    }
+};
+
+const CommandModifierEntry commandModifierTable_braille[] = {
+  {.name="dot1" , .bit=BRL_DOT1},
+  {.name="dot2" , .bit=BRL_DOT2},
+  {.name="dot3" , .bit=BRL_DOT3},
+  {.name="dot4" , .bit=BRL_DOT4},
+  {.name="dot5" , .bit=BRL_DOT5},
+  {.name="dot6" , .bit=BRL_DOT6},
+  {.name="dot7" , .bit=BRL_DOT7},
+  {.name="dot8" , .bit=BRL_DOT8},
+  {.name="space", .bit=BRL_DOTC},
+  {.name=NULL   , .bit=0       }
+};
+
+const CommandModifierEntry commandModifierTable_keyboard[] = {
+  {.name="release", .bit=BRL_FLG_KBD_RELEASE},
+  {.name="emul0"  , .bit=BRL_FLG_KBD_EMUL0  },
+  {.name="emul1"  , .bit=BRL_FLG_KBD_EMUL1  },
+  {.name=NULL     , .bit=0                  }
+};
+
+static int
+compareCommandCodes (const void *element1, const void *element2) {
+  const CommandEntry *const *cmd1 = element1;
+  const CommandEntry *const *cmd2 = element2;
+
+  if ((*cmd1)->code < (*cmd2)->code) return -1;
+  if ((*cmd1)->code > (*cmd2)->code) return 1;
+  return 0;
+}
+
+int
+getCommandCount (void) {
+  static int commandCount = -1;
+
+  if (commandCount < 0) {
+    const CommandEntry *cmd = commandTable;
+    while (cmd->name) cmd += 1;
+    commandCount = cmd - commandTable;
+  }
+
+  return commandCount;
+}
+
+const CommandEntry *
+findCommandEntry (int code) {
+  static const CommandEntry **commandEntries = NULL;
+  int count = getCommandCount();
+  code &= BRL_MSK_CMD;
+
+  if (!commandEntries) {
+    const CommandEntry **entries = malloc(ARRAY_SIZE(entries, count));
+
+    if (!entries) {
+      logMallocError();
+      return NULL;
+    }
+
+    {
+      const CommandEntry *cmd = commandTable;
+      const CommandEntry **entry = entries;
+      while (cmd->name) *entry++ = cmd++;
+    }
+
+    qsort(entries, count, sizeof(*entries), compareCommandCodes);
+    commandEntries = entries;
+    registerProgramMemory("sorted-command-table", &commandEntries);
+  }
+
+  {
+    int first = 0;
+    int last = count - 1;
+
+    while (first <= last) {
+      int current = (first + last) / 2;
+      const CommandEntry *cmd = commandEntries[current];
+
+      if (code < cmd->code) {
+        last = current - 1;
+      } else {
+        first = current + 1;
+      }
+    }
+
+    if (last >= 0) {
+      const CommandEntry *cmd = commandEntries[last];
+      int blk = cmd->code & BRL_MSK_BLK;
+      int arg = cmd->code & BRL_MSK_ARG;
+
+      if (blk == (code & BRL_MSK_BLK)) {
+        if (arg == (code & BRL_MSK_ARG)) return cmd;
+
+        if (blk) {
+          int next = last + 1;
+          if (next == count) return cmd;
+
+          if (blk != (commandEntries[next]->code & BRL_MSK_BLK)) {
+            return cmd;
+          }
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static
+STR_BEGIN_FORMATTER(formatCommandModifiers, int command, const CommandModifierEntry *modifiers)
+  const CommandModifierEntry *modifier = modifiers;
+
+  while (modifier->name) {
+    if (command & modifier->bit) {
+      STR_PRINTF(" + %s", modifier->name);
+    }
+
+    modifier += 1;
+  }
+STR_END_FORMATTER
+
+STR_BEGIN_FORMATTER(describeCommand, int command, CommandDescriptionOption options)
+  unsigned int arg = BRL_ARG_GET(command);
+  unsigned int arg1 = BRL_CODE_GET(ARG, command);
+  unsigned int arg2 = BRL_CODE_GET(EXT, command);
+  const CommandEntry *cmd = findCommandEntry(command);
+
+  if (!cmd) {
+    STR_PRINTF("%s: %06X", gettext("unknown command"), command);
+  } else {
+    if (options & CDO_IncludeName) {
+      STR_PRINTF("%s: ", cmd->name);
+    }
+
+    if (cmd->isToggle && (command & BRL_FLG_TOGGLE_MASK)) {
+      const char *text = gettext(cmd->description);
+      size_t length = strlen(text);
+      char buffer[length + 1];
+      char *delimiter;
+
+      strcpy(buffer, text);
+      delimiter = strchr(buffer, '/');
+
+      if (delimiter) {
+        char *source;
+        char *target;
+
+        if (command & BRL_FLG_TOGGLE_ON) {
+          target = delimiter;
+          if (!(source = strchr(target, ' '))) source = target + strlen(target);
+        } else if (command & BRL_FLG_TOGGLE_OFF) {
+          {
+            char oldDelimiter = *delimiter;
+            *delimiter = 0;
+            target = strrchr(buffer, ' ');
+            *delimiter = oldDelimiter;
+          }
+
+          target = target? (target + 1): buffer;
+          source = delimiter + 1;
+        } else {
+          goto toggleReady;
+        }
+
+        memmove(target, source, (strlen(source) + 1));
+      }
+
+    toggleReady:
+      STR_PRINTF("%s", buffer);
+    } else {
+      STR_PRINTF("%s", gettext(cmd->description));
+    }
+
+    if (options & CDO_IncludeOperand) {
+      if (cmd->isCharacter) {
+        STR_PRINTF(" [U+%04X]", arg);
+      }
+
+      if (cmd->isBraille) {
+        int none = 1;
+        STR_PRINTF(" [");
+
+        {
+          static const int dots[] = {
+            BRL_DOTC,
+            BRL_DOT1, BRL_DOT2, BRL_DOT3, BRL_DOT4,
+            BRL_DOT5, BRL_DOT6, BRL_DOT7, BRL_DOT8,
+            0
+          };
+          const int *dot = dots;
+
+          while (*dot) {
+            if (command & *dot) {
+              none = 0;
+
+              if (dot == dots) {
+                STR_PRINTF("C");
+              } else {
+                unsigned int number = dot - dots;
+                STR_PRINTF("%u", number);
+              }
+            }
+
+            dot += 1;
+          }
+        }
+
+        if (none) STR_PRINTF("%s", gettext("space"));
+        STR_PRINTF("]");
+      }
+
+      if (cmd->isKeyboard) {
+        STR_PRINTF(" [\\X%02X]", arg1);
+      }
+
+      if (cmd->isColumn && !cmd->isRouting && (
+           (arg == BRL_MSK_ARG) /* key event processing */
+         ||
+           ((options & CDO_DefaultOperand) && !arg) /* key table listing */
+         )) {
+        STR_PRINTF(" %s", gettext("at cursor"));
+      } else if (cmd->isColumn || cmd->isRow || cmd->isOffset) {
+        int offset = arg - (cmd->code & BRL_MSK_ARG);
+        STR_PRINTF(" #%u", offset+1);
+      } else if (cmd->isRange) {
+        STR_PRINTF(" #%u-%u", arg1, arg2);
+      }
+
+      if (cmd->isInput) {
+        STR_FORMAT(formatCommandModifiers, command, commandModifierTable_input);
+      }
+
+      if (cmd->isCharacter) {
+        STR_FORMAT(formatCommandModifiers, command, commandModifierTable_character);
+      }
+
+      if (cmd->isKeyboard) {
+        STR_FORMAT(formatCommandModifiers, command, commandModifierTable_keyboard);
+      }
+    }
+
+    if (cmd->isMotion) {
+      STR_FORMAT(formatCommandModifiers, command, commandModifierTable_motion);
+    }
+
+    if (cmd->isRow) {
+      STR_FORMAT(formatCommandModifiers, command, commandModifierTable_row);
+    }
+
+    if (cmd->isVertical) {
+      STR_FORMAT(formatCommandModifiers, command, commandModifierTable_vertical);
+    }
+  }
+STR_END_FORMATTER
+
+static
+STR_BEGIN_FORMATTER(formatCommand, int command)
+  STR_PRINTF("%06X (", command);
+  STR_FORMAT(describeCommand, command, CDO_IncludeName | CDO_IncludeOperand);
+  STR_PRINTF(")");
+STR_END_FORMATTER
+
+typedef struct {
+  int command;
+} LogCommandData;
+
+static
+STR_BEGIN_FORMATTER(formatLogCommandData, const void *data)
+  const LogCommandData *cmd = data;
+
+  STR_PRINTF("command: ");
+  STR_FORMAT(formatCommand, cmd->command);
+STR_END_FORMATTER
+
+void
+logCommand (int command) {
+  const LogCommandData cmd = {
+    .command = command
+  };
+
+  logData(LOG_DEBUG, formatLogCommandData, &cmd);
+}
+
+typedef struct {
+  int oldCommand;
+  int newCommand;
+} LogTransformedCommandData;
+
+static
+STR_BEGIN_FORMATTER(formatLogTransformedCommandData, const void *data)
+  const LogTransformedCommandData *cmd = data;
+
+  STR_PRINTF("command: ");
+  STR_FORMAT(formatCommand, cmd->oldCommand);
+
+  STR_PRINTF(" -> ");
+  STR_FORMAT(formatCommand, cmd->newCommand);
+STR_END_FORMATTER
+
+void
+logTransformedCommand (int oldCommand, int newCommand) {
+  const LogTransformedCommandData cmd = {
+    .oldCommand = oldCommand,
+    .newCommand = newCommand
+  };
+
+  logData(LOG_DEBUG, formatLogTransformedCommandData, &cmd);
+}
diff --git a/Programs/cmd_brlapi.c b/Programs/cmd_brlapi.c
new file mode 100644
index 0000000..ad22133
--- /dev/null
+++ b/Programs/cmd_brlapi.c
@@ -0,0 +1,210 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "cmd_brlapi.h"
+#include "brl_cmds.h"
+#include "ttb.h"
+
+#ifdef ENABLE_API
+static brlapi_keyCode_t
+cmdWCharToBrlapi (wchar_t wc) {
+  if (iswLatin1(wc)) return BRLAPI_KEY_TYPE_SYM | wc;
+  return BRLAPI_KEY_TYPE_SYM | BRLAPI_KEY_SYM_UNICODE | wc;
+}
+
+int
+cmdBrlttyToBrlapi (brlapi_keyCode_t *code, int command, int retainDots) {
+  int blk = command & BRL_MSK_BLK;
+  int arg = BRL_ARG_GET(command);
+
+  switch (blk) {
+    case BRL_CMD_BLK(PASSCHAR):
+      *code = cmdWCharToBrlapi(arg);
+      break;
+
+    case BRL_CMD_BLK(PASSDOTS):
+      if (retainDots) goto doDefault;
+      *code = cmdWCharToBrlapi(convertInputToCharacter(arg));
+      break;
+
+    case BRL_CMD_BLK(PASSKEY):
+      switch (arg) {
+        case BRL_KEY_ENTER:        *code = BRLAPI_KEY_SYM_LINEFEED;  break;
+        case BRL_KEY_TAB:          *code = BRLAPI_KEY_SYM_TAB;       break;
+        case BRL_KEY_BACKSPACE:    *code = BRLAPI_KEY_SYM_BACKSPACE; break;
+        case BRL_KEY_ESCAPE:       *code = BRLAPI_KEY_SYM_ESCAPE;    break;
+        case BRL_KEY_CURSOR_LEFT:  *code = BRLAPI_KEY_SYM_LEFT;      break;
+        case BRL_KEY_CURSOR_RIGHT: *code = BRLAPI_KEY_SYM_RIGHT;     break;
+        case BRL_KEY_CURSOR_UP:    *code = BRLAPI_KEY_SYM_UP;        break;
+        case BRL_KEY_CURSOR_DOWN:  *code = BRLAPI_KEY_SYM_DOWN;      break;
+        case BRL_KEY_PAGE_UP:      *code = BRLAPI_KEY_SYM_PAGE_UP;   break;
+        case BRL_KEY_PAGE_DOWN:    *code = BRLAPI_KEY_SYM_PAGE_DOWN; break;
+        case BRL_KEY_HOME:         *code = BRLAPI_KEY_SYM_HOME;      break;
+        case BRL_KEY_END:          *code = BRLAPI_KEY_SYM_END;       break;
+        case BRL_KEY_INSERT:       *code = BRLAPI_KEY_SYM_INSERT;    break;
+        case BRL_KEY_DELETE:       *code = BRLAPI_KEY_SYM_DELETE;    break;
+
+        default: {
+          int key = arg - BRL_KEY_FUNCTION;
+          if (key < 0) return 0;
+          if (key > 34) return 0;
+          *code = BRLAPI_KEY_SYM_FUNCTION + key;
+          break;
+        }
+      }
+      break;
+
+    default:
+    doDefault:
+      *code = BRLAPI_KEY_TYPE_CMD
+            | (blk >> BRL_SHIFT_BLK << BRLAPI_KEY_CMD_BLK_SHIFT)
+            | (arg                  << BRLAPI_KEY_CMD_ARG_SHIFT)
+            ;
+      break;
+  }
+
+  switch (blk) {
+    case BRL_CMD_BLK(PASSCHAR):
+    case BRL_CMD_BLK(PASSDOTS):
+    case BRL_CMD_BLK(PASSKEY):
+      *code = *code
+            | (command & BRL_FLG_INPUT_SHIFT   ? BRLAPI_KEY_FLG_SHIFT   : 0)
+            | (command & BRL_FLG_INPUT_UPPER   ? BRLAPI_KEY_FLG_UPPER   : 0)
+            | (command & BRL_FLG_INPUT_CONTROL ? BRLAPI_KEY_FLG_CONTROL : 0)
+            | (command & BRL_FLG_INPUT_META    ? BRLAPI_KEY_FLG_META    : 0)
+            | (command & BRL_FLG_INPUT_ALTGR   ? BRLAPI_KEY_FLG_ALTGR   : 0)
+            | (command & BRL_FLG_INPUT_GUI     ? BRLAPI_KEY_FLG_GUI     : 0)
+            ;
+      break;
+
+    case BRL_CMD_BLK(PASSXT):
+    case BRL_CMD_BLK(PASSAT):
+    case BRL_CMD_BLK(PASSPS2):
+      *code = *code
+            | (command & BRL_FLG_KBD_RELEASE ? BRLAPI_KEY_FLG_KBD_RELEASE : 0)
+            | (command & BRL_FLG_KBD_EMUL0   ? BRLAPI_KEY_FLG_KBD_EMUL0   : 0)
+            | (command & BRL_FLG_KBD_EMUL1   ? BRLAPI_KEY_FLG_KBD_EMUL1   : 0)
+            ;
+      break;
+
+    default:
+      *code = *code
+            | (command & BRL_FLG_TOGGLE_ON     ? BRLAPI_KEY_FLG_TOGGLE_ON     : 0)
+            | (command & BRL_FLG_TOGGLE_OFF    ? BRLAPI_KEY_FLG_TOGGLE_OFF    : 0)
+            | (command & BRL_FLG_MOTION_ROUTE  ? BRLAPI_KEY_FLG_MOTION_ROUTE  : 0)
+            | (command & BRL_FLG_MOTION_SCALED ? BRLAPI_KEY_FLG_MOTION_SCALED : 0)
+            | (command & BRL_FLG_MOTION_TOLEFT ? BRLAPI_KEY_FLG_MOTION_TOLEFT : 0)
+            ;
+      break;
+  }
+
+  return 1;
+}
+
+int
+cmdBrlapiToBrltty (brlapi_keyCode_t code) {
+  int cmd;
+
+  switch (code & BRLAPI_KEY_TYPE_MASK) {
+    case BRLAPI_KEY_TYPE_CMD:
+      cmd = BRL_BLK_PUT((code & BRLAPI_KEY_CMD_BLK_MASK) >> BRLAPI_KEY_CMD_BLK_SHIFT);
+      cmd |= BRL_ARG_SET((code & BRLAPI_KEY_CMD_ARG_MASK) >> BRLAPI_KEY_CMD_ARG_SHIFT);
+      break;
+
+    case BRLAPI_KEY_TYPE_SYM: {
+      unsigned long keysym = code & BRLAPI_KEY_CODE_MASK;
+
+      switch (keysym) {
+        case BRLAPI_KEY_SYM_BACKSPACE: cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_BACKSPACE;    break;
+        case BRLAPI_KEY_SYM_TAB:       cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_TAB;          break;
+        case BRLAPI_KEY_SYM_LINEFEED:  cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_ENTER;        break;
+        case BRLAPI_KEY_SYM_ESCAPE:    cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_ESCAPE;       break;
+        case BRLAPI_KEY_SYM_HOME:      cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_HOME;         break;
+        case BRLAPI_KEY_SYM_LEFT:      cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_CURSOR_LEFT;  break;
+        case BRLAPI_KEY_SYM_UP:        cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_CURSOR_UP;    break;
+        case BRLAPI_KEY_SYM_RIGHT:     cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_CURSOR_RIGHT; break;
+        case BRLAPI_KEY_SYM_DOWN:      cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_CURSOR_DOWN;  break;
+        case BRLAPI_KEY_SYM_PAGE_UP:   cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_PAGE_UP;      break;
+        case BRLAPI_KEY_SYM_PAGE_DOWN: cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_PAGE_DOWN;    break;
+        case BRLAPI_KEY_SYM_END:       cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_END;          break;
+        case BRLAPI_KEY_SYM_INSERT:    cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_INSERT;       break;
+        case BRLAPI_KEY_SYM_DELETE:    cmd = BRL_CMD_BLK(PASSKEY)|BRL_KEY_DELETE;       break;
+
+        default:
+          if ((keysym >= BRLAPI_KEY_SYM_FUNCTION) &&
+              (keysym <= (BRLAPI_KEY_SYM_FUNCTION + 34))) {
+            cmd = BRL_CMD_KFN(keysym - BRLAPI_KEY_SYM_FUNCTION);
+          } else if ((keysym < 0X100) ||
+                     ((keysym & 0x1F000000) == BRLAPI_KEY_SYM_UNICODE)) {
+            wchar_t c = keysym & 0xFFFFFF;
+            cmd = BRL_CMD_BLK(PASSCHAR) | BRL_ARG_SET(c);
+          } else {
+            return EOF;
+          }
+          break;
+      }
+
+      break;
+    }
+
+    default:
+      return EOF;
+  }
+
+  switch (cmd & BRL_MSK_BLK) {
+    case BRL_CMD_BLK(PASSCHAR):
+    case BRL_CMD_BLK(PASSDOTS):
+    case BRL_CMD_BLK(PASSKEY):
+      cmd = cmd
+          | (code & BRLAPI_KEY_FLG_SHIFT   ? BRL_FLG_INPUT_SHIFT   : 0)
+          | (code & BRLAPI_KEY_FLG_UPPER   ? BRL_FLG_INPUT_UPPER   : 0)
+          | (code & BRLAPI_KEY_FLG_CONTROL ? BRL_FLG_INPUT_CONTROL : 0)
+          | (code & BRLAPI_KEY_FLG_META    ? BRL_FLG_INPUT_META    : 0)
+          | (code & BRLAPI_KEY_FLG_ALTGR   ? BRL_FLG_INPUT_ALTGR   : 0)
+          | (code & BRLAPI_KEY_FLG_GUI     ? BRL_FLG_INPUT_GUI     : 0)
+          ;
+      break;
+
+    case BRL_CMD_BLK(PASSXT):
+    case BRL_CMD_BLK(PASSAT):
+    case BRL_CMD_BLK(PASSPS2):
+      cmd = cmd
+          | (code & BRLAPI_KEY_FLG_KBD_RELEASE ? BRL_FLG_KBD_RELEASE : 0)
+          | (code & BRLAPI_KEY_FLG_KBD_EMUL0   ? BRL_FLG_KBD_EMUL0   : 0)
+          | (code & BRLAPI_KEY_FLG_KBD_EMUL1   ? BRL_FLG_KBD_EMUL1   : 0)
+          ;
+      break;
+
+    default:
+      cmd = cmd
+          | (code & BRLAPI_KEY_FLG_TOGGLE_ON     ? BRL_FLG_TOGGLE_ON     : 0)
+          | (code & BRLAPI_KEY_FLG_TOGGLE_OFF    ? BRL_FLG_TOGGLE_OFF    : 0)
+          | (code & BRLAPI_KEY_FLG_MOTION_ROUTE  ? BRL_FLG_MOTION_ROUTE  : 0)
+          | (code & BRLAPI_KEY_FLG_MOTION_SCALED ? BRL_FLG_MOTION_SCALED : 0)
+          | (code & BRLAPI_KEY_FLG_MOTION_TOLEFT ? BRL_FLG_MOTION_TOLEFT : 0)
+          ;
+      break;
+  }
+
+  return cmd;
+}
+#endif /* ENABLE_API */
diff --git a/Programs/cmd_brlapi.h b/Programs/cmd_brlapi.h
new file mode 100644
index 0000000..5f68b4c
--- /dev/null
+++ b/Programs/cmd_brlapi.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_BRLAPI
+#define BRLTTY_INCLUDED_CMD_BRLAPI
+
+#include "prologue.h"
+
+#ifdef ENABLE_API
+#include "brlapi_keycodes.h"
+#endif /* ENABLE_API */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef ENABLE_API
+extern int cmdBrlttyToBrlapi (brlapi_keyCode_t *code, int command, int retainDots);
+extern int cmdBrlapiToBrltty (brlapi_keyCode_t code);
+#endif /* ENABLE_API */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_BRLAPI */
diff --git a/Programs/cmd_clipboard.c b/Programs/cmd_clipboard.c
new file mode 100644
index 0000000..1c1ae8f
--- /dev/null
+++ b/Programs/cmd_clipboard.c
@@ -0,0 +1,672 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "log.h"
+#include "alert.h"
+#include "cmd_queue.h"
+#include "cmd_utils.h"
+#include "cmd_clipboard.h"
+#include "clipboard.h"
+#include "brl_cmds.h"
+#include "scr.h"
+#include "routing.h"
+#include "file.h"
+#include "datafile.h"
+#include "utf8.h"
+#include "core.h"
+
+typedef struct {
+  ClipboardObject *clipboard;
+
+  struct {
+    int column;
+    int row;
+    int offset;
+  } begin;
+} ClipboardCommandData;
+
+static wchar_t *
+cpbReadScreen (ClipboardCommandData *ccd, size_t *length, int fromColumn, int fromRow, int toColumn, int toRow) {
+  wchar_t *newBuffer = NULL;
+  int columns = toColumn - fromColumn + 1;
+  int rows = toRow - fromRow + 1;
+
+  if ((columns >= 1) && (rows >= 1) && (ccd->begin.offset >= 0)) {
+    wchar_t fromBuffer[rows * columns];
+
+    if (readScreenText(fromColumn, fromRow, columns, rows, fromBuffer)) {
+      wchar_t toBuffer[rows * (columns + 1)];
+      wchar_t *toAddress = toBuffer;
+
+      const wchar_t *fromAddress = fromBuffer;
+      int row;
+
+      for (row=fromRow; row<=toRow; row+=1) {
+        int column;
+
+        for (column=fromColumn; column<=toColumn; column+=1) {
+          wchar_t character = *fromAddress++;
+          if (iswcntrl(character) || iswspace(character)) character = WC_C(' ');
+          *toAddress++ = character;
+        }
+
+        if (row != toRow) *toAddress++ = WC_C('\r');
+      }
+
+      /* make a new permanent buffer of just the right size */
+      {
+        size_t newLength = toAddress - toBuffer;
+
+        if ((newBuffer = allocateCharacters(newLength))) {
+          wmemcpy(newBuffer, toBuffer, (*length = newLength));
+        }
+      }
+    }
+  }
+
+  return newBuffer;
+}
+
+static int
+cpbEndOperation (ClipboardCommandData *ccd, const wchar_t *characters, size_t length,
+                 int insertCR) {
+  lockMainClipboard();
+    if (insertCR && ccd->begin.offset >= 1) {
+      size_t length;
+      const wchar_t *characters = getClipboardContent(ccd->clipboard, &length);
+      if (length > ccd->begin.offset) length = ccd->begin.offset;
+      while (length > 0) {
+        size_t last = length - 1;
+        if (characters[last] == WC_C('\r')) insertCR = 0;
+        if (characters[last] != WC_C(' ')) break;
+        length = last;
+      }
+      ccd->begin.offset = length;
+    }
+    if (ccd->begin.offset <= 0) insertCR = 0;
+
+    int truncated = truncateClipboardContent(ccd->clipboard, ccd->begin.offset);
+    if (insertCR) appendClipboardContent(ccd->clipboard, &(wchar_t){WC_C('\r')}, 1);
+    int appended = appendClipboardContent(ccd->clipboard, characters, length);
+  unlockMainClipboard();
+
+  if (truncated || appended) onMainClipboardUpdated();
+  if (!appended) return 0;
+  alert(ALERT_CLIPBOARD_END);
+  return 1;
+}
+
+static void
+cpbBeginOperation (ClipboardCommandData *ccd, int column, int row) {
+  ccd->begin.column = column;
+  ccd->begin.row = row;
+
+  lockMainClipboard();
+    ccd->begin.offset = getClipboardContentLength(ccd->clipboard);
+  unlockMainClipboard();
+
+  alert(ALERT_CLIPBOARD_BEGIN);
+}
+
+static int
+cpbRectangularCopy (ClipboardCommandData *ccd, int column, int row) {
+  int copied = 0;
+  size_t length;
+  wchar_t *buffer = cpbReadScreen(ccd, &length, ccd->begin.column, ccd->begin.row, column, row);
+
+  if (buffer) {
+    {
+      const wchar_t *from = buffer;
+      const wchar_t *end = from + length;
+      wchar_t *to = buffer;
+      int spaces = 0;
+
+      while (from != end) {
+        wchar_t character = *from++;
+
+        switch (character) {
+          case WC_C(' '):
+            spaces += 1;
+            continue;
+
+          case WC_C('\r'):
+            spaces = 0;
+            break;
+
+          default:
+            break;
+        }
+
+        while (spaces) {
+          *to++ = WC_C(' ');
+          spaces -= 1;
+        }
+
+        *to++ = character;
+      }
+
+      length = to - buffer;
+    }
+
+    if (cpbEndOperation(ccd, buffer, length, 1)) copied = 1;
+    free(buffer);
+  }
+
+  return copied;
+}
+
+static int
+cpbLinearCopy (ClipboardCommandData *ccd, int column, int row) {
+  int copied = 0;
+  ScreenDescription screen;
+  describeScreen(&screen);
+
+  {
+    int rightColumn = screen.cols - 1;
+    size_t length;
+    wchar_t *buffer = cpbReadScreen(ccd, &length, 0, ccd->begin.row, rightColumn, row);
+
+    if (buffer) {
+      if (column < rightColumn) {
+        wchar_t *start = buffer + length;
+
+        while (start != buffer) {
+          if (*--start == WC_C('\r')) {
+            start += 1;
+            break;
+          }
+        }
+
+        {
+          int adjustment = (column + 1) - (buffer + length - start);
+          if (adjustment < 0) length += adjustment;
+        }
+      }
+
+      if (ccd->begin.column) {
+        wchar_t *start = wmemchr(buffer, WC_C('\r'), length);
+        if (!start) start = buffer + length;
+        if ((start - buffer) > ccd->begin.column) start = buffer + ccd->begin.column;
+        if (start != buffer) wmemmove(buffer, start, (length -= start - buffer));
+      }
+
+      {
+        const wchar_t *from = buffer;
+        const wchar_t *end = from + length;
+        wchar_t *to = buffer;
+        int spaces = 0;
+        int newlines = 0;
+
+        while (from != end) {
+          wchar_t character = *from++;
+
+          switch (character) {
+            case WC_C(' '):
+              spaces += 1;
+              continue;
+
+            case WC_C('\r'):
+              newlines += 1;
+              continue;
+
+            default:
+              break;
+          }
+
+          if (newlines) {
+            if ((newlines > 1) || (spaces > 0)) spaces = 1;
+            newlines = 0;
+          }
+
+          while (spaces) {
+            *to++ = WC_C(' ');
+            spaces -= 1;
+          }
+
+          *to++ = character;
+        }
+
+        if (spaces || newlines) *to++ = WC_C(' ');
+        length = to - buffer;
+      }
+
+      if (cpbEndOperation(ccd, buffer, length, 0)) copied = 1;
+      free(buffer);
+    }
+  }
+
+  return copied;
+}
+
+static int
+pasteCharacters (const wchar_t *characters, size_t count) {
+  if (!characters) return 0;
+  if (!count) return 0;
+
+  if (!isMainScreen()) return 0;
+  if (isRouting()) return 0;
+
+  {
+    unsigned int i;
+
+    for (i=0; i<count; i+=1) {
+      if (!insertScreenKey(characters[i])) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+cpbPaste (ClipboardCommandData *ccd, unsigned int index) {
+  int pasted;
+
+  lockMainClipboard();
+    const wchar_t *characters;
+    size_t length;
+
+    if (index) {
+      characters = getClipboardHistory(ccd->clipboard, index-1, &length);
+    } else {
+      characters = getClipboardContent(ccd->clipboard, &length);
+    }
+
+    while (length > 0) {
+      size_t last = length - 1;
+      if (characters[last] != WC_C(' ')) break;
+      length = last;
+    }
+
+    pasted = pasteCharacters(characters, length);
+  unlockMainClipboard();
+
+  return pasted;
+}
+
+static FILE *
+cpbOpenFile (const char *mode) {
+  const char *file = "clipboard";
+  char *path = makeUpdatablePath(file);
+
+  if (path) {
+    FILE *stream = openDataFile(path, mode, 0);
+
+    free(path);
+    path = NULL;
+
+    if (stream) return stream;
+  }
+
+  return NULL;
+}
+
+static int
+cpbSave (ClipboardCommandData *ccd) {
+  int ok = 0;
+
+  lockMainClipboard();
+    size_t length;
+    const wchar_t *characters = getClipboardContent(ccd->clipboard, &length);
+
+    if (length > 0) {
+      FILE *stream = cpbOpenFile("w");
+
+      if (stream) {
+        if (writeUtf8Characters(stream, characters, length)) {
+          ok = 1;
+        }
+
+        if (fclose(stream) == EOF) {
+          logSystemError("fclose");
+          ok = 0;
+        }
+      }
+    }
+  unlockMainClipboard();
+
+  return ok;
+}
+
+static int
+cpbRestore (ClipboardCommandData *ccd) {
+  int ok = 0;
+  FILE *stream = cpbOpenFile("r");
+
+  if (stream) {
+    int wasUpdated = 0;
+
+    lockMainClipboard();
+    {
+      int isClear = 0;
+
+      if (isClipboardEmpty(ccd->clipboard)) {
+        isClear = 1;
+      } else if (clearClipboardContent(ccd->clipboard)) {
+        isClear = 1;
+        wasUpdated = 1;
+      }
+
+      if (isClear) {
+        ok = 1;
+
+        size_t size = 0X1000;
+        char buffer[size];
+        size_t length = 0;
+
+        do {
+          size_t count = fread(&buffer[length], 1, (size - length), stream);
+          int done = (length += count) < size;
+
+          if (ferror(stream)) {
+            logSystemError("fread");
+            ok = 0;
+          } else {
+            const char *next = buffer;
+            size_t left = length;
+
+            while (left > 0) {
+              const char *start = next;
+              wint_t wi = convertUtf8ToWchar(&next, &left);
+
+              if (wi == WEOF) {
+                length = next - start;
+
+                if (left > 0) {
+                  logBytes(LOG_ERR, "invalid UTF-8 character", start, length);
+                  ok = 0;
+                  break;
+                }
+
+                memmove(buffer, start, length);
+              } else {
+                wchar_t wc = wi;
+
+                if (appendClipboardContent(ccd->clipboard, &wc, 1)) {
+                  wasUpdated = 1;
+                } else {
+                  ok = 0;
+                  break;
+                }
+              }
+            }
+          }
+
+          if (done) break;
+        } while (ok);
+      }
+    }
+    unlockMainClipboard();
+
+    if (fclose(stream) == EOF) {
+      logSystemError("fclose");
+      ok = 0;
+    }
+
+    if (wasUpdated) onMainClipboardUpdated();
+  }
+
+  return ok;
+}
+
+static int
+findCharacters (const wchar_t **address, size_t *length, const wchar_t *characters, size_t count) {
+  const wchar_t *ptr = *address;
+  size_t len = *length;
+
+  while (count <= len) {
+    const wchar_t *next = wmemchr(ptr, *characters, len);
+    if (!next) break;
+
+    len -= next - ptr;
+    if (wmemcmp((ptr = next), characters, count) == 0) {
+      *address = ptr;
+      *length = len;
+      return 1;
+    }
+
+    ++ptr, --len;
+  }
+
+  return 0;
+}
+
+static int
+handleClipboardCommands (int command, void *data) {
+  ClipboardCommandData *ccd = data;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_PASTE:
+      if (!cpbPaste(ccd, 0)) alert(ALERT_COMMAND_REJECTED);
+      break;
+
+    case BRL_CMD_CLIP_SAVE:
+      alert(cpbSave(ccd)? ALERT_COMMAND_DONE: ALERT_COMMAND_REJECTED);
+      break;
+
+    case BRL_CMD_CLIP_RESTORE:
+      alert(cpbRestore(ccd)? ALERT_COMMAND_DONE: ALERT_COMMAND_REJECTED);
+      break;
+
+    {
+      int increment;
+
+      const wchar_t *cpbBuffer;
+      size_t cpbLength;
+
+    case BRL_CMD_PRSEARCH:
+      increment = -1;
+      goto doSearch;
+
+    case BRL_CMD_NXSEARCH:
+      increment = 1;
+      goto doSearch;
+
+    doSearch:
+      lockMainClipboard();
+        if ((cpbBuffer = getClipboardContent(ccd->clipboard, &cpbLength))) {
+          int found = 0;
+          size_t count = cpbLength;
+
+          if (count <= scr.cols) {
+            int line = ses->winy;
+            wchar_t buffer[scr.cols];
+            wchar_t characters[count];
+
+            {
+              unsigned int i;
+              for (i=0; i<count; i+=1) characters[i] = towlower(cpbBuffer[i]);
+            }
+
+            while ((line >= 0) && (line <= (int)(scr.rows - brl.textRows))) {
+              const wchar_t *address = buffer;
+              size_t length = scr.cols;
+              readScreenText(0, line, length, 1, buffer);
+
+              {
+                for (size_t i=0; i<length; i+=1) buffer[i] = towlower(buffer[i]);
+              }
+
+              if (line == ses->winy) {
+                if (increment < 0) {
+                  int end = ses->winx + count - 1;
+                  if (end < length) length = end;
+                } else {
+                  int start = ses->winx + textCount;
+                  if (start > length) start = length;
+                  address += start;
+                  length -= start;
+                }
+              }
+
+              if (findCharacters(&address, &length, characters, count)) {
+                if (increment < 0) {
+                  while (findCharacters(&address, &length, characters, count)) {
+                    ++address, --length;
+                  }
+                }
+
+                ses->winy = line;
+                ses->winx = (address - buffer) / textCount * textCount;
+                found = 1;
+                break;
+              }
+
+              line += increment;
+            }
+          }
+
+          if (!found) alert(ALERT_BOUNCE);
+        } else {
+          alert(ALERT_COMMAND_REJECTED);
+        }
+      unlockMainClipboard();
+
+      break;
+    }
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+      int ext = BRL_CODE_GET(EXT, command);
+
+      switch (command & BRL_MSK_BLK) {
+        {
+          int clear;
+          int column, row;
+
+        case BRL_CMD_BLK(CLIP_NEW):
+          clear = 1;
+          goto doClipBegin;
+
+        case BRL_CMD_BLK(CLIP_ADD):
+          clear = 0;
+          goto doClipBegin;
+
+        doClipBegin:
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            if (clear) clearClipboardContent(ccd->clipboard);
+            cpbBeginOperation(ccd, column, row);
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+
+          break;
+        }
+
+        case BRL_CMD_BLK(COPY_RECT): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, NULL, &column, 1))
+            if (cpbRectangularCopy(ccd, column, row))
+              break;
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(COPY_LINE): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, NULL, &column, 1))
+            if (cpbLinearCopy(ccd, column, row))
+              break;
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        {
+          int clear;
+
+        case BRL_CMD_BLK(CLIP_COPY):
+          clear = 1;
+          goto doCopy;
+
+        case BRL_CMD_BLK(CLIP_APPEND):
+          clear = 0;
+          goto doCopy;
+
+        doCopy:
+          if (ext > arg) {
+            int column1, row1;
+
+            if (getCharacterCoordinates(arg, &row1, &column1, NULL, 0)) {
+              int column2, row2;
+
+              if (getCharacterCoordinates(ext, &row2, NULL, &column2, 1)) {
+                if (clear) clearClipboardContent(ccd->clipboard);
+                cpbBeginOperation(ccd, column1, row1);
+                if (cpbLinearCopy(ccd, column2, row2)) break;
+              }
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(PASTE_HISTORY):
+          if (!cpbPaste(ccd, arg)) alert(ALERT_COMMAND_REJECTED);
+          break;
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+static void
+destroyClipboardCommandData (void *data) {
+  ClipboardCommandData *ccd = data;
+  free(ccd);
+}
+
+int
+addClipboardCommands (void) {
+  ClipboardCommandData *ccd;
+
+  if ((ccd = malloc(sizeof(*ccd)))) {
+    memset(ccd, 0, sizeof(*ccd));
+    ccd->clipboard = getMainClipboard();
+
+    ccd->begin.column = 0;
+    ccd->begin.row = 0;
+    ccd->begin.offset = -1;
+
+    if (pushCommandHandler("clipboard", KTB_CTX_DEFAULT,
+                           handleClipboardCommands, destroyClipboardCommandData, ccd)) {
+      return 1;
+    }
+
+    free(ccd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_clipboard.h b/Programs/cmd_clipboard.h
new file mode 100644
index 0000000..b5ece9f
--- /dev/null
+++ b/Programs/cmd_clipboard.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_CLIPBOARD
+#define BRLTTY_INCLUDED_CMD_CLIPBOARD
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addClipboardCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_CLIPBOARD */
diff --git a/Programs/cmd_custom.c b/Programs/cmd_custom.c
new file mode 100644
index 0000000..53a645d
--- /dev/null
+++ b/Programs/cmd_custom.c
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "cmd_queue.h"
+#include "cmd_custom.h"
+#include "brl_cmds.h"
+#include "brl_custom.h"
+
+static int
+handleCustomCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    default: {
+      switch (command & BRL_MSK_BLK) {
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+int
+addCustomCommands (void) {
+  return pushCommandHandler("custom", KTB_CTX_DEFAULT,
+                            handleCustomCommands, NULL, NULL);
+}
diff --git a/Programs/cmd_custom.h b/Programs/cmd_custom.h
new file mode 100644
index 0000000..9d7a4f7
--- /dev/null
+++ b/Programs/cmd_custom.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_CUSTOM
+#define BRLTTY_INCLUDED_CMD_CUSTOM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addCustomCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_CUSTOM */
diff --git a/Programs/cmd_input.c b/Programs/cmd_input.c
new file mode 100644
index 0000000..8783a8e
--- /dev/null
+++ b/Programs/cmd_input.c
@@ -0,0 +1,442 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "report.h"
+#include "alert.h"
+#include "parameters.h"
+#include "cmd_queue.h"
+#include "cmd_input.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "unicode.h"
+#include "ttb.h"
+#include "scr.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "prefs.h"
+#include "core.h"
+
+typedef struct {
+  ReportListenerInstance *resetListener;
+
+  struct {
+    AsyncHandle timeout;
+    int once;
+    int lock;
+  } modifiers;
+} InputCommandData;
+
+static void
+initializeModifierTimeout (InputCommandData *icd) {
+  icd->modifiers.timeout = NULL;
+}
+
+static void
+cancelModifierTimeout (InputCommandData *icd) {
+  if (icd->modifiers.timeout) {
+    asyncCancelRequest(icd->modifiers.timeout);
+    initializeModifierTimeout(icd);
+  }
+}
+
+static void
+initializeModifierFlags (InputCommandData *icd) {
+  icd->modifiers.once = 0;
+  icd->modifiers.lock = 0;
+}
+
+static void
+clearModifierFlags (InputCommandData *icd) {
+  initializeModifierFlags(icd);
+
+  if (prefs.speakModifierKey) {
+    speakAlertMessage("modifiers reset");
+  } else {
+    alert(ALERT_MODIFIER_OFF);
+  }
+}
+
+ASYNC_ALARM_CALLBACK(handleStickyModifiersTimeout) {
+  InputCommandData *icd = parameters->data;
+
+  asyncDiscardHandle(icd->modifiers.timeout);
+  initializeModifierTimeout(icd);
+
+  clearModifierFlags(icd);
+}
+
+static int
+haveModifierFlags (InputCommandData *icd) {
+  return icd->modifiers.once || icd->modifiers.lock;
+}
+
+static int
+setModifierTimeout (InputCommandData *icd) {
+  if (!haveModifierFlags(icd)) {
+    cancelModifierTimeout(icd);
+    return 1;
+  }
+
+  return icd->modifiers.timeout?
+         asyncResetAlarmIn(icd->modifiers.timeout,
+                           INPUT_STICKY_MODIFIERS_TIMEOUT):
+         asyncNewRelativeAlarm(&icd->modifiers.timeout,
+                               INPUT_STICKY_MODIFIERS_TIMEOUT,
+                               handleStickyModifiersTimeout, icd);
+}
+
+static void
+applyModifierFlags (InputCommandData *icd, int *flags) {
+  *flags |= icd->modifiers.lock;
+  *flags |= icd->modifiers.once;
+  icd->modifiers.once = 0;
+
+  setModifierTimeout(icd);
+}
+
+static int
+insertKey (ScreenKey key, int flags) {
+  if (flags & BRL_FLG_INPUT_SHIFT) key |= SCR_KEY_SHIFT;
+  if (flags & BRL_FLG_INPUT_UPPER) key |= SCR_KEY_UPPER;
+  if (flags & BRL_FLG_INPUT_CONTROL) key |= SCR_KEY_CONTROL;
+  if (flags & BRL_FLG_INPUT_META) key |= SCR_KEY_ALT_LEFT;
+  if (flags & BRL_FLG_INPUT_ALTGR) key |= SCR_KEY_ALT_RIGHT;
+  if (flags & BRL_FLG_INPUT_GUI) key |= SCR_KEY_GUI;
+
+  if (flags & BRL_FLG_INPUT_ESCAPED) {
+    if (!insertScreenKey(SCR_KEY_ESCAPE)) {
+      return 0;
+    }
+  }
+
+  return insertScreenKey(key);
+}
+
+static void
+handleVirtualTerminalSwitched (int switched) {
+  if (switched) {
+    updateSessionAttributes();
+  } else {
+    alert(ALERT_COMMAND_REJECTED);
+  }
+}
+
+static int
+selectVirtualTerminal (int vt) {
+  int selected = selectScreenVirtualTerminal(vt);
+
+  if (selected) {
+    updateSessionAttributes();
+  } else {
+    alert(ALERT_COMMAND_REJECTED);
+  }
+
+  return selected;
+}
+
+static int
+handleInputCommands (int command, void *data) {
+  InputCommandData *icd = data;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_UNSTICK: {
+      cancelModifierTimeout(icd);
+
+      if (haveModifierFlags(icd)) {
+        clearModifierFlags(icd);
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    {
+      int modifierFlag;
+      const char *modifierName;
+
+    case BRL_CMD_SHIFT:
+      modifierFlag = BRL_FLG_INPUT_SHIFT;
+      modifierName = "shift";
+      goto doModifier;
+
+    case BRL_CMD_UPPER:
+      modifierFlag = BRL_FLG_INPUT_UPPER;
+      modifierName = "uppercase";
+      goto doModifier;
+
+    case BRL_CMD_CONTROL:
+      modifierFlag = BRL_FLG_INPUT_CONTROL;
+      modifierName = "control";
+      goto doModifier;
+
+    case BRL_CMD_META:
+      modifierFlag = BRL_FLG_INPUT_META;
+      modifierName = "left alt";
+      goto doModifier;
+
+    case BRL_CMD_ALTGR:
+      modifierFlag = BRL_FLG_INPUT_ALTGR;
+      modifierName = "right alt";
+      goto doModifier;
+
+    case BRL_CMD_GUI:
+      modifierFlag = BRL_FLG_INPUT_GUI;
+      modifierName = "graphic";
+      goto doModifier;
+
+    doModifier:
+      cancelModifierTimeout(icd);
+
+      AlertIdentifier modifierAlert;
+      const char *modifierState;
+
+      if (icd->modifiers.lock & modifierFlag) {
+        icd->modifiers.once &= ~modifierFlag;
+        icd->modifiers.lock &= ~modifierFlag;
+        modifierAlert = ALERT_MODIFIER_OFF;
+        modifierState = "off";
+      } else if (icd->modifiers.once & modifierFlag) {
+        icd->modifiers.once &= ~modifierFlag;
+        icd->modifiers.lock |= modifierFlag;
+        modifierAlert = ALERT_MODIFIER_LOCK;
+        modifierState = "lock";
+      } else {
+        icd->modifiers.once |= modifierFlag;
+        modifierAlert = ALERT_MODIFIER_ONCE;
+        modifierState = "once";
+      }
+
+      if (prefs.speakModifierKey) {
+        char message[strlen(modifierName) + 2 + strlen(modifierState) + 1];
+
+        snprintf(
+          message, sizeof(message),
+          "%s: %s", modifierName, modifierState
+        );
+
+        speakAlertMessage(message);
+      } else {
+        alert(modifierAlert);
+      }
+
+      setModifierTimeout(icd);
+      break;
+    }
+
+    case BRL_CMD_SWITCHVT_PREV:
+      handleVirtualTerminalSwitched(previousScreenVirtualTerminal());
+      break;
+
+    case BRL_CMD_SWITCHVT_NEXT:
+      handleVirtualTerminalSwitched(nextScreenVirtualTerminal());
+      break;
+
+    case BRL_CMD_SELECTVT_PREV:
+      selectVirtualTerminal(scr.number-1);
+      break;
+
+    case BRL_CMD_SELECTVT_NEXT:
+      selectVirtualTerminal(scr.number+1);
+      break;
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+      int flags = command & BRL_MSK_FLG;
+
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(PASSKEY): {
+          ScreenKey key;
+          int mightScroll = 0;
+
+          switch (arg) {
+            case BRL_KEY_ENTER:
+              key = SCR_KEY_ENTER;
+              break;
+
+            case BRL_KEY_TAB:
+              key = SCR_KEY_TAB;
+              break;
+
+            case BRL_KEY_BACKSPACE:
+              key = SCR_KEY_BACKSPACE;
+              break;
+
+            case BRL_KEY_ESCAPE:
+              key = SCR_KEY_ESCAPE;
+              break;
+
+            case BRL_KEY_CURSOR_LEFT:
+              key = SCR_KEY_CURSOR_LEFT;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_CURSOR_RIGHT:
+              key = SCR_KEY_CURSOR_RIGHT;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_CURSOR_UP:
+              key = SCR_KEY_CURSOR_UP;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_CURSOR_DOWN:
+              key = SCR_KEY_CURSOR_DOWN;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_PAGE_UP:
+              key = SCR_KEY_PAGE_UP;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_PAGE_DOWN:
+              key = SCR_KEY_PAGE_DOWN;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_HOME:
+              key = SCR_KEY_HOME;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_END:
+              key = SCR_KEY_END;
+              mightScroll = 1;
+              break;
+
+            case BRL_KEY_INSERT:
+              key = SCR_KEY_INSERT;
+              break;
+
+            case BRL_KEY_DELETE:
+              key = SCR_KEY_DELETE;
+              break;
+
+            default:
+              if (arg < BRL_KEY_FUNCTION) goto REJECT_KEY;
+              key = SCR_KEY_FUNCTION + (arg - BRL_KEY_FUNCTION);
+              break;
+          }
+
+          if (mightScroll && prefs.scrollAwareCursorNavigation && ses->trackScreenCursor) {
+            if (!trackScreenCursor(1)) {
+              goto REJECT_KEY;
+            }
+          }
+
+          applyModifierFlags(icd, &flags);
+          if (!insertKey(key, flags)) goto REJECT_KEY;
+          break;
+
+        REJECT_KEY:
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(PASSCHAR): {
+          applyModifierFlags(icd, &flags);
+          if (!insertKey(BRL_ARG_GET(command), flags)) alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(PASSDOTS): {
+          applyModifierFlags(icd, &flags);
+          wchar_t character = convertInputToCharacter(arg);
+
+          if (!insertKey(character, flags)) {
+            alert(ALERT_COMMAND_REJECTED);
+          } else if ((command & BRL_DOTC) && arg) {
+            if (!insertKey(WC_C(' '), flags)) alert(ALERT_COMMAND_REJECTED);
+          }
+
+          break;
+        }
+
+        case BRL_CMD_BLK(SWITCHVT):
+          handleVirtualTerminalSwitched(switchScreenVirtualTerminal(arg+1));
+          break;
+
+        case BRL_CMD_BLK(SELECTVT):
+          selectVirtualTerminal(arg+1);
+          break;
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+static void
+resetInputCommandData (InputCommandData *icd) {
+  cancelModifierTimeout(icd);
+  initializeModifierFlags(icd);
+}
+
+REPORT_LISTENER(inputCommandDataResetListener) {
+  InputCommandData *icd = parameters->listenerData;
+
+  resetInputCommandData(icd);
+}
+
+static void
+destroyInputCommandData (void *data) {
+  InputCommandData *icd = data;
+
+  unregisterReportListener(icd->resetListener);
+  cancelModifierTimeout(icd);
+  free(icd);
+}
+
+int
+addInputCommands (void) {
+  InputCommandData *icd;
+
+  if ((icd = malloc(sizeof(*icd)))) {
+    memset(icd, 0, sizeof(*icd));
+    initializeModifierTimeout(icd);
+    initializeModifierFlags(icd);
+
+    if ((icd->resetListener = registerReportListener(REPORT_BRAILLE_DEVICE_ONLINE, inputCommandDataResetListener, icd))) {
+      if (pushCommandHandler("input", KTB_CTX_DEFAULT,
+                             handleInputCommands, destroyInputCommandData, icd)) {
+        return 1;
+      }
+
+      unregisterReportListener(icd->resetListener);
+    }
+
+    free(icd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_input.h b/Programs/cmd_input.h
new file mode 100644
index 0000000..f6ea5ea
--- /dev/null
+++ b/Programs/cmd_input.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_INPUT
+#define BRLTTY_INCLUDED_CMD_INPUT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addInputCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_INPUT */
diff --git a/Programs/cmd_keycodes.c b/Programs/cmd_keycodes.c
new file mode 100644
index 0000000..6295253
--- /dev/null
+++ b/Programs/cmd_keycodes.c
@@ -0,0 +1,814 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "report.h"
+#include "cmd_queue.h"
+#include "cmd_keycodes.h"
+#include "kbd_keycodes.h"
+#include "brl_cmds.h"
+#include "alert.h"
+
+typedef enum {
+  MOD_RELEASE = 0, /* must be first */
+
+  MOD_GUI_LEFT,
+  MOD_GUI_RIGHT,
+  MOD_CONTEXT,
+
+  MOD_LOCK_CAPS,
+  MOD_LOCK_SCROLL,
+  MOD_LOCK_NUMBER,
+
+  MOD_SHIFT_LEFT,
+  MOD_SHIFT_RIGHT,
+
+  MOD_CONTROL_LEFT,
+  MOD_CONTROL_RIGHT,
+
+  MOD_ALT_LEFT,
+  MOD_ALT_RIGHT
+} Modifier;
+
+#define MOD_BIT(number) (1 << (number))
+#define MOD_SET(number, bits) ((bits) |= MOD_BIT((number)))
+#define MOD_CLR(number, bits) ((bits) &= ~MOD_BIT((number)))
+#define MOD_TST(number, bits) ((bits) & MOD_BIT((number)))
+
+typedef struct {
+  int command;
+  int alternate;
+} KeyEntry;
+
+static const KeyEntry keyEntry_Escape = {BRL_CMD_KEY(ESCAPE)};
+static const KeyEntry keyEntry_F1 = {BRL_CMD_KFN(1)};
+static const KeyEntry keyEntry_F2 = {BRL_CMD_KFN(2)};
+static const KeyEntry keyEntry_F3 = {BRL_CMD_KFN(3)};
+static const KeyEntry keyEntry_F4 = {BRL_CMD_KFN(4)};
+static const KeyEntry keyEntry_F5 = {BRL_CMD_KFN(5)};
+static const KeyEntry keyEntry_F6 = {BRL_CMD_KFN(6)};
+static const KeyEntry keyEntry_F7 = {BRL_CMD_KFN(7)};
+static const KeyEntry keyEntry_F8 = {BRL_CMD_KFN(8)};
+static const KeyEntry keyEntry_F9 = {BRL_CMD_KFN(9)};
+static const KeyEntry keyEntry_F10 = {BRL_CMD_KFN(10)};
+static const KeyEntry keyEntry_F11 = {BRL_CMD_KFN(11)};
+static const KeyEntry keyEntry_F12 = {BRL_CMD_KFN(12)};
+static const KeyEntry keyEntry_ScrollLock = {MOD_LOCK_SCROLL};
+
+static const KeyEntry keyEntry_F13 = {BRL_CMD_KFN(13)};
+static const KeyEntry keyEntry_F14 = {BRL_CMD_KFN(14)};
+static const KeyEntry keyEntry_F15 = {BRL_CMD_KFN(15)};
+static const KeyEntry keyEntry_F16 = {BRL_CMD_KFN(16)};
+static const KeyEntry keyEntry_F17 = {BRL_CMD_KFN(17)};
+static const KeyEntry keyEntry_F18 = {BRL_CMD_KFN(18)};
+static const KeyEntry keyEntry_F19 = {BRL_CMD_KFN(19)};
+static const KeyEntry keyEntry_F20 = {BRL_CMD_KFN(20)};
+static const KeyEntry keyEntry_F21 = {BRL_CMD_KFN(21)};
+static const KeyEntry keyEntry_F22 = {BRL_CMD_KFN(22)};
+static const KeyEntry keyEntry_F23 = {BRL_CMD_KFN(23)};
+static const KeyEntry keyEntry_F24 = {BRL_CMD_KFN(24)};
+
+static const KeyEntry keyEntry_Grave = {BRL_CMD_CHAR(WC_C('`')), BRL_CMD_CHAR(WC_C('~'))};
+static const KeyEntry keyEntry_1 = {BRL_CMD_CHAR(WC_C('1')), BRL_CMD_CHAR(WC_C('!'))};
+static const KeyEntry keyEntry_2 = {BRL_CMD_CHAR(WC_C('2')), BRL_CMD_CHAR(WC_C('@'))};
+static const KeyEntry keyEntry_3 = {BRL_CMD_CHAR(WC_C('3')), BRL_CMD_CHAR(WC_C('#'))};
+static const KeyEntry keyEntry_4 = {BRL_CMD_CHAR(WC_C('4')), BRL_CMD_CHAR(WC_C('$'))};
+static const KeyEntry keyEntry_5 = {BRL_CMD_CHAR(WC_C('5')), BRL_CMD_CHAR(WC_C('%'))};
+static const KeyEntry keyEntry_6 = {BRL_CMD_CHAR(WC_C('6')), BRL_CMD_CHAR(WC_C('^'))};
+static const KeyEntry keyEntry_7 = {BRL_CMD_CHAR(WC_C('7')), BRL_CMD_CHAR(WC_C('&'))};
+static const KeyEntry keyEntry_8 = {BRL_CMD_CHAR(WC_C('8')), BRL_CMD_CHAR(WC_C('*'))};
+static const KeyEntry keyEntry_9 = {BRL_CMD_CHAR(WC_C('9')), BRL_CMD_CHAR(WC_C('('))};
+static const KeyEntry keyEntry_0 = {BRL_CMD_CHAR(WC_C('0')), BRL_CMD_CHAR(WC_C(')'))};
+static const KeyEntry keyEntry_Minus = {BRL_CMD_CHAR(WC_C('-')), BRL_CMD_CHAR(WC_C('_'))};
+static const KeyEntry keyEntry_Equal = {BRL_CMD_CHAR(WC_C('=')), BRL_CMD_CHAR(WC_C('+'))};
+static const KeyEntry keyEntry_Backspace = {BRL_CMD_KEY(BACKSPACE)};
+
+static const KeyEntry keyEntry_Tab = {BRL_CMD_KEY(TAB)};
+static const KeyEntry keyEntry_Q = {BRL_CMD_CHAR(WC_C('q')), BRL_CMD_CHAR(WC_C('Q'))};
+static const KeyEntry keyEntry_W = {BRL_CMD_CHAR(WC_C('w')), BRL_CMD_CHAR(WC_C('W'))};
+static const KeyEntry keyEntry_E = {BRL_CMD_CHAR(WC_C('e')), BRL_CMD_CHAR(WC_C('E'))};
+static const KeyEntry keyEntry_R = {BRL_CMD_CHAR(WC_C('r')), BRL_CMD_CHAR(WC_C('R'))};
+static const KeyEntry keyEntry_T = {BRL_CMD_CHAR(WC_C('t')), BRL_CMD_CHAR(WC_C('T'))};
+static const KeyEntry keyEntry_Y = {BRL_CMD_CHAR(WC_C('y')), BRL_CMD_CHAR(WC_C('Y'))};
+static const KeyEntry keyEntry_U = {BRL_CMD_CHAR(WC_C('u')), BRL_CMD_CHAR(WC_C('U'))};
+static const KeyEntry keyEntry_I = {BRL_CMD_CHAR(WC_C('i')), BRL_CMD_CHAR(WC_C('I'))};
+static const KeyEntry keyEntry_O = {BRL_CMD_CHAR(WC_C('o')), BRL_CMD_CHAR(WC_C('O'))};
+static const KeyEntry keyEntry_P = {BRL_CMD_CHAR(WC_C('p')), BRL_CMD_CHAR(WC_C('P'))};
+static const KeyEntry keyEntry_LeftBracket = {BRL_CMD_CHAR(WC_C('[')), BRL_CMD_CHAR(WC_C('{'))};
+static const KeyEntry keyEntry_RightBracket = {BRL_CMD_CHAR(WC_C(']')), BRL_CMD_CHAR(WC_C('}'))};
+static const KeyEntry keyEntry_Backslash = {BRL_CMD_CHAR('\\'), BRL_CMD_CHAR(WC_C('|'))};
+
+static const KeyEntry keyEntry_CapsLock = {MOD_LOCK_CAPS};
+static const KeyEntry keyEntry_A = {BRL_CMD_CHAR(WC_C('a')), BRL_CMD_CHAR(WC_C('A'))};
+static const KeyEntry keyEntry_S = {BRL_CMD_CHAR(WC_C('s')), BRL_CMD_CHAR(WC_C('S'))};
+static const KeyEntry keyEntry_D = {BRL_CMD_CHAR(WC_C('d')), BRL_CMD_CHAR(WC_C('D'))};
+static const KeyEntry keyEntry_F = {BRL_CMD_CHAR(WC_C('f')), BRL_CMD_CHAR(WC_C('F'))};
+static const KeyEntry keyEntry_G = {BRL_CMD_CHAR(WC_C('g')), BRL_CMD_CHAR(WC_C('G'))};
+static const KeyEntry keyEntry_H = {BRL_CMD_CHAR(WC_C('h')), BRL_CMD_CHAR(WC_C('H'))};
+static const KeyEntry keyEntry_J = {BRL_CMD_CHAR(WC_C('j')), BRL_CMD_CHAR(WC_C('J'))};
+static const KeyEntry keyEntry_K = {BRL_CMD_CHAR(WC_C('k')), BRL_CMD_CHAR(WC_C('K'))};
+static const KeyEntry keyEntry_L = {BRL_CMD_CHAR(WC_C('l')), BRL_CMD_CHAR(WC_C('L'))};
+static const KeyEntry keyEntry_Semicolon = {BRL_CMD_CHAR(WC_C(';')), BRL_CMD_CHAR(WC_C(':'))};
+static const KeyEntry keyEntry_Apostrophe = {BRL_CMD_CHAR(WC_C('\'')), BRL_CMD_CHAR(WC_C('"'))};
+static const KeyEntry keyEntry_Enter = {BRL_CMD_KEY(ENTER)};
+
+static const KeyEntry keyEntry_LeftShift = {MOD_SHIFT_LEFT};
+static const KeyEntry keyEntry_Europe2 = {BRL_CMD_CHAR(WC_C('<')), BRL_CMD_CHAR(WC_C('>'))};
+static const KeyEntry keyEntry_Z = {BRL_CMD_CHAR(WC_C('z')), BRL_CMD_CHAR(WC_C('Z'))};
+static const KeyEntry keyEntry_X = {BRL_CMD_CHAR(WC_C('x')), BRL_CMD_CHAR(WC_C('X'))};
+static const KeyEntry keyEntry_C = {BRL_CMD_CHAR(WC_C('c')), BRL_CMD_CHAR(WC_C('C'))};
+static const KeyEntry keyEntry_V = {BRL_CMD_CHAR(WC_C('v')), BRL_CMD_CHAR(WC_C('V'))};
+static const KeyEntry keyEntry_B = {BRL_CMD_CHAR(WC_C('b')), BRL_CMD_CHAR(WC_C('B'))};
+static const KeyEntry keyEntry_N = {BRL_CMD_CHAR(WC_C('n')), BRL_CMD_CHAR(WC_C('N'))};
+static const KeyEntry keyEntry_M = {BRL_CMD_CHAR(WC_C('m')), BRL_CMD_CHAR(WC_C('M'))};
+static const KeyEntry keyEntry_Comma = {BRL_CMD_CHAR(WC_C(',')), BRL_CMD_CHAR(WC_C('<'))};
+static const KeyEntry keyEntry_Period = {BRL_CMD_CHAR(WC_C('.')), BRL_CMD_CHAR(WC_C('>'))};
+static const KeyEntry keyEntry_Slash = {BRL_CMD_CHAR(WC_C('/')), BRL_CMD_CHAR(WC_C('?'))};
+static const KeyEntry keyEntry_RightShift = {MOD_SHIFT_RIGHT};
+
+static const KeyEntry keyEntry_LeftControl = {MOD_CONTROL_LEFT};
+static const KeyEntry keyEntry_LeftGUI = {MOD_GUI_LEFT};
+static const KeyEntry keyEntry_LeftAlt = {MOD_ALT_LEFT};
+static const KeyEntry keyEntry_Space = {BRL_CMD_CHAR(WC_C(' '))};
+static const KeyEntry keyEntry_RightAlt = {MOD_ALT_RIGHT};
+static const KeyEntry keyEntry_RightGUI = {MOD_GUI_RIGHT};
+static const KeyEntry keyEntry_Context = {MOD_CONTEXT};
+static const KeyEntry keyEntry_RightControl = {MOD_CONTROL_RIGHT};
+
+static const KeyEntry keyEntry_Insert = {BRL_CMD_KEY(INSERT)};
+static const KeyEntry keyEntry_Delete = {BRL_CMD_KEY(DELETE)};
+static const KeyEntry keyEntry_Home = {BRL_CMD_KEY(HOME)};
+static const KeyEntry keyEntry_End = {BRL_CMD_KEY(END)};
+static const KeyEntry keyEntry_PageUp = {BRL_CMD_KEY(PAGE_UP)};
+static const KeyEntry keyEntry_PageDown = {BRL_CMD_KEY(PAGE_DOWN)};
+
+static const KeyEntry keyEntry_ArrowUp = {BRL_CMD_KEY(CURSOR_UP)};
+static const KeyEntry keyEntry_ArrowLeft = {BRL_CMD_KEY(CURSOR_LEFT)};
+static const KeyEntry keyEntry_ArrowDown = {BRL_CMD_KEY(CURSOR_DOWN)};
+static const KeyEntry keyEntry_ArrowRight = {BRL_CMD_KEY(CURSOR_RIGHT)};
+
+static const KeyEntry keyEntry_NumLock = {MOD_LOCK_NUMBER};
+static const KeyEntry keyEntry_KPSlash = {BRL_CMD_CHAR(WC_C('/'))};
+static const KeyEntry keyEntry_KPAsterisk = {BRL_CMD_CHAR(WC_C('*'))};
+static const KeyEntry keyEntry_KPMinus = {BRL_CMD_CHAR(WC_C('-'))};
+static const KeyEntry keyEntry_KPPlus = {BRL_CMD_CHAR(WC_C('+'))};
+static const KeyEntry keyEntry_KPEnter = {BRL_CMD_KEY(ENTER)};
+static const KeyEntry keyEntry_KPPeriod = {BRL_CMD_KEY(DELETE), BRL_CMD_CHAR(WC_C('.'))};
+static const KeyEntry keyEntry_KP0 = {BRL_CMD_KEY(INSERT), BRL_CMD_CHAR(WC_C('0'))};
+static const KeyEntry keyEntry_KP1 = {BRL_CMD_KEY(END), BRL_CMD_CHAR(WC_C('1'))};
+static const KeyEntry keyEntry_KP2 = {BRL_CMD_KEY(CURSOR_DOWN), BRL_CMD_CHAR(WC_C('2'))};
+static const KeyEntry keyEntry_KP3 = {BRL_CMD_KEY(PAGE_DOWN), BRL_CMD_CHAR(WC_C('3'))};
+static const KeyEntry keyEntry_KP4 = {BRL_CMD_KEY(CURSOR_LEFT), BRL_CMD_CHAR(WC_C('4'))};
+static const KeyEntry keyEntry_KP5 = {BRL_CMD_CHAR(WC_C('5'))};
+static const KeyEntry keyEntry_KP6 = {BRL_CMD_KEY(CURSOR_RIGHT), BRL_CMD_CHAR(WC_C('6'))};
+static const KeyEntry keyEntry_KP7 = {BRL_CMD_KEY(HOME), BRL_CMD_CHAR(WC_C('7'))};
+static const KeyEntry keyEntry_KP8 = {BRL_CMD_KEY(CURSOR_UP), BRL_CMD_CHAR(WC_C('8'))};
+static const KeyEntry keyEntry_KP9 = {BRL_CMD_KEY(PAGE_UP), BRL_CMD_CHAR(WC_C('9'))};
+static const KeyEntry keyEntry_KPComma = {BRL_CMD_CHAR(WC_C(','))};
+
+typedef struct {
+  ReportListenerInstance *resetListener;
+
+  struct {
+    const KeyEntry *const *keyMap;
+    size_t keyCount;
+    unsigned int modifiers;
+  } xt;
+
+  struct {
+    const KeyEntry *const *keyMap;
+    size_t keyCount;
+    unsigned int modifiers;
+  } at;
+
+  struct {
+    unsigned int modifiers;
+  } ps2;
+} KeycodeCommandData;
+
+#define USE_KEY_MAP(set,escape) \
+  (kcd->set.keyCount = (kcd->set.keyMap = set##KeyMap##escape)? \
+  (sizeof(set##KeyMap##escape) / sizeof(*kcd->set.keyMap)): 0)
+
+static void
+handleKey (const KeyEntry *key, int release, unsigned int *modifiers) {
+  if (key) {
+    int cmd = key->command;
+    int blk = cmd & BRL_MSK_BLK;
+
+    if (key->alternate) {
+      int alternate = 0;
+
+      if (blk == BRL_CMD_BLK(PASSCHAR)) {
+        if (MOD_TST(MOD_SHIFT_LEFT, *modifiers) || MOD_TST(MOD_SHIFT_RIGHT, *modifiers)) alternate = 1;
+      } else {
+        if (MOD_TST(MOD_LOCK_NUMBER, *modifiers)) alternate = 1;
+      }
+
+      if (alternate) {
+        cmd = key->alternate;
+        blk = cmd & BRL_MSK_BLK;
+      }
+    }
+
+    if (cmd) {
+      if (blk) {
+        if (!release) {
+          if (blk == BRL_CMD_BLK(PASSCHAR)) {
+            if (MOD_TST(MOD_LOCK_CAPS, *modifiers)) cmd |= BRL_FLG_INPUT_UPPER;
+            if (MOD_TST(MOD_ALT_LEFT, *modifiers)) cmd |= BRL_FLG_INPUT_META;
+            if (MOD_TST(MOD_ALT_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_ALTGR;
+            if (MOD_TST(MOD_GUI_LEFT, *modifiers) || MOD_TST(MOD_GUI_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_GUI;
+            if (MOD_TST(MOD_CONTROL_LEFT, *modifiers) || MOD_TST(MOD_CONTROL_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_CONTROL;
+          } else if ((blk == BRL_CMD_BLK(PASSKEY)) && MOD_TST(MOD_ALT_LEFT, *modifiers)) {
+            int arg = cmd & BRL_MSK_ARG;
+
+            switch (arg) {
+              case BRL_KEY_CURSOR_LEFT:
+                cmd = BRL_CMD_SWITCHVT_PREV;
+                break;
+
+              case BRL_KEY_CURSOR_RIGHT:
+                cmd = BRL_CMD_SWITCHVT_NEXT;
+                break;
+
+              default:
+                if (arg >= BRL_KEY_FUNCTION) {
+                  cmd = BRL_CMD_BLK(SWITCHVT) + (arg - BRL_KEY_FUNCTION);
+                }
+
+                break;
+            }
+          }
+
+          handleCommand(cmd);
+        }
+      } else {
+        switch (cmd) {
+          case MOD_LOCK_SCROLL:
+          case MOD_LOCK_NUMBER:
+          case MOD_LOCK_CAPS:
+            if (!release) {
+              if (MOD_TST(cmd, *modifiers)) {
+                MOD_CLR(cmd, *modifiers);
+              } else {
+                MOD_SET(cmd, *modifiers);
+              }
+            }
+
+            break;
+
+          case MOD_SHIFT_LEFT:
+          case MOD_SHIFT_RIGHT:
+          case MOD_CONTROL_LEFT:
+          case MOD_CONTROL_RIGHT:
+          case MOD_ALT_LEFT:
+          case MOD_ALT_RIGHT:
+            if (release) {
+              MOD_CLR(cmd, *modifiers);
+            } else {
+              MOD_SET(cmd, *modifiers);
+            }
+
+            break;
+        }
+      }
+    }
+  }
+}
+
+static const KeyEntry *const xtKeyMap00[] = {
+  [XT_KEY_00_Escape] = &keyEntry_Escape,
+  [XT_KEY_00_F1] = &keyEntry_F1,
+  [XT_KEY_00_F2] = &keyEntry_F2,
+  [XT_KEY_00_F3] = &keyEntry_F3,
+  [XT_KEY_00_F4] = &keyEntry_F4,
+  [XT_KEY_00_F5] = &keyEntry_F5,
+  [XT_KEY_00_F6] = &keyEntry_F6,
+  [XT_KEY_00_F7] = &keyEntry_F7,
+  [XT_KEY_00_F8] = &keyEntry_F8,
+  [XT_KEY_00_F9] = &keyEntry_F9,
+  [XT_KEY_00_F10] = &keyEntry_F10,
+  [XT_KEY_00_F11] = &keyEntry_F11,
+  [XT_KEY_00_F12] = &keyEntry_F12,
+  [XT_KEY_00_ScrollLock] = &keyEntry_ScrollLock,
+
+  [XT_KEY_00_F13] = &keyEntry_F13,
+  [XT_KEY_00_F14] = &keyEntry_F14,
+  [XT_KEY_00_F15] = &keyEntry_F15,
+  [XT_KEY_00_F16] = &keyEntry_F16,
+  [XT_KEY_00_F17] = &keyEntry_F17,
+  [XT_KEY_00_F18] = &keyEntry_F18,
+  [XT_KEY_00_F19] = &keyEntry_F19,
+  [XT_KEY_00_F20] = &keyEntry_F20,
+  [XT_KEY_00_F21] = &keyEntry_F21,
+  [XT_KEY_00_F22] = &keyEntry_F22,
+  [XT_KEY_00_F23] = &keyEntry_F23,
+  [XT_KEY_00_F24] = &keyEntry_F24,
+
+  [XT_KEY_00_Grave] = &keyEntry_Grave,
+  [XT_KEY_00_1] = &keyEntry_1,
+  [XT_KEY_00_2] = &keyEntry_2,
+  [XT_KEY_00_3] = &keyEntry_3,
+  [XT_KEY_00_4] = &keyEntry_4,
+  [XT_KEY_00_5] = &keyEntry_5,
+  [XT_KEY_00_6] = &keyEntry_6,
+  [XT_KEY_00_7] = &keyEntry_7,
+  [XT_KEY_00_8] = &keyEntry_8,
+  [XT_KEY_00_9] = &keyEntry_9,
+  [XT_KEY_00_0] = &keyEntry_0,
+  [XT_KEY_00_Minus] = &keyEntry_Minus,
+  [XT_KEY_00_Equal] = &keyEntry_Equal,
+  [XT_KEY_00_Backspace] = &keyEntry_Backspace,
+
+  [XT_KEY_00_Tab] = &keyEntry_Tab,
+  [XT_KEY_00_Q] = &keyEntry_Q,
+  [XT_KEY_00_W] = &keyEntry_W,
+  [XT_KEY_00_E] = &keyEntry_E,
+  [XT_KEY_00_R] = &keyEntry_R,
+  [XT_KEY_00_T] = &keyEntry_T,
+  [XT_KEY_00_Y] = &keyEntry_Y,
+  [XT_KEY_00_U] = &keyEntry_U,
+  [XT_KEY_00_I] = &keyEntry_I,
+  [XT_KEY_00_O] = &keyEntry_O,
+  [XT_KEY_00_P] = &keyEntry_P,
+  [XT_KEY_00_LeftBracket] = &keyEntry_LeftBracket,
+  [XT_KEY_00_RightBracket] = &keyEntry_RightBracket,
+  [XT_KEY_00_Backslash] = &keyEntry_Backslash,
+
+  [XT_KEY_00_CapsLock] = &keyEntry_CapsLock,
+  [XT_KEY_00_A] = &keyEntry_A,
+  [XT_KEY_00_S] = &keyEntry_S,
+  [XT_KEY_00_D] = &keyEntry_D,
+  [XT_KEY_00_F] = &keyEntry_F,
+  [XT_KEY_00_G] = &keyEntry_G,
+  [XT_KEY_00_H] = &keyEntry_H,
+  [XT_KEY_00_J] = &keyEntry_J,
+  [XT_KEY_00_K] = &keyEntry_K,
+  [XT_KEY_00_L] = &keyEntry_L,
+  [XT_KEY_00_Semicolon] = &keyEntry_Semicolon,
+  [XT_KEY_00_Apostrophe] = &keyEntry_Apostrophe,
+  [XT_KEY_00_Enter] = &keyEntry_Enter,
+
+  [XT_KEY_00_LeftShift] = &keyEntry_LeftShift,
+  [XT_KEY_00_Europe2] = &keyEntry_Europe2,
+  [XT_KEY_00_Z] = &keyEntry_Z,
+  [XT_KEY_00_X] = &keyEntry_X,
+  [XT_KEY_00_C] = &keyEntry_C,
+  [XT_KEY_00_V] = &keyEntry_V,
+  [XT_KEY_00_B] = &keyEntry_B,
+  [XT_KEY_00_N] = &keyEntry_N,
+  [XT_KEY_00_M] = &keyEntry_M,
+  [XT_KEY_00_Comma] = &keyEntry_Comma,
+  [XT_KEY_00_Period] = &keyEntry_Period,
+  [XT_KEY_00_Slash] = &keyEntry_Slash,
+  [XT_KEY_00_RightShift] = &keyEntry_RightShift,
+
+  [XT_KEY_00_LeftControl] = &keyEntry_LeftControl,
+  [XT_KEY_00_LeftAlt] = &keyEntry_LeftAlt,
+  [XT_KEY_00_Space] = &keyEntry_Space,
+
+  [XT_KEY_00_NumLock] = &keyEntry_NumLock,
+  [XT_KEY_00_KPAsterisk] = &keyEntry_KPAsterisk,
+  [XT_KEY_00_KPMinus] = &keyEntry_KPMinus,
+  [XT_KEY_00_KPPlus] = &keyEntry_KPPlus,
+  [XT_KEY_00_KPPeriod] = &keyEntry_KPPeriod,
+  [XT_KEY_00_KP0] = &keyEntry_KP0,
+  [XT_KEY_00_KP1] = &keyEntry_KP1,
+  [XT_KEY_00_KP2] = &keyEntry_KP2,
+  [XT_KEY_00_KP3] = &keyEntry_KP3,
+  [XT_KEY_00_KP4] = &keyEntry_KP4,
+  [XT_KEY_00_KP5] = &keyEntry_KP5,
+  [XT_KEY_00_KP6] = &keyEntry_KP6,
+  [XT_KEY_00_KP7] = &keyEntry_KP7,
+  [XT_KEY_00_KP8] = &keyEntry_KP8,
+  [XT_KEY_00_KP9] = &keyEntry_KP9,
+};
+
+static const KeyEntry *const xtKeyMapE0[] = {
+  [XT_KEY_E0_LeftGUI] = &keyEntry_LeftGUI,
+  [XT_KEY_E0_RightAlt] = &keyEntry_RightAlt,
+  [XT_KEY_E0_RightGUI] = &keyEntry_RightGUI,
+  [XT_KEY_E0_Context] = &keyEntry_Context,
+  [XT_KEY_E0_RightControl] = &keyEntry_RightControl,
+
+  [XT_KEY_E0_Insert] = &keyEntry_Insert,
+  [XT_KEY_E0_Delete] = &keyEntry_Delete,
+  [XT_KEY_E0_Home] = &keyEntry_Home,
+  [XT_KEY_E0_End] = &keyEntry_End,
+  [XT_KEY_E0_PageUp] = &keyEntry_PageUp,
+  [XT_KEY_E0_PageDown] = &keyEntry_PageDown,
+
+  [XT_KEY_E0_ArrowUp] = &keyEntry_ArrowUp,
+  [XT_KEY_E0_ArrowLeft] = &keyEntry_ArrowLeft,
+  [XT_KEY_E0_ArrowDown] = &keyEntry_ArrowDown,
+  [XT_KEY_E0_ArrowRight] = &keyEntry_ArrowRight,
+
+  [XT_KEY_E0_KPSlash] = &keyEntry_KPSlash,
+  [XT_KEY_E0_KPEnter] = &keyEntry_KPEnter,
+};
+
+#define xtKeyMapE1 NULL
+
+static void
+xtHandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
+  if (code == XT_MOD_E0) {
+    USE_KEY_MAP(xt, E0);
+  } else if (code == XT_MOD_E1) {
+    USE_KEY_MAP(xt, E1);
+  } else {
+    int release = (code & XT_BIT_RELEASE) != 0;
+    code &= ~XT_BIT_RELEASE;
+
+    if (code < kcd->xt.keyCount) {
+      const KeyEntry *key = kcd->xt.keyMap[code];
+
+      USE_KEY_MAP(xt, 00);
+      handleKey(key, release, &kcd->xt.modifiers);
+    }
+  }
+}
+
+static const KeyEntry *const atKeyMap00[] = {
+  [AT_KEY_00_Escape] = &keyEntry_Escape,
+  [AT_KEY_00_F1] = &keyEntry_F1,
+  [AT_KEY_00_F2] = &keyEntry_F2,
+  [AT_KEY_00_F3] = &keyEntry_F3,
+  [AT_KEY_00_F4] = &keyEntry_F4,
+  [AT_KEY_00_F5] = &keyEntry_F5,
+  [AT_KEY_00_F6] = &keyEntry_F6,
+  [AT_KEY_00_F7] = &keyEntry_F7,
+  [AT_KEY_00_F8] = &keyEntry_F8,
+  [AT_KEY_00_F9] = &keyEntry_F9,
+  [AT_KEY_00_F10] = &keyEntry_F10,
+  [AT_KEY_00_F11] = &keyEntry_F11,
+  [AT_KEY_00_F12] = &keyEntry_F12,
+  [AT_KEY_00_ScrollLock] = &keyEntry_ScrollLock,
+
+  [AT_KEY_00_F13] = &keyEntry_F13,
+  [AT_KEY_00_F14] = &keyEntry_F14,
+  [AT_KEY_00_F15] = &keyEntry_F15,
+  [AT_KEY_00_F16] = &keyEntry_F16,
+  [AT_KEY_00_F17] = &keyEntry_F17,
+  [AT_KEY_00_F18] = &keyEntry_F18,
+  [AT_KEY_00_F19] = &keyEntry_F19,
+  [AT_KEY_00_F20] = &keyEntry_F20,
+  [AT_KEY_00_F21] = &keyEntry_F21,
+  [AT_KEY_00_F22] = &keyEntry_F22,
+  [AT_KEY_00_F23] = &keyEntry_F23,
+  [AT_KEY_00_F24] = &keyEntry_F24,
+
+  [AT_KEY_00_Grave] = &keyEntry_Grave,
+  [AT_KEY_00_1] = &keyEntry_1,
+  [AT_KEY_00_2] = &keyEntry_2,
+  [AT_KEY_00_3] = &keyEntry_3,
+  [AT_KEY_00_4] = &keyEntry_4,
+  [AT_KEY_00_5] = &keyEntry_5,
+  [AT_KEY_00_6] = &keyEntry_6,
+  [AT_KEY_00_7] = &keyEntry_7,
+  [AT_KEY_00_8] = &keyEntry_8,
+  [AT_KEY_00_9] = &keyEntry_9,
+  [AT_KEY_00_0] = &keyEntry_0,
+  [AT_KEY_00_Minus] = &keyEntry_Minus,
+  [AT_KEY_00_Equal] = &keyEntry_Equal,
+  [AT_KEY_00_Backspace] = &keyEntry_Backspace,
+
+  [AT_KEY_00_Tab] = &keyEntry_Tab,
+  [AT_KEY_00_Q] = &keyEntry_Q,
+  [AT_KEY_00_W] = &keyEntry_W,
+  [AT_KEY_00_E] = &keyEntry_E,
+  [AT_KEY_00_R] = &keyEntry_R,
+  [AT_KEY_00_T] = &keyEntry_T,
+  [AT_KEY_00_Y] = &keyEntry_Y,
+  [AT_KEY_00_U] = &keyEntry_U,
+  [AT_KEY_00_I] = &keyEntry_I,
+  [AT_KEY_00_O] = &keyEntry_O,
+  [AT_KEY_00_P] = &keyEntry_P,
+  [AT_KEY_00_LeftBracket] = &keyEntry_LeftBracket,
+  [AT_KEY_00_RightBracket] = &keyEntry_RightBracket,
+  [AT_KEY_00_Backslash] = &keyEntry_Backslash,
+
+  [AT_KEY_00_CapsLock] = &keyEntry_CapsLock,
+  [AT_KEY_00_A] = &keyEntry_A,
+  [AT_KEY_00_S] = &keyEntry_S,
+  [AT_KEY_00_D] = &keyEntry_D,
+  [AT_KEY_00_F] = &keyEntry_F,
+  [AT_KEY_00_G] = &keyEntry_G,
+  [AT_KEY_00_H] = &keyEntry_H,
+  [AT_KEY_00_J] = &keyEntry_J,
+  [AT_KEY_00_K] = &keyEntry_K,
+  [AT_KEY_00_L] = &keyEntry_L,
+  [AT_KEY_00_Semicolon] = &keyEntry_Semicolon,
+  [AT_KEY_00_Apostrophe] = &keyEntry_Apostrophe,
+  [AT_KEY_00_Enter] = &keyEntry_Enter,
+
+  [AT_KEY_00_LeftShift] = &keyEntry_LeftShift,
+  [AT_KEY_00_Europe2] = &keyEntry_Europe2,
+  [AT_KEY_00_Z] = &keyEntry_Z,
+  [AT_KEY_00_X] = &keyEntry_X,
+  [AT_KEY_00_C] = &keyEntry_C,
+  [AT_KEY_00_V] = &keyEntry_V,
+  [AT_KEY_00_B] = &keyEntry_B,
+  [AT_KEY_00_N] = &keyEntry_N,
+  [AT_KEY_00_M] = &keyEntry_M,
+  [AT_KEY_00_Comma] = &keyEntry_Comma,
+  [AT_KEY_00_Period] = &keyEntry_Period,
+  [AT_KEY_00_Slash] = &keyEntry_Slash,
+  [AT_KEY_00_RightShift] = &keyEntry_RightShift,
+
+  [AT_KEY_00_LeftControl] = &keyEntry_LeftControl,
+  [AT_KEY_00_LeftAlt] = &keyEntry_LeftAlt,
+  [AT_KEY_00_Space] = &keyEntry_Space,
+
+  [AT_KEY_00_NumLock] = &keyEntry_NumLock,
+  [AT_KEY_00_KPAsterisk] = &keyEntry_KPAsterisk,
+  [AT_KEY_00_KPMinus] = &keyEntry_KPMinus,
+  [AT_KEY_00_KPPlus] = &keyEntry_KPPlus,
+  [AT_KEY_00_KPPeriod] = &keyEntry_KPPeriod,
+  [AT_KEY_00_KP0] = &keyEntry_KP0,
+  [AT_KEY_00_KP1] = &keyEntry_KP1,
+  [AT_KEY_00_KP2] = &keyEntry_KP2,
+  [AT_KEY_00_KP3] = &keyEntry_KP3,
+  [AT_KEY_00_KP4] = &keyEntry_KP4,
+  [AT_KEY_00_KP5] = &keyEntry_KP5,
+  [AT_KEY_00_KP6] = &keyEntry_KP6,
+  [AT_KEY_00_KP7] = &keyEntry_KP7,
+  [AT_KEY_00_KP8] = &keyEntry_KP8,
+  [AT_KEY_00_KP9] = &keyEntry_KP9,
+};
+
+static const KeyEntry *const atKeyMapE0[] = {
+  [AT_KEY_E0_LeftGUI] = &keyEntry_LeftGUI,
+  [AT_KEY_E0_RightAlt] = &keyEntry_RightAlt,
+  [AT_KEY_E0_RightGUI] = &keyEntry_RightGUI,
+  [AT_KEY_E0_Context] = &keyEntry_Context,
+  [AT_KEY_E0_RightControl] = &keyEntry_RightControl,
+
+  [AT_KEY_E0_Insert] = &keyEntry_Insert,
+  [AT_KEY_E0_Delete] = &keyEntry_Delete,
+  [AT_KEY_E0_Home] = &keyEntry_Home,
+  [AT_KEY_E0_End] = &keyEntry_End,
+  [AT_KEY_E0_PageUp] = &keyEntry_PageUp,
+  [AT_KEY_E0_PageDown] = &keyEntry_PageDown,
+
+  [AT_KEY_E0_ArrowUp] = &keyEntry_ArrowUp,
+  [AT_KEY_E0_ArrowLeft] = &keyEntry_ArrowLeft,
+  [AT_KEY_E0_ArrowDown] = &keyEntry_ArrowDown,
+  [AT_KEY_E0_ArrowRight] = &keyEntry_ArrowRight,
+
+  [AT_KEY_E0_KPSlash] = &keyEntry_KPSlash,
+  [AT_KEY_E0_KPEnter] = &keyEntry_KPEnter,
+};
+
+#define atKeyMapE1 NULL
+
+static void
+atHandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
+  if (code == AT_MOD_RELEASE) {
+    MOD_SET(MOD_RELEASE, kcd->at.modifiers);
+  } else if (code == AT_MOD_E0) {
+    USE_KEY_MAP(at, E0);
+  } else if (code == AT_MOD_E1) {
+    USE_KEY_MAP(at, E1);
+  } else if (code < kcd->at.keyCount) {
+    const KeyEntry *key = kcd->at.keyMap[code];
+    int release = MOD_TST(MOD_RELEASE, kcd->at.modifiers);
+
+    MOD_CLR(MOD_RELEASE, kcd->at.modifiers);
+    USE_KEY_MAP(at, 00);
+
+    handleKey(key, release, &kcd->at.modifiers);
+  }
+}
+
+static const KeyEntry *const ps2KeyMap[] = {
+  [PS2_KEY_Escape] = &keyEntry_Escape,
+  [PS2_KEY_F1] = &keyEntry_F1,
+  [PS2_KEY_F2] = &keyEntry_F2,
+  [PS2_KEY_F3] = &keyEntry_F3,
+  [PS2_KEY_F4] = &keyEntry_F4,
+  [PS2_KEY_F5] = &keyEntry_F5,
+  [PS2_KEY_F6] = &keyEntry_F6,
+  [PS2_KEY_F7] = &keyEntry_F7,
+  [PS2_KEY_F8] = &keyEntry_F8,
+  [PS2_KEY_F9] = &keyEntry_F9,
+  [PS2_KEY_F10] = &keyEntry_F10,
+  [PS2_KEY_F11] = &keyEntry_F11,
+  [PS2_KEY_F12] = &keyEntry_F12,
+  [PS2_KEY_ScrollLock] = &keyEntry_ScrollLock,
+
+  [PS2_KEY_Grave] = &keyEntry_Grave,
+  [PS2_KEY_1] = &keyEntry_1,
+  [PS2_KEY_2] = &keyEntry_2,
+  [PS2_KEY_3] = &keyEntry_3,
+  [PS2_KEY_4] = &keyEntry_4,
+  [PS2_KEY_5] = &keyEntry_5,
+  [PS2_KEY_6] = &keyEntry_6,
+  [PS2_KEY_7] = &keyEntry_7,
+  [PS2_KEY_8] = &keyEntry_8,
+  [PS2_KEY_9] = &keyEntry_9,
+  [PS2_KEY_0] = &keyEntry_0,
+  [PS2_KEY_Minus] = &keyEntry_Minus,
+  [PS2_KEY_Equal] = &keyEntry_Equal,
+  [PS2_KEY_Backspace] = &keyEntry_Backspace,
+
+  [PS2_KEY_Tab] = &keyEntry_Tab,
+  [PS2_KEY_Q] = &keyEntry_Q,
+  [PS2_KEY_W] = &keyEntry_W,
+  [PS2_KEY_E] = &keyEntry_E,
+  [PS2_KEY_R] = &keyEntry_R,
+  [PS2_KEY_T] = &keyEntry_T,
+  [PS2_KEY_Y] = &keyEntry_Y,
+  [PS2_KEY_U] = &keyEntry_U,
+  [PS2_KEY_I] = &keyEntry_I,
+  [PS2_KEY_O] = &keyEntry_O,
+  [PS2_KEY_P] = &keyEntry_P,
+  [PS2_KEY_LeftBracket] = &keyEntry_LeftBracket,
+  [PS2_KEY_RightBracket] = &keyEntry_RightBracket,
+  [PS2_KEY_Backslash] = &keyEntry_Backslash,
+
+  [PS2_KEY_CapsLock] = &keyEntry_CapsLock,
+  [PS2_KEY_A] = &keyEntry_A,
+  [PS2_KEY_S] = &keyEntry_S,
+  [PS2_KEY_D] = &keyEntry_D,
+  [PS2_KEY_F] = &keyEntry_F,
+  [PS2_KEY_G] = &keyEntry_G,
+  [PS2_KEY_H] = &keyEntry_H,
+  [PS2_KEY_J] = &keyEntry_J,
+  [PS2_KEY_K] = &keyEntry_K,
+  [PS2_KEY_L] = &keyEntry_L,
+  [PS2_KEY_Semicolon] = &keyEntry_Semicolon,
+  [PS2_KEY_Apostrophe] = &keyEntry_Apostrophe,
+  [PS2_KEY_Enter] = &keyEntry_Enter,
+
+  [PS2_KEY_LeftShift] = &keyEntry_LeftShift,
+  [PS2_KEY_Europe2] = &keyEntry_Europe2,
+  [PS2_KEY_Z] = &keyEntry_Z,
+  [PS2_KEY_X] = &keyEntry_X,
+  [PS2_KEY_C] = &keyEntry_C,
+  [PS2_KEY_V] = &keyEntry_V,
+  [PS2_KEY_B] = &keyEntry_B,
+  [PS2_KEY_N] = &keyEntry_N,
+  [PS2_KEY_M] = &keyEntry_M,
+  [PS2_KEY_Comma] = &keyEntry_Comma,
+  [PS2_KEY_Period] = &keyEntry_Period,
+  [PS2_KEY_Slash] = &keyEntry_Slash,
+  [PS2_KEY_RightShift] = &keyEntry_RightShift,
+
+  [PS2_KEY_LeftControl] = &keyEntry_LeftControl,
+  [PS2_KEY_LeftGUI] = &keyEntry_LeftGUI,
+  [PS2_KEY_LeftAlt] = &keyEntry_LeftAlt,
+  [PS2_KEY_Space] = &keyEntry_Space,
+  [PS2_KEY_RightAlt] = &keyEntry_RightAlt,
+  [PS2_KEY_RightGUI] = &keyEntry_RightGUI,
+  [PS2_KEY_Context] = &keyEntry_Context,
+  [PS2_KEY_RightControl] = &keyEntry_RightControl,
+
+  [PS2_KEY_Insert] = &keyEntry_Insert,
+  [PS2_KEY_Delete] = &keyEntry_Delete,
+  [PS2_KEY_Home] = &keyEntry_Home,
+  [PS2_KEY_End] = &keyEntry_End,
+  [PS2_KEY_PageUp] = &keyEntry_PageUp,
+  [PS2_KEY_PageDown] = &keyEntry_PageDown,
+
+  [PS2_KEY_ArrowUp] = &keyEntry_ArrowUp,
+  [PS2_KEY_ArrowLeft] = &keyEntry_ArrowLeft,
+  [PS2_KEY_ArrowDown] = &keyEntry_ArrowDown,
+  [PS2_KEY_ArrowRight] = &keyEntry_ArrowRight,
+
+  [PS2_KEY_NumLock] = &keyEntry_NumLock,
+  [PS2_KEY_KPSlash] = &keyEntry_KPSlash,
+  [PS2_KEY_KPAsterisk] = &keyEntry_KPAsterisk,
+  [PS2_KEY_KPMinus] = &keyEntry_KPMinus,
+  [PS2_KEY_KPPlus] = &keyEntry_KPPlus,
+  [PS2_KEY_KPEnter] = &keyEntry_KPEnter,
+  [PS2_KEY_KPPeriod] = &keyEntry_KPPeriod,
+  [PS2_KEY_KP0] = &keyEntry_KP0,
+  [PS2_KEY_KP1] = &keyEntry_KP1,
+  [PS2_KEY_KP2] = &keyEntry_KP2,
+  [PS2_KEY_KP3] = &keyEntry_KP3,
+  [PS2_KEY_KP4] = &keyEntry_KP4,
+  [PS2_KEY_KP5] = &keyEntry_KP5,
+  [PS2_KEY_KP6] = &keyEntry_KP6,
+  [PS2_KEY_KP7] = &keyEntry_KP7,
+  [PS2_KEY_KP8] = &keyEntry_KP8,
+  [PS2_KEY_KP9] = &keyEntry_KP9,
+  [PS2_KEY_KPComma] = &keyEntry_KPComma,
+};
+
+static void
+ps2HandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
+  if (code == PS2_MOD_RELEASE) {
+    MOD_SET(MOD_RELEASE, kcd->ps2.modifiers);
+  } else if (code < ARRAY_COUNT(ps2KeyMap)) {
+    const KeyEntry *key = ps2KeyMap[code];
+    int release = MOD_TST(MOD_RELEASE, kcd->ps2.modifiers);
+
+    MOD_CLR(MOD_RELEASE, kcd->ps2.modifiers);
+
+    handleKey(key, release, &kcd->ps2.modifiers);
+  }
+}
+
+static int
+handleKeycodeCommands (int command, void *data) {
+  KeycodeCommandData *kcd = data;
+  int arg = command & BRL_MSK_ARG;
+
+  switch (command & BRL_MSK_BLK) {
+    case BRL_CMD_BLK(PASSXT):
+      if (command & BRL_FLG_KBD_RELEASE) arg |= XT_BIT_RELEASE;
+      if (command & BRL_FLG_KBD_EMUL0) xtHandleScanCode(kcd, XT_MOD_E0);
+      if (command & BRL_FLG_KBD_EMUL1) xtHandleScanCode(kcd, XT_MOD_E1);
+      xtHandleScanCode(kcd, arg);
+      break;
+
+    case BRL_CMD_BLK(PASSAT):
+      if (command & BRL_FLG_KBD_RELEASE) atHandleScanCode(kcd, AT_MOD_RELEASE);
+      if (command & BRL_FLG_KBD_EMUL0) atHandleScanCode(kcd, AT_MOD_E0);
+      if (command & BRL_FLG_KBD_EMUL1) atHandleScanCode(kcd, AT_MOD_E1);
+      atHandleScanCode(kcd, arg);
+      break;
+
+    case BRL_CMD_BLK(PASSPS2):
+      if (command & BRL_FLG_KBD_RELEASE) ps2HandleScanCode(kcd, PS2_MOD_RELEASE);
+      ps2HandleScanCode(kcd, arg);
+      break;
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+static void
+resetKeycodeCommandData (void *data) {
+  KeycodeCommandData *kcd = data;
+
+  USE_KEY_MAP(xt, 00);
+  kcd->xt.modifiers = 0;
+
+  USE_KEY_MAP(at, 00);
+  kcd->at.modifiers = 0;
+
+  kcd->ps2.modifiers = 0;
+}
+
+REPORT_LISTENER(keycodeCommandDataResetListener) {
+  KeycodeCommandData *kcd = parameters->listenerData;
+
+  resetKeycodeCommandData(kcd);
+}
+
+static void
+destroyKeycodeCommandData (void *data) {
+  KeycodeCommandData *kcd = data;
+
+  unregisterReportListener(kcd->resetListener);
+  free(kcd);
+}
+
+int
+addKeycodeCommands (void) {
+  KeycodeCommandData *kcd;
+
+  if ((kcd = malloc(sizeof(*kcd)))) {
+    memset(kcd, 0, sizeof(*kcd));
+    resetKeycodeCommandData(kcd);
+
+    if ((kcd->resetListener = registerReportListener(REPORT_BRAILLE_DEVICE_ONLINE, keycodeCommandDataResetListener, kcd))) {
+      if (pushCommandHandler("keycodes", KTB_CTX_DEFAULT,
+                             handleKeycodeCommands, destroyKeycodeCommandData, kcd)) {
+        return 1;
+      }
+
+      unregisterReportListener(kcd->resetListener);
+    }
+
+    free(kcd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_keycodes.h b/Programs/cmd_keycodes.h
new file mode 100644
index 0000000..966d056
--- /dev/null
+++ b/Programs/cmd_keycodes.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_KEYCODES
+#define BRLTTY_INCLUDED_CMD_KEYCODES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addKeycodeCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_KEYCODES */
diff --git a/Programs/cmd_learn.c b/Programs/cmd_learn.c
new file mode 100644
index 0000000..460ea73
--- /dev/null
+++ b/Programs/cmd_learn.c
@@ -0,0 +1,79 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "brl_cmds.h"
+#include "ktb_types.h"
+#include "cmd_queue.h"
+#include "cmd_learn.h"
+#include "learn.h"
+#include "async_task.h"
+#include "update.h"
+#include "message.h"
+#include "core.h"
+
+typedef struct {
+  int timeout;
+} LearnModeParameters;
+
+ASYNC_TASK_CALLBACK(presentLearnMode) {
+  LearnModeParameters *lmp = data;
+
+  suspendUpdates();
+  learnMode(lmp->timeout);
+  resumeUpdates(1);
+  free(lmp);
+}
+
+static int
+handleLearnCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_LEARN: {
+      LearnModeParameters *lmp;
+
+      if ((lmp = malloc(sizeof(*lmp)))) {
+        memset(lmp, 0, sizeof(*lmp));
+        lmp->timeout = LEARN_MODE_TIMEOUT;
+
+        if (asyncAddTask(NULL, presentLearnMode, lmp)) break;
+        free(lmp);
+      } else {
+        logMallocError();
+      }
+
+      brl.hasFailed = 1;
+      break;
+    }
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+int
+addLearnCommands (void) {
+  return pushCommandHandler("learn", KTB_CTX_DEFAULT,
+                            handleLearnCommands, NULL, NULL);
+}
diff --git a/Programs/cmd_learn.h b/Programs/cmd_learn.h
new file mode 100644
index 0000000..8d9bd2e
--- /dev/null
+++ b/Programs/cmd_learn.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_LEARN
+#define BRLTTY_INCLUDED_CMD_LEARN
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addLearnCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_LEARN */
diff --git a/Programs/cmd_miscellaneous.c b/Programs/cmd_miscellaneous.c
new file mode 100644
index 0000000..0861288
--- /dev/null
+++ b/Programs/cmd_miscellaneous.c
@@ -0,0 +1,292 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "strfmt.h"
+#include "cmd_queue.h"
+#include "cmd_miscellaneous.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "prefs.h"
+#include "scr_special.h"
+#include "message.h"
+#include "alert.h"
+#include "core.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static
+STR_BEGIN_FORMATTER(formatSpeechDate, const TimeFormattingData *fmt)
+  const char *yearFormat = "%u";
+  const char *monthFormat = "%s";
+  const char *dayFormat = "%u";
+
+  uint16_t year = fmt->components.year;
+  uint8_t day = fmt->components.day + 1;
+
+  char month[0X20];
+  size_t length = strftime(month, sizeof(month), "%B", &fmt->components.time);
+  month[length] = 0;
+
+  switch (prefs.dateFormat) {
+    default:
+    case dfYearMonthDay:
+      STR_PRINTF(yearFormat, year);
+      STR_PRINTF(" ");
+      STR_PRINTF(monthFormat, month);
+      STR_PRINTF(" ");
+      STR_PRINTF(dayFormat, day);
+      break;
+
+    case dfMonthDayYear:
+      STR_PRINTF(monthFormat, month);
+      STR_PRINTF(" ");
+      STR_PRINTF(dayFormat, day);
+      STR_PRINTF(", ");
+      STR_PRINTF(yearFormat, year);
+      break;
+
+    case dfDayMonthYear:
+      STR_PRINTF(dayFormat, day);
+      STR_PRINTF(" ");
+      STR_PRINTF(monthFormat, month);
+      STR_PRINTF(", ");
+      STR_PRINTF(yearFormat, year);
+      break;
+  }
+STR_END_FORMATTER
+
+static
+STR_BEGIN_FORMATTER(formatSpeechTime, const TimeFormattingData *fmt)
+  unsigned int hours = fmt->components.hour;
+  unsigned int minutes = fmt->components.minute;
+  unsigned int seconds = fmt->components.second;
+
+  if (minutes > 0) {
+    STR_PRINTF("%u", hours);
+    if (minutes < 10) STR_PRINTF(" 0");
+    STR_PRINTF(" %u", minutes);
+  } else if (!fmt->meridian) {
+    // xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
+    // xgettext: (%u represents the number of hours)
+    STR_PRINTF(ngettext("%u o'clock", "%u o'clock", hours), hours);
+  }
+
+  if (fmt->meridian) {
+    const char *character = fmt->meridian;
+    while (*character) STR_PRINTF(" %c", *character++);
+  }
+
+  if (prefs.showSeconds) {
+    STR_PRINTF(", ");
+
+    if (seconds == 0) {
+      // xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
+      STR_PRINTF("%s", gettext("exactly"));
+    } else {
+      STR_PRINTF("%s", gettext("and"));
+
+      // xgettext: This is a number (%u) of seconds (time units).
+      STR_PRINTF(ngettext("%u second", "%u seconds", seconds), seconds);
+    }
+  }
+STR_END_FORMATTER
+
+static void
+speakTime (const TimeFormattingData *fmt) {
+  char announcement[0X100];
+  char time[0X80];
+
+  STR_BEGIN(announcement, sizeof(announcement));
+  formatSpeechTime(time, sizeof(time), fmt);
+
+  if (prefs.datePosition == dpNone) {
+    STR_PRINTF("%s", time);
+  } else {
+    char date[0X40];
+    formatSpeechDate(date, sizeof(date), fmt);
+
+    switch (prefs.datePosition) {
+      case dpBeforeTime:
+        STR_PRINTF("%s, %s", date, time);
+        break;
+
+      case dpAfterTime:
+        STR_PRINTF("%s, %s", time, date);
+        break;
+
+      default:
+        STR_PRINTF("%s", date);
+        break;
+    }
+  }
+
+  STR_PRINTF(".");
+  STR_END;
+  sayString(&spk, announcement, SAY_OPT_MUTE_FIRST);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+static void
+showTime (const TimeFormattingData *fmt) {
+  char buffer[0X80];
+
+  formatBrailleTime(buffer, sizeof(buffer), fmt);
+  message(NULL, buffer, MSG_SILENT);
+}
+
+static int
+handleMiscellaneousCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_RESTARTBRL:
+      brl.hasFailed = 1;
+      break;
+
+    case BRL_CMD_BRL_STOP:
+      disableBrailleDriver(gettext("braille driver stopped"));
+      break;
+
+    case BRL_CMD_BRL_START:
+      enableBrailleDriver();
+      break;
+
+    case BRL_CMD_SCR_STOP:
+      disableScreenDriver(gettext("screen driver stopped"));
+      break;
+
+    case BRL_CMD_SCR_START:
+      enableScreenDriver();
+      break;
+
+    case BRL_CMD_HELP: {
+      int ok = 0;
+      unsigned int pageNumber;
+
+      if (isSpecialScreen(SCR_HELP)) {
+        pageNumber = getHelpPageNumber() + 1;
+        ok = 1;
+      } else {
+        pageNumber = haveSpecialScreen(SCR_HELP)? getHelpPageNumber(): 1;
+        if (!activateSpecialScreen(SCR_HELP)) pageNumber = 0;
+      }
+
+      if (pageNumber) {
+        unsigned int pageCount = getHelpPageCount();
+
+        while (pageNumber <= pageCount) {
+          if (setHelpPageNumber(pageNumber))
+            if (getHelpLineCount())
+              break;
+
+          pageNumber += 1;
+        }
+
+        if (pageNumber > pageCount) {
+          deactivateSpecialScreen(SCR_HELP);
+        } else {
+          ok = 1;
+        }
+
+        updateSessionAttributes();
+      }
+
+      if (ok) {
+        infoMode = 0;
+      } else {
+        message(NULL, gettext("help not available"), 0);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_TIME: {
+      TimeFormattingData fmt;
+      getTimeFormattingData(&fmt);
+
+#ifdef ENABLE_SPEECH_SUPPORT
+      if (isAutospeakActive()) speakTime(&fmt);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+      showTime(&fmt);
+      break;
+    }
+
+    case BRL_CMD_REFRESH: {
+      if (canRefreshBrailleDisplay(&brl)) {
+        if (refreshBrailleDisplay(&brl)) {
+          break;
+        }
+      }
+
+      alert(ALERT_COMMAND_REJECTED);
+      break;
+    }
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(DESCCHAR): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            char description[0X80];
+            STR_BEGIN(description, sizeof(description));
+            STR_FORMAT(formatCharacterDescription, column, row);
+            STR_END;
+            message(NULL, description, 0);
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+
+          break;
+        }
+
+        case BRL_CMD_BLK(REFRESH_LINE): {
+          if (canRefreshBrailleRow(&brl)) {
+            if (refreshBrailleRow(&brl, arg)) {
+              break;
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(ALERT):
+          alert(arg);
+          break;
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+int
+addMiscellaneousCommands (void) {
+  return pushCommandHandler("miscellaneous", KTB_CTX_DEFAULT,
+                            handleMiscellaneousCommands, NULL, NULL);
+}
diff --git a/Programs/cmd_miscellaneous.h b/Programs/cmd_miscellaneous.h
new file mode 100644
index 0000000..e832e23
--- /dev/null
+++ b/Programs/cmd_miscellaneous.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_MISCELLANEOUS
+#define BRLTTY_INCLUDED_CMD_MISCELLANEOUS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addMiscellaneousCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_MISCELLANEOUS */
diff --git a/Programs/cmd_navigation.c b/Programs/cmd_navigation.c
new file mode 100644
index 0000000..9216cea
--- /dev/null
+++ b/Programs/cmd_navigation.c
@@ -0,0 +1,834 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "log.h"
+#include "alert.h"
+#include "parameters.h"
+#include "program.h"
+#include "cmd_queue.h"
+#include "cmd_navigation.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "parse.h"
+#include "rgx.h"
+#include "prefs.h"
+#include "routing.h"
+#include "scr.h"
+#include "core.h"
+
+static int
+getWindowLength (void) {
+  if (isContracting()) return getContractedLength(textCount);
+  return textCount;
+}
+
+typedef int (*CanMoveWindow) (void);
+
+static int
+canMoveUp (void) {
+  return ses->winy > 0;
+}
+
+static int
+canMoveDown (void) {
+  return (ses->winy + brl.textRows) < scr.rows;
+}
+
+static int
+toDifferentLine (
+  IsSameCharacter isSameCharacter,
+  CanMoveWindow canMoveWindow,
+  int amount, int from, int width
+) {
+  if (canMoveWindow()) {
+    ScreenCharacter characters1[width];
+    unsigned int skipped = 0;
+
+    if ((isSameCharacter == isSameText) && ses->displayMode) isSameCharacter = isSameAttributes;
+    readScreen(from, ses->winy, width, 1, characters1);
+
+    do {
+      ScreenCharacter characters2[width];
+      readScreen(from, ses->winy+=amount, width, 1, characters2);
+
+      if (!isSameRow(characters1, characters2, width, isSameCharacter) ||
+          (showScreenCursor() && (scr.posy == ses->winy) &&
+           (scr.posx >= from) && (scr.posx < (from + width)))) {
+        return 1;
+      }
+
+      /* lines are identical */
+      alertLineSkipped(&skipped);
+    } while (canMoveWindow());
+  }
+
+  alert(ALERT_BOUNCE);
+  return 0;
+}
+
+static int
+upDifferentLine (IsSameCharacter isSameCharacter) {
+  return toDifferentLine(isSameCharacter, canMoveUp, -1, 0, scr.cols);
+}
+
+static int
+downDifferentLine (IsSameCharacter isSameCharacter) {
+  return toDifferentLine(isSameCharacter, canMoveDown, 1, 0, scr.cols);
+}
+
+static int
+upDifferentCharacter (IsSameCharacter isSameCharacter, int column) {
+  return toDifferentLine(isSameCharacter, canMoveUp, -1, column, 1);
+}
+
+static int
+downDifferentCharacter (IsSameCharacter isSameCharacter, int column) {
+  return toDifferentLine(isSameCharacter, canMoveDown, 1, column, 1);
+}
+
+static void
+upOneLine (void) {
+  if (canMoveUp()) {
+    ses->winy--;
+  } else {
+    alert(ALERT_BOUNCE);
+  }
+}
+
+static void
+downOneLine (void) {
+  if (canMoveDown()) {
+    ses->winy++;
+  } else {
+    alert(ALERT_BOUNCE);
+  }
+}
+
+static void
+upLine (IsSameCharacter isSameCharacter) {
+  if (prefs.skipIdenticalLines) {
+    upDifferentLine(isSameCharacter);
+  } else {
+    upOneLine();
+  }
+}
+
+static void
+downLine (IsSameCharacter isSameCharacter) {
+  if (prefs.skipIdenticalLines) {
+    downDifferentLine(isSameCharacter);
+  } else {
+    downOneLine();
+  }
+}
+
+typedef int (*RowTester) (int column, int row, void *data);
+
+static void
+findRow (int column, int increment, RowTester test, void *data) {
+  int row = ses->winy;
+
+  while (1) {
+    row += increment;
+    if (row < 0) break;
+    if ((row + brl.textRows) > scr.rows) break;
+
+    if (test(column, row, data)) {
+      ses->winy = row;
+      return;
+    }
+  }
+
+  alert(ALERT_BOUNCE);
+}
+
+static int
+testIndent (int column, int row, void *data UNUSED) {
+  int count = column+1;
+  ScreenCharacter characters[count];
+  readScreenRow(row, count, characters);
+
+  while (column >= 0) {
+    wchar_t text = characters[column].text;
+    if (text != WC_C(' ')) return 1;
+    column -= 1;
+  }
+
+  return 0;
+}
+
+static RGX_Object *promptPatterns = NULL;
+
+static void
+exitPromptPatterns (void *data) {
+  if (promptPatterns) {
+    rgxDestroyObject(promptPatterns);
+    promptPatterns = NULL;
+  }
+}
+
+int
+addPromptPattern (const char *string) {
+  if (!promptPatterns) {
+    if (!(promptPatterns = rgxNewObject(NULL))) return 0;
+    onProgramExit("prompt-patterns", exitPromptPatterns, NULL);
+    rgxCompileOption(promptPatterns, RGX_OPTION_SET, RGX_COMPILE_ANCHOR_START);
+  }
+
+  RGX_Matcher *matcher = rgxAddPatternUTF8(
+    promptPatterns, string, NULL, NULL
+  );
+
+  if (!matcher) return 0;
+  return 1;
+}
+
+static int
+testPromptOriginal (int column, int row, void *data) {
+  if (!column) return 0;
+
+  int length = column + 1;
+  ScreenCharacter characters[length];
+  readScreenRow(row, length, characters);
+
+  const ScreenCharacter *prompt = data;
+  return isSameRow(characters, prompt, length, isSameText);
+}
+
+static int
+testPromptPatterns (int column, int row, void *data) {
+  int length = scr.cols;
+  wchar_t text[length];
+
+  {
+    ScreenCharacter characters[length];
+    readScreenRow(row, length, characters);
+
+    const ScreenCharacter *from = characters;
+    const ScreenCharacter *end = from + length;
+
+    wchar_t *to = text;
+    while (from < end) *to++ = from++->text;
+  }
+
+  return !!rgxMatchTextCharacters(promptPatterns, text, length, NULL, NULL);
+}
+
+static void
+toPreviousNonblankWindow (void) {
+  int oldX = ses->winx;
+  int oldY = ses->winy;
+  int tuneLimit = 3;
+  ScreenCharacter characters[scr.cols];
+
+  while (1) {
+    int charCount;
+    int charIndex;
+
+    if (!shiftBrailleWindowLeft(fullWindowShift)) {
+      if (ses->winy == 0) {
+        ses->winx = oldX;
+        ses->winy = oldY;
+
+        alert(ALERT_BOUNCE);
+        break;
+      }
+
+      if (tuneLimit-- > 0) alert(ALERT_WRAP_UP);
+      upLine(isSameText);
+      placeBrailleWindowRight();
+    }
+
+    charCount = getWindowLength();
+    charCount = MIN(charCount, scr.cols-ses->winx);
+    readScreen(ses->winx, ses->winy, charCount, 1, characters);
+
+    for (charIndex=charCount-1; charIndex>=0; charIndex-=1) {
+      wchar_t text = characters[charIndex].text;
+
+      if (text != WC_C(' ')) break;
+    }
+
+    if (showScreenCursor() &&
+        (scr.posy == ses->winy) &&
+        (scr.posx >= 0) &&
+        (scr.posx < (ses->winx + charCount))) {
+      charIndex = MAX(charIndex, scr.posx-ses->winx);
+    }
+
+    if (charIndex >= 0) break;
+  }
+}
+
+static void
+toNextNonblankWindow (void) {
+  int oldX = ses->winx;
+  int oldY = ses->winy;
+  int tuneLimit = 3;
+  ScreenCharacter characters[scr.cols];
+
+  while (1) {
+    int charCount;
+    int charIndex;
+
+    if (!shiftBrailleWindowRight(fullWindowShift)) {
+      if (ses->winy >= (scr.rows - brl.textRows)) {
+        ses->winx = oldX;
+        ses->winy = oldY;
+
+        alert(ALERT_BOUNCE);
+        break;
+      }
+
+      if (tuneLimit-- > 0) alert(ALERT_WRAP_DOWN);
+      downLine(isSameText);
+      ses->winx = 0;
+    }
+
+    charCount = getWindowLength();
+    charCount = MIN(charCount, scr.cols-ses->winx);
+    readScreen(ses->winx, ses->winy, charCount, 1, characters);
+
+    for (charIndex=0; charIndex<charCount; charIndex+=1) {
+      wchar_t text = characters[charIndex].text;
+
+      if (text != WC_C(' ')) break;
+    }
+
+    if (showScreenCursor() &&
+        (scr.posy == ses->winy) &&
+        (scr.posx < scr.cols) &&
+        (scr.posx >= ses->winx)) {
+      charIndex = MIN(charIndex, scr.posx-ses->winx);
+    }
+
+    if (charIndex < charCount) break;
+  }
+}
+
+static int
+handleNavigationCommands (int command, void *data) {
+  int oldwiny = ses->winy;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_TOP_LEFT:
+      ses->winx = 0;
+      /* fall through */
+    case BRL_CMD_TOP:
+      ses->winy = 0;
+      break;
+
+    case BRL_CMD_BOT_LEFT:
+      ses->winx = 0;
+      /* fall through */
+    case BRL_CMD_BOT:
+      ses->winy = MAX(scr.rows, brl.textRows) - brl.textRows;
+      break;
+
+    case BRL_CMD_WINUP:
+      if (canMoveUp()) {
+        ses->winy -= MIN(verticalWindowShift, ses->winy);
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+    case BRL_CMD_WINDN:
+      if (canMoveDown()) {
+        ses->winy += MIN(verticalWindowShift, (scr.rows - brl.textRows - ses->winy));
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+
+    case BRL_CMD_LNUP:
+      upOneLine();
+      break;
+    case BRL_CMD_LNDN:
+      downOneLine();
+      break;
+
+    case BRL_CMD_PRDIFLN:
+      upDifferentLine(isSameText);
+      break;
+    case BRL_CMD_NXDIFLN:
+      downDifferentLine(isSameText);
+      break;
+
+    case BRL_CMD_ATTRUP:
+      upDifferentLine(isSameAttributes);
+      break;
+    case BRL_CMD_ATTRDN:
+      downDifferentLine(isSameAttributes);
+      break;
+
+    case BRL_CMD_PRPGRPH: {
+      typedef enum {
+        STARTING,
+        START_LINE_NOT_BLANK,
+        FINDING_LAST_LINE,
+        FINDING_FIRST_LINE
+      } State;
+
+      State state = STARTING;
+      ScreenCharacter characters[scr.cols];
+      int line = ses->winy;
+
+      while (1) {
+        int isBlankLine;
+
+        readScreenRow(line, scr.cols, characters);
+        isBlankLine = isAllSpaceCharacters(characters, scr.cols);
+
+        switch (state) {
+          case STARTING:
+            state = isBlankLine? FINDING_LAST_LINE: START_LINE_NOT_BLANK;
+            break;
+
+          case START_LINE_NOT_BLANK:
+            state = isBlankLine? FINDING_LAST_LINE: FINDING_FIRST_LINE;
+            break;
+
+          case FINDING_LAST_LINE:
+            if (!isBlankLine) state = FINDING_FIRST_LINE;
+            break;
+
+          case FINDING_FIRST_LINE:
+            if (!isBlankLine) break;
+            line += 1;
+            goto foundFirstLine;
+        }
+
+        if (!line) break;
+        line -= 1;
+      }
+
+      if (state == FINDING_FIRST_LINE) {
+      foundFirstLine:
+        ses->winy = line;
+        ses->winx = 0;
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_NXPGRPH: {
+      int width = scr.cols;
+      ScreenCharacter characters[width];
+
+      int found = 0;
+      int findBlankLine = 1;
+      int line = ses->winy;
+
+      while (line < scr.rows) {
+        readScreenRow(line, width, characters);
+
+        if (isAllSpaceCharacters(characters, width) == findBlankLine) {
+          if (!findBlankLine) {
+            ses->winy = line;
+            ses->winx = 0;
+            found = 1;
+            break;
+          }
+
+          findBlankLine = 0;
+        }
+
+        line += 1;
+      }
+
+      if (!found) alert(ALERT_BOUNCE);
+      break;
+    }
+
+    {
+      int increment;
+
+    case BRL_CMD_PRPROMPT:
+      increment = -1;
+      goto findPrompt;
+
+    case BRL_CMD_NXPROMPT:
+      increment = 1;
+      goto findPrompt;
+
+    findPrompt:
+      {
+        size_t length = scr.cols;
+        ScreenCharacter characters[length];
+        readScreenRow(ses->winy, length, characters);
+
+        if (promptPatterns) {
+          findRow(length, increment, testPromptPatterns, characters);
+        } else {
+          int column = 0;
+
+          while (column < length) {
+            if (characters[column].text == WC_C(' ')) break;
+            column += 1;
+          }
+
+          if (column < length) {
+            findRow(column, increment, testPromptOriginal, characters);
+          } else {
+            alert(ALERT_BOUNCE);
+          }
+        }
+      }
+
+      break;
+    }
+
+    case BRL_CMD_LNBEG:
+      if (ses->winx) {
+        ses->winx = 0;
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+
+    case BRL_CMD_LNEND: {
+      int end = MAX(scr.cols, textCount) - textCount;
+
+      if (ses->winx < end) {
+        ses->winx = end;
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+    }
+
+    case BRL_CMD_CHRLT:
+      if (!moveBrailleWindowLeft(1)) alert(ALERT_BOUNCE);
+      break;
+    case BRL_CMD_CHRRT:
+      if (!moveBrailleWindowRight(1)) alert(ALERT_BOUNCE);
+      break;
+
+    case BRL_CMD_HWINLT:
+      if (!shiftBrailleWindowLeft(halfWindowShift)) alert(ALERT_BOUNCE);
+      break;
+
+    case BRL_CMD_HWINRT:
+      if (!shiftBrailleWindowRight(halfWindowShift)) alert(ALERT_BOUNCE);
+      break;
+
+    case BRL_CMD_PRNBWIN:
+      toPreviousNonblankWindow();
+      break;
+
+    case BRL_CMD_NXNBWIN:
+      toNextNonblankWindow();
+      break;
+
+    case BRL_CMD_FWINLTSKIP:
+      if (prefs.skipBlankBrailleWindowsMode == sbwAll) {
+        toPreviousNonblankWindow();
+        break;
+      }
+
+    {
+      int skipBlankBrailleWindows;
+
+      skipBlankBrailleWindows = 1;
+      goto moveLeft;
+
+    case BRL_CMD_FWINLT:
+      skipBlankBrailleWindows = 0;
+      goto moveLeft;
+
+    moveLeft:
+      {
+        int oldX = ses->winx;
+
+        if (shiftBrailleWindowLeft(fullWindowShift)) {
+          if (skipBlankBrailleWindows) {
+            if (prefs.skipBlankBrailleWindowsMode == sbwEndOfLine) goto skipEndOfLine;
+            int charCount = MIN(scr.cols, ses->winx+textCount);
+
+            if (!showScreenCursor() ||
+                (scr.posy != ses->winy) ||
+                (scr.posx < 0) ||
+                (scr.posx >= charCount)) {
+              int charIndex;
+              ScreenCharacter characters[charCount];
+
+              readScreenRow(ses->winy, charCount, characters);
+
+              for (charIndex=0; charIndex<charCount; charIndex+=1) {
+                wchar_t text = characters[charIndex].text;
+
+                if (text != WC_C(' ')) break;
+              }
+
+              if (charIndex == charCount) goto wrapUp;
+            }
+          }
+
+          break;
+        }
+
+      wrapUp:
+        if (ses->winy == 0) {
+          ses->winx = oldX;
+          alert(ALERT_BOUNCE);
+          break;
+        }
+
+        alert(ALERT_WRAP_UP);
+        upLine(isSameText);
+        placeBrailleWindowRight();
+
+      skipEndOfLine:
+        if (skipBlankBrailleWindows && (prefs.skipBlankBrailleWindowsMode == sbwEndOfLine)) {
+          ScreenCharacter characters[scr.cols];
+          readScreenRow(ses->winy, scr.cols, characters);
+          int last = scr.cols;
+
+          while (last > 0) {
+            if (!isWordBreak(characters, --last)) break;
+          }
+
+          if (ses->winx > last) placeRightEdge(last);
+        }
+
+        if (prefs.wordWrap) setWordWrapStart(ses->winx);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_FWINRTSKIP:
+      if (prefs.skipBlankBrailleWindowsMode == sbwAll) {
+        toNextNonblankWindow();
+        break;
+      }
+
+    {
+      int skipBlankBrailleWindows;
+
+      skipBlankBrailleWindows = 1;
+      goto moveRight;
+
+    case BRL_CMD_FWINRT:
+      skipBlankBrailleWindows = 0;
+      goto moveRight;
+
+    moveRight:
+      {
+        int oldX = ses->winx;
+
+        if (shiftBrailleWindowRight(fullWindowShift)) {
+          if (skipBlankBrailleWindows) {
+            if (!showScreenCursor() ||
+                (scr.posy != ses->winy) ||
+                (scr.posx < ses->winx)) {
+              int charCount = scr.cols - ses->winx;
+              int charIndex;
+              ScreenCharacter characters[charCount];
+
+              readScreen(ses->winx, ses->winy, charCount, 1, characters);
+
+              for (charIndex=0; charIndex<charCount; charIndex+=1) {
+                wchar_t text = characters[charIndex].text;
+
+                if (text != WC_C(' ')) break;
+              }
+
+              if (charIndex == charCount) goto wrapDown;
+            }
+          }
+
+          break;
+        }
+
+      wrapDown:
+        if (ses->winy >= (scr.rows - brl.textRows)) {
+          ses->winx = oldX;
+
+          alert(ALERT_BOUNCE);
+          break;
+        }
+
+        alert(ALERT_WRAP_DOWN);
+        downLine(isSameText);
+        ses->winx = 0;
+      }
+
+      break;
+    }
+
+    case BRL_CMD_RETURN:
+      if ((ses->winx != ses->motx) || (ses->winy != ses->moty)) goto doBack;
+      /* fall through */
+    case BRL_CMD_HOME:
+      if (!trackScreenCursor(1)) alert(ALERT_COMMAND_REJECTED);
+      break;
+
+    doBack:
+    case BRL_CMD_BACK:
+      ses->winx = ses->motx;
+      ses->winy = ses->moty;
+      break;
+
+    case BRL_CMD_CSRJMP_VERT: {
+      if (!startScreenCursorRouting(-1, ses->winy)) {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    default: {
+      int blk = command & BRL_MSK_BLK;
+      int arg = command & BRL_MSK_ARG;
+      int flags = command & BRL_MSK_FLG;
+
+      switch (blk) {
+        case BRL_CMD_BLK(ROUTE): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 1)) {
+            if (startScreenCursorRouting(column, row)) {
+              break;
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(ROUTE_LINE): {
+          if (!startScreenCursorRouting(-1, arg)) {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+
+          break;
+        }
+
+        case BRL_CMD_BLK(SETLEFT): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            ses->winx = column;
+            ses->winy = row;
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+          break;
+        }
+
+        case BRL_CMD_BLK(GOTOLINE):
+          if (flags & BRL_FLG_MOTION_SCALED) {
+            arg = rescaleInteger(arg, BRL_MSK_ARG, scr.rows-1);
+          }
+          if (arg < scr.rows) {
+            slideBrailleWindowVertically(arg);
+            oldwiny = -1;
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+          break;
+
+        case BRL_CMD_BLK(SETMARK): {
+          ScreenLocation *mark = &ses->marks[arg];
+          mark->column = ses->winx;
+          mark->row = ses->winy;
+          alert(ALERT_MARK_SET);
+          break;
+        }
+        case BRL_CMD_BLK(GOTOMARK): {
+          ScreenLocation *mark = &ses->marks[arg];
+          ses->winx = mark->column;
+          ses->winy = mark->row;
+          break;
+        }
+
+        {
+          int column, row;
+          int increment;
+
+        case BRL_CMD_BLK(PRINDENT):
+          increment = -1;
+          goto findIndent;
+
+        case BRL_CMD_BLK(NXINDENT):
+          increment = 1;
+
+        findIndent:
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            ses->winy = row;
+            findRow(column, increment, testIndent, NULL);
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+          break;
+        }
+
+        case BRL_CMD_BLK(PRDIFCHAR): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            ses->winy = row;
+            upDifferentCharacter(isSameText, column);
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+          break;
+        }
+
+        case BRL_CMD_BLK(NXDIFCHAR): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            ses->winy = row;
+            downDifferentCharacter(isSameText, column);
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+          break;
+        }
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  if (ses->winy != oldwiny) {
+    if (command & BRL_FLG_MOTION_TOLEFT) {
+      ses->winx = 0;
+    }
+  }
+
+  cancelDelayedCursorTrackingAlarm();
+  return 1;
+}
+
+int
+addNavigationCommands (void) {
+  return pushCommandHandler("navigation", KTB_CTX_DEFAULT,
+                            handleNavigationCommands, NULL, NULL);
+}
diff --git a/Programs/cmd_navigation.h b/Programs/cmd_navigation.h
new file mode 100644
index 0000000..976ce29
--- /dev/null
+++ b/Programs/cmd_navigation.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_NAVIGATION
+#define BRLTTY_INCLUDED_CMD_NAVIGATION
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addNavigationCommands (void);
+
+extern int addPromptPattern (const char *string);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_NAVIGATION */
diff --git a/Programs/cmd_override.c b/Programs/cmd_override.c
new file mode 100644
index 0000000..df23805
--- /dev/null
+++ b/Programs/cmd_override.c
@@ -0,0 +1,205 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "alert.h"
+#include "cmd_queue.h"
+#include "cmd_override.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "scr.h"
+#include "prefs.h"
+#include "core.h"
+
+typedef struct {
+  int row;
+
+  struct {
+    int first;
+    int last;
+  } column;
+} SelectionEndpoint;
+
+typedef struct {
+  struct {
+    SelectionEndpoint start;
+    SelectionEndpoint end;
+    unsigned char started:1;
+  } selection;
+} OverrideCommandData;
+
+static int
+getSelectionEndpoint (int arg, SelectionEndpoint *endpoint) {
+  return getCharacterCoordinates(arg, &endpoint->row, &endpoint->column.first, &endpoint->column.last, 0);
+}
+
+static int
+compareSelectionEndpoints (const SelectionEndpoint *endpoint1, const SelectionEndpoint *endpoint2) {
+  if (endpoint1->row < endpoint2->row) return -1;
+  if (endpoint1->row > endpoint2->row) return 1;
+
+  if (endpoint1->column.first < endpoint2->column.first) return -1;
+  if (endpoint1->column.first > endpoint2->column.first) return 1;
+
+  return 0;
+}
+
+static int
+setSelection (const SelectionEndpoint *start, const SelectionEndpoint *end, OverrideCommandData *ocd) {
+  {
+    const SelectionEndpoint *from = start;
+    const SelectionEndpoint *to = end;
+
+    if (compareSelectionEndpoints(from, to) > 0) {
+      const SelectionEndpoint *temp = from;
+      from = to;
+      to = temp;
+    }
+
+    if (!setScreenTextSelection(from->column.first, from->row, to->column.last, to->row)) {
+      return 0;
+    }
+  }
+
+  ocd->selection.start = *start;
+  ocd->selection.end = *end;
+  return 1;
+}
+
+static int
+startSelection (const SelectionEndpoint *start, OverrideCommandData *ocd) {
+  return setSelection(start, start, ocd);
+}
+
+static int
+handleOverrideCommands (int command, void *data) {
+  OverrideCommandData *ocd = data;
+  if (!scr.hasSelection) ocd->selection.started = 0;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_TXTSEL_CLEAR: {
+      if (clearScreenTextSelection()) {
+        ocd->selection.started = 0;
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    default: {
+      int blk = command & BRL_MSK_BLK;
+      int arg = command & BRL_MSK_ARG;
+      int ext = BRL_CODE_GET(EXT, command);
+
+      switch (blk) {
+        case BRL_CMD_BLK(TXTSEL_SET): {
+          if (ext > arg) {
+            SelectionEndpoint start;
+
+            if (getSelectionEndpoint(arg, &start)) {
+              SelectionEndpoint end;
+
+              if (getSelectionEndpoint(ext, &end)) {
+                if (setSelection(&start, &end, ocd)) {
+                  ocd->selection.started = 0;
+                  break;
+                }
+              }
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(TXTSEL_START): {
+          SelectionEndpoint start;
+
+          if (getSelectionEndpoint(arg, &start)) {
+            if (startSelection(&start, ocd)) {
+              ocd->selection.started = 1;
+              break;
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        case BRL_CMD_BLK(ROUTE): {
+          if (!ocd->selection.started && !prefs.startSelectionWithRoutingKey) return 0;
+          SelectionEndpoint endpoint;
+
+          if (getSelectionEndpoint(arg, &endpoint)) {
+            if (ocd->selection.started) {;
+              if (setSelection(&ocd->selection.start, &endpoint, ocd)) break;
+            } else if ((endpoint.row != scr.posy) || !((endpoint.column.first <= scr.posx) && (scr.posx <= endpoint.column.last))) {
+              return 0;
+            } else if (startSelection(&endpoint, ocd)) {
+              ocd->selection.started = 1;
+              break;
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+static void
+destroyOverrideCommandData (void *data) {
+  OverrideCommandData *ocd = data;
+  free(ocd);
+}
+
+int
+addOverrideCommands (void) {
+  OverrideCommandData *ocd;
+
+  if ((ocd = malloc(sizeof(*ocd)))) {
+    memset(ocd, 0, sizeof(*ocd));
+
+    ocd->selection.started = 0;
+
+    if (pushCommandHandler("override", KTB_CTX_DEFAULT,
+                           handleOverrideCommands, destroyOverrideCommandData, ocd)) {
+      return 1;
+    }
+
+    free(ocd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_override.h b/Programs/cmd_override.h
new file mode 100644
index 0000000..0b8874f
--- /dev/null
+++ b/Programs/cmd_override.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_OVERRIDE
+#define BRLTTY_INCLUDED_CMD_OVERRIDE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addOverrideCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_OVERRIDE */
diff --git a/Programs/cmd_preferences.c b/Programs/cmd_preferences.c
new file mode 100644
index 0000000..4a1b6be
--- /dev/null
+++ b/Programs/cmd_preferences.c
@@ -0,0 +1,191 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "cmd_queue.h"
+#include "cmd_preferences.h"
+#include "brl_cmds.h"
+#include "prefs.h"
+#include "menu.h"
+#include "menu_prefs.h"
+#include "scr_special.h"
+#include "scr_menu.h"
+#include "message.h"
+#include "alert.h"
+#include "core.h"
+
+typedef struct {
+  PreferenceSettings savedPreferences;
+} PreferencesCommandData;
+
+static int
+save (void) {
+  int saved = savePreferences();
+
+  if (saved) {
+    alert(ALERT_COMMAND_DONE);
+  } else {
+    message(NULL, gettext("not saved"), 0);
+  }
+
+  return saved;
+}
+
+static int
+handlePreferencesCommands (int command, void *data) {
+  static const char modeString_preferences[] = "prf";
+  PreferencesCommandData *pcd = data;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_PREFMENU: {
+      int ok = 0;
+
+      if (isSpecialScreen(SCR_MENU)) {
+        if (prefs.saveOnExit) save();
+        deactivateSpecialScreen(SCR_MENU);
+        ok = 1;
+      } else if (activateSpecialScreen(SCR_MENU)) {
+        updateLogMessagesSubmenu();
+        updateSessionAttributes();
+        pcd->savedPreferences = prefs;
+        ok = 1;
+      }
+
+      if (ok) {
+        infoMode = 0;
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_PREFSAVE:
+      if (isSpecialScreen(SCR_MENU)) {
+        save();
+        deactivateSpecialScreen(SCR_MENU);
+      } else if (!save()) {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+      break;
+
+    case BRL_CMD_PREFLOAD:
+      if (isSpecialScreen(SCR_MENU)) {
+        setPreferences(&pcd->savedPreferences);
+        menuScreenUpdated();
+        message(modeString_preferences, gettext("changes discarded"), 0);
+      } else if (loadPreferences(0)) {
+        menuScreenUpdated();
+        alert(ALERT_COMMAND_DONE);
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+      break;
+
+    case BRL_CMD_PREFRESET:
+      if (loadPreferences(1)) {
+        menuScreenUpdated();
+        alert(ALERT_COMMAND_DONE);
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+      break;
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+
+      switch (command & BRL_MSK_BLK) {
+        {
+          MenuItem *item;
+
+        case BRL_CMD_BLK(SET_TEXT_TABLE):
+          item = getPreferencesMenuItem_textTable();
+          goto doSetMenuItem;
+
+        case BRL_CMD_BLK(SET_ATTRIBUTES_TABLE):
+          item = getPreferencesMenuItem_attributesTable();
+          goto doSetMenuItem;
+
+        case BRL_CMD_BLK(SET_CONTRACTION_TABLE):
+          item = getPreferencesMenuItem_contractionTable();
+          goto doSetMenuItem;
+
+        case BRL_CMD_BLK(SET_KEYBOARD_TABLE):
+          item = getPreferencesMenuItem_keyboardTable();
+          goto doSetMenuItem;
+
+        case BRL_CMD_BLK(SET_LANGUAGE_PROFILE):
+          item = getPreferencesMenuItem_languageProfile();
+          goto doSetMenuItem;
+
+        doSetMenuItem:
+          if (item) {
+            unsigned int count = brl.textColumns;
+
+            if (count <= arg) count = arg + 1;
+            changeMenuItem(item);
+
+            if (changeMenuSettingScaled(getMenuItemMenu(item), arg, count)) {
+              break;
+            }
+          }
+
+          alert(ALERT_COMMAND_REJECTED);
+          break;
+        }
+
+        default:
+          return 0;
+      }
+
+      break;
+    }
+  }
+
+  return 1;
+}
+
+static void
+destroyPreferencesCommandData (void *data) {
+  PreferencesCommandData *pcd = data;
+  free(pcd);
+}
+
+int
+addPreferencesCommands (void) {
+  PreferencesCommandData *pcd;
+
+  if ((pcd = malloc(sizeof(*pcd)))) {
+    memset(pcd, 0, sizeof(*pcd));
+
+    if (pushCommandHandler("preferences", KTB_CTX_DEFAULT,
+                           handlePreferencesCommands, destroyPreferencesCommandData, pcd)) {
+      return 1;
+    }
+
+    free(pcd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_preferences.h b/Programs/cmd_preferences.h
new file mode 100644
index 0000000..62b2120
--- /dev/null
+++ b/Programs/cmd_preferences.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_PREFERENCES
+#define BRLTTY_INCLUDED_CMD_PREFERENCES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addPreferencesCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_PREFERENCES */
diff --git a/Programs/cmd_queue.c b/Programs/cmd_queue.c
new file mode 100644
index 0000000..832bd40
--- /dev/null
+++ b/Programs/cmd_queue.c
@@ -0,0 +1,405 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "cmd_queue.h"
+#include "cmd_enqueue.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "cmd.h"
+#include "queue.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "prefs.h"
+#include "ktb_types.h"
+#include "scr.h"
+#include "core.h"
+
+#define LOG_LEVEL LOG_DEBUG
+
+typedef struct CommandEnvironmentStruct CommandEnvironment;
+typedef struct CommandHandlerLevelStruct CommandHandlerLevel;
+
+struct CommandHandlerLevelStruct {
+  CommandHandlerLevel *previousLevel;
+  const char *levelName;
+
+  CommandHandler *handleCommand;
+  CommandDataDestructor *destroyData;
+  void *handlerData;
+  KeyTableCommandContext commandContext;
+};
+
+struct CommandEnvironmentStruct {
+  CommandEnvironment *previousEnvironment;
+  const char *environmentName;
+
+  CommandHandlerLevel *handlerStack;
+  CommandPreprocessor *preprocessCommand;
+  CommandPostprocessor *postprocessCommand;
+  unsigned handlingCommand:1;
+};
+
+static CommandEnvironment *commandEnvironmentStack = NULL;
+static unsigned int commandQueueSuspendCount = 0;
+
+static CommandHandlerLevel **
+getCommandHandlerTop (void) {
+  if (!commandEnvironmentStack) return NULL;
+  return &commandEnvironmentStack->handlerStack;
+}
+
+KeyTableCommandContext
+getCurrentCommandContext (void) {
+  CommandHandlerLevel **top = getCommandHandlerTop();
+  KeyTableCommandContext context = top && *top? (*top)->commandContext: KTB_CTX_DEFAULT;
+
+  if (context == KTB_CTX_DEFAULT) context = getScreenCommandContext();
+  return context;
+}
+
+static int
+toPreferredCommand (int command) {
+  int preferred = command;
+
+  int cmd = command & BRL_MSK_CMD;
+  int blk = command & BRL_MSK_BLK;
+
+  if (blk) {
+  } else {
+    if (prefs.skipIdenticalLines) {
+      switch (cmd) {
+        case BRL_CMD_LNUP:
+          preferred = BRL_CMD_PRDIFLN;
+          break;
+
+        case BRL_CMD_LNDN:
+          preferred = BRL_CMD_NXDIFLN;
+          break;
+
+        case BRL_CMD_PRDIFLN:
+          preferred = BRL_CMD_LNUP;
+          break;
+
+        case BRL_CMD_NXDIFLN:
+          preferred = BRL_CMD_LNDN;
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    if (prefs.skipBlankBrailleWindows) {
+      switch (cmd) {
+        case BRL_CMD_FWINLT:
+          preferred = BRL_CMD_FWINLTSKIP;
+          break;
+
+        case BRL_CMD_FWINRT:
+          preferred = BRL_CMD_FWINRTSKIP;
+          break;
+
+        case BRL_CMD_FWINLTSKIP:
+          preferred = BRL_CMD_FWINLT;
+          break;
+
+        case BRL_CMD_FWINRTSKIP:
+          preferred = BRL_CMD_FWINRT;
+          break;
+
+        default:
+          break;
+      }
+    }
+  }
+
+  if (preferred == command) {
+    logCommand(command);
+  } else {
+    preferred |= (command & ~BRL_MSK_CMD);
+    logTransformedCommand(command, preferred);
+    command = preferred;
+  }
+
+  return command;
+}
+
+int
+handleCommand (int command) {
+  const CommandEnvironment *env = commandEnvironmentStack;
+  const CommandHandlerLevel *chl = env->handlerStack;
+
+  while (chl) {
+    if (chl->handleCommand(command, chl->handlerData)) return 1;
+    chl = chl->previousLevel;
+  }
+
+  logMessage(LOG_WARNING, "%s: %04X", gettext("unhandled command"), command);
+  return 0;
+}
+
+typedef struct {
+  int command;
+} CommandQueueItem;
+
+static void
+deallocateCommandQueueItem (void *item, void *data) {
+  CommandQueueItem *cmd = item;
+
+  free(cmd);
+}
+
+static Queue *
+createCommandQueue (void *data) {
+  return newQueue(deallocateCommandQueueItem, NULL);
+}
+
+static Queue *
+getCommandQueue (int create) {
+  static Queue *commands = NULL;
+
+  return getProgramQueue(&commands, "command-queue", create,
+                         createCommandQueue, NULL);
+}
+
+static int
+dequeueCommand (Queue *queue) {
+  CommandQueueItem *item;
+
+  if ((item = dequeueItem(queue))) {
+    int command = item->command;
+
+    free(item);
+    item = NULL;
+
+    return command;
+  }
+
+  return EOF;
+}
+
+static void setCommandAlarm (void *data);
+static AsyncHandle commandAlarm = NULL;
+
+ASYNC_ALARM_CALLBACK(handleCommandAlarm) {
+  Queue *queue = getCommandQueue(0);
+
+  asyncDiscardHandle(commandAlarm);
+  commandAlarm = NULL;
+
+  if (queue) {
+    int command = dequeueCommand(queue);
+
+    if (command != EOF) {
+      command = toPreferredCommand(command);
+      const CommandEntry *cmd = findCommandEntry(command);
+
+      CommandEnvironment *env = commandEnvironmentStack;
+      env->handlingCommand = 1;
+
+      void *pre = env->preprocessCommand? env->preprocessCommand(): NULL;
+      int handled = handleCommand(command);
+
+      if (env->postprocessCommand) {
+        env->postprocessCommand(pre, command, cmd, handled);
+      }
+
+      env->handlingCommand = 0;
+    }
+  }
+
+  setCommandAlarm(parameters->data);
+}
+
+static void
+setCommandAlarm (void *data) {
+  if (!commandAlarm && !commandQueueSuspendCount) {
+    const CommandEnvironment *env = commandEnvironmentStack;
+
+    if (env && !env->handlingCommand) {
+      Queue *queue = getCommandQueue(0);
+
+      if (queue && (getQueueSize(queue) > 0)) {
+        asyncNewRelativeAlarm(&commandAlarm, 0, handleCommandAlarm, data);
+      }
+    }
+  }
+}
+
+static void
+cancelCommandAlarm (void) {
+  if (commandAlarm) {
+    asyncCancelRequest(commandAlarm);
+    commandAlarm = NULL;
+  }
+}
+
+int
+enqueueCommand (int command) {
+  if (command == EOF) return 1;
+
+  {
+    Queue *queue = getCommandQueue(1);
+
+    if (queue) {
+      CommandQueueItem *item = malloc(sizeof(CommandQueueItem));
+
+      if (item) {
+        item->command = command;
+
+        if (enqueueItem(queue, item)) {
+          setCommandAlarm(NULL);
+          return 1;
+        }
+
+        free(item);
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return 0;
+}
+
+int
+pushCommandHandler (
+  const char *name,
+  KeyTableCommandContext context,
+  CommandHandler *handler,
+  CommandDataDestructor *destructor,
+  void *data
+) {
+  CommandHandlerLevel *chl;
+
+  if ((chl = malloc(sizeof(*chl)))) {
+    memset(chl, 0, sizeof(*chl));
+    chl->levelName = name;
+    chl->handleCommand = handler;
+    chl->destroyData = destructor;
+    chl->handlerData = data;
+    chl->commandContext = context;
+
+    {
+      CommandHandlerLevel **top = getCommandHandlerTop();
+
+      chl->previousLevel = *top;
+      *top = chl;
+    }
+
+    logMessage(LOG_LEVEL, "pushed command handler: %s", chl->levelName);
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+int
+popCommandHandler (void) {
+  CommandHandlerLevel **top = getCommandHandlerTop();
+  CommandHandlerLevel *chl = *top;
+
+  if (!chl) return 0;
+  *top = chl->previousLevel;
+
+  logMessage(LOG_LEVEL, "popped command handler: %s", chl->levelName);
+  if (chl->destroyData) chl->destroyData(chl->handlerData);
+  free(chl);
+  return 1;
+}
+
+int
+pushCommandEnvironment (
+  const char *name,
+  CommandPreprocessor *preprocessCommand,
+  CommandPostprocessor *postprocessCommand
+) {
+  CommandEnvironment *env;
+
+  if ((env = malloc(sizeof(*env)))) {
+    memset(env, 0, sizeof(*env));
+    env->environmentName = name;
+    env->handlerStack = NULL;
+    env->preprocessCommand = preprocessCommand;
+    env->postprocessCommand = postprocessCommand;
+    env->handlingCommand = 0;
+
+    env->previousEnvironment = commandEnvironmentStack;
+    commandEnvironmentStack = env;
+    setCommandAlarm(NULL);
+
+    logMessage(LOG_LEVEL, "pushed command environment: %s", env->environmentName);
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+int
+popCommandEnvironment (void) {
+  CommandEnvironment *env = commandEnvironmentStack;
+
+  if (!env) return 0;
+  while (popCommandHandler());
+  commandEnvironmentStack = env->previousEnvironment;
+
+  {
+    const CommandEnvironment *env = commandEnvironmentStack;
+
+    if (!env || env->handlingCommand) {
+      cancelCommandAlarm();
+    }
+  }
+
+  logMessage(LOG_LEVEL, "popped command environment: %s", env->environmentName);
+  free(env);
+  return 1;
+}
+
+int
+beginCommandQueue (void) {
+  commandEnvironmentStack = NULL;
+  commandQueueSuspendCount = 0;
+
+  return pushCommandEnvironment("initial", NULL, NULL);
+}
+
+void
+endCommandQueue (void) {
+  while (popCommandEnvironment());
+}
+
+void
+suspendCommandQueue (void) {
+  if (!commandQueueSuspendCount++) cancelCommandAlarm();
+}
+
+void
+resumeCommandQueue (void) {
+  if (!--commandQueueSuspendCount) setCommandAlarm(NULL);
+}
diff --git a/Programs/cmd_queue.h b/Programs/cmd_queue.h
new file mode 100644
index 0000000..518cf00
--- /dev/null
+++ b/Programs/cmd_queue.h
@@ -0,0 +1,66 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_QUEUE
+#define BRLTTY_INCLUDED_CMD_QUEUE
+
+#include "cmd_types.h"
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int beginCommandQueue (void);
+extern void endCommandQueue (void);
+
+extern void suspendCommandQueue (void);
+extern void resumeCommandQueue (void);
+
+typedef void *CommandPreprocessor (void);
+typedef void CommandPostprocessor (void *state, int command, const CommandEntry *cmd, int handled);
+
+extern int pushCommandEnvironment (
+  const char *name,
+  CommandPreprocessor *preprocessCommand,
+  CommandPostprocessor *postprocessCommand
+);
+
+extern int popCommandEnvironment (void);
+
+typedef int CommandHandler (int command, void *data);
+typedef void CommandDataDestructor (void *data);
+
+extern int pushCommandHandler (
+  const char *name,
+  KeyTableCommandContext context,
+  CommandHandler *handler,
+  CommandDataDestructor *destructor,
+  void *data
+);
+
+extern int popCommandHandler (void);
+
+extern int handleCommand (int command);
+extern KeyTableCommandContext getCurrentCommandContext (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_QUEUE */
diff --git a/Programs/cmd_speech.c b/Programs/cmd_speech.c
new file mode 100644
index 0000000..2cfb466
--- /dev/null
+++ b/Programs/cmd_speech.c
@@ -0,0 +1,545 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "embed.h"
+#include "strfmt.h"
+#include "cmd_queue.h"
+#include "cmd_speech.h"
+#include "cmd_utils.h"
+#include "brl_cmds.h"
+#include "prefs.h"
+#include "alert.h"
+#include "spk.h"
+#include "scr.h"
+#include "update.h"
+#include "core.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static void
+sayScreenRegion (int left, int top, int width, int height, int track, SayMode mode) {
+  if (mode == sayImmediate) muteSpeech(&spk, __func__);
+
+  size_t count = width * height;
+  ScreenCharacter characters[count];
+  readScreen(left, top, width, height, characters);
+
+  {
+    ScreenCharacter *character = characters;
+    ScreenCharacter *end = character + count;
+    character += width - 1;
+
+    while (character < end) {
+      if (iswspace(character->text)) character->text = WC_C('\n');
+      character += width;
+    }
+  }
+
+  spk.track.isActive = track;
+  spk.track.screenNumber = scr.number;
+  spk.track.firstLine = top;
+  spk.track.speechLocation = SPK_LOC_NONE;
+
+  sayScreenCharacters(characters, count, 0);
+}
+
+static void
+sayScreenLines (int line, int count, int track, SayMode mode) {
+  sayScreenRegion(0, line, scr.cols, count, track, mode);
+}
+
+static void
+speakDone (const ScreenCharacter *line, int column, int count, int spell) {
+  ScreenCharacter internalBuffer[count];
+
+  if (line) {
+    line = &line[column];
+  } else {
+    readScreen(column, ses->spky, count, 1, internalBuffer);
+    line = internalBuffer;
+  }
+
+  speakCharacters(line, count, spell, 1);
+  placeBrailleWindowHorizontally(ses->spkx);
+  slideBrailleWindowVertically(ses->spky);
+  suppressAutospeak();
+}
+
+static void
+speakCurrentCharacter (void) {
+  speakDone(NULL, ses->spkx, 1, 0);
+}
+
+static void
+speakCurrentLine (void) {
+  speakDone(NULL, 0, scr.cols, 0);
+}
+
+static int
+handleSpeechCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_RESTARTSPEECH:
+      restartSpeechDriver();
+      break;
+
+    case BRL_CMD_SPK_STOP:
+      disableSpeechDriver(gettext("speech driver stopped"));
+      break;
+
+    case BRL_CMD_SPK_START:
+      enableSpeechDriver(1);
+      break;
+
+    case BRL_CMD_SPKHOME: {
+      if (scr.number == spk.track.screenNumber) {
+        if (startScreenCursorRouting(ses->spkx, ses->spky)) {
+          break;
+        }
+      }
+
+      alert(ALERT_COMMAND_REJECTED);
+      break;
+    }
+
+    case BRL_CMD_MUTE:
+      muteSpeech(&spk, "command");
+      break;
+
+    case BRL_CMD_SAY_LINE:
+      sayScreenLines(ses->winy, 1, 0, prefs.sayLineMode);
+      break;
+
+    case BRL_CMD_SAY_ALL:
+      sayScreenLines(0, scr.rows, 1, sayImmediate);
+      break;
+
+    case BRL_CMD_SAY_ABOVE:
+      sayScreenLines(0, ses->winy+1, 1, sayImmediate);
+      break;
+
+    case BRL_CMD_SAY_BELOW:
+      sayScreenLines(ses->winy, scr.rows-ses->winy, 1, sayImmediate);
+      break;
+
+    case BRL_CMD_SAY_SOFTER:
+      if (!canSetSpeechVolume(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechVolume > 0) {
+        setSpeechVolume(&spk, --prefs.speechVolume, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SAY_LOUDER:
+      if (!canSetSpeechVolume(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechVolume < SPK_VOLUME_MAXIMUM) {
+        setSpeechVolume(&spk, ++prefs.speechVolume, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SAY_SLOWER:
+      if (!canSetSpeechRate(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechRate > 0) {
+        setSpeechRate(&spk, --prefs.speechRate, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SAY_FASTER:
+      if (!canSetSpeechRate(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechRate < SPK_RATE_MAXIMUM) {
+        setSpeechRate(&spk, ++prefs.speechRate, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SAY_LOWER:
+      if (!canSetSpeechPitch(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechPitch > 0) {
+        setSpeechPitch(&spk, --prefs.speechPitch, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SAY_HIGHER:
+      if (!canSetSpeechPitch(&spk)) {
+        alert(ALERT_COMMAND_REJECTED);
+      } else if (prefs.speechPitch < SPK_PITCH_MAXIMUM) {
+        setSpeechPitch(&spk, ++prefs.speechPitch, 1);
+      } else {
+        alert(ALERT_NO_CHANGE);
+      }
+      break;
+
+    case BRL_CMD_SPEAK_CURR_CHAR:
+      speakCurrentCharacter();
+      break;
+
+    case BRL_CMD_SPEAK_PREV_CHAR:
+      if (ses->spkx > 0) {
+        ses->spkx -= 1;
+        speakCurrentCharacter();
+      } else if (ses->spky > 0) {
+        ses->spky -= 1;
+        ses->spkx = scr.cols - 1;
+        alert(ALERT_WRAP_UP);
+        speakCurrentCharacter();
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+
+    case BRL_CMD_SPEAK_NEXT_CHAR:
+      if (ses->spkx < (scr.cols - 1)) {
+        ses->spkx += 1;
+        speakCurrentCharacter();
+      } else if (ses->spky < (scr.rows - 1)) {
+        ses->spky += 1;
+        ses->spkx = 0;
+        alert(ALERT_WRAP_DOWN);
+        speakCurrentCharacter();
+      } else {
+        alert(ALERT_BOUNCE);
+      }
+      break;
+
+    case BRL_CMD_SPEAK_FRST_CHAR: {
+      ScreenCharacter characters[scr.cols];
+      int column;
+
+      readScreenRow(ses->spky, scr.cols, characters);
+      if ((column = findFirstNonSpaceCharacter(characters, scr.cols)) >= 0) {
+        ses->spkx = column;
+        speakDone(characters, column, 1, 0);
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_SPEAK_LAST_CHAR: {
+      ScreenCharacter characters[scr.cols];
+      int column;
+
+      readScreenRow(ses->spky, scr.cols, characters);
+      if ((column = findLastNonSpaceCharacter(characters, scr.cols)) >= 0) {
+        ses->spkx = column;
+        speakDone(characters, column, 1, 0);
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    {
+      int direction;
+      int spell;
+
+    case BRL_CMD_SPEAK_PREV_WORD:
+      direction = -1;
+      spell = 0;
+      goto speakWord;
+
+    case BRL_CMD_SPEAK_NEXT_WORD:
+      direction = 1;
+      spell = 0;
+      goto speakWord;
+
+    case BRL_CMD_SPEAK_CURR_WORD:
+      direction = 0;
+      spell = 0;
+      goto speakWord;
+
+    case BRL_CMD_SPELL_CURR_WORD:
+      direction = 0;
+      spell = 1;
+      goto speakWord;
+
+    speakWord:
+      {
+        int row = ses->spky;
+        int column = ses->spkx;
+
+        ScreenCharacter characters[scr.cols];
+        int onCurrentWord;
+        int onSpace;
+
+        int from = column;
+        int to = from + 1;
+
+      findWord:
+        readScreenRow(row, scr.cols, characters);
+        onCurrentWord = (row == ses->spky) && !iswspace(characters[column].text);
+        onSpace = !onCurrentWord;
+
+        if (direction < 0) {
+          while (1) {
+            if (column == 0) {
+              if (!onSpace && !onCurrentWord) {
+                ses->spkx = from = column;
+                ses->spky = row;
+                break;
+              }
+
+              if (row == 0) goto noWord;
+              if (row-- == ses->spky) alert(ALERT_WRAP_UP);
+              column = scr.cols;
+              goto findWord;
+            }
+
+            {
+              int isSpace = iswspace(characters[--column].text);
+
+              if (isSpace != onSpace) {
+                if (onCurrentWord) {
+                  onCurrentWord = 0;
+                } else if (!onSpace) {
+                  ses->spkx = from = column + 1;
+                  ses->spky = row;
+                  break;
+                }
+
+                if (!isSpace) to = column + 1;
+                onSpace = isSpace;
+              }
+            }
+          }
+        } else if (direction > 0) {
+          while (1) {
+            if (++column == scr.cols) {
+              if (!onSpace && !onCurrentWord) {
+                to = column;
+                ses->spkx = from;
+                ses->spky = row;
+                break;
+              }
+
+              if (row == (scr.rows - 1)) goto noWord;
+              if (row++ == ses->spky) alert(ALERT_WRAP_DOWN);
+              column = -1;
+              goto findWord;
+            }
+
+            {
+              int isSpace = iswspace(characters[column].text);
+
+              if (isSpace != onSpace) {
+                if (onCurrentWord) {
+                  onCurrentWord = 0;
+                } else if (!onSpace) {
+                  to = column;
+                  ses->spkx = from;
+                  ses->spky = row;
+                  break;
+                }
+
+                if (!isSpace) from = column;
+                onSpace = isSpace;
+              }
+            }
+          }
+        } else if (!onSpace) {
+          while (from > 0) {
+            if (iswspace(characters[--from].text)) {
+              from += 1;
+              break;
+            }
+          }
+
+          while (to < scr.cols) {
+            if (iswspace(characters[to].text)) break;
+            to += 1;
+          }
+        }
+
+        speakDone(characters, from, to-from, spell);
+        break;
+      }
+
+    noWord:
+      alert(ALERT_BOUNCE);
+      break;
+    }
+
+    case BRL_CMD_SPEAK_CURR_LINE:
+      speakCurrentLine();
+      break;
+
+    {
+      int increment;
+      int limit;
+
+    case BRL_CMD_SPEAK_PREV_LINE:
+      increment = -1;
+      limit = 0;
+      goto speakLine;
+
+    case BRL_CMD_SPEAK_NEXT_LINE:
+      increment = 1;
+      limit = scr.rows - 1;
+      goto speakLine;
+
+    speakLine:
+      if (ses->spky == limit) {
+        alert(ALERT_BOUNCE);
+      } else {
+        if (prefs.skipIdenticalLines) {
+          ScreenCharacter original[scr.cols];
+          ScreenCharacter current[scr.cols];
+          unsigned int count = 0;
+
+          readScreenRow(ses->spky, scr.cols, original);
+
+          do {
+            readScreenRow(ses->spky+=increment, scr.cols, current);
+            if (!isSameRow(original, current, scr.cols, isSameText)) break;
+            alertLineSkipped(&count);
+          } while (ses->spky != limit);
+        } else {
+          ses->spky += increment;
+        }
+
+        speakCurrentLine();
+      }
+
+      break;
+    }
+
+    case BRL_CMD_SPEAK_FRST_LINE: {
+      ScreenCharacter characters[scr.cols];
+      int row = 0;
+
+      while (row < scr.rows) {
+        readScreenRow(row, scr.cols, characters);
+        if (!isAllSpaceCharacters(characters, scr.cols)) break;
+        row += 1;
+      }
+
+      if (row < scr.rows) {
+        ses->spky = row;
+        ses->spkx = 0;
+        speakCurrentLine();
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_SPEAK_LAST_LINE: {
+      ScreenCharacter characters[scr.cols];
+      int row = scr.rows - 1;
+
+      while (row >= 0) {
+        readScreenRow(row, scr.cols, characters);
+        if (!isAllSpaceCharacters(characters, scr.cols)) break;
+        row -= 1;
+      }
+
+      if (row >= 0) {
+        ses->spky = row;
+        ses->spkx = 0;
+        speakCurrentLine();
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_DESC_CURR_CHAR: {
+      char description[0X50];
+      STR_BEGIN(description, sizeof(description));
+      STR_FORMAT(formatPhoneticPhrase, ses->spkx, ses->spky);
+      STR_END;
+      sayString(&spk, description, SAY_OPT_MUTE_FIRST);
+      break;
+    }
+
+    case BRL_CMD_ROUTE_CURR_LOCN: {
+      if (!startScreenCursorRouting(ses->spkx, ses->spky)) {
+        alert(ALERT_COMMAND_REJECTED);
+      }
+
+      break;
+    }
+
+    case BRL_CMD_SPEAK_CURR_LOCN: {
+      char buffer[0X50];
+      snprintf(buffer, sizeof(buffer), "%s %d, %s %d",
+               gettext("line"), ses->spky+1,
+               gettext("column"), ses->spkx+1);
+      sayString(&spk, buffer, SAY_OPT_MUTE_FIRST);
+      break;
+    }
+
+    case BRL_CMD_SPEAK_INDENT:
+      speakIndent(NULL, 0, 1);
+      break;
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(ROUTE_SPEECH): {
+          int column, row;
+
+          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
+            ses->spkx = column;
+            ses->spky = row;
+          } else {
+            alert(ALERT_COMMAND_REJECTED);
+          }
+
+          break;
+        }
+
+        default:
+          return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+int
+addSpeechCommands (void) {
+#ifdef ENABLE_SPEECH_SUPPORT
+  return pushCommandHandler("speech", KTB_CTX_DEFAULT,
+                            handleSpeechCommands, NULL, NULL);
+#else /* ENABLE_SPEECH_SUPPORT */
+  return 0;
+#endif /* ENABLE_SPEECH_SUPPORT */
+}
diff --git a/Programs/cmd_speech.h b/Programs/cmd_speech.h
new file mode 100644
index 0000000..c66bd2b
--- /dev/null
+++ b/Programs/cmd_speech.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_SPEECH
+#define BRLTTY_INCLUDED_CMD_SPEECH
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addSpeechCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_SPEECH */
diff --git a/Programs/cmd_toggle.c b/Programs/cmd_toggle.c
new file mode 100644
index 0000000..58c51a8
--- /dev/null
+++ b/Programs/cmd_toggle.c
@@ -0,0 +1,306 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "parameters.h"
+#include "api_control.h"
+#include "cmd_queue.h"
+#include "cmd_toggle.h"
+#include "brl_cmds.h"
+#include "prefs.h"
+#include "scr.h"
+#include "scr_special.h"
+#include "scr_menu.h"
+#include "alert.h"
+#include "tune.h"
+#include "core.h"
+
+typedef enum {
+  TOGGLE_ERROR,
+  TOGGLE_SAME,
+  TOGGLE_OFF,
+  TOGGLE_ON
+} ToggleResult;
+
+static ToggleResult
+toggleBit (
+  int *bits, int bit, int command,
+  AlertIdentifier offAlert,
+  AlertIdentifier onAlert
+) {
+  int oldBits = *bits;
+
+  switch (command & BRL_FLG_TOGGLE_MASK) {
+    case 0:
+      *bits ^= bit;
+      break;
+
+    case BRL_FLG_TOGGLE_ON:
+      *bits |= bit;
+      break;
+
+    case BRL_FLG_TOGGLE_OFF:
+      *bits &= ~bit;
+      break;
+
+    default:
+      alert(ALERT_COMMAND_REJECTED);
+      return TOGGLE_ERROR;
+  }
+
+  {
+    int isOn = (*bits & bit) != 0;
+    AlertIdentifier identifier = isOn? onAlert: offAlert;
+
+    alert(identifier);
+    if (*bits != oldBits) return isOn? TOGGLE_ON: TOGGLE_OFF;
+
+    tuneWait(TUNE_TOGGLE_REPEAT_DELAY);
+    alert(identifier);
+    return TOGGLE_SAME;
+  }
+}
+
+static ToggleResult
+toggleSetting (
+  unsigned char *setting, int command,
+  AlertIdentifier offAlert,
+  AlertIdentifier onAlert
+) {
+  const int bit = 1;
+  int bits = *setting? bit: 0;
+  ToggleResult result = toggleBit(&bits, bit, command, offAlert, onAlert);
+
+  *setting = (bits & bit)? bit: 0;
+  return result;
+}
+
+static ToggleResult
+togglePreferenceSetting (unsigned char *setting, int command) {
+  ToggleResult result = toggleSetting(setting, command, ALERT_TOGGLE_OFF, ALERT_TOGGLE_ON);
+  if (result > TOGGLE_SAME) menuScreenUpdated();
+  return result;
+}
+
+static ToggleResult
+toggleModeSetting (unsigned char *setting, int command) {
+  return toggleSetting(setting, command, ALERT_NONE, ALERT_NONE);
+}
+
+static ToggleResult
+toggleFunctionalSetting (
+  int command,
+  int (*get) (void),
+  void (*set) (int value)
+) {
+  const int bit = 1;
+  int bits = get()? bit: 0;
+
+  ToggleResult result = toggleBit(
+    &bits, bit, command,
+    ALERT_TOGGLE_OFF, ALERT_TOGGLE_ON
+  );
+
+  set(!!(bits & bit));
+  return result;
+}
+
+static int
+handleToggleCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_SKPIDLNS:
+      togglePreferenceSetting(&prefs.skipIdenticalLines, command);
+      api.updateParameter(BRLAPI_PARAM_SKIP_IDENTICAL_LINES, 0);
+      break;
+
+    case BRL_CMD_SKPBLNKWINS:
+      togglePreferenceSetting(&prefs.skipBlankBrailleWindows, command);
+      break;
+
+    case BRL_CMD_SLIDEWIN:
+      togglePreferenceSetting(&prefs.slidingBrailleWindow, command);
+      break;
+
+    case BRL_CMD_SIXDOTS:
+      togglePreferenceSetting(&prefs.brailleVariant, command);
+      onBrailleVariantUpdated();
+      break;
+
+    case BRL_CMD_CONTRACTED:
+      toggleFunctionalSetting(command, isContractedBraille, setContractedBraille);
+      break;
+
+    case BRL_CMD_COMPBRL6:
+      toggleFunctionalSetting(command, isSixDotComputerBraille, setSixDotComputerBraille);
+      break;
+
+    case BRL_CMD_CSRTRK:
+      toggleSetting(&ses->trackScreenCursor, command, ALERT_CURSOR_UNLINKED, ALERT_CURSOR_LINKED);
+
+      if (ses->trackScreenCursor) {
+#ifdef ENABLE_SPEECH_SUPPORT
+        if (spk.track.isActive && (scr.number == spk.track.screenNumber)) {
+          spk.track.speechLocation = SPK_LOC_NONE;
+        } else
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+        {
+          trackScreenCursor(1);
+        }
+      }
+      break;
+
+    case BRL_CMD_CSRSIZE:
+      togglePreferenceSetting(&prefs.screenCursorStyle, command);
+      break;
+
+    case BRL_CMD_CSRVIS:
+      togglePreferenceSetting(&prefs.showScreenCursor, command);
+      break;
+
+    case BRL_CMD_CSRHIDE:
+      toggleModeSetting(&ses->hideScreenCursor, command);
+      break;
+
+    case BRL_CMD_CSRBLINK:
+      togglePreferenceSetting(&prefs.blinkingScreenCursor, command);
+      break;
+
+    case BRL_CMD_ATTRVIS:
+      togglePreferenceSetting(&prefs.showAttributes, command);
+      break;
+
+    case BRL_CMD_ATTRBLINK:
+      togglePreferenceSetting(&prefs.blinkingAttributes, command);
+      break;
+
+    case BRL_CMD_CAPBLINK:
+      togglePreferenceSetting(&prefs.blinkingCapitals, command);
+      break;
+
+    case BRL_CMD_AUTOREPEAT:
+      togglePreferenceSetting(&prefs.autorepeatEnabled, command);
+      break;
+
+    case BRL_CMD_BRLKBD:
+      togglePreferenceSetting(&prefs.brailleKeyboardEnabled, command);
+      break;
+
+    case BRL_CMD_BRLUCDOTS:
+      togglePreferenceSetting(&prefs.brailleTypingMode, command);
+      break;
+
+    case BRL_CMD_TOUCH_NAV:
+      togglePreferenceSetting(&prefs.touchNavigation, command);
+      break;
+
+    case BRL_CMD_TUNES:
+      togglePreferenceSetting(&prefs.alertTunes, command);        /* toggle sound on/off */
+      api.updateParameter(BRLAPI_PARAM_AUDIBLE_ALERTS, 0);
+      break;
+
+    case BRL_CMD_AUTOSPEAK:
+      togglePreferenceSetting(&prefs.autospeak, command);
+      break;
+
+    case BRL_CMD_ASPK_SEL_LINE:
+      togglePreferenceSetting(&prefs.autospeakSelectedLine, command);
+      break;
+
+    case BRL_CMD_ASPK_SEL_CHAR:
+      togglePreferenceSetting(&prefs.autospeakSelectedCharacter, command);
+      break;
+
+    case BRL_CMD_ASPK_INS_CHARS:
+      togglePreferenceSetting(&prefs.autospeakInsertedCharacters, command);
+      break;
+
+    case BRL_CMD_ASPK_DEL_CHARS:
+      togglePreferenceSetting(&prefs.autospeakDeletedCharacters, command);
+      break;
+
+    case BRL_CMD_ASPK_REP_CHARS:
+      togglePreferenceSetting(&prefs.autospeakReplacedCharacters, command);
+      break;
+
+    case BRL_CMD_ASPK_CMP_WORDS:
+      togglePreferenceSetting(&prefs.autospeakCompletedWords, command);
+      break;
+
+    case BRL_CMD_ASPK_INDENT:
+      togglePreferenceSetting(&prefs.autospeakLineIndent, command);
+      break;
+
+    case BRL_CMD_SHOW_CURR_LOCN:
+      togglePreferenceSetting(&prefs.showSpeechCursor, command);
+      break;
+
+    case BRL_CMD_INFO:
+      if (haveStatusCells() || !(textMaximized || statusCount)) {
+        toggleModeSetting(&infoMode, command);
+      } else {
+        ToggleResult result = toggleModeSetting(&textMaximized, command);
+        if (result > TOGGLE_SAME) reconfigureBrailleWindow();
+      }
+      break;
+
+    case BRL_CMD_DISPMD:
+      toggleModeSetting(&ses->displayMode, command);
+      break;
+
+    case BRL_CMD_FREEZE: {
+      unsigned char setting;
+
+      if (isMainScreen()) {
+        setting = 0;
+      } else if (isSpecialScreen(SCR_FROZEN)) {
+        setting = 1;
+      } else {
+        alert(ALERT_COMMAND_REJECTED);
+        break;
+      }
+
+      switch (toggleSetting(&setting, command, ALERT_SCREEN_UNFROZEN, ALERT_SCREEN_FROZEN)) {
+        case TOGGLE_OFF:
+          deactivateSpecialScreen(SCR_FROZEN);
+          break;
+
+        case TOGGLE_ON:
+          if (!activateSpecialScreen(SCR_FROZEN)) alert(ALERT_COMMAND_REJECTED);
+          break;
+
+        default:
+          break;
+      }
+
+      break;
+    }
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+int
+addToggleCommands (void) {
+  return pushCommandHandler("toggle", KTB_CTX_DEFAULT,
+                            handleToggleCommands, NULL, NULL);
+}
diff --git a/Programs/cmd_toggle.h b/Programs/cmd_toggle.h
new file mode 100644
index 0000000..96ca52b
--- /dev/null
+++ b/Programs/cmd_toggle.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_TOGGLE
+#define BRLTTY_INCLUDED_CMD_TOGGLE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addToggleCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_TOGGLE */
diff --git a/Programs/cmd_touch.c b/Programs/cmd_touch.c
new file mode 100644
index 0000000..44cfb4f
--- /dev/null
+++ b/Programs/cmd_touch.c
@@ -0,0 +1,178 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "cmd_utils.h"
+#include "cmd_queue.h"
+#include "cmd_touch.h"
+#include "brl_cmds.h"
+#include "brl_utils.h"
+#include "report.h"
+#include "bitmask.h"
+#include "prefs.h"
+
+typedef struct {
+  struct {
+    ReportListenerInstance *brailleWindowUpdated;
+  } reportListeners;
+
+  BITMASK(touched, 88, int);
+  unsigned char cells[88];
+  unsigned int count;
+  unsigned int activeCells;
+  unsigned int lastActive;
+  int lastTouched;
+} TouchCommandData;
+
+static void
+resetTouched (TouchCommandData *tcd) {
+  tcd->activeCells = 0;
+  tcd->lastTouched = -1;
+  BITMASK_ZERO(tcd->touched);
+
+  for (int i = 0; i < tcd->count; ++i) {
+    if (tcd->cells[i]) {
+      BITMASK_SET(tcd->touched, i);
+      tcd->lastActive = i;
+      tcd->activeCells += 1;
+    }
+  }
+}
+
+static void
+handleTouchAt (int offset, TouchCommandData *tcd) {
+  tcd->lastTouched = offset;
+  BITMASK_CLEAR(tcd->touched, offset);
+}
+
+static void
+handleTouchOff (TouchCommandData *tcd) {
+  int ok = 0;
+
+  if (prefs.touchNavigation && (tcd->lastTouched > ((int)tcd->lastActive - 2))) {
+    BITMASK_COUNT(tcd->touched, unread);
+
+    if (tcd->activeCells && unread == 0) {
+      ok = 1;
+    }
+
+    if (!ok && tcd->activeCells && unread) {
+      float factor = (float)tcd->activeCells / unread;
+
+      if (factor > 6) ok = 1;
+    }
+  }
+
+  if (ok) {
+    resetTouched(tcd);
+    handleCommand(BRL_CMD_NXNBWIN);
+  }
+}
+
+static void
+handleBrailleWindowUpdated (
+  const BrailleWindowUpdatedReport *report, TouchCommandData *tcd
+) {
+  if (cellsHaveChanged(&tcd->cells[0], report->cells, report->count, NULL, NULL, NULL)) {
+    tcd->count = report->count;
+
+    resetTouched(tcd);
+  }
+}
+
+REPORT_LISTENER(brailleWindowUpdatedListener) {
+  TouchCommandData *tcd = parameters->listenerData;
+  const BrailleWindowUpdatedReport *report = parameters->reportData;
+
+  handleBrailleWindowUpdated(report, tcd);
+}
+
+static TouchCommandData *
+newTouchCommandData (void) {
+  TouchCommandData *tcd;
+
+  if ((tcd = malloc(sizeof(*tcd)))) {
+    memset(tcd, 0, sizeof(*tcd));
+
+    if ((tcd->reportListeners.brailleWindowUpdated = registerReportListener(REPORT_BRAILLE_WINDOW_UPDATED, brailleWindowUpdatedListener, tcd))) {
+      return tcd;
+    }
+
+    free(tcd);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+destroyTouchCommandData (TouchCommandData *tcd) {
+  unregisterReportListener(tcd->reportListeners.brailleWindowUpdated);
+  free(tcd);
+}
+
+static int
+handleTouchCommands (int command, void *data) {
+  switch (command & BRL_MSK_BLK) {
+    case BRL_CMD_BLK(TOUCH_AT): {
+      int arg = command & BRL_MSK_ARG;
+
+      if (arg == BRL_MSK_ARG) {
+        handleTouchOff(data);
+      } else {
+        int at;
+        if (isTextOffset(arg, &at, NULL, 0)) handleTouchAt(at, data);
+      }
+
+      break;
+    }
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+static void
+destructTouchCommandData (void *data) {
+  TouchCommandData *tcd = data;
+
+  destroyTouchCommandData(tcd);
+}
+
+int
+addTouchCommands (void) {
+  TouchCommandData *tcd;
+
+  if ((tcd = newTouchCommandData())) {
+    if (pushCommandHandler("touch", KTB_CTX_DEFAULT, handleTouchCommands,
+                           destructTouchCommandData, tcd)) {
+      return 1;
+    }
+
+    destroyTouchCommandData(tcd);
+  }
+
+  return 0;
+}
diff --git a/Programs/cmd_touch.h b/Programs/cmd_touch.h
new file mode 100644
index 0000000..7093d88
--- /dev/null
+++ b/Programs/cmd_touch.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_TOUCH
+#define BRLTTY_INCLUDED_CMD_TOUCH
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int addTouchCommands (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_TOUCH */
diff --git a/Programs/cmd_utils.c b/Programs/cmd_utils.c
new file mode 100644
index 0000000..f0c597f
--- /dev/null
+++ b/Programs/cmd_utils.c
@@ -0,0 +1,373 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "alert.h"
+#include "brl_cmds.h"
+#include "unicode.h"
+#include "ascii.h"
+#include "scr.h"
+#include "core.h"
+
+void
+alertLineSkipped (unsigned int *count) {
+  const unsigned int interval = 4;
+
+  if (!*count) {
+    alert(ALERT_SKIP_FIRST);
+  } else if (*count <= interval) {
+    alert(ALERT_SKIP_ONE);
+  } else if (!(*count % interval)) {
+    alert(ALERT_SKIP_SEVERAL);
+  }
+
+  *count += 1;
+}
+
+int
+isTextOffset (int arg, int *first, int *last, int relaxed) {
+  int y = arg / brl.textColumns;
+  if (y >= brl.textRows) return 0;
+  if ((ses->winy + y) >= scr.rows) return 0;
+
+  int x = arg % brl.textColumns;
+  if (x < textStart) return 0;
+  if ((x -= textStart) >= textCount) return 0;
+
+  if (isContracted) {
+    BrailleRowDescriptor *brd = getBrailleRowDescriptor(y);
+    if (!brd) return 0;
+
+    int *offsets = brd->contracted.offsets.array;
+    if (!offsets) return 0;
+
+    int start = 0;
+    int end = 0;
+
+    {
+      int textIndex = 0;
+
+      while (textIndex < brd->contracted.length) {
+        int cellIndex = offsets[textIndex];
+
+        if (cellIndex != CTB_NO_OFFSET) {
+          if (cellIndex > x) {
+            end = textIndex - 1;
+            break;
+          }
+
+          start = textIndex;
+        }
+
+        textIndex += 1;
+      }
+
+      if (textIndex == brd->contracted.length) end = textIndex - 1;
+    }
+
+    if (first) *first = start;
+    if (last) *last = end;
+  } else {
+    if ((ses->winx + x) >= scr.cols) {
+      if (!relaxed) return 0;
+      x = scr.cols - ses->winx - 1;
+    }
+
+    if (prefs.wordWrap) {
+      int length = getWordWrapLength(ses->winy, ses->winx, textCount);
+      if (length > textCount) length = textCount;
+      if (x >= length) x = length - 1;
+    }
+
+    if (first) *first = x;
+    if (last) *last = x;
+  }
+
+  return 1;
+}
+
+int
+getCharacterCoordinates (int arg, int *row, int *first, int *last, int relaxed) {
+  if (arg == BRL_MSK_ARG) {
+    if (!SCR_CURSOR_OK()) return 0;
+    *row = scr.posy;
+    if (first) *first = scr.posx;
+    if (last) *last = scr.posx;
+  } else {
+    if (!isTextOffset(arg, first, last, relaxed)) return 0;
+    if (row) *row = ses->winy;
+    if (first) *first += ses->winx;
+    if (last) *last += ses->winx;
+  }
+
+  return 1;
+}
+
+static ScreenCharacter
+getScreenCharacter (int column, int row) {
+  ScreenCharacter character;
+  readScreen(column, row, 1, 1, &character);
+  return character;
+}
+
+STR_BEGIN_FORMATTER(formatCharacterDescription, int column, int row)
+  ScreenCharacter character = getScreenCharacter(column, row);
+
+  {
+    char name[0X40];
+
+    if (getCharacterName(character.text, name, sizeof(name))) {
+      {
+        size_t length = strlen(name);
+        for (int i=0; i<length; i+=1) name[i] = tolower(name[i]);
+      }
+
+      STR_PRINTF(" %s: ", name);
+    }
+  }
+
+  {
+    uint32_t text = character.text;
+    STR_PRINTF("U+%04" PRIX32 " (%" PRIu32 "):", text, text);
+  }
+
+  {
+    STR_PRINTF(" ");
+
+    static const char *const colours[] = {
+      /*      */ strtext("black"),
+      /*    B */ strtext("blue"),
+      /*   G  */ strtext("green"),
+      /*   GB */ strtext("cyan"),
+      /*  R   */ strtext("red"),
+      /*  R B */ strtext("magenta"),
+      /*  RG  */ strtext("brown"),
+      /*  RGB */ strtext("light grey"),
+      /* L    */ strtext("dark grey"),
+      /* L  B */ strtext("light blue"),
+      /* L G  */ strtext("light green"),
+      /* L GB */ strtext("light cyan"),
+      /* LR   */ strtext("light red"),
+      /* LR B */ strtext("light magenta"),
+      /* LRG  */ strtext("yellow"),
+      /* LRGB */ strtext("white")
+    };
+
+    unsigned char attributes = character.attributes;
+    const char *foreground = gettext(colours[attributes & SCR_MASK_FG]);
+    const char *background = gettext(colours[(attributes & SCR_MASK_BG) >> 4]);
+
+    // xgettext: This phrase describes the colour of a character on the screen.
+    // xgettext: %1$s is the (already translated) foreground colour.
+    // xgettext: %2$s is the (already translated) background colour.
+    STR_PRINTF(gettext("%1$s on %2$s"), foreground, background);
+  }
+
+  if (character.attributes & SCR_ATTR_BLINK) {
+    STR_PRINTF(" %s", gettext("blinking"));
+  }
+STR_END_FORMATTER
+
+static const char *const phoneticWords[] = {
+  [' '] = "space",
+
+  ['a'] = "alpha",
+  ['b'] = "bravo",
+  ['c'] = "charlie",
+  ['d'] = "delta",
+  ['e'] = "echo",
+  ['f'] = "foxtrot",
+  ['g'] = "golf",
+  ['h'] = "hotel",
+  ['i'] = "india",
+  ['j'] = "juliet",
+  ['k'] = "kilo",
+  ['l'] = "lima",
+  ['m'] = "mike",
+  ['n'] = "november",
+  ['o'] = "oscar",
+  ['p'] = "papa",
+  ['q'] = "quebec",
+  ['r'] = "romeo",
+  ['s'] = "sierra",
+  ['t'] = "tango",
+  ['u'] = "uniform",
+  ['v'] = "victor",
+  ['w'] = "whiskey",
+  ['x'] = "x-ray",
+  ['y'] = "yankee",
+  ['z'] = "zulu",
+
+  ['0'] = "zero",
+  ['1'] = "one",
+  ['2'] = "two",
+  ['3'] = "three",
+  ['4'] = "four",
+  ['5'] = "five",
+  ['6'] = "six",
+  ['7'] = "seven",
+  ['8'] = "eight",
+  ['9'] = "nine",
+
+  ['+'] = "plus",
+  ['='] = "equals",
+  ['<'] = "less than",
+  ['>'] = "greater than",
+
+  ['('] = "left parenthesis",
+  [')'] = "right parenthesis",
+  ['['] = "left bracket",
+  [']'] = "right bracket",
+  ['{'] = "left brace",
+  ['}'] = "right brace",
+
+  ['"'] = "quote",
+  ['\''] = "apostrophe",
+  [','] = "comma",
+  [';'] = "semicolon",
+  [':'] = "colon",
+  ['.'] = "period",
+  ['!'] = "exclamation",
+  ['?'] = "question",
+
+  ['`'] = "grave",
+  ['~'] = "tilde",
+  ['@'] = "at",
+  ['#'] = "number",
+  ['$'] = "dollar",
+  ['%'] = "percent",
+  ['^'] = "circumflex",
+  ['&'] = "ampersand",
+  ['*'] = "asterisk",
+  ['-'] = "dash",
+  ['_'] = "underscore",
+
+  ['/'] = "slash",
+  ['\\'] = "backslash",
+  ['|'] = "vertical bar",
+
+  [ASCII_NUL] = "null",
+  [ASCII_SOH] = "start of header",
+  [ASCII_STX] = "start of text",
+  [ASCII_ETX] = "end of text",
+  [ASCII_EOT] = "end of transmission",
+  [ASCII_ENQ] = "enquiry",
+  [ASCII_ACK] = "acknowledgement",
+  [ASCII_BEL] = "bell",
+  [ASCII_BS] = "backspace",
+  [ASCII_HT] = "horizontal tab",
+  [ASCII_LF] = "line feed",
+  [ASCII_VT] = "vertical tab",
+  [ASCII_FF] = "form feed",
+  [ASCII_CR] = "carriage return",
+  [ASCII_SO] = "shift out",
+  [ASCII_SI] = "shift in",
+  [ASCII_DLE] = "data link escape",
+  [ASCII_DC1] = "device control one",
+  [ASCII_DC2] = "device control two",
+  [ASCII_DC3] = "device control three",
+  [ASCII_DC4] = "device control four",
+  [ASCII_NAK] = "negative acknowledgement",
+  [ASCII_SYN] = "synchronous idle",
+  [ASCII_ETB] = "end of transmission block",
+  [ASCII_CAN] = "cancel",
+  [ASCII_EM] = "end of medium",
+  [ASCII_SUB] = "substitute",
+  [ASCII_ESC] = "escape",
+  [ASCII_FS] = "file separator",
+  [ASCII_GS] = "group separator",
+  [ASCII_RS] = "record separator",
+  [ASCII_US] = "unit separator",
+  [ASCII_DEL] = "delete",
+};
+
+static const char *
+getPhoneticWord (wchar_t character) {
+  if (character >= ARRAY_COUNT(phoneticWords)) return NULL;
+  return phoneticWords[character];
+}
+
+STR_BEGIN_FORMATTER(formatPhoneticPhrase, int column, int row)
+  wchar_t character = getScreenCharacter(column, row).text;
+
+  wchar_t characters[0X10];
+  size_t characterCount = decomposeCharacter(character, characters, ARRAY_COUNT(characters));
+
+  if (!characterCount) {
+    characters[0] = character;
+    characterCount = 1;
+  }
+
+  for (unsigned int characterIndex=0; characterIndex<characterCount; characterIndex+=1) {
+    if (characterIndex > 0) {
+      STR_PRINTF("%s ", ((characterIndex == 1)? " with": ","));
+    }
+
+    character = characters[characterIndex];
+    const char *word = getPhoneticWord(character);
+    char nameBuffer[0X40];
+
+    if (!word) {
+      if (iswupper(character)) {
+        wchar_t lowercase = towlower(character);
+        word = getPhoneticWord(lowercase);
+
+        if (word) {
+          STR_PRINTF("cap ");
+          character = lowercase;
+        }
+      }
+    }
+
+    if (!word) {
+      if (getCharacterName(character, nameBuffer, sizeof(nameBuffer))) {
+        word = nameBuffer;
+
+        {
+          char *byte = nameBuffer;
+
+          while (*byte) {
+            *byte = tolower((unsigned char)*byte);
+            byte += 1;
+          }
+        }
+
+        {
+          const char *space = strchr(word, ' ');
+
+          if (space) {
+            size_t length = space - word + 1;
+            if (memcmp(word,  "combining ", length) == 0) word += length;
+          }
+        }
+      }
+    }
+
+    if (word) {
+      if (STR_LENGTH > 0) STR_PRINTF(" ");
+      STR_PRINTF("%s", word);
+    }
+  }
+STR_END_FORMATTER
diff --git a/Programs/cmd_utils.h b/Programs/cmd_utils.h
new file mode 100644
index 0000000..d7beaa9
--- /dev/null
+++ b/Programs/cmd_utils.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CMD_UTILS
+#define BRLTTY_INCLUDED_CMD_UTILS
+
+#include "strfmth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void alertLineSkipped (unsigned int *count);
+
+extern int isTextOffset (int arg, int *first, int *last, int relaxed);
+
+extern int getCharacterCoordinates (int arg, int *row, int *first, int *last, int relaxed);
+
+extern STR_DECLARE_FORMATTER(formatCharacterDescription, int column, int row);
+extern STR_DECLARE_FORMATTER(formatPhoneticPhrase, int column, int row);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CMD_UTILS */
diff --git a/Programs/cmdline.c b/Programs/cmdline.c
new file mode 100644
index 0000000..11833a1
--- /dev/null
+++ b/Programs/cmdline.c
@@ -0,0 +1,1268 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "params.h"
+#include "log.h"
+#include "strfmt.h"
+#include "file.h"
+#include "datafile.h"
+#include "utf8.h"
+#include "parse.h"
+
+#undef ALLOW_DOS_OPTION_SYNTAX
+#if defined(__MINGW32__) || defined(__MSDOS__)
+#define ALLOW_DOS_OPTION_SYNTAX
+#endif /* allow DOS syntax */
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif /* HAVE_GETOPT_H */
+
+typedef struct {
+  const CommandLineOptions *const options;
+  uint8_t *const ensuredSettings;
+
+  uint8_t exitImmediately:1;
+  uint8_t warning:1;
+  uint8_t syntaxError:1;
+} OptionProcessingInformation;
+
+static int
+hasExtendableArgument (const CommandLineOption *option) {
+  return option->argument && (option->flags & OPT_Extend);
+}
+
+static uint8_t *
+getEnsuredSetting (
+  const OptionProcessingInformation *info,
+  const CommandLineOption *option
+) {
+  return &info->ensuredSettings[option - info->options->table];
+}
+
+static void
+setEnsuredSetting (
+  const OptionProcessingInformation *info,
+  const CommandLineOption *option,
+  uint8_t yes
+) {
+  *getEnsuredSetting(info, option) = yes;
+}
+
+static int
+ensureSetting (
+  OptionProcessingInformation *info,
+  const CommandLineOption *option,
+  const char *value
+) {
+  uint8_t *ensured = getEnsuredSetting(info, option);
+
+  if (!*ensured || hasExtendableArgument(option)) {
+    *ensured = 1;
+
+    if (option->argument) {
+      if (option->setting.string) {
+        if (option->flags & OPT_Extend) {
+          if (!extendStringSetting(option->setting.string, value, 1)) return 0;
+        } else if (!changeStringSetting(option->setting.string, value)) {
+          return 0;
+        }
+      }
+    } else {
+      if (option->setting.flag) {
+        if (option->flags & OPT_Extend) {
+          int count;
+
+          if (isInteger(&count, value) && (count >= 0)) {
+            *option->setting.flag = count;
+          } else {
+            logMessage(LOG_ERR, "%s: %s", gettext("invalid counter setting"), value);
+            info->warning = 1;
+          }
+        } else {
+          unsigned int on;
+
+          if (validateFlagKeyword(&on, value)) {
+            *option->setting.flag = on;
+          } else {
+            logMessage(LOG_ERR, "%s: %s", gettext("invalid flag setting"), value);
+            info->warning = 1;
+          }
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static void
+showWrappedText (
+  FILE *stream, const char *text, char *line,
+  unsigned int offset, unsigned int width
+) {
+  unsigned int limit = width - offset;
+  unsigned int charsLeft = strlen(text);
+
+  while (1) {
+    unsigned int charCount = charsLeft;
+
+    if (charCount > limit) {
+      charCount = limit;
+
+      while (charCount > 0) {
+        if (isspace(text[charCount])) break;
+        charCount -= 1;
+      }
+
+      while (charCount > 0) {
+        if (!isspace(text[--charCount])) {
+          charCount += 1;
+          break;
+        }
+      }
+    }
+
+    if (charCount > 0) {
+      memcpy(line+offset, text, charCount);
+      unsigned int length = offset + charCount;
+
+      writeWithConsoleEncoding(stream, line, length);
+      fputc('\n', stream);
+    }
+
+    while (charCount < charsLeft) {
+      if (!isspace(text[charCount])) break;
+      charCount += 1;
+    }
+
+    if (!(charsLeft -= charCount)) break;
+    text += charCount;
+    memset(line, ' ', offset);
+  }
+}
+
+static void
+showFormattedLines (
+  FILE *stream, const char *const *const *blocks,
+  char *line, int width
+) {
+  const char *const *const *block = blocks;
+
+  char *paragraphText = NULL;
+  size_t paragraphSize = 0;
+  size_t paragraphLength = 0;
+
+  while (*block) {
+    const char *const *chunk = *block++;
+    if (!*chunk) continue;
+
+    while (1) {
+      const char *text = *chunk;
+      if (!text) break;
+      text = gettext(text);
+
+      if (*text && !iswspace(*text)) {
+        size_t textLength = strlen(text);
+
+        size_t newLength = paragraphLength + textLength + 1;
+        int extending = !!paragraphLength;
+        if (extending) newLength += 1;
+
+        if (newLength > paragraphSize) {
+          size_t newSize = (newLength | 0XFF) + 1;
+          char *newText = realloc(paragraphText, newSize);
+
+          if (!newText) {
+            logMallocError();
+            goto done;
+          }
+
+          paragraphText = newText;
+          paragraphSize = newSize;
+        }
+
+        if (extending) paragraphText[paragraphLength++] = ' ';
+        memcpy(&paragraphText[paragraphLength], text, textLength);
+        paragraphText[paragraphLength += textLength] = 0;
+      } else {
+        if (paragraphLength) {
+          showWrappedText(stream, paragraphText, line, 0, width);
+          paragraphLength = 0;
+        }
+
+        fprintf(stream, "%s\n", text);
+      }
+
+      chunk += 1;
+    }
+
+    if (paragraphLength) {
+      showWrappedText(stream, paragraphText, line, 0, width);
+      paragraphLength = 0;
+    }
+
+    if (*block) fputc('\n', stream);
+  }
+
+done:
+  if (paragraphText) free(paragraphText);
+}
+
+static void
+showSyntax (
+  FILE *stream,
+  int haveOptions,
+  const char *parameters
+) {
+  fprintf(stream, "%s: %s", gettext("Syntax"), programName);
+
+  if (haveOptions) {
+    fprintf(stream, " [-%s ...]", gettext("option"));
+  }
+
+  if (parameters && *parameters) {
+    fprintf(stream, " %s", parameters);
+  }
+
+  fprintf(stream, "\n");
+}
+
+static void
+showOptions (
+  FILE *stream, char *line, unsigned int lineWidth,
+  OptionProcessingInformation *info
+) {
+  size_t optionCount = info->options->count;
+
+  if (info->options->count > 0) {
+    unsigned int letterWidth = 0;
+    unsigned int wordWidth = 0;
+    unsigned int argumentWidth = 0;
+
+    for (unsigned int optionIndex=0; optionIndex<optionCount; optionIndex+=1) {
+      const CommandLineOption *option = &info->options->table[optionIndex];
+
+      if (option->word) {
+        unsigned int length = strlen(option->word);
+        if (option->argument) length += 1;
+        wordWidth = MAX(wordWidth, length);
+      }
+
+      if (option->letter) letterWidth = 2;
+      if (option->argument) argumentWidth = MAX(argumentWidth, strlen(gettext(option->argument)));
+    }
+
+    fprintf(stream, "\n%s:\n", gettext("Options"));
+
+    for (unsigned int optionIndex=0; optionIndex<optionCount; optionIndex+=1) {
+      const CommandLineOption *option = &info->options->table[optionIndex];
+
+      unsigned int lineLength = 0;
+      while (lineLength < 2) line[lineLength++] = ' ';
+
+      {
+        unsigned int end = lineLength + letterWidth;
+
+        if (option->letter) {
+          line[lineLength++] = '-';
+          line[lineLength++] = option->letter;
+        }
+
+        while (lineLength < letterWidth) {
+          line[lineLength++] = ' ';
+        }
+
+        while (lineLength < end) line[lineLength++] = ' ';
+      }
+      line[lineLength++] = ' ';
+
+      {
+        unsigned int end = lineLength + 2 + wordWidth;
+
+        if (option->word) {
+          size_t wordLength = strlen(option->word);
+
+          line[lineLength++] = '-';
+          line[lineLength++] = '-';
+          memcpy(line+lineLength, option->word, wordLength);
+          lineLength += wordLength;
+          if (option->argument) line[lineLength++] = '=';
+        }
+
+        while (lineLength < end) line[lineLength++] = ' ';
+      }
+      line[lineLength++] = ' ';
+
+      {
+        unsigned int end = lineLength + argumentWidth;
+
+        if (option->argument) {
+          const char *argument = gettext(option->argument);
+          size_t argumentLength = strlen(argument);
+
+          memcpy(line+lineLength, argument, argumentLength);
+          lineLength += argumentLength;
+        }
+
+        while (lineLength < end) line[lineLength++] = ' ';
+      }
+      line[lineLength++] = ' ';
+
+      line[lineLength++] = ' ';
+      {
+        const int formatStrings = !!(option->flags & OPT_Format);
+        const char *description = option->description? gettext(option->description): "";
+
+        char buffer[0X400];
+        char *from = buffer;
+        const char *const to = from + sizeof(buffer);
+
+        if (formatStrings? !!option->strings.format: !!option->strings.array) {
+          unsigned int index = 0;
+          const unsigned int limit = 4;
+          const char *strings[limit];
+
+          while (index < limit) {
+            const char *string;
+
+            if (formatStrings) {
+              size_t length = option->strings.format(from, (to - from), index);
+
+              if (length) {
+                string = from;
+                from += length + 1;
+              } else {
+                string = NULL;
+              }
+            } else {
+              string = option->strings.array[index];
+            }
+
+            if (!string) break;
+            strings[index++] = string;
+          }
+
+          while (index < limit) strings[index++] = "";
+          snprintf(from, (to - from),
+            description, strings[0], strings[1], strings[2], strings[3]
+          );
+          description = from;
+        }
+
+        showWrappedText(stream, description, line, lineLength, lineWidth);
+      }
+    }
+  }
+}
+
+static void
+processCommandLine (
+  OptionProcessingInformation *info,
+  int *argumentCount,
+  char ***argumentVector,
+  const CommandLineUsage *usage
+) {
+  const char *reset = NULL;
+  const char resetPrefix = '+';
+  int resetLetter;
+
+  int dosSyntax = 0;
+  const int firstNonLetter = 0X80;
+  const CommandLineOption *letterToOption[firstNonLetter + info->options->count];
+
+  for (unsigned int index=0; index<ARRAY_COUNT(letterToOption); index+=1) {
+    letterToOption[index] = NULL;
+  }
+
+  int indexToLetter[info->options->count];
+  char shortOptions[2 + (info->options->count * 2) + 1];
+
+  {
+    int nextNonLetter = firstNonLetter;
+
+    char *opt = shortOptions;
+    *opt++ = '+'; // stop parsing options as soon as a non-option argument is encountered
+    *opt++ = ':'; // Don't write any error messages
+
+    for (unsigned int index=0; index<info->options->count; index+=1) {
+      const CommandLineOption *option = &info->options->table[index];
+      int letter = option->letter;
+
+      if (letter) {
+        if (letterToOption[letter]) {
+          logMessage(LOG_WARNING, "duplicate short option: -%c", letter);
+          letter = 0;
+        } else {
+          *opt++ = letter;
+          if (option->argument) *opt++ = ':';
+        }
+      }
+
+      if (!letter) letter = nextNonLetter++;
+      indexToLetter[index] = letter;
+      letterToOption[letter] = option;
+
+      if (option->argument) {
+        if (option->setting.string) *option->setting.string = NULL;
+      } else {
+        if (option->setting.flag) *option->setting.flag = 0;
+      }
+    }
+
+    *opt = 0;
+  }
+
+#ifdef HAVE_GETOPT_LONG
+  struct option longOptions[(info->options->count * 2) + 1];
+
+  {
+    struct option *opt = longOptions;
+
+    for (unsigned int index=0; index<info->options->count; index+=1) {
+      const CommandLineOption *option = &info->options->table[index];
+      const char *word = option->word;
+      if (!word) continue;
+      int letter = indexToLetter[index];
+
+      opt->name = word;
+      opt->has_arg = option->argument? required_argument: no_argument;
+      opt->flag = NULL;
+      opt->val = letter;
+      opt += 1;
+
+      if (!option->argument && option->setting.flag) {
+        char *name;
+
+        const char *noPrefix = "no-";
+        size_t noLength = strlen(noPrefix);
+
+        if (strncasecmp(noPrefix, word, noLength) == 0) {
+          name = strdup(&word[noLength]);
+        } else {
+          size_t size = noLength + strlen(word) + 1;
+
+          if ((name = malloc(size))) {
+            snprintf(name, size, "%s%s", noPrefix, word);
+          }
+        }
+
+        if (name) {
+          opt->name = name;
+          opt->has_arg = no_argument;
+          opt->flag = &resetLetter;
+          opt->val = letter;
+          opt += 1;
+        } else {
+          logMallocError();
+        }
+      }
+    }
+
+    memset(opt, 0, sizeof(*opt));
+  }
+#endif /* HAVE_GETOPT_LONG */
+
+#ifdef ALLOW_DOS_OPTION_SYNTAX
+  const char dosPrefix = '/';
+
+  if (*argumentCount > 1) {
+    if (*(*argumentVector)[1] == dosPrefix) {
+      dosSyntax = 1;
+    }
+  }
+#endif /* ALLOW_DOS_OPTION_SYNTAX */
+
+  opterr = 0;
+  optind = 1;
+  int lastOptInd = -1;
+  int optHelp = 0;
+
+  while (1) {
+    int letter;
+    char prefix = '-';
+    resetLetter = 0;
+
+    if (optind == *argumentCount) {
+      letter = -1;
+    } else {
+      char *argument = (*argumentVector)[optind];
+
+#ifdef ALLOW_DOS_OPTION_SYNTAX
+      if (dosSyntax) {
+        prefix = dosPrefix;
+        optind += 1;
+
+        if (*argument != dosPrefix) {
+          letter = -1;
+        } else {
+          char *name = argument + 1;
+          size_t nameLength = strcspn(name, ":");
+          char *value = name[nameLength]? (name + nameLength + 1): NULL;
+          const CommandLineOption *option;
+
+          if (nameLength == 1) {
+            option = letterToOption[letter = *name];
+          } else {
+            letter = -1;
+
+            for (unsigned int index=0; index<info->options->count; index+=1) {
+              option = &info->options->table[index];
+              const char *word = option->word;
+
+              if (word) {
+                if ((nameLength == strlen(word)) &&
+                    (strncasecmp(word, name, nameLength) == 0)) {
+                  letter = indexToLetter[index];
+                  break;
+                }
+              }
+            }
+
+            if (letter < 0) {
+              option = NULL;
+              letter = 0;
+            }
+          }
+
+          optopt = letter;
+          optarg = value;
+
+          if (!option) {
+            letter = '?';
+          } else if (option->argument) {
+            if (!optarg) letter = ':';
+          } else if (value) {
+            unsigned int on;
+
+            if (!validateFlagKeyword(&on, value)) {
+              letter = '-';
+            } else if (!on) {
+              resetLetter = letter;
+              letter = 0;
+            }
+          }
+        }
+      } else
+#endif /* ALLOW_DOS_OPTION_SYNTAX */
+
+      if (reset) {
+        prefix = resetPrefix;
+
+        if (!(letter = *reset++)) {
+          reset = NULL;
+          optind += 1;
+          continue;
+        }
+
+        {
+          const CommandLineOption *option = letterToOption[letter];
+
+          if (option && !option->argument && option->setting.flag) {
+            resetLetter = letter;
+            letter = 0;
+          } else {
+            optopt = letter;
+            letter = '?';
+          }
+        }
+      } else {
+        if (optind != lastOptInd) {
+          lastOptInd = optind;
+          if ((reset = (*argument == resetPrefix)? argument+1: NULL)) continue;
+        }
+
+#ifdef HAVE_GETOPT_LONG
+        letter = getopt_long(*argumentCount, *argumentVector, shortOptions, longOptions, NULL);
+#else /* HAVE_GETOPT_LONG */
+        letter = getopt(*argumentCount, *argumentVector, shortOptions);
+#endif /* HAVE_GETOPT_LONG */
+      }
+    }
+
+    if (letter == -1) break;
+    /* continue on error as much as possible, as often we are typing blind
+     * and won't even see the error message unless the display comes up.
+     */
+
+    switch (letter) {
+      default: {
+        const CommandLineOption *option = letterToOption[letter];
+
+        if (option->argument) {
+          if (!*optarg) {
+            setEnsuredSetting(info, option, 0);
+            break;
+          }
+
+          if (option->setting.string) {
+            if (option->flags & OPT_Extend) {
+              extendStringSetting(option->setting.string, optarg, 0);
+            } else {
+              changeStringSetting(option->setting.string, optarg);
+            }
+          }
+        } else {
+          if (option->setting.flag) {
+            if (option->flags & OPT_Extend) {
+              *option->setting.flag += 1;
+            } else {
+              *option->setting.flag = 1;
+            }
+          }
+        }
+
+        setEnsuredSetting(info, option, 1);
+        break;
+      }
+
+      case 0: { // reset a flag
+        const CommandLineOption *option = letterToOption[resetLetter];
+        *option->setting.flag = 0;
+        setEnsuredSetting(info, option, 1);
+        break;
+      }
+
+    {
+      const char *problem;
+      char message[0X100];
+
+      case '?': // an unknown option has been specified
+        info->syntaxError = 1;
+        problem = gettext("unknown option");
+        goto logOptionProblem;
+
+      case ':': // the operand for a string option hasn't been specified
+        info->syntaxError = 1;
+        problem = gettext("missing operand");
+        goto logOptionProblem;
+
+      case '-': // the operand for an option is invalid
+        info->warning = 1;
+        problem = gettext("invalid operand");
+        goto logOptionProblem;
+
+      logOptionProblem:
+        STR_BEGIN(message, sizeof(message));
+
+        STR_PRINTF("%s: ", problem);
+        size_t optionStart = STR_LENGTH;
+
+        if (optopt) {
+          const CommandLineOption *option = letterToOption[optopt];
+
+          if (option) {
+            const char *beforeLetter = "";
+            const char *afterLetter = "";
+
+            if (option->word) {
+              if (!dosSyntax) STR_PRINTF("%c", prefix);
+              STR_PRINTF("%c%s", prefix, option->word);
+
+              beforeLetter = " (";
+              afterLetter = ")";
+            }
+
+            if (option->letter) {
+              STR_PRINTF("%s%c%c%s", beforeLetter, prefix, option->letter, afterLetter);
+            }
+          } else if (optopt < firstNonLetter) {
+            STR_PRINTF("%c%c", prefix, optopt);
+          }
+        }
+
+        if (STR_LENGTH == optionStart) {
+          STR_PRINTF("%s", (*argumentVector)[optind-1]);
+        }
+
+        STR_END;
+        logMessage(LOG_WARNING, "%s", message);
+        break;
+    }
+
+      case 'h': // help - show usage summary and then exit
+        optHelp = 1;
+        break;
+    }
+  }
+
+  *argumentVector += optind;
+  *argumentCount -= optind;
+
+  if (optHelp) {
+    FILE *usageStream = stdout;
+
+    size_t width = UINT16_MAX;
+    getConsoleSize(&width, NULL);
+    char line[width+1];
+
+    {
+      const char *purpose = gettext(usage->purpose);
+
+      if (purpose && *purpose) {
+        showWrappedText(usageStream, purpose, line, 0, width);
+        fputc('\n', usageStream);
+      }
+    }
+
+    showSyntax(usageStream, !!info->options->count, usage->parameters);
+    showOptions(usageStream, line, width, info);
+
+    {
+      const char *const *const *notes = usage->notes;
+
+      if (notes && *notes) {
+        fputc('\n', usageStream);
+        showFormattedLines(usageStream, notes, line, width);
+      }
+    }
+
+    info->exitImmediately = 1;
+  }
+
+#ifdef HAVE_GETOPT_LONG
+  {
+    struct option *opt = longOptions;
+
+    while (opt->name) {
+      if (opt->flag) free((char *)opt->name);
+      opt += 1;
+    }
+  }
+#endif /* HAVE_GETOPT_LONG */
+}
+
+static void
+processBootParameters (
+  OptionProcessingInformation *info,
+  const char *parameter
+) {
+  const char *value;
+  char *allocated = NULL;
+
+  if (!(value = allocated = getBootParameters(parameter))) {
+    if (!(value = getenv(parameter))) {
+      return;
+    }
+  }
+
+  {
+    int parameterCount = 0;
+    char **parameters = splitString(value, ',', &parameterCount);
+
+    for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
+      const CommandLineOption *option = &info->options->table[optionIndex];
+
+      if ((option->bootParameter) && (option->bootParameter <= parameterCount)) {
+        char *parameter = parameters[option->bootParameter-1];
+
+        if (*parameter) {
+          {
+            char *byte = parameter;
+
+            do {
+              if (*byte == '+') *byte = ',';
+            } while (*++byte);
+          }
+
+          ensureSetting(info, option, parameter);
+        }
+      }
+    }
+
+    deallocateStrings(parameters);
+  }
+
+  if (allocated) free(allocated);
+}
+
+static int
+processEnvironmentVariable (
+  OptionProcessingInformation *info,
+  const CommandLineOption *option,
+  const char *prefix
+) {
+  size_t prefixLength = strlen(prefix);
+
+  if ((option->flags & OPT_EnvVar) && option->word) {
+    size_t nameSize = prefixLength + 1 + strlen(option->word) + 1;
+    char name[nameSize];
+
+    snprintf(name, nameSize, "%s_%s", prefix, option->word);
+
+    {
+      char *character = name;
+
+      while (*character) {
+        if (*character == '-') {
+          *character = '_';
+        } else if (islower((unsigned char)*character)) {
+          *character = toupper((unsigned char)*character);
+        }
+
+        character += 1;
+      }
+    }
+
+    {
+      const char *setting = getenv(name);
+
+      if (setting && *setting) {
+        if (!ensureSetting(info, option, setting)) {
+          return 0;
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+processEnvironmentVariables (
+  OptionProcessingInformation *info,
+  const char *prefix
+) {
+  for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
+    const CommandLineOption *option = &info->options->table[optionIndex];
+
+    if (!processEnvironmentVariable(info, option, prefix)) return 0;
+  }
+
+  return 1;
+}
+
+static void
+processInternalSettings (
+  OptionProcessingInformation *info,
+  int config
+) {
+  for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
+    const CommandLineOption *option = &info->options->table[optionIndex];
+
+    if (!(option->flags & OPT_Config) == !config) {
+      const char *setting = option->internal.setting;
+      char *newSetting = NULL;
+
+      if (!setting) setting = option->argument? "": OPT_WORD_FALSE;
+
+      if (option->internal.adjust) {
+        if (*setting) {
+          if ((newSetting = strdup(setting))) {
+            if (option->internal.adjust(&newSetting)) {
+              setting = newSetting;
+            }
+          } else {
+            logMallocError();
+          }
+        }
+      }
+
+      ensureSetting(info, option, setting);
+      if (newSetting) free(newSetting);
+    }
+  }
+}
+
+typedef struct {
+  unsigned int option;
+  wchar_t keyword[0];
+} ConfigurationDirective;
+
+static int
+sortConfigurationDirectives (const void *element1, const void *element2) {
+  const ConfigurationDirective *const *directive1 = element1;
+  const ConfigurationDirective *const *directive2 = element2;
+
+  return compareKeywords((*directive1)->keyword, (*directive2)->keyword);
+}
+
+static int
+searchConfigurationDirective (const void *target, const void *element) {
+  const wchar_t *keyword = target;
+  const ConfigurationDirective *const *directive = element;
+
+  return compareKeywords(keyword, (*directive)->keyword);
+}
+
+typedef struct {
+  OptionProcessingInformation *info;
+  char **settings;
+
+  struct {
+    ConfigurationDirective **table;
+    unsigned int count;
+  } directive;
+} ConfigurationFileProcessingData;
+
+static const ConfigurationDirective *
+findConfigurationDirective (const wchar_t *keyword, const ConfigurationFileProcessingData *conf) {
+  const ConfigurationDirective *const *directive = bsearch(keyword, conf->directive.table, conf->directive.count, sizeof(*conf->directive.table), searchConfigurationDirective);
+
+  if (directive) return *directive;
+  return NULL;
+}
+
+static int
+processConfigurationDirective (
+  const wchar_t *keyword,
+  const char *value,
+  const ConfigurationFileProcessingData *conf
+) {
+  const ConfigurationDirective *directive = findConfigurationDirective(keyword, conf);
+
+  if (directive) {
+    const CommandLineOption *option = &conf->info->options->table[directive->option];
+    char **setting = &conf->settings[directive->option];
+
+    if (*setting && !hasExtendableArgument(option)) {
+      logMessage(LOG_ERR, "%s: %" PRIws, gettext("configuration directive specified more than once"), keyword);
+      conf->info->warning = 1;
+
+      free(*setting);
+      *setting = NULL;
+    }
+
+    if (*setting) {
+      if (!extendStringSetting(setting, value, 0)) return 0;
+    } else {
+      if (!(*setting = strdup(value))) {
+        logMallocError();
+        return 0;
+      }
+    }
+  } else {
+    logMessage(LOG_ERR, "%s: %" PRIws, gettext("unknown configuration directive"), keyword);
+    conf->info->warning = 1;
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processConfigurationOperands) {
+  const ConfigurationFileProcessingData *conf = data;
+  int ok = 1;
+  DataString keyword;
+
+  if (getDataString(file, &keyword, 0, "configuration directive")) {
+    DataString value;
+
+    if (getDataString(file, &value, 0, "configuration value")) {
+      char *v = getUtf8FromWchars(value.characters, value.length, NULL);
+
+      if (v) {
+        if (!processConfigurationDirective(keyword.characters, v, conf)) ok = 0;
+
+        free(v);
+      } else {
+        ok = 0;
+      }
+    } else {
+      conf->info->warning = 1;
+    }
+  } else {
+    conf->info->warning = 1;
+  }
+
+  return ok;
+}
+
+static DATA_CONDITION_TESTER(testConfigurationDirectiveSet) {
+  const ConfigurationFileProcessingData *conf = data;
+  wchar_t keyword[identifier->length + 1];
+
+  wmemcpy(keyword, identifier->characters, identifier->length);
+  keyword[identifier->length] = 0;
+
+  {
+    const ConfigurationDirective *directive = findConfigurationDirective(keyword, conf);
+
+    if (directive) {
+      if (conf->settings[directive->option]) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+processConfigurationDirectiveTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testConfigurationDirectiveSet, not, "configuration directive", data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfSetOperands) {
+  return processConfigurationDirectiveTestOperands(file,00, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfNotSetOperands) {
+  return processConfigurationDirectiveTestOperands(file, 1, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processConfigurationLine) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    DATA_VARIABLE_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    {.name=WS_C("ifset"), .processor=processIfSetOperands, .unconditional=1},
+    {.name=WS_C("ifnotset"), .processor=processIfNotSetOperands, .unconditional=1},
+    {.name=NULL, .processor=processConfigurationOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "configuration file directive", data);
+}
+
+static void
+freeConfigurationDirectives (ConfigurationFileProcessingData *conf) {
+  while (conf->directive.count > 0) free(conf->directive.table[--conf->directive.count]);
+}
+
+static int
+addConfigurationDirectives (ConfigurationFileProcessingData *conf) {
+  for (unsigned int optionIndex=0; optionIndex<conf->info->options->count; optionIndex+=1) {
+    const CommandLineOption *option = &conf->info->options->table[optionIndex];
+
+    if ((option->flags & OPT_Config) && option->word) {
+      ConfigurationDirective *directive;
+      const char *keyword = option->word;
+      size_t length = countUtf8Characters(keyword);
+      size_t size = sizeof(*directive) + ((length + 1) * sizeof(wchar_t));
+
+      if (!(directive = malloc(size))) {
+        logMallocError();
+        freeConfigurationDirectives(conf);
+        return 0;
+      }
+
+      directive->option = optionIndex;
+
+      {
+        const char *utf8 = keyword;
+        wchar_t *wc = directive->keyword;
+        convertUtf8ToWchars(&utf8, &wc, length+1);
+      }
+
+      conf->directive.table[conf->directive.count++] = directive;
+    }
+  }
+
+  qsort(conf->directive.table, conf->directive.count,
+        sizeof(*conf->directive.table), sortConfigurationDirectives);
+
+  return 1;
+}
+
+static void
+processConfigurationFile (
+  OptionProcessingInformation *info,
+  const char *path,
+  int optional
+) {
+  if (setBaseDataVariables(NULL)) {
+    FILE *file = openDataFile(path, "r", optional);
+
+    if (file) {
+      char *settings[info->options->count];
+      ConfigurationDirective *directives[info->options->count];
+
+      ConfigurationFileProcessingData conf = {
+        .info = info,
+        .settings = settings,
+
+        .directive = {
+          .table = directives,
+          .count = 0
+        }
+      };
+
+      if (addConfigurationDirectives(&conf)) {
+        int processed;
+
+        for (unsigned int index=0; index<info->options->count; index+=1) {
+          conf.settings[index] = NULL;
+        }
+
+        {
+          const DataFileParameters dataFileParameters = {
+            .processOperands = processConfigurationLine,
+            .data = &conf
+          };
+
+          processed = processDataStream(NULL, file, path, &dataFileParameters);
+        }
+
+        for (unsigned int index=0; index<info->options->count; index+=1) {
+          char *setting = conf.settings[index];
+
+          if (setting) {
+            ensureSetting(info, &info->options->table[index], setting);
+            free(setting);
+          }
+        }
+
+        if (!processed) {
+          logMessage(LOG_ERR, gettext("file '%s' processing error."), path);
+          info->warning = 1;
+        }
+
+        freeConfigurationDirectives(&conf);
+      }
+
+      fclose(file);
+    } else if (!optional || (errno != ENOENT)) {
+      info->warning = 1;
+    }
+  }
+}
+
+void
+resetOptions (const CommandLineOptions *options) {
+  for (unsigned int index=0; index<options->count; index+=1) {
+    const CommandLineOption *option = &options->table[index];
+
+    if (option->argument) {
+      char **string = option->setting.string;
+      if (string) changeStringSetting(string, NULL);
+    } else {
+      int *flag = option->setting.flag;
+      if (flag) *flag = 0;
+    }
+  }
+}
+
+static void
+exitOptions (void *data) {
+  const CommandLineOptions *options = data;
+  resetOptions(options);
+}
+
+ProgramExitStatus
+processOptions (const CommandLineDescriptor *descriptor, int *argumentCount, char ***argumentVector) {
+  uint8_t ensuredSettings[descriptor->options->count];
+  memset(ensuredSettings, 0, sizeof(ensuredSettings));
+
+  OptionProcessingInformation info = {
+    .options = descriptor->options,
+    .ensuredSettings = ensuredSettings,
+
+    .exitImmediately = 0,
+    .warning = 0,
+    .syntaxError = 0
+  };
+
+  onProgramExit("options", exitOptions, (void *)descriptor->options);
+  beginProgram(*argumentCount, *argumentVector);
+  processCommandLine(&info, argumentCount, argumentVector, &descriptor->usage);
+
+  if (descriptor->doBootParameters && *descriptor->doBootParameters) {
+    processBootParameters(&info, descriptor->applicationName);
+  }
+
+  if (descriptor->doEnvironmentVariables && *descriptor->doEnvironmentVariables) {
+    processEnvironmentVariables(&info, descriptor->applicationName);
+  }
+
+  processInternalSettings(&info, 0);
+  {
+    int configurationFileSpecified = descriptor->configurationFile && *descriptor->configurationFile;
+
+    if (configurationFileSpecified) {
+      processConfigurationFile(&info, *descriptor->configurationFile, !configurationFileSpecified);
+    }
+  }
+  processInternalSettings(&info, 1);
+
+  if (info.exitImmediately) return PROG_EXIT_FORCE;
+  if (info.syntaxError) return PROG_EXIT_SYNTAX;
+  return PROG_EXIT_SUCCESS;
+}
+
+static ProgramExitStatus
+processInputStream (
+  FILE *stream, const char *name,
+  const InputFilesProcessingParameters *parameters
+) {
+  int ok = 0;
+
+  if (parameters->beginStream) {
+    parameters->beginStream(name, parameters->dataFileParameters.data);
+  }
+
+  if (setBaseDataVariables(NULL)) {
+    if (processDataStream(NULL, stream, name, &parameters->dataFileParameters)) {
+      ok = 1;
+    }
+  }
+
+  if (parameters->endStream) {
+    parameters->endStream(!ok, parameters->dataFileParameters.data);
+  }
+
+  return ok? PROG_EXIT_SUCCESS: PROG_EXIT_FATAL;
+}
+
+static ProgramExitStatus
+processStandardInput (const InputFilesProcessingParameters *parameters) {
+  return processInputStream(stdin, standardInputName, parameters);
+}
+
+static ProgramExitStatus
+processInputFile (const char *path, const InputFilesProcessingParameters *parameters) {
+  if (strcmp(path, standardStreamArgument) == 0) {
+    return processStandardInput(parameters);
+  }
+
+  {
+    FILE *stream = fopen(path, "r");
+
+    if (!stream) {
+      logMessage(LOG_ERR, "input file open error: %s: %s", path, strerror(errno));
+      return PROG_EXIT_FATAL;
+    }
+
+    ProgramExitStatus status = processInputStream(stream, path, parameters);
+    fclose(stream);
+    return status;
+  }
+}
+
+ProgramExitStatus
+processInputFiles (
+  char **paths, int count,
+  const InputFilesProcessingParameters *parameters
+) {
+  if (!count) return processStandardInput(parameters);
+
+  do {
+    ProgramExitStatus status = processInputFile(*paths++, parameters);
+    if (status != PROG_EXIT_SUCCESS) return status;
+  } while (count -= 1);
+
+  return PROG_EXIT_SUCCESS;
+}
diff --git a/Programs/cmds.auto.h b/Programs/cmds.auto.h
new file mode 100644
index 0000000..13d3bf7
--- /dev/null
+++ b/Programs/cmds.auto.h
@@ -0,0 +1,1894 @@
+{
+    // BRL_CMD_NOOP
+    .name = "NOOP",
+    .code = BRL_CMD_NOOP,
+    // xgettext: This is the description of the NOOP command.
+    .description = strtext("do nothing"),
+},
+
+    {
+        // BRL_CMD_LNUP
+        .name = "LNUP",
+        .code = BRL_CMD_LNUP,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the LNUP command.
+        .description = strtext("go up one line"),
+    },
+
+    {
+        // BRL_CMD_LNDN
+        .name = "LNDN",
+        .code = BRL_CMD_LNDN,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the LNDN command.
+        .description = strtext("go down one line"),
+    },
+
+    {
+        // BRL_CMD_WINUP
+        .name = "WINUP",
+        .code = BRL_CMD_WINUP,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the WINUP command.
+        .description = strtext("go up several lines"),
+    },
+
+    {
+        // BRL_CMD_WINDN
+        .name = "WINDN",
+        .code = BRL_CMD_WINDN,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the WINDN command.
+        .description = strtext("go down several lines"),
+    },
+
+    {
+        // BRL_CMD_PRDIFLN
+        .name = "PRDIFLN",
+        .code = BRL_CMD_PRDIFLN,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the PRDIFLN command.
+        .description = strtext("go up to nearest line with different content"),
+    },
+
+    {
+        // BRL_CMD_NXDIFLN
+        .name = "NXDIFLN",
+        .code = BRL_CMD_NXDIFLN,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the NXDIFLN command.
+        .description =
+            strtext("go down to nearest line with different content"),
+    },
+
+    {
+        // BRL_CMD_ATTRUP
+        .name = "ATTRUP",
+        .code = BRL_CMD_ATTRUP,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the ATTRUP command.
+        .description =
+            strtext("go up to nearest line with different highlighting"),
+    },
+
+    {
+        // BRL_CMD_ATTRDN
+        .name = "ATTRDN",
+        .code = BRL_CMD_ATTRDN,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the ATTRDN command.
+        .description =
+            strtext("go down to nearest line with different highlighting"),
+    },
+
+    {
+        // BRL_CMD_TOP
+        .name = "TOP",
+        .code = BRL_CMD_TOP,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the TOP command.
+        .description = strtext("go to top line"),
+    },
+
+    {
+        // BRL_CMD_BOT
+        .name = "BOT",
+        .code = BRL_CMD_BOT,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the BOT command.
+        .description = strtext("go to bottom line"),
+    },
+
+    {
+        // BRL_CMD_TOP_LEFT
+        .name = "TOP_LEFT",
+        .code = BRL_CMD_TOP_LEFT,
+        .isMotion = 1,
+        .isVertical = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the TOP_LEFT command.
+        .description = strtext("go to beginning of top line"),
+    },
+
+    {
+        // BRL_CMD_BOT_LEFT
+        .name = "BOT_LEFT",
+        .code = BRL_CMD_BOT_LEFT,
+        .isMotion = 1,
+        .isVertical = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the BOT_LEFT command.
+        .description = strtext("go to beginning of bottom line"),
+    },
+
+    {
+        // BRL_CMD_PRPGRPH
+        .name = "PRPGRPH",
+        .code = BRL_CMD_PRPGRPH,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the PRPGRPH command.
+        .description = strtext("go up to first line of paragraph"),
+    },
+
+    {
+        // BRL_CMD_NXPGRPH
+        .name = "NXPGRPH",
+        .code = BRL_CMD_NXPGRPH,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the NXPGRPH command.
+        .description = strtext("go down to first line of next paragraph"),
+    },
+
+    {
+        // BRL_CMD_PRPROMPT
+        .name = "PRPROMPT",
+        .code = BRL_CMD_PRPROMPT,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the PRPROMPT command.
+        .description = strtext("go up to previous command prompt"),
+    },
+
+    {
+        // BRL_CMD_NXPROMPT
+        .name = "NXPROMPT",
+        .code = BRL_CMD_NXPROMPT,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the NXPROMPT command.
+        .description = strtext("go down to next command prompt"),
+    },
+
+    {
+        // BRL_CMD_PRSEARCH
+        .name = "PRSEARCH",
+        .code = BRL_CMD_PRSEARCH,
+        // xgettext: This is the description of the PRSEARCH command.
+        .description = strtext("search backward for clipboard text"),
+    },
+
+    {
+        // BRL_CMD_NXSEARCH
+        .name = "NXSEARCH",
+        .code = BRL_CMD_NXSEARCH,
+        // xgettext: This is the description of the NXSEARCH command.
+        .description = strtext("search forward for clipboard text"),
+    },
+
+    {
+        // BRL_CMD_CHRLT
+        .name = "CHRLT",
+        .code = BRL_CMD_CHRLT,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the CHRLT command.
+        .description = strtext("go left one character"),
+    },
+
+    {
+        // BRL_CMD_CHRRT
+        .name = "CHRRT",
+        .code = BRL_CMD_CHRRT,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the CHRRT command.
+        .description = strtext("go right one character"),
+    },
+
+    {
+        // BRL_CMD_HWINLT
+        .name = "HWINLT",
+        .code = BRL_CMD_HWINLT,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the HWINLT command.
+        .description = strtext("go left half a braille window"),
+    },
+
+    {
+        // BRL_CMD_HWINRT
+        .name = "HWINRT",
+        .code = BRL_CMD_HWINRT,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the HWINRT command.
+        .description = strtext("go right half a braille window"),
+    },
+
+    {
+        // BRL_CMD_FWINLT
+        .name = "FWINLT",
+        .code = BRL_CMD_FWINLT,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the FWINLT command.
+        .description = strtext("go backward one braille window"),
+    },
+
+    {
+        // BRL_CMD_FWINRT
+        .name = "FWINRT",
+        .code = BRL_CMD_FWINRT,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the FWINRT command.
+        .description = strtext("go forward one braille window"),
+    },
+
+    {
+        // BRL_CMD_FWINLTSKIP
+        .name = "FWINLTSKIP",
+        .code = BRL_CMD_FWINLTSKIP,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the FWINLTSKIP command.
+        .description = strtext("go backward skipping blank braille windows"),
+    },
+
+    {
+        // BRL_CMD_FWINRTSKIP
+        .name = "FWINRTSKIP",
+        .code = BRL_CMD_FWINRTSKIP,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the FWINRTSKIP command.
+        .description = strtext("go forward skipping blank braille windows"),
+    },
+
+    {
+        // BRL_CMD_LNBEG
+        .name = "LNBEG",
+        .code = BRL_CMD_LNBEG,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the LNBEG command.
+        .description = strtext("go to beginning of line"),
+    },
+
+    {
+        // BRL_CMD_LNEND
+        .name = "LNEND",
+        .code = BRL_CMD_LNEND,
+        .isMotion = 1,
+        .isHorizontal = 1,
+        // xgettext: This is the description of the LNEND command.
+        .description = strtext("go to end of line"),
+    },
+
+    {
+        // BRL_CMD_HOME
+        .name = "HOME",
+        .code = BRL_CMD_HOME,
+        .isMotion = 1,
+        // xgettext: This is the description of the HOME command.
+        .description = strtext("go to screen cursor"),
+    },
+
+    {
+        // BRL_CMD_BACK
+        .name = "BACK",
+        .code = BRL_CMD_BACK,
+        .isMotion = 1,
+        // xgettext: This is the description of the BACK command.
+        .description = strtext("go back after cursor tracking"),
+    },
+
+    {
+        // BRL_CMD_RETURN
+        .name = "RETURN",
+        .code = BRL_CMD_RETURN,
+        .isMotion = 1,
+        // xgettext: This is the description of the RETURN command.
+        .description =
+            strtext("go to screen cursor or go back after cursor tracking"),
+    },
+
+    {
+        // BRL_CMD_FREEZE
+        .name = "FREEZE",
+        .code = BRL_CMD_FREEZE,
+        .isToggle = 1,
+        // xgettext: This is the description of the FREEZE command.
+        .description = strtext("set screen image frozen/unfrozen"),
+    },
+
+    {
+        // BRL_CMD_DISPMD
+        .name = "DISPMD",
+        .code = BRL_CMD_DISPMD,
+        .isToggle = 1,
+        // xgettext: This is the description of the DISPMD command.
+        .description = strtext("set display mode attributes/text"),
+    },
+
+    {
+        // BRL_CMD_SIXDOTS
+        .name = "SIXDOTS",
+        .code = BRL_CMD_SIXDOTS,
+        .isToggle = 1,
+        // xgettext: This is the description of the SIXDOTS command.
+        .description = strtext("set text style 6-dot/8-dot"),
+    },
+
+    {
+        // BRL_CMD_SLIDEWIN
+        .name = "SLIDEWIN",
+        .code = BRL_CMD_SLIDEWIN,
+        .isToggle = 1,
+        // xgettext: This is the description of the SLIDEWIN command.
+        .description = strtext("set sliding braille window on/off"),
+    },
+
+    {
+        // BRL_CMD_SKPIDLNS
+        .name = "SKPIDLNS",
+        .code = BRL_CMD_SKPIDLNS,
+        .isToggle = 1,
+        // xgettext: This is the description of the SKPIDLNS command.
+        .description =
+            strtext("set skipping of lines with identical content on/off"),
+    },
+
+    {
+        // BRL_CMD_SKPBLNKWINS
+        .name = "SKPBLNKWINS",
+        .code = BRL_CMD_SKPBLNKWINS,
+        .isToggle = 1,
+        // xgettext: This is the description of the SKPBLNKWINS command.
+        .description = strtext("set skipping of blank braille windows on/off"),
+    },
+
+    {
+        // BRL_CMD_CSRVIS
+        .name = "CSRVIS",
+        .code = BRL_CMD_CSRVIS,
+        .isToggle = 1,
+        // xgettext: This is the description of the CSRVIS command.
+        .description = strtext("set screen cursor visibility on/off"),
+    },
+
+    {
+        // BRL_CMD_CSRHIDE
+        .name = "CSRHIDE",
+        .code = BRL_CMD_CSRHIDE,
+        .isToggle = 1,
+        // xgettext: This is the description of the CSRHIDE command.
+        .description = strtext("set hidden screen cursor on/off"),
+    },
+
+    {
+        // BRL_CMD_CSRTRK
+        .name = "CSRTRK",
+        .code = BRL_CMD_CSRTRK,
+        .isToggle = 1,
+        // xgettext: This is the description of the CSRTRK command.
+        .description = strtext("set track screen cursor on/off"),
+    },
+
+    {
+        // BRL_CMD_CSRSIZE
+        .name = "CSRSIZE",
+        .code = BRL_CMD_CSRSIZE,
+        .isToggle = 1,
+        // xgettext: This is the description of the CSRSIZE command.
+        .description = strtext("set screen cursor style block/underline"),
+    },
+
+    {
+        // BRL_CMD_CSRBLINK
+        .name = "CSRBLINK",
+        .code = BRL_CMD_CSRBLINK,
+        .isToggle = 1,
+        // xgettext: This is the description of the CSRBLINK command.
+        .description = strtext("set screen cursor blinking on/off"),
+    },
+
+    {
+        // BRL_CMD_ATTRVIS
+        .name = "ATTRVIS",
+        .code = BRL_CMD_ATTRVIS,
+        .isToggle = 1,
+        // xgettext: This is the description of the ATTRVIS command.
+        .description = strtext("set attribute underlining on/off"),
+    },
+
+    {
+        // BRL_CMD_ATTRBLINK
+        .name = "ATTRBLINK",
+        .code = BRL_CMD_ATTRBLINK,
+        .isToggle = 1,
+        // xgettext: This is the description of the ATTRBLINK command.
+        .description = strtext("set attribute blinking on/off"),
+    },
+
+    {
+        // BRL_CMD_CAPBLINK
+        .name = "CAPBLINK",
+        .code = BRL_CMD_CAPBLINK,
+        .isToggle = 1,
+        // xgettext: This is the description of the CAPBLINK command.
+        .description = strtext("set capital letter blinking on/off"),
+    },
+
+    {
+        // BRL_CMD_TUNES
+        .name = "TUNES",
+        .code = BRL_CMD_TUNES,
+        .isToggle = 1,
+        // xgettext: This is the description of the TUNES command.
+        .description = strtext("set alert tunes on/off"),
+    },
+
+    {
+        // BRL_CMD_AUTOREPEAT
+        .name = "AUTOREPEAT",
+        .code = BRL_CMD_AUTOREPEAT,
+        .isToggle = 1,
+        // xgettext: This is the description of the AUTOREPEAT command.
+        .description = strtext("set autorepeat on/off"),
+    },
+
+    {
+        // BRL_CMD_AUTOSPEAK
+        .name = "AUTOSPEAK",
+        .code = BRL_CMD_AUTOSPEAK,
+        .isToggle = 1,
+        // xgettext: This is the description of the AUTOSPEAK command.
+        .description = strtext("set autospeak on/off"),
+    },
+
+    {
+        // BRL_CMD_HELP
+        .name = "HELP",
+        .code = BRL_CMD_HELP,
+        // xgettext: This is the description of the HELP command.
+        .description = strtext("enter/leave help display"),
+    },
+
+    {
+        // BRL_CMD_INFO
+        .name = "INFO",
+        .code = BRL_CMD_INFO,
+        // xgettext: This is the description of the INFO command.
+        .description = strtext("enter/leave status display"),
+    },
+
+    {
+        // BRL_CMD_LEARN
+        .name = "LEARN",
+        .code = BRL_CMD_LEARN,
+        // xgettext: This is the description of the LEARN command.
+        .description = strtext("enter/leave command learn mode"),
+    },
+
+    {
+        // BRL_CMD_PREFMENU
+        .name = "PREFMENU",
+        .code = BRL_CMD_PREFMENU,
+        // xgettext: This is the description of the PREFMENU command.
+        .description = strtext("enter/leave preferences menu"),
+    },
+
+    {
+        // BRL_CMD_PREFSAVE
+        .name = "PREFSAVE",
+        .code = BRL_CMD_PREFSAVE,
+        // xgettext: This is the description of the PREFSAVE command.
+        .description = strtext("save preferences to disk"),
+    },
+
+    {
+        // BRL_CMD_PREFLOAD
+        .name = "PREFLOAD",
+        .code = BRL_CMD_PREFLOAD,
+        // xgettext: This is the description of the PREFLOAD command.
+        .description = strtext("restore preferences from disk"),
+    },
+
+    {
+        // BRL_CMD_MENU_FIRST_ITEM
+        .name = "MENU_FIRST_ITEM",
+        .code = BRL_CMD_MENU_FIRST_ITEM,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the MENU_FIRST_ITEM command.
+        .description = strtext("go up to first item"),
+    },
+
+    {
+        // BRL_CMD_MENU_LAST_ITEM
+        .name = "MENU_LAST_ITEM",
+        .code = BRL_CMD_MENU_LAST_ITEM,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the MENU_LAST_ITEM command.
+        .description = strtext("go down to last item"),
+    },
+
+    {
+        // BRL_CMD_MENU_PREV_ITEM
+        .name = "MENU_PREV_ITEM",
+        .code = BRL_CMD_MENU_PREV_ITEM,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the MENU_PREV_ITEM command.
+        .description = strtext("go up to previous item"),
+    },
+
+    {
+        // BRL_CMD_MENU_NEXT_ITEM
+        .name = "MENU_NEXT_ITEM",
+        .code = BRL_CMD_MENU_NEXT_ITEM,
+        .isMotion = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the MENU_NEXT_ITEM command.
+        .description = strtext("go down to next item"),
+    },
+
+    {
+        // BRL_CMD_MENU_PREV_SETTING
+        .name = "MENU_PREV_SETTING",
+        .code = BRL_CMD_MENU_PREV_SETTING,
+        // xgettext: This is the description of the MENU_PREV_SETTING command.
+        .description = strtext("select previous choice"),
+    },
+
+    {
+        // BRL_CMD_MENU_NEXT_SETTING
+        .name = "MENU_NEXT_SETTING",
+        .code = BRL_CMD_MENU_NEXT_SETTING,
+        // xgettext: This is the description of the MENU_NEXT_SETTING command.
+        .description = strtext("select next choice"),
+    },
+
+    {
+        // BRL_CMD_MUTE
+        .name = "MUTE",
+        .code = BRL_CMD_MUTE,
+        // xgettext: This is the description of the MUTE command.
+        .description = strtext("stop speaking"),
+    },
+
+    {
+        // BRL_CMD_SPKHOME
+        .name = "SPKHOME",
+        .code = BRL_CMD_SPKHOME,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPKHOME command.
+        .description = strtext("go to current speaking position"),
+    },
+
+    {
+        // BRL_CMD_SAY_LINE
+        .name = "SAY_LINE",
+        .code = BRL_CMD_SAY_LINE,
+        // xgettext: This is the description of the SAY_LINE command.
+        .description = strtext("speak current line"),
+    },
+
+    {
+        // BRL_CMD_SAY_ABOVE
+        .name = "SAY_ABOVE",
+        .code = BRL_CMD_SAY_ABOVE,
+        // xgettext: This is the description of the SAY_ABOVE command.
+        .description = strtext("speak from top of screen through current line"),
+    },
+
+    {
+        // BRL_CMD_SAY_BELOW
+        .name = "SAY_BELOW",
+        .code = BRL_CMD_SAY_BELOW,
+        // xgettext: This is the description of the SAY_BELOW command.
+        .description =
+            strtext("speak from current line through bottom of screen"),
+    },
+
+    {
+        // BRL_CMD_SAY_SLOWER
+        .name = "SAY_SLOWER",
+        .code = BRL_CMD_SAY_SLOWER,
+        // xgettext: This is the description of the SAY_SLOWER command.
+        .description = strtext("decrease speaking rate"),
+    },
+
+    {
+        // BRL_CMD_SAY_FASTER
+        .name = "SAY_FASTER",
+        .code = BRL_CMD_SAY_FASTER,
+        // xgettext: This is the description of the SAY_FASTER command.
+        .description = strtext("increase speaking rate"),
+    },
+
+    {
+        // BRL_CMD_SAY_SOFTER
+        .name = "SAY_SOFTER",
+        .code = BRL_CMD_SAY_SOFTER,
+        // xgettext: This is the description of the SAY_SOFTER command.
+        .description = strtext("decrease speaking volume"),
+    },
+
+    {
+        // BRL_CMD_SAY_LOUDER
+        .name = "SAY_LOUDER",
+        .code = BRL_CMD_SAY_LOUDER,
+        // xgettext: This is the description of the SAY_LOUDER command.
+        .description = strtext("increase speaking volume"),
+    },
+
+    {
+        // BRL_CMD_SWITCHVT_PREV
+        .name = "SWITCHVT_PREV",
+        .code = BRL_CMD_SWITCHVT_PREV,
+        // xgettext: This is the description of the SWITCHVT_PREV command.
+        .description = strtext("switch to the previous virtual terminal"),
+    },
+
+    {
+        // BRL_CMD_SWITCHVT_NEXT
+        .name = "SWITCHVT_NEXT",
+        .code = BRL_CMD_SWITCHVT_NEXT,
+        // xgettext: This is the description of the SWITCHVT_NEXT command.
+        .description = strtext("switch to the next virtual terminal"),
+    },
+
+    {
+        // BRL_CMD_CSRJMP_VERT
+        .name = "CSRJMP_VERT",
+        .code = BRL_CMD_CSRJMP_VERT,
+        .isRouting = 1,
+        // xgettext: This is the description of the CSRJMP_VERT command.
+        .description = strtext("bring screen cursor to current line"),
+    },
+
+    {
+        // BRL_CMD_PASTE
+        .name = "PASTE",
+        .code = BRL_CMD_PASTE,
+        // xgettext: This is the description of the PASTE command.
+        .description = strtext("insert clipboard text after screen cursor"),
+    },
+
+    {
+        // BRL_CMD_RESTARTBRL
+        .name = "RESTARTBRL",
+        .code = BRL_CMD_RESTARTBRL,
+        // xgettext: This is the description of the RESTARTBRL command.
+        .description = strtext("restart braille driver"),
+    },
+
+    {
+        // BRL_CMD_RESTARTSPEECH
+        .name = "RESTARTSPEECH",
+        .code = BRL_CMD_RESTARTSPEECH,
+        // xgettext: This is the description of the RESTARTSPEECH command.
+        .description = strtext("restart speech driver"),
+    },
+
+    {
+        // BRL_CMD_OFFLINE
+        .name = "OFFLINE",
+        .code = BRL_CMD_OFFLINE,
+        // xgettext: This is the description of the OFFLINE command.
+        .description = strtext("braille display temporarily unavailable"),
+    },
+
+    {
+        // BRL_CMD_SHIFT
+        .name = "SHIFT",
+        .code = BRL_CMD_SHIFT,
+        // xgettext: This is the description of the SHIFT command.
+        .description =
+            strtext("cycle the Shift sticky input modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_UPPER
+        .name = "UPPER",
+        .code = BRL_CMD_UPPER,
+        // xgettext: This is the description of the UPPER command.
+        .description =
+            strtext("cycle the Upper sticky input modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_CONTROL
+        .name = "CONTROL",
+        .code = BRL_CMD_CONTROL,
+        // xgettext: This is the description of the CONTROL command.
+        .description =
+            strtext("cycle the Control sticky input modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_META
+        .name = "META",
+        .code = BRL_CMD_META,
+        // xgettext: This is the description of the META command.
+        .description = strtext(
+            "cycle the Meta (Left Alt) sticky input modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_TIME
+        .name = "TIME",
+        .code = BRL_CMD_TIME,
+        // xgettext: This is the description of the TIME command.
+        .description = strtext("show current date and time"),
+    },
+
+    {
+        // BRL_CMD_MENU_PREV_LEVEL
+        .name = "MENU_PREV_LEVEL",
+        .code = BRL_CMD_MENU_PREV_LEVEL,
+        .isMotion = 1,
+        // xgettext: This is the description of the MENU_PREV_LEVEL command.
+        .description = strtext("go to previous menu level"),
+    },
+
+    {
+        // BRL_CMD_ASPK_SEL_LINE
+        .name = "ASPK_SEL_LINE",
+        .code = BRL_CMD_ASPK_SEL_LINE,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_SEL_LINE command.
+        .description = strtext("set autospeak selected line on/off"),
+    },
+
+    {
+        // BRL_CMD_ASPK_SEL_CHAR
+        .name = "ASPK_SEL_CHAR",
+        .code = BRL_CMD_ASPK_SEL_CHAR,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_SEL_CHAR command.
+        .description = strtext("set autospeak selected character on/off"),
+    },
+
+    {
+        // BRL_CMD_ASPK_INS_CHARS
+        .name = "ASPK_INS_CHARS",
+        .code = BRL_CMD_ASPK_INS_CHARS,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_INS_CHARS command.
+        .description = strtext("set autospeak inserted characters on/off"),
+    },
+
+    {
+        // BRL_CMD_ASPK_DEL_CHARS
+        .name = "ASPK_DEL_CHARS",
+        .code = BRL_CMD_ASPK_DEL_CHARS,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_DEL_CHARS command.
+        .description = strtext("set autospeak deleted characters on/off"),
+    },
+
+    {
+        // BRL_CMD_ASPK_REP_CHARS
+        .name = "ASPK_REP_CHARS",
+        .code = BRL_CMD_ASPK_REP_CHARS,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_REP_CHARS command.
+        .description = strtext("set autospeak replaced characters on/off"),
+    },
+
+    {
+        // BRL_CMD_ASPK_CMP_WORDS
+        .name = "ASPK_CMP_WORDS",
+        .code = BRL_CMD_ASPK_CMP_WORDS,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_CMP_WORDS command.
+        .description = strtext("set autospeak completed words on/off"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_CURR_CHAR
+        .name = "SPEAK_CURR_CHAR",
+        .code = BRL_CMD_SPEAK_CURR_CHAR,
+        // xgettext: This is the description of the SPEAK_CURR_CHAR command.
+        .description = strtext("speak current character"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_PREV_CHAR
+        .name = "SPEAK_PREV_CHAR",
+        .code = BRL_CMD_SPEAK_PREV_CHAR,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_PREV_CHAR command.
+        .description = strtext("go to and speak previous character"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_NEXT_CHAR
+        .name = "SPEAK_NEXT_CHAR",
+        .code = BRL_CMD_SPEAK_NEXT_CHAR,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_NEXT_CHAR command.
+        .description = strtext("go to and speak next character"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_CURR_WORD
+        .name = "SPEAK_CURR_WORD",
+        .code = BRL_CMD_SPEAK_CURR_WORD,
+        // xgettext: This is the description of the SPEAK_CURR_WORD command.
+        .description = strtext("speak current word"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_PREV_WORD
+        .name = "SPEAK_PREV_WORD",
+        .code = BRL_CMD_SPEAK_PREV_WORD,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_PREV_WORD command.
+        .description = strtext("go to and speak previous word"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_NEXT_WORD
+        .name = "SPEAK_NEXT_WORD",
+        .code = BRL_CMD_SPEAK_NEXT_WORD,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_NEXT_WORD command.
+        .description = strtext("go to and speak next word"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_CURR_LINE
+        .name = "SPEAK_CURR_LINE",
+        .code = BRL_CMD_SPEAK_CURR_LINE,
+        // xgettext: This is the description of the SPEAK_CURR_LINE command.
+        .description = strtext("speak current line"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_PREV_LINE
+        .name = "SPEAK_PREV_LINE",
+        .code = BRL_CMD_SPEAK_PREV_LINE,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_PREV_LINE command.
+        .description = strtext("go to and speak previous line"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_NEXT_LINE
+        .name = "SPEAK_NEXT_LINE",
+        .code = BRL_CMD_SPEAK_NEXT_LINE,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_NEXT_LINE command.
+        .description = strtext("go to and speak next line"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_FRST_CHAR
+        .name = "SPEAK_FRST_CHAR",
+        .code = BRL_CMD_SPEAK_FRST_CHAR,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_FRST_CHAR command.
+        .description =
+            strtext("go to and speak first non-blank character on line"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_LAST_CHAR
+        .name = "SPEAK_LAST_CHAR",
+        .code = BRL_CMD_SPEAK_LAST_CHAR,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_LAST_CHAR command.
+        .description =
+            strtext("go to and speak last non-blank character on line"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_FRST_LINE
+        .name = "SPEAK_FRST_LINE",
+        .code = BRL_CMD_SPEAK_FRST_LINE,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_FRST_LINE command.
+        .description =
+            strtext("go to and speak first non-blank line on screen"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_LAST_LINE
+        .name = "SPEAK_LAST_LINE",
+        .code = BRL_CMD_SPEAK_LAST_LINE,
+        .isMotion = 1,
+        // xgettext: This is the description of the SPEAK_LAST_LINE command.
+        .description = strtext("go to and speak last non-blank line on screen"),
+    },
+
+    {
+        // BRL_CMD_DESC_CURR_CHAR
+        .name = "DESC_CURR_CHAR",
+        .code = BRL_CMD_DESC_CURR_CHAR,
+        // xgettext: This is the description of the DESC_CURR_CHAR command.
+        .description = strtext("describe current character"),
+    },
+
+    {
+        // BRL_CMD_SPELL_CURR_WORD
+        .name = "SPELL_CURR_WORD",
+        .code = BRL_CMD_SPELL_CURR_WORD,
+        // xgettext: This is the description of the SPELL_CURR_WORD command.
+        .description = strtext("spell current word"),
+    },
+
+    {
+        // BRL_CMD_ROUTE_CURR_LOCN
+        .name = "ROUTE_CURR_LOCN",
+        .code = BRL_CMD_ROUTE_CURR_LOCN,
+        .isRouting = 1,
+        // xgettext: This is the description of the ROUTE_CURR_LOCN command.
+        .description = strtext("bring screen cursor to speech cursor"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_CURR_LOCN
+        .name = "SPEAK_CURR_LOCN",
+        .code = BRL_CMD_SPEAK_CURR_LOCN,
+        // xgettext: This is the description of the SPEAK_CURR_LOCN command.
+        .description = strtext("speak speech cursor location"),
+    },
+
+    {
+        // BRL_CMD_SHOW_CURR_LOCN
+        .name = "SHOW_CURR_LOCN",
+        .code = BRL_CMD_SHOW_CURR_LOCN,
+        .isToggle = 1,
+        // xgettext: This is the description of the SHOW_CURR_LOCN command.
+        .description = strtext("set speech cursor visibility on/off"),
+    },
+
+    {
+        // BRL_CMD_CLIP_SAVE
+        .name = "CLIP_SAVE",
+        .code = BRL_CMD_CLIP_SAVE,
+        // xgettext: This is the description of the CLIP_SAVE command.
+        .description = strtext("save clipboard to disk"),
+    },
+
+    {
+        // BRL_CMD_CLIP_RESTORE
+        .name = "CLIP_RESTORE",
+        .code = BRL_CMD_CLIP_RESTORE,
+        // xgettext: This is the description of the CLIP_RESTORE command.
+        .description = strtext("restore clipboard from disk"),
+    },
+
+    {
+        // BRL_CMD_BRLUCDOTS
+        .name = "BRLUCDOTS",
+        .code = BRL_CMD_BRLUCDOTS,
+        .isToggle = 1,
+        // xgettext: This is the description of the BRLUCDOTS command.
+        .description = strtext("set braille typing mode dots/text"),
+    },
+
+    {
+        // BRL_CMD_BRLKBD
+        .name = "BRLKBD",
+        .code = BRL_CMD_BRLKBD,
+        .isToggle = 1,
+        // xgettext: This is the description of the BRLKBD command.
+        .description = strtext("set braille keyboard enabled/disabled"),
+    },
+
+    {
+        // BRL_CMD_UNSTICK
+        .name = "UNSTICK",
+        .code = BRL_CMD_UNSTICK,
+        // xgettext: This is the description of the UNSTICK command.
+        .description = strtext("clear all sticky input modifiers"),
+    },
+
+    {
+        // BRL_CMD_ALTGR
+        .name = "ALTGR",
+        .code = BRL_CMD_ALTGR,
+        // xgettext: This is the description of the ALTGR command.
+        .description = strtext("cycle the AltGr (Right Alt) sticky input "
+                               "modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_GUI
+        .name = "GUI",
+        .code = BRL_CMD_GUI,
+        // xgettext: This is the description of the GUI command.
+        .description = strtext(
+            "cycle the GUI (Windows) sticky input modifier (next, on, off)"),
+    },
+
+    {
+        // BRL_CMD_BRL_STOP
+        .name = "BRL_STOP",
+        .code = BRL_CMD_BRL_STOP,
+        // xgettext: This is the description of the BRL_STOP command.
+        .description = strtext("stop the braille driver"),
+    },
+
+    {
+        // BRL_CMD_BRL_START
+        .name = "BRL_START",
+        .code = BRL_CMD_BRL_START,
+        // xgettext: This is the description of the BRL_START command.
+        .description = strtext("start the braille driver"),
+    },
+
+    {
+        // BRL_CMD_SPK_STOP
+        .name = "SPK_STOP",
+        .code = BRL_CMD_SPK_STOP,
+        // xgettext: This is the description of the SPK_STOP command.
+        .description = strtext("stop the speech driver"),
+    },
+
+    {
+        // BRL_CMD_SPK_START
+        .name = "SPK_START",
+        .code = BRL_CMD_SPK_START,
+        // xgettext: This is the description of the SPK_START command.
+        .description = strtext("start the speech driver"),
+    },
+
+    {
+        // BRL_CMD_SCR_STOP
+        .name = "SCR_STOP",
+        .code = BRL_CMD_SCR_STOP,
+        // xgettext: This is the description of the SCR_STOP command.
+        .description = strtext("stop the screen driver"),
+    },
+
+    {
+        // BRL_CMD_SCR_START
+        .name = "SCR_START",
+        .code = BRL_CMD_SCR_START,
+        // xgettext: This is the description of the SCR_START command.
+        .description = strtext("start the screen driver"),
+    },
+
+    {
+        // BRL_CMD_SELECTVT_PREV
+        .name = "SELECTVT_PREV",
+        .code = BRL_CMD_SELECTVT_PREV,
+        // xgettext: This is the description of the SELECTVT_PREV command.
+        .description = strtext("bind to the previous virtual terminal"),
+    },
+
+    {
+        // BRL_CMD_SELECTVT_NEXT
+        .name = "SELECTVT_NEXT",
+        .code = BRL_CMD_SELECTVT_NEXT,
+        // xgettext: This is the description of the SELECTVT_NEXT command.
+        .description = strtext("bind to the next virtual terminal"),
+    },
+
+    {
+        // BRL_CMD_PRNBWIN
+        .name = "PRNBWIN",
+        .code = BRL_CMD_PRNBWIN,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the PRNBWIN command.
+        .description =
+            strtext("go backward to nearest non-blank braille window"),
+    },
+
+    {
+        // BRL_CMD_NXNBWIN
+        .name = "NXNBWIN",
+        .code = BRL_CMD_NXNBWIN,
+        .isMotion = 1,
+        .isPanning = 1,
+        // xgettext: This is the description of the NXNBWIN command.
+        .description =
+            strtext("go forward to nearest non-blank braille window"),
+    },
+
+    {
+        // BRL_CMD_TOUCH_NAV
+        .name = "TOUCH_NAV",
+        .code = BRL_CMD_TOUCH_NAV,
+        .isToggle = 1,
+        // xgettext: This is the description of the TOUCH_NAV command.
+        .description = strtext("set touch navigation on/off"),
+    },
+
+    {
+        // BRL_CMD_SPEAK_INDENT
+        .name = "SPEAK_INDENT",
+        .code = BRL_CMD_SPEAK_INDENT,
+        // xgettext: This is the description of the SPEAK_INDENT command.
+        .description = strtext("speak indent of current line"),
+    },
+
+    {
+        // BRL_CMD_ASPK_INDENT
+        .name = "ASPK_INDENT",
+        .code = BRL_CMD_ASPK_INDENT,
+        .isToggle = 1,
+        // xgettext: This is the description of the ASPK_INDENT command.
+        .description = strtext("set autospeak indent of current line on/off"),
+    },
+
+    {
+        // BRL_CMD_REFRESH
+        .name = "REFRESH",
+        .code = BRL_CMD_REFRESH,
+        // xgettext: This is the description of the REFRESH command.
+        .description = strtext("refresh braille display"),
+    },
+
+    {
+        // BRL_CMD_INDICATORS
+        .name = "INDICATORS",
+        .code = BRL_CMD_INDICATORS,
+        // xgettext: This is the description of the INDICATORS command.
+        .description = strtext("show various device status indicators"),
+    },
+
+    {
+        // BRL_CMD_TXTSEL_CLEAR
+        .name = "TXTSEL_CLEAR",
+        .code = BRL_CMD_TXTSEL_CLEAR,
+        // xgettext: This is the description of the TXTSEL_CLEAR command.
+        .description = strtext("clear the text selection"),
+    },
+
+    {
+        // BRL_CMD_TXTSEL_ALL
+        .name = "TXTSEL_ALL",
+        .code = BRL_CMD_TXTSEL_ALL,
+        // xgettext: This is the description of the TXTSEL_ALL command.
+        .description = strtext("select all of the text"),
+    },
+
+    {
+        // BRL_CMD_HOST_COPY
+        .name = "HOST_COPY",
+        .code = BRL_CMD_HOST_COPY,
+        // xgettext: This is the description of the HOST_COPY command.
+        .description = strtext("copy selected text to host clipboard"),
+    },
+
+    {
+        // BRL_CMD_HOST_CUT
+        .name = "HOST_CUT",
+        .code = BRL_CMD_HOST_CUT,
+        // xgettext: This is the description of the HOST_CUT command.
+        .description = strtext("cut selected text to host clipboard"),
+    },
+
+    {
+        // BRL_CMD_HOST_PASTE
+        .name = "HOST_PASTE",
+        .code = BRL_CMD_HOST_PASTE,
+        // xgettext: This is the description of the HOST_PASTE command.
+        .description =
+            strtext("insert host clipboard text after screen cursor"),
+    },
+
+    {
+        // BRL_CMD_GUI_TITLE
+        .name = "GUI_TITLE",
+        .code = BRL_CMD_GUI_TITLE,
+        // xgettext: This is the description of the GUI_TITLE command.
+        .description = strtext("show the window title"),
+    },
+
+    {
+        // BRL_CMD_GUI_BRL_ACTIONS
+        .name = "GUI_BRL_ACTIONS",
+        .code = BRL_CMD_GUI_BRL_ACTIONS,
+        // xgettext: This is the description of the GUI_BRL_ACTIONS command.
+        .description = strtext("open the braille actions window"),
+    },
+
+    {
+        // BRL_CMD_GUI_HOME
+        .name = "GUI_HOME",
+        .code = BRL_CMD_GUI_HOME,
+        .isMotion = 1,
+        // xgettext: This is the description of the GUI_HOME command.
+        .description = strtext("go to the home screen"),
+    },
+
+    {
+        // BRL_CMD_GUI_BACK
+        .name = "GUI_BACK",
+        .code = BRL_CMD_GUI_BACK,
+        .isMotion = 1,
+        // xgettext: This is the description of the GUI_BACK command.
+        .description = strtext("go back to the previous screen"),
+    },
+
+    {
+        // BRL_CMD_GUI_DEV_SETTINGS
+        .name = "GUI_DEV_SETTINGS",
+        .code = BRL_CMD_GUI_DEV_SETTINGS,
+        // xgettext: This is the description of the GUI_DEV_SETTINGS command.
+        .description = strtext("open the device settings window"),
+    },
+
+    {
+        // BRL_CMD_GUI_DEV_OPTIONS
+        .name = "GUI_DEV_OPTIONS",
+        .code = BRL_CMD_GUI_DEV_OPTIONS,
+        // xgettext: This is the description of the GUI_DEV_OPTIONS command.
+        .description = strtext("open the device options window"),
+    },
+
+    {
+        // BRL_CMD_GUI_APP_LIST
+        .name = "GUI_APP_LIST",
+        .code = BRL_CMD_GUI_APP_LIST,
+        // xgettext: This is the description of the GUI_APP_LIST command.
+        .description = strtext("open the application list window"),
+    },
+
+    {
+        // BRL_CMD_GUI_APP_MENU
+        .name = "GUI_APP_MENU",
+        .code = BRL_CMD_GUI_APP_MENU,
+        // xgettext: This is the description of the GUI_APP_MENU command.
+        .description = strtext("open the application-specific menu"),
+    },
+
+    {
+        // BRL_CMD_GUI_APP_ALERTS
+        .name = "GUI_APP_ALERTS",
+        .code = BRL_CMD_GUI_APP_ALERTS,
+        // xgettext: This is the description of the GUI_APP_ALERTS command.
+        .description = strtext("open the application alerts window"),
+    },
+
+    {
+        // BRL_CMD_GUI_AREA_ACTV
+        .name = "GUI_AREA_ACTV",
+        .code = BRL_CMD_GUI_AREA_ACTV,
+        // xgettext: This is the description of the GUI_AREA_ACTV command.
+        .description = strtext("return to the active screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_AREA_PREV
+        .name = "GUI_AREA_PREV",
+        .code = BRL_CMD_GUI_AREA_PREV,
+        // xgettext: This is the description of the GUI_AREA_PREV command.
+        .description = strtext("switch to the previous screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_AREA_NEXT
+        .name = "GUI_AREA_NEXT",
+        .code = BRL_CMD_GUI_AREA_NEXT,
+        // xgettext: This is the description of the GUI_AREA_NEXT command.
+        .description = strtext("switch to the next screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_ITEM_FRST
+        .name = "GUI_ITEM_FRST",
+        .code = BRL_CMD_GUI_ITEM_FRST,
+        // xgettext: This is the description of the GUI_ITEM_FRST command.
+        .description = strtext("move to the first item in the screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_ITEM_PREV
+        .name = "GUI_ITEM_PREV",
+        .code = BRL_CMD_GUI_ITEM_PREV,
+        // xgettext: This is the description of the GUI_ITEM_PREV command.
+        .description = strtext("move to the previous item in the screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_ITEM_NEXT
+        .name = "GUI_ITEM_NEXT",
+        .code = BRL_CMD_GUI_ITEM_NEXT,
+        // xgettext: This is the description of the GUI_ITEM_NEXT command.
+        .description = strtext("move to the next item in the screen area"),
+    },
+
+    {
+        // BRL_CMD_GUI_ITEM_LAST
+        .name = "GUI_ITEM_LAST",
+        .code = BRL_CMD_GUI_ITEM_LAST,
+        // xgettext: This is the description of the GUI_ITEM_LAST command.
+        .description = strtext("move to the last item in the screen area"),
+    },
+
+    {
+        // BRL_CMD_SAY_LOWER
+        .name = "SAY_LOWER",
+        .code = BRL_CMD_SAY_LOWER,
+        // xgettext: This is the description of the SAY_LOWER command.
+        .description = strtext("decrease speaking pitch"),
+    },
+
+    {
+        // BRL_CMD_SAY_HIGHER
+        .name = "SAY_HIGHER",
+        .code = BRL_CMD_SAY_HIGHER,
+        // xgettext: This is the description of the SAY_HIGHER command.
+        .description = strtext("increase speaking pitch"),
+    },
+
+    {
+        // BRL_CMD_SAY_ALL
+        .name = "SAY_ALL",
+        .code = BRL_CMD_SAY_ALL,
+        // xgettext: This is the description of the SAY_ALL command.
+        .description =
+            strtext("speak from top of screen through bottom of screen"),
+    },
+
+    {
+        // BRL_CMD_CONTRACTED
+        .name = "CONTRACTED",
+        .code = BRL_CMD_CONTRACTED,
+        .isToggle = 1,
+        // xgettext: This is the description of the CONTRACTED command.
+        .description = strtext("set contracted/computer braille"),
+    },
+
+    {
+        // BRL_CMD_COMPBRL6
+        .name = "COMPBRL6",
+        .code = BRL_CMD_COMPBRL6,
+        .isToggle = 1,
+        // xgettext: This is the description of the COMPBRL6 command.
+        .description = strtext("set six/eight dot computer braille"),
+    },
+
+    {
+        // BRL_CMD_PREFRESET
+        .name = "PREFRESET",
+        .code = BRL_CMD_PREFRESET,
+        // xgettext: This is the description of the PREFRESET command.
+        .description = strtext("reset preferences to defaults"),
+    },
+
+    {
+        // BRL_BLK_ROUTE
+        .name = "ROUTE",
+        .code = BRL_CMD_BLK(ROUTE),
+        .isRouting = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the ROUTE command.
+        .description = strtext("bring screen cursor to character"),
+    },
+
+    {
+        // BRL_BLK_CLIP_NEW
+        .name = "CLIP_NEW",
+        .code = BRL_CMD_BLK(CLIP_NEW),
+        .isColumn = 1,
+        // xgettext: This is the description of the CLIP_NEW command.
+        .description = strtext("start new clipboard at character"),
+    },
+
+    {
+        // BRL_BLK_CLIP_ADD
+        .name = "CLIP_ADD",
+        .code = BRL_CMD_BLK(CLIP_ADD),
+        .isColumn = 1,
+        // xgettext: This is the description of the CLIP_ADD command.
+        .description = strtext("append to clipboard from character"),
+    },
+
+    {
+        // BRL_BLK_COPY_RECT
+        .name = "COPY_RECT",
+        .code = BRL_CMD_BLK(COPY_RECT),
+        .isColumn = 1,
+        // xgettext: This is the description of the COPY_RECT command.
+        .description = strtext("rectangular copy to character"),
+    },
+
+    {
+        // BRL_BLK_COPY_LINE
+        .name = "COPY_LINE",
+        .code = BRL_CMD_BLK(COPY_LINE),
+        .isColumn = 1,
+        // xgettext: This is the description of the COPY_LINE command.
+        .description = strtext("linear copy to character"),
+    },
+
+    {
+        // BRL_BLK_SWITCHVT
+        .name = "SWITCHVT",
+        .code = BRL_CMD_BLK(SWITCHVT),
+        .isOffset = 1,
+        // xgettext: This is the description of the SWITCHVT command.
+        .description = strtext("switch to specific virtual terminal"),
+    },
+
+    {
+        // BRL_BLK_PRINDENT
+        .name = "PRINDENT",
+        .code = BRL_CMD_BLK(PRINDENT),
+        .isMotion = 1,
+        .isVertical = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the PRINDENT command.
+        .description =
+            strtext("go up to nearest line with less indent than character"),
+    },
+
+    {
+        // BRL_BLK_NXINDENT
+        .name = "NXINDENT",
+        .code = BRL_CMD_BLK(NXINDENT),
+        .isMotion = 1,
+        .isVertical = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the NXINDENT command.
+        .description =
+            strtext("go down to nearest line with less indent than character"),
+    },
+
+    {
+        // BRL_BLK_DESCCHAR
+        .name = "DESCCHAR",
+        .code = BRL_CMD_BLK(DESCCHAR),
+        .isColumn = 1,
+        // xgettext: This is the description of the DESCCHAR command.
+        .description = strtext("describe character"),
+    },
+
+    {
+        // BRL_BLK_SETLEFT
+        .name = "SETLEFT",
+        .code = BRL_CMD_BLK(SETLEFT),
+        .isColumn = 1,
+        // xgettext: This is the description of the SETLEFT command.
+        .description = strtext("place left end of braille window at character"),
+    },
+
+    {
+        // BRL_BLK_SETMARK
+        .name = "SETMARK",
+        .code = BRL_CMD_BLK(SETMARK),
+        .isOffset = 1,
+        // xgettext: This is the description of the SETMARK command.
+        .description = strtext("remember current braille window position"),
+    },
+
+    {
+        // BRL_BLK_GOTOMARK
+        .name = "GOTOMARK",
+        .code = BRL_CMD_BLK(GOTOMARK),
+        .isMotion = 1,
+        .isOffset = 1,
+        // xgettext: This is the description of the GOTOMARK command.
+        .description = strtext("go to remembered braille window position"),
+    },
+
+    {
+        // BRL_BLK_GOTOLINE
+        .name = "GOTOLINE",
+        .code = BRL_CMD_BLK(GOTOLINE),
+        .isMotion = 1,
+        .isRow = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the GOTOLINE command.
+        .description = strtext("go to selected line"),
+    },
+
+    {
+        // BRL_BLK_PRDIFCHAR
+        .name = "PRDIFCHAR",
+        .code = BRL_CMD_BLK(PRDIFCHAR),
+        .isMotion = 1,
+        .isVertical = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the PRDIFCHAR command.
+        .description =
+            strtext("go up to nearest line with different character"),
+    },
+
+    {
+        // BRL_BLK_NXDIFCHAR
+        .name = "NXDIFCHAR",
+        .code = BRL_CMD_BLK(NXDIFCHAR),
+        .isMotion = 1,
+        .isVertical = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the NXDIFCHAR command.
+        .description =
+            strtext("go down to nearest line with different character"),
+    },
+
+    {
+        // BRL_BLK_CLIP_COPY
+        .name = "CLIP_COPY",
+        .code = BRL_CMD_BLK(CLIP_COPY),
+        .isRange = 1,
+        // xgettext: This is the description of the CLIP_COPY command.
+        .description = strtext("copy characters to clipboard"),
+    },
+
+    {
+        // BRL_BLK_CLIP_APPEND
+        .name = "CLIP_APPEND",
+        .code = BRL_CMD_BLK(CLIP_APPEND),
+        .isRange = 1,
+        // xgettext: This is the description of the CLIP_APPEND command.
+        .description = strtext("append characters to clipboard"),
+    },
+
+    {
+        // BRL_BLK_PASTE_HISTORY
+        .name = "PASTE_HISTORY",
+        .code = BRL_CMD_BLK(PASTE_HISTORY),
+        .isOffset = 1,
+        // xgettext: This is the description of the PASTE_HISTORY command.
+        .description =
+            strtext("insert clipboard history entry after screen cursor"),
+    },
+
+    {
+        // BRL_BLK_SET_TEXT_TABLE
+        .name = "SET_TEXT_TABLE",
+        .code = BRL_CMD_BLK(SET_TEXT_TABLE),
+        .isOffset = 1,
+        // xgettext: This is the description of the SET_TEXT_TABLE command.
+        .description = strtext("set text table"),
+    },
+
+    {
+        // BRL_BLK_SET_ATTRIBUTES_TABLE
+        .name = "SET_ATTRIBUTES_TABLE",
+        .code = BRL_CMD_BLK(SET_ATTRIBUTES_TABLE),
+        .isOffset = 1,
+        // xgettext: This is the description of the SET_ATTRIBUTES_TABLE
+        // command.
+        .description = strtext("set attributes table"),
+    },
+
+    {
+        // BRL_BLK_SET_CONTRACTION_TABLE
+        .name = "SET_CONTRACTION_TABLE",
+        .code = BRL_CMD_BLK(SET_CONTRACTION_TABLE),
+        .isOffset = 1,
+        // xgettext: This is the description of the SET_CONTRACTION_TABLE
+        // command.
+        .description = strtext("set contraction table"),
+    },
+
+    {
+        // BRL_BLK_SET_KEYBOARD_TABLE
+        .name = "SET_KEYBOARD_TABLE",
+        .code = BRL_CMD_BLK(SET_KEYBOARD_TABLE),
+        .isOffset = 1,
+        // xgettext: This is the description of the SET_KEYBOARD_TABLE command.
+        .description = strtext("set keyboard table"),
+    },
+
+    {
+        // BRL_BLK_SET_LANGUAGE_PROFILE
+        .name = "SET_LANGUAGE_PROFILE",
+        .code = BRL_CMD_BLK(SET_LANGUAGE_PROFILE),
+        .isOffset = 1,
+        // xgettext: This is the description of the SET_LANGUAGE_PROFILE
+        // command.
+        .description = strtext("set language profile"),
+    },
+
+    {
+        // BRL_BLK_ROUTE_LINE
+        .name = "ROUTE_LINE",
+        .code = BRL_CMD_BLK(ROUTE_LINE),
+        .isRouting = 1,
+        .isRow = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the ROUTE_LINE command.
+        .description = strtext("bring screen cursor to line"),
+    },
+
+    {
+        // BRL_BLK_REFRESH_LINE
+        .name = "REFRESH_LINE",
+        .code = BRL_CMD_BLK(REFRESH_LINE),
+        .isRow = 1,
+        .isVertical = 1,
+        // xgettext: This is the description of the REFRESH_LINE command.
+        .description = strtext("refresh braille line"),
+    },
+
+    {
+        // BRL_BLK_TXTSEL_START
+        .name = "TXTSEL_START",
+        .code = BRL_CMD_BLK(TXTSEL_START),
+        .isOffset = 1,
+        // xgettext: This is the description of the TXTSEL_START command.
+        .description = strtext("start text selection"),
+    },
+
+    {
+        // BRL_BLK_TXTSEL_SET
+        .name = "TXTSEL_SET",
+        .code = BRL_CMD_BLK(TXTSEL_SET),
+        .isOffset = 1,
+        // xgettext: This is the description of the TXTSEL_SET command.
+        .description = strtext("set text selection"),
+    },
+
+    {
+        // BRL_BLK_ROUTE_SPEECH
+        .name = "ROUTE_SPEECH",
+        .code = BRL_CMD_BLK(ROUTE_SPEECH),
+        .isRouting = 1,
+        .isColumn = 1,
+        // xgettext: This is the description of the ROUTE_SPEECH command.
+        .description = strtext("bring speech cursor to character"),
+    },
+
+    {
+        // BRL_BLK_SELECTVT
+        .name = "SELECTVT",
+        .code = BRL_CMD_BLK(SELECTVT),
+        .isOffset = 1,
+        // xgettext: This is the description of the SELECTVT command.
+        .description = strtext("bind to specific virtual terminal"),
+    },
+
+    {
+        // BRL_BLK_ALERT
+        .name = "ALERT",
+        .code = BRL_CMD_BLK(ALERT),
+        .isOffset = 1,
+        // xgettext: This is the description of the ALERT command.
+        .description = strtext("render an alert"),
+    },
+
+    {
+        // BRL_BLK_PASSCHAR
+        .name = "PASSCHAR",
+        .code = BRL_CMD_BLK(PASSCHAR),
+        .isInput = 1,
+        .isCharacter = 1,
+        // xgettext: This is the description of the PASSCHAR command.
+        .description = strtext("type unicode character"),
+    },
+
+    {
+        // BRL_BLK_PASSDOTS
+        .name = "PASSDOTS",
+        .code = BRL_CMD_BLK(PASSDOTS),
+        .isInput = 1,
+        .isBraille = 1,
+        // xgettext: This is the description of the PASSDOTS command.
+        .description = strtext("type braille dots"),
+    },
+
+    {
+        // BRL_BLK_PASSAT
+        .name = "PASSAT",
+        .code = BRL_CMD_BLK(PASSAT),
+        .isKeyboard = 1,
+        // xgettext: This is the description of the PASSAT command.
+        .description = strtext("AT (set 2) keyboard scan code"),
+    },
+
+    {
+        // BRL_BLK_PASSXT
+        .name = "PASSXT",
+        .code = BRL_CMD_BLK(PASSXT),
+        .isKeyboard = 1,
+        // xgettext: This is the description of the PASSXT command.
+        .description = strtext("XT (set 1) keyboard scan code"),
+    },
+
+    {
+        // BRL_BLK_PASSPS2
+        .name = "PASSPS2",
+        .code = BRL_CMD_BLK(PASSPS2),
+        .isKeyboard = 1,
+        // xgettext: This is the description of the PASSPS2 command.
+        .description = strtext("PS/2 (set 3) keyboard scan code"),
+    },
+
+    {
+        // BRL_BLK_CONTEXT
+        .name = "CONTEXT",
+        .code = BRL_CMD_BLK(CONTEXT),
+        .isOffset = 1,
+        // xgettext: This is the description of the CONTEXT command.
+        .description = strtext("switch to command context"),
+    },
+
+    {
+        // BRL_BLK_TOUCH_AT
+        .name = "TOUCH_AT",
+        .code = BRL_CMD_BLK(TOUCH_AT),
+        .isOffset = 1,
+        // xgettext: This is the description of the TOUCH_AT command.
+        .description = strtext("current reading location"),
+    },
+
+    {
+        // BRL_BLK_MACRO
+        .name = "MACRO",
+        .code = BRL_CMD_BLK(MACRO),
+        .isOffset = 1,
+        // xgettext: This is the description of the MACRO command.
+        .description = strtext("execute command macro"),
+    },
+
+    {
+        // BRL_BLK_HOSTCMD
+        .name = "HOSTCMD",
+        .code = BRL_CMD_BLK(HOSTCMD),
+        .isOffset = 1,
+        // xgettext: This is the description of the HOSTCMD command.
+        .description = strtext("run host command"),
+    },
+
+    {
+        // BRL_KEY_ENTER
+        .name = "KEY_ENTER",
+        .code = BRL_CMD_KEY(ENTER),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_ENTER command.
+        .description = strtext("enter key"),
+    },
+
+    {
+        // BRL_KEY_TAB
+        .name = "KEY_TAB",
+        .code = BRL_CMD_KEY(TAB),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_TAB command.
+        .description = strtext("tab key"),
+    },
+
+    {
+        // BRL_KEY_BACKSPACE
+        .name = "KEY_BACKSPACE",
+        .code = BRL_CMD_KEY(BACKSPACE),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_BACKSPACE command.
+        .description = strtext("backspace key"),
+    },
+
+    {
+        // BRL_KEY_ESCAPE
+        .name = "KEY_ESCAPE",
+        .code = BRL_CMD_KEY(ESCAPE),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_ESCAPE command.
+        .description = strtext("escape key"),
+    },
+
+    {
+        // BRL_KEY_CURSOR_LEFT
+        .name = "KEY_CURSOR_LEFT",
+        .code = BRL_CMD_KEY(CURSOR_LEFT),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_CURSOR_LEFT command.
+        .description = strtext("cursor-left key"),
+    },
+
+    {
+        // BRL_KEY_CURSOR_RIGHT
+        .name = "KEY_CURSOR_RIGHT",
+        .code = BRL_CMD_KEY(CURSOR_RIGHT),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_CURSOR_RIGHT command.
+        .description = strtext("cursor-right key"),
+    },
+
+    {
+        // BRL_KEY_CURSOR_UP
+        .name = "KEY_CURSOR_UP",
+        .code = BRL_CMD_KEY(CURSOR_UP),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_CURSOR_UP command.
+        .description = strtext("cursor-up key"),
+    },
+
+    {
+        // BRL_KEY_CURSOR_DOWN
+        .name = "KEY_CURSOR_DOWN",
+        .code = BRL_CMD_KEY(CURSOR_DOWN),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_CURSOR_DOWN command.
+        .description = strtext("cursor-down key"),
+    },
+
+    {
+        // BRL_KEY_PAGE_UP
+        .name = "KEY_PAGE_UP",
+        .code = BRL_CMD_KEY(PAGE_UP),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_PAGE_UP command.
+        .description = strtext("page-up key"),
+    },
+
+    {
+        // BRL_KEY_PAGE_DOWN
+        .name = "KEY_PAGE_DOWN",
+        .code = BRL_CMD_KEY(PAGE_DOWN),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_PAGE_DOWN command.
+        .description = strtext("page-down key"),
+    },
+
+    {
+        // BRL_KEY_HOME
+        .name = "KEY_HOME",
+        .code = BRL_CMD_KEY(HOME),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_HOME command.
+        .description = strtext("home key"),
+    },
+
+    {
+        // BRL_KEY_END
+        .name = "KEY_END",
+        .code = BRL_CMD_KEY(END),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_END command.
+        .description = strtext("end key"),
+    },
+
+    {
+        // BRL_KEY_INSERT
+        .name = "KEY_INSERT",
+        .code = BRL_CMD_KEY(INSERT),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_INSERT command.
+        .description = strtext("insert key"),
+    },
+
+    {
+        // BRL_KEY_DELETE
+        .name = "KEY_DELETE",
+        .code = BRL_CMD_KEY(DELETE),
+        .isInput = 1,
+        // xgettext: This is the description of the KEY_DELETE command.
+        .description = strtext("delete key"),
+    },
+
+    {
+        // BRL_KEY_FUNCTION
+        .name = "KEY_FUNCTION",
+        .code = BRL_CMD_KEY(FUNCTION),
+        .isInput = 1,
+        .isOffset = 1,
+        // xgettext: This is the description of the KEY_FUNCTION command.
+        .description = strtext("function key"),
+    },
diff --git a/Programs/cmds.awk b/Programs/cmds.awk
new file mode 100644
index 0000000..2e9d1b8
--- /dev/null
+++ b/Programs/cmds.awk
@@ -0,0 +1,101 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+function writeCommandField(name, value) {
+  print "  ." name " = " value ","
+}
+
+function writeCommandAttribute(attribute) {
+  writeCommandField("is" attribute, "1")
+}
+
+function writeCommandEntry(name, symbol, value, help) {
+  if (help ~ /^deprecated /) return
+
+  print "{ // " symbol
+  writeCommandField("name", "\"" name "\"")
+  writeCommandField("code", value)
+
+  if (help ~ /^set .*\//) writeCommandAttribute("Toggle")
+  if (help ~ /^bring /) writeCommandAttribute("Routing")
+
+  if (help ~ /^go /) {
+    writeCommandAttribute("Motion")
+    if (help ~ / (up|down|top|bottom) /) writeCommandAttribute("Vertical")
+    if (help ~ / (left|right|beginning|end) /) writeCommandAttribute("Horizontal")
+    if (help ~ / (backward|forward) /) writeCommandAttribute("Panning")
+  }
+
+  if (symbol ~ /^BRL_BLK_/) {
+    if (symbol ~ /^BRL_BLK_PASS/) {
+      if (symbol ~ /PASS(CHAR|DOTS|KEY)/) {
+        writeCommandAttribute("Input")
+      }
+
+      if (symbol ~ /PASSCHAR/) {
+        writeCommandAttribute("Character")
+      }
+
+      if (symbol ~ /PASSDOTS/) {
+        writeCommandAttribute("Braille")
+      }
+
+      if (symbol ~ /PASS(XT|AT|PS2)/) {
+        writeCommandAttribute("Keyboard")
+      }
+    } else if (help ~ / character$/) {
+      writeCommandAttribute("Column")
+    } else if (help ~ / characters /) {
+      writeCommandAttribute("Range")
+    } else if (help ~ / line$/) {
+      writeCommandAttribute("Row")
+      writeCommandAttribute("Vertical")
+    } else {
+      writeCommandAttribute("Offset")
+    }
+  } else if (symbol ~ /^BRL_KEY_/) {
+    writeCommandAttribute("Input")
+
+    if (symbol ~ /_FUNCTION/) {
+      writeCommandAttribute("Offset")
+    }
+  }
+
+  print "  " makeTranslatorNote("This is the description of the " name " command.")
+  writeCommandField("description", "strtext(\"" help "\")")
+  print "},"
+  print ""
+}
+
+function brlCommand(name, symbol, value, help) {
+  writeCommandEntry(name, symbol, symbol, help)
+}
+
+function brlBlock(name, symbol, value, help) {
+  writeCommandEntry(name, symbol, "BRL_CMD_BLK(" name ")", help)
+}
+
+function brlKey(name, symbol, value, help) {
+  writeCommandEntry("KEY_" name, symbol, "BRL_CMD_KEY(" name ")", help)
+}
+
+function brlFlag(name, symbol, value, help) {
+}
+
+function brlDot(number, symbol, value, help) {
+}
diff --git a/Programs/config.c b/Programs/config.c
new file mode 100644
index 0000000..92edd21
--- /dev/null
+++ b/Programs/config.c
@@ -0,0 +1,3148 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <locale.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "parameters.h"
+#include "embed.h"
+#include "log.h"
+#include "report.h"
+#include "strfmt.h"
+#include "pgmprivs.h"
+#include "lock.h"
+#include "activity.h"
+#include "update.h"
+#include "cmd.h"
+#include "cmd_navigation.h"
+#include "brl.h"
+#include "brl_utils.h"
+#include "spk.h"
+#include "spk_input.h"
+#include "scr.h"
+#include "scr_special.h"
+#include "status.h"
+#include "blink.h"
+#include "variables.h"
+#include "datafile.h"
+#include "ttb.h"
+#include "atb.h"
+#include "ctb.h"
+#include "ktb.h"
+#include "ktb_keyboard.h"
+#include "kbd.h"
+#include "alert.h"
+#include "bell.h"
+#include "leds.h"
+#include "tune.h"
+#include "notes.h"
+#include "message.h"
+#include "file.h"
+#include "parse.h"
+#include "dynld.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "program.h"
+#include "messages.h"
+#include "revision.h"
+#include "service.h"
+#include "cmdline.h"
+#include "profile_types.h"
+#include "brl_input.h"
+#include "cmd_queue.h"
+#include "core.h"
+#include "api_control.h"
+#include "prefs.h"
+#include "utf8.h"
+
+#include "io_generic.h"
+#include "io_usb.h"
+#include "io_bluetooth.h"
+
+#ifdef __MINGW32__
+int isWindowsService = 0;
+#endif /* __MINGW32__ */
+
+#ifdef __MSDOS__
+#include "system_msdos.h"
+#endif /* __MSDOS__ */
+
+static void
+makeProgramBanner (char *buffer, size_t size, int includeRevision) {
+  const char *revision = includeRevision? getRevisionIdentifier(): "";
+  snprintf(buffer, size, "%s %s%s%s",
+           PACKAGE_NAME, PACKAGE_VERSION,
+           (*revision? " rev ": ""), revision);
+}
+
+static void
+logProgramBanner (void) {
+  char banner[0X100];
+  makeProgramBanner(banner, sizeof(banner), 1);
+
+  {
+    int pushed = pushLogPrefix("");
+    logMessage(LOG_NOTICE, "%s [%s]", banner, PACKAGE_URL);
+    if (pushed) popLogPrefix();
+  }
+}
+
+static void
+logProperty (const char *value, const char *variable, const char *label) {
+  if (value && *value) {
+    if (variable) setGlobalVariable(variable, value);
+  } else {
+    value = "none";
+  }
+
+  logMessage(LOG_INFO, "%s: %s", label, value);
+}
+
+static const char optionOperand_none[] = "no";
+static const char optionOperand_autodetect[] = "auto";
+static const char optionOperand_off[] = "off";
+
+static const char *const *const fallbackBrailleDrivers =
+  NULL_TERMINATED_STRING_ARRAY(
+    optionOperand_none
+  );
+
+static const char *const *const autodetectableBrailleDrivers_serial =
+  NULL_TERMINATED_STRING_ARRAY(
+    "md", "pm", "ts", "ht", "bn", "al", "bm", "pg", "sk"
+  );
+
+static const char *const *const autodetectableBrailleDrivers_USB =
+  NULL_TERMINATED_STRING_ARRAY(
+    "al", "bm", "bn", "cn", "dp", "eu", "fs", "hd", "hm", "ht", "hw", "ic", "mt", "pg", "pm", "sk", "vo"
+  );
+
+static const char *const *const autodetectableBrailleDrivers_Bluetooth =
+  NULL_TERMINATED_STRING_ARRAY(
+    "np", "ht", "al", "bm"
+  );
+
+#define SERVICE_NAME "BrlAPI"
+#define SERVICE_DESCRIPTION "Braille Devices API"
+
+static
+STR_BEGIN_FORMATTER(formatLogLevelString, unsigned int index)
+  switch (index) {
+    case 0:
+      STR_PRINTF("0-%u", logLevelCount-1);
+      break;
+
+    case 1: {
+      for (unsigned int level=0; level<logLevelCount; level+=1) {
+        if (level) STR_PRINTF(" ");
+        STR_PRINTF("%s", logLevelNames[level]);
+      }
+
+      break;
+    }
+
+    case 2: {
+      LogCategoryIndex category;
+
+      STR_PRINTF("%s", logCategoryName_all);
+
+      for (category=0; category<LOG_CATEGORY_COUNT; category+=1) {
+        const char *name = getLogCategoryName(category);
+
+        if (name && *name) {
+          STR_PRINTF(" %s", name);
+        }
+      }
+
+      break;
+    }
+
+    case 3:
+      STR_PRINTF("%c", logCategoryPrefix_disable);
+      break;
+
+    default:
+      break;
+  }
+STR_END_FORMATTER
+
+static const char *const *const screenContentQualityChoices =
+  NULL_TERMINATED_STRING_ARRAY(
+    "none", "low", "poor", "fair", "good", "high"
+  );
+
+STR_BEGIN_FORMATTER(formatScreenContentQualityChoices, unsigned int index)
+  switch (index) {
+    case 0: {
+      const char *const *choices = screenContentQualityChoices;
+      const char *const *choice = choices;
+
+      while (*choice) {
+        if (choice != choices) STR_PRINTF(" ");
+        STR_PRINTF("%s", *choice);
+        choice += 1;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+STR_END_FORMATTER
+
+static int opt_installService;
+static const char *const optionStrings_InstallService[] = {
+  SERVICE_NAME,
+  NULL
+};
+
+static int opt_removeService;
+static const char *const optionStrings_RemoveService[] = {
+  SERVICE_NAME,
+  NULL
+};
+
+static char *opt_startMessage;
+static char *opt_stopMessage;
+static char *opt_localeDirectory;
+
+static int opt_version;
+static int opt_verify;
+static int opt_quiet;
+static int opt_noDaemon;
+static int opt_standardError;
+static char *opt_logLevel;
+static char *opt_logFile;
+static int opt_bootParameters = 1;
+static int opt_environmentVariables;
+static char *opt_messageTime;
+
+static int opt_cancelExecution;
+static const char *const optionStrings_CancelExecution[] = {
+  PACKAGE_TARNAME,
+  NULL
+};
+
+static char *opt_promptPatterns;
+
+static int opt_stayPrivileged;
+static char *opt_privilegeParameters;
+
+static char *opt_pidFile;
+static char *opt_configurationFile;
+
+static char *opt_updatableDirectory;
+static char *opt_writableDirectory;
+char *opt_driversDirectory;
+
+char *opt_brailleDevice;
+static char **brailleDevices = NULL;
+static const char *brailleDevice = NULL;
+int opt_releaseDevice;
+
+static char *opt_brailleDriver;
+static char **brailleDrivers = NULL;
+static const BrailleDriver *brailleDriver = NULL;
+static void *brailleObject = NULL;
+static int brailleDriverConstructed;
+
+static char *opt_brailleParameters;
+static char *brailleParameters = NULL;
+static char **brailleDriverParameters = NULL;
+
+static char *opt_preferencesFile;
+static char *opt_overridePreferences;
+
+static char *oldPreferencesFile = NULL;
+static int oldPreferencesEnabled = 1;
+
+char *opt_tablesDirectory;
+char *opt_textTable;
+char *opt_contractionTable;
+char *opt_attributesTable;
+
+char *opt_keyboardTable;
+KeyTable *keyboardTable = NULL;
+static KeyboardMonitorObject *keyboardMonitor = NULL;
+
+static char *opt_keyboardProperties;
+static KeyboardProperties keyboardProperties;
+
+#ifdef ENABLE_API
+static int opt_noApi;
+static char *opt_apiParameters = NULL;
+static char **apiParameters = NULL;
+#endif /* ENABLE_API */
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static char *opt_speechDriver;
+static char **speechDrivers = NULL;
+static const SpeechDriver *speechDriver = NULL;
+static void *speechObject = NULL;
+
+static char *opt_speechParameters;
+static char *speechParameters = NULL;
+static char **speechDriverParameters = NULL;
+
+static char *opt_speechInput;
+static SpeechInputObject *speechInputObject;
+
+int opt_quietIfNoBraille;
+static char *opt_autospeakThreshold;
+unsigned int autospeakMinimumScreenContentQuality;
+
+static void
+setAutospeakThreshold (void) {
+  const char *choice = opt_autospeakThreshold;
+
+  int ok = validateChoice(
+    &autospeakMinimumScreenContentQuality,
+    choice, screenContentQualityChoices
+  );
+
+  if (!ok) {
+    logMessage(LOG_ERR, "%s: %s",
+      gettext("unknown screen content quality"),
+      choice
+    );
+  }
+
+  logProperty(
+    screenContentQualityChoices[autospeakMinimumScreenContentQuality],
+    "autospeakThreshold", "Autospeak Threshold"
+  );
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+static char *opt_screenDriver;
+static char **screenDrivers = NULL;
+static const ScreenDriver *screenDriver = NULL;
+static void *screenObject = NULL;
+static char *opt_screenParameters;
+static char *screenParameters = NULL;
+static char **screenDriverParameters = NULL;
+
+static const char *const optionStrings_TextTable[] = {
+  optionOperand_autodetect,
+  NULL
+};
+
+static const char *const optionStrings_BrailleDriver[] = {
+  optionOperand_autodetect,
+  optionOperand_none,
+  BRAILLE_DRIVER_CODES,
+  NULL
+};
+
+static const char *const optionStrings_ScreenDriver[] = {
+  optionOperand_autodetect,
+  optionOperand_none,
+  SCREEN_DRIVER_CODES,
+  NULL
+};
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static const char *const optionStrings_SpeechDriver[] = {
+  optionOperand_autodetect,
+  optionOperand_none,
+  SPEECH_DRIVER_CODES,
+  NULL
+};
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "version",
+    .letter = 'V',
+    .setting.flag = &opt_version,
+    .description = strtext("Log the versions of the core, API, and built-in drivers, and then exit.")
+  },
+
+  { .word = "environment-variables",
+    .letter = 'E',
+    .setting.flag = &opt_environmentVariables,
+    .description = strtext("Recognize environment variables.")
+  },
+
+  { .word = "configuration-file",
+    .letter = 'f',
+    .flags = OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_configurationFile,
+    .internal.setting = CONFIGURATION_DIRECTORY "/" CONFIGURATION_FILE,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to default settings file.")
+  },
+
+  { .word = "braille-driver",
+    .letter = 'b',
+    .bootParameter = 1,
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("driver,..."),
+    .setting.string = &opt_brailleDriver,
+    .internal.setting = optionOperand_autodetect,
+    .description = strtext("Braille driver code (%s, %s, or one of {%s})."),
+    .strings.array = optionStrings_BrailleDriver
+  },
+
+  { .word = "braille-parameters",
+    .letter = 'B',
+    .bootParameter = 4,
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_brailleParameters,
+    .internal.setting = BRAILLE_PARAMETERS,
+    .description = strtext("Parameters for the braille driver.")
+  },
+
+  { .word = "braille-device",
+    .letter = 'd',
+    .bootParameter = 2,
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("identifier,..."),
+    .setting.string = &opt_brailleDevice,
+    .internal.setting = BRAILLE_DEVICE,
+    .description = strtext("Device for accessing braille display.")
+  },
+
+  { .word = "release-device",
+    .letter = 'r',
+    .flags = OPT_Config | OPT_EnvVar,
+    .setting.flag = &opt_releaseDevice,
+#ifdef WINDOWS
+    .internal.setting = OPT_WORD_TRUE,
+#else /* WINDOWS */
+    .internal.setting = OPT_WORD_FALSE,
+#endif /* WINDOWS */
+    .description = strtext("Release braille device when screen or window is unreadable.")
+  },
+
+  { .word = "text-table",
+    .letter = 't',
+    .bootParameter = 3,
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_textTable,
+    .internal.setting = optionOperand_autodetect,
+    .description = strtext("Name of or path to text table (or %s)."),
+    .strings.array = optionStrings_TextTable
+  },
+
+  { .word = "contraction-table",
+    .letter = 'c',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_contractionTable,
+    .internal.setting = optionOperand_autodetect,
+    .description = strtext("Name of or path to contraction table.")
+  },
+
+  { .word = "attributes-table",
+    .letter = 'a',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_attributesTable,
+    .description = strtext("Name of or path to attributes table.")
+  },
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  { .word = "speech-driver",
+    .letter = 's',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("driver,..."),
+    .setting.string = &opt_speechDriver,
+    .internal.setting = optionOperand_autodetect,
+    .description = strtext("Speech driver code (%s, %s, or one of {%s})."),
+    .strings.array = optionStrings_SpeechDriver
+  },
+
+  { .word = "speech-parameters",
+    .letter = 'S',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_speechParameters,
+    .internal.setting = SPEECH_PARAMETERS,
+    .description = strtext("Parameters for the speech driver.")
+  },
+
+  { .word = "speech-input",
+    .letter = 'i',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_speechInput,
+    .description = strtext("Name of or path to speech input object.")
+  },
+
+  { .word = "quiet-if-no-braille",
+    .letter = 'Q',
+    .flags = OPT_Config | OPT_EnvVar,
+    .setting.flag = &opt_quietIfNoBraille,
+    .description = strtext("Do not autospeak when braille is not being used.")
+  },
+
+  { .word = "autospeak-threshold",
+    .flags = OPT_Config | OPT_EnvVar | OPT_Format,
+    .argument = strtext("quality"),
+    .setting.string = &opt_autospeakThreshold,
+    .description = strtext("Minimum screen content quality to autospeak (one of {%s})."),
+    .strings.format = formatScreenContentQualityChoices
+  },
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  { .word = "screen-driver",
+    .letter = 'x',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("driver,..."),
+    .setting.string = &opt_screenDriver,
+    .internal.setting = DEFAULT_SCREEN_DRIVER,
+    .description = strtext("Screen driver code (%s, %s, or one of {%s})."),
+    .strings.array = optionStrings_ScreenDriver
+  },
+
+  { .word = "screen-parameters",
+    .letter = 'X',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_screenParameters,
+    .internal.setting = SCREEN_PARAMETERS,
+    .description = strtext("Parameters for the screen driver.")
+  },
+
+  { .word = "keyboard-table",
+    .letter = 'k',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_keyboardTable,
+    .internal.setting = optionOperand_off,
+    .description = strtext("Name of or path to keyboard table.")
+  },
+
+  { .word = "keyboard-properties",
+    .letter = 'K',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_keyboardProperties,
+    .description = strtext("Properties of eligible keyboards.")
+  },
+
+  { .word = "preferences-file",
+    .letter = 'F',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_preferencesFile,
+    .internal.setting = PREFERENCES_FILE,
+    .description = strtext("Name of or path to default preferences file.")
+  },
+
+  { .word = "override-preferences",
+    .letter = 'o',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_overridePreferences,
+    .description = strtext("Explicit preference settings.")
+  },
+
+#ifdef ENABLE_API
+  { .word = "no-api",
+    .letter = 'N',
+    .flags = OPT_Config | OPT_EnvVar,
+    .setting.flag = &opt_noApi,
+    .description = strtext("Disable the application programming interface.")
+  },
+
+  { .word = "api-parameters",
+    .letter = 'A',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_apiParameters,
+    .internal.setting = API_PARAMETERS,
+    .description = strtext("Parameters for the application programming interface.")
+  },
+#endif /* ENABLE_API */
+
+  { .word = "quiet",
+    .letter = 'q',
+    .flags = OPT_Config | OPT_EnvVar,
+    .setting.flag = &opt_quiet,
+    .description = strtext("Suppress start-up messages.")
+  },
+
+  { .word = "log-level",
+    .letter = 'l',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar | OPT_Format,
+    .argument = strtext("lvl|cat,..."),
+    .setting.string = &opt_logLevel,
+    .description = strtext("Logging level (%s or one of {%s}) and/or log categories to enable (any combination of {%s}, each optionally prefixed by %s to disable)."),
+    .strings.format = formatLogLevelString
+  },
+
+  { .word = "log-file",
+    .letter = 'L',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_logFile,
+    .description = strtext("Path to log file.")
+  },
+
+  { .word = "standard-error",
+    .letter = 'e',
+    .setting.flag = &opt_standardError,
+    .description = strtext("Log to standard error rather than to the system log.")
+  },
+
+  { .word = "no-daemon",
+    .letter = 'n',
+    .setting.flag = &opt_noDaemon,
+    .description = strtext("Remain a foreground process.")
+  },
+
+  { .word = "stay-privileged",
+    .letter = 'z',
+    .flags = OPT_Config | OPT_EnvVar,
+    .setting.flag = &opt_stayPrivileged,
+    .description = strtext("Don't switch to an unprivileged user or relinquish any privileges (group memberships, capabilities, etc).")
+  },
+
+  { .word = "privilege-parameters",
+    .letter = 'Z',
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("name=value,..."),
+    .setting.string = &opt_privilegeParameters,
+    .internal.setting = PRIVILEGE_PARAMETERS,
+    .description = strtext("Parameters for the privilege establishment stage.")
+  },
+
+  { .word = "message-time",
+    .letter = 'M',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("csecs"),
+    .setting.string = &opt_messageTime,
+    .description = strtext("Message hold timeout (in 10ms units).")
+  },
+
+  { .word = "start-message",
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("text"),
+    .setting.string = &opt_startMessage,
+    .description = strtext("The text to be shown when the braille driver starts and to be spoken when the speech driver starts.")
+  },
+
+  { .word = "stop-message",
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("text"),
+    .setting.string = &opt_stopMessage,
+    .description = strtext("The text to be shown when the braille driver stops.")
+  },
+
+  { .word = "prompt-patterns",
+    .flags = OPT_Extend | OPT_Config | OPT_EnvVar,
+    .argument = strtext("regexp,..."),
+    .setting.string = &opt_promptPatterns,
+    .description = strtext("Patterns that match command prompts.")
+  },
+
+#ifdef HAVE_PCM_SUPPORT
+  { .word = "pcm-device",
+    .letter = 'p',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("device"),
+    .setting.string = &opt_pcmDevice,
+    .description = strtext("PCM (soundcard digital audio) device specifier.")
+  },
+#endif /* HAVE_PCM_SUPPORT */
+
+#ifdef HAVE_MIDI_SUPPORT
+  { .word = "midi-device",
+    .letter = 'm',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("device"),
+    .setting.string = &opt_midiDevice,
+    .description = strtext("MIDI (Musical Instrument Digital Interface) device specifier.")
+  },
+#endif /* HAVE_MIDI_SUPPORT */
+
+  { .word = "tables-directory",
+    .letter = 'T',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("directory"),
+    .setting.string = &opt_tablesDirectory,
+    .internal.setting = TABLES_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing tables.")
+  },
+
+  { .word = "drivers-directory",
+    .letter = 'D',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("directory"),
+    .setting.string = &opt_driversDirectory,
+    .internal.setting = DRIVERS_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory containing drivers.")
+  },
+
+  { .word = "updatable-directory",
+    .letter = 'U',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("directory"),
+    .setting.string = &opt_updatableDirectory,
+    .internal.setting = UPDATABLE_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory which contains files that can be updated.")
+  },
+
+  { .word = "writable-directory",
+    .letter = 'W',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("directory"),
+    .setting.string = &opt_writableDirectory,
+    .internal.setting = WRITABLE_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory which can be written to.")
+  },
+
+  { .word = "locale-directory",
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("directory"),
+    .setting.string = &opt_localeDirectory,
+    .internal.setting = LOCALE_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to directory which contains message localizations.")
+  },
+
+  { .word = "pid-file",
+    .letter = 'P',
+    .flags = OPT_Config | OPT_EnvVar,
+    .argument = strtext("file"),
+    .setting.string = &opt_pidFile,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("Path to process identifier file.")
+  },
+
+  { .word = "cancel-execution",
+    .letter = 'C',
+    .setting.flag = &opt_cancelExecution,
+    .description = strtext("Stop an existing instance of %s, and then exit."),
+    .strings.array = optionStrings_CancelExecution
+  },
+
+  { .word = "install-service",
+    .letter = 'I',
+    .setting.flag = &opt_installService,
+    .description = strtext("Install the %s service, and then exit."),
+    .strings.array = optionStrings_InstallService
+  },
+
+  { .word = "remove-service",
+    .letter = 'R',
+    .setting.flag = &opt_removeService,
+    .description = strtext("Remove the %s service, and then exit."),
+    .strings.array = optionStrings_RemoveService
+  },
+
+  { .word = "verify",
+    .letter = 'v',
+    .setting.flag = &opt_verify,
+    .description = strtext("Write the start-up logs, and then exit.")
+  },
+END_OPTION_TABLE(programOptions)
+
+int
+changeLogLevel (const char *operand) {
+  int ok = 1;
+  char **strings = splitString(operand, ',', NULL);
+
+  if (strings) {
+    char **string = strings;
+
+    while (*string) {
+      unsigned int level;
+
+      if (isLogLevel(&level, *string)) {
+        systemLogLevel = level;
+      } else if (!setLogCategory(*string)) {
+        logMessage(LOG_ERR, "%s: %s", gettext("unknown log level or category"), *string);
+        ok = 0;
+      }
+
+      string += 1;
+    }
+
+    deallocateStrings(strings);
+  }
+
+  return ok;
+}
+
+int
+changeLogCategories (const char *operand) {
+  disableAllLogCategories();
+  return changeLogLevel(operand);
+}
+
+static void
+exitLog (void *data) {
+  closeSystemLog();
+  closeLogFile();
+}
+
+static void
+setLogLevels (void) {
+  systemLogLevel = LOG_NOTICE;
+  disableAllLogCategories();
+  changeLogLevel(opt_logLevel);
+
+  {
+    unsigned char level;
+
+    if (opt_standardError) {
+      level = systemLogLevel;
+    } else {
+      level = LOG_NOTICE;
+      if (opt_version || opt_verify) level += 1;
+      if (opt_quiet) level -= 1;
+    }
+
+    stderrLogLevel = level;
+  }
+}
+
+static void
+establishPrivileges (void) {
+  const char *platform = getPrivilegeParametersPlatform();
+  const char *const *names = getPrivilegeParameterNames();
+  char **parameters = getParameters(names, platform, opt_privilegeParameters);
+
+  if (parameters) {
+    logParameters(names, parameters, "Privilege Parameter");
+    establishProgramPrivileges(parameters, opt_stayPrivileged);
+    deallocateStrings(parameters);
+  }
+}
+
+ProgramExitStatus
+brlttyPrepare (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+
+      .applicationName = "brltty",
+      .configurationFile = &opt_configurationFile,
+      .doEnvironmentVariables = &opt_environmentVariables,
+      .doBootParameters = &opt_bootParameters,
+
+      .usage = {
+        .purpose = strtext("Screen reader for those who use a braille device."),
+      }
+    };
+
+    ProgramExitStatus exitStatus = processOptions(&descriptor, &argc, &argv);
+
+    switch (exitStatus) {
+      case PROG_EXIT_SYNTAX:
+      case PROG_EXIT_SUCCESS:
+        break;
+
+      default:
+        return exitStatus;
+    }
+  }
+
+  if (argc) {
+    logMessage(LOG_ERR, "%s: %s", gettext("excess argument"), argv[0]);
+  }
+
+  setMessagesDirectory(opt_localeDirectory);
+  setUpdatableDirectory(opt_updatableDirectory);
+  setWritableDirectory(opt_writableDirectory);
+
+  setLogLevels();
+  onProgramExit("log", exitLog, NULL);
+
+  {
+    const char *logFile;
+
+    if (*opt_logFile) {
+      logFile = opt_logFile;
+      openLogFile(logFile);
+    } else {
+      logFile = "<system>";
+      openSystemLog();
+    }
+
+    logProgramBanner();
+    logProperty(logFile, "logFile", "Log File");
+    logProperty(opt_logLevel, "logLevel", "Log Level");
+  }
+
+  logProperty(getMessagesLocale(), "messagesLocale", "Messages Locale");
+  logProperty(getMessagesDomain(), "messagesDomain", "Messages Domain");
+  logProperty(getMessagesDirectory(), "messagesDirectory", "Messages Directory");
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  setAutospeakThreshold();
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  establishPrivileges();
+  return PROG_EXIT_SUCCESS;
+}
+
+static int
+setTextTable (const char *name) {
+  if (!name) name = "";
+  if (!replaceTextTable(opt_tablesDirectory, name)) return 0;
+
+  if (!*name) name = TEXT_TABLE;
+  changeStringSetting(&opt_textTable, name);
+
+  api.updateParameter(BRLAPI_PARAM_COMPUTER_BRAILLE_TABLE, 0);
+  return 1;
+}
+
+static int
+setTextTableForLocale (void) {
+  changeStringSetting(&opt_textTable, "");
+  char *name = getTextTableForLocale(opt_tablesDirectory);
+
+  if (name) {
+    logMessage(LOG_DEBUG, "using autoselected text table: %s", name);
+    int ok = setTextTable(name);
+    free(name);
+    if (ok) return 1;
+  }
+
+  return 0;
+}
+
+int
+changeTextTable (const char *name) {
+  if (strcmp(name, optionOperand_autodetect) == 0) {
+    return setTextTableForLocale();
+  }
+
+  return setTextTable(name);
+}
+
+static void
+exitTextTable (void *data) {
+  setTextTable(NULL);
+}
+
+static int
+setContractionTable (const char *name) {
+  if (!name) name = "";
+  if (!replaceContractionTable(opt_tablesDirectory, name)) return 0;
+
+  if (!*name) name = CONTRACTION_TABLE;
+  changeStringSetting(&opt_contractionTable, name);
+
+  api.updateParameter(BRLAPI_PARAM_LITERARY_BRAILLE_TABLE, 0);
+  return 1;
+}
+
+static int
+setContractionTableForLocale (void) {
+  changeStringSetting(&opt_contractionTable, "");
+  char *name = getContractionTableForLocale(opt_tablesDirectory);
+
+  if (name) {
+    logMessage(LOG_DEBUG, "using autoselected contraction table: %s", name);
+    int ok = setContractionTable(name);
+    free(name);
+    if (ok) return 1;
+  }
+
+  return 0;
+}
+
+int
+changeContractionTable (const char *name) {
+  if (strcmp(name, optionOperand_autodetect) == 0) {
+    return setContractionTableForLocale();
+  }
+
+  return setContractionTable(name);
+}
+
+static void
+exitContractionTable (void *data) {
+  setContractionTable(NULL);
+}
+
+static void
+setTextAndContractionTables (void) {
+  int usingInternalTextTable = 0;
+
+  if (*opt_textTable) {
+    if (strcmp(opt_textTable, optionOperand_autodetect) == 0) {
+      setTextTableForLocale();
+    } else if (!setTextTable(opt_textTable)) {
+      changeStringSetting(&opt_textTable, "");
+    }
+  }
+
+  if (!*opt_textTable) {
+    logMessage(LOG_DEBUG, "using internal text table: %s", TEXT_TABLE);
+    changeStringSetting(&opt_textTable, TEXT_TABLE);
+    usingInternalTextTable = 1;
+  }
+
+  logProperty(opt_textTable, "textTable", "Text Table");
+  onProgramExit("text-table", exitTextTable, NULL);
+
+  if (*opt_contractionTable) {
+    if (strcmp(opt_contractionTable, optionOperand_autodetect) == 0) {
+      if (setContractionTableForLocale()) {
+        if (usingInternalTextTable) {
+          if (!isContractedBraille()) {
+            setContractedBraille(1);
+            logMessage(LOG_DEBUG, "contracted braille has been enabled");
+          }
+        }
+      }
+    } else if (!setContractionTable(opt_contractionTable)) {
+      changeStringSetting(&opt_contractionTable, "");
+    }
+  }
+
+  if (!*opt_contractionTable) {
+    if (setContractionTable(NULL)) {
+      logMessage(LOG_DEBUG, "using internal contraction table: %s", CONTRACTION_TABLE);
+    }
+  }
+
+  logProperty(opt_contractionTable, "contractionTable", "Contraction Table");
+  onProgramExit("contraction-table", exitContractionTable, NULL);
+}
+
+int
+changeAttributesTable (const char *name) {
+  if (!name) name = "";
+  if (!replaceAttributesTable(opt_tablesDirectory, name)) return 0;
+
+  if (!*name) name = ATTRIBUTES_TABLE;
+  changeStringSetting(&opt_attributesTable, name);
+
+  return 1;
+}
+
+static void
+exitAttributesTable (void *data) {
+  changeAttributesTable(NULL);
+}
+
+static void
+setAttributesTable (void) {
+  if (*opt_attributesTable) {
+    if (!changeAttributesTable(opt_attributesTable)) {
+      changeStringSetting(&opt_attributesTable, "");
+    }
+  }
+
+  if (!*opt_attributesTable) {
+    changeStringSetting(&opt_attributesTable, ATTRIBUTES_TABLE);
+  }
+
+  logProperty(opt_attributesTable, "attributesTable", "Attributes Table");
+  onProgramExit("attributes-table", exitAttributesTable, NULL);
+}
+
+static KeyTableState
+handleKeyboardEvent (KeyGroup group, KeyNumber number, int press) {
+  if (keyboardTable) {
+    if (!scr.unreadable) {
+      return processKeyEvent(keyboardTable, getCurrentCommandContext(), group, number, press);
+    }
+
+    resetKeyTable(keyboardTable);
+  }
+
+  return KTS_UNBOUND;
+}
+
+static int
+startKeyboardMonitor (void) {
+  return !!(keyboardMonitor = newKeyboardMonitorObject(&keyboardProperties, handleKeyboardEvent));
+}
+
+static void
+stopKeyboardMonitor (void) {
+  if (keyboardMonitor) {
+    destroyKeyboardMonitorObject(keyboardMonitor);
+    keyboardMonitor = NULL;
+  }
+}
+
+static int
+prepareKeyboardMonitorActivity (void *data) {
+  return 1;
+}
+
+static int
+startKeyboardMonitorActivity (void *data) {
+  return startKeyboardMonitor();
+}
+
+static void
+stopKeyboardMonitorActivity (void *data) {
+  stopKeyboardMonitor();
+}
+
+static const ActivityMethods keyboardMonitorActivityMethods = {
+  .activityName = "keyboard-monitor",
+  .retryInterval = KEYBOARD_MONITOR_START_RETRY_INTERVAL,
+
+  .prepare = prepareKeyboardMonitorActivity,
+  .start = startKeyboardMonitorActivity,
+  .stop = stopKeyboardMonitorActivity
+};
+
+static ActivityObject *keyboardMonitorActivity = NULL;
+
+static void
+exitKeyboardMonitor (void *data) {
+  if (keyboardMonitorActivity) {
+    destroyActivity(keyboardMonitorActivity);
+    keyboardMonitorActivity = NULL;
+  }
+}
+
+static ActivityObject *
+getKeyboardMonitorActivity (int allocate) {
+  if (!keyboardMonitorActivity) {
+    if (allocate) {
+      if (!(keyboardMonitorActivity = newActivity(&keyboardMonitorActivityMethods, NULL))) {
+        return NULL;
+      }
+
+      onProgramExit("keyboard-monitor", exitKeyboardMonitor, NULL);
+    }
+  }
+
+  return keyboardMonitorActivity;
+}
+
+static void
+enableKeyboardMonitor (void) {
+  ActivityObject *activity = getKeyboardMonitorActivity(1);
+  if (activity) startActivity(activity);
+}
+
+static void
+disableKeyboardMonitor (void) {
+  ActivityObject *activity = getKeyboardMonitorActivity(0);
+  if (activity) stopActivity(activity);
+}
+
+static unsigned int brailleHelpPageNumber = 0;
+static unsigned int keyboardHelpPageNumber = 0;
+
+static int
+enableHelpPage (unsigned int *pageNumber) {
+  if (!*pageNumber) {
+    if (!constructHelpScreen()) return 0;
+    if (!(*pageNumber = addHelpPage())) return 0;
+  }
+
+  return setHelpPageNumber(*pageNumber);
+}
+
+static int
+enableBrailleHelpPage (void) {
+  return enableHelpPage(&brailleHelpPageNumber);
+}
+
+static int
+enableKeyboardHelpPage (void) {
+  return enableHelpPage(&keyboardHelpPageNumber);
+}
+
+static void
+disableHelpPage (unsigned int pageNumber) {
+  if (pageNumber) {
+    if (setHelpPageNumber(pageNumber)) {
+      clearHelpPage();
+    }
+  }
+}
+
+static void
+disableBrailleHelpPage (void) {
+  disableHelpPage(brailleHelpPageNumber);
+}
+
+static void
+disableKeyboardHelpPage (void) {
+  disableHelpPage(keyboardHelpPageNumber);
+}
+
+static int
+handleWcharHelpLine (const wchar_t *line, void *data UNUSED) {
+  return addHelpLine(line);
+}
+
+static int
+handleUtf8HelpLine (const LineHandlerParameters *parameters) {
+  const char *utf8 = parameters->line.text;
+  size_t size = parameters->line.length + 1;
+  wchar_t characters[size];
+  wchar_t *character = characters;
+
+  convertUtf8ToWchars(&utf8, &character, size);
+  return handleWcharHelpLine(characters, parameters->data);
+}
+
+static int
+loadHelpFile (const char *file) {
+  int loaded = 0;
+  FILE *stream;
+
+  if ((stream = openDataFile(file, "r", 0))) {
+    if (processLines(stream, handleUtf8HelpLine, NULL)) loaded = 1;
+
+    fclose(stream);
+  }
+
+  return loaded;
+}
+
+static char *
+makeBrailleKeyTablePath (void) {
+  return makeInputTablePath(opt_tablesDirectory, braille->definition.code, brl.keyBindings);
+}
+
+static void
+makeBrailleHelpPage (const char *keyTablePath) {
+  if (enableBrailleHelpPage()) {
+    if (brl.keyTable) {
+      listKeyTable(brl.keyTable, NULL, handleWcharHelpLine, NULL);
+    } else {
+      char *keyHelpPath = replaceFileExtension(keyTablePath, KEY_HELP_EXTENSION);
+
+      if (keyHelpPath) {
+        if (loadHelpFile(keyHelpPath)) {
+          logMessage(LOG_INFO, "%s: %s", gettext("Key Help"), keyHelpPath);
+        } else {
+          logMessage(LOG_WARNING, "%s: %s", gettext("cannot open key help"), keyHelpPath);
+        }
+
+        free(keyHelpPath);
+      }
+    }
+
+    if (!getHelpLineCount()) {
+      addHelpLine(WS_C("help not available"));
+      message(NULL, gettext("no key bindings"), 0);
+    }
+  }
+}
+
+static void
+makeKeyboardHelpPage (void) {
+  if (enableKeyboardHelpPage()) {
+    listKeyTable(keyboardTable, NULL, handleWcharHelpLine, NULL);
+  }
+}
+
+static void
+exitKeyboardTable (void *data) {
+  if (keyboardTable) {
+    destroyKeyTable(keyboardTable);
+    keyboardTable = NULL;
+  }
+
+  disableKeyboardHelpPage();
+}
+
+int
+changeKeyboardTable (const char *name) {
+  KeyTable *table = NULL;
+
+  if (!*name) name = "";
+  if (strcmp(name, optionOperand_off) == 0) name = "";
+
+  if (*name) {
+    char *path = makeKeyboardTablePath(opt_tablesDirectory, name);
+
+    if (path) {
+      logMessage(LOG_DEBUG, "compiling keyboard table: %s", path);
+
+      if (!(table = compileKeyTable(path, KEY_NAME_TABLES(keyboard)))) {
+        logMessage(LOG_ERR, "%s: %s", gettext("cannot compile keyboard table"), path);
+      }
+
+      free(path);
+    }
+
+    if (!table) return 0;
+  }
+
+  if (keyboardTable) {
+    disableKeyboardHelpPage();
+    disableKeyboardMonitor();
+
+    destroyKeyTable(keyboardTable);
+    keyboardTable = NULL;
+  }
+
+  if (table) {
+    setKeyTableLogLabel(table, "kbd");
+    setLogKeyEventsFlag(table, &LOG_CATEGORY_FLAG(KEYBOARD_KEYS));
+
+    keyboardTable = table;
+    enableKeyboardMonitor();
+    makeKeyboardHelpPage();
+  }
+
+  if (!*name) name = optionOperand_off;
+  logMessage(LOG_DEBUG, "keyboard table changed: %s -> %s", opt_keyboardTable, name);
+
+  changeStringSetting(&opt_keyboardTable, name);
+  return 1;
+}
+
+static void
+setKeyboardTable (void) {
+  parseKeyboardProperties(&keyboardProperties, opt_keyboardProperties);
+  onProgramExit("keyboard-table", exitKeyboardTable, NULL);
+  changeKeyboardTable(opt_keyboardTable);
+  logProperty(opt_keyboardTable, "keyboardTable", "Keyboard Table");
+}
+
+int
+haveStatusCells (void) {
+  return brl.statusColumns > 0;
+}
+
+static void
+brailleWindowReconfigured (unsigned int rows, unsigned int columns) {
+  textStart = 0;
+  textCount = columns;
+  statusStart = 0;
+  statusCount = 0;
+
+  if (!(textMaximized || haveStatusCells())) {
+    unsigned int separatorWidth = (prefs.statusSeparator == ssNone)? 0: 1;
+    unsigned int reserved = 1 + separatorWidth;
+
+    if (brl.textColumns > reserved) {
+      unsigned int statusWidth = prefs.statusCount;
+
+      if (!statusWidth) statusWidth = getStatusFieldsLength(prefs.statusFields);
+      statusWidth = MIN(statusWidth, brl.textColumns-reserved);
+
+      if (statusWidth > 0) {
+        switch (prefs.statusPosition) {
+          case spLeft:
+            statusStart = 0;
+            statusCount = statusWidth;
+            textStart = statusCount + separatorWidth;
+            textCount = columns - textStart;
+            break;
+
+          case spRight:
+            statusCount = statusWidth;
+            statusStart = columns - statusCount;
+            textCount = statusStart - separatorWidth;
+            textStart = 0;
+            break;
+        }
+      }
+    }
+  }
+
+  logMessage(LOG_DEBUG,
+    "regions: text=%u.%u status=%u.%u",
+    textStart, textCount,
+    statusStart, statusCount
+  );
+
+  fullWindowShift = MAX(textCount-prefs.brailleWindowOverlap, 1);
+  halfWindowShift = textCount / 2;
+  verticalWindowShift = (rows > 1)? rows: 5;
+
+  logMessage(LOG_DEBUG,
+    "shifts: full=%u half=%u vertical=%u",
+    fullWindowShift, halfWindowShift, verticalWindowShift
+  );
+}
+
+void
+reconfigureBrailleWindow (void) {
+  brailleWindowReconfigured(brl.textRows, brl.textColumns);
+}
+
+static void
+applyBraillePreferences (void) {
+  reconfigureBrailleWindow();
+
+  setBrailleFirmness(&brl, prefs.brailleFirmness);
+  setTouchSensitivity(&brl, prefs.touchSensitivity);
+
+  setAutorepeatProperties(&brl, prefs.autorepeatEnabled,
+                          PREFS2MSECS(prefs.longPressTime),
+                          PREFS2MSECS(prefs.autorepeatInterval));
+
+  if (brl.keyTable) {
+    setKeyAutoreleaseTime(brl.keyTable, prefs.autoreleaseTime);
+  }
+}
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static void
+applySpeechPreferences (void) {
+  setSpeechVolume(&spk, prefs.speechVolume, 0);
+  setSpeechRate(&spk, prefs.speechRate, 0);
+  setSpeechPitch(&spk, prefs.speechPitch, 0);
+  setSpeechPunctuation(&spk, prefs.speechPunctuation, 0);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+static void
+applyAllPreferences (void) {
+  setConsoleBellMonitoring(prefs.consoleBellAlert);
+  setLedMonitoring(prefs.keyboardLedAlerts);
+  tuneSetDevice(prefs.tuneDevice);
+  applyBraillePreferences();
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  applySpeechPreferences();
+#endif /* ENABLE_SPEECH_SUPPORT */
+}
+
+void
+setPreferences (const PreferenceSettings *newPreferences) {
+  prefs = *newPreferences;
+  applyAllPreferences();
+}
+
+static void
+ensureStatusFields (void) {
+  const unsigned char *fields = braille->statusFields;
+  unsigned int count = brl.statusColumns * brl.statusRows;
+
+  if (!fields && count) {
+    static const unsigned char fields1[] = {
+      sfWindowRow, sfEnd
+    };
+
+    static const unsigned char fields2[] = {
+      sfWindowRow, sfCursorRow, sfEnd
+    };
+
+    static const unsigned char fields3[] = {
+      sfWindowRow, sfCursorRow, sfCursorColumn, sfEnd
+    };
+
+    static const unsigned char fields4[] = {
+      sfWindowCoordinates2, sfCursorCoordinates2, sfEnd
+    };
+
+    static const unsigned char fields5[] = {
+      sfWindowCoordinates2, sfCursorCoordinates2, sfStateDots, sfEnd
+    };
+
+    static const unsigned char fields6[] = {
+      sfWindowCoordinates2, sfCursorCoordinates2, sfStateDots, sfScreenNumber,
+      sfEnd
+    };
+
+    static const unsigned char fields7[] = {
+      sfWindowCoordinates2, sfCursorCoordinates2, sfStateDots, sfTime,
+      sfEnd
+    };
+
+    static const unsigned char *const fieldsTable[] = {
+      fields1, fields2, fields3, fields4, fields5, fields6, fields7
+    };
+
+    static const unsigned char fieldsCount = ARRAY_COUNT(fieldsTable);
+    if (count > fieldsCount) count = fieldsCount;
+    fields = fieldsTable[count - 1];
+  }
+
+  setStatusFields(fields);
+}
+
+static void
+setPreferenceOverrides (void) {
+  int count;
+  char **settings = splitString(opt_overridePreferences, PARAMETER_SEPARATOR_CHARACTER, &count);
+
+  if (settings) {
+    char **setting = settings;
+    char **end = setting + count;
+
+    while (setting < end) {
+      setPreference(*setting);
+      setting += 1;
+    }
+
+    deallocateStrings(settings);
+  }
+}
+
+static void
+finishPreferencesLoad (void) {
+  setPreferenceOverrides();
+  applyAllPreferences();
+}
+
+int
+loadPreferences (int reset) {
+  int ok = 0;
+  int found = 0;
+
+  if (reset) {
+    resetPreferences();
+  } else {
+    {
+      char *path = makePreferencesFilePath(opt_preferencesFile);
+
+      if (path) {
+        if (testFilePath(path)) {
+          found = 1;
+          if (loadPreferencesFile(path)) ok = 1;
+          oldPreferencesEnabled = 0;
+        } else {
+          logMessage(LOG_DEBUG, "preferences file not found: %s", path);
+        }
+
+        free(path);
+      }
+    }
+
+    if (oldPreferencesEnabled) {
+      const char *path = oldPreferencesFile;
+
+      if (path) {
+        if (testFilePath(path)) {
+          found = 1;
+          if (loadPreferencesFile(path)) ok = 1;
+        } else {
+          logMessage(LOG_DEBUG, "old preferences file not found: %s", path);
+        }
+      }
+    }
+  }
+
+  if (!found) {
+    char *path = makePath(opt_tablesDirectory, "default.prefs");
+
+    if (path) {
+      if (loadPreferencesFile(path)) ok = 1;
+      free(path);
+    }
+  }
+
+  finishPreferencesLoad();
+  return ok;
+}
+
+int 
+savePreferences (void) {
+  int ok = 0;
+  char *path = makePreferencesFilePath(opt_preferencesFile);
+
+  if (path) {
+    if (savePreferencesFile(path)) {
+      ok = 1;
+      oldPreferencesEnabled = 0;
+    }
+
+    free(path);
+  }
+
+  return ok;
+}
+
+#ifdef ENABLE_API
+static void
+exitApiServer (void *data) {
+  if (api.isServerLinked()) api.unlinkServer();
+  if (api.isServerRunning()) api.stopServer();
+
+  if (apiParameters) {
+    deallocateStrings(apiParameters);
+    apiParameters = NULL;
+  }
+}
+#endif /* ENABLE_API */
+
+static void
+startApiServer (void) {
+#ifdef ENABLE_API
+  if (!(opt_noApi || api.isServerRunning())) {
+    const char *const *parameters = api.getServerParameters();
+
+    apiParameters = getParameters(parameters,
+                                  NULL,
+                                  opt_apiParameters);
+
+    if (apiParameters) {
+      api.logServerIdentity(0);
+      logParameters(parameters, apiParameters, "API Parameter");
+
+      if (!opt_verify) {
+        if (api.startServer(apiParameters)) {
+          onProgramExit("api-server", exitApiServer, NULL);
+        }
+      }
+    }
+  }
+#endif /* ENABLE_API */
+}
+
+typedef struct {
+  const char *driverType;
+  const char *const *requestedDrivers;
+  const char *const *autodetectableDrivers;
+  const char * (*getDefaultDriver) (void);
+  int (*haveDriver) (const char *code);
+  int (*initializeDriver) (const char *code, int verify);
+} DriverActivationData;
+
+static int
+activateDriver (const DriverActivationData *data, int verify) {
+  int oneDriver = data->requestedDrivers[0] && !data->requestedDrivers[1];
+  int autodetect = oneDriver && (strcmp(data->requestedDrivers[0], optionOperand_autodetect) == 0);
+  const char *const defaultDrivers[] = {data->getDefaultDriver(), NULL};
+  const char *const *driver;
+
+  if (!oneDriver || autodetect) verify = 0;
+
+  if (!autodetect) {
+    driver = data->requestedDrivers;
+  } else if (defaultDrivers[0]) {
+    driver = defaultDrivers;
+  } else if (*(driver = data->autodetectableDrivers)) {
+    logMessage(LOG_DEBUG, "performing %s driver autodetection", data->driverType);
+  } else {
+    logMessage(LOG_DEBUG, "no autodetectable %s drivers", data->driverType);
+  }
+
+  if (!*driver) {
+    driver = fallbackBrailleDrivers;
+    autodetect = 0;
+  }
+
+  while (*driver) {
+    if (!autodetect || data->haveDriver(*driver)) {
+      logMessage(LOG_DEBUG, "checking for %s driver: %s", data->driverType, *driver);
+      if (data->initializeDriver(*driver, verify)) return 1;
+    }
+
+    ++driver;
+  }
+
+  logMessage(LOG_DEBUG, "%s driver not found", data->driverType);
+  return 0;
+}
+
+static void
+unloadDriverObject (void **object) {
+#ifdef ENABLE_SHARED_OBJECTS
+  if (*object) {
+    unloadSharedObject(*object);
+    *object = NULL;
+  }
+#endif /* ENABLE_SHARED_OBJECTS */
+}
+
+void
+forgetDevices (void) {
+  usbForgetDevices();
+  bthForgetDevices();
+}
+
+static void
+initializeBrailleDisplay (void) {
+  constructBrailleDisplay(&brl);
+  brl.bufferResized = &brailleWindowReconfigured;
+}
+
+static LockDescriptor *
+getBrailleDriverLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "braille-driver");
+}
+
+void
+lockBrailleDriver (void) {
+  obtainExclusiveLock(getBrailleDriverLock());
+}
+
+void
+unlockBrailleDriver (void) {
+  releaseLock(getBrailleDriverLock());
+}
+
+int
+isBrailleDriverConstructed (void) {
+  return brailleDriverConstructed;
+}
+
+static void
+setBrailleDriverConstructed (int yes) {
+  lockBrailleDriver();
+  brailleDriverConstructed = yes;
+  unlockBrailleDriver();
+
+  if (brailleDriverConstructed) {
+    announceBrailleOnline();
+  } else {
+    announceBrailleOffline();
+  }
+
+  static const brlapi_param_t parameters[] = {
+    BRLAPI_PARAM_DRIVER_CODE,
+    BRLAPI_PARAM_DRIVER_NAME,
+    BRLAPI_PARAM_DRIVER_VERSION,
+    BRLAPI_PARAM_DEVICE_MODEL,
+    BRLAPI_PARAM_DEVICE_CELL_SIZE,
+    BRLAPI_PARAM_DISPLAY_SIZE,
+    BRLAPI_PARAM_DEVICE_IDENTIFIER,
+    BRLAPI_PARAM_DEVICE_SPEED,
+    BRLAPI_PARAM_DEVICE_KEY_CODES,
+    BRLAPI_PARAM_BOUND_COMMAND_CODES,
+  };
+
+  const brlapi_param_t *parameter = parameters;
+  const brlapi_param_t *end = parameter + ARRAY_COUNT(parameters);
+
+  while (parameter < end) {
+    api.updateParameter(*parameter++, 0);
+  }
+}
+
+int
+constructBrailleDriver (void) {
+  initializeBrailleDisplay();
+
+  if (braille->construct(&brl, brailleDriverParameters, brailleDevice)) {
+    if (ensureBrailleBuffer(&brl, LOG_INFO)) {
+      if (brl.keyBindings) {
+        char *keyTablePath = makeBrailleKeyTablePath();
+
+        logMessage(LOG_INFO, "%s: %s", gettext("Key Bindings"), brl.keyBindings);
+
+        if (keyTablePath) {
+          if (brl.keyNames) {
+            if ((brl.keyTable = compileKeyTable(keyTablePath, brl.keyNames))) {
+              logMessage(LOG_INFO, "%s: %s", gettext("Key Table"), keyTablePath);
+
+              setKeyTableLogLabel(brl.keyTable, "brl");
+              setLogKeyEventsFlag(brl.keyTable, &LOG_CATEGORY_FLAG(BRAILLE_KEYS));
+              setKeyboardEnabledFlag(brl.keyTable, &prefs.brailleKeyboardEnabled);
+            } else {
+              logMessage(LOG_WARNING, "%s: %s", gettext("cannot compile key table"), keyTablePath);
+            }
+          }
+
+          if (haveBrailleDisplay()) makeBrailleHelpPage(keyTablePath);
+          free(keyTablePath);
+        }
+      }
+
+      setBrailleDriverConstructed(1);
+      startBrailleInput();
+      return 1;
+    }
+
+    braille->destruct(&brl);
+  } else {
+    logMessage(LOG_DEBUG, "%s: %s -> %s",
+               "braille driver initialization failed",
+               braille->definition.code, brailleDevice);
+  }
+
+  return 0;
+}
+
+void
+destructBrailleDriver (void) {
+  stopBrailleInput();
+  drainBrailleOutput(&brl, 0);
+
+  setBrailleDriverConstructed(0);
+  braille->destruct(&brl);
+
+  disableBrailleHelpPage();
+  destructBrailleDisplay(&brl);
+}
+
+int
+isBrailleOnline (void) {
+  return isBrailleDriverConstructed() && !brl.isOffline;
+}
+
+static int
+initializeBrailleDriver (const char *code, int verify) {
+  if ((braille = loadBrailleDriver(code, &brailleObject, opt_driversDirectory))) {
+    brailleDriverParameters = getParameters(braille->parameters,
+                                            braille->definition.code,
+                                            brailleParameters);
+
+    if (brailleDriverParameters) {
+      int constructed = verify;
+
+      if (!constructed) {
+        logMessage(LOG_DEBUG, "initializing braille driver: %s -> %s",
+                   braille->definition.code, brailleDevice);
+
+        if (constructBrailleDriver()) {
+          brailleDriver = braille;
+          constructed = 1;
+        }
+      }
+
+      if (constructed) {
+        identifyBrailleDriver(braille, 0);
+        logMessage(LOG_INFO, "%s: %s", gettext("Braille Device"), brailleDevice);
+
+        logParameters(
+          braille->parameters,
+          brailleDriverParameters,
+          "Braille Parameter"
+        );
+
+        {
+          const char *strings[] = {
+            CONFIGURATION_DIRECTORY, "/",
+            PACKAGE_TARNAME, "-",
+            braille->definition.code, ".prefs"
+          };
+
+          oldPreferencesFile = joinStrings(strings, ARRAY_COUNT(strings));
+        }
+
+        if (oldPreferencesFile) {
+          logMessage(LOG_INFO, "%s: %s", gettext("Old Preferences File"), oldPreferencesFile);
+
+          api.linkServer();
+
+          return 1;
+        } else {
+          logMallocError();
+        }
+      }
+
+      deallocateStrings(brailleDriverParameters);
+      brailleDriverParameters = NULL;
+    }
+
+    unloadDriverObject(&brailleObject);
+  } else {
+    logMessage(LOG_ERR, "%s: %s", gettext("braille driver not loadable"), code);
+  }
+
+  braille = &noBraille;
+  return 0;
+}
+
+static int
+activateBrailleDriver (int verify) {
+  int oneDevice = brailleDevices[0] && !brailleDevices[1];
+  const char *const *device = (const char *const *)brailleDevices;
+
+  if (!oneDevice) verify = 0;
+
+  while (*device) {
+    const char *const *autodetectableDrivers = NULL;
+
+    brailleDevice = *device;
+    logMessage(LOG_DEBUG, "checking braille device: %s", brailleDevice);
+
+    {
+      const char *dev = brailleDevice;
+      const GioPublicProperties *properties = gioGetPublicProperties(&dev);
+
+      if (properties) {
+        logMessage(LOG_DEBUG, "braille device type: %s", properties->type.name);
+
+        switch (properties->type.identifier) {
+          case GIO_TYPE_SERIAL: {
+            autodetectableDrivers = autodetectableBrailleDrivers_serial;
+            break;
+          }
+
+          case GIO_TYPE_USB: {
+            autodetectableDrivers = autodetectableBrailleDrivers_USB;
+            break;
+          }
+
+          case GIO_TYPE_BLUETOOTH: {
+            if (!(autodetectableDrivers = bthGetDriverCodes(dev, BLUETOOTH_DEVICE_NAME_OBTAIN_TIMEOUT))) {
+              autodetectableDrivers = autodetectableBrailleDrivers_Bluetooth;
+            }
+
+            break;
+          }
+
+          default:
+            break;
+        }
+      } else {
+        logMessage(LOG_DEBUG, "unrecognized braille device type");
+      }
+    }
+
+    if (!autodetectableDrivers) {
+      static const char *noDrivers[] = {NULL};
+      autodetectableDrivers = noDrivers;
+    }
+
+    {
+      const DriverActivationData data = {
+        .driverType = "braille",
+        .requestedDrivers = (const char *const *)brailleDrivers,
+        .autodetectableDrivers = autodetectableDrivers,
+        .getDefaultDriver = getDefaultBrailleDriver,
+        .haveDriver = haveBrailleDriver,
+        .initializeDriver = initializeBrailleDriver
+      };
+      if (activateDriver(&data, verify)) return 1;
+    }
+
+    device += 1;
+  }
+
+  brailleDevice = NULL;
+  return 0;
+}
+
+static void
+deactivateBrailleDriver (void) {
+  if (brailleDriver) {
+    api.unlinkServer();
+    if (brailleDriverConstructed) destructBrailleDriver();
+    braille = &noBraille;
+    brailleDevice = NULL;
+    brailleDriver = NULL;
+  }
+
+  unloadDriverObject(&brailleObject);
+  stopAllBlinkDescriptors();
+
+  if (brailleDriverParameters) {
+    deallocateStrings(brailleDriverParameters);
+    brailleDriverParameters = NULL;
+  }
+
+  if (oldPreferencesFile) {
+    free(oldPreferencesFile);
+    oldPreferencesFile = NULL;
+  }
+}
+
+static int
+startBrailleDriver (void) {
+  forgetDevices();
+
+  if (activateBrailleDriver(0)) {
+    if (oldPreferencesEnabled) {
+      loadPreferencesFile(oldPreferencesFile);
+      finishPreferencesLoad();
+    } else {
+      applyBraillePreferences();
+    }
+
+    ensureStatusFields();
+    alert(ALERT_BRAILLE_ON);
+
+    ses->winx = 0;
+    trackScreenCursor(1);
+
+    if (clearStatusCells(&brl)) {
+      if (opt_quiet) {
+        scheduleUpdate("braille driver start");
+        return 1;
+      }
+
+      {
+        char banner[0X100];
+        const char *text = opt_startMessage;
+
+        if (*text) {
+          text = gettext(text);
+        } else {
+          makeProgramBanner(banner, sizeof(banner), 0);
+          text = banner;
+        }
+
+        if (message(NULL, text, MSG_SILENT)) return 1;
+      }
+    }
+
+    deactivateBrailleDriver();
+  }
+
+  return 0;
+}
+
+static void
+stopBrailleDriver (void) {
+  deactivateBrailleDriver();
+  alert(ALERT_BRAILLE_OFF);
+}
+
+static int
+prepareBrailleDriverActivity (void *data) {
+  initializeBrailleDisplay();
+  ensureBrailleBuffer(&brl, LOG_DEBUG);
+  return 1;
+}
+
+static int
+startBrailleDriverActivity (void *data) {
+  return startBrailleDriver();
+}
+
+static void
+stopBrailleDriverActivity (void *data) {
+  stopBrailleDriver();
+}
+
+static const ActivityMethods brailleDriverActivityMethods = {
+  .activityName = "braille-driver",
+  .retryInterval = BRAILLE_DRIVER_START_RETRY_INTERVAL,
+
+  .prepare = prepareBrailleDriverActivity,
+  .start = startBrailleDriverActivity,
+  .stop = stopBrailleDriverActivity
+};
+
+static ActivityObject *brailleDriverActivity = NULL;
+
+static void
+writeBrailleMessage (const char *text) {
+  clearStatusCells(&brl);
+  message(NULL, text, (MSG_NODELAY | MSG_SILENT | MSG_SYNC));
+  brl.noDisplay = 1;
+}
+
+static void
+exitBrailleDriver (void *data) {
+  if (brailleDriverConstructed) {
+    const char *text = opt_stopMessage;
+
+    if (*text) {
+      text = gettext(text);
+    } else {
+      text = gettext("BRLTTY stopped");
+    }
+
+    writeBrailleMessage(text);
+  }
+
+  if (brailleDriverActivity) {
+    destroyActivity(brailleDriverActivity);
+    brailleDriverActivity = NULL;
+  }
+
+  forgetDevices();
+}
+
+static ActivityObject *
+getBrailleDriverActivity (int allocate) {
+  if (!brailleDriverActivity) {
+    if (allocate) {
+      if (!(brailleDriverActivity = newActivity(&brailleDriverActivityMethods, NULL))) {
+        return NULL;
+      }
+
+      onProgramExit("braille-driver", exitBrailleDriver, NULL);
+    }
+  }
+
+  return brailleDriverActivity;
+}
+
+static int canEnableBrailleDriver = 1;
+
+void
+enableBrailleDriver (void) {
+  if (canEnableBrailleDriver) {
+    ActivityObject *activity = getBrailleDriverActivity(1);
+    if (activity) startActivity(activity);
+  }
+}
+
+void
+disableBrailleDriver (const char *reason) {
+  ActivityObject *activity = getBrailleDriverActivity(0);
+
+  if (activity) {
+    if (reason) writeBrailleMessage(reason);
+    stopActivity(activity);
+  }
+}
+
+void
+setBrailleOn (void) {
+  if (!canEnableBrailleDriver) {
+    canEnableBrailleDriver = 1;
+    enableBrailleDriver();
+  }
+}
+
+void
+setBrailleOff (const char *message) {
+  canEnableBrailleDriver = 0;
+  disableBrailleDriver(message);
+}
+
+void
+restartBrailleDriver (void) {
+  disableBrailleDriver(gettext("braille driver restarting"));
+  awaitActivityStopped(brailleDriverActivity);
+  brl.hasFailed = 0;
+
+  logMessage(LOG_INFO, gettext("reinitializing braille driver"));
+  enableBrailleDriver();
+}
+
+static void
+exitBrailleData (void *data) {
+  if (brailleDrivers) {
+    deallocateStrings(brailleDrivers);
+    brailleDrivers = NULL;
+  }
+
+  if (brailleParameters) {
+    free(brailleParameters);
+    brailleParameters = NULL;
+  }
+
+  if (brailleDevices) {
+    deallocateStrings(brailleDevices);
+    brailleDevices = NULL;
+  }
+}
+
+int
+changeBrailleDriver (const char *driver) {
+  return changeListSetting(&brailleDrivers, &opt_brailleDriver, driver);
+}
+
+int
+changeBrailleParameters (const char *parameters) {
+  if (!parameters) parameters = "";
+  return changeStringSetting(&brailleParameters, parameters);
+}
+
+int
+changeBrailleDevice (const char *device) {
+  return changeListSetting(&brailleDevices, &opt_brailleDevice, device);
+}
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static AsyncHandle autospeakDelayAlarm = NULL;
+
+static void
+cancelAutospeakDelayAlarm (void) {
+  if (autospeakDelayAlarm) {
+    asyncCancelRequest(autospeakDelayAlarm);
+    autospeakDelayAlarm = NULL;
+  }
+}
+
+static void
+endAutospeakDelay (SpeechSynthesizer *spk) {
+  cancelAutospeakDelayAlarm();
+
+  if (!spk->canAutospeak) {
+    spk->canAutospeak = 1;
+    scheduleUpdate("banner spoken");
+  }
+}
+
+ASYNC_ALARM_CALLBACK(handleAutospeakDelayAlarm) {
+  asyncDiscardHandle(autospeakDelayAlarm);
+  autospeakDelayAlarm = NULL;
+
+  endAutospeakDelay(&spk);
+}
+
+static void
+beginAutospeakDelay (int duration) {
+  if (asyncNewRelativeAlarm(&autospeakDelayAlarm, duration,
+                            handleAutospeakDelayAlarm, NULL)) {
+    spk.canAutospeak = 0;
+  }
+}
+
+static void
+setSpeechFinished (SpeechSynthesizer *spk) {
+  spk->track.isActive = 0;
+  spk->track.speechLocation = SPK_LOC_NONE;
+
+  endAutospeakDelay(spk);
+}
+
+static void
+setSpeechLocation (SpeechSynthesizer *spk, int location) {
+  if (spk->track.isActive) {
+    if (scr.number == spk->track.screenNumber) {
+      if (location != spk->track.speechLocation) {
+        spk->track.speechLocation = location;
+        if (ses->trackScreenCursor) trackSpeech();
+      }
+
+      return;
+    }
+
+    setSpeechFinished(spk);
+  }
+}
+
+static void
+initializeSpeechSynthesizer (void) {
+  constructSpeechSynthesizer(&spk);
+  spk.setFinished = setSpeechFinished;
+  spk.setLocation = setSpeechLocation;
+}
+
+int
+constructSpeechDriver (void) {
+  initializeSpeechSynthesizer();
+
+  if (startSpeechDriverThread(&spk, speechDriverParameters)) {
+    return 1;
+  } else {
+    logMessage(LOG_DEBUG, "speech driver initialization failed: %s",
+               speech->definition.code);
+  }
+
+  return 0;
+}
+
+void
+destructSpeechDriver (void) {
+  stopSpeechDriverThread(&spk);
+  destructSpeechSynthesizer(&spk);
+}
+
+static int
+initializeSpeechDriver (const char *code, int verify) {
+  if ((speech = loadSpeechDriver(code, &speechObject, opt_driversDirectory))) {
+    speechDriverParameters = getParameters(speech->parameters,
+                                           speech->definition.code,
+                                           speechParameters);
+
+    if (speechDriverParameters) {
+      int constructed = verify;
+
+      if (!constructed) {
+        logMessage(LOG_DEBUG, "initializing speech driver: %s",
+                   speech->definition.code);
+
+        if (constructSpeechDriver()) {
+          constructed = 1;
+          speechDriver = speech;
+        }
+      }
+
+      if (constructed) {
+        identifySpeechDriver(speech, 0);
+
+        logParameters(
+          speech->parameters,
+          speechDriverParameters,
+          "Speech Parameter"
+        );
+
+        return 1;
+      }
+
+      deallocateStrings(speechDriverParameters);
+      speechDriverParameters = NULL;
+    }
+
+    unloadDriverObject(&speechObject);
+  } else {
+    logMessage(LOG_ERR, "%s: %s", gettext("speech driver not loadable"), code);
+  }
+
+  speech = &noSpeech;
+  return 0;
+}
+
+static int
+activateSpeechDriver (int verify) {
+  static const char *const autodetectableDrivers[] = {
+    NULL
+  };
+
+  const DriverActivationData data = {
+    .driverType = "speech",
+    .requestedDrivers = (const char *const *)speechDrivers,
+    .autodetectableDrivers = autodetectableDrivers,
+    .getDefaultDriver = getDefaultSpeechDriver,
+    .haveDriver = haveSpeechDriver,
+    .initializeDriver = initializeSpeechDriver
+  };
+
+  return activateDriver(&data, verify);
+}
+
+static void
+deactivateSpeechDriver (void) {
+  if (speechDriver) {
+    destructSpeechDriver();
+
+    speech = &noSpeech;
+    speechDriver = NULL;
+  }
+
+  unloadDriverObject(&speechObject);
+
+  if (speechDriverParameters) {
+    deallocateStrings(speechDriverParameters);
+    speechDriverParameters = NULL;
+  }
+}
+
+static int
+startSpeechDriver (void) {
+  if (!activateSpeechDriver(0)) return 0;
+  applySpeechPreferences();
+
+  if (!opt_quiet && spk.sayBanner) {
+    char banner[0X100];
+    const char *text = opt_startMessage;
+
+    if (*text) {
+      text = gettext(text);
+    } else {
+      makeProgramBanner(banner, sizeof(banner), 0);
+      text = banner;
+    }
+
+    sayString(&spk, text, SAY_OPT_MUTE_FIRST);
+    beginAutospeakDelay(SPEECH_DRIVER_START_AUTOSPEAK_DELAY);
+  } else if (isAutospeakActive()) {
+    autospeak(AUTOSPEAK_FORCE);
+  }
+
+  return 1;
+}
+
+static void
+stopSpeechDriver (void) {
+  cancelAutospeakDelayAlarm();
+
+  muteSpeech(&spk, "driver stop");
+  deactivateSpeechDriver();
+}
+
+static int
+prepareSpeechDriverActivity (void *data) {
+  initializeSpeechSynthesizer();
+  return 1;
+}
+
+static int
+startSpeechDriverActivity (void *data) {
+  return startSpeechDriver();
+}
+
+static void
+stopSpeechDriverActivity (void *data) {
+  stopSpeechDriver();
+}
+
+static const ActivityMethods speechDriverActivityMethods = {
+  .activityName = "speech-driver",
+  .retryInterval = SPEECH_DRIVER_START_RETRY_INTERVAL,
+
+  .prepare = prepareSpeechDriverActivity,
+  .start = startSpeechDriverActivity,
+  .stop = stopSpeechDriverActivity
+};
+
+static ActivityObject *speechDriverActivity = NULL;
+
+static void
+exitSpeechDriver (void *data) {
+  if (speechDriverActivity) {
+    destroyActivity(speechDriverActivity);
+    speechDriverActivity = NULL;
+  }
+}
+
+static ActivityObject *
+getSpeechDriverActivity (int allocate) {
+  if (!speechDriverActivity) {
+    if (allocate) {
+      if (!(speechDriverActivity = newActivity(&speechDriverActivityMethods, NULL))) {
+        return NULL;
+      }
+
+      onProgramExit("speech-driver", exitSpeechDriver, NULL);
+    }
+  }
+
+  return speechDriverActivity;
+}
+
+void
+enableSpeechDriver (int sayBanner) {
+  ActivityObject *activity = getSpeechDriverActivity(1);
+
+  spk.sayBanner = sayBanner;
+  if (activity) startActivity(activity);
+}
+
+void
+disableSpeechDriver (const char *reason) {
+  ActivityObject *activity = getSpeechDriverActivity(0);
+
+  if (activity) {
+    if (reason) {
+      sayString(&spk, reason, SAY_OPT_MUTE_FIRST);
+      drainSpeech(&spk);
+    }
+
+    stopActivity(activity);
+  }
+}
+
+void
+restartSpeechDriver (void) {
+  disableSpeechDriver(gettext("speech driver restarting"));
+  awaitActivityStopped(speechDriverActivity);
+
+  logMessage(LOG_INFO, gettext("reinitializing speech driver"));
+  enableSpeechDriver(1);
+}
+
+static void
+exitSpeechData (void *data) {
+  if (speechDrivers) {
+    deallocateStrings(speechDrivers);
+    speechDrivers = NULL;
+  }
+
+  if (speechParameters) {
+    free(speechParameters);
+    speechParameters = NULL;
+  }
+}
+
+static void
+exitSpeechInput (void *data) {
+  if (speechInputObject) {
+    destroySpeechInputObject(speechInputObject);
+    speechInputObject = NULL;
+  }
+}
+
+int
+changeSpeechDriver (const char *driver) {
+  return changeListSetting(&speechDrivers, &opt_speechDriver, driver);
+}
+
+int
+changeSpeechParameters (const char *parameters) {
+  if (!parameters) parameters = "";
+  return changeStringSetting(&speechParameters, parameters);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+static int
+initializeScreenDriver (const char *code, int verify) {
+  if ((screen = loadScreenDriver(code, &screenObject, opt_driversDirectory))) {
+    screenDriverParameters = getParameters(
+      getScreenParameters(screen),
+      screen->definition.code,
+      screenParameters
+    );
+
+    if (screenDriverParameters) {
+      int constructed = verify;
+
+      if (!constructed) {
+        logMessage(LOG_DEBUG,
+          "initializing screen driver: %s",
+          screen->definition.code
+        );
+
+        if (constructScreenDriver(screenDriverParameters)) {
+          constructed = 1;
+          screenDriver = screen;
+        }
+      }
+
+      if (constructed) {
+        identifyScreenDriver(screen, 0);
+
+        logParameters(
+          getScreenParameters(screen),
+          screenDriverParameters,
+          "Screen Parameter"
+        );
+
+        return 1;
+      }
+
+      deallocateStrings(screenDriverParameters);
+      screenDriverParameters = NULL;
+    }
+
+    unloadDriverObject(&screenObject);
+  } else {
+    logMessage(LOG_ERR, "%s: %s", gettext("screen driver not loadable"), code);
+  }
+
+  setNoScreen();
+  return 0;
+}
+
+static int
+activateScreenDriver (int verify) {
+  static const char *const autodetectableDrivers[] = {
+    NULL
+  };
+
+  const DriverActivationData data = {
+    .driverType = "screen",
+    .requestedDrivers = (const char *const *)screenDrivers,
+    .autodetectableDrivers = autodetectableDrivers,
+    .getDefaultDriver = getDefaultScreenDriver,
+    .haveDriver = haveScreenDriver,
+    .initializeDriver = initializeScreenDriver
+  };
+
+  return activateDriver(&data, verify);
+}
+
+static void
+deactivateScreenDriver (void) {
+  if (screenDriver) {
+    destructScreenDriver();
+
+    setNoScreen();
+    screenDriver = NULL;
+  }
+
+  unloadDriverObject(&screenObject);
+
+  if (screenDriverParameters) {
+    deallocateStrings(screenDriverParameters);
+    screenDriverParameters = NULL;
+  }
+}
+
+static int
+startScreenDriver (void) {
+  if (!activateScreenDriver(0)) return 0;
+  if (isMainScreen()) scheduleUpdate("main screen started");
+  return 1;
+}
+
+static void
+stopScreenDriver (void) {
+  deactivateScreenDriver();
+}
+
+static int
+prepareScreenDriverActivity (void *data) {
+  return 1;
+}
+
+static int
+startScreenDriverActivity (void *data) {
+  return startScreenDriver();
+}
+
+static void
+stopScreenDriverActivity (void *data) {
+  stopScreenDriver();
+}
+
+static const ActivityMethods screenDriverActivityMethods = {
+  .activityName = "screen-driver",
+  .retryInterval = SCREEN_DRIVER_START_RETRY_INTERVAL,
+
+  .prepare = prepareScreenDriverActivity,
+  .start = startScreenDriverActivity,
+  .stop = stopScreenDriverActivity
+};
+
+static ActivityObject *screenDriverActivity = NULL;
+
+static void
+exitScreenDriver (void *data) {
+  if (screenDriverActivity) {
+    destroyActivity(screenDriverActivity);
+    screenDriverActivity = NULL;
+  }
+}
+
+static ActivityObject *
+getScreenDriverActivity (int allocate) {
+  if (!screenDriverActivity) {
+    if (allocate) {
+      if (!(screenDriverActivity = newActivity(&screenDriverActivityMethods, NULL))) {
+        return NULL;
+      }
+
+      onProgramExit("screen-driver", exitScreenDriver, NULL);
+    }
+  }
+
+  return screenDriverActivity;
+}
+
+void
+enableScreenDriver (void) {
+  ActivityObject *activity = getScreenDriverActivity(1);
+
+  setNoScreenDriverReason(NULL);
+  if (activity) startActivity(activity);
+}
+
+void
+disableScreenDriver (const char *reason) {
+  ActivityObject *activity = getScreenDriverActivity(0);
+
+  setNoScreenDriverReason(reason);
+  if (activity) stopActivity(activity);
+}
+
+void
+restartScreenDriver (void) {
+  disableScreenDriver(gettext("screen driver restarting"));
+  awaitActivityStopped(screenDriverActivity);
+
+  logMessage(LOG_INFO, gettext("reinitializing screen driver"));
+  enableScreenDriver();
+}
+
+static void
+exitScreenData (void *data) {
+  endSpecialScreens();
+
+  if (screenDrivers) {
+    deallocateStrings(screenDrivers);
+    screenDrivers = NULL;
+  }
+
+  if (screenParameters) {
+    free(screenParameters);
+    screenParameters = NULL;
+  }
+}
+
+int
+changeScreenDriver (const char *driver) {
+  return changeListSetting(&screenDrivers, &opt_screenDriver, driver);
+}
+
+int
+changeScreenParameters (const char *parameters) {
+  if (!parameters) parameters = "";
+  return changeStringSetting(&screenParameters, parameters);
+}
+
+int
+changeMessageLocale (const char *locale) {
+  int changed = !!setlocale(LC_ALL, locale);
+
+  if (changed) {
+    api.updateParameter(BRLAPI_PARAM_MESSAGE_LOCALE, 0);
+  } else {
+    logMessage(LOG_WARNING, "message locale change failed: %s", locale);
+  }
+
+  return changed;
+}
+
+static void
+exitPidFile (void *data) {
+#if defined(GRUB_RUNTIME)
+
+#else /* remove pid file */
+  unlink(opt_pidFile);
+#endif /* remove pid file */
+}
+
+static int
+makePidFile (ProcessIdentifier pid) {
+  return createPidFile(opt_pidFile, pid);
+}
+
+static int tryPidFile (void);
+
+ASYNC_ALARM_CALLBACK(retryPidFile) {
+  tryPidFile();
+}
+
+static int
+tryPidFile (void) {
+  if (makePidFile(0)) {
+    onProgramExit("pid-file", exitPidFile, NULL);
+  } else if (errno == EEXIST) {
+    return 0;
+  } else {
+    asyncNewRelativeAlarm(NULL, PID_FILE_CREATE_RETRY_INTERVAL, retryPidFile, NULL);
+  }
+
+  return 1;
+}
+
+#if defined(__MINGW32__)
+static void
+background (void) {
+  char *variableName;
+
+  {
+    const char *strings[] = {programName, "_DAEMON"};
+    variableName = joinStrings(strings, ARRAY_COUNT(strings));
+  }
+
+  {
+    int i;
+    for (i=0; variableName[i]; i+=1) {
+      char c = variableName[i];
+
+      if (c == '_') continue;
+      if (isdigit((unsigned char)c) && (i > 0)) continue;
+
+      if (isalpha((unsigned char)c)) {
+        if (islower((unsigned char)c)) variableName[i] = toupper((unsigned char)c);
+        continue;
+      }
+
+      variableName[i] = '_';
+    }
+  }
+
+  if (!getenv(variableName)) {
+    LPTSTR commandLine = GetCommandLine();
+    STARTUPINFO startupInfo;
+    PROCESS_INFORMATION processInfo;
+    
+    memset(&startupInfo, 0, sizeof(startupInfo));
+    startupInfo.cb = sizeof(startupInfo);
+
+    if (!SetEnvironmentVariable(variableName, "BACKGROUND")) {
+      logWindowsSystemError("SetEnvironmentVariable");
+      exit(PROG_EXIT_FATAL);
+    }
+
+    if (!CreateProcess(NULL, commandLine, NULL, NULL, TRUE,
+                       CREATE_NEW_PROCESS_GROUP | CREATE_SUSPENDED,
+                       NULL, NULL, &startupInfo, &processInfo)) {
+      logWindowsSystemError("CreateProcess");
+      exit(PROG_EXIT_FATAL);
+    }
+
+    {
+      int created = makePidFile(processInfo.dwProcessId);
+      int resumed = ResumeThread(processInfo.hThread) != -1;
+
+      if (!created) {
+        if (errno == EEXIST) {
+          ExitProcess(PROG_EXIT_FATAL);
+        }
+      }
+
+      if (!resumed) {
+        logWindowsSystemError("ResumeThread");
+        ExitProcess(PROG_EXIT_FATAL);
+      }
+    }
+
+    ExitProcess(PROG_EXIT_SUCCESS);
+  }
+
+  free(variableName);
+}
+
+#elif defined(__MSDOS__)
+static void
+background (void) {
+  msdosBackground();
+}
+
+#elif defined(GRUB_RUNTIME)
+static void
+background (void) {
+}
+
+#else /* Unix */
+static void
+background (void) {
+  int fds[2];
+
+  if (pipe(fds) == -1) {
+    logSystemError("pipe");
+    exit(PROG_EXIT_FATAL);
+  }
+
+  fflush(stdout);
+  fflush(stderr);
+
+  {
+    pid_t child = fork();
+
+    if (child == -1) {
+      logSystemError("fork");
+      exit(PROG_EXIT_FATAL);
+    }
+
+    if (child) {
+      ProgramExitStatus exitStatus = PROG_EXIT_SUCCESS;
+
+      if (close(fds[0]) == -1) logSystemError("close");
+
+      if (!makePidFile(child)) {
+        if (errno == EEXIST) {
+          exitStatus = PROG_EXIT_SEMANTIC;
+        }
+      }
+
+      if (close(fds[1]) == -1) logSystemError("close");
+      _exit(exitStatus);
+    }
+  }
+
+  if (close(fds[1]) == -1) logSystemError("close");
+
+  {
+    unsigned char buffer[1];
+
+    if (read(fds[0], buffer, sizeof(buffer)) == -1) logSystemError("read");
+    if (close(fds[0]) == -1) logSystemError("close");
+  }
+
+  if (setsid() == -1) {                        
+    logSystemError("setsid");
+    exit(PROG_EXIT_FATAL);
+  }
+}
+#endif /* background() */
+
+static int
+validateInterval (int *value, const char *string) {
+  if (!*string) return 1;
+
+  {
+    static const int minimum = 1;
+    int ok = validateInteger(value, string, &minimum, NULL);
+    if (ok) *value *= 10;
+    return ok;
+  }
+}
+
+ProgramExitStatus
+brlttyStart (void) {
+  if (opt_cancelExecution) {
+    ProgramExitStatus exitStatus;
+
+    if (!*opt_pidFile) {
+      exitStatus = PROG_EXIT_SEMANTIC;
+      logMessage(LOG_ERR, "%s", gettext("pid file not specified"));
+    } else if (cancelProgram(opt_pidFile)) {
+      exitStatus = PROG_EXIT_FORCE;
+    } else {
+      exitStatus = PROG_EXIT_FATAL;
+    }
+
+    return exitStatus;
+  }
+
+  {
+    int stop = 0;
+
+    if (opt_removeService) {
+      removeService(SERVICE_NAME);
+      stop = 1;
+    }
+
+    if (opt_installService) {
+      installService(SERVICE_NAME, SERVICE_DESCRIPTION, opt_configurationFile);
+      stop = 1;
+    }
+
+    if (stop) return PROG_EXIT_FORCE;
+  }
+
+  if (!validateInterval(&messageHoldTimeout, opt_messageTime)) {
+    logMessage(LOG_ERR, "%s: %s", gettext("invalid message hold timeout"), opt_messageTime);
+  }
+
+  if (opt_version) {
+    logMessage(LOG_INFO, "Copyright %s", PACKAGE_COPYRIGHT);
+    identifyScreenDrivers(1);
+
+#ifdef ENABLE_API
+    api.logServerIdentity(1);
+#endif /* ENABLE_API */
+
+    identifyBrailleDrivers(1);
+
+#ifdef ENABLE_SPEECH_SUPPORT
+    identifySpeechDrivers(1);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+    return PROG_EXIT_FORCE;
+  }
+
+  if (opt_verify) opt_noDaemon = 1;
+  if (!opt_noDaemon
+#ifdef __MINGW32__
+      && !isWindowsService
+#endif
+     ) {
+    background();
+  }
+
+  if (*opt_pidFile) {
+    if (!tryPidFile()) {
+      return PROG_EXIT_SEMANTIC;
+    }
+  }
+
+  if (!opt_noDaemon) {
+    stderrLogLevel = 0;
+
+    detachStandardInput();
+    detachStandardOutput();
+    if (!opt_standardError) detachStandardError();
+
+#ifdef __MINGW32__
+    {
+      HANDLE h = CreateFile("NUL", GENERIC_READ|GENERIC_WRITE,
+                            FILE_SHARE_READ|FILE_SHARE_WRITE,
+                            NULL, OPEN_EXISTING, 0, NULL);
+
+      if (!h) {
+        logWindowsSystemError("CreateFile[NUL]");
+      } else {
+        SetStdHandle(STD_INPUT_HANDLE, h);
+        SetStdHandle(STD_OUTPUT_HANDLE, h);
+
+        if (!opt_standardError) {
+          SetStdHandle(STD_ERROR_HANDLE, h);
+        }
+      }
+    }
+#endif /* __MINGW32__ */
+  }
+
+  /*
+   * From this point, all IO functions as printf, puts, perror, etc. can't be
+   * used anymore since we are a daemon.  The logMessage() facility should 
+   * be used instead.
+   */
+
+  changeScreenDriver(opt_screenDriver);
+  changeScreenParameters(opt_screenParameters);
+  beginSpecialScreens();
+  onProgramExit("screen-data", exitScreenData, NULL);
+
+  suppressTuneDeviceOpenErrors();
+
+  {
+    char *directory;
+
+    if ((directory = getWorkingDirectory())) {
+      logProperty(directory, "workingDirectory", "Working Directory");
+      free(directory);
+    } else {
+      logMessage(LOG_WARNING, "%s: %s", gettext("cannot determine working directory"), strerror(errno));
+    }
+  }
+
+  logProperty(opt_configurationFile, "configurationFile", "Configuration File");
+  logProperty(opt_tablesDirectory, "tablesDirectory", "Tables Directory");
+  logProperty(opt_driversDirectory, "driversDirectory", "Drivers Directory");
+  logProperty(opt_writableDirectory, "writableDirectory", "Writable Directory");
+  logProperty(opt_updatableDirectory, "updatableDirectory", "Updatable Directory");
+  logProperty(opt_preferencesFile, "preferencesFile", "Preferences File");
+
+  resetPreferences();
+  loadPreferences(0);
+
+  if (opt_promptPatterns && *opt_promptPatterns) {
+    int count;
+    char **patterns = splitString(opt_promptPatterns, PARAMETER_SEPARATOR_CHARACTER, &count);
+
+    if (patterns) {
+      for (int index=0; index<count; index+=1) {
+        if (!addPromptPattern(patterns[index])) break;
+      }
+
+      deallocateStrings(patterns);
+    }
+  }
+
+  setTextAndContractionTables();
+  setAttributesTable();
+  setKeyboardTable();
+
+  /* initialize screen driver */
+  if (opt_verify) {
+    if (activateScreenDriver(1)) deactivateScreenDriver();
+  } else {
+    setNoScreen();
+    enableScreenDriver();
+  }
+  
+  /* The device(s) the braille display might be connected to. */
+  if (!*opt_brailleDevice) {
+    logMessage(LOG_ERR, gettext("braille device not specified"));
+    return PROG_EXIT_SYNTAX;
+  }
+
+  constructBrailleDisplay(&brl);
+  changeBrailleDriver(opt_brailleDriver);
+  changeBrailleParameters(opt_brailleParameters);
+  changeBrailleDevice(opt_brailleDevice);
+  brailleDriverConstructed = 0;
+  onProgramExit("braille-data", exitBrailleData, NULL);
+
+  if (opt_verify) {
+    if (activateBrailleDriver(1)) deactivateBrailleDriver();
+  } else {
+    enableBrailleDriver();
+  }
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  constructSpeechSynthesizer(&spk);
+  changeSpeechDriver(opt_speechDriver);
+  changeSpeechParameters(opt_speechParameters);
+  onProgramExit("speech-data", exitSpeechData, NULL);
+
+  if (opt_verify) {
+    if (activateSpeechDriver(1)) deactivateSpeechDriver();
+  } else {
+    enableSpeechDriver(1);
+  }
+
+  /* Create the file system object for speech input. */
+  logProperty(opt_speechInput, "speechInput", "Speech Input");
+  if (!opt_verify) {
+    if (*opt_speechInput) {
+      speechInputObject = newSpeechInputObject(opt_speechInput);
+      onProgramExit("speech-input", exitSpeechInput, NULL);
+    }
+  }
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  startApiServer();
+
+  if (!opt_verify) notifyServiceReady();
+
+  return opt_verify? PROG_EXIT_FORCE: PROG_EXIT_SUCCESS;
+}
+
+static char *configuredLocale = "";
+
+static int
+changeLocale (const char *locale) {
+  if (changeMessageLocale(locale)) return 1;
+  setlocale(LC_ALL, configuredLocale);
+  return 0;
+}
+
+static const ProfileProperty languageProfileProperties[] = {
+  { .name = WS_C("locale"),
+    .defaultValue = &configuredLocale,
+    .change = changeLocale
+  },
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  { .name = WS_C("speech-driver"),
+    .defaultValue = &opt_speechDriver,
+    .change = changeSpeechDriver
+  },
+
+  { .name = WS_C("speech-parameters"),
+    .defaultValue = &opt_speechParameters,
+    .change = changeSpeechParameters
+  },
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  { .name = WS_C("text-table"),
+    .defaultValue = &opt_textTable,
+    .change = changeTextTable
+  },
+
+  { .name = WS_C("contraction-table"),
+    .defaultValue = &opt_contractionTable,
+    .change = changeContractionTable
+  },
+};
+
+static int
+beginLanguageProfile (void) {
+#ifdef ENABLE_SPEECH_SUPPORT
+  disableSpeechDriver(NULL);
+  awaitActivityStopped(speechDriverActivity);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  return 1;
+}
+
+static int
+endLanguageProfile (void) {
+#ifdef ENABLE_SPEECH_SUPPORT
+  enableSpeechDriver(0);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  if (brl.keyTable) {
+    char *path = makeBrailleKeyTablePath();
+
+    if (path) {
+      disableBrailleHelpPage();
+      makeBrailleHelpPage(path);
+      free(path);
+    }
+  }
+
+  if (keyboardTable) {
+    disableKeyboardHelpPage();
+    makeKeyboardHelpPage();
+  }
+
+  return 1;
+}
+
+const ProfileDescriptor languageProfile = {
+  .category = strtext("Language"),
+  .extension = LANGUAGE_PROFILE_EXTENSION,
+
+  .begin = beginLanguageProfile,
+  .end = endLanguageProfile,
+
+  .properties = {
+    .array = languageProfileProperties,
+    .count = ARRAY_COUNT(languageProfileProperties)
+  }
+};
diff --git a/Programs/core.c b/Programs/core.c
new file mode 100644
index 0000000..8fd08a7
--- /dev/null
+++ b/Programs/core.c
@@ -0,0 +1,1645 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif /* HAVE_SYS_WAIT_H */
+
+#include "parameters.h"
+#include "embed.h"
+#include "log.h"
+#include "alert.h"
+#include "strfmt.h"
+
+#include "cmd_queue.h"
+#include "cmd_clipboard.h"
+#include "cmd_custom.h"
+#include "cmd_input.h"
+#include "cmd_keycodes.h"
+#include "cmd_learn.h"
+#include "cmd_miscellaneous.h"
+#include "cmd_navigation.h"
+#include "cmd_override.h"
+#include "cmd_preferences.h"
+#include "cmd_speech.h"
+#include "cmd_toggle.h"
+#include "cmd_touch.h"
+
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_alarm.h"
+#include "async_event.h"
+#include "async_signal.h"
+#include "async_task.h"
+
+#include "brl_cmds.h"
+#include "timing.h"
+#include "ctb.h"
+#include "routing.h"
+#include "utf8.h"
+#include "unicode.h"
+#include "scr.h"
+#include "update.h"
+#include "ses.h"
+#include "brl.h"
+#include "brl_utils.h"
+#include "prefs.h"
+#include "api_control.h"
+#include "core.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+#include "spk.h"
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+BrailleDisplay brl;                        /* For the Braille routines */
+
+int
+haveBrailleDisplay (void) {
+  return braille->definition.code != noBraille.definition.code;
+}
+
+ScreenDescription scr;
+SessionEntry *ses = NULL;
+
+unsigned char infoMode = 0;
+
+unsigned int textStart;
+unsigned int textCount;
+unsigned char textMaximized = 0;
+
+unsigned int statusStart;
+unsigned int statusCount;
+
+unsigned int fullWindowShift;                /* Full window horizontal distance */
+unsigned int halfWindowShift;                /* Half window horizontal distance */
+unsigned int verticalWindowShift;                /* Window vertical distance */
+
+int
+isContractedBraille (void) {
+  return (prefs.brailleVariant == bvContracted6)
+      || (prefs.brailleVariant == bvContracted8)
+      ;
+}
+
+int
+isSixDotComputerBraille (void) {
+  return (prefs.brailleVariant == bvComputer6)
+      || (prefs.brailleVariant == bvContracted6)
+      ;
+}
+
+static void
+setBrailleVariant (int contracted, int sixDot) {
+  prefs.brailleVariant = contracted?
+                         (sixDot? bvContracted6: bvContracted8):
+                         (sixDot? bvComputer6: bvComputer8);
+}
+
+void
+setContractedBraille (int contracted) {
+  setBrailleVariant(contracted, isSixDotComputerBraille());
+  api.updateParameter(BRLAPI_PARAM_LITERARY_BRAILLE, 0);
+}
+
+void
+setSixDotComputerBraille (int sixDot) {
+  setBrailleVariant(isContractedBraille(), sixDot);
+  api.updateParameter(BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE, 0);
+}
+
+void
+onBrailleVariantUpdated (void) {
+  api.updateParameter(BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE, 0);
+  api.updateParameter(BRLAPI_PARAM_LITERARY_BRAILLE, 0);
+}
+
+int
+startScreenCursorRouting (int column, int row) {
+  if (!routeScreenCursor(column, row, scr.number)) return 0;
+  if (isRouting()) alert(ALERT_ROUTING_STARTED);
+  return 1;
+}
+
+int
+bringScreenCursor (int column, int row) {
+  if (!startScreenCursorRouting(column, row)) return 0;
+  RoutingStatus status = getRoutingStatus(1);
+
+  if (status != ROUTING_STATUS_NONE) {
+    alert(
+      (status > ROUTING_STATUS_COLUMN)? ALERT_ROUTING_FAILED:
+      ALERT_ROUTING_SUCCEEDED
+    );
+
+    ses->spkx = scr.posx;
+    ses->spky = scr.posy;
+  }
+
+  return 1;
+}
+
+typedef struct {
+  int motionColumn;
+  int motionRow;
+
+  int speechColumn;
+  int speechRow;
+} PrecommandState;
+
+static void *
+preprocessCommand (void) {
+  PrecommandState *pre;
+
+  if ((pre = malloc(sizeof(*pre)))) {
+    memset(pre, 0, sizeof(*pre));
+
+    pre->motionColumn = ses->winx;
+    pre->motionRow = ses->winy;
+
+    pre->speechColumn = ses->spkx;
+    pre->speechRow = ses->spky;
+
+    suspendUpdates();
+    return pre;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+postprocessCommand (void *state, int command, const CommandEntry *cmd, int handled) {
+  PrecommandState *pre = state;
+
+  if (pre) {
+    resumeUpdates(0);
+    if (handled) scheduleUpdate("command executed");
+
+    if ((ses->winx != pre->motionColumn) || (ses->winy != pre->motionRow)) {
+      /* The braille window has been manually moved. */
+      reportBrailleWindowMoved();
+
+      ses->motx = ses->winx;
+      ses->moty = ses->winy;
+
+      isContracted = 0;
+    }
+
+    if (cmd) {
+      if (cmd->isMotion) {
+        if (command & BRL_FLG_MOTION_ROUTE) {
+          if ((ses->spkx != pre->speechColumn) || (ses->spky != pre->speechRow)) {
+            /* The speech cursor has moved. */
+            bringScreenCursor(ses->spkx, ses->spky);
+          } else if (command & BRL_MSK_BLK) {
+            bringScreenCursor((command & BRL_MSK_ARG), ses->winy);
+          } else {
+            int left = ses->winx;
+            int right = MIN(left+textCount, scr.cols) - 1;
+
+            int top = ses->winy;
+            int bottom = MIN(top+brl.textRows, scr.rows) - 1;
+
+            if ((scr.posx < left) || (scr.posx > right) ||
+                (scr.posy < top) || (scr.posy > bottom)) {
+              bringScreenCursor(left, top);
+            }
+          }
+        }
+      }
+    }
+
+    free(pre);
+  }
+}
+
+static int
+handleUnhandledCommands (int command, void *data) {
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_NOOP:        /* do nothing but loop */
+      break;
+
+    default:
+      alert(ALERT_COMMAND_REJECTED);
+      return 0;
+  }
+
+  return 1;
+}
+
+static int
+handleApiCommands (int command, void *data) {
+  return api.handleCommand(command);
+}
+
+static int
+addScreenCommands (void) {
+  return pushCommandHandler("screen", KTB_CTX_DEFAULT,
+                            handleScreenCommands, NULL, NULL);
+}
+
+static int
+addCommands (void) {
+  if (!pushCommandEnvironment("main", preprocessCommand, postprocessCommand)) return 0;
+
+  pushCommandHandler("unhandled", KTB_CTX_DEFAULT,
+                     handleUnhandledCommands, NULL, NULL);
+
+  addMiscellaneousCommands();
+  addLearnCommands();
+  addSpeechCommands();
+  addClipboardCommands();
+  addPreferencesCommands();
+  addToggleCommands();
+  addTouchCommands();
+  addKeycodeCommands();
+  addInputCommands();
+  addNavigationCommands();
+
+  addOverrideCommands();
+  addScreenCommands();
+  addCustomCommands();
+
+  pushCommandHandler("API", KTB_CTX_DEFAULT,
+                     handleApiCommands, NULL, NULL);
+
+  return 1;
+}
+
+static AsyncHandle delayedCursorTrackingAlarm;
+
+ASYNC_ALARM_CALLBACK(handleDelayedCursorTrackingAlarm) {
+  asyncDiscardHandle(delayedCursorTrackingAlarm);
+  delayedCursorTrackingAlarm = NULL;
+
+  ses->trkx = ses->dctx;
+  ses->trky = ses->dcty;
+
+  ses->dctx = -1;
+  ses->dcty = -1;
+
+  scheduleUpdate("delayed cursor tracking");
+}
+
+void
+cancelDelayedCursorTrackingAlarm (void) {
+  if (delayedCursorTrackingAlarm) {
+    asyncCancelRequest(delayedCursorTrackingAlarm);
+    delayedCursorTrackingAlarm = NULL;
+  }
+}
+
+static void
+setSessionEntry (void) {
+  describeScreen(&scr);
+  if (scr.number == -1) scr.number = userVirtualTerminal(0);
+
+  {
+    typedef enum {SAME, DIFFERENT, FIRST} State;
+    State state = (!ses)? FIRST:
+                  (scr.number == ses->number)? SAME:
+                  DIFFERENT;
+
+    if (state != SAME) {
+      cancelDelayedCursorTrackingAlarm();
+      ses = getSessionEntry(scr.number);
+
+      if (state == FIRST) {
+        addCommands();
+      }
+    }
+  }
+}
+
+void
+updateSessionAttributes (void) {
+  setSessionEntry();
+
+  {
+    int maximum = MAX(scr.rows-1, 0);
+    int *table[] = {&ses->winy, &ses->moty, NULL};
+    int **value = table;
+
+    while (*value) {
+      if (**value > maximum) **value = maximum;
+      value += 1;
+    }
+  }
+
+  {
+    int maximum = MAX(scr.cols-1, 0);
+    int *table[] = {&ses->winx, &ses->motx, NULL};
+    int **value = table;
+
+    while (*value) {
+      if (**value > maximum) **value = maximum;
+      value += 1;
+    }
+  }
+}
+
+void
+fillStatusSeparator (wchar_t *text, unsigned char *dots) {
+  if ((prefs.statusSeparator != ssNone) && (statusCount > 0)) {
+    int onRight = statusStart > 0;
+    unsigned int column = (onRight? statusStart: textStart) - 1;
+
+    wchar_t textSeparator;
+#ifdef HAVE_WCHAR_H 
+    const wchar_t textSeparator_left  = 0X23B8; /* LEFT VERTICAL BOX LINE */
+    const wchar_t textSeparator_right = 0X23B9; /* RIGHT VERTICAL BOX LINE */
+    const wchar_t textSeparator_block = 0X2503; /* BOX DRAWINGS HEAVY VERTICAL */
+#else /* HAVE_WCHAR_H */
+    const wchar_t textSeparator_left  = 0X5B; /* LEFT SQUARE BRACKET */
+    const wchar_t textSeparator_right = 0X5D; /* RIGHT SQUARE BRACKET */
+    const wchar_t textSeparator_block = 0X7C; /* VERTICAL LINE */
+#endif /* HAVE_WCHAR_H */
+
+    unsigned char dotsSeparator;
+    const unsigned char dotsSeparator_left = BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT7;
+    const unsigned char dotsSeparator_right = BRL_DOT4 | BRL_DOT5 | BRL_DOT6 | BRL_DOT8;
+    const unsigned char dotsSeparator_block = dotsSeparator_left | dotsSeparator_right;
+
+    text += column;
+    dots += column;
+
+    switch (prefs.statusSeparator) {
+      case ssBlock:
+        textSeparator = textSeparator_block;
+        dotsSeparator = dotsSeparator_block;
+        break;
+
+      case ssStatusSide:
+        textSeparator = onRight? textSeparator_right: textSeparator_left;
+        dotsSeparator = onRight? dotsSeparator_right: dotsSeparator_left;
+        break;
+
+      case ssTextSide:
+        textSeparator = onRight? textSeparator_left: textSeparator_right;
+        dotsSeparator = onRight? dotsSeparator_left: dotsSeparator_right;
+        break;
+
+      default:
+        textSeparator = WC_C(' ');
+        dotsSeparator = 0;
+        break;
+    }
+
+    {
+      unsigned int row;
+      for (row=0; row<brl.textRows; row+=1) {
+        *text = textSeparator;
+        text += brl.textColumns;
+
+        *dots = dotsSeparator;
+        dots += brl.textColumns;
+      }
+    }
+  }
+}
+
+int
+writeBrailleCharacters (const char *mode, const wchar_t *characters, size_t length) {
+  wchar_t textBuffer[brl.textColumns * brl.textRows];
+
+  fillTextRegion(textBuffer, brl.buffer,
+                 textStart, textCount, brl.textColumns, brl.textRows,
+                 characters, length);
+
+  {
+    size_t modeLength = mode? countUtf8Characters(mode): 0;
+    wchar_t modeCharacters[modeLength + 1];
+    makeWcharsFromUtf8(mode, modeCharacters, ARRAY_COUNT(modeCharacters));
+    fillTextRegion(textBuffer, brl.buffer,
+                   statusStart, statusCount, brl.textColumns, brl.textRows,
+                   modeCharacters, modeLength);
+  }
+
+  fillStatusSeparator(textBuffer, brl.buffer);
+
+  return writeBrailleWindow(&brl, textBuffer, 0);
+}
+
+int
+writeBrailleText (const char *mode, const char *text) {
+  size_t count = countUtf8Characters(text) + 1;
+  wchar_t characters[count];
+  size_t length = makeWcharsFromUtf8(text, characters, count);
+  return writeBrailleCharacters(mode, characters, length);
+}
+
+int
+showBrailleText (const char *mode, const char *text, int minimumDelay) {
+  int ok = writeBrailleText(mode, text);
+  drainBrailleOutput(&brl, minimumDelay);
+  return ok;
+}
+
+static inline const char *
+getMeridianString_am (void) {
+#ifdef HAVE_NL_LANGINFO
+  return nl_langinfo(AM_STR);
+#else /* HAVE_NL_LANGINFO */
+  return "am";
+#endif /* HAVE_NL_LANGINFO */
+}
+
+static inline const char *
+getMeridianString_pm (void) {
+#ifdef HAVE_NL_LANGINFO
+  return nl_langinfo(PM_STR);
+#else /* HAVE_NL_LANGINFO */
+  return "pm";
+#endif /* HAVE_NL_LANGINFO */
+}
+
+static const char *
+getMeridianString (uint8_t *hour) {
+  const char *string = NULL;
+
+  switch (prefs.timeFormat) {
+    case tf12Hour: {
+      const uint8_t twelve = 12;
+
+      string = (*hour < twelve)? getMeridianString_am(): getMeridianString_pm();
+      *hour %= twelve;
+      if (!*hour) *hour = twelve;
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return string;
+}
+
+STR_BEGIN_FORMATTER(formatBrailleTime, const TimeFormattingData *fmt)
+  char time[0X40];
+
+  {
+    const char *hourFormat = "%02" PRIu8;
+    const char *minuteFormat = "%02" PRIu8;
+    const char *secondFormat = "%02" PRIu8;
+    char separator;
+
+    switch (prefs.timeSeparator) {
+      default:
+      case tsColon:
+        separator = ':';
+        break;
+
+      case tsDot:
+        separator = '.';
+        break;
+    }
+
+    switch (prefs.timeFormat) {
+      default:
+      case tf24Hour:
+        break;
+
+      case tf12Hour:
+        hourFormat = "%" PRIu8;
+        break;
+    }
+
+    STR_BEGIN(time, sizeof(time));
+    STR_PRINTF(hourFormat, fmt->components.hour);
+    STR_PRINTF("%c", separator);
+    STR_PRINTF(minuteFormat, fmt->components.minute);
+
+    if (prefs.showSeconds) {
+      STR_PRINTF("%c", separator);
+      STR_PRINTF(secondFormat, fmt->components.second);
+    }
+
+    if (fmt->meridian) STR_PRINTF("%s", fmt->meridian);
+    STR_END;
+  }
+
+  if (prefs.datePosition == dpNone) {
+    STR_PRINTF("%s", time);
+  } else {
+    char date[0X40];
+
+    {
+      const char *yearFormat = "%04" PRIu16;
+      const char *monthFormat = "%02" PRIu8;
+      const char *dayFormat = "%02" PRIu8;
+
+      uint16_t year = fmt->components.year;
+      uint8_t month = fmt->components.month + 1;
+      uint8_t day = fmt->components.day + 1;
+
+      char separator;
+
+      switch (prefs.dateSeparator) {
+        default:
+        case dsDash:
+          separator = '-';
+          break;
+
+        case dsSlash:
+          separator = '/';
+          break;
+
+        case dsDot:
+          separator = '.';
+          break;
+      }
+
+      STR_BEGIN(date, sizeof(date));
+      switch (prefs.dateFormat) {
+        default:
+        case dfYearMonthDay:
+          STR_PRINTF(yearFormat, year);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(monthFormat, month);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(dayFormat, day);
+          break;
+
+        case dfMonthDayYear:
+          STR_PRINTF(monthFormat, month);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(dayFormat, day);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(yearFormat, year);
+          break;
+
+        case dfDayMonthYear:
+          STR_PRINTF(dayFormat, day);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(monthFormat, month);
+          STR_PRINTF("%c", separator);
+          STR_PRINTF(yearFormat, year);
+          break;
+      }
+      STR_END;
+
+      switch (prefs.datePosition) {
+        case dpBeforeTime:
+          STR_PRINTF("%s %s", date, time);
+          break;
+
+        case dpAfterTime:
+          STR_PRINTF("%s %s", time, date);
+          break;
+
+        default:
+          STR_PRINTF("%s", date);
+          break;
+      }
+    }
+  }
+STR_END_FORMATTER
+
+void
+getTimeFormattingData (TimeFormattingData *fmt) {
+  getCurrentTime(&fmt->value);
+  expandTimeValue(&fmt->value, &fmt->components);
+  fmt->meridian = getMeridianString(&fmt->components.hour);
+}
+
+int
+isCursorPosition (int x) {
+  return (x == scr.posx) && (ses->winy == scr.posy) && showScreenCursor();
+}
+
+int
+isWordBreak (const ScreenCharacter *characters, int x) {
+  if (!iswspace(characters[x].text)) return 0;
+  return !isCursorPosition(x);
+}
+
+int
+getWordWrapLength (int row, int from, int count) {
+  int width = scr.cols;
+  if (from >= width) return 0;
+
+  int end = from + count;
+  if (end >= width) return width - from;
+
+  ScreenCharacter characters[width];
+  readScreenRow(row, width, characters);
+
+  int to = end;
+  int onWordBreak = iswspace(characters[to].text);
+
+  if (!onWordBreak) {
+    int index = to;
+
+    while (index > from) {
+      if (iswspace(characters[--index].text)) {
+        to = index;
+        onWordBreak = 1;
+        break;
+      }
+    }
+  }
+
+  if (onWordBreak) {
+    while (to < width) {
+      if (!iswspace(characters[to].text)) break;
+      if ((to >= end) && isCursorPosition(to)) break;
+      to += 1;
+    }
+  }
+
+  return to - from;
+}
+
+void
+setWordWrapStart (int start) {
+  if (start < 0) start = 0;
+  ses->winx = start;
+
+  if (start > 0) {
+    int end = start + textCount;
+    if (end > scr.cols) end = scr.cols;
+
+    ScreenCharacter characters[end];
+    readScreenRow(ses->winy, end, characters);
+
+    while (end > 0) {
+      if (!isWordBreak(characters, --end)) {
+        end += 1;
+        break;
+      }
+    }
+
+    start = end - textCount;
+    if (start < 0) start = 0;
+
+    if (start > 0) {
+      if (!isWordBreak(characters, start-1)) {
+        while (start < end) {
+          if (isWordBreak(characters, start)) break;
+          start += 1;
+        }
+      }
+
+      while (start < end) {
+        if (!isWordBreak(characters, start)) break;
+        start += 1;
+      }
+    }
+
+    if (start < end) ses->winx = start;
+  }
+}
+
+void 
+placeBrailleWindowHorizontally (int x) {
+  if (prefs.slidingBrailleWindow) {
+    ses->winx = MAX(0, (x - (int)(textCount / 2)));
+  } else {
+    ses->winx = x / textCount * textCount;
+  }
+}
+
+void
+placeRightEdge (int column) {
+  if (isContracting()) {
+    ses->winx = 0;
+
+    while (1) {
+      int length = getContractedLength(textCount);
+      int end = ses->winx + length;
+
+      if (end > column) break;
+      if (end == ses->winx) break;
+      ses->winx = end;
+    }
+  } else {
+    ses->winx = column / textCount * textCount;
+  }
+}
+
+void
+placeBrailleWindowRight (void) {
+  placeRightEdge(scr.cols-1);
+}
+
+int
+moveBrailleWindowLeft (unsigned int amount) {
+  if (ses->winx < 1) return 0;
+  if (amount < 1) return 0;
+
+  ses->winx -= MIN(ses->winx, amount);
+  return 1;
+}
+
+int
+moveBrailleWindowRight (unsigned int amount) {
+  if (amount < 1) return 0;
+  int newx = ses->winx + amount;
+  if (newx >= scr.cols) return 0;
+
+  ses->winx = newx;
+  return 1;
+}
+
+int
+shiftBrailleWindowLeft (unsigned int amount) {
+  if (isContracting()) {
+    int reference = ses->winx;
+    if (!reference) return 0;
+
+    {
+      int from = 0;
+      int to = ses->winx;
+
+      while (from < to) {
+        int end = (ses->winx = ((from + to) / 2)) + getContractedLength(amount);
+
+        if (end < reference) {
+          from = ses->winx + 1;
+        } else {
+          to = ses->winx;
+        }
+      }
+
+      if (!(ses->winx = from)) return 1;
+    }
+
+    ScreenCharacter characters[reference];
+    readScreenRow(ses->winy, reference, characters);
+    int x = ses->winx;
+
+    if (!isWordBreak(characters, x-1)) {
+      int wasIdeographic = isIdeographicCharacter(characters[x-1].text);
+
+      for (int i=x; i<reference; i+=1) {
+        int isIdeographic = isIdeographicCharacter(characters[i].text);
+
+        if (!(isIdeographic && wasIdeographic)) {
+          if (!isWordBreak(characters, i)) {
+            wasIdeographic = isIdeographic;
+            continue;
+          }
+        }
+
+        x = i;
+        break;
+      }
+    }
+
+    while (x < reference) {
+      if (!isWordBreak(characters, x)) break;
+      x += 1;
+    }
+
+    if (x < reference) ses->winx = x;
+    return 1;
+  }
+
+  if (prefs.wordWrap) {
+    if (ses->winx < 1) return 0;
+    setWordWrapStart(ses->winx - amount);
+    return 1;
+  }
+
+  return moveBrailleWindowLeft(amount);
+}
+
+int
+shiftBrailleWindowRight (unsigned int amount) {
+  if (isContracting()) {
+    amount = getContractedLength(amount);
+  } else if (prefs.wordWrap) {
+    amount = getWordWrapLength(ses->winy, ses->winx, amount);
+  }
+
+  return moveBrailleWindowRight(amount);
+}
+
+void
+slideBrailleWindowVertically (int y) {
+  if ((y < ses->winy) || (y >= (int)(ses->winy + brl.textRows))) {
+    y -= brl.textRows / 2;
+
+    {
+      int maxy = scr.rows - brl.textRows;
+      if (y > maxy) y = maxy;
+    }
+
+    if (y < 0) y = 0;
+    ses->winy = y;
+  }
+}
+
+static int
+isWithinBrailleWindow (int x, int y) {
+  return (x >= ses->winx)
+      && (x < (int)(ses->winx + textCount))
+      && (y >= ses->winy)
+      && (y < (int)(ses->winy + brl.textRows))
+      ;
+}
+
+int
+trackScreenCursor (int place) {
+  if (!SCR_CURSOR_OK()) return 0;
+
+  if (place) {
+    cancelDelayedCursorTrackingAlarm();
+  } else if (delayedCursorTrackingAlarm) {
+    /* A cursor tracking motion has been delayed. If the cursor returned
+     * to its initial location in the mean time then we discard and ignore
+     * the previous motion. Otherwise we wait for the timer to expire.
+     */
+    if ((ses->dctx == scr.posx) && (ses->dcty == scr.posy)) {
+      cancelDelayedCursorTrackingAlarm();
+    }
+
+    return 1;
+  } else if ((prefs.cursorTrackingDelay > 0) && (ses->dctx != -1) &&
+             !isWithinBrailleWindow(ses->trkx, ses->trky)) {
+    /* The cursor may move spuriously while a program updates information
+     * on a status bar. If cursor tracking is on and the cursor was
+     * outside the braille window before it moved, we delay the tracking
+     * motion for a while so as not to obnoxiously move the braille window
+     * in case the cursor will eventually return to its initial location
+     * within a short time.
+     */
+    ses->dctx = ses->trkx;
+    ses->dcty = ses->trky;
+
+    int delay = 250 << (prefs.cursorTrackingDelay - 1);
+    asyncNewRelativeAlarm(&delayedCursorTrackingAlarm, delay,
+                          handleDelayedCursorTrackingAlarm, NULL);
+
+    return 1;
+  }
+
+  /* anything but -1 */
+  ses->dctx = 0;
+  ses->dcty = 0;
+
+  if (isContracted) {
+    slideBrailleWindowVertically(scr.posy);
+    contractedTrack = 1;
+
+    if (scr.posx > ses->winx) {
+      if (scr.posx < (ses->winx + getContractedLength(textCount))) {
+        return 1;
+      }
+    }
+
+    ses->winx = scr.posx;
+    shiftBrailleWindowLeft(halfWindowShift);
+    return 1;
+  }
+
+  if (place && !isWithinBrailleWindow(scr.posx, scr.posy)) {
+    placeBrailleWindowHorizontally(scr.posx);
+  }
+
+  if (prefs.slidingBrailleWindow) {
+    {
+      int width = scr.cols;
+      ScreenCharacter characters[width];
+      readScreenRow(scr.posy, width, characters);
+
+      int column = findLastNonSpaceCharacter(characters, width);
+      if (column < 0) column = 0;
+      if (column < textCount) ses->winx = 0;
+    }
+
+    int reset = textCount * 3 / 10;
+    int trigger = prefs.eagerSlidingBrailleWindow? textCount*3/20: 0;
+    if (scr.posx == ses->winx) trigger = 1;
+
+    if (scr.posx < (ses->winx + trigger)) {
+      ses->winx = MAX(scr.posx-reset, 0);
+    } else if (scr.posx >= (int)(ses->winx + textCount - trigger)) {
+      ses->winx = MAX(MIN(scr.posx+reset+1, scr.cols)-(int)textCount, 0);
+    }
+  } else if (scr.posx < ses->winx) {
+    ses->winx -= ((ses->winx - scr.posx - 1) / textCount + 1) * textCount;
+    if (ses->winx < 0) ses->winx = 0;
+  } else {
+    ses->winx += (scr.posx - ses->winx) / textCount * textCount;
+  }
+
+  if (prefs.wordWrap) {
+    int length = getWordWrapLength(ses->winy, ses->winx, textCount);
+    int next = ses->winx + length;
+    if (scr.posx >= next) ses->winx = next;
+  }
+
+  slideBrailleWindowVertically(scr.posy);
+  return 1;
+}
+
+int
+findFirstNonSpaceCharacter (const ScreenCharacter *characters, int count) {
+  int index = 0;
+
+  while (index < count) {
+    if (!iswspace(characters[index].text)) return index;
+    index += 1;
+  }
+
+  return -1;
+}
+
+int
+findLastNonSpaceCharacter (const ScreenCharacter *characters, int count) {
+  int index = count;
+
+  while (index > 0)
+    if (!iswspace(characters[--index].text))
+      return index;
+
+  return -1;
+}
+
+int
+isAllSpaceCharacters (const ScreenCharacter *characters, int count) {
+  return findFirstNonSpaceCharacter(characters, count) < 0;
+}
+
+#ifdef ENABLE_SPEECH_SUPPORT
+SpeechSynthesizer spk;
+
+int
+haveSpeechSynthesizer (void) {
+  return speech->definition.code != noSpeech.definition.code;
+}
+
+void
+trackSpeech (void) {
+  int location = spk.track.speechLocation;
+
+  if (location != SPK_LOC_NONE) {
+    placeBrailleWindowHorizontally(location % scr.cols);
+    slideBrailleWindowVertically(spk.track.firstLine + (location / scr.cols));
+    scheduleUpdate("speech tracked");
+  }
+}
+
+int
+isAutospeakActive (void) {
+  if (!haveSpeechSynthesizer()) return 0;
+  if (prefs.autospeak) return 1;
+  if (haveBrailleDisplay()) return 0;
+  return !opt_quietIfNoBraille;
+}
+
+void
+sayScreenCharacters (const ScreenCharacter *characters, size_t count, SayOptions options) {
+  wchar_t text[count];
+  wchar_t *t = text;
+
+  unsigned char attributes[count];
+  unsigned char *a = attributes;
+
+  for (unsigned int i=0; i<count; i+=1) {
+    const ScreenCharacter *character = &characters[i];
+    *t++ = character->text;
+    *a++ = character->attributes;
+  }
+
+  sayWideCharacters(&spk, text, attributes, count, options);
+}
+
+void
+speakCharacters (const ScreenCharacter *characters, size_t count, int spell, int interrupt) {
+  SayOptions sayOptions = 0;
+  if (interrupt) sayOptions |= SAY_OPT_MUTE_FIRST;
+
+  if (isAllSpaceCharacters(characters, count)) {
+    switch (prefs.speechWhitespaceIndicator) {
+      default:
+      case swsNone:
+        break;
+
+      case swsSaySpace: {
+        wchar_t buffer[0X100];
+        size_t length = makeWcharsFromUtf8(gettext("space"), buffer, ARRAY_COUNT(buffer));
+
+        sayWideCharacters(&spk, buffer, NULL, length, sayOptions);
+        break;
+      }
+    }
+  } else if (count == 1) {
+    wchar_t character = characters[0].text;
+    unsigned char attributes = characters[0].attributes;
+    const char *prefix = NULL;
+
+    if (iswupper(character)) {
+      switch (prefs.speechUppercaseIndicator) {
+        default:
+        case sucNone:
+          break;
+
+        case sucSayCap:
+          // "cap" here, used during speech output, is short for "capital".
+          // It is spoken just before an uppercase letter, e.g. "cap A".
+          prefix = gettext("cap");
+          break;
+
+        case sucRaisePitch:
+          sayOptions |= SAY_OPT_HIGHER_PITCH;
+          break;
+      }
+    } else if (iswpunct(character)) {
+      sayOptions |= SAY_OPT_ALL_PUNCTUATION;
+    }
+
+    if (prefix) {
+      wchar_t textBuffer[0X100];
+      size_t length = makeWcharsFromUtf8(prefix, textBuffer, ARRAY_COUNT(textBuffer));
+
+      textBuffer[length++] = WC_C(' ');
+      textBuffer[length++] = character;
+
+      unsigned char attributesBuffer[length];
+      memset(attributesBuffer, SCR_COLOUR_DEFAULT, length);
+      attributesBuffer[length-1] = attributes;
+
+      sayWideCharacters(&spk, textBuffer, attributesBuffer, length, sayOptions);
+    } else {
+      sayWideCharacters(&spk, &character, &attributes, 1, sayOptions);
+    }
+  } else if (spell) {
+    size_t length = count * 2;
+    wchar_t textBuffer[length];
+    unsigned char attributesBuffer[length];
+
+    wchar_t *text = textBuffer;
+    unsigned char *attributes = attributesBuffer;
+
+    const ScreenCharacter *character = characters;
+    const ScreenCharacter *end = character + count;
+
+    while (character < end) {
+      *text++ = character->text;
+      *attributes++ = character->attributes;
+
+      *text++ = WC_C(' ');
+      *attributes++ = SCR_COLOUR_DEFAULT;
+
+      character += 1;
+    }
+
+    sayWideCharacters(&spk, textBuffer, attributesBuffer, length-1, sayOptions);
+  } else {
+    sayScreenCharacters(characters, count, sayOptions);
+  }
+}
+
+int
+speakIndent (const ScreenCharacter *characters, int count, int evenIfNoIndent) {
+  int length = scr.cols;
+  ScreenCharacter buffer[length];
+
+  if (!characters) {
+    readScreenRow(ses->spky, length, buffer);
+    characters = buffer;
+    count = length;
+  }
+
+  int indent = findFirstNonSpaceCharacter(characters, count);
+  if ((indent < 1) && !evenIfNoIndent) return 0;
+
+  char message[50];
+  const char *text = message;
+
+  if (indent < 0) {
+    text = gettext("blank line");
+  } else {
+    snprintf(message, sizeof(message),
+             "%s %d", gettext("indent"), indent);
+  }
+
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS),
+             "line indent: %d", indent);
+
+  sayString(&spk, text, SAY_OPT_MUTE_FIRST);
+  return 1;
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+int
+isContracting (void) {
+  return isContractedBraille() && contractionTable;
+}
+
+int
+getContractedLength (unsigned int outputLimit) {
+  int inputLength = scr.cols - ses->winx;
+  wchar_t inputBuffer[inputLength];
+  readScreenText(ses->winx, ses->winy, inputLength, 1, inputBuffer);
+
+  int outputLength = outputLimit;
+  unsigned char outputBuffer[outputLength];
+
+  int offsetCount = inputLength;
+  int outputOffsets[offsetCount + 1];
+
+  contractText(
+    contractionTable, NULL,
+    inputBuffer, &inputLength,
+    outputBuffer, &outputLength,
+    outputOffsets, getCursorOffsetForContracting()
+  );
+
+  for (int length=0; length<inputLength; length+=1) {
+    int offset = outputOffsets[length];
+
+    if (offset != CTB_NO_OFFSET) {
+      if (offset >= outputLimit) {
+        return length;
+      }
+    }
+  }
+
+  return inputLength;
+}
+
+int
+showScreenCursor (void) {
+  return scr.hasCursor
+      && prefs.showScreenCursor
+      && !(ses->hideScreenCursor || brl.hideCursor)
+      ;
+}
+
+int
+isSameText (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+) {
+  return character1->text == character2->text;
+}
+
+int
+isSameAttributes (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+) {
+  return character1->attributes == character2->attributes;
+}
+
+int
+isSameCharacter (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+) {
+  return isSameText(character1, character2) && isSameAttributes(character1, character2);
+}
+
+int
+isSameRow (
+  const ScreenCharacter *characters1,
+  const ScreenCharacter *characters2,
+  int count,
+  IsSameCharacter isSameCharacter
+) {
+  int i;
+  for (i=0; i<count; ++i)
+    if (!isSameCharacter(&characters1[i], &characters2[i]))
+      return 0;
+
+  return 1;
+}
+
+int
+canBraille (void) {
+  return braille && brl.buffer && !brl.noDisplay && !brl.isSuspended;
+}
+
+static unsigned int interruptEnabledCount;
+static AsyncEvent *interruptEvent;
+static int interruptPending;
+static WaitResult waitResult;
+
+typedef struct {
+  WaitResult waitResult;
+} InterruptEventParameters;
+
+int
+brlttyInterrupt (WaitResult waitResult) {
+  if (interruptEvent) {
+    InterruptEventParameters *iep;
+
+    if ((iep = malloc(sizeof(*iep)))) {
+      memset(iep, 0, sizeof(*iep));
+      iep->waitResult = waitResult;
+
+      if (asyncSignalEvent(interruptEvent, iep)) {
+        return 1;
+      }
+
+      free(iep);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+ASYNC_EVENT_CALLBACK(handleCoreInterrupt) {
+  InterruptEventParameters *iep = parameters->signalData;
+
+  if (iep) {
+    interruptPending = 1;
+    waitResult = iep->waitResult;
+    free(iep);
+  }
+}
+
+int
+brlttyEnableInterrupt (void) {
+  if (!interruptEnabledCount) {
+    if (!(interruptEvent = asyncNewEvent(handleCoreInterrupt, NULL))) {
+      return 0;
+    }
+  }
+
+  interruptEnabledCount += 1;
+  return 1;
+}
+
+int
+brlttyDisableInterrupt (void) {
+  if (!interruptEnabledCount) return 0;
+
+  if (!--interruptEnabledCount) {
+    asyncDiscardEvent(interruptEvent);
+    interruptEvent = NULL;
+  }
+
+  return 1;
+}
+
+typedef void UnmonitoredConditionHandler (const void *data);
+
+static void
+handleRoutingDone (const void *data) {
+  const RoutingStatus *status = data;
+
+  alert(
+    (*status > ROUTING_STATUS_SUCCEESS)? ALERT_ROUTING_FAILED:
+    ALERT_ROUTING_SUCCEEDED
+  );
+
+  ses->spkx = scr.posx;
+  ses->spky = scr.posy;
+}
+
+static void
+handleBrailleDriverFailed (const void *data) {
+  restartBrailleDriver();
+}
+
+static time_t programTerminationRequestTime;
+static int programTerminationRequestSignal;
+static volatile sig_atomic_t programTerminationRequestCount;
+
+typedef struct {
+  UnmonitoredConditionHandler *handler;
+  const void *data;
+} UnmonitoredConditionDescriptor;
+
+ASYNC_CONDITION_TESTER(checkUnmonitoredConditions) {
+  UnmonitoredConditionDescriptor *ucd = data;
+
+  if (interruptPending) {
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "interrupt pending");
+    ucd->data = &waitResult;
+    interruptPending = 0;
+    return 1;
+  }
+
+  if (programTerminationRequestCount) {
+    // This is a memory read barrier to ensure that the most recent
+    // time and number for the program termination signal are seen.
+    __sync_synchronize();
+
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS),
+      "program termination requested: Count=%ld Signal=%d",
+      (long)programTerminationRequestCount, programTerminationRequestSignal
+    );
+
+    static const WaitResult result = WAIT_STOP;
+    ucd->data = &result;
+    return 1;
+  }
+
+  {
+    static RoutingStatus status;
+
+    if ((status = getRoutingStatus(0)) != ROUTING_STATUS_NONE) {
+      logMessage(LOG_CATEGORY(ASYNC_EVENTS), "routing status: %u", status);
+      ucd->handler = handleRoutingDone;
+      ucd->data = &status;
+      return 1;
+    }
+  }
+
+  if (brl.hasFailed) {
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "braille driver failed");
+    ucd->handler = handleBrailleDriverFailed;
+    return 1;
+  }
+
+  return 0;
+}
+
+WaitResult
+brlttyWait (int duration) {
+  UnmonitoredConditionDescriptor ucd = {
+    .handler = NULL,
+    .data = NULL
+  };
+
+  if (asyncAwaitCondition(duration, checkUnmonitoredConditions, &ucd)) {
+    if (!ucd.handler) {
+      const WaitResult *result = ucd.data;
+      return *result;
+    }
+
+    ucd.handler(ucd.data);
+  }
+
+  return 1;
+}
+
+int
+showDotPattern (unsigned char dots, unsigned char duration) {
+  if (braille->writeStatus && (brl.statusColumns > 0)) {
+    unsigned int length = brl.statusColumns * brl.statusRows;
+    unsigned char cells[length];        /* status cell buffer */
+    memset(cells, dots, length);
+    if (!braille->writeStatus(&brl, cells)) return 0;
+  }
+
+  memset(brl.buffer, dots, brl.textColumns*brl.textRows);
+  if (!writeBrailleWindow(&brl, NULL, 0)) return 0;
+
+  drainBrailleOutput(&brl, duration);
+  return 1;
+}
+
+static void
+exitSessions (void *data) {
+  cancelDelayedCursorTrackingAlarm();
+
+  if (ses) {
+    popCommandEnvironment();
+    ses = NULL;
+  }
+
+  deallocateSessionEntries();
+}
+
+static AsyncEvent *addCoreTaskEvent = NULL;
+
+static int
+startCoreTasks (void) {
+  if (!addCoreTaskEvent) {
+    if (!(addCoreTaskEvent = asyncNewAddTaskEvent())) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static void
+stopCoreTasks (void) {
+  if (addCoreTaskEvent) {
+    asyncDiscardEvent(addCoreTaskEvent);
+    addCoreTaskEvent = NULL;
+  }
+}
+
+static void
+logCoreTaskAction (CoreTaskCallback *callback, const char *action) {
+  logSymbol(LOG_DEBUG, callback, "%s core task", action);
+}
+
+typedef struct {
+  struct {
+    CoreTaskCallback *callback;
+    void *data;
+  } run;
+
+  struct {
+    AsyncEvent *event;
+    unsigned finished:1;
+  } wait;
+} CoreTaskData;
+
+ASYNC_TASK_CALLBACK(handleCoreTask) {
+  CoreTaskData *ctd = data;
+
+  {
+    CoreTaskCallback *callback = ctd->run.callback;
+    logCoreTaskAction(callback, "starting");
+    callback(ctd->run.data);
+    logCoreTaskAction(callback, "finished");
+  }
+
+  {
+    AsyncEvent *event = ctd->wait.event;
+    if (event) asyncSignalEvent(event, ctd);
+  }
+}
+
+ASYNC_CONDITION_TESTER(testCoreTaskFinished) {
+  CoreTaskData *ctd = data;
+  return ctd->wait.finished;
+}
+
+ASYNC_EVENT_CALLBACK(setCoreTaskFinished) {
+  CoreTaskData *ctd = parameters->signalData;
+  ctd->wait.finished = 1;
+}
+
+int
+runCoreTask (CoreTaskCallback *callback, void *data, int wait) {
+  int wasScheduled = 0;
+
+  if (addCoreTaskEvent) {
+    CoreTaskData *ctd;
+
+    if ((ctd = malloc(sizeof(*ctd)))) {
+      memset(ctd, 0, sizeof(*ctd));
+
+      ctd->run.callback = callback;
+      ctd->run.data = data;
+
+      ctd->wait.event = NULL;
+      ctd->wait.finished = 0;
+
+      if (!wait || (ctd->wait.event = asyncNewEvent(setCoreTaskFinished, NULL))) {
+        logCoreTaskAction(callback, "scheduling");
+
+        if (asyncAddTask(addCoreTaskEvent, handleCoreTask, ctd)) {
+          wasScheduled = 1;
+
+          if (wait) {
+            logCoreTaskAction(callback, "awaiting");
+            asyncWaitFor(testCoreTaskFinished, ctd);
+            logCoreTaskAction(callback, "completed");
+          }
+        }
+
+        {
+          AsyncEvent *event = ctd->wait.event;
+          if (event) asyncDiscardEvent(event);
+        }
+      }
+
+      free(ctd);
+    } else {
+      logMallocError();
+    }
+  } else {
+    logMessage(LOG_ERR, "core tasks not started");
+  }
+
+  return wasScheduled;
+}
+
+#ifdef ASYNC_CAN_HANDLE_SIGNALS
+ASYNC_SIGNAL_HANDLER(handleProgramTerminationRequest) {
+  time_t now = time(NULL);
+
+  int reset = difftime(now, programTerminationRequestTime)
+            > PROGRAM_TERMINATION_REQUEST_RESET_SECONDS;
+
+  int count = reset? 0: programTerminationRequestCount;
+  if (++count > PROGRAM_TERMINATION_REQUEST_COUNT_THRESHOLD) exit(1);
+
+  programTerminationRequestTime = now;
+  programTerminationRequestSignal = signalNumber;
+
+  // This is a memory write barrier to ensure that the time and number
+  // for this signal will be visible before its count is adjusted.
+  __sync_synchronize();
+
+  programTerminationRequestCount = count;
+}
+
+#ifdef SIGCHLD
+ASYNC_SIGNAL_HANDLER(handleChildDeath) {
+}
+#endif /* SIGCHLD */
+#endif /* ASYNC_CAN_HANDLE_SIGNALS */
+
+ProgramExitStatus
+brlttyConstruct (int argc, char *argv[]) {
+  {
+    TimeValue now;
+    getMonotonicTime(&now);
+    srand(now.seconds ^ now.nanoseconds);
+  }
+
+  {
+    ProgramExitStatus exitStatus = brlttyPrepare(argc, argv);
+    if (exitStatus != PROG_EXIT_SUCCESS) return exitStatus;
+  }
+
+  programTerminationRequestTime = time(NULL);
+  programTerminationRequestSignal = 0;
+  programTerminationRequestCount = 0;
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+  asyncBlockObtainableSignals();
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+#ifdef ASYNC_CAN_HANDLE_SIGNALS
+#ifdef SIGPIPE
+  /* We ignore SIGPIPE before calling brlttyStart() so that a driver
+   * which uses a broken pipe won't abort program execution.
+   */
+  asyncIgnoreSignal(SIGPIPE, NULL);
+#endif /* SIGPIPE */
+
+#ifdef SIGTERM
+  asyncHandleSignal(SIGTERM, handleProgramTerminationRequest, NULL);
+#endif /* SIGTERM */
+
+#ifdef SIGINT
+  asyncHandleSignal(SIGINT, handleProgramTerminationRequest, NULL);
+#endif /* SIGINT */
+
+#ifdef SIGCHLD
+  asyncHandleSignal(SIGCHLD, handleChildDeath, NULL);
+#endif /* SIGCHLD */
+#endif /* ASYNC_CAN_HANDLE_SIGNALS */
+
+  interruptEnabledCount = 0;
+  interruptEvent = NULL;
+  interruptPending = 0;
+
+  delayedCursorTrackingAlarm = NULL;
+
+  startCoreTasks();
+  beginCommandQueue();
+  beginUpdates();
+  suspendUpdates();
+
+  {
+    ProgramExitStatus exitStatus = brlttyStart();
+    if (exitStatus != PROG_EXIT_SUCCESS) return exitStatus;
+  }
+
+  onProgramExit("sessions", exitSessions, NULL);
+  setSessionEntry();
+  ses->trkx = scr.posx; ses->trky = scr.posy;
+  if (!trackScreenCursor(1)) ses->winx = ses->winy = 0;
+  ses->motx = ses->winx; ses->moty = ses->winy;
+  ses->spkx = ses->winx; ses->spky = ses->winy;
+
+  resumeUpdates(1);
+  return PROG_EXIT_SUCCESS;
+}
+
+int
+brlttyDestruct (void) {
+  if (prefs.saveOnExit) savePreferences();
+
+  suspendUpdates();
+  stopCoreTasks();
+
+  endProgram();
+  endCommandQueue();
+  return 1;
+}
diff --git a/Programs/core.h b/Programs/core.h
new file mode 100644
index 0000000..1379045
--- /dev/null
+++ b/Programs/core.h
@@ -0,0 +1,235 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CORE
+#define BRLTTY_INCLUDED_CORE
+
+#include "prologue.h"
+
+#include "strfmth.h"
+#include "program.h"
+#include "timing.h"
+#include "cmd.h"
+#include "brl.h"
+#include "spk.h"
+#include "scr_types.h"
+#include "ses.h"
+#include "ctb.h"
+#include "ktb.h"
+#include "prefs.h"
+#include "profile_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int isContractedBraille (void);
+extern int isSixDotComputerBraille (void);
+extern void setContractedBraille (int contracted);
+extern void setSixDotComputerBraille (int sixDot);
+extern void onBrailleVariantUpdated (void);
+
+extern ScreenDescription scr;
+#define SCR_COLUMN_OK(column) IS_WITHIN_BOUNDS((column), scr.cols)
+#define SCR_ROW_OK(row) IS_WITHIN_BOUNDS((row), scr.rows)
+#define SCR_COORDINATES_OK(column,row) (SCR_COLUMN_OK((column)) && SCR_ROW_OK((row)))
+#define SCR_CURSOR_OK() SCR_COORDINATES_OK(scr.posx, scr.posy)
+#define SCR_COLUMN_NUMBER(column) (SCR_COLUMN_OK((column))? (column)+1: 0)
+#define SCR_ROW_NUMBER(row) (SCR_ROW_OK((row))? (row)+1: 0)
+
+extern void updateSessionAttributes (void);
+extern SessionEntry *ses;
+
+typedef int (*IsSameCharacter) (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+);
+
+extern int isSameText (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+);
+
+extern int isSameAttributes (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+);
+
+extern int
+isSameCharacter (
+  const ScreenCharacter *character1,
+  const ScreenCharacter *character2
+);
+
+extern int isSameRow (
+  const ScreenCharacter *characters1,
+  const ScreenCharacter *characters2,
+  int count,
+  IsSameCharacter isSameCharacter
+);
+
+extern unsigned char infoMode;
+
+extern int canBraille (void);
+extern int writeBrailleCharacters (const char *mode, const wchar_t *characters, size_t length);
+extern void fillStatusSeparator (wchar_t *text, unsigned char *dots);
+
+extern int writeBrailleText (const char *mode, const char *text);
+extern int showBrailleText (const char *mode, const char *text, int minimumDelay);
+
+extern char *opt_driversDirectory;
+extern char *opt_tablesDirectory;
+extern char *opt_textTable;
+extern char *opt_contractionTable;
+extern char *opt_attributesTable;
+extern char *opt_keyboardTable;
+
+extern char *opt_brailleDevice;
+extern int opt_releaseDevice;
+
+extern int isWordBreak (const ScreenCharacter *characters, int x);
+extern int getWordWrapLength (int row, int from, int count);
+extern void setWordWrapStart (int start);
+
+extern void placeRightEdge (int column);
+extern void placeBrailleWindowRight (void);
+extern void placeBrailleWindowHorizontally (int x);
+
+extern int moveBrailleWindowLeft (unsigned int amount);
+extern int moveBrailleWindowRight (unsigned int amount);
+
+extern int shiftBrailleWindowLeft (unsigned int amount);
+extern int shiftBrailleWindowRight (unsigned int amount);
+
+extern void slideBrailleWindowVertically (int y);
+
+extern int showScreenCursor (void);
+extern int trackScreenCursor (int place);
+extern void cancelDelayedCursorTrackingAlarm (void);
+
+extern int startScreenCursorRouting (int column, int row);
+extern int bringScreenCursor (int column, int row);
+
+typedef struct {
+  TimeValue value;
+  TimeComponents components;
+  const char *meridian;
+} TimeFormattingData;
+
+extern void getTimeFormattingData (TimeFormattingData *fmt);
+extern STR_DECLARE_FORMATTER(formatBrailleTime, const TimeFormattingData *fmt);
+
+extern int isContracted;
+extern int contractedTrack;
+extern BrailleRowDescriptor *getBrailleRowDescriptor(unsigned int row);
+extern int getCursorOffsetForContracting(void);
+
+extern int isContracting (void);
+extern int getContractedLength (unsigned int outputLimit);
+
+extern ContractionTable *contractionTable;
+
+extern KeyTable *keyboardTable;
+
+extern ProgramExitStatus brlttyPrepare (int argc, char *argv[]);
+extern ProgramExitStatus brlttyStart (void);
+
+extern void setPreferences (const PreferenceSettings *newPreferences);
+extern int loadPreferences(int reset);
+extern int savePreferences (void);
+
+extern unsigned char getCursorDots (const unsigned char *setting);
+extern int setCursorDots (unsigned char *setting, unsigned char dots);
+extern unsigned char mapCursorDots (unsigned char dots);
+
+extern unsigned char getScreenCursorDots (void);
+extern int setScreenCursorDots (unsigned char dots);
+
+extern unsigned char getSpeechCursorDots (void);
+extern int setSpeechCursorDots (unsigned char dots);
+
+extern BrailleDisplay brl;			/* braille driver reference */
+extern int haveBrailleDisplay(void);
+
+extern unsigned int textStart;
+extern unsigned int textCount;
+extern unsigned char textMaximized;
+
+extern unsigned int statusStart;
+extern unsigned int statusCount;
+
+extern unsigned int fullWindowShift;			/* Full window horizontal distance */
+extern unsigned int halfWindowShift;			/* Half window horizontal distance */
+extern unsigned int verticalWindowShift;			/* Window vertical distance */
+
+extern void setBrailleOn (void);
+extern void setBrailleOff (const char *message);
+extern void lockBrailleDriver (void);
+extern void unlockBrailleDriver (void);
+extern void enableBrailleDriver (void);
+extern void disableBrailleDriver (const char *reason);
+extern int constructBrailleDriver (void);
+extern void destructBrailleDriver (void);
+extern int isBrailleDriverConstructed (void);
+extern int isBrailleOnline (void);
+extern void forgetDevices (void);
+
+extern void reconfigureBrailleWindow (void);
+extern int haveStatusCells (void);
+
+extern int findFirstNonSpaceCharacter (const ScreenCharacter *characters, int count);
+extern int findLastNonSpaceCharacter (const ScreenCharacter *characters, int count);
+extern int isAllSpaceCharacters (const ScreenCharacter *characters, int count);
+
+#ifdef ENABLE_SPEECH_SUPPORT
+extern SpeechSynthesizer spk;
+extern int haveSpeechSynthesizer(void);
+extern int opt_quietIfNoBraille;
+
+extern int isAutospeakActive (void);
+extern unsigned int autospeakMinimumScreenContentQuality;
+
+extern void sayScreenCharacters (const ScreenCharacter *characters, size_t count, SayOptions options);
+extern void speakCharacters (const ScreenCharacter *characters, size_t count, int spell, int interrupt);
+extern int speakIndent (const ScreenCharacter *characters, int count, int evenIfNoIndent);
+extern void trackSpeech (void);
+
+extern void enableSpeechDriver (int sayBanner);
+extern void disableSpeechDriver (const char *reason);
+extern int constructSpeechDriver (void);
+extern void destructSpeechDriver (void);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+extern void enableScreenDriver (void);
+extern void disableScreenDriver (const char *reason);
+
+#ifdef __MINGW32__
+extern int isWindowsService;
+#endif /* __MINGW32__ */
+
+extern const ProfileDescriptor languageProfile;
+
+#define CORE_TASK_CALLBACK(name) void name (void *data)
+typedef CORE_TASK_CALLBACK(CoreTaskCallback);
+extern int runCoreTask (CoreTaskCallback *callback, void *data, int wait);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CORE */
diff --git a/Programs/crc_algorithms.c b/Programs/crc_algorithms.c
new file mode 100644
index 0000000..ff06d7e
--- /dev/null
+++ b/Programs/crc_algorithms.c
@@ -0,0 +1,964 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "crc_algorithms.h"
+
+#define CRC_ALGORITHM_SYMBOL(name) crcAlgorithm_ ## name
+#define CRC_ALGORITHM_DEFINITION(name) static const CRCAlgorithm CRC_ALGORITHM_SYMBOL(name)
+#define CRC_SECONDARY_NAMES(...) .secondaryNames = (const char *const []){__VA_ARGS__, NULL}
+
+/*
+ * These CRC algorithms have been copied from:
+ * http://reveng.sourceforge.net/crc-catalogue/: 1-15.htm, 16.htm, 17plus.htm
+ */
+
+CRC_ALGORITHM_DEFINITION(CRC8_AUTOSAR) = {
+  .primaryName = "CRC-8/AUTOSAR",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X2F),
+  .initialValue = UINT8_MAX,
+  .xorMask = UINT8_MAX,
+
+  .checkValue = UINT8_C(0XDF),
+  .residue = UINT8_C(0X42),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_BLUETOOTH) = {
+  .primaryName = "CRC-8/BLUETOOTH",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0XA7),
+
+  .checkValue = UINT8_C(0X26),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_CDMA2000) = {
+  .primaryName = "CRC-8/CDMA2000",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X9B),
+  .initialValue = UINT8_MAX,
+
+  .checkValue = UINT8_C(0XDA),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_DARC) = {
+  .primaryName = "CRC-8/DARC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0X39),
+
+  .checkValue = UINT8_C(0X15),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_DVB_S2) = {
+  .primaryName = "CRC-8/DVB-S2",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0XD5),
+
+  .checkValue = UINT8_C(0XBC),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_GSM_A) = {
+  .primaryName = "CRC-8/GSM-A",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X1D),
+
+  .checkValue = UINT8_C(0X37),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_GSM_B) = {
+  .primaryName = "CRC-8/GSM-B",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X49),
+  .xorMask = UINT8_MAX,
+
+  .checkValue = UINT8_C(0X94),
+  .residue = UINT8_C(0X53),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_I_432_1) = {
+  .primaryName = "CRC-8/I-432-1",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+  CRC_SECONDARY_NAMES("CRC-8/ITU"),
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X07),
+  .xorMask = UINT8_C(0X55),
+
+  .checkValue = UINT8_C(0XA1),
+  .residue = UINT8_C(0XAC),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_I_CODE) = {
+  .primaryName = "CRC-8/I-CODE",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X1D),
+  .initialValue = UINT8_C(0XFD),
+
+  .checkValue = UINT8_C(0X7E),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_LTE) = {
+  .primaryName = "CRC-8/LTE",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X9B),
+
+  .checkValue = UINT8_C(0XEA),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_MAXIM_DOW) = {
+  .primaryName = "CRC-8/MAXIM-DOW",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-8/MAXIM", "DOW-CRC"),
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0X31),
+
+  .checkValue = UINT8_C(0XA1),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_MIFARE_MAD) = {
+  .primaryName = "CRC-8/MIFARE-MAD",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X1D),
+  .initialValue = UINT8_C(0XC7),
+
+  .checkValue = UINT8_C(0X99),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_NRSC_5) = {
+  .primaryName = "CRC-8/NRSC-5",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X31),
+  .initialValue = UINT8_MAX,
+
+  .checkValue = UINT8_C(0XF7),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_OPENSAFETY) = {
+  .primaryName = "CRC-8/OPENSAFETY",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X2F),
+
+  .checkValue = UINT8_C(0X3E),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_ROHC) = {
+  .primaryName = "CRC-8/ROHC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0X07),
+  .initialValue = UINT8_MAX,
+
+  .checkValue = UINT8_C(0XD0),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_SAE_J1850) = {
+  .primaryName = "CRC-8/SAE-J1850",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X1D),
+  .initialValue = UINT8_MAX,
+  .xorMask = UINT8_MAX,
+
+  .checkValue = UINT8_C(0X4B),
+  .residue = UINT8_C(0XC4),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_SMBUS) = {
+  .primaryName = "CRC-8/SMBUS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-8"),
+
+  .checksumWidth = 8,
+  .generatorPolynomial = UINT8_C(0X07),
+
+  .checkValue = UINT8_C(0XF4),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_TECH_3250) = {
+  .primaryName = "CRC-8/TECH-3250",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-8/AES", "CRC-8/EBU"),
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0X1D),
+  .initialValue = UINT8_MAX,
+
+  .checkValue = UINT8_C(0X97),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC8_WCDMA) = {
+  .primaryName = "CRC-8/WCDMA",
+  .algorithmClass = CRC_ALGORITHM_CLASS_THIRD_PARTY,
+
+  .checksumWidth = 8,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT8_C(0X9B),
+
+  .checkValue = UINT8_C(0X25),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_ARC) = {
+  .primaryName = "CRC-16/ARC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("ARC", "CRC-16", "CRC-16/LHA", "CRC-IBM"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X8005),
+
+  .checkValue = UINT16_C(0XBB3D),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_CDMA2000) = {
+  .primaryName = "CRC-16/CDMA2000",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0XC867),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X4C06),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_CMS) = {
+  .primaryName = "CRC-16/CMS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_THIRD_PARTY,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X8005),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XAEE7),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_DDS_110) = {
+  .primaryName = "CRC-16/DDS-110",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X8005),
+  .initialValue = UINT16_C(0X800D),
+
+  .checkValue = UINT16_C(0X9ECF),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_DECT_R) = {
+  .primaryName = "CRC-16/DECT-R",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("R-CRC-16"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X0589),
+  .xorMask = UINT16_C(0X0001),
+
+  .checkValue = UINT16_C(0X007E),
+  .residue = UINT16_C(0X0589),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_DECT_X) = {
+  .primaryName = "CRC-16/DECT-X",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("X-CRC-16"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X0589),
+
+  .checkValue = UINT16_C(0X007F),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_DNP) = {
+  .primaryName = "CRC-16/DNP",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X3D65),
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XEA82),
+  .residue = UINT16_C(0X66C5),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_EN_13757) = {
+  .primaryName = "CRC-16/EN-13757",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X3D65),
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XC2B7),
+  .residue = UINT16_C(0XA366),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_GENIBUS) = {
+  .primaryName = "CRC-16/GENIBUS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/DARC", "CRC-16/EPC", "CRC-16/EPC-C1G2", "CRC-16/I-CODE"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_MAX,
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XD64E),
+  .residue = UINT16_C(0X1D0F),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_GSM) = {
+  .primaryName = "CRC-16/GSM",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XCE3C),
+  .residue = UINT16_C(0X1D0F),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_IBM_3740) = {
+  .primaryName = "CRC-16/IBM-3740",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/AUTOSAR", "CRC-16/CCITT-FALSE"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X29B1),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_IBM_SDLC) = {
+  .primaryName = "CRC-16/IBM-SDLC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/ISO-HDLC", "CRC-16/ISO-IEC-14443-3-B", "CRC-16/X-25", "CRC-B", "X-25"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_MAX,
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X906E),
+  .residue = UINT16_C(0XF0B8),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_ISO_IEC_14443_3_A) = {
+  .primaryName = "CRC-16/ISO-IEC-14443-3-A",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-A"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_C(0XC6C6),
+
+  .checkValue = UINT16_C(0XBF05),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_KERMIT) = {
+  .primaryName = "CRC-16/KERMIT",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/CCITT", "CRC-16/CCITT-TRUE", "CRC-16/V-41-LSB", "CRC-CCITT", "KERMIT"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+
+  .checkValue = UINT16_C(0X2189),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_LJ1200) = {
+  .primaryName = "CRC-16/LJ1200",
+  .algorithmClass = CRC_ALGORITHM_CLASS_THIRD_PARTY,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X6F63),
+
+  .checkValue = UINT16_C(0XBDF4),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_MAXIM_DOW) = {
+  .primaryName = "CRC-16/MAXIM-DOW",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/MAXIM"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X8005),
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X44C2),
+  .residue = UINT16_C(0XB001),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_MCRF4XX) = {
+  .primaryName = "CRC-16/MCRF4XX",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X6F91),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_MODBUS) = {
+  .primaryName = "CRC-16/MODBUS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("MODBUS"),
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X8005),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0X4B37),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_NRSC_5) = {
+  .primaryName = "CRC-16/NRSC-5",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X080B),
+  .initialValue = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XA066),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_OPENSAFETY_A) = {
+  .primaryName = "CRC-16/OPENSAFETY-A",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X5935),
+
+  .checkValue = UINT16_C(0X5D38),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_OPENSAFETY_B) = {
+  .primaryName = "CRC-16/OPENSAFETY-B",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X755B),
+
+  .checkValue = UINT16_C(0X20FE),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_PROFIBUS) = {
+  .primaryName = "CRC-16/PROFIBUS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/IEC-61158-2"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1DCF),
+  .initialValue = UINT16_MAX,
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XA819),
+  .residue = UINT16_C(0XE394),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_RIELLO) = {
+  .primaryName = "CRC-16/RIELLO",
+  .algorithmClass = CRC_ALGORITHM_CLASS_THIRD_PARTY,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_C(0XB2AA),
+
+  .checkValue = UINT16_C(0X63D0),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_SPI_FUJITSU) = {
+  .primaryName = "CRC-16/SPI-FUJITSU",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/AUG-CCITT"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_C(0X1D0F),
+
+  .checkValue = UINT16_C(0XE5CC),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_T10_DIF) = {
+  .primaryName = "CRC-16/T10-DIF",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X8BB7),
+
+  .checkValue = UINT16_C(0XD0DB),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_TELEDISK) = {
+  .primaryName = "CRC-16/TELEDISK",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0XA097),
+
+  .checkValue = UINT16_C(0X0FB3),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_TMS37157) = {
+  .primaryName = "CRC-16/TMS37157",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X1021),
+  .initialValue = UINT16_C(0X89EC),
+
+  .checkValue = UINT16_C(0X26B1),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_UMTS) = {
+  .primaryName = "CRC-16/UMTS",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/BUYPASS", "CRC-16/VERIFONE"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X8005),
+
+  .checkValue = UINT16_C(0XFEE8),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_USB) = {
+  .primaryName = "CRC-16/USB",
+  .algorithmClass = CRC_ALGORITHM_CLASS_THIRD_PARTY,
+
+  .checksumWidth = 16,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT16_C(0X8005),
+  .initialValue = UINT16_MAX,
+  .xorMask = UINT16_MAX,
+
+  .checkValue = UINT16_C(0XB4C8),
+  .residue = UINT16_C(0XB001),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC16_XMODEM) = {
+  .primaryName = "CRC-16/XMODEM",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-16/ACORN", "CRC-16/LTE", "CRC-16/V-41-MSB", "XMODEM", "ZMODEM"),
+
+  .checksumWidth = 16,
+  .generatorPolynomial = UINT16_C(0X1021),
+
+  .checkValue = UINT16_C(0X31C3),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_BLE) = {
+  .primaryName = "CRC-24/BLE",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 24,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT24_C(0X00065B),
+  .initialValue = UINT24_C(0X555555),
+
+  .checkValue = UINT24_C(0XC25A56),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_FLEXRAY_A) = {
+  .primaryName = "CRC-24/FLEXRAY-A",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X5D6DCB),
+  .initialValue = UINT24_C(0XFEDCBA),
+
+  .checkValue = UINT24_C(0X7979BD),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_FLEXRAY_B) = {
+  .primaryName = "CRC-24/FLEXRAY-B",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X5D6DCB),
+  .initialValue = UINT24_C(0XABCDEF),
+
+  .checkValue = UINT24_C(0X1F23B8),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_INTERLAKEN) = {
+  .primaryName = "CRC-24/INTERLAKEN",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X328B63),
+  .initialValue = UINT24_MAX,
+  .xorMask = UINT24_MAX,
+
+  .checkValue = UINT24_C(0XB4F3E6),
+  .residue = UINT24_C(0X144E63),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_LTE_A) = {
+  .primaryName = "CRC-24/LTE-A",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X864CFB),
+
+  .checkValue = UINT24_C(0XCDE703),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_LTE_B) = {
+  .primaryName = "CRC-24/LTE-B",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X800063),
+
+  .checkValue = UINT24_C(0X23EF52),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_OPENPGP) = {
+  .primaryName = "CRC-24/OPENPGP",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-24"),
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X864CFB),
+  .initialValue = UINT24_C(0XB704CE),
+
+  .checkValue = UINT24_C(0X21CF02),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC24_OS_9) = {
+  .primaryName = "CRC-24/OS-9",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 24,
+  .generatorPolynomial = UINT24_C(0X800063),
+  .initialValue = UINT24_MAX,
+  .xorMask = UINT24_MAX,
+
+  .checkValue = UINT24_C(0X200FA5),
+  .residue = UINT24_C(0X800FE3),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_AIXM) = {
+  .primaryName = "CRC-32/AIXM",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-32Q"),
+
+  .checksumWidth = 32,
+  .generatorPolynomial = UINT32_C(0X814141AB),
+
+  .checkValue = UINT32_C(0X3010BF7F),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_AUTOSAR) = {
+  .primaryName = "CRC-32/AUTOSAR",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0XF4ACFB13),
+  .initialValue = UINT32_MAX,
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0X1697D06A),
+  .residue = UINT32_C(0X904CDDBF),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_BASE91_D) = {
+  .primaryName = "CRC-32/BASE91-D",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+  CRC_SECONDARY_NAMES("CRC-32D"),
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0XA833982B),
+  .initialValue = UINT32_MAX,
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0X87315576),
+  .residue = UINT32_C(0X45270551),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_BZIP2) = {
+  .primaryName = "CRC-32/BZIP2",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-32/AAL5", "CRC-32/DECT-B", "B-CRC-32"),
+
+  .checksumWidth = 32,
+  .generatorPolynomial = UINT32_C(0X04C11DB7),
+  .initialValue = UINT32_MAX,
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0XFC891918),
+  .residue = UINT32_C(0XC704DD7B),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_CD_ROM_EDC) = {
+  .primaryName = "CRC-32/CD-ROM-EDC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ACADEMIC,
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0X8001801B),
+
+  .checkValue = UINT32_C(0X6EC2EDC4),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_CKSUM) = {
+  .primaryName = "CRC-32/CKSUM",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CKSUM", "CRC-32/POSIX"),
+
+  .checksumWidth = 32,
+  .generatorPolynomial = UINT32_C(0X04C11DB7),
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0X765E7680),
+  .residue = UINT32_C(0XC704DD7B),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_ISCSI) = {
+  .primaryName = "CRC-32/ISCSI",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-32/BASE91-C", "CRC-32/CASTAGNOLI", "CRC-32/INTERLAKEN", "CRC-32C"),
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0X1EDC6F41),
+  .initialValue = UINT32_MAX,
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0XE3069283),
+  .residue = UINT32_C(0XB798B438),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_ISO_HDLC) = {
+  .primaryName = "CRC-32/ISO-HDLC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+  CRC_SECONDARY_NAMES("CRC-32", "CRC-32/ADCCP", "CRC-32/V-42", "CRC-32/XZ", "PKZIP"),
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0X04C11DB7),
+  .initialValue = UINT32_MAX,
+  .xorMask = UINT32_MAX,
+
+  .checkValue = UINT32_C(0XCBF43926),
+  .residue = UINT32_C(0XDEBB20E3),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_JAMCRC) = {
+  .primaryName = "CRC-32/JAMCRC",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+  CRC_SECONDARY_NAMES("JAMCRC"),
+
+  .checksumWidth = 32,
+  .reflectData = 1,
+  .reflectResult = 1,
+  .generatorPolynomial = UINT32_C(0X04C11DB7),
+  .initialValue = UINT32_MAX,
+
+  .checkValue = UINT32_C(0X340BC6D9),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_MPEG_2) = {
+  .primaryName = "CRC-32/MPEG-2",
+  .algorithmClass = CRC_ALGORITHM_CLASS_ATTESTED,
+
+  .checksumWidth = 32,
+  .generatorPolynomial = UINT32_C(0X04C11DB7),
+  .initialValue = UINT32_MAX,
+
+  .checkValue = UINT32_C(0X0376E6E7),
+};
+
+CRC_ALGORITHM_DEFINITION(CRC32_XFER) = {
+  .primaryName = "CRC-32/XFER",
+  .algorithmClass = CRC_ALGORITHM_CLASS_CONFIRMED,
+  CRC_SECONDARY_NAMES("XFER"),
+
+  .checksumWidth = 32,
+  .generatorPolynomial = UINT32_C(0X000000AF),
+
+  .checkValue = UINT32_C(0XBD0BE338),
+};
+
+const CRCAlgorithm *crcProvidedAlgorithms[] = {
+  &CRC_ALGORITHM_SYMBOL(CRC8_AUTOSAR),
+  &CRC_ALGORITHM_SYMBOL(CRC8_BLUETOOTH),
+  &CRC_ALGORITHM_SYMBOL(CRC8_CDMA2000),
+  &CRC_ALGORITHM_SYMBOL(CRC8_DARC),
+  &CRC_ALGORITHM_SYMBOL(CRC8_DVB_S2),
+  &CRC_ALGORITHM_SYMBOL(CRC8_GSM_A),
+  &CRC_ALGORITHM_SYMBOL(CRC8_GSM_B),
+  &CRC_ALGORITHM_SYMBOL(CRC8_I_432_1),
+  &CRC_ALGORITHM_SYMBOL(CRC8_I_CODE),
+  &CRC_ALGORITHM_SYMBOL(CRC8_LTE),
+  &CRC_ALGORITHM_SYMBOL(CRC8_MAXIM_DOW),
+  &CRC_ALGORITHM_SYMBOL(CRC8_MIFARE_MAD),
+  &CRC_ALGORITHM_SYMBOL(CRC8_NRSC_5),
+  &CRC_ALGORITHM_SYMBOL(CRC8_OPENSAFETY),
+  &CRC_ALGORITHM_SYMBOL(CRC8_ROHC),
+  &CRC_ALGORITHM_SYMBOL(CRC8_SAE_J1850),
+  &CRC_ALGORITHM_SYMBOL(CRC8_SMBUS),
+  &CRC_ALGORITHM_SYMBOL(CRC8_TECH_3250),
+  &CRC_ALGORITHM_SYMBOL(CRC8_WCDMA),
+
+  &CRC_ALGORITHM_SYMBOL(CRC16_ARC),
+  &CRC_ALGORITHM_SYMBOL(CRC16_CDMA2000),
+  &CRC_ALGORITHM_SYMBOL(CRC16_CMS),
+  &CRC_ALGORITHM_SYMBOL(CRC16_DDS_110),
+  &CRC_ALGORITHM_SYMBOL(CRC16_DECT_R),
+  &CRC_ALGORITHM_SYMBOL(CRC16_DECT_X),
+  &CRC_ALGORITHM_SYMBOL(CRC16_DNP),
+  &CRC_ALGORITHM_SYMBOL(CRC16_EN_13757),
+  &CRC_ALGORITHM_SYMBOL(CRC16_GENIBUS),
+  &CRC_ALGORITHM_SYMBOL(CRC16_GSM),
+  &CRC_ALGORITHM_SYMBOL(CRC16_IBM_3740),
+  &CRC_ALGORITHM_SYMBOL(CRC16_IBM_SDLC),
+  &CRC_ALGORITHM_SYMBOL(CRC16_ISO_IEC_14443_3_A),
+  &CRC_ALGORITHM_SYMBOL(CRC16_KERMIT),
+  &CRC_ALGORITHM_SYMBOL(CRC16_LJ1200),
+  &CRC_ALGORITHM_SYMBOL(CRC16_MAXIM_DOW),
+  &CRC_ALGORITHM_SYMBOL(CRC16_MCRF4XX),
+  &CRC_ALGORITHM_SYMBOL(CRC16_MODBUS),
+  &CRC_ALGORITHM_SYMBOL(CRC16_NRSC_5),
+  &CRC_ALGORITHM_SYMBOL(CRC16_OPENSAFETY_A),
+  &CRC_ALGORITHM_SYMBOL(CRC16_OPENSAFETY_B),
+  &CRC_ALGORITHM_SYMBOL(CRC16_PROFIBUS),
+  &CRC_ALGORITHM_SYMBOL(CRC16_RIELLO),
+  &CRC_ALGORITHM_SYMBOL(CRC16_SPI_FUJITSU),
+  &CRC_ALGORITHM_SYMBOL(CRC16_T10_DIF),
+  &CRC_ALGORITHM_SYMBOL(CRC16_TELEDISK),
+  &CRC_ALGORITHM_SYMBOL(CRC16_TMS37157),
+  &CRC_ALGORITHM_SYMBOL(CRC16_UMTS),
+  &CRC_ALGORITHM_SYMBOL(CRC16_USB),
+  &CRC_ALGORITHM_SYMBOL(CRC16_XMODEM),
+
+  &CRC_ALGORITHM_SYMBOL(CRC24_BLE),
+  &CRC_ALGORITHM_SYMBOL(CRC24_FLEXRAY_A),
+  &CRC_ALGORITHM_SYMBOL(CRC24_FLEXRAY_B),
+  &CRC_ALGORITHM_SYMBOL(CRC24_INTERLAKEN),
+  &CRC_ALGORITHM_SYMBOL(CRC24_LTE_A),
+  &CRC_ALGORITHM_SYMBOL(CRC24_LTE_B),
+  &CRC_ALGORITHM_SYMBOL(CRC24_OPENPGP),
+  &CRC_ALGORITHM_SYMBOL(CRC24_OS_9),
+
+  &CRC_ALGORITHM_SYMBOL(CRC32_AIXM),
+  &CRC_ALGORITHM_SYMBOL(CRC32_AUTOSAR),
+  &CRC_ALGORITHM_SYMBOL(CRC32_BASE91_D),
+  &CRC_ALGORITHM_SYMBOL(CRC32_BZIP2),
+  &CRC_ALGORITHM_SYMBOL(CRC32_CD_ROM_EDC),
+  &CRC_ALGORITHM_SYMBOL(CRC32_CKSUM),
+  &CRC_ALGORITHM_SYMBOL(CRC32_ISCSI),
+  &CRC_ALGORITHM_SYMBOL(CRC32_ISO_HDLC),
+  &CRC_ALGORITHM_SYMBOL(CRC32_JAMCRC),
+  &CRC_ALGORITHM_SYMBOL(CRC32_MPEG_2),
+  &CRC_ALGORITHM_SYMBOL(CRC32_XFER),
+
+  NULL
+};
+
+const CRCAlgorithm *
+crcGetProvidedAlgorithm (const char *name) {
+  const CRCAlgorithm **algorithm = crcProvidedAlgorithms;
+
+  while (*algorithm) {
+    if (strcmp(name, (*algorithm)->primaryName) == 0) return *algorithm;
+    const char *const *alias = (*algorithm)->secondaryNames;
+
+    if (alias) {
+      while (*alias) {
+        if (strcmp(name, *alias) == 0) return *algorithm;
+        alias += 1;
+      }
+    }
+
+    algorithm += 1;
+  }
+
+  return NULL;
+}
diff --git a/Programs/crc_generate.c b/Programs/crc_generate.c
new file mode 100644
index 0000000..57c1aaa
--- /dev/null
+++ b/Programs/crc_generate.c
@@ -0,0 +1,223 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "crc_generate.h"
+#include "crc_internal.h"
+#include "log.h"
+
+crc_t
+crcMostSignificantBit (unsigned int width) {
+  return CRC_C(1) << (width - 1);
+}
+
+crc_t
+crcReflectBits (crc_t fromValue, unsigned int width) {
+  crc_t fromBit = crcMostSignificantBit(width);
+  crc_t toBit = 1;
+  crc_t toValue = 0;
+
+  while (fromBit) {
+    if (fromValue & fromBit) toValue |= toBit;
+    fromBit >>= 1;
+    toBit <<= 1;
+  }
+
+  return toValue;
+}
+
+void
+crcReflectValue (crc_t *value, const CRCAlgorithm *algorithm) {
+  *value = crcReflectBits(*value, algorithm->checksumWidth);
+}
+
+void
+crcReflectByte (uint8_t *byte) {
+  *byte = crcReflectBits(*byte, CRC_BYTE_WIDTH);
+}
+
+static uint8_t crcDirectDataTranslationTable[CRC_BYTE_INDEXED_TABLE_SIZE] = {1};
+static uint8_t crcReflectedDataTranslationTable[CRC_BYTE_INDEXED_TABLE_SIZE] = {1};
+
+static void
+crcMakeDataTranslationTable (CRCProperties *properties, const CRCAlgorithm *algorithm) {
+  if (algorithm->reflectData) {
+    uint8_t *table = crcReflectedDataTranslationTable;
+    properties->dataTranslationTable = table;
+
+    if (*table) {
+      for (unsigned int index=0; index<=UINT8_MAX; index+=1) {
+        uint8_t *byte = &table[index];
+        *byte = index;
+        crcReflectByte(byte);
+      }
+    }
+  } else {
+    uint8_t *table = crcDirectDataTranslationTable;
+    properties->dataTranslationTable = table;
+
+    if (*table) {
+      for (unsigned int index=0; index<=UINT8_MAX; index+=1) {
+        table[index] = index;
+      }
+    }
+  }
+}
+
+static void
+crcMakeRemainderCache (CRCProperties *properties, const CRCAlgorithm *algorithm) {
+  // Compute the remainder for each possible dividend.
+  for (unsigned int dividend=0; dividend<=UINT8_MAX; dividend+=1) {
+    // Start with the dividend followed by zeros.
+    crc_t remainder = dividend << properties->byteShift;
+
+    // Perform modulo-2 division, a bit at a time.
+    for (unsigned int bit=CRC_BYTE_WIDTH; bit>0; bit-=1) {
+      // Try to divide the current data bit.
+      if (remainder & properties->mostSignificantBit) {
+        remainder <<= 1;
+        remainder ^= algorithm->generatorPolynomial;
+      } else {
+        remainder <<= 1;
+      }
+    }
+
+    // Store the result into the table.
+    properties->remainderCache[dividend] = remainder & properties->valueMask;
+  }
+}
+
+void
+crcMakeProperties (CRCProperties *properties, const CRCAlgorithm *algorithm) {
+  properties->byteShift = algorithm->checksumWidth - CRC_BYTE_WIDTH;
+  properties->mostSignificantBit = crcMostSignificantBit(algorithm->checksumWidth);
+  properties->valueMask = (properties->mostSignificantBit - 1) | properties->mostSignificantBit;
+
+  crcMakeDataTranslationTable(properties, algorithm);
+  crcMakeRemainderCache(properties, algorithm);
+}
+
+void
+crcResetGenerator (CRCGenerator *crc) {
+  crc->currentValue = crc->algorithm.initialValue;
+}
+
+CRCGenerator *
+crcNewGenerator (const CRCAlgorithm *algorithm) {
+  CRCGenerator *crc;
+  const char *name = algorithm->primaryName;
+  size_t size = sizeof(*crc) + strlen(name) + 1;
+
+  if ((crc = malloc(size))) {
+    memset(crc, 0, size);
+
+    crc->algorithm = *algorithm;
+    strcpy(crc->algorithmName, name);
+    crc->algorithm.primaryName = crc->algorithmName;
+
+    crcMakeProperties(&crc->properties, &crc->algorithm);
+    crcResetGenerator(crc);
+    return crc;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+crcDestroyGenerator (CRCGenerator *crc) {
+  free(crc);
+}
+
+void
+crcAddByte (CRCGenerator *crc, uint8_t byte) {
+  byte = crc->properties.dataTranslationTable[byte];
+  byte ^= crc->currentValue >> crc->properties.byteShift;
+  crc->currentValue = crc->properties.remainderCache[byte] ^ (crc->currentValue << CRC_BYTE_WIDTH);
+  crc->currentValue &= crc->properties.valueMask;
+}
+
+void
+crcAddData (CRCGenerator *crc, const void *data, size_t size) {
+  const uint8_t *byte = data;
+  const uint8_t *end = byte + size;
+  while (byte < end) crcAddByte(crc, *byte++);
+}
+
+crc_t
+crcGetValue (const CRCGenerator *crc) {
+  return crc->currentValue;
+}
+
+crc_t
+crcGetChecksum (const CRCGenerator *crc) {
+  const CRCAlgorithm *algorithm = &crc->algorithm;
+  crc_t checksum = crc->currentValue;
+  if (crc->algorithm.reflectResult) crcReflectValue(&checksum, algorithm);
+  checksum ^= algorithm->xorMask;
+  return checksum;
+}
+
+crc_t
+crcGetResidue (CRCGenerator *crc) {
+  const CRCAlgorithm *algorithm = &crc->algorithm;
+
+  crc_t originalValue = crc->currentValue;
+  crc_t checksum = crcGetChecksum(crc);
+
+  unsigned int size = algorithm->checksumWidth / CRC_BYTE_WIDTH;
+  uint8_t data[size];
+
+  if (algorithm->reflectResult) {
+    uint8_t *byte = data;
+    const uint8_t *end = byte + size;
+
+    while (byte < end) {
+      *byte++ = checksum;
+      checksum >>= CRC_BYTE_WIDTH;
+    }
+  } else {
+    uint8_t *byte = data + size;
+
+    while (byte-- > data) {
+      *byte = checksum;
+      checksum >>= CRC_BYTE_WIDTH;
+    }
+  }
+
+  crcAddData(crc, data, size);
+  crc_t residue = crc->currentValue;
+  if (algorithm->reflectResult) crcReflectValue(&residue, algorithm);
+
+  crc->currentValue = originalValue;
+  return residue;
+}
+
+const CRCAlgorithm *
+crcGetAlgorithm (const CRCGenerator *crc) {
+  return &crc->algorithm;
+}
+
+const CRCProperties *
+crcGetProperties (const CRCGenerator *crc) {
+  return &crc->properties;
+}
diff --git a/Programs/crc_internal.h b/Programs/crc_internal.h
new file mode 100644
index 0000000..606b91c
--- /dev/null
+++ b/Programs/crc_internal.h
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CRC_INTERNAL
+#define BRLTTY_INCLUDED_CRC_INTERNAL
+
+#include "crc_generate.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct CRCGeneratorStruct {
+  CRCAlgorithm algorithm;
+  CRCProperties properties;
+  crc_t currentValue;
+  char algorithmName[];
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CRC_INTERNAL */
diff --git a/Programs/crc_verify.c b/Programs/crc_verify.c
new file mode 100644
index 0000000..307f092
--- /dev/null
+++ b/Programs/crc_verify.c
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "crc_verify.h"
+#include "crc_internal.h"
+#include "log.h"
+
+const uint8_t crcCheckData[] = {
+  0X31, 0X32, 0X33, 0X34, 0X35, 0X36, 0X37, 0X38, 0X39
+};
+
+const uint8_t crcCheckSize = sizeof(crcCheckData);
+
+static void
+crcLogMismatch (const CRCGenerator *crc,const char *what, crc_t actual, crc_t expected) {
+  logMessage(LOG_WARNING,
+    "CRC %s mismatch: %s: Actual:%"PRIcrc " Expected:%"PRIcrc,
+    what, crc->algorithmName, actual, expected
+  );
+}
+
+int
+crcVerifyChecksum (const CRCGenerator *crc, crc_t expected) {
+  crc_t actual = crcGetChecksum(crc);
+  int ok = actual == expected;
+  if (!ok) crcLogMismatch(crc, "checksum", actual, expected);
+  return ok;
+}
+
+int
+crcVerifyResidue (CRCGenerator *crc) {
+  crc_t actual = crcGetResidue(crc);
+  crc_t expected = crc->algorithm.residue;
+  int ok = actual == expected;
+  if (!ok) crcLogMismatch(crc, "residue", actual, expected);
+  return ok;
+}
+
+int
+crcVerifyAlgorithmWithData (
+  const CRCAlgorithm *algorithm,
+  const void *data, size_t size, crc_t expected
+) {
+  CRCGenerator *crc = crcNewGenerator(algorithm);
+  crcAddData(crc, data, size);
+
+  int ok = crcVerifyChecksum(crc, expected);
+  if (ok) ok = crcVerifyResidue(crc);
+
+  crcDestroyGenerator(crc);
+  return ok;
+}
+
+int
+crcVerifyAlgorithmWithString (
+  const CRCAlgorithm *algorithm,
+  const char *string, crc_t expected
+) {
+  return crcVerifyAlgorithmWithData(algorithm, string, strlen(string), expected);
+}
+
+int
+crcVerifyAlgorithm (const CRCAlgorithm *algorithm) {
+  return crcVerifyAlgorithmWithData(
+    algorithm, crcCheckData, crcCheckSize, algorithm->checkValue
+  );
+}
+
+int
+crcVerifyProvidedAlgorithms (void) {
+  int ok = 1;
+  const CRCAlgorithm **algorithm = crcProvidedAlgorithms;
+
+  while (*algorithm) {
+    if (!crcVerifyAlgorithm(*algorithm)) ok = 0;
+    algorithm += 1;
+  }
+
+  return ok;
+}
diff --git a/Programs/crctest.c b/Programs/crctest.c
new file mode 100644
index 0000000..e098892
--- /dev/null
+++ b/Programs/crctest.c
@@ -0,0 +1,132 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "program.h"
+#include "cmdline.h"
+#include "crc.h"
+
+static char *opt_algorithmName;
+static char *opt_algorithmClass;
+static char *opt_checksumWidth;
+static char *opt_reflectData;
+static char *opt_reflectResult;
+static char *opt_generatorPolynomial;
+static char *opt_initialValue;
+static char *opt_xorMask;
+static char *opt_checkValue;
+static char *opt_residue;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "name",
+    .letter = 'n',
+    .argument = "string",
+    .setting.string = &opt_algorithmName,
+    .description = "the algorithm name"
+  },
+
+  { .word = "class",
+    .letter = 'c',
+    .argument = "string",
+    .setting.string = &opt_algorithmClass,
+    .description = "the algorithm class"
+  },
+
+  { .word = "width",
+    .letter = 'w',
+    .argument = "integer",
+    .setting.string = &opt_checksumWidth,
+    .description = "the checksum width"
+  },
+
+  { .word = "reflect-data",
+    .letter = 'd',
+    .argument = "boolean",
+    .setting.string = &opt_reflectData,
+    .description = "reflect the data"
+  },
+
+  { .word = "reflect-result",
+    .letter = 'r',
+    .argument = "boolean",
+    .setting.string = &opt_reflectResult,
+    .description = "reflect the result"
+  },
+
+  { .word = "polynomial",
+    .letter = 'p',
+    .argument = "integer",
+    .setting.string = &opt_generatorPolynomial,
+    .description = "the generator polynomial"
+  },
+
+  { .word = "initial-value",
+    .letter = 'i',
+    .argument = "integer",
+    .setting.string = &opt_initialValue,
+    .description = "the initial value"
+  },
+
+  { .word = "xor-mask",
+    .letter = 'x',
+    .argument = "integer",
+    .setting.string = &opt_xorMask,
+    .description = "the final xor mask"
+  },
+
+  { .word = "check-value",
+    .letter = 'C',
+    .argument = "integer",
+    .setting.string = &opt_checkValue,
+    .description = "the check value"
+  },
+
+  { .word = "residue",
+    .letter = 'R',
+    .argument = "integer",
+    .setting.string = &opt_residue,
+    .description = "the residue"
+  },
+END_OPTION_TABLE(programOptions)
+
+static int
+validateOptions (void) {
+  return 1;
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "crctest",
+
+      .usage = {
+        .purpose = strtext("Test supported CRC (Cyclic Redundancy Check) checksum algorithms."),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (!validateOptions()) return PROG_EXIT_SYNTAX;
+
+  if (!crcVerifyProvidedAlgorithms()) return PROG_EXIT_FATAL;
+  return PROG_EXIT_SUCCESS;
+}
diff --git a/Programs/ctb.auto.h b/Programs/ctb.auto.h
new file mode 100644
index 0000000..b7c04e1
--- /dev/null
+++ b/Programs/ctb.auto.h
@@ -0,0 +1 @@
+[0X1117] = 0X00
diff --git a/Programs/ctb_compile.c b/Programs/ctb_compile.c
new file mode 100644
index 0000000..9d97516
--- /dev/null
+++ b/Programs/ctb_compile.c
@@ -0,0 +1,1045 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+ 
+#include "log.h"
+#include "parse.h"
+#include "file.h"
+#include "datafile.h"
+#include "dataarea.h"
+#include "unicode.h"
+#include "utf8.h"
+#include "charset.h"
+#include "ctb.h"
+#include "ctb_internal.h"
+#include "brl_dots.h"
+#include "cldr.h"
+#include "hostcmd.h"
+
+static const wchar_t *const characterClassNames[] = {
+  WS_C("space"),
+  WS_C("letter"),
+  WS_C("digit"),
+  WS_C("punctuation"),
+  WS_C("uppercase"),
+  WS_C("lowercase"),
+  NULL
+};
+
+struct CharacterClass {
+  struct CharacterClass *next;
+  ContractionTableCharacterAttributes attribute;
+  BYTE length;
+  wchar_t name[1];
+};
+
+static const wchar_t *const opcodeNames[CTO_None] = {
+  [CTO_CapitalSign] = WS_C("capsign"),
+  [CTO_BeginCapitalSign] = WS_C("begcaps"),
+  [CTO_EndCapitalSign] = WS_C("endcaps"),
+
+  [CTO_LetterSign] = WS_C("letsign"),
+  [CTO_NumberSign] = WS_C("numsign"),
+
+  [CTO_Literal] = WS_C("literal"),
+  [CTO_Always] = WS_C("always"),
+  [CTO_Repeatable] = WS_C("repeatable"),
+
+  [CTO_LargeSign] = WS_C("largesign"),
+  [CTO_LastLargeSign] = WS_C("lastlargesign"),
+  [CTO_WholeWord] = WS_C("word"),
+  [CTO_JoinedWord] = WS_C("joinword"),
+  [CTO_LowWord] = WS_C("lowword"),
+  [CTO_Contraction] = WS_C("contraction"),
+
+  [CTO_SuffixableWord] = WS_C("sufword"),
+  [CTO_PrefixableWord] = WS_C("prfword"),
+  [CTO_BegWord] = WS_C("begword"),
+  [CTO_BegMidWord] = WS_C("begmidword"),
+  [CTO_MidWord] = WS_C("midword"),
+  [CTO_MidEndWord] = WS_C("midendword"),
+  [CTO_EndWord] = WS_C("endword"),
+
+  [CTO_PrePunc] = WS_C("prepunc"),
+  [CTO_PostPunc] = WS_C("postpunc"),
+
+  [CTO_BegNum] = WS_C("begnum"),
+  [CTO_MidNum] = WS_C("midnum"),
+  [CTO_EndNum] = WS_C("endnum"),
+
+  [CTO_Class] = WS_C("class"),
+  [CTO_After] = WS_C("after"),
+  [CTO_Before] = WS_C("before"),
+
+  [CTO_Replace] = WS_C("replace")
+};
+
+typedef struct {
+  DataArea *area;
+
+  ContractionTableCharacter *characterTable;
+  int characterTableSize;
+  int characterEntryCount;
+
+  struct CharacterClass *characterClasses;
+  ContractionTableCharacterAttributes characterClassAttribute;
+
+  unsigned char opcodeNameLengths[CTO_None];
+} ContractionTableData;
+
+static inline ContractionTableHeader *
+getContractionTableHeader (ContractionTableData *ctd) {
+  return getDataItem(ctd->area, 0);
+}
+
+static ContractionTableCharacter *
+getCharacterEntry (wchar_t character, ContractionTableData *ctd) {
+  int first = 0;
+  int last = ctd->characterEntryCount - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    ContractionTableCharacter *entry = &ctd->characterTable[current];
+
+    if (entry->value < character) {
+      first = current + 1;
+    } else if (entry->value > character) {
+      last = current - 1;
+    } else {
+      return entry;
+    }
+  }
+
+  if (ctd->characterEntryCount == ctd->characterTableSize) {
+    int newSize = ctd->characterTableSize;
+    newSize = newSize? newSize<<1: 0X80;
+
+    {
+      ContractionTableCharacter *newTable = realloc(ctd->characterTable, (newSize * sizeof(*newTable)));
+
+      if (!newTable) {
+        logMallocError();
+        return NULL;
+      }
+
+      ctd->characterTable = newTable;
+      ctd->characterTableSize = newSize;
+    }
+  }
+
+  memmove(&ctd->characterTable[first+1],
+          &ctd->characterTable[first],
+          (ctd->characterEntryCount - first) * sizeof(*ctd->characterTable));
+  ctd->characterEntryCount += 1;
+
+  {
+    ContractionTableCharacter *entry = &ctd->characterTable[first];
+    memset(entry, 0, sizeof(*entry));
+    entry->value = character;
+    return entry;
+  }
+}
+
+static int
+saveCharacterTable (ContractionTableData *ctd) {
+  DataOffset offset;
+  if (!ctd->characterEntryCount) return 1;
+  if (!saveDataItem(ctd->area, &offset, ctd->characterTable,
+                    ctd->characterEntryCount * sizeof(ctd->characterTable[0]),
+                    __alignof__(ctd->characterTable[0])))
+    return 0;
+
+  {
+    ContractionTableHeader *header = getContractionTableHeader(ctd);
+    header->characters = offset;
+    header->characterCount = ctd->characterEntryCount;
+  }
+
+  return 1;
+}
+
+static ContractionTableRule *
+addByteRule (
+  DataFile *file,
+  ContractionTableOpcode opcode,
+  const DataString *find,
+  const ByteOperand *replace,
+  ContractionTableCharacterAttributes after,
+  ContractionTableCharacterAttributes before,
+  ContractionTableData *ctd
+) {
+  DataOffset ruleOffset;
+  size_t ruleSize = sizeof(ContractionTableRule);
+  if (find) ruleSize += find->length * sizeof(find->characters[0]);
+  if (replace) ruleSize += replace->length;
+
+  if (allocateDataItem(ctd->area, &ruleOffset, ruleSize, __alignof__(ContractionTableRule))) {
+    ContractionTableRule *newRule = getDataItem(ctd->area, ruleOffset);
+
+    newRule->opcode = opcode;
+    newRule->after = after;
+    newRule->before = before;
+
+    if (find) {
+      wmemcpy(&newRule->findrep[0], &find->characters[0],
+              (newRule->findlen = find->length));
+    } else {
+      newRule->findlen = 0;
+    }
+
+    if (replace) {
+      memcpy(&newRule->findrep[newRule->findlen], &replace->bytes[0],
+             (newRule->replen = replace->length));
+    } else {
+      newRule->replen = 0;
+    }
+
+    /*link new rule into table.*/
+    {
+      ContractionTableOffset *offsetAddress;
+
+      if (newRule->findlen == 1) {
+        ContractionTableCharacter *ctc = getCharacterEntry(newRule->findrep[0], ctd);
+        if (!ctc) return NULL;
+
+        switch (newRule->opcode) {
+          case CTO_Repeatable:
+            if (ctc->always) break;
+            /* fall through */
+          case CTO_Always:
+            ctc->always = ruleOffset;
+            /* fall through */
+          default:
+            break;
+        }
+
+        if (iswupper(ctc->value)) {
+          ctc = getCharacterEntry(towlower(ctc->value), ctd);
+          if (!ctc) return NULL;
+        }
+
+        offsetAddress = &ctc->rules;
+      } else {
+        offsetAddress = &getContractionTableHeader(ctd)->rules[CTH(newRule->findrep)];
+      }
+
+      while (*offsetAddress) {
+        ContractionTableRule *currentRule = getDataItem(ctd->area, *offsetAddress);
+
+        if (newRule->findlen > currentRule->findlen) break;
+
+        if (newRule->findlen == currentRule->findlen) {
+          if ((newRule->opcode == currentRule->opcode) &&
+              (newRule->after == currentRule->after) &&
+              (newRule->before == currentRule->before) &&
+              (wmemcmp(newRule->findrep, currentRule->findrep, newRule->findlen) == 0))
+            break;
+
+          if ((currentRule->opcode == CTO_Always) && (newRule->opcode != CTO_Always))
+            break;
+        }
+
+        offsetAddress = &currentRule->next;
+      }
+
+      newRule->next = *offsetAddress;
+      *offsetAddress = ruleOffset;
+    }
+
+    return newRule;
+  }
+
+  return NULL;
+}
+
+static ContractionTableRule *
+addTextRule (
+  DataFile *file,
+  ContractionTableOpcode opcode,
+  const DataString *find,
+  const DataString *replace,
+  ContractionTableCharacterAttributes after,
+  ContractionTableCharacterAttributes before,
+  ContractionTableData *ctd
+) {
+  ByteOperand text;
+  unsigned char *to = text.bytes;
+  const unsigned char *toEnd = to + ARRAY_COUNT(text.bytes);
+
+  const wchar_t *from = replace->characters;
+  const wchar_t *fromEnd = from + replace->length;
+
+  while (from < fromEnd) {
+    Utf8Buffer buffer;
+    size_t length = convertWcharToUtf8(*from++, buffer);
+
+    if (length > (toEnd - to)) {
+      reportDataError(file, "replace text too long");
+      break;
+    }
+
+    to = mempcpy(to, buffer, length);
+  }
+
+  text.length = to - text.bytes;
+  return addByteRule(file, opcode, find, &text, after, before, ctd);
+}
+
+static const struct CharacterClass *
+findCharacterClass (const wchar_t *name, int length, ContractionTableData *ctd) {
+  const struct CharacterClass *class = ctd->characterClasses;
+
+  while (class) {
+    if (length == class->length)
+      if (wmemcmp(name, class->name, length) == 0)
+        return class;
+
+    class = class->next;
+  }
+
+  return NULL;
+}
+
+static struct CharacterClass *
+addCharacterClass (DataFile *file, const wchar_t *name, int length, ContractionTableData *ctd) {
+  struct CharacterClass *class;
+
+  if (ctd->characterClassAttribute) {
+    if ((class = malloc(sizeof(*class) + ((length - 1) * sizeof(class->name[0]))))) {
+      memset(class, 0, sizeof(*class));
+      wmemcpy(class->name, name, (class->length = length));
+
+      class->attribute = ctd->characterClassAttribute;
+      ctd->characterClassAttribute <<= 1;
+
+      class->next = ctd->characterClasses;
+      ctd->characterClasses = class;
+      return class;
+    } else {
+      logMallocError();
+    }
+  } else {
+    reportDataError(file, "character class table overflow: %.*" PRIws, length, name);
+  }
+
+  return NULL;
+}
+
+static int
+getCharacterClass (DataFile *file, const struct CharacterClass **class, ContractionTableData *ctd) {
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, "character class name")) {
+    if ((*class = findCharacterClass(operand.characters, operand.length, ctd))) return 1;
+    reportDataError(file, "character class not defined: %.*" PRIws, operand.length, operand.characters);
+  }
+
+  return 0;
+}
+
+static void
+deallocateCharacterClasses (ContractionTableData *ctd) {
+  while (ctd->characterClasses) {
+    struct CharacterClass *class = ctd->characterClasses;
+    ctd->characterClasses = ctd->characterClasses->next;
+    free(class);
+  }
+}
+
+static int
+allocateCharacterClasses (ContractionTableData *ctd) {
+  const wchar_t *const *name = characterClassNames;
+
+  while (*name) {
+    if (!addCharacterClass(NULL, *name, wcslen(*name), ctd)) {
+      deallocateCharacterClasses(ctd);
+      return 0;
+    }
+
+    name += 1;
+  }
+
+  return 1;
+}
+
+const wchar_t *
+getContractionTableOpcodeName (ContractionTableOpcode opcode) {
+  const wchar_t *name = NULL;
+
+  if (opcode >= 0) {
+    if (opcode < ARRAY_COUNT(opcodeNames)) {
+      name = opcodeNames[opcode];
+    }
+  }
+
+  if (!name) name = WS_C("<unknown>");
+  return name;
+}
+
+static ContractionTableOpcode
+getOpcode (DataFile *file, ContractionTableData *ctd) {
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, "opcode")) {
+    ContractionTableOpcode opcode;
+
+    for (opcode=0; opcode<CTO_None; opcode+=1)
+      if (operand.length == ctd->opcodeNameLengths[opcode])
+        if (wmemcmp(operand.characters, opcodeNames[opcode], operand.length) == 0)
+          return opcode;
+
+    reportDataError(file, "opcode not defined: %.*" PRIws, operand.length, operand.characters);
+  }
+
+  return CTO_None;
+}
+
+static int
+saveCellsOperand (DataFile *file, DataOffset *offset, const ByteOperand *sequence, ContractionTableData *ctd) {
+  if (allocateDataItem(ctd->area, offset, sequence->length+1, __alignof__(BYTE))) {
+    BYTE *address = getDataItem(ctd->area, *offset);
+    memcpy(address+1, sequence->bytes, (*address = sequence->length));
+    return 1;
+  }
+  return 0;
+}
+
+static int
+getReplacePattern (DataFile *file, ByteOperand *replace) {
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, "replacement pattern")) {
+    if ((operand.length == 1) && (operand.characters[0] == WC_C('='))) {
+      replace->length = 0;
+      return 1;
+    }
+
+    if (parseCellsOperand(file, replace, operand.characters, operand.length)) return 1;
+  }
+
+  return 0;
+}
+
+static int
+getFindText (DataFile *file, DataString *find) {
+  return getDataString(file, find, 0, "find text");
+}
+
+static int
+getReplaceText (DataFile *file, DataString *replace) {
+  return getDataString(file, replace, 0, "replace text");
+}
+
+static DATA_OPERANDS_PROCESSOR(processContractionTableDirective) {
+  ContractionTableData *ctd = data;
+
+  ContractionTableCharacterAttributes after = 0;
+  ContractionTableCharacterAttributes before = 0;
+
+  while (1) {
+    ContractionTableOpcode opcode;
+
+    switch ((opcode = getOpcode(file, ctd))) {
+      case CTO_None:
+        break;
+
+      case CTO_Always:
+      case CTO_LargeSign:
+      case CTO_LastLargeSign:
+      case CTO_WholeWord:
+      case CTO_JoinedWord:
+      case CTO_LowWord:
+      case CTO_SuffixableWord:
+      case CTO_PrefixableWord:
+      case CTO_BegWord:
+      case CTO_BegMidWord:
+      case CTO_MidWord:
+      case CTO_MidEndWord:
+      case CTO_EndWord:
+      case CTO_PrePunc:
+      case CTO_PostPunc:
+      case CTO_BegNum:
+      case CTO_MidNum:
+      case CTO_EndNum:
+      case CTO_Repeatable: {
+        DataString find;
+        ByteOperand replace;
+
+        if (getFindText(file, &find))
+          if (getReplacePattern(file, &replace))
+            if (!addByteRule(file, opcode, &find, &replace, after, before, ctd))
+              return 0;
+
+        break;
+      }
+
+      case CTO_Contraction:
+      case CTO_Literal: {
+        DataString find;
+        if (getFindText(file, &find))
+          if (!addByteRule(file, opcode, &find, NULL, after, before, ctd))
+            return 0;
+        break;
+      }
+
+      case CTO_CapitalSign: {
+        ByteOperand cells;
+        if (getCellsOperand(file, &cells, "capital sign")) {
+          DataOffset offset;
+          if (!saveCellsOperand(file, &offset, &cells, ctd)) return 0;
+          getContractionTableHeader(ctd)->capitalSign = offset;
+        }
+        break;
+      }
+
+      case CTO_BeginCapitalSign: {
+        ByteOperand cells;
+        if (getCellsOperand(file, &cells, "begin capital sign")) {
+          DataOffset offset;
+          if (!saveCellsOperand(file, &offset, &cells, ctd)) return 0;
+          getContractionTableHeader(ctd)->beginCapitalSign = offset;
+        }
+        break;
+      }
+
+      case CTO_EndCapitalSign: {
+        ByteOperand cells;
+        if (getCellsOperand(file, &cells, "end capital sign")) {
+          DataOffset offset;
+          if (!saveCellsOperand(file, &offset, &cells, ctd)) return 0;
+          getContractionTableHeader(ctd)->endCapitalSign = offset;
+        }
+        break;
+      }
+
+      case CTO_LetterSign: {
+        ByteOperand cells;
+        if (getCellsOperand(file, &cells, "letter sign")) {
+          DataOffset offset;
+          if (!saveCellsOperand(file, &offset, &cells, ctd)) return 0;
+          getContractionTableHeader(ctd)->letterSign = offset;
+        }
+        break;
+      }
+
+      case CTO_NumberSign: {
+        ByteOperand cells;
+        if (getCellsOperand(file, &cells, "number sign")) {
+          DataOffset offset;
+          if (!saveCellsOperand(file, &offset, &cells, ctd)) return 0;
+          getContractionTableHeader(ctd)->numberSign = offset;
+        }
+        break;
+      }
+
+      case CTO_Class: {
+        DataOperand name;
+
+        if (getDataOperand(file, &name, "character class name")) {
+          const struct CharacterClass *class;
+
+          if ((class = findCharacterClass(name.characters, name.length, ctd))) {
+            reportDataError(file, "character class already defined: %.*" PRIws,
+                            name.length, name.characters);
+          } else if ((class = addCharacterClass(file, name.characters, name.length, ctd))) {
+            DataString characters;
+
+            if (getDataString(file, &characters, 0, "characters")) {
+              for (int index=0; index<characters.length; index+=1) {
+                wchar_t character = characters.characters[index];
+                ContractionTableCharacter *entry = getCharacterEntry(character, ctd);
+                if (!entry) return 0;
+                entry->attributes |= class->attribute;
+              }
+            }
+          }
+        }
+
+        break;
+      }
+
+      {
+        ContractionTableCharacterAttributes *attributes;
+        const struct CharacterClass *class;
+
+      case CTO_After:
+        attributes = &after;
+        goto doClass;
+      case CTO_Before:
+        attributes = &before;
+      doClass:
+
+        if (getCharacterClass(file, &class, ctd)) {
+          *attributes |= class->attribute;
+          continue;
+        }
+        break;
+      }
+
+      case CTO_Replace: {
+        DataString find;
+        DataString replace;
+
+        if (getFindText(file, &find))
+          if (getReplaceText(file, &replace))
+            if (!addTextRule(file, opcode, &find, &replace, after, before, ctd))
+              return 0;
+
+        break;
+      }
+
+      default:
+        reportDataError(file, "unimplemented opcode: %" PRIws, getContractionTableOpcodeName(opcode));
+        break;
+    }
+
+    return 1;
+  }
+}
+
+typedef struct {
+  DataFile *file;
+  ContractionTableData *ctd;
+} AnnotationHandlerData;
+
+static CLDR_ANNOTATION_HANDLER(handleAnnotation) {
+  const AnnotationHandlerData *ahd = parameters->data;
+  DataFile *file = ahd->file;
+  ContractionTableData *ctd = ahd->ctd;
+
+  DataString find;
+  const char *findUTF8 = parameters->sequence;
+  size_t findSize = strlen(findUTF8) + 1;
+  wchar_t findCharacters[findSize];
+  {
+    const char *byte = findUTF8;
+    wchar_t *character = findCharacters;
+    convertUtf8ToWchars(&byte, &character, findSize);
+    size_t length = character - findCharacters;
+    if (!isEmojiSequence(findCharacters, length)) return 1;
+
+    if (length > ARRAY_COUNT(find.characters)) {
+      reportDataError(file, "CLDR sequence too long");
+      return 1;
+    }
+
+    wmemcpy(find.characters, findCharacters, (find.length = length));
+  }
+
+  ByteOperand replace;
+  {
+    const char *string = parameters->name;
+    size_t length = strlen(string);
+    size_t size = sizeof(replace.bytes);
+
+    if (length > size) {
+      reportDataError(file, "CLDR name too long");
+      return 1;
+    }
+
+    memcpy(replace.bytes, string, (replace.length = length));
+  }
+
+  return !!addByteRule(file, CTO_Replace, &find, &replace, 0, 0, ctd);
+}
+
+static DATA_OPERANDS_PROCESSOR(processEmojiOperands) {
+  ContractionTableData *ctd = data;
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, "CLDR annotations file name/path")) {
+    char *name = getUtf8FromWchars(operand.characters, operand.length, NULL);
+
+    if (name) {
+      AnnotationHandlerData ahd = {
+        .file = file,
+        .ctd = ctd
+      };
+
+      if (!cldrParseFile(name, handleAnnotation, &ahd)) {
+        logMessage(LOG_WARNING, "emoji substitutiion won't be performed");
+      }
+
+      free(name);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processContractionTableOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    {.name=WS_C("emoji"), .processor=processEmojiOperands},
+    {.name=NULL, .processor=processContractionTableDirective},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "contraction table directive", data);
+}
+
+static void
+initializeCommonFields (ContractionTable *table) {
+  table->characters.array = NULL;
+  table->characters.size = 0;
+  table->characters.count = 0;
+
+  table->rules.array = NULL;
+  table->rules.size = 0;
+  table->rules.count = 0;
+}
+
+static void
+destroyCommonFields (ContractionTable *table) {
+  if (table->characters.array) {
+    free(table->characters.array);
+    table->characters.array = NULL;
+  }
+
+  if (table->rules.array) {
+    {
+      ContractionTableRule **rule = table->rules.array;
+      ContractionTableRule **end = rule + table->rules.count;
+      while (rule < end) free(*rule++);
+    }
+
+    free(table->rules.array);
+    table->rules.array = NULL;
+  }
+}
+
+static void
+destroyContractionTable_native (ContractionTable *table) {
+  destroyCommonFields(table);
+
+  if (table->data.internal.size) {
+    free(table->data.internal.header.fields);
+    free(table);
+  }
+}
+
+static const ContractionTableManagementMethods nativeManagementMethods = {
+  .destroy = destroyContractionTable_native
+};
+
+static ContractionTable *
+newContractionTable (const unsigned char *bytes, size_t size) {
+  ContractionTable *table;
+
+  if ((table = malloc(sizeof(*table)))) {
+    table->managementMethods = &nativeManagementMethods;
+    table->translationMethods = getContractionTableTranslationMethods_native();
+    initializeCommonFields(table);
+
+    table->data.internal.header.bytes = bytes;
+    table->data.internal.size = size;
+  } else {
+    logMallocError();
+  }
+
+  return table;
+}
+
+static ContractionTable *
+compileContractionTable_native (const char *name) {
+  ContractionTable *table = NULL;
+
+  if (setTableDataVariables(CONTRACTION_TABLE_EXTENSION, CONTRACTION_SUBTABLE_EXTENSION)) {
+    ContractionTableData ctd;
+    memset(&ctd, 0, sizeof(ctd));
+
+    ctd.characterTable = NULL;
+    ctd.characterTableSize = 0;
+    ctd.characterEntryCount = 0;
+
+    ctd.characterClasses = NULL;
+    ctd.characterClassAttribute = 1;
+
+    {
+      ContractionTableOpcode opcode;
+
+      for (opcode=0; opcode<CTO_None; opcode+=1)
+        ctd.opcodeNameLengths[opcode] = wcslen(opcodeNames[opcode]);
+    }
+
+    if (allocateCharacterClasses(&ctd)) {
+      if (*name) {
+        if ((ctd.area = newDataArea())) {
+          if (allocateDataItem(ctd.area, NULL, sizeof(ContractionTableHeader), __alignof__(ContractionTableHeader))) {
+            const DataFileParameters parameters = {
+              .processOperands = processContractionTableOperands,
+              .data = &ctd
+            };
+
+            if (processDataFile(name, &parameters)) {
+              if (saveCharacterTable(&ctd)) {
+                table = newContractionTable(getDataItem(ctd.area, 0), getDataSize(ctd.area));
+                resetDataArea(ctd.area);
+              }
+            }
+          }
+
+          destroyDataArea(ctd.area);
+        }
+      } else {
+        table = newContractionTable(getInternalContractionTableBytes(), 0);
+      }
+
+      deallocateCharacterClasses(&ctd);
+    }
+
+    if (ctd.characterTable) free(ctd.characterTable);
+  }
+
+  return table;
+}
+
+int
+startContractionCommand (ContractionTable *table) {
+  if (!table->data.external.commandStarted) {
+    const char *command[] = {table->data.external.command, NULL};
+    HostCommandOptions options;
+
+    initializeHostCommandOptions(&options);
+    options.asynchronous = 1;
+    options.standardInput = &table->data.external.standardInput;
+    options.standardOutput = &table->data.external.standardOutput;
+
+    logMessage(LOG_DEBUG, "starting external contraction table: %s", table->data.external.command);
+    if (runHostCommand(command, &options) != 0) return 0;
+    logMessage(LOG_DEBUG, "external contraction table started: %s", table->data.external.command);
+
+    table->data.external.commandStarted = 1;
+  }
+
+  return 1;
+}
+
+void
+stopContractionCommand (ContractionTable *table) {
+  if (table->data.external.commandStarted) {
+    fclose(table->data.external.standardInput);
+    fclose(table->data.external.standardOutput);
+
+    logMessage(LOG_DEBUG, "external contraction table stopped: %s", table->data.external.command);
+    table->data.external.commandStarted = 0;
+  }
+}
+
+static void
+destroyContractionTable_external (ContractionTable *table) {
+  stopContractionCommand(table);
+  if (table->data.external.input.buffer) free(table->data.external.input.buffer);
+  free(table->data.external.command);
+
+  destroyCommonFields(table);
+  free(table);
+}
+
+static const ContractionTableManagementMethods externalManagementMethods = {
+  .destroy = destroyContractionTable_external
+};
+
+static ContractionTable *
+compileContractionTable_external (const char *name) {
+  ContractionTable *table;
+
+  if ((table = malloc(sizeof(*table)))) {
+    memset(table, 0, sizeof(*table));
+
+    if ((table->data.external.command = strdup(name))) {
+      table->managementMethods = &externalManagementMethods;
+      table->translationMethods = getContractionTableTranslationMethods_external();
+      initializeCommonFields(table);
+
+      table->data.external.commandStarted = 0;
+
+      table->data.external.input.buffer = NULL;
+      table->data.external.input.size = 0;
+
+      if (startContractionCommand(table)) {
+        return table;
+      }
+
+      free(table->data.external.command);
+    } else {
+      logMallocError();
+    }
+
+    free(table);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+#ifdef LOUIS_TABLES_DIRECTORY
+static void
+destroyContractionTable_louis (ContractionTable *table) {
+  free(table->data.louis.tableList);
+
+  destroyCommonFields(table);
+  free(table);
+}
+
+static const ContractionTableManagementMethods louisManagementMethods = {
+  .destroy = destroyContractionTable_louis
+};
+
+static ContractionTable *
+compileContractionTable_louis (const char *fileName) {
+  ContractionTable *table;
+
+  if ((table = malloc(sizeof(*table)))) {
+    memset(table, 0, sizeof(*table));
+
+    if ((table->data.louis.tableList = strdup(fileName))) {
+      table->managementMethods = &louisManagementMethods;
+      table->translationMethods = getContractionTableTranslationMethods_louis();
+      initializeCommonFields(table);
+
+      return table;
+    } else {
+      logMallocError();
+    }
+
+    free(table);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+#endif /* LOUIS_TABLES_DIRECTORY */
+
+typedef ContractionTable *ContractionTableCompileFunction (const char *fileName);
+
+typedef struct {
+  const char *qualifier;
+  ContractionTableCompileFunction *compile;
+  const char *directory;
+} ContractionTableQualifierEntry;
+
+static const ContractionTableQualifierEntry contractionTableQualifierTable[] = {
+#ifdef LOUIS_TABLES_DIRECTORY
+  { .qualifier = "louis",
+    .compile = &compileContractionTable_louis,
+    .directory = LOUIS_TABLES_DIRECTORY
+  },
+#endif /* LOUIS_TABLES_DIRECTORY */
+
+  { .qualifier = NULL }
+};
+
+const ContractionTableQualifierEntry *
+getContractionTableQualifierEntry (const char **fileName) {
+  const ContractionTableQualifierEntry *entry = contractionTableQualifierTable;
+
+  while (entry->qualifier) {
+    if (hasQualifier(fileName, entry->qualifier)) return entry;
+    entry += 1;
+  }
+
+  return NULL;
+}
+
+ContractionTable *
+compileContractionTable (const char *name) {
+  ContractionTableCompileFunction *compile = NULL;
+  const ContractionTableQualifierEntry *ctq = getContractionTableQualifierEntry(&name);
+
+  if (ctq) {
+    compile = ctq->compile;
+  } else {
+    if (!isAbsolutePath(name)) {
+      if (!hasNoQualifier(name)) {
+        logMessage(LOG_ERR, "unsupported contraction table: %s", name);
+        return NULL;
+      }
+    }
+
+    if (testProgramPath(name)) {
+      compile = &compileContractionTable_external;
+    } else {
+      compile = &compileContractionTable_native;
+    }
+  }
+
+  return compile(name);
+}
+
+void
+destroyContractionTable (ContractionTable *table) {
+  table->managementMethods->destroy(table);
+}
+
+char *
+ensureContractionTableExtension (const char *path) {
+  return ensureFileExtension(path, CONTRACTION_TABLE_EXTENSION);
+}
+
+char *
+makeContractionTablePath (const char *directory, const char *name) {
+  const char *qualifier = name;
+  const ContractionTableQualifierEntry *ctq = getContractionTableQualifierEntry(&name);
+  if (!ctq) hasQualifier(&name, NULL);
+  int qualifierLength = name - qualifier;
+
+  char *path;
+  const char *extension;
+
+  if (ctq && ctq->directory) {
+    if (!(path = strdup(ctq->directory))) logMallocError();
+    extension = NULL;
+  } else {
+    path = makePath(directory, CONTRACTION_TABLES_SUBDIRECTORY);
+    extension = CONTRACTION_TABLE_EXTENSION;
+  }
+
+  if (path) {
+    char *file = makeFilePath(path, name, extension);
+
+    free(path);
+    path = NULL;
+
+    if (file) {
+      if (qualifierLength) {
+        char buffer[qualifierLength + strlen(file) + 1];
+        snprintf(buffer, sizeof(buffer), "%.*s%s",
+                 qualifierLength, qualifier, file);
+
+        free(file);
+        if (!(file = strdup(buffer))) logMallocError();
+      }
+
+      if (file) return file;
+    }
+  }
+
+  return NULL;
+}
+
+char *
+getContractionTableForLocale (const char *directory) {
+  return getFileForLocale(directory, makeContractionTablePath);
+}
diff --git a/Programs/ctb_external.c b/Programs/ctb_external.c
new file mode 100644
index 0000000..9c6a9bf
--- /dev/null
+++ b/Programs/ctb_external.c
@@ -0,0 +1,361 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "ctb_translate.h"
+#include "brl_dots.h"
+#include "file.h"
+#include "parse.h"
+#include "utf8.h"
+
+static int
+putExternalRequests (BrailleContractionData *bcd) {
+  typedef enum {
+    REQ_TEXT,
+    REQ_NUMBER
+  } ExternalRequestType;
+
+  typedef struct {
+    const char *name;
+    ExternalRequestType type;
+
+    union {
+      struct {
+        const wchar_t *start;
+        size_t count;
+      } text;
+
+      unsigned int number;
+    } value;
+  } ExternalRequestEntry;
+
+  const ExternalRequestEntry externalRequestTable[] = {
+    { .name = "cursor-position",
+      .type = REQ_NUMBER,
+      .value.number = bcd->input.cursor? bcd->input.cursor-bcd->input.begin+1: 0
+    },
+
+    { .name = "expand-current-word",
+      .type = REQ_NUMBER,
+      .value.number = prefs.expandCurrentWord
+    },
+
+    { .name = "capitalization-mode",
+      .type = REQ_NUMBER,
+      .value.number = prefs.capitalizationMode
+    },
+
+    { .name = "maximum-length",
+      .type = REQ_NUMBER,
+      .value.number = getOutputCount(bcd)
+    },
+
+    { .name = "text",
+      .type = REQ_TEXT,
+      .value.text = {
+        .start = bcd->input.begin,
+        .count = getInputCount(bcd)
+      }
+    },
+
+    { .name = NULL }
+  };
+
+  FILE *stream = bcd->table->data.external.standardInput;
+  const ExternalRequestEntry *req = externalRequestTable;
+
+  while (req->name) {
+    if (fputs(req->name, stream) == EOF) goto outputError;
+    if (fputc('=', stream) == EOF) goto outputError;
+
+    switch (req->type) {
+      case REQ_TEXT: {
+        const wchar_t *character = req->value.text.start;
+        const wchar_t *end = character + req->value.text.count;
+
+        while (character < end) {
+          Utf8Buffer utf8;
+          size_t utfs = convertWcharToUtf8(*character++, utf8);
+
+          if (!utfs) return 0;
+          if (fputs(utf8, stream) == EOF) goto outputError;
+        }
+
+        break;
+      }
+
+      case REQ_NUMBER:
+        if (fprintf(stream, "%u", req->value.number) == EOF) goto outputError;
+        break;
+
+      default:
+        logMessage(LOG_WARNING, "unimplemented external contraction request property type: %s: %u (%s)", bcd->table->data.external.command, req->type, req->name);
+        return 0;
+    }
+
+    if (fputc('\n', stream) == EOF) goto outputError;
+    req += 1;
+  }
+
+  if (fflush(stream) == EOF) goto outputError;
+  return 1;
+
+outputError:
+  logMessage(LOG_WARNING, "external contraction output error: %s: %s", bcd->table->data.external.command, strerror(errno));
+  return 0;
+}
+
+static const unsigned char brfTable[0X40] = {
+  /* 0X20   */ 0,
+  /* 0X21 ! */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X22 " */ BRL_DOT_5,
+  /* 0X23 # */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X24 $ */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X25 % */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X26 & */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X27 ' */ BRL_DOT_3,
+  /* 0X28 ( */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X29 ) */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X2A * */ BRL_DOT_1 | BRL_DOT_6,
+  /* 0X2B + */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X2C , */ BRL_DOT_6,
+  /* 0X2D - */ BRL_DOT_3 | BRL_DOT_6,
+  /* 0X2E . */ BRL_DOT_4 | BRL_DOT_6,
+  /* 0X2F / */ BRL_DOT_3 | BRL_DOT_4,
+  /* 0X30 0 */ BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X31 1 */ BRL_DOT_2,
+  /* 0X32 2 */ BRL_DOT_2 | BRL_DOT_3,
+  /* 0X33 3 */ BRL_DOT_2 | BRL_DOT_5,
+  /* 0X34 4 */ BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X35 5 */ BRL_DOT_2 | BRL_DOT_6,
+  /* 0X36 6 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5,
+  /* 0X37 7 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X38 8 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_6,
+  /* 0X39 9 */ BRL_DOT_3 | BRL_DOT_5,
+  /* 0X3A : */ BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X3B ; */ BRL_DOT_5 | BRL_DOT_6,
+  /* 0X3C < */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_6,
+  /* 0X3D = */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X3E > */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X3F ? */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X40 @ */ BRL_DOT_4,
+  /* 0X41 A */ BRL_DOT_1,
+  /* 0X42 B */ BRL_DOT_1 | BRL_DOT_2,
+  /* 0X43 C */ BRL_DOT_1 | BRL_DOT_4,
+  /* 0X44 D */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X45 E */ BRL_DOT_1 | BRL_DOT_5,
+  /* 0X46 F */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4,
+  /* 0X47 G */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X48 H */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5,
+  /* 0X49 I */ BRL_DOT_2 | BRL_DOT_4,
+  /* 0X4A J */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X4B K */  BRL_DOT_1 | BRL_DOT_3,
+  /* 0X4C L */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3,
+  /* 0X4D M */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4,
+  /* 0X4E N */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X4F O */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_5,
+  /* 0X50 P */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4,
+  /* 0X51 Q */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X52 R */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5,
+  /* 0X53 S */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4,
+  /* 0X54 T */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
+  /* 0X55 U */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_6,
+  /* 0X56 V */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_6,
+  /* 0X57 W */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X58 X */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X59 Y */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X5A Z */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X5B [ */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_6,
+  /* 0X5C \ */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X5D ] */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
+  /* 0X5E ^ */ BRL_DOT_4 | BRL_DOT_5,
+  /* 0X5F _ */ BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6
+};
+
+static int
+handleExternalResponse_brf (BrailleContractionData *bcd, const char *value) {
+  int useDot7 = prefs.capitalizationMode == CTB_CAP_DOT7;
+
+  while (*value && (bcd->output.current < bcd->output.end)) {
+    unsigned char brf = *value++ & 0XFF;
+    unsigned char dots = 0;
+    unsigned char superimpose = 0;
+
+    if ((brf >= 0X60) && (brf <= 0X7F)) {
+      brf -= 0X20;
+    } else if ((brf >= 0X41) && (brf <= 0X5A)) {
+      if (useDot7) superimpose |= BRL_DOT_7;
+    }
+
+    if ((brf >= 0X20) && (brf <= 0X5F)) dots = brfTable[brf - 0X20] | superimpose;
+    *bcd->output.current++ = dots;
+  }
+
+  return 1;
+}
+
+static int
+handleExternalResponse_consumedLength (BrailleContractionData *bcd, const char *value) {
+  int length;
+
+  if (!isInteger(&length, value)) return 0;
+  if (length < 1) return 0;
+  if (length > getInputCount(bcd)) return 0;
+
+  bcd->input.current = bcd->input.begin + length;
+  return 1;
+}
+
+static int
+handleExternalResponse_outputOffsets (BrailleContractionData *bcd, const char *value) {
+  if (bcd->input.offsets) {
+    int previous = CTB_NO_OFFSET;
+    unsigned int count = getInputCount(bcd);
+    unsigned int index = 0;
+
+    while (*value && (index < count)) {
+      int offset;
+
+      {
+        char *delimiter = strchr(value, ',');
+
+        if (delimiter) {
+          int ok;
+
+          {
+            char oldDelimiter = *delimiter;
+            *delimiter = 0;
+            ok = isInteger(&offset, value);
+            *delimiter = oldDelimiter;
+          }
+
+          if (!ok) return 0;
+          value = delimiter + 1;
+        } else if (isInteger(&offset, value)) {
+          value += strlen(value);
+        } else {
+          return 0;
+        }
+      }
+
+      if (offset < ((index == 0)? 0: previous)) return 0;
+      if (offset >= getOutputCount(bcd)) return 0;
+
+      bcd->input.offsets[index++] = (offset == previous)? CTB_NO_OFFSET: offset;
+      previous = offset;
+    }
+  }
+
+  return 1;
+}
+
+typedef struct {
+  const char *name;
+  int (*handler) (BrailleContractionData *bcd, const char *value);
+  unsigned stop:1;
+} ExternalResponseEntry;
+
+static const ExternalResponseEntry externalResponseTable[] = {
+  { .name = "brf",
+    .stop = 1,
+    .handler = handleExternalResponse_brf
+  },
+
+  { .name = "consumed-length",
+    .handler = handleExternalResponse_consumedLength
+  },
+
+  { .name = "output-offsets",
+    .handler = handleExternalResponse_outputOffsets
+  },
+
+  { .name = NULL }
+};
+
+static int
+getExternalResponses (BrailleContractionData *bcd) {
+  FILE *stream = bcd->table->data.external.standardOutput;
+
+  while (readLine(stream, &bcd->table->data.external.input.buffer, &bcd->table->data.external.input.size, NULL)) {
+    int ok = 0;
+    int stop = 0;
+    char *delimiter = strchr(bcd->table->data.external.input.buffer, '=');
+
+    if (delimiter) {
+      const char *value = delimiter + 1;
+      const ExternalResponseEntry *rsp = externalResponseTable;
+
+      char oldDelimiter = *delimiter;
+      *delimiter = 0;
+
+      while (rsp->name) {
+        if (strcmp(bcd->table->data.external.input.buffer, rsp->name) == 0) {
+          if (rsp->handler(bcd, value)) ok = 1;
+          if (rsp->stop) stop = 1;
+          break;
+        }
+
+        rsp += 1;
+      }
+
+      *delimiter = oldDelimiter;
+    }
+
+    if (!ok) logMessage(LOG_WARNING, "unexpected external contraction response: %s: %s", bcd->table->data.external.command, bcd->table->data.external.input.buffer);
+    if (stop) return 1;
+  }
+
+  logMessage(LOG_WARNING, "incomplete external contraction response: %s", bcd->table->data.external.command);
+  return 0;
+}
+
+static int
+contractText_external (BrailleContractionData *bcd) {
+  setOffset(bcd);
+  while (++bcd->input.current < bcd->input.end) clearOffset(bcd);
+
+  if (startContractionCommand(bcd->table)) {
+    if (putExternalRequests(bcd)) {
+      if (getExternalResponses(bcd)) {
+        return 1;
+      }
+    }
+  }
+
+  stopContractionCommand(bcd->table);
+  return 0;
+}
+
+static void
+finishCharacterEntry_external (BrailleContractionData *bcd, CharacterEntry *entry) {
+}
+
+static const ContractionTableTranslationMethods externalTranslationMethods = {
+  .contractText = contractText_external,
+  .finishCharacterEntry = finishCharacterEntry_external
+};
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_external (void) {
+  return &externalTranslationMethods;
+}
diff --git a/Programs/ctb_internal.h b/Programs/ctb_internal.h
new file mode 100644
index 0000000..3ffdc6d
--- /dev/null
+++ b/Programs/ctb_internal.h
@@ -0,0 +1,194 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CTB_INTERNAL
+#define BRLTTY_INCLUDED_CTB_INTERNAL
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define BYTE unsigned char
+
+#define HASHNUM 1087
+#define CTH(x) (((x[0]<<8)+x[1])%HASHNUM)
+
+typedef uint32_t ContractionTableOffset;
+
+typedef enum {
+  CTC_Space       = 0X01,
+  CTC_Letter      = 0X02,
+  CTC_Digit       = 0X04,
+  CTC_Punctuation = 0X08,
+  CTC_UpperCase   = 0X10,
+  CTC_LowerCase   = 0X20
+} ContractionTableCharacterAttribute;
+typedef uint32_t ContractionTableCharacterAttributes;
+
+typedef struct {
+  wchar_t value;
+  ContractionTableOffset rules;
+  ContractionTableOffset always;
+  ContractionTableCharacterAttributes attributes;
+} ContractionTableCharacter;
+
+typedef enum {
+  CTO_CapitalSign, /*dot pattern for capital sign*/
+  CTO_BeginCapitalSign, /*dot pattern for beginning capital block*/
+  CTO_EndCapitalSign, /*dot pattern for ending capital block*/
+
+  CTO_LetterSign, /*dot pattern for letter sign*/
+  CTO_NumberSign, /*number sign*/
+
+  CTO_Literal, /*don't translate this string*/
+  CTO_Always, /*always use this contraction*/
+  CTO_Repeatable, /*take just the first, i.e. multiple blanks*/
+
+  CTO_LargeSign, /*and, for, of, the, with*/
+  CTO_LastLargeSign, /*a*/
+  CTO_WholeWord, /*whole word contraction*/
+  CTO_JoinedWord, /*to, by, into*/
+  CTO_LowWord, /*enough, were, was, etc.*/
+  CTO_Contraction, /*multiletter word sign that needs letsign*/
+
+  CTO_SuffixableWord, /*whole word or beginning of word*/
+  CTO_PrefixableWord, /*whole word or end of word*/
+  CTO_BegWord, /*beginning of word only*/
+  CTO_BegMidWord, /*beginning or middle of word*/
+  CTO_MidWord, /*middle of word only*/
+  CTO_MidEndWord, /*middle or end of word*/
+  CTO_EndWord, /*end of word only*/
+
+  CTO_PrePunc, /*punctuation in string at beginning of word*/
+  CTO_PostPunc, /*punctuation in string at end of word*/
+
+  CTO_BegNum, /*beginning of number*/
+  CTO_MidNum, /*middle of number, e.g., decimal point*/
+  CTO_EndNum, /*end of number*/
+
+  CTO_Class, /*define a character class*/
+  CTO_After, /*only match if after character in class*/
+  CTO_Before, /*only match if before character in class*/
+
+  CTO_Replace, /*replace text*/
+
+  CTO_None /*for internal use only*/
+} ContractionTableOpcode;
+
+extern const wchar_t *getContractionTableOpcodeName (ContractionTableOpcode opcode);
+
+typedef struct {
+  ContractionTableOffset next; /*next entry*/
+  ContractionTableOpcode opcode; /*rule for testing validity of replacement*/
+  ContractionTableCharacterAttributes after; /*character types which must foollow*/
+  ContractionTableCharacterAttributes before; /*character types which must precede*/
+  BYTE findlen; /*length of string to be replaced*/
+  BYTE replen; /*length of replacement string*/
+  wchar_t findrep[]; /*find and replacement strings*/
+} ContractionTableRule;
+
+typedef struct {
+  ContractionTableOffset capitalSign; /*capitalization sign*/
+  ContractionTableOffset beginCapitalSign; /*begin capitals sign*/
+  ContractionTableOffset endCapitalSign; /*end capitals sign*/
+  ContractionTableOffset letterSign; /*letter sign*/
+  ContractionTableOffset numberSign; /*number sign*/
+  ContractionTableOffset characters;
+  uint32_t characterCount;
+  ContractionTableOffset rules[HASHNUM]; /*locations of multi-character rules in table*/
+} ContractionTableHeader;
+
+typedef struct {
+  const ContractionTableRule *always;
+  ContractionTableCharacterAttributes attributes;
+
+  wchar_t value;
+  wchar_t uppercase;
+  wchar_t lowercase;
+} CharacterEntry;
+
+typedef struct {
+  void (*destroy) (ContractionTable *table);
+} ContractionTableManagementMethods;
+
+typedef struct ContractionTableTranslationMethodsStruct ContractionTableTranslationMethods;
+typedef const ContractionTableTranslationMethods *GetContractionTableTranslationMethodsFunction (void);
+extern GetContractionTableTranslationMethodsFunction getContractionTableTranslationMethods_native;
+extern GetContractionTableTranslationMethodsFunction getContractionTableTranslationMethods_external;
+extern GetContractionTableTranslationMethodsFunction getContractionTableTranslationMethods_louis;
+
+typedef struct {
+  union {
+    ContractionTableHeader *fields;
+    const unsigned char *bytes;
+  } header;
+
+  size_t size;
+} InternalContractionTable;
+
+struct ContractionTableStruct {
+  const ContractionTableManagementMethods *managementMethods;
+  const ContractionTableTranslationMethods *translationMethods;
+
+  struct {
+    CharacterEntry *array;
+    unsigned int size;
+    unsigned int count;
+  } characters;
+
+  struct {
+    ContractionTableRule **array;
+    unsigned int size;
+    unsigned int count;
+  } rules;
+
+  union {
+    InternalContractionTable internal;
+
+    struct {
+      char *command;
+      FILE *standardInput;
+      FILE *standardOutput;
+      unsigned commandStarted:1;
+
+      struct {
+        char *buffer;
+        size_t size;
+      } input;
+    } external;
+
+#ifdef LOUIS_TABLES_DIRECTORY
+    struct {
+      char *tableList;
+    } louis;
+#endif /* LOUIS_TABLES_DIRECTORY */
+  } data;
+};
+
+extern int startContractionCommand (ContractionTable *table);
+extern void stopContractionCommand (ContractionTable *table);
+
+extern const unsigned char *getInternalContractionTableBytes (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CTB_INTERNAL */
diff --git a/Programs/ctb_louis.c b/Programs/ctb_louis.c
new file mode 100644
index 0000000..62bac82
--- /dev/null
+++ b/Programs/ctb_louis.c
@@ -0,0 +1,128 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <liblouis.h>
+
+#include "log.h"
+#include "ctb_translate.h"
+#include "prefs.h"
+
+static void
+initialize (void) {
+  static unsigned char initialized = 0;
+
+  if (!initialized) {
+    int logLevel = LOG_INFO;
+
+    logMessage(logLevel, "LibLouis version: %s", lou_version());
+    logMessage(logLevel, "LibLouis Data Directory: %s", lou_getDataPath());
+    logMessage(logLevel, "LibLouis Character Size: %d", lou_charSize());
+
+    initialized = 1;
+  }
+}
+
+static int
+contractText_louis (BrailleContractionData *bcd) {
+  initialize();
+
+  int inputLength = getInputCount(bcd);
+  widechar inputBuffer[inputLength];
+  int outputOffsets[inputLength];
+
+  {
+    const wchar_t *source = bcd->input.begin;
+    widechar *target = inputBuffer;
+
+    while (source < bcd->input.end) {
+      *target++ = *source++;
+    }
+  }
+
+  int outputLength = getOutputCount(bcd);
+  widechar outputBuffer[outputLength];
+  int inputOffsets[outputLength];
+
+  int *cursor = NULL;
+  int position;
+
+  if (bcd->input.cursor) {
+    position = bcd->input.cursor - bcd->input.begin;
+    if ((position >= 0) && (position < inputLength)) cursor = &position;
+  }
+
+  int translationMode = dotsIO | ucBrl;
+  if (prefs.expandCurrentWord) translationMode |= compbrlAtCursor;
+
+  int translated = lou_translate(
+    bcd->table->data.louis.tableList,
+    inputBuffer, &inputLength, outputBuffer, &outputLength,
+    NULL /* typeForm */, NULL /* spacing */,
+    outputOffsets, inputOffsets, cursor, translationMode
+  );
+
+  if (translated) {
+    bcd->input.current = bcd->input.begin + inputLength;
+    bcd->output.current = bcd->output.begin + outputLength;
+
+    {
+      const widechar *source = outputBuffer;
+      BYTE *target = bcd->output.begin;
+
+      while (target < bcd->output.current) {
+        *target++ = *source++ & 0XFF;
+      }
+    }
+
+    if (bcd->input.offsets) {
+      const int *source = outputOffsets;
+      int *target = bcd->input.offsets;
+      const int *end = target + inputLength;
+      int previousOffset = -1;
+
+      while (target < end) {
+        int offset = *source++;
+        int same = offset == previousOffset;
+
+        *target++ = same? CTB_NO_OFFSET: offset;
+        previousOffset = offset;
+      }
+
+      end += bcd->input.end - bcd->input.current;
+      while (target < end) *target++ = CTB_NO_OFFSET;
+    }
+  }
+
+  return translated;
+}
+
+static void
+finishCharacterEntry_louis (BrailleContractionData *bcd, CharacterEntry *entry) {
+}
+
+static const ContractionTableTranslationMethods louisTranslationMethods = {
+  .contractText = contractText_louis,
+  .finishCharacterEntry = finishCharacterEntry_louis
+};
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_louis (void) {
+  return &louisTranslationMethods;
+}
diff --git a/Programs/ctb_native.c b/Programs/ctb_native.c
new file mode 100644
index 0000000..b24b6e6
--- /dev/null
+++ b/Programs/ctb_native.c
@@ -0,0 +1,1251 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "ctb_translate.h"
+#include "ttb.h"
+#include "brl_dots.h"
+#include "unicode.h"
+#include "utf8.h"
+
+#ifdef HAVE_ICU
+#include <unicode/uchar.h>
+
+typedef struct {
+  unsigned int index;
+  ULineBreak after;
+  ULineBreak before;
+  ULineBreak previous;
+  ULineBreak indirect;
+} LineBreakOpportunitiesState;
+
+static void
+prepareLineBreakOpportunitiesState (LineBreakOpportunitiesState *lbo) {
+  lbo->index = 0;
+  lbo->after = U_LB_SPACE;
+  lbo->before = lbo->after;
+  lbo->previous = lbo->before;
+  lbo->indirect = U_LB_SPACE;
+}
+
+static void
+findLineBreakOpportunities (
+  BrailleContractionData *bcd,
+  LineBreakOpportunitiesState *lbo,
+  unsigned char *opportunities,
+  const wchar_t *characters, unsigned int end
+) {
+  /* UAX #14: Line Breaking Properties
+   * http://unicode.org/reports/tr14/
+   * Section 6: Line Breaking Algorithm
+   *
+   * !  Mandatory break at the indicated position
+   * ^  No break allowed at the indicated position
+   * _  Break allowed at the indicated position
+   *
+   * H  ideographs
+   * h  small kana
+   * 9  digits
+   */
+
+  while (lbo->index <= end) {
+    unsigned char *opportunity = &opportunities[lbo->index];
+
+    lbo->previous = lbo->before;
+    lbo->before = lbo->after;
+    lbo->after = u_getIntPropertyValue(characters[lbo->index], UCHAR_LINE_BREAK);
+    lbo->index += 1;
+
+    /* LB9  Do not break a combining character sequence.
+     */
+    if (lbo->after == U_LB_COMBINING_MARK) {
+      /* LB10: Treat any remaining combining mark as AL.
+       */
+      if ((lbo->before == U_LB_MANDATORY_BREAK) ||
+          (lbo->before == U_LB_CARRIAGE_RETURN) ||
+          (lbo->before == U_LB_LINE_FEED) ||
+          (lbo->before == U_LB_NEXT_LINE) ||
+          (lbo->before == U_LB_SPACE) ||
+          (lbo->before == U_LB_ZWSPACE)) {
+        lbo->before = U_LB_ALPHABETIC;
+      }
+
+      /* treat it as if it has the line breaking class of the base character
+       */
+      lbo->after = lbo->before;
+      *opportunity = 0;
+      continue;
+    }
+
+    if (lbo->before != U_LB_SPACE) lbo->indirect = lbo->before;
+
+    /* LB2: Never break at the start of text.
+     * sot ×
+     */
+    if (opportunity == opportunities) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB4: Always break after hard line breaks
+     * BK !
+     */
+    if (lbo->before == U_LB_MANDATORY_BREAK) {
+      *opportunity = 1;
+      continue;
+    }
+
+    /* LB5: Treat CR followed by LF, as well as CR, LF, and NL as hard line breaks.
+     * CR ^ LF
+     * CR !
+     * LF !
+     * NL !
+     */
+    if ((lbo->before == U_LB_CARRIAGE_RETURN) && (lbo->after == U_LB_LINE_FEED)) {
+      *opportunity = 0;
+      continue;
+    }
+    if ((lbo->before == U_LB_CARRIAGE_RETURN) ||
+        (lbo->before == U_LB_LINE_FEED) ||
+        (lbo->before == U_LB_NEXT_LINE)) {
+      *opportunity = 1;
+      continue;
+    }
+
+    /* LB6: Do not break before hard line breaks.
+     * ^ ( BK | CR | LF | NL )
+     */
+    if ((lbo->after == U_LB_MANDATORY_BREAK) ||
+        (lbo->after == U_LB_CARRIAGE_RETURN) ||
+        (lbo->after == U_LB_LINE_FEED) ||
+        (lbo->after == U_LB_NEXT_LINE)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB7: Do not break before spaces or zero width space.
+     * ^ SP
+     * ^ ZW
+     */
+    if ((lbo->after == U_LB_SPACE) || (lbo->after == U_LB_ZWSPACE)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB8: Break after zero width space.
+     * ZW _
+     */
+    if (lbo->before == U_LB_ZWSPACE) {
+      *opportunity = 1;
+      continue;
+    }
+
+    /* LB11: Do not break before or after Word joiner and related characters.
+     * ^ WJ
+     * WJ ^
+     */
+    if ((lbo->before == U_LB_WORD_JOINER) || (lbo->after == U_LB_WORD_JOINER)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB12: Do not break before or after NBSP and related characters.
+     * [^SP] ^ GL
+     * GL ^
+     */
+    if ((lbo->before != U_LB_SPACE) && (lbo->after == U_LB_GLUE)) {
+      *opportunity = 0;
+      continue;
+    }
+    if (lbo->before == U_LB_GLUE) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB13: Do not break before ‘]' or ‘!' or ‘;' or ‘/', even after spaces.
+     * ^ CL
+     * ^ EX
+     * ^ IS
+     * ^ SY
+     */
+    if ((lbo->after == U_LB_CLOSE_PUNCTUATION) ||
+        (lbo->after == U_LB_EXCLAMATION) ||
+        (lbo->after == U_LB_INFIX_NUMERIC) ||
+        (lbo->after == U_LB_BREAK_SYMBOLS)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB14: Do not break after ‘[', even after spaces.
+     * OP SP* ^
+     */
+    if (lbo->indirect == U_LB_OPEN_PUNCTUATION) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB15: Do not break within ‘"[', even with intervening spaces.
+     * QU SP* ^ OP
+     */
+    if ((lbo->indirect == U_LB_QUOTATION) && (lbo->after == U_LB_OPEN_PUNCTUATION)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB16: Do not break within ‘]h', even with intervening spaces.
+     * CL SP* ^ NS
+     */
+    if ((lbo->indirect == U_LB_CLOSE_PUNCTUATION) && (lbo->after == U_LB_NONSTARTER)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB17: Do not break within ‘ــ', even with intervening spaces.
+     * B2 SP* ^ B2
+     */
+    if ((lbo->indirect == U_LB_BREAK_BOTH) && (lbo->after == U_LB_BREAK_BOTH)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB18: Break after spaces.
+     * SP _
+     */
+    if (lbo->before == U_LB_SPACE) {
+      *opportunity = 1;
+      continue;
+    }
+
+    /* LB19: Do not break before or after  quotation marks.
+     * ^ QU
+     * QU ^
+     */
+    if ((lbo->before == U_LB_QUOTATION) || (lbo->after == U_LB_QUOTATION)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB20: Break before and after unresolved.
+     * _ CB
+     * CB _
+     */
+    if ((lbo->after == U_LB_CONTINGENT_BREAK) || (lbo->before == U_LB_CONTINGENT_BREAK)) {
+      *opportunity = 1;
+      continue;
+    }
+
+    /* LB21: Do not break before hyphen-minus, other hyphens,
+     *       fixed-width spaces, small kana, and other non-starters,
+     *       or lbo->after acute accents.
+     * ^ BA
+     * ^ HY
+     * ^ NS
+     * BB ^
+     */
+    if ((lbo->after == U_LB_BREAK_AFTER) ||
+        (lbo->after == U_LB_HYPHEN) ||
+        (lbo->after == U_LB_NONSTARTER) ||
+        (lbo->before == U_LB_BREAK_BEFORE)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB22: Do not break between two ellipses,
+     *       or between letters or numbers and ellipsis.
+     * AL ^ IN
+     * ID ^ IN
+     * IN ^ IN
+     * NU ^ IN
+     */
+    if ((lbo->after == U_LB_INSEPARABLE) &&
+        ((lbo->before == U_LB_ALPHABETIC) ||
+         (lbo->before == U_LB_IDEOGRAPHIC) ||
+         (lbo->before == U_LB_INSEPARABLE) ||
+         (lbo->before == U_LB_NUMERIC))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB23: Do not break within ‘a9', ‘3a', or ‘H%'.
+     * ID ^ PO
+     * AL ^ NU
+     * NU ^ AL
+     */
+    if (((lbo->before == U_LB_IDEOGRAPHIC) && (lbo->after == U_LB_POSTFIX_NUMERIC)) ||
+        ((lbo->before == U_LB_ALPHABETIC) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_NUMERIC) && (lbo->after == U_LB_ALPHABETIC))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB24: Do not break between prefix and letters or ideographs.
+     * PR ^ ID
+     * PR ^ AL
+     * PO ^ AL
+     */
+    if (((lbo->before == U_LB_PREFIX_NUMERIC) && (lbo->after == U_LB_IDEOGRAPHIC)) ||
+        ((lbo->before == U_LB_PREFIX_NUMERIC) && (lbo->after == U_LB_ALPHABETIC)) ||
+        ((lbo->before == U_LB_POSTFIX_NUMERIC) && (lbo->after == U_LB_ALPHABETIC))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB25:  Do not break between the following pairs of classes relevant to numbers:
+     * CL ^ PO
+     * CL ^ PR
+     * NU ^ PO
+     * NU ^ PR
+     * PO ^ OP
+     * PO ^ NU
+     * PR ^ OP
+     * PR ^ NU
+     * HY ^ NU
+     * IS ^ NU
+     * NU ^ NU
+     * SY ^ NU
+     */
+    if (((lbo->before == U_LB_CLOSE_PUNCTUATION) && (lbo->after == U_LB_POSTFIX_NUMERIC)) ||
+        ((lbo->before == U_LB_CLOSE_PUNCTUATION) && (lbo->after == U_LB_PREFIX_NUMERIC)) ||
+        ((lbo->before == U_LB_NUMERIC) && (lbo->after == U_LB_POSTFIX_NUMERIC)) ||
+        ((lbo->before == U_LB_NUMERIC) && (lbo->after == U_LB_PREFIX_NUMERIC)) ||
+        ((lbo->before == U_LB_POSTFIX_NUMERIC) && (lbo->after == U_LB_OPEN_PUNCTUATION)) ||
+        ((lbo->before == U_LB_POSTFIX_NUMERIC) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_PREFIX_NUMERIC) && (lbo->after == U_LB_OPEN_PUNCTUATION)) ||
+        ((lbo->before == U_LB_PREFIX_NUMERIC) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_HYPHEN) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_INFIX_NUMERIC) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_NUMERIC) && (lbo->after == U_LB_NUMERIC)) ||
+        ((lbo->before == U_LB_BREAK_SYMBOLS) && (lbo->after == U_LB_NUMERIC))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB26: Do not break a Korean syllable.
+     * JL ^ (JL | JV | H2 | H3)
+     * (JV | H2) ^ (JV | JT)
+     * (JT | H3) ^ JT
+     */
+    if ((lbo->before == U_LB_JL) &&
+        ((lbo->after == U_LB_JL) ||
+         (lbo->after == U_LB_JV) ||
+         (lbo->after == U_LB_H2) ||
+         (lbo->after == U_LB_H3))) {
+      *opportunity = 0;
+      continue;
+    }
+    if (((lbo->before == U_LB_JV) || (lbo->before == U_LB_H2)) &&
+        ((lbo->after == U_LB_JV) || (lbo->after == U_LB_JT))) {
+      *opportunity = 0;
+      continue;
+    }
+    if (((lbo->before == U_LB_JT) || (lbo->before == U_LB_H3)) &&
+        (lbo->after == U_LB_JT)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB27: Treat a Korean Syllable Block the same as ID.
+     * (JL | JV | JT | H2 | H3) ^ IN
+     * (JL | JV | JT | H2 | H3) ^ PO
+     * PR ^ (JL | JV | JT | H2 | H3)
+     */
+    if (((lbo->before == U_LB_JL) || (lbo->before == U_LB_JV) || (lbo->before == U_LB_JT) ||
+         (lbo->before == U_LB_H2) || (lbo->before == U_LB_H3)) &&
+        (lbo->after == U_LB_INSEPARABLE)) {
+      *opportunity = 0;
+      continue;
+    }
+    if (((lbo->before == U_LB_JL) || (lbo->before == U_LB_JV) || (lbo->before == U_LB_JT) ||
+         (lbo->before == U_LB_H2) || (lbo->before == U_LB_H3)) &&
+        (lbo->after == U_LB_POSTFIX_NUMERIC)) {
+      *opportunity = 0;
+      continue;
+    }
+    if ((lbo->before == U_LB_PREFIX_NUMERIC) &&
+        ((lbo->after == U_LB_JL) || (lbo->after == U_LB_JV) || (lbo->after == U_LB_JT) ||
+         (lbo->after == U_LB_H2) || (lbo->after == U_LB_H3))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB28: Do not break between alphabetics.
+     * AL ^ AL
+     */
+    if ((lbo->before == U_LB_ALPHABETIC) && (lbo->after == U_LB_ALPHABETIC)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB29: Do not break between numeric punctuation and alphabetics.
+     * IS ^ AL
+     */
+    if ((lbo->before == U_LB_INFIX_NUMERIC) && (lbo->after == U_LB_ALPHABETIC)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB30: Do not break between letters, numbers, or ordinary symbols
+     *       and opening or closing punctuation.
+     * (AL | NU) ^ OP
+     * CL ^ (AL | NU)
+     */
+    if (((lbo->before == U_LB_ALPHABETIC) || (lbo->before == U_LB_NUMERIC)) &&
+        (lbo->after == U_LB_OPEN_PUNCTUATION)) {
+      *opportunity = 0;
+      continue;
+    }
+    if ((lbo->before == U_LB_CLOSE_PUNCTUATION) &&
+        ((lbo->after == U_LB_ALPHABETIC) || (lbo->after == U_LB_NUMERIC))) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* Unix options begin with a minus sign. */
+    if ((lbo->before == U_LB_HYPHEN) &&
+        (lbo->after != U_LB_SPACE) &&
+        (lbo->previous == U_LB_SPACE)) {
+      *opportunity = 0;
+      continue;
+    }
+
+    /* LB31: Break everywhere else.
+     * ALL _
+     * _ ALL
+     */
+    *opportunity = 1;
+  }
+}
+
+#else /* HAVE_ICU */
+typedef struct {
+  unsigned int index;
+  int wasSpace;
+} LineBreakOpportunitiesState;
+
+static void
+prepareLineBreakOpportunitiesState (LineBreakOpportunitiesState *lbo) {
+  lbo->index = 0;
+  lbo->wasSpace = 0;
+}
+
+static void
+findLineBreakOpportunities (
+  BrailleContractionData *bcd,
+  LineBreakOpportunitiesState *lbo,
+  unsigned char *opportunities,
+  const wchar_t *characters, unsigned int end
+) {
+  while (lbo->index <= end) {
+    int isSpace = testCharacter(bcd, characters[lbo->index], CTC_Space);
+    opportunities[lbo->index] = lbo->wasSpace && !isSpace;
+
+    lbo->wasSpace = isSpace;
+    lbo->index += 1;
+  }
+}
+#endif /* HAVE_ICU */
+
+static int
+isLineBreakOpportunity (
+  BrailleContractionData *bcd,
+  LineBreakOpportunitiesState *lbo,
+  unsigned char *opportunities
+) {
+  unsigned int index = getInputConsumed(bcd);
+  if (index == getInputCount(bcd)) return 1;
+
+  findLineBreakOpportunities(bcd, lbo, opportunities, bcd->input.begin, index);
+  return opportunities[index];
+}
+
+static inline ContractionTableHeader *
+getContractionTableHeader (BrailleContractionData *bcd) {
+  return bcd->table->data.internal.header.fields;
+}
+
+static inline const void *
+getContractionTableItem (BrailleContractionData *bcd, ContractionTableOffset offset) {
+  return &bcd->table->data.internal.header.bytes[offset];
+}
+
+static const ContractionTableCharacter *
+getContractionTableCharacter (BrailleContractionData *bcd, wchar_t character) {
+  const ContractionTableCharacter *characters = getContractionTableItem(bcd, getContractionTableHeader(bcd)->characters);
+  int first = 0;
+  int last = getContractionTableHeader(bcd)->characterCount - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    const ContractionTableCharacter *ctc = &characters[current];
+
+    if (ctc->value < character) {
+      first = current + 1;
+    } else if (ctc->value > character) {
+      last = current - 1;
+    } else {
+      return ctc;
+    }
+  }
+
+  return NULL;
+}
+
+static int
+addRule (BrailleContractionData *bcd, ContractionTableRule *rule) {
+  ContractionTable *table = bcd->table;
+
+  if (table->rules.count == table->rules.size) {
+    size_t newSize = table->rules.size + 10;
+    ContractionTableRule **newArray = realloc(table->rules.array, ARRAY_SIZE(newArray, newSize));
+
+    if (!newArray) {
+      logMallocError();
+      return 0;
+    }
+
+    table->rules.array = newArray;
+    table->rules.size = newSize;
+  }
+
+  table->rules.array[table->rules.count++] = rule;
+  return 1;
+}
+
+static size_t
+makeDecomposedBraille (BrailleContractionData *bcd, wchar_t character, BYTE *cells, size_t size) {
+  wchar_t characters[0X10];
+  size_t characterCount = decomposeCharacter(character, characters, ARRAY_COUNT(characters));
+
+  if (characterCount > 1) {
+    BYTE *from = cells;
+    const BYTE *end = from + size;
+    unsigned int characterIndex = 1;
+
+    while (1) {
+      wchar_t character = characters[characterIndex];
+      const CharacterEntry *entry = getCharacterEntry(bcd, character);
+      if (!entry) break;
+      if (character != entry->value) break;
+
+      const ContractionTableRule *rule = entry->always;
+      if (!rule) break;
+
+      unsigned int cellCount = rule->replen;
+      if (!cellCount) break;
+      if ((end - from) < cellCount) break;
+      from = mempcpy(from, &rule->findrep[rule->findlen], cellCount);
+
+      if (!characterIndex) return from - cells;
+      if (++characterIndex == characterCount) characterIndex = 0;
+    }
+  }
+
+  return 0;
+}
+
+typedef struct {
+  BrailleContractionData *bcd;
+  CharacterEntry *character;
+} SetAlwaysRuleData;
+
+static int
+setAlwaysRule (wchar_t character, void *data) {
+  SetAlwaysRuleData *sar = data;
+  BrailleContractionData *bcd = sar->bcd;
+
+  CharacterEntry *entry = sar->character;
+  const ContractionTableCharacter *ctc = getContractionTableCharacter(bcd, character);
+
+  if (ctc) {
+    ContractionTableOffset offset = ctc->always;
+
+    if (offset) {
+      const ContractionTableRule *rule = getContractionTableItem(bcd, offset);
+
+      if (rule->replen) {
+        entry->always = rule;
+        return 1;
+      }
+    }
+  }
+
+  if (character == entry->value) {
+    BYTE cells[0X100];
+    size_t count = makeDecomposedBraille(bcd, character, cells, sizeof(cells));
+
+    {
+      unsigned int position;
+      findCharacterEntry(bcd, character, &position);
+
+      entry = &bcd->table->characters.array[position];
+      sar->character = entry;
+    }
+
+    if (count) {
+      ContractionTableRule *rule;
+      size_t size = sizeof(*rule) + sizeof(character) + count;
+
+      if ((rule = malloc(size))) {
+        memset(rule, 0, sizeof(*rule));
+        rule->opcode = CTO_Always;
+
+        rule->findrep[0] = character;
+        memcpy(&rule->findrep[rule->findlen = 1], cells, (rule->replen = count));
+
+        if (addRule(bcd, rule)) {
+          entry->always = rule;
+          return 1;
+        }
+
+        free(rule);
+      }
+    }
+  }
+
+  return 0;
+}
+
+static wchar_t
+toLowerCase (BrailleContractionData *bcd, wchar_t character) {
+  const CharacterEntry *entry = getCharacterEntry(bcd, character);
+  return entry? entry->lowercase: character;
+}
+
+static const ContractionTableRule *
+getAlwaysRule (BrailleContractionData *bcd, wchar_t character) {
+  const CharacterEntry *entry = getCharacterEntry(bcd, toLowerCase(bcd, character));
+  return entry? entry->always: NULL;
+}
+
+static wchar_t
+getBestCharacter (BrailleContractionData *bcd, wchar_t character) {
+  const ContractionTableRule *rule = getAlwaysRule(bcd, character);
+  return rule? rule->findrep[0]: 0;
+}
+
+static int
+sameCharacters (BrailleContractionData *bcd, wchar_t character1, wchar_t character2) {
+  wchar_t best1 = getBestCharacter(bcd, character1);
+  return best1 && (best1 == getBestCharacter(bcd, character2));
+}
+
+static int
+matchCurrentRule (BrailleContractionData *bcd) {
+  const wchar_t *input = bcd->input.current;
+  const wchar_t *find = bcd->current.rule->findrep;
+  const wchar_t *findEnd = find + bcd->current.length;
+
+  while (find < findEnd) {
+    if (toLowerCase(bcd, *input++) != toLowerCase(bcd, *find++)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static void
+setBefore (BrailleContractionData *bcd) {
+  bcd->current.before = (bcd->input.current == bcd->input.begin)? WC_C(' '): bcd->input.current[-1];
+}
+
+static void
+setAfter (BrailleContractionData *bcd, int length) {
+  bcd->current.after = (bcd->input.current + length < bcd->input.end)? bcd->input.current[length]: WC_C(' ');
+}
+
+static int
+isBeginning (BrailleContractionData *bcd) {
+  const wchar_t *ptr = bcd->input.current;
+
+  while (ptr > bcd->input.begin) {
+    if (!testCharacter(bcd, *--ptr, CTC_Punctuation)) {
+      if (!testCharacter(bcd, *ptr, CTC_Space)) return 0;
+      break;
+    }
+  }
+
+  return 1;
+}
+
+static int
+isEnding (BrailleContractionData *bcd) {
+  const wchar_t *ptr = bcd->input.current + bcd->current.length;
+
+  while (ptr < bcd->input.end) {
+    if (!testCharacter(bcd, *ptr, CTC_Punctuation)) {
+      if (!testCharacter(bcd, *ptr, CTC_Space)) return 0;
+      break;
+    }
+
+    ptr += 1;
+  }
+
+  return 1;
+}
+
+static void
+setCurrentRule (BrailleContractionData *bcd, const ContractionTableRule *rule) {
+  bcd->current.rule = rule;
+  bcd->current.opcode = bcd->current.rule->opcode;
+  bcd->current.length = bcd->current.rule->findlen;
+  setAfter(bcd, bcd->current.length);
+}
+
+static int
+selectRule (BrailleContractionData *bcd, int length) {
+  if (length < 1) return 0;
+
+  int ruleOffset;
+  int maximumLength;
+
+  if (length == 1) {
+    wchar_t character = toLowerCase(bcd, *bcd->input.current);
+    const ContractionTableCharacter *ctc = getContractionTableCharacter(bcd, character);
+
+    if (!ctc) {
+      const CharacterEntry *entry = getCharacterEntry(bcd, character);
+      if (!entry) return 0;
+
+      const ContractionTableRule *rule = entry->always;
+      if (!rule) return 0;
+
+      setCurrentRule(bcd, rule);
+      return 1;
+    }
+
+    ruleOffset = ctc->rules;
+    maximumLength = 1;
+  } else {
+    const wchar_t characters[] = {
+      toLowerCase(bcd, bcd->input.current[0]),
+      toLowerCase(bcd, bcd->input.current[1]),
+    };
+
+    ruleOffset = getContractionTableHeader(bcd)->rules[CTH(characters)];
+    maximumLength = 0;
+  }
+
+  while (ruleOffset) {
+    setCurrentRule(bcd, getContractionTableItem(bcd, ruleOffset));
+
+    if ((length == 1) ||
+        ((bcd->current.length <= length) &&
+         matchCurrentRule(bcd))) {
+      if (!maximumLength) {
+        maximumLength = bcd->current.length;
+
+        if (prefs.capitalizationMode != CTB_CAP_NONE) {
+          typedef enum {CS_Any, CS_Lower, CS_UpperSingle, CS_UpperMultiple} CapitalizationState;
+#define STATE(c) (testCharacter(bcd, (c), CTC_UpperCase)? CS_UpperSingle: testCharacter(bcd, (c), CTC_LowerCase)? CS_Lower: CS_Any)
+
+          CapitalizationState current = STATE(bcd->current.before);
+
+          for (int i=0; i<bcd->current.length; i+=1) {
+            wchar_t character = bcd->input.current[i];
+            CapitalizationState next = STATE(character);
+
+            if (i > 0) {
+              if (((current == CS_Lower) && (next == CS_UpperSingle)) ||
+                  ((current == CS_UpperMultiple) && (next == CS_Lower))) {
+                maximumLength = i;
+                break;
+              }
+
+              if ((prefs.capitalizationMode != CTB_CAP_SIGN) &&
+                  (next == CS_UpperSingle)) {
+                maximumLength = i;
+                break;
+              }
+            }
+
+            if ((prefs.capitalizationMode == CTB_CAP_SIGN) && (current > CS_Lower) && (next == CS_UpperSingle)) {
+              current = CS_UpperMultiple;
+            } else if (next != CS_Any) {
+              current = next;
+            } else if (current == CS_Any) {
+              current = CS_Lower;
+            }
+          }
+
+#undef STATE
+        }
+      }
+
+      if ((bcd->current.length <= maximumLength) &&
+          (!bcd->current.rule->after || testBefore(bcd, bcd->current.rule->after)) &&
+          (!bcd->current.rule->before || testAfter(bcd, bcd->current.rule->before))) {
+        switch (bcd->current.opcode) {
+          case CTO_Always:
+          case CTO_Repeatable:
+          case CTO_Literal:
+          case CTO_Replace:
+            return 1;
+
+          case CTO_LargeSign:
+          case CTO_LastLargeSign:
+            if (!isBeginning(bcd) || !isEnding(bcd)) bcd->current.opcode = CTO_Always;
+            return 1;
+
+          case CTO_WholeWord:
+            if (testBefore(bcd, CTC_Space|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Space|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_Contraction:
+            if ((bcd->input.current > bcd->input.begin) && sameCharacters(bcd, bcd->input.current[-1], WC_C('\''))) break;
+            if (isBeginning(bcd) && isEnding(bcd)) return 1;
+            break;
+
+          case CTO_LowWord:
+            if (testBefore(bcd, CTC_Space) && testAfter(bcd, CTC_Space) &&
+                (bcd->previous.opcode != CTO_JoinedWord) &&
+                ((bcd->output.current == bcd->output.begin) || !bcd->output.current[-1]))
+              return 1;
+            break;
+
+          case CTO_JoinedWord:
+            if (testBefore(bcd, CTC_Space|CTC_Punctuation) &&
+                !sameCharacters(bcd, bcd->current.before, WC_C('-')) &&
+                (bcd->output.current + bcd->current.rule->replen < bcd->output.end)) {
+              const wchar_t *end = bcd->input.current + bcd->current.length;
+              const wchar_t *ptr = end;
+
+              while (ptr < bcd->input.end) {
+                if (!testCharacter(bcd, *ptr, CTC_Space)) {
+                  if (!testCharacter(bcd, *ptr, CTC_Letter)) break;
+                  if (ptr == end) break;
+                  return 1;
+                }
+
+                if (ptr++ == bcd->input.cursor) break;
+              }
+            }
+            break;
+
+          case CTO_SuffixableWord:
+            if (testBefore(bcd, CTC_Space|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Space|CTC_Letter|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_PrefixableWord:
+            if (testBefore(bcd, CTC_Space|CTC_Letter|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Space|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_BegWord:
+            if (testBefore(bcd, CTC_Space|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Letter))
+              return 1;
+            break;
+
+          case CTO_BegMidWord:
+            if (testBefore(bcd, CTC_Letter|CTC_Space|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Letter))
+              return 1;
+            break;
+
+          case CTO_MidWord:
+            if (testBefore(bcd, CTC_Letter) && testAfter(bcd, CTC_Letter))
+              return 1;
+            break;
+
+          case CTO_MidEndWord:
+            if (testBefore(bcd, CTC_Letter) &&
+                testAfter(bcd, CTC_Letter|CTC_Space|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_EndWord:
+            if (testBefore(bcd, CTC_Letter) &&
+                testAfter(bcd, CTC_Space|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_BegNum:
+            if (testBefore(bcd, CTC_Space|CTC_Punctuation) &&
+                testAfter(bcd, CTC_Digit))
+              return 1;
+            break;
+
+          case CTO_MidNum:
+            if (testBefore(bcd, CTC_Digit) && testAfter(bcd, CTC_Digit))
+              return 1;
+            break;
+
+          case CTO_EndNum:
+            if (testBefore(bcd, CTC_Digit) &&
+                testAfter(bcd, CTC_Space|CTC_Punctuation))
+              return 1;
+            break;
+
+          case CTO_PrePunc:
+            if (testCurrent(bcd, CTC_Punctuation) && isBeginning(bcd) && !isEnding(bcd)) return 1;
+            break;
+
+          case CTO_PostPunc:
+            if (testCurrent(bcd, CTC_Punctuation) && !isBeginning(bcd) && isEnding(bcd)) return 1;
+            break;
+
+          default:
+            break;
+        }
+      }
+    }
+
+    ruleOffset = bcd->current.rule->next;
+  }
+
+  return 0;
+}
+
+static int
+putCells (BrailleContractionData *bcd, const BYTE *cells, int count) {
+  if (bcd->output.current + count > bcd->output.end) return 0;
+  bcd->output.current = mempcpy(bcd->output.current, cells, count);
+  return 1;
+}
+
+static int
+putCell (BrailleContractionData *bcd, BYTE byte) {
+  return putCells(bcd, &byte, 1);
+}
+
+static int
+putReplace (BrailleContractionData *bcd, const ContractionTableRule *rule, wchar_t character) {
+  const BYTE *cells = (BYTE *)&rule->findrep[rule->findlen];
+  int count = rule->replen;
+
+  if ((prefs.capitalizationMode == CTB_CAP_DOT7) &&
+      testCharacter(bcd, character, CTC_UpperCase)) {
+    if (!putCell(bcd, *cells++ | BRL_DOT_7)) return 0;
+    if (!(count -= 1)) return 1;
+  }
+
+  return putCells(bcd, cells, count);
+}
+
+static int
+putCharacter (BrailleContractionData *bcd, wchar_t character) {
+  {
+    const ContractionTableRule *rule = getAlwaysRule(bcd, character);
+    if (rule) return putReplace(bcd, rule, character);
+  }
+
+  if (isBrailleCharacter(character)) {
+    return putCell(bcd, (character & UNICODE_CELL_MASK));
+  }
+
+  if (textTable) {
+    unsigned char dots = convertCharacterToDots(textTable, character);
+    return putCell(bcd, dots);
+  }
+
+  {
+    const wchar_t replacementCharacter = getReplacementCharacter();
+
+    if (replacementCharacter != character) {
+      const ContractionTableRule *rule = getAlwaysRule(bcd, replacementCharacter);
+      if (rule) return putReplace(bcd, rule, replacementCharacter);
+    }
+  }
+
+  return putCell(bcd, (BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_7 | BRL_DOT_8));
+}
+
+static int
+putSequence (BrailleContractionData *bcd, ContractionTableOffset offset) {
+  const BYTE *sequence = getContractionTableItem(bcd, offset);
+  return putCells(bcd, sequence+1, *sequence);
+}
+
+static void
+clearRemainingOffsets (BrailleContractionData *bcd) {
+  const wchar_t *next = bcd->input.current + bcd->current.length;
+  while (++bcd->input.current < next) clearOffset(bcd);
+}
+
+static int
+contractText_native (BrailleContractionData *bcd) {
+  bcd->previous.opcode = CTO_None;
+
+  const wchar_t *srcword = NULL;
+  const wchar_t *srcjoin = NULL;
+  const wchar_t *literal = NULL;
+
+  BYTE *destword = NULL;
+  BYTE *destjoin = NULL;
+  BYTE *destlast = NULL;
+
+  unsigned char lineBreakOpportunities[getInputCount(bcd) + 1];
+  LineBreakOpportunitiesState lbo;
+  prepareLineBreakOpportunitiesState(&lbo);
+
+  while (bcd->input.current < bcd->input.end) {
+    int wasLiteral = bcd->input.current == literal;
+
+    destlast = bcd->output.current;
+    setOffset(bcd);
+    setBefore(bcd);
+
+    if (literal)
+      if (bcd->input.current >= literal)
+        if (testCurrent(bcd, CTC_Space) || testPrevious(bcd, CTC_Space))
+          literal = NULL;
+
+    if ((!literal && selectRule(bcd, getInputUnconsumed(bcd))) || selectRule(bcd, 1)) {
+      if (!literal &&
+          ((bcd->current.opcode == CTO_Literal) ||
+           (prefs.expandCurrentWord &&
+            (bcd->input.cursor >= bcd->input.current) &&
+            (bcd->input.cursor < (bcd->input.current + bcd->current.length))))) {
+        literal = bcd->input.current + bcd->current.length;
+
+        if (!testCurrent(bcd, CTC_Space)) {
+          if (destjoin) {
+            bcd->input.current = srcjoin;
+            bcd->output.current = destjoin;
+          } else {
+            bcd->input.current = bcd->input.begin;
+            bcd->output.current = bcd->output.begin;
+          }
+        }
+
+        continue;
+      }
+
+      if (bcd->current.opcode == CTO_Replace) {
+        const ContractionTableRule *rule = bcd->current.rule;
+
+        size_t size = rule->replen + 1;
+        wchar_t characters[size];
+        wchar_t *to = characters;
+        const char *from = (const char *)&rule->findrep[rule->findlen];
+        convertUtf8ToWchars(&from, &to, size);
+
+        const wchar_t *inputBuffer = characters;
+        int inputLength = to - characters;
+        int outputLength = bcd->output.end - bcd->output.current;
+
+        contractText(
+          bcd->table, NULL,
+          inputBuffer, &inputLength,
+          bcd->output.current, &outputLength,
+          NULL, CTB_NO_CURSOR
+        );
+
+        bcd->output.current += outputLength;
+        clearRemainingOffsets(bcd);
+        continue;
+      }
+
+      if (getContractionTableHeader(bcd)->numberSign && (bcd->previous.opcode != CTO_MidNum) &&
+          !testBefore(bcd, CTC_Digit) && testCurrent(bcd, CTC_Digit)) {
+        if (!putSequence(bcd, getContractionTableHeader(bcd)->numberSign)) break;
+      } else if (getContractionTableHeader(bcd)->letterSign && testCurrent(bcd, CTC_Letter)) {
+        if ((bcd->current.opcode == CTO_Contraction) ||
+            ((bcd->current.opcode != CTO_EndNum) && testBefore(bcd, CTC_Digit)) ||
+            (testCurrent(bcd, CTC_Letter) &&
+             (bcd->current.opcode == CTO_Always) &&
+             (bcd->current.length == 1) &&
+             testBefore(bcd, CTC_Space) &&
+             (((bcd->input.current + 1) == bcd->input.end) ||
+              testNext(bcd, CTC_Space) ||
+              (testNext(bcd, CTC_Punctuation) &&
+               !sameCharacters(bcd, bcd->input.current[1], WC_C('.')) &&
+               !sameCharacters(bcd, bcd->input.current[1], WC_C('\'')))))) {
+          if (!putSequence(bcd, getContractionTableHeader(bcd)->letterSign)) break;
+        }
+      }
+
+      if (prefs.capitalizationMode == CTB_CAP_SIGN) {
+        if (testCurrent(bcd, CTC_UpperCase)) {
+          if (!testBefore(bcd, CTC_UpperCase)) {
+            if (getContractionTableHeader(bcd)->beginCapitalSign &&
+                (bcd->input.current + 1 < bcd->input.end) && testNext(bcd, CTC_UpperCase)) {
+              if (!putSequence(bcd, getContractionTableHeader(bcd)->beginCapitalSign)) break;
+            } else if (getContractionTableHeader(bcd)->capitalSign) {
+              if (!putSequence(bcd, getContractionTableHeader(bcd)->capitalSign)) break;
+            }
+          }
+        } else if (testCurrent(bcd, CTC_LowerCase)) {
+          if (getContractionTableHeader(bcd)->endCapitalSign && (bcd->input.current - 2 >= bcd->input.begin) &&
+              testPrevious(bcd, CTC_UpperCase) && testRelative(bcd, -2, CTC_UpperCase)) {
+            if (!putSequence(bcd, getContractionTableHeader(bcd)->endCapitalSign)) break;
+          }
+        }
+      }
+
+      switch (bcd->current.opcode) {
+        case CTO_LargeSign:
+        case CTO_LastLargeSign:
+          if ((bcd->previous.opcode == CTO_LargeSign) && !wasLiteral) {
+            while ((bcd->output.current > bcd->output.begin) && !bcd->output.current[-1]) bcd->output.current -= 1;
+            setOffset(bcd);
+
+            {
+              BYTE **destptrs[] = {&destword, &destjoin, &destlast, NULL};
+              BYTE ***destptr = destptrs;
+
+              while (*destptr) {
+                if (**destptr && (**destptr > bcd->output.current)) **destptr = bcd->output.current;
+                destptr += 1;
+              }
+            }
+          }
+          break;
+
+        default:
+          break;
+      }
+
+      if (bcd->current.rule->replen &&
+          !((bcd->current.opcode == CTO_Always) && (bcd->current.length == 1))) {
+        if (!putReplace(bcd, bcd->current.rule, *bcd->input.current)) goto done;
+        clearRemainingOffsets(bcd);
+      } else {
+        const wchar_t *srclim = bcd->input.current + bcd->current.length;
+        while (1) {
+          if (!putCharacter(bcd, *bcd->input.current)) goto done;
+          if (++bcd->input.current == srclim) break;
+          setOffset(bcd);
+        }
+      }
+
+      {
+        const wchar_t *srcorig = bcd->input.current;
+        const wchar_t *srcbeg = NULL;
+        BYTE *destbeg = NULL;
+
+        switch (bcd->current.opcode) {
+          case CTO_Repeatable: {
+            const wchar_t *srclim = bcd->input.end - bcd->current.length;
+
+            srcbeg = bcd->input.current - bcd->current.length;
+            destbeg = destlast;
+
+            while ((bcd->input.current <= srclim) && matchCurrentRule(bcd)) {
+              clearOffset(bcd);
+              clearRemainingOffsets(bcd);
+            }
+
+            break;
+          }
+
+          case CTO_JoinedWord:
+            srcbeg = bcd->input.current;
+            destbeg = bcd->output.current;
+
+            while ((bcd->input.current < bcd->input.end) && testCurrent(bcd, CTC_Space)) {
+              clearOffset(bcd);
+              bcd->input.current += 1;
+            }
+            break;
+
+          default:
+            break;
+        }
+
+        if (srcbeg && (bcd->input.cursor >= srcbeg) && (bcd->input.cursor < bcd->input.current)) {
+          int repeat = !literal;
+          literal = bcd->input.current;
+
+          if (repeat) {
+            bcd->input.current = srcbeg;
+            bcd->output.current = destbeg;
+            continue;
+          }
+
+          bcd->input.current = srcorig;
+        }
+      }
+    } else {
+      bcd->current.opcode = CTO_Always;
+      if (!putCharacter(bcd, *bcd->input.current)) break;
+      bcd->input.current += 1;
+    }
+
+    if (isLineBreakOpportunity(bcd, &lbo, lineBreakOpportunities)) {
+      srcjoin = bcd->input.current;
+      destjoin = bcd->output.current;
+
+      if (bcd->current.opcode != CTO_JoinedWord) {
+        srcword = bcd->input.current;
+        destword = bcd->output.current;
+      }
+    }
+
+    if ((bcd->output.current == bcd->output.begin) || bcd->output.current[-1]) {
+      bcd->previous.opcode = bcd->current.opcode;
+    }
+  }
+
+done:
+  if (bcd->input.current < bcd->input.end) {
+    if (destword && (destword > bcd->output.begin) &&
+        (!(testPrevious(bcd, CTC_Space) || testCurrent(bcd, CTC_Space)) ||
+         (bcd->previous.opcode == CTO_JoinedWord))) {
+      bcd->input.current = srcword;
+      bcd->output.current = destword;
+    } else if (destlast) {
+      bcd->output.current = destlast;
+    }
+  }
+
+  return 1;
+}
+
+static void
+finishCharacterEntry_native (BrailleContractionData *bcd, CharacterEntry *entry) {
+  wchar_t character = entry->value;
+
+  {
+    const ContractionTableCharacter *ctc = getContractionTableCharacter(bcd, character);
+    if (ctc) entry->attributes |= ctc->attributes;
+  }
+
+  {
+    SetAlwaysRuleData sar = {
+      .bcd = bcd,
+      .character = entry
+    };
+
+    int ok = (character == getReplacementCharacter())?
+             setAlwaysRule(character, &sar):
+             handleBestCharacter(character, setAlwaysRule, &sar);
+
+    if (!ok) entry->always = NULL;
+  }
+}
+
+static const ContractionTableTranslationMethods nativeTranslationMethods = {
+  .contractText = contractText_native,
+  .finishCharacterEntry = finishCharacterEntry_native
+};
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_native (void) {
+  return &nativeTranslationMethods;
+}
diff --git a/Programs/ctb_translate.c b/Programs/ctb_translate.c
new file mode 100644
index 0000000..72892b0
--- /dev/null
+++ b/Programs/ctb_translate.c
@@ -0,0 +1,422 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "lock.h"
+#include "ctb_translate.h"
+#include "ttb.h"
+#include "unicode.h"
+#include "prefs.h"
+
+static const unsigned char internalContractionTableBytes[] = {
+#include "ctb.auto.h"
+};
+
+const unsigned char *
+getInternalContractionTableBytes (void) {
+  return internalContractionTableBytes;
+}
+
+ContractionTable *contractionTable = NULL;
+
+static LockDescriptor *
+getContractionTableLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "contraction-table");
+}
+
+void
+lockContractionTable (void) {
+  obtainExclusiveLock(getContractionTableLock());
+}
+
+void
+unlockContractionTable (void) {
+  releaseLock(getContractionTableLock());
+}
+
+const CharacterEntry *
+findCharacterEntry (BrailleContractionData *bcd, wchar_t character, unsigned int *position) {
+  unsigned int from = 0;
+  unsigned int to = bcd->table->characters.count;
+
+  while (from < to) {
+    unsigned int current = (from + to) / 2;
+    const CharacterEntry *entry = &bcd->table->characters.array[current];
+
+    if (entry->value < character) {
+      from = current + 1;
+    } else if (entry->value > character) {
+      to = current;
+    } else {
+      if (position) *position = current;
+      return entry;
+    }
+  }
+
+  if (position) *position = from;
+  return NULL;
+}
+
+static const CharacterEntry *
+addCharacterEntry (BrailleContractionData *bcd, wchar_t character, unsigned int position) {
+  ContractionTable *table = bcd->table;
+
+  if (table->characters.count == table->characters.size) {
+    int newSize = table->characters.size;
+    newSize = newSize? newSize<<1: 0X80;
+    CharacterEntry *newArray = realloc(table->characters.array, ARRAY_SIZE(newArray, newSize));
+
+    if (!newArray) {
+      logMallocError();
+      return NULL;
+    }
+
+    table->characters.array = newArray;
+    table->characters.size = newSize;
+  }
+
+  memmove(
+    &table->characters.array[position+1],
+    &table->characters.array[position],
+    ((table->characters.count++ - position) * sizeof(*table->characters.array))
+  );
+
+  CharacterEntry *entry = &bcd->table->characters.array[position];
+  memset(entry, 0, sizeof(*entry));
+  entry->value = entry->uppercase = entry->lowercase = character;
+
+  if (iswspace(character)) {
+    entry->attributes |= CTC_Space;
+  } else if (iswalpha(character)) {
+    entry->attributes |= CTC_Letter;
+
+    if (iswupper(character)) {
+      entry->attributes |= CTC_UpperCase;
+      entry->lowercase = towlower(character);
+    }
+
+    if (iswlower(character)) {
+      entry->attributes |= CTC_LowerCase;
+      entry->uppercase = towupper(character);
+    }
+  } else if (iswdigit(character)) {
+    entry->attributes |= CTC_Digit;
+  } else if (iswpunct(character)) {
+    entry->attributes |= CTC_Punctuation;
+  }
+
+  bcd->table->translationMethods->finishCharacterEntry(bcd, entry);
+  return entry;
+}
+
+const CharacterEntry *
+getCharacterEntry (BrailleContractionData *bcd, wchar_t character) {
+  unsigned int position;
+  const CharacterEntry *entry = findCharacterEntry(bcd, character, &position);
+  if (entry) return entry;
+  return addCharacterEntry(bcd, character, position);
+}
+
+static inline int
+makeCachedCursorOffset (BrailleContractionData *bcd) {
+  return bcd->input.cursor? (bcd->input.cursor - bcd->input.begin): CTB_NO_CURSOR;
+}
+
+static int
+checkContractionCache (BrailleContractionData *bcd, ContractionCache *cache) {
+  if (!cache) return 0;
+  if (!cache->input.characters) return 0;
+  if (!cache->output.cells) return 0;
+  if (bcd->input.offsets && !cache->offsets.count) return 0;
+  if (cache->output.maximum != getOutputCount(bcd)) return 0;
+  if (cache->cursorOffset != makeCachedCursorOffset(bcd)) return 0;
+  if (cache->expandCurrentWord != prefs.expandCurrentWord) return 0;
+  if (cache->capitalizationMode != prefs.capitalizationMode) return 0;
+
+  {
+    unsigned int count = getInputCount(bcd);
+    if (cache->input.count != count) return 0;
+    if (wmemcmp(bcd->input.begin, cache->input.characters, count) != 0) return 0;
+  }
+
+  return 1;
+}
+
+static void
+useContractionCache (BrailleContractionData *bcd, ContractionCache *cache) {
+  bcd->input.current = bcd->input.begin + cache->input.consumed;
+  bcd->output.current = bcd->output.begin + cache->output.count;
+
+  memcpy(
+    bcd->output.begin, cache->output.cells,
+    ARRAY_SIZE(bcd->output.begin, cache->output.count)
+  );
+
+  if (bcd->input.offsets) {
+    memcpy(
+      bcd->input.offsets, cache->offsets.array,
+      ARRAY_SIZE(bcd->input.offsets, cache->offsets.count)
+    );
+  }
+}
+
+static void
+updateContractionCache (BrailleContractionData *bcd, ContractionCache *cache) {
+  if (cache) {
+    {
+      unsigned int count = getInputCount(bcd);
+
+      if (count > cache->input.size) {
+        unsigned int newSize = count | 0X7F;
+        wchar_t *newCharacters = malloc(ARRAY_SIZE(newCharacters, newSize));
+
+        if (!newCharacters) {
+          logMallocError();
+          cache->input.count = 0;
+          goto inputDone;
+        }
+
+        if (cache->input.characters) free(cache->input.characters);
+        cache->input.characters = newCharacters;
+        cache->input.size = newSize;
+      }
+
+      wmemcpy(cache->input.characters, bcd->input.begin, count);
+      cache->input.count = count;
+      cache->input.consumed = getInputConsumed(bcd);
+    }
+  inputDone:
+
+    {
+      unsigned int count = getOutputConsumed(bcd);
+
+      if (count > cache->output.size) {
+        unsigned int newSize = count | 0X7F;
+        unsigned char *newCells = malloc(ARRAY_SIZE(newCells, newSize));
+
+        if (!newCells) {
+          logMallocError();
+          cache->output.count = 0;
+          goto outputDone;
+        }
+
+        if (cache->output.cells) free(cache->output.cells);
+        cache->output.cells = newCells;
+        cache->output.size = newSize;
+      }
+
+      memcpy(cache->output.cells, bcd->output.begin, count);
+      cache->output.count = count;
+      cache->output.maximum = getOutputCount(bcd);
+    }
+  outputDone:
+
+    if (bcd->input.offsets) {
+      unsigned int count = getInputCount(bcd);
+
+      if (count > cache->offsets.size) {
+        unsigned int newSize = count | 0X7F;
+        int *newArray = malloc(ARRAY_SIZE(newArray, newSize));
+
+        if (!newArray) {
+          logMallocError();
+          cache->offsets.count = 0;
+          goto offsetsDone;
+        }
+
+        if (cache->offsets.array) free(cache->offsets.array);
+        cache->offsets.array = newArray;
+        cache->offsets.size = newSize;
+      }
+
+      memcpy(cache->offsets.array, bcd->input.offsets, ARRAY_SIZE(bcd->input.offsets, count));
+      cache->offsets.count = count;
+    } else {
+      cache->offsets.count = 0;
+    }
+  offsetsDone:
+
+    cache->cursorOffset = makeCachedCursorOffset(bcd);
+    cache->expandCurrentWord = prefs.expandCurrentWord;
+    cache->capitalizationMode = prefs.capitalizationMode;
+  }
+}
+
+void
+contractText (
+  ContractionTable *contractionTable,
+  ContractionCache *contractionCache,
+  const wchar_t *inputBuffer, int *inputLength,
+  BYTE *outputBuffer, int *outputLength,
+  int *offsetsMap, const int cursorOffset
+) {
+  BrailleContractionData bcd = {
+    .table = contractionTable,
+
+    .input = {
+      .begin = inputBuffer,
+      .current = inputBuffer,
+      .end = inputBuffer + *inputLength,
+      .cursor = (cursorOffset == CTB_NO_CURSOR)? NULL: &inputBuffer[cursorOffset],
+      .offsets = offsetsMap
+    },
+
+    .output = {
+      .begin = outputBuffer,
+      .end = outputBuffer + *outputLength,
+      .current = outputBuffer
+    }
+  };
+
+  if (checkContractionCache(&bcd, contractionCache)) {
+    useContractionCache(&bcd, contractionCache);
+  } else {
+    int contracted;
+
+    {
+      size_t length = getInputCount(&bcd);
+      wchar_t buffer[length];
+      unsigned int map[length + 1];
+
+      if (composeCharacters(&length, bcd.input.begin, buffer, map)) {
+        const wchar_t *oldBegin = bcd.input.begin;
+        const wchar_t *oldEnd = bcd.input.end;
+
+        bcd.input.begin = buffer;
+        bcd.input.current = bcd.input.begin + (bcd.input.current - oldBegin);
+        bcd.input.end = bcd.input.begin + length;
+
+        if (bcd.input.cursor) {
+          ptrdiff_t offset = bcd.input.cursor - oldBegin;
+          unsigned int mapIndex;
+
+          bcd.input.cursor = NULL;
+
+          for (mapIndex=0; mapIndex<=length; mapIndex+=1) {
+            unsigned int mappedIndex = map[mapIndex];
+
+            if (mappedIndex > offset) break;
+            bcd.input.cursor = &bcd.input.begin[mappedIndex];
+          }
+        }
+
+        contracted = contractionTable->translationMethods->contractText(&bcd);
+
+        if (bcd.input.offsets) {
+          size_t mapIndex = length;
+          size_t offsetsIndex = oldEnd - oldBegin;
+
+          while (mapIndex > 0) {
+            unsigned int mappedIndex = map[--mapIndex];
+            int offset = bcd.input.offsets[mapIndex];
+
+            if (offset != CTB_NO_OFFSET) {
+              while (--offsetsIndex > mappedIndex) bcd.input.offsets[offsetsIndex] = CTB_NO_OFFSET;
+              bcd.input.offsets[offsetsIndex] = offset;
+            }
+          }
+
+          while (offsetsIndex > 0) bcd.input.offsets[--offsetsIndex] = CTB_NO_OFFSET;
+        }
+
+        bcd.input.begin = oldBegin;
+        bcd.input.current = bcd.input.begin + map[bcd.input.current - buffer];
+        bcd.input.end = oldEnd;
+      } else {
+        contracted = contractionTable->translationMethods->contractText(&bcd);
+      }
+    }
+
+    if (!contracted) {
+      bcd.input.current = bcd.input.begin;
+      bcd.output.current = bcd.output.begin;
+
+      while ((bcd.input.current < bcd.input.end) && (bcd.output.current < bcd.output.end)) {
+        setOffset(&bcd);
+        *bcd.output.current++ = convertCharacterToDots(textTable, *bcd.input.current++);
+      }
+    }
+
+    if (bcd.input.current < bcd.input.end) {
+      const wchar_t *srcorig = bcd.input.current;
+      int done = 1;
+
+      setOffset(&bcd);
+      while (1) {
+        if (done && !testCurrent(&bcd, CTC_Space)) {
+          done = 0;
+
+          if (!bcd.input.cursor || (bcd.input.cursor < srcorig) || (bcd.input.cursor >= bcd.input.current)) {
+            setOffset(&bcd);
+            srcorig = bcd.input.current;
+          }
+        }
+
+        if (++bcd.input.current == bcd.input.end) break;
+        clearOffset(&bcd);
+      }
+
+      if (!done) bcd.input.current = srcorig;
+    }
+
+    updateContractionCache(&bcd, contractionCache);
+  }
+
+  *inputLength = getInputConsumed(&bcd);
+  *outputLength = getOutputConsumed(&bcd);
+}
+
+int
+replaceContractionTable (const char *directory, const char *name) {
+  ContractionTable *newTable = NULL;
+
+  if (*name) {
+    char *path = makeContractionTablePath(directory, name);
+
+    if (path) {
+      logMessage(LOG_DEBUG, "compiling contraction table: %s", path);
+
+      if (!(newTable = compileContractionTable(path))) {
+        logMessage(LOG_ERR, "%s: %s", gettext("cannot compile contraction table"), path);
+      }
+
+      free(path);
+    }
+  } else if (!(newTable = compileContractionTable(name))) {
+    logMessage(LOG_ERR, "%s: %s", gettext("cannot access internal contraction table"), CONTRACTION_TABLE);
+  }
+
+  if (newTable) {
+    ContractionTable *oldTable = contractionTable;
+
+    lockContractionTable();
+      contractionTable = newTable;
+    unlockContractionTable();
+
+    if (oldTable) destroyContractionTable(oldTable);
+    return 1;
+  }
+
+  logMessage(LOG_ERR, "%s: %s", gettext("cannot load contraction table"), name);
+  return 0;
+}
diff --git a/Programs/ctb_translate.h b/Programs/ctb_translate.h
new file mode 100644
index 0000000..57006e3
--- /dev/null
+++ b/Programs/ctb_translate.h
@@ -0,0 +1,151 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CTB_TRANSLATE
+#define BRLTTY_INCLUDED_CTB_TRANSLATE
+
+#include "ctb.h"
+#include "ctb_internal.h"
+#include "prefs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  ContractionTable *const table;
+
+  struct {
+    const wchar_t *begin;
+    const wchar_t *end;
+    const wchar_t *current;
+    const wchar_t *cursor;
+    int *offsets;
+  } input;
+
+  struct {
+    BYTE *begin;
+    BYTE *end;
+    BYTE *current;
+  } output;
+
+  struct {
+    const ContractionTableRule *rule;
+    ContractionTableOpcode opcode;
+    int length;
+
+    wchar_t before;
+    wchar_t after;
+  } current;
+
+  struct {
+    ContractionTableOpcode opcode;
+  } previous;
+} BrailleContractionData;
+
+struct ContractionTableTranslationMethodsStruct {
+  int (*contractText) (BrailleContractionData *bcd);
+  void (*finishCharacterEntry) (BrailleContractionData *bcd, CharacterEntry *entry);
+};
+
+static inline unsigned int
+getInputCount (BrailleContractionData *bcd) {
+  return bcd->input.end - bcd->input.begin;
+}
+
+static inline unsigned int
+getInputConsumed (BrailleContractionData *bcd) {
+  return bcd->input.current - bcd->input.begin;
+}
+
+static inline unsigned int
+getInputUnconsumed (BrailleContractionData *bcd) {
+  return bcd->input.end - bcd->input.current;
+}
+
+static inline unsigned int
+getOutputCount (BrailleContractionData *bcd) {
+  return bcd->output.end - bcd->output.begin;
+}
+
+static inline unsigned int
+getOutputConsumed (BrailleContractionData *bcd) {
+  return bcd->output.current - bcd->output.begin;
+}
+
+static inline void
+assignOffset (BrailleContractionData *bcd, int value) {
+  if (bcd->input.offsets) {
+    bcd->input.offsets[getInputConsumed(bcd)] = value;
+  }
+}
+
+static inline void
+setOffset (BrailleContractionData *bcd) {
+  assignOffset(bcd, getOutputConsumed(bcd));
+}
+
+static inline void
+clearOffset (BrailleContractionData *bcd) {
+  assignOffset(bcd, CTB_NO_OFFSET);
+}
+
+extern const CharacterEntry *getCharacterEntry (BrailleContractionData *bcd, wchar_t character);
+extern const CharacterEntry *findCharacterEntry (BrailleContractionData *bcd, wchar_t character, unsigned int *position);
+
+static inline int
+testCharacter (BrailleContractionData *bcd, wchar_t character, ContractionTableCharacterAttributes attributes) {
+  const CharacterEntry *entry = getCharacterEntry(bcd, character);
+  return entry && (attributes & entry->attributes);
+}
+
+static inline int
+testRelative (BrailleContractionData *bcd, int offset, ContractionTableCharacterAttributes attributes) {
+  return testCharacter(bcd, bcd->input.current[offset], attributes);
+}
+
+static inline int
+testCurrent (BrailleContractionData *bcd, ContractionTableCharacterAttributes attributes) {
+  return testRelative(bcd, 0, attributes);
+}
+
+static inline int
+testPrevious (BrailleContractionData *bcd, ContractionTableCharacterAttributes attributes) {
+  return testRelative(bcd, -1, attributes);
+}
+
+static inline int
+testNext (BrailleContractionData *bcd, ContractionTableCharacterAttributes attributes) {
+  return testRelative(bcd, 1, attributes);
+}
+
+static inline int
+testBefore (BrailleContractionData *bcd, ContractionTableCharacterAttributes attributes) {
+  return testCharacter(bcd, bcd->current.before, attributes);
+}
+
+static inline int
+testAfter (BrailleContractionData *bcd, ContractionTableCharacterAttributes attributes) {
+  return testCharacter(bcd, bcd->current.after, attributes);
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CTB_TRANSLATE */
diff --git a/Programs/dataarea.c b/Programs/dataarea.c
new file mode 100644
index 0000000..1d24bdf
--- /dev/null
+++ b/Programs/dataarea.c
@@ -0,0 +1,91 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "dataarea.h"
+
+struct DataAreaStruct {
+  unsigned char *address;
+  size_t size;
+  size_t used;
+};
+
+void
+resetDataArea (DataArea *area) {
+  area->address = NULL;
+  area->size = 0;
+  area->used = 0;
+}
+
+DataArea *
+newDataArea (void) {
+  DataArea *area;
+  if ((area = malloc(sizeof(*area)))) resetDataArea(area);
+  return area;
+}
+
+void
+destroyDataArea (DataArea *area) {
+  if (area->address) free(area->address);
+  free(area);
+}
+
+int
+allocateDataItem (DataArea *area, DataOffset *offset, size_t size, size_t alignment) {
+  size_t newOffset = (area->used + (alignment - 1)) / alignment * alignment;
+  size_t newUsed = newOffset + size;
+
+  if (newUsed > area->size) {
+    size_t newSize = (newUsed | 0XFFF) + 1;
+    unsigned char *newAddress;
+
+    if (!(newAddress = realloc(area->address, newSize))) {
+      logMallocError();
+      return 0;
+    }
+
+    memset(newAddress+area->size, 0, (newSize - area->size));
+    area->address = newAddress;
+    area->size = newSize;
+  }
+
+  area->used = newUsed;
+  if (offset) *offset = newOffset;
+  return 1;
+}
+
+void *
+getDataItem (DataArea *area, DataOffset offset) {
+  return area->address + offset;
+}
+
+size_t
+getDataSize (DataArea *area) {
+  return area->used;
+}
+
+int
+saveDataItem (DataArea *area, DataOffset *offset, const void *item, size_t size, size_t alignment) {
+  if (!allocateDataItem(area, offset, size, alignment)) return 0;
+  memcpy(getDataItem(area, *offset), item, size);
+  return 1;
+}
diff --git a/Programs/datafile.c b/Programs/datafile.c
new file mode 100644
index 0000000..7b8f27f
--- /dev/null
+++ b/Programs/datafile.c
@@ -0,0 +1,1335 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "file.h"
+#include "queue.h"
+#include "datafile.h"
+#include "variables.h"
+#include "utf8.h"
+#include "unicode.h"
+#include "brl_dots.h"
+
+struct DataFileStruct {
+  const char *const name;
+  const DataFileParameters *const parameters;
+  DataFile *const includer;
+  int line;
+
+  struct {
+    dev_t device;
+    ino_t file;
+  } identity;
+
+  Queue *conditions;
+  VariableNestingLevel *variables;
+
+  const wchar_t *start;
+  const wchar_t *end;
+};
+
+const wchar_t brlDotNumbers[BRL_DOT_COUNT] = {
+  WC_C('1'), WC_C('2'), WC_C('3'), WC_C('4'),
+  WC_C('5'), WC_C('6'), WC_C('7'), WC_C('8')
+};
+const unsigned char brlDotBits[BRL_DOT_COUNT] = {
+  BRL_DOT_1, BRL_DOT_2, BRL_DOT_3, BRL_DOT_4,
+  BRL_DOT_5, BRL_DOT_6, BRL_DOT_7, BRL_DOT_8
+};
+
+int
+brlDotNumberToIndex (wchar_t number, int *index) {
+  const wchar_t *character = wmemchr(brlDotNumbers, number, ARRAY_COUNT(brlDotNumbers));
+  if (!character) return 0;
+  *index = character - brlDotNumbers;
+  return 1;
+}
+
+int
+brlDotBitToIndex (unsigned char bit, int *index) {
+  const unsigned char *cell = memchr(brlDotBits, bit, ARRAY_COUNT(brlDotBits));
+  if (!cell) return 0;
+  *index = cell - brlDotBits;
+  return 1;
+}
+
+void
+reportDataError (DataFile *file, const char *format, ...) {
+  char message[0X200];
+
+  {
+    const char *name = NULL;
+    const int *line = NULL;
+    va_list args;
+
+    if (file) {
+      name = file->name;
+      if (file->line) line = &file->line;
+    }
+
+    va_start(args, format);
+    formatInputError(message, sizeof(message), name, line, format, args);
+    va_end(args);
+  }
+
+  logMessage(LOG_WARNING, "%s", message);
+}
+
+int
+compareKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count) {
+  while (count > 0) {
+    wchar_t character1;
+    wchar_t character2;
+
+    if (!(character1 = *keyword)) return -1;
+    keyword += 1;
+
+    character1 = towlower(character1);
+    character2 = towlower(*characters++);
+
+    if (character1 < character2) return -1;
+    if (character1 > character2) return 1;
+
+    count -= 1;
+  }
+
+  return *keyword? 1: 0;
+}
+
+int
+compareKeywords (const wchar_t *keyword1, const wchar_t *keyword2) {
+  return compareKeyword(keyword1, keyword2, wcslen(keyword2));
+}
+
+int
+isKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count) {
+  return compareKeyword(keyword, characters, count) == 0;
+}
+
+int
+isHexadecimalDigit (wchar_t character, int *value, int *shift) {
+  if ((character >= WC_C('0')) && (character <= WC_C('9'))) {
+    *value = character - WC_C('0');
+  } else if ((character >= WC_C('a')) && (character <= WC_C('f'))) {
+    *value = character - WC_C('a') + 10;
+  } else if ((character >= WC_C('A')) && (character <= WC_C('F'))) {
+    *value = character - WC_C('A') + 10;
+  } else {
+    return 0;
+  }
+
+  *shift = 4;
+  return 1;
+}
+
+int
+isOctalDigit (wchar_t character, int *value, int *shift) {
+  if ((character < WC_C('0')) || (character > WC_C('7'))) return 0;
+  *value = character - WC_C('0');
+  *shift = 3;
+  return 1;
+}
+
+int
+isNumber (int *number, const wchar_t *characters, int length) {
+  if (length > 0) {
+    char string[length + 1];
+    string[length] = 0;
+
+    {
+      int index;
+
+      for (index=0; index<length; index+=1) {
+        wchar_t wc = characters[index];
+        if (!iswLatin1(wc)) return 0;
+        string[index] = wc;
+      }
+    }
+
+    {
+      char *end;
+      long value = strtol(string, &end, 0);
+
+      if (!*end) {
+        *number = value;
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static VariableNestingLevel *baseDataVariables = NULL;
+static VariableNestingLevel *currentDataVariables = NULL;
+
+static VariableNestingLevel *
+getBaseDataVariables (void) {
+  if (baseDataVariables) {
+    releaseVariableNestingLevel(currentDataVariables);
+    deleteVariables(baseDataVariables);
+  } else {
+    VariableNestingLevel *globalVariables = getGlobalVariables(1);
+    if (!globalVariables) return NULL;
+
+    VariableNestingLevel *baseVariables = newVariableNestingLevel(globalVariables, "base");
+    if (!baseVariables) return NULL;
+
+    baseDataVariables = claimVariableNestingLevel(baseVariables);
+  }
+
+  currentDataVariables = claimVariableNestingLevel(baseDataVariables);
+  return baseDataVariables;
+}
+
+int
+setBaseDataVariables (const VariableInitializer *initializers) {
+  VariableNestingLevel *variables = getBaseDataVariables();
+  if (!variables) return 0;
+  return setStringVariables(variables, initializers);
+}
+
+int
+setTableDataVariables (const char *tableExtension, const char *subtableExtension) {
+  const VariableInitializer initializers[] = {
+    { .name = "tableExtension", .value = tableExtension },
+    { .name = "subtableExtension", .value = subtableExtension },
+    { .name = NULL }
+  };
+
+  return setBaseDataVariables(initializers);
+}
+
+static int
+pushDataVariableNestingLevel (void) {
+  VariableNestingLevel *variables = newVariableNestingLevel(currentDataVariables, NULL);
+  if (!variables) return 0;
+
+  releaseVariableNestingLevel(currentDataVariables);
+  currentDataVariables = claimVariableNestingLevel(variables);
+
+  return 1;
+}
+
+int
+findDataOperand (DataFile *file, const char *description) {
+  file->start = file->end;
+
+  while (iswspace(file->start[0])) file->start += 1;
+  if (*(file->end = file->start)) return 1;
+  if (description) reportDataError(file, "%s not specified", description);
+  return 0;
+}
+
+int
+getDataCharacter (DataFile *file, wchar_t *character) {
+  if (!*file->end) return 0;
+  *character = *file->end++;
+  return 1;
+}
+
+int
+ungetDataCharacters (DataFile *file, unsigned int count) {
+  unsigned int maximum = file->end - file->start;
+
+  if (count > maximum) {
+    reportDataError(file, "unget character count out of range: %u > %u",
+                    count, maximum);
+    return 0;
+  }
+
+  file->end -= count;
+  return 1;
+}
+
+void
+getTextRemaining (DataFile *file, DataOperand *text) {
+  file->end = file->start + wcslen(file->start);
+  text->characters = file->start;
+  text->length = file->end - file->start;
+}
+
+int
+getTextOperand (DataFile *file, DataOperand *text, const char *description) {
+  if (!findDataOperand(file, description)) return 0;
+  getTextRemaining(file, text);
+
+  while (text->length) {
+    unsigned int newLength = text->length - 1;
+    if (!iswspace(text->characters[newLength])) break;
+    text->length = newLength;
+  }
+
+  return 1;
+}
+
+int
+getDataOperand (DataFile *file, DataOperand *operand, const char *description) {
+  if (!findDataOperand(file, description)) return 0;
+
+  do {
+    file->end += 1;
+  } while (file->end[0] && !iswspace(file->end[0]));
+
+  operand->characters = file->start;
+  operand->length = file->end - file->start;
+  return 1;
+}
+
+int
+parseDataString (DataFile *file, DataString *string, const wchar_t *characters, int length, int noUnicode) {
+  int index = 0;
+
+  string->length = 0;
+
+  while (index < length) {
+    wchar_t character = characters[index];
+    DataOperand substitution = {
+      .characters = &character,
+      .length = 1
+    };
+
+    if (character == WC_C('\\')) {
+      int start = index;
+      const char *problem = strtext("invalid escape sequence");
+      int ok = 0;
+
+      if (++index < length) {
+        switch ((character = characters[index])) {
+          case WC_C('#'):
+          case WC_C('\\'):
+            ok = 1;
+            break;
+
+          case WC_C('b'):
+            character = WC_C('\b');
+            ok = 1;
+            break;
+
+          case WC_C('f'):
+            character = WC_C('\f');
+            ok = 1;
+            break;
+
+          case WC_C('n'):
+            character = WC_C('\n');
+            ok = 1;
+            break;
+
+          case WC_C('r'):
+            character = WC_C('\r');
+            ok = 1;
+            break;
+
+          case WC_C('R'):
+            character = UNICODE_REPLACEMENT_CHARACTER;
+            ok = 1;
+            break;
+
+          case WC_C('s'):
+            character = WC_C(' ');
+            ok = 1;
+            break;
+
+          case WC_C('t'):
+            character = WC_C('\t');
+            ok = 1;
+            break;
+
+          case WC_C('v'):
+            character = WC_C('\v');
+            ok = 1;
+            break;
+
+          {
+            uint32_t result;
+            int count;
+            int (*isDigit) (wchar_t character, int *value, int *shift);
+
+          case WC_C('o'):
+            count = 3;
+            isDigit = isOctalDigit;
+            goto doNumber;
+
+          case WC_C('U'):
+            if (noUnicode) break;
+            count = 8;
+            goto doHexadecimal;
+
+          case WC_C('u'):
+            if (noUnicode) break;
+            count = 4;
+            goto doHexadecimal;
+
+          case WC_C('X'):
+          case WC_C('x'):
+            count = 2;
+            goto doHexadecimal;
+
+          doHexadecimal:
+            isDigit = isHexadecimalDigit;
+            goto doNumber;
+
+          doNumber:
+            result = 0;
+
+            while (++index < length) {
+              {
+                int value;
+                int shift;
+
+                if (!isDigit(characters[index], &value, &shift)) break;
+                result = (result << shift) | value;
+              }
+
+              if (!--count) {
+                if (result > WCHAR_MAX) {
+                  problem = NULL;
+                } else {
+                  character = result;
+                  ok = 1;
+                }
+
+                break;
+              }
+            }
+
+            break;
+          }
+
+          case WC_C('{'): {
+            const wchar_t *first = &characters[++index];
+            const wchar_t *end = wmemchr(first, WC_C('}'), length-index);
+
+            if (end) {
+              int count = end - first;
+              index += count;
+
+              const Variable *variable = findReadableVariable(currentDataVariables, first, count);
+
+              if (variable) {
+                getVariableValue(variable, &substitution.characters, &substitution.length);
+                ok = 1;
+              }
+            } else {
+              index = length - 1;
+            }
+
+            break;
+          }
+
+          case WC_C('<'): {
+            const wchar_t *first = &characters[++index];
+            const wchar_t *end = wmemchr(first, WC_C('>'), length-index);
+
+            if (noUnicode) break;
+
+            if (end) {
+              int count = end - first;
+              index += count;
+
+              {
+                char name[count+1];
+
+                {
+                  unsigned int i;
+
+                  for (i=0; i<count; i+=1) {
+                    wchar_t wc = first[i];
+
+                    if (wc == WC_C('_')) wc = WC_C(' ');
+                    if (!iswLatin1(wc)) break;
+                    name[i] = wc;
+                  }
+
+                  if (i < count) break;
+                  name[i] = 0;
+                }
+
+                if (getCharacterByName(&character, name)) ok = 1;
+              }
+            } else {
+              index = length - 1;
+            }
+
+            break;
+          }
+        }
+      }
+
+      if (!ok) {
+        if (index < length) index += 1;
+
+        if (problem) {
+          reportDataError(file, "%s: %.*" PRIws,
+                          gettext(problem),
+                          index-start, &characters[start]);
+        }
+
+        return 0;
+      }
+    }
+    index += 1;
+
+    {
+      unsigned int newLength = string->length + substitution.length;
+
+      /* there needs to be room for a trailing NUL */
+      if (newLength >= ARRAY_COUNT(string->characters)) {
+        reportDataError(file, "string operand too long");
+        return 0;
+      }
+
+      wmemcpy(&string->characters[string->length], substitution.characters, substitution.length);
+      string->length = newLength;
+    }
+  }
+  string->characters[string->length] = 0;
+
+  return 1;
+}
+
+int
+getDataString (DataFile *file, DataString *string, int noUnicode, const char *description) {
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, description))
+    if (parseDataString(file, string, operand.characters, operand.length, noUnicode))
+      return 1;
+
+  return 0;
+}
+
+int
+writeHexadecimalCharacter (FILE *stream, wchar_t character) {
+  uint32_t value = character;
+
+  if (value < 0X100) {
+    return fprintf(stream, "\\x%02" PRIX32, value) != EOF;
+  } else if (value < 0X10000) {
+    return fprintf(stream, "\\u%04" PRIX32, value) != EOF;
+  } else {
+    return fprintf(stream, "\\U%08" PRIX32, value) != EOF;
+  }
+}
+
+int
+writeEscapedCharacter (FILE *stream, wchar_t character) {
+  {
+    static const char escapes[] = {
+      [' ']  = 's',
+      ['\\'] = '\\'
+    };
+
+    if (character < ARRAY_COUNT(escapes)) {
+      char escape = escapes[character];
+
+      if (escape) {
+        return fprintf(stream, "\\%c", escape) != EOF;
+      }
+    }
+  }
+
+  if (iswspace(character) || iswcntrl(character)) return writeHexadecimalCharacter(stream, character);
+  return writeUtf8Character(stream, character);
+}
+
+int
+writeEscapedCharacters (FILE *stream, const wchar_t *characters, size_t count) {
+  const wchar_t *end = characters + count;
+
+  while (characters < end)
+    if (!writeEscapedCharacter(stream, *characters++))
+      return 0;
+
+  return 1;
+}
+
+static int
+parseDotOperand (DataFile *file, int *index, const wchar_t *characters, int length) {
+  if (length == 1)
+    if (brlDotNumberToIndex(characters[0], index))
+      return 1;
+
+  reportDataError(file, "invalid braille dot number: %.*" PRIws, length, characters);
+  return 0;
+}
+
+int
+getDotOperand (DataFile *file, int *index) {
+  DataOperand number;
+
+  if (getDataOperand(file, &number, "dot number"))
+    if (parseDotOperand(file, index, number.characters, number.length))
+      return 1;
+
+  return 0;
+}
+
+int
+parseCellsOperand (DataFile *file, ByteOperand *cells, const wchar_t *characters, int length) {
+  unsigned char cell = 0;
+  int start = 0;
+  int index;
+
+  cells->length = 0;
+
+  for (index=0; index<length; index+=1) {
+    int started = index != start;
+    wchar_t character = characters[index];
+
+    switch (character) {
+      {
+        int dot;
+
+      case WC_C('1'):
+        dot = BRL_DOT_1;
+        goto doDot;
+
+      case WC_C('2'):
+        dot = BRL_DOT_2;
+        goto doDot;
+
+      case WC_C('3'):
+        dot = BRL_DOT_3;
+        goto doDot;
+
+      case WC_C('4'):
+        dot = BRL_DOT_4;
+        goto doDot;
+
+      case WC_C('5'):
+        dot = BRL_DOT_5;
+        goto doDot;
+
+      case WC_C('6'):
+        dot = BRL_DOT_6;
+        goto doDot;
+
+      case WC_C('7'):
+        dot = BRL_DOT_7;
+        goto doDot;
+
+      case WC_C('8'):
+        dot = BRL_DOT_8;
+        goto doDot;
+
+      doDot:
+        if (started && !cell) goto invalid;
+
+        if (cell & dot) {
+          reportDataError(file, "dot specified more than once: %.1" PRIws, &character);
+          return 0;
+        }
+
+        cell |= dot;
+        break;
+      }
+
+      case CELLS_OPERAND_SPACE:
+        if (started) goto invalid;
+        break;
+
+      case CELLS_OPERAND_DELIMITER:
+        if (!started) {
+          reportDataError(file, "missing cell specification: %.*" PRIws,
+                          length-index, &characters[index]);
+          return 0;
+        }
+
+        cells->bytes[cells->length++] = cell;
+
+        if (cells->length == ARRAY_COUNT(cells->bytes)) {
+          reportDataError(file, "cells operand too long");
+          return 0;
+        }
+
+        cell = 0;
+        start = index + 1;
+        break;
+
+      default:
+      invalid:
+        reportDataError(file, "invalid dot number: %.1" PRIws, &character);
+        return 0;
+    }
+  }
+
+  if (index == start) {
+    reportDataError(file, "missing cell specification");
+    return 0;
+  }
+
+  cells->bytes[cells->length++] = cell;		/*last cell */
+  return 1;
+}
+
+int
+getCellsOperand (DataFile *file, ByteOperand *cells, const char *description) {
+  DataOperand operand;
+
+  if (getDataOperand(file, &operand, description))
+    if (parseCellsOperand(file, cells, operand.characters, operand.length))
+      return 1;
+
+  return 0;
+}
+
+int
+writeDots (FILE *stream, unsigned char cell) {
+  unsigned int dot;
+
+  for (dot=1; dot<=BRL_DOT_COUNT; dot+=1) {
+    if (cell & BRL_DOT(dot)) {
+      if (fprintf(stream, "%u", dot) == EOF) return 0;
+    }
+  }
+
+  return 1;
+}
+
+int
+writeDotsCell (FILE *stream, unsigned char cell) {
+  if (!cell) return fputc('0', stream) != EOF;
+  return writeDots(stream, cell);
+}
+
+int
+writeDotsCells (FILE *stream, const unsigned char *cells, size_t count) {
+  const unsigned char *cell = cells;
+  const unsigned char *end = cells + count;
+
+  while (cell < end) {
+    if (cell != cells)
+      if (fputc('-', stream) == EOF)
+        return 0;
+
+    if (!writeDotsCell(stream, *cell++)) return 0;
+  }
+
+  return 1;
+}
+
+int
+writeUtf8Cell (FILE *stream, unsigned char cell) {
+  return writeUtf8Character(stream, (UNICODE_BRAILLE_ROW | cell));
+}
+
+int
+writeUtf8Cells (FILE *stream, const unsigned char *cells, size_t count) {
+  const unsigned char *end = cells + count;
+
+  while (cells < end)
+    if (!writeUtf8Cell(stream, *cells++))
+      return 0;
+
+  return 1;
+}
+
+typedef struct {
+  unsigned canInclude:1;
+  unsigned isIncluding:1;
+  unsigned inElse:1;
+} DataCondition;
+
+static inline int
+shallInclude (const DataCondition *condition) {
+  return condition->canInclude && condition->isIncluding;
+}
+
+static void
+deallocateDataCondition (void *item, void *data UNUSED) {
+  DataCondition *condition = item;
+
+  free(condition);
+}
+
+static Element *
+getInnermostDataCondition (DataFile *file) {
+  return getStackHead(file->conditions);
+}
+
+static Element *
+getCurrentDataCondition (DataFile *file) {
+  {
+    Element *element = getInnermostDataCondition(file);
+
+    if (element) return element;
+  }
+
+  reportDataError(file, "no outstanding condition");
+  return NULL;
+}
+
+static int
+removeDataCondition (DataFile *file, Element *element, int identifier) {
+  if (!(element && (identifier == getElementIdentifier(element)))) return 0;
+  deleteElement(element);
+  return 1;
+}
+
+static Element *
+pushDataCondition (
+  DataFile *file, const DataString *name,
+  DataConditionTester *testCondition, int negateCondition
+) {
+  DataCondition *condition;
+
+  if ((condition = malloc(sizeof(*condition)))) {
+    memset(condition, 0, sizeof(*condition));
+    condition->inElse = 0;
+
+    {
+      const DataOperand identifier = {
+        .characters = name->characters,
+        .length = name->length
+      };
+
+      condition->isIncluding = testCondition(file, &identifier, file->parameters->data);
+      if (negateCondition) condition->isIncluding = !condition->isIncluding;
+    }
+
+    {
+      Element *element = getInnermostDataCondition(file);
+
+      if (element) {
+        const DataCondition *parent = getElementItem(element);
+
+        condition->canInclude = shallInclude(parent);
+      } else {
+        condition->canInclude = 1;
+      }
+    }
+
+    {
+      Element *element = enqueueItem(file->conditions, condition);
+
+      if (element) return element;
+    }
+
+    free(condition);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+testDataCondition (DataFile *file) {
+  Element *element = getInnermostDataCondition(file);
+
+  if (element) {
+    const DataCondition *condition = getElementItem(element);
+
+    if (!shallInclude(condition)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+compareDataDirectiveNames (const wchar_t *name1, const wchar_t *name2) {
+  return compareKeywords(name1, name2);
+}
+
+static int
+sortDataDirectivesByName (const void *element1, const void *element2) {
+  const DataDirective *const *directive1 = element1;
+  const DataDirective *const *directive2 = element2;
+
+  return compareDataDirectiveNames((*directive1)->name, (*directive2)->name);
+}
+
+static int
+searchDataDirectiveByName (const void *target, const void *element) {
+  const wchar_t *name = target;
+  const DataDirective *const *directive = element;
+
+  return compareDataDirectiveNames(name, (*directive)->name);
+}
+
+static const DataDirective *
+findDataDirectiveByName (const DataDirectives *directives, const wchar_t *name) {
+  DataDirective **directive = bsearch(
+    name, directives->sorted.table, directives->sorted.count,
+    sizeof(*directives->sorted.table), searchDataDirectiveByName
+  );
+
+  return directive? *directive: NULL;
+}
+
+static int
+prepareDataDirectives (DataDirectives *directives) {
+  if (!directives->sorted.table) {
+    static const DataDirective unnamed = {
+      .name = NULL,
+      .processor = NULL
+    };
+
+    directives->unnamed = &unnamed;
+    directives->sorted.count = 0;
+
+    if (!(directives->sorted.table = malloc(ARRAY_SIZE(directives->sorted.table, directives->unsorted.count)))) {
+      logMallocError();
+      return 0;
+    }
+
+    {
+      const DataDirective *directive = directives->unsorted.table;
+      const DataDirective *end = directive + directives->unsorted.count;
+
+      while (directive < end) {
+        if (directive->name) {
+          directives->sorted.table[directives->sorted.count++] = directive;
+        } else {
+          directives->unnamed = directive;
+        }
+
+        directive += 1;
+      }
+    }
+
+    qsort(directives->sorted.table, directives->sorted.count,
+          sizeof(*directives->sorted.table), sortDataDirectivesByName);
+  }
+
+  return 1;
+}
+
+int
+processDirectiveOperand (DataFile *file, DataDirectives *directives, const char *description, void *data) {
+  DataOperand name;
+
+  if (getDataOperand(file, &name, description)) {
+    const DataDirective *directive;
+
+    if (!prepareDataDirectives(directives)) return 0;
+
+    {
+      wchar_t string[name.length + 1];
+
+      wmemcpy(string, name.characters, name.length);
+      string[name.length] = 0;
+
+      if (!(directive = findDataDirectiveByName(directives, string))) {
+        directive = directives->unnamed;
+        ungetDataCharacters(file, name.length);
+      }
+    }
+
+    if (!(directive->unconditional || testDataCondition(file))) return 1;
+    if (directive->processor) return directive->processor(file, data);
+
+    reportDataError(file, "unknown %s: %.*" PRIws,
+                    description, name.length, name.characters);
+  }
+
+  return 1;
+}
+
+static int
+processDataOperands (DataFile *file) {
+  return file->parameters->processOperands(file, file->parameters->data);
+}
+
+static int
+processDataCharacters (DataFile *file, const wchar_t *line) {
+  file->end = file->start = line;
+
+  if (!(file->parameters->options & DFO_NO_COMMENTS)) {
+    if (!findDataOperand(file, NULL)) return 1;
+    if (file->start[0] == WC_C('#')) return 1;
+  }
+
+  return processDataOperands(file);
+}
+
+static int
+processConditionSubdirective (DataFile *file, Element *element) {
+  int identifier = getElementIdentifier(element);
+
+  if (findDataOperand(file, NULL)) {
+    int result = processDataOperands(file);
+    removeDataCondition(file, element, identifier);
+    return result;
+  }
+
+  return 1;
+}
+
+int
+processConditionOperands (
+  DataFile *file,
+  DataConditionTester *testCondition, int negateCondition,
+  const char *description, void *data
+) {
+  DataString name;
+
+  if (getDataString(file, &name, 1, description)) {
+    Element *element = pushDataCondition(file, &name, testCondition, negateCondition);
+
+    if (!element) return 0;
+    if (!processConditionSubdirective(file, element)) return 0;
+  }
+
+  return 1;
+}
+
+static DATA_CONDITION_TESTER(testVariableDefined) {
+  return !!findReadableVariable(currentDataVariables, identifier->characters, identifier->length);
+}
+
+static int
+processVariableTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testVariableDefined, not, "variable name", data);
+}
+
+DATA_OPERANDS_PROCESSOR(processIfVarOperands) {
+  return processVariableTestOperands(file, 0, data);
+}
+
+DATA_OPERANDS_PROCESSOR(processIfNotVarOperands) {
+  return processVariableTestOperands(file, 1, data);
+}
+
+DATA_OPERANDS_PROCESSOR(processBeginVariablesOperands) {
+  return pushDataVariableNestingLevel();
+}
+
+DATA_OPERANDS_PROCESSOR(processEndVariablesOperands) {
+  if (currentDataVariables == file->variables) {
+    reportDataError(file, "no nested variables");
+  } else {
+    currentDataVariables = removeVariableNestingLevel(currentDataVariables);
+  }
+
+  return 1;
+}
+
+DATA_OPERANDS_PROCESSOR(processListVariablesOperands) {
+  listVariables(currentDataVariables);
+  return 1;
+}
+
+static int
+processVariableAssignmentOperands (DataFile *file, int ifNotSet, void *data) {
+  DataOperand name;
+
+  if (getDataOperand(file, &name, "variable name")) {
+    DataString value;
+
+    if (!getDataString(file, &value, 0, NULL)) {
+      value.length = 0;
+    }
+
+    if (ifNotSet) {
+      const Variable *variable = findReadableVariable(currentDataVariables, name.characters, name.length);
+
+      if (variable) return 1;
+    }
+
+    {
+      Variable *variable = findWritableVariable(currentDataVariables, name.characters, name.length);
+
+      if (variable) {
+        if (setVariable(variable, value.characters, value.length)) return 1;
+      }
+    }
+  }
+
+  return 1;
+}
+
+DATA_OPERANDS_PROCESSOR(processAssignDefaultOperands) {
+  return processVariableAssignmentOperands(file, 1, data);
+}
+
+DATA_OPERANDS_PROCESSOR(processAssignOperands) {
+  return processVariableAssignmentOperands(file, 0, data);
+}
+
+DATA_OPERANDS_PROCESSOR(processElseOperands) {
+  Element *element = getCurrentDataCondition(file);
+
+  if (element) {
+    DataCondition *condition = getElementItem(element);
+
+    if (condition->inElse) {
+      reportDataError(file, "already in else");
+    } else {
+      condition->inElse = 1;
+      condition->isIncluding = !condition->isIncluding;
+      if (!processConditionSubdirective(file, element)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+DATA_OPERANDS_PROCESSOR(processEndIfOperands) {
+  Element *element = getCurrentDataCondition(file);
+
+  if (element) {
+    removeDataCondition(file, element, getElementIdentifier(element));
+  }
+
+  return 1;
+}
+
+static int
+isDataFileIncluded (DataFile *file, const char *path) {
+  struct stat info;
+
+  if (stat(path, &info) != -1) {
+    while (file) {
+      if ((file->identity.device == info.st_dev) && (file->identity.file == info.st_ino)) return 1;
+      file = file->includer;
+    }
+  }
+
+  return 0;
+}
+
+static FILE *
+openIncludedDataFile (DataFile *includer, const char *path, const char *mode, int optional) {
+  const char *const *overrideDirectories = getAllOverrideDirectories();
+  const char *overrideDirectory = NULL;
+  char *overridePath = NULL;
+
+  int writable = (*mode == 'w') || (*mode == 'a');
+  const char *name = locatePathName(path);
+  FILE *file;
+
+  if (overrideDirectories) {
+    const char *const *directory = overrideDirectories;
+
+    while (*directory) {
+      if (**directory) {
+        char *path = makePath(*directory, name);
+
+        if (path) {
+          if (!isDataFileIncluded(includer, path)) {
+            if (testFilePath(path)) {
+              file = openFile(path, mode, optional);
+              overrideDirectory = *directory;
+              overridePath = path;
+              goto done;
+            }
+          }
+
+          free(path);
+        }
+      }
+
+      directory += 1;
+    }
+  }
+
+  if (isDataFileIncluded(includer, path)) {
+    logMessage(LOG_WARNING, "data file include loop: %s", path);
+    file = NULL;
+    errno = ENOENT;
+  } else if (!(file = openFile(path, mode, optional))) {
+    if (writable) {
+      if (errno == ENOENT) {
+        char *directory = getPathDirectory(path);
+
+        if (directory) {
+          int exists = ensureDirectory(directory, 0);
+          free(directory);
+
+          if (exists) {
+            file = openFile(path, mode, optional);
+            goto done;
+          }
+        }
+      }
+
+      if ((errno == EACCES) || (errno == EROFS)) {
+        if ((overrideDirectory = getPrimaryOverrideDirectory())) {
+          if ((overridePath = makePath(overrideDirectory, name))) {
+            if (ensureDirectory(overrideDirectory, 0)) {
+              file = openFile(overridePath, mode, optional);
+              goto done;
+            }
+          }
+        }
+      }
+    }
+  }
+
+done:
+  if (overridePath) free(overridePath);
+  return file;
+}
+
+FILE *
+openDataFile (const char *path, const char *mode, int optional) {
+  return openIncludedDataFile(NULL, path, mode, optional);
+}
+
+int
+includeDataFile (DataFile *file, const wchar_t *name, int length) {
+  int ok = 0;
+
+  const char *prefixAddress = file->name;
+  size_t prefixLength = 0;
+
+  size_t suffixLength;
+  char *suffixAddress = getUtf8FromWchars(name, length, &suffixLength);
+
+  if (suffixAddress) {
+    if (!isAbsolutePath(suffixAddress)) {
+      const char *prefixEnd = strrchr(prefixAddress, '/');
+      if (prefixEnd) prefixLength = prefixEnd - prefixAddress + 1;
+    }
+
+    {
+      char path[prefixLength + suffixLength + 1];
+      FILE *stream;
+
+      snprintf(path, sizeof(path), "%.*s%.*s",
+               (int)prefixLength, prefixAddress,
+               (int)suffixLength, suffixAddress);
+
+      if ((stream = openIncludedDataFile(file, path, "r", 0))) {
+        if (processDataStream(file, stream, path, file->parameters)) ok = 1;
+        fclose(stream);
+      }
+    }
+
+    free(suffixAddress);
+  } else {
+    logMallocError();
+  }
+
+  return ok;
+}
+
+DATA_OPERANDS_PROCESSOR(processIncludeOperands) {
+  DataString path;
+
+  if (getDataString(file, &path, 0, "include file path"))
+    if (!includeDataFile(file, path.characters, path.length))
+      return 0;
+
+  return 1;
+}
+
+static int
+processDataLine (const LineHandlerParameters *parameters) {
+  DataFile *file = parameters->data;
+  file->line += 1;
+
+  const char *byte = parameters->line.text;
+  size_t size = parameters->line.length + 1;
+  wchar_t characters[size];
+  wchar_t *character = characters;
+
+  convertUtf8ToWchars(&byte, &character, size);
+  character = characters;
+
+  if (*byte) {
+    unsigned int offset = byte - parameters->line.text;
+    reportDataError(file, "illegal UTF-8 character at offset %u", offset);
+    return 1;
+  }
+
+  if (file->line == 1) {
+    if (*character == UNICODE_BYTE_ORDER_MARK) {
+      character += 1;
+    }
+  }
+
+  return processDataCharacters(file, character);
+}
+
+int
+processDataStream (
+  DataFile *includer,
+  FILE *stream, const char *name,
+  const DataFileParameters *parameters
+) {
+  int ok = 0;
+
+  if (parameters->logFileName) {
+    parameters->logFileName(name, parameters->data);
+  } else {
+    logMessage(LOG_DEBUG, "including data file: %s", name);
+  }
+
+  DataFile file = {
+    .name = name,
+    .parameters = parameters,
+    .includer = includer,
+    .line = 0,
+  };
+
+  {
+    struct stat info;
+
+    if (fstat(fileno(stream), &info) != -1) {
+      file.identity.device = info.st_dev;
+      file.identity.file = info.st_ino;
+    }
+  }
+
+  VariableNestingLevel *oldVariables = currentDataVariables;
+
+  if ((file.variables = newVariableNestingLevel(oldVariables, name))) {
+    currentDataVariables = claimVariableNestingLevel(file.variables);
+
+    if ((file.conditions = newQueue(deallocateDataCondition, NULL))) {
+      if (processLines(stream, processDataLine, &file)) ok = 1;
+
+      if (getInnermostDataCondition(&file)) {
+        reportDataError(&file, "outstanding condition at end of file");
+      }
+
+      deallocateQueue(file.conditions);
+    }
+
+    releaseVariableNestingLevel(currentDataVariables);
+    currentDataVariables = oldVariables;
+  }
+
+  return ok;
+}
+
+int
+processDataFile (const char *name, const DataFileParameters *parameters) {
+  int ok = 0;
+  FILE *stream;
+
+  if ((stream = openDataFile(name, "r", 0))) {
+    if (processDataStream(NULL, stream, name, parameters)) ok = 1;
+    fclose(stream);
+  }
+
+  return ok;
+}
diff --git a/Programs/defaults.h b/Programs/defaults.h
new file mode 100644
index 0000000..3c2f87e
--- /dev/null
+++ b/Programs/defaults.h
@@ -0,0 +1,162 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_DEFAULTS
+#define BRLTTY_INCLUDED_DEFAULTS
+
+#include "parameters.h"
+#include "ctb_types.h"
+#include "brl_types.h"
+#include "spk_types.h"
+#include "tune_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Edit as necessary for your system. */
+
+#define DEFAULT_MESSAGE_HOLD_TIMEOUT MESSAGE_HOLD_TIMEOUT
+/* Under 5 seconds (init's SIGTERM-SIGKILL delay during shutdown)
+ * is good as that allows "exiting" to be replaced by "terminated"
+ * on the display.
+ */
+
+#define DEFAULT_TRACK_SCREEN_CURSOR 1		/* 1 for on, 0 for off */
+#define DEFAULT_HIDE_SCREEN_CURSOR 0		/* 1 for yes, 0 for no */
+
+#define DEFAULT_SAVE_ON_EXIT 0
+#define DEFAULT_SHOW_SUBMENU_SIZES 0
+#define DEFAULT_SHOW_ADVANCED_SUBMENUS 0
+#define DEFAULT_SHOW_ALL_ITEMS 0
+
+#define DEFAULT_BRAILLE_VARIANT bvComputer8
+#define DEFAULT_EXPAND_CURRENT_WORD 1
+#define DEFAULT_CAPITALIZATION_MODE CTB_CAP_SIGN
+#define DEFAULT_BRAILLE_FIRMNESS BRL_FIRMNESS_MEDIUM
+
+#define DEFAULT_SHOW_SCREEN_CURSOR 1		/* 1 for yes, 0 for no */
+#define DEFAULT_SCREEN_CURSOR_STYLE csBottomDots
+#define DEFAULT_BLINKING_SCREEN_CURSOR 0		/* 1 for on, 0 for off */
+#define DEFAULT_SCREEN_CURSOR_VISIBLE_TIME 40
+#define DEFAULT_SCREEN_CURSOR_INVISIBLE_TIME 40
+
+#define DEFAULT_SHOW_ATTRIBUTES 0          /* 1 for on, 0 for off */
+#define DEFAULT_BLINKING_ATTRIBUTES 1        /* 1 for on, 0 for off */
+#define DEFAULT_ATTRIBUTES_VISIBLE_TIME 20      /* for attribute underlining */
+#define DEFAULT_ATTRIBUTES_INVISIBLE_TIME 60
+
+#define DEFAULT_BLINKING_CAPITALS 0		/* 1 for on, 0 for off */
+#define DEFAULT_CAPITALS_VISIBLE_TIME 60	/* for blinking caps */
+#define DEFAULT_CAPITALS_INVISIBLE_TIME 20
+
+#define DEFAULT_WORD_WRAP 0
+#define DEFAULT_SKIP_IDENTICAL_LINES 0
+#define DEFAULT_SKIP_BLANK_BRAILLE_WINDOWS 0
+#define DEFAULT_SKIP_BLANK_BRAILLE_WINDOWS_MODE sbwEndOfLine
+#define DEFAULT_SLIDING_BRAILLE_WINDOW 0
+#define DEFAULT_EAGER_SLIDING_BRAILLE_WINDOW 0
+#define DEFAULT_BRAILLE_WINDOW_OVERLAP 0
+
+#define DEFAULT_SCROLL_AWARE_CURSOR_NAVIGATION 0
+#define DEFAULT_CURSOR_TRACKING_DELAY ctd250ms
+#define DEFAULT_TRACK_SCREEN_SCROLL 0		/* 1 for on, 0 for off */
+#define DEFAULT_TRACK_SCREEN_POINTER 0		/* 1 for on, 0 for off */
+#define DEFAULT_HIGHLIGHT_BRAILLE_WINDOW_LOCATION 0		/* 1 for on, 0 for off */
+#define DEFAULT_START_SELECTION_WITH_ROUTING_KEY 0		/* 1 for on, 0 for off */
+
+#define DEFAULT_BRAILLE_KEYBOARD_ENABLED 1
+#define DEFAULT_BRAILLE_TYPING_MODE BRL_TYPING_TEXT
+#define DEFAULT_BRAILLE_QUICK_SPACE 0
+
+#define DEFAULT_AUTORELEASE_TIME at20s
+#define DEFAULT_ON_FIRST_RELEASE 1
+#define DEFAULT_LONG_PRESS_TIME 50	/* hundredths of a second */
+#define DEFAULT_AUTOREPEAT_ENABLED 1		/* 1 for on, 0 for off */
+#define DEFAULT_AUTOREPEAT_INTERVAL 10	/* hundredths of a second */
+#define DEFAULT_AUTOREPEAT_PANNING 0	/* 1 for on, 0 for off */
+#define DEFAULT_TOUCH_NAVIGATION 0
+#define DEFAULT_TOUCH_SENSITIVITY BRL_SENSITIVITY_MEDIUM
+
+#define DEFAULT_CONSOLE_BELL_ALERT 0 /* 1 for on, 0 for off */
+#define DEFAULT_KEYBOARD_LED_ALERTS 0 /* 1 for on, 0 for off */
+
+#define DEFAULT_SPEAK_KEY_CONTEXT 0  /* 1 for on, 0 for off */
+#define DEFAULT_SPEAK_MODIFIER_KEY 0 /* 1 for on, 0 for off */
+
+#define DEFAULT_ALERT_TUNES 1		/* 1 for on, 0 for off */
+#define DEFAULT_ALERT_DOTS 0		/* 1 for on, 0 for off */
+#define DEFAULT_ALERT_MESSAGES 0		/* 1 for on, 0 for off */
+
+#if defined(HAVE_BEEP_SUPPORT)
+#define DEFAULT_TUNE_DEVICE tdBeeper
+#elif defined(HAVE_PCM_SUPPORT)
+#define DEFAULT_TUNE_DEVICE tdPcm
+#elif defined(HAVE_MIDI_SUPPORT)
+#define DEFAULT_TUNE_DEVICE tdMidi
+#elif defined(HAVE_FM_SUPPORT)
+#define DEFAULT_TUNE_DEVICE tdFm
+#else /* no tune devices are supported */
+#define DEFAULT_TUNE_DEVICE 0
+#endif /* default tune device */
+
+#define DEFAULT_PCM_VOLUME 70		/* 0 to 100 (percent) */
+#define DEFAULT_MIDI_VOLUME 70		/* 0 to 100 (percent) */
+#define DEFAULT_MIDI_INSTRUMENT 0	/* 0 to 127 */
+#define DEFAULT_FM_VOLUME 70		/* 0 to 100 (percent) */
+
+#define DEFAULT_SPEECH_VOLUME SPK_VOLUME_DEFAULT
+#define DEFAULT_SPEECH_RATE SPK_RATE_DEFAULT
+#define DEFAULT_SPEECH_PITCH SPK_PITCH_DEFAULT
+#define DEFAULT_SPEECH_PUNCTUATION SPK_PUNCTUATION_SOME
+
+#define DEFAULT_SPEECH_UPPERCASE_INDICATOR sucNone
+#define DEFAULT_SPEECH_WHITESPACE_INDICATOR swsNone
+#define DEFAULT_SAY_LINE_MODE sayImmediate
+
+#define DEFAULT_AUTOSPEAK 0		/* 1 for on, 0 for off */
+#define DEFAULT_AUTOSPEAK_SELECTED_LINE 1
+#define DEFAULT_AUTOSPEAK_SELECTED_CHARACTER 1
+#define DEFAULT_AUTOSPEAK_INSERTED_CHARACTERS 1
+#define DEFAULT_AUTOSPEAK_DELETED_CHARACTERS 1
+#define DEFAULT_AUTOSPEAK_REPLACED_CHARACTERS 1
+#define DEFAULT_AUTOSPEAK_COMPLETED_WORDS 1
+#define DEFAULT_AUTOSPEAK_LINE_INDENT 0
+
+#define DEFAULT_SHOW_SPEECH_CURSOR 0
+#define DEFAULT_SPEECH_CURSOR_STYLE csLowerRightDot
+#define DEFAULT_BLINKING_SPEECH_CURSOR 0
+#define DEFAULT_SPEECH_CURSOR_VISIBLE_TIME 50
+#define DEFAULT_SPEECH_CURSOR_INVISIBLE_TIME 30
+
+#define DEFAULT_TIME_FORMAT tf24Hour
+#define DEFAULT_TIME_SEPARATOR tsColon
+#define DEFAULT_SHOW_SECONDS 1
+#define DEFAULT_DATE_POSITION dpNone
+#define DEFAULT_DATE_FORMAT dfYearMonthDay
+#define DEFAULT_DATE_SEPARATOR dsDash
+
+#define DEFAULT_STATUS_POSITION spNone
+#define DEFAULT_STATUS_COUNT 0
+#define DEFAULT_STATUS_SEPARATOR ssNone
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_DEFAULTS */
diff --git a/Programs/device.c b/Programs/device.c
new file mode 100644
index 0000000..68083ed
--- /dev/null
+++ b/Programs/device.c
@@ -0,0 +1,230 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "device.h"
+#include "file.h"
+#include "parse.h"
+
+FILE *
+getConsole (void) {
+#if defined(GRUB_RUNTIME)
+  return stdout;
+#else /* get console */
+  static FILE *console = NULL;
+
+  if (!console) {
+    if ((console = fopen("/dev/console", "wb"))) {
+      logMessage(LOG_DEBUG, "console opened: fd=%d", fileno(console));
+      registerProgramStream("console-stream", &console);
+    } else {
+      logSystemError("console open");
+    }
+  }
+
+  return console;
+#endif /* get console */
+}
+
+int
+writeToConsole (const unsigned char *bytes, size_t count) {
+  FILE *console = getConsole();
+  if (!console) return 0;
+
+  while (count) {
+    size_t result = fwrite(bytes, 1, count, console);
+    if (!ferror(console)) fflush(console);
+
+    if (ferror(console)) {
+      logSystemError("console write");
+      return 0;
+    }
+
+    bytes += result;
+    count -= result;
+  }
+
+  return 1;
+}
+
+int
+ringConsoleBell (void) {
+  static unsigned char bellSequence[] = {0X07};
+  return writeToConsole(bellSequence, sizeof(bellSequence));
+}
+
+const char *
+getDeviceDirectory (void) {
+  static const char *deviceDirectory = NULL;
+
+  if (!deviceDirectory) {
+    const char *directory = DEVICE_DIRECTORY;
+    const size_t directoryLength = strlen(directory);
+
+    static const char *const variables[] = {"DTDEVROOT", "UTDEVROOT", NULL};
+    const char *const *variable = variables;
+
+    while (*variable) {
+      const char *root = getenv(*variable);
+
+      if (root && *root) {
+        const size_t rootLength = strlen(root);
+        char path[rootLength + directoryLength + 1];
+        snprintf(path, sizeof(path), "%s%s", root, directory);
+
+        if (testDirectoryPath(path)) {
+          if ((deviceDirectory = strdup(path))) goto found;
+          logMallocError();
+        } else if (errno != ENOENT) {
+          logMessage(LOG_ERR, "device directory error: %s (%s): %s",
+                     path, *variable, strerror(errno));
+        }
+      }
+
+      variable += 1;
+    }
+
+    deviceDirectory = directory;
+  found:
+    logMessage(LOG_DEBUG, "device directory: %s", deviceDirectory);
+  }
+
+  return deviceDirectory;
+}
+
+char *
+getDevicePath (const char *device) {
+  const char *directory = getDeviceDirectory();
+
+#ifdef ALLOW_DOS_DEVICE_NAMES
+  if (isDosDevice(device, NULL)) {
+  //directory = NULL;
+  }
+#endif /* ALLOW_DOS_DEVICE_NAMES */
+
+  return makePath(directory, device);
+}
+
+const char *
+resolveDeviceName (const char *const *names, int strict, const char *description) {
+  const char *first = *names;
+  const char *device = NULL;
+  const char *name;
+
+  while ((name = *names++)) {
+    char *path = getDevicePath(name);
+    if (!path) break;
+    logMessage(LOG_DEBUG, "checking %s device: %s", description, path);
+
+    if (testPath(path)) {
+      device = name;
+      free(path);
+      break;
+    }
+
+    logMessage(LOG_DEBUG, "%s device access error: %s: %s",
+               description, path, strerror(errno));
+
+    if (errno != ENOENT) {
+      if (!device) {
+        device = name;
+      }
+    }
+
+    free(path);
+  }
+
+  if (!device) {
+    if (!first) {
+      logMessage(LOG_ERR, "%s device names not defined", description);
+    } else if (strict) {
+      logMessage(LOG_ERR, "%s device not found", description);
+    } else {
+      device = first;
+    }
+  }
+
+  if (device) logMessage(LOG_INFO, "%s device: %s", description, device);
+  return device;
+}
+
+char **
+getDeviceParameters (const char *const *names, const char *identifier) {
+  char parameters[strlen(names[0]) + 1 + strlen(identifier) + 1];
+  STR_BEGIN(parameters, sizeof(parameters))
+
+  {
+    static const char characters[] = {
+      DEVICE_PARAMETER_SEPARATOR,
+      PARAMETER_ASSIGNMENT_CHARACTER,
+      0
+    };
+    const char *character = strpbrk(identifier, characters);
+
+    if (!(character && (*character == PARAMETER_ASSIGNMENT_CHARACTER))) {
+       STR_PRINTF("%s%c", names[0], PARAMETER_ASSIGNMENT_CHARACTER);
+    }
+  }
+
+  {
+    char *character = STR_NEXT;
+    STR_PRINTF("%s", identifier);
+
+    while (character < STR_NEXT) {
+      if (*character == DEVICE_PARAMETER_SEPARATOR) *character = PARAMETER_SEPARATOR_CHARACTER;
+      character += 1;
+    }
+  }
+
+  STR_END;
+  return getParameters(names, NULL, parameters);
+}
+
+#ifdef ALLOW_DOS_DEVICE_NAMES
+int
+isDosDevice (const char *identifier, const char *prefix) {
+  size_t count = strcspn(identifier, ":");
+  size_t length;
+
+  if (!count) return 0;
+
+  if (prefix) {
+    if (!(length = strlen(prefix))) return 0;
+    if (length > count) return 0;
+    if (strncasecmp(identifier, prefix, length) != 0) return 0;
+  } else {
+    length = strspn(identifier, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+    if (!length) return 0;
+  }
+
+  identifier += length;
+  count -= length;
+
+  if (strspn(identifier, "0123456789") != count) return 0;
+  return 1;
+}
+#endif /* ALLOW_DOS_DEVICE_NAMES */
diff --git a/Programs/driver.c b/Programs/driver.c
new file mode 100644
index 0000000..576a8a6
--- /dev/null
+++ b/Programs/driver.c
@@ -0,0 +1,87 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "driver.h"
+
+void
+unsupportedDeviceIdentifier (const char *identifier) {
+  logMessage(LOG_WARNING, "unsupported device identifier: %s", identifier);
+}
+
+void
+logOutputPacket (const void *packet, size_t size) {
+  logBytes(LOG_CATEGORY(OUTPUT_PACKETS), "sent", packet, size);
+}
+
+void
+logInputPacket (const void *packet, size_t size) {
+  logBytes(LOG_CATEGORY(INPUT_PACKETS), NULL, packet, size);
+}
+
+void
+logInputProblem (const char *problem, const unsigned char *bytes, size_t count) {
+  logBytes(LOG_WARNING, "%s", bytes, count, problem);
+}
+
+void
+logIgnoredByte (unsigned char byte) {
+  logInputProblem("Ignored Byte", &byte, 1);
+}
+
+void
+logDiscardedByte (unsigned char byte) {
+  logInputProblem("Discarded Byte", &byte, 1);
+}
+
+void
+logUnknownPacket (unsigned char byte) {
+  logInputProblem("Unknown Packet", &byte, 1);
+}
+
+void
+logPartialPacket (const void *packet, size_t size) {
+  logInputProblem("Partial Packet", packet, size);
+}
+
+void
+logTruncatedPacket (const void *packet, size_t size) {
+  logInputProblem("Truncated Packet", packet, size);
+}
+
+void
+logShortPacket (const void *packet, size_t size) {
+  logInputProblem("Short Packet", packet, size);
+}
+
+void
+logUnexpectedPacket (const void *packet, size_t size) {
+  logInputProblem("Unexpected Packet", packet, size);
+}
+
+void
+logCorruptPacket (const void *packet, size_t size) {
+  logInputProblem("Corrupt Packet", packet, size);
+}
+
+void
+logDiscardedBytes (const unsigned char *bytes, size_t count) {
+  logInputProblem("Discarded Bytes", bytes, count);
+}
diff --git a/Programs/drivers.c b/Programs/drivers.c
new file mode 100644
index 0000000..e64237c
--- /dev/null
+++ b/Programs/drivers.c
@@ -0,0 +1,201 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "file.h"
+#include "dynld.h"
+#include "drivers.h"
+
+int
+isDriverAvailable (const char *code, const char *codes) {
+  int length = strlen(code);
+  const char *string = codes;
+
+  while ((string = strstr(string, code))) {
+    if (((string == codes) || (string[-1] == ' ')) &&
+        (!string[length] || (string[length] == ' '))) {
+      return 1;
+    }
+
+    string += length;
+  }
+
+  return 0;
+}
+
+int
+isDriverIncluded (const char *code, const DriverEntry *table) {
+  while (table->address) {
+    if (strcmp(code, table->definition->code) == 0) return 1;
+    ++table;
+  }
+  return 0;
+}
+
+int
+haveDriver (const char *code, const char *codes, const DriverEntry *table) {
+  return (table && table->address)? isDriverIncluded(code, table):
+                                    isDriverAvailable(code, codes);
+}
+
+const char *
+getDefaultDriver (const DriverEntry *table) {
+  return (table && table[0].address && !table[1].address)? table[0].definition->code: NULL;
+}
+
+static int
+isDriverCode (const char *code, const DriverDefinition *definition) {
+  if (strcmp(code, definition->code) == 0) return 1;
+  return 0;
+}
+
+const void *
+loadDriver (
+  const char *driverCode, void **driverObject,
+  const char *driverDirectory, const DriverEntry *driverTable,
+  const char *typeName, char typeLetter, const char *symbolPrefix,
+  const void *nullAddress, const DriverDefinition *nullDefinition
+) {
+  const void *driverAddress = NULL;
+  *driverObject = NULL;
+
+  if (!driverCode || !*driverCode) {
+    if (driverTable)
+      if (driverTable->address)
+        return driverTable->address;
+    return nullAddress;
+  }
+
+  if (isDriverCode(driverCode, nullDefinition)) return nullAddress;
+
+  if (driverTable) {
+    const DriverEntry *driverEntry = driverTable;
+    while (driverEntry->address) {
+      if (isDriverCode(driverCode, driverEntry->definition)) return driverEntry->address;
+      ++driverEntry;
+    }
+  }
+
+#ifdef ENABLE_SHARED_OBJECTS
+  {
+    char *libraryPath;
+    const int libraryNameLength = strlen(MODULE_NAME) + strlen(driverCode) + strlen(MODULE_EXTENSION) + 3;
+    char libraryName[libraryNameLength];
+    snprintf(libraryName, libraryNameLength, "%s%c%s.%s",
+             MODULE_NAME, typeLetter, driverCode, MODULE_EXTENSION);
+
+    if ((libraryPath = makePath(driverDirectory, libraryName))) {
+      void *libraryHandle = loadSharedObject(libraryPath);
+
+      if (libraryHandle) {
+        const int driverSymbolLength = strlen(symbolPrefix) + 8 + strlen(driverCode) + 1;
+        char driverSymbol[driverSymbolLength];
+        snprintf(driverSymbol, driverSymbolLength, "%s_driver_%s",
+                 symbolPrefix, driverCode);
+
+        if (findSharedSymbol(libraryHandle, driverSymbol, &driverAddress)) {
+          *driverObject = libraryHandle;
+
+          {
+            const void *versionAddress = NULL;
+            const int versionSymbolLength = strlen(symbolPrefix) + 9 + strlen(driverCode) + 1;
+            char versionSymbol[versionSymbolLength];
+            snprintf(versionSymbol, versionSymbolLength, "%s_version_%s",
+                     symbolPrefix, driverCode);
+
+            if (findSharedSymbol(libraryHandle, versionSymbol, &versionAddress)) {
+              const char *actualVersion = versionAddress;
+              static const char *expectedVersion = DRIVER_VERSION_STRING;
+
+              if (strcmp(actualVersion, expectedVersion) != 0) {
+                logMessage(LOG_WARNING,
+                  "%s driver version mismatch: %s: Expected:%s Actual%s",
+                  typeName, driverCode,
+                  expectedVersion, actualVersion
+                );
+              }
+            } else {
+              logMessage(LOG_WARNING,
+                "cannot find %s driver version symbol: %s: %s",
+                typeName, driverCode, versionSymbol
+              );
+            }
+          }
+        } else {
+          logMessage(LOG_ERR,
+            "cannot find %s driver symbol: %s",
+            typeName, driverSymbol
+          );
+
+          unloadSharedObject(libraryHandle);
+          driverAddress = NULL;
+        }
+      } else {
+        logMessage(LOG_ERR,
+          "cannot load %s driver: %s",
+          typeName, libraryPath
+        );
+      }
+
+      free(libraryPath);
+    }
+  }
+#endif /* ENABLE_SHARED_OBJECTS */
+
+  return driverAddress;
+}
+
+void
+identifyDriver (
+  const char *type,
+  const DriverDefinition *definition,
+  int full
+) {
+  {
+    char buffer[0X100];
+    STR_BEGIN(buffer, sizeof(buffer));
+
+    STR_PRINTF(
+      "%s Driver: %s [%s]",
+      type, definition->code, definition->name
+    );
+
+    if (definition->version && *definition->version) {
+      STR_PRINTF(" Version:%s", definition->version);
+    }
+
+    if (full) {
+      STR_PRINTF(" (compiled on %s at %s)", definition->date, definition->time);
+    }
+
+    STR_END;
+    logMessage(LOG_NOTICE, "%s", buffer);
+  }
+
+  if (full) {
+    if (definition->developers && *definition->developers) {
+      logMessage(LOG_INFO, "   Developed by %s", definition->developers);
+    }
+  }
+}
diff --git a/Programs/dynld_dlfcn.c b/Programs/dynld_dlfcn.c
new file mode 100644
index 0000000..48cda39
--- /dev/null
+++ b/Programs/dynld_dlfcn.c
@@ -0,0 +1,116 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <dlfcn.h>
+
+#ifdef __MSDOS__
+#include <debug/syms.h>
+#include "program.h"
+#endif /* __MSDOS__ */
+
+#include "log.h"
+#include "dynld.h"
+
+static void
+clearError (void) {
+  dlerror();
+}
+
+static int
+logError (void) {
+  const char *error = dlerror();
+  if (!error) return 1;
+
+  logMessage(LOG_ERR, "%s", error);
+  return 0;
+}
+
+static inline int
+getSharedObjectLoadFlags (void) {
+  int flags = 0;
+
+#ifdef DL_LAZY
+  flags |= DL_LAZY;
+#else /* DL_LAZY */
+  flags |= RTLD_LAZY | RTLD_GLOBAL;
+#endif /* DL_LAZY */
+
+  return flags;
+}
+
+void *
+loadSharedObject (const char *path) {
+  void *object;
+
+  clearError();
+  object = dlopen(path, getSharedObjectLoadFlags());
+  if (!object) logError();
+  return object;
+}
+
+void 
+unloadSharedObject (void *object) {
+  clearError();
+  if (dlclose(object)) logError();
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, void *pointerAddress) {
+  void **address = pointerAddress;
+
+  clearError(); /* clear any previous error condition */
+  *address = dlsym(object, symbol);
+  return logError();
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+#if defined(__MSDOS__)
+  {
+    static int symsInitialized = 0;
+
+    if (!symsInitialized) {
+      syms_init((char *)programPath);
+      symsInitialized = 1;
+    }
+  }
+
+  {
+    unsigned long delta;
+    char *name = syms_val2name((unsigned long)address, &delta);
+
+    if (name) {
+      if (offset) *offset = delta;
+      return name;
+    }
+  }
+
+#elif defined(__linux__) || defined(__ANDROID__)
+  Dl_info info;
+
+  if (dladdr(address, &info)) {
+    if (offset) *offset = address - info.dli_saddr;
+    return info.dli_sname;
+  }
+
+#endif /* get symbol name */
+
+  return NULL;
+}
diff --git a/Programs/dynld_dyld.c b/Programs/dynld_dyld.c
new file mode 100644
index 0000000..52dd26a
--- /dev/null
+++ b/Programs/dynld_dyld.c
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <mach-o/dyld.h>
+
+#include "log.h"
+#include "dynld.h"
+
+static void
+logDyldError (const char *action) {
+  NSLinkEditErrors errors;
+  int number;
+  const char *file;
+  const char *message;
+  NSLinkEditError(&errors, &number, &file, &message);
+  logMessage(LOG_ERR, "%.*s", (int)(strlen(message)-1), message);
+}
+
+void *
+loadSharedObject (const char *path) {
+  NSObjectFileImage image;
+  switch (NSCreateObjectFileImageFromFile(path, &image)) {
+    case NSObjectFileImageSuccess: {
+      NSModule module = NSLinkModule(image, path, NSLINKMODULE_OPTION_RETURN_ON_ERROR);
+      if (module) return module;
+      logDyldError("link module");
+      logMessage(LOG_ERR, "shared object not linked: %s", path);
+      break;
+    }
+
+    case NSObjectFileImageInappropriateFile:
+      logMessage(LOG_ERR, "inappropriate object type: %s", path);
+      break;
+
+    case NSObjectFileImageArch:
+      logMessage(LOG_ERR, "incorrect object architecture: %s", path);
+      break;
+
+    case NSObjectFileImageFormat:
+      logMessage(LOG_ERR, "invalid object format: %s", path);
+      break;
+
+    case NSObjectFileImageAccess:
+      logMessage(LOG_ERR, "inaccessible object: %s", path);
+      break;
+
+    case NSObjectFileImageFailure:
+    default:
+      logMessage(LOG_ERR, "shared object not loaded: %s", path);
+      break;
+  }
+  return NULL;
+}
+
+void 
+unloadSharedObject (void *object) {
+  NSModule module = object;
+  NSUnLinkModule(module, NSUNLINKMODULE_OPTION_NONE);
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, void *pointerAddress) {
+  NSModule module = object;
+  char name[strlen(symbol) + 2];
+  snprintf(name, sizeof(name), "_%s", symbol);
+  {
+    NSSymbol sym = NSLookupSymbolInModule(module, name);
+    if (sym) {
+      void **address = pointerAddress;
+      *address = NSAddressOfSymbol(sym);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+  return NULL;
+}
diff --git a/Programs/dynld_grub.c b/Programs/dynld_grub.c
new file mode 100644
index 0000000..8f13a23
--- /dev/null
+++ b/Programs/dynld_grub.c
@@ -0,0 +1,48 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <grub/dl.h>
+
+#include "dynld.h"
+
+void *
+loadSharedObject (const char *name) {
+  return grub_dl_load(name);
+}
+
+void 
+unloadSharedObject (void *object) {
+  grub_dl_unload(object);
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, void *pointerAddress) {
+  void **address = pointerAddress;
+  grub_symbol_t sym = grub_get_symbol(symbol, object);
+
+  if (!sym) return 0;
+  *address = sym->addr;
+  return 1;
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+  return NULL;
+}
diff --git a/Programs/dynld_none.c b/Programs/dynld_none.c
new file mode 100644
index 0000000..184a4a4
--- /dev/null
+++ b/Programs/dynld_none.c
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "dynld.h"
+
+void *
+loadSharedObject (const char *path) {
+  return NULL;
+}
+
+void 
+unloadSharedObject (void *object) {
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, void *pointerAddress) {
+  return 0;
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+  return NULL;
+}
diff --git a/Programs/dynld_shl.c b/Programs/dynld_shl.c
new file mode 100644
index 0000000..fafaec4
--- /dev/null
+++ b/Programs/dynld_shl.c
@@ -0,0 +1,64 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#ifdef HAVE_SHL_LOAD
+#include <dl.h>
+#endif /* HAVE_SHL_LOAD */
+
+#include "log.h"
+#include "dynld.h"
+
+void *
+loadSharedObject (const char *path) {
+#ifdef HAVE_SHL_LOAD
+  shl_t object = shl_load(path, BIND_IMMEDIATE|BIND_VERBOSE|DYNAMIC_PATH, 0L);
+  if (object) return object;
+  logMessage(LOG_ERR, "Shared library '%s' not loaded: %s",
+             path, strerror(errno));
+#endif /* HAVE_SHL_LOAD */
+  return NULL;
+}
+
+void 
+unloadSharedObject (void *object) {
+#ifdef HAVE_SHL_LOAD
+  if (shl_unload(object) == -1)
+    logMessage(LOG_ERR, "Shared library unload error: %s",
+               strerror(errno));
+#endif /* HAVE_SHL_LOAD */
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, const void **address) {
+#ifdef HAVE_SHL_LOAD
+  shl_t handle = object;
+  if (shl_findsym(&handle, symbol, TYPE_UNDEFINED, address) != -1) return 1;
+  logMessage(LOG_ERR, "Shared symbol '%s' not found: %s",
+             symbol, strerror(errno));
+#endif /* HAVE_SHL_LOAD */
+  return 0;
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+  return NULL;
+}
diff --git a/Programs/dynld_windows.c b/Programs/dynld_windows.c
new file mode 100644
index 0000000..3cf3e98
--- /dev/null
+++ b/Programs/dynld_windows.c
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "dynld.h"
+
+void *
+loadSharedObject (const char *path) {
+  HMODULE library;
+  if (!(library = LoadLibrary(path)))
+    logWindowsSystemError("loading library");
+  return library;
+}
+
+void 
+unloadSharedObject (void *object) {
+  if (!(FreeLibrary((HMODULE) object)))
+    logWindowsSystemError("unloading library");
+}
+
+int 
+findSharedSymbol (void *object, const char *symbol, void *pointerAddress) {
+  void **address = pointerAddress;
+  if ((*address = GetProcAddress((HMODULE) object, symbol)))
+    return 1;
+  logWindowsSystemError("looking up symbol in library");
+  return 0;
+}
+
+const char *
+getSharedSymbolName (void *address, ptrdiff_t *offset) {
+  return NULL;
+}
diff --git a/Programs/ezusb.c b/Programs/ezusb.c
new file mode 100644
index 0000000..7496827
--- /dev/null
+++ b/Programs/ezusb.c
@@ -0,0 +1,174 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "ezusb.h"
+#include "ihex.h"
+#include "timing.h"
+#include "core.h"
+
+#define EZUSB_REQUEST_TIMEOUT 1000
+#define EZUSB_CPUCS_DELAY 10
+
+int
+ezusbWriteData (UsbDevice *device, EzusbAction action, IhexAddress address, const unsigned char *data, size_t length) {
+  ssize_t result = usbControlWrite(
+    device, EZUSB_REQUEST_RECIPIENT, EZUSB_REQUEST_TYPE,
+    action, address, EZUSB_REQUEST_INDEX,
+    data, length, EZUSB_REQUEST_TIMEOUT
+  );
+
+  if (result != -1) {
+    size_t count = result;
+    if (count == length) return 1;
+
+    logMessage(LOG_ERR,
+      "firmware write length mismatch:"
+      " Address:%04"PRIX16
+      " Expect:%"PRIsize
+      " Actual:%"PRIsize,
+      address, length, count
+    );
+  }
+
+  return 0;
+}
+
+int
+ezusbReadData (UsbDevice *device, EzusbAction action, IhexAddress address, unsigned char *buffer, size_t size) {
+  ssize_t result = usbControlRead(
+    device, EZUSB_REQUEST_RECIPIENT, EZUSB_REQUEST_TYPE,
+    action, address, EZUSB_REQUEST_INDEX,
+    buffer, size, EZUSB_REQUEST_TIMEOUT
+  );
+
+  if (result != -1) {
+    size_t count = result;
+    if (count == size) return 1;
+
+    logMessage(LOG_ERR,
+      "firmware read length mismatch:"
+      " Address:%04"PRIX16
+      " Expect:%"PRIsize
+      " Actual:%"PRIsize,
+      address, size, count
+    );
+  }
+
+  return 0;
+}
+
+int
+ezusbVerifyData (UsbDevice *device, EzusbAction action, IhexAddress address, const unsigned char *data, size_t length) {
+  unsigned char buffer[length];
+
+  {
+    int ok = ezusbReadData(
+      device, action, address, buffer, length
+    );
+
+    if (!ok) return 0;
+  }
+
+  if (memcmp(buffer, data, length) != 0) {
+    logMessage(LOG_ERR,
+      "firmware data verification mismatch:"
+      " Address:%04"PRIX16,
+      address
+    );
+
+    logBytes(LOG_DEBUG, "expect", data, length);
+    logBytes(LOG_DEBUG, "actual", buffer, length);
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+ezusbWriteCPUCS (UsbDevice *device, uint8_t state) {
+  int ok = ezusbWriteData(
+    device, EZUSB_ACTION_RW_INTERNAL,
+    EZUSB_CPUCS_ADDRESS, &state, sizeof(state)
+  );
+
+  if (ok) approximateDelay(EZUSB_CPUCS_DELAY);
+  return ok;
+}
+
+int
+ezusbStopCPU (UsbDevice *device) {
+  return ezusbWriteCPUCS(device, EZUSB_CPUCS_STOP);
+}
+
+int
+ezusbResetCPU (UsbDevice *device) {
+  return ezusbWriteCPUCS(device, EZUSB_CPUCS_RESET);
+}
+
+int
+ezusbProcessBlob (const char *name, IhexRecordHandler *handler, void *data) {
+  int ok = 0;
+  char *path = ihexMakePath(opt_driversDirectory, name);
+
+  if (path) {
+    if (ihexProcessFile(path, handler, data)) {
+      ok = 1;
+    }
+
+    free(path);
+  }
+
+  return ok;
+}
+
+typedef struct {
+  UsbDevice *device;
+  EzusbAction action;
+} EzusbRecordProcessingData;
+
+static int
+ezusbInstallData (const IhexParsedRecord *record, void *data) {
+  const EzusbRecordProcessingData *rpd = data;
+
+  UsbDevice *const device = rpd->device;
+  const EzusbAction action = rpd->action;
+
+  const IhexAddress address = record->address;
+  const IhexByte *const bytes = record->data;
+  const IhexCount count = record->count;
+
+  if (!ezusbWriteData(device, action, address, bytes, count)) return 0;
+  if (!ezusbVerifyData(device, action, address, bytes, count)) return 0;
+
+  return 1;
+}
+
+int
+ezusbInstallBlob (UsbDevice *device, const char *name, EzusbAction action) {
+  EzusbRecordProcessingData rpd = {
+    .device = device,
+    .action = action
+  };
+
+  return ezusbProcessBlob(name, ezusbInstallData, &rpd);
+}
diff --git a/Programs/file.c b/Programs/file.c
new file mode 100644
index 0000000..826fb18
--- /dev/null
+++ b/Programs/file.c
@@ -0,0 +1,1393 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <locale.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+#endif /* HAVE_ICONV_H */
+
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif /* HAVE_SYS_FILE_H */
+
+#include "parameters.h"
+#include "log.h"
+#include "strfmt.h"
+#include "file.h"
+#include "lock.h"
+#include "parse.h"
+#include "async_wait.h"
+#include "utf8.h"
+#include "program.h"
+
+
+static inline int
+allowBackslashAsPathSeparator (void) {
+#if defined(__MINGW32__) || defined(__MSDOS__)
+  return 1;
+#else /* allow backslash */
+  return 0;
+#endif /* allow backslash */
+}
+
+static int
+isDriveLetter (char character) {
+  return !!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(character));
+}
+
+int
+isPathSeparator (const char character) {
+  if (character == PATH_SEPARATOR_CHARACTER) return 1;
+
+  if (allowBackslashAsPathSeparator()) {
+    if (character == '\\') {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+isAbsolutePath (const char *path) {
+  if (isPathSeparator(path[0])) return 1;
+
+  if (allowBackslashAsPathSeparator()) {
+    if (isDriveLetter(path[0])) {
+      if (path[1] == ':') {
+        if (isPathSeparator(path[2])) {
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static size_t
+stripPathSeparator (const char *path, size_t length) {
+  while (length) {
+    if (!isPathSeparator(path[length-1])) break;
+    length -= 1;
+  }
+
+  return length;
+}
+
+char *
+getPathDirectory (const char *path) {
+  size_t length = strlen(path);
+  size_t end = stripPathSeparator(path, length);
+
+  if (end) {
+    while (--end) {
+      if (isPathSeparator(path[end-1])) {
+        break;
+      }
+    }
+
+    if ((length = end)) {
+      if ((end = stripPathSeparator(path, length))) {
+        length = end;
+      }
+    }
+  }
+
+  if (!length) length = strlen((path = CURRENT_DIRECTORY_NAME));
+  {
+    char *directory = malloc(length + 1);
+
+    if (directory) {
+      memcpy(directory, path, length);
+      directory[length] = 0;
+    } else {
+      logMallocError();
+    }
+
+    return directory;
+  }
+}
+
+const char *
+locatePathName (const char *path) {
+  const char *name = path + strlen(path);
+
+  while (name != path) {
+    if (isPathSeparator(*--name)) {
+      ++name;
+      break;
+    }
+  }
+
+  return name;
+}
+
+const char *
+locatePathExtension (const char *path) {
+  const char *name = locatePathName(path);
+  const char *extension = strrchr(name, '.');
+
+  if (extension && extension[1]) {
+    const char *c = extension;
+
+    while (c > name) {
+      if (*--c != '.') {
+        return extension;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+int
+isExplicitPath (const char *path) {
+  return locatePathName(path) != path;
+}
+
+char *
+joinPath (const char *const *components, unsigned int count) {
+  const char *const *component = components + count;
+  unsigned int size = (count * 2) - 1;
+  const char *strings[size];
+  unsigned int first = size;
+
+  while (component != components) {
+    const char *next = *--component;
+
+    if (next && *next) {
+      if ((first != size) && !isPathSeparator(next[strlen(next)-1])) {
+        strings[--first] = "/";
+      }
+
+      strings[--first] = next;
+      if (isAbsolutePath(next)) break;
+    }
+  }
+
+  return joinStrings(&strings[first], size-first);
+}
+
+char *
+makePath (const char *directory, const char *file) {
+  const char *const components[] = {directory, file};
+
+  return joinPath(components, ARRAY_COUNT(components));
+}
+
+int
+hasFileExtension (const char *path, const char *extension) {
+  const char *tail = locatePathExtension(path);
+
+  if (!tail) return 0;
+  return strcmp(tail, extension) == 0;
+}
+
+char *
+replaceFileExtension (const char *path, const char *extension) {
+  const char *oldExtension = locatePathExtension(path);
+
+  if (oldExtension) {
+    size_t headLength = oldExtension - path;
+    size_t extensionLength = strlen(extension);
+    char *newPath = malloc(headLength + extensionLength + 1);
+
+    if (newPath) {
+      char *byte = newPath;
+
+      byte = mempcpy(byte, path, headLength);
+      byte = mempcpy(byte, extension, extensionLength);
+      *byte = 0;
+
+      return newPath;
+    } else {
+      logMallocError();
+    }
+  } else {
+    logMessage(LOG_WARNING, "path has no extension: %s", path);
+  }
+
+  return NULL;
+}
+
+char *
+ensureFileExtension (const char *path, const char *extension) {
+  const char *strings[2];
+  int count = 0;
+
+  strings[count++] = path;
+  if (extension && !locatePathExtension(path)) strings[count++] = extension;
+  return joinStrings(strings, count);
+}
+
+char *
+makeFilePath (const char *directory, const char *name, const char *extension) {
+  char *path = NULL;
+  char *file = ensureFileExtension(name, extension);
+
+  if (file) {
+    if (isExplicitPath(file)) return file;
+    path = makePath(directory, file);
+    free(file);
+  }
+
+  return path;
+}
+
+int
+testPath (const char *path) {
+#ifdef F_OK
+  return access(path, F_OK) != -1;
+#else /* F_OK */
+  errno = ENOSYS;
+  return 0;
+#endif /* F_OK */
+}
+
+int
+testFilePath (const char *path) {
+#ifdef S_ISREG
+  struct stat status;
+
+  if (stat(path, &status) != -1) {
+    if (S_ISREG(status.st_mode)) return 1;
+    errno = EEXIST;
+  }
+#else /* S_ISREG */
+  int result = open(path, O_RDONLY);
+
+  if (result != -1) {
+    close(result);
+    return 1;
+  }
+#endif /* S_ISREG */
+
+  return 0;
+}
+
+int
+testProgramPath (const char *path) {
+  if (!testFilePath(path)) return 0;
+
+#if defined(__MINGW32__)
+  {
+    const char *extension = locatePathExtension(path);
+
+    if (extension) {
+      static char **extensions = NULL;
+
+      if (!extensions) {
+        const char *string = getenv("PATHEXT");
+
+        {
+          static char *noExtensions[] = {NULL};
+
+          extensions = noExtensions;
+        }
+
+        if (string) {
+          char **strings = splitString(string, ';', NULL);
+
+          if (strings) extensions = strings;
+        }
+      }
+
+      {
+        char **x = extensions;
+
+        while (*x) {
+          if (strcasecmp(*x, extension) == 0) return 1;
+          x += 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+
+#elif defined(X_OK)
+  return access(path, X_OK) != -1;
+
+#else /* X_OK */
+  errno = ENOSYS;
+  return 0;
+#endif /* X_OK */
+}
+
+int
+testDirectoryPath (const char *path) {
+#ifdef S_ISDIR
+  struct stat status;
+
+  if (stat(path, &status) != -1) {
+    if (S_ISDIR(status.st_mode)) return 1;
+    errno = EEXIST;
+  }
+#else /* S_ISDIR */
+  errno = ENOSYS;
+#endif /* S_ISDIR */
+
+  return 0;
+}
+
+static LockDescriptor *
+getUmaskLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "umask");
+}
+
+void
+lockUmask (void) {
+  obtainExclusiveLock(getUmaskLock());
+}
+
+void
+unlockUmask (void) {
+  releaseLock(getUmaskLock());
+}
+
+int
+createDirectory (const char *path, int worldWritable) {
+#if defined(GRUB_RUNTIME)
+  errno = EROFS;
+
+#else /* make directory */
+#ifdef __MINGW32__
+  if (mkdir(path) != -1) return 1;
+#else /* __MINGW32__ */
+  lockUmask();
+  int created = mkdir(path, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) != -1;
+  unlockUmask();
+
+  if (created) {
+    if (!worldWritable) return 1;
+    mode_t mode = 0;
+
+    #ifdef S_IRWXU
+    mode |= S_IRWXU;
+    #endif /* S_IRWXU */
+
+    #ifdef S_IRWXG
+    mode |= S_IRWXG;
+    #endif /* S_IRWXG */
+
+    #ifdef S_IRWXO
+    mode |= S_IRWXO;
+    #endif /* S_IRWXO */
+
+    #ifdef S_ISVTX
+    mode |= S_ISVTX;
+    #endif /* S_ISVTX */
+
+    {
+      lockUmask();
+      int changed = chmod(path, mode) != -1;
+      unlockUmask();
+      if (changed) return 1;
+    }
+
+    logMessage(LOG_WARNING,
+      "%s: %s: %s",
+      gettext("cannot make world writable"),
+      path, strerror(errno)
+    );
+
+    return 0;
+  }
+#endif /* __MINGW32__ */
+#endif /* make directory */
+
+  logMessage(LOG_WARNING,
+    "%s: %s: %s",
+    gettext("cannot create directory"),
+    path, strerror(errno)
+  );
+
+  return 0;
+}
+
+int
+ensureDirectory (const char *path, int worldWritable) {
+  if (testDirectoryPath(path)) return 1;
+
+  if (errno == EEXIST) {
+    logMessage(LOG_ERR, "not a directory: %s", path);
+  } else if (errno != ENOENT) {
+    logMessage(LOG_ERR, "cannot access directory: %s: %s", path, strerror(errno));
+  } else {
+    {
+      char *parent = getPathDirectory(path);
+      if (!parent) return 0;
+      int exists = 0;
+
+      if (strcmp(parent, path) != 0) {
+        exists = ensureDirectory(parent, 0);
+      }
+
+      free(parent);
+      if (!exists) return 0;
+    }
+
+    if (createDirectory(path, worldWritable)) {
+      logMessage(LOG_NOTICE, "directory created: %s", path);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+ensurePathDirectory (const char *path) {
+  char *directory = getPathDirectory(path);
+  if (!directory) return 0;
+
+  {
+    int exists = ensureDirectory(directory, 0);
+    free(directory);
+    return exists;
+  }
+}
+
+static void
+setDirectory (const char **variable, const char *directory) {
+  *variable = directory;
+}
+
+static const char *
+getDirectory (const char *const *variable) {
+  if (*variable && **variable) {
+    if (ensureDirectory(*variable, 0)) {
+      return *variable;
+    }
+  }
+
+  return NULL;
+}
+
+static char *
+makeDirectoryPath (const char *const *variable, const char *file) {
+  const char *directory = getDirectory(variable);
+  if (directory) return makePath(directory, file);
+  return NULL;
+}
+
+static const char *updatableDirectory = NULL;
+
+void
+setUpdatableDirectory (const char *directory) {
+  setDirectory(&updatableDirectory, directory);
+}
+
+const char *
+getUpdatableDirectory (void) {
+  return getDirectory(&updatableDirectory);
+}
+
+char *
+makeUpdatablePath (const char *file) {
+  return makeDirectoryPath(&updatableDirectory, file);
+}
+
+static const char *writableDirectory = NULL;
+
+void
+setWritableDirectory (const char *directory) {
+  setDirectory(&writableDirectory, directory);
+}
+
+const char *
+getWritableDirectory (void) {
+  return getDirectory(&writableDirectory);
+}
+
+char *
+makeWritablePath (const char *file) {
+  return makeDirectoryPath(&writableDirectory, file);
+}
+
+char *
+getWorkingDirectory (void) {
+#if defined(GRUB_RUNTIME)
+  errno = ENOSYS;
+#else /* get working directory */
+  size_t size = 0X80;
+  char *buffer = NULL;
+
+  while (1) {
+    {
+      char *newBuffer = realloc(buffer, size<<=1);
+
+      if (!newBuffer) {
+        logMallocError();
+        break;
+      }
+
+      buffer = newBuffer;
+    }
+
+    if (getcwd(buffer, size)) return buffer;
+
+    if (errno != ERANGE) {
+      logSystemError("getcwd");
+      break;
+    }
+  }
+
+  if (buffer) free(buffer);
+#endif /* get working directory */
+
+  logMessage(LOG_WARNING, "%s: %s",
+             gettext("cannot get working directory"),
+             strerror(errno));
+  return NULL;
+}
+
+int
+setWorkingDirectory (const char *path) {
+#if defined(GRUB_RUNTIME)
+  errno = ENOSYS;
+#else /* set working directory */
+  if (chdir(path) != -1) return 1;
+#endif /* set working directory */
+
+  logMessage(LOG_WARNING, "%s: %s: %s",
+             gettext("cannot set working directory"),
+             path, strerror(errno));
+  return 0;
+}
+
+char *
+getHomeDirectory (void) {
+#if defined(GRUB_RUNTIME)
+#else /* get home directory */
+  char *path = getenv("HOME");
+
+  if (path && *path) {
+    if ((path = strdup(path))) return path;
+    logMallocError();
+  }
+#endif /* get home directory */
+
+  return NULL;
+}
+
+static char *
+makeOverridePath (const char *base, int xdg) {
+  return makePath(base, (xdg? PACKAGE_TARNAME: (CURRENT_DIRECTORY_NAME PACKAGE_TARNAME)));
+}
+
+static int
+addOverridePath (char **paths, size_t *index, const char *base, int xdg) {
+  char *path = makeOverridePath(base, xdg);
+  if (!path) return 0;
+
+  logMessage(LOG_DEBUG, "override directory: %s", path);
+  paths[(*index)++] = path;
+  return 1;
+}
+
+static char **overrideDirectories = NULL;
+
+const char *const *
+getAllOverrideDirectories (void) {
+  if (!overrideDirectories) {
+    logMessage(LOG_DEBUG, "determining override directories");
+
+    const char *secondaryList = getenv("XDG_CONFIG_DIRS");
+    int secondaryCount;
+    char **secondaryBases = splitString(((secondaryList && *secondaryList)? secondaryList: "/etc/xdg"), ':', &secondaryCount);
+
+    if (secondaryBases) {
+      size_t count = 1 + secondaryCount + 1;
+      char **paths;
+
+      if ((paths = malloc(sizeof(*paths) * (count + 1)))) {
+        size_t index = 0;
+
+        {
+          const char *primary = getenv("XDG_CONFIG_HOME");
+
+          if (primary && *primary) {
+            if (!addOverridePath(paths, &index, primary, 1)) goto done;
+          } else {
+            char *home = getHomeDirectory();
+
+            if (home) {
+              char *base = *home? makePath(home, ".config"): NULL;
+
+              free(home);
+              home = NULL;
+
+              if (base) {
+                int added = addOverridePath(paths, &index, base, 1);
+
+                free(base);
+                base = NULL;
+
+                if (!added) goto done;
+              }
+            }
+          }
+        }
+
+        if (!index) {
+          char *primary = strdup("");
+
+          if (!primary) {
+            logMallocError();
+            goto done;
+          }
+
+          paths[index++] = primary;
+        }
+
+        {
+          char **base = secondaryBases;
+
+          while (*base) {
+            if (**base) {
+              if (!addOverridePath(paths, &index, *base, 1)) {
+                break;
+              }
+            } else {
+              count -= 1;
+            }
+
+            base += 1;
+          }
+
+          if (*base) goto done;
+        }
+
+        {
+          int added = 0;
+          char *home = getHomeDirectory();
+
+          if (home && *home) {
+            if (addOverridePath(paths, &index, home, 0)) added = 1;
+          } else {
+            char *current = getWorkingDirectory();
+
+            if (current) {
+              if (addOverridePath(paths, &index, current, 0)) added = 1;
+              free(current);
+            }
+          }
+
+          if (home) free(home);
+          if (!added) goto done;
+        }
+
+done:
+        paths[index] = NULL;
+
+        if (index == count) {
+          overrideDirectories = paths;
+        } else {
+          deallocateStrings(paths);
+        }
+      } else {
+        logMallocError();
+      }
+
+      deallocateStrings(secondaryBases);
+    }
+
+    if (!overrideDirectories) logMessage(LOG_WARNING, "no override directories");
+  }
+
+  return (const char *const *)overrideDirectories;
+}
+
+const char *
+getPrimaryOverrideDirectory (void) {
+  const char *const *directories = getAllOverrideDirectories();
+
+  if (directories) {
+    const char *directory = directories[0];
+
+    if (directory && *directory) return directory;
+  }
+
+  logMessage(LOG_WARNING, "no primary override directory");
+  return NULL;
+}
+
+void
+forgetOverrideDirectories (void) {
+  if (overrideDirectories) {
+    logMessage(LOG_DEBUG, "forgetting override directories");
+    deallocateStrings(overrideDirectories);
+    overrideDirectories = NULL;
+  }
+}
+
+#if defined(F_SETLK)
+static int
+modifyFileLock (int file, int action, short type) {
+  struct flock lock;
+
+  memset(&lock, 0, sizeof(lock));
+  lock.l_type = type;
+  lock.l_whence = SEEK_SET;
+  lock.l_start = 0;
+  lock.l_len = 0;
+
+  do {
+    if (fcntl(file, action, &lock) != -1) return 1;
+  } while (errno == EINTR);
+
+  if (errno == EACCES) errno = EAGAIN;
+  if (errno != EAGAIN) logSystemError("fcntl[struct flock *]");
+  return 0;
+}
+
+static int
+lockFile (int file, int exclusive, int wait) {
+  return modifyFileLock(file, (wait? F_SETLKW: F_SETLK), (exclusive? F_WRLCK: F_RDLCK));
+}
+
+int
+acquireFileLock (int file, int exclusive) {
+  return lockFile(file, exclusive, 1);
+}
+
+int
+attemptFileLock (int file, int exclusive) {
+  return lockFile(file, exclusive, 0);
+}
+
+int
+releaseFileLock (int file) {
+  return modifyFileLock(file, F_SETLK, F_UNLCK);
+}
+
+#elif defined(LOCK_EX)
+static int
+modifyFileLock (int file, int operation) {
+  do {
+    if (flock(file, operation) != -1) return 1;
+  } while (errno == EINTR);
+
+#ifdef EWOULDBLOCK
+  if (errno == EWOULDBLOCK) errno = EAGAIN;
+#endif /* EWOULDBLOCK */
+
+  if (errno == EACCES) errno = EAGAIN;
+  if (errno != EAGAIN) logSystemError("flock");
+  return 0;
+}
+
+int
+acquireFileLock (int file, int exclusive) {
+  return modifyFileLock(file, (exclusive? LOCK_EX: LOCK_SH));
+}
+
+int
+attemptFileLock (int file, int exclusive) {
+  return modifyFileLock(file, ((exclusive? LOCK_EX: LOCK_SH) | LOCK_NB));
+}
+
+int
+releaseFileLock (int file) {
+  return modifyFileLock(file, LOCK_UN);
+}
+
+#elif defined(F_LOCK)
+static int
+modifyRegionLock (int file, int command, off_t length) {
+  do {
+    if (lockf(file, command, length) != -1) return 1;
+  } while (errno == EINTR);
+
+  if (errno == EACCES) errno = EAGAIN;
+  if (errno != EAGAIN) logSystemError("lockf");
+  return 0;
+}
+
+static int
+modifyFileLock (int file, int command) {
+  off_t offset;
+
+  if ((offset = lseek(file, 0, SEEK_CUR)) == -1) {
+    logSystemError("lseek");
+  } else if (modifyRegionLock(file, command, 0)) {
+    if (!offset) return 1;
+    if (modifyRegionLock(file, command, -offset)) return 1;
+  }
+
+  return 0;
+}
+
+int
+acquireFileLock (int file, int exclusive) {
+  return modifyFileLock(file, F_LOCK);
+}
+
+int
+attemptFileLock (int file, int exclusive) {
+  return modifyFileLock(file, F_TLOCK);
+}
+
+int
+releaseFileLock (int file) {
+  return modifyFileLock(file, F_ULOCK);
+}
+
+#elif defined(__MINGW32__)
+#include <io.h>
+#include <sys/locking.h>
+#include <limits.h>
+
+static int
+modifyFileLock (int file, int mode) {
+  int ok = 0;
+  off_t offset;
+
+  if ((offset = lseek(file, 0, SEEK_CUR)) != -1) {
+    if (lseek(file, 0, SEEK_SET) != -1) {
+      int wait;
+
+      if (mode == _LK_LOCK) {
+        mode = _LK_NBLCK;
+        wait = 1;
+      } else if (mode == _LK_RLCK) {
+        mode = _LK_NBRLCK;
+        wait = 1;
+      } else {
+        wait = 0;
+      }
+
+      while (1) {
+        if (_locking(file, mode, LONG_MAX) != -1) {
+          ok = 1;
+          break;
+        }
+
+        if (errno != EACCES) {
+          logSystemError("_locking");
+          break;
+        }
+
+        if (!wait) break;
+        asyncWait(WINDOWS_FILE_LOCK_RETRY_INTERVAL);
+      }
+
+      if (lseek(file, offset, SEEK_SET) == -1) {
+        logSystemError("lseek");
+        ok = 0;
+      }
+    } else {
+      logSystemError("lseek");
+    }
+  } else {
+    logSystemError("lseek");
+  }
+
+  return ok;
+}
+
+int
+acquireFileLock (int file, int exclusive) {
+  return modifyFileLock(file, (exclusive? _LK_LOCK: _LK_RLCK));
+}
+
+int
+attemptFileLock (int file, int exclusive) {
+  return modifyFileLock(file, (exclusive? _LK_NBLCK: _LK_NBRLCK));
+}
+
+int
+releaseFileLock (int file) {
+  return modifyFileLock(file, _LK_UNLCK);
+}
+
+#else /* file locking */
+#warning file lock support not available on this platform
+
+int
+acquireFileLock (int file, int exclusive) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+attemptFileLock (int file, int exclusive) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+releaseFileLock (int file) {
+  logUnsupportedFunction();
+  return 0;
+}
+#endif /* file locking */
+
+static void
+exitProgramStream (void *data) {
+  FILE **stream = data;
+
+  if (*stream) {
+    fclose(*stream);
+    *stream = NULL;
+  }
+}
+
+void
+registerProgramStream (const char *name, FILE **stream) {
+  onProgramExit(name, exitProgramStream, stream);
+}
+
+FILE *
+openFile (const char *path, const char *mode, int optional) {
+  FILE *file = fopen(path, mode);
+
+  if (file) {
+    logMessage(LOG_DEBUG, "file opened: %s fd=%d", path, fileno(file));
+  } else {
+    logMessage((optional && (errno == ENOENT))? LOG_DEBUG: LOG_ERR,
+               "cannot open file: %s: %s", path, strerror(errno));
+  }
+
+  return file;
+}
+
+int
+readLine (FILE *file, char **buffer, size_t *size, size_t *length) {
+  char *line;
+
+  if (ferror(file)) return 0;
+  if (feof(file)) return 0;
+
+  if (!*size) {
+    if (!(*buffer = malloc((*size = 0X80)))) {
+      logMallocError();
+      return 0;
+    }
+  }
+
+  if ((line = fgets(*buffer, *size, file))) {
+    size_t count = strlen(line); /* Line length including new-line. */
+
+    /* No trailing new-line means that the buffer isn't big enough. */
+    while (line[count-1] != '\n') {
+      /* If necessary, extend the buffer. */
+      if ((*size - (count + 1)) == 0) {
+        size_t newSize = *size << 1;
+        char *newBuffer = realloc(*buffer, newSize);
+
+        if (!newBuffer) {
+          logMallocError();
+          return 0;
+        }
+
+        *buffer = newBuffer;
+        *size = newSize;
+      }
+
+      /* Read the rest of the line into the end of the buffer. */
+      if (!(line = fgets(&(*buffer)[count], (*size -count), file))) {
+        if (!ferror(file)) goto done;
+        logSystemError("fgets");
+        return 0;
+      }
+
+      count += strlen(line); /* New total line length. */
+      line = *buffer; /* Point to the beginning of the line. */
+    }
+
+    if (--count > 0) {
+      if (line[count-1] == '\r') {
+        count -= 1;
+      }
+    }
+
+    line[count] = 0; /* Remove trailing new-line. */
+  done:
+    if (length) *length = count;
+    return 1;
+  } else if (ferror(file)) {
+    logSystemError("fgets");
+  }
+
+  return 0;
+}
+
+/* Process each line of an input text file safely.
+ * This routine handles the actual reading of the file,
+ * insuring that the input buffer is always big enough,
+ * and calls a caller-supplied handler once for each line in the file.
+ * The caller-supplied data pointer is passed straight through to the handler.
+ */
+int
+processLines (FILE *file, LineHandler handleLine, void *data) {
+  char *buffer = NULL;
+  size_t bufferSize = 0;
+
+  LineHandlerParameters parameters = {
+    .data = data,
+
+    .line = {
+      .number = 0,
+    },
+  };
+
+  while (1) {
+    parameters.line.number += 1;
+    if (!readLine(file, &buffer, &bufferSize, &parameters.line.length)) break;
+    parameters.line.text = buffer;
+    if (!handleLine(&parameters)) break;
+  }
+
+  if (buffer) free(buffer);
+  return !ferror(file);
+}
+
+STR_BEGIN_FORMATTER(formatInputError, const char *file, const int *line, const char *format, va_list arguments)
+  if (file) STR_PRINTF("%s", file);
+  if (line) STR_PRINTF("[%d]", *line);
+  if (STR_LENGTH) STR_PRINTF(": ");
+  STR_VPRINTF(format, arguments);
+STR_END_FORMATTER
+
+static void
+detachStandardStream (FILE *stream, const char *name, int output) {
+  const char *nullDevice = "/dev/null";
+
+  if (output) {
+    fflush(stream);
+  }
+
+  if (!freopen(nullDevice, (output? "a": "r"), stream)) {
+    if (errno != ENOENT) {
+      char action[0X40];
+      snprintf(action, sizeof(action), "freopen[%s]", name);
+      logSystemError(action);
+    }
+  }
+}
+
+void
+detachStandardInput (void) {
+  detachStandardStream(stdin, "stdin", 0);
+}
+
+void
+detachStandardOutput (void) {
+  detachStandardStream(stdout, "stdout", 1);
+}
+
+void
+detachStandardError (void) {
+  detachStandardStream(stderr, "stderr", 1);
+}
+
+void
+detachStandardStreams (void) {
+  detachStandardInput();
+  detachStandardOutput();
+  detachStandardError();
+}
+
+#ifdef __MINGW32__
+int
+getConsoleSize (size_t *width, size_t *height) {
+  HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
+
+  if (handle) {
+    if (handle != INVALID_HANDLE_VALUE) {
+      if (GetFileType(handle) == FILE_TYPE_CHAR) {
+        CONSOLE_SCREEN_BUFFER_INFO info;
+
+        if (GetConsoleScreenBufferInfo(handle, &info)) {
+          COORD size = info.dwSize;
+          if (width) *width = size.X;
+          if (height) *height = size.Y;
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+const char *
+getConsoleEncoding (void) {
+  static char encoding[0X10];
+
+  if (!encoding[0]) {
+    unsigned cp = GetConsoleOutputCP();
+
+    if (cp == CP_UTF8) {
+      strcpy(encoding, "UTF-8");
+    } else {
+      snprintf(encoding, sizeof(encoding), "CP%u", cp);
+    }
+
+    logMessage(LOG_DEBUG, "Console Encoding: %s", encoding);
+  }
+
+  return encoding;
+}
+
+ssize_t
+readFileDescriptor (FileDescriptor fileDescriptor, void *buffer, size_t size) {
+  {
+    DWORD count;
+
+    if (ReadFile(fileDescriptor, buffer, size, &count, NULL)) return count;
+  }
+
+  setSystemErrno();
+  return -1;
+}
+
+ssize_t
+writeFileDescriptor (FileDescriptor fileDescriptor, const void *buffer, size_t size) {
+  {
+    DWORD count;
+
+    if (WriteFile(fileDescriptor, buffer, size, &count, NULL)) return count;
+  }
+
+  setSystemErrno();
+  return -1;
+}
+
+const char *
+getNamedPipeDirectory (void) {
+  return "//./pipe";
+}
+
+int
+createAnonymousPipe (FileDescriptor *pipeInput, FileDescriptor *pipeOutput) {
+  SECURITY_ATTRIBUTES attributes;
+
+  ZeroMemory(&attributes, sizeof(attributes));
+  attributes.nLength = sizeof(attributes);
+  attributes.bInheritHandle = TRUE;
+  attributes.lpSecurityDescriptor = NULL;
+
+  if (CreatePipe(pipeOutput, pipeInput, &attributes, 0)) {
+    return 1;
+  } else {
+    logWindowsSystemError("CreatePipe");
+  }
+
+  return 0;
+}
+
+#else /* unix file/socket descriptor operations */
+#include <sys/ioctl.h>
+
+int
+getConsoleSize (size_t *width, size_t *height) {
+  struct winsize size;
+  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1) return 0;
+
+  if (width) *width = size.ws_col;
+  if (height) *height = size.ws_row;
+  return 1;
+}
+
+const char *
+getConsoleEncoding (void) {
+  static const char *encoding = NULL;
+
+  if (!encoding) {
+    setlocale(LC_ALL, "");
+
+#ifdef HAVE_NL_LANGINFO
+    encoding = nl_langinfo(CODESET);
+#endif /* HAVE_NL_LANGINFO */
+
+    if (encoding) {
+      if (!(encoding = strdup(encoding))) {
+        logMallocError();
+      }
+    }
+
+    if (!encoding) encoding = "";
+    logMessage(LOG_DEBUG, "Console Encoding: %s", encoding);
+  }
+
+  return encoding;
+}
+
+ssize_t
+readFileDescriptor (FileDescriptor fileDescriptor, void *buffer, size_t size) {
+  return read(fileDescriptor, buffer, size);
+}
+
+ssize_t
+writeFileDescriptor (FileDescriptor fileDescriptor, const void *buffer, size_t size) {
+  return write(fileDescriptor, buffer, size);
+}
+
+const char *
+getNamedPipeDirectory (void) {
+  return getWritableDirectory();
+}
+
+int
+createAnonymousPipe (FileDescriptor *pipeInput, FileDescriptor *pipeOutput) {
+  int fileDescriptors[2];
+
+  if (pipe(fileDescriptors) != -1) {
+    *pipeInput = fileDescriptors[1];
+    *pipeOutput = fileDescriptors[0];
+    return 1;
+  } else {
+    logSystemError("pipe");
+  }
+
+  return 0;
+}
+#endif /* basic file/socket descriptor operations */
+
+void
+writeWithConsoleEncoding (FILE *stream, const char *bytes, size_t count) {
+  static const char *consoleEncoding = NULL;
+  if (!consoleEncoding) consoleEncoding = getConsoleEncoding();
+
+  if (!consoleEncoding || isCharsetUTF8(consoleEncoding)) {
+    consoleEncoding = "";
+  }
+
+#ifdef HAVE_ICONV_H
+  if (*consoleEncoding) {
+    static iconv_t iconvHandle = (iconv_t)-1;
+
+    if (iconvHandle == (iconv_t)-1) {
+      static const char internalEncoding[] = "UTF-8";
+      const char *externalEncoding = consoleEncoding;
+
+      if ((iconvHandle = iconv_open(externalEncoding, internalEncoding)) == (iconv_t)-1) {
+        consoleEncoding = "";
+
+        logMessage(LOG_WARNING,
+          "iconv open error: %s -> %s: %s",
+          internalEncoding, externalEncoding, strerror(errno)
+        );
+
+        goto ENCODING_NOT_SUPPORTED;
+      }
+    }
+
+    const char *inputNext = bytes;
+    size_t inputLeft = count;
+
+    char outputBuffer[inputLeft * MB_LEN_MAX];
+    char *outputNext = outputBuffer;
+    size_t outputLeft = sizeof(outputBuffer);
+
+    ssize_t result = iconv(
+      iconvHandle,
+      (char **)&inputNext, &inputLeft,
+      &outputNext, &outputLeft
+    );
+
+    if (result != -1) {
+      size_t length = outputNext - outputBuffer;
+      outputBuffer[length] = 0;
+      fputs(outputBuffer, stream);
+    }
+
+    return;
+  }
+ENCODING_NOT_SUPPORTED:
+#endif /* HAVE_ICONV_H */
+
+  fwrite(bytes, 1, count, stream);
+}
+
+#ifdef GOT_SOCKETS
+ssize_t
+readSocketDescriptor (SocketDescriptor socketDescriptor, void *buffer, size_t size) {
+  return recv(socketDescriptor, buffer, size, 0);
+}
+
+ssize_t
+writeSocketDescriptor (SocketDescriptor socketDescriptor, const void *buffer, size_t size) {
+  return send(socketDescriptor, buffer, size, 0);
+}
+#endif /* GOT_SOCKETS */
+
+char *
+readSymbolicLink (const char *path) {
+  char *content = NULL;
+  size_t size = 0X80;
+  char *buffer = NULL;
+
+  while (1) {
+    {
+      char *newBuffer = realloc(buffer, size<<=1);
+
+      if (!newBuffer) {
+        logMallocError();
+        break;
+      }
+
+      buffer = newBuffer;
+    }
+
+    {
+      int length;
+
+      #ifdef HAVE_READLINK
+      length = readlink(path, buffer, size);
+      #else /* HAVE_READLINK */
+      length = -1;
+      errno = ENOSYS;
+      #endif /* HAVE_READLINK */
+
+      if (length == -1) {
+        if (errno != ENOENT) logSystemError("readlink");
+        break;
+      }
+
+      if (length < size) {
+        buffer[length] = 0;
+        if (!(content = strdup(buffer))) logMallocError();
+        break;
+      }
+    }
+  }
+
+  if (buffer) free(buffer);
+  return content;
+}
diff --git a/Programs/fm_adlib.c b/Programs/fm_adlib.c
new file mode 100644
index 0000000..20c428b
--- /dev/null
+++ b/Programs/fm_adlib.c
@@ -0,0 +1,194 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/*
+ * Miscellaneous FM chip soundcard routines for BRLTTY.
+ * Implemented by Dave Mielke <dave@mielke.cc>.
+ * Method gleaned from sccw, a morse code program written
+ * by Steven J. Merrifield <sjm@ee.latrobe.edu.au> (VK3ESM).
+ * Must compile with -O2.
+ * Must link with -lm.
+ * May compile with -DDEBUG_ADLIB.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "async_wait.h"
+#include "timing.h"
+#include "ports.h"
+#include "fm.h"
+#include "fm_adlib.h"
+
+const unsigned char AL_channelOffsets[] = {
+  /* 1     2     3     4     5     6     7     8     9 */
+  0X00, 0X01, 0X02, 0X08, 0X09, 0X0A, 0X10, 0X11, 0X12
+};
+const unsigned char AL_channelCount = ARRAY_COUNT(AL_channelOffsets);
+
+static unsigned int portsEnabledCount = 0;
+
+int
+fmEnablePorts (int errorLevel) {
+  if (portsEnabledCount) return 1;
+
+  if (enablePorts(errorLevel, ALP_REGISTER, 1)) {
+    if (enablePorts(errorLevel, ALP_DATA, 1)) {
+      portsEnabledCount++;
+      return 1;
+    }
+
+    disablePorts(ALP_REGISTER, 1);
+  }
+
+  return 0;
+}
+
+void
+fmDisablePorts (void) {
+  if (!--portsEnabledCount) {
+    disablePorts(ALP_REGISTER, 1);
+    disablePorts(ALP_DATA, 1);
+  }
+}
+
+unsigned char
+AL_readStatus (void) {
+  return readPort1(ALP_STATUS);
+}
+
+static void
+AL_writeDelay (int delay) {
+  while (delay-- > 0) {
+    AL_readStatus();
+  }
+}
+
+void
+AL_writeRegister (int number, unsigned char data) {
+  /* logMessage(LOG_DEBUG, "AL_writeRegister: %2.2X=%2.2X", number, data); */
+  writePort1(ALP_REGISTER, number);
+  AL_writeDelay(6);
+  writePort1(ALP_DATA, data);
+  AL_writeDelay(35);
+}
+
+void
+fmResetCard (void) {
+  int number;
+  for (number=ALR_FIRST; number<=ALR_LAST; ++number) {
+    AL_writeRegister(number, 0);
+  }
+}
+
+static void
+AL_resetTimers (void) {
+  AL_writeRegister(ALR_TCTL, AL_TCTL_T1MASK|AL_TCTL_T2MASK);
+  AL_writeRegister(ALR_TCTL, AL_TCTL_RESET);
+}
+
+int
+fmTestCard (int errorLevel) {
+  const unsigned char mask = AL_STAT_EXP | AL_STAT_EXP1 | AL_STAT_EXP2;
+
+  AL_resetTimers();
+  if (!(AL_readStatus() & mask)) {
+    unsigned char status;
+
+    AL_writeRegister(ALR_T1DATA, 0xFF);
+    AL_writeRegister(ALR_TCTL, AL_TCTL_T1START|AL_TCTL_T2MASK);
+
+    {
+      const TimeValue duration = {
+        .seconds = 0,
+        .nanoseconds = 80 * NSECS_PER_USEC
+      };
+
+      accurateDelay(&duration);
+    }
+
+    status = AL_readStatus();
+    AL_resetTimers(); 
+
+    if ((status & mask) == (AL_STAT_EXP | AL_STAT_EXP1)) return 1; 
+  }
+
+  logMessage(errorLevel, "FM synthesizer initialization failure");
+  return 0;
+}
+
+static void
+AL_evaluatePitch (int pitch, int *exponent, int *mantissa) {
+  int shift = 21;
+  while ((*mantissa = (int)((float)pitch * (1 << --shift) / 50000.0)) > 0X3FF);
+  *exponent = 20 - shift;
+}
+
+static void
+AL_initiateTone (int channel, int exponent, int mantissa) {
+  /* logMessage(LOG_DEBUG, "AL_initiateTone: %1.1X[%3.3X]", exponent, mantissa); */
+  AL_writeRegister(ALR_FREQUENCY_LSB(channel),
+                   (mantissa & 0XFF));
+  AL_writeRegister(ALR_FREQUENCY_MSB(channel),
+                   (((mantissa >> 8) & 0X3) |
+                    ((exponent & 0X7) << AL_OCTAVE_SHIFT) |
+                    AL_FREQ_ON));
+}
+
+void
+fmStartTone (int channel, int pitch) {
+  int exponent;
+  int mantissa;
+  AL_evaluatePitch(pitch, &exponent, &mantissa);
+  /* logMessage(LOG_DEBUG, "fmStartTone: %d", pitch); */
+  AL_initiateTone(channel, exponent, mantissa);
+}
+
+void
+fmStopTone (int channel) {
+  AL_writeRegister(ALR_FREQUENCY_MSB(channel), 0);
+}
+
+void
+fmPlayTone (int channel, unsigned int pitch, unsigned long int duration, unsigned int volume) {
+  /* Play tone at fundamental frequency. */
+  AL_writeRegister(ALR_MODULATOR(ALG_EFFECT, channel),
+                   (AL_HARMONIC_1 << AL_HARMONIC_SHIFT));
+
+  /* Set the carrier to the fundamental frequency. */
+  AL_writeRegister(ALR_CARRIER(ALG_EFFECT, channel),
+                   (AL_HARMONIC_1 << AL_HARMONIC_SHIFT));
+
+  /* Set the volume (passed in as 0-100) */
+  AL_writeRegister(ALR_CARRIER(ALG_LEVEL, channel),
+                   ((AL_VOLUME_SOFT - ((AL_VOLUME_SOFT * volume) / 100)) << AL_VOLUME_SHIFT));
+
+  /* Set fast attack and slow decay. */
+  AL_writeRegister(ALR_CARRIER(ALG_ATTDEC, channel),
+                   ((AL_ATTACK_FAST << AL_ATTACK_SHIFT) |
+                    (AL_DECAY_SLOW << AL_DECAY_SHIFT)));
+
+  /* Set soft sustain and fast release. */
+  AL_writeRegister(ALR_CARRIER(ALG_SUSREL, channel),
+                   ((AL_SUSTAIN_SOFT << AL_SUSTAIN_SHIFT) |
+                    (AL_RELEASE_FAST << AL_RELEASE_SHIFT)));
+      
+  fmStartTone(channel, pitch);
+  asyncWait(duration);
+  fmStopTone(channel);
+}
diff --git a/Programs/fm_none.c b/Programs/fm_none.c
new file mode 100644
index 0000000..c42f08f
--- /dev/null
+++ b/Programs/fm_none.c
@@ -0,0 +1,51 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "fm.h"
+
+int
+fmEnablePorts (int errorLevel) {
+  return 0;
+}
+
+void
+fmDisablePorts (void) {
+}
+
+void
+fmResetCard (void) {
+}
+
+int
+fmTestCard (int errorLevel) {
+  return 0;
+}
+
+void
+fmStartTone (int channel, int pitch) {
+}
+
+void
+fmStopTone (int channel) {
+}
+
+void
+fmPlayTone (int channel, unsigned int pitch, unsigned long int duration, unsigned int volume) {
+}
diff --git a/Programs/gio.c b/Programs/gio.c
new file mode 100644
index 0000000..9d54741
--- /dev/null
+++ b/Programs/gio.c
@@ -0,0 +1,747 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_alarm.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "io_serial.h"
+#include "hid_types.h"
+
+const GioProperties *const gioProperties[] = {
+  &gioProperties_serial,
+  &gioProperties_usb,
+  &gioProperties_bluetooth,
+  &gioProperties_hid,
+  &gioProperties_null,
+  NULL
+};
+
+static void
+gioInitializeOptions (GioOptions *options) {
+  options->applicationData = NULL;
+  options->readyDelay = 0;
+  options->inputTimeout = 0;
+  options->outputTimeout = 0;
+  options->requestTimeout = 0;
+}
+
+void
+gioInitializeDescriptor (GioDescriptor *descriptor) {
+  descriptor->serial.parameters = NULL;
+  gioInitializeOptions(&descriptor->serial.options);
+  descriptor->serial.options.inputTimeout = 100;
+
+  descriptor->usb.channelDefinitions = NULL;
+  descriptor->usb.setConnectionProperties = NULL;
+  gioInitializeOptions(&descriptor->usb.options);
+  descriptor->usb.options.inputTimeout = 1000;
+  descriptor->usb.options.outputTimeout = 1000;
+  descriptor->usb.options.requestTimeout = 1000;
+
+  descriptor->bluetooth.channelNumber = 0;
+  descriptor->bluetooth.discoverChannel = 0;
+  gioInitializeOptions(&descriptor->bluetooth.options);
+  descriptor->bluetooth.options.inputTimeout = 1000;
+  descriptor->bluetooth.options.requestTimeout = 5000;
+
+  descriptor->hid.modelTable = NULL;
+  gioInitializeOptions(&descriptor->hid.options);
+
+  gioInitializeOptions(&descriptor->null.options);
+}
+
+void
+gioInitializeSerialParameters (SerialParameters *parameters) {
+  parameters->baud = SERIAL_DEFAULT_BAUD;
+  parameters->dataBits = SERIAL_DEFAULT_DATA_BITS;
+  parameters->stopBits = SERIAL_DEFAULT_STOP_BITS;
+  parameters->parity = SERIAL_DEFAULT_PARITY;
+  parameters->flowControl = SERIAL_DEFAULT_FLOW_CONTROL;
+}
+
+void
+gioSetBytesPerSecond (GioEndpoint *endpoint, const SerialParameters *parameters) {
+  endpoint->bytesPerSecond = parameters->baud / serialGetCharacterSize(parameters);
+}
+
+void
+gioSetApplicationData (GioEndpoint *endpoint, const void *data) {
+  endpoint->options.applicationData = data;
+}
+
+static int
+gioStartEndpoint (GioEndpoint *endpoint) {
+  {
+    int delay = endpoint->options.readyDelay;
+    if (delay) asyncWait(delay);
+  }
+
+  return 1;
+}
+
+static const GioProperties *
+gioGetProperties (
+  const char **identifier,
+  const GioDescriptor *descriptor
+) {
+  for (const GioProperties *const *properties = gioProperties;
+       *properties; properties+=1) {
+    if (descriptor) {
+      GioIsSupportedMethod *isSupported = (*properties)->private->isSupported;
+      if (!isSupported) continue;
+      if (!isSupported(descriptor)) continue;
+    }
+
+    {
+      GioTestIdentifierMethod *testIdentifier = (*properties)->public->testIdentifier;
+      if (!testIdentifier) continue;
+      if (testIdentifier(identifier)) return *properties;
+    }
+  }
+
+  errno = ENOSYS;
+  logMessage(LOG_WARNING, "unsupported generic resource identifier: %s", *identifier);
+  return NULL;
+}
+
+const GioPublicProperties *
+gioGetPublicProperties (const char **identifier) {
+  const GioProperties *properties = gioGetProperties(identifier, NULL);
+  if (properties == NULL) return NULL;
+  return properties->public;
+}
+
+GioEndpoint *
+gioConnectResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  const GioProperties *properties = gioGetProperties(&identifier, descriptor);
+
+  if (properties) {
+    GioEndpoint *endpoint;
+
+    if ((endpoint = malloc(sizeof(*endpoint)))) {
+      memset(endpoint, 0, sizeof(*endpoint));
+      endpoint->referenceCount = 1;
+
+      endpoint->resourceType = properties->public->type.identifier;
+      endpoint->bytesPerSecond = 0;
+
+      endpoint->input.error = 0;
+      endpoint->input.from = 0;
+      endpoint->input.to = 0;
+
+      if (descriptor && properties->private->getOptions) {
+        endpoint->options = *properties->private->getOptions(descriptor);
+      } else {
+        gioInitializeOptions(&endpoint->options);
+      }
+
+      if (properties->private->getHandleMethods) {
+        endpoint->handleMethods = properties->private->getHandleMethods();
+      } else {
+        endpoint->handleMethods = NULL;
+      }
+
+      if (properties->private->connectResource) {
+        if ((endpoint->handle = properties->private->connectResource(identifier, descriptor))) {
+          {
+            GioGetChainedEndpointMethod *getChainedEndpoint = endpoint->handleMethods->getChainedEndpoint;
+
+            if (getChainedEndpoint) {
+              GioEndpoint *chainedEndpoint = getChainedEndpoint(endpoint->handle);
+
+              if (chainedEndpoint) {
+                chainedEndpoint->referenceCount += 1;
+                gioDisconnectResource(endpoint);
+                return chainedEndpoint;
+              }
+            }
+          }
+
+          if (!properties->private->prepareEndpoint || properties->private->prepareEndpoint(endpoint)) {
+            if (gioStartEndpoint(endpoint)) {
+              return endpoint;
+            }
+          }
+
+          {
+            int originalErrno = errno;
+            gioDisconnectResource(endpoint);
+            errno = originalErrno;
+          }
+
+          return NULL;
+        }
+      } else {
+        logUnsupportedOperation("connectResource");
+      }
+
+      free(endpoint);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+const void *
+gioGetApplicationData (GioEndpoint *endpoint) {
+  return endpoint->options.applicationData;
+}
+
+int
+gioDisconnectResource (GioEndpoint *endpoint) {
+  if (--endpoint->referenceCount > 0) return 1;
+
+  int ok = 0;
+  GioDisconnectResourceMethod *method = endpoint->handleMethods->disconnectResource;
+
+  if (!method) {
+    logUnsupportedOperation("disconnectResource");
+    errno = ENOSYS;
+  } else if (method(endpoint->handle)) {
+    ok = 1;
+  }
+
+  free(endpoint);
+  return ok;
+}
+
+const char *
+gioMakeResourceIdentifier (GioEndpoint *endpoint, char *buffer, size_t size) {
+  const char *identifier = NULL;
+  MakeResourceIdentifierMethod *method = endpoint->handleMethods->makeResourceIdentifier;
+
+  if (!method) {
+    logUnsupportedOperation("makeResourceIdentifier");
+    errno = ENOSYS;
+  } else {
+    identifier = method(endpoint->handle, buffer, size);
+  }
+
+  return identifier;
+}
+
+char *
+gioGetResourceIdentifier (GioEndpoint *endpoint) {
+  char buffer[0X100];
+  const char *identifier = gioMakeResourceIdentifier(endpoint, buffer, sizeof(buffer));
+  if (!identifier) return NULL;
+
+  char *copy = strdup(identifier);
+  if (!copy) logMallocError();
+  return copy;
+}
+
+char *
+gioGetResourceName (GioEndpoint *endpoint) {
+  char *name = NULL;
+  GioGetResourceNameMethod *method = endpoint->handleMethods->getResourceName;
+
+  if (!method) {
+    logUnsupportedOperation("getResourceName");
+    errno = ENOSYS;
+  } else {
+    name = method(endpoint->handle, endpoint->options.requestTimeout);
+  }
+
+  return name;
+}
+
+GioTypeIdentifier
+gioGetResourceType (GioEndpoint *endpoint) {
+  return endpoint->resourceType;
+}
+
+void *
+gioGetResourceObject (GioEndpoint *endpoint) {
+  GioGetResourceObjectMethod *method = endpoint->handleMethods->getResourceObject;
+
+  if (method) return method(endpoint->handle);
+  logUnsupportedOperation("getResourceObject");
+  return NULL;
+}
+
+ssize_t
+gioWriteData (GioEndpoint *endpoint, const void *data, size_t size) {
+  GioWriteDataMethod *method = endpoint->handleMethods->writeData;
+
+  if (!method) {
+    logUnsupportedOperation("writeData");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  logBytes(LOG_CATEGORY(GENERIC_IO), "output", data, size);
+
+  ssize_t result = method(endpoint->handle, data, size,
+                          endpoint->options.outputTimeout);
+
+  if (endpoint->options.ignoreWriteTimeouts) {
+    if (result == -1) {
+      if ((errno == EAGAIN)
+#ifdef ETIMEDOUT
+       || (errno == ETIMEDOUT)
+#endif /* ETIMEDOUT */
+      ) result = size;
+    }
+  }
+
+  return result;
+}
+
+int
+gioAwaitInput (GioEndpoint *endpoint, int timeout) {
+  GioAwaitInputMethod *method = endpoint->handleMethods->awaitInput;
+
+  if (!method) {
+    logUnsupportedOperation("awaitInput");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  if (endpoint->input.to - endpoint->input.from) return 1;
+
+  return method(endpoint->handle, timeout);
+}
+
+ssize_t
+gioReadData (GioEndpoint *endpoint, void *buffer, size_t size, int wait) {
+  GioReadDataMethod *method = endpoint->handleMethods->readData;
+
+  if (!method) {
+    logUnsupportedOperation("readData");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  {
+    unsigned char *start = buffer;
+    unsigned char *next = start;
+
+    while (size) {
+      {
+        unsigned int count = endpoint->input.to - endpoint->input.from;
+
+        if (count) {
+          if (count > size) count = size;
+          memcpy(next, &endpoint->input.buffer[endpoint->input.from], count);
+
+          endpoint->input.from += count;
+          next += count;
+          size -= count;
+          continue;
+        }
+
+        endpoint->input.from = endpoint->input.to = 0;
+      }
+
+      if (endpoint->input.error) {
+        if (next != start) break;
+        errno = endpoint->input.error;
+        endpoint->input.error = 0;
+        return -1;
+      }
+
+      {
+        ssize_t result = method(endpoint->handle,
+                                &endpoint->input.buffer[endpoint->input.to],
+                                sizeof(endpoint->input.buffer) - endpoint->input.to,
+                                (wait? endpoint->options.inputTimeout: 0), 0);
+
+        if (result > 0) {
+          logBytes(LOG_CATEGORY(GENERIC_IO), "input", &endpoint->input.buffer[endpoint->input.to], result);
+          endpoint->input.to += result;
+          wait = 1;
+        } else {
+          if (!result) break;
+          if (errno == EAGAIN) break;
+          endpoint->input.error = errno;
+        }
+      }
+    }
+
+    if (next == start) errno = EAGAIN;
+    return next - start;
+  }
+}
+
+int
+gioReadByte (GioEndpoint *endpoint, unsigned char *byte, int wait) {
+  ssize_t result = gioReadData(endpoint, byte, 1, wait);
+  if (result > 0) return 1;
+  if (result == 0) errno = EAGAIN;
+  return 0;
+}
+
+int
+gioDiscardInput (GioEndpoint *endpoint) {
+  unsigned char byte;
+  while (gioReadByte(endpoint, &byte, 0));
+  return errno == EAGAIN;
+}
+
+int
+gioMonitorInput (GioEndpoint *endpoint, AsyncMonitorCallback *callback, void *data) {
+  GioMonitorInputMethod *method = endpoint->handleMethods->monitorInput;
+
+  if (method) {
+    if (method(endpoint->handle, callback, data)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+gioReconfigureResource (
+  GioEndpoint *endpoint,
+  const SerialParameters *parameters
+) {
+  int ok = 1;
+  GioReconfigureResourceMethod *method = endpoint->handleMethods->reconfigureResource;
+
+  if (!method) {
+    logUnsupportedOperation("reconfigureResource");
+  } else if (method(endpoint->handle, parameters)) {
+    gioSetBytesPerSecond(endpoint, parameters);
+  } else {
+    ok = 0;
+  }
+
+  return ok;
+}
+
+unsigned int
+gioGetBytesPerSecond (GioEndpoint *endpoint) {
+  return endpoint->bytesPerSecond;
+}
+
+unsigned int
+gioGetMillisecondsToTransfer (GioEndpoint *endpoint, size_t bytes) {
+  return endpoint->bytesPerSecond? (((bytes * 1000) / endpoint->bytesPerSecond) + 1): 0;
+}
+
+ssize_t
+gioTellResource (
+  GioEndpoint *endpoint,
+  uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  const void *data, uint16_t size
+) {
+  GioTellResourceMethod *method = endpoint->handleMethods->tellResource;
+
+  if (!method) {
+    logUnsupportedOperation("tellResource");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  return method(endpoint->handle, recipient, type,
+                request, value, index, data, size,
+                endpoint->options.requestTimeout);
+}
+
+ssize_t
+gioAskResource (
+  GioEndpoint *endpoint,
+  uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  void *buffer, uint16_t size
+) {
+  GioAskResourceMethod *method = endpoint->handleMethods->askResource;
+
+  if (!method) {
+    logUnsupportedOperation("askResource");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  return method(endpoint->handle, recipient, type,
+                request, value, index, buffer, size,
+                endpoint->options.requestTimeout);
+}
+
+HidItemsDescriptor *
+gioGetHidDescriptorMethod (
+  GioEndpoint *endpoint) {
+  GioGetHidDescriptorMethod *method = endpoint->handleMethods->getHidDescriptor;
+
+  if (!method) {
+    logUnsupportedOperation("getHidDescriptor");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return method(
+    endpoint->handle
+  );
+}
+
+int
+gioGetHidReportSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+) {
+  GioGetHidReportSizeMethod *method = endpoint->handleMethods->getHidReportSize;
+
+  if (!method) {
+    logUnsupportedOperation("getHidReportSize");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  int ok = method(
+    endpoint->handle, identifier, size,
+    endpoint->options.requestTimeout
+  );
+
+  if (ok) return 1;
+  logMessage(LOG_WARNING, "HID report not found: %02X", identifier);
+  return 0;
+}
+
+size_t
+gioGetHidInputSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+) {
+  HidReportSize size;
+  if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0;
+  return size.input;
+}
+
+size_t
+gioGetHidOutputSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+) {
+  HidReportSize size;
+  if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0;
+  return size.output;
+}
+
+size_t
+gioGetHidFeatureSize (
+  GioEndpoint *endpoint,
+  HidReportIdentifier identifier
+) {
+  HidReportSize size;
+  if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0;
+  return size.feature;
+}
+
+ssize_t
+gioGetHidReport (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size
+) {
+  GioGetHidReportMethod *method = endpoint->handleMethods->getHidReport;
+
+  if (!method) {
+    logUnsupportedOperation("getHidReport");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  buffer[0] = identifier;
+  return method(endpoint->handle, identifier,
+                buffer, size, endpoint->options.requestTimeout);
+}
+
+ssize_t
+gioReadHidReport (
+  GioEndpoint *endpoint,
+  unsigned char *buffer, size_t size
+) {
+  return gioGetHidReport(endpoint, buffer[0], buffer, size);
+}
+
+ssize_t
+gioSetHidReport (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size
+) {
+  GioSetHidReportMethod *method = endpoint->handleMethods->setHidReport;
+
+  if (!method) {
+    logUnsupportedOperation("setHidReport");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  return method(endpoint->handle, identifier,
+                data, size, endpoint->options.requestTimeout);
+}
+
+ssize_t
+gioWriteHidReport (
+  GioEndpoint *endpoint,
+  const unsigned char *data, size_t size
+) {
+  HidReportIdentifier identifier = data[0];
+  if (!identifier) data += 1, size -= 1;
+  return gioSetHidReport(endpoint, identifier, data, size);
+}
+
+ssize_t
+gioGetHidFeature (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size
+) {
+  GioGetHidFeatureMethod *method = endpoint->handleMethods->getHidFeature;
+
+  if (!method) {
+    logUnsupportedOperation("getHidFeature");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  buffer[0] = identifier;
+  return method(endpoint->handle, identifier,
+                buffer, size, endpoint->options.requestTimeout);
+}
+
+ssize_t
+gioReadHidFeature (
+  GioEndpoint *endpoint,
+  unsigned char *buffer, size_t size
+) {
+  return gioGetHidFeature(endpoint, buffer[0], buffer, size);
+}
+
+ssize_t
+gioSetHidFeature (
+  GioEndpoint *endpoint, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size
+) {
+  GioSetHidFeatureMethod *method = endpoint->handleMethods->setHidFeature;
+
+  if (!method) {
+    logUnsupportedOperation("setHidFeature");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  return method(endpoint->handle, identifier,
+                data, size, endpoint->options.requestTimeout);
+}
+
+ssize_t
+gioWriteHidFeature (
+  GioEndpoint *endpoint,
+  const unsigned char *data, size_t size
+) {
+  HidReportIdentifier identifier = data[0];
+  if (!identifier) data += 1, size -= 1;
+  return gioSetHidFeature(endpoint, identifier, data, size);
+}
+
+struct GioHandleInputObjectStruct {
+  GioEndpoint *endpoint;
+  AsyncHandle pollAlarm;
+
+  GioInputHandler *handler;
+  void *data;
+};
+
+static int
+handleInput (GioHandleInputObject *hio, int error) {
+  GioHandleInputParameters parameters = {
+    .error = error,
+    .data = hio->data
+  };
+
+  return hio->handler(&parameters);
+}
+
+ASYNC_MONITOR_CALLBACK(gioInputMonitor) {
+  GioHandleInputObject *hio = parameters->data;
+
+  handleInput(hio, parameters->error);
+  return 1;
+}
+
+ASYNC_ALARM_CALLBACK(handleInputAlarm) {
+  GioHandleInputObject *hio = parameters->data;
+
+  if (handleInput(hio, 0)) asyncResetAlarmIn(hio->pollAlarm, 0);
+}
+
+GioHandleInputObject *
+gioNewHandleInputObject (
+  GioEndpoint *endpoint, int pollInterval,
+  GioInputHandler *handler, void *data
+) {
+  GioHandleInputObject *hio;
+
+  if ((hio = malloc(sizeof(*hio)))) {
+    memset(hio, 0, sizeof(*hio));
+
+    hio->endpoint = endpoint;
+    hio->pollAlarm = NULL;
+
+    hio->handler = handler;
+    hio->data = data;
+
+    if (endpoint) {
+      if (gioMonitorInput(endpoint, gioInputMonitor, hio)) {
+        handleInput(hio, 0);
+        return hio;
+      }
+    }
+
+    if (asyncNewRelativeAlarm(&hio->pollAlarm, 0, handleInputAlarm, hio)) {
+      if (asyncResetAlarmInterval(hio->pollAlarm, pollInterval)) {
+        return hio;
+      }
+
+      asyncCancelRequest(hio->pollAlarm);
+    }
+
+    free(hio);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+gioDestroyHandleInputObject (GioHandleInputObject *hio) {
+  if (hio->pollAlarm) {
+    asyncCancelRequest(hio->pollAlarm);
+  } else {
+    gioMonitorInput(hio->endpoint, NULL, NULL);
+  }
+
+  free(hio);
+}
diff --git a/Programs/gio_bluetooth.c b/Programs/gio_bluetooth.c
new file mode 100644
index 0000000..26ea5b5
--- /dev/null
+++ b/Programs/gio_bluetooth.c
@@ -0,0 +1,201 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "io_bluetooth.h"
+#include "brl.h"
+
+struct GioHandleStruct {
+  BluetoothConnection *connection;
+  GioEndpoint *hidEndpoint;
+};
+
+static int
+disconnectBluetoothResource (GioHandle *handle) {
+  if (handle->connection) bthCloseConnection(handle->connection);
+  if (handle->hidEndpoint) gioDisconnectResource(handle->hidEndpoint);
+  free(handle);
+  return 1;
+}
+
+static GioEndpoint *
+getBluetoothChainedEndpoint (GioHandle *handle) {
+  return handle->hidEndpoint;
+}
+
+static const char *
+makeBluetoothResourceIdentifier (GioHandle *handle, char *buffer, size_t size) {
+  return bthMakeConnectionIdentifier(handle->connection, buffer, size);
+}
+
+static char *
+getBluetoothResourceName (GioHandle *handle, int timeout) {
+  const char *name = bthGetNameOfDevice(handle->connection, timeout);
+
+  if (name) {
+    char *copy = strdup(name);
+    if (copy) return copy;
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void *
+getBluetoothResourceObject (GioHandle *handle) {
+  return handle->connection;
+}
+
+static ssize_t
+writeBluetoothData (GioHandle *handle, const void *data, size_t size, int timeout) {
+  return bthWriteData(handle->connection, data, size);
+}
+
+static int
+awaitBluetoothInput (GioHandle *handle, int timeout) {
+  return bthAwaitInput(handle->connection, timeout);
+}
+
+static ssize_t
+readBluetoothData (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return bthReadData(handle->connection, buffer, size,
+                     initialTimeout, subsequentTimeout);
+}
+
+static int
+monitorBluetoothInput (GioHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  return bthMonitorInput(handle->connection, callback, data);
+}
+
+static const GioHandleMethods gioBluetoothMethods = {
+  .disconnectResource = disconnectBluetoothResource,
+  .getChainedEndpoint = getBluetoothChainedEndpoint,
+
+  .makeResourceIdentifier = makeBluetoothResourceIdentifier,
+  .getResourceName = getBluetoothResourceName,
+  .getResourceObject = getBluetoothResourceObject,
+
+  .writeData = writeBluetoothData,
+  .awaitInput = awaitBluetoothInput,
+  .readData = readBluetoothData,
+  .monitorInput = monitorBluetoothInput,
+};
+
+static int
+testBluetoothIdentifier (const char **identifier) {
+  return isBluetoothDeviceIdentifier(identifier);
+}
+
+static const GioPublicProperties gioPublicProperties_bluetooth = {
+  .testIdentifier = testBluetoothIdentifier,
+
+  .type = {
+    .name = "Bluetooth",
+    .identifier = GIO_TYPE_BLUETOOTH
+  }
+};
+
+static int
+isBluetoothSupported (const GioDescriptor *descriptor) {
+  return descriptor->bluetooth.channelNumber || descriptor->bluetooth.discoverChannel;
+}
+
+static const GioOptions *
+getBluetoothOptions (const GioDescriptor *descriptor) {
+  return &descriptor->bluetooth.options;
+}
+
+static const GioHandleMethods *
+getBluetoothMethods (void) {
+  return &gioBluetoothMethods;
+}
+
+static GioEndpoint *
+getHidEndpoint (uint64_t address, const GioDescriptor *descriptor) {
+  char identifier[0X40];
+  STR_BEGIN(identifier, sizeof(identifier));
+  STR_PRINTF("hid:address=");
+  STR_FORMAT(bthFormatAddress, address);
+  STR_END;
+
+  return gioConnectResource(identifier, descriptor);
+}
+
+static GioHandle *
+connectBluetoothResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  GioHandle *handle = malloc(sizeof(*handle));
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    BluetoothConnectionRequest request;
+    bthInitializeConnectionRequest(&request);
+
+    request.driver = braille->definition.code;
+    request.channel = descriptor->bluetooth.channelNumber;
+    request.discover = descriptor->bluetooth.discoverChannel;
+
+    if (bthApplyParameters(&request, identifier)) {
+      if (gioIsHidSupported(descriptor)) {
+        GioEndpoint *hidEndpoint = getHidEndpoint(request.address, descriptor);
+
+        if (hidEndpoint) {
+          handle->hidEndpoint = hidEndpoint;
+          return handle;
+        }
+      }
+
+      if ((handle->connection = bthOpenConnection(&request))) {
+        return handle;
+      }
+    }
+
+    free(handle);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static const GioPrivateProperties gioPrivateProperties_bluetooth = {
+  .isSupported = isBluetoothSupported,
+
+  .getOptions = getBluetoothOptions,
+  .getHandleMethods = getBluetoothMethods,
+
+  .connectResource = connectBluetoothResource
+};
+
+const GioProperties gioProperties_bluetooth = {
+  .public = &gioPublicProperties_bluetooth,
+  .private = &gioPrivateProperties_bluetooth
+};
diff --git a/Programs/gio_hid.c b/Programs/gio_hid.c
new file mode 100644
index 0000000..2eecd64
--- /dev/null
+++ b/Programs/gio_hid.c
@@ -0,0 +1,290 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "io_hid.h"
+
+struct GioHandleStruct {
+  HidDevice *device;
+  const HidModelEntry *model;
+};
+
+static int
+disconnectHidResource (GioHandle *handle) {
+  hidCloseDevice(handle->device);
+  free(handle);
+  return 1;
+}
+
+static const char *
+makeHidResourceIdentifier (GioHandle *handle, char *buffer, size_t size) {
+  return hidMakeDeviceIdentifier(handle->device, buffer, size);
+}
+
+static char *
+getHidResourceName (GioHandle *handle, int timeout) {
+  const char *name = hidGetDeviceName(handle->device);
+
+  if (name) {
+    char *copy = strdup(name);
+    if (copy) return copy;
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void *
+getHidResourceObject (GioHandle *handle) {
+  return handle->device;
+}
+
+static ssize_t
+writeHidData (GioHandle *handle, const void *data, size_t size, int timeout) {
+  return hidWriteData(handle->device, data, size);
+}
+
+static int
+awaitHidInput (GioHandle *handle, int timeout) {
+  return hidAwaitInput(handle->device, timeout);
+}
+
+static ssize_t
+readHidData (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return hidReadData(handle->device, buffer, size,
+                     initialTimeout, subsequentTimeout);
+}
+
+static int
+monitorHidInput (GioHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  return hidMonitorInput(handle->device, callback, data);
+}
+
+int
+getHidReportSize (
+  GioHandle *handle, HidReportIdentifier identifier,
+  HidReportSize *size, int timeout
+) {
+  return hidGetReportSize(handle->device, identifier, size);
+}
+
+static HidItemsDescriptor *
+getHidDescriptor (GioHandle *handle
+) {
+  return hidGetItems(handle->device);
+}
+
+static ssize_t
+getHidReport (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+) {
+  buffer[0] = identifier;
+  return hidGetReport(handle->device, buffer, size);
+}
+
+static ssize_t
+setHidReport (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+) {
+  unsigned char buffer[1 + size];
+
+  if (!identifier) {
+    buffer[0] = identifier;
+    memcpy(&buffer[1], data, size);
+
+    data = buffer;
+    size += 1;
+  }
+
+  return hidSetReport(handle->device, data, size);
+}
+
+static ssize_t
+getHidFeature (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+) {
+  buffer[0] = identifier;
+  return hidGetFeature(handle->device, buffer, size);
+}
+
+static ssize_t
+setHidFeature (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+) {
+  unsigned char buffer[1 + size];
+
+  if (!identifier) {
+    buffer[0] = identifier;
+    memcpy(&buffer[1], data, size);
+
+    data = buffer;
+    size += 1;
+  }
+
+  return hidSetFeature(handle->device, data, size);
+}
+
+static const GioHandleMethods gioHidMethods = {
+  .disconnectResource = disconnectHidResource,
+
+  .makeResourceIdentifier = makeHidResourceIdentifier,
+  .getResourceName = getHidResourceName,
+  .getResourceObject = getHidResourceObject,
+
+  .writeData = writeHidData,
+  .awaitInput = awaitHidInput,
+  .readData = readHidData,
+  .monitorInput = monitorHidInput,
+
+  .getHidReportSize = getHidReportSize,
+  .getHidDescriptor = getHidDescriptor,
+  .getHidReport = getHidReport,
+  .setHidReport = setHidReport,
+  .getHidFeature = getHidFeature,
+  .setHidFeature = setHidFeature,
+};
+
+static int
+testHidIdentifier (const char **identifier) {
+  return isHidDeviceIdentifier(identifier);
+}
+
+static const GioPublicProperties gioPublicProperties_hid = {
+  .testIdentifier = testHidIdentifier,
+
+  .type = {
+    .name = "HID",
+    .identifier = GIO_TYPE_HID
+  }
+};
+
+static int
+isHidSupported (const GioDescriptor *descriptor) {
+  return !!descriptor->hid.modelTable;
+}
+
+static const GioOptions *
+getHidOptions (const GioDescriptor *descriptor) {
+  return &descriptor->hid.options;
+}
+
+static const GioHandleMethods *
+getHidMethods (void) {
+  return &gioHidMethods;
+}
+
+static const HidModelEntry *
+getHidModelEntry (HidDevice *device, const HidModelEntry *model) {
+  if (model) {
+    HidDeviceIdentifier vendor;
+    HidDeviceIdentifier product;
+
+    if (hidGetDeviceIdentifiers(device, &vendor, &product)) {
+      for (; (model->vendor || model->product || model->name); model+=1) {
+        if (model->vendor) {
+          if (model->vendor != vendor) {
+            continue;
+          }
+        }
+
+        if (model->product) {
+          if (model->product != product) {
+            continue;
+          }
+        }
+
+        if (model->name) {
+          const char *name = hidGetDeviceName(device);
+          if (!name) continue;
+
+          size_t length = strlen(model->name);
+          if (length > strlen(name)) continue;
+          if (strncasecmp(name, model->name, length) != 0) continue;
+        }
+
+        logMessage(LOG_CATEGORY(HID_IO), "model found: %s", model->name);
+        return model;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static GioHandle *
+connectHidResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  GioHandle *handle = malloc(sizeof(*handle));
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    if (hidOpenDeviceWithParameters(&handle->device, identifier)) {
+      if (handle->device) {
+        handle->model = getHidModelEntry(
+          handle->device,
+          descriptor->hid.modelTable
+        );
+
+        if (handle->model) return handle;
+      }
+    }
+
+    free(handle);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+prepareHidEndpoint (GioEndpoint *endpoint) {
+  gioSetApplicationData(endpoint, endpoint->handle->model->data);
+  return 1;
+}
+
+static const GioPrivateProperties gioPrivateProperties_hid = {
+  .isSupported = isHidSupported,
+
+  .getOptions = getHidOptions,
+  .getHandleMethods = getHidMethods,
+
+  .connectResource = connectHidResource,
+  .prepareEndpoint = prepareHidEndpoint,
+};
+
+const GioProperties gioProperties_hid = {
+  .public = &gioPublicProperties_hid,
+  .private = &gioPrivateProperties_hid
+};
diff --git a/Programs/gio_internal.h b/Programs/gio_internal.h
new file mode 100644
index 0000000..6d0b5df
--- /dev/null
+++ b/Programs/gio_internal.h
@@ -0,0 +1,178 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_GIO_INTERNAL
+#define BRLTTY_INCLUDED_GIO_INTERNAL
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct GioHandleStruct GioHandle;
+
+typedef int GioDisconnectResourceMethod (GioHandle *handle);
+
+typedef GioEndpoint *GioGetChainedEndpointMethod (GioHandle *handle);
+
+typedef const char *MakeResourceIdentifierMethod (GioHandle *handle, char *buffer, size_t size);
+
+typedef char *GioGetResourceNameMethod (GioHandle *handle, int timeout);
+
+typedef void *GioGetResourceObjectMethod (GioHandle *handle);
+
+typedef ssize_t GioWriteDataMethod (GioHandle *handle, const void *data, size_t size, int timeout);
+
+typedef int GioAwaitInputMethod (GioHandle *handle, int timeout);
+
+typedef ssize_t GioReadDataMethod (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+typedef int GioMonitorInputMethod (GioHandle *handle, AsyncMonitorCallback *callback, void *data);
+
+typedef int GioReconfigureResourceMethod (GioHandle *handle, const SerialParameters *parameters);
+
+typedef ssize_t GioTellResourceMethod (
+  GioHandle *handle, uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  const void *data, uint16_t size, int timeout
+);
+
+typedef ssize_t GioAskResourceMethod (
+  GioHandle *handle, uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  void *buffer, uint16_t size, int timeout
+);
+
+typedef HidItemsDescriptor *GioGetHidDescriptorMethod(GioHandle *handle);
+
+typedef int GioGetHidReportSizeMethod (
+  GioHandle *handle, HidReportIdentifier identifier,
+  HidReportSize *size, int timeout
+);
+
+typedef ssize_t GioGetHidReportMethod (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+);
+
+typedef ssize_t GioSetHidReportMethod (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+);
+
+typedef ssize_t GioGetHidFeatureMethod (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+);
+
+typedef ssize_t GioSetHidFeatureMethod (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+);
+
+typedef struct {
+  GioDisconnectResourceMethod *disconnectResource;
+  GioGetChainedEndpointMethod *getChainedEndpoint;
+
+  MakeResourceIdentifierMethod *makeResourceIdentifier;
+  GioGetResourceNameMethod *getResourceName;
+  GioGetResourceObjectMethod *getResourceObject;
+
+  GioWriteDataMethod *writeData;
+  GioAwaitInputMethod *awaitInput;
+  GioReadDataMethod *readData;
+  GioMonitorInputMethod *monitorInput;
+  GioReconfigureResourceMethod *reconfigureResource;
+
+  GioTellResourceMethod *tellResource;
+  GioAskResourceMethod *askResource;
+
+  GioGetHidReportSizeMethod *getHidReportSize;
+  GioGetHidDescriptorMethod *getHidDescriptor;
+  GioGetHidReportMethod *getHidReport;
+  GioSetHidReportMethod *setHidReport;
+  GioGetHidFeatureMethod *getHidFeature;
+  GioSetHidFeatureMethod *setHidFeature;
+} GioHandleMethods;
+
+struct GioEndpointStruct {
+  GioHandle *handle;
+  const GioHandleMethods *handleMethods;
+  GioOptions options;
+  GioTypeIdentifier resourceType;
+  unsigned int bytesPerSecond;
+  unsigned char referenceCount;
+
+  struct {
+    int error;
+    unsigned int from;
+    unsigned int to;
+    unsigned char buffer[0X40];
+  } input;
+};
+
+typedef int GioIsSupportedMethod (const GioDescriptor *descriptor);
+
+typedef const GioOptions *GioGetOptionsMethod (const GioDescriptor *descriptor);
+
+typedef const GioHandleMethods *GioGetHandleMethodsMethod (void);
+
+typedef GioHandle *GioConnectResourceMethod (
+  const char *identifier,
+  const GioDescriptor *descriptor
+);
+
+typedef int GioPrepareEndpointMethod (GioEndpoint *endpoint);
+
+typedef struct {
+  GioIsSupportedMethod *isSupported;
+
+  GioGetOptionsMethod *getOptions;
+  GioGetHandleMethodsMethod *getHandleMethods;
+
+  GioConnectResourceMethod *connectResource;
+  GioPrepareEndpointMethod *prepareEndpoint;
+} GioPrivateProperties;
+
+typedef struct {
+  const GioPublicProperties *public;
+  const GioPrivateProperties *private;
+} GioProperties;
+
+extern const GioProperties *const gioProperties[];
+extern const GioProperties gioProperties_serial;
+extern const GioProperties gioProperties_usb;
+extern const GioProperties gioProperties_bluetooth;
+extern const GioProperties gioProperties_hid;
+extern const GioProperties gioProperties_null;
+
+extern void gioSetBytesPerSecond (GioEndpoint *endpoint, const SerialParameters *parameters);
+extern void gioSetApplicationData (GioEndpoint *endpoint, const void *data);
+
+static inline int
+gioIsHidSupported (const GioDescriptor *descriptor) {
+  return gioProperties_hid.private->isSupported(descriptor);
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_GIO_INTERNAL */
diff --git a/Programs/gio_null.c b/Programs/gio_null.c
new file mode 100644
index 0000000..22d2c63
--- /dev/null
+++ b/Programs/gio_null.c
@@ -0,0 +1,140 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "parse.h"
+
+struct GioHandleStruct {
+  int place_holder;
+};
+
+static int
+disconnectNullResource (GioHandle *handle) {
+  free(handle);
+  return 1;
+}
+
+static const char *
+makeNullResourceIdentifier (GioHandle *handle, char *buffer, size_t size) {
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s%c", "null", PARAMETER_QUALIFIER_CHARACTER);
+  STR_END;
+  return buffer;
+}
+
+static ssize_t
+writeNullData (GioHandle *handle, const void *data, size_t size, int timeout) {
+  return size;
+}
+
+static int
+awaitNullInput (GioHandle *handle, int timeout) {
+  return 1;
+}
+
+static ssize_t
+readNullData (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return 0;
+}
+
+static int
+monitorNullInput (GioHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  return 1;
+}
+
+static const GioHandleMethods gioNullMethods = {
+  .disconnectResource = disconnectNullResource,
+
+  .makeResourceIdentifier = makeNullResourceIdentifier,
+
+  .writeData = writeNullData,
+  .awaitInput = awaitNullInput,
+  .readData = readNullData,
+  .monitorInput = monitorNullInput
+};
+
+static int
+testNullIdentifier (const char **identifier) {
+  return hasQualifier(identifier, "null");
+}
+
+static const GioPublicProperties gioPublicProperties_null = {
+  .testIdentifier = testNullIdentifier,
+
+  .type = {
+    .name = "null",
+    .identifier = GIO_TYPE_NULL
+  }
+};
+
+static int
+isNullSupported (const GioDescriptor *descriptor) {
+  return 1;
+}
+
+static const GioOptions *
+getNullOptions (const GioDescriptor *descriptor) {
+  return &descriptor->null.options;
+}
+
+static const GioHandleMethods *
+getNullMethods (void) {
+  return &gioNullMethods;
+}
+
+static GioHandle *
+connectNullResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  GioHandle *handle = malloc(sizeof(*handle));
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    return handle;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static const GioPrivateProperties gioPrivateProperties_null = {
+  .isSupported = isNullSupported,
+
+  .getOptions = getNullOptions,
+  .getHandleMethods = getNullMethods,
+
+  .connectResource = connectNullResource
+};
+
+const GioProperties gioProperties_null = {
+  .public = &gioPublicProperties_null,
+  .private = &gioPrivateProperties_null
+};
diff --git a/Programs/gio_serial.c b/Programs/gio_serial.c
new file mode 100644
index 0000000..b36f908
--- /dev/null
+++ b/Programs/gio_serial.c
@@ -0,0 +1,170 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "io_serial.h"
+
+struct GioHandleStruct {
+  SerialDevice *device;
+  SerialParameters parameters;
+};
+
+static int
+disconnectSerialResource (GioHandle *handle) {
+  serialCloseDevice(handle->device);
+  free(handle);
+  return 1;
+}
+
+static const char *
+makeSerialResourceIdentifier (GioHandle *handle, char *buffer, size_t size) {
+  return serialMakeDeviceIdentifier(handle->device, buffer, size);
+}
+
+static void *
+getSerialResourceObject (GioHandle *handle) {
+  return handle->device;
+}
+
+static ssize_t
+writeSerialData (GioHandle *handle, const void *data, size_t size, int timeout) {
+  return serialWriteData(handle->device, data, size);
+}
+
+static int
+awaitSerialInput (GioHandle *handle, int timeout) {
+  return serialAwaitInput(handle->device, timeout);
+}
+
+static ssize_t
+readSerialData (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return serialReadData(handle->device, buffer, size,
+                        initialTimeout, subsequentTimeout);
+}
+
+static int
+monitorSerialInput (GioHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  return serialMonitorInput(handle->device, callback, data);
+}
+
+static int
+reconfigureSerialResource (GioHandle *handle, const SerialParameters *parameters) {
+  int ok = serialSetParameters(handle->device, parameters);
+
+  if (ok) handle->parameters = *parameters;
+  return ok;
+}
+
+static const GioHandleMethods gioSerialMethods = {
+  .disconnectResource = disconnectSerialResource,
+
+  .makeResourceIdentifier = makeSerialResourceIdentifier,
+  .getResourceObject = getSerialResourceObject,
+
+  .writeData = writeSerialData,
+  .awaitInput = awaitSerialInput,
+  .readData = readSerialData,
+  .monitorInput = monitorSerialInput,
+  .reconfigureResource = reconfigureSerialResource,
+};
+
+static int
+testSerialIdentifier (const char **identifier) {
+  return isSerialDeviceIdentifier(identifier);
+}
+
+static const GioPublicProperties gioPublicProperties_serial = {
+  .testIdentifier = testSerialIdentifier,
+
+  .type = {
+    .name = "serial",
+    .identifier = GIO_TYPE_SERIAL
+  }
+};
+
+static int
+isSerialSupported (const GioDescriptor *descriptor) {
+  return descriptor->serial.parameters != NULL;
+}
+
+static const GioOptions *
+getSerialOptions (const GioDescriptor *descriptor) {
+  return &descriptor->serial.options;
+}
+
+static const GioHandleMethods *
+getSerialMethods (void) {
+  return &gioSerialMethods;
+}
+
+static GioHandle *
+connectSerialResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  GioHandle *handle = malloc(sizeof(*handle));
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    if ((handle->device = serialOpenDevice(identifier))) {
+      if (serialSetParameters(handle->device, descriptor->serial.parameters)) {
+        handle->parameters = *descriptor->serial.parameters;
+        return handle;
+      }
+
+      serialCloseDevice(handle->device);
+    }
+
+    free(handle);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+prepareSerialEndpoint (GioEndpoint *endpoint) {
+  gioSetBytesPerSecond(endpoint, &endpoint->handle->parameters);
+  return 1;
+}
+
+static const GioPrivateProperties gioPrivateProperties_serial = {
+  .isSupported = isSerialSupported,
+
+  .getOptions = getSerialOptions,
+  .getHandleMethods = getSerialMethods,
+
+  .connectResource = connectSerialResource,
+  .prepareEndpoint = prepareSerialEndpoint
+};
+
+const GioProperties gioProperties_serial = {
+  .public = &gioPublicProperties_serial,
+  .private = &gioPrivateProperties_serial
+};
diff --git a/Programs/gio_usb.c b/Programs/gio_usb.c
new file mode 100644
index 0000000..5a67fa1
--- /dev/null
+++ b/Programs/gio_usb.c
@@ -0,0 +1,397 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_POSIX_THREADS
+#ifdef __MINGW32__
+#include "win_pthread.h"
+#else /* __MINGW32__ */
+#include <pthread.h>
+#endif /* __MINGW32__ */
+#endif /* HAVE_POSIX_THREADS */
+
+#include "log.h"
+#include "parameters.h"
+#include "async_wait.h"
+#include "io_generic.h"
+#include "gio_internal.h"
+#include "io_usb.h"
+#include "usb_hid.h"
+#include "hid_items.h"
+
+struct GioHandleStruct {
+  UsbChannel *channel;
+  GioUsbConnectionProperties properties;
+  HidItemsDescriptor *hidItems;
+};
+
+static int
+disconnectUsbResource (GioHandle *handle) {
+  usbCloseChannel(handle->channel);
+  if (handle->hidItems) free(handle->hidItems);
+  free(handle);
+  return 1;
+}
+
+static const char *
+makeUsbResourceIdentifier (GioHandle *handle, char *buffer, size_t size) {
+  return usbMakeChannelIdentifier(handle->channel, buffer, size);
+}
+
+static char *
+getUsbResourceName (GioHandle *handle, int timeout) {
+  UsbChannel *channel = handle->channel;
+
+  return usbGetProduct(channel->device, timeout);
+}
+
+static void *
+getUsbResourceObject (GioHandle *handle) {
+  return handle->channel;
+}
+
+static ssize_t
+writeUsbData (GioHandle *handle, const void *data, size_t size, int timeout) {
+  UsbChannel *channel = handle->channel;
+
+  {
+    GioUsbWriteDataMethod *method = handle->properties.writeData;
+
+    if (method) {
+      return method(channel->device, channel->definition, data, size, timeout);
+    }
+  }
+
+  if (channel->definition->outputEndpoint) {
+    return usbWriteData(channel->device,
+                        channel->definition->outputEndpoint,
+                        data, size, timeout);
+  }
+
+  {
+    const UsbSerialOperations *serial = usbGetSerialOperations(channel->device);
+
+    if (serial) {
+      if (serial->writeData) {
+        return serial->writeData(channel->device, data, size);
+      }
+    }
+  }
+
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+awaitUsbInput (GioHandle *handle, int timeout) {
+  UsbChannel *channel = handle->channel;
+
+  {
+    GioUsbAwaitInputMethod *method = handle->properties.awaitInput;
+
+    if (method) {
+      return method(channel->device, channel->definition, timeout);
+    }
+  }
+
+  {
+    unsigned char endpoint = channel->definition->inputEndpoint;
+
+    if (!endpoint) {
+      asyncWait(timeout);
+      return 0;
+    }
+
+    return usbAwaitInput(channel->device, endpoint, timeout);
+  }
+}
+
+static ssize_t
+readUsbData (
+  GioHandle *handle, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  {
+    GioUsbReadDataMethod *method = handle->properties.readData;
+
+    if (method) {
+      return method(channel->device, channel->definition, buffer, size, initialTimeout, subsequentTimeout);
+    }
+  }
+
+  {
+    unsigned char endpoint = channel->definition->inputEndpoint;
+
+    if (!endpoint) {
+      errno = EAGAIN;
+      return -1;
+    }
+
+    return usbReadData(channel->device, endpoint,
+                       buffer, size, initialTimeout, subsequentTimeout);
+  }
+}
+
+static int
+monitorUsbInput (GioHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  if (!GIO_USB_INPUT_MONITOR_DISABLE) {
+    UsbChannel *channel = handle->channel;
+    unsigned char endpoint = channel->definition->inputEndpoint;
+
+    if (!endpoint) return 0;
+    return usbMonitorInputEndpoint(channel->device, endpoint, callback, data);
+  }
+
+  return 0;
+}
+
+static int
+reconfigureUsbResource (GioHandle *handle, const SerialParameters *parameters) {
+  UsbChannel *channel = handle->channel;
+
+  return usbSetSerialParameters(channel->device, parameters);
+}
+
+static ssize_t
+tellUsbResource (
+  GioHandle *handle, uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  const void *data, uint16_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbControlWrite(channel->device, recipient, type,
+                         request, value, index, data, size, timeout);
+}
+
+static ssize_t
+askUsbResource (
+  GioHandle *handle, uint8_t recipient, uint8_t type,
+  uint8_t request, uint16_t value, uint16_t index,
+  void *buffer, uint16_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbControlRead(channel->device, recipient, type,
+                        request, value, index, buffer, size, timeout);
+}
+
+static const HidItemsDescriptor *
+getUsbHidItems (GioHandle *handle, int timeout) {
+  if (!handle->hidItems) {
+    UsbChannel *channel = handle->channel;
+
+    handle->hidItems = usbHidGetItems(
+      channel->device, channel->definition->interface,
+      0, timeout
+    );
+  }
+
+  return handle->hidItems;
+}
+
+static int
+getUsbHidReportSize (
+  GioHandle *handle, HidReportIdentifier identifier,
+  HidReportSize *size, int timeout
+) {
+  const HidItemsDescriptor *items = getUsbHidItems(handle, timeout);
+  if (!items) return 0;
+  return hidReportSize(items, identifier, size);
+}
+
+static ssize_t
+getUsbHidReport (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbHidGetReport(channel->device, channel->definition->interface,
+                         identifier, buffer, size, timeout);
+}
+
+static ssize_t
+setUsbHidReport (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbHidSetReport(channel->device, channel->definition->interface,
+                         identifier, data, size, timeout);
+}
+
+static ssize_t
+getUsbHidFeature (
+  GioHandle *handle, HidReportIdentifier identifier,
+  unsigned char *buffer, size_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbHidGetFeature(channel->device, channel->definition->interface,
+                          identifier, buffer, size, timeout);
+}
+
+static ssize_t
+setUsbHidFeature (
+  GioHandle *handle, HidReportIdentifier identifier,
+  const unsigned char *data, size_t size, int timeout
+) {
+  UsbChannel *channel = handle->channel;
+
+  return usbHidSetFeature(channel->device, channel->definition->interface,
+                          identifier, data, size, timeout);
+}
+
+static const GioHandleMethods gioUsbMethods = {
+  .disconnectResource = disconnectUsbResource,
+
+  .makeResourceIdentifier = makeUsbResourceIdentifier,
+  .getResourceName = getUsbResourceName,
+  .getResourceObject = getUsbResourceObject,
+
+  .writeData = writeUsbData,
+  .awaitInput = awaitUsbInput,
+  .readData = readUsbData,
+  .monitorInput = monitorUsbInput,
+  .reconfigureResource = reconfigureUsbResource,
+
+  .tellResource = tellUsbResource,
+  .askResource = askUsbResource,
+
+  .getHidReportSize = getUsbHidReportSize,
+  .getHidReport = getUsbHidReport,
+  .setHidReport = setUsbHidReport,
+  .getHidFeature = getUsbHidFeature,
+  .setHidFeature = setUsbHidFeature,
+};
+
+static int
+testUsbIdentifier (const char **identifier) {
+  return isUsbDeviceIdentifier(identifier);
+}
+
+static const GioPublicProperties gioPublicProperties_usb = {
+  .testIdentifier = testUsbIdentifier,
+
+  .type = {
+    .name = "USB",
+    .identifier = GIO_TYPE_USB
+  }
+};
+
+static int
+isUsbSupported (const GioDescriptor *descriptor) {
+  return descriptor->usb.channelDefinitions != NULL;
+}
+
+static const GioOptions *
+getUsbOptions (const GioDescriptor *descriptor) {
+  return &descriptor->usb.options;
+}
+
+static const GioHandleMethods *
+getUsbMethods (void) {
+  return &gioUsbMethods;
+}
+
+static GioHandle *
+connectUsbResource (
+  const char *identifier,
+  const GioDescriptor *descriptor
+) {
+  GioHandle *handle = malloc(sizeof(*handle));
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    if ((handle->channel = usbOpenChannel(descriptor->usb.channelDefinitions, identifier))) {
+      const UsbChannel *channel = handle->channel;
+      const UsbChannelDefinition *definition = channel->definition;
+      GioUsbConnectionProperties *properties = &handle->properties;
+
+      memset(properties, 0, sizeof(*properties));
+      properties->applicationData = definition->data;
+      properties->writeData = NULL;
+      properties->awaitInput = NULL;
+      properties->readData = NULL;
+      properties->inputFilter = NULL;
+
+      {
+        GioUsbSetConnectionPropertiesMethod *method = descriptor->usb.setConnectionProperties;
+
+        if (method) method(properties, definition);
+      }
+
+      if (!properties->inputFilter ||
+          usbAddInputFilter(channel->device, properties->inputFilter)) {
+        return handle;
+      }
+
+      usbCloseChannel(handle->channel);
+    }
+
+    free(handle);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+prepareUsbEndpoint (GioEndpoint *endpoint) {
+  GioHandle *handle = endpoint->handle;
+  UsbChannel *channel = handle->channel;
+
+  if (!endpoint->options.applicationData) {
+    endpoint->options.applicationData = handle->properties.applicationData;
+  }
+
+  {
+    const SerialParameters *parameters = channel->definition->serial;
+
+    if (parameters) {
+      gioSetBytesPerSecond(endpoint, parameters);
+    }
+  }
+
+  return 1;
+}
+
+static const GioPrivateProperties gioPrivateProperties_usb = {
+  .isSupported = isUsbSupported,
+
+  .getOptions = getUsbOptions,
+  .getHandleMethods = getUsbMethods,
+
+  .connectResource = connectUsbResource,
+  .prepareEndpoint = prepareUsbEndpoint
+};
+
+const GioProperties gioProperties_usb = {
+  .public = &gioPublicProperties_usb,
+  .private = &gioPrivateProperties_usb
+};
diff --git a/Programs/hid.c b/Programs/hid.c
new file mode 100644
index 0000000..4831dc1
--- /dev/null
+++ b/Programs/hid.c
@@ -0,0 +1,754 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "io_hid.h"
+#include "hid_internal.h"
+#include "parse.h"
+#include "device.h"
+
+typedef enum {
+  HID_PARM_ADDRESS,
+  HID_PARM_NAME,
+
+  HID_PARM_MANUFACTURER,
+  HID_PARM_DESCRIPTION,
+  HID_PARM_SERIAL_NUMBER,
+
+  HID_PARM_VENDOR,
+  HID_PARM_PRODUCT,
+} HidDeviceParameter;
+
+static const char *const hidDeviceParameterNames[] = {
+  "address",
+  "name",
+
+  "manufacturer",
+  "description",
+  "serialNumber",
+
+  "vendor",
+  "product",
+
+  NULL
+};
+
+static char **
+hidGetDeviceParameters (const char *string) {
+  if (!string) string = "";
+  return getDeviceParameters(hidDeviceParameterNames, string);
+}
+
+typedef struct {
+  STR_DECLARE_FORMATTER((*extendDeviceIdentifier), HidDevice *device);
+} HidBusMethods;
+
+static
+STR_BEGIN_FORMATTER(hidExtendUSBDeviceIdentifier, HidDevice *device)
+  {
+    const char *serialNumber = hidGetDeviceAddress(device);
+
+    if (serialNumber && *serialNumber) {
+      STR_PRINTF(
+        "%s%c%s%c",
+        hidDeviceParameterNames[HID_PARM_SERIAL_NUMBER],
+        PARAMETER_ASSIGNMENT_CHARACTER,
+        serialNumber, DEVICE_PARAMETER_SEPARATOR
+      );
+    }
+  }
+STR_END_FORMATTER
+
+static const HidBusMethods hidUSBBusMethods = {
+  .extendDeviceIdentifier = hidExtendUSBDeviceIdentifier,
+};
+
+static
+STR_BEGIN_FORMATTER(hidExtendBluetoothDeviceIdentifier, HidDevice *device)
+  {
+    const char *macAddress = hidGetDeviceAddress(device);
+
+    if (macAddress && *macAddress) {
+      STR_PRINTF(
+        "%s%c%s%c",
+        hidDeviceParameterNames[HID_PARM_ADDRESS],
+        PARAMETER_ASSIGNMENT_CHARACTER,
+        macAddress, DEVICE_PARAMETER_SEPARATOR
+      );
+    }
+  }
+STR_END_FORMATTER
+
+static const HidBusMethods hidBluetoothBusMethods= {
+  .extendDeviceIdentifier = hidExtendBluetoothDeviceIdentifier,
+};
+
+void
+hidInitializeUSBFilter (HidUSBFilter *filter) {
+  memset(filter, 0, sizeof(*filter));
+}
+
+void
+hidInitializeBluetoothFilter (HidBluetoothFilter *filter) {
+  memset(filter, 0, sizeof(*filter));
+}
+
+int
+hidParseDeviceIdentifier (HidDeviceIdentifier *identifier, const char *string) {
+  if (!string) return 0;
+  if (!*string) return 0;
+  if (strlen(string) > 4) return 0;
+
+  char *end;
+  long unsigned int value = strtoul(string, &end, 0X10);
+  if (*end) return 0;
+  if (value > UINT16_MAX) return 0;
+
+  *identifier = value;
+  return 1;
+}
+
+int
+hidMatchString (const char *actualString, const char *testString) {
+  size_t testLength = strlen(testString);
+  if (testLength > strlen(actualString)) return 0;
+  return strncasecmp(actualString, testString, testLength) == 0;
+}
+
+struct HidDeviceStruct {
+  HidHandle *handle;
+
+  const HidBusMethods *busMethods;
+  const HidHandleMethods *handleMethods;
+};
+
+static void
+hidDestroyHandle (HidHandle *handle) {
+  HidDestroyHandleMethod *method = hidPackageDescriptor.handleMethods->destroyHandle;
+
+  if (method) {
+    method(handle);
+  } else {
+    logUnsupportedOperation("hidCloseDevice");
+    errno = ENOSYS;
+  }
+}
+
+static HidDevice *
+hidNewDevice (HidHandle *handle, const HidBusMethods *busMethods) {
+  if (handle) {
+    HidDevice *device;
+
+    if ((device = malloc(sizeof(*device)))) {
+      memset(device, 0, sizeof(*device));
+      device->handle = handle;
+      device->handleMethods = hidPackageDescriptor.handleMethods;
+      device->busMethods = busMethods;
+      return device;
+    } else {
+      logMallocError();
+    }
+
+    hidDestroyHandle(handle);
+  }
+
+  return NULL;
+}
+
+HidDevice *
+hidOpenUSBDevice (const HidUSBFilter *filter) {
+  HidNewUSBHandleMethod *method = hidPackageDescriptor.newUSBHandle;
+
+  if (!method) {
+    logUnsupportedOperation("hidOpenUSBDevice");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return hidNewDevice(method(filter), &hidUSBBusMethods);
+}
+
+HidDevice *
+hidOpenBluetoothDevice (const HidBluetoothFilter *filter) {
+  HidNewBluetoothHandleMethod *method = hidPackageDescriptor.newBluetoothHandle;
+
+  if (!method) {
+    logUnsupportedOperation("hidOpenBluetoothDevice");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return hidNewDevice(method(filter), &hidBluetoothBusMethods);
+}
+
+void
+hidInitializeFilter (HidFilter *filter) {
+  memset(filter, 0, sizeof(*filter));
+}
+
+int
+hidSetFilterIdentifiers (
+  HidFilter *filter, const char *vendor, const char *product
+) {
+  typedef struct {
+    const char *name;
+    const char *operand;
+    HidDeviceIdentifier *identifier;
+  } IdentifierEntry;
+
+  const IdentifierEntry identifierTable[] = {
+    { .name = "vendor",
+      .operand = vendor,
+      .identifier = &filter->common.vendorIdentifier,
+    },
+
+    { .name = "product",
+      .operand = product,
+      .identifier = &filter->common.productIdentifier,
+    },
+  };
+
+  const IdentifierEntry *cur = identifierTable;
+  const IdentifierEntry *end = cur + ARRAY_COUNT(identifierTable);
+
+  while (cur < end) {
+    if (cur->operand && *cur->operand) {
+      if (!hidParseDeviceIdentifier(cur->identifier, cur->operand)) {
+        logMessage(LOG_ERR, "invalid %s identifier: %s", cur->name, cur->operand);
+        return 0;
+      }
+    }
+
+    cur += 1;
+  }
+
+  return 1;
+}
+
+static int
+hidCopyStringFilter (const void *from, void *to) {
+  const char *fromString = from;
+  if (!fromString) return 0;
+  if (!*fromString) return 0;
+
+  const char **toString = to;
+  *toString = fromString;
+  return 1;
+}
+
+static int
+hidCopyIdentifierFilter (const void *from, void *to) {
+  const HidDeviceIdentifier *fromIdentifier = from;
+  if (!*fromIdentifier) return 0;
+
+  HidDeviceIdentifier *toIdentifier = to;
+  *toIdentifier = *fromIdentifier;
+  return 1;
+}
+
+static int
+hidTestMacAddress (const void *from) {
+  const char *address = from;
+  const char *byte = address;
+
+  unsigned int state = 0;
+  unsigned int octets = 0;
+  const char *digits = "0123456789ABCDEFabcdef";
+
+  while (*byte) {
+    if (!state) octets += 1;
+
+    if (++state < 3) {
+      if (!strchr(digits, *byte)) return 0;
+    } else {
+      if (*byte != ':') return 0;
+      state = 0;
+    }
+
+    byte += 1;
+  }
+
+  return (octets == 6) && (state == 2);
+}
+
+int
+hidOpenDeviceWithFilter (HidDevice **device, const HidFilter *filter) {
+  unsigned char wantUSB = filter->flags.wantUSB;
+  unsigned char wantBluetooth = filter->flags.wantBluetooth;
+
+  HidCommonProperties common;
+  memset(&common, 0, sizeof(common));
+
+  HidUSBProperties usb;
+  memset(&usb, 0, sizeof(usb));
+
+  HidBluetoothProperties bluetooth;
+  memset(&bluetooth, 0, sizeof(bluetooth));
+
+  typedef struct {
+    const char *name;
+    unsigned char *flag;
+    int (*copy) (const void *from, void *to);
+    int (*test) (const void *from);
+    const void *from;
+    void *to;
+  } FilterEntry;
+
+  const FilterEntry filterTable[] = {
+    { .name = "vendor identifier",
+      .copy = hidCopyIdentifierFilter,
+      .from = &filter->common.vendorIdentifier,
+      .to = &common.vendorIdentifier,
+    },
+
+    { .name = "product identifier",
+      .copy = hidCopyIdentifierFilter,
+      .from = &filter->common.productIdentifier,
+      .to = &common.productIdentifier,
+    },
+
+    { .name = "manufacturer name",
+      .copy = hidCopyStringFilter,
+      .from = filter->usb.manufacturerName,
+      .to = &usb.manufacturerName,
+      .flag = &wantUSB,
+    },
+
+    { .name = "product description",
+      .copy = hidCopyStringFilter,
+      .from = filter->usb.productDescription,
+      .to = &usb.productDescription,
+      .flag = &wantUSB,
+    },
+
+    { .name = "serial number",
+      .copy = hidCopyStringFilter,
+      .from = filter->usb.serialNumber,
+      .to = &usb.serialNumber,
+      .flag = &wantUSB,
+    },
+
+    { .name = "MAC address",
+      .copy = hidCopyStringFilter,
+      .test = hidTestMacAddress,
+      .from = filter->bluetooth.macAddress,
+      .to = &bluetooth.macAddress,
+      .flag = &wantBluetooth,
+    },
+
+    { .name = "device name",
+      .copy = hidCopyStringFilter,
+      .from = filter->bluetooth.deviceName,
+      .to = &bluetooth.deviceName,
+      .flag = &wantBluetooth,
+    },
+  };
+
+  const FilterEntry *cur = filterTable;
+  const FilterEntry *end = cur + ARRAY_COUNT(filterTable);
+
+  while (cur < end) {
+    if (cur->copy(cur->from, cur->to)) {
+      if (cur->flag) *cur->flag = 1;
+
+      if (cur->test) {
+        if (!cur->test(cur->from)) {
+          const char *operand = cur->from;
+          logMessage(LOG_ERR, "invalid %s: %s", cur->name, operand);
+          return 0;
+        }
+      }
+    }
+
+    cur += 1;
+  }
+
+  if (wantUSB && wantBluetooth) {
+    logMessage(LOG_ERR, "conflicting filter options");
+    return 0;
+  }
+
+  if (wantBluetooth) {
+    HidBluetoothFilter hbf = {
+      .common = common,
+      .bluetooth = bluetooth,
+    };
+    *device = hidOpenBluetoothDevice(&hbf);
+  } else {
+    HidUSBFilter huf = {
+      .common = common,
+      .usb = usb,
+    };
+
+    *device = hidOpenUSBDevice(&huf);
+  }
+
+  return 1;
+}
+
+int
+hidOpenDeviceWithParameters (HidDevice **device, const char *string) {
+  char **parameters = hidGetDeviceParameters(string);
+
+  if (parameters) {
+    HidFilter filter = {
+      .usb = {
+        .manufacturerName = parameters[HID_PARM_MANUFACTURER],
+        .productDescription = parameters[HID_PARM_DESCRIPTION],
+        .serialNumber = parameters[HID_PARM_SERIAL_NUMBER],
+      },
+
+      .bluetooth = {
+        .macAddress = parameters[HID_PARM_ADDRESS],
+        .deviceName = parameters[HID_PARM_NAME],
+      },
+    };
+
+    int ok = hidSetFilterIdentifiers(
+      &filter,
+      parameters[HID_PARM_VENDOR],
+      parameters[HID_PARM_PRODUCT]
+    );
+
+    if (ok) ok = hidOpenDeviceWithFilter(device, &filter);
+    deallocateStrings(parameters);
+    if (ok) return 1;
+  }
+
+  return 0;
+}
+
+void
+hidCloseDevice (HidDevice *device) {
+  hidDestroyHandle(device->handle);
+  free(device);
+}
+
+const HidItemsDescriptor *
+hidGetItems (HidDevice *device) {
+  HidGetItemsMethod *method = device->handleMethods->getItems;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetItems");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  return method(device->handle);
+}
+
+int
+hidGetReportSize (
+  HidDevice *device,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+) {
+  HidGetReportSizeMethod *method = device->handleMethods->getReportSize;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetReportSize");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  return method(device->handle, identifier, size);
+}
+
+static void
+hidLogDataTransfer (const char *action, const unsigned char *data, size_t size, HidReportIdentifier identifier) {
+  logBytes(LOG_CATEGORY(HID_IO),
+    "%s: %02X", data, size, action, identifier
+  );
+}
+
+ssize_t
+hidGetReport (HidDevice *device, unsigned char *buffer, size_t size) {
+  HidGetReportMethod *method = device->handleMethods->getReport;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetReport");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  HidReportIdentifier identifier = *buffer;
+  ssize_t result = method(device->handle, buffer, size);
+
+  if (result != -1) {
+    hidLogDataTransfer("get report", buffer, result, identifier);
+  }
+
+  return result;
+}
+
+ssize_t
+hidSetReport (HidDevice *device, const unsigned char *report, size_t size) {
+  HidSetReportMethod *method = device->handleMethods->setReport;
+
+  if (!method) {
+    logUnsupportedOperation("hidSetReport");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  hidLogDataTransfer("set report", report+1, size-1, *report);
+  return method(device->handle, report, size);
+}
+
+ssize_t
+hidGetFeature (HidDevice *device, unsigned char *buffer, size_t size) {
+  HidGetFeatureMethod *method = device->handleMethods->getFeature;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetFeature");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  HidReportIdentifier identifier = *buffer;
+  ssize_t result = method(device->handle, buffer, size);
+
+  if (result != -1) {
+    hidLogDataTransfer("get feature", buffer, result, identifier);
+  }
+
+  return result;
+}
+
+ssize_t
+hidSetFeature (HidDevice *device, const unsigned char *feature, size_t size) {
+  HidSetFeatureMethod *method = device->handleMethods->setFeature;
+
+  if (!method) {
+    logUnsupportedOperation("hidSetFeature");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  hidLogDataTransfer("set feature", feature+1, size-1, *feature);
+  return method(device->handle, feature, size);
+}
+
+int
+hidWriteData (HidDevice *device, const unsigned char *data, size_t size) {
+  HidWriteDataMethod *method = device->handleMethods->writeData;
+
+  if (!method) {
+    logUnsupportedOperation("hidWriteData");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  logBytes(LOG_CATEGORY(HID_IO),
+    "output", data, size
+  );
+
+  return method(device->handle, data, size);
+}
+
+int
+hidMonitorInput (HidDevice *device, AsyncMonitorCallback *callback, void *data) {
+  HidMonitorInputMethod *method = device->handleMethods->monitorInput;
+
+  if (!method) {
+    logUnsupportedOperation("hidMonitorInput");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  return method(device->handle, callback, data);
+}
+
+int
+hidAwaitInput (HidDevice *device, int timeout) {
+  HidAwaitInputMethod *method = device->handleMethods->awaitInput;
+
+  if (!method) {
+    logUnsupportedOperation("hidAwaitInput");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  return method(device->handle, timeout);
+}
+
+ssize_t
+hidReadData (
+  HidDevice *device, unsigned char *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  HidReadDataMethod *method = device->handleMethods->readData;
+
+  if (!method) {
+    logUnsupportedOperation("hidReadData");
+    errno = ENOSYS;
+    return -1;
+  }
+
+  ssize_t result = method(device->handle, buffer, size, initialTimeout, subsequentTimeout);
+
+  if (result != -1) {
+    if (result > 0) {
+      logBytes(LOG_CATEGORY(HID_IO),
+        "input", buffer, result
+      );
+    }
+  }
+
+  return result;
+}
+
+int
+hidGetDeviceIdentifiers (HidDevice *device, HidDeviceIdentifier *vendor, HidDeviceIdentifier *product) {
+  HidGetDeviceIdentifiersMethod *method = device->handleMethods->getDeviceIdentifiers;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetDeviceIdentifiers");
+    errno = ENOSYS;
+    return 0;
+  }
+
+  return method(device->handle, vendor, product);
+}
+
+const char *
+hidGetDeviceAddress (HidDevice *device) {
+  HidGetDeviceAddressMethod *method = device->handleMethods->getDeviceAddress;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetDeviceAddress");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return method(device->handle);
+}
+
+const char *
+hidGetDeviceName (HidDevice *device) {
+  HidGetDeviceNameMethod *method = device->handleMethods->getDeviceName;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetDeviceName");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return method(device->handle);
+}
+
+const char *
+hidGetHostPath (HidDevice *device) {
+  HidGetHostPathMethod *method = device->handleMethods->getHostPath;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetHostPath");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return method(device->handle);
+}
+
+const char *
+hidGetHostDevice (HidDevice *device) {
+  HidGetHostDeviceMethod *method = device->handleMethods->getHostDevice;
+
+  if (!method) {
+    logUnsupportedOperation("hidGetHostDevice");
+    errno = ENOSYS;
+    return NULL;
+  }
+
+  return method(device->handle);
+}
+
+const char *
+hidCacheString (
+  HidHandle *handle, char **cachedValue,
+  char *buffer, size_t size,
+  HidGetStringMethod *getString, void *data
+) {
+  if (!*cachedValue) {
+    if (!getString(handle, buffer, size, data)) return NULL;
+    char *value = strdup(buffer);
+
+    if (!value) {
+      logMallocError();
+      return NULL;
+    }
+
+    *cachedValue = value;
+  }
+
+  return *cachedValue;
+}
+
+const char *
+hidMakeDeviceIdentifier (HidDevice *device, char *buffer, size_t size) {
+  size_t length;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s%c", HID_DEVICE_QUALIFIER, PARAMETER_QUALIFIER_CHARACTER);
+
+  {
+    HidDeviceIdentifier vendor;
+    HidDeviceIdentifier product;
+
+    if (hidGetDeviceIdentifiers(device, &vendor, &product)) {
+      if (vendor) {
+        STR_PRINTF(
+          "%s%c%04X%c",
+          hidDeviceParameterNames[HID_PARM_VENDOR],
+          PARAMETER_ASSIGNMENT_CHARACTER,
+          vendor, DEVICE_PARAMETER_SEPARATOR
+        );
+      }
+
+      if (product) {
+        STR_PRINTF(
+          "%s%c%04X%c",
+          hidDeviceParameterNames[HID_PARM_PRODUCT],
+          PARAMETER_ASSIGNMENT_CHARACTER,
+          product, DEVICE_PARAMETER_SEPARATOR
+        );
+      }
+    }
+  }
+
+  STR_FORMAT(device->busMethods->extendDeviceIdentifier, device);
+  length = STR_LENGTH;
+  STR_END;
+
+  {
+    char *last = &buffer[length] - 1;
+    if (*last == DEVICE_PARAMETER_SEPARATOR) *last = 0;
+  }
+
+  return buffer;
+}
+
+int
+isHidDeviceIdentifier (const char **identifier) {
+  return hasQualifier(identifier, HID_DEVICE_QUALIFIER);
+}
diff --git a/Programs/hid_braille.c b/Programs/hid_braille.c
new file mode 100644
index 0000000..30d2690
--- /dev/null
+++ b/Programs/hid_braille.c
@@ -0,0 +1,23 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "hid_items.h"
+#include "hid_braille.h"
diff --git a/Programs/hid_inspect.c b/Programs/hid_inspect.c
new file mode 100644
index 0000000..40bb6b6
--- /dev/null
+++ b/Programs/hid_inspect.c
@@ -0,0 +1,397 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "bitmask.h"
+#include "hid_defs.h"
+#include "hid_items.h"
+#include "hid_tables.h"
+#include "hid_inspect.h"
+
+static int
+hidCompareTableEntriesByValue (const void *element1, const void *element2) {
+  const HidTableEntryHeader *const *header1 = element1;
+  const HidTableEntryHeader *const *header2 = element2;
+
+  HidUnsignedValue value1 = (*header1)->value;
+  HidUnsignedValue value2 = (*header2)->value;
+
+  if (value1 < value2) return -1;
+  if (value1 > value2) return 1;
+  return 0;
+}
+
+const void *
+hidTableEntry (HidTable *table, HidUnsignedValue value) {
+  if (!table->sorted) {
+    if (!(table->sorted = malloc(ARRAY_SIZE(table->sorted, table->count)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    {
+      const void *entry = table->entries;
+      const HidTableEntryHeader **header = table->sorted;
+
+      for (unsigned int index=0; index<table->count; index+=1) {
+        *header++ = entry;
+        entry += table->size;
+      }
+    }
+
+    qsort(
+      table->sorted, table->count, sizeof(*table->sorted),
+      hidCompareTableEntriesByValue
+    );
+  }
+
+  unsigned int from = 0;
+  unsigned int to = table->count;
+
+  while (from < to) {
+    unsigned int current = (from + to) / 2;
+    const HidTableEntryHeader *header = table->sorted[current];
+    if (value == header->value) return header;
+
+    if (value < header->value) {
+      to = current;
+    } else {
+      from = current + 1;
+    }
+  }
+
+  return NULL;
+}
+
+static int
+hidCompareReportIdentifiers (const void *element1, const void *element2) {
+  const HidReportIdentifier *identifier1 = element1;
+  const HidReportIdentifier *identifier2 = element2;
+
+  if (*identifier1 < *identifier2) return -1;
+  if (*identifier1 > *identifier2) return 1;
+  return 0;
+}
+
+HidReports *
+hidGetReports (const HidItemsDescriptor *items) {
+  HidReportIdentifier identifiers[UINT8_MAX];
+  unsigned char count = 0;
+
+  BITMASK(haveIdentifier, UINT8_MAX+1, char);
+  BITMASK_ZERO(haveIdentifier);
+
+  const unsigned char *nextByte = items->bytes;
+  size_t bytesLeft = items->count;
+
+  while (1) {
+    HidItem item;
+    if (!hidNextItem(&item, &nextByte, &bytesLeft)) break;
+
+    switch (item.tag) {
+      case HID_ITM_ReportID: {
+        HidUnsignedValue identifier = item.value.u;
+
+        if (!identifier) continue;
+        if (identifier > UINT8_MAX) continue;
+        if (BITMASK_TEST(haveIdentifier, identifier)) continue;
+
+        BITMASK_SET(haveIdentifier, identifier);
+        identifiers[count++] = identifier;
+        break;
+      }
+
+      case HID_ITM_Input:
+      case HID_ITM_Output:
+      case HID_ITM_Feature:
+        if (!count) identifiers[count++] = 0;
+        break;
+    }
+  }
+
+  if (count > 1) {
+    qsort(
+      identifiers, count, sizeof(identifiers[0]),
+      hidCompareReportIdentifiers
+    );
+  }
+
+  HidReports *reports;
+  size_t size = sizeof(*reports);
+  size += count;
+
+  if ((reports = malloc(size))) {
+    memset(reports, 0, sizeof(*reports));
+
+    reports->count = count;
+    memcpy(reports->identifiers, identifiers, count);
+
+    return reports;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+STR_BEGIN_FORMATTER(hidFormatUsageFlags, HidUnsignedValue flags)
+  typedef struct {
+    const char *on;
+    const char *off;
+    HidUnsignedValue bit;
+  } FlagEntry;
+
+  static const FlagEntry flagTable[] = {
+    { .bit = HID_USG_FLG_CONSTANT,
+      .on = "const",
+      .off = "data"
+    },
+
+    { .bit = HID_USG_FLG_VARIABLE,
+      .on = "var",
+      .off = "array"
+    },
+
+    { .bit = HID_USG_FLG_RELATIVE,
+      .on = "rel",
+      .off = "abs"
+    },
+
+    { .bit = HID_USG_FLG_WRAP,
+      .on = "wrap",
+    },
+
+    { .bit = HID_USG_FLG_NON_LINEAR,
+      .on = "nonlin",
+    },
+
+    { .bit = HID_USG_FLG_NO_PREFERRED,
+      .on = "nopref",
+    },
+
+    { .bit = HID_USG_FLG_NULL_STATE,
+      .on = "null",
+    },
+
+    { .bit = HID_USG_FLG_VOLATILE,
+      .on = "volatile",
+    },
+
+    { .bit = HID_USG_FLG_BUFFERED_BYTE,
+      .on = "buffbyte",
+    },
+  };
+
+  const FlagEntry *flag = flagTable;
+  const FlagEntry *end = flag + ARRAY_COUNT(flagTable);
+
+  while (flag < end) {
+    const char *name = (flags & flag->bit)? flag->on: flag->off;
+
+    if (name) {
+      if (STR_LENGTH > 0) STR_PRINTF(" ");
+      STR_PRINTF("%s", name);
+    }
+
+    flag += 1;
+  }
+STR_END_FORMATTER
+
+static int
+hidListItem (const char *line, void *data) {
+  return logMessage((LOG_CATEGORY(HID_IO) | LOG_DEBUG), "%s", line);
+}
+
+int
+hidListItems (const HidItemsDescriptor *items, HidItemLister *listItem, void *data) {
+  if (!listItem) listItem = hidListItem;
+  const char *label = "Items List";
+
+  {
+    char line[0X40];
+    STR_BEGIN(line, sizeof(line));
+    STR_PRINTF("Begin %s: Bytes:%"PRIsize, label, items->count);
+    STR_END;
+    if (!listItem(line, data)) return 0;
+  }
+
+  unsigned int itemCount = 0;
+  const unsigned char *nextByte = items->bytes;
+  size_t bytesLeft = items->count;
+
+  int decOffsetWidth;
+  int hexOffsetWidth;
+  {
+    unsigned int maximumOffset = bytesLeft;
+    char buffer[0X20];
+
+    decOffsetWidth = snprintf(buffer, sizeof(buffer), "%u", maximumOffset);
+    hexOffsetWidth = snprintf(buffer, sizeof(buffer), "%x", maximumOffset);
+  }
+
+  HidUnsignedValue usagePage = 0;
+
+  while (1) {
+    unsigned int offset = nextByte - items->bytes;
+    HidItem item;
+    int ok = hidNextItem(&item, &nextByte, &bytesLeft);
+
+    char line[0X100];
+    STR_BEGIN(line, sizeof(line));
+
+    STR_PRINTF(
+      "Item: %*u (0X%.*X):",
+      decOffsetWidth, offset, hexOffsetWidth, offset
+    );
+
+    if (ok) {
+      itemCount += 1;
+
+      switch (item.tag) {
+        case HID_ITM_UsagePage:
+          usagePage = item.value.u;
+          break;
+      }
+
+      {
+        const HidItemTagEntry *tag = hidItemTagEntry(item.tag);
+
+        if (tag) {
+          STR_PRINTF(" %s", tag->header.name);
+        } else {
+          STR_PRINTF(" unknown item tag: 0X%02X", item.tag);
+        }
+      }
+
+      if (item.valueSize > 0) {
+        HidUnsignedValue hexValue = item.value.u & ((UINT64_C(1) << (item.valueSize * 8)) - 1);
+        int hexPrecision = item.valueSize * 2;
+
+        STR_PRINTF(
+          " = %" PRId32 " (0X%.*" PRIX32 ")",
+          item.value.s, hexPrecision, hexValue
+        );
+      }
+
+      {
+        HidUnsignedValue value = item.value.u;
+
+        char name[0X100];
+        STR_BEGIN(name, sizeof(name));
+
+        switch (item.tag) {
+          case HID_ITM_UsagePage: {
+            const HidUsagePageEntry *upg = hidUsagePageEntry(value);
+            if (upg) STR_PRINTF("%s", upg->header.name);
+            break;
+          }
+
+          case HID_ITM_UsageMinimum:
+          case HID_ITM_UsageMaximum:
+          case HID_ITM_Usage: {
+            HidUnsignedValue usage = item.value.u;
+
+            HidUnsignedValue page;
+            const HidUsagePageEntry *upg;
+
+            if (item.valueSize == 4) {
+              page = usage >> 0X10;
+              usage &= UINT16_MAX;
+            } else {
+              page = usagePage;
+            }
+
+            if ((upg = hidUsagePageEntry(page))) {
+              HidTable *utb = upg->usageTable;
+
+              if (utb) {
+                const HidUsageEntryHeader *usg = hidTableEntry(utb, usage);
+
+                if (usg) {
+                  STR_PRINTF("%s", usg->header.name);
+
+                  {
+                    const HidUsageTypeEntry *type = hidUsageTypeEntry(usg->usageType);
+                    if (type) STR_PRINTF(" (%s)", type->header.name);
+                  }
+                }
+              }
+            }
+
+            if (page != usagePage) {
+              if (*name) STR_PRINTF(" ");
+              STR_PRINTF("[");
+
+              if (upg) {
+                STR_PRINTF("%s", upg->header.name);
+              } else {
+                STR_PRINTF("0X%02"PRIX32, page);
+              }
+
+              STR_PRINTF("]");
+            }
+
+            break;
+          }
+
+          case HID_ITM_Collection: {
+            const HidCollectionTypeEntry *col = hidCollectionTypeEntry(value);
+            if (col) STR_PRINTF("%s", col->header.name);
+            break;
+          }
+
+          case HID_ITM_Input:
+          case HID_ITM_Output:
+          case HID_ITM_Feature: {
+            STR_FORMAT(hidFormatUsageFlags, value);
+            break;
+          }
+        }
+
+        STR_END;
+        if (*name) STR_PRINTF(": %s", name);
+      }
+    } else if (bytesLeft) {
+      STR_PRINTF(" incomplete:");
+      const unsigned char *end = nextByte + bytesLeft;
+
+      while (nextByte < end) {
+        STR_PRINTF(" %02X", *nextByte++);
+      }
+    } else {
+      STR_PRINTF(" end");
+    }
+
+    STR_END;
+    if (!listItem(line, data)) return 0;
+    if (!ok) break;
+  }
+
+  {
+    char line[0X40];
+    STR_BEGIN(line, sizeof(line));
+    STR_PRINTF("End %s: Items:%u", label, itemCount);
+    STR_END;
+    return listItem(line, data);
+  }
+}
diff --git a/Programs/hid_internal.h b/Programs/hid_internal.h
new file mode 100644
index 0000000..d730136
--- /dev/null
+++ b/Programs/hid_internal.h
@@ -0,0 +1,113 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HID_INTERNAL
+#define BRLTTY_INCLUDED_HID_INTERNAL
+
+#include "hid_types.h"
+#include "async_types_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct HidHandleStruct HidHandle;
+typedef void HidDestroyHandleMethod (HidHandle *handle);
+
+typedef const HidItemsDescriptor *HidGetItemsMethod (HidHandle *handle);
+
+typedef int HidGetReportSizeMethod (
+  HidHandle *handle,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+);
+
+typedef ssize_t HidGetReportMethod (HidHandle *handle, unsigned char *buffer, size_t size);
+typedef ssize_t HidSetReportMethod (HidHandle *handle, const unsigned char *report, size_t size);
+typedef ssize_t HidGetFeatureMethod (HidHandle *handle, unsigned char *buffer, size_t size);
+typedef ssize_t HidSetFeatureMethod (HidHandle *handle, const unsigned char *feature, size_t size);
+
+typedef int HidWriteDataMethod (HidHandle *handle, const unsigned char *data, size_t size);
+typedef int HidMonitorInputMethod (HidHandle *handle, AsyncMonitorCallback *callback, void *data);
+typedef int HidAwaitInputMethod (HidHandle *handle, int timeout);
+
+typedef ssize_t HidReadDataMethod (
+  HidHandle *handle, unsigned char *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+typedef int HidGetDeviceIdentifiersMethod (HidHandle *handle, uint16_t *vendor, uint16_t *product);
+typedef const char *HidGetDeviceAddressMethod (HidHandle *handle);
+typedef const char *HidGetDeviceNameMethod (HidHandle *handle);
+typedef const char *HidGetHostPathMethod (HidHandle *handle);
+typedef const char *HidGetHostDeviceMethod (HidHandle *handle);
+
+typedef struct {
+  HidDestroyHandleMethod *destroyHandle;
+
+  HidGetItemsMethod *getItems;
+
+  HidGetReportSizeMethod *getReportSize;
+  HidGetReportMethod *getReport;
+  HidSetReportMethod *setReport;
+  HidGetFeatureMethod *getFeature;
+  HidSetFeatureMethod *setFeature;
+
+  HidWriteDataMethod *writeData;
+  HidMonitorInputMethod *monitorInput;
+  HidAwaitInputMethod *awaitInput;
+  HidReadDataMethod *readData;
+
+  HidGetDeviceIdentifiersMethod *getDeviceIdentifiers;
+  HidGetDeviceAddressMethod *getDeviceAddress;
+  HidGetDeviceNameMethod *getDeviceName;
+  HidGetHostPathMethod *getHostPath;
+  HidGetHostDeviceMethod *getHostDevice;
+} HidHandleMethods;
+
+typedef HidHandle *HidNewUSBHandleMethod (const HidUSBFilter *filter);
+typedef HidHandle *HidNewBluetoothHandleMethod (const HidBluetoothFilter *filter);
+
+typedef struct {
+  const char *packageName;
+  const HidHandleMethods *handleMethods;
+
+  HidNewUSBHandleMethod *newUSBHandle;
+  HidNewBluetoothHandleMethod *newBluetoothHandle;
+} HidPackageDescriptor;
+
+extern const HidPackageDescriptor hidPackageDescriptor;
+
+extern int hidParseDeviceIdentifier (HidDeviceIdentifier *identifier, const char *string);
+extern int hidMatchString (const char *actualString, const char *testString);
+
+typedef int HidGetStringMethod (
+  HidHandle *handle, char *buffer, size_t size, void *data
+);
+
+extern const char *hidCacheString (
+  HidHandle *handle, char **cachedValue,
+  char *buffer, size_t size,
+  HidGetStringMethod *getString, void *data
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HID_INTERNAL */
diff --git a/Programs/hid_items.c b/Programs/hid_items.c
new file mode 100644
index 0000000..e4558c8
--- /dev/null
+++ b/Programs/hid_items.c
@@ -0,0 +1,219 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "hid_items.h"
+#include "hid_defs.h"
+#include "hid_tables.h"
+
+unsigned char
+hidItemValueSize (unsigned char item) {
+  static const unsigned char sizes[4] = {0, 1, 2, 4};
+  return sizes[HID_ITEM_SIZE(item)];
+}
+
+int
+hidNextItem (
+  HidItem *item,
+  const unsigned char **bytes,
+  size_t *count
+) {
+  if (!*count) return 0;
+
+  const unsigned char *byte = *bytes;
+  const unsigned char *endBytes = byte + *count;
+
+  unsigned char itemTag = HID_ITEM_TAG(*byte);
+  unsigned char valueSize = hidItemValueSize(*byte);
+
+  const unsigned char *endValue = ++byte + valueSize;
+  if (endValue > endBytes) return 0;
+
+  item->tag = itemTag;
+  item->valueSize = valueSize;
+  item->value.u = 0;
+
+  {
+    unsigned char shift = 0;
+
+    while (byte < endValue) {
+      item->value.u |= *byte++ << shift;
+      shift += 8;
+    }
+
+    if (hidHasSignedValue(item->tag)) {
+      shift = 0X20 - shift;
+      item->value.u <<= shift;
+      item->value.s >>= shift;
+    }
+  }
+
+  *bytes = byte;
+  *count = endBytes - byte;
+  return 1;
+}
+
+int
+hidReportSize (
+  const HidItemsDescriptor *items,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+) {
+  const unsigned char *nextByte = items->bytes;
+  size_t bytesLeft = items->count;
+
+  int noIdentifier = !identifier;
+  int reportFound = noIdentifier;
+
+  size_t inputSize = 0;
+  size_t outputSize = 0;
+  size_t featureSize = 0;
+
+  uint64_t itemTagsEncountered = 0;
+  HidUnsignedValue reportIdentifier = 0;
+  HidUnsignedValue reportSize = 0;
+  HidUnsignedValue reportCount = 0;
+
+  while (bytesLeft) {
+    size_t offset = nextByte - items->bytes;
+    HidItem item;
+
+    if (!hidNextItem(&item, &nextByte, &bytesLeft)) {
+      if (bytesLeft) return 0;
+      break;
+    }
+
+    if (item.tag == HID_ITM_ReportID) {
+      if (noIdentifier) {
+        reportFound = 0;
+        break;
+      }
+
+      reportIdentifier = item.value.u;
+      if (reportIdentifier == identifier) reportFound = 1;
+    } else {
+      switch (item.tag) {
+      {
+        size_t *size;
+
+        case HID_ITM_Input:
+          size = &inputSize;
+          goto doSize;
+
+        case HID_ITM_Output:
+          size = &outputSize;
+          goto doSize;
+
+        case HID_ITM_Feature:
+          size = &featureSize;
+          goto doSize;
+
+        doSize:
+          if (reportIdentifier == identifier) *size += reportSize * reportCount;
+          break;
+      }
+
+        case HID_ITM_ReportCount:
+          reportCount = item.value.u;
+          break;
+
+        case HID_ITM_ReportSize:
+          reportSize = item.value.u;
+          break;
+
+        case HID_ITM_Collection:
+        case HID_ITM_EndCollection:
+        case HID_ITM_UsagePage:
+        case HID_ITM_UsageMinimum:
+        case HID_ITM_UsageMaximum:
+        case HID_ITM_Usage:
+        case HID_ITM_LogicalMinimum:
+        case HID_ITM_LogicalMaximum:
+        case HID_ITM_PhysicalMinimum:
+        case HID_ITM_PhysicalMaximum:
+          break;
+
+        default: {
+          if (!(itemTagsEncountered & HID_ITEM_TAG_BIT(item.tag))) {
+            logMessage(LOG_CATEGORY(HID_IO),
+              "unhandled item tag at offset %"PRIsize ": 0X%02X",
+              offset, item.tag
+            );
+          }
+
+          break;
+        }
+      }
+    }
+
+    itemTagsEncountered |= HID_ITEM_TAG_BIT(item.tag);
+  }
+
+  if (reportFound) {
+    char log[0X100];
+    STR_BEGIN(log, sizeof(log));
+    STR_PRINTF("report size: %02X", identifier);
+
+    {
+      typedef struct {
+        const char *label;
+        size_t *bytes;
+        size_t bits;
+      } SizeEntry;
+
+      SizeEntry sizeTable[] = {
+        { .label = "In",
+          .bits = inputSize,
+          .bytes = &size->input
+        },
+
+        { .label = "Out",
+          .bits = outputSize,
+          .bytes = &size->output
+        },
+
+        { .label = "Ftr",
+          .bits = featureSize,
+          .bytes = &size->feature
+        },
+      };
+
+      SizeEntry *entry = sizeTable;
+      const SizeEntry *end = entry + ARRAY_COUNT(sizeTable);
+
+      while (entry < end) {
+        size_t bytes = (entry->bits + 7) / 8;
+        if (bytes && !noIdentifier) bytes += 1;
+        *entry->bytes = bytes;
+
+        STR_PRINTF(" %s:%" PRIsize, entry->label, bytes);
+        entry += 1;
+      }
+    }
+
+    STR_END;
+    logMessage(LOG_CATEGORY(HID_IO), "%s", log);
+  }
+
+  return reportFound;
+}
diff --git a/Programs/hid_linux.c b/Programs/hid_linux.c
new file mode 100644
index 0000000..9105c37
--- /dev/null
+++ b/Programs/hid_linux.c
@@ -0,0 +1,544 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <libudev.h>
+#include <linux/hidraw.h>
+#include <linux/input.h>
+
+#include "log.h"
+#include "hid_types.h"
+#include "hid_internal.h"
+#include "hid_items.h"
+#include "io_misc.h"
+#include "async_handle.h"
+#include "async_io.h"
+
+struct HidHandleStruct {
+  char *sysfsPath;
+  char *devicePath;
+
+  int fileDescriptor;
+  AsyncHandle inputMonitor;
+  struct hidraw_devinfo deviceInformation;
+
+  HidItemsDescriptor *hidItems;
+  char *deviceAddress;
+  char *deviceName;
+  char *hostPath;
+
+  char strings[];
+};
+
+static void
+hidLinuxCancelInputMonitor (HidHandle *handle) {
+  if (handle->inputMonitor) {
+    asyncCancelRequest(handle->inputMonitor);
+    handle->inputMonitor = NULL;
+  }
+}
+
+static void
+hidLinuxDestroyHandle (HidHandle *handle) {
+  hidLinuxCancelInputMonitor(handle);
+  close(handle->fileDescriptor);
+
+  if (handle->hidItems) free(handle->hidItems);
+  if (handle->deviceAddress) free(handle->deviceAddress);
+  if (handle->deviceName) free(handle->deviceName);
+  if (handle->hostPath) free(handle->hostPath);
+
+  free(handle);
+}
+
+static const HidItemsDescriptor *
+hidLinuxGetItems (HidHandle *handle) {
+  if (handle->hidItems) return handle->hidItems;
+  int size;
+
+  if (ioctl(handle->fileDescriptor, HIDIOCGRDESCSIZE, &size) != -1) {
+    struct hidraw_report_descriptor descriptor = {
+      .size = size
+    };
+
+    if (ioctl(handle->fileDescriptor, HIDIOCGRDESC, &descriptor) != -1) {
+      HidItemsDescriptor *items;
+
+      if ((items = malloc(sizeof(*items) + size))) {
+        memset(items, 0, sizeof(*items));
+        items->count = size;
+        memcpy(items->bytes, descriptor.value, size);
+        return (handle->hidItems = items);
+      } else {
+        logMallocError();
+      }
+    } else {
+      logSystemError("ioctl[HIDIOCGRDESC]");
+    }
+  } else {
+    logSystemError("ioctl[HIDIOCGRDESCSIZE]");
+  }
+
+  return NULL;
+}
+
+static int
+hidLinuxGetReportSize (
+  HidHandle *handle,
+  HidReportIdentifier identifier,
+  HidReportSize *size
+) {
+  const HidItemsDescriptor *items = hidLinuxGetItems(handle);
+  if (!items) return 0;
+  return hidReportSize(items, identifier, size);
+}
+
+static ssize_t
+hidLinuxGetReport (HidHandle *handle, unsigned char *buffer, size_t size) {
+  int length;
+
+#ifdef HIDIOCGINPUT
+  length = ioctl(handle->fileDescriptor, HIDIOCGINPUT(size), buffer);
+#else /* HIDIOCGINPUT */
+  length = -1;
+  errno = ENOSYS;
+#endif /* HIDIOCGINPUT */
+
+  if (length == -1) logSystemError("ioctl[HIDIOCGINPUT]");
+  return length;
+}
+
+static ssize_t
+hidLinuxSetReport (HidHandle *handle, const unsigned char *report, size_t size) {
+  int count;
+
+#ifdef HIDIOCSOUTPUT
+  count = ioctl(handle->fileDescriptor, HIDIOCSOUTPUT(size), report);
+#else /* HIDIOCSOUTPUT */
+  count = write(handle->fileDescriptor, report, size);
+#endif /* HIDIOCSOUTPUT */
+
+  if (count == -1) logSystemError("ioctl[HIDIOCSOUTPUT]");
+  return count;
+}
+
+static ssize_t
+hidLinuxGetFeature (HidHandle *handle, unsigned char *buffer, size_t size) {
+  int result = ioctl(handle->fileDescriptor, HIDIOCGFEATURE(size), buffer);
+
+  if (result == -1) {
+    logSystemError("ioctl[HIDIOCGFEATURE]");
+  }
+
+  return result;
+}
+
+static ssize_t
+hidLinuxSetFeature (HidHandle *handle, const unsigned char *feature, size_t size) {
+  int count = ioctl(handle->fileDescriptor, HIDIOCSFEATURE(size), feature);
+  if (count == -1) logSystemError("ioctl[HIDIOCSFEATURE]");
+  return count;
+}
+
+static int
+hidLinuxWriteData (HidHandle *handle, const unsigned char *data, size_t size) {
+  return writeFile(handle->fileDescriptor, data, size);
+}
+
+static int
+hidLinuxMonitorInput (HidHandle *handle, AsyncMonitorCallback *callback, void *data) {
+  hidLinuxCancelInputMonitor(handle);
+  if (!callback) return 1;
+  return asyncMonitorFileInput(&handle->inputMonitor, handle->fileDescriptor, callback, data);
+}
+
+static int
+hidLinuxAwaitInput (HidHandle *handle, int timeout) {
+  return awaitFileInput(handle->fileDescriptor, timeout);
+}
+
+static ssize_t
+hidLinuxReadData (
+  HidHandle *handle, unsigned char *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return readFile(handle->fileDescriptor, buffer, size, initialTimeout, subsequentTimeout);
+}
+
+static int
+hidLinuxGetDeviceIdentifiers (HidHandle *handle, HidDeviceIdentifier *vendor, HidDeviceIdentifier *product) {
+  if (vendor) *vendor = handle->deviceInformation.vendor;
+  if (product) *product = handle->deviceInformation.product;
+  return 1;
+}
+
+static int
+hidLinuxGetRawName (HidHandle *handle, char *buffer, size_t size, void *data) {
+  // For USB, this will be the manufacturer string, a space, and the product string.
+  // For Bluetooth, this will be the name of the device.
+  int length = ioctl(handle->fileDescriptor, HIDIOCGRAWNAME(size), buffer);
+
+  if (length == -1) {
+    logSystemError("ioctl[HIDIOCGRAWNAME]");
+    length = 0;
+  } else if (length == size) {
+    length -= 1;
+  }
+
+  buffer[length] = 0;
+  return !!length;
+}
+
+static int
+hidLinuxGetRawPhysical (HidHandle *handle, char *buffer, size_t size, void *data) {
+  // For USB, this will be the physical path (controller, hubs, ports, etc) to the device.
+  // For Bluetooth, this will be the address of the host controller.
+  int length = ioctl(handle->fileDescriptor, HIDIOCGRAWPHYS(size), buffer);
+
+  if (length == -1) {
+    logSystemError("ioctl[HIDIOCGRAWPHYS]");
+    length = 0;
+  } else if (length == size) {
+    length -= 1;
+  }
+
+  buffer[length] = 0;
+  return !!length;
+}
+
+static int
+hidLinuxGetRawUnique (HidHandle *handle, char *buffer, size_t size, void *data) {
+  // For USB, this will be the serial number of the device.
+  // For Bluetooth, this will be the MAC (hardware) address of the device.
+  int length;
+
+#ifdef HIDIOCGRAWUNIQ
+  length = ioctl(handle->fileDescriptor, HIDIOCGRAWUNIQ(size), buffer);
+#else /* HIDIOCGRAWUNIQ */
+  length = -1;
+  errno = ENOSYS;
+#endif /* HIDIOCGRAWUNIQ */
+
+  if (length == -1) {
+    logSystemError("ioctl[HIDIOCGRAWUNIQ]");
+    length = 0;
+  } else if (length == size) {
+    length -= 1;
+  }
+
+  buffer[length] = 0;
+  return !!length;
+}
+
+static const char *
+hidLinuxGetDeviceAddress (HidHandle *handle) {
+  char buffer[0X1000];
+
+  return hidCacheString(
+    handle, &handle->deviceAddress,
+    buffer, sizeof(buffer),
+    hidLinuxGetRawUnique, NULL
+  );
+}
+
+static const char *
+hidLinuxGetDeviceName (HidHandle *handle) {
+  char buffer[0X1000];
+
+  return hidCacheString(
+    handle, &handle->deviceName,
+    buffer, sizeof(buffer),
+    hidLinuxGetRawName, NULL
+  );
+}
+
+static const char *
+hidLinuxGetHostPath (HidHandle *handle) {
+  char buffer[0X1000];
+
+  return hidCacheString(
+    handle, &handle->hostPath,
+    buffer, sizeof(buffer),
+    hidLinuxGetRawPhysical, NULL
+  );
+}
+
+static const char *
+hidLinuxGetHostDevice (HidHandle *handle) {
+  return handle->devicePath;
+}
+
+static const HidHandleMethods hidLinuxHandleMethods = {
+  .destroyHandle = hidLinuxDestroyHandle,
+
+  .getItems = hidLinuxGetItems,
+
+  .getReportSize = hidLinuxGetReportSize,
+  .getReport = hidLinuxGetReport,
+  .setReport = hidLinuxSetReport,
+  .getFeature = hidLinuxGetFeature,
+  .setFeature = hidLinuxSetFeature,
+
+  .writeData = hidLinuxWriteData,
+  .monitorInput = hidLinuxMonitorInput,
+  .awaitInput = hidLinuxAwaitInput,
+  .readData = hidLinuxReadData,
+
+  .getDeviceIdentifiers = hidLinuxGetDeviceIdentifiers,
+  .getDeviceAddress = hidLinuxGetDeviceAddress,
+  .getDeviceName = hidLinuxGetDeviceName,
+  .getHostPath = hidLinuxGetHostPath,
+  .getHostDevice = hidLinuxGetHostDevice,
+};
+
+typedef int HidLinuxAttributeTester (
+  struct udev_device *device,
+  const char *name,
+  const void *value
+);
+
+static int
+hidLinuxTestString (
+  struct udev_device *device,
+  const char *name,
+  const void *value
+) {
+  const char *testString = value;
+  if (!testString) return 1;
+  if (!*testString) return 1;
+
+  const char *actualString = udev_device_get_sysattr_value(device, name);
+  if (!actualString) return 0;
+  if (!*actualString) return 0;
+
+  return hidMatchString(actualString, testString);
+}
+
+typedef struct {
+  const char *name;
+  const void *value;
+  HidLinuxAttributeTester *function;
+} HidLinuxAttributeTest;
+
+static int
+hidLinuxTestAttributes (
+  struct udev_device *device,
+  const HidLinuxAttributeTest *tests,
+  size_t testCount
+) {
+  const HidLinuxAttributeTest *test = tests;
+  const HidLinuxAttributeTest *end = test + testCount;
+
+  while (test < end) {
+    if (!test->function(device, test->name, test->value)) return 0;
+    test += 1;
+  }
+
+  return 1;
+}
+
+static HidHandle *
+hidLinuxNewHandle (struct udev_device *device) {
+  const char *sysPath = udev_device_get_syspath(device);
+  const char *devPath = udev_device_get_devnode(device);
+
+  size_t sysSize = strlen(sysPath) + 1;
+  size_t devSize = strlen(devPath) + 1;
+  HidHandle *handle = malloc(sizeof(*handle) + sysSize + devSize);
+
+  if (handle) {
+    memset(handle, 0, sizeof(*handle));
+
+    {
+      char *string = handle->strings;
+      string = mempcpy((handle->sysfsPath = string), sysPath, sysSize);
+      string = mempcpy((handle->devicePath = string), devPath, devSize);
+    }
+
+    if ((handle->fileDescriptor = open(devPath, (O_RDWR | O_NONBLOCK))) != -1) {
+      if (ioctl(handle->fileDescriptor, HIDIOCGRAWINFO, &handle->deviceInformation) != -1) {
+        return handle;
+      } else {
+        logSystemError("ioctl[HIDIOCGRAWINFO]");
+      }
+
+      close(handle->fileDescriptor);
+    } else {
+      logMessage(LOG_ERR, "device open error: %s: %s", devPath, strerror(errno));
+    }
+
+    free(handle);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+typedef int HidLinuxPropertiesTester (
+  HidHandle *handle,
+  struct udev_device *device,
+  const void *filter
+);
+
+static HidHandle *
+hidLinuxFindDevice (HidLinuxPropertiesTester *testProperties, const void *filter) {
+  HidHandle *handle = NULL;
+  struct udev *udev = udev_new();
+
+  if (udev) {
+    struct udev_enumerate *enumeration = udev_enumerate_new(udev);
+
+    if (enumeration) {
+      udev_enumerate_add_match_subsystem(enumeration, "hidraw");
+      udev_enumerate_scan_devices(enumeration);
+
+      struct udev_list_entry *deviceList = udev_enumerate_get_list_entry(enumeration);
+      struct udev_list_entry *deviceEntry;
+
+      udev_list_entry_foreach(deviceEntry, deviceList) {
+        const char *sysPath = udev_list_entry_get_name(deviceEntry);
+        struct udev_device *hidDevice = udev_device_new_from_syspath(udev, sysPath);
+
+        if (hidDevice) {
+          if ((handle = hidLinuxNewHandle(hidDevice))) {
+            if (!testProperties(handle, hidDevice, filter)) {
+              hidLinuxDestroyHandle(handle);
+              handle = NULL;
+            }
+          }
+
+          udev_device_unref(hidDevice);
+        }
+
+        if (handle) break;
+      }
+
+      udev_enumerate_unref(enumeration);
+      enumeration = NULL;
+    }
+
+    udev_unref(udev);
+    udev = NULL;
+  }
+
+  return handle;
+}
+
+static int
+hidLinuxTestCommonProperties (HidHandle *handle, const HidCommonProperties *common) {
+  if (common->vendorIdentifier) {
+    if (handle->deviceInformation.vendor != common->vendorIdentifier) {
+      return 0;
+    }
+  }
+
+  if (common->productIdentifier) {
+    if (handle->deviceInformation.product != common->productIdentifier) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+hidLinuxTestUSBProperties (HidHandle *handle, struct udev_device *hidDevice, const void *filter) {
+  if (handle->deviceInformation.bustype != BUS_USB) return 0;
+
+  const HidUSBFilter *huf = filter;
+  if (!hidLinuxTestCommonProperties(handle, &huf->common)) return 0;
+
+  struct udev_device *usbDevice = udev_device_get_parent_with_subsystem_devtype(hidDevice, "usb", "usb_device");
+  if (!usbDevice) return 0;
+  const HidUSBProperties *test = &huf->usb;
+
+  const HidLinuxAttributeTest tests[] = {
+    { .name = "manufacturer",
+      .value = test->manufacturerName,
+      .function = hidLinuxTestString
+    },
+
+    { .name = "product",
+      .value = test->productDescription,
+      .function = hidLinuxTestString
+    },
+
+    { .name = "serial",
+      .value = test->serialNumber,
+      .function = hidLinuxTestString
+    },
+  };
+
+  return hidLinuxTestAttributes(usbDevice, tests, ARRAY_COUNT(tests));
+}
+
+static HidHandle *
+hidLinuxNewUSBHandle (const HidUSBFilter *filter) {
+  return hidLinuxFindDevice(hidLinuxTestUSBProperties, filter);
+}
+
+static int
+hidLinuxTestBluetoothProperties (HidHandle *handle, struct udev_device *hidDevice, const void *filter) {
+  if (handle->deviceInformation.bustype != BUS_BLUETOOTH) return 0;
+
+  const HidBluetoothFilter *hbf = filter;
+  if (!hidLinuxTestCommonProperties(handle, &hbf->common)) return 0;
+  const HidBluetoothProperties *test = &hbf->bluetooth;
+
+  {
+    const char *testAddress = test->macAddress;
+
+    if (testAddress && *testAddress) {
+      const char *actualAddress = hidLinuxGetDeviceAddress(handle);
+      if (!actualAddress) return 0;
+      if (strcasecmp(actualAddress, testAddress) != 0) return 0;
+    }
+  }
+
+  {
+    const char *testName = test->deviceName;
+
+    if (testName && *testName) {
+      const char *actualName = hidLinuxGetDeviceName(handle);
+      if (!actualName) return 0;
+      if (!hidMatchString(actualName, testName)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static HidHandle *
+hidLinuxNewBluetoothHandle (const HidBluetoothFilter *filter) {
+  return hidLinuxFindDevice(hidLinuxTestBluetoothProperties, filter);
+}
+
+const HidPackageDescriptor hidPackageDescriptor = {
+  .packageName = "Linux",
+  .handleMethods = &hidLinuxHandleMethods,
+
+  .newUSBHandle = hidLinuxNewUSBHandle,
+  .newBluetoothHandle = hidLinuxNewBluetoothHandle,
+};
diff --git a/Programs/hid_none.c b/Programs/hid_none.c
new file mode 100644
index 0000000..6bbae5e
--- /dev/null
+++ b/Programs/hid_none.c
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "io_hid.h"
+#include "hid_internal.h"
+
+const HidPackageDescriptor hidPackageDescriptor = {
+  .packageName = "None"
+};
diff --git a/Programs/hid_tables.c b/Programs/hid_tables.c
new file mode 100644
index 0000000..ec3637d
--- /dev/null
+++ b/Programs/hid_tables.c
@@ -0,0 +1,532 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "hid_defs.h"
+#include "hid_tables.h"
+
+#define HID_ITEM_TAG_ENTRY(name) HID_TABLE_ENTRY(HID_ITM, name)
+HID_BEGIN_TABLE(ItemTag)
+  HID_ITEM_TAG_ENTRY(UsagePage),
+  HID_ITEM_TAG_ENTRY(Usage),
+  HID_ITEM_TAG_ENTRY(LogicalMinimum),
+  HID_ITEM_TAG_ENTRY(UsageMinimum),
+  HID_ITEM_TAG_ENTRY(LogicalMaximum),
+  HID_ITEM_TAG_ENTRY(UsageMaximum),
+  HID_ITEM_TAG_ENTRY(PhysicalMinimum),
+  HID_ITEM_TAG_ENTRY(DesignatorIndex),
+  HID_ITEM_TAG_ENTRY(PhysicalMaximum),
+  HID_ITEM_TAG_ENTRY(DesignatorMinimum),
+  HID_ITEM_TAG_ENTRY(UnitExponent),
+  HID_ITEM_TAG_ENTRY(DesignatorMaximum),
+  HID_ITEM_TAG_ENTRY(Unit),
+  HID_ITEM_TAG_ENTRY(ReportSize),
+  HID_ITEM_TAG_ENTRY(StringIndex),
+  HID_ITEM_TAG_ENTRY(Input),
+  HID_ITEM_TAG_ENTRY(ReportID),
+  HID_ITEM_TAG_ENTRY(StringMinimum),
+  HID_ITEM_TAG_ENTRY(Output),
+  HID_ITEM_TAG_ENTRY(ReportCount),
+  HID_ITEM_TAG_ENTRY(StringMaximum),
+  HID_ITEM_TAG_ENTRY(Collection),
+  HID_ITEM_TAG_ENTRY(Push),
+  HID_ITEM_TAG_ENTRY(Delimiter),
+  HID_ITEM_TAG_ENTRY(Feature),
+  HID_ITEM_TAG_ENTRY(Pop),
+  HID_ITEM_TAG_ENTRY(EndCollection),
+HID_END_TABLE(ItemTag)
+
+#define HID_COLLECTION_TYPE_ENTRY(name) HID_TABLE_ENTRY(HID_COL, name)
+HID_BEGIN_TABLE(CollectionType)
+  HID_COLLECTION_TYPE_ENTRY(Physical),
+  HID_COLLECTION_TYPE_ENTRY(Application),
+  HID_COLLECTION_TYPE_ENTRY(Logical),
+HID_END_TABLE(CollectionType)
+
+#define HID_USAGE_TYPE_ENTRY(name) HID_TABLE_ENTRY(HID_USG_TYPE, name)
+HID_BEGIN_TABLE(UsageType)
+  HID_USAGE_TYPE_ENTRY(LC),
+  HID_USAGE_TYPE_ENTRY(OOC),
+  HID_USAGE_TYPE_ENTRY(MC),
+  HID_USAGE_TYPE_ENTRY(OSC),
+  HID_USAGE_TYPE_ENTRY(RTC),
+  HID_USAGE_TYPE_ENTRY(SEL),
+  HID_USAGE_TYPE_ENTRY(SV),
+  HID_USAGE_TYPE_ENTRY(SF),
+  HID_USAGE_TYPE_ENTRY(DV),
+  HID_USAGE_TYPE_ENTRY(DF),
+  HID_USAGE_TYPE_ENTRY(NARY),
+  HID_USAGE_TYPE_ENTRY(CA),
+  HID_USAGE_TYPE_ENTRY(CL),
+  HID_USAGE_TYPE_ENTRY(CP),
+  HID_USAGE_TYPE_ENTRY(US),
+  HID_USAGE_TYPE_ENTRY(UM),
+HID_END_TABLE(UsageType)
+
+#define HID_USAGE_ENTRY(prefix, name, type) HID_TABLE_ENTRY(prefix, name, .usageType=HID_USG_TYPE_##type)
+
+#define HID_GENERIC_DESKTOP_USAGE_ENTRY(name, type) HID_USAGE_ENTRY(HID_USG_GDT, name, type)
+HID_BEGIN_TABLE(GenericDesktopUsage)
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Pointer, CP),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Mouse, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Joystick, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(GamePad, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Keyboard, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Keypad, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(MultiAxisController, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(TabletPCSystemControls, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(X, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Y, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Z, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Rx, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Ry, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Rz, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Slider, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Dial, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Wheel, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(HatSwitch, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(CountedBuffer, CL),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(ByteCount, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(MotionWakeup, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Start, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Select, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vx, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vy, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vz, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vbrx, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vbry, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vbrz, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(Vno, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(FeatureNotification, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(ResolutionMultiplier, DV),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemControl, CA),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemPowerDown, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemSleep, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemWakeUp, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemContextMenu, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMainMenu, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemAppMenu, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuHelp, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuExit, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuSelect, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuRight, RTC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuLeft, RTC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuUp, RTC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemMenuDown, RTC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemColdRestart, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemWarmRestart, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(DPadUp, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(DPadDown, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(DPadRight, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(DPadLeft, OOC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDock, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemUndock, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemSetup, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemBreak, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDebuggerBreak, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(ApplicationBreak, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(ApplicationDebuggerBreak, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemSpeakerMute, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemHibernate, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayInvert, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayInternal, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayExternal, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayBoth, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayDual, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayToggleIntExt, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplaySwap, OSC),
+  HID_GENERIC_DESKTOP_USAGE_ENTRY(SystemDisplayLCDAutoscale, OSC),
+HID_END_TABLE(GenericDesktopUsage)
+
+#define HID_KEYBOARD_USAGE_ENTRY(name, type) HID_USAGE_ENTRY(HID_USG_KBD, name, type)
+HID_BEGIN_TABLE(KeyboardUsage)
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardErrorRollOver, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPostFail, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardErrorUndefined, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardA, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardB, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardC, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardD, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardE, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardG, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardH, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardI, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardJ, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardK, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardL, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardM, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardN, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardO, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardP, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardQ, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardR, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardS, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardT, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardU, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardV, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardW, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardX, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardY, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardZ, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard3, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard4, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard5, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard6, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard7, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard8, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard9, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keyboard0, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEnter, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEscape, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardBackspace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardTab, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSpace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardMinus, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEqual, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftBracket, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightBracket, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardBackslash, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEurope1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSemicolon, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardApostrophe, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardGrave, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardComma, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPeriod, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSlash, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardCapsLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF3, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF4, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF5, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF6, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF7, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF8, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF9, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF10, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF11, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF12, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPrintScreen, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardScrollLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPause, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInsert, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardHome, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPageUp, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardDelete, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEnd, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPageDown, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightArrow, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftArrow, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardDownArrow, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardUpArrow, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadNumLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadSlash, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadAsterisk, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMinus, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadPlus, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadEnter, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad3, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad4, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad5, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad6, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad7, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad8, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad9, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad0, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadPeriod, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardEurope2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardApplication, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPower, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadEqual, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF13, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF14, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF15, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF16, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF17, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF18, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF19, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF20, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF21, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF22, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF23, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardF24, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardExecute, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardHelp, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardMenu, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSelect, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardStop, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardAgain, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardUndo, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardCut, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardCopy, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPaste, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardFind, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardMute, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardVolumeUp, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardVolumeDown, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLockingCapsLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLockingNumLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLockingScrollLock, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadComma, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadEqualSign, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational3, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational4, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational5, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational6, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational7, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational8, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardInternational9, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage1, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage2, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage3, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage4, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage5, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage6, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage7, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage8, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLanguage9, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardAlternateErase, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSystemRequest, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardCancel, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardClear, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardPrior, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardReturn, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardSeparator, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardOut, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardOper, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardClearAgain, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardCrSel, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardExSel, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad00, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(Keypad000, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(ThousandsSeparator, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(DecimalSeparator, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(CurrencyUnit, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(CurrencySubunit, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadLeftParenthesis, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadRightParenthesis, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadLeftBrace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadRightBrace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadTab, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBackspace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadA, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadB, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadC, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadD, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadE, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadF, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBitwiseXOR, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadExponentiate, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadModulo, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadLess, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadGreater, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBitwiseAND, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBooleanAND, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBitwiseOR, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBooleanOR, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadColon, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadNumber, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadSpace, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadAt, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBoleanNOT, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryStore, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryRecall, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryClear, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryAdd, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemorySubtract, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryMultiply, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadMemoryDivide, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadPlusMinus, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadClear, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadClearEntry, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadBinary, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadOctal, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadDecimal, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeypadHexadecimal, SEL),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftControl, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftShift, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftAlt, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardLeftGUI, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightControl, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightShift, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightAlt, DF),
+  HID_KEYBOARD_USAGE_ENTRY(KeyboardRightGUI, DF),
+HID_END_TABLE(KeyboardUsage)
+
+#define HID_LEDS_USAGE_ENTRY(name, type) HID_USAGE_ENTRY(HID_USG_LED, name, type)
+HID_BEGIN_TABLE(LEDsUsage)
+  HID_LEDS_USAGE_ENTRY(NumLock, OOC),
+  HID_LEDS_USAGE_ENTRY(CapsLock, OOC),
+  HID_LEDS_USAGE_ENTRY(ScrollLock, OOC),
+  HID_LEDS_USAGE_ENTRY(Compose, OOC),
+  HID_LEDS_USAGE_ENTRY(Kana, OOC),
+  HID_LEDS_USAGE_ENTRY(Power, OOC),
+  HID_LEDS_USAGE_ENTRY(Shift, OOC),
+  HID_LEDS_USAGE_ENTRY(DoNotDisturb, OOC),
+  HID_LEDS_USAGE_ENTRY(Mute, OOC),
+  HID_LEDS_USAGE_ENTRY(ToneEnable, OOC),
+  HID_LEDS_USAGE_ENTRY(HighCutFilter, OOC),
+  HID_LEDS_USAGE_ENTRY(LowCutFilter, OOC),
+  HID_LEDS_USAGE_ENTRY(EqualizerEnable, OOC),
+  HID_LEDS_USAGE_ENTRY(SoundFieldOn, OOC),
+  HID_LEDS_USAGE_ENTRY(SurroundOn, OOC),
+  HID_LEDS_USAGE_ENTRY(Repeat, OOC),
+  HID_LEDS_USAGE_ENTRY(Stereo, OOC),
+  HID_LEDS_USAGE_ENTRY(SamplingRateDetect, OOC),
+  HID_LEDS_USAGE_ENTRY(Spinning, OOC),
+  HID_LEDS_USAGE_ENTRY(CAV, OOC),
+  HID_LEDS_USAGE_ENTRY(CLV, OOC),
+  HID_LEDS_USAGE_ENTRY(RecordingFormatDetect, OOC),
+  HID_LEDS_USAGE_ENTRY(OffHook, OOC),
+  HID_LEDS_USAGE_ENTRY(Ring, OOC),
+  HID_LEDS_USAGE_ENTRY(MessageWaiting, OOC),
+  HID_LEDS_USAGE_ENTRY(DataMode, OOC),
+  HID_LEDS_USAGE_ENTRY(BatteryOperation, OOC),
+  HID_LEDS_USAGE_ENTRY(BatteryOK, OOC),
+  HID_LEDS_USAGE_ENTRY(BatteryLow, OOC),
+  HID_LEDS_USAGE_ENTRY(Speaker, OOC),
+  HID_LEDS_USAGE_ENTRY(HeadSet, OOC),
+  HID_LEDS_USAGE_ENTRY(Hold, OOC),
+  HID_LEDS_USAGE_ENTRY(Microphone, OOC),
+  HID_LEDS_USAGE_ENTRY(Coverage, OOC),
+  HID_LEDS_USAGE_ENTRY(NightMode, OOC),
+  HID_LEDS_USAGE_ENTRY(SendCalls, OOC),
+  HID_LEDS_USAGE_ENTRY(CallPickup, OOC),
+  HID_LEDS_USAGE_ENTRY(Conference, OOC),
+  HID_LEDS_USAGE_ENTRY(StandBy, OOC),
+  HID_LEDS_USAGE_ENTRY(CameraOn, OOC),
+  HID_LEDS_USAGE_ENTRY(CameraOff, OOC),
+  HID_LEDS_USAGE_ENTRY(OnLine, OOC),
+  HID_LEDS_USAGE_ENTRY(OffLine, OOC),
+  HID_LEDS_USAGE_ENTRY(Busy, OOC),
+  HID_LEDS_USAGE_ENTRY(Ready, OOC),
+  HID_LEDS_USAGE_ENTRY(PaperOut, OOC),
+  HID_LEDS_USAGE_ENTRY(PaperJam, OOC),
+  HID_LEDS_USAGE_ENTRY(Remote, OOC),
+  HID_LEDS_USAGE_ENTRY(Forward, OOC),
+  HID_LEDS_USAGE_ENTRY(Reverse, OOC),
+  HID_LEDS_USAGE_ENTRY(Stop, OOC),
+  HID_LEDS_USAGE_ENTRY(Rewind, OOC),
+  HID_LEDS_USAGE_ENTRY(FastForward, OOC),
+  HID_LEDS_USAGE_ENTRY(Play, OOC),
+  HID_LEDS_USAGE_ENTRY(Pause, OOC),
+  HID_LEDS_USAGE_ENTRY(Record, OOC),
+  HID_LEDS_USAGE_ENTRY(Error, OOC),
+  HID_LEDS_USAGE_ENTRY(UsageSelectedIndicator, US),
+  HID_LEDS_USAGE_ENTRY(UsageInUseIndicator, US),
+  HID_LEDS_USAGE_ENTRY(UsageMultiModeIndicator, UM),
+  HID_LEDS_USAGE_ENTRY(IndicatorOn, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorFlash, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorSlowBlink, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorFastBlink, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorOff, SEL),
+  HID_LEDS_USAGE_ENTRY(FlashOnTime, DV),
+  HID_LEDS_USAGE_ENTRY(SlowBlinkOnTime, DV),
+  HID_LEDS_USAGE_ENTRY(SlowBlinkOffTime, DV),
+  HID_LEDS_USAGE_ENTRY(FastBlinkOnTime, DV),
+  HID_LEDS_USAGE_ENTRY(FastBlinkOffTime, DV),
+  HID_LEDS_USAGE_ENTRY(UsageIndicatorColor, UM),
+  HID_LEDS_USAGE_ENTRY(IndicatorRed, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorGreen, SEL),
+  HID_LEDS_USAGE_ENTRY(IndicatorAmber, SEL),
+  HID_LEDS_USAGE_ENTRY(GenericIndicator, OOC),
+  HID_LEDS_USAGE_ENTRY(SystemSuspend, OOC),
+  HID_LEDS_USAGE_ENTRY(ExternalPowerConnected, OOC),
+HID_END_TABLE(LEDsUsage)
+
+#define HID_BRAILLE_USAGE_ENTRY(name, type) HID_USAGE_ENTRY(HID_USG_BRL, name, type)
+HID_BEGIN_TABLE(BrailleUsage)
+  HID_BRAILLE_USAGE_ENTRY(BrailleDisplay, CA),
+  HID_BRAILLE_USAGE_ENTRY(BrailleRow, NARY),
+  HID_BRAILLE_USAGE_ENTRY(8DotCell, DV),
+  HID_BRAILLE_USAGE_ENTRY(6DotCell, DV),
+  HID_BRAILLE_USAGE_ENTRY(CellCount, DV),
+  HID_BRAILLE_USAGE_ENTRY(ScreenReaderControl, NARY),
+  HID_BRAILLE_USAGE_ENTRY(ScreenReaderIdentifier, DV),
+  HID_BRAILLE_USAGE_ENTRY(RouterSet1, NARY),
+  HID_BRAILLE_USAGE_ENTRY(RouterSet2, NARY),
+  HID_BRAILLE_USAGE_ENTRY(RouterSet3, NARY),
+  HID_BRAILLE_USAGE_ENTRY(RouterKey, SEL),
+  HID_BRAILLE_USAGE_ENTRY(RowRouterKey, SEL),
+  HID_BRAILLE_USAGE_ENTRY(BrailleButtons, NARY),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot1, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot2, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot3, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot4, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot5, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot6, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot7, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardDot8, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardSpace, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardLeftSpace, SEL),
+  HID_BRAILLE_USAGE_ENTRY(KeyboardRightSpace, SEL),
+  HID_BRAILLE_USAGE_ENTRY(FrontControls, NARY),
+  HID_BRAILLE_USAGE_ENTRY(LeftControls, NARY),
+  HID_BRAILLE_USAGE_ENTRY(RightControls, NARY),
+  HID_BRAILLE_USAGE_ENTRY(TopControls, NARY),
+  HID_BRAILLE_USAGE_ENTRY(JoystickCenter, SEL),
+  HID_BRAILLE_USAGE_ENTRY(JoystickUp, SEL),
+  HID_BRAILLE_USAGE_ENTRY(JoystickDown, SEL),
+  HID_BRAILLE_USAGE_ENTRY(JoystickLeft, SEL),
+  HID_BRAILLE_USAGE_ENTRY(JoystickRight, SEL),
+  HID_BRAILLE_USAGE_ENTRY(DPadCenter, SEL),
+  HID_BRAILLE_USAGE_ENTRY(DPadUp, SEL),
+  HID_BRAILLE_USAGE_ENTRY(DPadDown, SEL),
+  HID_BRAILLE_USAGE_ENTRY(DPadLeft, SEL),
+  HID_BRAILLE_USAGE_ENTRY(DPadRight, SEL),
+  HID_BRAILLE_USAGE_ENTRY(PanLeft, SEL),
+  HID_BRAILLE_USAGE_ENTRY(PanRight, SEL),
+  HID_BRAILLE_USAGE_ENTRY(RockerUp, SEL),
+  HID_BRAILLE_USAGE_ENTRY(RockerDown, SEL),
+  HID_BRAILLE_USAGE_ENTRY(RockerPress, SEL),
+HID_END_TABLE(BrailleUsage)
+
+#define HID_USAGE_PAGE_ENTRY(name, usages) HID_TABLE_ENTRY(HID_UPG, name, .usageTable=usages)
+HID_BEGIN_TABLE(UsagePage)
+  HID_USAGE_PAGE_ENTRY(GenericDesktop, &hidGenericDesktopUsageTable),
+  HID_USAGE_PAGE_ENTRY(Simulation, NULL),
+  HID_USAGE_PAGE_ENTRY(VirtualReality, NULL),
+  HID_USAGE_PAGE_ENTRY(Sport, NULL),
+  HID_USAGE_PAGE_ENTRY(Game, NULL),
+  HID_USAGE_PAGE_ENTRY(GenericDevice, NULL),
+  HID_USAGE_PAGE_ENTRY(Keyboard_Keypad, &hidKeyboardUsageTable),
+  HID_USAGE_PAGE_ENTRY(LEDs, &hidLEDsUsageTable),
+  HID_USAGE_PAGE_ENTRY(Button, NULL),
+  HID_USAGE_PAGE_ENTRY(Ordinal, NULL),
+  HID_USAGE_PAGE_ENTRY(Telephony, NULL),
+  HID_USAGE_PAGE_ENTRY(Consumer, NULL),
+  HID_USAGE_PAGE_ENTRY(Digitizer, NULL),
+  HID_USAGE_PAGE_ENTRY(PhysicalInterfaceDevice, NULL),
+  HID_USAGE_PAGE_ENTRY(Unicode, NULL),
+  HID_USAGE_PAGE_ENTRY(AlphanumericDisplay, NULL),
+  HID_USAGE_PAGE_ENTRY(MedicalInstruments, NULL),
+  HID_USAGE_PAGE_ENTRY(BarCodeScanner, NULL),
+  HID_USAGE_PAGE_ENTRY(Braille, &hidBrailleUsageTable),
+  HID_USAGE_PAGE_ENTRY(Scale, NULL),
+  HID_USAGE_PAGE_ENTRY(MagneticStripeReader, NULL),
+  HID_USAGE_PAGE_ENTRY(Camera, NULL),
+  HID_USAGE_PAGE_ENTRY(Arcade, NULL),
+HID_END_TABLE(UsagePage)
diff --git a/Programs/hidkeys.c b/Programs/hidkeys.c
new file mode 100644
index 0000000..b9e5417
--- /dev/null
+++ b/Programs/hidkeys.c
@@ -0,0 +1,1660 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "hidkeys.h"
+#include "kbd_keycodes.h"
+#include "bitmask.h"
+#include "brl_cmds.h"
+#include "cmd_enqueue.h"
+
+typedef struct {
+  uint16_t xtCode;
+  uint16_t atCode;
+  uint8_t ps2Code;
+} HidKeyEntry;
+
+static const HidKeyEntry hidKeyTable[] = {
+  /* aA */
+  [HID_KEY_A] = {
+    .xtCode = XT_KEY(00, A),
+    .atCode = AT_KEY(00, A),
+    .ps2Code = PS2_KEY_A
+  },
+
+  /* bB */
+  [HID_KEY_B] = {
+    .xtCode = XT_KEY(00, B),
+    .atCode = AT_KEY(00, B),
+    .ps2Code = PS2_KEY_B
+  },
+
+  /* cC */
+  [HID_KEY_C] = {
+    .xtCode = XT_KEY(00, C),
+    .atCode = AT_KEY(00, C),
+    .ps2Code = PS2_KEY_C
+  },
+
+  /* dD */
+  [HID_KEY_D] = {
+    .xtCode = XT_KEY(00, D),
+    .atCode = AT_KEY(00, D),
+    .ps2Code = PS2_KEY_D
+  },
+
+  /* eE */
+  [HID_KEY_E] = {
+    .xtCode = XT_KEY(00, E),
+    .atCode = AT_KEY(00, E),
+    .ps2Code = PS2_KEY_E
+  },
+
+  /* fF */
+  [HID_KEY_F] = {
+    .xtCode = XT_KEY(00, F),
+    .atCode = AT_KEY(00, F),
+    .ps2Code = PS2_KEY_F
+  },
+
+  /* gG */
+  [HID_KEY_G] = {
+    .xtCode = XT_KEY(00, G),
+    .atCode = AT_KEY(00, G),
+    .ps2Code = PS2_KEY_G
+  },
+
+  /* hH */
+  [HID_KEY_H] = {
+    .xtCode = XT_KEY(00, H),
+    .atCode = AT_KEY(00, H),
+    .ps2Code = PS2_KEY_H
+  },
+
+  /* iI */
+  [HID_KEY_I] = {
+    .xtCode = XT_KEY(00, I),
+    .atCode = AT_KEY(00, I),
+    .ps2Code = PS2_KEY_I
+  },
+
+  /* jJ */
+  [HID_KEY_J] = {
+    .xtCode = XT_KEY(00, J),
+    .atCode = AT_KEY(00, J),
+    .ps2Code = PS2_KEY_J
+  },
+
+  /* kK */
+  [HID_KEY_K] = {
+    .xtCode = XT_KEY(00, K),
+    .atCode = AT_KEY(00, K),
+    .ps2Code = PS2_KEY_K
+  },
+
+  /* lL */
+  [HID_KEY_L] = {
+    .xtCode = XT_KEY(00, L),
+    .atCode = AT_KEY(00, L),
+    .ps2Code = PS2_KEY_L
+  },
+
+  /* mM */
+  [HID_KEY_M] = {
+    .xtCode = XT_KEY(00, M),
+    .atCode = AT_KEY(00, M),
+    .ps2Code = PS2_KEY_M
+  },
+
+  /* nN */
+  [HID_KEY_N] = {
+    .xtCode = XT_KEY(00, N),
+    .atCode = AT_KEY(00, N),
+    .ps2Code = PS2_KEY_N
+  },
+
+  /* oO */
+  [HID_KEY_O] = {
+    .xtCode = XT_KEY(00, O),
+    .atCode = AT_KEY(00, O),
+    .ps2Code = PS2_KEY_O
+  },
+
+  /* pP */
+  [HID_KEY_P] = {
+    .xtCode = XT_KEY(00, P),
+    .atCode = AT_KEY(00, P),
+    .ps2Code = PS2_KEY_P
+  },
+
+  /* qQ */
+  [HID_KEY_Q] = {
+    .xtCode = XT_KEY(00, Q),
+    .atCode = AT_KEY(00, Q),
+    .ps2Code = PS2_KEY_Q
+  },
+
+  /* rR */
+  [HID_KEY_R] = {
+    .xtCode = XT_KEY(00, R),
+    .atCode = AT_KEY(00, R),
+    .ps2Code = PS2_KEY_R
+  },
+
+  /* sS */
+  [HID_KEY_S] = {
+    .xtCode = XT_KEY(00, S),
+    .atCode = AT_KEY(00, S),
+    .ps2Code = PS2_KEY_S
+  },
+
+  /* tT */
+  [HID_KEY_T] = {
+    .xtCode = XT_KEY(00, T),
+    .atCode = AT_KEY(00, T),
+    .ps2Code = PS2_KEY_T
+  },
+
+  /* uU */
+  [HID_KEY_U] = {
+    .xtCode = XT_KEY(00, U),
+    .atCode = AT_KEY(00, U),
+    .ps2Code = PS2_KEY_U
+  },
+
+  /* vV */
+  [HID_KEY_V] = {
+    .xtCode = XT_KEY(00, V),
+    .atCode = AT_KEY(00, V),
+    .ps2Code = PS2_KEY_V
+  },
+
+  /* wW */
+  [HID_KEY_W] = {
+    .xtCode = XT_KEY(00, W),
+    .atCode = AT_KEY(00, W),
+    .ps2Code = PS2_KEY_W
+  },
+
+  /* xX */
+  [HID_KEY_X] = {
+    .xtCode = XT_KEY(00, X),
+    .atCode = AT_KEY(00, X),
+    .ps2Code = PS2_KEY_X
+  },
+
+  /* yY */
+  [HID_KEY_Y] = {
+    .xtCode = XT_KEY(00, Y),
+    .atCode = AT_KEY(00, Y),
+    .ps2Code = PS2_KEY_Y
+  },
+
+  /* zZ */
+  [HID_KEY_Z] = {
+    .xtCode = XT_KEY(00, Z),
+    .atCode = AT_KEY(00, Z),
+    .ps2Code = PS2_KEY_Z
+  },
+
+  /* 1! */
+  [HID_KEY_1] = {
+    .xtCode = XT_KEY(00, 1),
+    .atCode = AT_KEY(00, 1),
+    .ps2Code = PS2_KEY_1
+  },
+
+  /* 2@ */
+  [HID_KEY_2] = {
+    .xtCode = XT_KEY(00, 2),
+    .atCode = AT_KEY(00, 2),
+    .ps2Code = PS2_KEY_2
+  },
+
+  /* 3# */
+  [HID_KEY_3] = {
+    .xtCode = XT_KEY(00, 3),
+    .atCode = AT_KEY(00, 3),
+    .ps2Code = PS2_KEY_3
+  },
+
+  /* 4$ */
+  [HID_KEY_4] = {
+    .xtCode = XT_KEY(00, 4),
+    .atCode = AT_KEY(00, 4),
+    .ps2Code = PS2_KEY_4
+  },
+
+  /* 5% */
+  [HID_KEY_5] = {
+    .xtCode = XT_KEY(00, 5),
+    .atCode = AT_KEY(00, 5),
+    .ps2Code = PS2_KEY_5
+  },
+
+  /* 6^ */
+  [HID_KEY_6] = {
+    .xtCode = XT_KEY(00, 6),
+    .atCode = AT_KEY(00, 6),
+    .ps2Code = PS2_KEY_6
+  },
+
+  /* 7& */
+  [HID_KEY_7] = {
+    .xtCode = XT_KEY(00, 7),
+    .atCode = AT_KEY(00, 7),
+    .ps2Code = PS2_KEY_7
+  },
+
+  /* 8* */
+  [HID_KEY_8] = {
+    .xtCode = XT_KEY(00, 8),
+    .atCode = AT_KEY(00, 8),
+    .ps2Code = PS2_KEY_8
+  },
+
+  /* 9( */
+  [HID_KEY_9] = {
+    .xtCode = XT_KEY(00, 9),
+    .atCode = AT_KEY(00, 9),
+    .ps2Code = PS2_KEY_9
+  },
+
+  /* 0) */
+  [HID_KEY_0] = {
+    .xtCode = XT_KEY(00, 0),
+    .atCode = AT_KEY(00, 0),
+    .ps2Code = PS2_KEY_0
+  },
+
+  /* Return */
+  [HID_KEY_Enter] = {
+    .xtCode = XT_KEY(00, Enter),
+    .atCode = AT_KEY(00, Enter),
+    .ps2Code = PS2_KEY_Enter
+  },
+
+  /* Escape */
+  [HID_KEY_Escape] = {
+    .xtCode = XT_KEY(00, Escape),
+    .atCode = AT_KEY(00, Escape),
+    .ps2Code = PS2_KEY_Escape
+  },
+
+  /* Backspace */
+  [HID_KEY_Backspace] = {
+    .xtCode = XT_KEY(00, Backspace),
+    .atCode = AT_KEY(00, Backspace),
+    .ps2Code = PS2_KEY_Backspace
+  },
+
+  /* Tab */
+  [HID_KEY_Tab] = {
+    .xtCode = XT_KEY(00, Tab),
+    .atCode = AT_KEY(00, Tab),
+    .ps2Code = PS2_KEY_Tab
+  },
+
+  /* Space */
+  [HID_KEY_Space] = {
+    .xtCode = XT_KEY(00, Space),
+    .atCode = AT_KEY(00, Space),
+    .ps2Code = PS2_KEY_Space
+  },
+
+  /* -_ */
+  [HID_KEY_Minus] = {
+    .xtCode = XT_KEY(00, Minus),
+    .atCode = AT_KEY(00, Minus),
+    .ps2Code = PS2_KEY_Minus
+  },
+
+  /* =+ */
+  [HID_KEY_Equal] = {
+    .xtCode = XT_KEY(00, Equal),
+    .atCode = AT_KEY(00, Equal),
+    .ps2Code = PS2_KEY_Equal
+  },
+
+  /* [{ */
+  [HID_KEY_LeftBracket] = {
+    .xtCode = XT_KEY(00, LeftBracket),
+    .atCode = AT_KEY(00, LeftBracket),
+    .ps2Code = PS2_KEY_LeftBracket
+  },
+
+  /* ]} */
+  [HID_KEY_RightBracket] = {
+    .xtCode = XT_KEY(00, RightBracket),
+    .atCode = AT_KEY(00, RightBracket),
+    .ps2Code = PS2_KEY_RightBracket
+  },
+
+  /* \| */
+  [HID_KEY_Backslash] = {
+    .xtCode = XT_KEY(00, Backslash),
+    .atCode = AT_KEY(00, Backslash),
+    .ps2Code = PS2_KEY_Backslash
+  },
+
+  /* Europe 1 (Note 2) */
+  [HID_KEY_Europe1] = {
+    .xtCode = XT_KEY(00, Europe1),
+    .atCode = AT_KEY(00, Europe1),
+    .ps2Code = PS2_KEY_Europe1
+  },
+
+  /* ;: */
+  [HID_KEY_Semicolon] = {
+    .xtCode = XT_KEY(00, Semicolon),
+    .atCode = AT_KEY(00, Semicolon),
+    .ps2Code = PS2_KEY_Semicolon
+  },
+
+  /* '" */
+  [HID_KEY_Apostrophe] = {
+    .xtCode = XT_KEY(00, Apostrophe),
+    .atCode = AT_KEY(00, Apostrophe),
+    .ps2Code = PS2_KEY_Apostrophe
+  },
+
+  /* `~ */
+  [HID_KEY_Grave] = {
+    .xtCode = XT_KEY(00, Grave),
+    .atCode = AT_KEY(00, Grave),
+    .ps2Code = PS2_KEY_Grave
+  },
+
+  /* ,< */
+  [HID_KEY_Comma] = {
+    .xtCode = XT_KEY(00, Comma),
+    .atCode = AT_KEY(00, Comma),
+    .ps2Code = PS2_KEY_Comma
+  },
+
+  /* .> */
+  [HID_KEY_Period] = {
+    .xtCode = XT_KEY(00, Period),
+    .atCode = AT_KEY(00, Period),
+    .ps2Code = PS2_KEY_Period
+  },
+
+  /* /? */
+  [HID_KEY_Slash] = {
+    .xtCode = XT_KEY(00, Slash),
+    .atCode = AT_KEY(00, Slash),
+    .ps2Code = PS2_KEY_Slash
+  },
+
+  /* Caps Lock */
+  [HID_KEY_CapsLock] = {
+    .xtCode = XT_KEY(00, CapsLock),
+    .atCode = AT_KEY(00, CapsLock),
+    .ps2Code = PS2_KEY_CapsLock
+  },
+
+  /* F1 */
+  [HID_KEY_F1] = {
+    .xtCode = XT_KEY(00, F1),
+    .atCode = AT_KEY(00, F1),
+    .ps2Code = PS2_KEY_F1
+  },
+
+  /* F2 */
+  [HID_KEY_F2] = {
+    .xtCode = XT_KEY(00, F2),
+    .atCode = AT_KEY(00, F2),
+    .ps2Code = PS2_KEY_F2
+  },
+
+  /* F3 */
+  [HID_KEY_F3] = {
+    .xtCode = XT_KEY(00, F3),
+    .atCode = AT_KEY(00, F3),
+    .ps2Code = PS2_KEY_F3
+  },
+
+  /* F4 */
+  [HID_KEY_F4] = {
+    .xtCode = XT_KEY(00, F4),
+    .atCode = AT_KEY(00, F4),
+    .ps2Code = PS2_KEY_F4
+  },
+
+  /* F5 */
+  [HID_KEY_F5] = {
+    .xtCode = XT_KEY(00, F5),
+    .atCode = AT_KEY(00, F5),
+    .ps2Code = PS2_KEY_F5
+  },
+
+  /* F6 */
+  [HID_KEY_F6] = {
+    .xtCode = XT_KEY(00, F6),
+    .atCode = AT_KEY(00, F6),
+    .ps2Code = PS2_KEY_F6
+  },
+
+  /* F7 */
+  [HID_KEY_F7] = {
+    .xtCode = XT_KEY(00, F7),
+    .atCode = AT_KEY(00, F7),
+    .ps2Code = PS2_KEY_F7
+  },
+
+  /* F8 */
+  [HID_KEY_F8] = {
+    .xtCode = XT_KEY(00, F8),
+    .atCode = AT_KEY(00, F8),
+    .ps2Code = PS2_KEY_F8
+  },
+
+  /* F9 */
+  [HID_KEY_F9] = {
+    .xtCode = XT_KEY(00, F9),
+    .atCode = AT_KEY(00, F9),
+    .ps2Code = PS2_KEY_F9
+  },
+
+  /* F10 */
+  [HID_KEY_F10] = {
+    .xtCode = XT_KEY(00, F10),
+    .atCode = AT_KEY(00, F10),
+    .ps2Code = PS2_KEY_F10
+  },
+
+  /* F11 */
+  [HID_KEY_F11] = {
+    .xtCode = XT_KEY(00, F11),
+    .atCode = AT_KEY(00, F11),
+    .ps2Code = PS2_KEY_F11
+  },
+
+  /* F12 */
+  [HID_KEY_F12] = {
+    .xtCode = XT_KEY(00, F12),
+    .atCode = AT_KEY(00, F12),
+    .ps2Code = PS2_KEY_F12
+  },
+
+  /* Print Screen (Note 1) */
+  [HID_KEY_PrintScreen] = {
+    .xtCode = XT_KEY(E0, PrintScreen),
+    .atCode = AT_KEY(E0, PrintScreen),
+    .ps2Code = PS2_KEY_PrintScreen
+  },
+
+  /* Scroll Lock */
+  [HID_KEY_ScrollLock] = {
+    .xtCode = XT_KEY(00, ScrollLock),
+    .atCode = AT_KEY(00, ScrollLock),
+    .ps2Code = PS2_KEY_ScrollLock
+  },
+
+  /* Pause */
+  [HID_KEY_Pause] = {
+    .xtCode = XT_KEY(E1, Pause),
+    .atCode = AT_KEY(E1, Pause),
+    .ps2Code = PS2_KEY_Pause
+  },
+
+  /* Insert (Note 1) */
+  [HID_KEY_Insert] = {
+    .xtCode = XT_KEY(E0, Insert),
+    .atCode = AT_KEY(E0, Insert),
+    .ps2Code = PS2_KEY_Insert
+  },
+
+  /* Home (Note 1) */
+  [HID_KEY_Home] = {
+    .xtCode = XT_KEY(E0, Home),
+    .atCode = AT_KEY(E0, Home),
+    .ps2Code = PS2_KEY_Home
+  },
+
+  /* Page Up (Note 1) */
+  [HID_KEY_PageUp] = {
+    .xtCode = XT_KEY(E0, PageUp),
+    .atCode = AT_KEY(E0, PageUp),
+    .ps2Code = PS2_KEY_PageUp
+  },
+
+  /* Delete (Note 1) */
+  [HID_KEY_Delete] = {
+    .xtCode = XT_KEY(E0, Delete),
+    .atCode = AT_KEY(E0, Delete),
+    .ps2Code = PS2_KEY_Delete
+  },
+
+  /* End (Note 1) */
+  [HID_KEY_End] = {
+    .xtCode = XT_KEY(E0, End),
+    .atCode = AT_KEY(E0, End),
+    .ps2Code = PS2_KEY_End
+  },
+
+  /* Page Down (Note 1) */
+  [HID_KEY_PageDown] = {
+    .xtCode = XT_KEY(E0, PageDown),
+    .atCode = AT_KEY(E0, PageDown),
+    .ps2Code = PS2_KEY_PageDown
+  },
+
+  /* Right Arrow (Note 1) */
+  [HID_KEY_ArrowRight] = {
+    .xtCode = XT_KEY(E0, ArrowRight),
+    .atCode = AT_KEY(E0, ArrowRight),
+    .ps2Code = PS2_KEY_ArrowRight
+  },
+
+  /* Left Arrow (Note 1) */
+  [HID_KEY_ArrowLeft] = {
+    .xtCode = XT_KEY(E0, ArrowLeft),
+    .atCode = AT_KEY(E0, ArrowLeft),
+    .ps2Code = PS2_KEY_ArrowLeft
+  },
+
+  /* Down Arrow (Note 1) */
+  [HID_KEY_ArrowDown] = {
+    .xtCode = XT_KEY(E0, ArrowDown),
+    .atCode = AT_KEY(E0, ArrowDown),
+    .ps2Code = PS2_KEY_ArrowDown
+  },
+
+  /* Up Arrow (Note 1) */
+  [HID_KEY_ArrowUp] = {
+    .xtCode = XT_KEY(E0, ArrowUp),
+    .atCode = AT_KEY(E0, ArrowUp),
+    .ps2Code = PS2_KEY_ArrowUp
+  },
+
+  /* Num Lock */
+  [HID_KEY_NumLock] = {
+    .xtCode = XT_KEY(00, NumLock),
+    .atCode = AT_KEY(00, NumLock),
+    .ps2Code = PS2_KEY_NumLock
+  },
+
+  /* Keypad / (Note 1) */
+  [HID_KEY_KPSlash] = {
+    .xtCode = XT_KEY(E0, KPSlash),
+    .atCode = AT_KEY(E0, KPSlash),
+    .ps2Code = PS2_KEY_KPSlash
+  },
+
+  /* Keypad * */
+  [HID_KEY_KPAsterisk] = {
+    .xtCode = XT_KEY(00, KPAsterisk),
+    .atCode = AT_KEY(00, KPAsterisk),
+    .ps2Code = PS2_KEY_KPAsterisk
+  },
+
+  /* Keypad - */
+  [HID_KEY_KPMinus] = {
+    .xtCode = XT_KEY(00, KPMinus),
+    .atCode = AT_KEY(00, KPMinus),
+    .ps2Code = PS2_KEY_KPMinus
+  },
+
+  /* Keypad + */
+  [HID_KEY_KPPlus] = {
+    .xtCode = XT_KEY(00, KPPlus),
+    .atCode = AT_KEY(00, KPPlus),
+    .ps2Code = PS2_KEY_KPPlus
+  },
+
+  /* Keypad Enter */
+  [HID_KEY_KPEnter] = {
+    .xtCode = XT_KEY(E0, KPEnter),
+    .atCode = AT_KEY(E0, KPEnter),
+    .ps2Code = PS2_KEY_KPEnter
+  },
+
+  /* Keypad 1 End */
+  [HID_KEY_KP1] = {
+    .xtCode = XT_KEY(00, KP1),
+    .atCode = AT_KEY(00, KP1),
+    .ps2Code = PS2_KEY_KP1
+  },
+
+  /* Keypad 2 Down */
+  [HID_KEY_KP2] = {
+    .xtCode = XT_KEY(00, KP2),
+    .atCode = AT_KEY(00, KP2),
+    .ps2Code = PS2_KEY_KP2
+  },
+
+  /* Keypad 3 PageDn */
+  [HID_KEY_KP3] = {
+    .xtCode = XT_KEY(00, KP3),
+    .atCode = AT_KEY(00, KP3),
+    .ps2Code = PS2_KEY_KP3
+  },
+
+  /* Keypad 4 Left */
+  [HID_KEY_KP4] = {
+    .xtCode = XT_KEY(00, KP4),
+    .atCode = AT_KEY(00, KP4),
+    .ps2Code = PS2_KEY_KP4
+  },
+
+  /* Keypad 5 */
+  [HID_KEY_KP5] = {
+    .xtCode = XT_KEY(00, KP5),
+    .atCode = AT_KEY(00, KP5),
+    .ps2Code = PS2_KEY_KP5
+  },
+
+  /* Keypad 6 Right */
+  [HID_KEY_KP6] = {
+    .xtCode = XT_KEY(00, KP6),
+    .atCode = AT_KEY(00, KP6),
+    .ps2Code = PS2_KEY_KP6
+  },
+
+  /* Keypad 7 Home */
+  [HID_KEY_KP7] = {
+    .xtCode = XT_KEY(00, KP7),
+    .atCode = AT_KEY(00, KP7),
+    .ps2Code = PS2_KEY_KP7
+  },
+
+  /* Keypad 8 Up */
+  [HID_KEY_KP8] = {
+    .xtCode = XT_KEY(00, KP8),
+    .atCode = AT_KEY(00, KP8),
+    .ps2Code = PS2_KEY_KP8
+  },
+
+  /* Keypad 9 PageUp */
+  [HID_KEY_KP9] = {
+    .xtCode = XT_KEY(00, KP9),
+    .atCode = AT_KEY(00, KP9),
+    .ps2Code = PS2_KEY_KP9
+  },
+
+  /* Keypad 0 Insert */
+  [HID_KEY_KP0] = {
+    .xtCode = XT_KEY(00, KP0),
+    .atCode = AT_KEY(00, KP0),
+    .ps2Code = PS2_KEY_KP0
+  },
+
+  /* Keypad . Delete */
+  [HID_KEY_KPPeriod] = {
+    .xtCode = XT_KEY(00, KPPeriod),
+    .atCode = AT_KEY(00, KPPeriod),
+    .ps2Code = PS2_KEY_KPPeriod
+  },
+
+  /* Europe 2 (Note 2) */
+  [HID_KEY_Europe2] = {
+    .xtCode = XT_KEY(00, Europe2),
+    .atCode = AT_KEY(00, Europe2),
+    .ps2Code = PS2_KEY_Europe2
+  },
+
+  /* App */
+  [HID_KEY_Context] = {
+    .xtCode = XT_KEY(E0, Context),
+    .atCode = AT_KEY(E0, Context),
+    .ps2Code = PS2_KEY_Context
+  },
+
+  /* Keyboard Power */
+  [HID_KEY_Power] = {
+    .xtCode = XT_KEY(E0, Power),
+    .atCode = AT_KEY(E0, Power),
+    .ps2Code = 0X00
+  },
+
+  /* Keypad = */
+  [HID_KEY_KPEqual] = {
+    .xtCode = XT_KEY(00, KPEqual),
+    .atCode = AT_KEY(00, KPEqual),
+    .ps2Code = 0X00
+  },
+
+  /* F13 */
+  [HID_KEY_F13] = {
+    .xtCode = XT_KEY(00, F13),
+    .atCode = AT_KEY(00, F13),
+    .ps2Code = 0X00
+  },
+
+  /* F14 */
+  [HID_KEY_F14] = {
+    .xtCode = XT_KEY(00, F14),
+    .atCode = AT_KEY(00, F14),
+    .ps2Code = 0X00
+  },
+
+  /* F15 */
+  [HID_KEY_F15] = {
+    .xtCode = XT_KEY(00, F15),
+    .atCode = AT_KEY(00, F15),
+    .ps2Code = 0X00
+  },
+
+  /* F16 */
+  [HID_KEY_F16] = {
+    .xtCode = XT_KEY(00, F16),
+    .atCode = AT_KEY(00, F16),
+    .ps2Code = 0X00
+  },
+
+  /* F17 */
+  [HID_KEY_F17] = {
+    .xtCode = XT_KEY(00, F17),
+    .atCode = AT_KEY(00, F17),
+    .ps2Code = 0X00
+  },
+
+  /* F18 */
+  [HID_KEY_F18] = {
+    .xtCode = XT_KEY(00, F18),
+    .atCode = AT_KEY(00, F18),
+    .ps2Code = 0X00
+  },
+
+  /* F19 */
+  [HID_KEY_F19] = {
+    .xtCode = XT_KEY(00, F19),
+    .atCode = AT_KEY(00, F19),
+    .ps2Code = 0X00
+  },
+
+  /* F20 */
+  [HID_KEY_F20] = {
+    .xtCode = XT_KEY(00, F20),
+    .atCode = AT_KEY(00, F20),
+    .ps2Code = 0X00
+  },
+
+  /* F21 */
+  [HID_KEY_F21] = {
+    .xtCode = XT_KEY(00, F21),
+    .atCode = AT_KEY(00, F21),
+    .ps2Code = 0X00
+  },
+
+  /* F22 */
+  [HID_KEY_F22] = {
+    .xtCode = XT_KEY(00, F22),
+    .atCode = AT_KEY(00, F22),
+    .ps2Code = 0X00
+  },
+
+  /* F23 */
+  [HID_KEY_F23] = {
+    .xtCode = XT_KEY(00, F23),
+    .atCode = AT_KEY(00, F23),
+    .ps2Code = 0X00
+  },
+
+  /* F24 */
+  [HID_KEY_F24] = {
+    .xtCode = XT_KEY(00, F24),
+    .atCode = AT_KEY(00, F24),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Execute */
+  [HID_KEY_Execute] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Help */
+  [HID_KEY_Help] = {
+    .xtCode = XT_KEY(E0, Help),
+    .atCode = AT_KEY(E0, Help),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Menu */
+  [HID_KEY_Menu] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Select */
+  [HID_KEY_Select] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Stop */
+  [HID_KEY_Stop] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Again */
+  [HID_KEY_Again] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Undo */
+  [HID_KEY_Undo] = {
+    .xtCode = XT_KEY(E0, Undo),
+    .atCode = AT_KEY(E0, Undo),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Cut */
+  [HID_KEY_Cut] = {
+    .xtCode = XT_KEY(E0, Cut),
+    .atCode = AT_KEY(E0, Cut),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Copy */
+  [HID_KEY_Copy] = {
+    .xtCode = XT_KEY(E0, Copy),
+    .atCode = AT_KEY(E0, Copy),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Paste */
+  [HID_KEY_Paste] = {
+    .xtCode = XT_KEY(E0, Paste),
+    .atCode = AT_KEY(E0, Paste),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Find */
+  [HID_KEY_Find] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Mute */
+  [HID_KEY_Mute] = {
+    .xtCode = XT_KEY(E0, Mute),
+    .atCode = AT_KEY(E0, Mute),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Volume Up */
+  [HID_KEY_VolumeUp] = {
+    .xtCode = XT_KEY(E0, VolumeUp),
+    .atCode = AT_KEY(E0, VolumeUp),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Volume Dn */
+  [HID_KEY_VolumeDown] = {
+    .xtCode = XT_KEY(E0, VolumeDown),
+    .atCode = AT_KEY(E0, VolumeDown),
+    .ps2Code = 0X00
+  },
+
+  /* Caps Lock */
+  [HID_KEY_CapsLocking] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Num Lock */
+  [HID_KEY_NumLocking] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Scroll Lock */
+  [HID_KEY_ScrollLocking] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keypad , (Brazilian Keypad .) */
+  [HID_KEY_KPComma] = {
+    .xtCode = XT_KEY(00, KPComma),
+    .atCode = AT_KEY(00, KPComma),
+    .ps2Code = PS2_KEY_KPComma
+  },
+
+  /* Keyboard Equal Sign */
+  [HID_KEY_Equal_X1] = {
+    .xtCode = XT_KEY(00, Equal),
+    .atCode = AT_KEY(00, Equal),
+    .ps2Code = PS2_KEY_Equal
+  },
+
+  /* Keyboard Int'l 1 (Ro) */
+  [HID_KEY_International1] = {
+    .xtCode = XT_KEY(00, International1),
+    .atCode = AT_KEY(00, International1),
+    .ps2Code = PS2_KEY_International1
+  },
+
+  /* Keyboard Intl'2 (Katakana/Hiragana) */
+  [HID_KEY_International2] = {
+    .xtCode = XT_KEY(00, International2),
+    .atCode = AT_KEY(00, International2),
+    .ps2Code = PS2_KEY_International2
+  },
+
+  /* Keyboard Int'l 3 (Yen) */
+  [HID_KEY_International3] = {
+    .xtCode = XT_KEY(00, International3),
+    .atCode = AT_KEY(00, International3),
+    .ps2Code = PS2_KEY_International3
+  },
+
+  /* Keyboard Int'l 4 (Henkan) */
+  [HID_KEY_International4] = {
+    .xtCode = XT_KEY(00, International4),
+    .atCode = AT_KEY(00, International4),
+    .ps2Code = PS2_KEY_International4
+  },
+
+  /* Keyboard Int'l 5 (Muhenkan) */
+  [HID_KEY_International5] = {
+    .xtCode = XT_KEY(00, International5),
+    .atCode = AT_KEY(00, International5),
+    .ps2Code = PS2_KEY_International5
+  },
+
+  /* Keyboard Int'l 6 (PC9800 Keypad ,) */
+  [HID_KEY_International6] = {
+    .xtCode = XT_KEY(00, International6),
+    .atCode = AT_KEY(00, International6),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Int'l 7 */
+  [HID_KEY_International7] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Int'l 8 */
+  [HID_KEY_International8] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Int'l 9 */
+  [HID_KEY_International9] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 1 (Hanguel/English) */
+  [HID_KEY_Language1] = {
+    .xtCode = XT_KEY(00, Language1),
+    .atCode = AT_KEY(00, Language1),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 2 (Hanja) */
+  [HID_KEY_Language2] = {
+    .xtCode = XT_KEY(00, Language2),
+    .atCode = AT_KEY(00, Language2),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 3 (Katakana) */
+  [HID_KEY_Language3] = {
+    .xtCode = XT_KEY(00, Language3),
+    .atCode = AT_KEY(00, Language3),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 4 (Hiragana) */
+  [HID_KEY_Language4] = {
+    .xtCode = XT_KEY(00, Language4),
+    .atCode = AT_KEY(00, Language4),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 5 (Hiragana) */
+  [HID_KEY_Language5] = {
+    .xtCode = XT_KEY(00, Language5),
+    .atCode = AT_KEY(00, Language5),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 6 */
+  [HID_KEY_Language6] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 7 */
+  [HID_KEY_Language7] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 8 */
+  [HID_KEY_Language8] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Lang 9 */
+  [HID_KEY_Language9] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Alternate Erase */
+  [HID_KEY_AlternateErase] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard SysReq/Attention */
+  [HID_KEY_SystemReequest] = {
+    .xtCode = XT_KEY(00, SystemRequest),
+    .atCode = AT_KEY(00, SystemRequest),
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Cancel */
+  [HID_KEY_Cancel] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Clear */
+  [HID_KEY_Clear] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Prior */
+  [HID_KEY_Prior] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Return */
+  [HID_KEY_Return] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Separator */
+  [HID_KEY_Separator] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Out */
+  [HID_KEY_Out] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Oper */
+  [HID_KEY_Oper] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard Clear/Again */
+  [HID_KEY_ClearAgain] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Keyboard CrSel/Props */
+  [HID_KEY_CrSel] = {
+    .xtCode = XT_KEY(00, CrSel),
+    .atCode = AT_KEY(00, CrSel),
+    .ps2Code = PS2_KEY_CrSel
+  },
+
+  /* Keyboard ExSel */
+  [HID_KEY_ExSel] = {
+    .xtCode = XT_KEY(00, ExSel),
+    .atCode = AT_KEY(00, ExSel),
+    .ps2Code = PS2_KEY_ExSel
+  },
+
+  /* xxx */
+  [HID_KEY_KP00] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KP000] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPThousandsSeparator] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPDecimalSeparator] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPCurrencyUnit] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPCurrencySubunit] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPLeftParenthesis] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPRightParenthesis] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPLeftBrace] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPRightBrace] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPTab] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBackspace] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPA] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPB] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPC] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPD] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPE] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPF] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBitwiseXor] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPExponentiate] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPmodulo] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPLess] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPGreater] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBitwiseAnd] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBooleanAnd] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBitwiseOr] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBooleanOr] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPColon] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPNumber] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPSpace] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPAt] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBooleanNot] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryStore] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryRecall] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryClear] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryAdd] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemorySubtract] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryMultiply] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPMemoryDivide] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPPlusMinus] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPClear] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPClearEntry] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPBinary] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPOctal] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPDecimal] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* xxx */
+  [HID_KEY_KPHexadecimal] = {
+    .xtCode = 0X0000,
+    .atCode = 0X0000,
+    .ps2Code = 0X00
+  },
+
+  /* Left Control */
+  [HID_KEY_LeftControl] = {
+    .xtCode = XT_KEY(00, LeftControl),
+    .atCode = AT_KEY(00, LeftControl),
+    .ps2Code = PS2_KEY_LeftControl
+  },
+
+  /* Left Shift */
+  [HID_KEY_LeftShift] = {
+    .xtCode = XT_KEY(00, LeftShift),
+    .atCode = AT_KEY(00, LeftShift),
+    .ps2Code = PS2_KEY_LeftShift
+  },
+
+  /* Left Alt */
+  [HID_KEY_LeftAlt] = {
+    .xtCode = XT_KEY(00, LeftAlt),
+    .atCode = AT_KEY(00, LeftAlt),
+    .ps2Code = PS2_KEY_LeftAlt
+  },
+
+  /* Left GUI */
+  [HID_KEY_LeftGUI] = {
+    .xtCode = XT_KEY(E0, LeftGUI),
+    .atCode = AT_KEY(E0, LeftGUI),
+    .ps2Code = PS2_KEY_LeftGUI
+  },
+
+  /* Right Control */
+  [HID_KEY_RightControl] = {
+    .xtCode = XT_KEY(E0, RightControl),
+    .atCode = AT_KEY(E0, RightControl),
+    .ps2Code = PS2_KEY_RightControl
+  },
+
+  /* Right Shift */
+  [HID_KEY_RightShift] = {
+    .xtCode = XT_KEY(00, RightShift),
+    .atCode = AT_KEY(00, RightShift),
+    .ps2Code = PS2_KEY_RightShift
+  },
+
+  /* Right Alt */
+  [HID_KEY_RightAlt] = {
+    .xtCode = XT_KEY(E0, RightAlt),
+    .atCode = AT_KEY(E0, RightAlt),
+    .ps2Code = PS2_KEY_RightAlt
+  },
+
+  /* Right GUI */
+  [HID_KEY_RightGUI] = {
+    .xtCode = XT_KEY(E0, RightGUI),
+    .atCode = AT_KEY(E0, RightGUI),
+    .ps2Code = PS2_KEY_RightGUI
+  },
+};
+
+static int
+enqueueXtCode (uint8_t code) {
+  return enqueueCommand(BRL_CMD_BLK(PASSXT) | code);
+}
+
+static int
+enqueueHidKeyEvent (unsigned char key, int press) {
+  if (key < ARRAY_COUNT(hidKeyTable)) {
+    uint16_t code = hidKeyTable[key].xtCode;
+
+    if (code) {
+      {
+        uint8_t escape = (code >> 8) & 0XFF;
+
+        if (escape)
+          if (!enqueueXtCode(escape))
+            return 0;
+      }
+
+      code &= 0XFF;
+
+      if (!press) {
+        if (code & 0X80) return 1;
+        code |= 0X80;
+      }
+
+      if (!enqueueXtCode(code)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static unsigned char
+getPressedKeys (const HidKeyboardPacket *packet, unsigned char *keys) {
+  unsigned char count = 0;
+
+  {
+    static const unsigned char modifiers[] = {
+      HID_KEY_LeftControl,
+      HID_KEY_LeftShift,
+      HID_KEY_LeftAlt,
+      HID_KEY_LeftGUI,
+      HID_KEY_RightControl,
+      HID_KEY_RightShift,
+      HID_KEY_RightAlt,
+      HID_KEY_RightGUI,
+    };
+
+    const unsigned char *modifier = modifiers;
+    uint8_t bit = 0X1;
+
+    while (bit) {
+      if (packet->modifiers & bit) keys[count++] = *modifier;
+      modifier += 1;
+      bit <<= 1;
+    }
+  }
+
+  {
+    int index;
+
+    for (index=0; index<6; index+=1) {
+      unsigned char key = packet->keys[index];
+      if (!key) break;
+      keys[count++] = key;
+    }
+  }
+
+  return count;
+}
+
+void
+initializeHidKeyboardPacket (HidKeyboardPacket *packet) {
+  memset(packet, 0, sizeof(*packet));
+}
+
+void
+processHidKeyboardPacket (
+  HidKeyboardPacket *oldPacket,
+  const HidKeyboardPacket *newPacket
+) {
+  unsigned char oldKeys[14];
+  unsigned char oldCount = getPressedKeys(oldPacket, oldKeys);
+
+  unsigned char newKeys[14];
+  unsigned char newCount = getPressedKeys(newPacket, newKeys);
+
+  BITMASK(pressedKeys, 0X100, char);
+  unsigned char index;
+
+  BITMASK_ZERO(pressedKeys);
+
+  for (index=0; index<newCount; index+=1) {
+    unsigned char key = newKeys[index];
+
+    BITMASK_SET(pressedKeys, key);
+  }
+
+  for (index=0; index<oldCount; index+=1) {
+    unsigned char key = oldKeys[index];
+
+    if (BITMASK_TEST(pressedKeys, key)) {
+      BITMASK_CLEAR(pressedKeys, key);
+    } else {
+      enqueueHidKeyEvent(key, 0);
+    }
+  }
+
+  for (index=0; index<newCount; index+=1) {
+    unsigned char key = newKeys[index];
+
+    if (BITMASK_TEST(pressedKeys, key)) {
+      enqueueHidKeyEvent(key, 1);
+    }
+  }
+
+  *oldPacket = *newPacket;
+}
diff --git a/Programs/hidkeys.h b/Programs/hidkeys.h
new file mode 100644
index 0000000..cc3de80
--- /dev/null
+++ b/Programs/hidkeys.h
@@ -0,0 +1,42 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HIDKEYS
+#define BRLTTY_INCLUDED_HIDKEYS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  uint8_t modifiers;
+  uint8_t reserved;
+  uint8_t keys[6];
+} PACKED HidKeyboardPacket;
+
+extern void initializeHidKeyboardPacket (HidKeyboardPacket *packet);
+extern void processHidKeyboardPacket (
+  HidKeyboardPacket *oldPacket,
+  const HidKeyboardPacket *newPacket
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HIDKEYS */
diff --git a/Programs/hostcmd.c b/Programs/hostcmd.c
new file mode 100644
index 0000000..3145a5a
--- /dev/null
+++ b/Programs/hostcmd.c
@@ -0,0 +1,165 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "hostcmd.h"
+
+#if defined(USE_PKG_HOSTCMD_NONE)
+#include "hostcmd_none.h"
+#elif defined(USE_PKG_HOSTCMD_UNIX)
+#include "hostcmd_unix.h"
+#elif defined(USE_PKG_HOSTCMD_WINDOWS)
+#include "hostcmd_windows.h"
+#else /* host command package */
+#warning host command package not selected
+#include "hostcmd_none.h"
+#endif /* host command package */
+
+#include "hostcmd_internal.h"
+
+int
+finishHostCommandStream (HostCommandStream *hcs, int fileDescriptor) {
+  const char *mode = hcs->isInput? "w": "r";
+
+  if ((**hcs->streamVariable = fdopen(fileDescriptor, mode))) {
+    return 1;
+  } else {
+    logSystemError("fdopen");
+  }
+
+  return 0;
+}
+
+int
+processHostCommandStreams (
+  HostCommandStream *hcs,
+  HostCommandStreamProcessor *processStream,
+  void *data
+) {
+  while (hcs->streamVariable) {
+    if (*hcs->streamVariable) {
+      if (!processStream(hcs, data)) {
+        return 0;
+      }
+    }
+
+    hcs += 1;
+  }
+
+  return 1;
+}
+
+void
+initializeHostCommandOptions (HostCommandOptions *options) {
+  memset(options, 0, sizeof(*options));
+  options->asynchronous = 0;
+
+  options->standardInput = NULL;
+  options->standardOutput = NULL;
+  options->standardError = NULL;
+}
+
+static int
+constructHostCommandStream (HostCommandStream *hcs, void *data) {
+  **hcs->streamVariable = NULL;
+  return constructHostCommandPackageData(&hcs->package);
+}
+
+static int
+destructHostCommandStream (HostCommandStream *hcs, void *data) {
+  destructHostCommandPackageData(&hcs->package);
+
+  if (**hcs->streamVariable) {
+    fclose(**hcs->streamVariable);
+    **hcs->streamVariable = NULL;
+  }
+
+  return 1;
+}
+
+int
+runHostCommand (
+  const char *const *command,
+  const HostCommandOptions *options
+) {
+  int result = 0XFF;
+  HostCommandOptions defaults;
+
+  if (!options) {
+    initializeHostCommandOptions(&defaults);
+    options = &defaults;
+  }
+
+  {
+    char buffer[0X100];
+    STR_BEGIN(buffer, sizeof(buffer));
+
+    {
+      const char *const *argument = command;
+      while (*argument) STR_PRINTF(" %s", *argument++);
+    }
+
+    STR_END;
+    logMessage(LOG_DEBUG, "starting host command:%s", buffer);
+  }
+
+  {
+    HostCommandStream streams[] = {
+      { .streamVariable = &options->standardInput,
+        .fileDescriptor = 0,
+        .isInput = 1
+      },
+
+      { .streamVariable = &options->standardOutput,
+        .fileDescriptor = 1,
+        .isInput = 0
+      },
+
+      { .streamVariable = &options->standardError,
+        .fileDescriptor = 2,
+        .isInput = 0
+      },
+
+      { .streamVariable = NULL }
+    };
+
+    if (processHostCommandStreams(streams, constructHostCommandStream, NULL)) {
+      int ok = 0;
+
+      if (processHostCommandStreams(streams, prepareHostCommandStream, NULL)) {
+        if (runCommand(&result, command, streams, options->asynchronous)) {
+          ok = 1;
+        }
+      }
+
+      if (!ok) processHostCommandStreams(streams, destructHostCommandStream, NULL);
+    }
+  }
+
+  return result;
+}
+
+int
+executeHostCommand (const char *const *command) {
+  return runHostCommand(command, NULL);
+}
diff --git a/Programs/hostcmd_internal.h b/Programs/hostcmd_internal.h
new file mode 100644
index 0000000..4bfb623
--- /dev/null
+++ b/Programs/hostcmd_internal.h
@@ -0,0 +1,61 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HOSTCMD_INTERNAL
+#define BRLTTY_INCLUDED_HOSTCMD_INTERNAL
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  FILE **const *const streamVariable;
+  const int fileDescriptor;
+  const unsigned isInput:1;
+
+  HostCommandPackageData package;
+} HostCommandStream;
+
+typedef int HostCommandStreamProcessor (HostCommandStream *hcs, void *data);
+
+extern int processHostCommandStreams (
+  HostCommandStream *hcs,
+  HostCommandStreamProcessor *processStream,
+  void *data
+);
+
+extern int finishHostCommandStream (HostCommandStream *hcs, int fileDescriptor);
+
+extern int constructHostCommandPackageData (HostCommandPackageData *pkg);
+extern void destructHostCommandPackageData (HostCommandPackageData *pkg);
+extern int prepareHostCommandStream (HostCommandStream *hcs, void *data);
+
+extern int runCommand (
+  int *result,
+  const char *const *command,
+  HostCommandStream *streams,
+  int asynchronous
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HOSTCMD_INTERNAL */
diff --git a/Programs/hostcmd_none.c b/Programs/hostcmd_none.c
new file mode 100644
index 0000000..389d9c9
--- /dev/null
+++ b/Programs/hostcmd_none.c
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "hostcmd_none.h"
+#include "hostcmd_internal.h"
+
+int
+constructHostCommandPackageData (HostCommandPackageData *pkg) {
+  return 1;
+}
+
+void
+destructHostCommandPackageData (HostCommandPackageData *pkg) {
+}
+
+int
+prepareHostCommandStream (HostCommandStream *hcs, void *data) {
+  return 1;
+}
+
+int
+runCommand (
+  int *result,
+  const char *const *command,
+  HostCommandStream *streams,
+  int asynchronous
+) {
+  return 0;
+}
diff --git a/Programs/hostcmd_none.h b/Programs/hostcmd_none.h
new file mode 100644
index 0000000..bd87460
--- /dev/null
+++ b/Programs/hostcmd_none.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HOSTCMD_NONE
+#define BRLTTY_INCLUDED_HOSTCMD_NONE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  unsigned char dummy;
+} HostCommandPackageData;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HOSTCMD_NONE */
diff --git a/Programs/hostcmd_unix.c b/Programs/hostcmd_unix.c
new file mode 100644
index 0000000..3ab65d2
--- /dev/null
+++ b/Programs/hostcmd_unix.c
@@ -0,0 +1,203 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include "log.h"
+#include "hostcmd_unix.h"
+#include "hostcmd_internal.h"
+
+static int *
+getPipeDescriptor (HostCommandStream *hcs, unsigned int index) {
+  return &hcs->package.pipe[index];
+}
+
+static int *
+getInputDescriptor (HostCommandStream *hcs) {
+  return getPipeDescriptor(hcs, 0);
+}
+
+static int *
+getOutputDescriptor (HostCommandStream *hcs) {
+  return getPipeDescriptor(hcs, 1);
+}
+
+static int *
+getParentDescriptor (HostCommandStream *hcs) {
+  return hcs->isInput? getOutputDescriptor(hcs): getInputDescriptor(hcs);
+}
+
+static int *
+getChildDescriptor (HostCommandStream *hcs) {
+  return hcs->isInput? getInputDescriptor(hcs): getOutputDescriptor(hcs);
+}
+
+int
+constructHostCommandPackageData (HostCommandPackageData *pkg) {
+  pkg->pipe[0] = pkg->pipe[1] = -1;
+  return 1;
+}
+
+void
+destructHostCommandPackageData (HostCommandPackageData *pkg) {
+  {
+    int *fileDescriptor = pkg->pipe;
+    const int *end = fileDescriptor + 2;
+
+    while (fileDescriptor < end) {
+      if (*fileDescriptor != -1) {
+        close(*fileDescriptor);
+        *fileDescriptor = -1;
+      }
+
+      fileDescriptor += 1;
+    }
+  }
+}
+
+int
+prepareHostCommandStream (HostCommandStream *hcs, void *data) {
+  if (pipe(hcs->package.pipe) == -1) {
+    logSystemError("pipe");
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+finishParentHostCommandStream (HostCommandStream *hcs, void *data) {
+  int *local = getParentDescriptor(hcs);
+  int *remote = getChildDescriptor(hcs);
+
+  close(*remote);
+  *remote = -1;
+
+  if (!finishHostCommandStream(hcs, *local)) return 0;
+  *local = -1;
+
+  return 1;
+}
+
+static int
+finishChildHostCommandStream (HostCommandStream *hcs, void *data) {
+  int *local = getChildDescriptor(hcs);
+  int *remote = getParentDescriptor(hcs);
+
+  close(*remote);
+  *remote = -1;
+
+  if (close(hcs->fileDescriptor) == -1) {
+    logSystemError("close");
+    return 0;
+  }
+
+  if (fcntl(*local, F_DUPFD, hcs->fileDescriptor) == -1) {
+    logSystemError("fcntl[F_DUPFD]");
+    return 0;
+  }
+
+  close(*local);
+  *local = -1;
+
+  return 1;
+}
+
+int
+runCommand (
+  int *result,
+  const char *const *command,
+  HostCommandStream *streams,
+  int asynchronous
+) {
+  int ok = 0;
+  sigset_t newMask, oldMask;
+  pid_t pid;
+
+  sigemptyset(&newMask);
+  sigaddset(&newMask, SIGCHLD);
+  sigprocmask(SIG_BLOCK, &newMask, &oldMask);
+
+  switch ((pid = fork())) {
+    case -1: /* error */
+      logSystemError("fork");
+      break;
+
+    case 0: /* child */
+      sigprocmask(SIG_SETMASK, &oldMask, NULL);
+
+      if (processHostCommandStreams(streams, finishChildHostCommandStream, NULL)) {
+        execvp(command[0], (char *const*)command);
+
+        switch (errno) {
+          case ENOENT:
+            logMessage(LOG_ERR, "command not found: %s", command[0]);
+            break;
+
+          default:
+            logSystemError("execvp");
+            break;
+        }
+      }
+
+      _exit(1);
+
+    default: /* parent */
+      if (processHostCommandStreams(streams, finishParentHostCommandStream, NULL)) {
+        ok = 1;
+
+        if (asynchronous) {
+          *result = 0;
+        } else {
+          int status;
+
+          if (waitpid(pid, &status, 0) == -1) {
+            logSystemError("waitpid");
+          } else if (WIFEXITED(status)) {
+            *result = WEXITSTATUS(status);
+            logMessage(LOG_DEBUG, "host command exit status: %d: %s",
+                       *result, command[0]);
+          } else if (WIFSIGNALED(status)) {
+            *result = WTERMSIG(status);
+            logMessage(LOG_DEBUG, "host command termination signal: %d: %s",
+                       *result, command[0]);
+            *result += 0X80;
+          } else if (WIFSTOPPED(status)) {
+            *result = WSTOPSIG(status);
+            logMessage(LOG_DEBUG, "host command stop signal: %d: %s",
+                       *result, command[0]);
+            *result += 0X80;
+          } else {
+            logMessage(LOG_DEBUG, "unknown host command status: 0X%X: %s",
+                       status, command[0]);
+          }
+        }
+      }
+
+      break;
+  }
+
+  sigprocmask(SIG_SETMASK, &oldMask, NULL);
+  return ok;
+}
diff --git a/Programs/hostcmd_unix.h b/Programs/hostcmd_unix.h
new file mode 100644
index 0000000..ba55e86
--- /dev/null
+++ b/Programs/hostcmd_unix.h
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HOSTCMD_UNIX
+#define BRLTTY_INCLUDED_HOSTCMD_UNIX
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  int pipe[2];
+} HostCommandPackageData;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HOSTCMD_UNIX */
diff --git a/Programs/hostcmd_windows.c b/Programs/hostcmd_windows.c
new file mode 100644
index 0000000..e0b0041
--- /dev/null
+++ b/Programs/hostcmd_windows.c
@@ -0,0 +1,217 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <io.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "system_windows.h"
+#include "hostcmd_windows.h"
+#include "hostcmd_internal.h"
+
+typedef struct {
+  DWORD identifier;
+  HANDLE *field;
+} HandleEntry;
+
+static void
+closeHandle (HANDLE *handle) {
+  if (*handle != INVALID_HANDLE_VALUE) {
+    if (*handle) {
+      CloseHandle(*handle);
+    }
+
+    *handle = INVALID_HANDLE_VALUE;
+  }
+}
+
+static HANDLE *
+getParentHandle (HostCommandStream *hcs) {
+  return hcs->isInput? &hcs->package.outputHandle: &hcs->package.inputHandle;
+}
+
+static HANDLE *
+getChildHandle (HostCommandStream *hcs) {
+  return hcs->isInput? &hcs->package.inputHandle: &hcs->package.outputHandle;
+}
+
+int
+constructHostCommandPackageData (HostCommandPackageData *pkg) {
+  pkg->inputHandle = INVALID_HANDLE_VALUE;
+  pkg->outputHandle = INVALID_HANDLE_VALUE;
+  return 1;
+}
+
+void
+destructHostCommandPackageData (HostCommandPackageData *pkg) {
+  closeHandle(&pkg->inputHandle);
+  closeHandle(&pkg->outputHandle);
+}
+
+int
+prepareHostCommandStream (HostCommandStream *hcs, void *data) {
+  SECURITY_ATTRIBUTES attributes;
+
+  ZeroMemory(&attributes, sizeof(attributes));
+  attributes.nLength = sizeof(attributes);
+  attributes.bInheritHandle = TRUE;
+  attributes.lpSecurityDescriptor = NULL;
+
+  if (CreatePipe(&hcs->package.inputHandle, &hcs->package.outputHandle, &attributes, 0)) {
+    if (SetHandleInformation(*getParentHandle(hcs), HANDLE_FLAG_INHERIT, 0)) {
+      return 1;
+    } else {
+      logWindowsSystemError("SetHandleInformation");
+    }
+  } else {
+    logWindowsSystemError("CreatePipe");
+  }
+
+  return 0;
+}
+
+typedef struct {
+  const HandleEntry *const handleTable;
+} SetChildHandleData;
+
+static int
+setChildHandle (HostCommandStream *hcs, void *data) {
+  SetChildHandleData *sch = data;
+
+  *sch->handleTable[hcs->fileDescriptor].field = *getChildHandle(hcs);
+  return 1;
+}
+
+static int
+finishParentHostCommandStream (HostCommandStream *hcs, void *data) {
+  {
+    HANDLE *handle = getParentHandle(hcs);
+    int mode = hcs->isInput? O_WRONLY: O_RDONLY;
+    int fileDescriptor;
+
+    if ((fileDescriptor = _open_osfhandle((intptr_t)*handle, mode)) == -1) {
+      logSystemError("_open_osfhandle");
+      return 0;
+    }
+    *handle = INVALID_HANDLE_VALUE;
+
+    if (!finishHostCommandStream(hcs, fileDescriptor)) {
+      _close(fileDescriptor);
+      return 0;
+    }
+  }
+
+  closeHandle(getChildHandle(hcs));
+  return 1;
+}
+
+int
+runCommand (
+  int *result,
+  const char *const *command,
+  HostCommandStream *streams,
+  int asynchronous
+) {
+  int ok = 0;
+  char *line = makeWindowsCommandLine(command);
+
+  if (line) {
+    STARTUPINFO startup;
+
+    const HandleEntry handleTable[] = {
+      [0] = {.identifier=STD_INPUT_HANDLE , .field=&startup.hStdInput },
+      [1] = {.identifier=STD_OUTPUT_HANDLE, .field=&startup.hStdOutput},
+      [2] = {.identifier=STD_ERROR_HANDLE , .field=&startup.hStdError },
+    };
+
+    const unsigned int handleCount = ARRAY_COUNT(handleTable);
+    const HandleEntry *const handleEnd = handleTable + handleCount;
+
+    SetChildHandleData sch = {
+      .handleTable = handleTable
+    };
+
+    logMessage(LOG_DEBUG, "host command: %s", line);
+
+    ZeroMemory(&startup, sizeof(startup));
+    startup.cb = sizeof(startup);
+    startup.dwFlags = STARTF_USESTDHANDLES;
+
+    {
+      const HandleEntry *hdl = handleTable;
+
+      while (hdl < handleEnd) {
+        if ((*hdl->field = GetStdHandle(hdl->identifier)) == INVALID_HANDLE_VALUE) {
+          logWindowsSystemError("GetStdHandle");
+          return 0;
+        }
+
+        hdl += 1;
+      }
+    }
+
+    if (processHostCommandStreams(streams, setChildHandle, &sch)) {
+      PROCESS_INFORMATION info;
+
+      ZeroMemory(&info, sizeof(info));
+
+      if (CreateProcess(NULL, line, NULL, NULL, TRUE,
+                        CREATE_NEW_PROCESS_GROUP,
+                        NULL, NULL, &startup, &info)) {
+        if (processHostCommandStreams(streams, finishParentHostCommandStream, NULL)) {
+          ok = 1;
+
+          if (asynchronous) {
+            *result = 0;
+          } else {
+            DWORD waitResult;
+
+            *result = 0XFF;
+
+            while ((waitResult = WaitForSingleObject(info.hProcess, INFINITE)) == WAIT_TIMEOUT);
+
+            if (waitResult == WAIT_OBJECT_0) {
+              DWORD exitCode;
+
+              if (GetExitCodeProcess(info.hProcess, &exitCode)) {
+                *result = exitCode;
+              } else {
+                logWindowsSystemError("GetExitCodeProcess");
+              }
+            } else {
+              logWindowsSystemError("WaitForSingleObject");
+            }
+          }
+        }
+
+        CloseHandle(info.hProcess);
+        CloseHandle(info.hThread);
+      } else {
+        logWindowsSystemError("CreateProcess");
+      }
+    }
+
+    free(line);
+  } else {
+    logMallocError();
+  }
+
+  return ok;
+}
diff --git a/Programs/hostcmd_windows.h b/Programs/hostcmd_windows.h
new file mode 100644
index 0000000..01f6c22
--- /dev/null
+++ b/Programs/hostcmd_windows.h
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_HOSTCMD_WINDOWS
+#define BRLTTY_INCLUDED_HOSTCMD_WINDOWS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  HANDLE inputHandle;
+  HANDLE outputHandle;
+} HostCommandPackageData;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_HOSTCMD_WINDOWS */
diff --git a/Programs/ihex.c b/Programs/ihex.c
new file mode 100644
index 0000000..72ecad6
--- /dev/null
+++ b/Programs/ihex.c
@@ -0,0 +1,377 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "ihex.h"
+#include "file.h"
+#include "datafile.h"
+
+#define IHEX_PARSE_VERIFY 0
+
+#define IHEX_RECORD_PREFIX  ':'
+#define IHEX_COMMENT_PREFIX '#'
+
+#define IHEX_BYTE_WIDTH 8
+#define IHEX_BYTE_MASK ((1 << IHEX_BYTE_WIDTH) - 1)
+
+static size_t
+ihexByteCount (size_t count) {
+  return 1 // the number of data bytes
+       + 2 // the starting address
+       + 1 // the record type
+       + count // the data
+       + 1 // the checksum
+       ;
+}
+
+size_t
+ihexRecordLength (size_t count) {
+  return 1 // the colon prefix
+       + (ihexByteCount(count) * 2) // hexadecimal digit pairs
+       ;
+}
+
+int
+ihexMakeRecord (char *buffer, size_t size, IhexType type, IhexAddress address, const IhexByte *data, IhexCount count) {
+  IhexByte bytes[ihexByteCount(count)];
+  IhexByte *end = bytes;
+
+  *end++ = count;
+  *end++ = (address >> IHEX_BYTE_WIDTH) & IHEX_BYTE_MASK;
+  *end++ = address & IHEX_BYTE_MASK;
+  *end++ = type;
+  if (count > 0) end = mempcpy(end, data, count);
+
+  {
+    uint32_t checksum = 0;
+
+    {
+      const IhexByte *byte = bytes;
+      while (byte < end) checksum += *byte++;
+    }
+
+    checksum ^= IHEX_BYTE_MASK;
+    checksum += 1;
+    *end++ = checksum & IHEX_BYTE_MASK;
+  }
+
+  if ((1 + (end - bytes) + 1) > size) return 0;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%c", IHEX_RECORD_PREFIX);
+
+  {
+    const IhexByte *byte = bytes;
+    while (byte < end) STR_PRINTF("%02X", *byte++);
+  }
+
+  STR_END;
+  return 1;
+}
+
+int
+ihexMakeDataRecord (char *buffer, size_t size, IhexAddress address, const IhexByte *data, IhexCount count) {
+  return ihexMakeRecord(buffer, size, IHEX_TYPE_DATA, address, data, count);
+}
+
+int
+ihexMakeEndRecord (char *buffer, size_t size) {
+  return ihexMakeRecord(buffer, size, IHEX_TYPE_END, 0, NULL, 0);
+}
+
+typedef struct {
+  const char *record;
+  const char *source;
+  unsigned int line;
+
+  unsigned char error:1;
+} IhexRecordProcessingData;
+
+typedef struct {
+  IhexRecordProcessingData rpd;
+  IhexRecordHandler *handler;
+  void *data;
+} IhexFileProcessingData;
+
+static void
+ihexReportProblem (IhexRecordProcessingData *rpd, const char *message) {
+  rpd->error = 1;
+
+  logMessage(LOG_ERR,
+    "ihex error: %s: %s[%u]: %s",
+    message, rpd->source, rpd->line, rpd->record
+  );
+}
+
+static int
+ihexCheckDigit (IhexRecordProcessingData *rpd, unsigned char *value, char digit) {
+  typedef struct {
+    char first;
+    char last;
+    char offset;
+  } Range;
+
+  static const Range ranges[] = {
+    { .first='0', .last='9', .offset= 0 },
+    { .first='A', .last='F', .offset=10 },
+    { .first='a', .last='f', .offset=10 },
+  };
+
+  const Range *range = ranges;
+  const Range *end = range + ARRAY_COUNT(ranges);
+
+  while (range < end) {
+    if ((digit >= range->first) && (digit <= range->last)) {
+      *value = (digit - range->first) + range->offset;
+      return 1;
+    }
+
+    range += 1;
+  }
+
+  ihexReportProblem(rpd, "invalid hexadecimal digit");
+  return 0;
+}
+
+static IhexParsedRecord *
+ihexParseRecord (IhexRecordProcessingData *rpd) {
+  const char *character = rpd->record;
+
+  if (!*character || (*character != IHEX_RECORD_PREFIX)) {
+    ihexReportProblem(rpd, "not an ihex record");
+    return NULL;
+  }
+
+  size_t length = strlen(++character);
+  IhexByte bytes[length + 1]; // +1 in case length is 0
+  IhexByte *end = bytes;
+  int first = 1;
+
+  while (*character) {
+    unsigned char value;
+    if (!ihexCheckDigit(rpd, &value, *character)) return NULL;
+
+    if (first) {
+      *end = value << 4;
+    } else {
+      *end++ |= value;
+    }
+
+    first = !first;
+    character += 1;
+  }
+
+  if (!first) {
+    ihexReportProblem(rpd, "missing hexadecimal digit");
+    return NULL;
+  }
+
+  {
+    uint32_t checksum = 0;
+    const IhexByte *byte = bytes;
+    while (byte < end) checksum += *byte++;
+    checksum &= IHEX_BYTE_MASK;
+
+    if (checksum) {
+      ihexReportProblem(rpd, "checksum mismatch");
+      return NULL;
+    }
+  }
+
+  const IhexByte *byte = bytes;
+  size_t actualCount = end - byte;
+
+  {
+    static const char *const messages[] = {
+      [0] = "missing data byte count",
+      [1] = "missing address",
+      [2] = "incomplete address",
+      [3] = "missing record type",
+    };
+
+    if (actualCount < ARRAY_COUNT(messages)) {
+      const char *message = messages[actualCount];
+      if (!message) message = "unknown error";
+
+      ihexReportProblem(rpd, message);
+      return NULL;
+    }
+  }
+
+  IhexCount count = *byte++;
+  size_t expectCount = ihexByteCount(count);
+
+  if (actualCount < expectCount) {
+    ihexReportProblem(rpd, "truncated data");
+    return NULL;
+  }
+
+  if (actualCount > expectCount) {
+    ihexReportProblem(rpd, "excessive data");
+    return NULL;
+  }
+
+  IhexParsedRecord *record;
+  size_t size = sizeof(*record) + count;
+  record = malloc(size);
+
+  if (!record) {
+    logMallocError();
+    return NULL;
+  }
+
+  memset(record, 0, size);
+  record->count = count;
+  record->address = *byte++ << IHEX_BYTE_WIDTH;
+  record->address |= *byte++;
+  record->type = *byte++;
+  memcpy(record->data, byte, count);
+
+  if (IHEX_PARSE_VERIFY) {
+    const char *expect = rpd->record;
+    char actual[ihexRecordLength(record->count) + 1];
+
+    ihexMakeRecord(
+      actual, sizeof(actual),
+      record->type, record->address,
+      record->data, record->count
+    );
+
+    if (strcmp(actual, expect) != 0) {
+      ihexReportProblem(rpd, "ihex parse mismatch");
+      logMessage(LOG_DEBUG, "expect: %s", expect);
+      logMessage(LOG_DEBUG, "actual: %s", actual);
+
+      free(record);
+      return NULL;
+    }
+  }
+
+  return record;
+}
+
+static int
+ihexCallHandler (IhexFileProcessingData *fpd, const IhexParsedRecord *record) {
+  IhexRecordProcessingData *rpd = &fpd->rpd;
+
+  switch (record->type) {
+    case IHEX_TYPE_DATA:
+      if (!record->count) return 0;
+      break;
+
+    case IHEX_TYPE_END:
+      return 0;
+
+    default:
+      ihexReportProblem(rpd, "unsupported record type");
+      return 0;
+  }
+
+  if (!fpd->handler(record, fpd->data)) {
+    ihexReportProblem(rpd, "record handler failed");
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+ihexProcessLine (const LineHandlerParameters *parameters) {
+  IhexFileProcessingData *fpd = parameters->data;
+  IhexRecordProcessingData *rpd = &fpd->rpd;
+  rpd->line += 1;
+
+  const char *line = parameters->line.text;
+  while (*line == ' ') line += 1;
+  if (!*line) return 1;
+  if (*line == IHEX_COMMENT_PREFIX) return 1;
+
+  rpd->record = line;
+  IhexParsedRecord *record = ihexParseRecord(rpd);
+  int ok = 0;
+
+  if (record) {
+    if (ihexCallHandler(fpd, record)) {
+      ok = 1;
+    }
+
+    free(record);
+  }
+
+  return ok;
+}
+
+int
+ihexProcessFile (const char *path, IhexRecordHandler *handler, void *data) {
+  IhexFileProcessingData fpd = {
+    .rpd = {
+      .source = path,
+      .line = 0
+    },
+
+    .handler = handler,
+    .data = data
+  };
+
+  int ok = 0;
+  FILE *file = openDataFile(path, "r", 0);
+
+  if (file) {
+    if (processLines(file, ihexProcessLine, &fpd)) {
+      if (!fpd.rpd.error) {
+        ok = 1;
+      }
+    }
+
+    fclose(file);
+  } else if (errno == ENOENT) {
+    char *url = makePath(PACKAGE_URL, IHEX_FILES_SUBDIRECTORY);
+
+    if (url) {
+      logMessage(LOG_WARNING, "missing firmware blobs can be downloaded from %s", url);
+      free(url);
+    }
+  }
+
+  return ok;
+}
+
+char *
+ihexEnsureExtension (const char *path) {
+  return ensureFileExtension(path, IHEX_FILE_EXTENSION);
+}
+
+char *
+ihexMakePath (const char *directory, const char *name) {
+  char *subdirectory = makePath(directory, IHEX_FILES_SUBDIRECTORY);
+
+  if (subdirectory) {
+    char *file = makeFilePath(subdirectory, name, IHEX_FILE_EXTENSION);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
diff --git a/Programs/io_log.c b/Programs/io_log.c
new file mode 100644
index 0000000..4e43258
--- /dev/null
+++ b/Programs/io_log.c
@@ -0,0 +1,47 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "io_log.h"
+#include "log.h"
+
+void
+logUnsupportedBaud (unsigned int baud) {
+  logMessage(LOG_WARNING, "unsupported baud: %u", baud);
+}
+
+void
+logUnsupportedDataBits (unsigned int dataBits) {
+  logMessage(LOG_WARNING, "unsupported data bits: %u", dataBits);
+}
+
+void
+logUnsupportedStopBits (SerialStopBits stopBits) {
+  logMessage(LOG_WARNING, "unsupported stop bits: %u", stopBits);
+}
+
+void
+logUnsupportedParity (SerialParity parity) {
+  logMessage(LOG_WARNING, "unsupported parity: %u", parity);
+}
+
+void
+logUnsupportedFlowControl (SerialFlowControl flowControl) {
+  logMessage(LOG_WARNING, "unsupported flow control: %02X", flowControl);
+}
diff --git a/Programs/io_log.h b/Programs/io_log.h
new file mode 100644
index 0000000..aa7c6c9
--- /dev/null
+++ b/Programs/io_log.h
@@ -0,0 +1,38 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_IO_LOG
+#define BRLTTY_INCLUDED_IO_LOG
+
+#include "serial_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void logUnsupportedBaud (unsigned int baud);
+extern void logUnsupportedDataBits (unsigned int dataBits);
+extern void logUnsupportedStopBits (SerialStopBits stopBits);
+extern void logUnsupportedParity (SerialParity parity);
+extern void logUnsupportedFlowControl (SerialFlowControl flowControl);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_IO_LOG */
diff --git a/Programs/io_misc.c b/Programs/io_misc.c
new file mode 100644
index 0000000..ff8bdab
--- /dev/null
+++ b/Programs/io_misc.c
@@ -0,0 +1,507 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "io_misc.h"
+#include "log.h"
+#include "file.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_io.h"
+
+typedef struct InputOutputMethodsStruct InputOutputMethods;
+
+typedef struct {
+  const InputOutputMethods *methods;
+
+  union {
+    FileDescriptor file;
+    SocketDescriptor socket;
+  } descriptor;
+} InputOutputHandle;
+
+typedef struct {
+  unsigned ready:1;
+} InputOutputMonitor;
+
+typedef int MonitorInputOutputMethod (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom);
+typedef ssize_t ReadDataMethod (const InputOutputHandle *ioh, void *buffer, size_t size);
+typedef ssize_t WriteDataMethod (const InputOutputHandle *ioh, const void *buffer, size_t size);
+
+struct InputOutputMethodsStruct {
+  MonitorInputOutputMethod *monitorInput;
+  MonitorInputOutputMethod *monitorOutput;
+  MonitorInputOutputMethod *monitorAlert;
+
+  ReadDataMethod *readData;
+  WriteDataMethod *writeData;
+};
+
+ASYNC_MONITOR_CALLBACK(setInputOutputMonitor) {
+  InputOutputMonitor *iom = parameters->data;
+
+  iom->ready = 1;
+  return 0;
+}
+
+ASYNC_CONDITION_TESTER(testInputOutputMonitor) {
+  InputOutputMonitor *iom = data;
+
+  return iom->ready;
+}
+
+static int
+awaitInputOutput (const InputOutputHandle *ioh, int timeout, MonitorInputOutputMethod *monitorInputOutput) {
+  InputOutputMonitor iom = {
+    .ready = 0
+  };
+
+  AsyncHandle monitor;
+
+  if (monitorInputOutput(ioh, &monitor, &iom)) {
+    asyncAwaitCondition(timeout, testInputOutputMonitor, &iom);
+    asyncCancelRequest(monitor);
+    if (iom.ready) return 1;
+
+#ifdef ETIMEDOUT
+    errno = ETIMEDOUT;
+#else /* ETIMEDOUT */
+    errno = EAGAIN;
+#endif /* ETIMEDOUT */
+  }
+
+  return 0;
+}
+
+static int
+awaitInput (const InputOutputHandle *ioh, int timeout) {
+  return awaitInputOutput(ioh, timeout, ioh->methods->monitorInput);
+}
+
+static int
+awaitOutput (const InputOutputHandle *ioh, int timeout) {
+  return awaitInputOutput(ioh, timeout, ioh->methods->monitorOutput);
+}
+
+static int
+awaitAlert (const InputOutputHandle *ioh, int timeout) {
+  return awaitInputOutput(ioh, timeout, ioh->methods->monitorAlert);
+}
+
+static ssize_t
+readData (
+  const InputOutputHandle *ioh,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  unsigned char *address = buffer;
+
+#ifdef __MSDOS__
+  int tried = 0;
+  goto noInput;
+#endif /* __MSDOS__ */
+
+  while (size > 0) {
+    ssize_t count = ioh->methods->readData(ioh, address, size);
+
+#ifdef __MSDOS__
+    tried = 1;
+#endif /* __MSDOS__ */
+
+    if (count == -1) {
+      if (errno == EINTR) continue;
+      if (errno == EAGAIN) goto noInput;
+
+#ifdef EWOULDBLOCK
+      if (errno == EWOULDBLOCK) goto noInput;
+#endif /* EWOULDBLOCK */
+
+      logSystemError("read");
+      return count;
+    }
+
+    if (!count) {
+      unsigned char *start;
+      unsigned int offset;
+      int timeout;
+
+    noInput:
+      start = buffer;
+      offset = address - start;
+      timeout = offset? subsequentTimeout: initialTimeout;
+
+      if (timeout) {
+        if (awaitInput(ioh, timeout)) continue;
+      } else
+
+#ifdef __MSDOS__
+      if (!tried) {
+        if (awaitInput(ioh, 0)) continue;
+      } else
+#endif /* __MSDOS__ */
+
+      {
+        errno = EAGAIN;
+      }
+
+      break;
+    }
+
+    address += count;
+    size -= count;
+  }
+
+  {
+    unsigned char *start = buffer;
+
+    return address - start;
+  }
+}
+
+static ssize_t
+writeData (const InputOutputHandle *ioh, const void *buffer, size_t size) {
+  const unsigned char *address = buffer;
+
+canWrite:
+  while (size > 0) {
+    ssize_t count = ioh->methods->writeData(ioh, address, size);
+
+    if (count == -1) {
+      if (errno == EINTR) continue;
+      if (errno == EAGAIN) goto noOutput;
+
+#ifdef EWOULDBLOCK
+      if (errno == EWOULDBLOCK) goto noOutput;
+#endif /* EWOULDBLOCK */
+
+      logSystemError("Write");
+      return count;
+    }
+
+    if (!count) {
+      errno = EAGAIN;
+
+    noOutput:
+      do {
+        if (awaitOutput(ioh, 15000)) goto canWrite;
+      } while (errno == EAGAIN);
+
+      return -1;
+    }
+
+    address += count;
+    size -= count;
+  }
+
+  {
+    const unsigned char *start = buffer;
+    return address - start;
+  }
+}
+
+static int
+monitorFileInput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorFileInput(handle, ioh->descriptor.file, setInputOutputMonitor, iom);
+}
+
+static int
+monitorFileOutput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorFileOutput(handle, ioh->descriptor.file, setInputOutputMonitor, iom);
+}
+
+static int
+monitorFileAlert (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorFileAlert(handle, ioh->descriptor.file, setInputOutputMonitor, iom);
+}
+
+static ssize_t
+readFileData (const InputOutputHandle *ioh, void *buffer, size_t size) {
+  return readFileDescriptor(ioh->descriptor.file, buffer, size);
+}
+
+static ssize_t
+writeFileData (const InputOutputHandle *ioh, const void *buffer, size_t size) {
+  return writeFileDescriptor(ioh->descriptor.file, buffer, size);
+}
+
+static const InputOutputMethods fileMethods = {
+  .monitorInput = monitorFileInput,
+  .monitorOutput = monitorFileOutput,
+  .monitorAlert = monitorFileAlert,
+
+  .readData = readFileData,
+  .writeData = writeFileData
+};
+
+static void
+makeFileHandle (InputOutputHandle *ioh, FileDescriptor fileDescriptor) {
+  ioh->methods = &fileMethods;
+  ioh->descriptor.file = fileDescriptor;
+}
+
+void
+closeFile (FileDescriptor *fileDescriptor) {
+  if (*fileDescriptor != INVALID_FILE_DESCRIPTOR) {
+    closeFileDescriptor(*fileDescriptor);
+    *fileDescriptor = INVALID_FILE_DESCRIPTOR;
+  }
+}
+
+int
+awaitFileInput (FileDescriptor fileDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeFileHandle(&ioh, fileDescriptor);
+  return awaitInput(&ioh, timeout);
+}
+
+int
+awaitFileOutput (FileDescriptor fileDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeFileHandle(&ioh, fileDescriptor);
+  return awaitOutput(&ioh, timeout);
+}
+
+int
+awaitFileAlert (FileDescriptor fileDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeFileHandle(&ioh, fileDescriptor);
+  return awaitAlert(&ioh, timeout);
+}
+
+ssize_t
+readFile (
+  FileDescriptor fileDescriptor, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  InputOutputHandle ioh;
+
+  makeFileHandle(&ioh, fileDescriptor);
+  return readData(&ioh, buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+writeFile (FileDescriptor fileDescriptor, const void *buffer, size_t size) {
+  InputOutputHandle ioh;
+
+  makeFileHandle(&ioh, fileDescriptor);
+  return writeData(&ioh, buffer, size);
+}
+
+#ifdef GOT_SOCKETS
+static int
+monitorSocketInput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorSocketInput(handle, ioh->descriptor.socket, setInputOutputMonitor, iom);
+}
+
+static int
+monitorSocketOutput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorSocketOutput(handle, ioh->descriptor.socket, setInputOutputMonitor, iom);
+}
+
+static int
+monitorSocketAlert (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) {
+  return asyncMonitorSocketAlert(handle, ioh->descriptor.socket, setInputOutputMonitor, iom);
+}
+
+static ssize_t
+readSocketData (const InputOutputHandle *ioh, void *buffer, size_t size) {
+  return readSocketDescriptor(ioh->descriptor.socket, buffer, size);
+}
+
+static ssize_t
+writeSocketData (const InputOutputHandle *ioh, const void *buffer, size_t size) {
+  return writeSocketDescriptor(ioh->descriptor.socket, buffer, size);
+}
+
+static const InputOutputMethods socketMethods = {
+  .monitorInput = monitorSocketInput,
+  .monitorOutput = monitorSocketOutput,
+  .monitorAlert = monitorSocketAlert,
+
+  .readData = readSocketData,
+  .writeData = writeSocketData
+};
+
+static void
+makeSocketHandle (InputOutputHandle *ioh, SocketDescriptor socketDescriptor) {
+  ioh->methods = &socketMethods;
+  ioh->descriptor.socket = socketDescriptor;
+}
+
+void
+closeSocket (SocketDescriptor *socketDescriptor) {
+  if (*socketDescriptor != INVALID_SOCKET_DESCRIPTOR) {
+    closeSocketDescriptor(*socketDescriptor);
+    *socketDescriptor = INVALID_SOCKET_DESCRIPTOR;
+  }
+}
+
+int
+awaitSocketInput (SocketDescriptor socketDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeSocketHandle(&ioh, socketDescriptor);
+  return awaitInput(&ioh, timeout);
+}
+
+int
+awaitSocketOutput (SocketDescriptor socketDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeSocketHandle(&ioh, socketDescriptor);
+  return awaitOutput(&ioh, timeout);
+}
+
+int
+awaitSocketAlert (SocketDescriptor socketDescriptor, int timeout) {
+  InputOutputHandle ioh;
+
+  makeSocketHandle(&ioh, socketDescriptor);
+  return awaitAlert(&ioh, timeout);
+}
+
+ssize_t
+readSocket (
+  SocketDescriptor socketDescriptor, void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  InputOutputHandle ioh;
+
+  makeSocketHandle(&ioh, socketDescriptor);
+  return readData(&ioh, buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+writeSocket (SocketDescriptor socketDescriptor, const void *buffer, size_t size) {
+  InputOutputHandle ioh;
+
+  makeSocketHandle(&ioh, socketDescriptor);
+  return writeData(&ioh, buffer, size);
+}
+
+int
+connectSocket (
+  SocketDescriptor socketDescriptor,
+  const struct sockaddr *address,
+  size_t addressLength,
+  int timeout
+) {
+  int result = connect(socketDescriptor, address, addressLength);
+
+  if (result == -1) {
+#ifdef EINPROGRESS
+    if (getSocketError() == EINPROGRESS) {
+      if (awaitSocketOutput(socketDescriptor, timeout)) {
+        int error;
+        socklen_t length = sizeof(error);
+
+        if (getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (void *)&error, &length) != -1) {
+          if (!error) return 0;
+          errno = error;
+        }
+      }
+    }
+#endif /* EINPROGRESS */
+  }
+
+  return result;
+}
+
+int
+setSocketLingerTime (SocketDescriptor socketDescriptor, int seconds) {
+  struct linger linger = {
+    .l_onoff = 1,
+    .l_linger = seconds
+  };
+
+  if (setsockopt(socketDescriptor, SOL_SOCKET, SO_LINGER, (const void *)&linger, sizeof(linger)) != -1) return 1;
+  logSystemError("setsockopt[SO_LINGER]");
+  return 0;
+}
+
+int
+setSocketNoLinger (SocketDescriptor socketDescriptor) {
+  return setSocketLingerTime(socketDescriptor, 0);
+}
+
+#else /* have sockets */
+#warning sockets not supported on this platform
+#endif /* GOT_SOCKETS */
+
+int
+changeOpenFlags (FileDescriptor fileDescriptor, int flagsToClear, int flagsToSet) {
+#if defined(F_GETFL) && defined(F_SETFL)
+  int flags;
+
+  if ((flags = fcntl(fileDescriptor, F_GETFL)) != -1) {
+    flags &= ~flagsToClear;
+    flags |= flagsToSet;
+    if (fcntl(fileDescriptor, F_SETFL, flags) != -1) {
+      return 1;
+    } else {
+      logSystemError("F_SETFL");
+    }
+  } else {
+    logSystemError("F_GETFL");
+  }
+#else /* defined(F_GETFL) && defined(F_SETFL) */
+  errno = ENOSYS;
+#endif /* defined(F_GETFL) && defined(F_SETFL) */
+
+  return 0;
+}
+
+int
+setOpenFlags (FileDescriptor fileDescriptor, int state, int flags) {
+  if (state) {
+    return changeOpenFlags(fileDescriptor, 0, flags);
+  } else {
+    return changeOpenFlags(fileDescriptor, flags, 0);
+  }
+}
+
+int
+setBlockingIo (FileDescriptor fileDescriptor, int state) {
+#ifdef O_NONBLOCK
+  if (setOpenFlags(fileDescriptor, !state, O_NONBLOCK)) return 1;
+#else /* O_NONBLOCK */
+  errno = ENOSYS;
+#endif /* O_NONBLOCK */
+
+  return 0;
+}
+
+int
+setCloseOnExec (FileDescriptor fileDescriptor, int state) {
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+  if (fcntl(fileDescriptor, F_SETFD, (state? FD_CLOEXEC: 0)) != -1) return 1;
+#else /* defined(F_SETFD) && defined(FD_CLOEXEC) */
+  errno = ENOSYS;
+#endif /* defined(F_SETFD) && defined(FD_CLOEXEC) */
+
+  return 0;
+}
diff --git a/Programs/kbd.c b/Programs/kbd.c
new file mode 100644
index 0000000..af5b2d2
--- /dev/null
+++ b/Programs/kbd.c
@@ -0,0 +1,326 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "parse.h"
+#include "bitmask.h"
+#include "kbd.h"
+#include "kbd_internal.h"
+
+const KeyboardProperties anyKeyboard = {
+  .type = KBD_TYPE_ANY,
+  .vendor = 0,
+  .product = 0
+};
+
+int
+parseKeyboardProperties (KeyboardProperties *properties, const char *string) {
+  enum {
+    KBD_PARM_TYPE,
+    KBD_PARM_VENDOR,
+    KBD_PARM_PRODUCT
+  };
+
+  static const char *const names[] = {"type", "vendor", "product", NULL};
+  char **parameters = getParameters(names, NULL, string);
+  int ok = 1;
+
+  logParameters(names, parameters, "Keyboard Property");
+  *properties = anyKeyboard;
+
+  if (*parameters[KBD_PARM_TYPE]) {
+    static const KeyboardType types[] = {
+      KBD_TYPE_ANY, KBD_TYPE_PS2, KBD_TYPE_USB, KBD_TYPE_BLUETOOTH, KBD_TYPE_INTERNAL
+    };
+
+    static const char *choices[] = {"any", "ps2", "usb", "bluetooth", "internal", NULL};
+    unsigned int choice;
+
+    if (validateChoice(&choice, parameters[KBD_PARM_TYPE], choices)) {
+      properties->type = types[choice];
+    } else {
+      logMessage(LOG_WARNING, "invalid keyboard type: %s", parameters[KBD_PARM_TYPE]);
+      ok = 0;
+    }
+  }
+
+  if (*parameters[KBD_PARM_VENDOR]) {
+    static const int minimum = 0;
+    static const int maximum = 0XFFFF;
+    int value;
+
+    if (validateInteger(&value, parameters[KBD_PARM_VENDOR], &minimum, &maximum)) {
+      properties->vendor = value;
+    } else {
+      logMessage(LOG_WARNING, "invalid keyboard vendor code: %s", parameters[KBD_PARM_VENDOR]);
+      ok = 0;
+    }
+  }
+
+  if (*parameters[KBD_PARM_PRODUCT]) {
+    static const int minimum = 0;
+    static const int maximum = 0XFFFF;
+    int value;
+
+    if (validateInteger(&value, parameters[KBD_PARM_PRODUCT], &minimum, &maximum)) {
+      properties->product = value;
+    } else {
+      logMessage(LOG_WARNING, "invalid keyboard product code: %s", parameters[KBD_PARM_PRODUCT]);
+      ok = 0;
+    }
+  }
+
+  deallocateStrings(parameters);
+  return ok;
+}
+
+int
+checkKeyboardProperties (const KeyboardProperties *actual, const KeyboardProperties *required) {
+  if (!required) return 1;
+  if (!actual)  actual = &anyKeyboard;
+
+  if (required->type != KBD_TYPE_ANY) {
+    if (required->type != actual->type) return 0;
+  }
+
+  if (required->vendor) {
+    if (required->vendor != actual->vendor) return 0;
+  }
+
+  if (required->product) {
+    if (required->product != actual->product) return 0;
+  }
+
+  return 1;
+}
+
+static void
+logKeyEvent (const char *action, int code, int press) {
+  logMessage(LOG_CATEGORY(KEYBOARD_KEYS),
+             "%s %d: %s",
+             (press? "press": "release"), code, action);
+}
+
+static void
+flushKeyEvents (KeyboardInstanceObject *kio) {
+  const KeyEventEntry *event = kio->events.buffer;
+
+  while (kio->events.count) {
+    logKeyEvent("flushing", event->code, event->press);
+    forwardKeyEvent(kio, event->code, event->press);
+
+    event += 1;
+    kio->events.count -= 1;
+  }
+
+  memset(kio->deferred.mask, 0, kio->deferred.size);
+  kio->deferred.modifiersOnly = 0;
+}
+
+KeyboardInstanceObject *
+newKeyboardInstanceObject (KeyboardMonitorObject *kmo) {
+  KeyboardInstanceObject *kio;
+  unsigned int count = BITMASK_ELEMENT_COUNT(keyCodeCount, BITMASK_ELEMENT_SIZE(unsigned char));
+  size_t size = sizeof(*kio) + count;
+
+  if ((kio = malloc(size))) {
+    memset(kio, 0, size);
+    kio->kmo = kmo;
+
+    kio->actualProperties = anyKeyboard;
+
+    kio->events.buffer = NULL;
+    kio->events.size = 0;
+    kio->events.count = 0;
+
+    kio->deferred.modifiersOnly = 0;
+    kio->deferred.size = count;
+
+    if (newKeyboardInstanceExtension(&kio->kix)) {
+      if (enqueueItem(kmo->instanceQueue, kio)) {
+        return kio;
+      }
+
+      destroyKeyboardInstanceExtension(kio->kix);
+    }
+
+    free(kio);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyKeyboardInstanceObject (KeyboardInstanceObject *kio) {
+  flushKeyEvents(kio);
+  if (kio->events.buffer) free(kio->events.buffer);
+
+  deleteItem(kio->kmo->instanceQueue, kio);
+  if (kio->kix) destroyKeyboardInstanceExtension(kio->kix);
+  free(kio);
+}
+
+void
+destroyKeyboardMonitorObject (KeyboardMonitorObject *kmo) {
+  kmo->isActive = 0;
+
+  while (getQueueSize(kmo->instanceQueue) > 0) {
+    Element *element = getQueueHead(kmo->instanceQueue);
+    KeyboardInstanceObject *kio = getElementItem(element);
+
+    destroyKeyboardInstanceObject(kio);
+  }
+
+  if (kmo->instanceQueue) deallocateQueue(kmo->instanceQueue);
+  if (kmo->kmx) destroyKeyboardMonitorExtension(kmo->kmx);
+  free(kmo);
+}
+
+KeyboardMonitorObject *
+newKeyboardMonitorObject (const KeyboardProperties *properties, KeyEventHandler handleKeyEvent) {
+  KeyboardMonitorObject *kmo;
+
+  if ((kmo = malloc(sizeof(*kmo)))) {
+    memset(kmo, 0, sizeof(*kmo));
+
+    kmo->requiredProperties = *properties;
+    kmo->handleKeyEvent = handleKeyEvent;
+
+    if (newKeyboardMonitorExtension(&kmo->kmx)) {
+      if ((kmo->instanceQueue = newQueue(NULL, NULL))) {
+        if (monitorKeyboards(kmo)) {
+          kmo->isActive = 1;
+          return kmo;
+        }
+
+        deallocateQueue(kmo->instanceQueue);
+      }
+
+      destroyKeyboardMonitorExtension(kmo->kmx);
+    }
+
+    free(kmo);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+handleKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
+  KeyTableState state = KTS_UNBOUND;
+
+  logKeyEvent("received", code, press);
+
+  if (kio->kmo->isActive) {
+    if ((code >= 0) && (code < keyCodeCount)) {
+      const KeyValue *kv = &keyCodeMap[code];
+
+      if ((kv->group != KBD_GROUP(SPECIAL)) || (kv->number != KBD_KEY(SPECIAL, Unmapped))) {
+        if ((kv->group == KBD_GROUP(SPECIAL)) && (kv->number == KBD_KEY(SPECIAL, Ignore))) return;
+        state = kio->kmo->handleKeyEvent(kv->group, kv->number, press);
+      }
+    }
+  }
+
+  if (state == KTS_HOTKEY) {
+    logKeyEvent("ignoring", code, press);
+  } else {
+    typedef enum {
+      WKA_NONE,
+      WKA_CURRENT,
+      WKA_ALL
+    } WriteKeysAction;
+    WriteKeysAction action = WKA_NONE;
+
+    if (press) {
+      kio->deferred.modifiersOnly = state == KTS_MODIFIERS;
+
+      if (state == KTS_UNBOUND) {
+        action = WKA_ALL;
+      } else {
+        if (kio->events.count == kio->events.size) {
+          unsigned int newSize = kio->events.size? kio->events.size<<1: 0X1;
+          KeyEventEntry *newBuffer = realloc(kio->events.buffer, (newSize * sizeof(*newBuffer)));
+
+          if (newBuffer) {
+            kio->events.buffer = newBuffer;
+            kio->events.size = newSize;
+          } else {
+            logMallocError();
+          }
+        }
+
+        if (kio->events.count < kio->events.size) {
+          KeyEventEntry *event = &kio->events.buffer[kio->events.count++];
+
+          event->code = code;
+          event->press = press;
+          BITMASK_SET(kio->deferred.mask, code);
+
+          logKeyEvent("deferring", code, press);
+        } else {
+          logKeyEvent("discarding", code, press);
+        }
+      }
+    } else if (kio->deferred.modifiersOnly) {
+      kio->deferred.modifiersOnly = 0;
+      action = WKA_ALL;
+    } else if (BITMASK_TEST(kio->deferred.mask, code)) {
+      KeyEventEntry *to = kio->events.buffer;
+      const KeyEventEntry *from = to;
+      unsigned int count = kio->events.count;
+
+      while (count) {
+        if (from->code == code) {
+          logKeyEvent("dropping", from->code, from->press);
+        } else if (to != from) {
+          *to++ = *from;
+        } else {
+          to += 1;
+        }
+
+        from += 1, count -= 1;
+      }
+
+      kio->events.count = to - kio->events.buffer;
+      BITMASK_CLEAR(kio->deferred.mask, code);
+    } else {
+      action = WKA_CURRENT;
+    }
+
+    switch (action) {
+      case WKA_ALL:
+        flushKeyEvents(kio);
+        /* fall through */
+      case WKA_CURRENT:
+        logKeyEvent("forwarding", code, press);
+        forwardKeyEvent(kio, code, press);
+
+      case WKA_NONE:
+        break;
+    }
+  }
+}
diff --git a/Programs/kbd.h b/Programs/kbd.h
new file mode 100644
index 0000000..d9e8595
--- /dev/null
+++ b/Programs/kbd.h
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KBD
+#define BRLTTY_INCLUDED_KBD
+
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  KBD_TYPE_ANY = 0,
+  KBD_TYPE_PS2,
+  KBD_TYPE_USB,
+  KBD_TYPE_BLUETOOTH,
+  KBD_TYPE_INTERNAL,
+} KeyboardType;
+
+typedef struct {
+  KeyboardType type;
+  int vendor;
+  int product;
+} KeyboardProperties;
+
+extern const KeyboardProperties anyKeyboard;
+
+extern int parseKeyboardProperties (KeyboardProperties *properties, const char *string);
+extern int checkKeyboardProperties (const KeyboardProperties *actual, const KeyboardProperties *required);
+
+typedef struct KeyboardMonitorObjectStruct KeyboardMonitorObject;
+typedef KeyTableState KeyEventHandler (KeyGroup group, KeyNumber number, int press);
+
+extern KeyboardMonitorObject *newKeyboardMonitorObject (const KeyboardProperties *properties, KeyEventHandler handleKeyEvent);
+extern void destroyKeyboardMonitorObject (KeyboardMonitorObject *kmo);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KBD */
diff --git a/Programs/kbd_android.c b/Programs/kbd_android.c
new file mode 100644
index 0000000..7716929
--- /dev/null
+++ b/Programs/kbd_android.c
@@ -0,0 +1,434 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <android/keycodes.h>
+
+#include "log.h"
+#include "system_java.h"
+
+#include "kbd.h"
+#include "kbd_internal.h"
+#include "kbd_android.h"
+
+BEGIN_KEY_CODE_MAP
+  [ANDROID_KEY_0] = KBD_KEY_NUMBER(Zero),
+  [ANDROID_KEY_1] = KBD_KEY_NUMBER(One),
+  [ANDROID_KEY_2] = KBD_KEY_NUMBER(Two),
+  [ANDROID_KEY_3] = KBD_KEY_NUMBER(Three),
+  [ANDROID_KEY_3D_MODE] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_4] = KBD_KEY_NUMBER(Four),
+  [ANDROID_KEY_5] = KBD_KEY_NUMBER(Five),
+  [ANDROID_KEY_6] = KBD_KEY_NUMBER(Six),
+  [ANDROID_KEY_7] = KBD_KEY_NUMBER(Seven),
+  [ANDROID_KEY_8] = KBD_KEY_NUMBER(Eight),
+  [ANDROID_KEY_9] = KBD_KEY_NUMBER(Nine),
+
+  [ANDROID_KEY_A] = KBD_KEY_LETTER(A),
+  [ANDROID_KEY_ALT_LEFT] = KBD_KEY_MODIFIER(AltLeft),
+  [ANDROID_KEY_ALT_RIGHT] = KBD_KEY_MODIFIER(AltRight),
+  [ANDROID_KEY_APOSTROPHE] = KBD_KEY_SYMBOL(Apostrophe),
+  [ANDROID_KEY_APP_SWITCH] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_ASSIST] = KBD_KEY_ACTION(Help),
+  [ANDROID_KEY_AT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_AVR_INPUT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_AVR_POWER] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_B] = KBD_KEY_LETTER(B),
+  [ANDROID_KEY_BACK] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BACKSLASH] = KBD_KEY_SYMBOL(Backslash),
+  [ANDROID_KEY_BOOKMARK] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BREAK] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_1] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_10] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_11] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_12] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_13] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_14] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_15] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_16] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_2] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_3] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_4] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_5] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_6] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_7] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_8] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_9] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_A] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_B] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_C] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_L1] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_L2] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_MODE] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_R1] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_R2] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_SELECT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_START] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_THUMBL] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_THUMBR] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_X] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_Y] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_BUTTON_Z] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_C] = KBD_KEY_LETTER(C),
+  [ANDROID_KEY_CALCULATOR] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CALENDAR] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CALL] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CAMERA] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CAPS_LOCK] = KBD_KEY_LOCK(Capitals),
+  [ANDROID_KEY_CAPTIONS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CHANNEL_DOWN] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CHANNEL_UP] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CLEAR] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_COMMA] = KBD_KEY_SYMBOL(Comma),
+  [ANDROID_KEY_CONTACTS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_CTRL_LEFT] = KBD_KEY_MODIFIER(ControlLeft),
+  [ANDROID_KEY_CTRL_RIGHT] = KBD_KEY_MODIFIER(ControlRight),
+
+  [ANDROID_KEY_D] = KBD_KEY_LETTER(D),
+  [ANDROID_KEY_DEL] = KBD_KEY_ACTION(DeleteBackward),
+  [ANDROID_KEY_DPAD_CENTER] = KBD_KEY_ACTION(Select),
+  [ANDROID_KEY_DPAD_DOWN] = KBD_KEY_ACTION(ArrowDown),
+  [ANDROID_KEY_DPAD_LEFT] = KBD_KEY_ACTION(ArrowLeft),
+  [ANDROID_KEY_DPAD_RIGHT] = KBD_KEY_ACTION(ArrowRight),
+  [ANDROID_KEY_DPAD_UP] = KBD_KEY_ACTION(ArrowUp),
+  [ANDROID_KEY_DVR] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_E] = KBD_KEY_LETTER(E),
+  [ANDROID_KEY_EISU] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_ENDCALL] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_ENTER] = KBD_KEY_ACTION(Enter),
+  [ANDROID_KEY_ENVELOPE] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_EQUALS] = KBD_KEY_SYMBOL(Equals),
+  [ANDROID_KEY_ESCAPE] = KBD_KEY_ACTION(Escape),
+  [ANDROID_KEY_EXPLORER] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_F] = KBD_KEY_LETTER(F),
+  [ANDROID_KEY_F1] = KBD_KEY_FUNCTION(F1),
+  [ANDROID_KEY_F10] = KBD_KEY_FUNCTION(F10),
+  [ANDROID_KEY_F11] = KBD_KEY_FUNCTION(F11),
+  [ANDROID_KEY_F12] = KBD_KEY_FUNCTION(F12),
+  [ANDROID_KEY_F2] = KBD_KEY_FUNCTION(F2),
+  [ANDROID_KEY_F3] = KBD_KEY_FUNCTION(F3),
+  [ANDROID_KEY_F4] = KBD_KEY_FUNCTION(F4),
+  [ANDROID_KEY_F5] = KBD_KEY_FUNCTION(F5),
+  [ANDROID_KEY_F6] = KBD_KEY_FUNCTION(F6),
+  [ANDROID_KEY_F7] = KBD_KEY_FUNCTION(F7),
+  [ANDROID_KEY_F8] = KBD_KEY_FUNCTION(F8),
+  [ANDROID_KEY_F9] = KBD_KEY_FUNCTION(F9),
+  [ANDROID_KEY_FOCUS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_FORWARD] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_FORWARD_DEL] = KBD_KEY_ACTION(DeleteForward),
+  [ANDROID_KEY_FUNCTION] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_G] = KBD_KEY_LETTER(G),
+  [ANDROID_KEY_GRAVE] = KBD_KEY_SYMBOL(Grave),
+  [ANDROID_KEY_GUIDE] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_H] = KBD_KEY_LETTER(H),
+  [ANDROID_KEY_HEADSETHOOK] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_HENKAN] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_HOME] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_I] = KBD_KEY_LETTER(I),
+  [ANDROID_KEY_INFO] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_INSERT] = KBD_KEY_ACTION(Insert),
+
+  [ANDROID_KEY_J] = KBD_KEY_LETTER(J),
+
+  [ANDROID_KEY_K] = KBD_KEY_LETTER(K),
+  [ANDROID_KEY_KANA] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_KATAKANA_HIRAGANA] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_L] = KBD_KEY_LETTER(L),
+  [ANDROID_KEY_LANGUAGE_SWITCH] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_LEFT_BRACKET] = KBD_KEY_SYMBOL(LeftBracket),
+
+  [ANDROID_KEY_M] = KBD_KEY_LETTER(M),
+  [ANDROID_KEY_MANNER_MODE] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_MEDIA_CLOSE] = KBD_KEY_MEDIA(Close),
+  [ANDROID_KEY_MEDIA_EJECT] = KBD_KEY_MEDIA(Eject),
+  [ANDROID_KEY_MEDIA_FAST_FORWARD] = KBD_KEY_MEDIA(Forward),
+  [ANDROID_KEY_MEDIA_NEXT] = KBD_KEY_MEDIA(Next),
+  [ANDROID_KEY_MEDIA_PAUSE] = KBD_KEY_MEDIA(Pause),
+  [ANDROID_KEY_MEDIA_PLAY] = KBD_KEY_MEDIA(Play),
+  [ANDROID_KEY_MEDIA_PLAY_PAUSE] = KBD_KEY_MEDIA(PlayPause),
+  [ANDROID_KEY_MEDIA_PREVIOUS] = KBD_KEY_MEDIA(Previous),
+  [ANDROID_KEY_MEDIA_RECORD] = KBD_KEY_MEDIA(Record),
+  [ANDROID_KEY_MEDIA_REWIND] = KBD_KEY_MEDIA(Backward),
+  [ANDROID_KEY_MEDIA_STOP] = KBD_KEY_MEDIA(Stop),
+  [ANDROID_KEY_MENU] = KBD_KEY_ACTION(Menu),
+  [ANDROID_KEY_META_LEFT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_META_RIGHT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_MINUS] = KBD_KEY_SYMBOL(Minus),
+  [ANDROID_KEY_MOVE_END] = KBD_KEY_ACTION(End),
+  [ANDROID_KEY_MOVE_HOME] = KBD_KEY_ACTION(Home),
+  [ANDROID_KEY_MUHENKAN] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_MUSIC] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_MUTE] = KBD_KEY_MEDIA(Mute),
+
+  [ANDROID_KEY_N] = KBD_KEY_LETTER(N),
+  [ANDROID_KEY_NOTIFICATION] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_NUM] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_NUMPAD_0] = KBD_KEY_KPNUMBER(Zero),
+  [ANDROID_KEY_NUMPAD_1] = KBD_KEY_KPNUMBER(One),
+  [ANDROID_KEY_NUMPAD_2] = KBD_KEY_KPNUMBER(Two),
+  [ANDROID_KEY_NUMPAD_3] = KBD_KEY_KPNUMBER(Three),
+  [ANDROID_KEY_NUMPAD_4] = KBD_KEY_KPNUMBER(Four),
+  [ANDROID_KEY_NUMPAD_5] = KBD_KEY_KPNUMBER(Five),
+  [ANDROID_KEY_NUMPAD_6] = KBD_KEY_KPNUMBER(Six),
+  [ANDROID_KEY_NUMPAD_7] = KBD_KEY_KPNUMBER(Seven),
+  [ANDROID_KEY_NUMPAD_8] = KBD_KEY_KPNUMBER(Eight),
+  [ANDROID_KEY_NUMPAD_9] = KBD_KEY_KPNUMBER(Nine),
+  [ANDROID_KEY_NUMPAD_ADD] = KBD_KEY_KPSYMBOL(Plus),
+  [ANDROID_KEY_NUMPAD_COMMA] = KBD_KEY_KPSYMBOL(Comma),
+  [ANDROID_KEY_NUMPAD_DIVIDE] = KBD_KEY_KPSYMBOL(Divide),
+  [ANDROID_KEY_NUMPAD_DOT] = KBD_KEY_KPSYMBOL(Period),
+  [ANDROID_KEY_NUMPAD_ENTER] = KBD_KEY_KPACTION(Enter),
+  [ANDROID_KEY_NUMPAD_EQUALS] = KBD_KEY_KPSYMBOL(Equals),
+  [ANDROID_KEY_NUMPAD_LEFT_PAREN] = KBD_KEY_KPSYMBOL(LeftParenthesis),
+  [ANDROID_KEY_NUMPAD_MULTIPLY] = KBD_KEY_KPSYMBOL(Multiply),
+  [ANDROID_KEY_NUMPAD_RIGHT_PAREN] = KBD_KEY_KPSYMBOL(RightParenthesis),
+  [ANDROID_KEY_NUMPAD_SUBTRACT] = KBD_KEY_KPSYMBOL(Minus),
+  [ANDROID_KEY_NUM_LOCK] = KBD_KEY_LOCK(Numbers),
+
+  [ANDROID_KEY_O] = KBD_KEY_LETTER(O),
+
+  [ANDROID_KEY_P] = KBD_KEY_LETTER(P),
+  [ANDROID_KEY_PAGE_DOWN] = KBD_KEY_ACTION(PageDown),
+  [ANDROID_KEY_PAGE_UP] = KBD_KEY_ACTION(PageUp),
+  [ANDROID_KEY_PERIOD] = KBD_KEY_SYMBOL(Period),
+  [ANDROID_KEY_PICTSYMBOLS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_PLUS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_POUND] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_POWER] = KBD_KEY_ACTION(Power),
+  [ANDROID_KEY_PROG_BLUE] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_PROG_GREEN] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_PROG_RED] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_PROG_YELLOW] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_Q] = KBD_KEY_LETTER(Q),
+
+  [ANDROID_KEY_R] = KBD_KEY_LETTER(R),
+  [ANDROID_KEY_RIGHT_BRACKET] = KBD_KEY_SYMBOL(RightBracket),
+  [ANDROID_KEY_RO] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_S] = KBD_KEY_LETTER(S),
+  [ANDROID_KEY_SCROLL_LOCK] = KBD_KEY_LOCK(Scroll),
+  [ANDROID_KEY_SEARCH] = KBD_KEY_ACTION(Find),
+  [ANDROID_KEY_SEMICOLON] = KBD_KEY_SYMBOL(Semicolon),
+  [ANDROID_KEY_SETTINGS] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SHIFT_LEFT] = KBD_KEY_MODIFIER(ShiftLeft),
+  [ANDROID_KEY_SHIFT_RIGHT] = KBD_KEY_MODIFIER(ShiftRight),
+  [ANDROID_KEY_SLASH] = KBD_KEY_SYMBOL(Slash),
+  [ANDROID_KEY_SOFT_LEFT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SOFT_RIGHT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SPACE] = KBD_KEY_SYMBOL(Space),
+  [ANDROID_KEY_STAR] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_STB_INPUT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_STB_POWER] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SWITCH_CHARSET] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SYM] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_SYSRQ] = KBD_KEY_ACTION(SystemRequest),
+
+  [ANDROID_KEY_T] = KBD_KEY_LETTER(T),
+  [ANDROID_KEY_TAB] = KBD_KEY_ACTION(Tab),
+  [ANDROID_KEY_TV] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_TV_INPUT] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_TV_POWER] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_U] = KBD_KEY_LETTER(U),
+  [ANDROID_KEY_UNKNOWN] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_V] = KBD_KEY_LETTER(V),
+  [ANDROID_KEY_VOLUME_DOWN] = KBD_KEY_MEDIA(VolumeDown),
+  [ANDROID_KEY_VOLUME_MUTE] = KBD_KEY_MEDIA(Mute),
+  [ANDROID_KEY_VOLUME_UP] = KBD_KEY_MEDIA(VolumeUp),
+
+  [ANDROID_KEY_W] = KBD_KEY_LETTER(W),
+  [ANDROID_KEY_WINDOW] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_X] = KBD_KEY_LETTER(X),
+
+  [ANDROID_KEY_Y] = KBD_KEY_LETTER(Y),
+  [ANDROID_KEY_YEN] = KBD_KEY_UNMAPPED,
+
+  [ANDROID_KEY_Z] = KBD_KEY_LETTER(Z),
+  [ANDROID_KEY_ZENKAKU_HANKAKU] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_ZOOM_IN] = KBD_KEY_UNMAPPED,
+  [ANDROID_KEY_ZOOM_OUT] = KBD_KEY_UNMAPPED,
+
+  [B2G_KEY_CHARACTERS ... B2G_KEY_CHARACTERS+0XFF] = KBD_KEY_IGNORE,
+  [B2G_KEY_CHORDS ... B2G_KEY_CHORDS+0XFF] = KBD_KEY_IGNORE,
+
+  [B2G_KEY_DOT1] = KBD_KEY_BRAILLE(Dot7),
+  [B2G_KEY_DOT2] = KBD_KEY_BRAILLE(Dot3),
+  [B2G_KEY_DOT3] = KBD_KEY_BRAILLE(Dot2),
+  [B2G_KEY_DOT4] = KBD_KEY_BRAILLE(Dot1),
+  [B2G_KEY_DOT5] = KBD_KEY_BRAILLE(Dot4),
+  [B2G_KEY_DOT6] = KBD_KEY_BRAILLE(Dot5),
+  [B2G_KEY_DOT7] = KBD_KEY_BRAILLE(Dot6),
+  [B2G_KEY_DOT8] = KBD_KEY_BRAILLE(Dot8),
+  [B2G_KEY_DOT9] = KBD_KEY_BRAILLE(Space),
+
+  [B2G_KEY_CURSOR0] = KBD_KEY_ROUTING(0),
+  [B2G_KEY_CURSOR1] = KBD_KEY_ROUTING(1),
+  [B2G_KEY_CURSOR2] = KBD_KEY_ROUTING(2),
+  [B2G_KEY_CURSOR3] = KBD_KEY_ROUTING(3),
+  [B2G_KEY_CURSOR4] = KBD_KEY_ROUTING(4),
+  [B2G_KEY_CURSOR5] = KBD_KEY_ROUTING(5),
+  [B2G_KEY_CURSOR6] = KBD_KEY_ROUTING(6),
+  [B2G_KEY_CURSOR7] = KBD_KEY_ROUTING(7),
+  [B2G_KEY_CURSOR8] = KBD_KEY_ROUTING(8),
+  [B2G_KEY_CURSOR9] = KBD_KEY_ROUTING(9),
+  [B2G_KEY_CURSOR10] = KBD_KEY_ROUTING(10),
+  [B2G_KEY_CURSOR11] = KBD_KEY_ROUTING(11),
+  [B2G_KEY_CURSOR12] = KBD_KEY_ROUTING(12),
+  [B2G_KEY_CURSOR13] = KBD_KEY_ROUTING(13),
+  [B2G_KEY_CURSOR14] = KBD_KEY_ROUTING(14),
+  [B2G_KEY_CURSOR15] = KBD_KEY_ROUTING(15),
+  [B2G_KEY_CURSOR16] = KBD_KEY_ROUTING(16),
+  [B2G_KEY_CURSOR17] = KBD_KEY_ROUTING(17),
+  [B2G_KEY_CURSOR18] = KBD_KEY_ROUTING(18),
+  [B2G_KEY_CURSOR19] = KBD_KEY_ROUTING(19),
+  [B2G_KEY_CURSOR20] = KBD_KEY_ROUTING(20),
+  [B2G_KEY_CURSOR21] = KBD_KEY_ROUTING(21),
+  [B2G_KEY_CURSOR22] = KBD_KEY_ROUTING(22),
+  [B2G_KEY_CURSOR23] = KBD_KEY_ROUTING(23),
+  [B2G_KEY_CURSOR24] = KBD_KEY_ROUTING(24),
+  [B2G_KEY_CURSOR25] = KBD_KEY_ROUTING(25),
+  [B2G_KEY_CURSOR26] = KBD_KEY_ROUTING(26),
+  [B2G_KEY_CURSOR27] = KBD_KEY_ROUTING(27),
+  [B2G_KEY_CURSOR28] = KBD_KEY_ROUTING(28),
+  [B2G_KEY_CURSOR29] = KBD_KEY_ROUTING(29),
+  [B2G_KEY_CURSOR30] = KBD_KEY_ROUTING(30),
+  [B2G_KEY_CURSOR31] = KBD_KEY_ROUTING(31),
+  [B2G_KEY_CURSOR32] = KBD_KEY_ROUTING(32),
+  [B2G_KEY_CURSOR33] = KBD_KEY_ROUTING(33),
+  [B2G_KEY_CURSOR34] = KBD_KEY_ROUTING(34),
+  [B2G_KEY_CURSOR35] = KBD_KEY_ROUTING(35),
+  [B2G_KEY_CURSOR36] = KBD_KEY_ROUTING(36),
+  [B2G_KEY_CURSOR37] = KBD_KEY_ROUTING(37),
+  [B2G_KEY_CURSOR38] = KBD_KEY_ROUTING(38),
+  [B2G_KEY_CURSOR39] = KBD_KEY_ROUTING(39),
+
+  [B2G_KEY_BACK] = KBD_KEY_BRAILLE(Backward),
+  [B2G_KEY_FORWARD] = KBD_KEY_BRAILLE(Forward),
+END_KEY_CODE_MAP
+
+static KeyboardInstanceObject *keyboardInstance = NULL;
+
+struct KeyboardInstanceExtensionStruct {
+  JNIEnv *env;
+  jobject object;
+
+  jclass inputService;
+  jmethodID forwardKeyEvent;
+};
+
+int
+newKeyboardMonitorExtension (KeyboardMonitorExtension **kmx) {
+  return 1;
+}
+
+void
+destroyKeyboardMonitorExtension (KeyboardMonitorExtension *kmx) {
+}
+
+int
+newKeyboardInstanceExtension (KeyboardInstanceExtension **kix) {
+  if ((*kix = malloc(sizeof(**kix)))) {
+    memset(*kix,  0, sizeof(**kix));
+
+    (*kix)->env = NULL;
+    (*kix)->object = NULL;
+
+    (*kix)->inputService = NULL;
+    (*kix)->forwardKeyEvent = 0;
+
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+void
+destroyKeyboardInstanceExtension (KeyboardInstanceExtension *kix) {
+  keyboardInstance = NULL;
+  free(kix);
+}
+
+int
+forwardKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
+  KeyboardInstanceExtension *kix = kio->kix;
+
+  if (findJavaClass(kix->env, &kix->inputService, JAVA_OBJ_BRLTTY("InputService"))) {
+    if (findJavaInstanceMethod(kix->env, &kix->forwardKeyEvent, kix->inputService, "forwardKeyEvent",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                               JAVA_SIG_INT // code
+                                               JAVA_SIG_BOOLEAN // press
+                                              ))) {
+      (*kix->env)->CallVoidMethod(
+        kix->env, kix->object, kix->forwardKeyEvent,
+        code, (press? JNI_TRUE: JNI_FALSE)
+      );
+
+      if (!clearJavaException(kix->env, 1)) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+JAVA_INSTANCE_METHOD (
+  org_a11y_brltty_android_InputService, handleKeyEvent, jboolean,
+  jint code, jboolean press
+) {
+  KeyboardInstanceObject *kio = keyboardInstance;
+
+  if (kio) {
+    KeyboardInstanceExtension *kix = kio->kix;
+
+    kix->env = env;
+    kix->object = this;
+
+    handleKeyEvent(kio, code, (press != JNI_FALSE));
+    return JNI_TRUE;
+  }
+
+  return JNI_FALSE;
+}
+
+int
+monitorKeyboards (KeyboardMonitorObject *kmo) {
+  KeyboardInstanceObject *kio;
+
+  if ((kio = newKeyboardInstanceObject(kmo))) {
+    keyboardInstance = kio;
+    return 1;
+  }
+
+  return 0;
+}
diff --git a/Programs/kbd_android.h b/Programs/kbd_android.h
new file mode 100644
index 0000000..47592f0
--- /dev/null
+++ b/Programs/kbd_android.h
@@ -0,0 +1,336 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KBD_ANDROID
+#define BRLTTY_INCLUDED_KBD_ANDROID
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  ANDROID_KEY_0 = 7,
+  ANDROID_KEY_1 = 8,
+  ANDROID_KEY_2 = 9,
+  ANDROID_KEY_3 = 10,
+  ANDROID_KEY_3D_MODE = 206,
+  ANDROID_KEY_4 = 11,
+  ANDROID_KEY_5 = 12,
+  ANDROID_KEY_6 = 13,
+  ANDROID_KEY_7 = 14,
+  ANDROID_KEY_8 = 15,
+  ANDROID_KEY_9 = 16,
+
+  ANDROID_KEY_A = 29,
+  ANDROID_KEY_ALT_LEFT = 57,
+  ANDROID_KEY_ALT_RIGHT = 58,
+  ANDROID_KEY_APOSTROPHE = 75,
+  ANDROID_KEY_APP_SWITCH = 187,
+  ANDROID_KEY_ASSIST = 219,
+  ANDROID_KEY_AT = 77,
+  ANDROID_KEY_AVR_INPUT = 182,
+  ANDROID_KEY_AVR_POWER = 181,
+
+  ANDROID_KEY_B = 30,
+  ANDROID_KEY_BACK = 4,
+  ANDROID_KEY_BACKSLASH = 73,
+  ANDROID_KEY_BOOKMARK = 174,
+  ANDROID_KEY_BREAK = 121,
+  ANDROID_KEY_BUTTON_1 = 188,
+  ANDROID_KEY_BUTTON_10 = 197,
+  ANDROID_KEY_BUTTON_11 = 198,
+  ANDROID_KEY_BUTTON_12 = 199,
+  ANDROID_KEY_BUTTON_13 = 200,
+  ANDROID_KEY_BUTTON_14 = 201,
+  ANDROID_KEY_BUTTON_15 = 202,
+  ANDROID_KEY_BUTTON_16 = 203,
+  ANDROID_KEY_BUTTON_2 = 189,
+  ANDROID_KEY_BUTTON_3 = 190,
+  ANDROID_KEY_BUTTON_4 = 191,
+  ANDROID_KEY_BUTTON_5 = 192,
+  ANDROID_KEY_BUTTON_6 = 193,
+  ANDROID_KEY_BUTTON_7 = 194,
+  ANDROID_KEY_BUTTON_8 = 195,
+  ANDROID_KEY_BUTTON_9 = 196,
+  ANDROID_KEY_BUTTON_A = 96,
+  ANDROID_KEY_BUTTON_B = 97,
+  ANDROID_KEY_BUTTON_C = 98,
+  ANDROID_KEY_BUTTON_L1 = 102,
+  ANDROID_KEY_BUTTON_L2 = 104,
+  ANDROID_KEY_BUTTON_MODE = 110,
+  ANDROID_KEY_BUTTON_R1 = 103,
+  ANDROID_KEY_BUTTON_R2 = 105,
+  ANDROID_KEY_BUTTON_SELECT = 109,
+  ANDROID_KEY_BUTTON_START = 108,
+  ANDROID_KEY_BUTTON_THUMBL = 106,
+  ANDROID_KEY_BUTTON_THUMBR = 107,
+  ANDROID_KEY_BUTTON_X = 99,
+  ANDROID_KEY_BUTTON_Y = 100,
+  ANDROID_KEY_BUTTON_Z = 101,
+
+  ANDROID_KEY_C = 31,
+  ANDROID_KEY_CALCULATOR = 210,
+  ANDROID_KEY_CALENDAR = 208,
+  ANDROID_KEY_CALL = 5,
+  ANDROID_KEY_CAMERA = 27,
+  ANDROID_KEY_CAPS_LOCK = 115,
+  ANDROID_KEY_CAPTIONS = 175,
+  ANDROID_KEY_CHANNEL_DOWN = 167,
+  ANDROID_KEY_CHANNEL_UP = 166,
+  ANDROID_KEY_CLEAR = 28,
+  ANDROID_KEY_COMMA = 55,
+  ANDROID_KEY_CONTACTS = 207,
+  ANDROID_KEY_CTRL_LEFT = 113,
+  ANDROID_KEY_CTRL_RIGHT = 114,
+
+  ANDROID_KEY_D = 32,
+  ANDROID_KEY_DEL = 67,
+  ANDROID_KEY_DPAD_CENTER = 23,
+  ANDROID_KEY_DPAD_DOWN = 20,
+  ANDROID_KEY_DPAD_LEFT = 21,
+  ANDROID_KEY_DPAD_RIGHT = 22,
+  ANDROID_KEY_DPAD_UP = 19,
+  ANDROID_KEY_DVR = 173,
+
+  ANDROID_KEY_E = 33,
+  ANDROID_KEY_EISU = 212,
+  ANDROID_KEY_ENDCALL = 6,
+  ANDROID_KEY_ENTER = 66,
+  ANDROID_KEY_ENVELOPE = 65,
+  ANDROID_KEY_EQUALS = 70,
+  ANDROID_KEY_ESCAPE = 111,
+  ANDROID_KEY_EXPLORER = 64,
+
+  ANDROID_KEY_F = 34,
+  ANDROID_KEY_F1 = 131,
+  ANDROID_KEY_F10 = 140,
+  ANDROID_KEY_F11 = 141,
+  ANDROID_KEY_F12 = 142,
+  ANDROID_KEY_F2 = 132,
+  ANDROID_KEY_F3 = 133,
+  ANDROID_KEY_F4 = 134,
+  ANDROID_KEY_F5 = 135,
+  ANDROID_KEY_F6 = 136,
+  ANDROID_KEY_F7 = 137,
+  ANDROID_KEY_F8 = 138,
+  ANDROID_KEY_F9 = 139,
+  ANDROID_KEY_FOCUS = 80,
+  ANDROID_KEY_FORWARD = 125,
+  ANDROID_KEY_FORWARD_DEL = 112,
+  ANDROID_KEY_FUNCTION = 119,
+
+  ANDROID_KEY_G = 35,
+  ANDROID_KEY_GRAVE = 68,
+  ANDROID_KEY_GUIDE = 172,
+
+  ANDROID_KEY_H = 36,
+  ANDROID_KEY_HEADSETHOOK = 79,
+  ANDROID_KEY_HENKAN = 214,
+  ANDROID_KEY_HOME = 3,
+
+  ANDROID_KEY_I = 37,
+  ANDROID_KEY_INFO = 165,
+  ANDROID_KEY_INSERT = 124,
+
+  ANDROID_KEY_J = 38,
+
+  ANDROID_KEY_K = 39,
+  ANDROID_KEY_KANA = 218,
+  ANDROID_KEY_KATAKANA_HIRAGANA = 215,
+
+  ANDROID_KEY_L = 40,
+  ANDROID_KEY_LANGUAGE_SWITCH = 204,
+  ANDROID_KEY_LEFT_BRACKET = 71,
+
+  ANDROID_KEY_M = 41,
+  ANDROID_KEY_MANNER_MODE = 205,
+  ANDROID_KEY_MEDIA_CLOSE = 128,
+  ANDROID_KEY_MEDIA_EJECT = 129,
+  ANDROID_KEY_MEDIA_FAST_FORWARD = 90,
+  ANDROID_KEY_MEDIA_NEXT = 87,
+  ANDROID_KEY_MEDIA_PAUSE = 127,
+  ANDROID_KEY_MEDIA_PLAY = 126,
+  ANDROID_KEY_MEDIA_PLAY_PAUSE = 85,
+  ANDROID_KEY_MEDIA_PREVIOUS = 88,
+  ANDROID_KEY_MEDIA_RECORD = 130,
+  ANDROID_KEY_MEDIA_REWIND = 89,
+  ANDROID_KEY_MEDIA_STOP = 86,
+  ANDROID_KEY_MENU = 82,
+  ANDROID_KEY_META_LEFT = 117,
+  ANDROID_KEY_META_RIGHT = 118,
+  ANDROID_KEY_MINUS = 69,
+  ANDROID_KEY_MOVE_END = 123,
+  ANDROID_KEY_MOVE_HOME = 122,
+  ANDROID_KEY_MUHENKAN = 213,
+  ANDROID_KEY_MUSIC = 209,
+  ANDROID_KEY_MUTE = 91,
+
+  ANDROID_KEY_N = 42,
+  ANDROID_KEY_NOTIFICATION = 83,
+  ANDROID_KEY_NUM = 78,
+  ANDROID_KEY_NUMPAD_0 = 144,
+  ANDROID_KEY_NUMPAD_1 = 145,
+  ANDROID_KEY_NUMPAD_2 = 146,
+  ANDROID_KEY_NUMPAD_3 = 147,
+  ANDROID_KEY_NUMPAD_4 = 148,
+  ANDROID_KEY_NUMPAD_5 = 149,
+  ANDROID_KEY_NUMPAD_6 = 150,
+  ANDROID_KEY_NUMPAD_7 = 151,
+  ANDROID_KEY_NUMPAD_8 = 152,
+  ANDROID_KEY_NUMPAD_9 = 153,
+  ANDROID_KEY_NUMPAD_ADD = 157,
+  ANDROID_KEY_NUMPAD_COMMA = 159,
+  ANDROID_KEY_NUMPAD_DIVIDE = 154,
+  ANDROID_KEY_NUMPAD_DOT = 158,
+  ANDROID_KEY_NUMPAD_ENTER = 160,
+  ANDROID_KEY_NUMPAD_EQUALS = 161,
+  ANDROID_KEY_NUMPAD_LEFT_PAREN = 162,
+  ANDROID_KEY_NUMPAD_MULTIPLY = 155,
+  ANDROID_KEY_NUMPAD_RIGHT_PAREN = 163,
+  ANDROID_KEY_NUMPAD_SUBTRACT = 156,
+  ANDROID_KEY_NUM_LOCK = 143,
+
+  ANDROID_KEY_O = 43,
+
+  ANDROID_KEY_P = 44,
+  ANDROID_KEY_PAGE_DOWN = 93,
+  ANDROID_KEY_PAGE_UP = 92,
+  ANDROID_KEY_PERIOD = 56,
+  ANDROID_KEY_PICTSYMBOLS = 94,
+  ANDROID_KEY_PLUS = 81,
+  ANDROID_KEY_POUND = 18,
+  ANDROID_KEY_POWER = 26,
+  ANDROID_KEY_PROG_BLUE = 186,
+  ANDROID_KEY_PROG_GREEN = 184,
+  ANDROID_KEY_PROG_RED = 183,
+  ANDROID_KEY_PROG_YELLOW = 185,
+
+  ANDROID_KEY_Q = 45,
+
+  ANDROID_KEY_R = 46,
+  ANDROID_KEY_RIGHT_BRACKET = 72,
+  ANDROID_KEY_RO = 217,
+
+  ANDROID_KEY_S = 47,
+  ANDROID_KEY_SCROLL_LOCK = 116,
+  ANDROID_KEY_SEARCH = 84,
+  ANDROID_KEY_SEMICOLON = 74,
+  ANDROID_KEY_SETTINGS = 176,
+  ANDROID_KEY_SHIFT_LEFT = 59,
+  ANDROID_KEY_SHIFT_RIGHT = 60,
+  ANDROID_KEY_SLASH = 76,
+  ANDROID_KEY_SOFT_LEFT = 1,
+  ANDROID_KEY_SOFT_RIGHT = 2,
+  ANDROID_KEY_SPACE = 62,
+  ANDROID_KEY_STAR = 17,
+  ANDROID_KEY_STB_INPUT = 180,
+  ANDROID_KEY_STB_POWER = 179,
+  ANDROID_KEY_SWITCH_CHARSET = 95,
+  ANDROID_KEY_SYM = 63,
+  ANDROID_KEY_SYSRQ = 120,
+
+  ANDROID_KEY_T = 48,
+  ANDROID_KEY_TAB = 61,
+  ANDROID_KEY_TV = 170,
+  ANDROID_KEY_TV_INPUT = 178,
+  ANDROID_KEY_TV_POWER = 177,
+
+  ANDROID_KEY_U = 49,
+  ANDROID_KEY_UNKNOWN = 0,
+
+  ANDROID_KEY_V = 50,
+  ANDROID_KEY_VOLUME_DOWN = 25,
+  ANDROID_KEY_VOLUME_MUTE = 164,
+  ANDROID_KEY_VOLUME_UP = 24,
+
+  ANDROID_KEY_W = 51,
+  ANDROID_KEY_WINDOW = 171,
+
+  ANDROID_KEY_X = 52,
+
+  ANDROID_KEY_Y = 53,
+  ANDROID_KEY_YEN = 216,
+
+  ANDROID_KEY_Z = 54,
+  ANDROID_KEY_ZENKAKU_HANKAKU = 211,
+  ANDROID_KEY_ZOOM_IN = 168,
+  ANDROID_KEY_ZOOM_OUT = 169,
+
+  B2G_KEY_CHARACTERS = 0X100,
+  B2G_KEY_CHORDS     = 0X200,
+
+  B2G_KEY_DOT1 = 769,
+  B2G_KEY_DOT2 = 770,
+  B2G_KEY_DOT3 = 771,
+  B2G_KEY_DOT4 = 772,
+  B2G_KEY_DOT5 = 773,
+  B2G_KEY_DOT6 = 774,
+  B2G_KEY_DOT7 = 775,
+  B2G_KEY_DOT8 = 776,
+  B2G_KEY_DOT9 = 777,
+
+  B2G_KEY_CURSOR0  = 778,
+  B2G_KEY_CURSOR1  = 779,
+  B2G_KEY_CURSOR2  = 780,
+  B2G_KEY_CURSOR3  = 781,
+  B2G_KEY_CURSOR4  = 782,
+  B2G_KEY_CURSOR5  = 783,
+  B2G_KEY_CURSOR6  = 784,
+  B2G_KEY_CURSOR7  = 785,
+  B2G_KEY_CURSOR8  = 786,
+  B2G_KEY_CURSOR9  = 787,
+  B2G_KEY_CURSOR10 = 788,
+  B2G_KEY_CURSOR11 = 789,
+  B2G_KEY_CURSOR12 = 790,
+  B2G_KEY_CURSOR13 = 791,
+  B2G_KEY_CURSOR14 = 792,
+  B2G_KEY_CURSOR15 = 793,
+  B2G_KEY_CURSOR16 = 794,
+  B2G_KEY_CURSOR17 = 795,
+  B2G_KEY_CURSOR18 = 796,
+  B2G_KEY_CURSOR19 = 797,
+  B2G_KEY_CURSOR20 = 798,
+  B2G_KEY_CURSOR21 = 799,
+  B2G_KEY_CURSOR22 = 800,
+  B2G_KEY_CURSOR23 = 801,
+  B2G_KEY_CURSOR24 = 802,
+  B2G_KEY_CURSOR25 = 803,
+  B2G_KEY_CURSOR26 = 804,
+  B2G_KEY_CURSOR27 = 805,
+  B2G_KEY_CURSOR28 = 806,
+  B2G_KEY_CURSOR29 = 807,
+  B2G_KEY_CURSOR30 = 808,
+  B2G_KEY_CURSOR31 = 809,
+  B2G_KEY_CURSOR32 = 810,
+  B2G_KEY_CURSOR33 = 811,
+  B2G_KEY_CURSOR34 = 812,
+  B2G_KEY_CURSOR35 = 813,
+  B2G_KEY_CURSOR36 = 814,
+  B2G_KEY_CURSOR37 = 815,
+  B2G_KEY_CURSOR38 = 816,
+  B2G_KEY_CURSOR39 = 817,
+
+  B2G_KEY_BACK    = 818,
+  B2G_KEY_FORWARD = 819,
+} AndroidKeyCode;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KBD_ANDROID */
diff --git a/Programs/kbd_internal.h b/Programs/kbd_internal.h
new file mode 100644
index 0000000..d0095cc
--- /dev/null
+++ b/Programs/kbd_internal.h
@@ -0,0 +1,91 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KBD_INTERNAL
+#define BRLTTY_INCLUDED_KBD_INTERNAL
+
+#include "queue.h"
+#include "ktb_keyboard.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct KeyboardMonitorExtensionStruct KeyboardMonitorExtension;
+
+struct KeyboardMonitorObjectStruct {
+  KeyboardMonitorExtension *kmx;
+  unsigned isActive:1;
+
+  KeyboardProperties requiredProperties;
+  Queue *instanceQueue;
+
+  KeyEventHandler *handleKeyEvent;
+};
+
+typedef struct {
+  int code;
+  int press;
+} KeyEventEntry;
+
+typedef struct KeyboardInstanceExtensionStruct KeyboardInstanceExtension;
+
+typedef struct {
+  KeyboardMonitorObject *kmo;
+  KeyboardInstanceExtension *kix;
+
+  KeyboardProperties actualProperties;
+
+  struct {
+    KeyEventEntry *buffer;
+    unsigned int size;
+    unsigned int count;
+  } events;
+
+  struct {
+    unsigned modifiersOnly:1;
+    unsigned int size;
+    unsigned char mask[0];
+  } deferred;
+} KeyboardInstanceObject;
+
+extern void handleKeyEvent (KeyboardInstanceObject *kio, int code, int press);
+
+extern KeyboardInstanceObject *newKeyboardInstanceObject (KeyboardMonitorObject *kmo);
+extern void destroyKeyboardInstanceObject (KeyboardInstanceObject *kio);
+
+extern int monitorKeyboards (KeyboardMonitorObject *kmo);
+extern int forwardKeyEvent (KeyboardInstanceObject *kio, int code, int press);
+
+extern int newKeyboardMonitorExtension (KeyboardMonitorExtension **kmx);
+extern void destroyKeyboardMonitorExtension (KeyboardMonitorExtension *kmx);
+
+extern int newKeyboardInstanceExtension (KeyboardInstanceExtension **kix);
+extern void destroyKeyboardInstanceExtension (KeyboardInstanceExtension *kix);
+
+extern const KeyValue keyCodeMap[];
+extern const unsigned int keyCodeCount;
+
+#define BEGIN_KEY_CODE_MAP const KeyValue keyCodeMap[] = {
+#define END_KEY_CODE_MAP }; const unsigned int keyCodeCount = ARRAY_COUNT(keyCodeMap);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KBD_INTERNAL */
diff --git a/Programs/kbd_keycodes.c b/Programs/kbd_keycodes.c
new file mode 100644
index 0000000..c509902
--- /dev/null
+++ b/Programs/kbd_keycodes.c
@@ -0,0 +1,152 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "kbd_keycodes.h"
+
+const unsigned char AT2XT[0X80] = {
+  [0X00] = 0X00,
+  [AT_KEY_00_Escape] = XT_KEY_00_Escape,
+  [AT_KEY_00_1] = XT_KEY_00_1,
+  [AT_KEY_00_2] = XT_KEY_00_2,
+  [AT_KEY_00_3] = XT_KEY_00_3,
+  [AT_KEY_00_4] = XT_KEY_00_4,
+  [AT_KEY_00_5] = XT_KEY_00_5,
+  [AT_KEY_00_6] = XT_KEY_00_6,
+  [AT_KEY_00_7] = XT_KEY_00_7,
+  [AT_KEY_00_8] = XT_KEY_00_8,
+  [AT_KEY_00_9] = XT_KEY_00_9,
+  [AT_KEY_00_0] = XT_KEY_00_0,
+  [AT_KEY_00_Minus] = XT_KEY_00_Minus,
+  [AT_KEY_00_Equal] = XT_KEY_00_Equal,
+  [AT_KEY_00_Backspace] = XT_KEY_00_Backspace,
+  [AT_KEY_00_Tab] = XT_KEY_00_Tab,
+  [AT_KEY_00_Q] = XT_KEY_00_Q,
+  [AT_KEY_00_W] = XT_KEY_00_W,
+  [AT_KEY_00_E] = XT_KEY_00_E,
+  [AT_KEY_00_R] = XT_KEY_00_R,
+  [AT_KEY_00_T] = XT_KEY_00_T,
+  [AT_KEY_00_Y] = XT_KEY_00_Y,
+  [AT_KEY_00_U] = XT_KEY_00_U,
+  [AT_KEY_00_I] = XT_KEY_00_I,
+  [AT_KEY_00_O] = XT_KEY_00_O,
+  [AT_KEY_00_P] = XT_KEY_00_P,
+  [AT_KEY_00_LeftBracket] = XT_KEY_00_LeftBracket,
+  [AT_KEY_00_RightBracket] = XT_KEY_00_RightBracket,
+  [AT_KEY_00_Enter] = XT_KEY_00_Enter,
+  [AT_KEY_00_LeftControl] = XT_KEY_00_LeftControl,
+  [AT_KEY_00_A] = XT_KEY_00_A,
+  [AT_KEY_00_S] = XT_KEY_00_S,
+  [AT_KEY_00_D] = XT_KEY_00_D,
+  [AT_KEY_00_F] = XT_KEY_00_F,
+  [AT_KEY_00_G] = XT_KEY_00_G,
+  [AT_KEY_00_H] = XT_KEY_00_H,
+  [AT_KEY_00_J] = XT_KEY_00_J,
+  [AT_KEY_00_K] = XT_KEY_00_K,
+  [AT_KEY_00_L] = XT_KEY_00_L,
+  [AT_KEY_00_Semicolon] = XT_KEY_00_Semicolon,
+  [AT_KEY_00_Apostrophe] = XT_KEY_00_Apostrophe,
+  [AT_KEY_00_Grave] = XT_KEY_00_Grave,
+  [AT_KEY_00_LeftShift] = XT_KEY_00_LeftShift,
+  [AT_KEY_00_Backslash] = XT_KEY_00_Backslash,
+  [AT_KEY_00_Z] = XT_KEY_00_Z,
+  [AT_KEY_00_X] = XT_KEY_00_X,
+  [AT_KEY_00_C] = XT_KEY_00_C,
+  [AT_KEY_00_V] = XT_KEY_00_V,
+  [AT_KEY_00_B] = XT_KEY_00_B,
+  [AT_KEY_00_N] = XT_KEY_00_N,
+  [AT_KEY_00_M] = XT_KEY_00_M,
+  [AT_KEY_00_Comma] = XT_KEY_00_Comma,
+  [AT_KEY_00_Period] = XT_KEY_00_Period,
+  [AT_KEY_00_Slash] = XT_KEY_00_Slash,
+  [AT_KEY_00_RightShift] = XT_KEY_00_RightShift,
+  [AT_KEY_00_KPAsterisk] = XT_KEY_00_KPAsterisk,
+  [AT_KEY_00_LeftAlt] = XT_KEY_00_LeftAlt,
+  [AT_KEY_00_Space] = XT_KEY_00_Space,
+  [AT_KEY_00_CapsLock] = XT_KEY_00_CapsLock,
+  [AT_KEY_00_F1] = XT_KEY_00_F1,
+  [AT_KEY_00_F2] = XT_KEY_00_F2,
+  [AT_KEY_00_F3] = XT_KEY_00_F3,
+  [AT_KEY_00_F4] = XT_KEY_00_F4,
+  [AT_KEY_00_F5] = XT_KEY_00_F5,
+  [AT_KEY_00_F6] = XT_KEY_00_F6,
+  [AT_KEY_00_F7_X1] = XT_KEY_00_F7,
+  [AT_KEY_00_F8] = XT_KEY_00_F8,
+  [AT_KEY_00_F9] = XT_KEY_00_F9,
+  [AT_KEY_00_F10] = XT_KEY_00_F10,
+  [AT_KEY_00_NumLock] = XT_KEY_00_NumLock,
+  [AT_KEY_00_ScrollLock] = XT_KEY_00_ScrollLock,
+  [AT_KEY_00_KP7] = XT_KEY_00_KP7,
+  [AT_KEY_00_KP8] = XT_KEY_00_KP8,
+  [AT_KEY_00_KP9] = XT_KEY_00_KP9,
+  [AT_KEY_00_KPMinus] = XT_KEY_00_KPMinus,
+  [AT_KEY_00_KP4] = XT_KEY_00_KP4,
+  [AT_KEY_00_KP5] = XT_KEY_00_KP5,
+  [AT_KEY_00_KP6] = XT_KEY_00_KP6,
+  [AT_KEY_00_KPPlus] = XT_KEY_00_KPPlus,
+  [AT_KEY_00_KP1] = XT_KEY_00_KP1,
+  [AT_KEY_00_KP2] = XT_KEY_00_KP2,
+  [AT_KEY_00_KP3] = XT_KEY_00_KP3,
+  [AT_KEY_00_KP0] = XT_KEY_00_KP0,
+  [AT_KEY_00_KPPeriod] = XT_KEY_00_KPPeriod,
+  [0X7F] = 0X54,
+  [0X60] = 0X55,
+  [AT_KEY_00_Europe2] = XT_KEY_00_Europe2,
+  [AT_KEY_00_F11] = XT_KEY_00_F11,
+  [AT_KEY_00_F12] = XT_KEY_00_F12,
+  [AT_KEY_00_KPEqual] = XT_KEY_00_KPEqual,
+  [0X17] = 0X5A,
+  [0X1F] = 0X5B,
+  [AT_KEY_00_International6] = XT_KEY_00_International6,
+  [0X2F] = 0X5D,
+  [0X37] = 0X5E,
+  [0X3F] = 0X5F,
+  [0X47] = 0X60,
+  [0X4F] = 0X61,
+  [0X56] = 0X62,
+  [0X5E] = 0X63,
+  [AT_KEY_00_F13] = XT_KEY_00_F13,
+  [AT_KEY_00_F14] = XT_KEY_00_F14,
+  [AT_KEY_00_F15] = XT_KEY_00_F15,
+  [AT_KEY_00_F16] = XT_KEY_00_F16,
+  [AT_KEY_00_F17] = XT_KEY_00_F17,
+  [AT_KEY_00_F18] = XT_KEY_00_F18,
+  [AT_KEY_00_F19] = XT_KEY_00_F19,
+  [AT_KEY_00_F20] = XT_KEY_00_F20,
+  [AT_KEY_00_F21] = XT_KEY_00_F21,
+  [AT_KEY_00_F22] = XT_KEY_00_F22,
+  [AT_KEY_00_F23] = XT_KEY_00_F23,
+  [0X6F] = 0X6F,
+  [AT_KEY_00_International2] = XT_KEY_00_International2,
+  [0X19] = 0X71,
+  [AT_KEY_00_CrSel] = XT_KEY_00_CrSel,
+  [AT_KEY_00_International1] = XT_KEY_00_International1,
+  [AT_KEY_00_ExSel] = XT_KEY_00_ExSel,
+  [AT_KEY_00_EnlHelp] = XT_KEY_00_EnlHelp,
+  [AT_KEY_00_F24] = XT_KEY_00_F24,
+  [AT_KEY_00_Language4] = XT_KEY_00_Language4,
+  [AT_KEY_00_Language3] = XT_KEY_00_Language3,
+  [AT_KEY_00_International4] = XT_KEY_00_International4,
+  [0X65] = 0X7A,
+  [AT_KEY_00_International5] = XT_KEY_00_International5,
+  [0X68] = 0X7C,
+  [AT_KEY_00_International3] = XT_KEY_00_International3,
+  [AT_KEY_00_KPComma] = XT_KEY_00_KPComma,
+  [0X6E] = 0X7F
+};
diff --git a/Programs/kbd_linux.c b/Programs/kbd_linux.c
new file mode 100644
index 0000000..fa4b308
--- /dev/null
+++ b/Programs/kbd_linux.c
@@ -0,0 +1,982 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "parameters.h"
+#include "log.h"
+#include "strfmt.h"
+#include "file.h"
+#include "system_linux.h"
+#include "kbd.h"
+#include "kbd_internal.h"
+
+#ifdef HAVE_LINUX_UINPUT_H
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+
+#include "bitmask.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "async_io.h"
+
+struct KeyboardMonitorExtensionStruct {
+  struct {
+    int socket;
+    AsyncHandle monitor;
+  } uevent;
+};
+
+struct KeyboardInstanceExtensionStruct {
+  UinputObject *uinput;
+  AsyncHandle udevDelay;
+
+  struct {
+    int descriptor;
+    AsyncHandle monitor;
+  } file;
+
+  struct {
+    char *path;
+    int major;
+    int minor;
+  } device;
+};
+
+BEGIN_KEY_CODE_MAP
+  [KEY_ESC] = KBD_KEY_ACTION(Escape),
+  [KEY_1] = KBD_KEY_NUMBER(One),
+  [KEY_2] = KBD_KEY_NUMBER(Two),
+  [KEY_3] = KBD_KEY_NUMBER(Three),
+  [KEY_4] = KBD_KEY_NUMBER(Four),
+  [KEY_5] = KBD_KEY_NUMBER(Five),
+  [KEY_6] = KBD_KEY_NUMBER(Six),
+  [KEY_7] = KBD_KEY_NUMBER(Seven),
+  [KEY_8] = KBD_KEY_NUMBER(Eight),
+  [KEY_9] = KBD_KEY_NUMBER(Nine),
+  [KEY_0] = KBD_KEY_NUMBER(Zero),
+  [KEY_MINUS] = KBD_KEY_SYMBOL(Minus),
+  [KEY_EQUAL] = KBD_KEY_SYMBOL(Equals),
+  [KEY_BACKSPACE] = KBD_KEY_ACTION(DeleteBackward),
+  [KEY_TAB] = KBD_KEY_ACTION(Tab),
+  [KEY_Q] = KBD_KEY_LETTER(Q),
+  [KEY_W] = KBD_KEY_LETTER(W),
+  [KEY_E] = KBD_KEY_LETTER(E),
+  [KEY_R] = KBD_KEY_LETTER(R),
+  [KEY_T] = KBD_KEY_LETTER(T),
+  [KEY_Y] = KBD_KEY_LETTER(Y),
+  [KEY_U] = KBD_KEY_LETTER(U),
+  [KEY_I] = KBD_KEY_LETTER(I),
+  [KEY_O] = KBD_KEY_LETTER(O),
+  [KEY_P] = KBD_KEY_LETTER(P),
+  [KEY_LEFTBRACE] = KBD_KEY_SYMBOL(LeftBracket),
+  [KEY_RIGHTBRACE] = KBD_KEY_SYMBOL(RightBracket),
+  [KEY_ENTER] = KBD_KEY_ACTION(Enter),
+  [KEY_LEFTCTRL] = KBD_KEY_MODIFIER(ControlLeft),
+  [KEY_A] = KBD_KEY_LETTER(A),
+  [KEY_S] = KBD_KEY_LETTER(S),
+  [KEY_D] = KBD_KEY_LETTER(D),
+  [KEY_F] = KBD_KEY_LETTER(F),
+  [KEY_G] = KBD_KEY_LETTER(G),
+  [KEY_H] = KBD_KEY_LETTER(H),
+  [KEY_J] = KBD_KEY_LETTER(J),
+  [KEY_K] = KBD_KEY_LETTER(K),
+  [KEY_L] = KBD_KEY_LETTER(L),
+  [KEY_SEMICOLON] = KBD_KEY_SYMBOL(Semicolon),
+  [KEY_APOSTROPHE] = KBD_KEY_SYMBOL(Apostrophe),
+  [KEY_GRAVE] = KBD_KEY_SYMBOL(Grave),
+  [KEY_LEFTSHIFT] = KBD_KEY_MODIFIER(ShiftLeft),
+  [KEY_BACKSLASH] = KBD_KEY_SYMBOL(Backslash),
+  [KEY_Z] = KBD_KEY_LETTER(Z),
+  [KEY_X] = KBD_KEY_LETTER(X),
+  [KEY_C] = KBD_KEY_LETTER(C),
+  [KEY_V] = KBD_KEY_LETTER(V),
+  [KEY_B] = KBD_KEY_LETTER(B),
+  [KEY_N] = KBD_KEY_LETTER(N),
+  [KEY_M] = KBD_KEY_LETTER(M),
+  [KEY_COMMA] = KBD_KEY_SYMBOL(Comma),
+  [KEY_DOT] = KBD_KEY_SYMBOL(Period),
+  [KEY_SLASH] = KBD_KEY_SYMBOL(Slash),
+  [KEY_RIGHTSHIFT] = KBD_KEY_MODIFIER(ShiftRight),
+  [KEY_KPASTERISK] = KBD_KEY_KPSYMBOL(Multiply),
+  [KEY_LEFTALT] = KBD_KEY_MODIFIER(AltLeft),
+  [KEY_SPACE] = KBD_KEY_SYMBOL(Space),
+  [KEY_CAPSLOCK] = KBD_KEY_LOCK(Capitals),
+  [KEY_F1] = KBD_KEY_FUNCTION(F1),
+  [KEY_F2] = KBD_KEY_FUNCTION(F2),
+  [KEY_F3] = KBD_KEY_FUNCTION(F3),
+  [KEY_F4] = KBD_KEY_FUNCTION(F4),
+  [KEY_F5] = KBD_KEY_FUNCTION(F5),
+  [KEY_F6] = KBD_KEY_FUNCTION(F6),
+  [KEY_F7] = KBD_KEY_FUNCTION(F7),
+  [KEY_F8] = KBD_KEY_FUNCTION(F8),
+  [KEY_F9] = KBD_KEY_FUNCTION(F9),
+  [KEY_F10] = KBD_KEY_FUNCTION(F10),
+  [KEY_NUMLOCK] = KBD_KEY_LOCK(Numbers),
+  [KEY_SCROLLLOCK] = KBD_KEY_LOCK(Scroll),
+  [KEY_KP7] = KBD_KEY_KPNUMBER(Seven),
+  [KEY_KP8] = KBD_KEY_KPNUMBER(Eight),
+  [KEY_KP9] = KBD_KEY_KPNUMBER(Nine),
+  [KEY_KPMINUS] = KBD_KEY_KPSYMBOL(Minus),
+  [KEY_KP4] = KBD_KEY_KPNUMBER(Four),
+  [KEY_KP5] = KBD_KEY_KPNUMBER(Five),
+  [KEY_KP6] = KBD_KEY_KPNUMBER(Six),
+  [KEY_KPPLUS] = KBD_KEY_KPSYMBOL(Plus),
+  [KEY_KP1] = KBD_KEY_KPNUMBER(One),
+  [KEY_KP2] = KBD_KEY_KPNUMBER(Two),
+  [KEY_KP3] = KBD_KEY_KPNUMBER(Three),
+  [KEY_KP0] = KBD_KEY_KPNUMBER(Zero),
+  [KEY_KPDOT] = KBD_KEY_KPSYMBOL(Period),
+  [KEY_ZENKAKUHANKAKU] = KBD_KEY_UNMAPPED,
+  [KEY_102ND] = KBD_KEY_SYMBOL(Europe2),
+  [KEY_F11] = KBD_KEY_FUNCTION(F11),
+  [KEY_F12] = KBD_KEY_FUNCTION(F12),
+  [KEY_RO] = KBD_KEY_UNMAPPED,
+  [KEY_KATAKANA] = KBD_KEY_UNMAPPED,
+  [KEY_HIRAGANA] = KBD_KEY_UNMAPPED,
+  [KEY_HENKAN] = KBD_KEY_UNMAPPED,
+  [KEY_KATAKANAHIRAGANA] = KBD_KEY_UNMAPPED,
+  [KEY_MUHENKAN] = KBD_KEY_UNMAPPED,
+  [KEY_KPJPCOMMA] = KBD_KEY_UNMAPPED,
+  [KEY_KPENTER] = KBD_KEY_KPACTION(Enter),
+  [KEY_RIGHTCTRL] = KBD_KEY_MODIFIER(ControlRight),
+  [KEY_KPSLASH] = KBD_KEY_KPSYMBOL(Divide),
+  [KEY_SYSRQ] = KBD_KEY_ACTION(SystemRequest),
+  [KEY_RIGHTALT] = KBD_KEY_MODIFIER(AltRight),
+  [KEY_LINEFEED] = KBD_KEY_UNMAPPED,
+  [KEY_HOME] = KBD_KEY_ACTION(Home),
+  [KEY_UP] = KBD_KEY_ACTION(ArrowUp),
+  [KEY_PAGEUP] = KBD_KEY_ACTION(PageUp),
+  [KEY_LEFT] = KBD_KEY_ACTION(ArrowLeft),
+  [KEY_RIGHT] = KBD_KEY_ACTION(ArrowRight),
+  [KEY_END] = KBD_KEY_ACTION(End),
+  [KEY_DOWN] = KBD_KEY_ACTION(ArrowDown),
+  [KEY_PAGEDOWN] = KBD_KEY_ACTION(PageDown),
+  [KEY_INSERT] = KBD_KEY_ACTION(Insert),
+  [KEY_DELETE] = KBD_KEY_ACTION(DeleteForward),
+  [KEY_MACRO] = KBD_KEY_UNMAPPED,
+  [KEY_MUTE] = KBD_KEY_MEDIA(Mute),
+  [KEY_VOLUMEDOWN] = KBD_KEY_MEDIA(VolumeDown),
+  [KEY_VOLUMEUP] = KBD_KEY_MEDIA(VolumeUp),
+  [KEY_POWER] = KBD_KEY_ACTION(Power),
+  [KEY_KPEQUAL] = KBD_KEY_KPSYMBOL(Equals),
+  [KEY_KPPLUSMINUS] = KBD_KEY_KPSYMBOL(PlusMinus),
+  [KEY_LEFTMETA] = KBD_KEY_ACTION(GuiLeft),
+  [KEY_RIGHTMETA] = KBD_KEY_ACTION(GuiRight),
+  [KEY_COMPOSE] = KBD_KEY_ACTION(Context),
+  [KEY_PAUSE] = KBD_KEY_ACTION(Pause),
+  [KEY_KPCOMMA] = KBD_KEY_KPSYMBOL(Comma),
+  [KEY_HANGEUL] = KBD_KEY_UNMAPPED,
+  [KEY_HANGUEL] = KBD_KEY_UNMAPPED,
+  [KEY_HANJA] = KBD_KEY_UNMAPPED,
+  [KEY_YEN] = KBD_KEY_UNMAPPED,
+  [KEY_LEFTMETA] = KBD_KEY_UNMAPPED,
+  [KEY_RIGHTMETA] = KBD_KEY_UNMAPPED,
+  [KEY_COMPOSE] = KBD_KEY_UNMAPPED,
+  [KEY_STOP] = KBD_KEY_ACTION(Stop),
+  [KEY_AGAIN] = KBD_KEY_ACTION(Again),
+  [KEY_PROPS] = KBD_KEY_ACTION(Props),
+  [KEY_UNDO] = KBD_KEY_ACTION(Undo),
+  [KEY_FRONT] = KBD_KEY_ACTION(Front),
+  [KEY_COPY] = KBD_KEY_ACTION(Copy),
+  [KEY_OPEN] = KBD_KEY_ACTION(Open),
+  [KEY_PASTE] = KBD_KEY_ACTION(Paste),
+  [KEY_FIND] = KBD_KEY_ACTION(Find),
+  [KEY_CUT] = KBD_KEY_ACTION(Cut),
+  [KEY_HELP] = KBD_KEY_ACTION(Help),
+  [KEY_MENU] = KBD_KEY_ACTION(Menu),
+  [KEY_CALC] = KBD_KEY_UNMAPPED,
+  [KEY_SETUP] = KBD_KEY_UNMAPPED,
+  [KEY_SLEEP] = KBD_KEY_UNMAPPED,
+  [KEY_WAKEUP] = KBD_KEY_UNMAPPED,
+  [KEY_FILE] = KBD_KEY_UNMAPPED,
+  [KEY_SENDFILE] = KBD_KEY_UNMAPPED,
+  [KEY_DELETEFILE] = KBD_KEY_UNMAPPED,
+  [KEY_XFER] = KBD_KEY_UNMAPPED,
+  [KEY_PROG1] = KBD_KEY_UNMAPPED,
+  [KEY_PROG2] = KBD_KEY_UNMAPPED,
+  [KEY_WWW] = KBD_KEY_UNMAPPED,
+  [KEY_MSDOS] = KBD_KEY_UNMAPPED,
+  [KEY_COFFEE] = KBD_KEY_UNMAPPED,
+  [KEY_SCREENLOCK] = KBD_KEY_UNMAPPED,
+  [KEY_DIRECTION] = KBD_KEY_UNMAPPED,
+  [KEY_CYCLEWINDOWS] = KBD_KEY_UNMAPPED,
+  [KEY_MAIL] = KBD_KEY_UNMAPPED,
+  [KEY_BOOKMARKS] = KBD_KEY_UNMAPPED,
+  [KEY_COMPUTER] = KBD_KEY_UNMAPPED,
+  [KEY_BACK] = KBD_KEY_UNMAPPED,
+  [KEY_FORWARD] = KBD_KEY_UNMAPPED,
+  [KEY_CLOSECD] = KBD_KEY_MEDIA(Close),
+  [KEY_EJECTCD] = KBD_KEY_MEDIA(Eject),
+  [KEY_EJECTCLOSECD] = KBD_KEY_MEDIA(EjectClose),
+  [KEY_NEXTSONG] = KBD_KEY_MEDIA(Next),
+  [KEY_PLAYPAUSE] = KBD_KEY_MEDIA(PlayPause),
+  [KEY_PREVIOUSSONG] = KBD_KEY_MEDIA(Previous),
+  [KEY_STOPCD] = KBD_KEY_MEDIA(Stop),
+  [KEY_RECORD] = KBD_KEY_MEDIA(Record),
+  [KEY_REWIND] = KBD_KEY_MEDIA(Backward),
+  [KEY_PHONE] = KBD_KEY_UNMAPPED,
+  [KEY_ISO] = KBD_KEY_UNMAPPED,
+  [KEY_CONFIG] = KBD_KEY_UNMAPPED,
+  [KEY_HOMEPAGE] = KBD_KEY_UNMAPPED,
+  [KEY_REFRESH] = KBD_KEY_UNMAPPED,
+  [KEY_EXIT] = KBD_KEY_UNMAPPED,
+  [KEY_MOVE] = KBD_KEY_UNMAPPED,
+  [KEY_EDIT] = KBD_KEY_UNMAPPED,
+  [KEY_SCROLLUP] = KBD_KEY_UNMAPPED,
+  [KEY_SCROLLDOWN] = KBD_KEY_UNMAPPED,
+  [KEY_KPLEFTPAREN] = KBD_KEY_KPSYMBOL(LeftParenthesis),
+  [KEY_KPRIGHTPAREN] = KBD_KEY_KPSYMBOL(RightParenthesis),
+  [KEY_NEW] = KBD_KEY_UNMAPPED,
+  [KEY_REDO] = KBD_KEY_UNMAPPED,
+  [KEY_F13] = KBD_KEY_FUNCTION(F13),
+  [KEY_F14] = KBD_KEY_FUNCTION(F14),
+  [KEY_F15] = KBD_KEY_FUNCTION(F15),
+  [KEY_F16] = KBD_KEY_FUNCTION(F16),
+  [KEY_F17] = KBD_KEY_FUNCTION(F17),
+  [KEY_F18] = KBD_KEY_FUNCTION(F18),
+  [KEY_F19] = KBD_KEY_FUNCTION(F19),
+  [KEY_F20] = KBD_KEY_FUNCTION(F20),
+  [KEY_F21] = KBD_KEY_FUNCTION(F21),
+  [KEY_F22] = KBD_KEY_FUNCTION(F22),
+  [KEY_F23] = KBD_KEY_FUNCTION(F23),
+  [KEY_F24] = KBD_KEY_FUNCTION(F24),
+  [KEY_PLAYCD] = KBD_KEY_MEDIA(Play),
+  [KEY_PAUSECD] = KBD_KEY_MEDIA(Pause),
+  [KEY_PROG3] = KBD_KEY_UNMAPPED,
+  [KEY_PROG4] = KBD_KEY_UNMAPPED,
+  [KEY_DASHBOARD] = KBD_KEY_UNMAPPED,
+  [KEY_SUSPEND] = KBD_KEY_UNMAPPED,
+  [KEY_CLOSE] = KBD_KEY_UNMAPPED,
+  [KEY_PLAY] = KBD_KEY_UNMAPPED,
+  [KEY_FASTFORWARD] = KBD_KEY_MEDIA(Forward),
+  [KEY_BASSBOOST] = KBD_KEY_UNMAPPED,
+  [KEY_PRINT] = KBD_KEY_UNMAPPED,
+  [KEY_HP] = KBD_KEY_UNMAPPED,
+  [KEY_CAMERA] = KBD_KEY_UNMAPPED,
+  [KEY_SOUND] = KBD_KEY_UNMAPPED,
+  [KEY_QUESTION] = KBD_KEY_UNMAPPED,
+  [KEY_EMAIL] = KBD_KEY_UNMAPPED,
+  [KEY_CHAT] = KBD_KEY_UNMAPPED,
+  [KEY_SEARCH] = KBD_KEY_UNMAPPED,
+  [KEY_CONNECT] = KBD_KEY_UNMAPPED,
+  [KEY_FINANCE] = KBD_KEY_UNMAPPED,
+  [KEY_SPORT] = KBD_KEY_UNMAPPED,
+  [KEY_SHOP] = KBD_KEY_UNMAPPED,
+  [KEY_ALTERASE] = KBD_KEY_UNMAPPED,
+  [KEY_CANCEL] = KBD_KEY_UNMAPPED,
+  [KEY_BRIGHTNESSDOWN] = KBD_KEY_UNMAPPED,
+  [KEY_BRIGHTNESSUP] = KBD_KEY_UNMAPPED,
+  [KEY_MEDIA] = KBD_KEY_UNMAPPED,
+  [KEY_SWITCHVIDEOMODE] = KBD_KEY_UNMAPPED,
+  [KEY_KBDILLUMTOGGLE] = KBD_KEY_UNMAPPED,
+  [KEY_KBDILLUMDOWN] = KBD_KEY_UNMAPPED,
+  [KEY_KBDILLUMUP] = KBD_KEY_UNMAPPED,
+  [KEY_SEND] = KBD_KEY_UNMAPPED,
+  [KEY_REPLY] = KBD_KEY_UNMAPPED,
+  [KEY_FORWARDMAIL] = KBD_KEY_UNMAPPED,
+  [KEY_SAVE] = KBD_KEY_UNMAPPED,
+  [KEY_DOCUMENTS] = KBD_KEY_UNMAPPED,
+  [KEY_BATTERY] = KBD_KEY_UNMAPPED,
+  [KEY_BLUETOOTH] = KBD_KEY_UNMAPPED,
+  [KEY_WLAN] = KBD_KEY_UNMAPPED,
+  [KEY_UWB] = KBD_KEY_UNMAPPED,
+  [KEY_UNKNOWN] = KBD_KEY_UNMAPPED,
+  [KEY_VIDEO_NEXT] = KBD_KEY_UNMAPPED,
+  [KEY_VIDEO_PREV] = KBD_KEY_UNMAPPED,
+  [KEY_BRIGHTNESS_CYCLE] = KBD_KEY_UNMAPPED,
+  [KEY_BRIGHTNESS_ZERO] = KBD_KEY_UNMAPPED,
+  [KEY_DISPLAY_OFF] = KBD_KEY_UNMAPPED,
+  [KEY_WIMAX] = KBD_KEY_UNMAPPED,
+  [KEY_OK] = KBD_KEY_UNMAPPED,
+  [KEY_SELECT] = KBD_KEY_ACTION(Select),
+  [KEY_GOTO] = KBD_KEY_UNMAPPED,
+  [KEY_CLEAR] = KBD_KEY_ACTION(Clear),
+  [KEY_POWER2] = KBD_KEY_UNMAPPED,
+  [KEY_OPTION] = KBD_KEY_UNMAPPED,
+  [KEY_INFO] = KBD_KEY_UNMAPPED,
+  [KEY_TIME] = KBD_KEY_UNMAPPED,
+  [KEY_VENDOR] = KBD_KEY_UNMAPPED,
+  [KEY_ARCHIVE] = KBD_KEY_UNMAPPED,
+  [KEY_PROGRAM] = KBD_KEY_UNMAPPED,
+  [KEY_CHANNEL] = KBD_KEY_UNMAPPED,
+  [KEY_FAVORITES] = KBD_KEY_UNMAPPED,
+  [KEY_EPG] = KBD_KEY_UNMAPPED,
+  [KEY_PVR] = KBD_KEY_UNMAPPED,
+  [KEY_MHP] = KBD_KEY_UNMAPPED,
+  [KEY_LANGUAGE] = KBD_KEY_UNMAPPED,
+  [KEY_TITLE] = KBD_KEY_UNMAPPED,
+  [KEY_SUBTITLE] = KBD_KEY_UNMAPPED,
+  [KEY_ANGLE] = KBD_KEY_UNMAPPED,
+  [KEY_ZOOM] = KBD_KEY_UNMAPPED,
+  [KEY_MODE] = KBD_KEY_UNMAPPED,
+  [KEY_KEYBOARD] = KBD_KEY_UNMAPPED,
+  [KEY_SCREEN] = KBD_KEY_UNMAPPED,
+  [KEY_PC] = KBD_KEY_UNMAPPED,
+  [KEY_TV] = KBD_KEY_UNMAPPED,
+  [KEY_TV2] = KBD_KEY_UNMAPPED,
+  [KEY_VCR] = KBD_KEY_UNMAPPED,
+  [KEY_VCR2] = KBD_KEY_UNMAPPED,
+  [KEY_SAT] = KBD_KEY_UNMAPPED,
+  [KEY_SAT2] = KBD_KEY_UNMAPPED,
+  [KEY_CD] = KBD_KEY_UNMAPPED,
+  [KEY_TAPE] = KBD_KEY_UNMAPPED,
+  [KEY_RADIO] = KBD_KEY_UNMAPPED,
+  [KEY_TUNER] = KBD_KEY_UNMAPPED,
+  [KEY_PLAYER] = KBD_KEY_UNMAPPED,
+  [KEY_TEXT] = KBD_KEY_UNMAPPED,
+  [KEY_DVD] = KBD_KEY_UNMAPPED,
+  [KEY_AUX] = KBD_KEY_UNMAPPED,
+  [KEY_MP3] = KBD_KEY_UNMAPPED,
+  [KEY_AUDIO] = KBD_KEY_UNMAPPED,
+  [KEY_VIDEO] = KBD_KEY_UNMAPPED,
+  [KEY_DIRECTORY] = KBD_KEY_UNMAPPED,
+  [KEY_LIST] = KBD_KEY_UNMAPPED,
+  [KEY_MEMO] = KBD_KEY_UNMAPPED,
+  [KEY_CALENDAR] = KBD_KEY_UNMAPPED,
+  [KEY_RED] = KBD_KEY_UNMAPPED,
+  [KEY_GREEN] = KBD_KEY_UNMAPPED,
+  [KEY_YELLOW] = KBD_KEY_UNMAPPED,
+  [KEY_BLUE] = KBD_KEY_UNMAPPED,
+  [KEY_CHANNELUP] = KBD_KEY_UNMAPPED,
+  [KEY_CHANNELDOWN] = KBD_KEY_UNMAPPED,
+  [KEY_FIRST] = KBD_KEY_UNMAPPED,
+  [KEY_LAST] = KBD_KEY_UNMAPPED,
+  [KEY_AB] = KBD_KEY_UNMAPPED,
+  [KEY_NEXT] = KBD_KEY_UNMAPPED,
+  [KEY_RESTART] = KBD_KEY_UNMAPPED,
+  [KEY_SLOW] = KBD_KEY_UNMAPPED,
+  [KEY_SHUFFLE] = KBD_KEY_UNMAPPED,
+  [KEY_BREAK] = KBD_KEY_UNMAPPED,
+  [KEY_PREVIOUS] = KBD_KEY_UNMAPPED,
+  [KEY_DIGITS] = KBD_KEY_UNMAPPED,
+  [KEY_TEEN] = KBD_KEY_UNMAPPED,
+  [KEY_TWEN] = KBD_KEY_UNMAPPED,
+  [KEY_VIDEOPHONE] = KBD_KEY_UNMAPPED,
+  [KEY_GAMES] = KBD_KEY_UNMAPPED,
+  [KEY_ZOOMIN] = KBD_KEY_UNMAPPED,
+  [KEY_ZOOMOUT] = KBD_KEY_UNMAPPED,
+  [KEY_ZOOMRESET] = KBD_KEY_UNMAPPED,
+  [KEY_WORDPROCESSOR] = KBD_KEY_UNMAPPED,
+  [KEY_EDITOR] = KBD_KEY_UNMAPPED,
+  [KEY_SPREADSHEET] = KBD_KEY_UNMAPPED,
+  [KEY_GRAPHICSEDITOR] = KBD_KEY_UNMAPPED,
+  [KEY_PRESENTATION] = KBD_KEY_UNMAPPED,
+  [KEY_DATABASE] = KBD_KEY_UNMAPPED,
+  [KEY_NEWS] = KBD_KEY_UNMAPPED,
+  [KEY_VOICEMAIL] = KBD_KEY_UNMAPPED,
+  [KEY_ADDRESSBOOK] = KBD_KEY_UNMAPPED,
+  [KEY_MESSENGER] = KBD_KEY_UNMAPPED,
+  [KEY_DISPLAYTOGGLE] = KBD_KEY_UNMAPPED,
+  [KEY_SPELLCHECK] = KBD_KEY_UNMAPPED,
+  [KEY_LOGOFF] = KBD_KEY_UNMAPPED,
+  [KEY_DOLLAR] = KBD_KEY_UNMAPPED,
+  [KEY_EURO] = KBD_KEY_UNMAPPED,
+  [KEY_FRAMEBACK] = KBD_KEY_UNMAPPED,
+  [KEY_FRAMEFORWARD] = KBD_KEY_UNMAPPED,
+  [KEY_CONTEXT_MENU] = KBD_KEY_UNMAPPED,
+  [KEY_MEDIA_REPEAT] = KBD_KEY_UNMAPPED,
+  [KEY_DEL_EOL] = KBD_KEY_UNMAPPED,
+  [KEY_DEL_EOS] = KBD_KEY_UNMAPPED,
+  [KEY_INS_LINE] = KBD_KEY_UNMAPPED,
+  [KEY_DEL_LINE] = KBD_KEY_UNMAPPED,
+  [KEY_FN] = KBD_KEY_UNMAPPED,
+  [KEY_FN_ESC] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F1] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F2] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F3] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F4] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F5] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F6] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F7] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F8] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F9] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F10] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F11] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F12] = KBD_KEY_UNMAPPED,
+  [KEY_FN_1] = KBD_KEY_UNMAPPED,
+  [KEY_FN_2] = KBD_KEY_UNMAPPED,
+  [KEY_FN_D] = KBD_KEY_UNMAPPED,
+  [KEY_FN_E] = KBD_KEY_UNMAPPED,
+  [KEY_FN_F] = KBD_KEY_UNMAPPED,
+  [KEY_FN_S] = KBD_KEY_UNMAPPED,
+  [KEY_FN_B] = KBD_KEY_UNMAPPED,
+  [KEY_BRL_DOT1] = KBD_KEY_BRAILLE(Dot1),
+  [KEY_BRL_DOT2] = KBD_KEY_BRAILLE(Dot2),
+  [KEY_BRL_DOT3] = KBD_KEY_BRAILLE(Dot3),
+  [KEY_BRL_DOT4] = KBD_KEY_BRAILLE(Dot4),
+  [KEY_BRL_DOT5] = KBD_KEY_BRAILLE(Dot5),
+  [KEY_BRL_DOT6] = KBD_KEY_BRAILLE(Dot6),
+  [KEY_BRL_DOT7] = KBD_KEY_BRAILLE(Dot7),
+  [KEY_BRL_DOT8] = KBD_KEY_BRAILLE(Dot8),
+  [KEY_BRL_DOT9] = KBD_KEY_BRAILLE(Backward),
+  [KEY_BRL_DOT10] = KBD_KEY_BRAILLE(Forward),
+END_KEY_CODE_MAP
+
+int
+newKeyboardMonitorExtension (KeyboardMonitorExtension **kmx) {
+  if ((*kmx = malloc(sizeof(**kmx)))) {
+    memset(*kmx,  0, sizeof(**kmx));
+
+    (*kmx)->uevent.socket = -1;
+    (*kmx)->uevent.monitor = NULL;
+
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+void
+destroyKeyboardMonitorExtension (KeyboardMonitorExtension *kmx) {
+  if (kmx->uevent.monitor) asyncCancelRequest(kmx->uevent.monitor);
+  if (kmx->uevent.socket != -1) close(kmx->uevent.socket);
+  free(kmx);
+}
+
+int
+newKeyboardInstanceExtension (KeyboardInstanceExtension **kix) {
+  if ((*kix = malloc(sizeof(**kix)))) {
+    memset(*kix,  0, sizeof(**kix));
+
+    (*kix)->uinput = NULL;
+    (*kix)->udevDelay = NULL;
+
+    (*kix)->file.descriptor = -1;
+    (*kix)->file.monitor = NULL;
+
+    (*kix)->device.path = NULL;
+    (*kix)->device.major = 0;
+    (*kix)->device.minor = 0;
+
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  *kix = NULL;
+  return 0;
+}
+
+void
+destroyKeyboardInstanceExtension (KeyboardInstanceExtension *kix) {
+  if (kix->file.monitor) {
+    asyncCancelRequest(kix->file.monitor);
+    logMessage(LOG_DEBUG, "closing keyboard: %s: fd=%d",
+               kix->device.path, kix->file.descriptor);
+  }
+
+  if (kix->file.descriptor != -1) close(kix->file.descriptor);
+  if (kix->udevDelay) asyncCancelRequest(kix->udevDelay);
+  if (kix->uinput) destroyUinputObject(kix->uinput);
+  if (kix->device.path) free(kix->device.path);
+  free(kix);
+}
+
+int
+forwardKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
+  return writeKeyEvent(kio->kix->uinput, code, (press? 1: 0));
+}
+
+ASYNC_INPUT_CALLBACK(handleLinuxKeyboardEvent) {
+  KeyboardInstanceObject *kio = parameters->data;
+  static const char label[] = "keyboard";
+
+  if (parameters->error) {
+    logMessage(LOG_DEBUG, "%s read error: fd=%d: %s",
+               label, kio->kix->file.descriptor, strerror(parameters->error));
+    destroyKeyboardInstanceObject(kio);
+  } else if (parameters->end) {
+    logMessage(LOG_DEBUG, "%s end-of-file: fd=%d", 
+               label, kio->kix->file.descriptor);
+    destroyKeyboardInstanceObject(kio);
+  } else {
+    const struct input_event *event = parameters->buffer;
+
+    if (parameters->length >= sizeof(*event)) {
+      switch (event->type) {
+        case EV_KEY: {
+          int release = event->value == 0;
+          int press   = event->value == 1;
+
+          if (release || press) handleKeyEvent(kio, event->code, press);
+          break;
+        }
+
+        case EV_REP: {
+          switch (event->code) {
+            case REP_DELAY: {
+              writeRepeatDelay(kio->kix->uinput, event->value);
+              break;
+            }
+
+            case REP_PERIOD: {
+              writeRepeatPeriod(kio->kix->uinput, event->value);
+              break;
+            }
+
+            default:
+              break;
+          }
+
+          break;
+        }
+
+        default:
+          break;
+      }
+
+      return sizeof(*event);
+    }
+  }
+
+  return 0;
+}
+
+static UinputObject *
+newUinputInstance (const char *device) {
+  char name[0X40];
+
+  snprintf(name, sizeof(name), "Keyboard Instance - %s", locatePathName(device));
+  return newUinputObject(name);
+}
+
+static int
+prepareUinputInstance (UinputObject *uinput, int keyboard) {
+  {
+    int type = EV_KEY;
+    BITMASK(mask, KEY_MAX+1, char);
+    int size = ioctl(keyboard, EVIOCGBIT(type, sizeof(mask)), mask);
+
+    if (size == -1) {
+      logSystemError("ioctl[EVIOCGBIT]");
+      return 0;
+    }
+
+    {
+      int count = size * 8;
+
+      {
+        static const int keys[] = {KEY_ENTER, KEY_SPACE, KEY_BRL_DOT1};
+        const int *key = keys;
+        const int *end = key + ARRAY_COUNT(keys);
+
+        while (key < end) {
+          if (*key >= count) continue;
+          if (BITMASK_TEST(mask, *key)) break;
+          key += 1;
+        }
+
+        if (key == end) return 0;
+      }
+
+      if (!enableUinputEventType(uinput, type)) return 0;
+
+      for (int key=0; key<count; key+=1) {
+        if (BITMASK_TEST(mask, key)) {
+          if (!enableUinputKey(uinput, key)) {
+            return 0;
+          }
+        }
+      }
+    }
+  }
+
+  if (!enableUinputEventType(uinput, EV_REP)) return 0;
+  if (!createUinputDevice(uinput)) return 0;
+
+  {
+    int properties[2];
+
+    if (ioctl(keyboard, EVIOCGREP, properties) != -1) {
+      if (!writeRepeatDelay(uinput, properties[0])) return 0;
+      if (!writeRepeatPeriod(uinput, properties[1])) return 0;
+    }
+  }
+
+  {
+    BITMASK(mask, KEY_MAX+1, char);
+    int size = ioctl(keyboard, EVIOCGKEY(sizeof(mask)), mask);
+
+    if (size != -1) {
+      int count = size * 8;
+      for (int key=0; key<count; key+=1) {
+        if (BITMASK_TEST(mask, key)) {
+          logMessage(LOG_WARNING, "key already pressed: %d", key);
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int
+monitorKeyboard (KeyboardInstanceObject *kio) {
+  const char *deviceName = locatePathName(kio->kix->device.path);
+
+  if ((kio->kix->file.descriptor = open(kio->kix->device.path, O_RDONLY)) != -1) {
+    struct stat status;
+
+    if (fstat(kio->kix->file.descriptor, &status) != -1) {
+      if (S_ISCHR(status.st_mode)) {
+        {
+          char description[0X100];
+
+          STR_BEGIN(description, sizeof(description));
+          STR_PRINTF("%s:", deviceName);
+
+          {
+            struct input_id identity;
+
+            if (ioctl(kio->kix->file.descriptor, EVIOCGID, &identity) != -1) {
+              STR_PRINTF(" bus=%04X vnd=%04X prd=%04X ver=%04X",
+                         identity.bustype, identity.vendor, identity.product, identity.version);
+
+              {
+                static const KeyboardType typeTable[] = {
+  #ifdef BUS_I8042
+                  [BUS_I8042] = KBD_TYPE_PS2,
+  #endif /* BUS_I8042 */
+
+  #ifdef BUS_USB
+                  [BUS_USB] = KBD_TYPE_USB,
+  #endif /* BUS_USB */
+
+  #ifdef BUS_BLUETOOTH
+                  [BUS_BLUETOOTH] = KBD_TYPE_BLUETOOTH,
+  #endif /* BUS_BLUETOOTH */
+
+  #ifdef BUS_HOST
+                  [BUS_HOST] = KBD_TYPE_INTERNAL,
+  #endif /* BUS_HOST */
+                };
+
+                if (identity.bustype < ARRAY_COUNT(typeTable)) {
+                  kio->actualProperties.type = typeTable[identity.bustype];
+                }
+              }
+
+              kio->actualProperties.vendor = identity.vendor;
+              kio->actualProperties.product = identity.product;
+            } else if (errno != ENOTTY) {
+              logMessage(LOG_WARNING, "cannot get input device identity: %s: %s",
+                         deviceName, strerror(errno));
+            }
+          }
+
+          {
+            char topology[0X100];
+
+            if (ioctl(kio->kix->file.descriptor, EVIOCGPHYS(sizeof(topology)), topology) != -1) {
+              if (*topology) {
+                STR_PRINTF(" tpl=%s", topology);
+              }
+            }
+          }
+
+          {
+            char identifier[0X100];
+
+            if (ioctl(kio->kix->file.descriptor, EVIOCGUNIQ(sizeof(identifier)), identifier) != -1) {
+              if (*identifier) {
+                STR_PRINTF(" id=%s", identifier);
+              }
+            }
+          }
+
+          {
+            char name[0X100];
+
+            if (ioctl(kio->kix->file.descriptor, EVIOCGNAME(sizeof(name)), name) != -1) {
+              if (*name) {
+                STR_PRINTF(" nam=%s", name);
+              }
+            }
+          }
+
+          STR_END;
+          logMessage(LOG_DEBUG, "checking input device: %s", description);
+        }
+        
+        if (kio->actualProperties.type) {
+          if (checkKeyboardProperties(&kio->actualProperties, &kio->kmo->requiredProperties)) {
+            if (ioctl(kio->kix->file.descriptor, EVIOCGRAB, 1) != -1) {
+              if ((kio->kix->uinput = newUinputInstance(kio->kix->device.path))) {
+                if (prepareUinputInstance(kio->kix->uinput, kio->kix->file.descriptor)) {
+                  if (asyncReadFile(&kio->kix->file.monitor,
+                                    kio->kix->file.descriptor, sizeof(struct input_event),
+                                    handleLinuxKeyboardEvent, kio)) {
+                    logMessage(LOG_DEBUG, "keyboard opened: %s: fd=%d",
+                               kio->kix->device.path, kio->kix->file.descriptor);
+
+                    return 1;
+                  }
+                }
+              }
+            } else {
+              logSystemError("ioctl[EVIOCGRAB]");
+            }
+          }
+        }
+      }
+    } else {
+      logMessage(LOG_WARNING, "cannot stat input device: %s: %s",
+                 deviceName, strerror(errno));
+    }
+  } else {
+    logMessage(LOG_WARNING, "cannot open input device: %s: %s",
+               deviceName, strerror(errno));
+  }
+
+  return 0;
+}
+
+static void
+monitorCurrentKeyboards (KeyboardMonitorObject *kmo) {
+  const char *root = "/dev/input";
+  const size_t rootLength = strlen(root);
+  DIR *directory;
+
+  logMessage(LOG_DEBUG, "searching for keyboards");
+
+  if ((directory = opendir(root))) {
+    struct dirent *entry;
+
+    while ((entry = readdir(directory))) {
+      KeyboardInstanceObject *kio;
+
+      if ((kio = newKeyboardInstanceObject(kmo))) {
+        const size_t pathSize = rootLength + 1 + strlen(entry->d_name) + 1;
+
+        if ((kio->kix->device.path = malloc(pathSize))) {
+          snprintf(kio->kix->device.path, pathSize, "%s/%s", root, entry->d_name);
+          if (monitorKeyboard(kio)) continue;
+        } else {
+          logMallocError();
+        }
+
+        destroyKeyboardInstanceObject(kio);
+      }
+    }
+
+    closedir(directory);
+  } else {
+    logMessage(LOG_DEBUG, "cannot open directory: %s: %s", root, strerror(errno));
+  }
+
+  logMessage(LOG_DEBUG, "keyboard search complete");
+}
+
+#ifdef NETLINK_KOBJECT_UEVENT
+ASYNC_ALARM_CALLBACK(openLinuxInputDevice) {
+  KeyboardInstanceObject *kio = parameters->data;
+
+  asyncDiscardHandle(kio->kix->udevDelay);
+  kio->kix->udevDelay = NULL;
+
+  if (!monitorKeyboard(kio)) destroyKeyboardInstanceObject(kio);
+}
+
+static int
+getDeviceNumbers (const char *device, int *major, int *minor) {
+  int ok = 0;
+  static const char prefix[] = "/sys";
+  static const char suffix[] = "/dev";
+  char path[strlen(prefix) + strlen(device) + sizeof(suffix)];
+  int descriptor;
+
+  snprintf(path, sizeof(path), "%s%s%s", prefix, device, suffix);
+
+  if ((descriptor = open(path, O_RDONLY)) != -1) {
+    char buffer[0X10];
+    ssize_t length;
+
+    if ((length = read(descriptor, buffer, sizeof(buffer))) > 0) {
+      if (sscanf(buffer, "%d:%d", major, minor) == 2) {
+        ok = 1;
+      }
+    }
+
+    close(descriptor);
+  } else {
+    logMessage(LOG_DEBUG, "cannot open sysfs dev file: %s: %s",
+               path, strerror(errno));
+  }
+
+  return ok;
+}
+
+ASYNC_INPUT_CALLBACK(handleKobjectUeventString) {
+  KeyboardMonitorObject *kmo = parameters->data;
+  static const char label[] = "kobject uevent";
+
+  if (parameters->error) {
+    logMessage(LOG_DEBUG, "%s read error: %s", label, strerror(parameters->error));
+  } else if (parameters->end) {
+    logMessage(LOG_DEBUG, "%s end-of-file", label);
+  } else {
+    const char *string = parameters->buffer;
+    const char *end = memchr(string, 0, parameters->length);
+
+    if (end) {
+      size_t length = end - string;
+
+      static const char delimiters[] = {'@', '=', '\0'};
+      const char *delimiter = strpbrk(string, delimiters);
+
+      if (!delimiter) {
+        const char *data = end + 1;
+        size_t size;
+
+        if (strcmp(string, "libudev") == 0) {
+          size = 32;
+        } else {
+          logMessage(LOG_WARNING, "unrecognized %s segment: %s", label, string);
+          size = 0;
+        }
+
+        length += size;
+        if (parameters->length < length) return 0;
+        logBytes(LOG_DEBUG, "%s data: %s", data, size, label, string);
+      } else if (*delimiter == '@') {
+        const char *action = string;
+        const char *device = delimiter + 1;
+        int actionLength = delimiter - action;
+
+        logMessage(LOG_DEBUG, "%s action: %.*s %s", label, actionLength, action, device);
+
+        if (strncmp(action, "add", actionLength) == 0) {
+          const char *suffix = device;
+
+          while ((suffix = strstr(suffix, "/input"))) {
+            int input;
+            int event;
+
+            if (sscanf(++suffix, "input%d/event%d", &input, &event) == 2) {
+              KeyboardInstanceObject *kio;
+
+              if ((kio = newKeyboardInstanceObject(kmo))) {
+                if (getDeviceNumbers(device, &kio->kix->device.major, &kio->kix->device.minor)) {
+                  char path[0X40];
+
+                  snprintf(path, sizeof(path), "/dev/input/event%d", event);
+
+                  if ((kio->kix->device.path = strdup(path))) {
+                    if (asyncNewRelativeAlarm(&kio->kix->udevDelay,
+                                              LINUX_INPUT_DEVICE_OPEN_DELAY,
+                                              openLinuxInputDevice, kio)) {
+                      break;
+                    }
+                  } else {
+                    logMallocError();
+                  }
+                }
+
+                destroyKeyboardInstanceObject(kio);
+              }
+            }
+          }
+        }
+      } else if (*delimiter == '=') {
+        const char *name = string;
+        const char *value = delimiter + 1;
+        int nameLength = delimiter - name;
+
+        logMessage(LOG_DEBUG, "%s property: %.*s %s", label, nameLength, name, value);
+      }
+
+      return length + 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+getKobjectUeventSocket (void) {
+  static int socketDescriptor = -1;
+
+  if (socketDescriptor == -1) {
+    const struct sockaddr_nl socketAddress = {
+      .nl_family = AF_NETLINK,
+      .nl_pid = getpid(),
+      .nl_groups = 0XFFFFFFFF
+    };
+
+    if ((socketDescriptor = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) != -1) {
+      if (bind(socketDescriptor, (const struct sockaddr *)&socketAddress, sizeof(socketAddress)) != -1) {
+        logMessage(LOG_DEBUG,
+          "netlink kobject uevent socket opened: fd=%d", socketDescriptor
+        );
+      } else {
+        logSystemError("netlink kobject uevent socket bind");
+        close(socketDescriptor);
+        socketDescriptor = -1;
+      }
+    } else {
+      logSystemError("netlink kobject uevent socket creation");
+    }
+  }
+
+  return socketDescriptor;
+}
+#endif /* NETLINK_KOBJECT_UEVENT */
+
+static int
+monitorNewKeyboards (KeyboardMonitorObject *kmo) {
+#ifdef NETLINK_KOBJECT_UEVENT
+  if ((kmo->kmx->uevent.socket = getKobjectUeventSocket()) != -1) {
+    if (asyncReadSocket(&kmo->kmx->uevent.monitor,
+                        kmo->kmx->uevent.socket, 6+1+PATH_MAX+1,
+                        handleKobjectUeventString, kmo)) {
+      return 1;
+    }
+
+    close(kmo->kmx->uevent.socket);
+    kmo->kmx->uevent.socket = -1;
+  }
+#endif /* NETLINK_KOBJECT_UEVENT */
+
+  return 0;
+}
+#endif /* HAVE_LINUX_UINPUT_H */
+
+int
+monitorKeyboards (KeyboardMonitorObject *kmo) {
+#ifdef HAVE_LINUX_UINPUT_H
+  monitorCurrentKeyboards(kmo);
+  monitorNewKeyboards(kmo);
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 1;
+}
diff --git a/Programs/kbd_none.c b/Programs/kbd_none.c
new file mode 100644
index 0000000..3faf2d5
--- /dev/null
+++ b/Programs/kbd_none.c
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "kbd.h"
+#include "kbd_internal.h"
+
+BEGIN_KEY_CODE_MAP
+  [0] = KBD_KEY_UNMAPPED
+END_KEY_CODE_MAP
+
+int
+newKeyboardMonitorExtension (KeyboardMonitorExtension **kmx) {
+  return 1;
+}
+
+void
+destroyKeyboardMonitorExtension (KeyboardMonitorExtension *kmx) {
+}
+
+int
+newKeyboardInstanceExtension (KeyboardInstanceExtension **kix) {
+  return 1;
+}
+
+void
+destroyKeyboardInstanceExtension (KeyboardInstanceExtension *kix) {
+}
+
+int
+forwardKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
+  return 0;
+}
+
+int
+monitorKeyboards (KeyboardMonitorObject *kmo) {
+  return 0;
+}
diff --git a/Programs/ktb_audit.c b/Programs/ktb_audit.c
new file mode 100644
index 0000000..aa0a010
--- /dev/null
+++ b/Programs/ktb_audit.c
@@ -0,0 +1,192 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "ktb.h"
+#include "ktb_internal.h"
+#include "ktb_inspect.h"
+
+typedef struct {
+  KeyTable *table;
+  const KeyContext *ctx;
+  const char *path;
+} KeyTableAuditorParameters;
+
+#define KEY_TABLE_AUDITOR(name) int name (const KeyTableAuditorParameters *kta)
+typedef KEY_TABLE_AUDITOR(KeyTableAuditor);
+
+static void
+reportKeyTableAudit (const char *audit) {
+  logMessage(LOG_WARNING, "%s", audit);
+}
+
+static
+STR_BEGIN_FORMATTER(
+  formatKeyTableAuditPrefix,
+  const KeyTableAuditorParameters *kta,
+  const char *problem
+)
+  if (kta->path) STR_PRINTF("%s: ", kta->path);
+  STR_PRINTF("%s", problem);
+  if (kta->ctx) STR_PRINTF(": %" PRIws, kta->ctx->name);
+STR_END_FORMATTER
+
+static KEY_TABLE_AUDITOR(reportKeyContextProblems) {
+  int ok = 1;
+
+  if (kta->ctx->name && !kta->ctx->isSpecial) {
+    const char *problem = NULL;
+
+    if (!kta->ctx->isDefined) {
+      problem = "undefined context";
+    } else if (!kta->ctx->isReferenced) {
+      problem = "unreferenced context";
+    } else if (!(kta->ctx->keyBindings.count ||
+                 kta->ctx->mappedKeys.count ||
+                 kta->ctx->mappedKeys.superimpose ||
+                 kta->ctx->hotkeys.count)) {
+      problem = "empty context";
+    }
+
+    if (problem) {
+      ok = 0;
+
+      char audit[0X100];
+      STR_BEGIN(audit, sizeof(audit));
+
+      STR_FORMAT(formatKeyTableAuditPrefix, kta, problem);
+
+      STR_END;
+      reportKeyTableAudit(audit);
+    }
+  }
+
+  return ok;
+}
+
+static KEY_TABLE_AUDITOR(reportDuplicateKeyBindings) {
+  int ok = 1;
+  const KeyBinding *binding = kta->ctx->keyBindings.table;
+  const KeyBinding *end = binding + kta->ctx->keyBindings.count;
+
+  while (binding < end) {
+    if (binding->flags & KBF_DUPLICATE) {
+      ok = 0;
+
+      char audit[0X100];
+      STR_BEGIN(audit, sizeof(audit));
+
+      STR_FORMAT(formatKeyTableAuditPrefix, kta, "duplicate key binding");
+      STR_PRINTF(": ");
+      STR_FORMAT(formatKeyCombination, kta->table, &binding->keyCombination);
+
+      STR_END;
+      reportKeyTableAudit(audit);
+    }
+
+    binding += 1;
+  }
+
+  return ok;
+}
+
+static void
+reportKeyProblem (const KeyTableAuditorParameters *kta, const KeyValue *key, const char *problem) {
+  char audit[0X100];
+  STR_BEGIN(audit, sizeof(audit));
+
+  STR_FORMAT(formatKeyTableAuditPrefix, kta, problem);
+  STR_PRINTF(": ");
+  STR_FORMAT(formatKeyName, kta->table, key);
+
+  STR_END;
+  reportKeyTableAudit(audit);
+}
+
+static KEY_TABLE_AUDITOR(reportDuplicateHotkeys) {
+  int ok = 1;
+  const HotkeyEntry *hotkey = kta->ctx->hotkeys.table;
+  const HotkeyEntry *end = hotkey + kta->ctx->hotkeys.count;
+
+  while (hotkey < end) {
+    if (hotkey->flags & HKF_DUPLICATE) {
+      ok = 0;
+      reportKeyProblem(kta, &hotkey->keyValue, "duplicate hotkey");
+    }
+
+    hotkey += 1;
+  }
+
+  return ok;
+}
+
+static KEY_TABLE_AUDITOR(reportDuplicateMappedKeys) {
+  int ok = 1;
+  const MappedKeyEntry *map = kta->ctx->mappedKeys.table;
+  const MappedKeyEntry *end = map + kta->ctx->mappedKeys.count;
+
+  while (map < end) {
+    if (map->flags & MKF_DUPLICATE) {
+      ok = 0;
+      reportKeyProblem(kta, &map->keyValue, "duplicate mapped key");
+    }
+
+    map += 1;
+  }
+
+  return ok;
+}
+
+int
+auditKeyTable (KeyTable *table, const char *path) {
+  int ok = 1;
+
+  for (unsigned int context=0; context<table->keyContexts.count; context+=1) {
+    const KeyContext *ctx = getKeyContext(table, context);
+
+    if (ctx) {
+      static KeyTableAuditor *const auditors[] = {
+        reportKeyContextProblems,
+        reportDuplicateKeyBindings,
+        reportDuplicateHotkeys,
+        reportDuplicateMappedKeys,
+        NULL
+      };
+
+      const KeyTableAuditorParameters kta = {
+        .table = table,
+        .ctx = ctx,
+        .path = path
+      };
+
+      for (
+        KeyTableAuditor *const *auditor=auditors;
+        *auditor!=NULL; auditor+=1
+      ) {
+        if (!(*auditor)(&kta)) ok = 0;
+      }
+    }
+  }
+
+  return ok;
+}
diff --git a/Programs/ktb_cmds.c b/Programs/ktb_cmds.c
new file mode 100644
index 0000000..8bce24c
--- /dev/null
+++ b/Programs/ktb_cmds.c
@@ -0,0 +1,341 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ktb_cmds.h"
+#include "brl_cmds.h"
+
+static const CommandListEntry commandList_modes[] = {
+  { .code = BRL_CMD_HELP },
+  { .code = BRL_CMD_LEARN },
+  { .code = BRL_CMD_PREFMENU },
+  { .code = BRL_CMD_INFO },
+  { .code = BRL_CMD_DISPMD },
+  { .code = BRL_CMD_FREEZE },
+  { .code = BRL_CMD_BLK(DESCCHAR) },
+  { .code = BRL_CMD_TIME },
+  { .code = BRL_CMD_INDICATORS },
+  { .code = BRL_CMD_BLK(CONTEXT) },
+};
+
+static const CommandListEntry commandList_cursor[] = {
+  { .code = BRL_CMD_HOME },
+  { .code = BRL_CMD_BACK },
+  { .code = BRL_CMD_RETURN },
+  { .code = BRL_CMD_BLK(ROUTE) },
+  { .code = BRL_CMD_BLK(ROUTE_LINE) },
+  { .code = BRL_CMD_CSRJMP_VERT },
+  { .code = BRL_CMD_ROUTE_CURR_LOCN },
+};
+
+static const CommandListEntry commandList_vertical[] = {
+  { .code = BRL_CMD_LNUP },
+  { .code = BRL_CMD_LNDN },
+  { .code = BRL_CMD_TOP },
+  { .code = BRL_CMD_BOT },
+  { .code = BRL_CMD_TOP_LEFT },
+  { .code = BRL_CMD_BOT_LEFT },
+  { .code = BRL_CMD_PRDIFLN },
+  { .code = BRL_CMD_NXDIFLN },
+  { .code = BRL_CMD_ATTRUP },
+  { .code = BRL_CMD_ATTRDN },
+  { .code = BRL_CMD_PRPGRPH },
+  { .code = BRL_CMD_NXPGRPH },
+  { .code = BRL_CMD_PRPROMPT },
+  { .code = BRL_CMD_NXPROMPT },
+  { .code = BRL_CMD_WINUP },
+  { .code = BRL_CMD_WINDN },
+  { .code = BRL_CMD_BLK(PRINDENT) },
+  { .code = BRL_CMD_BLK(NXINDENT) },
+  { .code = BRL_CMD_BLK(PRDIFCHAR) },
+  { .code = BRL_CMD_BLK(NXDIFCHAR) },
+  { .code = BRL_CMD_BLK(GOTOLINE) },
+};
+
+static const CommandListEntry commandList_horizontal[] = {
+  { .code = BRL_CMD_FWINLT },
+  { .code = BRL_CMD_FWINRT },
+  { .code = BRL_CMD_FWINLTSKIP },
+  { .code = BRL_CMD_FWINRTSKIP },
+  { .code = BRL_CMD_PRNBWIN},
+  { .code = BRL_CMD_NXNBWIN},
+  { .code = BRL_CMD_LNBEG },
+  { .code = BRL_CMD_LNEND },
+  { .code = BRL_CMD_CHRLT },
+  { .code = BRL_CMD_CHRRT },
+  { .code = BRL_CMD_HWINLT },
+  { .code = BRL_CMD_HWINRT },
+  { .code = BRL_CMD_BLK(SETLEFT) },
+};
+
+static const CommandListEntry commandList_window[] = {
+  { .code = BRL_CMD_GUI_TITLE },
+  { .code = BRL_CMD_GUI_BRL_ACTIONS },
+  { .code = BRL_CMD_GUI_HOME },
+  { .code = BRL_CMD_GUI_BACK },
+  { .code = BRL_CMD_GUI_DEV_SETTINGS },
+  { .code = BRL_CMD_GUI_DEV_OPTIONS },
+  { .code = BRL_CMD_GUI_APP_LIST },
+  { .code = BRL_CMD_GUI_APP_MENU },
+  { .code = BRL_CMD_GUI_APP_ALERTS },
+  { .code = BRL_CMD_GUI_AREA_ACTV },
+  { .code = BRL_CMD_GUI_AREA_PREV },
+  { .code = BRL_CMD_GUI_AREA_NEXT },
+  { .code = BRL_CMD_GUI_ITEM_FRST },
+  { .code = BRL_CMD_GUI_ITEM_PREV },
+  { .code = BRL_CMD_GUI_ITEM_NEXT },
+  { .code = BRL_CMD_GUI_ITEM_LAST },
+};
+
+static const CommandListEntry commandList_clipboard[] = {
+  { .code = BRL_CMD_BLK(CLIP_NEW) },
+  { .code = BRL_CMD_BLK(CLIP_ADD) },
+  { .code = BRL_CMD_BLK(COPY_LINE) },
+  { .code = BRL_CMD_BLK(COPY_RECT) },
+  { .code = BRL_CMD_BLK(CLIP_COPY) },
+  { .code = BRL_CMD_BLK(CLIP_APPEND) },
+  { .code = BRL_CMD_PASTE },
+  { .code = BRL_CMD_BLK(PASTE_HISTORY) },
+  { .code = BRL_CMD_PRSEARCH },
+  { .code = BRL_CMD_NXSEARCH },
+  { .code = BRL_CMD_CLIP_SAVE },
+  { .code = BRL_CMD_CLIP_RESTORE },
+};
+
+static const CommandListEntry commandList_text[] = {
+  { .code = BRL_CMD_TXTSEL_CLEAR },
+  { .code = BRL_CMD_BLK(TXTSEL_SET) },
+  { .code = BRL_CMD_BLK(TXTSEL_START) },
+  { .code = BRL_CMD_TXTSEL_ALL },
+  { .code = BRL_CMD_HOST_COPY },
+  { .code = BRL_CMD_HOST_CUT },
+  { .code = BRL_CMD_HOST_PASTE },
+};
+
+static const CommandListEntry commandList_feature[] = {
+  { .code = BRL_CMD_TOUCH_NAV },
+  { .code = BRL_CMD_AUTOREPEAT },
+  { .code = BRL_CMD_SIXDOTS },
+  { .code = BRL_CMD_CONTRACTED },
+  { .code = BRL_CMD_COMPBRL6 },
+  { .code = BRL_CMD_SKPIDLNS },
+  { .code = BRL_CMD_SKPBLNKWINS },
+  { .code = BRL_CMD_SLIDEWIN },
+  { .code = BRL_CMD_CSRTRK },
+  { .code = BRL_CMD_CSRSIZE },
+  { .code = BRL_CMD_CSRVIS },
+  { .code = BRL_CMD_CSRHIDE },
+  { .code = BRL_CMD_CSRBLINK },
+  { .code = BRL_CMD_ATTRVIS },
+  { .code = BRL_CMD_ATTRBLINK },
+  { .code = BRL_CMD_CAPBLINK },
+  { .code = BRL_CMD_TUNES },
+  { .code = BRL_CMD_BLK(SET_TEXT_TABLE) },
+  { .code = BRL_CMD_BLK(SET_ATTRIBUTES_TABLE) },
+  { .code = BRL_CMD_BLK(SET_CONTRACTION_TABLE) },
+  { .code = BRL_CMD_BLK(SET_KEYBOARD_TABLE) },
+  { .code = BRL_CMD_BLK(SET_LANGUAGE_PROFILE) },
+};
+
+static const CommandListEntry commandList_menu[] = {
+  { .code = BRL_CMD_MENU_PREV_ITEM },
+  { .code = BRL_CMD_MENU_NEXT_ITEM },
+  { .code = BRL_CMD_MENU_FIRST_ITEM },
+  { .code = BRL_CMD_MENU_LAST_ITEM },
+  { .code = BRL_CMD_MENU_PREV_SETTING },
+  { .code = BRL_CMD_MENU_NEXT_SETTING },
+  { .code = BRL_CMD_MENU_PREV_LEVEL },
+  { .code = BRL_CMD_PREFSAVE },
+  { .code = BRL_CMD_PREFLOAD },
+  { .code = BRL_CMD_PREFRESET },
+};
+
+static const CommandListEntry commandList_say[] = {
+  { .code = BRL_CMD_MUTE },
+  { .code = BRL_CMD_SAY_LINE },
+  { .code = BRL_CMD_SAY_ALL },
+  { .code = BRL_CMD_SAY_ABOVE },
+  { .code = BRL_CMD_SAY_BELOW },
+  { .code = BRL_CMD_SPKHOME },
+  { .code = BRL_CMD_SAY_SOFTER },
+  { .code = BRL_CMD_SAY_LOUDER },
+  { .code = BRL_CMD_SAY_SLOWER },
+  { .code = BRL_CMD_SAY_FASTER },
+  { .code = BRL_CMD_SAY_LOWER },
+  { .code = BRL_CMD_SAY_HIGHER },
+  { .code = BRL_CMD_AUTOSPEAK },
+  { .code = BRL_CMD_ASPK_SEL_LINE },
+  { .code = BRL_CMD_ASPK_SEL_CHAR },
+  { .code = BRL_CMD_ASPK_INS_CHARS },
+  { .code = BRL_CMD_ASPK_DEL_CHARS },
+  { .code = BRL_CMD_ASPK_REP_CHARS },
+  { .code = BRL_CMD_ASPK_CMP_WORDS },
+  { .code = BRL_CMD_ASPK_INDENT },
+};
+
+static const CommandListEntry commandList_speak[] = {
+  { .code = BRL_CMD_BLK(ROUTE_SPEECH) },
+  { .code = BRL_CMD_SPEAK_CURR_CHAR },
+  { .code = BRL_CMD_DESC_CURR_CHAR },
+  { .code = BRL_CMD_SPEAK_PREV_CHAR },
+  { .code = BRL_CMD_SPEAK_NEXT_CHAR },
+  { .code = BRL_CMD_SPEAK_FRST_CHAR },
+  { .code = BRL_CMD_SPEAK_LAST_CHAR },
+  { .code = BRL_CMD_SPEAK_CURR_WORD },
+  { .code = BRL_CMD_SPELL_CURR_WORD },
+  { .code = BRL_CMD_SPEAK_PREV_WORD },
+  { .code = BRL_CMD_SPEAK_NEXT_WORD },
+  { .code = BRL_CMD_SPEAK_CURR_LINE },
+  { .code = BRL_CMD_SPEAK_PREV_LINE },
+  { .code = BRL_CMD_SPEAK_NEXT_LINE },
+  { .code = BRL_CMD_SPEAK_FRST_LINE },
+  { .code = BRL_CMD_SPEAK_LAST_LINE },
+  { .code = BRL_CMD_SPEAK_INDENT },
+  { .code = BRL_CMD_SPEAK_CURR_LOCN },
+  { .code = BRL_CMD_SHOW_CURR_LOCN },
+};
+
+static const CommandListEntry commandList_input[] = {
+  { .code = BRL_CMD_BLK(PASSDOTS) },
+  { .code = BRL_CMD_BLK(PASSCHAR) },
+  { .code = BRL_CMD_KEY(BACKSPACE) },
+  { .code = BRL_CMD_KEY(ENTER) },
+  { .code = BRL_CMD_KEY(TAB) },
+  { .code = BRL_CMD_KEY(CURSOR_LEFT) },
+  { .code = BRL_CMD_KEY(CURSOR_RIGHT) },
+  { .code = BRL_CMD_KEY(CURSOR_UP) },
+  { .code = BRL_CMD_KEY(CURSOR_DOWN) },
+  { .code = BRL_CMD_KEY(PAGE_UP) },
+  { .code = BRL_CMD_KEY(PAGE_DOWN) },
+  { .code = BRL_CMD_KEY(HOME) },
+  { .code = BRL_CMD_KEY(END) },
+  { .code = BRL_CMD_KEY(INSERT) },
+  { .code = BRL_CMD_KEY(DELETE) },
+  { .code = BRL_CMD_UNSTICK },
+  { .code = BRL_CMD_UPPER },
+  { .code = BRL_CMD_SHIFT },
+  { .code = BRL_CMD_CONTROL },
+  { .code = BRL_CMD_META },
+  { .code = BRL_CMD_ALTGR },
+  { .code = BRL_CMD_GUI },
+  { .code = BRL_CMD_KEY(ESCAPE) },
+  { .code = BRL_CMD_KEY(FUNCTION) },
+  { .code = BRL_CMD_BLK(SWITCHVT) },
+  { .code = BRL_CMD_SWITCHVT_PREV },
+  { .code = BRL_CMD_SWITCHVT_NEXT },
+  { .code = BRL_CMD_BLK(SELECTVT) },
+  { .code = BRL_CMD_SELECTVT_PREV },
+  { .code = BRL_CMD_SELECTVT_NEXT },
+  { .code = BRL_CMD_BRLKBD },
+  { .code = BRL_CMD_BRLUCDOTS },
+};
+
+static const CommandListEntry commandList_special[] = {
+  { .code = BRL_CMD_BLK(SETMARK) },
+  { .code = BRL_CMD_BLK(GOTOMARK) },
+  { .code = BRL_CMD_REFRESH },
+  { .code = BRL_CMD_BLK(REFRESH_LINE) },
+  { .code = BRL_CMD_RESTARTBRL },
+  { .code = BRL_CMD_BRL_STOP },
+  { .code = BRL_CMD_BRL_START },
+  { .code = BRL_CMD_RESTARTSPEECH },
+  { .code = BRL_CMD_SPK_STOP },
+  { .code = BRL_CMD_SPK_START },
+  { .code = BRL_CMD_SCR_STOP },
+  { .code = BRL_CMD_SCR_START },
+};
+
+static const CommandListEntry commandList_internal[] = {
+  { .code = BRL_CMD_NOOP },
+  { .code = BRL_CMD_OFFLINE },
+  { .code = BRL_CMD_BLK(ALERT) },
+  { .code = BRL_CMD_BLK(PASSXT) },
+  { .code = BRL_CMD_BLK(PASSAT) },
+  { .code = BRL_CMD_BLK(PASSPS2) },
+  { .code = BRL_CMD_BLK(TOUCH_AT) },
+  { .code = BRL_CMD_BLK(MACRO) },
+  { .code = BRL_CMD_BLK(HOSTCMD) },
+};
+
+#define COMMAND_LIST(name) .commands = { \
+  .table = commandList_##name, \
+  .count = ARRAY_COUNT(commandList_##name), \
+}
+
+const CommandGroupEntry commandGroupTable[] = {
+  { COMMAND_LIST(modes),
+    .after = commandGroupHook_hotkeys,
+    .name = "Special Modes"
+  },
+
+  { COMMAND_LIST(cursor),
+    .name = "Cursor Functions"
+  },
+
+  { COMMAND_LIST(vertical),
+    .name = "Vertical Navigation"
+  },
+
+  { COMMAND_LIST(horizontal),
+    .name = "Horizontal Navigation"
+  },
+
+  { COMMAND_LIST(window),
+    .name = "Window Navigation"
+  },
+
+  { COMMAND_LIST(clipboard),
+    .name = "Clipboard Functions"
+  },
+
+  { COMMAND_LIST(text),
+    .name = "Text Selection and the Host Clipboard"
+  },
+
+  { COMMAND_LIST(feature),
+    .name = "Configuration Functions"
+  },
+
+  { COMMAND_LIST(menu),
+    .name = "Menu Operations"
+  },
+
+  { COMMAND_LIST(say),
+    .name = "Speech Functions"
+  },
+
+  { COMMAND_LIST(speak),
+    .name = "Speech Navigation"
+  },
+
+  { COMMAND_LIST(input),
+    .before = commandGroupHook_keyboardFunctions,
+    .name = "Keyboard Input"
+  },
+
+  { COMMAND_LIST(special),
+    .name = "Special Functions"
+  },
+
+  { COMMAND_LIST(internal),
+    .name = "Internal Functions"
+  },
+};
+
+const unsigned char commandGroupCount = ARRAY_COUNT(commandGroupTable);
diff --git a/Programs/ktb_cmds.h b/Programs/ktb_cmds.h
new file mode 100644
index 0000000..d232121
--- /dev/null
+++ b/Programs/ktb_cmds.h
@@ -0,0 +1,53 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_CMDS
+#define BRLTTY_INCLUDED_KTB_CMDS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct CommandGroupHookDataStruct CommandGroupHookData;
+typedef void CommandGroupHook (CommandGroupHookData *data);
+extern CommandGroupHook commandGroupHook_keyboardFunctions;
+extern CommandGroupHook commandGroupHook_hotkeys;
+
+typedef struct {
+  int code;
+} CommandListEntry;
+
+typedef struct {
+  struct {
+    const CommandListEntry *table;
+    unsigned int count;
+  } commands;
+
+  const char *name;
+  CommandGroupHook *before;
+  CommandGroupHook *after;
+} CommandGroupEntry;
+
+extern const CommandGroupEntry commandGroupTable[];
+extern const unsigned char commandGroupCount;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_CMDS */
diff --git a/Programs/ktb_compile.c b/Programs/ktb_compile.c
new file mode 100644
index 0000000..6eac434
--- /dev/null
+++ b/Programs/ktb_compile.c
@@ -0,0 +1,1980 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <ctype.h>
+
+#include "log.h"
+#include "file.h"
+#include "datafile.h"
+#include "utf8.h"
+#include "cmd.h"
+#include "brl_cmds.h"
+#include "ktb.h"
+#include "ktb_internal.h"
+#include "program.h"
+
+const KeyboardFunction keyboardFunctionTable[] = {
+  {.name="dot1", .bit=BRL_DOT1},
+  {.name="dot2", .bit=BRL_DOT2},
+  {.name="dot3", .bit=BRL_DOT3},
+  {.name="dot4", .bit=BRL_DOT4},
+  {.name="dot5", .bit=BRL_DOT5},
+  {.name="dot6", .bit=BRL_DOT6},
+  {.name="dot7", .bit=BRL_DOT7},
+  {.name="dot8", .bit=BRL_DOT8},
+  {.name="space", .bit=BRL_DOTC},
+  {.name="shift", .bit=BRL_FLG_INPUT_SHIFT},
+  {.name="upper", .bit=BRL_FLG_INPUT_UPPER},
+  {.name="control", .bit=BRL_FLG_INPUT_CONTROL},
+  {.name="meta", .bit=BRL_FLG_INPUT_META},
+  {.name="altgr", .bit=BRL_FLG_INPUT_ALTGR},
+  {.name="gui", .bit=BRL_FLG_INPUT_GUI}
+};
+unsigned char keyboardFunctionCount = ARRAY_COUNT(keyboardFunctionTable);
+
+typedef struct {
+  const char *file;
+  KeyTable *table;
+
+  const CommandEntry **commandTable;
+  unsigned int commandCount;
+
+  BoundCommand nullBoundCommand;
+
+  unsigned char context;
+  unsigned hideRequested:1;
+  unsigned hideInherited:1;
+} KeyTableData;
+
+void
+copyKeyValues (KeyValue *target, const KeyValue *source, unsigned int count) {
+  memcpy(target, source, count*sizeof(*target));
+}
+
+int
+compareKeyValues (const KeyValue *value1, const KeyValue *value2) {
+  if (value1->group < value2->group) return -1;
+  if (value1->group > value2->group) return 1;
+
+  if (value1->number < value2->number) return -1;
+  if (value1->number > value2->number) return 1;
+
+  return 0;
+}
+
+static int
+compareKeyArrays (
+  unsigned int count1, const KeyValue *array1,
+  unsigned int count2, const KeyValue *array2
+) {
+  if (count1 < count2) return -1;
+  if (count1 > count2) return 1;
+  return memcmp(array1, array2, count1*sizeof(*array1));
+}
+
+int
+findKeyValue (
+  const KeyValue *values, unsigned int count,
+  const KeyValue *target, unsigned int *position
+) {
+  int first = 0;
+  int last = count - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    const KeyValue *value = &values[current];
+    int relation = compareKeyValues(target, value);
+
+    if (relation < 0) {
+      last = current - 1;
+    } else if (relation > 0) {
+      first = current + 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+int
+insertKeyValue (
+  KeyValue **values, unsigned int *count, unsigned int *size,
+  const KeyValue *value, unsigned int position
+) {
+  if (*count == *size) {
+    unsigned int newSize = (*size)? (*size)<<1: 0X10;
+    KeyValue *newValues = realloc(*values, ARRAY_SIZE(newValues, newSize));
+
+    if (!newValues) {
+      logMallocError();
+      return 0;
+    }
+
+    *values = newValues;
+    *size = newSize;
+  }
+
+  memmove(&(*values)[position+1], &(*values)[position],
+          ((*count)++ - position) * sizeof(**values));
+  (*values)[position] = *value;
+  return 1;
+}
+
+void
+removeKeyValue (KeyValue *values, unsigned int *count, unsigned int position) {
+  memmove(&values[position], &values[position+1],
+          (--*count - position) * sizeof(*values));
+}
+
+int
+deleteKeyValue (KeyValue *values, unsigned int *count, const KeyValue *value) {
+  unsigned int position;
+  int found = findKeyValue(values, *count, value, &position);
+
+  if (found) removeKeyValue(values, count, position);
+  return found;
+}
+
+static inline int
+hideBindings (const KeyTableData *ktd) {
+  return ktd->hideRequested || ktd->hideInherited;
+}
+
+static KeyContext *
+getKeyContext (KeyTableData *ktd, unsigned char context) {
+  if (context >= ktd->table->keyContexts.count) {
+    unsigned int newCount = context + 1;
+    KeyContext *newTable = realloc(ktd->table->keyContexts.table, ARRAY_SIZE(newTable, newCount));
+
+    if (!newTable) {
+      logMallocError();
+      return NULL;
+    }
+    ktd->table->keyContexts.table = newTable;
+
+    while (ktd->table->keyContexts.count < newCount) {
+      KeyContext *ctx = &ktd->table->keyContexts.table[ktd->table->keyContexts.count++];
+      memset(ctx, 0, sizeof(*ctx));
+
+      ctx->name = NULL;
+      ctx->title = NULL;
+
+      ctx->isSpecial = 0;
+      ctx->isDefined = 0;
+      ctx->isReferenced = 0;
+      ctx->isIsolated = 0;
+
+      ctx->keyBindings.table = NULL;
+      ctx->keyBindings.size = 0;
+      ctx->keyBindings.count = 0;
+
+      ctx->hotkeys.table = NULL;
+      ctx->hotkeys.size = 0;
+      ctx->hotkeys.count = 0;
+
+      ctx->mappedKeys.table = NULL;
+      ctx->mappedKeys.size = 0;
+      ctx->mappedKeys.count = 0;
+      ctx->mappedKeys.superimpose = 0;
+    }
+  }
+
+  return &ktd->table->keyContexts.table[context];
+}
+
+static inline KeyContext *
+getCurrentKeyContext (KeyTableData *ktd) {
+  return getKeyContext(ktd, ktd->context);
+}
+
+static int
+setString (wchar_t **string, const wchar_t *characters, size_t length) {
+  if (*string) free(*string);
+
+  if (!(*string = malloc(ARRAY_SIZE(*string, length+1)))) {
+    logMallocError();
+    return 0;
+  }
+
+  wmemcpy(*string, characters, length);
+  (*string)[length] = 0;
+  return 1;
+}
+
+static int
+setKeyContextName (KeyContext *ctx, const wchar_t *name, size_t length) {
+  return setString(&ctx->name, name, length);
+}
+
+static int
+setKeyContextTitle (KeyContext *ctx, const wchar_t *title, size_t length) {
+  return setString(&ctx->title, title, length);
+}
+
+static int
+findKeyContext (unsigned char *context, const wchar_t *name, int length, KeyTableData *ktd) {
+  for (*context=0; *context<ktd->table->keyContexts.count; *context+=1) {
+    KeyContext *ctx = &ktd->table->keyContexts.table[*context];
+
+    if (ctx->name) {
+      if (wcslen(ctx->name) == length) {
+        if (wmemcmp(ctx->name, name, length) == 0) {
+          return 1;
+        }
+      }
+    }
+  }
+
+  {
+    KeyContext *ctx = getKeyContext(ktd, *context);
+
+    if (ctx) {
+      if (setKeyContextName(ctx, name, length)) {
+        return 1;
+      }
+
+      ktd->table->keyContexts.count -= 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+compareToName (const wchar_t *location1, int length1, const char *location2) {
+  const wchar_t *end1 = location1 + length1;
+
+  while (1) {
+    if (location1 == end1) return *location2? -1: 0;
+    if (!*location2) return 1;
+
+    {
+      wchar_t character1 = towlower(*location1);
+      char character2 = tolower((unsigned char)*location2);
+
+      if (character1 < character2) return -1;
+      if (character1 > character2) return 1;
+    }
+
+    location1 += 1;
+    location2 += 1;
+  }
+}
+
+static int
+sortKeyNames (const void *element1, const void *element2) {
+  const KeyNameEntry *const *kne1 = element1;
+  const KeyNameEntry *const *kne2 = element2;
+  return strcasecmp((*kne1)->name, (*kne2)->name);
+}
+
+static int
+searchKeyName (const void *target, const void *element) {
+  const DataOperand *name = target;
+  const KeyNameEntry *const *kne = element;
+  return compareToName(name->characters, name->length, (*kne)->name);
+}
+
+static int
+sortKeyValues (const void *element1, const void *element2) {
+  const KeyNameEntry *const *kne1 = element1;
+  const KeyNameEntry *const *kne2 = element2;
+
+  {
+    int result = compareKeyValues(&(*kne1)->value, &(*kne2)->value);
+    if (result != 0) return result;
+  }
+
+  if (*kne1 < *kne2) return -1;
+  if (*kne1 > *kne2) return 1;
+
+  return 0;
+}
+
+typedef struct {
+  unsigned int count;
+} CountKeyNameData;
+
+static int
+countKeyName (const KeyNameEntry *kne, void *data) {
+  if (kne) {
+    CountKeyNameData *ckd = data;
+
+    ckd->count += 1;
+  }
+
+  return 1;
+}
+
+typedef struct {
+  const KeyNameEntry **kne;
+} AddKeyNameData;
+
+static int
+addKeyName (const KeyNameEntry *kne, void *data) {
+  if (kne) {
+    AddKeyNameData *akd = data;
+
+    *akd->kne++ = kne;
+  }
+
+  return 1;
+}
+
+static int
+allocateKeyNameTable (KeyTableData *ktd, KEY_NAME_TABLES_REFERENCE keys) {
+  {
+    CountKeyNameData ckd = {
+      .count = 0
+    };
+
+    forEachKeyName(keys, countKeyName, &ckd);
+    ktd->table->keyNames.count = ckd.count;
+  }
+
+  if ((ktd->table->keyNames.table = malloc(ARRAY_SIZE(ktd->table->keyNames.table, ktd->table->keyNames.count)))) {
+    {
+      AddKeyNameData akd = {
+        .kne = ktd->table->keyNames.table
+      };
+
+      forEachKeyName(keys, addKeyName, &akd);
+    }
+
+    qsort(ktd->table->keyNames.table, ktd->table->keyNames.count, sizeof(*ktd->table->keyNames.table), sortKeyNames);
+    return 1;
+  }
+
+  return 0;
+}
+
+static const KeyNameEntry *const *
+findKeyName (const wchar_t *characters, int length, KeyTableData *ktd) {
+  const DataOperand name = {
+    .characters = characters,
+    .length = length
+  };
+
+  return bsearch(&name, ktd->table->keyNames.table, ktd->table->keyNames.count, sizeof(*ktd->table->keyNames.table), searchKeyName);
+}
+
+static int
+parseKeyName (DataFile *file, KeyValue *value, const wchar_t *characters, int length, KeyTableData *ktd) {
+  const wchar_t *suffix = wmemchr(characters, WC_C('.'), length);
+  int prefixLength;
+  int suffixLength;
+
+  if (suffix) {
+    if (!(prefixLength = suffix - characters)) {
+      reportDataError(file, "missing key group name: %.*" PRIws, length, characters);
+      return 0;
+    }
+
+    if (!(suffixLength = (characters + length) - ++suffix)) {
+      reportDataError(file, "missing key number: %.*" PRIws, length, characters);
+      return 0;
+    }
+  } else {
+    prefixLength = length;
+    suffixLength = 0;
+  }
+
+  {
+    const KeyNameEntry *const *kne = findKeyName(characters, prefixLength, ktd);
+
+    if (!kne) {
+      reportDataError(file, "unknown key name: %.*" PRIws, prefixLength, characters);
+      return 0;
+    }
+
+    *value = (*kne)->value;
+  }
+
+  if (suffix) {
+    int ok = 0;
+    int number;
+
+    if (isNumber(&number, suffix, suffixLength))
+      if (number > 0)
+        if (--number <= KTB_KEY_MAX)
+          ok = 1;
+
+    if (!ok) {
+      reportDataError(file, "invalid key number: %.*" PRIws, suffixLength, suffix);
+      return 0;
+    }
+
+    if (value->number != KTB_KEY_ANY) {
+      reportDataError(file, "not a key group: %.*" PRIws, prefixLength, characters);
+      return 0;
+    }
+
+    value->number = number;
+  }
+
+  return 1;
+}
+
+static int
+getKeyOperand (DataFile *file, KeyValue *value, KeyTableData *ktd) {
+  DataString name;
+
+  if (getDataString(file, &name, 1, "key name")) {
+    if (parseKeyName(file, value, name.characters, name.length, ktd)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+newModifierPosition (const KeyCombination *combination, const KeyValue *modifier, unsigned int *position) {
+  int found = findKeyValue(combination->modifierKeys, combination->modifierCount, modifier, position);
+  return found && (modifier->number != KTB_KEY_ANY);
+}
+
+static int
+insertModifier (DataFile *file, KeyCombination *combination, unsigned int position, const KeyValue *value) {
+  if (combination->modifierCount == MAX_MODIFIERS_PER_COMBINATION) {
+    reportDataError(file, "too many modifier keys");
+    return 0;
+  }
+
+  {
+    int index = combination->modifierCount;
+
+    while (index--) {
+      if (index >= position) {
+        combination->modifierKeys[index+1] = combination->modifierKeys[index];
+      }
+
+      if (combination->modifierPositions[index] >= position) {
+        combination->modifierPositions[index] += 1;
+      }
+    }
+  }
+
+  combination->modifierKeys[position] = *value;
+  combination->modifierPositions[combination->modifierCount++] = position;
+  return 1;
+}
+
+static int
+parseKeyCombination (DataFile *file, KeyCombination *combination, const wchar_t *characters, int length, KeyTableData *ktd) {
+  KeyValue value;
+
+  memset(combination, 0, sizeof(*combination));
+  combination->modifierCount = 0;
+
+  while (1) {
+    const wchar_t *end = wmemchr(characters, WC_C('+'), length);
+    if (!end) break;
+
+    {
+      int count = end - characters;
+
+      if (!count) {
+        reportDataError(file, "missing modifier key");
+        return 0;
+      }
+      if (!parseKeyName(file, &value, characters, count, ktd)) return 0;
+
+      {
+        unsigned int position;
+
+        if (newModifierPosition(combination, &value, &position)) {
+          reportDataError(file, "duplicate modifier key: %.*" PRIws, count, characters);
+          return 0;
+        }
+
+        if (!insertModifier(file, combination, position, &value)) return 0;
+        if (value.number == KTB_KEY_ANY) combination->anyKeyCount += 1;
+      }
+
+      length -= count + 1;
+      characters = end + 1;
+    }
+  }
+
+  if (length) {
+    if (*characters == WC_C('!')) {
+      characters += 1, length -= 1;
+      combination->flags |= KCF_IMMEDIATE_KEY;
+    }
+  }
+
+  if (!length) {
+    reportDataError(file, "missing key");
+    return 0;
+  }
+  if (!parseKeyName(file, &value, characters, length, ktd)) return 0;
+
+  {
+    unsigned int position;
+
+    if (newModifierPosition(combination, &value, &position)) {
+      reportDataError(file, "duplicate key: %.*" PRIws, length, characters);
+      return 0;
+    }
+
+    if (combination->flags & KCF_IMMEDIATE_KEY) {
+      combination->immediateKey = value;
+    } else if (!insertModifier(file, combination, position, &value)) {
+      return 0;
+    }
+    if (value.number == KTB_KEY_ANY) combination->anyKeyCount += 1;
+  }
+
+  return 1;
+}
+
+static int
+getKeysOperand (DataFile *file, KeyCombination *combination, KeyTableData *ktd) {
+  DataString names;
+
+  if (getDataString(file, &names, 1, "key combination")) {
+    if (parseKeyCombination(file, combination, names.characters, names.length, ktd)) return 1;
+  }
+
+  return 0;
+}
+
+static int
+sortKeyboardFunctionNames (const void *element1, const void *element2) {
+  const KeyboardFunction *const *kbf1 = element1;
+  const KeyboardFunction *const *kbf2 = element2;
+  return strcasecmp((*kbf1)->name, (*kbf2)->name);
+}
+
+static int
+searchKeyboardFunctionName (const void *target, const void *element) {
+  const DataOperand *name = target;
+  const KeyboardFunction *const *kbf = element;
+  return compareToName(name->characters, name->length, (*kbf)->name);
+}
+
+static int
+parseKeyboardFunctionName (DataFile *file, const KeyboardFunction **keyboardFunction, const wchar_t *characters, int length, KeyTableData *ktd) {
+  static const KeyboardFunction **sortedKeyboardFunctions = NULL;
+
+  if (!sortedKeyboardFunctions) {
+    const KeyboardFunction **newTable = malloc(ARRAY_SIZE(newTable, keyboardFunctionCount));
+
+    if (!newTable) {
+      logMallocError();
+      return 0;
+    }
+
+    {
+      const KeyboardFunction *source = keyboardFunctionTable;
+      const KeyboardFunction **target = newTable;
+      unsigned int count = keyboardFunctionCount;
+
+      do {
+        *target++ = source++;
+      } while (--count);
+
+      qsort(newTable, keyboardFunctionCount, sizeof(*newTable), sortKeyboardFunctionNames);
+    }
+
+    sortedKeyboardFunctions = newTable;
+    registerProgramMemory("sorted-keyboard-functions", &sortedKeyboardFunctions);
+  }
+
+  {
+    const DataOperand name = {
+      .characters = characters,
+      .length = length
+    };
+    const KeyboardFunction *const *kbf = bsearch(&name, sortedKeyboardFunctions, keyboardFunctionCount, sizeof(*sortedKeyboardFunctions), searchKeyboardFunctionName);
+
+    if (kbf) {
+      *keyboardFunction = *kbf;
+      return 1;
+    }
+  }
+
+  reportDataError(file, "unknown keyboard function: %.*" PRIws, length, characters);
+  return 0;
+}
+
+static int
+getKeyboardFunctionOperand (DataFile *file, const KeyboardFunction **keyboardFunction, KeyTableData *ktd) {
+  DataOperand name;
+
+  if (getDataOperand(file, &name, "keyboard function name")) {
+    if (parseKeyboardFunctionName(file, keyboardFunction, name.characters, name.length, ktd)) return 1;
+  }
+
+  return 0;
+}
+
+static int
+sortCommandNames (const void *element1, const void *element2) {
+  const CommandEntry *const *cmd1 = element1;
+  const CommandEntry *const *cmd2 = element2;
+  return strcasecmp((*cmd1)->name, (*cmd2)->name);
+}
+
+static int
+searchCommandName (const void *target, const void *element) {
+  const DataOperand *name = target;
+  const CommandEntry *const *cmd = element;
+  return compareToName(name->characters, name->length, (*cmd)->name);
+}
+
+static int
+allocateCommandTable (KeyTableData *ktd) {
+  {
+    const CommandEntry *command = commandTable;
+
+    ktd->commandCount = 0;
+    while (command->name) {
+      ktd->commandCount += 1;
+      command += 1;
+    }
+  }
+
+  if ((ktd->commandTable = malloc(ktd->commandCount * sizeof(*ktd->commandTable)))) {
+    {
+      const CommandEntry *command = commandTable;
+      const CommandEntry **address = ktd->commandTable;
+      while (command->name) *address++ = command++;
+    }
+
+    qsort(ktd->commandTable, ktd->commandCount, sizeof(*ktd->commandTable), sortCommandNames);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+applyCommandModifier (int *command, const CommandModifierEntry *modifiers, const DataOperand *name) {
+  const CommandModifierEntry *modifier = modifiers;
+
+  while (modifier->name) {
+    if (!(*command & modifier->bit)) {
+      if (compareToName(name->characters, name->length, modifier->name) == 0) {
+        *command |= modifier->bit;
+        return 1;
+      }
+    }
+
+    modifier += 1;
+  }
+
+  return 0;
+}
+
+static int
+parseCommandOperand (DataFile *file, BoundCommand *cmd, const wchar_t *characters, int length, KeyTableData *ktd) {
+  int offsetDone = 0;
+  int unicodeDone = 0;
+
+  const wchar_t *end = wmemchr(characters, WC_C('+'), length);
+  const CommandEntry *const *command;
+
+  {
+    const DataOperand name = {
+      .characters = characters,
+      .length = end? end-characters: length
+    };
+
+    if (!name.length) {
+      reportDataError(file, "missing command name");
+      return 0;
+    }
+
+    if (!(command = bsearch(&name, ktd->commandTable, ktd->commandCount, sizeof(*ktd->commandTable), searchCommandName))) {
+      reportDataError(file, "unknown command name: %.*" PRIws, name.length, name.characters);
+      return 0;
+    }
+  }
+
+  cmd->value = (cmd->entry = *command)->code;
+
+  while (end) {
+    DataOperand modifier;
+
+    if ((modifier.length = (length -= (end - characters) + 1))) {
+      modifier.characters = characters = end + 1;
+      end = wmemchr(characters, WC_C('+'), length);
+      if (end) modifier.length = end - characters;
+    }
+
+    if (!modifier.length) {
+      reportDataError(file, "missing command modifier");
+      return 0;
+    }
+
+    if ((*command)->isToggle && !(cmd->value & BRL_FLG_TOGGLE_MASK)) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_toggle, &modifier)) continue;
+    }
+
+    if ((*command)->isMotion) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_motion, &modifier)) continue;
+    }
+
+    if ((*command)->isRow) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_row, &modifier)) continue;
+    }
+
+    if ((*command)->isVertical) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_vertical, &modifier)) continue;
+    }
+
+    if ((*command)->isInput) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_input, &modifier)) continue;
+    }
+
+    if ((*command)->isCharacter) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_character, &modifier)) continue;
+
+      if (!unicodeDone) {
+        if (modifier.length == 1) {
+          cmd->value |= BRL_ARG_SET(modifier.characters[0]);
+          unicodeDone = 1;
+          continue;
+        }
+      }
+    }
+
+    if ((*command)->isBraille) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_braille, &modifier)) continue;
+      if (applyCommandModifier(&cmd->value, commandModifierTable_character, &modifier)) continue;
+    }
+
+    if ((*command)->isKeyboard) {
+      if (applyCommandModifier(&cmd->value, commandModifierTable_keyboard, &modifier)) continue;
+    }
+
+    if (!offsetDone) {
+      if ((*command)->code == BRL_CMD_BLK(CONTEXT)) {
+        unsigned char context;
+
+        if (findKeyContext(&context, modifier.characters, modifier.length, ktd)) {
+          KeyContext *ctx = getKeyContext(ktd, context);
+          if (!ctx) return 0;
+
+          if (ctx->isSpecial) {
+            reportDataError(file, "invalid target context: %"PRIws, ctx->name);
+          } else {
+            ctx->isReferenced = 1;
+            cmd->value += context - KTB_CTX_DEFAULT;
+          }
+
+          offsetDone = 1;
+          continue;
+        }
+      } else if (((*command)->isOffset || (*command)->isColumn) || (*command)->isRow) {
+        int maximum = BRL_MSK_ARG - ((*command)->code & BRL_MSK_ARG);
+        int offset;
+
+        if (isNumber(&offset, modifier.characters, modifier.length)) {
+          if ((offset >= 0) && (offset <= maximum)) {
+            cmd->value += offset;
+            offsetDone = 1;
+            continue;
+          }
+        }
+      }
+    }
+
+    reportDataError(file, "unknown command modifier: %.*" PRIws, modifier.length, modifier.characters);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+getCommandsOperand (DataFile *file, BoundCommand **cmds, KeyTableData *ktd) {
+  DataString commands;
+
+  if (getDataString(file, &commands, 1, "command")) {
+    const wchar_t *characters = commands.characters;
+    unsigned int length = commands.length;
+    int first = 1;
+
+    while (1) {
+      int count;
+
+      BoundCommand *cmd = *cmds++;
+      if (!cmd) break;
+
+      if (first) {
+        first = 0;
+      } else if (length) {
+        characters += 1;
+        length -= 1;
+      }
+
+      {
+        const wchar_t *end = wmemchr(characters, WC_C(':'), length);
+        count = end? (end - characters): length;
+      }
+
+      if (!count) {
+        *cmd = ktd->nullBoundCommand;
+      } else if (!parseCommandOperand(file, cmd, characters, count, ktd)) {
+        return 0;
+      }
+
+      characters += count;
+      length -= count;
+    }
+
+    if (!length) return 1;
+    reportDataError(file, "too many commands: %.*" PRIws, length, characters);
+  }
+
+  return 0;
+}
+
+static int
+getCommandOperand (DataFile *file, BoundCommand *cmd, KeyTableData *ktd) {
+  BoundCommand *cmds[] = {cmd, NULL};
+
+  return getCommandsOperand(file, cmds, ktd);
+}
+
+static int
+compareKeyCombinations (const KeyCombination *combination1, const KeyCombination *combination2) {
+  if (combination1->flags & KCF_IMMEDIATE_KEY) {
+    if (combination2->flags & KCF_IMMEDIATE_KEY) {
+      int relation = compareKeyValues(&combination1->immediateKey, &combination2->immediateKey);
+      if (relation) return relation;
+    } else {
+      return -1;
+    }
+  } else if (combination2->flags & KCF_IMMEDIATE_KEY) {
+    return 1;
+  }
+
+  return compareKeyArrays(combination1->modifierCount, combination1->modifierKeys,
+                          combination2->modifierCount, combination2->modifierKeys);
+}
+
+int
+compareKeyBindings (const KeyBinding *binding1, const KeyBinding *binding2) {
+  return compareKeyCombinations(&binding1->keyCombination, &binding2->keyCombination);
+}
+
+static int
+findKeyBinding (
+  const KeyBinding *bindings, unsigned int count,
+  const KeyBinding *target, unsigned int *position
+) {
+  int first = 0;
+  int last = count - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    const KeyBinding *binding = &bindings[current];
+    int relation = compareKeyBindings(target, binding);
+
+    if (relation < 0) {
+      last = current - 1;
+    } else if (relation > 0) {
+      first = current + 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+static int
+addKeyBinding (KeyContext *ctx, const KeyBinding *binding, int incomplete) {
+  unsigned int position;
+  int found = findKeyBinding(ctx->keyBindings.table, ctx->keyBindings.count, binding, &position);
+
+  if (!found) {
+    if (ctx->keyBindings.count == ctx->keyBindings.size) {
+      unsigned int newSize = ctx->keyBindings.size? ctx->keyBindings.size<<1: 0X10;
+      KeyBinding *newTable = realloc(ctx->keyBindings.table, ARRAY_SIZE(newTable, newSize));
+
+      if (!newTable) {
+        logMallocError();
+        return 0;
+      }
+
+      ctx->keyBindings.table = newTable;
+      ctx->keyBindings.size = newSize;
+    }
+
+    memmove(&ctx->keyBindings.table[position+1],
+            &ctx->keyBindings.table[position],
+            (ctx->keyBindings.count++ - position) * sizeof(*binding));
+  } else if (incomplete) {
+    return 1;
+  }
+
+  {
+    KeyBinding *kb = &ctx->keyBindings.table[position];
+    *kb = *binding;
+    if (found) kb->flags |= KBF_DUPLICATE;
+  }
+
+  return 1;
+}
+
+static void
+initializeKeyBinding (KeyBinding *binding, KeyTableData *ktd) {
+  memset(binding, 0, sizeof(*binding));
+  binding->primaryCommand = ktd->nullBoundCommand;
+  binding->secondaryCommand = ktd->nullBoundCommand;
+  if (hideBindings(ktd)) binding->flags |= KBF_HIDDEN;
+}
+
+int
+compareHotkeyEntries (const HotkeyEntry *hotkey1, const HotkeyEntry *hotkey2) {
+  return compareKeyValues(&hotkey1->keyValue, &hotkey2->keyValue);
+}
+
+static int
+findHotkeyEntry (
+  const HotkeyEntry *hotkeyEntries, unsigned int count,
+  const HotkeyEntry *target, unsigned int *position
+) {
+  int first = 0;
+  int last = count - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    const HotkeyEntry *hotkey = &hotkeyEntries[current];
+    int relation = compareHotkeyEntries(target, hotkey);
+
+    if (relation < 0) {
+      last = current - 1;
+    } else if (relation > 0) {
+      first = current + 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+static int
+addHotkey (KeyContext *ctx, const HotkeyEntry *hotkey) {
+  unsigned int position;
+  int found = findHotkeyEntry(ctx->hotkeys.table, ctx->hotkeys.count, hotkey, &position);
+
+  if (!found) {
+    if (ctx->hotkeys.count == ctx->hotkeys.size) {
+      unsigned int newSize = ctx->hotkeys.size? ctx->hotkeys.size<<1: 0X8;
+      HotkeyEntry *newTable = realloc(ctx->hotkeys.table, ARRAY_SIZE(newTable, newSize));
+
+      if (!newTable) {
+        logMallocError();
+        return 0;
+      }
+
+      ctx->hotkeys.table = newTable;
+      ctx->hotkeys.size = newSize;
+    }
+
+    memmove(&ctx->hotkeys.table[position+1],
+            &ctx->hotkeys.table[position],
+            (ctx->hotkeys.count++ - position) * sizeof(*hotkey));
+  }
+
+  {
+    HotkeyEntry *hk = &ctx->hotkeys.table[position];
+    *hk = *hotkey;
+    if (found) hk->flags |= HKF_DUPLICATE;
+  }
+
+  return 1;
+}
+
+int
+compareMappedKeyEntries (const MappedKeyEntry *map1, const MappedKeyEntry *map2) {
+  return compareKeyValues(&map1->keyValue, &map2->keyValue);
+}
+
+static int
+findMappedKeyEntry (
+  const MappedKeyEntry *mappedKeyEntries, unsigned int count,
+  const MappedKeyEntry *target, unsigned int *position
+) {
+  int first = 0;
+  int last = count - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    const MappedKeyEntry *map = &mappedKeyEntries[current];
+    int relation = compareMappedKeyEntries(target, map);
+
+    if (relation < 0) {
+      last = current - 1;
+    } else if (relation > 0) {
+      first = current + 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+static int
+addMappedKey (KeyContext *ctx, const MappedKeyEntry *map) {
+  unsigned int position;
+  int found = findMappedKeyEntry(ctx->mappedKeys.table, ctx->mappedKeys.count, map, &position);
+
+  if (!found) {
+    if (ctx->mappedKeys.count == ctx->mappedKeys.size) {
+      unsigned int newSize = ctx->mappedKeys.size? ctx->mappedKeys.size<<1: 0X8;
+      MappedKeyEntry *newTable = realloc(ctx->mappedKeys.table, ARRAY_SIZE(newTable, newSize));
+
+      if (!newTable) {
+        logMallocError();
+        return 0;
+      }
+
+      ctx->mappedKeys.table = newTable;
+      ctx->mappedKeys.size = newSize;
+    }
+
+    memmove(&ctx->mappedKeys.table[position+1],
+            &ctx->mappedKeys.table[position],
+            (ctx->mappedKeys.count++ - position) * sizeof(*map));
+  }
+
+  {
+    MappedKeyEntry *mk = &ctx->mappedKeys.table[position];
+    *mk = *map;
+    if (found) mk->flags |= MKF_DUPLICATE;
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processBindOperands) {
+  KeyTableData *ktd = data;
+
+  KeyBinding binding;
+  initializeKeyBinding(&binding, ktd);
+
+  if (getKeysOperand(file, &binding.keyCombination, ktd)) {
+    BoundCommand *cmds[] = {
+      &binding.primaryCommand,
+      &binding.secondaryCommand,
+      NULL
+    };
+
+    if (getCommandsOperand(file, cmds, ktd)) {
+      KeyContext *ctx = getCurrentKeyContext(ktd);
+
+      if (ctx) {
+        if (addKeyBinding(ctx, &binding, 0)) {
+          return 1;
+        }
+      }
+
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processContextOperands) {
+  KeyTableData *ktd = data;
+  DataString name;
+
+  if (getDataString(file, &name, 1, "context name")) {
+    if (findKeyContext(&ktd->context, name.characters, name.length, ktd)) {
+      KeyContext *ctx = getCurrentKeyContext(ktd);
+
+      if (ctx) {
+        DataOperand title;
+
+        ctx->isDefined = 1;
+
+        if (getTextOperand(file, &title, NULL)) {
+          if (ctx->title) {
+            if ((title.length != wcslen(ctx->title)) ||
+                (wmemcmp(title.characters, ctx->title, title.length) != 0)) {
+              reportDataError(file, "context title redefined");
+            }
+          } else if (!setKeyContextTitle(ctx, title.characters, title.length)) {
+            return 0;
+          }
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processHideOperands) {
+  KeyTableData *ktd = data;
+  DataString state;
+
+  if (getDataString(file, &state, 1, "hide state")) {
+    if (isKeyword(WS_C("on"), state.characters, state.length)) {
+      ktd->hideRequested = 1;
+    } else if (isKeyword(WS_C("off"), state.characters, state.length)) {
+      ktd->hideRequested = 0;
+    } else {
+      reportDataError(file, "unknown hide state: %.*" PRIws, state.length, state.characters);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processHotkeyOperands) {
+  KeyTableData *ktd = data;
+  HotkeyEntry hotkey;
+
+  memset(&hotkey, 0, sizeof(hotkey));
+  if (hideBindings(ktd)) hotkey.flags |= HKF_HIDDEN;
+
+  if (getKeyOperand(file, &hotkey.keyValue, ktd)) {
+    if (getCommandOperand(file, &hotkey.pressCommand, ktd)) {
+      if (getCommandOperand(file, &hotkey.releaseCommand, ktd)) {
+        KeyContext *ctx = getCurrentKeyContext(ktd);
+
+        if (ctx) {
+          if (addHotkey(ctx, &hotkey)) {
+            return 1;
+          }
+        }
+
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_CONDITION_TESTER(testKeyDefined) {
+  return !!findKeyName(identifier->characters, identifier->length, data);
+}
+
+static int
+processKeyTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testKeyDefined, not, "key name", data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfKeyOperands) {
+  return processKeyTestOperands(file, 0, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfNotKeyOperands) {
+  return processKeyTestOperands(file, 1, data);
+}
+
+static DATA_CONDITION_TESTER(testPlatformName) {
+  static const wchar_t *const platforms[] = {
+#ifdef __ANDROID__
+    WS_C("android"),
+#endif /* __ANDROID__ */
+
+#ifdef __APPLE__
+    WS_C("apple"),
+#endif /* __APPLE__ */
+
+#ifdef __CYGWIN__
+    WS_C("cygwin"),
+#endif /* __CYGWIN__ */
+
+#ifdef __MSDOS__
+    WS_C("dos"),
+#endif /* __MSDOS__ */
+
+#ifdef GRUB_RUNTIME
+    WS_C("grub"),
+#endif /* GRUB_RUNTIME */
+
+#ifdef __linux__
+    WS_C("linux"),
+#endif /* __linux__ */
+
+#ifdef __MINGW32__
+    WS_C("mingw32"),
+#endif /* __MINGW32__ */
+
+#ifdef __MINGW64__
+    WS_C("mingw64"),
+#endif /* __MINGW64__ */
+
+#ifdef __OpenBSD__
+    WS_C("openbsd"),
+#endif /* __OpenBSD__ */
+
+#ifdef __sun__
+    WS_C("sun"),
+#endif /* __sun__ */
+
+#ifdef WINDOWS
+    WS_C("windows"),
+#endif /* WINDOWS */
+
+    NULL
+  };
+
+  const wchar_t *const *platform = platforms;
+
+  while (*platform) {
+    if (identifier->length == wcslen(*platform)) {
+      if (wcsncmp(*platform, identifier->characters, identifier->length) == 0) {
+        return 1;
+      }
+    }
+
+    platform += 1;
+  }
+
+  return 0;
+}
+
+static int
+processPlatformTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testPlatformName, not, "platform name", data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfPlatformOperands) {
+  return processPlatformTestOperands(file, 0, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfNotPlatformOperands) {
+  return processPlatformTestOperands(file, 1, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIgnoreOperands) {
+  KeyTableData *ktd = data;
+  HotkeyEntry hotkey;
+
+  memset(&hotkey, 0, sizeof(hotkey));
+  if (hideBindings(ktd)) hotkey.flags |= HKF_HIDDEN;
+  hotkey.pressCommand = hotkey.releaseCommand = ktd->nullBoundCommand;
+
+  if (getKeyOperand(file, &hotkey.keyValue, ktd)) {
+    KeyContext *ctx = getCurrentKeyContext(ktd);
+
+    if (ctx) {
+      if (addHotkey(ctx, &hotkey)) {
+        return 1;
+      }
+    }
+
+    return 0;
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processIncludeWrapper) {
+  KeyTableData *ktd = data;
+  int result;
+
+  unsigned char context = ktd->context;
+  unsigned int hideRequested = ktd->hideRequested;
+  unsigned int hideInherited = ktd->hideInherited;
+
+  if (ktd->hideRequested) ktd->hideInherited = 1;
+  result = processIncludeOperands(file, data);
+
+  ktd->context = context;
+  ktd->hideRequested = hideRequested;
+  ktd->hideInherited = hideInherited;
+  return result;
+}
+
+static DATA_OPERANDS_PROCESSOR(processIsolatedOperands) {
+  KeyTableData *ktd = data;
+  KeyContext *ctx = getCurrentKeyContext(ktd);
+
+  if (ctx) {
+    if (!ctx->isIsolated) {
+      ctx->isIsolated = 1;
+    } else {
+      reportDataError(file, "context already solated: %"PRIws, ctx->name);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processMacroOperands) {
+  KeyTableData *ktd = data;
+  KeyTable *table = ktd->table;
+
+  KeyBinding binding;
+  initializeKeyBinding(&binding, ktd);
+
+  {
+    BoundCommand *cmd = &binding.primaryCommand;
+    cmd->value = BRL_CMD_BLK(MACRO);
+    cmd->entry = findCommandEntry(cmd->value);
+    cmd->value += table->commandMacros.count;
+  }
+
+  if (getKeysOperand(file, &binding.keyCombination, ktd)) {
+    size_t limit = 100;
+    BoundCommand commands[limit];
+    size_t count = 0;
+
+    while (findDataOperand(file, NULL)) {
+      if (count == limit) {
+        reportDataError(file, "command macro too large");
+        return 1;
+      }
+
+      BoundCommand *command = &commands[count];
+      if (!getCommandOperand(file, command, ktd)) return 1;
+      count += 1;
+    }
+
+    if (count == 0) {
+      reportDataError(file, "empty command macro");
+    } else {
+      if (table->commandMacros.count == table->commandMacros.size) {
+        size_t newSize = table->commandMacros.size? table->commandMacros.size<<1: 4;
+        CommandMacro *newTable = realloc(table->commandMacros.table, ARRAY_SIZE(table->commandMacros.table, newSize));
+
+        if (!newTable) {
+          logMallocError();
+          return 0;
+        }
+
+        table->commandMacros.table = newTable;
+        table->commandMacros.size = newSize;
+      }
+
+      CommandMacro *macro = &table->commandMacros.table[table->commandMacros.count];
+      memset(macro, 0, sizeof(*macro));
+      size_t size = ARRAY_SIZE(macro->commands, (macro->count = count));
+
+      if ((macro->commands = malloc(size))) {
+        memcpy(macro->commands, commands, size);
+        KeyContext *ctx = getCurrentKeyContext(ktd);
+
+        if (ctx) {
+          if (addKeyBinding(ctx, &binding, 0)) {
+            table->commandMacros.count += 1;
+            return 1;
+          }
+        }
+
+        free(macro->commands);
+      }
+
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processMapOperands) {
+  KeyTableData *ktd = data;
+  MappedKeyEntry map;
+
+  memset(&map, 0, sizeof(map));
+  if (hideBindings(ktd)) map.flags |= MKF_HIDDEN;
+
+  if (getKeyOperand(file, &map.keyValue, ktd)) {
+    if (map.keyValue.number != KTB_KEY_ANY) {
+      if (getKeyboardFunctionOperand(file, &map.keyboardFunction, ktd)) {
+        KeyContext *ctx = getCurrentKeyContext(ktd);
+
+        if (ctx) {
+          if (addMappedKey(ctx, &map)) {
+            return 1;
+          }
+        }
+
+        return 0;
+      }
+    } else {
+      reportDataError(file, "cannot map a key group");
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processNoteOperands) {
+  KeyTableData *ktd = data;
+  DataOperand operand;
+
+  if (getTextOperand(file, &operand, "note text")) {
+    if (!hideBindings(ktd)) {
+      DataString string;
+
+      if (parseDataString(file, &string, operand.characters, operand.length, 0)) {
+        if (ktd->table->notes.count == ktd->table->notes.size) {
+          unsigned int newSize = (ktd->table->notes.size == 0)? 8: (ktd->table->notes.size << 1);
+          wchar_t **newTable = realloc(ktd->table->notes.table, ARRAY_SIZE(newTable, newSize));
+
+          if (!newTable) {
+            logMallocError();
+            return 0;
+          }
+
+          ktd->table->notes.table = newTable;
+          ktd->table->notes.size = newSize;
+        }
+
+        {
+          wchar_t *noteString = malloc(ARRAY_SIZE(noteString, string.length+1));
+
+          if (!noteString) {
+            logMallocError();
+            return 0;
+          }
+
+          wmemcpy(noteString, string.characters, string.length);
+          noteString[string.length] = 0;
+
+          ktd->table->notes.table[ktd->table->notes.count++] = noteString;
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processRunOperands) {
+  KeyTableData *ktd = data;
+  KeyTable *table = ktd->table;
+  int seriousFailure = 0;
+
+  KeyBinding binding;
+  initializeKeyBinding(&binding, ktd);
+
+  {
+    BoundCommand *cmd = &binding.primaryCommand;
+    cmd->value = BRL_CMD_BLK(HOSTCMD);
+    cmd->entry = findCommandEntry(cmd->value);
+    cmd->value += table->hostCommands.count;
+  }
+
+  if (getKeysOperand(file, &binding.keyCombination, ktd)) {
+    int allArgumentsParsed = 1;
+
+    size_t limit = 100;
+    char *arguments[limit];
+    size_t count = 0;
+
+    while (findDataOperand(file, NULL)) {
+      if (count == limit) {
+        reportDataError(file, "too many host command arguments");
+        allArgumentsParsed = 0;
+        break;
+      }
+
+      DataString argument;
+      if (!getDataString(file, &argument, 0, "host command argument")) {
+        allArgumentsParsed = 0;
+        break;
+      }
+
+      if (!(arguments[count] = getUtf8FromWchars(argument.characters, argument.length, NULL))) {
+        seriousFailure = 1;
+        break;
+      }
+
+      count += 1;
+    }
+
+    if (allArgumentsParsed && !seriousFailure) {
+      if (count == 0) {
+        reportDataError(file, "host command name/path not specified");
+      } else {
+        seriousFailure = 1;
+
+        if (table->hostCommands.count == table->hostCommands.size) {
+          size_t newSize = table->hostCommands.size? table->hostCommands.size<<1: 4;
+          HostCommand *newTable = realloc(table->hostCommands.table, ARRAY_SIZE(table->hostCommands.table, newSize));
+
+          if (!newTable) {
+            logMallocError();
+            goto SERIOUS_FAILURE;
+          }
+
+          table->hostCommands.table = newTable;
+          table->hostCommands.size = newSize;
+        }
+
+        HostCommand *hc = &table->hostCommands.table[table->hostCommands.count];
+        memset(hc, 0, sizeof(*hc));
+        size_t size = ARRAY_SIZE(hc->arguments, (hc->count = count));
+
+        if ((hc->arguments = malloc(size + sizeof(*hc->arguments)))) {
+          memcpy(hc->arguments, arguments, size);
+          hc->arguments[hc->count] = NULL;
+          KeyContext *ctx = getCurrentKeyContext(ktd);
+
+          if (ctx) {
+            if (addKeyBinding(ctx, &binding, 0)) {
+              table->hostCommands.count += 1;
+              return 1;
+            }
+          }
+
+          free(hc->arguments);
+        }
+      }
+    }
+
+  SERIOUS_FAILURE:
+    while (count > 0) free(arguments[--count]);
+  }
+
+  return !seriousFailure;
+}
+
+static DATA_OPERANDS_PROCESSOR(processSuperimposeOperands) {
+  KeyTableData *ktd = data;
+
+  {
+    const KeyboardFunction *kbf;
+
+    if (getKeyboardFunctionOperand(file, &kbf, ktd)) {
+      KeyContext *ctx = getCurrentKeyContext(ktd);
+
+      if (ctx) {
+        ctx->mappedKeys.superimpose |= kbf->bit;
+        return 1;
+      }
+
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processTitleOperands) {
+  KeyTableData *ktd = data;
+  DataOperand title;
+
+  if (getTextOperand(file, &title, "title text")) {
+    if (ktd->table->title) {
+      reportDataError(file, "table title specified more than once");
+    } else if (!(ktd->table->title = malloc(ARRAY_SIZE(ktd->table->title, title.length+1)))) {
+      logMallocError();
+      return 0;
+    } else {
+      wmemcpy(ktd->table->title, title.characters, title.length);
+      ktd->table->title[title.length] = 0;
+      return 1;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processKeyTableOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_VARIABLE_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    {.name=WS_C("bind"), .processor=processBindOperands},
+    {.name=WS_C("context"), .processor=processContextOperands},
+    {.name=WS_C("hide"), .processor=processHideOperands},
+    {.name=WS_C("hotkey"), .processor=processHotkeyOperands},
+    {.name=WS_C("ifkey"), .processor=processIfKeyOperands, .unconditional=1},
+    {.name=WS_C("ifnotkey"), .processor=processIfNotKeyOperands, .unconditional=1},
+    {.name=WS_C("ifplatform"), .processor=processIfPlatformOperands, .unconditional=1},
+    {.name=WS_C("ifnotplatform"), .processor=processIfNotPlatformOperands, .unconditional=1},
+    {.name=WS_C("ignore"), .processor=processIgnoreOperands},
+    {.name=WS_C("include"), .processor=processIncludeWrapper},
+    {.name=WS_C("isolated"), .processor=processIsolatedOperands},
+    {.name=WS_C("macro"), .processor=processMacroOperands},
+    {.name=WS_C("map"), .processor=processMapOperands},
+    {.name=WS_C("note"), .processor=processNoteOperands},
+    {.name=WS_C("run"), .processor=processRunOperands},
+    {.name=WS_C("superimpose"), .processor=processSuperimposeOperands},
+    {.name=WS_C("title"), .processor=processTitleOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "key table directive", data);
+}
+
+void
+resetLongPressData (KeyTable *table) {
+  if (table->longPress.alarm) {
+    asyncCancelRequest(table->longPress.alarm);
+    table->longPress.alarm = NULL;
+  }
+
+  table->longPress.command = BRL_CMD_NOOP;
+  table->longPress.repeat = 0;
+
+  table->longPress.keyAction = NULL;
+  table->longPress.keyContext = KTB_CTX_DEFAULT;
+  table->longPress.keyValue.group = 0;
+  table->longPress.keyValue.number = KTB_KEY_ANY;
+}
+
+void
+resetKeyTable (KeyTable *table) {
+  resetLongPressData(table);
+  table->release.command = BRL_CMD_NOOP;
+  table->pressedKeys.count = 0;
+  table->context.current = table->context.next = table->context.persistent = KTB_CTX_DEFAULT;
+}
+
+static int
+addIncompleteBinding (KeyContext *ctx, const KeyValue *keys, unsigned char count) {
+  static const BoundCommand command = {
+    .entry = NULL,
+    .value = EOF
+  };
+
+  KeyBinding binding = {
+    .flags = KBF_HIDDEN,
+
+    .primaryCommand = command,
+    .secondaryCommand = command,
+
+    .keyCombination = {
+      .modifierCount = count
+    }
+  };
+
+  copyKeyValues(binding.keyCombination.modifierKeys, keys, count);
+  return addKeyBinding(ctx, &binding, 1);
+}
+
+static int
+addIncompleteSubbindings (KeyContext *ctx, const KeyValue *keys, unsigned char count) {
+  if (count > 1) {
+    KeyValue values[--count];
+    unsigned int index = 0;
+
+    copyKeyValues(values, &keys[1], count);
+
+    while (1) {
+      if (!addIncompleteBinding(ctx, values, count)) return 0;
+      if (!addIncompleteSubbindings(ctx, values, count)) return 0;
+      if (index == count) break;
+      values[index] = keys[index];
+      index += 1;
+    }
+  }
+
+  return 1;
+}
+
+static int
+addIncompleteBindings (KeyContext *ctx) {
+  size_t count = ctx->keyBindings.count;
+
+  if (count > 0) {
+    KeyBinding bindings[count];
+
+    memcpy(
+      bindings, ctx->keyBindings.table,
+      (count * sizeof(*ctx->keyBindings.table))
+    );
+
+    const KeyBinding *binding = bindings;
+    const KeyBinding *end = binding + count;
+
+    while (binding < end) {
+      const KeyCombination *combination = &binding->keyCombination;
+      if (!addIncompleteBinding(ctx, combination->modifierKeys, combination->modifierCount)) return 0;
+      if (!addIncompleteSubbindings(ctx, combination->modifierKeys, combination->modifierCount)) return 0;
+      binding += 1;
+    }
+  }
+
+  return 1;
+}
+
+static int
+prepareKeyBindings (KeyContext *ctx) {
+  if (!addIncompleteBindings(ctx)) return 0;
+
+  if (ctx->keyBindings.count < ctx->keyBindings.size) {
+    if (ctx->keyBindings.count) {
+      KeyBinding *newTable = realloc(ctx->keyBindings.table, ARRAY_SIZE(newTable, ctx->keyBindings.count));
+
+      if (!newTable) {
+        logMallocError();
+        return 0;
+      }
+
+      ctx->keyBindings.table = newTable;
+    } else {
+      free(ctx->keyBindings.table);
+      ctx->keyBindings.table = NULL;
+    }
+
+    ctx->keyBindings.size = ctx->keyBindings.count;
+  }
+
+  return 1;
+}
+
+int
+finishKeyTable (KeyTableData *ktd) {
+  for (unsigned int context=0; context<ktd->table->keyContexts.count; context+=1) {
+    KeyContext *ctx = &ktd->table->keyContexts.table[context];
+    if (!prepareKeyBindings(ctx)) return 0;
+  }
+
+  qsort(ktd->table->keyNames.table, ktd->table->keyNames.count, sizeof(*ktd->table->keyNames.table), sortKeyValues);
+  resetKeyTable(ktd->table);
+  return 1;
+}
+
+static int
+defineInitialKeyContexts (KeyTableData *ktd) {
+  typedef struct {
+    unsigned char context;
+    const wchar_t *name;
+    const wchar_t *title;
+  } PropertiesEntry;
+
+  static const PropertiesEntry propertiesTable[] = {
+    { .context = KTB_CTX_DEFAULT,
+      .title = WS_C("Default Bindings"),
+      .name = WS_C("default")
+    },
+
+    { .context = KTB_CTX_MENU,
+      .title = WS_C("Menu Bindings"),
+      .name = WS_C("menu")
+    },
+
+    { .name = NULL }
+  };
+  const PropertiesEntry *properties = propertiesTable;
+
+  while (properties->name) {
+    KeyContext *ctx = getKeyContext(ktd, properties->context);
+
+    if (!ctx) return 0;
+    if (properties->context != KTB_CTX_DEFAULT) ctx->isSpecial = 1;
+
+    ctx->isDefined = 1;
+    ctx->isReferenced = 1;
+
+    if (properties->name) {
+      if (!setKeyContextName(ctx, properties->name, wcslen(properties->name))) {
+        return 0;
+      }
+    }
+
+    if (properties->title) {
+      if (!setKeyContextTitle(ctx, properties->title, wcslen(properties->title))) {
+        return 0;
+      }
+    }
+
+    properties += 1;
+  }
+
+  return 1;
+}
+
+KeyTable *
+compileKeyTable (const char *name, KEY_NAME_TABLES_REFERENCE keys) {
+  KeyTable *table = NULL;
+
+  if (setTableDataVariables(KEY_TABLE_EXTENSION, KEY_SUBTABLE_EXTENSION)) {
+    KeyTableData ktd;
+
+    memset(&ktd, 0, sizeof(ktd));
+    ktd.file = name;
+    ktd.context = KTB_CTX_DEFAULT;
+
+    {
+      BoundCommand *cmd = &ktd.nullBoundCommand;
+
+      cmd->entry = findCommandEntry(cmd->value = BRL_CMD_NOOP);
+    }
+
+    if ((ktd.table = malloc(sizeof(*ktd.table)))) {
+      ktd.table->title = NULL;
+
+      ktd.table->notes.table = NULL;
+      ktd.table->notes.size = 0;
+      ktd.table->notes.count = 0;
+
+      ktd.table->keyNames.table = NULL;
+      ktd.table->keyNames.count = 0;
+
+      ktd.table->keyContexts.table = NULL;
+      ktd.table->keyContexts.count = 0;
+
+      ktd.table->pressedKeys.table = NULL;
+      ktd.table->pressedKeys.size = 0;
+      ktd.table->pressedKeys.count = 0;
+
+      ktd.table->longPress.alarm = NULL;
+
+      ktd.table->autorelease.alarm = NULL;
+      ktd.table->autorelease.time = 0;
+
+      ktd.table->commandMacros.table = NULL;
+      ktd.table->commandMacros.size = 0;
+      ktd.table->commandMacros.count = 0;
+
+      ktd.table->hostCommands.table = NULL;
+      ktd.table->hostCommands.size = 0;
+      ktd.table->hostCommands.count = 0;
+
+      ktd.table->options.logLabel = NULL;
+      ktd.table->options.logKeyEventsFlag = NULL;
+      ktd.table->options.keyboardEnabledFlag = NULL;
+
+      if (defineInitialKeyContexts(&ktd)) {
+        if (allocateKeyNameTable(&ktd, keys)) {
+          if (allocateCommandTable(&ktd)) {
+            const DataFileParameters parameters = {
+              .processOperands = processKeyTableOperands,
+              .data = &ktd
+            };
+
+            if (processDataFile(name, &parameters)) {
+              if (finishKeyTable(&ktd)) {
+                table = ktd.table;
+                ktd.table = NULL;
+              }
+            }
+
+            if (ktd.commandTable) free(ktd.commandTable);
+          }
+        }
+      }
+
+      if (ktd.table) destroyKeyTable(ktd.table);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return table;
+}
+
+void
+destroyKeyTable (KeyTable *table) {
+  resetLongPressData(table);
+  setKeyAutoreleaseTime(table, 0);
+
+  while (table->notes.count) free(table->notes.table[--table->notes.count]);
+
+  while (table->keyContexts.count) {
+    KeyContext *ctx = &table->keyContexts.table[--table->keyContexts.count];
+
+    if (ctx->name) free(ctx->name);
+    if (ctx->title) free(ctx->title);
+
+    if (ctx->keyBindings.table) free(ctx->keyBindings.table);
+    if (ctx->hotkeys.table) free(ctx->hotkeys.table);
+    if (ctx->mappedKeys.table) free(ctx->mappedKeys.table);
+  }
+
+  if (table->commandMacros.table) {
+    while (table->commandMacros.count > 0) {
+      CommandMacro *macro = &table->commandMacros.table[--table->commandMacros.count];
+      free(macro->commands);
+    }
+
+    free(table->commandMacros.table);
+  }
+
+  if (table->hostCommands.table) {
+    while (table->hostCommands.count > 0) {
+      HostCommand *hcmd = &table->hostCommands.table[--table->hostCommands.count];
+      free(hcmd->arguments);
+    }
+
+    free(table->hostCommands.table);
+  }
+
+  if (table->keyContexts.table) free(table->keyContexts.table);
+  if (table->keyNames.table) free(table->keyNames.table);
+  if (table->notes.table) free(table->notes.table);
+  if (table->title) free(table->title);
+  if (table->pressedKeys.table) free(table->pressedKeys.table);
+  free(table);
+}
+
+char *
+ensureKeyTableExtension (const char *path) {
+  return ensureFileExtension(path, KEY_TABLE_EXTENSION);
+}
+
+char *
+makeKeyTablePath (const char *directory, const char *name) {
+  return makeFilePath(directory, name, KEY_TABLE_EXTENSION);
+}
+
+char *
+makeKeyboardTablePath (const char *directory, const char *name) {
+  char *subdirectory = makePath(directory, KEYBOARD_TABLES_SUBDIRECTORY);
+
+  if (subdirectory) {
+    char *file = makeKeyTablePath(subdirectory, name);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
+
+char *
+makeInputTablePath (const char *directory, const char *driver, const char *name) {
+  const char *components[] = {
+    directory,
+    INPUT_TABLES_SUBDIRECTORY,
+    driver
+  };
+  char *subdirectory = joinPath(components, ARRAY_COUNT(components));
+
+  if (subdirectory) {
+    char *file = makeKeyTablePath(subdirectory, name);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
diff --git a/Programs/ktb_inspect.h b/Programs/ktb_inspect.h
new file mode 100644
index 0000000..837d402
--- /dev/null
+++ b/Programs/ktb_inspect.h
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_INSPECT
+#define BRLTTY_INCLUDED_KTB_INSPECT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+static inline const KeyContext *
+getKeyContext (KeyTable *table, unsigned char context) {
+  if (context < table->keyContexts.count) return &table->keyContexts.table[context];
+  return NULL;
+}
+
+static inline const KeyContext *getCurrentKeyContext(KeyTable *table) {
+  return getKeyContext(table, table->context.current);
+}
+
+static inline int
+isTemporaryKeyContext (const KeyTable *table, const KeyContext *ctx) {
+  return ((ctx - table->keyContexts.table) > KTB_CTX_DEFAULT) && !ctx->title;
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_INSPECT */
diff --git a/Programs/ktb_internal.h b/Programs/ktb_internal.h
new file mode 100644
index 0000000..8e5b3b6
--- /dev/null
+++ b/Programs/ktb_internal.h
@@ -0,0 +1,230 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_INTERNAL
+#define BRLTTY_INCLUDED_KTB_INTERNAL
+
+#include "strfmth.h"
+#include "cmd_types.h"
+#include "async_handle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MAX_MODIFIERS_PER_COMBINATION 10
+
+typedef struct {
+  const char *name;
+  int bit;
+} KeyboardFunction;
+
+extern const KeyboardFunction keyboardFunctionTable[];
+extern unsigned char keyboardFunctionCount;
+
+typedef enum {
+  KCF_IMMEDIATE_KEY = 0X01
+} KeyCombinationFlag;
+
+typedef struct {
+  unsigned char flags;
+  unsigned char anyKeyCount;
+  unsigned char modifierCount;
+  unsigned char modifierPositions[MAX_MODIFIERS_PER_COMBINATION];
+  KeyValue modifierKeys[MAX_MODIFIERS_PER_COMBINATION];
+  KeyValue immediateKey;
+} KeyCombination;
+
+typedef struct {
+  const CommandEntry *entry;
+  int value;
+} BoundCommand;
+
+typedef enum {
+  KBF_HIDDEN    = 0X01,
+  KBF_DUPLICATE = 0X80
+} KeyBindingFlag;
+
+typedef struct {
+  BoundCommand primaryCommand;
+  BoundCommand secondaryCommand;
+  KeyCombination keyCombination;
+  unsigned char flags;
+} KeyBinding;
+
+typedef enum {
+  HKF_HIDDEN    = 0X01,
+  HKF_DUPLICATE = 0X80
+} HotkeyFlag;
+
+typedef struct {
+  KeyValue keyValue;
+  BoundCommand pressCommand;
+  BoundCommand releaseCommand;
+  unsigned char flags;
+} HotkeyEntry;
+
+typedef enum {
+  MKF_HIDDEN    = 0X01,
+  MKF_DUPLICATE = 0X80
+} MappedKeyFlag;
+
+typedef struct {
+  KeyValue keyValue;
+  const KeyboardFunction *keyboardFunction;
+  unsigned char flags;
+} MappedKeyEntry;
+
+typedef struct {
+  wchar_t *name;
+  wchar_t *title;
+
+  unsigned char isSpecial : 1;
+  unsigned char isDefined : 1;
+  unsigned char isReferenced : 1;
+  unsigned char isIsolated : 1;
+
+  struct {
+    KeyBinding *table;
+    unsigned int size;
+    unsigned int count;
+  } keyBindings;
+
+  struct {
+    HotkeyEntry *table;
+    unsigned int size;
+    unsigned int count;
+  } hotkeys;
+
+  struct {
+    MappedKeyEntry *table;
+    unsigned int size;
+    unsigned int count;
+    int superimpose;
+  } mappedKeys;
+} KeyContext;
+
+typedef struct {
+  BoundCommand *commands;
+  unsigned int count;
+} CommandMacro;
+
+typedef struct {
+  char **arguments;
+  unsigned int count;
+} HostCommand;
+
+struct KeyTableStruct {
+  wchar_t *title;
+
+  struct {
+    wchar_t **table;
+    unsigned int size;
+    unsigned int count;
+  } notes;
+
+  struct {
+    const KeyNameEntry **table;
+    unsigned int count;
+  } keyNames;
+
+  struct {
+    KeyContext *table;
+    unsigned int count;
+  } keyContexts;
+
+  struct {
+    unsigned char persistent;
+    unsigned char next;
+    unsigned char current;
+  } context;
+
+  struct {
+    KeyValue *table;
+    unsigned int size;
+    unsigned int count;
+  } pressedKeys;
+
+  struct {
+    int command;
+  } release;
+
+  struct {
+    AsyncHandle alarm;
+    int command;
+    unsigned repeat:1;
+
+    const char *keyAction;
+    unsigned char keyContext;
+    KeyValue keyValue;
+  } longPress;
+
+  struct {
+    AsyncHandle alarm;
+    int time;
+  } autorelease;
+
+  struct {
+    CommandMacro *table;
+    unsigned int size;
+    unsigned int count;
+  } commandMacros;
+
+  struct {
+    HostCommand *table;
+    unsigned int size;
+    unsigned int count;
+  } hostCommands;
+
+  struct {
+    const char *logLabel;
+    const unsigned char *logKeyEventsFlag;
+    const unsigned char *keyboardEnabledFlag;
+  } options;
+};
+
+extern void copyKeyValues (KeyValue *target, const KeyValue *source, unsigned int count);
+extern int compareKeyValues (const KeyValue *value1, const KeyValue *value2);
+
+extern int findKeyValue (
+  const KeyValue *values, unsigned int count,
+  const KeyValue *target, unsigned int *position
+);
+
+extern int insertKeyValue (
+  KeyValue **values, unsigned int *count, unsigned int *size,
+  const KeyValue *value, unsigned int position
+);
+
+extern void removeKeyValue (KeyValue *values, unsigned int *count, unsigned int position);
+extern int deleteKeyValue (KeyValue *values, unsigned int *count, const KeyValue *value);
+
+extern int compareKeyBindings (const KeyBinding *binding1, const KeyBinding *binding2);
+extern int compareHotkeyEntries (const HotkeyEntry *hotkey1, const HotkeyEntry *hotkey2);
+extern int compareMappedKeyEntries (const MappedKeyEntry *map1, const MappedKeyEntry *map2);
+
+extern STR_DECLARE_FORMATTER(formatKeyName, KeyTable *table, const KeyValue *value);
+extern STR_DECLARE_FORMATTER(formatKeyCombination, KeyTable *table, const KeyCombination *combination);
+
+extern void resetLongPressData (KeyTable *table);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_INTERNAL */
diff --git a/Programs/ktb_keyboard.c b/Programs/ktb_keyboard.c
new file mode 100644
index 0000000..057b267
--- /dev/null
+++ b/Programs/ktb_keyboard.c
@@ -0,0 +1,613 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ktb_keyboard.h"
+
+BEGIN_KEY_NAME_TABLE(keyboard)
+  KBD_KEY_NAME(LETTER, A, "A"),
+  KBD_KEY_NAME(LETTER, A, "LETTER_A"),
+
+  KBD_KEY_NAME(LETTER, B, "B"),
+  KBD_KEY_NAME(LETTER, B, "LETTER_B"),
+
+  KBD_KEY_NAME(LETTER, C, "C"),
+  KBD_KEY_NAME(LETTER, C, "LETTER_C"),
+
+  KBD_KEY_NAME(LETTER, D, "D"),
+  KBD_KEY_NAME(LETTER, D, "LETTER_D"),
+
+  KBD_KEY_NAME(LETTER, E, "E"),
+  KBD_KEY_NAME(LETTER, E, "LETTER_E"),
+
+  KBD_KEY_NAME(LETTER, F, "F"),
+  KBD_KEY_NAME(LETTER, F, "LETTER_F"),
+
+  KBD_KEY_NAME(LETTER, G, "G"),
+  KBD_KEY_NAME(LETTER, G, "LETTER_G"),
+
+  KBD_KEY_NAME(LETTER, H, "H"),
+  KBD_KEY_NAME(LETTER, H, "LETTER_H"),
+
+  KBD_KEY_NAME(LETTER, I, "I"),
+  KBD_KEY_NAME(LETTER, I, "LETTER_I"),
+
+  KBD_KEY_NAME(LETTER, J, "J"),
+  KBD_KEY_NAME(LETTER, J, "LETTER_J"),
+
+  KBD_KEY_NAME(LETTER, K, "K"),
+  KBD_KEY_NAME(LETTER, K, "LETTER_K"),
+
+  KBD_KEY_NAME(LETTER, L, "L"),
+  KBD_KEY_NAME(LETTER, L, "LETTER_L"),
+
+  KBD_KEY_NAME(LETTER, M, "M"),
+  KBD_KEY_NAME(LETTER, M, "LETTER_M"),
+
+  KBD_KEY_NAME(LETTER, N, "N"),
+  KBD_KEY_NAME(LETTER, N, "LETTER_N"),
+
+  KBD_KEY_NAME(LETTER, O, "O"),
+  KBD_KEY_NAME(LETTER, O, "LETTER_O"),
+
+  KBD_KEY_NAME(LETTER, P, "P"),
+  KBD_KEY_NAME(LETTER, P, "LETTER_P"),
+
+  KBD_KEY_NAME(LETTER, Q, "Q"),
+  KBD_KEY_NAME(LETTER, Q, "LETTER_Q"),
+
+  KBD_KEY_NAME(LETTER, R, "R"),
+  KBD_KEY_NAME(LETTER, R, "LETTER_R"),
+
+  KBD_KEY_NAME(LETTER, S, "S"),
+  KBD_KEY_NAME(LETTER, S, "LETTER_S"),
+
+  KBD_KEY_NAME(LETTER, T, "T"),
+  KBD_KEY_NAME(LETTER, T, "LETTER_T"),
+
+  KBD_KEY_NAME(LETTER, U, "U"),
+  KBD_KEY_NAME(LETTER, U, "LETTER_U"),
+
+  KBD_KEY_NAME(LETTER, V, "V"),
+  KBD_KEY_NAME(LETTER, V, "LETTER_V"),
+
+  KBD_KEY_NAME(LETTER, W, "W"),
+  KBD_KEY_NAME(LETTER, W, "LETTER_W"),
+
+  KBD_KEY_NAME(LETTER, X, "X"),
+  KBD_KEY_NAME(LETTER, X, "LETTER_X"),
+
+  KBD_KEY_NAME(LETTER, Y, "Y"),
+  KBD_KEY_NAME(LETTER, Y, "LETTER_Y"),
+
+  KBD_KEY_NAME(LETTER, Z, "Z"),
+  KBD_KEY_NAME(LETTER, Z, "LETTER_Z"),
+
+  KBD_KEY_NAME(NUMBER, Zero, "Zero"),
+  KBD_KEY_NAME(NUMBER, Zero, "NUMBER_Zero"),
+
+  KBD_KEY_NAME(NUMBER, One, "One"),
+  KBD_KEY_NAME(NUMBER, One, "NUMBER_One"),
+
+  KBD_KEY_NAME(NUMBER, Two, "Two"),
+  KBD_KEY_NAME(NUMBER, Two, "NUMBER_Two"),
+
+  KBD_KEY_NAME(NUMBER, Three, "Three"),
+  KBD_KEY_NAME(NUMBER, Three, "NUMBER_Three"),
+
+  KBD_KEY_NAME(NUMBER, Four, "Four"),
+  KBD_KEY_NAME(NUMBER, Four, "NUMBER_Four"),
+
+  KBD_KEY_NAME(NUMBER, Five, "Five"),
+  KBD_KEY_NAME(NUMBER, Five, "NUMBER_Five"),
+
+  KBD_KEY_NAME(NUMBER, Six, "Six"),
+  KBD_KEY_NAME(NUMBER, Six, "NUMBER_Six"),
+
+  KBD_KEY_NAME(NUMBER, Seven, "Seven"),
+  KBD_KEY_NAME(NUMBER, Seven, "NUMBER_Seven"),
+
+  KBD_KEY_NAME(NUMBER, Eight, "Eight"),
+  KBD_KEY_NAME(NUMBER, Eight, "NUMBER_Eight"),
+
+  KBD_KEY_NAME(NUMBER, Nine, "Nine"),
+  KBD_KEY_NAME(NUMBER, Nine, "NUMBER_Nine"),
+
+  KBD_KEY_NAME(SYMBOL, Grave, "Grave"),
+  KBD_KEY_NAME(SYMBOL, Grave, "SYMBOL_Grave"),
+
+  KBD_KEY_NAME(SYMBOL, Minus, "Minus"),
+  KBD_KEY_NAME(SYMBOL, Minus, "SYMBOL_Minus"),
+
+  KBD_KEY_NAME(SYMBOL, Equals, "Equals"),
+  KBD_KEY_NAME(SYMBOL, Equals, "SYMBOL_Equals"),
+
+  KBD_KEY_NAME(SYMBOL, Backslash, "Backslash"),
+  KBD_KEY_NAME(SYMBOL, Backslash, "SYMBOL_Backslash"),
+
+  KBD_KEY_NAME(SYMBOL, LeftBracket, "LeftBracket"),
+  KBD_KEY_NAME(SYMBOL, LeftBracket, "SYMBOL_LeftBracket"),
+
+  KBD_KEY_NAME(SYMBOL, RightBracket, "RightBracket"),
+  KBD_KEY_NAME(SYMBOL, RightBracket, "SYMBOL_RightBracket"),
+
+  KBD_KEY_NAME(SYMBOL, Semicolon, "Semicolon"),
+  KBD_KEY_NAME(SYMBOL, Semicolon, "SYMBOL_Semicolon"),
+
+  KBD_KEY_NAME(SYMBOL, Apostrophe, "Apostrophe"),
+  KBD_KEY_NAME(SYMBOL, Apostrophe, "SYMBOL_Apostrophe"),
+
+  KBD_KEY_NAME(SYMBOL, Europe2, "Europe2"),
+  KBD_KEY_NAME(SYMBOL, Europe2, "SYMBOL_Europe2"),
+
+  KBD_KEY_NAME(SYMBOL, Comma, "Comma"),
+  KBD_KEY_NAME(SYMBOL, Comma, "SYMBOL_Comma"),
+
+  KBD_KEY_NAME(SYMBOL, Period, "Period"),
+  KBD_KEY_NAME(SYMBOL, Period, "SYMBOL_Period"),
+
+  KBD_KEY_NAME(SYMBOL, Slash, "Slash"),
+  KBD_KEY_NAME(SYMBOL, Slash, "SYMBOL_Slash"),
+
+  KBD_KEY_NAME(SYMBOL, Space, "Space"),
+  KBD_KEY_NAME(SYMBOL, Space, "SYMBOL_Space"),
+
+  KBD_KEY_NAME(ACTION, Enter, "Enter"),
+  KBD_KEY_NAME(ACTION, Enter, "ACTION_Enter"),
+
+  KBD_KEY_NAME(ACTION, Tab, "Tab"),
+  KBD_KEY_NAME(ACTION, Tab, "ACTION_Tab"),
+
+  KBD_KEY_NAME(ACTION, Escape, "Escape"),
+  KBD_KEY_NAME(ACTION, Escape, "ACTION_Escape"),
+
+  KBD_KEY_NAME(ACTION, Insert, "Insert"),
+  KBD_KEY_NAME(ACTION, Insert, "ACTION_Insert"),
+
+  KBD_KEY_NAME(ACTION, DeleteBackward, "DeleteBackward"),
+  KBD_KEY_NAME(ACTION, DeleteBackward, "ACTION_DeleteBackward"),
+
+  KBD_KEY_NAME(ACTION, DeleteForward, "DeleteForward"),
+  KBD_KEY_NAME(ACTION, DeleteForward, "ACTION_DeleteForward"),
+
+  KBD_KEY_NAME(ACTION, Home, "Home"),
+  KBD_KEY_NAME(ACTION, Home, "ACTION_Home"),
+
+  KBD_KEY_NAME(ACTION, End, "End"),
+  KBD_KEY_NAME(ACTION, End, "ACTION_End"),
+
+  KBD_KEY_NAME(ACTION, PageUp, "PageUp"),
+  KBD_KEY_NAME(ACTION, PageUp, "ACTION_PageUp"),
+
+  KBD_KEY_NAME(ACTION, PageDown, "PageDown"),
+  KBD_KEY_NAME(ACTION, PageDown, "ACTION_PageDown"),
+
+  KBD_KEY_NAME(ACTION, ArrowUp, "ArrowUp"),
+  KBD_KEY_NAME(ACTION, ArrowUp, "ACTION_ArrowUp"),
+
+  KBD_KEY_NAME(ACTION, ArrowDown, "ArrowDown"),
+  KBD_KEY_NAME(ACTION, ArrowDown, "ACTION_ArrowDown"),
+
+  KBD_KEY_NAME(ACTION, ArrowLeft, "ArrowLeft"),
+  KBD_KEY_NAME(ACTION, ArrowLeft, "ACTION_ArrowLeft"),
+
+  KBD_KEY_NAME(ACTION, ArrowRight, "ArrowRight"),
+  KBD_KEY_NAME(ACTION, ArrowRight, "ACTION_ArrowRight"),
+
+  KBD_KEY_NAME(ACTION, PrintScreen, "PrintScreen"),
+  KBD_KEY_NAME(ACTION, PrintScreen, "ACTION_PrintScreen"),
+
+  KBD_KEY_NAME(ACTION, SystemRequest, "SystemRequest"),
+  KBD_KEY_NAME(ACTION, SystemRequest, "ACTION_SystemRequest"),
+
+  KBD_KEY_NAME(ACTION, Pause, "Pause"),
+  KBD_KEY_NAME(ACTION, Pause, "ACTION_Pause"),
+
+  KBD_KEY_NAME(ACTION, GuiLeft, "GuiLeft"),
+  KBD_KEY_NAME(ACTION, GuiLeft, "ACTION_GuiLeft"),
+
+  KBD_KEY_NAME(ACTION, GuiRight, "GuiRight"),
+  KBD_KEY_NAME(ACTION, GuiRight, "ACTION_GuiRight"),
+
+  KBD_KEY_NAME(ACTION, Context, "Context"),
+  KBD_KEY_NAME(ACTION, Context, "ACTION_Context"),
+  KBD_KEY_NAME(ACTION, Context, "Application"),
+  KBD_KEY_NAME(ACTION, Context, "ACTION_Application"),
+
+  KBD_KEY_NAME(ACTION, Help, "ACTION_Help"),
+
+  KBD_KEY_NAME(ACTION, Stop, "ACTION_Stop"),
+
+  KBD_KEY_NAME(ACTION, Props, "ACTION_Props"),
+
+  KBD_KEY_NAME(ACTION, Front, "ACTION_Front"),
+
+  KBD_KEY_NAME(ACTION, Open, "ACTION_Open"),
+
+  KBD_KEY_NAME(ACTION, Find, "ACTION_Find"),
+
+  KBD_KEY_NAME(ACTION, Again, "ACTION_Again"),
+
+  KBD_KEY_NAME(ACTION, Undo, "ACTION_Undo"),
+
+  KBD_KEY_NAME(ACTION, Copy, "ACTION_Copy"),
+
+  KBD_KEY_NAME(ACTION, Paste, "ACTION_Paste"),
+
+  KBD_KEY_NAME(ACTION, Cut, "ACTION_Cut"),
+
+  KBD_KEY_NAME(ACTION, Power, "ACTION_Power"),
+
+  KBD_KEY_NAME(ACTION, Sleep, "ACTION_Sleep"),
+
+  KBD_KEY_NAME(ACTION, Wakeup, "ACTION_Wakeup"),
+
+  KBD_KEY_NAME(ACTION, Menu, "ACTION_Menu"),
+
+  KBD_KEY_NAME(ACTION, Select, "Select"),
+  KBD_KEY_NAME(ACTION, Select, "ACTION_Select"),
+
+  KBD_KEY_NAME(ACTION, Cancel, "ACTION_Cancel"),
+
+  KBD_KEY_NAME(ACTION, Clear, "ACTION_Clear"),
+
+  KBD_KEY_NAME(ACTION, Prior, "ACTION_Prior"),
+
+  KBD_KEY_NAME(ACTION, Return, "ACTION_Return"),
+
+  KBD_KEY_NAME(ACTION, Separator, "ACTION_Separator"),
+
+  KBD_KEY_NAME(ACTION, Out, "ACTION_Out"),
+
+  KBD_KEY_NAME(ACTION, Oper, "ACTION_Oper"),
+
+  KBD_KEY_NAME(ACTION, Clear_Again, "ACTION_Clear_Again"),
+
+  KBD_KEY_NAME(ACTION, CrSel_Props, "ACTION_CrSel_Props"),
+
+  KBD_KEY_NAME(ACTION, ExSel, "ACTION_ExSel"),
+
+  KBD_KEY_NAME(MEDIA, Mute, "MEDIA_Mute"),
+
+  KBD_KEY_NAME(MEDIA, VolumeDown, "MEDIA_VolumeDown"),
+
+  KBD_KEY_NAME(MEDIA, VolumeUp, "MEDIA_VolumeUp"),
+
+  KBD_KEY_NAME(MEDIA, Stop, "MEDIA_Stop"),
+
+  KBD_KEY_NAME(MEDIA, Play, "MEDIA_Play"),
+
+  KBD_KEY_NAME(MEDIA, Record, "MEDIA_Record"),
+
+  KBD_KEY_NAME(MEDIA, Pause, "MEDIA_Pause"),
+
+  KBD_KEY_NAME(MEDIA, PlayPause, "MEDIA_PlayPause"),
+
+  KBD_KEY_NAME(MEDIA, Previous, "MEDIA_Previous"),
+
+  KBD_KEY_NAME(MEDIA, Next, "MEDIA_Next"),
+
+  KBD_KEY_NAME(MEDIA, Backward, "MEDIA_Backward"),
+
+  KBD_KEY_NAME(MEDIA, Forward, "MEDIA_Forward"),
+
+  KBD_KEY_NAME(MEDIA, Eject, "MEDIA_Eject"),
+
+  KBD_KEY_NAME(MEDIA, Close, "MEDIA_Close"),
+
+  KBD_KEY_NAME(MEDIA, EjectClose, "MEDIA_EjectClose"),
+
+  KBD_KEY_NAME(FUNCTION, F1, "F1"),
+  KBD_KEY_NAME(FUNCTION, F1, "FUNCTION_F1"),
+
+  KBD_KEY_NAME(FUNCTION, F2, "F2"),
+  KBD_KEY_NAME(FUNCTION, F2, "FUNCTION_F2"),
+
+  KBD_KEY_NAME(FUNCTION, F3, "F3"),
+  KBD_KEY_NAME(FUNCTION, F3, "FUNCTION_F3"),
+
+  KBD_KEY_NAME(FUNCTION, F4, "F4"),
+  KBD_KEY_NAME(FUNCTION, F4, "FUNCTION_F4"),
+
+  KBD_KEY_NAME(FUNCTION, F5, "F5"),
+  KBD_KEY_NAME(FUNCTION, F5, "FUNCTION_F5"),
+
+  KBD_KEY_NAME(FUNCTION, F6, "F6"),
+  KBD_KEY_NAME(FUNCTION, F6, "FUNCTION_F6"),
+
+  KBD_KEY_NAME(FUNCTION, F7, "F7"),
+  KBD_KEY_NAME(FUNCTION, F7, "FUNCTION_F7"),
+
+  KBD_KEY_NAME(FUNCTION, F8, "F8"),
+  KBD_KEY_NAME(FUNCTION, F8, "FUNCTION_F8"),
+
+  KBD_KEY_NAME(FUNCTION, F9, "F9"),
+  KBD_KEY_NAME(FUNCTION, F9, "FUNCTION_F9"),
+
+  KBD_KEY_NAME(FUNCTION, F10, "F10"),
+  KBD_KEY_NAME(FUNCTION, F10, "FUNCTION_F10"),
+
+  KBD_KEY_NAME(FUNCTION, F11, "F11"),
+  KBD_KEY_NAME(FUNCTION, F11, "FUNCTION_F11"),
+
+  KBD_KEY_NAME(FUNCTION, F12, "F12"),
+  KBD_KEY_NAME(FUNCTION, F12, "FUNCTION_F12"),
+
+  KBD_KEY_NAME(FUNCTION, F13, "F13"),
+  KBD_KEY_NAME(FUNCTION, F13, "FUNCTION_F13"),
+
+  KBD_KEY_NAME(FUNCTION, F14, "F14"),
+  KBD_KEY_NAME(FUNCTION, F14, "FUNCTION_F14"),
+
+  KBD_KEY_NAME(FUNCTION, F15, "F15"),
+  KBD_KEY_NAME(FUNCTION, F15, "FUNCTION_F15"),
+
+  KBD_KEY_NAME(FUNCTION, F16, "F16"),
+  KBD_KEY_NAME(FUNCTION, F16, "FUNCTION_F16"),
+
+  KBD_KEY_NAME(FUNCTION, F17, "F17"),
+  KBD_KEY_NAME(FUNCTION, F17, "FUNCTION_F17"),
+
+  KBD_KEY_NAME(FUNCTION, F18, "F18"),
+  KBD_KEY_NAME(FUNCTION, F18, "FUNCTION_F18"),
+
+  KBD_KEY_NAME(FUNCTION, F19, "F19"),
+  KBD_KEY_NAME(FUNCTION, F19, "FUNCTION_F19"),
+
+  KBD_KEY_NAME(FUNCTION, F20, "F20"),
+  KBD_KEY_NAME(FUNCTION, F20, "FUNCTION_F20"),
+
+  KBD_KEY_NAME(FUNCTION, F21, "F21"),
+  KBD_KEY_NAME(FUNCTION, F21, "FUNCTION_F21"),
+
+  KBD_KEY_NAME(FUNCTION, F22, "F22"),
+  KBD_KEY_NAME(FUNCTION, F22, "FUNCTION_F22"),
+
+  KBD_KEY_NAME(FUNCTION, F23, "F23"),
+  KBD_KEY_NAME(FUNCTION, F23, "FUNCTION_F23"),
+
+  KBD_KEY_NAME(FUNCTION, F24, "F24"),
+  KBD_KEY_NAME(FUNCTION, F24, "FUNCTION_F24"),
+
+  KBD_KEY_NAME(MODIFIER, ShiftLeft, "ShiftLeft"),
+  KBD_KEY_NAME(MODIFIER, ShiftLeft, "MODIFIER_ShiftLeft"),
+
+  KBD_KEY_NAME(MODIFIER, ShiftRight, "ShiftRight"),
+  KBD_KEY_NAME(MODIFIER, ShiftRight, "MODIFIER_ShiftRight"),
+
+  KBD_KEY_NAME(MODIFIER, ControlLeft, "ControlLeft"),
+  KBD_KEY_NAME(MODIFIER, ControlLeft, "MODIFIER_ControlLeft"),
+
+  KBD_KEY_NAME(MODIFIER, ControlRight, "ControlRight"),
+  KBD_KEY_NAME(MODIFIER, ControlRight, "MODIFIER_ControlRight"),
+
+  KBD_KEY_NAME(MODIFIER, AltLeft, "AltLeft"),
+  KBD_KEY_NAME(MODIFIER, AltLeft, "MODIFIER_AltLeft"),
+  KBD_KEY_NAME(MODIFIER, AltLeft, "Alt"),
+  KBD_KEY_NAME(MODIFIER, AltLeft, "MODIFIER_Alt"),
+
+  KBD_KEY_NAME(MODIFIER, AltRight, "AltRight"),
+  KBD_KEY_NAME(MODIFIER, AltRight, "MODIFIER_AltRight"),
+  KBD_KEY_NAME(MODIFIER, AltRight, "AltGr"),
+  KBD_KEY_NAME(MODIFIER, AltRight, "MODIFIER_AltGr"),
+
+  KBD_KEY_NAME(LOCK, Capitals, "CapsLock"),
+  KBD_KEY_NAME(LOCK, Capitals, "LOCK_Capitals"),
+
+  KBD_KEY_NAME(LOCK, Scroll, "ScrollLock"),
+  KBD_KEY_NAME(LOCK, Scroll, "LOCK_Scroll"),
+
+  KBD_KEY_NAME(LOCK, Numbers, "NumLock"),
+  KBD_KEY_NAME(LOCK, Numbers, "LOCK_Numbers"),
+
+  KBD_KEY_NAME(KPNUMBER, Zero, "KP0"),
+  KBD_KEY_NAME(KPNUMBER, Zero, "KPNUMBER_Zero"),
+
+  KBD_KEY_NAME(KPNUMBER, One, "KP1"),
+  KBD_KEY_NAME(KPNUMBER, One, "KPNUMBER_One"),
+
+  KBD_KEY_NAME(KPNUMBER, Two, "KP2"),
+  KBD_KEY_NAME(KPNUMBER, Two, "KPNUMBER_Two"),
+
+  KBD_KEY_NAME(KPNUMBER, Three, "KP3"),
+  KBD_KEY_NAME(KPNUMBER, Three, "KPNUMBER_Three"),
+
+  KBD_KEY_NAME(KPNUMBER, Four, "KP4"),
+  KBD_KEY_NAME(KPNUMBER, Four, "KPNUMBER_Four"),
+
+  KBD_KEY_NAME(KPNUMBER, Five, "KP5"),
+  KBD_KEY_NAME(KPNUMBER, Five, "KPNUMBER_Five"),
+
+  KBD_KEY_NAME(KPNUMBER, Six, "KP6"),
+  KBD_KEY_NAME(KPNUMBER, Six, "KPNUMBER_Six"),
+
+  KBD_KEY_NAME(KPNUMBER, Seven, "KP7"),
+  KBD_KEY_NAME(KPNUMBER, Seven, "KPNUMBER_Seven"),
+
+  KBD_KEY_NAME(KPNUMBER, Eight, "KP8"),
+  KBD_KEY_NAME(KPNUMBER, Eight, "KPNUMBER_Eight"),
+
+  KBD_KEY_NAME(KPNUMBER, Nine, "KP9"),
+  KBD_KEY_NAME(KPNUMBER, Nine, "KPNUMBER_Nine"),
+
+  KBD_KEY_NAME(KPNUMBER, A, "KPA"),
+  KBD_KEY_NAME(KPNUMBER, A, "KPNUMBER_A"),
+
+  KBD_KEY_NAME(KPNUMBER, B, "KPB"),
+  KBD_KEY_NAME(KPNUMBER, B, "KPNUMBER_B"),
+
+  KBD_KEY_NAME(KPNUMBER, C, "KPC"),
+  KBD_KEY_NAME(KPNUMBER, C, "KPNUMBER_C"),
+
+  KBD_KEY_NAME(KPNUMBER, D, "KPD"),
+  KBD_KEY_NAME(KPNUMBER, D, "KPNUMBER_D"),
+
+  KBD_KEY_NAME(KPNUMBER, E, "KPE"),
+  KBD_KEY_NAME(KPNUMBER, E, "KPNUMBER_E"),
+
+  KBD_KEY_NAME(KPNUMBER, F, "KPF"),
+  KBD_KEY_NAME(KPNUMBER, F, "KPNUMBER_F"),
+
+  KBD_KEY_NAME(KPSYMBOL, DecimalSeparator, "KPSYMBOL_DecimalSeparator"),
+
+  KBD_KEY_NAME(KPSYMBOL, ThousandsSeparator, "KPSYMBOL_ThousandsSeparator"),
+
+  KBD_KEY_NAME(KPSYMBOL, 00, "KPSYMBOL_00"),
+
+  KBD_KEY_NAME(KPSYMBOL, 000, "KPSYMBOL_000"),
+
+  KBD_KEY_NAME(KPSYMBOL, Plus, "KPPlus"),
+  KBD_KEY_NAME(KPSYMBOL, Plus, "KPSYMBOL_Plus"),
+
+  KBD_KEY_NAME(KPSYMBOL, Minus, "KPMinus"),
+  KBD_KEY_NAME(KPSYMBOL, Minus, "KPSYMBOL_Minus"),
+
+  KBD_KEY_NAME(KPSYMBOL, Multiply, "KPMultiply"),
+  KBD_KEY_NAME(KPSYMBOL, Multiply, "KPSYMBOL_Multiply"),
+
+  KBD_KEY_NAME(KPSYMBOL, Divide, "KPDivide"),
+  KBD_KEY_NAME(KPSYMBOL, Divide, "KPSYMBOL_Divide"),
+
+  KBD_KEY_NAME(KPSYMBOL, Modulo, "KPSYMBOL_Modulo"),
+
+  KBD_KEY_NAME(KPSYMBOL, Equals, "KPEquals"),
+  KBD_KEY_NAME(KPSYMBOL, Equals, "KPSYMBOL_Equals"),
+
+  KBD_KEY_NAME(KPSYMBOL, Less, "KPSYMBOL_Less"),
+
+  KBD_KEY_NAME(KPSYMBOL, Greater, "KPSYMBOL_Greater"),
+
+  KBD_KEY_NAME(KPSYMBOL, PlusMinus, "KPSYMBOL_PlusMinus"),
+
+  KBD_KEY_NAME(KPSYMBOL, LeftParenthesis, "KPSYMBOL_LeftParenthesis"),
+
+  KBD_KEY_NAME(KPSYMBOL, RightParenthesis, "KPSYMBOL_RightParenthesis"),
+
+  KBD_KEY_NAME(KPSYMBOL, LeftBrace, "KPSYMBOL_LeftBrace"),
+
+  KBD_KEY_NAME(KPSYMBOL, RightBrace, "KPSYMBOL_RightBrace"),
+
+  KBD_KEY_NAME(KPSYMBOL, BitwiseAnd, "KPSYMBOL_BitwiseAnd"),
+
+  KBD_KEY_NAME(KPSYMBOL, BitwiseOr, "KPSYMBOL_BitwiseOr"),
+
+  KBD_KEY_NAME(KPSYMBOL, BitwiseXor, "KPSYMBOL_BitwiseXor"),
+
+  KBD_KEY_NAME(KPSYMBOL, BooleanNot, "KPSYMBOL_BooleanNot"),
+
+  KBD_KEY_NAME(KPSYMBOL, BooleanAnd, "KPSYMBOL_BooleanAnd"),
+
+  KBD_KEY_NAME(KPSYMBOL, BooleanOr, "KPSYMBOL_BooleanOr"),
+
+  KBD_KEY_NAME(KPSYMBOL, BooleanXor, "KPSYMBOL_BooleanXor"),
+
+  KBD_KEY_NAME(KPSYMBOL, Space, "KPSYMBOL_Space"),
+
+  KBD_KEY_NAME(KPSYMBOL, Period, "KPPeriod"),
+  KBD_KEY_NAME(KPSYMBOL, Period, "KPSYMBOL_Period"),
+
+  KBD_KEY_NAME(KPSYMBOL, Comma, "KPSYMBOL_Comma"),
+
+  KBD_KEY_NAME(KPSYMBOL, Colon, "KPSYMBOL_Colon"),
+
+  KBD_KEY_NAME(KPSYMBOL, At, "KPSYMBOL_At"),
+
+  KBD_KEY_NAME(KPSYMBOL, Number, "KPSYMBOL_Number"),
+
+  KBD_KEY_NAME(KPSYMBOL, CurrencyUnit, "KPSYMBOL_CurrencyUnit"),
+
+  KBD_KEY_NAME(KPSYMBOL, CurrencySubunit, "KPSYMBOL_CurrencySubunit"),
+
+  KBD_KEY_NAME(KPACTION, Enter, "KPEnter"),
+  KBD_KEY_NAME(KPACTION, Enter, "KPACTION_Enter"),
+
+  KBD_KEY_NAME(KPACTION, Backspace, "KPACTION_Backspace"),
+
+  KBD_KEY_NAME(KPACTION, Tab, "KPACTION_Tab"),
+
+  KBD_KEY_NAME(KPACTION, Clear, "KPACTION_Clear"),
+
+  KBD_KEY_NAME(KPACTION, ClearEntry, "KPACTION_ClearEntry"),
+
+  KBD_KEY_NAME(KPACTION, MemoryClear, "KPACTION_MemoryClear"),
+
+  KBD_KEY_NAME(KPACTION, MemoryStore, "KPACTION_MemoryStore"),
+
+  KBD_KEY_NAME(KPACTION, MemoryRecall, "KPACTION_MemoryRecall"),
+
+  KBD_KEY_NAME(KPACTION, MemoryAdd, "KPACTION_MemoryAdd"),
+
+  KBD_KEY_NAME(KPACTION, MemorySubtract, "KPACTION_MemorySubtract"),
+
+  KBD_KEY_NAME(KPACTION, MemoryMultiply, "KPACTION_MemoryMultiply"),
+
+  KBD_KEY_NAME(KPACTION, MemoryDivide, "KPACTION_MemoryDivide"),
+
+  KBD_KEY_NAME(KPACTION, Binary, "KPACTION_Binary"),
+
+  KBD_KEY_NAME(KPACTION, Octal, "KPACTION_Octal"),
+
+  KBD_KEY_NAME(KPACTION, Decimal, "KPACTION_Decimal"),
+
+  KBD_KEY_NAME(KPACTION, Hexadecimal, "KPACTION_Hexadecimal"),
+
+  KBD_KEY_NAME(BRAILLE, Space, "Dot0"),
+  KBD_KEY_NAME(BRAILLE, Space, "BRAILLE_Dot0"),
+  KBD_KEY_NAME(BRAILLE, Space, "BRAILLE_Space"),
+
+  KBD_KEY_NAME(BRAILLE, Dot1, "Dot1"),
+  KBD_KEY_NAME(BRAILLE, Dot1, "BRAILLE_Dot1"),
+
+  KBD_KEY_NAME(BRAILLE, Dot2, "Dot2"),
+  KBD_KEY_NAME(BRAILLE, Dot2, "BRAILLE_Dot2"),
+
+  KBD_KEY_NAME(BRAILLE, Dot3, "Dot3"),
+  KBD_KEY_NAME(BRAILLE, Dot3, "BRAILLE_Dot3"),
+
+  KBD_KEY_NAME(BRAILLE, Dot4, "Dot4"),
+  KBD_KEY_NAME(BRAILLE, Dot4, "BRAILLE_Dot4"),
+
+  KBD_KEY_NAME(BRAILLE, Dot5, "Dot5"),
+  KBD_KEY_NAME(BRAILLE, Dot5, "BRAILLE_Dot5"),
+
+  KBD_KEY_NAME(BRAILLE, Dot6, "Dot6"),
+  KBD_KEY_NAME(BRAILLE, Dot6, "BRAILLE_Dot6"),
+
+  KBD_KEY_NAME(BRAILLE, Dot7, "Dot7"),
+  KBD_KEY_NAME(BRAILLE, Dot7, "BRAILLE_Dot7"),
+
+  KBD_KEY_NAME(BRAILLE, Dot8, "Dot8"),
+  KBD_KEY_NAME(BRAILLE, Dot8, "BRAILLE_Dot8"),
+
+  KBD_KEY_NAME(BRAILLE, Backward, "Backward"),
+  KBD_KEY_NAME(BRAILLE, Backward, "BRAILLE_Backward"),
+
+  KBD_KEY_NAME(BRAILLE, Forward, "Forward"),
+  KBD_KEY_NAME(BRAILLE, Forward, "BRAILLE_Forward"),
+
+  KBD_GROUP_NAME(NUMBER, "NumberKey"),
+  KBD_GROUP_NAME(FUNCTION, "FunctionKey"),
+  KBD_GROUP_NAME(KPNUMBER, "KeypadNumberKey"),
+  KBD_GROUP_NAME(ROUTING, "RoutingKey"),
+END_KEY_NAME_TABLE
+
+KEY_NAME_TABLES_DECLARATION(keyboard) = {
+  KEY_NAME_TABLE(keyboard),
+  LAST_KEY_NAME_TABLE
+};
diff --git a/Programs/ktb_keyboard.h b/Programs/ktb_keyboard.h
new file mode 100644
index 0000000..c20a645
--- /dev/null
+++ b/Programs/ktb_keyboard.h
@@ -0,0 +1,377 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_KEYBOARD
+#define BRLTTY_INCLUDED_KTB_KEYBOARD
+
+#include "ktb_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define KBD_GROUP(grp) KBD_GRP_##grp
+#define KBD_KEY(grp,key) KBD_KEY_##grp##_##key
+
+#define KBD_KEY_VALUE(grp,num) {.group=KBD_GROUP(grp), .number=num}
+#define KBD_KEY_ENTRY(grp,key) KBD_KEY_VALUE(grp, KBD_KEY(grp, key))
+
+#define KBD_GROUP_NAME(grp,nam) {.value=KBD_KEY_VALUE(grp, KTB_KEY_ANY), .name=nam}
+#define KBD_KEY_NAME(grp,key,nam) {.value=KBD_KEY_ENTRY(grp, key), .name=nam}
+
+#define KBD_KEY_SPECIAL(name) KBD_KEY_ENTRY(SPECIAL, name)
+#define KBD_KEY_UNMAPPED KBD_KEY_SPECIAL(Unmapped)
+#define KBD_KEY_IGNORE KBD_KEY_SPECIAL(Ignore)
+
+#define KBD_KEY_LETTER(name) KBD_KEY_ENTRY(LETTER, name)
+#define KBD_KEY_NUMBER(name) KBD_KEY_ENTRY(NUMBER, name)
+#define KBD_KEY_SYMBOL(name) KBD_KEY_ENTRY(SYMBOL, name)
+
+#define KBD_KEY_ACTION(name) KBD_KEY_ENTRY(ACTION, name)
+#define KBD_KEY_MEDIA(name) KBD_KEY_ENTRY(MEDIA, name)
+#define KBD_KEY_FUNCTION(name) KBD_KEY_ENTRY(FUNCTION, name)
+
+#define KBD_KEY_MODIFIER(name) KBD_KEY_ENTRY(MODIFIER, name)
+#define KBD_KEY_LOCK(name) KBD_KEY_ENTRY(LOCK, name)
+
+#define KBD_KEY_KPNUMBER(name) KBD_KEY_ENTRY(KPNUMBER, name)
+#define KBD_KEY_KPSYMBOL(name) KBD_KEY_ENTRY(KPSYMBOL, name)
+#define KBD_KEY_KPACTION(name) KBD_KEY_ENTRY(KPACTION, name)
+
+#define KBD_KEY_BRAILLE(name) KBD_KEY_ENTRY(BRAILLE, name)
+#define KBD_KEY_ROUTING(number) KBD_KEY_VALUE(ROUTING, (number))
+
+typedef enum {
+  KBD_GROUP(SPECIAL) = 0 /* KBD_KEY_UNMAPPED must be all zeros */,
+
+  KBD_GROUP(LETTER),
+  KBD_GROUP(NUMBER),
+  KBD_GROUP(SYMBOL),
+
+  KBD_GROUP(ACTION),
+  KBD_GROUP(MEDIA),
+  KBD_GROUP(FUNCTION),
+
+  KBD_GROUP(MODIFIER),
+  KBD_GROUP(LOCK),
+
+  KBD_GROUP(KPNUMBER),
+  KBD_GROUP(KPSYMBOL),
+  KBD_GROUP(KPACTION),
+
+  KBD_GROUP(BRAILLE),
+  KBD_GROUP(ROUTING),
+} KBD_KeyGroup;
+
+typedef enum {
+  KBD_KEY(SPECIAL, Unmapped) = 0 /* KBD_KEY_UNMAPPED must be all zeros */,
+  KBD_KEY(SPECIAL, Ignore),
+} KBD_SpecialKey;
+
+typedef enum {
+  KBD_KEY(LETTER, A),
+  KBD_KEY(LETTER, B),
+  KBD_KEY(LETTER, C),
+  KBD_KEY(LETTER, D),
+  KBD_KEY(LETTER, E),
+  KBD_KEY(LETTER, F),
+  KBD_KEY(LETTER, G),
+  KBD_KEY(LETTER, H),
+  KBD_KEY(LETTER, I),
+  KBD_KEY(LETTER, J),
+  KBD_KEY(LETTER, K),
+  KBD_KEY(LETTER, L),
+  KBD_KEY(LETTER, M),
+  KBD_KEY(LETTER, N),
+  KBD_KEY(LETTER, O),
+  KBD_KEY(LETTER, P),
+  KBD_KEY(LETTER, Q),
+  KBD_KEY(LETTER, R),
+  KBD_KEY(LETTER, S),
+  KBD_KEY(LETTER, T),
+  KBD_KEY(LETTER, U),
+  KBD_KEY(LETTER, V),
+  KBD_KEY(LETTER, W),
+  KBD_KEY(LETTER, X),
+  KBD_KEY(LETTER, Y),
+  KBD_KEY(LETTER, Z),
+} KBD_LetterKey;
+
+typedef enum {
+  KBD_KEY(NUMBER, Zero),
+  KBD_KEY(NUMBER, One),
+  KBD_KEY(NUMBER, Two),
+  KBD_KEY(NUMBER, Three),
+  KBD_KEY(NUMBER, Four),
+  KBD_KEY(NUMBER, Five),
+  KBD_KEY(NUMBER, Six),
+  KBD_KEY(NUMBER, Seven),
+  KBD_KEY(NUMBER, Eight),
+  KBD_KEY(NUMBER, Nine),
+} KBD_NumberKey;
+
+typedef enum {
+  KBD_KEY(SYMBOL, Grave),
+  KBD_KEY(SYMBOL, Minus),
+  KBD_KEY(SYMBOL, Equals),
+  KBD_KEY(SYMBOL, Backslash),
+
+  KBD_KEY(SYMBOL, LeftBracket),
+  KBD_KEY(SYMBOL, RightBracket),
+
+  KBD_KEY(SYMBOL, Semicolon),
+  KBD_KEY(SYMBOL, Apostrophe),
+
+  KBD_KEY(SYMBOL, Europe2),
+  KBD_KEY(SYMBOL, Comma),
+  KBD_KEY(SYMBOL, Period),
+  KBD_KEY(SYMBOL, Slash),
+
+  KBD_KEY(SYMBOL, Space),
+} KBD_SymbolKey;
+
+typedef enum {
+  KBD_KEY(ACTION, Enter),
+  KBD_KEY(ACTION, Tab),
+  KBD_KEY(ACTION, Escape),
+
+  KBD_KEY(ACTION, Insert),
+  KBD_KEY(ACTION, DeleteBackward),
+  KBD_KEY(ACTION, DeleteForward),
+
+  KBD_KEY(ACTION, Home),
+  KBD_KEY(ACTION, End),
+
+  KBD_KEY(ACTION, PageUp),
+  KBD_KEY(ACTION, PageDown),
+
+  KBD_KEY(ACTION, ArrowUp),
+  KBD_KEY(ACTION, ArrowDown),
+  KBD_KEY(ACTION, ArrowLeft),
+  KBD_KEY(ACTION, ArrowRight),
+
+  KBD_KEY(ACTION, PrintScreen),
+  KBD_KEY(ACTION, SystemRequest),
+  KBD_KEY(ACTION, Pause),
+
+  KBD_KEY(ACTION, GuiLeft),
+  KBD_KEY(ACTION, GuiRight),
+  KBD_KEY(ACTION, Context),
+
+  KBD_KEY(ACTION, Help),
+  KBD_KEY(ACTION, Stop),
+  KBD_KEY(ACTION, Props),
+  KBD_KEY(ACTION, Front),
+  KBD_KEY(ACTION, Open),
+  KBD_KEY(ACTION, Find),
+  KBD_KEY(ACTION, Again),
+  KBD_KEY(ACTION, Undo),
+  KBD_KEY(ACTION, Copy),
+  KBD_KEY(ACTION, Paste),
+  KBD_KEY(ACTION, Cut),
+
+  KBD_KEY(ACTION, Power),
+  KBD_KEY(ACTION, Sleep),
+  KBD_KEY(ACTION, Wakeup),
+
+  KBD_KEY(ACTION, Menu),
+  KBD_KEY(ACTION, Select),
+
+  KBD_KEY(ACTION, Cancel),
+  KBD_KEY(ACTION, Clear),
+  KBD_KEY(ACTION, Prior),
+  KBD_KEY(ACTION, Return),
+  KBD_KEY(ACTION, Separator),
+  KBD_KEY(ACTION, Out),
+  KBD_KEY(ACTION, Oper),
+  KBD_KEY(ACTION, Clear_Again),
+  KBD_KEY(ACTION, CrSel_Props),
+  KBD_KEY(ACTION, ExSel),
+} KBD_ActionKey;
+
+typedef enum {
+  KBD_KEY(MEDIA, Mute),
+  KBD_KEY(MEDIA, VolumeDown),
+  KBD_KEY(MEDIA, VolumeUp),
+
+  KBD_KEY(MEDIA, Stop),
+  KBD_KEY(MEDIA, Play),
+  KBD_KEY(MEDIA, Record),
+  KBD_KEY(MEDIA, Pause),
+  KBD_KEY(MEDIA, PlayPause),
+
+  KBD_KEY(MEDIA, Previous),
+  KBD_KEY(MEDIA, Next),
+  KBD_KEY(MEDIA, Backward),
+  KBD_KEY(MEDIA, Forward),
+
+  KBD_KEY(MEDIA, Eject),
+  KBD_KEY(MEDIA, Close),
+  KBD_KEY(MEDIA, EjectClose),
+} KBD_MediaKey;
+
+typedef enum {
+  KBD_KEY(FUNCTION, F1),
+  KBD_KEY(FUNCTION, F2),
+  KBD_KEY(FUNCTION, F3),
+  KBD_KEY(FUNCTION, F4),
+  KBD_KEY(FUNCTION, F5),
+  KBD_KEY(FUNCTION, F6),
+  KBD_KEY(FUNCTION, F7),
+  KBD_KEY(FUNCTION, F8),
+  KBD_KEY(FUNCTION, F9),
+  KBD_KEY(FUNCTION, F10),
+  KBD_KEY(FUNCTION, F11),
+  KBD_KEY(FUNCTION, F12),
+  KBD_KEY(FUNCTION, F13),
+  KBD_KEY(FUNCTION, F14),
+  KBD_KEY(FUNCTION, F15),
+  KBD_KEY(FUNCTION, F16),
+  KBD_KEY(FUNCTION, F17),
+  KBD_KEY(FUNCTION, F18),
+  KBD_KEY(FUNCTION, F19),
+  KBD_KEY(FUNCTION, F20),
+  KBD_KEY(FUNCTION, F21),
+  KBD_KEY(FUNCTION, F22),
+  KBD_KEY(FUNCTION, F23),
+  KBD_KEY(FUNCTION, F24),
+} KBD_FunctionKey;
+
+typedef enum {
+  KBD_KEY(MODIFIER, ShiftLeft),
+  KBD_KEY(MODIFIER, ShiftRight),
+
+  KBD_KEY(MODIFIER, ControlLeft),
+  KBD_KEY(MODIFIER, ControlRight),
+
+  KBD_KEY(MODIFIER, AltLeft),
+  KBD_KEY(MODIFIER, AltRight),
+} KBD_ModifierKey;
+
+typedef enum {
+  KBD_KEY(LOCK, Capitals),
+  KBD_KEY(LOCK, Scroll),
+  KBD_KEY(LOCK, Numbers),
+} KBD_LockKey;
+
+typedef enum {
+  KBD_KEY(KPNUMBER, Zero),
+  KBD_KEY(KPNUMBER, One),
+  KBD_KEY(KPNUMBER, Two),
+  KBD_KEY(KPNUMBER, Three),
+  KBD_KEY(KPNUMBER, Four),
+  KBD_KEY(KPNUMBER, Five),
+  KBD_KEY(KPNUMBER, Six),
+  KBD_KEY(KPNUMBER, Seven),
+  KBD_KEY(KPNUMBER, Eight),
+  KBD_KEY(KPNUMBER, Nine),
+
+  KBD_KEY(KPNUMBER, A),
+  KBD_KEY(KPNUMBER, B),
+  KBD_KEY(KPNUMBER, C),
+  KBD_KEY(KPNUMBER, D),
+  KBD_KEY(KPNUMBER, E),
+  KBD_KEY(KPNUMBER, F),
+} KBD_KPNumberKey;
+
+typedef enum {
+  KBD_KEY(KPSYMBOL, DecimalSeparator),
+  KBD_KEY(KPSYMBOL, ThousandsSeparator),
+  KBD_KEY(KPSYMBOL, 00),
+  KBD_KEY(KPSYMBOL, 000),
+
+  KBD_KEY(KPSYMBOL, Plus),
+  KBD_KEY(KPSYMBOL, Minus),
+  KBD_KEY(KPSYMBOL, Multiply),
+  KBD_KEY(KPSYMBOL, Divide),
+  KBD_KEY(KPSYMBOL, Modulo),
+
+  KBD_KEY(KPSYMBOL, Equals),
+  KBD_KEY(KPSYMBOL, Less),
+  KBD_KEY(KPSYMBOL, Greater),
+  KBD_KEY(KPSYMBOL, PlusMinus),
+
+  KBD_KEY(KPSYMBOL, LeftParenthesis),
+  KBD_KEY(KPSYMBOL, RightParenthesis),
+  KBD_KEY(KPSYMBOL, LeftBrace),
+  KBD_KEY(KPSYMBOL, RightBrace),
+
+  KBD_KEY(KPSYMBOL, BitwiseAnd),
+  KBD_KEY(KPSYMBOL, BitwiseOr),
+  KBD_KEY(KPSYMBOL, BitwiseXor),
+
+  KBD_KEY(KPSYMBOL, BooleanNot),
+  KBD_KEY(KPSYMBOL, BooleanAnd),
+  KBD_KEY(KPSYMBOL, BooleanOr),
+  KBD_KEY(KPSYMBOL, BooleanXor),
+
+  KBD_KEY(KPSYMBOL, Space),
+  KBD_KEY(KPSYMBOL, Period),
+  KBD_KEY(KPSYMBOL, Comma),
+  KBD_KEY(KPSYMBOL, Colon),
+  KBD_KEY(KPSYMBOL, At),
+  KBD_KEY(KPSYMBOL, Number),
+  KBD_KEY(KPSYMBOL, CurrencyUnit),
+  KBD_KEY(KPSYMBOL, CurrencySubunit),
+} KBD_KPSymbolKey;
+
+typedef enum {
+  KBD_KEY(KPACTION, Enter),
+  KBD_KEY(KPACTION, Backspace),
+  KBD_KEY(KPACTION, Tab),
+
+  KBD_KEY(KPACTION, Clear),
+  KBD_KEY(KPACTION, ClearEntry),
+
+  KBD_KEY(KPACTION, MemoryClear),
+  KBD_KEY(KPACTION, MemoryStore),
+  KBD_KEY(KPACTION, MemoryRecall),
+  KBD_KEY(KPACTION, MemoryAdd),
+  KBD_KEY(KPACTION, MemorySubtract),
+  KBD_KEY(KPACTION, MemoryMultiply),
+  KBD_KEY(KPACTION, MemoryDivide),
+
+  KBD_KEY(KPACTION, Binary),
+  KBD_KEY(KPACTION, Octal),
+  KBD_KEY(KPACTION, Decimal),
+  KBD_KEY(KPACTION, Hexadecimal),
+} KBD_KPActionKey;
+
+typedef enum {
+  KBD_KEY(BRAILLE, Space),
+
+  KBD_KEY(BRAILLE, Dot1),
+  KBD_KEY(BRAILLE, Dot2),
+  KBD_KEY(BRAILLE, Dot3),
+  KBD_KEY(BRAILLE, Dot4),
+  KBD_KEY(BRAILLE, Dot5),
+  KBD_KEY(BRAILLE, Dot6),
+  KBD_KEY(BRAILLE, Dot7),
+  KBD_KEY(BRAILLE, Dot8),
+
+  KBD_KEY(BRAILLE, Backward),
+  KBD_KEY(BRAILLE, Forward),
+} KBD_BrailleKey;
+
+extern KEY_NAME_TABLES_DECLARATION(keyboard);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_KEYBOARD */
diff --git a/Programs/ktb_list.c b/Programs/ktb_list.c
new file mode 100644
index 0000000..040051a
--- /dev/null
+++ b/Programs/ktb_list.c
@@ -0,0 +1,1034 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "utf8.h"
+#include "cmd.h"
+#include "brl_cmds.h"
+#include "ktb.h"
+#include "ktb_list.h"
+#include "ktb_cmds.h"
+
+struct CommandGroupHookDataStruct {
+  ListGenerationData *const lgd;
+  const KeyContext *const ctx;
+
+  int ok;
+};
+
+static inline void
+listCommandSubgroup (
+  int (*list) (ListGenerationData *lgd, const KeyContext *ctx),
+  CommandGroupHookData *cgh
+) {
+  cgh->ok = list(cgh->lgd, cgh->ctx);
+}
+
+static int
+handleCommandGroupHook (CommandGroupHook *handler, CommandGroupHookData *cgh) {
+  if (!handler) return 1;
+  cgh->ok = 0;
+  handler(cgh);
+  return cgh->ok;
+}
+
+static void *
+getMethodData (ListGenerationData *lgd) {
+  return lgd->list.internal? lgd: lgd->list.data;
+}
+
+static int
+writeHeader (ListGenerationData *lgd, const wchar_t *text, int level) {
+  return lgd->list.methods->writeHeader(text, level, getMethodData(lgd));
+}
+
+static int
+writeLine (ListGenerationData *lgd, const wchar_t *line) {
+  return lgd->list.writeLine(line, lgd->list.data);
+}
+
+static int
+writeBlankLine (ListGenerationData *lgd) {
+  return writeLine(lgd, WS_C(""));
+}
+
+static int
+addCharacters (ListGenerationData *lgd, const wchar_t *characters, size_t count) {
+  size_t newLength = lgd->line.length + count;
+
+  if (newLength > lgd->line.size) {
+    size_t newSize = (newLength | 0X3F) + 1;
+    wchar_t *newCharacters = realloc(lgd->line.characters, ARRAY_SIZE(newCharacters, newSize));
+
+    if (!newCharacters) {
+      logSystemError("realloc");
+      return 0;
+    }
+
+    lgd->line.characters = newCharacters;
+    lgd->line.size = newSize;
+  }
+
+  wmemcpy(&lgd->line.characters[lgd->line.length], characters, count);
+  lgd->line.length = newLength;
+  return 1;
+}
+
+static int
+putCharacters (ListGenerationData *lgd, const wchar_t *characters, size_t count) {
+  if (lgd->line.length == 0) {
+    if (lgd->list.elementLevel > 0) {
+      const unsigned int indent = 2;
+      const unsigned int count = indent * lgd->list.elementLevel;
+      wchar_t characters[count];
+
+      wmemset(characters, WC_C(' '), count);
+      characters[count - indent] = lgd->list.elementBullet;
+      lgd->list.elementBullet = WC_C(' ');
+
+      if (!addCharacters(lgd, characters, count)) return 0;
+    }
+  }
+
+  return addCharacters(lgd, characters, count);
+}
+
+static int
+putCharacter (ListGenerationData *lgd, wchar_t character) {
+  return putCharacters(lgd, &character, 1);
+}
+
+static int
+putCharacterString (ListGenerationData *lgd, const wchar_t *string) {
+  return putCharacters(lgd, string, wcslen(string));
+}
+
+static int
+putUtf8String (ListGenerationData *lgd, const char *string) {
+  size_t size = strlen(string) + 1;
+  wchar_t characters[size];
+  wchar_t *character = characters;
+
+  convertUtf8ToWchars(&string, &character, size);
+  return putCharacters(lgd, characters, character-characters);
+}
+
+static void
+clearLine (ListGenerationData *lgd) {
+  lgd->line.length = 0;
+}
+
+static void
+trimLine (ListGenerationData *lgd) {
+  while (lgd->line.length > 0) {
+    size_t last = lgd->line.length - 1;
+
+    if (lgd->line.characters[last] != WC_C(' ')) break;
+    lgd->line.length = last;
+  }
+}
+
+static int
+finishLine (ListGenerationData *lgd) {
+  trimLine(lgd);
+  if (!putCharacter(lgd, 0)) return 0;
+  return 1;
+}
+
+static int
+endLine (ListGenerationData *lgd) {
+  if (lgd->topicHeader) {
+    if (!writeHeader(lgd, lgd->topicHeader, 1)) return 0;
+    lgd->topicHeader = NULL;
+  }
+
+  if (lgd->listHeader) {
+    const char *string = lgd->listHeader;
+    lgd->listHeader = NULL;
+
+    size_t size = strlen(string) + 1;
+    wchar_t characters[size];
+    wchar_t *character = characters;
+
+    convertUtf8ToWchars(&string, &character, size);
+    if (!writeHeader(lgd, characters, 2)) return 0;
+  }
+
+  if (!finishLine(lgd)) return 0;
+  if (!writeLine(lgd, lgd->line.characters)) return 0;
+  clearLine(lgd);
+  return 1;
+}
+
+static int
+beginList (ListGenerationData *lgd, const char *header) {
+  lgd->listHeader = header;
+  return 1;
+}
+
+static int
+endList (ListGenerationData *lgd) {
+  return lgd->list.methods->endList(getMethodData(lgd));
+}
+
+static int
+beginElement (ListGenerationData *lgd, unsigned int level) {
+  return lgd->list.methods->beginElement(level, getMethodData(lgd));
+}
+
+static int
+searchKeyNameEntry (const void *target, const void *element) {
+  const KeyValue *value = target;
+  const KeyNameEntry *const *kne = element;
+  return compareKeyValues(value, &(*kne)->value);
+}
+
+const KeyNameEntry *
+findKeyNameEntry (KeyTable *table, const KeyValue *value) {
+  const KeyNameEntry *const *array = table->keyNames.table;
+  unsigned int count = table->keyNames.count;
+
+  const KeyNameEntry *const *kne = bsearch(value, array, count, sizeof(*array), searchKeyNameEntry);
+  if (!kne) return NULL;
+
+  while (kne > array) {
+    if (compareKeyValues(value, &(*--kne)->value) != 0) {
+      kne += 1;
+      break;
+    }
+  }
+
+  return *kne;
+}
+
+STR_BEGIN_FORMATTER(formatKeyName, KeyTable *table, const KeyValue *value)
+  const KeyNameEntry *kne = findKeyNameEntry(table, value);
+
+  if (kne) {
+    STR_PRINTF("%s", kne->name);
+  } else if (value->number != KTB_KEY_ANY) {
+    const KeyValue anyKey = {
+      .group = value->group,
+      .number = KTB_KEY_ANY
+    };
+
+    if ((kne = findKeyNameEntry(table, &anyKey))) {
+      STR_PRINTF("%s.%u", kne->name, value->number+1);
+    }
+  }
+
+  if (STR_LENGTH == 0) STR_PRINTF("?");
+STR_END_FORMATTER
+
+static int
+putKeyName (ListGenerationData *lgd, const KeyValue *value) {
+  char string[0X100];
+  formatKeyName(string, sizeof(string), lgd->keyTable, value);
+  return putUtf8String(lgd, string);
+}
+
+STR_BEGIN_FORMATTER(formatKeyCombination, KeyTable *table, const KeyCombination *combination)
+  char keyDelimiter = 0;
+  unsigned char dotCount = 0;
+
+  const char *dotPrefix = "dot";
+  const size_t dotPrefixLength = strlen(dotPrefix);
+  const size_t dotNameLength = dotPrefixLength + 1;
+
+  for (unsigned char index=0; index<combination->modifierCount; index+=1) {
+    char keyName[0X100];
+    formatKeyName(keyName, sizeof(keyName), table,
+                  &combination->modifierKeys[combination->modifierPositions[index]]);
+
+    if (strlen(keyName) == dotNameLength) {
+      if (strncasecmp(keyName, dotPrefix, dotPrefixLength) == 0) {
+        char dotNumber = keyName[dotPrefixLength];
+
+        if ((dotNumber >= '1') && (dotNumber <= '8')) {
+          if (++dotCount == 1) goto FIRST_DOT;
+
+          if (dotCount == 2) {
+            char firstDot = *STR_POP();
+            STR_PRINTF("s%c", firstDot);
+          }
+
+          STR_PRINTF("%c", dotNumber);
+          continue;
+        }
+      }
+    }
+
+    dotCount = 0;
+  FIRST_DOT:
+
+    if (keyDelimiter) {
+      STR_PRINTF("%c", keyDelimiter);
+    } else {
+      keyDelimiter = '+';
+    }
+
+    STR_PRINTF("%s", keyName);
+  }
+
+  if (combination->flags & KCF_IMMEDIATE_KEY) {
+    if (keyDelimiter) STR_PRINTF("%c", keyDelimiter);
+    STR_FORMAT(formatKeyName, table, &combination->immediateKey);
+  }
+STR_END_FORMATTER
+
+static int
+putKeyCombination (ListGenerationData *lgd, const KeyCombination *combination) {
+  char string[0X100];
+  formatKeyCombination(string, sizeof(string), lgd->keyTable, combination);
+  return putUtf8String(lgd, string);
+}
+
+static int
+putCommandDescription (ListGenerationData *lgd, const BoundCommand *cmd, int details) {
+  char description[0X60];
+
+  describeCommand(description, sizeof(description), cmd->value,
+                  (details? (CDO_IncludeOperand | CDO_DefaultOperand): 0));
+  return putUtf8String(lgd, description);
+}
+
+static int
+putKeyboardFunction (ListGenerationData *lgd, const KeyboardFunction *kbf) {
+  if (!beginElement(lgd, 1)) return 0;
+  if (!putCharacterString(lgd, WS_C("braille keyboard "))) return 0;
+  if (!putUtf8String(lgd, kbf->name)) return 0;
+  if (!putCharacterString(lgd, WS_C(": "))) return 0;
+  return 1;
+}
+
+static int
+listKeyboardFunctions (ListGenerationData *lgd, const KeyContext *ctx) {
+  if (ctx->mappedKeys.count > 0) {
+    for (unsigned int index=0; index<ctx->mappedKeys.count; index+=1) {
+      const MappedKeyEntry *map = &ctx->mappedKeys.table[index];
+
+      if (!(map->flags & MKF_HIDDEN)) {
+        const KeyboardFunction *kbf = map->keyboardFunction;
+
+        if (!putKeyboardFunction(lgd, kbf)) return 0;
+        if (!putKeyName(lgd, &map->keyValue)) return 0;
+        if (!endLine(lgd)) return 0;
+      }
+    }
+
+    {
+      const KeyboardFunction *kbf = keyboardFunctionTable;
+      const KeyboardFunction *end = kbf + keyboardFunctionCount;
+
+      while (kbf < end) {
+        if (ctx->mappedKeys.superimpose & kbf->bit) {
+          if (!putKeyboardFunction(lgd, kbf)) return 0;
+          if (!putCharacterString(lgd, WS_C("superimposed"))) return 0;
+          if (!endLine(lgd)) return 0;
+        }
+
+        kbf += 1;
+      }
+    }
+  }
+
+  return 1;
+}
+
+void
+commandGroupHook_keyboardFunctions (CommandGroupHookData *cgh) {
+  listCommandSubgroup(listKeyboardFunctions, cgh);
+}
+
+static int
+listHotkeyEvent (ListGenerationData *lgd, const KeyValue *keyValue, const char *event, const BoundCommand *cmd) {
+  if (cmd->value != BRL_CMD_NOOP) {
+    if (!beginElement(lgd, 1)) return 0;
+
+    if ((cmd->value & BRL_MSK_BLK) == BRL_CMD_BLK(CONTEXT)) {
+      const KeyContext *ctx = getKeyContext(lgd->keyTable, (KTB_CTX_DEFAULT + (cmd->value & BRL_MSK_ARG)));
+      if (!ctx) return 0;
+      if (!putUtf8String(lgd, "switch to ")) return 0;
+      if (!putCharacterString(lgd, ctx->title)) return 0;
+    } else {
+      if (!putCommandDescription(lgd, cmd, (keyValue->number != KTB_KEY_ANY))) return 0;
+    }
+
+    if (!putCharacterString(lgd, WS_C(": "))) return 0;
+    if (!putUtf8String(lgd, event)) return 0;
+    if (!putCharacter(lgd, WC_C(' '))) return 0;
+    if (!putKeyName(lgd, keyValue)) return 0;
+    if (!endLine(lgd)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+listHotkeys (ListGenerationData *lgd, const KeyContext *ctx) {
+  const HotkeyEntry *hotkey = ctx->hotkeys.table;
+  unsigned int count = ctx->hotkeys.count;
+
+  while (count) {
+    if (!(hotkey->flags & HKF_HIDDEN)) {
+      if (!listHotkeyEvent(lgd, &hotkey->keyValue, "press", &hotkey->pressCommand)) return 0;
+      if (!listHotkeyEvent(lgd, &hotkey->keyValue, "release", &hotkey->releaseCommand)) return 0;
+    }
+
+    hotkey += 1, count -= 1;
+  }
+
+  return 1;
+}
+
+void
+commandGroupHook_hotkeys (CommandGroupHookData *cgh) {
+  listCommandSubgroup(listHotkeys, cgh);
+}
+
+static int
+saveBindingLine (
+  ListGenerationData *lgd, size_t keysOffset,
+  const BoundCommand *command, const KeyBinding *binding
+) {
+  if (lgd->binding.count == lgd->binding.size) {
+    size_t newSize = lgd->binding.size? (lgd->binding.size << 1): 0X10;
+    BindingLine **newLines = realloc(lgd->binding.lines, ARRAY_SIZE(newLines, newSize));
+
+    if (!newLines) {
+      logMallocError();
+      return 0;
+    }
+
+    lgd->binding.lines = newLines;
+
+    while (lgd->binding.size < newSize) {
+      lgd->binding.lines[lgd->binding.size++] = NULL;
+    }
+  }
+
+  {
+    BindingLine *line;
+    size_t size = sizeof(*line) + (sizeof(line->text[0]) * lgd->line.length);
+
+    if (!(line = malloc(size))) {
+      logMallocError();
+      return 0;
+    }
+
+    line->command = command;
+    line->keyCombination = &binding->keyCombination;
+    line->keysOffset = keysOffset;
+    wmemcpy(line->text, lgd->line.characters, (line->length = lgd->line.length));
+    lgd->binding.lines[lgd->binding.count++] = line;
+  }
+
+  clearLine(lgd);
+  return 1;
+}
+
+static void
+removeBindingLine (ListGenerationData *lgd, int index) {
+  {
+    BindingLine *bl = lgd->binding.lines[index];
+
+    free(bl);
+  }
+
+  if (--lgd->binding.count > index) {
+    memmove(&lgd->binding.lines[index], &lgd->binding.lines[index+1],
+            ((lgd->binding.count - index) * sizeof(*lgd->binding.lines)));
+  }
+}
+
+static void
+removeBindingLines (ListGenerationData *lgd) {
+  size_t *count = &lgd->binding.count;
+
+  while (*count > 0) removeBindingLine(lgd, (*count - 1));
+}
+
+static int
+sortBindingLines (const void *element1, const void *element2) {
+  const BindingLine *const *line1 = element1;
+  const BindingLine *const *line2 = element2;
+
+  int command1 = (*line1)->command->value;
+  int command2 = (*line2)->command->value;
+
+  int cmd1 = command1 & BRL_MSK_CMD;
+  int cmd2 = command2 & BRL_MSK_CMD;
+  if (cmd1 < cmd2) return -1;
+  if (cmd1 > cmd2) return 1;
+
+  if (command1 < command2) return -1;
+  if (command1 > command2) return 1;
+
+  const KeyCombination *combination1 = (*line1)->keyCombination;
+  const KeyCombination *combination2 = (*line2)->keyCombination;
+  if (combination1->anyKeyCount < combination2->anyKeyCount) return -1;
+  if (combination1->anyKeyCount > combination2->anyKeyCount) return 1;
+
+  if (combination1 < combination2) return -1;
+  if (combination1 > combination2) return 1;
+  return 0;
+}
+
+static int
+listBindingLine (ListGenerationData *lgd, int index, int *isSame) {
+  const BindingLine *bl = lgd->binding.lines[index];
+  int asList = *isSame;
+
+  if (*isSame) {
+    *isSame = 0;
+  } else {
+    if (!beginElement(lgd, 1)) return 0;
+    if (!putCharacters(lgd, bl->text, bl->keysOffset)) return 0;
+  }
+
+  {
+    int next = index + 1;
+
+    if (next < lgd->binding.count) {
+      const BindingLine *nl = lgd->binding.lines[next];
+
+      if (bl->command->value == nl->command->value) {
+        if (bl->keyCombination->anyKeyCount == nl->keyCombination->anyKeyCount) {
+          if (!asList) {
+            if (!endLine(lgd)) return 0;
+          }
+
+          asList = 1;
+          *isSame = 1;
+        }
+      }
+    }
+  }
+
+  if (asList) {
+    if (!beginElement(lgd, 2)) return 0;
+  }
+
+  if (!putCharacters(lgd, &bl->text[bl->keysOffset], (bl->length - bl->keysOffset))) return 0;
+  if (!endLine(lgd)) return 0;
+
+  removeBindingLine(lgd, index);
+  return 1;
+}
+
+static int
+listBindingLines (ListGenerationData *lgd, const KeyContext *ctx) {
+  if (lgd->binding.count > 0) {
+    qsort(lgd->binding.lines, lgd->binding.count,
+          sizeof(*lgd->binding.lines), sortBindingLines);
+
+    {
+      const CommandGroupEntry *grp = commandGroupTable;
+      const CommandGroupEntry *grpEnd = grp + commandGroupCount;
+
+      while (grp < grpEnd) {
+        const CommandListEntry *cmd = grp->commands.table;
+        const CommandListEntry *cmdEnd = cmd + grp->commands.count;
+
+        CommandGroupHookData cgh = {
+          .lgd = lgd,
+          .ctx = ctx
+        };
+
+        if (!beginList(lgd, grp->name)) return 0;
+        if (!handleCommandGroupHook(grp->before, &cgh)) return 0;
+
+        while (cmd < cmdEnd) {
+          int first = 0;
+          int last = lgd->binding.count - 1;
+
+          while (first <= last) {
+            int current = (first + last) / 2;
+            const BindingLine *bl = lgd->binding.lines[current];
+            int command = bl->command->value & BRL_MSK_CMD;
+
+            if (command < cmd->code) {
+              first = current + 1;
+            } else {
+              last = current - 1;
+            }
+          }
+
+          int isSame = 0;
+          int current = cmd->code;
+
+          // Binding lines are removed as they're processed, so, even though
+          // first isn't being incremented, the count will be decremented.
+          while (first < lgd->binding.count) {
+            const BindingLine *bl = lgd->binding.lines[first];
+            int next = bl->command->value;
+
+            if ((next & BRL_MSK_CMD) != current) {
+              int blk = next & BRL_MSK_BLK;
+              if (!blk) break;
+              if (blk != (current & BRL_MSK_BLK)) break;
+            }
+
+            if (!listBindingLine(lgd, first, &isSame)) return 0;
+          }
+
+          cmd += 1;
+        }
+
+        if (!handleCommandGroupHook(grp->after, &cgh)) return 0;
+        if (!endList(lgd)) return 0;
+        grp += 1;
+      }
+
+      {
+        int isSame = 0;
+
+        if (!beginList(lgd, "Uncategorized Bindings")) return 0;
+
+        while (lgd->binding.count > 0) {
+          if (!listBindingLine(lgd, 0, &isSame)) return 0;
+        }
+
+        if (!endList(lgd)) return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int listKeyBindings (ListGenerationData *lgd, const KeyContext *ctx, const wchar_t *keysPrefix);
+
+static int
+listKeyBinding (ListGenerationData *lgd, const KeyBinding *binding, int longPress, const wchar_t *keysPrefix) {
+  const BoundCommand *cmd = longPress? &binding->secondaryCommand: &binding->primaryCommand;
+
+  if (cmd->value == BRL_CMD_NOOP) return 1;
+
+  if (!putCommandDescription(lgd, cmd, !binding->keyCombination.anyKeyCount)) return 0;
+  if (!putCharacterString(lgd, WS_C(": "))) return 0;
+  size_t keysOffset = lgd->line.length;
+
+  if (keysPrefix) {
+    if (!putCharacterString(lgd, keysPrefix)) return 0;
+    if (!putCharacterString(lgd, WS_C(", "))) return 0;
+  }
+
+  if (longPress) {
+    if (!putCharacterString(lgd, WS_C("long "))) return 0;
+  }
+
+  if (!putKeyCombination(lgd, &binding->keyCombination)) return 0;
+
+  if ((cmd->value & BRL_MSK_BLK) == BRL_CMD_BLK(CONTEXT)) {
+    const KeyContext *ctx = getKeyContext(lgd->keyTable, (KTB_CTX_DEFAULT + (cmd->value & BRL_MSK_ARG)));
+    if (!ctx) return 0;
+
+    {
+      size_t length = lgd->line.length - keysOffset;
+      wchar_t keys[length + 1];
+
+      wmemcpy(keys, &lgd->line.characters[keysOffset], length);
+      keys[length] = 0;
+      clearLine(lgd);
+
+      if (isTemporaryKeyContext(lgd->keyTable, ctx)) {
+        if (!listKeyBindings(lgd, ctx, keys)) return 0;
+      } else {
+        if (!putCharacterString(lgd, WS_C("switch to "))) return 0;
+        if (!putCharacterString(lgd, ctx->title)) return 0;
+        if (!putCharacterString(lgd, WS_C(": "))) return 0;
+        keysOffset = lgd->line.length;
+        if (!putCharacterString(lgd, keys)) return 0;
+        if (!saveBindingLine(lgd, keysOffset, cmd, binding)) return 0;
+      }
+    }
+  } else {
+    if (!saveBindingLine(lgd, keysOffset, cmd, binding)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+listKeyBindings (ListGenerationData *lgd, const KeyContext *ctx, const wchar_t *keysPrefix) {
+  const KeyBinding *binding = ctx->keyBindings.table;
+  unsigned int count = ctx->keyBindings.count;
+
+  while (count) {
+    if (!(binding->flags & KBF_HIDDEN)) {
+      if (!listKeyBinding(lgd, binding, 0, keysPrefix)) return 0;
+      if (!listKeyBinding(lgd, binding, 1, keysPrefix)) return 0;
+    }
+
+    binding += 1, count -= 1;
+  }
+
+  return 1;
+}
+
+static int
+listKeyContext (ListGenerationData *lgd, const KeyContext *ctx) {
+  lgd->topicHeader = ctx->title;
+  if (!listKeyBindings(lgd, ctx, NULL)) return 0;
+  if (!listBindingLines(lgd, ctx)) return 0;
+  return 1;
+}
+
+static int
+listSpecialKeyContexts (ListGenerationData *lgd) {
+  static const unsigned char contexts[] = {
+    KTB_CTX_DEFAULT,
+    KTB_CTX_MENU
+  };
+
+  const unsigned char *context = contexts;
+  unsigned int count = ARRAY_COUNT(contexts);
+
+  while (count) {
+    const KeyContext *ctx = getKeyContext(lgd->keyTable, *context);
+
+    if (ctx) {
+      if (!listKeyContext(lgd, ctx)) return 0;
+    }
+
+    context += 1, count -= 1;
+  }
+
+  return 1;
+}
+
+static int
+listPersistentKeyContexts (ListGenerationData *lgd) {
+  for (unsigned int context=KTB_CTX_DEFAULT+1; context<lgd->keyTable->keyContexts.count; context+=1) {
+    const KeyContext *ctx = getKeyContext(lgd->keyTable, context);
+
+    if (ctx && !isTemporaryKeyContext(lgd->keyTable, ctx)) {
+      if (!listKeyContext(lgd, ctx)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+listKeyTableTitle (ListGenerationData *lgd) {
+  if (!putUtf8String(lgd, gettext("Key Table"))) return 0;
+
+  if (lgd->keyTable->title) {
+    if (!putCharacterString(lgd, WS_C(": "))) return 0;
+    if (!putCharacterString(lgd, lgd->keyTable->title)) return 0;
+
+    if (!finishLine(lgd)) return 0;
+    if (!writeHeader(lgd, lgd->line.characters, 0)) return 0;
+    clearLine(lgd);
+  }
+
+  return 1;
+}
+
+static int
+listKeyTableNotes (ListGenerationData *lgd) {
+  if (!beginList(lgd, "Notes")) return 0;
+
+  for (unsigned int noteIndex=0; noteIndex<lgd->keyTable->notes.count; noteIndex+=1) {
+    const wchar_t *line = lgd->keyTable->notes.table[noteIndex];
+    unsigned int level;
+    int prefixed;
+
+    if (*line == WC_C('*')) {
+      level = 0;
+      prefixed = 1;
+    } else if (*line == WC_C('+')) {
+      level = 2;
+      prefixed = 1;
+    } else {
+      level = 1;
+      prefixed = 0;
+    }
+
+    if (level > 0) {
+      if (!beginElement(lgd, level)) return 0;
+    }
+
+    if (prefixed) {
+      line += 1;
+      while (iswspace(*line)) line += 1;
+    }
+
+    if (!putCharacterString(lgd, line)) return 0;
+    if (!endLine(lgd)) return 0;
+  }
+
+  if (!endList(lgd)) return 0;
+  return 1;
+}
+
+static int
+listCommandMacros (ListGenerationData *lgd) {
+  unsigned int count = lgd->keyTable->commandMacros.count;
+
+  if (count > 0) {
+    lgd->topicHeader = WS_C("Command Macros");
+
+    const CommandMacro *macro = lgd->keyTable->commandMacros.table;
+    const CommandMacro *endMacros = macro + count;
+    unsigned int number = 0;
+
+    while (macro < endMacros) {
+      {
+        char buffer[0X100];
+        snprintf(buffer, sizeof(buffer), "Command Macro #%u:", ++number);
+        if (!putUtf8String(lgd, buffer)) return 0;
+      }
+
+      {
+        const BoundCommand *command = macro->commands;
+        const BoundCommand *endCommands = command + macro->count;
+
+        while (command < endCommands) {
+          if (!putCharacter(lgd, WC_C(' '))) return 0;
+          if (!putUtf8String(lgd, command->entry->name)) return 0;
+          command += 1;
+        }
+      }
+
+      if (!endLine(lgd)) return 0;
+      macro += 1;
+    }
+
+    if (!endLine(lgd)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+listHostCommands (ListGenerationData *lgd) {
+  unsigned int count = lgd->keyTable->hostCommands.count;
+
+  if (count > 0) {
+    lgd->topicHeader = WS_C("Host Commands");
+
+    const HostCommand *hc = lgd->keyTable->hostCommands.table;
+    const HostCommand *end = hc + count;
+    unsigned int number = 0;
+
+    while (hc < end) {
+      {
+        char buffer[0X100];
+        snprintf(buffer, sizeof(buffer), "Host Command #%u:", ++number);
+        if (!putUtf8String(lgd, buffer)) return 0;
+      }
+
+      {
+        char **argument = hc->arguments;
+
+        while (*argument) {
+          if (!putCharacter(lgd, WC_C(' '))) return 0;
+          if (!putUtf8String(lgd, *argument)) return 0;
+          argument += 1;
+        }
+      }
+
+      if (!endLine(lgd)) return 0;
+      hc += 1;
+    }
+
+    if (!endLine(lgd)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+listKeyTableSections (ListGenerationData *lgd) {
+  typedef int Lister (ListGenerationData *lgd);
+
+  static Lister *const listerTable[] = {
+    listKeyTableTitle,
+    listKeyTableNotes,
+    listCommandMacros,
+    listHostCommands,
+    listSpecialKeyContexts,
+    listPersistentKeyContexts
+  };
+
+  Lister *const *lister = listerTable;
+  Lister *const *end = lister + ARRAY_COUNT(listerTable);
+
+  while (lister < end) {
+    if (!(*lister)(lgd)) return 0;
+    lister += 1;
+  }
+
+  return 1;
+}
+
+static int
+internalWriteHeader (const wchar_t *text, unsigned int level, void *data) {
+  static const wchar_t characters[] = {WC_C('='), WC_C('-')};
+  ListGenerationData *lgd = data;
+
+  if (!writeLine(lgd, text)) return 0;
+
+  if (level < ARRAY_COUNT(characters)) {
+    size_t length = wcslen(text);
+    wchar_t underline[length + 1];
+
+    wmemset(underline, characters[level], length);
+    underline[length] = 0;
+
+    if (!writeLine(lgd, underline)) return 0;
+    if (!writeBlankLine(lgd)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+internalBeginElement (unsigned int level, void *data) {
+  ListGenerationData *lgd = data;
+
+  static const wchar_t bullets[] = {
+    WC_C('*'),
+    WC_C('+'),
+    WC_C('-')
+  };
+
+  lgd->list.elementLevel = level;
+  lgd->list.elementBullet = bullets[level - 1];
+  return 1;
+}
+
+static int
+internalEndList (void *data) {
+  ListGenerationData *lgd = data;
+
+  if (lgd->list.elementLevel > 0) {
+    lgd->list.elementLevel = 0;
+    lgd->listHeader = NULL;
+    if (!writeBlankLine(lgd)) return 0;
+  }
+
+  return 1;
+}
+
+int
+listKeyTable (KeyTable *table, const KeyTableListMethods *methods, KeyTableWriteLineMethod *writeLine, void *data) {
+  static const KeyTableListMethods internalMethods = {
+    .writeHeader = internalWriteHeader,
+    .beginElement = internalBeginElement,
+    .endList = internalEndList
+  };
+
+  ListGenerationData lgd = {
+    .keyTable = table,
+
+    .topicHeader = NULL,
+    .listHeader = NULL,
+
+    .line = {
+      .characters = NULL,
+      .size = 0,
+      .length = 0,
+    },
+
+    .list = {
+      .methods = methods? methods: &internalMethods,
+      .writeLine = writeLine,
+      .data = data,
+      .internal = !methods,
+
+      .elementLevel = 0
+    },
+
+    .binding = {
+      .lines = NULL,
+      .size = 0,
+      .count = 0
+    }
+  };
+
+  int result = listKeyTableSections(&lgd);
+
+  if (lgd.binding.lines) {
+    removeBindingLines(&lgd);
+    free(lgd.binding.lines);
+  }
+
+  if (lgd.line.characters) free(lgd.line.characters);
+  return result;
+}
+
+int
+forEachKeyName (KEY_NAME_TABLES_REFERENCE keys, KeyNameEntryHandler *handleKeyNameEntry, void *data) {
+  const KeyNameEntry *const *knt = keys;
+
+  while (*knt) {
+    const KeyNameEntry *kne = *knt;
+
+    if (knt != keys) {
+      if (!handleKeyNameEntry(NULL, data)) {
+        return 0;
+      }
+    }
+
+    while (kne->name) {
+      if (!handleKeyNameEntry(kne, data)) return 0;
+      kne += 1;
+    }
+
+    knt += 1;
+  }
+
+  return 1;
+}
+
+typedef struct {
+  KeyTableWriteLineMethod *const writeLine;
+  void *const data;
+} ListKeyNameData;
+
+static int
+listKeyName (const KeyNameEntry *kne, void *data) {
+  const ListKeyNameData *lkn = data;
+  const char *name = kne? kne->name: "";
+  size_t size = strlen(name) + 1;
+  wchar_t characters[size];
+  wchar_t *character = characters;
+
+  convertUtf8ToWchars(&name, &character, size);
+  return lkn->writeLine(characters, lkn->data);
+}
+
+int
+listKeyNames (KEY_NAME_TABLES_REFERENCE keys, KeyTableWriteLineMethod *writeLine, void *data) {
+  ListKeyNameData lkn = {
+    .writeLine = writeLine,
+    .data = data
+  };
+
+  return forEachKeyName(keys, listKeyName, &lkn);
+}
diff --git a/Programs/ktb_list.h b/Programs/ktb_list.h
new file mode 100644
index 0000000..8876c1b
--- /dev/null
+++ b/Programs/ktb_list.h
@@ -0,0 +1,70 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_KTB_LIST
+#define BRLTTY_INCLUDED_KTB_LIST
+
+#include "ktb_internal.h"
+#include "ktb_inspect.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const BoundCommand *command;
+  const KeyCombination *keyCombination;
+  size_t keysOffset;
+  size_t length;
+  wchar_t text[0];
+} BindingLine;
+
+typedef struct {
+  KeyTable *const keyTable;
+
+  const wchar_t *topicHeader;
+  const char *listHeader;
+
+  struct {
+    wchar_t *characters;
+    size_t size;
+    size_t length;
+  } line;
+
+  struct {
+    const KeyTableListMethods *const methods;
+    KeyTableWriteLineMethod *const writeLine;
+    void *const data;
+    const unsigned internal:1;
+
+    unsigned int elementLevel;
+    wchar_t elementBullet;
+  } list;
+
+  struct {
+    BindingLine **lines;
+    size_t size;
+    size_t count;
+  } binding;
+} ListGenerationData;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_KTB_LIST */
diff --git a/Programs/ktb_translate.c b/Programs/ktb_translate.c
new file mode 100644
index 0000000..31b54c2
--- /dev/null
+++ b/Programs/ktb_translate.c
@@ -0,0 +1,924 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "alert.h"
+#include "prefs.h"
+#include "unicode.h"
+#include "ktb.h"
+#include "ktb_internal.h"
+#include "ktb_inspect.h"
+#include "brl_types.h"
+#include "brl_cmds.h"
+#include "cmd.h"
+#include "cmd_enqueue.h"
+#include "async_alarm.h"
+#include "hostcmd.h"
+
+#define BRL_CMD_ALERT(alert) BRL_CMD_ARG(ALERT, ALERT_##alert)
+
+ASYNC_ALARM_CALLBACK(handleKeyAutoreleaseAlarm) {
+  KeyTable *table = parameters->data;
+
+  asyncDiscardHandle(table->autorelease.alarm);
+  table->autorelease.alarm = NULL;
+
+  for (unsigned int index=0; index<table->pressedKeys.count; index+=1) {
+    const KeyValue *kv = &table->pressedKeys.table[index];
+
+    char key[0X40];
+    STR_BEGIN(key, sizeof(key));
+
+    STR_FORMAT(formatKeyName, table, kv);
+    STR_PRINTF(" (Grp:%u Num:%u)", kv->group, kv->number);
+
+    STR_END;
+    logMessage(LOG_WARNING, "autoreleasing key: %s", key);
+  }
+
+  resetKeyTable(table);
+  alert(ALERT_KEYS_AUTORELEASED);
+}
+
+static void
+cancelAutoreleaseAlarm (KeyTable *table) {
+  if (table->autorelease.alarm) {
+    asyncCancelRequest(table->autorelease.alarm);
+    table->autorelease.alarm = NULL;
+  }
+}
+
+static void
+setAutoreleaseAlarm (KeyTable *table) {
+  if (!table->autorelease.time || !table->pressedKeys.count) {
+    cancelAutoreleaseAlarm(table);
+  } else if (table->autorelease.alarm) {
+    asyncResetAlarmIn(table->autorelease.alarm, table->autorelease.time);
+  } else {
+    asyncNewRelativeAlarm(&table->autorelease.alarm, table->autorelease.time,
+                          handleKeyAutoreleaseAlarm, table);
+  }
+}
+
+void
+setKeyAutoreleaseTime (KeyTable *table, unsigned char setting) {
+  table->autorelease.time = setting? (5000 << (setting - 1)): 0;
+  setAutoreleaseAlarm(table);
+}
+
+static int
+sortModifierKeys (const void *element1, const void *element2) {
+  const KeyValue *modifier1 = element1;
+  const KeyValue *modifier2 = element2;
+  return compareKeyValues(modifier1, modifier2);
+}
+
+static int
+searchKeyBinding (const void *target, const void *element) {
+  const KeyBinding *reference = target;
+  const KeyBinding *binding = element;
+  return compareKeyBindings(reference, binding);
+}
+
+static const KeyBinding *
+findKeyBinding (KeyTable *table, unsigned char context, const KeyValue *immediate, int *isIncomplete) {
+  const KeyContext *ctx = getKeyContext(table, context);
+
+  if (!ctx) return NULL;
+  if (!ctx->keyBindings.table) return NULL;
+  if (table->pressedKeys.count > MAX_MODIFIERS_PER_COMBINATION) return NULL;
+
+  KeyBinding target = {
+    .keyCombination.modifierCount = table->pressedKeys.count
+  };
+
+  if (immediate) {
+    target.keyCombination.immediateKey = *immediate;
+    target.keyCombination.flags |= KCF_IMMEDIATE_KEY;
+  }
+
+  while (1) {
+    unsigned int all = (1 << table->pressedKeys.count) - 1;
+
+    for (unsigned int bits=0; bits<=all; bits+=1) {
+      {
+        unsigned int index;
+        unsigned int bit;
+
+        for (index=0, bit=1; index<table->pressedKeys.count; index+=1, bit<<=1) {
+          KeyValue *modifier = &target.keyCombination.modifierKeys[index];
+
+          *modifier = table->pressedKeys.table[index];
+          if (bits & bit) modifier->number = KTB_KEY_ANY;
+        }
+      }
+
+      qsort(
+        target.keyCombination.modifierKeys, table->pressedKeys.count,
+        sizeof(*target.keyCombination.modifierKeys), sortModifierKeys
+      );
+
+      {
+        const KeyBinding *binding = bsearch(&target, ctx->keyBindings.table,
+                                            ctx->keyBindings.count,
+                                            sizeof(*ctx->keyBindings.table),
+                                            searchKeyBinding);
+
+        if (binding) {
+          if (binding->primaryCommand.value != EOF) return binding;
+          *isIncomplete = 1;
+        }
+      }
+    }
+
+    if (!(target.keyCombination.flags & KCF_IMMEDIATE_KEY)) break;
+    if (target.keyCombination.immediateKey.number == KTB_KEY_ANY) break;
+    target.keyCombination.immediateKey.number = KTB_KEY_ANY;
+  }
+
+  return NULL;
+}
+
+static int
+searchHotkeyEntry (const void *target, const void *element) {
+  const HotkeyEntry *reference = target;
+  const HotkeyEntry *hotkey = element;
+  return compareHotkeyEntries(reference, hotkey);
+}
+
+static const HotkeyEntry *
+findHotkeyEntry (KeyTable *table, unsigned char context, const KeyValue *keyValue) {
+  const KeyContext *ctx = getKeyContext(table, context);
+
+  if (!ctx) return NULL;
+  if (!ctx->hotkeys.table) return NULL;
+
+  HotkeyEntry target = {
+    .keyValue = *keyValue
+  };
+
+  return bsearch(&target, ctx->hotkeys.table, ctx->hotkeys.count,
+                 sizeof(*ctx->hotkeys.table), searchHotkeyEntry);
+}
+
+static int
+searchMappedKeyEntry (const void *target, const void *element) {
+  const MappedKeyEntry *reference = target;
+  const MappedKeyEntry *map = element;
+  return compareMappedKeyEntries(reference, map);
+}
+
+static const MappedKeyEntry *
+findMappedKeyEntry (const KeyContext *ctx, const KeyValue *keyValue) {
+  if (!ctx) return NULL;
+  if (!ctx->mappedKeys.table) return NULL;
+
+  MappedKeyEntry target = {
+    .keyValue = *keyValue
+  };
+
+  return bsearch(&target, ctx->mappedKeys.table, ctx->mappedKeys.count,
+                 sizeof(*ctx->mappedKeys.table), searchMappedKeyEntry);
+}
+
+static int
+makeKeyboardCommand (KeyTable *table, unsigned char context, int allowChords) {
+  const KeyContext *ctx;
+
+  if ((ctx = getKeyContext(table, context))) {
+    int bits = 0;
+
+    for (unsigned int pressedIndex=0; pressedIndex<table->pressedKeys.count; pressedIndex+=1) {
+      const KeyValue *keyValue = &table->pressedKeys.table[pressedIndex];
+      const MappedKeyEntry *map = findMappedKeyEntry(ctx, keyValue);
+
+      if (!map) return EOF;
+      bits |= map->keyboardFunction->bit;
+    }
+
+    {
+      int space = bits & BRL_DOTC;
+      int dots = bits & BRL_ALL_DOTS;
+
+      if (!allowChords) {
+        if (!space == !dots) return EOF;
+        bits &= ~BRL_DOTC;
+      }
+
+      if (dots) bits |= ctx->mappedKeys.superimpose;
+    }
+
+    return BRL_CMD_BLK(PASSDOTS) | bits;
+  }
+
+  return EOF;
+}
+
+static int
+findPressedKey (KeyTable *table, const KeyValue *value, unsigned int *position) {
+  return findKeyValue(table->pressedKeys.table, table->pressedKeys.count, value, position);
+}
+
+static int
+insertPressedKey (KeyTable *table, const KeyValue *value, unsigned int position) {
+  return insertKeyValue(&table->pressedKeys.table, &table->pressedKeys.count, &table->pressedKeys.size, value, position);
+}
+
+static void
+removePressedKey (KeyTable *table, unsigned int position) {
+  removeKeyValue(table->pressedKeys.table, &table->pressedKeys.count, position);
+}
+
+static inline void
+deleteExplicitKeyValue (KeyValue *values, unsigned int *count, const KeyValue *value) {
+  if (value->number != KTB_KEY_ANY) deleteKeyValue(values, count, value);
+}
+
+static int
+sortKeyOffsets (const void *element1, const void *element2) {
+  const KeyValue *value1 = element1;
+  const KeyValue *value2 = element2;
+
+  if (value1->number < value2->number) return -1;
+  if (value1->number > value2->number) return 1;
+  return 0;
+}
+
+static void
+addCommandArguments (KeyTable *table, int *command, const CommandEntry *entry, const KeyBinding *binding) {
+  if (entry->isOffset | entry->isColumn | entry->isRow | entry->isRange | entry->isKeyboard) {
+    unsigned int keyCount = table->pressedKeys.count;
+    KeyValue keyValues[keyCount];
+    copyKeyValues(keyValues, table->pressedKeys.table, keyCount);
+
+    {
+      int index;
+
+      for (index=0; index<binding->keyCombination.modifierCount; index+=1) {
+        deleteExplicitKeyValue(keyValues, &keyCount, &binding->keyCombination.modifierKeys[index]);
+      }
+    }
+
+    if (binding->keyCombination.flags & KCF_IMMEDIATE_KEY) {
+      deleteExplicitKeyValue(keyValues, &keyCount, &binding->keyCombination.immediateKey);
+    }
+
+    if (keyCount > 0) {
+      if (keyCount > 1) {
+        qsort(keyValues, keyCount, sizeof(*keyValues), sortKeyOffsets);
+        if (entry->isRange) *command |= BRL_EXT_PUT(keyValues[1].number);
+      }
+
+      *command += keyValues[0].number;
+    } else if (entry->isColumn) {
+      if (!entry->isRouting) *command |= BRL_MSK_ARG;
+    }
+  }
+}
+
+static int
+isInputKey (BRL_Key key) {
+  switch (key) {
+    case BRL_KEY_BACKSPACE:
+    case BRL_KEY_DELETE:
+    case BRL_KEY_ESCAPE:
+    case BRL_KEY_TAB:
+    case BRL_KEY_ENTER:
+      return 1;
+
+    default:
+      return 0;
+  }
+}
+
+static int
+processCommand (KeyTable *table, int command) {
+  int isInput = 0;
+
+  switch (command) {
+    default: {
+      int arg = command & BRL_MSK_ARG;
+
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(CONTEXT): {
+          unsigned char context = KTB_CTX_DEFAULT + arg;
+          const KeyContext *ctx = getKeyContext(table, context);
+
+          if (ctx) {
+            table->context.next = context;
+
+            if (isTemporaryKeyContext(table, ctx)) {
+              command = BRL_CMD_ALERT(CONTEXT_TEMPORARY);
+            } else {
+              table->context.persistent = context;
+              command =
+                (context == KTB_CTX_DEFAULT)?
+                BRL_CMD_ALERT(CONTEXT_DEFAULT):
+                BRL_CMD_ALERT(CONTEXT_PERSISTENT);
+            }
+
+            if (prefs.speakKeyContext) {
+              if (ctx->title) {
+                speakAlertText(ctx->title);
+              } else {
+                const wchar_t *name = ctx->name;
+                const wchar_t *from = name;
+
+                wchar_t text[(wcslen(name) * 2) + 1];
+                wchar_t *to = text;
+
+                while (*from) {
+                  wchar_t character = *from++;
+
+                  if (iswupper(character)) {
+                    if (to != text) {
+                      *to++ = WC_C(' ');
+                    }
+                  }
+
+                  *to++ = character;
+                }
+
+                *to = 0;
+                speakAlertText(text);
+              }
+            } else if (!enqueueCommand(command)) {
+              return 0;
+            }
+
+            command = BRL_CMD_NOOP;
+          }
+
+          break;
+        }
+
+        case BRL_CMD_BLK(MACRO): {
+          if (arg < table->commandMacros.count) {
+            const CommandMacro *macro = &table->commandMacros.table[arg];
+            const BoundCommand *cmd = macro->commands;
+            const BoundCommand *end = cmd + macro->count;
+
+            while (cmd < end) {
+              if (!processCommand(table, (cmd++)->value)) return 0;
+            }
+          }
+
+          command = BRL_CMD_NOOP;
+          break;
+        }
+
+        case BRL_CMD_BLK(HOSTCMD): {
+          if (arg < table->hostCommands.count) {
+            const HostCommand *hc = &table->hostCommands.table[arg];
+
+            HostCommandOptions options;
+            initializeHostCommandOptions(&options);
+            options.asynchronous = 1;
+
+            runHostCommand((const char *const *)hc->arguments, &options);
+          }
+
+          command = BRL_CMD_NOOP;
+          break;
+        }
+
+        case BRL_CMD_BLK(PASSDOTS):
+          switch (prefs.brailleTypingMode) {
+            case BRL_TYPING_DOTS: {
+              wchar_t character = UNICODE_BRAILLE_ROW | arg;
+              int flags = command & BRL_MSK_FLG;
+              command = BRL_CMD_BLK(PASSCHAR) | BRL_ARG_SET(character) | flags;
+              break;
+            }
+          }
+
+          isInput = 1;
+          break;
+
+        case BRL_CMD_BLK(PASSCHAR):
+          isInput = 1;
+          break;
+
+        case BRL_CMD_BLK(PASSKEY):
+          if (isInputKey(arg)) isInput = 1;
+          break;
+
+        default:
+          break;
+      }
+
+      break;
+    }
+  }
+
+  if (isInput) {
+    if (table->options.keyboardEnabledFlag && !*table->options.keyboardEnabledFlag) {
+      command = BRL_CMD_ALERT(COMMAND_REJECTED);
+    }
+  }
+
+  return enqueueCommand(command);
+}
+
+static void
+logKeyEvent (
+  KeyTable *table, const char *action,
+  unsigned char context, const KeyValue *keyValue, int command
+) {
+  if (table->options.logKeyEventsFlag && *table->options.logKeyEventsFlag) {
+    char buffer[0X100];
+
+    STR_BEGIN(buffer, sizeof(buffer));
+    if (table->options.logLabel) STR_PRINTF("%s ", table->options.logLabel);
+    STR_PRINTF("key %s: ", action);
+    STR_FORMAT(formatKeyName, table, keyValue);
+    STR_PRINTF(" (Ctx:%u Grp:%u Num:%u)", context, keyValue->group, keyValue->number);
+
+    if (command != EOF) {
+      const CommandEntry *cmd = findCommandEntry(command);
+      const char *name = cmd? cmd->name: "?";
+
+      STR_PRINTF(" -> %s (Cmd:%06X)", name, command);
+    }
+
+    STR_END;
+    logMessage(categoryLogLevel, "%s", buffer);
+  }
+}
+
+static void setLongPressAlarm (KeyTable *table, unsigned char when);
+
+ASYNC_ALARM_CALLBACK(handleLongPressAlarm) {
+  KeyTable *table = parameters->data;
+  int command = table->longPress.command;
+
+  asyncDiscardHandle(table->longPress.alarm);
+  table->longPress.alarm = NULL;
+
+  logKeyEvent(table, table->longPress.keyAction,
+              table->longPress.keyContext,
+              &table->longPress.keyValue,
+              command);
+
+  if (table->longPress.repeat) {
+    table->longPress.keyAction = "repeat";
+    setLongPressAlarm(table, prefs.autorepeatInterval);
+  }
+
+  table->release.command = BRL_CMD_NOOP;
+  processCommand(table, command);
+}
+
+static void
+setLongPressAlarm (KeyTable *table, unsigned char when) {
+  asyncNewRelativeAlarm(&table->longPress.alarm, PREFS2MSECS(when),
+                        handleLongPressAlarm, table);
+}
+
+static int
+isRepeatableCommand (int command) {
+  if (prefs.autorepeatEnabled) {
+    switch (command & BRL_MSK_BLK) {
+      case BRL_CMD_BLK(PASSCHAR):
+      case BRL_CMD_BLK(PASSDOTS):
+        return 1;
+
+      default:
+        switch (command & BRL_MSK_CMD) {
+          case BRL_CMD_LNUP:
+          case BRL_CMD_LNDN:
+          case BRL_CMD_PRDIFLN:
+          case BRL_CMD_NXDIFLN:
+          case BRL_CMD_CHRLT:
+          case BRL_CMD_CHRRT:
+
+          case BRL_CMD_MENU_PREV_ITEM:
+          case BRL_CMD_MENU_NEXT_ITEM:
+          case BRL_CMD_MENU_PREV_SETTING:
+          case BRL_CMD_MENU_NEXT_SETTING:
+
+          case BRL_CMD_KEY(BACKSPACE):
+          case BRL_CMD_KEY(DELETE):
+          case BRL_CMD_KEY(PAGE_UP):
+          case BRL_CMD_KEY(PAGE_DOWN):
+          case BRL_CMD_KEY(CURSOR_UP):
+          case BRL_CMD_KEY(CURSOR_DOWN):
+          case BRL_CMD_KEY(CURSOR_LEFT):
+          case BRL_CMD_KEY(CURSOR_RIGHT):
+
+          case BRL_CMD_SPEAK_PREV_CHAR:
+          case BRL_CMD_SPEAK_NEXT_CHAR:
+          case BRL_CMD_SPEAK_PREV_WORD:
+          case BRL_CMD_SPEAK_NEXT_WORD:
+          case BRL_CMD_SPEAK_PREV_LINE:
+          case BRL_CMD_SPEAK_NEXT_LINE:
+            return 1;
+
+          case BRL_CMD_FWINLT:
+          case BRL_CMD_FWINRT:
+            if (prefs.autorepeatPanning) return 1;
+
+          default:
+            break;
+        }
+        break;
+    }
+  }
+
+  return 0;
+}
+
+static int
+getPressedKeysCommand (
+  KeyTable *table, unsigned char context,
+  const KeyValue *key, unsigned int position,
+  const KeyBinding **binding, int *wasInserted,
+  int *isIncomplete, int *isImmediate
+) {
+  *binding = findKeyBinding(table, context, key, isIncomplete);
+  *wasInserted = insertPressedKey(table, key, position);
+
+  if (*binding) {
+    *isImmediate = 1;
+    return (*binding)->primaryCommand.value;
+  }
+
+  if ((*binding = findKeyBinding(table, context, NULL, isIncomplete))) {
+    *isImmediate = 0;
+    return (*binding)->primaryCommand.value;
+  }
+
+  return EOF;
+}
+
+KeyTableState
+processKeyEvent (
+  KeyTable *table, unsigned char context,
+  KeyGroup keyGroup, KeyNumber keyNumber, int press
+) {
+  const KeyValue keyValue = {
+    .group = keyGroup,
+    .number = keyNumber
+  };
+
+  KeyTableState state = KTS_UNBOUND;
+  int command = EOF;
+  const HotkeyEntry *hotkey;
+
+  if (press && !table->pressedKeys.count) {
+    table->context.current = table->context.next;
+    table->context.next = table->context.persistent;
+  }
+  if (context == KTB_CTX_DEFAULT) context = table->context.current;
+
+  if (!(hotkey = findHotkeyEntry(table, context, &keyValue))) {
+    const KeyValue anyKey = {
+      .group = keyValue.group,
+      .number = KTB_KEY_ANY
+    };
+
+    hotkey = findHotkeyEntry(table, context, &anyKey);
+  }
+
+  if (hotkey) {
+    const BoundCommand *cmd = press? &hotkey->pressCommand: &hotkey->releaseCommand;
+
+    if (cmd->value != BRL_CMD_NOOP) processCommand(table, (command = cmd->value));
+    state = KTS_HOTKEY;
+  } else {
+    int isImmediate = 1;
+    unsigned int keyPosition;
+    int wasPressed = findPressedKey(table, &keyValue, &keyPosition);
+    if (wasPressed) removePressedKey(table, keyPosition);
+
+    if (press) {
+      const KeyBinding *binding;
+      int isIncomplete = 0;
+      int wasInserted;
+
+      int command = getPressedKeysCommand(
+        table, context,
+        &keyValue, keyPosition,
+        &binding, &wasInserted,
+        &isIncomplete, &isImmediate
+      );
+
+      if (command == EOF) {
+        if ((command = makeKeyboardCommand(table, context, 0)) != EOF) {
+          isImmediate = 0;
+        }       ;
+      }
+
+      if (command == EOF) {
+        int tryDefaultContext = wasInserted && (context != KTB_CTX_DEFAULT);
+
+        if (tryDefaultContext) {
+          const KeyContext *ctx = getKeyContext(table, context);
+
+          if (ctx && ctx->isIsolated) {
+            tryDefaultContext = 0;
+            command = BRL_CMD_NOOP;
+          }
+        }
+
+        if (tryDefaultContext) {
+          removePressedKey(table, keyPosition);
+
+          command = getPressedKeysCommand(
+            table, KTB_CTX_DEFAULT,
+            &keyValue, keyPosition,
+            &binding, &wasInserted,
+            &isIncomplete, &isImmediate
+          );
+
+          if (command != EOF) {
+            switch (command & BRL_MSK_BLK) {
+              case BRL_CMD_BLK(PASSDOTS): {
+                command = BRL_CMD_ALERT(COMMAND_REJECTED);
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      if (prefs.brailleQuickSpace) {
+        int cmd = makeKeyboardCommand(table, context, 1);
+
+        if (cmd != EOF) {
+          command = cmd;
+          isImmediate = 0;
+        }
+      }
+
+      if (command == EOF) {
+        command = BRL_CMD_NOOP;
+        if (isIncomplete) state = KTS_MODIFIERS;
+      } else {
+        state = KTS_COMMAND;
+      }
+
+      if (!wasPressed) {
+        int secondaryCommand = BRL_CMD_NOOP;
+
+        resetLongPressData(table);
+        table->release.command = BRL_CMD_NOOP;
+
+        if (binding) {
+          addCommandArguments(table, &command, binding->primaryCommand.entry, binding);
+
+          secondaryCommand = binding->secondaryCommand.value;
+          addCommandArguments(table, &secondaryCommand, binding->secondaryCommand.entry, binding);
+        }
+
+        if (context == KTB_CTX_WAITING) {
+          table->release.command = BRL_CMD_NOOP;
+        } else {
+          if (secondaryCommand == BRL_CMD_NOOP) {
+            if (isRepeatableCommand(command)) {
+              secondaryCommand = command;
+            }
+          }
+
+          if (isImmediate) {
+            table->release.command = BRL_CMD_NOOP;
+          } else {
+            table->release.command = command;
+            command = BRL_CMD_NOOP;
+          }
+
+          if (secondaryCommand != BRL_CMD_NOOP) {
+            table->longPress.command = secondaryCommand;
+            table->longPress.repeat = isRepeatableCommand(secondaryCommand);
+
+            table->longPress.keyAction = "long";
+            table->longPress.keyContext = context;
+            table->longPress.keyValue = keyValue;
+
+            setLongPressAlarm(table, prefs.longPressTime);
+          }
+        }
+
+        processCommand(table, command);
+      }
+    } else {
+      resetLongPressData(table);
+
+      if (prefs.onFirstRelease || (table->pressedKeys.count == 0)) {
+        int *cmd = &table->release.command;
+
+        if (*cmd != BRL_CMD_NOOP) {
+          processCommand(table, (command = *cmd));
+          *cmd = BRL_CMD_NOOP;
+        }
+      }
+    }
+
+    setAutoreleaseAlarm(table);
+  }
+
+  logKeyEvent(table, (press? "press": "release"), context, &keyValue, command);
+  return state;
+}
+
+void
+releaseAllKeys (KeyTable *table) {
+  while (table->pressedKeys.count) {
+    const KeyValue *kv = &table->pressedKeys.table[0];
+    processKeyEvent(table, KTB_CTX_DEFAULT, kv->group, kv->number, 0);
+  }
+}
+
+void
+setKeyTableLogLabel (KeyTable *table, const char *label) {
+  table->options.logLabel = label;
+}
+
+void
+setLogKeyEventsFlag (KeyTable *table, const unsigned char *flag) {
+  table->options.logKeyEventsFlag = flag;
+}
+
+void
+setKeyboardEnabledFlag (KeyTable *table, const unsigned char *flag) {
+  table->options.keyboardEnabledFlag = flag;
+}
+
+void
+getKeyGroupCommands (KeyTable *table, KeyGroup group, int *commands, unsigned int size) {
+  const KeyContext *ctx = getKeyContext(table, KTB_CTX_DEFAULT);
+
+  if (ctx) {
+    unsigned int i;
+
+    for (i=0; i<size; i+=1) {
+      commands[i] = BRL_CMD_NOOP;
+    }
+
+    for (i=0; i<ctx->keyBindings.count; i+=1) {
+      const KeyBinding *binding = &ctx->keyBindings.table[i];
+      const KeyCombination *combination = &binding->keyCombination;
+      const KeyValue *key;
+
+      if (combination->flags & KCF_IMMEDIATE_KEY) {
+        if (combination->modifierCount != 0) continue;
+        key = &combination->immediateKey;
+      } else {
+        if (combination->modifierCount != 1) continue;
+        key = &combination->modifierKeys[0];
+      }
+
+      if (key->group == group) {
+        if (key->number != KTB_KEY_ANY) {
+          if (key->number < size) {
+            int command = binding->primaryCommand.value;
+
+            if (command != BRL_CMD_NOOP) {
+              commands[key->number] = command;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+typedef struct {
+  int *array;
+  unsigned int size;
+  unsigned int count;
+} AddCommandData;
+
+static int
+addCommand (AddCommandData *acd, int command) {
+  if (command == EOF) return 1;
+  int blk = command & BRL_MSK_BLK;
+  command &= blk? BRL_MSK_BLK: BRL_MSK_CMD;
+  if (command == BRL_CMD_NOOP) return 1;
+
+  if (acd->count == acd->size) {
+    unsigned int newSize = acd->size? (acd->size << 1): 0X100;
+    int *newArray = realloc(acd->array, ARRAY_SIZE(newArray, newSize));
+
+    if (!newArray) {
+      logMallocError();
+      return 0;
+    }
+
+    acd->array = newArray;
+    acd->size = newSize;
+  }
+
+  acd->array[acd->count++] = command;
+  return 1;
+}
+
+static int
+addBoundCommand (AddCommandData *acd, const BoundCommand *cmd) {
+  return addCommand(acd, cmd->value);
+}
+
+static int
+sortCommands (const void *element1, const void *element2) {
+  const int *command1 = element1;
+  const int *command2 = element2;
+
+  if (*command1 < *command2) return -1;
+  if (*command1 > *command2) return 1;
+  return 0;
+}
+
+int *
+getBoundCommands (KeyTable *table, unsigned int *count) {
+  AddCommandData acd = {
+    .array = NULL,
+    .size = 0,
+    .count = 0
+  };
+
+  for (unsigned int context=0; context<table->keyContexts.count; context+=1) {
+    const KeyContext *ctx = getKeyContext(table, context);
+
+    if (ctx) {
+      {
+        const KeyBinding *binding = ctx->keyBindings.table;
+        const KeyBinding *end = binding + ctx->keyBindings.count;
+
+        while (binding < end) {
+          if (!addBoundCommand(&acd, &binding->primaryCommand)) goto error;
+          if (!addBoundCommand(&acd, &binding->secondaryCommand)) goto error;
+          binding += 1;
+        }
+      }
+
+      {
+        const HotkeyEntry *hotkey = ctx->hotkeys.table;
+        const HotkeyEntry *end = hotkey + ctx->hotkeys.count;
+
+        while (hotkey < end) {
+          if (!addBoundCommand(&acd, &hotkey->pressCommand)) goto error;
+          if (!addBoundCommand(&acd, &hotkey->releaseCommand)) goto error;
+          hotkey += 1;
+        }
+      }
+
+      if (ctx->mappedKeys.count) {
+        if (!addCommand(&acd, BRL_CMD_BLK(PASSDOTS))) {
+          goto error;
+        }
+      }
+    }
+  }
+
+  if (acd.count > 1) {
+    qsort(acd.array, acd.count, sizeof(*acd.array), sortCommands);
+
+    int *to = acd.array;
+    const int *from = to;
+    const int *end = from + acd.count;
+
+    while (from < end) {
+      if ((from == acd.array) || (*from != *(from - 1))) {
+        if (from != to) *to = *from;
+        to += 1;
+      }
+
+      from += 1;
+    }
+
+    acd.count = to - acd.array;
+  }
+
+  {
+    int *newArray = realloc(acd.array, ARRAY_SIZE(newArray, acd.count));
+    if (newArray) acd.array = newArray;
+  }
+
+  *count = acd.count;
+  return acd.array;
+
+error:
+  if (acd.array) free(acd.array);
+  return NULL;
+}
diff --git a/Programs/learn.c b/Programs/learn.c
new file mode 100644
index 0000000..f30bd65
--- /dev/null
+++ b/Programs/learn.c
@@ -0,0 +1,117 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "learn.h"
+#include "message.h"
+#include "async_wait.h"
+#include "cmd.h"
+#include "cmd_queue.h"
+#include "brl.h"
+#include "brl_cmds.h"
+#include "core.h"
+
+typedef enum {
+  LMS_CONTINUE,
+  LMS_TIMEOUT,
+  LMS_EXIT,
+  LMS_ERROR
+} LearnModeState;
+
+typedef struct {
+  const char *mode;
+  LearnModeState state;
+} LearnModeData;
+
+ASYNC_CONDITION_TESTER(testEndLearnWait) {
+  LearnModeData *lmd = data;
+
+  return lmd->state != LMS_TIMEOUT;
+}
+
+static int
+handleLearnModeCommands (int command, void *data) {
+  LearnModeData *lmd = data;
+
+  logMessage(LOG_DEBUG, "learn: command=%06X", command);
+  lmd->state = LMS_CONTINUE;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_LEARN:
+      lmd->state = LMS_EXIT;
+      return 1;
+
+    case BRL_CMD_NOOP:
+      return 1;
+
+    default:
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(TOUCH_AT):
+          return 1;
+
+        default:
+          break;
+      }
+      break;
+  }
+
+  {
+    char buffer[0X100];
+
+    describeCommand(buffer, sizeof(buffer), command,
+                    (CDO_IncludeName | CDO_IncludeOperand));
+
+    logMessage(LOG_DEBUG, "learn: %s", buffer);
+    if (!message(lmd->mode, buffer, MSG_SYNC|MSG_NODELAY)) lmd->state = LMS_ERROR;
+  }
+
+  return 1;
+}
+
+int
+learnMode (int timeout) {
+  LearnModeData lmd = {
+    .mode = "lrn"
+  };
+
+  pushCommandEnvironment("learnMode", NULL, NULL);
+  pushCommandHandler("learnMode", KTB_CTX_DEFAULT,
+                     handleLearnModeCommands, NULL, &lmd);
+
+  if (setStatusText(&brl, lmd.mode)) {
+    if (message(lmd.mode, gettext("Learn Mode"), MSG_SYNC|MSG_NODELAY)) {
+      do {
+        lmd.state = LMS_TIMEOUT;
+        if (!asyncAwaitCondition(timeout, testEndLearnWait, &lmd)) break;
+      } while (lmd.state == LMS_CONTINUE);
+
+      if (lmd.state == LMS_TIMEOUT) {
+        if (!message(lmd.mode, gettext("done"), MSG_SYNC)) {
+          lmd.state = LMS_ERROR;
+        }
+      }
+    }
+  }
+
+  popCommandEnvironment();
+  return lmd.state != LMS_ERROR;
+}
diff --git a/Programs/learn.h b/Programs/learn.h
new file mode 100644
index 0000000..a024975
--- /dev/null
+++ b/Programs/learn.h
@@ -0,0 +1,32 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_LEARN
+#define BRLTTY_INCLUDED_LEARN
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int learnMode (int timeout);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_LEARN */
diff --git a/Programs/leds.c b/Programs/leds.c
new file mode 100644
index 0000000..0637d7a
--- /dev/null
+++ b/Programs/leds.c
@@ -0,0 +1,29 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "leds.h"
+
+int
+setLedMonitoring (int on) {
+  if (on) return startMonitoringLeds();
+
+  stopMonitoringLeds();
+  return 1;
+}
diff --git a/Programs/leds_linux.c b/Programs/leds_linux.c
new file mode 100644
index 0000000..090f337
--- /dev/null
+++ b/Programs/leds_linux.c
@@ -0,0 +1,98 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "leds.h"
+
+#ifdef HAVE_LINUX_INPUT_H
+#include <linux/input.h>
+
+#include "system_linux.h"
+
+static InputEventMonitor *inputEventMonitor = NULL;
+
+static int
+prepareUinputObject (UinputObject *uinput) {
+  if (!enableUinputEventType(uinput, EV_LED)) return 0;
+  if (!enableUinputLed(uinput, LED_NUML)) return 0;
+  if (!enableUinputLed(uinput, LED_CAPSL)) return 0;
+  if (!enableUinputLed(uinput, LED_SCROLLL)) return 0;
+  return 1;
+}
+
+static void
+handleInputEvent (const InputEvent *event) {
+  switch (event->type) {
+    case EV_LED: {
+      switch (event->code) {
+        default:
+          break;
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+}
+
+int
+canMonitorLeds (void) {
+  return 1;
+}
+
+int
+startMonitoringLeds (void) {
+  if (!inputEventMonitor) {
+    InputEventMonitor *monitor = newInputEventMonitor(
+      "Keyboard LED Monitor", prepareUinputObject, handleInputEvent
+    );
+
+    if (!monitor) return 0;
+    inputEventMonitor = monitor;
+  }
+
+  return 1;
+}
+
+void
+stopMonitoringLeds (void) {
+  if (inputEventMonitor) {
+    destroyInputEventMonitor(inputEventMonitor);
+    inputEventMonitor = NULL;
+  }
+}
+
+#else /* HAVE_LINUX_INPUT_H */
+int
+canMonitorLeds (void) {
+  return 0;
+}
+
+int
+startMonitoringLeds (void) {
+  return 0;
+}
+
+void
+stopMonitoringLeds (void) {
+}
+#endif /* HAVE_LINUX_INPUT_H */
diff --git a/Programs/leds_none.c b/Programs/leds_none.c
new file mode 100644
index 0000000..1211bf9
--- /dev/null
+++ b/Programs/leds_none.c
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "leds.h"
+
+int
+canMonitorLeds (void) {
+  return 0;
+}
+
+int
+startMonitoringLeds (void) {
+  return 0;
+}
+
+void
+stopMonitoringLeds (void) {
+}
diff --git a/Programs/lock.c b/Programs/lock.c
new file mode 100644
index 0000000..7443a79
--- /dev/null
+++ b/Programs/lock.c
@@ -0,0 +1,238 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "lock.h"
+#include "log.h"
+#include "get_thread.h"
+ 
+#undef CAN_LOCK
+#if defined(PTHREAD_RWLOCK_INITIALIZER)
+#define CAN_LOCK
+
+struct LockDescriptorStruct {
+  pthread_rwlock_t lock;
+};
+
+static int
+constructLockDescriptor (LockDescriptor *lock) {
+  int error;
+
+  if (!(error = pthread_rwlock_init(&lock->lock, NULL))) {
+    return 1;
+  } else {
+    logActionError(error, "pthread_rwlock_init");
+  }
+
+  return 0;
+}
+
+static void
+destructLockDescriptor (LockDescriptor *lock) {
+  pthread_rwlock_destroy(&lock->lock);
+}
+
+int
+obtainLock (LockDescriptor *lock, LockOptions options) {
+  if (options & LOCK_Exclusive) {
+    if (options & LOCK_NoWait) return !pthread_rwlock_trywrlock(&lock->lock);
+    pthread_rwlock_wrlock(&lock->lock);
+  } else {
+    if (options & LOCK_NoWait) return !pthread_rwlock_tryrdlock(&lock->lock);
+    pthread_rwlock_rdlock(&lock->lock);
+  }
+
+  return 1;
+}
+
+void
+releaseLock (LockDescriptor *lock) {
+  pthread_rwlock_unlock(&lock->lock);
+}
+
+#elif defined(PTHREAD_MUTEX_INITIALIZER)
+#define CAN_LOCK
+
+struct LockDescriptorStruct {
+  pthread_mutex_t mutex;
+  pthread_cond_t read;
+  pthread_cond_t write;
+  int count;
+  unsigned int writers;
+};
+
+static int
+constructLockDescriptor (LockDescriptor *lock) {
+  int error;
+
+  if (!(error = pthread_cond_init(&lock->read, NULL))) {
+    if (!(error = pthread_cond_init(&lock->write, NULL))) {
+      if (!(error = pthread_mutex_init(&lock->mutex, NULL))) {
+        lock->count = 0;
+        lock->writers = 0;
+        return 1;
+      } else {
+        logActionError(error, "pthread_mutex_init");
+      }
+
+      pthread_cond_destroy(&lock->write);
+    } else {
+      logActionError(error, "pthread_cond_init");
+    }
+
+    pthread_cond_destroy(&lock->read);
+  } else {
+    logActionError(error, "pthread_cond_init");
+  }
+
+  return 0;
+}
+
+static void
+destructLockDescriptor (LockDescriptor *lock) {
+  pthread_mutex_destroy(&lock->mutex);
+  pthread_cond_destroy(&lock->read);
+  pthread_cond_destroy(&lock->write);
+}
+
+int
+obtainLock (LockDescriptor *lock, LockOptions options) {
+  int locked = 0;
+
+  pthread_mutex_lock(&lock->mutex);
+
+  if (options & LOCK_Exclusive) {
+    while (lock->count) {
+      if (options & LOCK_NoWait) goto done;
+      lock->writers += 1;
+      pthread_cond_wait(&lock->write, &lock->mutex);
+      lock->writers -= 1;
+    }
+
+    lock->count = -1;
+  } else {
+    while (lock->count < 0) {
+      if (options & LOCK_NoWait) goto done;
+      pthread_cond_wait(&lock->read, &lock->mutex);
+    }
+
+    lock->count += 1;
+  }
+
+  locked = 1;
+done:
+  pthread_mutex_unlock(&lock->mutex);
+  return locked;
+}
+
+void
+releaseLock (LockDescriptor *lock) {
+  pthread_mutex_lock(&lock->mutex);
+
+  if (lock->count < 0) {
+    lock->count = 0;
+  } else if (--lock->count) {
+    goto done;
+  }
+
+  if (lock->writers) {
+    pthread_cond_signal(&lock->write);
+  } else {
+    pthread_cond_broadcast(&lock->read);
+  }
+
+done:
+  pthread_mutex_unlock(&lock->mutex);
+}
+
+#endif /* lock paradigm */
+
+#ifdef CAN_LOCK
+LockDescriptor *
+newLockDescriptor (void) {
+  LockDescriptor *lock;
+
+  if ((lock = malloc(sizeof(*lock)))) {
+    memset(lock, 0, sizeof(*lock));
+    if (constructLockDescriptor(lock)) return lock;
+
+    free(lock);
+    lock = NULL;
+  } else {
+    logMallocError();
+  }
+
+  return lock;
+}
+
+void
+freeLockDescriptor (LockDescriptor *lock) {
+  destructLockDescriptor(lock);
+  free(lock);
+}
+
+LockDescriptor *
+getLockDescriptor (LockDescriptor **lock, const char *name) {
+  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+  int isNew = 0;
+
+  pthread_mutex_lock(&mutex);
+    if (!*lock) {
+      if ((*lock = newLockDescriptor())) {
+        isNew = 1;
+      }
+    }
+  pthread_mutex_unlock(&mutex);
+
+  if (isNew) {
+    logMessage(LOG_DEBUG, "lock descriptor allocated: %s", name);
+  }
+
+  return *lock;
+}
+#else /* CAN_LOCK */
+#warning thread lock support not available on this platform
+
+int
+obtainLock (LockDescriptor *lock, LockOptions options) {
+  return 1;
+}
+
+void
+releaseLock (LockDescriptor *lock) {
+}
+
+LockDescriptor *
+newLockDescriptor (void) {
+  errno = ENOSYS;
+  return NULL;
+}
+
+void
+freeLockDescriptor (LockDescriptor *lock) {
+}
+
+LockDescriptor *
+getLockDescriptor (LockDescriptor **lock, const char *name) {
+  return *lock;
+}
+#endif /* CAN_LOCK */
diff --git a/Programs/log.c b/Programs/log.c
new file mode 100644
index 0000000..47c6ffc
--- /dev/null
+++ b/Programs/log.c
@@ -0,0 +1,717 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif /* __ANDROID__ */
+
+#include "log.h"
+#include "log_history.h"
+#include "strfmt.h"
+#include "file.h"
+#include "unicode.h"
+#include "utf8.h"
+#include "timing.h"
+#include "addresses.h"
+#include "stdiox.h"
+#include "io_misc.h"
+#include "thread.h"
+
+const char logCategoryName_all[] = "all";
+const char logCategoryPrefix_disable = '-';
+
+const char *const logLevelNames[] = {
+  "emergency", "alert", "critical", "error",
+  "warning", "notice", "information", "debug"
+};
+const unsigned int logLevelCount = ARRAY_COUNT(logLevelNames);
+
+unsigned char systemLogLevel = LOG_NOTICE;
+unsigned char stderrLogLevel = LOG_NOTICE;
+
+typedef struct {
+  const char *name;
+  const char *title;
+  const char *prefix;
+} LogCategoryEntry;
+
+static const LogCategoryEntry logCategoryTable[LOG_CATEGORY_COUNT] = {
+  [LOG_CATEGORY_INDEX(INPUT_PACKETS)] = {
+    .name = "inpkts",
+    .title = strtext("Input Packets"),
+    .prefix = "input packet"
+  },
+
+  [LOG_CATEGORY_INDEX(OUTPUT_PACKETS)] = {
+    .name = "outpkts",
+    .title = strtext("Output Packets"),
+    .prefix = "output packet"
+  },
+
+  [LOG_CATEGORY_INDEX(BRAILLE_KEYS)] = {
+    .name = "brlkeys",
+    .title = strtext("Braille Key Events"),
+    .prefix = "braille key"
+  },
+
+  [LOG_CATEGORY_INDEX(KEYBOARD_KEYS)] = {
+    .name = "kbdkeys",
+    .title = strtext("Keyboard Key Events"),
+    .prefix = "keyboard key"
+  },
+
+  [LOG_CATEGORY_INDEX(CURSOR_TRACKING)] = {
+    .name = "csrtrk",
+    .title = strtext("Cursor Tracking"),
+    .prefix = "cursor tracking"
+  },
+
+  [LOG_CATEGORY_INDEX(CURSOR_ROUTING)] = {
+    .name = "csrrtg",
+    .title = strtext("Cursor Routing"),
+    .prefix = "cursor routing"
+  },
+
+  [LOG_CATEGORY_INDEX(UPDATE_EVENTS)] = {
+    .name = "update",
+    .title = strtext("Update Events"),
+    .prefix = "update"
+  },
+
+  [LOG_CATEGORY_INDEX(SPEECH_EVENTS)] = {
+    .name = "speech",
+    .title = strtext("Speech Events"),
+    .prefix = "speech"
+  },
+
+  [LOG_CATEGORY_INDEX(ASYNC_EVENTS)] = {
+    .name = "async",
+    .title = strtext("Async Events"),
+    .prefix = "async"
+  },
+
+  [LOG_CATEGORY_INDEX(SERVER_EVENTS)] = {
+    .name = "server",
+    .title = strtext("Server Events"),
+    .prefix = "server"
+  },
+
+  [LOG_CATEGORY_INDEX(GENERIC_IO)] = {
+    .name = "gio",
+    .title = strtext("generic I/O"),
+    .prefix = "GIO"
+  },
+
+  [LOG_CATEGORY_INDEX(SERIAL_IO)] = {
+    .name = "serial",
+    .title = strtext("Serial I/O"),
+    .prefix = "serial"
+  },
+
+  [LOG_CATEGORY_INDEX(USB_IO)] = {
+    .name = "usb",
+    .title = strtext("USB I/O"),
+    .prefix = "USB"
+  },
+
+  [LOG_CATEGORY_INDEX(BLUETOOTH_IO)] = {
+    .name = "bt",
+    .title = strtext("Bluetooth I/O"),
+    .prefix = "Bluetooth"
+  },
+
+  [LOG_CATEGORY_INDEX(HID_IO)] = {
+    .name = "hid",
+    .title = strtext("Human Interface I/O"),
+    .prefix = "HID"
+  },
+
+  [LOG_CATEGORY_INDEX(BRAILLE_DRIVER)] = {
+    .name = "brldrv",
+    .title = strtext("Braille Driver Events"),
+    .prefix = "braille driver"
+  },
+
+  [LOG_CATEGORY_INDEX(SPEECH_DRIVER)] = {
+    .name = "spkdrv",
+    .title = strtext("Speech Driver Events"),
+    .prefix = "speech driver"
+  },
+
+  [LOG_CATEGORY_INDEX(SCREEN_DRIVER)] = {
+    .name = "scrdrv",
+    .title = strtext("Screen Driver Events"),
+    .prefix = "screen driver"
+  },
+};
+
+unsigned char categoryLogLevel = LOG_WARNING;
+unsigned char logCategoryFlags[LOG_CATEGORY_COUNT];
+
+#if defined(WINDOWS)
+static HANDLE windowsEventLog = INVALID_HANDLE_VALUE;
+
+static WORD
+toWindowsEventType (int level) {
+  if (level <= LOG_ERR) return EVENTLOG_ERROR_TYPE;
+  if (level <= LOG_WARNING) return EVENTLOG_WARNING_TYPE;
+  return EVENTLOG_INFORMATION_TYPE;
+}
+
+#elif defined(__MSDOS__)
+
+#elif defined(__ANDROID__)
+static int
+toAndroidLogPriority (int level) {
+  switch (level) {
+    case LOG_EMERG:   return ANDROID_LOG_FATAL;
+    case LOG_ALERT:   return ANDROID_LOG_FATAL;
+    case LOG_CRIT:    return ANDROID_LOG_FATAL;
+    case LOG_ERR:     return ANDROID_LOG_ERROR;
+    case LOG_WARNING: return ANDROID_LOG_WARN;
+    case LOG_NOTICE:  return ANDROID_LOG_INFO;
+    case LOG_INFO:    return ANDROID_LOG_INFO;
+    case LOG_DEBUG:   return ANDROID_LOG_DEBUG;
+    default:          return ANDROID_LOG_UNKNOWN;
+  }
+}
+
+#elif defined(HAVE_SYSLOG_H)
+static int syslogOpened = 0;
+#endif /* system log internal definitions */
+
+static LogEntry *logPrefixStack = NULL;
+static FILE *logFile = NULL;
+
+static inline const LogCategoryEntry *
+getLogCategoryEntry (LogCategoryIndex index) {
+  return (index < LOG_CATEGORY_COUNT)? &logCategoryTable[index]: NULL;
+}
+
+const char *
+getLogCategoryName (LogCategoryIndex index) {
+  const LogCategoryEntry *ctg = getLogCategoryEntry(index);
+
+  return (ctg && ctg->name)? ctg->name: "";
+}
+
+const char *
+getLogCategoryTitle (LogCategoryIndex index) {
+  const LogCategoryEntry *ctg = getLogCategoryEntry(index);
+
+  return (ctg && ctg->title)? ctg->title: "";
+}
+
+static inline void
+setLogCategoryFlag (const LogCategoryEntry *ctg, unsigned char state) {
+  logCategoryFlags[ctg - logCategoryTable] = state;
+}
+
+void
+disableAllLogCategories (void) {
+  const LogCategoryEntry *ctg = logCategoryTable;
+  const LogCategoryEntry *end = ctg + LOG_CATEGORY_COUNT;
+
+  while (ctg < end) setLogCategoryFlag(ctg++, 0);
+}
+
+int
+setLogCategory (const char *name) {
+  const LogCategoryEntry *ctg = logCategoryTable;
+  const LogCategoryEntry *end = ctg + LOG_CATEGORY_COUNT;
+
+  int on = 1;
+  int all;
+
+  if (*name == logCategoryPrefix_disable) {
+    on = 0;
+    name += 1;
+  }
+
+  all = strcasecmp(name, logCategoryName_all) == 0;
+
+  while (ctg < end) {
+    if (all || (ctg->name && (strcasecmp(name, ctg->name) == 0))) {
+      setLogCategoryFlag(ctg, on);
+      if (!all) return 1;
+    }
+
+    ctg += 1;
+  }
+
+  return all;
+}
+
+int
+pushLogPrefix (const char *prefix) {
+  if (!prefix) prefix = "";
+  return pushLogEntry(&logPrefixStack, prefix, 0);
+}
+
+int
+popLogPrefix (void) {
+  return popLogEntry(&logPrefixStack);
+}
+
+void
+closeLogFile (void) {
+  if (logFile) {
+    fclose(logFile);
+    logFile = NULL;
+  }
+}
+
+void
+openLogFile (const char *path) {
+  closeLogFile();
+  FILE *stream = fopen(path, "w");
+
+  if (stream) {
+  //setCloseOnExec(fileno(stream), 1);
+    writeUtf8ByteOrderMark(stream);
+    logFile = stream;
+  }
+}
+
+static void
+writeLogRecord (const char *record) {
+  if (logFile) {
+    lockStream(logFile);
+
+    {
+      TimeValue now;
+      char buffer[0X20];
+      size_t length;
+      unsigned int milliseconds;
+
+      getCurrentTime(&now);
+      length = formatSeconds(buffer, sizeof(buffer), "%Y-%m-%d@%H:%M:%S", now.seconds);
+      milliseconds = now.nanoseconds / NSECS_PER_MSEC;
+
+      fprintf(logFile, "%.*s.%03u ", (int)length, buffer, milliseconds);
+    }
+
+    {
+      char name[0X40];
+      size_t length = formatThreadName(name, sizeof(name));
+
+      if (length) fprintf(logFile, "[%s] ", name);
+    }
+
+    fputs(record, logFile);
+    fputc('\n', logFile);
+    flushStream(logFile);
+    unlockStream(logFile);
+  }
+}
+
+void
+openSystemLog (void) {
+#if defined(WINDOWS)
+  if (windowsEventLog == INVALID_HANDLE_VALUE) {
+    windowsEventLog = RegisterEventSource(NULL, PACKAGE_TARNAME);
+  }
+
+#elif defined(__MSDOS__)
+  if (!logFile) {
+    char *path = makeWritablePath(PACKAGE_TARNAME ".log");
+
+    if (path) {
+      openLogFile(path);
+      free(path);
+    }
+  }
+
+#elif defined(__ANDROID__)
+
+#elif defined(HAVE_SYSLOG_H)
+  if (!syslogOpened) {
+    openlog(PACKAGE_TARNAME, LOG_PID, LOG_DAEMON);
+    syslogOpened = 1;
+  }
+#endif /* open system log */
+}
+
+void
+closeSystemLog (void) {
+#if defined(WINDOWS)
+  if (windowsEventLog != INVALID_HANDLE_VALUE) {
+    DeregisterEventSource(windowsEventLog);
+    windowsEventLog = INVALID_HANDLE_VALUE;
+  }
+
+#elif defined(__MSDOS__)
+  closeLogFile();
+
+#elif defined(__ANDROID__)
+
+#elif defined(HAVE_SYSLOG_H)
+  if (syslogOpened) {
+    closelog();
+    syslogOpened = 0;
+  }
+#endif /* close system log */
+}
+
+int
+logData (int level, LogDataFormatter *formatLogData, const void *data) {
+  LogCategoryIndex category = level >> LOG_LEVEL_WIDTH;
+  level &= LOG_LEVEL_MASK;
+
+  const char *prefix;
+  int push;
+
+  if (category) {
+    category -= 1;
+
+    if (!logCategoryFlags[category]) return 0;
+    if (!level) level = categoryLogLevel;
+
+    const LogCategoryEntry *ctg = &logCategoryTable[category];
+    prefix = ctg->prefix;
+    push = 0;
+  } else {
+    prefix = NULL;
+    push = level <= LOG_WARNING;
+  }
+
+  int write = level <= systemLogLevel;
+  int print = level <= stderrLogLevel;
+  if (!(write || print || push)) return 0;
+
+  int oldErrno = errno;
+
+  char record[0X1000];
+  STR_BEGIN(record, sizeof(record));
+  if (prefix) STR_PRINTF("%s: ", prefix);
+  STR_FORMAT(formatLogData, data);
+  STR_END;
+
+  if (write) {
+    writeLogRecord(record);
+
+#if defined(WINDOWS)
+    if (windowsEventLog != INVALID_HANDLE_VALUE) {
+      const char *strings[] = {record};
+
+      ReportEvent(
+        windowsEventLog, toWindowsEventType(level), 0, 0, NULL,
+        ARRAY_COUNT(strings), 0, strings, NULL
+      );
+    }
+
+#elif defined(__MSDOS__)
+
+#elif defined(__ANDROID__)
+    __android_log_write(
+      toAndroidLogPriority(level), PACKAGE_TARNAME, record
+    );
+
+#elif defined(HAVE_SYSLOG_H)
+    if (syslogOpened) syslog(level, "%s", record);
+#endif /* write system log */
+  }
+
+  if (print) {
+    FILE *stream = stderr;
+    lockStream(stream);
+
+    if (logPrefixStack) {
+      const char *prefix = getLogEntryText(logPrefixStack);
+
+      if (*prefix) {
+        fputs(prefix, stream);
+        fputs(": ", stream);
+      }
+    }
+
+    writeWithConsoleEncoding(stream, record, strlen(record));
+    fputc('\n', stream);
+
+    flushStream(stream);
+    unlockStream(stream);
+  }
+
+  if (push) pushLogMessage(record);
+  errno = oldErrno;
+  return 1;
+}
+
+static size_t
+formatLogArguments (char *buffer, size_t size, const char *format, va_list *arguments) {
+  int length = vsnprintf(buffer, size, format, *arguments);
+  if (length < 0) return 0;
+  if (length < size) return length;
+  return size;
+}
+
+typedef struct {
+  const char *format;
+  va_list *arguments;
+} LogMessageData;
+
+static size_t
+formatLogMessageData (char *buffer, size_t size, const void *data) {
+  const LogMessageData *msg = data;
+  return formatLogArguments(buffer, size, msg->format, msg->arguments);
+}
+
+int
+vlogMessage (int level, const char *format, va_list *arguments) {
+  const LogMessageData msg = {
+    .format = format,
+    .arguments = arguments
+  };
+
+  return logData(level, formatLogMessageData, &msg);
+}
+
+int
+logMessage (int level, const char *format, ...) {
+  int wasLogged;
+  va_list arguments;
+
+  va_start(arguments, format);
+  wasLogged = vlogMessage(level, format, &arguments);
+  va_end(arguments);
+
+  return wasLogged;
+}
+
+typedef struct {
+  const char *label;
+  va_list *arguments;
+  const void *data;
+  size_t length;
+} LogBytesData;
+
+static
+STR_BEGIN_FORMATTER(formatLogBytesData, const void *data)
+  const LogBytesData *bytes = data;
+  const unsigned char *byte = bytes->data;
+  const unsigned char *end = byte + bytes->length;
+
+  if (bytes->label) {
+    STR_FORMAT(formatLogArguments, bytes->label, bytes->arguments);
+    STR_PRINTF(": ");
+  }
+
+  while (byte < end) {
+    if (byte != bytes->data) STR_PRINTF(" ");
+    STR_PRINTF("%2.2X", *byte++);
+  }
+STR_END_FORMATTER
+
+int
+logBytes (int level, const char *label, const void *data, size_t length, ...) {
+  int wasLogged;
+  va_list arguments;
+
+  va_start(arguments, length);
+  {
+    const LogBytesData bytes = {
+      .label = label,
+      .arguments = &arguments,
+      .data = data,
+      .length = length
+    };
+
+    wasLogged = logData(level, formatLogBytesData, &bytes);
+  }
+  va_end(arguments);
+
+  return wasLogged;
+}
+
+typedef struct {
+  void *address;
+  const char *format;
+  va_list *arguments;
+} LogSymbolData;
+
+static
+STR_BEGIN_FORMATTER(formatLogSymbolData, const void *data)
+  const LogSymbolData *symbol = data;
+  ptrdiff_t offset = 0;
+  const char *name = getAddressName(symbol->address, &offset);
+
+  STR_FORMAT(formatLogArguments, symbol->format, symbol->arguments);
+  STR_PRINTF(": ");
+
+  if (name && *name) {
+    STR_PRINTF("%s", name);
+    if (offset) STR_PRINTF("+%"PRIXPTR, (uintptr_t)offset);
+  } else {
+    STR_PRINTF("%p", symbol->address);
+  }
+STR_END_FORMATTER
+
+int
+logSymbol (int level, void *address, const char *format, ...) {
+  int wasLogged;
+  va_list arguments;
+
+  va_start(arguments, format);
+  {
+    const LogSymbolData symbol = {
+      .address = address,
+      .format = format,
+      .arguments = &arguments
+    };
+
+    wasLogged = logData(level, formatLogSymbolData, &symbol);
+  }
+  va_end(arguments);
+
+  return wasLogged;
+}
+
+int
+logActionProblem (int level, int error, const char *action) {
+  return logMessage(level, "%s error %d: %s", action, error, strerror(error));
+}
+
+int
+logActionError (int error, const char *action) {
+  return logActionProblem(LOG_ERR, error, action);
+}
+
+int
+logSystemProblem (int level, const char *action) {
+  return logActionProblem(level, errno, action);
+}
+
+int
+logSystemError (const char *action) {
+  return logSystemProblem(LOG_ERR, action);
+}
+
+int
+logMallocError (void) {
+  return logSystemError("malloc");
+}
+
+int
+logUnsupportedFeature (const char *name) {
+  return logMessage(LOG_WARNING, "feature not supported: %s", name);
+}
+
+int
+logUnsupportedOperation (const char *name) {
+  errno = ENOSYS;
+  return logSystemError(name);
+}
+
+int
+logPossibleCause (const char *cause) {
+  return logMessage(LOG_WARNING, "possible cause: %s", cause);
+}
+
+#ifdef WINDOWS
+int
+logWindowsError (DWORD error, const char *action) {
+  char *message;
+  DWORD count = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+                              NULL, error,
+                              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                              (char *)&message, 0, NULL);
+
+  if (count) {
+    char *end = strpbrk(message, "\r\n");
+
+    if (end) *end = 0;
+  } else {
+    message = "unknown";
+  }
+
+  int wasLogged = logMessage(LOG_ERR, "%s error %d: %s", action, (int)error, message);
+  if (count) LocalFree(message);
+  return wasLogged;
+}
+
+int
+logWindowsSystemError (const char *action) {
+  DWORD error = GetLastError();
+  return logWindowsError(error, action);
+}
+
+#ifdef __MINGW32__
+int
+logWindowsSocketError (const char *action) {
+  DWORD error = WSAGetLastError();
+  return logWindowsError(error, action);
+}
+#endif /* __MINGW32__ */
+#endif /* WINDOWS */
+
+static int
+logBacktraceString (const char *string) {
+  return logMessage(LOG_DEBUG, "backtrace: %s", string);
+}
+
+#if defined(HAVE_EXECINFO_H)
+#include <execinfo.h>
+
+int
+logBacktrace (void) {
+  const int limit = 30;
+  void *frames[limit];
+  int count = backtrace(frames, limit);
+
+  if (count > 0) {
+    char **strings;
+
+    if ((strings = backtrace_symbols(frames, count))) {
+      char **string = strings;
+      char **end = string + count;
+
+      while (string < end) {
+        if (!logBacktraceString(*string)) return 0;
+        string += 1;
+      }
+
+      if (count == limit) {
+        if (!logBacktraceString("...")) return 0;
+      }
+
+      free(strings);
+    } else {
+      logSystemError("backtrace_symbols");
+    }
+  } else {
+    if (!logBacktraceString("no frames")) return 0;
+  }
+
+  return 1;
+}
+
+#else /* log backtrace */
+int
+logBacktrace (void) {
+  return logBacktraceString("not supported");
+}
+#endif /* log backtrace */
diff --git a/Programs/log_history.c b/Programs/log_history.c
new file mode 100644
index 0000000..8a1dce1
--- /dev/null
+++ b/Programs/log_history.c
@@ -0,0 +1,127 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "log_history.h"
+#include "timing.h"
+
+struct LogEntryStruct {
+  struct LogEntryStruct *previous;
+  TimeValue time;
+  unsigned int count;
+  unsigned noSquash:1;
+  char text[0];
+};
+
+const LogEntry *
+getPreviousLogEntry (const LogEntry *entry) {
+  return entry->previous;
+}
+
+const char *
+getLogEntryText (const LogEntry *entry) {
+  return entry->text;
+}
+
+const TimeValue *
+getLogEntryTime (const LogEntry *entry) {
+  return &entry->time;
+}
+
+unsigned int
+getLogEntryCount (const LogEntry *entry) {
+  return entry->count;
+}
+
+int
+pushLogEntry (LogEntry **head, const char *text, LogEntryPushOptions options) {
+  int log = !(options & LPO_NOLOG);
+  LogEntry *entry = NULL;
+
+  if (options & LPO_SQUASH) {
+    if ((entry = *head)) {
+      if (!entry->noSquash && (strcmp(entry->text, text) == 0)) {
+        entry->count += 1;
+      } else {
+        entry = NULL;
+      }
+    }
+  }
+
+  if (!entry) {
+    const size_t size = sizeof(*entry) + strlen(text) + 1;
+
+    if (!(entry = malloc(size))) {
+      if (log) logMallocError();
+      return 0;
+    }
+
+    memset(entry, 0, sizeof(*entry));
+    entry->count = 1;
+    strcpy(entry->text, text);
+
+    entry->previous = *head;
+    *head = entry;
+  }
+
+  getCurrentTime(&entry->time);
+  return 1;
+}
+
+int
+popLogEntry (LogEntry **head) {
+  if (!*head) return 0;
+  LogEntry *entry = *head;
+  *head = entry->previous;
+  free(entry);
+  return 1;
+}
+
+static CriticalSectionLock logMessageLock = CRITICAL_SECTION_LOCK_INITIALIZER;
+
+static void
+lockLogMessages (void) {
+  enterCriticalSection(&logMessageLock);
+}
+
+static void
+unlockLogMessages (void) {
+  leaveCriticalSection(&logMessageLock);
+}
+
+static LogEntry *logMessageStack = NULL;
+
+const LogEntry *
+getNewestLogMessage (int freeze) {
+  lockLogMessages();
+  LogEntry *message = logMessageStack;
+  if (freeze && message) message->noSquash = 1;
+  unlockLogMessages();
+  return message;
+}
+
+void
+pushLogMessage (const char *message) {
+  lockLogMessages();
+  pushLogEntry(&logMessageStack, message, (LPO_NOLOG | LPO_SQUASH));
+  unlockLogMessages();
+}
diff --git a/Programs/menu.c b/Programs/menu.c
new file mode 100644
index 0000000..d16f3a1
--- /dev/null
+++ b/Programs/menu.c
@@ -0,0 +1,1135 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+
+#undef CAN_GLOB
+#if defined(HAVE_GLOB)
+#define CAN_GLOB
+#include <glob.h>
+
+#elif defined(__MINGW32__)
+#define CAN_GLOB
+#include <io.h>
+
+#else /* glob: paradigm-specific global definitions */
+#warning file globbing support not available on this platform
+#endif /* glob: paradigm-specific global definitions */
+
+#include "log.h"
+#include "menu.h"
+#include "strfmt.h"
+#include "prefs.h"
+#include "timing.h"
+#include "parse.h"
+#include "file.h"
+
+typedef struct {
+  char *directory;
+  const char *extension;
+  const char *pattern;
+  char *initial;
+  char *current;
+  unsigned none:1;
+
+#if defined(HAVE_GLOB)
+  glob_t glob;
+#elif defined(__MINGW32__)
+  char **names;
+  int offset;
+#endif /* glob: paradigm-specific field declarations */
+
+  char **paths;
+  int count;
+  unsigned char setting;
+  char *pathsArea[3];
+} FileData;
+
+typedef struct {
+  Menu *menu;
+  unsigned opened:1;
+
+  unsigned int total;
+  unsigned int visible;
+} SubmenuData;
+
+struct MenuStruct {
+  Menu *parent;
+
+  struct {
+    MenuItem *array;
+    unsigned int size;
+    unsigned int count;
+    unsigned int index;
+  } items;
+
+  unsigned int menuNumber;
+  unsigned int submenuCount;
+  MenuItem *activeItem;
+
+  char valueBuffer[0X20];
+};
+
+typedef struct {
+  int (*testItem) (const MenuItem *item);
+  int (*beginItem) (MenuItem *item);
+  void (*endItem) (MenuItem *item, int deallocating);
+  void (*activateItem) (MenuItem *item);
+  const char * (*getValue) (const MenuItem *item);
+  const char * (*getText) (const MenuItem *item);
+  const char * (*getComment) (const MenuItem *item);
+} MenuItemMethods;
+
+struct MenuItemStruct {
+  Menu *menu;
+  unsigned char *setting;                 /* pointer to current value */
+
+  const char *title;                      /* item name for presentation */
+  const char *subtitle;                      /* item name for presentation */
+
+  const MenuItemMethods *methods;
+  MenuItemTester *test;                     /* returns true if item should be presented */
+  MenuItemChanged *changed;
+
+  unsigned char minimum;                  /* lowest valid value */
+  unsigned char maximum;                  /* highest valid value */
+  unsigned char step;                  /* present only multiples of this value */
+
+  union {
+    const char *text;
+    const MenuString *strings;
+    FileData *files;
+    SubmenuData *submenu;
+
+    struct {
+      const char *unit;
+      NumericMenuItemFormatter *formatter;
+    } numeric;
+
+    struct {
+      MenuToolFunction *function;
+    } tool;
+  } data;
+};
+
+static inline const char *
+getLocalizedText (const char *string) {
+  return (string && *string)? gettext(string): "";
+}
+
+static const char *
+formatValue (Menu *menu, const char *format, ...) {
+  {
+    va_list arguments;
+
+    va_start(arguments, format);
+    vsnprintf(menu->valueBuffer, sizeof(menu->valueBuffer), format, arguments);
+    va_end(arguments);
+  }
+
+  return menu->valueBuffer;
+}
+
+Menu *
+newMenu (void) {
+  Menu *menu;
+
+  if ((menu = malloc(sizeof(*menu)))) {
+    memset(menu, 0, sizeof(*menu));
+    menu->parent = NULL;
+
+    menu->items.array = NULL;
+    menu->items.size = 0;
+    menu->items.count = 0;
+    menu->items.index = 0;
+
+    menu->menuNumber = 0;
+    menu->submenuCount = 0;
+    menu->activeItem = NULL;
+
+    return menu;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+beginMenuItem (MenuItem *item) {
+  return !item->methods->beginItem || item->methods->beginItem(item);
+}
+
+static void
+endMenuItem (MenuItem *item, int deallocating) {
+  if (item->methods->endItem) item->methods->endItem(item, deallocating);
+}
+
+void
+destroyMenu (Menu *menu) {
+  if (menu) {
+    if (menu->items.array) {
+      MenuItem *item = menu->items.array;
+      const MenuItem *end = item + menu->items.count;
+
+      while (item < end) endMenuItem(item++, 1);
+      free(menu->items.array);
+    }
+
+    free(menu);
+  }
+}
+
+unsigned int
+getMenuNumber (const Menu *menu) {
+  return menu->menuNumber;
+}
+
+Menu *
+getMenuParent (const Menu *menu) {
+  return menu->parent;
+}
+
+unsigned int
+getMenuSize (const Menu *menu) {
+  return menu->items.count;
+}
+
+unsigned int
+getMenuIndex (const Menu *menu) {
+  return menu->items.index;
+}
+
+MenuItem *
+getMenuItem (Menu *menu, unsigned int index) {
+  return (index < menu->items.count)? &menu->items.array[index]: NULL;
+}
+
+static MenuItem *
+getSelectedMenuItem (Menu *menu) {
+  return getMenuItem(menu, menu->items.index);
+}
+
+static int
+testMenuItem (const MenuItem *item, int all) {
+  if (!item) return 0;
+  if (all) return 1;
+  if (item->methods->testItem && !item->methods->testItem(item)) return 0;
+  return !item->test || item->test();
+}
+
+int
+isMenuItemSettable (const MenuItem *item) {
+  return !!item->setting;
+}
+
+int
+isMenuItemAction (const MenuItem *item) {
+  return !!item->methods->activateItem;
+}
+
+int
+isMenuItemVisible (const MenuItem *item) {
+  return testMenuItem(item, 0);
+}
+
+static inline int
+testMenuItemActive (Menu *menu, unsigned int index) {
+  return testMenuItem(getMenuItem(menu, index), 0);
+}
+
+static inline int
+testMenuItemVisible (Menu *menu, unsigned int index) {
+  return testMenuItem(getMenuItem(menu, index), prefs.showAllItems);
+}
+
+Menu *
+getMenuItemMenu (const MenuItem *item) {
+  return item->menu;
+}
+
+unsigned int
+getMenuItemIndex (const MenuItem *item) {
+  return item - item->menu->items.array;
+}
+
+const char *
+getMenuItemTitle (const MenuItem *item) {
+  return getLocalizedText(item->title);
+}
+
+const char *
+getMenuItemSubtitle (const MenuItem *item) {
+  return getLocalizedText(item->subtitle);
+}
+
+const char *
+getMenuItemValue (const MenuItem *item) {
+  return item->methods->getValue? item->methods->getValue(item): "";
+}
+
+const char *
+getMenuItemText (const MenuItem *item) {
+  return item->methods->getText? item->methods->getText(item): getMenuItemValue(item);
+}
+
+const char *
+getMenuItemComment (const MenuItem *item) {
+  return item->methods->getComment? item->methods->getComment(item): "";
+}
+
+static MenuItem *
+newMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
+  if (menu->items.count == menu->items.size) {
+    unsigned int newSize = menu->items.size? (menu->items.size << 1): 0X10;
+    MenuItem *newArray = realloc(menu->items.array, (newSize * sizeof(*newArray)));
+
+    if (!newArray) {
+      logMallocError();
+      return NULL;
+    }
+
+    menu->items.array = newArray;
+    menu->items.size = newSize;
+  }
+
+  {
+    MenuItem *item = getMenuItem(menu, menu->items.count++);
+
+    item->menu = menu;
+    item->setting = setting;
+
+    if (name) {
+      item->title = name->label;
+      item->subtitle = name->comment;
+    } else {
+      item->title = NULL;
+      item->subtitle = NULL;
+    }
+
+    item->methods = NULL;
+    item->test = NULL;
+    item->changed = NULL;
+
+    item->minimum = 0;
+    item->maximum = 0;
+    item->step = 1;
+
+    return item;
+  }
+}
+
+void
+setMenuItemTester (MenuItem *item, MenuItemTester *handler) {
+  item->test = handler;
+}
+
+void
+setMenuItemChanged (MenuItem *item, MenuItemChanged *handler) {
+  item->changed = handler;
+}
+
+static const char *
+getValue_text (const MenuItem *item) {
+  return item->data.text;
+}
+
+static const MenuItemMethods menuItemMethods_text = {
+  .getValue = getValue_text
+};
+
+MenuItem *
+newTextMenuItem (Menu *menu, const MenuString *name, const char *text) {
+  MenuItem *item = newMenuItem(menu, NULL, name);
+
+  if (item) {
+    item->methods = &menuItemMethods_text;
+    item->data.text = text;
+  }
+
+  return item;
+}
+
+static const char *
+getValue_numeric (const MenuItem *item) {
+  Menu *menu = item->menu;
+
+  item->data.numeric.formatter(
+    menu, *item->setting,
+    menu->valueBuffer, sizeof(menu->valueBuffer)
+  );
+
+  return menu->valueBuffer;
+}
+
+static const char *
+getComment_numeric (const MenuItem *item) {
+  return getLocalizedText(item->data.numeric.unit);
+}
+
+static const MenuItemMethods menuItemMethods_numeric = {
+  .getValue = getValue_numeric,
+  .getComment = getComment_numeric
+};
+
+static void
+defaultNumericMenuItemFormatter (
+  Menu *menu, unsigned char value,
+  char *buffer, size_t size
+) {
+  snprintf(buffer, size, "%u", value);
+}
+
+MenuItem *
+newNumericMenuItem (
+  Menu *menu, unsigned char *setting, const MenuString *name,
+  unsigned char minimum, unsigned char maximum, unsigned char step,
+  const char *unit, NumericMenuItemFormatter *formatter
+) {
+  if (!formatter) formatter = defaultNumericMenuItemFormatter;
+
+  MenuItem *item = newMenuItem(menu, setting, name);
+
+  if (item) {
+    item->methods = &menuItemMethods_numeric;
+    item->minimum = minimum;
+    item->maximum = maximum;
+    item->step = step;
+    item->data.numeric.unit = unit;
+    item->data.numeric.formatter = formatter;
+  }
+
+  return item;
+}
+
+static void
+formatTime (Menu *menu, unsigned char time, char *buffer, size_t size) {
+  unsigned int milliseconds = PREFS2MSECS(time);
+
+  unsigned int seconds = milliseconds / MSECS_PER_SEC;
+  milliseconds %= MSECS_PER_SEC;
+
+  const char *decimalPoint;
+#ifdef HAVE_NL_LANGINFO
+  decimalPoint = nl_langinfo(RADIXCHAR);
+#else /* HAVE_NL_LANGINFO */
+  decimalPoint = NULL;
+#endif /* HAVE_NL_LANGINFO */
+  if (!decimalPoint) decimalPoint = ".";
+
+  size_t end;
+  size_t decimalFrom;
+  size_t decimalTo;
+
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%u", seconds);
+  decimalFrom = STR_LENGTH;
+  STR_PRINTF("%s", decimalPoint);
+  decimalTo = STR_LENGTH;
+  STR_PRINTF("%03u", milliseconds);
+  end = STR_LENGTH;
+  STR_END;
+
+  while (buffer[--end] == '0');
+  if (++end == decimalTo) end = decimalFrom;
+  buffer[end] = 0;
+}
+
+MenuItem *
+newTimeMenuItem (
+  Menu *menu, unsigned char *setting,
+  const MenuString *name
+) {
+  return newNumericMenuItem(menu, setting, name, 10, 250, 10, strtext("seconds"), formatTime);
+}
+
+MenuItem *
+newPercentMenuItem (
+  Menu *menu, unsigned char *setting,
+  const MenuString *name, unsigned char step
+) {
+  return newNumericMenuItem(menu, setting, name, 0, 100, step, "%", NULL);
+}
+
+static const char *
+getValue_strings (const MenuItem *item) {
+  const MenuString *strings = item->data.strings;
+  return getLocalizedText(strings[*item->setting - item->minimum].label);
+}
+
+static const char *
+getComment_strings (const MenuItem *item) {
+  const MenuString *strings = item->data.strings;
+  return getLocalizedText(strings[*item->setting - item->minimum].comment);
+}
+
+static const MenuItemMethods menuItemMethods_strings = {
+  .getValue = getValue_strings,
+  .getComment = getComment_strings
+};
+
+static void
+setMenuItemStrings (MenuItem *item, const MenuString *strings, unsigned char count) {
+  item->methods = &menuItemMethods_strings;
+  item->data.strings = strings;
+  item->minimum = 0;
+  item->maximum = count - 1;
+  item->step = 1;
+}
+
+MenuItem *
+newStringsMenuItem (
+  Menu *menu, unsigned char *setting, const MenuString *name,
+  const MenuString *strings, unsigned char count
+) {
+  MenuItem *item = newMenuItem(menu, setting, name);
+
+  if (item) {
+    setMenuItemStrings(item, strings, count);
+  }
+
+  return item;
+}
+
+MenuItem *
+newBooleanMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
+  static const MenuString strings[] = {
+    {.label=strtext("No")},
+    {.label=strtext("Yes")}
+  };
+
+  return newEnumeratedMenuItem(menu, setting, name, strings);
+}
+
+static int
+qsortCompare_fileNames (const void *element1, const void *element2) {
+  const char *const *name1 = element1;
+  const char *const *name2 = element2;
+  return strcmp(*name1, *name2);
+}
+
+static int
+beginItem_files (MenuItem *item) {
+  FileData *files = item->data.files;
+  int index;
+
+  files->paths = files->pathsArea;
+  files->count = ARRAY_COUNT(files->pathsArea) - 1;
+  files->paths[files->count] = NULL;
+  index = files->count;
+
+#ifdef CAN_GLOB
+  {
+#ifdef HAVE_FCHDIR
+    int originalDirectory = open(".", O_RDONLY);
+
+    if (originalDirectory != -1)
+#else /* HAVE_FCHDIR */
+    char *originalDirectory = getWorkingDirectory();
+
+    if (originalDirectory)
+#endif /* HAVE_FCHDIR */
+    {
+      if (chdir(files->directory) != -1) {
+#if defined(HAVE_GLOB)
+        memset(&files->glob, 0, sizeof(files->glob));
+        files->glob.gl_offs = files->count;
+
+        if (glob(files->pattern, GLOB_DOOFFS, NULL, &files->glob) == 0) {
+          files->paths = files->glob.gl_pathv;
+
+          /* The behaviour of gl_pathc is inconsistent. Some implementations
+           * include the leading NULL pointers and some don't. Let's just
+           * figure it out the hard way by finding the trailing NULL.
+           */
+          while (files->paths[files->count]) files->count += 1;
+        }
+#elif defined(__MINGW32__)
+        struct _finddata_t findData;
+        long findHandle = _findfirst(files->pattern, &findData);
+        int allocated = files->count | 0XF;
+
+        files->offset = files->count;
+        files->names = malloc(allocated * sizeof(*files->names));
+
+        if (findHandle != -1) {
+          do {
+            if (files->count >= allocated) {
+              allocated = allocated * 2;
+              files->names = realloc(files->names, allocated * sizeof(*files->names));
+            }
+
+            files->names[files->count++] = strdup(findData.name);
+          } while (_findnext(findHandle, &findData) == 0);
+
+          _findclose(findHandle);
+        }
+
+        files->names = realloc(files->names, files->count * sizeof(*files->names));
+        files->paths = files->names;
+#endif /* glob: paradigm-specific field initialization */
+
+#ifdef HAVE_FCHDIR
+        if (fchdir(originalDirectory) == -1) logSystemError("fchdir");
+#else /* HAVE_FCHDIR */
+        if (chdir(originalDirectory) == -1) logSystemError("chdir");
+#endif /* HAVE_FCHDIR */
+      } else {
+        logMessage(LOG_ERR, "%s: %s: %s",
+                   gettext("cannot set working directory"), files->directory, strerror(errno));
+      }
+
+#ifdef HAVE_FCHDIR
+      close(originalDirectory);
+#else /* HAVE_FCHDIR */
+      free(originalDirectory);
+#endif /* HAVE_FCHDIR */
+    } else {
+#ifdef HAVE_FCHDIR
+      logMessage(LOG_ERR, "%s: %s",
+                 gettext("cannot open working directory"), strerror(errno));
+#else /* HAVE_FCHDIR */
+      logMessage(LOG_ERR, "%s", gettext("cannot determine working directory"));
+#endif /* HAVE_FCHDIR */
+    }
+  }
+#endif /* CAN_GLOB */
+
+  qsort(&files->paths[index], files->count-index, sizeof(*files->paths), qsortCompare_fileNames);
+  if (files->none) files->paths[--index] = "";
+  files->paths[--index] = files->initial;
+  files->paths += index;
+  files->count -= index;
+  files->setting = 0;
+
+  for (index=1; index<files->count; index+=1) {
+    if (strcmp(files->paths[index], files->initial) == 0) {
+      files->paths += 1;
+      files->count -= 1;
+      break;
+    }
+  }
+
+  for (index=0; index<files->count; index+=1) {
+    if (strcmp(files->paths[index], files->current) == 0) {
+      files->setting = index;
+      break;
+    }
+  }
+
+  item->maximum = files->count - 1;
+  return 1;
+}
+
+static void
+endItem_files (MenuItem *item, int deallocating) {
+  FileData *files = item->data.files;
+
+  if (files->current) free(files->current);
+  files->current = deallocating? NULL: strdup(files->paths[files->setting]);
+
+#if defined(HAVE_GLOB)
+  if (files->glob.gl_pathc) {
+    for (int i=0; i<files->glob.gl_offs; i+=1) files->glob.gl_pathv[i] = NULL;
+    globfree(&files->glob);
+    files->glob.gl_pathc = 0;
+  }
+#elif defined(__MINGW32__)
+  if (files->names) {
+    for (int i=files->offset; i<files->count; i+=1) free(files->names[i]);
+    free(files->names);
+    files->names = NULL;
+  }
+#endif /* glob: paradigm-specific memory deallocation */
+}
+
+static const char *
+getValue_files (const MenuItem *item) {
+  const FileData *files = item->data.files;
+  const char *path;
+
+  if (item == item->menu->activeItem) {
+    path = files->paths[files->setting];
+  } else {
+    path = files->current;
+  }
+
+  if (!path) path = "";
+  const char *name = locatePathName(path);
+
+  if (name == path) {
+    const char *extension = files->extension;
+
+    if (hasFileExtension(name, extension)) {
+      int length = strlen(path) - strlen(extension);
+      Menu *menu = item->menu;
+
+      snprintf(
+        menu->valueBuffer, sizeof(menu->valueBuffer),
+        "%.*s", length, name
+      );
+
+      path = menu->valueBuffer;
+    }
+  }
+
+  return path;
+}
+
+static const char *
+getText_files (const MenuItem *item) {
+  return item->methods->getValue(item);
+}
+
+static const MenuItemMethods menuItemMethods_files = {
+  .beginItem = beginItem_files,
+  .endItem = endItem_files,
+  .getValue = getValue_files,
+  .getText = getText_files
+};
+
+MenuItem *
+newFilesMenuItem (
+  Menu *menu, const MenuString *name,
+  const char *directory, const char *subdirectory, const char *extension,
+  const char *initial, int none
+) {
+  FileData *files;
+
+  if ((files = malloc(sizeof(*files)))) {
+    memset(files, 0, sizeof(*files));
+    files->extension = extension;
+    files->none = !!none;
+
+    char *pattern;
+    {
+      const char *strings[] = {"*", extension};
+      pattern = joinStrings(strings, ARRAY_COUNT(strings));
+    }
+
+    if (pattern) {
+      files->pattern = pattern; 
+
+      if ((files->initial = *initial? ensureFileExtension(initial, extension): strdup(""))) {
+        if ((files->current = strdup(files->initial))) {
+          if (subdirectory) {
+            files->directory = makePath(directory, subdirectory);
+          } else if (!(files->directory = strdup(directory))) {
+            logMallocError();
+          }
+
+          if (files->directory) {
+            MenuItem *item = newMenuItem(menu, &files->setting, name);
+
+            if (item) {
+              item->methods = &menuItemMethods_files;
+              item->data.files = files;
+              return item;
+            }
+
+            free(files->directory);
+          }
+
+          free(files->current);
+        } else {
+          logMallocError();
+        }
+
+        free(files->initial);
+      } else {
+        logMallocError();
+      }
+
+      free(pattern);
+    } else {
+      logMallocError();
+    }
+
+    free(files);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+activateItem_tool (MenuItem *item) {
+  item->data.tool.function();
+}
+
+static const char *
+getValue_tool (const MenuItem *item) {
+  return NULL;
+}
+
+static const MenuItemMethods menuItemMethods_tool = {
+  .activateItem = activateItem_tool,
+  .getValue = getValue_tool
+};
+
+MenuItem *
+newToolMenuItem (Menu *menu, const MenuString *name, MenuToolFunction *function) {
+  MenuItem *item = newMenuItem(menu, NULL, name);
+
+  if (item) {
+    item->methods = &menuItemMethods_tool;
+    item->data.tool.function = function;
+  }
+
+  return item;
+}
+
+static MenuItem *
+getParentMenuItem (const MenuItem *item) {
+  return getSelectedMenuItem(item->menu->parent);
+}
+
+static int
+testItem_submenu (const MenuItem *item) {
+  return getMenuSize(item->data.submenu->menu) > 1;
+}
+
+static int
+beginItem_submenu (MenuItem *item) {
+  item->data.submenu->visible = 0;
+
+  {
+    Menu *menu = item->data.submenu->menu;
+    unsigned int size = getMenuSize(menu);
+
+    for (unsigned int index=1; index<size; index+=1) {
+      if (testMenuItemActive(menu, index)) {
+        item->data.submenu->visible += 1;
+      }
+    }
+
+    item->data.submenu->total = size - 1;
+  }
+
+  return 1;
+}
+
+static void
+endItem_submenu (MenuItem *item, int deallocating) {
+  if (deallocating) {
+    SubmenuData *submenu = item->data.submenu;
+
+    destroyMenu(submenu->menu);
+    free(submenu);
+  }
+}
+
+static void
+activateItem_submenu (MenuItem *item) {
+  endMenuItem(item, 0);
+  item->data.submenu->opened = 1;
+}
+
+static const char *
+getValue_submenu (const MenuItem *item) {
+  return "--->";
+}
+
+static const char *
+getComment_submenu (const MenuItem *item) {
+  if (!prefs.showSubmenuSizes) return "";
+
+  {
+    const SubmenuData *submenu = item->data.submenu;
+
+    return prefs.showAllItems?
+             formatValue(item->menu, "%u", submenu->total):
+             formatValue(item->menu, "%u/%u", submenu->visible, submenu->total);
+  }
+}
+
+static const MenuItemMethods menuItemMethods_submenu = {
+  .testItem = testItem_submenu,
+  .beginItem = beginItem_submenu,
+  .endItem = endItem_submenu,
+  .activateItem = activateItem_submenu,
+  .getValue = getValue_submenu,
+  .getComment = getComment_submenu
+};
+
+static void
+activateItem_close (MenuItem *item) {
+  item = getParentMenuItem(item);
+  item->data.submenu->opened = 0;
+  beginMenuItem(item);
+}
+
+static const char *
+getValue_close (const MenuItem *item) {
+  return getLocalizedText(strtext("Close"));
+}
+
+static const char *
+getComment_close (const MenuItem *item) {
+  return getMenuItemTitle(getParentMenuItem(item));
+}
+
+static const MenuItemMethods menuItemMethods_close = {
+  .activateItem = activateItem_close,
+  .getValue = getValue_close,
+  .getComment = getComment_close
+};
+
+Menu *
+newSubmenuMenuItem (
+  Menu *menu, const MenuString *name
+) {
+  SubmenuData *submenu;
+
+  if ((submenu = malloc(sizeof(*submenu)))) {
+    memset(submenu, 0, sizeof(*submenu));
+
+    if ((submenu->menu = newMenu())) {
+      static const MenuString closeName = {.label="<---"};
+      MenuItem *close;
+
+      if ((close = newMenuItem(submenu->menu, NULL, &closeName))) {
+        MenuItem *item;
+
+        if ((item = newMenuItem(menu, NULL, name))) {
+          submenu->menu->parent = menu;
+          submenu->opened = 0;
+          close->methods = &menuItemMethods_close;
+
+          item->methods = &menuItemMethods_submenu;
+          item->data.submenu = submenu;
+
+          while (1) {
+            menu->submenuCount += 1;
+            if (!menu->parent) break;
+            menu = menu->parent;
+          }
+
+          submenu->menu->menuNumber = menu->submenuCount;
+          return submenu->menu;
+        }
+      }
+
+      destroyMenu(submenu->menu);
+    }
+
+    free(submenu);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+changeMenuItem (MenuItem *item) {
+  Menu *menu = item->menu;
+
+  menu->items.index = getMenuItemIndex(item);
+}
+
+int
+changeMenuItemPrevious (Menu *menu, int wrap) {
+  unsigned int index = menu->items.index;
+  if (index >= menu->items.count) return 0;
+
+  do {
+    if (!menu->items.index) {
+      if (!wrap) {
+        menu->items.index = index;
+        return 0;
+      }
+
+      menu->items.index = menu->items.count;
+    }
+
+    if (--menu->items.index == index) return 0;
+  } while (!testMenuItemVisible(menu, menu->items.index));
+
+  return 1;
+}
+
+int
+changeMenuItemNext (Menu *menu, int wrap) {
+  unsigned int index = menu->items.index;
+  if (index >= menu->items.count) return 0;
+
+  do {
+    if (++menu->items.index == menu->items.count) {
+      if (!wrap) {
+        menu->items.index = index;
+        return 0;
+      }
+
+      menu->items.index = 0;
+    }
+
+    if (menu->items.index == index) return 0;
+  } while (!testMenuItemVisible(menu, menu->items.index));
+
+  return 1;
+}
+
+int
+changeMenuItemFirst (Menu *menu) {
+  if (!menu->items.count) return 0;
+  menu->items.index = 0;
+  return testMenuItemVisible(menu, menu->items.index) || changeMenuItemNext(menu, 0);
+}
+
+int
+changeMenuItemLast (Menu *menu) {
+  if (!menu->items.count) return 0;
+  menu->items.index = menu->items.count - 1;
+  return testMenuItemVisible(menu, menu->items.index) || changeMenuItemPrevious(menu, 0);
+}
+
+int
+changeMenuItemIndex (Menu *menu, unsigned int index) {
+  if (index >= menu->items.count) return 0;
+  menu->items.index = index;
+  return 1;
+}
+
+static int
+activateMenuItem (MenuItem *item) {
+  if (!item->methods->activateItem) return 0;
+  item->methods->activateItem(item);
+  return 1;
+}
+
+static int
+adjustMenuSetting (const MenuItem *item, int (*adjust) (const MenuItem *item, int wrap), int wrap) {
+  unsigned char setting = *item->setting;
+  int count = item->maximum - item->minimum + 1;
+
+  do {
+    int ok = 0;
+
+    if (--count) {
+      if (adjust(item, wrap)) {
+        ok = 1;
+      }
+    }
+
+    if (!ok) {
+      *item->setting = setting;
+      return 0;
+    }
+  } while ((*item->setting % item->step) || (item->changed && !item->changed(item, *item->setting)));
+
+  return 1;
+}
+
+static int
+decrementMenuSetting (const MenuItem *item, int wrap) {
+  if ((*item->setting)-- <= item->minimum) {
+    if (!wrap) return 0;
+    *item->setting = item->maximum;
+  }
+
+  return 1;
+}
+
+int
+changeMenuSettingPrevious (Menu *menu, int wrap) {
+  MenuItem *item = getCurrentMenuItem(menu);
+
+  if (activateMenuItem(item)) return 1;
+  if (!item->setting) return 0;
+  return adjustMenuSetting(item, decrementMenuSetting, wrap);
+}
+
+static int
+incrementMenuSetting (const MenuItem *item, int wrap) {
+  if ((*item->setting)++ >= item->maximum) {
+    if (!wrap) return 0;
+    *item->setting = item->minimum;
+  }
+
+  return 1;
+}
+
+int
+changeMenuSettingNext (Menu *menu, int wrap) {
+  MenuItem *item = getCurrentMenuItem(menu);
+
+  if (activateMenuItem(item)) return 1;
+  if (!item->setting) return 0;
+  return adjustMenuSetting(item, incrementMenuSetting, wrap);
+}
+
+int
+changeMenuSettingScaled (Menu *menu, unsigned int index, unsigned int count) {
+  MenuItem *item = getCurrentMenuItem(menu);
+
+  if (activateMenuItem(item)) return 1;
+
+  if (item->setting) {
+    unsigned char oldSetting = *item->setting;
+
+    if (item->methods->getValue == getValue_numeric) {
+      *item->setting = rescaleInteger(index, count-1, item->maximum-item->minimum) + item->minimum;
+    } else {
+      *item->setting = index % (item->maximum + 1);
+    }
+
+    if (!item->changed || item->changed(item, *item->setting)) return 1;
+    *item->setting = oldSetting;
+  }
+
+  return 0;
+}
+
+MenuItem *
+getCurrentMenuItem (Menu *menu) {
+  MenuItem *newItem = getSelectedMenuItem(menu);
+  MenuItem *oldItem = menu->activeItem;
+
+  if (newItem != oldItem) {
+    if (oldItem) endMenuItem(oldItem, 0);
+    menu->activeItem = beginMenuItem(newItem)? newItem: NULL;
+  }
+
+  return newItem;
+}
+
+Menu *
+getCurrentSubmenu (Menu *menu) {
+  while (1) {
+    MenuItem *item = getCurrentMenuItem(menu);
+
+    if (item->methods != &menuItemMethods_submenu) break;
+    if (!item->data.submenu->opened) break;
+    menu = item->data.submenu->menu;
+  }
+
+  return menu;
+}
diff --git a/Programs/menu_prefs.c b/Programs/menu_prefs.c
new file mode 100644
index 0000000..465d248
--- /dev/null
+++ b/Programs/menu_prefs.c
@@ -0,0 +1,1611 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "log_history.h"
+#include "embed.h"
+#include "revision.h"
+#include "api_control.h"
+#include "menu.h"
+#include "menu_prefs.h"
+#include "prefs.h"
+#include "profile.h"
+#include "status_types.h"
+#include "blink.h"
+#include "timing.h"
+#include "brl.h"
+#include "spk.h"
+#include "ttb.h"
+#include "atb.h"
+#include "ctb.h"
+#include "ktb.h"
+#include "tune.h"
+#include "bell.h"
+#include "leds.h"
+#include "midi.h"
+#include "core.h"
+
+#define PREFS_MENU_ITEM_VARIABLE(name) prefsMenuItemVariable_##name
+#define PREFS_MENU_ITEM_GETTER_DECLARE(name) \
+static MenuItem *PREFS_MENU_ITEM_VARIABLE(name) = NULL; \
+PREFS_MENU_ITEM_GETTER_PROTOTYPE(name) { \
+  return getPreferencesMenu()? PREFS_MENU_ITEM_VARIABLE(name): NULL; \
+}
+
+PREFS_MENU_ITEM_APPLY(PREFS_MENU_ITEM_GETTER_DECLARE)
+
+#define NAME(name) static const MenuString itemName = {.label=name}
+#define ITEM(new) MenuItem *item = (new); if (!item) goto noItem
+#define TEST(property) setMenuItemTester(item, test##property)
+#define CHANGED(property) setMenuItemChanged(item, changed##property)
+#define SET(name) PREFS_MENU_ITEM_VARIABLE(name) = item
+
+#define SUBMENU(variable, parent, name) \
+  NAME(name); \
+  Menu *variable = newSubmenuMenuItem(parent, &itemName); \
+  if (!variable) goto noItem
+
+#define PROPERTY(variable, value) \
+  static unsigned char variable; \
+  variable = (value)
+
+#define BLINK_PERIOD(blink) PROPERTY(period, MSECS2PREFS(getBlinkPeriod((blink))))
+#define BLINK_VISIBLE(blink) PROPERTY(visible, getBlinkPercentVisible((blink)))
+
+static int
+testAdvancedSubmenu (void) {
+  return prefs.showAdvancedSubmenus;
+}
+
+static void
+setAdvancedSubmenu (Menu *submenu) {
+  Menu *parent = getMenuParent(submenu);
+
+  if (parent) {
+    unsigned int size = getMenuSize(parent);
+
+    if (size) {
+      MenuItem *item = getMenuItem(parent, size-1);
+
+      if (item) setMenuItemTester(item, testAdvancedSubmenu);
+    }
+  }
+}
+
+static int
+testSlidingBrailleWindow (void) {
+  return prefs.slidingBrailleWindow;
+}
+
+static int
+changedBrailleWindowOverlap (const MenuItem *item UNUSED, unsigned char setting) {
+  if (setting >= textCount) return 0;
+  reconfigureBrailleWindow();
+  return 1;
+}
+
+static int
+changedAutoreleaseTime (const MenuItem *item UNUSED, unsigned char setting) {
+  if (brl.keyTable) setKeyAutoreleaseTime(brl.keyTable, setting);
+  return 1;
+}
+
+static int
+testAutorepeatEnabled (void) {
+  return prefs.autorepeatEnabled;
+}
+
+static int
+changeAutorepeatProperties (BrailleDisplay *brl, int on, int delay, int interval) {
+  if (!canSetAutorepeatProperties(brl)) return 1;
+  return setAutorepeatProperties(brl, on, delay, interval);
+}
+
+static int
+changedAutorepeatEnabled (const MenuItem *item UNUSED, unsigned char setting) {
+  return changeAutorepeatProperties(&brl, setting,
+                                    PREFS2MSECS(prefs.longPressTime),
+                                    PREFS2MSECS(prefs.autorepeatInterval));
+}
+
+static int
+changedAutorepeatDelay (const MenuItem *item UNUSED, unsigned char setting) {
+  return changeAutorepeatProperties(&brl, prefs.autorepeatEnabled,
+                                    setting,
+                                    PREFS2MSECS(prefs.autorepeatInterval));
+}
+
+static int
+changedAutorepeatInterval (const MenuItem *item UNUSED, unsigned char setting) {
+  return changeAutorepeatProperties(&brl, prefs.autorepeatEnabled,
+                                    PREFS2MSECS(prefs.longPressTime),
+                                    setting);
+}
+
+static int
+testShowScreenCursor (void) {
+  return prefs.showScreenCursor;
+}
+
+static int
+testBlinkingScreenCursor (void) {
+  return testShowScreenCursor() && prefs.blinkingScreenCursor;
+}
+
+static int
+changedScreenCursorBlinkPeriod (const MenuItem *item UNUSED, unsigned char setting) {
+  setBlinkPeriod(&screenCursorBlinkDescriptor, PREFS2MSECS(setting));
+  return 1;
+}
+
+static int
+changedScreenCursorBlinkPercentage (const MenuItem *item UNUSED, unsigned char setting) {
+  return setBlinkPercentVisible(&screenCursorBlinkDescriptor, setting);
+}
+
+static int
+testShowAttributes (void) {
+  return prefs.showAttributes;
+}
+
+static int
+testBlinkingAttributes (void) {
+  return testShowAttributes() && prefs.blinkingAttributes;
+}
+
+static int
+changedAttributesUnderlineBlinkPeriod (const MenuItem *item UNUSED, unsigned char setting) {
+  setBlinkPeriod(&attributesUnderlineBlinkDescriptor, PREFS2MSECS(setting));
+  return 1;
+}
+
+static int
+changedAttributesUnderlineBlinkPercentage (const MenuItem *item UNUSED, unsigned char setting) {
+  return setBlinkPercentVisible(&attributesUnderlineBlinkDescriptor, setting);
+}
+
+static int
+testBlinkingCapitals (void) {
+  return prefs.blinkingCapitals;
+}
+
+static int
+changedUppercaseLettersBlinkPeriod (const MenuItem *item UNUSED, unsigned char setting) {
+  setBlinkPeriod(&uppercaseLettersBlinkDescriptor, PREFS2MSECS(setting));
+  return 1;
+}
+
+static int
+changedUppercaseLettersBlinkPercentage (const MenuItem *item UNUSED, unsigned char setting) {
+  return setBlinkPercentVisible(&uppercaseLettersBlinkDescriptor, setting);
+}
+
+static int
+testBrailleFirmness (void) {
+  return canSetBrailleFirmness(&brl);
+}
+
+static int
+changedBrailleFirmness (const MenuItem *item UNUSED, unsigned char setting) {
+  return setBrailleFirmness(&brl, setting);
+}
+
+static int
+testTouchSensitivity (void) {
+  return canSetTouchSensitivity(&brl);
+}
+
+static int
+changedTouchSensitivity (const MenuItem *item UNUSED, unsigned char setting) {
+  return setTouchSensitivity(&brl, setting);
+}
+
+static int
+testConsoleBellAlert (void) {
+  return canMonitorConsoleBell();
+}
+
+static int
+changedConsoleBellAlert (const MenuItem *item UNUSED, unsigned char setting) {
+  return setConsoleBellMonitoring(setting);
+}
+
+static int
+testKeyboardLedAlerts (void) {
+  return canMonitorLeds();
+}
+
+static int
+changedKeyboardLedAlerts (const MenuItem *item UNUSED, unsigned char setting) {
+  return setLedMonitoring(setting);
+}
+
+static int
+testAlertTunes (void) {
+  return prefs.alertTunes;
+}
+
+static int
+changedAlertTunes (const MenuItem *item UNUSED, unsigned char setting) {
+  api.updateParameter(BRLAPI_PARAM_AUDIBLE_ALERTS, 0);
+  return 1;
+}
+
+static int
+changedTuneDevice (const MenuItem *item UNUSED, unsigned char setting) {
+  return tuneSetDevice(setting);
+}
+
+#ifdef HAVE_PCM_SUPPORT
+static int
+testTunesPcm (void) {
+  return testAlertTunes() && (prefs.tuneDevice == tdPcm);
+}
+#endif /* HAVE_PCM_SUPPORT */
+
+#ifdef HAVE_MIDI_SUPPORT
+static int
+testTunesMidi (void) {
+  return testAlertTunes() && (prefs.tuneDevice == tdMidi);
+}
+#endif /* HAVE_MIDI_SUPPORT */
+
+#ifdef HAVE_FM_SUPPORT
+static int
+testTunesFm (void) {
+  return testAlertTunes() && (prefs.tuneDevice == tdFm);
+}
+#endif /* HAVE_FM_SUPPORT */
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static int
+testSpeechVolume (void) {
+  return canSetSpeechVolume(&spk);
+}
+
+static int
+changedSpeechVolume (const MenuItem *item UNUSED, unsigned char setting) {
+  return setSpeechVolume(&spk, setting, !prefs.autospeak);
+}
+
+static void
+formatSpeechVolume (Menu *menu, unsigned char volume, char *buffer, size_t size) {
+  snprintf(
+    buffer, size, "%d", toNormalizedSpeechVolume(volume)
+  );
+}
+
+static int
+testSpeechRate (void) {
+  return canSetSpeechRate(&spk);
+}
+
+static int
+changedSpeechRate (const MenuItem *item UNUSED, unsigned char setting) {
+  return setSpeechRate(&spk, setting, !prefs.autospeak);
+}
+
+static void
+formatSpeechRate (Menu *menu, unsigned char rate, char *buffer, size_t size) {
+  snprintf(
+    buffer, size, "%d", toNormalizedSpeechRate(rate)
+  );
+}
+
+static int
+testSpeechPitch (void) {
+  return canSetSpeechPitch(&spk);
+}
+
+static int
+changedSpeechPitch (const MenuItem *item UNUSED, unsigned char setting) {
+  return setSpeechPitch(&spk, setting, !prefs.autospeak);
+}
+
+static void
+formatSpeechPitch (Menu *menu, unsigned char pitch, char *buffer, size_t size) {
+  snprintf(
+    buffer, size, "%d", toNormalizedSpeechPitch(pitch)
+  );
+}
+
+static int
+testSpeechPunctuation (void) {
+  return canSetSpeechPunctuation(&spk);
+}
+
+static int
+changedSpeechPunctuation (const MenuItem *item UNUSED, unsigned char setting) {
+  return setSpeechPunctuation(&spk, setting, !prefs.autospeak);
+}
+
+static int
+testAutospeak (void) {
+  return prefs.autospeak;
+}
+
+static int
+testShowSpeechCursor (void) {
+  return prefs.showSpeechCursor;
+}
+
+static int
+testBlinkingSpeechCursor (void) {
+  return testShowSpeechCursor() && prefs.blinkingSpeechCursor;
+}
+
+static int
+changedSpeechCursorBlinkPeriod (const MenuItem *item UNUSED, unsigned char setting) {
+  setBlinkPeriod(&speechCursorBlinkDescriptor, PREFS2MSECS(setting));
+  return 1;
+}
+
+static int
+changedSpeechCursorBlinkPercentage (const MenuItem *item UNUSED, unsigned char setting) {
+  return setBlinkPercentVisible(&speechCursorBlinkDescriptor, setting);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+static int
+testShowDate (void) {
+  return prefs.datePosition != dpNone;
+}
+
+static int
+testStatusPosition (void) {
+  return !haveStatusCells();
+}
+
+static int
+changedStatusPosition (const MenuItem *item UNUSED, unsigned char setting UNUSED) {
+  reconfigureBrailleWindow();
+  return 1;
+}
+
+static int
+testStatusCount (void) {
+  return testStatusPosition() && (prefs.statusPosition != spNone);
+}
+
+static int
+changedStatusCount (const MenuItem *item UNUSED, unsigned char setting UNUSED) {
+  reconfigureBrailleWindow();
+  return 1;
+}
+
+static int
+testStatusSeparator (void) {
+  return testStatusCount();
+}
+
+static int
+changedStatusSeparator (const MenuItem *item UNUSED, unsigned char setting UNUSED) {
+  reconfigureBrailleWindow();
+  return 1;
+}
+
+static int
+testStatusField (unsigned char index) {
+  return (haveStatusCells() || (prefs.statusPosition != spNone)) &&
+         ((index == 0) || (prefs.statusFields[index-1] != sfEnd));
+}
+
+static int
+changedStatusField (unsigned char index, unsigned char setting) {
+  switch (setting) {
+    case sfGeneric:
+      if (index > 0) return 0;
+      if (!haveStatusCells()) return 0;
+      if (!braille->statusFields) return 0;
+      if (*braille->statusFields != sfGeneric) return 0;
+      /* fall through */
+
+    case sfEnd:
+      if (prefs.statusFields[index+1] != sfEnd) return 0;
+      break;
+
+    default:
+      if ((index > 0) && (prefs.statusFields[index-1] == sfGeneric)) return 0;
+      break;
+  }
+
+  reconfigureBrailleWindow();
+  return 1;
+}
+
+#define STATUS_FIELD_HANDLERS(n) \
+  static int testStatusField##n (void) { return testStatusField(n-1); } \
+  static int changedStatusField##n (const MenuItem *item UNUSED, unsigned char setting) { return changedStatusField(n-1, setting); }
+STATUS_FIELD_HANDLERS(1)
+STATUS_FIELD_HANDLERS(2)
+STATUS_FIELD_HANDLERS(3)
+STATUS_FIELD_HANDLERS(4)
+STATUS_FIELD_HANDLERS(5)
+STATUS_FIELD_HANDLERS(6)
+STATUS_FIELD_HANDLERS(7)
+STATUS_FIELD_HANDLERS(8)
+STATUS_FIELD_HANDLERS(9)
+#undef STATUS_FIELD_HANDLERS
+
+static int
+changedSkipIdenticalLines (const MenuItem *item, unsigned char setting UNUSED) {
+  api.updateParameter(BRLAPI_PARAM_SKIP_IDENTICAL_LINES, 0);
+  return 1;
+}
+
+static int
+changedTextTable (const MenuItem *item, unsigned char setting UNUSED) {
+  return changeTextTable(getMenuItemValue(item));
+}
+
+static int
+changedAttributesTable (const MenuItem *item, unsigned char setting UNUSED) {
+  return changeAttributesTable(getMenuItemValue(item));
+}
+
+static int
+changedKeyboardTable (const MenuItem *item, unsigned char setting UNUSED) {
+  return changeKeyboardTable(getMenuItemValue(item));
+}
+
+static int
+testComputerBraille (void) {
+  return !isContractedBraille();
+}
+
+static int
+testContractedBraille (void) {
+  return isContractedBraille();
+}
+
+static int
+changedContractedBraille (const MenuItem *item, unsigned char setting UNUSED) {
+  setContractedBraille(setting);
+  api.updateParameter(BRLAPI_PARAM_LITERARY_BRAILLE, 0);
+  return 1;
+}
+
+static int
+changedContractionTable (const MenuItem *item, unsigned char setting UNUSED) {
+  return changeContractionTable(getMenuItemValue(item));
+}
+
+static int
+changedComputerBraille (const MenuItem *item, unsigned char setting UNUSED) {
+  setSixDotComputerBraille(setting);
+  api.updateParameter(BRLAPI_PARAM_COMPUTER_BRAILLE_CELL_SIZE, 0);
+  return 1;
+}
+
+static int
+testInputTable (void) {
+  return !!brl.keyTable;
+}
+
+static int
+testKeyboardTable (void) {
+  return !!keyboardTable;
+}
+
+static MenuItem *
+newProfileMenuItem (Menu *menu, const ProfileDescriptor *profile) {
+  MenuString name = {.label = profile->category};
+
+  return newFilesMenuItem(menu, &name, opt_tablesDirectory, PROFILES_SUBDIRECTORY, profile->extension, "", 1);
+}
+
+static int
+changedProfile (const ProfileDescriptor *profile, const MenuItem *item) {
+  const char *value = getMenuItemValue(item);
+
+  if (*value) {
+    activateProfile(profile, opt_tablesDirectory, value);
+  } else {
+    deactivateProfile(profile);
+  }
+
+  return 1;
+}
+
+static int
+changedLanguageProfile (const MenuItem *item, unsigned char setting UNUSED) {
+  return changedProfile(&languageProfile, item);
+}
+
+static MenuItem *
+newStatusFieldMenuItem (
+  Menu *menu, unsigned char number,
+  MenuItemTester *test, MenuItemChanged *changed
+) {
+  static const MenuString strings[] = {
+    [sfEnd] = {.label=strtext("End")},
+    [sfWindowCoordinates2] = {.label=strtext("Window Coordinates"), .comment=strtext("2 cells")},
+    [sfWindowColumn] = {.label=strtext("Window Column"), .comment=strtext("1 cell")},
+    [sfWindowRow] = {.label=strtext("Window Row"), .comment=strtext("1 cell")},
+    [sfCursorCoordinates2] = {.label=strtext("Cursor Coordinates"), .comment=strtext("2 cells")},
+    [sfCursorColumn] = {.label=strtext("Cursor Column"), .comment=strtext("1 cell")},
+    [sfCursorRow] = {.label=strtext("Cursor Row"), .comment=strtext("1 cell")},
+    [sfCursorAndWindowColumn2] = {.label=strtext("Cursor and Window Column"), .comment=strtext("2 cells")},
+    [sfCursorAndWindowRow2] = {.label=strtext("Cursor and Window Row"), .comment=strtext("2 cells")},
+    [sfScreenNumber] = {.label=strtext("Screen Number"), .comment=strtext("1 cell")},
+    [sfStateDots] = {.label=strtext("State Dots"), .comment=strtext("1 cell")},
+    [sfStateLetter] = {.label=strtext("State Letter"), .comment=strtext("1 cell")},
+    [sfTime] = {.label=strtext("Time"), .comment=strtext("2 cells")},
+    [sfAlphabeticWindowCoordinates] = {.label=strtext("Alphabetic Window Coordinates"), .comment=strtext("1 cell")},
+    [sfAlphabeticCursorCoordinates] = {.label=strtext("Alphabetic Cursor Coordinates"), .comment=strtext("1 cell")},
+    [sfGeneric] = {.label=strtext("Generic")},
+    [sfCursorCoordinates3] = {.label=strtext("Cursor Coordinates"), .comment=strtext("3 cells")},
+    [sfWindowCoordinates3] = {.label=strtext("Window Coordinates"), .comment=strtext("3 cells")},
+    [sfCursorAndWindowColumn3] = {.label=strtext("Cursor and Window Column"), .comment=strtext("3 cells")},
+    [sfCursorAndWindowRow3] = {.label=strtext("Cursor and Window Row"), .comment=strtext("3 cells")},
+    [sfSpace] = {.label=strtext("Space"), .comment=strtext("1 cell")},
+  };
+
+  MenuString name = {
+    .label = strtext("Status Field")
+  };
+
+  char *comment;
+  {
+    char buffer[0X3];
+    snprintf(buffer, sizeof(buffer), "%u", number);
+    name.comment = comment = strdup(buffer);
+  }
+
+  if (comment) {
+    MenuItem *item = newEnumeratedMenuItem(menu, &prefs.statusFields[number-1], &name, strings);
+
+    if (item) {
+      setMenuItemTester(item, test);
+      setMenuItemChanged(item, changed);
+      return item;
+    }
+
+    free(comment);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static MenuItem *
+newBlinkVisibleMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
+  return newPercentMenuItem(menu, setting, name, 5);
+}
+
+#if defined(HAVE_PCM_SUPPORT) || defined(HAVE_MIDI_SUPPORT) || defined(HAVE_FM_SUPPORT)
+static MenuItem *
+newVolumeMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
+  return newPercentMenuItem(menu, setting, name, 5);
+}
+#endif /* defined(HAVE_PCM_SUPPORT) || defined(HAVE_MIDI_SUPPORT) || defined(HAVE_FM_SUPPORT) */
+
+#ifdef HAVE_MIDI_SUPPORT
+static MenuString *
+makeMidiInstrumentMenuStrings (void) {
+  MenuString *strings = malloc(midiInstrumentCount * sizeof(*strings));
+
+  if (strings) {
+    for (unsigned int instrument=0; instrument<midiInstrumentCount; instrument+=1) {
+      MenuString *string = &strings[instrument];
+      string->label = midiInstrumentTable[instrument];
+      string->comment = midiGetInstrumentGroup(instrument);
+    }
+  }
+
+  return strings;
+}
+#endif /* HAVE_MIDI_SUPPORT */
+
+static Menu *logMessagesMenu = NULL;
+static const LogEntry *newestLogMessage = NULL;
+
+static int
+addNewLogMessages (const LogEntry *message) {
+  if (message == newestLogMessage) return 1;
+  if (!addNewLogMessages(getPreviousLogEntry(message))) return 0;
+
+  MenuString name;
+  const TimeValue *time = getLogEntryTime(message);
+  unsigned int count = getLogEntryCount(message);
+
+  if (time) {
+    char buffer[0X20];
+    formatSeconds(buffer, sizeof(buffer), "%Y-%m-%d@%H:%M:%S", time->seconds);
+    name.label = strdup(buffer);
+  } else {
+    name.label = NULL;
+  }
+
+  if (count > 1) {
+    char buffer[0X10];
+    snprintf(buffer, sizeof(buffer), "(%u)", count);
+    name.comment = strdup(buffer);
+  } else {
+    name.comment = NULL;
+  }
+
+  MenuItem *item = newTextMenuItem(logMessagesMenu, &name, getLogEntryText(message));
+  if (!item) return 0;
+
+  newestLogMessage = message;
+  return 1;
+}
+
+int
+updateLogMessagesSubmenu (void) {
+  return addNewLogMessages(getNewestLogMessage(1));
+}
+
+static Menu *
+makePreferencesMenu (void) {
+  static const MenuString cursorStyles[] = {
+    [csBottomDots] = {.label=strtext("Underline"), .comment=strtext("dots 7 and 8")},
+    [csAllDots] = {.label=strtext("Block"), .comment=strtext("all dots")},
+    [csLowerLeftDot] = {.label=strtext("Lower Left Dot"), .comment=strtext("dot 7")},
+    [csLowerRightDot] = {.label=strtext("Lower Right Dot"), .comment=strtext("dot 8")},
+    [csNoDots] = {.label=strtext("Hide"), .comment=strtext("no dots")},
+  };
+
+  Menu *rootMenu = newMenu();
+  if (!rootMenu) goto noMenu;
+
+  {
+    NAME(strtext("Save on Exit"));
+    ITEM(newBooleanMenuItem(rootMenu, &prefs.saveOnExit, &itemName));
+  }
+
+  {
+    SUBMENU(optionsSubmenu, rootMenu, strtext("Menu Options"));
+
+    {
+      NAME(strtext("Show Submenu Sizes"));
+      ITEM(newBooleanMenuItem(optionsSubmenu, &prefs.showSubmenuSizes, &itemName));
+    }
+
+    {
+      NAME(strtext("Show Advanced Submenus"));
+      ITEM(newBooleanMenuItem(optionsSubmenu, &prefs.showAdvancedSubmenus, &itemName));
+    }
+
+    {
+      NAME(strtext("Show All Items"));
+      ITEM(newBooleanMenuItem(optionsSubmenu, &prefs.showAllItems, &itemName));
+    }
+  }
+
+  {
+    SUBMENU(presentationSubmenu, rootMenu, strtext("Braille Presentation"));
+
+    {
+      static const MenuString strings[] = {
+        {.label=strtext("Computer Braille")},
+        {.label=strtext("Contracted Braille")},
+      };
+
+      NAME(strtext("Braille Variant"));
+      PROPERTY(yes, isContractedBraille());
+      ITEM(newEnumeratedMenuItem(presentationSubmenu, &yes, &itemName, strings));
+      CHANGED(ContractedBraille);
+    }
+
+    {
+      NAME(strtext("Expand Current Word"));
+      ITEM(newBooleanMenuItem(presentationSubmenu, &prefs.expandCurrentWord, &itemName));
+      TEST(ContractedBraille);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [CTB_CAP_NONE] = {.label=strtext("No Capitalization")},
+        [CTB_CAP_SIGN] = {.label=strtext("Use Capital Sign")},
+        [CTB_CAP_DOT7] = {.label=strtext("Superimpose Dot 7")},
+      };
+
+      NAME(strtext("Capitalization Mode"));
+      ITEM(newEnumeratedMenuItem(presentationSubmenu, &prefs.capitalizationMode, &itemName, strings));
+      TEST(ContractedBraille);
+    }
+
+    {
+      static const MenuString strings[] = {
+        {.label=strtext("8-dot")},
+        {.label=strtext("6-dot")},
+      };
+
+      NAME(strtext("Computer Braille Cell Type"));
+      PROPERTY(yes, isSixDotComputerBraille());
+      ITEM(newEnumeratedMenuItem(presentationSubmenu, &yes, &itemName, strings));
+      TEST(ComputerBraille);
+      CHANGED(ComputerBraille);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [BRL_FIRMNESS_MINIMUM] = {.label=strtext("Minimum")},
+        [BRL_FIRMNESS_LOW] = {.label=strtext("Low")},
+        [BRL_FIRMNESS_MEDIUM] = {.label=strtext("Medium")},
+        [BRL_FIRMNESS_HIGH] = {.label=strtext("High")},
+        [BRL_FIRMNESS_MAXIMUM] = {.label=strtext("Maximum")},
+      };
+
+      NAME(strtext("Braille Firmness"));
+      ITEM(newEnumeratedMenuItem(presentationSubmenu, &prefs.brailleFirmness, &itemName, strings));
+      TEST(BrailleFirmness);
+      CHANGED(BrailleFirmness);
+    }
+  }
+
+  {
+    SUBMENU(indicatorsSubmenu, rootMenu, strtext("Text Indicators"));
+
+    {
+      NAME(strtext("Show Screen Cursor"));
+      ITEM(newBooleanMenuItem(indicatorsSubmenu, &prefs.showScreenCursor, &itemName));
+    }
+
+    {
+      NAME(strtext("Screen Cursor Style"));
+      ITEM(newEnumeratedMenuItem(indicatorsSubmenu, &prefs.screenCursorStyle, &itemName, cursorStyles));
+      TEST(ShowScreenCursor);
+    }
+
+    {
+      NAME(strtext("Blinking Screen Cursor"));
+      ITEM(newBooleanMenuItem(indicatorsSubmenu, &prefs.blinkingScreenCursor, &itemName));
+      TEST(ShowScreenCursor);
+    }
+
+    {
+      NAME(strtext("Screen Cursor Blink Period"));
+      BLINK_PERIOD(&screenCursorBlinkDescriptor);
+      ITEM(newTimeMenuItem(indicatorsSubmenu, &period, &itemName));
+      TEST(BlinkingScreenCursor);
+      CHANGED(ScreenCursorBlinkPeriod);
+    }
+
+    {
+      NAME(strtext("Screen Cursor Percent Visible"));
+      BLINK_VISIBLE(&screenCursorBlinkDescriptor);
+      ITEM(newBlinkVisibleMenuItem(indicatorsSubmenu, &visible, &itemName));
+      TEST(BlinkingScreenCursor);
+      CHANGED(ScreenCursorBlinkPercentage);
+    }
+
+    {
+      NAME(strtext("Show Attributes"));
+      ITEM(newBooleanMenuItem(indicatorsSubmenu, &prefs.showAttributes, &itemName));
+    }
+
+    {
+      NAME(strtext("Blinking Attributes"));
+      ITEM(newBooleanMenuItem(indicatorsSubmenu, &prefs.blinkingAttributes, &itemName));
+      TEST(ShowAttributes);
+    }
+
+    {
+      NAME(strtext("Attributes Blink Period"));
+      BLINK_PERIOD(&attributesUnderlineBlinkDescriptor);
+      ITEM(newTimeMenuItem(indicatorsSubmenu, &period, &itemName));
+      TEST(BlinkingAttributes);
+      CHANGED(AttributesUnderlineBlinkPeriod);
+    }
+
+    {
+      NAME(strtext("Attributes Percent Visible"));
+      BLINK_VISIBLE(&attributesUnderlineBlinkDescriptor);
+      ITEM(newBlinkVisibleMenuItem(indicatorsSubmenu, &visible, &itemName));
+      TEST(BlinkingAttributes);
+      CHANGED(AttributesUnderlineBlinkPercentage);
+    }
+
+    {
+      NAME(strtext("Blinking Capitals"));
+      ITEM(newBooleanMenuItem(indicatorsSubmenu, &prefs.blinkingCapitals, &itemName));
+    }
+
+    {
+      NAME(strtext("Capitals Blink Period"));
+      BLINK_PERIOD(&uppercaseLettersBlinkDescriptor);
+      ITEM(newTimeMenuItem(indicatorsSubmenu, &period, &itemName));
+      TEST(BlinkingCapitals);
+      CHANGED(UppercaseLettersBlinkPeriod);
+    }
+
+    {
+      NAME(strtext("Capitals Percent Visible"));
+      BLINK_VISIBLE(&uppercaseLettersBlinkDescriptor);
+      ITEM(newBlinkVisibleMenuItem(indicatorsSubmenu, &visible, &itemName));
+      TEST(BlinkingCapitals);
+      CHANGED(UppercaseLettersBlinkPercentage);
+    }
+  }
+
+  {
+    SUBMENU(navigationSubmenu, rootMenu, strtext("Navigation Options"));
+
+    {
+      NAME(strtext("Word Wrap"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.wordWrap, &itemName));
+    }
+
+    {
+      NAME(strtext("Skip Identical Lines"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.skipIdenticalLines, &itemName));
+      CHANGED(SkipIdenticalLines);
+    }
+
+    {
+      NAME(strtext("Skip Blank Braille Windows"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.skipBlankBrailleWindows, &itemName));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [sbwAll] = {.label=strtext("All")},
+        [sbwEndOfLine] = {.label=strtext("End of Line")},
+        [sbwRestOfLine] = {.label=strtext("Rest of Line")},
+      };
+
+      NAME(strtext("Skip Which Blank Braille Windows"));
+      ITEM(newEnumeratedMenuItem(navigationSubmenu, &prefs.skipBlankBrailleWindowsMode, &itemName, strings));
+    }
+
+    {
+      NAME(strtext("Sliding Braille Window"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.slidingBrailleWindow, &itemName));
+    }
+
+    {
+      NAME(strtext("Eager Sliding Braille Window"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.eagerSlidingBrailleWindow, &itemName));
+      TEST(SlidingBrailleWindow);
+    }
+
+    {
+      NAME(strtext("Braille Window Overlap"));
+      ITEM(newNumericMenuItem(navigationSubmenu, &prefs.brailleWindowOverlap, &itemName, 0, 20, 1, strtext("cells"), NULL));
+      CHANGED(BrailleWindowOverlap);
+    }
+
+    {
+      NAME(strtext("Scroll-aware Cursor Navigation"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.scrollAwareCursorNavigation, &itemName));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [ctdNone] = {.label=strtext("None")},
+        [ctd250ms] = {.label=strtext("250 milliseconds")},
+        [ctd500ms] = {.label=strtext("500 milliseconds")},
+        [ctd1s] = {.label=strtext("1 second")},
+        [ctd2s] = {.label=strtext("2 seconds")},
+      };
+
+      NAME(strtext("Cursor Tracking Delay"));
+      ITEM(newEnumeratedMenuItem(navigationSubmenu, &prefs.cursorTrackingDelay, &itemName, strings));
+    }
+
+    {
+      NAME(strtext("Track Screen Scroll"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.trackScreenScroll, &itemName));
+    }
+
+  #ifdef HAVE_LIBGPM
+    {
+      NAME(strtext("Track Screen Pointer"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.trackScreenPointer, &itemName));
+    }
+  #endif /* HAVE_LIBGPM */
+
+    {
+      NAME(strtext("Highlight Braille Window Location"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.highlightBrailleWindowLocation, &itemName));
+    }
+
+    {
+      NAME(strtext("Start Selection with Routing Key"));
+      ITEM(newBooleanMenuItem(navigationSubmenu, &prefs.startSelectionWithRoutingKey, &itemName));
+    }
+  }
+
+  {
+    SUBMENU(typingSubmenu, rootMenu, strtext("Braille Typing"));
+
+    {
+      NAME(strtext("Keyboard Enabled"));
+      ITEM(newBooleanMenuItem(typingSubmenu, &prefs.brailleKeyboardEnabled, &itemName));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [BRL_TYPING_TEXT] = {.label=strtext("Translated via Text Table")},
+        [BRL_TYPING_DOTS] = {.label=strtext("Dots via Unicode Braille")},
+      };
+
+      NAME(strtext("Typing Mode"));
+      ITEM(newEnumeratedMenuItem(typingSubmenu, &prefs.brailleTypingMode, &itemName, strings));
+    }
+
+    {
+      NAME(strtext("Quick Space"));
+      ITEM(newBooleanMenuItem(typingSubmenu, &prefs.brailleQuickSpace, &itemName));
+    }
+  }
+
+  {
+    SUBMENU(inputSubmenu, rootMenu, strtext("Input Options"));
+
+    {
+      static const MenuString strings[] = {
+        [atOff] = {.label=strtext("Off")},
+        [at5s] = {.label=strtext("5 seconds")},
+        [at10s] = {.label=strtext("10 seconds")},
+        [at20s] = {.label=strtext("20 seconds")},
+        [at40s] = {.label=strtext("40 seconds")},
+      };
+
+      NAME(strtext("Autorelease Time"));
+      ITEM(newEnumeratedMenuItem(inputSubmenu, &prefs.autoreleaseTime, &itemName, strings));
+      CHANGED(AutoreleaseTime);
+    }
+
+    {
+      NAME(strtext("On First Release"));
+      ITEM(newBooleanMenuItem(inputSubmenu, &prefs.onFirstRelease, &itemName));
+    }
+
+    {
+      NAME(strtext("Long Press Time"));
+      ITEM(newTimeMenuItem(inputSubmenu, &prefs.longPressTime, &itemName));
+      CHANGED(AutorepeatDelay);
+    }
+
+    {
+      NAME(strtext("Autorepeat Enabled"));
+      ITEM(newBooleanMenuItem(inputSubmenu, &prefs.autorepeatEnabled, &itemName));
+      CHANGED(AutorepeatEnabled);
+    }
+
+    {
+      NAME(strtext("Autorepeat Interval"));
+      ITEM(newTimeMenuItem(inputSubmenu, &prefs.autorepeatInterval, &itemName));
+      TEST(AutorepeatEnabled);
+      CHANGED(AutorepeatInterval);
+    }
+
+    {
+      NAME(strtext("Autorepeat Panning"));
+      ITEM(newBooleanMenuItem(inputSubmenu, &prefs.autorepeatPanning, &itemName));
+      TEST(AutorepeatEnabled);
+    }
+
+    {
+      NAME(strtext("Touch Navigation"));
+      ITEM(newBooleanMenuItem(inputSubmenu, &prefs.touchNavigation, &itemName));
+      TEST(TouchSensitivity);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [BRL_SENSITIVITY_MINIMUM] = {.label=strtext("Minimum")},
+        [BRL_SENSITIVITY_LOW] = {.label=strtext("Low")},
+        [BRL_SENSITIVITY_MEDIUM] = {.label=strtext("Medium")},
+        [BRL_SENSITIVITY_HIGH] = {.label=strtext("High")},
+        [BRL_SENSITIVITY_MAXIMUM] = {.label=strtext("Maximum")},
+      };
+
+      NAME(strtext("Touch Sensitivity"));
+      ITEM(newEnumeratedMenuItem(inputSubmenu, &prefs.touchSensitivity, &itemName, strings));
+      TEST(TouchSensitivity);
+      CHANGED(TouchSensitivity);
+    }
+
+    {
+      NAME(strtext("Keyboard Table"));
+      ITEM(newFilesMenuItem(inputSubmenu, &itemName, opt_tablesDirectory, KEYBOARD_TABLES_SUBDIRECTORY, KEY_TABLE_EXTENSION, opt_keyboardTable, 1));
+      CHANGED(KeyboardTable);
+      SET(keyboardTable);
+    }
+  }
+
+  {
+    SUBMENU(alertsSubmenu, rootMenu, strtext("Event Alerts"));
+
+    {
+      NAME(strtext("Console Bell Alert"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.consoleBellAlert, &itemName));
+      TEST(ConsoleBellAlert);
+      CHANGED(ConsoleBellAlert);
+    }
+
+    {
+      NAME(strtext("Keyboard LED Alerts"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.keyboardLedAlerts, &itemName));
+      TEST(KeyboardLedAlerts);
+      CHANGED(KeyboardLedAlerts);
+    }
+
+    {
+      NAME(strtext("Alert Tunes"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.alertTunes, &itemName));
+      CHANGED(AlertTunes);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [tdBeeper] = {.label=strtext("Beeper"), .comment=strtext("console tone generator")},
+        [tdPcm] = {.label=strtext("PCM"), .comment=strtext("soundcard digital audio")},
+        [tdMidi] = {.label=strtext("MIDI"), .comment=strtext("Musical Instrument Digital Interface")},
+        [tdFm] = {.label=strtext("FM"), .comment=strtext("soundcard synthesizer")},
+      };
+
+      NAME(strtext("Tune Device"));
+      ITEM(newEnumeratedMenuItem(alertsSubmenu, &prefs.tuneDevice, &itemName, strings));
+      TEST(AlertTunes);
+      CHANGED(TuneDevice);
+    }
+
+  #ifdef HAVE_PCM_SUPPORT
+    {
+      NAME(strtext("PCM Volume"));
+      ITEM(newVolumeMenuItem(alertsSubmenu, &prefs.pcmVolume, &itemName));
+      TEST(TunesPcm);
+    }
+  #endif /* HAVE_PCM_SUPPORT */
+
+  #ifdef HAVE_MIDI_SUPPORT
+    {
+      NAME(strtext("MIDI Volume"));
+      ITEM(newVolumeMenuItem(alertsSubmenu, &prefs.midiVolume, &itemName));
+      TEST(TunesMidi);
+    }
+
+    {
+      const MenuString *strings = makeMidiInstrumentMenuStrings();
+      if (!strings) goto noItem;
+
+      {
+        NAME(strtext("MIDI Instrument"));
+        ITEM(newStringsMenuItem(alertsSubmenu, &prefs.midiInstrument, &itemName, strings, midiInstrumentCount));
+        TEST(TunesMidi);
+      }
+    }
+  #endif /* HAVE_MIDI_SUPPORT */
+
+  #ifdef HAVE_FM_SUPPORT
+    {
+      NAME(strtext("FM Volume"));
+      ITEM(newVolumeMenuItem(alertsSubmenu, &prefs.fmVolume, &itemName));
+      TEST(TunesFm);
+    }
+  #endif /* HAVE_FM_SUPPORT */
+
+    {
+      NAME(strtext("Alert Dots"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.alertDots, &itemName));
+    }
+
+    {
+      NAME(strtext("Alert Messages"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.alertMessages, &itemName));
+    }
+
+#ifdef ENABLE_SPEECH_SUPPORT
+    {
+      NAME(strtext("Speak Key Context"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.speakKeyContext, &itemName));
+    }
+
+    {
+      NAME(strtext("Speak Modifier Key"));
+      ITEM(newBooleanMenuItem(alertsSubmenu, &prefs.speakModifierKey, &itemName));
+    }
+#endif /* ENABLE_SPEECH_SUPPORT */
+  }
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  {
+    SUBMENU(autospeakSubmenu, rootMenu, strtext("Autospeak Options"));
+
+    {
+      NAME(strtext("Autospeak"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeak, &itemName));
+    }
+
+    {
+      NAME(strtext("Speak Selected Line"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakSelectedLine, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Selected Character"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakSelectedCharacter, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Inserted Characters"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakInsertedCharacters, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Deleted Characters"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakDeletedCharacters, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Replaced Characters"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakReplacedCharacters, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Completed Words"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakCompletedWords, &itemName));
+      TEST(Autospeak);
+    }
+
+    {
+      NAME(strtext("Speak Line Indent"));
+      ITEM(newBooleanMenuItem(autospeakSubmenu, &prefs.autospeakLineIndent, &itemName));
+      TEST(Autospeak);
+    }
+  }
+
+  {
+    SUBMENU(speechSubmenu, rootMenu, strtext("Speech Options"));
+
+    {
+      NAME(strtext("Speech Volume"));
+      ITEM(newNumericMenuItem(speechSubmenu, &prefs.speechVolume, &itemName, 0, SPK_VOLUME_MAXIMUM, 1, "%", formatSpeechVolume));
+      TEST(SpeechVolume);
+      CHANGED(SpeechVolume);
+    }
+
+    {
+      NAME(strtext("Speech Rate"));
+      ITEM(newNumericMenuItem(speechSubmenu, &prefs.speechRate, &itemName, 0, SPK_RATE_MAXIMUM, 1, NULL, formatSpeechRate));
+      TEST(SpeechRate);
+      CHANGED(SpeechRate);
+    }
+
+    {
+      NAME(strtext("Speech Pitch"));
+      ITEM(newNumericMenuItem(speechSubmenu, &prefs.speechPitch, &itemName, 0, SPK_PITCH_MAXIMUM, 1, NULL, formatSpeechPitch));
+      TEST(SpeechPitch);
+      CHANGED(SpeechPitch);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [SPK_PUNCTUATION_NONE] = {.label=strtext("None")},
+        [SPK_PUNCTUATION_SOME] = {.label=strtext("Some")},
+        [SPK_PUNCTUATION_ALL] = {.label=strtext("All")},
+      };
+
+      NAME(strtext("Speech Punctuation"));
+      ITEM(newEnumeratedMenuItem(speechSubmenu, &prefs.speechPunctuation, &itemName, strings));
+      TEST(SpeechPunctuation);
+      CHANGED(SpeechPunctuation);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [sucNone] = {.label=strtext("None")},
+        // "cap" here, used during speech output, is short for "capital".
+        // It is spoken just before an uppercase letter, e.g. "cap A".
+        [sucSayCap] = {.label=strtext("Say Cap")},
+        [sucRaisePitch] = {.label=strtext("Raise Pitch")},
+      };
+
+      NAME(strtext("Speech Uppercase Indicator"));
+      ITEM(newEnumeratedMenuItem(speechSubmenu, &prefs.speechUppercaseIndicator, &itemName, strings));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [swsNone] = {.label=strtext("None")},
+        [swsSaySpace] = {.label=strtext("Say Space")},
+      };
+
+      NAME(strtext("Speech Whitespace Indicator"));
+      ITEM(newEnumeratedMenuItem(speechSubmenu, &prefs.speechWhitespaceIndicator, &itemName, strings));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [sayImmediate] = {.label=strtext("Immediate")},
+        [sayEnqueue] = {.label=strtext("Enqueue")},
+      };
+
+      NAME(strtext("Say Line Mode"));
+      ITEM(newEnumeratedMenuItem(speechSubmenu, &prefs.sayLineMode, &itemName, strings));
+    }
+
+    {
+      NAME(strtext("Show Speech Cursor"));
+      ITEM(newBooleanMenuItem(speechSubmenu, &prefs.showSpeechCursor, &itemName));
+    }
+
+    {
+      NAME(strtext("Speech Cursor Style"));
+      ITEM(newEnumeratedMenuItem(speechSubmenu, &prefs.speechCursorStyle, &itemName, cursorStyles));
+      TEST(ShowSpeechCursor);
+    }
+
+    {
+      NAME(strtext("Blinking Speech Cursor"));
+      ITEM(newBooleanMenuItem(speechSubmenu, &prefs.blinkingSpeechCursor, &itemName));
+      TEST(ShowSpeechCursor);
+    }
+
+    {
+      NAME(strtext("Speech Cursor Blink Period"));
+      BLINK_PERIOD(&speechCursorBlinkDescriptor);
+      ITEM(newTimeMenuItem(speechSubmenu, &period, &itemName));
+      TEST(BlinkingSpeechCursor);
+      CHANGED(SpeechCursorBlinkPeriod);
+    }
+
+    {
+      NAME(strtext("Speech Cursor Percent Visible"));
+      BLINK_VISIBLE(&speechCursorBlinkDescriptor);
+      ITEM(newBlinkVisibleMenuItem(speechSubmenu, &visible, &itemName));
+      TEST(BlinkingSpeechCursor);
+      CHANGED(SpeechCursorBlinkPercentage);
+    }
+  }
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  {
+    SUBMENU(timeSubmenu, rootMenu, strtext("Time Presentation"));
+
+    {
+      static const MenuString strings[] = {
+        [tf24Hour] = {.label=strtext("24 Hour")},
+        [tf12Hour] = {.label=strtext("12 Hour")},
+      };
+
+      NAME(strtext("Time Format"));
+      ITEM(newEnumeratedMenuItem(timeSubmenu, &prefs.timeFormat, &itemName, strings));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [tsColon] = {.label=strtext("Colon"), ":"},
+        [tsDot] = {.label=strtext("Dot"), "."},
+      };
+
+      NAME(strtext("Time Separator"));
+      ITEM(newEnumeratedMenuItem(timeSubmenu, &prefs.timeSeparator, &itemName, strings));
+    }
+
+    {
+      NAME(strtext("Show Seconds"));
+      ITEM(newBooleanMenuItem(timeSubmenu, &prefs.showSeconds, &itemName));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [dpNone] = {.label=strtext("None")},
+        [dpBeforeTime] = {.label=strtext("Before Time")},
+        [dpAfterTime] = {.label=strtext("After Time")},
+      };
+
+      NAME(strtext("Date Position"));
+      ITEM(newEnumeratedMenuItem(timeSubmenu, &prefs.datePosition, &itemName, strings));
+    }
+
+    {
+      static const MenuString strings[] = {
+        [dfYearMonthDay] = {.label=strtext("Year Month Day")},
+        [dfMonthDayYear] = {.label=strtext("Month Day Year")},
+        [dfDayMonthYear] = {.label=strtext("Day Month Year")},
+      };
+
+      NAME(strtext("Date Format"));
+      ITEM(newEnumeratedMenuItem(timeSubmenu, &prefs.dateFormat, &itemName, strings));
+      TEST(ShowDate);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [dsDash] = {.label=strtext("Dash"), "-"},
+        [dsSlash] = {.label=strtext("Slash"), "/"},
+        [dsDot] = {.label=strtext("Dot"), "."},
+      };
+
+      NAME(strtext("Date Separator"));
+      ITEM(newEnumeratedMenuItem(timeSubmenu, &prefs.dateSeparator, &itemName, strings));
+      TEST(ShowDate);
+    }
+  }
+
+  {
+    SUBMENU(statusSubmenu, rootMenu, strtext("Status Cells"));
+
+    {
+      static const MenuString strings[] = {
+        [spNone] = {.label=strtext("None")},
+        [spLeft] = {.label=strtext("Left")},
+        [spRight] = {.label=strtext("Right")},
+      };
+
+      NAME(strtext("Status Position"));
+      ITEM(newEnumeratedMenuItem(statusSubmenu, &prefs.statusPosition, &itemName, strings));
+      TEST(StatusPosition);
+      CHANGED(StatusPosition);
+    }
+
+    {
+      NAME(strtext("Status Count"));
+      ITEM(newNumericMenuItem(statusSubmenu, &prefs.statusCount, &itemName, 0, MAX((int)brl.textColumns/2-1, 0), 1, strtext("cells"), NULL));
+      TEST(StatusCount);
+      CHANGED(StatusCount);
+    }
+
+    {
+      static const MenuString strings[] = {
+        [ssNone] = {.label=strtext("None")},
+        [ssSpace] = {.label=strtext("Space")},
+        [ssBlock] = {.label=strtext("Block")},
+        [ssStatusSide] = {.label=strtext("Status Side")},
+        [ssTextSide] = {.label=strtext("Text Side")},
+      };
+
+      NAME(strtext("Status Separator"));
+      ITEM(newEnumeratedMenuItem(statusSubmenu, &prefs.statusSeparator, &itemName, strings));
+      TEST(StatusSeparator);
+      CHANGED(StatusSeparator);
+    }
+
+    {
+      #define STATUS_FIELD_ITEM(number) { ITEM(newStatusFieldMenuItem(statusSubmenu, number, testStatusField##number, changedStatusField##number)); }
+      STATUS_FIELD_ITEM(1);
+      STATUS_FIELD_ITEM(2);
+      STATUS_FIELD_ITEM(3);
+      STATUS_FIELD_ITEM(4);
+      STATUS_FIELD_ITEM(5);
+      STATUS_FIELD_ITEM(6);
+      STATUS_FIELD_ITEM(7);
+      STATUS_FIELD_ITEM(8);
+      STATUS_FIELD_ITEM(9);
+      #undef STATUS_FIELD_ITEM
+    }
+  }
+
+  {
+    SUBMENU(tablesSubmenu, rootMenu, strtext("Braille Tables"));
+
+    {
+      NAME(strtext("Text Table"));
+      ITEM(newFilesMenuItem(tablesSubmenu, &itemName, opt_tablesDirectory, TEXT_TABLES_SUBDIRECTORY, TEXT_TABLE_EXTENSION, opt_textTable, 0));
+      CHANGED(TextTable);
+      SET(textTable);
+    }
+
+    {
+      NAME(strtext("Contraction Table"));
+      ITEM(newFilesMenuItem(tablesSubmenu, &itemName, opt_tablesDirectory, CONTRACTION_TABLES_SUBDIRECTORY, CONTRACTION_TABLE_EXTENSION, opt_contractionTable, 1));
+      CHANGED(ContractionTable);
+      SET(contractionTable);
+    }
+
+    {
+      NAME(strtext("Attributes Table"));
+      ITEM(newFilesMenuItem(tablesSubmenu, &itemName, opt_tablesDirectory, ATTRIBUTES_TABLES_SUBDIRECTORY, ATTRIBUTES_TABLE_EXTENSION, opt_attributesTable, 0));
+      CHANGED(AttributesTable);
+      SET(attributesTable);
+    }
+  }
+
+  {
+    SUBMENU(profilesSubmenu, rootMenu, strtext("Profiles"));
+
+    {
+      ITEM(newProfileMenuItem(profilesSubmenu, &languageProfile));
+      CHANGED(LanguageProfile);
+      SET(languageProfile);
+    }
+  }
+
+  {
+    SUBMENU(buildSubmenu, rootMenu, strtext("Build Information"));
+    setAdvancedSubmenu(buildSubmenu);
+
+    {
+      NAME(strtext("Package Version"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, PACKAGE_VERSION));
+    }
+
+    {
+      NAME(strtext("Package Revision"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, getRevisionIdentifier()));
+    }
+
+    {
+      NAME(strtext("Web Site"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, PACKAGE_URL));
+    }
+
+    {
+      NAME(strtext("Mailing List"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, PACKAGE_BUGREPORT));
+    }
+
+    {
+      NAME(strtext("Configuration Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, CONFIGURATION_DIRECTORY));
+    }
+
+    {
+      NAME(strtext("Configuration File"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, CONFIGURATION_FILE));
+    }
+
+    {
+      NAME(strtext("Updatable Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, UPDATABLE_DIRECTORY));
+    }
+
+    {
+      NAME(strtext("Preferences File"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, PREFERENCES_FILE));
+    }
+
+    {
+      NAME(strtext("Writable Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, WRITABLE_DIRECTORY));
+    }
+
+    {
+      NAME(strtext("Drivers Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, DRIVERS_DIRECTORY));
+    }
+
+    {
+      NAME(strtext("Tables Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, TABLES_DIRECTORY));
+    }
+
+    {
+      NAME(strtext("Locale Directory"));
+      ITEM(newTextMenuItem(buildSubmenu, &itemName, LOCALE_DIRECTORY));
+    }
+  }
+
+  {
+    static const MenuString logLevels[] = {
+      [LOG_EMERG] = {.label=strtext("Emergency")},
+      [LOG_ALERT] = {.label=strtext("Alert")},
+      [LOG_CRIT] = {.label=strtext("Critical")},
+      [LOG_ERR] = {.label=strtext("Error")},
+      [LOG_WARNING] = {.label=strtext("Warning")},
+      [LOG_NOTICE] = {.label=strtext("Notice")},
+      [LOG_INFO] = {.label=strtext("Information")},
+      [LOG_DEBUG] = {.label=strtext("Debug")},
+    };
+
+    SUBMENU(internalSubmenu, rootMenu, strtext("Internal Parameters"));
+    setAdvancedSubmenu(internalSubmenu);
+
+    {
+      NAME(strtext("System Log Level"));
+      ITEM(newEnumeratedMenuItem(internalSubmenu, &systemLogLevel, &itemName, logLevels));
+    }
+
+    {
+      NAME(strtext("Standard Error Log Level"));
+      ITEM(newEnumeratedMenuItem(internalSubmenu, &stderrLogLevel, &itemName, logLevels));
+    }
+
+    {
+      NAME(strtext("Category Log Level"));
+      ITEM(newEnumeratedMenuItem(internalSubmenu, &categoryLogLevel, &itemName, logLevels));
+    }
+
+    {
+      SUBMENU(logCategoriesSubmenu, internalSubmenu, strtext("Log Categories"));
+      setAdvancedSubmenu(logCategoriesSubmenu);
+
+      {
+        LogCategoryIndex category;
+
+        for (category=0; category<LOG_CATEGORY_COUNT; category+=1) {
+          const char *description = getLogCategoryTitle(category);
+
+          if (description && *description) {
+            MenuString *name;
+
+            if (!(name = malloc(sizeof(*name)))) goto noItem;
+            memset(name, 0, sizeof(*name));
+            name->label = description;
+
+            {
+              ITEM(newBooleanMenuItem(logCategoriesSubmenu, &logCategoryFlags[category], name));
+
+              switch (category) {
+                case LOG_CATEGORY_INDEX(BRAILLE_KEYS):
+                  TEST(InputTable);
+                  break;
+
+                case LOG_CATEGORY_INDEX(KEYBOARD_KEYS):
+                  TEST(KeyboardTable);
+                  break;
+
+                default:
+                  break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  {
+    SUBMENU(toolsSubmenu, rootMenu, strtext("Tools"));
+    setAdvancedSubmenu(toolsSubmenu);
+
+    {
+      NAME(strtext("Restart Braille Driver"));
+      ITEM(newToolMenuItem(toolsSubmenu, &itemName, restartBrailleDriver));
+    }
+
+  #ifdef ENABLE_SPEECH_SUPPORT
+    {
+      NAME(strtext("Restart Speech Driver"));
+      ITEM(newToolMenuItem(toolsSubmenu, &itemName, restartSpeechDriver));
+    }
+  #endif /* ENABLE_SPEECH_SUPPORT */
+
+    {
+      NAME(strtext("Restart Screen Driver"));
+      ITEM(newToolMenuItem(toolsSubmenu, &itemName, restartScreenDriver));
+    }
+  }
+
+  {
+    NAME(strtext("Log Messages"));
+    logMessagesMenu = newSubmenuMenuItem(rootMenu, &itemName);
+  }
+
+  return rootMenu;
+
+noItem:
+  destroyMenu(rootMenu);
+noMenu:
+  return NULL;
+}
+
+Menu *
+getPreferencesMenu (void) {
+  static Menu *menu = NULL;
+  if (!menu) menu = makePreferencesMenu();
+  return menu;
+}
diff --git a/Programs/menu_prefs.h b/Programs/menu_prefs.h
new file mode 100644
index 0000000..32d0b25
--- /dev/null
+++ b/Programs/menu_prefs.h
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MENU_PREFS
+#define BRLTTY_INCLUDED_MENU_PREFS
+
+#include "menu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern Menu *getPreferencesMenu (void);
+extern int updateLogMessagesSubmenu (void);
+
+#define PREFS_MENU_ITEM_APPLY(apply) \
+apply(textTable) \
+apply(attributesTable) \
+apply(contractionTable) \
+apply(keyboardTable) \
+apply(languageProfile)
+
+#define PREFS_MENU_ITEM_GETTER_PROTOTYPE(name) MenuItem *getPreferencesMenuItem_##name (void)
+#define PREFS_MENU_ITEM_GETTER_DEFINE(name) extern PREFS_MENU_ITEM_GETTER_PROTOTYPE(name);
+PREFS_MENU_ITEM_APPLY(PREFS_MENU_ITEM_GETTER_DEFINE)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MENU */
diff --git a/Programs/message.c b/Programs/message.c
new file mode 100644
index 0000000..e4fd916
--- /dev/null
+++ b/Programs/message.c
@@ -0,0 +1,265 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "embed.h"
+#include "log.h"
+#include "log_history.h"
+#include "message.h"
+#include "defaults.h"
+#include "async_wait.h"
+#include "async_task.h"
+#include "utf8.h"
+#include "brl_utils.h"
+#include "brl_cmds.h"
+#include "spk.h"
+#include "ktb_types.h"
+#include "update.h"
+#include "cmd_queue.h"
+#include "api_control.h"
+#include "core.h"
+
+int messageHoldTimeout = DEFAULT_MESSAGE_HOLD_TIMEOUT;
+
+typedef struct {
+  const char *mode;
+  MessageOptions options;
+  unsigned presented:1;
+  unsigned deallocate:1;
+  char text[0];
+} MessageParameters;
+
+typedef struct {
+  const wchar_t *start;
+  size_t length;
+} MessageSegment;
+
+typedef struct {
+  const MessageParameters *parameters;
+
+  struct {
+    const MessageSegment *first;
+    const MessageSegment *current;
+    const MessageSegment *last;
+  } segments;
+
+  int timeout;
+  unsigned endWait:1;
+  unsigned hold:1;
+  unsigned touch:1;
+} MessageData;
+
+ASYNC_CONDITION_TESTER(testEndMessageWait) {
+  const MessageData *mgd = data;
+  return mgd->endWait;
+}
+
+static int
+handleMessageCommands (int command, void *data) {
+  MessageData *mgd = data;
+
+  switch (command & BRL_MSK_CMD) {
+    case BRL_CMD_LNUP:
+    case BRL_CMD_PRDIFLN:
+    case BRL_CMD_FWINLTSKIP:
+    case BRL_CMD_FWINLT: {
+      if (mgd->segments.current > mgd->segments.first) {
+        mgd->segments.current -= 1;
+        mgd->endWait = 1;
+      }
+
+      mgd->hold = 1;
+      return 1;
+    }
+
+    case BRL_CMD_LNDN:
+    case BRL_CMD_NXDIFLN:
+    case BRL_CMD_FWINRTSKIP:
+    case BRL_CMD_FWINRT: {
+      if ((mgd->hold = mgd->segments.current < mgd->segments.last)) {
+        mgd->segments.current += 1;
+      }
+
+      break;
+    }
+
+    default: {
+      int arg = command & BRL_MSK_ARG;
+
+      switch (command & BRL_MSK_BLK) {
+        case BRL_CMD_BLK(TOUCH_AT):
+          if ((mgd->touch = arg != BRL_MSK_ARG)) return 1;
+          mgd->timeout = 1000;
+          break;
+
+        default:
+          mgd->hold = 0;
+          break;
+      }
+
+      break;
+    }
+  }
+
+  mgd->endWait = 1;
+  return 1;
+}
+
+ASYNC_TASK_CALLBACK(presentMessage) {
+  MessageParameters *mgp = data;
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  if (!(mgp->options & MSG_SILENT)) {
+    if (isAutospeakActive()) {
+      sayString(&spk, mgp->text, SAY_OPT_MUTE_FIRST);
+    }
+  }
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  if (canBraille()) {
+    MessageData mgd = {
+      .hold = 0,
+      .touch = 0,
+      .parameters = mgp
+    };
+
+    const size_t characterCount = countUtf8Characters(mgp->text);
+    MessageSegment messageSegments[characterCount];
+    wchar_t characters[characterCount + 1];
+    makeWcharsFromUtf8(mgp->text, characters, ARRAY_COUNT(characters));
+
+    const size_t brailleSize = textCount * brl.textRows;
+    wchar_t brailleBuffer[brailleSize];
+
+    {
+      const wchar_t *character = characters;
+      const wchar_t *const end = character + characterCount;
+
+      MessageSegment *segment = messageSegments;
+      mgd.segments.current = mgd.segments.first = segment;
+
+      while (*character) {
+        /* strip leading spaces */
+        while ((character < end) && iswspace(*character)) character += 1;
+
+        const size_t charactersLeft = end - character;
+        if (!charactersLeft) break;
+
+        segment->start = character;
+        segment->length = MIN(charactersLeft, brailleSize);
+
+        character += segment->length;
+        segment += 1;
+      }
+
+      mgd.segments.last = segment - 1;
+    }
+
+    int wasLinked = api.isServerLinked();
+    if (wasLinked) api.unlinkServer();
+
+    suspendUpdates();
+    pushCommandEnvironment("message", NULL, NULL);
+    pushCommandHandler("message", KTB_CTX_WAITING,
+                       handleMessageCommands, NULL, &mgd);
+
+    while (1) {
+      const MessageSegment *segment = mgd.segments.current;
+      size_t cellCount = segment->length;
+      int lastSegment = segment == mgd.segments.last;
+
+      wmemcpy(brailleBuffer, segment->start, cellCount);
+      brl.cursor = BRL_NO_CURSOR;
+
+      if (!writeBrailleCharacters(mgp->mode, brailleBuffer, cellCount)) {
+        mgp->presented = 0;
+        break;
+      }
+
+      mgd.timeout = messageHoldTimeout - brl.writeDelay;
+      drainBrailleOutput(&brl, 0);
+      if (!mgd.hold && lastSegment && (mgp->options & MSG_NODELAY)) break;
+      mgd.timeout = MAX(mgd.timeout, 0);
+
+      while (1) {
+        int timeout = mgd.timeout;
+        mgd.timeout = -1;
+
+        mgd.endWait = 0;
+        int timedOut = !asyncAwaitCondition(timeout, testEndMessageWait, &mgd);
+        if (mgd.segments.current != segment) break;
+
+        if (mgd.hold || mgd.touch) {
+          mgd.timeout = 1000000;
+        } else if (timedOut) {
+          if (lastSegment) goto DONE;
+          mgd.segments.current += 1;
+          mgd.timeout = messageHoldTimeout;
+          break;
+        } else if (mgd.timeout < 0) {
+          goto DONE;
+        }
+      }
+    }
+
+  DONE:
+    popCommandEnvironment();
+    resumeUpdates(1);
+    if (wasLinked) api.linkServer();
+  }
+
+  if (mgp->deallocate) free(mgp);
+}
+
+int 
+message (const char *mode, const char *text, MessageOptions options) {
+  if (options & MSG_LOG) pushLogMessage(text);
+
+  int presented = 0;
+  MessageParameters *mgp;
+  size_t size = sizeof(*mgp) + strlen(text) + 1;
+
+  if ((mgp = malloc(size))) {
+    memset(mgp, 0, size);
+    mgp->mode = mode? mode: "";
+    mgp->options = options;
+    mgp->presented = 1;
+    strcpy(mgp->text, text);
+
+    if (mgp->options & MSG_SYNC) {
+      mgp->deallocate = 0;
+      presentMessage(mgp);
+      if (mgp->presented) presented = 1;
+    } else {
+      mgp->deallocate = 1;
+      if (asyncAddTask(NULL, presentMessage, mgp)) return 1;
+    }
+
+    free(mgp);
+  }
+
+  return presented;
+}
+
+void
+showMessage (const char *text) {
+  message(NULL, text, 0);
+}
diff --git a/Programs/messages.c b/Programs/messages.c
new file mode 100644
index 0000000..d56d42d
--- /dev/null
+++ b/Programs/messages.c
@@ -0,0 +1,632 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "messages.h"
+#include "file.h"
+
+// MinGW doesn't define LC_MESSAGES
+#ifndef LC_MESSAGES
+#define LC_MESSAGES LC_ALL
+#endif /* LC_MESSAGES */
+
+// Windows needs O_BINARY
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif /* O_BINARY */
+
+static char *localeDirectory = NULL;
+static char *localeSpecifier = NULL;
+static char *domainName = NULL;
+
+const char *
+getMessagesDirectory (void) {
+  return localeDirectory;
+}
+
+const char *
+getMessagesLocale (void) {
+  return localeSpecifier;
+}
+
+const char *
+getMessagesDomain (void) {
+  return domainName;
+}
+
+static const uint32_t magicNumber = UINT32_C(0X950412DE);
+typedef uint32_t GetIntegerFunction (uint32_t value);
+
+typedef struct {
+  uint32_t magicNumber;
+  uint32_t versionNumber;
+  uint32_t messageCount;
+  uint32_t sourceMessages;
+  uint32_t translatedMessages;
+  uint32_t hashSize;
+  uint32_t hashOffset;
+} MessageCatalogHeader;
+
+typedef struct {
+  union {
+    void *data;
+    const unsigned char *bytes;
+    const MessageCatalogHeader *header;
+  } view;
+
+  size_t dataSize;
+  GetIntegerFunction *getInteger;
+} MessageCatalog;
+
+static MessageCatalog messageCatalog = {
+  .view.data = NULL,
+  .dataSize = 0,
+  .getInteger = NULL,
+};
+
+static uint32_t
+getNativeInteger (uint32_t value) {
+  return value;
+}
+
+static uint32_t
+getFlippedInteger (uint32_t value) {
+  uint32_t result = 0;
+
+  while (value) {
+    result <<= 8;
+    result |= value & UINT8_MAX;
+    value >>= 8;
+  }
+
+  return result;
+}
+
+static int
+checkMagicNumber (MessageCatalog *catalog) {
+  const MessageCatalogHeader *header = catalog->view.header;
+
+  {
+    static GetIntegerFunction *const functions[] = {
+      getNativeInteger,
+      getFlippedInteger,
+      NULL
+    };
+
+    GetIntegerFunction *const *function = functions;
+
+    while (*function) {
+      if ((*function)(header->magicNumber) == magicNumber) {
+        catalog->getInteger = *function;
+        return 1;
+      }
+
+      function += 1;
+    }
+  }
+
+  return 0;
+}
+
+static char *
+makeLocaleDirectoryPath (void) {
+  size_t length = strlen(localeSpecifier);
+
+  char dialect[length + 1];
+  strcpy(dialect, localeSpecifier);
+  length = strcspn(dialect, ".@");
+  dialect[length] = 0;
+
+  char language[length + 1];
+  strcpy(language, dialect);
+  length = strcspn(language, "_");
+  language[length] = 0;
+
+  char *codes[] = {dialect, language, NULL};
+  char **code = codes;
+
+  while (*code && **code) {
+    char *path = makePath(localeDirectory, *code);
+
+    if (path) {
+      if (testDirectoryPath(path)) return path;
+      free(path);
+    }
+
+    code += 1;
+  }
+
+  logMessage(LOG_DEBUG, "messages locale not found: %s", localeSpecifier);
+  return NULL;
+}
+
+static char *
+makeCatalogFilePath (void) {
+  char *locale = makeLocaleDirectoryPath();
+
+  if (locale) {
+    char *category = makePath(locale, "LC_MESSAGES");
+
+    free(locale);
+    locale = NULL;
+
+    if (category) {
+      char *catalog = makeFilePath(category, domainName, ".mo");
+
+      free(category);
+      category = NULL;
+
+      if (catalog) return catalog;
+    }
+  }
+
+  return NULL;
+}
+
+static int
+setMessageCatalog (void *data, size_t size) {
+  MessageCatalog catalog = {
+    .view.data = data,
+    .dataSize = size
+  };
+
+  if (checkMagicNumber(&catalog)) {
+    messageCatalog = catalog;
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+setEmptyMessageCatalog (void) {
+  MessageCatalogHeader *header;
+  size_t size = sizeof(*header);
+
+  if ((header = malloc(size))) {
+    memset(header, 0, size);
+    header->magicNumber = magicNumber;
+
+    header->sourceMessages = size;
+    header->translatedMessages = header->sourceMessages;
+
+    if (setMessageCatalog(header, size)) return 1;
+    free(header);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+int
+loadMessageCatalog (void) {
+  if (messageCatalog.view.data) return 1;
+  ensureAllMessagesProperties();
+
+  int loaded = 0;
+  char *path = makeCatalogFilePath();
+
+  if (path) {
+    int fd = open(path, (O_RDONLY | O_BINARY));
+
+    if (fd != -1) {
+      struct stat info;
+
+      if (fstat(fd, &info) != -1) {
+        size_t size = info.st_size;
+        void *data = NULL;
+
+        if (size) {
+          if ((data = malloc(size))) {
+            ssize_t count = read(fd, data, size);
+
+            if (count == -1) {
+              logMessage(LOG_WARNING,
+                "message catalog read error: %s: %s",
+                path, strerror(errno)
+              );
+            } else if (count < size) {
+              logMessage(LOG_WARNING,
+                "truncated message catalog: %"PRIssize" < %"PRIsize": %s",
+                count, size, path
+              );
+            } else if (setMessageCatalog(data, size)) {
+              data = NULL;
+              loaded = 1;
+            }
+
+            if (!loaded) free(data);
+          } else {
+            logMallocError();
+          }
+        } else {
+          logMessage(LOG_WARNING, "empty message catalog");
+        }
+      } else {
+        logMessage(LOG_WARNING,
+          "message catalog stat error: %s: %s",
+          path, strerror(errno)
+        );
+      }
+
+      close(fd);
+    } else {
+      logMessage(LOG_WARNING,
+        "message catalog open error: %s: %s",
+        path, strerror(errno)
+      );
+    }
+
+    free(path);
+  }
+
+  if (!loaded) {
+    if (setEmptyMessageCatalog()) {
+      loaded = 1;
+      logMessage(LOG_DEBUG, "no message translations");
+    }
+  }
+
+  return loaded;
+}
+
+void
+releaseMessageCatalog (void) {
+  if (messageCatalog.view.data) free(messageCatalog.view.data);
+  memset(&messageCatalog, 0, sizeof(messageCatalog));
+}
+
+static inline const MessageCatalogHeader *
+getHeader (void) {
+  return messageCatalog.view.header;
+}
+
+static inline const void *
+getItem (uint32_t offset) {
+  return &messageCatalog.view.bytes[messageCatalog.getInteger(offset)];
+}
+
+uint32_t
+getMessageCount (void) {
+  return messageCatalog.getInteger(getHeader()->messageCount);
+}
+
+struct MessageStruct {
+  uint32_t length;
+  uint32_t offset;
+};
+
+uint32_t
+getMessageLength (const Message *message) {
+  return messageCatalog.getInteger(message->length);
+}
+
+const char *
+getMessageText (const Message *message) {
+  return getItem(message->offset);
+}
+
+static inline const Message *
+getSourceMessages (void) {
+  return getItem(getHeader()->sourceMessages);
+}
+
+static inline const Message *
+getTranslatedMessages (void) {
+  return getItem(getHeader()->translatedMessages);
+}
+
+const Message *
+getSourceMessage (unsigned int index) {
+  return &getSourceMessages()[index];
+}
+
+const Message *
+getTranslatedMessage (unsigned int index) {
+  return &getTranslatedMessages()[index];
+}
+
+const char *
+getMessagesMetadata (void) {
+  if (getMessageCount() == 0) return "";
+
+  {
+    const Message *message = getSourceMessage(0);
+    if (getMessageLength(message) != 0) return "";
+  }
+
+  return getMessageText(getTranslatedMessage(0));
+}
+
+char *
+getMessagesProperty (const char *name) {
+  size_t nameLength = strlen(name);
+  const char *metadata = getMessagesMetadata();
+
+  while (metadata) {
+    const char *line = metadata;
+    size_t lineLength = strcspn(line, "\n\x00");
+
+    const char *end = line + lineLength;
+    metadata = *end? (line + lineLength + 1): NULL;
+
+    if (nameLength < lineLength) {
+      if (memcmp(line, name, nameLength) == 0) {
+        if (line[nameLength] == ':') {
+          const char *value = line + nameLength + 1;
+          while (iswspace(*value)) value += 1;
+
+          size_t valueLength = end - value;
+          char *copy = malloc(valueLength + 1);
+
+          if (copy) {
+            memcpy(copy, value, valueLength);
+            copy[valueLength] = 0;
+          } else {
+            logMallocError();
+          }
+
+          return copy;
+        }
+      }
+    }
+  }
+
+  return NULL;
+}
+
+char *
+getMessagesAttribute (const char *property, const char *name) {
+  size_t nameLength = strlen(name);
+  const char *byte = property;
+
+  while (*byte) {
+    while (iswspace(*byte)) byte += 1;
+    const char *nameStart = byte;
+
+    while (iswalpha(*byte)) byte += 1;
+    const char *nameEnd = byte;
+
+    while (iswspace(*byte)) byte += 1;
+    if (!*byte) break;
+
+    if (*byte != '=') continue;
+    byte += 1;
+
+    while (iswspace(*byte)) byte += 1;
+    const char *valueStart = byte;
+
+    while (*byte && (*byte != ';')) byte += 1;
+    const char *valueEnd = byte;
+    if (*byte) byte += 1;
+
+    if ((nameEnd - nameStart) == nameLength) {
+      if (memcmp(name, nameStart, nameLength) == 0) {
+        size_t length = valueEnd - valueStart;
+        char *value = malloc(length + 1);
+
+        if (value) {
+          memcpy(value, valueStart, length);
+          value[length] = 0;
+        } else {
+          logMallocError();
+        }
+
+        return value;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+int
+findSourceMessage (const char *text, size_t textLength, unsigned int *index) {
+  const Message *messages = getSourceMessages();
+  int from = 0;
+  int to = getMessageCount();
+
+  while (from < to) {
+    int current = (from + to) / 2;
+    const Message *message = &messages[current];
+
+    uint32_t messageLength = getMessageLength(message);
+    int relation = memcmp(text, getMessageText(message), MIN(textLength, messageLength));
+
+    if (relation == 0) {
+      if (textLength == messageLength) {
+        *index = current;
+        return 1;
+      }
+
+      relation = (textLength < messageLength)? -1: 1;
+    }
+
+    if (relation < 0) {
+      to = current;
+    } else {
+      from = current + 1;
+    }
+  }
+
+  return 0;
+}
+
+const Message *
+findSimpleTranslation (const char *text, size_t length) {
+  if (!text) return NULL;
+  if (!length) return NULL;
+
+  if (loadMessageCatalog()) {
+    unsigned int index;
+
+    if (findSourceMessage(text, length, &index)) {
+      return getTranslatedMessage(index);
+    }
+  }
+
+  return NULL;
+}
+
+const char *
+getSimpleTranslation (const char *text) {
+  const Message *translation = findSimpleTranslation(text, strlen(text));
+  if (translation) return getMessageText(translation);
+  return text;
+}
+
+const Message *
+findPluralTranslation (const char *const *strings) {
+  unsigned int count = 0;
+  while (strings[count]) count += 1;
+  if (!count) return NULL;
+
+  size_t size = 0;
+  size_t lengths[count];
+
+  for (unsigned int index=0; index<count; index+=1) {
+    size_t length = strlen(strings[index]);
+    lengths[index] = length;
+    size += length + 1;
+  }
+
+  char text[size];
+  char *byte = text;
+
+  for (unsigned int index=0; index<count; index+=1) {
+    byte = mempcpy(byte, strings[index], (lengths[index] + 1));
+  }
+
+  byte -= 1; // the length mustn't include the final NUL
+  return findSimpleTranslation(text, (byte - text));
+}
+
+const char *
+getPluralTranslation (const char *singular, const char *plural, unsigned long int count) {
+  int useSingular = count == 1;
+
+  const char *const strings[] = {singular, plural, NULL};
+  const Message *message = findPluralTranslation(strings);
+  if (!message) return useSingular? singular: plural;
+
+  const char *translation = getMessageText(message);
+  if (!useSingular) translation += strlen(translation) + 1;
+  return translation;
+}
+
+#ifdef ENABLE_I18N_SUPPORT
+static int
+setDirectory (const char *directory) {
+  if (bindtextdomain(domainName, directory)) return 1;
+  logSystemError("bindtextdomain");
+  return 0;
+}
+
+static int
+setDomain (const char *domain) {
+  if (!textdomain(domain)) {
+    logSystemError("textdomain");
+    return 0;
+  }
+
+  if (!bind_textdomain_codeset(domain, "UTF-8")) {
+    logSystemError("bind_textdomain_codeset");
+  }
+
+  return 1;
+}
+#else /* ENABLE_I18N_SUPPORT */
+static int
+setDirectory (const char *directory) {
+  return 1;
+}
+
+static int
+setDomain (const char *domain) {
+  return 1;
+}
+
+char *
+gettext (const char *text) {
+  return (char *)getSimpleTranslation(text);
+}
+
+char *
+ngettext (const char *singular, const char *plural, unsigned long int count) {
+  return (char *)getPluralTranslation(singular, plural, count);
+}
+#endif /* ENABLE_I18N_SUPPORT */
+
+static int
+updateProperty (
+  char **property, const char *value, const char *defaultValue,
+  int (*setter) (const char *value)
+) {
+  releaseMessageCatalog();
+
+  if (!(value && *value)) value = defaultValue;
+  char *copy = strdup(value);
+
+  if (copy) {
+    if (!setter || setter(value)) {
+      if (*property) free(*property);
+      *property = copy;
+      return 1;
+    }
+
+    free(copy);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+int
+setMessagesDirectory (const char *directory) {
+  return updateProperty(&localeDirectory, directory, LOCALE_DIRECTORY, setDirectory);
+}
+
+int
+setMessagesLocale (const char *specifier) {
+  return updateProperty(&localeSpecifier, specifier, "C.UTF-8", NULL);
+}
+
+int
+setMessagesDomain (const char *name) {
+  return updateProperty(&domainName, name, PACKAGE_TARNAME, setDomain);
+}
+
+void
+ensureAllMessagesProperties (void) {
+  if (!localeSpecifier) {
+    setMessagesLocale(setlocale(LC_MESSAGES, ""));
+  }
+
+  if (!domainName) setMessagesDomain(NULL);
+  if (!localeDirectory) setMessagesDirectory(NULL);
+}
diff --git a/Programs/midi.c b/Programs/midi.c
new file mode 100644
index 0000000..d26b2e2
--- /dev/null
+++ b/Programs/midi.c
@@ -0,0 +1,484 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "midi.h"
+
+static const char *const midiInstrumentGroups[] = {
+  // xgettext: This is the name of MIDI musical instrument group #1.
+  [0X0] = strtext("Piano"),
+
+  // xgettext: This is the name of MIDI musical instrument group #2.
+  [0X1] = strtext("Chromatic Percussion"),
+
+  // xgettext: This is the name of MIDI musical instrument group #3.
+  [0X2] = strtext("Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument group #4.
+  [0X3] = strtext("Guitar"),
+
+  // xgettext: This is the name of MIDI musical instrument group #5.
+  [0X4] = strtext("Bass"),
+
+  // xgettext: This is the name of MIDI musical instrument group #6.
+  [0X5] = strtext("Strings"),
+
+  // xgettext: This is the name of MIDI musical instrument group #7.
+  [0X6] = strtext("Ensemble"),
+
+  // xgettext: This is the name of MIDI musical instrument group #8.
+  [0X7] = strtext("Brass"),
+
+  // xgettext: This is the name of MIDI musical instrument group #9.
+  [0X8] = strtext("Reed"),
+
+  // xgettext: This is the name of MIDI musical instrument group #10.
+  [0X9] = strtext("Pipe"),
+
+  // xgettext: This is the name of MIDI musical instrument group #11.
+  // xgettext: (synth is a common short form for synthesizer)
+  [0XA] = strtext("Synth Lead"),
+
+  // xgettext: This is the name of MIDI musical instrument group #12.
+  // xgettext: (synth is a common short form for synthesizer)
+  [0XB] = strtext("Synth Pad"),
+
+  // xgettext: This is the name of MIDI musical instrument group #13.
+  // xgettext: (synth is a common short form for synthesizer)
+  // xgettext: (FM is the acronym for Frequency Modulation)
+  [0XC] = strtext("Synth FM"),
+
+  // xgettext: This is the name of MIDI musical instrument group #14.
+  [0XD] = strtext("Ethnic Instruments"),
+
+  // xgettext: This is the name of MIDI musical instrument group #15.
+  [0XE] = strtext("Percussive Instruments"),
+
+  // xgettext: This is the name of MIDI musical instrument group #16.
+  [0XF] = strtext("Sound Effects")
+};
+
+const char *const midiInstrumentTable[] = {
+/* Piano */
+  // xgettext: This is the name of MIDI musical instrument #1 (in the Piano group).
+  [0X00] = strtext("Acoustic Grand Piano"),
+
+  // xgettext: This is the name of MIDI musical instrument #2 (in the Piano group).
+  [0X01] = strtext("Bright Acoustic Piano"),
+
+  // xgettext: This is the name of MIDI musical instrument #3 (in the Piano group).
+  [0X02] = strtext("Electric Grand Piano"),
+
+  // xgettext: This is the name of MIDI musical instrument #4 (in the Piano group).
+  [0X03] = strtext("Honkytonk Piano"),
+
+  // xgettext: This is the name of MIDI musical instrument #5 (in the Piano group).
+  [0X04] = strtext("Electric Piano 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #6 (in the Piano group).
+  [0X05] = strtext("Electric Piano 2"),
+
+  // xgettext: This is the name of MIDI musical instrument #7 (in the Piano group).
+  [0X06] = strtext("Harpsichord"),
+
+  // xgettext: This is the name of MIDI musical instrument #8 (in the Piano group).
+  [0X07] = strtext("Clavinet"),
+
+/* Chromatic Percussion */
+  // xgettext: This is the name of MIDI musical instrument #9 (in the Chromatic Percussion group).
+  [0X08] = strtext("Celesta"),
+
+  // xgettext: This is the name of MIDI musical instrument #10 (in the Chromatic Percussion group).
+  [0X09] = strtext("Glockenspiel"),
+
+  // xgettext: This is the name of MIDI musical instrument #11 (in the Chromatic Percussion group).
+  [0X0A] = strtext("Music Box"),
+
+  // xgettext: This is the name of MIDI musical instrument #12 (in the Chromatic Percussion group).
+  [0X0B] = strtext("Vibraphone"),
+
+  // xgettext: This is the name of MIDI musical instrument #13 (in the Chromatic Percussion group).
+  [0X0C] = strtext("Marimba"),
+
+  // xgettext: This is the name of MIDI musical instrument #14 (in the Chromatic Percussion group).
+  [0X0D] = strtext("Xylophone"),
+
+  // xgettext: This is the name of MIDI musical instrument #15 (in the Chromatic Percussion group).
+  [0X0E] = strtext("Tubular Bells"),
+
+  // xgettext: This is the name of MIDI musical instrument #16 (in the Chromatic Percussion group).
+  [0X0F] = strtext("Dulcimer"),
+
+/* Organ */
+  // xgettext: This is the name of MIDI musical instrument #17 (in the Organ group).
+  [0X10] = strtext("Drawbar Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument #18 (in the Organ group).
+  [0X11] = strtext("Percussive Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument #19 (in the Organ group).
+  [0X12] = strtext("Rock Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument #20 (in the Organ group).
+  [0X13] = strtext("Church Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument #21 (in the Organ group).
+  [0X14] = strtext("Reed Organ"),
+
+  // xgettext: This is the name of MIDI musical instrument #22 (in the Organ group).
+  [0X15] = strtext("Accordion"),
+
+  // xgettext: This is the name of MIDI musical instrument #23 (in the Organ group).
+  [0X16] = strtext("Harmonica"),
+
+  // xgettext: This is the name of MIDI musical instrument #24 (in the Organ group).
+  [0X17] = strtext("Tango Accordion"),
+
+/* Guitar */
+  // xgettext: This is the name of MIDI musical instrument #25 (in the Guitar group).
+  [0X18] = strtext("Acoustic Guitar (nylon)"),
+
+  // xgettext: This is the name of MIDI musical instrument #26 (in the Guitar group).
+  [0X19] = strtext("Acoustic Guitar (steel)"),
+
+  // xgettext: This is the name of MIDI musical instrument #27 (in the Guitar group).
+  [0X1A] = strtext("Electric Guitar (jazz)"),
+
+  // xgettext: This is the name of MIDI musical instrument #28 (in the Guitar group).
+  [0X1B] = strtext("Electric Guitar (clean)"),
+
+  // xgettext: This is the name of MIDI musical instrument #29 (in the Guitar group).
+  [0X1C] = strtext("Electric Guitar (muted)"),
+
+  // xgettext: This is the name of MIDI musical instrument #30 (in the Guitar group).
+  [0X1D] = strtext("Overdriven Guitar"),
+
+  // xgettext: This is the name of MIDI musical instrument #31 (in the Guitar group).
+  [0X1E] = strtext("Distortion Guitar"),
+
+  // xgettext: This is the name of MIDI musical instrument #32 (in the Guitar group).
+  [0X1F] = strtext("Guitar Harmonics"),
+
+/* Bass */
+  // xgettext: This is the name of MIDI musical instrument #33 (in the Bass group).
+  [0X20] = strtext("Acoustic Bass"),
+
+  // xgettext: This is the name of MIDI musical instrument #34 (in the Bass group).
+  [0X21] = strtext("Electric Bass (finger)"),
+
+  // xgettext: This is the name of MIDI musical instrument #35 (in the Bass group).
+  [0X22] = strtext("Electric Bass (pick)"),
+
+  // xgettext: This is the name of MIDI musical instrument #36 (in the Bass group).
+  [0X23] = strtext("Fretless Bass"),
+
+  // xgettext: This is the name of MIDI musical instrument #37 (in the Bass group).
+  [0X24] = strtext("Slap Bass 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #38 (in the Bass group).
+  [0X25] = strtext("Slap Bass 2"),
+
+  // xgettext: This is the name of MIDI musical instrument #39 (in the Bass group).
+  [0X26] = strtext("Synth Bass 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #40 (in the Bass group).
+  [0X27] = strtext("Synth Bass 2"),
+
+/* Strings */
+  // xgettext: This is the name of MIDI musical instrument #41 (in the Strings group).
+  [0X28] = strtext("Violin"),
+
+  // xgettext: This is the name of MIDI musical instrument #42 (in the Strings group).
+  [0X29] = strtext("Viola"),
+
+  // xgettext: This is the name of MIDI musical instrument #43 (in the Strings group).
+  [0X2A] = strtext("Cello"),
+
+  // xgettext: This is the name of MIDI musical instrument #44 (in the Strings group).
+  [0X2B] = strtext("Contrabass"),
+
+  // xgettext: This is the name of MIDI musical instrument #45 (in the Strings group).
+  [0X2C] = strtext("Tremolo Strings"),
+
+  // xgettext: This is the name of MIDI musical instrument #46 (in the Strings group).
+  [0X2D] = strtext("Pizzicato Strings"),
+
+  // xgettext: This is the name of MIDI musical instrument #47 (in the Strings group).
+  [0X2E] = strtext("Orchestral Harp"),
+
+  // xgettext: This is the name of MIDI musical instrument #48 (in the Strings group).
+  [0X2F] = strtext("Timpani"),
+
+/* Ensemble */
+  // xgettext: This is the name of MIDI musical instrument #49 (in the Ensemble group).
+  [0X30] = strtext("String Ensemble 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #50 (in the Ensemble group).
+  [0X31] = strtext("String Ensemble 2"),
+
+  // xgettext: This is the name of MIDI musical instrument #51 (in the Ensemble group).
+  [0X32] = strtext("SynthStrings 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #52 (in the Ensemble group).
+  [0X33] = strtext("SynthStrings 2"),
+
+  // xgettext: This is the name of MIDI musical instrument #53 (in the Ensemble group).
+  [0X34] = strtext("Choir Aahs"),
+
+  // xgettext: This is the name of MIDI musical instrument #54 (in the Ensemble group).
+  [0X35] = strtext("Voice Oohs"),
+
+  // xgettext: This is the name of MIDI musical instrument #55 (in the Ensemble group).
+  [0X36] = strtext("Synth Voice"),
+
+  // xgettext: This is the name of MIDI musical instrument #56 (in the Ensemble group).
+  [0X37] = strtext("Orchestra Hit"),
+
+/* Brass */
+  // xgettext: This is the name of MIDI musical instrument #57 (in the Brass group).
+  [0X38] = strtext("Trumpet"),
+
+  // xgettext: This is the name of MIDI musical instrument #58 (in the Brass group).
+  [0X39] = strtext("Trombone"),
+
+  // xgettext: This is the name of MIDI musical instrument #59 (in the Brass group).
+  [0X3A] = strtext("Tuba"),
+
+  // xgettext: This is the name of MIDI musical instrument #60 (in the Brass group).
+  [0X3B] = strtext("Muted Trumpet"),
+
+  // xgettext: This is the name of MIDI musical instrument #61 (in the Brass group).
+  [0X3C] = strtext("French Horn"),
+
+  // xgettext: This is the name of MIDI musical instrument #62 (in the Brass group).
+  [0X3D] = strtext("Brass Section"),
+
+  // xgettext: This is the name of MIDI musical instrument #63 (in the Brass group).
+  [0X3E] = strtext("SynthBrass 1"),
+
+  // xgettext: This is the name of MIDI musical instrument #64 (in the Brass group).
+  [0X3F] = strtext("SynthBrass 2"),
+
+/* Reed */
+  // xgettext: This is the name of MIDI musical instrument #65 (in the Reed group).
+  [0X40] = strtext("Soprano Saxophone"),
+
+  // xgettext: This is the name of MIDI musical instrument #66 (in the Reed group).
+  [0X41] = strtext("Alto Saxophone"),
+
+  // xgettext: This is the name of MIDI musical instrument #67 (in the Reed group).
+  [0X42] = strtext("Tenor Saxophone"),
+
+  // xgettext: This is the name of MIDI musical instrument #68 (in the Reed group).
+  [0X43] = strtext("Baritone Saxophone"),
+
+  // xgettext: This is the name of MIDI musical instrument #69 (in the Reed group).
+  [0X44] = strtext("Oboe"),
+
+  // xgettext: This is the name of MIDI musical instrument #70 (in the Reed group).
+  [0X45] = strtext("English Horn"),
+
+  // xgettext: This is the name of MIDI musical instrument #71 (in the Reed group).
+  [0X46] = strtext("Bassoon"),
+
+  // xgettext: This is the name of MIDI musical instrument #72 (in the Reed group).
+  [0X47] = strtext("Clarinet"),
+
+/* Pipe */
+  // xgettext: This is the name of MIDI musical instrument #73 (in the Pipe group).
+  [0X48] = strtext("Piccolo"),
+
+  // xgettext: This is the name of MIDI musical instrument #74 (in the Pipe group).
+  [0X49] = strtext("Flute"),
+
+  // xgettext: This is the name of MIDI musical instrument #75 (in the Pipe group).
+  [0X4A] = strtext("Recorder"),
+
+  // xgettext: This is the name of MIDI musical instrument #76 (in the Pipe group).
+  [0X4B] = strtext("Pan Flute"),
+
+  // xgettext: This is the name of MIDI musical instrument #77 (in the Pipe group).
+  [0X4C] = strtext("Blown Bottle"),
+
+  // xgettext: This is the name of MIDI musical instrument #78 (in the Pipe group).
+  [0X4D] = strtext("Shakuhachi"),
+
+  // xgettext: This is the name of MIDI musical instrument #79 (in the Pipe group).
+  [0X4E] = strtext("Whistle"),
+
+  // xgettext: This is the name of MIDI musical instrument #80 (in the Pipe group).
+  [0X4F] = strtext("Ocarina"),
+
+/* Synth Lead */
+  // xgettext: This is the name of MIDI musical instrument #81 (in the Synth Lead group).
+  [0X50] = strtext("Lead 1 (square)"),
+
+  // xgettext: This is the name of MIDI musical instrument #82 (in the Synth Lead group).
+  [0X51] = strtext("Lead 2 (sawtooth)"),
+
+  // xgettext: This is the name of MIDI musical instrument #83 (in the Synth Lead group).
+  [0X52] = strtext("Lead 3 (calliope)"),
+
+  // xgettext: This is the name of MIDI musical instrument #84 (in the Synth Lead group).
+  [0X53] = strtext("Lead 4 (chiff)"),
+
+  // xgettext: This is the name of MIDI musical instrument #85 (in the Synth Lead group).
+  [0X54] = strtext("Lead 5 (charang)"),
+
+  // xgettext: This is the name of MIDI musical instrument #86 (in the Synth Lead group).
+  [0X55] = strtext("Lead 6 (voice)"),
+
+  // xgettext: This is the name of MIDI musical instrument #87 (in the Synth Lead group).
+  [0X56] = strtext("Lead 7 (fifths)"),
+
+  // xgettext: This is the name of MIDI musical instrument #88 (in the Synth Lead group).
+  [0X57] = strtext("Lead 8 (bass + lead)"),
+
+/* Synth Pad */
+  // xgettext: This is the name of MIDI musical instrument #89 (in the Synth Pad group).
+  [0X58] = strtext("Pad 1 (new age)"),
+
+  // xgettext: This is the name of MIDI musical instrument #90 (in the Synth Pad group).
+  [0X59] = strtext("Pad 2 (warm)"),
+
+  // xgettext: This is the name of MIDI musical instrument #91 (in the Synth Pad group).
+  [0X5A] = strtext("Pad 3 (polysynth)"),
+
+  // xgettext: This is the name of MIDI musical instrument #92 (in the Synth Pad group).
+  [0X5B] = strtext("Pad 4 (choir)"),
+
+  // xgettext: This is the name of MIDI musical instrument #93 (in the Synth Pad group).
+  [0X5C] = strtext("Pad 5 (bowed)"),
+
+  // xgettext: This is the name of MIDI musical instrument #94 (in the Synth Pad group).
+  [0X5D] = strtext("Pad 6 (metallic)"),
+
+  // xgettext: This is the name of MIDI musical instrument #95 (in the Synth Pad group).
+  [0X5E] = strtext("Pad 7 (halo)"),
+
+  // xgettext: This is the name of MIDI musical instrument #96 (in the Synth Pad group).
+  [0X5F] = strtext("Pad 8 (sweep)"),
+
+/* Synth FM */
+  // xgettext: This is the name of MIDI musical instrument #97 (in the Synth FM group).
+  [0X60] = strtext("FX 1 (rain)"),
+
+  // xgettext: This is the name of MIDI musical instrument #98 (in the Synth FM group).
+  [0X61] = strtext("FX 2 (soundtrack)"),
+
+  // xgettext: This is the name of MIDI musical instrument #99 (in the Synth FM group).
+  [0X62] = strtext("FX 3 (crystal)"),
+
+  // xgettext: This is the name of MIDI musical instrument #100 (in the Synth FM group).
+  [0X63] = strtext("FX 4 (atmosphere)"),
+
+  // xgettext: This is the name of MIDI musical instrument #101 (in the Synth FM group).
+  [0X64] = strtext("FX 5 (brightness)"),
+
+  // xgettext: This is the name of MIDI musical instrument #102 (in the Synth FM group).
+  [0X65] = strtext("FX 6 (goblins)"),
+
+  // xgettext: This is the name of MIDI musical instrument #103 (in the Synth FM group).
+  [0X66] = strtext("FX 7 (echoes)"),
+
+  // xgettext: This is the name of MIDI musical instrument #104 (in the Synth FM group).
+  // xgettext: (sci-fi is a common short form for science fiction)
+  [0X67] = strtext("FX 8 (sci-fi)"),
+
+/* Ethnic Instruments */
+  // xgettext: This is the name of MIDI musical instrument #105 (in the Ethnic group).
+  [0X68] = strtext("Sitar"),
+
+  // xgettext: This is the name of MIDI musical instrument #106 (in the Ethnic group).
+  [0X69] = strtext("Banjo"),
+
+  // xgettext: This is the name of MIDI musical instrument #107 (in the Ethnic group).
+  [0X6A] = strtext("Shamisen"),
+
+  // xgettext: This is the name of MIDI musical instrument #108 (in the Ethnic group).
+  [0X6B] = strtext("Koto"),
+
+  // xgettext: This is the name of MIDI musical instrument #109 (in the Ethnic group).
+  [0X6C] = strtext("Kalimba"),
+
+  // xgettext: This is the name of MIDI musical instrument #110 (in the Ethnic group).
+  [0X6D] = strtext("Bag Pipe"),
+
+  // xgettext: This is the name of MIDI musical instrument #111 (in the Ethnic group).
+  [0X6E] = strtext("Fiddle"),
+
+  // xgettext: This is the name of MIDI musical instrument #112 (in the Ethnic group).
+  [0X6F] = strtext("Shanai"),
+
+/* Percussive Instruments */
+  // xgettext: This is the name of MIDI musical instrument #113 (in the Percussive group).
+  [0X70] = strtext("Tinkle Bell"),
+
+  // xgettext: This is the name of MIDI musical instrument #114 (in the Percussive group).
+  [0X71] = strtext("Agogo"),
+
+  // xgettext: This is the name of MIDI musical instrument #115 (in the Percussive group).
+  [0X72] = strtext("Steel Drums"),
+
+  // xgettext: This is the name of MIDI musical instrument #116 (in the Percussive group).
+  [0X73] = strtext("Woodblock"),
+
+  // xgettext: This is the name of MIDI musical instrument #117 (in the Percussive group).
+  [0X74] = strtext("Taiko Drum"),
+
+  // xgettext: This is the name of MIDI musical instrument #118 (in the Percussive group).
+  [0X75] = strtext("Melodic Tom"),
+
+  // xgettext: This is the name of MIDI musical instrument #119 (in the Percussive group).
+  [0X76] = strtext("Synth Drum"),
+
+  // xgettext: This is the name of MIDI musical instrument #120 (in the Percussive group).
+  [0X77] = strtext("Reverse Cymbal"),
+
+/* Sound Effects */
+  // xgettext: This is the name of MIDI musical instrument #121 (in the Sound Effects group).
+  [0X78] = strtext("Guitar Fret Noise"),
+
+  // xgettext: This is the name of MIDI musical instrument #122 (in the Sound Effects group).
+  [0X79] = strtext("Breath Noise"),
+
+  // xgettext: This is the name of MIDI musical instrument #123 (in the Sound Effects group).
+  [0X7A] = strtext("Seashore"),
+
+  // xgettext: This is the name of MIDI musical instrument #124 (in the Sound Effects group).
+  [0X7B] = strtext("Bird Tweet"),
+
+  // xgettext: This is the name of MIDI musical instrument #125 (in the Sound Effects group).
+  [0X7C] = strtext("Telephone Ring"),
+
+  // xgettext: This is the name of MIDI musical instrument #126 (in the Sound Effects group).
+  [0X7D] = strtext("Helicopter"),
+
+  // xgettext: This is the name of MIDI musical instrument #127 (in the Sound Effects group).
+  [0X7E] = strtext("Applause"),
+
+  // xgettext: This is the name of MIDI musical instrument #128 (in the Sound Effects group).
+  [0X7F] = strtext("Gunshot")
+};
+const unsigned int midiInstrumentCount = ARRAY_COUNT(midiInstrumentTable);
+
+const char *
+midiGetInstrumentGroup (unsigned char instrument) {
+  return midiInstrumentGroups[instrument >> 3];
+}
diff --git a/Programs/midi_alsa.c b/Programs/midi_alsa.c
new file mode 100644
index 0000000..9dd67f1
--- /dev/null
+++ b/Programs/midi_alsa.c
@@ -0,0 +1,391 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <alsa/asoundlib.h>
+
+#include "log.h"
+#include "parse.h"
+#include "timing.h"
+#include "midi.h"
+
+struct MidiDeviceStruct {
+  snd_seq_t *sequencer;
+  int port;
+  int queue;
+  snd_seq_queue_status_t *status;
+  snd_seq_real_time_t time;
+  unsigned char note;
+};
+
+#define snd_seq_control_queue snd_seq_control_queue
+
+static int
+findMidiDevice (MidiDevice *midi, int errorLevel, int *client, int *port) {
+  snd_seq_client_info_t *clientInformation = malloc(snd_seq_client_info_sizeof());
+
+  if (clientInformation) {
+    memset(clientInformation, 0, snd_seq_client_info_sizeof());
+    snd_seq_client_info_set_client(clientInformation, -1);
+
+    while (snd_seq_query_next_client(midi->sequencer, clientInformation) >= 0) {
+      int clientIdentifier = snd_seq_client_info_get_client(clientInformation);
+      snd_seq_port_info_t *portInformation = malloc(snd_seq_port_info_sizeof());
+
+      if (portInformation) {
+        memset(portInformation, 0, snd_seq_port_info_sizeof());
+        snd_seq_port_info_set_client(portInformation, clientIdentifier);
+        snd_seq_port_info_set_port(portInformation, -1);
+
+        while (snd_seq_query_next_port(midi->sequencer, portInformation) >= 0) {
+          int portIdentifier = snd_seq_port_info_get_port(portInformation);
+          int actualCapabilities = snd_seq_port_info_get_capability(portInformation);
+          const int neededCapabilties = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
+
+          if (((actualCapabilities & neededCapabilties) == neededCapabilties) &&
+              !(actualCapabilities & SND_SEQ_PORT_CAP_NO_EXPORT)) {
+            *client = clientIdentifier;
+            *port = portIdentifier;
+            logMessage(LOG_DEBUG, "Using ALSA MIDI device: %d[%s] %d[%s]",
+                       clientIdentifier, snd_seq_client_info_get_name(clientInformation),
+                       portIdentifier, snd_seq_port_info_get_name(portInformation));
+
+            free(portInformation);
+            free(clientInformation);
+            return 1;
+          }
+        }
+
+        free(portInformation);
+      } else {
+        logMallocError();
+      }
+    }
+
+    free(clientInformation);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(errorLevel, "No MDII devices.");
+  return 0;
+}
+
+static int
+parseMidiDevice (MidiDevice *midi, int errorLevel, const char *device, int *client, int *port) {
+  char **components = splitString(device, ':', NULL);
+
+  if (components) {
+    char **component = components;
+
+    if (*component && **component) {
+      const char *clientSpecifier = *component++;
+
+      if (*component && **component) {
+        const char *portSpecifier = *component++;
+
+        if (!*component) {
+          int clientIdentifier;
+          int clientOk = 0;
+
+          if (isInteger(&clientIdentifier, clientSpecifier)) {
+            if ((clientIdentifier >= 0) && (clientIdentifier <= 0XFFFF)) clientOk = 1;
+          } else {
+            snd_seq_client_info_t *info = malloc(snd_seq_client_info_sizeof());
+
+            if (info) {
+              memset(info, 0, snd_seq_client_info_sizeof());
+              snd_seq_client_info_set_client(info, -1);
+
+              while (snd_seq_query_next_client(midi->sequencer, info) >= 0) {
+                const char *name = snd_seq_client_info_get_name(info);
+
+                if (strstr(name, clientSpecifier)) {
+                  clientIdentifier = snd_seq_client_info_get_client(info);
+                  clientOk = 1;
+                  logMessage(LOG_INFO, "Using ALSA MIDI client: %d[%s]",
+                             clientIdentifier, name);
+                  break;
+                }
+              }
+
+              free(info);
+            } else {
+              logMallocError();
+            }
+          }
+
+          if (clientOk) {
+            int portIdentifier;
+            int portOk = 0;
+
+            if (isInteger(&portIdentifier, portSpecifier)) {
+              if ((portIdentifier >= 0) && (portIdentifier <= 0XFFFF)) portOk = 1;
+            } else {
+              snd_seq_port_info_t *info = malloc(snd_seq_port_info_sizeof());
+
+              if (info) {
+                memset(info, 0, snd_seq_port_info_sizeof());
+                snd_seq_port_info_set_client(info, clientIdentifier);
+                snd_seq_port_info_set_port(info, -1);
+
+                while (snd_seq_query_next_port(midi->sequencer, info) >= 0) {
+                  const char *name = snd_seq_port_info_get_name(info);
+
+                  if (strstr(name, portSpecifier)) {
+                    portIdentifier = snd_seq_port_info_get_port(info);
+                    portOk = 1;
+                    logMessage(LOG_INFO, "Using ALSA MIDI port: %d[%s]",
+                               portIdentifier, name);
+                    break;
+                  }
+                }
+
+                free(info);
+              } else {
+                logMallocError();
+              }
+            }
+
+            if (portOk) {
+              *client = clientIdentifier;
+              *port = portIdentifier;
+
+              deallocateStrings(components);
+              return 1;
+            } else {
+              logMessage(errorLevel, "Invalid ALSA MIDI port: %s", device);
+            }
+          } else {
+            logMessage(errorLevel, "Invalid ALSA MIDI client: %s", device);
+          }
+        } else {
+          logMessage(errorLevel, "Too many ALSA MIDI device components: %s", device);
+        }
+      } else {
+        logMessage(errorLevel, "Missing ALSA MIDI port specifier: %s", device);
+      }
+    } else {
+      logMessage(errorLevel, "Missing ALSA MIDI client specifier: %s", device);
+    }
+
+    deallocateStrings(components);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+updateMidiStatus (MidiDevice *midi) {
+  snd_seq_get_queue_status(midi->sequencer, midi->queue, midi->status);
+}
+
+static void
+startMidiTimer (MidiDevice *midi) {
+  if (!midi->time.tv_sec && !midi->time.tv_nsec) {
+    updateMidiStatus(midi);
+    midi->time = *snd_seq_queue_status_get_real_time(midi->status);
+  }
+}
+
+static void
+stopMidiTimer (MidiDevice *midi) {
+  midi->time.tv_sec = 0;
+  midi->time.tv_nsec = 0;
+}
+
+MidiDevice *
+openMidiDevice (int errorLevel, const char *device) {
+  MidiDevice *midi;
+
+  if ((midi = malloc(sizeof(*midi)))) {
+    const char *sequencerName = "default";
+    int result;
+
+    if ((result = snd_seq_open(&midi->sequencer, sequencerName, SND_SEQ_OPEN_OUTPUT, 0)) >= 0) {
+      snd_seq_set_client_name(midi->sequencer, PACKAGE_NAME);
+
+      if ((midi->port = snd_seq_create_simple_port(midi->sequencer, "out0",
+                                                        SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
+                                                        SND_SEQ_PORT_TYPE_APPLICATION)) >= 0) {
+        if ((midi->queue = snd_seq_alloc_queue(midi->sequencer)) >= 0) {
+          if ((result = snd_seq_queue_status_malloc(&midi->status)) >= 0) {
+            int client = 0;
+            int port = 0;
+            int deviceOk;
+
+            if (*device) {
+              deviceOk = parseMidiDevice(midi, errorLevel, device, &client, &port);
+            } else {
+              deviceOk = findMidiDevice(midi, errorLevel, &client, &port);
+            }
+
+            if (deviceOk) {
+              logMessage(LOG_DEBUG, "Connecting to ALSA MIDI device: %d:%d", client, port);
+
+              if ((result = snd_seq_connect_to(midi->sequencer, midi->port, client, port)) >= 0) {
+                if ((result = snd_seq_start_queue(midi->sequencer, midi->queue, NULL)) >= 0) {
+                  stopMidiTimer(midi);
+                  return midi;
+                } else {
+                  logMessage(errorLevel, "Cannot start ALSA MIDI queue: %d:%d: %s",
+                             client, port, snd_strerror(result));
+                }
+              } else {
+                logMessage(errorLevel, "Cannot connect to ALSA MIDI device: %d:%d: %s",
+                           client, port, snd_strerror(result));
+              }
+            }
+
+            snd_seq_queue_status_free(midi->status);
+          } else {
+            logMessage(errorLevel, "Cannot allocate ALSA MIDI queue status container: %s",
+                       snd_strerror(result));
+          }
+        } else {
+          logMessage(errorLevel, "Cannot allocate ALSA MIDI queue: %s",
+                     snd_strerror(result));
+        }
+      } else {
+        logMessage(errorLevel, "Cannot create ALSA MIDI output port: %s",
+                   snd_strerror(midi->port));
+      }
+
+      snd_seq_close(midi->sequencer);
+    } else {
+      logMessage(errorLevel, "Cannot open ALSA sequencer: %s: %s",
+                 sequencerName, snd_strerror(result));
+    }
+
+    free(midi);
+  } else {
+    logSystemError("MIDI device allocation");
+  }
+  return NULL;
+}
+
+void
+closeMidiDevice (MidiDevice *midi) {
+  snd_seq_queue_status_free(midi->status);
+  snd_seq_close(midi->sequencer);
+  free(midi);
+}
+
+int
+flushMidiDevice (MidiDevice *midi) {
+  while (1) {
+    int duration;
+
+    updateMidiStatus(midi);
+    {
+      const snd_seq_real_time_t *time = snd_seq_queue_status_get_real_time(midi->status);
+      int seconds = midi->time.tv_sec - time->tv_sec;
+      int nanoseconds = midi->time.tv_nsec - time->tv_nsec;
+      duration = (seconds * 1000) + (nanoseconds / 1000000);
+    }
+
+    if (duration <= 0) break;
+    approximateDelay(duration);
+  }
+
+  stopMidiTimer(midi);
+  return 1;
+}
+
+static void
+prepareMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
+  snd_seq_ev_clear(event);
+  snd_seq_ev_set_source(event, midi->port);
+  snd_seq_ev_set_subs(event);
+}
+
+static void
+scheduleMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
+  snd_seq_ev_schedule_real(event, midi->queue, 1, &midi->time);
+}
+
+static int
+sendMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
+  int result;
+
+  if ((result = snd_seq_event_output(midi->sequencer, event)) >= 0) {
+    snd_seq_drain_output(midi->sequencer);
+    return 1;
+  } else {
+    logMessage(LOG_ERR, "ALSA MIDI write error: %s", snd_strerror(result));
+  }
+  return 0;
+}
+
+int
+setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
+  snd_seq_event_t event;
+
+  prepareMidiEvent(midi, &event);
+  snd_seq_ev_set_pgmchange(&event, channel, instrument);
+  return sendMidiEvent(midi, &event);
+}
+
+int
+beginMidiBlock (MidiDevice *midi) {
+  startMidiTimer(midi);
+  return 1;
+}
+
+int
+endMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
+  snd_seq_event_t event;
+
+  prepareMidiEvent(midi, &event);
+  snd_seq_ev_set_noteon(&event, channel, note, volume);
+  midi->note = note;
+  scheduleMidiEvent(midi, &event);
+  return sendMidiEvent(midi, &event);
+}
+
+int
+stopMidiNote (MidiDevice *midi, unsigned char channel) {
+  snd_seq_event_t event;
+
+  prepareMidiEvent(midi, &event);
+  snd_seq_ev_set_noteoff(&event, channel, midi->note, 0);
+  midi->note = 0;
+  scheduleMidiEvent(midi, &event);
+  return sendMidiEvent(midi, &event);
+}
+
+int
+insertMidiWait (MidiDevice *midi, int duration) {
+  midi->time.tv_sec += duration / 1000;
+  midi->time.tv_nsec += (duration % 1000) * 1000000;
+
+  while (midi->time.tv_nsec >= 1000000000) {
+    midi->time.tv_nsec -= 1000000000;
+    midi->time.tv_sec++;
+  }
+
+  return 1;
+}
diff --git a/Programs/midi_darwin.c b/Programs/midi_darwin.c
new file mode 100644
index 0000000..01296ba
--- /dev/null
+++ b/Programs/midi_darwin.c
@@ -0,0 +1,195 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <CoreServices/CoreServices.h>
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+#include "log.h"
+#include "midi.h"
+
+struct MidiDeviceStruct {
+  AUGraph graph;
+  AudioUnit synth;
+  /* Note that is currently playing. */
+  int note;
+};
+  
+MidiDevice *
+openMidiDevice (int errorLevel, const char *device) {
+  MidiDevice *midi;
+  int result;
+  AUNode synthNode, outNode;
+  ComponentDescription cd;
+  UInt32 propVal;
+
+  if (!(midi = malloc(sizeof(*midi)))) {
+    logMallocError();
+    return NULL;
+  }
+
+  /* Create a graph with a software synth and a default output unit. */
+
+  cd.componentManufacturer = kAudioUnitManufacturer_Apple;
+  cd.componentFlags = 0;
+  cd.componentFlagsMask = 0;
+
+  if ((result = NewAUGraph(&midi->graph)) != noErr) {
+    logMessage(errorLevel, "Can't create audio graph component: %d", result);
+    goto err;
+  }
+
+  cd.componentType = kAudioUnitType_MusicDevice;
+  cd.componentSubType = kAudioUnitSubType_DLSSynth;
+  if ((result = AUGraphNewNode(midi->graph, &cd, 0, NULL, &synthNode))
+      != noErr) {
+    logMessage(errorLevel, "Can't create software synthersizer component: %d",
+	       result);
+    goto err;
+  }
+
+  cd.componentType = kAudioUnitType_Output;
+  cd.componentSubType = kAudioUnitSubType_DefaultOutput;
+  if ((result = AUGraphNewNode(midi->graph, &cd, 0, NULL, &outNode))
+      != noErr) {
+    logMessage(errorLevel, "Can't create default output audio component: %d",
+	       result);
+    goto err;
+  }
+
+  if ((result = AUGraphOpen(midi->graph)) != noErr) {
+    logMessage(errorLevel, "Can't open audio graph component: %d", result);
+    goto err;
+  }
+
+  if ((result = AUGraphConnectNodeInput(midi->graph, synthNode, 0, outNode, 0))
+      != noErr) {
+    logMessage(errorLevel, "Can't connect synth audio component to output: %d",
+	       result);
+    goto err;
+  }
+
+  if ((result = AUGraphGetNodeInfo(midi->graph, synthNode, 0, 0, 0,
+				   &midi->synth)) != noErr) {
+    logMessage(errorLevel, "Can't get audio component for software synth: %d",
+	       result);
+    goto err;
+  }
+
+  if ((result = AUGraphInitialize(midi->graph)) != noErr) {
+    logMessage(errorLevel, "Can't initialize audio graph: %d", result);
+    goto err;
+  }
+
+  /* Turn off the reverb.  The value range is -120 to 40 dB. */
+  propVal = false;
+  if ((result = AudioUnitSetProperty(midi->synth,
+				     kMusicDeviceProperty_UsesInternalReverb,
+				     kAudioUnitScope_Global, 0,
+				     &propVal, sizeof(propVal)))
+      != noErr) {
+    /* So, having reverb isn't that critical, is it? */
+    logMessage(LOG_DEBUG, "Can't turn of software synth reverb: %d",
+	       result);
+  }
+
+  /* TODO: Maybe just start the graph when we are going to use it? */
+  if ((result = AUGraphStart(midi->graph)) != noErr) {
+    logMessage(errorLevel, "Can't start audio graph component: %d", result);
+    goto err;
+  }
+
+  return midi;
+
+ err:
+  if (midi->graph)
+    DisposeAUGraph(midi->graph);
+  free(midi);
+  return NULL;
+}
+
+void
+closeMidiDevice (MidiDevice *midi) {
+  int result;
+  if (midi) {
+    if ((result = DisposeAUGraph(midi->graph)) != noErr)
+      logMessage(LOG_ERR, "Can't dispose audio graph component: %d", result);
+    free(midi);
+  }
+}
+
+int
+flushMidiDevice (MidiDevice *midi) {
+  return 1;
+}
+
+int
+setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
+  int result;
+  if ((result = MusicDeviceMIDIEvent(midi->synth, 0xC0 | channel, instrument,
+				     0, 0)) != noErr)
+    logMessage(LOG_ERR, "Can't set MIDI instrument: %d", result);
+
+  return result == noErr;
+}
+
+int
+beginMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+endMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
+  int result;
+
+  if ((result = MusicDeviceMIDIEvent(midi->synth, 0x90 | channel, note,
+				     volume, 0)) != noErr) {
+    logMessage(LOG_ERR, "Can't start MIDI note: %d", result);
+    return 0;
+  }
+  midi->note = note;
+
+  return 1;
+}
+
+int
+stopMidiNote (MidiDevice *midi, unsigned char channel) {
+  int result;
+
+  if ((result = MusicDeviceMIDIEvent(midi->synth, 0x90 | channel, midi->note,
+				     0, 0)) != noErr) {
+    logMessage(LOG_ERR, "Can't stop MIDI note: %d", result);
+    return 0;
+  }
+  midi->note = 0;
+
+  return 1;
+}
+
+int
+insertMidiWait (MidiDevice *midi, int duration) {
+  usleep(duration * 1000);
+  return 1;
+}
diff --git a/Programs/midi_none.c b/Programs/midi_none.c
new file mode 100644
index 0000000..cad5e0c
--- /dev/null
+++ b/Programs/midi_none.c
@@ -0,0 +1,67 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "midi.h"
+
+MidiDevice *
+openMidiDevice (int errorLevel, const char *device) {
+  logMessage(errorLevel, "MIDI device not supported.");
+  return NULL;
+}
+
+void
+closeMidiDevice (MidiDevice *midi) {
+}
+
+int
+flushMidiDevice (MidiDevice *midi) {
+  return 1;
+}
+
+int
+setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
+  return 1;
+}
+
+int
+beginMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+endMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
+  return 1;
+}
+
+int
+stopMidiNote (MidiDevice *midi, unsigned char channel) {
+  return 1;
+}
+
+int
+insertMidiWait (MidiDevice *midi, int duration) {
+  return 1;
+}
diff --git a/Programs/midi_oss.c b/Programs/midi_oss.c
new file mode 100644
index 0000000..e30c233
--- /dev/null
+++ b/Programs/midi_oss.c
@@ -0,0 +1,209 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "midi.h"
+
+#define MIDI_OSS_DEVICE_PATH "/dev/sequencer";
+
+struct MidiDeviceStruct {
+  int fileDescriptor;
+  int deviceNumber;
+/*SEQ_DEFINEBUF(0X80);*/
+  unsigned char buffer[0X80];
+  int bufferLength;
+  int bufferUsed;
+};
+
+#ifndef SAMPLE_TYPE_AWE
+#define SAMPLE_TYPE_AWE 0x20
+#endif /* SAMPLE_TYPE_AWE */
+
+static MidiDevice *midiDevice = NULL;
+#define _seqbuf midiDevice->buffer
+#define _seqbuflen midiDevice->bufferLength
+#define _seqbufptr midiDevice->bufferUsed
+
+void
+seqbuf_dump (void) {
+  if (_seqbufptr)
+    if (writeFile(midiDevice->fileDescriptor, _seqbuf, _seqbufptr) == -1)
+      logSystemError("MIDI write");
+  _seqbufptr = 0;
+}
+
+MidiDevice *
+openMidiDevice (int errorLevel, const char *device) {
+  MidiDevice *midi;
+  if ((midi = malloc(sizeof(*midi)))) {
+    if (!*device) device = MIDI_OSS_DEVICE_PATH;
+    if ((midi->fileDescriptor = open(device, O_WRONLY)) != -1) {
+      {
+        int count;
+        int awe = -1;
+        int fm = -1;
+        int gus = -1;
+        int ext = -1;
+
+        if (ioctl(midi->fileDescriptor, SNDCTL_SEQ_NRSYNTHS, &count) != -1) {
+          int index;
+          for (index=0; index<count; ++index) {
+            struct synth_info info;
+            info.device = index;
+            if (ioctl(midi->fileDescriptor, SNDCTL_SYNTH_INFO, &info) != -1) {
+              switch (info.synth_type) {
+                case SYNTH_TYPE_SAMPLE:
+                  switch (info.synth_subtype) {
+                    case SAMPLE_TYPE_AWE:
+                      awe = index;
+                      continue;
+
+                    case SAMPLE_TYPE_GUS:
+                      gus = index;
+                      continue;
+                  }
+                  break;
+
+                case SYNTH_TYPE_FM:
+                  fm = index;
+                  continue;
+              }
+
+              logMessage(LOG_DEBUG, "Unknown synthesizer: %d[%d]: %s",
+                         info.synth_type, info.synth_subtype, info.name);
+            } else {
+              logMessage(errorLevel, "Cannot get description for synthesizer %d: %s",
+                         index, strerror(errno));
+            }
+          }
+
+          if (gus >= 0)
+            if (ioctl(midi->fileDescriptor, SNDCTL_SEQ_RESETSAMPLES, &gus) == -1)
+              logMessage(errorLevel, "Cannot reset samples for gus synthesizer %d: %s",
+                         gus, strerror(errno));
+        } else {
+          logMessage(errorLevel, "Cannot get MIDI synthesizer count: %s",
+                     strerror(errno));
+        }
+
+        if (ioctl(midi->fileDescriptor, SNDCTL_SEQ_NRMIDIS, &count) != -1) {
+          if (count > 0) ext = count - 1;
+        } else {
+          logMessage(errorLevel, "Cannot get MIDI device count: %s",
+                     strerror(errno));
+        }
+
+        midi->deviceNumber = (awe >= 0)? awe:
+                             (gus >= 0)? gus:
+                             (fm >= 0)? fm:
+                             (ext >= 0)? ext:
+                             0;
+      }
+
+      midi->bufferLength = sizeof(midi->buffer);
+      midi->bufferUsed = 0;
+
+      return midi;
+    } else {
+      logMessage(errorLevel, "Cannot open MIDI device: %s: %s", device, strerror(errno));
+    }
+
+    free(midi);
+  } else {
+    logSystemError("MIDI device allocation");
+  }
+  return NULL;
+}
+
+void
+closeMidiDevice (MidiDevice *midi) {
+  close(midi->fileDescriptor);
+  free(midi);
+}
+
+static void
+beginMidiOperation (MidiDevice *midi) {
+  midiDevice = midi;
+}
+
+static int
+endMidiOperation (MidiDevice *midi) {
+  midiDevice = NULL;
+  return 1;
+}
+
+int
+flushMidiDevice (MidiDevice *midi) {
+  beginMidiOperation(midi);
+  seqbuf_dump();
+  return endMidiOperation(midi);
+}
+
+int
+setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
+  beginMidiOperation(midi);
+  SEQ_SET_PATCH(midi->deviceNumber, channel, instrument);
+  return endMidiOperation(midi);
+}
+
+int
+beginMidiBlock (MidiDevice *midi) {
+  beginMidiOperation(midi);
+  SEQ_START_TIMER();
+  return endMidiOperation(midi);
+}
+
+int
+endMidiBlock (MidiDevice *midi) {
+  beginMidiOperation(midi);
+  SEQ_STOP_TIMER();
+  seqbuf_dump();
+  ioctl(midi->fileDescriptor, SNDCTL_SEQ_SYNC);
+  return endMidiOperation(midi);
+}
+
+int
+startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
+  beginMidiOperation(midi);
+  SEQ_START_NOTE(midi->deviceNumber, channel, note, 0X7F*volume/100);
+  return endMidiOperation(midi);
+}
+
+int
+stopMidiNote (MidiDevice *midi, unsigned char channel) {
+  beginMidiOperation(midi);
+  SEQ_STOP_NOTE(midi->deviceNumber, channel, 0, 0);
+  return endMidiOperation(midi);
+}
+
+int
+insertMidiWait (MidiDevice *midi, int duration) {
+  beginMidiOperation(midi);
+  SEQ_DELTA_TIME((duration + 9) / 10);
+  return endMidiOperation(midi);
+}
diff --git a/Programs/midi_windows.c b/Programs/midi_windows.c
new file mode 100644
index 0000000..f0e35bb
--- /dev/null
+++ b/Programs/midi_windows.c
@@ -0,0 +1,196 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "parse.h"
+#include "timing.h"
+#include "midi.h"
+
+struct MidiDeviceStruct {
+  HMIDIOUT handle;
+  unsigned char note;
+  int count;
+  char buffer[0X80];
+};
+
+typedef enum {
+  MIDI_NoteOff        = 0X80,
+  MIDI_NoteOn         = 0X90,
+  MIDI_KeyPressure    = 0XA0,
+  MIDI_ControlChange  = 0XB0,
+  MIDI_ProgramChange  = 0XC0,
+  MIDI_ChannelPresure = 0XD0,
+  MIDI_PitchBend      = 0XE0,
+  MIDI_SystemPrefix   = 0XF0
+} MidiEvent;
+
+static void
+logMidiOutError (MMRESULT error, int errorLevel, const char *action) {
+  char text[MAXERRORLENGTH];
+  midiOutGetErrorText(error, text, sizeof(text));
+  logMessage(errorLevel, "%s error %d: %s", action, error, text);
+}
+
+static int
+addMidiMessage (MidiDevice *midi, const unsigned char *message, int length) {
+  if ((midi->count + length) > sizeof(midi->buffer))
+    if (!flushMidiDevice(midi))
+      return 0;
+
+  memcpy(&midi->buffer[midi->count], message, length);
+  midi->count += length;
+  return 1;
+}
+
+static int
+writeMidiMessage (MidiDevice *midi, const unsigned char *message, int length) {
+  if (!addMidiMessage(midi, message, length)) return 0;
+  if (!flushMidiDevice(midi)) return 0;
+  return 1;
+}
+
+MidiDevice *
+openMidiDevice (int errorLevel, const char *device) {
+  MidiDevice *midi;
+  MMRESULT error;
+  int id = 0;
+  static const char *const defaultDevice = "default";
+
+  if (!*device) device = defaultDevice;
+
+  if (strcmp(device, defaultDevice) == 0) {
+    id = -1;
+  } else if (!isInteger(&id, device) || (id < 0) || (id >= midiOutGetNumDevs())) {
+    int count = midiOutGetNumDevs();
+    for (id=0; id<count; ++id) {
+      MIDIOUTCAPS cap;
+      if (midiOutGetDevCaps(id, &cap, sizeof(cap)) == MMSYSERR_NOERROR)
+        if (strncasecmp(device, cap.szPname, strlen(device)) == 0)
+          break;
+    }
+
+    if (id == count) {
+      logMessage(errorLevel, "invalid MIDI device number: %s", device);
+      return NULL;
+    }
+  }
+
+  if ((midi = malloc(sizeof(*midi)))) {
+    if ((error = midiOutOpen(&midi->handle, id, 0, 0, CALLBACK_NULL)) == MMSYSERR_NOERROR) {
+      midi->note = 0;
+      midi->count = 0;
+      return midi;
+    } else {
+      logMidiOutError(error, errorLevel, "MIDI device open");
+    }
+
+    free(midi);
+  } else {
+    logSystemError("MIDI device allocation");
+  }
+  return NULL;
+}
+
+void
+closeMidiDevice (MidiDevice *midi) {
+  flushMidiDevice(midi);
+  midiOutClose(midi->handle);
+  free(midi);
+}
+
+int
+flushMidiDevice (MidiDevice *midi) {
+  int ok = 1;
+
+  if (midi->count > 0) {
+    MMRESULT error;
+    MIDIHDR header;
+    
+    header.lpData = midi->buffer;
+    header.dwBufferLength = midi->count;
+    header.dwFlags = 0;
+
+    if ((error = midiOutPrepareHeader(midi->handle, &header, sizeof(header))) == MMSYSERR_NOERROR) {
+      if ((error = midiOutLongMsg(midi->handle, &header, sizeof(header))) == MMSYSERR_NOERROR) {
+        midi->count = 0;
+      } else {
+        logMidiOutError(error, LOG_ERR, "midiOutLongMsg");
+        ok = 0;
+      }
+
+      while ((error = midiOutUnprepareHeader(midi->handle, &header, sizeof(header))) == MIDIERR_STILLPLAYING) {
+        approximateDelay(1);
+      }
+
+      if (error != MMSYSERR_NOERROR) {
+        logMidiOutError(error, LOG_ERR, "midiOutUnprepareHeader");
+      }
+    } else {
+      logMidiOutError(error, LOG_ERR, "midiOutPrepareHeader");
+      ok = 0;
+    }
+  }
+
+  return ok;
+}
+
+int
+setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
+  const unsigned char message[] = {
+    MIDI_ProgramChange|channel, instrument
+  };
+  return writeMidiMessage(midi, message, sizeof(message));
+}
+
+int
+beginMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+endMidiBlock (MidiDevice *midi) {
+  return 1;
+}
+
+int
+startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
+  const unsigned char message[] = {
+    MIDI_NoteOn|channel, note, (0X7F * volume / 100)
+  };
+  int ok = writeMidiMessage(midi, message, sizeof(message));
+  if (ok) midi->note = note;
+  return ok;
+}
+
+int
+stopMidiNote (MidiDevice *midi, unsigned char channel) {
+  const unsigned char message[] = {
+    MIDI_NoteOff|channel, midi->note, 0
+  };
+  int ok = writeMidiMessage(midi, message, sizeof(message));
+  if (ok) midi->note = 0;
+  return ok;
+}
+
+int
+insertMidiWait (MidiDevice *midi, int duration) {
+  approximateDelay(duration);
+  return 1;
+}
diff --git a/Programs/mkdrvtab b/Programs/mkdrvtab
new file mode 100755
index 0000000..3be866e
--- /dev/null
+++ b/Programs/mkdrvtab
@@ -0,0 +1,54 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+programName="${0}"
+programMessage() {
+   echo >&2 "${programName}: ${1}"
+}
+syntaxError() {
+   [ -n "${1}" ] && programMessage "${1}"
+   exit 2
+}
+
+if [ "${#}" -eq 0 ]
+then
+   syntaxError "missing driver type."
+fi
+driverType="${1}"
+shift
+
+if [ "${#}" -eq 0 ]
+then
+   syntaxError "missing symbol prefix."
+fi
+symbolPrefix="${1}"
+shift
+
+for driverCode
+do
+   echo "extern const ${driverType} ${symbolPrefix}${driverCode};"
+done
+
+echo "static const DriverEntry driverTable[] = {"
+for driverCode
+do
+   echo "  {&${symbolPrefix}${driverCode}, &${symbolPrefix}${driverCode}.definition},"
+done
+echo "  {NULL, NULL}"
+echo "};"
diff --git a/Programs/mntfs_linux.c b/Programs/mntfs_linux.c
new file mode 100644
index 0000000..d23de35
--- /dev/null
+++ b/Programs/mntfs_linux.c
@@ -0,0 +1,28 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <sys/mount.h>
+
+#include "mntfs.h"
+
+int
+mountFileSystem (const char *path, const char *reference, const char *type) {
+  return mount(reference, path, type, 0, NULL) != -1;
+}
diff --git a/Programs/mntfs_none.c b/Programs/mntfs_none.c
new file mode 100644
index 0000000..1a7efd0
--- /dev/null
+++ b/Programs/mntfs_none.c
@@ -0,0 +1,29 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "mntfs.h"
+
+int
+mountFileSystem (const char *path, const char *reference, const char *type) {
+  errno = ENOSYS;
+  return 0;
+}
diff --git a/Programs/mntpt.c b/Programs/mntpt.c
new file mode 100644
index 0000000..e1e9c72
--- /dev/null
+++ b/Programs/mntpt.c
@@ -0,0 +1,150 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "mntfs.h"
+#include "async_alarm.h"
+#include "mntpt.h"
+
+#if defined(USE_PKG_MNTPT_NONE)
+#include "mntpt_none.h"
+#elif defined(USE_PKG_MNTPT_MNTENT)
+#include "mntpt_mntent.h"
+#elif defined(USE_PKG_MNTPT_MNTTAB)
+#include "mntpt_mnttab.h"
+#else /* mount point package */
+#error mount point package not selected
+#include "mntpt_none.h"
+#endif /* mount point package */
+
+#include "mntpt_internal.h"
+
+#if defined(MNTOPT_RW)
+#define MOUNT_OPTION_RW MNTOPT_RW
+#else /* MOUNT_OPTION_RW */
+#define MOUNT_OPTION_RW "rw"
+#endif /* MOUNT_OPTION_RW */
+
+char *
+findMountPoint (MountPointTester test) {
+  char *path = NULL;
+  FILE *table;
+
+  if ((table = openMountsTable(0))) {
+    MountEntry *entry;
+
+    while ((entry = readMountsTable(table))) {
+      if (test(entry->mountPath, entry->mountType)) {
+        if (!(path = strdup(entry->mountPath))) logMallocError();
+        break;
+      }
+    }
+
+    closeMountsTable(table);
+  }
+
+  return path;
+}
+
+static void updateMountsTable (MountEntry *entry);
+
+ASYNC_ALARM_CALLBACK(retryMountsTableUpdate) {
+  MountEntry *entry = parameters->data;
+  updateMountsTable(entry);
+}
+
+static void
+updateMountsTable (MountEntry *entry) {
+  int retry = 0;
+
+  {
+    FILE *table;
+
+    if ((table = openMountsTable(1))) {
+      addMountEntry(table, entry);
+      closeMountsTable(table);
+    } else if ((errno == EROFS) || (errno == EACCES)) {
+      retry = 1;
+    }
+  }
+
+  if (retry) {
+    asyncNewRelativeAlarm(NULL, MOUNT_TABLE_UPDATE_RETRY_INTERVAL, retryMountsTableUpdate, entry);
+  } else {
+    if (entry->mountPath) free(entry->mountPath);
+    if (entry->mountReference) free(entry->mountReference);
+    if (entry->mountType) free(entry->mountType);
+    if (entry->mountOptions) free(entry->mountOptions);
+    free(entry);
+  }
+}
+
+int
+makeMountPoint (const char *path, const char *reference, const char *type) {
+  if (mountFileSystem(path, reference, type)) {
+    MountEntry *entry;
+
+    logMessage(LOG_NOTICE, "file system mounted: %s[%s] -> %s",
+               type, reference, path);
+
+    if ((entry = malloc(sizeof(*entry)))) {
+      memset(entry, 0, sizeof(*entry));
+
+      if ((entry->mountPath = strdup(path))) {
+        if ((entry->mountReference = strdup(reference))) {
+          if ((entry->mountType = strdup(type))) {
+            if ((entry->mountOptions = strdup(MOUNT_OPTION_RW))) {
+              updateMountsTable(entry);
+              return 1;
+            } else {
+              logMallocError();
+            }
+
+            free(entry->mountType);
+          } else {
+            logMallocError();
+          }
+
+          free(entry->mountReference);
+        } else {
+          logMallocError();
+        }
+
+        free(entry->mountPath);
+      } else {
+        logMallocError();
+      }
+
+      free(entry);
+    } else {
+      logMallocError();
+    }
+  } else {
+    logMessage(LOG_ERR, "file system mount error: %s[%s] -> %s: %s",
+               type, reference, path, strerror(errno));
+  }
+
+  return 0;
+}
diff --git a/Programs/mntpt_internal.h b/Programs/mntpt_internal.h
new file mode 100644
index 0000000..343daa2
--- /dev/null
+++ b/Programs/mntpt_internal.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTPT_INTERNAL
+#define BRLTTY_INCLUDED_MNTPT_INTERNAL
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern FILE *openMountsTable (int update);
+extern void closeMountsTable (FILE *table);
+extern MountEntry *readMountsTable (FILE *table);
+extern int addMountEntry (FILE *table, MountEntry *entry);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTPT_INTERNAL */
diff --git a/Programs/mntpt_mntent.c b/Programs/mntpt_mntent.c
new file mode 100644
index 0000000..81e5895
--- /dev/null
+++ b/Programs/mntpt_mntent.c
@@ -0,0 +1,60 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "mntpt_mntent.h"
+#include "mntpt_internal.h"
+
+FILE *
+openMountsTable (int update) {
+  FILE *table = setmntent(MOUNTS_TABLE_PATH, (update? "a": "r"));
+  if (!table)
+    logMessage((errno == ENOENT)? LOG_WARNING: LOG_ERR,
+               "mounted file systems table open error: %s: %s",
+               MOUNTS_TABLE_PATH, strerror(errno));
+  return table;
+}
+
+void
+closeMountsTable (FILE *table) {
+  endmntent(table);
+}
+
+MountEntry *
+readMountsTable (FILE *table) {
+  return getmntent(table);
+}
+
+int
+addMountEntry (FILE *table, MountEntry *entry) {
+#ifdef HAVE_ADDMNTENT
+  if (addmntent(table, entry)) {
+    logMessage(LOG_ERR, "mounts table entry add error: %s[%s] -> %s: %s",
+               entry->mnt_type, entry->mnt_fsname, entry->mnt_dir, strerror(errno));
+    return 0;
+  }
+#endif /* HAVE_ADDMNTENT */
+  return 1;
+}
diff --git a/Programs/mntpt_mntent.h b/Programs/mntpt_mntent.h
new file mode 100644
index 0000000..d486d3f
--- /dev/null
+++ b/Programs/mntpt_mntent.h
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTPT_MNTENT
+#define BRLTTY_INCLUDED_MNTPT_MNTENT
+
+#include <mntent.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#if defined(MOUNTED)
+#define MOUNTS_TABLE_PATH MOUNTED
+#elif defined(MNT_MNTTAB)
+#define MOUNTS_TABLE_PATH MNT_MNTTAB
+#endif /* MOUNTS_TABLE_PATH */
+
+typedef struct mntent MountEntry;
+#define mountPath mnt_dir
+#define mountReference mnt_fsname
+#define mountType mnt_type
+#define mountOptions mnt_opts
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTPT_MNTENT */
diff --git a/Programs/mntpt_mnttab.c b/Programs/mntpt_mnttab.c
new file mode 100644
index 0000000..e4c275d
--- /dev/null
+++ b/Programs/mntpt_mnttab.c
@@ -0,0 +1,59 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+
+#include "mntpt_mnttab.h"
+#include "mntpt_internal.h"
+
+FILE *
+openMountsTable (int update) {
+  FILE *table = fopen(MNTTAB, (update? "a": "r"));
+  if (!table)
+    logMessage((errno == ENOENT)? LOG_WARNING: LOG_ERR,
+               "mounted file systems table open error: %s: %s",
+               MNTTAB, strerror(errno));
+  return table;
+}
+
+void
+closeMountsTable (FILE *table) {
+  fclose(table);
+}
+
+MountEntry *
+readMountsTable (FILE *table) {
+  static struct mnttab entry;
+  if (getmntent(table, &entry) == 0) return &entry;
+  return NULL;
+}
+
+int
+addMountEntry (FILE *table, MountEntry *entry) {
+  errno = ENOSYS;
+  if (!putmntent(table, entry)) return 1;
+  logMessage(LOG_ERR, "mounts table entry add error: %s[%s] -> %s: %s",
+             entry->mnt_fstype, entry->mnt_special, entry->mnt_mountp, strerror(errno));
+  return 0;
+}
diff --git a/Programs/mntpt_mnttab.h b/Programs/mntpt_mnttab.h
new file mode 100644
index 0000000..34222b0
--- /dev/null
+++ b/Programs/mntpt_mnttab.h
@@ -0,0 +1,38 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTPT_MNTTAB
+#define BRLTTY_INCLUDED_MNTPT_MNTTAB
+
+#include <sys/mnttab.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct mnttab MountEntry;
+#define mountPath mnt_mountp
+#define mountReference mnt_special
+#define mountType mnt_fstype
+#define mountOptions mnt_mntopts
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTPT_MNTTAB */
diff --git a/Programs/mntpt_none.c b/Programs/mntpt_none.c
new file mode 100644
index 0000000..28a2b01
--- /dev/null
+++ b/Programs/mntpt_none.c
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "log.h"
+#include "mntpt_none.h"
+#include "mntpt_internal.h"
+
+FILE *
+openMountsTable (int update) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+void
+closeMountsTable (FILE *table) {
+}
+
+MountEntry *
+readMountsTable (FILE *table) {
+  return NULL;
+}
+
+int
+addMountEntry (FILE *table, MountEntry *entry) {
+  return 0;
+}
diff --git a/Programs/mntpt_none.h b/Programs/mntpt_none.h
new file mode 100644
index 0000000..407c125
--- /dev/null
+++ b/Programs/mntpt_none.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_MNTPT_NONE
+#define BRLTTY_INCLUDED_MNTPT_NONE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  char *mountPath;
+  char *mountReference;
+  char *mountType;
+  char *mountOptions;
+} MountEntry;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_MNTPT_NONE */
diff --git a/Programs/morse.c b/Programs/morse.c
new file mode 100644
index 0000000..258b622
--- /dev/null
+++ b/Programs/morse.c
@@ -0,0 +1,317 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "morse.h"
+#include "tune.h"
+#include "utf8.h"
+
+static const MorsePattern morsePatterns[] = {
+  [WC_C('a')] = 0B101,
+  [WC_C('b')] = 0B11110,
+  [WC_C('c')] = 0B11010,
+  [WC_C('d')] = 0B1110,
+  [WC_C('e')] = 0B11,
+  [WC_C('f')] = 0B11011,
+  [WC_C('g')] = 0B1100,
+  [WC_C('h')] = 0B11111,
+  [WC_C('i')] = 0B111,
+  [WC_C('j')] = 0B10001,
+  [WC_C('k')] = 0B1010,
+  [WC_C('l')] = 0B11101,
+  [WC_C('m')] = 0B100,
+  [WC_C('n')] = 0B110,
+  [WC_C('o')] = 0B1000,
+  [WC_C('p')] = 0B11001,
+  [WC_C('q')] = 0B10100,
+  [WC_C('r')] = 0B1101,
+  [WC_C('s')] = 0B1111,
+  [WC_C('t')] = 0B10,
+  [WC_C('u')] = 0B1011,
+  [WC_C('v')] = 0B10111,
+  [WC_C('w')] = 0B1001,
+  [WC_C('x')] = 0B10110,
+  [WC_C('y')] = 0B10010,
+  [WC_C('z')] = 0B11100,
+
+#ifdef HAVE_WCHAR_H
+  [WC_C('ä')] = 0B10101,
+  [WC_C('á')] = 0B101001,
+  [WC_C('å')] = 0B101001,
+  [WC_C('é')] = 0B111011,
+  [WC_C('ñ')] = 0B100100,
+  [WC_C('ö')] = 0B11000,
+  [WC_C('ü')] = 0B10011,
+#endif /* HAVE_WCHAR_H */
+
+  [WC_C('0')] = 0B100000,
+  [WC_C('1')] = 0B100001,
+  [WC_C('2')] = 0B100011,
+  [WC_C('3')] = 0B100111,
+  [WC_C('4')] = 0B101111,
+  [WC_C('5')] = 0B111111,
+  [WC_C('6')] = 0B111110,
+  [WC_C('7')] = 0B111100,
+  [WC_C('8')] = 0B111000,
+  [WC_C('9')] = 0B110000,
+
+  [WC_C('.')] = 0B1010101,
+  [WC_C(',')] = 0B1001100,
+  [WC_C('?')] = 0B1110011,
+  [WC_C('!')] = 0B1001010,
+  [WC_C(':')] = 0B1111000,
+  [WC_C('\'')] = 0B1100001,
+  [WC_C('"')] = 0B1101101,
+  [WC_C('(')] = 0B110010,
+  [WC_C(')')] = 0B1010010,
+  [WC_C('=')] = 0B101110,
+  [WC_C('+')] = 0B110101,
+  [WC_C('-')] = 0B1011110,
+  [WC_C('/')] = 0B110110,
+  [WC_C('&')] = 0B111101,
+  [WC_C('@')] = 0B1101001,
+
+  [0] = 0
+};
+
+MorsePattern
+getMorsePattern (wchar_t character) {
+  character = towlower(character);
+  if (character < 0) return 0;
+  if (character >= ARRAY_COUNT(morsePatterns)) return 0;
+  return morsePatterns[character];
+}
+
+struct  MorseObjectStruct {
+  struct {
+    unsigned int frequency;
+    unsigned int unit;
+  } parameters;
+
+  struct {
+    unsigned wasSpace:1;
+  } state;
+
+  struct {
+    ToneElement *array;
+    size_t size;
+    size_t count;
+  } elements;
+};
+
+static int
+addMorseElement (MorseObject *morse, const ToneElement *element) {
+  if (morse->elements.count == morse->elements.size) {
+    size_t newSize = morse->elements.size? (morse->elements.size << 1): 0X10;
+    ToneElement *newArray = realloc(morse->elements.array, (newSize * sizeof(*newArray)));
+
+    if (!newArray) {
+      logMallocError();
+      return 0;
+    }
+
+    morse->elements.array = newArray;
+    morse->elements.size = newSize;
+  }
+
+  morse->elements.array[morse->elements.count++] = *element;
+  return 1;
+}
+
+static int
+addMorseMark (MorseObject *morse, unsigned int units) {
+  ToneElement element = TONE_PLAY((morse->parameters.unit * units), morse->parameters.frequency);
+  return addMorseElement(morse, &element);
+}
+
+static int
+addMorseGap (MorseObject *morse, unsigned int units) {
+  ToneElement element = TONE_REST((morse->parameters.unit * units));
+  return addMorseElement(morse, &element);
+}
+
+int
+addMorsePattern (MorseObject *morse, MorsePattern pattern) {
+  if (pattern) {
+    int addGap = 0;
+
+    while (pattern != 0B1) {
+      if (!addGap) {
+        addGap = 1;
+      } else if (!addMorseGap(morse, MORSE_UNITS_GAP_SYMBOL)) {
+        return 0;
+      }
+
+      unsigned int units = (pattern & 0B1)? MORSE_UNITS_MARK_SHORT: MORSE_UNITS_MARK_LONG;
+      if (!addMorseMark(morse, units)) return 0;
+      pattern >>= 1;
+    }
+  }
+
+  return 1;
+}
+
+int
+addMorseCharacter (MorseObject *morse, wchar_t character) {
+  if (!iswspace(character)) {
+    if (morse->state.wasSpace) {
+      morse->state.wasSpace = 0;
+    } else if (!addMorseGap(morse, MORSE_UNITS_GAP_LETTER)) {
+      return 0;
+    }
+
+    if (!addMorsePattern(morse, getMorsePattern(character))) return 0;
+  } else if (!morse->state.wasSpace) {
+    morse->state.wasSpace = 1;
+    if (!addMorseGap(morse, MORSE_UNITS_GAP_WORD)) return 0;
+  }
+
+  return 1;
+}
+
+int
+addMorseSpace (MorseObject *morse) {
+  return addMorseCharacter(morse, WC_C(' '));
+}
+
+int
+addMorseCharacters (MorseObject *morse, const wchar_t *characters, size_t count) {
+  const wchar_t *character = characters;
+  const wchar_t *end = character + count;
+
+  while (character < end) {
+    if (!addMorseCharacter(morse, *character++)) return 0;
+  }
+
+  return 1;
+}
+
+int
+addMorseString (MorseObject *morse, const char *string) {
+  size_t size = strlen(string) + 1;
+  wchar_t characters[size];
+
+  const char *byte = string;
+  wchar_t *end = characters;
+
+  convertUtf8ToWchars(&byte, &end, size);
+  return addMorseCharacters(morse, characters, (end - characters));
+}
+
+int
+playMorseSequence (MorseObject *morse) {
+  {
+    ToneElement element = TONE_STOP();
+    if (!addMorseElement(morse, &element)) return 0;
+  }
+
+  tunePlayTones(morse->elements.array);
+  tuneSynchronize();
+  return 1;
+}
+
+void
+clearMorseSequence (MorseObject *morse) {
+  morse->elements.count = 0;
+  morse->state.wasSpace = 1;
+}
+
+unsigned int
+getMorsePitch (MorseObject *morse) {
+  return morse->parameters.frequency;
+}
+
+int
+setMorsePitch (MorseObject *morse, unsigned int frequency) {
+  if (frequency < 1) return 0;
+  if (frequency > 0XFFFF) return 0;
+
+  morse->parameters.frequency = frequency;
+  return 1;
+}
+
+static inline unsigned int
+getMorseReferenceDuration (unsigned int unitsPerMinute) {
+  return 60000 / unitsPerMinute;
+}
+
+static unsigned int
+getMorseSpeed (MorseObject *morse, unsigned int unitsPerMinute) {
+  return getMorseReferenceDuration(unitsPerMinute) / morse->parameters.unit;
+}
+
+static int
+setMorseSpeed (MorseObject *morse, unsigned int speed, unsigned int unitsPerMinute) {
+  unsigned int unitDuration = getMorseReferenceDuration(unitsPerMinute) / speed;
+  if (unitDuration < 10) return 0;
+
+  morse->parameters.unit = unitDuration;
+  return 1;
+}
+
+unsigned int
+getMorseWordsPerMinute (MorseObject *morse) {
+  return getMorseSpeed(morse, MORSE_UNITS_PER_WORD);
+}
+
+int
+setMorseWordsPerMinute (MorseObject *morse, unsigned int speed) {
+  return setMorseSpeed(morse, speed, MORSE_UNITS_PER_WORD);
+}
+
+unsigned int
+getMorseGroupsPerMinute (MorseObject *morse) {
+  return getMorseSpeed(morse, MORSE_UNITS_PER_GROUP);
+}
+
+int
+setMorseGroupsPerMinute (MorseObject *morse, unsigned int speed) {
+  return setMorseSpeed(morse, speed, MORSE_UNITS_PER_GROUP);
+}
+
+void *
+newMorseObject (void) {
+  MorseObject *morse;
+
+  if ((morse = malloc(sizeof(*morse)))) {
+    memset(morse, 0, sizeof(*morse));
+
+    setMorsePitch(morse, 440);
+    setMorseWordsPerMinute(morse, 20);
+
+    morse->elements.array = NULL;
+    morse->elements.size = 0;
+
+    clearMorseSequence(morse);
+    return morse;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyMorseObject (MorseObject *morse) {
+  if (morse->elements.array) free(morse->elements.array);
+  free(morse);
+}
diff --git a/Programs/msg_queue.c b/Programs/msg_queue.c
new file mode 100644
index 0000000..cd6a37b
--- /dev/null
+++ b/Programs/msg_queue.c
@@ -0,0 +1,167 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/msg.h>
+
+#include "log.h"
+#include "msg_queue.h"
+#include "async_event.h"
+#include "thread.h"
+
+typedef struct {
+  MessageType type;
+  char content[];
+} Message;
+
+#define MESSAGE(name, length) \
+  unsigned char name##_bytes[sizeof(MessageType) + (length)]; \
+  Message *name = (void *)name##_bytes;
+
+int
+sendMessage (int queue, MessageType type, const void *content, size_t length, int flags) {
+  MESSAGE(message, length);
+
+  if (!content) {
+    length = 0;
+  } else if (length) {
+    memcpy(message->content, content, length);
+  }
+
+  message->type = type;
+  if (msgsnd(queue, message, length, flags) != -1) return 1;
+
+  logSystemError("msgsnd");
+  return 0;
+}
+
+ssize_t
+receiveMessage (int queue, MessageType *type, void *buffer, size_t size, int flags) {
+  MESSAGE(message, size);
+
+  if (!buffer) size = 0;
+  ssize_t length = msgrcv(queue, message, size, *type, flags);
+
+  if (length != -1) {
+    *type = message->type;
+    if (length) memcpy(buffer, message->content, length);
+  } else if (errno != EIDRM) {
+    logSystemError("msgrcv");
+  }
+
+  return length;
+}
+
+typedef struct {
+  AsyncEvent *event;
+  pthread_t thread;
+
+  MessageHandler *handler;
+  void *data;
+
+  int queue;
+  MessageType type;
+  size_t size;
+} MessageReceiverArgument;
+
+ASYNC_EVENT_CALLBACK(handleReceivedMessage) {
+  MessageReceiverArgument *mra = parameters->eventData;
+  MessageHandlerParameters *mhp = parameters->signalData;
+
+  if (mhp) {
+    mra->handler(mhp);
+    free(mhp);
+  } else {
+    void *result;
+    pthread_join(mra->thread, &result);
+
+    asyncDiscardEvent(mra->event);
+    free(mra);
+  }
+}
+
+THREAD_FUNCTION(messageReceiverThread) {
+  MessageReceiverArgument *mra = argument;
+  char buffer[mra->size];
+
+  while (1) {
+    MessageType type = mra->type;
+    ssize_t length = receiveMessage(mra->queue, &type, buffer, mra->size, 0);
+
+    if (length != -1) {
+      MessageHandlerParameters *mhp;
+
+      if ((mhp = malloc(sizeof(*mhp) + length))) {
+        memset(mhp, 0, sizeof(*mhp));
+
+        mhp->data = mra->data;
+        mhp->type = type;
+
+        mhp->length = length;
+        memcpy(mhp->content, buffer, mhp->length);
+
+        if (asyncSignalEvent(mra->event, mhp)) continue;
+        free(mhp);
+      } else {
+        logMallocError();
+      }
+    }
+
+    break;
+  }
+
+  asyncSignalEvent(mra->event, NULL);
+  return NULL;
+}
+
+int
+startMessageReceiver (const char *name, int queue, MessageType type, size_t size, MessageHandler *handler, void *data) {
+  MessageReceiverArgument *mra;
+
+  if ((mra = malloc(sizeof(*mra)))) {
+    memset(mra, 0, sizeof(*mra));
+
+    mra->handler = handler;
+    mra->data = data;
+
+    mra->queue = queue;
+    mra->type = type;
+    mra->size = size;
+
+    if ((mra->event = asyncNewEvent(handleReceivedMessage, mra))) {
+      int threadCreationError = createThread(name, &mra->thread, NULL, messageReceiverThread, mra);
+
+      if (!threadCreationError) {
+        logMessage(LOG_DEBUG, "message receiver started: %s", name);
+        return 1;
+      }
+
+      asyncDiscardEvent(mra->event);
+    }
+
+    free(mra);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(LOG_WARNING, "message receiver not started: %s", name);
+  return 0;
+}
diff --git a/Programs/msgtest.c b/Programs/msgtest.c
new file mode 100644
index 0000000..0155d95
--- /dev/null
+++ b/Programs/msgtest.c
@@ -0,0 +1,308 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "program.h"
+#include "cmdline.h"
+#include "messages.h"
+#include "parse.h"
+#include "file.h"
+
+static char *opt_localeDirectory;
+static char *opt_localeSpecifier;
+static char *opt_domainName;
+
+static FILE *outputStream;
+static int opt_utf8Output;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "directory",
+    .letter = 'd',
+    .argument = strtext("path"),
+    .setting.string = &opt_localeDirectory,
+    .internal.adjust = fixInstallPath,
+    .description = strtext("the locale directory containing the translations")
+  },
+
+  { .word = "locale",
+    .letter = 'l',
+    .argument = strtext("specifier"),
+    .setting.string = &opt_localeSpecifier,
+    .description = strtext("the locale in which to look up a translation")
+  },
+
+  { .word = "domain",
+    .letter = 'n',
+    .argument = strtext("name"),
+    .setting.string = &opt_domainName,
+    .description = strtext("the name of the domain containing the translations")
+  },
+
+  { .word = "utf8",
+    .letter = 'u',
+    .setting.flag = &opt_utf8Output,
+    .description = strtext("write the translations using UTF-8")
+  },
+END_OPTION_TABLE(programOptions)
+
+static int
+noOutputErrorYet (void) {
+  if (!ferror(outputStream)) return 1;
+  logMessage(LOG_ERR, "output error: %s", strerror(errno));
+  return 0;
+}
+
+static int
+putCharacter (char c) {
+  fputc(c, outputStream);
+  return noOutputErrorYet();
+}
+
+static int
+putNewline (void) {
+  return putCharacter('\n');
+}
+
+static int
+putBytes (const char *bytes, size_t count) {
+  while (count) {
+    uint32_t last = count - 1;
+    if (bytes[last] != '\n') break;
+    count = last;
+  }
+
+  if (opt_utf8Output) {
+    fwrite(bytes, 1, count, outputStream);
+  } else {
+    writeWithConsoleEncoding(outputStream, bytes, count);
+  }
+
+  return noOutputErrorYet();
+}
+
+static int
+putString (const char *string) {
+  return putBytes(string, strlen(string));
+}
+
+static int
+putMessage (const Message *message) {
+  return putBytes(getMessageText(message), getMessageLength(message));
+}
+
+static int
+listTranslation (const Message *source, const Message *translation) {
+  return putMessage(source)
+      && putString(" -> ")
+      && putMessage(translation)
+      && putNewline();
+}
+
+static int
+listAllTranslations (void) {
+  uint32_t count = getMessageCount();
+
+  for (unsigned int index=0; index<count; index+=1) {
+    const Message *source = getSourceMessage(index);
+    if (getMessageLength(source) == 0) continue;
+
+    const Message *translation = getTranslatedMessage(index);
+    if (!listTranslation(source, translation)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+showSimpleTranslation (const char *text) {
+  {
+    unsigned int index;
+
+    if (findSourceMessage(text, strlen(text), &index)) {
+      return putMessage(getTranslatedMessage(index)) && putNewline();
+    }
+  }
+
+  logMessage(LOG_WARNING, "translation not found: %s", text);
+  return 0;
+}
+
+static int
+showPluralTranslation (const char *singular, const char *plural, int count) {
+  const char *translation = getPluralTranslation(singular, plural, count);
+  return putString(translation) && putNewline();
+}
+
+static int
+showProperty (const char *propertyName, const char *attributeName) {
+  int ok = 0;
+  char *propertyValue = getMessagesProperty(propertyName);
+
+  if (propertyValue) {
+    if (!attributeName) {
+      fprintf(outputStream, "%s\n", propertyValue);
+      ok = noOutputErrorYet();
+    } else {
+      char *attributeValue = getMessagesAttribute(propertyValue, attributeName);
+
+      if (attributeValue) {
+        fprintf(outputStream, "%s\n", attributeValue);
+        ok = noOutputErrorYet();
+        free(attributeValue);
+      } else {
+        logMessage(LOG_WARNING,
+          "attribute not defined: %s: %s",
+          propertyName, attributeName
+        );
+      }
+    }
+
+    free(propertyValue);
+  } else {
+    logMessage(LOG_WARNING, "property not defined: %s", propertyName);
+  }
+
+  return ok;
+}
+
+static int
+parseQuantity (int *count, const char *quantity) {
+  static const int minimum = 0;
+  static const int maximum = 999999999;
+
+  if (validateInteger(count, quantity, &minimum, &maximum)) return 1;
+  logMessage(LOG_ERR, "invalid quantity: %s", quantity);
+  return 0;
+}
+
+static const char *
+nextParameter (char ***argv, int *argc, const char *description) {
+  if (*argc) {
+    *argc -= 1;
+    return *(*argv)++;
+  }
+
+  if (!description) return NULL;
+  logMessage(LOG_ERR, "missing %s", description);
+  exit(PROG_EXIT_SYNTAX);
+}
+
+static void
+noMoreParameters (char ***argv, int *argc) {
+  if (*argc) {
+    logMessage(LOG_ERR, "too many parameters");
+    exit(PROG_EXIT_SYNTAX);
+  }
+}
+
+static void
+beginAction (char ***argv, int *argc) {
+  noMoreParameters(argv, argc);
+
+  {
+    const char *directory = opt_localeDirectory;
+
+    if (*directory) {
+      if (!testDirectoryPath(directory)) {
+        logMessage(LOG_WARNING, "not a directory: %s", directory);
+        exit(PROG_EXIT_SEMANTIC);
+      }
+
+      setMessagesDirectory(directory);
+    }
+  }
+
+  if (*opt_localeSpecifier) setMessagesLocale(opt_localeSpecifier);
+  if (*opt_domainName) setMessagesDomain(opt_domainName);
+  if (!loadMessageCatalog()) exit(PROG_EXIT_FATAL);
+}
+
+int
+main (int argc, char *argv[]) {
+  outputStream = stdout;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "msgtest",
+
+      .usage = {
+        .purpose = strtext("Test message localization using the message catalog reader."),
+        .parameters = "action [argument ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (!argc) {
+    logMessage(LOG_ERR, "missing action");
+    exit(PROG_EXIT_SYNTAX);
+  }
+
+  const char *action = *argv++;
+  argc -= 1;
+  int ok = 1;
+
+  if (isAbbreviation("translation", action)) {
+    const char *message = nextParameter(&argv, &argc, "message");
+    const char *plural = nextParameter(&argv, &argc, NULL);
+
+    if (plural) {
+      const char *quantity = nextParameter(&argv, &argc, "quantity");
+
+      int count;
+      if (!parseQuantity(&count, quantity)) return PROG_EXIT_SYNTAX;
+
+      beginAction(&argv, &argc);
+      ok = showPluralTranslation(message, plural, count);
+    } else {
+      beginAction(&argv, &argc);
+      ok = showSimpleTranslation(message);
+    }
+  } else if (isAbbreviation("count", action)) {
+    beginAction(&argv, &argc);
+    fprintf(outputStream, "%u\n", getMessageCount());
+    ok = noOutputErrorYet();
+  } else if (isAbbreviation("all", action)) {
+    beginAction(&argv, &argc);
+    ok = listAllTranslations();
+  } else if (isAbbreviation("metadata", action)) {
+    beginAction(&argv, &argc);
+    fprintf(outputStream, "%s\n", getMessagesMetadata());
+    ok = noOutputErrorYet();
+  } else if (isAbbreviation("property", action)) {
+    const char *property = nextParameter(&argv, &argc, "property name");
+    const char *attribute = nextParameter(&argv, &argc, NULL);
+
+    beginAction(&argv, &argc);
+    ok = showProperty(property, attribute);
+  } else {
+    logMessage(LOG_ERR, "unknown action: %s", action);
+    return PROG_EXIT_SYNTAX;
+  }
+
+  if (ferror(outputStream)) return PROG_EXIT_FATAL;
+  return ok? PROG_EXIT_SUCCESS: PROG_EXIT_SEMANTIC;
+}
diff --git a/Programs/notes.c b/Programs/notes.c
new file mode 100644
index 0000000..955a2c0
--- /dev/null
+++ b/Programs/notes.c
@@ -0,0 +1,214 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "notes.h"
+
+#define NOTE_FREQUENCY_FACTOR 1000
+
+static const uint32_t scaledNoteFrequencies[] = {
+  /*   0 rest */        0,
+  /*   1 -1C# */     8662,
+  /*   2 -1D  */     9177,
+  /*   3 -1D# */     9723,
+  /*   4 -1E  */    10301,
+  /*   5 -1F  */    10913,
+  /*   6 -1F# */    11562,
+  /*   7 -1G  */    12250,
+  /*   8 -1G# */    12978,
+  /*   9 -1A  */    13750,
+  /*  10 -1A# */    14568,
+  /*  11 -1B  */    15434,
+  /*  12  0C  */    16352,
+  /*  13  0C# */    17324,
+  /*  14  0D  */    18354,
+  /*  15  0D# */    19445,
+  /*  16  0E  */    20602,
+  /*  17  0F  */    21827,
+  /*  18  0F# */    23125,
+  /*  19  0G  */    24500,
+  /*  20  0G# */    25957,
+  /*  21  0A  */    27500,
+  /*  22  0A# */    29135,
+  /*  23  0B  */    30868,
+  /*  24  1C  */    32703,
+  /*  25  1C# */    34648,
+  /*  26  1D  */    36708,
+  /*  27  1D# */    38891,
+  /*  28  1E  */    41203,
+  /*  29  1F  */    43654,
+  /*  30  1F# */    46249,
+  /*  31  1G  */    48999,
+  /*  32  1G# */    51913,
+  /*  33  1A  */    55000,
+  /*  34  1A# */    58270,
+  /*  35  1B  */    61735,
+  /*  36  2C  */    65406,
+  /*  37  2C# */    69296,
+  /*  38  2D  */    73416,
+  /*  39  2D# */    77782,
+  /*  40  2E  */    82407,
+  /*  41  2F  */    87307,
+  /*  42  2F# */    92499,
+  /*  43  2G  */    97999,
+  /*  44  2G# */   103826,
+  /*  45  2A  */   110000,
+  /*  46  2A# */   116541,
+  /*  47  2B  */   123471,
+  /*  48  3C  */   130813,
+  /*  49  3C# */   138591,
+  /*  50  3D  */   146832,
+  /*  51  3D# */   155563,
+  /*  52  3E  */   164814,
+  /*  53  3F  */   174614,
+  /*  54  3F# */   184997,
+  /*  55  3G  */   195998,
+  /*  56  3G# */   207652,
+  /*  57  3A  */   220000,
+  /*  58  3A# */   233082,
+  /*  59  3B  */   246942,
+  /*  60  4C  */   261626,
+  /*  61  4C# */   277183,
+  /*  62  4D  */   293665,
+  /*  63  4D# */   311127,
+  /*  64  4E  */   329628,
+  /*  65  4F  */   349228,
+  /*  66  4F# */   369994,
+  /*  67  4G  */   391995,
+  /*  68  4G# */   415305,
+  /*  69  4A  */   440000,
+  /*  70  4A# */   466164,
+  /*  71  4B  */   493883,
+  /*  72  5C  */   523251,
+  /*  73  5C# */   554365,
+  /*  74  5D  */   587330,
+  /*  75  5D# */   622254,
+  /*  76  5E  */   659255,
+  /*  77  5F  */   698456,
+  /*  78  5F# */   739989,
+  /*  79  5G  */   783991,
+  /*  80  5G# */   830609,
+  /*  81  5A  */   880000,
+  /*  82  5A# */   932328,
+  /*  83  5B  */   987767,
+  /*  84  6C  */  1046502,
+  /*  85  6C# */  1108731,
+  /*  86  6D  */  1174659,
+  /*  87  6D# */  1244508,
+  /*  88  6E  */  1318510,
+  /*  89  6F  */  1396913,
+  /*  90  6F# */  1479978,
+  /*  91  6G  */  1567982,
+  /*  92  6G# */  1661219,
+  /*  93  6A  */  1760000,
+  /*  94  6A# */  1864655,
+  /*  95  6B  */  1975533,
+  /*  96  7C  */  2093005,
+  /*  97  7C# */  2217461,
+  /*  98  7D  */  2349318,
+  /*  99  7D# */  2489016,
+  /* 100  7E  */  2637020,
+  /* 101  7F  */  2793826,
+  /* 102  7F# */  2959955,
+  /* 103  7G  */  3135963,
+  /* 104  7G# */  3322438,
+  /* 105  7A  */  3520000,
+  /* 106  7A# */  3729310,
+  /* 107  7B  */  3951066,
+  /* 108  8C  */  4186009,
+  /* 109  8C# */  4434922,
+  /* 110  8D  */  4698636,
+  /* 111  8D# */  4978032,
+  /* 112  8E  */  5274041,
+  /* 113  8F  */  5587652,
+  /* 114  8F# */  5919911,
+  /* 115  8G  */  6271927,
+  /* 116  8G# */  6644875,
+  /* 117  8A  */  7040000,
+  /* 118  8A# */  7458620,
+  /* 119  8B  */  7902133,
+  /* 120  9C  */  8372018,
+  /* 121  9C# */  8869844,
+  /* 122  9D  */  9397273,
+  /* 123  9D# */  9956063,
+  /* 124  9E  */ 10548082,
+  /* 125  9F  */ 11175303,
+  /* 126  9F# */ 11839822,
+  /* 127  9G  */ 12543854
+};
+
+unsigned char
+getLowestNote (void) {
+  return 1;
+}
+
+unsigned char
+getHighestNote (void) {
+  return ARRAY_COUNT(scaledNoteFrequencies) - 1;
+}
+
+static inline uint32_t
+getScaledNoteFrequency (unsigned char note) {
+  unsigned char highestNote = getHighestNote();
+
+  if (note > highestNote) note = highestNote;
+  return scaledNoteFrequencies[note];
+}
+
+uint32_t
+getIntegerNoteFrequency (unsigned char note) {
+  return getScaledNoteFrequency(note) / NOTE_FREQUENCY_FACTOR;
+}
+
+#ifndef NO_FLOAT
+float
+getRealNoteFrequency (unsigned char note) {
+  return (float)getScaledNoteFrequency(note) / (float)NOTE_FREQUENCY_FACTOR;
+}
+#endif /* NO_FLOAT */
+
+unsigned char
+getNearestNote (NoteFrequency frequency) {
+  if (!frequency) return 0;
+
+  unsigned char lowestNote = getLowestNote();
+  if (frequency <= getNoteFrequency(lowestNote)) return lowestNote;
+
+  unsigned char highestNote = getHighestNote();
+  if (frequency >= getNoteFrequency(highestNote)) return highestNote;
+
+  while (lowestNote <= highestNote) {
+    unsigned char currentNote = (lowestNote + highestNote) / 2;
+
+    if (frequency < getNoteFrequency(currentNote)) {
+      highestNote = currentNote - 1;
+    } else {
+      lowestNote = currentNote + 1;
+    }
+  }
+
+  unsigned char lowerNote = highestNote;
+  unsigned char higherNote = lowerNote + 1;
+
+  NoteFrequency lowerFrequency = getNoteFrequency(lowerNote);
+  NoteFrequency higherFrequency = getNoteFrequency(higherNote);
+
+  return ((frequency - lowerFrequency) < (higherFrequency - frequency))?
+         lowerNote: higherNote;
+}
diff --git a/Programs/notes_beep.c b/Programs/notes_beep.c
new file mode 100644
index 0000000..9eb65c7
--- /dev/null
+++ b/Programs/notes_beep.c
@@ -0,0 +1,86 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "async_wait.h"
+#include "beep.h"
+#include "notes.h"
+
+struct NoteDeviceStruct {
+  char mustHaveAtLeastOneField;
+};
+
+static NoteDevice *
+beepConstruct (int errorLevel) {
+  NoteDevice *device;
+
+  if ((device = malloc(sizeof(*device)))) {
+    if (canBeep()) {
+      logMessage(LOG_DEBUG, "beeper enabled");
+      return device;
+    }
+
+    free(device);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(LOG_DEBUG, "beeper not available");
+  return NULL;
+}
+
+static void
+beepDestruct (NoteDevice *device) {
+  endBeep();
+  free(device);
+  logMessage(LOG_DEBUG, "beeper disabled");
+}
+
+static int
+beepTone (NoteDevice *device, unsigned int duration, NoteFrequency frequency) {
+  uint32_t pitch = frequency;
+  logMessage(LOG_DEBUG, "tone: MSecs:%u Freq:%"PRIu32, duration, pitch);
+
+  if (!pitch) {
+    asyncWait(duration);
+    return 1;
+  }
+
+  return playBeep(pitch, duration);
+}
+
+static int
+beepNote (NoteDevice *device, unsigned int duration, unsigned char note) {
+  return beepTone(device, duration, getNoteFrequency(note));
+}
+
+static int
+beepFlush (NoteDevice *device) {
+  return 1;
+}
+
+const NoteMethods beepNoteMethods = {
+  .construct = beepConstruct,
+  .destruct = beepDestruct,
+
+  .tone = beepTone,
+  .note = beepNote,
+  .flush = beepFlush
+};
diff --git a/Programs/notes_fm.c b/Programs/notes_fm.c
new file mode 100644
index 0000000..011bbd7
--- /dev/null
+++ b/Programs/notes_fm.c
@@ -0,0 +1,95 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "prefs.h"
+#include "async_wait.h"
+#include "notes.h"
+#include "fm.h"
+
+struct NoteDeviceStruct {
+  unsigned int channelNumber;
+};
+
+static NoteDevice *
+fmConstruct (int errorLevel) {
+  NoteDevice *device;
+
+  if ((device = malloc(sizeof(*device)))) {
+    if (fmEnablePorts(errorLevel)) {
+      if (fmTestCard(errorLevel)) {
+        device->channelNumber = 0;
+
+        logMessage(LOG_DEBUG, "FM enabled");
+        return device;
+      }
+
+      fmDisablePorts();
+    }
+
+    free(device);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(LOG_DEBUG, "FM not available");
+  return NULL;
+}
+
+static void
+fmDestruct (NoteDevice *device) {
+  free(device);
+  fmDisablePorts();
+  logMessage(LOG_DEBUG, "FM disabled");
+}
+
+static int
+fmTone (NoteDevice *device, unsigned int duration, NoteFrequency frequency) {
+  uint32_t pitch = frequency;
+  logMessage(LOG_DEBUG, "tone: MSecs:%u Freq:%"PRIu32,
+             duration, pitch);
+
+  if (pitch) {
+    fmPlayTone(device->channelNumber, pitch, duration, prefs.fmVolume);
+  } else {
+    asyncWait(duration);
+  }
+
+  return 1;
+}
+
+static int
+fmNote (NoteDevice *device, unsigned int duration, unsigned char note) {
+  return fmTone(device, duration, getNoteFrequency(note));
+}
+
+static int
+fmFlush (NoteDevice *device) {
+  return 1;
+}
+
+const NoteMethods fmNoteMethods = {
+  .construct = fmConstruct,
+  .destruct = fmDestruct,
+
+  .tone = fmTone,
+  .note = fmNote,
+  .flush = fmFlush
+};
diff --git a/Programs/notes_midi.c b/Programs/notes_midi.c
new file mode 100644
index 0000000..dc1e414
--- /dev/null
+++ b/Programs/notes_midi.c
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "prefs.h"
+#include "log.h"
+#include "midi.h"
+#include "notes.h"
+
+char *opt_midiDevice;
+
+struct NoteDeviceStruct {
+  MidiDevice *midi;
+  int channelNumber;
+};
+
+static NoteDevice *
+midiConstruct (int errorLevel) {
+  NoteDevice *device;
+
+  if ((device = malloc(sizeof(*device)))) {
+    if ((device->midi = openMidiDevice(errorLevel, opt_midiDevice))) {
+      device->channelNumber = 0;
+      setMidiInstrument(device->midi, device->channelNumber, prefs.midiInstrument);
+
+      logMessage(LOG_DEBUG, "MIDI enabled");
+      return device;
+    }
+
+    free(device);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(LOG_DEBUG, "MIDI not available");
+  return NULL;
+}
+
+static void
+midiDestruct (NoteDevice *device) {
+  closeMidiDevice(device->midi);
+  free(device);
+  logMessage(LOG_DEBUG, "MIDI disabled");
+}
+
+static int
+midiNote (NoteDevice *device, unsigned int duration, unsigned char note) {
+  logMessage(LOG_DEBUG, "tone: MSecs:%u Note:%u", duration, note);
+  beginMidiBlock(device->midi);
+
+  if (note) {
+    startMidiNote(device->midi, device->channelNumber, note, prefs.midiVolume);
+    insertMidiWait(device->midi, duration);
+    stopMidiNote(device->midi, device->channelNumber);
+  } else {
+    insertMidiWait(device->midi, duration);
+  }
+
+  endMidiBlock(device->midi);
+  return 1;
+}
+
+static int
+midiTone (NoteDevice *device, unsigned int duration, NoteFrequency frequency) {
+  return midiNote(device, duration, getNearestNote(frequency));
+}
+
+static int
+midiFlush (NoteDevice *device) {
+  return flushMidiDevice(device->midi);
+}
+
+const NoteMethods midiNoteMethods = {
+  .construct = midiConstruct,
+  .destruct = midiDestruct,
+
+  .tone = midiTone,
+  .note = midiNote,
+  .flush = midiFlush
+};
diff --git a/Programs/notes_pcm.c b/Programs/notes_pcm.c
new file mode 100644
index 0000000..f79c1a7
--- /dev/null
+++ b/Programs/notes_pcm.c
@@ -0,0 +1,269 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "prefs.h"
+#include "log.h"
+#include "pcm.h"
+#include "notes.h"
+
+char *opt_pcmDevice;
+
+struct NoteDeviceStruct {
+  PcmDevice *pcm;
+
+  int blockSize;
+  int sampleRate;
+  int channelCount;
+  PcmAmplitudeFormat amplitudeFormat;
+
+  unsigned char *blockAddress;
+  int blockUsed;
+
+  PcmSampleMaker makeSample;
+};
+
+static int
+pcmFlushBytes (NoteDevice *device) {
+  int ok = writePcmData(device->pcm, device->blockAddress, device->blockUsed);
+  if (ok) device->blockUsed = 0;
+  return ok;
+}
+
+static int
+pcmWriteSample (NoteDevice *device, int16_t amplitude) {
+  PcmSample *sample = (PcmSample *)&device->blockAddress[device->blockUsed];
+  PcmSampleSize size = device->makeSample(sample, amplitude);
+  device->blockUsed += size;
+
+  for (int channel=1; channel<device->channelCount; channel+=1) {
+    for (int byte=0; byte<size; byte+=1) {
+      device->blockAddress[device->blockUsed++] = sample->bytes[byte];
+    }
+  }
+
+  if (device->blockUsed == device->blockSize) {
+    if (!pcmFlushBytes(device)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int
+pcmFlushBlock (NoteDevice *device) {
+  while (device->blockUsed)
+    if (!pcmWriteSample(device, 0))
+      return 0;
+
+  return 1;
+}
+
+static NoteDevice *
+pcmConstruct (int errorLevel) {
+  NoteDevice *device;
+
+  if ((device = malloc(sizeof(*device)))) {
+    memset(device, 0, sizeof(*device));
+
+    if ((device->pcm = openPcmDevice(errorLevel, opt_pcmDevice))) {
+      device->blockSize = getPcmBlockSize(device->pcm);
+      device->sampleRate = getPcmSampleRate(device->pcm);
+      device->channelCount = getPcmChannelCount(device->pcm);
+      device->amplitudeFormat = getPcmAmplitudeFormat(device->pcm);
+
+      device->blockUsed = 0;
+      device->makeSample = getPcmSampleMaker(device->amplitudeFormat);
+
+      PcmSample sample;
+      PcmSampleSize sampleSize = device->makeSample(&sample, 0);
+      sampleSize *= device->channelCount;
+
+      if (sampleSize && device->blockSize &&
+          !(device->blockSize % sampleSize)) {
+        if ((device->blockAddress = malloc(device->blockSize))) {
+          logMessage(LOG_DEBUG, "PCM enabled: BlkSz:%d Rate:%d ChnCt:%d Fmt:%d",
+                     device->blockSize, device->sampleRate, device->channelCount, device->amplitudeFormat);
+          return device;
+        } else {
+          logMallocError();
+        }
+      } else {
+        logMessage(LOG_ERR,
+                   "PCM block size not multiple of sample size:"
+                   " BlkSz:%d" " SmpSz:%u",
+                   device->blockSize, sampleSize);
+      }
+
+      closePcmDevice(device->pcm);
+    }
+
+    free(device);
+  } else {
+    logMallocError();
+  }
+
+  logMessage(LOG_DEBUG, "PCM not available");
+  return NULL;
+}
+
+static void
+pcmDestruct (NoteDevice *device) {
+  pcmFlushBlock(device);
+  free(device->blockAddress);
+  closePcmDevice(device->pcm);
+  free(device);
+  logMessage(LOG_DEBUG, "PCM disabled");
+}
+
+static int
+pcmTone (NoteDevice *device, unsigned int duration, NoteFrequency frequency) {
+  int32_t sampleCount = device->sampleRate * duration / 1000;
+
+  logMessage(LOG_DEBUG, "tone: MSecs:%u SmpCt:%"PRId32 " Freq:%"PRIfreq,
+             duration, sampleCount, frequency);
+
+  if (frequency) {
+    /* A triangle waveform sounds nice, is lightweight, and avoids
+     * relying too much on floating-point performance and/or on
+     * expensive math functions like sin(). Considerations like
+     * these are especially important on PDAs without any FPU.
+     */ 
+
+    /* We need to know the maximum amplitude based on the currently set
+     * volume percentage. This percentage then needs to be squared because
+     * we perceive loudness exponentially.
+     */
+    const unsigned char fullVolume = 100;
+    const unsigned char currentVolume = MIN(fullVolume, prefs.pcmVolume);
+    const int32_t maximumAmplitude = INT16_MAX
+                                   * (currentVolume * currentVolume)
+                                   / (fullVolume * fullVolume);
+
+    /* The calculations for triangle wave generation work out nicely and
+     * efficiently if we map a full period onto a 32-bit unsigned range.
+     */
+
+    /* The two high-order bits specify which quarter wave a sample is for.
+     *   00 -> ascending from the negative peak to zero
+     *   01 -> ascending from zero to the positive peak
+     *   10 -> descending from the positive peak to zero
+     *   11 -> descending from zero to the negative peak
+     * The higher bit is 0 for the ascending segment and 1 for the
+     * descending segment. The lower bit is 0 when going from a peak to
+     * zero and 1 when going from zero to a peak.
+     */
+    const uint8_t magnitudeWidth = 32 - 2;
+
+    /* The amplitude is 0 when the lower bit of the quarter wave indicator
+     * is 1 and the rest of the (magnitude) bits are all 0.
+     */
+    const uint32_t zeroValue = UINT32_C(1) << magnitudeWidth;
+
+    /* We need to know how many steps to make from one sample to the next.
+     * stepsPerSample = stepsPerWave * wavesPerSecond / samplesPerSecond
+     *                = stepsPerWave * frequency / sampleRate
+     *                = stepsPerWave / sampleRate * frequency
+     */
+    const uint32_t stepsPerSample = (NoteFrequency)UINT32_MAX 
+                                  / (NoteFrequency)device->sampleRate
+                                  * frequency;
+
+    /* The current value needs to be a signed value so that the >> operator
+     * will extend its sign bit. We start by initializing it to the value
+     * that corresponds to the start of the first logical quarter wave
+     * (the one that ascends from zero to the positive peak).
+     */
+    int32_t currentValue = zeroValue;
+
+    /* Round the number of samples up to a whole number of periods:
+     * partialSteps = (sampleCount * stepsPerSample) % stepsPerWave
+     *
+     * With stepsPerWave being (1 << 32), we simply let the product
+     * overflow. The modulus corresponds to the remaining 32 low bits:
+     * partialSteps = (uint32_t)(sampleCount * stepsPerSample)
+     *
+     * missingSteps = stepsPerWave - partialSteps
+     *              = (uint32_t) -partialSteps
+
+     * extraSamples = missingSteps / stepsPerSample
+     */
+    sampleCount += (uint32_t)(sampleCount * -stepsPerSample) / stepsPerSample;
+
+    while (sampleCount > 0) {
+      /* Convert the current 32-bit unsigned linear value to a 31-bit
+       * triangular amplitude by inverting its low-order 31 bits if its
+       * high-order (sign) bit is set.
+       */
+      int32_t amplitude = currentValue ^ (currentValue >> 31);
+
+      /* Convert the 31-bit amplitude from unsigned to signed. */
+      amplitude -= zeroValue;
+
+      /* Convert the amplitude's magnitude from 30 bits to 16 bits. */
+      amplitude >>= magnitudeWidth - 16;
+
+      /* Adjust the 17-bit signed amplitude (sign bit + 16-bit value) by
+       * the currently set volume (15-bit value):
+       * (16-bit value) * (15-bit value) + (sign bit) = 32-bit signed value
+       */
+      amplitude *= maximumAmplitude;
+
+      /* Convert the signed amplitude from 32 bits to 16 bits. */
+      amplitude >>= 16;
+
+      if (!pcmWriteSample(device, amplitude)) break;
+      currentValue += stepsPerSample;
+      sampleCount -= 1;
+    }
+  } else {
+    /* generate silence */
+    while (sampleCount > 0) {
+      if (!pcmWriteSample(device, 0)) break;
+      sampleCount -= 1;
+    }
+  }
+
+  return (sampleCount > 0) ? 0 : 1;
+}
+
+static int
+pcmNote (NoteDevice *device, unsigned int duration, unsigned char note) {
+  return pcmTone(device, duration, getNoteFrequency(note));
+}
+
+static int
+pcmFlush (NoteDevice *device) {
+  int ok = pcmFlushBlock(device);
+  if (ok) pushPcmOutput(device->pcm);
+  return ok;
+}
+
+const NoteMethods pcmNoteMethods = {
+  .construct = pcmConstruct,
+  .destruct = pcmDestruct,
+
+  .tone = pcmTone,
+  .note = pcmNote,
+  .flush = pcmFlush
+};
diff --git a/Programs/parameters.h b/Programs/parameters.h
new file mode 100644
index 0000000..2f43efb
--- /dev/null
+++ b/Programs/parameters.h
@@ -0,0 +1,102 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PARAMETERS
+#define BRLTTY_INCLUDED_PARAMETERS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define PROGRAM_TERMINATION_REQUEST_COUNT_THRESHOLD 3
+#define PROGRAM_TERMINATION_REQUEST_RESET_SECONDS 5
+
+#define DEFAULT_ACTIVITY_START_TIMEOUT 1000
+#define DEFAULT_ACTIVITY_STOP_TIMEOUT 1000
+
+#define BRAILLE_DRIVER_START_RETRY_INTERVAL 5000
+#define BRAILLE_DRIVER_INPUT_POLL_INTERVAL 40
+
+#define BRAILLE_MESSAGE_ACKNOWLEDGEMENT_TIMEOUT 1000
+#define BRAILLE_MESSAGE_UNACKNOWLEDGEED_LIMIT 5
+
+#define SPEECH_DRIVER_START_RETRY_INTERVAL 5000
+#define SPEECH_DRIVER_START_AUTOSPEAK_DELAY 4000
+
+#define SPEECH_DRIVER_THREAD_START_TIMEOUT 15000
+#define SPEECH_DRIVER_THREAD_STOP_TIMEOUT 5000
+
+#define SPEECH_RESPONSE_WAIT_TIMEOUT 5000
+
+#define SCREEN_DRIVER_START_RETRY_INTERVAL 5000
+#define SCREEN_FREEZE_REMINDER_INTERVAL 30000
+#define SCREEN_UPDATE_POLL_INTERVAL 40
+#define SCREEN_UPDATE_SCHEDULE_DELAY 5
+
+#define KEYBOARD_MONITOR_START_RETRY_INTERVAL 5000
+
+#define PID_FILE_CREATE_RETRY_INTERVAL 5000
+
+#define UPDATE_SCHEDULE_DELAY 15
+
+#define ROUTING_PROCESS_NICENESS 10
+#define ROUTING_POLL_INTERVAL 1
+#define ROUTING_MAXIMUM_TIMEOUT 2000
+
+#define TUNE_DEVICE_CLOSE_DELAY 2000
+#define TUNE_TOGGLE_REPEAT_DELAY 100
+
+#define MESSAGE_HOLD_TIMEOUT 4000
+
+#define LEARN_MODE_TIMEOUT 10000
+
+#define INPUT_STICKY_MODIFIERS_TIMEOUT 5000
+
+#define MOUNT_TABLE_UPDATE_RETRY_INTERVAL 5000
+
+#define GPM_CONNECTION_RESET_DELAY 5000
+
+#define GIO_USB_INPUT_MONITOR_DISABLE 0
+
+#define SERIAL_DEVICE_RESTART_DELAY 500
+
+#define USB_INPUT_AWAIT_RETRY_INTERVAL_MINIMUM 10
+#define USB_INPUT_READ_INITIAL_TIMEOUT_DEFAULT 20
+#define USB_INPUT_INTERRUPT_DELAY_MAXIMUM 16
+#define USB_INPUT_INTERRUPT_REQUESTS_MAXIMUM 8
+
+#define BLUETOOTH_DEVICE_NAME_OBTAIN_TIMEOUT 5000
+#define BLUETOOTH_CHANNEL_BUSY_RETRY_TIMEOUT 2000
+#define BLUETOOTH_CHANNEL_BUSY_RETRY_INTERVAL 100
+#define BLUETOOTH_CHANNEL_CONNECT_TIMEOUT 15000
+
+#define LINUX_INPUT_DEVICE_OPEN_DELAY 1000
+#define LINUX_USB_INPUT_PIPE_DISABLE 0
+#define LINUX_USB_INPUT_USE_SIGNAL_MONITOR 0
+#define LINUX_USB_INPUT_TREAT_INTERRUPT_AS_BULK 0
+#define LINUX_BLUETOOTH_NAME_OBTAIN_ASYNCHRONOUS 1
+#define LINUX_BLUETOOTH_CHANNEL_DISCOVER_ASYNCHRONOUS 1
+#define LINUX_BLUETOOTH_CHANNEL_CONNECT_ASYNCHRONOUS 1
+
+#define WINDOWS_FILE_LOCK_RETRY_INTERVAL 1000
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PARAMETERS */
diff --git a/Programs/params_linux.c b/Programs/params_linux.c
new file mode 100644
index 0000000..fb07e60
--- /dev/null
+++ b/Programs/params_linux.c
@@ -0,0 +1,67 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "params.h"
+
+char *
+getBootParameters (const char *name) {
+  size_t nameLength = strlen(name);
+  char *parameters;
+
+  if ((parameters = strdup(""))) {
+    const char *path = "/proc/cmdline";
+    FILE *file;
+
+    if ((file = fopen(path, "r"))) {
+      char buffer[0X1000];
+      char *line = fgets(buffer, sizeof(buffer), file);
+
+      if (line) {
+        char *token;
+
+        while ((token = strtok(line, " \n"))) {
+          line = NULL;
+
+          if ((strncmp(token, name, nameLength) == 0) &&
+              (token[nameLength] == '=')) {
+            char *newParameters = strdup(token + nameLength + 1);
+
+            if (newParameters) {
+              free(parameters);
+              parameters = newParameters;
+            } else {
+              logMallocError();
+            }
+          }
+        }
+      }
+
+      fclose(file);
+    }
+  } else {
+    logMallocError();
+  }
+
+  return parameters;
+}
diff --git a/Programs/params_none.c b/Programs/params_none.c
new file mode 100644
index 0000000..359199e
--- /dev/null
+++ b/Programs/params_none.c
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "params.h"
+
+char *
+getBootParameters (const char *name) {
+  return NULL;
+}
diff --git a/Programs/parse.c b/Programs/parse.c
new file mode 100644
index 0000000..74cd0ed
--- /dev/null
+++ b/Programs/parse.c
@@ -0,0 +1,625 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "parse.h"
+#include "log.h"
+
+char *
+joinStrings (const char *const *strings, int count) {
+  char *string;
+  size_t length = 0;
+  size_t lengths[count];
+
+  for (unsigned int index=0; index<count; index+=1) {
+    length += lengths[index] = strlen(strings[index]);
+  }
+
+  if ((string = malloc(length+1))) {
+    char *target = string;
+
+    for (unsigned int index=0; index<count; index+=1) {
+      length = lengths[index];
+      memcpy(target, strings[index], length);
+      target += length;
+    }
+
+    *target = 0;
+  }
+
+  return string;
+}
+
+int
+changeStringSetting (char **setting, const char *value) {
+  if (value == *setting) return 1;
+  char *string;
+
+  if (!value) {
+    string = NULL;
+  } else if (!(string = strdup(value))) {
+    logMallocError();
+    return 0;
+  }
+
+  if (*setting) free(*setting);
+  *setting = string;
+  return 1;
+}
+
+int
+extendStringSetting (char **setting, const char *value, int prepend) {
+  if (!value) value = "";
+
+  if (*setting && **setting) {
+    if (*value) {
+      size_t newSize = strlen(*setting) + 1 + strlen(value) + 1;
+      char newSetting[newSize];
+
+      if (prepend) {
+        snprintf(newSetting, newSize, "%s%c%s", value, PARAMETER_SEPARATOR_CHARACTER, *setting);
+      } else {
+        snprintf(newSetting, newSize, "%s%c%s", *setting, PARAMETER_SEPARATOR_CHARACTER, value);
+      }
+
+      if (!changeStringSetting(setting, newSetting)) return 0;
+    }
+  } else if (!changeStringSetting(setting, value)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+void
+deallocateStrings (char **array) {
+  char **element = array;
+  while (*element) free(*element++);
+  free(array);
+}
+
+char **
+splitString (const char *string, char delimiter, int *count) {
+  char **array = NULL;
+
+  if (!string) string = "";
+
+  if (string) {
+    while (1) {
+      const char *start = string;
+      int index = 0;
+
+      if (*start) {
+        while (1) {
+          const char *end = strchr(start, delimiter);
+          size_t length = end? (size_t)(end-start): strlen(start);
+
+          if (array) {
+            char *element = malloc(length+1);
+
+            if (!(array[index] = element)) {
+              logMallocError();
+              deallocateStrings(array);
+              array = NULL;
+              goto done;
+            }
+
+            memcpy(element, start, length);
+            element[length] = 0;
+          }
+          index += 1;
+
+          if (!end) break;
+          start = end + 1;
+        }
+      }
+
+      if (array) {
+        array[index] = NULL;
+        if (count) *count = index;
+        break;
+      }
+
+      if (!(array = malloc((index + 1) * sizeof(*array)))) {
+        logMallocError();
+        break;
+      }
+    }
+  }
+
+done:
+  if (!array && count) *count = 0;
+  return array;
+}
+
+int
+changeListSetting (char ***list, char **setting, const char *value) {
+  char **newList = splitString(value, PARAMETER_SEPARATOR_CHARACTER, NULL);
+
+  if (newList) {
+    if (changeStringSetting(setting, value)) {
+      char **oldList = *list;
+      *list = newList;
+      if (oldList) deallocateStrings(oldList);
+      return 1;
+    }
+
+    deallocateStrings(newList);
+  }
+
+  return 0;
+}
+
+int
+rescaleInteger (int value, int from, int to) {
+  return (to * (value + (from / (to * 2)))) / from;
+}
+
+int
+isInteger (int *value, const char *string) {
+  if (*string) {
+    char *end;
+    long l = strtol(string, &end, 0);
+
+    if (!*end) {
+      *value = l;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+isUnsignedInteger (unsigned int *value, const char *string) {
+  if (*string) {
+    char *end;
+    unsigned long l = strtoul(string, &end, 0);
+
+    if (!*end) {
+      *value = l;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+isLogLevel (unsigned int *level, const char *string) {
+  {
+    size_t length = strlen(string);
+
+    for (unsigned int index=0; index<logLevelCount; index+=1) {
+      if (strncasecmp(string, logLevelNames[index], length) == 0) {
+        *level = index;
+        return 1;
+      }
+    }
+  }
+
+  {
+    unsigned int value;
+
+    if (isUnsignedInteger(&value, string) && (value < logLevelCount)) {
+      *level = value;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+isAbbreviation (const char *actual, const char *supplied) {
+  return strncasecmp(actual, supplied, strlen(supplied)) == 0;
+}
+
+int
+isAbbreviatedPhrase (const char *actual, const char *supplied) {
+  while (1) {
+    if (!*supplied) return 1;
+
+    if (*supplied == '-') {
+      while (*actual != '-') {
+        if (!*actual) return 0;
+        actual += 1;
+      }
+    } else if (tolower(*supplied) != tolower(*actual)) {
+      return 0;
+    }
+
+    actual += 1;
+    supplied += 1;
+  }
+}
+
+int
+validateInteger (int *value, const char *string, const int *minimum, const int *maximum) {
+  if (*string) {
+    int i;
+
+    if (!isInteger(&i, string)) return 0;
+    if (minimum && (i < *minimum)) return 0;
+    if (maximum && (i > *maximum)) return 0;
+
+    *value = i;
+  }
+
+  return 1;
+}
+
+int
+validateChoiceEx (unsigned int *value, const char *string, const void *choices, size_t size) {
+  *value = 0;
+  if (!*string) return 1;
+  const void *choice = choices;
+
+  while (1) {
+    typedef struct {
+      const char *name;
+    } Entry;
+
+    const Entry *entry = choice;
+    const char *name = entry->name;
+    if (!name) break;
+
+    if (isAbbreviatedPhrase(name, string)) {
+      *value = (choice - choices) / size;
+      return 1;
+    }
+
+    choice += size;
+  }
+
+  return 0;
+}
+
+int
+validateChoice (unsigned int *value, const char *string, const char *const *choices) {
+  return validateChoiceEx(value, string, choices, sizeof(*choices));
+}
+
+FlagKeywordPair fkpOnOff     = {.on="on"  , .off="off"  };
+FlagKeywordPair fkpTrueFalse = {.on="true", .off="false"};
+FlagKeywordPair fkpYesNo     = {.on="yes" , .off="no"   };
+FlagKeywordPair fkp10        = {.on="1"   , .off="0"    };
+
+const FlagKeywordPair *const flagKeywordPairs[] = {
+  &fkpOnOff, &fkpTrueFalse, &fkpYesNo, &fkp10
+};
+
+int
+validateFlagKeyword (unsigned int *value, const char *string) {
+  static const char **choices = NULL;
+
+  if (!choices) {
+    unsigned int count = ARRAY_COUNT(flagKeywordPairs);
+    size_t size = ARRAY_SIZE(choices, ((count * 2) + 1));
+
+    if (!(choices = malloc(size))) {
+      logMallocError();
+      return 0;
+    }
+
+    const FlagKeywordPair *const *fkp = flagKeywordPairs;
+    const FlagKeywordPair *const *end = fkp + count;
+    const char **choice = choices;
+
+    while (fkp < end) {
+      *choice++ = (*fkp)->off;
+      *choice++ = (*fkp)->on;
+      fkp += 1;
+    }
+
+    *choice = NULL;
+  }
+
+  if (!validateChoice(value, string, choices)) return 0;
+  *value %= 2;
+  return 1;
+}
+
+int
+validateFlag (unsigned int *value, const char *string, const FlagKeywordPair *fkp) {
+  const char *choices[] = {fkp->off, fkp->on, NULL};
+  return validateChoice(value, string, choices);
+}
+
+int
+validateOnOff (unsigned int *value, const char *string) {
+  return validateFlag(value, string, &fkpOnOff);
+}
+
+int
+validateYesNo (unsigned int *value, const char *string) {
+  return validateFlag(value, string, &fkpYesNo);
+}
+
+#ifndef NO_FLOAT
+int
+isFloat (float *value, const char *string) {
+  if (*string) {
+    char *end;
+    double d = strtod(string, &end);
+
+    if (!*end) {
+      *value = d;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+validateFloat (float *value, const char *string, const float *minimum, const float *maximum) {
+  if (*string) {
+    float f;
+
+    if (!isFloat(&f, string)) return 0;
+    if (minimum && (f < *minimum)) return 0;
+    if (maximum && (f > *maximum)) return 0;
+
+    *value = f;
+  }
+
+  return 1;
+}
+#endif /* NO_FLOAT */
+
+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+#include <pwd.h>
+#include <grp.h>
+
+int
+validateUser (uid_t *value, const char *string, gid_t *group) {
+  {
+    int integer = geteuid();
+    static const int minimum = 0;
+
+    if (validateInteger(&integer, string, &minimum, NULL)) {
+      *value = integer;
+
+      if (group) {
+        struct passwd *user = getpwuid(*value);
+        *group = user? user->pw_gid: 0;
+      }
+
+      return 1;
+    }
+  }
+
+  {
+    struct passwd *user = getpwnam(string);
+
+    if (user) {
+      *value = user->pw_uid;
+      if (group) *group = user->pw_gid;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+validateGroup (gid_t *value, const char *string) {
+  {
+    int integer = geteuid();
+    static const int minimum = 0;
+
+    if (validateInteger(&integer, string, &minimum, NULL)) {
+      *value = integer;
+      return 1;
+    }
+  }
+
+  {
+    struct group *group = getgrnam(string);
+
+    if (group) {
+      *value = group->gr_gid;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+#endif /* defined(HAVE_PWD_H) && defined(HAVE_GRP_H) */
+
+int
+hasQualifier (const char **identifier, const char *qualifier) {
+  const char *delimiter = strchr(*identifier, PARAMETER_QUALIFIER_CHARACTER);
+  if (!delimiter) return 0;
+
+  size_t count = delimiter - *identifier;
+  if (memchr(*identifier, PATH_SEPARATOR_CHARACTER, count)) return 0;
+
+  if (qualifier) {
+    if (count != strlen(qualifier)) return 0;
+    if (strncasecmp(*identifier, qualifier, count) != 0) return 0;
+  }
+
+  *identifier += count + 1;
+  return 1;
+}
+
+int
+hasNoQualifier (const char *identifier) {
+  return !hasQualifier(&identifier, NULL);
+}
+
+static int
+parseParameters (
+  char **values,
+  const char *const *names,
+  const char *qualifier,
+  const char *parameters
+) {
+  if (parameters && *parameters) {
+    const char *parameter = parameters;
+
+    while (1) {
+      const char *parameterEnd = strchr(parameter, PARAMETER_SEPARATOR_CHARACTER);
+      int done = !parameterEnd;
+
+      if (done) parameterEnd = parameter + strlen(parameter);
+      int parameterLength = parameterEnd - parameter;
+
+      if (parameterLength > 0) {
+        const char *value = memchr(parameter, PARAMETER_ASSIGNMENT_CHARACTER, parameterLength);
+
+        if (!value) {
+          logMessage(LOG_WARNING, "%s: %.*s",
+                     gettext("missing parameter value"),
+                     parameterLength, parameter);
+          goto NEXT_PARAMETER;
+        }
+
+        {
+          const char *name = parameter;
+          size_t nameLength = value++ - name;
+          size_t valueLength = parameterEnd - value;
+          int isEligible = 1;
+
+          if (qualifier) {
+            const char *delimiter = memchr(name, PARAMETER_QUALIFIER_CHARACTER, nameLength);
+
+            if (delimiter) {
+              size_t qualifierLength = delimiter - name;
+              size_t nameAdjustment = qualifierLength + 1;
+
+              name += nameAdjustment;
+              nameLength -= nameAdjustment;
+              isEligible = 0;
+
+              if (!qualifierLength) {
+                logMessage(LOG_WARNING, "%s: %.*s",
+                           gettext("missing parameter qualifier"),
+                           parameterLength, parameter);
+                goto NEXT_PARAMETER;
+              }
+
+              if ((qualifierLength == strlen(qualifier)) &&
+                  (memcmp(parameter, qualifier, qualifierLength) == 0)) {
+                isEligible = 1;
+              }
+            }
+          }
+
+          if (!nameLength) {
+            logMessage(LOG_WARNING, "%s: %.*s",
+                       gettext("missing parameter name"),
+                       parameterLength, parameter);
+            goto NEXT_PARAMETER;
+          }
+
+          if (isEligible) {
+            unsigned int index = 0;
+
+            while (names[index]) {
+              if (strncasecmp(name, names[index], nameLength) == 0) {
+                char *newValue = malloc(valueLength + 1);
+
+                if (!newValue) {
+                  logMallocError();
+                  return 0;
+                }
+
+                memcpy(newValue, value, valueLength);
+                newValue[valueLength] = 0;
+
+                free(values[index]);
+                values[index] = newValue;
+                goto NEXT_PARAMETER;
+              }
+
+              index += 1;
+            }
+
+            logMessage(LOG_WARNING, "%s: %.*s",
+                       gettext("unsupported parameter"),
+                       parameterLength, parameter);
+            goto NEXT_PARAMETER;
+          }
+        }
+      }
+
+    NEXT_PARAMETER:
+      if (done) break;
+      parameter = parameterEnd + 1;
+    }
+  }
+
+  return 1;
+}
+
+char **
+getParameters (const char *const *names, const char *qualifier, const char *parameters) {
+  if (!names) {
+    static const char *const noNames[] = {NULL};
+    names = noNames;
+  }
+
+  {
+    char **values;
+    unsigned int count = 0;
+    while (names[count]) count += 1;
+
+    if ((values = malloc((count + 1) * sizeof(*values)))) {
+      unsigned int index = 0;
+
+      while (index < count) {
+        if (!(values[index] = strdup(""))) {
+          logMallocError();
+          break;
+        }
+
+        index += 1;
+      }
+
+      if (index == count) {
+        values[index] = NULL;
+        if (parseParameters(values, names, qualifier, parameters)) return values;
+      }
+
+      deallocateStrings(values);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+void
+logParameters (const char *const *names, char **values, const char *description) {
+  if (names && values) {
+    while (*names) {
+      logMessage(LOG_INFO, "%s: %s=%s", description, *names, *values);
+      ++names;
+      ++values;
+    }
+  }
+}
diff --git a/Programs/pcm.c b/Programs/pcm.c
new file mode 100644
index 0000000..79ed4cc
--- /dev/null
+++ b/Programs/pcm.c
@@ -0,0 +1,189 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "pcm.h"
+
+#define UNSIGNED_TO_SIGNED amplitude += INT16_MIN
+
+static inline PcmSampleSize
+makePcmSample_8 (PcmSample *sample, int16_t amplitude) {
+  sample->bytes[0] = amplitude >> 8;
+  return 1;
+}
+
+static PcmSampleSize
+makePcmSample_S8 (PcmSample *sample, int16_t amplitude) {
+  return makePcmSample_8(sample, amplitude);
+}
+
+static PcmSampleSize
+makePcmSample_U8 (PcmSample *sample, int16_t amplitude) {
+  UNSIGNED_TO_SIGNED;
+  return makePcmSample_8(sample, amplitude);
+}
+
+static inline PcmSampleSize
+makePcmSample_16N (PcmSample *sample, int16_t amplitude) {
+  union {
+    unsigned char *bytes;
+    int16_t *s16;
+  } overlay;
+
+  overlay.bytes = sample->bytes;
+  *overlay.s16 = amplitude;
+  return 2;
+}
+
+static PcmSampleSize
+makePcmSample_S16N (PcmSample *sample, int16_t amplitude) {
+  return makePcmSample_16N(sample, amplitude);
+}
+
+static PcmSampleSize
+makePcmSample_U16N (PcmSample *sample, int16_t amplitude) {
+  UNSIGNED_TO_SIGNED;
+  return makePcmSample_16N(sample, amplitude);
+}
+
+#ifdef WORDS_BIGENDIAN
+#define makePcmSample_S16B makePcmSample_S16N
+#define makePcmSample_U16B makePcmSample_U16N
+#else /* WORDS_BIGENDIAN */
+static inline PcmSampleSize
+makePcmSample_16B (PcmSample *sample, int16_t amplitude) {
+  sample->bytes[0] = amplitude >> 8;
+  sample->bytes[1] = amplitude;
+  return 2;
+}
+
+static PcmSampleSize
+makePcmSample_S16B (PcmSample *sample, int16_t amplitude) {
+  return makePcmSample_16B(sample, amplitude);
+}
+
+static PcmSampleSize
+makePcmSample_U16B (PcmSample *sample, int16_t amplitude) {
+  UNSIGNED_TO_SIGNED;
+  return makePcmSample_16B(sample, amplitude);
+}
+#endif /* WORDS_BIGENDIAN */
+
+#ifndef WORDS_BIGENDIAN
+#define makePcmSample_S16L makePcmSample_S16N
+#define makePcmSample_U16L makePcmSample_U16N
+#else /* WORDS_BIGENDIAN */
+static inline PcmSampleSize
+makePcmSample_16L (PcmSample *sample, int16_t amplitude) {
+  sample->bytes[0] = amplitude;
+  sample->bytes[1] = amplitude >> 8;
+  return 2;
+}
+
+static PcmSampleSize
+makePcmSample_S16L (PcmSample *sample, int16_t amplitude) {
+  return makePcmSample_16L(sample, amplitude);
+}
+
+static PcmSampleSize
+makePcmSample_U16L (PcmSample *sample, int16_t amplitude) {
+  UNSIGNED_TO_SIGNED;
+  return makePcmSample_16L(sample, amplitude);
+}
+#endif /* WORDS_BIGENDIAN */
+
+static PcmSampleSize
+makePcmSample_ULAW (PcmSample *sample, int16_t amplitude) {
+  int negative = amplitude < 0;
+  int exponent = 0X7;
+  unsigned char value;
+  const unsigned int bias = 0X84;
+  const unsigned int clip = 0X7FFF - bias;
+
+  if (negative) amplitude = -amplitude;
+  if (amplitude > clip) amplitude = clip;
+  amplitude += bias;
+
+  while ((exponent > 0) && !(amplitude & 0X4000)) {
+    amplitude <<= 1;
+    --exponent;
+  }
+
+  value = (exponent << 4) | ((amplitude >> 10) & 0X0F);
+  if (negative) value |= 0X80;
+
+  sample->bytes[0] = ~value;
+  return 1;
+}
+
+static PcmSampleSize
+makePcmSample_ALAW (PcmSample *sample, int16_t amplitude) {
+  int negative = amplitude < 0;
+  int exponent = 0X7;
+  unsigned char value;
+
+  if (negative) amplitude = -amplitude;
+
+  while ((exponent > 0) && !(amplitude & 0X4000)) {
+    amplitude <<= 1;
+    --exponent;
+  }
+
+  if (!exponent) amplitude >>= 1;
+  value = (exponent << 4) | ((amplitude >> 10) & 0X0F);
+  if (negative) value |= 0X80;
+
+  sample->bytes[0] = value ^ 0X55;
+  return 1;
+}
+
+static PcmSampleSize
+makePcmSample_UNKNOWN (PcmSample *sample, int16_t amplitude) {
+  return 0;
+}
+
+PcmSampleMaker
+getPcmSampleMaker (PcmAmplitudeFormat format) {
+#define PCM_SAMPLE_MAKER_ENTRY(format) [PCM_FMT_##format] = makePcmSample_##format
+
+  static PcmSampleMaker const pcmSampleMakers[] = {
+    PCM_SAMPLE_MAKER_ENTRY(S8),
+    PCM_SAMPLE_MAKER_ENTRY(U8),
+
+    PCM_SAMPLE_MAKER_ENTRY(S16B),
+    PCM_SAMPLE_MAKER_ENTRY(U16B),
+
+    PCM_SAMPLE_MAKER_ENTRY(S16L),
+    PCM_SAMPLE_MAKER_ENTRY(U16L),
+
+    PCM_SAMPLE_MAKER_ENTRY(ULAW),
+    PCM_SAMPLE_MAKER_ENTRY(ALAW),
+
+    PCM_SAMPLE_MAKER_ENTRY(UNKNOWN)
+  };
+
+  if (format < ARRAY_COUNT(pcmSampleMakers)) {
+    PcmSampleMaker sampleMaker = pcmSampleMakers[format];
+    if (sampleMaker) return sampleMaker;
+  }
+
+  logMessage(LOG_WARNING, "unsupported PCM format: %d", format);
+  return pcmSampleMakers[PCM_FMT_UNKNOWN];
+}
diff --git a/Programs/pcm_alsa.c b/Programs/pcm_alsa.c
new file mode 100644
index 0000000..db71c38
--- /dev/null
+++ b/Programs/pcm_alsa.c
@@ -0,0 +1,351 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#include <alsa/asoundlib.h>
+
+#include "log.h"
+#include "timing.h"
+#include "pcm.h"
+
+struct PcmDeviceStruct {
+  snd_pcm_t *handle;
+  snd_pcm_hw_params_t *hardwareParameters;
+  unsigned int channelCount;
+  unsigned int sampleRate;
+  unsigned int bufferTime;
+  unsigned int periodTime;
+};
+
+static void
+logPcmError (int level, const char *action, int code) {
+  logMessage(level, "ALSA PCM %s error: %s", action, snd_strerror(code));
+}
+
+static int
+configurePcmSampleFormat (PcmDevice *pcm, int errorLevel) {
+  static const snd_pcm_format_t formats[] = {
+    SND_PCM_FORMAT_S16, SND_PCM_FORMAT_U16,
+    SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S8,
+    SND_PCM_FORMAT_MU_LAW,
+    SND_PCM_FORMAT_UNKNOWN
+  };
+  const snd_pcm_format_t *format = formats;
+
+  while (*format != SND_PCM_FORMAT_UNKNOWN) {
+    int result = snd_pcm_hw_params_set_format(pcm->handle, pcm->hardwareParameters, *format);
+    if (result >= 0) return 1;
+
+    if (result != -EINVAL) {
+      logPcmError(errorLevel, "set format", result);
+      return 0;
+    }
+
+    ++format;
+  }
+
+  logMessage(errorLevel, "Unsupported PCM sample format.");
+  return 0;
+}
+
+static int
+configurePcmSampleRate (PcmDevice *pcm, int errorLevel) {
+  int result;
+  unsigned int minimum;
+  unsigned int maximum;
+
+  if ((result = snd_pcm_hw_params_get_rate_min(pcm->hardwareParameters, &minimum, NULL)) < 0) {
+    logPcmError(errorLevel, "get rate min", result);
+    return 0;
+  }
+
+  if ((result = snd_pcm_hw_params_get_rate_max(pcm->hardwareParameters, &maximum, NULL)) < 0) {
+    logPcmError(errorLevel, "get rate max", result);
+    return 0;
+  }
+
+  if ((minimum > maximum) || (minimum < 1)) {
+    logMessage(errorLevel, "Invalid PCM rate range: %u-%u", minimum, maximum);
+    return 0;
+  }
+
+  pcm->sampleRate = MIN(MAX(16000, minimum), maximum);
+  if ((result = snd_pcm_hw_params_set_rate_near(pcm->handle, pcm->hardwareParameters, &pcm->sampleRate, NULL)) < 0) {
+    logPcmError(errorLevel, "set rate near", result);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+configurePcmChannelCount (PcmDevice *pcm, int errorLevel) {
+  int result;
+  unsigned int minimum;
+  unsigned int maximum;
+
+  if ((result = snd_pcm_hw_params_get_channels_min(pcm->hardwareParameters, &minimum)) < 0) {
+    logPcmError(errorLevel, "get channels min", result);
+    return 0;
+  }
+
+  if ((result = snd_pcm_hw_params_get_channels_max(pcm->hardwareParameters, &maximum)) < 0) {
+    logPcmError(errorLevel, "get channels max", result);
+    return 0;
+  }
+
+  if ((minimum > maximum) || (minimum < 1)) {
+    logMessage(errorLevel, "Invalid PCM channel range: %u-%u", minimum, maximum);
+    return 0;
+  }
+
+  pcm->channelCount = minimum;
+  if ((result = snd_pcm_hw_params_set_channels_near(pcm->handle, pcm->hardwareParameters, &pcm->channelCount)) < 0) {
+    logPcmError(errorLevel, "set channels near", result);
+    return 0;
+  }
+
+  return 1;
+}
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm;
+
+  if ((pcm = malloc(sizeof(*pcm)))) {
+    int result;
+
+    if (!*device) device = "default";
+    if ((result = snd_pcm_open(&pcm->handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0) {
+      snd_pcm_nonblock(pcm->handle, 0);
+
+      if ((result = snd_pcm_hw_params_malloc(&pcm->hardwareParameters)) >= 0) {
+        if ((result = snd_pcm_hw_params_any(pcm->handle, pcm->hardwareParameters)) >= 0) {
+          if ((result = snd_pcm_hw_params_set_access(pcm->handle, pcm->hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED)) >= 0) {
+            if (configurePcmSampleFormat(pcm, errorLevel)) {
+              if (configurePcmSampleRate(pcm, errorLevel)) {
+                if (configurePcmChannelCount(pcm, errorLevel)) {
+                  pcm->bufferTime = 500000;
+                  if ((result = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pcm->hardwareParameters, &pcm->bufferTime, NULL)) >= 0) {
+                    pcm->periodTime = pcm->bufferTime / 8;
+                    if ((result = snd_pcm_hw_params_set_period_time_near(pcm->handle, pcm->hardwareParameters, &pcm->periodTime, NULL)) >= 0) {
+                      if ((result = snd_pcm_hw_params(pcm->handle, pcm->hardwareParameters)) >= 0) {
+                        logMessage(LOG_DEBUG, "ALSA PCM: Chan=%u Rate=%u BufTim=%u PerTim=%u", pcm->channelCount, pcm->sampleRate, pcm->bufferTime, pcm->periodTime);
+                        return pcm;
+                      } else {
+                        logPcmError(errorLevel, "set hardware parameters", result);
+                      }
+                    } else {
+                      logPcmError(errorLevel, "set period time near", result);
+                    }
+                  } else {
+                    logPcmError(errorLevel, "set buffer time near", result);
+                  }
+                }
+              }
+            }
+          } else {
+            logPcmError(errorLevel, "set access", result);
+          }
+        } else {
+          logPcmError(errorLevel, "get hardware parameters", result);
+        }
+
+        snd_pcm_hw_params_free(pcm->hardwareParameters);
+      } else {
+        logPcmError(errorLevel, "hardware parameters allocation", result);
+      }
+
+      snd_pcm_close(pcm->handle);
+    } else {
+      logPcmError(errorLevel, "open", result);
+    }
+
+    free(pcm);
+  } else {
+    logSystemError("PCM device allocation");
+  }
+
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  awaitPcmOutput(pcm);
+  snd_pcm_close(pcm->handle);
+  snd_pcm_hw_params_free(pcm->hardwareParameters);
+  free(pcm);
+}
+
+static int
+getPcmFrameSize (PcmDevice *pcm) {
+  return getPcmChannelCount(pcm) * (snd_pcm_hw_params_get_sbits(pcm->hardwareParameters) / 8);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  int frameSize = getPcmFrameSize(pcm);
+  int framesLeft = count / frameSize;
+
+  while (framesLeft > 0) {
+    int result;
+
+    if ((result = snd_pcm_writei(pcm->handle, buffer, framesLeft)) > 0) {
+      framesLeft -= result;
+      buffer += result * frameSize;
+    } else {
+      switch (result) {
+        case -EPIPE:
+          if ((result = snd_pcm_prepare(pcm->handle)) < 0) {
+            logPcmError(LOG_WARNING, "underrun recovery - prepare", result);
+            return 0;
+          }
+          continue;
+
+#if ESTRPIPE != EPIPE
+        case -ESTRPIPE:
+          while ((result = snd_pcm_resume(pcm->handle)) == -EAGAIN) approximateDelay(1);
+
+          if (result < 0) {
+            if ((result = snd_pcm_prepare(pcm->handle)) < 0) {
+              logPcmError(LOG_WARNING, "resume - prepare", result);
+              return 0;
+            }
+          }
+          continue;
+#endif /* ESTRPIPE != EPIPE */
+      }
+    }
+  }
+  return 1;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  snd_pcm_uframes_t frames;
+  int result;
+
+  if ((result = snd_pcm_hw_params_get_period_size(pcm->hardwareParameters, &frames, NULL)) >= 0) {
+    return frames * getPcmFrameSize(pcm);
+  } else {
+    logPcmError(LOG_ERR, "get period size", result);
+  }
+  return 65535;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  return pcm->sampleRate;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  int result;
+
+  pcm->sampleRate = rate;
+  if ((result = snd_pcm_hw_params_set_rate_near(pcm->handle, pcm->hardwareParameters, &pcm->sampleRate, NULL)) < 0) {
+    logPcmError(LOG_ERR, "set rate near", result);
+  }
+
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  return pcm->channelCount;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  int result;
+
+  pcm->channelCount = channels;
+  if ((result = snd_pcm_hw_params_set_channels_near(pcm->handle, pcm->hardwareParameters, &pcm->channelCount)) < 0) {
+    logPcmError(LOG_ERR, "set channels near", result);
+  }
+
+  return getPcmChannelCount(pcm);
+}
+
+typedef struct {
+  PcmAmplitudeFormat internal;
+  snd_pcm_format_t external;
+} AmplitudeFormatEntry;
+static const AmplitudeFormatEntry amplitudeFormatTable[] = {
+  {PCM_FMT_U8     , SND_PCM_FORMAT_U8     },
+  {PCM_FMT_S8     , SND_PCM_FORMAT_S8     },
+  {PCM_FMT_U16B   , SND_PCM_FORMAT_U16_BE },
+  {PCM_FMT_S16B   , SND_PCM_FORMAT_S16_BE },
+  {PCM_FMT_U16L   , SND_PCM_FORMAT_U16_LE },
+  {PCM_FMT_S16L   , SND_PCM_FORMAT_S16_LE },
+  {PCM_FMT_ULAW   , SND_PCM_FORMAT_MU_LAW },
+  {PCM_FMT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN}
+};
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  snd_pcm_format_t format;
+  int result;
+
+  if ((result = snd_pcm_hw_params_get_format(pcm->hardwareParameters, &format)) < 0) {
+    logPcmError(LOG_ERR, "get format", result);
+  } else {
+    const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+    while (entry->internal != PCM_FMT_UNKNOWN) {
+      if (entry->external == format) return entry->internal;
+      ++entry;
+    }
+  }
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+  int result;
+
+  while (entry->internal != PCM_FMT_UNKNOWN) {
+    if (entry->internal == format) break;
+    ++entry;
+  }
+
+  if ((result = snd_pcm_hw_params_set_format(pcm->handle, pcm->hardwareParameters, entry->external)) < 0) {
+    logPcmError(LOG_ERR, "set format", result);
+    return getPcmAmplitudeFormat(pcm);
+  }
+
+  return entry->internal;
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  int result;
+  if ((result = snd_pcm_drain(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drain", result);
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  int result;
+  if ((result = snd_pcm_drop(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drop", result);
+}
diff --git a/Programs/pcm_android.c b/Programs/pcm_android.c
new file mode 100644
index 0000000..f7536c9
--- /dev/null
+++ b/Programs/pcm_android.c
@@ -0,0 +1,284 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "pcm.h"
+#include "system_java.h"
+
+struct PcmDeviceStruct {
+  JNIEnv *env;
+  jobject device;
+};
+
+static jclass pcmDeviceClass = NULL;
+
+static int
+findPcmDeviceClass (JNIEnv *env) {
+  return findJavaClass(env, &pcmDeviceClass, JAVA_OBJ_BRLTTY("PcmDevice"));
+}
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm = malloc(sizeof(*pcm));
+
+  if (pcm) {
+    memset(pcm, 0, sizeof(*pcm));
+    pcm->env = getJavaNativeInterface();
+    pcm->device = NULL;
+
+    if (findPcmDeviceClass(pcm->env)) {
+      static jmethodID constructor = 0;
+
+      if (findJavaConstructor(pcm->env, &constructor, pcmDeviceClass,
+                              JAVA_SIG_CONSTRUCTOR())) {
+        jobject localReference = (*pcm->env)->NewObject(pcm->env, pcmDeviceClass, constructor);
+
+        if (!clearJavaException(pcm->env, 1)) {
+          jobject globalReference = (*pcm->env)->NewGlobalRef(pcm->env, localReference);
+
+          (*pcm->env)->DeleteLocalRef(pcm->env, localReference);
+          localReference = NULL;
+
+          if (globalReference) {
+            pcm->device = globalReference;
+            return pcm;
+          } else {
+            logMallocError();
+            clearJavaException(pcm->env, 0);
+          }
+        }
+      }
+    }
+
+    free(pcm);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  if (pcm) {
+    if (pcm->device) {
+      if (findPcmDeviceClass(pcm->env)) {
+        static jmethodID method = 0;
+
+        if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "close",
+                                   JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                                  ))) {
+          (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
+          clearJavaException(pcm->env, 1);
+        }
+      }
+
+      (*pcm->env)->DeleteGlobalRef(pcm->env, pcm->device);
+    }
+
+    free(pcm);
+  }
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "write",
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                               JAVA_SIG_ARRAY(JAVA_SIG_SHORT) // samples
+                                              ))) {
+      jint size = count / 2;
+      jshortArray jSamples = (*pcm->env)->NewShortArray(pcm->env, size);
+
+      if (jSamples) {
+        typedef union {
+          const unsigned char *bytes;
+          const int16_t *actual;
+        } Samples;
+
+        Samples samples = {
+          .bytes = buffer
+        };
+
+        (*pcm->env)->SetShortArrayRegion(
+          pcm->env, jSamples, 0, size, samples.actual
+        );
+
+        if (!clearJavaException(pcm->env, 1)) {
+          jboolean result = (*pcm->env)->CallBooleanMethod(
+            pcm->env, pcm->device, method, jSamples
+          );
+
+          (*pcm->env)->DeleteLocalRef(pcm->env, jSamples);
+          jSamples = NULL;
+
+          if (!clearJavaException(pcm->env, 1)) {
+            if (result == JNI_TRUE) {
+              return 1;
+            }
+          }
+        }
+      } else {
+        logMallocError();
+        clearJavaException(pcm->env, 0);
+      }
+    }
+  }
+
+  return 0;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getBufferSize",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                              ))) {
+      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
+      if (!clearJavaException(pcm->env, 1)) return result;
+    }
+  }
+
+  return 0X100;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getSampleRate",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                              ))) {
+      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
+      if (!clearJavaException(pcm->env, 1)) return result;
+    }
+  }
+
+  return 8000;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "setSampleRate",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                               JAVA_SIG_INT // rate
+                                              ))) {
+      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method, rate);
+      clearJavaException(pcm->env, 1);
+    }
+  }
+
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getChannelCount",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                              ))) {
+      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
+      if (!clearJavaException(pcm->env, 1)) return result;
+    }
+  }
+
+  return 1;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "setChannelCount",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                               JAVA_SIG_INT // count
+                                              ))) {
+      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method, channels);
+      clearJavaException(pcm->env, 1);
+    }
+  }
+
+  return getPcmChannelCount(pcm);
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  return PCM_FMT_S16N;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  return getPcmAmplitudeFormat(pcm);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "push",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                              ))) {
+      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
+      clearJavaException(pcm->env, 1);
+    }
+  }
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "await",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                              ))) {
+      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
+      clearJavaException(pcm->env, 1);
+    }
+  }
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  if (findPcmDeviceClass(pcm->env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "cancel",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
+                                              ))) {
+      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
+      clearJavaException(pcm->env, 1);
+    }
+  }
+}
diff --git a/Programs/pcm_audio.c b/Programs/pcm_audio.c
new file mode 100644
index 0000000..8dddedc
--- /dev/null
+++ b/Programs/pcm_audio.c
@@ -0,0 +1,184 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/audio.h>
+#include <stropts.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "pcm.h"
+
+#define PCM_AUDIO_DEVICE_PATH "/dev/audio"
+
+struct PcmDeviceStruct {
+  int fileDescriptor;
+};
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm;
+  if ((pcm = malloc(sizeof(*pcm)))) {
+    if (!*device) device = getenv("AUDIODEV");
+    if (!device || !*device) device = PCM_AUDIO_DEVICE_PATH;
+    if ((pcm->fileDescriptor = open(device, O_WRONLY|O_NONBLOCK)) != -1) {
+      audio_info_t info;
+      AUDIO_INITINFO(&info);
+#ifdef AUMODE_PLAY
+      info.mode = AUMODE_PLAY;
+#endif /* AUMODE_PLAY */
+#ifdef AUDIO_ENCODING_SLINEAR
+      info.play.encoding = AUDIO_ENCODING_SLINEAR;
+#else /* AUDIO_ENCODING_SLINEAR */
+      info.play.encoding = AUDIO_ENCODING_LINEAR;
+#endif /* AUDIO_ENCODING_SLINEAR */
+      info.play.sample_rate = 16000;
+      info.play.channels = 1;
+      info.play.precision = 16;
+      info.play.gain = AUDIO_MAX_GAIN;
+      if (ioctl(pcm->fileDescriptor, AUDIO_SETINFO, &info) == -1)
+        logMessage(errorLevel, "Cannot set audio info: %s", strerror(errno));
+      return pcm;
+    } else {
+      logMessage(errorLevel, "Cannot open PCM device: %s: %s", device, strerror(errno));
+    }
+    free(pcm);
+  } else {
+    logSystemError("PCM device allocation");
+  }
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  close(pcm->fileDescriptor);
+  free(pcm);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  return writeFile(pcm->fileDescriptor, buffer, count) != -1;
+}
+
+static int
+getPcmAudioInfo (PcmDevice *pcm, audio_info_t *info) {
+  if (ioctl(pcm->fileDescriptor, AUDIO_GETINFO, info) != -1) return 1;
+  logSystemError("AUDIO_GETINFO");
+  return 0;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  audio_info_t info;
+  if (getPcmAudioInfo(pcm, &info)) return (info.play.precision / 8 * info.play.channels) * 0X400;
+  return 0X100;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  audio_info_t info;
+  if (getPcmAudioInfo(pcm, &info)) return info.play.sample_rate;
+  return 8000;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  audio_info_t info;
+  if (getPcmAudioInfo(pcm, &info)) return info.play.channels;
+  return 1;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  return getPcmChannelCount(pcm);
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  audio_info_t info;
+  if (getPcmAudioInfo(pcm, &info)) {
+    switch (info.play.encoding) {
+      default:
+        break;
+
+#ifdef AUDIO_ENCODING_SLINEAR_BE
+      case AUDIO_ENCODING_SLINEAR_BE:
+        if (info.play.precision == 16) return PCM_FMT_S16B;
+        goto testLinearSigned8;
+#endif /* AUDIO_ENCODING_SLINEAR_BE */
+
+#ifdef AUDIO_ENCODING_SLINEAR_LE
+      case AUDIO_ENCODING_SLINEAR_LE:
+        if (info.play.precision == 16) return PCM_FMT_S16L;
+        goto testLinearSigned8;
+#endif /* AUDIO_ENCODING_SLINEAR_LE */
+
+#ifdef AUDIO_ENCODING_LINEAR
+      case AUDIO_ENCODING_LINEAR:
+#ifdef WORDS_BIGENDIAN
+        if (info.play.precision == 16) return PCM_FMT_S16B;
+#else /* WORDS_BIGENDIAN */
+        if (info.play.precision == 16) return PCM_FMT_S16L;
+#endif /* WORDS_BIGENDIAN */
+        goto testLinearSigned8;
+#endif /* AUDIO_ENCODING_LINEAR */
+
+      testLinearSigned8:
+        if (info.play.precision == 8) return PCM_FMT_S8;
+        break;
+
+      case AUDIO_ENCODING_LINEAR8:
+        return PCM_FMT_U8;
+
+      case AUDIO_ENCODING_ULAW:
+        return PCM_FMT_ULAW;
+
+      case AUDIO_ENCODING_ALAW:
+        return PCM_FMT_ALAW;
+    }
+  }
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  return getPcmAmplitudeFormat(pcm);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  ioctl(pcm->fileDescriptor, AUDIO_DRAIN);
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  ioctl(pcm->fileDescriptor, I_FLUSH);
+}
diff --git a/Programs/pcm_hpux.c b/Programs/pcm_hpux.c
new file mode 100644
index 0000000..0940388
--- /dev/null
+++ b/Programs/pcm_hpux.c
@@ -0,0 +1,192 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#ifdef HAVE_HPUX_AUDIO
+#include <Alib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif /* HAVE_HPUX_AUDIO */
+
+#include "log.h"
+#include "pcm.h"
+
+#ifdef HAVE_HPUX_AUDIO
+static Audio *audioServer = NULL;
+
+struct PcmDeviceStruct {
+  ATransID transaction;
+  SStream stream;
+  int socket;
+};
+
+static void
+logAudioError (int level, long status, const char *action) {
+  char message[132];
+  AGetErrorText(audioServer, status, message, sizeof(message)-1);
+  logMessage(level, "%s error %ld: %s", action, status, message);
+}
+
+static const AudioAttributes *
+getAudioAttributes (PcmDevice *pcm) {
+  return &pcm->stream.audio_attr;
+}
+#endif /* HAVE_HPUX_AUDIO */
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+#ifdef HAVE_HPUX_AUDIO
+  PcmDevice *pcm;
+  if ((pcm = malloc(sizeof(*pcm)))) {
+    long status;
+    AudioAttrMask mask = 0;
+    AudioAttributes attributes;
+    SSPlayParams parameters;
+  
+    if (!audioServer) {
+      char *server = "";
+      audioServer = AOpenAudio(server, &status);
+      if (status != AENoError) {
+        logAudioError(errorLevel, status, "AOpenAudio");
+        audioServer = NULL;
+        goto noServer;
+      }
+      logMessage(LOG_DEBUG, "connected to audio server: %s", AAudioString(audioServer));
+  
+      ASetCloseDownMode(audioServer, AKeepTransactions, &status);
+      if (status != AENoError) {
+        logAudioError(errorLevel, status, "ASetCloseDownMode");
+      }
+    }
+  
+    memset(&attributes, 0, sizeof(attributes));
+  
+    parameters.gain_matrix = *ASimplePlayer(audioServer);
+    parameters.play_volume = AUnityGain;
+    parameters.priority = APriorityUrgent;
+    parameters.event_mask = 0;
+  
+    pcm->transaction = APlaySStream(audioServer, mask, &attributes, &parameters, &pcm->stream, &status);
+    if (status == AENoError) {
+      if ((pcm->socket = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
+        if (connect(pcm->socket, (struct sockaddr *)&pcm->stream.tcp_sockaddr, sizeof(pcm->stream.tcp_sockaddr)) != -1) {
+          return pcm;
+        } else {
+          logSystemError("PCM socket connection");
+        }
+        close(pcm->socket);
+      } else {
+        logSystemError("PCM socket creation");
+      }
+    } else {
+      logAudioError(errorLevel, status, "APlaySStream");
+    }
+
+  noServer:
+    free(pcm);
+  } else {
+    logSystemError("PCM device allocation");
+  }
+#endif /* HAVE_HPUX_AUDIO */
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  close(pcm->socket);
+  free(pcm);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  return safe_write(pcm->socket, buffer, count) != -1;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  int size = 0X100;
+#ifdef HAVE_HPUX_AUDIO
+  size = MIN(size, pcm->stream.max_block_size);
+#endif /* HAVE_HPUX_AUDIO */
+  return size;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+#ifdef HAVE_HPUX_AUDIO
+  return getAudioAttributes(pcm)->attr.sampled_attr.sampling_rate;
+#else /* HAVE_HPUX_AUDIO */
+  return 8000;
+#endif /* HAVE_HPUX_AUDIO */
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+#ifdef HAVE_HPUX_AUDIO
+  return getAudioAttributes(pcm)->attr.sampled_attr.channels;
+#else /* HAVE_HPUX_AUDIO */
+  return 1;
+#endif /* HAVE_HPUX_AUDIO */
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  return getPcmChannelCount(pcm);
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+#ifdef HAVE_HPUX_AUDIO
+  switch (getAudioAttributes(pcm)->attr.sampled_attr.data_format) {
+    default:
+      break;
+    case ADFLin8:
+      return PCM_FMT_S8;
+    case ADFLin8Offset:
+      return PCM_FMT_U8;
+    case ADFLin16:
+      return PCM_FMT_S16B;
+    case ADFMuLaw:
+      return PCM_FMT_ULAW;
+  }
+#endif /* HAVE_HPUX_AUDIO */
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  return getPcmAmplitudeFormat(pcm);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+}
diff --git a/Programs/pcm_none.c b/Programs/pcm_none.c
new file mode 100644
index 0000000..be4156e
--- /dev/null
+++ b/Programs/pcm_none.c
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "pcm.h"
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  logMessage(errorLevel, "PCM device not supported.");
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  return 0;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  return 0X100;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  return 8000;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  return 1;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  return getPcmChannelCount(pcm);
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  return getPcmAmplitudeFormat(pcm);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+}
diff --git a/Programs/pcm_oss.c b/Programs/pcm_oss.c
new file mode 100644
index 0000000..b9eed12
--- /dev/null
+++ b/Programs/pcm_oss.c
@@ -0,0 +1,186 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "pcm.h"
+
+#define PCM_OSS_DEVICE_PATH "/dev/dsp"
+
+#ifndef SNDCTL_DSP_SPEED
+#define SNDCTL_DSP_SPEED SOUND_PCM_WRITE_RATE
+#endif /* SNDCTL_DSP_SPEED */
+
+#ifndef SNDCTL_DSP_CHANNELS
+#define SNDCTL_DSP_CHANNELS SOUND_PCM_WRITE_CHANNELS
+#endif /* SNDCTL_DSP_CHANNELS */
+
+struct PcmDeviceStruct {
+  int fileDescriptor;
+  int driverVersion;
+  int sampleRate;
+  int channelCount;
+};
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm;
+  if ((pcm = malloc(sizeof(*pcm)))) {
+    if (!*device) device = PCM_OSS_DEVICE_PATH;
+    if ((pcm->fileDescriptor = open(device, O_WRONLY|O_NONBLOCK)) != -1) {
+      /* Nonblocking if snd_seq_oss is loaded with nonblock_open=1.
+       * There appears to be a bug in this case as write() always
+       * returns the full count even though large chunks of sound are
+       * missing. For now, therefore, force blocking output.
+       */
+      setBlockingIo(pcm->fileDescriptor, 1);
+
+      pcm->driverVersion = 0X030000;
+#ifdef OSS_GETVERSION
+      if (ioctl(pcm->fileDescriptor, OSS_GETVERSION, &pcm->driverVersion) == -1)
+        logMessage(errorLevel, "cannot get OSS driver version");
+#endif /* OSS_GETVERSION */
+      logMessage(LOG_DEBUG, "OPSS driver version: %06X", pcm->driverVersion);
+
+      setPcmSampleRate(pcm, 8000);
+      setPcmChannelCount(pcm, 1);
+      return pcm;
+    } else {
+      logMessage(errorLevel, "cannot open PCM device: %s: %s", device, strerror(errno));
+    }
+    free(pcm);
+  } else {
+    logSystemError("PCM device allocation");
+  }
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  close(pcm->fileDescriptor);
+  free(pcm);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  return writeFile(pcm->fileDescriptor, buffer, count) != -1;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  int fragmentCount = (1 << 0X10) - 1;
+  int fragmentShift = 7;
+  int fragmentSize = 1 << fragmentShift;
+  int fragmentSetting = (fragmentCount << 0X10) | fragmentShift;
+  ioctl(pcm->fileDescriptor, SNDCTL_DSP_SETFRAGMENT, &fragmentSetting);
+
+  {
+    int blockSize;
+    if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_GETBLKSIZE, &blockSize) != -1) return blockSize;
+  }
+  return fragmentSize;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  return pcm->sampleRate;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_SPEED, &rate) != -1) pcm->sampleRate = rate;
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  return pcm->channelCount;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_CHANNELS, &channels) != -1) pcm->channelCount = channels;
+  return getPcmChannelCount(pcm);
+}
+
+typedef struct {
+  PcmAmplitudeFormat internal;
+  int external;
+} AmplitudeFormatEntry;
+static const AmplitudeFormatEntry amplitudeFormatTable[] = {
+  {PCM_FMT_U8     , AFMT_U8    },
+  {PCM_FMT_S8     , AFMT_S8    },
+  {PCM_FMT_U16B   , AFMT_U16_BE},
+  {PCM_FMT_S16B   , AFMT_S16_BE},
+  {PCM_FMT_U16L   , AFMT_U16_LE},
+  {PCM_FMT_S16L   , AFMT_S16_LE},
+  {PCM_FMT_ULAW   , AFMT_MU_LAW},
+  {PCM_FMT_ALAW   , AFMT_A_LAW},
+  {PCM_FMT_UNKNOWN, AFMT_QUERY }
+};
+
+static PcmAmplitudeFormat
+doPcmAmplitudeFormat (PcmDevice *pcm, int format) {
+  if (ioctl(pcm->fileDescriptor, SNDCTL_DSP_SETFMT, &format) != -1) {
+    const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+    while (entry->internal != PCM_FMT_UNKNOWN) {
+      if (entry->external == format) return entry->internal;
+      ++entry;
+    }
+  }
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  return doPcmAmplitudeFormat(pcm, AFMT_QUERY);
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+  while (entry->internal != PCM_FMT_UNKNOWN) {
+    if (entry->internal == format) break;
+    ++entry;
+  }
+  return doPcmAmplitudeFormat(pcm, entry->external);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+  ioctl(pcm->fileDescriptor, SNDCTL_DSP_POST, 0);
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  ioctl(pcm->fileDescriptor, SNDCTL_DSP_SYNC, 0);
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  ioctl(pcm->fileDescriptor, SNDCTL_DSP_RESET, 0);
+}
diff --git a/Programs/pcm_qsa.c b/Programs/pcm_qsa.c
new file mode 100644
index 0000000..e8a6305
--- /dev/null
+++ b/Programs/pcm_qsa.c
@@ -0,0 +1,265 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <sys/asoundlib.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "pcm.h"
+
+struct PcmDeviceStruct {
+  int card;
+  int device;
+  snd_pcm_t *handle;
+  snd_pcm_channel_params_t parameters;
+};
+
+static void
+logPcmError (int level, const char *action, int code) {
+  logMessage(level, "QSA PCM %s error: %s", action, snd_strerror(code));
+}
+
+static int
+reconfigurePcmChannel (PcmDevice *pcm, int errorLevel) {
+  int code;
+  if ((code = snd_pcm_channel_params(pcm->handle, &pcm->parameters)) >= 0) {
+    snd_pcm_channel_setup_t setup;
+    setup.channel = pcm->parameters.channel;
+    if ((code = snd_pcm_channel_setup(pcm->handle, &setup)) >= 0) {
+      pcm->parameters.mode = setup.mode;
+      pcm->parameters.format = setup.format;
+      pcm->parameters.buf.block.frag_size = setup.buf.block.frag_size;
+      pcm->parameters.buf.block.frags_min = setup.buf.block.frags_min;
+      pcm->parameters.buf.block.frags_max = setup.buf.block.frags_max;
+      return 1;
+    } else {
+      logPcmError(errorLevel, "get channel setup", code);
+    }
+  } else {
+    logPcmError(errorLevel, "set channel parameters", code);
+  }
+  return 0;
+}
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm;
+  if ((pcm = malloc(sizeof(*pcm)))) {
+    int code;
+
+    if (*device) {
+      {
+	int ok = 0;
+	long number;
+	char *end;
+	const char *component = device;
+
+	number = strtol(component, &end, 0);
+	if ((*end && (*end != ':')) || (number < 0) || (number > 0XFF)) {
+	  logMessage(errorLevel, "Invalid QSA card number: %s", device);
+	} else if (end == component) {
+	  logMessage(errorLevel, "Missing QSA card number: %s", device);
+	} else {
+	  pcm->card = number;
+
+	  if (*end) {
+	    component = end + 1;
+	    number = strtol(component, &end, 0);
+	    if (*end || (number < 0) || (number > 0XFF)) {
+	      logMessage(errorLevel, "Invalid QSA device number: %s", device);
+	    } else if (end == component) {
+	      logMessage(errorLevel, "Missing QSA device number: %s", device);
+	    } else {
+	      pcm->device = number;
+	      ok = 1;
+	    }
+	  } else {
+	    pcm->device = 0;
+	    ok = 1;
+	  }
+	}
+
+	if (!ok) goto openError;
+      }
+
+      if ((code = snd_pcm_open(&pcm->handle, pcm->card, pcm->device, SND_PCM_OPEN_PLAYBACK)) < 0) {
+	logPcmError(errorLevel, "open", code);
+	goto openError;
+      }
+    } else if ((code = snd_pcm_open_preferred(&pcm->handle, &pcm->card, &pcm->device, SND_PCM_OPEN_PLAYBACK)) < 0) {
+      logPcmError(errorLevel, "preferred open", code);
+      goto openError;
+    }
+    logMessage(LOG_DEBUG, "QSA PCM device opened: %d:%d", pcm->card, pcm->device);
+
+    {
+      snd_pcm_channel_info_t info;
+      info.channel = SND_PCM_CHANNEL_PLAYBACK;
+      if ((code = snd_pcm_channel_info(pcm->handle, &info)) >= 0) {
+	logMessage(LOG_DEBUG, "QSA PCM Info: Frag=%d-%d Rate=%d-%d Chan=%d-%d",
+	           info.min_fragment_size, info.max_fragment_size,
+	           info.min_rate, info.max_rate,
+	           info.min_voices, info.max_voices);
+	memset(&pcm->parameters, 0, sizeof(pcm->parameters));
+
+	pcm->parameters.channel = info.channel;
+	pcm->parameters.start_mode = SND_PCM_START_DATA;
+	pcm->parameters.stop_mode = SND_PCM_STOP_ROLLOVER;
+
+	switch (pcm->parameters.mode = SND_PCM_MODE_BLOCK) {
+	  case SND_PCM_MODE_BLOCK:
+	    pcm->parameters.buf.block.frag_size = MIN(MAX(0X400, info.min_fragment_size), info.max_fragment_size);
+	    pcm->parameters.buf.block.frags_min = 1;
+	    pcm->parameters.buf.block.frags_max = 0X40;
+	    break;
+
+	  default:
+	    logMessage(LOG_WARNING, "Unsupported QSA PCM mode: %d", pcm->parameters.mode);
+	    goto openError;
+	}
+
+	pcm->parameters.format.interleave = 1;
+	pcm->parameters.format.rate = info.max_rate;
+	pcm->parameters.format.voices = MIN(MAX(1, info.min_voices), info.max_voices);
+	pcm->parameters.format.format = SND_PCM_SFMT_S16;
+
+	if (reconfigurePcmChannel(pcm, errorLevel)) {
+	  if ((code = snd_pcm_channel_prepare(pcm->handle, pcm->parameters.channel)) >= 0) {
+	    return pcm;
+	  } else {
+	    logPcmError(errorLevel, "prepare channel", code);
+	  }
+	}
+      } else {
+        logPcmError(errorLevel, "get channel information", code);
+      }
+    }
+
+  openError:
+    free(pcm);
+  } else {
+    logSystemError("PCM device allocation");
+  }
+
+  return NULL;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  int code;
+  if ((code = snd_pcm_close(pcm->handle)) < 0) {
+    logPcmError(LOG_WARNING, "close", code);
+  }
+
+  free(pcm);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  return writeFile(snd_pcm_file_descriptor(pcm->handle, pcm->parameters.channel), buffer, count);
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  return pcm->parameters.buf.block.frag_size;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  return pcm->parameters.format.rate;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  pcm->parameters.format.rate = rate;
+  reconfigurePcmChannel(pcm, LOG_WARNING);
+  return getPcmSampleRate(pcm);
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  return pcm->parameters.format.voices;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  pcm->parameters.format.voices = channels;
+  reconfigurePcmChannel(pcm, LOG_WARNING);
+  return getPcmChannelCount(pcm);
+}
+
+typedef struct {
+  PcmAmplitudeFormat internal;
+  int external;
+} AmplitudeFormatEntry;
+static const AmplitudeFormatEntry amplitudeFormatTable[] = {
+  {PCM_FMT_U8     , SND_PCM_SFMT_U8     },
+  {PCM_FMT_S8     , SND_PCM_SFMT_S8     },
+  {PCM_FMT_U16B   , SND_PCM_SFMT_U16_BE },
+  {PCM_FMT_S16B   , SND_PCM_SFMT_S16_BE },
+  {PCM_FMT_U16L   , SND_PCM_SFMT_U16_LE },
+  {PCM_FMT_S16L   , SND_PCM_SFMT_S16_LE },
+  {PCM_FMT_ULAW   , SND_PCM_SFMT_MU_LAW },
+  {PCM_FMT_UNKNOWN, SND_PCM_SFMT_SPECIAL}
+};
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+  while (entry->internal != PCM_FMT_UNKNOWN) {
+    if (entry->external == pcm->parameters.format.format) break;
+    ++entry;
+  }
+  return entry->internal;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  const AmplitudeFormatEntry *entry = amplitudeFormatTable;
+  while (entry->internal != PCM_FMT_UNKNOWN) {
+    if (entry->internal == format) {
+      pcm->parameters.format.format = format;
+      reconfigurePcmChannel(pcm, LOG_WARNING);
+      break;
+    }
+    ++entry;
+  }
+  return getPcmAmplitudeFormat(pcm);
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  int code;
+  if ((code = snd_pcm_playback_flush(pcm->handle)) < 0) {
+    logPcmError(LOG_WARNING, "flush", code);
+  }
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  int code;
+  if ((code = snd_pcm_playback_drain(pcm->handle)) < 0) {
+    logPcmError(LOG_WARNING, "drain", code);
+  }
+}
diff --git a/Programs/pcm_windows.c b/Programs/pcm_windows.c
new file mode 100644
index 0000000..0493566
--- /dev/null
+++ b/Programs/pcm_windows.c
@@ -0,0 +1,293 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "parse.h"
+#include "pcm.h"
+
+struct PcmDeviceStruct {
+  HWAVEOUT handle;
+  UINT deviceID;
+  WAVEFORMATEX format;
+  HANDLE done;
+  WAVEHDR waveHdr;
+  size_t bufSize;
+};
+
+static WAVEFORMATEX defaultFormat = { WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8, 0 };
+
+static void
+recomputeWaveOutFormat(WAVEFORMATEX *format) {
+  format->nBlockAlign = format->nChannels * ((format->wBitsPerSample + 7) / 8);
+  format->nAvgBytesPerSec = format->nBlockAlign * format->nSamplesPerSec;
+}
+
+static WAVEHDR initWaveHdr = { NULL, 0, 0, 0, 0, 1, NULL, 0 };
+
+static void
+LogWaveOutError(MMRESULT error, int errorLevel, const char *action) {
+  char msg[MAXERRORLENGTH];
+  waveOutGetErrorText(error, msg, sizeof(msg));
+  logMessage(errorLevel, "%s error %d: %s.", action, error, msg);
+}
+
+PcmDevice *
+openPcmDevice (int errorLevel, const char *device) {
+  PcmDevice *pcm;
+  MMRESULT mmres;
+  WAVEOUTCAPS caps;
+  int id = 0;
+
+  if (*device) {
+    if (!isInteger(&id, device) || (id < 0) || (id >= waveOutGetNumDevs())) {
+      logMessage(errorLevel, "invalid PCM device number: %s", device);
+      return NULL;
+    }
+  }
+
+  if (!(pcm = malloc(sizeof(*pcm)))) {
+    logSystemError("PCM device allocation");
+    return NULL;
+  }
+  pcm->deviceID = id;
+
+  if ((waveOutGetDevCaps(pcm->deviceID, &caps, sizeof(caps))) != MMSYSERR_NOERROR)
+    pcm->format = defaultFormat;
+  else {
+    logMessage(errorLevel, "PCM device %d is %s", pcm->deviceID, caps.szPname);
+    pcm->format.wFormatTag = WAVE_FORMAT_PCM;
+    if (caps.dwFormats & 
+	(WAVE_FORMAT_1S08
+	|WAVE_FORMAT_1S16
+	|WAVE_FORMAT_2S08
+	|WAVE_FORMAT_2S16
+	|WAVE_FORMAT_4S08
+	|WAVE_FORMAT_4S16))
+      pcm->format.nChannels = 2;
+    else
+      pcm->format.nChannels = 1;
+    if (caps.dwFormats &
+	(WAVE_FORMAT_4M08
+	|WAVE_FORMAT_4M16
+	|WAVE_FORMAT_4S08
+	|WAVE_FORMAT_4S16))
+      pcm->format.nSamplesPerSec = 44100;
+    else if (caps.dwFormats &
+	(WAVE_FORMAT_2M08
+	|WAVE_FORMAT_2M16
+	|WAVE_FORMAT_2S08
+	|WAVE_FORMAT_2S16))
+      pcm->format.nSamplesPerSec = 22050;
+    else if (caps.dwFormats &
+	(WAVE_FORMAT_1M08
+	|WAVE_FORMAT_1M16
+	|WAVE_FORMAT_1S08
+	|WAVE_FORMAT_1S16))
+      pcm->format.nSamplesPerSec = 11025;
+    else {
+      logMessage(errorLevel, "unknown PCM capability %#lx", caps.dwFormats);
+      goto out;
+    }
+    if (caps.dwFormats &
+	(WAVE_FORMAT_1M16
+	|WAVE_FORMAT_1S16
+	|WAVE_FORMAT_2M16
+	|WAVE_FORMAT_2S16
+	|WAVE_FORMAT_4M16
+	|WAVE_FORMAT_4S16))
+      pcm->format.wBitsPerSample = 16;
+    else if (caps.dwFormats &
+	(WAVE_FORMAT_1M08
+	|WAVE_FORMAT_1S08
+	|WAVE_FORMAT_2M08
+	|WAVE_FORMAT_2S08
+	|WAVE_FORMAT_4M08
+	|WAVE_FORMAT_4S08))
+      pcm->format.wBitsPerSample = 8;
+    else {
+      logMessage(LOG_ERR, "unknown PCM capability %#lx", caps.dwFormats);
+      goto out;
+    }
+    recomputeWaveOutFormat(&pcm->format);
+    pcm->format.cbSize = 0;
+  }
+
+  if (!(pcm->done = CreateEvent(NULL, FALSE, TRUE, NULL))) {
+    logWindowsSystemError("creating PCM completion event");
+    goto out;
+  }
+
+  pcm->waveHdr = initWaveHdr;
+  pcm->bufSize = 0;
+
+  if ((mmres = waveOutOpen(&pcm->handle, pcm->deviceID,
+	  &pcm->format, (DWORD) pcm->done, 0, CALLBACK_EVENT)) != MMSYSERR_NOERROR) {
+    LogWaveOutError(mmres, errorLevel, "opening PCM device");
+    goto outEvent;
+  }
+  return pcm;
+
+outEvent:
+  CloseHandle(pcm->done);
+out:
+  free(pcm);
+  return NULL;
+}
+
+static int
+unprepareHeader(PcmDevice *pcm) {
+  MMRESULT mmres;
+  awaitPcmOutput(pcm);
+  if ((mmres = waveOutUnprepareHeader(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) {
+    LogWaveOutError(mmres, LOG_ERR, "unpreparing PCM data header");
+    return 0;
+  }
+  return 1;
+}
+
+static int
+updateWaveOutFormat(PcmDevice *pcm, WAVEFORMATEX *format, const char *errmsg) {
+  MMRESULT mmres;
+  recomputeWaveOutFormat(format);
+  if (!(unprepareHeader(pcm))) return 0;
+  if (waveOutOpen(NULL, pcm->deviceID, format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR) {
+    waveOutClose(pcm->handle);
+    pcm->handle = INVALID_HANDLE_VALUE;
+    if ((mmres = waveOutOpen(&pcm->handle, pcm->deviceID, format,
+	    (DWORD) pcm->done, 0, CALLBACK_EVENT)) == MMSYSERR_NOERROR) {
+      pcm->format = *format;
+      return 1;
+    }
+    LogWaveOutError(mmres, LOG_ERR, errmsg);
+  }
+  return 0;
+}
+
+void
+closePcmDevice (PcmDevice *pcm) {
+  CloseHandle(pcm->done);
+  unprepareHeader(pcm);
+  waveOutClose(pcm->handle);
+  free(pcm->waveHdr.lpData);
+  free(pcm);
+}
+
+int
+writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
+  MMRESULT mmres;
+  void *newBuf;
+  if (!count) return 1;
+  if (count > pcm->bufSize) {
+    if (!(unprepareHeader(pcm))) return 0;
+    if (!(newBuf = realloc(pcm->waveHdr.lpData, 2 * count))) {
+      logSystemError("allocating PCM data buffer");
+      return 0;
+    }
+    pcm->waveHdr.lpData = newBuf;
+    pcm->waveHdr.dwFlags = 0;
+    pcm->waveHdr.dwBufferLength = pcm->bufSize = 2 * count;
+  }
+  awaitPcmOutput(pcm);
+  if (!(pcm->waveHdr.dwFlags & WHDR_PREPARED))
+    if ((mmres = waveOutPrepareHeader(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) {
+      LogWaveOutError(mmres, LOG_ERR, "preparing PCM data header");
+      return 0;
+    }
+  pcm->waveHdr.dwBufferLength = count;
+  memcpy(pcm->waveHdr.lpData, buffer, count);
+  ResetEvent(pcm->done);
+  if ((mmres = waveOutWrite(pcm->handle, &pcm->waveHdr, sizeof(pcm->waveHdr))) != MMSYSERR_NOERROR) {
+    SetEvent(pcm->done);
+    LogWaveOutError(mmres, LOG_ERR, "writing PCM data");
+    return 0;
+  }
+  return 1;
+}
+
+int
+getPcmBlockSize (PcmDevice *pcm) {
+  return 0X10000;
+}
+
+int
+getPcmSampleRate (PcmDevice *pcm) {
+  return pcm->format.nSamplesPerSec;
+}
+
+int
+setPcmSampleRate (PcmDevice *pcm, int rate) {
+  WAVEFORMATEX format = pcm->format;
+  format.nSamplesPerSec = rate;
+  if (!updateWaveOutFormat(pcm, &format, "setting PCM sample rate"))
+    return getPcmSampleRate(pcm);
+  else
+    return rate;
+}
+
+int
+getPcmChannelCount (PcmDevice *pcm) {
+  return pcm->format.nChannels;
+}
+
+int
+setPcmChannelCount (PcmDevice *pcm, int channels) {
+  WAVEFORMATEX format = pcm->format;
+  format.nChannels = channels;
+  if (!updateWaveOutFormat(pcm, &format, "setting PCM channel count"))
+    return getPcmChannelCount(pcm);
+  else
+    return channels;
+}
+
+PcmAmplitudeFormat
+getPcmAmplitudeFormat (PcmDevice *pcm) {
+  if (pcm->format.wBitsPerSample == 8) return PCM_FMT_U8;
+  if (pcm->format.wBitsPerSample == 16) return PCM_FMT_S16L;
+  return PCM_FMT_UNKNOWN;
+}
+
+PcmAmplitudeFormat
+setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
+  WAVEFORMATEX newFormat = pcm->format;
+  if (format == PCM_FMT_U8) newFormat.wBitsPerSample = 8;
+  else if (format == PCM_FMT_S16L) newFormat.wBitsPerSample = 16;
+  else return getPcmAmplitudeFormat(pcm);
+  if (!updateWaveOutFormat(pcm, &newFormat, "setting PCM amplitude format"))
+    return getPcmAmplitudeFormat(pcm);
+  else
+    return format;
+}
+
+void
+pushPcmOutput (PcmDevice *pcm) {
+}
+
+void
+awaitPcmOutput (PcmDevice *pcm) {
+  while ((pcm->waveHdr.dwFlags & WHDR_PREPARED)
+		  && !(pcm->waveHdr.dwFlags & WHDR_DONE))
+    WaitForSingleObject(pcm->done, INFINITE);
+  SetEvent(pcm->done);
+}
+
+void
+cancelPcmOutput (PcmDevice *pcm) {
+  waveOutReset(pcm->handle);
+}
diff --git a/Programs/pgmpath_freebsd.c b/Programs/pgmpath_freebsd.c
new file mode 100644
index 0000000..50d1767
--- /dev/null
+++ b/Programs/pgmpath_freebsd.c
@@ -0,0 +1,31 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "pgmpath.h"
+#include "file.h"
+
+char *
+getProgramPath (void) {
+  return readSymbolicLink("/proc/curproc/file");
+}
diff --git a/Programs/pgmpath_hurd.c b/Programs/pgmpath_hurd.c
new file mode 100644
index 0000000..ee45dd9
--- /dev/null
+++ b/Programs/pgmpath_hurd.c
@@ -0,0 +1,31 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "pgmpath.h"
+#include "file.h"
+
+char *
+getProgramPath (void) {
+  return readSymbolicLink("/proc/self/exe");
+}
diff --git a/Programs/pgmpath_linux.c b/Programs/pgmpath_linux.c
new file mode 100644
index 0000000..ee45dd9
--- /dev/null
+++ b/Programs/pgmpath_linux.c
@@ -0,0 +1,31 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "pgmpath.h"
+#include "file.h"
+
+char *
+getProgramPath (void) {
+  return readSymbolicLink("/proc/self/exe");
+}
diff --git a/Programs/pgmpath_none.c b/Programs/pgmpath_none.c
new file mode 100644
index 0000000..8cbad98
--- /dev/null
+++ b/Programs/pgmpath_none.c
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "pgmpath.h"
+
+char *
+getProgramPath (void) {
+  return NULL;
+}
diff --git a/Programs/pgmpath_solaris.c b/Programs/pgmpath_solaris.c
new file mode 100644
index 0000000..1bf4e6e
--- /dev/null
+++ b/Programs/pgmpath_solaris.c
@@ -0,0 +1,31 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "pgmpath.h"
+#include "file.h"
+
+char *
+getProgramPath (void) {
+  return readSymbolicLink("/proc/self/path/a.out");
+}
diff --git a/Programs/pgmpath_windows.c b/Programs/pgmpath_windows.c
new file mode 100644
index 0000000..1931b15
--- /dev/null
+++ b/Programs/pgmpath_windows.c
@@ -0,0 +1,75 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "pgmpath.h"
+#include "system_windows.h"
+
+char *
+getProgramPath (void) {
+  char *path = NULL;
+  HMODULE handle;
+
+  if ((handle = GetModuleHandle(NULL))) {
+    size_t size = 0X80;
+    char *buffer = NULL;
+
+    while (1) {
+      {
+        char *newBuffer = realloc(buffer, size<<=1);
+
+        if (!newBuffer) {
+          logMallocError();
+          break;
+        }
+
+        buffer = newBuffer;
+      }
+
+      {
+        DWORD length = GetModuleFileName(handle, buffer, size);
+
+        if (!length) {
+          logWindowsSystemError("GetModuleFileName");
+          break;
+        }
+
+        if (length < size) {
+          buffer[length] = 0;
+          if ((path = strdup(buffer))) {
+            while (length > 0)
+              if (path[--length] == '\\')
+                path[length] = '/';
+          } else {
+            logMallocError();
+          }
+
+          break;
+        }
+      }
+    }
+
+    free(buffer);
+  } else {
+    logWindowsSystemError("GetModuleHandle");
+  }
+
+  return path;
+}
diff --git a/Programs/pgmprivs_linux.c b/Programs/pgmprivs_linux.c
new file mode 100644
index 0000000..0870131
--- /dev/null
+++ b/Programs/pgmprivs_linux.c
@@ -0,0 +1,2287 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "pgmprivs.h"
+#include "system_linux.h"
+#include "file.h"
+#include "parse.h"
+
+#define SCF_LOG_LEVEL LOG_DEBUG
+#define SCF_LOG_PROGRAM 0
+
+//#undef HAVE_PWD_H
+//#undef HAVE_GRP_H
+//#undef HAVE_SYS_PRCTL_H
+//#undef HAVE_SYS_CAPABILITY_H
+//#undef HAVE_LIBCAP
+//#undef HAVE_SCHED_H
+//#undef HAVE_LINUX_AUDIT_H
+//#undef HAVE_LINUX_FILTER_H
+//#undef HAVE_LINUX_SECCOMP_H
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif /* HAVE_SYS_PRCTL_H */
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif /* HAVE_PWD_H */
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif /* HAVE_GRP_H */
+
+#ifdef HAVE_LIBCAP
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+
+static
+STR_BEGIN_FORMATTER(formatCapabilityName, cap_value_t capability)
+  {
+    char *name = cap_to_name(capability);
+
+    if (name) {
+      STR_PRINTF("%s", name);
+      cap_free(name);
+    }
+  }
+
+  if (!STR_LENGTH) STR_PRINTF("CAP#%d", capability);
+STR_END_FORMATTER
+
+#define MAKE_CAPABILITY_NAME(buffer,capability) \
+char buffer[0X20]; \
+STR_BEGIN(buffer, sizeof(buffer)); \
+STR_FORMAT(formatCapabilityName, capability); \
+STR_END;
+
+static int
+hasCapability (cap_t caps, cap_flag_t set, cap_value_t capability) {
+  cap_flag_value_t value;
+  if (cap_get_flag(caps, capability, set, &value) != -1) return value == CAP_SET;
+  logSystemError("cap_get_flag");
+  return 0;
+}
+
+static int
+setCapabilities (cap_t caps) {
+  if (cap_set_proc(caps) != -1) return 1;
+  logSystemError("cap_set_proc");
+  return 0;
+}
+
+static int
+addCapability (cap_t caps, cap_flag_t set, cap_value_t capability) {
+  if (cap_set_flag(caps, set, 1, &capability, CAP_SET) != -1) return 1;
+  logSystemError("cap_set_flag");
+  return 0;
+}
+
+static int
+requestCapability (cap_t caps, cap_value_t capability, int inheritable) {
+  if (!hasCapability(caps, CAP_EFFECTIVE, capability)) {
+    if (!hasCapability(caps, CAP_PERMITTED, capability)) {
+      MAKE_CAPABILITY_NAME(nameBuffer, capability);
+      logMessage(LOG_DEBUG, "capability not permitted: %s", nameBuffer);
+      return 0;
+    }
+
+    if (!addCapability(caps, CAP_EFFECTIVE, capability)) return 0;
+    if (!inheritable) return setCapabilities(caps);
+  } else if (!inheritable) {
+    return 1;
+  }
+
+  if (!hasCapability(caps, CAP_INHERITABLE, capability)) {
+    if (!addCapability(caps, CAP_INHERITABLE, capability)) {
+      return 0;
+    }
+  }
+
+  if (setCapabilities(caps)) {
+#ifdef PR_CAP_AMBIENT_RAISE
+    if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, capability, 0, 0) != -1) return 1;
+    logSystemError("prctl[PR_CAP_AMBIENT_RAISE]");
+#else /* PR_CAP_AMBIENT_RAISE */
+    logMessage(LOG_WARNING, "can't raise ambient capabilities");
+#endif /* PR_CAP_AMBIENT_RAISE */
+  }
+
+  return 0;
+}
+
+static int
+needCapability (cap_value_t capability, int inheritable, const char *reason) {
+  int haveCapability = 0;
+  const char *outcome = NULL;
+  cap_t caps;
+
+  if ((caps = cap_get_proc())) {
+    if (hasCapability(caps, CAP_EFFECTIVE, capability)) {
+      haveCapability = 1;
+      outcome = "already added";
+    } else if (requestCapability(caps, capability, inheritable)) {
+      haveCapability = 1;
+      outcome = "added";
+    } else {
+      outcome = "not granted";
+    }
+
+    cap_free(caps);
+  } else {
+    logSystemError("cap_get_proc");
+  }
+
+  if (outcome) {
+    MAKE_CAPABILITY_NAME(nameBuffer, capability);
+
+    logMessage(LOG_DEBUG,
+      "temporary capability %s: %s (%s)",
+      outcome, nameBuffer, reason
+    );
+  }
+
+  return haveCapability;
+}
+#endif /* HAVE_SYS_CAPABILITY_H */
+#endif /* HAVE_LIBCAP */
+
+#if defined(HAVE_GRP_H) || defined(HAVE_PWD_H)
+static int
+canSetSupplementaryGroups (const char *reason) {
+#ifdef CAP_SETGID
+  if (needCapability(CAP_SETGID, 0, reason)) {
+    return 1;
+  }
+#endif /* CAP_SETGID */
+
+  return 0;
+}
+#endif /* defined(HAVE_GRP_H) || defined(HAVE_PWD_H) */
+
+static int
+amPrivilegedUser (void) {
+  return !geteuid();
+}
+
+typedef struct {
+  const char *reason;
+  int (*install) (void);
+} KernelModuleEntry;
+
+static const KernelModuleEntry kernelModuleTable[] = {
+  { .reason = "for playing alert tunes via the built-in PC speaker",
+    .install = installSpeakerModule,
+  },
+
+  { .reason = "for creating virtual devices",
+    .install = installUinputModule,
+  },
+}; static const uint8_t kernelModuleCount = ARRAY_COUNT(kernelModuleTable);
+
+static void
+installKernelModules (int stayPrivileged) {
+  const KernelModuleEntry *kme = kernelModuleTable;
+  const KernelModuleEntry *end = kme + kernelModuleCount;
+
+  while (kme < end) {
+    kme->install();
+    kme += 1;
+  }
+}
+
+#ifdef HAVE_GRP_H
+typedef struct {
+  const char *message;
+  const gid_t *groups;
+  size_t count;
+} GroupsLogData;
+
+static size_t
+groupsLogFormatter (char *buffer, size_t size, const void *data) {
+  const GroupsLogData *gld = data;
+
+  size_t length;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s:", gld->message);
+
+  const gid_t *gid = gld->groups;
+  const gid_t *end = gid + gld->count;
+
+  while (gid < end) {
+    STR_PRINTF(" %d", *gid);
+
+    const struct group *grp = getgrgid(*gid);
+    if (grp) STR_PRINTF("(%s)", grp->gr_name);
+
+    gid += 1;
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+  return length;
+}
+
+static void
+logGroups (int level, const char *message, const gid_t *groups, size_t count) {
+  GroupsLogData gld = {
+    .message = message,
+    .groups = groups,
+    .count = count
+  };
+
+  logData(level, groupsLogFormatter, &gld);
+}
+
+static void
+logGroup (int level, const char *message, gid_t group) {
+  logGroups(level, message, &group, 1);
+}
+
+typedef struct {
+  const char *reason;
+  const char *name;
+  const char *path;
+  unsigned char needRead:1;
+  unsigned char needWrite:1;
+} RequiredGroupEntry;
+
+static const RequiredGroupEntry requiredGroupTable[] = {
+  { .reason = "for reading screen content",
+    .name = "tty",
+    .path = "/dev/vcs1",
+  },
+
+  { .reason = "for virtual console monitoring and control",
+    .name = "tty",
+    .path = "/dev/tty1",
+  },
+
+  { .reason = "for serial I/O",
+    .path = "/dev/ttyS0",
+  },
+
+  { .reason = "for USB I/O via USBFS",
+    .path = "/dev/bus/usb",
+  },
+
+  { .reason = "for playing sound via the ALSA framework",
+    .name = "audio",
+    .path = "/dev/snd/seq",
+  },
+
+  { .reason = "for playing sound via the Pulse Audio daemon",
+    .name = "pulse-access",
+  },
+
+  { .reason = "for monitoring keyboard input",
+    .name = "input",
+    .path = "/dev/input/mice",
+  },
+
+  { .reason = "for creating virtual devices",
+    .path = "/dev/uinput",
+    .needRead = 1,
+    .needWrite = 1,
+  },
+
+  { .reason = "for reading BrlAPI's authorization key file",
+    .path = BRLAPI_ETCDIR "/" BRLAPI_AUTHKEYFILE,
+    .needRead = 1,
+  },
+}; static const uint8_t requiredGroupCount = ARRAY_COUNT(requiredGroupTable);
+
+static void
+processRequiredGroups (GroupsProcessor *processGroups, int logProblems, void *data) {
+  gid_t groups[requiredGroupCount * 2];
+  size_t count = 0;
+
+  {
+    const RequiredGroupEntry *rge = requiredGroupTable;
+    const RequiredGroupEntry *end = rge + requiredGroupCount;
+
+    while (rge < end) {
+      {
+        const char *name = rge->name;
+
+        if (name) {
+          const struct group *grp;
+
+          if ((grp = getgrnam(name))) {
+            groups[count++] = grp->gr_gid;
+          } else if (logProblems) {
+            logMessage(LOG_DEBUG, "unknown group: %s", name);
+          }
+        }
+      }
+
+      {
+        const char *path = rge->path;
+
+        if (path) {
+          struct stat status;
+
+          if (stat(path, &status) != -1) {
+            groups[count++] = status.st_gid;
+
+            if (logProblems) {
+              if (rge->needRead && !(status.st_mode & S_IRGRP)) {
+                logMessage(LOG_DEBUG, "path not group readable: %s", path);
+              }
+
+              if (rge->needWrite && !(status.st_mode & S_IWGRP)) {
+                logMessage(LOG_DEBUG, "path not group writable: %s", path);
+              }
+            }
+          } else if (logProblems) {
+            logMessage(LOG_DEBUG, "path access error: %s: %s", path, strerror(errno));
+          }
+        }
+      }
+
+      rge += 1;
+    }
+  }
+
+  removeDuplicateGroups(groups, &count);
+  processGroups(groups, count, data);
+}
+
+typedef struct {
+  const gid_t *groups;
+  size_t count;
+} CurrentGroupsData;
+
+static void
+setSupplementaryGroups (const gid_t *groups, size_t count, void *data) {
+  if (haveSupplementaryGroups(groups, count)) return;
+  const CurrentGroupsData *cgd = data;
+
+  size_t total = count;
+  if (cgd) total += cgd->count;
+  gid_t buffer[total];
+
+  if (cgd && (cgd->count > 0)) {
+    gid_t *gid = buffer;
+    gid = mempcpy(gid, groups, ARRAY_SIZE(groups, count));
+
+    if (cgd) {
+      gid = mempcpy(gid, cgd->groups, ARRAY_SIZE(cgd->groups, cgd->count));
+    }
+
+    count = gid - buffer;
+    removeDuplicateGroups(buffer, &count);
+    groups = buffer;
+  }
+
+  if (canSetSupplementaryGroups("for joining the required groups")) {
+    logGroups(LOG_DEBUG, "setting supplementary groups", groups, count);
+
+    if (setgroups(count, groups) == -1) {
+      logSystemError("setgroups");
+    }
+  } else {
+    logMessage(LOG_WARNING, "can't set supplementary groups");
+  }
+}
+
+static void
+joinRequiredGroups (int stayPrivileged) {
+  const int logProblems = 1;
+
+#ifdef HAVE_PWD_H
+  if (stayPrivileged || !amPrivilegedUser()) {
+    uid_t uid = geteuid();
+    const struct passwd *pwd = getpwuid(uid);
+
+    if (pwd) {
+      const char *user = pwd->pw_name;
+      gid_t group = pwd->pw_gid;
+
+      int count = 0;
+      getgrouplist(user, group, NULL, &count);
+
+      count += 1; // allow for the primary group
+      gid_t groups[count];
+
+      if (getgrouplist(user, group, groups, &count) != -1) {
+        size_t size = count;
+        removeDuplicateGroups(groups, &size);
+
+        CurrentGroupsData cgd = {
+          .groups = groups,
+          .count = size
+        };
+
+        processRequiredGroups(setSupplementaryGroups, logProblems, &cgd);
+        return;
+      } else {
+        logSystemError("getgrouplist");
+      }
+    }
+  }
+#endif /* HAVE_PWD_H */
+
+  processRequiredGroups(setSupplementaryGroups, logProblems, NULL);
+}
+
+static void
+logUnjoinedGroups (const gid_t *groups, size_t count, void *data) {
+  const CurrentGroupsData *cgd = data;
+
+  const gid_t *cur = cgd->groups;
+  const gid_t *curEnd = cur + cgd->count;
+
+  const gid_t *req = groups;
+  const gid_t *reqEnd = req + count;
+
+  while (req < reqEnd) {
+    int relation = (cur < curEnd)? compareGroups(*cur, *req): 1;
+
+    if (relation > 0) {
+      logGroup(LOG_WARNING, "group not joined", *req++);
+    } else {
+      if (!relation) req += 1;
+      cur += 1;
+    }
+  }
+}
+
+static void
+logWantedGroups (const gid_t *groups, size_t count, void *data) {
+  CurrentGroupsData cgd = {
+    .groups = groups,
+    .count = count
+  };
+
+  processRequiredGroups(logUnjoinedGroups, 0, &cgd);
+}
+
+static void
+logMissingGroups (void) {
+  processSupplementaryGroups(logWantedGroups, NULL);
+}
+
+static void
+closeGroupsDatabase (void) {
+  endgrent();
+}
+#endif /* HAVE_GRP_H */
+
+#ifdef CAP_IS_SUPPORTED
+typedef struct {
+  const char *label;
+  cap_t caps;
+} CapabilitiesLogData;
+
+static size_t
+capabilitiesLogFormatter (char *buffer, size_t size, const void *data) {
+  const CapabilitiesLogData *cld = data;
+
+  size_t length;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("capabilities: %s:", cld->label);
+
+  int capsAllocated = 0;
+  cap_t caps;
+
+  if (!(caps = cld->caps)) {
+    if (!(caps = cap_get_proc())) {
+      logSystemError("cap_get_proc");
+      goto done;
+    }
+
+    capsAllocated = 1;
+  }
+
+  {
+    char *text;
+
+    if ((text = cap_to_text(caps, NULL))) {
+      STR_PRINTF(" %s", text);
+      cap_free(text);
+    } else {
+      logSystemError("cap_to_text");
+    }
+  }
+
+  if (capsAllocated) {
+    cap_free(caps);
+    caps = NULL;
+  }
+
+done:
+  length = STR_LENGTH;
+  STR_END;
+  return length;
+}
+
+static void
+logCapabilities (cap_t caps, const char *label) {
+  CapabilitiesLogData cld = { .label=label, .caps=caps };
+  logData(LOG_DEBUG, capabilitiesLogFormatter, &cld);
+}
+
+static void
+logCurrentCapabilities (const char *label) {
+  logCapabilities(NULL, label);
+}
+
+typedef struct {
+  const char *reason;
+  cap_value_t value;
+} RequiredCapabilityEntry;
+
+static const RequiredCapabilityEntry requiredCapabilityTable[] = {
+  { .reason = "for injecting input characters typed on a braille device",
+    .value = CAP_SYS_ADMIN,
+  },
+
+  { .reason = "for playing alert tunes via the built-in PC speaker",
+    .value = CAP_SYS_TTY_CONFIG,
+  },
+
+  { .reason = "for creating needed but missing special device files",
+    .value = CAP_MKNOD,
+  },
+}; static const uint8_t requiredCapabilityCount = ARRAY_COUNT(requiredCapabilityTable);
+
+static void
+setRequiredCapabilities (int stayPrivileged) {
+  cap_t newCaps, oldCaps;
+
+  if (amPrivilegedUser()) {
+    oldCaps = NULL;
+  } else if (!(oldCaps = cap_get_proc())) {
+    logSystemError("cap_get_proc");
+    return;
+  }
+
+  {
+    cap_t (*function) (void);
+    const char *name;
+
+    if (stayPrivileged) {
+      function = cap_get_proc;
+      name = "cap_get_proc";
+    } else {
+      function = cap_init;
+      name = "cap_init";
+    }
+
+    if (!(newCaps = function())) logSystemError(name);
+  }
+
+  if (newCaps) {
+    {
+      const RequiredCapabilityEntry *rce = requiredCapabilityTable;
+      const RequiredCapabilityEntry *end = rce + requiredCapabilityCount;
+
+      while (rce < end) {
+        cap_value_t capability = rce->value;
+
+        if (!oldCaps || hasCapability(oldCaps, CAP_PERMITTED, capability)) {
+          if (!addCapability(newCaps, CAP_PERMITTED, capability)) break;
+          if (!addCapability(newCaps, CAP_EFFECTIVE, capability)) break;
+        }
+
+        rce += 1;
+      }
+    }
+
+    setCapabilities(newCaps);
+    cap_free(newCaps);
+  }
+
+#ifdef PR_CAP_AMBIENT_CLEAR_ALL
+  if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) == -1) {
+    logSystemError("prctl[PR_CAP_AMBIENT_CLEAR_ALL]");
+  }
+#else /* PR_CAP_AMBIENT_CLEAR_ALL */
+  logMessage(LOG_WARNING, "can't clear ambient capabilities");
+#endif /* PR_CAP_AMBIENT_CLEAR_ALL */
+
+  if (oldCaps) cap_free(oldCaps);
+}
+
+static void
+logMissingCapabilities (void) {
+  cap_t caps;
+
+  if ((caps = cap_get_proc())) {
+    const RequiredCapabilityEntry *rce = requiredCapabilityTable;
+    const RequiredCapabilityEntry *end = rce + requiredCapabilityCount;
+
+    while (rce < end) {
+      cap_value_t capability = rce->value;
+
+      if (!hasCapability(caps, CAP_EFFECTIVE, capability)) {
+        MAKE_CAPABILITY_NAME(nameBuffer, capability);
+
+        logMessage(LOG_WARNING,
+          "required capability not granted: %s (%s)",
+          nameBuffer, rce->reason
+        );
+      }
+
+      rce += 1;
+    }
+
+    cap_free(caps);
+  } else {
+    logSystemError("cap_get_proc");
+  }
+}
+
+#else /* CAP_IS_SUPPORTED */
+static void
+logCurrentCapabilities (const char *label) {
+}
+#endif /* CAP_IS_SUPPORTED */
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+
+typedef struct {
+  const char *name;
+  const char *summary;
+  int unshareFlag;
+} IsolatedNamespaceEntry;
+
+static const IsolatedNamespaceEntry isolatedNamespaceTable[] = {
+  #ifdef CLONE_NEWCGROUP
+  { .unshareFlag = CLONE_NEWCGROUP,
+    .name = "cgroup",
+    .summary = "control groups",
+  },
+  #endif /* CLONE_NEWCGROUP */
+
+  #ifdef CLONE_NEWNS
+  { .unshareFlag = CLONE_NEWNS,
+    .name = "mount",
+    .summary = "mount points",
+  },
+  #endif /* CLONE_NEWNS */
+
+  #ifdef CLONE_NEWUTS
+  { .unshareFlag = CLONE_NEWUTS,
+    .name = "UTS",
+    .summary = "host name and NIS domain name",
+  },
+  #endif /* CLONE_NEWUTS */
+}; static const uint8_t isolatedNamespaceCount = ARRAY_COUNT(isolatedNamespaceTable);
+
+static void
+isolateNamespaces (void) {
+  int canIsolateNamespaces = 0;
+
+#ifdef CAP_SYS_ADMIN
+  if (needCapability(CAP_SYS_ADMIN, 0, "for isolating namespaces")) {
+    canIsolateNamespaces = 1;
+  }
+#endif /* CAP_SYS_ADMIN */
+
+  if (canIsolateNamespaces) {
+    int unshareFlags = 0;
+
+    const IsolatedNamespaceEntry *ine = isolatedNamespaceTable;
+    const IsolatedNamespaceEntry *end = ine + isolatedNamespaceCount;
+
+    while (ine < end) {
+      logMessage(LOG_DEBUG,
+        "isolating namespace: %s (%s)", ine->name, ine->summary
+      );
+
+      unshareFlags |= ine->unshareFlag;
+      ine += 1;
+    }
+
+    if (unshare(unshareFlags) == -1) {
+      logSystemError("unshare");
+    }
+  } else {
+    logMessage(LOG_WARNING, "can't isolate namespaces");
+  }
+}
+#endif /* HAVE_SCHED_H */
+
+#ifdef HAVE_LINUX_AUDIT_H
+#include <linux/audit.h>
+
+#if defined(__i386__)
+#define SYSTEM_CALL_ARCHITECTURE AUDIT_ARCH_I386
+#elif defined(__x86_64__)
+#define SYSTEM_CALL_ARCHITECTURE AUDIT_ARCH_X86_64
+#else /* system call architecture */
+#warning system call architecture not known for this platform
+#endif /* system call architecture */
+#endif /* HAVE_LINUX_AUDIT_H */
+
+#ifdef SYSTEM_CALL_ARCHITECTURE
+#ifdef HAVE_LINUX_FILTER_H
+#include <linux/filter.h>
+
+#ifdef HAVE_LINUX_SECCOMP_H
+#include <linux/seccomp.h>
+#endif /* HAVE_LINUX_SECCOMP_H */
+#endif /* HAVE_LINUX_FILTER_H */
+#endif /* SYSTEM_CALL_ARCHITECTURE */
+
+#ifdef SECCOMP_MODE_FILTER
+static const  char scfLogLabel[] = "SCF";
+
+typedef struct SCFArgumentDescriptorStruct SCFArgumentDescriptor;
+
+// best to protect each of these with an #ifdef for the value's macro
+typedef struct {
+  // required - must be first
+  uint32_t value;
+
+  // optional - use SCF_ARGUMENT(index, group)
+  const SCFArgumentDescriptor *argument;
+} SCFValueDescriptor;
+
+typedef struct {
+  const char *name;
+  const SCFValueDescriptor *descriptors;
+  size_t count;
+} SCFValueGroup;
+
+#define SCF_VALUE_GROUP(group) { \
+  .name = #group, \
+  .descriptors = group##Values, \
+  .count = ARRAY_COUNT(group##Values), \
+}
+
+struct SCFArgumentDescriptorStruct {
+  SCFValueGroup values;
+  uint8_t index;
+};
+
+#define SCF_ARGUMENT(n, group) \
+.argument = &(const SCFArgumentDescriptor){ \
+  .values = SCF_VALUE_GROUP(group), \
+  .index = (n) \
+}
+
+#define SCF_BEGIN_VALUES(group) static const SCFValueDescriptor group##Values[] = {
+#define SCF_END_VALUES };
+
+#include "syscalls_linux.h"
+static const SCFValueGroup scfSystemCalls = SCF_VALUE_GROUP(systemCall);
+
+typedef struct SCFJumpStruct SCFJump;
+
+typedef enum {
+  SCF_JUMP_ALWAYS,
+  SCF_JUMP_TRUE,
+  SCF_JUMP_FALSE
+} SCFJumpType;
+
+struct SCFJumpStruct {
+  SCFJump *next;
+  size_t location;
+  SCFJumpType type;
+};
+
+typedef struct {
+  const SCFArgumentDescriptor *descriptor;
+  SCFJump jump;
+} SCFArgument;
+
+#define SCF_INSTRUCTION(code, k) \
+(const struct sock_filter)BPF_STMT((code), (k))
+
+#define SCF_RETURN_INSTRUCTION(action, value) \
+SCF_INSTRUCTION(BPF_RET|BPF_K, (SECCOMP_RET_##action | ((value) & SECCOMP_RET_DATA)))
+
+typedef struct {
+  const char *name; // must be first
+  const struct sock_filter *deny;
+} SCFMode;
+
+static const SCFMode scfModes[] = {
+  // must be first
+  { .name = "no",
+  },
+
+  #ifdef SECCOMP_RET_LOG
+  { .name = "log",
+    .deny = &SCF_RETURN_INSTRUCTION(LOG, 0)
+  },
+  #endif /* SECCOMP_RET_LOG */
+
+  #ifdef SECCOMP_RET_ERRNO
+  { .name = "fail",
+    .deny = &SCF_RETURN_INSTRUCTION(ERRNO, EPERM)
+  },
+  #endif /* SECCOMP_RET_ERRNO */
+
+  #ifdef SECCOMP_RET_KILL_PROCESS
+  { .name = "kill",
+    .deny = &SCF_RETURN_INSTRUCTION(KILL_PROCESS, 0)
+  },
+  #endif /* SECCOMP_RET_KILL_PROCESS */
+
+  // must be last
+  { .name = NULL }
+};
+
+static const SCFMode *
+scfGetMode (const char *name) {
+  unsigned int choice;
+  int valid = validateChoiceEx(&choice, name, scfModes, sizeof(scfModes[0]));
+  const SCFMode *mode = &scfModes[choice];
+
+  if (!valid) {
+    logMessage(LOG_WARNING,
+      "unknown system call filter mode: %s: assuming %s",
+      name, mode->name
+    );
+  }
+
+  return mode;
+}
+
+typedef struct {
+  const SCFMode *mode;
+
+  struct {
+    struct sock_filter *array;
+    size_t size;
+    size_t count;
+  } instruction;
+
+  struct {
+    SCFArgument *array;
+    size_t size;
+    size_t count;
+  } argument;
+
+  struct {
+    SCFJump *jumps;
+  } allow;
+} SCFObject;
+
+static int
+scfAddInstruction (SCFObject *scf, const struct sock_filter *instruction) {
+  if (scf->instruction.count == BPF_MAXINSNS) {
+    logMessage(LOG_ERR, "system call filter too large");
+    return 0;
+  }
+
+  if (scf->instruction.count == scf->instruction.size) {
+    size_t newSize = scf->instruction.size? scf->instruction.size<<1: 0X10;
+    struct sock_filter *newArray;
+
+    if (!(newArray = realloc(scf->instruction.array, ARRAY_SIZE(newArray, newSize)))) {
+      logMallocError();
+      return 0;
+    }
+
+    scf->instruction.array = newArray;
+    scf->instruction.size = newSize;
+  }
+
+  scf->instruction.array[scf->instruction.count++] = *instruction;
+  return 1;
+}
+
+static int
+scfAddAllowInstruction (SCFObject *scf) {
+  static const struct sock_filter allow = SCF_RETURN_INSTRUCTION(ALLOW, 0);
+  return scfAddInstruction(scf, &allow);
+}
+
+static int
+scfAddDenyInstruction (SCFObject *scf) {
+  return scfAddInstruction(scf, scf->mode->deny);
+}
+
+static int
+scfLoadData (SCFObject *scf, uint32_t offset, uint8_t width) {
+  struct sock_filter instruction = BPF_STMT(BPF_LD|BPF_ABS, offset);
+
+  switch (width) {
+    case 1:
+      instruction.code |= BPF_B;
+      break;
+
+    case 2:
+      instruction.code |= BPF_H;
+      break;
+
+    case 4:
+      instruction.code |= BPF_W;
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unsupported field width: %u", width);
+      return 0;
+  }
+
+  return scfAddInstruction(scf, &instruction);
+}
+
+#define SCF_DATA_OFFSET(field) (offsetof(struct seccomp_data, field))
+
+static int
+scfLoadArchitecture (SCFObject *scf) {
+  return scfLoadData(scf, SCF_DATA_OFFSET(arch), 4);
+}
+
+static int
+scfLoadSystemCall (SCFObject *scf) {
+  return scfLoadData(scf, SCF_DATA_OFFSET(nr), 4);
+}
+
+static int
+scfLoadArgument (SCFObject *scf, uint8_t index) {
+  return scfLoadData(scf, SCF_DATA_OFFSET(args[index]), 4);
+}
+
+static void
+scfBeginJump (SCFObject *scf, SCFJump *jump, SCFJumpType type) {
+  memset(jump, 0, sizeof(*jump));
+  jump->next = NULL;
+  jump->location = scf->instruction.count;
+  jump->type = type;
+}
+
+static int
+scfEndJump (SCFObject *scf, const SCFJump *jump) {
+  size_t from = jump->location;
+  struct sock_filter *instruction = &scf->instruction.array[from];
+
+  size_t to = scf->instruction.count - from - 1;
+  SCFJumpType type = jump->type;
+
+  switch (type) {
+    case SCF_JUMP_ALWAYS:
+      instruction->k = to;
+      break;
+
+    case SCF_JUMP_TRUE:
+      instruction->jt = to;
+      break;
+
+    case SCF_JUMP_FALSE:
+      instruction->jf = to;
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unsupported jump type: %u", type);
+      return 0;
+  }
+
+  return 1;
+}
+
+static void
+scfPushJump (SCFJump **jumps, SCFJump *jump) {
+  jump->next = *jumps;
+  *jumps = jump;
+}
+
+static SCFJump *
+scfPopJump (SCFJump **jumps) {
+  SCFJump *jump = *jumps;
+
+  if (jump) {
+    *jumps = jump->next;
+    jump->next = NULL;
+  }
+
+  return jump;
+}
+
+static int
+scfEndJumps (SCFObject *scf, SCFJump **jumps) {
+  int ok = 1;
+
+  while (1) {
+    SCFJump *jump = scfPopJump(jumps);
+    if (!jump) break;
+
+    if (!scfEndJump(scf, jump)) ok = 0;
+    free(jump);
+  }
+
+  return ok;
+}
+
+static int
+scfJumpTo (SCFObject *scf, SCFJump *jump) {
+  struct sock_filter instruction = BPF_STMT(BPF_JMP|BPF_K|BPF_JA, 0);
+  scfBeginJump(scf, jump, SCF_JUMP_ALWAYS);
+  return scfAddInstruction(scf, &instruction);
+}
+
+typedef enum {
+  SCF_TEST_NE,
+  SCF_TEST_LT,
+  SCF_TEST_LE,
+  SCF_TEST_EQ,
+  SCF_TEST_GE,
+  SCF_TEST_GT,
+} SCFTest;
+
+static int
+scfJumpIf (SCFObject *scf, SCFTest test, uint32_t value, SCFJump *jump) {
+  struct sock_filter instruction = BPF_STMT(BPF_JMP|BPF_K, value);
+  int invert = 0;
+
+  switch (test) {
+    case SCF_TEST_NE:
+      invert = 1;
+    case SCF_TEST_EQ:
+      instruction.code |= BPF_JEQ;
+      break;
+
+    case SCF_TEST_LT:
+      invert = 1;
+    case SCF_TEST_GE:
+      instruction.code |= BPF_JGE;
+      break;
+
+    case SCF_TEST_LE:
+      invert = 1;
+    case SCF_TEST_GT:
+      instruction.code |= BPF_JGT;
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unsupported value test: %u", test);
+      return 0;
+  }
+
+  scfBeginJump(scf, jump, (invert? SCF_JUMP_FALSE: SCF_JUMP_TRUE));
+  return scfAddInstruction(scf, &instruction);
+}
+
+static int
+scfVerifyArchitecture (SCFObject *scf) {
+  SCFJump eqArch;
+
+  return scfLoadArchitecture(scf)
+      && scfJumpIf(scf, SCF_TEST_EQ, SYSTEM_CALL_ARCHITECTURE, &eqArch)
+      && scfAddDenyInstruction(scf)
+      && scfEndJump(scf, &eqArch);
+}
+
+static int
+scfJumpToArgument (SCFObject *scf, const SCFArgumentDescriptor *descriptor) {
+  if (scf->argument.count == scf->argument.size) {
+    size_t newSize = scf->argument.size? scf->argument.size<<1: 0X10;
+    SCFArgument *newArray;
+
+    if (!(newArray = realloc(scf->argument.array, ARRAY_SIZE(newArray, newSize)))) {
+      logMallocError();
+      return 0;
+    }
+
+    scf->argument.array = newArray;
+    scf->argument.size = newSize;
+  }
+
+  {
+    size_t *count = &scf->argument.count;
+    SCFArgument *argument = &scf->argument.array[*count];
+
+    argument->descriptor = descriptor;
+    if (!scfJumpTo(scf, &argument->jump)) return 0;
+
+    *count += 1;
+  }
+
+  return 1;
+}
+
+static int
+scfAllowValue (SCFObject *scf, const SCFValueDescriptor *descriptor) {
+  if (descriptor->argument) {
+    SCFJump neValue;
+
+    return scfJumpIf(scf, SCF_TEST_NE, descriptor->value, &neValue)
+        && scfJumpToArgument(scf, descriptor->argument)
+        && scfEndJump(scf, &neValue);
+  } else {
+    SCFJump *eqValue;
+
+    if ((eqValue = malloc(sizeof(*eqValue)))) {
+      if (scfJumpIf(scf, SCF_TEST_EQ, descriptor->value, eqValue)) {
+        scfPushJump(&scf->allow.jumps, eqValue);
+        return 1;
+      }
+
+      free(eqValue);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+static int
+scfAllowValues (SCFObject *scf, const SCFValueDescriptor *descriptors, size_t count) {
+  if (count <= 3) {
+    const SCFValueDescriptor *descriptor = descriptors;
+    const SCFValueDescriptor *end = descriptor + count;
+
+    while (descriptor < end) {
+      if (!scfAllowValue(scf, descriptor)) return 0;
+      descriptor += 1;
+    }
+
+    return scfAddDenyInstruction(scf);
+  }
+
+  const SCFValueDescriptor *descriptor = descriptors + (count / 2);
+  SCFJump gtValue;
+  if (!scfJumpIf(scf, SCF_TEST_GT, descriptor->value, &gtValue)) return 0;
+  if (!scfAllowValue(scf, descriptor)) return 0;
+
+  if (!scfAllowValues(scf, descriptors, (descriptor - descriptors))) return 0;
+  if (!scfEndJump(scf, &gtValue)) return 0;
+
+  const SCFValueDescriptor *end = descriptors + count;
+  descriptor += 1;
+  return scfAllowValues(scf, descriptor, (end - descriptor));
+}
+
+static int
+scfValueSorter (const void *element1, const void *element2) {
+  const SCFValueDescriptor *descriptor1 = element1;
+  const SCFValueDescriptor *descriptor2 = element2;
+
+  if (descriptor1->value < descriptor2->value) return -1;
+  if (descriptor1->value > descriptor2->value) return 1;
+  return 0;
+}
+
+static void
+scfSortValues (SCFValueDescriptor *values, size_t count) {
+  qsort(values, count, sizeof(*values), scfValueSorter);
+}
+
+static void
+scfRemoveDuplicateValues (SCFValueDescriptor *values, size_t *count, const char *name) {
+  if (*count > 1) {
+    SCFValueDescriptor *to = values;
+    const SCFValueDescriptor *from = values + 1;
+    const SCFValueDescriptor *end = values + *count;
+
+    while (from < end) {
+      if (from->value == to->value) {
+        logMessage(LOG_WARNING,
+          "%s: duplicate value: %s: 0X%08"PRIX32,
+          scfLogLabel, name, from->value
+        );
+      } else if (++to != from) {
+        *to = *from;
+      }
+
+      from += 1;
+    }
+
+    *count = ++to - values;
+  }
+}
+
+static int
+scfAllowValueGroup (SCFObject *scf, const SCFValueGroup *values) {
+  {
+    const char *name = values->name;
+    size_t count = values->count;
+
+    SCFValueDescriptor descriptors[count];
+    memcpy(descriptors, values->descriptors, sizeof(descriptors));
+
+    scfSortValues(descriptors, count);
+    scfRemoveDuplicateValues(descriptors, &count, name);
+
+    logMessage(SCF_LOG_LEVEL,
+      "%s: value group size: %s: %zu", scfLogLabel, name, count
+    );
+
+    if (!scfAllowValues(scf, descriptors, count)) return 0;
+  }
+
+  if (scf->allow.jumps) {
+    if (!scfEndJumps(scf, &scf->allow.jumps)) return 0;
+    if (!scfAddAllowInstruction(scf)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+scfCheckSysemCall (SCFObject *scf) {
+  return scfLoadSystemCall(scf)
+      && scfAllowValueGroup(scf, &scfSystemCalls);
+}
+
+static int
+scfCheckArgument (SCFObject *scf, const SCFArgument *argument) {
+  const SCFArgumentDescriptor *descriptor = argument->descriptor;
+
+  return scfEndJump(scf, &argument->jump)
+      && scfLoadArgument(scf, descriptor->index)
+      && scfAllowValueGroup(scf, &descriptor->values);
+}
+
+static int
+scfCheckArguments (SCFObject *scf) {
+  /* An argument's value group can include the specification of another
+   * argument and its associated value group. The following iteration,
+   * therefore, needs to handle the possibility that the pending argument
+   * count may increase as each argument is processed.
+   */
+
+  while (scf->argument.count > 0) {
+    if (!scfCheckArgument(scf, &scf->argument.array[--scf->argument.count])) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+#if SCF_LOG_PROGRAM
+typedef struct {
+  const struct sock_filter *instruction;
+  size_t location;
+
+  struct {
+    int decimal;
+    int hexadecimal;
+  } width;
+} SCFInstructionFormattingData;
+
+static
+STR_BEGIN_FORMATTER(scfFormatLocation, size_t location, const SCFInstructionFormattingData *ifd)
+  STR_PRINTF("X%0*zX", ifd->width.hexadecimal, location);
+STR_END_FORMATTER
+
+static
+STR_BEGIN_FORMATTER(scfDisassembleInstruction, const SCFInstructionFormattingData *ifd)
+  const struct sock_filter *instruction = ifd->instruction;
+  uint16_t code = instruction->code;
+  uint32_t operand = instruction->k;
+
+  const char *name = NULL;
+  int hasSize = 0;
+  int hasMode = 0;
+  int hasSource = 0;
+  int isJump = 0;
+  int isReturn = 0;
+  int problem = 0;
+
+  switch (BPF_CLASS(code)) {
+    case BPF_LD:
+      name = "ld";
+      hasSize = 1;
+      hasMode = 1;
+      break;
+
+    case BPF_LDX:
+      name = "ldx";
+      hasSize = 1;
+      hasMode = 1;
+      break;
+
+    case BPF_ST:
+      name = "st";
+      hasSize = 1;
+      hasMode = 1;
+      break;
+
+    case BPF_STX:
+      name = "stx";
+      hasSize = 1;
+      hasMode = 1;
+      break;
+
+    case BPF_ALU:
+      switch (BPF_OP(code)) {
+        case BPF_ADD:
+          name = "add";
+          break;
+
+        case BPF_SUB:
+          name = "sub";
+          break;
+
+        case BPF_MUL:
+          name = "mul";
+          break;
+
+        case BPF_DIV:
+          name = "div";
+          break;
+
+        case BPF_MOD:
+          name = "mod";
+          break;
+
+        case BPF_LSH:
+          name = "lsh";
+          break;
+
+        case BPF_RSH:
+          name = "rsh";
+          break;
+
+        case BPF_AND:
+          name = "and";
+          break;
+
+        case BPF_OR:
+          name = "or";
+          break;
+
+        case BPF_XOR:
+          name = "xor";
+          break;
+
+        case BPF_NEG:
+          name = "neg";
+          break;
+
+        default:
+          name = "alu";
+          problem = 1;
+          break;
+      }
+
+      hasSource = 1;
+      break;
+
+    case BPF_JMP:
+      switch (BPF_OP(code)) {
+        case BPF_JEQ:
+          name = "jeq";
+          break;
+
+        case BPF_JGT:
+          name = "jgt";
+          break;
+
+        case BPF_JGE:
+          name = "jge";
+          break;
+
+        case BPF_JSET:
+          name = "jseT";
+          break;
+
+        default:
+          problem = 1;
+          /* fall through */
+
+        case BPF_JA:
+          name = "jmp";
+          break;
+      }
+
+      hasSource = 1;
+      isJump = 1;
+      break;
+
+    case BPF_RET:
+      name = "ret";
+      isReturn = 1;
+      break;
+
+    default:
+      problem = 1;
+      break;
+  }
+
+  if (name) STR_PRINTF("%s", name);
+
+  if (hasSize) {
+    const char *size = NULL;
+
+    switch (BPF_SIZE(code)) {
+      case BPF_B:
+        size = "b";
+        break;
+
+      case BPF_H:
+        size = "h";
+        break;
+
+      case BPF_W:
+        size = "w";
+        break;
+
+      default:
+        problem = 1;
+        break;
+    }
+
+    if (size) STR_PRINTF("%s", size);
+  }
+
+  if (hasMode) {
+    const char *mode = NULL;
+
+    switch (BPF_MODE(code)) {
+      case BPF_IMM:
+        mode = "imm";
+        break;
+
+      case BPF_ABS:
+        mode = "abs";
+        break;
+
+      case BPF_IND:
+        mode = "ind";
+        break;
+
+      case BPF_MEM:
+        mode = "mem";
+        break;
+
+      case BPF_LEN:
+        mode = "len";
+        break;
+
+      default:
+        problem = 1;
+        break;
+    }
+
+    if (mode) STR_PRINTF("-%s", mode);
+  }
+
+  if (hasSource) {
+    const char *source = NULL;
+
+    switch (BPF_SRC(code)) {
+      case BPF_K:
+        source = "k";
+        break;
+
+      case BPF_X:
+        source = "x";
+        break;
+
+      default:
+        problem = 1;
+        break;
+    }
+
+    if (source) STR_PRINTF("-%s", source);
+  }
+
+  if (isReturn) {
+    const char *action = NULL;
+
+    switch (operand & SECCOMP_RET_ACTION_FULL) {
+      case SECCOMP_RET_KILL_PROCESS:
+        action = "kill-process";
+        break;
+
+      case SECCOMP_RET_KILL_THREAD:
+        action = "kill-thread";
+        break;
+
+      case SECCOMP_RET_TRAP:
+        action = "trap";
+        break;
+
+      case SECCOMP_RET_ERRNO:
+        action = "errno";
+        break;
+
+      case SECCOMP_RET_USER_NOTIF:
+        action = "notify";
+        break;
+
+      case SECCOMP_RET_TRACE:
+        action = "trace";
+        break;
+
+      case SECCOMP_RET_LOG:
+        action = "log";
+        break;
+
+      case SECCOMP_RET_ALLOW:
+        action = "allow";
+        break;
+
+      default:
+        break;
+    }
+
+    if (action) {
+      STR_PRINTF("-%s", action);
+      uint16_t data = operand & SECCOMP_RET_DATA;
+      if (data) STR_PRINTF("(%u)", data);
+    }
+  }
+
+  if (problem) {
+    STR_PRINTF("?");
+  } else if (isJump) {
+    STR_PRINTF(" -> ");
+    size_t from = ifd->location + 1;
+
+    if (BPF_OP(code) == BPF_JA) {
+      STR_FORMAT(scfFormatLocation, (from + operand), ifd);
+    } else {
+      STR_FORMAT(scfFormatLocation, (from + instruction->jt), ifd);
+      STR_PRINTF(" ");
+      STR_FORMAT(scfFormatLocation, (from + instruction->jf), ifd);
+    }
+  }
+STR_END_FORMATTER
+
+static
+STR_BEGIN_FORMATTER(scfFormatInstruction, const SCFInstructionFormattingData *ifd)
+  STR_FORMAT(scfFormatLocation, ifd->location, ifd);
+
+  STR_PRINTF(
+    ": %04X %08X %02X %02X: ",
+    ifd->instruction->code, ifd->instruction->k,
+    ifd->instruction->jt, ifd->instruction->jf
+  );
+
+  STR_FORMAT(scfDisassembleInstruction, ifd);
+STR_END_FORMATTER
+
+static void
+scfLogProgram (SCFObject *scf) {
+  size_t count = scf->instruction.count;
+
+  SCFInstructionFormattingData ifd;
+  memset(&ifd, 0, sizeof(ifd));
+
+  {
+    size_t index = count;
+    if (index > 0) index -= 1;
+
+    char buffer[0X40];
+    ifd.width.decimal = snprintf(buffer, sizeof(buffer), "%zu", index);
+    ifd.width.hexadecimal = snprintf(buffer, sizeof(buffer), "%zx", index);
+  }
+
+  for (size_t index=0; index<count; index+=1) {
+    char log[0X100];
+    STR_BEGIN(log, sizeof(log));
+    STR_PRINTF("%s: instruction: %*zu ", scfLogLabel, ifd.width.decimal, index);
+
+    ifd.instruction = &scf->instruction.array[index];
+    ifd.location = index;
+    STR_FORMAT(scfFormatInstruction, &ifd);
+
+    STR_END;
+    logMessage(SCF_LOG_LEVEL, "%s", log);
+  }
+}
+#endif /* SCF_LOG_PROGRAM */
+
+static void
+scfDestroyJumps (SCFJump **jumps) {
+  while (1) {
+    SCFJump *jump = scfPopJump(jumps);
+    if (!jump) break;
+    free(jump);
+  }
+}
+
+static void
+scfDestroyObject (SCFObject *scf) {
+  scfDestroyJumps(&scf->allow.jumps);
+  if (scf->instruction.array) free(scf->instruction.array);
+  if (scf->argument.array) free(scf->argument.array);
+  free(scf);
+}
+
+static SCFObject *
+scfNewObject (const SCFMode *mode) {
+  SCFObject *scf;
+
+  if ((scf = malloc(sizeof(*scf)))) {
+    memset(scf, 0, sizeof(*scf));
+    scf->mode = mode;
+
+    scf->instruction.array = NULL;
+    scf->instruction.size = 0;
+    scf->instruction.count = 0;
+
+    scf->argument.array = NULL;
+    scf->argument.size = 0;
+    scf->argument.count = 0;
+
+    scf->allow.jumps = NULL;
+
+    return scf;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static SCFObject *
+scfMakeFilter (const SCFMode *mode) {
+  SCFObject *scf;
+
+  if ((scf = scfNewObject(mode))) {
+    if (scfVerifyArchitecture(scf)) {
+      if (scfCheckSysemCall(scf)) {
+        if (scfCheckArguments(scf)) {
+          logMessage(SCF_LOG_LEVEL,
+            "%s: program size: %zu",
+            scfLogLabel, scf->instruction.count
+          );
+
+          #if SCF_LOG_PROGRAM
+          logMessage(SCF_LOG_LEVEL, "%s: begin program", scfLogLabel);
+          scfLogProgram(scf);
+          logMessage(SCF_LOG_LEVEL, "%s: end program", scfLogLabel);
+          #endif /* SCF_LOG_PROGRAM */
+
+          return scf;
+        }
+      }
+    }
+
+    scfDestroyObject(scf);
+  }
+
+  return NULL;
+}
+
+static void
+scfInstallFilter (const char *modeName) {
+  const SCFMode *mode = scfGetMode(modeName);
+  if (!mode->deny) return;
+  SCFObject *scf;
+
+#ifdef PR_SET_NO_NEW_PRIVS
+  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+    logSystemError("prctl[PR_SET_NO_NEW_PRIVS]");
+  }
+#endif /* PR_SET_NO_NEW_PRIVS */
+
+  if ((scf = scfMakeFilter(mode))) {
+    struct sock_fprog program = {
+      .filter = scf->instruction.array,
+      .len = scf->instruction.count
+    };
+
+#if defined(PR_SET_SECCOMP)
+    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program, 0, 0) == -1) {
+      logSystemError("prctl[PR_SET_SECCOMP,SECCOMP_MODE_FILTER]");
+    }
+#elif defined(SECCOMP_SET_MODE_FILTER)
+    unsigned int flags = 0;
+
+    if (seccomp(SECCOMP_SET_MODE_FILTER, flags, &program) == -1) {
+      logSystemError("seccomp[SECCOMP_SET_MODE_FILTER]");
+    }
+#else /* install system call filter */
+#warning no mechanism for installing the system call filter
+#endif /* install system call filter */
+
+    scfDestroyObject(scf);
+  }
+}
+#endif /* SECCOMP_MODE_FILTER */
+
+typedef void PrivilegesEstablishmentFunction (int stayPrivileged);
+typedef void MissingPrivilegesLogger (void);
+typedef void ReleaseResourcesFunction (void);
+
+typedef struct {
+  const char *reason;
+  PrivilegesEstablishmentFunction *establishPrivileges;
+  MissingPrivilegesLogger *logMissingPrivileges;
+  ReleaseResourcesFunction *releaseResources;
+
+  #ifdef CAP_IS_SUPPORTED
+  cap_value_t capability;
+  unsigned char inheritable:1;
+  #endif /* CAP_IS_SUPPORTED */
+} PrivilegesMechanismEntry;
+
+static const PrivilegesMechanismEntry privilegesMechanismTable[] = {
+  { .reason = "for installing kernel modules",
+    .establishPrivileges = installKernelModules,
+
+    #ifdef CAP_SYS_MODULE
+    .capability = CAP_SYS_MODULE,
+    .inheritable = 1,
+    #endif /* CAP_SYS_MODULE, */
+  },
+
+#ifdef HAVE_GRP_H
+  { .reason = "for joining the required groups",
+    .establishPrivileges = joinRequiredGroups,
+    .logMissingPrivileges = logMissingGroups,
+    .releaseResources = closeGroupsDatabase,
+  },
+#endif /* HAVE_GRP_H */
+
+// This one must be last because it relinquishes the temporary capabilities.
+#ifdef CAP_IS_SUPPORTED
+  { .reason = "for assigning required capabilities",
+    .establishPrivileges = setRequiredCapabilities,
+    .logMissingPrivileges = logMissingCapabilities,
+  }
+#endif /* CAP_IS_SUPPORTED */
+}; static const uint8_t privilegesMechanismCount = ARRAY_COUNT(privilegesMechanismTable);
+
+static void
+establishPrivileges (int stayPrivileged) {
+  if (amPrivilegedUser()) {
+    const PrivilegesMechanismEntry *pme = privilegesMechanismTable;
+    const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount;
+
+    while (pme < end) {
+      pme->establishPrivileges(stayPrivileged);
+      pme += 1;
+    }
+  }
+
+#ifdef CAP_IS_SUPPORTED
+  else {
+    const PrivilegesMechanismEntry *pme = privilegesMechanismTable;
+    const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount;
+
+    while (pme < end) {
+      cap_value_t capability = pme->capability;
+
+      if (!capability || needCapability(capability, pme->inheritable, pme->reason)) {
+        pme->establishPrivileges(stayPrivileged);
+      }
+
+      pme += 1;
+    }
+  }
+#endif /* CAP_IS_SUPPORTED */
+
+  {
+    const PrivilegesMechanismEntry *pme = privilegesMechanismTable;
+    const PrivilegesMechanismEntry *end = pme + privilegesMechanismCount;
+
+    while (pme < end) {
+      {
+        MissingPrivilegesLogger *log = pme->logMissingPrivileges;
+        if (log) log();
+      }
+
+      {
+        ReleaseResourcesFunction *release = pme->releaseResources;
+        if (release) release();
+      }
+
+      pme += 1;
+    }
+  }
+}
+
+static int
+isEnvironmentVariableSet (const char *name) {
+  const char *value = getenv(name);
+  return value && *value;
+}
+
+static int
+unsetEnvironmentVariable (const char *name) {
+  if (!isEnvironmentVariableSet(name)) return 1;
+
+  if (unsetenv(name) != -1) {
+    logMessage(LOG_DEBUG, "environment variable unset: %s", name);
+    return 1;
+  } else {
+    logSystemError("unsetenv");
+  }
+
+  return 0;
+}
+
+static int
+setEnvironmentVariable (const char *name, const char *value) {
+  if (setenv(name, value, 1) != -1) {
+    logMessage(LOG_DEBUG, "environment variable set: %s: %s", name, value);
+    return 1;
+  } else {
+    logSystemError("setenv");
+  }
+
+  return 0;
+}
+
+static int
+changeEnvironmentVariable (const char *name, const char *value) {
+  if (!isEnvironmentVariableSet(name)) return 1;
+  return setEnvironmentVariable(name, value);
+}
+
+static int
+setHomeDirectory (const char *directory) {
+  if (!directory) return 0;
+  if (!*directory) return 0;
+
+  if (chdir(directory) != -1) {
+    logMessage(LOG_INFO, "%s: %s", gettext("working directory changed"), directory);
+    setEnvironmentVariable("HOME", directory);
+    return 1;
+  } else {
+    logMessage(LOG_WARNING, 
+      "working directory not changed: %s: %s",
+      directory, strerror(errno)
+    );
+  }
+
+  return 0;
+}
+
+static int
+setCommandSearchPath (const char *path) {
+  const char *variable = "PATH";
+
+  if (!*path) {
+    int parameter = _CS_PATH;
+    size_t size = confstr(parameter, NULL, 0);
+
+    if (size > 0) {
+      char buffer[size];
+      confstr(parameter, buffer, sizeof(buffer));
+      return setEnvironmentVariable(variable, buffer);
+    }
+
+    path = "/usr/sbin:/sbin:/usr/bin:/bin";
+  }
+
+  return setEnvironmentVariable(variable, path);
+}
+
+static int
+setDefaultShell (const char *shell) {
+  if (!*shell) shell = "/bin/sh";
+  return setEnvironmentVariable("SHELL", shell);
+}
+
+#ifdef HAVE_PWD_H
+static int
+canSwitchGroup (gid_t gid) {
+  {
+    gid_t rGid, eGid, sGid;
+    getresgid(&rGid, &eGid, &sGid);
+    if ((gid == rGid) || (gid == eGid) || (gid == sGid)) return 1;
+  }
+
+  return canSetSupplementaryGroups("for switching to the writable group");
+}
+
+static int
+setXDGRuntimeDirectory (uid_t uid, gid_t gid) {
+  const char *variable = "XDG_RUNTIME_DIR";
+
+  const char *oldPath = getenv(variable);
+  if (!oldPath) return 1;
+  if (!*oldPath) return 1;
+
+  const char *oldName = locatePathName(oldPath);
+  if (!oldName) return 1;
+
+  int length = oldName - oldPath;
+  char newPath[length + 0X20];
+  snprintf(newPath, sizeof(newPath), "%.*s%d", length, oldPath, uid);
+
+  {
+    logMessage(LOG_DEBUG, "checking XDG runtime directory: %s", newPath);
+    int exists = 0;
+
+    if (access(newPath, F_OK) != -1) {
+      exists = 1;
+      logMessage(LOG_DEBUG, "%s: %s", gettext("XDG runtime directory exists"), newPath);
+    } else if (errno == ENOENT) {
+      if (mkdir(newPath, S_IRWXU) != -1) {
+        if (chown(newPath, uid, gid) != 01) {
+          exists = 1;
+          logMessage(LOG_INFO, "%s: %s", gettext("XDG runtime directory created"), newPath);
+        } else {
+          logSystemError("chown");
+        }
+
+        if (!exists) {
+          if (rmdir(newPath) == -1) {
+            logSystemError("rmdir");
+          }
+        }
+      } else {
+        logSystemError("mkdir");
+      }
+    } else {
+      logSystemError("access");
+    }
+
+    if (!exists) {
+      logMessage(LOG_WARNING, "%s: %s", gettext("XDG runtime directory access problem"), newPath);
+    }
+  }
+
+  return setEnvironmentVariable(variable, newPath);
+}
+
+static int
+setProcessOwnership (uid_t uid, gid_t gid) {
+  if (setXDGRuntimeDirectory(uid, gid)) {
+    gid_t oldRgid, oldEgid, oldSgid;
+
+    if (getresgid(&oldRgid, &oldEgid, &oldSgid) != -1) {
+      if (setresgid(gid, gid, gid) != -1) {
+        if (setresuid(uid, uid, uid) != -1) {
+          return 1;
+        } else {
+          logSystemError("setresuid");
+        }
+
+        if (setresgid(oldRgid, oldEgid, oldSgid) == -1) {
+          logSystemError("setresgid");
+        }
+      } else {
+        logSystemError("setresgid");
+      }
+    } else {
+      logSystemError("getresgid");
+    }
+  }
+
+  return 0;
+}
+
+static int
+switchToUser (const char *user, int *haveHomeDirectory) {
+  const struct passwd *pwd;
+
+  if ((pwd = getpwnam(user))) {
+    uid_t uid = pwd->pw_uid;
+    gid_t gid = pwd->pw_gid;
+
+    if (!uid) {
+      logMessage(LOG_WARNING, "not an unprivileged user: %s", user);
+    } else if (setProcessOwnership(uid, gid)) {
+      logMessage(LOG_NOTICE, "%s: %s", gettext("switched to unprivileged user"), user);
+
+      changeEnvironmentVariable("USER", user);
+      changeEnvironmentVariable("LOGNAME", user);
+
+      unsetEnvironmentVariable("XDG_CONFIG_HOME");
+      unsetEnvironmentVariable("XDG_DATA_DIRS");
+
+      if (setHomeDirectory(pwd->pw_dir)) *haveHomeDirectory = 1;
+      forgetOverrideDirectories();
+
+      return 1;
+    }
+  } else {
+    logMessage(LOG_WARNING, "unprivileged user not found: %s", user);
+  }
+
+  return 0;
+}
+
+static int
+switchUser (const char *user, int stayPrivileged, int *haveHomeDirectory) {
+  if (amPrivilegedUser()) {
+    if (stayPrivileged) {
+      logMessage(LOG_NOTICE, "%s", gettext("not switching to an unprivileged user"));
+    } else if (!*user) {
+      logMessage(LOG_DEBUG, "default unprivileged user not configured");
+    } else if (switchToUser(user, haveHomeDirectory)) {
+      return 1;
+    } else {
+      logMessage(LOG_WARNING, "couldn't switch to the unprivileged user: %s", user);
+    }
+  }
+
+  {
+    uid_t uid = getuid();
+    gid_t gid = getgid();
+
+    {
+      const struct passwd *pwd;
+      const char *name;
+      char number[0X10];
+
+      if ((pwd = getpwuid(uid))) {
+        name = pwd->pw_name;
+        if (canSwitchGroup(pwd->pw_gid)) gid = pwd->pw_gid;
+      } else {
+        snprintf(number, sizeof(number), "%d", uid);
+        name = number;
+      }
+
+      logMessage(LOG_NOTICE, "%s: %s", gettext("executing as the invoking user"), name);
+    }
+
+    setProcessOwnership(uid, gid);
+    if (!amPrivilegedUser()) *haveHomeDirectory = 1;
+  }
+
+  return 0;
+}
+
+static const char *
+getSocketsDirectory (void) {
+  const char *path = BRLAPI_SOCKETPATH;
+  if (!ensureDirectory(path, 1)) path = NULL;
+  return path;
+}
+
+typedef struct {
+  const char *whichDirectory;
+  const char *(*getPath) (void);
+  const char *expectedName;
+} StateDirectoryEntry;
+
+static const StateDirectoryEntry stateDirectoryTable[] = {
+  { .whichDirectory = "updatable",
+    .getPath = getUpdatableDirectory,
+    .expectedName = "brltty",
+  },
+
+  { .whichDirectory = "writable",
+    .getPath = getWritableDirectory,
+    .expectedName = "brltty",
+  },
+
+  { .whichDirectory = "sockets",
+    .getPath = getSocketsDirectory,
+    .expectedName = "BrlAPI",
+  },
+}; static const uint8_t stateDirectoryCount = ARRAY_COUNT(stateDirectoryTable);
+
+static int
+canCreateStateDirectory (void) {
+#ifdef CAP_DAC_OVERRIDE
+  if (needCapability(CAP_DAC_OVERRIDE, 0, "for creating missing state directories")) {
+    return 1;
+  }
+#endif /* CAP_DAC_OVERRIDE */
+
+  return 0;
+}
+
+static const char *
+getStateDirectoryPath (const StateDirectoryEntry *sde) {
+  {
+    const char *path = sde->getPath();
+    if (path) return path;
+  }
+
+  if (!canCreateStateDirectory()) return NULL;
+  return sde->getPath();
+}
+
+static int
+canChangePathOwnership (const char *path) {
+#ifdef CAP_CHOWN
+  if (needCapability(CAP_CHOWN, 0, "for claiming ownership of the state directories")) {
+    return 1;
+  }
+#endif /* CAP_CHOWN */
+
+  return 0;
+}
+
+static int
+canChangePathPermissions (const char *path) {
+#ifdef CAP_FOWNER
+  if (needCapability(CAP_FOWNER, 0, "for adding group permissions to the state directories")) {
+    return 1;
+  }
+#endif /* CAP_FOWNER */
+
+  return 0;
+}
+
+typedef struct {
+  uid_t owningUser;
+  gid_t owningGroup;
+} StateDirectoryData;
+
+static int
+claimStateDirectory (const PathProcessorParameters *parameters) {
+  const StateDirectoryData *sdd = parameters->data;
+  const char *path = parameters->path;
+  uid_t user = sdd->owningUser;
+  gid_t group = sdd->owningGroup;
+  struct stat status;
+
+  if (stat(path, &status) != -1) {
+    int ownershipClaimed = 0;
+
+    if ((status.st_uid == user) && (status.st_gid == group)) {
+      ownershipClaimed = 1;
+    } else if (!canChangePathOwnership(path)) {
+      logMessage(LOG_WARNING, "can't claim ownership: %s", path);
+    } else if (chown(path, user, group) == -1) {
+      logSystemError("chown");
+    } else {
+      logMessage(LOG_INFO, "%s: %s", gettext("ownership claimed"), path);
+      ownershipClaimed = 1;
+    }
+
+    if (ownershipClaimed) {
+      mode_t oldMode = status.st_mode;
+      mode_t newMode = oldMode;
+
+      newMode |= S_IRGRP | S_IWGRP;
+      if (S_ISDIR(newMode)) newMode |= S_IXGRP | S_ISGID;
+
+      if (newMode != oldMode) {
+        if (!canChangePathPermissions(path)) {
+          logMessage(LOG_WARNING, "can't add group permissions: %s", path);
+        } else if (chmod(path, newMode) != -1) {
+          logMessage(LOG_INFO, "%s: %s", gettext("group permissions added"), path);
+        } else {
+          logSystemError("chmod");
+        }
+      }
+    }
+  } else {
+    logSystemError("stat");
+  }
+
+  return 1;
+}
+
+static void
+claimStateDirectories (void) {
+  StateDirectoryData sdd = {
+    .owningUser = geteuid(),
+    .owningGroup = getegid(),
+  };
+
+  const StateDirectoryEntry *sde = stateDirectoryTable;
+  const StateDirectoryEntry *end = sde + stateDirectoryCount;
+
+  while (sde < end) {
+    const char *path = getStateDirectoryPath(sde);
+
+    if (path && *path) {
+      const char *name = locatePathName(path);
+
+      if (strcasecmp(name, sde->expectedName) == 0) {
+        processPathTree(path, claimStateDirectory, &sdd);
+      } else {
+        logMessage(LOG_DEBUG,
+          "not claiming %s directory: %s (expecting %s)",
+          sde->whichDirectory, path, sde->expectedName
+        );
+      }
+    }
+
+    sde += 1;
+  }
+}
+#endif /* HAVE_PWD_H */
+
+typedef enum {
+  PARM_PATH,
+  PARM_SCFMODE,
+  PARM_SHELL,
+  PARM_USER,
+} Parameters;
+
+
+static const char *const *const privilegeParameterNames =
+  NULL_TERMINATED_STRING_ARRAY(
+    "path", "scfmode", "shell", "user"
+  );
+
+const char *const *
+getPrivilegeParameterNames (void) {
+  return privilegeParameterNames;
+}
+
+const char *
+getPrivilegeParametersPlatform (void) {
+  return "lx";
+}
+
+void
+establishProgramPrivileges (char **parameters, int stayPrivileged) {
+  logCurrentCapabilities("at start");
+
+  setCommandSearchPath(parameters[PARM_PATH]);
+  setDefaultShell(parameters[PARM_SHELL]);
+
+#ifdef PR_SET_KEEPCAPS
+  if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+    logSystemError("prctl[PR_SET_KEEPCAPS]");
+  }
+#endif /* PR_SET_KEEPCAPS */
+
+#ifdef HAVE_SCHED_H
+  isolateNamespaces();
+#endif /* HAVE_SCHED_H */
+
+  {
+    const char *unprivilegedUser = parameters[PARM_USER];
+    int haveHomeDirectory = 0;
+
+#ifdef HAVE_PWD_H
+    int switched = switchUser(
+      unprivilegedUser,
+      stayPrivileged,
+      &haveHomeDirectory
+    );
+
+    if (switched) {
+      umask(umask(0) & ~S_IRWXG);
+      claimStateDirectories();
+    } else {
+      logMessage(LOG_DEBUG, "not claiming state directories");
+    }
+
+    endpwent();
+#endif /* HAVE_PWD_H */
+
+    if (!haveHomeDirectory) {
+      if (!setHomeDirectory(getUpdatableDirectory())) {
+        logMessage(LOG_WARNING, "home directory not set");
+      }
+    }
+  }
+
+  establishPrivileges(stayPrivileged);
+  logCurrentCapabilities("after relinquish");
+
+#ifdef SECCOMP_MODE_FILTER
+  scfInstallFilter(parameters[PARM_SCFMODE]);
+#endif /* SECCOMP_MODE_FILTER */
+}
diff --git a/Programs/pgmprivs_none.c b/Programs/pgmprivs_none.c
new file mode 100644
index 0000000..f22b52f
--- /dev/null
+++ b/Programs/pgmprivs_none.c
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "pgmprivs.h"
+
+const char *const *
+getPrivilegeParameterNames (void) {
+  static const char *const names[] = {NULL};
+  return names;
+}
+
+const char *
+getPrivilegeParametersPlatform (void) {
+  return "no";
+}
+
+void
+establishProgramPrivileges (char **parameters, int stayPrivileged) {
+}
diff --git a/Programs/pid.c b/Programs/pid.c
new file mode 100644
index 0000000..b2b7e81
--- /dev/null
+++ b/Programs/pid.c
@@ -0,0 +1,84 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "pid.h"
+
+#if defined(__MSDOS__) || defined(GRUB_RUNTIME)
+ProcessIdentifier
+getProcessIdentifier (void) {
+  return MY_PROCESS_ID;
+}
+
+int
+testProcessIdentifier (ProcessIdentifier pid) {
+  return pid == MY_PROCESS_ID;
+}
+
+int
+cancelProcess (ProcessIdentifier pid) {
+  errno = ENOSYS;
+  return 0;
+}
+
+#elif defined(__MINGW32__)
+ProcessIdentifier
+getProcessIdentifier (void) {
+  return GetCurrentProcessId();
+}
+
+int
+testProcessIdentifier (ProcessIdentifier pid) {
+  HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+  if (!handle) return 0;
+  CloseHandle(handle);
+  return 1;
+}
+
+int
+cancelProcess (ProcessIdentifier pid) {
+  errno = ENOSYS;
+  return 0;
+}
+
+#else /* Unix */
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif /* HAVE_SIGNAL_H */
+
+ProcessIdentifier
+getProcessIdentifier (void) {
+  return getpid();
+}
+
+int
+testProcessIdentifier (ProcessIdentifier pid) {
+  return kill(pid, 0) != -1;
+}
+
+int
+cancelProcess (ProcessIdentifier pid) {
+  if (kill(pid, SIGTERM) != -1) return 1;
+  return 0;
+}
+
+#endif /* pid support */
diff --git a/Programs/pipe.c b/Programs/pipe.c
new file mode 100644
index 0000000..c3616e4
--- /dev/null
+++ b/Programs/pipe.c
@@ -0,0 +1,362 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "pipe.h"
+#include "file.h"
+#include "io_misc.h"
+#include "async_handle.h"
+#include "async_io.h"
+
+struct NamedPipeObjectStruct {
+  NamedPipeInputCallback *callback;
+  void *data;
+
+  int (*createPipe) (NamedPipeObject *obj);
+  int (*monitorPipe) (NamedPipeObject *obj);
+  void (*resetPipe) (NamedPipeObject *obj);
+  void (*releaseResources) (NamedPipeObject *obj);
+
+  struct {
+    char *path;
+  } host;
+
+  struct {
+    FileDescriptor descriptor;
+    AsyncHandle monitor;
+  } input;
+
+#if defined(__MINGW32__)
+  struct {
+    struct {
+      AsyncHandle monitor;
+      HANDLE event;
+      OVERLAPPED overlapped;
+    } connect;
+  } windows;
+
+#endif /*  */
+};
+
+static void
+initializeHostPath (NamedPipeObject *obj) {
+  obj->host.path = NULL;
+}
+
+static void
+deallocateHostPath (NamedPipeObject *obj) {
+  free(obj->host.path);
+  initializeHostPath(obj);
+}
+
+static void
+removePipe (NamedPipeObject *obj) {
+  unlink(obj->host.path);
+}
+
+static void
+initializeInputDescriptor (NamedPipeObject *obj) {
+  obj->input.descriptor = INVALID_FILE_DESCRIPTOR;
+}
+
+static void
+closeInputDescriptor (NamedPipeObject *obj) {
+  closeFileDescriptor(obj->input.descriptor);
+  initializeInputDescriptor(obj);
+}
+
+static void
+initializeInputMonitor (NamedPipeObject *obj) {
+  obj->input.monitor = NULL;
+}
+
+static void
+stopInputMonitor (NamedPipeObject *obj) {
+  asyncCancelRequest(obj->input.monitor);
+  initializeInputMonitor(obj);
+}
+
+ASYNC_INPUT_CALLBACK(handleNamedPipeInput) {
+  NamedPipeObject *obj = parameters->data;
+
+  if (parameters->error) {
+    logMessage(LOG_WARNING, "named pipe input error: %s: %s",
+               obj->host.path, strerror(parameters->error));
+  } else if (parameters->end) {
+    logMessage(LOG_WARNING, "named pipe end-of-file: %s", obj->host.path);
+  } else {
+    const NamedPipeInputCallbackParameters input = {
+      .buffer = parameters->buffer,
+      .length = parameters->length,
+      .data = obj->data
+    };
+
+    return obj->callback(&input);
+  }
+
+  asyncDiscardHandle(obj->input.monitor);
+  initializeInputMonitor(obj);
+
+  if (obj->resetPipe) obj->resetPipe(obj);
+  obj->monitorPipe(obj);
+
+  return 0;
+}
+
+static int
+monitorInput (NamedPipeObject *obj) {
+  if (!obj->input.monitor) {
+    if (!asyncReadFile(&obj->input.monitor, obj->input.descriptor, 0X1000, handleNamedPipeInput, obj)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+#if defined(__MINGW32__)
+static int
+createWindowsPipe (NamedPipeObject *obj) {
+  obj->windows.connect.monitor = NULL;
+
+  if ((obj->windows.connect.event = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+    if ((obj->input.descriptor = CreateNamedPipe(obj->host.path,
+                                                 PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
+                                                 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
+                                                 1, 0, 0, 0, NULL)) != INVALID_HANDLE_VALUE) {
+      logMessage(LOG_DEBUG, "named pipe created: %s: handle=%u",
+                 obj->host.path, (unsigned int)obj->input.descriptor);
+
+      return 1;
+    } else {
+      logWindowsSystemError("CreateNamedPipe");
+    }
+
+    CloseHandle(obj->windows.connect.event);
+  } else {
+    logWindowsSystemError("CreateEvent");
+  }
+
+  return 0;
+}
+
+static int
+doWindowsPipeConnected (NamedPipeObject *obj) {
+  return monitorInput(obj);
+}
+
+ASYNC_MONITOR_CALLBACK(handleWindowsPipeConnected) {
+  NamedPipeObject *obj = parameters->data;
+
+  asyncDiscardHandle(obj->windows.connect.monitor);
+  obj->windows.connect.monitor = NULL;
+
+  doWindowsPipeConnected(obj);
+  return 0;
+}
+
+static int
+monitorWindowsPipeConnect (NamedPipeObject *obj) {
+  if (ResetEvent(obj->windows.connect.event)) {
+    ZeroMemory(&obj->windows.connect.overlapped, sizeof(obj->windows.connect.overlapped));
+    obj->windows.connect.overlapped.hEvent = obj->windows.connect.event;
+
+    if (ConnectNamedPipe(obj->input.descriptor, &obj->windows.connect.overlapped)) {
+      if (doWindowsPipeConnected(obj)) {
+        return 1;
+      }
+    } else {
+      DWORD error = GetLastError();
+
+      if (error == ERROR_PIPE_CONNECTED) {
+        if (doWindowsPipeConnected(obj)) {
+          return 1;
+        }
+      } else if (error == ERROR_IO_PENDING) {
+        if (asyncMonitorFileInput(&obj->windows.connect.monitor, obj->windows.connect.event, handleWindowsPipeConnected, obj)) {
+          return 1;
+        }
+      } else {
+        logWindowsError(error, "ConnectNamedPipe");
+      }
+    }
+  } else {
+    logWindowsSystemError("ResetEvent");
+  }
+
+  return 0;
+}
+
+static void
+disconnectWindowsPipe (NamedPipeObject *obj) {
+  if (!DisconnectNamedPipe(obj->input.descriptor)) {
+    logWindowsSystemError("DisconnectNamedPipe");
+  }
+}
+
+static void
+releaseWindowsResources (NamedPipeObject *obj) {
+  if (obj->windows.connect.monitor) {
+    asyncCancelRequest(obj->windows.connect.monitor);
+    obj->windows.connect.monitor = NULL;
+  }
+
+  if (obj->windows.connect.event) {
+    CloseHandle(obj->windows.connect.event);
+    obj->windows.connect.event = NULL;
+  }
+}
+
+static void
+setNamedPipeMethods (NamedPipeObject *obj) {
+  obj->createPipe = createWindowsPipe;
+  obj->monitorPipe = monitorWindowsPipeConnect;
+  obj->resetPipe = disconnectWindowsPipe;
+  obj->releaseResources = releaseWindowsResources;
+}
+
+#elif defined(S_ISFIFO)
+static int
+createFifo (NamedPipeObject *obj) {
+  lockUmask();
+  int result = mkfifo(obj->host.path, 0);
+  unlockUmask();
+
+  if ((result == -1) && (errno == EEXIST)) {
+    struct stat fifo;
+
+    if (lstat(obj->host.path, &fifo) == -1) {
+      logMessage(LOG_ERR, "cannot stat FIFO: %s: %s",
+                 obj->host.path, strerror(errno));
+    } else if (S_ISFIFO(fifo.st_mode)) {
+      result = 0;
+    }
+  }
+
+  if (result != -1) {
+    lockUmask();
+    int changed = chmod(obj->host.path, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH) != -1;
+    unlockUmask();
+
+    if (changed) {
+      // open read-write even though we only read to prevent an end-of-file condition
+      if ((obj->input.descriptor = open(obj->host.path, O_RDWR|O_NONBLOCK)) != -1) {
+        logMessage(LOG_DEBUG, "FIFO created: %s: fd=%d",
+                   obj->host.path, obj->input.descriptor);
+
+        setCloseOnExec(obj->input.descriptor, 1);
+        return 1;
+      } else {
+        logMessage(LOG_ERR, "cannot open FIFO: %s: %s",
+                   obj->host.path, strerror(errno));
+      }
+    } else {
+      logMessage(LOG_ERR, "cannot set FIFO permissions: %s: %s",
+                 obj->host.path, strerror(errno));
+    }
+
+    removePipe(obj);
+  } else {
+    logMessage(LOG_ERR, "cannot create FIFO: %s: %s",
+               obj->host.path, strerror(errno));
+  }
+
+  return 0;
+}
+
+static void
+setNamedPipeMethods (NamedPipeObject *obj) {
+  obj->createPipe = createFifo;
+}
+
+#else /* named pipe functions */
+#warning named pipes not supported on this platform
+
+static void
+setNamedPipeMethods (NamedPipeObject *obj) {
+}
+#endif /* named pipes functions */
+
+NamedPipeObject *
+newNamedPipeObject (const char *name, NamedPipeInputCallback *callback, void *data) {
+  NamedPipeObject *obj;
+
+  if ((obj = malloc(sizeof(*obj)))) {
+    memset(obj, 0, sizeof(*obj));
+
+    obj->callback = callback;
+    obj->data = data;
+
+    obj->createPipe = NULL;
+    obj->monitorPipe = monitorInput;
+    obj->resetPipe = NULL;
+    obj->releaseResources = NULL;
+    setNamedPipeMethods(obj);
+
+    initializeHostPath(obj);
+    initializeInputDescriptor(obj);
+    initializeInputMonitor(obj);
+
+    {
+      const char *directory = getNamedPipeDirectory();
+
+      obj->host.path = directory? makePath(directory, name): NULL;
+    }
+
+    if (obj->host.path) {
+      if (!obj->createPipe) {
+        logUnsupportedOperation("create named pipe");
+      } else if (obj->createPipe(obj)) {
+        if (!obj->monitorPipe) {
+          logUnsupportedOperation("monitor named pipe");
+        } else if (obj->monitorPipe(obj)) {
+          return obj;
+        }
+
+        closeInputDescriptor(obj);
+        removePipe(obj);
+      }
+
+      deallocateHostPath(obj);
+    }
+
+    free(obj);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyNamedPipeObject (NamedPipeObject *obj) {
+  logMessage(LOG_DEBUG, "destroying named pipe: %s", obj->host.path);
+  if (obj->releaseResources) obj->releaseResources(obj);
+  stopInputMonitor(obj);
+  closeInputDescriptor(obj);
+  removePipe(obj);
+  deallocateHostPath(obj);
+  free(obj);
+}
diff --git a/Programs/pipe.h b/Programs/pipe.h
new file mode 100644
index 0000000..25c0342
--- /dev/null
+++ b/Programs/pipe.h
@@ -0,0 +1,44 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PIPE
+#define BRLTTY_INCLUDED_PIPE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct NamedPipeObjectStruct NamedPipeObject;
+
+typedef struct {
+  const unsigned char *buffer;
+  size_t length;
+  void *data;
+} NamedPipeInputCallbackParameters;
+
+#define NAMED_PIPE_INPUT_CALLBACK(name) size_t name (const NamedPipeInputCallbackParameters *parameters)
+typedef NAMED_PIPE_INPUT_CALLBACK(NamedPipeInputCallback);
+
+extern NamedPipeObject *newNamedPipeObject (const char *name, NamedPipeInputCallback *callback, void *data);
+extern void destroyNamedPipeObject (NamedPipeObject *obj);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PIPE */
diff --git a/Programs/ports_glibc.c b/Programs/ports_glibc.c
new file mode 100644
index 0000000..2f687cb
--- /dev/null
+++ b/Programs/ports_glibc.c
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_IO_H
+#include <sys/io.h>
+#endif /* HAVE_SYS_IO_H */
+
+#include "log.h"
+#include "ports.h"
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+#ifdef HAVE_SYS_IO_H
+  if (ioperm(base, count, 1) != -1) return 1;
+  logMessage(errorLevel, "Port enable error: %u.%u: %s", base, count, strerror(errno));
+#else /* HAVE_SYS_IO_H */
+  logMessage(errorLevel, "I/O ports not supported.");
+#endif /* HAVE_SYS_IO_H */
+  return 0;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+#ifdef HAVE_SYS_IO_H
+  if (ioperm(base, count, 0) != -1) return 1;
+  logMessage(LOG_ERR, "Port disable error: %u.%u: %s", base, count, strerror(errno));
+#endif /* HAVE_SYS_IO_H */
+  return 0;
+}
+
+unsigned char
+readPort1 (unsigned short int port) {
+#ifdef HAVE_SYS_IO_H
+  return inb(port);
+#else /* HAVE_SYS_IO_H */
+  return 0;
+#endif /* HAVE_SYS_IO_H */
+}
+
+void
+writePort1 (unsigned short int port, unsigned char value) {
+#ifdef HAVE_SYS_IO_H
+  outb(value, port);
+#endif /* HAVE_SYS_IO_H */
+}
diff --git a/Programs/ports_grub.c b/Programs/ports_grub.c
new file mode 100644
index 0000000..4004a2f
--- /dev/null
+++ b/Programs/ports_grub.c
@@ -0,0 +1,43 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <grub/cpu/io.h>
+
+#include "ports.h"
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+  return 1;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+  return 1;
+}
+
+unsigned char
+readPort1 (unsigned short int port) {
+  return grub_inb(port);
+}
+
+void
+writePort1 (unsigned short int port, unsigned char value) {
+  grub_outb(value, port);
+}
diff --git a/Programs/ports_kfreebsd.c b/Programs/ports_kfreebsd.c
new file mode 100644
index 0000000..a7241f2
--- /dev/null
+++ b/Programs/ports_kfreebsd.c
@@ -0,0 +1,65 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_IO_H
+#include <sys/io.h>
+#endif /* HAVE_SYS_IO_H */
+
+#include "log.h"
+#include "ports.h"
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+#ifdef HAVE_SYS_IO_H
+  if (iopl(3) != -1) return 1;
+  logMessage(errorLevel, "Port enable error: %u.%u: %s", base, count, strerror(errno));
+#else /* HAVE_SYS_IO_H */
+  logMessage(errorLevel, "I/O ports not supported.");
+#endif /* HAVE_SYS_IO_H */
+  return 0;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+#ifdef HAVE_SYS_IO_H
+  if (iopl(0) != -1) return 1;
+  logMessage(LOG_ERR, "Port disable error: %u.%u: %s", base, count, strerror(errno));
+#endif /* HAVE_SYS_IO_H */
+  return 0;
+}
+
+unsigned char
+readPort1 (unsigned short int port) {
+#ifdef HAVE_SYS_IO_H
+  return inb(port);
+#else /* HAVE_SYS_IO_H */
+  return 0;
+#endif /* HAVE_SYS_IO_H */
+}
+
+void
+writePort1 (unsigned short int port, unsigned char value) {
+#ifdef HAVE_SYS_IO_H
+  outb(value, port);
+#endif /* HAVE_SYS_IO_H */
+}
diff --git a/Programs/ports_msdos.c b/Programs/ports_msdos.c
new file mode 100644
index 0000000..a94806a
--- /dev/null
+++ b/Programs/ports_msdos.c
@@ -0,0 +1,33 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ports.h"
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+  return 1;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+  return 1;
+}
+
+#include "ports_x86.h"
diff --git a/Programs/ports_none.c b/Programs/ports_none.c
new file mode 100644
index 0000000..e054b47
--- /dev/null
+++ b/Programs/ports_none.c
@@ -0,0 +1,42 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "ports.h"
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+  logMessage(errorLevel, "I/O ports not supported.");
+  return 0;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+  return 0;
+}
+
+unsigned char
+readPort1 (unsigned short int port) {
+  return 0;
+}
+
+void
+writePort1 (unsigned short int port, unsigned char value) {
+}
diff --git a/Programs/ports_windows.c b/Programs/ports_windows.c
new file mode 100644
index 0000000..ebc4e54
--- /dev/null
+++ b/Programs/ports_windows.c
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ports.h"
+#include "system_windows.h"
+
+#define USE_PORTS_X86
+
+static int portsEnabled = 0;
+
+int
+enablePorts (int errorLevel, unsigned short int base, unsigned short int count) {
+  if (!portsEnabled && NtSetInformationProcessProc) {
+    ULONG Iopl=3;
+    if (NtSetInformationProcessProc(GetCurrentProcess(), ProcessUserModeIOPL,
+                                &Iopl, sizeof(Iopl)) != STATUS_SUCCESS) {
+      return 0;
+    }
+    portsEnabled = 1;
+  }
+  return 1;
+}
+
+int
+disablePorts (unsigned short int base, unsigned short int count) {
+  return 1;
+}
+
+#include "ports_x86.h"
diff --git a/Programs/ports_x86.h b/Programs/ports_x86.h
new file mode 100644
index 0000000..15603bc
--- /dev/null
+++ b/Programs/ports_x86.h
@@ -0,0 +1,29 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+unsigned char
+readPort1 (unsigned short int port) {
+  unsigned char v;
+  __asm__ __volatile__ ("inb %w1,%0" : "=a" (v) : "Nd" (port));
+  return v;
+}
+
+void
+writePort1 (unsigned short int port, unsigned char value) {
+  __asm__ __volatile__ ("outb %b0,%w1" : : "a" (value), "Nd" (port));
+}
diff --git a/Programs/pref_tables.c b/Programs/pref_tables.c
new file mode 100644
index 0000000..bf909f4
--- /dev/null
+++ b/Programs/pref_tables.c
@@ -0,0 +1,739 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "prefs.h"
+#include "pref_tables.h"
+#include "status_types.h"
+#include "defaults.h"
+
+PreferenceSettings prefs;                /* environment (i.e. global) parameters */
+unsigned char statusFieldsSet;
+
+#define PREFERENCE_STRING_TABLE(name, ...) \
+static const char *const preferenceStringArray_##name[] = {__VA_ARGS__}; \
+static const PreferenceStringTable preferenceStringTable_##name = { \
+  .table = preferenceStringArray_##name, \
+  .count = ARRAY_COUNT(preferenceStringArray_##name) \
+};
+
+PREFERENCE_STRING_TABLE(boolean,
+  "no", "yes"
+)
+
+PREFERENCE_STRING_TABLE(textStyle,
+  [bvComputer8] = "8dot",
+  [bvContracted6] = "contracted",
+  [bvComputer6] = "6dot",
+  [bvContracted8] = "literary"
+)
+
+PREFERENCE_STRING_TABLE(brailleVariant,
+  [bvComputer8] = "computer8",
+  [bvContracted6] = "contracted6",
+  [bvComputer6] = "computer6",
+  [bvContracted8] = "contracted8"
+)
+
+PREFERENCE_STRING_TABLE(capitalizationMode,
+  [CTB_CAP_NONE] = "none",
+  [CTB_CAP_SIGN] = "sign",
+  [CTB_CAP_DOT7] = "dot7",
+)
+
+PREFERENCE_STRING_TABLE(skipBlankWindowsMode,
+  [sbwAll] = "all",
+  [sbwEndOfLine] = "end",
+  [sbwRestOfLine] = "rest",
+)
+
+PREFERENCE_STRING_TABLE(cursorTrackingDelay,
+  [ctdNone] = "0",
+  [ctd250ms] = "25",
+  [ctd500ms] = "50",
+  [ctd1s] = "100",
+  [ctd2s] = "200",
+)
+
+PREFERENCE_STRING_TABLE(autoreleaseTime,
+  [atOff] = "0",
+  [at5s] = "5",
+  [at10s] = "10",
+  [at20s] = "20",
+  [at40s] = "40",
+)
+
+PREFERENCE_STRING_TABLE(cursorStyle,
+  [csBottomDots] = "underline",
+  [csAllDots] = "block",
+  [csLowerLeftDot] = "dot7",
+  [csLowerRightDot] = "dot8",
+  [csNoDots] = "hide",
+)
+
+PREFERENCE_STRING_TABLE(brailleFirmness,
+  [BRL_FIRMNESS_MINIMUM] = "minimum",
+  [BRL_FIRMNESS_LOW] = "low",
+  [BRL_FIRMNESS_MEDIUM] = "medium",
+  [BRL_FIRMNESS_HIGH] = "high",
+  [BRL_FIRMNESS_MAXIMUM] = "maximum",
+)
+
+PREFERENCE_STRING_TABLE(touchSensitivity,
+  [BRL_SENSITIVITY_MINIMUM] = "minimum",
+  [BRL_SENSITIVITY_LOW] = "low",
+  [BRL_SENSITIVITY_MEDIUM] = "medium",
+  [BRL_SENSITIVITY_HIGH] = "high",
+  [BRL_SENSITIVITY_MAXIMUM] = "maximum"
+)
+
+PREFERENCE_STRING_TABLE(brailleTypingMode,
+  [BRL_TYPING_TEXT] = "text",
+  [BRL_TYPING_DOTS] = "dots",
+)
+
+PREFERENCE_STRING_TABLE(tuneDevice,
+  [tdBeeper] = "beeper",
+  [tdPcm] = "pcm",
+  [tdMidi] = "midi",
+  [tdFm] = "fm",
+)
+
+PREFERENCE_STRING_TABLE(speechPunctuation,
+  [SPK_PUNCTUATION_NONE] = "none",
+  [SPK_PUNCTUATION_SOME] = "some",
+  [SPK_PUNCTUATION_ALL] = "all",
+)
+
+PREFERENCE_STRING_TABLE(speechUppercaseIndicator,
+  [sucNone] = "none",
+  [sucSayCap] = "cap",
+  [sucRaisePitch] = "higher",
+)
+
+PREFERENCE_STRING_TABLE(speechWhitespaceIndicator,
+  [swsNone] = "none",
+  [swsSaySpace] = "space",
+)
+
+PREFERENCE_STRING_TABLE(sayLineMode,
+  [sayImmediate] = "immediate",
+  [sayEnqueue] = "enqueue",
+)
+
+PREFERENCE_STRING_TABLE(timeFormat,
+  [tf24Hour] = "24hour",
+  [tf12Hour] = "12hour",
+)
+
+PREFERENCE_STRING_TABLE(timeSeparator,
+  [tsColon] = "colon",
+  [tsDot] = "dot",
+)
+
+PREFERENCE_STRING_TABLE(datePosition,
+  [dpNone] = "no",
+  [dpBeforeTime] = "before",
+  [dpAfterTime] = "after",
+)
+
+PREFERENCE_STRING_TABLE(dateFormat,
+  [dfYearMonthDay] = "ymd",
+  [dfMonthDayYear] = "mdy",
+  [dfDayMonthYear] = "dmy",
+)
+
+PREFERENCE_STRING_TABLE(dateSeparator,
+  [dsDash] = "dash",
+  [dsSlash] = "slash",
+  [dsDot] = "dot",
+)
+
+PREFERENCE_STRING_TABLE(statusPosition,
+  [spNone] = "none",
+  [spLeft] = "left",
+  [spRight] = "right",
+)
+
+PREFERENCE_STRING_TABLE(statusSeparator,
+  [ssNone] = "none",
+  [ssSpace] = "space",
+  [ssBlock] = "block",
+  [ssStatusSide] = "status",
+  [ssTextSide] = "text",
+)
+
+PREFERENCE_STRING_TABLE(statusField,
+  [sfEnd] = "end",
+  [sfWindowCoordinates2] = "wxy",
+  [sfWindowColumn] = "wx",
+  [sfWindowRow] = "wy",
+  [sfCursorCoordinates2] = "cxy",
+  [sfCursorColumn] = "cx",
+  [sfCursorRow] = "cy",
+  [sfCursorAndWindowColumn2] = "cwx",
+  [sfCursorAndWindowRow2] = "cwy",
+  [sfScreenNumber] = "sn",
+  [sfStateDots] = "dots",
+  [sfStateLetter] = "letter",
+  [sfTime] = "time",
+  [sfAlphabeticWindowCoordinates] = "wxya",
+  [sfAlphabeticCursorCoordinates] = "cxya",
+  [sfGeneric] = "generic",
+  [sfCursorCoordinates3] = "cxy3",
+  [sfWindowCoordinates3] = "wxy3",
+  [sfCursorAndWindowColumn3] = "cwx3",
+  [sfCursorAndWindowRow3] = "cwy3",
+  [sfSpace] = "space",
+)
+
+const PreferenceDefinition preferenceDefinitionTable[] = {
+  { .name = "save-on-exit",
+    .defaultValue = DEFAULT_SAVE_ON_EXIT,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.saveOnExit
+  },
+
+  { .name = "show-submenu-sizes",
+    .defaultValue = DEFAULT_SHOW_SUBMENU_SIZES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showSubmenuSizes
+  },
+
+  { .name = "show-advanced-submenus",
+    .defaultValue = DEFAULT_SHOW_ADVANCED_SUBMENUS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showAdvancedSubmenus
+  },
+
+  { .name = "show-all-items",
+    .defaultValue = DEFAULT_SHOW_ALL_ITEMS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showAllItems
+  },
+
+  // text-style is an old entry which should be before braille-variant
+  { .name = "text-style",
+    .dontSave = 1,
+    .defaultValue = DEFAULT_BRAILLE_VARIANT,
+    .settingNames = &preferenceStringTable_textStyle,
+    .setting = &prefs.brailleVariant
+  },
+
+  // braille-variant is a new entry which should be after text-style
+  { .name = "braille-variant",
+    .defaultValue = DEFAULT_BRAILLE_VARIANT,
+    .settingNames = &preferenceStringTable_brailleVariant,
+    .setting = &prefs.brailleVariant
+  },
+
+  { .name = "expand-current-word",
+    .defaultValue = DEFAULT_EXPAND_CURRENT_WORD,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.expandCurrentWord
+  },
+
+  { .name = "capitalization-mode",
+    .defaultValue = DEFAULT_CAPITALIZATION_MODE,
+    .settingNames = &preferenceStringTable_capitalizationMode,
+    .setting = &prefs.capitalizationMode
+  },
+
+  { .name = "braille-firmness",
+    .defaultValue = DEFAULT_BRAILLE_FIRMNESS,
+    .settingNames = &preferenceStringTable_brailleFirmness,
+    .setting = &prefs.brailleFirmness
+  },
+
+  { .name = "show-screen-cursor",
+    .defaultValue = DEFAULT_SHOW_SCREEN_CURSOR,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showScreenCursor
+  },
+
+  { .name = "screen-cursor-style",
+    .defaultValue = DEFAULT_SCREEN_CURSOR_STYLE,
+    .settingNames = &preferenceStringTable_cursorStyle,
+    .setting = &prefs.screenCursorStyle
+  },
+
+  { .name = "blinking-screen-cursor",
+    .defaultValue = DEFAULT_BLINKING_SCREEN_CURSOR,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.blinkingScreenCursor
+  },
+
+  { .name = "screen-cursor-visible-time",
+    .defaultValue = DEFAULT_SCREEN_CURSOR_VISIBLE_TIME,
+    .setting = &prefs.screenCursorVisibleTime
+  },
+
+  { .name = "screen-cursor-invisible-time",
+    .defaultValue = DEFAULT_SCREEN_CURSOR_INVISIBLE_TIME,
+    .setting = &prefs.screenCursorInvisibleTime
+  },
+
+  { .name = "show-attributes",
+    .defaultValue = DEFAULT_SHOW_ATTRIBUTES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showAttributes
+  },
+
+  { .name = "blinking-attributes",
+    .defaultValue = DEFAULT_BLINKING_ATTRIBUTES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.blinkingAttributes
+  },
+
+  { .name = "attributes-visible-time",
+    .defaultValue = DEFAULT_ATTRIBUTES_VISIBLE_TIME,
+    .setting = &prefs.attributesVisibleTime
+  },
+
+  { .name = "attributes-invisible-time",
+    .defaultValue = DEFAULT_ATTRIBUTES_INVISIBLE_TIME,
+    .setting = &prefs.attributesInvisibleTime
+  },
+
+  { .name = "blinking-capitals",
+    .defaultValue = DEFAULT_BLINKING_CAPITALS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.blinkingCapitals
+  },
+
+  { .name = "capitals-visible-time",
+    .defaultValue = DEFAULT_CAPITALS_VISIBLE_TIME,
+    .setting = &prefs.capitalsVisibleTime
+  },
+
+  { .name = "capitals-invisible-time",
+    .defaultValue = DEFAULT_CAPITALS_INVISIBLE_TIME,
+    .setting = &prefs.capitalsInvisibleTime
+  },
+
+  { .name = "word-wrap",
+    .defaultValue = DEFAULT_WORD_WRAP,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.wordWrap
+  },
+
+  { .name = "skip-identical-lines",
+    .defaultValue = DEFAULT_SKIP_IDENTICAL_LINES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.skipIdenticalLines
+  },
+
+  { .name = "skip-blank-braille-windows",
+    .defaultValue = DEFAULT_SKIP_BLANK_BRAILLE_WINDOWS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.skipBlankBrailleWindows
+  },
+
+  { .name = "skip-blank-braille-windows-mode",
+    .defaultValue = DEFAULT_SKIP_BLANK_BRAILLE_WINDOWS_MODE,
+    .settingNames = &preferenceStringTable_skipBlankWindowsMode,
+    .setting = &prefs.skipBlankBrailleWindowsMode
+  },
+
+  { .name = "sliding-braille-window",
+    .defaultValue = DEFAULT_SLIDING_BRAILLE_WINDOW,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.slidingBrailleWindow
+  },
+
+  { .name = "eager-sliding-braille-window",
+    .defaultValue = DEFAULT_EAGER_SLIDING_BRAILLE_WINDOW,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.eagerSlidingBrailleWindow
+  },
+
+  { .name = "braille-window-overlap",
+    .defaultValue = DEFAULT_BRAILLE_WINDOW_OVERLAP,
+    .setting = &prefs.brailleWindowOverlap
+  },
+
+  { .name = "scrollaware-cursor-navigation",
+    .defaultValue = DEFAULT_SCROLL_AWARE_CURSOR_NAVIGATION,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.scrollAwareCursorNavigation
+  },
+
+  { .name = "cursor-tracking-delay",
+    .defaultValue = DEFAULT_CURSOR_TRACKING_DELAY,
+    .settingNames = &preferenceStringTable_cursorTrackingDelay,
+    .setting = &prefs.cursorTrackingDelay
+  },
+
+  { .name = "track-screen-scroll",
+    .defaultValue = DEFAULT_TRACK_SCREEN_SCROLL,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.trackScreenScroll
+  },
+
+  { .name = "track-screen-pointer",
+    .defaultValue = DEFAULT_TRACK_SCREEN_POINTER,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.trackScreenPointer
+  },
+
+  { .name = "highlight-braille-window-location",
+    .defaultValue = DEFAULT_HIGHLIGHT_BRAILLE_WINDOW_LOCATION,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.highlightBrailleWindowLocation
+  },
+
+  { .name = "routingkey-start-selection",
+    .defaultValue = DEFAULT_START_SELECTION_WITH_ROUTING_KEY,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.startSelectionWithRoutingKey
+  },
+
+  { .name = "autorelease-time",
+    .defaultValue = DEFAULT_AUTORELEASE_TIME,
+    .settingNames = &preferenceStringTable_autoreleaseTime,
+    .setting = &prefs.autoreleaseTime
+  },
+
+  { .name = "on-first-release",
+    .defaultValue = DEFAULT_ON_FIRST_RELEASE,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.onFirstRelease
+  },
+
+  { .name = "long-press-time",
+    .defaultValue = DEFAULT_LONG_PRESS_TIME,
+    .setting = &prefs.longPressTime
+  },
+
+  { .name = "autorepeat",
+    .defaultValue = DEFAULT_AUTOREPEAT_ENABLED,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autorepeatEnabled
+  },
+
+  { .name = "autorepeat-interval",
+    .defaultValue = DEFAULT_AUTOREPEAT_INTERVAL,
+    .setting = &prefs.autorepeatInterval
+  },
+
+  { .name = "autorepeat-panning",
+    .defaultValue = DEFAULT_AUTOREPEAT_PANNING,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autorepeatPanning
+  },
+
+  { .name = "touch-navigation",
+    .defaultValue = DEFAULT_TOUCH_NAVIGATION,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.touchNavigation
+  },
+
+  { .name = "touch-sensitivity",
+    .defaultValue = DEFAULT_TOUCH_SENSITIVITY,
+    .settingNames = &preferenceStringTable_touchSensitivity,
+    .setting = &prefs.touchSensitivity
+  },
+
+  { .name = "braille-keyboard-enabled",
+    .defaultValue = DEFAULT_BRAILLE_KEYBOARD_ENABLED,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.brailleKeyboardEnabled
+  },
+
+  { .name = "braille-typing-mode",
+    .defaultValue = DEFAULT_BRAILLE_TYPING_MODE,
+    .settingNames = &preferenceStringTable_brailleTypingMode,
+    .setting = &prefs.brailleTypingMode
+  },
+
+  { .name = "braille-quick-space",
+    .defaultValue = DEFAULT_BRAILLE_QUICK_SPACE,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.brailleQuickSpace
+  },
+
+  { .name = "alerts-console-bell",
+    .defaultValue = DEFAULT_CONSOLE_BELL_ALERT,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.consoleBellAlert
+  },
+
+  { .name = "alerts-keyboard-leds",
+    .defaultValue = DEFAULT_KEYBOARD_LED_ALERTS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.keyboardLedAlerts
+  },
+
+  { .name = "speak-key-context",
+    .defaultValue = DEFAULT_SPEAK_KEY_CONTEXT,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.speakKeyContext
+  },
+
+  { .name = "speak-modifier-key",
+    .defaultValue = DEFAULT_SPEAK_MODIFIER_KEY,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.speakModifierKey
+  },
+
+  { .name = "alert-tunes",
+    .defaultValue = DEFAULT_ALERT_TUNES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.alertTunes
+  },
+
+  { .name = "tune-device",
+    .defaultValue = DEFAULT_TUNE_DEVICE,
+    .settingNames = &preferenceStringTable_tuneDevice,
+    .setting = &prefs.tuneDevice
+  },
+
+  { .name = "pcm-volume",
+    .defaultValue = DEFAULT_PCM_VOLUME,
+    .setting = &prefs.pcmVolume
+  },
+
+  { .name = "midi-volume",
+    .defaultValue = DEFAULT_MIDI_VOLUME,
+    .setting = &prefs.midiVolume
+  },
+
+  { .name = "midi-instrument",
+    .defaultValue = DEFAULT_MIDI_INSTRUMENT,
+    .setting = &prefs.midiInstrument
+  },
+
+  { .name = "fm-volume",
+    .defaultValue = DEFAULT_FM_VOLUME,
+    .setting = &prefs.fmVolume
+  },
+
+  { .name = "alert-dots",
+    .defaultValue = DEFAULT_ALERT_DOTS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.alertDots
+  },
+
+  { .name = "alert-messages",
+    .defaultValue = DEFAULT_ALERT_MESSAGES,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.alertMessages
+  },
+
+  { .name = "speech-volume",
+    .defaultValue = DEFAULT_SPEECH_VOLUME,
+    .setting = &prefs.speechVolume
+  },
+
+  { .name = "speech-rate",
+    .defaultValue = DEFAULT_SPEECH_RATE,
+    .setting = &prefs.speechRate
+  },
+
+  { .name = "speech-pitch",
+    .defaultValue = DEFAULT_SPEECH_PITCH,
+    .setting = &prefs.speechPitch
+  },
+
+  { .name = "speech-punctuation",
+    .defaultValue = DEFAULT_SPEECH_PUNCTUATION,
+    .settingNames = &preferenceStringTable_speechPunctuation,
+    .setting = &prefs.speechPunctuation
+  },
+
+  { .name = "speech-uppercase-indicator",
+    .defaultValue = DEFAULT_SPEECH_UPPERCASE_INDICATOR,
+    .settingNames = &preferenceStringTable_speechUppercaseIndicator,
+    .setting = &prefs.speechUppercaseIndicator
+  },
+
+  { .name = "speech-whitespace-indicator",
+    .defaultValue = DEFAULT_SPEECH_WHITESPACE_INDICATOR,
+    .settingNames = &preferenceStringTable_speechWhitespaceIndicator,
+    .setting = &prefs.speechWhitespaceIndicator
+  },
+
+  { .name = "say-line-mode",
+    .defaultValue = DEFAULT_SAY_LINE_MODE,
+    .settingNames = &preferenceStringTable_sayLineMode,
+    .setting = &prefs.sayLineMode
+  },
+
+  { .name = "autospeak",
+    .defaultValue = DEFAULT_AUTOSPEAK,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeak
+  },
+
+  { .name = "autospeak-selected-line",
+    .defaultValue = DEFAULT_AUTOSPEAK_SELECTED_LINE,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakSelectedLine
+  },
+
+  { .name = "autospeak-selected-character",
+    .defaultValue = DEFAULT_AUTOSPEAK_SELECTED_CHARACTER,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakSelectedCharacter
+  },
+
+  { .name = "autospeak-inserted-characters",
+    .defaultValue = DEFAULT_AUTOSPEAK_INSERTED_CHARACTERS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakInsertedCharacters
+  },
+
+  { .name = "autospeak-deleted-characters",
+    .defaultValue = DEFAULT_AUTOSPEAK_DELETED_CHARACTERS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakDeletedCharacters
+  },
+
+  { .name = "autospeak-replaced-characters",
+    .defaultValue = DEFAULT_AUTOSPEAK_REPLACED_CHARACTERS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakReplacedCharacters
+  },
+
+  { .name = "autospeak-completed-words",
+    .defaultValue = DEFAULT_AUTOSPEAK_COMPLETED_WORDS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakCompletedWords
+  },
+
+  { .name = "autospeak-line-indent",
+    .defaultValue = DEFAULT_AUTOSPEAK_LINE_INDENT,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.autospeakLineIndent
+  },
+
+  { .name = "show-speech-cursor",
+    .defaultValue = DEFAULT_SHOW_SPEECH_CURSOR,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showSpeechCursor
+  },
+
+  { .name = "speech-cursor-style",
+    .defaultValue = DEFAULT_SPEECH_CURSOR_STYLE,
+    .settingNames = &preferenceStringTable_cursorStyle,
+    .setting = &prefs.speechCursorStyle
+  },
+
+  { .name = "blinking-speech-cursor",
+    .defaultValue = DEFAULT_BLINKING_SPEECH_CURSOR,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.blinkingSpeechCursor
+  },
+
+  { .name = "speech-cursor-visible-time",
+    .defaultValue = DEFAULT_SPEECH_CURSOR_VISIBLE_TIME,
+    .setting = &prefs.speechCursorVisibleTime
+  },
+
+  { .name = "speech-cursor-invisible-time",
+    .defaultValue = DEFAULT_SPEECH_CURSOR_INVISIBLE_TIME,
+    .setting = &prefs.speechCursorInvisibleTime
+  },
+
+  { .name = "time-format",
+    .defaultValue = DEFAULT_TIME_FORMAT,
+    .settingNames = &preferenceStringTable_timeFormat,
+    .setting = &prefs.timeFormat
+  },
+
+  { .name = "time-separator",
+    .defaultValue = DEFAULT_TIME_SEPARATOR,
+    .settingNames = &preferenceStringTable_timeSeparator,
+    .setting = &prefs.timeSeparator
+  },
+
+  { .name = "show-seconds",
+    .defaultValue = DEFAULT_SHOW_SECONDS,
+    .settingNames = &preferenceStringTable_boolean,
+    .setting = &prefs.showSeconds
+  },
+
+  { .name = "date-position",
+    .defaultValue = DEFAULT_DATE_POSITION,
+    .settingNames = &preferenceStringTable_datePosition,
+    .setting = &prefs.datePosition
+  },
+
+  { .name = "date-format",
+    .defaultValue = DEFAULT_DATE_FORMAT,
+    .settingNames = &preferenceStringTable_dateFormat,
+    .setting = &prefs.dateFormat
+  },
+
+  { .name = "date-separator",
+    .defaultValue = DEFAULT_DATE_SEPARATOR,
+    .settingNames = &preferenceStringTable_dateSeparator,
+    .setting = &prefs.dateSeparator
+  },
+
+  { .name = "status-position",
+    .defaultValue = DEFAULT_STATUS_POSITION,
+    .settingNames = &preferenceStringTable_statusPosition,
+    .setting = &prefs.statusPosition
+  },
+
+  { .name = "status-count",
+    .defaultValue = DEFAULT_STATUS_COUNT,
+    .setting = &prefs.statusCount
+  },
+
+  { .name = "status-separator",
+    .defaultValue = DEFAULT_STATUS_SEPARATOR,
+    .settingNames = &preferenceStringTable_statusSeparator,
+    .setting = &prefs.statusSeparator
+  },
+
+  { .name = "status-fields",
+    .defaultValue = sfEnd,
+    .encountered = &statusFieldsSet,
+    .settingNames = &preferenceStringTable_statusField,
+    .settingCount = ARRAY_COUNT(prefs.statusFields),
+    .setting = prefs.statusFields
+  }
+};
+
+const PreferenceAlias preferenceAliasTable[] = {
+  {.oldName="autorepeat-delay", .newName="long-press-time"},
+  {.oldName="show-cursor", .newName="show-screen-cursor"},
+  {.oldName="cursor-style", .newName="screen-cursor-style"},
+  {.oldName="blinking-cursor", .newName="blinking-screen-cursor"},
+  {.oldName="cursor-visible-time", .newName="screen-cursor-visible-time"},
+  {.oldName="cursor-invisible-time", .newName="screen-cursor-invisible-time"},
+  {.oldName="skip-blank-windows", .newName="skip-blank-braille-windows"},
+  {.oldName="skip-blank-windows-mode", .newName="skip-blank-braille-windows-mode"},
+  {.oldName="sliding-window", .newName="sliding-braille-window"},
+  {.oldName="eager-sliding-window", .newName="eager-sliding-braille-window"},
+  {.oldName="window-overlap", .newName="braille-window-overlap"},
+  {.oldName="window-follows-pointer", .newName="track-screen-pointer"},
+  {.oldName="highlight-window", .newName="highlight-braille-window-location"},
+  {.oldName="uppercase-indicator", .newName="speech-uppercase-indicator"},
+  {.oldName="whitespace-indicator", .newName="speech-whitespace-indicator"},
+  {.oldName="braille-sensitivity", .newName="touch-sensitivity"},
+  {.oldName="braille-input-mode", .newName="braille-typing-mode"},
+  {.oldName="braille-display-orientation"},
+  {.oldName="first-release", .newName="on-first-release"},
+};
+
+const unsigned char preferenceDefinitionCount = ARRAY_COUNT(preferenceDefinitionTable);
+const unsigned char preferenceAliasCount = ARRAY_COUNT(preferenceAliasTable);
diff --git a/Programs/pref_tables.h b/Programs/pref_tables.h
new file mode 100644
index 0000000..059339e
--- /dev/null
+++ b/Programs/pref_tables.h
@@ -0,0 +1,57 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PREF_TABLES
+#define BRLTTY_INCLUDED_PREF_TABLES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *const *table;
+  unsigned char count;
+} PreferenceStringTable;
+
+struct PreferenceDefinitionStruct {
+  const char *name;
+  unsigned char *setting;
+  const PreferenceStringTable *settingNames;
+  unsigned char *encountered;
+  unsigned char settingCount;
+  unsigned char defaultValue;
+  unsigned char dontSave:1;
+};
+
+extern unsigned char statusFieldsSet;
+extern const PreferenceDefinition preferenceDefinitionTable[];
+extern const unsigned char preferenceDefinitionCount;
+
+typedef struct {
+  const char *oldName;
+  const char *newName;
+} PreferenceAlias;
+
+extern const PreferenceAlias preferenceAliasTable[];
+extern const unsigned char preferenceAliasCount;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PREF_TABLES */
diff --git a/Programs/prefs.c b/Programs/prefs.c
new file mode 100644
index 0000000..4b5a205
--- /dev/null
+++ b/Programs/prefs.c
@@ -0,0 +1,585 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "prefs.h"
+#include "pref_tables.h"
+#include "status_types.h"
+#include "defaults.h"
+#include "log.h"
+#include "file.h"
+#include "datafile.h"
+#include "parse.h"
+
+#define PREFS_COMMENT_CHARACTER '#'
+#define PREFS_MAGIC_NUMBER 0x4005
+
+void
+setStatusFields (const unsigned char *fields) {
+  if (!statusFieldsSet) {
+    if (fields) {
+      unsigned int index = 0;
+
+      while (index < (ARRAY_COUNT(prefs.statusFields) - 1)) {
+        unsigned char field = fields[index];
+        if (field == sfEnd) break;
+        prefs.statusFields[index++] = field;
+      }
+
+      statusFieldsSet = 1;
+    }
+  }
+}
+
+static void
+setStatusStyle (unsigned char style) {
+  static const unsigned char styleNone[] = {
+    sfEnd
+  };
+
+  static const unsigned char styleAlva[] = {
+    sfAlphabeticCursorCoordinates, sfAlphabeticWindowCoordinates, sfStateLetter, sfEnd
+  };
+
+  static const unsigned char styleTieman[] = {
+    sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots, sfEnd
+  };
+
+  static const unsigned char stylePB80[] = {
+    sfWindowRow, sfEnd
+  };
+
+  static const unsigned char styleConfigurable[] = {
+    sfGeneric, sfEnd
+  };
+
+  static const unsigned char styleMDV[] = {
+    sfWindowCoordinates2, sfEnd
+  };
+
+  static const unsigned char styleVoyager[] = {
+    sfWindowRow, sfCursorRow, sfCursorColumn, sfEnd
+  };
+
+  static const unsigned char styleTime[] = {
+    sfTime, sfEnd
+  };
+
+  static const unsigned char *const styleTable[] = {
+    styleNone, styleAlva, styleTieman, stylePB80,
+    styleConfigurable, styleMDV, styleVoyager, styleTime
+  };
+  static const unsigned char styleCount = ARRAY_COUNT(styleTable);
+
+  if (style < styleCount) {
+    const unsigned char *fields = styleTable[style];
+    if (*fields != sfEnd) setStatusFields(fields);
+  }
+}
+
+static int
+comparePreferenceNames (const char *name1, const char *name2) {
+  return strcmp(name1, name2);
+}
+
+static int
+sortPreferenceDefinitions (const void *element1, const void *element2) {
+  const PreferenceDefinition *const *pref1 = element1;
+  const PreferenceDefinition *const *pref2 = element2;
+  return comparePreferenceNames((*pref1)->name, (*pref2)->name);
+}
+
+static int
+searchPreferenceDefinition (const void *target, const void *element) {
+  const char *name = target;
+  const PreferenceDefinition *const *pref = element;
+  return comparePreferenceNames(name, (*pref)->name);
+}
+
+static const PreferenceDefinition *
+findPreferenceDefinition (const char *name) {
+  static const PreferenceDefinition **sortedDefinitions = NULL;
+
+  if (!sortedDefinitions) {
+    if (!(sortedDefinitions = malloc(ARRAY_SIZE(sortedDefinitions, preferenceDefinitionCount)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    for (unsigned int index=0; index<preferenceDefinitionCount; index+=1) {
+      sortedDefinitions[index] = &preferenceDefinitionTable[index];
+    }
+
+    qsort(
+      sortedDefinitions, preferenceDefinitionCount,
+      sizeof(*sortedDefinitions), sortPreferenceDefinitions
+    );
+  }
+
+  {
+    const PreferenceDefinition *const *pref = bsearch(
+      name, sortedDefinitions, preferenceDefinitionCount,
+      sizeof(*sortedDefinitions), searchPreferenceDefinition
+    );
+
+    if (pref) return *pref;
+  }
+
+  return NULL;
+}
+
+static int
+sortPreferenceAliases (const void *element1, const void *element2) {
+  const PreferenceAlias *const *alias1 = element1;
+  const PreferenceAlias *const *alias2 = element2;
+  return comparePreferenceNames((*alias1)->oldName, (*alias2)->oldName);
+}
+
+static int
+searchPreferenceAlias (const void *target, const void *element) {
+  const char *name = target;
+  const PreferenceAlias *const *alias = element;
+  return comparePreferenceNames(name, (*alias)->oldName);
+}
+
+static const PreferenceAlias *
+findPreferenceAlias (const char *name) {
+  static const PreferenceAlias **sortedAliases = NULL;
+
+  if (!sortedAliases) {
+    if (!(sortedAliases = malloc(ARRAY_SIZE(sortedAliases, preferenceAliasCount)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    for (unsigned int index=0; index<preferenceAliasCount; index+=1) {
+      sortedAliases[index] = &preferenceAliasTable[index];
+    }
+
+    qsort(
+      sortedAliases, preferenceAliasCount,
+      sizeof(*sortedAliases), sortPreferenceAliases
+    );
+  }
+
+  {
+    const PreferenceAlias *const *alias = bsearch(
+      name, sortedAliases, preferenceAliasCount,
+      sizeof(*sortedAliases), searchPreferenceAlias
+    );
+
+    if (alias) return *alias;
+  }
+
+  return NULL;
+}
+
+const PreferenceDefinition *
+findPreference (const char *name) {
+  while (name) {
+    {
+      const PreferenceDefinition *pref = findPreferenceDefinition(name);
+      if (pref) return pref;
+    }
+
+    {
+      const PreferenceAlias *alias = findPreferenceAlias(name);
+      if (!alias) break;
+      name = alias->newName;
+    }
+  }
+
+  if (name) logMessage(LOG_WARNING, "unknown preference: %s", name);
+  return NULL;
+}
+
+static void
+resetPreference (const PreferenceDefinition *pref) {
+  if (pref->settingCount) {
+    memset(pref->setting, pref->defaultValue, pref->settingCount);
+  } else {
+    *pref->setting = pref->defaultValue;
+  }
+
+  if (pref->encountered) *pref->encountered = 0;
+}
+
+void
+resetPreferences (void) {
+  memset(&prefs, 0, sizeof(prefs));
+
+  prefs.magic[0] = PREFS_MAGIC_NUMBER & 0XFF;
+  prefs.magic[1] = PREFS_MAGIC_NUMBER >> 8;
+  prefs.version = 6;
+
+  {
+    const PreferenceDefinition *pref = preferenceDefinitionTable;
+    const PreferenceDefinition *end = pref + preferenceDefinitionCount;
+
+    while (pref < end) resetPreference(pref++);
+  }
+}
+
+static const char *
+getSettingName (const PreferenceDefinition *pref, unsigned char index) {
+  const PreferenceStringTable *names = pref->settingNames;
+  if (!names) return NULL;
+  if (index >= names->count) return NULL;
+  return names->table[index];
+}
+
+static int
+changePreferenceSetting (
+  const char *name, const char *operand,
+  unsigned char *setting, const PreferenceStringTable *names
+) {
+  if (names) {
+    for (unsigned int index=0; index<names->count; index+=1) {
+      const char *name = names->table[index];
+      if (!name) continue;
+
+      if (strcmp(operand, name) == 0) {
+        *setting = index;
+        return 1;
+      }
+    }
+  }
+
+  {
+    int value;
+
+    if (isInteger(&value, operand)) {
+      unsigned char maximum = names? (names->count - 1): 0XFF;
+
+      if ((value >= 0) && (value <= maximum)) {
+        *setting = value;
+        return 1;
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "invalid preference setting: %s %s", name, operand);
+  return 0;
+}
+
+int
+setPreference (char *string) {
+  const char *name;
+
+  {
+    static const char delimiters[] = {
+      ' ', '\t', PARAMETER_ASSIGNMENT_CHARACTER, 0
+    };
+
+    name = strtok(string, delimiters);
+  }
+
+  if (name) {
+    const PreferenceDefinition *pref = findPreference(name);
+
+    if (pref) {
+      if (pref->encountered) *pref->encountered = 1;
+
+      static const char delimiters[] = " \t";
+      const char *operand;
+
+      if (pref->settingCount) {
+        unsigned char count = pref->settingCount;
+        unsigned char *setting = pref->setting;
+
+        while (count) {
+          if ((operand = strtok(NULL, delimiters))) {
+            if (changePreferenceSetting(name, operand, setting, pref->settingNames)) {
+              setting += 1;
+              count -= 1;
+              continue;
+            }
+          }
+
+          *setting = 0;
+          break;
+        }
+      } else if (!(operand = strtok(NULL, delimiters))) {
+        logMessage(LOG_WARNING, "missing preference setting: %s", name);
+      } else if (!changePreferenceSetting(name, operand, pref->setting, pref->settingNames)) {
+      }
+    }
+  } else {
+    logMessage(LOG_WARNING, "missing preference name");
+  }
+
+  return 1;
+}
+
+char *
+makePreferencesFilePath (const char *name) {
+  if (!name) name = PREFERENCES_FILE;
+  return makeUpdatablePath(name);
+}
+
+static int
+processPreferenceLine (const LineHandlerParameters *parameters) {
+  char *line = parameters->line.text;
+  while (isspace(*line)) line += 1;
+  if (!*line) return 1;
+  if (*line == PREFS_COMMENT_CHARACTER) return 1;
+  return setPreference(line);
+}
+
+int
+loadPreferencesFile (const char *path) {
+  int ok = 0;
+
+  logMessage(LOG_DEBUG, "loading preferences file: %s", path);
+  FILE *file = openDataFile(path, "rb", 1);
+
+  if (file) {
+    PreferenceSettings newPreferences;
+    size_t length = fread(&newPreferences, 1, sizeof(newPreferences), file);
+
+    if (ferror(file)) {
+      logMessage(LOG_ERR, "%s: %s: %s",
+                 gettext("cannot read preferences file"), path, strerror(errno));
+    } else if ((length < 40) ||
+               (newPreferences.magic[0] != (PREFS_MAGIC_NUMBER & 0XFF)) ||
+               (newPreferences.magic[1] != (PREFS_MAGIC_NUMBER >> 8))) {
+      fclose(file);
+
+      if ((file = openDataFile(path, "r", 1))) {
+        resetPreferences();
+        if (processLines(file, processPreferenceLine, NULL)) ok = 1;
+      }
+    } else {
+      prefs = newPreferences;
+      ok = 1;
+
+      {
+        const PreferenceDefinition *pref = preferenceDefinitionTable;
+        const PreferenceDefinition *end = pref + preferenceDefinitionCount;
+
+        const unsigned char *from = prefs.magic;
+        const unsigned char *to = from + length;
+
+        while (pref < end) {
+          unsigned char count = pref->settingCount;
+          if (!count) count = 1;
+
+          if ((pref->setting < from) || ((pref->setting + count) > to)) {
+            resetPreference(pref);
+          }
+
+          pref += 1;
+        }
+      }
+
+      if (length < (prefs.statusFields + sizeof(prefs.statusFields) - prefs.magic)) {
+        setStatusStyle(prefs.expandCurrentWord);
+      } else {
+        statusFieldsSet = 1;
+      }
+
+      if (prefs.version == 0) {
+        prefs.version += 1;
+        prefs.pcmVolume = DEFAULT_PCM_VOLUME;
+        prefs.midiVolume = DEFAULT_MIDI_VOLUME;
+        prefs.fmVolume = DEFAULT_FM_VOLUME;
+      }
+
+      if (prefs.version == 1) {
+        prefs.version += 1;
+        prefs.sayLineMode = DEFAULT_SAY_LINE_MODE;
+        prefs.autospeak = DEFAULT_AUTOSPEAK;
+      }
+
+      if (prefs.version == 2) {
+        prefs.version += 1;
+        prefs.autorepeatEnabled = DEFAULT_AUTOREPEAT_ENABLED;
+        prefs.longPressTime = DEFAULT_LONG_PRESS_TIME;
+        prefs.autorepeatInterval = DEFAULT_AUTOREPEAT_INTERVAL;
+        prefs.screenCursorVisibleTime *= 4;
+        prefs.screenCursorInvisibleTime *= 4;
+        prefs.attributesVisibleTime *= 4;
+        prefs.attributesInvisibleTime *= 4;
+        prefs.capitalsVisibleTime *= 4;
+        prefs.capitalsInvisibleTime *= 4;
+      }
+
+      if (prefs.version == 3) {
+        prefs.version += 1;
+        prefs.autorepeatPanning = DEFAULT_AUTOREPEAT_PANNING;
+      }
+
+      if (prefs.version == 4) {
+        prefs.version += 1;
+        prefs.touchSensitivity = DEFAULT_TOUCH_SENSITIVITY;
+      }
+
+      if (prefs.version == 5) {
+        prefs.version += 1;
+        prefs.expandCurrentWord = DEFAULT_EXPAND_CURRENT_WORD;
+      }
+    }
+
+    if (file) {
+      fclose(file);
+      file = NULL;
+    }
+  }
+
+  return ok;
+}
+
+static int
+putPreferenceComment (FILE *file, const PreferenceDefinition *pref) {
+  if (fprintf(file, "\n%c %s", PREFS_COMMENT_CHARACTER, pref->name) < 0) return 0;
+
+  if (pref->settingCount) {
+    if (fprintf(file, "[%u]", pref->settingCount) < 0) return 0;
+  }
+
+  if (fputs(": ", file) == EOF) return 0;
+  const char *name = getSettingName(pref, pref->defaultValue);
+
+  if (name) {
+    if (fputs(name, file) == EOF) return 0;
+  } else {
+    if (fprintf(file, "%u", pref->defaultValue) < 0) return 0;
+  }
+
+  if (pref->settingNames) {
+    if (fputs(" {", file) == EOF) return 0;
+
+    unsigned char count = pref->settingNames->count;
+    int first = 1;
+
+    for (unsigned char index=0; index<count; index+=1) {
+      const char *name = getSettingName(pref, index);
+      if (name) {
+        if (first) {
+          first = 0;
+        } else if (fputc(' ', file) == EOF) {
+          return 0;
+        }
+
+        if (fputs(name, file) == EOF) return 0;
+      } else {
+        logMessage(LOG_WARNING,
+          "unnamed preference setting: %s: %u",
+          pref->name, index
+        );
+      }
+    }
+
+    if (fputc('}', file) == EOF) return 0;
+  }
+
+  if (fputc('\n', file) == EOF) return 0;
+  return 1;
+}
+
+static int
+putSetting (FILE *file, const PreferenceDefinition *pref, unsigned char setting) {
+  if (fputc(' ', file) == EOF) return 0;
+  const char *name = getSettingName(pref, setting);
+
+  if (name) {
+    if (fputs(name, file) == EOF) return 0;
+  } else {
+    if (fprintf(file, "%u", setting) < 0) return 0;
+  }
+
+  return 1;
+}
+
+static int
+putPreference (FILE *file, const PreferenceDefinition *pref) {
+  if (pref->dontSave) return 1;
+
+  if (!putPreferenceComment(file, pref)) return 0;
+  if (fputs(pref->name, file) == EOF) return 0;
+
+  if (pref->settingCount) {
+    unsigned char count = pref->settingCount;
+    unsigned char *setting = pref->setting;
+
+    while (count-- && *setting) {
+      if (!putSetting(file, pref, *setting++)) return 0;
+    }
+  } else if (!putSetting(file, pref, *pref->setting)) {
+    return 0;
+  }
+
+  if (fputs("\n", file) == EOF) return 0;
+  return 1;
+}
+
+static int
+putPreferences (FILE *file) {
+  const PreferenceDefinition *pref = preferenceDefinitionTable;
+  const PreferenceDefinition *const end = pref + preferenceDefinitionCount;
+
+  while (pref < end) {
+    if (!putPreference(file, pref)) return 0;
+    pref += 1;
+  }
+
+  return 1;
+}
+
+static int
+putHeader (FILE *file) {
+  fprintf(file,
+    "%c %s Preferences File\n",
+    PREFS_COMMENT_CHARACTER, PACKAGE_NAME
+  );
+
+  return !ferror(file);
+}
+
+int
+savePreferencesFile (const char *path) {
+  int ok = 0;
+  FILE *file = openDataFile(path, "w", 0);
+
+  if (file) {
+    if (putHeader(file)) {
+      if (putPreferences(file)) {
+        ok = 1;
+      }
+    }
+
+    if (!ok) {
+      if (!ferror(file)) errno = EIO;
+      logMessage(LOG_ERR,
+        "%s: %s: %s",
+        gettext("cannot write to preferences file"),
+        path, strerror(errno)
+      );
+    }
+
+    fclose(file);
+  }
+
+  return ok;
+}
diff --git a/Programs/profile.c b/Programs/profile.c
new file mode 100644
index 0000000..223d999
--- /dev/null
+++ b/Programs/profile.c
@@ -0,0 +1,189 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "profile.h"
+#include "datafile.h"
+#include "file.h"
+#include "utf8.h"
+
+typedef struct {
+  const ProfileDescriptor *profile;
+  char **values;
+} ProfileActivationData;
+
+static int
+processPropertyAssignment (
+  DataFile *file,
+  const DataString *name,
+  char **value,
+  const ProfileActivationData *pad
+) {
+  unsigned int index;
+
+  for (index=0; index<pad->profile->properties.count; index+=1) {
+    if (isKeyword(pad->profile->properties.array[index].name, name->characters, name->length)) {
+      char **v = &pad->values[index];
+
+      if (*v) {
+        reportDataError(file, "property assigned more than once: %s:%.*"PRIws,
+                        pad->profile->category, name->length, name->characters);
+        free(*v);
+      }
+
+      *v = *value;
+      *value = NULL;
+      return 1;
+    }
+  }
+
+  reportDataError(file, "unknown property: %s:%.*"PRIws,
+                  pad->profile->category, name->length, name->characters);
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processPropertyOperands) {
+  const ProfileActivationData *pad = data;
+  int ok = 1;
+  DataString name;
+
+  if (getDataString(file, &name, 0, "property name")) {
+    DataString value;
+
+    if (getDataString(file, &value, 0, "property value")) {
+      char *v = getUtf8FromWchars(value.characters, value.length, NULL);
+
+      if (v) {
+        if (!processPropertyAssignment(file, &name, &v, pad)) ok = 0;
+        if (v) free(v);
+      } else {
+        ok = 0;
+      }
+    }
+  }
+
+  return ok;
+}
+
+static DATA_OPERANDS_PROCESSOR(processProfileOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    DATA_VARIABLE_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    {.name=NULL, .processor=processPropertyOperands},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "profile directive", data);
+}
+
+char *
+makeProfilePath (const ProfileDescriptor *profile, const char *directory, const char *name) {
+  char *subdirectory = makePath(directory, PROFILES_SUBDIRECTORY);
+
+  if (subdirectory) {
+    char *file = makeFilePath(subdirectory, name, profile->extension);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
+
+static int
+changeProperty (const ProfileProperty *property, const char *value) {
+  if (property->change) {
+    if (!value) value = *property->defaultValue;
+
+    if (property->change(value)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+changeProperties (const ProfileDescriptor *profile, char **values) {
+  int ok = !profile->begin || profile->begin();
+
+  if (ok) {
+    const ProfileProperty *const start = profile->properties.array;
+    const ProfileProperty *const end = start + profile->properties.count;
+    const ProfileProperty *property = start;
+
+    while (property < end) {
+      char *value = values? values[property - start]: NULL;
+
+      if (!changeProperty(property, value)) ok = 0;
+      if (value) free(value);
+      property += 1;
+    }
+
+    if (profile->end && !profile->end()) ok = 0;
+  }
+
+  return ok;
+}
+
+int
+activateProfile (const ProfileDescriptor *profile, const char *directory, const char *name) {
+  int ok = 0;
+
+  if (setBaseDataVariables(NULL)) {
+    char *path;
+
+    if ((path = makeProfilePath(profile, directory, name))) {
+      ProfileActivationData pad = {
+        .profile = profile
+      };
+
+      if ((pad.values = malloc(ARRAY_SIZE(pad.values, profile->properties.count)))) {
+        for (unsigned int index=0; index<profile->properties.count; index+=1) {
+          pad.values[index] = NULL;
+        }
+
+        const DataFileParameters parameters = {
+          .processOperands = processProfileOperands,
+          .data = &pad
+        };
+
+        if (processDataFile(path, &parameters)) {
+          if (changeProperties(profile, pad.values)) {
+            ok = 1;
+          }
+        }
+
+        free(pad.values);
+      } else {
+        logMallocError();
+      }
+
+      free(path);
+    }
+  }
+
+  return ok;
+}
+
+int
+deactivateProfile (const ProfileDescriptor *profile) {
+  return changeProperties(profile, NULL);
+}
diff --git a/Programs/profile.h b/Programs/profile.h
new file mode 100644
index 0000000..8b66a36
--- /dev/null
+++ b/Programs/profile.h
@@ -0,0 +1,36 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PROFILE
+#define BRLTTY_INCLUDED_PROFILE
+
+#include "profile_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern char *makeProfilePath (const ProfileDescriptor *profile, const char *directory, const char *name);
+extern int activateProfile (const ProfileDescriptor *profile, const char *directory, const char *name);
+extern int deactivateProfile (const ProfileDescriptor *profile);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PROFILE */
diff --git a/Programs/profile_types.h b/Programs/profile_types.h
new file mode 100644
index 0000000..4b0efbf
--- /dev/null
+++ b/Programs/profile_types.h
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_PROFILE_TYPES
+#define BRLTTY_INCLUDED_PROFILE_TYPES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const wchar_t *name;
+  char **defaultValue;
+  int (*change) (const char *value);
+} ProfileProperty;
+
+typedef struct {
+  const char *category;
+  const char *extension;
+
+  int (*begin) (void);
+  int (*end) (void);
+
+  struct {
+    const ProfileProperty *array;
+    unsigned int count;
+  } properties;
+} ProfileDescriptor;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_PROFILE_TYPES */
diff --git a/Programs/program.c b/Programs/program.c
new file mode 100644
index 0000000..827fddb
--- /dev/null
+++ b/Programs/program.c
@@ -0,0 +1,404 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <limits.h>
+
+#include "program.h"
+#include "messages.h"
+#include "pgmpath.h"
+#include "pid.h"
+#include "log.h"
+#include "file.h"
+#include "parse.h"
+#include "system.h"
+
+const char standardStreamArgument[] = "-";
+const char standardInputName[] = "<standard-input>";
+const char standardOutputName[] = "<standard-output>";
+const char standardErrorName[] = "<standard-error>";
+
+const char *programPath;
+const char *programName;
+
+static char *
+testProgram (const char *directory, const char *name) {
+  char *path;
+
+  if ((path = makePath(directory, name))) {
+    if (testProgramPath(path)) return path;
+
+    free(path);
+  }
+
+  return NULL;
+}
+
+static char *
+findProgram (const char *name) {
+  char *path = NULL;
+  const char *string;
+
+  if ((string = getenv("PATH"))) {
+    int count;
+    char **array;
+
+    if ((array = splitString(string, ':', &count))) {
+      for (unsigned int index=0; index<count; index+=1) {
+        const char *directory = array[index];
+        if (!*directory) directory = CURRENT_DIRECTORY_NAME;
+        if ((path = testProgram(directory, name))) break;
+      }
+
+      deallocateStrings(array);
+    }
+  }
+
+  return path;
+}
+
+void
+beginProgram (int argumentCount, char **argumentVector) {
+#if defined(GRUB_RUNTIME)
+
+#else /* at exit */
+  atexit(endProgram);
+#endif /* at exit */
+
+  setlocale(LC_ALL, "");
+  initializeSystemObject();
+  ensureAllMessagesProperties();
+
+  if ((programPath = getProgramPath())) {
+    registerProgramMemory("program-path", &programPath);
+  } else {
+    programPath = argumentVector[0];
+  }
+
+  if (!isExplicitPath(programPath)) {
+    char *path = findProgram(programPath);
+    if (!path) path = testProgram(CURRENT_DIRECTORY_NAME, programPath);
+    if (path) programPath = path;
+  }
+
+  if (isExplicitPath(programPath)) {
+#if defined(HAVE_REALPATH) && defined(PATH_MAX)
+    if (!isAbsolutePath(programPath)) {
+      char buffer[PATH_MAX];
+      char *path = realpath(programPath, buffer);
+
+      if (path) {
+        char *realPath = strdup(path);
+
+        if (realPath) {
+          programPath = realPath;
+        } else {
+          logMallocError();
+        }
+      } else {
+        logSystemError("realpath");
+      }
+    }
+#endif /* defined(HAVE_REALPATH) && defined(PATH_MAX) */
+
+    if (!isAbsolutePath(programPath)) {
+      char *directory;
+
+      if ((directory = getWorkingDirectory())) {
+        char *path;
+        if ((path = makePath(directory, programPath))) programPath = path;
+        free(directory);
+      }
+    }
+  }
+
+  programName = locatePathName(programPath);
+  pushLogPrefix(programName);
+}
+
+const char *
+getProgramDirectory (void) {
+  static const char *programDirectory = NULL;
+
+  if (!programDirectory) {
+    if ((programDirectory = getPathDirectory(programPath))) {
+      logMessage(LOG_DEBUG, "program directory: %s", programDirectory);
+      registerProgramMemory("program-directory", &programDirectory);
+    } else {
+      logMessage(LOG_WARNING, gettext("cannot determine program directory"));
+      programDirectory = "";
+    }
+  }
+
+  if (!*programDirectory) return NULL;
+  return programDirectory;
+}
+
+int
+fixInstallPath (char **path) {
+  const char *programDirectory = getProgramDirectory();
+  if (!programDirectory) programDirectory = CURRENT_DIRECTORY_NAME;
+
+  const char *problem = strtext("cannot fix install path");
+  char *newPath = makePath(programDirectory, *path);
+
+  if (newPath) {
+    if (changeStringSetting(path, newPath)) {
+      if (isAbsolutePath(*path)) {
+        problem = NULL;
+      } else {
+        problem = strtext("install path not absolute");
+      }
+    }
+
+    free(newPath);
+  }
+
+  if (!problem) return 1;
+  logMessage(LOG_WARNING, "%s: %s", gettext(problem), *path);
+  return 0;
+}
+
+char *
+makeProgramPath (const char *name) {
+   const char *directory = getProgramDirectory();
+   if (!directory) return NULL;
+   return makePath(directory, name);
+}
+
+char *
+makeCommandPath (const char *name) {
+  char *path = NULL;
+  char *directory = NULL;
+
+  if (changeStringSetting(&directory, COMMANDS_DIRECTORY)) {
+    if (fixInstallPath(&directory)) {
+      path = makePath(directory, name);
+    }
+  }
+
+  if (directory) free(directory);
+  return path;
+}
+
+int
+createPidFile (const char *path, ProcessIdentifier pid) {
+#if defined(GRUB_RUNTIME)
+  errno = EROFS;
+
+#else /* create pid file */
+  if (!pid) pid = getProcessIdentifier();
+
+  if (path && *path) {
+    if (!ensurePathDirectory(path)) return 0;
+
+    typedef enum {PFS_ready, PFS_stale, PFS_clash, PFS_error} PidFileState;
+    PidFileState state = PFS_error;
+
+    lockUmask();
+    int file = open(
+      path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR
+#ifdef S_IRGRP
+                            | S_IRGRP
+#endif /* S_IRGRP */
+#ifdef S_IROTH
+                            | S_IROTH
+#endif /* S_IROTH */
+    );
+    unlockUmask();
+
+    if (file != -1) {
+      int locked = acquireFileLock(file, 1);
+
+      if (locked || (errno == ENOSYS)) {
+        char buffer[0X20];
+        ssize_t length;
+
+        if ((length = read(file, buffer, sizeof(buffer))) != -1) {
+          ProcessIdentifier oldPid;
+          char terminator;
+          int count;
+
+          if (length == sizeof(buffer)) length -= 1;
+          buffer[length] = 0;
+          count = sscanf(buffer, "%" SCNpid "%c", &oldPid, &terminator);
+          state = PFS_stale;
+
+          if ((count == 1) ||
+              ((count == 2) && ((terminator == '\n') || (terminator == '\r')))) {
+            if (oldPid == pid) {
+              state = PFS_ready;
+            } else if (testProcessIdentifier(oldPid)) {
+              logMessage(LOG_ERR, "instance already running: PID=%" PRIpid, oldPid);
+              state = PFS_clash;
+            }
+          }
+        } else {
+          logSystemError("read");
+        }
+
+        if (state == PFS_stale) {
+          state = PFS_error;
+
+          if (lseek(file, 0, SEEK_SET) != -1) {
+            if (ftruncate(file, 0) != -1) {
+              length = snprintf(buffer, sizeof(buffer), "%" PRIpid "\n", pid);
+
+              if (write(file, buffer, length) != -1) {
+                state = PFS_ready;
+              } else {
+                logSystemError("write");
+              }
+            } else {
+              logSystemError("ftruncate");
+            }
+          } else {
+            logSystemError("lseek");
+          }
+        }
+
+        if (locked) releaseFileLock(file);
+      }
+
+      close(file);
+    } else {
+      logMessage(LOG_WARNING, "%s: %s: %s",
+                 gettext("cannot open process identifier file"),
+                 path, strerror(errno));
+    }
+
+    switch (state) {
+      case PFS_ready:
+        return 1;
+
+      case PFS_clash:
+        errno = EEXIST;
+        break;
+
+      case PFS_error:
+        break;
+
+      default:
+        logMessage(LOG_WARNING, "unexpected PID file state: %u", state);
+        break;
+    }
+  }
+#endif /* create pid file */
+
+  return 0;
+}
+
+int
+cancelProgram (const char *pidFile) {
+  int cancelled = 0;
+  FILE *file;
+
+  if ((file = fopen(pidFile, "r"))) {
+    char buffer[0X100];
+    const char *line;
+
+    if ((line = fgets(buffer, sizeof(buffer), file))) {
+      char *end;
+      long int pid = strtol(line, &end, 10);
+
+      if (!*end || isspace((unsigned char)*end)) {
+        if (cancelProcess(pid)) cancelled = 1;
+      }
+    }
+
+    fclose(file);
+  } else {
+    logMessage(LOG_ERR, "%s: %s: %s",
+               gettext("pid file open error"),
+               pidFile, strerror(errno));
+  }
+
+  return cancelled;
+}
+
+typedef struct ProgramExitEntryStruct ProgramExitEntry;
+static ProgramExitEntry *programExitEntries = NULL;
+
+struct ProgramExitEntryStruct {
+  ProgramExitEntry *next;
+  char *name;
+  ProgramExitHandler *handler;
+  void *data;
+};
+
+void
+onProgramExit (const char *name, ProgramExitHandler *handler, void *data) {
+  ProgramExitEntry *pxe;
+
+  if ((pxe = malloc(sizeof(*pxe)))) {
+    pxe->name = strdup(name);
+    pxe->handler = handler;
+    pxe->data = data;
+
+    pxe->next = programExitEntries;
+    programExitEntries = pxe;
+    logMessage(LOG_DEBUG, "program exit event added: %s", name);
+  } else {
+    logMallocError();
+  }
+}
+
+static void
+exitProgramMemory (void *data) {
+  char **pointer = data;
+
+  if (*pointer) {
+    free(*pointer);
+    *pointer = NULL;
+  }
+}
+
+void
+registerProgramMemory (const char *name, void *pointer) {
+  onProgramExit(name, exitProgramMemory, pointer);
+}
+
+void
+endProgram (void) {
+  logMessage(LOG_DEBUG, "stopping program components");
+
+  while (programExitEntries) {
+    ProgramExitEntry *pxe = programExitEntries;
+    const char *name = pxe->name;
+
+    programExitEntries = pxe->next;
+    if (!name) name = "unknown";
+
+    logMessage(LOG_DEBUG, "stopping program component: %s", name);
+    pxe->handler(pxe->data);
+
+    if (pxe->name) free(pxe->name);
+    free(pxe);
+  }
+
+  logMessage(LOG_DEBUG, "stopped program components");
+  popLogPrefix();
+}
diff --git a/Programs/pty_object.c b/Programs/pty_object.c
new file mode 100644
index 0000000..00c2c55
--- /dev/null
+++ b/Programs/pty_object.c
@@ -0,0 +1,211 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "pty_object.h"
+#include "scr_types.h"
+
+struct PtyObjectStruct {
+  char *path;
+  int master;
+
+  unsigned char logLevel;
+  unsigned char logInput:1;
+};
+
+PtyObject *
+ptyNewObject (void) {
+  PtyObject *pty;
+
+  if ((pty = malloc(sizeof(*pty)))) {
+    memset(pty, 0, sizeof(*pty));
+
+    pty->path = NULL;
+    pty->master = INVALID_FILE_DESCRIPTOR;
+
+    pty->logLevel = LOG_DEBUG;
+    pty->logInput = 0;
+
+    if ((pty->master = posix_openpt(O_RDWR)) != -1) {
+      if ((pty->path = ptsname(pty->master))) {
+        if ((pty->path = strdup(pty->path))) {
+          if (grantpt(pty->master) != -1) {
+            if (unlockpt(pty->master) != -1) {
+              return pty;
+            } else {
+              logSystemError("unlockpt");
+            }
+          } else {
+            logSystemError("grantpt");
+          }
+
+          free(pty->path);
+        } else {
+          logMallocError();
+        }
+      } else {
+        logSystemError("ptsname");
+      }
+
+      close(pty->master);
+    } else {
+      logSystemError("posix_openpt");
+    }
+
+    free(pty);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+const char *
+ptyGetPath (const PtyObject *pty) {
+  return pty->path;
+}
+
+int
+ptyGetMaster (const PtyObject *pty) {
+  return pty->master;
+}
+
+void
+ptySetLogLevel (PtyObject *pty, unsigned char level) {
+  pty->logLevel = level;
+}
+
+void
+ptySetLogInput (PtyObject *pty, int yes) {
+  pty->logInput = yes;
+}
+
+int
+ptyWriteInputData (PtyObject *pty, const void *data, size_t length) {
+  if (pty->logInput) {
+    logBytes(pty->logLevel, "pty input", data, length);
+  }
+
+  if (write(pty->master, data, length) != -1) return 1;
+  logSystemError("pty write input");
+  return 0;
+}
+
+int
+ptyWriteInputCharacter (PtyObject *pty, wchar_t character, int kxMode) {
+  if (!isSpecialKey(character)) {
+    char buffer[MB_CUR_MAX];
+    int count = wctomb(buffer, character);
+    if (count == -1) return 0;
+    return ptyWriteInputData(pty, buffer, count);
+  }
+
+  const char *sequence = NULL;
+  char buffer[0X20];
+
+  #define KEY(key, seq) case SCR_KEY_##key: sequence = seq; break;
+  switch (character) {
+    KEY(ENTER       , "\r")
+    KEY(TAB         , "\t")
+    KEY(BACKSPACE   , "\x7F")
+    KEY(ESCAPE      , "\x1B")
+
+    KEY(CURSOR_UP   , "\x1BOA")
+    KEY(CURSOR_DOWN , "\x1BOB")
+    KEY(CURSOR_RIGHT, "\x1BOC")
+    KEY(CURSOR_LEFT , "\x1BOD")
+
+    KEY(HOME        , "\x1B[1~")
+    KEY(INSERT      , "\x1B[2~")
+    KEY(DELETE      , "\x1B[3~")
+    KEY(END         , "\x1B[4~")
+    KEY(PAGE_UP     , "\x1B[5~")
+    KEY(PAGE_DOWN   , "\x1B[6~")
+
+    KEY(F1          , "\x1BOP")
+    KEY(F2          , "\x1BOQ")
+    KEY(F3          , "\x1BOR")
+    KEY(F4          , "\x1BOS")
+    KEY(F5          , "\x1B[15~")
+    KEY(F6          , "\x1B[17~")
+    KEY(F7          , "\x1B[18~")
+    KEY(F8          , "\x1B[19~")
+    KEY(F9          , "\x1B[20~")
+    KEY(F10         , "\x1B[21~")
+    KEY(F11         , "\x1B[23~")
+    KEY(F12         , "\x1B[24~")
+
+    default:
+      logMessage(LOG_WARNING, "unsupported pty screen key: %04X", character);
+      break;
+  }
+  #undef KEY
+
+  if (sequence) {
+    switch (character) {
+      case SCR_KEY_CURSOR_LEFT:
+      case SCR_KEY_CURSOR_RIGHT:
+      case SCR_KEY_CURSOR_UP:
+      case SCR_KEY_CURSOR_DOWN:
+        strcpy(buffer, sequence);
+        buffer[1] = kxMode? 'O': '[';
+        sequence = buffer;
+        break;
+    }
+
+    if (!ptyWriteInputData(pty, sequence, strlen(sequence))) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+void
+ptyCloseMaster (PtyObject *pty) {
+  if (pty->master != INVALID_FILE_DESCRIPTOR) {
+    close(pty->master);
+    pty->master = INVALID_FILE_DESCRIPTOR;
+  }
+}
+
+int
+ptyOpenSlave (const PtyObject *pty, int *fileDescriptor) {
+  int result = open(pty->path, O_RDWR);
+  int opened = result != INVALID_FILE_DESCRIPTOR;
+
+  if (opened) {
+    *fileDescriptor = result;
+  } else {
+    logSystemError("pty slave open");
+  }
+
+  return opened;
+}
+
+void
+ptyDestroyObject (PtyObject *pty) {
+  ptyCloseMaster(pty);
+  free(pty->path);
+  free(pty);
+}
diff --git a/Programs/pty_screen.c b/Programs/pty_screen.c
new file mode 100644
index 0000000..c5626d5
--- /dev/null
+++ b/Programs/pty_screen.c
@@ -0,0 +1,597 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "pty_screen.h"
+#include "scr_emulator.h"
+#include "msg_queue.h"
+#include "utf8.h"
+
+#define ENABLE_ROW_ARRAY 1
+
+static unsigned char screenLogLevel = LOG_DEBUG;
+
+void
+ptySetScreenLogLevel (unsigned char level) {
+  screenLogLevel = level;
+}
+
+static unsigned char hasColors = 0;
+static unsigned char currentForegroundColor;
+static unsigned char currentBackgroundColor;
+static unsigned char defaultForegroundColor;
+static unsigned char defaultBackgroundColor;
+static unsigned char colorPairMap[0100];
+
+static unsigned char
+toColorPair (unsigned char foreground, unsigned char background) {
+  return colorPairMap[(background << 3) | foreground];
+}
+
+static void
+initializeColors (unsigned char foreground, unsigned char background) {
+  currentForegroundColor = defaultForegroundColor = foreground;
+  currentBackgroundColor = defaultBackgroundColor = background;
+}
+
+static void
+initializeColorPairs (void) {
+  for (unsigned int pair=0; pair<ARRAY_COUNT(colorPairMap); pair+=1) {
+    colorPairMap[pair] = pair;
+  }
+
+  {
+    short foreground, background;
+    pair_content(0, &foreground, &background);
+    initializeColors(foreground, background);
+
+    unsigned char pair = toColorPair(foreground, background);
+    colorPairMap[pair] = 0;
+    colorPairMap[0] = pair;
+  }
+
+  for (unsigned char foreground=COLOR_BLACK; foreground<=COLOR_WHITE; foreground+=1) {
+    for (unsigned char background=COLOR_BLACK; background<=COLOR_WHITE; background+=1) {
+      unsigned char pair = toColorPair(foreground, background);
+      if (!pair) continue;
+      init_pair(pair, foreground, background);
+    }
+  }
+}
+
+static int haveTerminalMessageQueue = 0;
+static int terminalMessageQueue;
+static int haveInputTextHandler = 0;
+
+static int
+sendTerminalMessage (MessageType type, const void *content, size_t length) {
+  if (!haveTerminalMessageQueue) return 0;
+  return sendMessage(terminalMessageQueue, type, content, length, 0);
+}
+
+static int
+startTerminalMessageReceiver (const char *name, MessageType type, size_t size, MessageHandler *handler, void *data) {
+  if (!haveTerminalMessageQueue) return 0;
+  return startMessageReceiver(name, terminalMessageQueue, type, size, handler, data);
+}
+
+static void
+messageHandler_InputText (const MessageHandlerParameters *parameters) {
+  PtyObject *pty = parameters->data;
+  const char *content = parameters->content;
+  size_t length = parameters->length;
+
+  while (length) {
+    wint_t character = convertUtf8ToWchar(&content, &length);
+    if (character == WEOF) break;
+    if (!ptyWriteInputCharacter(pty, character, 0)) break;
+  }
+}
+
+static void
+enableMessages (key_t key) {
+  haveTerminalMessageQueue = createMessageQueue(&terminalMessageQueue, key);
+}
+
+static int segmentIdentifier = 0;
+static ScreenSegmentHeader *segmentHeader = NULL;
+
+static int
+destroySegment (void) {
+  if (haveTerminalMessageQueue) {
+    destroyMessageQueue(terminalMessageQueue);
+    haveTerminalMessageQueue = 0;
+  }
+
+  return destroyScreenSegment(segmentIdentifier);
+}
+
+static int
+createSegment (const char *path, int driverDirectives) {
+  key_t key;
+
+  if (makeTerminalKey(&key, path)) {
+    segmentHeader = createScreenSegment(&segmentIdentifier, key, COLS, LINES, ENABLE_ROW_ARRAY);
+
+    if (segmentHeader) {
+      if (driverDirectives) enableMessages(key);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static void
+storeCursorPosition (void) {
+  segmentHeader->cursorRow = getcury(stdscr);
+  segmentHeader->cursorColumn = getcurx(stdscr);
+}
+
+static void
+setColor (ScreenSegmentColor *ssc, unsigned char color, unsigned char level) {
+  if (color & COLOR_RED) ssc->red = level;
+  if (color & COLOR_GREEN) ssc->green = level;
+  if (color & COLOR_BLUE) ssc->blue = level;
+}
+
+static ScreenSegmentCharacter *
+setCharacter (unsigned int row, unsigned int column, const ScreenSegmentCharacter **end) {
+  wchar_t text;
+  attr_t attributes;
+  int colorPair;
+
+  {
+    unsigned int oldRow = segmentHeader->cursorRow;
+    unsigned int oldColumn = segmentHeader->cursorColumn;
+    int move = (row != oldRow) || (column != oldColumn);
+    if (move) ptySetCursorPosition(row, column);
+
+    {
+    #ifdef GOT_CURSES_WCH
+      cchar_t character;
+      in_wch(&character);
+
+      text = character.chars[0];
+      attributes = character.attr;
+      colorPair = character.ext_color;
+    #else /* GOT_CURSES_WCH */
+      chtype character = inch();
+      text = character & A_CHARTEXT;
+      attributes = character & A_ATTRIBUTES;
+      colorPair = PAIR_NUMBER(character);
+    #endif /* GOT_CURSES_WCH */
+    }
+
+    if (move) ptySetCursorPosition(oldRow, oldColumn);
+  }
+
+  ScreenSegmentCharacter character = {
+    .text = text,
+    .alpha = UINT8_MAX,
+  };
+
+  {
+    short fgColor, bgColor;
+    pair_content(colorPair, &fgColor, &bgColor);
+
+    unsigned char bgLevel = SCREEN_SEGMENT_COLOR_LEVEL;
+    unsigned char fgLevel = bgLevel;
+
+    if (attributes & (A_BOLD | A_STANDOUT)) fgLevel = UINT8_MAX;
+    if (attributes & A_DIM) fgLevel >>= 1, bgLevel >>= 1;
+
+    {
+      ScreenSegmentColor *cfg, *cbg;
+
+      if (attributes & A_REVERSE) {
+        cfg = &character.background;
+        cbg = &character.foreground;
+      } else {
+        cfg = &character.foreground;
+        cbg = &character.background;
+      }
+
+      setColor(cfg, fgColor, fgLevel);
+      setColor(cbg, bgColor, bgLevel);
+    }
+  }
+
+  if (attributes & A_BLINK) character.blink = 1;
+  if (attributes & A_UNDERLINE) character.underline = 1;
+
+  {
+    ScreenSegmentCharacter *location = getScreenCharacter(segmentHeader, row, column, end);
+    *location = character;
+    return location;
+  }
+}
+
+static ScreenSegmentCharacter *
+setCurrentCharacter (const ScreenSegmentCharacter **end) {
+  return setCharacter(segmentHeader->cursorRow, segmentHeader->cursorColumn, end);
+}
+
+static ScreenSegmentCharacter *
+getCurrentCharacter (const ScreenSegmentCharacter **end) {
+  return getScreenCharacter(segmentHeader, segmentHeader->cursorRow, segmentHeader->cursorColumn, end);
+}
+
+static void
+fillCharacters (unsigned int row, unsigned int column, unsigned int count) {
+  ScreenSegmentCharacter *from = setCharacter(row, column, NULL);
+  propagateScreenCharacter(from, (from + count));
+}
+
+static void
+fillRows (unsigned int row, unsigned int count) {
+  const ScreenSegmentCharacter *character = setCharacter(row, 0, NULL);
+  fillScreenRows(segmentHeader, row, count, character);
+}
+
+static unsigned int scrollRegionTop;
+static unsigned int scrollRegionBottom;
+
+static unsigned int savedCursorRow = 0;
+static unsigned int savedCursorColumn = 0;
+
+int
+ptyBeginScreen (PtyObject *pty, int driverDirectives) {
+  haveTerminalMessageQueue = 0;
+  haveInputTextHandler = 0;
+
+  if (initscr()) {
+    intrflush(stdscr, FALSE);
+    keypad(stdscr, TRUE);
+
+    raw();
+    noecho();
+
+    scrollok(stdscr, TRUE);
+    idlok(stdscr, TRUE);
+    idcok(stdscr, TRUE);
+
+    scrollRegionTop = getbegy(stdscr);
+    scrollRegionBottom = getmaxy(stdscr) - 1;
+
+    savedCursorRow = 0;
+    savedCursorColumn = 0;
+
+    hasColors = has_colors();
+    initializeColors(COLOR_WHITE, COLOR_BLACK);
+
+    if (hasColors) {
+      start_color();
+      initializeColorPairs();
+    }
+
+    if (createSegment(ptyGetPath(pty), driverDirectives)) {
+      segmentHeader->screenNumber = 1;
+      storeCursorPosition();
+
+      haveInputTextHandler = startTerminalMessageReceiver(
+        "terminal-input-text-receiver", TERM_MSG_INPUT_TEXT,
+        0X200, messageHandler_InputText, pty
+      );
+
+      return 1;
+    }
+
+    endwin();
+  }
+
+  return 0;
+}
+
+void
+ptyEndScreen (void) {
+  endwin();
+  sendTerminalMessage(TERM_MSG_EMULATOR_EXITING, NULL, 0);
+  detachScreenSegment(segmentHeader);
+  destroySegment();
+}
+
+void
+ptyRefreshScreen (void) {
+  sendTerminalMessage(TERM_MSG_SEGMENT_UPDATED, NULL, 0);
+  refresh();
+}
+
+void
+ptySetCursorPosition (unsigned int row, unsigned int column) {
+  move(row, column);
+  storeCursorPosition();
+}
+
+void
+ptySetCursorRow (unsigned int row) {
+  ptySetCursorPosition(row, segmentHeader->cursorColumn);
+}
+
+void
+ptySetCursorColumn (unsigned int column) {
+  ptySetCursorPosition(segmentHeader->cursorRow, column);
+}
+
+void
+ptySaveCursorPosition (void) {
+  savedCursorRow = segmentHeader->cursorRow;
+  savedCursorColumn = segmentHeader->cursorColumn;
+}
+
+void
+ptyRestoreCursorPosition (void) {
+  ptySetCursorPosition(savedCursorRow, savedCursorColumn);
+}
+
+void
+ptySetScrollRegion (unsigned int top, unsigned int bottom) {
+  scrollRegionTop = top;
+  scrollRegionBottom = bottom;
+  setscrreg(top, bottom);
+}
+
+static int
+isWithinScrollRegion (unsigned int row) {
+  if (row < scrollRegionTop) return 0;
+  if (row > scrollRegionBottom) return 0;
+  return 1;
+}
+
+int
+ptyAmWithinScrollRegion (void) {
+  return isWithinScrollRegion(segmentHeader->cursorRow);
+}
+
+static void
+scrollRows (unsigned int count, int down) {
+  unsigned int top = scrollRegionTop;
+  unsigned int bottom = scrollRegionBottom + 1;
+  unsigned int size = bottom - top;
+  if (count > size) count = size;
+  unsigned int clear;
+
+  if (down) {
+    scrl(-count);
+    clear = top;
+  } else {
+    scrl(count);
+    clear = bottom - count;
+  }
+
+  scrollScreenRows(segmentHeader, top, size, count, down);
+  fillRows(clear, count);
+}
+
+void
+ptyScrollDown (unsigned int count) {
+  scrollRows(count, true);
+}
+
+void
+ptyScrollUp (unsigned int count) {
+  scrollRows(count, false);
+}
+
+void
+ptyMoveCursorUp (unsigned int amount) {
+  unsigned int row = segmentHeader->cursorRow;
+  if (amount > row) amount = row;
+  if (amount > 0) ptySetCursorRow(row-amount);
+}
+
+void
+ptyMoveCursorDown (unsigned int amount) {
+  unsigned int oldRow = segmentHeader->cursorRow;
+  unsigned int newRow = MIN(oldRow+amount, LINES-1);
+  if (newRow != oldRow) ptySetCursorRow(newRow);
+}
+
+void
+ptyMoveCursorLeft (unsigned int amount) {
+  unsigned int column = segmentHeader->cursorColumn;
+  if (amount > column) amount = column;
+  if (amount > 0) ptySetCursorColumn(column-amount);
+}
+
+void
+ptyMoveCursorRight (unsigned int amount) {
+  unsigned int oldColumn = segmentHeader->cursorColumn;
+  unsigned int newColumn = MIN(oldColumn+amount, COLS-1);
+  if (newColumn != oldColumn) ptySetCursorColumn(newColumn);
+}
+
+void
+ptyMoveUp1 (void) {
+  if (segmentHeader->cursorRow == scrollRegionTop) {
+    ptyScrollDown(1);
+  } else {
+    ptyMoveCursorUp(1);
+  }
+}
+
+void
+ptyMoveDown1 (void) {
+  if (segmentHeader->cursorRow == scrollRegionBottom) {
+    ptyScrollUp(1);
+  } else {
+    ptyMoveCursorDown(1);
+  }
+}
+
+void
+ptyTabBackward (void) {
+  ptySetCursorColumn(((segmentHeader->cursorColumn - 1) / TABSIZE) * TABSIZE);
+}
+
+void
+ptyTabForward (void) {
+  ptySetCursorColumn(((segmentHeader->cursorColumn / TABSIZE) + 1) * TABSIZE);
+}
+
+void
+ptyInsertLines (unsigned int count) {
+  if (ptyAmWithinScrollRegion()) {
+    unsigned int row = segmentHeader->cursorRow;
+    unsigned int oldTop = scrollRegionTop;
+    unsigned int oldBottom = scrollRegionBottom;
+
+    ptySetScrollRegion(row, scrollRegionBottom);
+    ptyScrollDown(count);
+    ptySetScrollRegion(oldTop, oldBottom);
+  }
+}
+
+void
+ptyDeleteLines (unsigned int count) {
+  if (ptyAmWithinScrollRegion()) {
+    unsigned int row = segmentHeader->cursorRow;
+    unsigned int oldTop = scrollRegionTop;
+    unsigned int oldBottom = scrollRegionBottom;
+
+    ptySetScrollRegion(row, scrollRegionBottom);
+    ptyScrollUp(count);
+    ptySetScrollRegion(oldTop, oldBottom);
+  }
+}
+
+void
+ptyInsertCharacters (unsigned int count) {
+  const ScreenSegmentCharacter *end;
+  ScreenSegmentCharacter *from = getCurrentCharacter(&end);
+
+  if ((from + count) > end) count = end - from;
+  ScreenSegmentCharacter *to = from + count;
+  moveScreenCharacters(to, from, (end - to));
+
+  {
+    unsigned int counter = count;
+    while (counter-- > 0) insch(' ');
+  }
+
+  fillCharacters(segmentHeader->cursorRow, segmentHeader->cursorColumn, count);
+}
+
+void
+ptyDeleteCharacters (unsigned int count) {
+  const ScreenSegmentCharacter *end;
+  ScreenSegmentCharacter *to = getCurrentCharacter(&end);
+
+  if ((to + count) > end) count = end - to;
+  ScreenSegmentCharacter *from = to + count;
+  if (from < end) moveScreenCharacters(to, from, (end - from));
+
+  {
+    unsigned int counter = count;
+    while (counter-- > 0) delch();
+  }
+
+  fillCharacters(segmentHeader->cursorRow, (COLS - count), count);
+}
+
+void
+ptyAddCharacter (unsigned char character) {
+  unsigned int row = segmentHeader->cursorRow;
+  unsigned int column = segmentHeader->cursorColumn;
+
+  addch(character);
+  storeCursorPosition();
+
+  setCharacter(row, column, NULL);
+}
+
+void
+ptySetCursorVisibility (unsigned int visibility) {
+  curs_set(visibility);
+}
+
+void
+ptySetAttributes (attr_t attributes) {
+  attrset(attributes);
+}
+
+void
+ptyAddAttributes (attr_t attributes) {
+  attron(attributes);
+}
+
+void
+ptyRemoveAttributes (attr_t attributes) {
+  attroff(attributes);
+}
+
+static void
+setCharacterColors (void) {
+  attroff(A_COLOR);
+  attron(COLOR_PAIR(toColorPair(currentForegroundColor, currentBackgroundColor)));
+}
+
+void
+ptySetForegroundColor (int color) {
+  if (color == -1) color = defaultForegroundColor;
+  currentForegroundColor = color;
+  setCharacterColors();
+}
+
+void
+ptySetBackgroundColor (int color) {
+  if (color == -1) color = defaultBackgroundColor;
+  currentBackgroundColor = color;
+  setCharacterColors();
+}
+
+void
+ptyClearToEndOfLine (void) {
+  clrtoeol();
+
+  const ScreenSegmentCharacter *to;
+  ScreenSegmentCharacter *from = setCurrentCharacter(&to);
+  propagateScreenCharacter(from, to);
+}
+
+void
+ptyClearToBeginningOfLine (void) {
+  unsigned int column = segmentHeader->cursorColumn;
+  if (column > 0) ptySetCursorColumn(0);
+
+  while (1) {
+    ptyAddCharacter(' ');
+    if (segmentHeader->cursorColumn > column) break;
+  }
+
+  ptySetCursorColumn(column);
+}
+
+void
+ptyClearToEndOfDisplay (void) {
+  clrtobot();
+
+  if (haveScreenRowArray(segmentHeader)) {
+    ptyClearToEndOfLine();
+
+    unsigned int bottomRows = segmentHeader->screenHeight - segmentHeader->cursorRow - 1;
+    if (bottomRows > 0) fillRows((segmentHeader->cursorRow + 1), bottomRows);
+  } else {
+    ScreenSegmentCharacter *from = setCurrentCharacter(NULL);
+    const ScreenSegmentCharacter *to;
+    getScreenCharacterArray(segmentHeader, &to);
+    propagateScreenCharacter(from, to);
+  }
+}
diff --git a/Programs/pty_terminal.c b/Programs/pty_terminal.c
new file mode 100644
index 0000000..179d55b
--- /dev/null
+++ b/Programs/pty_terminal.c
@@ -0,0 +1,841 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* unimplemented output actions
+ * enacs=\E(B\E)0 - enable alternate charset mode
+ * hts=\EH - set tab
+ * kmous=\E[M - mouse event
+ * tbc=\E[3g - clear all tabs
+ * u6=\E[%i%d;%dR - user string 6
+ * u7=\E[6n - user string 7
+ * u8=\E[?1;2c - user string 8
+ * u9=\E[c - user string 9
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "pty_terminal.h"
+#include "pty_screen.h"
+#include "scr_types.h"
+#include "ascii.h"
+
+static unsigned char terminalLogLevel = LOG_DEBUG;
+static unsigned char logInput = 0;
+static unsigned char logOutput = 0;
+static unsigned char logSequences = 0;
+static unsigned char logUnexpected = 0;
+
+void
+ptySetTerminalLogLevel (unsigned char level) {
+  terminalLogLevel = level;
+  ptySetScreenLogLevel(level);
+}
+
+void
+ptySetLogTerminalInput (int yes) {
+  logInput = yes;
+}
+
+void
+ptySetLogTerminalOutput (int yes) {
+  logOutput = yes;
+}
+
+void
+ptySetLogTerminalSequences (int yes) {
+  logSequences = yes;
+}
+
+void
+ptySetLogUnexpectedTerminalIO (int yes) {
+  logUnexpected = yes;
+}
+
+static const char ptyTerminalType[] = "screen";
+
+const char *
+ptyGetTerminalType (void) {
+  return ptyTerminalType;
+}
+
+static unsigned char insertMode = 0;
+static unsigned char alternateCharsetMode = 0;
+static unsigned char keypadTransmitMode = 0;
+static unsigned char bracketedPasteMode = 0;
+static unsigned char absoluteCursorAddressingMode = 0;
+
+int
+ptyBeginTerminal (PtyObject *pty, int driverDirectives) {
+  insertMode = 0;
+  alternateCharsetMode = 0;
+  keypadTransmitMode = 0;
+  bracketedPasteMode = 0;
+  absoluteCursorAddressingMode = 0;
+
+  return ptyBeginScreen(pty, driverDirectives);
+}
+
+void
+ptyEndTerminal (void) {
+  ptyEndScreen();
+}
+
+static void
+soundAlert (void) {
+  beep();
+}
+
+static void
+showAlert (void) {
+  flash();
+}
+
+int
+ptyProcessTerminalInput (PtyObject *pty) {
+  int character = getch();
+
+  if (logInput) {
+    const char *name = keyname(character);
+    if (!name) name = "unknown";
+    logMessage(terminalLogLevel, "input: 0X%02X (%s)", character, name);
+  }
+
+  if (character > UINT8_MAX) {
+    ScreenKey key = 0;
+
+    #define KEY(from, to) case KEY_##from: key = SCR_KEY_##to; break;
+    switch (character) {
+      KEY(ENTER    , ENTER)
+      KEY(BACKSPACE, BACKSPACE)
+
+      KEY(LEFT     , CURSOR_LEFT)
+      KEY(RIGHT    , CURSOR_RIGHT)
+      KEY(UP       , CURSOR_UP)
+      KEY(DOWN     , CURSOR_DOWN)
+
+      KEY(PPAGE    , PAGE_UP)
+      KEY(NPAGE    , PAGE_DOWN)
+      KEY(HOME     , HOME)
+      KEY(END      , END)
+      KEY(IC       , INSERT)
+      KEY(DC       , DELETE)
+
+      KEY(F( 1)    , F1)
+      KEY(F( 2)    , F2)
+      KEY(F( 3)    , F3)
+      KEY(F( 4)    , F4)
+      KEY(F( 5)    , F5)
+      KEY(F( 6)    , F6)
+      KEY(F( 7)    , F7)
+      KEY(F( 8)    , F8)
+      KEY(F( 9)    , F9)
+      KEY(F(10)    , F10)
+      KEY(F(11)    , F11)
+      KEY(F(12)    , F12)
+    }
+    #undef KEY
+
+    if (key) {
+      if (!ptyWriteInputCharacter(pty, key, keypadTransmitMode)) {
+        return 0;
+      }
+    } else if (logUnexpected) {
+      const char *name = keyname(character);
+      if (!name) name = "unknown";
+      logMessage(terminalLogLevel, "unexpected input: 0X%02X (%s)", character, name);
+    }
+
+    return 1;
+  }
+
+  char byte = character;
+  return ptyWriteInputData(pty, &byte,1);
+}
+
+static unsigned char outputByteBuffer[0X40];
+static unsigned char outputByteCount;
+
+static void
+logUnexpectedSequence (void) {
+  if (logUnexpected) {
+    logBytes(
+      terminalLogLevel, "unexpected sequence",
+      outputByteBuffer, outputByteCount
+    );
+  }
+}
+
+typedef enum {
+  OPS_BASIC,
+  OPS_ESCAPE,
+  OPS_BRACKET,
+  OPS_NUMBER,
+  OPS_DIGIT,
+  OPS_ACTION,
+} OutputParserState;
+
+static OutputParserState outputParserState;
+static unsigned char outputParserQuestionMark;
+
+static unsigned int outputParserNumber;
+static unsigned int outputParserNumberArray[9];
+static unsigned char outputParserNumberCount;
+
+static void
+addOutputParserNumber (unsigned int number) {
+  if (outputParserNumberCount < ARRAY_COUNT(outputParserNumberArray)) {
+    outputParserNumberArray[outputParserNumberCount++] = number;
+  }
+}
+
+static unsigned int
+getOutputActionCount (void) {
+  if (outputParserNumberCount == 0) return 1;
+  return outputParserNumberArray[0];
+}
+
+static void
+logOutputAction (const char *name, const char *description) {
+  if (logSequences) {
+    char prefix[0X100];
+    STR_BEGIN(prefix, sizeof(prefix));
+    STR_PRINTF("action: %s", name);
+
+    for (unsigned int i=0; i<outputParserNumberCount; i+=1) {
+      STR_PRINTF(" %u", outputParserNumberArray[i]);
+    }
+
+    if (description && *description) STR_PRINTF(" (%s)", description);
+    STR_END;
+    logBytes(terminalLogLevel, "%s", outputByteBuffer, outputByteCount, prefix);
+  }
+}
+
+typedef enum {
+  OBP_DONE,
+  OBP_CONTINUE,
+  OBP_REPROCESS,
+  OBP_UNEXPECTED,
+} OutputByteParserResult;
+
+typedef OutputByteParserResult OutputByteParser (unsigned char byte);
+
+static OutputByteParserResult
+parseOutputByte_BASIC (unsigned char byte) {
+  outputParserQuestionMark = 0;
+  outputParserNumberCount = 0;
+
+  switch (byte) {
+    case ASCII_ESC:
+      outputParserState = OPS_ESCAPE;
+      return OBP_CONTINUE;
+
+    case ASCII_BEL:
+      logOutputAction("bel", "audible alert");
+      soundAlert();
+      return OBP_DONE;
+
+    case ASCII_BS:
+      logOutputAction("cub1", "cursor left 1");
+      ptyMoveCursorLeft(1);
+      return OBP_DONE;
+
+    case ASCII_HT:
+      logOutputAction("ht", "tab forward");
+      ptyTabForward();
+      return OBP_DONE;
+
+    case ASCII_LF:
+      if (ptyAmWithinScrollRegion()) {
+        logOutputAction("ind", "move down 1");
+        ptyMoveDown1();
+      } else {
+        logOutputAction("cud1", "cursor down 1");
+        ptyMoveCursorDown(1);
+      }
+      return OBP_DONE;
+
+    case ASCII_CR:
+      logOutputAction("cr", "carriage return");
+      ptySetCursorColumn(0);
+      return OBP_DONE;
+
+    case ASCII_SO:
+      logOutputAction("smacs", "alternate charset on");
+      alternateCharsetMode = 1;
+      return OBP_DONE;
+
+    case ASCII_SI:
+      logOutputAction("rmacs", "alternate charset off");
+      alternateCharsetMode = 0;
+      return OBP_DONE;
+
+    default: {
+      if (logOutput) {
+        logMessage(terminalLogLevel, "output: 0X%02X", byte);
+      }
+
+      if (insertMode) ptyInsertCharacters(1);
+      ptyAddCharacter(byte);
+      return OBP_DONE;
+    }
+  }
+}
+
+static OutputByteParserResult
+parseOutputByte_ESCAPE (unsigned char byte) {
+  switch (byte) {
+    case '[':
+      outputParserState = OPS_BRACKET;
+      return OBP_CONTINUE;
+
+    case '=':
+      logOutputAction("smkx", "keypad transmit on");
+      keypadTransmitMode = 1;
+      return OBP_DONE;
+
+    case '>':
+      logOutputAction("rmkx", "keypad transmit off");
+      keypadTransmitMode = 0;
+      return OBP_DONE;
+
+    case 'E':
+      logOutputAction("nel", "new line");
+      ptySetCursorColumn(0);
+      ptyMoveDown1();
+      return OBP_DONE;
+
+    case 'M':
+      if (ptyAmWithinScrollRegion()) {
+        logOutputAction("ri", "move up 1");
+        ptyMoveUp1();
+      } else {
+        logOutputAction("cuu1", "cursor up 1");
+        ptyMoveCursorUp(1);
+      }
+      return OBP_DONE;
+
+    case 'c':
+      logOutputAction("clear", "clear screen");
+      ptySetCursorPosition(0, 0);
+      ptyClearToEndOfDisplay();
+      return OBP_DONE;
+
+    case 'g':
+      logOutputAction("flash", "visual alert");
+      showAlert();
+      return OBP_DONE;
+
+    case '7':
+      logOutputAction("sc", "save cursor position");
+      ptySaveCursorPosition();
+      return OBP_DONE;
+
+    case '8':
+      logOutputAction("rc", "restore cursor position");
+      ptyRestoreCursorPosition();
+      return OBP_DONE;
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+parseOutputByte_BRACKET (unsigned char byte) {
+  outputParserState = OPS_NUMBER;
+  if (outputParserQuestionMark) return OBP_REPROCESS;
+  if (byte != '?') return OBP_REPROCESS;
+
+  outputParserQuestionMark = 1;
+  outputParserState = OPS_BRACKET;
+  return OBP_CONTINUE;
+}
+
+static OutputByteParserResult
+parseOutputByte_NUMBER (unsigned char byte) {
+  if (iswdigit(byte)) {
+    outputParserNumber = 0;
+    outputParserState = OPS_DIGIT;
+  } else {
+    outputParserState = OPS_ACTION;
+  }
+
+  return OBP_REPROCESS;
+}
+
+static OutputByteParserResult
+parseOutputByte_DIGIT (unsigned char byte) {
+  if (iswdigit(byte)) {
+    outputParserNumber *= 10;
+    outputParserNumber += byte - '0';
+    return OBP_CONTINUE;
+  }
+
+  addOutputParserNumber(outputParserNumber);
+  outputParserNumber = 0;
+  if (byte == ';') return OBP_CONTINUE;
+
+  outputParserState = OPS_ACTION;
+  return OBP_REPROCESS;
+}
+
+static OutputByteParserResult
+performBracketAction_h (unsigned char byte) {
+  if (outputParserNumberCount == 1) {
+    switch (outputParserNumberArray[0]) {
+      case 4:
+        logOutputAction("smir", "insert on");
+        insertMode = 1;
+        return OBP_DONE;
+
+      case 34:
+        logOutputAction("cnorm", "cursor normal visibility");
+        ptySetCursorVisibility(1);
+        return OBP_DONE;
+    }
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+performBracketAction_l (unsigned char byte) {
+  if (outputParserNumberCount == 1) {
+    switch (outputParserNumberArray[0]) {
+      case 4:
+        logOutputAction("rmir", "insert off");
+        insertMode = 0;
+        return OBP_DONE;
+
+      case 34:
+        logOutputAction("cvvis", "cursor very visile");
+        ptySetCursorVisibility(2);
+        return OBP_DONE;
+    }
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+performBracketAction_m (unsigned char byte) {
+  if (outputParserNumberCount == 0) addOutputParserNumber(0);
+
+  for (unsigned int index=0; index<outputParserNumberCount; index+=1) {
+    unsigned int number = outputParserNumberArray[index];
+
+    switch (number / 10) {
+      {
+        const char *name;
+        const char *description;
+        void (*setColor) (int color);
+        int color;
+
+      case 3:
+        name = "setaf";
+        description = "foreground color";
+        setColor = ptySetForegroundColor;
+        goto doColor;
+
+      case 4:
+        name = "setab";
+        description = "background color";
+        setColor = ptySetBackgroundColor;
+        goto doColor;
+
+      doColor:
+        color = number % 10;
+        if (color == 8) return OBP_UNEXPECTED;
+        if (color == 9) color = -1;
+
+        logOutputAction(name, description);
+        setColor(color);
+        continue;
+      }
+    }
+
+    switch (number) {
+      case 0:
+        logOutputAction("sgr0", "all attributes off");
+        ptySetAttributes(0);
+        continue;
+
+      case 1:
+        logOutputAction("bold", "bold on");
+        ptyAddAttributes(A_BOLD);
+        continue;
+
+      case 2:
+        logOutputAction("dim", "dim on");
+        ptyAddAttributes(A_DIM);
+        continue;
+
+      case 3:
+        logOutputAction("smso", "standout on");
+        ptyAddAttributes(A_STANDOUT);
+        continue;
+
+      case 4:
+        logOutputAction("smul", "underline on");
+        ptyAddAttributes(A_UNDERLINE);
+        continue;
+
+      case 5:
+        logOutputAction("blink", "blink on");
+        ptyAddAttributes(A_BLINK);
+        continue;
+
+      case 7:
+        logOutputAction("rev", "reverse video on");
+        ptyAddAttributes(A_REVERSE);
+        continue;
+
+      case 22:
+        logOutputAction("normal", "bold/dim off");
+        ptyRemoveAttributes(A_BOLD | A_DIM);
+        continue;
+
+      case 23:
+        logOutputAction("rmso", "standout off");
+        ptyRemoveAttributes(A_STANDOUT);
+        continue;
+
+      case 24:
+        logOutputAction("rmul", "underline off");
+        ptyRemoveAttributes(A_UNDERLINE);
+        continue;
+
+      case 25:
+        logOutputAction("unblink", "blink off");
+        ptyRemoveAttributes(A_BLINK);
+        continue;
+
+      case 27:
+        logOutputAction("unrev", "reverse video off");
+        ptyRemoveAttributes(A_REVERSE);
+        continue;
+    }
+
+    return OBP_UNEXPECTED;
+  }
+
+  return OBP_DONE;
+}
+
+static OutputByteParserResult
+performBracketAction (unsigned char byte) {
+  switch (byte) {
+    case 'A':
+      logOutputAction("cuu", "cursor up");
+      ptyMoveCursorUp(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'B':
+      logOutputAction("cud", "cursor down");
+      ptyMoveCursorDown(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'C':
+      logOutputAction("cuf", "cursor right");
+      ptyMoveCursorRight(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'D':
+      logOutputAction("cub", "cursor left");
+      ptyMoveCursorLeft(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'G': {
+      if (outputParserNumberCount != 1) return OBP_UNEXPECTED   ;
+      unsigned int *column = &outputParserNumberArray[0];
+      if (!(*column)--) return OBP_UNEXPECTED;
+
+      logOutputAction("hpa", "set cursor column");
+      ptySetCursorColumn(*column);
+      return OBP_DONE;
+    }
+
+    case 'H': {
+      if (outputParserNumberCount == 0) {
+        addOutputParserNumber(1);
+        addOutputParserNumber(1);
+      } else if (outputParserNumberCount != 2) {
+        return OBP_UNEXPECTED;
+      }
+
+      unsigned int *row = &outputParserNumberArray[0];
+      unsigned int *column = &outputParserNumberArray[1];
+
+      if (!(*row)--) return OBP_UNEXPECTED;
+      if (!(*column)--) return OBP_UNEXPECTED;
+
+      logOutputAction("cup", "set cursor position");
+      ptySetCursorPosition(*row, *column);
+      return OBP_DONE;
+    }
+
+    case 'J':
+      if (outputParserNumberCount != 0) return OBP_UNEXPECTED;
+      logOutputAction("ed", "clear to end of display");
+      ptyClearToEndOfDisplay();
+      return OBP_DONE;
+
+    case 'K': {
+      if (outputParserNumberCount == 0) addOutputParserNumber(0);
+      if (outputParserNumberCount != 1) return OBP_UNEXPECTED;
+
+      switch (outputParserNumberArray[0]) {
+        case 0:
+          logOutputAction("el", "clear to end of line");
+          ptyClearToEndOfLine();
+          return OBP_DONE;
+
+        case 1:
+          logOutputAction("el1", "clear to beginning of line");
+          ptyClearToBeginningOfLine();
+          return OBP_DONE;
+      }
+
+      break;
+    }
+
+    case 'L':
+      logOutputAction("il", "insert lines");
+      ptyInsertLines(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'M':
+      logOutputAction("dl", "delete lines");
+      ptyDeleteLines(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'P':
+      logOutputAction("dch", "delete characters");
+      ptyDeleteCharacters(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'S':
+      logOutputAction("indn", "scroll forward");
+      ptyScrollUp(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'T':
+      logOutputAction("rin", "scroll backward");
+      ptyScrollDown(getOutputActionCount());
+      return OBP_DONE;
+
+    case 'Z':
+      logOutputAction("cbt", "tab backward");
+      ptyTabBackward();
+      return OBP_DONE;
+
+    case 'd': {
+      if (outputParserNumberCount != 1) return OBP_UNEXPECTED   ;
+      unsigned int *row = &outputParserNumberArray[0];
+      if (!(*row)--) return OBP_UNEXPECTED;
+
+      logOutputAction("vpa", "set cursor row");
+      ptySetCursorRow(*row);
+      return OBP_DONE;
+    }
+
+    case 'h':
+      return performBracketAction_h(byte);
+
+    case 'l':
+      return performBracketAction_l(byte);
+
+    case 'm':
+      return performBracketAction_m(byte);
+
+    case 'r': {
+      if (outputParserNumberCount != 2) return OBP_UNEXPECTED;
+
+      unsigned int *top = &outputParserNumberArray[0];
+      unsigned int *bottom = &outputParserNumberArray[1];
+
+      if (!(*top)--) return OBP_UNEXPECTED;
+      if (!(*bottom)--) return OBP_UNEXPECTED;
+
+      logOutputAction("csr", "set scroll region");
+      ptySetScrollRegion(*top, *bottom);
+      return OBP_DONE;
+    }
+
+    case '@':
+      logOutputAction("ic", "insert characters");
+      ptyInsertCharacters(getOutputActionCount());
+      return OBP_DONE;
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+performQuestionMarkAction_h (unsigned char byte) {
+  if (outputParserNumberCount == 1) {
+    switch (outputParserNumberArray[0]) {
+      case 1:
+        logOutputAction("smkx", "keypad transmit on");
+        keypadTransmitMode = 1;
+        return OBP_DONE;
+
+      case 25:
+        logOutputAction("cnorm", "cursor normal visibility");
+        ptySetCursorVisibility(1);
+        return OBP_DONE;
+
+      case 1049:
+        logOutputAction("smcup", "absolute cursor addressing on");
+        absoluteCursorAddressingMode = 1;
+        return OBP_DONE;
+
+      case 2004:
+        logOutputAction("smbp", "bracketed paste on");
+        bracketedPasteMode = 1;
+        return OBP_DONE;
+    }
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+performQuestionMarkAction_l (unsigned char byte) {
+  if (outputParserNumberCount == 1) {
+    switch (outputParserNumberArray[0]) {
+      case 1:
+        logOutputAction("rmkx", "keypad transmit off");
+        keypadTransmitMode = 0;
+        return OBP_DONE;
+
+      case 25:
+        logOutputAction("civis", "cursor invisible");
+        ptySetCursorVisibility(0);
+        return OBP_DONE;
+
+      case 1049:
+        logOutputAction("rmcup", "absolute cursor addressing off");
+        absoluteCursorAddressingMode = 0;
+        return OBP_DONE;
+
+      case 2004:
+        logOutputAction("rmbp", "bracketed paste off");
+        bracketedPasteMode = 0;
+        return OBP_DONE;
+    }
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+performQuestionMarkAction (unsigned char byte) {
+  switch (byte) {
+    case 'h':
+      return performQuestionMarkAction_h(byte);
+
+    case 'l':
+      return performQuestionMarkAction_l(byte);
+  }
+
+  return OBP_UNEXPECTED;
+}
+
+static OutputByteParserResult
+parseOutputByte_ACTION (unsigned char byte) {
+  if (outputParserQuestionMark) {
+    return performQuestionMarkAction(byte);
+  } else {
+    return performBracketAction(byte);
+  }
+}
+
+typedef struct {
+  OutputByteParser *parseOutputByte;
+  const char *name;
+} OutputParserStateEntry;
+
+#define OPS(state) \
+[OPS_##state] = { \
+  .parseOutputByte = parseOutputByte_##state, \
+  .name = #state, \
+}
+
+static const OutputParserStateEntry outputParserStateTable[] = {
+  OPS(BASIC),
+  OPS(ESCAPE),
+  OPS(BRACKET),
+  OPS(NUMBER),
+  OPS(DIGIT),
+  OPS(ACTION),
+};
+#undef OPS
+
+static int
+parseOutputByte (unsigned char byte) {
+  if (outputParserState == OPS_BASIC) {
+    outputByteCount = 0;
+  }
+
+  if (outputByteCount < ARRAY_COUNT(outputByteBuffer)) {
+    outputByteBuffer[outputByteCount++] = byte;
+  }
+
+  while (1) {
+    OutputByteParserResult result = outputParserStateTable[outputParserState].parseOutputByte(byte);
+
+    switch (result) {
+      case OBP_REPROCESS:
+        continue;
+
+      case OBP_UNEXPECTED:
+        logUnexpectedSequence();
+        /* fall through */
+
+      case OBP_DONE:
+        outputParserState = OPS_BASIC;
+        return 1;
+
+      case OBP_CONTINUE:
+        return 0;
+    }
+  }
+}
+
+int
+ptyProcessTerminalOutput (const unsigned char *bytes, size_t count) {
+  int wantRefresh = 0;
+
+  const unsigned char *byte = bytes;
+  const unsigned char *end = byte + count;
+
+  while (byte < end) {
+    wantRefresh = parseOutputByte(*byte++);
+  }
+
+  if (wantRefresh) {
+    ptyRefreshScreen();
+  }
+
+  return 1;
+}
diff --git a/Programs/queue.c b/Programs/queue.c
new file mode 100644
index 0000000..effdbcf
--- /dev/null
+++ b/Programs/queue.c
@@ -0,0 +1,449 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "queue.h"
+#include "lock.h"
+#include "program.h"
+
+static Element *discardedElements = NULL;
+
+static LockDescriptor *
+getDiscardedElementsLock (void) {
+  static LockDescriptor *lock = NULL;
+
+  return getLockDescriptor(&lock, "queue-discarded-elements");
+}
+
+static void
+lockDiscardedElements (void) {
+  obtainExclusiveLock(getDiscardedElementsLock());
+}
+
+static void
+unlockDiscardedElements (void) {
+  releaseLock(getDiscardedElementsLock());
+}
+
+struct QueueStruct {
+  Element *head;
+  unsigned int size;
+  void *data;
+  ItemDeallocator *deallocateItem;
+  ItemComparator *compareItems;
+};
+
+struct ElementStruct {
+  Element *next;
+  Element *previous;
+  Queue *queue;
+  int identifier;
+  void *item;
+};
+
+static void
+addElement (Queue *queue, Element *element) {
+  {
+    static int identifier = 0;
+    element->identifier = ++identifier;
+  }
+
+  element->queue = queue;
+  queue->size += 1;
+}
+
+static void
+removeElement (Element *element) {
+  element->queue->size -= 1;
+  element->queue = NULL;
+  element->identifier = 0;
+}
+
+static void
+removeItem (Element *element) {
+  if (element->item) {
+    Queue *queue = element->queue;
+    ItemDeallocator *deallocateItem = queue->deallocateItem;
+
+    if (deallocateItem) deallocateItem(element->item, queue->data);
+    element->item = NULL;
+  }
+}
+
+static void
+discardElement (Element *element) {
+  removeItem(element);
+  removeElement(element);
+
+  lockDiscardedElements();
+    element->next = discardedElements;
+    discardedElements = element;
+  unlockDiscardedElements();
+}
+
+static Element *
+retrieveElement (void) {
+  Element *element;
+
+  lockDiscardedElements();
+    if ((element = discardedElements)) {
+      discardedElements = element->next;
+      element->next = NULL;
+    }
+  unlockDiscardedElements();
+
+  return element;
+}
+
+static Element *
+newElement (Queue *queue, void *item) {
+  Element *element;
+
+  if (!(element = retrieveElement())) {
+    if (!(element = malloc(sizeof(*element)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    element->previous = element->next = NULL;
+  }
+
+  addElement(queue, element);
+  element->item = item;
+  return element;
+}
+
+static void
+linkFirstElement (Element *element) {
+  element->queue->head = element->previous = element->next = element;
+}
+
+static void
+linkAdditionalElement (Element *reference, Element *element) {
+  element->next = reference;
+  element->previous = reference->previous;
+  element->next->previous = element;
+  element->previous->next = element;
+}
+
+static void
+unlinkElement (Element *element) {
+  Queue *queue = element->queue;
+  if (element == element->next) {
+    queue->head = NULL;
+  } else {
+    if (element == queue->head) queue->head = element->next;
+    element->next->previous = element->previous;
+    element->previous->next = element->next;
+  }
+  element->previous = element->next = NULL;
+}
+
+void
+deleteElement (Element *element) {
+  unlinkElement(element);
+  discardElement(element);
+}
+
+typedef struct {
+  Queue *queue;
+  void *item;
+} FindReferenceElementData;
+
+static int
+findReferenceElement (const void *item, void *data) {
+  const FindReferenceElementData *fre = data;
+
+  return fre->queue->compareItems(fre->item, item, fre->queue->data);
+}
+
+static void
+linkElement (Element *element) {
+  Queue *queue = element->queue;
+
+  if (queue->head) {
+    Element *reference;
+    int isNewHead = 0;
+
+    if (queue->compareItems) {
+      FindReferenceElementData fre = {
+        .queue = queue,
+        .item = element->item
+      };
+
+      if (!(reference = findElement(queue, findReferenceElement, &fre))) {
+        reference = queue->head;
+      } else if (reference == queue->head) {
+        isNewHead = 1;
+      }
+    } else {
+      reference = queue->head;
+    }
+
+    linkAdditionalElement(reference, element);
+    if (isNewHead) queue->head = element;
+  } else {
+    linkFirstElement(element);
+  }
+}
+
+Element *
+enqueueItem (Queue *queue, void *item) {
+  Element *element = newElement(queue, item);
+
+  if (element) linkElement(element);
+  return element;
+}
+
+void
+requeueElement (Element *element) {
+  unlinkElement(element);
+  linkElement(element);
+}
+
+void
+moveElement (Element *element, Queue *queue) {
+  unlinkElement(element);
+  removeElement(element);
+  addElement(queue, element);
+  linkElement(element);
+}
+
+void *
+dequeueItem (Queue *queue) {
+  void *item;
+  Element *element;
+
+  if (!(element = queue->head)) return NULL;
+  item = element->item;
+  element->item = NULL;
+
+  deleteElement(element);
+  return item;
+}
+
+Queue *
+getElementQueue (const Element *element) {
+  return element->queue;
+}
+
+int
+getElementIdentifier (const Element *element) {
+  return element->identifier;
+}
+
+void *
+getElementItem (const Element *element) {
+  return element->item;
+}
+
+static int queueInitialized = 0;
+
+static void
+exitQueue (void *data) {
+  lockDiscardedElements();
+    while (discardedElements) {
+      Element *element = discardedElements;
+      discardedElements = element->next;
+      free(element);
+    }
+  unlockDiscardedElements();
+
+  queueInitialized = 0;
+}
+
+Queue *
+newQueue (ItemDeallocator *deallocateItem, ItemComparator *compareItems) {
+  Queue *queue;
+
+  if (!queueInitialized) {
+    queueInitialized = 1;
+    onProgramExit("queue", exitQueue, NULL);
+  }
+
+  if ((queue = malloc(sizeof(*queue)))) {
+    queue->head = NULL;
+    queue->size = 0;
+    queue->data = NULL;
+    queue->deallocateItem = deallocateItem;
+    queue->compareItems = compareItems;
+    return queue;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+deleteElements (Queue *queue) {
+  while (queue->head) deleteElement(queue->head);
+}
+
+void
+deallocateQueue (Queue *queue) {
+  deleteElements(queue);
+  free(queue);
+}
+
+static void
+exitProgramQueue (void *data) {
+  Queue **queue = data;
+
+  if (*queue) {
+    deallocateQueue(*queue);
+    *queue = NULL;
+  }
+}
+
+Queue *
+getProgramQueue (
+  Queue **queue, const char *name, int create,
+  QueueCreator *createQueue, void *data
+) {
+  if (!*queue && create) {
+    if ((*queue = createQueue(data))) {
+      onProgramExit(name, exitProgramQueue, queue);
+    }
+  }
+
+  return *queue;
+}
+
+int
+getQueueSize (const Queue *queue) {
+  return queue->size;
+}
+
+void *
+getQueueData (const Queue *queue) {
+  return queue->data;
+}
+
+void *
+setQueueData (Queue *queue, void *data) {
+  void *previous = queue->data;
+  queue->data = data;
+  return previous;
+}
+
+Element *
+getQueueHead (const Queue *queue) {
+  return queue->head;
+}
+
+Element *
+getStackHead (const Queue *queue) {
+  Element *head = queue->head;
+  return head? head->previous: NULL;
+}
+
+static Element *
+getElementByIndex (const Queue *queue, unsigned int index, int fromTail) {
+  if (index < queue->size) {
+    Element *element = queue->head;
+
+    {
+      int i = queue->size - 1 - index;
+
+      if (i < index) {
+        index = i;
+        fromTail = !fromTail;
+      }
+    }
+
+    if (fromTail) element = element->previous;
+
+    while (index > 0) {
+      element = fromTail? element->previous: element->next;
+      index -= 1;
+    }
+
+    return element;
+  }
+
+  return 0;
+}
+
+Element *
+getQueueElement (const Queue *queue, unsigned int index) {
+  return getElementByIndex(queue, index, 0);
+}
+
+Element *
+getStackElement (const Queue *queue, unsigned int index) {
+  return getElementByIndex(queue, index, 1);
+}
+
+Element *
+findElement (const Queue *queue, ItemTester *testItem, void *data) {
+  if (queue->head) {
+    Element *element = queue->head;
+
+    do {
+      if (testItem(element->item, data)) return element;
+    } while ((element = element->next) != queue->head);
+  }
+  return NULL;
+}
+
+void *
+findItem (const Queue *queue, ItemTester *testItem, void *data) {
+  Element *element = findElement(queue, testItem, data);
+  if (element) return element->item;
+  return NULL;
+}
+
+static int
+testElementHasItem (const void *item, void *data) {
+  return item == data;
+}
+
+Element *
+findElementWithItem (const Queue *queue, void *item) {
+  return findElement(queue, testElementHasItem, item);
+}
+
+Element *
+processQueue (Queue *queue, ItemProcessor *processItem, void *data) {
+  Element *element = queue->head;
+  while (element) {
+    Element *next = element->next;
+    if (next == queue->head) next = NULL;
+    if (processItem(element->item, data)) return element;
+    element = next;
+  }
+  return NULL;
+}
+
+static int
+testItemAddress (const void *item, void *data) {
+  return item == data;
+}
+
+int
+deleteItem (Queue *queue, void *item) {
+  Element *element = findElement(queue, testItemAddress, item);
+  if (!element) return 0;
+
+  element->item = NULL;
+  deleteElement(element);
+  return 1;
+}
diff --git a/Programs/report.c b/Programs/report.c
new file mode 100644
index 0000000..d4f04f5
--- /dev/null
+++ b/Programs/report.c
@@ -0,0 +1,209 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "report.h"
+#include "queue.h"
+
+struct ReportListenerInstanceStruct {
+  Element *element;
+  ReportListener *listener;
+  void *data;
+};
+
+typedef struct {
+  ReportIdentifier identifier;
+  Queue *listeners;
+} ReportEntry;
+
+static ReportEntry **reportTable = NULL;
+static unsigned int reportSize = 0;
+static unsigned int reportCount = 0;
+
+static int
+findReportEntry (ReportIdentifier identifier, int *position) {
+  int first = 0;
+  int last = reportCount - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    ReportEntry *report = reportTable[current];
+
+    if (report->identifier < identifier) {
+      first = current + 1;
+    } else if (report->identifier > identifier) {
+      last = current - 1;
+    } else {
+      *position = current;
+      return 1;
+    }
+  }
+
+  *position = first;
+  return 0;
+}
+
+static ReportEntry *
+getReportEntry (ReportIdentifier identifier, int add) {
+  int position;
+  int found = findReportEntry(identifier, &position);
+
+  if (found) return reportTable[position];
+  if (!add) return NULL;
+
+  if (reportCount == reportSize) {
+    unsigned int newSize = reportCount? (reportCount << 1): 1;
+    ReportEntry **newTable = realloc(reportTable, ARRAY_SIZE(reportTable, newSize));
+
+    if (!newTable) {
+      logMallocError();
+      return NULL;
+    }
+
+    reportTable = newTable;
+    reportSize = newSize;
+  }
+
+  {
+    ReportEntry **slot = &reportTable[position];
+    ReportEntry *report = malloc(sizeof(*report));
+
+    if (!report) {
+      logMallocError();
+      return NULL;
+    }
+
+    memset(report, 0, sizeof(*report));
+    report->identifier = identifier;
+    report->listeners = NULL;
+
+    memmove(slot+1, slot, ((reportCount++ - position) * sizeof(*slot)));
+    *slot = report;
+
+    return report;
+  }
+}
+
+static int
+tellListener (void *item, void *data) {
+  ReportListenerInstance *rli = item;
+  ReportListenerParameters *parameters = data;
+
+  parameters->listenerData = rli->data;
+  rli->listener(parameters);
+
+  return 0;
+}
+
+void
+report (ReportIdentifier identifier, const void *data) {
+  ReportEntry *report = getReportEntry(identifier, 0);
+
+  if (report) {
+    if (report->listeners) {
+      ReportListenerParameters parameters = {
+        .reportIdentifier = identifier,
+        .reportData = data,
+        .listenerData = NULL
+      };
+
+      processQueue(report->listeners, tellListener, &parameters);
+    }
+  }
+}
+
+void
+reportParameterUpdated (brlapi_param_t parameter, brlapi_param_subparam_t subparam) {
+  const ApiParameterUpdatedReport data = {
+    .parameter = parameter,
+    .subparam = subparam
+  };
+
+  report(REPORT_API_PARAMETER_UPDATED, &data);
+}
+
+static int
+testListener (void *item, void *data) {
+  ReportListenerInstance *rli = item;
+  ReportListener *listener = data;
+
+  return rli->listener == listener;
+}
+
+static Element *
+findListenerElement (ReportEntry *report, ReportListener *listener) {
+  return processQueue(report->listeners, testListener, listener);
+}
+
+static void
+deallocateReportListenerInstance (void *item, void *data) {
+  ReportListenerInstance *rli = item;
+  ReportEntry *report = data;
+
+  logSymbol(LOG_DEBUG, rli->listener,
+            "report listener unregistered: %u", report->identifier);
+
+  free(rli);
+}
+
+ReportListenerInstance *
+registerReportListener (ReportIdentifier identifier, ReportListener *listener, void *data) {
+  ReportEntry *report = getReportEntry(identifier, 1);
+
+  if (report) {
+    if (!report->listeners) {
+      if (!(report->listeners = newQueue(deallocateReportListenerInstance, NULL))) {
+        return NULL;
+      }
+
+      setQueueData(report->listeners, report);
+    }
+
+    if (findListenerElement(report, listener)) {
+      logSymbol(LOG_WARNING, listener, "report listener already registered: %u", identifier);
+    } else {
+      ReportListenerInstance *rli;
+
+      if ((rli = malloc(sizeof(*rli)))) {
+        memset(rli, 0, sizeof(*rli));
+        rli->listener = listener;
+        rli->data = data;
+
+        if ((rli->element = enqueueItem(report->listeners, rli))) {
+          logSymbol(LOG_DEBUG, listener, "report listener registered: %u", identifier);
+          return rli;
+        }
+
+        free(rli);
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return NULL;
+}
+
+void
+unregisterReportListener (ReportListenerInstance *rli) {
+  deleteElement(rli->element);
+}
diff --git a/Programs/report.h b/Programs/report.h
new file mode 100644
index 0000000..7c80ced
--- /dev/null
+++ b/Programs/report.h
@@ -0,0 +1,86 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_REPORT
+#define BRLTTY_INCLUDED_REPORT
+
+#include "brlapi_param.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  REPORT_BRAILLE_DEVICE_ONLINE,
+  REPORT_BRAILLE_DEVICE_OFFLINE,
+  REPORT_BRAILLE_WINDOW_MOVED,
+  REPORT_BRAILLE_WINDOW_UPDATED,
+  REPORT_BRAILLE_KEY_EVENT,
+  REPORT_API_PARAMETER_UPDATED,
+} ReportIdentifier;
+
+extern void report (ReportIdentifier identiier, const void *data);
+
+extern void reportParameterUpdated (
+  brlapi_param_t parameter, brlapi_param_subparam_t subparam
+);
+
+typedef struct {
+  ReportIdentifier reportIdentifier;
+  const void *reportData;
+  void *listenerData;
+} ReportListenerParameters;
+
+#define REPORT_LISTENER(name) void name (const ReportListenerParameters *parameters)
+typedef REPORT_LISTENER(ReportListener);
+typedef struct ReportListenerInstanceStruct ReportListenerInstance;
+
+extern ReportListenerInstance *registerReportListener (
+  ReportIdentifier identifier,
+  ReportListener *listener,
+  void *data
+);
+
+extern void unregisterReportListener (ReportListenerInstance *rli);
+
+typedef struct {
+  struct {
+    unsigned int column;
+    unsigned int row;
+  } screen;
+
+  struct {
+    unsigned int count;
+  } text;
+} BrailleWindowMovedReport;
+
+typedef struct {
+  const unsigned char *cells;
+  unsigned int count;
+} BrailleWindowUpdatedReport;
+
+typedef struct {
+  brlapi_param_t parameter;
+  brlapi_param_subparam_t subparam;
+} ApiParameterUpdatedReport;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_REPORT */
diff --git a/Programs/revision.c b/Programs/revision.c
new file mode 100644
index 0000000..c9bd2b1
--- /dev/null
+++ b/Programs/revision.c
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "revision.h"
+
+static const char revisionIdentifier[] =
+#include "revision_identifier.auto.h"
+;
+
+const char *
+getRevisionIdentifier (void) {
+  return revisionIdentifier;
+}
diff --git a/Programs/revision_identifier.auto.h b/Programs/revision_identifier.auto.h
new file mode 100644
index 0000000..6f2f1aa
--- /dev/null
+++ b/Programs/revision_identifier.auto.h
@@ -0,0 +1 @@
+"BRLTTY-6.6"
diff --git a/Programs/rgx.c b/Programs/rgx.c
new file mode 100644
index 0000000..cf80719
--- /dev/null
+++ b/Programs/rgx.c
@@ -0,0 +1,421 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "rgx.h"
+#include "rgx_internal.h"
+#include "utf8.h"
+#include "queue.h"
+#include "strfmt.h"
+
+#define RGX_UTF8_TO_CHARACTERS \
+  size_t size = strlen(string) + 1; \
+  wchar_t characters[size]; \
+  size_t count; \
+  { \
+    const char *from = string; \
+    wchar_t *to = characters; \
+    convertUtf8ToWchars(&from, &to, size); \
+    count = to - characters; \
+  }
+
+#define RGX_CHARACTERS_TO_INTERNAL \
+  RGX_CharacterType internal[length + 1]; \
+  internal[length] = 0; \
+  for (unsigned int index=0; index<length; index+=1) { \
+    internal[index] = characters[index]; \
+  }
+
+struct RGX_ObjectStruct {
+  void *data;
+  Queue *matchers;
+  RGX_OptionsType options;
+};
+
+struct RGX_MatcherStruct {
+  void *data;
+  RGX_MatchHandler *handler;
+  RGX_OptionsType options;
+
+  struct {
+    wchar_t *characters;
+    size_t length;
+  } pattern;
+
+  struct {
+    RGX_CodeType *code;
+    RGX_DataType *data;
+  } compiled;
+};
+
+static void
+rgxLogError (int error, const RGX_Matcher *matcher, RGX_OffsetType *offset) {
+  char log[0X100];
+  STR_BEGIN(log, sizeof(log));
+
+  STR_PRINTF("regular expression error");
+  if (offset) STR_PRINTF(" at offset %"PRIu32, (uint32_t)*offset);
+  STR_PRINTF(": ");
+
+  {
+    size_t oldLength = STR_LENGTH;
+    STR_FORMAT(rgxFormatErrorMessage, error);
+    if (STR_LENGTH == oldLength) STR_PRINTF("unrecognized error %d", error);
+  }
+
+  if (matcher) {
+    STR_PRINTF(
+      ": %.*"PRIws, (int)matcher->pattern.length, matcher->pattern.characters
+    );
+  }
+
+  STR_END;
+  logMessage(LOG_WARNING, "%s", log);
+}
+
+static void
+rgxDeallocateMatcher (void *item, void *data) {
+  RGX_Matcher *matcher = item;
+
+  rgxDeallocateData(matcher->compiled.data);
+  rgxDeallocateCode(matcher->compiled.code);
+  free(matcher->pattern.characters);
+  free(matcher);
+}
+
+RGX_Matcher *
+rgxAddPatternCharacters (
+  RGX_Object *rgx,
+  const wchar_t *characters, size_t length,
+  RGX_MatchHandler *handler, void *data
+) {
+  RGX_Matcher *matcher;
+
+  if ((matcher = malloc(sizeof(*matcher)))) {
+    memset(matcher, 0, sizeof(*matcher));
+    matcher->data = data;
+    matcher->handler = handler;
+    matcher->options = 0;
+
+    matcher->pattern.characters = calloc(
+      (matcher->pattern.length = length) + 1,
+      sizeof(*matcher->pattern.characters)
+    );
+
+    if (matcher->pattern.characters) {
+      wmemcpy(matcher->pattern.characters, characters, length);
+      matcher->pattern.characters[length] = 0;
+
+      RGX_CHARACTERS_TO_INTERNAL;
+      int error;
+      RGX_OffsetType offset;
+
+      matcher->compiled.code = rgxCompilePattern(
+        internal, length, rgx->options, &offset, &error
+      );
+
+      if (matcher->compiled.code) {
+        matcher->compiled.data = rgxAllocateData(matcher->compiled.code);
+
+        if (matcher->compiled.data) {
+          if (enqueueItem(rgx->matchers, matcher)) {
+            return matcher;
+          }
+
+          rgxDeallocateData(matcher->compiled.data);
+        } else {
+          logMallocError();
+        }
+
+        rgxDeallocateCode(matcher->compiled.code);
+      } else {
+        rgxLogError(error, matcher, &offset);
+      }
+
+      free(matcher->pattern.characters);
+    } else {
+      logMallocError();
+    }
+
+    free(matcher);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+RGX_Matcher *
+rgxAddPatternString (
+  RGX_Object *rgx,
+  const wchar_t *string,
+  RGX_MatchHandler *handler, void *data
+) {
+  return rgxAddPatternCharacters(rgx, string, wcslen(string), handler, data);
+}
+
+RGX_Matcher *
+rgxAddPatternUTF8 (
+  RGX_Object *rgx,
+  const char *string,
+  RGX_MatchHandler *handler, void *data
+) {
+  RGX_UTF8_TO_CHARACTERS;
+  return rgxAddPatternCharacters(rgx, characters, count, handler, data);
+}
+
+static int
+rgxTestMatcher (const void *item, void *data) {
+  const RGX_Matcher *matcher = item;
+  RGX_Match *match = data;
+
+  int error;
+  int matched = rgxMatchText(
+    match->text.internal, match->text.length,
+    matcher->compiled.code, matcher->compiled.data,
+    matcher->options, &match->capture.count, &error
+  );
+
+  if (!matched) {
+    if (error != RGX_NO_MATCH) rgxLogError(error, matcher, NULL);
+    return 0;
+  }
+
+  RGX_MatchHandler *handler = matcher->handler;
+  if (!handler) return 1;
+
+  match->matcher = matcher;
+  match->data.pattern = matcher->data;
+  match->pattern.characters = matcher->pattern.characters;
+  match->pattern.length = matcher->pattern.length;
+  return handler(match);
+}
+
+RGX_Matcher *
+rgxMatchTextCharacters (
+  RGX_Object *rgx,
+  const wchar_t *characters, size_t length,
+  RGX_Match **result, void *data
+) {
+  RGX_CHARACTERS_TO_INTERNAL;
+
+  RGX_Match match = {
+    .text = {
+      .internal = internal,
+      .characters = characters,
+      .length = length
+    },
+
+    .data = {
+      .object = rgx->data,
+      .match = data
+    }
+  };
+
+  Element *element = findElement(rgx->matchers, rgxTestMatcher, &match);
+  if (!element) return NULL;
+
+  if (result) {
+    typedef struct {
+      RGX_Match match;
+      wchar_t text[];
+    } Block;
+
+    Block *block;
+    size_t size = sizeof(*block);
+    size += (length + 1) * sizeof(block->text[0]);
+
+    if (!(block = malloc(size))) {
+      logMallocError();
+      return NULL;
+    }
+
+    block->match = match;
+    block->match.text.internal = NULL;
+
+    wmemcpy(block->text, match.text.characters, length);
+    block->text[length] = 0;
+    block->match.text.characters = block->text;
+
+    *result = &block->match;
+  }
+
+  return getElementItem(element);
+}
+
+RGX_Matcher *
+rgxMatchTextString (
+  RGX_Object *rgx,
+  const wchar_t *string,
+  RGX_Match **result, void *data
+) {
+  return rgxMatchTextCharacters(rgx, string, wcslen(string), result, data);
+}
+
+RGX_Matcher *
+rgxMatchTextUTF8 (
+  RGX_Object *rgx,
+  const char *string,
+  RGX_Match **result, void *data
+) {
+  RGX_UTF8_TO_CHARACTERS;
+  return rgxMatchTextCharacters(rgx, characters, count, result, data);
+}
+
+int
+rgxGetNameNumberCharacters (
+  const RGX_Matcher *matcher,
+  const wchar_t *characters, size_t length,
+  size_t *number
+) {
+  RGX_CHARACTERS_TO_INTERNAL;
+
+  int error;
+  if (rgxNameNumber(matcher->compiled.code, internal, number, &error)) return 1;
+
+  if (error != RGX_NO_NAME) rgxLogError(error, matcher, NULL);
+  return 0;
+}
+
+int
+rgxGetNameNumberString (
+  const RGX_Matcher *matcher,
+  const wchar_t *string,
+  size_t *number
+) {
+  return rgxGetNameNumberCharacters(matcher, string, wcslen(string), number);
+}
+
+int
+rgxGetNameNumberUTF8 (
+  const RGX_Matcher *matcher,
+  const char *string,
+  size_t *number
+) {
+  RGX_UTF8_TO_CHARACTERS;
+  return rgxGetNameNumberCharacters(matcher, characters, count, number);
+}
+
+size_t
+rgxGetCaptureCount (
+  const RGX_Match *match
+) {
+  return match->capture.count;
+}
+
+int
+rgxGetCaptureBounds (
+  const RGX_Match *match,
+  size_t number, size_t *from, size_t *to
+) {
+  if (number > match->capture.count) return 0;
+  return rgxCaptureBounds(match->matcher->compiled.data, number, from, to);
+}
+
+int
+rgxGetCaptureText (
+  const RGX_Match *match,
+  size_t number, const wchar_t **characters, size_t *length
+) {
+  size_t from, to;
+  if (!rgxGetCaptureBounds(match, number, &from, &to)) return 0;
+
+  *characters = &match->text.characters[from];
+  *length = to - from;
+  return 1;
+}
+
+RGX_Object *
+rgxNewObject (void *data) {
+  RGX_Object *rgx;
+
+  if ((rgx = malloc(sizeof(*rgx)))) {
+    memset(rgx, 0, sizeof(*rgx));
+    rgx->data = data;
+    rgx->options = 0;
+
+    if ((rgx->matchers = newQueue(rgxDeallocateMatcher, NULL))) {
+      return rgx;
+    }
+
+    free(rgx);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+rgxDestroyObject (RGX_Object *rgx) {
+  deallocateQueue(rgx->matchers);
+  free(rgx);
+}
+
+static int
+rgxOption (
+  RGX_OptionAction action, int option,
+  RGX_OptionsType *bits, const RGX_OptionMap *map
+) {
+  RGX_OptionsType bit = ((option >= 0) && (option < map->count))? map->array[option]: 0;
+  int wasSet = !!(*bits & bit);
+
+  if (action == RGX_OPTION_TOGGLE) {
+    action = wasSet? RGX_OPTION_CLEAR: RGX_OPTION_SET;
+  }
+
+  switch (action) {
+    case RGX_OPTION_SET:
+      *bits |= bit;
+      break;
+
+    case RGX_OPTION_CLEAR:
+      *bits &= ~bit;
+      break;
+
+    default:
+      logMessage(LOG_WARNING, "unimplemented regular expression option action: %d", action);
+      /* fall through */
+    case RGX_OPTION_TEST:
+      break;
+  }
+
+  return wasSet;
+}
+
+int
+rgxCompileOption (
+  RGX_Object *rgx,
+  RGX_OptionAction action,
+  RGX_CompileOption option
+) {
+  return rgxOption(action, option, &rgx->options, &rgxCompileOptionsMap);
+}
+
+int
+rgxMatchOption (
+  RGX_Matcher *matcher,
+  RGX_OptionAction action,
+  RGX_MatchOption option
+) {
+  return rgxOption(action, option, &matcher->options, &rgxMatchOptionsMap);
+}
diff --git a/Programs/rgx_internal.h b/Programs/rgx_internal.h
new file mode 100644
index 0000000..0d84039
--- /dev/null
+++ b/Programs/rgx_internal.h
@@ -0,0 +1,119 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_RGX_INTERNAL
+#define BRLTTY_INCLUDED_RGX_INTERNAL
+
+#include "strfmth.h"
+
+#if defined(USE_PKG_RGX_NONE)
+#define RGX_NO_MATCH 1
+#define RGX_NO_NAME 2
+
+typedef wchar_t RGX_CharacterType;
+typedef size_t RGX_OffsetType;
+typedef int RGX_OptionsType;
+typedef uint8_t RGX_CodeType;
+typedef uint8_t RGX_DataType;
+
+#elif defined(USE_PKG_RGX_LIBPCRE32)
+#include <pcre.h>
+
+#define RGX_NO_MATCH PCRE_ERROR_NOMATCH
+#define RGX_NO_NAME PCRE_ERROR_NOSUBSTRING
+
+typedef PCRE_UCHAR32 RGX_CharacterType;
+typedef int RGX_OffsetType;
+typedef int RGX_OptionsType;
+typedef pcre32 RGX_CodeType;
+
+typedef struct {
+  pcre32_extra *study;
+  size_t matches;
+  size_t count;
+  RGX_OffsetType offsets[];
+} RGX_DataType;
+
+#elif defined(USE_PKG_RGX_LIBPCRE2_32)
+#define PCRE2_CODE_UNIT_WIDTH 32
+#include <pcre2.h>
+
+#define RGX_NO_MATCH PCRE2_ERROR_NOMATCH
+#define RGX_NO_NAME PCRE2_ERROR_NOSUBSTRING
+
+typedef PCRE2_UCHAR RGX_CharacterType;
+typedef PCRE2_SIZE RGX_OffsetType;
+typedef uint32_t RGX_OptionsType;
+typedef pcre2_code RGX_CodeType;
+typedef pcre2_match_data RGX_DataType;
+
+#else /* regular expression package */
+#error regular expression package not selected
+#endif /* regular expression package */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern RGX_CodeType *rgxCompilePattern (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_OptionsType options, RGX_OffsetType *offset,
+  int *error
+);
+
+extern void rgxDeallocateCode (RGX_CodeType *code);
+extern RGX_DataType *rgxAllocateData (RGX_CodeType *code);
+extern void rgxDeallocateData (RGX_DataType *data);
+
+extern int rgxMatchText (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_CodeType *code, RGX_DataType *data,
+  RGX_OptionsType options, size_t *count, int *error
+);
+
+extern int rgxNameNumber (
+  RGX_CodeType *code, const RGX_CharacterType *name,
+  size_t *number, int *error
+);
+
+extern int rgxCaptureBounds (
+  RGX_DataType *data, size_t number, size_t *from, size_t *to
+);
+
+extern STR_DECLARE_FORMATTER(rgxFormatErrorMessage, int error);
+
+typedef struct {
+  const RGX_OptionsType *array;
+  uint8_t count;
+} RGX_OptionMap;
+
+extern const RGX_OptionMap rgxCompileOptionsMap;
+extern const RGX_OptionMap rgxMatchOptionsMap;
+
+#define RGX_BEGIN_OPTION_MAP(name) static const RGX_OptionsType name##Array[] = {
+#define RGX_END_OPTION_MAP(name) }; \
+  const RGX_OptionMap name##Map = { \
+  .array = name##Array, \
+  .count = ARRAY_COUNT(name##Array) \
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_RGX_INTERNAL */
diff --git a/Programs/rgx_libpcre2-32.c b/Programs/rgx_libpcre2-32.c
new file mode 100644
index 0000000..17f9d88
--- /dev/null
+++ b/Programs/rgx_libpcre2-32.c
@@ -0,0 +1,123 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "rgx.h"
+#include "rgx_internal.h"
+#include "strfmt.h"
+
+RGX_CodeType *
+rgxCompilePattern (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_OptionsType options, RGX_OffsetType *offset,
+  int *error
+) {
+  return pcre2_compile(
+    characters, length, options, error, offset, NULL
+  );
+}
+
+void
+rgxDeallocateCode (RGX_CodeType *code) {
+  pcre2_code_free(code);
+}
+
+RGX_DataType *
+rgxAllocateData (RGX_CodeType *code) {
+  return pcre2_match_data_create_from_pattern(code, NULL);
+}
+
+void
+rgxDeallocateData (RGX_DataType *data) {
+  pcre2_match_data_free(data);
+}
+
+int
+rgxMatchText (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_CodeType *code, RGX_DataType *data,
+  RGX_OptionsType options, size_t *count, int *error
+) {
+  int result = pcre2_match(
+    code, characters, length, 0, options, data, NULL
+  );
+
+  if (result < 0) {
+    *error = result;
+    return 0;
+  }
+
+  if (!result) result = pcre2_get_ovector_count(data);
+  *count = result - 1;
+  return 1;
+}
+
+int
+rgxNameNumber (
+  RGX_CodeType *code, const RGX_CharacterType *name,
+  size_t *number, int *error
+) {
+  int result = pcre2_substring_number_from_name(code, name);
+
+  if (result > 0) {
+    *number = result;
+    return 1;
+  } else {
+    *error = result;
+    return 0;
+  }
+}
+
+int
+rgxCaptureBounds (
+  RGX_DataType *data, size_t number, size_t *from, size_t *to
+) {
+  const RGX_OffsetType *offsets = pcre2_get_ovector_pointer(data);
+  offsets += number * 2;
+
+  if (offsets[0] == PCRE2_UNSET) return 0;
+  if (offsets[1] == PCRE2_UNSET) return 0;
+
+  *from = offsets[0];
+  *to = offsets[1];
+  return 1;
+}
+
+STR_BEGIN_FORMATTER(rgxFormatErrorMessage, int error)
+  size_t size = STR_LEFT;
+  RGX_CharacterType message[size];
+  int length = pcre2_get_error_message(error, message, size);
+
+  if (length > 0) {
+    for (unsigned int index=0; index<length; index+=1) {
+      STR_PRINTF("%"PRIwc, message[index]);
+    }
+  }
+STR_END_FORMATTER
+
+RGX_BEGIN_OPTION_MAP(rgxCompileOptions)
+  [RGX_COMPILE_ANCHOR_START] = PCRE2_ANCHORED,
+
+  [RGX_COMPILE_IGNORE_CASE] = PCRE2_CASELESS,
+  [RGX_COMPILE_UNICODE_PROPERTIES] = PCRE2_UCP,
+RGX_END_OPTION_MAP(rgxCompileOptions)
+
+RGX_BEGIN_OPTION_MAP(rgxMatchOptions)
+  [RGX_MATCH_ANCHOR_START] = PCRE2_ANCHORED,
+RGX_END_OPTION_MAP(rgxMatchOptions)
diff --git a/Programs/rgx_libpcre32.c b/Programs/rgx_libpcre32.c
new file mode 100644
index 0000000..d483f1f
--- /dev/null
+++ b/Programs/rgx_libpcre32.c
@@ -0,0 +1,218 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "rgx.h"
+#include "rgx_internal.h"
+#include "strfmt.h"
+
+static int savedErrorCode = 0;
+static const char *savedErrorMessage = NULL;
+
+static void
+saveErrorMessage (int error, const char *message) {
+  if (message && *message) {
+    savedErrorCode = error;
+    savedErrorMessage = message;
+  }
+}
+
+static const char *
+getErrorMessage (int error) {
+  if (error == savedErrorCode) return savedErrorMessage;
+  return NULL;
+}
+
+RGX_CodeType *
+rgxCompilePattern (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_OptionsType options, RGX_OffsetType *offset,
+  int *error
+) {
+  const char *message;
+
+  RGX_CodeType *code = pcre32_compile2(
+    characters, options, error, &message, offset, NULL
+  );
+
+  if (!code) saveErrorMessage(*error, message);
+  return code;
+}
+
+void
+rgxDeallocateCode (RGX_CodeType *code) {
+  pcre32_free(code);
+}
+
+RGX_DataType *
+rgxAllocateData (RGX_CodeType *code) {
+  RGX_DataType *data;
+  size_t size = sizeof(*data);
+
+  size_t matches = 10;
+  size_t count = matches * 3;
+  size += count * sizeof(data->offsets[0]);
+
+  if (!(data = malloc(size))) return NULL;
+  memset(data, 0, size);
+
+  data->matches = matches;
+  data->count = count;
+
+  {
+    const char *message = NULL;
+    data->study = pcre32_study(code, 0, &message);
+
+    if (message) {
+      logMessage(LOG_WARNING, "pcre study error: %s", message);
+
+      if (data->study) {
+        pcre32_free_study(data->study);
+        data->study = NULL;
+      }
+    }
+  }
+
+  return data;
+}
+
+void
+rgxDeallocateData (RGX_DataType *data) {
+  if (data->study) pcre32_free_study(data->study);
+  free(data);
+}
+
+int
+rgxMatchText (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_CodeType *code, RGX_DataType *data,
+  RGX_OptionsType options, size_t *count, int *error
+) {
+  int result = pcre32_exec(
+    code, data->study,
+    characters, length,
+    0, options,
+    data->offsets, data->count
+  );
+
+  if (result < 0) {
+    *error = result;
+    return 0;
+  }
+
+  if (!result) result = data->matches;
+  *count = result - 1;
+  return 1;
+}
+
+int
+rgxNameNumber (
+  RGX_CodeType *code, const RGX_CharacterType *name,
+  size_t *number, int *error
+) {
+  int result = pcre32_get_stringnumber(code, name);
+
+  if (result > 0) {
+    *number = result;
+    return 1;
+  } else {
+    *error = result;
+    return 0;
+  }
+}
+
+int
+rgxCaptureBounds (
+  RGX_DataType *data, size_t number, size_t *from, size_t *to
+) {
+  const RGX_OffsetType *offsets = data->offsets;
+  offsets += number * 2;
+
+  if (offsets[0] == -1) return 0;
+  if (offsets[1] == -1) return 0;
+
+  *from = offsets[0];
+  *to = offsets[1];
+  return 1;
+}
+
+static const char *const rgxNegativeErrors[] = {
+  [0] = "no error",
+   [-PCRE_ERROR_NOMATCH] = "no match",
+   [-PCRE_ERROR_NULL] = "required pointer argument is null",
+   [-PCRE_ERROR_BADOPTION] = "unrecognized option",
+   [-PCRE_ERROR_BADMAGIC] = "magic number not found",
+   [-PCRE_ERROR_UNKNOWN_OPCODE] = "invalid item in compiled pattern",
+   [-PCRE_ERROR_NOMEMORY] = "insufficient memory",
+   [-PCRE_ERROR_NOSUBSTRING] = "no capture with specified number or name",
+   [-PCRE_ERROR_MATCHLIMIT] = "match limit exceeded",
+   [-PCRE_ERROR_CALLOUT] = "error in callout",
+   [-PCRE_ERROR_BADUTF32] = "invalid UTF-32 character",
+   [-PCRE_ERROR_BADUTF16_OFFSET] = "start offset is within a multibyte character",
+   [-PCRE_ERROR_PARTIAL] = "partial match",
+   [-PCRE_ERROR_BADPARTIAL] = "pattern contains item not supported for partial match",
+   [-PCRE_ERROR_INTERNAL] = "internal error",
+   [-PCRE_ERROR_BADCOUNT] = "size of offsets vector is negative",
+   [-PCRE_ERROR_DFA_UITEM] = "pattern contains item not supported for DFA match",
+   [-PCRE_ERROR_DFA_UCOND] = "DFA match uses back reference for condition or test for recursion in specific group",
+   [-PCRE_ERROR_DFA_UMLIMIT] = "match or recursion limit specified for DFA match",
+   [-PCRE_ERROR_DFA_WSSIZE] = "DFA workspace overflow",
+   [-PCRE_ERROR_DFA_RECURSE] = "DFA recursion offsets vector too small",
+   [-PCRE_ERROR_RECURSIONLIMIT] = "recursion limit exceeded",
+   [-PCRE_ERROR_NULLWSLIMIT] = "",
+   [-PCRE_ERROR_BADNEWLINE] = "invalid newline option combination",
+   [-PCRE_ERROR_BADOFFSET] = "start offset out of bounds",
+   [-PCRE_ERROR_SHORTUTF16] = "truncated multibyte character",
+   [-PCRE_ERROR_RECURSELOOP] = "recursion loop detected",
+   [-PCRE_ERROR_JIT_STACKLIMIT] = "JIT stack too small",
+   [-PCRE_ERROR_BADMODE] = "pattern compiled for different character size",
+   [-PCRE_ERROR_BADENDIANNESS] = "pattern compiled for different host endianness",
+   [-PCRE_ERROR_DFA_BADRESTART] = "unable to resume partial DFA match",
+   [-PCRE_ERROR_JIT_BADOPTION] = "invalid JIT option",
+   [-PCRE_ERROR_BADLENGTH] = "text length is negative",
+   [-PCRE_ERROR_UNSET] = "required value not set",
+};
+
+STR_BEGIN_FORMATTER(rgxFormatErrorMessage, int error)
+  const char *message = getErrorMessage(error);
+
+  if (!message) {
+    if (error <= 0) {
+      if ((error = -error) < ARRAY_COUNT(rgxNegativeErrors)) {
+        message = rgxNegativeErrors[error];
+      }
+    }
+  }
+
+  if (message && *message) STR_PRINTF("%s", message);
+STR_END_FORMATTER
+
+RGX_BEGIN_OPTION_MAP(rgxCompileOptions)
+  [RGX_COMPILE_ANCHOR_START] = PCRE_ANCHORED,
+
+  [RGX_COMPILE_IGNORE_CASE] = PCRE_CASELESS,
+  [RGX_COMPILE_UNICODE_PROPERTIES] = PCRE_UCP,
+RGX_END_OPTION_MAP(rgxCompileOptions)
+
+RGX_BEGIN_OPTION_MAP(rgxMatchOptions)
+  [RGX_MATCH_ANCHOR_START] = PCRE_ANCHORED,
+RGX_END_OPTION_MAP(rgxMatchOptions)
diff --git a/Programs/rgx_none.c b/Programs/rgx_none.c
new file mode 100644
index 0000000..1c0f5ee
--- /dev/null
+++ b/Programs/rgx_none.c
@@ -0,0 +1,85 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "rgx.h"
+#include "rgx_internal.h"
+#include "strfmt.h"
+
+RGX_CodeType *
+rgxCompilePattern (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_OptionsType options, RGX_OffsetType *offset,
+  int *error
+) {
+  return NULL;
+}
+
+void
+rgxDeallocateCode (RGX_CodeType *code) {
+}
+
+RGX_DataType *
+rgxAllocateData (RGX_CodeType *code) {
+  return NULL;
+}
+
+void
+rgxDeallocateData (RGX_DataType *data) {
+}
+
+int
+rgxMatchText (
+  const RGX_CharacterType *characters, size_t length,
+  RGX_CodeType *code, RGX_DataType *data,
+  RGX_OptionsType options, size_t *count, int *error
+) {
+  *error = RGX_NO_MATCH;
+  return 0;
+}
+
+int
+rgxNameNumber (
+  RGX_CodeType *code, const RGX_CharacterType *name,
+  size_t *number, int *error
+) {
+  *error = RGX_NO_NAME;
+  return 0;
+}
+
+int
+rgxCaptureBounds (
+  RGX_DataType *data, size_t number, size_t *from, size_t *to
+) {
+  return 0;
+}
+
+STR_BEGIN_FORMATTER(rgxFormatErrorMessage, int error)
+  switch (error) {
+    case RGX_NO_MATCH:
+      STR_PRINTF("no match");
+      break;
+  }
+STR_END_FORMATTER
+
+RGX_BEGIN_OPTION_MAP(rgxCompileOptions)
+RGX_END_OPTION_MAP(rgxCompileOptions)
+
+RGX_BEGIN_OPTION_MAP(rgxMatchOptions)
+RGX_END_OPTION_MAP(rgxMatchOptions)
diff --git a/Programs/routing.c b/Programs/routing.c
new file mode 100644
index 0000000..f9824f2
--- /dev/null
+++ b/Programs/routing.c
@@ -0,0 +1,573 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif /* HAVE_SIGNAL_H */
+
+#ifdef SIGUSR1
+#include <sys/wait.h>
+#endif /* SIGUSR1 */
+
+#include "parameters.h"
+#include "log.h"
+#include "program.h"
+#include "thread.h"
+#include "async_wait.h"
+#include "timing.h"
+#include "scr.h"
+#include "routing.h"
+
+typedef enum {
+  CRR_DONE,
+  CRR_NEAR,
+  CRR_FAIL
+} RoutingResult;
+
+typedef struct {
+#ifdef SIGUSR1
+  struct {
+    sigset_t mask;
+  } signal;
+#endif /* SIGUSR1 */
+
+  struct {
+    int number;
+    int width;
+    int height;
+  } screen;
+
+  struct {
+    int scroll;
+    int row;
+    ScreenCharacter *buffer;
+  } vertical;
+
+  struct {
+    int column;
+    int row;
+  } current;
+
+  struct {
+    int column;
+    int row;
+  } previous;
+
+  struct {
+    long sum;
+    int count;
+  } time;
+} CursorRoutingData;
+
+typedef enum {
+  CURSOR_DIR_LEFT,
+  CURSOR_DIR_RIGHT,
+  CURSOR_DIR_UP,
+  CURSOR_DIR_DOWN
+} CursorDirection;
+
+typedef struct {
+  const char *name;
+  ScreenKey key;
+} CursorDirectionEntry;
+
+static const CursorDirectionEntry cursorDirectionTable[] = {
+  [CURSOR_DIR_LEFT]  = {.name="left" , .key=SCR_KEY_CURSOR_LEFT },
+  [CURSOR_DIR_RIGHT] = {.name="right", .key=SCR_KEY_CURSOR_RIGHT},
+  [CURSOR_DIR_UP]    = {.name="up"   , .key=SCR_KEY_CURSOR_UP   },
+  [CURSOR_DIR_DOWN]  = {.name="down" , .key=SCR_KEY_CURSOR_DOWN }
+};
+
+typedef enum {
+  CURSOR_AXIS_HORIZONTAL,
+  CURSOR_AXIS_VERTICAL
+} CursorAxis;
+
+typedef struct {
+  const CursorDirectionEntry *forward;
+  const CursorDirectionEntry *backward;
+} CursorAxisEntry;
+
+static const CursorAxisEntry cursorAxisTable[] = {
+  [CURSOR_AXIS_HORIZONTAL] = {
+    .forward  = &cursorDirectionTable[CURSOR_DIR_RIGHT],
+    .backward = &cursorDirectionTable[CURSOR_DIR_LEFT]
+  }
+  ,
+  [CURSOR_AXIS_VERTICAL] = {
+    .forward  = &cursorDirectionTable[CURSOR_DIR_DOWN],
+    .backward = &cursorDirectionTable[CURSOR_DIR_UP]
+  }
+};
+
+#define logRouting(...) logMessage(LOG_CATEGORY(CURSOR_ROUTING), __VA_ARGS__)
+
+static int
+readRow (CursorRoutingData *crd, ScreenCharacter *buffer, int row) {
+  if (!buffer) buffer = crd->vertical.buffer;
+  if (readScreenRow(row, crd->screen.width, buffer)) return 1;
+  logRouting("read failed: row=%d", row);
+  return 0;
+}
+
+static int
+getCurrentPosition (CursorRoutingData *crd) {
+  ScreenDescription description;
+  describeScreen(&description);
+
+  if (description.number != crd->screen.number) {
+    logRouting("screen changed: %d -> %d", crd->screen.number, description.number);
+    crd->screen.number = description.number;
+    return 0;
+  }
+
+  if (!crd->vertical.buffer) {
+    crd->screen.width = description.cols;
+    crd->screen.height = description.rows;
+    crd->vertical.scroll = 0;
+
+    if (!(crd->vertical.buffer = malloc(ARRAY_SIZE(crd->vertical.buffer, crd->screen.width)))) {
+      logMallocError();
+      goto error;
+    }
+
+    logRouting("screen: num=%d cols=%d rows=%d",
+               crd->screen.number,
+               crd->screen.width, crd->screen.height);
+  } else if ((crd->screen.width != description.cols) ||
+             (crd->screen.height != description.rows)) {
+    logRouting("size changed: %dx%d -> %dx%d",
+               crd->screen.width, crd->screen.height,
+               description.cols, description.rows);
+    goto error;
+  }
+
+  crd->current.row = description.posy + crd->vertical.scroll;
+  crd->current.column = description.posx;
+  return 1;
+
+error:
+  crd->screen.number = -1;
+  return 0;
+}
+
+static void
+handleVerticalScrolling (CursorRoutingData *crd, int direction) {
+  int firstRow = crd->vertical.row;
+  int currentRow = firstRow;
+
+  int bestRow = firstRow;
+  int bestLength = 0;
+
+  do {
+    ScreenCharacter buffer[crd->screen.width];
+    if (!readRow(crd, buffer, currentRow)) break;
+
+    int length;
+    {
+      int before = crd->current.column;
+      int after = before;
+
+      while (buffer[before].text == crd->vertical.buffer[before].text)
+        if (--before < 0)
+          break;
+
+      while (buffer[after].text == crd->vertical.buffer[after].text)
+        if (++after >= crd->screen.width)
+          break;
+
+      length = after - before - 1;
+    }
+
+    if (length > bestLength) {
+      bestRow = currentRow;
+      if ((bestLength = length) == crd->screen.width) break;
+    }
+
+    currentRow -= direction;
+  } while ((currentRow >= 0) && (currentRow < crd->screen.height));
+
+  int delta = bestRow - firstRow;
+  crd->vertical.scroll -= delta;
+  crd->current.row -= delta;
+}
+
+static int
+awaitCursorMotion (CursorRoutingData *crd, int direction) {
+  crd->previous.column = crd->current.column;
+  crd->previous.row = crd->current.row;
+
+  TimeValue start;
+  getMonotonicTime(&start);
+
+  int moved = 0;
+  long int timeout = crd->time.sum / crd->time.count;
+
+  while (1) {
+    asyncWait(ROUTING_POLL_INTERVAL);
+
+    TimeValue now;
+    getMonotonicTime(&now);
+    long int time = millisecondsBetween(&start, &now) + 1;
+
+    int oldy = crd->current.row;
+    int oldx = crd->current.column;
+    if (!getCurrentPosition(crd)) return 0;
+
+    if ((crd->current.row != oldy) || (crd->current.column != oldx)) {
+      logRouting("moved: [%d,%d] -> [%d,%d] (%ldms)",
+                 oldx, oldy, crd->current.column, crd->current.row, time);
+
+      if (!moved) {
+        moved = 1;
+        timeout = (time * 2) + 1;
+
+        crd->time.sum += time * 8;
+        crd->time.count += 1;
+      }
+
+      if (ROUTING_POLL_INTERVAL) {
+        start = now;
+      } else {
+        asyncWait(1);
+        getMonotonicTime(&start);
+      }
+    } else if (time > timeout) {
+      break;
+    }
+  }
+
+  handleVerticalScrolling(crd, direction);
+  return 1;
+}
+
+static int
+moveCursor (CursorRoutingData *crd, const CursorDirectionEntry *direction) {
+  crd->vertical.row = crd->current.row - crd->vertical.scroll;
+  if (!readRow(crd, NULL, crd->vertical.row)) return 0;
+
+#ifdef SIGUSR1
+  sigset_t oldMask;
+  sigprocmask(SIG_BLOCK, &crd->signal.mask, &oldMask);
+#endif /* SIGUSR1 */
+
+  logRouting("move: %s", direction->name);
+  insertScreenKey(direction->key);
+
+#ifdef SIGUSR1
+  sigprocmask(SIG_SETMASK, &oldMask, NULL);
+#endif /* SIGUSR1 */
+
+  return 1;
+}
+
+static RoutingResult
+adjustCursorPosition (CursorRoutingData *crd, int where, int trgy, int trgx, const CursorAxisEntry *axis) {
+  logRouting("to: [%d,%d]", trgx, trgy);
+
+  while (1) {
+    int dify = trgy - crd->current.row;
+    int difx = (trgx < 0)? 0: (trgx - crd->current.column);
+    int dir;
+
+    /* determine which direction the cursor needs to move in */
+    if (dify) {
+      dir = (dify > 0)? 1: -1;
+    } else if (difx) {
+      dir = (difx > 0)? 1: -1;
+    } else {
+      return CRR_DONE;
+    }
+
+    /* tell the cursor to move in the needed direction */
+    if (!moveCursor(crd, ((dir > 0)? axis->forward: axis->backward))) return CRR_FAIL;
+    if (!awaitCursorMotion(crd, dir)) return CRR_FAIL;
+
+    if (crd->current.row != crd->previous.row) {
+      if (crd->previous.row != trgy) {
+        if (((crd->current.row - crd->previous.row) * dir) > 0) {
+          int dif = trgy - crd->current.row;
+          if ((dif * dify) >= 0) continue;
+          if (where > 0) {
+            if (crd->current.row > trgy) return CRR_NEAR;
+          } else if (where < 0) {
+            if (crd->current.row < trgy) return CRR_NEAR;
+          } else {
+            if ((dif * dif) < (dify * dify)) return CRR_NEAR;
+          }
+        }
+      }
+    } else if (crd->current.column != crd->previous.column) {
+      if (((crd->current.column - crd->previous.column) * dir) > 0) {
+        int dif = trgx - crd->current.column;
+        if (crd->current.row != trgy) continue;
+        if ((dif * difx) >= 0) continue;
+        if (where > 0) {
+          if (crd->current.column > trgx) return CRR_NEAR;
+        } else if (where < 0) {
+          if (crd->current.column < trgx) return CRR_NEAR;
+        } else {
+          if ((dif * dif) < (difx * difx)) return CRR_NEAR;
+        }
+      }
+    } else {
+      return CRR_NEAR;
+    }
+
+    /* We're getting farther from our target. Before giving up, let's
+     * try going back to the previous position since it was obviously
+     * the nearest ever reached.
+     */
+    if (!moveCursor(crd, ((dir > 0)? axis->backward: axis->forward))) return CRR_FAIL;
+    return awaitCursorMotion(crd, -dir)? CRR_NEAR: CRR_FAIL;
+  }
+}
+
+static RoutingResult
+adjustCursorHorizontally (CursorRoutingData *crd, int where, int row, int column) {
+  return adjustCursorPosition(crd, where, row, column, &cursorAxisTable[CURSOR_AXIS_HORIZONTAL]);
+}
+
+static RoutingResult
+adjustCursorVertically (CursorRoutingData *crd, int where, int row) {
+  return adjustCursorPosition(crd, where, row, -1, &cursorAxisTable[CURSOR_AXIS_VERTICAL]);
+}
+
+typedef struct {
+  int column;
+  int row;
+  int screen;
+} RoutingParameters;
+
+static RoutingStatus
+routeCursor (const RoutingParameters *parameters) {
+  CursorRoutingData crd;
+
+#ifdef SIGUSR1
+  /* Set up the signal mask. */
+  sigemptyset(&crd.signal.mask);
+  sigaddset(&crd.signal.mask, SIGUSR1);
+  sigprocmask(SIG_UNBLOCK, &crd.signal.mask, NULL);
+#endif /* SIGUSR1 */
+
+  /* initialize the routing data structure */
+  crd.screen.number = parameters->screen;
+  crd.vertical.buffer = NULL;
+  crd.time.sum = ROUTING_MAXIMUM_TIMEOUT;
+  crd.time.count = 1;
+
+  if (getCurrentPosition(&crd)) {
+    logRouting("from: [%d,%d]", crd.current.column, crd.current.row);
+
+    if (parameters->column < 0) {
+      adjustCursorVertically(&crd, 0, parameters->row);
+    } else {
+      if (adjustCursorVertically(&crd, -1, parameters->row) != CRR_FAIL) {
+        if (adjustCursorHorizontally(&crd, 0, parameters->row, parameters->column) == CRR_NEAR) {
+          if (crd.current.row < parameters->row) {
+            if (adjustCursorVertically(&crd, 1, crd.current.row+1) != CRR_FAIL) {
+              adjustCursorHorizontally(&crd, 0, parameters->row, parameters->column);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if (crd.vertical.buffer) free(crd.vertical.buffer);
+
+  if (crd.screen.number != parameters->screen) return ROUTING_STATUS_FAILURE;
+  if (crd.current.row != parameters->row) return ROUTING_STATUS_ROW;
+  if ((parameters->column >= 0) && (crd.current.column != parameters->column)) return ROUTING_STATUS_COLUMN;
+  return ROUTING_STATUS_SUCCEESS;
+}
+
+#ifdef SIGUSR1
+#define NOT_ROUTING 0
+
+static pid_t routingProcess = NOT_ROUTING;
+
+int
+isRouting (void) {
+  return routingProcess != NOT_ROUTING;
+}
+
+RoutingStatus
+getRoutingStatus (int wait) {
+  if (isRouting()) {
+    int options = 0;
+    if (!wait) options |= WNOHANG;
+
+  doWait:
+    {
+      int status;
+      pid_t process = waitpid(routingProcess, &status, options);
+
+      if (process == routingProcess) {
+        routingProcess = NOT_ROUTING;
+        return WIFEXITED(status)? WEXITSTATUS(status): ROUTING_STATUS_FAILURE;
+      }
+
+      if (process == -1) {
+        if (errno == EINTR) goto doWait;
+
+        if (errno == ECHILD) {
+          routingProcess = NOT_ROUTING;
+          return ROUTING_STATUS_FAILURE;
+        }
+
+        logSystemError("waitpid");
+      }
+    }
+  }
+
+  return ROUTING_STATUS_NONE;
+}
+
+static void
+stopRouting (void) {
+  if (isRouting()) {
+    kill(routingProcess, SIGUSR1);
+    getRoutingStatus(1);
+  }
+}
+
+static void
+exitCursorRouting (void *data) {
+  stopRouting();
+}
+#else /* SIGUSR1 */
+static RoutingStatus routingStatus = ROUTING_STATUS_NONE;
+
+RoutingStatus
+getRoutingStatus (int wait) {
+  RoutingStatus status = routingStatus;
+  routingStatus = ROUTING_STATUS_NONE;
+  return status;
+}
+
+int
+isRouting (void) {
+  return 0;
+}
+#endif /* SIGUSR1 */
+
+static int
+startRoutingProcess (const RoutingParameters *parameters) {
+#ifdef SIGUSR1
+  int started = 0;
+
+  stopRouting();
+
+  switch (routingProcess = fork()) {
+    case 0: { /* child: cursor routing subprocess */
+      RoutingStatus status = ROUTING_STATUS_FAILURE;
+
+      if (!ROUTING_POLL_INTERVAL) {
+        int niceness = nice(ROUTING_PROCESS_NICENESS);
+
+        if (niceness == -1) {
+          logSystemError("nice");
+        }
+      }
+
+      if (constructRoutingScreen()) {
+        status = routeCursor(parameters);		/* terminate child process */
+        destructRoutingScreen();		/* close second thread of screen reading */
+      }
+
+      _exit(status);		/* terminate child process */
+    }
+
+    case -1: /* error: fork() failed */
+      logSystemError("fork");
+      routingProcess = NOT_ROUTING;
+      break;
+
+    default: {
+      /* parent: continue while cursor is being routed */
+
+      {
+        static int first = 1;
+
+        if (first) {
+          first = 0;
+          onProgramExit("cursor-routing", exitCursorRouting, NULL);
+        }
+      }
+
+      started = 1;
+      break;
+    }
+  }
+
+  return started;
+#else /* SIGUSR1 */
+  routingStatus = routeCursor(parameters);
+  return 1;
+#endif /* SIGUSR1 */
+}
+
+#ifdef GOT_PTHREADS
+typedef struct {
+  const RoutingParameters *const parameters;
+
+  int result;
+} RoutingThreadArgument;
+
+THREAD_FUNCTION(runRoutingThread) {
+  RoutingThreadArgument *rta = argument;
+
+  rta->result = startRoutingProcess(rta->parameters);
+  return NULL;
+}
+#endif /* GOT_PTHREADS */
+
+int
+startRouting (int column, int row, int screen) {
+  const RoutingParameters parameters = {
+    .column = column,
+    .row = row,
+    .screen = screen
+  };
+
+#ifdef GOT_PTHREADS
+  int started = 0;
+
+  RoutingThreadArgument rta = {
+    .parameters = &parameters
+  };
+
+  if (callThreadFunction("cursor-routing", runRoutingThread, &rta, NULL)) {
+    if (rta.result) {
+      started = 1;
+    }
+  }
+
+  return started;
+#else /* GOT_PTHREADS */
+  return startRoutingProcess(&parameters);
+#endif /* GOT_PTHREADS */
+}
diff --git a/Programs/routing.h b/Programs/routing.h
new file mode 100644
index 0000000..9e3b527
--- /dev/null
+++ b/Programs/routing.h
@@ -0,0 +1,43 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_ROUTING
+#define BRLTTY_INCLUDED_ROUTING
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  // don't change the order
+  ROUTING_STATUS_NONE,
+  ROUTING_STATUS_SUCCEESS,
+  ROUTING_STATUS_COLUMN,
+  ROUTING_STATUS_ROW,
+  ROUTING_STATUS_FAILURE
+} RoutingStatus;
+
+extern int startRouting (int column, int row, int screen);
+extern int isRouting (void);
+extern RoutingStatus getRoutingStatus (int wait);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_ROUTING */
diff --git a/Programs/scr.auto.h b/Programs/scr.auto.h
new file mode 100644
index 0000000..bd2cd9d
--- /dev/null
+++ b/Programs/scr.auto.h
@@ -0,0 +1,3 @@
+static const DriverEntry driverTable[] = {
+  {NULL, NULL}
+};
diff --git a/Programs/scr.c b/Programs/scr.c
new file mode 100644
index 0000000..f4648fb
--- /dev/null
+++ b/Programs/scr.c
@@ -0,0 +1,257 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* scr.cc - The screen reading library
+ *
+ * Note: Although C++, this code requires no standard C++ library.
+ * This is important as BRLTTY *must not* rely on too many
+ * run-time shared libraries, nor be a huge executable.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "unicode.h"
+#include "scr.h"
+#include "scr_real.h"
+#include "driver.h"
+
+MainScreen mainScreen;
+BaseScreen *currentScreen = NULL;
+
+int
+isMainScreen (void) {
+  return currentScreen == &mainScreen.base;
+}
+
+const char *const *
+getScreenParameters (const ScreenDriver *driver) {
+  return driver->parameters;
+}
+
+static void
+initializeScreen (void) {
+  screen->initialize(&mainScreen);
+  currentScreen = &mainScreen.base;
+  currentScreen->onForeground();
+}
+
+void
+setNoScreen (void) {
+  screen = &noScreen;
+  initializeScreen();
+}
+
+int
+constructScreenDriver (char **parameters) {
+  initializeScreen();
+
+  if (mainScreen.processParameters(parameters)) {
+    if (mainScreen.construct()) {
+      return 1;
+    } else {
+      logMessage(LOG_DEBUG, "screen driver initialization failed: %s",
+                 screen->definition.code);
+    }
+
+    mainScreen.releaseParameters();
+  }
+
+  return 0;
+}
+
+void
+destructScreenDriver (void) {
+  mainScreen.destruct();
+  mainScreen.releaseParameters();
+}
+
+
+int
+pollScreen (void) {
+  return currentScreen->poll();
+}
+
+int
+refreshScreen (void) {
+  return currentScreen->refresh();
+}
+
+void
+describeScreen (ScreenDescription *description) {
+  describeBaseScreen(currentScreen, description);
+  if (description->unreadable) description->quality = SCQ_NONE;
+}
+
+int
+readScreen (short left, short top, short width, short height, ScreenCharacter *buffer) {
+  const ScreenBox box = {
+    .left = left,
+    .top = top,
+    .width = width,
+    .height = height,
+  };
+
+  if (!currentScreen->readCharacters(&box, buffer)) return 0;
+
+  ScreenCharacter *character = buffer;
+  const ScreenCharacter *end = character + (box.width * box.height);
+
+  while (character < end) {
+    wchar_t *text = &character->text;
+
+    if ((*text <= 0) || (*text > UNICODE_LAST_CHARACTER)) {
+      // This is not a valid Unicode character - return the replacement character.
+
+      size_t index = character - buffer;
+      unsigned int column = box.left + (index % box.width);
+      unsigned int row = box.top + (index / box.width);
+
+      logMessage(LOG_ERR,
+        "invalid character U+%04lX on screen at [%u,%u]",
+        (unsigned long)*text, column, row
+      );
+
+      *text = UNICODE_REPLACEMENT_CHARACTER;
+    }
+
+    character += 1;
+  }
+
+  return 1;
+}
+
+int
+readScreenText (short left, short top, short width, short height, wchar_t *buffer) {
+  unsigned int count = width * height;
+  ScreenCharacter characters[count];
+  if (!readScreen(left, top, width, height, characters)) return 0;
+
+  for (int i=0; i<count; i+=1) {
+    buffer[i] = characters[i].text;
+  }
+
+  return 1;
+}
+
+int
+insertScreenKey (ScreenKey key) {
+  logMessage(LOG_CATEGORY(SCREEN_DRIVER), "insert key: 0X%04X", key);
+  return currentScreen->insertKey(key);
+}
+
+int
+routeScreenCursor (int column, int row, int screen) {
+  return currentScreen->routeCursor(column, row, screen);
+}
+
+int
+highlightScreenRegion (int left, int right, int top, int bottom) {
+  return currentScreen->highlightRegion(left, right, top, bottom);
+}
+
+int
+unhighlightScreenRegion (void) {
+  return currentScreen->unhighlightRegion();
+}
+
+int
+getScreenPointer (int *column, int *row) {
+  return currentScreen->getPointer(column, row);
+}
+
+int
+clearScreenTextSelection (void) {
+  return currentScreen->clearSelection();
+}
+
+int
+setScreenTextSelection (int startColumn, int startRow, int endColumn, int endRow) {
+  if ((endRow < startRow) || ((endRow == startRow) && (endColumn < startColumn))) {
+    int temp;
+
+    temp = endColumn;
+    endColumn = startColumn;
+    startColumn = temp;
+
+    temp = endRow;
+    endRow = startRow;
+    startRow = temp;
+  }
+
+  return currentScreen->setSelection(startColumn, startRow, endColumn, endRow);
+}
+
+int
+currentVirtualTerminal (void) {
+  return currentScreen->currentVirtualTerminal();
+}
+
+int
+selectScreenVirtualTerminal (int vt) {
+  return currentScreen->selectVirtualTerminal(vt);
+}
+
+int
+switchScreenVirtualTerminal (int vt) {
+  return currentScreen->switchVirtualTerminal(vt);
+}
+
+int
+nextScreenVirtualTerminal (void) {
+  return currentScreen->nextVirtualTerminal();
+}
+
+int
+previousScreenVirtualTerminal (void) {
+  return currentScreen->previousVirtualTerminal();
+}
+
+int
+userVirtualTerminal (int number) {
+  return mainScreen.userVirtualTerminal(number);
+}
+
+int
+handleScreenCommands (int command, void *data) {
+  return currentScreen->handleCommand(command);
+}
+
+KeyTableCommandContext
+getScreenCommandContext (void) {
+  return currentScreen->getCommandContext();
+}
+
+
+int
+constructRoutingScreen (void) {
+  /* This function should be used in a forked process. Though we want to
+   * have a separate file descriptor for the main screen from the one used
+   * in the main thread.  So we close and reopen the device.
+   */
+  mainScreen.destruct();
+  return mainScreen.construct();
+}
+
+void
+destructRoutingScreen (void) {
+  mainScreen.destruct();
+  mainScreen.releaseParameters();
+}
diff --git a/Programs/scr.h b/Programs/scr.h
new file mode 100644
index 0000000..f4e53b4
--- /dev/null
+++ b/Programs/scr.h
@@ -0,0 +1,94 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR
+#define BRLTTY_INCLUDED_SCR
+
+#include "scr_types.h"
+#include "ktb_types.h"
+#include "driver.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int isMainScreen (void);
+
+/* Routines which apply to the current screen. */
+extern int pollScreen (void);
+extern int refreshScreen (void);
+extern void describeScreen (ScreenDescription *);		/* get screen status */
+extern int readScreen (short left, short top, short width, short height, ScreenCharacter *buffer);
+extern int readScreenText (short left, short top, short width, short height, wchar_t *buffer);
+extern int insertScreenKey (ScreenKey key);
+extern int routeScreenCursor (int column, int row, int screen);
+extern int highlightScreenRegion (int left, int right, int top, int bottom);
+extern int unhighlightScreenRegion (void);
+extern int getScreenPointer (int *column, int *row);
+extern int clearScreenTextSelection (void);
+extern int setScreenTextSelection (int startColumn, int startRow, int endColumn, int endRow);
+extern int currentVirtualTerminal (void);
+extern int selectScreenVirtualTerminal (int vt);
+extern int switchScreenVirtualTerminal (int vt);
+extern int nextScreenVirtualTerminal (void);
+extern int previousScreenVirtualTerminal (void);
+extern int userVirtualTerminal (int number);
+extern int handleScreenCommands (int command, void *data);
+extern KeyTableCommandContext getScreenCommandContext (void);
+
+static inline int
+readScreenRows (int row, int width, int height, ScreenCharacter *buffer) {
+  return readScreen(0, row, width, height, buffer);
+}
+
+static inline int
+readScreenRow (int row, int width, ScreenCharacter *buffer) {
+  return readScreenRows(row, width, 1, buffer);
+}
+
+/* Routines which apply to the routing screen.
+ * An extra `thread' for the cursor routing subprocess.
+ * This is needed because the forked subprocess shares its parent's
+ * file descriptors.  A readScreen equivalent is not needed.
+ */
+extern int constructRoutingScreen (void);
+extern void destructRoutingScreen (void);
+
+extern const ScreenDriver *screen;
+extern const ScreenDriver noScreen;
+extern void setNoScreen (void);
+
+extern const char *const *getScreenParameters (const ScreenDriver *driver);
+
+extern int haveScreenDriver (const char *code);
+extern const char *getDefaultScreenDriver (void);
+
+extern const ScreenDriver *loadScreenDriver (const char *code, void **driverObject, const char *driverDirectory);
+extern int constructScreenDriver (char **parameters);
+extern void destructScreenDriver (void);
+
+extern void identifyScreenDriver (const ScreenDriver *driver, int full);
+extern void identifyScreenDrivers (int full);
+
+extern void setNoScreenDriverReason (const char *reason);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR */
diff --git a/Programs/scr_base.c b/Programs/scr_base.c
new file mode 100644
index 0000000..23c7e4d
--- /dev/null
+++ b/Programs/scr_base.c
@@ -0,0 +1,302 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "scr.h"
+#include "scr_utils.h"
+#include "scr_base.h"
+#include "scr_internal.h"
+#include "ascii.h"
+
+void
+setScreenKeyModifiers (ScreenKey *key, ScreenKey which) {
+  if (!isSpecialKey(*key)) {
+    wchar_t character = *key & SCR_KEY_CHAR_MASK;
+    ScreenKey modifiers = *key & ~SCR_KEY_CHAR_MASK;
+
+    if (which & (SCR_KEY_UPPER | SCR_KEY_SHIFT)) {
+      if (!(modifiers & (SCR_KEY_UPPER | SCR_KEY_SHIFT))) {
+        if (iswupper(character)) {
+          character = towlower(character);
+
+          if (which & SCR_KEY_UPPER) {
+            modifiers |= SCR_KEY_UPPER;
+          } else {
+            modifiers |= SCR_KEY_SHIFT;
+          }
+        }
+      }
+    } else {
+      if (modifiers & (SCR_KEY_UPPER | SCR_KEY_SHIFT)) {
+        if (iswalpha(character)) {
+          character = towupper(character);
+          modifiers &= ~SCR_KEY_SHIFT;
+        }
+
+        modifiers &= ~SCR_KEY_UPPER;
+      }
+    }
+
+    if (which & SCR_KEY_CONTROL) {
+      if (!(modifiers & SCR_KEY_CONTROL)) {
+        if (character < 0X20) {
+          character |= 0X60;
+          modifiers |= SCR_KEY_CONTROL;
+        }
+      }
+    } else {
+      if (modifiers & SCR_KEY_CONTROL) {
+        if (character <= 0X7F) {
+          if ((character & 0X6F) == 0X2F) {
+            character |= 0X50;
+          } else {
+            character &= 0X1F;
+          }
+        }
+
+        modifiers &= ~SCR_KEY_CONTROL;
+      }
+    }
+
+    ScreenKey newKey = character | modifiers;
+    if (newKey != *key) {
+      logMessage(LOG_CATEGORY(SCREEN_DRIVER), "transformed key: 0X%04X -> 0X%04X", *key, newKey);
+      *key = newKey;
+    }
+  }
+}
+
+void
+mapScreenKey (ScreenKey *key) {
+  switch (*key & SCR_KEY_CHAR_MASK) {
+    case '\n':
+    case '\r':   *key = SCR_KEY_ENTER;     break;
+    case '\t':   *key = SCR_KEY_TAB;       break;
+    case '\b':   *key = SCR_KEY_BACKSPACE; break;
+    case ASCII_ESC: *key = SCR_KEY_ESCAPE;    break;
+  }
+}
+
+static const char text_BaseScreen[] = " ";
+
+static int
+currentVirtualTerminal_BaseScreen (void) {
+  return 0;
+}
+
+static int
+selectVirtualTerminal_BaseScreen (int vt) {
+  return 0;
+}
+
+static int
+switchVirtualTerminal_BaseScreen (int vt) {
+  return 0;
+}
+
+static int
+nextVirtualTerminal_BaseScreen (void) {
+  return currentScreen->switchVirtualTerminal(currentScreen->currentVirtualTerminal() + 1);
+}
+
+static int
+previousVirtualTerminal_BaseScreen (void) {
+  return currentScreen->switchVirtualTerminal(currentScreen->currentVirtualTerminal() - 1);
+}
+
+static const char *
+getTitle_BaseScreen (void) {
+  return NULL;
+}
+
+static void
+onForeground_BaseScreen (void) {
+}
+
+static void
+onBackground_BaseScreen (void) {
+}
+
+static int
+poll_BaseScreen (void) {
+  return 0;
+}
+
+static int
+refresh_BaseScreen (void) {
+  return 1;
+}
+
+static void
+describe_BaseScreen (ScreenDescription *description) {
+  description->rows = 1;
+  description->cols = strlen(text_BaseScreen);
+  description->posx = 0;
+  description->posy = 0;
+  description->number = currentVirtualTerminal_BaseScreen();
+}
+
+static int
+readCharacters_BaseScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  ScreenDescription description;
+  describe_BaseScreen(&description);
+  if (!validateScreenBox(box, description.cols, description.rows)) return 0;
+  setScreenMessage(box, buffer, text_BaseScreen);
+  return 1;
+}
+
+static int
+insertKey_BaseScreen (ScreenKey key) {
+  return 0;
+}
+
+static int
+routeCursor_BaseScreen (int column, int row, int screen) {
+  return 0;
+}
+
+static int
+highlightRegion_BaseScreen (int left, int right, int top, int bottom) {
+  return 0;
+}
+
+int
+unhighlightRegion_BaseScreen (void) {
+  return 0;
+}
+
+static int
+getPointer_BaseScreen (int *column, int *row) {
+  return 0;
+}
+
+static int
+clearSelection_BaseScreen (void) {
+  return 0;
+}
+
+static int
+setSelection_BaseScreen (int startColumn, int startRow, int endColumn, int endRow) {
+  return 0;
+}
+
+static int
+handleCommand_BaseScreen (int command) {
+  return 0;
+}
+
+static KeyTableCommandContext
+getCommandContext_BaseScreen (void) {
+  return KTB_CTX_DEFAULT;
+}
+
+void
+initializeBaseScreen (BaseScreen *base) {
+  base->getTitle = getTitle_BaseScreen;
+  base->onForeground = onForeground_BaseScreen;
+  base->onBackground = onBackground_BaseScreen;
+
+  base->poll = poll_BaseScreen;
+  base->refresh = refresh_BaseScreen;
+  base->describe = describe_BaseScreen;
+
+  base->readCharacters = readCharacters_BaseScreen;
+  base->insertKey = insertKey_BaseScreen;
+  base->routeCursor = routeCursor_BaseScreen;
+
+  base->highlightRegion = highlightRegion_BaseScreen;
+  base->unhighlightRegion = unhighlightRegion_BaseScreen;
+  base->getPointer = getPointer_BaseScreen;
+
+  base->clearSelection = clearSelection_BaseScreen;
+  base->setSelection = setSelection_BaseScreen;
+
+  base->currentVirtualTerminal = currentVirtualTerminal_BaseScreen;
+  base->selectVirtualTerminal = selectVirtualTerminal_BaseScreen;
+  base->switchVirtualTerminal = switchVirtualTerminal_BaseScreen;
+  base->nextVirtualTerminal = nextVirtualTerminal_BaseScreen;
+  base->previousVirtualTerminal = previousVirtualTerminal_BaseScreen;
+
+  base->handleCommand = handleCommand_BaseScreen;
+  base->getCommandContext = getCommandContext_BaseScreen;
+}
+
+void
+describeBaseScreen (BaseScreen *base, ScreenDescription *description) {
+  description->unreadable = NULL;
+  description->quality = SCQ_GOOD;
+
+  description->number = 0;
+  description->cols = description->rows = 1;
+  description->posx = description->posy = 0;
+
+  description->hasCursor = 1;
+  description->hasSelection = 0;
+
+  base->describe(description);
+
+  if (description->unreadable) {
+    description->hasCursor = 0;
+  }
+}
+
+int
+validateScreenBox (const ScreenBox *box, int columns, int rows) {
+  if ((box->left >= 0))
+    if ((box->width > 0))
+      if (((box->left + box->width) <= columns))
+        if ((box->top >= 0))
+          if ((box->height > 0))
+            if (((box->top + box->height) <= rows))
+              return 1;
+
+  logMessage(LOG_ERR, "invalid screen area: cols=%d left=%d width=%d rows=%d top=%d height=%d",
+             columns, box->left, box->width,
+             rows, box->top, box->height);
+  return 0;
+}
+
+void
+setScreenMessage (const ScreenBox *box, ScreenCharacter *buffer, const char *message) {
+  const ScreenCharacter *end = buffer + box->width;
+  unsigned int index = 0;
+  size_t length = strlen(message);
+  mbstate_t state;
+
+  memset(&state, 0, sizeof(state));
+  clearScreenCharacters(buffer, (box->width * box->height));
+
+  while (length) {
+    wchar_t wc;
+    size_t result = mbrtowc(&wc, message, length, &state);
+    if ((ssize_t)result < 1) break;
+
+    message += result;
+    length -= result;
+
+    if (index++ >= box->left) {
+      if (buffer == end) break;
+      (buffer++)->text = wc;
+    }
+  }
+}
diff --git a/Programs/scr_driver.c b/Programs/scr_driver.c
new file mode 100644
index 0000000..57e602b
--- /dev/null
+++ b/Programs/scr_driver.c
@@ -0,0 +1,150 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "drivers.h"
+
+typedef enum {
+  PARM_MESSAGE
+} ScreenParameters;
+#define SCRPARMS "message"
+
+#define SCRSYMBOL noScreen
+#define DRIVER_NAME NoScreen
+#define DRIVER_CODE no
+#define DRIVER_COMMENT "no screen support"
+#define DRIVER_VERSION ""
+#define DRIVER_DEVELOPERS ""
+#include "scr_driver.h"
+#include "scr.auto.h"
+
+static const char defaultMessage[] = strtext("no screen");
+static const char *messageParameter = NULL;
+static const char *noDriverReason = NULL;
+static const char *screenMessage = NULL;
+
+void
+setNoScreenDriverReason (const char *reason) {
+  noDriverReason = reason;
+}
+
+static int
+processParameters_NoScreen (char **parameters) {
+  {
+    const char *message = parameters[PARM_MESSAGE];
+    if (message && !*message) message = NULL;
+    messageParameter = message;
+  }
+
+  return 1;
+}
+
+static void
+releaseParameters_NoScreen (void) {
+  messageParameter = NULL;
+}
+
+static int
+currentVirtualTerminal_NoScreen (void) {
+  return SCR_NO_VT;
+}
+
+static void
+describe_NoScreen (ScreenDescription *description) {
+  {
+    const char *message = noDriverReason;
+
+    if (!message) {
+      message = messageParameter;
+      if (!message) message = defaultMessage;
+      message = gettext(message);
+    }
+
+    screenMessage = message;
+  }
+
+  description->rows = 1;
+  description->cols = strlen(screenMessage);
+  description->posx = 0;
+  description->posy = 0;
+  description->number = currentVirtualTerminal_NoScreen();
+}
+
+static int
+readCharacters_NoScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  ScreenDescription description;
+  describe_NoScreen(&description);
+  if (!validateScreenBox(box, description.cols, description.rows)) return 0;
+  setScreenMessage(box, buffer, screenMessage);
+  return 1;
+}
+
+static int
+poll_NoScreen (void) {
+  return 0;
+}
+
+static void
+scr_initialize (MainScreen *main) {
+  initializeMainScreen(main);
+
+  main->base.poll = poll_NoScreen;
+  main->base.describe = describe_NoScreen;
+  main->base.readCharacters = readCharacters_NoScreen;
+  main->base.currentVirtualTerminal = currentVirtualTerminal_NoScreen;
+
+  main->processParameters = processParameters_NoScreen;
+  main->releaseParameters = releaseParameters_NoScreen;
+}
+
+const ScreenDriver *screen = &noScreen;
+
+int
+haveScreenDriver (const char *code) {
+  return haveDriver(code, SCREEN_DRIVER_CODES, driverTable);
+}
+
+const char *
+getDefaultScreenDriver (void) {
+  return getDefaultDriver(driverTable);
+}
+
+const ScreenDriver *
+loadScreenDriver (const char *code, void **driverObject, const char *driverDirectory) {
+  return loadDriver(code, driverObject,
+                    driverDirectory, driverTable,
+                    "screen", 'x', "scr",
+                    &noScreen, &noScreen.definition);
+}
+
+void
+identifyScreenDriver (const ScreenDriver *driver, int full) {
+  identifyDriver("Screen", &driver->definition, full);
+}
+
+void
+identifyScreenDrivers (int full) {
+  const DriverEntry *entry = driverTable;
+  while (entry->address) {
+    const ScreenDriver *driver = entry++->address;
+    identifyScreenDriver(driver, full);
+  }
+}
diff --git a/Programs/scr_emulator.c b/Programs/scr_emulator.c
new file mode 100644
index 0000000..9abd6d4
--- /dev/null
+++ b/Programs/scr_emulator.c
@@ -0,0 +1,286 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/msg.h>
+#include <sys/shm.h>
+
+#include "log.h"
+#include "scr_emulator.h"
+
+static const int ipcCreationFlags = IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR;
+
+void
+moveScreenCharacters (ScreenSegmentCharacter *to, const ScreenSegmentCharacter *from, size_t count) {
+  if (count && (from != to)) {
+    memmove(to, from, (count * sizeof(*from)));
+  }
+}
+
+void
+setScreenCharacters (ScreenSegmentCharacter *from, const ScreenSegmentCharacter *to, const ScreenSegmentCharacter *character) {
+  while (from < to) *from++ = *character;
+}
+
+void
+propagateScreenCharacter (ScreenSegmentCharacter *from, const ScreenSegmentCharacter *to) {
+  setScreenCharacters(from+1, to, from);
+}
+
+void
+fillScreenRows (ScreenSegmentHeader *segment, unsigned int row, unsigned int count, const ScreenSegmentCharacter *character) {
+  while (count--) {
+    const ScreenSegmentCharacter *to;
+    ScreenSegmentCharacter *from = getScreenRow(segment, row++, &to);
+    setScreenCharacters(from, to, character);
+  }
+}
+
+void
+moveScreenRows (ScreenSegmentHeader *segment, unsigned int from, unsigned int to, unsigned int count) {
+  if (count && (from != to)) {
+    moveScreenCharacters(
+      getScreenRow(segment, to, NULL),
+      getScreenRow(segment, from, NULL),
+      (count * segment->screenWidth)
+    );
+  }
+}
+
+#define SWAP(a, b) do { (a) ^= (b); (b) ^= (a); (a) ^= (b); } while (0)
+
+#undef HAVE_BUILTIN_CTZ
+#ifdef __has_builtin
+#if __has_builtin(__builtin_ctz)
+#define HAVE_BUILTIN_CTZ
+#endif /* __has_builtin(__builtin_ctz) */
+#endif /* __has_builtin */
+
+#ifdef HAVE_BUILTIN_CTZ
+static inline int
+ctz (unsigned int x) {
+  return __builtin_ctz(x);
+}
+
+#else /* HAVE_BUILTIN_CTZ */
+#include <string.h>
+
+static inline int
+ctz (unsigned int x) {
+  return ffs(x) - 1;
+}
+#endif /* HAVE_BUILTIN_CTZ */
+
+/* Greatest Common Divisor
+ *
+ * gcd(a,b) computes the greatest common divisor of a and b. I included 
+ * a highly optimized implementation for speed. But the simplest 
+ * implementation would look like:
+ *
+ * unsigned long gcd(unsigned long a, unsigned long b) {
+ *   if (b == 0) return a;
+ *   return gcd(b, a % b);
+ * }
+ */
+static unsigned int
+gcd (unsigned int a, unsigned int b) {
+  unsigned int r = a | b;
+  if (!a || !b) return r;
+
+  b >>= ctz(b);
+  if (b == 1) return r & -r;
+
+  while (1) {
+    a >>= ctz(a);
+    if (a == 1) return r & -r;
+    if (a == b) return a << ctz(r);
+
+    if (a < b) SWAP(a, b);
+    a -= b;
+  }
+}
+
+/* Scrolling the Row Array
+ *
+ * The idea is to have lines indexed into an array. Then, a screen scroll 
+ * can be achieved by performing an array rotation. To scroll one line 
+ * up, the array is rotated left by one position and what used to be the 
+ * top row becomes the bottom row and gets cleared. To scroll one line 
+ * down, the array is rotated left by n-1 positions instead, and the bottom 
+ * row becomes the top row. And this works the same regardless of the 
+ * number of lines to scroll.
+ *
+ * The array rotation algorithm used here is complexity O(n) in execution 
+ * and O(1) in memory usage, n being the array size. The scroll amount 
+ * doesn't affect complexity.
+ *
+ * See https://www.geeksforgeeks.org/array-rotation/ for algorithmic 
+ * details.
+ */
+void
+scrollScreenRows (ScreenSegmentHeader *segment, unsigned int top, unsigned int size, unsigned int count, int down) {
+  if (haveScreenRowArray(segment)) {
+    unsigned int delta = down? (size - count): count;
+
+    for (unsigned int i=0; i<gcd(delta, size); i+=1) {
+      ScreenSegmentRow row = getScreenRowArray(segment)[top + i];
+      unsigned int j = i;
+
+      while (1) {
+        unsigned int k = j + delta;
+        if (k >= size) k -= size;
+        if (k == i) break;
+
+        getScreenRowArray(segment)[top + j] = getScreenRowArray(segment)[top + k];
+        j = k;
+      }
+
+      getScreenRowArray(segment)[top + j] = row;
+    }
+  } else if (down) {
+    moveScreenRows(segment, top, (top + count), (size - count));
+  } else {
+    moveScreenRows(segment, (top + count), top, (size - count));
+  }
+}
+
+int
+destroyScreenSegment (int identifier) {
+  if (shmctl(identifier, IPC_RMID, NULL) != -1) return 1;
+  logSystemError("shmctl[IPC_RMID]");
+  return 0;
+}
+
+static void
+initializeScreenCharacters (ScreenSegmentCharacter *from, const ScreenSegmentCharacter *to) {
+  const ScreenSegmentCharacter initializer = {
+    .text = ' ',
+    .foreground = SCREEN_SEGMENT_COLOR_WHITE,
+    .background = SCREEN_SEGMENT_COLOR_BLACK,
+    .alpha = UINT8_MAX,
+  };
+
+  setScreenCharacters(from, to, &initializer);
+}
+
+ScreenSegmentHeader *
+createScreenSegment (int *identifier, key_t key, int columns, int rows, int enableRowArray) {
+  size_t rowsSize = enableRowArray? (sizeof(ScreenSegmentRow) * rows): 0;
+  size_t charactersSize = sizeof(ScreenSegmentCharacter) * rows * columns;
+
+  size_t segmentSize = sizeof(ScreenSegmentHeader) + rowsSize + charactersSize;
+  int segmentIdentifier;
+
+  if (getScreenSegment(&segmentIdentifier, key)) {
+    destroyScreenSegment(segmentIdentifier);
+  }
+
+  if ((segmentIdentifier = shmget(key, segmentSize, ipcCreationFlags)) != -1) {
+    ScreenSegmentHeader *segment = attachScreenSegment(segmentIdentifier);
+
+    if (segment) {
+      uint32_t nextOffset = 0;
+
+      segment->segmentSize = segmentSize;
+      segment->headerSize = sizeof(*segment);
+      nextOffset += segment->headerSize;
+
+      segment->screenHeight = rows;
+      segment->screenWidth = columns;
+
+      segment->cursorRow = 0;
+      segment->cursorColumn = 0;
+
+      segment->screenNumber = 0;
+      segment->commonFlags = 0;
+      segment->privateFlags = 0;
+
+      if (rowsSize) {
+        segment->rowSize = sizeof(ScreenSegmentRow);
+        segment->rowsOffset = nextOffset;
+        nextOffset += rowsSize;
+      } else {
+        segment->rowSize = 0;
+        segment->rowsOffset = 0;
+      }
+
+      segment->characterSize = sizeof(ScreenSegmentCharacter);
+      segment->charactersOffset = nextOffset;
+      nextOffset += charactersSize;
+
+      if (haveScreenRowArray(segment)) {
+        /* Rows are initially sequential. */
+
+        ScreenSegmentRow *row = getScreenRowArray(segment);
+        ScreenSegmentRow *end = row + rows;
+
+        uint32_t offset = segment->charactersOffset;
+        uint32_t increment = getScreenRowWidth(segment);
+
+        while (row < end) {
+          row->charactersOffset = offset;
+          offset += increment;
+          row += 1;
+        }
+      }
+
+      {
+        const ScreenSegmentCharacter *to;
+        ScreenSegmentCharacter *from = getScreenCharacterArray(segment, &to);
+        initializeScreenCharacters(from, to);
+      }
+
+      if (identifier) *identifier = segmentIdentifier;
+      return segment;
+    }
+
+    destroyScreenSegment(segmentIdentifier);
+  } else {
+    logSystemError("shmget");
+  }
+
+  return NULL;
+}
+
+int
+destroyMessageQueue (int queue) {
+  if (msgctl(queue, IPC_RMID, NULL) != -1) return 1;
+  logSystemError("msgctl[IPC_RMID]");
+  return 0;
+}
+
+int
+createMessageQueue (int *queue, key_t key) {
+  int q;
+
+  if (getMessageQueue(&q, key)) {
+    destroyMessageQueue(q);
+  }
+
+  if ((q = msgget(key, ipcCreationFlags)) != -1) {
+    if (queue) *queue = q;
+    return 1;
+  } else {
+    logSystemError("msgget");
+  }
+
+  return 0;
+}
diff --git a/Programs/scr_frozen.c b/Programs/scr_frozen.c
new file mode 100644
index 0000000..e568596
--- /dev/null
+++ b/Programs/scr_frozen.c
@@ -0,0 +1,129 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "alert.h"
+#include "scr.h"
+#include "scr_frozen.h"
+
+static ScreenDescription screenDescription;
+static ScreenCharacter *screenCharacters;
+
+static int startFreezeReminderAlarm (void);
+static AsyncHandle freezeReminderAlarm = NULL;
+
+ASYNC_ALARM_CALLBACK(handleFreezeReminderAlarm) {
+  asyncDiscardHandle(freezeReminderAlarm);
+  freezeReminderAlarm = NULL;
+
+  alert(ALERT_FREEZE_REMINDER);
+  startFreezeReminderAlarm();
+}
+
+static int
+startFreezeReminderAlarm (void) {
+  if (freezeReminderAlarm) return 1;
+  return asyncNewRelativeAlarm(&freezeReminderAlarm,
+                               SCREEN_FREEZE_REMINDER_INTERVAL,
+                               handleFreezeReminderAlarm, NULL);
+}
+
+static void
+stopFreezeReminderAlarm (void) {
+  if (freezeReminderAlarm) {
+    asyncCancelRequest(freezeReminderAlarm);
+    freezeReminderAlarm = NULL;
+  }
+}
+
+static int
+construct_FrozenScreen (BaseScreen *source) {
+  describeBaseScreen(source, &screenDescription);
+
+  if ((screenCharacters = calloc(screenDescription.rows*screenDescription.cols, sizeof(*screenCharacters)))) {
+    const ScreenBox box = {
+      .left=0, .width=screenDescription.cols,
+      .top=0, .height=screenDescription.rows
+    };
+
+    if (source->readCharacters(&box, screenCharacters)) {
+      startFreezeReminderAlarm();
+      return 1;
+    }
+
+    free(screenCharacters);
+    screenCharacters = NULL;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+destruct_FrozenScreen (void) {
+  stopFreezeReminderAlarm();
+
+  if (screenCharacters) {
+    free(screenCharacters);
+    screenCharacters = NULL;
+  }
+}
+
+static void
+describe_FrozenScreen (ScreenDescription *description) {
+  *description = screenDescription;
+}
+
+static int
+readCharacters_FrozenScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  if (validateScreenBox(box, screenDescription.cols, screenDescription.rows)) {
+    int row;
+    for (row=0; row<box->height; row++) {
+      memcpy(&buffer[row * box->width],
+             &screenCharacters[((box->top + row) * screenDescription.cols) + box->left],
+             box->width * sizeof(*screenCharacters));
+    }
+    return 1;
+  }
+  return 0;
+}
+
+static int
+currentVirtualTerminal_FrozenScreen (void) {
+  return screenDescription.number;
+}
+
+void
+initializeFrozenScreen (FrozenScreen *frozen) {
+  initializeBaseScreen(&frozen->base);
+  frozen->base.describe = describe_FrozenScreen;
+  frozen->base.readCharacters = readCharacters_FrozenScreen;
+  frozen->base.currentVirtualTerminal = currentVirtualTerminal_FrozenScreen;
+  frozen->construct = construct_FrozenScreen;
+  frozen->destruct = destruct_FrozenScreen;
+  screenCharacters = NULL;
+}
diff --git a/Programs/scr_frozen.h b/Programs/scr_frozen.h
new file mode 100644
index 0000000..be355ad
--- /dev/null
+++ b/Programs/scr_frozen.h
@@ -0,0 +1,40 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_FROZEN
+#define BRLTTY_INCLUDED_SCR_FROZEN
+
+#include "scr_base.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  BaseScreen base;
+  int (*construct) (BaseScreen *);		/* called every time the screen is frozen */
+  void (*destruct) (void);		/* called to discard frozen screen image */
+} FrozenScreen;
+
+extern void initializeFrozenScreen (FrozenScreen *frozen);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_FROZEN */
diff --git a/Programs/scr_gpm.c b/Programs/scr_gpm.c
new file mode 100644
index 0000000..f71c9f1
--- /dev/null
+++ b/Programs/scr_gpm.c
@@ -0,0 +1,174 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "device.h"
+#include "get_select.h"
+#include "async_alarm.h"
+#include "scr.h"
+#include "scr_gpm.h"
+
+#ifdef HAVE_LIBGPM
+#include <gpm.h>
+extern int gpm_tried;
+
+typedef enum {
+  GCS_CLOSED,
+  GCS_FAILED,
+  GCS_OPENED
+} GpmConnectionState;
+
+static int gpmConnectionState = GCS_CLOSED;
+
+ASYNC_ALARM_CALLBACK(gpmResetConnection) {
+  gpmConnectionState = GCS_CLOSED;
+}
+
+static int
+gpmOpenConnection (void) {
+  switch (gpmConnectionState) {
+    case  GCS_CLOSED: {
+      Gpm_Connect options = {
+        .eventMask = GPM_MOVE,
+        .defaultMask = ~0,
+        .minMod = 0,
+        .maxMod = ~0
+      };
+
+      gpm_tried = 0;
+      gpm_zerobased = 1;
+
+      if (Gpm_Open(&options, -1) == -1) {
+        logMessage(LOG_DEBUG, "GPM open error: %s", strerror(errno));
+        asyncNewRelativeAlarm(NULL, GPM_CONNECTION_RESET_DELAY, gpmResetConnection, NULL);
+        gpmConnectionState = GCS_FAILED;
+        return 0;
+      }
+
+      logMessage(LOG_DEBUG, "GPM opened: fd=%d con=%d", gpm_fd, gpm_consolefd);
+      gpmConnectionState = GCS_OPENED;
+    }
+
+    case GCS_OPENED:
+      return 1;
+  }
+
+  return 0;
+}
+
+static void
+gpmCloseConnection (int alreadyClosed) {
+  if (gpmConnectionState == GCS_OPENED) {
+    if (!alreadyClosed) Gpm_Close();
+    logMessage(LOG_DEBUG, "GPM closed");
+  }
+  gpmConnectionState = GCS_CLOSED;
+}
+#endif /* HAVE_LIBGPM */
+
+static int
+gpmScreenHandler_highlightRegion (int left, int right, int top, int bottom) {
+#ifdef HAVE_LIBGPM
+  FILE *console = getConsole();
+
+  if (console) {
+    if (gpmOpenConnection() && (gpm_fd >= 0)) {
+      if (Gpm_DrawPointer(left, top, fileno(console)) != -1) return 1;
+
+      if (errno != EINVAL) {
+        logMessage(LOG_DEBUG, "Gpm_DrawPointer error: %s", strerror(errno));
+        gpmCloseConnection(0);
+        return 0;
+      }
+    }
+  }
+#endif /* HAVE_LIBGPM */
+
+  return 0;
+}
+
+static int
+gpmScreenHandler_getPointer (int *column, int *row) {
+  int ok = 0;
+
+#ifdef HAVE_LIBGPM
+  if (gpmOpenConnection()) {
+    if (gpm_fd >= 0) {
+      int error = 0;
+
+      while (1) {
+        fd_set mask;
+        struct timeval timeout;
+        Gpm_Event event;
+        int result;
+
+        FD_ZERO(&mask);
+        FD_SET(gpm_fd, &mask);
+        memset(&timeout, 0, sizeof(timeout));
+
+        if ((result = select(gpm_fd+1, &mask, NULL, NULL, &timeout)) == 0) break;
+        error = 1;
+
+        if (result == -1) {
+          if (errno == EINTR) continue;
+          logSystemError("select");
+          break;
+        }
+
+        if (!FD_ISSET(gpm_fd, &mask)) {
+          logMessage(LOG_DEBUG, "GPM file descriptor not set: %d", gpm_fd);
+          break;
+        }
+
+        if ((result = Gpm_GetEvent(&event)) == -1) {
+          if (errno == EINTR) continue;
+          logSystemError("Gpm_GetEvent");
+          break;
+        }
+
+        error = 0;
+        if (result == 0) {
+          gpmCloseConnection(1);
+          break;
+        }
+
+        *column = event.x;
+        *row = event.y;
+        ok = 1;
+      }
+
+      if (error) gpmCloseConnection(0);
+    }
+  }
+#endif /* HAVE_LIBGPM */
+
+  return ok;
+}
+
+void
+gpmIncludeScreenHandlers (MainScreen *main) {
+  main->base.highlightRegion = gpmScreenHandler_highlightRegion;
+  main->base.getPointer = gpmScreenHandler_getPointer;
+}
diff --git a/Programs/scr_help.c b/Programs/scr_help.c
new file mode 100644
index 0000000..8632d05
--- /dev/null
+++ b/Programs/scr_help.c
@@ -0,0 +1,348 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "scr.h"
+#include "scr_help.h"
+
+typedef struct {
+  wchar_t *characters;
+  size_t length;
+} HelpLineEntry;
+
+typedef struct {
+  HelpLineEntry *lineTable;
+  unsigned int lineLimit;
+
+  unsigned int lineCount;
+  size_t lineLength;
+
+  unsigned char cursorRow;
+  unsigned char cursorColumn;
+} HelpPageEntry;
+
+static HelpPageEntry *pageTable;
+static unsigned int pageLimit;
+static unsigned int pageCount;
+static unsigned int pageIndex;
+
+static void
+initializePageTable (void) {
+  pageTable = NULL;
+  pageLimit = 0;
+  pageCount = 0;
+  pageIndex = 0;
+}
+
+static void
+initializePage (HelpPageEntry *page) {
+  page->lineTable = NULL;
+  page->lineLimit = 0;
+
+  page->lineCount = 0;
+  page->lineLength = 1;
+
+  page->cursorRow = 0;
+  page->cursorColumn = 0;
+}
+
+static unsigned int
+addPage (void) {
+  if (pageCount == pageLimit) {
+    unsigned int newLimit = pageLimit + 1;
+    HelpPageEntry *newTable = realloc(pageTable, ARRAY_SIZE(newTable, newLimit));
+
+    if (!newTable) {
+      logMallocError();
+      return 0;
+    }
+
+    pageTable = newTable;
+    pageLimit = newLimit;
+  }
+
+  {
+    HelpPageEntry *page = &pageTable[pageCount];
+    initializePage(page);
+  }
+
+  return pageCount += 1;
+}
+
+static void
+clearPage (HelpPageEntry *page) {
+  if (page->lineTable) {
+    while (page->lineCount) {
+      HelpLineEntry *line = &page->lineTable[--page->lineCount];
+
+      if (line->characters) free(line->characters);
+    }
+
+    free(page->lineTable);
+    initializePage(page);
+  }
+}
+
+static int
+addLine (HelpPageEntry *page, const wchar_t *characters) {
+  if (page->lineCount == page->lineLimit) {
+    unsigned int newLimit = page->lineLimit? page->lineLimit<<1: 0X40;
+    HelpLineEntry *newTable = realloc(page->lineTable, ARRAY_SIZE(newTable, newLimit));
+
+    if (!newTable) {
+      logMallocError();
+      return 0;
+    }
+
+    page->lineTable = newTable;
+    page->lineLimit = newLimit;
+  }
+
+  {
+    HelpLineEntry *line = &page->lineTable[page->lineCount];
+    size_t length = wcslen(characters);
+    if ((line->length = length) > page->lineLength) page->lineLength = length;
+
+    if (!(line->characters = malloc(ARRAY_SIZE(line->characters, length)))) {
+      logMallocError();
+      return 0;
+    }
+
+    wmemcpy(line->characters, characters, length);
+  }
+
+  page->lineCount += 1;
+  return 1;
+}
+
+static HelpPageEntry *
+getPage (void) {
+  if (pageIndex < pageCount) return &pageTable[pageIndex];
+  logMessage(LOG_WARNING, "help page index out of range: %u >= %u", pageIndex, pageCount);
+  return NULL;
+}
+
+static int
+construct_HelpScreen (void) {
+  initializePageTable();
+  return 1;
+}
+
+static void
+destruct_HelpScreen (void) {
+  if (pageTable) {
+    while (pageCount) clearPage(&pageTable[--pageCount]);
+    free(pageTable);
+  }
+
+  initializePageTable();
+}
+
+static unsigned int
+addPage_HelpScreen (void) {
+  return addPage();
+}
+
+static unsigned int
+getPageCount_HelpScreen (void) {
+  return pageCount;
+}
+
+static unsigned int
+getPageNumber_HelpScreen (void) {
+  return pageIndex + 1;
+}
+
+static int
+setPageNumber_HelpScreen (unsigned int number) {
+  if ((number < 1) || (number > pageCount)) return 0;
+  pageIndex = number - 1;
+  return 1;
+}
+
+static int
+clearPage_HelpScreen (void) {
+  HelpPageEntry *page = getPage();
+
+  if (!page) return 0;
+  clearPage(page);
+  return 1;
+}
+
+static int
+addLine_HelpScreen (const wchar_t *characters) {
+  HelpPageEntry *page = getPage();
+  if (!page) return 0;
+  return addLine(page, characters);
+}
+
+static unsigned int 
+getLineCount_HelpScreen (void) {
+  HelpPageEntry *page = getPage();
+
+  return page? page->lineCount: 0;
+}
+
+static int
+currentVirtualTerminal_HelpScreen (void) {
+  return userVirtualTerminal(pageIndex);
+}
+
+static const char *
+getTitle_HelpScreen (void) {
+  return gettext("Help Screen");
+}
+
+static void
+describe_HelpScreen (ScreenDescription *description) {
+  const HelpPageEntry *page = getPage();
+
+  if (page) {
+    description->posx = page->cursorColumn;
+    description->posy = page->cursorRow;
+    description->cols = page->lineLength;
+    description->rows = page->lineCount;
+    description->number = currentVirtualTerminal_HelpScreen();
+  } else {
+    description->unreadable = gettext("help screen not readable");
+  }
+}
+
+static int
+readCharacters_HelpScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  const HelpPageEntry *page = getPage();
+
+  if (page) {
+    if (validateScreenBox(box, page->lineLength, page->lineCount)) {
+      ScreenCharacter *character = buffer;
+      int row;
+
+      for (row=0; row<box->height; row+=1) {
+        const HelpLineEntry *line = &page->lineTable[box->top + row];
+        int column;
+
+        for (column=0; column<box->width; column+=1) {
+          int index = box->left + column;
+
+          if (index < line->length) {
+            character->text = line->characters[index];
+          } else {
+            character->text = WC_C(' ');
+          }
+
+          character->attributes = SCR_COLOUR_DEFAULT;
+          character += 1;
+        }
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+insertKey_HelpScreen (ScreenKey key) {
+  HelpPageEntry *page = getPage();
+
+  if (page) {
+    switch (key) {
+      case SCR_KEY_CURSOR_UP:
+        if (page->cursorRow > 0) {
+          page->cursorRow -= 1;
+          return 1;
+        }
+        break;
+
+      case SCR_KEY_CURSOR_DOWN:
+        if (page->cursorRow < (page->lineCount - 1)) {
+          page->cursorRow += 1;
+          return 1;
+        }
+        break;
+
+      case SCR_KEY_CURSOR_LEFT:
+        if (page->cursorColumn > 0) {
+          page->cursorColumn -= 1;
+          return 1;
+        }
+        break;
+
+      case SCR_KEY_CURSOR_RIGHT:
+        if (page->cursorColumn < (page->lineLength - 1)) {
+          page->cursorColumn += 1;
+          return 1;
+        }
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return 0;
+}
+
+static int
+routeCursor_HelpScreen (int column, int row, int screen) {
+  HelpPageEntry *page = getPage();
+  if (!page) return 0;
+
+  if (row != -1) {
+    if ((row < 0) || (row >= page->lineCount)) return 0;
+    page->cursorRow = row;
+  }
+
+  if (column != -1) {
+    if ((column < 0) || (column >= page->lineLength)) return 0;
+    page->cursorColumn = column;
+  }
+
+  return 1;
+}
+
+void
+initializeHelpScreen (HelpScreen *help) {
+  initializeBaseScreen(&help->base);
+  help->base.currentVirtualTerminal = currentVirtualTerminal_HelpScreen;
+  help->base.getTitle = getTitle_HelpScreen;
+  help->base.describe = describe_HelpScreen;
+  help->base.readCharacters = readCharacters_HelpScreen;
+  help->base.insertKey = insertKey_HelpScreen;
+  help->base.routeCursor = routeCursor_HelpScreen;
+
+  help->construct = construct_HelpScreen;
+  help->destruct = destruct_HelpScreen;
+
+  help->addPage = addPage_HelpScreen;
+  help->getPageCount = getPageCount_HelpScreen;
+  help->getPageNumber = getPageNumber_HelpScreen;
+  help->setPageNumber = setPageNumber_HelpScreen;
+
+  help->clearPage = clearPage_HelpScreen;
+  help->addLine = addLine_HelpScreen;
+  help->getLineCount = getLineCount_HelpScreen;
+}
diff --git a/Programs/scr_help.h b/Programs/scr_help.h
new file mode 100644
index 0000000..a30be6c
--- /dev/null
+++ b/Programs/scr_help.h
@@ -0,0 +1,49 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_HELP
+#define BRLTTY_INCLUDED_SCR_HELP
+
+#include "scr_base.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  BaseScreen base;
+  int (*construct) (void);
+  void (*destruct) (void);
+
+  unsigned int (*addPage) (void);
+  unsigned int (*getPageCount) (void);
+  unsigned int (*getPageNumber) (void);
+  int (*setPageNumber) (unsigned int number);
+
+  int (*clearPage) (void);
+  int (*addLine) (const wchar_t *characters);
+  unsigned int (*getLineCount) (void);
+} HelpScreen;
+
+extern void initializeHelpScreen (HelpScreen *help);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_HELP */
diff --git a/Programs/scr_internal.h b/Programs/scr_internal.h
new file mode 100644
index 0000000..d461058
--- /dev/null
+++ b/Programs/scr_internal.h
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_INTERNAL
+#define BRLTTY_INCLUDED_SCR_INTERNAL
+
+#include "scr_main.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern MainScreen mainScreen;
+extern BaseScreen *currentScreen;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_INTERNAL */
diff --git a/Programs/scr_main.c b/Programs/scr_main.c
new file mode 100644
index 0000000..59badb9
--- /dev/null
+++ b/Programs/scr_main.c
@@ -0,0 +1,75 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "parameters.h"
+#include "update.h"
+#include "scr.h"
+#include "scr_main.h"
+
+static int
+poll_MainScreen (void) {
+  return 1;
+}
+
+static int
+processParameters_MainScreen (char **parameters) {
+  return 1;
+}
+
+static void
+releaseParameters_MainScreen (void) {
+}
+
+static int
+construct_MainScreen (void) {
+  return 1;
+}
+
+static void
+destruct_MainScreen (void) {
+}
+
+static int
+userVirtualTerminal_MainScreen (int number) {
+  return 1 + number;
+}
+
+void
+initializeMainScreen (MainScreen *main) {
+  initializeBaseScreen(&main->base);
+  main->base.poll = poll_MainScreen;
+
+  main->processParameters = processParameters_MainScreen;
+  main->releaseParameters = releaseParameters_MainScreen;
+
+  main->construct = construct_MainScreen;
+  main->destruct = destruct_MainScreen;
+
+  main->userVirtualTerminal = userVirtualTerminal_MainScreen;
+}
+
+void
+mainScreenUpdated (void) {
+  if (isMainScreen()) {
+    scheduleUpdateIn("main screen updated", SCREEN_UPDATE_SCHEDULE_DELAY);
+  }
+}
diff --git a/Programs/scr_menu.c b/Programs/scr_menu.c
new file mode 100644
index 0000000..dc68136
--- /dev/null
+++ b/Programs/scr_menu.c
@@ -0,0 +1,507 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "parameters.h"
+#include "log.h"
+#include "strfmt.h"
+#include "scr.h"
+#include "scr_special.h"
+#include "scr_menu.h"
+#include "update.h"
+#include "brl_cmds.h"
+#include "cmd_queue.h"
+#include "alert.h"
+#include "utf8.h"
+#include "core.h"
+
+typedef struct {
+  MenuItem *item;
+  size_t length;
+  size_t settingIndent;
+  wchar_t text[0];
+} RenderedMenuItem;
+
+static Menu *rootMenu = NULL;
+
+static int screenUpdated;
+static RenderedMenuItem **screenLines;
+static unsigned int lineCount;
+static unsigned int screenHeight;
+
+static Menu *screenMenu;
+static unsigned int screenColumn;
+static unsigned int screenRow;
+static unsigned int screenWidth;
+
+static inline const MenuItem *
+getCurrentItem (void) {
+  Menu *menu = screenMenu;
+
+  return getMenuItem(menu, getMenuIndex(menu));
+}
+
+static inline void
+setFocusedItem (void) {
+  changeMenuItem(screenLines[ses->winy]->item);
+}
+
+static RenderedMenuItem *
+newRenderedMenuItem (Menu *menu) {
+  MenuItem *item = getCurrentMenuItem(menu);
+
+  char labelString[0X100];
+  size_t labelLength;
+  {
+    const char *title = getMenuItemTitle(item);
+    const char *subtitle = getMenuItemSubtitle(item);
+
+    STR_BEGIN(labelString, ARRAY_COUNT(labelString));
+    STR_PRINTF("%s", title);
+
+    if (*subtitle) {
+      if (*title) STR_PRINTF(" ");
+      STR_PRINTF("%s", subtitle);
+    }
+
+    if (labelString[0]) {
+      if (!isMenuItemAction(item)) STR_PRINTF(":");
+      STR_PRINTF(" ");
+    }
+
+    labelLength = STR_LENGTH;
+    STR_END;
+  }
+
+  char settingString[0X100];
+  size_t settingLength;
+  {
+    const char *text = getMenuItemText(item);
+    const char *comment = getMenuItemComment(item);
+
+    if (!text) {
+      text = "";
+    } else if (!*text) {
+      text = gettext("<off>");
+    }
+
+    STR_BEGIN(settingString, ARRAY_COUNT(settingString));
+    STR_PRINTF("%s", text);
+    if (*comment) STR_PRINTF(" (%s)", comment);
+    settingLength = STR_LENGTH;
+    STR_END;
+  }
+
+  {
+    size_t maximumLength = labelLength + settingLength;
+    wchar_t characters[maximumLength];
+
+    size_t settingIndent;
+    size_t actualLength;
+
+    {
+      size_t currentLength = 0;
+       
+      currentLength += makeWcharsFromUtf8(labelString, &characters[currentLength], maximumLength-currentLength);
+      settingIndent = currentLength;
+
+      currentLength += makeWcharsFromUtf8(settingString, &characters[currentLength], maximumLength-currentLength);
+      actualLength = currentLength;
+    }
+
+    {
+      RenderedMenuItem *rmi;
+      size_t size = sizeof(*rmi) + (actualLength * sizeof(rmi->text[0]));
+
+      if ((rmi = malloc(size))) {
+        memset(rmi, 0, sizeof(*rmi));
+        wmemcpy(rmi->text, characters, actualLength);
+
+        rmi->item = item;
+        rmi->length = actualLength;
+        rmi->settingIndent = settingIndent;
+
+        return rmi;
+      } else {
+        logMallocError();
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static void
+destroyRenderedMenuItem (RenderedMenuItem *rmi) {
+  free(rmi);
+}
+
+static void
+removeLines (void) {
+  while (screenHeight > 0) {
+    destroyRenderedMenuItem(screenLines[--screenHeight]);
+  }
+}
+
+static int
+setScreenRow (void) {
+  const MenuItem *item = getCurrentItem();
+
+  for (unsigned int row=0; row<screenHeight; row+=1) {
+    const RenderedMenuItem *rmi = screenLines[row];
+
+    if (rmi->item == item) {
+      screenRow = row;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+reloadScreen (int constructing) {
+  Menu *menu = getCurrentSubmenu(rootMenu);
+  unsigned int index = getMenuIndex(menu);
+
+  screenMenu = menu;
+  screenWidth = 0;
+  removeLines();
+
+  if (changeMenuItemFirst(menu)) {
+    {
+      unsigned int count = getMenuSize(menu);
+
+      if (count > lineCount) {
+        RenderedMenuItem **lines = realloc(screenLines, ARRAY_SIZE(screenLines, count));
+
+        if (!lines) {
+          logMallocError();
+          return 0;
+        }
+
+        screenLines = lines;
+        lineCount = count;
+      }
+    }
+
+    do {
+      RenderedMenuItem *rmi = newRenderedMenuItem(menu);
+
+      if (!rmi) return 0;
+      screenLines[screenHeight++] = rmi;
+      if (screenWidth < rmi->length) screenWidth = rmi->length;
+    } while (changeMenuItemNext(menu, 0));
+
+    if (changeMenuItemIndex(menu, index)) {
+      if (constructing) {
+        screenRow = 0;
+        screenColumn = 0;
+      } else {
+        if (!setScreenRow()) return 0;
+        screenColumn = screenLines[screenRow]->settingIndent;
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+refresh_MenuScreen (void) {
+  if (screenUpdated) {
+    if (!reloadScreen(0)) return 0;
+    screenUpdated = 0;
+  }
+
+  return 1;
+}
+
+static int
+construct_MenuScreen (Menu *menu) {
+  rootMenu = menu;
+
+  screenUpdated = 0;
+  screenLines = NULL;
+  lineCount = 0;
+  screenHeight = 0;
+
+  return reloadScreen(1);
+}
+
+static void
+destruct_MenuScreen (void) {
+  if (screenLines) {
+    removeLines();
+    free(screenLines);
+    screenLines = NULL;
+  }
+
+  rootMenu = NULL;
+}
+
+static int
+currentVirtualTerminal_MenuScreen (void) {
+  return userVirtualTerminal(2 + getMenuNumber(screenMenu));
+}
+
+static const char *
+getTitle_MenuScreen (void) {
+  return gettext("Preferences Menu");
+}
+
+static void
+describe_MenuScreen (ScreenDescription *description) {
+  description->cols = MAX(screenWidth, 1);
+  description->rows = MAX(screenHeight, 1);
+
+  description->posx = screenColumn;
+  description->posy = screenRow;
+
+  description->number = currentVirtualTerminal_MenuScreen();
+}
+
+static int
+readCharacters_MenuScreen (const ScreenBox *box, ScreenCharacter *buffer) {
+  if (validateScreenBox(box, screenWidth, screenHeight)) {
+    ScreenCharacter *character = buffer;
+
+    for (unsigned int row=0; row<box->height; row+=1) {
+      const RenderedMenuItem *rmi = screenLines[row + box->top];
+
+      for (unsigned int column=0; column<box->width; column+=1) {
+        unsigned int index = column + box->left;
+
+        character->text = (index < rmi->length)? rmi->text[index]: WC_C(' ');
+        character->attributes = SCR_COLOUR_DEFAULT;
+        character += 1;
+      }
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static void
+commandRejected (void) {
+  alert(ALERT_COMMAND_REJECTED);
+}
+
+static void
+itemChanged (void) {
+  setScreenRow();
+  screenColumn = 0;
+}
+
+static void
+settingChanged (void) {
+  screenUpdated = 1;
+}
+
+void
+menuScreenUpdated (void) {
+  screenUpdated = 1;
+
+  if (isSpecialScreen(SCR_MENU)) {
+    scheduleUpdateIn("menu screen updated", SCREEN_UPDATE_SCHEDULE_DELAY);
+  }
+}
+
+static int
+handleCommand_MenuScreen (int command) {
+  switch (command) {
+    case BRL_CMD_KEY(BACKSPACE):
+    case BRL_CMD_MENU_PREV_LEVEL: {
+      Menu *menu = screenMenu;
+
+      if (menu != rootMenu) {
+        if (!changeMenuItemIndex(screenMenu, 0)) {
+          commandRejected();
+        } else if (!changeMenuSettingNext(menu, 0)) {
+          commandRejected();
+        } else {
+          setFocusedItem();
+          settingChanged();
+        }
+
+        return 1;
+      }
+    }
+    /* fall through */
+    case BRL_CMD_KEY(ESCAPE):
+    case BRL_CMD_KEY(ENTER): {
+      int handled = handleCommand(BRL_CMD_PREFMENU);
+      if (handled) setFocusedItem();
+      return handled;
+    }
+
+    case BRL_CMD_KEY(HOME): {
+      int handled = handleCommand(BRL_CMD_PREFLOAD);
+
+      if (handled) settingChanged();
+      return handled;
+    }
+
+    case BRL_CMD_KEY(END): {
+      int handled = handleCommand(BRL_CMD_PREFSAVE);
+
+      if (handled) setFocusedItem();
+      return handled;
+    }
+
+    case BRL_CMD_KEY(PAGE_UP):
+    case BRL_CMD_MENU_FIRST_ITEM: {
+      if (changeMenuItemFirst(screenMenu)) {
+        itemChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_KEY(PAGE_DOWN):
+    case BRL_CMD_MENU_LAST_ITEM: {
+      if (changeMenuItemLast(screenMenu)) {
+        itemChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_KEY(CURSOR_UP):
+    case BRL_CMD_MENU_PREV_ITEM: {
+      if (changeMenuItemPrevious(screenMenu, 1)) {
+        itemChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_KEY(CURSOR_DOWN):
+    case BRL_CMD_MENU_NEXT_ITEM: {
+      if (changeMenuItemNext(screenMenu, 1)) {
+        itemChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_KEY(CURSOR_LEFT):
+    case BRL_CMD_BACK:
+    case BRL_CMD_MENU_PREV_SETTING: {
+      setFocusedItem();
+
+      if (changeMenuSettingPrevious(screenMenu, 1)) {
+        settingChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_KEY(CURSOR_RIGHT):
+    case BRL_CMD_HOME:
+    case BRL_CMD_RETURN:
+    case BRL_CMD_MENU_NEXT_SETTING: {
+      setFocusedItem();
+
+      if (changeMenuSettingNext(screenMenu, 1)) {
+        settingChanged();
+      } else {
+        commandRejected();
+      }
+
+      return 1;
+    }
+
+    case BRL_CMD_CSRJMP_VERT: {
+      setFocusedItem();
+      return 1;
+    }
+
+    default: {
+      if ((command & BRL_MSK_BLK) == BRL_CMD_BLK(ROUTE)) {
+        unsigned int key = command & BRL_MSK_ARG;
+        unsigned int count = brl.textColumns;
+
+        if (key < count) {
+          setFocusedItem();
+
+          if (changeMenuSettingScaled(screenMenu, key, count)) {
+            settingChanged();
+          } else {
+            commandRejected();
+          }
+        } else if ((key >= statusStart) && (key < (statusStart + statusCount))) {
+          switch (key - statusStart) {
+            default:
+              commandRejected();
+              break;
+          }
+        } else {
+          commandRejected();
+        }
+
+        return 1;
+      }
+
+      break;
+    }
+  }
+
+  return 0;
+}
+
+static KeyTableCommandContext
+getCommandContext_MenuScreen (void) {
+  return KTB_CTX_MENU;
+}
+
+void
+initializeMenuScreen (MenuScreen *menu) {
+  initializeBaseScreen(&menu->base);
+
+  menu->base.currentVirtualTerminal = currentVirtualTerminal_MenuScreen;
+  menu->base.getTitle = getTitle_MenuScreen;
+
+  menu->base.refresh = refresh_MenuScreen;
+  menu->base.describe = describe_MenuScreen;
+  menu->base.readCharacters = readCharacters_MenuScreen;
+
+  menu->base.handleCommand = handleCommand_MenuScreen;
+  menu->base.getCommandContext = getCommandContext_MenuScreen;
+
+  menu->construct = construct_MenuScreen;
+  menu->destruct = destruct_MenuScreen;
+}
diff --git a/Programs/scr_menu.h b/Programs/scr_menu.h
new file mode 100644
index 0000000..401d2fa
--- /dev/null
+++ b/Programs/scr_menu.h
@@ -0,0 +1,42 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_MENU
+#define BRLTTY_INCLUDED_SCR_MENU
+
+#include "scr_base.h"
+#include "menu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  BaseScreen base;
+  int (*construct) (Menu *menu);
+  void (*destruct) (void);
+} MenuScreen;
+
+extern void initializeMenuScreen (MenuScreen *menu);
+extern void menuScreenUpdated (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_MENU */
diff --git a/Programs/scr_real.c b/Programs/scr_real.c
new file mode 100644
index 0000000..7db2960
--- /dev/null
+++ b/Programs/scr_real.c
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "scr.h"
+#include "scr_real.h"
+#include "routing.h"
+
+static int
+routeCursor_RealScreen (int column, int row, int screen) {
+  return startRouting(column, row, screen);
+}
+
+void
+initializeRealScreen (MainScreen *main) {
+  initializeMainScreen(main);
+  main->base.routeCursor = routeCursor_RealScreen;
+}
diff --git a/Programs/scr_special.c b/Programs/scr_special.c
new file mode 100644
index 0000000..8d052c5
--- /dev/null
+++ b/Programs/scr_special.c
@@ -0,0 +1,267 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* scr.cc - The screen reading library
+ *
+ * Note: Although C++, this code requires no standard C++ library.
+ * This is important as BRLTTY *must not* rely on too many
+ * run-time shared libraries, nor be a huge executable.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "scr.h"
+#include "scr_special.h"
+#include "update.h"
+#include "message.h"
+
+#include "scr_frozen.h"
+static FrozenScreen frozenScreen;
+
+static int
+frozenScreen_construct (void) {
+  return frozenScreen.construct(&mainScreen.base);
+}
+
+#include "scr_help.h"
+static HelpScreen helpScreen;
+
+static int
+helpScreen_construct (void) {
+  return helpScreen.construct();
+}
+
+#include "scr_menu.h"
+#include "menu_prefs.h"
+static MenuScreen menuScreen;
+
+static int
+menuScreen_construct (void) {
+  Menu *menu = getPreferencesMenu();
+  if (!menu) return 0;
+
+  updateLogMessagesSubmenu();
+  return menuScreen.construct(menu);
+}
+
+typedef struct {
+  const char *const name;
+  int (*const construct) (void);
+  void (**const destruct) (void);
+  BaseScreen *const base;
+
+  const unsigned autoDestruct:1;
+
+  unsigned isConstructed:1;
+  unsigned isActive:1;
+} SpecialScreenEntry;
+
+#define SPECIAL_SCREEN_INITIALIZER(type) \
+  .name = #type, \
+  .construct = type ## Screen_construct, \
+  .destruct = &type ## Screen.destruct, \
+  .base = &type ## Screen.base
+
+static SpecialScreenEntry specialScreenTable[] = {
+  [SCR_FROZEN] = {
+    SPECIAL_SCREEN_INITIALIZER(frozen),
+    .autoDestruct = 1
+  },
+
+  [SCR_HELP] = {
+    SPECIAL_SCREEN_INITIALIZER(help)
+  },
+
+  [SCR_MENU] = {
+    SPECIAL_SCREEN_INITIALIZER(menu)
+  },
+};
+
+static const unsigned char specialScreenCount = ARRAY_COUNT(specialScreenTable);
+
+static SpecialScreenEntry *
+getSpecialScreenEntry (SpecialScreenType type) {
+  return &specialScreenTable[type];
+}
+
+static void
+logScreenAction (const char *type, const char *name, const char *action) {
+  logMessage(LOG_DEBUG, "%s %s screen: %s", action, type, name);
+}
+
+static void
+logMainScreenAction (const char *action) {
+  logScreenAction("main", screen->definition.name, action);
+}
+
+static void
+logSpecialScreenAction (const SpecialScreenEntry *sse, const char *action) {
+  logScreenAction("special", sse->name, action);
+}
+
+static int
+constructSpecialScreen (SpecialScreenEntry *sse) {
+  if (sse->isConstructed) return !sse->autoDestruct;
+  logSpecialScreenAction(sse, "constructing");
+  if (!sse->construct()) return 0;
+  sse->isConstructed = 1;
+  return 1;
+}
+
+static void
+destructSpecialScreen (SpecialScreenEntry *sse) {
+  if (sse->isConstructed) {
+    logSpecialScreenAction(sse, "destructing");
+    (*sse->destruct)();
+    sse->isConstructed = 0;
+  }
+}
+
+void
+beginSpecialScreens (void) {
+  initializeFrozenScreen(&frozenScreen);
+  initializeMenuScreen(&menuScreen);
+  initializeHelpScreen(&helpScreen);
+}
+
+void
+endSpecialScreens (void) {
+  SpecialScreenEntry *sse = specialScreenTable;
+  SpecialScreenEntry *end = sse + specialScreenCount;
+
+  while (sse < end) {
+    destructSpecialScreen(sse);
+    sse += 1;
+  }
+}
+
+static void
+announceCurrentScreen (void) {
+  const char *title = currentScreen->getTitle();
+  if (title) message(NULL, title, 0);
+}
+
+static void
+setCurrentScreen (BaseScreen *screen) {
+  if (screen != currentScreen) {
+    currentScreen->onBackground();
+    currentScreen = screen;
+    currentScreen->onForeground();
+
+    scheduleUpdate("new screen selected");
+    announceCurrentScreen();
+  }
+}
+
+static void
+setSpecialScreen (const SpecialScreenEntry *sse) {
+  logSpecialScreenAction(sse, "selecting");
+  setCurrentScreen(sse->base);
+}
+
+static void
+selectCurrentScreen (void) {
+  const SpecialScreenEntry *sse = specialScreenTable;
+  const SpecialScreenEntry *end = sse + specialScreenCount;
+
+  while (sse < end) {
+    if (sse->isActive) {
+      setSpecialScreen(sse);
+      return;
+    }
+
+    sse += 1;
+  }
+
+  logMainScreenAction("selecting");
+  setCurrentScreen(&mainScreen.base);
+}
+
+int
+activateSpecialScreen (SpecialScreenType type) {
+  SpecialScreenEntry *sse = getSpecialScreenEntry(type);
+
+  if (!constructSpecialScreen(sse)) return 0;
+  logSpecialScreenAction(sse, "activating");
+  sse->isActive = 1;
+  setSpecialScreen(sse);
+  return 1;
+}
+
+void
+deactivateSpecialScreen (SpecialScreenType type) {
+  SpecialScreenEntry *sse = getSpecialScreenEntry(type);
+
+  logSpecialScreenAction(sse, "deactivating");
+  sse->isActive = 0;
+  if (sse->autoDestruct) destructSpecialScreen(sse);
+  selectCurrentScreen();
+}
+
+int
+haveSpecialScreen (SpecialScreenType type) {
+  return getSpecialScreenEntry(type)->isActive;
+}
+
+int
+isSpecialScreen (SpecialScreenType type) {
+  return currentScreen == getSpecialScreenEntry(type)->base;
+}
+
+int
+constructHelpScreen (void) {
+  SpecialScreenEntry *sse = getSpecialScreenEntry(SCR_HELP);
+
+  return constructSpecialScreen(sse);
+}
+
+int
+addHelpPage (void) {
+  return helpScreen.addPage();
+}
+
+unsigned int
+getHelpPageCount (void) {
+  return helpScreen.getPageCount();
+}
+
+unsigned int
+getHelpPageNumber (void) {
+  return helpScreen.getPageNumber();
+}
+
+int
+setHelpPageNumber (unsigned int number) {
+  return helpScreen.setPageNumber(number);
+}
+
+int
+clearHelpPage (void) {
+  return helpScreen.clearPage();
+}
+
+int
+addHelpLine (const wchar_t *characters) {
+  return helpScreen.addLine(characters);
+}
+
+unsigned int
+getHelpLineCount (void) {
+  return helpScreen.getLineCount();
+}
diff --git a/Programs/scr_special.h b/Programs/scr_special.h
new file mode 100644
index 0000000..5c339a6
--- /dev/null
+++ b/Programs/scr_special.h
@@ -0,0 +1,55 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SCR_SPECIAL
+#define BRLTTY_INCLUDED_SCR_SPECIAL
+
+#include "scr_internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern void beginSpecialScreens (void);
+extern void endSpecialScreens (void);
+
+typedef enum {
+  SCR_FROZEN,
+  SCR_MENU,
+  SCR_HELP,
+} SpecialScreenType;
+
+extern int activateSpecialScreen (SpecialScreenType type);
+extern void deactivateSpecialScreen (SpecialScreenType type);
+extern int haveSpecialScreen (SpecialScreenType type);
+extern int isSpecialScreen (SpecialScreenType type);
+
+extern int constructHelpScreen (void);
+extern int addHelpPage (void);
+extern unsigned int getHelpPageCount (void);
+extern unsigned int getHelpPageNumber (void);
+extern int setHelpPageNumber (unsigned int number);
+extern int clearHelpPage (void);
+extern int addHelpLine (const wchar_t *characters);
+extern unsigned int getHelpLineCount (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SCR_SPECIAL */
diff --git a/Programs/scr_terminal.c b/Programs/scr_terminal.c
new file mode 100644
index 0000000..95ea1b9
--- /dev/null
+++ b/Programs/scr_terminal.c
@@ -0,0 +1,170 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/msg.h>
+#include <sys/shm.h>
+
+#include "log.h"
+#include "scr_terminal.h"
+
+int
+makeTerminalKey (key_t *key, const char *path) {
+  key_t result = ftok(path, 't');
+  int gotKey = result != -1;
+
+  if (gotKey) {
+    *key = result;
+  } else {
+    logSystemError("ftok");
+  }
+
+  return gotKey;
+}
+
+int
+getMessageQueue (int *queue, key_t key) {
+  int result = msgget(key, 0);
+  int foundQueue = result != -1;
+
+  if (foundQueue) {
+    *queue = result;
+  } else if (errno != ENOENT) {
+    logSystemError("msgget");
+  }
+
+  return foundQueue;
+}
+
+int
+getScreenSegment (int *identifier, key_t key) {
+  int result = shmget(key, 0, 0);
+  int foundSegment = result != -1;
+
+  if (foundSegment) {
+    *identifier = result;
+  } else if (errno != ENOENT) {
+    logSystemError("shmget");
+  }
+
+  return foundSegment;
+}
+
+ScreenSegmentHeader *
+attachScreenSegment (int identifier) {
+  void *address = shmat(identifier, NULL, 0);
+  if (address != (void *)-1) return address;
+
+  logSystemError("shmat");
+  return NULL;
+}
+
+int
+detachScreenSegment (ScreenSegmentHeader *segment) {
+  if (shmdt(segment) != -1) return 1;
+  logSystemError("shmdt");
+  return 0;
+}
+
+ScreenSegmentHeader *
+getScreenSegmentForKey (key_t key) {
+  int identifier;
+
+  if (getScreenSegment(&identifier, key)) {
+    ScreenSegmentHeader *segment = attachScreenSegment(identifier);
+    if (segment) return segment;
+  }
+
+  return NULL;
+}
+
+ScreenSegmentHeader *
+getScreenSegmentForPath (const char *path) {
+  key_t key;
+  if (!makeTerminalKey(&key, path)) return NULL;
+  return getScreenSegmentForKey(key);
+}
+
+void
+logScreenSegment (const ScreenSegmentHeader *segment) {
+  const void *const address = segment;
+  const unsigned char *const bytes = address;
+
+  uint32_t offset = 0;
+  const uint32_t end = segment->segmentSize;
+  unsigned int increment = 0X10;
+  const int width = snprintf(NULL, 0, "%X", end);
+
+  while (offset < end) {
+    {
+      const uint32_t count = end - offset;
+      if (increment > count) increment = count;
+    }
+
+    logBytes(LOG_NOTICE, "screen segment: %0*X", &bytes[offset], increment, width, offset);
+    offset += increment;
+  }
+}
+
+void *
+getScreenItem (ScreenSegmentHeader *segment, uint32_t offset) {
+  void *address = segment;
+  address += offset;
+  return address;
+}
+
+ScreenSegmentRow *
+getScreenRowArray (ScreenSegmentHeader *segment) {
+  return getScreenItem(segment, segment->rowsOffset);
+}
+
+ScreenSegmentCharacter *
+getScreenCharacterArray (ScreenSegmentHeader *segment, const ScreenSegmentCharacter **end) {
+  ScreenSegmentCharacter *array = getScreenItem(segment, segment->charactersOffset);
+  if (end) *end = array + getScreenCharacterCount(segment);
+  return array;
+}
+
+ScreenSegmentCharacter *
+getScreenRow (ScreenSegmentHeader *segment, unsigned int row, const ScreenSegmentCharacter **end) {
+  void *address = segment;
+
+  if (haveScreenRowArray(segment)) {
+    address += getScreenRowArray(segment)[row].charactersOffset;
+  } else {
+    address += segment->charactersOffset;
+    address += row * getScreenRowWidth(segment);
+  }
+
+  if (end) {
+    *end = address + getScreenRowWidth(segment);
+  }
+
+  return address;
+}
+
+ScreenSegmentCharacter *
+getScreenCharacter (ScreenSegmentHeader *segment, unsigned int row, unsigned int column, const ScreenSegmentCharacter **end) {
+  void *address = getScreenRow(segment, row, end);
+  address += column * segment->characterSize;
+  return address;
+}
diff --git a/Programs/scr_utils.c b/Programs/scr_utils.c
new file mode 100644
index 0000000..a6d733e
--- /dev/null
+++ b/Programs/scr_utils.c
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "scr_utils.h"
+
+void
+setScreenCharacterText (ScreenCharacter *characters, wchar_t text, size_t count) {
+  while (count > 0) {
+    characters[--count].text = text;
+  }
+}
+
+void
+setScreenCharacterAttributes (ScreenCharacter *characters, unsigned char attributes, size_t count) {
+  while (count > 0) {
+    characters[--count].attributes = attributes;
+  }
+}
+
+void
+clearScreenCharacters (ScreenCharacter *characters, size_t count) {
+  setScreenCharacterText(characters, WC_C(' '), count);
+  setScreenCharacterAttributes(characters, SCR_COLOUR_DEFAULT, count);
+}
diff --git a/Programs/scrtest.c b/Programs/scrtest.c
new file mode 100644
index 0000000..1526ea0
--- /dev/null
+++ b/Programs/scrtest.c
@@ -0,0 +1,256 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "parse.h"
+#include "scr.h"
+
+static char *opt_boxLeft;
+static char *opt_boxWidth;
+static char *opt_boxTop;
+static char *opt_boxHeight;
+static char *opt_screenDriver;
+static char *opt_driversDirectory;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "screen-driver",
+    .letter = 'x',
+    .argument = "driver",
+    .setting.string = &opt_screenDriver,
+    .internal.setting = DEFAULT_SCREEN_DRIVER,
+    .description = "Screen driver: one of {" SCREEN_DRIVER_CODES "}"
+  },
+
+  { .word = "left",
+    .letter = 'l',
+    .argument = "column",
+    .setting.string = &opt_boxLeft,
+    .description = "Left edge of region (from 0)."
+  },
+
+  { .word = "columns",
+    .letter = 'c',
+    .argument = "count",
+    .setting.string = &opt_boxWidth,
+    .description = "Width of region."
+  },
+
+  { .word = "top",
+    .letter = 't',
+    .argument = "row",
+    .setting.string = &opt_boxTop,
+    .description = "Top edge of region (from 0)."
+  },
+
+  { .word = "rows",
+    .letter = 'r',
+    .argument = "count",
+    .setting.string = &opt_boxHeight,
+    .description = "Height of region."
+  },
+
+  { .word = "drivers-directory",
+    .letter = 'D',
+    .argument = "directory",
+    .setting.string = &opt_driversDirectory,
+    .internal.setting = DRIVERS_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = "Path to directory for loading drivers."
+  },
+END_OPTION_TABLE(programOptions)
+
+static int
+setRegion (
+  int *offsetValue, const char *offsetOption, const char *offsetName,
+  int *sizeValue, const char *sizeOption, int sizeLimit, const char *sizeName
+) {
+  if (*offsetOption) {
+    {
+      const int minimum = 0;
+      const int maximum = sizeLimit - 1;
+      if (!validateInteger(offsetValue, offsetOption, &minimum, &maximum)) {
+        logMessage(LOG_ERR, "invalid %s: %s", offsetName, offsetOption);
+        return 0;
+      }
+    }
+
+    if (*sizeOption) {
+      const int minimum = 1;
+      const int maximum = sizeLimit - *offsetValue;
+      if (!validateInteger(sizeValue, sizeOption, &minimum, &maximum)) {
+        logMessage(LOG_ERR, "invalid %s: %s", sizeName, sizeOption);
+        return 0;
+      }
+      return 1;
+    }
+  } else if (*sizeOption) {
+    const int minimum = 1;
+    const int maximum = sizeLimit;
+    if (!validateInteger(sizeValue, sizeOption, &minimum, &maximum)) {
+      logMessage(LOG_ERR, "invalid %s: %s", sizeName, sizeOption);
+      return 0;
+    }
+    *offsetValue = (sizeLimit - *sizeValue) / 2;
+    return 1;
+  } else {
+    *offsetValue = sizeLimit / 4;
+  }
+  if ((*sizeValue = sizeLimit - (*offsetValue * 2)) < 1) *sizeValue = 1;
+  return 1;
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+  void *driverObject;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "scrtest",
+
+      .usage = {
+        .purpose = strtext("Test a screen driver."),
+        .parameters = "[parameter=value ...]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if ((screen = loadScreenDriver(opt_screenDriver, &driverObject, opt_driversDirectory))) {
+    const char *const *parameterNames = getScreenParameters(screen);
+    char **parameterSettings;
+
+    if (!parameterNames) {
+      static const char *const noNames[] = {NULL};
+      parameterNames = noNames;
+    }
+
+    {
+      const char *const *name = parameterNames;
+      unsigned int count;
+      char **setting;
+      while (*name) ++name;
+      count = name - parameterNames;
+      if (!(parameterSettings = malloc((count + 1) * sizeof(*parameterSettings)))) {
+        logMallocError();
+        return PROG_EXIT_FATAL;
+      }
+      setting = parameterSettings;
+      while (count--) *setting++ = "";
+      *setting = NULL;
+    }
+
+    while (argc) {
+      char *assignment = *argv++;
+      int ok = 0;
+      char *delimiter = strchr(assignment, '=');
+      if (!delimiter) {
+        logMessage(LOG_ERR, "missing screen parameter value: %s", assignment);
+      } else if (delimiter == assignment) {
+        logMessage(LOG_ERR, "missing screen parameter name: %s", assignment);
+      } else {
+        size_t nameLength = delimiter - assignment;
+        const char *const *name = parameterNames;
+        while (*name) {
+          if (strncasecmp(assignment, *name, nameLength) == 0) {
+            parameterSettings[name - parameterNames] = delimiter + 1;
+            ok = 1;
+            break;
+          }
+          ++name;
+        }
+        if (!ok) logMessage(LOG_ERR, "invalid screen parameter: %s", assignment);
+      }
+      if (!ok) return PROG_EXIT_SYNTAX;
+      --argc;
+    }
+
+    if (constructScreenDriver(parameterSettings)) {
+      ScreenDescription description;
+      int left, top, width, height;
+
+      describeScreen(&description);
+      printf("Screen: %dx%d\n", description.cols, description.rows);
+      printf("Cursor: [%d,%d]\n", description.posx, description.posy);
+
+      if (setRegion(&left, opt_boxLeft, "starting column",
+                &width, opt_boxWidth, description.cols, "region width")) {
+        if (setRegion(&top, opt_boxTop, "starting row",
+                  &height, opt_boxHeight, description.rows, "region height")) {
+          printf("Region: %dx%d@[%d,%d]\n", width, height, left, top);
+
+          {
+            ScreenCharacter buffer[width * height];
+
+            if (readScreen(left, top, width, height, buffer)) {
+              int line;
+              for (line=0; line<height; line++) {
+                int column;
+                for (column=0; column<width; column++) {
+                  wchar_t character = buffer[line * width + column].text;
+                  if (!iswLatin1(character)) {
+                    putchar('?');
+                  } else if (!isprint(character)) {
+                    putchar('*');
+                  } else {
+                    putchar(character);
+                  }
+                }
+                putchar('\n');
+              }
+              exitStatus = PROG_EXIT_SUCCESS;
+            } else {
+              logMessage(LOG_ERR, "Can't read screen.");
+              exitStatus = PROG_EXIT_FATAL;
+            }
+          }
+        } else {
+          exitStatus = PROG_EXIT_SYNTAX;
+        }
+      } else {
+        exitStatus = PROG_EXIT_SYNTAX;
+      }
+    } else {
+      logMessage(LOG_ERR, "can't open screen.");
+      exitStatus = PROG_EXIT_FATAL;
+    }
+
+    destructScreenDriver();
+  } else {
+    logMessage(LOG_ERR, "can't load screen driver.");
+    exitStatus = PROG_EXIT_FATAL;
+  }
+  return exitStatus;
+}
+
+#include "update.h"
+
+void
+scheduleUpdateIn (const char *reason, int delay) {
+}
diff --git a/Programs/serial.c b/Programs/serial.c
new file mode 100644
index 0000000..192626d
--- /dev/null
+++ b/Programs/serial.c
@@ -0,0 +1,897 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_log.h"
+#include "strfmt.h"
+#include "parameters.h"
+#include "parse.h"
+#include "device.h"
+#include "async_wait.h"
+
+#if defined(USE_PKG_SERIAL_NONE)
+#include "serial_none.h"
+#elif defined(USE_PKG_SERIAL_GRUB)
+#include "serial_grub.h"
+#elif defined(USE_PKG_SERIAL_MSDOS)
+#include "serial_msdos.h"
+#elif defined(USE_PKG_SERIAL_TERMIOS)
+#include "serial_termios.h"
+#elif defined(USE_PKG_SERIAL_WINDOWS)
+#include "serial_windows.h"
+#else /* serial package */
+#error serial I/O package not selected
+#include "serial_none.h"
+#endif /* serial package */
+
+#include "serial_internal.h"
+
+const char *
+serialGetDevicePath (SerialDevice *serial) {
+  return serial->devicePath;
+}
+
+const SerialBaudEntry *
+serialGetBaudEntry (unsigned int baud) {
+  const SerialBaudEntry *entry = serialBaudTable;
+
+  while (entry->baud) {
+    if (baud == entry->baud) return entry;
+    entry += 1;
+  }
+
+  logMessage(LOG_WARNING, "undefined serial baud: %u", baud);
+  return NULL;
+}
+
+static void
+serialInitializeAttributes (SerialAttributes *attributes) {
+  memset(attributes, 0, sizeof(*attributes));
+  serialPutInitialAttributes(attributes);
+
+  {
+    const SerialBaudEntry *entry = serialGetBaudEntry(SERIAL_DEFAULT_BAUD);
+
+    if (entry) {
+      if (!serialPutSpeed(attributes, entry->speed)) {
+        logMessage(LOG_WARNING, "default serial baud not supported: %u", SERIAL_DEFAULT_BAUD);
+      }
+    }
+  }
+
+  if (!serialPutDataBits(attributes, SERIAL_DEFAULT_DATA_BITS)) {
+    logMessage(LOG_WARNING, "default serial data bits not supported: %u", SERIAL_DEFAULT_DATA_BITS);
+  }
+
+  if (!serialPutStopBits(attributes, SERIAL_DEFAULT_STOP_BITS)) {
+    logMessage(LOG_WARNING, "default serial stop bits not supported: %u", SERIAL_DEFAULT_STOP_BITS);
+  }
+
+  if (!serialPutParity(attributes, SERIAL_DEFAULT_PARITY)) {
+    logMessage(LOG_WARNING, "default serial parity not supported: %u", SERIAL_DEFAULT_PARITY);
+  }
+
+  if (serialPutFlowControl(attributes, SERIAL_DEFAULT_FLOW_CONTROL)) {
+    logMessage(LOG_WARNING, "default serial flow control not supported: 0X%04X", SERIAL_DEFAULT_FLOW_CONTROL);
+  }
+
+  {
+    int state = 0;
+
+    if (!serialPutModemState(attributes, state)) {
+      logMessage(LOG_WARNING, "default serial modem state not supported: %d", state);
+    }
+  }
+}
+
+int
+serialSetBaud (SerialDevice *serial, unsigned int baud) {
+  const SerialBaudEntry *entry = serialGetBaudEntry(baud);
+
+  if (entry) {
+    logMessage(LOG_CATEGORY(SERIAL_IO), "set baud: %u", baud);
+    if (serialPutSpeed(&serial->pendingAttributes, entry->speed)) return 1;
+    logUnsupportedBaud(baud);
+  }
+
+  return 0;
+}
+
+int
+serialValidateBaud (unsigned int *baud, const char *description, const char *word, const unsigned int *choices) {
+  if (!*word || isUnsignedInteger(baud, word)) {
+    const SerialBaudEntry *entry = serialGetBaudEntry(*baud);
+
+    if (entry) {
+      if (!choices) return 1;
+
+      while (*choices) {
+        if (*baud == *choices) return 1;
+        choices += 1;
+      }
+
+      logMessage(LOG_ERR, "unsupported %s: %u", description, *baud);
+    } else {
+      logMessage(LOG_ERR, "undefined %s: %u", description, *baud);
+    }
+  } else {
+    logMessage(LOG_ERR, "invalid %s: %u", description, *baud);
+  }
+
+  return 0;
+}
+
+int
+serialSetDataBits (SerialDevice *serial, unsigned int bits) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "set data bits: %u", bits);
+  if (serialPutDataBits(&serial->pendingAttributes, bits)) return 1;
+
+  logUnsupportedDataBits(bits);
+  return 0;
+}
+
+int
+serialSetStopBits (SerialDevice *serial, SerialStopBits bits) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "set stop bits: %u", bits);
+  if (serialPutStopBits(&serial->pendingAttributes, bits)) return 1;
+
+  logUnsupportedStopBits(bits);
+  return 0;
+}
+
+int
+serialSetParity (SerialDevice *serial, SerialParity parity) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "set parity: %u", parity);
+  if (serialPutParity(&serial->pendingAttributes, parity)) return 1;
+
+  logUnsupportedParity(parity);
+  return 0;
+}
+
+#ifdef HAVE_POSIX_THREADS
+static void
+serialFlowControlProc_inputCTS (SerialDevice *serial) {
+  int up = serialTestLineCTS(serial);
+
+  while (!serial->flowControlStop) {
+    serialSetLineRTS(serial, up);
+    serialWaitLineCTS(serial, (up = !up), 0);
+  }
+}
+
+THREAD_FUNCTION(serialFlowControlThread) {
+  SerialDevice *serial = argument;
+
+  serial->currentFlowControlProc(serial);
+  return NULL;
+}
+
+static int
+serialStartFlowControlThread (SerialDevice *serial) {
+  if (!serial->flowControlRunning && serial->currentFlowControlProc) {
+    pthread_t thread;
+    pthread_attr_t attributes;
+
+    pthread_attr_init(&attributes);
+    pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);
+
+    serial->flowControlStop = 0;
+    if (createThread("serial-input-cts", &thread, &attributes,
+                     serialFlowControlThread, serial)) {
+      logSystemError("pthread_create");
+      return 0;
+    }
+
+    serial->flowControlThread = thread;
+    serial->flowControlRunning = 1;
+  }
+
+  return 1;
+}
+
+static void
+serialStopFlowControlThread (SerialDevice *serial) {
+  if (serial->flowControlRunning) {
+    serial->flowControlStop = 1;
+    serial->flowControlRunning = 0;
+  }
+}
+#endif /* HAVE_POSIX_THREADS */
+
+int
+serialSetFlowControl (SerialDevice *serial, SerialFlowControl flow) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "set flow control: 0X%02X", flow);
+  flow = serialPutFlowControl(&serial->pendingAttributes, flow);
+
+#ifdef HAVE_POSIX_THREADS
+  if (flow & SERIAL_FLOW_INPUT_CTS) {
+    flow &= ~SERIAL_FLOW_INPUT_CTS;
+    serial->pendingFlowControlProc = serialFlowControlProc_inputCTS;
+  } else {
+    serial->pendingFlowControlProc = NULL;
+  }
+
+  {
+    int state = !!serial->pendingFlowControlProc;
+
+    if (!serialPutModemState(&serial->pendingAttributes, state)) {
+      logMessage(LOG_WARNING, "unsupported serial modem state: %d", state);
+    }
+  }
+#endif /* HAVE_POSIX_THREADS */
+
+  if (!flow) return 1;
+  logUnsupportedFlowControl(flow);
+  return 0;
+}
+
+int
+serialSetParameters (SerialDevice *serial, const SerialParameters *parameters) {
+  if (!serialSetBaud(serial, parameters->baud)) return 0;
+  if (!serialSetDataBits(serial, parameters->dataBits)) return 0;
+  if (!serialSetStopBits(serial, parameters->stopBits)) return 0;
+  if (!serialSetParity(serial, parameters->parity)) return 0;
+  if (!serialSetFlowControl(serial, parameters->flowControl)) return 0;
+  return 1;
+}
+
+unsigned int
+serialGetCharacterSize (const SerialParameters *parameters) {
+  unsigned int size = 1 /* start bit */ + parameters->dataBits;
+  size += (parameters->stopBits == SERIAL_STOP_1)? 1: 2;
+  if (parameters->parity != SERIAL_PARITY_NONE) size += 1;
+  return size;
+}
+
+unsigned int
+serialGetCharacterBits (SerialDevice *serial) {
+  const SerialAttributes *attributes = &serial->pendingAttributes;
+  return 1 /* start bit */
+       + serialGetDataBits(attributes)
+       + serialGetParityBits(attributes)
+       + serialGetStopBits(attributes)
+       ;
+}
+
+int
+serialDiscardInput (SerialDevice *serial) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "discard input");
+  return serialCancelInput(serial);
+}
+
+int
+serialDiscardOutput (SerialDevice *serial) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "discard output");
+  return serialCancelOutput(serial);
+}
+
+int
+serialFlushOutput (SerialDevice *serial) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "flush output");
+
+  if (serial->stream) {
+    if (fflush(serial->stream) == EOF) {
+      logSystemError("fflush");
+      return 0;
+    }
+  }
+  return 1;
+}
+
+int
+serialAwaitOutput (SerialDevice *serial) {
+  if (!serialFlushOutput(serial)) return 0;
+  if (!serialDrainOutput(serial)) return 0;
+  return 1;
+}
+
+static void
+serialCopyAttributes (SerialAttributes *destination, const SerialAttributes *source) {
+  memcpy(destination, source, sizeof(*destination));
+}
+
+static int
+serialCompareAttributes (const SerialAttributes *attributes, const SerialAttributes *reference) {
+  return memcmp(attributes, reference, sizeof(*attributes)) == 0;
+}
+
+static int
+serialReadAttributes (SerialDevice *serial) {
+  return serialGetAttributes(serial, &serial->currentAttributes);
+}
+
+static int
+serialWriteAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  if (!serialCompareAttributes(attributes, &serial->currentAttributes)) {
+    if (!serialAwaitOutput(serial)) return 0;
+    logBytes(LOG_CATEGORY(SERIAL_IO), "attributes", attributes, sizeof(*attributes));
+    if (!serialPutAttributes(serial, attributes)) return 0;
+    serialCopyAttributes(&serial->currentAttributes, attributes);
+  }
+
+  return 1;
+}
+
+static int
+serialFlushAttributes (SerialDevice *serial) {
+#ifdef HAVE_POSIX_THREADS
+  int restartFlowControlThread = serial->pendingFlowControlProc != serial->currentFlowControlProc;
+  if (restartFlowControlThread) serialStopFlowControlThread(serial);
+#endif /* HAVE_POSIX_THREADS */
+
+  if (!serialWriteAttributes(serial, &serial->pendingAttributes)) return 0;
+
+#ifdef HAVE_POSIX_THREADS
+  if (restartFlowControlThread) {
+    serial->currentFlowControlProc = serial->pendingFlowControlProc;
+    if (!serialStartFlowControlThread(serial)) return 0;
+  }
+#endif /* HAVE_POSIX_THREADS */
+
+  return 1;
+}
+
+int
+serialAwaitInput (SerialDevice *serial, int timeout) {
+  if (!serialFlushAttributes(serial)) return 0;
+  if (!serialPollInput(serial, timeout)) return 0;
+  return 1;
+}
+
+ssize_t
+serialReadData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  if (!serialFlushAttributes(serial)) return -1;
+
+  {
+    ssize_t result = serialGetData(serial, buffer, size, initialTimeout, subsequentTimeout);
+
+    if (result > 0) {
+      logBytes(LOG_CATEGORY(SERIAL_IO), "input", buffer, result);
+    }
+
+    return result;
+  }
+}
+
+int
+serialReadChunk (
+  SerialDevice *serial,
+  void *buffer, size_t *offset, size_t count,
+  int initialTimeout, int subsequentTimeout
+) {
+  unsigned char *byte = buffer;
+  const unsigned char *const first = byte;
+  const unsigned char *const end = first + count;
+  int timeout = *offset? subsequentTimeout: initialTimeout;
+
+  if (!serialFlushAttributes(serial)) return 0;
+  byte += *offset;
+
+  while (byte < end) {
+    ssize_t result = serialGetData(serial, byte, 1, timeout, subsequentTimeout);
+
+    if (!result) {
+      result = -1;
+      errno = EAGAIN;
+    }
+
+    if (result == -1) {
+      if (errno == EINTR) continue;
+      return 0;
+    }
+
+    byte += 1;
+    *offset += 1;
+    timeout = subsequentTimeout;
+  }
+
+  if (byte > first) {
+    logBytes(LOG_CATEGORY(SERIAL_IO), "input", first, (byte - first));
+  }
+
+  return 1;
+}
+
+ssize_t
+serialWriteData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  if (!serialFlushAttributes(serial)) return -1;
+  if (size > 0) logBytes(LOG_CATEGORY(SERIAL_IO), "output", data, size);
+  return serialPutData(serial, data, size);
+}
+
+static int
+serialReadLines (SerialDevice *serial, SerialLines *lines) {
+  int result = serialGetLines(serial);
+  if (result) *lines = serial->linesState;
+  return result;
+}
+
+static int
+serialWriteLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  return serialPutLines(serial, high, low);
+}
+
+static int
+serialSetLine (SerialDevice *serial, SerialLines line, int up) {
+  return serialWriteLines(serial, up?line:0, up?0:line);
+}
+
+int
+serialSetLineRTS (SerialDevice *serial, int up) {
+  return serialSetLine(serial, SERIAL_LINE_RTS, up);
+}
+
+int
+serialSetLineDTR (SerialDevice *serial, int up) {
+  return serialSetLine(serial, SERIAL_LINE_DTR, up);
+}
+
+static int
+serialTestLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  SerialLines lines;
+  if (serialReadLines(serial, &lines))
+    if (((lines & high) == high) && ((~lines & low) == low))
+      return 1;
+  return 0;
+}
+
+int
+serialTestLineCTS (SerialDevice *serial) {
+  return serialTestLines(serial, SERIAL_LINE_CTS, 0);
+}
+
+int
+serialTestLineDSR (SerialDevice *serial) {
+  return serialTestLines(serial, SERIAL_LINE_DSR, 0);
+}
+
+static int
+serialDefineWaitLines (SerialDevice *serial, SerialLines lines) {
+  if (lines != serial->waitLines) {
+    if (!serialRegisterWaitLines(serial, lines)) return 0;
+    serial->waitLines = lines;
+  }
+
+  return 1;
+}
+
+static int
+serialAwaitLineChange (SerialDevice *serial) {
+  return serialMonitorWaitLines(serial);
+}
+
+static int
+serialWaitLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  SerialLines lines = high | low;
+  int ok = 0;
+
+  if (serialDefineWaitLines(serial, lines)) {
+    while (!serialTestLines(serial, high, low))
+      if (!serialAwaitLineChange(serial))
+        goto done;
+    ok = 1;
+  }
+
+done:
+  serialDefineWaitLines(serial, 0);
+  return ok;
+}
+
+static int
+serialWaitFlank (SerialDevice *serial, SerialLines line, int up) {
+  int ok = 0;
+
+  if (serialDefineWaitLines(serial, line)) {
+    while (!serialTestLines(serial, up?0:line, up?line:0))
+      if (!serialAwaitLineChange(serial))
+        goto done;
+    if (serialAwaitLineChange(serial)) ok = 1;
+  }
+
+done:
+  serialDefineWaitLines(serial, 0);
+  return ok;
+}
+
+int
+serialWaitLine (SerialDevice *serial, SerialLines line, int up, int flank) {
+  return flank? serialWaitFlank(serial, line, up):
+                serialWaitLines(serial, up?line:0, up?0:line);
+}
+
+int
+serialWaitLineCTS (SerialDevice *serial, int up, int flank) {
+  return serialWaitLine(serial, SERIAL_LINE_CTS, up, flank);
+}
+
+int
+serialWaitLineDSR (SerialDevice *serial, int up, int flank) {
+  return serialWaitLine(serial, SERIAL_LINE_DSR, up, flank);
+}
+
+int
+serialPrepareDevice (SerialDevice *serial) {
+  if (serialReadAttributes(serial)) {
+    serialCopyAttributes(&serial->originalAttributes, &serial->currentAttributes);
+    serialInitializeAttributes(&serial->pendingAttributes);
+
+    serial->linesState = 0;
+    serial->waitLines = 0;
+
+#ifdef HAVE_POSIX_THREADS
+    serial->currentFlowControlProc = NULL;
+    serial->pendingFlowControlProc = NULL;
+    serial->flowControlRunning = 0;
+#endif /* HAVE_POSIX_THREADS */
+
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+serialParseBaud (unsigned int *baud, const char *string) {
+  if (isUnsignedInteger(baud, string)) return 1;
+
+  logMessage(LOG_WARNING, "invalid serial baud: %s", string);
+  return 0;
+}
+
+int
+serialParseDataBits (unsigned int *bits, const char *string) {
+  if (isUnsignedInteger(bits, string)) return 1;
+
+  logMessage(LOG_WARNING, "invalid serial data bit count: %s", string);
+  return 0;
+}
+
+int
+serialParseStopBits (unsigned int *bits, const char *string) {
+  if (isUnsignedInteger(bits, string)) return 1;
+
+  logMessage(LOG_WARNING, "invalid serial stop bit count: %s", string);
+  return 0;
+}
+
+int
+serialParseParity (SerialParity *parity, const char *string) {
+  if (isAbbreviation(string, "none")) {
+    *parity = SERIAL_PARITY_NONE;
+  } else if (isAbbreviation(string, "odd")) {
+    *parity = SERIAL_PARITY_ODD;
+  } else if (isAbbreviation(string, "even")) {
+    *parity = SERIAL_PARITY_EVEN;
+  } else if (isAbbreviation(string, "space")) {
+    *parity = SERIAL_PARITY_SPACE;
+  } else if (isAbbreviation(string, "mark")) {
+    *parity = SERIAL_PARITY_MARK;
+  } else {
+    logMessage(LOG_WARNING, "invalid serial parity: %s", string);
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+serialParseFlowControl (SerialFlowControl *flow, const char *string) {
+  if (isAbbreviation(string, "none")) {
+    *flow = SERIAL_FLOW_NONE;
+  } else if (isAbbreviation(string, "hardware")) {
+    *flow = SERIAL_FLOW_HARDWARE;
+  } else {
+    logMessage(LOG_WARNING, "invalid serial flow control: %s", string);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+serialConfigureBaud (SerialDevice *serial, const char *string) {
+  if (string && *string) {
+    unsigned int baud;
+
+    if (!serialParseBaud(&baud, string)) return 0;
+    if (!serialSetBaud(serial, baud)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+serialConfigureDataBits (SerialDevice *serial, const char *string) {
+  if (string && *string) {
+    unsigned int bits;
+
+    if (!serialParseDataBits(&bits, string)) return 0;
+    if (!serialSetDataBits(serial, bits)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+serialConfigureStopBits (SerialDevice *serial, const char *string) {
+  if (string && *string) {
+    unsigned int bits;
+
+    if (!serialParseStopBits(&bits, string)) return 0;
+    if (!serialSetStopBits(serial, bits)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+serialConfigureParity (SerialDevice *serial, const char *string) {
+  if (string && *string) {
+    SerialParity parity;
+
+    if (!serialParseParity(&parity, string)) return 0;
+    if (!serialSetParity(serial, parity)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+serialConfigureFlowControl (SerialDevice *serial, const char *string) {
+  if (string && *string) {
+    SerialFlowControl flow;
+
+    if (!serialParseFlowControl(&flow, string)) return 0;
+    if (!serialSetFlowControl(serial, flow)) return 0;
+  }
+
+  return 1;
+}
+
+typedef enum {
+  SERIAL_PARM_NAME,
+  SERIAL_PARM_BAUD,
+  SERIAL_PARM_DATA_BITS,
+  SERIAL_PARM_STOP_BITS,
+  SERIAL_PARM_DATA_PARITY,
+  SERIAL_PARM_FLOW_CONTROL
+} SerialDeviceParameter;
+
+static const char *const serialDeviceParameterNames[] = {
+  "name",
+  "baud",
+  "dataBits",
+  "stopBits",
+  "parity",
+  "flowControl",
+  NULL
+};
+
+static char **
+serialGetDeviceParameters (const char *identifier) {
+  if (!identifier) identifier = "";
+  return getDeviceParameters(serialDeviceParameterNames, identifier);
+}
+
+SerialDevice *
+serialOpenDevice (const char *identifier) {
+  char **parameters = serialGetDeviceParameters(identifier);
+
+  if (parameters) {
+    SerialDevice *serial;
+
+    if ((serial = malloc(sizeof(*serial)))) {
+      memset(serial, 0, sizeof(*serial));
+
+      {
+        const char *name = parameters[SERIAL_PARM_NAME];
+        if (!*name) name = SERIAL_FIRST_DEVICE;
+        serial->devicePath = getDevicePath(name);
+      }
+
+      if (serial->devicePath) {
+        serial->fileDescriptor = -1;
+        serial->stream = NULL;
+
+        if (serialConnectDevice(serial, serial->devicePath)) {
+          int ok = 1;
+
+          if (!serialConfigureBaud(serial, parameters[SERIAL_PARM_BAUD])) ok = 0;
+          if (!serialConfigureDataBits(serial, parameters[SERIAL_PARM_DATA_BITS])) ok = 0;
+          if (!serialConfigureStopBits(serial, parameters[SERIAL_PARM_STOP_BITS])) ok = 0;
+          if (!serialConfigureParity(serial, parameters[SERIAL_PARM_DATA_PARITY])) ok = 0;
+          if (!serialConfigureFlowControl(serial, parameters[SERIAL_PARM_FLOW_CONTROL])) ok = 0;
+
+          deallocateStrings(parameters);
+          if (ok) return serial;
+
+          serialCloseDevice(serial);
+          return NULL;
+        }
+
+        free(serial->devicePath);
+      }
+
+      free(serial);
+    } else {
+      logMallocError();
+    }
+
+    deallocateStrings(parameters);
+  }
+
+  return NULL;
+}
+
+void
+serialCloseDevice (SerialDevice *serial) {
+#ifdef HAVE_POSIX_THREADS
+  serialStopFlowControlThread(serial);
+#endif /* HAVE_POSIX_THREADS */
+
+  serialWriteAttributes(serial, &serial->originalAttributes);
+
+  if (serial->stream) {
+    fclose(serial->stream);
+  } else if (serial->fileDescriptor != -1) {
+    close(serial->fileDescriptor);
+  } else {
+    serialDisconnectDevice(serial);
+  }
+
+  free(serial->devicePath);
+  free(serial);
+}
+
+const char *
+serialMakeDeviceIdentifier (SerialDevice *serial, char *buffer, size_t size) {
+  STR_BEGIN(buffer, size);
+
+  STR_PRINTF(
+    "%s%c%s%c%s",
+    SERIAL_DEVICE_QUALIFIER,
+    PARAMETER_QUALIFIER_CHARACTER,
+    serialDeviceParameterNames[SERIAL_PARM_NAME],
+    PARAMETER_ASSIGNMENT_CHARACTER,
+    serialGetDevicePath(serial)
+  );
+
+  STR_END;
+  return buffer;
+}
+
+int
+serialRestartDevice (SerialDevice *serial, unsigned int baud) {
+  SerialLines highLines = 0;
+  SerialLines lowLines = 0;
+  int usingB0;
+
+#ifdef HAVE_POSIX_THREADS
+  SerialFlowControlProc *flowControlProc = serial->pendingFlowControlProc;
+#endif /* HAVE_POSIX_THREADS */
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "restarting");
+
+  if (serial->stream) {
+#if defined(GRUB_RUNTIME)
+#else /* clearerr() */
+    clearerr(serial->stream);
+#endif /* clear error on stdio stream */
+  }
+
+  serialClearError(serial);
+
+  if (!serialDiscardOutput(serial)) return 0;
+
+#ifdef HAVE_POSIX_THREADS
+  serial->pendingFlowControlProc = NULL;
+#endif /* HAVE_POSIX_THREADS */
+
+#ifdef B0
+  if (!serialPutSpeed(&serial->pendingAttributes, B0)) return 0;
+  usingB0 = 1;
+#else /* B0 */
+  usingB0 = 0;
+#endif /* B0 */
+
+  if (!serialFlushAttributes(serial)) {
+    if (!usingB0) return 0;
+    if (!serialSetBaud(serial, baud)) return 0;
+    if (!serialFlushAttributes(serial)) return 0;
+    usingB0 = 0;
+  }
+
+  if (!usingB0) {
+    SerialLines lines;
+    if (!serialReadLines(serial, &lines)) return 0;
+
+    {
+      static const SerialLines linesTable[] = {SERIAL_LINE_DTR, SERIAL_LINE_RTS, 0};
+      const SerialLines *line = linesTable;
+
+      while (*line) {
+        *((lines & *line)? &highLines: &lowLines) |= *line;
+        line += 1;
+      }
+    }
+
+    if (highLines)
+      if (!serialWriteLines(serial, 0, highLines|lowLines))
+        return 0;
+  }
+
+  asyncWait(SERIAL_DEVICE_RESTART_DELAY);
+  if (!serialDiscardInput(serial)) return 0;
+
+  if (!usingB0)
+    if (!serialWriteLines(serial, highLines, lowLines))
+      return 0;
+
+#ifdef HAVE_POSIX_THREADS
+  serial->pendingFlowControlProc = flowControlProc;
+#endif /* HAVE_POSIX_THREADS */
+
+  if (!serialSetBaud(serial, baud)) return 0;
+  if (!serialFlushAttributes(serial)) return 0;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "restarted");
+  return 1;
+}
+
+FILE *
+serialGetStream (SerialDevice *serial) {
+  if (!serial->stream) {
+    if (!serialEnsureFileDescriptor(serial)) return NULL;
+
+#if defined(GRUB_RUNTIME)
+    errno = ENOSYS;
+#else /* fdopen() */
+    serial->stream = fdopen(serial->fileDescriptor, "ab+");
+#endif /* create stdio stream */
+
+    if (!serial->stream) {
+      logSystemError("fdopen");
+      return NULL;
+    }
+  }
+
+  return serial->stream;
+}
+
+int
+isSerialDeviceIdentifier (const char **identifier) {
+#ifdef ALLOW_DOS_DEVICE_NAMES
+  if (isDosDevice(*identifier, "COM")) return 1;
+#endif /* ALLOW_DOS_DEVICE_NAMES */
+
+  if (hasQualifier(identifier, SERIAL_DEVICE_QUALIFIER)) return 1;
+  if (hasNoQualifier(*identifier)) return 1;
+  return 0;
+}
diff --git a/Programs/serial_grub.c b/Programs/serial_grub.c
new file mode 100644
index 0000000..6d43eb0
--- /dev/null
+++ b/Programs/serial_grub.c
@@ -0,0 +1,281 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "log.h"
+#include "timing.h"
+
+#include "serial_grub.h"
+#include "serial_internal.h"
+
+#define SERIAL_NO_BYTE -1
+
+#define SERIAL_BAUD_ENTRY(baud) {(baud), (baud)}
+
+BEGIN_SERIAL_BAUD_TABLE
+  SERIAL_BAUD_ENTRY(   110),
+  SERIAL_BAUD_ENTRY(   150),
+  SERIAL_BAUD_ENTRY(   300),
+  SERIAL_BAUD_ENTRY(   600),
+  SERIAL_BAUD_ENTRY(  1200),
+  SERIAL_BAUD_ENTRY(  2400),
+  SERIAL_BAUD_ENTRY(  4800),
+  SERIAL_BAUD_ENTRY(  9600),
+  SERIAL_BAUD_ENTRY( 19200),
+  SERIAL_BAUD_ENTRY( 38400),
+  SERIAL_BAUD_ENTRY( 57600),
+  SERIAL_BAUD_ENTRY(115200),
+END_SERIAL_BAUD_TABLE
+
+void
+serialPutInitialAttributes (SerialAttributes *attributes) {
+}
+
+int
+serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed) {
+  attributes->speed = speed;
+  return 1;
+}
+
+int
+serialPutDataBits (SerialAttributes *attributes, unsigned int bits) {
+  if ((bits < 5) || (bits > 8)) return 0;
+  attributes->word_len = bits;
+  return 1;
+}
+
+int
+serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits) {
+  switch (bits) {
+    case SERIAL_STOP_1:
+      attributes->stop_bits = GRUB_SERIAL_STOP_BITS_1;
+      break;
+
+    case SERIAL_STOP_2:
+      attributes->stop_bits = GRUB_SERIAL_STOP_BITS_2;
+      break;
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+int
+serialPutParity (SerialAttributes *attributes, SerialParity parity) {
+  switch (parity) {
+    case SERIAL_PARITY_NONE:
+      attributes->parity = GRUB_SERIAL_PARITY_NONE;
+      break;
+
+    case SERIAL_PARITY_ODD:
+      attributes->parity = GRUB_SERIAL_PARITY_ODD;
+      break;
+
+    case SERIAL_PARITY_EVEN:
+      attributes->parity = GRUB_SERIAL_PARITY_EVEN;
+      break;
+
+    default: 
+      return 0;
+  }
+
+  return 1;
+}
+
+SerialFlowControl
+serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow) {
+  return flow;
+}
+
+int
+serialPutModemState (SerialAttributes *attributes, int enabled) {
+  return 0;
+}
+
+unsigned int
+serialGetDataBits (const SerialAttributes *attributes) {
+  return attributes->word_len;
+}
+
+unsigned int
+serialGetStopBits (const SerialAttributes *attributes) {
+  switch (attributes->stop_bits) {
+    case GRUB_SERIAL_STOP_BITS_1: return 1;
+    case GRUB_SERIAL_STOP_BITS_2: return 2;
+    default:                      return 0;
+  }
+}
+
+unsigned int
+serialGetParityBits (const SerialAttributes *attributes) {
+  return (attributes->parity == GRUB_SERIAL_PARITY_NONE)? 0: 1;
+}
+
+int
+serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes) {
+  *attributes = serial->package.port->config;
+  return 1;
+}
+
+int
+serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  grub_err_t result = serial->package.port->driver->configure(serial->package.port, attributes);
+  if (result == GRUB_ERR_NONE) return 1;
+  return 0;
+}
+
+int
+serialCancelInput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialCancelOutput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+serialPollInput (SerialDevice *serial, int timeout) {
+  if (serial->package.byte == SERIAL_NO_BYTE) {
+    TimePeriod period;
+    startTimePeriod(&period, timeout);
+
+    while ((serial->package.byte = serial->package.port->driver->fetch(serial->package.port)) == SERIAL_NO_BYTE) {
+      if (afterTimePeriod(&period, NULL)) {
+        errno = EAGAIN;
+        return 0;
+      }
+
+      approximateDelay(1);
+    }
+  }
+
+  return 1;
+}
+
+int
+serialDrainOutput (SerialDevice *serial) {
+  return 1;
+}
+
+ssize_t
+serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  unsigned char *byte = buffer;
+  const unsigned char *const first = byte;
+  const unsigned char *const end = first + size;
+  int timeout = initialTimeout;
+
+  while (byte < end) {
+    if (!serialPollInput(serial, timeout)) break;
+    *byte++ = serial->package.byte;
+    serial->package.byte = SERIAL_NO_BYTE;
+    timeout = subsequentTimeout;
+  }
+
+  {
+    size_t count = byte - first;
+    if (count) return count;
+  }
+
+  errno = EAGAIN;
+  return -1;
+}
+
+ssize_t
+serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  const unsigned char *byte = data;
+  const unsigned char *end = byte + size;
+
+  while (byte < end) {
+    serial->package.port->driver->put(serial->package.port, *byte++);
+  }
+
+  return size;
+}
+
+int
+serialGetLines (SerialDevice *serial) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialRegisterWaitLines (SerialDevice *serial, SerialLines lines) {
+  return 1;
+}
+
+int
+serialMonitorWaitLines (SerialDevice *serial) {
+  return 0;
+}
+
+int
+serialConnectDevice (SerialDevice *serial, const char *device) {
+  if ((serial->package.port = grub_serial_find(device))) {
+    serial->package.byte = SERIAL_NO_BYTE;
+
+    if (serialPrepareDevice(serial)) {
+      logMessage(LOG_CATEGORY(SERIAL_IO), "device opened: %s",
+                 device);
+      return 1;
+    }
+  } else {
+    logMessage(LOG_ERR, "cannot find serial device: %s", device);
+    errno = ENOENT;
+  }
+
+  return 0;
+}
+
+void
+serialDisconnectDevice (SerialDevice *serial) {
+  serial->package.port = NULL;
+}
+
+int
+serialEnsureFileDescriptor (SerialDevice *serial) {
+  return 1;
+}
+
+void
+serialClearError (SerialDevice *serial) {
+}
+
diff --git a/Programs/serial_grub.h b/Programs/serial_grub.h
new file mode 100644
index 0000000..bd193f7
--- /dev/null
+++ b/Programs/serial_grub.h
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_GRUB
+#define BRLTTY_INCLUDED_SERIAL_GRUB
+
+#include <grub/serial.h>
+
+#include "serial_uart.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct grub_serial_config SerialAttributes;
+typedef unsigned SerialSpeed;
+
+typedef unsigned char SerialLines;
+#define SERIAL_LINE_DTR UART_FLAG_MCR_DTR
+#define SERIAL_LINE_RTS UART_FLAG_MCR_RTS
+#define SERIAL_LINE_CTS UART_FLAG_MSR_CTS
+#define SERIAL_LINE_DSR UART_FLAG_MSR_DSR
+#define SERIAL_LINE_RNG UART_FLAG_MSR_RNG
+#define SERIAL_LINE_CAR UART_FLAG_MSR_CAR
+
+typedef struct {
+  struct grub_serial_port *port;
+  int byte;
+} SerialPackageFields;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_GRUB */
diff --git a/Programs/serial_internal.h b/Programs/serial_internal.h
new file mode 100644
index 0000000..402ce2c
--- /dev/null
+++ b/Programs/serial_internal.h
@@ -0,0 +1,118 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_INTERNAL
+#define BRLTTY_INCLUDED_SERIAL_INTERNAL
+
+#include "prologue.h"
+
+#include <stdio.h>
+
+#include "io_serial.h"
+#include "thread.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef void SerialFlowControlProc (SerialDevice *serial);
+
+struct SerialDeviceStruct {
+  char *devicePath;
+  int fileDescriptor;
+  FILE *stream;
+
+  SerialAttributes originalAttributes;
+  SerialAttributes currentAttributes;
+  SerialAttributes pendingAttributes;
+
+  SerialLines linesState;
+  SerialLines waitLines;
+
+#ifdef HAVE_POSIX_THREADS
+  SerialFlowControlProc *currentFlowControlProc;
+  SerialFlowControlProc *pendingFlowControlProc;
+  pthread_t flowControlThread;
+  unsigned flowControlRunning:1;
+  unsigned flowControlStop:1;
+#endif /* HAVE_POSIX_THREADS */
+
+  SerialPackageFields package;
+};
+
+typedef struct {
+  unsigned int baud;
+  SerialSpeed speed;
+} SerialBaudEntry;
+
+extern const SerialBaudEntry *serialGetBaudEntry (unsigned int baud);
+#define SERIAL_BAUD_TABLE_DECLARATION  const SerialBaudEntry serialBaudTable[]
+extern SERIAL_BAUD_TABLE_DECLARATION;
+#define BEGIN_SERIAL_BAUD_TABLE SERIAL_BAUD_TABLE_DECLARATION = {
+#define END_SERIAL_BAUD_TABLE {0} };
+
+extern void serialPutInitialAttributes (SerialAttributes *attributes);
+extern int serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed);
+extern int serialPutDataBits (SerialAttributes *attributes, unsigned int bits);
+extern int serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits);
+extern int serialPutParity (SerialAttributes *attributes, SerialParity parity);
+extern SerialFlowControl serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow);
+extern int serialPutModemState (SerialAttributes *attributes, int enabled);
+
+extern unsigned int serialGetDataBits (const SerialAttributes *attributes);
+extern unsigned int serialGetStopBits (const SerialAttributes *attributes);
+extern unsigned int serialGetParityBits (const SerialAttributes *attributes);
+
+extern int serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes);
+extern int serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes);
+
+extern int serialCancelInput (SerialDevice *serial);
+extern int serialCancelOutput (SerialDevice *serial);
+
+extern int serialPollInput (SerialDevice *serial, int timeout);
+extern int serialDrainOutput (SerialDevice *serial);
+
+extern ssize_t serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+);
+
+extern ssize_t serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+);
+
+extern int serialGetLines (SerialDevice *serial);
+extern int serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low);
+
+extern int serialRegisterWaitLines (SerialDevice *serial, SerialLines lines);
+extern int serialMonitorWaitLines (SerialDevice *serial);
+
+extern int serialConnectDevice (SerialDevice *serial, const char *device);
+extern int serialPrepareDevice (SerialDevice *serial);
+
+extern void serialDisconnectDevice (SerialDevice *serial);
+extern int serialEnsureFileDescriptor (SerialDevice *serial);
+extern void serialClearError (SerialDevice *serial);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_INTERNAL */
diff --git a/Programs/serial_msdos.c b/Programs/serial_msdos.c
new file mode 100644
index 0000000..d2a1a7a
--- /dev/null
+++ b/Programs/serial_msdos.c
@@ -0,0 +1,411 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "async_wait.h"
+#include "timing.h"
+#include "io_misc.h"
+#include "ports.h"
+
+#include "serial_msdos.h"
+#include "serial_internal.h"
+
+#include <dos.h>
+#include <dpmi.h>
+#include <bios.h>
+#include <go32.h>
+#include <sys/farptr.h>
+
+#define SERIAL_DIVISOR_BASE 115200
+#define SERIAL_DIVISOR(baud) (SERIAL_DIVISOR_BASE / (baud))
+#define SERIAL_SPEED(baud) {.divisor=SERIAL_DIVISOR(baud), .bps=SERIAL_BIOS_BAUD_##baud}
+#define SERIAL_BAUD_ENTRY(baud) {baud, .speed=SERIAL_SPEED(baud)}
+
+BEGIN_SERIAL_BAUD_TABLE
+  SERIAL_BAUD_ENTRY(110),
+  SERIAL_BAUD_ENTRY(150),
+  SERIAL_BAUD_ENTRY(300),
+  SERIAL_BAUD_ENTRY(600),
+  SERIAL_BAUD_ENTRY(1200),
+  SERIAL_BAUD_ENTRY(2400),
+  SERIAL_BAUD_ENTRY(4800),
+  SERIAL_BAUD_ENTRY(9600),
+  SERIAL_BAUD_ENTRY(19200),
+  SERIAL_BAUD_ENTRY(38400),
+  SERIAL_BAUD_ENTRY(57600),
+  SERIAL_BAUD_ENTRY(115200),
+END_SERIAL_BAUD_TABLE
+
+static inline int
+serialGetPort (SerialDevice *serial) {
+  return serial->package.deviceIndex;
+}
+
+static unsigned short
+serialPortBase (SerialDevice *serial) {
+  return _farpeekw(_dos_ds, (0X0400 + (2 * serialGetPort(serial))));
+}
+
+static unsigned char
+serialReadPort (SerialDevice *serial, unsigned char port) {
+  return readPort1(serialPortBase(serial)+port);
+}
+
+static void
+serialWritePort (SerialDevice *serial, unsigned char port, unsigned char value) {
+  writePort1(serialPortBase(serial)+port, value);
+}
+
+static unsigned int
+serialBiosCommand (SerialDevice *serial, int command, unsigned char data) {
+  return _bios_serialcom(command, serialGetPort(serial), data);
+}
+
+static int
+serialTestInput (SerialDevice *serial) {
+  return !!(serialBiosCommand(serial, _COM_STATUS, 0) & SERIAL_BIOS_STATUS_DATA_READY);
+}
+
+void
+serialPutInitialAttributes (SerialAttributes *attributes) {
+  attributes->speed = serialGetBaudEntry(9600)->speed;
+  attributes->bios.fields.bps = attributes->speed.bps;
+  attributes->bios.fields.dataBits = SERIAL_BIOS_DATA_8;
+  attributes->bios.fields.stopBits = SERIAL_BIOS_STOP_1;
+  attributes->bios.fields.parity = SERIAL_BIOS_PARITY_NONE;
+}
+
+int
+serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "put speed: bps=%u divisor=%u",
+             speed.bps, speed.divisor);
+
+  attributes->speed = speed;
+  attributes->bios.fields.bps = attributes->speed.bps;
+  return 1;
+}
+
+int
+serialPutDataBits (SerialAttributes *attributes, unsigned int bits) {
+  if (bits == 8) {
+    attributes->bios.fields.dataBits = SERIAL_BIOS_DATA_8;
+  } else if (bits == 7) {
+    attributes->bios.fields.dataBits = SERIAL_BIOS_DATA_7;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits) {
+  if (bits == SERIAL_STOP_1) {
+    attributes->bios.fields.stopBits = SERIAL_BIOS_STOP_1;
+  } else if (bits == SERIAL_STOP_2) {
+    attributes->bios.fields.stopBits = SERIAL_BIOS_STOP_2;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+serialPutParity (SerialAttributes *attributes, SerialParity parity) {
+  switch (parity) {
+    case SERIAL_PARITY_NONE:
+      attributes->bios.fields.parity = SERIAL_BIOS_PARITY_NONE;
+      break;
+
+    case SERIAL_PARITY_ODD:
+      attributes->bios.fields.parity = SERIAL_BIOS_PARITY_ODD;
+      break;
+
+    case SERIAL_PARITY_EVEN:
+      attributes->bios.fields.parity = SERIAL_BIOS_PARITY_EVEN;
+      break;
+
+    default:
+      return 0;
+  }
+
+  return 1;
+}
+
+SerialFlowControl
+serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow) {
+  return flow;
+}
+
+int
+serialPutModemState (SerialAttributes *attributes, int enabled) {
+  return !enabled;
+}
+
+unsigned int
+serialGetDataBits (const SerialAttributes *attributes) {
+  switch (attributes->bios.fields.dataBits) {
+    default:
+    case SERIAL_BIOS_DATA_8: return 8;
+    case SERIAL_BIOS_DATA_7: return 7;
+  }
+}
+
+unsigned int
+serialGetStopBits (const SerialAttributes *attributes) {
+  switch (attributes->bios.fields.stopBits) {
+    default:
+    case SERIAL_BIOS_STOP_1: return 1;
+    case SERIAL_BIOS_STOP_2: return 2;
+  }
+}
+
+unsigned int
+serialGetParityBits (const SerialAttributes *attributes) {
+  return (attributes->bios.fields.parity == SERIAL_BIOS_PARITY_NONE)? 0: 1;
+}
+
+int
+serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes) {
+  unsigned char lcr;
+  int divisor;
+
+  {
+    int wasEnabled = disable();
+
+    lcr = serialReadPort(serial, UART_PORT_LCR);
+    serialWritePort(serial, UART_PORT_LCR, (lcr | UART_FLAG_LCR_DLAB));
+
+    divisor = (serialReadPort(serial, UART_PORT_DLH) << 8) |
+               serialReadPort(serial, UART_PORT_DLL);
+    serialWritePort(serial, UART_PORT_LCR, lcr);
+
+    if (wasEnabled) enable();
+  }
+
+  attributes->bios.byte = lcr;
+
+  {
+    const SerialBaudEntry *baud = serialGetBaudEntry(SERIAL_DIVISOR_BASE/divisor);
+
+    if (baud) {
+      attributes->speed = baud->speed;
+    } else {
+      logMessage(LOG_WARNING, "unsupported serial divisor: %d", divisor);
+      memset(&attributes->speed, 0, sizeof(attributes->speed));
+    }
+  }
+
+  attributes->bios.fields.bps = attributes->speed.bps;
+  return 1;
+}
+
+int
+serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  if (attributes->speed.bps < (0X1 << 3)) {
+    unsigned char byte = attributes->bios.byte;
+
+    logMessage(LOG_CATEGORY(SERIAL_IO), "put attributes: port=%d byte=0X%02X",
+               serialGetPort(serial), byte);
+    serialBiosCommand(serial, _COM_INIT, byte);
+  } else {
+    SerialBiosConfiguration lcr = attributes->bios;
+
+    lcr.fields.bps = 0;
+    logMessage(LOG_CATEGORY(SERIAL_IO), "put attributes: port=%d lcr=0X%02X divisor=%u",
+               serialGetPort(serial), lcr.byte, attributes->speed.divisor);
+
+    {
+      int wasEnabled = disable();
+
+      serialWritePort(serial, UART_PORT_LCR, (lcr.byte | UART_FLAG_LCR_DLAB));
+      serialWritePort(serial, UART_PORT_DLL, (attributes->speed.divisor & 0XFF));
+      serialWritePort(serial, UART_PORT_DLH, (attributes->speed.divisor >> 8));
+      serialWritePort(serial, UART_PORT_LCR, lcr.byte);
+
+      if (wasEnabled) enable();
+    }
+  }
+
+  return 1;
+}
+
+int
+serialCancelInput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialCancelOutput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+serialPollInput (SerialDevice *serial, int timeout) {
+  TimePeriod period;
+
+  if (timeout) startTimePeriod(&period, timeout);
+
+  while (1) {
+    if (serialTestInput(serial)) return 1;
+    if (!timeout) break;
+    if (afterTimePeriod(&period, NULL)) break;
+    asyncWait(1);
+  }
+
+  errno = EAGAIN;
+  return 0;
+}
+
+int
+serialDrainOutput (SerialDevice *serial) {
+  return 1;
+}
+
+ssize_t
+serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  unsigned char *const start = buffer;
+  unsigned char *const end = start + size;
+  unsigned char *byte = start;
+  int timeout = initialTimeout;
+
+  while (byte < end) {
+    if (!serialPollInput(serial, timeout)) break;
+    timeout = subsequentTimeout;
+
+    {
+      int status = serialBiosCommand(serial, _COM_RECEIVE, 0);
+
+      *byte++ = status & 0XFF;
+    }
+  }
+
+  return byte - start;
+}
+
+ssize_t
+serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  return writeFile(serial->fileDescriptor, data, size);
+}
+
+int
+serialGetLines (SerialDevice *serial) {
+  serial->linesState = serialReadPort(serial, UART_PORT_MSR) & 0XF0;
+  return 1;
+}
+
+int
+serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  int wasEnabled = disable();
+  unsigned char oldMCR = serialReadPort(serial, UART_PORT_MCR);
+
+  serialWritePort(serial, UART_PORT_MCR,
+                  (oldMCR | high) & ~low);
+  if (wasEnabled) enable();
+  return 1;
+}
+
+int
+serialRegisterWaitLines (SerialDevice *serial, SerialLines lines) {
+  return 1;
+}
+
+int
+serialMonitorWaitLines (SerialDevice *serial) {
+  return 0;
+}
+
+int
+serialConnectDevice (SerialDevice *serial, const char *device) {
+  if ((serial->fileDescriptor = open(device, O_RDWR|O_NOCTTY|O_NONBLOCK)) != -1) {
+    serial->package.deviceIndex = -1;
+
+    {
+      char *truePath;
+
+      if ((truePath = _truename(device, NULL))) {
+        char *com;
+
+        {
+          char *c = truePath;
+
+          while (*c) {
+            *c = toupper(*c);
+            c += 1;
+          }
+        }
+
+        if ((com = strstr(truePath, "COM"))) {
+          serial->package.deviceIndex = atoi(com+3) - 1;
+        }
+
+        free(truePath);
+      }
+    }
+
+    if (serial->package.deviceIndex >= 0) {
+      if (serialPrepareDevice(serial)) {
+        logMessage(LOG_CATEGORY(SERIAL_IO), "device opened: %s: fd=%d",
+                   device, serial->fileDescriptor);
+        return 1;
+      }
+    } else {
+      logMessage(LOG_ERR, "could not determine serial device number: %s", device);
+    }
+
+    close(serial->fileDescriptor);
+  } else {
+    logMessage(LOG_ERR, "cannot open serial device: %s: %s", device, strerror(errno));
+  }
+
+  return 0;
+}
+
+void
+serialDisconnectDevice (SerialDevice *serial) {
+}
+
+int
+serialEnsureFileDescriptor (SerialDevice *serial) {
+  return 1;
+}
+
+void
+serialClearError (SerialDevice *serial) {
+}
+
diff --git a/Programs/serial_msdos.h b/Programs/serial_msdos.h
new file mode 100644
index 0000000..941b5da
--- /dev/null
+++ b/Programs/serial_msdos.h
@@ -0,0 +1,124 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_MSDOS
+#define BRLTTY_INCLUDED_SERIAL_MSDOS
+
+#include "serial_uart.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  unsigned short divisor;
+  unsigned short bps;
+} SerialSpeed;
+
+typedef union {
+  unsigned char byte;
+
+  struct {
+    unsigned dataBits:2;
+    unsigned stopBits:1;
+    unsigned parity:2;
+    unsigned bps:3;
+  } fields;
+} SerialBiosConfiguration;
+
+typedef enum {
+  SERIAL_BIOS_DATA_7 = 2,
+  SERIAL_BIOS_DATA_8 = 3
+} serialBiosDataBits;
+
+typedef enum {
+  SERIAL_BIOS_STOP_1 = 0,
+  SERIAL_BIOS_STOP_2 = 1
+} SerialBiosStopBits;
+
+typedef enum {
+  SERIAL_BIOS_PARITY_NONE = 0,
+  SERIAL_BIOS_PARITY_ODD  = 1,
+  SERIAL_BIOS_PARITY_EVEN = 3
+} SerialBiosParity;
+
+typedef enum {
+  SERIAL_BIOS_BAUD_110 = 0,
+  SERIAL_BIOS_BAUD_150,
+  SERIAL_BIOS_BAUD_300,
+  SERIAL_BIOS_BAUD_600,
+  SERIAL_BIOS_BAUD_1200,
+  SERIAL_BIOS_BAUD_2400,
+  SERIAL_BIOS_BAUD_4800,
+  SERIAL_BIOS_BAUD_9600,
+
+  /* Do not reorder, add to, or delete from the preceding set of definitions
+   * because their values are significant within the operating system.
+   * The set defined below may be extended as needed.
+   */
+
+  SERIAL_BIOS_BAUD_19200,
+  SERIAL_BIOS_BAUD_38400,
+  SERIAL_BIOS_BAUD_57600,
+  SERIAL_BIOS_BAUD_115200
+} SerialBiosBaud;
+
+typedef enum {
+  SERIAL_BIOS_STATUS_CTS_CHANGE    = 0X0001,
+  SERIAL_BIOS_STATUS_DSR_CHANGE    = 0X0002,
+  SERIAL_BIOS_STATUS_RNG_CHANGE    = 0X0004,
+  SERIAL_BIOS_STATUS_CAR_CHANGE    = 0X0008,
+
+  SERIAL_BIOS_STATUS_CTS_PRESENT   = 0X0010,
+  SERIAL_BIOS_STATUS_DSR_PRESENT   = 0X0020,
+  SERIAL_BIOS_STATUS_RNG_PRESENT   = 0X0040,
+  SERIAL_BIOS_STATUS_CAR_PRESENT   = 0X0080,
+
+  SERIAL_BIOS_STATUS_DATA_READY    = 0X0100,
+  SERIAL_BIOS_STATUS_OVERRUN_ERROR = 0X0200,
+  SERIAL_BIOS_STATUS_PARITY_ERROR  = 0X0400,
+  SERIAL_BIOS_STATUS_FRAMING_ERROR = 0X0800,
+
+  SERIAL_BIOS_STATUS_BRK_DETECT    = 0X1000,
+  SERIAL_BIOS_STATUS_THR_EMPTY     = 0X2000,
+  SERIAL_BIOS_STATUS_TSR_EMPTY     = 0X4000,
+  SERIAL_BIOS_STATUS_TIMEOUT       = 0X8000
+} SerialBiosStatus;
+
+typedef struct {
+  SerialBiosConfiguration bios;
+  SerialSpeed speed;
+} SerialAttributes;
+
+typedef unsigned char SerialLines;
+#define SERIAL_LINE_DTR UART_FLAG_MCR_DTR
+#define SERIAL_LINE_RTS UART_FLAG_MCR_RTS
+#define SERIAL_LINE_CTS UART_FLAG_MSR_CTS
+#define SERIAL_LINE_DSR UART_FLAG_MSR_DSR
+#define SERIAL_LINE_RNG UART_FLAG_MSR_RNG
+#define SERIAL_LINE_CAR UART_FLAG_MSR_CAR
+
+typedef struct {
+  int deviceIndex;
+} SerialPackageFields;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_MSDOS */
diff --git a/Programs/serial_none.c b/Programs/serial_none.c
new file mode 100644
index 0000000..ab9fbc1
--- /dev/null
+++ b/Programs/serial_none.c
@@ -0,0 +1,175 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "serial_none.h"
+#include "serial_internal.h"
+
+BEGIN_SERIAL_BAUD_TABLE
+END_SERIAL_BAUD_TABLE
+
+void
+serialPutInitialAttributes (SerialAttributes *attributes) {
+}
+
+int
+serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed) {
+  return 0;
+}
+
+int
+serialPutDataBits (SerialAttributes *attributes, unsigned int bits) {
+  return 0;
+}
+
+int
+serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits) {
+  return 0;
+}
+
+int
+serialPutParity (SerialAttributes *attributes, SerialParity parity) {
+  return 0;
+}
+
+SerialFlowControl
+serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow) {
+  return flow;
+}
+
+int
+serialPutModemState (SerialAttributes *attributes, int enabled) {
+  return 0;
+}
+
+unsigned int
+serialGetDataBits (const SerialAttributes *attributes) {
+  return 8;
+}
+
+unsigned int
+serialGetStopBits (const SerialAttributes *attributes) {
+  return 1;
+}
+
+unsigned int
+serialGetParityBits (const SerialAttributes *attributes) {
+  return 0;
+}
+
+int
+serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialCancelInput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialCancelOutput (SerialDevice *serial) {
+  return 1;
+}
+
+int
+serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+serialPollInput (SerialDevice *serial, int timeout) {
+  errno = EAGAIN;
+  return 0;
+}
+
+int
+serialDrainOutput (SerialDevice *serial) {
+  return 1;
+}
+
+ssize_t
+serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  errno = ENOSYS;
+  return -1;
+}
+
+ssize_t
+serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  errno = ENOSYS;
+  return -1;
+}
+
+int
+serialGetLines (SerialDevice *serial) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  errno = ENOSYS;
+  return 0;
+}
+
+int
+serialRegisterWaitLines (SerialDevice *serial, SerialLines lines) {
+  return 1;
+}
+
+int
+serialMonitorWaitLines (SerialDevice *serial) {
+  return 0;
+}
+
+int
+serialConnectDevice (SerialDevice *serial, const char *device) {
+  errno = ENOENT;
+  return 0;
+}
+
+void
+serialDisconnectDevice (SerialDevice *serial) {
+}
+
+int
+serialEnsureFileDescriptor (SerialDevice *serial) {
+  return 1;
+}
+
+void
+serialClearError (SerialDevice *serial) {
+}
+
diff --git a/Programs/serial_none.h b/Programs/serial_none.h
new file mode 100644
index 0000000..26f5e72
--- /dev/null
+++ b/Programs/serial_none.h
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_NONE
+#define BRLTTY_INCLUDED_SERIAL_NONE
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef unsigned char SerialSpeed;
+typedef unsigned char SerialAttributes;
+
+typedef unsigned char SerialLines;
+#define SERIAL_LINE_DTR 0X01
+#define SERIAL_LINE_RTS 0X02
+#define SERIAL_LINE_CTS 0X10
+#define SERIAL_LINE_DSR 0X20
+#define SERIAL_LINE_RNG 0X40
+#define SERIAL_LINE_CAR 0X80
+
+typedef struct {
+  char dummy;
+} SerialPackageFields;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_NONE */
diff --git a/Programs/serial_termios.c b/Programs/serial_termios.c
new file mode 100644
index 0000000..3743990
--- /dev/null
+++ b/Programs/serial_termios.c
@@ -0,0 +1,551 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "io_misc.h"
+#include "async_io.h"
+
+#include "serial_termios.h"
+#include "serial_internal.h"
+
+BEGIN_SERIAL_BAUD_TABLE
+#ifdef B50
+  {50, B50},
+#endif /* B50 */
+
+#ifdef B75
+  {75, B75},
+#endif /* B75 */
+
+#ifdef B110
+  {110, B110},
+#endif /* B110 */
+
+#ifdef B134
+  {134, B134},
+#endif /* B134 */
+
+#ifdef B150
+  {150, B150},
+#endif /* B150 */
+
+#ifdef B200
+  {200, B200},
+#endif /* B200 */
+
+#ifdef B300
+  {300, B300},
+#endif /* B300 */
+
+#ifdef B600
+  {600, B600},
+#endif /* B600 */
+
+#ifdef B1200
+  {1200, B1200},
+#endif /* B1200 */
+
+#ifdef B1800
+  {1800, B1800},
+#endif /* B1800 */
+
+#ifdef B2400
+  {2400, B2400},
+#endif /* B2400 */
+
+#ifdef B4800
+  {4800, B4800},
+#endif /* B4800 */
+
+#ifdef B9600
+  {9600, B9600},
+#endif /* B9600 */
+
+#ifdef B19200
+  {19200, B19200},
+#endif /* B19200 */
+
+#ifdef B38400
+  {38400, B38400},
+#endif /* B38400 */
+
+#ifdef B57600
+  {57600, B57600},
+#endif /* B57600 */
+
+#ifdef B115200
+  {115200, B115200},
+#endif /* B115200 */
+
+#ifdef B230400
+  {230400, B230400},
+#endif /* B230400 */
+
+#ifdef B460800
+  {460800, B460800},
+#endif /* B460800 */
+
+#ifdef B500000
+  {500000, B500000},
+#endif /* B500000 */
+
+#ifdef B576000
+  {576000, B576000},
+#endif /* B576000 */
+
+#ifdef B921600
+  {921600, B921600},
+#endif /* B921600 */
+
+#ifdef B1000000
+  {1000000, B1000000},
+#endif /* B1000000 */
+
+#ifdef B1152000
+  {1152000, B1152000},
+#endif /* B1152000 */
+
+#ifdef B1500000
+  {1500000, B1500000},
+#endif /* B1500000 */
+
+#ifdef B2000000
+  {2000000, B2000000},
+#endif /* B2000000 */
+
+#ifdef B2500000
+  {2500000, B2500000},
+#endif /* B2500000 */
+
+#ifdef B3000000
+  {3000000, B3000000},
+#endif /* B3000000 */
+
+#ifdef B3500000
+  {3500000, B3500000},
+#endif /* B3500000 */
+
+#ifdef B4000000
+  {4000000, B4000000},
+#endif /* B4000000 */
+END_SERIAL_BAUD_TABLE
+
+void
+serialPutInitialAttributes (SerialAttributes *attributes) {
+  attributes->c_cflag = CREAD;
+  attributes->c_iflag = IGNPAR | IGNBRK;
+
+#ifdef IEXTEN
+  attributes->c_lflag |= IEXTEN;
+#endif /* IEXTEN */
+
+#ifdef _POSIX_VDISABLE
+  if (_POSIX_VDISABLE) {
+    unsigned int i;
+
+    for (i=0; i<NCCS; i+=1) {
+      if (i == VTIME) continue;
+      if (i == VMIN) continue;
+      attributes->c_cc[i] = _POSIX_VDISABLE;
+    }
+  }
+#endif /* _POSIX_VDISABLE */
+}
+
+int
+serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed) {
+  if (cfsetospeed(attributes, speed) != -1) {
+    if (cfsetispeed(attributes, speed) != -1) {
+      return 1;
+    } else {
+      logSystemError("cfsetispeed");
+    }
+  } else {
+    logSystemError("cfsetospeed");
+  }
+
+  return 0;
+}
+
+int
+serialPutDataBits (SerialAttributes *attributes, unsigned int bits) {
+  tcflag_t size;
+
+  switch (bits) {
+#ifdef CS5
+#if !defined(CS6) || (CS5 != CS6)
+    case 5: size = CS5; break;
+#endif
+#endif /* CS5 */
+
+#ifdef CS6
+#if !defined(CS7) || (CS6 != CS7)
+    case 6: size = CS6; break;
+#endif
+#endif /* CS6 */
+
+#ifdef CS7
+    case 7: size = CS7; break;
+#endif /* CS7 */
+
+#ifdef CS8
+    case 8: size = CS8; break;
+#endif /* CS8 */
+
+    default:
+      return 0;
+  }
+
+  attributes->c_cflag &= ~CSIZE;
+  attributes->c_cflag |= size;
+  return 1;
+}
+
+int
+serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits) {
+  if (bits == SERIAL_STOP_1) {
+    attributes->c_cflag &= ~CSTOPB;
+  } else if (bits == SERIAL_STOP_2) {
+    attributes->c_cflag |= CSTOPB;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+serialPutParity (SerialAttributes *attributes, SerialParity parity) {
+  attributes->c_cflag &= ~(PARENB | PARODD);
+
+#ifdef PARSTK
+  attributes->c_cflag &= ~PARSTK;
+#endif /* PARSTK */
+
+  if (parity != SERIAL_PARITY_NONE) {
+    if (parity == SERIAL_PARITY_ODD) {
+      attributes->c_cflag |= PARODD;
+    } else
+
+#ifndef PARSTK
+#ifdef CMSPAR
+#define PARSTK CMSPAR
+#endif /* CMSPAR */
+#endif /* PARSTK */
+
+#ifdef PARSTK
+    if (parity == SERIAL_PARITY_SPACE) {
+      attributes->c_cflag |= PARSTK;
+    } else
+
+    if (parity == SERIAL_PARITY_MARK) {
+      attributes->c_cflag |= PARSTK | PARODD;
+    } else
+#endif /* PARSTK */
+
+    if (parity != SERIAL_PARITY_EVEN) {
+      return 0;
+    }
+
+    attributes->c_cflag |= PARENB;
+  }
+
+  return 1;
+}
+
+SerialFlowControl
+serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow) {
+  typedef struct {
+    tcflag_t *field;
+    tcflag_t flag;
+    SerialFlowControl flow;
+  } FlowControlEntry;
+
+  const FlowControlEntry flowControlTable[] = {
+#ifdef CRTSCTS
+    {&attributes->c_cflag, CRTSCTS, SERIAL_FLOW_OUTPUT_RTS | SERIAL_FLOW_OUTPUT_CTS},
+#endif /* CRTSCTS */
+
+#ifdef IHFLOW
+    {&attributes->c_cflag, IHFLOW, SERIAL_FLOW_INPUT_RTS},
+#endif /* IHFLOW */
+
+#ifdef OHFLOW
+    {&attributes->c_cflag, OHFLOW, SERIAL_FLOW_OUTPUT_CTS},
+#endif /* OHFLOW */
+
+#ifdef IXOFF
+    {&attributes->c_iflag, IXOFF, SERIAL_FLOW_INPUT_XON},
+#endif /* IXOFF */
+
+#ifdef IXON
+    {&attributes->c_iflag, IXON, SERIAL_FLOW_OUTPUT_XON},
+#endif /* IXON */
+
+    {NULL, 0, 0}
+  };
+  const FlowControlEntry *entry = flowControlTable;
+
+  while (entry->field) {
+    if ((flow & entry->flow) == entry->flow) {
+      flow &= ~entry->flow;
+      *entry->field |= entry->flag;
+    } else if (!(flow & entry->flow)) {
+      *entry->field &= ~entry->flag;
+    }
+
+    entry += 1;
+  }
+
+  return flow;
+}
+
+int
+serialPutModemState (SerialAttributes *attributes, int enabled) {
+  if (enabled) {
+    attributes->c_cflag &= ~CLOCAL;
+  } else {
+    attributes->c_cflag |= CLOCAL;
+  }
+
+  return 1;
+}
+
+unsigned int
+serialGetDataBits (const SerialAttributes *attributes) {
+  tcflag_t size = attributes->c_cflag & CSIZE;
+
+  switch (size) {
+#ifdef CS5
+#if !defined(CS6) || (CS5 != CS6)
+    case CS5: return 5;
+#endif
+#endif /* CS5 */
+
+#ifdef CS6
+#if !defined(CS7) || (CS6 != CS7)
+    case CS6: return 6;
+#endif
+#endif /* CS6 */
+
+#ifdef CS7
+    case CS7: return 7;
+#endif /* CS7 */
+
+#ifdef CS8
+    case CS8: return 8;
+#endif /* CS8 */
+
+    default:
+      logMessage(LOG_WARNING, "unsupported termios data bits: %lX", (unsigned long)size);
+      return 0;
+  }
+}
+
+unsigned int
+serialGetStopBits (const SerialAttributes *attributes) {
+  return (attributes->c_cflag & CSTOPB)? 2: 1;
+}
+
+unsigned int
+serialGetParityBits (const SerialAttributes *attributes) {
+  return (attributes->c_cflag & PARENB)? 1: 0;
+}
+
+int
+serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes) {
+  if (tcgetattr(serial->fileDescriptor, attributes) != -1) return 1;
+  logSystemError("tcgetattr");
+  return 0;
+}
+
+int
+serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  if (tcsetattr(serial->fileDescriptor, TCSANOW, attributes) != -1) return 1;
+  logSystemError("tcsetattr");
+  return 0;
+}
+
+int
+serialCancelInput (SerialDevice *serial) {
+  if (tcflush(serial->fileDescriptor, TCIFLUSH) != -1) return 1;
+  if (errno == EINVAL) return 1;
+  logSystemError("TCIFLUSH");
+  return 0;
+}
+
+int
+serialCancelOutput (SerialDevice *serial) {
+  if (tcflush(serial->fileDescriptor, TCOFLUSH) != -1) return 1;
+  if (errno == EINVAL) return 1;
+  logSystemError("TCOFLUSH");
+  return 0;
+}
+
+static void
+serialCancelInputMonitor (SerialDevice *serial) {
+  if (serial->package.inputMonitor) {
+    asyncCancelRequest(serial->package.inputMonitor);
+    serial->package.inputMonitor = NULL;
+  }
+}
+
+int
+serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data) {
+  serialCancelInputMonitor(serial);
+  if (!callback) return 1;
+  return asyncMonitorFileInput(&serial->package.inputMonitor, serial->fileDescriptor, callback, data);
+}
+
+int
+serialPollInput (SerialDevice *serial, int timeout) {
+  return awaitFileInput(serial->fileDescriptor, timeout);
+}
+
+int
+serialDrainOutput (SerialDevice *serial) {
+#ifdef HAVE_TCDRAIN
+  do {
+    if (tcdrain(serial->fileDescriptor) != -1) return 1;
+  } while (errno == EINTR);
+#else /* HAVE_TCDRAIN */
+  errno = ENOSYS;
+#endif /* HAVE_TCDRAIN */
+
+  logSystemError("tcdrain");
+  return 0;
+}
+
+ssize_t
+serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  return readFile(serial->fileDescriptor, buffer, size, initialTimeout, subsequentTimeout);
+}
+
+ssize_t
+serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  return writeFile(serial->fileDescriptor, data, size);
+}
+
+int
+serialGetLines (SerialDevice *serial) {
+#ifdef TIOCMGET
+  if (ioctl(serial->fileDescriptor, TIOCMGET, &serial->linesState) == -1) {
+    logSystemError("TIOCMGET");
+    return 0;
+  }
+#else /* TIOCMGET */
+#warning getting modem lines not supported on this platform
+  serial->linesState = SERIAL_LINE_RTS | SERIAL_LINE_CTS | SERIAL_LINE_DTR | SERIAL_LINE_DSR | SERIAL_LINE_CAR;
+#endif /* TIOCMGET */
+
+  return 1;
+}
+
+int
+serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+#ifdef TIOCMSET
+  if (serialGetLines(serial)) {
+    SerialLines lines = serial->linesState;
+    lines |= high;
+    lines &= ~low;
+
+    if (ioctl(serial->fileDescriptor, TIOCMSET, &lines) != -1) return 1;
+    logSystemError("TIOCMSET");
+  }
+#else /* TIOCMSET */
+#warning setting modem lines not supported on this platform
+#endif /* TIOCMSET */
+
+  return 0;
+}
+
+int
+serialRegisterWaitLines (SerialDevice *serial, SerialLines lines) {
+  return 1;
+}
+
+int
+serialMonitorWaitLines (SerialDevice *serial) {
+#ifdef TIOCMIWAIT
+  if (ioctl(serial->fileDescriptor, TIOCMIWAIT, serial->waitLines) != -1) return 1;
+  logSystemError("TIOCMIWAIT");
+#else /* TIOCMIWAIT */
+  SerialLines old = serial->linesState & serial->waitLines;
+
+  while (serialGetLines(serial)) {
+    if ((serial->linesState & serial->waitLines) != old) return 1;
+  }
+#endif /* TIOCMIWAIT */
+
+  return 0;
+}
+
+int
+serialConnectDevice (SerialDevice *serial, const char *device) {
+  serial->package.inputMonitor = NULL;
+
+  if ((serial->fileDescriptor = open(device, O_RDWR|O_NOCTTY|O_NONBLOCK)) != -1) {
+    setCloseOnExec(serial->fileDescriptor, 1);
+
+    if (isatty(serial->fileDescriptor)) {
+      if (serialPrepareDevice(serial)) {
+        logMessage(LOG_CATEGORY(SERIAL_IO), "device opened: %s: fd=%d",
+                   device, serial->fileDescriptor);
+        return 1;
+      }
+    } else {
+      logMessage(LOG_ERR, "not a serial device: %s", device);
+    }
+
+    close(serial->fileDescriptor);
+  } else {
+    logMessage(((errno == ENOENT)? LOG_DEBUG: LOG_ERR),
+               "cannot open serial device: %s: %s",
+               device, strerror(errno));
+  }
+
+  return 0;
+}
+
+void
+serialDisconnectDevice (SerialDevice *serial) {
+  serialCancelInputMonitor(serial);
+}
+
+int
+serialEnsureFileDescriptor (SerialDevice *serial) {
+  return 1;
+}
+
+void
+serialClearError (SerialDevice *serial) {
+}
diff --git a/Programs/serial_termios.h b/Programs/serial_termios.h
new file mode 100644
index 0000000..98ed7f9
--- /dev/null
+++ b/Programs/serial_termios.h
@@ -0,0 +1,54 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_TERMIOS
+#define BRLTTY_INCLUDED_SERIAL_TERMIOS
+
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SYS_MODEM_H
+#include <sys/modem.h>
+#endif /* HAVE_SYS_MODEM_H */
+
+#include "async_handle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef speed_t SerialSpeed;
+typedef struct termios SerialAttributes;
+
+typedef int SerialLines;
+#define SERIAL_LINE_RTS TIOCM_RTS
+#define SERIAL_LINE_DTR TIOCM_DTR
+#define SERIAL_LINE_CTS TIOCM_CTS
+#define SERIAL_LINE_DSR TIOCM_DSR
+#define SERIAL_LINE_RNG TIOCM_RNG
+#define SERIAL_LINE_CAR TIOCM_CAR
+
+typedef struct {
+  AsyncHandle inputMonitor;
+} SerialPackageFields;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_TERMIOS */
diff --git a/Programs/serial_uart.h b/Programs/serial_uart.h
new file mode 100644
index 0000000..741d84e
--- /dev/null
+++ b/Programs/serial_uart.h
@@ -0,0 +1,50 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_UART
+#define BRLTTY_INCLUDED_SERIAL_UART
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define UART_PORT_RBR 0 /* receive buffered register */
+#define UART_PORT_THR 0 /* transmit holding register */
+#define UART_PORT_DLL 0 /* divisor latch low */
+#define UART_PORT_IER 1 /* interrupt enable register */
+#define UART_PORT_DLH 1 /* divisor latch high */
+#define UART_PORT_IIR 2 /* interrupt id register */
+#define UART_PORT_LCR 3 /* line control register */
+#define UART_PORT_MCR 4 /* modem control register */
+#define UART_PORT_MSR 6 /* modem status register */
+
+#define UART_FLAG_LCR_DLAB 0X80 /* divisor latch access bit */
+
+#define UART_FLAG_MCR_DTR 0X01 /* data terminal ready */
+#define UART_FLAG_MCR_RTS 0X02 /* ready to send */
+
+#define UART_FLAG_MSR_CTS 0X10 /* clear to send */
+#define UART_FLAG_MSR_DSR 0X20 /* data set ready */
+#define UART_FLAG_MSR_RNG 0X40 /* ring indicator */
+#define UART_FLAG_MSR_CAR 0X80 /* carrier detect */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_UART */
diff --git a/Programs/serial_windows.c b/Programs/serial_windows.c
new file mode 100644
index 0000000..ed1f570
--- /dev/null
+++ b/Programs/serial_windows.c
@@ -0,0 +1,503 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "ascii.h"
+
+#include "serial_windows.h"
+#include "serial_internal.h"
+
+BEGIN_SERIAL_BAUD_TABLE
+#ifdef CBR_110
+  {110, CBR_110},
+#endif /* CBR_110 */
+
+#ifdef CBR_300
+  {300, CBR_300},
+#endif /* CBR_300 */
+
+#ifdef CBR_600
+  {600, CBR_600},
+#endif /* CBR_600 */
+
+#ifdef CBR_1200
+  {1200, CBR_1200},
+#endif /* CBR_1200 */
+
+#ifdef CBR_2400
+  {2400, CBR_2400},
+#endif /* CBR_2400 */
+
+#ifdef CBR_4800
+  {4800, CBR_4800},
+#endif /* CBR_4800 */
+
+#ifdef CBR_9600
+  {9600, CBR_9600},
+#endif /* CBR_9600 */
+
+#ifdef CBR_14400
+  {14400, CBR_14400},
+#endif /* CBR_14400 */
+
+#ifdef CBR_19200
+  {19200, CBR_19200},
+#endif /* CBR_19200 */
+
+#ifdef CBR_38400
+  {38400, CBR_38400},
+#endif /* CBR_38400 */
+
+#ifdef CBR_56000
+  {56000, CBR_56000},
+#endif /* CBR_56000 */
+
+#ifdef CBR_57600
+  {57600, CBR_57600},
+#endif /* CBR_57600 */
+
+#ifdef CBR_115200
+  {115200, CBR_115200},
+#endif /* CBR_115200 */
+
+#ifdef CBR_128000
+  {128000, CBR_128000},
+#endif /* CBR_128000 */
+
+#ifdef CBR_256000
+  {256000, CBR_256000},
+#endif /* CBR_256000 */
+END_SERIAL_BAUD_TABLE
+
+void
+serialPutInitialAttributes (SerialAttributes *attributes) {
+  attributes->DCBlength = sizeof(*attributes);
+  attributes->fBinary = TRUE;
+  attributes->fTXContinueOnXoff = TRUE;
+  attributes->XonChar = ASCII_DC1;
+  attributes->XoffChar = ASCII_DC3;
+}
+
+int
+serialPutSpeed (SerialAttributes *attributes, SerialSpeed speed) {
+  attributes->BaudRate = speed;
+  return 1;
+}
+
+int
+serialPutDataBits (SerialAttributes *attributes, unsigned int bits) {
+  if ((bits < 5) || (bits > 8)) return 0;
+  attributes->ByteSize = bits;
+  return 1;
+}
+
+int
+serialPutStopBits (SerialAttributes *attributes, SerialStopBits bits) {
+  if (bits == SERIAL_STOP_1) {
+    attributes->StopBits = ONESTOPBIT;
+  } else if (bits == SERIAL_STOP_1_5) {
+    attributes->StopBits = ONE5STOPBITS;
+  } else if (bits == SERIAL_STOP_2) {
+    attributes->StopBits = TWOSTOPBITS;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+serialPutParity (SerialAttributes *attributes, SerialParity parity) {
+  attributes->fParity = FALSE;
+  attributes->Parity = NOPARITY;
+
+  if (parity != SERIAL_PARITY_NONE) {
+    switch (parity) {
+      case SERIAL_PARITY_ODD:
+        attributes->Parity = ODDPARITY;
+        break;
+
+      case SERIAL_PARITY_EVEN:
+        attributes->Parity = EVENPARITY;
+        break;
+
+      case SERIAL_PARITY_MARK:
+        attributes->Parity = MARKPARITY;
+        break;
+
+      case SERIAL_PARITY_SPACE:
+        attributes->Parity = SPACEPARITY;
+        break;
+
+      default:
+        return 0;
+    }
+
+    attributes->fParity = TRUE;
+  }
+
+  return 1;
+}
+
+SerialFlowControl
+serialPutFlowControl (SerialAttributes *attributes, SerialFlowControl flow) {
+  if (flow & SERIAL_FLOW_OUTPUT_RTS) {
+    flow &= ~SERIAL_FLOW_OUTPUT_RTS;
+    attributes->fRtsControl = RTS_CONTROL_TOGGLE;
+  } else if (flow & SERIAL_FLOW_INPUT_RTS) {
+    flow &= ~SERIAL_FLOW_INPUT_RTS;
+    attributes->fRtsControl = RTS_CONTROL_HANDSHAKE;
+  } else {
+    attributes->fRtsControl = RTS_CONTROL_ENABLE;
+  }
+
+  if (flow & SERIAL_FLOW_INPUT_XON) {
+    flow &= ~SERIAL_FLOW_INPUT_XON;
+    attributes->fInX = TRUE;
+  } else {
+    attributes->fInX = FALSE;
+  }
+
+  if (flow & SERIAL_FLOW_OUTPUT_CTS) {
+    flow &= ~SERIAL_FLOW_OUTPUT_CTS;
+    attributes->fOutxCtsFlow = TRUE;
+  } else {
+    attributes->fOutxCtsFlow = FALSE;
+  }
+
+  if (flow & SERIAL_FLOW_OUTPUT_DSR) {
+    flow &= ~SERIAL_FLOW_OUTPUT_DSR;
+    attributes->fOutxDsrFlow = TRUE;
+  } else {
+    attributes->fOutxDsrFlow = FALSE;
+  }
+
+  if (flow & SERIAL_FLOW_OUTPUT_XON) {
+    flow &= ~SERIAL_FLOW_OUTPUT_XON;
+    attributes->fOutX = TRUE;
+  } else {
+    attributes->fOutX = FALSE;
+  }
+
+  return flow;
+}
+
+int
+serialPutModemState (SerialAttributes *attributes, int enabled) {
+  if (enabled) {
+    attributes->fDtrControl = DTR_CONTROL_HANDSHAKE;
+    attributes->fDsrSensitivity = TRUE;
+  } else {
+    attributes->fDtrControl = DTR_CONTROL_ENABLE;
+    attributes->fDsrSensitivity = FALSE;
+  }
+
+  return 1;
+}
+
+unsigned int
+serialGetDataBits (const SerialAttributes *attributes) {
+  return attributes->ByteSize;
+}
+
+unsigned int
+serialGetStopBits (const SerialAttributes *attributes) {
+  if (attributes->StopBits == ONESTOPBIT) return 1;
+  if (attributes->StopBits == TWOSTOPBITS) return 2;
+
+  logMessage(LOG_WARNING, "unsupported Windows serial stop bits value: %X", attributes->StopBits);
+  return 0;
+}
+
+unsigned int
+serialGetParityBits (const SerialAttributes *attributes) {
+  return (attributes->fParity && (attributes->Parity != NOPARITY))? 1: 0;
+}
+
+int
+serialGetAttributes (SerialDevice *serial, SerialAttributes *attributes) {
+  attributes->DCBlength = sizeof(serial->currentAttributes);
+  if (GetCommState(serial->package.fileHandle, attributes)) return 1;
+  logWindowsSystemError("GetCommState");
+  return 0;
+}
+
+int
+serialPutAttributes (SerialDevice *serial, const SerialAttributes *attributes) {
+  if (SetCommState(serial->package.fileHandle, (SerialAttributes *)attributes)) return 1;
+  logWindowsSystemError("SetCommState");
+  return 0;
+}
+
+int
+serialCancelInput (SerialDevice *serial) {
+  if (PurgeComm(serial->package.fileHandle, PURGE_RXCLEAR)) return 1;
+  logWindowsSystemError("PurgeComm");
+  return 0;
+}
+
+int
+serialCancelOutput (SerialDevice *serial) {
+  if (PurgeComm(serial->package.fileHandle, PURGE_TXCLEAR)) return 1;
+  logWindowsSystemError("PurgeComm");
+  return 0;
+}
+
+int
+serialMonitorInput (SerialDevice *serial, AsyncMonitorCallback *callback, void *data) {
+  return 0;
+}
+
+int
+serialPollInput (SerialDevice *serial, int timeout) {
+  if (serial->package.pendingCharacter != -1) return 1;
+
+  {
+    COMMTIMEOUTS timeouts = {MAXDWORD, 0, timeout, 0, 0};
+    DWORD bytesRead;
+    char c;
+
+    if (!(SetCommTimeouts(serial->package.fileHandle, &timeouts))) {
+      logWindowsSystemError("SetCommTimeouts serialAwaitInput");
+      setSystemErrno();
+      return 0;
+    }
+
+    if (!ReadFile(serial->package.fileHandle, &c, 1, &bytesRead, NULL)) {
+      logWindowsSystemError("ReadFile");
+      setSystemErrno();
+      return 0;
+    }
+
+    if (bytesRead) {
+      serial->package.pendingCharacter = (unsigned char)c;
+      return 1;
+    }
+  }
+  errno = EAGAIN;
+
+  return 0;
+}
+
+int
+serialDrainOutput (SerialDevice *serial) {
+  if (FlushFileBuffers(serial->package.fileHandle)) return 1;
+  logWindowsSystemError("FlushFileBuffers");
+  return 0;
+}
+
+ssize_t
+serialGetData (
+  SerialDevice *serial,
+  void *buffer, size_t size,
+  int initialTimeout, int subsequentTimeout
+) {
+  size_t length = 0;
+  COMMTIMEOUTS timeouts = {MAXDWORD, 0, initialTimeout, 0, 0};
+  DWORD bytesRead;
+
+  if (serial->package.pendingCharacter != -1) {
+    * (unsigned char *) buffer = serial->package.pendingCharacter;
+    serial->package.pendingCharacter = -1;
+    bytesRead = 1;
+  } else {
+    if (!(SetCommTimeouts(serial->package.fileHandle, &timeouts))) {
+      logWindowsSystemError("SetCommTimeouts serialReadChunk1");
+      setSystemErrno();
+      return -1;
+    }
+
+    if (!ReadFile(serial->package.fileHandle, buffer, size, &bytesRead, NULL)) {
+      logWindowsSystemError("ReadFile");
+      setSystemErrno();
+      return -1;
+    }
+
+    if (!bytesRead) return 0;
+  }
+
+  size -= bytesRead;
+  length += bytesRead;
+  timeouts.ReadTotalTimeoutConstant = subsequentTimeout;
+
+  if (!(SetCommTimeouts(serial->package.fileHandle, &timeouts))) {
+    logWindowsSystemError("SetCommTimeouts serialReadChunk2");
+    setSystemErrno();
+    return -1;
+  }
+
+  while (size && ReadFile(serial->package.fileHandle, buffer + length, size, &bytesRead, NULL)) {
+    if (!bytesRead) return length;
+    size -= bytesRead;
+    length += bytesRead;
+  }
+
+  if (!size) return length;
+  logWindowsSystemError("ReadFile");
+  setSystemErrno();
+  return -1;
+}
+
+ssize_t
+serialPutData (
+  SerialDevice *serial,
+  const void *data, size_t size
+) {
+  COMMTIMEOUTS timeouts = {MAXDWORD, 0, 0, 0, 15000};
+  size_t left = size;
+  DWORD bytesWritten;
+
+  if (!(SetCommTimeouts(serial->package.fileHandle, &timeouts))) {
+    logWindowsSystemError("SetCommTimeouts serialWriteData");
+    setSystemErrno();
+    return -1;
+  }
+
+  while (left && WriteFile(serial->package.fileHandle, data, left, &bytesWritten, NULL)) {
+    if (!bytesWritten) break;
+    left -= bytesWritten;
+    data += bytesWritten;
+  }
+
+  if (!left) return size;
+  logWindowsSystemError("WriteFile");
+  return -1;
+}
+
+int
+serialGetLines (SerialDevice *serial) {
+  if (!GetCommModemStatus(serial->package.fileHandle, &serial->linesState)) {
+    logWindowsSystemError("GetCommModemStatus");
+    return 0;
+  }
+
+  {
+    DCB dcb;
+    dcb.DCBlength = sizeof(dcb);
+
+    if (!GetCommState(serial->package.fileHandle, &dcb)) {
+      logWindowsSystemError("GetCommState");
+      return 0;
+    }
+
+    if (dcb.fRtsControl == RTS_CONTROL_ENABLE) serial->linesState |= SERIAL_LINE_RTS;
+    if (dcb.fDtrControl == DTR_CONTROL_ENABLE) serial->linesState |= SERIAL_LINE_DTR;
+  }
+
+  return 1;
+}
+
+int
+serialPutLines (SerialDevice *serial, SerialLines high, SerialLines low) {
+  DCB dcb;
+  dcb.DCBlength = sizeof(dcb);
+
+  if (GetCommState(serial->package.fileHandle, &dcb)) {
+    if (low & SERIAL_LINE_RTS) {
+      dcb.fRtsControl = RTS_CONTROL_DISABLE;
+    } else if (high & SERIAL_LINE_RTS) {
+      dcb.fRtsControl = RTS_CONTROL_ENABLE;
+    }
+
+    if (low & SERIAL_LINE_DTR) {
+      dcb.fDtrControl = DTR_CONTROL_DISABLE;
+    } else if (high & SERIAL_LINE_DTR) {
+      dcb.fDtrControl = DTR_CONTROL_ENABLE;
+    }
+
+    if (SetCommState(serial->package.fileHandle, &dcb)) return 1;
+    logWindowsSystemError("SetCommState");
+  } else {
+    logWindowsSystemError("GetCommState");
+  }
+
+  return 0;
+}
+
+int
+serialRegisterWaitLines (SerialDevice *serial, SerialLines lines) {
+  DWORD eventMask = 0;
+
+  if (lines & SERIAL_LINE_CTS) eventMask |= EV_CTS;
+  if (lines & SERIAL_LINE_DSR) eventMask |= EV_DSR;
+  if (lines & SERIAL_LINE_RNG) eventMask |= EV_RING;
+  if (lines & SERIAL_LINE_CAR) eventMask |= EV_RLSD;
+
+  if (SetCommMask(serial->package.fileHandle, eventMask)) return 1;
+  logWindowsSystemError("SetCommMask");
+  return 0;
+}
+
+int
+serialMonitorWaitLines (SerialDevice *serial) {
+  DWORD event;
+
+  if (WaitCommEvent(serial->package.fileHandle, &event, NULL)) return 1;
+  logWindowsSystemError("WaitCommEvent");
+  return 0;
+}
+
+int
+serialConnectDevice (SerialDevice *serial, const char *device) {
+  if ((serial->package.fileHandle = CreateFile(device, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE) {
+    serial->package.pendingCharacter = -1;
+
+    if (serialPrepareDevice(serial)) {
+      logMessage(LOG_CATEGORY(SERIAL_IO), "device opened: %s: fh=%" PRIfd,
+                 device, serial->package.fileHandle);
+      return 1;
+    }
+
+    CloseHandle(serial->package.fileHandle);
+  } else {
+    logWindowsSystemError("CreateFile");
+    logMessage(LOG_ERR, "cannot open serial device: %s", device);
+  }
+
+  return 0;
+}
+
+void
+serialDisconnectDevice (SerialDevice *serial) {
+  CloseHandle(serial->package.fileHandle);
+}
+
+int
+serialEnsureFileDescriptor (SerialDevice *serial) {
+#ifdef __CYGWIN__
+  if ((serial->fileDescriptor = cygwin_attach_handle_to_fd("serialdevice", -1, serial->package.fileHandle, TRUE, GENERIC_READ|GENERIC_WRITE)) >= 0) return 1;
+  logSystemError("cygwin_attach_handle_to_fd");
+#else /* __CYGWIN__ */
+  if ((serial->fileDescriptor = _open_osfhandle((long)serial->package.fileHandle, O_RDWR)) >= 0) return 1;
+  logSystemError("open_osfhandle");
+#endif /* __CYGWIN__ */
+
+  return 0;
+}
+
+void
+serialClearError (SerialDevice *serial) {
+  ClearCommError(serial->package.fileHandle, NULL, NULL);
+}
+
diff --git a/Programs/serial_windows.h b/Programs/serial_windows.h
new file mode 100644
index 0000000..8f69286
--- /dev/null
+++ b/Programs/serial_windows.h
@@ -0,0 +1,46 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SERIAL_WINDOWS
+#define BRLTTY_INCLUDED_SERIAL_WINDOWS
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef DWORD SerialSpeed;
+typedef DCB SerialAttributes;
+
+typedef DWORD SerialLines;
+#define SERIAL_LINE_RTS 0X01
+#define SERIAL_LINE_DTR 0X02
+#define SERIAL_LINE_CTS MS_CTS_ON
+#define SERIAL_LINE_DSR MS_DSR_ON
+#define SERIAL_LINE_RNG MS_RING_ON
+#define SERIAL_LINE_CAR MS_RLSD_ON
+
+typedef struct {
+  HANDLE fileHandle;
+  int pendingCharacter;
+} SerialPackageFields;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SERIAL_WINDOWS */
diff --git a/Programs/service_libsystemd.c b/Programs/service_libsystemd.c
new file mode 100644
index 0000000..22d72f5
--- /dev/null
+++ b/Programs/service_libsystemd.c
@@ -0,0 +1,45 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <systemd/sd-daemon.h>
+
+#include "log.h"
+#include "service.h"
+
+int
+installService (const char *name, const char *description, const char *configurationFile) {
+  logUnsupportedFeature("service installation");
+  return 0;
+}
+
+int
+removeService (const char *name) {
+  logUnsupportedFeature("service removal");
+  return 0;
+}
+
+int
+notifyServiceReady (void) {
+  if (sd_notify(1, "READY=1") < 0) {
+    logSystemError("sd_notify");
+  }
+
+  return 1;
+}
diff --git a/Programs/service_none.c b/Programs/service_none.c
new file mode 100644
index 0000000..11878ee
--- /dev/null
+++ b/Programs/service_none.c
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "service.h"
+
+int
+installService (const char *name, const char *description, const char *configurationFile) {
+  logUnsupportedFeature("service installation");
+  return 0;
+}
+
+int
+removeService (const char *name) {
+  logUnsupportedFeature("service removal");
+  return 0;
+}
+
+int
+notifyServiceReady (void) {
+  return 0;
+}
diff --git a/Programs/service_windows.c b/Programs/service_windows.c
new file mode 100644
index 0000000..34b9765
--- /dev/null
+++ b/Programs/service_windows.c
@@ -0,0 +1,123 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "pgmpath.h"
+#include "service.h"
+#include "system_windows.h"
+
+int
+installService (const char *name, const char *description, const char *configurationFile) {
+  const char *const arguments[] = {
+    getProgramPath(), "-n",
+    "-f", configurationFile,
+    NULL
+  };
+
+  int installed = 0;
+  char *command = makeWindowsCommandLine(arguments);
+
+  if (command) {
+    SC_HANDLE scm = OpenSCManager(
+      NULL, // machine name
+      NULL, // database name
+      SC_MANAGER_CREATE_SERVICE // desired access
+    );
+
+    if (scm) {
+      SC_HANDLE service = CreateService(
+        scm, // service control manager database
+        name, // service name
+        description, // display name
+        SERVICE_ALL_ACCESS, // desired access
+        (SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS), // service type
+        SERVICE_AUTO_START, // start type
+        SERVICE_ERROR_NORMAL, // error control
+        command, // binary path name
+        NULL, // load order group
+        NULL, // tag id
+        NULL, // dependencies
+        NULL, // service start name
+        NULL // password
+      );
+
+      if (service) {
+        logMessage(LOG_NOTICE, "service installed: %s", name);
+        installed = 1;
+
+        CloseServiceHandle(service);
+      } else if (GetLastError() == ERROR_SERVICE_EXISTS) {
+        logMessage(LOG_WARNING, "service already installed: %s", name);
+        installed = 1;
+      } else {
+        logWindowsSystemError("CreateService");
+      }
+
+      CloseServiceHandle(scm);
+    } else {
+      logWindowsSystemError("OpenSCManager");
+    }
+
+    free(command);
+  }
+
+  return installed;
+}
+
+int
+removeService (const char *name) {
+  int removed = 0;
+  SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+
+  if (scm) {
+    SC_HANDLE service = OpenService(scm, name, DELETE);
+
+    if (service) {
+      if (DeleteService(service)) {
+        logMessage(LOG_NOTICE, "service removed: %s", name);
+        removed = 1;
+      } else if (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE) {
+        logMessage(LOG_WARNING, "service already being removed: %s", name);
+        removed = 1;
+      } else {
+        logWindowsSystemError("DeleteService");
+      }
+
+      CloseServiceHandle(service);
+    } else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
+      logMessage(LOG_WARNING, "service not installed: %s", name);
+      removed = 1;
+    } else {
+      logWindowsSystemError("OpenService");
+    }
+
+    CloseServiceHandle(scm);
+  } else {
+    logWindowsSystemError("OpenSCManager");
+  }
+
+  return removed;
+}
+
+int
+notifyServiceReady (void) {
+  logUnsupportedFeature("service ready notification");
+  return 0;
+}
diff --git a/Programs/ses.c b/Programs/ses.c
new file mode 100644
index 0000000..e3a414c
--- /dev/null
+++ b/Programs/ses.c
@@ -0,0 +1,110 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "ses.h"
+#include "defaults.h"
+
+static const SessionEntry initialSessionEntry = {
+  .number = 0,
+
+  .trackScreenCursor = DEFAULT_TRACK_SCREEN_CURSOR,
+  .hideScreenCursor = DEFAULT_HIDE_SCREEN_CURSOR,
+
+  .ptrx = -1,
+  .ptry = -1
+};
+
+static SessionEntry **sessionArray = NULL;
+static unsigned int sessionLimit = 0;
+static unsigned int sessionCount = 0;
+
+SessionEntry *
+getSessionEntry (int number) {
+  int first = 0;
+  int last = sessionCount - 1;
+
+  while (first <= last) {
+    int current = (first + last) / 2;
+    SessionEntry *entry = sessionArray[current];
+    if (number == entry->number) return entry;
+
+    if (number < entry->number) {
+      last = current - 1;
+    } else {
+      first = current + 1;
+    }
+  }
+
+  if (sessionCount == sessionLimit) {
+    unsigned int newLimit = sessionLimit? sessionLimit<<1: 0X10;
+    SessionEntry **newArray;
+
+    if ((newArray = realloc(sessionArray, ARRAY_SIZE(newArray, newLimit)))) {
+      sessionArray = newArray;
+      sessionLimit = newLimit;
+    } else {
+      logMallocError();
+    }
+  }
+
+  if (sessionCount < sessionLimit) {
+    SessionEntry *entry = malloc(sizeof(*entry));
+
+    if (entry) {
+      *entry = initialSessionEntry;
+      entry->number = number;
+
+      memmove(&sessionArray[first+1], &sessionArray[first],
+              ARRAY_SIZE(sessionArray, sessionCount-first));
+      sessionArray[first] = entry;
+      sessionCount += 1;
+      return entry;
+    } else {
+      logMallocError();
+    }
+  }
+
+  {
+    static SessionEntry fallbackEntry;
+    static int initialized = 0;
+
+    if (!initialized) {
+      fallbackEntry = initialSessionEntry;
+      initialized = 1;
+    }
+
+    return &fallbackEntry;
+  }
+}
+
+void
+deallocateSessionEntries (void) {
+  if (sessionArray) {
+    while (sessionCount > 0) free(sessionArray[--sessionCount]);
+    free(sessionArray);
+    sessionArray = NULL;
+  } else {
+    sessionCount = 0;
+  }
+  sessionLimit = 0;
+}
diff --git a/Programs/ses.h b/Programs/ses.h
new file mode 100644
index 0000000..37a26be
--- /dev/null
+++ b/Programs/ses.h
@@ -0,0 +1,59 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SES
+#define BRLTTY_INCLUDED_SES
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* 
+ * Structure definition for volatile screen state variables.
+ */
+
+typedef struct {
+  short column;
+  short row;
+} ScreenLocation;
+
+typedef struct {
+  int number;
+
+  unsigned char trackScreenCursor;		/* cursor tracking mode */
+  unsigned char hideScreenCursor;		/* for temporarily hiding the cursor */
+  unsigned char displayMode;		/* text or attributes display */
+
+  int winx, winy;	/* upper-left corner of braille window */
+  int motx, moty;	/* last user motion of braille window */
+  int trkx, trky;	/* tracked cursor position */
+  int dctx, dcty;	/* initial cursor position */
+  int ptrx, ptry;	/* last known screen pointer position */
+  int spkx, spky;	/* current speech position */
+
+  ScreenLocation marks[0X100];
+} SessionEntry;
+
+extern SessionEntry *getSessionEntry (int number);
+extern void deallocateSessionEntries (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SES */
diff --git a/Programs/spk.auto.h b/Programs/spk.auto.h
new file mode 100644
index 0000000..0df42e2
--- /dev/null
+++ b/Programs/spk.auto.h
@@ -0,0 +1 @@
+static const DriverEntry driverTable[] = {{NULL, NULL}};
diff --git a/Programs/spk.c b/Programs/spk.c
new file mode 100644
index 0000000..191a8c9
--- /dev/null
+++ b/Programs/spk.c
@@ -0,0 +1,251 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "program.h"
+#include "file.h"
+#include "parse.h"
+#include "prefs.h"
+#include "utf8.h"
+#include "spk.h"
+#include "spk_thread.h"
+
+void
+constructSpeechSynthesizer (SpeechSynthesizer *spk) {
+  spk->canAutospeak = 1;
+
+  spk->track.isActive = 0;
+  spk->track.screenNumber = SPK_SCR_NONE;
+  spk->track.firstLine = 0;
+  spk->track.speechLocation = SPK_LOC_NONE;
+
+  spk->setVolume = NULL;
+  spk->setRate = NULL;
+  spk->setPitch = NULL;
+  spk->setPunctuation = NULL;
+  spk->drain = NULL;
+
+  spk->setFinished = NULL;
+  spk->setLocation = NULL;
+
+  spk->driver.thread = NULL;
+  spk->driver.data = NULL;
+}
+
+void
+destructSpeechSynthesizer (SpeechSynthesizer *spk) {
+}
+
+int
+startSpeechDriverThread (SpeechSynthesizer *spk, char **parameters) {
+  return constructSpeechDriverThread(spk, parameters);
+}
+
+void
+stopSpeechDriverThread (SpeechSynthesizer *spk) {
+  destroySpeechDriverThread(spk);
+}
+
+int
+muteSpeech (SpeechSynthesizer *spk, const char *reason) {
+  int result;
+
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "mute: %s", reason);
+  result = speechRequest_muteSpeech(spk->driver.thread);
+
+  if (spk->setFinished) spk->setFinished(spk);
+  return result;
+}
+
+int
+sayUtf8Characters (
+  SpeechSynthesizer *spk,
+  const char *text, const unsigned char *attributes,
+  size_t length, size_t count,
+  SayOptions options
+) {
+  if (count) {
+    logMessage(LOG_CATEGORY(SPEECH_EVENTS), "say: %s", text);
+    if (!speechRequest_sayText(spk->driver.thread, text, length, count, attributes, options)) return 0;
+  }
+
+  return 1;
+}
+
+int
+sayWideCharacters (
+  SpeechSynthesizer *spk,
+  const wchar_t *characters, const unsigned char *attributes,
+  size_t count, SayOptions options
+) {
+  int ok = 0;
+  size_t length;
+  void *text = getUtf8FromWchars(characters, count, &length);
+
+  if (text) {
+    if (sayUtf8Characters(spk, text, attributes, length, count, options)) ok = 1;
+    free(text);
+  } else {
+    logMallocError();
+  }
+
+  return ok;
+}
+
+int
+sayString (
+  SpeechSynthesizer *spk,
+  const char *string, SayOptions options
+) {
+  return sayUtf8Characters(spk, string, NULL, strlen(string), countUtf8Characters(string), options);
+}
+
+static int
+sayStringSetting (
+  SpeechSynthesizer *spk,
+  const char *name, const char *string
+) {
+  char statement[0X40];
+
+  snprintf(statement, sizeof(statement), "%s %s", name, string);
+  return sayString(spk, statement, SAY_OPT_MUTE_FIRST);
+}
+
+static int
+sayIntegerSetting (
+  SpeechSynthesizer *spk,
+  const char *name, int integer
+) {
+  char string[0X10];
+
+  snprintf(string, sizeof(string), "%d", integer);
+  return sayStringSetting(spk, name, string);
+}
+
+int
+canDrainSpeech (SpeechSynthesizer *spk) {
+  return spk->drain != NULL;
+}
+
+int
+drainSpeech (SpeechSynthesizer *spk) {
+  if (!canDrainSpeech(spk)) return 0;
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "drain speech");
+  speechRequest_drainSpeech(spk->driver.thread);
+  return 1;
+}
+
+int
+canSetSpeechVolume (SpeechSynthesizer *spk) {
+  return spk->setVolume != NULL;
+}
+
+int
+toNormalizedSpeechVolume (unsigned char volume) {
+  return rescaleInteger(volume, SPK_VOLUME_DEFAULT, 100);
+}
+
+int
+setSpeechVolume (SpeechSynthesizer *spk, int setting, int say) {
+  if (!canSetSpeechVolume(spk)) return 0;
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "set volume: %d", setting);
+  speechRequest_setVolume(spk->driver.thread, setting);
+
+  if (say) {
+    sayIntegerSetting(
+      spk, gettext("volume"),
+      toNormalizedSpeechVolume(setting)
+    );
+  }
+
+  return 1;
+}
+
+int
+canSetSpeechRate (SpeechSynthesizer *spk) {
+  return spk->setRate != NULL;
+}
+
+int
+toNormalizedSpeechRate (unsigned char rate) {
+  return rate - SPK_RATE_DEFAULT;
+}
+
+int
+setSpeechRate (SpeechSynthesizer *spk, int setting, int say) {
+  if (!canSetSpeechRate(spk)) return 0;
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "set rate: %d", setting);
+  speechRequest_setRate(spk->driver.thread, setting);
+
+  if (say) {
+    sayIntegerSetting(
+      spk, gettext("rate"),
+      toNormalizedSpeechRate(setting)
+    );
+  }
+
+  return 1;
+}
+
+int
+canSetSpeechPitch (SpeechSynthesizer *spk) {
+  return spk->setPitch != NULL;
+}
+
+int
+toNormalizedSpeechPitch (unsigned char pitch) {
+  return pitch - SPK_PITCH_DEFAULT;
+}
+
+int
+setSpeechPitch (SpeechSynthesizer *spk, int setting, int say) {
+  if (!canSetSpeechPitch(spk)) return 0;
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "set pitch: %d", setting);
+  speechRequest_setPitch(spk->driver.thread, setting);
+
+  if (say) {
+    sayIntegerSetting(
+      spk, gettext("pitch"),
+      toNormalizedSpeechPitch(setting)
+    );
+  }
+
+  return 1;
+}
+
+int
+canSetSpeechPunctuation (SpeechSynthesizer *spk) {
+  return spk->setPunctuation != NULL;
+}
+
+int
+setSpeechPunctuation (SpeechSynthesizer *spk, SpeechPunctuation setting, int say) {
+  if (!canSetSpeechPunctuation(spk)) return 0;
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "set punctuation: %d", setting);
+  speechRequest_setPunctuation(spk->driver.thread, setting);
+  return 1;
+}
diff --git a/Programs/spk_base.c b/Programs/spk_base.c
new file mode 100644
index 0000000..3741fd3
--- /dev/null
+++ b/Programs/spk_base.c
@@ -0,0 +1,99 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "log.h"
+#include "spk_types.h"
+#include "spk_base.h"
+#include "spk_thread.h"
+#include "parse.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+int
+tellSpeechFinished (SpeechSynthesizer *spk) {
+  return speechMessage_speechFinished(spk->driver.thread);
+}
+
+int
+tellSpeechLocation (SpeechSynthesizer *spk, int index) {
+  if (!spk->track.isActive) return 1;
+  return speechMessage_speechLocation(spk->driver.thread, index);
+}
+
+static unsigned int
+getIntegerSetting (unsigned char setting, unsigned char internal, unsigned int external) {
+  return rescaleInteger(setting, internal, external);
+}
+
+unsigned int
+getIntegerSpeechVolume (unsigned char setting, unsigned int normal) {
+  return getIntegerSetting(setting, SPK_VOLUME_DEFAULT, normal);
+}
+
+unsigned int
+getIntegerSpeechRate (unsigned char setting, unsigned int normal) {
+  return getIntegerSetting(setting, SPK_RATE_DEFAULT, normal);
+}
+
+unsigned int
+getIntegerSpeechPitch (unsigned char setting, unsigned int normal) {
+  return getIntegerSetting(setting, SPK_PITCH_DEFAULT, normal);
+}
+
+#ifndef NO_FLOAT
+float
+getFloatSpeechVolume (unsigned char setting) {
+  return (float)setting / (float)SPK_VOLUME_DEFAULT;
+}
+
+float
+getFloatSpeechRate (unsigned char setting) {
+  static const float spkRateTable[] = {
+    0.3333,
+    0.3720,
+    0.4152,
+    0.4635,
+    0.5173,
+    0.5774,
+    0.6444,
+    0.7192,
+    0.8027,
+    0.8960,
+    1.0000,
+    1.1161,
+    1.2457,
+    1.3904,
+    1.5518,
+    1.7320,
+    1.9332,
+    2.1577,
+    2.4082,
+    2.6879,
+    3.0000
+  };
+
+  return spkRateTable[setting];
+}
+
+float
+getFloatSpeechPitch (unsigned char setting) {
+  return (float)setting / (float)SPK_PITCH_DEFAULT;
+}
+#endif /* NO_FLOAT */
+#endif /* ENABLE_SPEECH_SUPPORT */
diff --git a/Programs/spk_driver.c b/Programs/spk_driver.c
new file mode 100644
index 0000000..55d4e32
--- /dev/null
+++ b/Programs/spk_driver.c
@@ -0,0 +1,82 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "drivers.h"
+#include "spk.h"
+
+#define SPKSYMBOL noSpeech
+#define DRIVER_NAME NoSpeech
+#define DRIVER_CODE no
+#define DRIVER_COMMENT "no speech support"
+#define DRIVER_VERSION ""
+#define DRIVER_DEVELOPERS ""
+#include "spk_driver.h"
+#include "spk.auto.h"
+
+static int
+spk_construct (SpeechSynthesizer *spk, char **parameters) {
+  return 1;
+}
+
+static void
+spk_destruct (SpeechSynthesizer *spk) {
+}
+
+static void
+spk_say (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes) {
+}
+
+static void
+spk_mute (SpeechSynthesizer *spk) {
+}
+
+const SpeechDriver *speech = &noSpeech;
+
+int
+haveSpeechDriver (const char *code) {
+  return haveDriver(code, SPEECH_DRIVER_CODES, driverTable);
+}
+
+const char *
+getDefaultSpeechDriver (void) {
+  return getDefaultDriver(driverTable);
+}
+
+const SpeechDriver *
+loadSpeechDriver (const char *code, void **driverObject, const char *driverDirectory) {
+  return loadDriver(code, driverObject,
+                    driverDirectory, driverTable,
+                    "speech", 's', "spk",
+                    &noSpeech, &noSpeech.definition);
+}
+
+void
+identifySpeechDriver (const SpeechDriver *driver, int full) {
+  identifyDriver("Speech", &driver->definition, full);
+}
+
+void
+identifySpeechDrivers (int full) {
+  const DriverEntry *entry = driverTable;
+  while (entry->address) {
+    const SpeechDriver *driver = entry++->address;
+    identifySpeechDriver(driver, full);
+  }
+}
diff --git a/Programs/spk_input.c b/Programs/spk_input.c
new file mode 100644
index 0000000..417dfcf
--- /dev/null
+++ b/Programs/spk_input.c
@@ -0,0 +1,90 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "spk_input.h"
+#include "spk.h"
+#include "pipe.h"
+#include "ascii.h"
+#include "core.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+struct SpeechInputObjectStruct {
+  NamedPipeObject *pipe;
+};
+
+static
+NAMED_PIPE_INPUT_CALLBACK(handleSpeechInput) {
+  const unsigned char *buffer = parameters->buffer;
+  size_t count = parameters->length;
+  const unsigned char *end = buffer + count;
+  SayOptions options = 0;
+
+  while (buffer < end) {
+     if (*buffer != ASCII_ESC) break;
+     if (++buffer == end) break;
+
+     switch (*buffer++) {
+       case '!':
+         options |= SAY_OPT_MUTE_FIRST;
+         break;
+     }
+  }
+
+  if (buffer < end) {
+    size_t length = end - buffer;
+    char string[length + 1];
+
+    memcpy(string, buffer, length);
+    string[length] = 0;
+
+    sayString(&spk, string, options);
+  }
+
+  return count;
+}
+
+SpeechInputObject *
+newSpeechInputObject (const char *name) {
+  SpeechInputObject *obj;
+
+  if ((obj = malloc(sizeof(*obj)))) {
+    memset(obj, 0, sizeof(*obj));
+
+    if ((obj->pipe = newNamedPipeObject(name, handleSpeechInput, obj))) {
+      return obj;
+    }
+
+    free(obj);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroySpeechInputObject (SpeechInputObject *obj) {
+  if (obj->pipe) destroyNamedPipeObject(obj->pipe);
+  free(obj);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
diff --git a/Programs/spk_input.h b/Programs/spk_input.h
new file mode 100644
index 0000000..6626ae1
--- /dev/null
+++ b/Programs/spk_input.h
@@ -0,0 +1,37 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK_INPUT
+#define BRLTTY_INCLUDED_SPK_INPUT
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef ENABLE_SPEECH_SUPPORT
+typedef struct SpeechInputObjectStruct SpeechInputObject;
+
+extern SpeechInputObject *newSpeechInputObject (const char *name);
+extern void destroySpeechInputObject (SpeechInputObject *obj);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK_INPUT */
diff --git a/Programs/spk_thread.c b/Programs/spk_thread.c
new file mode 100644
index 0000000..0654c9d
--- /dev/null
+++ b/Programs/spk_thread.c
@@ -0,0 +1,963 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "parameters.h"
+#include "log.h"
+#include "strfmt.h"
+#include "prefs.h"
+#include "spk_thread.h"
+#include "spk.h"
+#include "async_wait.h"
+#include "async_event.h"
+#include "thread.h"
+#include "queue.h"
+
+#ifdef ENABLE_SPEECH_SUPPORT
+typedef enum {
+  THD_CONSTRUCTING,
+  THD_STARTING,
+  THD_READY,
+  THD_STOPPING,
+  THD_FINISHED
+} ThreadState;
+
+typedef struct {
+  const char *name;
+} ThreadStateEntry;
+
+static const ThreadStateEntry threadStateTable[] = {
+  [THD_CONSTRUCTING] = {
+    .name = "constructing"
+  },
+
+  [THD_STARTING] = {
+    .name = "starting"
+  },
+
+  [THD_READY] = {
+    .name = "ready"
+  },
+
+  [THD_STOPPING] = {
+    .name = "stopping"
+  },
+
+  [THD_FINISHED] = {
+    .name = "finished"
+  },
+};
+
+static inline const ThreadStateEntry *
+getThreadStateEntry (ThreadState state) {
+  if (state >= ARRAY_COUNT(threadStateTable)) return NULL;
+  return &threadStateTable[state];
+}
+
+typedef enum {
+  RSP_PENDING,
+  RSP_INTEGER
+} SpeechResponseType;
+
+struct SpeechDriverThreadStruct {
+  ThreadState threadState;
+  Queue *requestQueue;
+
+  SpeechSynthesizer *speechSynthesizer;
+  char **driverParameters;
+
+#ifdef GOT_PTHREADS
+  pthread_t threadIdentifier;
+  AsyncEvent *requestEvent;
+  AsyncEvent *messageEvent;
+  unsigned isBeingDestroyed:1;
+#endif /* GOT_PTHREADS */
+
+  struct {
+    SpeechResponseType type;
+
+    union {
+      int INTEGER;
+    } value;
+  } response;
+};
+
+typedef enum {
+  REQ_SAY_TEXT,
+  REQ_MUTE_SPEECH,
+  REQ_DRAIN_SPEECH,
+
+  REQ_SET_VOLUME,
+  REQ_SET_RATE,
+  REQ_SET_PITCH,
+  REQ_SET_PUNCTUATION
+} SpeechRequestType;
+
+static const char *const speechRequestNames[] = {
+  [REQ_SAY_TEXT] = "say text",
+  [REQ_MUTE_SPEECH] = "mute speech",
+  [REQ_DRAIN_SPEECH] = "drain speech",
+
+  [REQ_SET_VOLUME] = "set volume",
+  [REQ_SET_RATE] = "set rate",
+  [REQ_SET_PITCH] = "set pitch",
+  [REQ_SET_PUNCTUATION] = "set punctuation"
+};
+
+typedef struct {
+  SpeechRequestType type;
+
+  union {
+    struct {
+      const unsigned char *text;
+      size_t length;
+      size_t count;
+      const unsigned char *attributes;
+      SayOptions options;
+    } sayText;
+
+    struct {
+      unsigned char setting;
+    } setVolume;
+
+    struct {
+      unsigned char setting;
+    } setRate;
+
+    struct {
+      unsigned char setting;
+    } setPitch;
+
+    struct {
+      SpeechPunctuation setting;
+    } setPunctuation;
+  } arguments;
+
+  unsigned char data[0];
+} SpeechRequest;
+
+typedef struct {
+  const void *address;
+  size_t size;
+  unsigned end:1;
+} SpeechDatum;
+
+#define BEGIN_SPEECH_DATA SpeechDatum data[] = {
+#define END_SPEECH_DATA {.end=1} };
+
+typedef enum {
+  MSG_REQUEST_FINISHED,
+  MSG_SPEECH_FINISHED,
+  MSG_SPEECH_LOCATION
+} SpeechMessageType;
+
+static const char *const speechMessageNames[] = {
+  [MSG_REQUEST_FINISHED] = "request finished",
+  [MSG_SPEECH_FINISHED] = "speech finished",
+  [MSG_SPEECH_LOCATION] = "speech location"
+};
+
+typedef struct {
+  SpeechMessageType type;
+
+  union {
+    struct {
+      int result;
+    } requestFinished;
+
+    struct {
+      int location;
+    } speechLocation;
+  } arguments;
+
+  unsigned char data[0];
+} SpeechMessage;
+
+static const char *
+getActionName (unsigned int action, const char *const *names, size_t count) {
+  return (action < count)? names[action]: NULL;
+}
+
+typedef struct {
+  const char *action;
+  const char *type;
+  const char *name;
+  unsigned int value;
+} LogSpeechActionData;
+
+static
+STR_BEGIN_FORMATTER(formatLogSpeechActionData, const void *data)
+  const LogSpeechActionData *lsa = data;
+
+  STR_PRINTF("%s speech %s: ", lsa->action, lsa->type);
+
+  if (lsa->name) {
+    STR_PRINTF("%s", lsa->name);
+  } else {
+    STR_PRINTF("%u", lsa->value);
+  }
+STR_END_FORMATTER
+
+static void
+logSpeechAction (const LogSpeechActionData *lsa) {
+  logData(LOG_CATEGORY(SPEECH_EVENTS), formatLogSpeechActionData, lsa);
+}
+
+static void
+logSpeechRequest (SpeechRequest *req, const char *action) {
+  const LogSpeechActionData lsa = {
+    .action = action,
+    .type = "request",
+    .name = req? getActionName(req->type, speechRequestNames, ARRAY_COUNT(speechRequestNames)): "stop",
+    .value = req? req->type: 0
+  };
+
+  logSpeechAction(&lsa);
+}
+
+static void
+logSpeechMessage (SpeechMessage *msg, const char *action) {
+  const LogSpeechActionData lsa = {
+    .action = action,
+    .type = "message",
+    .name = getActionName(msg->type, speechMessageNames, ARRAY_COUNT(speechMessageNames)),
+    .value = msg->type
+  };
+
+  logSpeechAction(&lsa);
+}
+
+static int
+testThreadValidity (SpeechDriverThread *sdt) {
+  if (!sdt) return 0;
+
+#ifdef GOT_PTHREADS
+  if (sdt->isBeingDestroyed) return 0;
+#endif /* GOT_PTHREADS */
+
+  SpeechSynthesizer *spk = sdt->speechSynthesizer;
+  if (!spk) return 0;
+  if (sdt != spk->driver.thread) return 0;
+
+  if (sdt->threadState != THD_READY) return 0;
+  return 1;
+}
+
+static void
+setThreadState (SpeechDriverThread *sdt, ThreadState state) {
+  const ThreadStateEntry *entry = getThreadStateEntry(state);
+  const char *name = entry? entry->name: NULL;
+
+  if (!name) name = "?";
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread %s", name);
+  sdt->threadState = state;
+}
+
+static size_t
+getSpeechDataSize (const SpeechDatum *data) {
+  size_t size = 0;
+
+  if (data) {
+    const SpeechDatum *datum = data;
+
+    while (!datum->end) {
+      if (datum->address) size += datum->size;
+      datum += 1;
+    }
+  }
+
+  return size;
+}
+
+static void
+moveSpeechData (unsigned char *target, SpeechDatum *data) {
+  if (data) {
+    SpeechDatum *datum = data;
+
+    while (!datum->end) {
+      if (datum->address) {
+        memcpy(target, datum->address, datum->size);
+        datum->address = target;
+        target += datum->size;
+      }
+
+      datum += 1;
+    }
+  }
+}
+
+static inline void
+setResponsePending (SpeechDriverThread *sdt) {
+  sdt->response.type = RSP_PENDING;
+}
+
+static void
+setIntegerResponse (SpeechDriverThread *sdt, int value) {
+  sdt->response.type = RSP_INTEGER;
+  sdt->response.value.INTEGER = value;
+}
+
+ASYNC_CONDITION_TESTER(testSpeechResponseReceived) {
+  SpeechDriverThread *sdt = data;
+
+  return sdt->response.type != RSP_PENDING;
+}
+
+static int
+awaitSpeechResponse (SpeechDriverThread *sdt, int timeout) {
+  return asyncAwaitCondition(timeout, testSpeechResponseReceived, sdt);
+}
+
+static void sendSpeechRequest (SpeechDriverThread *sdt);
+
+static void
+handleSpeechMessage (SpeechDriverThread *sdt, SpeechMessage *msg) {
+  logSpeechMessage(msg, "handling");
+
+  if (msg) {
+    switch (msg->type) {
+      case MSG_REQUEST_FINISHED:
+        setIntegerResponse(sdt, msg->arguments.requestFinished.result);
+        sendSpeechRequest(sdt);
+        break;
+
+      case MSG_SPEECH_FINISHED: {
+        SpeechSynthesizer *spk = sdt->speechSynthesizer;
+        SetSpeechFinishedMethod *setFinished = spk->setFinished;
+
+        if (setFinished) setFinished(spk);
+        break;
+      }
+
+      case MSG_SPEECH_LOCATION: {
+        SpeechSynthesizer *spk = sdt->speechSynthesizer;
+        SetSpeechLocationMethod *setLocation = spk->setLocation;
+
+        if (setLocation) setLocation(spk, msg->arguments.speechLocation.location);
+        break;
+      }
+
+      default:
+        logMessage(LOG_CATEGORY(SPEECH_EVENTS), "unimplemented message: %u", msg->type);
+        break;
+    }
+
+    free(msg);
+  }
+}
+
+static int
+sendSpeechMessage (SpeechDriverThread *sdt, SpeechMessage *msg) {
+  logSpeechMessage(msg, "sending");
+
+#ifdef GOT_PTHREADS
+  return asyncSignalEvent(sdt->messageEvent, msg);
+#else /* GOT_PTHREADS */
+  handleSpeechMessage(sdt, msg);
+  return 1;
+#endif /* GOT_PTHREADS */
+}
+
+static SpeechMessage *
+newSpeechMessage (SpeechMessageType type, SpeechDatum *data) {
+  SpeechMessage *msg;
+  size_t size = sizeof(*msg) + getSpeechDataSize(data);
+
+  if ((msg = malloc(size))) {
+    memset(msg, 0, sizeof(*msg));
+    msg->type = type;
+    moveSpeechData(msg->data, data);
+    return msg;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+speechMessage_requestFinished (
+  SpeechDriverThread *sdt,
+  int result
+) {
+  SpeechMessage *msg;
+
+  if ((msg = newSpeechMessage(MSG_REQUEST_FINISHED, NULL))) {
+    msg->arguments.requestFinished.result = result;
+    if (sendSpeechMessage(sdt, msg)) return 1;
+
+    free(msg);
+  }
+
+  return 0;
+}
+
+int
+speechMessage_speechFinished (
+  SpeechDriverThread *sdt
+) {
+  SpeechMessage *msg;
+
+  if ((msg = newSpeechMessage(MSG_SPEECH_FINISHED, NULL))) {
+    if (sendSpeechMessage(sdt, msg)) return 1;
+
+    free(msg);
+  }
+
+  return 0;
+}
+
+int
+speechMessage_speechLocation (
+  SpeechDriverThread *sdt,
+  int location
+) {
+  SpeechMessage *msg;
+
+  if ((msg = newSpeechMessage(MSG_SPEECH_LOCATION, NULL))) {
+    msg->arguments.speechLocation.location = location;
+    if (sendSpeechMessage(sdt, msg)) return 1;
+
+    free(msg);
+  }
+
+  return 0;
+}
+
+static int
+sendIntegerResponse (SpeechDriverThread *sdt, int result) {
+  return speechMessage_requestFinished(sdt, result);
+}
+
+static void
+handleSpeechRequest (SpeechDriverThread *sdt, SpeechRequest *req) {
+  SpeechSynthesizer *spk = sdt->speechSynthesizer;
+
+  logSpeechRequest(req, "handling");
+
+  if (req) {
+    switch (req->type) {
+      case REQ_SAY_TEXT: {
+        SayOptions options = req->arguments.sayText.options;
+        int restorePitch = 0;
+        int restorePunctuation = 0;
+
+        if (options & SAY_OPT_MUTE_FIRST) speech->mute(spk);
+
+        if (options & SAY_OPT_HIGHER_PITCH) {
+          if (spk->setPitch) {
+            unsigned char pitch = prefs.speechPitch + 7;
+
+            if (pitch > SPK_PITCH_MAXIMUM) pitch = SPK_PITCH_MAXIMUM;
+
+            if (pitch != prefs.speechPitch) {
+              spk->setPitch(spk, pitch);
+              restorePitch = 1;
+            }
+          }
+        }
+
+        if (options & SAY_OPT_ALL_PUNCTUATION) {
+          if (spk->setPunctuation) {
+            unsigned char punctuation = SPK_PUNCTUATION_ALL;
+
+            if (punctuation != prefs.speechPunctuation) {
+              spk->setPunctuation(spk, punctuation);
+              restorePunctuation = 1;
+            }
+          }
+        }
+
+        speech->say(spk,
+          req->arguments.sayText.text, req->arguments.sayText.length,
+          req->arguments.sayText.count, req->arguments.sayText.attributes
+        );
+
+        if (restorePunctuation) spk->setPunctuation(spk, prefs.speechPunctuation);
+        if (restorePitch) spk->setPitch(spk, prefs.speechPitch);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_MUTE_SPEECH: {
+        speech->mute(spk);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_DRAIN_SPEECH: {
+        spk->drain(spk);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_SET_VOLUME: {
+        spk->setVolume(spk, req->arguments.setVolume.setting);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_SET_RATE: {
+        spk->setRate(spk, req->arguments.setRate.setting);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_SET_PITCH: {
+        spk->setPitch(spk, req->arguments.setPitch.setting);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      case REQ_SET_PUNCTUATION: {
+        spk->setPunctuation(spk, req->arguments.setPunctuation.setting);
+
+        sendIntegerResponse(sdt, 1);
+        break;
+      }
+
+      default:
+        logMessage(LOG_CATEGORY(SPEECH_EVENTS), "unimplemented request: %u", req->type);
+        sendIntegerResponse(sdt, 0);
+        break;
+    }
+
+    free(req);
+  } else {
+    setThreadState(sdt, THD_STOPPING);
+    sendIntegerResponse(sdt, 1);
+  }
+}
+
+typedef struct {
+  SpeechRequestType const type;
+} TestSpeechRequestData;
+
+static int
+testSpeechRequest (const void *item, void *data) {
+  const SpeechRequest *req = item;
+  const TestSpeechRequestData *tsr = data;
+
+  return req->type == tsr->type;
+}
+
+static Element *
+findSpeechRequestElement (SpeechDriverThread *sdt, SpeechRequestType type) {
+  TestSpeechRequestData tsr = {
+    .type = type
+  };
+
+  if (!testThreadValidity(sdt)) return NULL;
+  return findElement(sdt->requestQueue, testSpeechRequest, &tsr);
+}
+
+static void
+removeSpeechRequests (SpeechDriverThread *sdt, SpeechRequestType type) {
+  Element *element;
+
+  while ((element = findSpeechRequestElement(sdt, type))) deleteElement(element);
+}
+
+static void
+muteSpeechRequestQueue (SpeechDriverThread *sdt) {
+  removeSpeechRequests(sdt, REQ_SAY_TEXT);
+  removeSpeechRequests(sdt, REQ_MUTE_SPEECH);
+}
+
+static void
+sendSpeechRequest (SpeechDriverThread *sdt) {
+  while (getQueueSize(sdt->requestQueue) > 0) {
+    SpeechRequest *req = dequeueItem(sdt->requestQueue);
+
+    logSpeechRequest(req, "sending");
+    setResponsePending(sdt);
+
+#ifdef GOT_PTHREADS
+    if (!asyncSignalEvent(sdt->requestEvent, req)) {
+      if (req) free(req);
+      setIntegerResponse(sdt, 0);
+      continue;
+    }
+#else /* GOT_PTHREADS */
+    handleSpeechRequest(sdt, req);
+#endif /* GOT_PTHREADS */
+
+    break;
+  }
+}
+
+static int
+enqueueSpeechRequest (SpeechDriverThread *sdt, SpeechRequest *req) {
+  if (testThreadValidity(sdt)) {
+    logSpeechRequest(req, "enqueuing");
+
+    if (enqueueItem(sdt->requestQueue, req)) {
+      if (sdt->response.type != RSP_PENDING) {
+        if (getQueueSize(sdt->requestQueue) == 1) {
+          sendSpeechRequest(sdt);
+        }
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static SpeechRequest *
+newSpeechRequest (SpeechRequestType type, SpeechDatum *data) {
+  SpeechRequest *req;
+  size_t size = sizeof(*req) + getSpeechDataSize(data);
+
+  if ((req = malloc(size))) {
+    memset(req, 0, sizeof(*req));
+    req->type = type;
+    moveSpeechData(req->data, data);
+    return req;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+int
+speechRequest_sayText (
+  SpeechDriverThread *sdt,
+  const char *text, size_t length,
+  size_t count, const unsigned char *attributes,
+  SayOptions options
+) {
+  SpeechRequest *req;
+
+  BEGIN_SPEECH_DATA
+    {.address=text, .size=length+1},
+    {.address=attributes, .size=count},
+  END_SPEECH_DATA
+
+  if ((req = newSpeechRequest(REQ_SAY_TEXT, data))) {
+    req->arguments.sayText.text = data[0].address;
+    req->arguments.sayText.length = length;
+    req->arguments.sayText.count = count;
+    req->arguments.sayText.attributes = data[1].address;
+    req->arguments.sayText.options = options;
+
+    if (options & SAY_OPT_MUTE_FIRST) muteSpeechRequestQueue(sdt);
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_muteSpeech (
+  SpeechDriverThread *sdt
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_MUTE_SPEECH, NULL))) {
+    muteSpeechRequestQueue(sdt);
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_drainSpeech (
+  SpeechDriverThread *sdt
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_DRAIN_SPEECH, NULL))) {
+    if (enqueueSpeechRequest(sdt, req)) {
+      awaitSpeechResponse(sdt, SPEECH_RESPONSE_WAIT_TIMEOUT);
+      return 1;
+    }
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_setVolume (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_SET_VOLUME, NULL))) {
+    req->arguments.setVolume.setting = setting;
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_setRate (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_SET_RATE, NULL))) {
+    req->arguments.setRate.setting = setting;
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_setPitch (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_SET_PITCH, NULL))) {
+    req->arguments.setPitch.setting = setting;
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+int
+speechRequest_setPunctuation (
+  SpeechDriverThread *sdt,
+  SpeechPunctuation setting
+) {
+  SpeechRequest *req;
+
+  if ((req = newSpeechRequest(REQ_SET_PUNCTUATION, NULL))) {
+    req->arguments.setPunctuation.setting = setting;
+    if (enqueueSpeechRequest(sdt, req)) return 1;
+
+    free(req);
+  }
+
+  return 0;
+}
+
+static void
+setThreadReady (SpeechDriverThread *sdt) {
+  setThreadState(sdt, THD_READY);
+  sendIntegerResponse(sdt, 1);
+}
+
+static int
+startSpeechDriver (SpeechDriverThread *sdt) {
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "starting driver");
+  return speech->construct(sdt->speechSynthesizer, sdt->driverParameters);
+}
+
+static void
+stopSpeechDriver (SpeechDriverThread *sdt) {
+  logMessage(LOG_CATEGORY(SPEECH_EVENTS), "stopping driver");
+  speech->destruct(sdt->speechSynthesizer);
+}
+
+#ifdef GOT_PTHREADS
+ASYNC_CONDITION_TESTER(testSpeechDriverThreadStopping) {
+  SpeechDriverThread *sdt = data;
+
+  return sdt->threadState == THD_STOPPING;
+}
+
+ASYNC_EVENT_CALLBACK(handleSpeechMessageEvent) {
+  SpeechDriverThread *sdt = parameters->eventData;
+  SpeechMessage *msg = parameters->signalData;
+
+  handleSpeechMessage(sdt, msg);
+}
+
+ASYNC_EVENT_CALLBACK(handleSpeechRequestEvent) {
+  SpeechDriverThread *sdt = parameters->eventData;
+  SpeechRequest *req = parameters->signalData;
+
+  handleSpeechRequest(sdt, req);
+}
+
+static void
+awaitSpeechDriverThreadTermination (SpeechDriverThread *sdt) {
+  void *result;
+
+  pthread_join(sdt->threadIdentifier, &result);
+}
+
+THREAD_FUNCTION(runSpeechDriverThread) {
+  SpeechDriverThread *sdt = argument;
+
+  setThreadState(sdt, THD_STARTING);
+
+  if ((sdt->requestEvent = asyncNewEvent(handleSpeechRequestEvent, sdt))) {
+    if (startSpeechDriver(sdt)) {
+      setThreadReady(sdt);
+      asyncWaitFor(testSpeechDriverThreadStopping, sdt);
+      stopSpeechDriver(sdt);
+    } else {
+      logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver construction failure");
+    }
+
+    asyncDiscardEvent(sdt->requestEvent);
+    sdt->requestEvent = NULL;
+  } else {
+    logMessage(LOG_CATEGORY(SPEECH_EVENTS), "request event construction failure");
+  }
+
+  {
+    int ok = sdt->threadState == THD_STOPPING;
+
+    sendIntegerResponse(sdt, ok);
+  }
+
+  setThreadState(sdt, THD_FINISHED);
+  return NULL;
+}
+#endif /* GOT_PTHREADS */
+
+static void
+deallocateSpeechRequest (void *item, void *data) {
+  SpeechRequest *req = item;
+
+  logSpeechRequest(req, "unqueuing");
+  free(req);
+}
+
+int
+constructSpeechDriverThread (
+  SpeechSynthesizer *spk,
+  char **parameters
+) {
+  SpeechDriverThread *sdt;
+
+  if ((sdt = malloc(sizeof(*sdt)))) {
+    memset(sdt, 0, sizeof(*sdt));
+    setThreadState(sdt, THD_CONSTRUCTING);
+    setResponsePending(sdt);
+
+    sdt->speechSynthesizer = spk;
+    sdt->driverParameters = parameters;
+
+    if ((sdt->requestQueue = newQueue(deallocateSpeechRequest, NULL))) {
+      spk->driver.thread = sdt;
+
+#ifdef GOT_PTHREADS
+      if ((sdt->messageEvent = asyncNewEvent(handleSpeechMessageEvent, sdt))) {
+        pthread_t threadIdentifier;
+        int createError = createThread("speech-driver",
+                                       &threadIdentifier, NULL,
+                                       runSpeechDriverThread, sdt);
+
+        if (!createError) {
+          sdt->threadIdentifier = threadIdentifier;
+
+          if (awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_START_TIMEOUT)) {
+            if (sdt->response.type == RSP_INTEGER) {
+              if (sdt->response.value.INTEGER) {
+                return 1;
+              }
+            }
+
+            logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread initialization failure");
+            awaitSpeechDriverThreadTermination(sdt);
+          } else {
+            logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread initialization timeout");
+          }
+        } else {
+          logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread creation failure: %s", strerror(createError));
+        }
+
+        asyncDiscardEvent(sdt->messageEvent);
+        sdt->messageEvent = NULL;
+      } else {
+        logMessage(LOG_CATEGORY(SPEECH_EVENTS), "response event construction failure");
+      }
+#else /* GOT_PTHREADS */
+      if (startSpeechDriver(sdt)) {
+        setThreadReady(sdt);
+        return 1;
+      }
+#endif /* GOT_PTHREADS */
+
+      spk->driver.thread = NULL;
+      deallocateQueue(sdt->requestQueue);
+    }
+
+    free(sdt);
+  } else {
+    logMallocError();
+  }
+
+  spk->driver.thread = NULL;
+  return 0;
+}
+
+void
+destroySpeechDriverThread (SpeechSynthesizer *spk) {
+  SpeechDriverThread *sdt = spk->driver.thread;
+
+  deleteElements(sdt->requestQueue);
+
+#ifdef GOT_PTHREADS
+  if (enqueueSpeechRequest(sdt, NULL)) {
+    sdt->isBeingDestroyed = 1;
+    awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_STOP_TIMEOUT);
+
+    setResponsePending(sdt);
+    awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_STOP_TIMEOUT);
+
+    awaitSpeechDriverThreadTermination(sdt);
+  }
+
+  if (sdt->messageEvent) asyncDiscardEvent(sdt->messageEvent);
+#else /* GOT_PTHREADS */
+  stopSpeechDriver(sdt);
+  setThreadState(sdt, THD_FINISHED);
+#endif /* GOT_PTHREADS */
+
+  sdt->speechSynthesizer->driver.thread = NULL;
+  deallocateQueue(sdt->requestQueue);
+  free(sdt);
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
diff --git a/Programs/spk_thread.h b/Programs/spk_thread.h
new file mode 100644
index 0000000..4241b77
--- /dev/null
+++ b/Programs/spk_thread.h
@@ -0,0 +1,87 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_SPK_THREAD
+#define BRLTTY_INCLUDED_SPK_THREAD
+
+#include "spk_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef ENABLE_SPEECH_SUPPORT
+extern int constructSpeechDriverThread (
+  SpeechSynthesizer *spk,
+  char **parameters
+);
+
+extern void destroySpeechDriverThread (
+  SpeechSynthesizer *spk
+);
+
+extern int speechRequest_sayText (
+  SpeechDriverThread *sdt,
+  const char *text, size_t length,
+  size_t count, const unsigned char *attributes,
+  SayOptions options
+);
+
+extern int speechRequest_muteSpeech (
+  SpeechDriverThread *sdt
+);
+
+extern int speechRequest_drainSpeech (
+  SpeechDriverThread *sdt
+);
+
+extern int speechRequest_setVolume (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+);
+
+extern int speechRequest_setRate (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+);
+
+extern int speechRequest_setPitch (
+  SpeechDriverThread *sdt,
+  unsigned char setting
+);
+
+extern int speechRequest_setPunctuation (
+  SpeechDriverThread *sdt,
+  SpeechPunctuation setting
+);
+
+extern int speechMessage_speechFinished (
+  SpeechDriverThread *sdt
+);
+
+extern int speechMessage_speechLocation (
+  SpeechDriverThread *sdt,
+  int location
+);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_SPK_THREAD */
diff --git a/Programs/spktest.c b/Programs/spktest.c
new file mode 100644
index 0000000..5c1189a
--- /dev/null
+++ b/Programs/spktest.c
@@ -0,0 +1,231 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* spktest.c - Test progrm for the speech synthesizer drivers.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+#include "program.h"
+#include "cmdline.h"
+#include "log.h"
+#include "spk.h"
+#include "file.h"
+#include "parse.h"
+#include "async_wait.h"
+
+static char *opt_textString;
+static char *opt_speechVolume;
+static char *opt_speechRate;
+static char *opt_pcmDevice;
+static char *opt_driversDirectory;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "text-string",
+    .letter = 't',
+    .argument = "string",
+    .setting.string = &opt_textString,
+    .description = "Text to be spoken."
+  },
+
+  { .word = "volume",
+    .letter = 'v',
+    .argument = "loudness",
+    .setting.string = &opt_speechVolume,
+    .description = "Floating-point speech volume multiplier."
+  },
+
+  { .word = "rate",
+    .letter = 'r',
+    .argument = "speed",
+    .setting.string = &opt_speechRate,
+    .description = "Floating-point speech rate multiplier."
+  },
+
+  { .word = "device",
+    .letter = 'd',
+    .argument = "device",
+    .setting.string = &opt_pcmDevice,
+    .description = "Digital audio soundcard device specifier."
+  },
+
+  { .word = "drivers-directory",
+    .letter = 'D',
+    .argument = "directory",
+    .setting.string = &opt_driversDirectory,
+    .internal.setting = DRIVERS_DIRECTORY,
+    .internal.adjust = fixInstallPath,
+    .description = "Path to directory for loading drivers."
+  },
+END_OPTION_TABLE(programOptions)
+
+static int
+say (SpeechSynthesizer *spk, const char *string) {
+  if (!sayString(spk, string, 0)) return 0;
+  asyncWait(250);
+  return 1;
+}
+
+static int
+sayLine (const LineHandlerParameters *parameters) {
+  SpeechSynthesizer *spk = parameters->data;
+
+  say(spk, parameters->line.text);
+  return 1;
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+  SpeechSynthesizer spk;
+
+
+  const char *driver = NULL;
+  void *object;
+
+  int speechVolume = SPK_VOLUME_DEFAULT;
+  int speechRate = SPK_RATE_DEFAULT;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "spktest",
+
+      .usage = {
+        .purpose = strtext("Test a speech driver."),
+        .parameters = "[driver [parameter=value ...]]",
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (opt_speechVolume && *opt_speechVolume) {
+    static const int minimum = 0;
+    static const int maximum = SPK_VOLUME_MAXIMUM;
+
+    if (!validateInteger(&speechVolume, opt_speechVolume, &minimum, &maximum)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid volume multiplier", opt_speechVolume);
+      return PROG_EXIT_SYNTAX;
+    }
+  }
+
+  if (opt_speechRate && *opt_speechRate) {
+    static const int minimum = 0;
+    static const int maximum = SPK_RATE_MAXIMUM;
+
+    if (!validateInteger(&speechRate, opt_speechRate, &minimum, &maximum)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid rate multiplier", opt_speechRate);
+      return PROG_EXIT_SYNTAX;
+    }
+  }
+
+  if (argc) {
+    driver = *argv++, --argc;
+  }
+
+  if ((speech = loadSpeechDriver(driver, &object, opt_driversDirectory))) {
+    const char *const *parameterNames = speech->parameters;
+    char **parameterSettings;
+
+    if (!parameterNames) {
+      static const char *const noNames[] = {NULL};
+
+      parameterNames = noNames;
+    }
+
+    {
+      const char *const *name = parameterNames;
+      unsigned int count;
+      char **setting;
+
+      while (*name) name += 1;
+      count = name - parameterNames;
+
+      if (!(parameterSettings = malloc((count + 1) * sizeof(*parameterSettings)))) {
+        logMallocError();
+        return PROG_EXIT_FATAL;
+      }
+
+      setting = parameterSettings;
+      while (count--) *setting++ = "";
+      *setting = NULL;
+    }
+
+    while (argc) {
+      char *assignment = *argv++;
+      int ok = 0;
+      char *delimiter = strchr(assignment, '=');
+
+      if (!delimiter) {
+        logMessage(LOG_ERR, "missing speech driver parameter value: %s", assignment);
+      } else if (delimiter == assignment) {
+        logMessage(LOG_ERR, "missing speech driver parameter name: %s", assignment);
+      } else {
+        size_t nameLength = delimiter - assignment;
+        const char *const *name = parameterNames;
+
+        while (*name) {
+          if (strncasecmp(assignment, *name, nameLength) == 0) {
+            parameterSettings[name - parameterNames] = delimiter + 1;
+            ok = 1;
+            break;
+          }
+
+          name += 1;
+        }
+
+        if (!ok) logMessage(LOG_ERR, "invalid speech driver parameter: %s", assignment);
+      }
+
+      if (!ok) return PROG_EXIT_SYNTAX;
+      argc -= 1;
+    }
+
+    constructSpeechSynthesizer(&spk);
+    identifySpeechDriver(speech, 0);		/* start-up messages */
+
+    if (startSpeechDriverThread(&spk, parameterSettings)) {
+      setSpeechVolume(&spk, speechVolume, 0);
+      setSpeechRate(&spk, speechRate, 0);
+
+      if (opt_textString && *opt_textString) {
+        say(&spk, opt_textString);
+      } else {
+        processLines(stdin, sayLine, (void *)&spk);
+      }
+
+      drainSpeech(&spk);
+      stopSpeechDriverThread(&spk);
+      exitStatus = PROG_EXIT_SUCCESS;
+    } else {
+      logMessage(LOG_ERR, "can't initialize speech driver");
+      exitStatus = PROG_EXIT_FATAL;
+    }
+  } else {
+    logMessage(LOG_ERR, "can't load speech driver");
+    exitStatus = PROG_EXIT_FATAL;
+  }
+
+  return exitStatus;
+}
diff --git a/Programs/status.c b/Programs/status.c
new file mode 100644
index 0000000..29f2080
--- /dev/null
+++ b/Programs/status.c
@@ -0,0 +1,429 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "status.h"
+#include "timing.h"
+#include "update.h"
+#include "brl_dots.h"
+#include "brl_utils.h"
+#include "scr_special.h"
+#include "core.h"
+#include "prefs.h"
+#include "ttb.h"
+
+static void
+renderCharacter (unsigned char *cell, char character) {
+  *cell = convertCharacterToDots(textTable, character);
+}
+
+static void
+renderDigitUpper (unsigned char *cell, int digit) {
+  *cell |= portraitDigits[digit % 10];
+}
+
+static void
+renderDigitLower (unsigned char *cell, int digit) {
+  *cell |= toLowerDigit(portraitDigits[digit % 10]);
+}
+
+static void
+renderNumberVertical (unsigned char *cell, int number) {
+  renderDigitUpper(cell, number/10);
+  renderDigitLower(cell, number);
+}
+
+static void
+renderNumberUpper2 (unsigned char *cells, int number) {
+  renderDigitUpper(&cells[0], number/10);
+  renderDigitUpper(&cells[1], number);
+}
+
+static void
+renderNumberLower2 (unsigned char *cells, int number) {
+  renderDigitLower(&cells[0], number/10);
+  renderDigitLower(&cells[1], number);
+}
+
+static void
+renderNumbers2 (unsigned char *cells, int upper, int lower) {
+  renderNumberUpper2(&cells[0], upper);
+  renderNumberLower2(&cells[0], lower);
+}
+
+static void
+renderNumberUpper3 (unsigned char *cells, int number) {
+  renderDigitUpper(&cells[0], number/100);
+  renderDigitUpper(&cells[1], number/10);
+  renderDigitUpper(&cells[2], number);
+}
+
+static void
+renderNumberLower3 (unsigned char *cells, int number) {
+  renderDigitLower(&cells[0], number/100);
+  renderDigitLower(&cells[1], number/10);
+  renderDigitLower(&cells[2], number);
+}
+
+static void
+renderNumbers3 (unsigned char *cells, int upper, int lower) {
+  renderNumberUpper3(&cells[0], upper);
+  renderNumberLower3(&cells[0], lower);
+}
+
+static void
+renderCoordinates2 (unsigned char *cells, int column, int row) {
+  renderNumbers2(&cells[0], row, column);
+}
+
+static void
+renderCoordinates3 (unsigned char *cells, int column, int row) {
+  renderNumbers3(&cells[0], row, column);
+}
+
+static void
+renderCoordinatesAlphabetic (unsigned char *cell, int column, int row) {
+  /* the coordinates are presented as an underlined letter as the Alva DOS TSR */
+  if (!SCR_COORDINATES_OK(column, row)) {
+    *cell = convertCharacterToDots(textTable, WC_C('z'));
+  } else {
+    const int32_t height = 25;
+    const int32_t frequency = row / height;
+
+    if (frequency) {
+      const int32_t interval = NSECS_PER_SEC / (frequency * 2);
+      TimeValue time;
+
+      getMonotonicTime(&time);
+      scheduleUpdateIn("alva status field",
+                       (((interval - (time.nanoseconds % interval)) / NSECS_PER_MSEC) + 1));
+
+      if (!((time.nanoseconds / interval) % 2)) {
+        *cell = 0;
+        return;
+      }
+    }
+
+    *cell = convertCharacterToDots(textTable, ((row % height) + WC_C('a')))
+          | ((column / textCount) << 6);
+  }
+}
+
+typedef void (*RenderStatusField) (unsigned char *cells);
+
+static void
+renderStatusField_cursorColumn (unsigned char *cells) {
+  renderNumberVertical(cells, SCR_COLUMN_NUMBER(scr.posx));
+}
+
+static void
+renderStatusField_cursorRow (unsigned char *cells) {
+  renderNumberVertical(cells, SCR_ROW_NUMBER(scr.posy));
+}
+
+static void
+renderStatusField_windowColumn (unsigned char *cells) {
+  renderNumberVertical(cells, SCR_COLUMN_NUMBER(ses->winx));
+}
+
+static void
+renderStatusField_windowRow (unsigned char *cells) {
+  renderNumberVertical(cells, SCR_ROW_NUMBER(ses->winy));
+}
+
+static void
+renderStatusField_cursorCoordinates2 (unsigned char *cells) {
+  renderCoordinates2(cells, SCR_COLUMN_NUMBER(scr.posx), SCR_ROW_NUMBER(scr.posy));
+}
+
+static void
+renderStatusField_windowCoordinates2 (unsigned char *cells) {
+  renderCoordinates2(cells, SCR_COLUMN_NUMBER(ses->winx), SCR_ROW_NUMBER(ses->winy));
+}
+
+static void
+renderStatusField_cursorCoordinates3 (unsigned char *cells) {
+  renderCoordinates3(cells, SCR_COLUMN_NUMBER(scr.posx), SCR_ROW_NUMBER(scr.posy));
+}
+
+static void
+renderStatusField_windowCoordinates3 (unsigned char *cells) {
+  renderCoordinates3(cells, SCR_COLUMN_NUMBER(ses->winx), SCR_ROW_NUMBER(ses->winy));
+}
+
+static void
+renderStatusField_cursorAndWindowColumn2 (unsigned char *cells) {
+  renderNumbers2(cells,
+    SCR_COLUMN_NUMBER(scr.posx),
+    SCR_COLUMN_NUMBER(ses->winx)
+  );
+}
+
+static void
+renderStatusField_cursorAndWindowRow2 (unsigned char *cells) {
+  renderNumbers2(cells,
+    SCR_ROW_NUMBER(scr.posy),
+    SCR_ROW_NUMBER(ses->winy)
+  );
+}
+
+static void
+renderStatusField_cursorAndWindowColumn3 (unsigned char *cells) {
+  renderNumbers3(cells,
+    SCR_COLUMN_NUMBER(scr.posx),
+    SCR_COLUMN_NUMBER(ses->winx)
+  );
+}
+
+static void
+renderStatusField_cursorAndWindowRow3 (unsigned char *cells) {
+  renderNumbers3(cells,
+    SCR_ROW_NUMBER(scr.posy),
+    SCR_ROW_NUMBER(ses->winy)
+  );
+}
+
+static void
+renderStatusField_screenNumber (unsigned char *cells) {
+  char character =
+    isSpecialScreen(SCR_HELP)  ? 'h':
+    isSpecialScreen(SCR_MENU)  ? 'm':
+    isSpecialScreen(SCR_FROZEN)? 'f':
+    0;
+
+  if (character) {
+    renderCharacter(cells, character);
+  } else {
+    renderNumberVertical(cells, scr.number);
+  }
+}
+
+static void
+renderStatusField_stateDots (unsigned char *cells) {
+  cells[0] = (isSpecialScreen(SCR_FROZEN)  ? BRL_DOT_1: 0)
+           | (prefs.showScreenCursor       ? BRL_DOT_4: 0)
+           | (ses->displayMode             ? BRL_DOT_2: 0)
+           | (prefs.showAttributes         ? BRL_DOT_5: 0)
+           | (prefs.alertTunes             ? BRL_DOT_3: 0)
+           | (prefs.brailleTypingMode      ? BRL_DOT_6: 0)
+           | (ses->trackScreenCursor       ? BRL_DOT_7: 0)
+           | (prefs.brailleKeyboardEnabled ? BRL_DOT_8: 0)
+           ;
+}
+
+static void
+renderStatusField_stateLetter (unsigned char *cells) {
+  renderCharacter(cells,
+    ses->displayMode            ? WC_C('a'):
+    isSpecialScreen(SCR_HELP)   ? WC_C('h'):
+    isSpecialScreen(SCR_MENU)   ? WC_C('m'):
+    isSpecialScreen(SCR_FROZEN) ? WC_C('f'):
+    ses->trackScreenCursor      ? WC_C('t'):
+    WC_C(' ')
+  );
+}
+
+static void
+renderStatusField_time (unsigned char *cells) {
+  TimeValue value;
+  getCurrentTime(&value);
+  scheduleUpdateIn("time status field", millisecondsTillNextMinute(&value));
+
+  TimeComponents components;
+  expandTimeValue(&value, &components);
+  renderNumbers2(cells, components.hour, components.minute);
+}
+
+static void
+renderStatusField_alphabeticWindowCoordinates (unsigned char *cells) {
+  renderCoordinatesAlphabetic(cells, ses->winx, ses->winy);
+}
+
+static void
+renderStatusField_alphabeticCursorCoordinates (unsigned char *cells) {
+  renderCoordinatesAlphabetic(cells, scr.posx, scr.posy);
+}
+
+static void
+renderStatusField_generic (unsigned char *cells) {
+  cells[GSC_FIRST] = GSC_MARKER;
+  cells[gscBrailleWindowColumn] = SCR_COLUMN_NUMBER(ses->winx);
+  cells[gscBrailleWindowRow] = SCR_ROW_NUMBER(ses->winy);
+  cells[gscScreenCursorColumn] = SCR_COLUMN_NUMBER(scr.posx);
+  cells[gscScreenCursorRow] = SCR_ROW_NUMBER(scr.posy);
+  cells[gscScreenNumber] = scr.number;
+  cells[gscFrozenScreen] = isSpecialScreen(SCR_FROZEN);
+  cells[gscDisplayMode] = ses->displayMode;
+  cells[gscSixDotComputerBraille] = isSixDotComputerBraille();
+  cells[gscContractedBraille] = isContractedBraille();
+  cells[gscSlidingBrailleWindow] = prefs.slidingBrailleWindow;
+  cells[gscSkipIdenticalLines] = prefs.skipIdenticalLines;
+  cells[gscSkipBlankBrailleWindows] = prefs.skipBlankBrailleWindows;
+  cells[gscShowScreenCursor] = prefs.showScreenCursor;
+  cells[gscHideScreenCursor] = ses->hideScreenCursor;
+  cells[gscTrackScreenCursor] = ses->trackScreenCursor;
+  cells[gscScreenCursorStyle] = prefs.screenCursorStyle;
+  cells[gscBlinkingScreenCursor] = prefs.blinkingScreenCursor;
+  cells[gscShowAttributes] = prefs.showAttributes;
+  cells[gscBlinkingAttributes] = prefs.blinkingAttributes;
+  cells[gscBlinkingCapitals] = prefs.blinkingCapitals;
+  cells[gscAlertTunes] = prefs.alertTunes;
+  cells[gscAutorepeat] = prefs.autorepeatEnabled;
+  cells[gscAutospeak] = prefs.autospeak;
+  cells[gscBrailleTypingMode] = prefs.brailleTypingMode;
+}
+
+static void
+renderStatusField_space (unsigned char *cells) {
+  cells[0] = 0;
+}
+
+typedef struct {
+  RenderStatusField render;
+  unsigned char length;
+} StatusFieldEntry;
+
+static const StatusFieldEntry statusFieldTable[] = {
+  [sfEnd] = {
+    .render = NULL,
+    .length = 0
+  }
+  ,
+  [sfWindowCoordinates2] = {
+    .render = renderStatusField_windowCoordinates2,
+    .length = 2
+  }
+  ,
+  [sfWindowColumn] = {
+    .render = renderStatusField_windowColumn,
+    .length = 1
+  }
+  ,
+  [sfWindowRow] = {
+    .render = renderStatusField_windowRow,
+    .length = 1
+  }
+  ,
+  [sfCursorCoordinates2] = {
+    .render = renderStatusField_cursorCoordinates2,
+    .length = 2
+  }
+  ,
+  [sfCursorColumn] = {
+    .render = renderStatusField_cursorColumn,
+    .length = 1
+  }
+  ,
+  [sfCursorRow] = {
+    .render = renderStatusField_cursorRow,
+    .length = 1
+  }
+  ,
+  [sfCursorAndWindowColumn2] = {
+    .render = renderStatusField_cursorAndWindowColumn2,
+    .length = 2
+  }
+  ,
+  [sfCursorAndWindowRow2] = {
+    .render = renderStatusField_cursorAndWindowRow2,
+    .length = 2
+  }
+  ,
+  [sfScreenNumber] = {
+    .render = renderStatusField_screenNumber,
+    .length = 1
+  }
+  ,
+  [sfStateDots] = {
+    .render = renderStatusField_stateDots,
+    .length = 1
+  }
+  ,
+  [sfStateLetter] = {
+    .render = renderStatusField_stateLetter,
+    .length = 1
+  }
+  ,
+  [sfTime] = {
+    .render = renderStatusField_time,
+    .length = 2
+  }
+  ,
+  [sfAlphabeticWindowCoordinates] = {
+    .render = renderStatusField_alphabeticWindowCoordinates,
+    .length = 1
+  }
+  ,
+  [sfAlphabeticCursorCoordinates] = {
+    .render = renderStatusField_alphabeticCursorCoordinates,
+    .length = 1
+  }
+  ,
+  [sfGeneric] = {
+    .render = renderStatusField_generic,
+    .length = GSC_COUNT
+  },
+
+  [sfCursorCoordinates3] = {
+    .render = renderStatusField_cursorCoordinates3,
+    .length = 3
+  }
+  ,
+  [sfWindowCoordinates3] = {
+    .render = renderStatusField_windowCoordinates3,
+    .length = 3
+  }
+  ,
+  [sfCursorAndWindowColumn3] = {
+    .render = renderStatusField_cursorAndWindowColumn3,
+    .length = 3
+  }
+  ,
+  [sfCursorAndWindowRow3] = {
+    .render = renderStatusField_cursorAndWindowRow3,
+    .length = 3
+  }
+  ,
+  [sfSpace] = {
+    .render = renderStatusField_space,
+    .length = 1
+  },
+};
+
+static const unsigned int statusFieldCount = ARRAY_COUNT(statusFieldTable);
+
+unsigned int
+getStatusFieldsLength (const unsigned char *fields) {
+  unsigned int length = 0;
+  while (*fields != sfEnd) length += statusFieldTable[*fields++].length;
+  return length;
+}
+
+void
+renderStatusFields (const unsigned char *fields, unsigned char *cells) {
+  while (*fields != sfEnd) {
+    StatusField field = *fields++;
+
+    if (field < statusFieldCount) {
+      const StatusFieldEntry *sf = &statusFieldTable[field];
+      sf->render(cells);
+      cells += sf->length;
+    }
+  }
+}
diff --git a/Programs/status.h b/Programs/status.h
new file mode 100644
index 0000000..d465248
--- /dev/null
+++ b/Programs/status.h
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_STATUS
+#define BRLTTY_INCLUDED_STATUS
+
+#include "status_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern unsigned int getStatusFieldsLength (const unsigned char *fields);
+extern void renderStatusFields (const unsigned char *fields, unsigned char *cells);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_STATUS */
diff --git a/Programs/sys_darwin.c b/Programs/sys_darwin.c
new file mode 100644
index 0000000..e25f2ce
--- /dev/null
+++ b/Programs/sys_darwin.c
@@ -0,0 +1,23 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "system.h"
diff --git a/Programs/sys_freebsd.c b/Programs/sys_freebsd.c
new file mode 100644
index 0000000..7d109c7
--- /dev/null
+++ b/Programs/sys_freebsd.c
@@ -0,0 +1,27 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "system.h"
diff --git a/Programs/sys_kfreebsd.c b/Programs/sys_kfreebsd.c
new file mode 100644
index 0000000..7d109c7
--- /dev/null
+++ b/Programs/sys_kfreebsd.c
@@ -0,0 +1,27 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "system.h"
diff --git a/Programs/sys_netbsd.c b/Programs/sys_netbsd.c
new file mode 100644
index 0000000..03e4f9f
--- /dev/null
+++ b/Programs/sys_netbsd.c
@@ -0,0 +1,28 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <sys/time.h>
+
+#include "system.h"
diff --git a/Programs/sys_openbsd.c b/Programs/sys_openbsd.c
new file mode 100644
index 0000000..03e4f9f
--- /dev/null
+++ b/Programs/sys_openbsd.c
@@ -0,0 +1,28 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <sys/time.h>
+
+#include "system.h"
diff --git a/Programs/sys_solaris.c b/Programs/sys_solaris.c
new file mode 100644
index 0000000..b041c87
--- /dev/null
+++ b/Programs/sys_solaris.c
@@ -0,0 +1,26 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "system.h"
diff --git a/Programs/syscalls_linux.h b/Programs/syscalls_linux.h
new file mode 100644
index 0000000..717c9fb
--- /dev/null
+++ b/Programs/syscalls_linux.h
@@ -0,0 +1,421 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <linux/unistd.h>
+
+SCF_BEGIN_VALUES(systemCall)
+  #ifdef __NR_accept
+  {__NR_accept},
+  #endif /* __NR_accept */
+  
+  #ifdef __NR_access
+  {__NR_access},
+  #endif /* __NR_access */
+  
+  #ifdef __NR_arch_prctl
+  {__NR_arch_prctl},
+  #endif /* __NR_arch_prctl */
+  
+  #ifdef __NR_bind
+  {__NR_bind},
+  #endif /* __NR_bind */
+  
+  #ifdef __NR_brk
+  {__NR_brk},
+  #endif /* __NR_brk */
+  
+  #ifdef __NR_chdir
+  {__NR_chdir},
+  #endif /* __NR_chdir */
+  
+  #ifdef __NR_chmod
+  {__NR_chmod},
+  #endif /* __NR_chmod */
+  
+  #ifdef __NR_clock_getres
+  {__NR_clock_getres},
+  #endif /* __NR_clock_getres */
+  
+  #ifdef __NR_clock_gettime
+  {__NR_clock_gettime},
+  #endif /* __NR_clock_gettime */
+  
+  #ifdef __NR_clock_nanosleep
+  {__NR_clock_nanosleep},
+  #endif /* __NR_clock_nanosleep */
+  
+  #ifdef __NR_clone
+  {__NR_clone},
+  #endif /* __NR_clone */
+  
+  #ifdef __NR_close
+  {__NR_close},
+  #endif /* __NR_close */
+  
+  #ifdef __NR_connect
+  {__NR_connect},
+  #endif /* __NR_connect */
+  
+  #ifdef __NR_dup
+  {__NR_dup},
+  #endif /* __NR_dup */
+  
+  #ifdef __NR_dup2
+  {__NR_dup2},
+  #endif /* __NR_dup2 */
+  
+  #ifdef __NR_eventfd2
+  {__NR_eventfd2},
+  #endif /* __NR_eventfd2 */
+  
+  #ifdef __NR_execve
+  {__NR_execve},
+  #endif /* __NR_execve */
+  
+  #ifdef __NR_exit
+  {__NR_exit},
+  #endif /* __NR_exit */
+  
+  #ifdef __NR_exit_group
+  {__NR_exit_group},
+  #endif /* __NR_exit_group */
+  
+  #ifdef __NR_fchdir
+  {__NR_fchdir},
+  #endif /* __NR_fchdir */
+  
+  #ifdef __NR_fcntl
+  {__NR_fcntl},
+  #endif /* __NR_fcntl */
+  
+  #ifdef __NR_fork
+  {__NR_fork},
+  #endif /* __NR_fork */
+  
+  #ifdef __NR_fstat
+  {__NR_fstat},
+  #endif /* __NR_fstat */
+  
+  #ifdef __NR_ftruncate
+  {__NR_ftruncate},
+  #endif /* __NR_ftruncate */
+  
+  #ifdef __NR_futex
+  {__NR_futex},
+  #endif /* __NR_futex */
+  
+  #ifdef __NR_getcwd
+  {__NR_getcwd},
+  #endif /* __NR_getcwd */
+  
+  #ifdef __NR_getdents
+  {__NR_getdents},
+  #endif /* __NR_getdents */
+  
+  #ifdef __NR_getdents64
+  {__NR_getdents64},
+  #endif /* __NR_getdents64 */
+  
+  #ifdef __NR_getegid
+  {__NR_getegid},
+  #endif /* __NR_getegid */
+  
+  #ifdef __NR_geteuid
+  {__NR_geteuid},
+  #endif /* __NR_geteuid */
+  
+  #ifdef __NR_getgid
+  {__NR_getgid},
+  #endif /* __NR_getgid */
+  
+  #ifdef __NR_getpeername
+  {__NR_getpeername},
+  #endif /* __NR_getpeername */
+  
+  #ifdef __NR_getpgrp
+  {__NR_getpgrp},
+  #endif /* __NR_getpgrp */
+  
+  #ifdef __NR_getpid
+  {__NR_getpid},
+  #endif /* __NR_getpid */
+  
+  #ifdef __NR_getppid
+  {__NR_getppid},
+  #endif /* __NR_getppid */
+  
+  #ifdef __NR_getrandom
+  {__NR_getrandom},
+  #endif /* __NR_getrandom */
+  
+  #ifdef __NR_getresgid
+  {__NR_getresgid},
+  #endif /* __NR_getresgid */
+  
+  #ifdef __NR_getresuid
+  {__NR_getresuid},
+  #endif /* __NR_getresuid */
+  
+  #ifdef __NR_get_robust_list
+  {__NR_get_robust_list},
+  #endif /* __NR_get_robust_list */
+  
+  #ifdef __NR_getsockname
+  {__NR_getsockname},
+  #endif /* __NR_getsockname */
+  
+  #ifdef __NR_getsockopt
+  {__NR_getsockopt},
+  #endif /* __NR_getsockopt */
+  
+  #ifdef __NR_gettid
+  {__NR_gettid},
+  #endif /* __NR_gettid */
+  
+  #ifdef __NR_getuid
+  {__NR_getuid},
+  #endif /* __NR_getuid */
+  
+  #ifdef __NR_ioctl
+  {__NR_ioctl},
+  #endif /* __NR_ioctl */
+  
+  #ifdef __NR_kill
+  {__NR_kill},
+  #endif /* __NR_kill */
+  
+  #ifdef __NR_link
+  {__NR_link},
+  #endif /* __NR_link */
+  
+  #ifdef __NR_listen
+  {__NR_listen},
+  #endif /* __NR_listen */
+  
+  #ifdef __NR_lseek
+  {__NR_lseek},
+  #endif /* __NR_lseek */
+  
+  #ifdef __NR_madvise
+  {__NR_madvise},
+  #endif /* __NR_madvise */
+  
+  #ifdef __NR_memfd_create
+  {__NR_memfd_create},
+  #endif /* __NR_memfd_create */
+  
+  #ifdef __NR_mkdir
+  {__NR_mkdir},
+  #endif /* __NR_mkdir */
+  
+  #ifdef __NR_mknod
+  {__NR_mknod},
+  #endif /* __NR_mknod */
+  
+  #ifdef __NR_mmap
+  {__NR_mmap},
+  #endif /* __NR_mmap */
+  
+  #ifdef __NR_mprotect
+  {__NR_mprotect},
+  #endif /* __NR_mprotect */
+  
+  #ifdef __NR_mremap
+  {__NR_mremap},
+  #endif /* __NR_mremap */
+  
+  #ifdef __NR_munmap
+  {__NR_munmap},
+  #endif /* __NR_munmap */
+  
+  #ifdef __NR_nanosleep
+  {__NR_nanosleep},
+  #endif /* __NR_nanosleep */
+  
+  #ifdef __NR_open
+  {__NR_open},
+  #endif /* __NR_open */
+  
+  #ifdef __NR_openat
+  {__NR_openat},
+  #endif /* __NR_openat */
+  
+  #ifdef __NR_pipe
+  {__NR_pipe},
+  #endif /* __NR_pipe */
+  
+  #ifdef __NR_pipe2
+  {__NR_pipe2},
+  #endif /* __NR_pipe2 */
+  
+  #ifdef __NR_poll
+  {__NR_poll},
+  #endif /* __NR_poll */
+  
+  #ifdef __NR_ppoll
+  {__NR_ppoll},
+  #endif /* __NR_ppoll */
+  
+  #ifdef __NR_prctl
+  {__NR_prctl},
+  #endif /* __NR_prctl */
+  
+  #ifdef __NR_pread64
+  {__NR_pread64},
+  #endif /* __NR_pread64 */
+  
+  #ifdef __NR_prlimit64
+  {__NR_prlimit64},
+  #endif /* __NR_prlimit64 */
+  
+  #ifdef __NR_read
+  {__NR_read},
+  #endif /* __NR_read */
+  
+  #ifdef __NR_readlink
+  {__NR_readlink},
+  #endif /* __NR_readlink */
+  
+  #ifdef __NR_readv
+  {__NR_readv},
+  #endif /* __NR_readv */
+  
+  #ifdef __NR_recvfrom
+  {__NR_recvfrom},
+  #endif /* __NR_recvfrom */
+  
+  #ifdef __NR_recvmsg
+  {__NR_recvmsg},
+  #endif /* __NR_recvmsg */
+  
+  #ifdef __NR_restart_syscall
+  {__NR_restart_syscall},
+  #endif /* __NR_restart_syscall */
+  
+  #ifdef __NR_rt_sigaction
+  {__NR_rt_sigaction},
+  #endif /* __NR_rt_sigaction */
+  
+  #ifdef __NR_rt_sigprocmask
+  {__NR_rt_sigprocmask},
+  #endif /* __NR_rt_sigprocmask */
+  
+  #ifdef __NR_rt_sigreturn
+  {__NR_rt_sigreturn},
+  #endif /* __NR_rt_sigreturn */
+  
+  #ifdef __NR_sched_getaffinity
+  {__NR_sched_getaffinity},
+  #endif /* __NR_sched_getaffinity */
+  
+  #ifdef __NR_sched_getattr
+  {__NR_sched_getattr},
+  #endif /* __NR_sched_getattr */
+  
+  #ifdef __NR_sched_setattr
+  {__NR_sched_setattr},
+  #endif /* __NR_sched_setattr */
+  
+  #ifdef __NR_select
+  {__NR_select},
+  #endif /* __NR_select */
+  
+  #ifdef __NR_sendmmsg
+  {__NR_sendmmsg},
+  #endif /* __NR_sendmmsg */
+  
+  #ifdef __NR_sendmsg
+  {__NR_sendmsg},
+  #endif /* __NR_sendmsg */
+  
+  #ifdef __NR_sendto
+  {__NR_sendto},
+  #endif /* __NR_sendto */
+  
+  #ifdef __NR_set_robust_list
+  {__NR_set_robust_list},
+  #endif /* __NR_set_robust_list */
+  
+  #ifdef __NR_setsid
+  {__NR_setsid},
+  #endif /* __NR_setsid */
+  
+  #ifdef __NR_setsockopt
+  {__NR_setsockopt},
+  #endif /* __NR_setsockopt */
+  
+  #ifdef __NR_set_tid_address
+  {__NR_set_tid_address},
+  #endif /* __NR_set_tid_address */
+  
+  #ifdef __NR_socket
+  {__NR_socket},
+  #endif /* __NR_socket */
+  
+  #ifdef __NR_socketpair
+  {__NR_socketpair},
+  #endif /* __NR_socketpair */
+  
+  #ifdef __NR_stat
+  {__NR_stat},
+  #endif /* __NR_stat */
+  
+  #ifdef __NR_statfs
+  {__NR_statfs},
+  #endif /* __NR_statfs */
+  
+  #ifdef __NR_symlink
+  {__NR_symlink},
+  #endif /* __NR_symlink */
+  
+  #ifdef __NR_sysinfo
+  {__NR_sysinfo},
+  #endif /* __NR_sysinfo */
+  
+  #ifdef __NR_tgkill
+  {__NR_tgkill},
+  #endif /* __NR_tgkill */
+  
+  #ifdef __NR_times
+  {__NR_times},
+  #endif /* __NR_times */
+  
+  #ifdef __NR_umask
+  {__NR_umask},
+  #endif /* __NR_umask */
+  
+  #ifdef __NR_uname
+  {__NR_uname},
+  #endif /* __NR_uname */
+  
+  #ifdef __NR_unlink
+  {__NR_unlink},
+  #endif /* __NR_unlink */
+  
+  #ifdef __NR_wait4
+  {__NR_wait4},
+  #endif /* __NR_wait4 */
+  
+  #ifdef __NR_write
+  {__NR_write},
+  #endif /* __NR_write */
+  
+  #ifdef __NR_writev
+  {__NR_writev},
+  #endif /* __NR_writev */
+SCF_END_VALUES
diff --git a/Programs/system_darwin.c b/Programs/system_darwin.c
new file mode 100644
index 0000000..6bc3c0a
--- /dev/null
+++ b/Programs/system_darwin.c
@@ -0,0 +1,278 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+#include <IOKit/IOKitLib.h>
+
+#import <Foundation/NSLock.h>
+#import <Foundation/NSThread.h>
+#import <Foundation/NSAutoreleasePool.h>
+
+#include "log.h"
+#include "system.h"
+#include "system_darwin.h"
+
+static inline CFRunLoopRef
+getRunLoop (void) {
+  return CFRunLoopGetCurrent();
+}
+
+static inline CFStringRef
+getRunMode (void) {
+  return kCFRunLoopDefaultMode;
+}
+
+IOReturn
+executeRunLoop (int seconds) {
+  return CFRunLoopRunInMode(getRunMode(), seconds, 1);
+}
+
+void
+addRunLoopSource (CFRunLoopSourceRef source) {
+  CFRunLoopAddSource(getRunLoop(), source, getRunMode());
+}
+
+void
+removeRunLoopSource (CFRunLoopSourceRef source) {
+  CFRunLoopRemoveSource(getRunLoop(), source, getRunMode());
+}
+
+void
+setDarwinSystemError (IOReturn result) {
+  switch (result) {
+    default: errno = EIO; break;
+
+  //MAP_DARWIN_ERROR(KERN_SUCCESS, )
+    MAP_DARWIN_ERROR(KERN_INVALID_ADDRESS, EINVAL)
+    MAP_DARWIN_ERROR(KERN_PROTECTION_FAILURE, EFAULT)
+    MAP_DARWIN_ERROR(KERN_NO_SPACE, ENOSPC)
+    MAP_DARWIN_ERROR(KERN_INVALID_ARGUMENT, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_FAILURE, )
+    MAP_DARWIN_ERROR(KERN_RESOURCE_SHORTAGE, EAGAIN)
+  //MAP_DARWIN_ERROR(KERN_NOT_RECEIVER, )
+    MAP_DARWIN_ERROR(KERN_NO_ACCESS, EACCES)
+    MAP_DARWIN_ERROR(KERN_MEMORY_FAILURE, EFAULT)
+    MAP_DARWIN_ERROR(KERN_MEMORY_ERROR, EFAULT)
+  //MAP_DARWIN_ERROR(KERN_ALREADY_IN_SET, )
+  //MAP_DARWIN_ERROR(KERN_NOT_IN_SET, )
+    MAP_DARWIN_ERROR(KERN_NAME_EXISTS, EEXIST)
+    MAP_DARWIN_ERROR(KERN_ABORTED, ECANCELED)
+    MAP_DARWIN_ERROR(KERN_INVALID_NAME, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_TASK, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_RIGHT, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_VALUE, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_UREFS_OVERFLOW, )
+    MAP_DARWIN_ERROR(KERN_INVALID_CAPABILITY, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_RIGHT_EXISTS, )
+    MAP_DARWIN_ERROR(KERN_INVALID_HOST, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_MEMORY_PRESENT, )
+  //MAP_DARWIN_ERROR(KERN_MEMORY_DATA_MOVED, )
+  //MAP_DARWIN_ERROR(KERN_MEMORY_RESTART_COPY, )
+    MAP_DARWIN_ERROR(KERN_INVALID_PROCESSOR_SET, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_POLICY_LIMIT, )
+    MAP_DARWIN_ERROR(KERN_INVALID_POLICY, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_OBJECT, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_ALREADY_WAITING, )
+  //MAP_DARWIN_ERROR(KERN_DEFAULT_SET, )
+  //MAP_DARWIN_ERROR(KERN_EXCEPTION_PROTECTED, )
+    MAP_DARWIN_ERROR(KERN_INVALID_LEDGER, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_MEMORY_CONTROL, EINVAL)
+    MAP_DARWIN_ERROR(KERN_INVALID_SECURITY, EINVAL)
+  //MAP_DARWIN_ERROR(KERN_NOT_DEPRESSED, )
+  //MAP_DARWIN_ERROR(KERN_TERMINATED, )
+  //MAP_DARWIN_ERROR(KERN_LOCK_SET_DESTROYED, )
+  //MAP_DARWIN_ERROR(KERN_LOCK_UNSTABLE, )
+  //MAP_DARWIN_ERROR(KERN_LOCK_OWNED, )
+  //MAP_DARWIN_ERROR(KERN_LOCK_OWNED_SELF, )
+  //MAP_DARWIN_ERROR(KERN_SEMAPHORE_DESTROYED, )
+  //MAP_DARWIN_ERROR(KERN_RPC_SERVER_TERMINATED, )
+  //MAP_DARWIN_ERROR(KERN_RPC_TERMINATE_ORPHAN, )
+  //MAP_DARWIN_ERROR(KERN_RPC_CONTINUE_ORPHAN, )
+    MAP_DARWIN_ERROR(KERN_NOT_SUPPORTED, ENOTSUP)
+    MAP_DARWIN_ERROR(KERN_NODE_DOWN, EHOSTDOWN)
+  //MAP_DARWIN_ERROR(KERN_NOT_WAITING, )
+    MAP_DARWIN_ERROR(KERN_OPERATION_TIMED_OUT, ETIMEDOUT)
+
+    MAP_DARWIN_ERROR(kIOReturnSuccess, 0)
+  //MAP_DARWIN_ERROR(kIOReturnError, )
+    MAP_DARWIN_ERROR(kIOReturnNoMemory, ENOMEM)
+    MAP_DARWIN_ERROR(kIOReturnNoResources, EAGAIN)
+  //MAP_DARWIN_ERROR(kIOReturnIPCError, )
+    MAP_DARWIN_ERROR(kIOReturnNoDevice, ENODEV)
+    MAP_DARWIN_ERROR(kIOReturnNotPrivileged, EACCES)
+    MAP_DARWIN_ERROR(kIOReturnBadArgument, EINVAL)
+    MAP_DARWIN_ERROR(kIOReturnLockedRead, ENOLCK)
+    MAP_DARWIN_ERROR(kIOReturnLockedWrite, ENOLCK)
+    MAP_DARWIN_ERROR(kIOReturnExclusiveAccess, EBUSY)
+  //MAP_DARWIN_ERROR(kIOReturnBadMessageID, )
+    MAP_DARWIN_ERROR(kIOReturnUnsupported, ENOTSUP)
+  //MAP_DARWIN_ERROR(kIOReturnVMError, )
+  //MAP_DARWIN_ERROR(kIOReturnInternalError, )
+    MAP_DARWIN_ERROR(kIOReturnIOError, EIO)
+    MAP_DARWIN_ERROR(kIOReturnCannotLock, ENOLCK)
+    MAP_DARWIN_ERROR(kIOReturnNotOpen, EBADF)
+    MAP_DARWIN_ERROR(kIOReturnNotReadable, EACCES)
+    MAP_DARWIN_ERROR(kIOReturnNotWritable, EROFS)
+  //MAP_DARWIN_ERROR(kIOReturnNotAligned, )
+    MAP_DARWIN_ERROR(kIOReturnBadMedia, ENXIO)
+  //MAP_DARWIN_ERROR(kIOReturnStillOpen, )
+  //MAP_DARWIN_ERROR(kIOReturnRLDError, )
+    MAP_DARWIN_ERROR(kIOReturnDMAError, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnBusy, EBUSY)
+    MAP_DARWIN_ERROR(kIOReturnTimeout, ETIMEDOUT)
+    MAP_DARWIN_ERROR(kIOReturnOffline, ENXIO)
+    MAP_DARWIN_ERROR(kIOReturnNotReady, ENXIO)
+    MAP_DARWIN_ERROR(kIOReturnNotAttached, ENXIO)
+    MAP_DARWIN_ERROR(kIOReturnNoChannels, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnNoSpace, ENOSPC)
+    MAP_DARWIN_ERROR(kIOReturnPortExists, EADDRINUSE)
+    MAP_DARWIN_ERROR(kIOReturnCannotWire, ENOMEM)
+  //MAP_DARWIN_ERROR(kIOReturnNoInterrupt, )
+    MAP_DARWIN_ERROR(kIOReturnNoFrames, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnMessageTooLarge, EMSGSIZE)
+    MAP_DARWIN_ERROR(kIOReturnNotPermitted, EPERM)
+    MAP_DARWIN_ERROR(kIOReturnNoPower, EPWROFF)
+    MAP_DARWIN_ERROR(kIOReturnNoMedia, ENXIO)
+    MAP_DARWIN_ERROR(kIOReturnUnformattedMedia, ENXIO)
+    MAP_DARWIN_ERROR(kIOReturnUnsupportedMode, ENOSYS)
+    MAP_DARWIN_ERROR(kIOReturnUnderrun, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnOverrun, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnDeviceError, EDEVERR)
+  //MAP_DARWIN_ERROR(kIOReturnNoCompletion, )
+    MAP_DARWIN_ERROR(kIOReturnAborted, ECANCELED)
+    MAP_DARWIN_ERROR(kIOReturnNoBandwidth, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnNotResponding, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnIsoTooOld, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnIsoTooNew, EDEVERR)
+    MAP_DARWIN_ERROR(kIOReturnNotFound, ENOENT)
+  //MAP_DARWIN_ERROR(kIOReturnInvalid, )
+  }
+}
+
+void
+initializeSystemObject (void) {
+}
+
+@interface AsynchronousResult ()
+@property (assign, readwrite) int isFinished;
+@property (assign, readwrite) IOReturn finalStatus;
+@end
+
+@implementation AsynchronousResult
+@synthesize isFinished;
+@synthesize finalStatus;
+
+- (int) wait
+  : (int) timeout
+  {
+    if (self.isFinished) return 1;
+
+    while (1) {
+      IOReturn result = executeRunLoop(timeout);
+
+      if (self.isFinished) return 1;
+      if (result == kCFRunLoopRunHandledSource) continue;
+      if (result == kCFRunLoopRunTimedOut) return 0;
+    }
+  }
+
+- (void) setStatus
+  : (IOReturn) status
+  {
+    self.finalStatus = status;
+    self.isFinished = 1;
+  }
+@end
+
+@interface AsynchronousTask ()
+@property (assign, readwrite) NSThread *taskThread;
+@property (assign, readwrite) CFRunLoopRef taskRunLoop;
+
+@property (retain) NSCondition *startSynchronizer;
+@property (retain) NSThread *resultThread;
+
+- (void) taskFinished;
+
+- (void) main;
+
+- (void) endTask;
+@end
+
+@implementation AsynchronousTask
+@synthesize taskThread;
+@synthesize taskRunLoop;
+@synthesize startSynchronizer;
+@synthesize resultThread;
+
+- (IOReturn) run
+  {
+    logMessage(LOG_WARNING, "run method not overridden");
+    return kIOReturnSuccess;
+  }
+
+- (void) taskFinished
+  {
+    self.resultThread = nil;
+  }
+
+- (void) main
+  {
+    NSAutoreleasePool *pool = [NSAutoreleasePool new];
+
+    [self.startSynchronizer lock];
+    self.taskThread = [NSThread currentThread];
+    self.taskRunLoop = CFRunLoopGetCurrent();
+    [self.startSynchronizer signal];
+    [self.startSynchronizer unlock];
+
+    [self setStatus:[self run]];
+    [self performSelector:@selector(taskFinished) onThread:self.resultThread withObject:nil waitUntilDone:0];
+
+    self.taskThread = nil;
+    [pool drain];
+  }
+
+- (int) start
+  {
+    if ((self.startSynchronizer = [NSCondition new])) {
+      self.resultThread = [NSThread currentThread];
+
+      [self.startSynchronizer lock];
+      [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
+      [self.startSynchronizer wait];
+      [self.startSynchronizer unlock];
+
+      self.startSynchronizer = nil;
+      return 1;
+    }
+
+    return 0;
+  }
+
+- (void) endTask
+  {
+    CFRunLoopStop(self.taskRunLoop);
+  }
+
+- (void) stop
+  {
+    [self performSelector:@selector(endTask) onThread:self.taskThread withObject:nil waitUntilDone:0];
+  }
+@end
diff --git a/Programs/system_java.c b/Programs/system_java.c
new file mode 100644
index 0000000..065c621
--- /dev/null
+++ b/Programs/system_java.c
@@ -0,0 +1,455 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "thread.h"
+#include "system.h"
+#include "system_java.h"
+
+static JavaVM *javaVirtualMachine = NULL;
+
+JNIEXPORT jint
+JNI_OnLoad (JavaVM *vm, void *reserved) {
+  javaVirtualMachine = vm;
+  return JAVA_JNI_VERSION;
+}
+
+JNIEXPORT void
+JNI_OnUnload (JavaVM *vm, void *reserved) {
+  javaVirtualMachine = NULL;
+}
+
+JavaVM *
+getJavaInvocationInterface (void) {
+  return javaVirtualMachine;
+}
+
+typedef struct {
+  JavaVM *virtualMachine;
+  JNIEnv *nativeInterface;
+  char *threadName;
+} ThreadSpecificData;
+
+static THREAD_SPECIFIC_DATA_NEW(tsdJavaNativeThread) {
+  ThreadSpecificData *tsd;
+
+  if ((tsd = malloc(sizeof(*tsd)))) {
+    memset(tsd, 0, sizeof(*tsd));
+    return tsd;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static THREAD_SPECIFIC_DATA_DESTROY(tsdJavaNativeThread) {
+  ThreadSpecificData *tsd = data;
+
+  {
+    JavaVM *vm = tsd->virtualMachine;
+    (*vm)->DetachCurrentThread(vm);
+  }
+
+  logMessage(LOG_DEBUG, "thread detached from Java VM: %s", tsd->threadName);
+  free(tsd->threadName);
+  free(tsd);
+}
+
+THREAD_SPECIFIC_DATA_CONTROL(tsdJavaNativeThread);
+
+static char *
+getJavaThreadName (JNIEnv *env) {
+  char *name = NULL;
+  static jclass Thread_class = NULL;
+
+  if (findJavaClass(env, &Thread_class, JAVA_OBJ_THREAD)) {
+    static jmethodID Thread_currentThread = 0;
+
+    if (findJavaStaticMethod(env, &Thread_currentThread, Thread_class, "currentThread",
+                             JAVA_SIG_METHOD(JAVA_SIG_THREAD, 
+                                            ))) {
+      jobject thread = (*env)->CallStaticObjectMethod(env, Thread_class, Thread_currentThread);
+
+      if (!clearJavaException(env, 1)) {
+        static jmethodID Thread_getName = 0;
+
+        if (findJavaInstanceMethod(env, &Thread_getName, Thread_class, "getName",
+                                   JAVA_SIG_METHOD(JAVA_SIG_STRING, 
+                                                  ))) {
+          jstring jName = (*env)->CallObjectMethod(env, thread, Thread_getName);
+
+          if (!clearJavaException(env, 1)) {
+            jboolean isCopy;
+            const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy);
+
+            if (!(name = strdup(cName))) {
+              logMallocError();
+            }
+
+            (*env)->ReleaseStringUTFChars(env, jName, cName);
+            (*env)->DeleteLocalRef(env, jName);
+          }
+        }
+
+        (*env)->DeleteLocalRef(env, thread);
+      }
+    }
+  }
+
+  return name;
+}
+
+JNIEnv *
+getJavaNativeInterface (void) {
+  JavaVM *vm = getJavaInvocationInterface();
+
+#ifdef __ANDROID__
+  JNIEnv *env = NULL;
+#else /* __ANDROID__ */
+  void *env = NULL;
+#endif /* __ANDROID__ */
+
+  if (vm) {
+    jint result = (*vm)->GetEnv(vm, (void **)&env, JAVA_JNI_VERSION);
+
+    if (result != JNI_OK) {
+      if (result == JNI_EDETACHED) {
+        JavaVMAttachArgs args = {
+          .version = JAVA_JNI_VERSION,
+          .name = NULL,
+          .group = NULL
+        };
+
+        if ((result = (*vm)->AttachCurrentThread(vm, &env, &args)) < 0) {
+          logMessage(LOG_WARNING, "Java AttachCurrentThread error: %d", result);
+        } else {
+          ThreadSpecificData *tsd = getThreadSpecificData(&tsdJavaNativeThread);
+          tsd->virtualMachine = vm;
+          tsd->nativeInterface = env;
+          tsd->threadName = getJavaThreadName(env);
+
+          logMessage(LOG_DEBUG, "thread attached to Java VM: %s", tsd->threadName);
+        }
+      } else {
+        logMessage(LOG_WARNING, "Java GetEnv error: %d", result);
+      }
+    }
+  }
+
+  return env;
+}
+
+int
+clearJavaException (JNIEnv *env, int describe) {
+  int exceptionOccurred = (*env)->ExceptionCheck(env);
+
+  if (exceptionOccurred) {
+    if (describe) (*env)->ExceptionDescribe(env);
+    (*env)->ExceptionClear(env);
+  }
+
+  return exceptionOccurred;
+}
+
+static jobject javaClassLoaderInstance = NULL;
+static jclass javaClassLoaderClass = NULL;
+static jmethodID loadClassMethod = 0;
+
+int
+setJavaClassLoader (JNIEnv *env, jobject instance) {
+  if (instance) {
+    javaClassLoaderInstance = (*env)->NewGlobalRef(env, instance);
+
+    if (javaClassLoaderInstance) {
+      jclass class = (*env)->GetObjectClass(env, instance);
+
+      if (class) {
+        javaClassLoaderClass = (*env)->NewGlobalRef(env, class);
+
+        (*env)->DeleteLocalRef(env, class);
+        class = NULL;
+
+        if (javaClassLoaderClass) {
+          jmethodID method = (*env)->GetMethodID(env, javaClassLoaderClass, "loadClass",
+                                                 JAVA_SIG_METHOD(JAVA_SIG_CLASS,
+                                                                 JAVA_SIG_STRING // className
+                                                                ));
+
+          if (method) {
+            loadClassMethod = method;
+            return 1;
+          }
+
+          (*env)->DeleteGlobalRef(env, javaClassLoaderClass);
+        }
+      }
+
+      (*env)->DeleteGlobalRef(env, javaClassLoaderInstance);
+    }
+  }
+
+  javaClassLoaderInstance = NULL;
+  javaClassLoaderClass = NULL;
+  loadClassMethod = 0;
+  return 0;
+}
+
+static jclass
+loadJavaClass (JNIEnv *env, const char *path) {
+  size_t size = strlen(path) + 1;
+  char cName[size];
+
+  jclass class = NULL;
+  jobject jName;
+
+  {
+    const char *p = path;
+    char *n = cName;
+    char c;
+
+    do {
+      c = *p++;
+      if (c == '/') c = '.';
+      *n++ = c;
+    } while (c);
+  }
+
+  if ((jName = (*env)->NewStringUTF(env, cName))) {
+    jclass result = (*env)->CallObjectMethod(env, javaClassLoaderInstance, loadClassMethod, jName);
+
+    if (clearJavaException(env, 1)) {
+      (*env)->DeleteLocalRef(env, result);
+    } else {
+      class = result;
+    }
+
+    (*env)->DeleteLocalRef(env, jName);
+  } else {
+    logMallocError();
+  }
+
+  return class;
+}
+
+int
+findJavaClass (JNIEnv *env, jclass *class, const char *path) {
+  if (*class) return 1;
+
+  {
+    jclass localReference = loadClassMethod?
+                              loadJavaClass(env, path):
+                              (*env)->FindClass(env, path);
+
+    if (localReference) {
+      jclass globalReference = (*env)->NewGlobalRef(env, localReference);
+
+      (*env)->DeleteLocalRef(env, localReference);
+      localReference = NULL;
+
+      if (globalReference) {
+        logMessage(LOG_DEBUG, "java class found: %s", path);
+        *class = globalReference;
+        return 1;
+      } else {
+        logMallocError();
+        clearJavaException(env, 0);
+      }
+    } else {
+      logMessage(LOG_ERR, "java class not found: %s", path);
+      clearJavaException(env, 1);
+    }
+  }
+
+  return 0;
+}
+
+int
+findJavaInstanceMethod (
+  JNIEnv *env, jmethodID *method,
+  jclass class, const char *name, const char *signature
+) {
+  if (!*method) {
+    if (!(*method = (*env)->GetMethodID(env, class, name, signature))) {
+      logMessage(LOG_ERR, "java instance method not found: %s: %s", name, signature);
+      clearJavaException(env, 0);
+      return 0;
+    }
+
+    logMessage(LOG_DEBUG, "java instance method found: %s: %s", name, signature);
+  }
+
+  return 1;
+}
+
+int
+findJavaStaticMethod (
+  JNIEnv *env, jmethodID *method,
+  jclass class, const char *name, const char *signature
+) {
+  if (!*method) {
+    if (!(*method = (*env)->GetStaticMethodID(env, class, name, signature))) {
+      logMessage(LOG_ERR, "java static method not found: %s: %s", name, signature);
+      clearJavaException(env, 0);
+      return 0;
+    }
+
+    logMessage(LOG_DEBUG, "java static method found: %s: %s", name, signature);
+  }
+
+  return 1;
+}
+
+int
+findJavaConstructor (
+  JNIEnv *env, jmethodID *constructor,
+  jclass class, const char *signature
+) {
+  return findJavaInstanceMethod(env, constructor, class, JAVA_CONSTRUCTOR_NAME, signature);
+}
+
+int
+findJavaInstanceField (
+  JNIEnv *env, jfieldID *field,
+  jclass class, const char *name, const char *signature
+) {
+  if (!*field) {
+    if (!(*field = (*env)->GetFieldID(env, class, name, signature))) {
+      logMessage(LOG_ERR, "java instance field not found: %s: %s", name, signature);
+      clearJavaException(env, 0);
+      return 0;
+    }
+
+    logMessage(LOG_DEBUG, "java instance field found: %s: %s", name, signature);
+  }
+
+  return 1;
+}
+
+int
+findJavaStaticField (
+  JNIEnv *env, jfieldID *field,
+  jclass class, const char *name, const char *signature
+) {
+  if (!*field) {
+    if (!(*field = (*env)->GetStaticFieldID(env, class, name, signature))) {
+      logMessage(LOG_ERR, "java static field not found: %s: %s", name, signature);
+      clearJavaException(env, 0);
+      return 0;
+    }
+
+    logMessage(LOG_DEBUG, "java static field found: %s: %s", name, signature);
+  }
+
+  return 1;
+}
+
+char *
+getJavaLocaleName (void) {
+  char *name = NULL;
+  JNIEnv *env;
+
+  if ((env = getJavaNativeInterface())) {
+    jclass Locale_class = NULL;
+
+    if (findJavaClass(env, &Locale_class, JAVA_OBJ_LOCALE)) {
+      jmethodID Locale_getDefault = 0;
+
+      if (findJavaStaticMethod(env, &Locale_getDefault, Locale_class, "getDefault",
+                               JAVA_SIG_METHOD(JAVA_SIG_LOCALE, 
+                                              ))) {
+        jobject locale = (*env)->CallStaticObjectMethod(env, Locale_class, Locale_getDefault);
+
+        if (!clearJavaException(env, 1)) {
+          jmethodID Locale_toString = 0;
+
+          if (findJavaInstanceMethod(env, &Locale_toString, Locale_class, "toString",
+                                     JAVA_SIG_METHOD(JAVA_SIG_STRING, 
+                                                    ))) {
+            jstring jName = (*env)->CallObjectMethod(env, locale, Locale_toString);
+
+            if (!clearJavaException(env, 1)) {
+              jboolean isCopy;
+              const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy);
+
+              if (!(name = strdup(cName))) {
+                logMallocError();
+              }
+
+              (*env)->ReleaseStringUTFChars(env, jName, cName);
+              (*env)->DeleteLocalRef(env, jName);
+            }
+          }
+
+          (*env)->DeleteLocalRef(env, locale);
+        }
+      }
+    }
+  }
+
+  return name;
+}
+
+#if defined(__ANDROID__)
+#include <locale.h>
+#include "messages.h"
+
+static void
+initializeAndroidEnvironment (JNIEnv *env) {
+  {
+    static jclass class = NULL;
+
+    if (findJavaClass(env, &class, JAVA_OBJ_BRLTTY("BrailleApplication"))) {
+      static jmethodID method = 0;
+
+      if (findJavaStaticMethod(env, &method, class, "getCurrentLocale",
+                               JAVA_SIG_METHOD(JAVA_SIG_STRING, 
+                                              ))) {
+        jstring jLocale = (*env)->CallStaticObjectMethod(env, class, method);
+
+        if (!clearJavaException(env, 1)) {
+          if (jLocale) {
+            jboolean isCopy;
+            const char *cLocale = (*env)->GetStringUTFChars(env, jLocale, &isCopy);
+
+            if (cLocale) {
+              if (setMessagesLocale(cLocale)) {
+              }
+
+              (*env)->ReleaseStringUTFChars(env, jLocale, cLocale);
+            }
+
+            (*env)->DeleteLocalRef(env, jLocale);
+          }
+        }
+      }
+    }
+  }
+}
+#endif /* platform-speciofic initialization */
+
+void
+initializeSystemObject (void) {
+#if defined(__ANDROID__)
+  initializeAndroidEnvironment(getJavaNativeInterface());
+#endif /* platform-speciofic initialization */
+}
diff --git a/Programs/system_linux.c b/Programs/system_linux.c
new file mode 100644
index 0000000..8674722
--- /dev/null
+++ b/Programs/system_linux.c
@@ -0,0 +1,1448 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <dirent.h>
+#include <sys/sysmacros.h>
+#include <linux/major.h>
+
+#include "log.h"
+#include "parse.h"
+#include "file.h"
+#include "device.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_io.h"
+#include "hostcmd.h"
+#include "bitmask.h"
+#include "system.h"
+#include "system_linux.h"
+
+typedef struct {
+  PathProcessor *processor;
+  const PathProcessorParameters *parameters;
+} PathProcessingData;
+
+static int
+processDirectoryEntry (const char *name, const PathProcessingData *ppd) {
+  const PathProcessorParameters *parameters = ppd->parameters;
+
+  const char *directory = parameters->path;
+  char path[strlen(directory) + 1 + strlen(name) + 1];
+
+  snprintf(
+    path, sizeof(path), "%s%c%s",
+    directory, PATH_SEPARATOR_CHARACTER, name
+  );
+
+  return processPathTree(path, ppd->processor, parameters->data);
+}
+
+int
+processPathTree (const char *path, PathProcessor *processPath, void *data) {
+  PathProcessorParameters parameters = {
+    .path = path,
+    .data = data
+  };
+
+  PathProcessingData ppd = {
+    .processor = processPath,
+    .parameters = &parameters
+  };
+
+  int stop = 0;
+  DIR *directory;
+
+  if ((directory = opendir(path))) {
+    if (processPath(&parameters)) {
+      struct dirent *entry;
+
+      while ((entry = readdir(directory))) {
+        const char *name = entry->d_name;
+
+        if (strcmp(name, CURRENT_DIRECTORY_NAME) == 0) continue;
+        if (strcmp(name, PARENT_DIRECTORY_NAME) == 0) continue;
+
+        if (!processDirectoryEntry(name, &ppd)) {
+          stop = 1;
+          break;
+        }
+      }
+    } else {
+      stop = 1;
+    }
+
+    closedir(directory);
+  } else if (errno == ENOTDIR) {
+    if (!processPath(&parameters)) stop = 1;
+  } else {
+    logMessage(LOG_WARNING, "can't access path: %s: %s", path, strerror(errno));
+  }
+
+  return !stop;
+}
+
+int
+compareGroups (gid_t group1, gid_t group2) {
+  if (group1 < group2) return -1;
+  if (group1 > group2) return 1;
+  return 0;
+}
+
+static int
+groupSorter (const void *element1,const void *element2) {
+  const gid_t *group1 = element1;
+  const gid_t *group2 = element2;
+  return compareGroups(*group1, *group2);
+}
+
+void
+sortGroups (gid_t *groups, size_t count) {
+  qsort(groups, count, sizeof(*groups), groupSorter);
+}
+
+void
+removeDuplicateGroups (gid_t *groups, size_t *count) {
+  if (*count > 1) {
+    sortGroups(groups, *count);
+
+    gid_t *to = groups;
+    const gid_t *from = to + 1;
+    const gid_t *end = to + *count;
+
+    while (from < end) {
+      if (*from != *to) {
+        if (++to != from) {
+          *to = *from;
+        }
+      }
+
+      from += 1;
+    }
+
+    *count = ++to - groups;
+  }
+}
+
+void
+processSupplementaryGroups (GroupsProcessor *processGroups, void *data) {
+  ssize_t size = getgroups(0, NULL);
+
+  if (size != -1) {
+    gid_t groups[size];
+    ssize_t result = getgroups(size, groups);
+
+    if (result != -1) {
+      size_t count = result;
+      removeDuplicateGroups(groups, &count);
+      processGroups(groups, count, data);
+    } else {
+      logSystemError("getgroups");
+    }
+  } else {
+    logSystemError("getgroups");
+  }
+}
+
+typedef struct {
+  const gid_t *groups;
+  size_t count;
+  unsigned char have:1;
+} HaveGroupsData;
+
+static void
+haveGroups (const gid_t *groups, size_t count, void *data) {
+  HaveGroupsData *hgd = data;
+
+  const gid_t *need = hgd->groups;
+  const gid_t *needEnd = need + hgd->count;
+
+  const gid_t *have = groups;
+  const gid_t *haveEnd = have + count;
+
+  while (have < haveEnd) {
+    if (*have > *need) break;
+    if (*have++ < *need) continue;
+
+    if (++need == needEnd) {
+      hgd->have = 1;
+      return;
+    }
+  }
+
+  hgd->have = 0;
+}
+
+int
+haveSupplementaryGroups (const gid_t *groups, size_t count) {
+  HaveGroupsData hgd = {
+    .groups = groups,
+    .count = count
+  };
+
+  processSupplementaryGroups(haveGroups, &hgd);
+  return hgd.have;
+}
+
+#ifdef HAVE_LINUX_INPUT_H
+#include <linux/input.h>
+
+#ifndef input_event_sec
+#define input_event_sec time.tv_sec
+#endif /* input_event_sec */
+
+#ifndef input_event_usec
+#define input_event_usec time.tv_usec
+#endif /* input_event_usec */
+
+#include "kbd_keycodes.h"
+
+LINUX_KEY_MAP(xt00) = {
+  [XT_KEY_00_Escape] = KEY_ESC,
+  [XT_KEY_00_F1] = KEY_F1,
+  [XT_KEY_00_F2] = KEY_F2,
+  [XT_KEY_00_F3] = KEY_F3,
+  [XT_KEY_00_F4] = KEY_F4,
+  [XT_KEY_00_F5] = KEY_F5,
+  [XT_KEY_00_F6] = KEY_F6,
+  [XT_KEY_00_F7] = KEY_F7,
+  [XT_KEY_00_F8] = KEY_F8,
+  [XT_KEY_00_F9] = KEY_F9,
+  [XT_KEY_00_F10] = KEY_F10,
+  [XT_KEY_00_F11] = KEY_F11,
+  [XT_KEY_00_F12] = KEY_F12,
+  [XT_KEY_00_SystemRequest] = KEY_SYSRQ,
+  [XT_KEY_00_ScrollLock] = KEY_SCROLLLOCK,
+
+  [XT_KEY_00_F13] = KEY_F13,
+  [XT_KEY_00_F14] = KEY_F14,
+  [XT_KEY_00_F15] = KEY_F15,
+  [XT_KEY_00_F16] = KEY_F16,
+  [XT_KEY_00_F17] = KEY_F17,
+  [XT_KEY_00_F18] = KEY_F18,
+  [XT_KEY_00_F19] = KEY_F19,
+  [XT_KEY_00_F20] = KEY_F20,
+  [XT_KEY_00_F21] = KEY_F21,
+  [XT_KEY_00_F22] = KEY_F22,
+  [XT_KEY_00_F23] = KEY_F23,
+  [XT_KEY_00_F24] = KEY_F24,
+
+  [XT_KEY_00_Grave] = KEY_GRAVE,
+  [XT_KEY_00_1] = KEY_1,
+  [XT_KEY_00_2] = KEY_2,
+  [XT_KEY_00_3] = KEY_3,
+  [XT_KEY_00_4] = KEY_4,
+  [XT_KEY_00_5] = KEY_5,
+  [XT_KEY_00_6] = KEY_6,
+  [XT_KEY_00_7] = KEY_7,
+  [XT_KEY_00_8] = KEY_8,
+  [XT_KEY_00_9] = KEY_9,
+  [XT_KEY_00_0] = KEY_0,
+  [XT_KEY_00_Minus] = KEY_MINUS,
+  [XT_KEY_00_Equal] = KEY_EQUAL,
+  [XT_KEY_00_Backspace] = KEY_BACKSPACE,
+
+  [XT_KEY_00_Tab] = KEY_TAB,
+  [XT_KEY_00_Q] = KEY_Q,
+  [XT_KEY_00_W] = KEY_W,
+  [XT_KEY_00_E] = KEY_E,
+  [XT_KEY_00_R] = KEY_R,
+  [XT_KEY_00_T] = KEY_T,
+  [XT_KEY_00_Y] = KEY_Y,
+  [XT_KEY_00_U] = KEY_U,
+  [XT_KEY_00_I] = KEY_I,
+  [XT_KEY_00_O] = KEY_O,
+  [XT_KEY_00_P] = KEY_P,
+  [XT_KEY_00_LeftBracket] = KEY_LEFTBRACE,
+  [XT_KEY_00_RightBracket] = KEY_RIGHTBRACE,
+  [XT_KEY_00_Backslash] = KEY_BACKSLASH,
+
+  [XT_KEY_00_CapsLock] = KEY_CAPSLOCK,
+  [XT_KEY_00_A] = KEY_A,
+  [XT_KEY_00_S] = KEY_S,
+  [XT_KEY_00_D] = KEY_D,
+  [XT_KEY_00_F] = KEY_F,
+  [XT_KEY_00_G] = KEY_G,
+  [XT_KEY_00_H] = KEY_H,
+  [XT_KEY_00_J] = KEY_J,
+  [XT_KEY_00_K] = KEY_K,
+  [XT_KEY_00_L] = KEY_L,
+  [XT_KEY_00_Semicolon] = KEY_SEMICOLON,
+  [XT_KEY_00_Apostrophe] = KEY_APOSTROPHE,
+  [XT_KEY_00_Enter] = KEY_ENTER,
+
+  [XT_KEY_00_LeftShift] = KEY_LEFTSHIFT,
+  [XT_KEY_00_Europe2] = KEY_102ND,
+  [XT_KEY_00_Z] = KEY_Z,
+  [XT_KEY_00_X] = KEY_X,
+  [XT_KEY_00_C] = KEY_C,
+  [XT_KEY_00_V] = KEY_V,
+  [XT_KEY_00_B] = KEY_B,
+  [XT_KEY_00_N] = KEY_N,
+  [XT_KEY_00_M] = KEY_M,
+  [XT_KEY_00_Comma] = KEY_COMMA,
+  [XT_KEY_00_Period] = KEY_DOT,
+  [XT_KEY_00_Slash] = KEY_SLASH,
+  [XT_KEY_00_RightShift] = KEY_RIGHTSHIFT,
+
+  [XT_KEY_00_LeftControl] = KEY_LEFTCTRL,
+  [XT_KEY_00_LeftAlt] = KEY_LEFTALT,
+  [XT_KEY_00_Space] = KEY_SPACE,
+
+  [XT_KEY_00_NumLock] = KEY_NUMLOCK,
+  [XT_KEY_00_KPAsterisk] = KEY_KPASTERISK,
+  [XT_KEY_00_KPMinus] = KEY_KPMINUS,
+  [XT_KEY_00_KPPlus] = KEY_KPPLUS,
+  [XT_KEY_00_KPPeriod] = KEY_KPDOT,
+  [XT_KEY_00_KP0] = KEY_KP0,
+  [XT_KEY_00_KP1] = KEY_KP1,
+  [XT_KEY_00_KP2] = KEY_KP2,
+  [XT_KEY_00_KP3] = KEY_KP3,
+  [XT_KEY_00_KP4] = KEY_KP4,
+  [XT_KEY_00_KP5] = KEY_KP5,
+  [XT_KEY_00_KP6] = KEY_KP6,
+  [XT_KEY_00_KP7] = KEY_KP7,
+  [XT_KEY_00_KP8] = KEY_KP8,
+  [XT_KEY_00_KP9] = KEY_KP9,
+
+  [XT_KEY_00_KPComma] = KEY_KPCOMMA,
+  [XT_KEY_00_KPEqual] = KEY_KPEQUAL,
+
+  [XT_KEY_00_International1] = KEY_RO,
+  [XT_KEY_00_International2] = KEY_KATAKANAHIRAGANA,
+  [XT_KEY_00_International3] = KEY_YEN,
+  [XT_KEY_00_International4] = KEY_HENKAN,
+  [XT_KEY_00_International5] = KEY_MUHENKAN,
+  [XT_KEY_00_International6] = KEY_KPJPCOMMA,
+
+  [XT_KEY_00_Language3] = KEY_KATAKANA,
+  [XT_KEY_00_Language4] = KEY_HIRAGANA,
+};
+
+LINUX_KEY_MAP(xtE0) = {
+  [XT_KEY_E0_LeftGUI] = KEY_LEFTMETA,
+  [XT_KEY_E0_RightAlt] = KEY_RIGHTALT,
+  [XT_KEY_E0_RightGUI] = KEY_RIGHTMETA,
+  [XT_KEY_E0_Context] = KEY_COMPOSE,
+  [XT_KEY_E0_RightControl] = KEY_RIGHTCTRL,
+
+  [XT_KEY_E0_Insert] = KEY_INSERT,
+  [XT_KEY_E0_Delete] = KEY_DELETE,
+  [XT_KEY_E0_Home] = KEY_HOME,
+  [XT_KEY_E0_End] = KEY_END,
+  [XT_KEY_E0_PageUp] = KEY_PAGEUP,
+  [XT_KEY_E0_PageDown] = KEY_PAGEDOWN,
+
+  [XT_KEY_E0_ArrowUp] = KEY_UP,
+  [XT_KEY_E0_ArrowLeft] = KEY_LEFT,
+  [XT_KEY_E0_ArrowDown] = KEY_DOWN,
+  [XT_KEY_E0_ArrowRight] = KEY_RIGHT,
+
+  [XT_KEY_E0_KPEnter] = KEY_KPENTER,
+  [XT_KEY_E0_KPSlash] = KEY_KPSLASH,
+
+  [XT_KEY_E0_Copy] = KEY_COPY,
+  [XT_KEY_E0_Cut] = KEY_CUT,
+  [XT_KEY_E0_Paste] = KEY_PASTE,
+  [XT_KEY_E0_Undo] = KEY_UNDO,
+  [XT_KEY_E0_Redo] = KEY_REDO,
+
+  [XT_KEY_E0_MyComputer] = KEY_COMPUTER,
+  [XT_KEY_E0_Calculator] = KEY_CALC,
+  [XT_KEY_E0_Mail] = KEY_MAIL,
+  [XT_KEY_E0_Mail_X1] = KEY_MAIL,
+
+  [XT_KEY_E0_WebHome] = KEY_HOMEPAGE,
+  [XT_KEY_E0_WebBookmarks] = KEY_BOOKMARKS,
+  [XT_KEY_E0_WebSearch] = KEY_SEARCH,
+  [XT_KEY_E0_WebBack] = KEY_BACK,
+  [XT_KEY_E0_WebForward] = KEY_FORWARD,
+  [XT_KEY_E0_WebRefresh] = KEY_REFRESH,
+  [XT_KEY_E0_WebStop] = KEY_STOP,
+
+  [XT_KEY_E0_Mute] = KEY_MUTE,
+  [XT_KEY_E0_VolumeDown] = KEY_VOLUMEDOWN,
+  [XT_KEY_E0_VolumeUp] = KEY_VOLUMEUP,
+
+  [XT_KEY_E0_MediaVideo] = KEY_MEDIA,
+  [XT_KEY_E0_MediaPlayPause] = KEY_PLAYPAUSE,
+  [XT_KEY_E0_MediaStop] = KEY_STOPCD,
+  [XT_KEY_E0_MediaPrevious] = KEY_PREVIOUSSONG,
+  [XT_KEY_E0_MediaNext] = KEY_NEXTSONG,
+
+  [XT_KEY_E0_Power] = KEY_POWER,
+  [XT_KEY_E0_Sleep] = KEY_SLEEP,
+  [XT_KEY_E0_Wake] = KEY_WAKEUP,
+};
+
+LINUX_KEY_MAP(xtE1) = {
+  [XT_KEY_E1_Pause] = KEY_PAUSE,
+};
+
+LINUX_KEY_MAP(at00) = {
+  [AT_KEY_00_Escape] = KEY_ESC,
+  [AT_KEY_00_F1] = KEY_F1,
+  [AT_KEY_00_F2] = KEY_F2,
+  [AT_KEY_00_F3] = KEY_F3,
+  [AT_KEY_00_F4] = KEY_F4,
+  [AT_KEY_00_F5] = KEY_F5,
+  [AT_KEY_00_F6] = KEY_F6,
+  [AT_KEY_00_F7] = KEY_F7,
+  [AT_KEY_00_F7_X1] = KEY_F7,
+  [AT_KEY_00_F8] = KEY_F8,
+  [AT_KEY_00_F9] = KEY_F9,
+  [AT_KEY_00_F10] = KEY_F10,
+  [AT_KEY_00_F11] = KEY_F11,
+  [AT_KEY_00_F12] = KEY_F12,
+  [AT_KEY_00_SystemRequest] = KEY_SYSRQ,
+  [AT_KEY_00_ScrollLock] = KEY_SCROLLLOCK,
+
+  [AT_KEY_00_F13] = KEY_F13,
+  [AT_KEY_00_F14] = KEY_F14,
+  [AT_KEY_00_F15] = KEY_F15,
+  [AT_KEY_00_F16] = KEY_F16,
+  [AT_KEY_00_F17] = KEY_F17,
+  [AT_KEY_00_F18] = KEY_F18,
+  [AT_KEY_00_F19] = KEY_F19,
+  [AT_KEY_00_F20] = KEY_F20,
+  [AT_KEY_00_F21] = KEY_F21,
+  [AT_KEY_00_F22] = KEY_F22,
+  [AT_KEY_00_F23] = KEY_F23,
+  [AT_KEY_00_F24] = KEY_F24,
+
+  [AT_KEY_00_Grave] = KEY_GRAVE,
+  [AT_KEY_00_1] = KEY_1,
+  [AT_KEY_00_2] = KEY_2,
+  [AT_KEY_00_3] = KEY_3,
+  [AT_KEY_00_4] = KEY_4,
+  [AT_KEY_00_5] = KEY_5,
+  [AT_KEY_00_6] = KEY_6,
+  [AT_KEY_00_7] = KEY_7,
+  [AT_KEY_00_8] = KEY_8,
+  [AT_KEY_00_9] = KEY_9,
+  [AT_KEY_00_0] = KEY_0,
+  [AT_KEY_00_Minus] = KEY_MINUS,
+  [AT_KEY_00_Equal] = KEY_EQUAL,
+  [AT_KEY_00_Backspace] = KEY_BACKSPACE,
+
+  [AT_KEY_00_Tab] = KEY_TAB,
+  [AT_KEY_00_Q] = KEY_Q,
+  [AT_KEY_00_W] = KEY_W,
+  [AT_KEY_00_E] = KEY_E,
+  [AT_KEY_00_R] = KEY_R,
+  [AT_KEY_00_T] = KEY_T,
+  [AT_KEY_00_Y] = KEY_Y,
+  [AT_KEY_00_U] = KEY_U,
+  [AT_KEY_00_I] = KEY_I,
+  [AT_KEY_00_O] = KEY_O,
+  [AT_KEY_00_P] = KEY_P,
+  [AT_KEY_00_LeftBracket] = KEY_LEFTBRACE,
+  [AT_KEY_00_RightBracket] = KEY_RIGHTBRACE,
+  [AT_KEY_00_Backslash] = KEY_BACKSLASH,
+
+  [AT_KEY_00_CapsLock] = KEY_CAPSLOCK,
+  [AT_KEY_00_A] = KEY_A,
+  [AT_KEY_00_S] = KEY_S,
+  [AT_KEY_00_D] = KEY_D,
+  [AT_KEY_00_F] = KEY_F,
+  [AT_KEY_00_G] = KEY_G,
+  [AT_KEY_00_H] = KEY_H,
+  [AT_KEY_00_J] = KEY_J,
+  [AT_KEY_00_K] = KEY_K,
+  [AT_KEY_00_L] = KEY_L,
+  [AT_KEY_00_Semicolon] = KEY_SEMICOLON,
+  [AT_KEY_00_Apostrophe] = KEY_APOSTROPHE,
+  [AT_KEY_00_Enter] = KEY_ENTER,
+
+  [AT_KEY_00_LeftShift] = KEY_LEFTSHIFT,
+  [AT_KEY_00_Europe2] = KEY_102ND,
+  [AT_KEY_00_Z] = KEY_Z,
+  [AT_KEY_00_X] = KEY_X,
+  [AT_KEY_00_C] = KEY_C,
+  [AT_KEY_00_V] = KEY_V,
+  [AT_KEY_00_B] = KEY_B,
+  [AT_KEY_00_N] = KEY_N,
+  [AT_KEY_00_M] = KEY_M,
+  [AT_KEY_00_Comma] = KEY_COMMA,
+  [AT_KEY_00_Period] = KEY_DOT,
+  [AT_KEY_00_Slash] = KEY_SLASH,
+  [AT_KEY_00_RightShift] = KEY_RIGHTSHIFT,
+
+  [AT_KEY_00_LeftControl] = KEY_LEFTCTRL,
+  [AT_KEY_00_LeftAlt] = KEY_LEFTALT,
+  [AT_KEY_00_Space] = KEY_SPACE,
+
+  [AT_KEY_00_NumLock] = KEY_NUMLOCK,
+  [AT_KEY_00_KPAsterisk] = KEY_KPASTERISK,
+  [AT_KEY_00_KPMinus] = KEY_KPMINUS,
+  [AT_KEY_00_KPPlus] = KEY_KPPLUS,
+  [AT_KEY_00_KPPeriod] = KEY_KPDOT,
+  [AT_KEY_00_KP0] = KEY_KP0,
+  [AT_KEY_00_KP1] = KEY_KP1,
+  [AT_KEY_00_KP2] = KEY_KP2,
+  [AT_KEY_00_KP3] = KEY_KP3,
+  [AT_KEY_00_KP4] = KEY_KP4,
+  [AT_KEY_00_KP5] = KEY_KP5,
+  [AT_KEY_00_KP6] = KEY_KP6,
+  [AT_KEY_00_KP7] = KEY_KP7,
+  [AT_KEY_00_KP8] = KEY_KP8,
+  [AT_KEY_00_KP9] = KEY_KP9,
+
+  [AT_KEY_00_KPComma] = KEY_KPCOMMA,
+  [AT_KEY_00_KPEqual] = KEY_KPEQUAL,
+
+  [AT_KEY_00_International1] = KEY_RO,
+  [AT_KEY_00_International2] = KEY_KATAKANAHIRAGANA,
+  [AT_KEY_00_International3] = KEY_YEN,
+  [AT_KEY_00_International4] = KEY_HENKAN,
+  [AT_KEY_00_International5] = KEY_MUHENKAN,
+  [AT_KEY_00_International6] = KEY_KPJPCOMMA,
+
+  [AT_KEY_00_Language3] = KEY_KATAKANA,
+  [AT_KEY_00_Language4] = KEY_HIRAGANA,
+};
+
+LINUX_KEY_MAP(atE0) = {
+  [AT_KEY_E0_LeftGUI] = KEY_LEFTMETA,
+  [AT_KEY_E0_RightAlt] = KEY_RIGHTALT,
+  [AT_KEY_E0_RightGUI] = KEY_RIGHTMETA,
+  [AT_KEY_E0_Context] = KEY_COMPOSE,
+  [AT_KEY_E0_RightControl] = KEY_RIGHTCTRL,
+
+  [AT_KEY_E0_Insert] = KEY_INSERT,
+  [AT_KEY_E0_Delete] = KEY_DELETE,
+  [AT_KEY_E0_Home] = KEY_HOME,
+  [AT_KEY_E0_End] = KEY_END,
+  [AT_KEY_E0_PageUp] = KEY_PAGEUP,
+  [AT_KEY_E0_PageDown] = KEY_PAGEDOWN,
+
+  [AT_KEY_E0_ArrowUp] = KEY_UP,
+  [AT_KEY_E0_ArrowLeft] = KEY_LEFT,
+  [AT_KEY_E0_ArrowDown] = KEY_DOWN,
+  [AT_KEY_E0_ArrowRight] = KEY_RIGHT,
+
+  [AT_KEY_E0_KPEnter] = KEY_KPENTER,
+  [AT_KEY_E0_KPSlash] = KEY_KPSLASH,
+
+  [AT_KEY_E0_Copy] = KEY_COPY,
+  [AT_KEY_E0_Cut] = KEY_CUT,
+  [AT_KEY_E0_Paste] = KEY_PASTE,
+  [AT_KEY_E0_Undo] = KEY_UNDO,
+  [AT_KEY_E0_Redo] = KEY_REDO,
+
+  [AT_KEY_E0_MyComputer] = KEY_COMPUTER,
+  [AT_KEY_E0_Calculator] = KEY_CALC,
+  [AT_KEY_E0_Mail] = KEY_MAIL,
+  [AT_KEY_E0_Mail_X1] = KEY_MAIL,
+
+  [AT_KEY_E0_WebHome] = KEY_HOMEPAGE,
+  [AT_KEY_E0_WebBookmarks] = KEY_BOOKMARKS,
+  [AT_KEY_E0_WebSearch] = KEY_SEARCH,
+  [AT_KEY_E0_WebBack] = KEY_BACK,
+  [AT_KEY_E0_WebForward] = KEY_FORWARD,
+  [AT_KEY_E0_WebRefresh] = KEY_REFRESH,
+  [AT_KEY_E0_WebStop] = KEY_STOP,
+
+  [AT_KEY_E0_Mute] = KEY_MUTE,
+  [AT_KEY_E0_VolumeDown] = KEY_VOLUMEDOWN,
+  [AT_KEY_E0_VolumeUp] = KEY_VOLUMEUP,
+
+  [AT_KEY_E0_MediaVideo] = KEY_MEDIA,
+  [AT_KEY_E0_MediaPlayPause] = KEY_PLAYPAUSE,
+  [AT_KEY_E0_MediaStop] = KEY_STOPCD,
+  [AT_KEY_E0_MediaPrevious] = KEY_PREVIOUSSONG,
+  [AT_KEY_E0_MediaNext] = KEY_NEXTSONG,
+
+  [AT_KEY_E0_Power] = KEY_POWER,
+  [AT_KEY_E0_Sleep] = KEY_SLEEP,
+  [AT_KEY_E0_Wake] = KEY_WAKEUP,
+};
+
+LINUX_KEY_MAP(atE1) = {
+  [AT_KEY_E1_Pause] = KEY_PAUSE,
+};
+
+LINUX_KEY_MAP(ps2) = {
+  [PS2_KEY_Escape] = KEY_ESC,
+  [PS2_KEY_F1] = KEY_F1,
+  [PS2_KEY_F2] = KEY_F2,
+  [PS2_KEY_F3] = KEY_F3,
+  [PS2_KEY_F4] = KEY_F4,
+  [PS2_KEY_F5] = KEY_F5,
+  [PS2_KEY_F6] = KEY_F6,
+  [PS2_KEY_F7] = KEY_F7,
+  [PS2_KEY_F8] = KEY_F8,
+  [PS2_KEY_F9] = KEY_F9,
+  [PS2_KEY_F10] = KEY_F10,
+  [PS2_KEY_F11] = KEY_F11,
+  [PS2_KEY_F12] = KEY_F12,
+  [PS2_KEY_Pause] = KEY_PAUSE,
+  [PS2_KEY_ScrollLock] = KEY_SCROLLLOCK,
+
+  [PS2_KEY_Grave] = KEY_GRAVE,
+  [PS2_KEY_1] = KEY_1,
+  [PS2_KEY_2] = KEY_2,
+  [PS2_KEY_3] = KEY_3,
+  [PS2_KEY_4] = KEY_4,
+  [PS2_KEY_5] = KEY_5,
+  [PS2_KEY_6] = KEY_6,
+  [PS2_KEY_7] = KEY_7,
+  [PS2_KEY_8] = KEY_8,
+  [PS2_KEY_9] = KEY_9,
+  [PS2_KEY_0] = KEY_0,
+  [PS2_KEY_Minus] = KEY_MINUS,
+  [PS2_KEY_Equal] = KEY_EQUAL,
+  [PS2_KEY_Backspace] = KEY_BACKSPACE,
+
+  [PS2_KEY_Tab] = KEY_TAB,
+  [PS2_KEY_Q] = KEY_Q,
+  [PS2_KEY_W] = KEY_W,
+  [PS2_KEY_E] = KEY_E,
+  [PS2_KEY_R] = KEY_R,
+  [PS2_KEY_T] = KEY_T,
+  [PS2_KEY_Y] = KEY_Y,
+  [PS2_KEY_U] = KEY_U,
+  [PS2_KEY_I] = KEY_I,
+  [PS2_KEY_O] = KEY_O,
+  [PS2_KEY_P] = KEY_P,
+  [PS2_KEY_LeftBracket] = KEY_LEFTBRACE,
+  [PS2_KEY_RightBracket] = KEY_RIGHTBRACE,
+  [PS2_KEY_Backslash] = KEY_BACKSLASH,
+  [PS2_KEY_Europe1] = KEY_BACKSLASH,
+
+  [PS2_KEY_CapsLock] = KEY_CAPSLOCK,
+  [PS2_KEY_A] = KEY_A,
+  [PS2_KEY_S] = KEY_S,
+  [PS2_KEY_D] = KEY_D,
+  [PS2_KEY_F] = KEY_F,
+  [PS2_KEY_G] = KEY_G,
+  [PS2_KEY_H] = KEY_H,
+  [PS2_KEY_J] = KEY_J,
+  [PS2_KEY_K] = KEY_K,
+  [PS2_KEY_L] = KEY_L,
+  [PS2_KEY_Semicolon] = KEY_SEMICOLON,
+  [PS2_KEY_Apostrophe] = KEY_APOSTROPHE,
+  [PS2_KEY_Enter] = KEY_ENTER,
+
+  [PS2_KEY_LeftShift] = KEY_LEFTSHIFT,
+  [PS2_KEY_Europe2] = KEY_102ND,
+  [PS2_KEY_Z] = KEY_Z,
+  [PS2_KEY_X] = KEY_X,
+  [PS2_KEY_C] = KEY_C,
+  [PS2_KEY_V] = KEY_V,
+  [PS2_KEY_B] = KEY_B,
+  [PS2_KEY_N] = KEY_N,
+  [PS2_KEY_M] = KEY_M,
+  [PS2_KEY_Comma] = KEY_COMMA,
+  [PS2_KEY_Period] = KEY_DOT,
+  [PS2_KEY_Slash] = KEY_SLASH,
+  [PS2_KEY_RightShift] = KEY_RIGHTSHIFT,
+
+  [PS2_KEY_LeftControl] = KEY_LEFTCTRL,
+  [PS2_KEY_LeftAlt] = KEY_LEFTALT,
+  [PS2_KEY_LeftGUI] = KEY_LEFTMETA,
+  [PS2_KEY_Space] = KEY_SPACE,
+  [PS2_KEY_RightAlt] = KEY_RIGHTALT,
+  [PS2_KEY_RightGUI] = KEY_RIGHTMETA,
+  [PS2_KEY_Context] = KEY_COMPOSE,
+  [PS2_KEY_RightControl] = KEY_RIGHTCTRL,
+
+  [PS2_KEY_Insert] = KEY_INSERT,
+  [PS2_KEY_Delete] = KEY_DELETE,
+  [PS2_KEY_Home] = KEY_HOME,
+  [PS2_KEY_End] = KEY_END,
+  [PS2_KEY_PageUp] = KEY_PAGEUP,
+  [PS2_KEY_PageDown] = KEY_PAGEDOWN,
+
+  [PS2_KEY_ArrowUp] = KEY_UP,
+  [PS2_KEY_ArrowLeft] = KEY_LEFT,
+  [PS2_KEY_ArrowDown] = KEY_DOWN,
+  [PS2_KEY_ArrowRight] = KEY_RIGHT,
+
+  [PS2_KEY_NumLock] = KEY_NUMLOCK,
+  [PS2_KEY_KPSlash] = KEY_KPSLASH,
+  [PS2_KEY_KPAsterisk] = KEY_KPASTERISK,
+  [PS2_KEY_KPMinus] = KEY_KPMINUS,
+  [PS2_KEY_KPPlus] = KEY_KPPLUS,
+  [PS2_KEY_KPEnter] = KEY_KPENTER,
+  [PS2_KEY_KPPeriod] = KEY_KPDOT,
+  [PS2_KEY_KP0] = KEY_KP0,
+  [PS2_KEY_KP1] = KEY_KP1,
+  [PS2_KEY_KP2] = KEY_KP2,
+  [PS2_KEY_KP3] = KEY_KP3,
+  [PS2_KEY_KP4] = KEY_KP4,
+  [PS2_KEY_KP5] = KEY_KP5,
+  [PS2_KEY_KP6] = KEY_KP6,
+  [PS2_KEY_KP7] = KEY_KP7,
+  [PS2_KEY_KP8] = KEY_KP8,
+  [PS2_KEY_KP9] = KEY_KP9,
+  [PS2_KEY_KPComma] = KEY_KPCOMMA,
+
+  [PS2_KEY_International1] = KEY_RO,
+  [PS2_KEY_International2] = KEY_KATAKANAHIRAGANA,
+  [PS2_KEY_International3] = KEY_YEN,
+  [PS2_KEY_International4] = KEY_HENKAN,
+  [PS2_KEY_International5] = KEY_MUHENKAN,
+};
+
+LINUX_KEY_MAP(hid) = {
+  [HID_KEY_Escape] = KEY_ESC,
+  [HID_KEY_F1] = KEY_F1,
+  [HID_KEY_F2] = KEY_F2,
+  [HID_KEY_F3] = KEY_F3,
+  [HID_KEY_F4] = KEY_F4,
+  [HID_KEY_F5] = KEY_F5,
+  [HID_KEY_F6] = KEY_F6,
+  [HID_KEY_F7] = KEY_F7,
+  [HID_KEY_F8] = KEY_F8,
+  [HID_KEY_F9] = KEY_F9,
+  [HID_KEY_F10] = KEY_F10,
+  [HID_KEY_F11] = KEY_F11,
+  [HID_KEY_F12] = KEY_F12,
+  [HID_KEY_Pause] = KEY_PAUSE,
+  [HID_KEY_ScrollLock] = KEY_SCROLLLOCK,
+
+  [HID_KEY_F13] = KEY_F13,
+  [HID_KEY_F14] = KEY_F14,
+  [HID_KEY_F15] = KEY_F15,
+  [HID_KEY_F16] = KEY_F16,
+  [HID_KEY_F17] = KEY_F17,
+  [HID_KEY_F18] = KEY_F18,
+  [HID_KEY_F19] = KEY_F19,
+  [HID_KEY_F20] = KEY_F20,
+  [HID_KEY_F21] = KEY_F21,
+  [HID_KEY_F22] = KEY_F22,
+  [HID_KEY_F23] = KEY_F23,
+  [HID_KEY_F24] = KEY_F24,
+
+  [HID_KEY_Grave] = KEY_GRAVE,
+  [HID_KEY_1] = KEY_1,
+  [HID_KEY_2] = KEY_2,
+  [HID_KEY_3] = KEY_3,
+  [HID_KEY_4] = KEY_4,
+  [HID_KEY_5] = KEY_5,
+  [HID_KEY_6] = KEY_6,
+  [HID_KEY_7] = KEY_7,
+  [HID_KEY_8] = KEY_8,
+  [HID_KEY_9] = KEY_9,
+  [HID_KEY_0] = KEY_0,
+  [HID_KEY_Minus] = KEY_MINUS,
+  [HID_KEY_Equal] = KEY_EQUAL,
+  [HID_KEY_Backspace] = KEY_BACKSPACE,
+
+  [HID_KEY_Tab] = KEY_TAB,
+  [HID_KEY_Q] = KEY_Q,
+  [HID_KEY_W] = KEY_W,
+  [HID_KEY_E] = KEY_E,
+  [HID_KEY_R] = KEY_R,
+  [HID_KEY_T] = KEY_T,
+  [HID_KEY_Y] = KEY_Y,
+  [HID_KEY_U] = KEY_U,
+  [HID_KEY_I] = KEY_I,
+  [HID_KEY_O] = KEY_O,
+  [HID_KEY_P] = KEY_P,
+  [HID_KEY_LeftBracket] = KEY_LEFTBRACE,
+  [HID_KEY_RightBracket] = KEY_RIGHTBRACE,
+  [HID_KEY_Backslash] = KEY_BACKSLASH,
+  [HID_KEY_Europe1] = KEY_BACKSLASH,
+
+  [HID_KEY_CapsLock] = KEY_CAPSLOCK,
+  [HID_KEY_A] = KEY_A,
+  [HID_KEY_S] = KEY_S,
+  [HID_KEY_D] = KEY_D,
+  [HID_KEY_F] = KEY_F,
+  [HID_KEY_G] = KEY_G,
+  [HID_KEY_H] = KEY_H,
+  [HID_KEY_J] = KEY_J,
+  [HID_KEY_K] = KEY_K,
+  [HID_KEY_L] = KEY_L,
+  [HID_KEY_Semicolon] = KEY_SEMICOLON,
+  [HID_KEY_Apostrophe] = KEY_APOSTROPHE,
+  [HID_KEY_Enter] = KEY_ENTER,
+
+  [HID_KEY_LeftShift] = KEY_LEFTSHIFT,
+  [HID_KEY_Europe2] = KEY_102ND,
+  [HID_KEY_Z] = KEY_Z,
+  [HID_KEY_X] = KEY_X,
+  [HID_KEY_C] = KEY_C,
+  [HID_KEY_V] = KEY_V,
+  [HID_KEY_B] = KEY_B,
+  [HID_KEY_N] = KEY_N,
+  [HID_KEY_M] = KEY_M,
+  [HID_KEY_Comma] = KEY_COMMA,
+  [HID_KEY_Period] = KEY_DOT,
+  [HID_KEY_Slash] = KEY_SLASH,
+  [HID_KEY_RightShift] = KEY_RIGHTSHIFT,
+
+  [HID_KEY_LeftControl] = KEY_LEFTCTRL,
+  [HID_KEY_LeftAlt] = KEY_LEFTALT,
+  [HID_KEY_LeftGUI] = KEY_LEFTMETA,
+  [HID_KEY_Space] = KEY_SPACE,
+  [HID_KEY_RightAlt] = KEY_RIGHTALT,
+  [HID_KEY_RightGUI] = KEY_RIGHTMETA,
+  [HID_KEY_Context] = KEY_COMPOSE,
+  [HID_KEY_RightControl] = KEY_RIGHTCTRL,
+
+  [HID_KEY_Insert] = KEY_INSERT,
+  [HID_KEY_Delete] = KEY_DELETE,
+  [HID_KEY_Home] = KEY_HOME,
+  [HID_KEY_End] = KEY_END,
+  [HID_KEY_PageUp] = KEY_PAGEUP,
+  [HID_KEY_PageDown] = KEY_PAGEDOWN,
+
+  [HID_KEY_ArrowUp] = KEY_UP,
+  [HID_KEY_ArrowLeft] = KEY_LEFT,
+  [HID_KEY_ArrowDown] = KEY_DOWN,
+  [HID_KEY_ArrowRight] = KEY_RIGHT,
+
+  [HID_KEY_NumLock] = KEY_NUMLOCK,
+  [HID_KEY_KPSlash] = KEY_KPSLASH,
+  [HID_KEY_KPAsterisk] = KEY_KPASTERISK,
+  [HID_KEY_KPMinus] = KEY_KPMINUS,
+  [HID_KEY_KPPlus] = KEY_KPPLUS,
+  [HID_KEY_KPEnter] = KEY_KPENTER,
+  [HID_KEY_KPPeriod] = KEY_KPDOT,
+  [HID_KEY_KP0] = KEY_KP0,
+  [HID_KEY_KP1] = KEY_KP1,
+  [HID_KEY_KP2] = KEY_KP2,
+  [HID_KEY_KP3] = KEY_KP3,
+  [HID_KEY_KP4] = KEY_KP4,
+  [HID_KEY_KP5] = KEY_KP5,
+  [HID_KEY_KP6] = KEY_KP6,
+  [HID_KEY_KP7] = KEY_KP7,
+  [HID_KEY_KP8] = KEY_KP8,
+  [HID_KEY_KP9] = KEY_KP9,
+
+  [HID_KEY_KPComma] = KEY_KPCOMMA,
+  [HID_KEY_KPEqual] = KEY_KPEQUAL,
+
+  [HID_KEY_International1] = KEY_RO,
+  [HID_KEY_International2] = KEY_KATAKANAHIRAGANA,
+  [HID_KEY_International3] = KEY_YEN,
+  [HID_KEY_International4] = KEY_HENKAN,
+  [HID_KEY_International5] = KEY_MUHENKAN,
+  [HID_KEY_International6] = KEY_KPJPCOMMA,
+
+  [HID_KEY_Language3] = KEY_KATAKANA,
+  [HID_KEY_Language4] = KEY_HIRAGANA,
+  [HID_KEY_Language5] = KEY_ZENKAKUHANKAKU,
+
+  [HID_KEY_Copy] = KEY_COPY,
+  [HID_KEY_Cut] = KEY_CUT,
+  [HID_KEY_Paste] = KEY_PASTE,
+  [HID_KEY_Undo] = KEY_UNDO,
+
+  [HID_KEY_Mute] = KEY_MUTE,
+  [HID_KEY_VolumeDown] = KEY_VOLUMEDOWN,
+  [HID_KEY_VolumeUp] = KEY_VOLUMEUP,
+
+  [HID_KEY_Power] = KEY_POWER,
+};
+
+#define LINUX_KEY_MAP_DESCRIPTOR(type) { \
+  .name = #type, \
+  .keys = LINUX_KEY_MAP_NAME(type), \
+  .count = ARRAY_COUNT(LINUX_KEY_MAP_NAME(type)) \
+}
+
+const LinuxKeyMapDescriptor linuxKeyMapDescriptors[] = {
+  LINUX_KEY_MAP_DESCRIPTOR(xt00),
+  LINUX_KEY_MAP_DESCRIPTOR(xtE0),
+  LINUX_KEY_MAP_DESCRIPTOR(xtE1),
+  LINUX_KEY_MAP_DESCRIPTOR(at00),
+  LINUX_KEY_MAP_DESCRIPTOR(atE0),
+  LINUX_KEY_MAP_DESCRIPTOR(atE1),
+  LINUX_KEY_MAP_DESCRIPTOR(ps2),
+  LINUX_KEY_MAP_DESCRIPTOR(hid)
+};
+
+const unsigned char linuxKeyMapCount = ARRAY_COUNT(linuxKeyMapDescriptors);
+#endif /* HAVE_LINUX_INPUT_H */
+
+#ifdef HAVE_LINUX_UINPUT_H
+#include <linux/uinput.h>
+
+struct UinputObjectStruct {
+  int fileDescriptor;
+  BITMASK(pressedKeys, KEY_MAX+1, char);
+};
+#endif /* HAVE_LINUX_UINPUT_H */
+
+int
+installKernelModule (const char *name, unsigned char *status) {
+  if (status && *status) return *status == 2;
+
+  {
+    const char *command = "modprobe";
+    char buffer[0X100];
+    if (status) ++*status;
+
+    {
+      const char *path = "/proc/sys/kernel/modprobe";
+      FILE *stream = fopen(path, "r");
+
+      if (stream) {
+        char *line = fgets(buffer, sizeof(buffer), stream);
+
+        if (line) {
+          size_t length = strlen(line);
+          if (length && (line[length-1] == '\n')) line[--length] = 0;
+          if (length) command = line;
+        }
+
+        fclose(stream);
+      } else {
+        logMessage(LOG_WARNING, "cannot open %s: %s", path, strerror(errno));
+      }
+    }
+
+    {
+      const char *const arguments[] = {command, "-q", name, NULL};
+      int ok = executeHostCommand(arguments) == 0;
+
+      if (!ok) {
+        logMessage(LOG_WARNING, "kernel module not installed: %s", name);
+        return 0;
+      }
+
+      if (status) ++*status;
+    }
+  }
+
+  return 1;
+}
+
+int
+installSpeakerModule (void) {
+  static unsigned char status = 0;
+  return installKernelModule("pcspkr", &status);
+}
+
+int
+installUinputModule (void) {
+  static unsigned char status = 0;
+  int wait = !status;
+  int installed = installKernelModule("uinput", &status);
+
+  if (!installed) wait = 0;
+  if (wait) asyncWait(500);
+  return installed;
+}
+
+static int
+openDevice (const char *path, int flags, int allowModeSubset) {
+  int descriptor;
+
+  #ifdef O_CLOEXEC
+  flags |= O_CLOEXEC;
+  #endif /* O_CLOEXEC */
+
+  if ((descriptor = open(path, flags)) != -1) goto opened;
+  if (!allowModeSubset) goto failed;
+  if ((flags & O_ACCMODE) != O_RDWR) goto failed;
+  flags &= ~O_ACCMODE;
+
+  {
+    int error = errno;
+
+    if (errno == EACCES) goto tryWriteOnly;
+    if (errno == EROFS) goto tryReadOnly;
+    goto failed;
+
+  tryWriteOnly:
+    if ((descriptor = open(path, (flags | O_WRONLY))) != -1) goto opened;
+
+  tryReadOnly:
+    if ((descriptor = open(path, (flags | O_RDONLY))) != -1) goto opened;
+
+    errno = error;
+  }
+
+failed:
+  logMessage(LOG_DEBUG, "cannot open device: %s: %s", path, strerror(errno));
+  return -1;
+
+opened:
+  logMessage(LOG_DEBUG, "device opened: %s: fd=%d", path, descriptor);
+  return descriptor;
+}
+
+static int
+canContainDevices (const char *directory) {
+  struct statvfs vfs;
+
+  if (statvfs(directory, &vfs) == -1) {
+    logSystemError("statvfs");
+  } else if (vfs.f_flag & ST_NODEV) {
+    logMessage(LOG_WARNING, "cannot contain device files: %s", directory);
+    errno = EPERM;
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+canCreateDevice (const char *path) {
+  int yes = 0;
+  char *directory;
+
+  if ((directory = getPathDirectory(path))) {
+    if (canContainDevices(directory)) yes = 1;
+    free(directory);
+  }
+
+  return yes;
+}
+
+static int
+createCharacterDevice (const char *path, int flags, int major, int minor) {
+  int descriptor = -1;
+
+  if (canCreateDevice(path)) {
+    descriptor = openDevice(path, flags, 0);
+  }
+
+  if (descriptor == -1) {
+    if (errno == ENOENT) {
+      mode_t mode = S_IFCHR | S_IRUSR | S_IWUSR;
+
+      if (mknod(path, mode, makedev(major, minor)) == -1) {
+        logMessage(LOG_DEBUG,
+          "cannot create device: %s: %s", path, strerror(errno)
+        );
+      } else {
+        logMessage(LOG_DEBUG,
+          "device created: %s mode=%06o major=%d minor=%d",
+          path, mode, major, minor
+        );
+
+        descriptor = openDevice(path, flags, 0);
+      }
+    }
+  }
+
+  return descriptor;
+}
+
+int
+openCharacterDevice (const char *name, int flags, int major, int minor) {
+  char *path = getDevicePath(name);
+  int descriptor;
+
+  if (!path) {
+    descriptor = -1;
+  } else if ((descriptor = openDevice(path, flags, 1)) == -1) {
+    if ((errno == ENOENT) || (errno == EACCES)) {
+      free(path);
+
+      if ((path = makeWritablePath(locatePathName(name)))) {
+        descriptor = createCharacterDevice(path, flags, major, minor);
+      }
+    }
+  }
+
+  if (descriptor != -1) {
+    int ok = 0;
+    struct stat status;
+
+    if (fstat(descriptor, &status) == -1) {
+      logMessage(LOG_DEBUG, "cannot fstat device: %d [%s]: %s",
+                 descriptor, path, strerror(errno));
+    } else if (!S_ISCHR(status.st_mode)) {
+      logMessage(LOG_DEBUG, "not a character device: %s: fd=%d", path, descriptor);
+    } else {
+      ok = 1;
+    }
+
+    if (!ok) {
+      close(descriptor);
+      logMessage(LOG_DEBUG, "device closed: %s: fd=%d", path, descriptor);
+      descriptor = -1;
+    }
+  }
+
+  if (path) free(path);
+  return descriptor;
+}
+
+UinputObject *
+newUinputObject (const char *name) {
+#ifdef HAVE_LINUX_UINPUT_H
+  UinputObject *uinput;
+
+  if ((uinput = malloc(sizeof(*uinput)))) {
+    memset(uinput, 0, sizeof(*uinput));
+    installUinputModule();
+
+    const char *device;
+    {
+      static const char *const names[] = {"uinput", "input/uinput", NULL};
+      device = resolveDeviceName(names, 0, "uinput");
+    }
+
+    if (device) {
+      if ((uinput->fileDescriptor = openCharacterDevice(device, O_RDWR, MISC_MAJOR, 223)) != -1) {
+        struct uinput_user_dev description;
+        
+        memset(&description, 0, sizeof(description));
+        snprintf(description.name, sizeof(description.name),
+                 "%s %s %s",
+                 PACKAGE_NAME, PACKAGE_VERSION, name);
+
+        if (write(uinput->fileDescriptor, &description, sizeof(description)) != -1) {
+#ifdef UI_SET_PHYS
+          {
+            extern const char *__progname;
+            char topology[0X40];
+
+            snprintf(topology, sizeof(topology),
+                     "pid-%"PRIu32"/%s/%d",
+                     (uint32_t)getpid(), __progname, uinput->fileDescriptor);
+
+            if (ioctl(uinput->fileDescriptor, UI_SET_PHYS, topology) == -1) {
+              logSystemError("ioctl[UI_SET_PHYS]");
+            }
+          }
+#endif /* UI_SET_PHYS */
+
+          logMessage(LOG_DEBUG, "uinput opened: %s: %s fd=%d",
+                     device, description.name, uinput->fileDescriptor);
+
+          return uinput;
+        } else {
+          logSystemError("write(struct uinput_user_dev)");
+        }
+
+        close(uinput->fileDescriptor);
+      } else {
+        logMessage(LOG_DEBUG, "cannot open uinput device: %s: %s", device, strerror(errno));
+      }
+    }
+
+    free(uinput);
+    uinput = NULL;
+  } else {
+    logMallocError();
+  }
+#else /* HAVE_LINUX_UINPUT_H */
+  logMessage(LOG_WARNING, "uinput support not available");
+  errno = ENOSYS;
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return NULL;
+}
+
+void
+destroyUinputObject (UinputObject *uinput) {
+#ifdef HAVE_LINUX_UINPUT_H
+  releasePressedKeys(uinput);
+  close(uinput->fileDescriptor);
+  free(uinput);
+#endif /* HAVE_LINUX_UINPUT_H */
+}
+
+int
+getUinputFileDescriptor (UinputObject *uinput) {
+  return uinput->fileDescriptor;
+}
+
+int
+createUinputDevice (UinputObject *uinput) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (ioctl(uinput->fileDescriptor, UI_DEV_CREATE) != -1) return 1;
+  logSystemError("ioctl[UI_DEV_CREATE]");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+int
+enableUinputEventType (UinputObject *uinput, int type) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (ioctl(uinput->fileDescriptor, UI_SET_EVBIT, type) != -1) return 1;
+  logSystemError("ioctl[UI_SET_EVBIT]");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+int
+writeInputEvent (UinputObject *uinput, uint16_t type, uint16_t code, int32_t value) {
+#ifdef HAVE_LINUX_UINPUT_H
+  struct timeval now;
+  gettimeofday(&now, NULL);
+
+  struct input_event event = {
+    .input_event_sec = now.tv_sec,
+    .input_event_usec = now.tv_usec,
+
+    .type = type,
+    .code = code,
+    .value = value,
+  };
+
+  if (write(uinput->fileDescriptor, &event, sizeof(event)) != -1) return 1;
+  logSystemError("write(struct input_event)");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+static int
+writeSynReport (UinputObject *uinput) {
+  return writeInputEvent(uinput, EV_SYN, SYN_REPORT, 0);
+}
+
+int
+enableUinputKey (UinputObject *uinput, int key) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (ioctl(uinput->fileDescriptor, UI_SET_KEYBIT, key) != -1) return 1;
+  logSystemError("ioctl[UI_SET_KEYBIT]");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+int
+writeKeyEvent (UinputObject *uinput, int key, int press) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (writeInputEvent(uinput, EV_KEY, key, press)) {
+    if (press) {
+      BITMASK_SET(uinput->pressedKeys, key);
+    } else {
+      BITMASK_CLEAR(uinput->pressedKeys, key);
+    }
+
+    if (writeSynReport(uinput)) {
+      return 1;
+    }
+  }
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+int
+releasePressedKeys (UinputObject *uinput) {
+#ifdef HAVE_LINUX_UINPUT_H
+  unsigned int key;
+
+  for (key=0; key<=KEY_MAX; key+=1) {
+    if (BITMASK_TEST(uinput->pressedKeys, key)) {
+      if (!writeKeyEvent(uinput, key, 0)) return 0;
+      BITMASK_CLEAR(uinput->pressedKeys, key);
+    }
+  }
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 1;
+}
+
+int
+writeRepeatDelay (UinputObject *uinput, int delay) {
+  if (writeInputEvent(uinput, EV_REP, REP_DELAY, delay)) {
+    if (writeSynReport(uinput)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+writeRepeatPeriod (UinputObject *uinput, int period) {
+  if (writeInputEvent(uinput, EV_REP, REP_PERIOD, period)) {
+    if (writeSynReport(uinput)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+#ifdef HAVE_LINUX_INPUT_H
+static int
+enableKeyboardKeys (UinputObject *uinput) {
+  const LinuxKeyMapDescriptor *map = linuxKeyMapDescriptors;
+  const LinuxKeyMapDescriptor *end = map + linuxKeyMapCount;
+  BITMASK(enabledKeys, KEY_MAX+1, char);
+
+  if (!enableUinputEventType(uinput, EV_KEY)) return 0;
+  BITMASK_ZERO(enabledKeys);
+
+  while (map < end) {
+    unsigned int code;
+
+    for (code=0; code<map->count; code+=1) {
+      LinuxKeyCode key = map->keys[code];
+
+      if (key) {
+        if (!BITMASK_TEST(enabledKeys, key)) {
+          BITMASK_SET(enabledKeys, key);
+          if (!enableUinputKey(uinput, key)) return 0;
+        }
+      }
+    }
+
+    map += 1;
+  }
+
+  return 1;
+}
+#endif /* HAVE_LINUX_INPUT_H */
+
+int
+enableUinputSound (UinputObject *uinput, int sound) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (ioctl(uinput->fileDescriptor, UI_SET_SNDBIT, sound) != -1) return 1;
+  logSystemError("ioctl[UI_SET_SNDBIT]");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+int
+enableUinputLed (UinputObject *uinput, int led) {
+#ifdef HAVE_LINUX_UINPUT_H
+  if (ioctl(uinput->fileDescriptor, UI_SET_LEDBIT, led) != -1) return 1;
+  logSystemError("ioctl[UI_SET_LEDBIT]");
+#endif /* HAVE_LINUX_UINPUT_H */
+
+  return 0;
+}
+
+UinputObject *
+newUinputKeyboard (const char *name) {
+#ifdef HAVE_LINUX_INPUT_H
+  UinputObject *uinput;
+
+  if ((uinput = newUinputObject(name))) {
+    if (enableKeyboardKeys(uinput)) {
+      if (enableUinputEventType(uinput, EV_REP)) {
+        if (createUinputDevice(uinput)) {
+          return uinput;
+        }
+      }
+    }
+
+    destroyUinputObject(uinput);
+  }
+#endif /* HAVE_LINUX_INPUT_H */
+
+  return NULL;
+}
+
+struct InputEventMonitorStruct {
+  UinputObject *uinputObject;
+  int fileDescriptor;
+  AsyncHandle asyncHandle;
+
+  UinputObjectPreparer *prepareUinputObject;
+  InputEventHandler *handleInputEvent;
+};
+
+static void
+closeInputEventMonitor (InputEventMonitor *monitor) {
+  close(monitor->fileDescriptor);
+  monitor->fileDescriptor = -1;
+}
+
+ASYNC_INPUT_CALLBACK(handleInterceptedInputEvent) {
+  InputEventMonitor *monitor = parameters->data;
+  static const char label[] = "input event monitor";
+
+  if (parameters->error) {
+    logMessage(LOG_DEBUG, "%s read error: fd=%d: %s",
+               label, monitor->fileDescriptor, strerror(parameters->error));
+    closeInputEventMonitor(monitor);
+  } else if (parameters->end) {
+    logMessage(LOG_DEBUG, "%s end-of-file: fd=%d",
+               label, monitor->fileDescriptor);
+    closeInputEventMonitor(monitor);
+  } else {
+    const struct input_event *event = parameters->buffer;
+
+    if (parameters->length >= sizeof(*event)) {
+      monitor->handleInputEvent(event);
+      return sizeof(*event);
+    }
+  }
+
+  return 0;
+}
+
+InputEventMonitor *
+newInputEventMonitor (
+  const char *name,
+  UinputObjectPreparer *prepareUinputObject,
+  InputEventHandler *handleInputEvent
+) {
+  InputEventMonitor *monitor;
+
+  if ((monitor = malloc(sizeof(*monitor)))) {
+    memset(monitor, 0, sizeof(*monitor));
+    monitor->prepareUinputObject = prepareUinputObject;
+    monitor->handleInputEvent = handleInputEvent;
+
+    if ((monitor->uinputObject = newUinputObject(name))) {
+      monitor->fileDescriptor = getUinputFileDescriptor(monitor->uinputObject);
+
+      if (prepareUinputObject(monitor->uinputObject)) {
+        if (createUinputDevice(monitor->uinputObject)) {
+          if (asyncReadFile(&monitor->asyncHandle, monitor->fileDescriptor,
+                            sizeof(struct input_event),
+                            handleInterceptedInputEvent, monitor)) {
+            logMessage(LOG_DEBUG, "input event monitor opened: fd=%d",
+                       monitor->fileDescriptor);
+
+            return monitor;
+          }
+        }
+      }
+
+      destroyUinputObject(monitor->uinputObject);
+    }
+
+    free(monitor);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyInputEventMonitor (InputEventMonitor *monitor) {
+  asyncCancelRequest(monitor->asyncHandle);
+  destroyUinputObject(monitor->uinputObject);
+  free(monitor);
+}
+
+void
+initializeSystemObject (void) {
+}
diff --git a/Programs/system_msdos.c b/Programs/system_msdos.c
new file mode 100644
index 0000000..1b052bb
--- /dev/null
+++ b/Programs/system_msdos.c
@@ -0,0 +1,389 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <dpmi.h>
+#include <pc.h>
+#include <dos.h>
+#include <go32.h>
+#include <crt0.h>
+#include <sys/farptr.h>
+
+#include "log.h"
+#include "system.h"
+#include "system_msdos.h"
+
+int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY;
+
+/* reduce image */
+int _stklen = 0X2000;
+
+void __crt0_load_environment_file(char *_app_name) { return; }
+char **__crt0_glob_function(char *_arg) { return 0; }
+
+static void tsrExit (void) NORETURN;
+
+/* disable this bit of magic as it causes a page fault on exit */
+#if 0
+/* Start undocumented way to make exception handling disappear (v2.03) */
+short __djgpp_ds_alias;
+void __djgpp_exception_processor(void) { return; }
+void __djgpp_exception_setup(void) { return; }
+void __djgpp_exception_toggle(void) { return; }
+int __djgpp_set_ctrl_c(int enable) { return 0; }
+void __maybe_fix_w2k_ntvdm_bug(void) { }
+void abort(void) { tsrExit(); }
+void _exit(int status) { tsrExit(); }
+int raise(int sig) { return 0; }
+void *signal(int signum, void*handler) { return NULL; }
+/* End undocumented way to make exception handling disappear */
+#endif
+
+#define TIMER_INTERRUPT 0X08
+#define DOS_INTERRUPT   0X21
+#define IDLE_INTERRUPT  0X28
+
+/* For saving interrupt and main contexts */
+
+typedef char FpuState[(7+20)*8];
+typedef struct {
+  unsigned short segment;
+  unsigned short offset;
+} DiskTransferAddress;
+
+typedef struct {
+  FpuState fpu;
+  DiskTransferAddress dta;
+  unsigned short psp;
+} State;
+
+static State mainState;
+static State interruptState;
+
+static jmp_buf mainContext;
+static jmp_buf interruptContext;
+
+static int isBackgrounded = 0; /* whether we really TSR */
+
+static unsigned long inDosFlagPointer;
+static unsigned long criticalOffset;
+
+/* prevent reentrancy */
+static volatile int inTimerInterrupt = 0;
+static volatile int inIdleInterrupt = 0;
+
+static inline int
+inInterrupt (void) {
+  return inTimerInterrupt || inIdleInterrupt;
+}
+
+static volatile unsigned long elapsedTickCount;
+static volatile unsigned long elapsedTickIncrement;
+
+static __dpmi_regs idleRegisters;
+
+static _go32_dpmi_seginfo origTimerSeginfo, timerSeginfo;
+static _go32_dpmi_seginfo origIdleSeginfo,  idleSeginfo;
+
+/* handle Program Segment Prefix switch for proper file descriptor table */
+static unsigned short
+getProgramSegmentPrefix (void) {
+  __dpmi_regs r;
+
+  r.h.ah = 0X51;
+  __dpmi_int(DOS_INTERRUPT, &r);
+  return r.x.bx;
+}
+
+static void
+setProgramSegmentPrefix (unsigned short segment) {
+  __dpmi_regs r;
+
+  r.h.ah = 0X50;
+  r.x.bx = segment;
+  __dpmi_int(DOS_INTERRUPT, &r);
+}
+
+/* handle Disk Transfer Address switch since djgpp uses it for FindFirst/FindNext */
+static void
+getDiskTransferAddress (DiskTransferAddress *dta) {
+  __dpmi_regs r;
+
+  r.h.ah = 0X2F;
+  __dpmi_int(DOS_INTERRUPT, &r);
+
+  dta->segment = r.x.es;
+  dta->offset = r.x.bx;
+}
+
+static void
+setDiskTransferAddress (const DiskTransferAddress *dta) {
+  __dpmi_regs r;
+
+  r.h.ah = 0X1A;
+  r.x.ds = dta->segment;
+  r.x.dx = dta->offset;
+  __dpmi_int(DOS_INTERRUPT, &r);
+}
+
+unsigned short
+msdosGetCodePage (void) {
+  __dpmi_regs r;
+
+  r.h.ah = 0X66;
+  r.h.al = 0X01;
+  __dpmi_int(DOS_INTERRUPT, &r);
+
+  return r.x.bx;
+}
+
+/* Handle FPU state switch */
+#define saveFpuState(p) asm volatile("fnsave (%0); fwait"::"r"(p):"memory")
+#define restoreFpuState(p) asm volatile("frstor (%0)"::"r"(p))
+
+static void
+saveState (State *state) {
+  saveFpuState(&state->fpu);
+  getDiskTransferAddress(&state->dta);
+  state->psp = getProgramSegmentPrefix();
+}
+
+static void
+restoreState (const State *state) {
+  restoreFpuState(&state->fpu);
+  setDiskTransferAddress(&state->dta);
+  setProgramSegmentPrefix(state->psp);
+}
+
+static unsigned short
+getTicksTillNextTimerInterrupt (void) {
+  unsigned char clo, chi;
+
+  outportb(0X43, 0XD2);
+  clo = inportb(0X40);
+  chi = inportb(0X40);
+
+  return (chi << 8) | clo;
+}
+
+/* Timer interrupt handler */
+static void
+timerInterruptHandler (void) {
+  elapsedTickCount += elapsedTickIncrement;
+  elapsedTickIncrement = getTicksTillNextTimerInterrupt();
+
+  if (!inInterrupt()) {
+    inTimerInterrupt = 1;
+    if (!setjmp(interruptContext)) longjmp(mainContext, 1);
+    inTimerInterrupt = 0;
+  }
+}
+
+/* Idle interrupt handler */
+static void
+idleInterruptHandler (_go32_dpmi_registers *r) {
+  if (!inInterrupt()) {
+    inIdleInterrupt = 1;
+    if (!setjmp(interruptContext)) longjmp(mainContext, 1);
+    inIdleInterrupt = 0;
+  }
+
+  r->x.cs = origIdleSeginfo.rm_segment;
+  r->x.ip = origIdleSeginfo.rm_offset;
+  _go32_dpmi_simulate_fcall_iret(r);
+}
+
+/* Try to restore interrupt handler */
+static int
+restore (int vector, _go32_dpmi_seginfo *seginfo, _go32_dpmi_seginfo *orig_seginfo) {
+  _go32_dpmi_seginfo cur_seginfo;
+
+  _go32_dpmi_get_protected_mode_interrupt_vector(vector, &cur_seginfo);
+
+  if ((cur_seginfo.pm_selector != seginfo->pm_selector) ||
+      (cur_seginfo.pm_offset != seginfo->pm_offset)) {
+    return 1;
+  }
+
+  _go32_dpmi_set_protected_mode_interrupt_vector(vector, orig_seginfo);
+  return 0;
+}
+
+/* TSR exit: trying to free as many resources as possible */
+static void
+tsrExit (void) {
+  if (isBackgrounded) {
+    unsigned long pspAddress = _go32_info_block.linear_address_of_original_psp;
+
+    if (restore(TIMER_INTERRUPT, &timerSeginfo, &origTimerSeginfo) +
+        restore(IDLE_INTERRUPT,  &idleSeginfo,  &origIdleSeginfo)) {
+      /* failed, hang */
+      setjmp(mainContext);
+      longjmp(interruptContext, 1);
+    }
+
+    {
+      __dpmi_regs r;
+
+      /* free environment */
+      r.x.es = _farpeekw(_dos_ds, pspAddress+0X2C);
+      r.x.ax = 0X4900;
+      __dpmi_int(DOS_INTERRUPT, &r);
+
+      /* free Program Segment Prefix */
+      r.x.es = pspAddress / 0X10;
+      r.x.ax = 0X4900;
+      __dpmi_int(DOS_INTERRUPT, &r);
+    }
+
+    /* and return */
+    longjmp(interruptContext, 1);
+
+    /* TODO: free protected mode memory */
+  }
+}
+
+/* go to background: TSR */
+void
+msdosBackground (void) {
+  __djgpp_set_ctrl_c(0);
+  saveState(&mainState);
+
+  if (!setjmp(mainContext)) {
+    __dpmi_regs regs;
+
+    /* set a chained Protected Mode Timer IRQ handler */
+    timerSeginfo.pm_selector = _my_cs();
+    timerSeginfo.pm_offset = (unsigned long)&timerInterruptHandler;
+    _go32_dpmi_get_protected_mode_interrupt_vector(TIMER_INTERRUPT, &origTimerSeginfo);
+    _go32_dpmi_chain_protected_mode_interrupt_vector(TIMER_INTERRUPT, &timerSeginfo);
+
+    /* set a real mode DOS Idle handler which calls back our Idle handler */
+    idleSeginfo.pm_selector = _my_cs();
+    idleSeginfo.pm_offset = (unsigned long)&idleInterruptHandler;
+    memset(&idleRegisters, 0, sizeof(idleRegisters));
+    _go32_dpmi_get_real_mode_interrupt_vector(IDLE_INTERRUPT, &origIdleSeginfo);
+    _go32_dpmi_allocate_real_mode_callback_iret(&idleSeginfo, &idleRegisters);
+    _go32_dpmi_set_real_mode_interrupt_vector(IDLE_INTERRUPT, &idleSeginfo);
+
+    /* Get InDos and Critical flags addresses */
+    regs.h.ah = 0X34;
+    __dpmi_int(DOS_INTERRUPT, &regs);
+    inDosFlagPointer = msdosMakeAddress(regs.x.es, regs.x.bx);
+
+    regs.x.ax = 0X5D06;
+    __dpmi_int(DOS_INTERRUPT, &regs);
+    criticalOffset = msdosMakeAddress(regs.x.ds, regs.x.si);
+
+    /* We are ready */
+    isBackgrounded = 1;
+
+    regs.x.ax = 0X3100;
+    msdosBreakAddress(0X100/*psp*/ + _go32_info_block.size_of_transfer_buffer, 0,
+                      &regs.x.dx, NULL);
+    __dpmi_int(DOS_INTERRUPT, &regs);
+
+    /* shouldn't be reached */
+    logMessage(LOG_ERR, "TSR installation failed");
+    isBackgrounded = 0;
+  }
+
+  saveState(&interruptState);
+  restoreState(&mainState);
+}
+
+unsigned long
+msdosUSleep (unsigned long microseconds) {
+  unsigned long ticks;
+
+  if (!isBackgrounded) {
+    usleep(microseconds);
+    return microseconds;
+  }
+
+  saveState(&mainState);
+  restoreState(&interruptState);
+
+  /* clock ticks to wait */
+  ticks = (microseconds * MSDOS_PIT_FREQUENCY) / UINT64_C(1000000);
+
+  /* we're starting in the middle of a timer period */
+  {
+    int wasEnabled = disable();
+
+    elapsedTickIncrement = getTicksTillNextTimerInterrupt();
+    elapsedTickCount = 0;
+
+    if (wasEnabled) enable();
+  }
+
+  while (elapsedTickCount < ticks) {
+    /* wait for next interrupt */
+    if (!setjmp(mainContext)) longjmp(interruptContext, 1);
+    /* interrupt returned */
+  }
+
+  /* wait for Dos to be free */
+  setjmp(mainContext);
+
+  /* critical sections of DOS are never reentrant */
+  if (_farpeekb(_dos_ds, criticalOffset)
+  /* DOS is busy but not idle */
+   || (!inIdleInterrupt && _farpeekb(_dos_ds, inDosFlagPointer)))
+    longjmp(interruptContext, 1);
+
+  saveState(&interruptState);
+  restoreState(&mainState);
+
+  return (elapsedTickCount * UINT64_C(1000000)) / MSDOS_PIT_FREQUENCY;
+}
+
+int
+vsnprintf (char *str, size_t size, const char *format, va_list ap) {
+  size_t alloc = 1024;
+  char *buf;
+  int ret;
+
+  if (alloc < size) alloc = size;
+  buf = alloca(alloc);
+  ret = vsprintf(buf, format, ap);
+  if (size > (ret + 1)) size = ret + 1;
+  memcpy(str, buf, size);
+  return ret;
+}
+
+int
+snprintf (char *str, size_t size, const char *format, ...) {
+  va_list argp;
+  int ret;
+
+  va_start(argp, format);
+  ret = vsnprintf(str, size, format, argp);
+  va_end(argp);
+
+  return ret;
+}
+
+void
+initializeSystemObject (void) {
+}
diff --git a/Programs/system_none.c b/Programs/system_none.c
new file mode 100644
index 0000000..e8cd046
--- /dev/null
+++ b/Programs/system_none.c
@@ -0,0 +1,25 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "system.h"
+
+void
+initializeSystemObject (void) {
+}
diff --git a/Programs/system_windows.c b/Programs/system_windows.c
new file mode 100644
index 0000000..900af3f
--- /dev/null
+++ b/Programs/system_windows.c
@@ -0,0 +1,669 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <fcntl.h>
+#include <errno.h>
+
+#include "log.h"
+#include "system.h"
+#include "system_windows.h"
+#include "timing.h"
+#include "messages.h"
+
+/* ntdll.dll */
+WIN_PROC_STUB(NtSetInformationProcess);
+
+
+/* kernel32.dll: console */
+WIN_PROC_STUB(AttachConsole);
+WIN_PROC_STUB(GetLocaleInfoEx);
+
+
+/* user32.dll */
+WIN_PROC_STUB(GetAltTabInfoA);
+WIN_PROC_STUB(SendInput);
+
+
+#ifdef __MINGW32__
+/* ws2_32.dll */
+WIN_PROC_STUB(getaddrinfo);
+WIN_PROC_STUB(freeaddrinfo);
+#endif /* __MINGW32__ */
+
+
+static void *
+loadLibrary (const char *name) {
+  HMODULE module = LoadLibrary(name);
+  if (!module) logMessage(LOG_DEBUG, "%s: %s", gettext("cannot load library"), name);
+  return module;
+}
+
+static void *
+getProcedure (HMODULE module, const char *name) {
+  void *address = module? GetProcAddress(module, name): NULL;
+  if (!address) logMessage(LOG_DEBUG, "%s: %s", gettext("cannot find procedure"), name);
+  return address;
+}
+
+static int
+addWindowsCommandLineCharacter (char **buffer, int *size, int *length, char character) {
+  if (*length == *size) {
+    char *newBuffer = realloc(*buffer, (*size = *size? *size<<1: 0X80));
+    if (!newBuffer) {
+      logSystemError("realloc");
+      return 0;
+    }
+    *buffer = newBuffer;
+  }
+
+  (*buffer)[(*length)++] = character;
+  return 1;
+}
+
+char *
+makeWindowsCommandLine (const char *const *arguments) {
+  const char backslash = '\\';
+  const char quote = '"';
+  char *buffer = NULL;
+  int size = 0;
+  int length = 0;
+
+#define ADD(c) if (!addWindowsCommandLineCharacter(&buffer, &size, &length, (c))) goto error
+  while (*arguments) {
+    const char *character = *arguments;
+    int backslashCount = 0;
+    int needQuotes = 0;
+    int start = length;
+
+    while (*character) {
+      if (*character == backslash) {
+        ++backslashCount;
+      } else {
+        if (*character == quote) {
+          needQuotes = 1;
+          backslashCount = (backslashCount * 2) + 1;
+        } else if ((*character == ' ') || (*character == '\t')) {
+          needQuotes = 1;
+        }
+
+        while (backslashCount > 0) {
+          ADD(backslash);
+          --backslashCount;
+        }
+
+        ADD(*character);
+      }
+
+      ++character;
+    }
+
+    if (needQuotes) backslashCount *= 2;
+    while (backslashCount > 0) {
+      ADD(backslash);
+      --backslashCount;
+    }
+
+    if (needQuotes) {
+      ADD(quote);
+      ADD(quote);
+      memmove(&buffer[start+1], &buffer[start], length-start-1);
+      buffer[start] = quote;
+    }
+
+    ADD(' ');
+    ++arguments;
+  }
+#undef ADD
+
+  buffer[length-1] = 0;
+  {
+    char *line = realloc(buffer, length);
+    if (line) return line;
+    logSystemError("realloc");
+  }
+
+error:
+  if (buffer) free(buffer);
+  return NULL;
+}
+
+static void
+loadLibraries (void) {
+  HMODULE library;
+
+#define LOAD_LIBRARY(name) (library = loadLibrary(name))
+#define GET_PROC(name) (name##Proc = getProcedure(library, #name))
+
+  if (LOAD_LIBRARY("ntdll.dll")) {
+    GET_PROC(NtSetInformationProcess);
+  }
+
+  if (LOAD_LIBRARY("kernel32.dll")) {
+    GET_PROC(AttachConsole);
+    GET_PROC(GetLocaleInfoEx);
+  }
+
+  if (LOAD_LIBRARY("user32.dll")) {
+    GET_PROC(GetAltTabInfoA);
+    GET_PROC(SendInput);
+  }
+
+#ifdef __MINGW32__
+  if (LOAD_LIBRARY("ws2_32.dll")) {
+    GET_PROC(getaddrinfo);
+    GET_PROC(freeaddrinfo);
+  }
+#endif /* __MINGW32__ */
+}
+
+static void
+setLocale (void) {
+  {
+    const char *locale = getenv("LANG");
+
+    if (locale && *locale) {
+      setMessagesLocale(locale);
+      return;
+    }
+  }
+
+  char *locale = getWindowsLocaleName();
+  setMessagesLocale(locale);
+  if (locale) free(locale);
+}
+
+void
+initializeSystemObject (void) {
+  setLocale();
+  loadLibraries();
+}
+
+#ifdef __MINGW32__
+#include "win_errno.h"
+
+#ifndef SUBLANG_DUTCH_NETHERLANDS
+#define SUBLANG_DUTCH_NETHERLANDS SUBLANG_DUTCH
+#endif /* SUBLANG_DUTCH_NETHERLANDS */
+
+#ifndef SUBLANG_ENGLISH_IRELAND
+#define SUBLANG_ENGLISH_IRELAND SUBLANG_ENGLISH_EIRE
+#endif /* SUBLANG_ENGLISH_IRELAND */
+
+#ifndef SUBLANG_FRENCH_FRANCE
+#define SUBLANG_FRENCH_FRANCE SUBLANG_FRENCH
+#endif /* SUBLANG_FRENCH_FRANCE */
+
+#ifndef SUBLANG_GERMAN_GERMANY
+#define SUBLANG_GERMAN_GERMANY SUBLANG_GERMAN
+#endif /* SUBLANG_GERMAN_GERMANY */
+
+#ifndef SUBLANG_ITALIAN_ITALY
+#define SUBLANG_ITALIAN_ITALY SUBLANG_ITALIAN
+#endif /* SUBLANG_ITALIAN_ITALY */
+
+#ifndef SUBLANG_KOREAN_KOREA
+#define SUBLANG_KOREAN_KOREA SUBLANG_KOREAN
+#endif /* SUBLANG_KOREAN_KOREA */
+
+#ifndef SUBLANG_LITHUANIAN_LITHUANIA
+#define SUBLANG_LITHUANIAN_LITHUANIA SUBLANG_LITHUANIAN
+#endif /* SUBLANG_LITHUANIAN_LITHUANIA */
+
+#ifndef SUBLANG_PORTUGUESE_PORTUGAL
+#define SUBLANG_PORTUGUESE_PORTUGAL SUBLANG_PORTUGUESE
+#endif /* SUBLANG_PORTUGUESE_PORTUGAL */
+
+#ifndef SUBLANG_SPANISH_SPAIN
+#define SUBLANG_SPANISH_SPAIN SUBLANG_SPANISH
+#endif /* SUBLANG_SPANISH_SPAIN */
+
+#ifndef SUBLANG_SWEDISH_SWEDEN
+#define SUBLANG_SWEDISH_SWEDEN SUBLANG_SWEDISH
+#endif /* SUBLANG_SWEDISH_SWEDEN */
+
+#ifndef SUBLANG_SYRIAC_TURKEY
+#define SUBLANG_SYRIAC_TURKEY SUBLANG_SYRIAC
+#endif /* SUBLANG_SYRIAC_TURKEY */
+
+char *
+getWindowsLocaleName (void) {
+  if (GetLocaleInfoExProc) {
+#define WIN_LOCALE_SIZE 85
+    WCHAR buffer[WIN_LOCALE_SIZE];
+    int result = GetLocaleInfoExProc(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, buffer, WIN_LOCALE_SIZE);
+
+    if (result > 0) {
+      char locale[WIN_LOCALE_SIZE];
+      const WCHAR *source = buffer;
+      char *target = locale;
+
+      do {
+        WCHAR c = *source;
+
+        if (c == '-') c = '_';
+        *target++ = c;
+      } while (*source++);
+
+      {
+        char *name = strdup(locale);
+
+        if (name) {
+          return name;
+        } else {
+          logMallocError();
+        }
+      }
+    } else {
+      logWindowsSystemError("GetLocaleInfoEx");
+    }
+  }
+
+  {
+    DWORD langid;
+    int result = GetLocaleInfo(LOCALE_USER_DEFAULT,
+                               LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER,
+                               (char *)&langid, sizeof(langid)/sizeof(TCHAR));
+
+    if (result > 0) {
+      char *name;
+
+      switch (langid) {
+#define DIALECT(primary,secondary,locale) case MAKELANGID(LANG_##primary, SUBLANG_##primary##_##secondary): name = (locale); break;
+        DIALECT(AFRIKAANS, SOUTH_AFRICA, "af_ZA");
+        DIALECT(ALBANIAN, ALBANIA, "sq_AL");
+        DIALECT(ALSATIAN, FRANCE, "gsw_FR");
+        DIALECT(AMHARIC, ETHIOPIA, "am_ET");
+        DIALECT(ARABIC, ALGERIA, "ar_DZ");
+        DIALECT(ARABIC, BAHRAIN, "ar_BH");
+        DIALECT(ARABIC, EGYPT, "ar_EG");
+        DIALECT(ARABIC, IRAQ, "ar_IQ");
+        DIALECT(ARABIC, JORDAN, "ar_JO");
+        DIALECT(ARABIC, KUWAIT, "ar_QW");
+        DIALECT(ARABIC, LEBANON, "ar_LB");
+        DIALECT(ARABIC, LIBYA, "ar_LY");
+        DIALECT(ARABIC, MOROCCO, "ar_MA");
+        DIALECT(ARABIC, OMAN, "ar_OM");
+        DIALECT(ARABIC, QATAR, "ar_QA");
+        DIALECT(ARABIC, SAUDI_ARABIA, "ar_SA");
+        DIALECT(ARABIC, SYRIA, "ar_SY");
+        DIALECT(ARABIC, TUNISIA, "ar_TN");
+        DIALECT(ARABIC, UAE, "ar_AE");
+        DIALECT(ARABIC, YEMEN, "ar_YE");
+        DIALECT(ARMENIAN, ARMENIA, "hy_AM");
+        DIALECT(ASSAMESE, INDIA, "as_IN");
+        DIALECT(AZERI, CYRILLIC, "az@cyrillic");
+        DIALECT(AZERI, LATIN, "az@latin");
+        DIALECT(BASHKIR, RUSSIA, "ba_RU");
+        DIALECT(BASQUE, BASQUE, "eu_XX");
+        DIALECT(BELARUSIAN, BELARUS, "be_BY");
+        DIALECT(BENGALI, BANGLADESH, "bn_HD");
+        DIALECT(BENGALI, INDIA, "bn_IN");
+        DIALECT(BOSNIAN, BOSNIA_HERZEGOVINA_CYRILLIC, "bs_BA@cyrillic");
+        DIALECT(BOSNIAN, BOSNIA_HERZEGOVINA_LATIN, "bs_BA@latin");
+        DIALECT(BRETON, FRANCE, "br_FR");
+        DIALECT(BULGARIAN, BULGARIA, "bg_BG");
+        DIALECT(CATALAN, CATALAN, "ca_XX");
+        DIALECT(CHINESE, HONGKONG, "zh_HK");
+        DIALECT(CHINESE, MACAU, "zh_MO");
+        DIALECT(CHINESE, SIMPLIFIED, "zh_CN");
+        DIALECT(CHINESE, SINGAPORE, "zh_SG");
+        DIALECT(CHINESE, TRADITIONAL, "zh_TW");
+        DIALECT(CORSICAN, FRANCE, "co_FR");
+        DIALECT(CROATIAN, BOSNIA_HERZEGOVINA_LATIN, "hr_BA@latin");
+        DIALECT(CROATIAN, CROATIA, "hr_HR");
+        DIALECT(CZECH, CZECH_REPUBLIC, "cs_CZ");
+        DIALECT(DANISH, DENMARK, "da_DK");
+        DIALECT(DIVEHI, MALDIVES, "dv_MV");
+        DIALECT(DUTCH, BELGIAN, "nl_BE");
+        DIALECT(DUTCH, NETHERLANDS, "nl_NL");
+        DIALECT(ENGLISH, AUS, "en_AU");
+        DIALECT(ENGLISH, BELIZE, "en_BZ");
+        DIALECT(ENGLISH, CAN, "en_CA");
+        DIALECT(ENGLISH, CARIBBEAN, "en_XX");
+        DIALECT(ENGLISH, INDIA, "en_IN");
+        DIALECT(ENGLISH, IRELAND, "en_IE");
+        DIALECT(ENGLISH, JAMAICA, "en_JM");
+        DIALECT(ENGLISH, MALAYSIA, "en_MY");
+        DIALECT(ENGLISH, NZ, "en_NZ");
+        DIALECT(ENGLISH, PHILIPPINES, "en_PH");
+        DIALECT(ENGLISH, SINGAPORE, "en_SG");
+        DIALECT(ENGLISH, SOUTH_AFRICA, "en_ZA");
+        DIALECT(ENGLISH, TRINIDAD, "en_TT");
+        DIALECT(ENGLISH, UK, "en_GB");
+        DIALECT(ENGLISH, US, "en_US");
+        DIALECT(ENGLISH, ZIMBABWE, "en_ZW");
+        DIALECT(ESTONIAN, ESTONIA, "et_EE");
+        DIALECT(FAEROESE, FAROE_ISLANDS, "fo_FO");
+        DIALECT(FILIPINO, PHILIPPINES, "fil_PH");
+        DIALECT(FINNISH, FINLAND, "fi_FI");
+        DIALECT(FRENCH, BELGIAN, "fr_BE");
+        DIALECT(FRENCH, CANADIAN, "fr_CA");
+        DIALECT(FRENCH, FRANCE, "fr_FR");
+        DIALECT(FRENCH, LUXEMBOURG, "fr_LU");
+        DIALECT(FRENCH, MONACO, "fr_MC");
+        DIALECT(FRENCH, SWISS, "fr_CH");
+        DIALECT(FRISIAN, NETHERLANDS, "fy_NL");
+        DIALECT(GALICIAN, GALICIAN, "gl_ES");
+        DIALECT(GEORGIAN, GEORGIA, "ka_GE");
+        DIALECT(GERMAN, AUSTRIAN, "de_AT");
+        DIALECT(GERMAN, GERMANY, "de_DE");
+        DIALECT(GERMAN, LIECHTENSTEIN, "de_LI");
+        DIALECT(GERMAN, LUXEMBOURG, "de_LU");
+        DIALECT(GERMAN, SWISS, "de_CH");
+        DIALECT(GREEK, GREECE, "el_GR");
+        DIALECT(GREENLANDIC, GREENLAND, "kl_GL");
+        DIALECT(GUJARATI, INDIA, "gu_IN");
+        DIALECT(HAUSA, NIGERIA, "ha_NG");
+        DIALECT(HEBREW, ISRAEL, "he_IL");
+        DIALECT(HINDI, INDIA, "hi_IN");
+        DIALECT(HUNGARIAN, HUNGARY, "hu_HU");
+        DIALECT(ICELANDIC, ICELAND, "is_IS");
+        DIALECT(IGBO, NIGERIA, "ig_NG");
+        DIALECT(INDONESIAN, INDONESIA, "id_ID");
+        DIALECT(INUKTITUT, CANADA, "iu_CA");
+        DIALECT(IRISH, IRELAND, "ga_IE");
+        DIALECT(ITALIAN, ITALY, "it_IT");
+        DIALECT(ITALIAN, SWISS, "it_CH");
+        DIALECT(JAPANESE, JAPAN, "ja_JP");
+        DIALECT(KASHMIRI, INDIA, "ks_IN");
+        DIALECT(KAZAK, KAZAKHSTAN, "kk_KZ");
+        DIALECT(KHMER, CAMBODIA, "km_KH");
+        DIALECT(KICHE, GUATEMALA, "quc_GT");
+        DIALECT(KINYARWANDA, RWANDA, "rw_RW");
+        DIALECT(KONKANI, INDIA, "kok_IN");
+        DIALECT(KOREAN, KOREA, "ko_KR");
+        DIALECT(KYRGYZ, KYRGYZSTAN, "ky_KG");
+        DIALECT(LAO, LAO_PDR, "lo_LA");
+        DIALECT(LATVIAN, LATVIA, "lv_LV");
+        DIALECT(LITHUANIAN, LITHUANIA, "lt_LT");
+        DIALECT(LOWER_SORBIAN, GERMANY, "dsb_DE");
+        DIALECT(LUXEMBOURGISH, LUXEMBOURG, "lb_LU");
+        DIALECT(MACEDONIAN, MACEDONIA, "mk_MK");
+        DIALECT(MALAY, BRUNEI_DARUSSALAM, "ms_BN");
+        DIALECT(MALAY, MALAYSIA, "ms_MY");
+        DIALECT(MALAYALAM, INDIA, "ml_IN");
+        DIALECT(MALTESE, MALTA, "mt_MT");
+        DIALECT(MAORI, NEW_ZEALAND, "mi_NZ");
+        DIALECT(MAPUDUNGUN, CHILE, "arn_CL");
+        DIALECT(MARATHI, INDIA, "mr_IN");
+        DIALECT(MOHAWK, MOHAWK, "moh");
+        DIALECT(MONGOLIAN, CYRILLIC_MONGOLIA, "mn_MN@cyrillic");
+        DIALECT(MONGOLIAN, PRC, "mn_CN");
+        DIALECT(NEPALI, INDIA, "ne_IN");
+        DIALECT(NEPALI, NEPAL, "ne_NP");
+        DIALECT(NORWEGIAN, BOKMAL, "nb_NO");
+        DIALECT(NORWEGIAN, NYNORSK, "nn_NO");
+        DIALECT(OCCITAN, FRANCE, "oc_FR");
+        DIALECT(ORIYA, INDIA, "or_IN");
+        DIALECT(PASHTO, AFGHANISTAN, "ps_AF");
+        DIALECT(PERSIAN, IRAN, "fa_IR");
+        DIALECT(POLISH, POLAND, "pl_PL");
+        DIALECT(PORTUGUESE, BRAZILIAN, "pt_BR");
+        DIALECT(PORTUGUESE, PORTUGAL, "pt_PT");
+        DIALECT(PUNJABI, INDIA, "pa_IN");
+#ifdef SUBLANG_PUNJABI_PAKISTAN
+        DIALECT(PUNJABI, PAKISTAN, "pa_PK");
+#endif /* SUBLANG_PUNJABI_PAKISTAN */
+        DIALECT(QUECHUA, BOLIVIA, "qu_BO");
+        DIALECT(QUECHUA, ECUADOR, "qu_EC");
+        DIALECT(QUECHUA, PERU, "qu_PE");
+#ifdef SUBLANG_ROMANIAN_MOLDOVA
+        DIALECT(ROMANIAN, MOLDOVA, "ro_MD");
+#endif /* SUBLANG_ROMANIAN_MOLDOVA */
+        DIALECT(ROMANIAN, ROMANIA, "ro_RO");
+        DIALECT(RUSSIAN, RUSSIA, "ru_RU");
+        DIALECT(SAMI, LULE_NORWAY, "smj_NO");
+        DIALECT(SAMI, LULE_SWEDEN, "smj_SE");
+        DIALECT(SAMI, NORTHERN_FINLAND, "sme_FI");
+        DIALECT(SAMI, NORTHERN_NORWAY, "sme_NO");
+        DIALECT(SAMI, NORTHERN_SWEDEN, "sme_SE");
+        DIALECT(SAMI, SOUTHERN_NORWAY, "sma_NO");
+        DIALECT(SAMI, SOUTHERN_SWEDEN, "sma_SE");
+        DIALECT(SANSKRIT, INDIA, "sa_IN");
+        DIALECT(SERBIAN, BOSNIA_HERZEGOVINA_CYRILLIC, "sr_BA@cyrillic");
+        DIALECT(SERBIAN, BOSNIA_HERZEGOVINA_LATIN, "sr_BA@latin");
+        DIALECT(SERBIAN, CYRILLIC, "sr@cyrillic");
+        DIALECT(SERBIAN, LATIN, "sr@latin");
+        DIALECT(SINDHI, AFGHANISTAN, "sd_AF");
+        DIALECT(SINHALESE, SRI_LANKA, "si_LK");
+        DIALECT(SLOVAK, SLOVAKIA, "sk_SK");
+        DIALECT(SLOVENIAN, SLOVENIA, "sl_SI");
+        DIALECT(SOTHO, NORTHERN_SOUTH_AFRICA, "st_XX");
+        DIALECT(SPANISH, ARGENTINA, "es_AR");
+        DIALECT(SPANISH, BOLIVIA, "es_BO");
+        DIALECT(SPANISH, CHILE, "es_CL");
+        DIALECT(SPANISH, COLOMBIA, "es_CO");
+        DIALECT(SPANISH, COSTA_RICA, "es_CR");
+        DIALECT(SPANISH, DOMINICAN_REPUBLIC, "es_DO");
+        DIALECT(SPANISH, ECUADOR, "es_EC");
+        DIALECT(SPANISH, EL_SALVADOR, "es_SV");
+        DIALECT(SPANISH, GUATEMALA, "es_GT");
+        DIALECT(SPANISH, HONDURAS, "es_HN");
+        DIALECT(SPANISH, MEXICAN, "es_MX");
+        DIALECT(SPANISH, MODERN, "es_XX");
+        DIALECT(SPANISH, NICARAGUA, "es_NI");
+        DIALECT(SPANISH, PANAMA, "es_PA");
+        DIALECT(SPANISH, PARAGUAY, "es_PY");
+        DIALECT(SPANISH, PERU, "es_PE");
+        DIALECT(SPANISH, PUERTO_RICO, "es_PR");
+        DIALECT(SPANISH, SPAIN, "es_ES");
+        DIALECT(SPANISH, URUGUAY, "es_UY");
+        DIALECT(SPANISH, US, "es_US");
+        DIALECT(SPANISH, VENEZUELA, "es_VE");
+        DIALECT(SWEDISH, FINLAND, "sv_FI");
+        DIALECT(SWEDISH, SWEDEN, "sv_SE");
+        DIALECT(SYRIAC, TURKEY, "syr_TR");
+        DIALECT(TAMAZIGHT, ALGERIA_LATIN, "ber_DZ@latin");
+        DIALECT(TAMIL, INDIA, "ta_IN");
+        DIALECT(TATAR, RUSSIA, "tt_RU");
+        DIALECT(TELUGU, INDIA, "te_IN");
+        DIALECT(THAI, THAILAND, "th_TH");
+        DIALECT(TIBETAN, BHUTAN, "bo_BT");
+        DIALECT(TIBETAN, PRC, "bo_CN");
+        DIALECT(TIGRIGNA, ERITREA, "ti_ER");
+        DIALECT(TSWANA, SOUTH_AFRICA, "tn_ZA");
+        DIALECT(TURKISH, TURKEY, "tr_TR");
+        DIALECT(UIGHUR, PRC, "ug_CN");
+        DIALECT(UKRAINIAN, UKRAINE, "uk_UA");
+      //DIALECT(UPPER_SORBIAN, GERMANY, "hsb_DE");
+        DIALECT(URDU, INDIA, "ur_IN");
+        DIALECT(URDU, PAKISTAN, "ur_PK");
+        DIALECT(UZBEK, CYRILLIC, "uz@cyrillic");
+        DIALECT(UZBEK, LATIN, "uz@latin");
+        DIALECT(VIETNAMESE, VIETNAM, "vi_VN");
+        DIALECT(WELSH, UNITED_KINGDOM, "cy_GB");
+        DIALECT(WOLOF, SENEGAL, "fy_SN");
+        DIALECT(XHOSA, SOUTH_AFRICA, "xh_ZA");
+        DIALECT(YAKUT, RUSSIA, "sah_RU");
+        DIALECT(YI, PRC, "ii_CN");
+        DIALECT(YORUBA, NIGERIA, "yo_NG");
+        DIALECT(ZULU, SOUTH_AFRICA, "zu_ZA");
+#undef DIALECTo
+
+        default:
+          switch (PRIMARYLANGID(langid)) {
+#define LANGUAGE(primary,locale) case LANG_##primary: name = (locale); break;
+            LANGUAGE(AFRIKAANS, "af");
+            LANGUAGE(ALBANIAN, "sq");
+            LANGUAGE(ALSATIAN, "gsw");
+            LANGUAGE(AMHARIC, "am");
+            LANGUAGE(ARABIC, "ar");
+            LANGUAGE(ARMENIAN, "hy");
+            LANGUAGE(ASSAMESE, "as");
+            LANGUAGE(AZERI, "az");
+            LANGUAGE(BASHKIR, "ba");
+            LANGUAGE(BASQUE, "eu");
+            LANGUAGE(BELARUSIAN, "be");
+            LANGUAGE(BENGALI, "bn");
+            LANGUAGE(BOSNIAN, "bs");
+            LANGUAGE(BOSNIAN_NEUTRAL, "bs");
+            LANGUAGE(BRETON, "br");
+            LANGUAGE(BULGARIAN, "bg");
+            LANGUAGE(CATALAN, "ca");
+            LANGUAGE(CHINESE, "zh");
+            LANGUAGE(CORSICAN, "co");
+          //LANGUAGE(CROATIAN, "hr");
+            LANGUAGE(CZECH, "cs");
+            LANGUAGE(DANISH, "da");
+            LANGUAGE(DARI, "gbz");
+            LANGUAGE(DIVEHI, "dv");
+            LANGUAGE(DUTCH, "nl");
+            LANGUAGE(ENGLISH, "en");
+            LANGUAGE(ESTONIAN, "et");
+            LANGUAGE(FAEROESE, "fo");
+            LANGUAGE(FILIPINO, "fil");
+            LANGUAGE(FINNISH, "fi");
+            LANGUAGE(FRENCH, "fr");
+            LANGUAGE(FRISIAN, "fy");
+            LANGUAGE(GALICIAN, "gl");
+            LANGUAGE(GEORGIAN, "ka");
+            LANGUAGE(GERMAN, "de");
+            LANGUAGE(GREEK, "el");
+            LANGUAGE(GREENLANDIC, "kl");
+            LANGUAGE(GUJARATI, "gu");
+            LANGUAGE(HAUSA, "ha");
+            LANGUAGE(HEBREW, "he");
+            LANGUAGE(HINDI, "hi");
+            LANGUAGE(HUNGARIAN, "hu");
+            LANGUAGE(ICELANDIC, "is");
+            LANGUAGE(IGBO, "ig");
+            LANGUAGE(INDONESIAN, "id");
+            LANGUAGE(INUKTITUT, "iu");
+            LANGUAGE(IRISH, "ga");
+            LANGUAGE(ITALIAN, "it");
+            LANGUAGE(JAPANESE, "ja");
+            LANGUAGE(KANNADA, "kn");
+            LANGUAGE(KASHMIRI, "ks");
+            LANGUAGE(KAZAK, "kk");
+            LANGUAGE(KHMER, "km");
+            LANGUAGE(KICHE, "quc");
+            LANGUAGE(KINYARWANDA, "rw");
+            LANGUAGE(KONKANI, "kok");
+            LANGUAGE(KOREAN, "ko");
+            LANGUAGE(KYRGYZ, "ky");
+            LANGUAGE(LAO, "lo");
+            LANGUAGE(LATVIAN, "lv");
+            LANGUAGE(LITHUANIAN, "lt");
+            LANGUAGE(LOWER_SORBIAN, "dsb");
+            LANGUAGE(LUXEMBOURGISH, "lb");
+            LANGUAGE(MACEDONIAN, "mk");
+#ifndef __MINGW64_VERSION_MAJOR
+            LANGUAGE(MALAGASY, "mg");
+#endif
+            LANGUAGE(MALAY, "ms");
+            LANGUAGE(MALAYALAM, "ml");
+            LANGUAGE(MALTESE, "mt");
+            LANGUAGE(MANIPURI, "mni");
+            LANGUAGE(MAORI, "mi");
+            LANGUAGE(MAPUDUNGUN, "arn");
+            LANGUAGE(MARATHI, "mr");
+            LANGUAGE(MOHAWK, "moh");
+            LANGUAGE(MONGOLIAN, "mn");
+            LANGUAGE(NEPALI, "ne");
+            LANGUAGE(NORWEGIAN, "no");
+            LANGUAGE(OCCITAN, "oc");
+            LANGUAGE(ORIYA, "or");
+            LANGUAGE(PASHTO, "ps");
+            LANGUAGE(PERSIAN, "fa");
+            LANGUAGE(POLISH, "pl");
+            LANGUAGE(PORTUGUESE, "pt");
+            LANGUAGE(PUNJABI, "pa");
+            LANGUAGE(QUECHUA, "qu");
+            LANGUAGE(ROMANIAN, "ro");
+            LANGUAGE(RUSSIAN, "ru");
+            LANGUAGE(SAMI, "se");
+            LANGUAGE(SANSKRIT, "sa");
+          //LANGUAGE(SERBIAN, "sr");
+            LANGUAGE(SERBIAN_NEUTRAL, "sr");
+            LANGUAGE(SINDHI, "sd");
+            LANGUAGE(SINHALESE, "si");
+            LANGUAGE(SLOVAK, "sk");
+            LANGUAGE(SLOVENIAN, "sl");
+            LANGUAGE(SOTHO, "st");
+            LANGUAGE(SPANISH, "es");
+            LANGUAGE(SWAHILI, "sw");
+            LANGUAGE(SWEDISH, "sv");
+            LANGUAGE(SYRIAC, "syr");
+            LANGUAGE(TAMAZIGHT, "ber");
+            LANGUAGE(TAMIL, "ta");
+            LANGUAGE(TATAR, "tt");
+            LANGUAGE(TELUGU, "te");
+            LANGUAGE(THAI, "th");
+            LANGUAGE(TIBETAN, "bo");
+            LANGUAGE(TIGRIGNA, "ti");
+            LANGUAGE(TSWANA, "tn");
+            LANGUAGE(TURKISH, "tr");
+            LANGUAGE(UIGHUR, "ug");
+            LANGUAGE(UKRAINIAN, "uk");
+          //LANGUAGE(UPPER_SORBIAN, "hsb");
+            LANGUAGE(URDU, "ur");
+            LANGUAGE(UZBEK, "uz");
+            LANGUAGE(VIETNAMESE, "vi");
+            LANGUAGE(WELSH, "cy");
+            LANGUAGE(WOLOF, "fy");
+            LANGUAGE(XHOSA, "xh");
+            LANGUAGE(YAKUT, "sah");
+            LANGUAGE(YI, "ii");
+            LANGUAGE(YORUBA, "yo");
+            LANGUAGE(ZULU, "zu");
+#undef LANGUAGE
+
+            default:
+              name = NULL;
+              break;
+          }
+          break;
+      }
+
+      if (name) {
+        if ((name = strdup(name))) {
+          return name;
+        } else {
+          logMallocError();
+        }
+      }
+    } else {
+      logWindowsSystemError("GetLocaleInfo");
+    }
+  }
+
+  return NULL;
+}
+
+#if (__MINGW32_MAJOR_VERSION < 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION < 10))
+int
+gettimeofday (struct timeval *tvp, void *tzp) {
+  DWORD time = GetTickCount();
+  /* this is not 49.7 days-proof ! */
+  tvp->tv_sec = time / 1000;
+  tvp->tv_usec = (time % 1000) * 1000;
+  return 0;
+}
+#endif /* gettimeofday() */
+
+#if !defined(__MINGW64_VERSION_MAJOR) && ((__MINGW32_MAJOR_VERSION < 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION < 15)))
+void
+usleep (int usec) {
+  if (usec > 0) {
+    approximateDelay((usec + (USECS_PER_MSEC - 1)) / USECS_PER_MSEC);
+  }
+}
+#endif /* usleep() */
+#endif /* __MINGW32__ */
diff --git a/Programs/tbl2hex.c b/Programs/tbl2hex.c
new file mode 100644
index 0000000..ad1aa9b
--- /dev/null
+++ b/Programs/tbl2hex.c
@@ -0,0 +1,279 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "cmdline.h"
+#include "log.h"
+#include "file.h"
+
+#include "ttb.h"
+#include "ttb_internal.h"
+
+#include "atb.h"
+#include "atb_internal.h"
+
+#include "ctb.h"
+#include "ctb_internal.h"
+
+BEGIN_OPTION_TABLE(programOptions)
+END_OPTION_TABLE(programOptions)
+
+typedef struct {
+  void *object;
+  const unsigned char *bytes;
+  size_t size;
+} TableData;
+
+typedef struct {
+  const char *extension;
+  int (*load) (const char *path, TableData *data);
+  void (*unload) (TableData *data);
+} TableEntry;
+
+static int
+loadTextTable (const char *path, TableData *data) {
+  TextTable *table = compileTextTable(path);
+  if (!table) return 0;
+
+  data->object = table;
+  data->bytes = table->header.bytes;
+  data->size = table->size;
+  return 1;
+}
+
+static void
+unloadTextTable (TableData *data) {
+  destroyTextTable(data->object);
+}
+
+static int
+loadAttributesTable (const char *path, TableData *data) {
+  AttributesTable *table = compileAttributesTable(path);
+  if (!table) return 0;
+
+  data->object = table;
+  data->bytes = table->header.bytes;
+  data->size = table->size;
+  return 1;
+}
+
+static void
+unloadAttributesTable (TableData *data) {
+  destroyAttributesTable(data->object);
+}
+
+static int
+loadContractionTable (const char *path, TableData *data) {
+  ContractionTable *table = compileContractionTable(path);
+  if (!table) return 0;
+
+  data->object = table;
+  data->bytes = table->data.internal.header.bytes;
+  data->size = table->data.internal.size;
+  return 1;
+}
+
+static void
+unloadContractionTable (TableData *data) {
+  destroyContractionTable(data->object);
+}
+
+static const TableEntry tableEntries[] = {
+  {
+    .extension = TEXT_TABLE_EXTENSION,
+    .load = loadTextTable,
+    .unload = unloadTextTable
+  }
+  ,
+  {
+    .extension = ATTRIBUTES_TABLE_EXTENSION,
+    .load = loadAttributesTable,
+    .unload = unloadAttributesTable
+  }
+  ,
+  {
+    .extension = CONTRACTION_TABLE_EXTENSION,
+    .load = loadContractionTable,
+    .unload = unloadContractionTable
+  }
+  ,
+  {
+    .extension = NULL
+  }
+};
+
+static const TableEntry *
+findTableEntry (const char *extension) {
+  const TableEntry *entry = tableEntries;
+
+  while (entry->extension) {
+    if (strcmp(entry->extension, extension) == 0) return entry;
+    entry += 1;
+  }
+
+  logMessage(LOG_ERR, "unrecognized file extension: %s", extension);
+  return NULL;
+}
+
+int
+dumpBytes (FILE *stream, const unsigned char *bytes, size_t count) {
+  const unsigned char *byte = bytes;
+  const unsigned char *end = byte + count;
+  int first = 1;
+  int digits;
+
+  if (count) {
+    char buffer[0X10];
+    digits = snprintf(buffer, sizeof(buffer), "%X", (unsigned int)count-1);
+  } else {
+    digits = 1;
+  }
+
+  while (byte < end) {
+    while (!*byte && (byte < (end - 1))) byte += 1;
+
+    {
+      unsigned int counter = 0;
+      unsigned int maximum = 8;
+
+      if ((byte + maximum) != end) {
+        while (maximum > 1) {
+          if (byte[maximum-1]) break;
+          maximum -= 1;
+        }
+      }
+
+      while (byte < end) {
+        if (first) {
+          first = 0;
+        } else {
+          fprintf(stream, ",");
+          if (ferror(stream)) goto outputError;
+
+          if (!counter) {
+            fprintf(stream, "\n");
+            if (ferror(stream)) goto outputError;
+          }
+        }
+
+        if (!counter) {
+          fprintf(stream, "[0X%0*X] =", digits, (unsigned int)(byte-bytes));
+          if (ferror(stream)) goto outputError;
+        }
+
+        fprintf(stream, " 0X%02X", *byte++);
+        if (ferror(stdout)) goto outputError;
+
+        if (++counter == maximum) break;
+      }
+    }
+  }
+
+  if (!first) {
+    fprintf(stream, "\n");
+    if (ferror(stream)) goto outputError;
+  }
+
+  return 1;
+
+outputError:
+  logMessage(LOG_ERR, "table write error: %s", strerror(errno));
+  return 0;
+}
+
+int
+main (int argc, char *argv[]) {
+  ProgramExitStatus exitStatus;
+  char *path;
+
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "tbl2hex",
+
+      .usage = {
+        .purpose = strtext("Write the hexadecimal array representation of a compiled table."),
+        .parameters = "table-file",
+      }
+    };
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  if (argc == 0) {
+    logMessage(LOG_ERR, "missing table file.");
+    return PROG_EXIT_SYNTAX;
+  }
+  path = *argv++, argc--;
+
+  {
+    const char *extension = locatePathExtension(path);
+
+    if (extension) {
+      const TableEntry *entry = findTableEntry(extension);
+
+      if (entry) {
+        TableData data;
+        if (entry->load(path, &data)) {
+          if (dumpBytes(stdout, data.bytes, data.size)) {
+            exitStatus = PROG_EXIT_SUCCESS;
+          } else {
+            exitStatus = PROG_EXIT_FATAL;
+          }
+
+          entry->unload(&data);
+        } else {
+          exitStatus = PROG_EXIT_FATAL;
+        }
+      } else {
+        exitStatus = PROG_EXIT_SEMANTIC;
+      }
+    } else {
+      logMessage(LOG_ERR, "no file extension");
+      exitStatus = PROG_EXIT_SEMANTIC;
+    }
+  }
+
+  return exitStatus;
+}
+
+#include "ctb_internal.h"
+
+const unsigned char *
+getInternalContractionTableBytes (void) {
+  return NULL;
+}
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_native (void) {
+  return NULL;
+}
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_external (void) {
+  return NULL;
+}
+
+const ContractionTableTranslationMethods *
+getContractionTableTranslationMethods_louis (void) {
+  return NULL;
+}
diff --git a/Programs/thread.c b/Programs/thread.c
new file mode 100644
index 0000000..e8f27db
--- /dev/null
+++ b/Programs/thread.c
@@ -0,0 +1,359 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "thread.h"
+#include "async_signal.h"
+#include "async_event.h"
+#include "async_wait.h"
+
+#undef HAVE_THREAD_NAMES
+
+#ifdef GOT_PTHREADS
+typedef struct {
+  ThreadFunction *function;
+  void *argument;
+  char name[];
+} RunThreadArgument;
+
+static void *
+runThread (void *argument) {
+  RunThreadArgument *run = argument;
+
+  setThreadName(run->name);
+  logMessage(LOG_CATEGORY(ASYNC_EVENTS), "thread starting: %s", run->name);
+  void *result = run->function(run->argument);
+  logMessage(LOG_CATEGORY(ASYNC_EVENTS), "thread finished: %s", run->name);
+
+  free(run);
+  return result;
+}
+
+typedef struct {
+  const char *const name;
+  pthread_t *const thread;
+  const pthread_attr_t *const attributes;
+  ThreadFunction *const function;
+  void *const argument;
+
+  int error;
+} CreateThreadParameters;
+
+static int
+createActualThread (void *parameters) {
+  CreateThreadParameters *create = parameters;
+  RunThreadArgument *run;
+
+  if ((run = malloc(sizeof(*run) + strlen(create->name) + 1))) {
+    memset(run, 0, sizeof(*run));
+    run->function = create->function;
+    run->argument = create->argument;
+    strcpy(run->name, create->name);
+
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "creating thread: %s", create->name);
+    create->error = pthread_create(create->thread, create->attributes, runThread, run);
+    if (!create->error) return 1;
+    logMessage(LOG_CATEGORY(ASYNC_EVENTS), "thread not created: %s: %s", create->name, strerror(create->error));
+
+    free(run);
+  } else {
+    create->error = errno;
+    logMallocError();
+  }
+
+  return 0;
+}
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(createSignalSafeThread) {
+  static const int signals[] = {
+#ifdef SIGINT
+    SIGINT,
+#endif /* SIGINT */
+
+#ifdef SIGTERM
+    SIGTERM,
+#endif /* SIGTERM */
+
+#ifdef SIGCHLD
+    SIGCHLD,
+#endif /* SIGCHLD */
+
+    0
+  };
+
+  CreateThreadParameters *create = data;
+  const int *signal = signals;
+
+  while (*signal) {
+    asyncSetSignalBlocked(*signal, 1);
+    signal += 1;
+  }
+
+  createActualThread(create);
+}
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+int
+createThread (
+  const char *name,
+  pthread_t *thread, const pthread_attr_t *attributes,
+  ThreadFunction *function, void *argument
+) {
+  CreateThreadParameters create = {
+    .name = name,
+    .thread = thread,
+    .attributes = attributes,
+    .function = function,
+    .argument = argument
+  };
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+  asyncWithObtainableSignalsBlocked(createSignalSafeThread, &create);
+#else /* ASYNC_CAN_BLOCK_SIGNALS */
+  createActualThread(&create);
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+  return create.error;
+}
+
+typedef struct {
+  ThreadFunction *const function;
+  void *const argument;
+
+  AsyncEvent *event;
+  unsigned returned:1;
+} CallThreadFunctionData;
+
+THREAD_FUNCTION(runThreadFunction) {
+  CallThreadFunctionData *ctf = argument;
+  void *result = ctf->function? ctf->function(ctf->argument): NULL;
+
+  asyncSignalEvent(ctf->event, NULL);
+  return result;
+}
+
+ASYNC_EVENT_CALLBACK(handleThreadFunctionReturned) {
+  CallThreadFunctionData *ctf = parameters->eventData;
+
+  ctf->returned = 1;
+}
+
+ASYNC_CONDITION_TESTER(testThreadFunctionReturned) {
+  CallThreadFunctionData *ctf = data;
+
+  return ctf->returned;
+}
+
+int
+callThreadFunction (
+  const char *name, ThreadFunction *function,
+  void *argument, void **result
+) {
+  int called = 0;
+
+  CallThreadFunctionData ctf = {
+    .function = function,
+    .argument = argument,
+
+    .returned = 0
+  };
+
+  if ((ctf.event = asyncNewEvent(handleThreadFunctionReturned, &ctf))) {
+    pthread_t thread;
+    int error = createThread(name, &thread, NULL, runThreadFunction, &ctf);
+
+    if (!error) {
+      asyncWaitFor(testThreadFunctionReturned, &ctf);
+
+      {
+        void *r;
+
+        if (!result) result = &r;
+        pthread_join(thread, result);
+      }
+
+      called = 1;
+    } else {
+      errno = error;
+    }
+
+    asyncDiscardEvent(ctf.event);
+  }
+
+  return called;
+}
+
+int
+lockMutex (pthread_mutex_t *mutex) {
+  int result = pthread_mutex_lock(mutex);
+
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS), mutex, "mutex lock");
+  return result;
+}
+
+int
+unlockMutex (pthread_mutex_t *mutex) {
+  logSymbol(LOG_CATEGORY(ASYNC_EVENTS), mutex, "mutex unlock");
+  return pthread_mutex_unlock(mutex);
+}
+
+#if defined(HAVE_PTHREAD_GETNAME_NP) && defined(__GLIBC__)
+#define HAVE_THREAD_NAMES
+
+size_t
+formatThreadName (char *buffer, size_t size) {
+  int error = pthread_getname_np(pthread_self(), buffer, size);
+
+  return error? 0: strlen(buffer);
+}
+
+void
+setThreadName (const char *name) {
+  pthread_setname_np(pthread_self(), name);
+}
+
+#elif defined(HAVE_PTHREAD_GETNAME_NP) && defined(__APPLE__)
+#define HAVE_THREAD_NAMES
+
+size_t
+formatThreadName (char *buffer, size_t size) {
+  {
+    int error = pthread_getname_np(pthread_self(), buffer, size);
+
+    if (error) return 0;
+    if (*buffer) return strlen(buffer);
+  }
+
+  if (pthread_main_np()) {
+    size_t length;
+
+    STR_BEGIN(buffer, size);
+    STR_PRINTF("main");
+    length = STR_LENGTH;
+    STR_END;
+
+    return length;
+  }
+
+  return 0;
+}
+
+void
+setThreadName (const char *name) {
+  pthread_setname_np(name);
+}
+
+#endif /* thread names */
+#endif /* GOT_PTHREADS */
+
+#ifndef HAVE_THREAD_NAMES
+size_t
+formatThreadName (char *buffer, size_t size) {
+  return 0;
+}
+
+void
+setThreadName (const char *name) {
+}
+#endif /* HAVE_THREAD_NAMES */
+
+#if defined(PTHREAD_MUTEX_INITIALIZER)
+static void
+createThreadSpecificDataKey (ThreadSpecificDataControl *ctl) {
+  int error;
+
+  pthread_mutex_lock(&ctl->mutex);
+    if (!ctl->key.created) {
+      error = pthread_key_create(&ctl->key.value, ctl->destroy);
+
+      if (!error) {
+        ctl->key.created = 1;
+      } else {
+        logActionError(error, "pthread_key_create");
+      }
+    }
+  pthread_mutex_unlock(&ctl->mutex);
+}
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+ASYNC_WITH_SIGNALS_BLOCKED_FUNCTION(createThreadSpecificDataKeyWithSignalsBlocked) {
+  ThreadSpecificDataControl *ctl = data;
+
+  createThreadSpecificDataKey(ctl);
+}
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+void *
+getThreadSpecificData (ThreadSpecificDataControl *ctl) {
+  int error;
+
+#ifdef ASYNC_CAN_BLOCK_SIGNALS
+  asyncWithAllSignalsBlocked(createThreadSpecificDataKeyWithSignalsBlocked, ctl);
+#else /* ASYNC_CAN_BLOCK_SIGNALS */
+  createThreadSpecificDataKey(ctl);
+#endif /* ASYNC_CAN_BLOCK_SIGNALS */
+
+  if (ctl->key.created) {
+    void *tsd = pthread_getspecific(ctl->key.value);
+    if (tsd) return tsd;
+
+    if ((tsd = ctl->new())) {
+      if (!(error = pthread_setspecific(ctl->key.value, tsd))) {
+        return tsd;
+      } else {
+        logActionError(error, "pthread_setspecific");
+      }
+
+      ctl->destroy(tsd);
+    }
+  }
+
+  return NULL;
+}
+
+#else /* thread specific data */
+#include "program.h"
+
+static void
+exitThreadSpecificData (void *data) {
+  ThreadSpecificDataControl *ctl = data;
+
+  if (ctl->data) {
+    ctl->destroy(ctl->data);
+    ctl->data = NULL;
+  }
+}
+
+void *
+getThreadSpecificData (ThreadSpecificDataControl *ctl) {
+  if (!ctl->data) {
+    if ((ctl->data = ctl->new())) {
+      onProgramExit("thread-specific-data", exitThreadSpecificData, ctl);
+    }
+  }
+
+  return ctl->data;
+}
+#endif /* thread specific data */
diff --git a/Programs/timing.c b/Programs/timing.c
new file mode 100644
index 0000000..f488524
--- /dev/null
+++ b/Programs/timing.c
@@ -0,0 +1,435 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+#include <time.h>
+
+#if defined(HAVE_GETTIMEOFDAY) || defined(HAVE_SETTIMEOFDAY)
+#include <sys/time.h>
+#endif /* HAVE_(GET|SET)TIMEOFDAY */
+
+#ifdef HAVE_SYS_POLL_H
+#include <poll.h>
+#endif /* HAVE_SYS_POLL_H */
+
+#ifdef HAVE_SELECT
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else /* HAVE_SYS_SELECT_H */
+#include <sys/time.h>
+#endif /* HAVE_SYS_SELECT_H */
+#endif /* HAVE_SELECT */
+
+#include "log.h"
+#include "timing.h"
+
+#ifdef __MSDOS__
+#include "system_msdos.h"
+#endif /* __MSDOS__ */
+
+#if !HAVE_DECL_LOCALTIME_R
+static inline struct tm *
+localtime_r (const time_t *timep, struct tm *result) {
+  *result = *localtime(timep);
+  return result;
+}
+#endif /* HAVE_DECL_LOCALTIME_R */
+
+void
+getCurrentTime (TimeValue *now) {
+  now->seconds = 0;
+  now->nanoseconds = 0;
+
+#if defined(GRUB_RUNTIME)
+  static time_t baseSeconds = 0;
+  static uint64_t baseMilliseconds;
+
+  if (!baseSeconds) {
+    baseSeconds = time(NULL);
+    baseMilliseconds = grub_get_time_ms();
+  }
+
+  {
+    uint64_t milliseconds = grub_get_time_ms() - baseMilliseconds;
+
+    now->seconds = baseSeconds + (milliseconds / MSECS_PER_SEC);
+    now->nanoseconds = (milliseconds % MSECS_PER_SEC) * NSECS_PER_MSEC;
+  }
+
+#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) && !defined(__MINGW32__)
+  struct timespec ts;
+
+  if (clock_gettime(CLOCK_REALTIME, &ts) != -1) {
+    now->seconds = ts.tv_sec;
+    now->nanoseconds = ts.tv_nsec;
+  } else {
+  //logSystemError("clock_gettime");
+  }
+
+#elif defined(HAVE_GETTIMEOFDAY)
+  struct timeval tv;
+
+  #pragma GCC diagnostic push
+  #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+  int result = gettimeofday(&tv, NULL);
+  #pragma GCC diagnostic pop
+
+  if (result != -1) {
+    now->seconds = tv.tv_sec;
+    now->nanoseconds = tv.tv_usec * NSECS_PER_USEC;
+  } else {
+  //logSystemError("gettimeofday");
+  }
+
+#elif defined(HAVE_TIME)
+  now->seconds = time(NULL);
+
+#else /* get current time */
+#warning get current time not supported on this platform
+#endif /* get current time */
+}
+
+void
+setCurrentTime (const TimeValue *now) {
+#if defined(HAVE_CLOCK_SETTIME) && defined(CLOCK_REALTIME)
+  const struct timespec ts = {
+    .tv_sec = now->seconds,
+    .tv_nsec = now->nanoseconds
+  };
+
+  if (clock_settime(CLOCK_REALTIME, &ts) == -1) {
+    logSystemError("clock_settime");
+  }
+
+#elif defined(HAVE_SETTIMEOFDAY)
+  struct timeval tv = {
+    .tv_sec = now->seconds,
+    .tv_usec = now->nanoseconds / NSECS_PER_USEC
+  };
+
+  if (settimeofday(&tv, NULL) == -1) {
+    logSystemError("settimeofday");
+  }
+
+#elif defined(__MINGW32__)
+  TimeComponents components;
+  expandTimeValue(now, &components);
+
+  SYSTEMTIME time = {
+    .wYear = components.year,
+    .wMonth = components.month + 1,
+    .wDay = components.day + 1,
+    .wHour = components.hour,
+    .wMinute = components.minute,
+    .wSecond = components.second,
+    .wMilliseconds = now->nanoseconds / NSECS_PER_MSEC
+  };
+
+  if (!SetLocalTime(&time)) {
+    logWindowsSystemError("SetLocalTime");
+  }
+
+#elif defined(HAVE_STIME)
+  const time_t seconds = now->seconds;
+
+  if (stime(&seconds) == -1) {
+    logSystemError("stime");
+  }
+
+#else /* set current time */
+#warning set current time not supported on this platform
+#endif /* get current time */
+}
+
+void
+makeTimeValue (TimeValue *value, const TimeComponents *components) {
+  value->nanoseconds = components->nanosecond;
+
+#if defined(GRUB_RUNTIME)
+  value->seconds = 0;
+
+#else /* make seconds */
+  struct tm time = {
+    .tm_year = components->year - 1900,
+    .tm_mon = components->month,
+    .tm_mday = components->day + 1,
+    .tm_hour = components->hour,
+    .tm_min = components->minute,
+    .tm_sec = components->second,
+    .tm_isdst = -1
+  };
+
+  value->seconds = mktime(&time);
+#endif /* make seconds */
+}
+
+void
+expandTimeValue (const TimeValue *value, TimeComponents *components) {
+  time_t seconds = value->seconds;
+  components->nanosecond = value->nanoseconds;
+
+  struct tm *time = &components->time;
+  localtime_r(&seconds, time);
+
+#if defined(GRUB_RUNTIME)
+  components->year = time->tm.year;
+  components->month = time->tm.month - 1;
+  components->day = time->tm.day - 1;
+  components->hour = time->tm.hour;
+  components->minute = time->tm.minute;
+  components->second = time->tm.second;
+
+#else /* expand seconds */
+  components->year = time->tm_year + 1900;
+  components->month = time->tm_mon;
+  components->day = time->tm_mday - 1;
+  components->hour = time->tm_hour;
+  components->minute = time->tm_min;
+  components->second = time->tm_sec;
+#endif /* expand seconds */
+}
+
+size_t
+formatSeconds (char *buffer, size_t size, const char *format, int32_t seconds) {
+  time_t time = seconds;
+  struct tm description;
+
+  localtime_r(&time, &description);
+  return strftime(buffer, size, format, &description);
+}
+
+void
+normalizeTimeValue (TimeValue *time) {
+  while (time->nanoseconds < 0) {
+    time->seconds -= 1;
+    time->nanoseconds += NSECS_PER_SEC;
+  }
+
+  while (time->nanoseconds >= NSECS_PER_SEC) {
+    time->seconds += 1;
+    time->nanoseconds -= NSECS_PER_SEC;
+  }
+}
+
+void
+adjustTimeValue (TimeValue *time, int milliseconds) {
+  TimeValue amount = {
+    .seconds = milliseconds / MSECS_PER_SEC,
+    .nanoseconds = (milliseconds % MSECS_PER_SEC) * NSECS_PER_MSEC
+  };
+
+  normalizeTimeValue(time);
+  normalizeTimeValue(&amount);
+  time->seconds += amount.seconds;
+  time->nanoseconds += amount.nanoseconds;
+  normalizeTimeValue(time);
+}
+
+int
+compareTimeValues (const TimeValue *first, const TimeValue *second) {
+  if (first->seconds < second->seconds) return -1;
+  if (first->seconds > second->seconds) return 1;
+
+  if (first->nanoseconds < second->nanoseconds) return -1;
+  if (first->nanoseconds > second->nanoseconds) return 1;
+
+  return 0;
+}
+
+long int
+millisecondsBetween (const TimeValue *from, const TimeValue *to) {
+  TimeValue elapsed = {
+    .seconds = to->seconds - from->seconds,
+    .nanoseconds = to->nanoseconds - from->nanoseconds
+  };
+
+  normalizeTimeValue(&elapsed);
+  return ((long int)elapsed.seconds * MSECS_PER_SEC)
+       + (elapsed.nanoseconds / NSECS_PER_MSEC);
+}
+
+long int
+millisecondsTillNextSecond (const TimeValue *reference) {
+  TimeValue time = *reference;
+
+  time.nanoseconds = 0;
+  time.seconds += 1;
+  return millisecondsBetween(reference, &time);
+}
+
+long int
+millisecondsTillNextMinute (const TimeValue *reference) {
+  TimeValue time = *reference;
+  int32_t *seconds = &time.seconds;
+
+  time.nanoseconds = 0;
+  *seconds /= SECS_PER_MIN;
+  *seconds += 1;
+  *seconds *= SECS_PER_MIN;
+  return millisecondsBetween(reference, &time);
+}
+
+void
+getMonotonicTime (TimeValue *now) {
+#if defined(GRUB_RUNTIME)
+  grub_uint64_t milliseconds = grub_get_time_ms();
+  now->seconds = milliseconds / MSECS_PER_SEC;
+  now->nanoseconds = (milliseconds % MSECS_PER_SEC) * NSECS_PER_MSEC;
+
+#elif defined(CLOCK_REALTIME)
+  static const clockid_t clocks[] = {
+#ifdef CLOCK_MONOTONIC_RAW
+    CLOCK_MONOTONIC_RAW,
+#endif /* CLOCK_MONOTONIC_RAW */
+
+#ifdef CLOCK_MONOTONIC_HR
+    CLOCK_MONOTONIC_HR,
+#endif /* CLOCK_MONOTONIC_HR */
+
+#ifdef CLOCK_MONOTONIC
+    CLOCK_MONOTONIC,
+#endif /* CLOCK_MONOTONIC */
+
+    CLOCK_REALTIME
+  };
+
+  static const clockid_t *clock = clocks;
+
+  while (*clock != CLOCK_REALTIME) {
+    struct timespec ts;
+
+    if (clock_gettime(*clock, &ts) != -1) {
+      now->seconds = ts.tv_sec;
+      now->nanoseconds = ts.tv_nsec;
+      return;
+    }
+
+    logMessage(LOG_WARNING, "clock not available: %u", (unsigned int)*clock);
+    clock += 1;
+  }
+#endif /* get monotonic time */
+
+  getCurrentTime(now);
+}
+
+long int
+getMonotonicElapsed (const TimeValue *start) {
+  TimeValue now;
+
+  getMonotonicTime(&now);
+  return millisecondsBetween(start, &now);
+}
+
+void
+restartTimePeriod (TimePeriod *period) {
+  getMonotonicTime(&period->start);
+}
+
+void
+startTimePeriod (TimePeriod *period, long int length) {
+  period->length = length;
+  restartTimePeriod(period);
+}
+
+int
+afterTimePeriod (const TimePeriod *period, long int *elapsed) {
+  long int milliseconds = getMonotonicElapsed(&period->start);
+
+  if (elapsed) *elapsed = milliseconds;
+  return milliseconds >= period->length;
+}
+
+void
+approximateDelay (int milliseconds) {
+  if (milliseconds > 0) {
+#if defined(__MINGW32__)
+    Sleep(milliseconds);
+
+#elif defined(__MSDOS__)
+    msdosUSleep(milliseconds * USECS_PER_MSEC);
+
+#elif defined (GRUB_RUNTIME)
+    grub_millisleep(milliseconds);
+
+#elif defined(HAVE_NANOSLEEP)
+    const struct timespec timeout = {
+      .tv_sec = milliseconds / MSECS_PER_SEC,
+      .tv_nsec = (milliseconds % MSECS_PER_SEC) * NSECS_PER_MSEC
+    };
+
+    if (nanosleep(&timeout, NULL) == -1) {
+      if (errno != EINTR) logSystemError("nanosleep");
+    }
+
+#elif defined(HAVE_SYS_POLL_H)
+    if (poll(NULL, 0, milliseconds) == -1) {
+      if (errno != EINTR) logSystemError("poll");
+    }
+
+#elif defined(HAVE_SELECT)
+    struct timeval timeout = {
+      .tv_sec = milliseconds / MSECS_PER_SEC,
+      .tv_usec = (milliseconds % MSECS_PER_SEC) * USECS_PER_MSEC
+    };
+
+    if (select(0, NULL, NULL, NULL, &timeout) == -1) {
+      if (errno != EINTR) logSystemError("select");
+    }
+
+#endif /* approximate delay */
+  }
+}
+
+void
+accurateDelay (const TimeValue *duration) {
+  TimeValue delay = *duration;
+  normalizeTimeValue(&delay);
+
+  if ((delay.seconds > 0) || ((delay.seconds == 0) && (delay.nanoseconds > 0))) {
+#if defined(HAVE_NANOSLEEP)
+    const struct timespec timeout = {
+      .tv_sec = delay.seconds,
+      .tv_nsec = delay.nanoseconds
+    };
+
+    if (nanosleep(&timeout, NULL) == -1) {
+      if (errno != EINTR) logSystemError("nanosleep");
+    }
+
+#elif defined(HAVE_SELECT)
+    struct timeval timeout = {
+      .tv_sec = delay.seconds,
+      .tv_usec = (delay.nanoseconds + (NSECS_PER_USEC - 1)) / NSECS_PER_USEC
+    };
+
+    if (timeout.tv_usec == USECS_PER_SEC) {
+      timeout.tv_sec += 1;
+      timeout.tv_usec = 0;
+    }
+
+    if (select(0, NULL, NULL, NULL, &timeout) == -1) {
+      if (errno != EINTR) logSystemError("select");
+    }
+
+#else /* accurate delay */
+    approximateDelay((delay.seconds * MSECS_PER_SEC) + ((delay.nanoseconds + (NSECS_PER_MSEC - 1)) / NSECS_PER_MSEC));
+#endif /* accurate delay */
+  }
+}
diff --git a/Programs/ttb.auto.h b/Programs/ttb.auto.h
new file mode 100644
index 0000000..d7dd98f
--- /dev/null
+++ b/Programs/ttb.auto.h
@@ -0,0 +1,1299 @@
+[0X0000] = 0X30, 0X06,
+[0X0200] = 0X20, 0X00, 0X00, 0X00, 0X61,
+[0X0208] = 0X31, 0X00, 0X00, 0X00, 0X62,
+[0X0210] = 0X27, 0X00, 0X00, 0X00, 0X6B,
+[0X0218] = 0X32, 0X00, 0X00, 0X00, 0X6C,
+[0X0220] = 0X60, 0X00, 0X00, 0X00, 0X63,
+[0X0228] = 0X69, 0X00, 0X00, 0X00, 0X66,
+[0X0230] = 0X2F, 0X00, 0X00, 0X00, 0X6D,
+[0X0238] = 0X73, 0X00, 0X00, 0X00, 0X70,
+[0X0240] = 0X22, 0X00, 0X00, 0X00, 0X65,
+[0X0248] = 0X33, 0X00, 0X00, 0X00, 0X68,
+[0X0250] = 0X39, 0X00, 0X00, 0X00, 0X6F,
+[0X0258] = 0X36, 0X00, 0X00, 0X00, 0X72,
+[0X0260] = 0X7E, 0X00, 0X00, 0X00, 0X64,
+[0X0268] = 0X6A, 0X00, 0X00, 0X00, 0X67,
+[0X0270] = 0X3E, 0X00, 0X00, 0X00, 0X6E,
+[0X0278] = 0X74, 0X00, 0X00, 0X00, 0X71,
+[0X0280] = 0X2C, 0X00, 0X00, 0X00, 0X2A,
+[0X0288] = 0X35, 0X00, 0X00, 0X00, 0X3C,
+[0X0290] = 0X2D, 0X00, 0X00, 0X00, 0X75,
+[0X0298] = 0X38, 0X00, 0X00, 0X00, 0X76,
+[0X02A0] = 0X2E, 0X00, 0X00, 0X00, 0X25,
+[0X02A8] = 0X7B, 0X00, 0X00, 0X00, 0X24,
+[0X02B0] = 0X2B, 0X00, 0X00, 0X00, 0X78,
+[0X02B8] = 0X21, 0X00, 0X00, 0X00, 0X26,
+[0X02C0] = 0X3B, 0X00, 0X00, 0X00, 0X3A,
+[0X02C8] = 0X34, 0X00, 0X00, 0X00, 0X7C,
+[0X02D0] = 0X30, 0X00, 0X00, 0X00, 0X7A,
+[0X02D8] = 0X37, 0X00, 0X00, 0X00, 0X28,
+[0X02E0] = 0X5F, 0X00, 0X00, 0X00, 0X3F,
+[0X02E8] = 0X77, 0X00, 0X00, 0X00, 0X7D,
+[0X02F0] = 0X23, 0X00, 0X00, 0X00, 0X79,
+[0X02F8] = 0X29, 0X00, 0X00, 0X00, 0X3D,
+[0X0300] = 0XBA, 0X00, 0X00, 0X00, 0X41,
+[0X0308] = 0XB9, 0X00, 0X00, 0X00, 0X42,
+[0X0310] = 0XB4, 0X00, 0X00, 0X00, 0X4B,
+[0X0318] = 0XB2, 0X00, 0X00, 0X00, 0X4C,
+[0X0320] = 0X40, 0X00, 0X00, 0X00, 0X43,
+[0X0328] = 0X49, 0X00, 0X00, 0X00, 0X46,
+[0X0330] = 0XF7, 0X00, 0X00, 0X00, 0X4D,
+[0X0338] = 0X53, 0X00, 0X00, 0X00, 0X50,
+[0X0340] = 0XA8, 0X00, 0X00, 0X00, 0X45,
+[0X0348] = 0XB3, 0X00, 0X00, 0X00, 0X48,
+[0X0350] = 0XA7, 0X00, 0X00, 0X00, 0X4F,
+[0X0358] = 0XB6, 0X00, 0X00, 0X00, 0X52,
+[0X0360] = 0X5E, 0X00, 0X00, 0X00, 0X44,
+[0X0368] = 0X4A, 0X00, 0X00, 0X00, 0X47,
+[0X0370] = 0XBB, 0X00, 0X00, 0X00, 0X4E,
+[0X0378] = 0X54, 0X00, 0X00, 0X00, 0X51,
+[0X0380] = 0XB8, 0X00, 0X00, 0X00, 0XD7,
+[0X0388] = 0XAF, 0X00, 0X00, 0X00, 0XAB,
+[0X0390] = 0XAD, 0X00, 0X00, 0X00, 0X55,
+[0X0398] = 0XAE, 0X00, 0X00, 0X00, 0X56,
+[0X03A0] = 0XB7, 0X00, 0X00, 0X00, 0XA4,
+[0X03A8] = 0X5B, 0X00, 0X00, 0X00, 0XA2,
+[0X03B0] = 0XB1, 0X00, 0X00, 0X00, 0X58,
+[0X03B8] = 0XA1, 0X00, 0X00, 0X00, 0XA5,
+[0X03C0] = 0XB5, 0X00, 0X00, 0X00, 0XA6,
+[0X03C8] = 0XAC, 0X00, 0X00, 0X00, 0X5C,
+[0X03D0] = 0XB0, 0X00, 0X00, 0X00, 0X5A,
+[0X03D8] = 0XA9, 0X00, 0X00, 0X00, 0XBC,
+[0X03E0] = 0X7F, 0X00, 0X00, 0X00, 0XBF,
+[0X03E8] = 0X57, 0X00, 0X00, 0X00, 0X5D,
+[0X03F0] = 0XA3, 0X00, 0X00, 0X00, 0X59,
+[0X03F8] = 0XBE, 0X00, 0X00, 0X00, 0XBD,
+[0X0400] = 0XAA, 0X00, 0X00, 0X00, 0X81,
+[0X0408] = 0XE2, 0X00, 0X00, 0X00, 0X82,
+[0X0410] = 0XE6, 0X00, 0X00, 0X00, 0X8B,
+[0X0418] = 0XEA, 0X00, 0X00, 0X00, 0X8C,
+[0X0420] = 0X80, 0X00, 0X00, 0X00, 0X83,
+[0X0428] = 0X89, 0X00, 0X00, 0X00, 0X86,
+[0X0430] = 0XF8, 0X00, 0X00, 0X00, 0X8D,
+[0X0438] = 0X93, 0X00, 0X00, 0X00, 0X90,
+[0X0440] = 0XE3, 0X00, 0X00, 0X00, 0X85,
+[0X0448] = 0XEE, 0X00, 0X00, 0X00, 0X88,
+[0X0450] = 0XF2, 0X00, 0X00, 0X00, 0X8F,
+[0X0458] = 0XE0, 0X00, 0X00, 0X00, 0X92,
+[0X0460] = 0X9E, 0X00, 0X00, 0X00, 0X84,
+[0X0468] = 0X8A, 0X00, 0X00, 0X00, 0X87,
+[0X0470] = 0XE5, 0X00, 0X00, 0X00, 0X8E,
+[0X0478] = 0X94, 0X00, 0X00, 0X00, 0X91,
+[0X0480] = 0XF0, 0X00, 0X00, 0X00, 0XE1,
+[0X0488] = 0XFB, 0X00, 0X00, 0X00, 0XE9,
+[0X0490] = 0XFE, 0X00, 0X00, 0X00, 0X95,
+[0X0498] = 0XEC, 0X00, 0X00, 0X00, 0X96,
+[0X04A0] = 0XF1, 0X00, 0X00, 0X00, 0XED,
+[0X04A8] = 0X9B, 0X00, 0X00, 0X00, 0XFD,
+[0X04B0] = 0XE7, 0X00, 0X00, 0X00, 0X98,
+[0X04B8] = 0XF6, 0X00, 0X00, 0X00, 0XE4,
+[0X04C0] = 0XF5, 0X00, 0X00, 0X00, 0XFA,
+[0X04C8] = 0XF4, 0X00, 0X00, 0X00, 0X9C,
+[0X04D0] = 0XF9, 0X00, 0X00, 0X00, 0X9A,
+[0X04D8] = 0XE8, 0X00, 0X00, 0X00, 0XEF,
+[0X04E0] = 0X9F, 0X00, 0X00, 0X00, 0XF3,
+[0X04E8] = 0X97, 0X00, 0X00, 0X00, 0X9D,
+[0X04F0] = 0XFF, 0X00, 0X00, 0X00, 0X99,
+[0X04F8] = 0XFC, 0X00, 0X00, 0X00, 0XEB,
+[0X0504] = 0X01, 0X00, 0X00, 0X00, 0XC2,
+[0X050C] = 0X02, 0X00, 0X00, 0X00, 0XC6,
+[0X0514] = 0X0B, 0X00, 0X00, 0X00, 0XCA,
+[0X051C] = 0X0C,
+[0X0524] = 0X03, 0X00, 0X00, 0X00, 0X09,
+[0X052C] = 0X06, 0X00, 0X00, 0X00, 0XD8,
+[0X0534] = 0X0D, 0X00, 0X00, 0X00, 0X13,
+[0X053C] = 0X10, 0X00, 0X00, 0X00, 0XC3,
+[0X0544] = 0X05, 0X00, 0X00, 0X00, 0XCE,
+[0X054C] = 0X08, 0X00, 0X00, 0X00, 0XD2,
+[0X0554] = 0X0F, 0X00, 0X00, 0X00, 0XC0,
+[0X055C] = 0X12, 0X00, 0X00, 0X00, 0X1E,
+[0X0564] = 0X04, 0X00, 0X00, 0X00, 0X0A,
+[0X056C] = 0X07, 0X00, 0X00, 0X00, 0XC5,
+[0X0574] = 0X0E, 0X00, 0X00, 0X00, 0X14,
+[0X057C] = 0X11, 0X00, 0X00, 0X00, 0XD0,
+[0X0584] = 0XC1, 0X00, 0X00, 0X00, 0XDB,
+[0X058C] = 0XC9, 0X00, 0X00, 0X00, 0XDE,
+[0X0594] = 0X15, 0X00, 0X00, 0X00, 0XCC,
+[0X059C] = 0X16, 0X00, 0X00, 0X00, 0XD1,
+[0X05A4] = 0XCD, 0X00, 0X00, 0X00, 0X1B,
+[0X05AC] = 0XDD, 0X00, 0X00, 0X00, 0XC7,
+[0X05B4] = 0X18, 0X00, 0X00, 0X00, 0XD6,
+[0X05BC] = 0XC4, 0X00, 0X00, 0X00, 0XD5,
+[0X05C4] = 0XDA, 0X00, 0X00, 0X00, 0XD4,
+[0X05CC] = 0X1C, 0X00, 0X00, 0X00, 0XD9,
+[0X05D4] = 0X1A, 0X00, 0X00, 0X00, 0XC8,
+[0X05DC] = 0XCF, 0X00, 0X00, 0X00, 0X1F,
+[0X05E4] = 0XD3, 0X00, 0X00, 0X00, 0X17,
+[0X05EC] = 0X1D, 0X00, 0X00, 0X00, 0XDF,
+[0X05F4] = 0X19, 0X00, 0X00, 0X00, 0XDC,
+[0X05FC] = 0XCB, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0604] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X060C] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0614] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFE, 0XFF, 0XFF, 0XFF,
+[0X061C] = 0XFF, 0XFF, 0XFF, 0XFF, 0X30, 0X26,
+[0X0628] = 0X29, 0X04,
+[0X0630] = 0X30, 0X0A, 0X00, 0X00, 0XF0, 0X11,
+[0X0A30] = 0X30, 0X0E, 0X00, 0X00, 0XB0, 0X23,
+[0X0A38] = 0XF0, 0X24,
+[0X0AB0] = 0X30, 0X1C, 0X00, 0X00, 0X30, 0X21,
+[0X0AB8] = 0XF0, 0X1F,
+[0X0AC0] = 0X70, 0X0F, 0X00, 0X00, 0X70, 0X22,
+[0X0AF0] = 0X70, 0X1D,
+[0X0E28] = 0XB0, 0X1E, 0X00, 0X00, 0XB0, 0X10,
+[0X0E30] = 0XC8, 0XC1, 0XC3, 0XC9, 0XD9, 0XD1, 0XCB, 0XDB,
+[0X0E38] = 0XD3, 0XCA, 0XDA, 0XC5, 0XC7, 0XCD, 0XDD, 0XD5,
+[0X0E40] = 0XCF, 0XDF, 0XD7, 0XCE, 0XDE, 0XE5, 0XE7, 0XFA,
+[0X0E48] = 0XED, 0XFD, 0XF5, 0XEA, 0XF3, 0XFB, 0XD8, 0XF8,
+[0X0E51] = 0X2E, 0X10, 0X3C, 0X2B, 0X29, 0X2F, 0X04, 0X37,
+[0X0E59] = 0X3E, 0X21, 0X2C, 0X20, 0X24, 0X28, 0X0C, 0X34,
+[0X0E61] = 0X02, 0X06, 0X12, 0X32, 0X22, 0X16, 0X36, 0X26,
+[0X0E69] = 0X14, 0X31, 0X30, 0X23, 0X3F, 0X1C, 0X39, 0X48,
+[0X0E71] = 0X41, 0X43, 0X49, 0X59, 0X51, 0X4B, 0X5B, 0X53,
+[0X0E79] = 0X4A, 0X5A, 0X45, 0X47, 0X4D, 0X5D, 0X55, 0X4F,
+[0X0E81] = 0X5F, 0X57, 0X4E, 0X5E, 0X65, 0X67, 0X7A, 0X6D,
+[0X0E89] = 0X7D, 0X75, 0X6A, 0X73, 0X7B, 0X58, 0X38, 0X08,
+[0X0E91] = 0X01, 0X03, 0X09, 0X19, 0X11, 0X0B, 0X1B, 0X13,
+[0X0E99] = 0X0A, 0X1A, 0X05, 0X07, 0X0D, 0X1D, 0X15, 0X0F,
+[0X0EA1] = 0X1F, 0X17, 0X0E, 0X1E, 0X25, 0X27, 0X3A, 0X2D,
+[0X0EA9] = 0X3D, 0X35, 0X2A, 0X33, 0X3B, 0X18, 0X78, 0X88,
+[0X0EB1] = 0X81, 0X83, 0X89, 0X99, 0X91, 0X8B, 0X9B, 0X93,
+[0X0EB9] = 0X8A, 0X9A, 0X85, 0X87, 0X8D, 0X9D, 0X95, 0X8F,
+[0X0EC1] = 0X9F, 0X97, 0X8E, 0X9E, 0XA5, 0XA7, 0XBA, 0XAD,
+[0X0EC9] = 0XBD, 0XB5, 0XAA, 0XB3, 0XBB, 0X98, 0XB8,
+[0X0ED1] = 0X6E, 0X6B, 0X7C, 0X69, 0X6F, 0X71, 0X54, 0X50,
+[0X0ED9] = 0X76, 0X80, 0X63, 0X72, 0X64, 0X66, 0X62, 0X74,
+[0X0EE1] = 0X6C, 0X46, 0X52, 0X44, 0X70, 0X56, 0X68, 0X60,
+[0X0EE9] = 0X42, 0X40, 0X5C, 0X77, 0X7F, 0X7E, 0X79, 0XD6,
+[0X0EF1] = 0XE1, 0XC2, 0XD0, 0XEF, 0XDC, 0XC4, 0XEC, 0XF6,
+[0X0EF9] = 0XE3, 0XC6, 0XFF, 0XE6, 0XE9, 0XD2, 0XF7, 0XE0,
+[0X0F01] = 0XE8, 0XD4, 0XF9, 0XF2, 0XF0, 0XEE, 0X61, 0XCC,
+[0X0F09] = 0XF4, 0XF1, 0XE2, 0XFE, 0XEB, 0XE4, 0XFC, 0X96,
+[0X0F11] = 0XA1, 0X82, 0X90, 0XAF, 0X9C, 0X84, 0XAC, 0XB6,
+[0X0F19] = 0XA3, 0X86, 0XBF, 0XA6, 0XA9, 0X92, 0XB7, 0XA0,
+[0X0F21] = 0XA8, 0X94, 0XB9, 0XB2, 0XB0, 0XAE, 0X4C, 0X8C,
+[0X0F29] = 0XB4, 0XB1, 0XA2, 0XBE, 0XAB, 0XA4, 0XBC, 0XFF,
+[0X0F31] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0F39] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0F41] = 0XFF, 0XFF, 0XFF, 0XFE, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0F49] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X0F60] = 0XFD, 0X5F, 0XFE, 0XDF, 0X01, 0X20,
+[0X1099] = 0X01, 0X00, 0X00, 0XFF, 0X01,
+[0X10A3] = 0XF0, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X10AB] = 0XFF, 0XFF, 0X07,
+[0X11D1] = 0X20, 0XFF, 0X03, 0XFE, 0XFF, 0XFF, 0X07, 0XFE,
+[0X11D9] = 0XFF, 0XFF, 0X07,
+[0X1540] = 0XF0, 0X15, 0X00, 0X00, 0X30, 0X17,
+[0X1548] = 0X70, 0X18, 0X00, 0X00, 0XF0, 0X1A,
+[0X15B4] = 0XB0, 0X19,
+[0X1710] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1718] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1720] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1728] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1850] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1858] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1860] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1868] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1990] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1998] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X19A0] = 0XFF, 0XFF, 0XFF, 0XFF, 0X0F,
+[0X1AD2] = 0XFF, 0XFF, 0XFF, 0X03, 0XFF, 0XFF, 0XFF, 0X03,
+[0X1ADA] = 0XFF, 0XFF, 0XFF, 0X03, 0XFF, 0XFF, 0XFF, 0X03,
+[0X1C29] = 0XC0, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X1D50] = 0XFC, 0X07, 0X3F, 0X77, 0X47, 0X80, 0X01, 0X06,
+[0X1D5A] = 0X04, 0X80,
+[0X1D65] = 0X10,
+[0X1E93] = 0X10, 0X00, 0X00, 0X01,
+[0X1FDB] = 0X01, 0X08,
+[0X2112] = 0X04,
+[0X2250] = 0X08, 0X02, 0X00, 0X00, 0X04,
+[0X2270] = 0X36, 0X36, 0XFF, 0XFF, 0X36, 0X36, 0XFF, 0XFF,
+[0X2278] = 0X36, 0X36, 0XFF, 0XFF, 0XF6, 0XF6, 0XF6, 0XF6,
+[0X2280] = 0XF6, 0XF6, 0XF6, 0XF6, 0X3F, 0X3F, 0X3F, 0X3F,
+[0X2288] = 0X3F, 0X3F, 0X3F, 0X3F, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X2290] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X2298] = 0XFF, 0XFF, 0XFF, 0XFF, 0XF6, 0XF6, 0XF6, 0XF6,
+[0X22A0] = 0XF6, 0XF6, 0XF6, 0XF6, 0X3F, 0X3F, 0X3F, 0X3F,
+[0X22A8] = 0X3F, 0X3F, 0X3F, 0X3F, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X22B0] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X22B8] = 0XFF, 0XFF, 0XFF, 0XFF, 0X36, 0X36, 0XFF, 0XFF,
+[0X22C0] = 0X36, 0XFF, 0XF6, 0XF6, 0XF6, 0XF6, 0XF6, 0XF6,
+[0X22C8] = 0X3F, 0X3F, 0X3F, 0X3F, 0X3F, 0X3F, 0XFF, 0XFF,
+[0X22D0] = 0XFF, 0XFF, 0XFF, 0XFF, 0XF6, 0XF6, 0XF6, 0X3F,
+[0X22D8] = 0X3F, 0X3F, 0XFF, 0XFF, 0XFF, 0XF6, 0XF6, 0X3F,
+[0X22E0] = 0X3F, 0X48, 0X81, 0XC9, 0X36, 0X3F, 0X36, 0XF6,
+[0X22E8] = 0X36, 0X3F, 0X36, 0XF6, 0X36, 0XFF, 0X36, 0XFF,
+[0X22F0] = 0X1B, 0X00, 0XC0, 0X00, 0XE4, 0X00, 0XF6,
+[0X22F8] = 0XFF, 0X00, 0X00, 0X00, 0X47,
+[0X2300] = 0XB8,
+[0X2370] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X2378] = 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
+[0X2380] = 0X55, 0X11, 0X01,
+[0X24DA] = 0X0C, 0X00, 0X03, 0X00, 0X00, 0X61,
+[0X24E2] = 0X04,
+[0X2628] = 0X40, 0X00, 0X00, 0X10,
+[0X2630] = 0X80, 0X00, 0X00, 0X00, 0XAC, 0X20,
+[0X2638] = 0X82, 0X00, 0X00, 0X00, 0X1A, 0X20,
+[0X2640] = 0X83, 0X00, 0X00, 0X00, 0X92, 0X01,
+[0X2648] = 0X84, 0X00, 0X00, 0X00, 0X1E, 0X20,
+[0X2650] = 0X85, 0X00, 0X00, 0X00, 0X26, 0X20,
+[0X2658] = 0X86, 0X00, 0X00, 0X00, 0X20, 0X20,
+[0X2660] = 0X87, 0X00, 0X00, 0X00, 0X21, 0X20,
+[0X2668] = 0X88, 0X00, 0X00, 0X00, 0XC6, 0X02,
+[0X2670] = 0X89, 0X00, 0X00, 0X00, 0X30, 0X20,
+[0X2678] = 0X8A, 0X00, 0X00, 0X00, 0X60, 0X01,
+[0X2680] = 0X8B, 0X00, 0X00, 0X00, 0X39, 0X20,
+[0X2688] = 0X8C, 0X00, 0X00, 0X00, 0X52, 0X01,
+[0X2690] = 0X8E, 0X00, 0X00, 0X00, 0X7D, 0X01,
+[0X2698] = 0X91, 0X00, 0X00, 0X00, 0X18, 0X20,
+[0X26A0] = 0X92, 0X00, 0X00, 0X00, 0X19, 0X20,
+[0X26A8] = 0X93, 0X00, 0X00, 0X00, 0X1C, 0X20,
+[0X26B0] = 0X94, 0X00, 0X00, 0X00, 0X1D, 0X20,
+[0X26B8] = 0X95, 0X00, 0X00, 0X00, 0X22, 0X20,
+[0X26C0] = 0X96, 0X00, 0X00, 0X00, 0X13, 0X20,
+[0X26C8] = 0X97, 0X00, 0X00, 0X00, 0X14, 0X20,
+[0X26D0] = 0X98, 0X00, 0X00, 0X00, 0XDC, 0X02,
+[0X26D8] = 0X99, 0X00, 0X00, 0X00, 0X22, 0X21,
+[0X26E0] = 0X9A, 0X00, 0X00, 0X00, 0X61, 0X01,
+[0X26E8] = 0X9B, 0X00, 0X00, 0X00, 0X3A, 0X20,
+[0X26F0] = 0X9C, 0X00, 0X00, 0X00, 0X53, 0X01,
+[0X26F8] = 0X9E, 0X00, 0X00, 0X00, 0X7E, 0X01,
+[0X2700] = 0X9F, 0X00, 0X00, 0X00, 0X78, 0X01,
+[0X2708] = 0XA0, 0X00, 0X00, 0X00, 0X20,
+[0X2710] = 0XAD, 0X00, 0X00, 0X00, 0X2D,
+[0X2718] = 0X52, 0X01, 0X00, 0X00, 0X8C,
+[0X2720] = 0X53, 0X01, 0X00, 0X00, 0X9C,
+[0X2728] = 0X60, 0X01, 0X00, 0X00, 0X8A,
+[0X2730] = 0X61, 0X01, 0X00, 0X00, 0X9A,
+[0X2738] = 0X78, 0X01, 0X00, 0X00, 0X9F,
+[0X2740] = 0X7D, 0X01, 0X00, 0X00, 0X8E,
+[0X2748] = 0X7E, 0X01, 0X00, 0X00, 0X9E,
+[0X2750] = 0X92, 0X01, 0X00, 0X00, 0X83,
+[0X2758] = 0XC6, 0X02, 0X00, 0X00, 0X88,
+[0X2760] = 0XDC, 0X02, 0X00, 0X00, 0X98,
+[0X2768] = 0X02, 0X20, 0X00, 0X00, 0X20,
+[0X2770] = 0X03, 0X20, 0X00, 0X00, 0X20,
+[0X2778] = 0X04, 0X20, 0X00, 0X00, 0X20,
+[0X2780] = 0X05, 0X20, 0X00, 0X00, 0X20,
+[0X2788] = 0X06, 0X20, 0X00, 0X00, 0X20,
+[0X2790] = 0X07, 0X20, 0X00, 0X00, 0X20,
+[0X2798] = 0X08, 0X20, 0X00, 0X00, 0X20,
+[0X27A0] = 0X09, 0X20, 0X00, 0X00, 0X20,
+[0X27A8] = 0X0A, 0X20, 0X00, 0X00, 0X20,
+[0X27B0] = 0X10, 0X20, 0X00, 0X00, 0X2D,
+[0X27B8] = 0X11, 0X20, 0X00, 0X00, 0X2D,
+[0X27C0] = 0X12, 0X20, 0X00, 0X00, 0X2D,
+[0X27C8] = 0X13, 0X20, 0X00, 0X00, 0X2D,
+[0X27D0] = 0X13, 0X20, 0X00, 0X00, 0X96,
+[0X27D8] = 0X14, 0X20, 0X00, 0X00, 0X2D,
+[0X27E0] = 0X14, 0X20, 0X00, 0X00, 0X97,
+[0X27E8] = 0X15, 0X20, 0X00, 0X00, 0X2D,
+[0X27F0] = 0X18, 0X20, 0X00, 0X00, 0X27,
+[0X27F8] = 0X18, 0X20, 0X00, 0X00, 0X91,
+[0X2800] = 0X19, 0X20, 0X00, 0X00, 0X27,
+[0X2808] = 0X19, 0X20, 0X00, 0X00, 0X92,
+[0X2810] = 0X1A, 0X20, 0X00, 0X00, 0X82,
+[0X2818] = 0X1C, 0X20, 0X00, 0X00, 0X93,
+[0X2820] = 0X1D, 0X20, 0X00, 0X00, 0X94,
+[0X2828] = 0X1E, 0X20, 0X00, 0X00, 0X84,
+[0X2830] = 0X20, 0X20, 0X00, 0X00, 0X86,
+[0X2838] = 0X21, 0X20, 0X00, 0X00, 0X87,
+[0X2840] = 0X22, 0X20, 0X00, 0X00, 0X95,
+[0X2848] = 0X26, 0X20, 0X00, 0X00, 0X85,
+[0X2850] = 0X2F, 0X20, 0X00, 0X00, 0X20,
+[0X2858] = 0X30, 0X20, 0X00, 0X00, 0X89,
+[0X2860] = 0X39, 0X20, 0X00, 0X00, 0X8B,
+[0X2868] = 0X3A, 0X20, 0X00, 0X00, 0X9B,
+[0X2870] = 0X52, 0X20, 0X00, 0X00, 0X2D,
+[0X2878] = 0X5F, 0X20, 0X00, 0X00, 0X20,
+[0X2880] = 0XAC, 0X20, 0X00, 0X00, 0X80,
+[0X2888] = 0X03, 0X21, 0X00, 0X00, 0X43,
+[0X2890] = 0X09, 0X21, 0X00, 0X00, 0X46,
+[0X2898] = 0X22, 0X21, 0X00, 0X00, 0X99,
+[0X28A0] = 0X12, 0X22, 0X00, 0X00, 0X2D,
+[0X28A8] = 0X48, 0X24, 0X00, 0X00, 0X2D,
+[0X28B0] = 0X60, 0X24, 0X00, 0X00, 0X31,
+[0X28B8] = 0X61, 0X24, 0X00, 0X00, 0X32,
+[0X28C0] = 0X62, 0X24, 0X00, 0X00, 0X33,
+[0X28C8] = 0X63, 0X24, 0X00, 0X00, 0X34,
+[0X28D0] = 0X64, 0X24, 0X00, 0X00, 0X35,
+[0X28D8] = 0X65, 0X24, 0X00, 0X00, 0X36,
+[0X28E0] = 0X66, 0X24, 0X00, 0X00, 0X37,
+[0X28E8] = 0X67, 0X24, 0X00, 0X00, 0X38,
+[0X28F0] = 0X68, 0X24, 0X00, 0X00, 0X39,
+[0X28F8] = 0X9C, 0X24, 0X00, 0X00, 0X61,
+[0X2900] = 0X9D, 0X24, 0X00, 0X00, 0X62,
+[0X2908] = 0X9E, 0X24, 0X00, 0X00, 0X63,
+[0X2910] = 0X9F, 0X24, 0X00, 0X00, 0X64,
+[0X2918] = 0XA0, 0X24, 0X00, 0X00, 0X65,
+[0X2920] = 0XA1, 0X24, 0X00, 0X00, 0X66,
+[0X2928] = 0XA2, 0X24, 0X00, 0X00, 0X67,
+[0X2930] = 0XA3, 0X24, 0X00, 0X00, 0X68,
+[0X2938] = 0XA4, 0X24, 0X00, 0X00, 0X69,
+[0X2940] = 0XA5, 0X24, 0X00, 0X00, 0X6A,
+[0X2948] = 0XA6, 0X24, 0X00, 0X00, 0X6B,
+[0X2950] = 0XA7, 0X24, 0X00, 0X00, 0X6C,
+[0X2958] = 0XA8, 0X24, 0X00, 0X00, 0X6D,
+[0X2960] = 0XA9, 0X24, 0X00, 0X00, 0X6E,
+[0X2968] = 0XAA, 0X24, 0X00, 0X00, 0X6F,
+[0X2970] = 0XAB, 0X24, 0X00, 0X00, 0X70,
+[0X2978] = 0XAC, 0X24, 0X00, 0X00, 0X71,
+[0X2980] = 0XAD, 0X24, 0X00, 0X00, 0X72,
+[0X2988] = 0XAE, 0X24, 0X00, 0X00, 0X73,
+[0X2990] = 0XAF, 0X24, 0X00, 0X00, 0X74,
+[0X2998] = 0XB0, 0X24, 0X00, 0X00, 0X75,
+[0X29A0] = 0XB1, 0X24, 0X00, 0X00, 0X76,
+[0X29A8] = 0XB2, 0X24, 0X00, 0X00, 0X77,
+[0X29B0] = 0XB3, 0X24, 0X00, 0X00, 0X78,
+[0X29B8] = 0XB4, 0X24, 0X00, 0X00, 0X79,
+[0X29C0] = 0XB5, 0X24, 0X00, 0X00, 0X7A,
+[0X29C8] = 0XB6, 0X24, 0X00, 0X00, 0X41,
+[0X29D0] = 0XB7, 0X24, 0X00, 0X00, 0X42,
+[0X29D8] = 0XB8, 0X24, 0X00, 0X00, 0X43,
+[0X29E0] = 0XB9, 0X24, 0X00, 0X00, 0X44,
+[0X29E8] = 0XBA, 0X24, 0X00, 0X00, 0X45,
+[0X29F0] = 0XBB, 0X24, 0X00, 0X00, 0X46,
+[0X29F8] = 0XBC, 0X24, 0X00, 0X00, 0X47,
+[0X2A00] = 0XBD, 0X24, 0X00, 0X00, 0X48,
+[0X2A08] = 0XBE, 0X24, 0X00, 0X00, 0X49,
+[0X2A10] = 0XBF, 0X24, 0X00, 0X00, 0X4A,
+[0X2A18] = 0XC0, 0X24, 0X00, 0X00, 0X4B,
+[0X2A20] = 0XC1, 0X24, 0X00, 0X00, 0X4C,
+[0X2A28] = 0XC2, 0X24, 0X00, 0X00, 0X4D,
+[0X2A30] = 0XC3, 0X24, 0X00, 0X00, 0X4E,
+[0X2A38] = 0XC4, 0X24, 0X00, 0X00, 0X4F,
+[0X2A40] = 0XC5, 0X24, 0X00, 0X00, 0X50,
+[0X2A48] = 0XC6, 0X24, 0X00, 0X00, 0X51,
+[0X2A50] = 0XC7, 0X24, 0X00, 0X00, 0X52,
+[0X2A58] = 0XC8, 0X24, 0X00, 0X00, 0X53,
+[0X2A60] = 0XC9, 0X24, 0X00, 0X00, 0X54,
+[0X2A68] = 0XCA, 0X24, 0X00, 0X00, 0X55,
+[0X2A70] = 0XCB, 0X24, 0X00, 0X00, 0X56,
+[0X2A78] = 0XCC, 0X24, 0X00, 0X00, 0X57,
+[0X2A80] = 0XCD, 0X24, 0X00, 0X00, 0X58,
+[0X2A88] = 0XCE, 0X24, 0X00, 0X00, 0X59,
+[0X2A90] = 0XCF, 0X24, 0X00, 0X00, 0X5A,
+[0X2A98] = 0XD0, 0X24, 0X00, 0X00, 0X61,
+[0X2AA0] = 0XD1, 0X24, 0X00, 0X00, 0X62,
+[0X2AA8] = 0XD2, 0X24, 0X00, 0X00, 0X63,
+[0X2AB0] = 0XD3, 0X24, 0X00, 0X00, 0X64,
+[0X2AB8] = 0XD4, 0X24, 0X00, 0X00, 0X65,
+[0X2AC0] = 0XD5, 0X24, 0X00, 0X00, 0X66,
+[0X2AC8] = 0XD6, 0X24, 0X00, 0X00, 0X67,
+[0X2AD0] = 0XD7, 0X24, 0X00, 0X00, 0X68,
+[0X2AD8] = 0XD8, 0X24, 0X00, 0X00, 0X69,
+[0X2AE0] = 0XD9, 0X24, 0X00, 0X00, 0X6A,
+[0X2AE8] = 0XDA, 0X24, 0X00, 0X00, 0X6B,
+[0X2AF0] = 0XDB, 0X24, 0X00, 0X00, 0X6C,
+[0X2AF8] = 0XDC, 0X24, 0X00, 0X00, 0X6D,
+[0X2B00] = 0XDD, 0X24, 0X00, 0X00, 0X6E,
+[0X2B08] = 0XDE, 0X24, 0X00, 0X00, 0X6F,
+[0X2B10] = 0XDF, 0X24, 0X00, 0X00, 0X70,
+[0X2B18] = 0XE0, 0X24, 0X00, 0X00, 0X71,
+[0X2B20] = 0XE1, 0X24, 0X00, 0X00, 0X72,
+[0X2B28] = 0XE2, 0X24, 0X00, 0X00, 0X73,
+[0X2B30] = 0XE3, 0X24, 0X00, 0X00, 0X74,
+[0X2B38] = 0XE4, 0X24, 0X00, 0X00, 0X75,
+[0X2B40] = 0XE5, 0X24, 0X00, 0X00, 0X76,
+[0X2B48] = 0XE6, 0X24, 0X00, 0X00, 0X77,
+[0X2B50] = 0XE7, 0X24, 0X00, 0X00, 0X78,
+[0X2B58] = 0XE8, 0X24, 0X00, 0X00, 0X79,
+[0X2B60] = 0XE9, 0X24, 0X00, 0X00, 0X7A,
+[0X2B68] = 0XEA, 0X24, 0X00, 0X00, 0X30,
+[0X2B70] = 0X1C, 0X30, 0X00, 0X00, 0X2D,
+[0X2B78] = 0X30, 0X30, 0X00, 0X00, 0X2D,
+[0X2B80] = 0X58, 0XFE, 0X00, 0X00, 0X2D,
+[0X2B88] = 0X63, 0XFE, 0X00, 0X00, 0X2D,
+[0X2B90] = 0X0D, 0XFF, 0X00, 0X00, 0X2D,
+[0X2B98] = 0X10, 0XFF, 0X00, 0X00, 0X30,
+[0X2BA0] = 0X11, 0XFF, 0X00, 0X00, 0X31,
+[0X2BA8] = 0X12, 0XFF, 0X00, 0X00, 0X32,
+[0X2BB0] = 0X13, 0XFF, 0X00, 0X00, 0X33,
+[0X2BB8] = 0X14, 0XFF, 0X00, 0X00, 0X34,
+[0X2BC0] = 0X15, 0XFF, 0X00, 0X00, 0X35,
+[0X2BC8] = 0X16, 0XFF, 0X00, 0X00, 0X36,
+[0X2BD0] = 0X17, 0XFF, 0X00, 0X00, 0X37,
+[0X2BD8] = 0X18, 0XFF, 0X00, 0X00, 0X38,
+[0X2BE0] = 0X19, 0XFF, 0X00, 0X00, 0X39,
+[0X2BE8] = 0X21, 0XFF, 0X00, 0X00, 0X41,
+[0X2BF0] = 0X22, 0XFF, 0X00, 0X00, 0X42,
+[0X2BF8] = 0X23, 0XFF, 0X00, 0X00, 0X43,
+[0X2C00] = 0X24, 0XFF, 0X00, 0X00, 0X44,
+[0X2C08] = 0X25, 0XFF, 0X00, 0X00, 0X45,
+[0X2C10] = 0X26, 0XFF, 0X00, 0X00, 0X46,
+[0X2C18] = 0X27, 0XFF, 0X00, 0X00, 0X47,
+[0X2C20] = 0X28, 0XFF, 0X00, 0X00, 0X48,
+[0X2C28] = 0X29, 0XFF, 0X00, 0X00, 0X49,
+[0X2C30] = 0X2A, 0XFF, 0X00, 0X00, 0X4A,
+[0X2C38] = 0X2B, 0XFF, 0X00, 0X00, 0X4B,
+[0X2C40] = 0X2C, 0XFF, 0X00, 0X00, 0X4C,
+[0X2C48] = 0X2D, 0XFF, 0X00, 0X00, 0X4D,
+[0X2C50] = 0X2E, 0XFF, 0X00, 0X00, 0X4E,
+[0X2C58] = 0X2F, 0XFF, 0X00, 0X00, 0X4F,
+[0X2C60] = 0X30, 0XFF, 0X00, 0X00, 0X50,
+[0X2C68] = 0X31, 0XFF, 0X00, 0X00, 0X51,
+[0X2C70] = 0X32, 0XFF, 0X00, 0X00, 0X52,
+[0X2C78] = 0X33, 0XFF, 0X00, 0X00, 0X53,
+[0X2C80] = 0X34, 0XFF, 0X00, 0X00, 0X54,
+[0X2C88] = 0X35, 0XFF, 0X00, 0X00, 0X55,
+[0X2C90] = 0X36, 0XFF, 0X00, 0X00, 0X56,
+[0X2C98] = 0X37, 0XFF, 0X00, 0X00, 0X57,
+[0X2CA0] = 0X38, 0XFF, 0X00, 0X00, 0X58,
+[0X2CA8] = 0X39, 0XFF, 0X00, 0X00, 0X59,
+[0X2CB0] = 0X3A, 0XFF, 0X00, 0X00, 0X5A,
+[0X2CB8] = 0X41, 0XFF, 0X00, 0X00, 0X61,
+[0X2CC0] = 0X42, 0XFF, 0X00, 0X00, 0X62,
+[0X2CC8] = 0X43, 0XFF, 0X00, 0X00, 0X63,
+[0X2CD0] = 0X44, 0XFF, 0X00, 0X00, 0X64,
+[0X2CD8] = 0X45, 0XFF, 0X00, 0X00, 0X65,
+[0X2CE0] = 0X46, 0XFF, 0X00, 0X00, 0X66,
+[0X2CE8] = 0X47, 0XFF, 0X00, 0X00, 0X67,
+[0X2CF0] = 0X48, 0XFF, 0X00, 0X00, 0X68,
+[0X2CF8] = 0X49, 0XFF, 0X00, 0X00, 0X69,
+[0X2D00] = 0X4A, 0XFF, 0X00, 0X00, 0X6A,
+[0X2D08] = 0X4B, 0XFF, 0X00, 0X00, 0X6B,
+[0X2D10] = 0X4C, 0XFF, 0X00, 0X00, 0X6C,
+[0X2D18] = 0X4D, 0XFF, 0X00, 0X00, 0X6D,
+[0X2D20] = 0X4E, 0XFF, 0X00, 0X00, 0X6E,
+[0X2D28] = 0X4F, 0XFF, 0X00, 0X00, 0X6F,
+[0X2D30] = 0X50, 0XFF, 0X00, 0X00, 0X70,
+[0X2D38] = 0X51, 0XFF, 0X00, 0X00, 0X71,
+[0X2D40] = 0X52, 0XFF, 0X00, 0X00, 0X72,
+[0X2D48] = 0X53, 0XFF, 0X00, 0X00, 0X73,
+[0X2D50] = 0X54, 0XFF, 0X00, 0X00, 0X74,
+[0X2D58] = 0X55, 0XFF, 0X00, 0X00, 0X75,
+[0X2D60] = 0X56, 0XFF, 0X00, 0X00, 0X76,
+[0X2D68] = 0X57, 0XFF, 0X00, 0X00, 0X77,
+[0X2D70] = 0X58, 0XFF, 0X00, 0X00, 0X78,
+[0X2D78] = 0X59, 0XFF, 0X00, 0X00, 0X79,
+[0X2D80] = 0X5A, 0XFF, 0X00, 0X00, 0X7A,
+[0X2D89] = 0XD4, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X01,
+[0X2D91] = 0XD4, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X02,
+[0X2D99] = 0XD4, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X03,
+[0X2DA1] = 0XD4, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X04,
+[0X2DA9] = 0XD4, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X05,
+[0X2DB1] = 0XD4, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X06,
+[0X2DB9] = 0XD4, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X07,
+[0X2DC1] = 0XD4, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X08,
+[0X2DC9] = 0XD4, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X09,
+[0X2DD1] = 0XD4, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X0A,
+[0X2DD9] = 0XD4, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X0B,
+[0X2DE1] = 0XD4, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X0C,
+[0X2DE9] = 0XD4, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X0D,
+[0X2DF1] = 0XD4, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X0E,
+[0X2DF9] = 0XD4, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X0F,
+[0X2E01] = 0XD4, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X10,
+[0X2E09] = 0XD4, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X11,
+[0X2E11] = 0XD4, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X12,
+[0X2E19] = 0XD4, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X13,
+[0X2E21] = 0XD4, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X14,
+[0X2E29] = 0XD4, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X15,
+[0X2E31] = 0XD4, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X16,
+[0X2E39] = 0XD4, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X17,
+[0X2E41] = 0XD4, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X18,
+[0X2E49] = 0XD4, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X19,
+[0X2E51] = 0XD4, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X1A,
+[0X2E59] = 0XD4, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X1B,
+[0X2E61] = 0XD4, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X1C,
+[0X2E69] = 0XD4, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X1D,
+[0X2E71] = 0XD4, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X1E,
+[0X2E79] = 0XD4, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X1F,
+[0X2E81] = 0XD4, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X20,
+[0X2E89] = 0XD4, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X21,
+[0X2E91] = 0XD4, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X22,
+[0X2E99] = 0XD4, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X23,
+[0X2EA1] = 0XD4, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X24,
+[0X2EA9] = 0XD4, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X25,
+[0X2EB1] = 0XD4, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X26,
+[0X2EB9] = 0XD4, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X27,
+[0X2EC1] = 0XD4, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X28,
+[0X2EC9] = 0XD4, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X29,
+[0X2ED1] = 0XD4, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X2A,
+[0X2ED9] = 0XD4, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X2B,
+[0X2EE1] = 0XD4, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X2C,
+[0X2EE9] = 0XD4, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X2D,
+[0X2EF1] = 0XD4, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X2E,
+[0X2EF9] = 0XD4, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X2F,
+[0X2F01] = 0XD4, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X30,
+[0X2F09] = 0XD4, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X31,
+[0X2F11] = 0XD4, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X32,
+[0X2F19] = 0XD4, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X33,
+[0X2F21] = 0XD4, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X34,
+[0X2F29] = 0XD4, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X35,
+[0X2F31] = 0XD4, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X36,
+[0X2F39] = 0XD4, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X37,
+[0X2F41] = 0XD4, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X38,
+[0X2F49] = 0XD4, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X39,
+[0X2F51] = 0XD4, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X3A,
+[0X2F59] = 0XD4, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X3B,
+[0X2F61] = 0XD4, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X3C,
+[0X2F69] = 0XD4, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X3D,
+[0X2F71] = 0XD4, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X3E,
+[0X2F79] = 0XD4, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X3F,
+[0X2F81] = 0XD4, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X40,
+[0X2F89] = 0XD4, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X41,
+[0X2F91] = 0XD4, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X42,
+[0X2F99] = 0XD4, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X43,
+[0X2FA1] = 0XD4, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X44,
+[0X2FA9] = 0XD4, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X45,
+[0X2FB1] = 0XD4, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X46,
+[0X2FB9] = 0XD4, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X47,
+[0X2FC1] = 0XD4, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X48,
+[0X2FC9] = 0XD4, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X49,
+[0X2FD1] = 0XD4, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X4A,
+[0X2FD9] = 0XD4, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X4B,
+[0X2FE1] = 0XD4, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X4C,
+[0X2FE9] = 0XD4, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X4D,
+[0X2FF1] = 0XD4, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X4E,
+[0X2FF9] = 0XD4, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X4F,
+[0X3001] = 0XD4, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X50,
+[0X3009] = 0XD4, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X51,
+[0X3011] = 0XD4, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X52,
+[0X3019] = 0XD4, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X53,
+[0X3021] = 0XD4, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X54,
+[0X3029] = 0XD4, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X55,
+[0X3031] = 0XD4, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X56,
+[0X3039] = 0XD4, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X57,
+[0X3041] = 0XD4, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X58,
+[0X3049] = 0XD4, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X59,
+[0X3051] = 0XD4, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X5A,
+[0X3059] = 0XD4, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X5B,
+[0X3061] = 0XD4, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X5C,
+[0X3069] = 0XD4, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X5D,
+[0X3071] = 0XD4, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X5E,
+[0X3079] = 0XD4, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X5F,
+[0X3081] = 0XD4, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X60,
+[0X3089] = 0XD4, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X61,
+[0X3091] = 0XD4, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X62,
+[0X3099] = 0XD4, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X63,
+[0X30A1] = 0XD4, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X64,
+[0X30A9] = 0XD4, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X65,
+[0X30B1] = 0XD4, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X66,
+[0X30B9] = 0XD4, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X67,
+[0X30C1] = 0XD4, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X68,
+[0X30C9] = 0XD4, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X69,
+[0X30D1] = 0XD4, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X6A,
+[0X30D9] = 0XD4, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X6B,
+[0X30E1] = 0XD4, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X6C,
+[0X30E9] = 0XD4, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X6D,
+[0X30F1] = 0XD4, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X6E,
+[0X30F9] = 0XD4, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X6F,
+[0X3101] = 0XD4, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X70,
+[0X3109] = 0XD4, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X71,
+[0X3111] = 0XD4, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X72,
+[0X3119] = 0XD4, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X73,
+[0X3121] = 0XD4, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X74,
+[0X3129] = 0XD4, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X75,
+[0X3131] = 0XD4, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X76,
+[0X3139] = 0XD4, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X77,
+[0X3141] = 0XD4, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X78,
+[0X3149] = 0XD4, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X79,
+[0X3151] = 0XD4, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X7A,
+[0X3159] = 0XD4, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X7B,
+[0X3161] = 0XD4, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X7C,
+[0X3169] = 0XD4, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X7D,
+[0X3171] = 0XD4, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X7E,
+[0X3179] = 0XD4, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X7F,
+[0X3181] = 0XD4, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X80,
+[0X3189] = 0XD4, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X81,
+[0X3191] = 0XD4, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X82,
+[0X3199] = 0XD4, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X83,
+[0X31A1] = 0XD4, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X84,
+[0X31A9] = 0XD4, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X85,
+[0X31B1] = 0XD4, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X86,
+[0X31B9] = 0XD4, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X87,
+[0X31C1] = 0XD4, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X88,
+[0X31C9] = 0XD4, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X89,
+[0X31D1] = 0XD4, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X8A,
+[0X31D9] = 0XD4, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X8B,
+[0X31E1] = 0XD4, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X8C,
+[0X31E9] = 0XD4, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X8D,
+[0X31F1] = 0XD4, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X8E,
+[0X31F9] = 0XD4, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X8F,
+[0X3201] = 0XD4, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X90,
+[0X3209] = 0XD4, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X91,
+[0X3211] = 0XD4, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X92,
+[0X3219] = 0XD4, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X93,
+[0X3221] = 0XD4, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X94,
+[0X3229] = 0XD4, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X95,
+[0X3231] = 0XD4, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X96,
+[0X3239] = 0XD4, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X97,
+[0X3241] = 0XD4, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X98,
+[0X3249] = 0XD4, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X99,
+[0X3251] = 0XD4, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X9A,
+[0X3259] = 0XD4, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X9B,
+[0X3261] = 0XD4, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X9C,
+[0X3269] = 0XD4, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X9D,
+[0X3271] = 0XD4, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X9E,
+[0X3279] = 0XD4, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X9F,
+[0X3281] = 0XD4, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0XA0,
+[0X3289] = 0XD4, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0XA1,
+[0X3291] = 0XD4, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0XA2,
+[0X3299] = 0XD4, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0XA3,
+[0X32A1] = 0XD4, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0XA4,
+[0X32A9] = 0XD4, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0XA5,
+[0X32B1] = 0XD4, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0XA6,
+[0X32B9] = 0XD4, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0XA7,
+[0X32C1] = 0XD4, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0XA8,
+[0X32C9] = 0XD4, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0XA9,
+[0X32D1] = 0XD4, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0XAA,
+[0X32D9] = 0XD4, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0XAB,
+[0X32E1] = 0XD4, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0XAC,
+[0X32E9] = 0XD4, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0XAD,
+[0X32F1] = 0XD4, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0XAE,
+[0X32F9] = 0XD4, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0XAF,
+[0X3301] = 0XD4, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0XB0,
+[0X3309] = 0XD4, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0XB1,
+[0X3311] = 0XD4, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0XB2,
+[0X3319] = 0XD4, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0XB3,
+[0X3321] = 0XD4, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0XB4,
+[0X3329] = 0XD4, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0XB5,
+[0X3331] = 0XD4, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0XB6,
+[0X3339] = 0XD4, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0XB7,
+[0X3341] = 0XD4, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0XB8,
+[0X3349] = 0XD4, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0XB9,
+[0X3351] = 0XD4, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0XBA,
+[0X3359] = 0XD4, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0XBB,
+[0X3361] = 0XD4, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0XBC,
+[0X3369] = 0XD4, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0XBD,
+[0X3371] = 0XD4, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0XBE,
+[0X3379] = 0XD4, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0XBF,
+[0X3381] = 0XD4, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0XC0,
+[0X3389] = 0XD4, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0XC1,
+[0X3391] = 0XD4, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0XC2,
+[0X3399] = 0XD4, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0XC3,
+[0X33A1] = 0XD4, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0XC4,
+[0X33A9] = 0XD4, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0XC5,
+[0X33B1] = 0XD4, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0XC6,
+[0X33B9] = 0XD4, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0XC7,
+[0X33C1] = 0XD4, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0XC8,
+[0X33C9] = 0XD4, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0XC9,
+[0X33D1] = 0XD4, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0XCA,
+[0X33D9] = 0XD4, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0XCB,
+[0X33E1] = 0XD4, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0XCC,
+[0X33E9] = 0XD4, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0XCD,
+[0X33F1] = 0XD4, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0XCE,
+[0X33F9] = 0XD4, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0XCF,
+[0X3401] = 0XD4, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0XD0,
+[0X3409] = 0XD4, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0XD1,
+[0X3411] = 0XD4, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0XD2,
+[0X3419] = 0XD4, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0XD3,
+[0X3421] = 0XD4, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0XD4,
+[0X3429] = 0XD4, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0XD5,
+[0X3431] = 0XD4, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0XD6,
+[0X3439] = 0XD4, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0XD7,
+[0X3441] = 0XD4, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0XD8,
+[0X3449] = 0XD4, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0XD9,
+[0X3451] = 0XD4, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0XDA,
+[0X3459] = 0XD4, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0XDB,
+[0X3461] = 0XD4, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0XDC,
+[0X3469] = 0XD4, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0XDD,
+[0X3471] = 0XD4, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0XDE,
+[0X3479] = 0XD4, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0XDF,
+[0X3481] = 0XD4, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0XE0,
+[0X3489] = 0XD4, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0XE1,
+[0X3491] = 0XD4, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0XE2,
+[0X3499] = 0XD4, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0XE3,
+[0X34A1] = 0XD4, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0XE4,
+[0X34A9] = 0XD4, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0XE5,
+[0X34B1] = 0XD4, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0XE6,
+[0X34B9] = 0XD4, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0XE7,
+[0X34C1] = 0XD4, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0XE8,
+[0X34C9] = 0XD4, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0XE9,
+[0X34D1] = 0XD4, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0XEA,
+[0X34D9] = 0XD4, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0XEB,
+[0X34E1] = 0XD4, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0XEC,
+[0X34E9] = 0XD4, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0XED,
+[0X34F1] = 0XD4, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0XEE,
+[0X34F9] = 0XD4, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0XEF,
+[0X3501] = 0XD4, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0XF0,
+[0X3509] = 0XD4, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0XF1,
+[0X3511] = 0XD4, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0XF2,
+[0X3519] = 0XD4, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0XF3,
+[0X3521] = 0XD4, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0XF4,
+[0X3529] = 0XD4, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0XF5,
+[0X3531] = 0XD4, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0XF6,
+[0X3539] = 0XD4, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0XF7,
+[0X3541] = 0XD4, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0XF8,
+[0X3549] = 0XD4, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0XF9,
+[0X3551] = 0XD4, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0XFA,
+[0X3559] = 0XD4, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0XFB,
+[0X3561] = 0XD4, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0XFC,
+[0X3569] = 0XD4, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0XFD,
+[0X3571] = 0XD4, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0XFE,
+[0X3579] = 0XD4, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0XFF,
+[0X3581] = 0XD4, 0X01, 0X00, 0X76,
+[0X3589] = 0XD5, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X01,
+[0X3591] = 0XD5, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X02,
+[0X3599] = 0XD5, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X03,
+[0X35A1] = 0XD5, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X04,
+[0X35A9] = 0XD5, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X05,
+[0X35B1] = 0XD5, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X06,
+[0X35B9] = 0XD5, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X07,
+[0X35C1] = 0XD5, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X08,
+[0X35C9] = 0XD5, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X09,
+[0X35D1] = 0XD5, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X0A,
+[0X35D9] = 0XD5, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X0B,
+[0X35E1] = 0XD5, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X0C,
+[0X35E9] = 0XD5, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X0D,
+[0X35F1] = 0XD5, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X0E,
+[0X35F9] = 0XD5, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X0F,
+[0X3601] = 0XD5, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X10,
+[0X3609] = 0XD5, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X11,
+[0X3611] = 0XD5, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X12,
+[0X3619] = 0XD5, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X13,
+[0X3621] = 0XD5, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X14,
+[0X3629] = 0XD5, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X15,
+[0X3631] = 0XD5, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X16,
+[0X3639] = 0XD5, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X17,
+[0X3641] = 0XD5, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X18,
+[0X3649] = 0XD5, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X19,
+[0X3651] = 0XD5, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X1A,
+[0X3659] = 0XD5, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X1B,
+[0X3661] = 0XD5, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X1C,
+[0X3669] = 0XD5, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X1D,
+[0X3671] = 0XD5, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X1E,
+[0X3679] = 0XD5, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X1F,
+[0X3681] = 0XD5, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X20,
+[0X3689] = 0XD5, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X21,
+[0X3691] = 0XD5, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X22,
+[0X3699] = 0XD5, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X23,
+[0X36A1] = 0XD5, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X24,
+[0X36A9] = 0XD5, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X25,
+[0X36B1] = 0XD5, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X26,
+[0X36B9] = 0XD5, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X27,
+[0X36C1] = 0XD5, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X28,
+[0X36C9] = 0XD5, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X29,
+[0X36D1] = 0XD5, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X2A,
+[0X36D9] = 0XD5, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X2B,
+[0X36E1] = 0XD5, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X2C,
+[0X36E9] = 0XD5, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X2D,
+[0X36F1] = 0XD5, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X2E,
+[0X36F9] = 0XD5, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X2F,
+[0X3701] = 0XD5, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X30,
+[0X3709] = 0XD5, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X31,
+[0X3711] = 0XD5, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X32,
+[0X3719] = 0XD5, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X33,
+[0X3721] = 0XD5, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X34,
+[0X3729] = 0XD5, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X35,
+[0X3731] = 0XD5, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X36,
+[0X3739] = 0XD5, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X37,
+[0X3741] = 0XD5, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X38,
+[0X3749] = 0XD5, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X39,
+[0X3751] = 0XD5, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X3A,
+[0X3759] = 0XD5, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X3B,
+[0X3761] = 0XD5, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X3C,
+[0X3769] = 0XD5, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X3D,
+[0X3771] = 0XD5, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X3E,
+[0X3779] = 0XD5, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X3F,
+[0X3781] = 0XD5, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X40,
+[0X3789] = 0XD5, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X41,
+[0X3791] = 0XD5, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X42,
+[0X3799] = 0XD5, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X43,
+[0X37A1] = 0XD5, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X44,
+[0X37A9] = 0XD5, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X45,
+[0X37B1] = 0XD5, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X46,
+[0X37B9] = 0XD5, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X47,
+[0X37C1] = 0XD5, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X48,
+[0X37C9] = 0XD5, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X49,
+[0X37D1] = 0XD5, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X4A,
+[0X37D9] = 0XD5, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X4B,
+[0X37E1] = 0XD5, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X4C,
+[0X37E9] = 0XD5, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X4D,
+[0X37F1] = 0XD5, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X4E,
+[0X37F9] = 0XD5, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X4F,
+[0X3801] = 0XD5, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X50,
+[0X3809] = 0XD5, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X51,
+[0X3811] = 0XD5, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X52,
+[0X3819] = 0XD5, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X53,
+[0X3821] = 0XD5, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X54,
+[0X3829] = 0XD5, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X55,
+[0X3831] = 0XD5, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X56,
+[0X3839] = 0XD5, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X57,
+[0X3841] = 0XD5, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X58,
+[0X3849] = 0XD5, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X59,
+[0X3851] = 0XD5, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X5A,
+[0X3859] = 0XD5, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X5B,
+[0X3861] = 0XD5, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X5C,
+[0X3869] = 0XD5, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X5D,
+[0X3871] = 0XD5, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X5E,
+[0X3879] = 0XD5, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X5F,
+[0X3881] = 0XD5, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X60,
+[0X3889] = 0XD5, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X61,
+[0X3891] = 0XD5, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X62,
+[0X3899] = 0XD5, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X63,
+[0X38A1] = 0XD5, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X64,
+[0X38A9] = 0XD5, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X65,
+[0X38B1] = 0XD5, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X66,
+[0X38B9] = 0XD5, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X67,
+[0X38C1] = 0XD5, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X68,
+[0X38C9] = 0XD5, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X69,
+[0X38D1] = 0XD5, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X6A,
+[0X38D9] = 0XD5, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X6B,
+[0X38E1] = 0XD5, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X6C,
+[0X38E9] = 0XD5, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X6D,
+[0X38F1] = 0XD5, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X6E,
+[0X38F9] = 0XD5, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X6F,
+[0X3901] = 0XD5, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X70,
+[0X3909] = 0XD5, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X71,
+[0X3911] = 0XD5, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X72,
+[0X3919] = 0XD5, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X73,
+[0X3921] = 0XD5, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X74,
+[0X3929] = 0XD5, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X75,
+[0X3931] = 0XD5, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X76,
+[0X3939] = 0XD5, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X77,
+[0X3941] = 0XD5, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X78,
+[0X3949] = 0XD5, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X79,
+[0X3951] = 0XD5, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X7A,
+[0X3959] = 0XD5, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X7B,
+[0X3961] = 0XD5, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X7C,
+[0X3969] = 0XD5, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X7D,
+[0X3971] = 0XD5, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X7E,
+[0X3979] = 0XD5, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X7F,
+[0X3981] = 0XD5, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X80,
+[0X3989] = 0XD5, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X81,
+[0X3991] = 0XD5, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X82,
+[0X3999] = 0XD5, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X83,
+[0X39A1] = 0XD5, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X84,
+[0X39A9] = 0XD5, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X85,
+[0X39B1] = 0XD5, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X86,
+[0X39B9] = 0XD5, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X87,
+[0X39C1] = 0XD5, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X88,
+[0X39C9] = 0XD5, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X89,
+[0X39D1] = 0XD5, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X8A,
+[0X39D9] = 0XD5, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X8B,
+[0X39E1] = 0XD5, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X8C,
+[0X39E9] = 0XD5, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X8D,
+[0X39F1] = 0XD5, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X8E,
+[0X39F9] = 0XD5, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X8F,
+[0X3A01] = 0XD5, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X90,
+[0X3A09] = 0XD5, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X91,
+[0X3A11] = 0XD5, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X92,
+[0X3A19] = 0XD5, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X93,
+[0X3A21] = 0XD5, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X94,
+[0X3A29] = 0XD5, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X95,
+[0X3A31] = 0XD5, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X96,
+[0X3A39] = 0XD5, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X97,
+[0X3A41] = 0XD5, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X98,
+[0X3A49] = 0XD5, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X99,
+[0X3A51] = 0XD5, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X9A,
+[0X3A59] = 0XD5, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X9B,
+[0X3A61] = 0XD5, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X9C,
+[0X3A69] = 0XD5, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X9D,
+[0X3A71] = 0XD5, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X9E,
+[0X3A79] = 0XD5, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X9F,
+[0X3A81] = 0XD5, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0XA0,
+[0X3A89] = 0XD5, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0XA1,
+[0X3A91] = 0XD5, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0XA2,
+[0X3A99] = 0XD5, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0XA3,
+[0X3AA1] = 0XD5, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0XA4,
+[0X3AA9] = 0XD5, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0XA5,
+[0X3AB1] = 0XD5, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0XA6,
+[0X3AB9] = 0XD5, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0XA7,
+[0X3AC1] = 0XD5, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0XA8,
+[0X3AC9] = 0XD5, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0XA9,
+[0X3AD1] = 0XD5, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0XAA,
+[0X3AD9] = 0XD5, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0XAB,
+[0X3AE1] = 0XD5, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0XAC,
+[0X3AE9] = 0XD5, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0XAD,
+[0X3AF1] = 0XD5, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0XAE,
+[0X3AF9] = 0XD5, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0XAF,
+[0X3B01] = 0XD5, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0XB0,
+[0X3B09] = 0XD5, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0XB1,
+[0X3B11] = 0XD5, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0XB2,
+[0X3B19] = 0XD5, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0XB3,
+[0X3B21] = 0XD5, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0XB4,
+[0X3B29] = 0XD5, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0XB5,
+[0X3B31] = 0XD5, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0XB6,
+[0X3B39] = 0XD5, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0XB7,
+[0X3B41] = 0XD5, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0XB8,
+[0X3B49] = 0XD5, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0XB9,
+[0X3B51] = 0XD5, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0XBA,
+[0X3B59] = 0XD5, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0XBB,
+[0X3B61] = 0XD5, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0XBC,
+[0X3B69] = 0XD5, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0XBD,
+[0X3B71] = 0XD5, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0XBE,
+[0X3B79] = 0XD5, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0XBF,
+[0X3B81] = 0XD5, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0XC0,
+[0X3B89] = 0XD5, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0XC1,
+[0X3B91] = 0XD5, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0XC2,
+[0X3B99] = 0XD5, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0XC3,
+[0X3BA1] = 0XD5, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0XC4,
+[0X3BA9] = 0XD5, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0XC5,
+[0X3BB1] = 0XD5, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0XC6,
+[0X3BB9] = 0XD5, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0XC7,
+[0X3BC1] = 0XD5, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0XC8,
+[0X3BC9] = 0XD5, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0XC9,
+[0X3BD1] = 0XD5, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0XCA,
+[0X3BD9] = 0XD5, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0XCB,
+[0X3BE1] = 0XD5, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0XCC,
+[0X3BE9] = 0XD5, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0XCD,
+[0X3BF1] = 0XD5, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0XCE,
+[0X3BF9] = 0XD5, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0XCF,
+[0X3C01] = 0XD5, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0XD0,
+[0X3C09] = 0XD5, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0XD1,
+[0X3C11] = 0XD5, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0XD2,
+[0X3C19] = 0XD5, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0XD3,
+[0X3C21] = 0XD5, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0XD4,
+[0X3C29] = 0XD5, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0XD5,
+[0X3C31] = 0XD5, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0XD6,
+[0X3C39] = 0XD5, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0XD7,
+[0X3C41] = 0XD5, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0XD8,
+[0X3C49] = 0XD5, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0XD9,
+[0X3C51] = 0XD5, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0XDA,
+[0X3C59] = 0XD5, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0XDB,
+[0X3C61] = 0XD5, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0XDC,
+[0X3C69] = 0XD5, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0XDD,
+[0X3C71] = 0XD5, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0XDE,
+[0X3C79] = 0XD5, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0XDF,
+[0X3C81] = 0XD5, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0XE0,
+[0X3C89] = 0XD5, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0XE1,
+[0X3C91] = 0XD5, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0XE2,
+[0X3C99] = 0XD5, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0XE3,
+[0X3CA1] = 0XD5, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0XE4,
+[0X3CA9] = 0XD5, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0XE5,
+[0X3CB1] = 0XD5, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0XE6,
+[0X3CB9] = 0XD5, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0XE7,
+[0X3CC1] = 0XD5, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0XE8,
+[0X3CC9] = 0XD5, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0XE9,
+[0X3CD1] = 0XD5, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0XEA,
+[0X3CD9] = 0XD5, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0XEB,
+[0X3CE1] = 0XD5, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0XEC,
+[0X3CE9] = 0XD5, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0XED,
+[0X3CF1] = 0XD5, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0XEE,
+[0X3CF9] = 0XD5, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0XEF,
+[0X3D01] = 0XD5, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0XF0,
+[0X3D09] = 0XD5, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0XF1,
+[0X3D11] = 0XD5, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0XF2,
+[0X3D19] = 0XD5, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0XF3,
+[0X3D21] = 0XD5, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0XF4,
+[0X3D29] = 0XD5, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0XF5,
+[0X3D31] = 0XD5, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0XF6,
+[0X3D39] = 0XD5, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0XF7,
+[0X3D41] = 0XD5, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0XF8,
+[0X3D49] = 0XD5, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0XF9,
+[0X3D51] = 0XD5, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0XFA,
+[0X3D59] = 0XD5, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0XFB,
+[0X3D61] = 0XD5, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0XFC,
+[0X3D69] = 0XD5, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0XFD,
+[0X3D71] = 0XD5, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0XFE,
+[0X3D79] = 0XD5, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0XFF,
+[0X3D81] = 0XD5, 0X01, 0X00, 0X72,
+[0X3D89] = 0XD6, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X01,
+[0X3D91] = 0XD6, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X02,
+[0X3D99] = 0XD6, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X03,
+[0X3DA1] = 0XD6, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X04,
+[0X3DA9] = 0XD6, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X05,
+[0X3DB1] = 0XD6, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X06,
+[0X3DB9] = 0XD6, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X07,
+[0X3DC1] = 0XD6, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X08,
+[0X3DC9] = 0XD6, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X09,
+[0X3DD1] = 0XD6, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X0A,
+[0X3DD9] = 0XD6, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X0B,
+[0X3DE1] = 0XD6, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X0C,
+[0X3DE9] = 0XD6, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X0D,
+[0X3DF1] = 0XD6, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X0E,
+[0X3DF9] = 0XD6, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X0F,
+[0X3E01] = 0XD6, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X10,
+[0X3E09] = 0XD6, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X11,
+[0X3E11] = 0XD6, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X12,
+[0X3E19] = 0XD6, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X13,
+[0X3E21] = 0XD6, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X14,
+[0X3E29] = 0XD6, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X15,
+[0X3E31] = 0XD6, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X16,
+[0X3E39] = 0XD6, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X17,
+[0X3E41] = 0XD6, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X18,
+[0X3E49] = 0XD6, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X19,
+[0X3E51] = 0XD6, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X1A,
+[0X3E59] = 0XD6, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X1B,
+[0X3E61] = 0XD6, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X1C,
+[0X3E69] = 0XD6, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X1D,
+[0X3E71] = 0XD6, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X1E,
+[0X3E79] = 0XD6, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X1F,
+[0X3E81] = 0XD6, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X20,
+[0X3E89] = 0XD6, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X21,
+[0X3E91] = 0XD6, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X22,
+[0X3E99] = 0XD6, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X23,
+[0X3EA1] = 0XD6, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X24,
+[0X3EA9] = 0XD6, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X25,
+[0X3EB1] = 0XD6, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X26,
+[0X3EB9] = 0XD6, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X27,
+[0X3EC1] = 0XD6, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X28,
+[0X3EC9] = 0XD6, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X29,
+[0X3ED1] = 0XD6, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X2A,
+[0X3ED9] = 0XD6, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X2B,
+[0X3EE1] = 0XD6, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X2C,
+[0X3EE9] = 0XD6, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X2D,
+[0X3EF1] = 0XD6, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X2E,
+[0X3EF9] = 0XD6, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X2F,
+[0X3F01] = 0XD6, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X30,
+[0X3F09] = 0XD6, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X31,
+[0X3F11] = 0XD6, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X32,
+[0X3F19] = 0XD6, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X33,
+[0X3F21] = 0XD6, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X34,
+[0X3F29] = 0XD6, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X35,
+[0X3F31] = 0XD6, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X36,
+[0X3F39] = 0XD6, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X37,
+[0X3F41] = 0XD6, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X38,
+[0X3F49] = 0XD6, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X39,
+[0X3F51] = 0XD6, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X3A,
+[0X3F59] = 0XD6, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X3B,
+[0X3F61] = 0XD6, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X3C,
+[0X3F69] = 0XD6, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X3D,
+[0X3F71] = 0XD6, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X3E,
+[0X3F79] = 0XD6, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X3F,
+[0X3F81] = 0XD6, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X40,
+[0X3F89] = 0XD6, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X41,
+[0X3F91] = 0XD6, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X42,
+[0X3F99] = 0XD6, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X43,
+[0X3FA1] = 0XD6, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X44,
+[0X3FA9] = 0XD6, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X45,
+[0X3FB1] = 0XD6, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X46,
+[0X3FB9] = 0XD6, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X47,
+[0X3FC1] = 0XD6, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X48,
+[0X3FC9] = 0XD6, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X49,
+[0X3FD1] = 0XD6, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X4A,
+[0X3FD9] = 0XD6, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X4B,
+[0X3FE1] = 0XD6, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X4C,
+[0X3FE9] = 0XD6, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X4D,
+[0X3FF1] = 0XD6, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X4E,
+[0X3FF9] = 0XD6, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X4F,
+[0X4001] = 0XD6, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X50,
+[0X4009] = 0XD6, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X51,
+[0X4011] = 0XD6, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X52,
+[0X4019] = 0XD6, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X53,
+[0X4021] = 0XD6, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X54,
+[0X4029] = 0XD6, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X55,
+[0X4031] = 0XD6, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X56,
+[0X4039] = 0XD6, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X57,
+[0X4041] = 0XD6, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X58,
+[0X4049] = 0XD6, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X59,
+[0X4051] = 0XD6, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X5A,
+[0X4059] = 0XD6, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X5B,
+[0X4061] = 0XD6, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X5C,
+[0X4069] = 0XD6, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X5D,
+[0X4071] = 0XD6, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X5E,
+[0X4079] = 0XD6, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X5F,
+[0X4081] = 0XD6, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X60,
+[0X4089] = 0XD6, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X61,
+[0X4091] = 0XD6, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X62,
+[0X4099] = 0XD6, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X63,
+[0X40A1] = 0XD6, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X64,
+[0X40A9] = 0XD6, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X65,
+[0X40B1] = 0XD6, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X66,
+[0X40B9] = 0XD6, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X67,
+[0X40C1] = 0XD6, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X68,
+[0X40C9] = 0XD6, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X69,
+[0X40D1] = 0XD6, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X6A,
+[0X40D9] = 0XD6, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X6B,
+[0X40E1] = 0XD6, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0X6C,
+[0X40E9] = 0XD6, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0X6D,
+[0X40F1] = 0XD6, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0X6E,
+[0X40F9] = 0XD6, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0X6F,
+[0X4101] = 0XD6, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0X70,
+[0X4109] = 0XD6, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X71,
+[0X4111] = 0XD6, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X72,
+[0X4119] = 0XD6, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X73,
+[0X4121] = 0XD6, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X74,
+[0X4129] = 0XD6, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X75,
+[0X4131] = 0XD6, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X76,
+[0X4139] = 0XD6, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X77,
+[0X4141] = 0XD6, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X78,
+[0X4149] = 0XD6, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X79,
+[0X4151] = 0XD6, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X7A,
+[0X4159] = 0XD6, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X7B,
+[0X4161] = 0XD6, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X7C,
+[0X4169] = 0XD6, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X7D,
+[0X4171] = 0XD6, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X7E,
+[0X4179] = 0XD6, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X7F,
+[0X4181] = 0XD6, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X80,
+[0X4189] = 0XD6, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X81,
+[0X4191] = 0XD6, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X82,
+[0X4199] = 0XD6, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X83,
+[0X41A1] = 0XD6, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X84,
+[0X41A9] = 0XD6, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X85,
+[0X41B1] = 0XD6, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X86,
+[0X41B9] = 0XD6, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X87,
+[0X41C1] = 0XD6, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X88,
+[0X41C9] = 0XD6, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X89,
+[0X41D1] = 0XD6, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X8A,
+[0X41D9] = 0XD6, 0X01, 0X00, 0X61, 0X00, 0X00, 0X00, 0X8B,
+[0X41E1] = 0XD6, 0X01, 0X00, 0X62, 0X00, 0X00, 0X00, 0X8C,
+[0X41E9] = 0XD6, 0X01, 0X00, 0X63, 0X00, 0X00, 0X00, 0X8D,
+[0X41F1] = 0XD6, 0X01, 0X00, 0X64, 0X00, 0X00, 0X00, 0X8E,
+[0X41F9] = 0XD6, 0X01, 0X00, 0X65, 0X00, 0X00, 0X00, 0X8F,
+[0X4201] = 0XD6, 0X01, 0X00, 0X66, 0X00, 0X00, 0X00, 0X90,
+[0X4209] = 0XD6, 0X01, 0X00, 0X67, 0X00, 0X00, 0X00, 0X91,
+[0X4211] = 0XD6, 0X01, 0X00, 0X68, 0X00, 0X00, 0X00, 0X92,
+[0X4219] = 0XD6, 0X01, 0X00, 0X69, 0X00, 0X00, 0X00, 0X93,
+[0X4221] = 0XD6, 0X01, 0X00, 0X6A, 0X00, 0X00, 0X00, 0X94,
+[0X4229] = 0XD6, 0X01, 0X00, 0X6B, 0X00, 0X00, 0X00, 0X95,
+[0X4231] = 0XD6, 0X01, 0X00, 0X6C, 0X00, 0X00, 0X00, 0X96,
+[0X4239] = 0XD6, 0X01, 0X00, 0X6D, 0X00, 0X00, 0X00, 0X97,
+[0X4241] = 0XD6, 0X01, 0X00, 0X6E, 0X00, 0X00, 0X00, 0X98,
+[0X4249] = 0XD6, 0X01, 0X00, 0X6F, 0X00, 0X00, 0X00, 0X99,
+[0X4251] = 0XD6, 0X01, 0X00, 0X70, 0X00, 0X00, 0X00, 0X9A,
+[0X4259] = 0XD6, 0X01, 0X00, 0X71, 0X00, 0X00, 0X00, 0X9B,
+[0X4261] = 0XD6, 0X01, 0X00, 0X72, 0X00, 0X00, 0X00, 0X9C,
+[0X4269] = 0XD6, 0X01, 0X00, 0X73, 0X00, 0X00, 0X00, 0X9D,
+[0X4271] = 0XD6, 0X01, 0X00, 0X74, 0X00, 0X00, 0X00, 0X9E,
+[0X4279] = 0XD6, 0X01, 0X00, 0X75, 0X00, 0X00, 0X00, 0X9F,
+[0X4281] = 0XD6, 0X01, 0X00, 0X76, 0X00, 0X00, 0X00, 0XA0,
+[0X4289] = 0XD6, 0X01, 0X00, 0X77, 0X00, 0X00, 0X00, 0XA1,
+[0X4291] = 0XD6, 0X01, 0X00, 0X78, 0X00, 0X00, 0X00, 0XA2,
+[0X4299] = 0XD6, 0X01, 0X00, 0X79, 0X00, 0X00, 0X00, 0XA3,
+[0X42A1] = 0XD6, 0X01, 0X00, 0X7A, 0X00, 0X00, 0X00, 0XCE,
+[0X42A9] = 0XD7, 0X01, 0X00, 0X30, 0X00, 0X00, 0X00, 0XCF,
+[0X42B1] = 0XD7, 0X01, 0X00, 0X31, 0X00, 0X00, 0X00, 0XD0,
+[0X42B9] = 0XD7, 0X01, 0X00, 0X32, 0X00, 0X00, 0X00, 0XD1,
+[0X42C1] = 0XD7, 0X01, 0X00, 0X33, 0X00, 0X00, 0X00, 0XD2,
+[0X42C9] = 0XD7, 0X01, 0X00, 0X34, 0X00, 0X00, 0X00, 0XD3,
+[0X42D1] = 0XD7, 0X01, 0X00, 0X35, 0X00, 0X00, 0X00, 0XD4,
+[0X42D9] = 0XD7, 0X01, 0X00, 0X36, 0X00, 0X00, 0X00, 0XD5,
+[0X42E1] = 0XD7, 0X01, 0X00, 0X37, 0X00, 0X00, 0X00, 0XD6,
+[0X42E9] = 0XD7, 0X01, 0X00, 0X38, 0X00, 0X00, 0X00, 0XD7,
+[0X42F1] = 0XD7, 0X01, 0X00, 0X39, 0X00, 0X00, 0X00, 0XD8,
+[0X42F9] = 0XD7, 0X01, 0X00, 0X30, 0X00, 0X00, 0X00, 0XD9,
+[0X4301] = 0XD7, 0X01, 0X00, 0X31, 0X00, 0X00, 0X00, 0XDA,
+[0X4309] = 0XD7, 0X01, 0X00, 0X32, 0X00, 0X00, 0X00, 0XDB,
+[0X4311] = 0XD7, 0X01, 0X00, 0X33, 0X00, 0X00, 0X00, 0XDC,
+[0X4319] = 0XD7, 0X01, 0X00, 0X34, 0X00, 0X00, 0X00, 0XDD,
+[0X4321] = 0XD7, 0X01, 0X00, 0X35, 0X00, 0X00, 0X00, 0XDE,
+[0X4329] = 0XD7, 0X01, 0X00, 0X36, 0X00, 0X00, 0X00, 0XDF,
+[0X4331] = 0XD7, 0X01, 0X00, 0X37, 0X00, 0X00, 0X00, 0XE0,
+[0X4339] = 0XD7, 0X01, 0X00, 0X38, 0X00, 0X00, 0X00, 0XE1,
+[0X4341] = 0XD7, 0X01, 0X00, 0X39, 0X00, 0X00, 0X00, 0XE2,
+[0X4349] = 0XD7, 0X01, 0X00, 0X30, 0X00, 0X00, 0X00, 0XE3,
+[0X4351] = 0XD7, 0X01, 0X00, 0X31, 0X00, 0X00, 0X00, 0XE4,
+[0X4359] = 0XD7, 0X01, 0X00, 0X32, 0X00, 0X00, 0X00, 0XE5,
+[0X4361] = 0XD7, 0X01, 0X00, 0X33, 0X00, 0X00, 0X00, 0XE6,
+[0X4369] = 0XD7, 0X01, 0X00, 0X34, 0X00, 0X00, 0X00, 0XE7,
+[0X4371] = 0XD7, 0X01, 0X00, 0X35, 0X00, 0X00, 0X00, 0XE8,
+[0X4379] = 0XD7, 0X01, 0X00, 0X36, 0X00, 0X00, 0X00, 0XE9,
+[0X4381] = 0XD7, 0X01, 0X00, 0X37, 0X00, 0X00, 0X00, 0XEA,
+[0X4389] = 0XD7, 0X01, 0X00, 0X38, 0X00, 0X00, 0X00, 0XEB,
+[0X4391] = 0XD7, 0X01, 0X00, 0X39, 0X00, 0X00, 0X00, 0XEC,
+[0X4399] = 0XD7, 0X01, 0X00, 0X30, 0X00, 0X00, 0X00, 0XED,
+[0X43A1] = 0XD7, 0X01, 0X00, 0X31, 0X00, 0X00, 0X00, 0XEE,
+[0X43A9] = 0XD7, 0X01, 0X00, 0X32, 0X00, 0X00, 0X00, 0XEF,
+[0X43B1] = 0XD7, 0X01, 0X00, 0X33, 0X00, 0X00, 0X00, 0XF0,
+[0X43B9] = 0XD7, 0X01, 0X00, 0X34, 0X00, 0X00, 0X00, 0XF1,
+[0X43C1] = 0XD7, 0X01, 0X00, 0X35, 0X00, 0X00, 0X00, 0XF2,
+[0X43C9] = 0XD7, 0X01, 0X00, 0X36, 0X00, 0X00, 0X00, 0XF3,
+[0X43D1] = 0XD7, 0X01, 0X00, 0X37, 0X00, 0X00, 0X00, 0XF4,
+[0X43D9] = 0XD7, 0X01, 0X00, 0X38, 0X00, 0X00, 0X00, 0XF5,
+[0X43E1] = 0XD7, 0X01, 0X00, 0X39, 0X00, 0X00, 0X00, 0XF6,
+[0X43E9] = 0XD7, 0X01, 0X00, 0X30, 0X00, 0X00, 0X00, 0XF7,
+[0X43F1] = 0XD7, 0X01, 0X00, 0X31, 0X00, 0X00, 0X00, 0XF8,
+[0X43F9] = 0XD7, 0X01, 0X00, 0X32, 0X00, 0X00, 0X00, 0XF9,
+[0X4401] = 0XD7, 0X01, 0X00, 0X33, 0X00, 0X00, 0X00, 0XFA,
+[0X4409] = 0XD7, 0X01, 0X00, 0X34, 0X00, 0X00, 0X00, 0XFB,
+[0X4411] = 0XD7, 0X01, 0X00, 0X35, 0X00, 0X00, 0X00, 0XFC,
+[0X4419] = 0XD7, 0X01, 0X00, 0X36, 0X00, 0X00, 0X00, 0XFD,
+[0X4421] = 0XD7, 0X01, 0X00, 0X37, 0X00, 0X00, 0X00, 0XFE,
+[0X4429] = 0XD7, 0X01, 0X00, 0X38, 0X00, 0X00, 0X00, 0XFF,
+[0X4431] = 0XD7, 0X01, 0X00, 0X39, 0X00, 0X00, 0X00, 0X10,
+[0X4439] = 0XF1, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X11,
+[0X4441] = 0XF1, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X12,
+[0X4449] = 0XF1, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X13,
+[0X4451] = 0XF1, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X14,
+[0X4459] = 0XF1, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X15,
+[0X4461] = 0XF1, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X16,
+[0X4469] = 0XF1, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X17,
+[0X4471] = 0XF1, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X18,
+[0X4479] = 0XF1, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X19,
+[0X4481] = 0XF1, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X1A,
+[0X4489] = 0XF1, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X1B,
+[0X4491] = 0XF1, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X1C,
+[0X4499] = 0XF1, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X1D,
+[0X44A1] = 0XF1, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X1E,
+[0X44A9] = 0XF1, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X1F,
+[0X44B1] = 0XF1, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X20,
+[0X44B9] = 0XF1, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X21,
+[0X44C1] = 0XF1, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X22,
+[0X44C9] = 0XF1, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X23,
+[0X44D1] = 0XF1, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X24,
+[0X44D9] = 0XF1, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X25,
+[0X44E1] = 0XF1, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X26,
+[0X44E9] = 0XF1, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X27,
+[0X44F1] = 0XF1, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X28,
+[0X44F9] = 0XF1, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X29,
+[0X4501] = 0XF1, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X30,
+[0X4509] = 0XF1, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X31,
+[0X4511] = 0XF1, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X32,
+[0X4519] = 0XF1, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X33,
+[0X4521] = 0XF1, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X34,
+[0X4529] = 0XF1, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X35,
+[0X4531] = 0XF1, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X36,
+[0X4539] = 0XF1, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X37,
+[0X4541] = 0XF1, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X38,
+[0X4549] = 0XF1, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X39,
+[0X4551] = 0XF1, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X3A,
+[0X4559] = 0XF1, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X3B,
+[0X4561] = 0XF1, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X3C,
+[0X4569] = 0XF1, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X3D,
+[0X4571] = 0XF1, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X3E,
+[0X4579] = 0XF1, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X3F,
+[0X4581] = 0XF1, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X40,
+[0X4589] = 0XF1, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X41,
+[0X4591] = 0XF1, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X42,
+[0X4599] = 0XF1, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X43,
+[0X45A1] = 0XF1, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X44,
+[0X45A9] = 0XF1, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X45,
+[0X45B1] = 0XF1, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X46,
+[0X45B9] = 0XF1, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X47,
+[0X45C1] = 0XF1, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X48,
+[0X45C9] = 0XF1, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X49,
+[0X45D1] = 0XF1, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X50,
+[0X45D9] = 0XF1, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X51,
+[0X45E1] = 0XF1, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X52,
+[0X45E9] = 0XF1, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X53,
+[0X45F1] = 0XF1, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X54,
+[0X45F9] = 0XF1, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X55,
+[0X4601] = 0XF1, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X56,
+[0X4609] = 0XF1, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X57,
+[0X4611] = 0XF1, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X58,
+[0X4619] = 0XF1, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X59,
+[0X4621] = 0XF1, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X5A,
+[0X4629] = 0XF1, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X5B,
+[0X4631] = 0XF1, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X5C,
+[0X4639] = 0XF1, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X5D,
+[0X4641] = 0XF1, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X5E,
+[0X4649] = 0XF1, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X5F,
+[0X4651] = 0XF1, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X60,
+[0X4659] = 0XF1, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X61,
+[0X4661] = 0XF1, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X62,
+[0X4669] = 0XF1, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X63,
+[0X4671] = 0XF1, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X64,
+[0X4679] = 0XF1, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X65,
+[0X4681] = 0XF1, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X66,
+[0X4689] = 0XF1, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X67,
+[0X4691] = 0XF1, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X68,
+[0X4699] = 0XF1, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X69,
+[0X46A1] = 0XF1, 0X01, 0X00, 0X5A, 0X00, 0X00, 0X00, 0X70,
+[0X46A9] = 0XF1, 0X01, 0X00, 0X41, 0X00, 0X00, 0X00, 0X71,
+[0X46B1] = 0XF1, 0X01, 0X00, 0X42, 0X00, 0X00, 0X00, 0X72,
+[0X46B9] = 0XF1, 0X01, 0X00, 0X43, 0X00, 0X00, 0X00, 0X73,
+[0X46C1] = 0XF1, 0X01, 0X00, 0X44, 0X00, 0X00, 0X00, 0X74,
+[0X46C9] = 0XF1, 0X01, 0X00, 0X45, 0X00, 0X00, 0X00, 0X75,
+[0X46D1] = 0XF1, 0X01, 0X00, 0X46, 0X00, 0X00, 0X00, 0X76,
+[0X46D9] = 0XF1, 0X01, 0X00, 0X47, 0X00, 0X00, 0X00, 0X77,
+[0X46E1] = 0XF1, 0X01, 0X00, 0X48, 0X00, 0X00, 0X00, 0X78,
+[0X46E9] = 0XF1, 0X01, 0X00, 0X49, 0X00, 0X00, 0X00, 0X79,
+[0X46F1] = 0XF1, 0X01, 0X00, 0X4A, 0X00, 0X00, 0X00, 0X7A,
+[0X46F9] = 0XF1, 0X01, 0X00, 0X4B, 0X00, 0X00, 0X00, 0X7B,
+[0X4701] = 0XF1, 0X01, 0X00, 0X4C, 0X00, 0X00, 0X00, 0X7C,
+[0X4709] = 0XF1, 0X01, 0X00, 0X4D, 0X00, 0X00, 0X00, 0X7D,
+[0X4711] = 0XF1, 0X01, 0X00, 0X4E, 0X00, 0X00, 0X00, 0X7E,
+[0X4719] = 0XF1, 0X01, 0X00, 0X4F, 0X00, 0X00, 0X00, 0X7F,
+[0X4721] = 0XF1, 0X01, 0X00, 0X50, 0X00, 0X00, 0X00, 0X80,
+[0X4729] = 0XF1, 0X01, 0X00, 0X51, 0X00, 0X00, 0X00, 0X81,
+[0X4731] = 0XF1, 0X01, 0X00, 0X52, 0X00, 0X00, 0X00, 0X82,
+[0X4739] = 0XF1, 0X01, 0X00, 0X53, 0X00, 0X00, 0X00, 0X83,
+[0X4741] = 0XF1, 0X01, 0X00, 0X54, 0X00, 0X00, 0X00, 0X84,
+[0X4749] = 0XF1, 0X01, 0X00, 0X55, 0X00, 0X00, 0X00, 0X85,
+[0X4751] = 0XF1, 0X01, 0X00, 0X56, 0X00, 0X00, 0X00, 0X86,
+[0X4759] = 0XF1, 0X01, 0X00, 0X57, 0X00, 0X00, 0X00, 0X87,
+[0X4761] = 0XF1, 0X01, 0X00, 0X58, 0X00, 0X00, 0X00, 0X88,
+[0X4769] = 0XF1, 0X01, 0X00, 0X59, 0X00, 0X00, 0X00, 0X89,
+[0X4771] = 0XF1, 0X01, 0X00, 0X5A,
+[0X4777] = 0X00
diff --git a/Programs/ttb_compile.c b/Programs/ttb_compile.c
new file mode 100644
index 0000000..98ec29e
--- /dev/null
+++ b/Programs/ttb_compile.c
@@ -0,0 +1,400 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "file.h"
+#include "datafile.h"
+#include "dataarea.h"
+#include "charset.h"
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "ttb_compile.h"
+
+struct TextTableDataStruct {
+  DataArea *area;
+
+  struct {
+    TextTableAliasEntry *array;
+    size_t size;
+    size_t count;
+  } alias;
+};
+
+void *
+getTextTableItem (TextTableData *ttd, TextTableOffset offset) {
+  return getDataItem(ttd->area, offset);
+}
+
+TextTableHeader *
+getTextTableHeader (TextTableData *ttd) {
+  return getTextTableItem(ttd, 0);
+}
+
+static DataOffset
+getUnicodeGroupOffset (TextTableData *ttd, wchar_t character, int allocate) {
+  unsigned int groupNumber = UNICODE_GROUP_NUMBER(character);
+  DataOffset groupOffset = getTextTableHeader(ttd)->unicodeGroups[groupNumber];
+
+  if (!groupOffset && allocate) {
+    if (!allocateDataItem(ttd->area, &groupOffset,
+                          sizeof(UnicodeGroupEntry),
+                          __alignof__(UnicodeGroupEntry))) {
+      return 0;
+    }
+
+    getTextTableHeader(ttd)->unicodeGroups[groupNumber] = groupOffset;
+  }
+
+  return groupOffset;
+}
+
+static DataOffset
+getUnicodePlaneOffset (TextTableData *ttd, wchar_t character, int allocate) {
+  DataOffset groupOffset = getUnicodeGroupOffset(ttd, character, allocate);
+  if (!groupOffset) return 0;
+
+  {
+    UnicodeGroupEntry *group = getDataItem(ttd->area, groupOffset);
+    unsigned int planeNumber = UNICODE_PLANE_NUMBER(character);
+    DataOffset planeOffset = group->planes[planeNumber];
+
+    if (!planeOffset && allocate) {
+      if (!allocateDataItem(ttd->area, &planeOffset,
+                            sizeof(UnicodePlaneEntry),
+                            __alignof__(UnicodePlaneEntry))) {
+        return 0;
+      }
+
+      group = getDataItem(ttd->area, groupOffset);
+      group->planes[planeNumber] = planeOffset;
+    }
+
+    return planeOffset;
+  }
+}
+
+static DataOffset
+getUnicodeRowOffset (TextTableData *ttd, wchar_t character, int allocate) {
+  DataOffset planeOffset = getUnicodePlaneOffset(ttd, character, allocate);
+  if (!planeOffset) return 0;
+
+  {
+    UnicodePlaneEntry *plane = getDataItem(ttd->area, planeOffset);
+    unsigned int rowNumber = UNICODE_ROW_NUMBER(character);
+    DataOffset rowOffset = plane->rows[rowNumber];
+
+    if (!rowOffset && allocate) {
+      if (!allocateDataItem(ttd->area, &rowOffset,
+                            sizeof(UnicodeRowEntry),
+                            __alignof__(UnicodeRowEntry))) {
+        return 0;
+      }
+
+      plane = getDataItem(ttd->area, planeOffset);
+      plane->rows[rowNumber] = rowOffset;
+    }
+
+    return rowOffset;
+  }
+}
+
+UnicodeRowEntry *
+getUnicodeRowEntry (TextTableData *ttd, wchar_t character, int allocate) {
+  DataOffset rowOffset = getUnicodeRowOffset(ttd, character, allocate);
+  if (!rowOffset) return NULL;
+  return getDataItem(ttd->area, rowOffset);
+}
+
+const unsigned char *
+getUnicodeCell (TextTableData *ttd, wchar_t character) {
+  const UnicodeRowEntry *row = getUnicodeRowEntry(ttd, character, 0);
+
+  if (row) {
+    unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
+    if (BITMASK_TEST(row->cellDefined, cellNumber)) return &row->cells[cellNumber];
+  }
+
+  return NULL;
+}
+
+static void
+clearTextTableInput (TextTableData *ttd, unsigned char dots, wchar_t character) {
+  TextTableHeader *header = getTextTableHeader(ttd);
+
+  if (BITMASK_TEST(header->inputCharacterDefined, dots)) {
+    if (header->inputCharacters[dots] == character) {
+      header->inputCharacters[dots] = 0;
+      BITMASK_CLEAR(header->inputCharacterDefined, dots);
+    }
+  }
+}
+
+int
+setTextTableInput (TextTableData *ttd, wchar_t character, unsigned char dots) {
+  TextTableHeader *header = getTextTableHeader(ttd);
+
+  if (!BITMASK_TEST(header->inputCharacterDefined, dots)) {
+    header->inputCharacters[dots] = character;
+    BITMASK_SET(header->inputCharacterDefined, dots);
+  }
+
+  return 1;
+}
+
+int
+setTextTableGlyph (TextTableData *ttd, wchar_t character, unsigned char dots) {
+  UnicodeRowEntry *row = getUnicodeRowEntry(ttd, character, 1);
+
+  if (row) {
+    unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
+    unsigned char *cell = &row->cells[cellNumber];
+
+    if (!BITMASK_TEST(row->cellDefined, cellNumber)) {
+      BITMASK_SET(row->cellDefined, cellNumber);
+    } else if (*cell != dots) {
+      clearTextTableInput(ttd, *cell, character);
+    }
+
+    *cell = dots;
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+setTextTableCharacter (TextTableData *ttd, wchar_t character, unsigned char dots) {
+  if (!setTextTableGlyph(ttd, character, dots)) return 0;
+  if (!setTextTableInput(ttd, character, dots)) return 0;
+  return 1;
+}
+
+void
+unsetTextTableCharacter (TextTableData *ttd, wchar_t character) {
+  UnicodeRowEntry *row = getUnicodeRowEntry(ttd, character, 0);
+
+  if (row) {
+    unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
+
+    if (BITMASK_TEST(row->cellDefined, cellNumber)) {
+      unsigned char *cell = &row->cells[cellNumber];
+
+      clearTextTableInput(ttd, *cell, character);
+      *cell = 0;
+      BITMASK_CLEAR(row->cellDefined, cellNumber);
+    }
+  }
+}
+
+int
+setTextTableByte (TextTableData *ttd, unsigned char byte, unsigned char dots) {
+  wint_t character = convertCharToWchar(byte);
+
+  if (character != WEOF)
+    if (!setTextTableCharacter(ttd, character, dots))
+      return 0;
+
+  return 1;
+}
+
+int
+addTextTableAlias (TextTableData *ttd, wchar_t from, wchar_t to) {
+  if (ttd->alias.count == ttd->alias.size) {
+    size_t newSize = ttd->alias.size? (ttd->alias.size << 1): 0X10;
+    TextTableAliasEntry *newArray;
+
+    if (!(newArray = realloc(ttd->alias.array, ARRAY_SIZE(newArray, newSize)))) {
+      logMallocError();
+      return 0;
+    }
+
+    ttd->alias.array = newArray;
+    ttd->alias.size = newSize;
+  }
+
+  {
+    unsigned int cellNumber = UNICODE_CELL_NUMBER(from);
+    UnicodeRowEntry *row = getUnicodeRowEntry(ttd, from, 1);
+
+    if (!row) return 0;
+    BITMASK_SET(row->cellAliased, cellNumber);
+  }
+
+  {
+    TextTableAliasEntry *alias = &ttd->alias.array[ttd->alias.count++];
+
+    memset(alias, 0, sizeof(*alias));
+    alias->from = from;
+    alias->to = to;
+  }
+
+  return 1;
+}
+
+TextTableData *
+newTextTableData (void) {
+  TextTableData *ttd;
+
+  if ((ttd = malloc(sizeof(*ttd)))) {
+    memset(ttd, 0, sizeof(*ttd));
+
+    ttd->alias.array = NULL;
+    ttd->alias.size = 0;
+    ttd->alias.count = 0;
+
+    if ((ttd->area = newDataArea())) {
+      if (allocateDataItem(ttd->area, NULL, sizeof(TextTableHeader), __alignof__(TextTableHeader))) {
+        return ttd;
+      }
+
+      destroyDataArea(ttd->area);
+    }
+
+    free(ttd);
+  }
+
+  return NULL;
+}
+
+void
+destroyTextTableData (TextTableData *ttd) {
+  if (ttd->alias.array) free(ttd->alias.array);
+  destroyDataArea(ttd->area);
+  free(ttd);
+}
+
+static int
+sortTextTableAliasArray (const void *element1, const void *element2) {
+  const TextTableAliasEntry *alias1 = element1;
+  const TextTableAliasEntry *alias2 = element2;
+
+  wchar_t wc1 = alias1->from;
+  wchar_t wc2 = alias2->from;
+
+  if (wc1 < wc2) return -1;
+  if (wc1 > wc2) return 1;
+  return 0;
+}
+
+static int
+finishTextTableData (TextTableData *ttd) {
+  qsort(ttd->alias.array, ttd->alias.count, sizeof(*ttd->alias.array), sortTextTableAliasArray);
+
+  {
+    DataOffset offset;
+
+    if (!saveDataItem(ttd->area, &offset, ttd->alias.array,
+                      ARRAY_SIZE(ttd->alias.array, ttd->alias.count),
+                      __alignof__(*ttd->alias.array))) {
+      return 0;
+    }
+
+    {
+      TextTableHeader *header = getTextTableHeader(ttd);
+
+      header->aliasArray = offset;
+      header->aliasCount = ttd->alias.count;
+    }
+  }
+
+  return 1;
+}
+
+TextTableData *
+processTextTableLines (FILE *stream, const char *name, DataOperandsProcessor *processOperands) {
+  if (setTableDataVariables(TEXT_TABLE_EXTENSION, TEXT_SUBTABLE_EXTENSION)) {
+    TextTableData *ttd;
+
+    if ((ttd = newTextTableData())) {
+      const DataFileParameters parameters = {
+        .processOperands = processOperands,
+        .data = ttd
+      };
+
+      if (processDataStream(NULL, stream, name, &parameters)) {
+        if (finishTextTableData(ttd)) {
+          return ttd;
+        }
+      }
+
+      destroyTextTableData(ttd);
+    }
+  }
+
+  return NULL;
+}
+
+TextTable *
+makeTextTable (TextTableData *ttd) {
+  TextTable *table = malloc(sizeof(*table));
+
+  if (table) {
+    memset(table, 0, sizeof(*table));
+
+    table->header.fields = getTextTableHeader(ttd);
+    table->size = getDataSize(ttd->area);
+
+    table->options.tryBaseCharacter = 1;
+
+    {
+      const unsigned char **cell = &table->cells.replacementCharacter;
+      *cell = getUnicodeCell(ttd, UNICODE_REPLACEMENT_CHARACTER);
+      if (!*cell) *cell = getUnicodeCell(ttd, WC_C('?'));
+    }
+
+    resetDataArea(ttd->area);
+  }
+
+  return table;
+}
+
+void
+destroyTextTable (TextTable *table) {
+  if (table->size) {
+    free(table->header.fields);
+    free(table);
+  }
+}
+
+char *
+ensureTextTableExtension (const char *path) {
+  return ensureFileExtension(path, TEXT_TABLE_EXTENSION);
+}
+
+char *
+makeTextTablePath (const char *directory, const char *name) {
+  char *subdirectory = makePath(directory, TEXT_TABLES_SUBDIRECTORY);
+
+  if (subdirectory) {
+    char *file = makeFilePath(subdirectory, name, TEXT_TABLE_EXTENSION);
+
+    free(subdirectory);
+    if (file) return file;
+  }
+
+  return NULL;
+}
+
+char *
+getTextTableForLocale (const char *directory) {
+  return getFileForLocale(directory, makeTextTablePath);
+}
diff --git a/Programs/ttb_compile.h b/Programs/ttb_compile.h
new file mode 100644
index 0000000..213e203
--- /dev/null
+++ b/Programs/ttb_compile.h
@@ -0,0 +1,59 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TTB_COMPILE
+#define BRLTTY_INCLUDED_TTB_COMPILE
+
+#include <stdio.h>
+
+#include "datafile.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct TextTableDataStruct TextTableData;
+extern TextTableData *newTextTableData (void);
+extern void destroyTextTableData (TextTableData *ttd);
+
+extern TextTableData *processTextTableLines (FILE *stream, const char *name, DataOperandsProcessor *processOperands);
+extern TextTable *makeTextTable (TextTableData *ttd);
+
+typedef TextTableData *TextTableProcessor (FILE *stream, const char *name);
+extern TextTableProcessor processTextTableStream;
+extern TextTableProcessor processGnomeBrailleStream;
+extern TextTableProcessor processLibLouisStream;
+
+extern void *getTextTableItem (TextTableData *ttd, TextTableOffset offset);
+extern TextTableHeader *getTextTableHeader (TextTableData *ttd);
+extern const unsigned char *getUnicodeCell (TextTableData *ttd, wchar_t character);
+
+extern int setTextTableInput (TextTableData *ttd, wchar_t character, unsigned char dots);
+extern int setTextTableGlyph (TextTableData *ttd, wchar_t character, unsigned char dots);
+extern int setTextTableCharacter (TextTableData *ttd, wchar_t character, unsigned char dots);
+extern void unsetTextTableCharacter (TextTableData *ttd, wchar_t character);
+extern int setTextTableByte (TextTableData *ttd, unsigned char byte, unsigned char dots);
+extern int addTextTableAlias (TextTableData *ttd, wchar_t from, wchar_t to);
+
+extern UnicodeRowEntry *getUnicodeRowEntry (TextTableData *ttd, wchar_t character, int allocate);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TTB_COMPILE */
diff --git a/Programs/ttb_gnome.c b/Programs/ttb_gnome.c
new file mode 100644
index 0000000..da04dbd
--- /dev/null
+++ b/Programs/ttb_gnome.c
@@ -0,0 +1,203 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "ttb_compile.h"
+
+static int inUcsBlock;
+
+static int
+getUnicodeCharacter (DataFile *file, wchar_t *character, const char *description) {
+  DataOperand string;
+
+  if (getDataOperand(file, &string, description)) {
+    if (string.length > 2) {
+      if ((string.characters[0] == WC_C('U')) &&
+          (string.characters[1] == WC_C('+'))) {
+        const wchar_t *digit = &string.characters[2];
+        int length = string.length - 2;
+
+        *character = 0;
+        while (length) {
+          int value;
+          int shift;
+          if (!isHexadecimalDigit(*digit++, &value, &shift)) break;
+
+          *character <<= shift;
+          *character |= value;
+          length -= 1;
+        }
+        if (!length) return 1;
+      }
+    }
+
+    reportDataError(file, "invalid Unicode character: %.*" PRIws,
+                    string.length, string.characters);
+  }
+
+  return 0;
+}
+
+static int
+testBrailleRepresentation (DataFile *file, wchar_t representation, unsigned char *dots) {
+  if ((representation & ~UNICODE_CELL_MASK) == UNICODE_BRAILLE_ROW) {
+    *dots = representation & UNICODE_CELL_MASK;
+    return 1;
+  } else {
+    reportDataError(file, "invalid braille representation");
+  }
+
+  return 0;
+}
+
+static DATA_OPERANDS_PROCESSOR(processEncodingOperands) {
+  DataOperand encoding;
+
+  if (getDataOperand(file, &encoding, "character encoding name")) {
+    if (!isKeyword(WS_C("UTF-8"), encoding.characters, encoding.length)) {
+      reportDataError(file, "unsupported character encoding: %.*" PRIws,
+                      encoding.length, encoding.characters);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processDelegateOperands) {
+  DataOperand type;
+
+  if (getDataOperand(file, &type, "delegate type")) {
+    if (isKeyword(WS_C("FILE"), type.characters, type.length)) {
+      DataOperand name;
+
+      if (getDataOperand(file, &name, "file name")) {
+        return includeDataFile(file, name.characters, name.length);
+      }
+    } else {
+      return includeDataFile(file, type.characters, type.length);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processUcsBlockOperands) {
+  DataOperand action;
+
+  if (getDataOperand(file, &action, "UCS block action")) {
+    const wchar_t *expected = inUcsBlock? WS_C("END"): WS_C("START");
+
+    if (isKeyword(expected, action.characters, action.length)) {
+      inUcsBlock = !inUcsBlock;
+    } else {
+      reportDataError(file, "unexpected UCS block action: %.*" PRIws " (expecting %" PRIws ")",
+                      action.length, action.characters, expected);
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processUcsCharOperands) {
+  TextTableData *ttd = data;
+  DataOperand string;
+
+  if (getDataOperand(file, &string, "character string")) {
+    if (string.length == 1) {
+      DataOperand representation;
+
+      if (getDataOperand(file, &representation, "braille representation")) {
+        if (representation.length == 1) {
+          unsigned char dots;
+
+          if (testBrailleRepresentation(file, representation.characters[0], &dots)) {
+            if (!setTextTableCharacter(ttd, string.characters[0], dots)) return 0;
+          }
+        } else {
+          reportDataError(file, "multi-cell braille representation not supported");
+        }
+      }
+    } else {
+      reportDataError(file, "multi-character string not supported");
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processUnicodeCharOperands) {
+  TextTableData *ttd = data;
+  wchar_t character;
+
+  if (getUnicodeCharacter(file, &character, "character")) {
+    wchar_t representation;
+
+    if (getUnicodeCharacter(file, &representation, "braille representation")) {
+      unsigned char dots;
+
+      if (testBrailleRepresentation(file, representation, &dots)) {
+        if (!setTextTableCharacter(ttd, character, dots)) return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processGnomeBrailleOperands) {
+  if (inUcsBlock) {
+    BEGIN_DATA_DIRECTIVE_TABLE
+      {.name=WS_C("UCS-BLOCK"), .processor=processUcsBlockOperands},
+      {.name=NULL, .processor=processUcsCharOperands},
+    END_DATA_DIRECTIVE_TABLE
+
+    return processDirectiveOperand(file, &directives, "gnome braille UCS block directive", data);
+  } else {
+    BEGIN_DATA_DIRECTIVE_TABLE
+      {.name=WS_C("ENCODING"), .processor=processEncodingOperands},
+  //  {.name=WS_C("NAME"), .processor=processNameOperands},
+  //  {.name=WS_C("LOCALES"), .processor=processLocalesOperands},
+  //  {.name=WS_C("UCS-SUFFIX"), .processor=processUcsSuffixOperands},
+      {.name=WS_C("DELEGATE"), .processor=processDelegateOperands},
+  //  {.name=WS_C("UTF8-STRING"), .processor=processUtf8StringOperands},
+      {.name=WS_C("UCS-BLOCK"), .processor=processUcsBlockOperands},
+      {.name=WS_C("UCS-CHAR"), .processor=processUcsCharOperands},
+      {.name=WS_C("UNICODE-CHAR"), .processor=processUnicodeCharOperands},
+  //  {.name=WS_C("UNKNOWN-CHAR"), .processor=processUnknownCharOperands},
+    END_DATA_DIRECTIVE_TABLE
+
+    return processDirectiveOperand(file, &directives, "gnome braille main directive", data);
+  }
+}
+
+TextTableData *
+processGnomeBrailleStream (FILE *stream, const char *name) {
+  TextTableData *ttd;
+
+  inUcsBlock = 0;
+  if ((ttd = processTextTableLines(stream, name, processGnomeBrailleOperands))) {
+    if (inUcsBlock) {
+      reportDataError(NULL, "unterminated UCS block");
+    }
+  };
+
+  return ttd;
+}
diff --git a/Programs/ttb_internal.h b/Programs/ttb_internal.h
new file mode 100644
index 0000000..0c9fc99
--- /dev/null
+++ b/Programs/ttb_internal.h
@@ -0,0 +1,88 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_TTB_INTERNAL
+#define BRLTTY_INCLUDED_TTB_INTERNAL
+
+#include "bitmask.h"
+#include "unicode.h"
+#include "dataarea.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef uint32_t TextTableOffset;
+
+#define CHARSET_BYTE_BITS 8
+#define CHARSET_BYTE_COUNT (1 << CHARSET_BYTE_BITS)
+#define CHARSET_BYTE_MAXIMUM (CHARSET_BYTE_COUNT - 1)
+
+typedef struct {
+  unsigned char cells[UNICODE_CELLS_PER_ROW];
+  BITMASK(cellDefined, UNICODE_CELLS_PER_ROW, char);
+  BITMASK(cellAliased, UNICODE_CELLS_PER_ROW, char);
+} UnicodeRowEntry;
+
+typedef struct {
+  TextTableOffset rows[UNICODE_ROWS_PER_PLANE];
+} UnicodePlaneEntry;
+
+typedef struct {
+  TextTableOffset planes[UNICODE_PLANES_PER_GROUP];
+} UnicodeGroupEntry;
+
+typedef struct {
+  wchar_t from;
+  wchar_t to;
+} TextTableAliasEntry;
+
+typedef struct {
+  TextTableOffset unicodeGroups[UNICODE_GROUP_COUNT];
+  wchar_t inputCharacters[0X100];
+  BITMASK(inputCharacterDefined, 0X100, char);
+  DataOffset aliasArray;
+  uint32_t aliasCount;
+} TextTableHeader;
+
+struct TextTableStruct {
+  union {
+    TextTableHeader *fields;
+    const unsigned char *bytes;
+  } header;
+
+  size_t size;
+
+  struct {
+    unsigned char tryBaseCharacter;
+  } options;
+
+  struct {
+    const unsigned char *replacementCharacter;
+  } cells;
+};
+
+extern const TextTableAliasEntry *locateTextTableAlias (
+  wchar_t character, const TextTableAliasEntry *array, size_t count
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_TTB_INTERNAL */
diff --git a/Programs/ttb_louis.c b/Programs/ttb_louis.c
new file mode 100644
index 0000000..788872c
--- /dev/null
+++ b/Programs/ttb_louis.c
@@ -0,0 +1,158 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "ttb_compile.h"
+
+static int
+getUnicodeCharacters (DataFile *file, wchar_t *character, int num, const char *description) {
+  DataOperand string;
+  const wchar_t *c;
+  int i;
+
+  if (getDataOperand(file, &string, description)) {
+    for (c = string.characters, i = 0; i < num; i++) {
+      if (*c == '\\') {
+	c++;
+	switch (*c) {
+	  case WC_C('\\'): character[i] = WC_C('\\'); continue;
+	  case WC_C('f'): case WC_C('F'): character[i] = WC_C('\f'); continue;
+	  case WC_C('n'): case WC_C('N'): character[i] = WC_C('\n'); continue;
+	  case WC_C('r'): case WC_C('R'): character[i] = WC_C('\r'); continue;
+	  case WC_C('s'): case WC_C('S'): character[i] = WC_C(' ' ); continue;
+	  case WC_C('t'): case WC_C('T'): character[i] = WC_C('\t'); continue;
+	  case WC_C('v'): case WC_C('V'): character[i] = WC_C('\v'); continue;
+	  case WC_C('x'): case WC_C('X'): {
+	    const wchar_t *digit = ++c;
+	    int length = string.length - (digit - string.characters);
+
+	    character[i] = 0;
+	    while (length) {
+	      int value;
+	      int shift;
+	      if (!isHexadecimalDigit(*digit, &value, &shift)) break;
+	      digit++;
+
+	      character[i] <<= shift;
+	      character[i] |= value;
+	      length -= 1;
+	    }
+            if (digit == c) goto invalid;
+	    c = digit;
+	    continue;
+	  }
+	  default:
+          invalid:
+	    reportDataError(file, "unknown escape sequence: %.*" PRIws,
+                            (int)(c-string.characters+1), string.characters);
+	}
+      } else {
+	character[i] = *c++;
+      }
+    }
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+getDots (DataFile *file, unsigned char *dots, const char *description) {
+  DataOperand string;
+  int i;
+
+  *dots = 0;
+
+  if (getDataOperand(file, &string, description)) {
+    for (i = 0; i < string.length; i++) {
+      if (string.characters[i] >= WC_C('1') && string.characters[i] <= WC_C('8')) {
+	*dots |= 1 << (string.characters[i] - WC_C('1'));
+      } else if (string.characters[i] == WC_C('-')) {
+	reportDataError(file, "no support for multi-cell %.*" PRIws,
+			string.length, string.characters);
+	return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processChar) {
+  TextTableData *ttd = data;
+  wchar_t character;
+
+  if (getUnicodeCharacters(file, &character, 1, "character")) {
+    unsigned char dots;
+
+    if (getDots(file, &dots, "braille representation")) {
+      if (!setTextTableCharacter(ttd, character, dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processUplow) {
+  TextTableData *ttd = data;
+  wchar_t characters[2];
+
+  if (getUnicodeCharacters(file, characters, 2, "characters")) {
+    unsigned char dots;
+
+    if (getDots(file, &dots, "braille representation")) {
+      if (!setTextTableCharacter(ttd, characters[0], dots)) return 0;
+      if (!setTextTableCharacter(ttd, characters[1], dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processInclude) {
+  reportDataError(file, "no support for include");
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processLibLouisOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    {.name=WS_C("space"), .processor=processChar},
+    {.name=WS_C("punctuation"), .processor=processChar},
+    {.name=WS_C("digit"), .processor=processChar},
+    {.name=WS_C("uplow"), .processor=processUplow},
+    {.name=WS_C("letter"), .processor=processChar},
+    {.name=WS_C("lowercase"), .processor=processChar},
+    {.name=WS_C("uppercase"), .processor=processChar},
+    {.name=WS_C("litdigit"), .processor=processChar},
+    {.name=WS_C("sign"), .processor=processChar},
+    {.name=WS_C("math"), .processor=processChar},
+    {.name=WS_C("decpoint"), .processor=processChar},
+    {.name=WS_C("hyphen"), .processor=processChar},
+    {.name=WS_C("include"), .processor=processInclude},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "lib louis directive", data);
+}
+
+TextTableData *
+processLibLouisStream (FILE *stream, const char *name) {
+  return processTextTableLines(stream, name, processLibLouisOperands);
+}
diff --git a/Programs/ttb_native.c b/Programs/ttb_native.c
new file mode 100644
index 0000000..2482628
--- /dev/null
+++ b/Programs/ttb_native.c
@@ -0,0 +1,324 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "file.h"
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "ttb_compile.h"
+
+static int
+getByteOperand (DataFile *file, unsigned char *byte) {
+  DataString string;
+  const char *description = "local character";
+
+  if (getDataString(file, &string, 1, description)) {
+    if ((string.length == 1) && iswLatin1(string.characters[0])) {
+      *byte = string.characters[0];
+      return 1;
+    } else {
+      reportDataError(file, "invalid %s: %.*" PRIws,
+                      description, string.length, string.characters);
+    }
+  }
+
+  return 0;
+}
+
+static const char characterDescription[] = "Unicode character";
+
+static int
+isCharacterOperand (
+  DataFile *file, wchar_t *character,
+  const wchar_t *characters, int length
+) {
+  if (length == 1) {
+    wchar_t wc = characters[0];
+
+    if (!(wc & ~UNICODE_CHARACTER_MASK)) {
+      *character = wc;
+      return 1;
+    } else {
+      reportDataError(file, "%s out of range: %.*" PRIws,
+                      characterDescription, length, characters);
+    }
+  } else {
+    reportDataError(file, "not a single %s: %.*" PRIws,
+                    characterDescription, length, characters);
+  }
+
+  return 0;
+}
+
+static int
+getCharacterOperand (DataFile *file, wchar_t *character) {
+  DataString string;
+
+  if (getDataString(file, &string, 0, characterDescription)) {
+    if (isCharacterOperand(file, character, string.characters, string.length)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+getDotsOperand (DataFile *file, unsigned char *dots) {
+  if (findDataOperand(file, "cell")) {
+    wchar_t character;
+
+    if (getDataCharacter(file, &character)) {
+      int noDots = 0;
+      wchar_t enclosed = (character == WC_C('('))? WC_C(')'):
+                         0;
+      *dots = 0;
+
+      if (!enclosed) {
+        if (wcschr(WS_C("0"), character)) {
+          noDots = 1;
+        } else {
+          ungetDataCharacters(file, 1);
+        }
+      }
+
+      while (getDataCharacter(file, &character)) {
+        int space = iswspace(character);
+
+        if (enclosed) {
+          if (character == enclosed) {
+            enclosed = 0;
+            break;
+          }
+
+          if (space) continue;
+        } else if (space) {
+          ungetDataCharacters(file, 1);
+          break;
+        }
+
+        {
+          int dot;
+
+          if (noDots || !brlDotNumberToIndex(character, &dot)) {
+            reportDataError(file, "invalid dot number: %.1" PRIws, &character);
+            return 0;
+          }
+
+          {
+            unsigned char bit = brlDotBits[dot];
+
+            if (*dots & bit) {
+              reportDataError(file, "duplicate dot number: %.1" PRIws, &character);
+              return 0;
+            }
+
+            *dots |= bit;
+          }
+        }
+      }
+
+      if (enclosed) {
+        reportDataError(file, "incomplete cell");
+        return 0;
+      }
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static DATA_OPERANDS_PROCESSOR(processAliasOperands) {
+  TextTableData *ttd = data;
+  wchar_t from;
+
+  if (getCharacterOperand(file, &from)) {
+    wchar_t to;
+
+    if (getCharacterOperand(file, &to)) {
+      if (!addTextTableAlias(ttd, from, to)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processByteOperands) {
+  TextTableData *ttd = data;
+  unsigned char byte;
+
+  if (getByteOperand(file, &byte)) {
+    unsigned char dots;
+
+    if (getDotsOperand(file, &dots)) {
+      if (!setTextTableByte(ttd, byte, dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processCharOperands) {
+  TextTableData *ttd = data;
+  wchar_t character;
+
+  if (getCharacterOperand(file, &character)) {
+    unsigned char dots;
+
+    if (getDotsOperand(file, &dots)) {
+      if (!setTextTableCharacter(ttd, character, dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processGlyphOperands) {
+  TextTableData *ttd = data;
+  wchar_t character;
+
+  if (getCharacterOperand(file, &character)) {
+    unsigned char dots;
+
+    if (getDotsOperand(file, &dots)) {
+      if (!setTextTableGlyph(ttd, character, dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_OPERANDS_PROCESSOR(processInputOperands) {
+  TextTableData *ttd = data;
+  wchar_t character;
+
+  if (getCharacterOperand(file, &character)) {
+    unsigned char dots;
+
+    if (getDotsOperand(file, &dots)) {
+      if (!setTextTableInput(ttd, character, dots)) return 0;
+    }
+  }
+
+  return 1;
+}
+
+static DATA_CONDITION_TESTER(testGlyphDefined) {
+  TextTableData *ttd = data;
+
+  wchar_t character;
+  if (!isCharacterOperand(file, &character, identifier->characters, identifier->length)) return 0;
+
+  UnicodeRowEntry *row = getUnicodeRowEntry(ttd, character, 0);
+  if (!row) return 0;
+
+  unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
+  return !!BITMASK_TEST(row->cellDefined, cellNumber);
+}
+
+static int
+processGlyphTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testGlyphDefined, not, characterDescription, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfGlyphOperands) {
+  return processGlyphTestOperands(file, 0, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfNotGlyphOperands) {
+  return processGlyphTestOperands(file, 1, data);
+}
+
+static const char inputDescription[] = "dot number(s)";
+
+static DATA_CONDITION_TESTER(testInputDefined) {
+  TextTableData *ttd = data;
+
+  ByteOperand cells;
+  if (!parseCellsOperand(file, &cells, identifier->characters, identifier->length)) return 0;
+
+  if (cells.length != 1) {
+    reportDataError(file, "not a single %s: %.*" PRIws,
+                    inputDescription, identifier->length, identifier->characters);
+
+    return 0;
+  }
+
+  const TextTableHeader *header = getTextTableHeader(ttd);
+  return !!BITMASK_TEST(header->inputCharacterDefined, cells.bytes[0]);
+}
+
+static int
+processInputTestOperands (DataFile *file, int not, void *data) {
+  return processConditionOperands(file, testInputDefined, not, inputDescription, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfInputOperands) {
+  return processInputTestOperands(file, 0, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processIfNotInputOperands) {
+  return processInputTestOperands(file, 1, data);
+}
+
+static DATA_OPERANDS_PROCESSOR(processNativeTextTableOperands) {
+  BEGIN_DATA_DIRECTIVE_TABLE
+    DATA_NESTING_DIRECTIVES,
+    DATA_VARIABLE_DIRECTIVES,
+    DATA_CONDITION_DIRECTIVES,
+    {.name=WS_C("alias"), .processor=processAliasOperands},
+    {.name=WS_C("byte"), .processor=processByteOperands},
+    {.name=WS_C("char"), .processor=processCharOperands},
+    {.name=WS_C("glyph"), .processor=processGlyphOperands},
+    {.name=WS_C("input"), .processor=processInputOperands},
+    {.name=WS_C("ifglyph"), .processor=processIfGlyphOperands, .unconditional=1},
+    {.name=WS_C("ifnotglyph"), .processor=processIfNotGlyphOperands, .unconditional=1},
+    {.name=WS_C("ifinput"), .processor=processIfInputOperands, .unconditional=1},
+    {.name=WS_C("ifnotinput"), .processor=processIfNotInputOperands, .unconditional=1},
+  END_DATA_DIRECTIVE_TABLE
+
+  return processDirectiveOperand(file, &directives, "text table directive", data);
+}
+
+TextTableData *
+processTextTableStream (FILE *stream, const char *name) {
+  return processTextTableLines(stream, name, processNativeTextTableOperands);
+}
+
+TextTable *
+compileTextTable (const char *name) {
+  TextTable *table = NULL;
+  FILE *stream;
+
+  if ((stream = openDataFile(name, "r", 0))) {
+    TextTableData *ttd;
+
+    if ((ttd = processTextTableStream(stream, name))) {
+      table = makeTextTable(ttd);
+
+      destroyTextTableData(ttd);
+    }
+
+    fclose(stream);
+  }
+
+  return table;
+}
diff --git a/Programs/ttb_translate.c b/Programs/ttb_translate.c
new file mode 100644
index 0000000..9c119ee
--- /dev/null
+++ b/Programs/ttb_translate.c
@@ -0,0 +1,371 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "lock.h"
+#include "file.h"
+#include "charset.h"
+#include "ttb.h"
+#include "ttb_internal.h"
+#include "brl_dots.h"
+#include "brl_types.h"
+#include "prefs.h"
+
+static const unsigned char internalTextTableBytes[] = {
+#include "ttb.auto.h"
+};
+
+static TextTable internalTextTable = {
+  .header.bytes = internalTextTableBytes,
+  .size = 0
+};
+
+TextTable *textTable = &internalTextTable;
+
+static LockDescriptor *
+getTextTableLock (void) {
+  static LockDescriptor *lock = NULL;
+  return getLockDescriptor(&lock, "text-table");
+}
+
+void
+lockTextTable (void) {
+  obtainExclusiveLock(getTextTableLock());
+}
+
+void
+unlockTextTable (void) {
+  releaseLock(getTextTableLock());
+}
+
+static inline const void *
+getTextTableItem (TextTable *table, TextTableOffset offset) {
+  return &table->header.bytes[offset];
+}
+
+static inline const UnicodeGroupEntry *
+getUnicodeGroupEntry (TextTable *table, wchar_t character) {
+  TextTableOffset offset = table->header.fields->unicodeGroups[UNICODE_GROUP_NUMBER(character)];
+  if (offset) return getTextTableItem(table, offset);
+  return NULL;
+}
+
+static inline const UnicodePlaneEntry *
+getUnicodePlaneEntry (TextTable *table, wchar_t character) {
+  const UnicodeGroupEntry *group = getUnicodeGroupEntry(table, character);
+
+  if (group) {
+    TextTableOffset offset = group->planes[UNICODE_PLANE_NUMBER(character)];
+    if (offset) return getTextTableItem(table, offset);
+  }
+
+  return NULL;
+}
+
+static inline const UnicodeRowEntry *
+getUnicodeRowEntry (TextTable *table, wchar_t character) {
+  const UnicodePlaneEntry *plane = getUnicodePlaneEntry(table, character);
+
+  if (plane) {
+    TextTableOffset offset = plane->rows[UNICODE_ROW_NUMBER(character)];
+    if (offset) return getTextTableItem(table, offset);
+  }
+
+  return NULL;
+}
+
+static inline const unsigned char *
+getUnicodeCell (TextTable *table, wchar_t character) {
+  const UnicodeRowEntry *row = getUnicodeRowEntry(table, character);
+
+  if (row) {
+    unsigned int cellNumber = UNICODE_CELL_NUMBER(character);
+    if (BITMASK_TEST(row->cellDefined, cellNumber)) return &row->cells[cellNumber];
+  }
+
+  return NULL;
+}
+
+void
+setTryBaseCharacter (TextTable *table, unsigned char yes) {
+  table->options.tryBaseCharacter = yes;
+}
+
+static int
+searchTextTableAlias (const void *target, const void *element) {
+  const wchar_t *reference = target;
+  const TextTableAliasEntry *alias = element;
+
+  if (*reference < alias->from) return -1;
+  if (*reference > alias->from) return 1;
+  return 0;
+}
+
+const TextTableAliasEntry *
+locateTextTableAlias (wchar_t character, const TextTableAliasEntry *array, size_t count) {
+  const TextTableAliasEntry *alias = bsearch(
+    &character, array, count, sizeof(*array), searchTextTableAlias
+  );
+
+  if (alias) return alias;
+  return NULL;
+}
+
+static const TextTableAliasEntry *
+findTextTableAlias (TextTable *table, wchar_t character) {
+  const TextTableHeader *header = table->header.fields;
+
+  return locateTextTableAlias(character, getTextTableItem(table, header->aliasArray), header->aliasCount);
+}
+
+static int
+getDotsForAliasedCharacter (TextTable *table, wchar_t *character, unsigned char *dots) {
+  unsigned int iterationLimit = 0X10;
+  wchar_t characterEncountered[iterationLimit];
+  unsigned int iterationNumber = 0;
+
+  while (iterationNumber < iterationLimit) {
+    if (wmemchr(characterEncountered, *character, iterationNumber)) break;
+    characterEncountered[iterationNumber++] = *character;
+    const UnicodeRowEntry *row = getUnicodeRowEntry(table, *character);
+
+    if (row) {
+      unsigned int cellNumber = UNICODE_CELL_NUMBER(*character);
+
+      if (BITMASK_TEST(row->cellDefined, cellNumber)) {
+        *dots = row->cells[cellNumber];
+        return 1;
+      }
+
+      if (BITMASK_TEST(row->cellAliased, cellNumber)) {
+        const TextTableAliasEntry *alias = findTextTableAlias(table, *character);
+
+        if (alias) {
+          *character = alias->to;
+          continue;
+        }
+      }
+    }
+
+    break;
+  }
+
+  return 0;
+}
+
+typedef struct {
+  TextTable *const table;
+  unsigned char dots;
+} SetBrailleRepresentationData;
+
+static int
+setBrailleRepresentation (wchar_t character, void *data) {
+  SetBrailleRepresentationData *sbr = data;
+  const unsigned char *cell = getUnicodeCell(sbr->table, character);
+
+  if (cell) {
+    sbr->dots = *cell;
+    return 1;
+  }
+
+  return 0;
+}
+
+unsigned char
+convertCharacterToDots (TextTable *table, wchar_t character) {
+  uint32_t row = character & ~UNICODE_CELL_MASK;
+
+  switch (row) {
+#if WCHAR_MAX >= UINT16_MAX
+    case 0XF000: {
+      wint_t wc = convertCharToWchar(character & UNICODE_CELL_MASK);
+      if (wc == WEOF) break;
+      character = wc;
+    }
+    /* fall through */
+#endif /* WCHAR_MAX >= UINT16_MAX */
+
+    default: {
+      {
+        unsigned char dots;
+        if (getDotsForAliasedCharacter(table, &character, &dots)) return dots;
+      }
+
+      if (character == UNICODE_REPLACEMENT_CHARACTER) break;
+
+      if (table->options.tryBaseCharacter) {
+        SetBrailleRepresentationData sbr = {
+          .table = table,
+          .dots = 0
+        };
+
+        if (handleBestCharacter(character, setBrailleRepresentation, &sbr)) {
+          return sbr.dots;
+        }
+      }
+
+      break;
+    }
+  }
+
+  if (row == UNICODE_BRAILLE_ROW) {
+    return character & UNICODE_CELL_MASK;
+  }
+
+  {
+    const unsigned char *cell = table->cells.replacementCharacter;
+    if (cell) return *cell;
+  }
+
+  return BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6 | BRL_DOT_7 | BRL_DOT_8;
+}
+
+wchar_t
+convertDotsToCharacter (TextTable *table, unsigned char dots) {
+  const TextTableHeader *header = table->header.fields;
+  if (BITMASK_TEST(header->inputCharacterDefined, dots)) return header->inputCharacters[dots];
+  return UNICODE_REPLACEMENT_CHARACTER;
+}
+
+wchar_t
+convertInputToCharacter (unsigned char dots) {
+  switch (prefs.brailleTypingMode) {
+    case BRL_TYPING_TEXT:
+      return convertDotsToCharacter(textTable, dots);
+
+    default:
+      logMessage(LOG_WARNING, "unknown braille typing mode: %u", prefs.brailleTypingMode);
+      /* fall through */
+    case BRL_TYPING_DOTS:
+      return UNICODE_BRAILLE_ROW | dots;
+  }
+}
+
+int
+replaceTextTable (const char *directory, const char *name) {
+  TextTable *newTable = NULL;
+
+  if (*name) {
+    char *path;
+
+    if ((path = makeTextTablePath(directory, name))) {
+      logMessage(LOG_DEBUG, "compiling text table: %s", path);
+
+      if (!(newTable = compileTextTable(path))) {
+        logMessage(LOG_ERR, "%s: %s", gettext("cannot compile text table"), path);
+      }
+
+      free(path);
+    }
+  } else {
+    newTable = &internalTextTable;
+  }
+
+  if (newTable) {
+    TextTable *oldTable = textTable;
+
+    lockTextTable();
+      textTable = newTable;
+    unlockTextTable();
+
+    destroyTextTable(oldTable);
+    return 1;
+  }
+
+  logMessage(LOG_ERR, "%s: %s", gettext("cannot load text table"), name);
+  return 0;
+}
+
+size_t
+getTextTableRowsMask (TextTable *table, uint8_t *mask, size_t size) {
+  size_t result = 0;
+  memset(mask, 0, size);
+
+  for (unsigned int groupNumber=0; groupNumber<UNICODE_GROUP_COUNT; groupNumber+=1) {
+    TextTableOffset groupOffset = table->header.fields->unicodeGroups[groupNumber];
+
+    if (groupOffset) {
+      const UnicodeGroupEntry *group = getTextTableItem(table, groupOffset);
+
+      for (unsigned int planeNumber=0; planeNumber<UNICODE_PLANES_PER_GROUP; planeNumber+=1) {
+        TextTableOffset planeOffset = group->planes[planeNumber];
+
+        if (planeOffset) {
+          const UnicodePlaneEntry *plane = getTextTableItem(table, planeOffset);
+
+          for (unsigned int rowNumber=0; rowNumber<UNICODE_ROWS_PER_PLANE; rowNumber+=1) {
+            TextTableOffset rowOffset = plane->rows[rowNumber];
+
+            if (rowOffset) {
+              uint32_t row = UNICODE_CHARACTER(groupNumber, planeNumber, rowNumber, 0) >> UNICODE_ROW_SHIFT;
+              uint32_t index = row / 8;
+              if (index >= size) goto done;
+              mask[index] |= 1 << (row % 8);
+              result = index + 1;
+            }
+          }
+        }
+      }
+    }
+  }
+
+done:
+  return result;
+}
+
+int
+getTextTableRowCells (TextTable *table, uint32_t rowIndex, uint8_t *cells, uint8_t *defined) {
+  wchar_t character = rowIndex << UNICODE_ROW_SHIFT;
+  const UnicodeRowEntry *row = getUnicodeRowEntry(table, character);
+  if (!row) return 0;
+
+  int maskIndex = -1;
+  uint8_t maskBit = 0;
+
+  for (unsigned int cellNumber=0; cellNumber<UNICODE_CELLS_PER_ROW; cellNumber+=1) {
+    unsigned char *cell = &cells[cellNumber];
+    *cell = 0;
+
+    if (!maskBit) {
+      defined[++maskIndex] = 0;
+      maskBit = 1;
+    }
+
+    if (BITMASK_TEST(row->cellDefined, cellNumber)) {
+      *cell = row->cells[cellNumber];
+      defined[maskIndex] |= maskBit;
+    } else if (BITMASK_TEST(row->cellAliased, cellNumber)) {
+      wchar_t wc = character | (cellNumber << UNICODE_CELL_SHIFT);
+      unsigned char dots;
+
+      if (getDotsForAliasedCharacter(table, &wc, &dots)) {
+        *cell = dots;
+        defined[maskIndex] |= maskBit;
+      }
+    }
+
+    maskBit <<= 1;
+  }
+
+  return 1;
+}
diff --git a/Programs/tune.c b/Programs/tune.c
new file mode 100644
index 0000000..8f069ca
--- /dev/null
+++ b/Programs/tune.c
@@ -0,0 +1,528 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "thread.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "async_event.h"
+#include "async_wait.h"
+#include "program.h"
+#include "tune.h"
+#include "notes.h"
+
+static int tuneInitialized = 0;
+static AsyncHandle tuneDeviceCloseTimer = NULL;
+static int openErrorLevel = LOG_ERR;
+
+static const NoteMethods *noteMethods = NULL;
+static NoteDevice *noteDevice = NULL;
+
+static int
+flushNoteDevice (void) {
+  if (!noteDevice) return 1;
+  return noteMethods->flush(noteDevice);
+}
+
+static void
+closeTuneDevice (void) {
+  if (tuneDeviceCloseTimer) {
+    asyncCancelRequest(tuneDeviceCloseTimer);
+    tuneDeviceCloseTimer = NULL;
+  }
+
+  if (noteDevice) {
+    noteMethods->destruct(noteDevice);
+    noteDevice = NULL;
+  }
+}
+
+ASYNC_ALARM_CALLBACK(handleTuneDeviceCloseTimeout) {
+  if (tuneDeviceCloseTimer) {
+    asyncDiscardHandle(tuneDeviceCloseTimer);
+    tuneDeviceCloseTimer = NULL;
+  }
+
+  closeTuneDevice();
+}
+
+static int
+openTuneDevice (void) {
+  const int timeout = TUNE_DEVICE_CLOSE_DELAY;
+
+  if (noteDevice) {
+    asyncResetAlarmIn(tuneDeviceCloseTimer, timeout);
+    return 1;
+  }
+
+  if (noteMethods) {
+    if ((noteDevice = noteMethods->construct(openErrorLevel)) != NULL) {
+      asyncNewRelativeAlarm(&tuneDeviceCloseTimer, timeout, handleTuneDeviceCloseTimeout, NULL);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static const NoteElement *currentlyPlayingNotes = NULL;
+static const ToneElement *currentlyPlayingTones = NULL;
+
+typedef unsigned char TuneSynchronizationMonitor;
+
+typedef enum {
+  TUNE_REQ_SET_DEVICE,
+  TUNE_REQ_PLAY_NOTES,
+  TUNE_REQ_PLAY_TONES,
+  TUNE_REQ_WAIT,
+  TUNE_REQ_SYNCHRONIZE
+} TuneRequestType;
+
+typedef struct {
+  TuneRequestType type;
+
+  union {
+    struct {
+      const NoteMethods *methods;
+    } setDevice;
+
+    struct {
+      const NoteElement *tune;
+    } playNotes;
+
+    struct {
+      const ToneElement *tune;
+    } playTones;
+
+    struct {
+      int time;
+    } wait;
+
+    struct {
+      TuneSynchronizationMonitor *monitor;
+    } synchronize;
+  } parameters;
+} TuneRequest;
+
+static void
+handleTuneRequest_setDevice (const NoteMethods *methods) {
+  if (methods != noteMethods) {
+    closeTuneDevice();
+    noteMethods = methods;
+  }
+}
+
+static void
+handleTuneRequest_playNotes (const NoteElement *tune) {
+  while (tune->duration) {
+    if (!openTuneDevice()) return;
+    if (!noteMethods->note(noteDevice, tune->duration, tune->note)) return;
+    tune += 1;
+  }
+
+  flushNoteDevice();
+}
+
+static void
+handleTuneRequest_playTones (const ToneElement *tune) {
+  while (tune->duration) {
+    if (!openTuneDevice()) return;
+    if (!noteMethods->tone(noteDevice, tune->duration, tune->frequency)) return;
+    tune += 1;
+  }
+
+  flushNoteDevice();
+}
+
+static void
+handleTuneRequest_wait (int time) {
+  asyncWait(time);
+}
+
+static void
+handleTuneRequest_synchronize (TuneSynchronizationMonitor *monitor) {
+  *monitor = 1;
+}
+
+static void
+handleTuneRequest (TuneRequest *req) {
+  if (req) {
+    switch (req->type) {
+      case TUNE_REQ_SET_DEVICE:
+        handleTuneRequest_setDevice(req->parameters.setDevice.methods);
+        break;
+
+      case TUNE_REQ_PLAY_NOTES: {
+        const NoteElement *tune = req->parameters.playNotes.tune;
+
+        currentlyPlayingNotes = tune;
+        handleTuneRequest_playNotes(tune);
+        currentlyPlayingNotes = NULL;
+
+        break;
+      }
+
+      case TUNE_REQ_PLAY_TONES: {
+        const ToneElement *tune = req->parameters.playTones.tune;
+
+        currentlyPlayingTones = tune;
+        handleTuneRequest_playTones(tune);
+        currentlyPlayingTones = NULL;
+
+        break;
+      }
+
+      case TUNE_REQ_WAIT:
+        handleTuneRequest_wait(req->parameters.wait.time);
+        break;
+
+      case TUNE_REQ_SYNCHRONIZE:
+        handleTuneRequest_synchronize(req->parameters.synchronize.monitor);
+        break;
+    }
+
+    free(req);
+  } else {
+    closeTuneDevice();
+  }
+}
+
+#ifdef GOT_PTHREADS
+typedef enum {
+  TUNE_THREAD_NONE,
+  TUNE_THREAD_STARTING,
+  TUNE_THREAD_FAILED,
+
+  TUNE_THREAD_RUNNING,
+  TUNE_THREAD_STOPPING,
+  TUNE_THREAD_STOPPED
+} TuneThreadState;
+
+static TuneThreadState tuneThreadState = TUNE_THREAD_NONE;
+static pthread_t tuneThreadIdentifier;
+static AsyncEvent *tuneRequestEvent = NULL;
+static AsyncEvent *tuneMessageEvent = NULL;
+
+static void
+setTuneThreadState (TuneThreadState newState) {
+  TuneThreadState oldState = tuneThreadState;
+
+  logMessage(LOG_DEBUG, "tune thread state change: %d -> %d", oldState, newState);
+  tuneThreadState = newState;
+}
+
+ASYNC_CONDITION_TESTER(testTuneThreadStarted) {
+  return tuneThreadState != TUNE_THREAD_STARTING;
+}
+
+ASYNC_CONDITION_TESTER(testTuneThreadStopping) {
+  return tuneThreadState == TUNE_THREAD_STOPPING;
+}
+
+ASYNC_CONDITION_TESTER(testTuneThreadStopped) {
+  return tuneThreadState == TUNE_THREAD_STOPPED;
+}
+
+typedef enum {
+  TUNE_MSG_SET_STATE
+} TuneMessageType;
+
+typedef struct {
+  TuneMessageType type;
+
+  union {
+    struct {
+      TuneThreadState state;
+    } setState;
+  } parameters;
+} TuneMessage;
+
+static void
+handleTuneMessage (TuneMessage *msg) {
+  switch (msg->type) {
+    case TUNE_MSG_SET_STATE:
+      setTuneThreadState(msg->parameters.setState.state);
+      break;
+  }
+
+  free(msg);
+}
+
+ASYNC_EVENT_CALLBACK(handleTuneMessageEvent) {
+  TuneMessage *msg = parameters->signalData;
+
+  if (msg) handleTuneMessage(msg);
+}
+
+static int
+sendTuneMessage (TuneMessage *msg) {
+  return asyncSignalEvent(tuneMessageEvent, msg);
+}
+
+static TuneMessage *
+newTuneMessage (TuneMessageType type) {
+  TuneMessage *msg;
+
+  if ((msg = malloc(sizeof(*msg)))) {
+    memset(msg, 0, sizeof(*msg));
+    msg->type = type;
+    return msg;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+sendTuneThreadState (TuneThreadState state) {
+  TuneMessage *msg;
+
+  if ((msg = newTuneMessage(TUNE_MSG_SET_STATE))) {
+    msg->parameters.setState.state = state;
+    if (!sendTuneMessage(msg)) free(msg);
+  }
+}
+
+static void
+finishTuneRequest_stop (void) {
+  setTuneThreadState(TUNE_THREAD_STOPPING);
+}
+
+static void
+finishTuneRequest_synchronize (void) {
+  sendTuneMessage(NULL);
+}
+
+ASYNC_EVENT_CALLBACK(handleTuneRequestEvent) {
+  TuneRequest *req = parameters->signalData;
+  void (*finish) (void) = NULL;
+
+  if (req) {
+    switch (req->type) {
+      case TUNE_REQ_SYNCHRONIZE:
+        finish = finishTuneRequest_synchronize;
+        break;
+
+      default:
+        break;
+    }
+  } else {
+    finish = finishTuneRequest_stop;
+  }
+
+  handleTuneRequest(req);
+  if (finish) finish();
+}
+
+THREAD_FUNCTION(runTuneThread) {
+  if ((tuneRequestEvent = asyncNewEvent(handleTuneRequestEvent, NULL))) {
+    sendTuneThreadState(TUNE_THREAD_RUNNING);
+    asyncWaitFor(testTuneThreadStopping, NULL);
+
+    asyncDiscardEvent(tuneRequestEvent);
+    tuneRequestEvent = NULL;
+  }
+
+  sendTuneThreadState(TUNE_THREAD_STOPPED);
+  return NULL;
+}
+
+static int
+startTuneThread (void) {
+  if (tuneThreadState == TUNE_THREAD_NONE) {
+    setTuneThreadState(TUNE_THREAD_STARTING);
+
+    if ((tuneMessageEvent = asyncNewEvent(handleTuneMessageEvent, NULL))) {
+      int creationError = createThread("tune-thread", &tuneThreadIdentifier,
+                                       NULL, runTuneThread, NULL);
+
+      if (!creationError) {
+        asyncWaitFor(testTuneThreadStarted, NULL);
+        if (tuneThreadState == TUNE_THREAD_RUNNING) return 1;
+      } else {
+        logActionError(creationError, "tune thread creation");
+        setTuneThreadState(TUNE_THREAD_FAILED);
+      }
+
+      asyncDiscardEvent(tuneMessageEvent);
+      tuneMessageEvent = NULL;
+    }
+  }
+
+  return tuneThreadState == TUNE_THREAD_RUNNING;
+}
+#endif /* GOT_PTHREADS */
+
+static int
+sendTuneRequest (TuneRequest *req) {
+#ifdef GOT_PTHREADS
+  if (startTuneThread()) {
+    return asyncSignalEvent(tuneRequestEvent, req);
+  }
+#endif /* GOT_PTHREADS */
+
+  handleTuneRequest(req);
+  return 1;
+}
+
+static void
+exitTunes (void *data) {
+  sendTuneRequest(NULL);
+
+#ifdef GOT_PTHREADS
+  if (tuneThreadState >= TUNE_THREAD_RUNNING) {
+    asyncWaitFor(testTuneThreadStopped, NULL);
+  }
+
+  tuneThreadState = TUNE_THREAD_NONE;
+#endif /* GOT_PTHREADS */
+
+  tuneInitialized = 0;
+}
+ 
+static TuneRequest *
+newTuneRequest (TuneRequestType type) {
+  TuneRequest *req;
+
+  if (!tuneInitialized) {
+    tuneInitialized = 1;
+    onProgramExit("tunes", exitTunes, NULL);
+  }
+
+  if ((req = malloc(sizeof(*req)))) {
+    memset(req, 0, sizeof(*req));
+    req->type = type;
+    return req;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+int
+tuneSetDevice (TuneDevice device) {
+  const NoteMethods *methods;
+
+  switch (device) {
+    default:
+      return 0;
+
+#ifdef HAVE_BEEP_SUPPORT
+    case tdBeeper:
+      methods = &beepNoteMethods;
+      break;
+#endif /* HAVE_BEEP_SUPPORT */
+
+#ifdef HAVE_PCM_SUPPORT
+    case tdPcm:
+      methods = &pcmNoteMethods;
+      break;
+#endif /* HAVE_PCM_SUPPORT */
+
+#ifdef HAVE_MIDI_SUPPORT
+    case tdMidi:
+      methods = &midiNoteMethods;
+      break;
+#endif /* HAVE_MIDI_SUPPORT */
+
+#ifdef HAVE_FM_SUPPORT
+    case tdFm:
+      methods = &fmNoteMethods;
+      break;
+#endif /* HAVE_FM_SUPPORT */
+  }
+
+  {
+    TuneRequest *req;
+
+    if ((req = newTuneRequest(TUNE_REQ_SET_DEVICE))) {
+      req->parameters.setDevice.methods = methods;
+      if (!sendTuneRequest(req)) free(req);
+    }
+  }
+
+  return 1;
+}
+
+void
+tunePlayNotes (const NoteElement *tune) {
+  if (tune != currentlyPlayingNotes) {
+    TuneRequest *req;
+
+    if ((req = newTuneRequest(TUNE_REQ_PLAY_NOTES))) {
+      req->parameters.playNotes.tune = tune;
+      if (!sendTuneRequest(req)) free(req);
+    }
+  }
+}
+
+void
+tunePlayTones (const ToneElement *tune) {
+  if (tune != currentlyPlayingTones) {
+    TuneRequest *req;
+
+    if ((req = newTuneRequest(TUNE_REQ_PLAY_TONES))) {
+      req->parameters.playTones.tune = tune;
+      if (!sendTuneRequest(req)) free(req);
+    }
+  }
+}
+
+void
+tuneWait (int time) {
+  TuneRequest *req;
+
+  if ((req = newTuneRequest(TUNE_REQ_WAIT))) {
+    req->parameters.wait.time = time;
+    if (!sendTuneRequest(req)) free(req);
+  }
+}
+
+ASYNC_CONDITION_TESTER(testTuneSynchronizationMonitor) {
+  TuneSynchronizationMonitor *monitor = data;
+
+  return !!*monitor;
+}
+
+void
+tuneSynchronize (void) {
+  TuneRequest *req;
+
+  if ((req = newTuneRequest(TUNE_REQ_SYNCHRONIZE))) {
+    TuneSynchronizationMonitor monitor = 0;
+    req->parameters.synchronize.monitor = &monitor;
+
+    if (sendTuneRequest(req)) {
+      asyncWaitFor(testTuneSynchronizationMonitor, &monitor);
+    } else {
+      free(req);
+    }
+  }
+}
+
+void
+suppressTuneDeviceOpenErrors (void) {
+  openErrorLevel = LOG_DEBUG;
+}
diff --git a/Programs/tune_builder.c b/Programs/tune_builder.c
new file mode 100644
index 0000000..cf8f1dd
--- /dev/null
+++ b/Programs/tune_builder.c
@@ -0,0 +1,788 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "log.h"
+#include "tune_builder.h"
+#include "notes.h"
+#include "utf8.h"
+
+typedef unsigned int TuneNumber;
+
+typedef struct {
+  const char *name;
+  TuneNumber minimum;
+  TuneNumber maximum;
+  TuneNumber current;
+} TuneParameter;
+
+struct TuneBuilderStruct {
+  TuneStatus status;
+
+  struct {
+    ToneElement *array;
+    unsigned int size;
+    unsigned int count;
+  } tones;
+
+  signed char accidentals[NOTES_PER_SCALE];
+  TuneParameter duration;
+  TuneParameter note;
+  TuneParameter octave;
+  TuneParameter percentage;
+  TuneParameter tempo;
+
+  TuneNumber durationMultiplier;
+  TuneNumber durationDivisor;
+
+  struct {
+    const wchar_t *text;
+    const char *name;
+    unsigned int index;
+  } source;
+};
+
+static const wchar_t *noteLetters = WS_C("cdefgab");
+static const unsigned char noteOffsets[] = {0, 2, 4, 5, 7, 9, 11};
+static const signed char scaleAccidentals[] = {0, 2, 4, -1, 1, 3, 5};
+static const unsigned char accidentalTable[] = {3, 0, 4, 1, 5, 2, 6};
+
+typedef struct {
+  const wchar_t *name;
+  signed char accidentals;
+} ModeEntry;
+
+static const ModeEntry modeTable[] = {
+  {.name=WS_C("major"), .accidentals=0},
+  {.name=WS_C("minor"), .accidentals=-3},
+
+  {.name=WS_C("ionian"), .accidentals=0},
+  {.name=WS_C("dorian"), .accidentals=-2},
+  {.name=WS_C("phrygian"), .accidentals=-4},
+  {.name=WS_C("lydian"), .accidentals=1},
+  {.name=WS_C("mixolydian"), .accidentals=-1},
+  {.name=WS_C("aeolian"), .accidentals=-3},
+  {.name=WS_C("locrian"), .accidentals=-5},
+};
+static const unsigned char modeCount = ARRAY_COUNT(modeTable);
+
+static void
+logSyntaxError (TuneBuilder *tb, const char *message) {
+  tb->status = TUNE_STATUS_SYNTAX;
+
+  logMessage(LOG_ERR, "tune error: %s[%u]: %s: %" PRIws,
+             tb->source.name, tb->source.index,
+             message, tb->source.text);
+}
+
+int
+addTone (TuneBuilder *tb, const ToneElement *tone) {
+  if (tb->tones.count == tb->tones.size) {
+    unsigned int newSize = tb->tones.size? (tb->tones.size << 1): 1;
+    ToneElement *newArray;
+
+    if (!(newArray = realloc(tb->tones.array, ARRAY_SIZE(newArray, newSize)))) {
+      tb->status = TUNE_STATUS_FATAL;
+      logMallocError();
+      return 0;
+    }
+
+    tb->tones.array = newArray;
+    tb->tones.size = newSize;
+  }
+
+  tb->tones.array[tb->tones.count++] = *tone;
+  return 1;
+}
+
+int
+addNote (TuneBuilder *tb, unsigned char note, int duration) {
+  if (!duration) return 1;
+
+  ToneElement tone = TONE_PLAY(duration, getNoteFrequency(note));
+  return addTone(tb, &tone);
+}
+
+static int
+parseNumber (
+  TuneBuilder *tb,
+  TuneNumber *number, const wchar_t **operand, int required,
+  const TuneNumber minimum, const TuneNumber maximum,
+  const char *name
+) {
+  const wchar_t *const start = *operand;
+  unsigned long value = 0;
+  const char *problem = "invalid";
+
+  while (**operand) {
+    if (!value && (*operand > start)) goto PROBLEM_ENCOUNTERED;
+
+    long int digit = **operand - WC_C('0');
+    if (digit < 0) break;
+    if (digit > 9) break;
+
+    value *= 10;
+    value += digit;
+    if (value > UINT_MAX) goto PROBLEM_ENCOUNTERED;
+
+    *operand += 1;
+  }
+
+  if (*operand > start) {
+    if (value < minimum) goto PROBLEM_ENCOUNTERED;
+    if (value > maximum) goto PROBLEM_ENCOUNTERED;
+    *number = value;
+  } else if (required) {
+    problem = "missing";
+    goto PROBLEM_ENCOUNTERED;
+  }
+
+  return 1;
+
+PROBLEM_ENCOUNTERED:
+  if (name) {
+    char message[0X80];
+    snprintf(message, sizeof(message), "%s %s", problem, name);
+    logSyntaxError(tb, message);
+  }
+
+  return 0;
+}
+
+static int
+parseParameter (
+  TuneBuilder *tb, TuneParameter *parameter,
+  const wchar_t **operand, int required
+) {
+  return parseNumber(tb, &parameter->current, operand, required,
+                     parameter->minimum, parameter->maximum, parameter->name);
+}
+
+static int
+parseOptionalParameter (TuneBuilder *tb, TuneParameter *parameter, const wchar_t **operand) {
+  return parseParameter(tb, parameter, operand, 0);
+}
+
+static int
+parseRequiredParameter (TuneBuilder *tb, TuneParameter *parameter, const wchar_t **operand) {
+  return parseParameter(tb, parameter, operand, 1);
+}
+
+static int
+parsePercentage (TuneBuilder *tb, const wchar_t **operand) {
+  return parseRequiredParameter(tb, &tb->percentage, operand);
+}
+
+static int
+parseTempo (TuneBuilder *tb, const wchar_t **operand) {
+  return parseRequiredParameter(tb, &tb->tempo, operand);
+}
+
+static void
+setCurrentDuration (TuneBuilder *tb) {
+  tb->duration.current = (60000 * tb->durationMultiplier) / (tb->tempo.current * tb->durationDivisor);
+}
+
+static int
+parseDuration (TuneBuilder *tb, const wchar_t **operand, int *duration) {
+  if (**operand == '@') {
+    *operand += 1;
+    if (!parseRequiredParameter(tb, &tb->duration, operand)) return 0;
+  } else {
+    const wchar_t *durationOperand = *operand;
+
+    TuneNumber multiplier;
+    TuneNumber divisor;
+
+    if (**operand == '*') {
+      *operand += 1;
+
+      if (!parseNumber(tb, &multiplier, operand, 1, 1, 16, "duration multiplier")) {
+        return 0;
+      }
+    } else {
+      multiplier = 1;
+    }
+
+    if (**operand == '/') {
+      *operand += 1;
+
+      if (!parseNumber(tb, &divisor, operand, 1, 1, 128, "duration divisor")) {
+        return 0;
+      }
+    } else {
+      divisor = 1;
+    }
+
+    if (*operand != durationOperand) {
+      tb->durationMultiplier = multiplier;
+      tb->durationDivisor = divisor;
+      tb->duration.current = 0;
+    }
+
+    if (!tb->duration.current) setCurrentDuration(tb);
+  }
+  *duration = tb->duration.current;
+
+  {
+    int increment = *duration;
+
+    while (**operand == '.') {
+      *duration += (increment /= 2);
+      *operand += 1;
+    }
+  }
+
+  return 1;
+}
+
+static TuneNumber
+toOctave (TuneNumber note) {
+  return note / NOTES_PER_OCTAVE;
+}
+
+static void
+setCurrentOctave (TuneBuilder *tb) {
+  tb->octave.current = toOctave(tb->note.current);
+}
+
+static void
+setAccidentals (TuneBuilder *tb, int accidentals) {
+  int quotient = accidentals / NOTES_PER_SCALE;
+  int remainder = accidentals % NOTES_PER_SCALE;
+
+  for (unsigned int index=0; index<ARRAY_COUNT(tb->accidentals); index+=1) {
+    tb->accidentals[index] = quotient;
+  }
+
+  while (remainder > 0) {
+    tb->accidentals[accidentalTable[--remainder]] += 1;
+  }
+
+  while (remainder < 0) {
+    tb->accidentals[accidentalTable[NOTES_PER_SCALE + remainder++]] -= 1;
+  }
+}
+
+static int
+parseNoteLetter (unsigned char *index, const wchar_t **operand) {
+  const wchar_t *letter = wcschr(noteLetters, **operand);
+
+  if (!letter) return 0;
+  if (!*letter) return 0;
+
+  *index = letter - noteLetters;
+  *operand += 1;
+  return 1;
+}
+
+static int
+parseMode (TuneBuilder *tb, int *accidentals, const wchar_t **operand) {
+  const wchar_t *from = *operand;
+  if (!isalpha(*from)) return 1;
+
+  const wchar_t *to = from;
+  while (isalpha(*++to));
+  unsigned int length = to - from;
+
+  const ModeEntry *mode = NULL;
+  const ModeEntry *current = modeTable;
+  const ModeEntry *end = current + modeCount;
+
+  while (current < end) {
+    if (wcsncmp(current->name, from, length) == 0) {
+      if (mode) {
+        logSyntaxError(tb, "ambiguous mode");
+        return 0;
+      }
+
+      mode = current;
+    }
+
+    current += 1;
+  }
+
+  if (!mode) {
+    logSyntaxError(tb, "unrecognized mode");
+    return 0;
+  }
+
+  *accidentals += mode->accidentals;
+  *operand = to;
+  return 1;
+}
+
+static int
+parseKey (TuneBuilder *tb, const wchar_t **operand) {
+  int noteSpecified = 0;
+  int accidentals;
+
+  {
+    unsigned char index;
+
+    if (parseNoteLetter(&index, operand)) {
+      noteSpecified = 1;
+      accidentals = scaleAccidentals[index];
+      if (!parseMode(tb, &accidentals, operand)) return 0;
+    }
+  }
+
+  if (!noteSpecified) {
+    TuneNumber count = 0;
+    int increment = 1;
+
+    if (!parseNumber(tb, &count, operand, 0, 1, NOTES_PER_OCTAVE-1, "accidental count")) {
+      return 0;
+    }
+
+    int haveCount = count != 0;
+    wchar_t accidental = **operand;
+
+    switch (accidental) {
+      case '-':
+        increment = -increment;
+        /* fall through */
+      case '+':
+        if (haveCount) {
+          *operand += 1;
+        } else {
+          do {
+            count += 1;
+          } while (*++*operand == accidental);
+        }
+        break;
+
+      default:
+        if (!haveCount) break;
+        logSyntaxError(tb, "accidental not specified");
+        return 0;
+    }
+
+    accidentals = increment * count;
+  }
+
+logMessage(LOG_NOTICE, "ccc=%d", accidentals);
+  setAccidentals(tb, accidentals);
+  return 1;
+}
+
+static int
+parseNote (TuneBuilder *tb, const wchar_t **operand, unsigned char *note) {
+  int noteNumber;
+
+  if (**operand == 'r') {
+    *operand += 1;
+    noteNumber = 0;
+  } else if (**operand == 'm') {
+    *operand += 1;
+    TuneParameter parameter = tb->note;
+    if (!parseRequiredParameter(tb, &parameter, operand)) return 0;
+    noteNumber = parameter.current;
+  } else {
+    unsigned char noteIndex;
+    if (!parseNoteLetter(&noteIndex, operand)) return 0;
+
+    const wchar_t *octaveOperand = *operand;
+    TuneParameter octave = tb->octave;
+    if (!parseOptionalParameter(tb, &octave, operand)) return 0;
+
+    int octaveSpecified = *operand != octaveOperand;
+    if (octaveSpecified) octave.current += 1;
+
+    noteNumber = (octave.current * NOTES_PER_OCTAVE) + noteOffsets[noteIndex];
+    int defaultAccidentals = tb->accidentals[noteIndex];
+
+    if (!octaveSpecified) {
+      int adjustOctave = 0;
+      TuneNumber previousNote = tb->note.current;
+      TuneNumber currentNote = noteNumber;
+
+      if (currentNote < previousNote) {
+        currentNote += NOTES_PER_OCTAVE;
+        if ((currentNote - previousNote) <= 3) adjustOctave = 1;
+      } else if (currentNote > previousNote) {
+        currentNote -= NOTES_PER_OCTAVE;
+        if ((previousNote - currentNote) <= 3) adjustOctave = 1;
+      }
+
+      if (adjustOctave) noteNumber = currentNote;
+    }
+
+    tb->note.current = noteNumber;
+    setCurrentOctave(tb);
+
+    {
+      wchar_t accidental = **operand;
+
+      switch (accidental) {
+        {
+          int increment;
+
+        case '+':
+          increment = 1;
+          goto doAccidental;
+
+        case '-':
+          increment = -1;
+          goto doAccidental;
+
+        doAccidental:
+          do {
+            noteNumber += increment;
+          } while (*++*operand == accidental);
+
+          break;
+        }
+
+        case '=':
+          *operand += 1;
+          break;
+
+        default:
+          noteNumber += defaultAccidentals;
+          break;
+      }
+    }
+
+    if (noteNumber < getLowestNote()) {
+      logSyntaxError(tb, "note too low");
+      return 0;
+    }
+
+    if (noteNumber > getHighestNote()) {
+      logSyntaxError(tb, "note too high");
+      return 0;
+    }
+  }
+
+  *note = noteNumber;
+  return 1;
+}
+
+static int
+parseTone (TuneBuilder *tb, const wchar_t **operand) {
+  while (1) {
+    tb->source.text = *operand;
+    unsigned char note;
+
+    {
+      const wchar_t *noteOperand = *operand;
+      if (!parseNote(tb, operand, &note)) return *operand == noteOperand;
+    }
+
+    int duration;
+    if (!parseDuration(tb, operand, &duration)) return 0;
+
+    if (note) {
+      int onDuration = (duration * tb->percentage.current) / 100;
+      if (!addNote(tb, note, onDuration)) return 0;
+      duration -= onDuration;
+    }
+
+    if (!addNote(tb, 0, duration)) return 0;
+  }
+
+  return 1;
+}
+
+static int
+parseCommand (TuneBuilder *tb, const wchar_t *operand) {
+  tb->source.text = operand;
+
+  switch (*operand) {
+    case 'k':
+      operand += 1;
+      if (!parseKey(tb, &operand)) return 0;
+      break;
+
+    case 'p':
+      operand += 1;
+      if (!parsePercentage(tb, &operand)) return 0;
+      break;
+
+    case 't':
+      operand += 1;
+      if (!parseTempo(tb, &operand)) return 0;
+      break;
+
+    default:
+      if (!parseTone(tb, &operand)) return 0;
+      break;
+  }
+
+  if (*operand) {
+    logSyntaxError(tb, "extra data");
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+parseTuneText (TuneBuilder *tb, const wchar_t *text) {
+  tb->source.text = text;
+
+  wchar_t buffer[wcslen(text) + 1];
+  wcscpy(buffer, text);
+
+  static const wchar_t *delimiters = WS_C(" \t\r\n");
+  wchar_t *string = buffer;
+  wchar_t *operand;
+
+#if !defined(__MINGW32__) && !defined(__MSDOS__)
+  wchar_t *next;
+#endif /* __MINGW32__ */
+
+  while ((operand = wcstok(string, delimiters
+#ifndef __MINGW32__
+                           , &next
+#endif /* __MINGW32__ */
+                          ))) {
+    if (*operand == '#') break;
+    if (!parseCommand(tb, operand)) return 0;
+    string = NULL;
+  }
+
+  return 1;
+}
+
+int
+parseTuneString (TuneBuilder *tb, const char *string) {
+  const size_t size = strlen(string) + 1;
+  wchar_t characters[size];
+
+  const char *byte = string;
+  wchar_t *character = characters;
+
+  convertUtf8ToWchars(&byte, &character, size);
+
+  return parseTuneText(tb, characters);
+}
+
+ToneElement *
+getTune (TuneBuilder *tb) {
+  if (tb->status == TUNE_STATUS_OK) {
+    unsigned int count = tb->tones.count;
+    ToneElement *tune;
+
+    if ((tune = malloc(ARRAY_SIZE(tune, (count + 1))))) {
+      memcpy(tune, tb->tones.array, ARRAY_SIZE(tune, count));
+
+      static const ToneElement tone = TONE_STOP();
+      tune[count] = tone;
+
+      return tune;
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+TuneStatus
+getTuneStatus (TuneBuilder *tb) {
+  return tb->status;
+}
+
+void
+setTuneSourceName (TuneBuilder *tb, const char *name) {
+  tb->source.name = name;
+}
+
+void
+setTuneSourceIndex (TuneBuilder *tb, unsigned int index) {
+  tb->source.index = index;
+}
+
+void
+incrementTuneSourceIndex (TuneBuilder *tb) {
+  tb->source.index += 1;
+}
+
+static inline void
+setParameter (
+  TuneParameter *parameter, const char *name,
+  TuneNumber minimum, TuneNumber maximum, TuneNumber current
+) {
+  parameter->name = name;
+  parameter->minimum = minimum;
+  parameter->maximum = maximum;
+  parameter->current = current;
+}
+
+void
+resetTuneBuilder (TuneBuilder *tb) {
+  tb->status = TUNE_STATUS_OK;
+
+  tb->tones.count = 0;
+
+  setParameter(&tb->duration, "note duration", 1, UINT16_MAX, 0);
+  setParameter(&tb->note, "MIDI note number", getLowestNote(), getHighestNote(), NOTE_MIDDLE_C+noteOffsets[2]);
+  setParameter(&tb->octave, "octave number", 0, 9, 0);
+  setParameter(&tb->percentage, "percentage", 1, 100, 80);
+  setParameter(&tb->tempo, "tempo", 40, UINT8_MAX, (60 * 2));
+
+  tb->durationMultiplier = 1;
+  tb->durationDivisor = 1;
+
+  setAccidentals(tb, 0);
+  setCurrentOctave(tb);
+
+  tb->source.text = WS_C("");
+  tb->source.name = "";
+  tb->source.index = 0;
+}
+
+TuneBuilder *
+newTuneBuilder (void) {
+  TuneBuilder *tb;
+
+  if ((tb = malloc(sizeof(*tb)))) {
+    memset(tb, 0, sizeof(*tb));
+
+    tb->tones.array = NULL;
+    tb->tones.size = 0;
+
+    resetTuneBuilder(tb);
+    return tb;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+destroyTuneBuilder (TuneBuilder *tb) {
+  if (tb->tones.array) free(tb->tones.array);
+  free(tb);
+}
+
+BEGIN_USAGE_NOTES(tuneBuilderUsageNotes)
+  "A command group is zero or more commands separated from one another by whitespace.",
+  "A number sign [#] at the beginning or after whitespace begins a comment.",
+  "",
+  "Each command is a letter immediately followed by its parameter(s).",
+  "In the following descriptions,",
+  "<angle brackets> are used to show that a parameter is required",
+  "and [square brackets] are used to show that it's optional.",
+  "While a command doesn't contain any spaces, some of the descriptions include them for clarity.",
+  "When there is a choice, {curly brackets} combined with vertical bar [|] separators are used.",
+  "These commands are recognized:",
+  "  a-g  the seven standard note letters",
+  "  k    change the key",
+  "  m    a MIDI note number",
+  "  p    change the note period",
+  "  r    a rest",
+  "  t    change the tempo",
+  "",
+  "A note command begins with any of the seven standard note letters (a, b, c, d, e, f, g).",
+  "Its general syntax is:",
+  "",
+  "  <letter> [octave] [accidental] [duration]",
+  "",
+  "The m<number>[duration] command specifies a note by its MIDI number.",
+  "The number must be within the range 1 through 127.",
+  "MIDI stands for Musical Instrument Digital Interface.",
+  "It specifies that Middle-C is note 60, ",
+  "that a higher number represents a higher pitch,",
+  "and that adjacent numbers represent notes that differ in pitch by 1 semitone.",
+  "",
+  "The r[duration] command specifies a rest - the musical way of saying \"no note\".",
+  "",
+  "Octaves are numbered according to International Pitch Notation,",
+  "so the scale starting with Middle-C is octave 4.",
+  "Octaves 0 through 9 may be specified, although notes above g9 can't be played (this is a MIDI limitation).",
+  "If the octave of the first note of the tune isn't specified then octave 4 is assumed.",
+  "If it isn't specified for any other note then the technique used in braille music is used.",
+  "Normally, the octave of the previous note is assumed.",
+  "If, however, the note in an adjacent octave is three semitones or less away from the previous one then the new octave is assumed.",
+  "",
+  "If the accidental (sharp, flat, or natural) isn't specified then the one defined by the current key is assumed.",
+  "It may be specified as",
+  "a plus sign [+] for sharp,",
+  "a minus sign [-] for flat,",
+  "or an equal sign [=] for natural.",
+  "More than one sharp or flat (+ or -) may be specified.",
+  "",
+  "If the duration of a ntoe isn't specified then the duration of the previous note is assumed.",
+  "If the duration of the first note isn't specified then the length of one beat at the default tempo is assumed.",
+  "A duration may be specified in two ways:",
+  "",
+  "@<number>:",
+  "It may be explicitly set by prefixing the number of milliseconds with an at sign [@].",
+  "",
+  "[*<multiplier>] [/<divisor>]:",
+  "It may be calculated by applying a multiplier and/or a divisor, in that order, to the length of one beat at the current tempo.",
+  "The multiplier is a number prefixed with an asterisk [*] and must be within the range 1 through 16.",
+  "The divisor is a number prefixed with a slash [/] and must be within the range 1 through 128.",
+  "Both default to 1.",
+  "",
+  "Both ways of specifying the duration allow any number of dots [.] to be appended.",
+  "These dots modify the duration of the note in the same way that adding dots to a note does in print (and braille) music.",
+  "For example:",
+  "At a tempo of 120 (beats per minute), a whole note (4 beats) has a duration of 2 seconds. So:",
+  "  #dots  seconds  beats",
+  "    0     2       4",
+  "    1     3       6",
+  "    2     3.5     7",
+  "    3     3.75    7+1/2",
+  "   etc",
+  "",
+  "The k command changes the key.",
+  "The initial key is C Major, i.e. it has no accidentals.",
+  "This command has two forms:",
+  "",
+  "k<root>[mode]:",
+  "The root note must be one of the seven standard note letters (a, b, c, d, e, f, g).",
+  "The mode may also be specified.",
+  "Any Unambiguous abbreviation of its name may be used.",
+  "The recognized mode names are:",
+  "major,",
+  "minor,",
+  "ionian,",
+  "dorian,",
+  "phrygian,",
+  "lydian,",
+  "mixolydian,",
+  "aeolian,",
+  "locrian.",
+  "",
+  "k[count]<accidental>:",
+  "The key may also be implied by specifying how many accidentals (sharps or flats) it has.",
+  "The count must be a number within the range 1 through 12 (the number of semitones within a scale).",
+  "The accidental must be either a plus sign [+] for sharp or a minus sign [-] for flat.",
+  "If the count is specified then there must be one accidental indicator.",
+  "If it isn't specified then more than one accidental indicator may be specified.",
+  "",
+  "The p<number> command changes the note period - the amount of time within its duration that a note is on.",
+  "It's a percentage, and must be within the range 1 through 100.",
+  "The initial note period is 80 percent.",
+  "",
+  "The t<number> command changes the tempo (speed).",
+  "It's the number of beats per minute, and must be within the range 40 through 255.",
+  "The initial tempo is 120 beats per minute.",
+END_USAGE_NOTES
diff --git a/Programs/tune_utils.c b/Programs/tune_utils.c
new file mode 100644
index 0000000..eb1fe1a
--- /dev/null
+++ b/Programs/tune_utils.c
@@ -0,0 +1,157 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "tune.h"
+#include "tune_utils.h"
+#include "tune_types.h"
+#include "midi.h"
+#include "parse.h"
+#include "prefs.h"
+
+static const char *tuneDeviceNames[] = {
+  "beeper",
+  "pcm",
+  "midi",
+  "fm",
+  NULL
+};
+
+const char *
+getTuneDeviceName (TuneDevice device) {
+  return tuneDeviceNames[device];
+}
+
+int
+parseTuneDevice (const char *setting) {
+  if (setting && *setting) {
+    unsigned int device;
+
+    if (!validateChoice(&device, setting, tuneDeviceNames)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid tune device", setting);
+      return 0;
+    }
+
+    prefs.tuneDevice = device;
+  }
+
+  return 1;
+}
+
+int
+setTuneDevice (void) {
+  unsigned char device = prefs.tuneDevice;
+
+  if (!tuneSetDevice(device)) {
+    logMessage(LOG_ERR, "unsupported tune device: %s", getTuneDeviceName(device));
+    return 0;
+  }
+
+  return 1;
+}
+
+int
+parseTuneVolume (const char *setting) {
+  if (setting && *setting) {
+    static const int minimum = 0;
+    static const int maximum = 100;
+    int volume;
+
+    if (!validateInteger(&volume, setting, &minimum, &maximum)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid volume percentage", setting);
+      return 0;
+    }
+
+    switch (prefs.tuneDevice) {
+      case tdPcm:
+        prefs.pcmVolume = volume;
+        break;
+
+      case tdMidi:
+        prefs.midiVolume = volume;
+        break;
+
+      case tdFm:
+        prefs.fmVolume = volume;
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  return 1;
+}
+
+#ifdef HAVE_MIDI_SUPPORT
+static int
+validateMidiInstrument (unsigned char *value, const char *string) {
+  size_t stringLength = strlen(string);
+  unsigned char instrument;
+  for (instrument=0; instrument<midiInstrumentCount; ++instrument) {
+    const char *component = midiInstrumentTable[instrument];
+    size_t componentLeft = strlen(component);
+    const char *word = string;
+    size_t wordLeft = stringLength;
+    {
+      const char *delimiter = memchr(component, '(', componentLeft);
+      if (delimiter) componentLeft = delimiter - component;
+    }
+    while (1) {
+      while (*component == ' ') component++, componentLeft--;
+      if ((componentLeft == 0) != (wordLeft == 0)) break; 
+      if (!componentLeft) {
+        *value = instrument;
+        return 1;
+      }
+      {
+        size_t wordLength = wordLeft;
+        size_t componentLength = componentLeft;
+        const char *delimiter;
+        if ((delimiter = memchr(word, '-', wordLeft))) wordLength = delimiter - word;
+        if ((delimiter = memchr(component, ' ', componentLeft))) componentLength = delimiter - component;
+        if (strncasecmp(word, component, wordLength) != 0) break;
+        word += wordLength; wordLeft -= wordLength;
+        if (*word) word++, wordLeft--;
+        component += componentLength; componentLeft -= componentLength;
+      }
+    }
+  }
+  return 0;
+}
+
+int
+parseTuneInstrument (const char *setting) {
+  if (setting && *setting) {
+    unsigned char instrument;
+
+    if (!validateMidiInstrument(&instrument, setting)) {
+      logMessage(LOG_ERR, "%s: %s", "invalid musical instrument", setting);
+      return 0;
+    }
+
+    prefs.midiInstrument = instrument;
+  }
+
+  return 1;
+}
+#endif /* HAVE_MIDI_SUPPORT */
diff --git a/Programs/unicode.c b/Programs/unicode.c
new file mode 100644
index 0000000..20dc1c3
--- /dev/null
+++ b/Programs/unicode.c
@@ -0,0 +1,400 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "unicode.h"
+#include "ascii.h"
+
+#ifdef HAVE_ICU
+#include <unicode/uversion.h>
+#include <unicode/uchar.h>
+
+#ifdef HAVE_UNICODE_UNORM2_H
+#include <unicode/unorm2.h>
+#else /* unorm */
+#include <unicode/unorm.h>
+#endif /* unorm */
+
+static int
+isUcharCompatible (wchar_t character) {
+  UChar uc = character;
+  return uc == character;
+}
+
+static int
+getName (wchar_t character, char *buffer, size_t size, UCharNameChoice choice) {
+  UErrorCode error = U_ZERO_ERROR;
+  u_charName(character, choice, buffer, size, &error);
+  return U_SUCCESS(error) && *buffer;
+}
+
+static int
+getByName (wchar_t *character, const char *name, UCharNameChoice choice) {
+  UErrorCode error = U_ZERO_ERROR;
+  UChar uc = u_charFromName(choice, name, &error);
+  if (!U_SUCCESS(error)) return 0;
+
+  *character = uc;
+  return 1;
+}
+
+static int
+nextBaseCharacter (const UChar **current, const UChar *end) {
+  do {
+    if (*current == end) return 0;
+  } while (u_getCombiningClass(*(*current)++));
+
+  return 1;
+}
+#endif /* HAVE_ICU */
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+#endif /* HAVE_ICONV_H */
+
+int
+getCharacterName (wchar_t character, char *buffer, size_t size) {
+#ifdef HAVE_ICU
+  return getName(character, buffer, size, U_EXTENDED_CHAR_NAME);
+#else /* HAVE_ICU */
+  return 0;
+#endif /* HAVE_ICU */
+}
+
+int
+getCharacterByName (wchar_t *character, const char *name) {
+#ifdef HAVE_ICU
+  return getByName(character, name, U_EXTENDED_CHAR_NAME);
+#else /* HAVE_ICU */
+  return 0;
+#endif /* HAVE_ICU */
+}
+
+int
+getCharacterAlias (wchar_t character, char *buffer, size_t size) {
+#ifdef HAVE_ICU
+  return getName(character, buffer, size, U_CHAR_NAME_ALIAS);
+#else /* HAVE_ICU */
+  return 0;
+#endif /* HAVE_ICU */
+}
+
+int
+getCharacterByAlias (wchar_t *character, const char *alias) {
+#ifdef HAVE_ICU
+  return getByName(character, alias, U_CHAR_NAME_ALIAS);
+#else /* HAVE_ICU */
+  return 0;
+#endif /* HAVE_ICU */
+}
+
+int
+getCharacterWidth (wchar_t character) {
+#if defined(HAVE_WCWIDTH)
+  return wcwidth(character);
+#elif defined(HAVE_ICU)
+  UCharCategory category = u_getIntPropertyValue(character, UCHAR_GENERAL_CATEGORY);
+  UEastAsianWidth width = u_getIntPropertyValue(character, UCHAR_EAST_ASIAN_WIDTH);
+
+  if (character == 0) return 0;
+  if (category == U_CONTROL_CHAR) return -1;
+
+  if (category == U_NON_SPACING_MARK) return 0;
+  if (category == U_ENCLOSING_MARK) return 0;
+
+  /* Hangul Jamo medial vowels and final consonants */
+  if ((character >= 0X1160) && (character <= 0X11FF) && (category == U_OTHER_LETTER)) return 0;
+
+  /*  */
+  if (character == 0XAD) return 1; /* soft hyphen */
+  if (category == U_FORMAT_CHAR) return 0;
+
+  if (width == U_EA_FULLWIDTH) return 2;
+  if (width == U_EA_HALFWIDTH) return 1;
+
+  if (width == U_EA_WIDE) return 2;
+  if (width == U_EA_NARROW) return 1;
+
+  if (width == U_EA_AMBIGUOUS) {
+    /* CJK Unified Ideographs block */
+    if ((character >= 0X4E00) && (character <= 0X9FFF)) return 2;
+
+    /* CJK Unified Ideographs Externsion A block */
+    if ((character >= 0X3400) && (character <= 0X4DBF)) return 2;
+
+    /* CJK Compatibility Ideographs block */
+    if ((character >= 0XF900) && (character <= 0XFAFF)) return 2;
+
+    /* Supplementary Ideographic Plane */
+  //if ((character >= 0X20000) && (character <= 0X2FFFF)) return 2;
+
+    /* Tertiary Ideographic Plane */
+  //if ((character >= 0X30000) && (character <= 0X3FFFF)) return 2;
+  }
+
+  if (category == U_UNASSIGNED) return -1;
+  return 1;
+#else /* character width */
+  if (character == ASCII_NUL) return 0;
+  if (character == ASCII_DEL) return -1;
+  if (!(character & 0X60)) return -1;
+  return 1;
+#endif /* character width */
+}
+
+int
+isBrailleCharacter (wchar_t character) {
+  return (character & ~UNICODE_CELL_MASK) == UNICODE_BRAILLE_ROW;
+}
+
+int
+isIdeographicCharacter (wchar_t character) {
+#ifdef HAVE_ICU
+  if (u_hasBinaryProperty(character, UCHAR_IDEOGRAPHIC)) return 1;
+#endif /* HAVE_ICU */
+
+  return 0;
+}
+
+int
+isEmojiSequence (const wchar_t *characters, size_t count) {
+#ifdef HAVE_ICU
+  const wchar_t *character = characters;
+  const wchar_t *end = character + count;
+
+  while (character < end) {
+    #if U_ICU_VERSION_MAJOR_NUM >= 57
+    if (u_hasBinaryProperty(*character, UCHAR_EMOJI)) {
+      if (u_hasBinaryProperty(*character, UCHAR_EMOJI_PRESENTATION)) {
+        return 1;
+      }
+    }
+    #endif /* U_ICU_VERSION_MAJOR_NUM >= 57 */
+
+    character += 1;
+  }
+#endif /* HAVE_ICU */
+
+  return 0;
+}
+
+wchar_t
+getReplacementCharacter (void) {
+#ifdef HAVE_WCHAR_H
+ return UNICODE_REPLACEMENT_CHARACTER;
+#else /* HAVE_WCHAR_H */
+ return ASCII_SUB;
+#endif /* HAVE_WCHAR_H */
+}
+
+int
+composeCharacters (
+  size_t *length, const wchar_t *characters,
+  wchar_t *buffer, unsigned int *map
+) {
+#ifdef HAVE_ICU
+  if (*length < 2) return 0;
+
+  UChar source[*length];
+  UChar target[*length];
+  int32_t count;
+
+  {
+    const wchar_t *src = characters;
+    const wchar_t *end = src + *length;
+    UChar *trg = source;
+
+    while (src < end) {
+      *trg++ = *src++;
+    }
+  }
+
+  {
+    UErrorCode error = U_ZERO_ERROR;
+
+#ifdef HAVE_UNICODE_UNORM2_H
+    static const UNormalizer2 *normalizer = NULL;
+
+    if (!normalizer) {
+      normalizer = unorm2_getNFCInstance(&error);
+      if (!U_SUCCESS(error)) return 0;
+    }
+
+    count = unorm2_normalize(normalizer,
+                             source, ARRAY_COUNT(source),
+                             target, ARRAY_COUNT(target),
+                             &error);
+#else /* unorm */
+    count = unorm_normalize(source, ARRAY_COUNT(source),
+                            UNORM_NFC, 0,
+                            target, ARRAY_COUNT(target),
+                            &error);
+#endif /* unorm */
+
+    if (!U_SUCCESS(error)) return 0;
+  }
+
+  if (count == *length) {
+    if (memcmp(source, target, (*length * sizeof(source[0]))) == 0) {
+      return 0;
+    }
+  }
+
+  {
+    const UChar *src = source;
+    const UChar *srcEnd = src + ARRAY_COUNT(source);
+    const UChar *trg = target;
+    const UChar *trgEnd = target + count;
+    wchar_t *out = buffer;
+
+    while (trg < trgEnd) {
+      if (!nextBaseCharacter(&src, srcEnd)) return 0;
+      if (map) *map++ = src - source - 1;
+      *out++ = *trg++;
+    }
+
+    if (nextBaseCharacter(&src, srcEnd)) return 0;
+    if (map) *map = src - source;
+  }
+
+  *length = count;
+  return 1;
+#else /* HAVE_ICU */
+  return 0;
+#endif /* HAVE_ICU */
+}
+
+size_t
+decomposeCharacter (
+  wchar_t character, wchar_t *buffer, size_t length
+) {
+#ifdef HAVE_ICU
+  if (isUcharCompatible(character)) {
+    UChar source[1] = {character};
+    UChar target[length];
+    int32_t count;
+
+    {
+      UErrorCode error = U_ZERO_ERROR;
+
+#ifdef HAVE_UNICODE_UNORM2_H
+      static const UNormalizer2 *normalizer = NULL;
+
+      if (!normalizer) {
+        normalizer = unorm2_getNFDInstance(&error);
+        if (!U_SUCCESS(error)) return 0;
+      }
+
+      count = unorm2_normalize(normalizer,
+                               source, ARRAY_COUNT(source),
+                               target, ARRAY_COUNT(target),
+                               &error);
+#else /* unorm */
+      count = unorm_normalize(source, ARRAY_COUNT(source),
+                              UNORM_NFD, 0,
+                              target, ARRAY_COUNT(target),
+                              &error);
+#endif /* unorm */
+
+      if (!U_SUCCESS(error)) return 0;
+    }
+
+    {
+      const UChar *trg = target;
+      const UChar *end = target + count;
+      wchar_t *out = buffer;
+
+      while (trg < end) {
+        *out++ = *trg++;
+      }
+    }
+
+    return count;
+  }
+#endif /* HAVE_ICU */
+
+  return 0;
+}
+
+wchar_t
+getBaseCharacter (wchar_t character) {
+  wchar_t decomposed[0X10];
+  size_t count = decomposeCharacter(character, decomposed, sizeof(decomposed));
+  if (count) return decomposed[0];
+  return 0;
+}
+
+wchar_t
+getTransliteratedCharacter (wchar_t character) {
+#ifdef HAVE_ICONV_H
+  static iconv_t handle = NULL;
+  if (!handle) handle = iconv_open("ASCII//TRANSLIT", "WCHAR_T");
+
+  if (handle != (iconv_t)-1) {
+    char *inputAddress = (char *)&character;
+    size_t inputSize = sizeof(character);
+    size_t outputSize = 0X10;
+    char outputBuffer[outputSize];
+    char *outputAddress = outputBuffer;
+
+    if (iconv(handle, &inputAddress, &inputSize, &outputAddress, &outputSize) != (size_t)-1) {
+      if ((outputAddress - outputBuffer) == 1) {
+        wchar_t result = outputBuffer[0] & 0XFF;
+
+        if (result != character) {
+          if (result == WC_C('?')) {
+            return 0;
+          }
+        }
+
+        return result;
+      }
+    }
+  }
+#endif /* HAVE_ICONV_H */
+
+  return 0;
+}
+
+int
+handleBestCharacter (wchar_t character, CharacterHandler handleCharacter, void *data) {
+  if (isBrailleCharacter(character)) return 0;
+
+  typedef wchar_t CharacterTranslator (wchar_t character);
+  static CharacterTranslator *const characterTranslators[] = {
+    getBaseCharacter,
+    getTransliteratedCharacter,
+    NULL
+  };
+
+  CharacterTranslator *const *translateCharacter = characterTranslators;
+  while (!handleCharacter(character, data)) {
+    if (!*translateCharacter) return 0;
+
+    {
+      wchar_t alternate = (*translateCharacter++)(character);
+      if (alternate) character = alternate;
+    }
+  }
+
+  return 1;
+}
diff --git a/Programs/update.c b/Programs/update.c
new file mode 100644
index 0000000..7747da0
--- /dev/null
+++ b/Programs/update.c
@@ -0,0 +1,1407 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "parameters.h"
+#include "log.h"
+#include "alert.h"
+#include "report.h"
+#include "strfmt.h"
+#include "update.h"
+#include "async_handle.h"
+#include "async_alarm.h"
+#include "timing.h"
+#include "unicode.h"
+#include "charset.h"
+#include "ttb.h"
+#include "atb.h"
+#include "brl_dots.h"
+#include "spk.h"
+#include "scr.h"
+#include "scr_special.h"
+#include "scr_utils.h"
+#include "prefs.h"
+#include "status.h"
+#include "blink.h"
+#include "routing.h"
+#include "api_control.h"
+#include "core.h"
+
+static void
+overlayAttributesUnderline (unsigned char *cell, unsigned char attributes) {
+  unsigned char dots;
+
+  switch (attributes) {
+    case SCR_COLOUR_FG_DARK_GREY | SCR_COLOUR_BG_BLACK:
+    case SCR_COLOUR_FG_LIGHT_GREY | SCR_COLOUR_BG_BLACK:
+    case SCR_COLOUR_FG_LIGHT_GREY | SCR_COLOUR_BG_BLUE:
+    case SCR_COLOUR_FG_BLACK | SCR_COLOUR_BG_CYAN:
+      return;
+
+    case SCR_COLOUR_FG_BLACK | SCR_COLOUR_BG_LIGHT_GREY:
+      dots = BRL_DOT_7 | BRL_DOT_8;
+      break;
+
+    case SCR_COLOUR_FG_WHITE | SCR_COLOUR_BG_BLACK:
+    default:
+      dots = BRL_DOT_8;
+      break;
+  }
+
+  {
+    BlinkDescriptor *blink = &attributesUnderlineBlinkDescriptor;
+
+    requireBlinkDescriptor(blink);
+    if (isBlinkVisible(blink)) *cell |= dots;
+  }
+}
+
+static void
+readBrailleWindow (ScreenCharacter *characters, size_t count) {
+  int screenColumns = MIN(textCount, scr.cols-ses->winx);
+  int screenRows = MIN(brl.textRows, scr.rows-ses->winy);
+
+  if (prefs.wordWrap) {
+    int length = getWordWrapLength(ses->winy, ses->winx, screenColumns);
+    if (length < screenColumns) screenColumns = length;
+  }
+
+  if (screenColumns > 0) {
+    readScreen(ses->winx, ses->winy, screenColumns, screenRows, characters);
+  }
+
+  if (screenColumns < textCount) {
+    /* We got a rectangular piece of text with readScreen but the display
+     * is in an off-right position with some cells at the end blank
+     * so we'll insert these cells and blank them.
+     */
+
+    {
+      int lastRow = screenRows - 1;
+      const ScreenCharacter *source = characters + (lastRow * screenColumns);
+      ScreenCharacter *target = characters + (lastRow * textCount);
+      size_t size = screenColumns * sizeof(*target);
+
+      while (source > characters) {
+        memmove(target, source, size);
+        source -= screenColumns;
+        target -= textCount;
+      }
+    }
+
+    {
+      ScreenCharacter *row = characters + screenColumns;
+      const ScreenCharacter *end = characters + (screenRows * textCount);
+      size_t count = textCount - screenColumns;
+
+      while (row < end) {
+        clearScreenCharacters(row, count);
+        row += textCount;
+      }
+    }
+  }
+
+  if (screenRows < brl.textRows) {
+    clearScreenCharacters(
+      characters + (screenRows * textCount),
+      (brl.textRows - screenRows) * textCount
+    );
+  }
+}
+
+typedef void ScreenCharacterTranslator (
+  const ScreenCharacter *character, unsigned char *cell, wchar_t *text
+);
+
+static void
+translateScreenCharacter_text (
+  const ScreenCharacter *character, unsigned char *cell, wchar_t *text
+) {
+  *cell = convertCharacterToDots(textTable, character->text);
+  *text = character->text;
+
+  {
+    const unsigned char dots = BRL_DOT_7 | BRL_DOT_8;
+
+    if (*cell & dots) {
+      if (isSixDotComputerBraille()) {
+        *cell &= ~dots;
+      }
+    }
+  }
+
+  if (prefs.showAttributes) {
+    overlayAttributesUnderline(cell, character->attributes);
+  }
+
+  {
+    BlinkDescriptor *blink = &uppercaseLettersBlinkDescriptor;
+
+    if (isBlinkEnabled(blink)) {
+      if (iswupper(character->text)) {
+        requireBlinkDescriptor(blink);
+        if (!isBlinkVisible(blink)) *cell = 0;
+      }
+    }
+  }
+}
+
+static void
+translateScreenCharacter_attributes (
+  const ScreenCharacter *character, unsigned char *cell, wchar_t *text
+) {
+  *text = UNICODE_BRAILLE_ROW | (*cell = convertAttributesToDots(attributesTable, character->attributes));
+}
+
+static void
+translateBrailleWindow (
+  const ScreenCharacter *characters, wchar_t *textBuffer
+) {
+  ScreenCharacterTranslator *translateScreenCharacter =
+    ses->displayMode?
+    translateScreenCharacter_attributes:
+    translateScreenCharacter_text;
+
+  for (unsigned int row=0; row<brl.textRows; row+=1) {
+    const ScreenCharacter *character = &characters[row * textCount];
+    const ScreenCharacter *end = character + textCount;
+
+    unsigned int start = (row * brl.textColumns) + textStart;
+    unsigned char *cell = &brl.buffer[start];
+    wchar_t *text = &textBuffer[start];
+
+    while (character < end) {
+      translateScreenCharacter(character++, cell++, text++);
+    }
+  }
+}
+
+int isContracted = 0;
+int contractedTrack = 0;
+
+static void
+constructContractionCache (ContractionCache *cache) {
+  cache->input.characters = NULL;
+  cache->input.size = 0;
+  cache->input.count = 0;
+
+  cache->output.cells = NULL;
+  cache->output.size = 0;
+  cache->output.count = 0;
+
+  cache->offsets.array = NULL;
+  cache->offsets.size = 0;
+  cache->offsets.count = 0;
+}
+
+static void
+constructBrailleRowDescriptor (BrailleRowDescriptor *brd) {
+  constructContractionCache(&brd->contracted.cache);
+  brd->contracted.length = 0;
+  brd->contracted.offsets.array = NULL;
+  brd->contracted.offsets.size = 0;
+}
+
+BrailleRowDescriptor *
+getBrailleRowDescriptor (unsigned int row) {
+  if (row >= brl.rowDescriptors.size) {
+    size_t newSize = row + 1;
+
+    BrailleRowDescriptor *newArray = realloc(
+      brl.rowDescriptors.array,
+      ARRAY_SIZE(brl.rowDescriptors.array, newSize)
+    );
+
+    if (!newArray) {
+      logMallocError();
+      return NULL;
+    }
+
+    while (brl.rowDescriptors.size < newSize) {
+      BrailleRowDescriptor *brd = &newArray[brl.rowDescriptors.size++];
+      constructBrailleRowDescriptor(brd);
+    }
+
+    brl.rowDescriptors.array = newArray;
+  }
+
+  return &brl.rowDescriptors.array[row];
+}
+
+static int
+ensureContractedOffsetsSize (BrailleRowDescriptor *brd, size_t size) {
+  if (++size > brd->contracted.offsets.size) {
+    size_t newSize = 1;
+    while (newSize < size) newSize <<= 1;
+
+    int *newArrayets = realloc(
+      brd->contracted.offsets.array,
+      ARRAY_SIZE(brd->contracted.offsets.array, newSize)
+    );
+
+    if (!newArrayets) {
+      logMallocError();
+      return 0;
+    }
+
+    brd->contracted.offsets.array = newArrayets;
+    brd->contracted.offsets.size = newSize;
+  }
+
+  return 1;
+}
+
+int
+getCursorOffsetForContracting (void) {
+  if (scr.posy != ses->winy) return CTB_NO_CURSOR;
+  if (scr.posx < ses->winx) return CTB_NO_CURSOR;
+  return scr.posx - ses->winx;
+}
+
+static int
+contractScreenRow (BrailleRowDescriptor *brd, unsigned int screenRow, unsigned char *cells, unsigned int cellCount) {
+  int isCursorRow = scr.posy == ses->winy;
+
+  int inputLength = scr.cols - ses->winx;
+  wchar_t inputText[inputLength];
+  int outputLength = cellCount;
+
+  ScreenCharacter inputCharacters[inputLength];
+  readScreen(ses->winx, screenRow, inputLength, 1, inputCharacters);
+
+  for (int i=0; i<inputLength; i+=1) {
+    inputText[i] = inputCharacters[i].text;
+  }
+
+  ensureContractedOffsetsSize(brd, inputLength);
+  int *offsetsArray = brd->contracted.offsets.array;
+
+  contractText(
+    contractionTable, &brd->contracted.cache,
+    inputText, &inputLength,
+    cells, &outputLength,
+    offsetsArray, getCursorOffsetForContracting()
+  );
+
+  {
+    int inputEnd = inputLength;
+
+    if (contractedTrack && isCursorRow) {
+      if (outputLength == cellCount) {
+        int inputIndex = inputEnd;
+
+        while (inputIndex) {
+          int offset = offsetsArray[--inputIndex];
+
+          if (offset != CTB_NO_OFFSET) {
+            if (offset != outputLength) break;
+            inputEnd = inputIndex;
+          }
+        }
+      }
+
+      if (scr.posx >= (ses->winx + inputEnd)) {
+        int offset = 0;
+        int length = scr.cols - ses->winx;
+        int onSpace = 0;
+
+        while (offset < length) {
+          if ((iswspace(inputCharacters[offset].text) != 0) != onSpace) {
+            if (onSpace) break;
+            onSpace = 1;
+          }
+
+          offset += 1;
+        }
+
+        if ((offset += ses->winx) > scr.posx) {
+          ses->winx = scr.posx;
+        } else {
+          ses->winx = offset;
+        }
+
+        return 0;
+      }
+    }
+  }
+
+  if (ses->displayMode || prefs.showAttributes) {
+    int outputOffset = 0;
+    unsigned char attributes = 0;
+    unsigned char attributesBuffer[outputLength];
+
+    for (int inputOffset=0; inputOffset<inputLength; inputOffset+=1) {
+      int offset = offsetsArray[inputOffset];
+
+      if (offset != CTB_NO_OFFSET) {
+        while (outputOffset < offset) attributesBuffer[outputOffset++] = attributes;
+        attributes = 0;
+      }
+
+      attributes |= inputCharacters[inputOffset].attributes;
+    }
+
+    while (outputOffset < outputLength) {
+      attributesBuffer[outputOffset++] = attributes;
+    }
+
+    if (ses->displayMode) {
+      for (unsigned int i=0; i<outputLength; i+=1) {
+        cells[i] = convertAttributesToDots(attributesTable, attributesBuffer[i]);
+      }
+    } else {
+      for (unsigned int i=0; i<outputLength; i+=1) {
+        overlayAttributesUnderline(&cells[i], attributesBuffer[i]);
+      }
+    }
+  }
+
+  brd->contracted.length = inputLength;
+  return 1;
+}
+
+static int
+generateContractedBraille (wchar_t *text) {
+  unsigned int brailleRow = 0;
+  unsigned char *cells = &brl.buffer[textStart];
+  text += textStart;
+
+  while (brailleRow < brl.textRows) {
+    unsigned int screenRow = brailleRow + ses->winy;
+    if (screenRow >= scr.rows) break;
+
+    BrailleRowDescriptor *brd = getBrailleRowDescriptor(brailleRow);
+    if (!contractScreenRow(brd, screenRow, cells, textCount)) return 0;
+
+    for (unsigned int i=0; i<textCount; i+=1) {
+      text[i] = UNICODE_BRAILLE_ROW | cells[i];
+    }
+
+    cells += brl.textColumns;
+    text += brl.textColumns;
+    brailleRow += 1;
+  }
+
+  while (brailleRow < brl.textRows) {
+    memset(cells, 0, textCount);
+    wmemset(text, WC_C(' '), textCount);
+
+    cells += brl.textColumns;
+    text += brl.textColumns;
+    brailleRow += 1;
+  }
+
+  return 1;
+}
+
+static int
+checkScreenPointer (void) {
+  int moved = 0;
+  int column, row;
+
+  if (prefs.trackScreenPointer && getScreenPointer(&column, &row)) {
+    if (column != ses->ptrx) {
+      if (ses->ptrx >= 0) moved = 1;
+      ses->ptrx = column;
+    }
+
+    if (row != ses->ptry) {
+      if (ses->ptry >= 0) moved = 1;
+      ses->ptry = row;
+    }
+
+    if (moved) {
+      if (column < ses->winx) {
+        ses->winx = column;
+      } else if (column >= (int)(ses->winx + textCount)) {
+        ses->winx = column + 1 - textCount;
+      }
+
+      if (row < ses->winy) {
+        ses->winy = row;
+      } else if (row >= (int)(ses->winy + brl.textRows)) {
+        ses->winy = row + 1 - brl.textRows;
+      }
+    }
+  } else {
+    ses->ptrx = ses->ptry = -1;
+  }
+
+  return moved;
+}
+
+static void
+highlightBrailleWindowLocation (void) {
+  if (prefs.highlightBrailleWindowLocation) {
+    int left = ses->winx;
+    int right = left;
+
+    int top = ses->winy;
+    int bottom = top;
+
+    if (!prefs.showAttributes) {
+      if ((right += textCount) > scr.cols) right = scr.cols;
+      right -= 1;
+
+      if ((bottom += brl.textRows) > scr.rows) bottom = scr.rows;
+      bottom -= 1;
+    }
+
+    highlightScreenRegion(left, right, top, bottom);
+  }
+}
+
+static const unsigned char cursorStyles[] = {
+  [csBottomDots] = (BRL_DOT_7 | BRL_DOT_8),
+  [csAllDots] = BRL_DOTS_ALL,
+  [csLowerLeftDot] = (BRL_DOT_7),
+  [csLowerRightDot] = (BRL_DOT_8),
+  [csNoDots] = (0)
+};
+
+unsigned char
+getCursorDots (const unsigned char *setting) {
+  if (*setting >= ARRAY_COUNT(cursorStyles)) return 0;
+  return cursorStyles[*setting];
+}
+
+int
+setCursorDots (unsigned char *setting, unsigned char dots) {
+  for (unsigned char style=0; style<ARRAY_COUNT(cursorStyles); style+=1) {
+    if (dots == cursorStyles[style]) {
+      *setting = style;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+unsigned char
+getScreenCursorDots (void) {
+  return getCursorDots(&prefs.screenCursorStyle);
+}
+
+int
+setScreenCursorDots (unsigned char dots) {
+  return setCursorDots(&prefs.screenCursorStyle, dots);
+}
+
+unsigned char
+getSpeechCursorDots (void) {
+  return getCursorDots(&prefs.speechCursorStyle);
+}
+
+int
+setSpeechCursorDots (unsigned char dots) {
+  return setCursorDots(&prefs.speechCursorStyle, dots);
+}
+
+unsigned char
+mapCursorDots (unsigned char dots) {
+  if (!hasEightDotCells(&brl)) {
+    brlRemapDot(&dots, BRL_DOT_7, BRL_DOT_3);
+    brlRemapDot(&dots, BRL_DOT_8, BRL_DOT_6);
+  }
+
+  return dots;
+}
+
+static int
+getScreenCursorPosition (int x, int y) {
+  if (y < ses->winy) return BRL_NO_CURSOR;
+  if (y >= scr.rows) return BRL_NO_CURSOR;
+  if (y >= (int)(ses->winy + brl.textRows)) return BRL_NO_CURSOR;
+
+  if (x < ses->winx) return BRL_NO_CURSOR;
+  if (x >= scr.cols) return BRL_NO_CURSOR;
+
+  int rowIndex = y - ses->winy;
+  int rowPosition = (rowIndex * brl.textColumns) + textStart;
+
+  if (isContracted) {
+    BrailleRowDescriptor *brd = getBrailleRowDescriptor(rowIndex);
+    if (!brd) return BRL_NO_CURSOR;
+
+    int *offsets = brd->contracted.offsets.array;
+    if (!offsets) return BRL_NO_CURSOR;
+
+    x -= ses->winx;
+    if (x >= brd->contracted.length) return BRL_NO_CURSOR;
+
+    while (x >= 0) {
+      int offset = offsets[x];
+
+      if (offset != CTB_NO_OFFSET) {
+        if (offset < textCount) return rowPosition + offset;
+        break;
+      }
+
+      x -= 1;
+    }
+  } else if (x < (int)(ses->winx + textCount)) {
+    return rowPosition + (x - ses->winx);
+  }
+
+  return BRL_NO_CURSOR;
+}
+
+static int
+writeStatusCells (void) {
+  if (braille->writeStatus) {
+    const unsigned char *fields = prefs.statusFields;
+    unsigned int length = getStatusFieldsLength(fields);
+
+    if (length > 0) {
+      unsigned int count = brl.statusColumns * brl.statusRows;
+      if (count < length) count = length;
+      unsigned char cells[count];
+
+      memset(cells, 0, count);
+      renderStatusFields(fields, cells);
+      if (!braille->writeStatus(&brl, cells)) return 0;
+    } else if (!clearStatusCells(&brl)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static inline char
+getScreenCursorTrackingCharacter (void) {
+  return ses->trackScreenCursor? 't': ' ';
+}
+
+static inline char
+getScreenCursorVisibilityCharacter (void) {
+  return prefs.showScreenCursor? 'c': ' ';
+}
+
+static inline char
+getAttributesUnderlineVisibilityCharacter (void) {
+  return prefs.showAttributes? 'u': ' ';
+}
+
+static inline char
+getSpecialScreenCharacter (void) {
+  if (isSpecialScreen(SCR_FROZEN)) return 'f';
+  if (isSpecialScreen(SCR_HELP)) return 'h';
+  if (isSpecialScreen(SCR_MENU)) return 'm';
+  return ' ';
+}
+
+static inline char
+getBrailleVariantCharacter (void) {
+  return ses->displayMode? 'a':
+         isContractedBraille()? 'c':
+         isSixDotComputerBraille()? '6': '8';
+}
+
+static inline char
+getBrailleKeyboardCharacter (void) {
+  if (!prefs.brailleKeyboardEnabled) return 'd';
+  if (prefs.brailleTypingMode) return 'b';
+  return ' ';
+}
+
+static inline char
+getSpeechCursorVisibilityCharacter (void) {
+  return prefs.showSpeechCursor? 's': ' ';
+}
+
+static int
+renderInfoLine (void) {
+  brl.cursor = BRL_NO_CURSOR;
+
+  static const char mode[] = "info";
+  if (!setStatusText(&brl, mode)) return 0;
+
+  /* We must be careful. Some displays (e.g. Braille Lite 18)
+   * are very small, and others (e.g. Bookworm) are even smaller.
+   * Also, some displays (e.g. Braille Me) have only six dots per cell.
+   */
+  const size_t size = brl.textColumns * brl.textRows;
+  int compact = (size < 22) && hasEightDotCells(&brl);
+
+  static const unsigned char compactFields[] = {
+    sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots, sfEnd
+  };
+
+  static unsigned int compactLength = 0;
+  if (!compactLength) compactLength = getStatusFieldsLength(compactFields);
+  unsigned char compactCells[compactLength];
+
+  size_t length;
+  char text[size + 1];
+  STR_BEGIN(text, sizeof(text));
+
+  if (compact) {
+    memset(compactCells, 0, compactLength);
+    renderStatusFields(compactFields, compactCells);
+
+    {
+      unsigned int counter = compactLength;
+      while (counter--) STR_PRINTF("x");
+    }
+  } else {
+    STR_PRINTF(
+      "%02d:%02d %02d:%02d",
+      SCR_COLUMN_NUMBER(ses->winx), SCR_ROW_NUMBER(ses->winy),
+      SCR_COLUMN_NUMBER(scr.posx), SCR_ROW_NUMBER(scr.posy)
+    );
+  }
+
+  STR_PRINTF(
+    " %02d %c%c%c%c%c%c%c",
+    scr.number, 
+    getScreenCursorTrackingCharacter(),
+    getScreenCursorVisibilityCharacter(),
+    getAttributesUnderlineVisibilityCharacter(),
+    getSpeechCursorVisibilityCharacter(),
+    getSpecialScreenCharacter(),
+    getBrailleVariantCharacter(),
+    getBrailleKeyboardCharacter()
+  );
+
+  if ((STR_LENGTH + 6) <= size) {
+    TimeFormattingData fmt;
+    getTimeFormattingData(&fmt);
+
+    STR_PRINTF(" ");
+    STR_FORMAT(formatBrailleTime, &fmt);
+
+    if (prefs.showSeconds) {
+      scheduleUpdateIn("info clock second", millisecondsTillNextSecond(&fmt.value));
+    } else {
+      scheduleUpdateIn("info clock minute", millisecondsTillNextMinute(&fmt.value));
+    }
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+
+  if (length > size) length = size;
+  wchar_t characters[length];
+
+  {
+    unsigned int threshold = compact? compactLength: 0;
+
+    for (unsigned int i=0; i<length; i+=1) {
+      wint_t character;
+
+      if (i < threshold) {
+        character = UNICODE_BRAILLE_ROW | compactCells[i];
+      } else {
+        character = convertCharToWchar(text[i]);
+        if (character == WEOF) character = WC_C('?');
+      }
+
+      characters[i] = character;
+    }
+  }
+
+  return writeBrailleCharacters(mode, characters, length);
+}
+
+static int
+saveScreenCharacters (
+  ScreenCharacter **buffer, size_t *size,
+  const ScreenCharacter *characters, size_t count
+) {
+  size_t newSize = count * sizeof(*characters);
+
+  if (newSize > *size) {
+    ScreenCharacter *newBuffer = malloc(newSize);
+
+    if (!newBuffer) {
+      logMallocError();
+      return 0;
+    }
+
+    if (*buffer) free(*buffer);
+    *buffer = newBuffer;
+    *size = newSize;
+  }
+
+  memcpy(*buffer, characters, newSize);
+  return 1;
+}
+
+static void
+checkScreenScroll (int track) {
+  const int rowCount = 3;
+
+  static int oldScreen = -1;
+  static int oldRow = -1;
+  static int oldWidth = 0;
+  static size_t oldSize = 0;
+  static ScreenCharacter *oldCharacters = NULL;
+
+  int newScreen = scr.number;
+  int newWidth = scr.cols;
+  size_t newCount = newWidth * rowCount;
+  ScreenCharacter newCharacters[newCount];
+
+  int newRow = ses->winy;
+  int newTop = newRow - (rowCount - 1);
+
+  if (newTop < 0) {
+    newCount = 0;
+  } else {
+    readScreenRows(newTop, newWidth, rowCount, newCharacters);
+
+    if (track && prefs.trackScreenScroll && oldCharacters &&
+        (newScreen == oldScreen) && (newWidth == oldWidth) &&
+        (newRow == oldRow)) {
+      while (newTop > 0) {
+        if ((scr.posy >= newTop) && (scr.posy <= newRow)) break;
+
+        if (isSameRow(oldCharacters, newCharacters, newCount, isSameCharacter)) {
+          if (newRow != ses->winy) {
+            ses->winy = newRow;
+            alert(ALERT_SCROLL_UP);
+          }
+
+          break;
+        }
+
+        readScreenRows(--newTop, newWidth, rowCount, newCharacters);
+        newRow -= 1;
+      }
+    }
+  }
+
+  if (saveScreenCharacters(&oldCharacters, &oldSize, newCharacters, newCount)) {
+    oldScreen = newScreen;
+    oldRow = ses->winy;
+    oldWidth = newWidth;
+  }
+}
+
+static int oldwinx;
+static int oldwiny;
+
+#ifdef ENABLE_SPEECH_SUPPORT
+static int wasAutospeaking;
+
+void
+autospeak (AutospeakMode mode) {
+  static int oldScreen = -1;
+  static int oldX = -1;
+  static int oldY = -1;
+  static int oldWidth = 0;
+  static ScreenCharacter *oldCharacters = NULL;
+  static size_t oldSize = 0;
+  static int cursorAssumedStable = 0;
+
+  int newScreen = scr.number;
+  int newX = scr.posx;
+  int newY = scr.posy;
+  int newWidth = scr.cols;
+  ScreenCharacter newCharacters[newWidth];
+
+  readScreenRow(ses->winy, newWidth, newCharacters);
+
+  if (!spk.track.isActive) {
+    const ScreenCharacter *characters = newCharacters;
+    int column = 0;
+    int count = newWidth;
+    const char *reason = NULL;
+    int indent = 0;
+
+    if (mode == AUTOSPEAK_FORCE) {
+      reason = "current line";
+    } else if (!oldCharacters) {
+      reason = "initial line";
+      count = 0;
+    } else if ((newScreen != oldScreen) || (ses->winy != oldwiny) || (newWidth != oldWidth)) {
+      if (!prefs.autospeakSelectedLine) count = 0;
+      reason = "line selected";
+      if (prefs.autospeakLineIndent) indent = 1;
+    } else {
+      int onScreen = (newX >= 0) && (newX < newWidth);
+
+      if (!isSameRow(newCharacters, oldCharacters, newWidth, isSameText)) {
+        if ((newY == ses->winy) && (newY == oldY) && onScreen) {
+          /* Sometimes the cursor moves after the screen content has been
+           * updated. Make sure we don't race ahead of such a cursor move
+           * before assuming that it is actually stable.
+           */
+	  if ((newX == oldX) && !cursorAssumedStable) {
+	    scheduleUpdate("autospeak cursor stability check");
+	    cursorAssumedStable = 1;
+	    return;
+	  }
+
+          if ((newX == oldX) &&
+              isSameRow(newCharacters, oldCharacters, newX, isSameText)) {
+            int oldLength = oldWidth;
+            int newLength = newWidth;
+            int x = newX;
+
+            while (oldLength > oldX) {
+              if (!iswspace(oldCharacters[oldLength-1].text)) break;
+              oldLength -= 1;
+            }
+            if (oldLength < oldWidth) oldLength += 1;
+
+            while (newLength > newX) {
+              if (!iswspace(newCharacters[newLength-1].text)) break;
+              newLength -= 1;
+            }
+            if (newLength < newWidth) newLength += 1;
+
+            while (1) {
+              int done = 1;
+
+              if (x < newLength) {
+                if (isSameRow(newCharacters+x, oldCharacters+oldX, newWidth-x, isSameText)) {
+                  column = newX;
+                  count = prefs.autospeakInsertedCharacters? (x - newX): 0;
+                  reason = "characters inserted after cursor";
+                  goto autospeak;
+                }
+
+                done = 0;
+              }
+
+              if (x < oldLength) {
+                if (isSameRow(newCharacters+newX, oldCharacters+x, oldWidth-x, isSameText)) {
+                  characters = oldCharacters;
+                  column = oldX;
+                  count = prefs.autospeakDeletedCharacters? (x - oldX): 0;
+                  reason = "characters deleted after cursor";
+                  goto autospeak;
+                }
+
+                done = 0;
+              }
+
+              if (done) break;
+              x += 1;
+            }
+          }
+
+          if (oldX < 0) oldX = 0;
+          if ((newX > oldX) &&
+              isSameRow(newCharacters, oldCharacters, oldX, isSameText) &&
+              isSameRow(newCharacters+newX, oldCharacters+oldX, newWidth-newX, isSameText)) {
+            column = oldX;
+            count = newX - oldX;
+
+            if (prefs.autospeakCompletedWords) {
+              int last = column + count - 1;
+
+              if (iswspace(characters[last].text)) {
+                int first = column;
+
+                while (first > 0) {
+                  if (iswspace(characters[--first].text)) {
+                    first += 1;
+                    break;
+                  }
+                }
+
+                if (first < column) {
+                  while (last >= first) {
+                    if (!iswspace(characters[last].text)) break;
+                    last -= 1;
+                  }
+
+                  if (++last > first) {
+                    column = first;
+                    count = (last + 1) - first;
+                    reason = "word inserted";
+                    goto autospeak;
+                  }
+                }
+              }
+            }
+
+            if (!prefs.autospeakInsertedCharacters) count = 0;
+            reason = "characters inserted before cursor";
+            goto autospeak;
+          }
+
+          if (oldX >= oldWidth) oldX = oldWidth - 1;
+          if ((newX < oldX) &&
+              isSameRow(newCharacters, oldCharacters, newX, isSameText) &&
+              isSameRow(newCharacters+newX, oldCharacters+oldX, oldWidth-oldX, isSameText)) {
+            characters = oldCharacters;
+            column = newX;
+            count = prefs.autospeakDeletedCharacters? (oldX - newX): 0;
+            reason = "characters deleted before cursor";
+            goto autospeak;
+          }
+        }
+
+        while (newCharacters[column].text == oldCharacters[column].text) ++column;
+        while (newCharacters[count-1].text == oldCharacters[count-1].text) --count;
+        count -= column;
+        if (!prefs.autospeakReplacedCharacters) count = 0;
+        reason = "characters replaced";
+      } else if ((newY == ses->winy) && ((newX != oldX) || (newY != oldY)) && onScreen) {
+        column = newX;
+        count = prefs.autospeakSelectedCharacter? 1: 0;
+        reason = "character selected";
+
+        if (prefs.autospeakCompletedWords) {
+          if ((newX > oldX) && (column >= 2)) {
+            int length = newWidth;
+
+            while (length > 0) {
+              if (!iswspace(characters[--length].text)) {
+                length += 1;
+                break;
+              }
+            }
+
+            if ((length + 1) == column) {
+              int first = length - 1;
+
+              while (first > 0) {
+                if (iswspace(characters[--first].text)) {
+                  first += 1;
+                  break;
+                }
+              }
+
+              if ((length -= first) > 0) {
+                column = first;
+                count = length + 1;
+                reason = "word appended";
+                goto autospeak;
+              }
+            }
+          }
+        }
+      } else {
+        count = 0;
+      }
+    }
+
+  autospeak:
+    if (mode == AUTOSPEAK_SILENT) count = 0;
+
+    characters += column;
+    int interrupt = 1;
+
+    if (indent) {
+      if (speakIndent(characters, count, 0)) {
+        interrupt = 0;
+      }
+    }
+
+    if (count && (scr.quality >= autospeakMinimumScreenContentQuality)) {
+      if (!reason) reason = "unknown reason";
+
+      logMessage(LOG_CATEGORY(SPEECH_EVENTS),
+        "autospeak: %s: [%d,%d] %d.%d",
+        reason, ses->winx, ses->winy, column, count
+      );
+
+      speakCharacters(characters, count, 0, interrupt);
+    }
+  }
+
+  if (saveScreenCharacters(&oldCharacters, &oldSize, newCharacters, newWidth)) {
+    oldScreen = newScreen;
+    oldX = newX;
+    oldY = newY;
+    oldWidth = newWidth;
+    cursorAssumedStable = 0;
+  }
+}
+
+void
+suppressAutospeak (void) {
+  if (isAutospeakActive()) {
+    autospeak(AUTOSPEAK_SILENT);
+    oldwinx = ses->winx;
+    oldwiny = ses->winy;
+  }
+}
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+void
+reportBrailleWindowMoved (void) {
+  const BrailleWindowMovedReport data = {
+    .screen = {
+      .column = ses->winx,
+      .row = ses->winy
+    },
+
+    .text = {
+      .count = textCount
+    }
+  };
+
+  report(REPORT_BRAILLE_WINDOW_MOVED, &data);
+}
+
+int
+writeBrailleWindow (BrailleDisplay *brl, const wchar_t *text, unsigned char quality) {
+  {
+    const BrailleWindowUpdatedReport data = {
+      .cells = &brl->buffer[textStart],
+      .count = textCount
+    };
+
+    report(REPORT_BRAILLE_WINDOW_UPDATED, &data);
+  }
+
+  brl->quality = quality;
+  return braille->writeWindow(brl, text);
+}
+
+static void
+doUpdate (void) {
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "starting");
+  unrequireAllBlinkDescriptors();
+  refreshScreen();
+  updateSessionAttributes();
+  api.flushOutput();
+
+  if (scr.unreadable) {
+    logMessage(LOG_CATEGORY(UPDATE_EVENTS), "screen unreadable: %s", scr.unreadable);
+  } else {
+    logMessage(LOG_CATEGORY(UPDATE_EVENTS), "screen: #%d %dx%d [%d,%d]",
+               scr.number, scr.cols, scr.rows, scr.posx, scr.posy);
+  }
+
+  if (opt_releaseDevice) {
+    if (scr.unreadable) {
+      if (canBraille()) {
+        logMessage(LOG_DEBUG, "suspending braille driver");
+        writeStatusCells();
+        writeBrailleText("wrn", scr.unreadable);
+        api.suspendDriver();
+        brl.isSuspended = 1;
+        logMessage(LOG_DEBUG, "braille driver suspended");
+      }
+    } else {
+      if (brl.isSuspended) {
+        logMessage(LOG_DEBUG, "resuming braille driver");
+        forgetDevices();
+        brl.isSuspended = !api.resumeDriver();
+
+        if (brl.isSuspended) {
+          logMessage(LOG_DEBUG, "braille driver not resumed");
+        } else {
+          logMessage(LOG_DEBUG, "braille driver resumed");
+        }
+      }
+    }
+  }
+
+  int screenPointerHasMoved = 0;
+  int trackScreenScroll = 0;
+
+  if (ses->trackScreenCursor) {
+#ifdef ENABLE_SPEECH_SUPPORT
+    if (!spk.track.isActive)
+#endif /* ENABLE_SPEECH_SUPPORT */
+    {
+      /* If screen cursor moves while blinking is on */
+      if (prefs.blinkingScreenCursor) {
+        if (scr.posy != ses->trky) {
+          /* turn off cursor to see what's under it while changing lines */
+          setBlinkState(&screenCursorBlinkDescriptor, 0);
+        } else if (scr.posx != ses->trkx) {
+          /* turn on cursor to see it moving on the line */
+          setBlinkState(&screenCursorBlinkDescriptor, 1);
+        }
+      }
+
+      /* If the cursor moves in cursor tracking mode: */
+      if (!isRouting()) {
+        if ((scr.posx != ses->trkx) || (scr.posy != ses->trky)) {
+          int oldx = ses->winx;
+          int oldy = ses->winy;
+          trackScreenCursor(0);
+
+          logMessage(LOG_CATEGORY(CURSOR_TRACKING),
+                     "scr=%u csr=[%u,%u]->[%u,%u] win=[%u,%u]->[%u,%u]",
+                     scr.number,
+                     ses->trkx, ses->trky, scr.posx, scr.posy,
+                     oldx, oldy, ses->winx, ses->winy);
+
+          ses->spkx = ses->trkx = scr.posx;
+          ses->spky = ses->trky = scr.posy;
+        } else if (checkScreenPointer()) {
+          screenPointerHasMoved = 1;
+        } else {
+          trackScreenScroll = 1;
+        }
+      }
+    }
+  } else {
+    trackScreenScroll = 1;
+  }
+
+  checkScreenScroll(trackScreenScroll);
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  if (spk.canAutospeak) {
+    int isAutospeaking = isAutospeakActive();
+
+    if (isAutospeaking) {
+      autospeak(wasAutospeaking? AUTOSPEAK_CHANGES: AUTOSPEAK_FORCE);
+    } else if (wasAutospeaking) {
+      muteSpeech(&spk, "autospeak disabled");
+    }
+
+    wasAutospeaking = isAutospeaking;
+  }
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  /* There are a few things to take care of if the display has moved. */
+  if ((ses->winx != oldwinx) || (ses->winy != oldwiny)) {
+    if (!screenPointerHasMoved) highlightBrailleWindowLocation();
+
+    /* Attributes are blinking.
+     * We could check to see if we changed screen, but that doesn't
+     * really matter... this is mainly for when you are hunting up/down
+     * for the line with attributes.
+     */
+    setBlinkState(&attributesUnderlineBlinkDescriptor, 1);
+    /* problem: this still doesn't help when the braille window is
+     * stationnary and the attributes themselves are moving
+     * (example: tin).
+     */
+
+    if ((ses->spky < ses->winy) || (ses->spky >= (ses->winy + brl.textRows))) ses->spky = ses->winy;
+    if ((ses->spkx < ses->winx) || (ses->spkx >= (ses->winx + textCount))) ses->spkx = ses->winx;
+
+    oldwinx = ses->winx;
+    oldwiny = ses->winy;
+  }
+
+  if (!brl.isOffline && canBraille()) {
+    api.claimDriver();
+
+    if (infoMode) {
+      if (!renderInfoLine()) brl.hasFailed = 1;
+    } else {
+      const unsigned int windowLength = brl.textColumns * brl.textRows;
+      memset(brl.buffer, 0, windowLength);
+
+      wchar_t textBuffer[windowLength];
+      wmemset(textBuffer, WC_C(' '), windowLength);
+
+      unsigned int textLength = textCount * brl.textRows;
+      isContracted = isContracting();
+
+      if (isContracted) {
+        while (1) {
+          int generated = generateContractedBraille(textBuffer);
+          contractedTrack = 0;
+          if (generated) break;
+        }
+      } else {
+        ScreenCharacter characters[textLength];
+        readBrailleWindow(characters, ARRAY_COUNT(characters));
+        translateBrailleWindow(characters, textBuffer);
+      }
+
+      if ((brl.cursor = getScreenCursorPosition(scr.posx, scr.posy)) != BRL_NO_CURSOR) {
+        if (showScreenCursor()) {
+          BlinkDescriptor *blink = &screenCursorBlinkDescriptor;
+          requireBlinkDescriptor(blink);
+
+          if (isBlinkVisible(blink)) {
+            brl.buffer[brl.cursor] |= mapCursorDots(getScreenCursorDots());
+          }
+        }
+      }
+
+      if (prefs.showSpeechCursor) {
+        int position = getScreenCursorPosition(ses->spkx, ses->spky);
+
+        if (position != BRL_NO_CURSOR) {
+          if (position != brl.cursor) {
+            BlinkDescriptor *blink = &speechCursorBlinkDescriptor;
+            requireBlinkDescriptor(blink);
+
+            if (isBlinkVisible(blink)) {
+              brl.buffer[position] |= mapCursorDots(getSpeechCursorDots());
+            }
+          }
+        }
+      }
+
+      if (statusCount > 0) {
+        const unsigned char *fields = prefs.statusFields;
+        unsigned int length = getStatusFieldsLength(fields);
+
+        if (length > 0) {
+          unsigned char cells[length];
+          memset(cells, 0, length);
+          renderStatusFields(fields, cells);
+          fillDotsRegion(textBuffer, brl.buffer,
+                         statusStart, statusCount, brl.textColumns, brl.textRows,
+                         cells, length);
+        }
+
+        fillStatusSeparator(textBuffer, brl.buffer);
+      }
+
+      if (!(writeStatusCells() && writeBrailleWindow(&brl, textBuffer, scr.quality))) brl.hasFailed = 1;
+    }
+
+    api.releaseDriver();
+  }
+
+  resetAllBlinkDescriptors();
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "finished");
+}
+
+static void setUpdateAlarm (void);
+static AsyncHandle updateAlarm;
+static int updateSuspendCount;
+
+static TimeValue updateTime;
+static TimeValue earliestTime;
+
+static void
+enforceEarliestTime (void) {
+  if (compareTimeValues(&updateTime, &earliestTime) < 0) {
+    updateTime = earliestTime;
+  }
+}
+
+static void
+setUpdateDelay (int delay) {
+  getMonotonicTime(&earliestTime);
+  adjustTimeValue(&earliestTime, delay);
+  enforceEarliestTime();
+}
+
+static void
+setUpdateTime (int delay, const TimeValue *from, int ifEarlier) {
+  TimeValue time;
+
+  if (from) {
+    time = *from;
+  } else {
+    getMonotonicTime(&time);
+  }
+
+  adjustTimeValue(&time, delay);
+
+  if (!ifEarlier || (millisecondsBetween(&updateTime, &time) < 0)) {
+    updateTime = time;
+    enforceEarliestTime();
+  }
+}
+
+void
+scheduleUpdateIn (const char *reason, int delay) {
+  setUpdateTime(delay, NULL, 1);
+  if (updateAlarm) asyncResetAlarmTo(updateAlarm, &updateTime);
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "scheduled: %s", reason);
+}
+
+void
+scheduleUpdate (const char *reason) {
+  scheduleUpdateIn(reason, 0);
+}
+
+ASYNC_ALARM_CALLBACK(handleUpdateAlarm) {
+  asyncDiscardHandle(updateAlarm);
+  updateAlarm = NULL;
+
+  suspendUpdates();
+  setUpdateTime((pollScreen()? SCREEN_UPDATE_POLL_INTERVAL: (SECS_PER_DAY * MSECS_PER_SEC)),
+                parameters->now, 0);
+
+  {
+    int oldColumn = ses->winx;
+    int oldRow = ses->winy;
+
+    doUpdate();
+
+    if ((ses->winx != oldColumn) || (ses->winy != oldRow)) {
+      reportBrailleWindowMoved();
+    }
+  }
+
+  setUpdateDelay(MAX((brl.writeDelay + 1), UPDATE_SCHEDULE_DELAY));
+  brl.writeDelay = 0;
+
+  resumeUpdates(0);
+}
+
+static void
+setUpdateAlarm (void) {
+  if (!updateSuspendCount && !updateAlarm) {
+    asyncNewAbsoluteAlarm(&updateAlarm, &updateTime, handleUpdateAlarm, NULL);
+  }
+}
+
+static ReportListenerInstance *updateBrailleDeviceOnlineListener = NULL;
+
+REPORT_LISTENER(handleUpdateBrailleDeviceOnline) {
+  scheduleUpdate("braille online");
+}
+
+void
+beginUpdates (void) {
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "begin");
+
+  setUpdateDelay(0);
+  setUpdateTime(0, NULL, 0);
+
+  updateAlarm = NULL;
+  updateSuspendCount = 0;
+
+  oldwinx = -1;
+  oldwiny = -1;
+
+#ifdef ENABLE_SPEECH_SUPPORT
+  wasAutospeaking = 0;
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+  updateBrailleDeviceOnlineListener = registerReportListener(REPORT_BRAILLE_DEVICE_ONLINE, handleUpdateBrailleDeviceOnline, NULL);
+}
+
+void
+suspendUpdates (void) {
+  if (updateAlarm) {
+    asyncCancelRequest(updateAlarm);
+    updateAlarm = NULL;
+  }
+
+  updateSuspendCount += 1;
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "suspend: %u", updateSuspendCount);
+}
+
+void
+resumeUpdates (int refresh) {
+  if (!--updateSuspendCount) {
+    setUpdateAlarm();
+    if (refresh) scheduleUpdate("updates resumed");
+  }
+
+  logMessage(LOG_CATEGORY(UPDATE_EVENTS), "resume: %u", updateSuspendCount);
+}
diff --git a/Programs/update.h b/Programs/update.h
new file mode 100644
index 0000000..d907cdd
--- /dev/null
+++ b/Programs/update.h
@@ -0,0 +1,53 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_UPDATE
+#define BRLTTY_INCLUDED_UPDATE
+
+#include "brl_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern int writeBrailleWindow (BrailleDisplay *brl, const wchar_t *text, unsigned char quality);
+extern void reportBrailleWindowMoved (void);
+
+extern void scheduleUpdate (const char *reason);
+extern void scheduleUpdateIn (const char *reason, int delay);
+
+extern void beginUpdates (void);
+extern void suspendUpdates (void);
+extern void resumeUpdates (int refresh);
+
+#ifdef ENABLE_SPEECH_SUPPORT
+typedef enum {
+  AUTOSPEAK_SILENT,
+  AUTOSPEAK_CHANGES,
+  AUTOSPEAK_FORCE
+} AutospeakMode;
+
+extern void autospeak (AutospeakMode mode);
+extern void suppressAutospeak (void);
+#endif /* ENABLE_SPEECH_SUPPORT */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_UPDATE */
diff --git a/Programs/usb.c b/Programs/usb.c
new file mode 100644
index 0000000..e2b89aa
--- /dev/null
+++ b/Programs/usb.c
@@ -0,0 +1,1788 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif /* HAVE_REGEX_H */
+
+#include "log.h"
+#include "strfmt.h"
+#include "parameters.h"
+#include "bitfield.h"
+#include "bitmask.h"
+#include "parse.h"
+#include "file.h"
+#include "utf8.h"
+#include "device.h"
+#include "timing.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_alarm.h"
+#include "io_misc.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+#include "usb_devices.h"
+#include "usb_serial.h"
+
+ssize_t
+usbControlRead (
+  UsbDevice *device,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  return usbControlTransfer(device, UsbControlDirection_Input, recipient, type,
+                            request, value, index, buffer, length, timeout);
+}
+
+ssize_t
+usbControlWrite (
+  UsbDevice *device,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  const void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  return usbControlTransfer(device, UsbControlDirection_Output, recipient, type,
+                            request, value, index, (void *)buffer, length, timeout);
+}
+
+ssize_t
+usbGetDescriptor (
+  UsbDevice *device,
+  unsigned char type,
+  unsigned char number,
+  unsigned int index,
+  UsbDescriptor *descriptor,
+  int timeout
+) {
+  return usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
+                        UsbStandardRequest_GetDescriptor, (type << 8) | number, index,
+                        descriptor->bytes, sizeof(descriptor->bytes), timeout);
+}
+
+int
+usbGetDeviceDescriptor (
+  UsbDevice *device,
+  UsbDeviceDescriptor *descriptor
+) {
+  UsbDescriptor desc;
+  int size = usbGetDescriptor(device, UsbDescriptorType_Device, 0, 0, &desc, 1000);
+
+  if (size != -1) {
+    *descriptor = desc.device;
+  }
+
+  return size;
+}
+
+int
+usbGetLanguage (
+  UsbDevice *device,
+  uint16_t *language,
+  int timeout
+) {
+  UsbDescriptor descriptor;
+  ssize_t size = usbGetDescriptor(device, UsbDescriptorType_String,
+                              0, 0, &descriptor, timeout);
+
+  if (size != -1) {
+    if (size >= 4) {
+      *language = getLittleEndian16(descriptor.string.wData[0]);
+      logMessage(LOG_CATEGORY(USB_IO), "USB language: %02X", *language);
+      return 1;
+    } else {
+      logMessage(LOG_ERR, "USB language code string too short: %"PRIssize, size);
+      errno = EIO;
+    }
+  } else {
+    logMessage(LOG_ERR, "USB language code string read error");
+  }
+
+  return 0;
+}
+
+char *
+usbDecodeString (const UsbStringDescriptor *descriptor) {
+  size_t count = (descriptor->bLength - 2) / sizeof(descriptor->wData[0]);
+  char buffer[(count * UTF8_LEN_MAX) + 1];
+
+  const uint16_t *source = descriptor->wData;
+  const uint16_t *end = source + count;
+  char *target = buffer;
+
+  while (source < end) {
+    size_t length = convertWcharToUtf8(getLittleEndian16(*source++), target);
+
+    target += length;
+  }
+  *target = 0;
+
+  {
+    char *string = strdup(buffer);
+
+    if (!string) logMallocError();
+    return string;
+  }
+}
+
+char *
+usbGetString (
+  UsbDevice *device,
+  unsigned char number,
+  int timeout
+) {
+  UsbDescriptor descriptor;
+
+  if (!device->language) {
+    if (!usbGetLanguage(device, &device->language, timeout)) {
+      return NULL;
+    }
+  }
+
+  if (usbGetDescriptor(device, UsbDescriptorType_String,
+                       number, device->language,
+                       &descriptor, timeout) == -1) {
+    logMessage(LOG_ERR, "USB string read error: %u", number);
+    return NULL;
+  }
+
+  return usbDecodeString(&descriptor.string);
+}
+
+char *
+usbGetManufacturer (UsbDevice *device, int timeout) {
+  return usbGetString(device, device->descriptor.iManufacturer, timeout);
+}
+
+char *
+usbGetProduct (UsbDevice *device, int timeout) {
+  return usbGetString(device, device->descriptor.iProduct, timeout);
+}
+
+char *
+usbGetSerialNumber (UsbDevice *device, int timeout) {
+  return usbGetString(device, device->descriptor.iSerialNumber, timeout);
+}
+
+static size_t
+usbFormatLogSetupPacket (char *buffer, size_t size, const void *data) {
+  const UsbSetupPacket *setup = data;
+  size_t length;
+  STR_BEGIN(buffer, size);
+
+  STR_PRINTF("setup packet: Typ:%02X Req:%02X Val:%04X Idx:%04X Len:%04X",
+             setup->bRequestType, setup->bRequest,
+             getLittleEndian16(setup->wValue),
+             getLittleEndian16(setup->wIndex),
+             getLittleEndian16(setup->wLength));
+
+  length = STR_LENGTH;
+  STR_END;
+  return length;
+}
+
+void
+usbLogSetupPacket (const UsbSetupPacket *setup) {
+  logData(LOG_CATEGORY(USB_IO), usbFormatLogSetupPacket, setup);
+}
+
+void
+usbMakeSetupPacket (
+  UsbSetupPacket *setup,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  uint16_t length
+) {
+  setup->bRequestType = direction | recipient | type;
+  setup->bRequest = request;
+  putLittleEndian16(&setup->wValue, value);
+  putLittleEndian16(&setup->wIndex, index);
+  putLittleEndian16(&setup->wLength, length);
+  usbLogSetupPacket(setup);
+}
+
+void
+usbLogEndpointData (
+  UsbEndpoint *endpoint, const char *label,
+  const void *data, size_t size
+) {
+  logBytes(LOG_CATEGORY(USB_IO), "endpoint %02X %s", data, size,
+           endpoint->descriptor->bEndpointAddress, label);
+}
+
+void
+usbLogString (
+  UsbDevice *device,
+  unsigned char number,
+  const char *label
+) {
+  if (number) {
+    char *string = usbGetString(device, number, 1000);
+    if (string) {
+      logMessage(LOG_INFO, "USB: %s: %s", label, string);
+      free(string);
+    }
+  }
+}
+
+int
+usbStringEquals (const char *reference, const char *value) {
+  return strcmp(reference, value) == 0;
+}
+
+int
+usbStringMatches (const char *reference, const char *value) {
+  int ok = 0;
+
+#ifdef REG_EXTENDED
+  regex_t expression;
+
+  if (regcomp(&expression, value, REG_EXTENDED|REG_NOSUB) == 0) {
+    if (regexec(&expression, reference, 0, NULL, 0) == 0) {
+      ok = 1;
+    }
+
+    regfree(&expression);
+  }
+#endif /* REG_EXTENDED */
+
+  return ok;
+}
+
+int
+usbVerifyString (
+  UsbDevice *device,
+  UsbStringVerifier verify,
+  unsigned char index,
+  const char *value
+) {
+  int ok = 0;
+  if (!(value && *value)) return 1;
+
+  if (index) {
+    char *reference = usbGetString(device, index, 1000);
+    if (reference) {
+      if (verify(reference, value)) ok = 1;
+      free(reference);
+    }
+  }
+  return ok;
+}
+
+int
+usbVerifyManufacturerName (UsbDevice *device, const char *eRegExp) {
+  return usbVerifyString(device, usbStringMatches,
+                         device->descriptor.iManufacturer, eRegExp);
+}
+
+int
+usbVerifyProductDescription (UsbDevice *device, const char *eRegExp) {
+  return usbVerifyString(device, usbStringMatches,
+                         device->descriptor.iProduct, eRegExp);
+}
+
+int
+usbVerifySerialNumber (UsbDevice *device, const char *string) {
+  return usbVerifyString(device, usbStringEquals,
+                         device->descriptor.iSerialNumber, string);
+}
+
+int
+usbParseVendorIdentifier (uint16_t *identifier, const char *string) {
+  if (string && *string) {
+    unsigned int value;
+
+    if (isUnsignedInteger(&value, string)) {
+      if ((value > 0) && (value <= UINT16_MAX)) {
+        *identifier = value;
+        return 1;
+      }
+    }
+
+    logMessage(LOG_WARNING, "invalid USB vendor identifier: %s", string);
+    return 0;
+  }
+
+  *identifier = 0;
+  return 1;
+}
+
+int
+usbVerifyVendorIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
+  if (!identifier) return 1;
+  return identifier == getLittleEndian16(descriptor->idVendor);
+}
+
+int
+usbParseProductIdentifier (uint16_t *identifier, const char *string) {
+  if (string && *string) {
+    unsigned int value;
+
+    if (isUnsignedInteger(&value, string)) {
+      if ((value > 0) && (value <= UINT16_MAX)) {
+        *identifier = value;
+        return 1;
+      }
+    }
+
+    logMessage(LOG_WARNING, "invalid USB product identifier: %s", string);
+    return 0;
+  }
+
+  *identifier = 0;
+  return 1;
+}
+
+int
+usbVerifyProductIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
+  if (!identifier) return 1;
+  return identifier == getLittleEndian16(descriptor->idProduct);
+}
+
+static int
+usbVerifyStrings (
+  UsbDevice *device,
+  const char *const *strings,
+  unsigned char number
+) {
+  if (!strings) return 1;
+  if (!number) return 0;
+
+  char *string = usbGetString(device, number, 1000);
+  int matched = 0;
+
+  if (string) {
+    while (*strings) {
+      if (strcmp(*strings, string) == 0) {
+        matched = 1;
+        break;
+      }
+
+      strings += 1;
+    }
+
+    free(string);
+  }
+
+  return matched;
+}
+
+const UsbDeviceDescriptor *
+usbDeviceDescriptor (UsbDevice *device) {
+  return &device->descriptor;
+}
+
+int
+usbGetConfiguration (
+  UsbDevice *device,
+  unsigned char *configuration
+) {
+  ssize_t size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
+                                UsbStandardRequest_GetConfiguration, 0, 0,
+                                configuration, sizeof(*configuration), 1000);
+  if (size != -1) return 1;
+  logMessage(LOG_WARNING, "USB standard request not supported: get configuration");
+  return 0;
+}
+
+static void
+usbDeallocateConfigurationDescriptor (UsbDevice *device) {
+  if (device->configuration) {
+    free(device->configuration);
+    device->configuration = NULL;
+  }
+}
+
+const UsbConfigurationDescriptor *
+usbConfigurationDescriptor (
+  UsbDevice *device
+) {
+  if (!device->configuration) {
+    unsigned char current;
+
+    if (device->descriptor.bNumConfigurations < 2) {
+      current = 1;
+    } else if (!usbGetConfiguration(device, &current)) {
+      current = 0;
+    }
+
+    if (current) {
+      UsbDescriptor descriptor;
+      unsigned char number;
+
+      for (number=0; number<device->descriptor.bNumConfigurations; number++) {
+        int size = usbGetDescriptor(device, UsbDescriptorType_Configuration,
+                                    number, 0, &descriptor, 1000);
+        if (size == -1) {
+          logMessage(LOG_WARNING, "USB configuration descriptor not readable: %d", number);
+        } else if (descriptor.configuration.bConfigurationValue == current) {
+          break;
+        }
+      }
+
+      if (number < device->descriptor.bNumConfigurations) {
+        int length = getLittleEndian16(descriptor.configuration.wTotalLength);
+        UsbDescriptor *descriptors;
+
+        if ((descriptors = malloc(length))) {
+          ssize_t size;
+
+          if (length > sizeof(descriptor)) {
+            size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
+                                  UsbStandardRequest_GetDescriptor,
+                                  (UsbDescriptorType_Configuration << 8) | number,
+                                  0, descriptors, length, 1000);
+          } else {
+            memcpy(descriptors, &descriptor, (size = length));
+          }
+
+          if (size != -1) {
+            device->configuration = &descriptors->configuration;
+          } else {
+            free(descriptors);
+          }
+        } else {
+          logSystemError("USB configuration descriptor allocate");
+        }
+      } else {
+        logMessage(LOG_ERR, "USB configuration descriptor not found: %d", current);
+      }
+    }
+  }
+
+  return device->configuration;
+}
+
+int
+usbConfigureDevice (
+  UsbDevice *device,
+  unsigned char configuration
+) {
+  usbCloseInterface(device);
+
+  if (usbSetConfiguration(device, configuration)) {
+    usbDeallocateConfigurationDescriptor(device);
+    return 1;
+  }
+
+  {
+    const UsbConfigurationDescriptor *descriptor = usbConfigurationDescriptor(device);
+
+    if (descriptor)
+      if (descriptor->bConfigurationValue == configuration)
+        return 1;
+  }
+
+  return 0;
+}
+
+int
+usbNextDescriptor (
+  UsbDevice *device,
+  const UsbDescriptor **descriptor
+) {
+  if (*descriptor) {
+    const UsbDescriptor *next = (UsbDescriptor *)&(*descriptor)->bytes[(*descriptor)->header.bLength];
+    const UsbDescriptor *first = (UsbDescriptor *)device->configuration;
+    unsigned int length = getLittleEndian16(first->configuration.wTotalLength);
+
+    if ((&next->bytes[0] - &first->bytes[0]) >= length) return 0;
+    if ((&next->bytes[next->header.bLength] - &first->bytes[0]) > length) return 0;
+
+    *descriptor = next;
+  } else if (usbConfigurationDescriptor(device)) {
+    *descriptor = (UsbDescriptor *)device->configuration;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+const UsbInterfaceDescriptor *
+usbInterfaceDescriptor (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  const UsbDescriptor *descriptor = NULL;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
+      if (descriptor->interface.bInterfaceNumber == interface) {
+        if (descriptor->interface.bAlternateSetting == alternative) {
+          return &descriptor->interface;
+        }
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "USB: interface descriptor not found: %d.%d", interface, alternative);
+  errno = ENOENT;
+  return NULL;
+}
+
+unsigned int
+usbAlternativeCount (
+  UsbDevice *device,
+  unsigned char interface
+) {
+  unsigned int count = 0;
+  const UsbDescriptor *descriptor = NULL;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
+      if (descriptor->interface.bInterfaceNumber == interface) {
+        count += 1;
+      }
+    }
+  }
+
+  return count;
+}
+
+const UsbEndpointDescriptor *
+usbEndpointDescriptor (
+  UsbDevice *device,
+  unsigned char endpointAddress
+) {
+  const UsbDescriptor *descriptor = NULL;
+  device->scratch.endpointInterfaceDescriptor = NULL;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_Interface) {
+      device->scratch.endpointInterfaceDescriptor = &descriptor->interface;
+      continue;
+    }
+
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_Endpoint) {
+      if (descriptor->endpoint.bEndpointAddress == endpointAddress) {
+        return &descriptor->endpoint;
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "USB: endpoint descriptor not found: %02X", endpointAddress);
+  errno = ENOENT;
+  return NULL;
+}
+
+static void
+usbCancelInputMonitor (UsbEndpoint *endpoint) {
+  if (endpoint->direction.input.pipe.monitor) {
+    asyncCancelRequest(endpoint->direction.input.pipe.monitor);
+    endpoint->direction.input.pipe.monitor = NULL;
+  }
+}
+
+static inline int
+usbHaveInputPipe (UsbEndpoint *endpoint) {
+  return endpoint->direction.input.pipe.output != INVALID_FILE_DESCRIPTOR;
+}
+
+static inline int
+usbHaveInputError (UsbEndpoint *endpoint) {
+  return endpoint->direction.input.pipe.input == INVALID_FILE_DESCRIPTOR;
+}
+
+void
+usbSetEndpointInputError (UsbEndpoint *endpoint, int error) {
+  if (!usbHaveInputError(endpoint)) {
+    endpoint->direction.input.pipe.error = error;
+    closeFile(&endpoint->direction.input.pipe.input);
+  }
+}
+
+static int
+usbSetInputError (void *item, void *data) {
+  UsbEndpoint *endpoint = item;
+  const int *error = data;
+
+  if (usbHaveInputPipe(endpoint)) {
+    usbSetEndpointInputError(endpoint, *error);
+  }
+
+  return 0;
+}
+
+void
+usbSetDeviceInputError (UsbDevice *device, int error) {
+  processQueue(device->endpoints, usbSetInputError, &error);
+}
+
+int
+usbEnqueueInput (UsbEndpoint *endpoint, const void *buffer, size_t length) {
+  if (usbHaveInputError(endpoint)) {
+    errno = EIO;
+    return 0;
+  }
+
+  return writeFile(endpoint->direction.input.pipe.input, buffer, length) != -1;
+}
+
+void
+usbDestroyInputPipe (UsbEndpoint *endpoint) {
+  usbCancelInputMonitor(endpoint);
+  closeFile(&endpoint->direction.input.pipe.input);
+  closeFile(&endpoint->direction.input.pipe.output);
+}
+
+int
+usbMakeInputPipe (UsbEndpoint *endpoint) {
+  if (usbHaveInputPipe(endpoint)) return 1;
+
+  if (createAnonymousPipe(&endpoint->direction.input.pipe.input,
+                          &endpoint->direction.input.pipe.output)) {
+    setCloseOnExec(endpoint->direction.input.pipe.input, 1);
+    setCloseOnExec(endpoint->direction.input.pipe.output, 1);
+
+    if (setBlockingIo(endpoint->direction.input.pipe.output, 0)) {
+      return 1;
+    }
+  }
+
+  usbDestroyInputPipe(endpoint);
+  return 0;
+}
+
+int
+usbMonitorInputPipe (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
+
+  if (endpoint) {
+    if (usbHaveInputPipe(endpoint)) {
+      usbCancelInputMonitor(endpoint);
+      if (!callback) return 1;
+
+      if (asyncMonitorFileInput(&endpoint->direction.input.pipe.monitor,
+                                endpoint->direction.input.pipe.output,
+                                callback, data)) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static void
+usbDeallocateEndpoint (void *item, void *data) {
+  UsbEndpoint *endpoint = item;
+
+  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+    case UsbEndpointDirection_Input:
+      if (endpoint->direction.input.pending.alarm) {
+        asyncCancelRequest(endpoint->direction.input.pending.alarm);
+        endpoint->direction.input.pending.alarm = NULL;
+      }
+
+      if (endpoint->direction.input.pending.requests) {
+        deallocateQueue(endpoint->direction.input.pending.requests);
+        endpoint->direction.input.pending.requests = NULL;
+      }
+
+      if (endpoint->direction.input.completed.request) {
+        free(endpoint->direction.input.completed.request);
+        endpoint->direction.input.completed.request = NULL;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+
+  if (endpoint->extension) {
+    usbDeallocateEndpointExtension(endpoint->extension);
+    endpoint->extension = NULL;
+  }
+
+  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+    case UsbEndpointDirection_Input:
+      usbDestroyInputPipe(endpoint);
+      break;
+
+    default:
+      break;
+  }
+
+  free(endpoint);
+}
+
+static int
+usbTestEndpoint (const void *item, void *data) {
+  const UsbEndpoint *endpoint = item;
+  const unsigned char *endpointAddress = data;
+  return endpoint->descriptor->bEndpointAddress == *endpointAddress;
+}
+
+UsbEndpoint *
+usbGetEndpoint (UsbDevice *device, unsigned char endpointAddress) {
+  UsbEndpoint *endpoint = findItem(device->endpoints, usbTestEndpoint, &endpointAddress);
+  if (endpoint) return endpoint;
+  const UsbEndpointDescriptor *descriptor = usbEndpointDescriptor(device, endpointAddress);
+
+  if (descriptor) {
+    {
+      const char *direction;
+      const char *transfer;
+
+      switch (USB_ENDPOINT_DIRECTION(descriptor)) {
+        default:                          direction = "?";   break;
+        case UsbEndpointDirection_Input:  direction = "in";  break;
+        case UsbEndpointDirection_Output: direction = "out"; break;
+      }
+
+      switch (USB_ENDPOINT_TRANSFER(descriptor)) {
+        default:                              transfer = "?";   break;
+        case UsbEndpointTransfer_Control:     transfer = "ctl"; break;
+        case UsbEndpointTransfer_Isochronous: transfer = "iso"; break;
+        case UsbEndpointTransfer_Bulk:        transfer = "blk"; break;
+        case UsbEndpointTransfer_Interrupt:   transfer = "int"; break;
+      }
+
+      logMessage(LOG_CATEGORY(USB_IO),
+        "ept=%02X dir=%s xfr=%s pkt=%d ivl=%dms",
+        descriptor->bEndpointAddress,
+        direction, transfer,
+        getLittleEndian16(descriptor->wMaxPacketSize),
+        descriptor->bInterval
+      );
+    }
+
+    if ((endpoint = malloc(sizeof(*endpoint)))) {
+      memset(endpoint, 0, sizeof(*endpoint));
+
+      endpoint->device = device;
+      endpoint->interface = device->scratch.endpointInterfaceDescriptor;
+      endpoint->descriptor = descriptor;
+      endpoint->extension = NULL;
+      endpoint->prepare = NULL;
+
+      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+        case UsbEndpointDirection_Input:
+          endpoint->direction.input.pending.requests = NULL;
+          endpoint->direction.input.pending.alarm = NULL;
+          endpoint->direction.input.pending.delay = 0;
+
+          endpoint->direction.input.completed.request = NULL;
+          endpoint->direction.input.completed.buffer = NULL;
+          endpoint->direction.input.completed.length = 0;
+
+          endpoint->direction.input.pipe.input = INVALID_FILE_DESCRIPTOR;
+          endpoint->direction.input.pipe.output = INVALID_FILE_DESCRIPTOR;
+          endpoint->direction.input.pipe.monitor = NULL;
+          endpoint->direction.input.pipe.error = 0;
+
+          break;
+      }
+
+      if (usbAllocateEndpointExtension(endpoint)) {
+        if (enqueueItem(device->endpoints, endpoint)) {
+          if (device->disableEndpointReset) {
+            logMessage(LOG_CATEGORY(USB_IO), "endpoint reset disabled");
+          } else {
+            usbClearHalt(device, endpoint->descriptor->bEndpointAddress);
+          }
+
+          if (!endpoint->prepare || endpoint->prepare(endpoint)) return endpoint;
+          deleteItem(device->endpoints, endpoint);
+        }
+
+        usbDeallocateEndpointExtension(endpoint->extension);
+        usbDestroyInputPipe(endpoint);
+      }
+
+      free(endpoint);
+    }
+  }
+
+  return NULL;
+}
+
+UsbEndpoint *
+usbGetInputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
+  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Input);
+}
+
+UsbEndpoint *
+usbGetOutputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
+  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Output);
+}
+
+static int
+usbFinishEndpoint (void *item, void *data) {
+  UsbEndpoint *endpoint = item;
+
+  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+    case UsbEndpointDirection_Input:
+      if (endpoint->direction.input.pending.requests) {
+        deleteElements(endpoint->direction.input.pending.requests);
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+static void
+usbRemoveEndpoints (UsbDevice *device, int final) {
+  if (device->endpoints) {
+    processQueue(device->endpoints, usbFinishEndpoint, NULL);
+    deleteElements(device->endpoints);
+
+    if (final) {
+      deallocateQueue(device->endpoints);
+      device->endpoints = NULL;
+    }
+  }
+}
+
+static void
+usbDeallocateInputFilter (void *item, void *data) {
+  UsbInputFilterEntry *entry = item;
+  free(entry);
+}
+
+int
+usbAddInputFilter (UsbDevice *device, UsbInputFilter *filter) {
+  UsbInputFilterEntry *entry;
+
+  if ((entry = malloc(sizeof(*entry)))) {
+    memset(entry, 0, sizeof(*entry));
+    entry->filter = filter;
+
+    if (enqueueItem(device->inputFilters, entry)) return 1;
+
+    free(entry);
+  }
+
+  return 0;
+}
+
+static int
+usbApplyInputFilter (void *item, void *data) {
+  UsbInputFilterEntry *entry = item;
+
+  return !entry->filter(data);
+}
+
+int
+usbApplyInputFilters (UsbEndpoint *endpoint, void *buffer, size_t size, ssize_t *length) {
+  Queue *filters = endpoint->device->inputFilters;
+
+  if (getQueueSize(filters) == 0) {
+    usbLogEndpointData(endpoint, "input", buffer, *length);
+  } else {
+    usbLogEndpointData(endpoint, "unfiltered input", buffer, *length);
+
+    UsbInputFilterData data = {
+      .buffer = buffer,
+      .size = size,
+      .length = *length
+    };
+
+    if (processQueue(filters, usbApplyInputFilter, &data)) {
+      errno = EIO;
+      return 0;
+    }
+
+    *length = data.length;
+    usbLogEndpointData(endpoint, "filtered input", buffer, *length);
+  }
+
+  return 1;
+}
+
+void
+usbCloseInterface (
+  UsbDevice *device
+) {
+  usbRemoveEndpoints(device, 0);
+
+  if (device->interface) {
+    usbReleaseInterface(device, device->interface->bInterfaceNumber);
+    device->interface = NULL;
+  }
+}
+
+int
+usbOpenInterface (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  const UsbInterfaceDescriptor *descriptor = usbInterfaceDescriptor(device, interface, alternative);
+  if (!descriptor) return 0;
+  if (descriptor == device->interface) return 1;
+
+  if (device->interface) {
+    if (device->interface->bInterfaceNumber != interface) {
+      usbCloseInterface(device);
+    }
+  }
+
+  if (!device->interface) {
+    if (!usbClaimInterface(device, interface)) {
+      return 0;
+    }
+  }
+
+  if (usbAlternativeCount(device, interface) == 1) goto done;
+
+  {
+    unsigned char response[1];
+    ssize_t size = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Standard,
+                                  UsbStandardRequest_GetInterface, 0, interface,
+                                  response, sizeof(response), 1000);
+
+    if (size != -1) {
+      if (response[0] == alternative) goto done;
+    } else {
+      logMessage(LOG_WARNING, "USB standard request not supported: get interface");
+    }
+  }
+
+  if (usbSetAlternative(device, interface, alternative)) goto done;
+  if (!device->interface) usbReleaseInterface(device, interface);
+  return 0;
+
+done:
+  device->interface = descriptor;
+  return 1;
+}
+
+void
+usbCloseDevice (UsbDevice *device) {
+  if (device->serial.operations) {
+    const UsbSerialOperations *uso = device->serial.operations;
+    if (uso->disableAdapter) uso->disableAdapter(device);
+  }
+
+  usbCloseInterface(device);
+  usbRemoveEndpoints(device, 1);
+
+  if (device->inputFilters) {
+    deallocateQueue(device->inputFilters);
+    device->inputFilters = NULL;
+  }
+
+  if (device->serial.data) {
+    device->serial.operations->destroyData(device->serial.data);
+    device->serial.data = NULL;
+  }
+
+  if (device->extension) {
+    usbDeallocateDeviceExtension(device->extension);
+    device->extension = NULL;
+  }
+
+  usbDeallocateConfigurationDescriptor(device);
+  free(device);
+}
+
+static UsbDevice *
+usbOpenDevice (UsbDeviceExtension *extension) {
+  UsbDevice *device;
+
+  if ((device = malloc(sizeof(*device)))) {
+    memset(device, 0, sizeof(*device));
+    device->extension = extension;
+    device->serial.operations = NULL;
+    device->serial.data = NULL;
+    device->resetDevice = 0;
+    device->disableEndpointReset = 0;
+
+    if ((device->endpoints = newQueue(usbDeallocateEndpoint, NULL))) {
+      if ((device->inputFilters = newQueue(usbDeallocateInputFilter, NULL))) {
+        if (usbReadDeviceDescriptor(device)) {
+          if (device->descriptor.bDescriptorType == UsbDescriptorType_Device) {
+            if (device->descriptor.bLength == UsbDescriptorSize_Device) {
+              return device;
+            }
+          }
+        }
+
+        deallocateQueue(device->inputFilters);
+      }
+
+      usbRemoveEndpoints(device, 1);
+    }
+    free(device);
+  }
+
+  logSystemError("USB device open");
+  return NULL;
+}
+
+UsbDevice *
+usbTestDevice (UsbDeviceExtension *extension, UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  UsbDevice *device;
+
+  if ((device = usbOpenDevice(extension))) {
+    logMessage(LOG_CATEGORY(USB_IO),
+      "testing device: vendor=%04X product=%04X",
+      getLittleEndian16(device->descriptor.idVendor),
+      getLittleEndian16(device->descriptor.idProduct)
+    );
+
+    if (chooser(device, data)) {
+      usbLogString(device, device->descriptor.iManufacturer, "Manufacturer Name");
+      usbLogString(device, device->descriptor.iProduct, "Product Description");
+      usbLogString(device, device->descriptor.iSerialNumber, "Serial Number");
+      return device;
+    }
+
+    errno = ENOENT;
+    device->extension = NULL;
+    usbCloseDevice(device);
+  }
+
+  return NULL;
+}
+
+void
+usbLogInputProblem (UsbEndpoint *endpoint, const char *problem) {
+  logMessage(LOG_WARNING, "USB input: %s: Ept:%02X",
+             problem, endpoint->descriptor->bEndpointAddress);
+}
+
+static void
+usbDeallocatePendingInputRequest (void *item, void *data) {
+  void *request = item;
+  UsbEndpoint *endpoint = data;
+  usbCancelRequest(endpoint->device, request);
+}
+
+static Element *
+usbAddPendingInputRequest (UsbEndpoint *endpoint) {
+  void *request = usbSubmitRequest(endpoint->device,
+                                   endpoint->descriptor->bEndpointAddress,
+                                   NULL,
+                                   getLittleEndian16(endpoint->descriptor->wMaxPacketSize),
+                                   endpoint);
+
+  if (request) {
+    Element *element = enqueueItem(endpoint->direction.input.pending.requests, request);
+
+    if (element) return element;
+    usbCancelRequest(endpoint->device, request);
+  }
+
+  return NULL;
+}
+
+static void
+usbEnsurePendingInputRequests (UsbEndpoint *endpoint, int count) {
+  int limit = USB_INPUT_INTERRUPT_REQUESTS_MAXIMUM;
+  if ((count < 1) || (count > limit)) count = limit;
+  endpoint->direction.input.pending.delay = 0;
+
+  while (getQueueSize(endpoint->direction.input.pending.requests) < count) {
+    if (!usbAddPendingInputRequest(endpoint)) {
+      break;
+    }
+  }
+}
+
+ASYNC_ALARM_CALLBACK(usbHandleSchedulePendingInputRequest) {
+  UsbEndpoint *endpoint = parameters->data;
+
+  asyncDiscardHandle(endpoint->direction.input.pending.alarm);
+  endpoint->direction.input.pending.alarm = NULL;
+
+  usbAddPendingInputRequest(endpoint);
+}
+
+static void
+usbSchedulePendingInputRequest (UsbEndpoint *endpoint) {
+  if (!endpoint->direction.input.pending.alarm) {
+    int *delay = &endpoint->direction.input.pending.delay;
+
+    if (!*delay) *delay = 1;
+    *delay = MIN(*delay, USB_INPUT_INTERRUPT_DELAY_MAXIMUM);
+
+    asyncNewRelativeAlarm(&endpoint->direction.input.pending.alarm, *delay,
+                          usbHandleSchedulePendingInputRequest, endpoint);
+
+    *delay += 1;
+  }
+}
+
+int
+usbHandleInputResponse (UsbEndpoint *endpoint, const void *buffer, size_t length) {
+  int requestsLeft = getQueueSize(endpoint->direction.input.pending.requests);
+
+  if (length > 0) {
+    if (!usbEnqueueInput(endpoint, buffer, length)) {
+      usbLogInputProblem(endpoint, "data not enqueued");
+      return 0;
+    }
+
+    usbEnsurePendingInputRequests(endpoint, requestsLeft+2);
+    return 1;
+  }
+
+  if (requestsLeft == 0) {
+    usbSchedulePendingInputRequest(endpoint);
+  }
+
+  return 1;
+}
+
+void
+usbBeginInput (
+  UsbDevice *device,
+  unsigned char endpointNumber
+) {
+  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
+
+  if (endpoint) {
+    if (!endpoint->direction.input.pending.requests) {
+      if ((endpoint->direction.input.pending.requests = newQueue(usbDeallocatePendingInputRequest, NULL))) {
+        setQueueData(endpoint->direction.input.pending.requests, endpoint);
+      }
+    }
+
+    if (endpoint->direction.input.pending.requests) {
+      usbEnsurePendingInputRequests(endpoint, 0);
+    }
+  }
+}
+
+static int
+usbGetPollInterval (UsbEndpoint *endpoint) {
+  int interval = endpoint->descriptor->bInterval;
+
+  if (interval > 0) {
+    if (getLittleEndian16(endpoint->device->descriptor.bcdUSB) >= UsbSpecificationVersion_2_0) {
+      interval = (1 << (interval - 1)) / 8;
+    }
+  }
+
+  return interval;
+}
+
+int
+usbAwaitInput (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  int timeout
+) {
+  UsbEndpoint *endpoint;
+  int retryInterval;
+
+  if (!(endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+    return 0;
+  }
+
+  if (usbHaveInputPipe(endpoint)) {
+    if (usbHaveInputError(endpoint)) {
+      errno = endpoint->direction.input.pipe.error;
+      return 0;
+    }
+
+    return awaitFileInput(endpoint->direction.input.pipe.output, timeout);
+  }
+
+  if (endpoint->direction.input.completed.request) {
+    return 1;
+  }
+
+  if (!timeout) {
+    errno = EAGAIN;
+    return 0;
+  }
+
+  retryInterval = usbGetPollInterval(endpoint);
+  retryInterval = MAX(USB_INPUT_AWAIT_RETRY_INTERVAL_MINIMUM, retryInterval);
+
+  if (!(endpoint->direction.input.pending.requests && getQueueSize(endpoint->direction.input.pending.requests))) {
+    int size = getLittleEndian16(endpoint->descriptor->wMaxPacketSize);
+    unsigned char *buffer = malloc(size);
+
+    if (buffer) {
+      TimePeriod period;
+      startTimePeriod(&period, timeout);
+
+      while (1) {
+        ssize_t count = usbReadEndpoint(device, endpointNumber, buffer, size, 20);
+
+        if (count != -1) {
+          if (count) {
+            endpoint->direction.input.completed.request = buffer;
+            endpoint->direction.input.completed.buffer = buffer;
+            endpoint->direction.input.completed.length = count;
+            return 1;
+          }
+
+          errno = EAGAIN;
+        }
+
+#ifdef ETIMEDOUT
+        if (errno == ETIMEDOUT) errno = EAGAIN;
+#endif /* ETIMEDOUT */
+
+        if (errno != EAGAIN) break;
+        if (afterTimePeriod(&period, NULL)) break;
+        asyncWait(retryInterval);
+      }
+
+      free(buffer);
+    } else {
+      logMallocError();
+    }
+
+    return 0;
+  }
+
+  {
+    TimePeriod period;
+    startTimePeriod(&period, timeout);
+
+    while (1) {
+      UsbResponse response;
+      void *request;
+
+      while (!(request = usbReapResponse(device,
+                                         endpointNumber | UsbEndpointDirection_Input,
+                                         &response, 0))) {
+        if (errno != EAGAIN) return 0;
+        if (afterTimePeriod(&period, NULL)) return 0;
+        asyncWait(retryInterval);
+      }
+
+      usbAddPendingInputRequest(endpoint);
+      deleteItem(endpoint->direction.input.pending.requests, request);
+
+      if (response.count > 0) {
+        endpoint->direction.input.completed.request = request;
+        endpoint->direction.input.completed.buffer = response.buffer;
+        endpoint->direction.input.completed.length = response.count;
+        return 1;
+      }
+
+      free(request);
+    }
+  }
+}
+
+ssize_t
+usbReadData (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int initialTimeout,
+  int subsequentTimeout
+) {
+  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
+
+  if (endpoint) {
+    unsigned char *bytes = buffer;
+    unsigned char *target = bytes;
+
+    if (usbHaveInputPipe(endpoint)) {
+      if (usbHaveInputError(endpoint)) {
+        errno = endpoint->direction.input.pipe.error;
+        endpoint->direction.input.pipe.error = EAGAIN;
+        return -1;
+      }
+
+      return readFile(endpoint->direction.input.pipe.output, buffer, length, initialTimeout, subsequentTimeout);
+    }
+
+    while (length > 0) {
+      int timeout = (target != bytes)? subsequentTimeout:
+                    initialTimeout? initialTimeout:
+                    USB_INPUT_READ_INITIAL_TIMEOUT_DEFAULT;
+
+      if (!usbAwaitInput(device, endpointNumber, timeout)) {
+        if (errno == EAGAIN) break;
+        return -1;
+      }
+
+      {
+        size_t count = endpoint->direction.input.completed.length;
+
+        if (length < count) count = length;
+        memcpy(target, endpoint->direction.input.completed.buffer, count);
+
+        if ((endpoint->direction.input.completed.length -= count)) {
+          endpoint->direction.input.completed.buffer += count;
+        } else {
+          endpoint->direction.input.completed.buffer = NULL;
+          free(endpoint->direction.input.completed.request);
+          endpoint->direction.input.completed.request = NULL;
+        }
+
+        target += count;
+        length -= count;
+      }
+    }
+
+    return target - bytes;
+  }
+
+  return -1;
+}
+
+ssize_t
+usbWriteData (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *data,
+  size_t length,
+  int timeout
+) {
+  UsbEndpoint *endpoint = usbGetOutputEndpoint(device, endpointNumber);
+
+  if (endpoint) {
+    const uint16_t size = getLittleEndian16(endpoint->descriptor->wMaxPacketSize);
+    const unsigned char *from = data;
+    const unsigned char *const end = from + length;
+
+    while (from < end) {
+      size_t count = MIN((end - from), size);
+      ssize_t result = usbWriteEndpoint(device, endpointNumber, from, count, timeout);
+
+      if (result == -1) return result;
+      from += result;
+    }
+
+    return length;
+  }
+
+  return -1;
+}
+
+static int
+usbPrepareChannel (UsbChannel *channel) {
+  const UsbChannelDefinition *definition = channel->definition;
+  UsbDevice *device = channel->device;
+
+  device->resetDevice = definition->resetDevice;
+  device->disableEndpointReset = definition->disableEndpointReset;
+
+  if (definition->disableAutosuspend) {
+    logMessage(LOG_CATEGORY(USB_IO), "disabling autosuspend");
+    usbDisableAutosuspend(device);
+  }
+
+  if (device->resetDevice) {
+    usbResetDevice(device);
+  }
+
+  if (usbConfigureDevice(device, definition->configuration)) {
+    if (usbOpenInterface(device, definition->interface, definition->alternative)) {
+      int ok = 1;
+
+      if (ok) {
+        if (!usbSetSerialOperations(device)) {
+          ok = 0;
+        } else if (device->serial.operations) {
+          logMessage(LOG_CATEGORY(USB_IO), "serial adapter: %s",
+                     device->serial.operations->name);
+        }
+      }
+
+      if (ok) {
+        if (device->serial.operations) {
+          if (device->serial.operations->enableAdapter) {
+            if (!device->serial.operations->enableAdapter(device)) {
+              ok = 0;
+            }
+          }
+        }
+      }
+
+      if (ok) {
+        if (definition->serial) {
+          if (!usbSetSerialParameters(device, definition->serial)) {
+            ok = 0;
+          }
+        }
+      }
+
+      if (ok) {
+        if (definition->inputEndpoint) {
+          UsbEndpoint *endpoint = usbGetInputEndpoint(device, definition->inputEndpoint);
+
+          if (!endpoint) {
+            ok = 0;
+          } else if ((USB_ENDPOINT_TRANSFER(endpoint->descriptor) == UsbEndpointTransfer_Interrupt) ||
+                     usbHaveInputPipe(endpoint)) {
+            usbBeginInput(device, definition->inputEndpoint);
+          }
+        }
+      }
+
+      if (ok) {
+        if (definition->outputEndpoint) {
+          UsbEndpoint *endpoint = usbGetOutputEndpoint(device, definition->outputEndpoint);
+
+          if (!endpoint) {
+            ok = 0;
+          }
+        }
+      }
+
+      if (ok) return 1;
+      usbCloseInterface(device);
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbVerifyInterface (UsbDevice *device, const UsbChannelDefinition *definition) {
+  const UsbInterfaceDescriptor *interface = usbInterfaceDescriptor(device, definition->interface, definition->alternative);
+  if (!interface) return 0;
+
+  BITMASK(endpoints, 0X100, char);
+  BITMASK_ZERO(endpoints);
+
+  {
+    const UsbDescriptor *descriptor = (const UsbDescriptor *)interface;
+
+    while (usbNextDescriptor(device, &descriptor)) {
+      uint8_t type = descriptor->header.bDescriptorType;
+      if (type == UsbDescriptorType_Interface) break;
+      if (type != UsbDescriptorType_Endpoint) continue;
+      BITMASK_SET(endpoints, descriptor->endpoint.bEndpointAddress);
+    }
+  }
+
+  if (definition->inputEndpoint) {
+    if (!BITMASK_TEST(endpoints, (definition->inputEndpoint | UsbEndpointDirection_Input))) {
+      return 0;
+    }
+  }
+
+  if (definition->outputEndpoint) {
+    if (!BITMASK_TEST(endpoints, (definition->outputEndpoint | UsbEndpointDirection_Output))) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+struct UsbChooseChannelDataStruct {
+  const UsbChannelDefinition *definition;
+
+  const char *serialNumber;
+  uint16_t vendorIdentifier;
+  uint16_t productIdentifier;
+  unsigned genericDevices:1;
+};
+
+static int
+usbChooseChannel (UsbDevice *device, UsbChooseChannelData *data) {
+  const UsbDeviceDescriptor *descriptor = &device->descriptor;
+  logBytes(LOG_CATEGORY(USB_IO), "device descriptor", descriptor, sizeof(*descriptor));
+
+  if (!(descriptor->iManufacturer || descriptor->iProduct || descriptor->iSerialNumber)) {
+    UsbDeviceDescriptor actualDescriptor;
+    ssize_t result = usbGetDeviceDescriptor(device, &actualDescriptor);
+
+    if (result == UsbDescriptorSize_Device) {
+      device->descriptor = actualDescriptor;
+
+      logBytes(LOG_CATEGORY(USB_IO),
+        "using actual device descriptor",
+        descriptor, sizeof(*descriptor)
+      );
+    }
+  }
+
+  {
+    uint16_t vendor = getLittleEndian16(descriptor->idVendor);
+    uint16_t product = getLittleEndian16(descriptor->idProduct);
+
+    const char *const *drivers = usbGetDriverCodes(vendor, product);
+    if (!drivers) return 0;
+  }
+
+  for (const UsbChannelDefinition *definition = data->definition;
+       definition->vendor; definition+=1) {
+    if (definition->version && (definition->version != getLittleEndian16(descriptor->bcdUSB))) continue;
+    if (!USB_IS_PRODUCT(descriptor, definition->vendor, definition->product)) continue;
+
+    if (!data->genericDevices) {
+      const UsbSerialAdapter *adapter = usbFindSerialAdapter(descriptor);
+      if (adapter && adapter->generic) continue;
+    }
+
+    if (!usbVerifyVendorIdentifier(descriptor, data->vendorIdentifier)) continue;
+    if (!usbVerifyProductIdentifier(descriptor, data->productIdentifier)) continue;
+    if (!usbVerifySerialNumber(device, data->serialNumber)) continue;
+
+    if (!usbVerifyStrings(device, definition->manufacturers, descriptor->iManufacturer)) continue;
+    if (!usbVerifyStrings(device, definition->products, descriptor->iProduct)) continue;
+
+    if (definition->verifyInterface) {
+      if (!usbConfigureDevice(device, definition->configuration)) continue;
+      if (!usbVerifyInterface(device, definition)) continue;
+    }
+
+    data->definition = definition;
+    return 1;
+  }
+
+  return 0;
+}
+
+static UsbChannel *
+usbNewChannel (UsbChooseChannelData *data) {
+  UsbChannel *channel;
+
+  if ((channel = malloc(sizeof(*channel)))) {
+    memset(channel, 0, sizeof(*channel));
+
+    if ((channel->device = usbFindDevice(usbChooseChannel, data))) {
+      channel->definition = data->definition;
+      return channel;
+    }
+
+    free(channel);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+typedef enum {
+  USB_PARM_SERIAL_NUMBER,
+  USB_PARM_VENDOR_IDENTIFIER,
+  USB_PARM_PRODUCT_IDENTIFIER,
+  USB_PARM_GENERIC_DEVICES
+} UsbDeviceParameter;
+
+static const char *const usbDeviceParameterNames[] = {
+  "serialNumber",
+  "vendorIdentifier",
+  "productIdentifier",
+  "genericDevices",
+  NULL
+};
+
+static char **
+usbGetDeviceParameters (const char *identifier) {
+  if (!identifier) identifier = "";
+  return getDeviceParameters(usbDeviceParameterNames, identifier);
+}
+
+UsbChannel *
+usbOpenChannel (const UsbChannelDefinition *definitions, const char *identifier) {
+  UsbChannel *channel = NULL;
+  char **parameters = usbGetDeviceParameters(identifier);
+
+  if (parameters) {
+    int ok = 1;
+
+    UsbChooseChannelData choose = {
+      .definition = definitions,
+      .serialNumber = parameters[USB_PARM_SERIAL_NUMBER]
+    };
+
+    if (!usbParseVendorIdentifier(&choose.vendorIdentifier, parameters[USB_PARM_VENDOR_IDENTIFIER])) ok = 0;
+    if (!usbParseProductIdentifier(&choose.productIdentifier, parameters[USB_PARM_PRODUCT_IDENTIFIER])) ok = 0;
+
+    {
+      const char *parameter = parameters[USB_PARM_GENERIC_DEVICES];
+
+      if (!(parameter && *parameter)) {
+        choose.genericDevices = 1;
+      } else {
+        unsigned int flag;
+
+        if (validateYesNo(&flag, parameter)) {
+          choose.genericDevices = flag;
+        } else {
+          logMessage(LOG_WARNING, "invalid generic devices option: %s", parameter);
+          ok = 0;
+        }
+      }
+    }
+
+    if (ok) {
+      if (!(channel = usbNewChannel(&choose))) {
+        logMessage(LOG_CATEGORY(USB_IO), "device not found%s%s",
+                   (*identifier? ": ": ""), identifier);
+      }
+    }
+
+    deallocateStrings(parameters);
+  }
+
+  if (channel) {
+    if (usbPrepareChannel(channel)) {
+      return channel;
+    }
+
+    usbCloseChannel(channel);
+  }
+
+  return NULL;
+}
+
+void
+usbCloseChannel (UsbChannel *channel) {
+  usbCloseDevice(channel->device);
+  free(channel);
+}
+
+const char *
+usbMakeChannelIdentifier (UsbChannel *channel, char *buffer, size_t size) {
+  UsbDevice *device = channel->device;
+
+  UsbDeviceDescriptor descriptor;
+  if (!usbGetDeviceDescriptor(device, &descriptor)) return NULL;
+
+  size_t length;
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s%c", USB_DEVICE_QUALIFIER, PARAMETER_QUALIFIER_CHARACTER);
+
+  {
+    uint16_t vendorIdentifier = getLittleEndian16(descriptor.idVendor);
+
+    if (vendorIdentifier) {
+      STR_PRINTF(
+        "%s%c0X%04X%c",
+        usbDeviceParameterNames[USB_PARM_VENDOR_IDENTIFIER],
+        PARAMETER_ASSIGNMENT_CHARACTER,
+        vendorIdentifier,
+        DEVICE_PARAMETER_SEPARATOR
+      );
+    }
+  }
+
+  {
+    uint16_t productIdentifier = getLittleEndian16(descriptor.idProduct);
+
+    if (productIdentifier) {
+      STR_PRINTF(
+        "%s%c0X%04X%c",
+        usbDeviceParameterNames[USB_PARM_PRODUCT_IDENTIFIER],
+        PARAMETER_ASSIGNMENT_CHARACTER,
+        productIdentifier,
+        DEVICE_PARAMETER_SEPARATOR
+      );
+    }
+  }
+
+  {
+    char *serialNumber = usbGetSerialNumber(device, 1000);
+
+    if (serialNumber) {
+      if (!strchr(serialNumber, DEVICE_PARAMETER_SEPARATOR)) {
+        STR_PRINTF(
+          "%s%c%s%c",
+          usbDeviceParameterNames[USB_PARM_SERIAL_NUMBER],
+          PARAMETER_ASSIGNMENT_CHARACTER,
+          serialNumber,
+          DEVICE_PARAMETER_SEPARATOR
+        );
+      }
+
+      free(serialNumber);
+    }
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+
+  {
+    char *last = &buffer[length] - 1;
+    if (*last == DEVICE_PARAMETER_SEPARATOR) *last = 0;
+  }
+
+  return buffer;
+}
+
+static int
+usbCompareDeviceEntries (const void *element1, const void *element2) {
+  const UsbDeviceEntry *entry1 = element1;
+  const UsbDeviceEntry *entry2 = element2;
+
+  if (entry1->vendorIdentifier < entry2->vendorIdentifier) return -1;
+  if (entry1->vendorIdentifier > entry2->vendorIdentifier) return 1;
+
+  if (entry1->productIdentifier < entry2->productIdentifier) return -1;
+  if (entry1->productIdentifier > entry2->productIdentifier) return 1;
+
+  return 0;
+}
+
+static int
+usbSearchDeviceEntry (const void *target, const void *element) {
+  const UsbDeviceEntry *entry1 = target;
+  const UsbDeviceEntry *entry2 = element;
+  return usbCompareDeviceEntries(entry1, entry2);
+}
+
+const char *const *
+usbGetDriverCodes (uint16_t vendor, uint16_t product) {
+  const UsbDeviceEntry target = {
+    .vendorIdentifier = vendor,
+    .productIdentifier = product
+  };
+
+  const UsbDeviceEntry *entry = bsearch(&target, usbDeviceTable,
+                                        usbDeviceCount,
+                                        sizeof(usbDeviceTable[0]),
+                                        usbSearchDeviceEntry);
+
+  return entry? entry->driverCodes: NULL;
+}
+
+int
+isUsbDeviceIdentifier (const char **identifier) {
+  return hasQualifier(identifier, USB_DEVICE_QUALIFIER);
+}
diff --git a/Programs/usb_adapters.c b/Programs/usb_adapters.c
new file mode 100644
index 0000000..e58fe1e
--- /dev/null
+++ b/Programs/usb_adapters.c
@@ -0,0 +1,180 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include "usb_adapters.h"
+
+const UsbSerialAdapter usbSerialAdapterTable[] = {
+  { /* Albatross, Cebra, HIMS SyncBraille, HandyTech FTDI, Hedo MobilLine, MDV */
+    .vendor=0X0403, .product=0X6001,
+    .generic = 1,
+    .operations = &usbSerialOperations_FTDI_FT8U232AM
+  },
+
+  { /* DotPad */
+    .vendor=0X0403, .product=0X6010,
+    .generic = 1,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Hedo MobilLine */
+    .vendor=0X0403, .product=0XDE58,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Hedo ProfiLine */
+    .vendor=0X0403, .product=0XDE59,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Papenmeier FTDI */
+    .vendor=0X0403, .product=0XF208,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum Vario40 (40 cells) */
+    .vendor=0X0403, .product=0XFE70,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum PocketVario (24 cells) */
+    .vendor=0X0403, .product=0XFE71,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum SuperVario 40 (40 cells) */
+    .vendor=0X0403, .product=0XFE72,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum SuperVario 32 (32 cells) */
+    .vendor=0X0403, .product=0XFE73,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum SuperVario 64 (64 cells) */
+    .vendor=0X0403, .product=0XFE74,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum SuperVario 80 (80 cells) */
+    .vendor=0X0403, .product=0XFE75,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioPro 80 (80 cells) */
+    .vendor=0X0403, .product=0XFE76,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioPro 64 (64 cells) */
+    .vendor=0X0403, .product=0XFE77,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioPro 40 (40 cells) */
+    .vendor=0X0904, .product=0X2000,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum EcoVario 24 (24 cells) */
+    .vendor=0X0904, .product=0X2001,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum EcoVario 40 (40 cells) */
+    .vendor=0X0904, .product=0X2002,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioConnect 40 (40 cells) */
+    .vendor=0X0904, .product=0X2007,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioConnect 32 (32 cells) */
+    .vendor=0X0904, .product=0X2008,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioConnect 24 (24 cells) */
+    .vendor=0X0904, .product=0X2009,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioConnect 64 (64 cells) */
+    .vendor=0X0904, .product=0X2010,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum VarioConnect 80 (80 cells) */
+    .vendor=0X0904, .product=0X2011,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum EcoVario 32 (32 cells) */
+    .vendor=0X0904, .product=0X2014,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum EcoVario 64 (64 cells) */
+    .vendor=0X0904, .product=0X2015,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum EcoVario 80 (80 cells) */
+    .vendor=0X0904, .product=0X2016,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* Baum Refreshabraille 18 (18 cells) */
+    .vendor=0X0904, .product=0X3000,
+    .operations = &usbSerialOperations_FTDI_FT232BM
+  },
+
+  { /* HandyTech GoHubs */
+    .vendor=0X0921, .product=0X1200,
+    .operations = &usbSerialOperations_Belkin
+  },
+
+  { /* BrailleMemo Pocket, Seika BrailleDisplay */
+    .vendor=0X10C4, .product=0XEA60,
+    .generic = 1,
+    .operations = &usbSerialOperations_CP2101
+  },
+
+  { /* Seika NoteTaker */
+    .vendor=0X10C4, .product=0XEA80,
+    .generic = 1,
+    .operations = &usbSerialOperations_CP2110
+  },
+
+  { /* Canute */
+    .vendor=0X16C0, .product=0X05E1,
+    .operations = &usbSerialOperations_CDC_ACM
+  },
+
+  { /* NLS eReader Zoomax */
+    .vendor=0X1A86, .product=0X7523,
+    .generic = 1,
+    .operations = &usbSerialOperations_CH341
+  },
+};
+
+const size_t usbSerialAdapterCount = ARRAY_COUNT(usbSerialAdapterTable);
diff --git a/Programs/usb_adapters.h b/Programs/usb_adapters.h
new file mode 100644
index 0000000..da8730e
--- /dev/null
+++ b/Programs/usb_adapters.h
@@ -0,0 +1,35 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_ADAPTERS
+#define BRLTTY_INCLUDED_USB_ADAPTERS
+
+#include "usb_serial.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern const UsbSerialAdapter usbSerialAdapterTable[];
+extern const size_t usbSerialAdapterCount;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_ADAPTERS */
diff --git a/Programs/usb_android.c b/Programs/usb_android.c
new file mode 100644
index 0000000..5095b3d
--- /dev/null
+++ b/Programs/usb_android.c
@@ -0,0 +1,976 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+#include "log.h"
+#include "bitfield.h"
+#include "queue.h"
+
+#include "io_usb.h"
+#include "usb_internal.h"
+#include "system_java.h"
+
+#define JAVA_OBJ_USB(name) "android/hardware/usb/Usb" name
+#define JAVA_OBJ_USB_DEVICE JAVA_OBJ_USB("Device")
+#define JAVA_OBJ_USB_INTERFACE JAVA_OBJ_USB("Interface")
+#define JAVA_OBJ_USB_ENDPOINT JAVA_OBJ_USB("Endpoint")
+#define JAVA_OBJ_USB_CONNECTION JAVA_OBJ_USB("DeviceConnection")
+
+#define JAVA_SIG_USB_DEVICE JAVA_SIG_OBJECT(JAVA_OBJ_USB_DEVICE)
+#define JAVA_SIG_USB_INTERFACE JAVA_SIG_OBJECT(JAVA_OBJ_USB_INTERFACE)
+#define JAVA_SIG_USB_ENDPOINT JAVA_SIG_OBJECT(JAVA_OBJ_USB_ENDPOINT)
+#define JAVA_SIG_USB_CONNECTION JAVA_SIG_OBJECT(JAVA_OBJ_USB_CONNECTION)
+
+typedef struct {
+  JNIEnv *env;
+  jobject device;
+  UsbDeviceDescriptor descriptor;
+} UsbHostDevice;
+
+static Queue *usbHostDevices = NULL;
+
+struct UsbDeviceExtensionStruct {
+  const UsbHostDevice *host;
+  jobject connection;
+  jobject interface;
+
+  int fileDescriptor;
+};
+
+struct UsbEndpointExtensionStruct {
+  UsbEndpoint *endpoint;
+  jobject object;
+};
+
+static jclass usbHelperClass = NULL;
+static jclass usbDeviceClass = NULL;
+static jclass usbInterfaceClass = NULL;
+static jclass usbConnectionClass = NULL;
+
+static int
+usbFindHelperClass (JNIEnv *env) {
+  return findJavaClass(env, &usbHelperClass, JAVA_OBJ_BRLTTY("UsbHelper"));
+}
+
+static jobject
+usbGetDeviceIterator (JNIEnv *env) {
+  if (usbFindHelperClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, usbHelperClass, "getDeviceIterator",
+                             JAVA_SIG_METHOD(JAVA_SIG_ITERATOR, ))) {
+      jobject iterator = (*env)->CallStaticObjectMethod(env, usbHelperClass, method);
+
+      if (iterator) return iterator;
+      clearJavaException(env, 1);
+      errno = EIO;
+    }
+  }
+
+  return NULL;
+}
+
+static jobject
+usbGetNextDevice (JNIEnv *env, jobject iterator) {
+  if (usbFindHelperClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, usbHelperClass, "getNextDevice",
+                             JAVA_SIG_METHOD(JAVA_SIG_USB_DEVICE, JAVA_SIG_ITERATOR))) {
+      jobject device = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, iterator);
+
+      if (device) return device;
+      clearJavaException(env, 1);
+    }
+  }
+
+  return NULL;
+}
+
+static jobject
+usbGetDeviceInterface (JNIEnv *env, jobject device, jint identifier) {
+  if (usbFindHelperClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, usbHelperClass, "getDeviceInterface",
+                             JAVA_SIG_METHOD(JAVA_SIG_USB_INTERFACE,
+                                             JAVA_SIG_USB_DEVICE // device
+                                             JAVA_SIG_INT // identifier
+                                            ))) {
+      jobject interface = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, device, identifier);
+
+      if (interface) return interface;
+      clearJavaException(env, 1);
+      errno = EIO;
+    }
+  }
+
+  return NULL;
+}
+
+static jobject
+usbGetInterfaceEndpoint (JNIEnv *env, jobject interface, jint address) {
+  if (usbFindHelperClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, usbHelperClass, "getInterfaceEndpoint",
+                             JAVA_SIG_METHOD(JAVA_SIG_USB_ENDPOINT,
+                                             JAVA_SIG_USB_INTERFACE // interface
+                                             JAVA_SIG_INT // address
+                                            ))) {
+      jobject endpoint = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, interface, address);
+
+      if (endpoint) return endpoint;
+      clearJavaException(env, 1);
+      errno = EIO;
+    }
+  }
+
+  return NULL;
+}
+
+static jobject
+usbOpenDeviceConnection (JNIEnv *env, jobject device) {
+  logMessage(LOG_CATEGORY(USB_IO), "opening device connection");
+
+  if (usbFindHelperClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaStaticMethod(env, &method, usbHelperClass, "openDeviceConnection",
+                             JAVA_SIG_METHOD(JAVA_SIG_USB_CONNECTION, JAVA_SIG_USB_DEVICE))) {
+      jobject connection = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, device);
+
+      if (!clearJavaException(env, 1)) {
+        if (connection) {
+          return connection;
+        }
+      }
+
+      errno = EIO;
+    }
+  }
+
+  return NULL;
+}
+
+static int
+usbFindDeviceClass (JNIEnv *env) {
+  return findJavaClass(env, &usbDeviceClass, JAVA_OBJ_USB_DEVICE);
+}
+
+static int
+usbGetIntDeviceProperty (
+  JNIEnv *env, jint *value,
+  jobject device, const char *methodName, jmethodID *methodIdentifier
+) {
+  if (usbFindDeviceClass(env)) {
+    if (findJavaInstanceMethod(env, methodIdentifier, usbDeviceClass, methodName,
+                               JAVA_SIG_METHOD(JAVA_SIG_INT, ))) {
+      if (!clearJavaException(env, 1)) {
+        *value = (*env)->CallIntMethod(env, device, *methodIdentifier);
+        return 1;
+      }
+
+      errno = EIO;
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbGetDeviceVendor (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) {
+  static jmethodID method = 0;
+
+  jint vendor;
+  int ok = usbGetIntDeviceProperty(env, &vendor, device, "getVendorId", &method);
+
+  if (ok) putLittleEndian16(&descriptor->idVendor, vendor);
+  return ok;
+}
+
+static int
+usbGetDeviceProduct (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) {
+  static jmethodID method = 0;
+
+  jint product;
+  int ok = usbGetIntDeviceProperty(env, &product, device, "getProductId", &method);
+
+  if (ok) putLittleEndian16(&descriptor->idProduct, product);
+  return ok;
+}
+
+static int
+usbGetDeviceClass (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) {
+  static jmethodID method = 0;
+
+  jint class;
+  int ok = usbGetIntDeviceProperty(env, &class, device, "getDeviceClass", &method);
+
+  if (ok) descriptor->bDeviceClass = class;
+  return ok;
+}
+
+static int
+usbGetDeviceSubclass (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) {
+  static jmethodID method = 0;
+
+  jint subclass;
+  int ok = usbGetIntDeviceProperty(env, &subclass, device, "getDeviceSubclass", &method);
+
+  if (ok) descriptor->bDeviceSubClass = subclass;
+  return ok;
+}
+
+static int
+usbGetDeviceProtocol (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) {
+  static jmethodID method = 0;
+
+  jint protocol;
+  int ok = usbGetIntDeviceProperty(env, &protocol, device, "getDeviceProtocol", &method);
+
+  if (ok) descriptor->bDeviceProtocol = protocol;
+  return ok;
+}
+
+static int
+usbFindInterfaceClass (JNIEnv *env) {
+  return findJavaClass(env, &usbInterfaceClass, JAVA_OBJ_USB_INTERFACE);
+}
+
+static int
+usbGetInterfaceIdentifier (JNIEnv *env, uint8_t *identifier, jobject interface) {
+  if (usbFindInterfaceClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbInterfaceClass, "getId",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                              ))) {
+      jint result = (*env)->CallIntMethod(env, interface, method);
+
+      if (!clearJavaException(env, 1)) {
+        *identifier = result;
+        return 1;
+      }
+
+      errno = EIO;
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbFindConnectionClass (JNIEnv *env) {
+  return findJavaClass(env, &usbConnectionClass, JAVA_OBJ_USB_CONNECTION);
+}
+
+static int
+usbDoClaimInterface (JNIEnv *env, jobject connection, jobject interface) {
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "claimInterface",
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                               JAVA_SIG_USB_INTERFACE // interface
+                                               JAVA_SIG_BOOLEAN // force
+                                              ))) {
+      jboolean result = (*env)->CallBooleanMethod(env, connection, method, interface, JNI_TRUE);
+
+      if (clearJavaException(env, 1)) {
+        errno = EIO;
+      } else if (result) {
+        return 1;
+      } else {
+        logSystemError("USB claim interface");
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbDoReleaseInterface (JNIEnv *env, jobject connection, jobject interface) {
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "releaseInterface",
+                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
+                                               JAVA_SIG_USB_INTERFACE // interface
+                                              ))) {
+      jboolean result = (*env)->CallBooleanMethod(env, connection, method, interface);
+
+      if (clearJavaException(env, 1)) {
+        errno = EIO;
+      } else if (result) {
+        return 1;
+      } else {
+        logSystemError("USB release interface");
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbDoControlTransfer (
+  JNIEnv *env, jobject connection,
+  int type, int request, int value, int index,
+  jbyteArray buffer, int length, int timeout
+) {
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "controlTransfer",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                               JAVA_SIG_INT // type
+                                               JAVA_SIG_INT // request
+                                               JAVA_SIG_INT // value
+                                               JAVA_SIG_INT // index
+                                               JAVA_SIG_ARRAY(JAVA_SIG_BYTE) // buffer
+                                               JAVA_SIG_INT // length
+                                               JAVA_SIG_INT // timeout
+                                              ))) {
+      jint result = (*env)->CallIntMethod(env, connection, method,
+                                          type, request, value, index,
+                                          buffer, length, timeout);
+
+      if (!clearJavaException(env, 1)) return result;
+      errno = EIO;
+    }
+  }
+
+  return -1;
+}
+
+static int
+usbDoBulkTransfer (
+  JNIEnv *env, jobject connection, jobject endpoint,
+  jbyteArray buffer, int length, int timeout
+) {
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "bulkTransfer",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT,
+                                               JAVA_SIG_USB_ENDPOINT // endpoint
+                                               JAVA_SIG_ARRAY(JAVA_SIG_BYTE) // buffer
+                                               JAVA_SIG_INT // length
+                                               JAVA_SIG_INT // timeout
+                                              ))) {
+      jint result = (*env)->CallIntMethod(env, connection, method,
+                                          endpoint, buffer, length, timeout);
+
+      if (!clearJavaException(env, 1)) return result;
+      errno = EIO;
+    }
+  }
+
+  return -1;
+}
+
+static void
+usbCloseDeviceConnection (JNIEnv *env, jobject connection) {
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "close",
+                               JAVA_SIG_METHOD(JAVA_SIG_VOID, ))) {
+      (*env)->CallVoidMethod(env, connection, method);
+      clearJavaException(env, 1);
+    }
+  }
+}
+
+static int
+usbOpenConnection (UsbDeviceExtension *devx) {
+  if (devx->connection) return 1;
+
+  {
+    const UsbHostDevice *host = devx->host;
+    jobject connection = usbOpenDeviceConnection(host->env, host->device);
+
+    if (connection) {
+      devx->connection = (*host->env)->NewGlobalRef(host->env, connection);
+
+      (*host->env)->DeleteLocalRef(host->env, connection);
+      connection = NULL;
+
+      if (devx->connection) return 1;
+      logMallocError();
+      clearJavaException(host->env, 0);
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbGetFileDescriptor (UsbDeviceExtension *devx) {
+  if (devx->fileDescriptor != INVALID_FILE_DESCRIPTOR) return 1;
+  JNIEnv *env = devx->host->env;
+
+  if (usbFindConnectionClass(env)) {
+    static jmethodID method = 0;
+
+    if (findJavaInstanceMethod(env, &method, usbConnectionClass, "getFileDescriptor",
+                               JAVA_SIG_METHOD(JAVA_SIG_INT, ))) {
+      jint fileDescriptor = (*env)->CallIntMethod(env, devx->connection, method);
+
+      if (!clearJavaException(env, 1)) {
+        devx->fileDescriptor = fileDescriptor;
+        return 1;
+      }
+    }
+  }
+
+  errno = EIO;
+  return 0;
+}
+
+static void
+usbUnsetInterface (UsbDeviceExtension *devx) {
+  if (devx->interface) {
+    JNIEnv *env = devx->host->env;
+
+    (*env)->DeleteGlobalRef(env, devx->interface);
+    devx->interface = NULL;
+  }
+}
+
+static int
+usbSetInterface (UsbDeviceExtension *devx, uint8_t identifier) {
+  JNIEnv *env = devx->host->env;
+
+  if (devx->interface) {
+    uint8_t id;
+
+    if (!usbGetInterfaceIdentifier(env, &id, devx->interface)) return 0;
+    if (id == identifier) return 1;
+  }
+
+  {
+    jobject interface = usbGetDeviceInterface(env, devx->host->device, identifier);
+
+    if (interface) {
+      usbUnsetInterface(devx);
+      devx->interface = (*env)->NewGlobalRef(env, interface);
+
+      (*env)->DeleteLocalRef(env, interface);
+      interface = NULL;
+
+      if (devx->interface) return 1;
+      logMallocError();
+    }
+  }
+
+  return 0;
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration);
+  if (configuration == 1) return 1;
+
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface);
+
+  if (usbSetInterface(devx, interface)) {
+    if (usbOpenConnection(devx)) {
+      if (usbDoClaimInterface(devx->host->env, devx->connection, devx->interface)) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface);
+
+  if (usbSetInterface(devx, interface)) {
+    if (usbOpenConnection(devx)) {
+      if (usbDoReleaseInterface(devx->host->env, devx->connection, devx->interface)) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative);
+  if (alternative == 0) return 1;
+
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "reset device");
+
+  if (usbGetFileDescriptor(devx)) {
+    unsigned int arg = 0;
+    if (ioctl(devx->fileDescriptor, USBDEVFS_RESET, &arg) != -1) return 1;
+    logSystemError("USB device reset");
+  }
+
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress);
+
+  if (usbGetFileDescriptor(devx)) {
+    unsigned int arg = endpointAddress;
+    if (ioctl(devx->fileDescriptor, USBDEVFS_CLEAR_HALT, &arg) != -1) return 1;
+    logSystemError("USB endpoint clear");
+  }
+
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  ssize_t result = -1;
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenConnection(devx)) {
+    const UsbHostDevice *host = devx->host;
+    jbyteArray bytes = (*host->env)->NewByteArray(host->env, length);
+
+    if (bytes) {
+      if (direction == UsbControlDirection_Output) {
+        (*host->env)->SetByteArrayRegion(host->env, bytes, 0, length, buffer);
+        if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length);
+      }
+
+      result = usbDoControlTransfer(host->env, devx->connection,
+                                    direction | recipient | type,
+                                    request, value, index,
+                                    bytes, length, timeout);
+
+      if (direction == UsbControlDirection_Input) {
+        if (result > 0) {
+          (*host->env)->GetByteArrayRegion(host->env, bytes, 0, result, buffer);
+          logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, result);
+        }
+      }
+
+      (*host->env)->DeleteLocalRef(host->env, bytes);
+    } else {
+      logMallocError();
+    }
+  }
+
+  if (result == -1) logSystemError("USB control transfer");
+  return result;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  ssize_t result = -1;
+  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
+
+  logMessage(LOG_CATEGORY(USB_IO), "reading endpoint: %u", endpointNumber);
+
+  if (endpoint) {
+    UsbDeviceExtension *devx = device->extension;
+
+    if (usbOpenConnection(devx)) {
+      const UsbHostDevice *host = devx->host;
+      JNIEnv *env = host->env;
+      jbyteArray bytes = (*env)->NewByteArray(env, length);
+
+      if (bytes) {
+        UsbEndpointExtension *eptx = endpoint->extension;
+
+        result = usbDoBulkTransfer(env, devx->connection, eptx->object, bytes, length, timeout);
+        if (result > 0) (*env)->GetByteArrayRegion(env, bytes, 0, result, buffer);
+
+        (*env)->DeleteLocalRef(env, bytes);
+      } else {
+        logMallocError();
+        clearJavaException(env, 0);
+      }
+    }
+  }
+
+  if (result >= 0) {
+    if (!usbApplyInputFilters(endpoint, buffer, length, &result)) {
+      errno = EIO;
+      result = -1;
+    }
+  }
+
+  if (result == -1) {
+    if (errno == ETIMEDOUT) errno = EAGAIN;
+    if (errno != EAGAIN) logSystemError("USB bulk read");
+  }
+
+  return result;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  ssize_t result = -1;
+  UsbEndpoint *endpoint = usbGetOutputEndpoint(device, endpointNumber);
+
+  if (endpoint) {
+    UsbDeviceExtension *devx = device->extension;
+
+    usbLogEndpointData(endpoint, "output", buffer, length);
+
+    if (usbOpenConnection(devx)) {
+      const UsbHostDevice *host = devx->host;
+      JNIEnv *env = host->env;
+      jbyteArray bytes = (*env)->NewByteArray(env, length);
+
+      if (bytes) {
+        UsbEndpointExtension *eptx = endpoint->extension;
+
+        (*env)->SetByteArrayRegion(env, bytes, 0, length, buffer);
+        result = usbDoBulkTransfer(env, devx->connection, eptx->object, bytes, length, timeout);
+
+        (*env)->DeleteLocalRef(env, bytes);
+      } else {
+        logMallocError();
+        clearJavaException(env, 0);
+      }
+    }
+  }
+
+  if (result == -1) logSystemError("USB bulk write");
+  return result;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  device->descriptor = device->extension->host->descriptor;
+  return 1;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  UsbDevice *device = endpoint->device;
+  const UsbInterfaceDescriptor *interface = endpoint->interface;
+
+  UsbDeviceExtension *devx = device->extension;
+  const UsbHostDevice *host = devx->host;
+  JNIEnv *env = host->env;
+
+  if (usbSetInterface(devx, interface->bInterfaceNumber)) {
+    jobject localReference = usbGetInterfaceEndpoint(env, devx->interface, endpoint->descriptor->bEndpointAddress);
+
+    if (localReference) {
+      jobject globalReference = (*env)->NewGlobalRef(env, localReference);
+
+      (*env)->DeleteLocalRef(env, localReference);
+      localReference = NULL;
+
+      if (globalReference) {
+        UsbEndpointExtension *eptx = malloc(sizeof(*eptx));
+
+        if (eptx) {
+          memset(eptx, 0, sizeof(*eptx));
+          eptx->endpoint = endpoint;
+          eptx->object = globalReference;
+
+          endpoint->extension = eptx;
+          return 1;
+        } else {
+          logMallocError();
+        }
+
+        (*env)->DeleteGlobalRef(env, globalReference);
+        globalReference = NULL;
+      } else {
+        logMallocError();
+        clearJavaException(env, 0);
+      }
+    } else {
+      logMessage(LOG_ERR, "couldn't get endpoint object");
+      errno = EIO;
+    }
+  } else {
+    errno = ENOSYS;
+  }
+
+  return 0;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+  UsbEndpoint *endpoint = eptx->endpoint;
+  UsbDevice *device = endpoint->device;
+  UsbDeviceExtension *devx = device->extension;
+  const UsbHostDevice *host = devx->host;
+  JNIEnv *env = host->env;
+
+  if (eptx->object) {
+    (*env)->DeleteGlobalRef(env, eptx->object);
+    eptx->object = NULL;
+  }
+
+  free(eptx);
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  usbUnsetInterface(devx);
+
+  if (devx->connection) {
+    const UsbHostDevice *host = devx->host;
+
+    usbCloseDeviceConnection(host->env, devx->connection);
+    (*host->env)->DeleteGlobalRef(host->env, devx->connection);
+    devx->connection = NULL;
+  }
+
+  free(devx);
+}
+
+static void
+usbDeallocateHostDevice (void *item, void *data) {
+  UsbHostDevice *host = item;
+
+  (*host->env)->DeleteGlobalRef(host->env, host->device);
+  free(host);
+}
+
+static int
+usbAddHostDevice (JNIEnv *env, jobject device) {
+  UsbHostDevice *host;
+
+  if ((host = malloc(sizeof(*host)))) {
+    memset(host, 0, sizeof(*host));
+    host->env = env;
+
+    host->descriptor.bLength = UsbDescriptorSize_Device;
+    host->descriptor.bDescriptorType = UsbDescriptorType_Device;
+    host->descriptor.bNumConfigurations = 1;
+
+    if ((host->device = (*host->env)->NewGlobalRef(host->env, device))) {
+      if (usbGetDeviceVendor(host->env, host->device, &host->descriptor)) {
+        if (usbGetDeviceProduct(host->env, host->device, &host->descriptor)) {
+          if (usbGetDeviceClass(host->env, host->device, &host->descriptor)) {
+            if (usbGetDeviceSubclass(host->env, host->device, &host->descriptor)) {
+              if (usbGetDeviceProtocol(host->env, host->device, &host->descriptor)) {
+                if (enqueueItem(usbHostDevices, host)) {
+                  return 1;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      (*host->env)->DeleteGlobalRef(host->env, host->device);
+    }
+
+    free(host);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+typedef struct {
+  UsbDeviceChooser *chooser;
+  void *data;
+  UsbDevice *device;
+} UsbTestHostDeviceData;
+
+static int
+usbTestHostDevice (void *item, void *data) {
+  const UsbHostDevice *host = item;
+  UsbTestHostDeviceData *test = data;
+  UsbDeviceExtension *devx;
+
+  if ((devx = malloc(sizeof(*devx)))) {
+    memset(devx, 0, sizeof(*devx));
+    devx->host = host;
+    devx->connection = NULL;
+    devx->interface = NULL;
+    devx->fileDescriptor = INVALID_FILE_DESCRIPTOR;
+
+    if ((test->device = usbTestDevice(devx, test->chooser, test->data))) return 1;
+
+    usbDeallocateDeviceExtension(devx);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  if (!usbHostDevices) {
+    int ok = 0;
+
+    if ((usbHostDevices = newQueue(usbDeallocateHostDevice, NULL))) {
+      JNIEnv *env = getJavaNativeInterface();
+
+      if (env) {
+        jobject iterator = usbGetDeviceIterator(env);
+
+        if (iterator) {
+          jobject device;
+
+          ok = 1;
+          while ((device = usbGetNextDevice(env, iterator))) {
+            int added = usbAddHostDevice(env, device);
+            (*env)->DeleteLocalRef(env, device);
+
+            if (!added) {
+              ok = 0;
+              break;
+            }
+          }
+
+          (*env)->DeleteLocalRef(env, iterator);
+        }
+      }
+
+      if (!ok) {
+        deallocateQueue(usbHostDevices);
+        usbHostDevices = NULL;
+      }
+    }
+  }
+
+  if (usbHostDevices) {
+    UsbTestHostDeviceData test = {
+      .chooser = chooser,
+      .data = data,
+      .device = NULL
+    };
+
+    if (processQueue(usbHostDevices, usbTestHostDevice, &test)) return test.device;
+  }
+
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+  if (usbHostDevices) {
+    deallocateQueue(usbHostDevices);
+    usbHostDevices = NULL;
+  }
+}
diff --git a/Programs/usb_belkin.c b/Programs/usb_belkin.c
new file mode 100644
index 0000000..1cb5885
--- /dev/null
+++ b/Programs/usb_belkin.c
@@ -0,0 +1,142 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "usb_serial.h"
+#include "usb_belkin.h"
+
+static int
+usbSetAttribute_Belkin (UsbDevice *device, unsigned char request, unsigned int value, unsigned int index) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "Belkin request: %02X %04X %04X", request, value, index);
+  return usbControlWrite(device, UsbControlRecipient_Device, UsbControlType_Vendor,
+                         request, value, index, NULL, 0, 1000) != -1;
+}
+
+static int
+usbSetBaud_Belkin (UsbDevice *device, unsigned int baud) {
+  const unsigned int base = 230400;
+  if (base % baud) {
+    logUnsupportedBaud(baud);
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_Belkin(device, 0, base/baud, 0);
+}
+
+static int
+usbSetFlowControl_Belkin (UsbDevice *device, SerialFlowControl flow) {
+  unsigned int value = 0;
+#define BELKIN_FLOW(from,to) if ((flow & (from)) == (from)) flow &= ~(from), value |= (to)
+  BELKIN_FLOW(SERIAL_FLOW_OUTPUT_CTS, 0X0001);
+  BELKIN_FLOW(SERIAL_FLOW_OUTPUT_DSR, 0X0002);
+  BELKIN_FLOW(SERIAL_FLOW_INPUT_DSR , 0X0004);
+  BELKIN_FLOW(SERIAL_FLOW_INPUT_DTR , 0X0008);
+  BELKIN_FLOW(SERIAL_FLOW_INPUT_RTS , 0X0010);
+  BELKIN_FLOW(SERIAL_FLOW_OUTPUT_RTS, 0X0020);
+  BELKIN_FLOW(SERIAL_FLOW_OUTPUT_XON, 0X0080);
+  BELKIN_FLOW(SERIAL_FLOW_INPUT_XON , 0X0100);
+#undef BELKIN_FLOW
+  if (flow) {
+    logUnsupportedFlowControl(flow);
+  }
+  return usbSetAttribute_Belkin(device, 16, value, 0);
+}
+
+static int
+usbSetDataBits_Belkin (UsbDevice *device, unsigned int bits) {
+  if ((bits < 5) || (bits > 8)) {
+    logUnsupportedDataBits(bits);
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_Belkin(device, 2, bits-5, 0);
+}
+
+static int
+usbSetStopBits_Belkin (UsbDevice *device, SerialStopBits bits) {
+  unsigned int value;
+  switch (bits) {
+    case SERIAL_STOP_1: value = 0; break;
+    case SERIAL_STOP_2: value = 1; break;
+    default:
+      logUnsupportedStopBits(bits);
+      errno = EINVAL;
+      return 0;
+  }
+  return usbSetAttribute_Belkin(device, 1, value, 0);
+}
+
+static int
+usbSetParity_Belkin (UsbDevice *device, SerialParity parity) {
+  unsigned int value;
+  switch (parity) {
+    case SERIAL_PARITY_SPACE: value = 4; break;
+    case SERIAL_PARITY_ODD:   value = 2; break;
+    case SERIAL_PARITY_EVEN:  value = 1; break;
+    case SERIAL_PARITY_MARK:  value = 3; break;
+    case SERIAL_PARITY_NONE:  value = 0; break;
+    default:
+      logUnsupportedParity(parity);
+      errno = EINVAL;
+      return 0;
+  }
+  return usbSetAttribute_Belkin(device, 3, value, 0);
+}
+
+static int
+usbSetDataFormat_Belkin (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
+  if (usbSetDataBits_Belkin(device, dataBits))
+    if (usbSetStopBits_Belkin(device, stopBits))
+      if (usbSetParity_Belkin(device, parity))
+        return 1;
+  return 0;
+}
+
+static int
+usbSetDtrState_Belkin (UsbDevice *device, int state) {
+  if ((state < 0) || (state > 1)) {
+    logMessage(LOG_WARNING, "Unsupported Belkin DTR state: %d", state);
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_Belkin(device, 10, state, 0);
+}
+
+static int
+usbSetRtsState_Belkin (UsbDevice *device, int state) {
+  if ((state < 0) || (state > 1)) {
+    logMessage(LOG_WARNING, "Unsupported Belkin RTS state: %d", state);
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_Belkin(device, 11, state, 0);
+}
+
+const UsbSerialOperations usbSerialOperations_Belkin = {
+  .name = "Belkin",
+  .setBaud = usbSetBaud_Belkin,
+  .setDataFormat = usbSetDataFormat_Belkin,
+  .setFlowControl = usbSetFlowControl_Belkin,
+  .setDtrState = usbSetDtrState_Belkin,
+  .setRtsState = usbSetRtsState_Belkin
+};
diff --git a/Programs/usb_belkin.h b/Programs/usb_belkin.h
new file mode 100644
index 0000000..4edc49b
--- /dev/null
+++ b/Programs/usb_belkin.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_BELKIN
+#define BRLTTY_INCLUDED_USB_BELKIN
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_BELKIN */
diff --git a/Programs/usb_bsd.h b/Programs/usb_bsd.h
new file mode 100644
index 0000000..53c9b83
--- /dev/null
+++ b/Programs/usb_bsd.h
@@ -0,0 +1,365 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include <limits.h>
+
+#include "log.h"
+
+struct UsbDeviceExtensionStruct {
+  char *path;
+  int file;
+  int timeout;
+};
+
+struct UsbEndpointExtensionStruct {
+  int file;
+  int timeout;
+};
+
+static int
+usbSetTimeout (int file, int new, int *old) {
+  if (!old || (new != *old)) {
+    int arg = new;
+    if (ioctl(file, USB_SET_TIMEOUT, &arg) == -1) {
+      logSystemError("USB timeout set");
+      return 0;
+    }
+    if (old) *old = new;
+  }
+  return 1;
+}
+
+static int
+usbSetShortTransfers (int file, int arg) {
+  if (ioctl(file, USB_SET_SHORT_XFER, &arg) != -1) return 1;
+  logSystemError("USB set short transfers");
+  return 0;
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+  int arg = configuration;
+  if (ioctl(devx->file, USB_SET_CONFIG, &arg) != -1) return 1;
+  logSystemError("USB configuration set");
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  return 1;
+/*
+  logUnsupportedFunction();
+  return 0;
+*/
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  return 1;
+/*
+  logUnsupportedFunction();
+  return 0;
+*/
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+  struct usb_alt_interface arg;
+  memset(&arg, 0, sizeof(arg));
+  arg.uai_interface_index = interface;
+  arg.uai_alt_no = alternative;
+  if (ioctl(devx->file, USB_SET_ALTINTERFACE, &arg) != -1) return 1;
+  logSystemError("USB alternative set");
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  struct usb_ctl_request arg;
+  memset(&arg, 0, sizeof(arg));
+  arg.ucr_request.bmRequestType = direction | recipient | type;
+  arg.ucr_request.bRequest = request;
+  USETW(arg.ucr_request.wValue, value);
+  USETW(arg.ucr_request.wIndex, index);
+  USETW(arg.ucr_request.wLength, length);
+  arg.ucr_data = buffer;
+  arg.ucr_flags = USBD_SHORT_XFER_OK;
+  if (usbSetTimeout(devx->file, timeout, &devx->timeout)) {
+    if (ioctl(devx->file, USB_DO_REQUEST, &arg) != -1) return arg.ucr_actlen;
+    logSystemError("USB control transfer");
+  }
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  ssize_t count = -1;
+  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
+  if (endpoint) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    if (usbSetTimeout(eptx->file, timeout, &eptx->timeout)) {
+      if ((count = read(eptx->file, buffer, length)) != -1) {
+        if (!usbApplyInputFilters(endpoint, buffer, length, &count)) {
+          errno = EIO;
+          count = -1;
+        }
+      } else if (errno != ETIMEDOUT) {
+        logSystemError("USB endpoint read");
+      }
+    }
+  }
+  return count;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbEndpoint *endpoint = usbGetOutputEndpoint(device, endpointNumber);
+  if (endpoint) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    if (usbSetTimeout(eptx->file, timeout, &eptx->timeout)) {
+      ssize_t count = write(eptx->file, buffer, length);
+      if (count != -1) return count;
+      logSystemError("USB endpoint write");
+    }
+  }
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+  if (ioctl(devx->file, USB_GET_DEVICE_DESC, &device->descriptor) != -1) {
+    return 1;
+  }
+  logSystemError("USB device descriptor read");
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  UsbDeviceExtension *devx = endpoint->device->extension;
+  UsbEndpointExtension *eptx;
+
+  if ((eptx = malloc(sizeof(*eptx)))) {
+    const char *prefix = devx->path;
+    const char *dot = strchr(prefix, '.');
+    int length = dot? (dot - prefix): strlen(prefix);
+    char path[PATH_MAX+1];
+    int flags = O_RDWR;
+
+    snprintf(path, sizeof(path), USB_ENDPOINT_PATH_FORMAT,
+             length, prefix, USB_ENDPOINT_NUMBER(endpoint->descriptor));
+
+    switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+      case UsbEndpointDirection_Input : flags = O_RDONLY; break;
+      case UsbEndpointDirection_Output: flags = O_WRONLY; break;
+    }
+
+    if ((eptx->file = open(path, flags)) != -1) {
+      if (((flags & O_ACCMODE) != O_RDONLY) || 
+          usbSetShortTransfers(eptx->file, 1)) {
+        eptx->timeout = -1;
+
+        endpoint->extension = eptx;
+        return 1;
+      }
+
+      close(eptx->file);
+    }
+
+    free(eptx);
+  }
+
+  return 0;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+  if (eptx->file != -1) {
+    close(eptx->file);
+    eptx->file = -1;
+  }
+
+  free(eptx);
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  if (devx->file != -1) {
+    close(devx->file);
+    devx->file = -1;
+  }
+
+  free(devx->path);
+  free(devx);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  UsbDevice *device = NULL;
+  int busNumber = 0;
+  while (1) {
+    char busPath[PATH_MAX+1];
+    int bus;
+    snprintf(busPath, sizeof(busPath), "/dev/usb%d", busNumber);
+    if ((bus = open(busPath, O_RDONLY)) != -1) {
+      int deviceNumber;
+      for (deviceNumber=1; deviceNumber<USB_MAX_DEVICES; deviceNumber++) {
+        struct usb_device_info info;
+        memset(&info, 0, sizeof(info));
+        info.udi_addr = deviceNumber;
+        if (ioctl(bus, USB_DEVICEINFO, &info) != -1) {
+          static const char *driver = "ugen";
+          const char *deviceName = info.udi_devnames[0];
+
+          logMessage(LOG_CATEGORY(USB_IO), "device [%d,%d]: vendor=%s product=%s",
+                     busNumber, deviceNumber, info.udi_vendor, info.udi_product);
+          {
+            int nameNumber;
+            for (nameNumber=0; nameNumber<USB_MAX_DEVNAMES; nameNumber++) {
+              const char *name = info.udi_devnames[nameNumber];
+              if (*name)
+                logMessage(LOG_CATEGORY(USB_IO), "name %d: %s", nameNumber, name);
+            }
+          }
+
+          if (strncmp(deviceName, driver, strlen(driver)) == 0) {
+            char devicePath[PATH_MAX+1];
+            snprintf(devicePath, sizeof(devicePath), USB_CONTROL_PATH_FORMAT, deviceName);
+
+            {
+              UsbDeviceExtension *devx;
+              if ((devx = malloc(sizeof(*devx)))) {
+                if ((devx->path = strdup(devicePath))) {
+                  if ((devx->file = open(devx->path, O_RDWR)) != -1) {
+                    devx->timeout = -1;
+                    if ((device = usbTestDevice(devx, chooser, data))) {
+                      close(bus);
+                      return device;
+                    }
+                    close(devx->file);
+                  }
+                  free(devx->path);
+                }
+                free(devx);
+              }
+            }
+          }
+        } else if (errno != ENXIO) {
+          logSystemError("USB device query");
+        }
+      }
+      close(bus);
+    } else if (errno == ENOENT) {
+      break;
+    } else if (errno != ENXIO) {
+      logSystemError("USB bus open");
+    }
+    busNumber++;
+  }
+  return device;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_cdc_acm.c b/Programs/usb_cdc_acm.c
new file mode 100644
index 0000000..801986b
--- /dev/null
+++ b/Programs/usb_cdc_acm.c
@@ -0,0 +1,344 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "usb_serial.h"
+#include "usb_cdc_acm.h"
+#include "usb_internal.h"
+#include "bitfield.h"
+
+struct UsbSerialDataStruct {
+  UsbDevice *device;
+  const UsbInterfaceDescriptor *interface;
+  const UsbEndpointDescriptor *endpoint;
+
+  USB_CDC_ACM_LineCoding lineCoding;
+};
+
+static int
+usbGetParameters_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value, void *data, uint16_t size) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  ssize_t result = usbControlRead(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    request, value, usd->interface->bInterfaceNumber,
+    data, size, 1000
+  );
+
+  return result != -1;
+}
+
+static int
+usbGetParameter_CDC_ACM (UsbDevice *device, uint8_t request, void *data, uint16_t size) {
+  return usbGetParameters_CDC_ACM(device, request, 0, data, size);
+}
+
+static int
+usbSetParameters_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value, const void *data, uint16_t size) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  ssize_t result = usbControlWrite(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    request, value, usd->interface->bInterfaceNumber,
+    data, size, 1000
+  );
+
+  return result != -1;
+}
+
+static int
+usbSetParameter_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value) {
+  return usbSetParameters_CDC_ACM(device, request, value, NULL, 0);
+}
+
+static int
+usbSetControlLines_CDC_ACM (UsbDevice *device, uint16_t lines) {
+  return usbSetParameter_CDC_ACM(device, USB_CDC_ACM_CTL_SetControlLineState, lines);
+}
+
+static void
+usbLogLineCoding_CDC_ACM (const USB_CDC_ACM_LineCoding *lineCoding) {
+  char log[0X80];
+
+  STR_BEGIN(log, sizeof(log));
+  STR_PRINTF("CDC ACM line coding:");
+
+  { // baud (bits per second)
+    uint32_t baud = getLittleEndian32(lineCoding->dwDTERate);
+    STR_PRINTF(" Baud:%" PRIu32, baud);
+  }
+
+  { // number of data bits
+    STR_PRINTF(" Data:%u", lineCoding->bDataBits);
+  }
+
+  { // number of stop bits
+    const char *bits;
+
+#define USB_CDC_ACM_STOP(value,name) \
+case USB_CDC_ACM_STOP_##value: bits = #name; break;
+    switch (lineCoding->bCharFormat) {
+      USB_CDC_ACM_STOP(1  , 1  )
+      USB_CDC_ACM_STOP(1_5, 1.5)
+      USB_CDC_ACM_STOP(2  , 2  )
+      default: bits = "?"; break;
+    }
+#undef USB_CDC_ACM_STOP
+
+    STR_PRINTF(" Stop:%s", bits);
+  }
+
+  { // type of parity
+    const char *parity;
+
+#define USB_CDC_ACM_PARITY(value,name) \
+case USB_CDC_ACM_PARITY_##value: parity = #name; break;
+    switch (lineCoding->bParityType) {
+      USB_CDC_ACM_PARITY(NONE , none )
+      USB_CDC_ACM_PARITY(ODD  , odd  )
+      USB_CDC_ACM_PARITY(EVEN , even )
+      USB_CDC_ACM_PARITY(MARK , mark )
+      USB_CDC_ACM_PARITY(SPACE, space)
+      default: parity = "?"; break;
+    }
+#undef USB_CDC_ACM_PARITY
+
+    STR_PRINTF(" Parity:%s", parity);
+  }
+
+  STR_END;
+  logMessage(LOG_CATEGORY(SERIAL_IO), "%s", log);
+}
+
+static int
+usbSetLineProperties_CDC_ACM (UsbDevice *device, unsigned int baud, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
+  USB_CDC_ACM_LineCoding lineCoding;
+  memset(&lineCoding, 0, sizeof(lineCoding));
+
+  putLittleEndian32(&lineCoding.dwDTERate, baud);
+
+  switch (dataBits) {
+    case  5:
+    case  6:
+    case  7:
+    case  8:
+    case 16:
+      lineCoding.bDataBits = dataBits;
+      break;
+
+    default:
+      logUnsupportedDataBits(dataBits);
+      errno = EINVAL;
+      return 0;
+  }
+
+  switch (stopBits) {
+    case SERIAL_STOP_1:
+      lineCoding.bCharFormat = USB_CDC_ACM_STOP_1;
+      break;
+
+    case SERIAL_STOP_1_5:
+      lineCoding.bCharFormat = USB_CDC_ACM_STOP_1_5;
+      break;
+
+    case SERIAL_STOP_2:
+      lineCoding.bCharFormat = USB_CDC_ACM_STOP_2;
+      break;
+
+    default:
+      logUnsupportedStopBits(stopBits);
+      errno = EINVAL;
+      return 0;
+  }
+
+  switch (parity) {
+    case SERIAL_PARITY_NONE:
+      lineCoding.bParityType = USB_CDC_ACM_PARITY_NONE;
+      break;
+
+    case SERIAL_PARITY_ODD:
+      lineCoding.bParityType = USB_CDC_ACM_PARITY_ODD;
+      break;
+
+    case SERIAL_PARITY_EVEN:
+      lineCoding.bParityType = USB_CDC_ACM_PARITY_EVEN;
+      break;
+
+    case SERIAL_PARITY_MARK:
+      lineCoding.bParityType = USB_CDC_ACM_PARITY_MARK;
+      break;
+
+    case SERIAL_PARITY_SPACE:
+      lineCoding.bParityType = USB_CDC_ACM_PARITY_SPACE;
+      break;
+
+    default:
+      logUnsupportedParity(parity);
+      errno = EINVAL;
+      return 0;
+  }
+
+  {
+    UsbSerialData *usd = usbGetSerialData(device);
+    USB_CDC_ACM_LineCoding *oldCoding = &usd->lineCoding;
+
+    if (memcmp(&lineCoding, oldCoding, sizeof(lineCoding)) != 0) {
+      if (!usbSetParameters_CDC_ACM(device, USB_CDC_ACM_CTL_SetLineCoding, 0,
+                                    &lineCoding, sizeof(lineCoding))) {
+        return 0;
+      }
+
+      *oldCoding = lineCoding;
+      usbLogLineCoding_CDC_ACM(&lineCoding);
+    }
+  }
+
+  return 1;
+}
+
+static int
+usbSetFlowControl_CDC_ACM (UsbDevice *device, SerialFlowControl flow) {
+  if (flow) {
+    logUnsupportedFlowControl(flow);
+    errno = EINVAL;
+    return 0;
+  }
+
+  return 1;
+}
+
+static const UsbInterfaceDescriptor *
+usbFindCommunicationInterface (UsbDevice *device) {
+  const UsbDescriptor *descriptor = NULL;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_Interface) {
+      if (descriptor->interface.bInterfaceClass == 0X02) {
+        return &descriptor->interface;
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "USB: communication interface descriptor not found");
+  errno = ENOENT;
+  return NULL;
+}
+
+static const UsbEndpointDescriptor *
+usbFindInterruptInputEndpoint (UsbDevice *device, const UsbInterfaceDescriptor *interface) {
+  const UsbDescriptor *descriptor = (const UsbDescriptor *)interface;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_Interface) break;
+
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_Endpoint) {
+      if (USB_ENDPOINT_DIRECTION(&descriptor->endpoint) == UsbEndpointDirection_Input) {
+        if (USB_ENDPOINT_TRANSFER(&descriptor->endpoint) == UsbEndpointTransfer_Interrupt) {
+          return &descriptor->endpoint;
+        }
+      }
+    }
+  }
+
+  logMessage(LOG_WARNING, "USB: interrupt input endpoint descriptor not found");
+  errno = ENOENT;
+  return NULL;
+}
+
+static int
+usbMakeData_CDC_ACM (UsbDevice *device, UsbSerialData **serialData) {
+  UsbSerialData *usd;
+
+  if ((usd = malloc(sizeof(*usd)))) {
+    memset(usd, 0, sizeof(*usd));
+    usd->device = device;
+
+    if ((usd->interface = usbFindCommunicationInterface(device))) {
+      unsigned char interfaceNumber = usd->interface->bInterfaceNumber;
+
+      if (usbClaimInterface(device, interfaceNumber)) {
+        if (usbSetAlternative(device, usd->interface->bInterfaceNumber, usd->interface->bAlternateSetting)) {
+          if ((usd->endpoint = usbFindInterruptInputEndpoint(device, usd->interface))) {
+            usbBeginInput(device, USB_ENDPOINT_NUMBER(usd->endpoint));
+            *serialData = usd;
+            return 1;
+          }
+        }
+
+        usbReleaseInterface(device, interfaceNumber);
+      }
+    }
+
+    free(usd);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+usbDestroyData_CDC_ACM (UsbSerialData *usd) {
+  usbReleaseInterface(usd->device, usd->interface->bInterfaceNumber);
+  free(usd);
+}
+
+static int
+usbEnableAdapter_CDC_ACM (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  if (!usbSetControlLines_CDC_ACM(device, 0)) return 0;
+  if (!usbSetControlLines_CDC_ACM(device, USB_CDC_ACM_LINE_DTR)) return 0;
+
+  {
+    USB_CDC_ACM_LineCoding *lineCoding = &usd->lineCoding;
+
+    if (!usbGetParameter_CDC_ACM(device, USB_CDC_ACM_CTL_GetLineCoding,
+                                  lineCoding, sizeof(*lineCoding))) {
+      return 0;
+    }
+
+    usbLogLineCoding_CDC_ACM(lineCoding);
+  }
+
+  return 1;
+}
+
+static void
+usbDisableAdapter_CDC_ACM (UsbDevice *device) {
+  usbSetControlLines_CDC_ACM(device, 0);
+}
+
+const UsbSerialOperations usbSerialOperations_CDC_ACM = {
+  .name = "CDC_ACM",
+
+  .makeData = usbMakeData_CDC_ACM,
+  .destroyData = usbDestroyData_CDC_ACM,
+
+  .setLineProperties = usbSetLineProperties_CDC_ACM,
+  .setFlowControl = usbSetFlowControl_CDC_ACM,
+
+  .enableAdapter = usbEnableAdapter_CDC_ACM,
+  .disableAdapter = usbDisableAdapter_CDC_ACM
+};
diff --git a/Programs/usb_cdc_acm.h b/Programs/usb_cdc_acm.h
new file mode 100644
index 0000000..5c09738
--- /dev/null
+++ b/Programs/usb_cdc_acm.h
@@ -0,0 +1,66 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_CDC_ACM
+#define BRLTTY_INCLUDED_USB_CDC_ACM
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  USB_CDC_ACM_CTL_SetCommFeature      = 0X02,
+  USB_CDC_ACM_CTL_GetCommFeature      = 0X03,
+  USB_CDC_ACM_CTL_ClearCommFeature    = 0X04,
+  USB_CDC_ACM_CTL_SetLineCoding       = 0X20,
+  USB_CDC_ACM_CTL_GetLineCoding       = 0X21,
+  USB_CDC_ACM_CTL_SetControlLineState = 0X22,
+  USB_CDC_ACM_CTL_SendBreak           = 0X23
+} USB_CDC_ACM_ControlRequest;
+
+typedef enum {
+  USB_CDC_ACM_LINE_DTR = 0X01,
+  USB_CDC_ACM_LINE_RTS = 0X02
+} USB_CDC_ACM_ControlLine;
+
+typedef enum {
+  USB_CDC_ACM_STOP_1,
+  USB_CDC_ACM_STOP_1_5,
+  USB_CDC_ACM_STOP_2
+} USB_CDC_ACM_StopBits;
+
+typedef enum {
+  USB_CDC_ACM_PARITY_NONE,
+  USB_CDC_ACM_PARITY_ODD,
+  USB_CDC_ACM_PARITY_EVEN,
+  USB_CDC_ACM_PARITY_MARK,
+  USB_CDC_ACM_PARITY_SPACE
+} USB_CDC_ACM_Parity;
+
+typedef struct {
+  uint32_t dwDTERate; /* transmission rate - bits per second */
+  uint8_t bCharFormat; /* number of stop bits */
+  uint8_t bParityType; /* type of parity */
+  uint8_t bDataBits; /* number of data bits - 5,6,7,8,16 */
+} PACKED USB_CDC_ACM_LineCoding;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_CDC_ACM */
diff --git a/Programs/usb_ch341.c b/Programs/usb_ch341.c
new file mode 100644
index 0000000..ea53c49
--- /dev/null
+++ b/Programs/usb_ch341.c
@@ -0,0 +1,639 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "usb_serial.h"
+#include "usb_ch341.h"
+
+struct UsbSerialDataStruct {
+  char version[2];
+
+  struct {
+    uint8_t prescaler;
+    uint8_t divisor;
+  } baud;
+
+  struct {
+    uint8_t lcr1;
+    uint8_t lcr2;
+    uint8_t lsr;
+  } line;
+
+  struct {
+    uint8_t mcr;
+    uint8_t msr;
+    uint8_t flow;
+  } modem;
+};
+
+typedef struct {
+  uint16_t factor;
+  uint8_t flags;
+} CH341_PrescalerEntry;
+
+static const CH341_PrescalerEntry CH341_prescalerTable[] = {
+  { .factor = 00001, // 1
+    .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_8x | USB_CH341_PSF_BYPASS_64x
+  },
+
+  { .factor = 00002, // 2
+    .flags = USB_CH341_PSF_BYPASS_8x | USB_CH341_PSF_BYPASS_64x
+  },
+
+  { .factor = 00010, // 8
+    .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_64x
+  },
+
+  { .factor = 00020, // 16
+    .flags = USB_CH341_PSF_BYPASS_64x
+  },
+
+  { .factor = 00100, // 64
+    .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_8x
+  },
+
+  { .factor = 00200, // 128
+    .flags = USB_CH341_PSF_BYPASS_8x
+  },
+
+  { .factor = 01000, // 512
+    .flags = USB_CH341_PSF_BYPASS_2x
+  },
+
+  { .factor = 02000, // 1024
+    .flags = 0
+  },
+};
+
+static const uint8_t CH341_prescalerCOUNT = ARRAY_COUNT(CH341_prescalerTable);
+
+static int
+usbControlRead_CH341 (
+  UsbDevice *device, uint8_t request,
+  uint16_t value, uint16_t index,
+  unsigned char *buffer, size_t size
+) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 control read: %02X %04X %04X",
+    request, value, index
+  );
+
+  ssize_t result = usbControlRead(device,
+    USB_CH341_CONTROL_RECIPIENT, USB_CH341_CONTROL_TYPE,
+    request, value, index, buffer, size,
+    USB_CH341_CONTROL_TIMEOUT
+  );
+
+  if (result == -1) return 0;
+  logBytes(LOG_CATEGORY(SERIAL_IO), "CH341 control response", buffer, result);
+  if (result == size) return 1;
+
+  logMessage(LOG_WARNING,
+    "short CH341 control response: %"PRIsize" < %"PRIssize,
+    result, size
+  );
+
+  return 0;
+}
+
+static int
+usbReadRegisters_CH341 (
+  UsbDevice *device,
+  uint8_t register1, uint8_t *value1,
+  uint8_t register2, uint8_t *value2
+) {
+  unsigned char buffer[2];
+
+  int ok = usbControlRead_CH341(
+    device, USB_CH341_REQ_READ_REGISTERS,
+    (register2 << 8) | register1,
+    0, buffer, sizeof(buffer)
+  );
+
+  if (ok) {
+    *value1 = buffer[0];
+    *value2 = buffer[1];
+  }
+
+  return ok;
+}
+
+static int
+usbReadRegister_CH341 (UsbDevice *device, uint8_t register1, uint8_t *value1) {
+  uint8_t register2 = register1;
+  uint8_t value2;
+  return usbReadRegisters_CH341(device, register1, value1, register2, &value2);
+}
+
+static int
+usbControlWrite_CH341 (
+  UsbDevice *device, uint8_t request,
+  uint16_t value, uint16_t index
+) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 control write: %02X %04X %04X",
+    request, value, index
+  );
+
+  ssize_t result = usbControlWrite(device,
+    USB_CH341_CONTROL_RECIPIENT, USB_CH341_CONTROL_TYPE,
+    request, value, index, NULL, 0,
+    USB_CH341_CONTROL_TIMEOUT
+  );
+
+  return result != -1;
+}
+
+static int
+usbWriteRegisters_CH341 (
+  UsbDevice *device,
+  uint8_t register1, uint8_t value1,
+  uint8_t register2, uint8_t value2
+) {
+  return usbControlWrite_CH341(
+    device, USB_CH341_REQ_WRITE_REGISTERS,
+    (register2 << 8) | register1,
+    (value2 << 8) | value1
+  );
+}
+
+static int
+usbWriteRegister_CH341 (UsbDevice *device, uint8_t register1, uint8_t value1) {
+  return usbWriteRegisters_CH341(device, register1, value1, register1, value1);
+}
+
+static void
+usbLogVersion_CH341 (const UsbSerialData *usd) {
+  logBytes(LOG_CATEGORY(SERIAL_IO),
+    "CH341 version", usd->version, sizeof(usd->version)
+  );
+}
+
+static int
+usbReadVersion_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+  const size_t size = sizeof(usd->version);
+  unsigned char version[size];
+
+  int ok = usbControlRead_CH341(
+    device, USB_CH341_REQ_READ_VERSION, 0, 0, version, size
+  );
+
+  if (ok) {
+    memcpy(usd->version, version, size);
+    usbLogVersion_CH341(usd);
+  }
+
+  return ok;
+}
+
+static void
+usbLogStatus_CH341 (const UsbSerialData *usd) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 status: MSR:%02X LSR:%02X",
+    usd->modem.msr, usd->line.lsr
+  );
+}
+
+static int
+usbReadStatus_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  int ok = usbReadRegisters_CH341(device,
+    USB_CH341_REG_MSR, &usd->modem.msr,
+    USB_CH341_REG_LSR, &usd->line.lsr
+  );
+
+  if (ok) {
+    usd->modem.msr ^= UINT8_MAX;
+    usd->line.lsr ^= UINT8_MAX;
+    usbLogStatus_CH341(usd);
+  }
+
+  return ok;
+}
+
+static inline unsigned long
+usbTransformValue_CH341 (uint16_t factor, unsigned long value) {
+  return (((2UL * USB_CH341_FREQUENCY) / (factor * value)) + 1UL) / 2UL;
+}
+
+static unsigned int
+usbCalculateBaud_CH341 (uint8_t prescaler, uint8_t divisor) {
+  const CH341_PrescalerEntry *ps = CH341_prescalerTable;
+  const CH341_PrescalerEntry *const end = ps + CH341_prescalerCOUNT;
+
+  while (ps < end) {
+    if (ps->flags == prescaler) {
+      return usbTransformValue_CH341(
+        ps->factor, (USB_CH341_DIVISOR_MINUEND - divisor)
+      );
+    }
+
+    ps += 1;
+  }
+
+  return 0;
+}
+
+static void
+usbLogBaud_CH341 (const UsbSerialData *usd) {
+  unsigned int baud = usbCalculateBaud_CH341(
+    usd->baud.prescaler, usd->baud.divisor
+  );
+
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 baud: PS:%02X DIV:%02X Baud:%u",
+    usd->baud.prescaler, usd->baud.divisor, baud
+  );
+}
+
+static int
+usbReadBaud_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  int ok = usbReadRegisters_CH341(device,
+    USB_CH341_REG_PRESCALER, &usd->baud.prescaler,
+    USB_CH341_REG_DIVISOR, &usd->baud.divisor
+  );
+
+  if (ok) usbLogBaud_CH341(usd);
+  return ok;
+}
+
+static int
+usbGetBaudParameters (
+  unsigned int wanted, unsigned int *actual,
+  uint8_t *prescaler, uint8_t *divisor
+) {
+  const CH341_PrescalerEntry *ps = CH341_prescalerTable;
+  const CH341_PrescalerEntry *const end = ps + CH341_prescalerCOUNT;
+
+  const int NOT_FOUND = -1;
+  int nearestDelta = NOT_FOUND;
+
+  while (ps < end) {
+    unsigned long psDivisor = usbTransformValue_CH341(ps->factor, wanted);
+
+    if (psDivisor < ((ps->factor == 1)? 9: USB_CH341_DIVISOR_MINIMUM)) {
+      break;
+    }
+
+    if (psDivisor <= USB_CH341_DIVISOR_MAXIMUM) {
+      unsigned int baud = usbTransformValue_CH341(ps->factor, psDivisor);
+      int delta = baud - wanted;
+      if (delta < 0) delta = -delta;
+
+      if ((nearestDelta == NOT_FOUND) || (delta <= nearestDelta)) {
+        nearestDelta = delta;
+        *actual = baud;
+        *prescaler = ps->flags;
+        *divisor = USB_CH341_DIVISOR_MINUEND - psDivisor;
+      }
+    }
+
+    ps += 1;
+  }
+
+  return nearestDelta != NOT_FOUND;
+}
+
+static int
+usbSetBaud_CH341 (UsbDevice *device, unsigned int baud) {
+  if ((baud < USB_CH341_BAUD_MINIMUM) || (baud > USB_CH341_BAUD_MAXIMUM)) {
+    logUnsupportedBaud(baud);
+    return 0;
+  }
+
+  unsigned int actual;
+  uint8_t prescaler;
+  uint8_t divisor;
+
+  if (!usbGetBaudParameters(baud, &actual, &prescaler, &divisor)) {
+    return 0;
+  }
+
+  UsbSerialData *usd = usbGetSerialData(device);
+  if ((prescaler == usd->baud.prescaler) && (divisor == usd->baud.divisor)) return 1;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "changing CH341 baud: %u -> %u",
+    baud, actual
+  );
+
+  int ok = usbWriteRegisters_CH341(device,
+    USB_CH341_REG_PRESCALER, (prescaler | USB_CH341_PSF_NO_WAIT),
+    USB_CH341_REG_DIVISOR, divisor
+  );
+
+  if (ok) {
+    usd->baud.prescaler = prescaler;
+    usd->baud.divisor = divisor;
+  }
+
+  return ok;
+}
+
+static void
+usbLogLineControl_CH341 (const UsbSerialData *usd) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 line control: LCR1:%02X LCR2:%02X",
+    usd->line.lcr1, usd->line.lcr2
+  );
+}
+
+static int
+usbReadLineControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  int ok = usbReadRegisters_CH341(device,
+    USB_CH341_REG_LCR1, &usd->line.lcr1,
+    USB_CH341_REG_LCR2, &usd->line.lcr2
+  );
+
+  if (ok) usbLogLineControl_CH341(usd);
+  return ok;
+}
+
+static int
+usbWriteLineControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  return usbWriteRegisters_CH341(device,
+    USB_CH341_REG_LCR1, usd->line.lcr1,
+    USB_CH341_REG_LCR2, usd->line.lcr2
+  );
+}
+
+static int
+usbUpdateLCR1_CH341 (UsbSerialData *usd, uint8_t mask, uint8_t value) {
+  return usbUpdateByte(&usd->line.lcr1, mask, value);
+}
+
+static int
+usbUpdateDataBits_CH341 (UsbSerialData *usd, unsigned int dataBits) {
+  const uint8_t mask = USB_CH341_LCR1_DATA_BITS_MASK;
+  uint8_t value;
+
+  switch (dataBits) {
+    case 5: value = USB_CH341_LCR1_DATA_BITS_5; break;
+    case 6: value = USB_CH341_LCR1_DATA_BITS_6; break;
+    case 7: value = USB_CH341_LCR1_DATA_BITS_7; break;
+    case 8: value = USB_CH341_LCR1_DATA_BITS_8; break;
+
+    default:
+      logUnsupportedDataBits(dataBits);
+      return 0;
+  }
+
+  return usbUpdateLCR1_CH341(usd, mask, value);
+}
+
+static int
+usbUpdateStopBits_CH341 (UsbSerialData *usd, SerialStopBits stopBits) {
+  const uint8_t mask = USB_CH341_LCR1_STOP_BITS_MASK;
+  uint8_t value;
+
+  switch (stopBits) {
+    case SERIAL_STOP_1:
+      value = USB_CH341_LCR1_STOP_BITS_1;
+      break;
+
+    case SERIAL_STOP_2:
+      value = USB_CH341_LCR1_STOP_BITS_2;
+      break;
+
+    default:
+      logUnsupportedStopBits(stopBits);
+      return 0;
+  }
+
+  return usbUpdateLCR1_CH341(usd, mask, value);
+}
+
+static int
+usbUpdateParity_CH341 (UsbSerialData *usd, SerialParity parity) {
+  const uint8_t mask = USB_CH341_LCR1_PARITY_MASK;
+  uint8_t value;
+
+  switch (parity) {
+    case SERIAL_PARITY_NONE:
+      value = USB_CH341_LCR1_PARITY_NONE;
+      break;
+
+    case SERIAL_PARITY_EVEN:
+      value = USB_CH341_LCR1_PARITY_EVEN;
+      break;
+
+    case SERIAL_PARITY_ODD:
+      value = USB_CH341_LCR1_PARITY_ODD;
+      break;
+
+    case SERIAL_PARITY_SPACE:
+      value = USB_CH341_LCR1_PARITY_SPACE;
+      break;
+
+    case SERIAL_PARITY_MARK:
+      value = USB_CH341_LCR1_PARITY_MARK;
+      break;
+
+    default:
+      logUnsupportedParity(parity);
+      return 0;
+  }
+
+  return usbUpdateLCR1_CH341(usd, mask, value);
+}
+
+static int
+usbSetDataFormat_CH341 (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  int changed = 0;
+  if (usbUpdateDataBits_CH341(usd, dataBits)) changed = 1;
+  if (usbUpdateStopBits_CH341(usd, stopBits)) changed = 1;
+  if (usbUpdateParity_CH341(usd, parity)) changed = 1;
+
+  if (!changed) return 1;
+  return usbWriteLineControl_CH341(device);
+}
+
+static void
+usbLogFlowControl_CH341 (const UsbSerialData *usd) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "CH341 flow control: %02X",
+    usd->modem.flow
+  );
+}
+
+static int
+usbReadFlowControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  int ok = usbReadRegister_CH341(device,
+    USB_CH341_REG_FLOW, &usd->modem.flow
+  );
+
+  if (ok) usbLogFlowControl_CH341(usd);
+  return ok;
+}
+
+static int
+usbWriteFlowControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+  return usbWriteRegister_CH341(device, USB_CH341_REG_FLOW, usd->modem.flow);
+}
+
+static int
+usbSetFlowControl_CH341 (UsbDevice *device, SerialFlowControl flowControl) {
+  uint8_t value;
+
+  switch (flowControl) {
+    case SERIAL_FLOW_NONE:
+      value = 0;
+      break;
+
+    case SERIAL_FLOW_HARDWARE:
+      value = USB_CH341_FLOW_RTSCTS;
+      break;
+
+    default:
+      logUnsupportedFlowControl(flowControl);
+      return 0;
+  }
+
+  UsbSerialData *usd = usbGetSerialData(device);
+  if (value == usd->modem.flow) return 1;
+
+  usd->modem.flow = value;
+  return usbWriteFlowControl_CH341(device);
+}
+
+static int
+usbWriteModemControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  return usbControlWrite_CH341(
+    device, USB_CH341_REQ_WRITE_MCR, ~usd->modem.mcr, 0
+  );
+}
+
+static int
+usbInitializeSerial_CH341 (UsbDevice *device) {
+  return usbControlWrite_CH341(device, USB_CH341_REQ_INITIALIZE_SERIAL, 0, 0);
+}
+
+static int
+usbInitializeBaud_CH341 (UsbDevice *device) {
+  if (!usbReadBaud_CH341(device)) return 0;
+  return usbSetBaud_CH341(device, SERIAL_DEFAULT_BAUD);
+}
+
+static int
+usbInitializeLineControl_CH341 (UsbDevice *device) {
+  if (!usbReadLineControl_CH341(device)) return 0;
+  UsbSerialData *usd = usbGetSerialData(device);
+
+  uint8_t oldLCR1 = usd->line.lcr1;
+  uint8_t oldLCR2 = usd->line.lcr2;
+
+  usd->line.lcr1 |= USB_CH341_LCR1_RECEIVE_ENABLE;
+  usd->line.lcr1 |= USB_CH341_LCR1_TRANSMIT_ENABLE;
+
+  usbUpdateDataBits_CH341(usd, SERIAL_DEFAULT_DATA_BITS);
+  usbUpdateStopBits_CH341(usd, SERIAL_DEFAULT_STOP_BITS);
+  usbUpdateParity_CH341(usd, SERIAL_DEFAULT_PARITY);
+
+  if ((usd->line.lcr1 == oldLCR1) && (usd->line.lcr2 == oldLCR2)) return 1;
+  return usbWriteLineControl_CH341(device);
+}
+
+static int
+usbInitializeFlowControl_CH341 (UsbDevice *device) {
+  if (!usbReadFlowControl_CH341(device)) return 0;
+  return usbSetFlowControl_CH341(device, SERIAL_DEFAULT_FLOW_CONTROL);
+}
+
+static int
+usbInitializeModemControl_CH341 (UsbDevice *device) {
+  UsbSerialData *usd = usbGetSerialData(device);
+  usd->modem.mcr = 0;
+  return usbWriteModemControl_CH341(device);
+}
+
+static int
+usbEnableAdapter_CH341 (UsbDevice *device) {
+  typedef int Function (UsbDevice *device);
+
+  static Function *const functions[] = {
+    &usbReadVersion_CH341, // should be first
+    &usbInitializeSerial_CH341,
+    &usbInitializeBaud_CH341,
+    &usbInitializeLineControl_CH341,
+    &usbInitializeFlowControl_CH341,
+    &usbInitializeModemControl_CH341,
+    &usbReadStatus_CH341,
+    NULL // must be last
+  };
+
+  Function *const *function = functions;
+
+  while (*function) {
+    if (!(*function)(device)) return 0;
+    function += 1;
+  }
+
+  return 1;
+}
+
+static int
+usbMakeData_CH341 (UsbDevice *device, UsbSerialData **serialData) {
+  UsbSerialData *usd;
+
+  if ((usd = malloc(sizeof(*usd)))) {
+    memset(usd, 0, sizeof(*usd));
+    *serialData = usd;
+    return 1;
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static void
+usbDestroyData_CH341 (UsbSerialData *usd) {
+  free(usd);
+}
+
+const UsbSerialOperations usbSerialOperations_CH341 = {
+  .name = "CH341",     
+
+  .makeData = usbMakeData_CH341,     
+  .destroyData = usbDestroyData_CH341,     
+
+  .enableAdapter = usbEnableAdapter_CH341,     
+  .setBaud = usbSetBaud_CH341,
+  .setDataFormat = usbSetDataFormat_CH341,
+  .setFlowControl = usbSetFlowControl_CH341,
+};
diff --git a/Programs/usb_ch341.h b/Programs/usb_ch341.h
new file mode 100644
index 0000000..dd17db6
--- /dev/null
+++ b/Programs/usb_ch341.h
@@ -0,0 +1,112 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_CH341
+#define BRLTTY_INCLUDED_USB_CH341
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define USB_CH341_CONTROL_TYPE UsbControlType_Vendor
+#define USB_CH341_CONTROL_RECIPIENT UsbControlRecipient_Device
+#define USB_CH341_CONTROL_TIMEOUT 1000
+
+typedef enum {
+  USB_CH341_REQ_READ_VERSION      = 0X5F,
+  USB_CH341_REQ_READ_REGISTERS    = 0X95,
+  USB_CH341_REQ_WRITE_REGISTERS   = 0X9A,
+  USB_CH341_REQ_INITIALIZE_SERIAL = 0XA1,
+  USB_CH341_REQ_WRITE_MCR         = 0XA4,
+} USB_CH341_ControlRequest;
+
+typedef enum {
+  USB_CH341_REG_BREAK     = 0X05,
+  USB_CH341_REG_MSR       = 0X06,
+  USB_CH341_REG_LSR       = 0X07,
+  USB_CH341_REG_PRESCALER = 0X12,
+  USB_CH341_REG_DIVISOR   = 0X13,
+  USB_CH341_REG_BPS_MOD   = 0X14,
+  USB_CH341_REG_LCR1      = 0X18,
+  USB_CH341_REG_LCR2      = 0X25,
+  USB_CH341_REG_FLOW      = 0X27,
+} USB_CH341_Register;
+
+#define USB_CH341_FREQUENCY 12000000
+#define USB_CH341_BAUD_MINIMUM 46
+#define USB_CH341_BAUD_MAXIMUM 2000000
+
+typedef enum {
+  USB_CH341_PSF_BYPASS_8x  = 0X01,
+  USB_CH341_PSF_BYPASS_64x = 0X02,
+  USB_CH341_PSF_BYPASS_2x  = 0X04,
+  USB_CH341_PSF_NO_WAIT    = 0X80, // don't wait till there are 32 bytes
+} USB_CH341_PrescalerFlags;
+
+#define USB_CH341_DIVISOR_MINIMUM   2
+#define USB_CH341_DIVISOR_MAXIMUM 256
+#define USB_CH341_DIVISOR_MINUEND 256
+
+typedef enum {
+  USB_CH341_LCR1_RECEIVE_ENABLE  = 0X80,
+  USB_CH341_LCR1_TRANSMIT_ENABLE = 0X40,
+
+  USB_CH341_LCR1_PAR_BIT_STICK   = 0X20, // mark/space modifier
+  USB_CH341_LCR1_PAR_BIT_EVEN    = 0X10,
+  USB_CH341_LCR1_PAR_BIT_ENABLE  = 0X08,
+
+  USB_CH341_LCR1_STOP_BITS_1     = 0X00,
+  USB_CH341_LCR1_STOP_BITS_2     = 0X04,
+
+  USB_CH341_LCR1_DATA_BITS_5     = 0X00,
+  USB_CH341_LCR1_DATA_BITS_6     = 0X01,
+  USB_CH341_LCR1_DATA_BITS_7     = 0X02,
+  USB_CH341_LCR1_DATA_BITS_8     = 0X03,
+
+  USB_CH341_LCR1_DATA_BITS_MASK  = 0X03,
+  USB_CH341_LCR1_STOP_BITS_MASK  = USB_CH341_LCR1_STOP_BITS_1 | USB_CH341_LCR1_STOP_BITS_2,
+
+  USB_CH341_LCR1_PARITY_MASK  = USB_CH341_LCR1_PAR_BIT_ENABLE | USB_CH341_LCR1_PAR_BIT_EVEN | USB_CH341_LCR1_PAR_BIT_STICK,
+  USB_CH341_LCR1_PARITY_NONE  = 0X00,
+  USB_CH341_LCR1_PARITY_ODD   = USB_CH341_LCR1_PAR_BIT_ENABLE,
+  USB_CH341_LCR1_PARITY_EVEN  = USB_CH341_LCR1_PAR_BIT_ENABLE | USB_CH341_LCR1_PAR_BIT_EVEN,
+  USB_CH341_LCR1_PARITY_MARK  = USB_CH341_LCR1_PAR_BIT_ENABLE | USB_CH341_LCR1_PAR_BIT_STICK,
+  USB_CH341_LCR1_PARITY_SPACE = USB_CH341_LCR1_PAR_BIT_ENABLE | USB_CH341_LCR1_PAR_BIT_STICK | USB_CH341_LCR1_PAR_BIT_EVEN,
+} USB_CH341_LineControlFlags1;
+
+typedef enum {
+  USB_CH341_MCR_DTR = 0X20, // data terminal ready
+  USB_CH341_MCR_RTS = 0X40, // request to send
+} USB_CH341_ModemControlFlags;
+
+typedef enum {
+  USB_CH341_MSR_CTS = 0X01, // clear to send
+  USB_CH341_MSR_DSR = 0X02, // data set ready
+  USB_CH341_MSR_RI  = 0X04, // ring indicator
+  USB_CH341_MSR_DCD = 0X08, // data carrier detect
+} USB_CH341_ModemStatusFlags;
+
+typedef enum {
+  USB_CH341_FLOW_RTSCTS = 0X01, // hardware flow control
+} USB_CH341_FlowControlFlags;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_CH341 */
diff --git a/Programs/usb_cp2101.c b/Programs/usb_cp2101.c
new file mode 100644
index 0000000..180dcaf
--- /dev/null
+++ b/Programs/usb_cp2101.c
@@ -0,0 +1,405 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "usb_serial.h"
+#include "usb_cp2101.h"
+#include "bitfield.h"
+
+static size_t
+usbGetProperty_CP2101 (UsbDevice *device, uint8_t request, void *data, size_t length) {
+  ssize_t result;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "getting CP2101 property: %02X", request);
+  result = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Vendor,
+                          request, 0, 0, data, length, 1000);
+  if (result == -1) return 0;
+
+  if (result < length) {
+    unsigned char *bytes = data;
+    memset(&bytes[result], 0, length-result);
+  }
+
+  logBytes(LOG_CATEGORY(SERIAL_IO), "CP2101 property input", data, result);
+  return result;
+}
+
+static int
+usbSetComplexProperty_CP2101 (
+  UsbDevice *device,
+  uint8_t request, uint16_t value,
+  const void *data, size_t length
+) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 property: %02X %04X", request, value);
+  if (length) logBytes(LOG_CATEGORY(SERIAL_IO), "CP2101 property output", data, length);
+
+  return usbControlWrite(device, UsbControlRecipient_Interface, UsbControlType_Vendor,
+                         request, value, 0, data, length, 1000) != -1;
+}
+
+static int
+usbSetSimpleProperty_CP2101 (UsbDevice *device, uint8_t request, uint16_t value) {
+  return usbSetComplexProperty_CP2101(device, request, value, NULL, 0);
+}
+
+static int
+usbVerifyBaudRate_CP2101 (UsbDevice *device, USB_CP2101_BaudRate expected) {
+  USB_CP2101_BaudRate actual;
+  ssize_t result;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 baud rate");
+  result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetBaudRate,
+                                 &actual, sizeof(actual));
+
+  if (result == -1) {
+    logMessage(LOG_WARNING, "unable to get CP2101 baud rate: %s", strerror(errno));
+  } else if (result != sizeof(actual)) {
+    logMessage(LOG_WARNING, "unexpected CP2101 baud rate size: %d", (int)result);
+  } else if (getLittleEndian32(actual) != expected) {
+    logMessage(LOG_WARNING,
+               "unexpected CP2101 baud rate value:"
+               " Expected:%"PRIu32 " Actual:%"PRIu32,
+               expected, getLittleEndian32(actual));
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+usbVerifyBaudDivisor_CP2101 (UsbDevice *device, USB_CP2101_BaudDivisor expected) {
+  USB_CP2101_BaudDivisor actual;
+  ssize_t result;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 baud divisor");
+  result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetBaudDivisor,
+                                 &actual, sizeof(actual));
+
+  if (result == -1) {
+    logMessage(LOG_WARNING, "unable to get CP2101 baud divisor: %s", strerror(errno));
+  } else if (result != sizeof(actual)) {
+    logMessage(LOG_WARNING, "unexpected CP2101 baud divisor size: %d", (int)result);
+  } else if (getLittleEndian16(actual) != expected) {
+    logMessage(LOG_WARNING,
+               "unexpected CP2101 baud divisor value: Expected:%u Actual:%u",
+               expected, getLittleEndian16(actual));
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+usbVerifyBaud_CP2101 (UsbDevice *device, USB_CP2101_BaudRate rate, USB_CP2101_BaudDivisor divisor) {
+  if (!usbVerifyBaudRate_CP2101(device, rate)) return 0;
+  if (!usbVerifyBaudDivisor_CP2101(device, divisor)) return 0;
+  return 1;
+}
+
+static int
+usbSetBaud_CP2101 (UsbDevice *device, unsigned int baud) {
+  USB_CP2101_BaudDivisor divisor = USB_CP2101_BAUD_BASE / baud;
+
+  if ((baud * divisor) != USB_CP2101_BAUD_BASE) {
+    logUnsupportedBaud(baud);
+    errno = EINVAL;
+    return 0;
+  }
+
+  {
+    USB_CP2101_BaudRate rate;
+
+    logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 baud rate: %u", baud);
+    putLittleEndian32(&rate, baud);
+
+    if (!usbSetComplexProperty_CP2101(device, USB_CP2101_CTL_SetBaudRate, 0,
+                                      &rate, sizeof(rate))) {
+      logMessage(LOG_WARNING, "unable to set CP2101 baud rate: %s", strerror(errno));
+    } else if (usbVerifyBaud_CP2101(device, baud, divisor)) {
+      return 1;
+    }
+  }
+
+  {
+    logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 baud divisor: %u", divisor);
+
+    if (!usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetBaudDivisor, divisor)) {
+      logMessage(LOG_WARNING, "unable to set CP2101 baud divisor: %s", strerror(errno));
+    } else if (usbVerifyBaud_CP2101(device, baud, divisor)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbSetModemState_CP2101 (UsbDevice *device, int state, int shift, const char *name) {
+  if ((state < 0) || (state > 1)) {
+    logMessage(LOG_WARNING, "unsupported CP2101 %s state: %d", name, state);
+    errno = EINVAL;
+    return 0;
+  }
+
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+             "setting CP2101 %s state: %s",
+             name, (state? "high": "low"));
+
+  return usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetModemHandShaking, ((1 << (shift + 8)) | (state << shift)));
+}
+
+static int
+usbSetDtrState_CP2101 (UsbDevice *device, int state) {
+  return usbSetModemState_CP2101(device, state, 0, "DTR");
+}
+
+static int
+usbSetRtsState_CP2101 (UsbDevice *device, int state) {
+  return usbSetModemState_CP2101(device, state, 1, "RTS");
+}
+
+static int
+usbVerifyFlowControl_CP2101 (UsbDevice *device, const USB_CP2101_FlowControl *expected, size_t size) {
+  USB_CP2101_FlowControl actual;
+  ssize_t result;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 flow control");
+  result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetFlowControl,
+                                 &actual, sizeof(actual));
+
+  if (result == -1) {
+    logMessage(LOG_WARNING, "unable to get CP2101 flow control: %s", strerror(errno));
+  } else if (result != size) {
+    logMessage(LOG_WARNING, "unexpected CP2101 flow control size: %d", (int)result);
+  } else if (memcmp(&actual, expected, size) != 0) {
+    logMessage(LOG_WARNING, "unexpected CP2101 flow control data");
+    logBytes(LOG_WARNING, "expected flow control", expected, size);
+    logBytes(LOG_WARNING, "actual flow control", &actual, size);
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+usbSetFlowControl_CP2101 (UsbDevice *device, SerialFlowControl flow) {
+  USB_CP2101_FlowControl oldSettings;
+  USB_CP2101_FlowControl newSettings;
+  size_t size;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "getting CP2101 flow control");
+  size = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetFlowControl,
+                               &oldSettings, sizeof(oldSettings));
+
+  if (!size) {
+    logMessage(LOG_WARNING, "unable to get CP2101 flow control");
+    return 0;
+  }
+
+  newSettings = oldSettings;
+  newSettings.handshakeOptions = getLittleEndian32(newSettings.handshakeOptions);
+  newSettings.dataFlowOptions = getLittleEndian32(newSettings.dataFlowOptions);
+
+  newSettings.handshakeOptions &= ~USB_CP2101_FLOW_HSO_DTR_MASK;
+  newSettings.handshakeOptions |= USB_CP2101_FLOW_HSO_DTR_ACTIVE;
+
+  if (flow & SERIAL_FLOW_OUTPUT_CTS) {
+    flow &= ~SERIAL_FLOW_OUTPUT_CTS;
+    newSettings.handshakeOptions |= USB_CP2101_FLOW_HSO_CTS_INTERPRET;
+  } else {
+    newSettings.handshakeOptions &= ~USB_CP2101_FLOW_HSO_CTS_INTERPRET;
+  }
+
+  if (flow & SERIAL_FLOW_OUTPUT_RTS) {
+    flow &= ~SERIAL_FLOW_OUTPUT_RTS;
+    newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_RTS_MASK;
+    newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_RTS_XMT_ACTIVE;
+  } else {
+    newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_RTS_MASK;
+    newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_RTS_ACTIVE;
+  }
+
+  if (flow & SERIAL_FLOW_OUTPUT_XON) {
+    flow &= ~SERIAL_FLOW_OUTPUT_XON;
+    newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_AUTO_TRANSMIT;
+  } else {
+    newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_AUTO_TRANSMIT;
+  }
+
+  if (flow & SERIAL_FLOW_INPUT_XON) {
+    flow &= ~SERIAL_FLOW_INPUT_XON;
+    newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_AUTO_RECEIVE;
+  } else {
+    newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_AUTO_RECEIVE;
+  }
+
+  putLittleEndian32(&newSettings.handshakeOptions, newSettings.handshakeOptions);
+  putLittleEndian32(&newSettings.dataFlowOptions, newSettings.dataFlowOptions);
+
+  if (flow) {
+    logUnsupportedFlowControl(flow);
+    errno = EINVAL;
+    return 0;
+  }
+
+  if (memcmp(&newSettings, &oldSettings, size) == 0) {
+    logMessage(LOG_CATEGORY(SERIAL_IO), "CP2101 flow control unchanged");
+  }
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 flow control");
+
+  if (!usbSetComplexProperty_CP2101(device, USB_CP2101_CTL_SetFlowControl, 0,
+                                    &newSettings, size)) {
+    logMessage(LOG_WARNING, "unable to set CP2101 flow control: %s", strerror(errno));
+  } else if (usbVerifyFlowControl_CP2101(device, &newSettings, size)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+usbVerifyLineControl_CP2101 (UsbDevice *device, USB_CP2101_LineControl expected) {
+  USB_CP2101_LineControl actual;
+  ssize_t result;
+
+  logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 line control");
+  result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetLineControl,
+                                 &actual, sizeof(actual));
+
+  if (result == -1) {
+    logMessage(LOG_WARNING, "unable to get CP2101 line control: %s", strerror(errno));
+  } else if (result != sizeof(actual)) {
+    logMessage(LOG_WARNING, "unexpected CP2101 line control size: %d", (int)result);
+  } else if (getLittleEndian16(actual) != expected) {
+    logMessage(LOG_WARNING,
+               "unexpected CP2101 line control value: Expected:0X%04X Actual:0X%04X",
+               expected, getLittleEndian16(actual));
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+usbSetDataFormat_CP2101 (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
+  int ok = 1;
+  USB_CP2101_LineControl lineControl = 0;
+
+  {
+    USB_CP2101_LineControl value;
+
+    if ((dataBits >= USB_CP2101_DATA_MINIMUM) &&
+        (dataBits <= USB_CP2101_DATA_MAXIMUM)) {
+      value = dataBits;
+    } else {
+      logUnsupportedDataBits(dataBits);
+      ok = 0;
+      value = 8;
+    }
+
+    lineControl |= value << USB_CP2101_DATA_SHIFT;
+  }
+
+  {
+    USB_CP2101_LineControl value;
+
+    switch (parity) {
+      case SERIAL_PARITY_NONE:  value = USB_CP2101_PARITY_NONE;  break;
+      case SERIAL_PARITY_ODD:   value = USB_CP2101_PARITY_ODD;   break;
+      case SERIAL_PARITY_EVEN:  value = USB_CP2101_PARITY_EVEN;  break;
+      case SERIAL_PARITY_MARK:  value = USB_CP2101_PARITY_MARK;  break;
+      case SERIAL_PARITY_SPACE: value = USB_CP2101_PARITY_SPACE; break;
+
+      default:
+        logUnsupportedParity(parity);
+        ok = 0;
+        value = USB_CP2101_PARITY_NONE;
+        break;
+    }
+
+    lineControl |= value << USB_CP2101_PARITY_SHIFT;
+  }
+
+  {
+    USB_CP2101_LineControl value;
+
+    switch (stopBits) {
+      case SERIAL_STOP_1:   value = USB_CP2101_STOP_1;   break;
+      case SERIAL_STOP_1_5: value = USB_CP2101_STOP_1_5; break;
+      case SERIAL_STOP_2:   value = USB_CP2101_STOP_2;   break;
+
+      default:
+        logUnsupportedStopBits(stopBits);
+        ok = 0;
+        value = USB_CP2101_STOP_1;
+        break;
+    }
+
+    lineControl |= value << USB_CP2101_STOP_SHIFT;
+  }
+
+  if (ok) {
+    logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 line control: 0X%04X", lineControl);
+
+    if (!usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetLineControl, lineControl)) {
+      logMessage(LOG_WARNING, "unable to set CP2101 line control: %0X04X", lineControl);
+    } else if (usbVerifyLineControl_CP2101(device, lineControl)) {
+      return 1;
+    }
+  }
+
+  errno = EINVAL;
+  return 0;
+}
+
+static int
+usbSetInterfaceState_CP2101 (UsbDevice *device, int state) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+             "setting CP2101 interface state: %s",
+             (state? "enabled": "disabled"));
+
+  return usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_EnableInterface, state);
+}
+
+static int
+usbEnableAdapter_CP2101 (UsbDevice *device) {
+  if (!usbSetInterfaceState_CP2101(device, 0)) return 0;
+  if (!usbSetInterfaceState_CP2101(device, 1)) return 0;
+
+  return 1;
+}
+
+const UsbSerialOperations usbSerialOperations_CP2101 = {
+  .name = "CP2101",     
+  .setBaud = usbSetBaud_CP2101,
+  .setDataFormat = usbSetDataFormat_CP2101,
+  .setFlowControl = usbSetFlowControl_CP2101,
+  .setDtrState = usbSetDtrState_CP2101,
+  .setRtsState = usbSetRtsState_CP2101,
+  .enableAdapter = usbEnableAdapter_CP2101
+};
diff --git a/Programs/usb_cp2101.h b/Programs/usb_cp2101.h
new file mode 100644
index 0000000..ad5f710
--- /dev/null
+++ b/Programs/usb_cp2101.h
@@ -0,0 +1,161 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_CP2101
+#define BRLTTY_INCLUDED_USB_CP2101
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+  USB_CP2101_CTL_EnableInterface        = 0x00,
+  USB_CP2101_CTL_SetBaudDivisor         = 0x01,
+  USB_CP2101_CTL_GetBaudDivisor         = 0x02,
+  USB_CP2101_CTL_SetLineControl         = 0x03,
+  USB_CP2101_CTL_GetLineControl         = 0x04,
+  USB_CP2101_CTL_SetBreak               = 0x05,
+  USB_CP2101_CTL_SendImmediateCharacter = 0x06,
+  USB_CP2101_CTL_SetModemHandShaking    = 0x07,
+  USB_CP2101_CTL_GetModemStatus         = 0x08,
+  USB_CP2101_CTL_SetXon                 = 0x09,
+  USB_CP2101_CTL_SetXoff                = 0x0A,
+  USB_CP2101_CTL_SetEventMask           = 0x0B,
+  USB_CP2101_CTL_GetEventMask           = 0x0C,
+  USB_CP2101_CTL_SetSpecialCharacter    = 0x0D,
+  USB_CP2101_CTL_GetSpecialCharacters   = 0x0E,
+  USB_CP2101_CTL_GetProperties          = 0x0F,
+  USB_CP2101_CTL_GetSerialStatus        = 0x10,
+  USB_CP2101_CTL_Reset                  = 0x11,
+  USB_CP2101_CTL_Purge                  = 0x12,
+  USB_CP2101_CTL_SetFlowControl         = 0x13,
+  USB_CP2101_CTL_GetFlowControl         = 0x14,
+  USB_CP2101_CTL_EmbedEvents            = 0x15,
+  USB_CP2101_CTL_GetEventState          = 0x16,
+  USB_CP2101_CTL_SetSpecialCharacters   = 0x19,
+  USB_CP2101_CTL_GetBaudRate            = 0x1D,
+  USB_CP2101_CTL_SetBaudRate            = 0x1E,
+  USB_CP2101_CTL_VendorSpecific         = 0xFF
+} USB_CP2101_ControlRequest;
+
+typedef uint32_t USB_CP2101_BaudRate;
+typedef uint16_t USB_CP2101_BaudDivisor;
+#define USB_CP2101_BAUD_BASE 0X384000
+
+typedef uint16_t USB_CP2101_LineControl;
+#define USB_CP2101_STOP_SHIFT 0
+#define USB_CP2101_STOP_WIDTH 4
+#define USB_CP2101_PARITY_SHIFT 4
+#define USB_CP2101_PARITY_WIDTH 4
+#define USB_CP2101_DATA_SHIFT 8
+#define USB_CP2101_DATA_WIDTH 8
+#define USB_CP2101_DATA_MINIMUM 5
+#define USB_CP2101_DATA_MAXIMUM 8
+
+
+typedef enum {
+  USB_CP2101_STOP_1,
+  USB_CP2101_STOP_1_5,
+  USB_CP2101_STOP_2
+} USB_CP2101_StopBits;
+
+typedef enum {
+  USB_CP2101_PARITY_NONE,
+  USB_CP2101_PARITY_ODD,
+  USB_CP2101_PARITY_EVEN,
+  USB_CP2101_PARITY_MARK,
+  USB_CP2101_PARITY_SPACE
+} USB_CP2101_Parity;
+
+typedef struct {
+  uint32_t handshakeOptions;
+  uint32_t dataFlowOptions;
+  uint32_t xonThreshold;
+  uint32_t xoffThreshold;
+} PACKED USB_CP2101_FlowControl;
+
+typedef enum {
+  USB_CP2101_FLOW_HSO_DTR_MASK       = 0X00000003, // DTR line usage
+  USB_CP2101_FLOW_HSO_DTR_INACTIVE   = 0X00000000, // DTR is held inactive
+  USB_CP2101_FLOW_HSO_DTR_ACTIVE     = 0X00000001, // DTR is held active
+  USB_CP2101_FLOW_HSO_DTR_CONTROLLED = 0X00000002, // DTR is controlled
+
+  USB_CP2101_FLOW_HSO_CTS_INTERPRET  = 0X00000008, // CTS is interpreted
+  USB_CP2101_FLOW_HSO_DSR_INTERPRET  = 0X00000010, // DSR is interpreted
+  USB_CP2101_FLOW_HSO_DCD_INTERPRET  = 0X00000020, // DCD is interpreted
+  USB_CP2101_FLOW_HSO_DSR_DISCARD    = 0X00000040  // DSR low discards data
+} USB_CP2101_ControlHandshake;
+
+typedef enum {
+  USB_CP2101_FLOW_DFO_AUTO_TRANSMIT   = 0X00000001, // respond to XON/XOFF from device
+  USB_CP2101_FLOW_DFO_AUTO_RECEIVE    = 0X00000002, // send XON/XOFF to device
+  USB_CP2101_FLOW_DFO_ERROR_CHARACTER = 0X00000004, // enable insertion of error special-character
+  USB_CP2101_FLOW_DFO_STRIP_NULS      = 0X00000008, // discard received NUL characters
+  USB_CP2101_FLOW_DFO_BREAK_CHARACTER = 0X00000010, // enable insertion of break special-character
+
+  USB_CP2101_FLOW_DFO_RTS_MASK        = 0X000000C0, // RTS line usage
+  USB_CP2101_FLOW_DFO_RTS_INACTIVE    = 0X00000000, // RTS is statically inactive
+  USB_CP2101_FLOW_DFO_RTS_ACTIVE      = 0X00000040, // RTS is statically active
+  USB_CP2101_FLOW_DFO_RTS_RCV_FLOW    = 0X00000080, // RTS is used for receive flow control
+  USB_CP2101_FLOW_DFO_RTS_XMT_ACTIVE  = 0X000000C0, // RTS signals transmit active
+
+  USB_CP2101_FLOW_DFO_AUTO_RCV_ALWAYS = 0X80000000  // send XON/XOFF to device even when suspended
+} USB_CP2101_FlowReplace;
+
+typedef struct {
+  uint8_t eof;   // indicates end-of-file on input
+  uint8_t error; // inserted when an error is detected
+  uint8_t brk  ; // inserted when a break is detected
+  uint8_t event; // sets bit 2 of the event-occurred mask
+  uint8_t xon;   // sent to resume input
+  uint8_t xoff;  // sent to suspend input
+} PACKED USB_CP2101_SpecialCharacters;
+
+typedef struct {
+  uint32_t errors;
+  uint32_t holdReasons;
+  uint32_t inputCount;
+  uint32_t outputCount;
+  uint8_t eofReceived;
+  uint8_t waitForImmediate;
+  uint8_t reserved;
+} PACKED USB_CP2101_SerialStatus;
+
+typedef enum {
+  USB_CP2101_SS_ERR_BREAK_SIGNAL     = 0X00000001,
+  USB_CP2101_SS_ERR_FRAMING_ERROR    = 0X00000002,
+  USB_CP2101_SS_ERR_HARDWARE_OVERRUN = 0X00000004,
+  USB_CP2101_SS_ERR_QUEUE_OVERRUN    = 0X00000008,
+  USB_CP2101_SS_ERR_PARITY_ERROR     = 0X00000010
+} USB_CP2101_SerialError;
+
+typedef enum {
+  USB_CP2101_SS_HLD_XMT_CTS_WAIT  = 0X00000001,
+  USB_CP2101_SS_HLD_XMT_DSR_WAIT  = 0X00000002,
+  USB_CP2101_SS_HLD_XMT_DCD_WAIT  = 0X00000004,
+  USB_CP2101_SS_HLD_XMT_XON_WAIT  = 0X00000008,
+  USB_CP2101_SS_HLD_XMT_XOFF_SENT = 0X00000010,
+  USB_CP2101_SS_HLD_XMT_BRK_WAIT  = 0X00000020,
+  USB_CP2101_SS_HLD_RCV_DSR_WAIT  = 0X00000040,
+} USB_CP2101_HoldReason;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_CP2101 */
diff --git a/Programs/usb_cp2110.c b/Programs/usb_cp2110.c
new file mode 100644
index 0000000..857ff77
--- /dev/null
+++ b/Programs/usb_cp2110.c
@@ -0,0 +1,217 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "usb_serial.h"
+#include "usb_cp2110.h"
+#include "usb_hid.h"
+#include "bitfield.h"
+
+typedef enum {
+  USB_CP2110_PARITY_NONE,
+  USB_CP2110_PARITY_EVEN,
+  USB_CP2110_PARITY_ODD,
+  USB_CP2110_PARITY_MARK,
+  USB_CP2110_PARITY_SPACE
+} USB_CP2110_Parity;
+
+typedef enum {
+  USB_CP2110_FLOW_NONE,
+  USB_CP2110_FLOW_HARDWARE
+} USB_CP2110_FlowControl;
+
+typedef enum {
+  USB_CP2110_DATA_5,
+  USB_CP2110_DATA_6,
+  USB_CP2110_DATA_7,
+  USB_CP2110_DATA_8
+} USB_CP2110_DataBits;
+
+typedef enum {
+  USB_CP2110_STOP_SHORT,
+  USB_CP2110_STOP_LONG
+} USB_CP2110_StopBits;
+
+typedef struct {
+  uint8_t reportIdentifier;
+  uint32_t baudRate;
+  uint8_t parity;
+  uint8_t flowControl;
+  uint8_t dataBits;
+  uint8_t stopBits;
+} PACKED USB_CP2110_UartConfigurationReport;
+
+static int
+usbInputFilter_CP2110 (UsbInputFilterData *data) {
+  return usbSkipInitialBytes(data, 1);
+}
+
+static int
+usbSetReport_CP2110 (UsbDevice *device, const void *report, size_t size) {
+  const unsigned char *bytes = report;
+  ssize_t result = usbHidSetReport(device, 0, bytes[0], report, size, 1000);
+  return result != -1;
+}
+
+static int
+usbSetLineConfiguration_CP2110 (UsbDevice *device, unsigned int baud, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity, SerialFlowControl flowControl) {
+  USB_CP2110_UartConfigurationReport report;
+
+  memset(&report, 0, sizeof(report));
+  report.reportIdentifier = 0X50;
+
+  if ((baud >= 300) && (baud <= 500000)) {
+    putBigEndian32(&report.baudRate, baud);
+  } else {
+    logUnsupportedBaud(baud);
+    errno = EINVAL;
+    return 0;
+  }
+
+  switch (dataBits) {
+    case 5:
+      report.dataBits = USB_CP2110_DATA_5;
+      break;
+
+    case 6:
+      report.dataBits = USB_CP2110_DATA_6;
+      break;
+
+    case 7:
+      report.dataBits = USB_CP2110_DATA_7;
+      break;
+
+    case 8:
+      report.dataBits = USB_CP2110_DATA_8;
+      break;
+
+    default:
+      logUnsupportedDataBits(dataBits);
+      errno = EINVAL;
+      return 0;
+  }
+
+  if (stopBits == SERIAL_STOP_1) {
+    report.stopBits = USB_CP2110_STOP_SHORT;
+  } else if (stopBits == ((dataBits > 5)? SERIAL_STOP_2: SERIAL_STOP_1_5)) {
+    report.stopBits = USB_CP2110_STOP_LONG;
+  } else {
+    logUnsupportedStopBits(stopBits);
+    errno = EINVAL;
+    return 0;
+  }
+
+  switch (parity) {
+    case SERIAL_PARITY_NONE:
+      report.parity = USB_CP2110_PARITY_NONE;
+      break;
+
+    case SERIAL_PARITY_ODD:
+      report.parity = USB_CP2110_PARITY_ODD;
+      break;
+
+    case SERIAL_PARITY_EVEN:
+      report.parity = USB_CP2110_PARITY_EVEN;
+      break;
+
+    case SERIAL_PARITY_MARK:
+      report.parity = USB_CP2110_PARITY_MARK;
+      break;
+
+    case SERIAL_PARITY_SPACE:
+      report.parity = USB_CP2110_PARITY_SPACE;
+      break;
+
+    default:
+      logUnsupportedParity(parity);
+      errno = EINVAL;
+      return 0;
+  }
+
+  switch (flowControl) {
+    case SERIAL_FLOW_NONE:
+      report.flowControl = USB_CP2110_FLOW_NONE;
+      break;
+
+    case SERIAL_FLOW_HARDWARE:
+      report.flowControl = USB_CP2110_FLOW_HARDWARE;
+      break;
+
+    default:
+      logUnsupportedFlowControl(flowControl);
+      errno = EINVAL;
+      return 0;
+  }
+
+  return usbSetReport_CP2110(device, &report, sizeof(report));
+}
+
+static int
+usbSetUartStatus_CP2110 (UsbDevice *device, unsigned char status) {
+  const unsigned char report[] = {0X41, status};
+  return usbSetReport_CP2110(device, report, sizeof(report));
+}
+
+static int
+usbEnableAdapter_CP2110 (UsbDevice *device) {
+  if (usbSetUartStatus_CP2110(device, 0X01)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static ssize_t
+usbWriteData_CP2110 (UsbDevice *device, const void *data, size_t size) {
+  const unsigned char *first = data;
+  const unsigned char *next = first;
+
+  while (size) {
+    unsigned char report[0X40];
+    size_t count = sizeof(report) - 1;
+
+    if (count > size) count = size;
+    report[0] = count;
+    memcpy(&report[1], next, count);
+
+    {
+      ssize_t result = usbWriteEndpoint(device, 2, report, count+1, 1000);
+      if (result == -1) return result;
+    }
+
+    next += count;
+    size -= count;
+  }
+
+  return next - first;
+}
+
+const UsbSerialOperations usbSerialOperations_CP2110 = {
+  .name = "CP2110",
+
+  .setLineConfiguration = &usbSetLineConfiguration_CP2110,
+
+  .enableAdapter = usbEnableAdapter_CP2110,
+  .inputFilter = usbInputFilter_CP2110,
+  .writeData = usbWriteData_CP2110
+};
diff --git a/Programs/usb_cp2110.h b/Programs/usb_cp2110.h
new file mode 100644
index 0000000..df441ae
--- /dev/null
+++ b/Programs/usb_cp2110.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_CP2110
+#define BRLTTY_INCLUDED_USB_CP2110
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_CP2110 */
diff --git a/Programs/usb_darwin.c b/Programs/usb_darwin.c
new file mode 100644
index 0000000..035971e
--- /dev/null
+++ b/Programs/usb_darwin.c
@@ -0,0 +1,836 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <mach/mach.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/usb/IOUSBLib.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+#include "system_darwin.h"
+
+typedef struct {
+  UsbEndpoint *endpoint;
+  void *context;
+  void *buffer;
+  size_t length;
+
+  IOReturn result;
+  ssize_t count;
+} UsbAsynchronousRequest;
+
+struct UsbDeviceExtensionStruct {
+  IOUSBDeviceInterface182 **device;
+  unsigned deviceOpened:1;
+
+  IOUSBInterfaceInterface190 **interface;
+  unsigned interfaceOpened:1;
+  UInt8 pipeCount;
+
+  CFRunLoopSourceRef runloopSource;
+};
+
+struct UsbEndpointExtensionStruct {
+  UsbEndpoint *endpoint;
+  Queue *completedRequests;
+
+  UInt8 pipeNumber;
+  UInt8 endpointNumber;
+  UInt8 transferDirection;
+  UInt8 transferMode;
+  UInt8 pollInterval;
+  UInt16 packetSize;
+};
+
+static void
+setUsbError (long int result, const char *action) {
+  switch (result) {
+    default: setDarwinSystemError(result); break;
+
+  //MAP_DARWIN_ERROR(kIOUSBUnknownPipeErr, )
+  //MAP_DARWIN_ERROR(kIOUSBTooManyPipesErr, )
+  //MAP_DARWIN_ERROR(kIOUSBNoAsyncPortErr, )
+  //MAP_DARWIN_ERROR(kIOUSBNotEnoughPipesErr, )
+  //MAP_DARWIN_ERROR(kIOUSBNotEnoughPowerErr, )
+  //MAP_DARWIN_ERROR(kIOUSBEndpointNotFound, )
+  //MAP_DARWIN_ERROR(kIOUSBConfigNotFound, )
+    MAP_DARWIN_ERROR(kIOUSBTransactionTimeout, ETIMEDOUT)
+  //MAP_DARWIN_ERROR(kIOUSBTransactionReturned, )
+  //MAP_DARWIN_ERROR(kIOUSBPipeStalled, )
+  //MAP_DARWIN_ERROR(kIOUSBInterfaceNotFound, )
+  //MAP_DARWIN_ERROR(kIOUSBLowLatencyBufferNotPreviouslyAllocated, )
+  //MAP_DARWIN_ERROR(kIOUSBLowLatencyFrameListNotPreviouslyAllocated, )
+  //MAP_DARWIN_ERROR(kIOUSBHighSpeedSplitError, )
+  //MAP_DARWIN_ERROR(kIOUSBLinkErr, )
+  //MAP_DARWIN_ERROR(kIOUSBNotSent2Err, )
+  //MAP_DARWIN_ERROR(kIOUSBNotSent1Err, )
+  //MAP_DARWIN_ERROR(kIOUSBBufferUnderrunErr, )
+  //MAP_DARWIN_ERROR(kIOUSBBufferOverrunErr, )
+  //MAP_DARWIN_ERROR(kIOUSBReserved2Err, )
+  //MAP_DARWIN_ERROR(kIOUSBReserved1Err, )
+  //MAP_DARWIN_ERROR(kIOUSBWrongPIDErr, )
+  //MAP_DARWIN_ERROR(kIOUSBPIDCheckErr, )
+  //MAP_DARWIN_ERROR(kIOUSBDataToggleErr, )
+  //MAP_DARWIN_ERROR(kIOUSBBitstufErr, )
+  //MAP_DARWIN_ERROR(kIOUSBCRCErr, )
+  }
+
+  if (action) {
+    logMessage(LOG_WARNING, "Darwin error 0X%lX.", result);
+    logSystemError(action);
+  }
+}
+
+static int
+openDevice (UsbDevice *device, int seize) {
+  UsbDeviceExtension *devx = device->extension;
+  IOReturn result;
+
+  if (!devx->deviceOpened) {
+    const char *action = "opened";
+    int level = LOG_INFO;
+
+    result = (*devx->device)->USBDeviceOpen(devx->device);
+    if (result != kIOReturnSuccess) {
+      setUsbError(result, "USB device open");
+      if ((result != kIOReturnExclusiveAccess) || !seize) return 0;
+
+      result = (*devx->device)->USBDeviceOpenSeize(devx->device);
+      if (result != kIOReturnSuccess) {
+        setUsbError(result, "USB device seize");
+        return 0;
+      }
+
+      action = "seized";
+      level = LOG_NOTICE;
+    }
+
+    logMessage(level, "USB device %s: vendor=%04X product=%04X",
+               action, device->descriptor.idVendor, device->descriptor.idProduct);
+    devx->deviceOpened = 1;
+  }
+
+  return 1;
+}
+
+static int
+unsetInterface (UsbDeviceExtension *devx) {
+  int ok = 1;
+
+  if (devx->interface) {
+    IOReturn result;
+
+    if (devx->interfaceOpened) {
+      if (devx->runloopSource) {
+        {
+          int pipe;
+          for (pipe=1; pipe<=devx->pipeCount; ++pipe) {
+            result = (*devx->interface)->AbortPipe(devx->interface, pipe);
+            if (result != kIOReturnSuccess) {
+              setUsbError(result, "USB pipe abort");
+            }
+          }
+        }
+
+        removeRunLoopSource(devx->runloopSource);
+      }
+
+      result = (*devx->interface)->USBInterfaceClose(devx->interface);
+      if (result != kIOReturnSuccess) {
+        setUsbError(result, "USB interface close");
+        ok = 0;
+      }
+      devx->interfaceOpened = 0;
+    }
+
+    (*devx->interface)->Release(devx->interface);
+    devx->interface = NULL;
+  }
+
+  return ok;
+}
+
+static int
+isInterface (IOUSBInterfaceInterface190 **interface, UInt8 number) {
+  IOReturn result;
+  UInt8 num;
+
+  result = (*interface)->GetInterfaceNumber(interface, &num);
+  if (result != kIOReturnSuccess) {
+    setUsbError(result, "USB interface number query");
+  } else if (num == number) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+setInterface (UsbDeviceExtension *devx, UInt8 number) {
+  int found = 0;
+  IOReturn result;
+  io_iterator_t iterator = 0;
+
+  if (devx->interface)
+    if (isInterface(devx->interface, number))
+      return 1;
+
+  {
+    IOUSBFindInterfaceRequest request;
+
+    request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
+    request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
+    request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
+    request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+
+    result = (*devx->device)->CreateInterfaceIterator(devx->device, &request, &iterator);
+  }
+
+  if ((result == kIOReturnSuccess) && iterator) {
+    io_service_t service;
+
+    while ((service = IOIteratorNext(iterator))) {
+      IOCFPlugInInterface **plugin = NULL;
+      SInt32 score;
+
+      result = IOCreatePlugInInterfaceForService(service,
+                                                 kIOUSBInterfaceUserClientTypeID,
+                                                 kIOCFPlugInInterfaceID,
+                                                 &plugin, &score);
+      IOObjectRelease(service);
+      service = 0;
+
+      if ((result == kIOReturnSuccess) && plugin) {
+        IOUSBInterfaceInterface190 **interface = NULL;
+
+        result = (*plugin)->QueryInterface(plugin,
+                                           CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID190),
+                                           (LPVOID)&interface);
+        (*plugin)->Release(plugin);
+        plugin = NULL;
+
+        if ((result == kIOReturnSuccess) && interface) {
+          if (isInterface(interface, number)) {
+            unsetInterface(devx);
+            devx->interface = interface;
+            found = 1;
+            break;
+          }
+
+          (*interface)->Release(interface);
+          interface = NULL;
+        } else {
+          setUsbError(result, "USB interface interface create");
+        }
+      } else {
+        setUsbError(result, "USB interface service plugin create");
+      }
+    }
+    if (!found) logMessage(LOG_ERR, "USB interface not found: %d", number);
+
+    IOObjectRelease(iterator);
+    iterator = 0;
+  } else {
+    setUsbError(result, "USB interface iterator create");
+  }
+
+  return found;
+}
+
+static void
+usbAsynchronousRequestCallback (void *context, IOReturn result, void *arg) {
+  UsbAsynchronousRequest *request = context;
+  UsbEndpoint *endpoint = request->endpoint;
+  UsbEndpointExtension *eptx = endpoint->extension;
+
+  request->result = result;
+  request->count = (intptr_t)arg;
+
+  if (!enqueueItem(eptx->completedRequests, request)) {
+    logSystemError("USB completed request enqueue");
+    free(request);
+  }
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (openDevice(device, 1)) {
+    UInt8 arg = configuration;
+    IOReturn result = (*devx->device)->SetConfiguration(devx->device, arg);
+    if (result == kIOReturnSuccess) return 1;
+    setUsbError(result, "USB configuration set");
+  }
+
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+  IOReturn result;
+
+  if (setInterface(devx, interface)) {
+    if (devx->interfaceOpened) return 1;
+
+    result = (*devx->interface)->USBInterfaceOpen(devx->interface);
+    if (result == kIOReturnSuccess) {
+      result = (*devx->interface)->GetNumEndpoints(devx->interface, &devx->pipeCount);
+      if (result == kIOReturnSuccess) {
+        devx->interfaceOpened = 1;
+        return 1;
+      } else {
+        setUsbError(result, "USB pipe count query");
+      }
+
+      (*devx->interface)->USBInterfaceClose(devx->interface);
+    } else {
+      setUsbError(result, "USB interface open");
+    }
+  }
+
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+  return setInterface(devx, interface) && unsetInterface(devx);
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (setInterface(devx, interface)) {
+    IOReturn result;
+    UInt8 arg;
+
+    result = (*devx->interface)->GetAlternateSetting(devx->interface, &arg);
+    if (result == kIOReturnSuccess) {
+      if (arg == alternative) return 1;
+
+      arg = alternative;
+      result = (*devx->interface)->SetAlternateInterface(devx->interface, arg);
+      if (result == kIOReturnSuccess) return 1;
+      setUsbError(result, "USB alternative set");
+    } else {
+      setUsbError(result, "USB alternative get");
+    }
+  }
+
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    IOReturn result;
+
+    result = (*devx->interface)->ClearPipeStallBothEnds(devx->interface, eptx->pipeNumber);
+    if (result == kIOReturnSuccess) return 1;
+    setUsbError(result, "USB endpoint clear");
+  }
+
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  IOReturn result;
+  IOUSBDevRequestTO arg;
+
+  arg.bmRequestType = direction | recipient | type;
+  arg.bRequest = request;
+  arg.wValue = value;
+  arg.wIndex = index;
+  arg.wLength = length;
+
+  arg.pData = buffer;
+  arg.noDataTimeout = timeout;
+  arg.completionTimeout = timeout;
+
+  result = (*devx->device)->DeviceRequestTO(devx->device, &arg);
+  if (result == kIOReturnSuccess) return arg.wLenDone;
+  setUsbError(result, "USB control transfer");
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    IOReturn result;
+    UsbAsynchronousRequest *request;
+
+    if (!devx->runloopSource) {
+      result = (*devx->interface)->CreateInterfaceAsyncEventSource(devx->interface,
+                                                                   &devx->runloopSource);
+      if (result != kIOReturnSuccess) {
+        setUsbError(result, "USB interface event source create");
+        return NULL;
+      }
+
+      addRunLoopSource(devx->runloopSource);
+    }
+
+    if ((request = malloc(sizeof(*request) + length))) {
+      request->endpoint = endpoint;
+      request->context = context;
+      request->buffer = (request->length = length)? (request + 1): NULL;
+
+      switch (eptx->transferDirection) {
+        case kUSBIn:
+          result = (*devx->interface)->ReadPipeAsync(devx->interface, eptx->pipeNumber,
+                                                     request->buffer, request->length,
+                                                     usbAsynchronousRequestCallback, request);
+          if (result == kIOReturnSuccess) return request;
+          setUsbError(result, "USB endpoint asynchronous read");
+          break;
+
+        case kUSBOut:
+          if (request->buffer) memcpy(request->buffer, buffer, length);
+          result = (*devx->interface)->WritePipeAsync(devx->interface, eptx->pipeNumber,
+                                                      request->buffer, request->length,
+                                                      usbAsynchronousRequestCallback, request);
+          if (result == kIOReturnSuccess) return request;
+          setUsbError(result, "USB endpoint asynchronous write");
+          break;
+
+        default:
+          logMessage(LOG_ERR, "USB endpoint direction not suppported: %d",
+                     eptx->transferDirection);
+          errno = ENOSYS;
+          break;
+      }
+
+      free(request);
+    } else {
+      logSystemError("USB asynchronous request allocate");
+    }
+  }
+
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    UsbAsynchronousRequest *request;
+
+    while (!(request = dequeueItem(eptx->completedRequests))) {
+      switch (executeRunLoop((wait? 60: 0))) {
+        case kCFRunLoopRunTimedOut:
+          if (wait) continue;
+        case kCFRunLoopRunFinished:
+          errno = EAGAIN;
+          goto none;
+
+        case kCFRunLoopRunStopped:
+        case kCFRunLoopRunHandledSource:
+        default:
+          continue;
+      }
+    }
+
+    response->context = request->context;
+    response->buffer = request->buffer;
+    response->size = request->length;
+
+    if (request->result == kIOReturnSuccess) {
+      response->error = 0;
+      response->count = request->count;
+
+      if (!usbApplyInputFilters(endpoint, response->buffer, response->size, &response->count)) {
+        response->error = EIO;
+        response->count = -1;
+      }
+    } else {
+      setUsbError(request->result, "USB asynchronous response");
+      response->error = errno;
+      response->count = -1;
+    }
+
+    return request;
+  }
+
+none:
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    IOReturn result;
+    UInt32 count;
+    int stalled = 0;
+
+  read:
+    count = length;
+    result = (*devx->interface)->ReadPipeTO(devx->interface, eptx->pipeNumber,
+                                            buffer, &count,
+                                            timeout, timeout);
+
+    switch (result) {
+      case kIOReturnSuccess:
+        {
+          ssize_t actual = count;
+
+          if (usbApplyInputFilters(endpoint, buffer, length, &actual)) return actual;
+        }
+
+        errno = EIO;
+        break;
+
+      case kIOUSBTransactionTimeout:
+        errno = EAGAIN;
+        break;
+
+      case kIOUSBPipeStalled:
+        if (!stalled) {
+          result = (*devx->interface)->ClearPipeStallBothEnds(devx->interface, eptx->pipeNumber);
+          if (result == kIOReturnSuccess) {
+            stalled = 1;
+            goto read;
+          }
+
+          setUsbError(result, "USB stall clear");
+          break;
+        }
+
+      default:
+        setUsbError(result, "USB endpoint read");
+        break;
+    }
+  }
+
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    IOReturn result;
+
+    result = (*devx->interface)->WritePipeTO(devx->interface, eptx->pipeNumber,
+                                             (void *)buffer, length,
+                                             timeout, timeout);
+    if (result == kIOReturnSuccess) return length;
+    setUsbError(result, "USB endpoint write");
+  }
+
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+  IOReturn result;
+
+  {
+    uint8_t speed;
+    if ((result = (*devx->device)->GetDeviceSpeed(devx->device, &speed)) != kIOReturnSuccess) goto error;
+    switch (speed) {
+      default                 : device->descriptor.bcdUSB = 0X0000; break;
+      case kUSBDeviceSpeedLow : device->descriptor.bcdUSB = kUSBRel10; break;
+      case kUSBDeviceSpeedFull: device->descriptor.bcdUSB = kUSBRel11; break;
+      case kUSBDeviceSpeedHigh: device->descriptor.bcdUSB = kUSBRel20; break;
+    }
+  }
+
+  if ((result = (*devx->device)->GetDeviceClass(devx->device, &device->descriptor.bDeviceClass)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->GetDeviceSubClass(devx->device, &device->descriptor.bDeviceSubClass)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->GetDeviceProtocol(devx->device, &device->descriptor.bDeviceProtocol)) != kIOReturnSuccess) goto error;
+
+  if ((result = (*devx->device)->GetDeviceVendor(devx->device, &device->descriptor.idVendor)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->GetDeviceProduct(devx->device, &device->descriptor.idProduct)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->GetDeviceReleaseNumber(devx->device, &device->descriptor.bcdDevice)) != kIOReturnSuccess) goto error;
+
+  if ((result = (*devx->device)->USBGetManufacturerStringIndex(devx->device, &device->descriptor.iManufacturer)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->USBGetProductStringIndex(devx->device, &device->descriptor.iProduct)) != kIOReturnSuccess) goto error;
+  if ((result = (*devx->device)->USBGetSerialNumberStringIndex(devx->device, &device->descriptor.iSerialNumber)) != kIOReturnSuccess) goto error;
+
+  if ((result = (*devx->device)->GetNumberOfConfigurations(devx->device, &device->descriptor.bNumConfigurations)) != kIOReturnSuccess) goto error;
+  device->descriptor.bMaxPacketSize0 = 0;
+
+  device->descriptor.bLength = UsbDescriptorSize_Device;
+  device->descriptor.bDescriptorType = UsbDescriptorType_Device;
+  return 1;
+
+error:
+  setUsbError(result, "USB device descriptor read");
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  UsbDeviceExtension *devx = endpoint->device->extension;
+  UsbEndpointExtension *eptx;
+
+  if ((eptx = malloc(sizeof(*eptx)))) {
+    if ((eptx->completedRequests = newQueue(NULL, NULL))) {
+      IOReturn result;
+      unsigned char number = USB_ENDPOINT_NUMBER(endpoint->descriptor);
+      unsigned char direction = USB_ENDPOINT_DIRECTION(endpoint->descriptor);
+
+      for (eptx->pipeNumber=1; eptx->pipeNumber<=devx->pipeCount; ++eptx->pipeNumber) {
+        result = (*devx->interface)->GetPipeProperties(devx->interface, eptx->pipeNumber,
+                                                       &eptx->transferDirection, &eptx->endpointNumber,
+                                                       &eptx->transferMode, &eptx->packetSize, &eptx->pollInterval);
+        if (result == kIOReturnSuccess) {
+          if ((eptx->endpointNumber == number) &&
+              (((eptx->transferDirection == kUSBIn) && (direction == UsbEndpointDirection_Input)) ||
+               ((eptx->transferDirection == kUSBOut) && (direction == UsbEndpointDirection_Output)))) {
+            logMessage(LOG_CATEGORY(USB_IO), "ept=%02X -> pip=%d (num=%d dir=%d xfr=%d int=%d pkt=%d)",
+                       endpoint->descriptor->bEndpointAddress, eptx->pipeNumber,
+                       eptx->endpointNumber, eptx->transferDirection, eptx->transferMode,
+                       eptx->pollInterval, eptx->packetSize);
+
+            eptx->endpoint = endpoint;
+            endpoint->extension = eptx;
+            return 1;
+          }
+        } else {
+          setUsbError(result, "USB pipe properties query");
+        }
+      }
+
+      errno = EIO;
+      logMessage(LOG_ERR, "USB pipe not found: ept=%02X",
+                 endpoint->descriptor->bEndpointAddress);
+
+      deallocateQueue(eptx->completedRequests);
+    } else {
+      logSystemError("USB completed request queue allocate");
+    }
+
+    free(eptx);
+  } else {
+    logSystemError("USB endpoint extension allocate");
+  }
+
+  return 0;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+  if (eptx->completedRequests) {
+    deallocateQueue(eptx->completedRequests);
+    eptx->completedRequests = NULL;
+  }
+
+  free(eptx);
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  IOReturn result;
+
+  unsetInterface(devx);
+
+  if (devx->deviceOpened) {
+    result = (*devx->device)->USBDeviceClose(devx->device);
+    if (result != kIOReturnSuccess) setUsbError(result, "USB device close");
+    devx->deviceOpened = 0;
+  }
+
+  (*devx->device)->Release(devx->device);
+  devx->device = NULL;
+
+  free(devx);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  UsbDevice *device = NULL;
+  kern_return_t kernelResult;
+  IOReturn ioResult;
+  mach_port_t port;
+
+  kernelResult = IOMasterPort(MACH_PORT_NULL, &port);
+  if (kernelResult == KERN_SUCCESS) {
+    CFMutableDictionaryRef dictionary;
+
+    if ((dictionary = IOServiceMatching(kIOUSBDeviceClassName))) {
+      io_iterator_t iterator = 0;
+
+      kernelResult = IOServiceGetMatchingServices(port, dictionary, &iterator);
+      dictionary = NULL;
+
+      if ((kernelResult == KERN_SUCCESS) && iterator) {
+        io_service_t service;
+
+        while ((service = IOIteratorNext(iterator))) {
+          IOCFPlugInInterface **plugin = NULL;
+          SInt32 score;
+
+          ioResult = IOCreatePlugInInterfaceForService(service,
+                                                       kIOUSBDeviceUserClientTypeID,
+                                                       kIOCFPlugInInterfaceID,
+                                                       &plugin, &score);
+          IOObjectRelease(service);
+          service = 0;
+
+          if ((ioResult == kIOReturnSuccess) && plugin) {
+            IOUSBDeviceInterface182 **interface = NULL;
+
+            ioResult = (*plugin)->QueryInterface(plugin,
+                                                 CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182),
+                                                 (LPVOID)&interface);
+            (*plugin)->Release(plugin);
+            plugin = NULL;
+
+            if ((ioResult == kIOReturnSuccess) && interface) {
+              UsbDeviceExtension *devx;
+
+              if ((devx = malloc(sizeof(*devx)))) {
+                devx->device = interface;
+                devx->deviceOpened = 0;
+
+                devx->interface = NULL;
+                devx->interfaceOpened = 0;
+
+                devx->runloopSource = NULL;
+
+                if ((device = usbTestDevice(devx, chooser, data))) break;
+                free(devx);
+                devx = NULL;
+              } else {
+                logSystemError("USB device extension allocate");
+              }
+
+              (*interface)->Release(interface);
+              interface = NULL;
+            } else {
+              setUsbError(ioResult, "USB device interface create");
+            }
+          } else {
+            setUsbError(ioResult, "USB device service plugin create");
+          }
+        }
+
+        IOObjectRelease(iterator);
+        iterator = 0;
+      } else {
+        setUsbError(kernelResult, "USB device iterator create");
+      }
+    } else {
+      logMessage(LOG_ERR, "USB device matching dictionary create error.");
+    }
+
+    mach_port_deallocate(mach_task_self(), port);
+  } else {
+    setUsbError(kernelResult, "Darwin master port create");
+  }
+
+  return device;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_devices.c b/Programs/usb_devices.c
new file mode 100644
index 0000000..372f86e
--- /dev/null
+++ b/Programs/usb_devices.c
@@ -0,0 +1,541 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "usb_devices.h"
+
+#define USB_DEVICE_ENTRY(vendor,product,...) { \
+  .vendorIdentifier = vendor, \
+  .productIdentifier = product, \
+  .driverCodes = NULL_TERMINATED_STRING_ARRAY(__VA_ARGS__) \
+}
+
+const UsbDeviceEntry usbDeviceTable[] = {
+// BEGIN_USB_BRAILLE_DEVICES
+
+// Device: 0403:6001
+// Generic Identifier
+// Vendor: Future Technology Devices International, Ltd.
+// Product: FT232 USB-Serial (UART) IC
+// Albatross [all models]
+// Cebra [all models]
+// HIMS [Sync Braille]
+// HandyTech [FTDI chip]
+// Hedo [MobilLine]
+// MDV [all models]
+USB_DEVICE_ENTRY(0X0403, 0X6001, "at", "ce", "hd", "hm", "ht", "md"),
+
+// Device: 0403:6010
+// Generic Identifier
+// Vendor: Future Technology Devices International, Ltd
+// Product: FT2232C/D/H Dual UART/FIFO IC
+// DotPad [all models]
+USB_DEVICE_ENTRY(0X0403, 0X6010, "dp"),
+
+// Device: 0403:DE58
+// Hedo [MobilLine]
+USB_DEVICE_ENTRY(0X0403, 0XDE58, "hd"),
+
+// Device: 0403:DE59
+// Hedo [ProfiLine]
+USB_DEVICE_ENTRY(0X0403, 0XDE59, "hd"),
+
+// Device: 0403:F208
+// Papenmeier [all models]
+USB_DEVICE_ENTRY(0X0403, 0XF208, "pm"),
+
+// Device: 0403:FE70
+// Baum [Vario 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE70, "bm"),
+
+// Device: 0403:FE71
+// Baum [PocketVario (24 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE71, "bm"),
+
+// Device: 0403:FE72
+// Baum [SuperVario 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE72, "bm"),
+
+// Device: 0403:FE73
+// Baum [SuperVario 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE73, "bm"),
+
+// Device: 0403:FE74
+// Baum [SuperVario 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE74, "bm"),
+
+// Device: 0403:FE75
+// Baum [SuperVario 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE75, "bm"),
+
+// Device: 0403:FE76
+// Baum [VarioPro 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE76, "bm"),
+
+// Device: 0403:FE77
+// Baum [VarioPro 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0403, 0XFE77, "bm"),
+
+// Device: 0452:0100
+// Metec [all models]
+USB_DEVICE_ENTRY(0X0452, 0X0100, "mt"),
+
+// Device: 045E:930A
+// HIMS [Braille Sense (USB 1.1)]
+// HIMS [Braille Sense (USB 2.0)]
+// HIMS [Braille Sense U2 (USB 2.0)]
+USB_DEVICE_ENTRY(0X045E, 0X930A, "hm"),
+
+// Device: 045E:930B
+// HIMS [Braille Edge and QBrailleXL]
+USB_DEVICE_ENTRY(0X045E, 0X930B, "hm"),
+
+// Device: 0483:A1D3
+// Baum [Orbit Reader 20 (20 cells)]
+USB_DEVICE_ENTRY(0X0483, 0XA1D3, "bm"),
+
+// Device: 0483:A366
+// Baum [Orbit Reader 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0483, 0XA366, "bm"),
+
+// Device: 06B0:0001
+// Alva [Satellite (5nn)]
+USB_DEVICE_ENTRY(0X06B0, 0X0001, "al"),
+
+// Device: 0798:0001
+// Voyager [all models]
+USB_DEVICE_ENTRY(0X0798, 0X0001, "vo"),
+
+// Device: 0798:0600
+// Alva [Voyager Protocol Converter]
+USB_DEVICE_ENTRY(0X0798, 0X0600, "al"),
+
+// Device: 0798:0624
+// Alva [BC624]
+USB_DEVICE_ENTRY(0X0798, 0X0624, "al"),
+
+// Device: 0798:0640
+// Alva [BC640]
+USB_DEVICE_ENTRY(0X0798, 0X0640, "al"),
+
+// Device: 0798:0680
+// Alva [BC680]
+USB_DEVICE_ENTRY(0X0798, 0X0680, "al"),
+
+// Device: 0904:1016
+// FrankAudiodata [B2K84 (before firmware installation)]
+USB_DEVICE_ENTRY(0X0904, 0X1016, "fa"),
+
+// Device: 0904:1017
+// FrankAudiodata [B2K84 (after firmware installation)]
+USB_DEVICE_ENTRY(0X0904, 0X1017, "fa"),
+
+// Device: 0904:2000
+// Baum [VarioPro 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2000, "bm"),
+
+// Device: 0904:2001
+// Baum [EcoVario 24 (24 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2001, "bm"),
+
+// Device: 0904:2002
+// Baum [EcoVario 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2002, "bm"),
+
+// Device: 0904:2007
+// Baum [VarioConnect 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2007, "bm"),
+
+// Device: 0904:2008
+// Baum [VarioConnect 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2008, "bm"),
+
+// Device: 0904:2009
+// Baum [VarioConnect 24 (24 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2009, "bm"),
+
+// Device: 0904:2010
+// Baum [VarioConnect 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2010, "bm"),
+
+// Device: 0904:2011
+// Baum [VarioConnect 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2011, "bm"),
+
+// Device: 0904:2014
+// Baum [EcoVario 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2014, "bm"),
+
+// Device: 0904:2015
+// Baum [EcoVario 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2015, "bm"),
+
+// Device: 0904:2016
+// Baum [EcoVario 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X2016, "bm"),
+
+// Device: 0904:3000
+// Baum [Refreshabraille 18 (18 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X3000, "bm"),
+
+// Device: 0904:3001
+// Baum [Orbit in Refreshabraille Emulation Mode (18 cells)]
+// Baum [Refreshabraille 18 (18 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X3001, "bm"),
+
+// Device: 0904:4004
+// Baum [Pronto! V3 18 (18 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X4004, "bm"),
+
+// Device: 0904:4005
+// Baum [Pronto! V3 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X4005, "bm"),
+
+// Device: 0904:4007
+// Baum [Pronto! V4 18 (18 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X4007, "bm"),
+
+// Device: 0904:4008
+// Baum [Pronto! V4 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X4008, "bm"),
+
+// Device: 0904:6001
+// Baum [SuperVario2 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6001, "bm"),
+
+// Device: 0904:6002
+// Baum [PocketVario2 (24 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6002, "bm"),
+
+// Device: 0904:6003
+// Baum [SuperVario2 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6003, "bm"),
+
+// Device: 0904:6004
+// Baum [SuperVario2 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6004, "bm"),
+
+// Device: 0904:6005
+// Baum [SuperVario2 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6005, "bm"),
+
+// Device: 0904:6006
+// Baum [Brailliant2 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6006, "bm"),
+
+// Device: 0904:6007
+// Baum [Brailliant2 24 (24 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6007, "bm"),
+
+// Device: 0904:6008
+// Baum [Brailliant2 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6008, "bm"),
+
+// Device: 0904:6009
+// Baum [Brailliant2 64 (64 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6009, "bm"),
+
+// Device: 0904:600A
+// Baum [Brailliant2 80 (80 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X600A, "bm"),
+
+// Device: 0904:6011
+// Baum [VarioConnect 24 (24 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6011, "bm"),
+
+// Device: 0904:6012
+// Baum [VarioConnect 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6012, "bm"),
+
+// Device: 0904:6013
+// Baum [VarioConnect 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6013, "bm"),
+
+// Device: 0904:6101
+// Baum [VarioUltra 20 (20 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6101, "bm"),
+
+// Device: 0904:6102
+// Baum [VarioUltra 40 (40 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6102, "bm"),
+
+// Device: 0904:6103
+// Baum [VarioUltra 32 (32 cells)]
+USB_DEVICE_ENTRY(0X0904, 0X6103, "bm"),
+
+// Device: 0921:1200
+// HandyTech [GoHubs chip]
+USB_DEVICE_ENTRY(0X0921, 0X1200, "ht"),
+
+// Device: 0F4E:0100
+// FreedomScientific [Focus 1]
+USB_DEVICE_ENTRY(0X0F4E, 0X0100, "fs"),
+
+// Device: 0F4E:0111
+// FreedomScientific [PAC Mate]
+USB_DEVICE_ENTRY(0X0F4E, 0X0111, "fs"),
+
+// Device: 0F4E:0112
+// FreedomScientific [Focus 2]
+USB_DEVICE_ENTRY(0X0F4E, 0X0112, "fs"),
+
+// Device: 0F4E:0114
+// FreedomScientific [Focus 3+]
+USB_DEVICE_ENTRY(0X0F4E, 0X0114, "fs"),
+
+// Device: 10C4:EA60
+// Generic Identifier
+// Vendor: Cygnal Integrated Products, Inc.
+// Product: CP210x UART Bridge / myAVR mySmartUSB light
+// BrailleMemo [Pocket]
+// Seika [Braille Display]
+USB_DEVICE_ENTRY(0X10C4, 0XEA60, "mm", "sk"),
+
+// Device: 10C4:EA80
+// Generic Identifier
+// Vendor: Cygnal Integrated Products, Inc.
+// Product: CP210x UART Bridge
+// Seika [Note Taker]
+USB_DEVICE_ENTRY(0X10C4, 0XEA80, "sk"),
+
+// Device: 1148:0301
+// BrailleMemo [Smart]
+USB_DEVICE_ENTRY(0X1148, 0X0301, "mm"),
+
+// Device: 1209:ABC0
+// Inceptor [all models]
+USB_DEVICE_ENTRY(0X1209, 0XABC0, "ic"),
+
+// Device: 16C0:05E1
+// Canute [all models]
+USB_DEVICE_ENTRY(0X16C0, 0X05E1, "cn"),
+
+// Device: 1A86:7523
+// Generic Identifier
+// Vendor: Jiangsu QinHeng, Ltd.
+// Product: CH341 USB Bridge Controller
+// Baum [NLS eReader Zoomax (20 cells)]
+USB_DEVICE_ENTRY(0X1A86, 0X7523, "bm"),
+
+// Device: 1C71:C004
+// BrailleNote [HumanWare APEX]
+USB_DEVICE_ENTRY(0X1C71, 0XC004, "bn"),
+
+// Device: 1C71:C005
+// HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC005, "hw"),
+
+// Device: 1C71:C006
+// HumanWare [non-Touch models (HID protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC006, "hw"),
+
+// Device: 1C71:C00A
+// HumanWare [BrailleNote Touch (HID protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC00A, "hw"),
+
+// Device: 1C71:C021
+// HumanWare [Brailliant BI 14 (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC021, "hw"),
+
+// Device: 1C71:C101
+// HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)]
+// HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XC101, "hw"),
+
+// Device: 1C71:C104
+// HumanWare [APH Chameleon 20 (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC104, "hw"),
+
+// Device: 1C71:C111
+// HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)]
+// HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XC111, "hw"),
+
+// Device: 1C71:C114
+// HumanWare [APH Mantis Q40 (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC114, "hw"),
+
+// Device: 1C71:C121
+// HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)]
+// HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XC121, "hw"),
+
+// Device: 1C71:C124
+// HumanWare [Humanware BrailleOne (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XC124, "hw"),
+
+// Device: 1C71:C131
+// HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)]
+// HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XC131, "hw"),
+
+// Device: 1C71:C141
+// HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)]
+// HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XC141, "hw"),
+
+// Device: 1C71:CE01
+// HumanWare [NLS eReader (HID protocol, firmware 1.0)]
+// HumanWare [NLS eReader (HID protocol, firmware 1.1)]
+USB_DEVICE_ENTRY(0X1C71, 0XCE01, "hw"),
+
+// Device: 1C71:CE04
+// HumanWare [NLS eReader (serial protocol)]
+USB_DEVICE_ENTRY(0X1C71, 0XCE04, "hw"),
+
+// Device: 1FE4:0003
+// HandyTech [USB-HID adapter]
+USB_DEVICE_ENTRY(0X1FE4, 0X0003, "ht"),
+
+// Device: 1FE4:0044
+// HandyTech [Easy Braille (HID)]
+USB_DEVICE_ENTRY(0X1FE4, 0X0044, "ht"),
+
+// Device: 1FE4:0054
+// HandyTech [Active Braille]
+USB_DEVICE_ENTRY(0X1FE4, 0X0054, "ht"),
+
+// Device: 1FE4:0055
+// HandyTech [Connect Braille 40]
+USB_DEVICE_ENTRY(0X1FE4, 0X0055, "ht"),
+
+// Device: 1FE4:0061
+// HandyTech [Actilino]
+USB_DEVICE_ENTRY(0X1FE4, 0X0061, "ht"),
+
+// Device: 1FE4:0064
+// HandyTech [Active Star 40]
+USB_DEVICE_ENTRY(0X1FE4, 0X0064, "ht"),
+
+// Device: 1FE4:0074
+// HandyTech [Braille Star 40 (HID)]
+USB_DEVICE_ENTRY(0X1FE4, 0X0074, "ht"),
+
+// Device: 1FE4:0081
+// HandyTech [Basic Braille 16]
+USB_DEVICE_ENTRY(0X1FE4, 0X0081, "ht"),
+
+// Device: 1FE4:0082
+// HandyTech [Basic Braille 20]
+USB_DEVICE_ENTRY(0X1FE4, 0X0082, "ht"),
+
+// Device: 1FE4:0083
+// HandyTech [Basic Braille 32]
+USB_DEVICE_ENTRY(0X1FE4, 0X0083, "ht"),
+
+// Device: 1FE4:0084
+// HandyTech [Basic Braille 40]
+USB_DEVICE_ENTRY(0X1FE4, 0X0084, "ht"),
+
+// Device: 1FE4:0086
+// HandyTech [Basic Braille 64]
+USB_DEVICE_ENTRY(0X1FE4, 0X0086, "ht"),
+
+// Device: 1FE4:0087
+// HandyTech [Basic Braille 80]
+USB_DEVICE_ENTRY(0X1FE4, 0X0087, "ht"),
+
+// Device: 1FE4:008A
+// HandyTech [Basic Braille 48]
+USB_DEVICE_ENTRY(0X1FE4, 0X008A, "ht"),
+
+// Device: 1FE4:008B
+// HandyTech [Basic Braille 160]
+USB_DEVICE_ENTRY(0X1FE4, 0X008B, "ht"),
+
+// Device: 1FE4:00A4
+// HandyTech [Activator]
+USB_DEVICE_ENTRY(0X1FE4, 0X00A4, "ht"),
+
+// Device: 4242:0001
+// Pegasus [all models]
+USB_DEVICE_ENTRY(0X4242, 0X0001, "pg"),
+
+// Device: C251:1122
+// EuroBraille [Esys (version < 3.0, no SD card)]
+USB_DEVICE_ENTRY(0XC251, 0X1122, "eu"),
+
+// Device: C251:1123
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1123, "eu"),
+
+// Device: C251:1124
+// EuroBraille [Esys (version < 3.0, with SD card)]
+USB_DEVICE_ENTRY(0XC251, 0X1124, "eu"),
+
+// Device: C251:1125
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1125, "eu"),
+
+// Device: C251:1126
+// EuroBraille [Esys (version >= 3.0, no SD card)]
+USB_DEVICE_ENTRY(0XC251, 0X1126, "eu"),
+
+// Device: C251:1127
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1127, "eu"),
+
+// Device: C251:1128
+// EuroBraille [Esys (version >= 3.0, with SD card)]
+USB_DEVICE_ENTRY(0XC251, 0X1128, "eu"),
+
+// Device: C251:1129
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1129, "eu"),
+
+// Device: C251:112A
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112A, "eu"),
+
+// Device: C251:112B
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112B, "eu"),
+
+// Device: C251:112C
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112C, "eu"),
+
+// Device: C251:112D
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112D, "eu"),
+
+// Device: C251:112E
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112E, "eu"),
+
+// Device: C251:112F
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X112F, "eu"),
+
+// Device: C251:1130
+// EuroBraille [Esytime (firmware 1.03, 2014-03-31)]
+// EuroBraille [Esytime]
+USB_DEVICE_ENTRY(0XC251, 0X1130, "eu"),
+
+// Device: C251:1131
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1131, "eu"),
+
+// Device: C251:1132
+// EuroBraille [reserved]
+USB_DEVICE_ENTRY(0XC251, 0X1132, "eu"),
+
+// END_USB_BRAILLE_DEVICES
+};
+
+const uint16_t usbDeviceCount = ARRAY_COUNT(usbDeviceTable);
+
diff --git a/Programs/usb_devices.h b/Programs/usb_devices.h
new file mode 100644
index 0000000..d52c20b
--- /dev/null
+++ b/Programs/usb_devices.h
@@ -0,0 +1,41 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_DEVICES
+#define BRLTTY_INCLUDED_USB_DEVICES
+
+#include "prologue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const char *const *driverCodes;
+  uint16_t vendorIdentifier;
+  uint16_t productIdentifier;
+} UsbDeviceEntry;
+
+extern const UsbDeviceEntry usbDeviceTable[];
+extern const uint16_t usbDeviceCount;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_DEVICES */
diff --git a/Programs/usb_freebsd.c b/Programs/usb_freebsd.c
new file mode 100644
index 0000000..17359cd
--- /dev/null
+++ b/Programs/usb_freebsd.c
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <dev/usb/usb.h>
+
+#include "io_usb.h"
+#include "usb_internal.h"
+
+#define USB_CONTROL_PATH_FORMAT "/dev/%s"
+#define USB_ENDPOINT_PATH_FORMAT "%.*s.%d"
+#include "usb_bsd.h"
diff --git a/Programs/usb_ftdi.c b/Programs/usb_ftdi.c
new file mode 100644
index 0000000..5c0f37a
--- /dev/null
+++ b/Programs/usb_ftdi.c
@@ -0,0 +1,213 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "usb_serial.h"
+#include "usb_ftdi.h"
+
+static int
+usbInputFilter_FTDI (UsbInputFilterData *data) {
+  return usbSkipInitialBytes(data, 2);
+}
+
+static int
+usbSetAttribute_FTDI (UsbDevice *device, unsigned char request, unsigned int value, unsigned int index) {
+  logMessage(LOG_CATEGORY(SERIAL_IO), "FTDI request: %02X %04X %04X", request, value, index);
+  return usbControlWrite(device, UsbControlRecipient_Device, UsbControlType_Vendor,
+                         request, value, index, NULL, 0, 1000) != -1;
+}
+
+static int
+usbSetBaud_FTDI (UsbDevice *device, unsigned int divisor) {
+  return usbSetAttribute_FTDI(device, 3, divisor&0XFFFF, divisor>>0X10);
+}
+
+static int
+usbSetBaud_FTDI_SIO (UsbDevice *device, unsigned int baud) {
+  unsigned int divisor;
+  switch (baud) {
+    case    300: divisor = 0; break;
+    case    600: divisor = 1; break;
+    case   1200: divisor = 2; break;
+    case   2400: divisor = 3; break;
+    case   4800: divisor = 4; break;
+    case   9600: divisor = 5; break;
+    case  19200: divisor = 6; break;
+    case  38400: divisor = 7; break;
+    case  57600: divisor = 8; break;
+    case 115200: divisor = 9; break;
+    default:
+      logUnsupportedBaud(baud);
+      errno = EINVAL;
+      return 0;
+  }
+  return usbSetBaud_FTDI(device, divisor);
+}
+
+static int
+usbSetBaud_FTDI_FT8U232AM (UsbDevice *device, unsigned int baud) {
+  if (baud > 3000000) {
+    logUnsupportedBaud(baud);
+    errno = EINVAL;
+    return 0;
+  }
+  {
+    const unsigned int base = 48000000;
+    unsigned int eighths = base / 2 / baud;
+    unsigned int divisor;
+    if ((eighths & 07) == 7) eighths++;
+    divisor = eighths >> 3;
+    divisor |= (eighths & 04)? 0X4000:
+               (eighths & 02)? 0X8000:
+               (eighths & 01)? 0XC000:
+                               0X0000;
+    if (divisor == 1) divisor = 0;
+    return usbSetBaud_FTDI(device, divisor);
+  }
+}
+
+static int
+usbSetBaud_FTDI_FT232BM (UsbDevice *device, unsigned int baud) {
+  if (baud > 3000000) {
+    logUnsupportedBaud(baud);
+    errno = EINVAL;
+    return 0;
+  }
+  {
+    static const unsigned char mask[8] = {00, 03, 02, 04, 01, 05, 06, 07};
+    const unsigned int base = 48000000;
+    const unsigned int eighths = base / 2 / baud;
+    unsigned int divisor = (eighths >> 3) | (mask[eighths & 07] << 14);
+    if (divisor == 1) {
+      divisor = 0;
+    } else if (divisor == 0X4001) {
+      divisor = 1;
+    }
+    return usbSetBaud_FTDI(device, divisor);
+  }
+}
+
+static int
+usbSetFlowControl_FTDI (UsbDevice *device, SerialFlowControl flow) {
+  unsigned int index = 0;
+#define FTDI_FLOW(from,to) if ((flow & (from)) == (from)) flow &= ~(from), index |= (to)
+  FTDI_FLOW(SERIAL_FLOW_OUTPUT_CTS|SERIAL_FLOW_INPUT_RTS, 0X0100);
+  FTDI_FLOW(SERIAL_FLOW_OUTPUT_DSR|SERIAL_FLOW_INPUT_DTR, 0X0200);
+  FTDI_FLOW(SERIAL_FLOW_OUTPUT_XON|SERIAL_FLOW_INPUT_XON, 0X0400);
+#undef FTDI_FLOW
+  if (flow) {
+    logUnsupportedFlowControl(flow);
+  }
+  return usbSetAttribute_FTDI(device, 2, ((index & 0X0400)? 0X1311: 0), index);
+}
+
+static int
+usbSetDataFormat_FTDI (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
+  int ok = 1;
+  unsigned int value = dataBits & 0XFF;
+  if (dataBits != value) {
+    logUnsupportedDataBits(dataBits);
+    ok = 0;
+  }
+  switch (parity) {
+    case SERIAL_PARITY_NONE:  value |= 0X000; break;
+    case SERIAL_PARITY_ODD:   value |= 0X100; break;
+    case SERIAL_PARITY_EVEN:  value |= 0X200; break;
+    case SERIAL_PARITY_MARK:  value |= 0X300; break;
+    case SERIAL_PARITY_SPACE: value |= 0X400; break;
+    default:
+      logUnsupportedParity(parity);
+      ok = 0;
+      break;
+  }
+  switch (stopBits) {
+    case SERIAL_STOP_1: value |= 0X0000; break;
+    case SERIAL_STOP_2: value |= 0X1000; break;
+    default:
+      logUnsupportedStopBits(stopBits);
+      ok = 0;
+      break;
+  }
+  if (!ok) {
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_FTDI(device, 4, value, 0);
+}
+
+static int
+usbSetModemState_FTDI (UsbDevice *device, int state, int shift, const char *name) {
+  if ((state < 0) || (state > 1)) {
+    logMessage(LOG_WARNING, "Unsupported FTDI %s state: %d", name, state);
+    errno = EINVAL;
+    return 0;
+  }
+  return usbSetAttribute_FTDI(device, 1, ((1 << (shift + 8)) | (state << shift)), 0);
+}
+
+static int
+usbSetDtrState_FTDI (UsbDevice *device, int state) {
+  return usbSetModemState_FTDI(device, state, 0, "DTR");
+}
+
+static int
+usbSetRtsState_FTDI (UsbDevice *device, int state) {
+  return usbSetModemState_FTDI(device, state, 1, "RTS");
+}
+
+const UsbSerialOperations usbSerialOperations_FTDI_SIO = {
+  .name = "FTDI_SIO",
+
+  .setBaud = usbSetBaud_FTDI_SIO,
+  .setDataFormat = usbSetDataFormat_FTDI,
+  .setFlowControl = usbSetFlowControl_FTDI,
+
+  .setDtrState = usbSetDtrState_FTDI,
+  .setRtsState = usbSetRtsState_FTDI
+};
+
+const UsbSerialOperations usbSerialOperations_FTDI_FT8U232AM = {
+  .name = "FTDI_FT8U232AM",
+
+  .setBaud = usbSetBaud_FTDI_FT8U232AM,
+  .setDataFormat = usbSetDataFormat_FTDI,
+  .setFlowControl = usbSetFlowControl_FTDI,
+
+  .setDtrState = usbSetDtrState_FTDI,
+  .setRtsState = usbSetRtsState_FTDI,
+
+  .inputFilter = usbInputFilter_FTDI
+};
+
+const UsbSerialOperations usbSerialOperations_FTDI_FT232BM = {
+  .name = "FTDI_FT232BM",
+
+  .setBaud = usbSetBaud_FTDI_FT232BM,
+  .setDataFormat = usbSetDataFormat_FTDI,
+  .setFlowControl = usbSetFlowControl_FTDI,
+
+  .setDtrState = usbSetDtrState_FTDI,
+  .setRtsState = usbSetRtsState_FTDI,
+
+  .inputFilter = usbInputFilter_FTDI
+};
diff --git a/Programs/usb_ftdi.h b/Programs/usb_ftdi.h
new file mode 100644
index 0000000..d12fec7
--- /dev/null
+++ b/Programs/usb_ftdi.h
@@ -0,0 +1,30 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_FTDI
+#define BRLTTY_INCLUDED_USB_FTDI
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_FTDI */
diff --git a/Programs/usb_grub.c b/Programs/usb_grub.c
new file mode 100644
index 0000000..d564938
--- /dev/null
+++ b/Programs/usb_grub.c
@@ -0,0 +1,178 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  return 1;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_hid.c b/Programs/usb_hid.c
new file mode 100644
index 0000000..1ad003e
--- /dev/null
+++ b/Programs/usb_hid.c
@@ -0,0 +1,157 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_hid.h"
+#include "bitfield.h"
+
+const UsbHidDescriptor *
+usbHidDescriptor (UsbDevice *device) {
+  const UsbDescriptor *descriptor = NULL;
+
+  while (usbNextDescriptor(device, &descriptor)) {
+    if (descriptor->header.bDescriptorType == UsbDescriptorType_HID) {
+      return &descriptor->hid;
+    }
+  }
+
+  logMessage(LOG_WARNING, "USB: HID descriptor not found");
+  errno = ENOENT;
+  return NULL;
+}
+
+HidItemsDescriptor *
+usbHidGetItems (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char number,
+  int timeout
+) {
+  const UsbHidDescriptor *hid = usbHidDescriptor(device);
+
+  if (hid) {
+    if (number < hid->bNumDescriptors) {
+      const UsbClassDescriptor *descriptor = &hid->descriptors[number];
+      uint16_t length = getLittleEndian16(descriptor->wDescriptorLength);
+
+      HidItemsDescriptor *items;
+      size_t size = sizeof(*items);
+      size += length;
+
+      if ((items = malloc(size))) {
+        ssize_t result = usbControlRead(
+          device, UsbControlRecipient_Interface, UsbControlType_Standard,
+          UsbStandardRequest_GetDescriptor,
+          (descriptor->bDescriptorType << 8) | interface,
+          number, items->bytes, length, timeout
+        );
+
+        if (result != -1) {
+          memset(items, 0, sizeof(*items));
+          items->count = result;
+          return items;
+        }
+
+        free(items);
+      } else {
+        logMallocError();
+      }
+    } else {
+      logMessage(LOG_WARNING,
+        "USB report descriptor not found: %u[%u]",
+        interface, number
+      );
+    }
+  }
+
+  return NULL;
+}
+
+ssize_t
+usbHidGetReport (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  unsigned char *buffer,
+  uint16_t size,
+  int timeout
+) {
+  return usbControlRead(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    UsbHidRequest_GetReport,
+    (UsbHidReportType_Input << 8) | identifier, interface,
+    buffer, size, timeout
+  );
+}
+
+ssize_t
+usbHidSetReport (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  const unsigned char *data,
+  uint16_t length,
+  int timeout
+) {
+  return usbControlWrite(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    UsbHidRequest_SetReport,
+    (UsbHidReportType_Output << 8) | identifier, interface,
+    data, length, timeout
+  );
+}
+
+ssize_t
+usbHidGetFeature (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  unsigned char *buffer,
+  uint16_t size,
+  int timeout
+) {
+  return usbControlRead(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    UsbHidRequest_GetReport,
+    (UsbHidReportType_Feature << 8) | identifier, interface,
+    buffer, size, timeout
+  );
+}
+
+ssize_t
+usbHidSetFeature (
+  UsbDevice *device,
+  unsigned char interface,
+  HidReportIdentifier identifier,
+  const unsigned char *data,
+  uint16_t length,
+  int timeout
+) {
+  return usbControlWrite(device,
+    UsbControlRecipient_Interface, UsbControlType_Class,
+    UsbHidRequest_SetReport,
+    (UsbHidReportType_Feature << 8) | identifier, interface,
+    data, length, timeout
+  );
+}
diff --git a/Programs/usb_internal.h b/Programs/usb_internal.h
new file mode 100644
index 0000000..3bd3521
--- /dev/null
+++ b/Programs/usb_internal.h
@@ -0,0 +1,175 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_INTERNAL
+#define BRLTTY_INCLUDED_USB_INTERNAL
+
+#include "usb_types.h"
+#include "queue.h"
+#include "async_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  UsbInputFilter *filter;
+} UsbInputFilterEntry;
+
+typedef struct UsbDeviceExtensionStruct UsbDeviceExtension;
+typedef struct UsbEndpointStruct UsbEndpoint;
+typedef struct UsbEndpointExtensionStruct UsbEndpointExtension;
+
+struct UsbEndpointStruct {
+  UsbDevice *device;
+  const UsbInterfaceDescriptor *interface;
+  const UsbEndpointDescriptor *descriptor;
+  UsbEndpointExtension *extension;
+  int (*prepare) (UsbEndpoint *endpoint);
+
+  union {
+    struct {
+      struct {
+        Queue *requests;
+        AsyncHandle alarm;
+        int delay;
+      } pending;
+
+      struct {
+        void *request;
+        unsigned char *buffer;
+        size_t length;
+      } completed;
+
+      struct {
+        FileDescriptor input;
+        FileDescriptor output;
+        AsyncHandle monitor;
+        int error;
+      } pipe;
+    } input;
+
+    struct {
+      char structMayNotBeEmpty;
+    } output;
+  } direction;
+};
+
+struct UsbDeviceStruct {
+  UsbDeviceDescriptor descriptor;
+  UsbDeviceExtension *extension;
+
+  struct {
+    const UsbSerialOperations *operations;
+    UsbSerialData *data;
+  } serial;
+
+  UsbConfigurationDescriptor *configuration;
+  const UsbInterfaceDescriptor *interface;
+  Queue *endpoints;
+  Queue *inputFilters;
+
+  uint16_t language;
+  unsigned char resetDevice:1;
+  unsigned char disableEndpointReset:1;
+
+  struct {
+    const UsbInterfaceDescriptor *endpointInterfaceDescriptor;
+  } scratch;
+};
+
+extern UsbDevice *usbTestDevice (
+  UsbDeviceExtension *extension,
+  UsbDeviceChooser *chooser,
+  UsbChooseChannelData *data
+);
+extern UsbEndpoint *usbGetEndpoint (UsbDevice *device, unsigned char endpointAddress);
+extern UsbEndpoint *usbGetInputEndpoint (UsbDevice *device, unsigned char endpointNumber);
+extern UsbEndpoint *usbGetOutputEndpoint (UsbDevice *device, unsigned char endpointNumber);
+extern int usbApplyInputFilters (UsbEndpoint *endpoint, void *buffer, size_t size, ssize_t *length);
+
+extern void usbLogInputProblem (UsbEndpoint *endpoint, const char *problem);
+extern int usbHandleInputResponse (UsbEndpoint *endpoint, const void *buffer, size_t length);
+
+extern int usbSetSerialOperations (UsbDevice *device);
+
+extern int usbSetConfiguration (UsbDevice *device, unsigned char configuration);
+
+extern int usbClaimInterface (UsbDevice *device, unsigned char interface);
+
+extern int usbReleaseInterface (UsbDevice *device, unsigned char interface);
+
+extern int usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+);
+
+extern int usbMakeInputPipe (UsbEndpoint *endpoint);
+extern void usbDestroyInputPipe (UsbEndpoint *endpoint);
+extern int usbEnqueueInput (UsbEndpoint *endpoint, const void *buffer, size_t length);
+
+extern void usbSetEndpointInputError (UsbEndpoint *endpoint, int error);
+extern void usbSetDeviceInputError (UsbDevice *device, int error);
+
+extern int usbMonitorInputPipe (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+);
+
+extern ssize_t usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+);
+
+extern int usbReadDeviceDescriptor (UsbDevice *device);
+extern int usbAllocateEndpointExtension (UsbEndpoint *endpoint);
+extern void usbDeallocateEndpointExtension (UsbEndpointExtension *eptx);
+extern void usbDeallocateDeviceExtension (UsbDeviceExtension *devx);
+
+extern void usbLogSetupPacket (const UsbSetupPacket *setup);
+
+extern void usbMakeSetupPacket (
+  UsbSetupPacket *setup,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  uint16_t length
+);
+
+extern void usbLogEndpointData (
+  UsbEndpoint *endpoint, const char *label,
+  const void *data, size_t size
+);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_INTERNAL */
diff --git a/Programs/usb_kfreebsd.c b/Programs/usb_kfreebsd.c
new file mode 100644
index 0000000..7063eae
--- /dev/null
+++ b/Programs/usb_kfreebsd.c
@@ -0,0 +1,39 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+
+#ifdef HAVE_LEGACY_DEV_USB_USB_H
+#include <legacy/dev/usb/usb.h>
+#else /* HAVE_LEGACY_DEV_USB_USB_H */
+#include <dev/usb/usb.h>
+#endif /* HAVE_LEGACY_DEV_USB_USB_H */
+
+#include "io_usb.h"
+#include "usb_internal.h"
+
+#define USB_CONTROL_PATH_FORMAT "/dev/%s"
+#define USB_ENDPOINT_PATH_FORMAT "%.*s.%d"
+#include "usb_bsd.h"
diff --git a/Programs/usb_libusb-1.0.c b/Programs/usb_libusb-1.0.c
new file mode 100644
index 0000000..f0b8ce9
--- /dev/null
+++ b/Programs/usb_libusb-1.0.c
@@ -0,0 +1,497 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+#include <libusb.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+#include "bitfield.h"
+
+struct UsbDeviceExtensionStruct {
+  libusb_device *device;
+  libusb_device_handle *handle;
+};
+
+static libusb_context *usbContext = NULL;
+static libusb_device **usbDeviceList = NULL;
+static int usbDeviceCount = 0;
+
+static int
+usbToErrno (enum libusb_error error) {
+  switch (error) {
+    case LIBUSB_ERROR_IO:
+      return EIO;
+
+    case LIBUSB_ERROR_INVALID_PARAM:
+      return EINVAL;
+
+    case LIBUSB_ERROR_ACCESS:
+      return EACCES;
+
+    case LIBUSB_ERROR_NO_DEVICE:
+      return ENODEV;
+
+    case LIBUSB_ERROR_NOT_FOUND:
+      return ENOENT;
+
+    case LIBUSB_ERROR_BUSY:
+      return EBUSY;
+
+    case LIBUSB_ERROR_TIMEOUT:
+      return EAGAIN;
+
+#ifdef EMSGSIZE
+    case LIBUSB_ERROR_OVERFLOW:
+      return EMSGSIZE;
+#endif /* EMSGSIZE */
+
+    case LIBUSB_ERROR_PIPE:
+      return EPIPE;
+
+    case LIBUSB_ERROR_INTERRUPTED:
+      return EINTR;
+
+    case LIBUSB_ERROR_NO_MEM:
+      return ENOMEM;
+
+    case LIBUSB_ERROR_NOT_SUPPORTED:
+      return ENOSYS;
+
+    default:
+      logMessage(LOG_CATEGORY(USB_IO), "unsupported libusb1 error code: %d", error);
+    case LIBUSB_ERROR_OTHER:
+      return EIO;
+  }
+}
+
+static void
+usbSetErrno (enum libusb_error error, const char *action) {
+  errno = usbToErrno(error);
+  if (action) logSystemError(action);
+}
+
+static int
+usbGetHandle (UsbDeviceExtension *devx) {
+  if (!devx->handle) {
+    int result;
+
+    if ((result = libusb_open(devx->device, &devx->handle)) != LIBUSB_SUCCESS) {
+      usbSetErrno(result, "libusb_open");
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    int result;
+
+    logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration);
+    result = libusb_set_configuration(devx->handle, configuration);
+    if (result == LIBUSB_SUCCESS) return 1;
+    usbSetErrno(result, "libusb_set_configuration");
+  }
+
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    int detached = 0;
+    int result;
+
+    logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface);
+
+    while (1) {
+      result = libusb_claim_interface(devx->handle, interface);
+      if (result == LIBUSB_SUCCESS) return 1;
+
+      if (result != LIBUSB_ERROR_BUSY)  break;
+      if (detached) break;
+
+      logMessage(LOG_WARNING, "USB interface in use: %u", interface);
+      result = libusb_detach_kernel_driver(devx->handle, interface);
+
+      if (result == LIBUSB_SUCCESS) {
+        logMessage(LOG_WARNING, "USB interface detached: %u", interface);
+        detached = 1;
+        continue;
+      }
+
+      result = LIBUSB_ERROR_BUSY;
+      break;
+    }
+
+    usbSetErrno(result, "libusb_claim_interface");
+  }
+
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    int result;
+
+    logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface);
+    result = libusb_release_interface(devx->handle, interface);
+    if (result == LIBUSB_SUCCESS) return 1;
+    usbSetErrno(result, "libusb_release_interface");
+  }
+
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    int result;
+
+    logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative);
+    result = libusb_set_interface_alt_setting(devx->handle, interface, alternative);
+    if (result == LIBUSB_SUCCESS) return 1;
+    usbSetErrno(result, "libusb_set_interface_alt_setting");
+  }
+
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    logMessage(LOG_CATEGORY(USB_IO), "reset device");
+    int result = libusb_reset_device(devx->handle);
+    if (result == LIBUSB_SUCCESS) return 1;
+    usbSetErrno(result, "libusb_reset_device");
+  }
+
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress);
+    int result = libusb_clear_halt(devx->handle, endpointAddress);
+    if (result == LIBUSB_SUCCESS) return 1;
+    usbSetErrno(result, "libusb_clear_halt");
+  }
+
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    UsbSetupPacket setup;
+    int result;
+
+    usbMakeSetupPacket(&setup, direction, recipient, type, request, value, index, length);
+
+    if (direction == UsbControlDirection_Output) {
+      if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length);
+    }
+
+    result = libusb_control_transfer(devx->handle,
+                                     setup.bRequestType, setup.bRequest,
+                                     getLittleEndian16(setup.wValue),
+                                     getLittleEndian16(setup.wIndex), buffer,
+                                     getLittleEndian16(setup.wLength), timeout);
+
+    if (result >= 0) {
+      if (direction == UsbControlDirection_Input) {
+        logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, result);
+      }
+
+      return result;
+    }
+
+    usbSetErrno(result, "");
+  }
+
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    UsbEndpoint *endpoint;
+
+    if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+      const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+      UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor);
+      int actual_length;
+      int result;
+
+      switch (transfer) {
+        case UsbEndpointTransfer_Bulk:
+          result = libusb_bulk_transfer(devx->handle, descriptor->bEndpointAddress,
+                                        buffer, length, &actual_length, timeout);
+          break;
+
+        case UsbEndpointTransfer_Interrupt:
+          result = libusb_interrupt_transfer(devx->handle, descriptor->bEndpointAddress,
+                                             buffer, length, &actual_length, timeout);
+          break;
+
+        default:
+          logMessage(LOG_ERR, "USB endpoint input transfer not supported: 0X%02X", transfer);
+          result = LIBUSB_ERROR_NOT_SUPPORTED;
+          break;
+      }
+
+      if (result == LIBUSB_SUCCESS) {
+        ssize_t count = actual_length;
+
+        if (usbApplyInputFilters(endpoint, buffer, length, &count)) return count;
+        result = LIBUSB_ERROR_IO;
+      }
+
+      usbSetErrno(result, NULL);
+    }
+  }
+
+  if (errno != EAGAIN) logSystemError("USB endpoint read");
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbGetHandle(devx)) {
+    UsbEndpoint *endpoint;
+
+    if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) {
+      const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+      UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor);
+      int actual_length;
+      int result;
+
+      switch (transfer) {
+        case UsbEndpointTransfer_Bulk:
+          result = libusb_bulk_transfer(devx->handle, descriptor->bEndpointAddress,
+                                        (void *)buffer, length, &actual_length, timeout);
+          break;
+
+        case UsbEndpointTransfer_Interrupt:
+          result = libusb_interrupt_transfer(devx->handle, descriptor->bEndpointAddress,
+                                             (void *)buffer, length, &actual_length, timeout);
+          break;
+
+        default:
+          logMessage(LOG_ERR, "USB endpoint output transfer not supported: 0X%02X", transfer);
+          result = LIBUSB_ERROR_NOT_SUPPORTED;
+          break;
+      }
+
+      if (result == LIBUSB_SUCCESS) return actual_length;
+      usbSetErrno(result, NULL);
+    }
+  }
+
+  logSystemError("USB endpoint write");
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+  struct libusb_device_descriptor descriptor;
+  int result;
+
+  if ((result = libusb_get_device_descriptor(devx->device, &descriptor)) == LIBUSB_SUCCESS) {
+    memcpy(&device->descriptor, &descriptor, UsbDescriptorSize_Device);
+    return 1;
+  } else {
+    usbSetErrno(result, "libusb_get_device_descriptor");
+  }
+
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  return 1;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  if (devx->handle) libusb_close(devx->handle);
+  libusb_unref_device(devx->device);
+  free(devx);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  int result;
+  UsbDeviceExtension *devx;
+
+  if (!usbContext) {
+    if ((result = libusb_init(&usbContext)) != LIBUSB_SUCCESS) {
+      usbSetErrno(result, "libusb_init");
+      return NULL;
+    }
+  }
+
+  if (!usbDeviceList) {
+    ssize_t count;
+
+    if ((count = libusb_get_device_list(usbContext, &usbDeviceList)) < 0) {
+      usbSetErrno(count, "libusb_get_device_list");
+      return NULL;
+    }
+
+    usbDeviceCount = count;
+  }
+
+  if ((devx = malloc(sizeof(*devx)))) {
+    libusb_device **libusbDevice = usbDeviceList;
+    int deviceCount = usbDeviceCount;
+
+    while (deviceCount) {
+      deviceCount -= 1;
+      devx->device = *libusbDevice++;
+      libusb_ref_device(devx->device);
+
+      devx->handle = NULL;
+
+      {
+        UsbDevice *device = usbTestDevice(devx, chooser, data);
+        if (device) return device;
+      }
+
+      libusb_unref_device(devx->device);
+    }
+
+    free(devx);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+  if (usbDeviceList) {
+    libusb_free_device_list(usbDeviceList, 1);
+    usbDeviceList = NULL;
+  }
+
+  usbDeviceCount = 0;
+}
diff --git a/Programs/usb_libusb.c b/Programs/usb_libusb.c
new file mode 100644
index 0000000..daa4d33
--- /dev/null
+++ b/Programs/usb_libusb.c
@@ -0,0 +1,442 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <usb.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+#include "bitfield.h"
+
+#ifdef __MINGW32__
+#ifndef ETIMEDOUT
+#define ETIMEDOUT 116
+#endif /* ETIMEDOUT */
+#endif /* __MINGW32__ */
+
+struct UsbDeviceExtensionStruct {
+  struct usb_dev_handle *handle;
+};
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+  int result;
+
+  logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration);
+  result = usb_set_configuration(devx->handle, configuration);
+  if (result >= 0) return 1;
+
+  errno = -result;
+  logSystemError("USB configuration set");
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+  int detached = 0;
+  int result;
+
+  logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface);
+
+  while (1) {
+    char driver[0X100];
+
+    result = usb_claim_interface(devx->handle, interface);
+    if (result >= 0) return 1;
+
+    if (result != -EBUSY) break;
+    if (detached) break;
+
+#ifdef LIBUSB_HAS_GET_DRIVER_NP
+    result = usb_get_driver_np(devx->handle, interface, driver, sizeof(driver));
+
+    if (result < 0)
+#endif /* LIBUSB_HAS_GET_DRIVER_NP */
+
+    {
+      strcpy(driver, "unknown");
+    }
+
+    logMessage(LOG_WARNING, "USB interface in use: %u (%s)", interface, driver);
+
+    if (strcmp(driver, "usbfs") == 0) {
+      result = -EBUSY;
+      break;
+    }
+
+#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
+    logMessage(LOG_CATEGORY(USB_IO), "detaching kernel driver: %u (%s)",
+               interface, driver);
+
+    result = usb_detach_kernel_driver_np(devx->handle, interface);
+
+    if (result >= 0) {
+      logMessage(LOG_CATEGORY(USB_IO), "detached kernel driver: %u (%s)",
+                 interface, driver);
+
+      detached = 1;
+      continue;
+    }
+
+    result = -EBUSY;
+#endif /* LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP */
+
+    break;
+  }
+
+  errno = -result;
+  logSystemError("USB interface claim");
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+  int result;
+
+  logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface);
+  result = usb_release_interface(devx->handle, interface);
+  if (result >= 0) return 1;
+
+  errno = -result;
+  logSystemError("USB interface release");
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+  int result;
+
+  logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative);
+  result = usb_set_altinterface(devx->handle, alternative);
+  if (result >= 0) return 1;
+
+  errno = -result;
+  logSystemError("USB alternative set");
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logMessage(LOG_CATEGORY(USB_IO), "reset device");
+
+  UsbDeviceExtension *devx = device->extension;
+  int result = usb_reset(devx->handle);
+  if (result >= 0) return 1;
+
+  errno = -result;
+  logSystemError("USB device reset");
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress);
+
+  UsbDeviceExtension *devx = device->extension;
+  int result = usb_clear_halt(devx->handle, endpointAddress);
+  if (result >= 0) return 1;
+
+  errno = -result;
+  logSystemError("USB endpoint clear");
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbSetupPacket setup;
+  int result;
+
+  usbMakeSetupPacket(&setup, direction, recipient, type, request, value, index, length);
+
+  if (direction == UsbControlDirection_Output) {
+    if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length);
+  }
+
+  result = usb_control_msg(devx->handle, setup.bRequestType, setup.bRequest,
+                           getLittleEndian16(setup.wValue),
+                           getLittleEndian16(setup.wIndex), buffer,
+                           getLittleEndian16(setup.wLength), timeout);
+
+  if (result >= 0) {
+    if (direction == UsbControlDirection_Input) {
+      logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, result);
+    }
+
+    return result;
+  }
+
+  errno = -result;
+  logSystemError("USB control transfer");
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *data
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+    const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+    UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor);
+    ssize_t result = -1;
+
+    switch (transfer) {
+      case UsbEndpointTransfer_Bulk:
+        result = usb_bulk_read(devx->handle, descriptor->bEndpointAddress,
+                               buffer, length, timeout);
+        break;
+
+      case UsbEndpointTransfer_Interrupt:
+        result = usb_interrupt_read(devx->handle, descriptor->bEndpointAddress,
+                                    buffer, length, timeout);
+        break;
+
+      default:
+        logMessage(LOG_ERR, "USB endpoint input transfer not supported: 0X%02X", transfer);
+        result = -ENOSYS;
+        break;
+    }
+
+    if (result >= 0) {
+      if (!usbApplyInputFilters(endpoint, buffer, length, &result)) {
+        result = -EIO;
+      }
+    }
+
+    if (result >= 0) return result;
+    errno = -result;
+  }
+
+#ifdef ETIMEDOUT
+  if (errno == ETIMEDOUT) errno = EAGAIN;
+#endif /* ETIMEDOUT */
+
+  if (errno != EAGAIN) logSystemError("USB endpoint read");
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) {
+    const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+    UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor);
+    int result = -1;
+
+    usbLogEndpointData(endpoint, "output", buffer, length);
+
+    switch (transfer) {
+      case UsbEndpointTransfer_Bulk:
+        result = usb_bulk_write(devx->handle, descriptor->bEndpointAddress,
+                                (char *)buffer, length, timeout);
+        break;
+
+      case UsbEndpointTransfer_Interrupt:
+        result = usb_interrupt_write(devx->handle, descriptor->bEndpointAddress,
+                                     (char *)buffer, length, timeout);
+        break;
+
+      default:
+        logMessage(LOG_ERR, "USB endpoint output transfer not supported: 0X%02X", transfer);
+        result = -ENOSYS;
+        break;
+    }
+
+    if (result >= 0) return result;
+    errno = -result;
+  }
+
+  logSystemError("USB endpoint write");
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+  memcpy(&device->descriptor, &usb_device(devx->handle)->descriptor, UsbDescriptorSize_Device);
+  return 1;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  return 1;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  if (devx->handle) {
+    usb_close(devx->handle);
+    devx->handle = NULL;
+  }
+
+  free(devx);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  UsbDevice *device = NULL;
+  int result;
+
+  {
+    static int initialized = 0;
+    if (!initialized) {
+      usb_init();
+      initialized = 1;
+    }
+  }
+
+  if ((result = usb_find_busses()) >= 0) {
+    if ((result = usb_find_devices()) >= 0) {
+      struct usb_bus *bus = usb_get_busses();
+
+      if (bus) {
+        struct usb_bus *bus0 = bus;
+
+        do {
+          struct usb_device *dev = bus->devices;
+
+          if (dev) {
+            struct usb_device *dev0 = dev;
+
+            do {
+              UsbDeviceExtension *devx;
+
+              if ((devx = malloc(sizeof(*devx)))) {
+                if ((devx->handle = usb_open(dev))) {
+                  if ((device = usbTestDevice(devx, chooser, data))) return device;
+
+                  usb_close(devx->handle);
+                } else {
+                  logMessage(LOG_ERR, "USB open error: vendor=%X product=%X",
+                             getLittleEndian16(dev->descriptor.idVendor),
+                             getLittleEndian16(dev->descriptor.idProduct));
+                }
+
+                free(devx);
+              } else {
+                logSystemError("USB device extension allocate");
+              }
+
+              if ((dev = dev->next) == dev0) dev = NULL;
+            } while (dev);
+          }
+
+          if ((bus = bus->next) == bus0) bus = NULL;
+        } while (bus);
+      }
+    } else {
+      errno = -result;
+      logSystemError("USB devices find");
+    }
+  } else {
+    errno = -result;
+    logSystemError("USB busses find");
+  }
+
+  return device;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_linux.c b/Programs/usb_linux.c
new file mode 100644
index 0000000..34a7178
--- /dev/null
+++ b/Programs/usb_linux.c
@@ -0,0 +1,1571 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+#ifndef USBDEVFS_DISCONNECT
+#define USBDEVFS_DISCONNECT _IO('U', 22)
+#endif /* USBDEVFS_DISCONNECT */
+
+#ifndef USBDEVFS_CONNECT
+#define USBDEVFS_CONNECT _IO('U', 23)
+#endif /* USBDEVFS_CONNECT */
+
+#include "log.h"
+#include "parameters.h"
+#include "bitfield.h"
+#include "strfmt.h"
+#include "file.h"
+#include "parse.h"
+#include "timing.h"
+#include "async_handle.h"
+#include "async_wait.h"
+#include "async_alarm.h"
+#include "async_io.h"
+#include "async_signal.h"
+#include "mntpt.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+
+typedef struct {
+  char *sysfsPath;
+  char *usbfsPath;
+  UsbDeviceDescriptor usbDescriptor;
+} UsbHostDevice;
+
+static Queue *usbHostDevices = NULL;
+
+struct UsbDeviceExtensionStruct {
+  const UsbHostDevice *host;
+  int usbfsFile;
+  AsyncHandle usbfsMonitorHandle;
+};
+
+struct UsbEndpointExtensionStruct {
+  Queue *completedRequests;
+
+  struct {
+    struct {
+      AsyncHandle handle;
+      int number;
+    } signal;
+  } monitor;
+};
+
+static int
+usbOpenUsbfsFile (UsbDeviceExtension *devx) {
+  if (devx->usbfsFile == -1) {
+    int openFlags = O_RDWR;
+
+    #ifdef O_CLOEXEC
+    openFlags |= O_CLOEXEC;
+    #endif /* O_CLOEXEC */
+
+    if ((devx->usbfsFile = open(devx->host->usbfsPath, openFlags)) == -1) {
+      logMessage(LOG_ERR, "USBFS open error: %s: %s",
+                 devx->host->usbfsPath, strerror(errno));
+      return 0;
+    }
+
+    logMessage(LOG_CATEGORY(USB_IO), "usbfs file opened: %s fd=%d",
+               devx->host->usbfsPath, devx->usbfsFile);
+  }
+
+  return 1;
+}
+
+static void
+usbCloseUsbfsFile (UsbDeviceExtension *devx) {
+  if (devx->usbfsFile != -1) {
+    close(devx->usbfsFile);
+    devx->usbfsFile = -1;
+  }
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+  int ok = 0;
+
+  if (devx->host->sysfsPath) {
+    char *path = makePath(devx->host->sysfsPath, "power/autosuspend");
+
+    if (path) {
+      int openFlags = O_WRONLY;
+
+      #ifdef O_CLOEXEC
+      openFlags |= O_CLOEXEC;
+      #endif /* O_CLOEXEC */
+
+      int file = open(path, openFlags);
+
+      if (file != -1) {
+        static const char *const values[] = {"-1", "0", NULL};
+        const char *const *value = values;
+
+        while (*value) {
+          size_t length = strlen(*value);
+          ssize_t result = write(file, *value, length);
+
+          if (result != -1) {
+            ok = 1;
+            break;
+          }
+
+          if (errno != EINVAL) {
+            logMessage(LOG_ERR, "write error: %s: %s", path, strerror(errno));
+            break;
+          }
+
+          ++value;
+        }
+
+        close(file);
+      } else {
+        logMessage((errno == ENOENT)? LOG_CATEGORY(USB_IO): LOG_ERR,
+                   "open error: %s: %s", path, strerror(errno));
+      }
+
+      free(path);
+    }
+  }
+
+  return ok;
+}
+
+static char *
+usbGetDriver (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    struct usbdevfs_getdriver arg;
+
+    memset(&arg, 0, sizeof(arg));
+    arg.interface = interface;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_GETDRIVER, &arg) != -1) {
+      char *name = strdup(arg.driver);
+      if (name) return name;
+      logMallocError();
+    } else {
+      logSystemError("USB get driver name");
+    }
+  }
+
+  return NULL;
+}
+
+static int
+usbControlDriver (
+  UsbDevice *device,
+  unsigned char interface,
+  int code,
+  void *data
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    struct usbdevfs_ioctl arg;
+
+    memset(&arg, 0, sizeof(arg));
+    arg.ifno = interface;
+    arg.ioctl_code = code;
+    arg.data = data;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_IOCTL, &arg) != -1) return 1;
+    logSystemError("USB driver control");
+  }
+
+  return 0;
+}
+
+static int
+usbDisconnectDriver (UsbDevice *device, unsigned char interface) {
+#ifdef USBDEVFS_DISCONNECT
+  logMessage(LOG_CATEGORY(USB_IO), "disconnecting kernel driver: Int:%u", interface);
+  if (usbControlDriver(device, interface, USBDEVFS_DISCONNECT, NULL)) return 1;
+#else /* USBDEVFS_DISCONNECT */
+  errno = ENOSYS;
+#endif /* USBDEVFS_DISCONNECT */
+
+  logSystemError("USAB driver disconnect");
+  return 0;
+}
+
+static int
+usbDisconnectInterface (UsbDevice *device, unsigned char interface) {
+  char *driver = usbGetDriver(device, interface);
+
+  if (driver) {
+    int isUsbfs = strcmp(driver, "usbfs") == 0;
+
+    logMessage(LOG_WARNING, "USB interface in use: %u (%s)", interface, driver);
+    free(driver);
+
+    if (isUsbfs) {
+      logPossibleCause("another " PACKAGE_TARNAME " process may be accessing the same device");
+      logPossibleCause("the device may be attached to a virtual machine running on this host");
+      errno = EBUSY;
+    } else if (usbDisconnectDriver(device, interface)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration);
+
+  if (usbOpenUsbfsFile(devx)) {
+    unsigned int arg = configuration;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_SETCONFIGURATION, &arg) != -1) return 1;
+    logSystemError("USB configuration set");
+  }
+
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface);
+
+  if (usbOpenUsbfsFile(devx)) {
+    int disconnected = 0;
+
+    while (1) {
+      unsigned int arg = interface;
+
+      if (ioctl(devx->usbfsFile, USBDEVFS_CLAIMINTERFACE, &arg) != -1) return 1;
+      if (errno != EBUSY) break;
+      if (disconnected) break;
+
+      if (!usbDisconnectInterface(device, interface)) {
+        errno = EBUSY;
+        break;
+      }
+      disconnected = 1;
+    }
+
+    logSystemError("USB interface claim");
+  }
+
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface);
+
+  if (usbOpenUsbfsFile(devx)) {
+    unsigned int arg = interface;
+    if (ioctl(devx->usbfsFile, USBDEVFS_RELEASEINTERFACE, &arg) != -1) return 1;
+    if (errno == ENODEV) return 1;
+    logSystemError("USB interface release");
+  }
+
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative);
+
+  if (usbOpenUsbfsFile(devx)) {
+    struct usbdevfs_setinterface arg;
+
+    memset(&arg, 0, sizeof(arg));
+    arg.interface = interface;
+    arg.altsetting = alternative;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_SETINTERFACE, &arg) != -1) return 1;
+    logSystemError("USB alternative set");
+  }
+
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "reset device");
+
+  if (usbOpenUsbfsFile(devx)) {
+    unsigned int arg = 0;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_RESET, &arg) != -1) return 1;
+    logSystemError("USB device reset");
+  }
+
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  UsbDeviceExtension *devx = device->extension;
+
+  logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress);
+
+  if (usbOpenUsbfsFile(devx)) {
+    unsigned int arg = endpointAddress;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_CLEAR_HALT, &arg) != -1) return 1;
+    logSystemError("USB endpoint clear");
+  }
+
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    UsbSetupPacket setup;
+    struct usbdevfs_ctrltransfer arg;
+
+    usbMakeSetupPacket(&setup, direction, recipient, type,
+                       request, value, index, length);
+
+    memset(&arg, 0, sizeof(arg));
+    arg.bRequestType = setup.bRequestType;
+    arg.bRequest = setup.bRequest;
+    arg.wValue = getLittleEndian16(setup.wValue);
+    arg.wIndex = getLittleEndian16(setup.wIndex);
+    arg.wLength = getLittleEndian16(setup.wLength);
+    arg.data = buffer;
+    arg.timeout = timeout;
+
+    if (direction == UsbControlDirection_Output) {
+      if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length);
+    }
+
+    {
+      ssize_t count = ioctl(devx->usbfsFile, USBDEVFS_CONTROL, &arg);
+
+      if (count != -1) {
+        if (direction == UsbControlDirection_Input) {
+          logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, count);
+        }
+
+        return count;
+      }
+
+      logSystemError("USB control transfer");
+    }
+  }
+
+  return -1;
+}
+
+static UsbEndpoint *
+usbReapURB (UsbDevice *device, int wait) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    struct usbdevfs_urb *urb;
+
+    if (ioctl(devx->usbfsFile,
+              wait? USBDEVFS_REAPURB: USBDEVFS_REAPURBNDELAY,
+              &urb) != -1) {
+      if (urb) {
+        UsbEndpoint *endpoint;
+
+        if ((endpoint = usbGetEndpoint(device, urb->endpoint))) {
+          UsbEndpointExtension *eptx = endpoint->extension;
+
+          if (enqueueItem(eptx->completedRequests, urb)) return endpoint;
+          logSystemError("USB completed request enqueue");
+          free(urb);
+        }
+      } else {
+        errno = EAGAIN;
+      }
+    } else {
+      if (wait || (errno != EAGAIN)) logSystemError("USB URB reap");
+    }
+  }
+
+  return NULL;
+}
+
+typedef struct {
+  const struct usbdevfs_urb *urb;
+  const char *action;
+} UsbFormatUrbData;
+
+static size_t
+usbFormatURB (char *buffer, size_t size, const void *data) {
+  const UsbFormatUrbData *fud = data;
+  const struct usbdevfs_urb *urb = fud->urb;
+  size_t length;
+
+  STR_BEGIN(buffer, size);
+  STR_PRINTF("%s URB:", fud->action);
+
+  STR_PRINTF(" Adr:%p", urb);
+  STR_PRINTF(" Ept:%02X", urb->endpoint);
+
+  STR_PRINTF(" Typ:%u", urb->type);
+  {
+    static const char *const types[] = {
+      [USBDEVFS_URB_TYPE_CONTROL] = "ctl",
+      [USBDEVFS_URB_TYPE_BULK] = "blk",
+      [USBDEVFS_URB_TYPE_INTERRUPT] = "int",
+      [USBDEVFS_URB_TYPE_ISO] = "iso"
+    };
+
+    if (urb->type < ARRAY_COUNT(types)) {
+      const char *type = types[urb->type];
+      if (type) STR_PRINTF("(%s)", type);
+    }
+  }
+
+  STR_PRINTF(" Flg:%02X", urb->flags);
+  {
+    typedef struct {
+      unsigned char bit;
+      const char *name;
+    } UrbFlagEntry;
+
+    static const UrbFlagEntry urbFlagTable[] = {
+#ifdef USBDEVFS_URB_SHORT_NOT_OK
+      { .bit = USBDEVFS_URB_SHORT_NOT_OK,
+        .name = "spd"
+      },
+#endif /* USBDEVFS_URB_SHORT_NOT_OK */
+
+#ifdef USBDEVFS_URB_ISO_ASAP
+      { .bit = USBDEVFS_URB_ISO_ASAP,
+        .name = "isa"
+      },
+#endif /* USBDEVFS_URB_ISO_ASAP */
+
+#ifdef USBDEVFS_URB_BULK_CONTINUATION
+      { .bit = USBDEVFS_URB_BULK_CONTINUATION,
+        .name = "bkc"
+      },
+#endif /* USBDEVFS_URB_BULK_CONTINUATION */
+
+#ifdef USBDEVFS_URB_NO_FSBR
+      { .bit = USBDEVFS_URB_NO_FSBR,
+        .name = "nof"
+      },
+#endif /* USBDEVFS_URB_NO_FSBR */
+
+#ifdef USBDEVFS_URB_ZERO_PACKET
+      { .bit = USBDEVFS_URB_ZERO_PACKET,
+        .name = "zpk"
+      },
+#endif /* USBDEVFS_URB_ZERO_PACKET */
+
+#ifdef USBDEVFS_URB_NO_INTERRUPT
+      { .bit = USBDEVFS_URB_NO_INTERRUPT,
+        .name = "noi"
+      },
+#endif /* USBDEVFS_URB_NO_INTERRUPT */
+
+      { .bit=0, .name=NULL }
+    };
+
+    int first = 1;
+    const UrbFlagEntry *flag = urbFlagTable;
+
+    while (flag->bit) {
+      if (urb->flags & flag->bit) {
+        STR_PRINTF("%c%s", (first? '(': ','), flag->name);
+        first = 0;
+      }
+
+      flag += 1;
+    }
+
+    if (!first) STR_PRINTF(")");
+  }
+
+  STR_PRINTF(" Buf:%p", urb->buffer);
+  STR_PRINTF(" Siz:%d", urb->buffer_length);
+  STR_PRINTF(" Len:%d", urb->actual_length);
+  STR_PRINTF(" Sig:%d", urb->signr);
+
+  {
+    int error = urb->status;
+    STR_PRINTF(" Err:%d", error);
+
+    if (error) {
+      if (error < 0) error = -error;
+      STR_PRINTF("(%s)", strerror(error));
+    }
+  }
+
+  length = STR_LENGTH;
+  STR_END;
+  return length;
+}
+
+static void
+usbLogURB (const struct usbdevfs_urb *urb, const char *action) {
+  const UsbFormatUrbData fud = {
+    .urb = urb,
+    .action = action
+  };
+
+  logData(LOG_CATEGORY(USB_IO), usbFormatURB, &fud);
+}
+
+static struct usbdevfs_urb *
+usbMakeURB (
+  const UsbEndpointDescriptor *endpoint,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  struct usbdevfs_urb *urb;
+
+  if ((urb = malloc(sizeof(*urb) + length))) {
+    memset(urb, 0, sizeof(*urb));
+    urb->endpoint = endpoint->bEndpointAddress;
+    urb->flags = 0;
+    urb->signr = 0;
+    urb->usercontext = context;
+
+    if (!(urb->buffer_length = length)) {
+      urb->buffer = NULL;
+    } else {
+      urb->buffer = urb + 1;
+      if (buffer) memcpy(urb->buffer, buffer, length);
+    }
+
+    switch (USB_ENDPOINT_TRANSFER(endpoint)) {
+      case UsbEndpointTransfer_Control:
+        urb->type = USBDEVFS_URB_TYPE_CONTROL;
+        break;
+
+      case UsbEndpointTransfer_Isochronous:
+        urb->type = USBDEVFS_URB_TYPE_ISO;
+        break;
+
+      case UsbEndpointTransfer_Interrupt:
+        urb->type = USBDEVFS_URB_TYPE_INTERRUPT;
+        break;
+
+      case UsbEndpointTransfer_Bulk:
+        urb->type = USBDEVFS_URB_TYPE_BULK;
+        break;
+    }
+
+    return urb;
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static int
+usbSubmitURB (struct usbdevfs_urb *urb, UsbEndpoint *endpoint) {
+  const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+  UsbDevice *device = endpoint->device;
+  UsbDeviceExtension *devx = device->extension;
+
+  while (1) {
+    usbLogURB(urb, "submitting");
+
+    if ((urb->endpoint & UsbEndpointDirection_Mask) == UsbEndpointDirection_Output) {
+      logBytes(LOG_CATEGORY(USB_IO), "URB output", urb->buffer, urb->buffer_length);
+    }
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_SUBMITURB, urb) != -1) {
+      logMessage(LOG_CATEGORY(USB_IO), "URB submitted");
+      return 1;
+    }
+
+    if ((errno == EINVAL) &&
+        (USB_ENDPOINT_TRANSFER(descriptor) == UsbEndpointTransfer_Interrupt) &&
+        (urb->type == USBDEVFS_URB_TYPE_BULK)) {
+      logMessage(LOG_CATEGORY(USB_IO), "changing URB type from bulk to interrupt");
+      urb->type = USBDEVFS_URB_TYPE_INTERRUPT;
+      continue;
+    }
+
+    /* UHCI support returns ENXIO if a URB is already submitted. */
+    logSystemError("USB URB submit");
+    return 0;
+  }
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    UsbEndpoint *endpoint;
+
+    if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+      UsbEndpointExtension *eptx = endpoint->extension;
+      struct usbdevfs_urb *urb;
+
+      if ((urb = usbMakeURB(endpoint->descriptor, buffer, length, context))) {
+        urb->actual_length = 0;
+        urb->signr = eptx->monitor.signal.number;
+
+        if (usbSubmitURB(urb, endpoint)) {
+          return urb;
+        }
+
+        free(urb);
+      } else {
+        logSystemError("USB URB allocate");
+      }
+    }
+  }
+
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    int reap = 1;
+
+    if (ioctl(devx->usbfsFile, USBDEVFS_DISCARDURB, request) == -1) {
+      if (errno == ENODEV) {
+        reap = 0;
+      } else if (errno != EINVAL) {
+        logSystemError("USB URB discard");
+      }
+    }
+    
+    {
+      struct usbdevfs_urb *urb = request;
+      UsbEndpoint *endpoint;
+
+      if ((endpoint = usbGetEndpoint(device, urb->endpoint))) {
+        UsbEndpointExtension *eptx = endpoint->extension;
+        int found = 1;
+
+        while (!deleteItem(eptx->completedRequests, request)) {
+          if (!reap) break;
+
+          if (!usbReapURB(device, 0)) {
+            found = 0;
+            break;
+          }
+        }
+
+        if (found) {
+          free(request);
+          return 1;
+        }
+
+        logMessage(LOG_ERR, "USB request not found: urb=%p ept=%02X",
+                   urb, urb->endpoint);
+      }
+    }
+  }
+
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    struct usbdevfs_urb *urb;
+
+    while (!(urb = dequeueItem(eptx->completedRequests))) {
+      if (!usbReapURB(device, wait)) return NULL;
+    }
+
+    usbLogURB(urb, "reaped");
+
+    response->context = urb->usercontext;
+    response->buffer = urb->buffer;
+    response->size = urb->buffer_length;
+
+    if ((response->error = urb->status)) {
+      if (response->error < 0) response->error = -response->error;
+      errno = response->error;
+      logSystemError("USB URB status");
+      response->count = -1;
+    } else {
+      response->count = urb->actual_length;
+
+      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+        case UsbEndpointDirection_Input:
+          if (!usbApplyInputFilters(endpoint, response->buffer, response->size, &response->count)) {
+            response->error = EIO;
+            response->count = -1;
+          }
+          break;
+      }
+    }
+
+    return urb;
+  }
+
+  return NULL;
+}
+
+static ssize_t
+usbBulkTransfer (
+  UsbEndpoint *endpoint,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = endpoint->device->extension;
+
+  if (usbOpenUsbfsFile(devx)) {
+    struct usbdevfs_bulktransfer arg;
+
+    memset(&arg, 0, sizeof(arg));
+    arg.ep = endpoint->descriptor->bEndpointAddress;
+    arg.data = buffer;
+    arg.len = length;
+    arg.timeout = timeout;
+
+    {
+      int count = ioctl(devx->usbfsFile, USBDEVFS_BULK, &arg);
+      if (count != -1) return count;
+      if (USB_ENDPOINT_DIRECTION(endpoint->descriptor) == UsbEndpointDirection_Input)
+        if (errno == ETIMEDOUT)
+          errno = EAGAIN;
+      if (errno != EAGAIN) logSystemError("USB bulk transfer");
+    }
+  }
+
+  return -1;
+}
+
+static struct usbdevfs_urb *
+usbInterruptTransfer (
+  UsbEndpoint *endpoint,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbDevice *device = endpoint->device;
+  struct usbdevfs_urb *urb = usbSubmitRequest(device,
+                                              endpoint->descriptor->bEndpointAddress,
+                                              buffer, length, NULL);
+
+  if (urb) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    int retryInterval = endpoint->descriptor->bInterval + 1;
+    TimePeriod period;
+
+    if (timeout) startTimePeriod(&period, timeout);
+
+    do {
+      if (usbReapURB(device, 0) &&
+          deleteItem(eptx->completedRequests, urb)) {
+        if (!urb->status) return urb;
+        if ((errno = urb->status) < 0) errno = -errno;
+        free(urb);
+        break;
+      }
+
+      if (!timeout || afterTimePeriod(&period, NULL)) {
+        usbCancelRequest(device, urb);
+        errno = ETIMEDOUT;
+        break;
+      }
+
+      asyncWait(retryInterval);
+    } while (1);
+  }
+
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return usbMonitorInputPipe(device, endpointNumber, callback, data);
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  ssize_t count = -1;
+  UsbEndpoint *endpoint;
+
+  logMessage(LOG_CATEGORY(USB_IO), "reading endpoint: %u", endpointNumber);
+
+  if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+    UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(endpoint->descriptor);
+
+    switch (transfer) {
+      case UsbEndpointTransfer_Interrupt:
+        if (!LINUX_USB_INPUT_TREAT_INTERRUPT_AS_BULK) {
+          struct usbdevfs_urb *urb = usbInterruptTransfer(endpoint, NULL, length, timeout);
+
+          if (urb) {
+            count = urb->actual_length;
+            if (count > length) count = length;
+            memcpy(buffer, urb->buffer, count);
+            free(urb);
+          }
+
+          break;
+        }
+        /* fall through */
+
+      case UsbEndpointTransfer_Bulk:
+        count = usbBulkTransfer(endpoint, buffer, length, timeout);
+        break;
+
+      default:
+        logMessage(LOG_ERR, "USB input transfer not supported: %d", transfer);
+        errno = ENOSYS;
+        break;
+    }
+
+    if (count != -1) {
+      if (!usbApplyInputFilters(endpoint, buffer, length, &count)) {
+        errno = EIO;
+        count = -1;
+      }
+    }
+  }
+
+  return count;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) {
+    UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(endpoint->descriptor);
+
+    usbLogEndpointData(endpoint, "output", buffer, length);
+
+    switch (transfer) {
+      case UsbEndpointTransfer_Interrupt:
+      case UsbEndpointTransfer_Bulk:
+        return usbBulkTransfer(endpoint, (void *)buffer, length, timeout);
+/*
+      case UsbEndpointTransfer_Interrupt: {
+        struct usbdevfs_urb *urb = usbInterruptTransfer(endpoint, (void *)buffer, length, timeout);
+
+        if (urb) {
+          ssize_t count = urb->actual_length;
+          free(urb);
+          return count;
+        }
+        break;
+      }
+*/
+      default:
+        logMessage(LOG_ERR, "USB output transfer not supported: %d", transfer);
+        errno = ENOSYS;
+        break;
+    }
+  }
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  device->descriptor = device->extension->host->usbDescriptor;
+  return 1;
+}
+
+static int
+usbHandleInputURB (UsbEndpoint *endpoint, struct usbdevfs_urb *urb) {
+  deleteItem(endpoint->direction.input.pending.requests, urb);
+
+  if (urb->actual_length < 0) {
+    usbLogInputProblem(endpoint, "data not available");
+    return 0;
+  }
+
+  return usbHandleInputResponse(endpoint, urb->buffer, urb->actual_length);
+}
+
+static void
+usbInitializeSignalMonitor (UsbEndpointExtension *eptx) {
+  eptx->monitor.signal.handle = NULL;
+  eptx->monitor.signal.number = 0;
+}
+
+static void
+usbStopSignalMonitor (UsbEndpointExtension *eptx) {
+  if (eptx->monitor.signal.handle) {
+    asyncCancelRequest(eptx->monitor.signal.handle);
+    eptx->monitor.signal.handle = NULL;
+  }
+
+  if (eptx->monitor.signal.number) {
+    asyncRelinquishSignalNumber(eptx->monitor.signal.number);
+    eptx->monitor.signal.number = 0;
+  }
+}
+
+ASYNC_SIGNAL_CALLBACK(usbHandleInputSignal) {
+  UsbEndpoint *endpoint = parameters->data;
+  UsbEndpointExtension *eptx = endpoint->extension;
+
+  while (1) {
+    UsbResponse response;
+    struct usbdevfs_urb *urb = usbReapResponse(endpoint->device,
+                                               endpoint->descriptor->bEndpointAddress,
+                                               &response, 0);
+
+    if (!urb) return 1;
+
+    {
+      int handled = 0;
+
+      if (!response.error) {
+        urb->actual_length = response.count;
+        if (usbHandleInputURB(endpoint, urb)) handled = 1;
+      } else {
+        errno = response.error;
+      }
+
+      if (!handled) {
+        usbSetEndpointInputError(endpoint, errno);
+        usbStopSignalMonitor(eptx);
+      }
+
+      free(urb);
+      if (!handled) return 0;
+    }
+  }
+}
+
+static int
+usbStartSignalMonitor (UsbEndpoint *endpoint) {
+  UsbEndpointExtension *eptx = endpoint->extension;
+
+  if ((eptx->monitor.signal.number = asyncObtainSignalNumber())) {
+    if (asyncMonitorSignal(&eptx->monitor.signal.handle,
+                           eptx->monitor.signal.number,
+                           usbHandleInputSignal, endpoint)) {
+      logMessage(LOG_CATEGORY(USB_IO),
+                 "signal monitor started: Ept:%02X Sig:%d",
+                 endpoint->descriptor->bEndpointAddress,
+                 eptx->monitor.signal.number);
+      return 1;
+    } else {
+      usbLogInputProblem(endpoint, "monitor not registered");
+    }
+
+    asyncRelinquishSignalNumber(eptx->monitor.signal.number);
+  } else {
+    usbLogInputProblem(endpoint, "signal number not obtained");
+  }
+
+  return 0;
+}
+
+static void
+usbInitializeUsbfsMonitor (UsbDeviceExtension *devx) {
+  devx->usbfsMonitorHandle = NULL;
+}
+
+static void
+usbStopUsbfsMonitor (UsbDeviceExtension *devx) {
+  if (devx->usbfsMonitorHandle) {
+    asyncCancelRequest(devx->usbfsMonitorHandle);
+    devx->usbfsMonitorHandle = NULL;
+  }
+}
+
+static int
+usbHandleCompletedInputRequest (UsbEndpoint *endpoint, struct usbdevfs_urb *urb) {
+  ssize_t count = urb->actual_length;
+  int error = urb->status;
+
+  if (!error) {
+    if (usbApplyInputFilters(endpoint, urb->buffer, urb->buffer_length, &count)) {
+      urb->actual_length = count;
+
+      if (usbHandleInputURB(endpoint, urb)) {
+        return 1;
+      }
+    }
+  } else {
+    if (error < 0) error = -error;
+    errno = error;
+    logSystemError("USB URB status");
+  }
+
+  return 0;
+}
+
+ASYNC_MONITOR_CALLBACK(usbHandleCompletedInputRequests) {
+  UsbDevice *device = parameters->data;
+  UsbEndpoint *endpoint;
+
+  {
+    int error = parameters->error;
+
+    if (error) {
+      logActionError(error, "USBFS monitor");
+      usbSetDeviceInputError(device, error);
+      return 0;
+    }
+  }
+
+  while ((endpoint = usbReapURB(device, 0))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+
+    while (1) {
+      struct usbdevfs_urb *urb = dequeueItem(eptx->completedRequests);
+      if (!urb) break;
+      usbLogURB(urb, "reaped");
+
+      int handled = usbHandleCompletedInputRequest(endpoint, urb);
+      int error = errno;
+      free(urb);
+
+      if (!handled) {
+        usbSetEndpointInputError(endpoint, error);
+        return 0;
+      }
+    }
+  }
+
+  if (errno == EAGAIN) return 1;
+  usbSetDeviceInputError(device, errno);
+  return 0;
+}
+
+static int
+usbStartUsbfsMonitor (UsbDevice *device) {
+  UsbDeviceExtension *devx = device->extension;
+
+  if (devx->usbfsMonitorHandle) return 1;
+
+  if (usbOpenUsbfsFile(devx)) {
+    if (asyncMonitorFileOutput(&devx->usbfsMonitorHandle,
+                               devx->usbfsFile,
+                               usbHandleCompletedInputRequests,
+                               device)) {
+      logMessage(LOG_CATEGORY(USB_IO), "USBFS monitor started");
+      return 1;
+    } else {
+      logMessage(LOG_ERR, "USBFS monitor error: %s: %s",
+                 devx->host->usbfsPath, strerror(errno));
+    }
+  }
+
+  return 0;
+}
+
+static int
+usbPrepareInputEndpoint (UsbEndpoint *endpoint) {
+  UsbDevice *device = endpoint->device;
+
+  if (LINUX_USB_INPUT_PIPE_DISABLE) return 1;
+
+  switch (USB_ENDPOINT_TRANSFER(endpoint->descriptor)) {
+    case UsbEndpointTransfer_Bulk:
+    case UsbEndpointTransfer_Interrupt:
+      break;
+
+    default:
+      return 1;
+  }
+
+  if (usbMakeInputPipe(endpoint)) {
+    int monitorStarted = LINUX_USB_INPUT_USE_SIGNAL_MONITOR?
+                         usbStartSignalMonitor(endpoint):
+                         usbStartUsbfsMonitor(device);
+
+    if (monitorStarted) {
+      return 1;
+    } else {
+      usbLogInputProblem(endpoint, "monitor not started");
+    }
+
+    usbDestroyInputPipe(endpoint);
+  } else {
+    usbLogInputProblem(endpoint, "pipe not created");
+  }
+
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  UsbEndpointExtension *eptx;
+
+  if ((eptx = malloc(sizeof(*eptx)))) {
+    memset(eptx, 0, sizeof(*eptx));
+    usbInitializeSignalMonitor(eptx);
+
+    if ((eptx->completedRequests = newQueue(NULL, NULL))) {
+      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+        case UsbEndpointDirection_Input:
+          endpoint->prepare = usbPrepareInputEndpoint;
+          break;
+      }
+
+      endpoint->extension = eptx;
+      return 1;
+    } else {
+      logSystemError("USB endpoint completed request queue allocate");
+    }
+
+    free(eptx);
+  } else {
+    logSystemError("USB endpoint extension allocate");
+  }
+
+  return 0;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+  usbStopSignalMonitor(eptx);
+
+  if (eptx->completedRequests) {
+    deallocateQueue(eptx->completedRequests);
+    eptx->completedRequests = NULL;
+  }
+
+  free(eptx);
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  usbStopUsbfsMonitor(devx);
+  usbCloseUsbfsFile(devx);
+  free(devx);
+}
+
+static void
+usbDeallocateHostDevice (void *item, void *data) {
+  UsbHostDevice *host = item;
+
+  if (host->sysfsPath) free(host->sysfsPath);
+  if (host->usbfsPath) free(host->usbfsPath);
+  free(host);
+}
+
+typedef struct {
+  UsbDeviceChooser *chooser;
+  UsbChooseChannelData *data;
+  UsbDevice *device;
+} UsbTestHostDeviceData;
+
+static int
+usbTestHostDevice (void *item, void *data) {
+  const UsbHostDevice *host = item;
+  UsbTestHostDeviceData *test = data;
+  UsbDeviceExtension *devx;
+
+  if ((devx = malloc(sizeof(*devx)))) {
+    memset(devx, 0, sizeof(*devx));
+    devx->host = host;
+    devx->usbfsFile = -1;
+    usbInitializeUsbfsMonitor(devx);
+
+    if ((test->device = usbTestDevice(devx, test->chooser, test->data))) return 1;
+
+    usbDeallocateDeviceExtension(devx);
+  } else {
+    logMallocError();
+  }
+
+  return 0;
+}
+
+static char *
+usbMakeSysfsPath (const char *usbfsPath) {
+  const char *tail = usbfsPath + strlen(usbfsPath);
+
+  {
+    int count = 0;
+    while (1) {
+      if (tail == usbfsPath) return 0;
+      if (!isPathSeparator(*--tail)) continue;
+      if (++count == 2) break;
+    }
+  }
+
+  {
+    unsigned int bus;
+    unsigned int device;
+    char extra;
+    int count = sscanf(tail, "/%u/%u%c", &bus, &device, &extra);
+
+    if (count == 2) {
+      unsigned int minor = ((bus - 1) << 7) | (device - 1);
+
+      static const char *const formats[] = {
+        "/sys/dev/char/189:%4$u%1$n%2$u%3$u",
+        "/sys/class/usb_device/usbdev%2$u.%3$u/device%1$n",
+        "/sys/class/usb_endpoint/usbdev%2$u.%3$u_ep00/device%1$n",
+        NULL
+      };
+      const char *const *format = formats;
+
+      while (*format) {
+        int length;
+        char path[strlen(*format) + (2 * 0X10) + 1];
+
+        snprintf(path, sizeof(path), *format, &length, bus, device, minor);
+        path[length] = 0;
+
+        if (access(path, F_OK) != -1) {
+          char *sysfsPath = strdup(path);
+          if (!sysfsPath) logSystemError("strdup");
+          return sysfsPath;
+        }
+
+        format += 1;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static int
+usbReadHostDeviceDescriptor (UsbHostDevice *host) {
+  int ok = 0;
+  int file = -1;
+  int sysfs = 0;
+
+  if (file == -1) {
+    if (host->sysfsPath) {
+      char *path;
+
+      if ((path = makePath(host->sysfsPath, "descriptors"))) {
+        int openFlags = O_RDONLY;
+
+        #ifdef O_CLOEXEC
+        openFlags |= O_CLOEXEC;
+        #endif /* O_CLOEXEC */
+
+        if ((file = open(path, openFlags)) != -1) {
+          sysfs = 1;
+        }
+
+        free(path);
+      }
+    }
+  }
+
+  if (file == -1) {
+    int openFlags = O_RDONLY;
+
+    #ifdef O_CLOEXEC
+    openFlags |= O_CLOEXEC;
+    #endif /* O_CLOEXEC */
+
+    file = open(host->usbfsPath, openFlags);
+  }
+
+  if (file != -1) {
+    int count = read(file, &host->usbDescriptor, UsbDescriptorSize_Device);
+
+    if (count == -1) {
+      logSystemError("USB device descriptor read");
+    } else if (count != UsbDescriptorSize_Device) {
+      logMessage(LOG_ERR, "USB short device descriptor: %d", count);
+    } else {
+      ok = 1;
+
+      if (!sysfs) {
+        host->usbDescriptor.bcdUSB = getLittleEndian16(host->usbDescriptor.bcdUSB);
+        host->usbDescriptor.idVendor = getLittleEndian16(host->usbDescriptor.idVendor);
+        host->usbDescriptor.idProduct = getLittleEndian16(host->usbDescriptor.idProduct);
+        host->usbDescriptor.bcdDevice = getLittleEndian16(host->usbDescriptor.bcdDevice);
+      }
+    }
+
+    close(file);
+  }
+
+  return ok;
+}
+
+static int
+usbAddHostDevice (const char *path) {
+  int ok = 0;
+  UsbHostDevice *host;
+
+  if ((host = malloc(sizeof(*host)))) {
+    if ((host->usbfsPath = strdup(path))) {
+      host->sysfsPath = usbMakeSysfsPath(host->usbfsPath);
+
+      if (!usbReadHostDeviceDescriptor(host)) {
+        ok = 1;
+      } else if (enqueueItem(usbHostDevices, host)) {
+        return 1;
+      }
+
+      if (host->sysfsPath) free(host->sysfsPath);
+      free(host->usbfsPath);
+    } else {
+      logSystemError("strdup");
+    }
+
+    free(host);
+  } else {
+    logMallocError();
+  }
+
+  return ok;
+}
+
+static int
+usbAddHostDevices (const char *root) {
+  int ok = 0;
+  size_t rootLength = strlen(root);
+  DIR *directory;
+
+  if ((directory = opendir(root))) {
+    struct dirent *entry;
+
+    ok = 1;
+    while ((entry = readdir(directory))) {
+      size_t nameLength = strlen(entry->d_name);
+      struct stat status;
+      char path[rootLength + 1 + nameLength + 1];
+
+      if (strspn(entry->d_name, "0123456789") != nameLength) continue;
+      snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
+      if (stat(path, &status) == -1) continue;
+
+      if (S_ISDIR(status.st_mode)) {
+        if (!usbAddHostDevices(path)) ok = 0;
+      } else if (S_ISREG(status.st_mode) || S_ISCHR(status.st_mode)) {
+        if (!usbAddHostDevice(path)) ok = 0;
+      }
+
+      if (!ok) break;
+    }
+
+    closedir(directory);
+  }
+
+  return ok;
+}
+
+typedef int (*FileSystemVerifier) (const char *path);
+
+typedef struct {
+  const char *path;
+  FileSystemVerifier verify;
+} FileSystemCandidate;
+
+static int
+usbVerifyFileSystem (const char *path, long type) {
+  struct statfs status;
+  if (statfs(path, &status) != -1) {
+    if (status.f_type == type) return 1;
+  }
+  return 0;
+}
+
+static char *
+usbGetFileSystem (const char *type, const FileSystemCandidate *candidates, MountPointTester test, FileSystemVerifier verify) {
+  if (candidates) {
+    const FileSystemCandidate *candidate = candidates;
+
+    while (candidate->path) {
+      logMessage(LOG_CATEGORY(USB_IO),
+                 "USBFS root candidate: %s: %s",
+                 type, candidate->path);
+
+      if (candidate->verify(candidate->path)) {
+        char *path = strdup(candidate->path);
+        if (path) return path;
+        logMallocError();
+      }
+
+      candidate += 1;
+    }
+  }
+
+  if (test) {
+    char *path = findMountPoint(test);
+    if (path) return path;
+  }
+
+  if (verify) {
+    char *directory = makeWritablePath(type);
+
+    if (directory) {
+      if (ensureDirectory(directory, 0)) {
+        if (verify(directory)) return directory;
+
+        {
+          const char *strings[] = {PACKAGE_TARNAME, "-", type};
+          char *name = joinStrings(strings, ARRAY_COUNT(strings));
+          if (makeMountPoint(directory, name, type)) return directory;
+        }
+      }
+
+      free(directory);
+    }
+  }
+
+  return NULL;
+}
+
+static int
+usbVerifyDirectory (const char *path) {
+  if (access(path, F_OK) != -1) return 1;
+  return 0;
+}
+
+static int
+usbVerifyUsbfs (const char *path) {
+  return usbVerifyFileSystem(path, USBDEVICE_SUPER_MAGIC);
+}
+
+static int
+usbTestUsbfs (const char *path, const char *type) {
+  if ((strcmp(type, "usbdevfs") == 0) ||
+      (strcmp(type, "usbfs") == 0)) {
+    if (usbVerifyUsbfs(path)) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static char *
+usbGetUsbfs (void) {
+  static const FileSystemCandidate usbfsCandidates[] = {
+    {.path="/dev/bus/usb", .verify=usbVerifyDirectory},
+    {.path="/proc/bus/usb", .verify=usbVerifyUsbfs},
+    {.path=NULL, .verify=NULL}
+  };
+
+  return usbGetFileSystem("usbfs", usbfsCandidates, usbTestUsbfs, usbVerifyUsbfs);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  if (!usbHostDevices) {
+    int ok = 0;
+
+    if ((usbHostDevices = newQueue(usbDeallocateHostDevice, NULL))) {
+      char *root;
+
+      if ((root = usbGetUsbfs())) {
+        logMessage(LOG_CATEGORY(USB_IO), "USBFS root: %s", root);
+        if (usbAddHostDevices(root)) ok = 1;
+
+        free(root);
+      } else {
+        logMessage(LOG_CATEGORY(USB_IO), "USBFS not mounted");
+      }
+
+      if (!ok) {
+        deallocateQueue(usbHostDevices);
+        usbHostDevices = NULL;
+      }
+    }
+  }
+
+  if (usbHostDevices) {
+    UsbTestHostDeviceData test = {
+      .chooser = chooser,
+      .data = data,
+      .device = NULL
+    };
+
+    if (processQueue(usbHostDevices, usbTestHostDevice, &test)) return test.device;
+  }
+
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+  if (usbHostDevices) {
+    deallocateQueue(usbHostDevices);
+    usbHostDevices = NULL;
+  }
+}
diff --git a/Programs/usb_netbsd.c b/Programs/usb_netbsd.c
new file mode 100644
index 0000000..7580fa6
--- /dev/null
+++ b/Programs/usb_netbsd.c
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <dev/usb/usb.h>
+
+#include "io_usb.h"
+#include "usb_internal.h"
+
+#define USB_CONTROL_PATH_FORMAT "/dev/%s.00"
+#define USB_ENDPOINT_PATH_FORMAT "%.*s.%02d"
+#include "usb_bsd.h"
diff --git a/Programs/usb_none.c b/Programs/usb_none.c
new file mode 100644
index 0000000..d564938
--- /dev/null
+++ b/Programs/usb_none.c
@@ -0,0 +1,178 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  return 1;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_openbsd.c b/Programs/usb_openbsd.c
new file mode 100644
index 0000000..7580fa6
--- /dev/null
+++ b/Programs/usb_openbsd.c
@@ -0,0 +1,34 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <dev/usb/usb.h>
+
+#include "io_usb.h"
+#include "usb_internal.h"
+
+#define USB_CONTROL_PATH_FORMAT "/dev/%s.00"
+#define USB_ENDPOINT_PATH_FORMAT "%.*s.%02d"
+#include "usb_bsd.h"
diff --git a/Programs/usb_serial.c b/Programs/usb_serial.c
new file mode 100644
index 0000000..18a96ed
--- /dev/null
+++ b/Programs/usb_serial.c
@@ -0,0 +1,220 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "log.h"
+#include "program.h"
+#include "usb_internal.h"
+#include "usb_serial.h"
+#include "usb_adapters.h"
+#include "bitfield.h"
+
+static void
+usbLogSerialProblem (UsbDevice *device, const char *problem) {
+  logMessage(LOG_CATEGORY(SERIAL_IO),
+    "%s: Vendor:%04X Product:%04X",
+    problem,
+    getLittleEndian16(device->descriptor.idVendor),
+    getLittleEndian16(device->descriptor.idProduct)
+  );
+}
+
+int
+usbSkipInitialBytes (UsbInputFilterData *data, unsigned int count) {
+  if (data->length > count) {
+    unsigned char *buffer = data->buffer;
+    memmove(buffer, buffer+count, data->length-=count);
+  } else {
+    data->length = 0;
+  }
+
+  return 1;
+}
+
+static const UsbSerialAdapter **usbSerialAdapters = NULL;
+
+static int
+usbCompareSerialAdapters (const UsbSerialAdapter *adapter1, const UsbSerialAdapter *adapter2) {
+  if (adapter1->vendor < adapter2->vendor) return -1;
+  if (adapter1->vendor > adapter2->vendor) return 1;
+
+  if (adapter1->product < adapter2->product) return -1;
+  if (adapter1->product > adapter2->product) return 1;
+
+  return 0;
+}
+
+static int
+usbSortSerialAdapters (const void *element1, const void *element2) {
+  const UsbSerialAdapter *const *adapter1 = element1;
+  const UsbSerialAdapter *const *adapter2 = element2;
+
+  return usbCompareSerialAdapters(*adapter1, *adapter2);
+}
+
+static int
+usbSearchSerialAdapter (const void *target, const void *element) {
+  const UsbSerialAdapter *const *adapter = element;
+
+  return usbCompareSerialAdapters(target, *adapter);
+}
+
+static const UsbSerialAdapter *
+usbGetSerialAdapter (uint16_t vendor, uint16_t product) {
+  const UsbSerialAdapter target = {
+    .vendor = vendor,
+    .product = product
+  };
+
+  const UsbSerialAdapter *const *adapter = bsearch(&target, usbSerialAdapters, usbSerialAdapterCount, sizeof(*usbSerialAdapters), usbSearchSerialAdapter);
+
+  return adapter? *adapter: NULL;
+}
+
+const UsbSerialAdapter *
+usbFindSerialAdapter (const UsbDeviceDescriptor *descriptor) {
+  if (!usbSerialAdapters) {
+    const UsbSerialAdapter **adapters;
+
+    if (!(adapters = malloc(usbSerialAdapterCount * sizeof(*adapters)))) {
+      logMallocError();
+      return NULL;
+    }
+
+    {
+      const UsbSerialAdapter *source = usbSerialAdapterTable;
+      const UsbSerialAdapter *end = source + usbSerialAdapterCount;
+      const UsbSerialAdapter **target = adapters;
+
+      while (source < end) *target++ = source++;
+      qsort(adapters, usbSerialAdapterCount, sizeof(*adapters), usbSortSerialAdapters);
+    }
+
+    usbSerialAdapters = adapters;
+    registerProgramMemory("sorted-usb-serial-adapters", &usbSerialAdapters);
+  }
+
+  {
+    uint16_t vendor = getLittleEndian16(descriptor->idVendor);
+    uint16_t product = getLittleEndian16(descriptor->idProduct);
+    const UsbSerialAdapter *adapter = usbGetSerialAdapter(vendor, product);
+
+    if (!adapter) adapter = usbGetSerialAdapter(vendor, 0);
+    return adapter;
+  }
+}
+
+int
+usbSetSerialOperations (UsbDevice *device) {
+  if (!device->serial.operations) {
+    const UsbSerialOperations *uso = NULL;
+
+    {
+      const UsbSerialAdapter *adapter = usbFindSerialAdapter(&device->descriptor);
+
+      if (adapter) {
+        if ((uso = adapter->operations)) {
+          UsbInputFilter *filter = uso->inputFilter;
+         
+          if (filter && !usbAddInputFilter(device, filter)) return 0;
+        }
+      }
+    }
+
+    if (!uso) {
+      if (device->descriptor.bDeviceClass == 0X02) {
+        uso = &usbSerialOperations_CDC_ACM;
+      }
+    }
+
+    if (uso) {
+      logMessage(LOG_CATEGORY(SERIAL_IO), "USB adapter: %s", uso->name);
+      UsbSerialData *usd = NULL;
+
+      if (uso->makeData) {
+        if (!uso->makeData(device, &usd)) {
+          return 0;
+        }
+      }
+
+      device->serial.operations = uso;
+      device->serial.data = usd;
+    }
+  }
+
+  return 1;
+}
+
+const UsbSerialOperations *
+usbGetSerialOperations (UsbDevice *device) {
+  return device->serial.operations;
+}
+
+UsbSerialData *
+usbGetSerialData (UsbDevice *device) {
+  return device->serial.data;
+}
+
+int
+usbSetSerialParameters (UsbDevice *device, const SerialParameters *parameters) {
+  int ok = 0;
+  const UsbSerialOperations *serial = usbGetSerialOperations(device);
+
+  if (!serial) {
+    usbLogSerialProblem(device, "no serial operations");
+    errno = ENOSYS;
+  } else if (serial->setLineConfiguration) {
+    ok = serial->setLineConfiguration(
+      device, parameters->baud,
+      parameters->dataBits, parameters->stopBits,
+      parameters->parity, parameters->flowControl
+    );
+  } else if (serial->setLineProperties) {
+    ok = serial->setLineProperties(
+      device, parameters->baud,
+      parameters->dataBits, parameters->stopBits,
+      parameters->parity
+    );
+  } else {
+    ok = 1;
+
+    if (!serial->setBaud) {
+      usbLogSerialProblem(device, "setting baud is not supported");
+    } else if (!serial->setBaud(device, parameters->baud)) {
+      ok = 0;
+    }
+
+    if (!serial->setDataFormat) {
+      usbLogSerialProblem(device, "setting data format is not supported");
+    } else if (!serial->setDataFormat(device, parameters->dataBits, parameters->stopBits, parameters->parity)) {
+      ok = 0;
+    }
+
+    if (!serial->setFlowControl) {
+      usbLogSerialProblem(device, "setting flow control is not supported");
+    } else if (!serial->setFlowControl(device, parameters->flowControl)) {
+      ok = 0;
+    }
+  }
+
+  return ok;
+}
diff --git a/Programs/usb_serial.h b/Programs/usb_serial.h
new file mode 100644
index 0000000..19886fc
--- /dev/null
+++ b/Programs/usb_serial.h
@@ -0,0 +1,62 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_USB_SERIAL
+#define BRLTTY_INCLUDED_USB_SERIAL
+
+#include "io_usb.h"
+#include "io_log.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  const UsbSerialOperations *operations;
+  uint16_t vendor;
+  uint16_t product;
+  unsigned generic:1;
+} UsbSerialAdapter;
+
+extern const UsbSerialAdapter *usbFindSerialAdapter (const UsbDeviceDescriptor *descriptor);
+extern const UsbSerialOperations usbSerialOperations_CDC_ACM;
+extern const UsbSerialOperations usbSerialOperations_Belkin;
+extern const UsbSerialOperations usbSerialOperations_CH341;
+extern const UsbSerialOperations usbSerialOperations_CP2101;
+extern const UsbSerialOperations usbSerialOperations_CP2110;
+extern const UsbSerialOperations usbSerialOperations_FTDI_SIO;
+extern const UsbSerialOperations usbSerialOperations_FTDI_FT8U232AM;
+extern const UsbSerialOperations usbSerialOperations_FTDI_FT232BM;
+
+extern UsbSerialData *usbGetSerialData (UsbDevice *devic3e);
+extern int usbSkipInitialBytes (UsbInputFilterData *data, unsigned int count);
+
+static inline int
+usbUpdateByte (uint8_t *byte, uint8_t mask, uint8_t value) {
+  if ((*byte & mask) == value) return 0;
+
+  *byte &= ~mask;
+  *byte |= value;
+  return 1;
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_USB_SERIAL */
diff --git a/Programs/usb_solaris.c b/Programs/usb_solaris.c
new file mode 100644
index 0000000..8984614
--- /dev/null
+++ b/Programs/usb_solaris.c
@@ -0,0 +1,649 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <strings.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/asynch.h>
+#include <sys/usb/clients/ugen/usb_ugen.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+
+struct UsbDeviceExtensionStruct {
+  char *path;
+  int data;
+  int status;
+
+  unsigned char configuration;
+  unsigned char interface;
+  unsigned char alternative;
+};
+
+struct UsbEndpointExtensionStruct {
+  Queue *requests;
+
+  char *name;
+  int data;
+  int status;
+};
+
+typedef struct {
+  aio_result_t result; /* must be first for aiowait() */
+
+  UsbEndpoint *endpoint;
+
+  void *context;
+  void *buffer;
+  size_t length;
+} UsbAsynchronousRequest;
+
+static int
+usbOpenStatusFile (const char *path, int *status) {
+  if ((*status = open(path, O_RDONLY)) != -1) return 1;
+  logSystemError("USB status file open");
+  return 0;
+}
+
+static int
+usbOpenEndpointFiles (
+  const char *device,
+  const char *endpoint,
+  int *data,
+  int *status,
+  int flags
+) {
+  static const char *const suffix = "stat";
+  char path[strlen(device) + 1 + strlen(endpoint) + strlen(suffix) + 1];
+
+  sprintf(path, "%s/%s", device, endpoint);
+  if ((*data = open(path, flags)) != -1) {
+    strcat(path, suffix);
+    if (usbOpenStatusFile(path, status)) {
+      return 1;
+    } else {
+      logSystemError("USB endpoint status open");
+    }
+
+    close(*data);
+    *data = -1;
+  } else {
+    logSystemError("USB endpoint data open");
+  }
+
+  return 0;
+}
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  UsbDeviceExtension *devx = device->extension;
+  devx->configuration = configuration;
+  return 1;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  UsbDeviceExtension *devx = device->extension;
+  devx->interface = interface;
+  devx->alternative = 0;
+  return 1;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  return 1;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  UsbDeviceExtension *devx = device->extension;
+  devx->interface = interface;
+  devx->alternative = alternative;
+  return 1;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  UsbDeviceExtension *devx = device->extension;
+  UsbSetupPacket setup;
+
+  setup.bRequestType = direction | recipient | type;
+  setup.bRequest = request;
+  putLittleEndian16(&setup.wValue, value);
+  putLittleEndian16(&setup.wIndex, index);
+  putLittleEndian16(&setup.wLength, length);
+
+  switch (direction) {
+    case UsbControlDirection_Input: {
+      size_t size = sizeof(setup);
+      ssize_t count;
+
+      if ((count = write(devx->data, &setup, size)) == -1) {
+        logSystemError("USB control request");
+      } else if (count != size) {
+        logMessage(LOG_ERR, "USB truncated control request: %d < %d", count, size);
+        errno = EIO;
+      } else if ((count = read(devx->data, buffer, length)) == -1) {
+        logSystemError("USB control read");
+      } else {
+        return count;
+      }
+      break;
+    }
+
+    case UsbControlDirection_Output: {
+      unsigned char packet[sizeof(setup) + length];
+      size_t size = 0;
+      ssize_t count;
+
+      memcpy(&packet[size], &setup, sizeof(setup));
+      size += sizeof(setup);
+
+      memcpy(&packet[size], buffer, length);
+      size += length;
+
+      if ((count = write(devx->data, packet, size)) == -1) {
+        logSystemError("USB control write");
+      } else if (count != size) {
+        logMessage(LOG_ERR, "USB truncated control write: %d < %d", count, size);
+        errno = EIO;
+      } else {
+        return size;
+      }
+      break;
+    }
+
+    default:
+      logMessage(LOG_ERR, "USB unsupported control direction: %02X", direction);
+      errno = ENOSYS;
+      break;
+  }
+
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    UsbAsynchronousRequest *request;
+
+    if ((request = malloc(sizeof(*request) + length))) {
+      UsbEndpointDirection direction = USB_ENDPOINT_DIRECTION(endpoint->descriptor);
+
+      request->endpoint = endpoint;
+      request->context = context;
+      request->buffer = (request->length = length)? (request + 1): NULL;
+      request->result.aio_return = AIO_INPROGRESS;
+
+      switch (direction) {
+        case UsbEndpointDirection_Input:
+          if (aioread(eptx->data, request->buffer, request->length, 0, SEEK_CUR, &request->result) != -1) return request;
+          logSystemError("USB asynchronous read");
+          break;
+
+        case UsbEndpointDirection_Output:
+          if (request->buffer) memcpy(request->buffer, buffer, length);
+          if (aiowrite(eptx->data, request->buffer, request->length, 0, SEEK_CUR, &request->result) != -1) return request;
+          logSystemError("USB asynchronous write");
+          break;
+
+        default:
+          logMessage(LOG_ERR, "USB unsupported asynchronous direction: %02X", direction);
+          errno = ENOSYS;
+          break;
+      }
+
+      free(request);
+    } else {
+      logSystemError("USB asynchronous request allocate");
+    }
+  }
+
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  UsbAsynchronousRequest *req = request;
+  UsbEndpoint *endpoint = req->endpoint;
+  UsbEndpointExtension *eptx = endpoint->extension;
+
+  if (!deleteItem(eptx->requests, req)) {
+    if (aiocancel(&req->result) == -1) {
+      if ((errno != EINVAL) && (errno != EACCES)) {
+        logSystemError("USB asynchronous cancel");
+        return 0;
+      }
+    }
+  }
+
+  free(request);
+  return 1;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetEndpoint(device, endpointAddress))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    struct timeval timeout;
+    UsbAsynchronousRequest *request;
+
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+
+    while (!(request = dequeueItem(eptx->requests))) {
+      aio_result_t *result;
+
+    doWait:
+      if ((int)(result = aiowait(wait? NULL: &timeout)) == -1) {
+        if (errno == EINTR) goto doWait;
+
+        if (errno != EINVAL) {
+          logSystemError("USB asynchronous wait");
+          return NULL;
+        }
+
+        result = NULL;
+      }
+
+      if (!result) {
+        errno = EAGAIN;
+        return NULL;
+      }
+
+      request = (UsbAsynchronousRequest *)result;
+      {
+        UsbEndpoint *ep = request->endpoint;
+        UsbEndpointExtension *epx = ep->extension;
+        if (!enqueueItem(epx->requests, request)) {
+          logSystemError("USB asynchronous enqueue");
+        }
+      }
+    }
+
+    response->context = request->context;
+    response->buffer = request->buffer;
+    response->size = request->length;
+    response->count = request->result.aio_return;
+    response->error = request->result.aio_errno;
+
+    if (response->count == -1) {
+      errno = response->error;
+      logSystemError("USB asynchronous completion");
+    } else {
+      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
+        case UsbEndpointDirection_Input:
+          if (!usbApplyInputFilters(endpoint, response->buffer, response->size, &response->count)) {
+            response->error = EIO;
+            response->count = -1;
+          }
+          break;
+      }
+    }
+
+    return request;
+  }
+
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    ssize_t count;
+
+  doRead:
+    if ((count = read(eptx->data, buffer, length)) == -1) {
+      if (errno == EINTR) goto doRead;
+      logSystemError("USB endpoint read");
+    } else if (!usbApplyInputFilters(endpoint, buffer, length, &count)) {
+      errno = EIO;
+    } else if (!count) {
+      errno = EAGAIN;
+    } else {
+      return count;
+    }
+  }
+
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  UsbEndpoint *endpoint;
+
+  if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) {
+    UsbEndpointExtension *eptx = endpoint->extension;
+    ssize_t count;
+
+  doWrite:
+    if ((count = write(eptx->data, buffer, length)) == -1) {
+      if (errno == EINTR) goto doWrite;
+      logSystemError("USB endpoint write");
+    } else if (count != length) {
+      logMessage(LOG_ERR, "USB truncated endpoint write: %d < %d", count, length);
+      errno = EIO;
+    } else {
+      return count;
+    }
+  }
+
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  int count = usbGetDeviceDescriptor(device, &device->descriptor);
+
+  if (count == UsbDescriptorSize_Device) {
+    return 1;
+  }
+
+  if (count != -1) {
+    logMessage(LOG_ERR, "USB short device descriptor (%d).", count);
+    errno = EIO;
+  }
+
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  UsbDevice *device = endpoint->device;
+  UsbDeviceExtension *devx = device->extension;
+  UsbEndpointExtension *eptx;
+
+  if ((eptx = malloc(sizeof(*eptx)))) {
+    if ((eptx->requests = newQueue(NULL, NULL))) {
+      int flags;
+  
+      {
+        char name[0X80];
+        int length = 0;
+  
+        if (devx->configuration != 1) {
+          int count;
+          sprintf(&name[length], "cfg%d%n", devx->configuration, &count);
+          length += count;
+        }
+  
+        {
+          int count;
+          sprintf(&name[length], "if%d%n", devx->interface, &count);
+          length += count;
+        }
+  
+        if (devx->alternative != 0) {
+          int count;
+          sprintf(&name[length], ".%d%n", devx->alternative, &count);
+          length += count;
+        }
+  
+        {
+          const UsbEndpointDescriptor *descriptor = endpoint->descriptor;
+          UsbEndpointDirection direction = USB_ENDPOINT_DIRECTION(descriptor);
+          const char *prefix;
+  
+          switch (direction) {
+            case UsbEndpointDirection_Input:
+              prefix = "in";
+              flags = O_RDONLY;
+              break;
+  
+            case UsbEndpointDirection_Output:
+              prefix = "out";
+              flags = O_WRONLY;
+              break;
+  
+            default:
+              logMessage(LOG_ERR, "USB unsupported endpoint direction: %02X", direction);
+              goto nameError;
+          }
+  
+          {
+            int count;
+            sprintf(&name[length], "%s%d%n", prefix, USB_ENDPOINT_NUMBER(descriptor), &count);
+            length += count;
+          }
+        }
+  
+        eptx->name = strdup(name);
+      }
+  
+      if (eptx->name) {
+        if (usbOpenEndpointFiles(devx->path, eptx->name, &eptx->data, &eptx->status,  flags)) {
+          endpoint->extension = eptx;
+          return 1;
+        }
+  
+        free(eptx->name);
+      }
+    nameError:
+
+      deallocateQueue(eptx->requests);
+    }
+
+    free(eptx);
+  }
+
+  return 0;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+  if (eptx->status != -1) {
+    close(eptx->status);
+    eptx->status = -1;
+  }
+
+  if (eptx->data != -1) {
+    close(eptx->data);
+    eptx->data = -1;
+  }
+
+  if (eptx->name) {
+    free(eptx->name);
+    eptx->name = NULL;
+  }
+
+  if (eptx->requests) {
+    deallocateQueue(eptx->requests);
+    eptx->requests = NULL;
+  }
+
+  free(eptx);
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+  if (devx->status != -1) {
+    close(devx->status);
+    devx->status = -1;
+  }
+
+  if (devx->data != -1) {
+    close(devx->data);
+    devx->data = -1;
+  }
+
+  if (devx->path) {
+    free(devx->path);
+    devx->path = NULL;
+  }
+
+  free(devx);
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  UsbDevice *device = NULL;
+  static const char *const rootPath = "/dev/usb";
+  int rootLength = strlen(rootPath);
+  DIR *rootDirectory;
+
+  if ((rootDirectory = opendir(rootPath))) {
+    struct dirent *deviceEntry;
+
+    while ((deviceEntry = readdir(rootDirectory))) {
+      const char *deviceName = deviceEntry->d_name;
+      int deviceLength = strlen(deviceName);
+
+      {
+        unsigned int vendor;
+        unsigned int product;
+        int length;
+        if (sscanf(deviceName, "%x.%x%n", &vendor, &product, &length) < 2) continue;
+        if (length != deviceLength) continue;
+      }
+
+      deviceLength += rootLength + 1;
+      {
+        char devicePath[deviceLength + 1];
+        DIR *deviceDirectory;
+
+        sprintf(devicePath, "%s/%s", rootPath, deviceName);
+        if ((deviceDirectory = opendir(devicePath))) {
+          struct dirent *instanceEntry;
+
+          while ((instanceEntry = readdir(deviceDirectory))) {
+            const char *instanceName = instanceEntry->d_name;
+            int instanceLength = strlen(instanceName);
+
+            {
+              unsigned int number;
+              int length;
+              if (sscanf(instanceName, "%u%n", &number, &length) < 1) continue;
+              if (length != instanceLength) continue;
+            }
+
+            instanceLength += deviceLength + 1;
+            {
+              char instancePath[instanceLength + 1];
+              UsbDeviceExtension *devx;
+
+              sprintf(instancePath, "%s/%s", devicePath, instanceName);
+              if ((devx = malloc(sizeof(*devx)))) {
+                if ((devx->path = strdup(instancePath))) {
+                  if (usbOpenEndpointFiles(devx->path, "cntrl0", &devx->data, &devx->status, O_RDWR)) {
+                    if ((device = usbTestDevice(devx, chooser, data))) break;
+
+                    close(devx->status);
+                    close(devx->data);
+                  }
+
+                  free(devx->path);
+                }
+
+                free(devx);
+              }
+            }
+          }
+
+          closedir(deviceDirectory);
+        }
+      }
+
+      if (device) break;
+    }
+
+    closedir(rootDirectory);
+  }
+
+  return device;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/usb_winusb.c b/Programs/usb_winusb.c
new file mode 100644
index 0000000..9e2d14a
--- /dev/null
+++ b/Programs/usb_winusb.c
@@ -0,0 +1,177 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <errno.h>
+
+#include "log.h"
+#include "io_usb.h"
+#include "usb_internal.h"
+
+int
+usbDisableAutosuspend (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetConfiguration (UsbDevice *device, unsigned char configuration) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClaimInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbReleaseInterface (UsbDevice *device, unsigned char interface) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbSetAlternative (
+  UsbDevice *device,
+  unsigned char interface,
+  unsigned char alternative
+) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbResetDevice (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbClearHalt (UsbDevice *device, unsigned char endpointAddress) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+ssize_t
+usbControlTransfer (
+  UsbDevice *device,
+  uint8_t direction,
+  uint8_t recipient,
+  uint8_t type,
+  uint8_t request,
+  uint16_t value,
+  uint16_t index,
+  void *buffer,
+  uint16_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+void *
+usbSubmitRequest (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  void *buffer,
+  size_t length,
+  void *context
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbCancelRequest (UsbDevice *device, void *request) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+void *
+usbReapResponse (
+  UsbDevice *device,
+  unsigned char endpointAddress,
+  UsbResponse *response,
+  int wait
+) {
+  logUnsupportedFunction();
+  return NULL;
+}
+
+int
+usbMonitorInputEndpoint (
+  UsbDevice *device, unsigned char endpointNumber,
+  AsyncMonitorCallback *callback, void *data
+) {
+  return 0;
+}
+
+ssize_t
+usbReadEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+ssize_t
+usbWriteEndpoint (
+  UsbDevice *device,
+  unsigned char endpointNumber,
+  const void *buffer,
+  size_t length,
+  int timeout
+) {
+  logUnsupportedFunction();
+  return -1;
+}
+
+int
+usbReadDeviceDescriptor (UsbDevice *device) {
+  logUnsupportedFunction();
+  return 0;
+}
+
+int
+usbAllocateEndpointExtension (UsbEndpoint *endpoint) {
+  return 1;
+}
+
+void
+usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) {
+}
+
+void
+usbDeallocateDeviceExtension (UsbDeviceExtension *devx) {
+}
+
+UsbDevice *
+usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
+  return NULL;
+}
+
+void
+usbForgetDevices (void) {
+}
diff --git a/Programs/utf8.c b/Programs/utf8.c
new file mode 100644
index 0000000..fde7030
--- /dev/null
+++ b/Programs/utf8.c
@@ -0,0 +1,290 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "log.h"
+#include "utf8.h"
+#include "unicode.h"
+
+wchar_t *
+allocateCharacters (size_t count) {
+  {
+    wchar_t *characters = malloc(count * sizeof(*characters));
+    if (characters) return characters;
+  }
+
+  logMallocError();
+  return NULL;
+}
+
+size_t
+convertCodepointToUtf8 (uint32_t codepoint, Utf8Buffer utf8) {
+  size_t length = 0;
+
+  if (!(codepoint & ~0X7F)) {
+    utf8[length++] = codepoint;
+  } else {
+    char *end = &utf8[2];
+
+    {
+      uint32_t value = codepoint;
+      uint32_t mask = ~((1 << 11) - 1);
+
+      while ((value &= mask)) {
+        mask <<= 5;
+        end += 1;
+      }
+
+      length = end - utf8;
+    }
+
+    {
+      uint32_t value = codepoint;
+
+      do {
+        *--end = (value & 0X3F) | 0X80;
+        value >>= 6;
+      } while (end > utf8);
+    }
+
+    *end |= ~((1 << (8 - length)) - 1);
+  }
+
+  utf8[length] = 0;
+  return length;
+}
+
+int
+convertUtf8ToCodepoint (uint32_t *codepoint, const char **utf8, size_t *utfs) {
+  int ok = 0;
+  uint32_t cp = 0;
+
+  int first = 1;
+  int state = 0;
+
+  while (*utfs) {
+    unsigned char byte = *(*utf8)++;
+    *utfs -= 1;
+
+    if (!(byte & 0X80)) {
+      if (!first) goto unexpected;
+      cp = byte;
+      ok = 1;
+      break;
+    }
+
+    if (!(byte & 0X40)) {
+      if (first) break;
+      cp = (cp << 6) | (byte & 0X3F);
+
+      if (!--state) {
+        ok = 1;
+        break;
+      }
+    } else {
+      if (!first) goto unexpected;
+
+      state = 1;
+      uint8_t bit = 0X20;
+
+      while (byte & bit) {
+        if (!(bit >>= 1)) break;
+        state += 1;
+      }
+
+      cp = byte & ((1 << (6 - state)) - 1);
+    }
+
+    first = 0;
+  }
+
+  while (*utfs) {
+    if ((**utf8 & 0XC0) != 0X80) break;
+    ok = 0;
+
+    *utf8 += 1;
+    *utfs -= 1;
+  }
+
+  if (!ok) goto error;
+  *codepoint = cp;
+  return 1;
+
+unexpected:
+  *utf8 -= 1;
+  *utfs += 1;
+error:
+  return 0;
+}
+
+size_t
+convertWcharToUtf8 (wchar_t character, Utf8Buffer utf8) {
+  return convertCodepointToUtf8(character, utf8);
+}
+
+wint_t
+convertUtf8ToWchar (const char **utf8, size_t *utfs) {
+  uint32_t codepoint;
+  int ok = convertUtf8ToCodepoint(&codepoint, utf8, utfs);
+  if (!ok) return WEOF;
+
+  if (codepoint > WCHAR_MAX) codepoint = UNICODE_REPLACEMENT_CHARACTER;
+  return codepoint;
+}
+
+void
+convertUtf8ToWchars (const char **utf8, wchar_t **characters, size_t count) {
+  while (**utf8 && (count > 1)) {
+    size_t utfs = UTF8_LEN_MAX;
+    wint_t character = convertUtf8ToWchar(utf8, &utfs);
+
+    if (character == WEOF) break;
+    *(*characters)++ = character;
+    count -= 1;
+  }
+
+  if (count) **characters = 0;
+}
+
+size_t
+makeUtf8FromWchars (const wchar_t *characters, unsigned int count, char *buffer, size_t size) {
+  char *byte = buffer;
+  const char *end = byte + size;
+
+  for (unsigned int i=0; i<count; i+=1) {
+    Utf8Buffer utf8;
+    size_t utfs = convertWcharToUtf8(characters[i], utf8);
+
+    char *next = byte + utfs;
+    if (next >= end) break;
+
+    memcpy(byte, utf8, utfs);
+    byte = next;
+  }
+
+  *byte = 0;
+  return byte - buffer;
+}
+
+char *
+getUtf8FromWchars (const wchar_t *characters, unsigned int count, size_t *length) {
+  size_t size = (count * UTF8_LEN_MAX) + 1;
+  char buffer[size];
+  size_t len = makeUtf8FromWchars(characters, count, buffer, size);
+  char *text = strdup(buffer);
+
+  if (!text) {
+    logMallocError();
+  } else if (length) {
+    *length = len;
+  }
+
+  return text;
+}
+
+size_t
+makeWcharsFromUtf8 (const char *text, wchar_t *characters, size_t size) {
+  size_t length = strlen(text);
+  size_t count = 0;
+
+  while (length > 0) {
+    const char *utf8 = text;
+    size_t utfs = length;
+    wint_t character = convertUtf8ToWchar(&utf8, &utfs);
+
+    if (character == WEOF) break;
+    if (!character) break;
+
+    if (characters) {
+      if (count == size) break;
+      characters[count] = character;
+    }
+
+    count += 1;
+    text = utf8;
+    length = utfs;
+  }
+
+  if (characters && (count < size)) characters[count] = 0;
+  return count;
+}
+
+size_t
+countUtf8Characters (const char *text) {
+  return makeWcharsFromUtf8(text, NULL, 0);
+}
+
+int
+writeUtf8Character (FILE *stream, wchar_t character) {
+  Utf8Buffer utf8;
+  size_t utfs = convertWcharToUtf8(character, utf8);
+
+  if (utfs) {
+    if (fwrite(utf8, 1, utfs, stream) == utfs) {
+      return 1;
+    } else {
+      logSystemError("fwrite");
+    }
+  } else {
+    logBytes(LOG_ERR, "invalid Unicode character", &character, sizeof(character));
+  }
+
+  return 0;
+}
+
+int
+writeUtf8Characters (FILE *stream, const wchar_t *characters, size_t count) {
+  const wchar_t *character = characters;
+  const wchar_t *end = character + count;
+
+  while (character < end) {
+    if (!writeUtf8Character(stream, *character++)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int
+writeUtf8ByteOrderMark (FILE *stream) {
+#if UNICODE_BYTE_ORDER_MARK <= WCHAR_MAX
+  if (!writeUtf8Character(stream, UNICODE_BYTE_ORDER_MARK)) {
+    return 0;
+  }
+#endif /* UNICODE_BYTE_ORDER_MARK <= WCHAR_MAX */
+
+  return 1;
+}
+
+int
+isCharsetUTF8 (const char *name) {
+  {
+    const char *substring = "utf";
+    size_t length = strlen(substring);
+    if (strncasecmp(name, substring, length) != 0) return 0;
+    name += length;
+    if (*name == '-') name += 1;
+  }
+
+  return strcmp(name, "8") == 0;
+}
diff --git a/Programs/variables.c b/Programs/variables.c
new file mode 100644
index 0000000..b8edc2a
--- /dev/null
+++ b/Programs/variables.c
@@ -0,0 +1,347 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "log.h"
+#include "strfmt.h"
+#include "variables.h"
+#include "queue.h"
+#include "utf8.h"
+
+typedef struct {
+  const wchar_t *characters;
+  int length;
+} CharacterString;
+
+static void
+initializeCharacterString (CharacterString *string) {
+  string->characters = WS_C("\0");
+  string->length = 0;
+}
+
+static void
+clearCharacterString (CharacterString *string) {
+  if (string->length > 0) free((void *)string->characters);
+  initializeCharacterString(string);
+}
+
+static int
+setCharacterString (CharacterString *string, const wchar_t *characters, int length) {
+  if (!length) {
+    clearCharacterString(string);
+  } else {
+    wchar_t *newCharacters;
+
+    if (!(newCharacters = malloc(ARRAY_SIZE(newCharacters, (length + 1))))) {
+      logMallocError();
+      return 0;
+    }
+
+    wmemcpy(newCharacters, characters, length);
+    newCharacters[length] = WC_C('\0');
+
+    clearCharacterString(string);
+    string->characters = newCharacters;
+    string->length = length;
+  }
+
+  return 1;
+}
+
+static void
+getCharacterString (const CharacterString *string, const wchar_t **characters, int *length) {
+  *characters = string->characters;
+  *length = string->length;
+}
+
+struct VariableStruct {
+  CharacterString name;
+  CharacterString value;
+};
+
+struct VariableNestingLevelStruct {
+  const char *name;
+  VariableNestingLevel *previous;
+  Queue *variables;
+  unsigned int references;
+};
+
+VariableNestingLevel *
+claimVariableNestingLevel (VariableNestingLevel *vnl) {
+  vnl->references += 1;
+  return vnl;
+}
+
+static void
+deallocateVariable (void *item, void *data) {
+  Variable *variable = item;
+
+  clearCharacterString(&variable->name);
+  clearCharacterString(&variable->value);
+  free(variable);
+}
+
+VariableNestingLevel *
+newVariableNestingLevel (VariableNestingLevel *previous, const char *name) {
+  VariableNestingLevel *vnl;
+
+  if ((vnl = malloc(sizeof(*vnl)))) {
+    memset(vnl, 0, sizeof(*vnl));
+    vnl->name = name;
+    vnl->references = 0;
+
+    if ((vnl->variables = newQueue(deallocateVariable, NULL))) {
+      if ((vnl->previous = previous)) claimVariableNestingLevel(previous);
+      return vnl;
+    }
+
+    free(vnl);
+  } else {
+    logMallocError();
+  }
+
+  return NULL;
+}
+
+static void
+destroyVariableNestingLevel (VariableNestingLevel *vnl) {
+  deallocateQueue(vnl->variables);
+  free(vnl);
+}
+
+VariableNestingLevel *
+removeVariableNestingLevel (VariableNestingLevel *vnl) {
+  VariableNestingLevel *previous = vnl->previous;
+  if (!--vnl->references) destroyVariableNestingLevel(vnl);
+  return previous;
+}
+
+void
+releaseVariableNestingLevel (VariableNestingLevel *vnl) {
+  while (vnl && !--vnl->references) {
+    VariableNestingLevel *previous = vnl->previous;
+    destroyVariableNestingLevel(vnl);
+    vnl = previous;
+  }
+}
+
+static void
+listVariableLine (const char *line) {
+  logMessage(LOG_NOTICE, "%s", line);
+}
+
+static int
+listVariable (void *item, void *data) {
+  const Variable *variable = item;
+
+  char line[0X100];
+  STR_BEGIN(line, sizeof(line));
+
+  STR_PRINTF("variable: ");
+  STR_PRINTF("%.*" PRIws, variable->name.length, variable->name.characters);
+  STR_PRINTF(" = ");
+  STR_PRINTF("%.*" PRIws, variable->value.length, variable->value.characters);
+
+  STR_END;
+  listVariableLine(line);
+
+  return 0;
+}
+
+void
+listVariables (VariableNestingLevel *from) {
+  listVariableLine("begin variable listing");
+
+  while (from) {
+    {
+      char header[0X100];
+      STR_BEGIN(header, sizeof(header));
+
+      STR_PRINTF("variable nesting level:");
+      if (from->name) STR_PRINTF(" %s", from->name);
+      if (from->references != 1) STR_PRINTF(" Refs:%u", from->references);
+
+      STR_END;
+      listVariableLine(header);
+    }
+
+    processQueue(from->variables, listVariable, NULL);
+    from = from->previous;
+  }
+
+  listVariableLine("end variable listing");
+}
+
+static int
+testVariableName (const void *item, void *data) {
+  const Variable *variable = item;
+  const CharacterString *key = data;
+
+  if (variable->name.length == key->length) {
+    if (wmemcmp(variable->name.characters, key->characters, key->length) == 0) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static Variable *
+findVariable (VariableNestingLevel *vnl, const wchar_t *name, int length, int create) {
+  Variable *variable;
+
+  {
+    CharacterString key = {
+      .characters = name,
+      .length = length
+    };
+
+    if ((variable = findItem(vnl->variables, testVariableName, &key))) return variable;
+  }
+
+  if (create) {
+    if ((variable = malloc(sizeof(*variable)))) {
+      memset(variable, 0, sizeof(*variable));
+      initializeCharacterString(&variable->name);
+      initializeCharacterString(&variable->value);
+
+      if (setCharacterString(&variable->name, name, length)) {
+        if (enqueueItem(vnl->variables, variable)) {
+          return variable;
+        }
+
+        clearCharacterString(&variable->name);
+      }
+
+      free(variable);
+    } else {
+      logMallocError();
+    }
+  }
+
+  return NULL;
+}
+
+const Variable *
+findReadableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length) {
+  while (vnl) {
+    Variable *variable = findVariable(vnl, name, length, 0);
+    if (variable) return variable;
+    vnl = vnl->previous;
+  }
+
+  return NULL;
+}
+
+Variable *
+findWritableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length) {
+  return findVariable(vnl, name, length, 1);
+}
+
+void
+deleteVariables (VariableNestingLevel *vnl) {
+  deleteElements(vnl->variables);
+}
+
+int
+setVariable (Variable *variable, const wchar_t *value, int length) {
+  return setCharacterString(&variable->value, value, length);
+}
+
+void
+getVariableName (const Variable *variable, const wchar_t **characters, int *length) {
+  getCharacterString(&variable->name, characters, length);
+}
+
+void
+getVariableValue (const Variable *variable, const wchar_t **characters, int *length) {
+  getCharacterString(&variable->value, characters, length);
+}
+
+int
+setStringVariable (VariableNestingLevel *vnl, const char *name, const char *value) {
+  size_t nameLength = countUtf8Characters(name);
+  wchar_t nameBuffer[nameLength + 1];
+
+  size_t valueLength = countUtf8Characters(value);
+  wchar_t valueBuffer[valueLength + 1];
+
+  {
+    const char *utf8 = name;
+    wchar_t *wc = nameBuffer;
+    convertUtf8ToWchars(&utf8, &wc, ARRAY_COUNT(nameBuffer));
+  }
+
+  {
+    const char *utf8 = value;
+    wchar_t *wc = valueBuffer;
+    convertUtf8ToWchars(&utf8, &wc, ARRAY_COUNT(valueBuffer));
+  }
+
+  Variable *variable = findVariable(vnl, nameBuffer, nameLength, 1);
+
+  if (variable) {
+    if (setVariable(variable, valueBuffer, valueLength)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int
+setStringVariables (VariableNestingLevel *vnl, const VariableInitializer *initializers) {
+  if (initializers) {
+    const VariableInitializer *initializer = initializers;
+     
+    while (initializer->name) {
+      if (!setStringVariable(vnl, initializer->name, initializer->value)) return 0;
+      initializer += 1;
+    }
+  }
+
+  return 1;
+}
+
+VariableNestingLevel *
+getGlobalVariables (int create) {
+  static VariableNestingLevel *globalVariables = NULL;
+
+  if (!globalVariables) {
+    VariableNestingLevel *vnl;
+
+    if (!(vnl = newVariableNestingLevel(NULL, "global"))) {
+      return NULL;
+    }
+
+    claimVariableNestingLevel(vnl);
+    globalVariables = vnl;
+  }
+
+  return globalVariables;
+}
+
+int
+setGlobalVariable (const char *name, const char *value) {
+  VariableNestingLevel *vnl = getGlobalVariables(1);
+  if (!vnl) return 0;
+  return setStringVariable(vnl, name, value);
+}
diff --git a/Programs/xbrlapi.c b/Programs/xbrlapi.c
new file mode 100644
index 0000000..27f0eef
--- /dev/null
+++ b/Programs/xbrlapi.c
@@ -0,0 +1,1079 @@
+/*
+ * XBrlAPI - A background process tinkering with X for proper BrlAPI behavior
+ *
+ * Copyright (C) 2003-2023 by Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *
+ * XBrlAPI comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+/* Compile with:
+ * gcc -O3 -Wall xbrlapi.c -L/usr/X11R6/lib -lbrlapi -lX11 -o xbrlapi
+ */
+
+#include "prologue.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif /* HAVE_LANGINFO_H */
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else /* HAVE_SYS_SELECT_H */
+#include <sys/time.h>
+#endif /* HAVE_SYS_SELECT_H */
+
+#ifdef HAVE_ICONV_H
+#include <iconv.h>
+#endif /* HAVE_ICONV_H */
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/XKBlib.h>
+#include <X11/keysym.h>
+
+#undef CAN_SIMULATE_KEY_PRESSES
+#if defined(HAVE_X11_EXTENSIONS_XTEST_H) && defined(HAVE_X11_EXTENSIONS_XKB_H)
+#include <X11/extensions/XTest.h>
+#define CAN_SIMULATE_KEY_PRESSES
+#else /* HAVE_X11_EXTENSIONS_XTEST_H && HAVE_X11_EXTENSIONS_XKB_H */
+#warning key press simulation not supported by this build - check that libxtst has been installed
+#endif /* HAVE_X11_EXTENSIONS_XTEST_H && HAVE_X11_EXTENSIONS_XKB_H */
+
+#include "xsel.h"
+
+#define BRLAPI_NO_DEPRECATED
+#include "brlapi.h"
+
+#include "cmdline.h"
+
+#define debugf(fmt, ...) do { if (verbose) fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+
+/******************************************************************************
+ * option handling
+ */
+
+static char *auth;
+static char *host;
+static char *xDisplay;
+static int no_daemon;
+static int quiet;
+static int verbose;
+static int xkb_major_opcode;
+
+static int brlapi_fd;
+
+static void *clipboardData;
+
+BEGIN_OPTION_TABLE(programOptions)
+  { .word = "brlapi",
+    .letter = 'b',
+    .argument = strtext("[host][:port]"),
+    .setting.string = &host,
+    .description = strtext("BrlAPI host and/or port to connect to")
+  },
+
+  { .word = "auth",
+    .letter = 'a',
+    .argument = strtext("scheme+..."),
+    .setting.string = &auth,
+    .description = strtext("BrlAPI authorization/authentication schemes")
+  },
+
+  { .word = "display",
+    .letter = 'd',
+    .argument = strtext("display"),
+    .setting.string = &xDisplay,
+    .description = strtext("X display to connect to")
+  },
+
+  { .word = "quiet",
+    .letter = 'q',
+    .setting.flag = &quiet,
+    .description = strtext("Do not write any text to the braille device")
+  },
+
+  { .word = "verbose",
+    .letter = 'v',
+    .setting.flag = &verbose,
+    .description = strtext("Write debugging output to stdout")
+  },
+
+  { .word = "no-daemon",
+    .letter = 'n',
+    .setting.flag = &no_daemon,
+    .description = strtext("Remain a foreground process")
+  },
+END_OPTION_TABLE(programOptions)
+
+/******************************************************************************
+ * error handling
+ */
+
+static void api_cleanExit(void) {
+  if (brlapi_fd>=0)
+  {
+    close(brlapi_fd);
+    brlapi_fd=-1;
+  }
+}
+
+/* dumps errors which are fatal to brlapi only */
+static void fatal_brlapi_errno(const char *msg, const char *fmt, ...) {
+  brlapi_perror(msg);
+  if (fmt) {
+    va_list va;
+    va_start(va,fmt);
+    vfprintf(stderr,fmt,va);
+    va_end(va);
+  }
+  api_cleanExit();
+}
+
+static void exception_handler(int error, brlapi_packetType_t type, const void *packet, size_t size) {
+  char str[0X100];
+  brlapi_strexception(str,0X100, error, type, packet, size);
+  fprintf(stderr, "xbrlapi: BrlAPI exception: %s\nDisconnecting from brlapi\n", str);
+  api_cleanExit();
+}
+
+/* dumps errors which are fatal to the whole xbrlapi */
+static void fatal_errno(const char *msg, const char *fmt, ...) {
+  perror(msg);
+  if (fmt) {
+    va_list va;
+    va_start(va,fmt);
+    vfprintf(stderr,fmt,va);
+    va_end(va);
+  }
+  exit(PROG_EXIT_FATAL);
+}
+
+static void fatal(const char *fmt, ...) {
+  if (fmt) {
+    va_list va;
+    va_start(va,fmt);
+    vfprintf(stderr,fmt,va);
+    va_end(va);
+  }
+  exit(PROG_EXIT_FATAL);
+}
+
+/******************************************************************************
+ * brlapi handling
+ */
+
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b))? (a): (b))
+#endif /* MIN */
+
+static void clipboardContentChanged(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len);
+
+static int tobrltty_init(char *auth, char *host) {
+  brlapi_connectionSettings_t settings;
+  unsigned int x,y;
+  settings.host=host;
+  settings.auth=auth;
+  static int had_succeeded;
+  brlapi_param_clientPriority_t priority;
+  brlapi_param_retainDots_t dots;
+
+  if ((brlapi_fd = brlapi_openConnection(&settings,&settings))<0)
+  {
+    if (!had_succeeded)
+    {
+      /* This is the first attempt to connect to BRLTTY, and it failed.
+       * Return the error immediately to the user, to provide feedback to users
+       * running xbrlapi by hand, but not fill logs, eat battery, spam
+       * 127.0.0.1 with reconnection attempts.
+       */
+      fatal_brlapi_errno("openConnection",gettext("cannot connect to braille devices daemon brltty at %s\n"),settings.host);
+      exit(PROG_EXIT_FATAL);
+    }
+    return 0;
+  }
+  /* We achieved connecting to BRLTTY.  If BRLTTY dies later on, we will
+   * silently try to reconnect to it.  */
+  had_succeeded = 1;
+
+  if (brlapi_getDisplaySize(&x,&y)<0)
+  {
+    fatal_brlapi_errno("getDisplaySize",NULL);
+    return 0;
+  }
+
+  if (x == 0)
+  {
+    /* Braille device not initialized yet */
+    api_cleanExit();
+    return 0;
+  }
+
+  brlapi_setExceptionHandler(exception_handler);
+
+  /* Our output is really not very interesting */
+  priority = 10;
+  brlapi_setParameter(BRLAPI_PARAM_CLIENT_PRIORITY, 0, BRLAPI_PARAMF_LOCAL, &priority, sizeof(priority));
+
+  /* We prefer to get translated keypresses */
+  dots = 0;
+  brlapi_setParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &dots, sizeof(dots));
+
+  /* X already has some clipboard content */
+  if (clipboardData)
+    brlapi_setParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardData, strlen(clipboardData));
+
+  /* We want to monitor clipboard changes */
+  brlapi_watchParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardContentChanged, NULL, NULL, 0);
+
+  return 1;
+}
+
+static int getXVTnb(void);
+
+static void getVT(void) {
+  char *path = getenv("WINDOWPATH");
+  char *vtnr = getenv("XDG_VTNR");
+  int vtno = -1;
+  if (!path && !vtnr)
+    /* Workaround for old xinit/xdm/gdm/kdm */
+    vtno = getXVTnb();
+
+  if (path || vtnr || vtno == -1) {
+    if (brlapi_enterTtyModeWithPath(NULL,0,NULL)<0)
+    {
+      fatal_brlapi_errno("geTtyPath",gettext("cannot get tty\n"));
+      return;
+    }
+  } else {
+    if (brlapi_enterTtyMode(vtno,NULL)<0)
+    {
+      fatal_brlapi_errno("enterTtyMode",gettext("cannot get tty %d\n"),vtno);
+      return;
+    }
+  }
+
+  if (brlapi_ignoreAllKeys()<0)
+  {
+    fatal_brlapi_errno("ignoreAllKeys",gettext("cannot ignore keys\n"));
+    return;
+  }
+#ifdef CAN_SIMULATE_KEY_PRESSES
+  /* All X keysyms with any modifier */
+  brlapi_keyCode_t cmd = BRLAPI_KEY_TYPE_SYM;
+  if (brlapi_acceptKeys(brlapi_rangeType_type, &cmd, 1))
+  {
+    fatal_brlapi_errno("acceptKeys",NULL);
+    return;
+  }
+  cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SHIFT;
+  if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
+  {
+    fatal_brlapi_errno("acceptKeys",NULL);
+    return;
+  }
+  cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_UPPER;
+  if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
+  {
+    fatal_brlapi_errno("acceptKeys",NULL);
+    return;
+  }
+  cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_CONTROL;
+  if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
+  {
+    fatal_brlapi_errno("acceptKeys",NULL);
+    return;
+  }
+  cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_META;
+  if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
+  {
+    fatal_brlapi_errno("acceptKeys",NULL);
+    return;
+  }
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+}
+
+static char *last_name;
+
+static void api_setLastName(void) {
+  if (!last_name) return;
+  if (brlapi_writeText(0,last_name)<0) {
+    brlapi_perror("writeText");
+    fprintf(stderr,gettext("xbrlapi: cannot write window name %s\n"),last_name);
+  }
+}
+
+static void api_setName(const char *wm_name) {
+  if (brlapi_fd<0) return;
+
+  debugf("%s got focus\n",wm_name);
+  if (last_name) {
+    if (!strcmp(wm_name,last_name)) return;
+    free(last_name);
+  }
+  if (!(last_name=strdup(wm_name))) fatal_errno("strdup(wm_name)",NULL);
+  api_setLastName();
+}
+
+static int last_win;
+
+static void api_setLastFocus(void)
+{
+  if (brlapi_setFocus(last_win)<0)
+    fatal_brlapi_errno("setFocus",gettext("cannot set focus to %#010x\n"),last_win);
+}
+
+static void api_setFocus(int win) {
+  if (brlapi_fd<0) return;
+  debugf("%#010x (%d) got focus\n",win,win);
+  last_win = win;
+  api_setLastFocus();
+}
+
+/******************************************************************************
+ * X handling
+ */
+
+static const char *Xdisplay;
+static Display *dpy;
+
+static Window curWindow;
+static Atom netWmNameAtom, utf8StringAtom;
+
+static XSelData xselData;
+
+static volatile sig_atomic_t grabFailed;
+
+#ifdef HAVE_ICONV_H
+iconv_t utf8Conv = (iconv_t)(-1);
+#endif /* HAVE_ICONV_H */
+
+#define WINHASHBITS 12
+
+static struct window {
+  Window win;
+  Window root;
+  char *wm_name;
+  struct window *next;
+} *windows[(1<<WINHASHBITS)];
+
+#define WINHASH(win) windows[(win)>>(32-WINHASHBITS)^(win&((1<<WINHASHBITS)-1))]
+
+static void add_window(Window win, Window root, char *wm_name) {
+  struct window *cur;
+  if (!(cur=malloc(sizeof(struct window))))
+    fatal_errno("malloc(struct window)",NULL);
+  cur->win=win;
+  cur->wm_name=wm_name;
+  cur->root=root;
+  cur->next=WINHASH(win);
+  WINHASH(win)=cur;
+}
+
+static struct window *window_of_Window(Window win) {
+  struct window *cur;
+  for (cur=WINHASH(win); cur && cur->win!=win; cur=cur->next);
+  return cur;
+}
+
+static int isRootWindow (Window win) {
+  if (win == PointerRoot) return 1;
+
+  {
+    int count = ScreenCount(dpy);
+     
+    for (int index=0; index<count; index+=1) {
+      if (RootWindow(dpy, index) == win) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int del_window(Window win) {
+  struct window **pred;
+  struct window *cur;
+
+  for (pred=&WINHASH(win); cur = *pred, cur && cur->win!=win; pred=&cur->next);
+
+  if (cur) {
+    *pred=cur->next;
+    free(cur->wm_name);
+    free(cur);
+    return 0;
+  } else return -1;
+}
+
+static int ErrorHandler(Display *dpy, XErrorEvent *ev) {
+  char buffer[128];
+  if (ev->error_code==BadWindow) {
+    grabFailed=1;
+    return 0;
+  }
+#ifdef CAN_SIMULATE_KEY_PRESSES
+  if (ev->request_code == xkb_major_opcode && ev->minor_code == X_kbSetMap) {
+    /* Server refused our Xkb remapping request, probably the buggy version 21, ignore error */
+    fprintf(stderr,gettext("xbrlapi: server refused our mapping request, could not synthesize key\n"));
+    return 0;
+  }
+#endif
+  if (XGetErrorText(dpy, ev->error_code, buffer, sizeof(buffer)))
+    fatal("XGetErrorText");
+  fprintf(stderr,gettext("xbrlapi: X Error %d, %s on display %s\n"), ev->type, buffer, XDisplayName(Xdisplay));
+  fprintf(stderr,gettext("xbrlapi: resource %#010lx, req %u:%u\n"),ev->resourceid,ev->request_code,ev->minor_code);
+  exit(PROG_EXIT_FATAL);
+}
+
+static int getXVTnb(void) {
+  Window root;
+  Atom property;
+  Atom actual_type;
+  int actual_format;
+  unsigned long nitems;
+  unsigned long bytes_after;
+  unsigned char *buf;
+  int vt = -1;
+
+  root=DefaultRootWindow(dpy);
+
+  if ((property=XInternAtom(dpy,"XFree86_VT",False))==None) {
+    fprintf(stderr,gettext("xbrlapi: no XFree86_VT atom\n"));
+    return -1;
+  }
+
+  if (XGetWindowProperty(dpy,root,property,0,1,False,AnyPropertyType,
+    &actual_type, &actual_format, &nitems, &bytes_after, &buf)) {
+    fprintf(stderr,gettext("xbrlapi: cannot get root window XFree86_VT property\n"));
+    return -1;
+  }
+
+  if (nitems<1) {
+    fprintf(stderr, gettext("xbrlapi: no items for VT number\n"));
+    goto out;
+  }
+  if (nitems>1)
+    fprintf(stderr,gettext("xbrlapi: more than one item for VT number\n"));
+  switch (actual_type) {
+  case XA_CARDINAL:
+  case XA_INTEGER:
+  case XA_WINDOW:
+    switch (actual_format) {
+    case 8:  vt = (*(uint8_t *)buf); break;
+    case 16: vt = (*(uint16_t *)buf); break;
+    case 32: vt = (*(uint32_t *)buf); break;
+    default: fprintf(stderr, gettext("xbrlapi: bad format for VT number\n")); goto out;
+    }
+    break;
+  default: fprintf(stderr, gettext("xbrlapi: bad type for VT number\n")); goto out;
+  }
+out:
+  if (!XFree(buf)) fatal("XFree(VTnobuf)");
+  return vt;
+}
+
+static int grabWindow(Window win,int level) {
+#ifdef DEBUG
+  char spaces[level+1];
+#endif /* DEBUG */
+
+  grabFailed=0;
+  if (!XSelectInput(dpy,win,PropertyChangeMask|FocusChangeMask|SubstructureNotifyMask) || grabFailed)
+    return 0;
+
+#ifdef DEBUG
+  memset(spaces,' ',level);
+  spaces[level]='\0';
+  debugf("%sgrabbed %#010lx\n",spaces,win);
+#endif /* DEBUG */
+  return 1;
+}
+
+static char *getWindowTitle(Window win) {
+  int wm_name_size=32;
+  Atom actual_type;
+  int actual_format;
+  unsigned long nitems,bytes_after;
+  unsigned char *wm_name=NULL;
+  char *ret;
+
+  do {
+    if (XGetWindowProperty(dpy,win,netWmNameAtom,0,wm_name_size,False,
+	/*XA_STRING*/AnyPropertyType,&actual_type,&actual_format,&nitems,&bytes_after,
+	&wm_name)) {
+      wm_name = NULL;
+      break; /* window disappeared or not available */
+    }
+    wm_name_size+=bytes_after;
+    if (!bytes_after) break;
+    if (!XFree(wm_name)) fatal("tempo_XFree(wm_name)");
+  } while (1);
+  if (!wm_name) do {
+    if (XGetWindowProperty(dpy,win,XA_WM_NAME,0,wm_name_size,False,
+	/*XA_STRING*/AnyPropertyType,&actual_type,&actual_format,&nitems,&bytes_after,
+	&wm_name))
+      return NULL; /* window disappeared */
+    if (wm_name_size >= nitems + 1) break;
+    wm_name_size += bytes_after + 1;
+    if (!XFree(wm_name)) fatal("tempo_XFree(wm_name)");
+  } while (1);
+  if (actual_type==None) {
+    XFree(wm_name);
+    return NULL;
+  }
+  wm_name[nitems++] = 0;
+  ret = strdup((char *) wm_name);
+  XFree(wm_name);
+  debugf("type %ld name %s len %ld\n",actual_type,ret,nitems);
+#ifdef HAVE_ICONV_H
+  {
+    if (actual_type == utf8StringAtom && utf8Conv != (iconv_t)(-1)) {
+      char *ret2;
+      size_t input_size, output_size;
+      char *input, *output;
+
+      input_size = nitems;
+      input = ret;
+      output_size = nitems * MB_CUR_MAX;
+      output = ret2 = malloc(output_size);
+      if (iconv(utf8Conv, &input, &input_size, &output, &output_size) == -1) {
+	free(ret2);
+      } else {
+	free(ret);
+	ret = realloc(ret2, nitems * MB_CUR_MAX - output_size);
+	debugf("-> %s\n",ret);
+      }
+    }
+  }
+#endif /* HAVE_ICONV_H */
+  return ret;
+}
+
+static int grabWindows(Window win,int level) {
+  Window root,parent,*children;
+  unsigned int nchildren,i;
+  int res=1;
+
+  if (!grabWindow(win,level)) return 1; /* window disappeared */
+
+  if (!XQueryTree(dpy,win,&root,&parent,&children,&nchildren)) return 0;
+
+  add_window(win,root,getWindowTitle(win));
+
+  if (!children) return 1;
+
+  for (i=0;i<nchildren;i++)
+    if (children[i] && !grabWindows(children[i],level+1)) {
+      res=0;
+      break;
+    }
+
+  if (!XFree(children)) fatal("XFree(children)");
+  return res;
+}
+
+static void setName(const struct window *window) {
+  if (!window->wm_name) {
+    if (window->win!=window->root)
+      api_setName("window without name");
+  } else api_setName(window->wm_name);
+}
+
+static void setFocus(Window win) {
+  curWindow=win;
+  api_setFocus((uint32_t)win);
+
+  if (!quiet) {
+    struct window *window = window_of_Window(win);
+
+    if (window) {
+      setName(window);
+    } else {
+      fprintf(stderr, gettext("xbrlapi: didn't grab window %#010lx but got focus\n"), win);
+      api_setName(isRootWindow(win)? "root window": "unnamed window");
+    }
+  }
+}
+
+#ifdef CAN_SIMULATE_KEY_PRESSES
+static int tryModifiers(KeyCode keycode, unsigned int *modifiers, unsigned int modifiers_try, KeySym keysym) {
+  KeySym keysymRet;
+  unsigned int modifiersRet;
+  if (!XkbLookupKeySym(dpy, keycode, modifiers_try, &modifiersRet, &keysymRet))
+    return 0;
+  if (keysymRet != keysym)
+    return 0;
+  *modifiers |= modifiers_try;
+  return 1;
+}
+
+static void ignoreServerKeys(void) {
+  brlapi_range_t range = {
+    .first = BRLAPI_KEY_FLG(ControlMask|Mod1Mask),
+    .last  = BRLAPI_KEY_FLG(ControlMask|Mod1Mask)|~BRLAPI_KEY_FLAGS_MASK,
+  };
+  if (brlapi_ignoreKeyRanges(&range, 1))
+  {
+    fatal_brlapi_errno("ignoreKeyRanges",NULL);
+    return;
+  }
+}
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+
+static void clipboardContentChanged(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len) {
+  free(clipboardData);
+  clipboardData = strndup(data, len);
+  debugf("new clipboard content from BrlAPI: '%s'\n", (const char *) clipboardData);
+  if (dpy)
+    XSelSet(dpy, &xselData);
+}
+
+static void XClipboardContentChanged(const char *data, unsigned long size) {
+  free(clipboardData);
+  if (data) {
+    clipboardData = strndup(data, size);
+    brlapi_setParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardData, size);
+    debugf("new clipboard content from X: '%s'\n", (const char *) clipboardData);
+  } else
+    clipboardData = NULL;
+}
+
+
+static void toX_f(const char *display) {
+  Window root;
+  XEvent ev;
+  int i;
+  int X_fd;
+  fd_set readfds;
+  int maxfd;
+#ifdef CAN_SIMULATE_KEY_PRESSES
+  int res;
+  brlapi_keyCode_t code;
+  unsigned int keysym, keycode, modifiers, next_modifiers = 0;
+  Bool haveXTest;
+  int eventBase, errorBase, majorVersion, minorVersion;
+  XkbDescPtr xkb = NULL;
+  XkbMapChangesRec changes = { .changed = XkbKeyTypesMask|XkbKeySymsMask };
+  int oneGroupType[XkbNumKbdGroups] = { XkbOneLevelIndex };
+  Status status;
+  int last_remap_keycode = -1, remap_keycode;
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+
+  Xdisplay = display;
+  if (!Xdisplay) Xdisplay=getenv("DISPLAY");
+  if (!(dpy=XOpenDisplay(Xdisplay))) fatal(gettext("cannot connect to display %s\n"),Xdisplay);
+
+  if (!XSetErrorHandler(ErrorHandler)) fatal(gettext("strange old error handler\n"));
+
+#ifdef CAN_SIMULATE_KEY_PRESSES
+  haveXTest = XTestQueryExtension(dpy, &eventBase, &errorBase, &majorVersion, &minorVersion);
+
+  {
+    int foo;
+    int major = XkbMajorVersion, minor = XkbMinorVersion;
+    if (!XkbLibraryVersion(&major, &minor))
+      fatal(gettext("Incompatible XKB library\n"));
+    if (!XkbQueryExtension(dpy, &foo, &foo, &foo, &major, &minor))
+      fatal(gettext("Incompatible XKB server support\n"));
+    if (!XQueryExtension(dpy, "XKEYBOARD", &xkb_major_opcode, &foo, &foo))
+      fatal(gettext("Could not get XKB major opcode\n"));
+  }
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+
+  XSelInit(dpy, &xselData);
+
+  if (clipboardData)
+    XSelSet(dpy, &xselData);
+
+  X_fd = XConnectionNumber(dpy);
+
+  if (brlapi_fd>=0)
+  {
+    getVT();
+#ifdef CAN_SIMULATE_KEY_PRESSES
+    ignoreServerKeys();
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+  }
+  netWmNameAtom = XInternAtom(dpy,"_NET_WM_NAME",False);
+  utf8StringAtom = XInternAtom(dpy,"UTF8_STRING",False);
+
+#if defined(HAVE_NL_LANGINFO) && defined(HAVE_ICONV_H)
+  {
+    char *localCharset = nl_langinfo(CODESET);
+    if (strcmp(localCharset, "UTF-8")) {
+      char buf[strlen(localCharset) + 10 + 1];
+      snprintf(buf, sizeof(buf), "%s//TRANSLIT", localCharset);
+      if ((utf8Conv = iconv_open(buf, "UTF-8")) == (iconv_t)(-1))
+        utf8Conv = iconv_open(localCharset, "UTF-8");
+    }
+  }
+#endif /* defined(HAVE_NL_LANGINFO) && defined(HAVE_ICONV_H) */
+
+  for (i=0;i<ScreenCount(dpy);i++) {
+    root=RootWindow(dpy,i);
+    if (!grabWindows(root,0)) fatal(gettext("cannot grab windows on screen %d\n"),i);
+  }
+
+  {
+    Window win;
+    int revert_to;
+    if (!XGetInputFocus(dpy,&win,&revert_to))
+      fatal(gettext("failed to get first focus\n"));
+    setFocus(win);
+  }
+  while(1) {
+    struct timeval timeout={.tv_sec=1,.tv_usec=0};
+    XFlush(dpy);
+    FD_ZERO(&readfds);
+    if (brlapi_fd>=0)
+      FD_SET(brlapi_fd, &readfds);
+    FD_SET(X_fd, &readfds);
+    maxfd = brlapi_fd>=0 && X_fd<brlapi_fd ? brlapi_fd+1 : X_fd+1;
+    /* Try to reconnect to brlapi every second while disconnected */
+    if (select(maxfd,&readfds,NULL,NULL,brlapi_fd<=0?&timeout:NULL)<0)
+      fatal_errno("select",NULL);
+    if (FD_ISSET(X_fd,&readfds))
+
+    while (XPending(dpy)) {
+      if ((i=XNextEvent(dpy,&ev)))
+	fatal("XNextEvent: %d\n",i);
+
+      if (!XSelProcess(dpy, &xselData, &ev, clipboardData, XClipboardContentChanged))
+      switch (ev.type) {
+      /* focus events */
+      case FocusIn:
+	switch (ev.xfocus.detail) {
+	case NotifyAncestor:
+	case NotifyInferior:
+	case NotifyNonlinear:
+	case NotifyPointerRoot:
+	case NotifyDetailNone:
+	  setFocus(ev.xfocus.window); break;
+	} break;
+      case FocusOut:
+	/* ignore
+	switch (ev.xfocus.detail) {
+	case NotifyAncestor:
+	case NotifyInferior:
+	case NotifyNonlinear:
+	case NotifyPointerRoot:
+	case NotifyDetailNone:
+	printf("win %#010lx lost focus\n",ev.xfocus.window);
+	break;
+	}
+	*/
+	break;
+
+      /* create & destroy events */
+      case CreateNotify: {
+      /* there's a race condition here : a window may get the focus or change
+       * its title between it is created and we achieve XSelectInput on it */
+	Window win = ev.xcreatewindow.window;
+	struct window *window;
+	if (!grabWindow(win,0)) break; /* window already disappeared ! */
+	debugf("win %#010lx created\n",win);
+	if (!(window = window_of_Window(ev.xcreatewindow.parent))) {
+	  fprintf(stderr,gettext("xbrlapi: didn't grab parent of %#010lx\n"),win);
+	  add_window(win,None,getWindowTitle(win));
+	} else add_window(win,window->root,getWindowTitle(win));
+      } break;
+      case DestroyNotify:
+	debugf("win %#010lx destroyed\n",ev.xdestroywindow.window);
+	if (del_window(ev.xdestroywindow.window))
+	  debugf("destroy: didn't grab window %#010lx\n",ev.xdestroywindow.window);
+	break;
+
+      /* Property change: WM_NAME ? */
+      case PropertyNotify:
+	if (ev.xproperty.atom==XA_WM_NAME ||
+	    (netWmNameAtom != None && ev.xproperty.atom == netWmNameAtom)) {
+	  Window win = ev.xproperty.window;
+	  debugf("WM_NAME property of %#010lx changed\n",win);
+	  struct window *window;
+	  if (!(window=window_of_Window(win))) {
+	    fprintf(stderr,gettext("xbrlapi: didn't grab window %#010lx\n"),win);
+	    add_window(win,None,getWindowTitle(win));
+	  } else {
+	    if (window->wm_name)
+	      if (!XFree(window->wm_name)) fatal(gettext("XFree(wm_name) for change"));
+	    if ((window->wm_name=getWindowTitle(win))) {
+	      if (!quiet && win==curWindow)
+		api_setName(window->wm_name);
+	    } else fprintf(stderr,gettext("xbrlapi: window %#010lx changed to NULL name\n"),win);
+	  }
+	}
+	break;
+      case MappingNotify:
+	XRefreshKeyboardMapping(&ev.xmapping);
+	break;
+      /* ignored events */
+      case UnmapNotify:
+      case MapNotify:
+      case MapRequest:
+      case ReparentNotify:
+      case ConfigureNotify:
+      case GravityNotify:
+      case ConfigureRequest:
+      case CirculateNotify:
+      case CirculateRequest:
+      case ClientMessage:
+	break;
+
+      /* "shouldn't happen" events */
+      default: fprintf(stderr,gettext("xbrlapi: unhandled event type: %d\n"),ev.type); break;
+      }
+    }
+    if (brlapi_fd>=0) {
+#ifdef CAN_SIMULATE_KEY_PRESSES
+     if (haveXTest && FD_ISSET(brlapi_fd,&readfds)) {
+      while (((res = brlapi_readKey(0, &code))==1)) {
+	switch (code & BRLAPI_KEY_TYPE_MASK) {
+	  case BRLAPI_KEY_TYPE_CMD:
+	    switch (code & BRLAPI_KEY_CODE_MASK) {
+              {
+                unsigned int modifier;
+
+              case BRLAPI_KEY_CMD_SHIFT:
+                modifier = ShiftMask;
+                goto doModifier;
+
+              case BRLAPI_KEY_CMD_UPPER:
+                modifier = LockMask;
+                goto doModifier;
+
+              case BRLAPI_KEY_CMD_CONTROL:
+                modifier = ControlMask;
+                goto doModifier;
+
+              case BRLAPI_KEY_CMD_META:
+                modifier = Mod1Mask;
+                goto doModifier;
+
+              doModifier:
+                switch (code & BRLAPI_KEY_FLG_TOGGLE_MASK) {
+                  case 0:
+                    next_modifiers ^= modifier;
+                    break;
+
+                  case BRLAPI_KEY_FLG_TOGGLE_ON:
+                    next_modifiers |= modifier;
+                    break;
+
+                  case BRLAPI_KEY_FLG_TOGGLE_OFF:
+                    next_modifiers &= ~modifier;
+                    break;
+
+                  default:
+                  case BRLAPI_KEY_FLG_TOGGLE_ON | BRLAPI_KEY_FLG_TOGGLE_OFF:
+                    break;
+                }
+
+                break;
+              }
+
+	      default:
+		fprintf(stderr, "xbrlapi: %s: %" BRLAPI_PRIxKEYCODE "\n",
+			gettext("unexpected cmd"), code);
+		break;
+	    }
+	    break;
+	  case BRLAPI_KEY_TYPE_SYM:
+	    modifiers = ((code & BRLAPI_KEY_FLAGS_MASK) >> BRLAPI_KEY_FLAGS_SHIFT) & 0xFF;
+	    keysym = code & BRLAPI_KEY_CODE_MASK;
+	    keycode = XKeysymToKeycode(dpy,keysym);
+	    remap_keycode = -1;
+	    if (keycode == NoSymbol) {
+	      debugf(gettext("xbrlapi: Couldn't translate keysym %08X to keycode.\n"),keysym);
+	      goto needRemap;
+	    }
+
+	    {
+	      static const unsigned int tryTable[] = {
+		0,
+		ShiftMask,
+		Mod2Mask,
+		Mod3Mask,
+		Mod4Mask,
+		Mod5Mask,
+		ShiftMask|Mod2Mask,
+		ShiftMask|Mod3Mask,
+		ShiftMask|Mod4Mask,
+		ShiftMask|Mod5Mask,
+		0
+	      };
+	      const unsigned int *try = tryTable;
+
+	      do {
+		if (tryModifiers(keycode, &modifiers, *try, keysym)) goto foundModifiers;
+	      } while (*++try);
+
+	      debugf(gettext("xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"),keycode,keysym);
+	    }
+
+	  needRemap:
+	    {
+	      /* Try tofind an unassigned keycode to remap it temporarily to the requested keysym. */
+	      xkb = XkbGetMap(dpy,XkbKeyTypesMask|XkbKeySymsMask,XkbUseCoreKbd);
+	      /* Start from big keycodes, usually unassigned. */
+	      for (i = xkb->max_key_code;
+		   i >= xkb->min_key_code && (XkbKeyNumGroups(xkb,i) != 0 || i == last_remap_keycode);
+		   i--)
+		;
+	      if (i < xkb->min_key_code) {
+		fprintf(stderr,gettext("xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"),keysym);
+		goto abortRemap;
+	      }
+	      remap_keycode = keycode = i;
+	      next_modifiers = modifiers = 0;
+
+	      /* Remap this keycode. */
+	      changes.first_key_sym = keycode;
+	      changes.num_key_syms = 1;
+	      if ((status = XkbChangeTypesOfKey(xkb,keycode,1,XkbGroup1Mask,oneGroupType,&changes))) {
+		debugf("Error while changing client keymap: %d\n", status);
+		goto abortRemap;
+	      }
+	      XkbKeySymEntry(xkb,keycode,0,0) = keysym;
+	      if (!XkbChangeMap(dpy,xkb,&changes)) {
+		debugf("Error while changing server keymap\n");
+		goto abortRemap;
+	      }
+	      XkbFreeKeyboard(xkb,0,True);
+	      debugf("Remapped keycode %d to keysym %08X\n",keycode,keysym);
+	    }
+
+	  foundModifiers:
+	    debugf("key %08X: (%d,%x,%x)\n", keysym, keycode, next_modifiers, modifiers);
+	    modifiers |= next_modifiers;
+	    next_modifiers = 0;
+	    if (modifiers)
+	      XkbLockModifiers(dpy, XkbUseCoreKbd, modifiers, modifiers);
+	    XTestFakeKeyEvent(dpy,keycode,True,1);
+	    XTestFakeKeyEvent(dpy,keycode,False,1);
+	    if (modifiers)
+	      XkbLockModifiers(dpy, XkbUseCoreKbd, modifiers, 0);
+
+	    /* Remove previous keycode mapping */
+	    if (last_remap_keycode != -1) {
+	      /* Note: since X11 is asynchronous, we should not immediately
+	       * unmap the just-mapped keycode, otherwise when the client
+	       * eventually gets to read the new Xkb state from the server,
+	       * the key might have been synthesized and the keycode unmapped
+	       * already. We just hope the user does not type too fast for the
+	       * application to catch up. */
+	      xkb = XkbGetMap(dpy,XkbKeyTypesMask|XkbKeySymsMask,XkbUseCoreKbd);
+	      changes.first_key_sym = last_remap_keycode;
+	      changes.num_key_syms = 1;
+	      if ((status = XkbChangeTypesOfKey(xkb,last_remap_keycode,0,XkbGroup1Mask,NULL,&changes))) {
+		debugf("Oops, error while restoring client keymap: %d\n", status);
+	      } else {
+		XkbChangeMap(dpy,xkb,&changes);
+		debugf("restored last keycode %d\n", last_remap_keycode);
+	      }
+	      XkbFreeKeyboard(xkb,0,True);
+	    }
+	    XFlush(dpy);
+	    last_remap_keycode = remap_keycode;
+	    break;
+
+	  abortRemap:
+	    XkbFreeKeyboard(xkb, 0, True);
+	    xkb = NULL;
+	    break;
+
+	  default:
+	    fprintf(stderr, "xbrlapi: %s: %" BRLAPI_PRIxKEYCODE "\n",
+		    gettext("unexpected block type"), code);
+	    next_modifiers = 0;
+	    break;
+	}
+      }
+      if (res<0)
+	fatal_brlapi_errno("brlapi_readKey",NULL);
+     }
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+    } else {
+      /* Try to reconnect */
+      if (tobrltty_init(auth,host))
+      {
+	getVT();
+#ifdef CAN_SIMULATE_KEY_PRESSES
+	ignoreServerKeys();
+#endif /* CAN_SIMULATE_KEY_PRESSES */
+	api_setLastName();
+	api_setLastFocus();
+      }
+    }
+  }
+}
+
+/******************************************************************************
+ * main
+ */
+
+static void term_handler(int foo) {
+  api_cleanExit();
+  exit(PROG_EXIT_SUCCESS);
+}
+
+int
+main (int argc, char *argv[]) {
+  {
+    const CommandLineDescriptor descriptor = {
+      .options = &programOptions,
+      .applicationName = "xbrlapi",
+
+      .usage = {
+        .purpose = strtext("Augment an X session by supporting input typed on the braille device, showing the title of the focused window on the braille display, and switching braille focus to it."),
+      }
+    };
+
+    PROCESS_OPTIONS(descriptor, argc, argv);
+  }
+
+  signal(SIGTERM,term_handler);
+  signal(SIGINT,term_handler);
+#ifdef SIGHUP
+  signal(SIGHUP,term_handler);
+#endif /* SIGHUP */
+#ifdef SIGQUIT
+  signal(SIGQUIT,term_handler);
+#endif /* SIGQUIT */
+#ifdef SIGPIPE
+  signal(SIGPIPE,term_handler);
+#endif /* SIGPIPE */
+
+  tobrltty_init(auth,host);
+
+  if (!no_daemon) {
+    pid_t child = fork();
+    if (child == -1)
+      fatal_errno("failed to fork", NULL);
+
+    if (child)
+      exit(PROG_EXIT_SUCCESS);
+
+    if (setsid() == -1)
+      fatal_errno("failed to create background session", NULL);
+  }
+
+  toX_f(xDisplay);
+
+  return PROG_EXIT_SUCCESS;
+}
diff --git a/Programs/xsel.c b/Programs/xsel.c
new file mode 100644
index 0000000..e09d7b3
--- /dev/null
+++ b/Programs/xsel.c
@@ -0,0 +1,118 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 2019-2023 by Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#include "prologue.h"
+
+#include <string.h>
+
+#include "xsel.h"
+#include <X11/Xatom.h>
+
+#ifdef HAVE_X11_EXTENSIONS_XFIXES_H
+#include <X11/extensions/Xfixes.h>
+#else /* HAVE_X11_EXTENSIONS_XFIXES_H */
+#warning clipboard tracking not supported by this build - check that libxfixes has been installed
+#endif /* HAVE_X11_EXTENSIONS_XFIXES_H */
+
+/* TODO: get initial clipboard value on startup */
+
+void XSelInit(Display *dpy, XSelData *data) {
+  data->sel = XInternAtom(dpy, "CLIPBOARD", False);
+  data->selProp = XInternAtom(dpy, "BRLTTY_CLIPBOARD", False);
+  data->incr = XInternAtom(dpy, "INCR", False);
+  data->utf8 = XInternAtom(dpy,"UTF8_STRING",False);
+  data->targetsAtom = XInternAtom(dpy,"TARGETS",False);
+  data->selWindow = XCreateSimpleWindow(dpy, RootWindow(dpy, DefaultScreen(dpy)), -10, -10, 1, 1, 0, 0, 0);
+
+#ifdef HAVE_X11_EXTENSIONS_XFIXES_H
+  data->haveXfixes = XFixesQueryExtension(dpy, &data->xfixesEventBase, &data->xfixesErrorBase);
+  if (data->haveXfixes) {
+    XFixesSelectSelectionInput(dpy, data->selWindow, data->sel, XFixesSetSelectionOwnerNotifyMask);
+  }
+#endif /* HAVE_X11_EXTENSIONS_XFIXES_H */
+
+}
+
+void XSelSet(Display *dpy, XSelData *data) {
+  XSetSelectionOwner(dpy, data->sel, data->selWindow, CurrentTime);
+  XFlush(dpy);
+}
+
+int XSelProcess(Display *dpy, XSelData *data, XEvent *ev, const char *content, XSelUpdate update) {
+#ifdef HAVE_X11_EXTENSIONS_XFIXES_H
+  if (data->haveXfixes && ev->type == data->xfixesEventBase + XFixesSelectionNotify) {
+    XFixesSelectionNotifyEvent *xfEvent = (XFixesSelectionNotifyEvent *) ev;
+    if (xfEvent->subtype == XFixesSetSelectionOwnerNotify &&
+	xfEvent->selection == data->sel &&
+	xfEvent->owner != None &&
+	xfEvent->owner != data->selWindow) {
+      /* TODO: use TARGETS to support non-utf8 clients */
+      XConvertSelection(dpy, data->sel, data->utf8, data->selProp, data->selWindow, xfEvent->selection_timestamp);
+    }
+    return 1;
+  } else
+#endif /* HAVE_X11_EXTENSIONS_XFIXES_H */
+
+  switch (ev->type) {
+    case SelectionNotify:
+      if (ev->xselection.property != None) {
+	Atom type;
+	int format;
+	unsigned long nitems, size, ignore;
+	unsigned char *prop_ret;
+	XGetWindowProperty(dpy, data->selWindow, data->selProp, 0, 0, False, AnyPropertyType, &type, &format, &nitems, &size, &prop_ret);
+	XFree(prop_ret);
+	if (type == data->incr) {
+	  // large data, but INCR not supported yet
+	} else if (size != 0) {
+	  XGetWindowProperty(dpy, data->selWindow, data->selProp, 0, size, False, AnyPropertyType, &type, &format, &nitems, &ignore, &prop_ret);
+
+	  update((char*) prop_ret, size);
+
+	  XFree(prop_ret);
+	  XDeleteProperty(dpy, data->selWindow, data->selProp);
+	}
+      }
+      return 1;
+    case SelectionClear:
+      update(NULL, 0);
+      return 1;
+    case SelectionRequest: {
+      XSelectionEvent sev;
+      XSelectionRequestEvent *srev = (XSelectionRequestEvent*)&ev->xselectionrequest;
+      if (content && srev->target == data->utf8) {
+	XChangeProperty(dpy, srev->requestor, srev->property, data->utf8, 8, PropModeReplace, (unsigned char*) content, strlen(content));
+	sev.property = srev->property;
+      } else if (srev->target == data->targetsAtom) {
+	Atom targets[] = { data->targetsAtom, data->utf8 };
+	XChangeProperty(dpy, srev->requestor, srev->property, XA_ATOM, 32, PropModeReplace, (unsigned char*) targets, sizeof(targets)/sizeof(*targets));
+	sev.property = srev->property;
+      } else {
+	sev.property = None;
+      }
+      sev.type = SelectionNotify;
+      sev.requestor = srev->requestor;
+      sev.selection = srev->selection;
+      sev.target = srev->target;
+      sev.time = srev->time;
+      XSendEvent(dpy, srev->requestor, True, NoEventMask, (XEvent *) &sev);
+      return 1;
+    }
+  }
+  return 0;
+}
diff --git a/README b/README
new file mode 100644
index 0000000..2268ad4
--- /dev/null
+++ b/README
@@ -0,0 +1,321 @@
+BRLTTY is a background process (daemon) providing access to the Linux/Unix
+console (when in text mode) for a blind person using a refreshable braille
+display.
+
+Version 6.6, Jul 2023
+
+Copyright (C) 1995-2023 by The BRLTTY Developers.
+
+Web Page: http://brltty.app/
+
+BRLTTY is free software. It comes with ABSOLUTELY NO WARRANTY.
+
+BRLTTY is placed under the terms of the GNU Lesser General Public License
+[LGPL] as published by the Free Software Foundation; see the file LICENSE-LGPL
+for details. Version 2.1 (or any later version) of the LGPL may be used when 
+redistributing and/or modifying this software. This statement applies to all 
+the files contained within this directory structure.
+
+This software is maintained by: Dave Mielke <dave@mielke.cc>
+
+The members of The BRLTTY Team are:
+
+   Dave Mielke <dave@mielke.cc>
+   Mario Lang <mlang@delysid.org>
+   Nicolas Pitre <nico@fluxnic.net>
+   Stéphane Doyon <s.doyon@videotron.ca>
+
+-------------------------------------------------------------------------------
+
+Contacting Us
+=============
+
+We recommend that you contact us via BRLTTY's mailing list. You can post to the
+list by sending an e-mail to <BRLTTY@brltty.app>. To subscribe, go to the list's
+Information Page at [http://brltty.app/mailman/listinfo/brltty].
+
+If you have any interest in BRLTTY, please drop us a note. We're interested in
+knowing who BRLTTY's users are. Even if all goes well and you have no problems
+with this package, please let us know you're there, and tell us which brand of
+display you use. If you have problems, we'll be happy to help. All your
+comments, suggestions, criticisms, questions, and contributions are welcome!
+
+We offer a special invitation to the developers of braille displays. It's our
+goal to support as many models as possible within the Linux/Unix environment.
+While we always do as much as we can toward this end, we'd be even more
+effective with your help. The most important thing we need is the details
+regarding the communication protocol(s) of your braille display(s). If you
+prefer to contact us privately for this sort of thing then we invite you to
+send e-mail to any member(s) of The BRLTTY Team (see above).
+
+-------------------------------------------------------------------------------
+
+Introduction for Those New to Refreshable Braille
+=================================================
+
+There are two common ways in which blind people access computers. 
+
+The first, and more widely known, is synthesized speech. While having many
+advantages, e.g. speed for reading plain text, speech does have its drawbacks.
+Speech output generally gives little information about formatting, making
+tables, spreadsheets, etc. difficult to use. It can also be difficult to use
+speech output with particularly technical material containing lots of symbols
+(though many determined people do use it for such things).
+
+The other solution, which attempts to answer some of these problems, is Braille
+output. A soft (or refreshable) Braille display typically consists of a single
+line of 20, 40 or 80 characters, each made up of a matrix of four rows and two
+columns of dots. Each dot is individually driven by a separate motor, making
+the whole assembly extremely expensive.
+
+A soft Braille display is connected to the PC by a serial, parallel, or USB
+port. Software on the PC drives the display, reproducing a rectangle of the
+screen image (which we shall call the window) in Braille. Buttons on the
+Braille display itself are used to send signals back to the software,
+instructing it to move the window around the screen, or to perform some other
+specialized function.
+
+Using a soft Braille display with a 40- or 80-character line, it is quite easy
+for a blind user to appreciate the format of information on the screen, as well
+as to read and edit on-line Braille documents (a concept not widely enough
+utilized).
+
+-------------------------------------------------------------------------------
+
+Introduction to BRLTTY
+======================
+
+While soft Braille displays have been used for many years under MS-DOS, and are
+now being used under Windows, it seems that they haven't been used on Unix
+consoles too much. This could well be because blind people have been able to
+access Unix systems through accessible terminals. With the advent of PC-based
+Unix systems, e.g. Linux, the need has become evident.
+
+BRLTTY attempts to fill this gap. It runs as a background process, possibly
+started at boot-time, and allows a refreshable Braille user to access text mode
+applications directly from a Unix console. Since BRLTTY runs as a background
+process, it gives the user complete freedom of choice regarding applications
+and development tools.
+
+-------------------------------------------------------------------------------
+
+Features
+========
+
+*  Full implementation of the usual screen review facilities.
+*  Choice between `block', `underline', or `no' cursor.
+*  Optional `underline' to indicate specially highlighted text.
+*  Optional use of `blinking' (rates individually settable) for cursor, special highlighting underline,
+   and/or capital letters.
+*  Screen freezing for leisurely review.
+*  `Intelligent' cursor routing, allowing easy fetching of cursor within text
+   editors, web browsers, etc., without moving ones hands from the Braille
+   display.
+*  A cut & paste function which is particularly useful for copying long file
+   names, copying text between virtual terminals, entering complicated
+   commands, etc.
+*  Table driven in-line contracted braille (English and French provided).
+*  Support for multiple braille codes.
+*  Ability to identify an unknown character.
+*  Ability to inspect character highlighting.
+*  An on-line help facility.
+*  A preferences menu.
+*  Basic speech support.
+*  Modular design allowing relatively easy addition of drivers for other
+   Braille displays, or even (hopefully) porting to other Unix-like platforms.
+*  An application programming interface.
+
+-------------------------------------------------------------------------------
+
+Currently Supported Hardware
+============================
+
+BRLTTY has been tested on:
+
+*  a variety of Intel-based PCs (desktops, servers, laptops, PDAs) 
+*  an Alpha workstation
+*  a StrongARM based Netwinder
+*  several Linux kernels (1.2.13 and beyond)
+*  all major Linux distributions (including Red Hat, Fedora, Debian, Ubuntu, Slackware, SuSE)
+*  Solaris/Sparc (release 7 and beyond)
+*  Solaris/Intel (release 9 and beyond)
+*  OpenBSD/Intel (release 3.4 and beyond)
+*  FreeBSD/Intel (release 5.1 and beyond)
+*  NetBSD/Intel (release 1.6 and beyond)
+*  Windows 95/98/XP
+*  MS-DOS
+*  Mac OS X (in conjunction with a supplied patch for the screen program)
+*  Android/ARM (4.0 and beyond)
+
+The following braille displays are supported:
+
+-  Albatross [46/80]
+-  Alva [ABT(3nn), Delphi(4nn), Satellite(5nn), Braille System 40,
+         Braille Controller 640/680, Easy Link 12]
+-  Baum [BrailleConnect 12/24/32/40/64/80, Brailliant 24/32/40/64/80, Conny 12,
+         DM80 Plus, EcoVario 24/32/40/64/80, Inka, NLS eReader Zoomax,
+         Orbit Reader 20/40, PocketVario 24, Pronto! V3 18/40, Pronto! V4 18/40,
+         RBT 40/80, Refreshabraille 18, SuperVario 32/40/64/80, Vario 40/80,
+         VarioConnect 12/24/32/40/64/80, VarioPro 40/64/80, VarioUltra 20/32/40]
+-  BrailComm [III]
+-  BrailleLite [18/40/M20/M40]
+-  BrailleMemo [Pocket (16), Smart (16), 32, 40]
+-  BrailleNote [18/32, Apex]
+-  Braudi
+-  BrlAPI
+-  Canute [360 (40x9)]
+-  Cebra [20/40/60/80/100/120/140]
+-  CombiBraille [25/45/85]
+-  DotPad
+-  EcoBraille [20/40/80]
+-  EuroBraille [AzerBraille, Clio, Esys, Iris, NoteBraille, Scriba]
+-  FrankAudiodata [B2K84]
+-  FreedomScientific [Focus 1 44/70/84, Focus 2 40/80, Focus Blue 14/40/80,
+                      PAC Mate 20/40]
+-  HandyTech [Modular 20/40/80, Modular Evolution 64/88, Modular Connect 88,
+              Active Braille, Active Braille S, Active Star 40,
+              Actilino, Activator,
+              Basic Braille 16/20/32/40/48/64/80, Braillino,
+              Braille Wave, Easy Braille, Braille Star 40/80,
+              Connect Braille 40, Bookworm]
+-  Hedo [ProfiLine, MobilLine]
+-  HIMS [Braille Sense, SyncBraille, Braille Edge,
+         Smart Beetle, QBrailleXL]
+-  HumanWare [Brailliant BI 14/32/40, Brailliant BI 20X/40X,
+              Brailliant B 80, BrailleNote Touch, BrailleOne,
+              APH Chameleon 20, APH Mantis Q40, NLS eReader]
+-  Inceptor [BrailleMe (20)]
+-  Iris
+-  Libbraille
+-  LogText [32]
+-  MDV [MB208, MB248, MB408L, MB408L+, Lilli Blu]
+-  Metec [BD-40]
+-  MiniBraille [20]
+-  MultiBraille [MB125CR, MB145CR, MB185CR]
+-  NinePoint
+-  Papenmeier [Compact 486, Compact/Tiny, IB 80 CR Soft, 2D Lite (plus),
+               2D Screen Soft, EL 80, EL 2D 40/66/80, EL 40/66/70/80 S,
+               EL 40/60/80 C, EL 2D 80 S, EL 40 P, EL 80 II, Elba 20/32,
+               Trio 40/Elba20/Elba32, Live 20/40]
+-  Pegasus [20/27/40/80]
+-  Seika [3/4/5 (40), 80, Mini (16)]
+-  TechniBraille
+-  TSI [Navigator 20/40/80, PowerBraille 40/65/80]
+-  VideoBraille [40]
+-  VisioBraille [20/40]
+-  Voyager [44/70, Part232 (serial adapter), BraillePen/EasyLink]
+
+The following speech synthesizers are supported:
+
+-  Alva [Delphi(4nn)]
+-  Android [text to speech engine]
+-  BrailleLite
+-  CombiBraille
+-  eSpeak [text to speech engine]
+-  eSpeak-NG [text to speech engine]
+-  ExternalSpeech [runs /usr/local/bin/externalspeech]
+-  Festival [text to speech engine]
+-  FestivalLite [text to speech engine]
+-  GenericSay [pipes to /usr/local/bin/say]
+-  Mikropuhe [text to speech engine]
+-  SpeechDispatcher [text to speech server]
+-  Swift [text to speech engine]
+-  Theta [text to speech engine]
+-  ViaVoice [text to speech engine]
+
+The ability to add a new Braille display depends on the level of cooperation
+from its manufacturer in providing programming information.
+
+-------------------------------------------------------------------------------
+
+Layout of the Archive
+=====================
+
+BRLTTY is distributed as a single GNU compressed tar file named
+
+   brltty-<release>.tar.gz
+
+where `<release>' is the release number. When the archive is unpacked, there
+should be a subdirectory called `Documents' which contains all of the general BRLTTY
+documentation including the manual in various formats, and the list of
+Frequently Asked Questions (FAQ).
+
+The source files for the main, device-independent core of BRLTTY are in the
+top-level directory. There is one subdirectory for each Braille display type,
+containing display-specific source files and documentation.
+
+Finally, some Braille definition tables, along with tools to manipulate them,
+are available in the `Tables' subdirectory.
+
+-------------------------------------------------------------------------------
+
+Building the Package
+====================
+
+Building BRLTTY is a four step process. The first is to run `./autogen` to
+setup your build enviroment for the first time. You typically only need to do
+this once. The second is to run `./configure' to prepare the build environment
+for your operating system, and to customize it for your particular requirement.
+The third is to run `make' to compile and link BRLTTY and its drivers. The
+fourth is to run `make install' to copy all the needed files into their proper
+locations.
+
+    ./autogen
+    ./configure
+    make
+    make install
+
+Before configuring BRLTTY, you may wish to check out what choices you have
+regarding its customization. Invoking `./configure --help' will show you what
+options are available. Although the defaults are adequate for most environments
+and requirements, and although many things can be specified at run-time, there
+are a couple of configuration options worth mentioning here. To build BRLTTY
+with a specific driver built-in (usually only necessary when preparing BRLTTY
+for use on a boot disk), use the `--with-braille-driver=' option. To set the
+default device to the one which your display is usually connected to, use the
+`--with-braille-device=' option.
+
+For information specific to a particular driver, please see the `README' file
+in the corresponding subdirectory. Finally, see the `Documents' subdirectory
+for the manual as well as a few other interesting literary creations.
+
+-------------------------------------------------------------------------------
+
+RedHat Package Manager
+======================
+
+BRLTTY is also distributed in the RPM (RedHat Package Manager) format. The 
+following files are available:
+
+    brltty-<release>-<version>.src.rpm
+    brltty-<release>-<version>.i386.rpm
+
+To build and install BRLTTY from scratch:
+
+    Download the .src.rpm file.
+    Install the source with:   rpm -ivh brltty-<release>-<version>.src.rpm
+    Build and install it with: rpm -bi brltty-<release>-<version>
+
+If your system is an x86 then you don't need to be concerned with building
+BRLTTY since we provide prebuilt binaries for these platforms. Just do:
+
+    Download the .i386.rpm file.
+    Install or upgrade it with: rpm -Uvh brltty-<release>-<version>.i386.rpm
+
+-------------------------------------------------------------------------------
+
+Executing BRLTTY
+================
+
+If you compiled BRLTTY with a braille driver built-in, simply invoking `brltty'
+should start it with all defaults. Alternatively, you might need to specify a
+Braille driver with the `-b' option. `brltty -h' displays a brief summary of
+all available options.
+
+You may use a configuration file for most options as well. See the example
+configuration file `brltty.conf' in the `Documents' subdirectory.
+
+And don't forget to review the notes for your Braille display. See the `README'
+file in its driver's subdirectory.
+
+-------------------------------------------------------------------------------
diff --git a/Tables.brailleback/README b/Tables.brailleback/README
new file mode 100644
index 0000000..ad2dbb7
--- /dev/null
+++ b/Tables.brailleback/README
@@ -0,0 +1,59 @@
+Device drivers
+==============
+
+| Device marketing name         | Brltty driver code | Device name pattern | Key table          |
+| ----------------------------- | ------------------ | ------------------- | ------------------ |
+| Harpo BraillePen              | vo                 | EL12-               | vo/bp.ktb          |
+| Eurobraille Esys              | eu                 | Esys-               | eu/esys-small.ktb  |
+|                               |                    |                     | eu/esys-medium.ktb |
+|                               |                    |                     | eu/esys-large.ktb  |
+| Freedom Scientific Focus Blue | fs                 | Focus 40 BT         | fs/focus_small.ktb |
+|                               |                    | Focus 14 BT         | fs/focus_small.ktb |
+| Humanware Brailliant          | hw                 | Brailliant BT       | hw/mb1.ktb         |
+|                               |                    |                     | hw/mb2.ktb         |
+| HIMS                          | hm                 | Hansone             | [1]                |
+|                               |                    | HansoneXL           | [1]                |
+|                               |                    | BrailleSense        | hm/qwerty.ktb      |
+|                               |                    |                     | hm/pan.ktb         |
+|                               |                    |                     | hm/scroll.ktb      |
+|                               |                    | BrailleEDGE         | hm/edge.ktb        |
+|                               |                    | SmartBeetle         | hm/beetle.ktb      |
+| APH Refreshabraille           | bm                 | Refreshabraille     | bm/rb.ktb          |
+| Baum VarioConnect             | bm                 | VarioConnect        | bm/default.ktb [2] |
+|                               |                    |                     | bm/connect.ktb     |
+| Baum VarioUltra               | bm                 | VarioUltra          | bm/default.ktb [2] |
+|                               |                    |                     | bm/ultra.ktb       |
+| Papenmeier Braillex Trio      | pm                 | braillex trio       | pm/trio.ktb        |
+| Alva BC640/BC680              | al                 | Alva BC             | al/bc640.ktb       |
+|                               |                    |                     | al/bc680.ktb       |
+| HandyTech                     | ht                 | Braille Wave        | ht/wave.ktb        |
+|                               |                    | BRW                 | ht/wave.ktb        |
+|                               |                    | Braillino           | ht/brln.ktb        |
+|                               |                    | BL2                 | ht/brln.ktb        |
+|                               |                    | Braille Star 40     | ht/bs40.ktb        |
+|                               |                    | BS4                 | ht/bs40.ktb        |
+|                               |                    | Easy Braille        | ht/easy.ktb        |
+|                               |                    | EBR                 | ht/easy.ktb        |
+|                               |                    | Active Braille      | ht/ab40.ktb        |
+|                               |                    | AB4                 | ht/ab40.ktb        |
+|                               |                    | Basic Braille       | ht/bb.ktb          |
+|                               |                    | BB3                 | ht/bb.ktb          |
+|                               |                    | BB4                 | ht/bb.ktb          |
+|                               |                    | BB6                 | ht/bb.ktb          |
+| Seika Mini Note Taker         | sk                 | TSM                 | sk/ntk.ktb         |
+| Seika Braille Display         | sk                 | TS5                 | sk/bdp.ktb         |
+
+[1] Unknown; need to verify. Probably using the same key tables as BrailleSense.
+[2] Used during initial probe for Baum devices.
+
+Last updated based on Brltty 5.4 key tables.
+
+Notes
+=====
+| HIMS driver | braille.kti | common.kti | f14.kti | f18.kti | left.kti | pan.kti | qwerty.kti | right.kti | scroll.kti |
+| ----------- | ----------- | ---------- | ------- | ------- | -------- | ------- | ---------- | --------- | ---------- |
+| beetle.ktb  | X           | X          | X       |         |          | X       |            |           |            |
+| edge.ktb    | X           | X          |         | X       | X        |         |            | X         | X          |
+| pan.ktb     | X           | X          | X       |         |          | X       |            |           |            |
+| qwerty.ktb  |             | X          |         |         |          |         | X          |           | X          |
+| scroll.ktb  | X           | X          | X       |         | X        |         |            |           | X          |
diff --git a/Tables.brailleback/al/abt_basic.kti b/Tables.brailleback/al/abt_basic.kti
new file mode 100644
index 0000000..4761b73
--- /dev/null
+++ b/Tables.brailleback/al/abt_basic.kti
@@ -0,0 +1,97 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the basic ABT/Delphi keys.
+note The two long keys are named Left and Right.
+note The small keys immediately to their left and right are named Up and Down.
+note The three small keys at the left are named Prog, Home, and Cursor.
+
+
+####################
+# Default Bindings #
+####################
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left LNBEG
+bind Home+!Right LNEND
+bind Cursor+!Left HWINLT
+bind Cursor+!Right HWINRT
+bind Prog+!Left CHRLT
+bind Prog+!Right CHRRT
+
+bind !Up LNUP
+bind !Down LNDN
+bind Home TOP_LEFT
+bind Home+!Up TOP
+bind Home+!Down BOT
+bind Cursor+!Up ATTRUP
+bind Cursor+!Down ATTRDN
+bind Home+Cursor+!Up PRDIFLN
+bind Home+Cursor+!Down NXDIFLN
+bind Prog+Home+!Up PRPROMPT
+
+bind Home+Cursor CSRTRK
+bind Cursor RETURN
+
+bind !RoutingKey1 ROUTE
+bind Prog+Home+!RoutingKey1 DESCCHAR
+bind Home+Cursor+!RoutingKey1 SETLEFT
+
+bind Prog+!RoutingKey1 CLIP_NEW
+bind Home+!RoutingKey1 COPY_RECT
+bind Prog+Home+!Down PASTE
+
+bind Prog HELP
+bind Prog+Home DISPMD
+bind Prog+Cursor PREFMENU
+bind Prog+!Up INFO
+bind Prog+!Down FREEZE
+
+bind !Status1A CAPBLINK
+bind !Status1B CSRVIS
+bind !Status1C CSRBLINK
+bind Cursor+!Status1A SIXDOTS
+bind Cursor+!Status1B CSRSIZE
+bind Cursor+!Status1C SLIDEWIN
+
+bind Home+Cursor+!Left MUTE
+bind Home+Cursor+!Right SAY_LINE
+bind Prog+Home+!Left RESTARTSPEECH
+bind Prog+Home+!Right SAY_BELOW
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind !Up MENU_PREV_ITEM
+bind !Down MENU_NEXT_ITEM
+bind Home+!Up MENU_FIRST_ITEM
+bind Home+!Down MENU_LAST_ITEM
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left PREFLOAD
+bind Home+!Right PREFSAVE
+
+bind Prog PREFMENU
+bind Home MENU_PREV_SETTING
+bind Cursor MENU_NEXT_SETTING
diff --git a/Tables.brailleback/al/abt_extra.kti b/Tables.brailleback/al/abt_extra.kti
new file mode 100644
index 0000000..bec556d
--- /dev/null
+++ b/Tables.brailleback/al/abt_extra.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the extra ABT/Delphi keys.
+note The three small keys at the right are named Cursor2, Home2, and Prog2.
+
diff --git a/Tables.brailleback/al/abt_large.ktb b/Tables.brailleback/al/abt_large.ktb
new file mode 100644
index 0000000..d83eccd
--- /dev/null
+++ b/Tables.brailleback/al/abt_large.ktb
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva ABT 380 / Delphi 480
+
+context default
+include abt_basic.kti
+
+context default
+include abt_extra.kti
+
diff --git a/Tables.brailleback/al/abt_small.ktb b/Tables.brailleback/al/abt_small.ktb
new file mode 100644
index 0000000..9080d94
--- /dev/null
+++ b/Tables.brailleback/al/abt_small.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva ABT 320,340 / Delphi 420,440
+
+context default
+include abt_basic.kti
+
diff --git a/Tables.brailleback/al/bc-etouch.kti b/Tables.brailleback/al/bc-etouch.kti
new file mode 100644
index 0000000..143f6e7
--- /dev/null
+++ b/Tables.brailleback/al/bc-etouch.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There are four ETouch keys, two at each end of the text cell area.
+note * Each is subnamed according to the side it's on (Left, Right),
+note * and according to its position on that side (Rear, Front).
+note * The two Rear keys are identified by two vertical bars.
+note * The two Front keys are identified by one horizontal bar.
diff --git a/Tables.brailleback/al/bc-smartpad.kti b/Tables.brailleback/al/bc-smartpad.kti
new file mode 100644
index 0000000..89844c2
--- /dev/null
+++ b/Tables.brailleback/al/bc-smartpad.kti
@@ -0,0 +1,107 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note * The outer key on the left, identified by three horizontal bars, is subnamed F1.
+note * The inner key on the left, identified by one horizontal bar, is subnamed F2.
+note * The inner key on the right, identified by one vertical bar, is subnamed F3.
+note * The outer key on the right, identified by three vertical bars, is subnamed F4.
+note * The keys in the middle form a five-way directional pad:
+note + The round key in the middle is subnamed Enter.
+note + The short, thin keys that form a square around it are subnamed Left, Right, Up, and Down.
+
+
+####################
+# Default Bindings #
+####################
+
+bind SmartpadLeft KEY_CURSOR_LEFT
+bind SmartpadRight KEY_CURSOR_RIGHT
+bind SmartpadUp KEY_CURSOR_UP
+bind SmartpadDown KEY_CURSOR_DOWN
+bind SmartpadEnter PASTE
+
+#Overridden by Brailleback
+#bind SmartpadF2 TIME
+
+bind SmartpadF1+SmartpadLeft SWITCHVT_PREV
+bind SmartpadF1+SmartpadRight SWITCHVT_NEXT
+bind SmartpadF1+SmartpadEnter KEY_INSERT
+bind SmartpadF1+!RoutingKey1 SWITCHVT
+
+bind SmartpadF2+SmartpadUp KEY_PAGE_UP
+bind SmartpadF2+SmartpadDown KEY_PAGE_DOWN
+bind SmartpadF2+SmartpadLeft KEY_HOME
+bind SmartpadF2+SmartpadRight KEY_END
+bind SmartpadF2+SmartpadEnter KEY_DELETE
+bind SmartpadF2+!RoutingKey1 KEY_FUNCTION
+
+bind SmartpadF3+SmartpadF4 MUTE
+
+bind SmartpadF3+SmartpadRight SAY_LINE
+bind SmartpadF3+SmartpadUp SAY_ABOVE
+bind SmartpadF3+SmartpadDown SAY_BELOW
+bind SmartpadF3+SmartpadEnter SPKHOME
+
+bind SmartpadF4+SmartpadLeft SAY_SLOWER
+bind SmartpadF4+SmartpadRight SAY_FASTER
+bind SmartpadF4+SmartpadDown SAY_SOFTER
+bind SmartpadF4+SmartpadUp SAY_LOUDER
+bind SmartpadF4+SmartpadEnter AUTOSPEAK
+
+bind SmartpadF1+SmartpadF2+SmartpadEnter RESTARTBRL
+bind SmartpadF3+SmartpadF4+SmartpadEnter RESTARTSPEECH
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by Brailleback
+#context menu
+
+#bind SmartpadF1 PREFMENU
+#bind SmartpadF2 MENU_PREV_LEVEL
+#bind SmartpadF4 PREFLOAD
+
+#bind SmartpadUp MENU_PREV_ITEM
+#bind SmartpadDown MENU_NEXT_ITEM
+#bind SmartpadLeft MENU_PREV_SETTING
+#bind SmartpadRight MENU_NEXT_SETTING
+#bind SmartpadEnter PREFSAVE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# Back key
+bind SmartpadF1 KEY_FUNCTION+108
+# Home key
+bind SmartpadF2 KEY_FUNCTION+109
+# Recent apps key
+bind SmartpadF3 KEY_FUNCTION+110
+# Notifications bar
+bind SmartpadF4 KEY_FUNCTION+111
+
+# Activate currently focused item.
+bind SmartpadEnter ROUTE+127
+
+# Long press currently focused item. This display handles auto-repeat on its
+# own so we can't use long press of SmartpadEnter for this command.
+bind ThumbHome+SmartpadEnter ROUTE+255
+
diff --git a/Tables.brailleback/al/bc-thumb.kti b/Tables.brailleback/al/bc-thumb.kti
new file mode 100644
index 0000000..b103fac
--- /dev/null
+++ b/Tables.brailleback/al/bc-thumb.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note * The Home key is identified by a vertical bar in the middle.
+note * The Left key is identified by a vertical bar near its left edge.
+note * The Right key is identified by a vertical bar near its right edge.
+note * The Up key is identified by a horizontal bar along its top edge.
+note * The Down key is identified by a horizontal bar along its bottom edge.
diff --git a/Tables.brailleback/al/bc.kti b/Tables.brailleback/al/bc.kti
new file mode 100644
index 0000000..ce9a413
--- /dev/null
+++ b/Tables.brailleback/al/bc.kti
@@ -0,0 +1,127 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+bind ETouchLeftRear+ETouchRightRear HELP
+bind ETouchLeftRear+ETouchRightFront LEARN
+bind ETouchLeftFront+ETouchRightRear INFO
+bind ETouchLeftFront+ETouchRightFront PREFMENU
+
+bind ThumbUp+!ThumbDown CSRTRK+on
+bind ThumbDown+!ThumbUp CSRTRK+off
+bind ThumbHome RETURN
+bind ThumbLeft+ThumbRight CSRJMP_VERT
+
+bind ThumbUp LNUP
+bind ThumbDown LNDN
+bind ThumbLeft FWINLT
+bind ThumbRight FWINRT
+
+bind ThumbHome+ThumbUp PRDIFLN
+bind ThumbHome+ThumbDown NXDIFLN
+bind ThumbHome+ThumbLeft FWINLTSKIP
+bind ThumbHome+ThumbRight FWINRTSKIP
+
+bind ThumbLeft+ThumbUp TOP_LEFT
+bind ThumbLeft+ThumbDown BOT_LEFT
+bind ThumbRight+ThumbUp ATTRUP
+bind ThumbRight+ThumbDown ATTRDN
+
+bind ETouchLeftRear+ThumbUp PRPROMPT
+bind ETouchLeftRear+ThumbDown NXPROMPT
+bind ETouchLeftFront+ThumbUp PRPGRPH
+bind ETouchLeftFront+ThumbDown NXPGRPH
+bind ETouchRightRear+ThumbUp PRSEARCH
+bind ETouchRightRear+ThumbDown NXSEARCH
+
+bind ETouchLeftRear LNBEG
+bind ETouchRightRear LNEND
+bind ETouchLeftFront CHRLT
+bind ETouchRightFront CHRRT
+
+bind RoutingKey1 ROUTE
+
+bind ETouchLeftRear+ETouchLeftFront+RoutingKey1 DESCCHAR
+bind RoutingKey1+!RoutingKey1 CLIP_COPY
+bind ETouchLeftRear+RoutingKey1 CLIP_NEW
+bind ETouchLeftFront+RoutingKey1 CLIP_ADD
+bind ETouchRightRear+RoutingKey1 COPY_LINE
+bind ETouchRightFront+RoutingKey1 COPY_RECT
+bind ETouchRightRear+ETouchRightFront PASTE
+
+bind ThumbLeft+RoutingKey1 PRINDENT
+bind ThumbRight+RoutingKey1 NXINDENT
+bind ThumbUp+RoutingKey1 PRDIFCHAR
+bind ThumbDown+RoutingKey1 NXDIFCHAR
+bind ThumbHome+RoutingKey1 SETLEFT
+
+bind ETouchLeftRear+ETouchLeftFront+ETouchRightRear FREEZE
+bind ETouchLeftRear+ETouchRightRear+ETouchRightFront DISPMD
+
+bind ThumbLeft+ETouchLeftRear CSRVIS
+bind ThumbLeft+ETouchLeftFront ATTRVIS
+bind ThumbLeft+ETouchRightFront SIXDOTS
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+map Control CONTROL
+map Alt META
+bind Enter KEY_ENTER
+bind Windows KEY_ESCAPE
+
+assign chord Space+
+include ../chords.kti
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by Brailleback
+#context menu
+
+#bind ThumbLeft FWINLT
+#bind ThumbRight FWINRT
+#bind ThumbUp MENU_PREV_ITEM
+#bind ThumbDown MENU_NEXT_ITEM
+#bind ETouchLeftRear MENU_FIRST_ITEM
+#bind ETouchLeftFront MENU_LAST_ITEM
+#bind ETouchRightRear MENU_PREV_SETTING
+#bind ETouchRightFront MENU_NEXT_SETTING
+
+bind ThumbHome PREFMENU
+bind ETouchLeftRear+ETouchLeftFront PREFLOAD
+bind ETouchRightRear+ETouchRightFront PREFSAVE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord Space
+include ../android-chords.kti
diff --git a/Tables.brailleback/al/bc640.ktb b/Tables.brailleback/al/bc640.ktb
new file mode 100644
index 0000000..5022012
--- /dev/null
+++ b/Tables.brailleback/al/bc640.ktb
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva BC640, BC624
+
+include bc-etouch.kti
+
+note There's a Smartpad in front of the text cell area.
+include bc-smartpad.kti
+
+note The five Thumb keys on the front, from left to right, are subnamed:
+note * Left, Up, Home, Down, Right.
+include bc-thumb.kti
+
+include bc.kti
diff --git a/Tables.brailleback/al/bc680.ktb b/Tables.brailleback/al/bc680.ktb
new file mode 100644
index 0000000..9bb9d37
--- /dev/null
+++ b/Tables.brailleback/al/bc680.ktb
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva BC680
+
+include bc-etouch.kti
+
+note There are two Smartpads in front of the text cell area.
+include bc-smartpad.kti
+
+note There's a group of five Thumb keys at each end of the front.
+note * The outer key of each group is subnamed Home.
+note * The four inner keys of each group, from left to right, are subnamed:
+note * Left, Up, Down, Right.
+include bc-thumb.kti
+
+include bc.kti
diff --git a/Tables.brailleback/al/el.ktb b/Tables.brailleback/al/el.ktb
new file mode 100644
index 0000000..40554e7
--- /dev/null
+++ b/Tables.brailleback/al/el.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Optelec EasyLink 12 Touch
+
+include ../bp/all.kti
diff --git a/Tables.brailleback/al/sat_common.kti b/Tables.brailleback/al/sat_common.kti
new file mode 100644
index 0000000..1640d22
--- /dev/null
+++ b/Tables.brailleback/al/sat_common.kti
@@ -0,0 +1,98 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the basic Satellite keys.
+note The two long, flat keys are named Up and Down.
+note The two long, bent keys are named Left and Right.
+note The two round keys are named Home and Cursor.
+note The row of keys immediately behind the braille cells is named RoutingKey1,
+note * and the row of keys further back is named RoutingKey2.
+note The keypad at the left side of the top is named SpeechPad,
+note * and the one at the right side of the top is named NavPad.
+note * They can be swapped via the internal menu.
+note * The four inner keys of each are subnamed Left, Right, Up, and Down.
+note * The two outer keys of each are subnamed F1 and F2.
+
+
+####################
+# Default Bindings #
+####################
+
+note Enter the internal menu by pressing:
+note * SpeechPadF1 + SpeechPadF2 + NavPadF1 + NavPadF2
+bind SpeechPadF1+SpeechPadF2+NavPadF1+NavPadF2 NOOP
+
+include sat_speech.kti
+include sat_nav.kti
+
+bind !Up LNUP
+bind !Down LNDN
+bind Home+!Up TOP_LEFT
+bind Home+!Down BOT_LEFT
+bind Cursor+!Up TOP
+bind Cursor+!Down BOT
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left LNBEG
+bind Home+!Right LNEND
+bind Cursor+!Left FWINLTSKIP
+bind Cursor+!Right FWINRTSKIP
+
+bind RoutingKey1 ROUTE
+bind RoutingKey2 DESCCHAR
+
+bind RoutingKey1+!RoutingKey1 CLIP_COPY
+bind RoutingKey2+!RoutingKey2 CLIP_APPEND
+
+bind Home+!RoutingKey2 SETMARK
+bind Home+!RoutingKey1 GOTOMARK
+bind Cursor+!RoutingKey2 PRINDENT
+bind Cursor+!RoutingKey1 NXINDENT
+
+bind !Status1A CSRVIS
+bind !Status2A SKPIDLNS
+bind !Status1B ATTRVIS
+bind !Status2B DISPMD
+bind !Status1C CAPBLINK
+bind !Status2C SKPBLNKWINS
+
+bind Home BACK
+bind Cursor HOME
+bind Home+Cursor CSRTRK
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind !Up MENU_PREV_ITEM
+bind !Down MENU_NEXT_ITEM
+bind Home+!Up MENU_FIRST_ITEM
+bind Home+!Down MENU_LAST_ITEM
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left PREFLOAD
+bind Home+!Right PREFSAVE
+
+bind Home MENU_PREV_SETTING
+bind Cursor MENU_NEXT_SETTING
+bind Home+Cursor PREFMENU
diff --git a/Tables.brailleback/al/sat_large.ktb b/Tables.brailleback/al/sat_large.ktb
new file mode 100644
index 0000000..cbe770c
--- /dev/null
+++ b/Tables.brailleback/al/sat_large.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva Satellite 570,584
+
+include sat_tumblers.kti
+include sat_common.kti
diff --git a/Tables.brailleback/al/sat_nav.kti b/Tables.brailleback/al/sat_nav.kti
new file mode 100644
index 0000000..1d0279d
--- /dev/null
+++ b/Tables.brailleback/al/sat_nav.kti
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for the Alva Satellite nav keypad.
+
+bind NavPadF1+!Up PRDIFLN
+bind NavPadF1+!Down NXDIFLN
+bind NavPadF2+!Up ATTRUP
+bind NavPadF2+!Down ATTRDN
+
+bind NavPadF1+!Left CHRLT
+bind NavPadF1+!Right CHRRT
+bind NavPadF2+!Left HWINLT
+bind NavPadF2+!Right HWINRT
+
+bind NavPadF1+!RoutingKey1 CLIP_NEW
+bind NavPadF1+!RoutingKey2 CLIP_ADD
+bind NavPadF2+!RoutingKey1 COPY_RECT
+bind NavPadF2+!RoutingKey2 COPY_LINE
+
+bind NavPadLeft PREFMENU
+bind NavPadRight INFO
+bind NavPadF1+NavPadLeft FREEZE
+bind NavPadF1+NavPadRight SIXDOTS
+bind NavPadF2+NavPadLeft PASTE
+bind NavPadF2+NavPadRight CSRJMP_VERT
+
+bind NavPadUp PRPROMPT
+bind NavPadDown NXPROMPT
+bind NavPadF1+NavPadUp PRPGRPH
+bind NavPadF1+NavPadDown NXPGRPH
+bind NavPadF2+NavPadUp PRSEARCH
+bind NavPadF2+NavPadDown NXSEARCH
+
+bind NavPadF1 HELP
+bind NavPadF2 LEARN
+bind NavPadF1+NavPadF2 RESTARTBRL
+
diff --git a/Tables.brailleback/al/sat_small.ktb b/Tables.brailleback/al/sat_small.ktb
new file mode 100644
index 0000000..4a7194a
--- /dev/null
+++ b/Tables.brailleback/al/sat_small.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva Satellite 544
+
+include sat_common.kti
diff --git a/Tables.brailleback/al/sat_speech.kti b/Tables.brailleback/al/sat_speech.kti
new file mode 100644
index 0000000..2248e83
--- /dev/null
+++ b/Tables.brailleback/al/sat_speech.kti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for the Alva Satellite speech keypad.
+
+bind SpeechPadLeft MUTE
+bind SpeechPadRight SAY_LINE
+bind SpeechPadUp SAY_ABOVE
+bind SpeechPadDown SAY_BELOW
+
+bind SpeechPadF2+SpeechPadLeft SAY_SLOWER
+bind SpeechPadF2+SpeechPadRight SAY_FASTER
+bind SpeechPadF2+SpeechPadDown SAY_SOFTER
+bind SpeechPadF2+SpeechPadUp SAY_LOUDER
+
+bind SpeechPadF1 SPKHOME
+bind SpeechPadF2 AUTOSPEAK
+bind SpeechPadF1+SpeechPadF2 RESTARTSPEECH
+
diff --git a/Tables.brailleback/al/sat_tumblers.kti b/Tables.brailleback/al/sat_tumblers.kti
new file mode 100644
index 0000000..9f021fb
--- /dev/null
+++ b/Tables.brailleback/al/sat_tumblers.kti
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva Satellite models which have tumbler keys.
+note The two three-position, sliding keys are named LeftTumbler and RightTumbler.
+
+bind LeftTumblerLeft CHRLT
+bind LeftTumblerRight CHRRT
+bind RightTumblerLeft LNBEG     
+bind RightTumblerRight LNEND
+
diff --git a/Tables.brailleback/al/voyager.ktb b/Tables.brailleback/al/voyager.ktb
new file mode 100644
index 0000000..e74090d
--- /dev/null
+++ b/Tables.brailleback/al/voyager.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Voyager Protocol Converter
+
+include ../vo/all.kti
diff --git a/Tables.brailleback/android-chords-google.kti b/Tables.brailleback/android-chords-google.kti
new file mode 100644
index 0000000..fb1189d
--- /dev/null
+++ b/Tables.brailleback/android-chords-google.kti
@@ -0,0 +1,141 @@
+# This is google's unified shortcuts for braille displays.
+# Devices without braille dot keys should define the shortcuts based on the keys they have.
+#
+# chord1-chord5, chord7 are required
+# chord6 is currently unused
+# chord8 is optional
+# CUSTOM_KEY are used for android only and start from KEY_FUNCTION+100.
+#
+# Space + 13 is the power-off chord for BraillePen, and so Space + 1378 also has
+# the effect of being a power-off chord, so we avoid using that.
+#
+# chord is Space by default, but can be customized for devices without Space.
+# chord7 and chord8 can be customized for devices that have only six input
+# dots; otherwise, they will be set by default to Dot7 and Dot8.
+
+
+# Next item
+bind \{chord4}+\{chord5}+\{chord8} KEY_FUNCTION+100
+# Previous item
+bind \{chord1}+\{chord2}+\{chord7} KEY_FUNCTION+101
+# Next line
+bind \{chord}+\{chord4} KEY_FUNCTION+102
+# Previous line
+bind \{chord}+\{chord1} KEY_FUNCTION+103
+# Scroll forward
+bind \{chord1}+\{chord3}+\{chord5}+\{chord8} KEY_FUNCTION+104
+# Scroll backward
+bind \{chord2}+\{chord4}+\{chord6}+\{chord7} KEY_FUNCTION+105
+# Top of the page
+bind \{chord}+\{chord1}+\{chord2}+\{chord3} KEY_FUNCTION+106
+# Bottom of the page
+bind \{chord}+\{chord4}+\{chord5}+\{chord6} KEY_FUNCTION+107
+# Back key
+bind \{chord}+\{chord1}+\{chord2} KEY_FUNCTION+108
+# Home key
+bind \{chord}+\{chord1}+\{chord2}+\{chord5} KEY_FUNCTION+109
+# Recent apps key
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord5} KEY_FUNCTION+110
+# Notifications bar
+bind \{chord}+\{chord1}+\{chord3}+\{chord4}+\{chord5} KEY_FUNCTION+111
+# Keyboard help
+bind \{chord1}+\{chord3}+\{chord7}+\{chord8} KEY_FUNCTION+112
+# Next heading
+bind \{chord1}+\{chord2}+\{chord5}+\{chord8} KEY_FUNCTION+113
+# Previous heading
+bind \{chord1}+\{chord2}+\{chord5}+\{chord7} KEY_FUNCTION+114
+# Next control
+bind \{chord1}+\{chord4}+\{chord8} KEY_FUNCTION+115
+# Previous control
+bind \{chord1}+\{chord4}+\{chord7} KEY_FUNCTION+116
+# Next link
+bind \{chord1}+\{chord2}+\{chord3}+\{chord8} KEY_FUNCTION+117
+# Previous link
+bind \{chord1}+\{chord2}+\{chord3}+\{chord7} KEY_FUNCTION+118
+# Incremental Search
+bind \{chord}+\{chord3}+\{chord4} KEY_FUNCTION+119
+# Edit custom label
+bind \{chord}+\{chord1}+\{chord3}+\{chord4}+\{chord8} KEY_FUNCTION+120
+# Switch to next input language
+bind \{chord2}+\{chord4}+\{chord7}+\{chord8} KEY_FUNCTION+121
+# Switch to next output language
+bind \{chord1}+\{chord3}+\{chord5}+\{chord7}+\{chord8} KEY_FUNCTION+122
+# Braille display Settings
+bind \{chord1}+\{chord2}+\{chord7}+\{chord8} KEY_FUNCTION+123
+# TalkBack Settings
+bind \{chord2}+\{chord3}+\{chord4}+\{chord5}+\{chord7}+\{chord8} KEY_FUNCTION+124
+# Quick Settings
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord4}+\{chord5} KEY_FUNCTION+125
+# All apps
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord4} KEY_FUNCTION+126
+# Open TaklBack Menu
+bind \{chord}+\{chord1}+\{chord3}+\{chord4} KEY_FUNCTION+127
+# Delete while editing.
+bind \{chord7} KEY_FUNCTION+128
+# Enter while editing.
+ifVar chord8 bind \{chord8} KEY_FUNCTION+129
+# Turn off braille display
+bind \{chord1}+\{chord2}+\{chord3}+\{chord4}+\{chord5}+\{chord6}+\{chord7}+\{chord8} KEY_FUNCTION+130
+# Previous character
+bind \{chord}+\{chord3} KEY_FUNCTION+131
+# Next character
+bind \{chord}+\{chord6} KEY_FUNCTION+132
+# Previous word
+bind \{chord}+\{chord2} KEY_FUNCTION+133
+# Next word
+bind \{chord}+\{chord5} KEY_FUNCTION+134
+# Previous window
+bind \{chord2}+\{chord4}+\{chord5}+\{chord6}+\{chord7} KEY_FUNCTION+135
+# Next window
+bind \{chord2}+\{chord4}+\{chord5}+\{chord6}+\{chord8} KEY_FUNCTION+136
+# Delete a word
+bind \{chord}+\{chord2}+\{chord7} KEY_FUNCTION+137
+# Toggle voice feedback (mute/unmute)
+bind \{chord1}+\{chord3}+\{chord4}+\{chord7}+\{chord8} KEY_FUNCTION+138
+# Previous reading control
+bind \{chord2}+\{chord3}+\{chord7} KEY_FUNCTION+139
+# Next reading control
+bind \{chord5}+\{chord6}+\{chord8} KEY_FUNCTION+140
+# Move navigation focus backward or adjust reading control up
+bind \{chord3}+\{chord7} KEY_FUNCTION+141
+# Move navigation focus forward or adjust reading control down
+bind \{chord6}+\{chord8} KEY_FUNCTION+142
+# Switch grade
+bind \{chord}+\{chord1}+\{chord2}+\{chord4}+\{chord5} KEY_FUNCTION+143
+# Long press current
+bind \{chord}+\{chord8} KEY_FUNCTION+144
+# Stop reading
+bind \{chord7}+\{chord8} KEY_FUNCTION+145
+# Cut
+bind \{chord}+\{chord1}+\{chord3}+\{chord4}+\{chord6}+\{chord8} KEY_FUNCTION+146
+# Copy
+bind \{chord}+\{chord1}+\{chord4}+\{chord8} KEY_FUNCTION+147
+# Paste
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord6}+\{chord8} KEY_FUNCTION+148
+# Select all
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord4}+\{chord5}+\{chord6}+\{chord8} KEY_FUNCTION+149
+# Select previous character
+bind \{chord}+\{chord3}+\{chord8} KEY_FUNCTION+150
+# Select Next Character
+bind \{chord}+\{chord6}+\{chord8} KEY_FUNCTION+151
+# Select previous word
+bind \{chord}+\{chord2}+\{chord8} KEY_FUNCTION+152
+# Select next word
+bind \{chord}+\{chord5}+\{chord8} KEY_FUNCTION+153
+# Select current to start
+bind \{chord}+\{chord1}+\{chord2}+\{chord3}+\{chord8} KEY_FUNCTION+159
+# Select current to end
+bind \{chord}+\{chord4}+\{chord5}+\{chord6}+\{chord8} KEY_FUNCTION+160
+
+# As the text selection for line granularity movement does not work,
+# we mask off the action of selecting text by line.
+# Select previous line
+# bind \{chord}+\{chord1}+\{chord8} KEY_FUNCTION+154
+# Select next line
+# bind \{chord}+\{chord4}+\{chord8} KEY_FUNCTION+155
+# Toggle auto scroll
+bind \{chord}+\{chord1}+\{chord2}+\{chord4}+\{chord5}+\{chord6} KEY_FUNCTION+156
+# Play or pause media
+bind \{chord}+\{chord7}+\{chord8} KEY_FUNCTION+157
+# Switch to next input method
+bind \{chord}+\{chord1}+\{chord3}+\{chord8} KEY_FUNCTION+158
diff --git a/Tables.brailleback/android-chords.kti b/Tables.brailleback/android-chords.kti
new file mode 100644
index 0000000..50497a9
--- /dev/null
+++ b/Tables.brailleback/android-chords.kti
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign prefix \{chord}Dot7+Dot8
+#Overridden by BrailleBack.
+#bind \{prefix}+Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 GUI_BRL_ACTIONS
+
+bind \{prefix}+\{a} TXTSEL_ALL
+bind \{prefix}+Dot4 TXTSEL_CLEAR
+bind \{prefix}+\{c} HOST_COPY
+bind \{prefix}+\{v} HOST_PASTE
+bind \{prefix}+\{x} HOST_CUT
+
+#Overridden by BrailleBack.
+#bind \{prefix}+\{b} GUI_BACK
+bind \{prefix}+\{h} GUI_HOME
+#Overridden by BrailleBack.
+#bind \{prefix}+\{i} INDICATORS
+#Overridden by BrailleBack.
+#bind \{prefix}+\{m} GUI_APP_MENU
+bind \{prefix}+\{n} GUI_APP_ALERTS
+#Overridden by BrailleBack.
+#bind \{prefix}+\{o} GUI_DEV_OPTIONS
+bind \{prefix}+\{r} GUI_APP_LIST
+bind \{prefix}+\{s} GUI_DEV_SETTINGS
+#Overridden by BrailleBack.
+#bind \{prefix}+\{t} GUI_TITLE
+
+bind \{prefix}+Dot3+Dot6 GUI_AREA_ACTV
+bind \{prefix}+Dot3 GUI_AREA_PREV
+bind \{prefix}+Dot6 GUI_AREA_NEXT
+
+bind \{prefix}+Dot2 GUI_ITEM_PREV
+bind \{prefix}+Dot5 GUI_ITEM_NEXT
+
+bind \{prefix}+Dot2+Dot3 GUI_ITEM_FRST
+bind \{prefix}+Dot5+Dot6 GUI_ITEM_LAST
+
+#Unused by Brailleback
+#bind \{prefix}+Dot2+Dot5 CONTEXT+chrome
+#context chrome Web Page Navigation
+#bind Dot7+Dot8 CONTEXT+default
+
+superimpose meta
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+########################
+# BrailleBack Bindings #
+########################
+
+# chord is Space by default, but can be customized for devices without Space.
+#
+# chord7 and chord8 can be customized for devices that have only six input
+# dots; otherwise, they will be set by default to Dot7 and Dot8.
+
+assignDefault chord Space
+assign chord1 Dot1
+assign chord2 Dot2
+assign chord3 Dot3
+assign chord4 Dot4
+assign chord5 Dot5
+assign chord6 Dot6
+assignDefault chord7 Dot7
+assignDefault chord8 Dot8
+
+include android-chords-google.kti
diff --git a/Tables.brailleback/bm/NLS_Zoomax.ktb b/Tables.brailleback/bm/NLS_Zoomax.ktb
new file mode 100644
index 0000000..c4f12f6
--- /dev/null
+++ b/Tables.brailleback/bm/NLS_Zoomax.ktb
@@ -0,0 +1,107 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NLS eReader Zoomax
+
+note An eight-dot standard braille keyboard is along the back edge of the top.
+note * From left to right, its keys are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+note * The Space key is in between Dot1 and Dot4.
+
+note The two round keys at each end of the front are system keys. From left to right:
+note * the two on the left are S1 and S2, and the two on the right are S3 and S4.
+
+assign space Space
+include navpad.kti
+note There's a rectangular key on either side of the navigation pad.
+note * The one to the left is named BL, and the one to the right is named BR.
+
+bind BL KEY_BACKSPACE
+bind BL+RoutingKey SETLEFT
+
+map S1 GUI
+map S2 CONTROL
+map S3 META
+map S4 ALTGR
+
+bind S1 HELP
+bind S2 TIME
+bind S3 INFO
+bind S4 LEARN
+
+bind BL+Select PREFMENU
+bind BL+Up PREFLOAD
+bind BL+Down PREFSAVE
+
+bind BL+Left PRNBWIN
+bind BL+Right NXNBWIN
+
+bind Select+Left FWINLTSKIP
+bind Select+Right FWINRTSKIP
+
+bind Left+Up TOP_LEFT
+bind Left+Down BOT_LEFT
+
+bind Right+Up TOP
+bind Right+Down BOT
+
+bind S1+Up ATTRUP
+bind S1+Down ATTRDN
+
+bind S2+Up PRDIFLN
+bind S2+Down NXDIFLN
+
+bind S3+Up PRPROMPT
+bind S3+Down NXPROMPT
+
+bind S4+Up PRPGRPH
+bind S4+Down NXPGRPH
+
+bind S1+Left LNBEG
+bind S1+Right LNEND
+
+bind S2+Left CHRLT
+bind S2+Right CHRRT
+
+bind S3+Left HWINLT
+bind S3+Right HWINRT
+
+bind S4+Left PRSEARCH
+bind S4+Right NXSEARCH
+
+bind S1+RoutingKey PRDIFCHAR
+bind S2+RoutingKey NXDIFCHAR
+
+bind S3+RoutingKey PRINDENT
+bind S4+RoutingKey NXINDENT
+
+bind Select+S1 ATTRVIS
+bind Select+S2 CSRVIS
+bind Select+S3 CSRTRK
+bind Select+S4 SIXDOTS
+
+bind BL+S3 DISPMD
+bind BL+S4 FREEZE
+
+bind Left+Right PASTE
+bind S2+S3 CSRJMP_VERT
+
+include display6.kti
+include routing6.kti
+
+assign speech BL+
+include ../speech.kti
diff --git a/Tables.brailleback/bm/b2g.ktb b/Tables.brailleback/bm/b2g.ktb
new file mode 100644
index 0000000..a8b876c
--- /dev/null
+++ b/Tables.brailleback/bm/b2g.ktb
@@ -0,0 +1,47 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NBP B2G
+
+note Press Space while holding Backward to switch to navigation mode.
+note In navigation mode: Backward is F1, Forward is F4, Dots1-6 are display keys 1-6, Dot7 is F2, Dot8 is F3.
+note Press Space while holding Forward to switch to keyboard mode.
+note In keyboard mode: Backward is B9, Forward is B10, Space is B11.
+
+include d6.kti
+include routing6.kti
+
+bind F1 FWINLT
+bind F4 FWINRT
+
+bind B9 FWINLT
+bind B10 FWINRT
+
+assign space B11
+assign press Select
+include navpad.kti
+
+bind B9+RoutingKey SETLEFT
+bind B10+RoutingKey SWITCHVT
+
+bind B11+Up TOP
+bind B11+Down BOT
+bind B11+Left FWINLTSKIP
+bind B11+Right FWINRTSKIP
+bind B11+Select PASTE
+
diff --git a/Tables.brailleback/bm/b9b10.kti b/Tables.brailleback/bm/b9b10.kti
new file mode 100644
index 0000000..8820b5c
--- /dev/null
+++ b/Tables.brailleback/bm/b9b10.kti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note B9 and B10 are the keys immediately to the left and right of the joystick.
+
+map B9 SPACE
+assign space B10
+include joystick.kti
+bind B9+RoutingKey SETLEFT
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord B9
+include ../android-chords.kti
+
+assign chord B10
+include ../android-chords.kti
diff --git a/Tables.brailleback/bm/b9b11b10.kti b/Tables.brailleback/bm/b9b11b10.kti
new file mode 100644
index 0000000..f59f7ea
--- /dev/null
+++ b/Tables.brailleback/bm/b9b11b10.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note B9 and B10 are the keys immediately to the left and right of the joystick.
+note * If either or both are used in a combination that includes at least
+note * one of the Dot1-6 keys while the device is in 8-dot braille mode
+note * then they become the Dot7 and Dot8 keys.
+note B11 is the key between the Dot1 and Dot4 keys.
+
+bind B9 KEY_BACKSPACE
+bind B10 KEY_ENTER
+
+assign space B11
+include joystick.kti
+bind B9+RoutingKey SETLEFT
+bind B10+RoutingKey SWITCHVT
+
+bind B11+Up TOP
+bind B11+Down BOT
+bind B11+Left FWINLTSKIP
+bind B11+Right FWINRTSKIP
+bind B11+Press PASTE
+
diff --git a/Tables.brailleback/bm/command.kti b/Tables.brailleback/bm/command.kti
new file mode 100644
index 0000000..42bdd0b
--- /dev/null
+++ b/Tables.brailleback/bm/command.kti
@@ -0,0 +1,36 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have command keys.
+note The command keys are in the middle, just behind the cursor routing keys,
+note and, from left to right, are named Command1 through Command7.
+
+map Command4 SPACE
+map Command3 DOT1
+map Command2 DOT2
+map Command1 DOT3
+map Command5 DOT4
+map Command6 DOT5
+map Command7 DOT6
+
+bind Command4+Command5 KEY_ENTER
+bind Command4+Command2 KEY_BACKSPACE
+bind Command4+Command6 KEY_TAB
+bind Command4+Command1 KEY_CURSOR_LEFT
+bind Command4+Command7 KEY_CURSOR_RIGHT
+
diff --git a/Tables.brailleback/bm/connect.ktb b/Tables.brailleback/bm/connect.ktb
new file mode 100644
index 0000000..d4e5c5b
--- /dev/null
+++ b/Tables.brailleback/bm/connect.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum VarioConnect / HWG BrailleConnect
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables.brailleback/bm/conny.ktb b/Tables.brailleback/bm/conny.ktb
new file mode 100644
index 0000000..5fc2f03
--- /dev/null
+++ b/Tables.brailleback/bm/conny.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Conny
+
+include display6.kti
+include routing6.kti
+include b9b11b10.kti
diff --git a/Tables.brailleback/bm/d6.kti b/Tables.brailleback/bm/d6.kti
new file mode 100644
index 0000000..83da489
--- /dev/null
+++ b/Tables.brailleback/bm/d6.kti
@@ -0,0 +1,86 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Display2 FWINLT
+bind Display5 FWINRT
+
+bind Display1+Display3 CHRLT
+bind Display4+Display6 CHRRT
+
+bind Display1+Display2+Display3 LNBEG
+bind Display4+Display5+Display6 LNEND
+
+bind Display4 LNUP
+bind Display6 LNDN
+
+bind Display1+Display4 TOP
+bind Display3+Display6 BOT
+
+bind Display2+Display4 TOP_LEFT
+bind Display2+Display6 BOT_LEFT
+
+bind Display5+Display4 PRDIFLN
+bind Display5+Display6 NXDIFLN
+
+bind Display2+Display1 ATTRUP
+bind Display2+Display3 ATTRDN
+
+bind Display2+Display5+Display1+Display4 PRPROMPT
+bind Display2+Display5+Display3+Display6 NXPROMPT
+
+bind Display2+Display5+Display4 PRPGRPH
+bind Display2+Display5+Display6 NXPGRPH
+
+bind Display2+Display4+Display6+Display1 PRSEARCH
+bind Display2+Display4+Display6+Display3 NXSEARCH
+
+bind Display1 CSRTRK+on
+bind Display3 CSRTRK+off
+
+bind Display1+Display6 BACK
+
+bind Display2+Display4+Display6 HOME
+bind Display1+Display3+Display5 SPKHOME
+
+bind Display1+Display3+Display4+Display6 CSRJMP_VERT
+bind Display2+Display5 INFO
+
+bind Display1+Display4+Display5 DISPMD
+bind Display1+Display5 TIME
+bind Display1+Display2+Display4 FREEZE
+bind Display1+Display2+Display5 HELP
+bind Display1+Display3+Display4 PREFMENU
+bind Display1+Display2+Display3+Display4 PASTE
+bind Display1+Display2+Display3+Display5 PREFLOAD
+bind Display2+Display3+Display4 RESTARTSPEECH
+bind Display2+Display3+Display4+Display5 ATTRVIS
+bind Display2+Display4+Display5+Display6 PREFSAVE
+
+bind Display2+Display3+Display5 SIXDOTS+on
+bind Display2+Display3+Display6 SIXDOTS+off
+
+bind Display1+Display4+Display5+Display6 LEARN
+bind Display1+Display2+Display3+Display6 SWITCHVT_NEXT
+bind Display3+Display4+Display5+Display6 SWITCHVT_PREV
+
+bind Display3+Display4 MUTE
+bind Display3+Display5 SAY_LINE
+bind Display3+Display5+Display4 SAY_ABOVE
+bind Display3+Display5+Display6 SAY_BELOW
+bind Display3+Display4+Display6 AUTOSPEAK
+
diff --git a/Tables.brailleback/bm/default.ktb b/Tables.brailleback/bm/default.ktb
new file mode 100644
index 0000000..71bdce5
--- /dev/null
+++ b/Tables.brailleback/bm/default.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum (default)
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables.brailleback/bm/display6.kti b/Tables.brailleback/bm/display6.kti
new file mode 100644
index 0000000..08cee8c
--- /dev/null
+++ b/Tables.brailleback/bm/display6.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There are three display keys at each end of the braille cells.
+note * From top to bottom:
+note + The three at the left are named Display1, Display2, and Display3.
+note + The three at the right are named Display4, Display5, and Display6.
+
+include d6.kti
diff --git a/Tables.brailleback/bm/display7.kti b/Tables.brailleback/bm/display7.kti
new file mode 100644
index 0000000..f1b6ecb
--- /dev/null
+++ b/Tables.brailleback/bm/display7.kti
@@ -0,0 +1,66 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 7 display keys.
+note The display keys are at both ends of the text cell area, just above it.
+note * The two at the left are named Display1 and Display2.
+note * The large key at the right is named Display5.
+note * The two immediately to its left are named Display3 and Display4.
+note * The two immediately to its right are named Display6 and Display7.
+
+bind Display5 RETURN
+bind Display3 LNUP
+bind Display4 LNDN
+bind Display6 FWINLT
+bind Display7 FWINRT
+
+bind Display1 TOP_LEFT
+bind Display2 BOT_LEFT
+
+bind Display5+Display3 PRDIFLN
+bind Display5+Display4 NXDIFLN
+bind Display5+Display6 ATTRUP
+bind Display5+Display7 ATTRDN
+
+bind Display2+Display5 FREEZE
+bind Display2+Display3 PRPROMPT
+bind Display2+Display4 NXPROMPT
+bind Display2+Display6 PRPGRPH
+bind Display2+Display7 NXPGRPH
+
+bind Display1+Display5 CSRTRK
+bind Display1+Display3 DISPMD
+bind Display1+Display4 SIXDOTS
+bind Display1+Display6 ATTRVIS
+bind Display1+Display7 CSRVIS
+
+bind Display1+Display2+Display5 AUTOSPEAK
+bind Display1+Display2+Display3 MUTE
+bind Display1+Display2+Display4 SAY_LINE
+bind Display1+Display2+Display6 SAY_ABOVE
+bind Display1+Display2+Display7 SAY_BELOW
+
+bind Display1+Display2 HELP
+bind Display3+Display4 LEARN
+bind Display6+Display7 PREFMENU
+bind Display3+Display6 PREFLOAD
+bind Display4+Display7 PREFSAVE
+
+bind Display5+Display3+Display6 CSRJMP_VERT
+bind Display5+Display4+Display7 PASTE
+
diff --git a/Tables.brailleback/bm/dm80p.ktb b/Tables.brailleback/bm/dm80p.ktb
new file mode 100644
index 0000000..b5d941a
--- /dev/null
+++ b/Tables.brailleback/bm/dm80p.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum DM 80 Plus
+
+include display7.kti
+include routing7.kti
diff --git a/Tables.brailleback/bm/emulate6.kti b/Tables.brailleback/bm/emulate6.kti
new file mode 100644
index 0000000..2b305d7
--- /dev/null
+++ b/Tables.brailleback/bm/emulate6.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Any display key combination can be emulated by holding \{press}
+note * while typing the corresponding combination of dots 1-6.
+include d6.kti
diff --git a/Tables.brailleback/bm/front10.kti b/Tables.brailleback/bm/front10.kti
new file mode 100644
index 0000000..3ed04bf
--- /dev/null
+++ b/Tables.brailleback/bm/front10.kti
@@ -0,0 +1,63 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 10 front keys.
+note The keys in the upper row on the front are named:
+note * Front1, Front3, Front5, Front7, Front9.
+note The keys in the lower row on the front are named:
+note * Front2, Front4, Front6, Front8, Front10.
+note The keys in the upper row on the back are named:
+note * Back1, Back3, Back5, Back7, Back9.
+note The keys in the lower row on the back are named:
+note * Back2, Back4, Back6, Back8, Back10.
+
+bind Front1 LNUP
+bind Front2 LNDN
+
+bind Front3 LNUP
+bind Front4 LNDN
+
+bind Front5 LNUP
+bind Front6 LNDN
+
+bind Front7 LNUP
+bind Front8 LNDN
+
+bind Front9 LNUP
+bind Front10 LNDN
+
+bind Front5+Front1 TOP_LEFT
+bind Front5+Front2 BOT_LEFT
+bind Front6+Front1 TOP
+bind Front6+Front2 BOT
+
+bind Front5+Front3 PRPROMPT
+bind Front5+Front4 NXPROMPT
+bind Front6+Front3 PRPGRPH
+bind Front6+Front4 NXPGRPH
+
+#bind Front5+Front7 LNUP
+#bind Front5+Front8 LNDN
+#bind Front6+Front7 LNUP
+#bind Front6+Front8 LNDN
+
+bind Front5+Front9 PRDIFLN
+bind Front5+Front10 NXDIFLN
+bind Front6+Front9 ATTRUP
+bind Front6+Front10 ATTRDN
+
diff --git a/Tables.brailleback/bm/front6.kti b/Tables.brailleback/bm/front6.kti
new file mode 100644
index 0000000..90daa5b
--- /dev/null
+++ b/Tables.brailleback/bm/front6.kti
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 6 front keys.
+note The keys in the upper row on the front are named:
+note * Front1, Front3, Front5.
+note The keys in the lower row on the front are named:
+note * Front2, Front4, Front6.
+note The keys in the upper row on the back are named:
+note * Back1, Back3, Back5.
+note The keys in the lower row on the back are named:
+note * Back2, Back4, Back6.
+
diff --git a/Tables.brailleback/bm/horizontal.kti b/Tables.brailleback/bm/horizontal.kti
new file mode 100644
index 0000000..642995b
--- /dev/null
+++ b/Tables.brailleback/bm/horizontal.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have horizontal sensors.
+
+bind !HorizontalSensor ROUTE
+
+bind Display1+!HorizontalSensor CLIP_NEW
+bind Display2+!HorizontalSensor CLIP_ADD
+bind Display4+!HorizontalSensor COPY_LINE
+bind Display5+!HorizontalSensor COPY_RECT
+
+bind Display3+!HorizontalSensor DESCCHAR
+bind Display6+!HorizontalSensor SETLEFT
+
+bind Display2+Display1+!HorizontalSensor PRINDENT
+bind Display2+Display3+!HorizontalSensor NXINDENT
+
+bind Display5+Display4+!HorizontalSensor PRDIFCHAR
+bind Display5+Display6+!HorizontalSensor NXDIFCHAR
+
+bind Display1+Display3+!HorizontalSensor SETMARK
+bind Display4+Display6+!HorizontalSensor GOTOMARK
+
diff --git a/Tables.brailleback/bm/inka.ktb b/Tables.brailleback/bm/inka.ktb
new file mode 100644
index 0000000..489240f
--- /dev/null
+++ b/Tables.brailleback/bm/inka.ktb
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Inka
+
+include display6.kti
+include horizontal.kti
+include vertical.kti
+
+note The switches are off when to the left and on when to the right.
+note * Switch1 (upper-left): disable all sensors.
+note * Switch2 (lower-left): scaled vertical sensor line selection.
+note * Switch3 (upper-right): show selected horizontal sensor (all dots raised).
+note * Switch4 (lower-right): enable braille keyboard.
diff --git a/Tables.brailleback/bm/joystick.kti b/Tables.brailleback/bm/joystick.kti
new file mode 100644
index 0000000..e90f170
--- /dev/null
+++ b/Tables.brailleback/bm/joystick.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault press Press
+note The five joystick motions are named Left, Right, Up, Down, and \{press}.
+include keyboard.kti
diff --git a/Tables.brailleback/bm/keyboard.kti b/Tables.brailleback/bm/keyboard.kti
new file mode 100644
index 0000000..eec31dc
--- /dev/null
+++ b/Tables.brailleback/bm/keyboard.kti
@@ -0,0 +1,84 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map \{space} SPACE
+
+assign chord \{space}+
+include ../chords.kti
+
+bind \{press} RETURN
+include ../nav.kti
+
+ifKey RoutingKey
+bind \{space}+RoutingKey KEY_FUNCTION
+bind \{space}+RoutingKey+!RoutingKey CLIP_APPEND
+
+bind RoutingKey+\{press} DESCCHAR
+bind RoutingKey+Left CLIP_NEW
+bind RoutingKey+Up CLIP_ADD
+bind RoutingKey+Right COPY_LINE
+bind RoutingKey+Down COPY_RECT
+endIf
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by Brailleback
+#context menu
+
+#bind \{press} PREFMENU
+#bind Up MENU_PREV_ITEM
+#bind Down MENU_NEXT_ITEM
+#bind Left MENU_PREV_SETTING
+#bind Right MENU_NEXT_SETTING
+
+#bind \{space} MENU_PREV_LEVEL
+#bind \{space}+\{press} PREFSAVE
+#bind \{space}+Up MENU_FIRST_ITEM
+#bind \{space}+Down MENU_LAST_ITEM
+#bind \{space}+Left FWINLT
+#bind \{space}+Right FWINRT
+
+#context default
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord Space
+include ../android-chords.kti
+
+# Activate the currently focused item.
+bind Press ROUTE+127
+
+# Long press the currently focused item
+# (The joystick press, at least on the Refreshabraille, will not send
+# a key down at key press, but instead the down/up pair are sent
+# in quick succession when the key is released.)
+bind Dot6+Press ROUTE+255
diff --git a/Tables.brailleback/bm/navpad.kti b/Tables.brailleback/bm/navpad.kti
new file mode 100644
index 0000000..8b2f652
--- /dev/null
+++ b/Tables.brailleback/bm/navpad.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault press Select
+note The five navigation pad keys are named Left, Right, Up, Down, and \{press}.
+include keyboard.kti
diff --git a/Tables.brailleback/bm/orbit.ktb b/Tables.brailleback/bm/orbit.ktb
new file mode 100644
index 0000000..f6de220
--- /dev/null
+++ b/Tables.brailleback/bm/orbit.ktb
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Orbit Reader
+
+note A six-dot standard braille keyboard is along the back edge of the top.
+note * From left to right, its keys are: Dot3, Dot2, Dot1 - Dot4, Dot5, Dot6.
+note There's a five-key navigation pad in between Dot1 and Dot4.
+note Space is the long key in the middle, just in front of the navigation pad.
+note Dot7 and Dot8 are the keys immediately to the left and right of Space.
+
+assign space Space
+include navpad.kti
+
+assign press Select
+include emulate6.kti
+include routing.kti
+
+note The rocker to the left of the braille cells emulates the Display2 key,
+note * and the one to their right emulates the Display5 key.
diff --git a/Tables.brailleback/bm/pro.ktb b/Tables.brailleback/bm/pro.ktb
new file mode 100644
index 0000000..ffe7ee0
--- /dev/null
+++ b/Tables.brailleback/bm/pro.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario Pro
+
+include display6.kti
+include wheels.kti
+include status.kti
+include routing6.kti
diff --git a/Tables.brailleback/bm/pronto.ktb b/Tables.brailleback/bm/pronto.ktb
new file mode 100644
index 0000000..08aa489
--- /dev/null
+++ b/Tables.brailleback/bm/pronto.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Pronto!
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables.brailleback/bm/pv.ktb b/Tables.brailleback/bm/pv.ktb
new file mode 100644
index 0000000..7b64a46
--- /dev/null
+++ b/Tables.brailleback/bm/pv.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum PocketVario
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables.brailleback/bm/rb.ktb b/Tables.brailleback/bm/rb.ktb
new file mode 100644
index 0000000..9a8a4a4
--- /dev/null
+++ b/Tables.brailleback/bm/rb.ktb
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Refreshabraille
+
+note B9 is the long key in the middle, just in front of the joystick.
+note Dot7 and Dot8 are the keys immediately to the left and right of B9.
+note B10 is the narrow key between and just behind the six-dot keyboard.
+
+assign press Press
+include emulate6.kti
+include routing6.kti
+
+assign space B9
+include joystick.kti
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord B9
+include ../android-chords.kti
+
+assign chord B10
+include ../android-chords.kti
diff --git a/Tables.brailleback/bm/routing.kti b/Tables.brailleback/bm/routing.kti
new file mode 100644
index 0000000..9086dd2
--- /dev/null
+++ b/Tables.brailleback/bm/routing.kti
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note RoutingKey refers to any of the keys immediately behind the braille cells.
+
+#Overridden by BrailleBack.
+#bind RoutingKey ROUTE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/bm/routing6.kti b/Tables.brailleback/bm/routing6.kti
new file mode 100644
index 0000000..c32b366
--- /dev/null
+++ b/Tables.brailleback/bm/routing6.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have routing keys and 6 display keys.
+
+include routing.kti
+
+bind Display1+RoutingKey CLIP_NEW
+bind Display2+RoutingKey CLIP_ADD
+bind Display4+RoutingKey COPY_LINE
+bind Display5+RoutingKey COPY_RECT
+
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind Display2+RoutingKey+!RoutingKey CLIP_APPEND
+
+bind Display3+RoutingKey DESCCHAR
+bind Display6+RoutingKey SETLEFT
+
+bind Display2+Display1+RoutingKey PRINDENT
+bind Display2+Display3+RoutingKey NXINDENT
+
+bind Display5+Display4+RoutingKey PRDIFCHAR
+bind Display5+Display6+RoutingKey NXDIFCHAR
+
+bind Display1+Display3+RoutingKey SETMARK
+bind Display4+Display6+RoutingKey GOTOMARK
+
+bind Display4+Display5+Display6+RoutingKey SWITCHVT
+
diff --git a/Tables.brailleback/bm/routing7.kti b/Tables.brailleback/bm/routing7.kti
new file mode 100644
index 0000000..dbeb640
--- /dev/null
+++ b/Tables.brailleback/bm/routing7.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have routing keys and 7 display keys.
+
+include routing.kti
+
+bind Display5+!RoutingKey DESCCHAR
+
+bind Display3+!RoutingKey CLIP_NEW
+bind Display4+!RoutingKey CLIP_ADD
+bind Display6+!RoutingKey COPY_LINE
+bind Display7+!RoutingKey COPY_RECT
+
+bind Display5+Display3+!RoutingKey PRINDENT
+bind Display5+Display4+!RoutingKey NXINDENT
+
+bind Display5+Display6+!RoutingKey PRDIFCHAR
+bind Display5+Display7+!RoutingKey NXDIFCHAR
+
+bind Display1+!RoutingKey SETMARK
+bind Display2+!RoutingKey GOTOMARK
+bind Display1+Display2+!RoutingKey SETLEFT
+
diff --git a/Tables.brailleback/bm/status.kti b/Tables.brailleback/bm/status.kti
new file mode 100644
index 0000000..4f0b466
--- /dev/null
+++ b/Tables.brailleback/bm/status.kti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have a status module.
+note The larger controls on the status module, from left to right,
+note are named StatusButton1 through StatusButton4.
+note The smaller controls on the status module, from left to right,
+note are named StatusKey1 through StatusKey4.
+
+bind StatusButton1 HELP
+bind StatusButton2 LEARN
+bind StatusButton3 INFO
+bind StatusButton4 PREFMENU
+
+bind StatusKey1 CSRVIS
+bind StatusKey2 ATTRVIS
+bind StatusKey3 FREEZE
+bind StatusKey4 SIXDOTS
+
diff --git a/Tables.brailleback/bm/sv.ktb b/Tables.brailleback/bm/sv.ktb
new file mode 100644
index 0000000..42a3c3f
--- /dev/null
+++ b/Tables.brailleback/bm/sv.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum SuperVario / HWG Brailliant
+
+include display6.kti
+include routing6.kti
diff --git a/Tables.brailleback/bm/ultra.ktb b/Tables.brailleback/bm/ultra.ktb
new file mode 100644
index 0000000..ff121e7
--- /dev/null
+++ b/Tables.brailleback/bm/ultra.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum VarioUltra
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables.brailleback/bm/v40.ktb b/Tables.brailleback/bm/v40.ktb
new file mode 100644
index 0000000..21757c0
--- /dev/null
+++ b/Tables.brailleback/bm/v40.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario 40
+
+include display6.kti
+include routing6.kti
diff --git a/Tables.brailleback/bm/v80.ktb b/Tables.brailleback/bm/v80.ktb
new file mode 100644
index 0000000..a5e884f
--- /dev/null
+++ b/Tables.brailleback/bm/v80.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario 80
+
+include display6.kti
+include command.kti
+include front10.kti
+include routing6.kti
diff --git a/Tables.brailleback/bm/vertical.kti b/Tables.brailleback/bm/vertical.kti
new file mode 100644
index 0000000..2b8163f
--- /dev/null
+++ b/Tables.brailleback/bm/vertical.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have vertical sensors.
+
+bind !LeftSensor GOTOLINE+toleft
+bind !RightSensor GOTOLINE
+bind !ScaledLeftSensor GOTOLINE+toleft+scaled
+bind !ScaledRightSensor GOTOLINE+scaled
diff --git a/Tables.brailleback/bm/vk.ktb b/Tables.brailleback/bm/vk.ktb
new file mode 100644
index 0000000..b2aff18
--- /dev/null
+++ b/Tables.brailleback/bm/vk.ktb
@@ -0,0 +1,42 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum (variokeys=yes)
+
+bind Display2 FWINLT
+bind Display5 FWINRT
+
+bind Display1 LNUP
+bind Display3 LNDN
+
+bind Display4 KEY_CURSOR_UP
+bind Display6 KEY_CURSOR_DOWN
+
+bind Display1+Display3 HOME
+bind Display2+Display1 TOP_LEFT
+bind Display2+Display3 BOT_LEFT
+
+bind Display1+Display4 PREFMENU
+bind Display1+Display2+Display4 FREEZE
+bind Display1+Display2+Display5 HELP
+bind Display2+Display4 INFO
+bind Display2+Display3+Display4+Display5 CSRTRK
+bind Display1+Display3+Display6 ATTRVIS
+bind Display1+Display2+Display3+Display6 DISPMD
+
+include routing6.kti
diff --git a/Tables.brailleback/bm/wheels.kti b/Tables.brailleback/bm/wheels.kti
new file mode 100644
index 0000000..a8a35e0
--- /dev/null
+++ b/Tables.brailleback/bm/wheels.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have wheels.
+note The wheels on the display module are identified by their ordinal position
+note (first through fourth) from the left.
+
+bind !FirstWheelUp LNUP
+bind !FirstWheelDown LNDN
+bind !FirstWheelPress HOME
+
+bind !SecondWheelUp LNUP
+bind !SecondWheelDown LNDN
+bind !SecondWheelPress HOME
+
+bind !ThirdWheelUp LNUP
+bind !ThirdWheelDown LNDN
+bind !ThirdWheelPress HOME
+
+bind !FourthWheelUp LNUP
+bind !FourthWheelDown LNDN
+bind !FourthWheelPress HOME
+
diff --git a/Tables.brailleback/bp/all.kti b/Tables.brailleback/bp/all.kti
new file mode 100644
index 0000000..0eaea13
--- /dev/null
+++ b/Tables.brailleback/bp/all.kti
@@ -0,0 +1,211 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The six round keys near the back are a braille keyboard.
+note From left to right, they're named: Dot3, Dot2, Dot1, Dot4, Dot5, Dot6.
+note From left to right, the three long function keys are named: Shift, Space, Control.
+note This table maps Shift to Dot7, and Control to Dot8.
+note The joystick between the dot and function keys has five positions named: Up, Down, Left, Right, Enter.
+note The round scroll keys at either end of the braille display are named: Left, Right.
+
+
+####################
+# Default Bindings #
+####################
+
+#Overridden by BrailleBack.
+#ifKey RoutingKey bind RoutingKey ROUTE
+
+bind ScrollLeft+ScrollRight HOME
+bind ScrollLeft FWINLT
+bind ScrollRight FWINRT
+bind ScrollLeft+Space LNUP
+bind ScrollRight+Space LNDN
+bind ScrollLeft+Shift FWINLTSKIP
+bind ScrollRight+Control FWINRTSKIP
+
+bind ScrollLeft+JoystickEnter SETLEFT
+bind ScrollLeft+JoystickLeft CHRLT
+bind ScrollLeft+JoystickRight CHRRT
+bind ScrollLeft+JoystickUp PRDIFLN
+bind ScrollLeft+JoystickDown NXDIFLN
+
+bind ScrollRight+JoystickEnter DESCCHAR
+bind ScrollRight+JoystickLeft HWINLT
+bind ScrollRight+JoystickRight HWINRT
+bind ScrollRight+JoystickUp ATTRUP
+bind ScrollRight+JoystickDown ATTRDN
+
+bind ScrollLeft+ScrollRight+JoystickEnter BACK
+bind ScrollLeft+ScrollRight+JoystickLeft PRPGRPH
+bind ScrollLeft+ScrollRight+JoystickRight NXPGRPH
+bind ScrollLeft+ScrollRight+JoystickUp PRPROMPT
+bind ScrollLeft+ScrollRight+JoystickDown NXPROMPT
+
+bind Shift+JoystickEnter CSRJMP_VERT
+bind Shift+JoystickLeft LNBEG
+bind Shift+JoystickRight LNEND
+bind Shift+JoystickUp TOP
+bind Shift+JoystickDown BOT
+
+bind Control+JoystickLeft CLIP_NEW
+bind Control+JoystickUp CLIP_ADD
+bind Control+JoystickRight COPY_LINE
+bind Control+JoystickDown COPY_RECT
+bind Control+JoystickEnter PASTE
+
+bind Dot1+JoystickEnter DISPMD
+bind Dot1+JoystickLeft DISPMD+off
+bind Dot1+JoystickRight DISPMD+on
+bind Dot1+Dot2+JoystickEnter SKPBLNKWINS
+bind Dot1+Dot2+JoystickLeft SKPBLNKWINS+off
+bind Dot1+Dot2+JoystickRight SKPBLNKWINS+on
+bind Dot1+Dot4+JoystickEnter CSRVIS
+bind Dot1+Dot4+JoystickLeft CSRVIS+off
+bind Dot1+Dot4+JoystickRight CSRVIS+on
+bind Dot1+Dot2+Dot4+JoystickEnter FREEZE
+bind Dot1+Dot2+Dot5+JoystickEnter HELP
+bind Dot2+Dot4+JoystickEnter SKPIDLNS
+bind Dot2+Dot4+JoystickLeft SKPIDLNS+off
+bind Dot2+Dot4+JoystickRight SKPIDLNS+on
+bind Dot1+Dot2+Dot3+JoystickEnter LEARN
+bind Dot1+Dot2+Dot3+Dot4+JoystickEnter PREFMENU
+bind Dot1+Dot2+Dot3+Dot4+JoystickLeft PREFLOAD
+bind Dot1+Dot2+Dot3+Dot4+JoystickRight PREFSAVE
+bind Dot2+Dot3+Dot4+JoystickEnter INFO
+bind Dot2+Dot3+Dot4+Dot5+JoystickEnter CSRTRK
+bind Dot2+Dot3+Dot4+Dot5+JoystickLeft CSRTRK+off
+bind Dot2+Dot3+Dot4+Dot5+JoystickRight CSRTRK+on
+bind Dot1+Dot3+Dot6+JoystickEnter ATTRVIS
+bind Dot1+Dot3+Dot6+JoystickLeft ATTRVIS+off
+bind Dot1+Dot3+Dot6+JoystickRight ATTRVIS+on
+bind Dot2+Dot4+Dot5+Dot6+JoystickEnter SLIDEWIN
+bind Dot2+Dot4+Dot5+Dot6+JoystickLeft SLIDEWIN+off
+bind Dot2+Dot4+Dot5+Dot6+JoystickRight SLIDEWIN+on
+bind Dot2+Dot3+Dot5+JoystickEnter SIXDOTS+on
+bind Dot2+Dot3+Dot6+JoystickEnter SIXDOTS+off
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Shift DOT7
+map Control DOT8
+map Space SPACE
+
+bind Space+Shift KEY_BACKSPACE
+bind Space+Control KEY_ENTER
+bind Space+Shift+Control KEY_ESCAPE
+
+bind Space+Dot1 KEY_CURSOR_LEFT
+bind Space+Dot4 KEY_CURSOR_RIGHT
+bind Space+Dot2 KEY_CURSOR_UP
+bind Space+Dot5 KEY_CURSOR_DOWN
+bind Space+Dot3 KEY_PAGE_UP
+bind Space+Dot6 KEY_PAGE_DOWN
+
+bind Space+Dot1+Dot2 KEY_BACKSPACE
+bind Space+Dot1+Dot4+Dot5 KEY_DELETE
+bind Space+Dot1+Dot5 KEY_END
+bind Space+Dot1+Dot2+Dot5 KEY_HOME
+bind Space+Dot2+Dot4 KEY_INSERT
+bind Space+Dot1+Dot2+Dot3+Dot5 KEY_ENTER
+bind Space+Dot2+Dot3+Dot4+Dot5 KEY_TAB
+bind Space+Dot1+Dot3+Dot4+Dot6 KEY_ESCAPE
+
+bind JoystickEnter ROUTE
+bind JoystickLeft KEY_CURSOR_LEFT
+bind JoystickRight KEY_CURSOR_RIGHT
+bind JoystickUp KEY_CURSOR_UP
+bind JoystickDown KEY_CURSOR_DOWN
+
+bind Space+JoystickEnter KEY_INSERT
+bind Space+JoystickLeft KEY_HOME
+bind Space+JoystickRight KEY_END
+bind Space+JoystickUp KEY_PAGE_UP
+bind Space+JoystickDown KEY_PAGE_DOWN
+
+bind Space+Shift+Dot1 KEY_FUNCTION+0
+bind Space+Shift+Dot1+Dot2 KEY_FUNCTION+1
+bind Space+Shift+Dot1+Dot4 KEY_FUNCTION+2
+bind Space+Shift+Dot1+Dot4+Dot5 KEY_FUNCTION+3
+bind Space+Shift+Dot1+Dot5 KEY_FUNCTION+4
+bind Space+Shift+Dot1+Dot2+Dot4 KEY_FUNCTION+5
+bind Space+Shift+Dot1+Dot2+Dot4+Dot5 KEY_FUNCTION+6
+bind Space+Shift+Dot1+Dot2+Dot5 KEY_FUNCTION+7
+bind Space+Shift+Dot2+Dot4 KEY_FUNCTION+8
+bind Space+Shift+Dot2+Dot4+Dot5 KEY_FUNCTION+9
+bind Space+Shift+Dot1+Dot3 KEY_FUNCTION+10
+bind Space+Shift+Dot1+Dot2+Dot3 KEY_FUNCTION+11
+bind Space+Shift+Dot1+Dot3+Dot4 KEY_FUNCTION+12
+bind Space+Shift+Dot1+Dot3+Dot4+Dot5 KEY_FUNCTION+13
+bind Space+Shift+Dot1+Dot3+Dot5 KEY_FUNCTION+14
+bind Space+Shift+Dot1+Dot2+Dot3+Dot4 KEY_FUNCTION+15
+bind Space+Shift+Dot1+Dot2+Dot3+Dot4+Dot5 KEY_FUNCTION+16
+bind Space+Shift+Dot1+Dot2+Dot3+Dot5 KEY_FUNCTION+17
+bind Space+Shift+Dot2+Dot3+Dot4 KEY_FUNCTION+18
+bind Space+Shift+Dot2+Dot3+Dot4+Dot5 KEY_FUNCTION+19
+
+bind Space+Control+Dot1 SWITCHVT+0
+bind Space+Control+Dot1+Dot2 SWITCHVT+1
+bind Space+Control+Dot1+Dot4 SWITCHVT+2
+bind Space+Control+Dot1+Dot4+Dot5 SWITCHVT+3
+bind Space+Control+Dot1+Dot5 SWITCHVT+4
+bind Space+Control+Dot1+Dot2+Dot4 SWITCHVT+5
+bind Space+Control+Dot1+Dot2+Dot4+Dot5 SWITCHVT+6
+bind Space+Control+Dot1+Dot2+Dot5 SWITCHVT+7
+bind Space+Control+Dot2+Dot4 SWITCHVT+8
+bind Space+Control+Dot2+Dot4+Dot5 SWITCHVT+9
+bind Space+Control+Dot1+Dot3 SWITCHVT+10
+bind Space+Control+Dot1+Dot2+Dot3 SWITCHVT+11
+bind Space+Control+Dot1+Dot3+Dot4 SWITCHVT+12
+bind Space+Control+Dot1+Dot3+Dot4+Dot5 SWITCHVT+13
+bind Space+Control+Dot1+Dot3+Dot5 SWITCHVT+14
+bind Space+Control+Dot1+Dot2+Dot3+Dot4 SWITCHVT+15
+bind Space+Control+Dot1+Dot2+Dot3+Dot4+Dot5 SWITCHVT+16
+bind Space+Control+Dot1+Dot2+Dot3+Dot5 SWITCHVT+17
+bind Space+Control+Dot2+Dot3+Dot4 SWITCHVT+18
+bind Space+Control+Dot2+Dot3+Dot4+Dot5 SWITCHVT+19
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by Brailleback
+#context menu
+#bind JoystickUp MENU_PREV_ITEM
+#bind JoystickDown MENU_NEXT_ITEM
+#bind JoystickLeft MENU_PREV_SETTING
+#bind JoystickRight MENU_NEXT_SETTING
+#bind JoystickEnter PREFSAVE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord7 Shift
+assign chord8 Control
+
+include ../android-chords.kti
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/chords.kti b/Tables.brailleback/chords.kti
new file mode 100644
index 0000000..dbfd65f
--- /dev/null
+++ b/Tables.brailleback/chords.kti
@@ -0,0 +1,194 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign a Dot1
+assign b Dot1+Dot2
+assign c Dot1+Dot4
+assign d Dot1+Dot4+Dot5
+assign e Dot1+Dot5
+assign f Dot1+Dot2+Dot4
+assign g Dot1+Dot2+Dot4+Dot5
+assign h Dot1+Dot2+Dot5
+assign i Dot2+Dot4
+assign j Dot2+Dot4+Dot5
+assign k Dot1+Dot3
+assign l Dot1+Dot2+Dot3
+assign m Dot1+Dot3+Dot4
+assign n Dot1+Dot3+Dot4+Dot5
+assign o Dot1+Dot3+Dot5
+assign p Dot1+Dot2+Dot3+Dot4
+assign q Dot1+Dot2+Dot3+Dot4+Dot5
+assign r Dot1+Dot2+Dot3+Dot5
+assign s Dot2+Dot3+Dot4
+assign t Dot2+Dot3+Dot4+Dot5
+assign u Dot1+Dot3+Dot6
+assign v Dot1+Dot2+Dot3+Dot6
+assign w Dot2+Dot4+Dot5+Dot6
+assign x Dot1+Dot3+Dot4+Dot6
+assign y Dot1+Dot3+Dot4+Dot5+Dot6
+assign z Dot1+Dot3+Dot5+Dot6
+
+assign toggleOff Dot7
+assign toggleOn Dot8
+
+#Overridden by BrailleBack.
+#beginVariables
+#assign toggleKeys \{chord}\{b}
+#assign toggleCommand SKPBLNKWINS
+#include toggle.kti
+#endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{c}
+assign toggleCommand CSRVIS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{d}
+assign toggleCommand DISPMD
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{f}
+assign toggleCommand FREEZE
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{g}
+assign toggleCommand CONTRACTED
+include toggle.kti
+endVariables
+
+#Overridden by BrailleBack.
+#bind \{chord}\{h} HELP
+
+beginVariables
+assign toggleKeys \{chord}\{i}
+assign toggleCommand SKPIDLNS
+include toggle.kti
+endVariables
+
+#Overridden by BrailleBack.
+#bind \{chord}\{l} LEARN
+
+#Overridden by BrailleBack.
+#bind \{chord}\{p} PREFMENU
+bind \{chord}\{p}+\{toggleOff} PREFLOAD
+bind \{chord}\{p}+\{toggleOn} PREFSAVE
+bind \{chord}\{p}+\{toggleOff}+\{toggleOn} PREFRESET
+
+#Overridden by BrailleBack.
+#beginVariables
+#assign toggleKeys \{chord}\{r}
+#assign toggleCommand AUTOREPEAT
+#include toggle.kti
+#endVariables
+
+#bind \{chord}\{s} INFO
+
+beginVariables
+assign toggleKeys \{chord}\{t}
+assign toggleCommand CSRTRK
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{u}
+assign toggleCommand ATTRVIS
+include toggle.kti
+endVariables
+
+bind \{chord}\{v} CSRJMP_VERT
+bind \{chord}\{v}+\{toggleOff} SWITCHVT_PREV
+bind \{chord}\{v}+\{toggleOn} SWITCHVT_NEXT
+
+#Overridden by BrailleBack.
+#beginVariables
+#assign toggleKeys \{chord}\{w}
+#assign toggleCommand SLIDEWIN
+#include toggle.kti
+#endVariables
+
+bind \{chord}\{x} PASTE
+bind \{chord}\{x}+\{toggleOff} CLIP_RESTORE
+bind \{chord}\{x}+\{toggleOn} CLIP_SAVE
+
+bind \{chord}Dot2+Dot3+Dot5 COMPBRL6+on
+bind \{chord}Dot2+Dot3+Dot6 COMPBRL6+off
+
+bind \{chord}Dot1+Dot3+\{toggleOff} BRLKBD+off
+bind \{chord}Dot1+Dot3+\{toggleOn} BRLKBD+on
+
+#Overridden by BrailleBack.
+#bind \{chord}Dot4+Dot6+\{toggleOff} BRLUCDOTS+off
+#Overridden by BrailleBack.
+#bind \{chord}Dot4+Dot6+\{toggleOn} BRLUCDOTS+on
+
+#Overridden by BrailleBack.
+#bind \{chord}Dot3 KEY_CURSOR_LEFT
+#Overridden by BrailleBack.
+#bind \{chord}Dot6 KEY_CURSOR_RIGHT
+#Overridden by BrailleBack.
+#bind \{chord}Dot2 KEY_HOME
+#Overridden by BrailleBack.
+#bind \{chord}Dot5 KEY_END
+#Overridden by BrailleBack.
+#bind \{chord}Dot1 KEY_CURSOR_UP
+#Overridden by BrailleBack.
+#bind \{chord}Dot4 KEY_CURSOR_DOWN
+
+bind \{chord}Dot2+Dot3 KEY_PAGE_UP
+bind \{chord}Dot5+Dot6 KEY_PAGE_DOWN
+#Overridden by BrailleBack.
+#bind \{chord}Dot4+Dot5 KEY_TAB
+
+bind \{chord}Dot2+Dot5+Dot6 KEY_DELETE
+bind \{chord}Dot2+Dot6 KEY_ESCAPE
+bind \{chord}Dot3+Dot5 KEY_INSERT
+
+bind \{chord}Dot1+Dot8 GUI
+bind \{chord}Dot4+Dot8 SHIFT
+bind \{chord}Dot2+Dot8 META
+bind \{chord}Dot5+Dot8 ALTGR
+bind \{chord}Dot3+Dot8 CONTROL
+#Overridden by BrailleBack.
+#bind \{chord}Dot6+Dot8 UPPER
+bind \{chord}Dot7+Dot8 UNSTICK
+
+ifNotVar noUnchorded
+assignDefault commandDot7 KEY_BACKSPACE
+assignDefault commandDot8 KEY_ENTER
+
+bind Dot7 \{commandDot7}
+bind Dot8 \{commandDot8}
+
+bind \{chord}Dot7 PASSDOTS+dot7
+bind \{chord}Dot8 PASSDOTS+dot8
+
+#Unused by BrailleBack.
+#include menu.kti
+endIf
+
+#Dont konw why it's not running into it, but currently brltty used by braille
+#display in AAS only so comment it out.
+#ifPlatform android
+include android-chords.kti
+#endif
diff --git a/Tables.brailleback/eu/all.txt b/Tables.brailleback/eu/all.txt
new file mode 100644
index 0000000..135dbc4
--- /dev/null
+++ b/Tables.brailleback/eu/all.txt
@@ -0,0 +1,28 @@
+Help: EuroBraille
+      Driver developped by Yannick PLASSIARD and Olivier BERT
+
+To enter in the Learn-Mode, press #+L (on notebraille/clio), Alpha+L8 (for 
+Scriba) or Level1+L7 (on Iris - Level1 may be performed by pressing 
+FG+FB simultaneously).
+For Esys put LeftJoystick in the "Right" position and RightJoystick in the 
+"Up" position.
+
+Using Cut And Paste
+-------------------
+	To begin a block, press "*E" or Beta+L1 or Layer2+L1 depending on your
+braille display and then click on the cell where you want to start the block.
+To end a block press "*M" or Beta+L9 or Layer2+l8 depending on your braille 
+display, and click on the cell where you want to end the block. 
+When you do this, the block is copied into the BRLTTY clipboard, waiting to 
+be pasted anywhere you want. To paste a block,
+press the "*L" or Beta+L8 or Layer2+L7 depending on your braille display. 
+A separate help file for each model will be created soon.
+
+Note
+----
+Please note that the README file contains also version information and
+copyright notice, so if you find a bug that was not listed out in the README
+file, feel free to send an e-mail to me (yan@mistigri.org), because it's
+hard to test all possible functions, even if I use the driver 10 hours a day. 
+
+Thank you.
diff --git a/Tables.brailleback/eu/braille.kti b/Tables.brailleback/eu/braille.kti
new file mode 100644
index 0000000..89a19b3
--- /dev/null
+++ b/Tables.brailleback/eu/braille.kti
@@ -0,0 +1,77 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The eight dot keys of the braille keyboard are behind the cursor routing keys.
+note From left to right, they are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+note The two keys of the braille keyboard in front of the braille cells, from left to right, are: Backspace, Space.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map Space SPACE
+bind Backspace KEY_BACKSPACE
+bind Backspace+Space KEY_ENTER
+
+bind Space+Dot7 SHIFT
+bind Space+Dot8 META
+bind Space+Dot7+Dot8 CONTROL
+
+bind Space+Dot2+Dot5+Dot6 KEY_TAB
+bind Space+Dot2+Dot3+Dot6 KEY_TAB+shift
+
+#Overridden by BrailleBack.
+#bind Space+Dot4 KEY_CURSOR_UP
+#bind Space+Dot6 KEY_CURSOR_DOWN
+#bind Space+Dot2 KEY_CURSOR_LEFT
+#bind Space+Dot5 KEY_CURSOR_RIGHT
+
+bind Space+Dot1+Dot3 KEY_PAGE_UP
+bind Space+Dot4+Dot6 KEY_PAGE_DOWN
+
+#bind Space+Dot1+Dot2+Dot3 KEY_HOME
+#bind Space+Dot4+Dot5+Dot6 KEY_END
+
+bind Space+Dot3+Dot6 KEY_DELETE
+bind Space+Dot1+Dot2+Dot4+Dot5 KEY_ESCAPE
+bind Space+Dot1+Dot3+Dot5 KEY_INSERT
+
+bind Backspace+Dot1 KEY_FUNCTION+0
+bind Backspace+Dot1+Dot2 KEY_FUNCTION+1
+bind Backspace+Dot1+Dot4 KEY_FUNCTION+2
+bind Backspace+Dot1+Dot4+Dot5 KEY_FUNCTION+3
+bind Backspace+Dot1+Dot5 KEY_FUNCTION+4
+bind Backspace+Dot1+Dot2+Dot4 KEY_FUNCTION+5
+bind Backspace+Dot1+Dot2+Dot4+Dot5 KEY_FUNCTION+6
+bind Backspace+Dot1+Dot2+Dot5 KEY_FUNCTION+7
+bind Backspace+Dot2+Dot4 KEY_FUNCTION+8
+bind Backspace+Dot2+Dot4+Dot5 KEY_FUNCTION+9
+bind Backspace+Dot1+Dot3 KEY_FUNCTION+10
+bind Backspace+Dot1+Dot2+Dot3 KEY_FUNCTION+11
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+include ../android-chords.kti
diff --git a/Tables.brailleback/eu/clio.ktb b/Tables.brailleback/eu/clio.ktb
new file mode 100644
index 0000000..14a2456
--- /dev/null
+++ b/Tables.brailleback/eu/clio.ktb
@@ -0,0 +1,102 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille AzerBraille, Clio, NoteBraille, PupiBraille, Scriba
+
+include braille.kti
+
+bind !Star CONTEXT+alt1
+bind !Sharp CONTEXT+alt2
+bind !RoutingKey ROUTE
+
+bind !Up KEY_CURSOR_UP
+bind !Left KEY_CURSOR_LEFT
+bind !Right KEY_CURSOR_Right
+bind !Down KEY_CURSOR_DOWN
+
+bind !Five HOME
+bind !One TOP_LEFT
+bind !Seven BOT_LEFT
+bind !Three PRDIFLN
+bind !Nine NXDIFLN
+bind !Zero CSRTRK
+
+bind !A FREEZE
+
+bind !E FWINLT
+bind !F LNUP
+bind !G PRPROMPT
+bind !H PREFMENU
+bind !I INFO
+bind !K NXPROMPT
+bind !L LNDN
+bind !M FWINRT
+
+
+context alt1
+bind !Star CONTEXT+default
+
+bind !Down CSRTRK
+bind !Right TUNES
+
+bind !E CONTEXT+CLIP_NEW
+bind !F CONTEXT+CLIP_ADD
+bind !G CSRVIS
+bind !K CONTEXT+COPY_RECT
+bind !L PASTE
+bind !M CONTEXT+COPY_LINE
+
+
+context alt2
+bind !Sharp CONTEXT+default
+
+bind !Left LNBEG
+bind !Right LNEND
+bind !Up HOME
+bind !Down BACK
+
+bind !One LEARN
+bind !Three TOP_LEFT
+bind !Nine BOT_LEFT
+
+bind !A DISPMD
+
+bind !E TOP_LEFT
+bind !G PRSEARCH
+bind !H HELP
+bind !K NXSEARCH
+bind !L LEARN
+bind !M BOT_LEFT
+
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+
diff --git a/Tables.brailleback/eu/common.kti b/Tables.brailleback/eu/common.kti
new file mode 100644
index 0000000..04f6748
--- /dev/null
+++ b/Tables.brailleback/eu/common.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/eu/esys_large.ktb b/Tables.brailleback/eu/esys_large.ktb
new file mode 100644
index 0000000..9594fe7
--- /dev/null
+++ b/Tables.brailleback/eu/esys_large.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 80
+
+include common.kti
+include joysticks.kti
+include sw12.kti
+include sw34.kti
+include sw56.kti
diff --git a/Tables.brailleback/eu/esys_medium.ktb b/Tables.brailleback/eu/esys_medium.ktb
new file mode 100644
index 0000000..558e618
--- /dev/null
+++ b/Tables.brailleback/eu/esys_medium.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 40,64
+
+include common.kti
+include joysticks.kti
+include sw12.kti
+include sw34.kti
diff --git a/Tables.brailleback/eu/esys_small.ktb b/Tables.brailleback/eu/esys_small.ktb
new file mode 100644
index 0000000..6ac4a2d
--- /dev/null
+++ b/Tables.brailleback/eu/esys_small.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 12,24
+
+include common.kti
+include joysticks.kti
+include sw12.kti
diff --git a/Tables.brailleback/eu/esytime.ktb b/Tables.brailleback/eu/esytime.ktb
new file mode 100644
index 0000000..b1fd68a
--- /dev/null
+++ b/Tables.brailleback/eu/esytime.ktb
@@ -0,0 +1,126 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esytime
+
+include common.kti
+
+note The four keys below the left joystick, from top to bottom, are:
+note * L1, Escape, Tab, Shift.
+note The four keys below the right joystick, from top to bottom, are:
+note * L8, Insert, Alt, Control.
+note Some key combinations have been reserved for internal use:
+note + LeftJoystickDown: the Alt key
+note + LeftJoystickPress: the Alt key pressed twice in a row
+note + LeftJoystickDown + RightJoystick(Right/Left): enable/disable braille functions
+note + LeftJoystickDown + RightJoystick(Up/Down): switch to internal/external (Esytime/PC) USB interface
+note + LeftJoystickDown + L1 + Escape: switch to single-hand braille keyboard 1
+note + LeftJoystickDown + Shift + Tab: switch to single-hand braille keyboard 2
+note + LeftJoystickDown + Insert + L8: switch to standard (two-hand) braille keyboard
+note + RightJoystickPress: the Enter key
+note + RightJoystick(left/right/up/down): the Arrow Left/Right/Up/Down key
+
+bind L1+L8 HOME
+bind LeftJoystickPress+RightJoystickPress BACK
+
+bind L1 FWINLT
+bind L8 FWINRT
+
+bind LeftJoystickLeft LNUP
+bind LeftJoystickRight LNDN
+bind LeftJoystickUp CSRTRK
+bind LeftJoystickDown NOOP # internal: the Alt key
+bind LeftJoystickPress NOOP # internal: the Alt key pressed twice in a row
+
+bind RightJoystickLeft NOOP # internal: the Arrow Left key
+bind RightJoystickRight NOOP # internal: the Arrow Right key
+bind RightJoystickUp NOOP # internal: the Arrow Up key
+bind RightJoystickDown NOOP # internal: the Arrow Down key
+bind RightJoystickPress NOOP # internal: the Enter key
+
+bind LeftJoystickLeft+RightJoystickPress TOP
+bind LeftJoystickLeft+RightJoystickLeft PRPROMPT
+bind LeftJoystickLeft+RightJoystickRight PRPGRPH
+bind LeftJoystickLeft+RightJoystickUp PRDIFLN
+bind LeftJoystickLeft+RightJoystickDown ATTRUP
+
+bind LeftJoystickRight+RightJoystickPress BOT
+bind LeftJoystickRight+RightJoystickLeft NXPROMPT
+bind LeftJoystickRight+RightJoystickRight NXPGRPH
+bind LeftJoystickRight+RightJoystickUp NXDIFLN
+bind LeftJoystickRight+RightJoystickDown ATTRDN
+
+bind LeftJoystickUp+RightJoystickPress DISPMD
+bind LeftJoystickUp+RightJoystickLeft CSRVIS
+bind LeftJoystickUp+RightJoystickRight ATTRVIS
+bind LeftJoystickUp+RightJoystickUp SIXDOTS+on
+bind LeftJoystickUp+RightJoystickDown SIXDOTS+off
+
+bind LeftJoystickDown+RightJoystickPress INFO
+bind LeftJoystickDown+RightJoystickLeft NOOP # internal: disable braille functions
+bind LeftJoystickDown+RightJoystickRight NOOP # internal: enable braille functions
+bind LeftJoystickDown+RightJoystickUp NOOP # internal: switch to internal (Esytime) USB interface
+bind LeftJoystickDown+RightJoystickDown NOOP # internal: switch to external (PC) USB interface
+
+bind LeftJoystickPress+RightJoystickLeft NOOP
+bind LeftJoystickPress+RightJoystickRight NOOP
+bind LeftJoystickPress+RightJoystickUp NOOP
+bind LeftJoystickPress+RightJoystickDown NOOP
+
+bind LeftJoystickPress+RoutingKey1 SETLEFT
+bind LeftJoystickLeft+RoutingKey1 PRDIFCHAR
+bind LeftJoystickRight+RoutingKey1 NXDIFCHAR
+bind LeftJoystickUp+RoutingKey1 PRINDENT
+bind LeftJoystickDown+RoutingKey1 NXINDENT
+
+bind RightJoystickPress+RoutingKey1 DESCCHAR
+bind RightJoystickLeft+RoutingKey1 CLIP_NEW
+bind RightJoystickUp+RoutingKey1 CLIP_ADD
+bind RightJoystickRight+RoutingKey1 COPY_LINE
+bind RightJoystickDown+RoutingKey1 COPY_RECT
+
+bind L1+LeftJoystickPress TIME
+bind L1+LeftJoystickLeft CHRLT
+bind L1+LeftJoystickRight CHRRT
+bind L1+LeftJoystickUp PRSEARCH
+bind L1+LeftJoystickDown NXSEARCH
+
+bind L8+LeftJoystickPress CSRJMP_VERT
+bind L8+LeftJoystickLeft FWINLTSKIP
+bind L8+LeftJoystickRight FWINRTSKIP
+bind L8+LeftJoystickUp LNBEG
+bind L8+LeftJoystickDown LNEND
+
+bind L1+RightJoystickPress PREFMENU
+bind L1+RightJoystickLeft PREFLOAD
+bind L1+RightJoystickRight PREFSAVE
+bind L1+RightJoystickUp HELP
+bind L1+RightJoystickDown LEARN
+
+bind L8+RightJoystickPress PASTE
+bind L8+RightJoystickLeft CLIP_RESTORE
+bind L8+RightJoystickRight CLIP_SAVE
+bind L8+RightJoystickUp FREEZE
+bind L8+RightJoystickDown AUTOREPEAT
+
+context menu
+bind L8+RightJoystickUp MENU_PREV_ITEM
+bind L8+RightJoystickDown MENU_NEXT_ITEM
+bind L8+RightJoystickLeft MENU_PREV_SETTING
+bind L8+RightJoystickRight MENU_NEXT_SETTING
+bind L8+RightJoystickPress MENU_PREV_LEVEL
diff --git a/Tables.brailleback/eu/iris.ktb b/Tables.brailleback/eu/iris.ktb
new file mode 100644
index 0000000..a6edc5f
--- /dev/null
+++ b/Tables.brailleback/eu/iris.ktb
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Iris
+
+include common.kti
+note The Menu key is the small, round button to the left of the cursor routing keys.
+note The Z key is the small, round button to the right of the cursor routing keys.
+
+bind Down+Left CONTEXT+alt1
+bind Down+Right CONTEXT+alt2
+
+bind L1 FWINLT
+bind L2 LNUP
+bind L3 PRPROMPT
+bind L4 PREFMENU
+bind L5 INFO
+bind L6 NXPROMPT
+bind L7 LNDN
+bind L8 FWINRT
+bind Left KEY_CURSOR_LEFT
+bind Right KEY_CURSOR_RIGHT
+bind Up KEY_CURSOR_UP
+bind Down KEY_CURSOR_DOWN
+bind L1+L2 TOP_LEFT
+bind L3+L4 FREEZE
+bind L6+L7 HOME
+bind L7+L8 BOT_LEFT
+bind L1+L2+L3+L4 RESTARTBRL
+bind L5+L6+L7+L8 RESTARTSPEECH
+
+
+context alt1
+bind L1 TOP_LEFT
+bind L3 PRSEARCH
+bind L4 HELP
+bind L5 LEARN
+bind L6 NXSEARCH
+bind L8 BOT_LEFT
+bind Left LNBEG
+bind Right LNEND
+bind Up HOME
+bind Down BACK
+
+
+context alt2
+bind L1 CONTEXT+CLIP_NEW
+bind L2 CONTEXT+CLIP_ADD
+bind L3 CSRVIS
+bind L6 CONTEXT+COPY_RECT
+bind L7 PASTE
+bind L8 CONTEXT+COPY_LINE
+bind Up PREFMENU
+bind Down CSRTRK
+bind Right TUNES
+
+
+context CLIP_NEW
+bind RoutingKey1 CLIP_NEW
+
+
+context CLIP_ADD
+bind RoutingKey1 CLIP_ADD
+
+
+context COPY_RECT
+bind RoutingKey1 COPY_RECT
+
+
+context COPY_LINE
+bind RoutingKey1 COPY_LINE
+
+
diff --git a/Tables.brailleback/eu/joysticks.kti b/Tables.brailleback/eu/joysticks.kti
new file mode 100644
index 0000000..abb659f
--- /dev/null
+++ b/Tables.brailleback/eu/joysticks.kti
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The joysticks are the small, round, five-way (left, right, up, down, press) controls to the left and right of the cursor routing keys.
+
+bind RightJoystickPress HOME
+bind RightJoystickLeft FWINLT
+bind RightJoystickRight FWINRT
+bind RightJoystickUp LNUP
+bind RightJoystickDown LNDN
+
+bind LeftJoystickLeft PRPROMPT
+bind LeftJoystickRight NXPROMPT
+bind LeftJoystickUp TOP_LEFT
+bind LeftJoystickDown BOT_LEFT
+
+bind LeftJoystickDown+RightJoystickLeft CONTEXT+CLIP_NEW
+bind LeftJoystickDown+RightJoystickUp CONTEXT+CLIP_ADD
+bind LeftJoystickDown+RightJoystickDown CONTEXT+COPY_RECT
+bind LeftJoystickDown+RightJoystickRight CONTEXT+COPY_LINE
+bind LeftJoystickDown+RightJoystickPress PASTE
+
+bind LeftJoystickLeft+RightJoystickPress SAY_LINE
+bind LeftJoystickLeft+RightJoystickUp SAY_ABOVE
+bind LeftJoystickLeft+RightJoystickDown SAY_BELOW
+bind LeftJoystickLeft+RightJoystickLeft SAY_SOFTER
+bind LeftJoystickLeft+RightJoystickRight SAY_LOUDER
+
+bind LeftJoystickRight+RightJoystickUp LEARN
+bind LeftJoystickRight+RightJoystickDown HELP
+
+
+context CLIP_NEW
+bind RoutingKey1 CLIP_NEW
+
+
+context CLIP_ADD
+bind RoutingKey1 CLIP_ADD
+
+
+context COPY_RECT
+bind RoutingKey1 COPY_RECT
+
+
+context COPY_LINE
+bind RoutingKey1 COPY_LINE
+
+
+context default
diff --git a/Tables.brailleback/eu/routing.kti b/Tables.brailleback/eu/routing.kti
new file mode 100644
index 0000000..b0c054b
--- /dev/null
+++ b/Tables.brailleback/eu/routing.kti
@@ -0,0 +1,33 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The cursor routing keys are the small, round buttons just behind the braille cells.
+note RoutingKey1 means pressing a cursor routing key once (a single click).
+note RoutingKey2 means pressing a cursor routing key twice quickly (a double click).
+
+#Overridden by BrailleBack.
+#bind RoutingKey1 ROUTE
+bind RoutingKey2 DESCCHAR
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey1 ROUTE:ROUTE+128
diff --git a/Tables.brailleback/eu/sw12.kti b/Tables.brailleback/eu/sw12.kti
new file mode 100644
index 0000000..2c39134
--- /dev/null
+++ b/Tables.brailleback/eu/sw12.kti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The switches are three-way (left, right, press) controls on the front surface.
+note Switch1 is the left outer switch.
+note Switch2 is the right outer switch.
+
+bind Switch1Left FWINLT
+bind Switch1Right FWINRT
+
+bind LeftJoystickLeft+Switch1Left+Switch1Right MUTE
+bind LeftJoystickLeft+Switch1Left SAY_SLOWER
+bind LeftJoystickLeft+Switch1Right SAY_FASTER
+
+bind LeftJoystickRight+Switch1Left LNBEG
+bind LeftJoystickRight+Switch1Right LNEND
+
+bind LeftJoystickRight+Switch1Left+Switch1Right PREFMENU
+bind LeftJoystickRight+Switch2Left+Switch2Right CSRTRK
+
diff --git a/Tables.brailleback/eu/sw34.kti b/Tables.brailleback/eu/sw34.kti
new file mode 100644
index 0000000..d0ed966
--- /dev/null
+++ b/Tables.brailleback/eu/sw34.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Switch3 is the left inner switch.
+note Switch4 is the right inner switch.
+
diff --git a/Tables.brailleback/eu/sw56.kti b/Tables.brailleback/eu/sw56.kti
new file mode 100644
index 0000000..dc43aa2
--- /dev/null
+++ b/Tables.brailleback/eu/sw56.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Switch5 is between Switch1 and Switch3.
+note Switch6 is between Switch2 and Switch4.
+
diff --git a/Tables.brailleback/fs/bumpers.kti b/Tables.brailleback/fs/bumpers.kti
new file mode 100644
index 0000000..d7bfc8e
--- /dev/null
+++ b/Tables.brailleback/fs/bumpers.kti
@@ -0,0 +1,54 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for FreedomScientific displays which have bumper bars.
+
+note The left and right bumpers are long, up/down, rocker bars.
+note * Newer models refer to them as panning rockers.
+note * On some models, each bumper is a pair of shorter bars.
+
+bind LeftBumperUp FWINLT
+bind LeftBumperDown FWINRT
+bind RightBumperUp FWINLT
+bind RightBumperDown FWINRT
+
+bind LeftBumperUp+PanLeft CHRLT
+bind LeftBumperDown+PanLeft CHRRT
+bind RightBumperUp+PanLeft HWINLT
+bind RightBumperDown+PanLeft HWINRT
+
+bind LeftBumperUp+LeftSelector FWINLTSKIP
+bind LeftBumperDown+LeftSelector FWINRTSKIP
+bind RightBumperUp+LeftSelector PRNBWIN
+bind RightBumperDown+LeftSelector NXNBWIN
+
+bind LeftBumperUp+RightSelector PRPROMPT
+bind LeftBumperDown+RightSelector NXPROMPT
+bind RightBumperUp+RightSelector PRPGRPH
+bind RightBumperDown+RightSelector NXPGRPH
+
+bind LeftBumperUp+PanRight FREEZE+on
+bind LeftBumperDown+PanRight FREEZE+off
+bind RightBumperUp+PanRight DISPMD+on
+bind RightBumperDown+PanRight DISPMD+off
+
+bind LeftBumperUp+RoutingKey PRDIFCHAR
+bind LeftBumperDown+RoutingKey NXDIFCHAR
+bind RightBumperUp+RoutingKey PRINDENT
+bind RightBumperDown+RoutingKey NXINDENT
+
diff --git a/Tables.brailleback/fs/common.kti b/Tables.brailleback/fs/common.kti
new file mode 100644
index 0000000..ef209ee
--- /dev/null
+++ b/Tables.brailleback/fs/common.kti
@@ -0,0 +1,78 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Left\{navKeyType}Press LNBEG
+bind Right\{navKeyType}Press LNEND
+
+bind LeftSelector BACK:TOP_LEFT
+bind RightSelector HOME:BOT_LEFT
+bind LeftSelector+RightSelector PASTE
+
+bind PanLeft FWINLT
+bind PanRight FWINRT
+bind PanLeft+PanRight CSRJMP_VERT
+
+bind LeftSelector+PanLeft TOP_LEFT
+bind LeftSelector+PanRight BOT_LEFT
+
+bind RightSelector+PanLeft TOP
+bind RightSelector+PanRight BOT
+
+bind !Left\{navKeyType}Up LNUP
+bind !Left\{navKeyType}Down LNDN
+
+bind !Right\{navKeyType}Up FWINLT
+bind !Right\{navKeyType}Down FWINRT
+
+bind Left\{navKeyType}Press+!Left\{navKeyType}Up PRDIFLN
+bind Left\{navKeyType}Press+!Left\{navKeyType}Down NXDIFLN
+
+bind Right\{navKeyType}Press+!Right\{navKeyType}Up CHRLT
+bind Right\{navKeyType}Press+!Right\{navKeyType}Down CHRRT
+
+bind PanLeft+!Left\{navKeyType}Up PRPROMPT
+bind PanLeft+!Left\{navKeyType}Down NXPROMPT
+
+bind PanRight+!Left\{navKeyType}Up PRPGRPH
+bind PanRight+!Left\{navKeyType}Down NXPGRPH
+
+bind LeftSelector+!Left\{navKeyType}Up ATTRUP
+bind LeftSelector+!Left\{navKeyType}Down ATTRDN
+
+bind RightSelector+!Left\{navKeyType}Up PRSEARCH
+bind RightSelector+!Left\{navKeyType}Down NXSEARCH
+
+
+#Overridden by BrailleBack.
+#bind RoutingKey ROUTE
+bind Left\{navKeyType}Press+RoutingKey SETLEFT
+bind Right\{navKeyType}Press+RoutingKey DESCCHAR
+
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind PanLeft+RoutingKey CLIP_NEW
+bind PanRight+RoutingKey COPY_RECT
+bind LeftSelector+RoutingKey CLIP_ADD
+bind RightSelector+RoutingKey COPY_LINE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/fs/focus.kti b/Tables.brailleback/fs/focus.kti
new file mode 100644
index 0000000..fbdbd0f
--- /dev/null
+++ b/Tables.brailleback/fs/focus.kti
@@ -0,0 +1,52 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for all FreedomScientific Focus displays.
+
+note This description is general because the key layout differs from model to model.
+
+note There is a routing key immediately behind each braille cell.
+note * Some models have a navrow key immediately behind each routing key.
+note * On others, a navrow key can be emulated by long-pressing the corresponding routing key.
+
+note There are two Nav controls - one at each end of the braille cells.
+note * Each has three actions: Up, Down, and Press.
+note * On newer models, each is a rocker combined with a button.
+note * On older models, each is a wheel that can be both rolled and pressed.
+
+note The keys on the front, from left to right, are:
+note * \{frontKeysLeft} - \{frontKeysRight}.
+
+note The left and right pan keys are short bars.
+note * On some models, each has a raised double-arrow.
+
+note The left and right selectors are round.
+note * On older models, they're known as GDF (General Display Function) keys.
+
+assign navKeyType Nav
+include common.kti
+include keyboard.kti
+include speech.kti
+
+bind NavrowKey DESCCHAR
+bind PanLeft+NavrowKey PRINDENT
+bind PanRight+NavrowKey NXINDENT
+bind LeftSelector+NavrowKey PRDIFCHAR
+bind RightSelector+NavrowKey NXDIFCHAR
+bind NavrowKey+!NavrowKey CLIP_APPEND
+
diff --git a/Tables.brailleback/fs/focus1.ktb b/Tables.brailleback/fs/focus1.ktb
new file mode 100644
index 0000000..7cf94f5
--- /dev/null
+++ b/Tables.brailleback/fs/focus1.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus1 (original)
+
+assign frontKeysLeft pan\sleft,\sleft\sselector,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sselector,\span\sright
+
+include focus.kti
diff --git a/Tables.brailleback/fs/focus14.ktb b/Tables.brailleback/fs/focus14.ktb
new file mode 100644
index 0000000..f1d5964
--- /dev/null
+++ b/Tables.brailleback/fs/focus14.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 14
+
+assign frontKeysLeft left\sselector,\sleft\srocker,\span\sleft,\sleft\sshift
+assign frontKeysRight right\sshift,\span\sright,\sright\srocker,\sright\sselector
+
+include focus.kti
+include rockers.kti
diff --git a/Tables.brailleback/fs/focus40.ktb b/Tables.brailleback/fs/focus40.ktb
new file mode 100644
index 0000000..5adb7a4
--- /dev/null
+++ b/Tables.brailleback/fs/focus40.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 40
+
+assign frontKeysLeft pan\sleft,\sleft\srocker,\sleft\sselector,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sselector,\sright\srocker,\span\sright
+
+include focus.kti
+include rockers.kti
diff --git a/Tables.brailleback/fs/focus80.ktb b/Tables.brailleback/fs/focus80.ktb
new file mode 100644
index 0000000..4fe92c2
--- /dev/null
+++ b/Tables.brailleback/fs/focus80.ktb
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 80
+
+assign frontKeysLeft pan\sleft,\sleft\srocker,\sleft\sselector,\sleft\sbumper,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sbumper,\sright\sselector,\sright\srocker,\span\sright
+
+include focus.kti
+include rockers.kti
+include bumpers.kti
diff --git a/Tables.brailleback/fs/keyboard.kti b/Tables.brailleback/fs/keyboard.kti
new file mode 100644
index 0000000..530fcba
--- /dev/null
+++ b/Tables.brailleback/fs/keyboard.kti
@@ -0,0 +1,55 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note An eight-dot, Perkins-style, braille keyboard is near the rear of the top.
+note * Its keys, From left to right, are: Dot7, Dot3, Dot2, Dot1 - Dot4, Dot5, Dot6, Dot8.
+note * The space bar is in front of the braille cells.
+note * On some models, the eight dot keys are in a straight line
+note * behind the space bar but in front of the braille cells.
+
+note The left and right shift keys are squarish.
+note * On some models, they're at the center of the front.
+note * On others, they're on either side of the space bar.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map Space SPACE
+map LeftShift CONTROL
+map RightShift META
+
+assign chord Space+
+include ../chords.kti
+
+bind Space+RoutingKey KEY_FUNCTION
+bind LeftShift+RoutingKey SETLEFT
+bind RightShift+RoutingKey SWITCHVT
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord Space
+include ../android-chords.kti
diff --git a/Tables.brailleback/fs/pacmate.ktb b/Tables.brailleback/fs/pacmate.ktb
new file mode 100644
index 0000000..e459fc5
--- /dev/null
+++ b/Tables.brailleback/fs/pacmate.ktb
@@ -0,0 +1,46 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific PAC Mate
+
+note The rear row of cursor routing keys is actually a set of navigation keys.
+note The 10 middle keys (5 on each side of the center) are special.
+note The two outer ones are named LeftSelector and RightSelector.
+note The eight inner ones are named Hot1 through Hot8.
+note The PanLeft key is any key to the left of the special keys.
+note The PanRight key is any key to the right of the special keys.
+
+bind Hot1 SKPIDLNS
+bind RightSelector+Hot1 SKPBLNKWINS
+bind Hot2 DISPMD
+bind RightSelector+Hot2 ATTRVIS
+bind Hot3 CSRTRK
+bind RightSelector+Hot3 CSRVIS
+bind Hot4 SIXDOTS
+bind RightSelector+Hot4 AUTOREPEAT
+bind Hot5 HELP
+bind RightSelector+Hot5 FREEZE
+bind Hot6 LEARN
+bind RightSelector+Hot6 PREFLOAD
+bind Hot7 PREFMENU
+bind RightSelector+Hot7 PREFSAVE
+bind Hot8 INFO
+bind RightSelector+Hot8 CSRJMP_VERT
+
+assign navKeyType Wheel
+include common.kti
diff --git a/Tables.brailleback/fs/rockers.kti b/Tables.brailleback/fs/rockers.kti
new file mode 100644
index 0000000..9050e97
--- /dev/null
+++ b/Tables.brailleback/fs/rockers.kti
@@ -0,0 +1,58 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for FreedomScientific displays which have rockers.
+
+note The left and right rockers are short, up/down, rocker keys.
+
+bind LeftRockerUp KEY_CURSOR_UP
+bind LeftRockerDown KEY_CURSOR_DOWN
+
+bind LeftRockerUp+PanLeft KEY_CURSOR_LEFT
+bind LeftRockerDown+PanLeft KEY_CURSOR_RIGHT
+
+bind LeftRockerUp+LeftSelector KEY_HOME
+bind LeftRockerDown+LeftSelector KEY_END
+
+bind LeftRockerUp+RightSelector KEY_PAGE_UP
+bind LeftRockerDown+RightSelector KEY_PAGE_DOWN
+
+bind LeftRockerUp+PanRight CSRTRK+off
+bind LeftRockerDown+PanRight CSRTRK+on
+
+bind RightRockerUp LNUP
+bind RightRockerDown LNDN
+
+bind RightRockerUp+PanLeft LNBEG
+bind RightRockerDown+PanLeft LNEND
+
+bind RightRockerUp+LeftSelector PRSEARCH
+bind RightRockerDown+LeftSelector NXSEARCH
+
+bind RightRockerUp+RightSelector ATTRUP
+bind RightRockerDown+RightSelector ATTRDN
+
+bind RightRockerUp+PanRight SIXDOTS+off
+bind RightRockerDown+PanRight SIXDOTS+on
+
+bind RightRockerUp+RoutingKey SETMARK
+bind RightRockerDown+RoutingKey GOTOMARK
+
+bind LeftRockerUp+RightRockerUp INFO
+bind LeftRockerDown+RightRockerDown TIME
+
diff --git a/Tables.brailleback/fs/speech.kti b/Tables.brailleback/fs/speech.kti
new file mode 100644
index 0000000..abdd820
--- /dev/null
+++ b/Tables.brailleback/fs/speech.kti
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind PanLeft+Space SPEAK_CURR_CHAR:DESC_CURR_CHAR
+bind PanLeft+LeftShift SPEAK_PREV_CHAR
+bind PanLeft+RightShift SPEAK_NEXT_CHAR
+bind PanLeft+LeftShift+Space SPEAK_FRST_CHAR
+bind PanLeft+RightShift+Space SPEAK_LAST_CHAR
+
+bind LeftSelector+Space SPEAK_CURR_WORD:SPELL_CURR_WORD
+bind LeftSelector+LeftShift SPEAK_PREV_WORD
+bind LeftSelector+RightShift SPEAK_NEXT_WORD
+bind LeftSelector+LeftShift+Space SPEAK_CURR_LOCN
+bind LeftSelector+RightShift+Space ROUTE_CURR_LOCN
+
+bind RightSelector+Space SPEAK_CURR_LINE
+bind RightSelector+LeftShift SPEAK_PREV_LINE
+bind RightSelector+RightShift SPEAK_NEXT_LINE
+bind RightSelector+LeftShift+Space SPEAK_FRST_LINE
+bind RightSelector+RightShift+Space SPEAK_LAST_LINE
+
+bind PanRight+Space AUTOSPEAK
+bind PanRight+LeftShift MUTE
+bind PanRight+RightShift SAY_LINE
+
+bind PanRight+LeftShift+Space SAY_ABOVE
+bind PanRight+RightShift+Space SAY_BELOW
+bind PanRight+LeftShift+RightShift+Space SAY_ALL
+
+bind Space+Left\{navKeyType}Down SAY_SOFTER
+bind Space+Left\{navKeyType}Up SAY_Louder
+
+bind LeftShift+Left\{navKeyType}Down SAY_SLOWER
+bind LeftShift+Left\{navKeyType}Up SAY_FASTER
+
+bind RightShift+Left\{navKeyType}Down SAY_LOWER
+bind RightShift+Left\{navKeyType}Up SAY_HIGHER
+
diff --git a/Tables.brailleback/hid/HID.ktb b/Tables.brailleback/hid/HID.ktb
new file mode 100644
index 0000000..9b62d39
--- /dev/null
+++ b/Tables.brailleback/hid/HID.ktb
@@ -0,0 +1,36 @@
+title Generic HID Braille Display
+note These files must be placed in a directory named 'hid' to be loaded properly by brltty
+
+include ../chords.kti
+
+bind RoutingKey ROUTE:ROUTE+128
+map Space SPACE
+
+note Panning and Rocker keys
+note     PanLeft/RockerUp move the Braille cell window left
+note     PanRight/RockerDown move the Braille cell window right
+assign panLeft PanLeft
+bind \{panLeft} FWINLT
+assign panRight PanRight
+bind \{panRight} FWINRT
+assign rockerUp RockerUp
+bind \{rockerUp} FWINLT
+assign rockerDown RockerDown
+bind \{rockerDown} FWINRT
+
+note D-Pad
+note     DPadLeft moves to previous item, DPadRight moves to next item
+note     DPadUp moves up a line, DPadDown moves down a line
+note     DPadCenter is KEY_ENTER
+assign dpadLeft DPadLeft
+bind \{dpadLeft} CHRLT
+assign dpadRight DPadRight
+bind \{dpadRight} CHRRT
+assign dpadLeft DPadUp
+bind \{dpadLeft} LNUP
+assign dpadRight DPadDown
+bind \{dpadRight} LNDN
+assign dpadCenter DPadCenter
+bind \{dpadCenter} KEY_ENTER
+
+include ../android-chords.kti
\ No newline at end of file
diff --git a/Tables.brailleback/hm/beetle.ktb b/Tables.brailleback/hm/beetle.ktb
new file mode 100644
index 0000000..11b4d8e
--- /dev/null
+++ b/Tables.brailleback/hm/beetle.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Smart Beetle
+
+include common.kti
+include braille.kti
+include f14.kti
+include pan.kti
diff --git a/Tables.brailleback/hm/braille.kti b/Tables.brailleback/hm/braille.kti
new file mode 100644
index 0000000..5c67d27
--- /dev/null
+++ b/Tables.brailleback/hm/braille.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+
+assignDefault metaModifier F2
+map \{metaModifier} META
+
+assignDefault controlModifier F3
+map \{controlModifier} CONTROL
+
+assign chord Space+
+include ../chords.kti
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord Space
+include ../android-chords.kti
diff --git a/Tables.brailleback/hm/common.kti b/Tables.brailleback/hm/common.kti
new file mode 100644
index 0000000..09972ce
--- /dev/null
+++ b/Tables.brailleback/hm/common.kti
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+#Overridden by BrailleBack.
+#bind RoutingKey ROUTE
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/hm/contexts.kti b/Tables.brailleback/hm/contexts.kti
new file mode 100644
index 0000000..de2f4ab
--- /dev/null
+++ b/Tables.brailleback/hm/contexts.kti
@@ -0,0 +1,43 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+############################
+# Routing Key Alternatives #
+############################
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+context SETLEFT
+bind !RoutingKey SETLEFT
+
+context DESCCHAR
+bind !RoutingKey DESCCHAR
+
+context KEY_FUNCTION
+bind RoutingKey KEY_FUNCTION
+
diff --git a/Tables.brailleback/hm/edge.ktb b/Tables.brailleback/hm/edge.ktb
new file mode 100644
index 0000000..5bc374d
--- /dev/null
+++ b/Tables.brailleback/hm/edge.ktb
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Edge and QBrailleXL
+
+include common.kti
+include scroll.kti
+include left.kti
+include right.kti
+include f18.kti
+include contexts.kti
+
+assign metaModifier F4
+assign controlModifier F5
+include braille.kti
+
+
+context PRDIFCHAR
+bind RoutingKey PRDIFCHAR
+
+context NXDIFCHAR
+bind RoutingKey NXDIFCHAR
+
+context PRINDENT
+bind RoutingKey PRINDENT
+
+context NXINDENT
+bind RoutingKey NXINDENT
+
diff --git a/Tables.brailleback/hm/f14.kti b/Tables.brailleback/hm/f14.kti
new file mode 100644
index 0000000..62458fc
--- /dev/null
+++ b/Tables.brailleback/hm/f14.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind F4 RETURN
+bind F1+F4 CONTEXT+KEY_FUNCTION
+bind F2+F3 CSRJMP_VERT
+
+bind F2 LNUP
+bind F3 LNDN
+
+bind F1+F2 FWINLTSKIP
+bind F3+F4 FWINRTSKIP
+
+bind F1+F3+F4 CONTEXT+CLIP_NEW
+bind F2+F3+F4 CONTEXT+CLIP_ADD
+bind F1+F2+F3 CONTEXT+COPY_LINE
+bind F1+F2+F4 CONTEXT+COPY_RECT
+bind F1+F2+F3+F4 PASTE
+
+bind F1+F3 CONTEXT+SETLEFT
+bind F2+F4 CONTEXT+DESCCHAR
+
+include contexts.kti
diff --git a/Tables.brailleback/hm/f18.kti b/Tables.brailleback/hm/f18.kti
new file mode 100644
index 0000000..08fc62a
--- /dev/null
+++ b/Tables.brailleback/hm/f18.kti
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind F1 PRPROMPT
+bind F2 NXPROMPT
+bind F3 PRPGRPH
+bind F4 NXPGRPH
+
+bind F5 LEARN
+bind F6 TIME
+bind F7 CHRLT
+bind F8 CHRRT
+
+bind Space+F1 BRLKBD
+bind Space+F2 BRLUCDOTS
+bind Space+F3 SKPIDLNS
+bind Space+F4 CONTEXT+KEY_FUNCTION
+
+bind Space+F5 CONTEXT+SWITCHVT
+bind Space+F6 SKPBLNKWINS
+bind Space+F7 ATTRVIS
+bind Space+F8 CSRVIS
+
+context SWITCHVT
+bind RoutingKey SWITCHVT
+
diff --git a/Tables.brailleback/hm/fnkey.kti b/Tables.brailleback/hm/fnkey.kti
new file mode 100644
index 0000000..7a2c26d
--- /dev/null
+++ b/Tables.brailleback/hm/fnkey.kti
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Function Key Bindings
+note + Function-b: skip blank windows on/off
+note + Function-c: cursor show/hide
+note + Function-d: display mode attributes/text
+note + Function-f: screen image frozen/unfrozen
+note + Function-h: help screen enter/leave
+note + Function-i: skip identical lines on/off
+note + Function-l: learn mode enter/leave
+note + Function-p: preferences menu enter/leave
+note + Function-s: status line enter/leave
+note + Function-t: cursor tracking on/off
+note + Function-u: highlight underline on/off
+note + Function-v: bring cursor to current line
+note + Function-w: sliding window on/off
+
+hide on
+assign function F2+Space
+bind \{function}+Dot1+Dot2 SKPBLNKWINS
+bind \{function}+Dot1+Dot4 CSRVIS
+bind \{function}+Dot1+Dot4+Dot5 DISPMD
+bind \{function}+Dot1+Dot2+Dot4 FREEZE
+bind \{function}+Dot1+Dot2+Dot5 HELP
+bind \{function}+Dot2+Dot4 SKPIDLNS
+bind \{function}+Dot1+Dot2+Dot3 LEARN
+bind \{function}+Dot1+Dot2+Dot3+Dot4 PREFMENU
+bind \{function}+Dot2+Dot3+Dot4 INFO
+bind \{function}+Dot2+Dot3+Dot4+Dot5 CSRTRK
+bind \{function}+Dot1+Dot3+Dot6 ATTRVIS
+bind \{function}+Dot1+Dot2+Dot3+Dot6 CSRJMP_VERT
+bind \{function}+Dot2+Dot4+Dot5+Dot6 SLIDEWIN
+hide off
diff --git a/Tables.brailleback/hm/left.kti b/Tables.brailleback/hm/left.kti
new file mode 100644
index 0000000..02621d4
--- /dev/null
+++ b/Tables.brailleback/hm/left.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind LeftPadLeft+LeftPadRight CSRJMP_VERT
+bind LeftPadUp+LeftPadDown CONTEXT+SETLEFT
+
+bind LeftPadUp ATTRUP
+bind LeftPadDown ATTRDN
+bind LeftPadLeft PRSEARCH
+bind LeftPadRight NXSEARCH
+
+bind LeftPadLeft+LeftPadUp CONTEXT+PRDIFCHAR
+bind LeftPadLeft+LeftPadDown CONTEXT+NXDIFCHAR
+
+bind LeftPadRight+LeftPadUp CONTEXT+PRINDENT
+bind LeftPadRight+LeftPadDown CONTEXT+NXINDENT
+
diff --git a/Tables.brailleback/hm/letters.kti b/Tables.brailleback/hm/letters.kti
new file mode 100644
index 0000000..ac7826a
--- /dev/null
+++ b/Tables.brailleback/hm/letters.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{keys}Dot1 PASSDOTS\{flags}+dot1
+bind \{keys}Dot1+Dot2 PASSDOTS\{flags}+dot1+dot2
+bind \{keys}Dot1+Dot4 PASSDOTS\{flags}+dot1+dot4
+bind \{keys}Dot1+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot4+dot5
+bind \{keys}Dot1+Dot5 PASSDOTS\{flags}+dot1+dot5
+bind \{keys}Dot1+Dot2+Dot4 PASSDOTS\{flags}+dot1+dot2+dot4
+bind \{keys}Dot1+Dot2+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot2+dot4+dot5
+bind \{keys}Dot1+Dot2+Dot5 PASSDOTS\{flags}+dot1+dot2+dot5
+bind \{keys}Dot2+Dot4 PASSDOTS\{flags}+dot2+dot4
+bind \{keys}Dot2+Dot4+Dot5 PASSDOTS\{flags}+dot2+dot4+dot5
+bind \{keys}Dot1+Dot3 PASSDOTS\{flags}+dot1+dot3
+bind \{keys}Dot1+Dot2+Dot3 PASSDOTS\{flags}+dot1+dot2+dot3
+bind \{keys}Dot1+Dot3+Dot4 PASSDOTS\{flags}+dot1+dot3+dot4
+bind \{keys}Dot1+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot3+dot4+dot5
+bind \{keys}Dot1+Dot3+Dot5 PASSDOTS\{flags}+dot1+dot3+dot5
+bind \{keys}Dot1+Dot2+Dot3+Dot4 PASSDOTS\{flags}+dot1+dot2+dot3+dot4
+bind \{keys}Dot1+Dot2+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot2+dot3+dot4+dot5
+bind \{keys}Dot1+Dot2+Dot3+Dot5 PASSDOTS\{flags}+dot1+dot2+dot3+dot5
+bind \{keys}Dot2+Dot3+Dot4 PASSDOTS\{flags}+dot2+dot3+dot4
+bind \{keys}Dot2+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot2+dot3+dot4+dot5
+bind \{keys}Dot1+Dot3+Dot6 PASSDOTS\{flags}+dot1+dot3+dot6
+bind \{keys}Dot1+Dot2+Dot3+Dot6 PASSDOTS\{flags}+dot1+dot2+dot3+dot6
+bind \{keys}Dot2+Dot4+Dot5+Dot6 PASSDOTS\{flags}+dot2+dot4+dot5+dot6
+bind \{keys}Dot1+Dot3+Dot4+Dot6 PASSDOTS\{flags}+dot1+dot3+dot4+dot6
+bind \{keys}Dot1+Dot3+Dot4+Dot5+Dot6 PASSDOTS\{flags}+dot1+dot3+dot4+dot5+dot6
+bind \{keys}Dot1+Dot3+Dot5+Dot6 PASSDOTS\{flags}+dot1+dot3+dot5+dot6
diff --git a/Tables.brailleback/hm/pan.ktb b/Tables.brailleback/hm/pan.ktb
new file mode 100644
index 0000000..71d6f0c
--- /dev/null
+++ b/Tables.brailleback/hm/pan.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with two scroll keys)
+
+include common.kti
+include braille.kti
+include f14.kti
+include pan.kti
diff --git a/Tables.brailleback/hm/pan.kti b/Tables.brailleback/hm/pan.kti
new file mode 100644
index 0000000..0b917d7
--- /dev/null
+++ b/Tables.brailleback/hm/pan.kti
@@ -0,0 +1,40 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Backward+Forward LNBEG
+
+bind F1+Backward PRPROMPT
+bind F1+Forward NXPROMPT
+
+bind F2+Backward PRDIFLN
+bind F2+Forward NXDIFLN
+
+bind F3+Backward ATTRUP
+bind F3+Forward ATTRDN
+
+bind F4+Backward PRPGRPH
+bind F4+Forward NXPGRPH
+
+bind F1+F2+Backward TOP_LEFT
+bind F1+F2+Forward BOT_LEFT
+
+bind F3+F4+Backward CHRLT
+bind F3+F4+Forward CHRRT
+
diff --git a/Tables.brailleback/hm/qwerty.ktb b/Tables.brailleback/hm/qwerty.ktb
new file mode 100644
index 0000000..9646a18
--- /dev/null
+++ b/Tables.brailleback/hm/qwerty.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with QWERTY keyboard)
+
+include common.kti
+include scroll.kti
+include qwerty.kti
diff --git a/Tables.brailleback/hm/qwerty.kti b/Tables.brailleback/hm/qwerty.kti
new file mode 100644
index 0000000..f1e1cc5
--- /dev/null
+++ b/Tables.brailleback/hm/qwerty.kti
@@ -0,0 +1,156 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include fnkey.kti
+hide on
+
+bind Dot7 KEY_BACKSPACE
+bind Dot8 KEY_ENTER
+
+bind Space+Dot1+Dot3+Dot6 UPPER
+bind Space+Dot1+Dot3+Dot4 META
+
+bind Space+Dot1+Dot5 KEY_ESCAPE
+bind Space+Dot2+Dot4 KEY_INSERT
+bind Space+Dot1+Dot4+Dot5 KEY_DELETE
+
+bind F2+Dot1+Dot2+Dot3+Dot4+Dot8 KEY_INSERT+control
+bind F1+Dot1+Dot2+Dot3+Dot4+Dot8 KEY_INSERT+shift+control
+
+bind Space+Dot4+Dot5 KEY_TAB
+bind Space+Dot1+Dot2 KEY_TAB+shift
+bind Space+Dot1+Dot2+Dot8 KEY_TAB+control
+bind F2+F3 KEY_TAB+meta
+bind F1+F2+F3 KEY_TAB+shift+meta
+
+bind Space+Dot1+Dot3 KEY_HOME
+bind Space+Dot4+Dot6 KEY_END
+
+bind Space+Dot1+Dot2+Dot3 KEY_HOME+control
+bind Space+Dot4+Dot5+Dot6 KEY_END+control
+
+bind Space+Dot1+Dot2+Dot6 KEY_PAGE_UP
+bind Space+Dot3+Dot4+Dot5 KEY_PAGE_DOWN
+
+bind Space+Dot1+Dot2+Dot6+Dot8 KEY_PAGE_UP+control
+bind Space+Dot3+Dot4+Dot5+Dot8 KEY_PAGE_DOWN+control
+
+bind Space+Dot1 KEY_CURSOR_UP
+bind Space+Dot4 KEY_CURSOR_DOWN
+
+bind Space+RightScrollUp KEY_CURSOR_UP+shift
+bind Space+RightScrollDown KEY_CURSOR_DOWN+shift
+
+bind Space+Dot2+Dot3 KEY_CURSOR_UP+control
+bind Space+Dot5+Dot6 KEY_CURSOR_DOWN+control
+
+bind F1+Space+Dot2+Dot3+Dot8 KEY_CURSOR_UP+shift+control
+bind F1+Space+Dot5+Dot6+Dot8 KEY_CURSOR_DOWN+shift+control
+
+bind Dot2+Dot3+Dot7 KEY_CURSOR_UP+meta
+bind Dot5+Dot6+Dot7 KEY_CURSOR_DOWN+meta
+
+bind F1+Dot2+Dot3+Dot7 KEY_CURSOR_UP+shift+meta
+bind F1+Dot5+Dot6+Dot7 KEY_CURSOR_DOWN+shift+meta
+
+bind Space+Dot3 KEY_CURSOR_LEFT
+bind Space+Dot6 KEY_CURSOR_RIGHT
+
+bind Space+Dot2 KEY_CURSOR_LEFT+control
+bind Space+Dot5 KEY_CURSOR_RIGHT+control
+
+bind F1+Space+Dot2+Dot8 KEY_CURSOR_LEFT+shift+control
+bind F1+Space+Dot5+Dot8 KEY_CURSOR_RIGHT+shift+control
+
+bind Dot2+Dot7 KEY_CURSOR_LEFT+meta
+bind Dot5+Dot7 KEY_CURSOR_RIGHT+meta
+
+bind F1+Dot2+Dot7 KEY_CURSOR_LEFT+shift+meta
+bind F1+Dot5+Dot7 KEY_CURSOR_RIGHT+shift+meta
+
+bind Space+Dot1+Dot2+Dot5 KEY_FUNCTION+0
+bind F4+Space+Dot1+Dot2 KEY_FUNCTION+1
+bind F4+Dot1+Dot2+Dot4+Dot8 KEY_FUNCTION+2
+bind F3+Dot7 KEY_FUNCTION+3
+bind F2+Dot7 KEY_FUNCTION+4
+bind F4+Dot7 KEY_FUNCTION+5
+bind F4+Space KEY_FUNCTION+6
+bind F4+Dot8 KEY_FUNCTION+7
+bind Dot3+Dot4+Dot5+Dot6+Dot7 KEY_FUNCTION+8
+bind Space+Dot1+Dot3+Dot5 KEY_FUNCTION+9
+bind Dot1+Dot4+Dot5+Dot6+Dot7 KEY_FUNCTION+10
+bind F4+Dot1+Dot2+Dot4+Dot5+Dot8 KEY_FUNCTION+11
+
+bind F1+Dot7 KEY_FUNCTION+5+shift
+bind F1+Space KEY_FUNCTION+6+shift
+bind F1+Dot8 KEY_FUNCTION+7+shift
+bind F1+F4 KEY_FUNCTION+9+meta
+
+map Space SPACE
+map Dot1 dot1
+map Dot2 dot2
+map Dot3 dot3
+map Dot4 dot4
+map Dot5 dot5
+map Dot6 dot6
+
+bind Space+Dot7+Dot4 PASSDOTS+dot4
+bind Space+Dot7+Dot2+Dot4+Dot6 PASSDOTS+dot2+dot4+dot6
+bind Space+Dot7+Dot1+Dot2+Dot5+Dot6 PASSDOTS+dot1+dot2+dot5+dot6
+bind Space+Dot7+Dot2+Dot3+Dot4+Dot5+Dot6 PASSDOTS+dot2+dot3+dot4+dot5+dot6
+bind Space+Dot7+Dot4+Dot5 PASSDOTS+dot4+dot5
+
+beginVariables
+assign keys
+assign flags
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Space+Dot7+
+assign flags +shift
+include letters.kti
+endVariables
+
+beginVariables
+assign keys F1+Dot7+
+assign flags +shift+meta
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Dot7+
+# these conflict with shifted letters
+#assign flags +meta
+assign flags +shift
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Dot8+
+assign flags +control
+include letters.kti
+endVariables
+
+beginVariables
+assign keys F1+
+assign flags +gui
+include letters.kti
+endVariables
+
+hide off
diff --git a/Tables.brailleback/hm/right.kti b/Tables.brailleback/hm/right.kti
new file mode 100644
index 0000000..6568316
--- /dev/null
+++ b/Tables.brailleback/hm/right.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RightPadUp PRDIFLN
+bind RightPadDown NXDIFLN
+bind RightPadLeft FWINLTSKIP
+bind RightPadRight FWINRTSKIP
+
+bind RightPadLeft+RightPadUp CONTEXT+CLIP_NEW
+bind RightPadLeft+RightPadDown CONTEXT+CLIP_ADD
+
+bind RightPadRight+RightPadUp CONTEXT+COPY_LINE
+bind RightPadRight+RightPadDown CONTEXT+COPY_RECT
+bind RightPadLeft+RightPadRight PASTE
+
+bind RightPadUp+RightPadDown CONTEXT+DESCCHAR
+
diff --git a/Tables.brailleback/hm/scroll.ktb b/Tables.brailleback/hm/scroll.ktb
new file mode 100644
index 0000000..b46b574
--- /dev/null
+++ b/Tables.brailleback/hm/scroll.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with four scroll keys)
+
+include common.kti
+include braille.kti
+include f14.kti
+include scroll.kti
diff --git a/Tables.brailleback/hm/scroll.kti b/Tables.brailleback/hm/scroll.kti
new file mode 100644
index 0000000..9ddc091
--- /dev/null
+++ b/Tables.brailleback/hm/scroll.kti
@@ -0,0 +1,71 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+
+####################
+# Default Bindings #
+####################
+
+bind LeftScrollUp LNUP
+bind LeftScrollDown LNDN
+
+bind RightScrollUp FWINLT
+bind RightScrollDown FWINRT
+
+bind LeftScrollUp+LeftScrollDown LNBEG
+bind RightScrollUp+RightScrollDown RETURN
+
+bind LeftScrollUp+RightScrollUp CSRTRK
+bind LeftScrollUp+RightScrollDown SIXDOTS
+bind LeftScrollDown+RightScrollUp FREEZE
+bind LeftScrollDown+RightScrollDown DISPMD
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp TOP
+bind LeftScrollUp+LeftScrollDown+RightScrollDown BOT
+
+bind RightScrollUp+RightScrollDown+LeftScrollUp INFO
+bind RightScrollUp+RightScrollDown+LeftScrollDown PREFMENU
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp+RightScrollDown HELP
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind LeftScrollUp MENU_PREV_ITEM
+bind LeftScrollDown MENU_NEXT_ITEM
+
+bind RightScrollUp MENU_PREV_SETTING
+bind RightScrollDown MENU_NEXT_SETTING
+
+bind LeftScrollUp+LeftScrollDown FWINLT
+bind RightScrollUp+RightScrollDown FWINRT
+
+bind LeftScrollUp+RightScrollUp MENU_FIRST_ITEM
+bind LeftScrollDown+RightScrollDown MENU_LAST_ITEM
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp PREFLOAD
+bind LeftScrollUp+LeftScrollDown+RightScrollDown PREFSAVE
+
+bind RightScrollUp+RightScrollDown+LeftScrollUp MENU_PREV_LEVEL
+bind RightScrollUp+RightScrollDown+LeftScrollDown PREFMENU
+
+
diff --git a/Tables.brailleback/hm/sync.ktb b/Tables.brailleback/hm/sync.ktb
new file mode 100644
index 0000000..4e65e7d
--- /dev/null
+++ b/Tables.brailleback/hm/sync.ktb
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS SyncBraille
+
+
+####################
+# Default Bindings #
+####################
+
+include common.kti
+include scroll.kti
diff --git a/Tables.brailleback/ht/ab.ktb b/Tables.brailleback/ht/ab.ktb
new file mode 100644
index 0000000..6b484ba
--- /dev/null
+++ b/Tables.brailleback/ht/ab.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Braille
+
+include ab.kti
diff --git a/Tables.brailleback/ht/ab.kti b/Tables.brailleback/ht/ab.kti
new file mode 100644
index 0000000..4f768f6
--- /dev/null
+++ b/Tables.brailleback/ht/ab.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Active Braille
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include bs.kti
diff --git a/Tables.brailleback/ht/ab_s.ktb b/Tables.brailleback/ht/ab_s.ktb
new file mode 100644
index 0000000..162e402
--- /dev/null
+++ b/Tables.brailleback/ht/ab_s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Braille S
+
+include ab.kti
+include joystick.kti
diff --git a/Tables.brailleback/ht/ac4.ktb b/Tables.brailleback/ht/ac4.ktb
new file mode 100644
index 0000000..cc1d6da
--- /dev/null
+++ b/Tables.brailleback/ht/ac4.ktb
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Activator
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include joystick.kti
+
+bind SpaceLeft+RoutingKey PRINDENT
+bind SpaceRight+RoutingKey NXINDENT
+
+#Overridden by BrailleBack.
+#bind SpaceLeft FWINLT
+#bind SpaceRight FWINRT
+#bind SpaceLeft+SpaceRight PASTE
+
+#bind B1+SpaceLeft LNBEG
+#bind B1+SpaceRight LNEND
+#bind B2+SpaceLeft TOP
+#bind B2+SpaceRight BOT
+#bind B3+SpaceLeft HWINLT
+#bind B3+SpaceRight HWINRT
+#bind B6+SpaceLeft CHRLT
+#bind B6+SpaceRight CHRRT
+#bind B2+B3+SpaceLeft MUTE
+#bind B2+B3+SpaceRight SAY_LINE
+
+include dots.kti
+
+#assign brailleOn B1+B8+SpaceRight
+#assign brailleOff B1+B8+SpaceLeft
+assign space SpaceLeft
+assign enter SpaceRight
+include input.kti
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables.brailleback/ht/alo.ktb b/Tables.brailleback/ht/alo.ktb
new file mode 100644
index 0000000..94140a9
--- /dev/null
+++ b/Tables.brailleback/ht/alo.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Actilino
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include joystick.kti
+include bs.kti
diff --git a/Tables.brailleback/ht/as40.ktb b/Tables.brailleback/ht/as40.ktb
new file mode 100644
index 0000000..ed64fe3
--- /dev/null
+++ b/Tables.brailleback/ht/as40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Star 40
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include bs.kti
diff --git a/Tables.brailleback/ht/bb.ktb b/Tables.brailleback/ht/bb.ktb
new file mode 100644
index 0000000..62a4ae2
--- /dev/null
+++ b/Tables.brailleback/ht/bb.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Basic Braille
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables.brailleback/ht/bbp.ktb b/Tables.brailleback/ht/bbp.ktb
new file mode 100644
index 0000000..698451d
--- /dev/null
+++ b/Tables.brailleback/ht/bbp.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Basic Braille Plus
+
+include bs.kti
diff --git a/Tables.brailleback/ht/bkwm.ktb b/Tables.brailleback/ht/bkwm.ktb
new file mode 100644
index 0000000..0668a8a
--- /dev/null
+++ b/Tables.brailleback/ht/bkwm.ktb
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Bookworm
+
+
+####################
+# Default Bindings #
+####################
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Escape CSRTRK
+bind Escape+Backward BACK
+bind Escape+Forward DISPMD
+bind Enter ROUTE
+bind Enter+Backward LNUP
+bind Enter+Forward LNDN
+bind Escape+Enter PREFMENU
+bind Escape+Enter+Backward LNBEG
+bind Escape+Enter+Forward LNEND
+bind Backward+Forward HELP
+bind Backward+Forward+Escape CSRSIZE
+bind Backward+Forward+Enter FREEZE
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Escape PREFLOAD
+bind Escape+Backward MENU_PREV_SETTING
+bind Escape+Forward MENU_NEXT_SETTING
+bind Enter PREFMENU
+bind Enter+Backward MENU_PREV_ITEM
+bind Enter+Forward MENU_NEXT_ITEM
+bind Escape+Enter PREFSAVE
+bind Escape+Enter+Backward MENU_FIRST_ITEM
+bind Escape+Enter+Forward MENU_LAST_ITEM
+bind Backward+Forward NOOP
+bind Backward+Forward+Escape NOOP
+bind Backward+Forward+Enter NOOP
diff --git a/Tables.brailleback/ht/brln.ktb b/Tables.brailleback/ht/brln.ktb
new file mode 100644
index 0000000..14af612
--- /dev/null
+++ b/Tables.brailleback/ht/brln.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braillino
+
+include bs.kti
diff --git a/Tables.brailleback/ht/bs.kti b/Tables.brailleback/ht/bs.kti
new file mode 100644
index 0000000..08a2754
--- /dev/null
+++ b/Tables.brailleback/ht/bs.kti
@@ -0,0 +1,70 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Braille Star
+
+#Overridden by BrailleBack
+#bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind SpaceLeft+RoutingKey PRINDENT
+bind SpaceRight+RoutingKey NXINDENT
+
+bind SpaceLeft FWINLT
+bind SpaceRight FWINRT
+bind SpaceLeft+SpaceRight PASTE
+
+bind B1+SpaceLeft LNBEG
+bind B1+SpaceRight LNEND
+bind B2+SpaceLeft TOP
+bind B2+SpaceRight BOT
+bind B3+SpaceLeft HWINLT
+bind B3+SpaceRight HWINRT
+bind B6+SpaceLeft CHRLT
+bind B6+SpaceRight CHRRT
+bind B2+B3+SpaceLeft MUTE
+bind B2+B3+SpaceRight SAY_LINE
+
+include dots.kti
+include rockers.kti
+
+assign brailleOn B1+B8+SpaceRight
+assign brailleOff B1+B8+SpaceLeft
+assign space SpaceLeft
+assign enter SpaceRight
+include input.kti
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
+
+assign space SpaceLeft
+include dots.kti
+
+assign space SpaceRight
+include dots.kti
+
+assign space SpaceLeft
+include input.kti
+
+assign space SpaceRight
+include input.kti
diff --git a/Tables.brailleback/ht/bs40.ktb b/Tables.brailleback/ht/bs40.ktb
new file mode 100644
index 0000000..75fadfa
--- /dev/null
+++ b/Tables.brailleback/ht/bs40.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Star 40
+
+include bs.kti
diff --git a/Tables.brailleback/ht/bs80.ktb b/Tables.brailleback/ht/bs80.ktb
new file mode 100644
index 0000000..b36515e
--- /dev/null
+++ b/Tables.brailleback/ht/bs80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Star 80
+
+include bs.kti
+include keypad.kti
diff --git a/Tables.brailleback/ht/cb40.ktb b/Tables.brailleback/ht/cb40.ktb
new file mode 100644
index 0000000..d9672d5
--- /dev/null
+++ b/Tables.brailleback/ht/cb40.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Connect Braille 40
+
+include bs.kti
diff --git a/Tables.brailleback/ht/dots.kti b/Tables.brailleback/ht/dots.kti
new file mode 100644
index 0000000..920645f
--- /dev/null
+++ b/Tables.brailleback/ht/dots.kti
@@ -0,0 +1,83 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech braille dot keys
+
+#Overridden by BrailleBack.
+#bind B1 HOME
+#bind B2 TOP_LEFT
+#bind B3 BACK
+#bind B4 LNUP
+#bind B5 LNDN
+#bind B1+B4 PRPGRPH
+#bind B1+B5 NXPGRPH
+#bind B2+B4 PRPROMPT
+#bind B2+B5 NXPROMPT
+#bind B3+B4 PRSEARCH
+#bind B3+B5 NXSEARCH
+#bind B4+B5 LEARN
+#bind B6+B4 ATTRUP
+#bind B6+B5 ATTRDN
+#bind B7+B4 WINUP
+#bind B7+B5 WINDN
+#bind B8+B4 PRDIFLN
+#bind B8+B5 NXDIFLN
+#bind B8 HELP
+#bind B8+B1 CSRTRK
+#bind B8+B2 CSRVIS
+#bind B8+B3 ATTRVIS
+#bind B8+B6 FREEZE
+#bind B8+B7 TUNES
+#bind B7 SIXDOTS
+#bind B7+B1 PREFMENU
+#bind B7+B2 PREFLOAD
+#bind B7+B3 PREFSAVE
+#bind B7+B6 INFO
+#bind B6 DISPMD
+#bind B6+B1 SKPIDLNS
+#bind B6+B2 SKPBLNKWINS
+#bind B6+B3 SLIDEWIN
+
+#bind B2+B3+B5+B6 TIME
+
+bind B1+RoutingKey SETLEFT
+bind B2+RoutingKey DESCCHAR
+bind B3+RoutingKey CLIP_ADD
+bind B4+RoutingKey CLIP_NEW
+bind B5+RoutingKey COPY_RECT
+bind B6+RoutingKey COPY_LINE
+bind B7+RoutingKey SETMARK
+bind B8+RoutingKey GOTOMARK
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# Needs to match up with input.kti
+assign chord \{space}
+assign chord1 B4
+assign chord2 B3
+assign chord3 B2
+assign chord4 B5
+assign chord5 B6
+assign chord6 B7
+assign chord7 B1
+assign chord8 B8
+
+include ../android-chords-google.kti
diff --git a/Tables.brailleback/ht/easy.ktb b/Tables.brailleback/ht/easy.ktb
new file mode 100644
index 0000000..362fe29
--- /dev/null
+++ b/Tables.brailleback/ht/easy.ktb
@@ -0,0 +1,52 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Easy Braille
+
+bind Left FWINLT
+bind Right FWINRT
+
+#Overridden by BrailleBack.
+#bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+include dots.kti
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/ht/input.kti b/Tables.brailleback/ht/input.kti
new file mode 100644
index 0000000..8b8142e
--- /dev/null
+++ b/Tables.brailleback/ht/input.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Unused by Braille display
+# context braille Braille Input Mode
+map B4 DOT1
+map B3 DOT2
+map B2 DOT3
+map B5 DOT4
+map B6 DOT5
+map B7 DOT6
+map B1 DOT7
+map B8 DOT8
+bind \{space} PASSDOTS
+bind \{enter} KEY_ENTER
+bind \{space}+B1 KEY_BACKSPACE
+map \{space} CONTROL
+map \{enter} META
+bind \{brailleOff} CONTEXT+default
+
+# Unused by Braille display
+# context default
+bind \{brailleOn} CONTEXT+braille
+bind \{brailleOff} CONTEXT+default
diff --git a/Tables.brailleback/ht/joystick.kti b/Tables.brailleback/ht/joystick.kti
new file mode 100644
index 0000000..74c3f2c
--- /dev/null
+++ b/Tables.brailleback/ht/joystick.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech: Actilino, Activator
+
+bind Left KEY_CURSOR_LEFT
+bind Right KEY_CURSOR_RIGHT
+bind Up KEY_CURSOR_UP
+bind Down KEY_CURSOR_DOWN
+bind Action KEY_ENTER
+
+
diff --git a/Tables.brailleback/ht/keypad.kti b/Tables.brailleback/ht/keypad.kti
new file mode 100644
index 0000000..28e37d6
--- /dev/null
+++ b/Tables.brailleback/ht/keypad.kti
@@ -0,0 +1,104 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech keypad keys
+note The 16-key pad is arranged in four columns and four rows.
+note * The keys in the first (top) row are named: B9, One, Two, Three.
+note * The keys in the second row are named: B10, Four, Five, Six.
+note * The keys in the third row are named: B11, Seven, Eight, Nine.
+note * The keys in the fourth (bottom) row are named: B12, B13, Zero, B14.
+
+bind B9 SAY_ABOVE
+bind B10 SAY_LINE
+bind B11 SAY_BELOW
+bind B12 MUTE
+bind Zero SPKHOME
+bind B13 SWITCHVT_PREV
+bind B14 SWITCHVT_NEXT
+bind Eight MENU_PREV_ITEM
+bind Nine MENU_FIRST_ITEM
+bind Four MENU_PREV_SETTING
+bind Five PREFSAVE
+bind Six MENU_NEXT_SETTING
+bind One PREFMENU
+bind Two MENU_NEXT_ITEM
+bind Three MENU_LAST_ITEM
+
+bind Zero+Seven KEY_HOME
+bind Zero+Eight KEY_CURSOR_UP
+bind Zero+Nine KEY_PAGE_UP
+bind Zero+Four KEY_CURSOR_LEFT
+bind Zero+Six KEY_CURSOR_RIGHT
+bind Zero+One KEY_END
+bind Zero+Two KEY_CURSOR_DOWN
+bind Zero+Three KEY_PAGE_DOWN
+bind Zero+B13 KEY_INSERT
+bind Zero+B14 KEY_DELETE
+
+bind B9+One SETMARK+0
+bind B9+Two SETMARK+1
+bind B9+Three SETMARK+2
+bind B9+Four SETMARK+3
+bind B9+Five SETMARK+4
+bind B9+Six SETMARK+5
+bind B9+Seven SETMARK+6
+bind B9+Eight SETMARK+7
+bind B9+Nine SETMARK+8
+bind B9+Zero SETMARK+9
+bind B9+B13 SETMARK+10
+bind B9+B14 SETMARK+11
+
+bind B10+One GOTOMARK+0
+bind B10+Two GOTOMARK+1
+bind B10+Three GOTOMARK+2
+bind B10+Four GOTOMARK+3
+bind B10+Five GOTOMARK+4
+bind B10+Six GOTOMARK+5
+bind B10+Seven GOTOMARK+6
+bind B10+Eight GOTOMARK+7
+bind B10+Nine GOTOMARK+8
+bind B10+Zero GOTOMARK+9
+bind B10+B13 GOTOMARK+10
+bind B10+B14 GOTOMARK+11
+
+bind B11+One SWITCHVT+0
+bind B11+Two SWITCHVT+1
+bind B11+Three SWITCHVT+2
+bind B11+Four SWITCHVT+3
+bind B11+Five SWITCHVT+4
+bind B11+Six SWITCHVT+5
+bind B11+Seven SWITCHVT+6
+bind B11+Eight SWITCHVT+7
+bind B11+Nine SWITCHVT+8
+bind B11+Zero SWITCHVT+9
+bind B11+B13 SWITCHVT+10
+bind B11+B14 SWITCHVT+11
+
+bind B12+One KEY_FUNCTION+0
+bind B12+Two KEY_FUNCTION+1
+bind B12+Three KEY_FUNCTION+2
+bind B12+Four KEY_FUNCTION+3
+bind B12+Five KEY_FUNCTION+4
+bind B12+Six KEY_FUNCTION+5
+bind B12+Seven KEY_FUNCTION+6
+bind B12+Eight KEY_FUNCTION+7
+bind B12+Nine KEY_FUNCTION+8
+bind B12+Zero KEY_FUNCTION+9
+bind B12+B13 KEY_FUNCTION+10
+bind B12+B14 KEY_FUNCTION+11
+
diff --git a/Tables.brailleback/ht/mc88.ktb b/Tables.brailleback/ht/mc88.ktb
new file mode 100644
index 0000000..495a431
--- /dev/null
+++ b/Tables.brailleback/ht/mc88.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Connect 88
+
+include me.kti
+include keypad.kti
diff --git a/Tables.brailleback/ht/mdlr.ktb b/Tables.brailleback/ht/mdlr.ktb
new file mode 100644
index 0000000..cb1b25d
--- /dev/null
+++ b/Tables.brailleback/ht/mdlr.ktb
@@ -0,0 +1,48 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left FWINLT
+bind Right FWINRT
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+bind Status1 HELP
+bind Status2 PREFMENU
+bind Status3 INFO
+bind Status4 FREEZE
+
+include dots.kti
+include keypad.kti
diff --git a/Tables.brailleback/ht/me.kti b/Tables.brailleback/ht/me.kti
new file mode 100644
index 0000000..896b457
--- /dev/null
+++ b/Tables.brailleback/ht/me.kti
@@ -0,0 +1,46 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Modular Evolution
+
+bind Left FWINLT
+bind Right FWINRT
+include rockers.kti
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+bind B1+B4+Left TOUCH_NAV
+
+include dots.kti
diff --git a/Tables.brailleback/ht/me64.ktb b/Tables.brailleback/ht/me64.ktb
new file mode 100644
index 0000000..e75f4eb
--- /dev/null
+++ b/Tables.brailleback/ht/me64.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Evolution 64
+
+include me.kti
diff --git a/Tables.brailleback/ht/me88.ktb b/Tables.brailleback/ht/me88.ktb
new file mode 100644
index 0000000..ef1b579
--- /dev/null
+++ b/Tables.brailleback/ht/me88.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Evolution 88
+
+include me.kti
+include keypad.kti
diff --git a/Tables.brailleback/ht/rockers.kti b/Tables.brailleback/ht/rockers.kti
new file mode 100644
index 0000000..cff2410
--- /dev/null
+++ b/Tables.brailleback/ht/rockers.kti
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech rocker keys
+
+bind LeftRockerTop KEY_CURSOR_UP
+bind LeftRockerBottom KEY_CURSOR_DOWN
+bind RightRockerTop LNUP
+bind RightRockerBottom LNDN
+bind LeftRockerTop+LeftRockerBottom FWINLT
+bind RightRockerTop+RightRockerBottom FWINRT
+bind LeftRockerTop+LeftRockerBottom+RightRockerTop+RightRockerBottom HOME
+bind LeftRockerTop+LeftRockerBottom+B3 HOME
+bind RightRockerTop+RightRockerBottom+B6 HOME
+bind RightRockerTop+RightRockerBottom+LeftRockerTop TOP_LEFT
+bind RightRockerTop+RightRockerBottom+B5 TOP_LEFT
+bind LeftRockerTop+B3 TOP_LEFT
+bind RightRockerTop+RightRockerBottom+LeftRockerBottom BOT_LEFT
+bind RightRockerTop+RightRockerBottom+B7 BOT_LEFT
+bind LeftRockerBottom+B3 BOT_LEFT
+bind LeftRockerTop+LeftRockerBottom+RightRockerTop TOP
+bind LeftRockerTop+LeftRockerBottom+B4 TOP
+bind RightRockerTop+B6 TOP
+bind LeftRockerTop+LeftRockerBottom+RightRockerBottom BOT
+bind LeftRockerTop+LeftRockerBottom+B2 BOT
+bind RightRockerBottom+B6 BOT
+bind LeftRockerTop+RightRockerTop PRDIFLN
+bind LeftRockerTop+B4 PRDIFLN
+bind RightRockerTop+B5 PRDIFLN
+bind LeftRockerTop+RightRockerBottom NXDIFLN
+bind LeftRockerTop+B2 NXDIFLN
+bind RightRockerBottom+B5 NXDIFLN
+bind LeftRockerBottom+RightRockerTop ATTRUP
+bind LeftRockerBottom+B4 ATTRUP
+bind RightRockerTop+B7 ATTRUP
+bind LeftRockerBottom+RightRockerBottom ATTRDN
+bind LeftRockerBottom+B2 ATTRDN
+bind RightRockerBottom+B7 ATTRDN
+
+bind LeftRockerTop+RoutingKey CLIP_NEW
+bind LeftRockerTop+LeftRockerBottom+RoutingKey KEY_FUNCTION
+bind LeftRockerBottom+RoutingKey CLIP_ADD
+bind RightRockerTop+RoutingKey COPY_LINE
+bind RightRockerTop+RightRockerBottom+RoutingKey SWITCHVT
+bind RightRockerBottom+RoutingKey COPY_RECT
+
+
+context menu
+bind RightRockerTop MENU_PREV_ITEM
+bind RightRockerBottom MENU_NEXT_ITEM
+bind LeftRockerTop MENU_PREV_SETTING
+bind LeftRockerBottom MENU_NEXT_SETTING
+
diff --git a/Tables.brailleback/ht/wave.ktb b/Tables.brailleback/ht/wave.ktb
new file mode 100644
index 0000000..47a0b0a
--- /dev/null
+++ b/Tables.brailleback/ht/wave.ktb
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Wave
+
+bind Left FWINLT
+bind Right FWINRT
+
+#Overridden by BrailleBack.
+#bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+include dots.kti
+
+bind Escape+Space+Return INFO
+
+assign brailleOn B1+B8+Right
+assign brailleOff B1+B8+Left
+assign space Space
+assign enter Return
+include input.kti
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# ROUTE+128 is a long-press on the routing key.
+bind RoutingKey ROUTE:ROUTE+128
diff --git a/Tables.brailleback/hw/B80.ktb b/Tables.brailleback/hw/B80.ktb
new file mode 100644
index 0000000..61bb205
--- /dev/null
+++ b/Tables.brailleback/hw/B80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant B 80
+
+include thumb.kti
+include command.kti
diff --git a/Tables.brailleback/hw/BI14.ktb b/Tables.brailleback/hw/BI14.ktb
new file mode 100644
index 0000000..e1f2a03
--- /dev/null
+++ b/Tables.brailleback/hw/BI14.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 14
+
+include thumb.kti
+include braille.kti
+include joystick.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/BI20X.ktb b/Tables.brailleback/hw/BI20X.ktb
new file mode 100644
index 0000000..24e7107
--- /dev/null
+++ b/Tables.brailleback/hw/BI20X.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 20X
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/BI32.ktb b/Tables.brailleback/hw/BI32.ktb
new file mode 100644
index 0000000..6d54540
--- /dev/null
+++ b/Tables.brailleback/hw/BI32.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 32
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables.brailleback/hw/BI40.ktb b/Tables.brailleback/hw/BI40.ktb
new file mode 100644
index 0000000..b06acbf
--- /dev/null
+++ b/Tables.brailleback/hw/BI40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 40
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables.brailleback/hw/BI40X.ktb b/Tables.brailleback/hw/BI40X.ktb
new file mode 100644
index 0000000..757ffcb
--- /dev/null
+++ b/Tables.brailleback/hw/BI40X.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 40X
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables.brailleback/hw/C20.ktb b/Tables.brailleback/hw/C20.ktb
new file mode 100644
index 0000000..706ac28
--- /dev/null
+++ b/Tables.brailleback/hw/C20.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Chameleon 20
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/M40.ktb b/Tables.brailleback/hw/M40.ktb
new file mode 100644
index 0000000..c5e5be9
--- /dev/null
+++ b/Tables.brailleback/hw/M40.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Mantis Q40
+
+include thumb.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/NLS.ktb b/Tables.brailleback/hw/NLS.ktb
new file mode 100644
index 0000000..c1a41aa
--- /dev/null
+++ b/Tables.brailleback/hw/NLS.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NLS eReader
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/braille.kti b/Tables.brailleback/hw/braille.kti
new file mode 100644
index 0000000..92f7f9d
--- /dev/null
+++ b/Tables.brailleback/hw/braille.kti
@@ -0,0 +1,48 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There's an eight-key braille keyboard near the back of the top.
+note * From left to right, its keys are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+
+assign chord Space+
+include ../chords.kti
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+map ThumbLeft META
+map ThumbRight CONTROL
+
+bind Space+RoutingKey KEY_FUNCTION
+bind ThumbLeft+Space+RoutingKey KEY_FUNCTION+meta
+bind ThumbRight+Space+RoutingKey KEY_FUNCTION+control
+bind ThumbLeft+ThumbRight+Space+RoutingKey KEY_FUNCTION+meta+control
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord Space
+include ../android-chords.kti
diff --git a/Tables.brailleback/hw/command.kti b/Tables.brailleback/hw/command.kti
new file mode 100644
index 0000000..00029ec
--- /dev/null
+++ b/Tables.brailleback/hw/command.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables.brailleback/hw/joystick.kti b/Tables.brailleback/hw/joystick.kti
new file mode 100644
index 0000000..e10c74c
--- /dev/null
+++ b/Tables.brailleback/hw/joystick.kti
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There's a five-way joystick between the Dot1 and Dot4 keys.
+note * The four directions are intuitively named: Up, Down, Left, Right.
+note * Pressing the joystick is named: Action.
+
+bind Up LNUP
+bind Down LNDN
+bind Left FWINLT
+bind Right FWINRT
+bind Action HOME
+
+bind Dot1+Up TOP
+bind Dot1+Down BOT
+bind Dot1+Left LNBEG
+bind Dot1+Right LNEND
+bind Dot1+Action BACK
+
+bind Dot2+Up KEY_CURSOR_UP
+bind Dot2+Down KEY_CURSOR_DOWN
+bind Dot2+Left KEY_CURSOR_LEFT
+bind Dot2+Right KEY_CURSOR_RIGHT
+bind Dot2+Action KEY_DELETE
+
+bind Dot3+Up KEY_PAGE_UP
+bind Dot3+Down KEY_PAGE_DOWN
+bind Dot3+Left KEY_HOME
+bind Dot3+Right KEY_END
+bind Dot3+Action KEY_INSERT
+
+bind Dot4+Up PRPROMPT
+bind Dot4+Down NXPROMPT
+bind Dot4+Left PRPGRPH
+bind Dot4+Right NXPGRPH
+bind Dot4+Action CSRTRK
+
+bind Dot5+Up PRDIFLN
+bind Dot5+Down NXDIFLN
+bind Dot5+Left FWINLTSKIP
+bind Dot5+Right FWINRTSKIP
+bind Dot5+Action CSRVIS
+
+bind Dot6+Up ATTRUP
+bind Dot6+Down ATTRDN
+bind Dot6+Left CHRLT
+bind Dot6+Right CHRRT
+bind Dot6+Action ATTRVIS
+
+bind Dot7+Up SAY_ABOVE
+bind Dot7+Down SAY_BELOW
+bind Dot7+Left MUTE
+bind Dot7+Right SAY_LINE
+bind Dot7+Action AUTOSPEAK
+
+bind Dot8+Up SAY_LOUDER
+bind Dot8+Down SAY_SOFTER
+bind Dot8+Left SAY_SLOWER
+bind Dot8+Right SAY_FASTER
+bind Dot8+Action SPKHOME
+
+bind RoutingKey+Up PRINDENT
+bind RoutingKey+Down NXINDENT
+bind RoutingKey+Left PRDIFCHAR
+bind RoutingKey+Right NXDIFCHAR
+bind RoutingKey+Action DESCCHAR
+
+
+########################
+# BrailleBack Bindings #
+########################
+bind Action ROUTE:ROUTE+127
+bind Dot6+Action ROUTE:ROUTE+255
diff --git a/Tables.brailleback/hw/one.ktb b/Tables.brailleback/hw/one.ktb
new file mode 100644
index 0000000..de00c27
--- /dev/null
+++ b/Tables.brailleback/hw/one.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare BrailleOne
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/hw/routing.kti b/Tables.brailleback/hw/routing.kti
new file mode 100644
index 0000000..f88bc47
--- /dev/null
+++ b/Tables.brailleback/hw/routing.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note RoutingKey refers to any of the keys immediately behind the braille cells.
+
+bind RoutingKey ROUTE
diff --git a/Tables.brailleback/hw/thumb.kti b/Tables.brailleback/hw/thumb.kti
new file mode 100644
index 0000000..dae2186
--- /dev/null
+++ b/Tables.brailleback/hw/thumb.kti
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault previous ThumbPrevious
+assignDefault left ThumbLeft
+assignDefault right ThumbRight
+assignDefault next ThumbNext
+
+note There are four rectangular thumb keys on the front. From left to right:
+note * The outer ones are named ThumbPrevious and ThumbNext.
+note * The inner ones are named ThumbLeft and ThumbRight.
+note * Some models have a round key in the middle - it's the internal menu key.
+
+####################
+# Default Bindings #
+####################
+
+bind ThumbLeft+ThumbRight HOME
+bind \{left} FWINLT
+bind \{right} FWINRT
+bind \{previous} LNUP
+bind \{next} LNDN
+
+bind \{left}+\{previous} TOP_LEFT
+bind \{left}+\{next} BOT_LEFT
+bind \{right}+\{previous} PRDIFLN
+bind \{right}+\{next} NXDIFLN
+
+bind ThumbPrevious+RoutingKey CLIP_NEW
+bind ThumbLeft+RoutingKey CLIP_ADD
+bind \{right}+RoutingKey COPY_LINE
+bind \{next}+RoutingKey COPY_RECT
+bind ThumbPrevious+ThumbNext PASTE
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind \{left} FWINLT
+bind \{right} FWINRT
+bind \{previous} MENU_PREV_ITEM
+bind \{next} MENU_NEXT_ITEM
+bind \{left}+\{previous} MENU_FIRST_ITEM
+bind \{left}+\{next} MENU_LAST_ITEM
+bind \{right}+\{previous} MENU_PREV_SETTING
+bind \{right}+\{next} MENU_NEXT_SETTING
+bind ThumbLeft+ThumbRight PREFMENU
+bind ThumbLeft+ThumbRight+ThumbPrevious PREFLOAD
+bind ThumbLeft+ThumbRight+ThumbNext PREFSAVE
+
diff --git a/Tables.brailleback/hw/thumb_legacy.kti b/Tables.brailleback/hw/thumb_legacy.kti
new file mode 100644
index 0000000..c4b0cf2
--- /dev/null
+++ b/Tables.brailleback/hw/thumb_legacy.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# If you'd like to have the old (pre-6.4) thumb key bindings then
+# copy this file into the user customizations directory like this:
+# cp thumb_legacy.kti /etc/xdg/brltty/thumb.kti
+
+assign previous ThumbLeft
+assign left ThumbPrevious
+assign right ThumbNext
+assign next ThumbRight
+include thumb.kti
diff --git a/Tables.brailleback/hw/touch.ktb b/Tables.brailleback/hw/touch.ktb
new file mode 100644
index 0000000..60e94f4
--- /dev/null
+++ b/Tables.brailleback/hw/touch.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare BrailleNote Touch
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables.brailleback/menu.kti b/Tables.brailleback/menu.kti
new file mode 100644
index 0000000..7832fc8
--- /dev/null
+++ b/Tables.brailleback/menu.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context menu
+
+bind Dot1 MENU_PREV_ITEM
+bind Dot4 MENU_NEXT_ITEM
+
+bind Dot2 MENU_FIRST_ITEM
+bind Dot5 MENU_LAST_ITEM
+
+bind Dot3 MENU_PREV_SETTING
+bind Dot6 MENU_NEXT_SETTING
+
+bind Dot7 MENU_PREV_LEVEL
+bind Dot8 PREFMENU
+
diff --git a/Tables.brailleback/nav.kti b/Tables.brailleback/nav.kti
new file mode 100644
index 0000000..a4ad695
--- /dev/null
+++ b/Tables.brailleback/nav.kti
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Up LNUP
+bind Down LNDN
+bind Left FWINLT
+bind Right FWINRT
+
+bind Dot4+Up TOP
+bind Dot4+Down BOT
+bind Dot4+Left LNBEG
+bind Dot4+Right LNEND
+
+bind Dot5+Up PRDIFLN
+bind Dot5+Down NXDIFLN
+bind Dot5+Left FWINLTSKIP
+bind Dot5+Right FWINRTSKIP
+
+bind Dot6+Up ATTRUP
+bind Dot6+Down ATTRDN
+bind Dot6+Left CHRLT
+bind Dot6+Right CHRRT
+
+Bind Dot4+Dot6+Up PRPGRPH
+Bind Dot4+Dot6+Down NXPGRPH
+Bind Dot4+Dot6+Left PRPROMPT
+Bind Dot4+Dot6+Right NXPROMPT
+
+Bind Dot4+Dot5+Dot6+Up PREFMENU
+Bind Dot4+Dot5+Dot6+Down TIME
+Bind Dot4+Dot5+Dot6+Left PREFLOAD
+Bind Dot4+Dot5+Dot6+Right PREFSAVE
+
+Bind Dot4+Dot5+Up FREEZE
+Bind Dot4+Dot5+Down DISPMD
+Bind Dot4+Dot5+Left COMPBRL6
+Bind Dot4+Dot5+Right CONTRACTED
+
+Bind Dot5+Dot6+Up PRSEARCH
+Bind Dot5+Dot6+Down NXSEARCH
+Bind Dot5+Dot6+Left SWITCHVT_PREV
+Bind Dot5+Dot6+Right SWITCHVT_NEXT
+
+bind Dot1+Up KEY_CURSOR_UP
+bind Dot1+Down KEY_CURSOR_DOWN
+bind Dot1+Left KEY_CURSOR_LEFT
+bind Dot1+Right KEY_CURSOR_RIGHT
+
+bind Dot2+Up KEY_PAGE_UP
+bind Dot2+Down KEY_PAGE_DOWN
+bind Dot2+Left KEY_HOME
+bind Dot2+Right KEY_END
+
+bind Dot3+Up KEY_ESCAPE
+bind Dot3+Down KEY_TAB
+bind Dot3+Left KEY_DELETE
+bind Dot3+Right KEY_INSERT
+
+bind Dot1+Dot2+Up SAY_ABOVE
+bind Dot1+Dot2+Down SAY_BELOW
+bind Dot1+Dot2+Left MUTE
+bind Dot1+Dot2+Right SAY_LINE
+
+bind Dot2+Dot3+Up SAY_LOUDER
+bind Dot2+Dot3+Down SAY_SOFTER
+bind Dot2+Dot3+Left SAY_SLOWER
+bind Dot2+Dot3+Right SAY_FASTER
+
+bind Dot1+Dot3+Up AUTOSPEAK
+bind Dot1+Dot3+Down SPKHOME
+bind Dot1+Dot3+Left SPEAK_INDENT
+bind Dot1+Dot3+Right SAY_ALL
+
diff --git a/Tables.brailleback/pm/2d_l.ktb b/Tables.brailleback/pm/2d_l.ktb
new file mode 100644
index 0000000..53b8f4f
--- /dev/null
+++ b/Tables.brailleback/pm/2d_l.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX 2D Lite (plus)
+
+assign statusKeys 13
+include front9.kti
diff --git a/Tables.brailleback/pm/2d_s.ktb b/Tables.brailleback/pm/2d_s.ktb
new file mode 100644
index 0000000..e05bf6d
--- /dev/null
+++ b/Tables.brailleback/pm/2d_s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX 2D Screen Soft
+
+assign statusKeys 22
+include front13.kti
diff --git a/Tables.brailleback/pm/bar.kti b/Tables.brailleback/pm/bar.kti
new file mode 100644
index 0000000..9e8be61
--- /dev/null
+++ b/Tables.brailleback/pm/bar.kti
@@ -0,0 +1,98 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have an Easy Access Bar.
+
+note The long key on the front is the Easy Access Bar.
+
+ifVar hasSingleStepBar
+  note * It can only be moved one step in each direction (left, right, up, down).
+  note * To emulate the second step, also press any of the lower routing keys
+  note * (those in the row just behind the text cells).
+else
+  note * It can be moved two steps in each direction (left, right, up, down).
+endIf
+
+bind BarUp1 LNUP
+bind BarDown1 LNDN
+bind BarLeft1 FWINLT
+bind BarRight1 FWINRT
+
+bind BarUp2 TOP
+bind BarDown2 BOT
+bind BarLeft2 LNBEG
+bind BarRight2 LNEND
+
+hide on
+bind BarUp1+BarUp2 TOP
+bind BarDown1+BarDown2 BOT
+bind BarLeft1+BarLeft2 LNBEG
+bind BarRight1+BarRight2 LNEND
+hide off
+
+include routing.kti
+
+ifNotVar hasSingleStepBar
+  bind BarUp1+RoutingKey1 PRINDENT
+  bind BarDown1+RoutingKey1 NXINDENT
+  bind BarLeft1+RoutingKey1 CLIP_ADD
+  bind BarRight1+RoutingKey1 COPY_LINE
+
+  bind BarUp2+RoutingKey1 SETLEFT
+  bind BarDown2+RoutingKey1 DESCCHAR
+  bind BarLeft2+RoutingKey1 CLIP_NEW
+  bind BarRight2+RoutingKey1 COPY_RECT
+
+  hide on
+  bind BarUp1+BarUp2+RoutingKey1 SETLEFT
+  bind BarDown1+BarDown2+RoutingKey1 DESCCHAR
+  bind BarLeft1+BarLeft2+RoutingKey1 CLIP_NEW
+  bind BarRight1+BarRight2+RoutingKey1 COPY_RECT
+  hide off
+endIf
+
+assign toggleOff BarLeft1
+assign toggleOn BarRight1
+include status\{statusKeys}.kti
+
+include keys.kti
+
+ifKey RoutingKey2
+endIf
+
+ifKey StatusKey2 bind !StatusKey2 GOTOLINE
+
+
+context menu
+bind BarUp1 MENU_PREV_ITEM
+bind BarDown1 MENU_NEXT_ITEM
+bind BarUp2 MENU_FIRST_ITEM
+bind BarDown2 MENU_LAST_ITEM
+bind BarLeft1 MENU_PREV_SETTING
+bind BarRight1 MENU_NEXT_SETTING
+bind BarLeft2 PREFLOAD
+bind BarRight2 PREFSAVE
+
+hide on
+bind BarUp1+BarUp2 MENU_FIRST_ITEM
+bind BarDown1+BarDown2 MENU_LAST_ITEM
+bind BarLeft1+BarLeft2 PREFLOAD
+bind BarRight1+BarRight2 PREFSAVE
+hide off
+
+
diff --git a/Tables.brailleback/pm/c.ktb b/Tables.brailleback/pm/c.ktb
new file mode 100644
index 0000000..aae0039
--- /dev/null
+++ b/Tables.brailleback/pm/c.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Compact/Tiny
+
+assign statusKeys 0
+include front9.kti
diff --git a/Tables.brailleback/pm/c_486.ktb b/Tables.brailleback/pm/c_486.ktb
new file mode 100644
index 0000000..4387182
--- /dev/null
+++ b/Tables.brailleback/pm/c_486.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Compact 486
+
+assign statusKeys 0
+include front9.kti
diff --git a/Tables.brailleback/pm/el2d_80s.ktb b/Tables.brailleback/pm/el2d_80s.ktb
new file mode 100644
index 0000000..bb05540
--- /dev/null
+++ b/Tables.brailleback/pm/el2d_80s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL2D-80s
+
+assign statusKeys 20
+include bar.kti
diff --git a/Tables.brailleback/pm/el40c.ktb b/Tables.brailleback/pm/el40c.ktb
new file mode 100644
index 0000000..81f303f
--- /dev/null
+++ b/Tables.brailleback/pm/el40c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL40c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el40s.ktb b/Tables.brailleback/pm/el40s.ktb
new file mode 100644
index 0000000..9c743a4
--- /dev/null
+++ b/Tables.brailleback/pm/el40s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL40s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el60c.ktb b/Tables.brailleback/pm/el60c.ktb
new file mode 100644
index 0000000..69e8c97
--- /dev/null
+++ b/Tables.brailleback/pm/el60c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL60c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el66s.ktb b/Tables.brailleback/pm/el66s.ktb
new file mode 100644
index 0000000..33cfb61
--- /dev/null
+++ b/Tables.brailleback/pm/el66s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL66s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el70s.ktb b/Tables.brailleback/pm/el70s.ktb
new file mode 100644
index 0000000..4946713
--- /dev/null
+++ b/Tables.brailleback/pm/el70s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL70s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el80_ii.ktb b/Tables.brailleback/pm/el80_ii.ktb
new file mode 100644
index 0000000..f85d229
--- /dev/null
+++ b/Tables.brailleback/pm/el80_ii.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80-II
+
+assign statusKeys 2
+include bar.kti
diff --git a/Tables.brailleback/pm/el80c.ktb b/Tables.brailleback/pm/el80c.ktb
new file mode 100644
index 0000000..fecbab9
--- /dev/null
+++ b/Tables.brailleback/pm/el80c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el80s.ktb b/Tables.brailleback/pm/el80s.ktb
new file mode 100644
index 0000000..94b61bc
--- /dev/null
+++ b/Tables.brailleback/pm/el80s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/el_2d_40.ktb b/Tables.brailleback/pm/el_2d_40.ktb
new file mode 100644
index 0000000..12b3b83
--- /dev/null
+++ b/Tables.brailleback/pm/el_2d_40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-40
+
+assign statusKeys 13
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/el_2d_66.ktb b/Tables.brailleback/pm/el_2d_66.ktb
new file mode 100644
index 0000000..be2e3af
--- /dev/null
+++ b/Tables.brailleback/pm/el_2d_66.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-66
+
+assign statusKeys 13
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/el_2d_80.ktb b/Tables.brailleback/pm/el_2d_80.ktb
new file mode 100644
index 0000000..7a5eca9
--- /dev/null
+++ b/Tables.brailleback/pm/el_2d_80.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-80
+
+assign statusKeys 20
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/el_40_p.ktb b/Tables.brailleback/pm/el_40_p.ktb
new file mode 100644
index 0000000..9214688
--- /dev/null
+++ b/Tables.brailleback/pm/el_40_p.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 40 P
+
+assign noRightKey
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/el_80.ktb b/Tables.brailleback/pm/el_80.ktb
new file mode 100644
index 0000000..1953ddd
--- /dev/null
+++ b/Tables.brailleback/pm/el_80.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 80
+
+assign statusKeys 2
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/elb_tr_20.ktb b/Tables.brailleback/pm/elb_tr_20.ktb
new file mode 100644
index 0000000..01dcbd4
--- /dev/null
+++ b/Tables.brailleback/pm/elb_tr_20.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba (Trio 20)
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/elb_tr_32.ktb b/Tables.brailleback/pm/elb_tr_32.ktb
new file mode 100644
index 0000000..b8bc370
--- /dev/null
+++ b/Tables.brailleback/pm/elb_tr_32.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba (Trio 32)
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables.brailleback/pm/elba_20.ktb b/Tables.brailleback/pm/elba_20.ktb
new file mode 100644
index 0000000..0dad0a1
--- /dev/null
+++ b/Tables.brailleback/pm/elba_20.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba 20
+
+assign keyEmulation
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/elba_32.ktb b/Tables.brailleback/pm/elba_32.ktb
new file mode 100644
index 0000000..a06c35a
--- /dev/null
+++ b/Tables.brailleback/pm/elba_32.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba 32
+
+assign keyEmulation
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables.brailleback/pm/front13.kti b/Tables.brailleback/pm/front13.kti
new file mode 100644
index 0000000..83d032f
--- /dev/null
+++ b/Tables.brailleback/pm/front13.kti
@@ -0,0 +1,151 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 13 front keys.
+note There are 13 keys on the front.
+note * The rectangular key at the center is named Shift.
+note * The square keys immediately to its left and right are named Home and End.
+note * The left and right bars are named Up and Down.
+note * The four square keys at the very left are named: Dot7, Dot3, Dot2, Dot1.
+note * The four square keys at the very right are named: Dot4, Dot5, Dot6, Dot8.
+
+bind !Shift HOME
+bind !Home TOP
+bind !End BOT
+bind !Up LNUP
+bind !Down LNDN
+bind Dot1 PRDIFLN
+bind Dot4 NXDIFLN
+bind Dot2 ATTRUP
+bind Dot5 ATTRDN
+bind Dot3 PRPGRPH
+bind Dot6 NXPGRPH
+bind Dot7 PRPROMPT
+bind Dot8 NXPROMPT
+
+bind Dot1+Dot2 WINUP
+bind Dot4+Dot5 WINDN
+bind Dot3+Dot7 PRSEARCH
+bind Dot6+Dot8 NXSEARCH
+
+bind Dot1+Dot2+!RoutingKey1 PRDIFCHAR
+bind Dot4+Dot5+!RoutingKey1 NXDIFCHAR
+bind Dot3+Dot7+!RoutingKey1 PRINDENT
+bind Dot6+Dot8+!RoutingKey1 NXINDENT
+
+bind Dot1+!Up FWINLT
+bind Dot1+!Down FWINRT
+bind Dot1+!Home TOP_LEFT
+bind Dot1+!End BOT_LEFT
+
+bind Dot4+!Up HWINLT
+bind Dot4+!Down HWINRT
+bind Dot4+!Home CHRLT
+bind Dot4+!End CHRRT
+
+bind Dot1+!Shift LNBEG
+bind Dot4+!Shift LNEND
+
+bind Dot1+!RoutingKey1 SETLEFT
+bind Dot4+!RoutingKey1 DESCCHAR
+
+bind Dot2+!Shift KEY_TAB
+bind Dot2+!Home KEY_CURSOR_LEFT
+bind Dot2+!End KEY_CURSOR_RIGHT
+bind Dot2+!Up KEY_CURSOR_UP
+bind Dot2+!Down KEY_CURSOR_DOWN
+bind Dot2+!RoutingKey1 KEY_FUNCTION
+
+bind Dot5+!Shift KEY_INSERT
+bind Dot5+!Home KEY_HOME
+bind Dot5+!End KEY_END
+bind Dot5+!Up KEY_PAGE_UP
+bind Dot5+!Down KEY_PAGE_DOWN
+bind Dot5+!RoutingKey1 SWITCHVT
+
+bind Dot6+!Shift UNSTICK
+bind Dot6+!Home META
+bind Dot6+!End GUI
+bind Dot6+!Up SHIFT
+bind Dot6+!Down CONTROL
+
+bind Dot7+!Shift SPKHOME
+bind Dot7+!Home SAY_ABOVE
+bind Dot7+!End SAY_BELOW
+bind Dot7+!Up MUTE
+bind Dot7+!Down SAY_LINE
+
+bind Dot8+!Shift RESTARTSPEECH
+bind Dot8+!Home SAY_SLOWER
+bind Dot8+!End SAY_FASTER
+bind Dot8+!Up SAY_SOFTER
+bind Dot8+!Down SAY_LOUDER
+
+bind Dot7+!RoutingKey1 CLIP_NEW
+bind Dot3+!RoutingKey1 CLIP_ADD
+bind Dot6+!RoutingKey1 COPY_LINE
+bind Dot8+!RoutingKey1 COPY_RECT
+
+bind Dot1+Dot2+Dot3+Dot7 TIME
+
+include routing.kti
+
+assign toggleOff Dot7
+assign toggleOn Dot8
+include status\{statusKeys}.kti
+
+
+bind Dot2+Dot3+!Shift CONTEXT+default
+bind Dot2+Dot3+!Home CONTEXT+chords
+bind Dot2+Dot3+!End CONTEXT+braille
+
+
+####################
+# Chord Input Mode #
+####################
+
+context chords Chorded Commands Mode
+
+assign noUnchorded
+assign chord
+include ../chords.kti
+include ../menu.kti
+
+
+######################
+# Braille Input Mode #
+######################
+
+context braille Braille Input Mode
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+bind Dot3+!Shift KEY_ESCAPE
+bind Dot3+!Home KEY_BACKSPACE
+bind Dot3+!End KEY_DELETE
+bind Dot3+!Up KEY_ENTER
+bind Dot3+!Down PASSDOTS
+
+
diff --git a/Tables.brailleback/pm/front9.kti b/Tables.brailleback/pm/front9.kti
new file mode 100644
index 0000000..523ebf9
--- /dev/null
+++ b/Tables.brailleback/pm/front9.kti
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 9 front keys.
+
+bind !Home HOME
+bind !Up WINUP
+bind !Down WINDN
+bind !Backward LNUP
+bind !Forward LNDN
+bind Cursor HWINLT
+bind Braille HWINRT
+bind Function FWINLT
+bind Attribute FWINRT
+
+bind Function+!Home LNBEG
+bind Attribute+!Home LNEND
+bind Cursor+!Home CHRLT
+bind Braille+!Home CHRRT
+
+bind Function+!Up PRDIFLN
+bind Attribute+!Up ATTRUP
+bind Cursor+!Up PRPGRPH
+bind Braille+!Up PRSEARCH
+bind Function+!Down NXDIFLN
+bind Attribute+!Down ATTRDN
+bind Cursor+!Down NXPGRPH
+bind Braille+!Down NXSEARCH
+
+bind Function+!Backward TOP_LEFT
+bind Attribute+!Backward TOP
+bind Function+!Forward BOT_LEFT
+bind Attribute+!Forward BOT
+
+bind Function+!RoutingKey1 CLIP_NEW
+bind Attribute+!RoutingKey1 COPY_RECT
+bind Cursor+!RoutingKey1 PRINDENT
+bind Braille+!RoutingKey1 NXINDENT
+
+bind Function+Attribute PASTE
+
+include routing.kti
+
+assign toggleOff Function
+assign toggleOn Attribute
+include status\{statusKeys}.kti
diff --git a/Tables.brailleback/pm/ib_80.ktb b/Tables.brailleback/pm/ib_80.ktb
new file mode 100644
index 0000000..f373aab
--- /dev/null
+++ b/Tables.brailleback/pm/ib_80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX IB 80 CR Soft
+
+assign statusKeys 4
+include front9.kti
diff --git a/Tables.brailleback/pm/keyboard.kti b/Tables.brailleback/pm/keyboard.kti
new file mode 100644
index 0000000..9b08f46
--- /dev/null
+++ b/Tables.brailleback/pm/keyboard.kti
@@ -0,0 +1,59 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have a braille keyboard.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map LeftSpace SPACE
+map RightSpace SPACE
+map LeftThumb CONTROL
+map RightThumb META
+
+assign chord Space+
+include ../chords.kti
+assign noUnchorded
+
+hide on
+assign chord LeftSpace+
+include ../chords.kti
+
+assign chord RightSpace+
+include ../chords.kti
+hide off
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+assign chord LeftSpace
+include ../android-chords.kti
+
+assign chord RightSpace
+include ../android-chords.kti
+
+assign chord Space
+include ../android-chords.kti
diff --git a/Tables.brailleback/pm/keys.kti b/Tables.brailleback/pm/keys.kti
new file mode 100644
index 0000000..d0c3491
--- /dev/null
+++ b/Tables.brailleback/pm/keys.kti
@@ -0,0 +1,114 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have keys.
+
+ifVar keyEmulation
+  note The left and right keys are emulated. Enter key emulation mode by
+  note * pressing Thumb1 + Thumb2 (Left Windows + Right Windows). Then press:
+  note + Left Key Rear: Left Thumb (Left Windows)
+  note + Left Key Front: Space + Left Thumb (Context + Left Windows)
+  note + Right Key Rear: Right Thumb (Right Windows)
+  note + Right Key Front: Space + Right Thumb (Context + Right Windows)
+else
+  ifVar noRightKey
+    note The rocker at the left side of the top that doesn't stay when pressed
+    note * is named the Left Key.
+  else
+    note The rockers at each side of the top that don't stay when pressed
+    note * are named the Left and Right Key.
+  endIf
+endIf
+
+bind LeftKeyRear BACK
+bind LeftKeyFront HOME
+
+bind LeftKeyRear+BarLeft1 DISPMD
+bind LeftKeyRear+BarRight1 CSRTRK
+bind LeftKeyRear+BarUp1 SIXDOTS
+bind LeftKeyRear+BarDown1 PASTE
+
+bind LeftKeyRear+BarLeft2 ATTRVIS
+bind LeftKeyRear+BarRight2 CSRVIS
+bind LeftKeyRear+BarUp2 CAPBLINK
+bind LeftKeyRear+BarDown2 CSRJMP_VERT
+
+hide on
+bind LeftKeyRear+BarLeft1+BarLeft2 ATTRVIS
+bind LeftKeyRear+BarRight1+BarRight2 CSRVIS
+bind LeftKeyRear+BarUp1+BarUp2 CAPBLINK
+bind LeftKeyRear+BarDown1+BarDown2 CSRJMP_VERT
+hide off
+
+bind LeftKeyFront+BarLeft1 INFO
+bind LeftKeyFront+BarRight1 PREFMENU
+bind LeftKeyFront+BarUp1 AUTOSPEAK
+bind LeftKeyFront+BarDown1 AUTOREPEAT
+
+bind LeftKeyFront+BarLeft2 PREFLOAD
+bind LeftKeyFront+BarRight2 PREFSAVE
+bind LeftKeyFront+BarUp2 RESTARTBRL
+bind LeftKeyFront+BarDown2 FREEZE
+
+hide on
+bind LeftKeyFront+BarLeft1+BarLeft2 PREFLOAD
+bind LeftKeyFront+BarRight1+BarRight2 PREFSAVE
+bind LeftKeyFront+BarUp1+BarUp2 RESTARTBRL
+bind LeftKeyFront+BarDown1+BarDown2 FREEZE
+hide off
+
+ifNotVar noRightKey
+  bind RightKeyRear HELP
+  bind RightKeyFront LEARN
+
+  bind RightKeyRear+BarLeft1 MUTE
+  bind RightKeyRear+BarRight1 SAY_LINE
+  bind RightKeyRear+BarUp1 SAY_ABOVE
+  bind RightKeyRear+BarDown1 SAY_BELOW
+
+  bind RightKeyRear+BarLeft2 SAY_SLOWER
+  bind RightKeyRear+BarRight2 SAY_FASTER
+  bind RightKeyRear+BarUp2 SAY_LOUDER
+  bind RightKeyRear+BarDown2 SAY_SOFTER
+
+  hide on
+  bind RightKeyRear+BarLeft1+BarLeft2 SAY_SLOWER
+  bind RightKeyRear+BarRight1+BarRight2 SAY_FASTER
+  bind RightKeyRear+BarUp1+BarUp2 SAY_LOUDER
+  bind RightKeyRear+BarDown1+BarDown2 SAY_SOFTER
+  hide off
+
+  bind RightKeyFront+BarLeft1 SKPIDLNS
+  bind RightKeyFront+BarRight1 SKPBLNKWINS
+  bind RightKeyFront+BarUp1 SPKHOME
+  bind RightKeyFront+BarDown1 TUNES
+
+  bind RightKeyFront+BarUp2 RESTARTSPEECH
+  bind RightKeyFront+BarRight2 SLIDEWIN
+
+  hide on
+  bind RightKeyFront+BarUp1+BarUp2 RESTARTSPEECH
+  bind RightKeyFront+BarRight1+BarRight2 SLIDEWIN
+  hide off
+
+  bind LeftKeyRear+RoutingKey1 CLIP_NEW
+  bind LeftKeyFront+RoutingKey1 CLIP_ADD
+  bind RightKeyRear+RoutingKey1 COPY_LINE
+  bind RightKeyFront+RoutingKey1 COPY_RECT
+endIf
+
diff --git a/Tables.brailleback/pm/live.ktb b/Tables.brailleback/pm/live.ktb
new file mode 100644
index 0000000..3ae3da8
--- /dev/null
+++ b/Tables.brailleback/pm/live.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Live
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
+include keyboard.kti
diff --git a/Tables.brailleback/pm/routing.kti b/Tables.brailleback/pm/routing.kti
new file mode 100644
index 0000000..64b9c2f
--- /dev/null
+++ b/Tables.brailleback/pm/routing.kti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2022 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have primary routing keys.
+
+note RoutingKey1 refers to the row of keys just behind the text cells.
+#Overridden by BrailleBack.
+#bind RoutingKey1 ROUTE
+
+ifKey RoutingKey2
+  note RoutingKey2 refers to the row of keys behind the RoutingKey1 row.
+  bind RoutingKey2 DESCCHAR
+endIf
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+bind RoutingKey1 ROUTE:ROUTE+128
diff --git a/Tables.brailleback/pm/status0.kti b/Tables.brailleback/pm/status0.kti
new file mode 100644
index 0000000..ea8f98a
--- /dev/null
+++ b/Tables.brailleback/pm/status0.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have no status keys.
+
diff --git a/Tables.brailleback/pm/status13.kti b/Tables.brailleback/pm/status13.kti
new file mode 100644
index 0000000..cd4a18d
--- /dev/null
+++ b/Tables.brailleback/pm/status13.kti
@@ -0,0 +1,51 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 13 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.13.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFMENU
+
+assign toggleKeys Status.10
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.11
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+bind !Status.12 TIME
+bind !Status.13 PASTE
diff --git a/Tables.brailleback/pm/status2.kti b/Tables.brailleback/pm/status2.kti
new file mode 100644
index 0000000..22b20ab
--- /dev/null
+++ b/Tables.brailleback/pm/status2.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 2 status keys.
+note The keys behind the status cells are named Status.1 and Status.2.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
diff --git a/Tables.brailleback/pm/status20.kti b/Tables.brailleback/pm/status20.kti
new file mode 100644
index 0000000..b0dc9a5
--- /dev/null
+++ b/Tables.brailleback/pm/status20.kti
@@ -0,0 +1,71 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 20 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.20.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFLOAD
+bind !Status.10 PREFMENU
+bind !Status.11 PREFSAVE
+
+assign toggleKeys Status.12
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.13
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.14
+assign toggleCommand SKPIDLNS
+include ../toggle.kti
+
+assign toggleKeys Status.15
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+bind Status.16 RESTARTBRL
+
+assign toggleKeys Status.17
+assign toggleCommand AUTOSPEAK
+include ../toggle.kti
+
+assign toggleKeys Status.18
+assign toggleCommand AUTOREPEAT
+include ../toggle.kti
+
+bind Status.19 TIME
+bind !Status.20 PASTE
diff --git a/Tables.brailleback/pm/status22.kti b/Tables.brailleback/pm/status22.kti
new file mode 100644
index 0000000..b496f3c
--- /dev/null
+++ b/Tables.brailleback/pm/status22.kti
@@ -0,0 +1,76 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 22 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.22.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFLOAD
+bind !Status.10 PREFMENU
+bind !Status.11 PREFSAVE
+
+assign toggleKeys Status.12
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.13
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.14
+assign toggleCommand SKPIDLNS
+include ../toggle.kti
+
+assign toggleKeys Status.15
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+bind !Status.16 RESTARTBRL
+bind !Status.17 RESTARTSPEECH
+
+assign toggleKeys Status.18
+assign toggleCommand AUTOSPEAK
+include ../toggle.kti
+
+assign toggleKeys Status.19
+assign toggleCommand AUTOREPEAT
+include ../toggle.kti
+
+assign toggleKeys Status.20
+assign toggleCommand BRLUCDOTS
+include ../toggle.kti
+
+bind !Status.21 TIME
+bind !Status.22 PASTE
diff --git a/Tables.brailleback/pm/status4.kti b/Tables.brailleback/pm/status4.kti
new file mode 100644
index 0000000..f7b3fa1
--- /dev/null
+++ b/Tables.brailleback/pm/status4.kti
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 4 status keys.
+note The keys behind the status cells are named Status.1 through Status.4.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
diff --git a/Tables.brailleback/pm/switches.kti b/Tables.brailleback/pm/switches.kti
new file mode 100644
index 0000000..f74cdac
--- /dev/null
+++ b/Tables.brailleback/pm/switches.kti
@@ -0,0 +1,164 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have switches.
+
+ifVar keyEmulation
+  note The left and right switches are emulated. Enter key emulation mode by
+  note * pressing Thumb1 + Thumb2 (Left Windows + Right Windows). Then press:
+  note + Left Switch Rear: Dot 1 (f)
+  note + Left Switch Center: Dot 2 (d)
+  note + Left Switch Front: Dot 3 (s)
+  note + Right Switch Rear: Dot 4 (j)
+  note + Right Switch Center: Dot 5 (k)
+  note + Right Switch Front: Dot 6 (l)
+  note + center both switches: Dot 7 (a)
+else
+  note The rockers at each side of the top that stay when pressed
+  note * are named the Left and Right Switch.
+endIf
+
+context switchesRearCenter Advanced Vertical Navigation (left switch rear)
+
+bind BarUp1 PRDIFLN
+bind BarDown1 NXDIFLN
+bind BarUp2 ATTRUP
+bind BarDown2 ATTRDN
+bind BarLeft1 PRPROMPT
+bind BarRight1 NXPROMPT
+bind BarLeft2 PRPGRPH
+bind BarRight2 NXPGRPH
+
+hide on
+bind BarUp1+BarUp2 ATTRUP
+bind BarDown1+BarDown2 ATTRDN
+bind BarLeft1+BarLeft2 PRPGRPH
+bind BarRight1+BarRight2 NXPGRPH
+hide off
+
+
+context switchesFrontCenter Specialized Navigation (left switch front)
+
+bind BarUp1 PRSEARCH
+bind BarDown1 NXSEARCH
+bind BarUp2 HELP
+bind BarDown2 LEARN
+bind BarLeft1 CHRLT
+bind BarRight1 CHRRT
+bind BarLeft2 HWINLT
+bind BarRight2 HWINRT
+
+hide on
+bind BarUp1+BarUp2 HELP
+bind BarDown1+BarDown2 LEARN
+bind BarLeft1+BarLeft2 HWINLT
+bind BarRight1+BarRight2 HWINRT
+hide off
+
+
+context switchesCenterRear Content-based Navigation (right switch rear)
+
+bind BarUp1 KEY_CURSOR_UP
+bind BarDown1 KEY_CURSOR_DOWN
+bind BarUp2 KEY_PAGE_UP
+bind BarDown2 KEY_PAGE_DOWN
+bind BarLeft1 FWINLT+route
+bind BarRight1 FWINRT+route
+bind BarLeft2 LNBEG+route
+bind BarRight2 LNEND+route
+
+hide on
+bind BarUp1+BarUp2 KEY_PAGE_UP
+bind BarDown1+BarDown2 KEY_PAGE_DOWN
+bind BarLeft1+BarLeft2 LNBEG+route
+bind BarRight1+BarRight2 LNEND+route
+hide off
+
+
+context switchesCenterFront Function Key Emulation (right switch front)
+
+bind BarUp1 KEY_CURSOR_UP
+bind BarDown1 KEY_CURSOR_DOWN
+bind BarUp2 KEY_PAGE_UP
+bind BarDown2 KEY_PAGE_DOWN
+bind BarLeft1 KEY_CURSOR_LEFT
+bind BarRight1 KEY_CURSOR_RIGHT
+bind BarLeft2 KEY_HOME
+bind BarRight2 KEY_END
+
+hide on
+bind BarUp1+BarUp2 KEY_PAGE_UP
+bind BarDown1+BarDown2 KEY_PAGE_DOWN
+bind BarLeft1+BarLeft2 KEY_HOME
+bind BarRight1+BarRight2 KEY_END
+hide off
+
+
+context switchesRearRear Unused (left switch rear, right switch rear)
+
+
+context switchesRearFront Unused (left switch rear, right switch front)
+
+
+context switchesFrontRear Unused (left switch front, right switch rear)
+
+
+context switchesFrontFront Unused (left switch front, right switch front)
+
+
+context switchesRearCenter
+hotkey LeftSwitchRear NOOP CONTEXT+default
+hotkey RightSwitchRear CONTEXT+switchesRearRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesRearFront NOOP
+
+context switchesFrontCenter
+hotkey LeftSwitchFront NOOP CONTEXT+default
+hotkey RightSwitchRear CONTEXT+switchesFrontRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesFrontFront NOOP
+
+context switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+default
+hotkey LeftSwitchRear CONTEXT+switchesRearRear NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontRear NOOP
+
+context switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+default
+hotkey LeftSwitchRear CONTEXT+switchesRearFront NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontFront NOOP
+
+context switchesRearRear
+hotkey LeftSwitchRear NOOP CONTEXT+switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+switchesRearCenter
+
+context switchesRearFront
+hotkey LeftSwitchRear NOOP CONTEXT+switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+switchesRearCenter
+
+context switchesFrontRear
+hotkey LeftSwitchFront NOOP CONTEXT+switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+switchesFrontCenter
+
+context switchesFrontFront
+hotkey LeftSwitchFront NOOP CONTEXT+switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+switchesFrontCenter
+
+context default
+hotkey LeftSwitchRear CONTEXT+switchesRearCenter NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontCenter NOOP
+hotkey RightSwitchRear CONTEXT+switchesCenterRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesCenterFront NOOP
diff --git a/Tables.brailleback/pm/trio.ktb b/Tables.brailleback/pm/trio.ktb
new file mode 100644
index 0000000..3702556
--- /dev/null
+++ b/Tables.brailleback/pm/trio.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Trio
+
+assign statusKeys 0
+include bar.kti
+include keyboard.kti
diff --git a/Tables.brailleback/sk/bdp.ktb b/Tables.brailleback/sk/bdp.ktb
new file mode 100644
index 0000000..a00e795
--- /dev/null
+++ b/Tables.brailleback/sk/bdp.ktb
@@ -0,0 +1,195 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Seika Braille Displays
+
+note The round keys to the left/right of the braille cells are named K1 and K8.
+note The left/right ends of the left rocker are named K2 and K3.
+note The long keys to the left/right of the center are named K4 and K5.
+note The left/right ends of the right rocker are named K6 and K7.
+
+####################
+# Default Bindings #
+####################
+
+bind !RoutingKey ROUTE
+
+bind K1 FWINLT
+bind K8 FWINRT
+
+bind K2 LNUP
+bind K3 LNDN
+#Overridden by BrailleBack
+#bind K2+K3 LNBEG
+
+#Overridden by BrailleBack
+#bind K6 FWINLTSKIP
+#Overridden by BrailleBack
+#bind K7 FWINRTSKIP
+#Overridden by BrailleBack
+#bind K6+K7 PASTE
+
+bind K4 CSRTRK
+bind K5 RETURN
+#Overridden by BrailleBack
+#bind K4+K5 CSRJMP_VERT
+
+bind K6+K2 TOP_LEFT
+bind K6+K3 BOT_LEFT
+bind K7+K2 TOP
+bind K7+K3 BOT
+
+bind K4+K2 ATTRUP
+bind K4+K3 ATTRDN
+#Overridden by BrailleBack
+#bind K5+K2 PRDIFLN
+#Overridden by BrailleBack
+#bind K5+K3 NXDIFLN
+#Overridden by BrailleBack
+#bind K4+K6 PRPROMPT
+#Overridden by BrailleBack
+#bind K4+K7 NXPROMPT
+#Overridden by BrailleBack
+#bind K5+K6 PRPGRPH
+#Overridden by BrailleBack
+#bind K5+K7 NXPGRPH
+
+bind K1+K8+K2 CONTEXT+PRINDENT
+bind K1+K8+K3 CONTEXT+NXINDENT
+bind K1+K8+K4 CONTEXT+SETLEFT
+bind K1+K8+K5 CONTEXT+DESCCHAR
+bind K1+K8+K6 CONTEXT+PRDIFCHAR
+bind K1+K8+K7 CONTEXT+NXDIFCHAR
+
+bind K1+K8+K2+K6 CONTEXT+CLIP_NEW
+bind K1+K8+K2+K7 CONTEXT+CLIP_ADD
+bind K1+K8+K3+K6 CONTEXT+COPY_LINE
+bind K1+K8+K3+K7 CONTEXT+COPY_RECT
+
+bind K1+K2 HELP
+#Overridden by BrailleBack
+#bind K1+K3 LEARN
+bind K1+K4 PREFLOAD
+bind K1+K5 PREFSAVE
+bind K1+K6 PREFMENU
+bind K1+K7 INFO
+
+bind K8+K2 DISPMD
+bind K8+K3 FREEZE
+bind K8+K6 SIXDOTS
+bind K8+K7 SKPIDLNS
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by Brailleback
+#context menu
+
+#bind K1 FWINLT
+#bind K8 FWINRT
+
+#bind K2 MENU_PREV_ITEM
+#bind K3 MENU_NEXT_ITEM
+
+#bind K6 MENU_PREV_SETTING
+#bind K7 MENU_NEXT_SETTING
+
+#bind K4 MENU_FIRST_ITEM
+#bind K5 MENU_LAST_ITEM
+
+
+############################
+# Routing Key Alternatives #
+############################
+
+#Unused by Brailleback
+#context CLIP_NEW
+#bind !RoutingKey CLIP_NEW
+
+#context CLIP_ADD
+#bind !RoutingKey CLIP_ADD
+
+#context COPY_LINE
+#bind !RoutingKey COPY_LINE
+
+#context COPY_RECT
+#bind !RoutingKey COPY_RECT
+
+#context SETLEFT
+#bind !RoutingKey SETLEFT
+
+#context DESCCHAR
+#bind !RoutingKey DESCCHAR
+
+#context PRINDENT
+#bind !RoutingKey PRINDENT
+
+#context NXINDENT
+#bind !RoutingKey NXINDENT
+
+#context PRDIFCHAR
+#bind !RoutingKey PRDIFCHar
+
+#context NXDIFCHAR
+#bind !RoutingKey NXDIFCHar
+
+
+########################
+# BrailleBack Bindings #
+########################
+
+# TODO: Add more google's customized shortcuts
+# Long press currently focused item
+bind K2+K3+K6+K7 ROUTE+255
+# Back key
+bind K2+K3 KEY_ESCAPE
+# Home key
+bind K4+K5 KEY_FUNCTION+109
+# Recent apps key
+bind K6+K7 KEY_FUNCTION+110
+# Notifications bar
+bind K2+K4 KEY_FUNCTION+111
+# Scroll backwards
+bind K3+K6 WINUP
+# Scroll forwards
+bind K3+K7 WINDN
+# Previous element
+bind K6 CHRLT
+# Next element
+bind K7 CHRRT
+# Keyboard help
+bind K1+K3 LEARN
+# Next heading
+bind K5+K7 KEY_FUNCTION+113
+# Previous heading
+bind K5+K6 KEY_FUNCTION+114
+# Next control
+bind K4+K7 KEY_FUNCTION+115
+# Previous control
+bind K4+K6 KEY_FUNCTION+116
+# Next link
+bind K5+K3 KEY_FUNCTION+117
+# Previous link
+bind K5+K2 KEY_FUNCTION+118
+# Top item in view
+bind K2+K6 TOP
+# Bottom item in view
+bind K2+K7 BOT
+# Incremental search mapping omitted since no braille input keyboard
diff --git a/Tables.brailleback/sk/ntk.ktb b/Tables.brailleback/sk/ntk.ktb
new file mode 100644
index 0000000..edaf5c2
--- /dev/null
+++ b/Tables.brailleback/sk/ntk.ktb
@@ -0,0 +1,173 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Seika Note Takers
+
+####################
+# Default Bindings #
+####################
+
+bind LeftJoystickPress HELP
+bind RightJoystickPress LEARN
+
+bind LeftButton+RightButton HOME
+bind LeftButton FWINLT
+bind RightButton FWINRT
+
+bind LeftJoystickUp top
+bind LeftJoystickDown BOT
+bind LeftJoystickLeft LNBEG
+bind LeftJoystickRight LNEND
+
+bind RightJoystickUp LNUP
+bind RightJoystickDown LNDN
+bind RightJoystickLeft CHRLT
+bind RightJoystickRight CHRRT
+
+bind LeftJoystickLeft+RightJoystickUp ATTRUP
+bind LeftJoystickLeft+RightJoystickDown ATTRDN
+bind LeftJoystickRight+RightJoystickUp PRDIFLN
+bind LeftJoystickRight+RightJoystickDown NXDIFLN
+bind LeftJoystickUp+RightJoystickUp PRPROMPT
+bind LeftJoystickUp+RightJoystickDown NXPROMPT
+bind LeftJoystickDown+RightJoystickUp PRPGRPH
+bind LeftJoystickDown+RightJoystickDown NXPGRPH
+
+bind LeftJoystickLeft+RightJoystickLeft HWINLT
+bind LeftJoystickLeft+RightJoystickRight HWINRT
+bind LeftJoystickRight+RightJoystickLeft FWINLTSKIP
+bind LeftJoystickRight+RightJoystickRight FWINRTSKIP
+bind LeftJoystickUp+RightJoystickLeft PRSEARCH
+bind LeftJoystickUp+RightJoystickRight NXSEARCH
+bind LeftJoystickDown+RightJoystickLeft BACK
+bind LeftJoystickDown+RightJoystickRight CSRJMP_VERT
+
+bind LeftJoystickPress+RightJoystickUp INFO
+bind LeftJoystickPress+RightJoystickDown PREFMENU
+bind LeftJoystickPress+RightJoystickLeft PREFLOAD
+bind LeftJoystickPress+RightJoystickRight PREFSAVE
+
+bind RoutingKey ROUTE
+bind LeftButton+RoutingKey SETLEFT
+bind RightButton+RoutingKey DESCCHAR
+
+bind RightJoystickUp+RoutingKey PRINDENT
+bind RightJoystickDown+RoutingKey NXINDENT
+bind RightJoystickLeft+RoutingKey PRDIFCHAR
+bind RightJoystickRight+RoutingKey NXDIFCHAR
+
+bind LeftJoystickPress+RoutingKey SETMARK
+bind RightJoystickPress+RoutingKey GOTOMARK
+
+bind LeftJoystickPress+RightJoystickPress PASTE
+bind RoutingKey+RoutingKey CLIP_COPY
+bind LeftJoystickUp+RoutingKey+RoutingKey CLIP_APPEND
+bind LeftJoystickLeft+RoutingKey CLIP_NEW
+bind LeftJoystickUp+RoutingKey CLIP_ADD
+bind LeftJoystickRight+RoutingKey COPY_LINE
+bind LeftJoystickDown+RoutingKey COPY_RECT
+
+assign chord Space+
+include ../chords.kti
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+#Overridden by BrailleBack.
+#map Backspace META
+map Space SPACE
+
+bind Space+LeftJoystickUp KEY_PAGE_UP
+bind Space+LeftJoystickDown KEY_PAGE_DOWN
+bind Space+LeftJoystickLeft KEY_HOME
+bind Space+LeftJoystickRight KEY_END
+bind Space+LeftJoystickPress KEY_INSERT
+
+bind Space+RightJoystickUp KEY_CURSOR_UP
+bind Space+RightJoystickDown KEY_CURSOR_DOWN
+bind Space+RightJoystickLeft KEY_CURSOR_LEFT
+bind Space+RightJoystickRight KEY_CURSOR_RIGHT
+bind Space+RightJoystickPress KEY_DELETE
+
+bind Space+RoutingKey KEY_FUNCTION
+bind Backspace+RoutingKey SWITCHVT
+
+bind Backspace+LeftJoystickUp SAY_LOUDER
+bind Backspace+LeftJoystickDown SAY_SOFTER
+bind Backspace+LeftJoystickLeft SAY_SLOWER
+bind Backspace+LeftJoystickRight SAY_FASTER
+bind Backspace+LeftJoystickPress AUTOSPEAK
+
+bind Backspace+RightJoystickUp SAY_ABOVE
+bind Backspace+RightJoystickDown SAY_BELOW
+bind Backspace+RightJoystickLeft MUTE
+bind Backspace+RightJoystickRight SAY_LINE
+bind Backspace+RightJoystickPress SPKHOME
+
+
+#################
+# Menu Bindings #
+#################
+
+#Unused by BrailleBack.
+#context menu
+
+#bind RightJoystickPress PREFMENU
+#bind RightJoystickUp MENU_PREV_ITEM
+#bind RightJoystickDown MENU_NEXT_ITEM
+#bind RightJoystickLeft MENU_PREV_SETTING
+#bind RightJoystickRight MENU_NEXT_SETTING
+
+#bind LeftJoystickUp MENU_FIRST_ITEM
+#bind LeftJoystickDown MENU_LAST_ITEM
+#bind LeftJoystickLeft PREFLOAD
+#bind LeftJoystickRight PREFSAVE
+
+########################
+# BrailleBack Bindings #
+########################
+
+# Activate currently focused item
+bind LeftJoystickPress ROUTE+127
+bind RightJoystickPress ROUTE+127
+
+# Long press currently focused item
+bind Space+LeftJoystickPress ROUTE+255
+bind Space+RightJoystickPress ROUTE+255
+
+# Long press item at routing key.
+bind Space+RoutingKey ROUTE+128
+
+map Backspace SPACE
+bind Backspace+Space SPACE
+
+include ../android-chords.kti
+
+assign chord Space
+include ../android-chords.kti
+
+assign chord Backspace
+include ../android-chords.kti
+
+assign chord Space+Backspace
+include ../android-chords.kti
diff --git a/Tables.brailleback/speech.kti b/Tables.brailleback/speech.kti
new file mode 100644
index 0000000..a67f1a1
--- /dev/null
+++ b/Tables.brailleback/speech.kti
@@ -0,0 +1,57 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{speech}Dot1 AUTOSPEAK+on
+bind \{speech}Dot2 MUTE
+bind \{speech}Dot3 AUTOSPEAK+off
+bind \{speech}Dot7 SPEAK_CURR_LOCN
+
+bind \{speech}Dot4 SAY_ABOVE
+bind \{speech}Dot5 SAY_LINE
+bind \{speech}Dot6 SAY_BELOW
+bind \{speech}Dot8 SAY_ALL
+
+bind \{speech}Dot1+Dot4 SPEAK_PREV_LINE
+bind \{speech}Dot1+Dot5 SPEAK_CURR_LINE
+bind \{speech}Dot1+Dot6 SPEAK_NEXT_LINE
+bind \{speech}Dot1+Dot8 SPEAK_INDENT
+
+bind \{speech}Dot2+Dot4 SPEAK_PREV_WORD
+bind \{speech}Dot2+Dot5 SPEAK_CURR_WORD
+bind \{speech}Dot2+Dot6 SPEAK_NEXT_WORD
+bind \{speech}Dot2+Dot8 SPELL_CURR_WORD
+
+bind \{speech}Dot3+Dot4 SPEAK_PREV_CHAR
+bind \{speech}Dot3+Dot5 SPEAK_CURR_CHAR
+bind \{speech}Dot3+Dot6 SPEAK_NEXT_CHAR
+bind \{speech}Dot3+Dot8 DESC_CURR_CHAR
+
+bind \{speech}Dot7+Dot4 SPEAK_FRST_LINE
+bind \{speech}Dot7+Dot5 SPEAK_FRST_CHAR
+bind \{speech}Dot7+Dot6 SPEAK_LAST_CHAR
+bind \{speech}Dot7+Dot8 SPEAK_LAST_LINE
+
+bind \{speech}Space+Dot1 SAY_SOFTER
+bind \{speech}Space+Dot4 SAY_LOUDER
+bind \{speech}Space+Dot2 SAY_SLOWER
+bind \{speech}Space+Dot5 SAY_FASTER
+bind \{speech}Space+Dot3 SAY_LOWER
+bind \{speech}Space+Dot6 SAY_HIGHER
+bind \{speech}Space+Dot7 ROUTE_CURR_LOCN
+bind \{speech}Space+Dot8 RETURN
+
diff --git a/Tables.brailleback/toggle.kti b/Tables.brailleback/toggle.kti
new file mode 100644
index 0000000..65615d9
--- /dev/null
+++ b/Tables.brailleback/toggle.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{toggleKeys} \{toggleCommand}
+bind \{toggleKeys}+\{toggleOn} \{toggleCommand}+on
+bind \{toggleKeys}+\{toggleOff} \{toggleCommand}+off
diff --git a/Tables.brailleback/vo/bp.ktb b/Tables.brailleback/vo/bp.ktb
new file mode 100644
index 0000000..4f6ceab
--- /dev/null
+++ b/Tables.brailleback/vo/bp.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Braille Pen 12
+
+include ../bp/all.kti
diff --git a/Tables/.gitignore b/Tables/.gitignore
new file mode 100644
index 0000000..1ef03c6
--- /dev/null
+++ b/Tables/.gitignore
@@ -0,0 +1 @@
+/Profiles/
diff --git a/Tables/Attributes/invleft_right.atb b/Tables/Attributes/invleft_right.atb
new file mode 100644
index 0000000..daa14ab
--- /dev/null
+++ b/Tables/Attributes/invleft_right.atb
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Attributes Table - inverse foreground colour in the left column and background colour in the right column
+
+dot 1 ~fg-blue
+dot 2 ~fg-green
+dot 3 ~fg-red
+dot 7 ~fg-bright
+dot 4 =bg-blue
+dot 5 =bg-green
+dot 6 =bg-red
+dot 8 =blink
diff --git a/Tables/Attributes/left_right.atb b/Tables/Attributes/left_right.atb
new file mode 100644
index 0000000..04d7dc0
--- /dev/null
+++ b/Tables/Attributes/left_right.atb
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Attributes Table - foreground colour in the left column and background colour in the right column
+
+dot 1 =fg-blue
+dot 2 =fg-green
+dot 3 =fg-red
+dot 7 =fg-bright
+dot 4 =bg-blue
+dot 5 =bg-green
+dot 6 =bg-red
+dot 8 =blink
diff --git a/Tables/Attributes/upper_lower.atb b/Tables/Attributes/upper_lower.atb
new file mode 100644
index 0000000..a20013f
--- /dev/null
+++ b/Tables/Attributes/upper_lower.atb
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Attributes Table - foreground colour in the upper square and background colour in the lower square
+
+dot 1 =fg-red
+dot 4 =fg-green
+dot 2 =fg-blue
+dot 5 =fg-bright
+dot 3 =bg-red
+dot 6 =bg-green
+dot 7 =bg-blue
+dot 8 =blink
diff --git a/Tables/Contraction/af.ctb b/Tables/Contraction/af.ctb
new file mode 100644
index 0000000..99a5868
--- /dev/null
+++ b/Tables/Contraction/af.ctb
@@ -0,0 +1,77 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Afrikaans (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always aa 2
+always aan 126
+always al 1246
+always an 12346
+always by 356
+always deur 256
+always die 2346
+always ee 156
+always ee 23
+always ei 146
+always el 3456
+always en 26
+always er 12456
+always ge 123456
+always ie 1456
+always ig 12345
+always in 35
+always met 23456
+always oe 246
+always of 12356
+always on 25
+always oo 2356
+always ou 1256
+always sk 16
+always st 34
+always te 235
+always ui 345
+always was 346
+always - 36
+always , 2
+always ; 23
+always : 25
+always ! 235
+always ? 236
+always / 34
+always . 256
+always ' 3
+always " 236
+always " 356
+always ( 2356
+always ) 2356
+always * 35-35
+always ^ 45
+numsign 3456
+capsign 6
+
+# inline contraction of emoji descriptions
+emoji af
diff --git a/Tables/Contraction/am.ctb b/Tables/Contraction/am.ctb
new file mode 100644
index 0000000..3058cef
--- /dev/null
+++ b/Tables/Contraction/am.ctb
@@ -0,0 +1,304 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Amharic (uncontracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+always ሀ 125			ETHIOPIC SYLLABLE HA
+always ሁ 125-136		ETHIOPIC SYLLABLE HU
+always ሂ 125-24			ETHIOPIC SYLLABLE HI
+always ሃ 125-1			ETHIOPIC SYLLABLE HAA
+always ሄ 125-15			ETHIOPIC SYLLABLE HEE
+always ህ 5			ETHIOPIC SYLLABLE HE
+always ሆ 125-135		ETHIOPIC SYLLABLE HO
+always ለ 123			ETHIOPIC SYLLABLE LA
+always ሉ 123-136		ETHIOPIC SYLLABLE LU
+always ሊ 123-24			ETHIOPIC SYLLABLE LI
+always ላ 123-1			ETHIOPIC SYLLABLE LAA
+always ሌ 123-15			ETHIOPIC SYLLABLE LEE
+always ል 456			ETHIOPIC SYLLABLE LE
+always ሎ 123-135		ETHIOPIC SYLLABLE LO
+always ሐ 125			ETHIOPIC SYLLABLE HHA
+always ሑ 125-136		ETHIOPIC SYLLABLE HHU
+always ሒ 125-24			ETHIOPIC SYLLABLE HHI
+always ሓ 125-1			ETHIOPIC SYLLABLE HHAA
+always ሔ 125-15			ETHIOPIC SYLLABLE HHEE
+always ሕ 5			ETHIOPIC SYLLABLE HHE
+always ሖ 125-135		ETHIOPIC SYLLABLE HHO
+always መ 134			ETHIOPIC SYLLABLE MA
+always ሙ 134-136		ETHIOPIC SYLLABLE MU
+always ሚ 134-24			ETHIOPIC SYLLABLE MI
+always ማ 134-1			ETHIOPIC SYLLABLE MAA
+always ሜ 134-15			ETHIOPIC SYLLABLE MEE
+always ም 23			ETHIOPIC SYLLABLE ME
+always ሞ 134-135		ETHIOPIC SYLLABLE MO
+always ሠ 6-234			ETHIOPIC SYLLABLE SZA
+always ሡ 6-234-136		ETHIOPIC SYLLABLE SZU
+always ሢ 6-234-24		ETHIOPIC SYLLABLE SZI
+always ሣ 6-234-1		ETHIOPIC SYLLABLE SZAA
+always ሤ 6-234-15		ETHIOPIC SYLLABLE SZEE
+always ሥ 6-56			ETHIOPIC SYLLABLE SZE
+always ሦ 6-234-135		ETHIOPIC SYLLABLE SZO
+always ረ 1235			ETHIOPIC SYLLABLE RA
+always ሩ 1235-136		ETHIOPIC SYLLABLE RU
+always ሪ 1235-24		ETHIOPIC SYLLABLE RI
+always ራ 1235-1			ETHIOPIC SYLLABLE RAA
+always ሬ 1235-15		ETHIOPIC SYLLABLE REE
+always ር 1256			ETHIOPIC SYLLABLE RE
+always ሮ 1235-135		ETHIOPIC SYLLABLE RO
+always ሰ 234			ETHIOPIC SYLLABLE SA
+always ሱ 234-136		ETHIOPIC SYLLABLE SU
+always ሲ 234-24			ETHIOPIC SYLLABLE SI
+always ሳ 234-1			ETHIOPIC SYLLABLE SAA
+always ሴ 234-15			ETHIOPIC SYLLABLE SEE
+always ስ 56			ETHIOPIC SYLLABLE SE
+always ሶ 234-135		ETHIOPIC SYLLABLE SO
+always ሸ 146			ETHIOPIC SYLLABLE SHA
+always ሹ 146-136		ETHIOPIC SYLLABLE SHU
+always ሺ 146-24			ETHIOPIC SYLLABLE SHI
+always ሻ 146-1			ETHIOPIC SYLLABLE SHAA
+always ሼ 146-15			ETHIOPIC SYLLABLE SHEE
+always ሽ 156			ETHIOPIC SYLLABLE SHE
+always ሾ 146-135		ETHIOPIC SYLLABLE SHO
+always ቀ 12345			ETHIOPIC SYLLABLE QA
+always ቁ 12345-136		ETHIOPIC SYLLABLE QU
+always ቂ 12345-24		ETHIOPIC SYLLABLE QI
+always ቃ 12345-1		ETHIOPIC SYLLABLE QAA
+always ቄ 12345-15		ETHIOPIC SYLLABLE QEE
+always ቅ 46			ETHIOPIC SYLLABLE QE
+always ቆ 12345-135		ETHIOPIC SYLLABLE QO
+always ቈ 12456-12345		ETHIOPIC SYLLABLE QWA
+always ቊ 12456-12345-136	ETHIOPIC SYLLABLE QWI
+always ቋ 12456-12345-1		ETHIOPIC SYLLABLE QWAA
+always ቌ 12456-12345-15		ETHIOPIC SYLLABLE QWEE
+always ቍ 12456-12345-24		ETHIOPIC SYLLABLE QWE
+always በ 12			ETHIOPIC SYLLABLE BA
+always ቡ 12-136			ETHIOPIC SYLLABLE BU
+always ቢ 12-24			ETHIOPIC SYLLABLE BI
+always ባ 12-1			ETHIOPIC SYLLABLE BAA
+always ቤ 12-15			ETHIOPIC SYLLABLE BEE
+always ብ 45			ETHIOPIC SYLLABLE BE
+always ቦ 12-135			ETHIOPIC SYLLABLE BO
+always ተ 2345			ETHIOPIC SYLLABLE TA
+always ቱ 2345-136		ETHIOPIC SYLLABLE TU
+always ቲ 2345-24		ETHIOPIC SYLLABLE TI
+always ታ 2345-1			ETHIOPIC SYLLABLE TAA
+always ቴ 2345-15		ETHIOPIC SYLLABLE TEE
+always ት 2			ETHIOPIC SYLLABLE TE
+always ቶ 2345-135		ETHIOPIC SYLLABLE TO
+always ቸ 16			ETHIOPIC SYLLABLE CA
+always ቹ 16-136			ETHIOPIC SYLLABLE CU
+always ቺ 16-24			ETHIOPIC SYLLABLE CI
+always ቻ 16-1			ETHIOPIC SYLLABLE CAA
+always ቼ 16-15			ETHIOPIC SYLLABLE CEE
+always ች 25			ETHIOPIC SYLLABLE CE
+always ቾ 16-135			ETHIOPIC SYLLABLE CO
+always ኀ 125			ETHIOPIC SYLLABLE XA
+always ኁ 125-136		ETHIOPIC SYLLABLE XU
+always ኂ 125-24			ETHIOPIC SYLLABLE XI
+always ኃ 125-1			ETHIOPIC SYLLABLE XAA
+always ኄ 125-15			ETHIOPIC SYLLABLE XEE
+always ኅ 5			ETHIOPIC SYLLABLE XE
+always ኆ 125-135		ETHIOPIC SYLLABLE XO
+always ኈ 12456-125		ETHIOPIC SYLLABLE XWA
+always ኊ 12456-125-136		ETHIOPIC SYLLABLE XWI
+always ኋ 12456-125-1		ETHIOPIC SYLLABLE XWAA
+always ኌ 12456-125-15		ETHIOPIC SYLLABLE XWEE
+always ኍ 12456-125-24		ETHIOPIC SYLLABLE XWE
+always ነ 1345			ETHIOPIC SYLLABLE NA
+always ኑ 1345-136		ETHIOPIC SYLLABLE NU
+always ኒ 1345-24		ETHIOPIC SYLLABLE NI
+always ና 1345-1			ETHIOPIC SYLLABLE NAA
+always ኔ 1345-15		ETHIOPIC SYLLABLE NEE
+always ን 1246			ETHIOPIC SYLLABLE NE
+always ኖ 1345-135		ETHIOPIC SYLLABLE NO
+always ኘ 346			ETHIOPIC SYLLABLE NYA
+always ኙ 346-136		ETHIOPIC SYLLABLE NYU
+always ኚ 346-24			ETHIOPIC SYLLABLE NYI
+always ኛ 346-1			ETHIOPIC SYLLABLE NYAA
+always ኜ 346-15			ETHIOPIC SYLLABLE NYEE
+always ኝ 26			ETHIOPIC SYLLABLE NYE
+always ኞ 346-135		ETHIOPIC SYLLABLE NYO
+always አ 3			ETHIOPIC SYLLABLE GLOTTAL A
+always ኡ 3-136			ETHIOPIC SYLLABLE GLOTTAL U
+always ኢ 3-24			ETHIOPIC SYLLABLE GLOTTAL I
+always ኣ 3-1			ETHIOPIC SYLLABLE GLOTTAL AA
+always ኤ 3-15			ETHIOPIC SYLLABLE GLOTTAL EE
+always እ 34			ETHIOPIC SYLLABLE GLOTTAL E
+always ኦ 3-135			ETHIOPIC SYLLABLE GLOTTAL O
+always ከ 13			ETHIOPIC SYLLABLE KA
+always ኩ 13-136			ETHIOPIC SYLLABLE KU
+always ኪ 13-24			ETHIOPIC SYLLABLE KI
+always ካ 13-1			ETHIOPIC SYLLABLE KAA
+always ኬ 13-15			ETHIOPIC SYLLABLE KEE
+always ክ 35			ETHIOPIC SYLLABLE KE
+always ኮ 13-135			ETHIOPIC SYLLABLE KO
+always ኰ 12456-13		ETHIOPIC SYLLABLE KWA
+always ኲ 12456-13-136		ETHIOPIC SYLLABLE KWI
+always ኳ 12456-13-1		ETHIOPIC SYLLABLE KWAA
+always ኴ 12456-13-15		ETHIOPIC SYLLABLE KWEE
+always ኵ 12456-13-24		ETHIOPIC SYLLABLE KWE
+always ኸ 1346			ETHIOPIC SYLLABLE KXA
+always ኹ 1346-136		ETHIOPIC SYLLABLE KXU
+always ኺ 1346-24		ETHIOPIC SYLLABLE KXI
+always ኻ 1346-1			ETHIOPIC SYLLABLE KXAA
+always ኼ 1346-15		ETHIOPIC SYLLABLE KXEE
+always ኽ 123456			ETHIOPIC SYLLABLE KXE
+always ኾ 1346-135		ETHIOPIC SYLLABLE KXO
+always ወ 2456			ETHIOPIC SYLLABLE WA
+always ዉ 2456-136		ETHIOPIC SYLLABLE WU
+always ዊ 2456-24		ETHIOPIC SYLLABLE WI
+always ዋ 2456-1			ETHIOPIC SYLLABLE WAA
+always ዌ 2456-15		ETHIOPIC SYLLABLE WEE
+always ው 246			ETHIOPIC SYLLABLE WE
+always ዎ 2456-135		ETHIOPIC SYLLABLE WO
+always ዐ 3			ETHIOPIC SYLLABLE PHARYNGEAL A
+always ዑ 3-136			ETHIOPIC SYLLABLE PHARYNGEAL U
+always ዒ 3-24			ETHIOPIC SYLLABLE PHARYNGEAL I
+always ዓ 3-1			ETHIOPIC SYLLABLE PHARYNGEAL AA
+always ዔ 3-15			ETHIOPIC SYLLABLE PHARYNGEAL EE
+always ዕ 34			ETHIOPIC SYLLABLE PHARYNGEAL E
+always ዖ 3-135			ETHIOPIC SYLLABLE PHARYNGEAL O
+always ዘ 1356			ETHIOPIC SYLLABLE ZA
+always ዙ 1356-136		ETHIOPIC SYLLABLE ZU
+always ዚ 1356-24		ETHIOPIC SYLLABLE ZI
+always ዛ 1356-1			ETHIOPIC SYLLABLE ZAA
+always ዜ 1356-15		ETHIOPIC SYLLABLE ZEE
+always ዝ 2346			ETHIOPIC SYLLABLE ZE
+always ዞ 1356-135		ETHIOPIC SYLLABLE ZO
+always ዠ 356			ETHIOPIC SYLLABLE ZHA
+always ዡ 356-136		ETHIOPIC SYLLABLE ZHU
+always ዢ 356-24			ETHIOPIC SYLLABLE ZHI
+always ዣ 356-1			ETHIOPIC SYLLABLE ZHAA
+always ዤ 356-15			ETHIOPIC SYLLABLE ZHEE
+always ዥ 236			ETHIOPIC SYLLABLE ZHE
+always ዦ 356-135		ETHIOPIC SYLLABLE ZHO
+always የ 13456			ETHIOPIC SYLLABLE YA
+always ዩ 13456-136		ETHIOPIC SYLLABLE YU
+always ዪ 13456-24		ETHIOPIC SYLLABLE YI
+always ያ 13456-1		ETHIOPIC SYLLABLE YAA
+always ዬ 13456-15		ETHIOPIC SYLLABLE YEE
+always ይ 1236			ETHIOPIC SYLLABLE YE
+always ዮ 13456-135		ETHIOPIC SYLLABLE YO
+always ደ 145			ETHIOPIC SYLLABLE DA
+always ዱ 145-136		ETHIOPIC SYLLABLE DU
+always ዲ 145-24			ETHIOPIC SYLLABLE DI
+always ዳ 145-1			ETHIOPIC SYLLABLE DAA
+always ዴ 145-15			ETHIOPIC SYLLABLE DEE
+always ድ 1456			ETHIOPIC SYLLABLE DE
+always ዶ 145-135		ETHIOPIC SYLLABLE DO
+always ጀ 245			ETHIOPIC SYLLABLE JA
+always ጁ 245-136		ETHIOPIC SYLLABLE JU
+always ጂ 245-24			ETHIOPIC SYLLABLE JI
+always ጃ 245-1			ETHIOPIC SYLLABLE JAA
+always ጄ 245-15			ETHIOPIC SYLLABLE JEE
+always ጅ 126			ETHIOPIC SYLLABLE JE
+always ጆ 245-135		ETHIOPIC SYLLABLE JO
+always ገ 1245			ETHIOPIC SYLLABLE GA
+always ጉ 1245-136		ETHIOPIC SYLLABLE GU
+always ጊ 1245-24		ETHIOPIC SYLLABLE GI
+always ጋ 1245-1			ETHIOPIC SYLLABLE GAA
+always ጌ 1245-15		ETHIOPIC SYLLABLE GEE
+always ግ 2356			ETHIOPIC SYLLABLE GE
+always ጎ 1245-135		ETHIOPIC SYLLABLE GO
+always ጐ 12456			ETHIOPIC SYLLABLE GWA
+always ጒ 12456-1245-136		ETHIOPIC SYLLABLE GWI
+always ጓ 12456-1245-1		ETHIOPIC SYLLABLE GWAA
+always ጔ 12456-1245-15		ETHIOPIC SYLLABLE GWEE
+always ጕ 12456-1245-24		ETHIOPIC SYLLABLE GWE
+always ጠ 23456			ETHIOPIC SYLLABLE THA
+always ጡ 23456-136		ETHIOPIC SYLLABLE THU
+always ጢ 23456-24		ETHIOPIC SYLLABLE THI
+always ጣ 23456-1		ETHIOPIC SYLLABLE THAA
+always ጤ 23456-15		ETHIOPIC SYLLABLE THEE
+always ጥ 12356			ETHIOPIC SYLLABLE THE
+always ጦ 23456-135		ETHIOPIC SYLLABLE THO
+always ጨ 14			ETHIOPIC SYLLABLE CHA
+always ጩ 14-136			ETHIOPIC SYLLABLE CHU
+always ጪ 14-24			ETHIOPIC SYLLABLE CHI
+always ጫ 14-1			ETHIOPIC SYLLABLE CHAA
+always ጬ 14-15			ETHIOPIC SYLLABLE CHEE
+always ጭ 36			ETHIOPIC SYLLABLE CHE
+always ጮ 14-135			ETHIOPIC SYLLABLE CHO
+always ጰ 235			ETHIOPIC SYLLABLE PHA
+always ጱ 235-136		ETHIOPIC SYLLABLE PHU
+always ጲ 235-24			ETHIOPIC SYLLABLE PHI
+always ጳ 235-1			ETHIOPIC SYLLABLE PHAA
+always ጴ 235-15			ETHIOPIC SYLLABLE PHEE
+always ጵ 3456			ETHIOPIC SYLLABLE PHE
+always ጶ 235-135		ETHIOPIC SYLLABLE PHO
+always ጸ 12346			ETHIOPIC SYLLABLE TSA
+always ጹ 12346-136		ETHIOPIC SYLLABLE TSU
+always ጺ 12346-24		ETHIOPIC SYLLABLE TSI
+always ጻ 12346-1		ETHIOPIC SYLLABLE TSAA
+always ጼ 12346-15		ETHIOPIC SYLLABLE TSEE
+always ጽ 345			ETHIOPIC SYLLABLE TSE
+always ጾ 12346-135		ETHIOPIC SYLLABLE TSO
+always ፀ 12346			ETHIOPIC SYLLABLE TZA
+always ፁ 12346-136		ETHIOPIC SYLLABLE TZU
+always ፂ 12346-24		ETHIOPIC SYLLABLE TZI
+always ፃ 12346-1		ETHIOPIC SYLLABLE TZAA
+always ፄ 12346-15		ETHIOPIC SYLLABLE TZEE
+always ፅ 345			ETHIOPIC SYLLABLE TZE
+always ፆ 12346-135		ETHIOPIC SYLLABLE TZO
+always ፈ 124			ETHIOPIC SYLLABLE FA
+always ፉ 124-136		ETHIOPIC SYLLABLE FU
+always ፊ 124-24			ETHIOPIC SYLLABLE FI
+always ፋ 124-1			ETHIOPIC SYLLABLE FAA
+always ፌ 124-15			ETHIOPIC SYLLABLE FEE
+always ፍ 4			ETHIOPIC SYLLABLE FE
+always ፎ 124-135		ETHIOPIC SYLLABLE FO
+always ፐ 1234			ETHIOPIC SYLLABLE PA
+always ፑ 1234-136		ETHIOPIC SYLLABLE PU
+always ፒ 1234-24		ETHIOPIC SYLLABLE PI
+always ፓ 1234-1			ETHIOPIC SYLLABLE PAA
+always ፔ 1234-15		ETHIOPIC SYLLABLE PEE
+always ፕ 6			ETHIOPIC SYLLABLE PE
+always ፖ 1234-135		ETHIOPIC SYLLABLE PO
+always ፡ 0			ETHIOPIC WORDSPACE
+always ። 256			ETHIOPIC FULL STOP
+always ፣ 2			ETHIOPIC COMMA
+always ፤ 23			ETHIOPIC SEMICOLON
+always ፥ 25			ETHIOPIC COLON
+always ፧ 236			ETHIOPIC QUESTION MARK
+always ፩ 16			ETHIOPIC DIGIT ONE
+always ፪ 126			ETHIOPIC DIGIT TWO
+always ፫ 146			ETHIOPIC DIGIT THREE
+always ፬ 1456			ETHIOPIC DIGIT FOUR
+always ፭ 156			ETHIOPIC DIGIT FIVE
+always ፮ 1246			ETHIOPIC DIGIT SIX
+always ፯ 12456			ETHIOPIC DIGIT SEVEN
+always ፰ 1256			ETHIOPIC DIGIT EIGHT
+always ፱ 246			ETHIOPIC DIGIT NINE
+always ፲ 16-3456		ETHIOPIC NUMBER TEN
+always ፳ 126-3456		ETHIOPIC NUMBER TWENTY
+always ፴ 146-3456		ETHIOPIC NUMBER THIRTY
+always ፵ 1456-3456		ETHIOPIC NUMBER FORTY
+always ፶ 156-3456		ETHIOPIC NUMBER FIFTY
+always ፷ 1246-3456		ETHIOPIC NUMBER SIXTY
+always ፸ 12456-3456		ETHIOPIC NUMBER SEVENTY
+always ፹ 1256-3456		ETHIOPIC NUMBER EIGHTY
+always ፺ 246-3456		ETHIOPIC NUMBER NINETY
+always ፻ 16-3456-3456		ETHIOPIC NUMBER HUNDRED
+always ፼ 16-3456-3456-3456-3456	ETHIOPIC NUMBER TEN THOUSAND
diff --git a/Tables/Contraction/countries.cti b/Tables/Contraction/countries.cti
new file mode 100644
index 0000000..b496b67
--- /dev/null
+++ b/Tables/Contraction/countries.cti
@@ -0,0 +1,261 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# show any "word" which contains a country code in 8-dot computer braille.
+literal .ad Andorra
+literal .ae United Arab Emirates
+literal .af Afghanistan
+literal .ag Antigua and Barbuda
+literal .ai Anguilla
+literal .al Albania
+literal .am Armenia
+literal .an Netherlands Antilles
+literal .ao Angola
+literal .aq Antarctica
+literal .ar Argentina
+literal .as American Samoa
+literal .at Austria
+literal .au Australia
+literal .aw Aruba
+literal .az Azerbaijan
+literal .ba Bosnia-Herzegovina
+literal .bb Barbados
+literal .bd Bangladesh
+literal .be Belgium
+literal .bf Burkina Faso
+literal .bg Bulgaria
+literal .bh Bahrain
+literal .bi Burundi
+literal .bj Benin
+literal .bm Bermuda
+literal .bn Brunei Darussalam
+literal .bo Bolivia
+literal .br Brazil
+literal .bs Bahamas
+literal .bt Bhutan
+literal .bv Bouvet Island
+literal .bw Botswana
+literal .by Belarus
+literal .bz Belize
+literal .ca Canada
+literal .cc Cocos (Keeling) Islands
+literal .cd The Democratic Republic of The Congo
+literal .cf Central African Republic
+literal .cg Congo
+literal .ch Switzerland
+literal .ci Ivory Coast
+literal .ck Cook Islands
+literal .cl Chile
+literal .cm Cameroon
+literal .cn China
+literal .co Colombia
+literal .cr Costa Rica
+literal .cu Cuba
+literal .cv Cape Verde
+literal .cx Christmas Island
+literal .cy Cyprus
+literal .cz Czech Republic
+literal .de Germany
+literal .dj Djibouti
+literal .dk Denmark
+literal .dm Dominica
+literal .do Dominican Republic
+literal .dz Algeria
+literal .ec Ecuador
+literal .ee Estonia
+literal .eg Egypt
+literal .eh Western Sahara
+literal .er Eritrea
+literal .es Spain
+literal .et Ethiopia
+literal .fi Finland
+literal .fj Fiji
+literal .fk Falkland Islands (Malvinas)
+literal .fm Micronesia
+literal .fo Faroe Islands
+literal .fr France
+literal .ga Gabon
+literal .gb United Kingdom
+literal .gd Grenada
+literal .ge Georgia
+literal .gf French Guiana
+literal .gh Ghana
+literal .gi Gibraltar
+literal .gl Greenland
+literal .gm Gambia
+literal .gn Guinea
+literal .gp Guadeloupe (Fr.)
+literal .gq Equatorial Guinea
+literal .gr Greece
+literal .gs South Georgia And The South Sandwich Islands
+literal .gt Guatemala
+literal .gu Guam (U.S.)
+literal .gw Guinea-Bissau
+literal .gy Guyana
+literal .hk Hong Kong
+literal .hm Heard Island And Mcdonald Islands
+literal .hn Honduras
+literal .hr Croatia
+literal .ht Haiti
+literal .hu Hungary
+literal .id Indonesia
+literal .ie Ireland
+literal .il Israel
+literal .im Isle of Man
+literal .in India
+literal .io British Indian Ocean Territory
+literal .iq Iraq
+literal .ir Iran
+literal .is Iceland
+literal .it Italy
+literal .jm Jamaica
+literal .jo Jordan
+literal .jp Japan
+literal .ke Kenya
+literal .kg Kyrgyzstan
+literal .kh Cambodia
+literal .ki Kiribati
+literal .km Comoros
+literal .kn Saint Kitts and Nevis
+literal .kp Korea (North)
+literal .kr Korea (South)
+literal .kw Kuwait
+literal .ky Cayman Islands
+literal .kz Kazakstan
+literal .la Lao People's Democratic Republic
+literal .lb Lebanon
+literal .lc Saint Lucia
+literal .li Liechtenstein
+literal .lk Sri Lanka
+literal .lr Liberia
+literal .ls Lesotho
+literal .lt Lithuania
+literal .lu Luxembourg
+literal .lv Latvia
+literal .ly Libyan Arab Jamahiriya
+literal .ma Morocco
+literal .mc Monaco
+literal .md Moldova
+literal .mg Madagascar
+literal .mh Marshall Islands
+literal .mk Macedonia
+literal .ml Mali
+literal .mm Myanmar
+literal .mn Mongolia
+literal .mo Macau
+literal .mp Northern Mariana Islands
+literal .mq Martinique
+literal .mr Mauritania
+literal .ms Montserrat
+literal .mt Malta
+literal .mu Mauritius
+literal .mv Maldives
+literal .mw Malawi
+literal .mx Mexico
+literal .my Malaysia
+literal .mz Mozambique
+literal .na Namibia
+literal .nc New Caledonia (Fr.)
+literal .ne Niger
+literal .nf Norfolk Island
+literal .ng Nigeria
+literal .ni Nicaragua
+literal .nl Netherlands
+literal .no Norway
+literal .np Nepal
+literal .nr Nauru
+literal .nu Niue
+literal .nz New Zealand
+literal .om Oman
+literal .pa Panama
+literal .pe Peru
+literal .pf Polynesia (Fr.)
+literal .pg Papua New Guinea
+literal .ph Philippines
+literal .pk Pakistan
+literal .pl Poland
+literal .pm Saint Pierre and Miquelon
+literal .pn Pitcairn
+literal .pr Puerto Rico (U.S.)
+literal .ps Palestinian Territory, Occupied
+literal .pt Portugal
+literal .pw Palau
+literal .py Paraguay
+literal .qa Qatar
+literal .re Reunion (Fr.)
+literal .ro Romania
+literal .ru Russia
+literal .rw Rwanda
+literal .sa Saudi Arabia
+literal .sb Solomon Islands
+literal .sc Seychelles
+literal .sd Sudan
+literal .se Sweden
+literal .sg Singapore
+literal .sh Saint Helena
+literal .si Slovenia
+literal .sj Svalbard and Jan Mayen
+literal .sk Slovakia
+literal .sl Sierra Leone
+literal .sm San Marino
+literal .sn Senegal
+literal .so Somalia
+literal .sr Suriname
+literal .st Sao Tome And Principe
+literal .su U.S.S.R.
+literal .sv El Salvador
+literal .sy Syrian Arab Republic
+literal .sz Swaziland
+literal .tc Turks And Caicos Islands
+literal .td Chad
+literal .tf French Southern Territories
+literal .tg Togo
+literal .th Thailand
+literal .tj Tajikistan
+literal .tk Tokelau
+literal .tm Turkmenistan
+literal .tn Tunisia
+literal .to Tonga
+literal .tp East Timor
+literal .tr Turkey
+literal .tt Trinidad and Tobago
+literal .tv Tuvalu
+literal .tw Taiwan
+literal .tz Tanzania
+literal .ua Ukraine
+literal .ug Uganda
+literal .uk United Kingdom
+literal .um United States Minor Outlying Islands
+literal .us United States
+literal .uy Uruguay
+literal .uz Uzbekistan
+literal .va Holy See (Vatican City State)
+literal .vc St. Vincent and the Grenadines
+literal .ve Venezuela
+literal .vg Virgin Islands, British
+literal .vi Virgin Islands, U.S.
+literal .vn Vietnam
+literal .vu Vanuatu
+literal .wf Wallis and Futuna
+literal .ws Samoa
+literal .ye Yemen
+literal .yt Mayotte
+literal .yu Yugoslavia
+literal .za South Africa
+literal .zm Zambia
+literal .zw Zimbabwe
diff --git a/Tables/Contraction/de-1998.ctb b/Tables/Contraction/de-1998.ctb
new file mode 100644
index 0000000..7f0cb41
--- /dev/null
+++ b/Tables/Contraction/de-1998.ctb
@@ -0,0 +1,1521 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German (contracted - 1998 standard)
+# Created by Mario Lang <mlang@delysid.org>.
+
+include de-g1.ctb
+
+class e e
+class g g
+class h h
+class m m
+class n n
+class r r
+class hilmnrsu hilmnrsu
+class l l
+class lnr lnr
+class st st
+class konsonant bcdfghjklmnpqrstvwxyz
+
+# Ausnahmen für Vokalgruppen aus de-g1.ctb
+before e always arteri 356-2345-12456-24
+always barrier 12-356-1235-24-12456 shouldn't use the ie-contraction
+always bankier 12-235-13-24-12456 shouldn't use the ie-contraction
+midendword iell 24-15-12345 shouldn't use the ie-contraction
+always indien 35-145-24-14 shouldn't use the ie-contraction
+always karrier 13-356-1235-24-12456
+always medien 134-15-145-24-14 shouldn't use the ie-contraction
+always propriet 12345-1234-1235-24-15-2345 proprietär shouldn't use the ie-contraction
+endword serie 234-12456-24-15
+always spezies 234-1234-15-1356-24-123456 shouldn't use the ie-contraction
+
+include de-wort.cti
+
+# Due to their low usage frequency in typical german text the letters
+# c, q, x and y are reused for the en-, ll-, ex/mm/nis- and el-contractions.
+# Therefore they need a proceding letsign to disambiguate them.
+always c 6-14
+always C 6-14
+always q 6-12345
+always Q 6-12345
+always x 6-1346
+always X 6-1346
+always y 6-13456
+always Y 6-13456
+
+# Lautgruppenkürzungen
+midendword ach 56
+begmidword al 25
+begmidword : 6-25
+begmidword an 235
+begmidword ar 356
+begmidword be 23
+midendword beule 12-126-123-15
+midendword beulen 12-126-123-14
+before g always beu 12-126
+always bell 23-12345 tabelle should use the ll-contraction
+midendword ck 46
+midword eh 2356
+always kohle =
+always kohlen 13-135-125-123-14
+always kohleintopf 13-135-125-123-1246-2345-135-1234-124
+always kohleintöpf 13-135-125-123-1246-2345-246-1234-124
+before g always kohleinla 13-135-125-123-1246-123-1
+before e always kohleul 13-135-125-123-126-123 ⠨⠅⠕⠓⠇⠣⠇⠉
+always ein 1246
+always einnen 15-35-1345-14 ⠨⠯⠓⠌⠎⠑⠔⠝⠉⠎⠩⠦ ⠨⠯⠓⠌⠎⠑⠔⠝⠉⠞⠷⠏⠻⠁⠞⠥⠗ ⠨⠧⠊⠵⠑⠔⠝⠉⠍⠔⠊⠾⠻
+always lateinisch 123-1-2345-146-1345-24-156 shouldn't use the ein-contraction
+always el 13456
+begmidword elineal 15-123-35-15-25
+endword elineal 15-123-35-15-1-123
+always eleist 15-123-146-23456
+always em 12356
+always emach 15-134-56
+before e always emachs 12356-56-234 Systemachse
+always en 14
+always denunz 145-15-1345-256-1356 shouldn't use the en-contraction
+always er 12456
+always dereferenzier 145-15-1235-15-124-12456-14-1356-346-1235 shouldn't use the er-contraction
+always deregulier 145-15-1235-15-1245-136-123-346-1235 shouldn't use the er-contraction
+always es 123456
+always ge 12346
+prfword gehe 1245-2356-15
+prfword gehen 1245-2356-14
+prfword gehend 1245-2356-14-145
+prfword gehende 1245-2356-14-145-15
+prfword gehendem 1245-2356-14-145-12356
+prfword gehenden 1245-2356-14-145-14
+prfword gehender 1245-2356-14-145-12456
+prfword gehendes 1245-2356-14-145-123456
+before konsonant always geh 1245-2356
+always umgehung 136-134-1245-2356-136 shouldn't use the ge-contraction
+before konsonant always bel 12-13456 Übelkeit shouldn't use the be-contraction
+word bel 12-13456
+before konsonant always bem 12-12356
+before konsonant always ben 12-14
+before konsonant always ber 12-12456
+before konsonant always gel 1245-13456 klingelton shouldn't use the ge-contraction
+before konsonant always gen 1245-14
+begword lungen 123-256-1245-14 shouldn't use the ge-contraction
+begword magen 134-1-1245-14 shouldn't use the ge-contraction
+before konsonant always ten 2345-14
+always elefant 13456-15-124-235-2345
+before konsonant always ter 2345-12456
+always güter 1245-1256-2345-12456 should use the er-contraction
+always güteregel 1245-1256-236-1235-15-1245-13456
+always gütericht 1245-1256-236-2-3456
+midendword ich 3456
+midendword ig 45
+always in 35
+
+midendword lich 456
+always lichtbogen 123-3456-2345-12-135-1245-14 shouldn't use the lich-contraction
+always lichtnahr 123-3456-2345-1345-1-125-1235 shouldn't use the lich-contraction
+always lichtsch 123-3456-2345-156
+always bodenlicht 12-135-145-14-123-3456-2345 shouldn't use the lich-contraction
+begword see =
+begword neusee 1345-126-234-15-15
+#before konsonant always seen 234-15-14
+word seele 234-15-13456-15
+sufword seelen 234-15-13456-14
+always seelisch 234-15-13456-24-156
+word teer 2345-15-12456
+sufword teerartig 2345-15-12456-356-2345-45
+before konsonant begword teer 2345-15-12456
+word teere 2345-15-12456-15
+word teeren 2345-15-12456-14
+word teerend 2345-15-12456-14-145
+word teerende 2345-15-12456-14-145-15
+word teerendem 2345-15-12456-14-145-12356
+word teerenden 2345-15-12456-14-145-14
+word teerender 2345-15-12456-14-145-12456
+word teerendes 2345-15-12456-14-145-123456
+word teerung 2345-15-12456-136
+word teerungen 2345-15-12456-136-14
+always teelich 2345-15-15-456 shouldn't use the el-contraction
+always teelöffel 2345-15-15-123-246-124-124-13456 shouldn't use the el-contraction
+always teemaschin 2345-15-15-134-156 shouldn't use the em-contraction
+always teesieb 2345-15-15-234-346-12 shouldn't use the es-contraction
+midendword ll 12345
+always holland 125-135-123-123-235-145 shouldn't use the ll-contraction
+always holländer 125-135-123-123-345-1345-145-12456 shouldn't use the ll-contraction
+always hollaender 125-135-123-123-345-1345-145-12456 shouldn't use the ll-contraction
+midendword mm 1346
+always wurm = wurmmittel shouldn't use the mm-contraction
+begmidword or 26
+
+always schaos 234-1456-1-135-234
+after konsonant midword ss =
+after konsonant midendword ssatz 234-234-1356
+after konsonant midendword ssätz 234-5-234-1356
+after konsonant midendword ssaetz 234-5-234-1356
+after konsonant midword ssch 234-156
+always sschicht 234-156-3456-2345
+always sschlag 234-156-1245
+always sschläg 234-5-156-1245
+always sschlaeg 234-5-156-1245
+always sschließ 234-156-2346
+always sschmuck 234-156-134-136-46
+always sschnur 234-156-1345-136-1235 shouldn't use the nur-contraction
+always sschool 234-234-1456-135-135-123 foreign word, sch-contraction not allowed
+always sschreib 234-156-12
+always sschrieb 234-2-156
+always sschrift 234-156-2345
+always sschwierig 234-156-45
+always sselbst 234-234-23456
+always sselbständ 234-234-13456-12-5-23456 shouldn't use the selbst-contraction
+always sselbstaend 234-234-13456-12-5-23456 shouldn't use the selbst-contraction
+after konsonant always ssetz 234-2-15
+after konsonant midendword ssesam 234-234-123456-1-134 shouldn't use the sam-contraction
+always ssicher 234-234-3456-12456
+after konsonant always ssitz 234-2-24
+always ssolch 234-234-1456
+after konsonant midendword ssoll 234-2-234
+always ssondern 234-234-1345
+always ssozial 234-234-123
+always espiel 15-2-346
+always sspiel 234-2-346
+always esprach 15-234-1234
+always ssprach 234-234-1234
+always sspräch 234-5-234-1234
+always sspraech 234-5-234-1234
+always esprech 15-2-2346
+always ssprech 234-2-2346
+after konsonant midendword sst 234-23456
+always sstaat 234-23456-2345
+after konsonant midendword sstand 234-2-23456
+after konsonant midendword sständ 234-5-23456
+after konsonant midendword sstaend 234-5-23456
+after konsonant midendword sstell 234-2-13456
+always ssteiger 234-23456-146-1245-12456
+before e always sstund 234-23456-256-145 shouldn't use the und-contraction
+midendword ss 2346
+
+midendword te 236
+sufword atem 1-2345-12356 shouldn't use the te-contraction
+always un 256
+
+midendword tei 2345-146
+
+always all 1-12345
+always ell 15-12345
+always emm 15-1346
+always esch 15-156
+always ess 15-2346
+always est 15-23456
+
+endword tel 2345-13456
+always scheitel 156-146-2345-13456 shouldn't use the te-contraction
+begword bettel 12-15-2345-2345-13456 shouldn't use the te-contraction
+endword teln 2345-13456-1345
+endword tels 2345-13456-234
+endword tem 2345-12356
+endword tene 2345-14-15
+always tten 2345-2345-14 shouldn't use the te-contraction
+always tter 2345-2345-12456 shouldn't use the te-contraction
+always daten 145-1-2345-14 
+always karten 13-356-2345-14
+midendword maten 134-1-2345-14 shouldn't use the te-contraction
+endword tenem 2345-14-12356
+endword tenen 2345-14-14
+endword tener 2345-14-12456
+endword benes 12-14-123456
+endword tenes 2345-14-123456
+endword tens 2345-14-234
+endword ter 2345-12456
+endword tere 2345-12456-15
+endword terem 2345-12456-12356
+endword teren 2345-12456-14
+endword teres 2345-12456-123456
+endword ters 2345-12456-234
+always liter 123-24-2345-12456
+endword tern 2345-12456-1345
+endword tes 2345-123456
+
+endword bel 12-13456 shouldn't use the be-contraction
+endword beln 12-13456-1345 shouldn't use the be-contraction
+endword belns 12-13456-1345-234 shouldn't use the be-contraction
+endword bels 12-13456-234 shouldn't use the be-contraction
+midword belläng 12-13456-5-123-1245 kabellänge shouldn't use the ll-contraction
+midword bellaeng 12-13456-5-123-1245
+midendword belung 12-13456-136
+before st midendword belungs 12-13456-136-234
+always belveder 12-13456-1236-15-145-12456 shouldn't use the be-contraction
+always wirbel 2456-24-1235-12-13456 shouldn't use the be-contraction
+endword ben 12-14 shouldn't use the be-contraction
+endword bend 12-14-145 shouldn't use the be-contraction
+endword bende 12-14-145-15 shouldn't use the be-contraction
+endword bendem 12-14-145-12356 shouldn't use the be-contraction
+endword benden 12-14-145-14 shouldn't use the be-contraction
+endword bender 12-14-145-12456 shouldn't use the be-contraction
+endword bendes 12-14-145-123456 shouldn't use the be-contraction
+endword benem 12-14-12356 shouldn't use the be-contraction
+endword benen 12-14-14 shouldn't use the be-contraction
+endword bens 12-14-234 shouldn't use the be-contraction
+endword ber 12-12456 shouldn't use the be-contraction
+endword berei 12-12456-146 shouldn't use the be-contraction
+endword bereien 12-12456-146-14 shouldn't use the be-contraction
+endword berin 12-12456-35 shouldn't use the be-contraction
+endword berinnen 12-12456-35-1345-14 shouldn't use the be-contraction
+endword bern 12-12456-1345 shouldn't use the be-contraction
+endword bers 12-12456-234 shouldn't use the be-contraction
+always ober 135-12-12456 shouldn't use the be-contraction
+endword bes 12-123456 shouldn't use the be-contraction
+
+always begeh 23-1245-2356 shouldn't use the ge-contraction
+always getriebegehäus 12346-2345-1235-346-23-12346-125-34-234 shouldn't use the eh-contraction
+always getriebegehaeus 12346-2345-1235-346-23-12346-125-34-234 shouldn't use the eh-contraction
+always umgeht 136-134-1245-2356-2345 shouldn't use the ge-contraction
+always geig 1245-146-1245 should use the ei-contraction
+always geisel 1245-146-234-13456 shouldn't use the ge-contraction
+always geist 1245-146-23456
+prfword gel 1245-13456 shouldn't use the ge-contraction
+midendword gelhaft 1245-13456-125-124
+midword gelläng 1245-13456-5-123-1245
+midword gellaeng 1245-13456-5-123-1245
+prfword geln 1245-13456-1345
+prfword gelns 1245-13456-1345-234
+prfword gels 1245-13456-234
+midendword gelung 1245-13456-136
+before st midendword gelungs 1245-13456-136-234
+prfword gen 1245-14 shouldn't use the ge-contraction
+midendword gend 1245-14-145 shouldn't use the ge-contraction
+endword gens 1245-14-234 shouldn't use the ge-contraction
+midendword ger 1245-12456 should use er-contraction if not part of a word intro
+before r begword abge 1-12-12346
+before r begword ange 235-12346
+word anger 235-1245-12456
+word angern 235-1245-12456-1345
+word angers 235-1245-12456-234
+begword unange 256-235-12346
+begmidword aufger 2-16-12346-1235
+begmidword ausger 34-12346-1235
+before r begmidword einge 1246-12346
+begword unger 256-12346-1235
+sufword ungerecht 256-12346-1235-2345
+always gerieben 12346-1235-346-12-14 should use the ge-contraction
+sufword zuge 2-1356-12346
+endword ges 1245-123456 shouldn't use the ge-contraction
+always sieges 234-346-1245-123456
+always tageslicht 2345-1-1245-123456-123-3456-2345 shoudln't use the ge- nor lich-contraction
+always euthanasie 126-2345-125-235-1-234-346
+always antasie 235-2345-1-234-346
+endword antasien 235-2345-1-234-24-14
+midendword ien 24-14 shouldn't use the ie-contraction
+always erschien 12456-156-346-1345
+prfword industrien 35-145-136-23456-1235-24-14
+before n always industrie 35-145-136-23456-1235-346 industrienation shouldn't use the en-contraction
+begmidword anomal 235-135-134-25 shouldn't use the mal-contraction
+endword anomal 235-135-134-1-123 shouldn't use the mal-contraction
+midendword iene 346-1345-15 should use the ie-contraction
+midendword ienen 346-1345-14 should use the ie-contraction
+endword tel 2345-13456 should use the el-contraction
+endword ten 2345-14 should use the en-contraction
+endword ter 2345-12456 should use the er-contraction
+endword tes 2345-123456 should use the es-contraction
+always trigraph = shouldn't use the ig-contraction
+
+# Vorsilbenkürzungen
+begword aus 34
+begword ent 2346
+word enter 14-2345-12456 shouldn't use the ent-contraction
+begword ex 1346
+begword pro 12345
+begword ver 36
+word verb 1236-12456-12
+word vers 1236-12456-234
+
+begword auspiz 16-234-1234-24-1356 shouldn't use the aus-contraction
+word ente 14-236 shouldn't use the ent-contraction
+sufword enten 14-2345-14 shouldn't use the ent-contraction
+word entchen 14-2345-1456-14
+begword veranda 1236-12456-236-145-1 shouldn't use the ver-contraction
+begword vertikal 1236-12456-2345-24-13-25 shouldn't use the ver-contraction
+word vertikal 1236-12456-2345-24-13-1-123 shouldn't use the ver-contraction
+
+# Nachsilbenkürzungen
+endword falls 124
+midendword heit 125
+before s midword heits 125-234
+midendword keit 13
+before s midword keits 13-234
+midendword mal 134
+sufword gemal 12346-134-25 gemalt shouldn't use the mal-contraction
+midendword nis 1346
+always nisier 1345-24-234-346-1235 technisierung shouldn't use -nis
+midendword sam 2346
+sufword bisam =
+always bischofsamt 12-24-156-135-124-234-1-134-2345 shouldn't use the sam-contraction
+midendword schaft 156
+midendword ung 136
+always dschungel 6-145-156-256-1245-13456 shouldn't use the ung-contraction
+midendword terung 2345-12456-136
+before cst midword ungs 136-234 Bindungscharakter, Regierungschef
+midendword wärts 2456
+
+midendword ation 5-1345
+always industrienation 35-145-136-23456-1235-346-1345-5-1345
+before s midword ations 5-1345-234
+midendword ativ 5-1236
+after st always ion 245
+always religion 1235-13456-45-245
+after st before st always ions 245-234
+always action 1-6-14-2345-245
+midword ionstricht 245-234-2345-1235-3456-2345
+endword ismus 5-24
+midendword istisch 5-156
+endword nismus 1345-5-24 shouldn't use the nis-contraction
+midendword nistisch 1345-5-156
+midendword ität 5-345
+midendword itaet 5-345
+before s midword itäts 5-345-234
+before s midword itaets 5-345-234
+midendword mität 134-5-345 shouldn't use the mit-contraction
+
+sufword anis 235-24-234 shouldn't use the nis-contraction
+before st midword ations 5-1345-234
+sufword barschaft 12-356-156-1-124-2345 shouldn't use the shaft-contraction
+always blumensamen 12-123-136-134-14-234-1-134-14 shouldn't use the sam-contraction
+prfword dezimal = if we use the mal-contraction here, we get a new word dezim
+begmidword dezimal 145-15-1356-24-134-25
+sufword small 234-134-1-12345 shouldn't use the mal-contraction
+word beaufort 12-15-16-124-26-2345 shouldn't use the auf-contraction
+sufword erheiter 12456-125-146-2345-12456 shouldn't use the heit-contraction
+prfword formal 124-26-134-1-123 shouldn't use the mal-contraction
+begword formal 124-26-134-25 shouldn't use the mal-contraction
+word firnis = shouldn't use the nis-contraction
+begword gesam 12346-234-1-134 shouldn't use the sam-contraction
+always herrschaft 1235-1235-156-1-124-2345 can not use the shaft-contraction
+always hoheit 125-135-125-146-2345 shouldn't use the heit-contraction
+always kuhdung 13-136-125-145-256-1245 shouldn't use the ung-contraction
+always hunger 125-256-1245-12456 shouldn't use the ung-contraction
+before g begword lun 123-256 shouldn't use the ung-contraction
+prfword maximal = shouldn't use the mal-contraction
+begmidword maximal 134-1-6-1346-24-134-25 shouldn't use the mal-contraction
+always tennis 2345-14-1345-24-234 shouldn't use the nis-contraction
+midendword ungscharakter 136-234-1456-13
+before st midendword ungs 136-234 shouldn't use the ss/st-contraction
+midendword zung 1356-136 auseinandersetzung shouldn't use the zu-contraction
+begword zung 1356-256-1245 shouldn't use the ung-contraction
+
+# Einformige Kürzungen, nur alleinstehend
+word als 146
+word auch 34
+word eu =
+word das 145
+word dass 2346
+word den 15
+word der 1235
+word des 3
+word die 346
+word ihm 236
+word im 36 not allowed when used in hyphenated words like Hans-im-Glück-Gefühl
+after letter literal -im-
+word ist 23456
+word kann 13
+word lässt 123
+word laesst 123
+word man 134
+word oder 135
+word schon 156
+word sich 14
+word sie 234
+word was 2456
+
+# Einformige Kürzungen, alleinstehend oder in Wortverbindungen
+word aber 1
+always aber 2-1
+always aberdeen 1-12-12456-145-15-15-1345
+midword aberech 1-23-1235-15-1456
+midendword abereich 1-23-1235-146-1456
+midendword abericht 1-23-2-3456
+begmidword aberkann 1-12-12456-13-235-1345
+always aberkenn 1-12-12456-13-14-1345
+begmidword abernt 1-12-12456-1345-2345
+prfword abernte 1-12-12456-1345-236
+prfword aberntest 1-12-12456-1345-236-23456
+prfword aberntet 1-12-12456-1345-236-2345
+prfword aberntete 1-12-12456-1345-236-236
+prfword abernteten 1-12-12456-1345-236-2345-14
+prfword aberntetest 1-12-12456-1345-236-236-23456
+prfword aberntetet 1-12-12456-1345-236-236-2345
+always aberrans 1-12-12456-1235-235-234
+always aberratio 1-12-12456-1235-1-2345-24-135
+always aberration 1-12-12456-1235-5-1345
+always aberrier 1-12-12456-1235-346-1235
+always aberzieh 1-12-12456-1356-346-125
+always aaberg 1-1-12-12456-1245
+always cabernet 6-14-1-12-12456-1345-15-2345
+sufword faber 124-1-12-12456
+midword gaber 1245-1-23-1235
+always gaberecht 1245-1-23-1235-2345
+always gaberegel 1245-1-23-1235-15-1245-13456
+sufword haber 125-1-12-12456
+always pharmaberat 1234-125-356-134-1-23-1235-1-2345
+always schaber 156-1-12-12456
+always eisschaber 146-234-156-1-12-12456
+always makaber 134-1-13-1-12-12456
+always kandelaber 13-235-145-13456-1-12-12456
+always laber 123-1-12-12456
+always annaberg 235-1345-1-12-12456-1245
+always araber 356-1-12-12456
+always graber 1245-1235-1-12-12456
+always traber 2345-1235-1-12-12456
+begmidword tabern 2345-1-12-12456-1345
+always waber 2456-1-12-12456
+always bergzabern 12-12456-1245-1356-1-12-12456-1345
+
+word auf 16
+always auf 2-16
+before g always aufwie 2-16-2456-346 aufwiegeln/aufwiegler/aufwiegst shouldn't use the wie-contraction
+before s always aufwie 2-16-2456-346
+begword aufzuck 2-16-1356-136-46
+before konsonant always aufzug 2-16-1356-136-1245
+always aufzugsturm 2-16-1356-136-1245-234-2345-136-1235-134 shouldn't use zu-contraction and st-contraction
+before m always aufzugstür 2-16-1356-136-1245-234-2345-1256-1235 shouldn't use zu-contraction and st-contraction
+always aufzugstür 2-16-1356-136-1245-234-2345-1256-1235 shouldn't use zu-contraction and st-contraction
+prfword lauf 123-16-124
+prfword laufe 123-16-124-15
+prfword laufen 123-16-124-14
+prfword laufend 123-16-124-14-145
+prfword laufende 123-16-124-14-145-15
+prfword laufendem 123-16-124-14-145-12356
+prfword laufenden 123-16-124-14-145-14
+prfword laufender 123-16-124-14-145-12456
+prfword laufendes 123-16-124-14-145-123456
+prfword laufens 123-16-124-14-234
+prfword laufes 123-16-124-123456
+prfword laufs 123-16-124-234
+prfword laufst 123-16-124-23456
+prfword lauft 123-16-124-2345
+always schlauf 156-123-16-124 shouldn't use the auf-contraction
+always abgelauf 1-12-12346-123-16-124
+always akkulauf 1-13-13-136-123-16-124
+always auflauf 2-16-123-16-124
+always durchlauf 2-1456-123-16-124
+begword verlauf 36-123-16-124 shouldn't use the auf-contraction
+midendword verlauf 1236-12456-123-16-124 shouldn't use the auf-contraction
+
+word bei 12
+always bei 2-12
+always beizung 12-146-1356-136 shouldn't use the bei-contraction
+always beiß 12-146-6-2346 shouldn't use the bei-contraction
+endword bein 12-1246 shouldn't use the bei-contraction
+endword beine 12-1246-15 shouldn't use the bei-contraction
+endword beinen 12-1246-14 shouldn't use the bei-contraction
+endword beines 12-1246-123456 shouldn't use the bei-contraction
+endword beins 12-1246-234 shouldn't use the bei-contraction
+before t always beinhal 23-35-125-25 shouldn't use the bei-contraction
+always beinhalter 12-1246-125-25-2345-12456 should use the ei-contraction
+always darmbein 145-356-134-12-1246
+always gabeinvent 1245-1-23-35-1236-14-2345 Übergabeinventar shouldn't use the bei-contraction
+always kreuzbein 13-1235-126-1356-12-1246
+always schienbein 156-346-1345-12-1246
+always schlüsselbein 156-123-1256-2346-13456-12-1246 shouldn't use the bei-contraction
+
+word dem 12356
+always dem 2-12356
+endword dem 145-12356 shouldn't use the dem-contraction
+always außerdem 16-6-2346-12456-2-12356
+always demask = demaskiert shouldn't use the dem-contraction
+always demilit = shouldn't use the dem-contraction
+always demonstr 145-12356-135-1345-23456-1235 shouldn't use the dem-contraction
+word demo 145-12356-135 shouldn't use the dem-contraction
+word demos 145-12356-135-234 shouldn't use the dem-contraction
+
+word durch 1456
+always durch 2-1456
+sufword durchzuck 2-1456-1356-136-46 shouldn't use the zu-contraction
+word durchzug 2-1456-1356-136-1245 shouldn't use the zu-contraction
+word durchzuges 2-1456-1356-136-1245-123456 shouldn't use the zu-contraction
+sufword durchzugs 2-1456-1356-136-1245-234 shouldn't use the zu-contraction
+#begword durcheinandergerat 1456-2-1246-12346-1235-1-2345
+always zugerat 2-1356-12346-1235-1-2345 shouldn't use the er-contraction
+
+word für 124
+always für 2-124
+always fürst 124-1256-1235-23456 shouldn't use the für-contraction
+
+word gegen 1245
+always gegen 2-1245
+prfword gegenzug 2-1245-1356-136-1245
+prfword gegenzuge 2-1245-1356-136-12346
+prfword gegenzuges 2-1245-1356-136-1245-123456
+prfword gegenzugs 2-1245-1356-136-1245-234
+before g sufword gegenzun 2-1245-1356-256
+
+word gewesen 12346
+always gewesen 2-12346
+always fürsorgewesen 2-124-234-26-12346-2456-123456-14 shouldn't use the gewesen-contraction
+
+word immer 1346
+always immer 2-1346
+always immersion 24-1346-12456-234-245 shouldn't use the immer-contraction
+always immersiv 24-1346-12456-234-24-1236
+always flimmer 124-123-24-1346-12456
+always wimmer 2456-24-1346-12456 shouldn't use the immer-contraction
+always zimmer 1356-24-1346-12456 shouldn't use the immer-contraction
+
+word jetzt 245
+always jetzt 2-245
+
+word mehr 2356
+always mehr 2-2356
+
+word mit 2345
+always mit 2-2345
+
+word nicht 1345
+word n 6-1345
+always nicht 2-1345
+always nichtzughör 2-1345-1356-136-1245-125-246-1235
+always nichtzuck 2-1345-1356-136-46
+
+word so 1234
+always so 2-1234
+endword son =
+always cursor = shouldn't use the so-contraction
+begword		absol		=
+begword		absorb		=
+begword		absorp		=
+always		adsorbier	1-145-234-26-12-346-1235
+always		aerosol		1-12456-135-234-135-123
+always		amtsober	1-134-2345-234-135-12-12456
+always		anthroposo	235-2345-125-1235-135-1234-135-234-135
+always		chromosom	1456-1235-135-134-135-234-135-134 shouldn't use the so-contraction
+always		iso		=
+always		konson		=
+begmidword	sensor		234-14-234-26
+endword		sensor		234-14-234-135-1235
+endword		sensor		234-14-234-135-1235
+always		sockel		234-135-46-13456
+word		soda		=
+always		soffizier	234-135-124-124-24-1356-346-1235
+always		soft		=
+always		soldat		=
+endword		solo		=
+always		sommer		234-135-1346-12456
+before konsonant always	son =
+always		sonogra		=
+always		sonn		=
+always		sonst		234-135-1345-23456
+always		sorientier	234-26-24-14-2345-346-1235
+before konsonant always	sor		234-26
+always		source		=
+sufword		south		=
+always		sowjet		=
+always		soziolo		=
+midword ungsopt 136-234-135-1234-2345 Abendgestaltungsoption shouldn't use the so-contraction
+
+word über 1256
+word ueber 1256
+always über 2-1256
+always überzucht 2-1256-1356-136-1456-2345
+always überzuck 2-1256-1356-136-46
+prfword überzug 2-1256-1356-136-1245
+prfword überzuges 2-1256-1356-136-1245-123456
+before konsonant always überzug 2-1256-1356-136-1245
+always überlauf 2-1256-123-16-124
+
+word und 136
+always und 2-136
+sufword gesund 12346-234-256-145
+prfword hund 125-256-145 shouldn't use the und-contraction
+always hunde 125-256-145-15 shouldn't use the und-contraction
+always hundert 125-256-145-12456-2345 shouldn't use the und-contraction
+prfword hunderte 125-256-145-12456-236 shouldn't use the und-contraction
+prfword hunden 125-256-145-14 shouldn't use the und-contraction
+prfword hundes 125-256-145-123456 shouldn't use the und-contraction
+always kund 13-256-145 shouldn't use the und-contraction
+always mund 134-256-145 shouldn't use the und-contraction
+always rundfunk 1235-256-145-124-256-13 shouldn't use the und-contraction
+sufword schrund 156-1235-256-145
+always wund 2456-256-145 shouldn't use the und-contraction
+
+word unter 256
+always unter 2-256
+sufword kunter 13-256-2345-12456 shouldn't use the unter-contraction
+sufword kunterbunter 13-256-2345-12456-12-256-2345-12456
+sufword munter 134-256-2345-12456 shouldn't use the unter-contraction
+sufword untereinander 256-2-1246
+always virus =
+
+word voll 12345
+always voll 2-12345
+prfword vollzug 2-12345-1356-136-1245
+prfword vollzuge 2-12345-1356-136-12346
+prfword vollzuges 2-12345-1356-136-1245-123456
+always vollzugs 2-12345-1356-136-1245-234
+
+word von 1236
+always von 2-1236
+
+word vor 26
+always vor 2-26
+always vorzugs 2-26-1356-136-1245-234
+always vorzugstimmen 2-26-1356-136-1245-23456-24-1346-14
+always favorit 124-1-1236-26-24-2345 shouldn't use the vor-contraction
+
+word wie 126
+always wie 2-126
+always zwiebel 1356-2456-346-12-13456 shouldn't use the wie-contraction
+
+word zu 1356
+always zu 2-1356
+always zugentlast 1356-136-1245-14-2345-123-1-23456 shouldn't use the zu- nor ge-contraction
+word indem 35-2-12356
+word trotzdem 2345-1356-2-12356
+word zudem 1356-2-12356
+always zucht 1356-136-1456-2345 shouldn't use the zu-contraction
+always zuck 1356-136-46 shouldn't use the zu-contraction
+before g always zug =
+endword zug =
+endword zuges 1356-136-1245-123456
+
+# Einformige Kürzungen, alleinstehend oder am Wortanfang
+sufword ihr 24
+sufword sein 246
+word war 356
+word waren 356-14
+word warst 356-23456
+word wart 356-2345
+word war's 356-6-234
+word wär 5-356
+word wäre 5-356-15
+word wären 5-356-14
+word wärest 5-356-15-23456
+word wäret 5-356-15-2345
+word wärst 5-356-23456
+word wärt 5-356-2345
+word wär's 5-356-6-234
+
+# Einformige Kürzungen, alleinstehend, mit Endungen oder in Wortverbindungen
+always hatt 125
+always hätt 345
+always haett 345
+always welch 13456
+
+word adonis = shouldn't use the nis-contraction
+always aktuell 1-13-2345-136-15-12345 should use the ll-contraction
+always all 1-12345
+sufword alle 1-15
+always allegor 1-12345-15-1245-26 Allegorie shouldn't use the ae-contraction
+always allein 1-1246
+word allem 1-12356
+always allen 1-14
+always aller 1-12456
+always allerg 1-12345-12456-1245 Allergiker
+word alles 1-123456
+always allesamt 1-12345-15-234-1-134-2345 shouldn't use the es-contraction
+always alphabet 25-1234-125-1-12-15-2345 shouldn't use the hab-contraction
+word also 1-135
+always ander 2-12456
+always wander 2456-235-145-12456 wandern shouldn't use the ander-contraction
+sufword zander 1356-235-145-12456
+always änder 5-12456
+always abänder 1-12-5-12456
+always bänder 12-345-1345-145-12456 shouldn't use the änder-contraction
+always aender 5-12456
+always arbeit 356-12
+before s always arbeits 356-12-234
+always arben 356-12-14
+always kauf 13-16-124
+begword aussprech 16-2-2346
+midendword aussprech 16-234-2-2346
+midendword ausstell 16-234-2-13456
+always australi 16-23456-1235-25-24 shouldn't use the aus-contraction
+always austria 16-23456-1235-24-1 shouldn't use the aus-contraction
+#begword äuß 5-34 FIXME: how to deal with äußerst?
+
+always ähnlich 345-456
+always aehnlich 345-456
+
+word balsam 12-25-234-1-134
+word been 12-15-15-1345 english word shouldn't use be- or en-contraction
+always beere 12-15-15-1235-15
+always beeren 12-15-15-1235-14
+always behr 12-2356-1235 entbehren
+begword beid 12-145 beiderseits
+always berg 12-12456-1245 shouldn't use the be-contraction
+always berge 12-12456-12346 shouldn't use the be-contraction
+always bergen 12-12456-1245-14 shouldn't use the be-contraction
+always berger 12-12456-1245-12456 shouldn't use the be-contraction
+always berges 12-12456-1245-123456 shouldn't use the be-contraction
+always besonder 23
+always besser 234-234
+contraction ss
+word beim 12-134
+contraction bm
+word bis 12-234
+sufword bisher 12-234-125-12456
+sufword bislang 12-2345-123-1245
+sufword bisweil 12-234-2456-146-123
+always bison =
+
+always bist 12-23456
+always bistum 12-24-23456-136-134 shouldn't use the bist-contraction
+always bleib 12-12
+contraction bb
+always blind 12-123
+contraction bl
+always brauch 2-34
+always bräuch 5-34
+always braeuch 5-34
+always brief 12-124
+contraction bf
+always bring 12-1245
+contraction bg
+
+always charakter 1456-13
+sufword chor 1456-135-1235 shouldn't use the or-contraction
+always comput 6-14-135-134-1234-136-2345 computer should use the er-contraction
+
+always dabei 145-12
+contraction db
+always dadurch 145-145
+contraction dd
+always dafür 145-124
+contraction df
+always dagegen 145-1245
+contraction dg
+always daher 145-125
+contraction dh
+always damit 145-134
+contraction dm
+always dank 145-13
+contraction dk
+always davon 145-1236
+contraction dv
+always dazu 145-1356
+contraction dz
+always dazubleib 145-1-2-1356-12-12 shouldn't use the dazu-contraction
+always dazumal 145-1-2-1356-134
+always deuten 145-126-2345-14 shouldn't use the te-contraction
+always deal = dealer
+word dei = Agnus Dei
+always demokrat 145-2345
+contraction dt
+word denen 15-14
+word dnister 145-1345-24-234-2345-12456 shouldn't use the nis-contraction
+word denn 145-1345
+word dennschon 145-1345-156-135-1345
+always dessen 145-2346
+always deutsch 145-156
+word diem 145-24-12356 we shouldn't use the ie-contraction here
+word diese 346-15
+word diesen 346-14
+word dieser 346-12456
+word dieses 346-123456
+sufword diesmal 346-134
+word dir 145-1235
+word doch 145-1456
+always druck 145-46
+always drück 5-145-46
+always dürf 2-145
+
+always eben 15-12-14
+word ebenso 15-135
+contraction eo
+sufword ehemal 15-125-15-134
+word ei 6-146
+always eigen 146-1245-14 Eigennutz shouldn't use the ge-contraction
+always einander 2-1246
+word en 15-1345 en passant
+always enig 14-45
+always erkenn 12456-13-14-1345
+sufword etwa 15-1
+contraction ea
+word etwas 2345-2456
+contraction tw
+
+always fahr 2-1235
+always fahrtsst 2-1235-2345-234-23456 shouldn't use the ss-contraction
+always fahrtsstell 2-1235-2345-234-2-13456
+always fähr 5-1235
+always faehr 5-1235
+always fall 124-12345
+always fäll 5-124-12345
+always faell 5-124-12345
+always fertig 124-45
+always fest 124-15-23456 should use the st-contraction
+always film =
+always folgen 124-135-123-1245-14 shouldn't use the ge-contraction
+always frag 124-1235
+contraction fr
+always fragil =
+always fragment 124-1235-1-1245-134-14-2345
+always freund 124-145
+contraction fd
+always führ 124-125
+contraction fh
+always fürcht 124-1256-1235-1456-2345 shouldn't use the für-contraction
+
+always ganz 1245-1356
+contraction gz
+always gänz 5-1245-1356
+always gaenz 5-1245-1356
+always garnison 1245-356-1345-24-234-135-1345 shouldn't use the nis-contraction
+word gegend 12346-1245-14-145 shouldn't use the gegen-contraction
+always gegenüber 1245-1256
+contraction gü
+always gegenwart 1245-2456
+contraction gw
+always gegenwärt 5-1245-2456
+always gegenwaert 5-1245-2456
+always gelb 1245-13456-12 should use the el-contraction
+always geld 1245-13456-145 should use the el-contraction
+always gelegen 1245-1245
+contraction gg
+begword gelt 1245-13456-2345 gelten shouldn't use the ge-contraction
+prfword gene 1245-14-15
+prfword genem 1245-14-12356
+prfword genen 1245-14-14
+prfword gener 1245-14-12456
+prfword genes 1245-14-123456
+always geogra = Geographie shouldn't use the ge-contraction
+prfword gern 1245-12456-1345
+midendword gerlich 1245-12456-456 bürgerlich shouldn't use the ge-contraction
+always gern 1245-12456-1345 we shouldn't use the ge-contraction here
+always geschäft 1245-124
+always geschaeft 1245-124
+contraction gf
+always gesellschaft 1245-156
+always geworden 12346-2456
+always gibt 1245-12
+contraction gb
+always gleich 1245-1456
+always glück 1245-46
+always groß 1245-2346
+contraction gß
+always größ 5-1245-2346
+always grund 1245-145
+contraction gd
+always gründ 5-1245-145
+always gründonners 1245-1235-1256-1345-145-135-1345-1345-12456-234 gründonnerstag shouldn't use the gründ-contraction
+always hab 2-125
+always haft 125-124
+contraction hf
+always häft 5-125-124
+always haeft 5-125-124
+word hain =
+always hamburger 125-1-134-12-136-1235-1245-12456 shouldn't use the ge-contraction
+always hand 125-145
+always händ 5-125-145
+always haend 5-125-145
+always halten 125-25-2345-14 shouldn't use the te-contraction
+always hast 125-23456
+always hat 125-2345
+contraction ht
+word hattest 125-15-23456 shouldn't use the es-contraction
+word hattrick 125-1-2345-2345-24-46 shouldn't use the hatt-contraction
+always haupt 125-1234
+contraction hp
+always häupt 5-125-1234
+always herr 1235-1235
+contraction rr
+always hier 125-1235
+contraction hr
+always hierar 125-24-12456-356 hierarchie
+always hoff 124-124
+contraction ff
+
+word ich 3456
+sufword ihn 24-125
+always inter 35-2345-12456
+always interess 2-35
+always irgend 24-1245 irgendetwas
+contraction ig
+
+always jahr 245-1235
+contraction jr
+always jähr 5-245-1235
+always jaehr 5-245-1235
+always jahrhundert 245-125
+contraction jh
+always jahrtausend 245-2345
+contraction jt
+always jahrzehnt 245-1356
+contraction jz
+sufword jed 245-145
+word jedoch 245-1456
+sufword jetzig 245-45
+always johannisberg 245-135-125-235-1345-24-234-12-12456-1245 shouldn't use the nis-contraction
+always jung 245-256-1245 shouldn't use the ung-contraction
+
+word kannst 13-23456
+always kapital 13-1234
+contraction kp
+always kapitäl 5-13-1234
+always kapitael 5-13-1234
+always klemm 13-123-15-1346 eingeklemmt shouldn't use the em-contraction
+always knoch 13-1345-135-1456 Knochen shouldn't use the noch-contraction
+always komm 13-1346
+contraction kx
+always akkommod 1-13-13-135-1346-135-145 shouldn't use the komm-contraction
+always kömm 5-13-1346
+always konnt 13-2345
+contraction kt
+always könn 2-13
+
+always kraft 13-124
+contraction kf
+always kräft 5-13-124
+always kraeft 5-13-124
+
+always kulturell 13-136-123-2345-136-1235-15-12345 should use the ll-contraction
+
+always kurz 13-1356
+contraction kz
+always kürz 5-13-1356
+
+always lang 123-1245
+contraction lg
+before g sufword schlan 156-123-235 shouldn't use the lang-contraction
+sufword schlangen 156-123-235-1245-14
+before g always warteschlan 2456-356-236-156-123-235
+always läng 5-123-1245
+always laeng 5-123-1245
+always jahrelang 245-1235-15-123-1245 shouldn't use the el-contraction
+always jahrhundertelang 245-125-15-123-1245 shouldn't use the el-contraction
+always jahrzehntelang 245-1356-15-123-1245 shouldn't use the el-contraction
+always jahrtausendelang 245-2345-15-123-1245 shouldn't use the el-contraction
+before s always jahres 245-1235-123456
+before g always jahresta 245-1235-123456-2345-1
+always langobard 123-235-1245-135-12-356-145 shouldn't use the lang-contraction
+always lass 2-123
+always läss 5-123
+always laess 5-123
+sufword blass 12-123-1-2346
+sufword blasst 12-123-1-234-23456
+always class = shouldn't use the lass-contraction
+before s always glas =
+sufword klass 13-123-1-2346 shouldn't use the lass-contraction
+always lassist 123-1-2346-24-23456
+always laich 123-1-24-1456 shouldn't use the ich-contraction
+always lasagne = shouldn't use the sag-contraction
+always lasso 123-1-2346-135 shouldn't use the lass-contraction
+always länd =
+always laend =
+always leb 123-12
+contraction lb
+always klebeband 13-123-15-23-12-235-145 shouldn't use the leb-contraction
+always klebebänder 13-123-15-23-12-345-1345-145-12456 shouldn't use the leb- nor änder-contraction
+always leicht 123-1456
+always letzt 123-2345
+contraction lt
+always lieb 123-346-12
+
+always mann 134-1345
+contraction mn
+always männ 5-134-1345
+always maenn 5-134-1345
+word manna = shouldn't use the mann-contraction
+always mannequin 134-235-1345-15-6-12345-35 shouldn't use the mann-contraction
+always maschin 134-156
+always material 134-123
+contraction ml
+always materiell 134-12345
+word mir 134-1235
+always mittel 134-2345
+contraction mt
+always moldawien 134-135-123-145-1-2456-24-14 shouldn't use the wie-contraction
+sufword moor = shouldn't use the or-contraction
+always möchte 1456-15
+word möchten 1456-14
+always mög 2-246
+always möglich 134-456
+always musik 134-13
+contraction mk
+always muss 134-2346
+always müss 2-134
+
+word nachdem 1345-145
+always nahm 1345-134 Annahme
+contraction nm
+always natur 1345-2345
+contraction nt
+always natürlich 1345-456
+always nächst 1345-23456
+always naechst 1345-23456
+always nehm 1345-125
+contraction nh
+endword nisch 1345-24-156 shouldn't use the nis-contraction
+endword nische 1345-24-156-15 shouldn't use the nis-contraction
+endword nischen 1345-24-156-14 shouldn't use the nis-contraction
+endword nischer 1345-24-156-12456 shouldn't use the nis-contraction
+endword nisches 1345-24-156-123456 shouldn't use the nis-contraction
+always nichtig 1345-45
+always nichts 1345-234
+contraction ns
+always nichtsehend 2-1345-234-2356-14-145 shouldn't use the nichts-contraction
+always nichtschwimm 2-1345-156-2456-24-1346 shouldn't use the nichts-contraction
+always noch 1345-1456
+always nommen 1345-1346
+contraction nx
+always genommen 12346-1345-1346
+midendword augenommen 16-12346-1345-1346
+always eigenommen 146-12346-1345-1346
+always notwendig 1345-2456
+contraction nw
+begmidword normal 1345-26-134-25 shouldn't use the mal-contraction
+prfword normal 1345-26-134-1-123 shouldn't use the mal-contraction
+always anim 235-24-134 animal shouldn't use the mal-contraction
+always nur 1345-1235
+contraction nr
+always nutz 1345-1356
+contraction nz
+always nütz 5-1345-1356
+
+sufword ohne 135-15
+contraction oe
+
+always öffentlich 246-456
+
+always paar = shouldn't use the ar-contraction
+always paragraph 1234-1245
+contraction pg
+always person 1234-1345
+contraction pn
+always philosoph 1234-125
+contraction ph
+always platz 1234-1356
+always plätz 5-1234-1356
+always plaetz 5-1234-1356
+always plötzlich 1234-456
+always ploetzlich 1234-456
+always politik 1234-13
+contraction pk
+always politisch 1234-156
+always punkt 1234-2345
+contraction pt
+
+always recht 1235-2345
+contraction rt
+before s always rechts 1235-2345-234
+always regier 1235-1245
+contraction rg
+before s always regierungs 1235-1245-136-234
+always rehabilit 1235-125
+contraction rh
+always republik 1235-13
+contraction rk
+sufword rest 1235-15-23456 should use the st-contraction
+always richt 2-3456
+always rück 1235-46
+always rueck 1235-46
+
+always sag 234-1245
+contraction sg
+always saal = shouldn't use the al-contraction
+word samen 234-1-134-14 shouldn't use the sam-contraction
+always satz 234-1356
+contraction sz
+always sätz 5-234-1356
+always saetz 5-234-1356
+always schnur 156-1345-136-1235 shouldn't use the nur-contraction
+always school 234-1456-135-135-123 foreign word, sch-contraction not allowed
+always schlag 156-1245
+always schläg 5-156-1245
+always schlaeg 5-156-1245
+always schließ 156-2346
+always schreib 156-12
+always schrift 156-2345
+always schrieb 2-156
+always schwierig 156-45
+always schwillt 156-2456-24-12345-2345 shouldn't use the will-contraction
+always schwoll 156-2456-135-12345 geschwollen shouldn't use the woll-contraction
+word sehr 234-1235
+sufword versehr 36-234-1235
+sufword unversehr 256-1236-12456-234-1235
+always selbst 234-23456
+always selbständig 234-13456-12-5-23456-45 shouldn't use the selbst-contraction
+always selbstaendig 234-13456-12-5-23456-45 shouldn't use the selbst-contraction
+always setz 2-15
+sufword sesam 234-123456-1-134 shouldn't use the sam-contraction
+word sind 234-145
+contraction sd
+always gesinde 12346-234-35-145-15
+always gesindel 12346-234-35-145-13456
+always gesindes 12346-234-35-145-123456
+always gesindest 12346-234-35-145-15-23456
+
+always sitz 2-24
+always sitzbein 2-24-12-1246 shouldn't use the bei-contraction
+always solch 234-1456
+always soll 2-234
+always sondern 234-1345
+contraction sn
+always sozial 234-123
+contraction sl
+always spiel 2-346
+always sprach 234-1234
+contraction sp
+always spräch 5-234-1234
+always spraech 5-234-1234
+always sprech 2-2346
+always staat 23456-2345
+always stand 2-23456
+always standard 23456-235-145-356-145 shouldn't use the stand-contraction
+always ständ 5-23456
+always staend 5-23456
+always stell 2-13456
+always sstell 234-2-13456 shouldn't use the ss-contraction
+word stets 23456-234
+always strahier 23456-1235-1-125-346-1235 abstrahieren
+
+word taiga = shouldn't use the ig-contraction
+always täter 2345-345-2345-12456 shouldn't use the er-contraction
+always technik 2345-13
+contraction tk
+always stechnik 234-2345-13
+always technisch 2345-156
+always stechnisch 234-2345-156
+sufword test 2345-15-23456 shouldn't use the es-contraction
+sufword töricht 2345-246-1235-3456-2345 shouldn't use the richt-contraction
+sufword toericht 2345-246-1235-3456-2345 shouldn't use the richt-contraction
+always trag 2345-1245
+contraction tg
+always träg 5-2345-1245
+always train = training shouldn't use the first possible in-contraction
+always treff 2345-124
+contraction tf
+always trinitro = shouldn't use the in-contraction
+always trotz 2345-1356
+contraction tz
+always trüb = trüber shouldn't use the über-contraction
+
+always unbeirr 256-23-24-1235-1235 unbeirrt shouldn't use the bei-contraction
+sufword under 256-145-12456 shouldn't use the und-contraction
+
+word überhaupt 1256-125
+word ueberhaupt 1256-125
+always übrig 1256-45
+
+always verhältnis 1236-125
+contraction vh
+always verhaeltnis 1236-125
+always viel 1236-123
+contraction vl
+word vielleicht 1236-2345
+always volk 1236-13
+contraction vk
+always völk 5-1236-13
+word vom 1236-134
+contraction vm
+
+always wahr 2456-125
+contraction wh
+always währ 5-2456-125
+always während 345-145
+contraction äd
+always waehrend 345-145
+sufword warm 2456-356-134 shouldn't use the war-contraction
+always weg 2456-1245
+contraction wg
+always weis 2-146 Anweisung
+always zweischicht 1356-2456-146-156-3456-2345 shouldn't use the weis-contraction
+always weit 2456-2345
+before konsonant begword zweit 1356-2456-146-2345 shouldn't use the weit-contraction
+always wenig 2456-45
+sufword wenn 2456-1345
+contraction wn
+always werd 2-2456
+always wesentlich 2456-456
+always wiegend 2456-346-1245-14-145 shouldn't use the wie-contraction
+always wieder 346-145
+always wien 2456-346-1345 shouldn't use the wie-contraction
+always will 2456-12345
+always william 2456-24-12345-24-1-134
+word wir 2456-1235
+word wird 2456-145
+always wirk 2456-13
+contraction wk
+word wirst 2456-23456
+always wirtschaft 2456-156
+always wiss 2456-2346
+contraction wß
+word swiss 234-2456-24-2346 shouldn't use the wiss-contraction
+always wohl 2456-123
+contraction wl
+always woll 2-135
+word Wolle 2456-135-12345-15 shouldn't use the woll-contraction
+always wollfad 2456-135-12345-124-1-145 shouldn't use the woll-contraction
+always baumwoll 12-16-134-2456-135-12345 shouldn't use the woll-contraction
+word worden 135-14
+sufword wurd 136
+sufword würd 1256
+
+always young 6-13456-135-136-1345-1245 shouldn't use the u-contraction
+
+always zahl 1356-123
+contraction zl
+always zähl 5-1356-123
+always zeit 1356-2345
+contraction zt
+before st midendword zug =
+word zum 1356-134
+word zunächst 1356-1345
+word zunaechst 1356-1345
+word zur 1356-1235
+sufword zurschau 1356-1235-156-16
+sufword zurück 1356-46
+sufword zurueck 1356-46
+always zusammen 1356-234
+always zwischen 1356-2456
+contraction zw
+
+# exceptions
+always aachen 1-1-1456-14 shouldn't use the ach-contraction
+always abbauf 1-12-12-16-124 Abbaufortschritt, Abbaufront
+sufword abend 1-12-14-145
+always abenteuer 1-12-14-2345-126-12456
+always abenteurer 1-12-14-2345-126-1235-12456
+always aberkann 1-12-12456-13-235-1345 shouldn't use the aber-contraction
+always aberkenn 1-12-12456-13-14-1345 shouldn't use the aber-contraction
+begword abernt 1-12-12456-1345-2345 shouldn't use the aber-contraction
+always abgaben 1-12-1245-1-12-14 shouldn't use the be-contraction
+begword ausgaben 34-1245-1-12-14 shouldn't use the be-contraction
+sufword ablageraum 1-12-123-1-12346-1235-16-134
+always ablageräum 1-12-123-1-12346-1235-34-134
+always ablageraeum 1-12-123-1-12346-1235-34-134
+always ablauf 1-12-123-16-124 shouldn't use the auf-contraction
+always ablösesumm 1-12-123-246-234-15-234-136-1346 shouldn't use the es-contraction
+always abloesesumm 1-12-123-246-234-15-234-136-1346 shouldn't use the es-contraction
+always abnormität 1-12-1345-26-134-5-345 shouldn't use the mit-contraction
+always abnormitaet 1-12-1345-26-134-5-345 shouldn't use the mit-contraction
+always abrund 1-12-1235-256-145 shouldn't use the und-contraction
+begword abschieds 1-12-156-346-145-234 Abschiedsschmerz
+always achteck 1-1456-2345-15-46 shouldn't use the te-contraction
+always asocia 1-234-135-6-14-24-1 asociación shouldn't use the so-contraction
+always afrikarefer 1-124-1235-24-13-1-1235-15-124-12456 shouldn't use the ar-contraction
+sufword agent 1-1245-14-2345 shouldn't use the ge-contraction
+always akadem 1-13-1-145-12356 shouldn't use the dem-contraction
+sufword akten 1-13-2345-14 shouldn't use the te-contraction
+always akteur 1-13-2345-126-1235 shouldn't use the te-contraction
+always ingenieur 35-1245-14-24-126-1235
+always interieur 35-2345-12456-24-126-1235
+always porteur 1234-26-2345-126-1235
+always alarm 25-356-134 Alarmmeldung shouldn't use the mm-contraction
+always albern 25-12-12456-1345 shouldn't use the be-contraction
+always albert 25-12-12456-2345 shouldn't use the be-contraction
+always alexander 25-15-6-1346-235-145-12456 shouldn't use the ander-contraction
+always algerier 25-1245-12456-24-12456 shouldn't use the ie-contraction
+sufword allee 1-12345-15-15 shouldn't use the alle-contraction
+prfword alleen 1-12345-15-14 shouldn't use the alle-contraction
+always baumallee 12-16-134-1-12345-15-15 shouldn't use the mal-contraction and alle-contraction
+always baumalleen 12-16-134-1-12345-15-14 shouldn't use the mal-contraction and alle-contraction
+before st always alltags 1-12345-2345-1-1245-234
+begword alm 25-134
+begword alter 25-2345-12456
+always amateur 1-134-1-2345-126-1235 should use the eu-contraction
+before r always amerika 1-134-12456-24-13-1
+always amtschines 1-134-2345-234-1456-35-123456 shouldn't use the sch-contraction
+always amtsstub 1-134-2345-234-23456-136-12 shouldn't use the ss-contraction
+always andalusier 235-145-25-136-234-24-12456 shouldn't use the ie-contraction
+midendword anebel 1-1345-15-12-13456 Andromedanebel shouldn't use the an-contraction
+before st always anfangs 235-124-235-1245-234
+before st always angriffs 235-1245-1235-24-124-124-234
+always anklage 235-13-123-1-12346 Anklagerede shouldn't use the er-contraction
+always anklang 235-13-123-235-1245 shouldn't use the lang-contraction
+always ankläng 235-13-123-345-1345-1245 shouldn't use the läng-contraction
+begword anlagen 235-123-1-1245-14
+always anlauf 235-123-16-124 shouldn't use the auf-contraction
+before m always anleihe 235-123-146-125-15 Anleihemarkt shouldn't use the em-contraction
+before m always film =
+class egn egn
+before egn begword anti 235-2345-24
+always antichrist 235-2345-24-1456-1235-24-23456 shouldn't use the ich-contraction
+always anästh 235-345-234-2345-125 Anästhesie
+begword armee 356-134-15-15
+word armeen 356-134-15-14
+
+always augen 16-1245-14 shouldn't use the ge-contraction
+midendword austausch 16-234-2345-16-156 shouldn't use the st-contraction
+always beilstein 12-146-123-23456-1246 shouldn't use the bei-contraction
+word bein 12-1246 shouldn't use the bei-contraction
+word beine 12-1246-15 shouldn't use the bei-contraction
+word beinen 12-1246-14 shouldn't use the bei-contraction
+word beines 12-1246-123456 shouldn't use the bei-contraction
+always bauform 12-16-124-26-134 shouldn't use the auf-contraction
+always beteuer 23-2345-126-12456 shouldn't use the te-contraction
+always beute 12-126-236 should use the eu-contraction
+always beutel 12-126-2345-13456 should use the eu-contraction
+always bssy 12-234-234-6-13456 shouldn't use the ss-contraction
+always bundes 12-256-145-123456 shouldn't use the und-contraction
+prfword chemikalie 1456-12356-24-13-25-24-15
+always donnerstag 145-135-1345-1345-12456-234-2345-1-1245 shouldn't use the st-contraction
+before g always samstag =
+endword stag = shouldn't use the st-contraction
+endword stage 234-2345-1-12346
+endword stages 234-2345-1-1245-123456
+sufword eheinstitut 15-125-15-35-23456-24-2345-136-2345
+begword eheleu 15-125-15-123-126
+before g begword eherin 15-125-15-1235-35 Ehering(e) shouldn't use the er-contraction
+midword ehilfs = Analysehilfsmittel shouldn't use the eh-contraction
+always einter 15-35-2345-12456 shouldn't use the ein-contraction
+midendword emethod = Analysemethode shouldn't use the eh-contraction
+midendword enorm 15-1345-26-134 Aussprachenorm shouldn't use the en-contraction 
+always emuskel 15-134-136-234-13-13456 shouldn't use the em-contraction
+always eschatolog 123456-1456-1-2345-135-123-135-1245 shouldn't use the sch-contraction
+always esther 123456-2345-125-12456 shouldn't use the st-contraction
+always ästhe 345-234-2345-125-15 ästhetisch
+midendword erecht 15-1235-2345
+midendword ericht 15-2-3456
+always erepublik 15-1235-13
+midendword emann 15-134-1345
+midendword emädchen 15-134-345-145-1456-14
+midendword emaedchen 15-134-345-145-1456-14
+midendword emänn 15-5-134-1345
+midendword emaenn 15-5-134-1345
+midendword estand 15-2-23456
+midword estell 15-2-13456
+always found 124-135-256-145 Foundation shouldn't use the und-contraction
+begword gänse =
+word gänsen 1245-345-1345-234-14
+sufword gänserich 1245-345-1345-234-12456-3456
+always geben 12346-12-14
+always gebunden 12346-12-256-145-14 shouldn't use the und-contraction
+always geier 1245-146-12456 should use the ei-contraction
+always geil 1245-146-123 shouldn't use the ge-contraction
+midendword geingang 1245-1246-1245-235-1245
+midendword geingän 1245-1246-1245-345-1345
+always gelungen 12346-123-256-1245-14
+always generat 1245-14-12456-1-2345
+always generier 1245-14-12456-346-1235
+always gerät 12346-1235-345-2345 should use the ge-contraction
+always geraet 12346-1235-345-2345 should use the ge-contraction
+always geräusch 12346-1235-34-156 should use the ge-contraction
+always geraeusch 12346-1235-34-156 should use the ge-contraction
+always gerecht 12346-1235-2345
+always gericht 12346-2-3456
+begword german 1245-12456-134-235 shouldn't use the ge-contraction
+always gewiesen 12346-2456-346-234-14 shouldn't use the wie-contraction
+prfword hallen 125-1-12345-14 shouldn't use the allen-contraction
+always hauf 125-16-124 shouldn't use the auf-contraction
+always hotel 125-135-2345-13456 shouldn't use the te-contraction
+always installer 35-23456-1-12345-12456 shouldn't use the aller-contraction
+always internet 35-2345-12456-1345-15-2345 shouldn't use the te-contraction
+always interview 35-2345-12456-1236-24-15-2456 shouldn't use the ie-contraction
+after st always ionstrieb 245-234-2345-1235-346-12
+after st always ionstrupp 245-234-2345-1235-136-1234-1234
+always jubel 245-136-12-13456 shouldn't use the be-contraction
+always kaffee =
+endword kaffees 13-1-124-124-15-123456
+always komponist 13-135-134-1234-135-1345-24-23456 shouldn't use the nis-contraction
+always leselamp 123-123456-15-123-1-134-1234 shouldn't use the el-contraction
+always liechtenstein 123-346-1456-2345-14-23456-1246 shouldn't use the te-contraction
+prfword linie 123-35-24-15 shouldn't use the ie-contraction
+always richtlini 2-3456-123-35-24
+prfword materie 134-1-2345-12456-24-15 shouldn't use the ie-contraction
+always metallen 134-15-2345-1-12345-14 shouldn't use the allen-contraction
+always regel 1235-15-1245-13456
+always release = shouldn't use the el-contraction
+always roboter 1235-135-12-135-2345-12456 shouldn't use the te-contraction
+always round 1235-135-256-145 shouldn't use the und-contraction
+sufword rund 1235-256-145 shouldn't use the und-contraction
+word räson = shouldn't use the so-contraction
+always döschen 145-246-234-1456-14 shouldn't use the sch-contraction
+always füsschen 124-1256-2346-1456-14
+always häschen 125-345-234-1456-14 shouldn't use the sch-contraction
+always höschen 125-246-234-1456-14 should use the ch-contraction
+always wollhöschen 2456-135-12345-125-246-234-1456-14 shouldn't use the woll-contraction
+always küsschen 13-1256-2346-1456-14 shouldn't use the sch-contraction
+always möschen 134-246-234-1456-14 should use the ch-contraction
+word röschen 1235-246-234-1456-14 should use the ch-contraction
+begword rosaro = shouldn't use the ar-contraction
+midendword sammel 234-1-1346-13456 shouldn't use the sam-contraction
+midendword samml 234-1-1346-123 shouldn't use the sam-contraction
+endword schef 234-1456-15-124
+endword schefin 234-1456-15-124-35
+endword schefins 234-1456-15-124-35-234
+endword schefinnen 234-1456-15-124-35-1345-14
+endword schefs 234-1456-15-124-234
+endword eschef 123456-1456-15-124
+endword eschefin 123456-1456-15-124-35
+endword eschefins 123456-1456-15-124-35-234
+endword eschefinnen 123456-1456-15-124-35-1345-14
+endword eschefs 123456-1456-15-124-234
+always schueler 156-1256-123-12456 should use the ue-symbol
+always schwung 156-2456-256-1245 shouldn't use the ung-contraction
+always september 234-15-1234-2345-12356-12-12456 should use the em-contraction
+always dezember 145-15-1356-12356-12-12456 shouldn't use the be-contraction
+before s always sichts 234-3456-2345-234 Ansichtssache shouldn't use the ss-contraction
+always silber 234-24-123-12-12456 shouldn't use the be-contraction
+always sprung 234-1234-1235-256-1245 shouldn't use the ung-contraction
+always ssitz 234-2-24 shouldn't use the ss-contraction
+always ssonntag =
+always sspiel 234-2-346
+always ssprach 234-234-1234
+always sstand 234-2-23456
+midendword stitel 234-2345-24-2345-13456
+always studien 23456-136-145-24-14 shouldn't use the ie-contraction
+always stunde 23456-256-145-15 shouldn't use the und-contraction
+always stunden 23456-256-145-14
+always esystem 15-234-6-13456-23456-12356 shouldn't use the es-contraction
+always sturz 23456-136-1235-1356 Absturzursache shouldn't use the zu-contraction
+always tagesstät 2345-1-1245-123456-23456-345-2345 shouldn't use the ss-contraction
+always tagesstaet 2345-1-1245-123456-23456-345-2345 shouldn't use the ss-contraction
+always team = shouldn't use the te-contraction
+midendword termin 2345-12456-134-35 shouldn't use the te-contraction
+always wagen 2456-1-1245-14 shouldn't use the ge-contraction
+always weiber 2456-146-12-12456 shouldn't use the be-contraction
+sufword verbund 36-12-256-145 shouldn't use the und-contraction
+midendword verbund 1236-12456-12-256-145 shouldn't use the und-contraction
+midword versamm 1236-12456-234-1-1346 shouldn't use the sam-contraction
+always viertel 1236-346-1235-2345-13456 shouldn't use the te-contraction
+before g always zeitsta 1356-2345-234-2345-1
+begword zion = zionist shouldn't use the nis-contraction
+always zugantenn 1356-136-1245-235-2345-14-1345
+always zweiseit 1356-2456-146-234-146-2345
+always zweistaat 1356-2456-146-23456-2345 shouldn't use the weis-contraction
+
+midendword heitstätig 125-234-2345-345-2345-45 gelegenheitstätigkeit shouldn't use the st-contraction
+begmidword admiral 1-145-134-24-1235-25
+begmidword astral 1-23456-1235-25
+before m sufword atom =
+begmidword bifokal 12-24-124-135-13-25
+#begmidword brachial 12-1235-56-24-25
+begmidword bronchial 12-1235-135-1345-1456-24-25 bronchiallymphknoten shouldn't use the ll-contraction
+begword general 1245-14-12456-25
+begmidword kolonial 13-135-123-135-1345-24-25
+begmidword zentral 1356-14-2345-1235-25 zentrallager
+always schall 156-1-12345 schallen shouldn't use the allen-contraction
+always herzultra 125-12456-1356-136-123-2345-1235-1 shouldn't use the zu-contraction
+always mittagessen 134-24-2345-2345-1-1245-15-2346-14
+always snakeskin 234-1345-1-13-15-234-13-35
+
+# countries
+always dänemark 145-345-1345-15-134-356-13 shouldn't use the em-contraction
+always daenemark 145-345-1345-15-134-356-13 shouldn't use the em-contraction
+
+# names
+word angela 235-1245-13456-1 shouldn't use the ge-contraction
+word angeles 235-1245-13456-123456 shouldn't use the ge-contraction
+word angelika 235-1245-13456-24-13-1 shouldn't use the ge-contraction
+word angelina 235-1245-13456-35-1 shouldn't use the ge-contraction
+word angelo 235-1245-13456-135 shouldn't use the ge-contraction
+always daniel 145-235-24-13456 shouldn't use the ie-contraction
+always gerlind 1245-12456-123-35-145 should use the er-contraction
+always solveig 234-135-123-1235-146-1245 shouldn't use the so-contraction
+
+# TODO:
+# 8-Punkte-Plan
+
+# inline contraction of emoji descriptions
+emoji de
diff --git a/Tables/Contraction/de-2015.ctb b/Tables/Contraction/de-2015.ctb
new file mode 100644
index 0000000..4cbecf7
--- /dev/null
+++ b/Tables/Contraction/de-2015.ctb
@@ -0,0 +1,1529 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German (contracted - 2015 standard)
+# Created by Mario Lang <mlang@delysid.org>.
+
+include de-g1.ctb
+
+class e e
+class g g
+class h h
+class i i
+class m m
+class n n
+class r r
+class hilmnrsu hilmnrsu
+class l l
+class lnr lnr
+class st st
+class konsonant bcdfghjklmnpqrstvwxyz
+
+include de-wort.cti
+before e always auslöse 16-234-123-246-234-15
+before e always ausreise 16-234-1235-146-234-15
+before hilmnrsu always ausnahme 16-234-1345-134-15
+prfword ausnahmen 16-234-1345-134-14
+before hilmnrsu always aussage 16-234-234-1245-15
+prfword aussagen 16-234-234-1245-14
+before konsonant always aussagen 16-234-234-1245-14
+prfword aussagend 16-234-234-1245-14-145
+prfword aussagende 16-234-234-1245-14-145-15
+prfword aussagendem 16-234-234-1245-14-145-12356
+prfword aussagenden 16-234-234-1245-14-145-14
+prfword aussagender 16-234-234-1245-14-145-12456
+prfword aussagendes 16-234-234-1245-14-145-123456
+always aussagenklass 16-234-234-1245-14-13-123-1-2346
+before g always aussagenklän 16-234-234-1245-14-13-123-345-1345
+
+
+before s always aus 16-234
+
+# Ausnahmen für Vokalgruppen aus de-g1.ctb
+before e always arteri 356-2345-12456-24
+always barrier 12-356-1235-24-12456 shouldn't use the ie-contraction
+always bankier 12-235-13-24-12456 shouldn't use the ie-contraction
+midendword iell 24-15-12345 shouldn't use the ie-contraction
+always indien 35-145-24-14 shouldn't use the ie-contraction
+always karrier 13-356-1235-24-12456
+always medien 134-15-145-24-14 shouldn't use the ie-contraction
+always propriet 12345-1234-1235-24-15-2345 proprietär shouldn't use the ie-contraction
+endword serie 234-12456-24-15
+always spezies 234-1234-15-1356-24-123456 shouldn't use the ie-contraction
+always tragödie =
+always tragödien 2345-1235-1-1245-246-145-24-14
+
+# Due to their low usage frequency in typical german text the letters
+# c, q, x and y are reused for the en-, ll-, ex/mm/nis- and el-contractions.
+# Therefore they need a proceding letsign to disambiguate them.
+always c 6-14
+always C 6-14
+always q 6-12345
+always Q 6-12345
+always x 6-1346
+always X 6-1346
+always y 6-13456
+always Y 6-13456
+
+# Lautgruppenkürzungen
+midendword ach 56
+begmidword al 25
+begmidword : 6-25
+begmidword an 235
+begmidword ar 356
+begmidword be 23
+midendword beule 12-126-123-15
+midendword beulen 12-126-123-14
+before g always beu 12-126
+always bell 23-12345 tabelle should use the ll-contraction
+midendword ck 46
+midword eh 2356
+always kohle =
+always kohlen 13-135-125-123-14
+always kohleintopf 13-135-125-123-1246-2345-135-1234-124
+always kohleintöpf 13-135-125-123-1246-2345-246-1234-124
+before g always kohleinla 13-135-125-123-1246-123-1
+before e always kohleul 13-135-125-123-126-123 ⠨⠅⠕⠓⠇⠣⠇⠉
+always ein 1246
+always einnen 15-35-1345-14 ⠨⠯⠓⠌⠎⠑⠔⠝⠉⠎⠩⠦ ⠨⠯⠓⠌⠎⠑⠔⠝⠉⠞⠷⠏⠻⠁⠞⠥⠗ ⠨⠧⠊⠵⠑⠔⠝⠉⠍⠔⠊⠾⠻
+always lateinisch 123-1-2345-146-1345-24-156 shouldn't use the ein-contraction
+always el 13456
+begmidword elineal 15-123-35-15-25
+endword elineal 15-123-35-15-1-123
+always eleist 15-123-146-23456
+always em 12356
+always emach 15-134-56
+before e always emachs 12356-56-234 Systemachse
+always en 14
+always denunz 145-15-1345-256-1356 shouldn't use the en-contraction
+always er 12456
+always dereferenzier 145-15-1235-15-124-12456-14-1356-346-1235 shouldn't use the er-contraction
+always deregulier 145-15-1235-15-1245-136-123-346-1235 shouldn't use the er-contraction
+always es 123456
+always ge 12346
+prfword gehe 1245-2356-15
+prfword gehen 1245-2356-14
+prfword gehend 1245-2356-14-145
+prfword gehende 1245-2356-14-145-15
+prfword gehendem 1245-2356-14-145-12356
+prfword gehenden 1245-2356-14-145-14
+prfword gehender 1245-2356-14-145-12456
+prfword gehendes 1245-2356-14-145-123456
+before konsonant always geh 1245-2356
+always umgehung 136-134-1245-2356-136 shouldn't use the ge-contraction
+before konsonant always bel 12-13456 Übelkeit shouldn't use the be-contraction
+word bel 12-13456
+before konsonant always bem 12-12356
+before konsonant always ben 12-14
+before konsonant always ber 12-12456
+before konsonant always gel 1245-13456 klingelton shouldn't use the ge-contraction
+before konsonant always gen 1245-14
+begword lungen 123-256-1245-14 shouldn't use the ge-contraction
+begword magen 134-1-1245-14 shouldn't use the ge-contraction
+before konsonant always ten 2345-14
+always elefant 13456-15-124-235-2345
+before konsonant always ter 2345-12456
+always güter 1245-1256-2345-12456 should use the er-contraction
+always güteregel 1245-1256-236-1235-15-1245-13456
+always gütericht 1245-1256-236-2-3456
+midendword ich 3456
+midendword ig 45
+always in 35
+
+midendword lich 456
+always lichtbogen 123-3456-2345-12-135-1245-14 shouldn't use the lich-contraction
+always lichtnahr 123-3456-2345-1345-1-125-1235 shouldn't use the lich-contraction
+always lichtsch 123-3456-2345-156
+always bodenlicht 12-135-145-14-123-3456-2345 shouldn't use the lich-contraction
+begword see =
+begword neusee 1345-126-234-15-15
+#before konsonant always seen 234-15-14
+word seele 234-15-13456-15
+sufword seelen 234-15-13456-14
+always seelisch 234-15-13456-24-156
+word teer 2345-15-12456
+sufword teerartig 2345-15-12456-356-2345-45
+before konsonant begword teer 2345-15-12456
+word teere 2345-15-12456-15
+word teeren 2345-15-12456-14
+word teerend 2345-15-12456-14-145
+word teerende 2345-15-12456-14-145-15
+word teerendem 2345-15-12456-14-145-12356
+word teerenden 2345-15-12456-14-145-14
+word teerender 2345-15-12456-14-145-12456
+word teerendes 2345-15-12456-14-145-123456
+word teerung 2345-15-12456-136
+word teerungen 2345-15-12456-136-14
+always teelich 2345-15-15-456 shouldn't use the el-contraction
+always teelöffel 2345-15-15-123-246-124-124-13456 shouldn't use the el-contraction
+always teemaschin 2345-15-15-134-156 shouldn't use the em-contraction
+always teesieb 2345-15-15-234-346-12 shouldn't use the es-contraction
+midendword ll 12345
+always holland 125-135-123-123-235-145 shouldn't use the ll-contraction
+always holländer 125-135-123-123-345-1345-145-12456 shouldn't use the ll-contraction
+always hollaender 125-135-123-123-345-1345-145-12456 shouldn't use the ll-contraction
+midendword mm 1346
+always wurm = wurmmittel shouldn't use the mm-contraction
+begmidword or 26
+
+always schaos 234-1456-1-135-234
+after konsonant midword ss =
+after konsonant midendword ssatz 234-234-1356
+after konsonant midendword ssätz 234-5-234-1356
+after konsonant midendword ssaetz 234-5-234-1356
+after konsonant midword ssch 234-156
+always sschicht 234-156-3456-2345
+always sschlag 234-156-1245
+always sschläg 234-5-156-1245
+always sschlaeg 234-5-156-1245
+always sschließ 234-156-2346
+always sschmuck 234-156-134-136-46
+always sschnur 234-156-1345-136-1235 shouldn't use the nur-contraction
+always sschool 234-234-1456-135-135-123 foreign word, sch-contraction not allowed
+always sschreib 234-156-12
+always sschrieb 234-2-156
+always sschrift 234-156-2345
+always sschwierig 234-156-45
+always sselbst 234-234-23456
+always sselbständ 234-234-13456-12-5-23456 shouldn't use the selbst-contraction
+always sselbstaend 234-234-13456-12-5-23456 shouldn't use the selbst-contraction
+after konsonant always ssetz 234-2-15
+after konsonant midendword ssesam 234-234-123456-1-134 shouldn't use the sam-contraction
+always ssicher 234-234-3456-12456
+after konsonant always ssitz 234-2-24
+always ssolch 234-234-1456
+after konsonant midendword ssoll 234-2-234
+always ssondern 234-234-1345
+always ssozial 234-234-123
+always espiel 15-2-346
+always sspiel 234-2-346
+always esprach 15-234-1234
+always ssprach 234-234-1234
+always sspräch 234-5-234-1234
+always sspraech 234-5-234-1234
+always esprech 15-2-2346
+always ssprech 234-2-2346
+after konsonant midendword sst 234-23456
+always sstaat 234-23456-2345
+after konsonant midendword sstand 234-2-23456
+after konsonant midendword sständ 234-5-23456
+after konsonant midendword sstaend 234-5-23456
+after konsonant midendword sstell 234-2-13456
+always ssteiger 234-23456-146-1245-12456
+before e always sstund 234-23456-256-145 shouldn't use the und-contraction
+midendword ss 2346
+
+midendword te 236
+sufword atem 1-2345-12356 shouldn't use the te-contraction
+always un 256
+
+midendword tei 2345-146
+
+always all 1-12345
+always ell 15-12345
+always emm 15-1346
+always esch 15-156
+always ess 15-2346
+always est 15-23456
+
+endword tel 2345-13456
+always scheitel 156-146-2345-13456 shouldn't use the te-contraction
+begword bettel 12-15-2345-2345-13456 shouldn't use the te-contraction
+endword teln 2345-13456-1345
+endword tels 2345-13456-234
+endword tem 2345-12356
+endword tene 2345-14-15
+always tten 2345-2345-14 shouldn't use the te-contraction
+always tter 2345-2345-12456 shouldn't use the te-contraction
+always daten 145-1-2345-14 
+always karten 13-356-2345-14
+midendword maten 134-1-2345-14 shouldn't use the te-contraction
+endword tenem 2345-14-12356
+endword tenen 2345-14-14
+endword tener 2345-14-12456
+endword benes 12-14-123456
+endword tenes 2345-14-123456
+endword tens 2345-14-234
+endword ter 2345-12456
+endword tere 2345-12456-15
+endword terem 2345-12456-12356
+endword teren 2345-12456-14
+endword teres 2345-12456-123456
+endword ters 2345-12456-234
+always liter 123-24-2345-12456
+endword tern 2345-12456-1345
+endword tes 2345-123456
+
+endword bel 12-13456 shouldn't use the be-contraction
+endword beln 12-13456-1345 shouldn't use the be-contraction
+endword belns 12-13456-1345-234 shouldn't use the be-contraction
+endword bels 12-13456-234 shouldn't use the be-contraction
+midword belläng 12-13456-5-123-1245 kabellänge shouldn't use the ll-contraction
+midword bellaeng 12-13456-5-123-1245
+midendword belung 12-13456-136
+before st midendword belungs 12-13456-136-234
+always belveder 12-13456-1236-15-145-12456 shouldn't use the be-contraction
+always wirbel 2456-24-1235-12-13456 shouldn't use the be-contraction
+endword ben 12-14 shouldn't use the be-contraction
+endword bend 12-14-145 shouldn't use the be-contraction
+endword bende 12-14-145-15 shouldn't use the be-contraction
+endword bendem 12-14-145-12356 shouldn't use the be-contraction
+endword benden 12-14-145-14 shouldn't use the be-contraction
+endword bender 12-14-145-12456 shouldn't use the be-contraction
+endword bendes 12-14-145-123456 shouldn't use the be-contraction
+endword benem 12-14-12356 shouldn't use the be-contraction
+endword benen 12-14-14 shouldn't use the be-contraction
+endword bens 12-14-234 shouldn't use the be-contraction
+endword ber 12-12456 shouldn't use the be-contraction
+endword berei 12-12456-146 shouldn't use the be-contraction
+endword bereien 12-12456-146-14 shouldn't use the be-contraction
+endword berin 12-12456-35 shouldn't use the be-contraction
+endword berinnen 12-12456-35-1345-14 shouldn't use the be-contraction
+endword bern 12-12456-1345 shouldn't use the be-contraction
+endword bers 12-12456-234 shouldn't use the be-contraction
+always ober 135-12-12456 shouldn't use the be-contraction
+endword bes 12-123456 shouldn't use the be-contraction
+
+always begeh 23-1245-2356 shouldn't use the ge-contraction
+always getriebegehäus 12346-2345-1235-346-23-12346-125-34-234 shouldn't use the eh-contraction
+always getriebegehaeus 12346-2345-1235-346-23-12346-125-34-234 shouldn't use the eh-contraction
+always umgeht 136-134-1245-2356-2345 shouldn't use the ge-contraction
+always geig 1245-146-1245 should use the ei-contraction
+always geisel 1245-146-234-13456 shouldn't use the ge-contraction
+always geist 1245-146-23456
+prfword gel 1245-13456 shouldn't use the ge-contraction
+midendword gelhaft 1245-13456-125-124
+midword gelläng 1245-13456-5-123-1245
+midword gellaeng 1245-13456-5-123-1245
+prfword geln 1245-13456-1345
+prfword gelns 1245-13456-1345-234
+prfword gels 1245-13456-234
+midendword gelung 1245-13456-136
+before st midendword gelungs 1245-13456-136-234
+prfword gen 1245-14 shouldn't use the ge-contraction
+midendword gend 1245-14-145 shouldn't use the ge-contraction
+endword gens 1245-14-234 shouldn't use the ge-contraction
+midendword ger 1245-12456 should use er-contraction if not part of a word intro
+before r begword abge 1-12-12346
+before r begword ange 235-12346
+word anger 235-1245-12456
+word angern 235-1245-12456-1345
+word angers 235-1245-12456-234
+begword unange 256-235-12346
+begmidword aufger 2-16-12346-1235
+begmidword ausger 16-234-12346-1235
+before r begmidword einge 1246-12346
+begword unger 256-12346-1235
+sufword ungerecht 256-12346-1235-2345
+always gerieben 12346-1235-346-12-14 should use the ge-contraction
+sufword zuge 2-1356-12346
+endword ges 1245-123456 shouldn't use the ge-contraction
+always sieges 234-346-1245-123456
+always tageslicht 2345-1-1245-123456-123-3456-2345 shoudln't use the ge- nor lich-contraction
+always euthanasie 126-2345-125-235-1-234-346
+always antasie 235-2345-1-234-346
+endword antasien 235-2345-1-234-24-14
+midendword ien 24-14 shouldn't use the ie-contraction
+always erschien 12456-156-346-1345
+prfword industrien 35-145-136-23456-1235-24-14
+before n always industrie 35-145-136-23456-1235-346 industrienation shouldn't use the en-contraction
+begmidword anomal 235-135-134-25 shouldn't use the mal-contraction
+endword anomal 235-135-134-1-123 shouldn't use the mal-contraction
+midendword iene 346-1345-15 should use the ie-contraction
+midendword ienen 346-1345-14 should use the ie-contraction
+endword tel 2345-13456 should use the el-contraction
+endword ten 2345-14 should use the en-contraction
+endword ter 2345-12456 should use the er-contraction
+endword tes 2345-123456 should use the es-contraction
+always trigraph = shouldn't use the ig-contraction
+
+# Vorsilbenkürzungen
+begword ent 2346
+word enter 14-2345-12456 shouldn't use the ent-contraction
+begword ex 1346
+begword pro 12345
+begword ver 36
+word verb 1236-12456-12
+word vers 1236-12456-234
+
+#begword auspiz 16-234-1234-24-1356 shouldn't use the aus-contraction
+word ente 14-236 shouldn't use the ent-contraction
+sufword enten 14-2345-14 shouldn't use the ent-contraction
+word entchen 14-2345-1456-14
+begword veranda 1236-12456-236-145-1 shouldn't use the ver-contraction
+begword vertikal 1236-12456-2345-24-13-25 shouldn't use the ver-contraction
+word vertikal 1236-12456-2345-24-13-1-123 shouldn't use the ver-contraction
+
+# Nachsilbenkürzungen
+endword falls 124
+midendword heit 125
+before s midword heits 125-234
+midendword keit 13
+before s midword keits 13-234
+endword mal 134
+sufword gemal 12346-134-25 gemalt shouldn't use the mal-contraction
+midendword nis 1346
+always nisier 1345-24-234-346-1235 technisierung shouldn't use -nis
+midendword sam 2346
+sufword bisam =
+always bischofsamt 12-24-156-135-124-234-1-134-2345 shouldn't use the sam-contraction
+midendword schaft 156
+midendword ung 136
+always dschungel 6-145-156-256-1245-13456 shouldn't use the ung-contraction
+midendword terung 2345-12456-136
+before cst midword ungs 136-234 Bindungscharakter, Regierungschef
+
+endword wärts 2456
+
+midendword ation 5-1345
+always industrienation 35-145-136-23456-1235-346-1345-5-1345
+before s midword ations 5-1345-234
+midendword ativ 5-1236
+#always religion 1235-13456-45-245
+endword ismus 5-24
+midendword istisch 5-156
+endword nismus 1345-5-24 shouldn't use the nis-contraction
+midendword nistisch 1345-5-156
+midendword ität 5-345
+midendword itaet 5-345
+before s midword itäts 5-345-234
+before s midword itaets 5-345-234
+midendword mität 134-5-345 shouldn't use the mit-contraction
+
+sufword anis 235-24-234 shouldn't use the nis-contraction
+before st midword ations 5-1345-234
+sufword barschaft 12-356-156-1-124-2345 shouldn't use the shaft-contraction
+always blumensamen 12-123-136-134-14-234-1-134-14 shouldn't use the sam-contraction
+prfword dezimal = if we use the mal-contraction here, we get a new word dezim
+begmidword dezimal 145-15-1356-24-134-25
+sufword small 234-134-1-12345 shouldn't use the mal-contraction
+word beaufort 12-15-16-124-26-2345 shouldn't use the auf-contraction
+sufword erheiter 12456-125-146-2345-12456 shouldn't use the heit-contraction
+prfword formal 124-26-134-1-123 shouldn't use the mal-contraction
+begword formal 124-26-134-25 shouldn't use the mal-contraction
+word firnis = shouldn't use the nis-contraction
+begword gesam 12346-234-1-134 shouldn't use the sam-contraction
+always herrschaft 1235-1235-156-1-124-2345 can not use the shaft-contraction
+always hoheit 125-135-125-146-2345 shouldn't use the heit-contraction
+always kuhdung 13-136-125-145-256-1245 shouldn't use the ung-contraction
+always hunger 125-256-1245-12456 shouldn't use the ung-contraction
+before g begword lun 123-256 shouldn't use the ung-contraction
+prfword maximal = shouldn't use the mal-contraction
+begmidword maximal 134-1-6-1346-24-134-25 shouldn't use the mal-contraction
+always tennis 2345-14-1345-24-234 shouldn't use the nis-contraction
+midendword ungscharakter 136-234-1456-13
+before st midendword ungs 136-234 shouldn't use the ss/st-contraction
+midendword zung 1356-136 auseinandersetzung shouldn't use the zu-contraction
+begword zung 1356-256-1245 shouldn't use the ung-contraction
+
+# Einformige Kürzungen, nur alleinstehend
+word als 146
+word auch 34
+word eu =
+word das 145
+word dass 2346
+word den 15
+word der 1235
+word des 3
+word die 346
+word ihm 236
+word im 36 not allowed when used in hyphenated words like Hans-im-Glück-Gefühl
+after letter literal -im-
+word ist 23456
+word kann 13
+word lässt 123
+word laesst 123
+word man 134
+word oder 135
+word schon 156
+word sich 14
+word sie 234
+word was 2456
+
+# Einformige Kürzungen, alleinstehend oder in Wortverbindungen
+word aber 1
+always aber 2-1
+always aberdeen 1-12-12456-145-15-15-1345
+midword aberech 1-23-1235-15-1456
+midendword abereich 1-23-1235-146-1456
+midendword abericht 1-23-2-3456
+begmidword aberkann 1-12-12456-13-235-1345
+always aberkenn 1-12-12456-13-14-1345
+begmidword abernt 1-12-12456-1345-2345
+prfword abernte 1-12-12456-1345-236
+prfword aberntest 1-12-12456-1345-236-23456
+prfword aberntet 1-12-12456-1345-236-2345
+prfword aberntete 1-12-12456-1345-236-236
+prfword abernteten 1-12-12456-1345-236-2345-14
+prfword aberntetest 1-12-12456-1345-236-236-23456
+prfword aberntetet 1-12-12456-1345-236-236-2345
+always aberrans 1-12-12456-1235-235-234
+always aberratio 1-12-12456-1235-1-2345-24-135
+always aberration 1-12-12456-1235-5-1345
+always aberrier 1-12-12456-1235-346-1235
+always aberzieh 1-12-12456-1356-346-125
+always aaberg 1-1-12-12456-1245
+always cabernet 6-14-1-12-12456-1345-15-2345
+sufword faber 124-1-12-12456
+midword gaber 1245-1-23-1235
+always gaberecht 1245-1-23-1235-2345
+always gaberegel 1245-1-23-1235-15-1245-13456
+sufword haber 125-1-12-12456
+always pharmaberat 1234-125-356-134-1-23-1235-1-2345
+always schaber 156-1-12-12456
+always eisschaber 146-234-156-1-12-12456
+always makaber 134-1-13-1-12-12456
+always kandelaber 13-235-145-13456-1-12-12456
+always laber 123-1-12-12456
+always annaberg 235-1345-1-12-12456-1245
+always araber 356-1-12-12456
+always graber 1245-1235-1-12-12456
+always traber 2345-1235-1-12-12456
+begmidword tabern 2345-1-12-12456-1345
+always waber 2456-1-12-12456
+always bergzabern 12-12456-1245-1356-1-12-12456-1345
+
+word auf 16
+always auf 2-16
+before g always aufwie 2-16-2456-346 aufwiegeln/aufwiegler/aufwiegst shouldn't use the wie-contraction
+before s always aufwie 2-16-2456-346
+begword aufzuck 2-16-1356-136-46
+before konsonant always aufzug 2-16-1356-136-1245
+always aufzugsturm 2-16-1356-136-1245-234-2345-136-1235-134 shouldn't use zu-contraction and st-contraction
+before m always aufzugstür 2-16-1356-136-1245-234-2345-1256-1235 shouldn't use zu-contraction and st-contraction
+always aufzugstür 2-16-1356-136-1245-234-2345-1256-1235 shouldn't use zu-contraction and st-contraction
+prfword lauf 123-16-124
+prfword laufe 123-16-124-15
+prfword laufen 123-16-124-14
+prfword laufend 123-16-124-14-145
+prfword laufende 123-16-124-14-145-15
+prfword laufendem 123-16-124-14-145-12356
+prfword laufenden 123-16-124-14-145-14
+prfword laufender 123-16-124-14-145-12456
+prfword laufendes 123-16-124-14-145-123456
+prfword laufens 123-16-124-14-234
+prfword laufes 123-16-124-123456
+prfword laufs 123-16-124-234
+prfword laufst 123-16-124-23456
+prfword lauft 123-16-124-2345
+always schlauf 156-123-16-124 shouldn't use the auf-contraction
+always abfragerate 1-12-124-1235-1-12346-1235-1-236 shouldn't use the er-contraction
+always abfrageraten 1-12-124-1235-1-12346-1235-1-2345-14
+always abgelauf 1-12-12346-123-16-124
+always akkulauf 1-13-13-136-123-16-124
+always auflauf 2-16-123-16-124
+always durchlauf 2-1456-123-16-124
+begword verlauf 36-123-16-124 shouldn't use the auf-contraction
+midendword verlauf 1236-12456-123-16-124 shouldn't use the auf-contraction
+
+word bei 12
+always bei 2-12
+always beizung 12-146-1356-136 shouldn't use the bei-contraction
+always beiß 12-146-6-2346 shouldn't use the bei-contraction
+endword bein 12-1246 shouldn't use the bei-contraction
+endword beine 12-1246-15 shouldn't use the bei-contraction
+endword beinen 12-1246-14 shouldn't use the bei-contraction
+endword beines 12-1246-123456 shouldn't use the bei-contraction
+endword beins 12-1246-234 shouldn't use the bei-contraction
+before t always beinhal 23-35-125-25 shouldn't use the bei-contraction
+always beinhalter 12-1246-125-25-2345-12456 should use the ei-contraction
+always darmbein 145-356-134-12-1246
+always gabeinvent 1245-1-23-35-1236-14-2345 Übergabeinventar shouldn't use the bei-contraction
+always kreuzbein 13-1235-126-1356-12-1246
+always schienbein 156-346-1345-12-1246
+always schlüsselbein 156-123-1256-2346-13456-12-1246 shouldn't use the bei-contraction
+
+word dem 12356
+always dem 2-12356
+endword dem 145-12356 shouldn't use the dem-contraction
+always außerdem 16-6-2346-12456-2-12356
+always demask = demaskiert shouldn't use the dem-contraction
+always demilit = shouldn't use the dem-contraction
+always demonstr 145-12356-135-1345-23456-1235 shouldn't use the dem-contraction
+word demo 145-12356-135 shouldn't use the dem-contraction
+word demos 145-12356-135-234 shouldn't use the dem-contraction
+
+word durch 1456
+always durch 2-1456
+sufword durchzuck 2-1456-1356-136-46 shouldn't use the zu-contraction
+word durchzug 2-1456-1356-136-1245 shouldn't use the zu-contraction
+word durchzuges 2-1456-1356-136-1245-123456 shouldn't use the zu-contraction
+sufword durchzugs 2-1456-1356-136-1245-234 shouldn't use the zu-contraction
+#begword durcheinandergerat 1456-2-1246-12346-1235-1-2345
+always zugerat 2-1356-12346-1235-1-2345 shouldn't use the er-contraction
+
+word für 124
+always für 2-124
+always fürst 124-1256-1235-23456 shouldn't use the für-contraction
+
+word gegen 1245
+always gegen 2-1245
+prfword gegenzug 2-1245-1356-136-1245
+prfword gegenzuge 2-1245-1356-136-12346
+prfword gegenzuges 2-1245-1356-136-1245-123456
+prfword gegenzugs 2-1245-1356-136-1245-234
+before g sufword gegenzun 2-1245-1356-256
+
+word gewesen 12346
+always gewesen 2-12346
+always fürsorgewesen 2-124-234-26-12346-2456-123456-14 shouldn't use the gewesen-contraction
+
+word immer 1346
+always immer 2-1346
+always immersion 24-1346-12456-234-24-135-1345 shouldn't use the immer-contraction
+always immersiv 24-1346-12456-234-24-1236
+always flimmer 124-123-24-1346-12456
+always wimmer 2456-24-1346-12456 shouldn't use the immer-contraction
+always zimmer 1356-24-1346-12456 shouldn't use the immer-contraction
+
+word jetzt 245
+always jetzt 2-245
+
+word mehr 2356
+always mehr 2-2356
+
+word mit 2345
+always mit 2-2345
+
+word nicht 1345
+word n 6-1345
+always nicht 2-1345
+always nichtzughör 2-1345-1356-136-1245-125-246-1235
+always nichtzuck 2-1345-1356-136-46
+
+word so 1234
+always so 2-1234
+endword son =
+always cursor = shouldn't use the so-contraction
+begword		absol		=
+begword		absorb		=
+begword		absorp		=
+always		adsorbier	1-145-234-26-12-346-1235
+always		aerosol		1-12456-135-234-135-123
+always		amtsober	1-134-2345-234-135-12-12456
+always		anthroposo	235-2345-125-1235-135-1234-135-234-135
+always		chromosom	1456-1235-135-134-135-234-135-134 shouldn't use the so-contraction
+always		iso		=
+always		konson		=
+begmidword	sensor		234-14-234-26
+endword		sensor		234-14-234-135-1235
+endword		sensor		234-14-234-135-1235
+always		sockel		234-135-46-13456
+word		soda		=
+always		soffizier	234-135-124-124-24-1356-346-1235
+always		soft		=
+always		soldat		=
+endword		solo		=
+always		sommer		234-135-1346-12456
+before konsonant always	son =
+always		sonogra		=
+always		sonn		=
+always		sonst		234-135-1345-23456
+always		sorientier	234-26-24-14-2345-346-1235
+before konsonant always	sor		234-26
+always		source		=
+sufword		south		=
+always		sowjet		=
+always		soziolo		=
+midword ungsopt 136-234-135-1234-2345 Abendgestaltungsoption shouldn't use the so-contraction
+
+word über 1256
+word ueber 1256
+always über 2-1256
+always überzucht 2-1256-1356-136-1456-2345
+always überzuck 2-1256-1356-136-46
+prfword überzug 2-1256-1356-136-1245
+prfword überzuges 2-1256-1356-136-1245-123456
+before konsonant always überzug 2-1256-1356-136-1245
+always überlauf 2-1256-123-16-124
+
+word und 136
+always und 2-136
+sufword gesund 12346-234-256-145
+prfword hund 125-256-145 shouldn't use the und-contraction
+always hunde 125-256-145-15 shouldn't use the und-contraction
+always hundert 125-256-145-12456-2345 shouldn't use the und-contraction
+prfword hunderte 125-256-145-12456-236 shouldn't use the und-contraction
+prfword hunden 125-256-145-14 shouldn't use the und-contraction
+prfword hundes 125-256-145-123456 shouldn't use the und-contraction
+always kund 13-256-145 shouldn't use the und-contraction
+always mund 134-256-145 shouldn't use the und-contraction
+always rundfunk 1235-256-145-124-256-13 shouldn't use the und-contraction
+sufword schrund 156-1235-256-145
+always wund 2456-256-145 shouldn't use the und-contraction
+
+word unter 256
+always unter 2-256
+sufword kunter 13-256-2345-12456 shouldn't use the unter-contraction
+sufword kunterbunter 13-256-2345-12456-12-256-2345-12456
+sufword munter 134-256-2345-12456 shouldn't use the unter-contraction
+always virus =
+
+word voll 12345
+always voll 2-12345
+prfword vollzug 2-12345-1356-136-1245
+prfword vollzuge 2-12345-1356-136-12346
+prfword vollzuges 2-12345-1356-136-1245-123456
+always vollzugs 2-12345-1356-136-1245-234
+
+word von 1236
+always von 2-1236
+
+word vor 26
+always vor 2-26
+always vorzugs 2-26-1356-136-1245-234
+always vorzugstimme 2-26-1356-136-1245-23456-24-1346-15
+always vorzugstimmen 2-26-1356-136-1245-23456-24-1346-14
+always favorit 124-1-1236-26-24-2345 shouldn't use the vor-contraction
+
+word wie 126
+always wie 2-126
+always zwiebel 1356-2456-346-12-13456 shouldn't use the wie-contraction
+
+word zu 1356
+always zu 2-1356
+always zugentlast 1356-136-1245-14-2345-123-1-23456 shouldn't use the zu- nor ge-contraction
+word indem 35-2-12356
+word trotzdem 2345-1356-2-12356
+word zudem 1356-2-12356
+always zucht 1356-136-1456-2345 shouldn't use the zu-contraction
+always zuck 1356-136-46 shouldn't use the zu-contraction
+before g always zug =
+endword zug =
+endword zuges 1356-136-1245-123456
+
+# Einformige Kürzungen, alleinstehend oder am Wortanfang
+before e sufword ihr 24
+before i sufword ihr 24
+before e sufword sein 246
+before i sufword sein 246
+word war 356
+word waren 356-14
+word warst 356-23456
+word wart 356-2345
+word war's 356-6-234
+word wär 5-356
+word wäre 5-356-15
+word wären 5-356-14
+word wärest 5-356-15-23456
+word wäret 5-356-15-2345
+word wärst 5-356-23456
+word wärt 5-356-2345
+word wär's 5-356-6-234
+
+# Einformige Kürzungen, alleinstehend, mit Endungen oder in Wortverbindungen
+always hatt 125
+always hätt 345
+always haett 345
+always welch 13456
+
+word adonis = shouldn't use the nis-contraction
+always aktuell 1-13-2345-136-15-12345 should use the ll-contraction
+always all 1-12345
+sufword alle 1-15
+always allegor 1-12345-15-1245-26 Allegorie shouldn't use the ae-contraction
+always allein 1-1246
+word allem 1-12356
+always allen 1-14
+always aller 1-12456
+always allerg 1-12345-12456-1245 Allergiker
+word alles 1-123456
+always allesamt 1-12345-15-234-1-134-2345 shouldn't use the es-contraction
+always alphabet 25-1234-125-1-12-15-2345 shouldn't use the hab-contraction
+word also 1-135
+always ander 2-12456
+always wander 2456-235-145-12456 wandern shouldn't use the ander-contraction
+sufword zander 1356-235-145-12456
+always änder 5-12456
+always abänder 1-12-5-12456
+always bänder 12-345-1345-145-12456 shouldn't use the änder-contraction
+always aender 5-12456
+always arbeit 356-12
+before s always arbeits 356-12-234
+always arben 356-12-14
+always kauf 13-16-124
+always ausst 16-234-23456 shouldn't use the ss-contraction
+always ausstell 16-234-2-13456
+always ausstand 16-234-2-23456
+always ausständ 16-234-5-23456
+
+always australi 16-23456-1235-25-24 australier shouldn't use the ie-contraction
+always lotaustralin 123-135-2345-16-23456-1235-25-35
+
+always ähnlich 345-456
+always aehnlich 345-456
+
+word balsam 12-25-234-1-134
+word been 12-15-15-1345 english word shouldn't use be- or en-contraction
+always beere 12-15-15-1235-15
+always beeren 12-15-15-1235-14
+always behr 12-2356-1235 entbehren
+begword beid 12-145 beiderseits
+always berg 12-12456-1245 shouldn't use the be-contraction
+always berge 12-12456-12346 shouldn't use the be-contraction
+always bergen 12-12456-1245-14 shouldn't use the be-contraction
+always berger 12-12456-1245-12456 shouldn't use the be-contraction
+always berges 12-12456-1245-123456 shouldn't use the be-contraction
+always besonder 23
+always besser 234-234
+contraction ss
+word beim 12-134
+contraction bm
+word bis 12-234
+sufword bisher 12-234-125-12456
+sufword bislang 12-2345-123-1245
+sufword bisweil 12-234-2456-146-123
+always bison =
+
+always bist 12-23456
+always bistum 12-24-23456-136-134 shouldn't use the bist-contraction
+always bleib 12-12
+contraction bb
+always brauch 2-34
+always bräuch 5-34
+always braeuch 5-34
+always brief 12-124
+contraction bf
+always bring 12-1245
+contraction bg
+
+always charakter 1456-13
+sufword chor 1456-135-1235 shouldn't use the or-contraction
+always comput 6-14-135-134-1234-136-2345 computer should use the er-contraction
+
+always dabei 145-12
+contraction db
+always dadurch 145-145
+contraction dd
+always dafür 145-124
+contraction df
+always dagegen 145-1245
+contraction dg
+always daher 145-125
+contraction dh
+always damit 145-134
+contraction dm
+always dank 145-13
+contraction dk
+always davon 145-1236
+contraction dv
+always dazu 145-1356
+contraction dz
+always dazubleib 145-1-2-1356-12-12 shouldn't use the dazu-contraction
+always dazumal 145-1-2-1356-134
+always deuten 145-126-2345-14 shouldn't use the te-contraction
+always deal = dealer
+word dei = Agnus Dei
+always demokrat 145-2345
+contraction dt
+word denen 15-14
+word dnister 145-1345-24-234-2345-12456 shouldn't use the nis-contraction
+word denn 145-1345
+word dennschon 145-1345-156-135-1345
+always dessen 145-2346
+always deutsch 145-156
+word diem 145-24-12356 we shouldn't use the ie-contraction here
+word diese 346-15
+word diesen 346-14
+word dieser 346-12456
+word dieses 346-123456
+sufword diesmal 346-134
+word dir 145-1235
+word doch 145-1456
+always druck 145-46
+always drück 5-145-46
+always dürf 2-145
+
+always eben 15-12-14
+word ebenso 15-135
+contraction eo
+sufword ehemal 15-125-15-134
+word ei 6-146
+always eigen 146-1245-14 Eigennutz shouldn't use the ge-contraction
+always einander 2-1246
+word en 15-1345 en passant
+always enig 14-45
+always erkenn 12456-13-14-1345
+sufword etwa 15-1
+contraction ea
+word etwas 2345-2456
+contraction tw
+
+always fahr 2-1235
+always fahrtsst 2-1235-2345-234-23456 shouldn't use the ss-contraction
+always fahrtsstell 2-1235-2345-234-2-13456
+always fähr 5-1235
+always faehr 5-1235
+always fall 124-12345
+always fäll 5-124-12345
+always faell 5-124-12345
+always fertig 124-45
+always fest 124-15-23456 should use the st-contraction
+always film =
+always folgen 124-135-123-1245-14 shouldn't use the ge-contraction
+always fragil =
+always fragment 124-1235-1-1245-134-14-2345
+always freund 124-145
+contraction fd
+always führ 124-125
+contraction fh
+always fürcht 124-1256-1235-1456-2345 shouldn't use the für-contraction
+
+always ganz 1245-1356
+contraction gz
+always gänz 5-1245-1356
+always gaenz 5-1245-1356
+always garnison 1245-356-1345-24-234-135-1345 shouldn't use the nis-contraction
+word gegend 12346-1245-14-145 shouldn't use the gegen-contraction
+always gegenüber 1245-1256
+contraction gü
+always gegenwart 1245-2456
+contraction gw
+always gegenwärt 5-1245-2456
+always gegenwaert 5-1245-2456
+always gelb 1245-13456-12 should use the el-contraction
+always geld 1245-13456-145 should use the el-contraction
+always gelegen 1245-1245
+contraction gg
+begword gelt 1245-13456-2345 gelten shouldn't use the ge-contraction
+prfword gene 1245-14-15
+prfword genem 1245-14-12356
+prfword genen 1245-14-14
+prfword gener 1245-14-12456
+prfword genes 1245-14-123456
+always geogra = Geographie shouldn't use the ge-contraction
+prfword gern 1245-12456-1345
+midendword gerlich 1245-12456-456 bürgerlich shouldn't use the ge-contraction
+always gern 1245-12456-1345 we shouldn't use the ge-contraction here
+always geschäft 1245-124
+always geschaeft 1245-124
+contraction gf
+always gesellschaft 1245-156
+always geworden 12346-2456
+always gibt 1245-12
+contraction gb
+always gleich 1245-1456
+always glück 1245-46
+always groß 1245-2346
+contraction gß
+always größ 5-1245-2346
+always grund 1245-145
+contraction gd
+always gründ 5-1245-145
+always gründonners 1245-1235-1256-1345-145-135-1345-1345-12456-234 gründonnerstag shouldn't use the gründ-contraction
+always hab 2-125
+always haft 125-124
+contraction hf
+always häft 5-125-124
+always haeft 5-125-124
+word hain =
+always hamburger 125-1-134-12-136-1235-1245-12456 shouldn't use the ge-contraction
+always hand 125-145
+always händ 5-125-145
+always haend 5-125-145
+always halten 125-25-2345-14 shouldn't use the te-contraction
+always hast 125-23456
+always hat 125-2345
+contraction ht
+word hattest 125-15-23456 shouldn't use the es-contraction
+word hattrick 125-1-2345-2345-24-46 shouldn't use the hatt-contraction
+always haupt 125-1234
+contraction hp
+always häupt 5-125-1234
+always herr 1235-1235
+contraction rr
+always hier 125-1235
+contraction hr
+always hierar 125-24-12456-356 hierarchie
+always hoff 124-124
+contraction ff
+
+word ich 3456
+sufword ihn 24-125
+always inter 35-2345-12456
+always interess 2-35
+always irgend 24-1245 irgendetwas
+contraction ig
+
+always jahr 245-1235
+contraction jr
+always jähr 5-245-1235
+always jaehr 5-245-1235
+always jahrhundert 245-125
+contraction jh
+always jahrtausend 245-2345
+contraction jt
+always jahrzehnt 245-1356
+contraction jz
+sufword jed 245-145
+word jedoch 245-1456
+sufword jetzig 245-45
+always johannisberg 245-135-125-235-1345-24-234-12-12456-1245 shouldn't use the nis-contraction
+always jung 245-256-1245 shouldn't use the ung-contraction
+
+word kannst 13-23456
+always kapital 13-1234
+contraction kp
+always klemm 13-123-15-1346 eingeklemmt shouldn't use the em-contraction
+always knoch 13-1345-135-1456 Knochen shouldn't use the noch-contraction
+always komm 13-1346
+contraction kx
+always akkommod 1-13-13-135-1346-135-145 shouldn't use the komm-contraction
+always kömm 5-13-1346
+always konnt 13-2345
+contraction kt
+always könn 2-13
+
+always kraft 13-124
+contraction kf
+always kräft 5-13-124
+always kraeft 5-13-124
+
+always kulturell 13-136-123-2345-136-1235-15-12345 should use the ll-contraction
+
+always kurz 13-1356
+contraction kz
+always kürz 5-13-1356
+
+always lang 123-1245
+contraction lg
+before g sufword schlan 156-123-235 shouldn't use the lang-contraction
+sufword schlangen 156-123-235-1245-14
+before g always warteschlan 2456-356-236-156-123-235
+always läng 5-123-1245
+always laeng 5-123-1245
+always jahrelang 245-1235-15-123-1245 shouldn't use the el-contraction
+always jahrhundertelang 245-125-15-123-1245 shouldn't use the el-contraction
+always jahrzehntelang 245-1356-15-123-1245 shouldn't use the el-contraction
+always jahrtausendelang 245-2345-15-123-1245 shouldn't use the el-contraction
+before s always jahres 245-1235-123456
+before g always jahresta 245-1235-123456-2345-1
+always langobard 123-235-1245-135-12-356-145 shouldn't use the lang-contraction
+always lass 2-123
+always läss 5-123
+always laess 5-123
+sufword blass 12-123-1-2346
+sufword blasst 12-123-1-234-23456
+always class = shouldn't use the lass-contraction
+before s always glas =
+sufword klass 13-123-1-2346 shouldn't use the lass-contraction
+always lassist 123-1-2346-24-23456
+always laich 123-1-24-1456 shouldn't use the ich-contraction
+always lasagne = shouldn't use the sag-contraction
+always lasso 123-1-2346-135 shouldn't use the lass-contraction
+always länd =
+always laend =
+always leb 123-12
+contraction lb
+always klebeband 13-123-15-23-12-235-145 shouldn't use the leb-contraction
+always klebebänder 13-123-15-23-12-345-1345-145-12456 shouldn't use the leb- nor änder-contraction
+always leicht 123-1456
+always letzt 123-2345
+contraction lt
+always lieb 123-346-12
+
+always männ 5-134-1345
+always maenn 5-134-1345
+#word manna = shouldn't use the mann-contraction
+#always mannequin 134-235-1345-15-6-12345-35 shouldn't use the mann-contraction
+always maschin 134-156
+always material 134-123
+contraction ml
+always materiell 134-12345
+word mir 134-1235
+always mittel 134-2345
+contraction mt
+always moldawien 134-135-123-145-1-2456-24-14 shouldn't use the wie-contraction
+sufword moor = shouldn't use the or-contraction
+always möchte 1456-15
+word möchten 1456-14
+always mög 2-246
+always möglich 134-456
+always musik 134-13
+contraction mk
+always muss 134-2346
+always müss 2-134
+
+word nachdem 1345-145
+always nahm 1345-134 Annahme
+contraction nm
+always natur 1345-2345
+contraction nt
+always natürlich 1345-456
+always nächst 1345-23456
+always naechst 1345-23456
+always nehm 1345-125
+contraction nh
+endword nisch 1345-24-156 shouldn't use the nis-contraction
+endword nische 1345-24-156-15 shouldn't use the nis-contraction
+endword nischen 1345-24-156-14 shouldn't use the nis-contraction
+endword nischer 1345-24-156-12456 shouldn't use the nis-contraction
+endword nisches 1345-24-156-123456 shouldn't use the nis-contraction
+always nichts 1345-234
+contraction ns
+always nichtsehend 2-1345-234-2356-14-145 shouldn't use the nichts-contraction
+always nichtschwimm 2-1345-156-2456-24-1346 shouldn't use the nichts-contraction
+always noch 1345-1456
+always nommen 1345-1346
+contraction nx
+always genommen 12346-1345-1346
+midendword augenommen 16-12346-1345-1346
+always eigenommen 146-12346-1345-1346
+always notwendig 1345-2456
+contraction nw
+begmidword normal 1345-26-134-25 shouldn't use the mal-contraction
+prfword normal 1345-26-134-1-123 shouldn't use the mal-contraction
+always anim 235-24-134 animal shouldn't use the mal-contraction
+always nur 1345-1235
+contraction nr
+always nutz 1345-1356
+contraction nz
+always nütz 5-1345-1356
+
+sufword ohne 135-15
+contraction oe
+
+always öffentlich 246-456
+
+always paar = shouldn't use the ar-contraction
+always paragraf 1234-1245
+contraction pg
+always person 1234-1345
+contraction pn
+always platz 1234-1356
+always plätz 5-1234-1356
+always plaetz 5-1234-1356
+always plötzlich 1234-456
+always ploetzlich 1234-456
+always politik 1234-13
+contraction pk
+always politisch 1234-156
+always punkt 1234-2345
+contraction pt
+
+always recht 1235-2345
+contraction rt
+before s always rechts 1235-2345-234
+always regier 1235-1245
+contraction rg
+before s always regierungs 1235-1245-136-234
+always rehabilit 1235-125
+contraction rh
+always republik 1235-13
+contraction rk
+sufword rest 1235-15-23456 should use the st-contraction
+always richt 2-3456
+always rück 1235-46
+always rueck 1235-46
+
+always sag 234-1245
+contraction sg
+always saal = shouldn't use the al-contraction
+word samen 234-1-134-14 shouldn't use the sam-contraction
+always satz 234-1356
+contraction sz
+always sätz 5-234-1356
+always saetz 5-234-1356
+always schnur 156-1345-136-1235 shouldn't use the nur-contraction
+always school 234-1456-135-135-123 foreign word, sch-contraction not allowed
+always schlag 156-1245
+always schläg 5-156-1245
+always schlaeg 5-156-1245
+always schließ 156-2346
+always schreib 156-12
+always schrift 156-2345
+always schrieb 2-156
+always schwierig 156-45
+always schwillt 156-2456-24-12345-2345 shouldn't use the will-contraction
+always schwoll 156-2456-135-12345 geschwollen shouldn't use the woll-contraction
+word sehr 234-1235
+sufword versehr 36-234-1235
+sufword unversehr 256-1236-12456-234-1235
+always selbst 234-23456
+always selbständig 234-13456-12-5-23456-45 shouldn't use the selbst-contraction
+always selbstaendig 234-13456-12-5-23456-45 shouldn't use the selbst-contraction
+always setz 2-15
+sufword sesam 234-123456-1-134 shouldn't use the sam-contraction
+word sind 234-145
+contraction sd
+always gesinde 12346-234-35-145-15
+always gesindel 12346-234-35-145-13456
+always gesindes 12346-234-35-145-123456
+always gesindest 12346-234-35-145-15-23456
+
+always sitz 2-24
+always sitzbein 2-24-12-1246 shouldn't use the bei-contraction
+always solch 234-1456
+always soll 2-234
+always sondern 234-1345
+contraction sn
+always sozial 234-123
+contraction sl
+always spiel 2-346
+always spräch 5-234-1234
+always spraech 5-234-1234
+always sprech 2-2346
+always staat 23456-2345
+always stand 2-23456
+always standard 23456-235-145-356-145 shouldn't use the stand-contraction
+always ständ 5-23456
+always staend 5-23456
+always stell 2-13456
+always sstell 234-2-13456 shouldn't use the ss-contraction
+word stets 23456-234
+always strahier 23456-1235-1-125-346-1235 abstrahieren
+
+word taiga = shouldn't use the ig-contraction
+always täter 2345-345-2345-12456 shouldn't use the er-contraction
+always technik 2345-13
+contraction tk
+always stechnik 234-2345-13
+always technisch 2345-156
+always stechnisch 234-2345-156
+sufword test 2345-15-23456 shouldn't use the es-contraction
+sufword töricht 2345-246-1235-3456-2345 shouldn't use the richt-contraction
+sufword toericht 2345-246-1235-3456-2345 shouldn't use the richt-contraction
+always trag 2345-1245
+contraction tg
+always träg 5-2345-1245
+always train = training shouldn't use the first possible in-contraction
+always treff 2345-124
+contraction tf
+always trinitro = shouldn't use the in-contraction
+always trotz 2345-1356
+contraction tz
+always trüb = trüber shouldn't use the über-contraction
+
+always unbeirr 256-23-24-1235-1235 unbeirrt shouldn't use the bei-contraction
+sufword under 256-145-12456 shouldn't use the und-contraction
+
+word überhaupt 1256-125
+word ueberhaupt 1256-125
+always übrig 1256-45
+
+always verhältnis 1236-125
+contraction vh
+always verhaeltnis 1236-125
+always viel 1236-123
+contraction vl
+word vielleicht 1236-2345
+always volk 1236-13
+contraction vk
+always völk 5-1236-13
+word vom 1236-134
+contraction vm
+
+always wahr 2456-125
+contraction wh
+always währ 5-2456-125
+always während 345-145
+contraction äd
+always waehrend 345-145
+sufword warm 2456-356-134 shouldn't use the war-contraction
+always weg 2456-1245
+contraction wg
+always weis 2-146 Anweisung
+always zweischicht 1356-2456-146-156-3456-2345 shouldn't use the weis-contraction
+always weit 2456-2345
+before konsonant begword zweit 1356-2456-146-2345 shouldn't use the weit-contraction
+always wenig 2456-45
+sufword wenn 2456-1345
+contraction wn
+always werd 2-2456
+always wesentlich 2456-456
+always wiegend 2456-346-1245-14-145 shouldn't use the wie-contraction
+always wieder 346-145
+always wien 2456-346-1345 shouldn't use the wie-contraction
+always will 2456-12345
+always william 2456-24-12345-24-1-134
+word wir 2456-1235
+word wird 2456-145
+always wirk 2456-13
+contraction wk
+word wirst 2456-23456
+always wirtschaft 2456-156
+always wiss 2456-2346
+contraction wß
+word swiss 234-2456-24-2346 shouldn't use the wiss-contraction
+always wohl 2456-123
+contraction wl
+always woll 2-135
+word Wolle 2456-135-12345-15 shouldn't use the woll-contraction
+always wollfad 2456-135-12345-124-1-145 shouldn't use the woll-contraction
+always baumwoll 12-16-134-2456-135-12345 shouldn't use the woll-contraction
+word worden 135-14
+sufword wurd 136
+sufword würd 1256
+
+always young 6-13456-135-136-1345-1245 shouldn't use the u-contraction
+
+always zahl 1356-123
+contraction zl
+always zähl 5-1356-123
+always zeit 1356-2345
+contraction zt
+before st midendword zug =
+word zum 1356-134
+word zunächst 1356-1345
+word zunaechst 1356-1345
+word zur 1356-1235
+sufword zurschau 1356-1235-156-16
+sufword zurück 1356-46
+sufword zurueck 1356-46
+always zusammen 1356-234
+always zwischen 1356-2456
+contraction zw
+
+# exceptions
+always aachen 1-1-1456-14 shouldn't use the ach-contraction
+always abbauf 1-12-12-16-124 Abbaufortschritt, Abbaufront
+sufword abend 1-12-14-145
+always abenteuer 1-12-14-2345-126-12456
+always abenteurer 1-12-14-2345-126-1235-12456
+always aberkann 1-12-12456-13-235-1345 shouldn't use the aber-contraction
+always aberkenn 1-12-12456-13-14-1345 shouldn't use the aber-contraction
+begword abernt 1-12-12456-1345-2345 shouldn't use the aber-contraction
+always abgaben 1-12-1245-1-12-14 shouldn't use the be-contraction
+always ausgaben 16-234-1245-1-12-14 shouldn't use the be-contraction
+sufword ablageraum 1-12-123-1-12346-1235-16-134
+always ablageräum 1-12-123-1-12346-1235-34-134
+always ablageraeum 1-12-123-1-12346-1235-34-134
+always ablauf 1-12-123-16-124 shouldn't use the auf-contraction
+always ablösesumm 1-12-123-246-234-15-234-136-1346 shouldn't use the es-contraction
+always abloesesumm 1-12-123-246-234-15-234-136-1346 shouldn't use the es-contraction
+always abnormität 1-12-1345-26-134-5-345 shouldn't use the mit-contraction
+always abnormitaet 1-12-1345-26-134-5-345 shouldn't use the mit-contraction
+always abrund 1-12-1235-256-145 shouldn't use the und-contraction
+begword abschieds 1-12-156-346-145-234 Abschiedsschmerz
+always achteck 1-1456-2345-15-46 shouldn't use the te-contraction
+always asocia 1-234-135-6-14-24-1 asociación shouldn't use the so-contraction
+always afrikarefer 1-124-1235-24-13-1-1235-15-124-12456 shouldn't use the ar-contraction
+sufword agent 1-1245-14-2345 shouldn't use the ge-contraction
+always akadem 1-13-1-145-12356 shouldn't use the dem-contraction
+sufword akten 1-13-2345-14 shouldn't use the te-contraction
+always akteur 1-13-2345-126-1235 shouldn't use the te-contraction
+always ingenieur 35-1245-14-24-126-1235
+always interieur 35-2345-12456-24-126-1235
+always porteur 1234-26-2345-126-1235
+always alarm 25-356-134 Alarmmeldung shouldn't use the mm-contraction
+always albern 25-12-12456-1345 shouldn't use the be-contraction
+always albert 25-12-12456-2345 shouldn't use the be-contraction
+always alexander 25-15-6-1346-235-145-12456 shouldn't use the ander-contraction
+always algerier 25-1245-12456-24-12456 shouldn't use the ie-contraction
+sufword allee 1-12345-15-15 shouldn't use the alle-contraction
+prfword alleen 1-12345-15-14 shouldn't use the alle-contraction
+always baumallee 12-16-134-1-12345-15-15 shouldn't use the mal-contraction and alle-contraction
+always baumalleen 12-16-134-1-12345-15-14 shouldn't use the mal-contraction and alle-contraction
+before st always alltags 1-12345-2345-1-1245-234
+begword alm 25-134
+begword alter 25-2345-12456
+always amateur 1-134-1-2345-126-1235 should use the eu-contraction
+before r always amerika 1-134-12456-24-13-1
+always amtschines 1-134-2345-234-1456-35-123456 shouldn't use the sch-contraction
+always amtsstub 1-134-2345-234-23456-136-12 shouldn't use the ss-contraction
+always andalusier 235-145-25-136-234-24-12456 shouldn't use the ie-contraction
+midendword anebel 1-1345-15-12-13456 Andromedanebel shouldn't use the an-contraction
+before st always anfangs 235-124-235-1245-234
+before st always angriffs 235-1245-1235-24-124-124-234
+always anklage 235-13-123-1-12346 Anklagerede shouldn't use the er-contraction
+always anklang 235-13-123-235-1245 shouldn't use the lang-contraction
+always ankläng 235-13-123-345-1345-1245 shouldn't use the läng-contraction
+begword anlagen 235-123-1-1245-14
+always anlauf 235-123-16-124 shouldn't use the auf-contraction
+before m always anleihe 235-123-146-125-15 Anleihemarkt shouldn't use the em-contraction
+before m always film =
+class egn egn
+before egn begword anti 235-2345-24
+always antichrist 235-2345-24-1456-1235-24-23456 shouldn't use the ich-contraction
+always anästh 235-345-234-2345-125 Anästhesie
+begword armee 356-134-15-15
+word armeen 356-134-15-14
+
+always augen 16-1245-14 shouldn't use the ge-contraction
+always austausch 16-234-2345-16-156 shouldn't use the st-contraction
+always beilstein 12-146-123-23456-1246 shouldn't use the bei-contraction
+word bein 12-1246 shouldn't use the bei-contraction
+word beine 12-1246-15 shouldn't use the bei-contraction
+word beinen 12-1246-14 shouldn't use the bei-contraction
+word beines 12-1246-123456 shouldn't use the bei-contraction
+always bauform 12-16-124-26-134 shouldn't use the auf-contraction
+always beteuer 23-2345-126-12456 shouldn't use the te-contraction
+always beute 12-126-236 should use the eu-contraction
+always beutel 12-126-2345-13456 should use the eu-contraction
+always bssy 12-234-234-6-13456 shouldn't use the ss-contraction
+always bundes 12-256-145-123456 shouldn't use the und-contraction
+prfword chemikalie 1456-12356-24-13-25-24-15
+always donnerstag 145-135-1345-1345-12456-234-2345-1-1245 shouldn't use the st-contraction
+before g always samstag =
+endword stag = shouldn't use the st-contraction
+endword stage 234-2345-1-12346
+endword stages 234-2345-1-1245-123456
+sufword eheinstitut 15-125-15-35-23456-24-2345-136-2345
+begword eheleu 15-125-15-123-126
+before g begword eherin 15-125-15-1235-35 Ehering(e) shouldn't use the er-contraction
+midword ehilfs = Analysehilfsmittel shouldn't use the eh-contraction
+always einter 15-35-2345-12456 shouldn't use the ein-contraction
+midendword emethod = Analysemethode shouldn't use the eh-contraction
+midendword enorm 15-1345-26-134 Aussprachenorm shouldn't use the en-contraction 
+always emuskel 15-134-136-234-13-13456 shouldn't use the em-contraction
+always eschatolog 123456-1456-1-2345-135-123-135-1245 shouldn't use the sch-contraction
+always esther 123456-2345-125-12456 shouldn't use the st-contraction
+always ästhe 345-234-2345-125-15 ästhetisch
+midendword erecht 15-1235-2345
+midendword ericht 15-2-3456
+always erepublik 15-1235-13
+midendword emädchen 15-134-345-145-1456-14
+midendword emaedchen 15-134-345-145-1456-14
+midendword emänn 15-5-134-1345
+midendword emaenn 15-5-134-1345
+midendword estand 15-2-23456
+midword estell 15-2-13456
+always found 124-135-256-145 Foundation shouldn't use the und-contraction
+begword gänse =
+word gänsen 1245-345-1345-234-14
+sufword gänserich 1245-345-1345-234-12456-3456
+always geben 12346-12-14
+always gebunden 12346-12-256-145-14 shouldn't use the und-contraction
+always geier 1245-146-12456 should use the ei-contraction
+always geil 1245-146-123 shouldn't use the ge-contraction
+midendword geingang 1245-1246-1245-235-1245
+midendword geingän 1245-1246-1245-345-1345
+always gelungen 12346-123-256-1245-14
+always generat 1245-14-12456-1-2345
+always generier 1245-14-12456-346-1235
+always gerät 12346-1235-345-2345 should use the ge-contraction
+always geraet 12346-1235-345-2345 should use the ge-contraction
+always geräusch 12346-1235-34-156 should use the ge-contraction
+always geraeusch 12346-1235-34-156 should use the ge-contraction
+always gerecht 12346-1235-2345
+always gericht 12346-2-3456
+begword german 1245-12456-134-235 shouldn't use the ge-contraction
+always gewiesen 12346-2456-346-234-14 shouldn't use the wie-contraction
+prfword hallen 125-1-12345-14 shouldn't use the allen-contraction
+always hauf 125-16-124 shouldn't use the auf-contraction
+always hotel 125-135-2345-13456 shouldn't use the te-contraction
+always installer 35-23456-1-12345-12456 shouldn't use the aller-contraction
+always internet 35-2345-12456-1345-15-2345 shouldn't use the te-contraction
+always interview 35-2345-12456-1236-24-15-2456 shouldn't use the ie-contraction
+after st always ionstrieb 245-234-2345-1235-346-12
+after st always ionstrupp 245-234-2345-1235-136-1234-1234
+always jubel 245-136-12-13456 shouldn't use the be-contraction
+always kaffee =
+endword kaffees 13-1-124-124-15-123456
+always komponist 13-135-134-1234-135-1345-24-23456 shouldn't use the nis-contraction
+always leselamp 123-123456-15-123-1-134-1234 shouldn't use the el-contraction
+always liechtenstein 123-346-1456-2345-14-23456-1246 shouldn't use the te-contraction
+prfword linie 123-35-24-15 shouldn't use the ie-contraction
+always richtlini 2-3456-123-35-24
+prfword materie 134-1-2345-12456-24-15 shouldn't use the ie-contraction
+always metallen 134-15-2345-1-12345-14 shouldn't use the allen-contraction
+always regel 1235-15-1245-13456
+always release = shouldn't use the el-contraction
+always roboter 1235-135-12-135-2345-12456 shouldn't use the te-contraction
+always round 1235-135-256-145 shouldn't use the und-contraction
+sufword rund 1235-256-145 shouldn't use the und-contraction
+word räson = shouldn't use the so-contraction
+always döschen 145-246-234-1456-14 shouldn't use the sch-contraction
+always füsschen 124-1256-2346-1456-14
+always häschen 125-345-234-1456-14 shouldn't use the sch-contraction
+always höschen 125-246-234-1456-14 should use the ch-contraction
+always wollhöschen 2456-135-12345-125-246-234-1456-14 shouldn't use the woll-contraction
+always küsschen 13-1256-2346-1456-14 shouldn't use the sch-contraction
+always möschen 134-246-234-1456-14 should use the ch-contraction
+word röschen 1235-246-234-1456-14 should use the ch-contraction
+begword rosaro = shouldn't use the ar-contraction
+midendword sammel 234-1-1346-13456 shouldn't use the sam-contraction
+midendword samml 234-1-1346-123 shouldn't use the sam-contraction
+endword schef 234-1456-15-124
+endword schefin 234-1456-15-124-35
+endword schefins 234-1456-15-124-35-234
+endword schefinnen 234-1456-15-124-35-1345-14
+endword schefs 234-1456-15-124-234
+endword eschef 123456-1456-15-124
+endword eschefin 123456-1456-15-124-35
+endword eschefins 123456-1456-15-124-35-234
+endword eschefinnen 123456-1456-15-124-35-1345-14
+endword eschefs 123456-1456-15-124-234
+always schueler 156-1256-123-12456 should use the ue-symbol
+always schwung 156-2456-256-1245 shouldn't use the ung-contraction
+always september 234-15-1234-2345-12356-12-12456 should use the em-contraction
+always dezember 145-15-1356-12356-12-12456 shouldn't use the be-contraction
+before s always sichts 234-3456-2345-234 Ansichtssache shouldn't use the ss-contraction
+always silber 234-24-123-12-12456 shouldn't use the be-contraction
+always sprung 234-1234-1235-256-1245 shouldn't use the ung-contraction
+always ssitz 234-2-24 shouldn't use the ss-contraction
+always ssonntag =
+always sspiel 234-2-346
+always ssprach 234-234-1234
+always sstand 234-2-23456
+midendword stitel 234-2345-24-2345-13456
+always studien 23456-136-145-24-14 shouldn't use the ie-contraction
+always stunde 23456-256-145-15 shouldn't use the und-contraction
+always stunden 23456-256-145-14
+always esystem 15-234-6-13456-23456-12356 shouldn't use the es-contraction
+always sturz 23456-136-1235-1356 Absturzursache shouldn't use the zu-contraction
+always tagesstät 2345-1-1245-123456-23456-345-2345 shouldn't use the ss-contraction
+always tagesstaet 2345-1-1245-123456-23456-345-2345 shouldn't use the ss-contraction
+always team = shouldn't use the te-contraction
+midendword termin 2345-12456-134-35 shouldn't use the te-contraction
+always wagen 2456-1-1245-14 shouldn't use the ge-contraction
+always weiber 2456-146-12-12456 shouldn't use the be-contraction
+sufword verbund 36-12-256-145 shouldn't use the und-contraction
+midendword verbund 1236-12456-12-256-145 shouldn't use the und-contraction
+midword versamm 1236-12456-234-1-1346 shouldn't use the sam-contraction
+always viertel 1236-346-1235-2345-13456 shouldn't use the te-contraction
+before g always zeitsta 1356-2345-234-2345-1
+begword zion = zionist shouldn't use the nis-contraction
+always zugantenn 1356-136-1245-235-2345-14-1345
+always zweiseit 1356-2456-146-234-146-2345
+always zweistaat 1356-2456-146-23456-2345 shouldn't use the weis-contraction
+
+midendword heitstätig 125-234-2345-345-2345-45 gelegenheitstätigkeit shouldn't use the st-contraction
+begmidword admiral 1-145-134-24-1235-25
+begmidword astral 1-23456-1235-25
+before m sufword atom =
+begmidword bifokal 12-24-124-135-13-25
+#begmidword brachial 12-1235-56-24-25
+begmidword bronchial 12-1235-135-1345-1456-24-25 bronchiallymphknoten shouldn't use the ll-contraction
+begword general 1245-14-12456-25
+begmidword kolonial 13-135-123-135-1345-24-25
+begmidword zentral 1356-14-2345-1235-25 zentrallager
+always schall 156-1-12345 schallen shouldn't use the allen-contraction
+always herzultra 125-12456-1356-136-123-2345-1235-1 shouldn't use the zu-contraction
+always mittagessen 134-24-2345-2345-1-1245-15-2346-14
+always snakeskin 234-1345-1-13-15-234-13-35
+
+# countries
+always dänemark 145-345-1345-15-134-356-13 shouldn't use the em-contraction
+always daenemark 145-345-1345-15-134-356-13 shouldn't use the em-contraction
+
+# names
+word angela 235-1245-13456-1 shouldn't use the ge-contraction
+word angeles 235-1245-13456-123456 shouldn't use the ge-contraction
+word angelika 235-1245-13456-24-13-1 shouldn't use the ge-contraction
+word angelina 235-1245-13456-35-1 shouldn't use the ge-contraction
+word angelo 235-1245-13456-135 shouldn't use the ge-contraction
+always daniel 145-235-24-13456 shouldn't use the ie-contraction
+always gerlind 1245-12456-123-35-145 should use the er-contraction
+always solveig 234-135-123-1235-146-1245 shouldn't use the so-contraction
+
+# TODO:
+# 8-Punkte-Plan
+
+# inline contraction of emoji descriptions
+emoji de
diff --git a/Tables/Contraction/de-g0.ctb b/Tables/Contraction/de-g0.ctb
new file mode 100644
index 0000000..cab5c63
--- /dev/null
+++ b/Tables/Contraction/de-g0.ctb
@@ -0,0 +1,260 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German (uncontracted)
+# Created by Mario Lang <mlang@delysid.org>.
+
+numsign 3456  number sign, just one operand
+letsign 6
+capsign 46 # FIXME: only allowed at begword, midendword capsign is 45 
+begcaps 45
+
+# the decimal digits
+always 1 1
+always 2 12
+always 3 14
+always 4 145
+always 5 15
+always 6 124
+always 7 1245
+always 8 125
+always 9 24
+always 0 245
+
+include letters-latin.cti
+always ä 345
+always Ä 345
+always ö 246
+always Ö 246
+always ß 6-2346
+always ü 1256
+always Ü 1256
+
+# accented letters
+always À 4-1      [C0] upper a grave
+always Á 4-1      [C1] upper a acute
+always  4-1      [C2] upper a circumflex
+always à 4-1      [C3] upper a tilde
+always Ç 4-14     [C7] upper c cedilla
+always È 4-15     [C8] upper e grave
+always É 4-15     [C9] upper e acute
+always Ê 4-15     [CA] upper e circumflex
+always Ë 4-15     [CB] upper e dieresis
+always Ì 4-24     [CC] upper i grave
+always Í 4-24     [CD] upper i acute
+always Î 4-24     [CE] upper i circumflex
+always Ï 4-24     [CF] upper i dieresis
+always Ð 4-15     [D0] upper eth
+always Ñ 4-1345   [D1] upper n tilde
+always Ò 4-135    [D2] upper o grave
+always Ó 4-135    [D3] upper o acute
+always Ô 4-135    [D4] upper o circumflex
+always Õ 4-135    [D5] upper o tilde
+always Ø 4-135    [D8] upper o slash
+always Ù 4-136    [D9] upper u grave
+always Ú 4-136    [DA] upper u acute
+always Û 4-136    [DB] upper u circumflex
+always Ý 4-13456  [DD] upper y acute
+always Þ 4-2345   [DE] upper t horn
+always à   4-1      [E0] lower a grave
+always á   4-1      [E1] lower a acute
+always â   4-1      [E2] lower a circumflex
+always ã   4-1      [E3] lower a tilde
+always å   4-1      [E5] lower a ring
+always æ   1-15     [E6] lower ae
+always ç   4-14     [E7] lower c cedilla
+always è   4-15     [E8] lower e grave
+always é   4-15     [E9] lower e acute
+always ê   4-15     [EA] lower e circumflex
+always ë   4-15     [EB] lower e dieresis
+always ì   4-24     [EC] lower i grave
+always í   4-24     [ED] lower i acute
+always î   4-24     [EE] lower i circumflex
+always ï   4-24     [EF] lower i dieresis
+always ð   4-15     [F0] lower eth
+always ñ   4-1345   [F1] lower n tilde
+always ò   4-135    [F2] lower o grave
+always ó   4-135    [F3] lower o acute
+always ô   4-135    [F4] lower o circumflex
+always õ   4-135    [F5] lower o tilde
+always ø   4-135    [F8] lower o slash
+always ù   4-136    [F9] lower u grave
+always ú   4-136    [FA] lower u acute
+always û   4-136    [FB] lower u circumflex
+always ý   4-13456  [FD] lower y acute
+always þ   4-2345   [FE] lower t horn
+always ÿ   4-13456  [FF] lower y dieresis
+
+# common abbreviations
+word GmbH 46-1245-134-12-45-125
+
+midnum ^ 4-346-3456
+always \u2070 4-346-3456-245  superscript 0
+always \u00B9 4-346-3456-1    superscript 1
+always \u00B2 4-346-3456-12        superscript 2
+always \u00B3 4-346-3456-14   superscript 3
+always \u2074 4-346-3456-145  superscript 4
+always \u2075 4-346-3456-15   superscript 5
+always \u2076 4-346-3456-124  superscript 6
+always \u2077 4-346-3456-1245 superscript 7
+always \u2078 4-346-3456-125  superscript 8
+always \u2079 4-346-3456-24   superscript 9
+
+always ° 4-356 Grad
+before space endnum ' 4-35 Winkelminute
+before space endnum \u2032 4-35 Winkelminute
+before space endnum '' 4-35-35
+before space endnum \u2033 4-35-35
+
+prepunc " 236
+postpunc " 356
+begword « 236
+endword » 356
+always " 6-4
+
+always ' 6-6
+
+prepunc `` 236
+always ` 4
+
+always ^ 456-126
+
+always ~ 4-156
+repeatable ~~~ 4-156-4-156-4-156
+
+midnum , 2
+always , 2
+
+always ; 23
+
+midnum : 25
+always : 25
+repeatable ::: 25-25-25
+
+midnum . 3
+always . 3
+always ... 3-3-3
+always .\s.\s. 3-3-3 . . .
+
+endnum ! 12346 (factorial)
+always ! 235
+
+endword ? 26
+always ? 6-26
+always \uFFFD 6-26
+
+always ( 2356
+always ) 2356
+
+always [ 6-2356
+always ] 6-2356
+
+always { 56-2356
+always } 56-2356
+
+always # 3456
+
+midnum * 35
+always * 6-35
+repeatable *** 6-35-35-35
+
+midnum / 256
+always / 5-2
+
+always % 3456-245-356
+always \u2030 3456-245-356-356 promille
+always & 5-136
+
+always @ 4-345
+
+always \\ 347
+
+always | 6-34
+
+repeatable \s 0
+repeatable \t 0
+repeatable \xa0 0 no break space
+
+repeatable --- 36-36-36
+
+always _ 4567
+repeatable ___ 4567-4567
+
+repeatable === 6-2356-2356-2356
+
+# the hyphen
+midendword - 36
+always - 6-36
+#repeatable ­­­ 6-36-36-36
+
+# mathematical symbols
+always < 5-13
+always = 4-2356
+always > 46-2
+midnum + 235
+always + 6-235
+midnum - 36
+always × 4-236 Mal(-Kreuz)
+midnum × 236 Mal(-Kreuz)
+midnum ÷ 46-34 division sign
+begnum $ 256
+always $ 256-3456
+always ¼ 3456-1-1256-145
+always ½ 3456-1-1256-12
+always ¾ 3456-1-1256-14
+
+# other special characters
+always © 2356-6-14-2356 copyright
+always ¶ 4-1234-345 paragraph
+always § 4-234-3 section
+always ¢ 4-14 cents
+always £ 4-123 pounds
+always ¥ 4-13456 yen
+always µ 46-134 mu
+
+# special character sequences
+literal :// URLs
+literal www.
+
+literal .com
+literal .edu
+literal .gov
+literal .mil
+literal .net
+literal .org
+include countries.cti
+
+literal .doc
+literal .htm
+literal .html
+literal .tex
+literal .txt
+
+literal .gif
+literal .jpg
+literal .png
+literal .wav
+
+literal .tar
+literal .zip
+
+# When an upper-case letter occurs inside a contraction, following a lower-case
+# letter, the contraction should not be used. Example McCan
+
+# when a decimal begins with a period, it should be translated with a 
+# number sign followed by a decimal point, followed by the number.
diff --git a/Tables/Contraction/de-g1.ctb b/Tables/Contraction/de-g1.ctb
new file mode 100644
index 0000000..45d0c8b
--- /dev/null
+++ b/Tables/Contraction/de-g1.ctb
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German (basic contractions)
+# Created by Mario Lang <mlang@delysid.org>.
+
+include de-g0.ctb
+
+class cst cst
+class s s
+class t t
+
+# Lautgruppenkürzungen
+always au 16
+always eu 126
+always ei 146
+always ch 1456
+always sch 156
+always st 23456
+midendword ie 346
+always äu 34
+
+before s midendword aus 16-234
+
+always barrier = shouldn't use the ie-contraction
+always bankier = shouldn't use the ie-contraction
+always famili = familie(n) shouldn't use the ie-contraction
+endword studie 23456-136-145-24-15 shouldn't use the ie-contraction
+midendword ien = shouldn't use the ie-contraction
+midendword iell = shouldn't use the ie-contraction
+begmidword dien 145-346-1345
+always indien = shouldn't use the ie-contraction
+midendword ietät =
+always spezies =
+before cst midword ungs = Bindungscharakter, Regierungschef
+
+# unsolvable problems:
+# die Premiere vs. ich premiere
+# die Premieren vs. sie premieren
+
+# exceptions
+word aktie = shouldn't use the ie-contraction
+always algerier = shouldn't use the ie-contraction
+prfword amphibie = shouldn't use the ie-contraction
+always amtschef 1-134-2345-234-1456-15-124 shouldn't use the sch-contraction
+always amtschines 1-134-2345-234-1456-24-1345-15-234 shouldn't use the sch-contraction
+always andalusier = shouldn't use the ie-contraction
+always asthma = shouldn't use the st-contraction
+before t always bundes = shouldn't use the st-contraction
+prfword chemikalie 1456-15-134-24-13-1-123-24-15 shouldn't use the ie-contraction
+always dienstag 145-346-1345-234-2345-1-1245 shouldn't use the st-contraction
+always donnerstag = shouldn't use the st-contraction
+always eschatolog 15-234-1456-1-2345-135-123-135-1245 shouldn't use the sch-contraction
+always esther = shouldn't use the st-contraction
+prfword folie = shouldn't use the ie-contraction
+sufword hoer 125-246-1235 should use the oe-symbol
+always interview = shouldn't use the ie-contraction
+always koffein = shouldn't use the ei-contraction
+always lilie = shouldn't use the ie-contraction
+prfword linie 123-24-1345-24-15 shouldn't use the ie-contraction
+prfword materie = shouldn't use the ie-contraction
+always medien = shouldn't use the ie-contraction
+always museum = shouldn't use the eu-contraction
+always propriet = proprietär shouldn't use the ie-contraction
+always döschen 145-246-234-1456-15-1345
+always höschen 125-246-234-1456-15-1345 should use the ch-contraction
+word röschen 1235-246-234-1456-15-1345 should use the ch-contraction
+always samstag = shouldn't use the st-contraction
+word wisst = shouldn't use the st-contraction
+always vietnam = shouldn't use the ie-contraction
+
+# names
+always daniel = shouldn't use the ie-contraction
diff --git a/Tables/Contraction/de-g2.ctb b/Tables/Contraction/de-g2.ctb
new file mode 100644
index 0000000..6b4f254
--- /dev/null
+++ b/Tables/Contraction/de-g2.ctb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German (contracted)
+
+include de-1998.ctb
+
diff --git a/Tables/Contraction/de-wort.cti b/Tables/Contraction/de-wort.cti
new file mode 100644
index 0000000..85002a7
--- /dev/null
+++ b/Tables/Contraction/de-wort.cti
@@ -0,0 +1,361 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Subtable - German (contracted)
+
+always aldehyd 25-145-15-125-6-13456-145
+sufword alpha 25-1234-125-1
+always arhythm =
+
+before hilmnrsu always aufnahme 2-16-1345-134-15
+prfword aufnahmen 2-16-1345-134-14
+
+before e always ablöse =
+always absolvent 1-12-234-135-123-1236-14-2345
+begmidword anachron 235-1-1456-1235-135-1345
+always analyse 235-25-6-13456-234-15
+prfword analysen 235-25-6-13456-234-14
+always analysenauf 235-25-6-13456-234-14-2-16
+always analysenergeb 235-25-6-13456-234-14-12456-12346-12
+before konsonant always analysen 235-25-6-13456-234-14
+always arthrose 356-2345-125-1235-135-234-15
+prfword arthrosen 356-2345-125-1235-135-234-14
+always ägypten 345-1245-6-13456-1234-2345-14 shouldn't use the te-contraction
+always aegypten 345-1245-6-13456-1234-2345-14
+always aufgaben 2-16-1245-1-12-14
+prfword bachelor 12-1-1456-13456-135-1235
+begmidword bachelor 12-1-1456-13456-26
+
+word beat =
+always beatbox =
+always beatmusik 12-15-1-2345-134-13
+always beatnik =
+always backbeat 12-1-46-12-15-1-2345
+word offbeat = Sauerstoffbeatmung
+always popbeat =
+always rockbeat 1235-135-46-12-15-1-2345
+
+always befund 23-124-256-145
+always beicht 12-146-1456-2345
+prfword beichte 12-146-1456-236
+prfword beichtest 12-146-1456-236-23456
+prfword beichtet 12-146-1456-236-2345
+prfword beichtete 12-146-1456-236-236
+prfword gebeichtetem 12346-12-146-1456-236-2345-12356
+prfword beichteten 12-146-1456-236-2345-14
+prfword gebeichteter 12346-12-146-1456-236-2345-12456
+prfword gebeichtetes 12346-12-146-1456-236-2345-123456
+prfword beichtetest 12-146-1456-236-236-23456
+prfword beichtetet 12-146-1456-236-236-2345
+endword betrieben 23-2345-1235-346-12-14
+always blaubeer 12-123-16-12-15-15-1235
+always brombeer =
+prfword bund 12-256-145
+always burgunder 12-136-1235-1245-256-145-12456
+#always cranberry 6-14-1235-235-23-1235-1235-6-13456
+always dealer 145-15-1-123-12456
+always dehydr =
+always diagnose =
+prfword diagnosen 145-24-1-1245-1345-135-234-14
+before konsonant always diagnosen 145-24-1-1245-1345-135-234-14
+always diamant 145-24-1-134-235-2345
+before e always einreise 1246-1235-146-234-15
+always eisstau 146-234-23456-16 eisstau eisstaub gleisstaub
+always erpresser 12456-1234-1235-15-2346-12456
+always estragon 123456-2345-1235-1-1245-135-1345 # trag?
+always europa 126-1235-135-1234-1
+midendword expresses 15-6-1346-1234-1235-15-2346-123456
+word expresses 1346-1234-1235-15-2346-123456
+sufword gamma 1245-1-1346-1
+before hilmnrsu always gebäude 12346-12-34-145-15
+prfword gebäuden 12346-12-34-145-14
+prfword gebäudes 12346-12-34-145-123456
+before hilmnrsu always gebinde 12346-12-35-145-15
+prfword gebinden 12346-12-35-145-14
+prfword gebindes 12346-12-35-145-123456
+sufword gehäuse 12346-125-34-234-15
+prfword gehäusen 12346-125-34-234-14
+prfword gehäuses 12346-125-34-234-123456
+always gesund 12346-234-256-145
+before hilmnrsu always gewinde 12346-2456-35-145-15
+always gewindelt 12346-2456-35-145-13456-2345
+always gewindelte 12346-2456-35-145-13456-236
+always gewindeltem 12346-2456-35-145-13456-2345-12356
+always gewindelten 12346-2456-35-145-13456-2345-14
+always gewindelter 12346-2456-35-145-13456-2345-12456
+always gewindeltes 12346-2456-35-145-13456-2345-123456
+prfword gewinden 12346-2456-35-145-14
+prfword gewindes 12346-2456-35-145-123456
+word Gewindeschaft 12346-2456-35-145-15-156-1-124-2345
+before hilmnrsu always gemeinde 12346-134-1246-145-15 gemeinderat shouldn't use the er-contraction
+
+before hilmnrsu always gelatine 12346-123-1-2345-35-15
+prfword gelatinen 12346-123-1-2345-35-14
+
+prfword gemeinden 12346-134-1246-145-14
+prfword gemeindend 12346-134-1246-145-14-145
+prfword gemeindende 12346-134-1246-145-14-145-15
+prfword gemeindendem 12346-134-1246-145-14-145-12356
+prfword gemeindenden 12346-134-1246-145-14-145-14
+prfword gemeindender 12346-134-1246-145-14-145-12456
+prfword gemeindendes 12346-134-1246-145-14-145-123456
+always gemeindeutsch 12346-134-1246-145-156
+always gemüse 12346-134-1256-234-15
+prfword gemüsen 12346-134-1256-234-14
+prfword gemüses 12346-134-1256-234-123456
+always getreide 12346-2345-1235-146-145-15
+prfword getreiden 12346-2345-1235-146-145-14
+prfword getreides 12346-2345-1235-146-145-123456
+always gorgonzola 1245-26-1245-135-1345-1356-135-123-1
+sufword gottes 1245-135-2345-2345-123456
+always gottesdienst 1245-135-2345-2345-123456-145-346-1345-23456
+always glucose =
+always glukose =
+before t always habich 125-1-12-3456
+prfword haie =
+always haustier 125-16-234-2345-346-1235
+always health =
+always heidelbeer 125-146-145-13456-12-15-15-1235
+before t always hilfs =
+prfword hilfst 125-24-123-124-23456
+always himbeer =
+sufword hirse =
+sufword holunder 125-135-123-256-145-12456
+sufword holunderbeer 125-135-123-256-145-12456-12-15-15-1235
+always hypnose =
+prfword hypnosen 125-6-13456-1234-1345-135-234-14
+always infanterie 35-124-235-2345-12456-346
+prfword infanterien 35-124-235-2345-12456-24-14
+prfword imitat =
+always johannis 245-135-125-235-1345-24-234
+always johannisbeer 245-135-125-235-1345-24-234-12-15-15-1235
+before lnr always kamera 13-1-134-12456-1
+before konsonant always kameral 13-1-134-12456-25
+always kameralia 13-1-134-12456-25-24-1
+always kameralismus 13-1-134-12456-25-5-24
+always kameralist 13-1-134-12456-25-24-23456
+always kameralistisch 13-1-134-12456-25-5-156
+before hilmnrsu always karriere 13-356-1235-24-12456-15
+prfword karrieren 13-356-1235-24-12456-14
+always klasse 13-123-1-2346-15
+always klassen 13-123-1-2346-14
+always koriander 13-135-1235-24-235-145-12456
+always kurzbein 13-1356-12-146-1345
+prfword kurzeit 13-136-1235-1356-146-2345
+prfword kurzeiten 13-136-1235-1356-146-2345-14
+always kurzentren 13-136-1235-1356-14-2345-1235-14
+always kurzentrum 13-136-1235-1356-14-2345-1235-136-134
+prfword kurzelle 13-136-1235-1356-15-12345-15
+prfword kurzellen 13-136-1235-1356-15-12345-14
+always kurzhaar 13-1356-125-1-1-1235
+always laufwerk 123-16-124-2456-12456-13
+always leiterin 123-146-2345-12456-35
+always limit =
+always lipizzaner 123-24-1234-24-1356-1356-235-12456
+
+always mantel 134-235-2345-13456
+
+before hilmnrsu always marine 134-356-35-15
+prfword marinem 134-356-35-12356
+prfword marinen 134-356-35-14
+prfword mariner 134-356-35-12456
+prfword marines 134-356-35-123456
+# people from san-marino
+prfword marinese 134-356-35-123456-15
+prfword marinesin 134-356-35-123456-35
+prfword marinesinnen 134-356-35-123456-35-1345-14
+prfword marinesisch 134-356-35-123456-24-156
+prfword marinesische 134-356-35-123456-24-156-15
+prfword marinesischem 134-356-35-123456-24-156-12356
+prfword marinesischen 134-356-35-123456-24-156-14
+prfword marinesischer 134-356-35-123456-24-156-12456
+prfword marinesisches 134-356-35-123456-24-156-123456
+
+always matura =
+always maturant 134-1-2345-136-1235-235-2345
+
+before s always mess 134-15-2346
+
+prfword mitte 134-24-2345-236
+prfword mitten 134-24-2345-2345-14
+prfword moore =
+prfword mooren 134-135-135-1235-14
+prfword moores 134-135-135-1235-123456
+always nappaleder 1345-1-1234-1234-1-123-15-145-12456
+begword neuro 1345-126-1235-135
+always olympia =
+
+prfword orgie 26-1245-24-15
+prfword orgien 26-1245-24-14
+
+sufword orts 26-2345-234
+always ortschaft 26-2345-156
+always ortstein 26-2345-23456-1246
+
+always papagei 1234-1-1234-1-1245-146
+always paprika =
+always pestorahm 1234-15-23456-135-1235-1-125-134
+before lnr always pizza =
+always preiselbeer 1234-1235-146-234-13456-12-15-15-1235
+before hilmnrsu always presse 1234-1235-15-2346-15
+prfword pressen 1234-1235-15-2346-14
+prfword pressend 1234-1235-15-2346-14-145
+prfword pressende 1234-1235-15-2346-14-145-15
+prfword pressendem 1234-1235-15-2346-14-145-12356
+prfword pressenden 1234-1235-15-2346-14-145-14
+prfword pressender 1234-1235-15-2346-14-145-12456
+prfword pressendes 1234-1235-15-2346-14-145-123456
+always pressendruck 1234-1235-15-2346-14-145-46
+prfword pressens 1234-1235-15-2346-14-234
+always pressentisch 1234-1235-15-2346-14-2345-24-156
+always rastalock 1235-1-23456-1-123-135-46
+before e always reise 1235-146-234-15
+always requiem 1235-15-6-12345-24-12356
+
+before hilmnrsu always reserve 1235-123456-12456-1236-15
+prfword reserven 1235-123456-12456-1236-14
+
+always rhabarber 1235-125-1-12-1-1235-12-12456
+prfword runde 1235-256-145-15
+prfword runden 1235-256-145-14
+sufword sahne =
+always sahnemeer =
+always salbei 234-25-12-146
+sufword sauf 234-16-124
+
+always sample =
+prfword samplen 234-1-134-1234-123-14
+prfword sampler 234-1-134-1234-123-12456
+always samplermodul 234-1-134-1234-123-12456-134-135-145-136-123
+prfword samplern 234-1-134-1234-123-12456-1345
+prfword samplers 234-1-134-1234-123-12456-234
+prfword samples 234-1-134-1234-123-123456
+
+midendword samt =
+before l always sauna 234-16-1345-1
+prfword solos =
+prfword soli =
+before l always schul 156-136-123
+always schulter 156-136-123-2345-12456 shouldn't use the te-contraction
+always schultragödie 156-136-123-2345-1235-1-1245-246-145-24-15
+always schultragödien 156-136-123-2345-1235-1-1245-246-145-24-14
+always schultrans 156-136-123-2345-1235-235-234 schultransport
+before hilmnrsu always see =
+sufword solar 234-135-123-356
+prfword soße =
+prfword soßen 234-135-6-2346-14
+always sound =
+always soundso 2-1234-2-136-2-1234
+before e always speise 234-1234-146-234-15
+always tabasco =
+always teacher 2345-15-1-1456-12456
+always teaching 2345-15-1-1456-35-1245
+before l always teil 2345-146-123
+before hilmnrsu always teilnahme 2345-146-123-1345-134-15
+prfword teilnahmen 2345-146-123-1345-134-14
+always temperatur 2345-12356-1234-12456-1-2345-136-1235 prefer em-contraction over te-contraction
+always tempo 2345-12356-1234-135
+prfword temporal 2345-12356-1234-26-1-123
+begmidword temporal 2345-12356-1234-26-25
+always temporär 2345-12356-1234-26-345-1235
+word tempore 2345-12356-1234-26-15
+always theater 2345-125-15-1-2345-12456 shouldn't use the te-contraction
+always tuberkulose 2345-136-12-12456-13-136-123-135-234-15
+prfword tuberkulosen 2345-136-12-12456-13-136-123-135-234-14
+before lnr always tundra 2345-256-145-1235-1
+sufword ultra =
+always ungleich 256-1245-1456 shouldn't use the ung-contraction
+always vegetarier 1236-15-12346-2345-356-24-12456
+before l always vokal 1236-135-13-25
+always völkerkunde 5-1236-13-12456-13-256-145-15
+always wachstum 2456-56-234-2345-136-134
+always wachstums 2456-56-234-2345-136-134-234
+
+before lnr always yoga =
+always ziegel 1356-346-1245-13456 shouldn't use the ge-contraction
+always zugbegleiter 1356-136-1245-23-1245-123-146-2345-12456
+always zugkraft 1356-136-1245-13-124
+always zugkräft 1356-136-1245-5-13-124
+word zugmitte 1356-136-1245-134-24-2345-236
+always zugtempo 1356-136-1245-2345-12356-1234-135
+always zwilling 1356-2456-24-12345-35-1245
+prfword zwillinge 1356-2456-24-12345-35-12346
+before konsonant always zwillings 1356-2456-24-12345-35-1245-234
+always zypressen 1356-6-13456-1234-1235-15-2346-14
+
+# Doppelvokale
+always erdbeer 12456-145-12-15-15-1235
+always waldbeer 2456-25-145-12-15-15-1235
+always zwergerdbeer 1356-2456-12456-1245-12456-145-12-15-15-1235
+always frisbee 124-1235-24-234-12-15-15
+always kaffee =
+always coffee 6-14-135-124-124-15-15
+always toffee 2345-135-124-124-15-15
+always dragee =
+always bungee 12-256-1245-15-15
+always dorothee 145-26-135-2345-125-15-15
+always cheese 1456-15-15-234-15
+always pappmaschee 1234-1-1234-1234-134-1-156-15-15
+always klischee 13-123-24-156-15-15
+always yankee 6-13456-235-13-15-15
+sufword teenie 2345-15-15-1345-346
+sufword teenager 2345-15-15-1345-1-1245-12456
+always gelee 1245-13456-15-15
+prfword geleerte 12346-123-15-15-1235-236
+always geleert 12346-123-15-15-1235-2345
+
+always soufflee =
+always defilee 145-15-124-24-123-15-15
+always klee =
+begword tee =
+always renommee 1235-14-135-1346-15-15
+always armee 356-134-15-15
+always resümee 1235-123456-1256-134-15-15
+always schnee 156-1345-15-15
+always trainee 2345-1235-1-24-1345-15-15
+always pralinee 1234-1235-1-123-35-15-15
+always portmonee 1234-26-2345-134-135-1345-15-15
+always kanapee 13-235-1-1234-15-15
+always portepee 1234-26-236-1234-15-15
+always separee 234-15-1234-356-15-15
+always referee 1235-15-124-12456-15-15
+always schikoree 156-24-13-26-15-15
+always spree 234-1234-1235-15-15
+always karree 13-356-1235-15-15
+always porree 1234-26-1235-15-15
+always püree 1234-1256-1235-15-15
+always frikassee 124-1235-24-13-1-2346-15-15
+always plissee 1234-123-24-2346-15-15
+always varietee 1236-356-24-15-2345-15-15
+always komitee =
+always trustee 2345-1235-136-23456-15-15
+always frottee 124-1235-135-2345-2345-15-15
+always queen =
+always halloween 125-1-12345-135-2456-15-15-1345
+
+always hawaii =
+always ascii =
+
+always boom =
+always voodoo =
+always yahoo =
+always waterloo 2456-1-2345-12456-123-135-135
+always shampoo =
+always tattoo =
+always daewoo =
+always zoo =
diff --git a/Tables/Contraction/de.ctb b/Tables/Contraction/de.ctb
new file mode 100644
index 0000000..e8de655
--- /dev/null
+++ b/Tables/Contraction/de.ctb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - German
+
+include de-g2.ctb
diff --git a/Tables/Contraction/en-ueb-g1.ctb b/Tables/Contraction/en-ueb-g1.ctb
new file mode 100644
index 0000000..6a3e8b5
--- /dev/null
+++ b/Tables/Contraction/en-ueb-g1.ctb
@@ -0,0 +1,170 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - English (UEB, grade 1)
+# Created by Michael Curran <mick@kulgan.net>
+
+#This is by no means complete, but at least all the important ascii symbols and general grade 2 contractions are here.
+#I am trying to compress space as much as possible so a lot of syllable boundary rules havn't been included.
+
+#Special signs
+capsign 6
+begcaps 6-6
+endcaps 6-3
+letsign 56
+
+#Numbers
+numsign 3456
+always 1 1
+always 2 12
+always 3 14
+always 4 145
+always 5 15
+always 6 124
+always 7 1245
+always 8 125
+always 9 24
+always 0 245
+midnum . 256
+midnum , 2
+midnum \s 5
+
+include letters-latin.cti
+always \s 0  
+repeatable \s\s 0-0
+always \t 456-2345
+
+always " 6-2356
+always ' 3
+
+always , 2
+always ; 23
+always : 25
+always . 256
+always ! 235
+always ? 236
+
+always ( 5-126
+always ) 5-345
+always [ 46-126
+always ] 46-345
+always { 456-126
+always } 456-345
+always < 4-126
+always > 4-345
+
+always | 456-1256
+always / 456-34
+always \\ 456-16
+
+always ` 46-16
+always ~ 4-35
+always @ 4-1
+always # 456-1456
+always $ 4-234
+always % 46-356
+always ^ 4-26
+always & 4-12346
+always * 5-35
+always - 36
+always _ 46-36
+always = 5-2356
+always + 5-235
+
+# http://www.brailleauthority.org/ueb/symbols_list.pdf
+always	\xA1	45-56-235	# ¡ ⠘⠰⠖	inverted exclamation mark - 13.5
+always	\xA2	4-14	# ¢ ⠈⠉	cent - 3.10
+always	\xA3	4-123	# £ ⠈⠇	pound (sterling) - 3.10
+always	\xA5	4-13456	# ¥ ⠈⠽	yen (Japan) - 3.10
+always	\xA7	45-234	# § ⠘⠎	section - 3.19
+always	\xA9	45-14	# © ⠘⠉	copyright - 3.8
+always	\xAE	45-1235	# ® ⠘⠗	registered - 3.8
+always	\xB0	45-245	# ° ⠘⠚	degree - 3.11
+always	\xB6	45-1234	# ¶ ⠘⠏	paragraph - 3.19
+always	\xBF	45-56-236	# ¿ ⠘⠰⠦	inverted question mark - 13.5
+always	\u0300	45-16	# ` ⠘⠡	grave accent (combining) - 4.2
+always	\u0301	45-34	# ´ ⠘⠌	acute accent (combining) - 4.2
+always	\u0302	45-146	# ^ ⠘⠩	circumflex accent (combining) - 4.2
+always	\u0303	45-12456	# ~ ⠘⠽	tilde (combining) - 4.2
+always	\u0308	45-25	# ¨ ⠘⠒	diaeresis (combining) - 4.2
+always	\u030A	45-1246	# ˚ ⠘⠫	ring above (combining) - 4.2
+always	\u0327	45-12346	# ¸ ⠘⠯	cedilla (combining) - 4.2
+always	\u2014	6-36	# — ⠠⠤	dash - 7.2
+always	\u2020	4-6-1456	# † ⠈⠠⠹	dagger - 3.3
+always	\u2021	4-6-12456	# ‡ ⠈⠠⠻	double dagger - 3.3
+always	\u2022	456-256	# • ⠸⠲	bullet - 3.5
+always	\u20A3	4-124	# ₣ ⠈⠋	franc (France) - 3.10
+always	\u20A6	4-1345	# ₦ ⠈⠝	naira (Nigeria) - 3.10
+always	\u20AC	4-15	# € ⠈⠑	euro - 3.10
+always	\u2122	45-2345	# ™ ⠘⠞	trade mark - 3.8
+always	\u2640	45-1346	# ♀ ⠘⠭	female (Venus) - 3.16
+always	\u2642	45-13456	# ♂ ⠘⠽	male (Mars) - 3.16
+always	\u3003	5-2	# 〃 ⠐⠂	ditto mark - 3.12
+
+# http://www.brailleauthority.org/ueb/Guidelines_for_Technical_Material_2008-10.pdf
+always	\x2B	5-235	# + ⠐⠖	plus
+always	\xB1	456-235	# ± ⠸⠖	plus or minus (plus over minus)
+always	\xB7	5-256	# · ⠐⠲	multiplication (middle dot)
+always	\xD7	5-236	# × ⠐⠦	multiplication (cross)
+always	\xF7	5-34	# ÷ ⠐⠌	division (horizontal line between dots)
+always	\u2032	2356	# ′ ⠶	prime (feet, minutes)
+always	\u2033	2356-2356	# ″ ⠶⠶	double prime (inches, seconds)
+always	\u2212	5-36	# − ⠐⠤	minus
+always	\u2213	456-36	# ∓ ⠸⠤	minus or plus (minus over plus)
+always	\u221D	456-5-2356	# ∝ ⠸⠐⠶	proportional to
+always	\u2243	456-35	# ≃ ⠸⠔	asymptomatically equal to (tilde over horizontal line)
+always	\u2245	5-456-35	# ≅ ⠐⠸⠔	approximately equal to (tilde over equals sign)
+always	\u2248	45-35	# ≈ ⠘⠔	almost equal to (tilde over tilde)
+always	\u224F	45-5-2356	# ≏ ⠘⠐⠶	difference between (equals sign with bump in top bar)
+always	\u2251	46-5-2356	# ≑ ⠨⠐⠶	geometrically equal to (equals sign dotted above and below)
+always	\u2260	5-2356-4-156	# ≠ ⠐⠶⠈⠱	not equal to (line through equals sign)
+always	\u2261	456-123456	# ≡ ⠸⠿	identical to (three horizontal lines)
+always	\u2264	456-4-126	# ≤ ⠸⠈⠣	less than or equal to
+always	\u2265	456-4-345	# ≥ ⠸⠈⠜	greater than or equal to
+always	\u226A	46-4-126	# ≪ ⠨⠈⠣	much less than
+always	\u226B	46-4-345	# ≫ ⠨⠈⠜	much greater than
+
+# The Greek letters
+always	\u03B1	46-1	# α ⠨⠁	alpha
+always	\u03B2	46-12	# β ⠨⠃	beta
+always	\u03B3	46-1245	# γ ⠨⠛	gamma
+always	\u03B4	46-145	# δ ⠨⠙	delta
+always	\u03B5	46-15	# ε ⠨⠑	epsilon
+always	\u03B6	46-1356	# ζ ⠨⠵	zeta
+always	\u03B7	46-156	# η ⠨⠱	eta
+always	\u03B8	46-1456	# θ ⠨⠹	theta
+always	\u03B9	46-24	# ι ⠨⠊	iota
+always	\u03BA	46-13	# κ ⠨⠅	kappa
+always	\u03BB	46-123	# λ ⠨⠇	lamda
+always	\u03BC	46-134	# μ ⠨⠍	mu
+always	\u03BD	46-1345	# ν ⠨⠝	nu
+always	\u03BE	46-1346	# ξ ⠨⠭	xi
+always	\u03BF	46-135	# ο ⠨⠕	omicron
+always	\u03C0	46-1234	# π ⠨⠏	pi
+always	\u03C1	46-1235	# ρ ⠨⠗	rho
+always	\u03C2	46-234	# ς ⠨⠎	final sigma
+always	\u03C3	46-234	# σ ⠨⠎	sigma
+always	\u03C4	46-2345	# τ ⠨⠞	tau
+always	\u03C5	46-136	# υ ⠨⠥	upsilon
+always	\u03C6	46-124	# φ ⠨⠋	phi
+always	\u03C7	46-12346	# χ ⠨⠯	chi
+always	\u03C8	46-13456	# ψ ⠨⠽	psi
+always	\u03C9	46-2456	# ω ⠨⠺	omega
+
+# inline contraction of emoji descriptions
+emoji en
diff --git a/Tables/Contraction/en-ueb-g2.ctb b/Tables/Contraction/en-ueb-g2.ctb
new file mode 100644
index 0000000..755a273
--- /dev/null
+++ b/Tables/Contraction/en-ueb-g2.ctb
@@ -0,0 +1,326 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - English (UEB, grade 2)
+# Created by Michael Curran <mick@kulgan.net>
+
+#This is by no means complete, but at least all the important ascii symbols and general grade 2 contractions are here.
+#I am trying to compress space as much as possible so a lot of syllable boundary rules havn't been included.
+
+#get the single character definitions from the grade 1 rules
+include en-ueb-g1.ctb
+
+#Largest contractions
+word a =
+word about 1-12
+contraction ab
+word above 1-12-1236
+contraction abv
+sufword according 1-14
+contraction ac
+word across 1-14-1235
+contraction acr
+word after 1-124
+contraction af
+sufword afterward 1-124-2456
+contraction afw
+sufword afternoon 1-124-1345
+contraction afn
+word again 1-1245
+contraction ag
+word against 1-1245-34
+contraction agst
+word almost 1-123-134
+contraction alm
+word already 1-123-1235
+contraction alr
+word also 1-123
+contraction al
+word although 1-123-1456
+word alth =
+word altogether 1-123-2345
+contraction alt
+word always 1-123-2456
+contraction alw
+word as 1356
+contraction as
+word be 23
+contraction be
+word because 23-14
+word bec =
+word before 23-124
+word bef =
+word behind 23-125
+word beh =
+word below 23-123
+word bel =
+word beneath 23-1345
+sufword beside 23-234
+sufword bes =
+sufword between 23-2345
+sufword bet =
+word beyond 23-13456
+word bey =
+sufword blind 12-123
+contraction bl
+sufword braille 12-1235-123
+contraction brl
+word but 12
+contraction b
+word can 14
+contraction c
+sufword conceive 25-14-1236
+contraction concv
+sufword conceiving 25-14-1236-1245
+contraction concvg
+word could 14-145
+contraction cd
+word cannot 456-14
+sufword character 5-16
+word children 16-1345
+word chn =
+always day 5-145
+always deceive 145-14-1236
+contraction dcv
+always deceiving 145-14-1236-1245
+contraction dcvg
+word declare 145-14-123
+contraction dcl
+word do 145
+contraction d
+word either 15-24
+contraction ei
+lowword enough 26
+contraction en
+word every 15
+contraction e
+always father 5-124
+always first 124-34
+always fst =
+word from 124
+contraction f
+always friend 124-1235
+contraction fr
+word go 1245
+contraction g
+always good 1245-145
+contraction gd
+word great 1245-1235-2345
+contraction grt
+word had 456-125
+word have 125
+contraction h
+always here 5-125
+word herself 125-12456-124
+contraction herf
+word him 125-134
+contraction hm
+word himself 125-134-124
+contraction hmf
+lowword his 236
+word i =
+sufword immediate 24-134-134
+contraction imm
+word it 1346
+contraction x
+word its 1346-234
+contraction xs
+word itself 1346-124
+contraction xf
+word just 245
+contraction j
+word knowledge 13
+contraction k
+always know 5-13
+sufword letter 123-1235
+contraction lr
+word like 123
+contraction l
+word little 123-123
+contraction ll
+sufword lord 5-123
+word more 134
+contraction m
+sufword mother 5-134
+sufword much 134-16
+contraction mch
+word must 134-34
+contraction mst
+word myself 134-13456-124
+contraction myf
+sufword name 5-1345
+sufword necessary 1345-15-14
+contraction nec
+word neither 1345-15-24
+contraction nei
+word not 1345
+contraction n
+word ourselves 1256-1235-1236-234
+contraction ourvs
+word paid 1234-145
+contraction pd
+word people 1234
+contraction p
+always perceive 1234-12456-14-1236
+contraction percv
+always perceiving 1234-12456-14-1236-1245
+contraction percvg
+word perhaps 1234-12456-125
+contraction perh
+sufword quick 12345-13
+contraction qk
+word quite 12345
+contraction q
+always question 5-12345
+word rather 1235
+contraction r
+always receive 1235-14-1236
+contraction rcv
+always receiving 1235-14-1236-1245
+contraction rcvg
+always rejoice 1235-245-14
+contraction rjc
+word said 234-145
+contraction sd
+word shall 146
+contraction sh
+always should 146-145
+contraction shd
+word so 234
+contraction s
+always some 5-234
+always spirit 456-234
+word still 34
+contraction st
+word such 234-16
+contraction sch
+word that 2345
+contraction t
+word this 1456
+contraction th
+word thyself 1456-13456-124
+contraction thyf
+word today 2345-145
+contraction td
+word tomorrow 2345-134
+contraction tm
+word tonight 2345-1345
+contraction tn
+word themselves 2346-134-1236-234
+contraction themvs
+always their 456-2346
+always there 5-2346
+word these 45-2346
+always through 5-1456
+always together 2345-1245-1235
+contraction tgr
+word those 45-1456
+word upon 45-136
+word us 136
+contraction u
+always under 5-136
+word very 1236
+contraction v
+lowword was 356
+word which 156
+contraction wh
+word will 2456
+contraction w
+lowword were 2356
+always where 5-156
+always with 23456
+always word 45-2456
+word whose 45-156
+always work 5-2456
+always would 2456-145
+contraction wd
+always world 456-2456
+word you 13456
+contraction y
+always young 5-13456
+word your 13456-1235
+contraction yr
+word yours 13456-1235-234
+contraction yrs
+word yourself 13456-1235-124
+contraction yrf
+word yourselves 13456-1235-1236-234
+contraction yrvs
+
+#Smaller contractions
+midendword ance 46-15
+midendword ence 56-15
+always ever 5-15
+always for 123456
+midendword ful 56-123
+midendword ing 346
+midendword ity 56-13456
+midendword less 46-234
+always many 456-134
+midendword ment 56-2345
+midendword eness 15-56-234
+midendword iness 24-56-234
+midendword ness 56-234
+midendword ong 56-1245
+midendword  ound 46-145
+midendword ount 46-2345
+always ought 5-1256
+always part 5-1234
+always right 5-1235
+midendword sion 46-1345
+always the 2346
+always time 5-2345
+midendword tion 56-1345
+
+#Smallist contractions
+always and 12346
+always ar 345
+midword bb 23
+midword cc 25
+always ch 16
+midword ea 2
+always ed 1246
+always en 26
+always er 12456
+midword ff 235
+midword gg 2356
+always gh 126
+always in 35
+word o =
+always of 12356
+always one 5-135
+always ou 1256
+always ow 246
+always sh 146
+always st 34
+always th 1456
+always wh 156
+
+#Disambiguation
+always ear 15-345
+
+endword 'd 3-145
+endword 'll 3-123-123
+endword 'm 3-134
+endword 're 3-1235-15
+endword 's 3-234
+endword 't 3-2345
+endword 've 3-1236-15
+
+# inline contraction of emoji descriptions
+emoji en
diff --git a/Tables/Contraction/en-us-g2.ctb b/Tables/Contraction/en-us-g2.ctb
new file mode 100644
index 0000000..4c2b0fc
--- /dev/null
+++ b/Tables/Contraction/en-us-g2.ctb
@@ -0,0 +1,1543 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - English (EBAE, grade 2)
+# Created by John Boyer <director@chpi.org>.
+
+class vowel aeiouyAEIOUY
+class consonant bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ
+class o oO
+class apostrophe '
+
+numsign 3456  number sign, just one operand
+letsign 56
+capsign 6
+begcaps 6-6
+endcaps 6-3
+# If you don't want capitalization comment out the preceding three lines.
+
+# the decimal digits
+always 1 1
+always 2 12
+always 3 14
+always 4 145
+always 5 15
+always 6 124
+always 7 1245
+always 8 125
+always 9 24
+always 0 245
+
+# the letter a
+lastlargesign a 1
+always a 1
+always A 1
+begword a- 1-36 a-goin'
+begword a-com 1-36-14-135-134 a-comin' a-coming
+always about 1-12
+contraction ab
+always above 1-12-1236
+contraction abv
+always according 1-14
+contraction ac
+always across 1-14-1235
+contraction acr
+always ae =
+endword aed 1-1246
+word aforesaid 1-123456-15-234-145
+before vowel begword after 1-124-2345-12456 afterimage
+sufword after 1-124 afterbirth
+contraction af
+sufword afternoon 1-124-1345 afternoons
+contraction afn
+always afterward 1-124-2456
+contraction afw
+word again 1-1245
+contraction ag
+sufword ag'in 1-1245-3-35 ag'inst
+word against 1-1245-34
+contraction agst
+endword ageries = messageries
+endword agery = savagery
+sufword airedale =
+midendword ally 6-13456
+word almost 1-123-134
+contraction alm
+word already 1-123-1235
+contraction alr
+word also 1-123
+contraction al
+word although 1-123-1456
+word alth =
+word altogether 1-123-2345
+contraction alt
+word always 1-123-2456
+contraction alw
+midendword ance 46-15
+largesign and 12346
+sufword anemone =
+midword angh = Shanghai
+begword antenn 1-1345-2345-26-1345 antenna
+begword anterio 1-1345-2345-12456-24-135 anterior
+word antigone =
+word antin 1-1345-2345-35 NY state senator Benjamin Antin
+word antins 1-1345-2345-35-234 the Antins
+sufword anting 1-1345-2345-346
+begword aqued = aqueduct
+always ar 345
+word aright 1-5-1235
+always arubber 1-1235-136-23-12456 indiarubber
+word as 1356
+contraction as
+sufword aspidistra 1-234-1234-24-145-24-34-1235-1
+sufword asshur 1-234-146-136-1235
+midendword ation 6-1345
+sufword audio = audiofile
+midendword aunder 1-136-1345-145-12456 saunders
+begword auto = autofocus
+
+# the letter b
+always b 12
+always B 12
+sufword bachelordom 12-1-16-15-123-135-1235-145-135-134
+always balone = abalone baloney
+begword barthole 12-345-1456-135-123-15 Bartholemy
+sufword bathorse =
+sufword battle = battleaxe
+word battled 12-1-2345-2345-123-1246
+sufword battler 12-1-2345-2345-123-12456 battlers
+midword bb 23
+midendword bble 12-3456
+lowword be 23
+begword be 23
+sufword be\s-\s 12-15-36-36
+word bea = Beatrice's nickname
+begword bea 12-2
+always bear 12-15-345
+begword beatif 23-1-2345-24-124
+begword beatit 23-1-2345-24-2345
+begword beatr 23-1-2345-1235 Beatrice
+begword becc 12-15-25 beccaccia
+always beck 12-15-14-13
+always bed 12-1246
+begword beda 23-145-1 bedazzle
+begword bede 23-145-15 bedevil
+word bede 12-1246-15 bede (a name)
+begword bedi 23-145-24 bedizzened
+begword bedra 23-145-1235-1 bedraggled
+word bee 12-15-15
+begword bee 12-15-15
+always been 12-15-26
+always beer 12-15-12456
+begword beff 12-15-235 beffana
+word beg =
+begword begg 12-15-2356
+word begs =
+begword beid = Beidermeier
+begword beig = beige
+word bein 23-24-1345
+word bein' 23-24-1345-3
+sufword beijing 12-15-24-245-346
+word beirut =
+sufword belch 12-15-123-16
+begword beld = Beldon
+begword belf = belfry, belfast
+begword belfor 12-15-123-123456 Belford
+begword belg = Belgium
+sufword belittle 23-123-123
+begword belk = Belkin
+always bell =
+begword belm = Belmont
+sufword belsen 12-15-123-234-26 Bergen-Belsen
+always belsh 12-15-123-146
+always belt =
+begword belv = belvedere
+begword belw = belwether
+always ben 12-26
+word benefic 23-1345-15-124-24-14
+sufword beneficence 23-1345-15-124-24-14-56-15
+sufword beneficent 23-1345-15-124-24-14-26-2345
+begword benev 23-1345-15-1236 benevolent
+begword benig 23-1345-24-1245 benign
+begword benigh 23-1345-24-126 benighted
+begword benu 23-1345-136 benumbed
+begword ber 12-12456
+begword bera 23-1235-1 berating
+begword bere 23-1235-15 bereft
+begword berea 23-1235-2 bereaved
+begword beren 12-12456-26 Berengaria, Berenice
+begword beres 12-12456-15-234 Beresford, Beresina
+begword berh 23-1235-125 berhymed
+begword beribb 23-1235-24-23 beribboned
+begword bering 23-1235-346 beringed (assume Bering with no suffix refers to the Sea and use "er")
+begword bero 23-1235-135 berobed
+begword beru 23-1235-136 beruffled
+always best 12-15-34
+begword besta 23-34-1 bestad bestain
+sufword bestar 23-34-345
+sufword bestead 23-34-2-145
+sufword bestick 23-34-24-14-13
+sufword bestir 23-34-24-1235
+sufword bestow 23-34-246
+begword bestr 23-34-1235 bestraddle bestreak
+begword bestu 23-34-136 bestuck bestudded
+always beth 12-15-1456 Bethany
+always bethe 12-15-2346 Bethel (Hans) Bethe
+sufword bethink 23-1456-35-13
+sufword bethought 23-1456-5-1256
+always bets =
+always bett =
+sufword betther 12-15-2345-2346-1235 Irish dialect for Better
+sufword betz = Betz
+begword beu = Beula Beulah
+begword beuncl 23-136-1345-14-123 beuncled
+sufword bev = bevies
+begword bever 12-5-15 Beverly beverage
+always bio =
+midendword bious 12-24-1256-234 dubious
+joinword by 356
+word by-and-by 12-13456-36-12346-36-12-13456
+always because 23-14
+word bec =
+always before 23-124
+word bef =
+always behind 23-125
+word behring 12-15-125-1235-346
+word beh =
+word bel =
+begword beln = Belnick
+sufword below 23-123
+always beneath 23-1345
+sufword bertheau 12-12456-1456-2-136
+always beside 23-234
+word bes =
+sufword bess = Bessie
+always between 23-2345
+word bet =
+always beyond 23-13456
+word bey =
+sufword binational 12-24-1345-6-1345-1-123
+begword binod = binodal
+begword binom = binomial
+sufword blake = Blakeney
+word ble 3456 amia- ble, but not amia- bleness
+midendword ble 3456
+midendword bleau 12-123-2-136 tableau
+midendword bleed 12-123-15-1246 nosebleed
+always bless 12-46-234 joblessness
+before vowel always blind 12-123-35-145
+always blind 12-123
+contraction bl
+begword bluenos = bluenosed
+sufword boat = boathook
+sufword boende 12-135-26-145-15 town (Republic of Congo)
+sufword boer = boerhaave
+begword bonedr 12-5-135-145-1235 bonedry
+sufword boneset 12-5-135-234-15-2345
+begword bottlenos = bottlenose dolphin
+always braille 12-1235-123
+contraction brl
+sufword brigham =
+sufword burlingame 12-136-1235-123-346-1-134-15
+word but 12
+sufword bysshe 12-13456-234-146-15 Percy Bysshe Shelley
+
+# the letter c
+always c 14
+always C 14
+sufword cajones =
+begword calcedon 14-1-123-14-1246-135-1345 calcedony calcedonies
+begword cart 14-345-2345 carthorse
+word carthage 14-345-1456-1-1245-15
+midword cch 14-16
+sufword chalcedon 16-1-123-14-1246-135-1345 chalcedony chalcedonies
+sufword cheat 16-2-2345 Cheatham
+always chlor 16-123-135-1235 dichlordiphenylethylene, chloroform
+sufword christmas 16-1235-24-34-134-1-234 Christmastide
+sufword ciboneyes =
+sufword citron = citronella
+sufword clemenceau 14-123-15-134-26-14-2-136
+sufword clothes 14-123-135-2346-234 clotheshorse
+sufword coat = coathanger
+sufword coed 14-135-1246
+begword coen 14-135-26 coenzyme (Bana)
+begword cofac = cofactor
+always cofound 14-135-124-46-145
+word cojones =
+sufword colonel =
+begword com 36
+sufword comedic 36-1246-24-14 comedically
+begword comen 14-135-134-26 John Amos Comenius
+word comin 36-24-1345
+sufword comin' 36-24-1345-3 comin's and goin's; I'm--comin' home
+begword comingl 14-135-134-346-123 comingle
+sufword common 36-134-135-1345 commonest
+begword con 25
+sufword conan =
+sufword conceive 25-14-1236
+word concv =
+word concvd =
+word concvr =
+word concvs =
+word concvst =
+word concvth =
+sufword conceiving 25-14-1236-1245
+word concvg =
+word conch 14-135-1345-16
+word conches 14-135-1345-16-15-234
+sufword cone 14-5-135
+word coned 14-135-1345-1246
+sufword conelrad =
+sufword coney =
+word conies =
+sufword conk =
+word cons 14-135-1345-234
+sufword conundrum = 
+word cony =
+sufword coronel =
+always could 14-145
+contraction cd
+sufword cowork 14-135-5-2456
+begword cowrit = cowrite
+begword cowrot = cowrote
+midword cc 25
+word can 14
+always cannot 456-14
+word ch =
+always ch 16
+always character 5-16
+sufword chatham 16-1-2345-125-1-134
+always chemo 16-15-134-135
+word child 16
+always children 16-1345
+word chn =
+sufword chisholm 16-24-234-125-135-123-134
+sufword clever 14-123-5-15 cleverest
+sufword credo 14-1235-1246-135 credos
+sufword cross = crosstree
+
+# the letter d
+always d 145
+always D 145
+word d'you 145-3-13456-1256
+begword dachs 145-1-16-234 dachshund
+begword dared 145-345-15-145 daredevil
+always day 5-145
+word day-to-day 5-145-36-2345-135-36-5-145
+begword deact = deactivation
+begword deall = deallocate
+begword decarb 145-15-14-345-12
+always deceive 145-14-1236
+contraction dcv
+contraction dcvd
+contraction dcvr
+contraction dcvs
+word dcvst =
+word dcvth =
+always deceiving 145-14-1236-1245
+contraction dcvg
+always declare 145-14-123
+contraction dcl
+contraction dcld
+contraction dclr
+contraction dcls
+word dclst =
+word dclth =
+always declaring 145-14-123-1245
+contraction dclg
+begword deref = dereferencing
+begword dereg = deregulation
+word deshabille =
+midword dd 256
+midendword ddamn =
+midendword dday 145-5-145 midday
+begword dedic 145-1246-24-14 dedicated
+always dedu = nondeductible
+sufword denational 145-15-1345-6-1345-1-123 denationalization
+begword denaz = denazification
+begword deno = denote
+always denom =
+begword denou 145-15-1345-1256 denounce
+begword denu = denunciation
+begword dera = derail
+begword deri 145-15-1235-24
+begword dero = derogatory
+sufword dew = Dewhurst
+begword dingh 145-35-126 dinghy
+begword dinu 145-24-1345-136
+begword dis 256
+word disc =
+word discs =
+sufword dish 145-24-146
+begword disha 256-125-1 dishabille
+begword disharmon 256-125-345-134-135-1345 disharmony
+begword dishear 256-125-15-345 disheartened
+begword disho 256-125-135 dishonor
+begword dishone 256-125-5-135 dishonest
+begword disin 256-35 disingenuous
+sufword disk =
+sufword dispirit 145-24-456-234 dispirited
+midword dist = contradistinction
+midendword distic 145-24-34-24-14 Methodistic Methodistical
+endword dists 145-24-34-234 Methodists
+begword disul = disulfide
+word do 145
+before o begword dogg =
+word doggo 145-135-2356-135
+begword donega = Donegalers, Donegan
+always dumb = dumbbell
+begword dumble 145-136-134-3456 dumbledor
+sufword dumfries = Dumfriesshire
+
+# the letter e
+always e 15
+always E 15
+always e'en 15-3-26 Hallowe'en
+midword ea 2
+midword eabil = interchangeability
+always eable 15-1-3456
+endword eably = noticeably
+midendword eage = mileage
+sufword eagle = eaglenose
+sufword eagled 15-1-1245-123-1246
+midendword eager 2-1245-12456 meager
+always eally 15-6-13456
+midendword eance 15-46-15 vengeance
+midendword eand 15-12346 meander
+always eation 15-6-1345
+always ear 15-345
+sufword hideaw = hideaway
+always ed 1246
+sufword edacious 15-145-1-14-24-1256-234 edaciousness
+always edic = edict Benedict
+sufword edition 15-145-24-56-1345 editions
+midword edo 15-145-135
+midendword edown 15-145-246-1345 facedown
+sufword torpedo 2345-135-1235-1234-1246-135
+sufword tuxedo 2345-136-1346-1246-135
+always edraw =
+always eever 15-15-1236-12456 Cheever
+word either 15-24
+contraction ei
+word en =
+always en 26
+always ename 15-5-1345
+sufword enamel 26-1-134-15-123 enameled
+midendword ence 56-15
+always enceph 26-14-15-1234-125 electroencephalogram
+begword encyclopedi 26-14-13456-14-123-135-1234-1246-24 encyclopedic
+sufword endow 26-145-246
+always eneck = bottleneck
+midendword eness 15-56-234 closeness
+begword enor = enormous
+begword enou 15-1345-1256
+lowword enough 26
+word enough 26-1256-126
+sufword enough\s-\s 26-1256-126-36-36
+midendword entiment 26-2345-24-56-2345 sentimental
+begword enu 15-1345-136
+begword equino = equinox
+always er 12456
+begword era =
+word eras 12456-1-234
+begword erec = erect 
+begword ero 15-1235-135
+begword erog 12456-135-1245 erogenous
+word eros 12456-135-234 Down Eros, up Mars!
+sufword erotic 12456-135-2345-24-14
+midendword eroom = storeroom
+begword eru = erupt
+begword erudi 12456-136-145-24 erudite
+midendword onesque = Runyonesques
+word ethereally 15-2346-1235-15-6-13456
+always ever 5-15
+sufword eversion 15-1236-12456-46-1345 eversions
+sufword evert 15-1236-12456-2345 everted
+word every 15
+sufword eyedrop = eyedropper
+
+# the letter f
+always f 124
+always F 124
+sufword falcon = falconer
+sufword fandom =
+midword ff 235
+always father 5-124
+sufword fed 124-1246 fedora
+always fein 124-15-35 Feingold
+always first 124-34
+word fst =
+always fever 124-15-1236-12456
+always ffor 124-123456
+sufword fiance =
+largesign for 123456
+always fore 123456-15
+begword forens 123456-26-234 forensic
+always forever 123456-5-15 forevermore
+always foot =
+sufword forbes 123456-12-15-234 Forbestown
+word from 124
+before vowel always friend 124-1235-24-26-145
+always friend 124-1235
+contraction fr
+word fruity =
+midendword ful 56-123
+endword full =
+always funder 124-136-1345-145-12456
+
+# the letter g
+always g 1245
+always G 1245
+word gainsaid 1245-1-35-234-145
+begword genealog 1245-26-15-1-123-135-1245 genealogy
+begword geo = geoengineering
+sufword geoff 1245-15-12356-124 Geoffrey
+midword gg 2356
+always gh 126
+endword ngham = Langham
+always ghead 1245-125-2-145
+always gheart 1245-125-15-345-2345
+midendword ghill = dunghill
+midendword ghof 1245-125-12356 Berghoffer
+midendword ghorn = bighorn
+always ghouse 1245-125-1256-234-15
+always ghz = (gigahertz)
+sufword gingold 1245-35-1245-135-123-145
+begword givea = giveaway
+sufword gnome = gnomedb
+word go 1245
+begword goath = goatherd
+sufword goddaughter 1245-135-145-145-1-136-126-2345-12456
+sufword goddess 1245-135-256-15-234-234 goddessship
+sufword good 1245-145
+contraction gd
+sufword goshawk =
+sufword gosherd 1245-135-234-125-12456-145
+midendword ingrad 35-1245-1235-1-145 Leningrad, Stalingrad
+begword grapea = grapeade
+begword gravedi = gravedigger
+begword gravero = graverobbing
+always great 1245-1235-2345
+contraction grt
+sufword guantanamera 1245-136-1-1345-2345-1-1345-1-134-12456-1
+sufword guess = guesstimate
+sufword guenever 1245-136-26-15-1236-12456 Guenevere
+sufword guinever 1245-136-35-15-1236-12456 Guinevere
+
+# the letter h
+always h 125
+always H 125
+always had 456-125
+begword hadd 125-1-256 haddock
+sufword hade = hadean
+sufword handsome 125-12346-5-234 handsomer
+sufword hadr = hadrian 
+word have 125
+always headd 125-2-145-145 headdress
+sufword headmistress 125-2-145-134-24-34-1235-15-234-234 headmistressship
+sufword hedgerow 125-1246-1245-15-1235-246 hedgerow's
+always here 5-125
+word hereafter 5-125-1-124
+sufword hereford 125-12456-15-123456-145
+word hereinafter 5-125-35-1-124
+always hered 125-12456-1246
+always heren 125-12456-26
+midendword herence 125-12456-56-15 adherence
+always herer 125-12456-12456
+begword heres 125-12456-15-234 heresy
+always heret 125-12456-15-2345
+word hereto 5-125-2345-135
+word heretofore 5-125-2345-135-123456-15
+word herself 125-12456-124
+word herf =
+word him 125-134
+contraction hm
+sufword hmm =
+word himself 125-134-124
+contraction hmf
+lowword his 236
+sufword his\s-\s 125-24-234-36-36
+word holloware 125-135-123-123-135-2456-345-15
+sufword horse = horseradish
+word horsed 125-135-1235-234-1246
+sufword horser 125-135-1235-234-12456 nine-horsers
+begword horsera = horserace
+sufword houghton 125-1256-126-2345-135-1345
+begword housedres 125-1256-234-15-145-1235-15-234 housedressed
+sufword humidistat 125-136-134-24-145-24-34-1-2345
+always hydro =
+begword hypsomet = hypsometrical
+
+# the letter i
+word i 24
+always i 24
+always I 24
+word ibuprofen 24-12-136-1234-1235-12356-26
+midendword iever 24-15-1236-12456
+always immediate 24-134-134
+contraction imm
+begword immuno = immunofluorescence
+lowword in 35
+word in =
+always in 35
+sufword in\s-\s 24-1345-36-36
+begword incon 35-14-135-1345 incongruous
+begword indis 35-145-24-234 indistinct
+begword inera 35-15-1235-1 ineradicable, inerasable, inerasible
+begword iness 35-15-234-234 inessential, inessive
+always iness 24-56-234
+word ing 346 such as after a hyphen
+midendword ing 346
+midword ingal 35-1245-1-123 farthingale martingale nightingale
+always ingar 35-1245-345 Weingarten
+sufword ingham 35-1245-125-1-134
+midendword ingism 35-1245-24-234-134 meningism
+midword ingit 35-1245-24-2345 meningitis
+midendword ingite 346-24-2345-15 wyomingite
+begword ingle 35-1245-123-15 inglenook
+sufword isinglass 24-234-35-1245-123-1-234-234
+joinword into 35-235
+sufword iou =
+begword irrevers 24-1235-1235-15-1236-12456-234 irreversible
+always isomer 24-234-135-134-12456
+word it 1346
+word its 1346-234
+contraction xs
+word itself 1346-124
+contraction xf
+midendword ity 56-13456
+
+# the letter j
+always j 245
+always J 245
+sufword jihad = jihadis
+always jone 245-5-135 jonesing Jonette
+word just 245
+begword jungleri 245-136-1345-1245-123-15-1235-24 jungleridden
+
+# the letter k
+always k 13
+always K 13
+sufword kettledrum =
+sufword kinross 13-35-1235-135-234-234 Kinrossshire
+word kneedeep =
+always know 5-13
+sufword knownothing 5-13-1345-135-1456-346 knownothingism
+word knowledge 13
+sufword knuckledust 13-1345-136-14-13-123-15-145-136-34 knuckleduster knuckledusting
+sufword kurdistan 13-136-1235-145-24-34-1-1345 Kurdistani
+
+# the letter l
+always l 123
+always L 123
+sufword lameduck =
+begword leatherett 123-2-2346-1235-15-2345-2345 leatherette
+midendword less 46-234
+always lesson = unlessoned
+always letter 123-1235
+contraction lr
+word like 123
+begword limea = limeade
+word lingerie 123-35-1245-12456-24-15
+word lionel =
+sufword little 123-123
+contraction ll
+always lord 5-123
+begword lordos = lordosis lordoses
+sufword lordotic =
+sufword lucknow 123-136-14-13-1345-246
+
+# the letter m
+always m 134
+always M 134
+begword maha = maharaja, maharani
+sufword mahoney = 
+sufword mailorder 134-1-24-123-135-1235-145-12456
+begword maneat 134-1-1345-15-1-2345 maneater, maneating
+always many 456-134
+begword mc =
+always medic 134-1246-24-14 medicare
+begword menager 134-26-1-1245-12456 menagery menageries
+midendword ment 56-2345
+midword menth 134-26-1456 Blumenthal
+always mention 134-26-56-1345
+sufword merioneth 134-12456-24-135-1345-15-1456 Merionethshire
+sufword messagerie =
+sufword midafternoon 134-24-145-1-124-1345
+sufword middorsal =
+always mideast 134-24-145-15-1-34
+begword midwifer = midwifery
+sufword milliner 134-24-123-123-35-12456
+word milling 134-24-123-123-346
+sufword mishallow 134-24-234-125-1-123-123-246
+begword mishand 134-24-234-125-12346 mishandled
+always mishap =
+sufword mishear 134-24-234-125-15-345 misheard
+sufword mishit =
+begword missh 134-24-234-146 misshapen
+sufword misshood =
+always mistak = mistake
+word mistook =
+sufword mistrain 134-24-234-2345-1235-1-35 mistrained
+begword mistran = mistranslation
+sufword mistreat 134-24-234-2345-1235-2-2345
+sufword mistrial = mistrials
+begword mistru = mistrust
+begword misty = mistyped
+word monetary 134-5-135-2345-345-13456
+word mongeese =
+sufword mongoose =
+sufword monteverdi 134-135-1345-2345-15-1236-12456-145-24 Monteverdi's
+word more 134
+word more'n 134-135-1235-15-3-1345
+word more's =
+always mother 5-134
+always much 134-16
+word mch =
+sufword mucha 134-136-16-1 muchacho muchas
+sufword mucho 134-136-16-135
+word must 134-34
+word mst =
+begword musti 134-34-24 mustiness
+word mustn 134-34-1345 mustn't
+word musty 134-34-13456
+begword myo = myofibroblasts
+word myself 134-13456-124
+contraction myf
+
+# the letter n
+always n 1345
+always N 1345
+always name 5-1345
+always namee = McNamee
+always nament 1345-1-56-2345 tournament
+always namese =
+endnum nd = (second)
+always necessary 1345-15-14
+contraction nec
+word neither 1345-15-24
+contraction nei
+begword neof = neofascist
+midendword ness 56-234
+sufword nevers 1345-15-1236-12456-234 Louis Nevers
+begword new =
+begword news = newshawk
+always nighth 1345-24-126-2345-125 nighthawk knighthood
+begword non =
+word none 1345-5-135
+word nones 1345-5-135-234
+word nonesuch 1345-5-135-234-16
+word nonetheless 1345-5-135-2346-46-234
+begword nonrevers 1345-135-1245-1235-15-1236-12456-234 nonreversible
+word noone 1345-135-5-135
+begword nosediv = nosedive
+word not 1345
+word noways =
+sufword nowhere 1345-135-5-156
+word nowise =
+begword nuth 1345-136-2345-125 nuthatch nuthouse
+word nuther 1345-136-2346-1235 (dialect)
+
+# the letter o
+word o 135
+always o 135
+always O 135
+begword oe = Oedipus
+midword oed = Schroeder
+always oen = Phoenix
+largesign of 12356
+midendword ofar 135-124-345 insofar
+always ofold = twofold
+midword ofor 135-123456 thermoform
+word oleaginous 135-123-15-1-1245-35-1256-234
+sufword onegin 135-1345-15-1245-35 Eugene Onegin's grandfather
+always onesi = Indonesia
+sufword onesie 5-135-234-24-15 (baby clothing)
+midendword oness 135-56-234
+midendword oneer 135-1345-15-12456 pioneer
+midendword oned 135-1345-1246 honed
+always one 5-135
+always aione = zabaione
+midendword mione = Hermione
+sufword oneiric =
+always onent 135-1345-26-2345
+midendword oneous 135-1345-15-1256-234 erroneous
+always oner 135-1345-12456
+midendword onese = Cantonese
+word oneself 5-135-124
+word onef =
+midendword oness 135-56-234 Deaconess
+midendword onet = phonetics bayonet
+endword oneth 135-1345-15-1456 fashioneth
+endword onez = Ordonez
+midendword ong 56-1245
+midendword ongen 135-1345-1245-26 uncongenial
+midword ooen 135-135-26 constitooency (dialect: Buchan)
+always oon = sooner
+begword orangea = orangeade
+begword oranger = orangery, -ies
+begword orthopedi 135-1235-1456-135-1234-1246-24 orthopedics
+word ou =
+always ou 1256
+midendword ound 46-145
+midendword ount 46-2345
+always ourselves 1256-1235-1236-234
+word ourvs =
+word out 1256
+begword outdist 1256-2345-145-24-234-2345 outdistance
+always ought 5-1256
+begword outh 1256-2345-125 outheld
+begword overea 135-1236-12456-15-1 overeat overeager overeasy
+begword overear 135-1236-12456-15-345 overearnest
+always ow 246
+word o'clock 135-3-14
+
+# the letter p
+always p 1234
+always P 1234
+always paid 1234-145
+contraction pd
+begword painst 1234-1-35-234-2345 painstake
+begword palingen 1234-1-123-35-1245-26 palingenesis (new birth)
+sufword panther 1234-1-1345-2346-1235 pantheresque
+always part 5-1234
+begword parta 1234-345-2345-1 partake
+begword parthe 1234-345-2346 Parthenon, Parthenia
+word parthy 1234-345-1456-13456 diminutive of Parthenia
+begword parto 1234-345-2345-135
+begword peacen 1234-2-14-15-1345 peacenik
+begword pedic 1234-1246-24-14 pedicure, pedicab, pedicle
+begword pedo 1234-1246-135 pedometer, pedobaptist
+sufword peebles 1234-15-15-3456-234 Peeblesshire
+word people 1234
+always perceive 1234-12456-14-1236
+word percv =
+word percvd =
+word percvr =
+word percvs =
+word percvst =
+word percvth =
+always perceiving 1234-12456-14-1236-1245
+word percvg =
+always perhaps 1234-12456-125
+word perh =
+word perin 1234-12456-35
+word perin's 1234-12456-35-3-234
+word perins 1234-12456-35-234
+begword persever 1234-12456-234-15-1236-12456 persevere
+sufword petar 1234-15-2345-345 petard
+always pher 1234-125-12456 cyphered
+word phoneme =
+word phonemes =
+word phoney =
+begword phonemi = phonemic
+begword phospho = phosphofructokinase
+sufword picoult 1234-24-14-1256-123-2345 Jodi Picoult, an author
+begword pinea 1234-35-15-1 pineapple
+word pineal 1234-35-2-123
+sufword pipedream 1234-24-1234-15-145-1235-2-134
+sufword poleax =
+begword portho = porthole
+sufword porthos 1234-135-1235-1456-135-234 (Dumas) Porthosesque
+begword poth = pothole pothook pothouse
+sufword pother 1234-135-2346-1235
+sufword potherb 1234-135-2345-125-12456-12
+begword pre =
+sufword preace 1234-1235-2-14-15 obsolete for press
+always preach 1234-1235-2-16
+sufword preakness 1234-1235-2-13-56-234
+word pred 1234-1235-1246 liquid pred: prednisone trade name
+sufword predator 1234-1235-1246-1-2345-135-1235
+sufword predecessor 1234-1235-1246-15-14-15-234-234-135-1235
+begword predicat 1234-1235-1246-24-14-1-2345 predicated
+sufword predication 1234-1235-1246-24-14-6-1345
+sufword predikant 1234-1235-1246-24-13-1-1345-2345
+begword predn 1234-1235-1246-1345 prednisone prednisolone
+sufword predsolan 1234-1235-1246-234-135-123-1-1345
+begword preer = preerect
+begword prefulg = prefulgence prefulgent
+word pren 1234-1235-26 a surname
+begword prend 1234-1235-26-145 prendergast
+sufword prensa 1234-1235-26-234-1
+sufword prent 1234-1235-26-2345
+sufword prepsychedelic 1234-1235-15-1234-234-13456-16-15-145-15-123-24-14
+always prof =
+word prof 1234-1235-12356
+sufword profanation 1234-1235-12356-1-1345-6-1345
+sufword proff 1234-1235-12356-124 proffer
+begword profliga 1234-1235-12356-123-24-1245-1 profligate profligacy
+word profs 1234-1235-12356-234
+always profit 1234-1235-12356-24-2345
+begword proto = protoenchanter (Cervantes)
+sufword prounion = prounionist
+begword psyched 1234-234-13456-16-15-145 psychedelic
+word pulseaudio =
+
+# the letter q
+always q 12345
+always Q 12345
+always quick 12345-13
+contraction qk
+word quite 12345
+always question 5-12345
+
+# the letter r
+always r 1235
+always R 1235
+sufword radio = radiofrequencies
+sufword rafter 1235-1-124-2345-12456
+word rather 1235
+begword ratho = rathole
+sufword raw = rawhide
+endnum rd = (third)
+begword reab = reabsorbed
+begword reacc 1235-15-1-25 reaccelerate
+always reach 1235-2-16
+begword reachiev 1235-15-1-16-24-15-1236 reachieving
+begword reacq 1235-15-1-14-12345 reacquire reacquaint
+always react =
+always reaction 1235-15-1-14-56-1345
+sufword readapt =
+word readd =
+begword readd 1235-15-1-256 readdressing
+begword readj = readjust
+begword readm = readmit
+begword reado = readoption readorn
+sufword readout 1235-2-145-1256-2345
+begword readv = readvance readvocate
+begword reaff 1235-15-1-235
+always reagent 1235-15-1-1245-26-2345
+begword reagg 1235-15-1-2356 reaggregated
+begword realig = realign
+begword reallo = reallocate 
+sufword realter 1235-15-1-123-2345-12456 realteration
+begword rean = reanalyze, reanimate
+begword reapp = reappear
+begword reasc 1235-15-1-234-14 reascend
+begword reass 1235-15-1-234-234
+begword reatt = reattach
+begword reau = reauthorization
+begword reaw = reawaken
+begword redat = redated
+begword rede =
+begmidword redee = unredeemable coredeemed
+begword redey 1235-1246-15-13456 redeyed
+begword redi =
+word redo =
+begword redol 1235-1246-135-123 redolent
+word redone 1235-15-145-5-135
+begword redou 1235-15-145-1256 redouble redoubt 
+sufword redound 1235-15-145-46-145
+always redu = reduce redundant reduplicate
+always redul 1235-1246-136-123 incredulous
+begword redy = redyed
+always receive 1235-14-1236
+contraction rcv
+contraction rcvd
+contraction rcvr
+contraction rcvs
+word rcvst =
+word rcvth =
+always receiving 1235-14-1236-1245
+contraction rcvg
+begword redis = redistribute
+begword redr = redress
+begword redro 1235-1246-1235-135 redroot redrock
+sufword redrove =
+begword redru 1235-1246-1235-136 redruthite
+begword refulg = refulgent refugence
+always rejoice 1235-245-14
+contraction rjc
+contraction rjcd
+contraction rjcr
+contraction rjcs
+word rjcst =
+word rjcth =
+always rejoicing 1235-245-14-1245
+contraction rjcg
+sufword rejone = rejoneador
+sufword renaming 1235-15-1345-1-134-346 renamings
+begword renational 1235-15-1345-6-1345-1-123 renationalise renationalization
+begword rene = renegotiate
+begword renegad 1235-26-15-1245-1-145 renegade
+sufword renfrew 1235-26-124-1235-15-2456 Renfrewshire
+begword reni = renig
+begword renom = renominate
+begword renou 1235-15-1345-1256 renounce
+sufword renown 1235-15-1345-246-1345 renowned
+begword renu = renunciation
+begword rer 1235-15-1235 reread
+begword rever 1235-15-1236-12456 revere
+begword reveren 1235-5-15-26 reverent
+sufword reverence 1235-5-15-56-15 reverence
+sufword reverie 1235-5-15-24-15
+begword rh = Rhadamanthus
+always rhein 1235-125-15-35
+word riflery =
+always right 5-1235
+sufword roseann =
+
+# the letter s
+endnum s = 40s (no letter sign)
+always s 234
+always S 234
+begword saddlenos 234-1-256-123-15-1345-135-234 saddlenose
+word said 234-145
+contraction sd
+sufword salmon = salmonella
+sufword saw = sawhorse
+sufword scheherezade 234-16-15-125-12456-15-1356-1-145-15 alternate spelling of Scheherazade
+sufword screw = screwhole
+always seda =
+sufword sedan 234-1246-1-1345
+always sedation 234-15-145-6-1345
+always sedativ 234-1246-1-2345-24-1236
+always sedi =
+always sediment 234-1246-24-56-2345
+always sedu =
+begword sedul 234-1246-136-123 sedulous
+sufword sedum 234-1246-136-134
+always severe 234-15-1236-12456-15 oversevere severely
+always severed 234-5-15-1246 dissevered
+word severer 234-15-1236-12456-12456 severe-r (could also be sever-er)
+word severers 234-5-15-12456-234
+always severit 234-15-1236-12456-24-2345 severities
+always severity 234-15-1236-12456-56-13456
+word shall 146
+word sh =
+always sh 146
+always shaus = In German names
+word shoofly 146-135-135-124-123-13456 shoofly pie
+sufword short 146-135-1235-2345 shorthorn
+sufword shoshone 146-135-146-135-1345-15 Shoshonean language
+always should 146-145
+word shd =
+always shoulder 146-1256-123-145-12456
+begword side = sidenote
+sufword sided 234-24-145-1246
+sufword sider 234-24-145-12456 sidereal siderite
+sufword sing 234-346 singalong
+midendword sion 46-1345
+begword skedaddl 234-13-15-145-1-256-123 skedaddling
+begword stevedor 34-15-1236-1246-135-1235 stevedoring
+midendword stion 234-56-1345
+word smithereens 234-134-24-2346-1235-15-26-234
+word so 234
+begword solo = soloensis (anthropology)
+always some 5-234
+midendword somed 234-135-134-1246 ransomed
+always somer 234-135-134-12456 somersault 
+always someter 234-135-134-15-2345-12456 gasometer 
+always somever 234-135-134-5-15
+sufword sparerib 234-1234-345-15-1235-24-12
+begword speakeas 234-1234-2-13-15-1-234 speakeasy
+sufword speed 234-1234-15-1246 speedometer
+always spirit 456-234
+begword spreadeagl 234-1234-1235-2-145-15-1-1245-123 spreadeagled
+sufword squall = squally
+midendword ssword 234-234-45-2456 crossword 
+endnum st 34 (first)
+word st =
+word st. 34-256 Saint Street
+always st 34
+begword stagh 34-1-1245-125 staghound staghunt
+always sth 234-1456
+midendword sthole 34-125-135-123-15 blasthole
+always sthe 234-2346
+midendword sthead 34-125-2-145 masthead
+midendword sthof 34-125-12356 Westhoff
+always sthood 34-125-135-135-145 priesthood
+word still 34
+always stime 234-5-2345
+midendword stown 234-2345-246-1345 Pickstown
+begword styro 34-13456-1235-135 styrofoam
+always shead 234-125-2-145
+always sheart 234-125-15-345-2345
+always shouse 234-125-1256-234-15
+always ssh =
+always shood =
+word such 234-16
+endword such 234-16 nonesuch somesuch
+before consonant sufword such 234-16
+word sch =
+sufword schofield 234-16-135-124-24-15-123-145
+midword sthous 34-125-1256-234 guesthouse oasthouse masthouse
+sufword straw 34-1235-1-2456 strawhat
+begword supersed 234-136-1234-12456-234-1246 superseding
+always sword =
+sufword symoens 234-13456-134-135-26-234 (an author)
+
+# the letter t
+always t 2345
+always T 2345
+sufword takeaway =
+sufword tearoom 2345-2-1235-135-135-134 tearooms
+endnum th 1456 (fourth, fifth, ...)
+word th =
+always th 1456
+always thand 2345-125-12346 shorthand
+word that 2345
+always theap 2345-125-2-1234 antheap
+midendword theresque 2346-1235-15-234-12345-136-15 Wertheresque
+always thill 2345-125-24-123-123 anthill
+before apostrophe word this 1456-24-234 this'll
+word this 1456
+midendword thof 2345-125-12356 Richthofen
+midendword thole = bolthole butthole cathole knothole
+begword threedim 1456-1235-15-15-145-24-134 threedimensional
+word thyself 1456-13456-124
+word thyf =
+always tnam =
+joinword to 235
+word today 2345-145
+word to-day 2345-145
+contraction td
+word tomorrow 2345-134
+word to-morrow 2345-134
+contraction tm
+word tonight 2345-1345
+word to-night 2345-1345
+contraction tn
+always thead 2345-125-2-145
+always theast 1456-15-1-34
+always theart 2345-125-15-345-2345
+always thouse 2345-125-1256-234-15
+always thoused 2345-125-1256-234-1246 hothoused, penthoused
+always thousing 2345-125-1256-234-346 hothousing
+lastlargesign the 2346
+word themselves 2346-134-1236-234
+word themvs 1456-15-134-1236-234
+always their 456-2346
+always thence 1456-56-15
+always there 5-2346
+word thereafter 5-2346-1-124
+word thereinafter 5-2346-35-1-124
+midendword thereal 2346-1235-2-123 ethereal
+word thereupon 5-2346-45-136
+always thered 2346-1235-1246
+always therer 2346-1235-12456
+begword theres 2346-1235-15-234 theresa therese 
+always thood =
+midendword tion 56-1345
+always time 5-2345
+midendword timed 1245-24-134-1246
+midendword timer 2345-24-134-12456
+midendword timet = altimeter
+word these 45-2346
+always through 5-1456
+always together 2345-1245-1235
+contraction tgr
+word those 45-1456
+begword trans = transtype
+sufword tranship 2345-1235-1-1345-146-24-1234
+begword trinod = trinodal
+begword trinom = trinomial
+begword tubenos = tubenosed
+sufword tuberose =
+begword tweedled 2345-2456-15-1246-123-15-145 tweedledee
+begword typea = typeahead
+
+# the letter u
+always u 136
+always U 136
+begword un = unameliorated
+begword unb = unblemished
+begword underea 5-136-15-1 undereat
+begword underear 5-136-15-345
+begword undis = undisturbed
+begword unea = uneaten uneasy
+begword unear 136-1345-15-345 unearth unearned
+begword unful = unfulfilled
+word upon 45-136 Dupont
+word us 136
+sufword ussher 136-234-146-12456 James Ussher
+always under 5-136
+word unsaid 136-1345-234-145
+
+# the letter v
+always v 1236
+always V 1236
+begword vaing 1236-1-35-1245 vainglory
+word vandyke =
+word very 1236
+begword vice = viceroy
+sufword vicenza 1236-24-14-26-1356-1
+sufword video = videofile
+
+# the letter w
+always w 2456
+always W 2456
+lowword was 356
+sufword was\s-\s 2456-1-234-36-36
+word wh =
+always wh 156
+word whaddaya 156-1-256-1-13456-1
+midendword whart 2456-125-345-2345 Newhart
+before apostrophe word which 156-24-16 which'll
+word which 156
+midendword whouse 2456-125-1256-234-15 Newhouse
+begword widea = wideawake
+word wikinews =
+word will 2456
+lowword were 2356
+sufword were\s-\s 2456-12456-15-36-36
+always where 5-156
+word whereafter 5-156-1-124
+word whereupon 5-156-45-136
+word wherever 156-12456-5-15
+sufword wingate 2456-35-1245-1-2345-15
+sufword wiseacre =
+largesign with 23456
+always word 45-2456
+word whose 45-156
+always work 5-2456
+always would 2456-145
+contraction wd
+always world 456-2456
+
+# the letter x
+always x 1346
+always X 1346
+
+# the letter y
+always y 13456
+always Y 13456
+word you 13456
+always young 5-13456
+word your 13456-1235
+contraction yr
+word yours 13456-1235-234
+contraction yrs
+word yourself 13456-1235-124
+contraction yrf
+word yourselves 13456-1235-1236-234
+contraction yrvs
+
+# the letter z
+always z 1356
+always Z 1356
+
+# heim (a German syllable) should always be literal
+midendword cheim =
+midendword gheim =
+midendword sheim = Hergesheimer Gundersheimer Rudisheim Weinsheimer
+midendword theim = Altheimer (sic)
+midendword wheim =
+midendword stheim 34-125-15-24-134 Westheimer
+
+always agosom = phagosome
+always ibosom = ribosome
+always omosom = chromosome
+
+# Système International Prefixes
+begword yotta 13456-135-2345-2345-1 10^24
+begword zetta 1356-15-2345-2345-1 10^21
+# begword exa 15-1346-1 10^18
+begword peta 1234-15-2345-1 10^15
+begword tera 2345-12456-1 10^12
+begword giga 1245-24-1245-1 10^9
+begword mega 134-15-1245-1 10^6
+begword kilo 13-24-123-135 10^3
+begword hecto 125-15-14-2345-135 10^2
+begword deca 145-15-14-1 10^1
+begword deci 145-15-14-24 10^-1
+begword centi 14-26-2345-24 10^-2
+begword milli 134-24-123-123-24 10^-3
+begword micro 134-24-14-1235-135 10^-6
+begword nano 1345-1-1345-135 10^-9
+begword pico 1234-24-14-135 10^-12
+begword femto 124-15-134-2345-135 10^-15
+begword atto 1-2345-2345-135 10^-18
+begword zepto 1356-15-1234-2345-135 10^-21
+begword yocto 13456-135-14-2345-135 10^-24
+
+word thz = terahertz
+word ghz = gigahertz
+word chz = centihertz
+
+begword ante =
+begword anti =
+begword endo 26-145-135
+begword epi =
+begword extra =
+begword hyper 125-13456-1234-12456
+begword hypo =
+begword infra 35-124-1235-1
+begword inter 35-2345-12456
+begword intra 35-2345-1235-1
+begword iso =
+begword macro =
+begword meta =
+begword micro =
+begword mono =
+begword multi =
+begword patho 1234-1-1456-135
+begword peri 1234-12456-24
+begword poly =
+begword post 1234-135-34
+begword pre =
+begword pseudo =
+begword retro =
+# begword semi = seminar
+begword sub =
+begword super 234-136-1234-12456
+begword tetra =
+begword trans =
+begword ultra =
+# begword uni =
+
+# other prefixes
+begword electro =
+begword gastro 1245-1-34-1235-135
+begword neuro =
+begword psycho 1234-234-13456-16-135
+
+prepunc " 236
+postpunc " 356
+always " 5
+
+postpunc '' 356
+postpunc ''' 3-356
+always ' 3
+word 'em = 
+word 'tis =
+word 'twas =
+word 'twill =
+endword 'd 3-145
+endword 'll 3-123-123
+endword 'm 3-134
+endword 're 3-1235-15
+endword 's 3-234
+endword 't 3-2345
+endword 've 3-1236-15
+
+prepunc `` 236
+always ` 4
+
+midnum ^ 45
+always ^ 45
+
+always ~ 4-156
+repeatable ~~~ 4-156-4-156-4-156
+
+midnum , 2
+always , 2
+
+always ; 23
+
+midnum : 25
+always : 25
+repeatable ::: 25-25-25
+
+midnum . 46
+always . 256
+always ... 3-3-3
+always .\s.\s. 3-3-3 . . .
+
+always ! 235
+always ? 236
+
+always ( 2356
+always ) 2356
+
+always [ 6-2356
+always ] 2356-3
+
+always { 246
+always } 12456
+
+always # 456-1456
+
+midnum * 4-16
+always * 35-35
+repeatable *** 35-35-35-35-35-35
+
+midnum / 34
+always / 456-34
+
+always % 4-25-1234
+
+always & 4-12346
+
+always @ 4-1
+
+always \\ 456-16
+
+always | 456-1256
+
+repeatable \s 0 space
+repeatable \t 0 character tabulation
+repeatable \xa0 0 no break space
+
+always \u00A9 45-14 copyright sign
+always \u00AE 45-1235 registered sign
+always \u2014 36-36 em dash
+always \u2122 45-2345 trade mark sign
+
+always -com =
+repeatable --- 36-36-36
+always \s-\s 36-36
+always \s-\scom 36-36-14-135-134
+
+always _ 46
+repeatable ___ 46-46-46
+
+repeatable === 123456-123456-123456
+
+# the hyphen
+always ­ 36
+repeatable ­­­ 36-36-36
+always \s­\s 36-36
+always \s\u2014\s 36-36
+
+# accented letters
+always À 6-4-1      [C0] upper a grave
+always Á 6-4-1      [C1] upper a acute
+always  6-4-1      [C2] upper a circumflex
+always à 6-4-1      [C3] upper a tilde
+always Ä 6-4-1      [C4] upper a dieresis
+always Å 6-4-1      [C5] upper a ring
+always Æ 6-1-15     [C6] upper ae
+always Ç 6-4-14     [C7] upper c cedilla
+always È 6-4-15     [C8] upper e grave
+always É 6-4-15     [C9] upper e acute
+always Ê 6-4-15     [CA] upper e circumflex
+always Ë 6-4-15     [CB] upper e dieresis
+always Ì 6-4-24     [CC] upper i grave
+always Í 6-4-24     [CD] upper i acute
+always Î 6-4-24     [CE] upper i circumflex
+always Ï 6-4-24     [CF] upper i dieresis
+always Ð 6-4-15     [D0] upper eth
+always Ñ 6-4-1345   [D1] upper n tilde
+always Ò 6-4-135    [D2] upper o grave
+always Ó 6-4-135    [D3] upper o acute
+always Ô 6-4-135    [D4] upper o circumflex
+always Õ 6-4-135    [D5] upper o tilde
+always Ö 6-4-135    [D6] upper o dieresis
+always Ø 6-4-135    [D8] upper o slash
+always Ù 6-4-136    [D9] upper u grave
+always Ú 6-4-136    [DA] upper u acute
+always Û 6-4-136    [DB] upper u circumflex
+always Ü 6-4-136    [DC] upper u dieresis
+always Ý 6-4-13456  [DD] upper y acute
+always Þ 6-4-2345   [DE] upper t horn
+always ß 234-234    [DF] lower ss
+always à   4-1      [E0] lower a grave
+always á   4-1      [E1] lower a acute
+always â   4-1      [E2] lower a circumflex
+always ã   4-1      [E3] lower a tilde
+always ä   4-1      [E4] lower a dieresis
+always å   4-1      [E5] lower a ring
+always æ   1-15     [E6] lower ae
+always ç   4-14     [E7] lower c cedilla
+always è   4-15     [E8] lower e grave
+always é   4-15     [E9] lower e acute
+always ê   4-15     [EA] lower e circumflex
+always ë   4-15     [EB] lower e dieresis
+always ì   4-24     [EC] lower i grave
+always í   4-24     [ED] lower i acute
+always î   4-24     [EE] lower i circumflex
+always ï   4-24     [EF] lower i dieresis
+always ð   4-15     [F0] lower eth
+always ñ   4-1345   [F1] lower n tilde
+always ò   4-135    [F2] lower o grave
+always ó   4-135    [F3] lower o acute
+always ô   4-135    [F4] lower o circumflex
+always õ   4-135    [F5] lower o tilde
+always ö   4-135    [F6] lower o dieresis
+always ø   4-135    [F8] lower o slash
+always ù   4-136    [F9] lower u grave
+always ú   4-136    [FA] lower u acute
+always û   4-136    [FB] lower u circumflex
+always ü   4-136    [FC] lower u dieresis
+always ý   4-13456  [FD] lower y acute
+always þ   4-2345   [FE] lower t horn
+always ÿ   4-13456  [FF] lower y dieresis
+
+# mathematical symbols
+always < 126
+always = 123456
+always > 345
+midnum + 346
+always + 346
+midnum - 36
+always - 36
+always × 46-16 multiplication sign
+midnum ÷ 46-34 division sign
+begnum $ 256
+always $ 256
+
+# other special characters
+always © 2356-6-14-2356 copyright
+always ¶ 4-1234-345 paragraph
+always § 4-234-3 section
+always ° 45-46-16 degrees
+always ¢ 4-14 cents
+always £ 4-123 pounds
+always ¥ 4-13456 yen
+always µ 46-134 mu
+
+# pseudo-words
+word usenet =
+
+# abbreviations
+word aarp = American Association of Retired Persons
+
+# special character sequences
+literal :// URLs
+literal www.
+
+literal .com
+literal .edu
+literal .gov
+literal .mil
+literal .net
+literal .org
+include countries.cti
+
+literal .doc
+literal .htm
+literal .html
+literal .tex
+literal .txt
+
+literal .gif
+literal .jpg
+literal .png
+literal .wav
+
+literal .tar
+literal .zip
+
+# d,g,r,rs,s,st,th conceive deceive declare perceive receive rejoice
+# n't could must should would
+
+# When an upper-case letter occurs inside a contraction, following a lower-case
+# letter, the contraction should not be used. Example McCan
+
+# Windows mail programs use all sorts of weird characters for apostrophes. 
+# I would prefer to have all characters above 126 just show up as a 
+# backslash and two hex digits.
+
+# problems with quotation marks before and after dashes: "division"--a
+
+# when a decimal begins with a period, it should be translated with a 
+# number sign followed by a decimal point, followed by the number.
+
+# inline contraction of emoji descriptions
+emoji en
diff --git a/Tables/Contraction/en.ctb b/Tables/Contraction/en.ctb
new file mode 100644
index 0000000..031d787
--- /dev/null
+++ b/Tables/Contraction/en.ctb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - English
+
+include en-ueb-g2.ctb
diff --git a/Tables/Contraction/en_US.ctb b/Tables/Contraction/en_US.ctb
new file mode 100644
index 0000000..f31952c
--- /dev/null
+++ b/Tables/Contraction/en_US.ctb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - English (United States)
+
+include en-us-g2.ctb
diff --git a/Tables/Contraction/es.ctb b/Tables/Contraction/es.ctb
new file mode 100644
index 0000000..b8ce6df
--- /dev/null
+++ b/Tables/Contraction/es.ctb
@@ -0,0 +1,356 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Spanish (grade 2)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+always \s 0
+
+class voy aeiouäáéíóú
+class con bcdfghjklmnñpqrstvwxz
+
+endword ble 12
+word bien 12
+endword cia 14
+word cual 14
+word de 145
+word fué 124
+word grande 1245
+word ha 125
+word si 24
+word jamás 245
+always al 13
+endword mente 134
+word me 134
+word no 1345
+word por 1234
+always que 12345
+word recién 1235
+endword se 234
+word se 234
+endword te 2345
+word te 2345
+word su 136
+endword ivo 1236
+word vez 1236
+before voy begword ex 1346
+midendword on 1346
+word son 1346
+before voy begword inter 13456
+word este 1356
+always as 12346
+word las 12346
+begmidword ll 123456
+endword ella 123456
+word ella 123456
+always á 12356
+word más 12356
+always é 2346
+word él 2346
+always ú 23456
+word tú 23456
+always ar 16
+word para 16
+begmidword em 126
+endword ente 126
+word siempre 126
+always an 146
+word ante 146
+always ad 1456
+word además 1456
+always or 156
+word yo 156
+always es 1246
+word es 1246
+always ñ 12456
+endword año 12456
+word año 12456
+begword ä 1256
+midendword ue 1256
+word pues 1256
+always os 246
+word los 246
+begmidword om 2456
+word como 2456
+begmidword ab 2
+always , 2
+before voy begword sobre 23
+midword br 23
+always ; 23
+word sobre 23
+before voy begword con 25
+midword cr 25
+always : 25
+word con 25
+before voy begword dis 256
+midword dr 256
+always . 256
+word del 256
+begword en 26
+always ? 26
+midendword en 26
+word en 26
+before voy begword pre 235
+always ! 235
+midword pr 235
+word pero 235
+begmidword gr 2356
+always ( 2356
+always ) 2356
+word gran 2356
+always er 236
+begword " 236
+word he 236
+before voy begword entre 356
+midword tr 356
+endword " 356
+word entre 356
+begmidword in 35
+word sin 35
+before voy begword re 3
+midendword ' 3
+word la 3
+before voy begword com 36
+midword cl 36
+always - 36
+word lo 36
+numsign 3456
+endword ión 3456
+word número 3456
+always í 34
+word sí 34
+always ó 346
+word aquel 346
+always im 345
+word tu 345
+
+# terminations
+
+endword acción 1-3456
+endword ección 15-3456
+endword icción 24-3456
+endword ucción 136-3456
+endword ando 146-145
+endword ando 26-145
+endword ado 1456-135
+endword ido 45-135
+
+# locutions
+
+word en\sconsecuencia 26-456-25
+word en\sseguida 26-456-234
+word en\sefecto 26-456-15
+word es\sdecir 246-456-145
+word no\sobstante 1345-456-135
+word poco\sa\spoco 1234-456-1234
+word por\sejemplo 1234-456-15
+word sobre\stodo 234-456-2345
+word tal\svez 2345-456-1236
+word sin\sembargo 234-456-15
+
+# several cells words
+
+word abajo 2-245
+word acaso 1-14
+word acción 1-3456
+word acerca 1-14-14
+word adelante 1456-123
+word ahora 4-125
+word algo 13-135
+word algún 13-23456
+word alguno 13-1345-135
+word alguien 13-1345
+word allá 5-123456
+word allí 1-123456
+word alrededor 13-156
+word antes 146-234
+word anterior 146-2345
+word anterioridad 146-2345-145
+word anteriormente 146-2345-134
+word apenas 1-1234
+word aquello 346-135
+word aquí 1-12345
+word arriba 16-1235
+word atrás 1-356
+word bajo 12-245
+word bastante 12-2345
+word bella 12-123456
+word belleza 12-123456-1356
+word braille 46-12-1235-123
+word breve 23-1236
+word brevedad 23-1236-145
+word brevemente 23-1236-134
+word cada 14-1456
+word cerca 14-14
+word casi 14-234
+word ciego 14-1245
+word condición 25-145-3456
+word condicional 25-145-3456-13
+word condicionalmente 25-145-3456-13-134
+word conjunto 25-245-2345
+word conmigo 25-134
+word contigo 25-2345
+word consigo 25-234
+word consecuencia 25-234-14
+word contra 25-356
+word cualquier 14-12345
+word cuando 14-145
+word cuanto 14-2345
+word cuyo 14-13456
+word debajo 145-12-245
+word delante 145-1234-2345
+word demás 145-134
+word demasiado 145-134-145
+word desde 145-234
+word después 145-1234
+word detrás 145-356
+word difícil 145-124
+word difícilmente 145-124-134
+word dificultad 145-124-2345
+word donde 145-145
+word durante 145-136
+word el 15
+word efecto 15-124
+word efectivo 15-124-1236
+word efectivamente 15-124-1236-134
+word ejemplo 15-245
+word encima 26-14
+word entonces 26-2345
+word entretanto 356-2345
+word estar 1246-1235
+word estaba 1246-2345-12
+word estaban 1246-2345-12-146
+word estado 1246-2345-145
+word éste 5-1356
+word exterior 1346-2345
+word exteriormente 1346-2345-134
+word fácil 124-14
+word fácilmente 124-14-134
+word facilidad 124-14-145
+word favor 124-1236
+word favorable 124-1236-12
+word favorablemente 124-1236-12-134
+word general 1245-1345
+word generalidad 1245-1345-145
+word generalmente 1245-1345-134
+word hacia 125-14
+word hasta 125-2345
+word haber 125-1235
+word había 125-12
+word habían 125-12-146
+word habido 125-12-145
+word hijo 125-245
+word hermano 125-134
+word hombre 125-23
+word hacer 236-1235
+word hacía 236-14
+word hacían 236-14-146
+word igual 24-1245
+word igualdad 24-1245-145
+word igualmente 14-1245-134
+word importancia 345-1234-14
+word importante 345-1234-2345
+word inferior 35-124
+word inferioridad 35-124-145
+word inmediato 35-134
+word inmediatamente 35-134-134
+word interior 35-2345
+word inteligencia 35-1245-14
+word joven 245-1236
+word junto 245-2345
+word juventud 245-1236-2345
+word le 123
+word lejos 123-245
+word luego 123-1256
+word madre 134-1
+word mayor 134-13456
+word medio 134-145
+word mediante 134-145-2345
+word menor 134-1345
+word menos 134-234
+word mientras 134-356
+word mismo 134-134
+word mucho 4-134
+word mujer 134-136
+word muy 6-134
+word nada 1345-1456
+word nadie 1345-145
+word ningún 1345-1245
+word ninguna 1345-1245-1
+word ninguno 1345-1245-135
+word ningunas 1345-1245-12346
+word ningunos 134-1245-246
+word nosotros 45-1345
+word nuestro 56-1345
+word nuevo 1345-1236
+word nunca 1345-14
+word número 3456
+word otro 135-356
+word padre 1234-1
+word pequeño 1234-12345-12456
+word poco 1234-14
+word porque 1234-12345
+word porqué 5-1234-12345
+word pronto 235-2345
+word propio 235-1234
+word propiedad 235-1234-145
+word propiamente 235-1234-134
+word qué 5-12345
+word quien 12345-1345
+word quién 5-12345-1345
+word quizá 12345-1356
+word recientemente 1235-2345-134
+word reciente 1235-2345
+word según 234-1245
+word sido 234-145
+word sino 234-1345
+word siquiera 234-12345
+word solo 234-123
+word sus 234-234
+word superior 234-1234
+word suyo 234-13456
+word también 2345-12
+word tampoco 2345-1234-14
+word tanto 2345-2345
+word tener 2345-1235
+word tenía 2345-1345
+word tenían 2345-1345-146
+word tenido 2345-1345-145
+word tiempo 2345-1234
+word todavía 2345-1236
+word todo 2345-145
+word tú 5-2345
+word tuyo 2345-13456
+word último 23456-123
+word único 23456-14
+word únicamente 23456-14-134
+word usted 134-145
+word verdad 1236-145
+word veces 1236-14
+word vosotros 45-1236
+word vuestro 56-1236
+
+# inline contraction of emoji descriptions
+emoji es
diff --git a/Tables/Contraction/fr-g1.ctb b/Tables/Contraction/fr-g1.ctb
new file mode 100644
index 0000000..38363e1
--- /dev/null
+++ b/Tables/Contraction/fr-g1.ctb
@@ -0,0 +1,138 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - French (uncontracted)
+
+# Définitions pour code braille français international unifié
+# Par Nicolas Pitre <nico@fluxnic.net>
+# Référence: http://www.inlb.qc.ca/apropos/c2003unificationdubraille.aspx
+
+capsign 46		indicateur de majuscule
+begcaps 46-46		succession de majuscules
+
+numsign 6		préfixe pour les chiffres
+midnum \s 3		espace entre les chiffres
+midnum , 2
+midnum . 256
+midnum - 36
+midnum / 34
+midnum : 25
+endnum # 56-3456
+
+include letters-latin.cti
+
+always ç 12346		c cédille
+always é 123456		e accent aigu
+always à 12356		a accent grave
+always è 2346		e accent grave
+always ù 23456		u accent grave
+always â 16		a accent circonflexe
+always ê 126		e accent circonflexe
+always î 146		i accent circonflexe
+always ô 1456		o accent circonflexe
+always û 156		u accent circonflexe
+always ë 1246		e tréma
+always ï 12456		i tréma
+always ü 1256		u tréma
+always oe 246		oe ligatur
+
+always Ç 12346		C cédille
+always É 123456		E accent aigu
+always À 12356		A accent grave
+always È 2346		E accent grave
+always Ù 23456		U accent grave
+always  16		A accent circonflexe
+always Ê 126		E accent circonflexe
+always Î 146		I accent circonflexe
+always Ô 1456		O accent circonflexe
+always Û 156		U accent circonflexe
+always Ë 1246		E tréma
+always Ï 12456		I tréma
+always Ü 1256		U tréma
+always Oe 246		Oe ligatur
+
+always , 2		virgule
+always ; 23		point-virgule
+always : 25		deux-points
+always . 256		point
+always ? 26		point d'interrogation
+always ! 235		point d'exclamation
+always " 2356		guillemet
+always ( 236		parenthèse ouvrante
+always * 35		astérisque
+always ) 356		parenthèse fermante
+always ' 3		apostrophe
+always / 34		barre oblique
+always @ 345		arobas
+always % 346		pour cent
+always - 36		trait d'union
+always # 3456		dièse
+
+always 0 3456		zéro
+always 1 16		un
+always 2 126		deux
+always 3 146		trois
+always 4 1456		quatre
+always 5 156		cinq
+always 6 1246		six
+always 7 12456		sept
+always 8 1256		huit
+always 9 246		neuf
+
+always ÷ 6-256		divisé par
+always + 6-235		plus
+always = 6-2356		égal
+always × 6-35		multiplié par
+always < 46-126		inférieur à
+always > 46-345		supérieur à
+
+always © 5-14		copyright
+always ° 5-135		degré
+always & 5-123456	perluète (et commercial)
+always ¢ 45-14		cent
+always ¤ 45-15		euro
+always £ 45-123		livre
+always § 45-1234	paragraphe
+always $ 45-234		dollar
+always ¥ 45-13456	yen
+always « 45-2356	guillemet français ouvrant
+always » 2356-12	guillemet français fermant
+always [ 45-236		crochet droit ouvrant
+always ] 356-12		crochet droit fermant
+always { 6-236		accolade de gauche
+always } 356-3		accolade de droite
+
+always ¹ 4-6-16		exposant 1
+always ² 4-6-126	exposant 2
+always ³ 4-6-146	exposant 3
+always ¼ 6-16-34-1456	un quart
+always ½ 6-16-34-126	un demi
+always ¾ 6-126-34-1456	trois quarts
+
+always _ 78		souligné
+
+repeatable \s 0		espaces
+repeatable \t 0		tabulations
+repeatable \xa0 0		espaces insécables
+
+repeatable ... 3-3-3		points de suite
+repeatable --- 36-36-36
+repeatable ___ 78-78-78
+
+always \s--\s 36-36	tiret
+
diff --git a/Tables/Contraction/fr-g2.ctb b/Tables/Contraction/fr-g2.ctb
new file mode 100644
index 0000000..44a78a7
--- /dev/null
+++ b/Tables/Contraction/fr-g2.ctb
@@ -0,0 +1,1791 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - French (contracted)
+
+# Auteur:
+#
+#	Nicolas Pitre <nico@fluxnic.net>
+#
+# Référence:
+#
+#	Index de l'abrégé orthographique français étendu, lecture et écriture
+#	Révision 1993
+#	Service de production Braille, Institut Nazareth et Louis-Braille
+#	Longueuil (Québec)
+
+include fr-g1.ctb
+
+###
+### 1. Assemblages de lettres représentés par un seul symbole,
+###    et finales représentées par deux ou trois symboles;
+###
+
+letsign 6
+
+class voy aeiouyéàèùâêîôûëïüAEIOUYÉÀÈÙÂÊËïü
+class con bcçdfghjklmnpqrstvwxzBCÇDFGHJKLMNPQRSTVWXZ
+class bmp bmpBMP
+
+endword able 45
+always ai 34
+endword ait 146
+before con always an 2
+endword ant 1456
+before con always ar 4
+endword ar 4
+endword ation 16
+always au 13
+
+always bl 45
+before voy always br 23
+
+always ch 12356
+before voy always cl 146
+before con begword com 36
+before con always con 25
+before voy always cr 25
+
+before con begword dis 256
+before voy always dr 1456
+
+endword elle 456
+before con always em 345
+before con always en 26
+endword en 26
+endword ent 126
+before con midword er 236
+endword er 236
+begword es 156
+endword es 156
+word eu 15-136
+always eu 5
+before con always eur 46
+endword eur 46
+before con always ex 1346
+endword ez 1356
+
+before voy always fl 126
+before voy always fr 16
+
+before voy always gl 345
+always gn 2356
+before voy always gr 12456
+
+always ien 256
+always ieu 6
+before bmp begword im 246
+always in 35
+always ion 3456
+endword ition 246
+
+before voy after voy always ll 456
+
+always oi 23456
+before con always om 2456
+endword om 2456
+before con always on 346
+endword on 346
+before con always or 56
+endword or 56
+always ou 1256
+before con always our 12346
+endword our 12346
+
+before voy always pl 1246
+before voy always pr 235
+before con always pro 235
+
+always qu 12345
+endword que 12345
+
+before con begword re 3
+
+before voy after voy always ss 2346
+
+before voy always tr 356
+before con begword trans 356
+before voy after voy always tt 2456
+
+before con always ui 23
+
+endword ablement 45-134
+endword bilité 12-123-2345
+endword bilités 12-123-2345-234
+endword ellement 456-134
+endword logie 123-1245
+endword quement 12345-134
+endword quements 12345-134-234
+endword tement 2345-134
+endword tements 2345-134-234
+endword ttement 2345-2345-134
+endword ttements 2345-2345-134-234
+endword vement 1236-134
+endword vements 1236-134-234
+
+before con always ain 1-35
+endword ain 1-35
+before con always oin 135-35
+endword oin 135-35
+
+before con begword recom 3-36
+before con begword redis 3-256
+before con begword retrans 3-356
+before con begword incom 35-36
+before con begword indis 35-256
+before con begword intrans 35-356
+begword ines 35-156
+
+prfword tient 2345-256-2345
+prfword vient 1236-256-2345
+endword ient 24-126
+word balbutient 12-1-123-12-136-2345-24-126
+word initient 35-24-2345-24-126
+word dévient 145-123456-1236-24-126
+word envient 1235-26-1236-24-126
+
+sufword bleu 45-15-136
+before con always brui 12-1235-23
+endword clait 14-123-146
+before voy always concr 14-346-25
+endword drant 145-1235-1456
+always drô 145-1235-1456
+endword en? 15-1345-26
+always ien. 24-26-256
+before voy always propr 235-135-235
+before con begword ren 1235-26
+always ssè 234-234-2346
+word (en 236-15-1345
+word (la 236-123-1
+
+###
+### 2. Mots représentés par un seul symbole
+###
+
+word a 1
+word à 12356
+word ai 34
+word au 13
+word aux 13-1346
+word bien 12
+word biens 12-234
+word ce 14
+word celui 36
+word cet 146
+word dans 1456
+word de 145
+word dès 256
+word du 236
+word elle 1356
+word elles 1356-234
+word en 26
+word est 156
+word et 23456
+word été 2356
+word étés 2356-234
+word faire 124
+word grand 12456
+word grands 12456-234
+word il 24
+word ils 24-234
+word je 245
+word la 3
+word le 123
+word les 345
+word lui 3456
+word mais 1346
+word me 134
+word même 126
+word mêmes 126-234
+word ne 1345
+word nous 135
+word on 346
+word ou 1256
+word par 1234
+word plus 1246
+word pour 12346
+word puis 235
+word que 12345
+word qui 1245
+word quoi 123456
+word rien 1235
+word riens 1235-234
+word sans 2346
+word se 234
+word si 35
+word son 246
+word sons 246-234
+word sous 356
+word sur 125
+word te 2345
+word tous 2456
+word tout 16
+word un 136
+word uns 136-234
+word vous 1236
+word y 13456
+
+begword c' 14-3
+begword d' 145-3
+begword j' 245-3
+begword l' 123-3
+begword m' 134-3
+begword n' 1345-3
+begword s' 234-3
+begword t' 2345-3
+
+###
+### 3. Mots représentés par deux ou plusieurs symboles
+###
+
+word absolu		1-12
+word absolus		1-12-234
+word absolue		1-12-15
+word absolues		1-12-156
+word absolument		1-12-134
+word action		1-3456
+word actions		1-3456-234
+word actionnaire	1-3456-1235
+word actionnaires	1-3456-1235-234
+word affaire		1-124-124
+word affaires		1-124-124-234
+word afin		1-124
+word ailleurs		34-136
+word ainsi		1-24
+word alors		1-123
+word amour		1-134
+word amours		1-134-234
+word amoureuse		1-134-234-15
+word amoureuses		1-134-234-156
+word amoureusement	1-134-234-134
+word amoureux		1-134-1346
+word apparemment	1-1234-134
+word apparence		1-1234-14
+word apparences		1-1234-14-234
+word apparent		1-1234
+word apparents		1-1234-234
+word apparente		1-1234-15
+word apparentes		1-1234-156
+word après		1-235
+word assez		1-1356
+word attentif		1-2345-124
+word attentifs		1-2345-124-234
+word attention		1-2345
+word attentions		1-2345-234
+word attentive		1-2345-1236
+word attentives		1-2345-1236-234
+word attentivement	1-2345-1236-134
+word aucun		13-14
+word aucune		13-1345
+word aucunement		13-1345-134
+word auparavant		13-1234
+word auprès		13-235
+word auquel		13-12345-123
+word aussi		13-234
+word aussitôt		13-2345
+word autour		13-1235
+word autre		13-356
+word autres		13-356-234
+word autrefois		13-124
+word autrement		13-356-134
+word auxquelles		13-1346-12345-123-123-234
+word auxquels		13-1346-12345-123-234
+word avance		1-1236-14
+word avances		1-1236-14-234
+word avancement		1-1236-14-134
+word avant		1-1236
+word avantage		1-1236-1245
+word avantages		1-1236-1245-234
+word avantageuse	1-1236-1245-234-15
+word avantageuses	1-1236-1245-234-156
+word avantageusement	1-1236-1245-234-134
+word avantageux		1-1236-1245-1346
+word avec		1-14
+word avoir		1-1235
+word avoirs		1-1235-234
+word ayant		1-13456
+
+word beaucoup		12-14
+word besogne		12-2356
+word besognes		12-2356-234
+word besogneuse		12-2356-234-15
+word besogneuses		12-2356-234-156
+word besogneux		12-2356-1346
+word besoin		12-35
+word besoins		12-35-234
+word bête		12-126
+word bêtes		12-126-234
+word bêtement		12-126-134
+word bienfaisance	12-124-14
+word bienfaisances	12-124-14-234
+word bienfait		12-124
+word bienfaits		12-124-234
+word bienfaiteur	12-124-46
+word bienfaiteurs	12-124-46-234
+word bientôt		12-2345
+word bienveillance	12-1236-14
+word bienveillances	12-1236-14-234
+word bienveillant	12-1236
+word bienveillants	12-1236-234
+word bienveillante	12-1236-15
+word bienveillantes	12-1236-156
+word bizarre		12-1356
+word bizarres		12-1356-234
+word bizarrement	12-1356-134
+word bonheur		12-125
+word bonheurs		12-125-234
+word bonjour		12-245
+word bonjours		12-245-234
+word bonne		12-1345
+word bonnes		12-1345-234
+word bonnement		12-1345-134
+word bonté		12-135
+word bontés		12-135-234
+word boulevard		12-145
+word boulevards		12-145-234
+word braille		23-123
+word branchage		23-12356-1245
+word branchages		23-12356-1245-234
+word branche		23-12356
+word branches		23-12356-234
+word branchement	23-12356-134
+word branchements	23-12356-134-234
+word brave		23-1236
+word braves		23-1236-234
+word bravement		23-1236-134
+word bruit		23-2345
+word bruits		23-2345-234
+word brusque		23-12345
+word brusques		23-12345-234
+word brusquement	23-12345-134
+word budget		12-1245
+word budgets		12-1245-234
+word budgétaire		12-1245-1235
+word budgétaires	12-1245-1235-234
+
+word caractère		14-2346
+word caractères		14-2346-234
+word caractéristique	14-123456-12345
+word caractéristiques	14-123456-12345-234
+word ceci		14-14
+word cela		14-1
+word celle		14-123
+word celles		14-123-234
+word celui-ci		36-36-14-24
+word celui-là		36-36-123-12356
+word cependant		14-1234
+word certain		14-35
+word certains		14-35-234
+word certaine		14-1345
+word certaines		14-1345-234
+word certainement	14-1345-134
+word certes		14-236
+word certitude		14-236-145
+word certitudes		14-236-145-234
+word ces		14-234
+word cette		14-2345
+word ceux		14-1346
+word chacun		12356-14
+word chacune		12356-1345
+word chagrin		12356-12456
+word chagrins		12356-12456-234
+word chaleur		12356-123
+word chaleurs		12356-123-234
+word chaleureuse	12356-123-234-15
+word chaleureuses	12356-123-234-156
+word chaleureusement	12356-123-234-134
+word chaleureux		12356-123-1346
+word champ		12356-1234
+word champs		12356-1234-234
+word change		12356-1245
+word changes		12356-1245-234
+word changement		12356-1245-134
+word changeur		12356-1245-46
+word changeurs		12356-1245-46-234
+word chaque		12356-12345
+word charitable		12356-2345-45
+word charitables		12356-2345-45-234
+word charitablement	12356-2345-45-134
+word charité		12356-2345
+word charités		12356-2345-234
+word chaud		12356-145
+word chauds		12356-145-234
+word chaude		12356-145-15
+word chaudes		12356-145-156
+word chaudement		12356-145-134
+word chemin		12356-134
+word chemins		12356-134-234
+word chère		12356-2346
+word chères		12356-2346-234
+word chèrement		12356-2346-134
+word chez		12356-1356
+word chiffrage		12356-124-1245
+word chiffrages		12356-124-1245-234
+word chiffre		12356-124
+word chiffres		12356-124-234
+word choeur		12356-1235
+word choeurs		12356-1235-234
+word choix		12356-1346
+word chose		12356-234
+word choses		12356-234-234
+word circonstance	14-25-14
+word circonstances	14-25-14-234
+word circonstanciel	14-25-14-123
+word circonstanciels	14-25-14-123-234
+word circonstancielle	14-25-14-123-123
+word circonstancielles	14-25-14-123-123-234
+word civil		14-1236
+word civils		14-1236-234
+word civile		14-1236-15
+word civiles		14-1236-156
+word civilement		14-1236-134
+word civilisation	14-1236-16
+word civilisations	14-1236-16-234
+word civilité		14-1236-2345
+word civilités		14-1236-2345-234
+word coeur		14-1235
+word coeurs		14-1235-234
+word combien		14-12
+word comme		14-134
+word commencement	36-134
+word commencements	36-134-234
+word comment		14-26
+word commentaire	14-26-1235
+word commentaires	14-26-1235-234
+word commentateur	14-26-46
+word commentateurs	14-26-46-234
+word commun		36-1345
+word communs		36-1345-234
+word commune		36-1345-15
+word communes		36-1345-156
+word communal		36-1345-123
+word communale		36-1345-123-15
+word communales		36-1345-123-156
+word communautaire	36-1345-2345-1235
+word communautaires	36-1345-2345-1235-234
+word communauté		36-1345-2345
+word communautés		36-1345-2345-234
+word communaux		36-1345-1346
+word communément	36-1345-134
+word communion		36-1345-3456
+word communions		36-1345-3456-234
+word complément		36-1246-134
+word compléments	36-1246-134-234
+word complémentaire	36-1246-134-1235
+word complémentaires	36-1246-134-1235-234
+word complet		36-1246
+word complets		36-1246-234
+word complète		36-2345
+word complètes		36-2345-234
+word complètement	36-2345-134
+word conclusion		25-14
+word conclusions	25-14-234
+word condition		25-145
+word conditions		25-145-234
+word conditionnel	25-145-123
+word conditionnels	25-145-123-234
+word conditionnelle	25-145-123-123
+word conditionnelles	25-145-123-123-234
+word conditionnellement	25-145-123-134
+word confiance		25-124-14
+word confiant		25-124
+word congrès		25-12456
+word connaissance	25-1345-14
+word connaissances	25-1345-14-234
+word connaître		25-1345
+word consciemment	25-234-134
+word conscience		25-234-14
+word consciences	25-234-14-234
+word consciencieuse	25-234-14-234-15
+word consciencieuses	25-234-14-234-156
+word consciencieusement	25-234-14-234-134
+word consciencieux	25-234-14-1346
+word conscient		25-234
+word conscients		25-234-234
+word consciente		25-234-15
+word conscientes	25-234-156
+word conséquemment	14-12345-134
+word conséquence	14-12345-14
+word conséquences	14-12345-14-234
+word conséquent		14-12345
+word conséquents	14-12345-234
+word conséquente	14-12345-15
+word conséquentes	14-12345-156
+word considérable	14-145
+word considérables	14-145-234
+word considérablement	14-145-134
+word considération	14-145-16
+word considérations	14-145-16-234
+word contraire		14-356
+word contraires		14-356-234
+word contrairement	14-356-134
+word conversation	25-1236
+word conversations	25-1236-234
+word côté		14-1456
+word côtés		14-1456-234
+word couple		14-1246
+word couples		14-1246-234
+word courage		14-1245
+word courageuse		14-1245-234-15
+word courageuses		14-1245-234-156
+word courageusement	14-1245-234-134
+word courageux		14-1245-1346
+
+word danger		145-1245
+word dangers		145-1245-234
+word dangeureuse	145-1245-234-15
+word dangeureuses	145-1245-234-156
+word dangeureusement	145-1245-234-134
+word dangeureux		145-1245-1346
+word davantage		145-1
+word debout		145-12
+word dedans		145-145
+word degré		145-12456
+word degrés		145-12456-234
+word dehors		145-125
+word déjà		145-245
+word demain		145-134
+word depuis		145-1234
+word dernier		145-1345
+word derniers		145-1345-234
+word dernière		145-1235
+word dernières		145-1235-234
+word dernièrement	145-1235-134
+word derrière		145-236
+word derrières		145-236-234
+word des		145-234
+word désormais		145-34
+word desquels		145-234-12345-123-234
+word desquelles		145-234-12345-123-123-234
+word destin		145-35
+word destins		145-35-234
+word destinataire	145-35-1235
+word destinataires	145-35-1235-234
+word destination	145-35-16
+word destinations	145-35-16-234
+word devant		145-1236
+word devants		145-1236-234
+word différemment	145-345-134
+word différence		145-26-14
+word différences	145-26-14-234
+word différent		145-26
+word différents		145-26-234
+word différente		145-26-15
+word différentes	145-26-156
+word difficile		145-124
+word difficiles		145-124-234
+word difficilement	145-124-134
+word difficulté		145-124-2345
+word difficultés	145-124-2345-234
+word digne		145-2356
+word dignes		145-2356-234
+word dignement		145-2356-134
+word dignitaire		145-2356-2345-1235
+word dignitaires	145-2356-2345-1235-234
+word dignité		145-2356-2345
+word discours		256-14
+word dispositif		256-1234-124
+word dispositifs	256-1234-124-234
+word disposition	256-1234
+word dispositions	256-1234-234
+word distance		256-2345-14
+word distances		256-2345-14-234
+word distant		256-2345
+word distants		256-2345-234
+word distante		256-2345-15
+word distantes		256-2345-156
+word donc		145-14
+word dont		145-2345
+word douleur		145-123
+word douleurs		145-123-234
+word douloureuse	145-123-234-15
+word douloureuses	145-123-234-156
+word douloureusement	145-123-234-134
+word douloureux		145-123-1346
+word doute		145-1256
+word doutes		145-1256-234
+word duquel		145-12345-123
+
+word effectif		15-124-124
+word effectifs		15-124-124-234
+word effective		15-124-1236
+word effectives		15-124-1236-234
+word effectivement	15-124-1236-134
+word effet		15-124
+word effets		15-124-234
+word égal		123456-1245
+word égale		123456-1245-15
+word égales		123456-1245-156
+word également		123456-1245-134
+word égalitaire		123456-1245-2345-1235
+word égalitaires	123456-1245-2345-1235-234
+word égalité		123456-1245-2345
+word égalités		123456-1245-2345-234
+word égaux		123456-1245-1346
+word élément		123456-123
+word éléments		123456-123-234
+word élémentaire	123456-123-1235
+word élémentaires	123456-123-1235-234
+word encore		26-14
+word endroit		26-145
+word endroits		26-145-234
+word énergie		123456-1345
+word énergies		123456-1345-234
+word énergique		123456-1345-12345
+word énergiques		123456-1345-12345-234
+word énergiquement	123456-1345-12345-134
+word enfin		26-124
+word ennui		26-1345
+word ennuis		26-1345-234
+word ennuyeuse		26-1345-234-15
+word ennuyeuses		26-1345-234-156
+word ennuyeux		26-1345-1346
+word enquête		26-12345
+word enquêtes		26-12345-234
+word enquêteur		26-12345-46
+word enquêteurs		26-12345-46-234
+word enquêteuse		26-12345-234-15
+word enquêteuses	26-12345-234-156
+word ensemble		26-345
+word ensembles		26-345-234
+word ensuite		26-234
+word entier		26-2345
+word entiers		26-2345-234
+word entière		26-1235
+word entières		26-1235-234
+word entièrement	26-1235-134
+word environ		26-1236
+word espèce		156-1234
+word espèces		156-1234-234
+word espérance		156-1235-14
+word espérances		156-1235-14-234
+word espoir		156-1235
+word espoirs		156-1235-234
+word esprit		15-235
+word esprits		15-235-234
+word essentiel		156-123
+word essentiels		156-123-234
+word essentielle	156-123-123
+word essentielles	156-123-123-234
+word essentiellement	156-123-134
+word étant		123456-2345
+word être		126-356
+word êtres		126-356-234
+word événement		123456-1236
+word événements		123456-1236-234
+word éventualité	123456-1236-123-2345
+word éventualités	123456-1236-123-2345-234
+word éventuel		123456-1236-123
+word éventuels		123456-1236-123-234
+word éventuelle		123456-1236-123-123
+word éventuelles	123456-1236-123-123-234
+word éventuellement	123456-1236-123-134
+word excellemment	1346-123-134
+word excellence		1346-123-14
+word excellences	1346-123-14-234
+word excellent		1346-123
+word excellents		1346-123-234
+word excellente		1346-123-15
+word excellentes	1346-123-156
+word excès		1346-14
+word excessif		1346-14-124
+word excessifs		1346-14-124-234
+word excessive		1346-14-1236
+word excessives		1346-14-1236-234
+word excessivement	1346-14-1236-134
+word exercice		1346-236
+word exercices		1346-236-234
+word expérience		1346-1234
+word expériences	1346-1234-234
+word expérimental	1346-1234-123
+word expérimentale	1346-1234-123-15
+word expérimentales	1346-1234-123-156
+word expérimentallement	1346-1234-123-134
+word expérimentateur	1346-1234-46
+word expérimentateurs	1346-1234-46-234
+word expérimentation	1346-1234-16
+word expérimentations	1346-1234-16-234
+word expérimentaux	1346-1234-1346
+word explicable		1346-1246-45
+word explicables	1346-1246-45-234
+word explicatif		1346-1246-124
+word explicatifs	1346-1246-124-234
+word explication	1346-1246
+word explications	1346-1246-234
+word explicative	1346-1246-1236
+word explicatives	1346-1246-1236-234
+word expressif		1346-235-124
+word expressifs		1346-235-124-234
+word expression		1346-235
+word expressions	1346-235-234
+word expressive		1346-235-1236
+word expressives	1346-235-1236-234
+word expressivement	1346-235-1236-134
+word extérieur		1346-2345
+word extérieurs		1346-2345-234
+word extérieure		1346-2345-15
+word extérieures	1346-2345-156
+word extérieurement	1346-2345-134
+word extrême		1346-356
+word extrêmes		1346-356-234
+word extrêmement	1346-356-134
+word extrémité		1346-356-2345
+word extrémités		1346-356-2345-234
+
+word facile		124-14
+word faciles		124-14-234
+word facilement		124-14-134
+word facilité		124-14-2345
+word facilités		124-14-2345-234
+word faubourg		124-12
+word faubourgs		124-12-234
+word faut		124-2345
+word faute		124-2345-15
+word fautes		124-2345-15-234
+word fautif		124-2345-124
+word fautifs		124-2345-124-234
+word fautive		124-2345-1236
+word fautives		124-2345-1236-234
+word faveur		124-1236
+word faveurs		124-1236-234
+word favorable		124-1236-45
+word favorables		124-1236-45-234
+word favorablement	124-1236-45-134
+word féminin		124-134-35
+word féminins		124-134-35-234
+word féminine		124-134-1345
+word féminines		124-134-1345-234
+word femme		124-134
+word femmes		124-134-234
+word fête		124-126
+word fêtes		124-126-234
+word fidèle		124-145
+word fidèles		124-145-234
+word fidèlement		124-145-134
+word fidélité		124-145-2345
+word fidélités		124-145-2345-234
+word figuratif		124-1245-124
+word figuratifs		124-1245-124-234
+word figuration		124-1245-16
+word figurations	124-1245-16-234
+word figurative		124-1245-1236
+word figuratives	124-1245-1236-234
+word figure		124-1245
+word figures		124-1245-234
+word fille		124-123
+word filles		124-123-234
+word fils		124-234
+word fonction		124-346
+word fonctions		124-346-234
+word fonctionnaire	124-346-1235
+word fonctionnaires	124-346-1235-234
+word fonctionnel	124-346-123
+word fonctionnels	124-346-123-234
+word fonctionnelle	124-346-123-123
+word fonctionnelles	124-346-123-123-234
+word fonctionnement	124-346-134
+word fonctionnements	124-346-134-234
+word force		124-135
+word forces		124-135-234
+word forcément		124-135-134
+word fortune		124-1345
+word fortunes		124-1345-234
+word fraternel		124-1235-123
+word fraternels		124-1235-123-234
+word fraternelle	124-1235-123-123
+word fraternelles	124-1235-123-123-234
+word fraternellement	124-1235-123-134
+word fraternisation	124-1235-16
+word fraternisations	124-1235-16-234
+word fraternité		124-1235-2345
+word fraternités	124-1235-2345-234
+word fréquemment	124-12345-134
+word fréquence		124-12345-14
+word fréquences		124-12345-14-234
+word fréquent		124-12345
+word fréquents		124-12345-234
+word fréquente		124-12345-15
+word fréquentes		124-12345-156
+word fréquentation	124-12345-16
+word fréquentations	124-12345-16-234
+word frère		124-1235
+word frères		124-1235-234
+
+word garde		1245-145
+word gardes		1245-145-234
+word général		1245-1345
+word générale		1245-1345-15
+word générales		1245-1345-156
+word généralement	1245-1345-134
+word généralisation	1245-1345-16
+word généralisations	1245-1345-16-234
+word généralité		1245-1345-2345
+word généralités	1245-1345-2345-234
+word généraux		1245-1345-1346
+word généreuse		1245-234-15
+word généreuses		1245-234-156
+word généreusement	1245-234-134
+word généreux		1245-1346
+word générosité		1245-234-2345
+word générosités	1245-234-2345-234
+word gloire		1245-1235
+word gloires		1245-1235-234
+word glorieuse		1245-1235-234-15
+word glorieuses		1245-1235-234-156
+word glorieusement	1245-1235-234-134
+word glorieux		1245-1235-1346
+word gouvernement	1245-1236
+word gouvernements	1245-1236-234
+word gouvernemental	1245-1236-123
+word gouvernementale	1245-1236-123-15
+word gouvernementales	1245-1236-123-156
+word gouvernementaux	1245-1236-1346
+word gouverneur		1245-1236-46
+word gouverneurs	1245-1236-46-234
+word grâce		12456-14
+word grâces		12456-14-234
+word gracieuse		12456-14-234-15
+word gracieuses		12456-14-234-156
+word gracieusement	12456-14-234-134
+word gracieux		12456-14-1346
+word grande		12456-145
+word grandes		12456-145-234
+word grandement		12456-145-134
+word grandeur		12456-46
+word grandeurs		12456-46-234
+word grave		12456-1236
+word graves		12456-1236-234
+word gravement		12456-1236-134
+word gravitation	12456-1236-2345-16
+word gravitations	12456-1236-2345-16-234
+word gravité		12456-1236-2345
+word gravités		12456-1236-2345-234
+word groupe		12456-1234
+word groupes		12456-1234-234
+word groupement		12456-1234-134
+word groupements	12456-1234-134-234
+word guère		1245-2346
+word guerre		1245-236
+word guerres		1245-236-234
+
+word habitude		125-12
+word habitudes		125-12-234
+word habituel		125-12-123
+word habituels		125-12-123-234
+word habituelle		125-12-123-123
+word habituelles	125-12-123-123-234
+word habituellement	125-12-123-134
+word hasard		125-145
+word hasards		125-145-234
+word hasardeuse		125-145-234-15
+word hasardeuses	125-145-234-156
+word hasardeux		125-145-1346
+word hélas		125-123
+word heure		125-1235
+word heures		125-1235-234
+word heureuse		125-234-15
+word heureuses		125-234-156
+word heureusement	125-234-134
+word heureux		125-1346
+word hier		125-236
+word histoire		125-2345
+word histoires		125-2345-234
+word historique		125-2345-12345
+word historiques	125-2345-12345-234
+word historiquement	125-2345-12345-134
+word hiver		125-1236
+word hivers		125-1236-234
+word hivernal		125-1236-123
+word hivernaux		125-1236-1346
+word hommage		125-1245
+word hommages		125-1245-234
+word homme		125-134
+word hommes		125-134-234
+word honnête		125-126
+word honnêtes		125-126-234
+word honnêtement	125-126-134
+word honnêteté		125-126-2345
+word honnêtetés		125-126-2345-234
+word honneur		125-1345
+word honneurs		125-1345-234
+word honorabilité	125-1345-12-123-2345
+word honorabilités	125-1345-12-123-2345-234
+word honorable		125-1345-45
+word honorables		125-1345-45-234
+word honorablement	125-1345-45-134
+word honoraire		125-1345-1235
+word honoraires		125-1345-1235-234
+word horaire		125-1235-1235
+word horaires		125-1235-1235-234
+word horizon		125-1356
+word horizons		125-1356-234
+word horizontal		125-1356-123
+word horizontale	125-1356-123-15
+word horizontales	125-1356-123-156
+word horizontalement	125-1356-123-134
+word horizontalité	125-1356-123-2345
+word horizontalités	125-1356-123-2345-234
+word horizontaux	125-1356-1346
+word hypothèse		125-1234
+word hypothèses		125-1234-234
+word hypothétique	125-1234-12345
+word hypothétiques	125-1234-12345-234
+word hypothétiquement	125-1234-12345-134
+word humain		125-134-35
+word humains		125-134-35-234
+word humaine		125-134-1345
+word humaines		125-134-1345-234
+word humainement	125-134-1345-134
+word humanitaire	125-134-1345-2345-1235
+word humanitaires	125-134-1345-2345-1235-234
+word humanité		125-134-1345-2345
+word humanités		125-134-1345-2345-234
+
+word idéal		24-145-123
+word idéale		24-145-123-15
+word idéales		24-145-123-156
+word idéalement		24-145-123-134
+word idéaux		24-145-1346
+word idée		24-145
+word idées		24-145-234
+word image		24-1245
+word images		24-1245-234
+word imaginable		24-1245-45
+word imaginables	24-1245-45-234
+word imaginaire		24-1245-1235
+word imaginaires	24-1245-1235-234
+word imagination	24-1245-16
+word immédiat		24-134
+word immédiats		24-134-234
+word immédiate		24-134-15
+word immédiates		24-134-156
+word immédiatement	24-134-134
+word impression		246-235
+word impressions	246-235-234
+word impressionnable	246-235-45
+word impressionnables	246-235-45-234
+word inférieur		35-124
+word inférieurs		35-124-234
+word inférieure		35-124-15
+word inférieures	35-124-156
+word inférieurement	35-124-134
+word infériorité	35-124-2345
+word infériorités	35-124-2345-234
+word inquiet		35-12345
+word inquiets		35-12345-234
+word inquiète		35-2346
+word inquiètes		35-2346-234
+word inquiétude		35-12345-145
+word inquiétudes	35-12345-145-234
+word intelligemment	35-1245-134
+word intelligence	35-1245-14
+word intelligent	35-1245
+word intelligents	35-1245-234
+word intelligente	35-1245-15
+word intelligentes	35-1245-156
+word intérieur		35-2345
+word intérieurs		35-2345-234
+word intérieure		35-2345-15
+word intérieures	35-2345-156
+word intérieurement	35-2345-134
+
+word jadis		245-145
+word jamais		245-134
+word jeune		245-1345
+word jeunes		245-1345-234
+word jour		245-1235
+word jours		245-1235-234
+word journal		245-1235-123
+word journaux		245-1235-1346
+word joyeuse		245-234-15
+word joyeuses		245-234-156
+word joyeusement	245-234-134
+word joyeux		245-1346
+word juge		245-1245
+word juges		245-1245-234
+word jugement		245-1245-134
+word jugements		245-1245-134-234
+word jusque		245-12345
+word juste		245-2345
+word justes		245-2345-234
+word justement		245-2345-134
+word justice		245-14
+
+word laquelle		123-123-123
+word lecture		123-1235
+word lectures		123-1235-234
+word lequel		123-123
+word lesquelles		123-123-123-234
+word lesquels		123-123-234
+word lettre		123-356
+word lettres		123-356-234
+word libéral		123-12-123
+word libérale		123-12-123-15
+word libérales		123-12-123-156
+word libéralement	123-12-123-134
+word libéralité		123-12-123-2345
+word libéralités	123-12-123-2345-234
+word libérateur		123-12-46
+word libérateurs	123-12-46-234
+word libération		123-12-16
+word libérations	123-12-16-234
+word libéraux		123-12-1346
+word liberté		123-12-2345
+word libertés		123-12-2345-234
+word libre		123-12
+word libres		123-12-234
+word librement		123-12-134
+word ligne		123-2356
+word lignes		123-2356-234
+word livre		123-1236
+word livres		123-1236-234
+word logique		123-1245-12345
+word logiques		123-1245-12345-234
+word logiquement	123-1245-12345-134
+word loin		123-1345
+word loins		123-1345-234
+word lointain		123-1345-35
+word lointains		123-1345-35-234
+word lointaine		123-1345-1345
+word lointaines		123-1345-1345-234
+word longtemps		123-2345
+word lorsque		123-12345
+word lourd		123-145
+word lourds		123-145-234
+word lourde		123-145-15
+word lourdes		123-145-156
+word lourdement		123-145-134
+word lourdeur		123-145-46
+word lourdeurs		123-145-46-234
+word lumière		123-134
+word lumières		123-134-234
+word lumineuse		123-134-234-15
+word lumineuses		123-134-234-156
+word lumineusement	123-134-234-134
+word lumineux		123-134-1346
+word luminosité		123-134-234-2345
+word luminosités	123-134-234-2345-234
+
+word madame		134-145
+word mademoiselle	134-134
+word magnificence	134-2356-14
+word magnificences	134-2356-14-234
+word magnifique		134-2356
+word magnifiques	134-2356-234
+word magnifiquement	134-2356-134
+word maintenant		134-2345
+word malgré		134-12456
+word malheur		134-125
+word malheurs		134-125-234
+word malheureuse	134-125-234-15
+word malheureuses	134-125-234-156
+word malheureusement	134-125-234-134
+word malheureux		134-125-1346
+word manière		134-1345
+word manières		134-1345-234
+word mauvais		134-1236
+word mauvaise		134-1236-15
+word mauvaises		134-1236-156
+word meilleur		134-123
+word meilleurs		134-123-234
+word meilleure		134-123-15
+word meilleures		134-123-156
+word merci		134-14
+word mère		134-2346
+word mères		134-2346-234
+word mes		134-234
+word mesdames		134-145-234
+word mesdemoiselles	134-134-234
+word messieurs		134-1235-234
+word mettre		134-356
+word mieux		134-1346
+word mission		134-3456
+word missions		134-3456-234
+word missionnaire	134-3456-1235
+word missionnaires	134-3456-1235-234
+word mobile		134-12
+word mobiles		134-12-234
+word mobilisation	134-12-16
+word mobilisations	134-12-16-234
+word mobilité		134-12-2345
+word mobilités		134-12-2345-234
+word moins		134-35
+word moment		134-26
+word moments		134-26-234
+word momentanément	134-26-134
+word monsieur		134-1235
+word multiple		134-1246
+word multiples		134-1246-234
+word multiplicateur	134-1246-46
+word multiplicateurs	134-1246-46-234
+word multiplication	134-1246-16
+word multiplications	134-1246-16-234
+word multiplicité	134-1246-2345
+word multiplicités	134-1246-2345-234
+word musique		134-12345
+word musiques		134-12345-234
+word mystère		134-13456
+word mystères		134-13456-234
+word mystérieuse	134-13456-234-15
+word mystérieuses	134-13456-234-156
+word mystérieusement	134-13456-234-134
+word mystérieux		134-13456-1346
+
+word naguère		1345-1245
+word nation		1345-16
+word nations		1345-16-234
+word national		1345-16-123
+word nationale		1345-16-123-15
+word nationales		1345-16-123-156
+word nationalité	1345-16-123-2345
+word nationalités	1345-16-123-2345-234
+word nationaux		1345-16-1346
+word nature		1345-2345
+word natures		1345-2345-234
+word naturel		1345-2345-123
+word naturels		1345-2345-123-234
+word naturelle		1345-2345-123-123
+word naturelles		1345-2345-123-123-234
+word naturellement	1345-2345-123-134
+word néanmoins		1345-134
+word nécessaire		1345-14
+word nécessaires	1345-14-234
+word nécessairement	1345-14-134
+word nécessité		1345-14-2345
+word nécessités		1345-14-2345-234
+word nécessiteuse	1345-14-2345-234-15
+word nécessiteuses	1345-14-2345-234-156
+word nécessiteux	1345-14-2345-1346
+word nombre		1345-12
+word nombres		1345-12-234
+word nombreuse		1345-12-234-15
+word nombreuses		1345-12-234-156
+word nombreux		1345-12-1346
+word nos		1345-234
+word notre		1345-356
+word nôtre		1345-1456
+word nôtres		1345-1456-234
+word nouveau		1345-1236
+word nouveaux		1345-1236-1346
+word nouveauté		1345-1236-2345
+word nouveautés		1345-1236-2345-234
+word nouvel		1345-123
+word nouvelle		1345-123-123
+word nouvelles		1345-123-123-234
+word nouvellement	1345-123-134
+
+word objectif		135-245-124
+word objectifs		135-245-124-234
+word objection		135-245-3456
+word objections		135-245-3456-234
+word objective		135-245-1236
+word objectives		135-245-1236-234
+word objectivement	135-245-1236-134
+word objectivité	135-245-1236-2345
+word objectivités	135-245-1236-2345-234
+word objet		135-245
+word objets		135-245-234
+word observateur	135-12-46
+word observateurs	135-12-46-234
+word observation	135-12
+word observations	135-12-234
+word occasion		135-14
+word occasions		135-14-234
+word occasionnel	135-14-123
+word occasionnels	135-14-123-234
+word occasionnelle	135-14-123-123
+word occasionnelles	135-14-123-123-234
+word occasionnellement	135-14-123-134
+word oeuvre		246-1236
+word oeuvres		246-1236-234
+word office		135-124
+word offices		135-124-234
+word officiel		135-124-123
+word officiels		135-124-123-234
+word officielle		135-124-123-123
+word officielles		135-124-123-123-234
+word officiellement	135-124-123-134
+word officieuse		135-124-234-15
+word officieuses	135-124-234-156
+word officieusement	135-124-234-134
+word officieux		135-124-1346
+word opinion		135-1234
+word opinions		135-1234-234
+word ordinaire		56-145
+word ordinaires		56-145-234
+word ordinairement	56-145-134
+word originaire		135-1245-1235
+word originaires	135-1245-1235-234
+word originairement	135-1245-1235-134
+word original		135-1245-123
+word originale		135-1245-123-15
+word originales		135-1245-123-156
+word originalement	135-1245-123-134
+word originalité	135-1245-123-2345
+word originalités	135-1245-123-2345-234
+word originaux		135-1245-1346
+word origine		135-1245
+word origines		135-1245-234
+word outrage		1256-356-1245
+word outrages		1256-356-1245-234
+word outrageuse		1256-356-1245-234-15
+word outrageuses	1256-356-1245-234-156
+word outrageusement	1256-356-1245-234-134
+word outrageux		1256-356-1245-1346
+word outre		1256-356
+word ouvrage		1256-1245
+word ouvrages		1256-1245-234
+word ouvrier		1256-1236
+word ouvriers		1256-1236-234
+word ouvrière		1256-2346
+word ouvrières		1256-2346-234
+
+word parfois		1234-124
+word parmi		1234-134
+word parole		1234-1235
+word paroles		1234-1235-234
+word particularité	1234-1235-123-2345
+word particularités	1234-1235-123-2345-234
+word particulier	1234-123
+word particuliers	1234-123-234
+word particulière	1234-123-1235
+word particulières	1234-123-1235-234
+word particulièrement	1234-123-1235-134
+word partout		1234-1
+word pas		1234-234
+word pauvre		1234-1236
+word pauvres		1234-1236-234
+word pauvrement		1234-1236-134
+word pauvreté		1234-1236-2345
+word pauvretés		1234-1236-2345-234
+word pendant		1234-145
+word pensée		1234-26
+word pensées		1234-26-234
+word pensif		1234-26-124
+word pensifs		1234-26-124-234
+word pensive		1234-26-1236
+word pensives		1234-26-1236-234
+word pensivement	1234-26-1236-134
+word père		1234-2346
+word pères		1234-2346-234
+word personnage		1234-1345-1245
+word personnages	1234-1345-1245-234
+word personnalité	1234-1345-123-2345
+word personnalités	1234-1345-123-2345-234
+word personne		1234-1345
+word personnes		1234-1345-234
+word personnel		1234-1345-123
+word personnels		1234-1345-123-234
+word personnelle	1234-1345-123-123
+word personnelles	1234-1345-123-123-234
+word personnellement	1234-1345-123-134
+word petit		1234-15
+word petits		1234-15-234
+word petite		1234-15-15
+word petites		1234-15-156
+word peuple		1234-1246
+word peuples		1234-1246-234
+word peuplement		1234-1246-134
+word peuplements	1234-1246-134-234
+word place		1246-14
+word places		1246-14-234
+word placement		1246-14-134
+word placements		1246-14-134-234
+word plaisir		1246-1235
+word plaisirs		1246-1235-234
+word plusieurs		1246-234
+word plutôt		1246-2345
+word point		1234-2345
+word points		1234-2345-234
+word pointe		1234-2345-15
+word pointes		1234-2345-15-234
+word populaire		1234-1234-1235
+word populaires		1234-1234-1235-234
+word populairement	1234-1234-1235-134
+word popularité		1234-1234-1235-2345
+word popularités	1234-1234-1235-2345-234
+word population		1234-1234
+word populations	1234-1234-234
+word populeuse		1234-1234-234-15
+word populeuses		1234-1234-234-156
+word populeux		1234-1234-1346
+word possibilité	1234-12-2345
+word possibilités	1234-12-2345-234
+word possible		1234-12
+word possibles		1234-12-234
+word pourquoi		1234-23456
+word pourtant		1234-135
+word praticable		235-2345-45
+word praticables	235-2345-45-234
+word pratique		235-2345
+word pratiques		235-2345-234
+word pratiquement	235-2345-134
+word premier		235-134
+word premiers		235-134-234
+word première		235-1235
+word premières		235-1235-234
+word premièrement	235-1235-134
+word près		235-234
+word presque		235-12345
+word preuve		235-1236
+word preuves		235-1236-234
+word primitif		235-134-124
+word primitifs		235-134-124-234
+word primitive		235-134-1236
+word primitives		235-134-1236-234
+word primitivement	235-134-1236-134
+word principal		235-14-123
+word principale		235-14-123-15
+word principales	235-14-123-156
+word principalement	235-14-123-134
+word principaux		235-14-1346
+word principe		235-14
+word principes		235-14-234
+word prix		235-1346
+word probabilité	235-12-2345
+word probabilités	235-12-2345-234
+word probable		235-12
+word probables		235-12-234
+word probablement	235-12-134
+word prochain		235-12356
+word prochains		235-12356-234
+word prochaine		235-1345
+word prochaines		235-1345-234
+word prochainement	235-1345-134
+word producteur		235-145-46
+word producteurs	235-145-46-234
+word productif		235-145-124
+word productifs		235-145-124-234
+word production		235-145-3456
+word productions	235-145-3456-234
+word productive		235-145-1236
+word productives	235-145-1236-234
+word productivement	235-145-1236-134
+word productivité	235-145-1236-2345
+word productivités	235-145-1236-2345-234
+word produit		235-145
+word produits		235-145-234
+word profit		235-124
+word profits		235-124-234
+word profitable		235-124-45
+word profitables	235-124-45-234
+word profiteur		235-124-46
+word profiteurs		235-124-46-234
+word profiteuse		235-124-234-15
+word profiteuses	235-124-234-156
+word progrès		235-12456
+word progressif		235-12456-124
+word progressifs	235-12456-124-234
+word progression	235-12456-3456
+word progressions	235-12456-3456-234
+word progressive	235-12456-1236
+word progressives	235-12456-1236-234
+word progressivement	235-12456-1236-134
+word projecteur		235-245-46
+word projecteurs	235-245-46-234
+word projection		235-245-3456
+word projections	235-245-3456-234
+word projet		235-245
+word projets		235-245-234
+word proportion		235-1234
+word proportions	235-1234-234
+word proportionnalité	235-1234-123-2345
+word proportionnalités	235-1234-123-2345-234
+word proportionnel	235-1234-123
+word proportionnels	235-1234-123-234
+word proportionnelle	235-1234-123-123
+word proportionnelles	235-1234-123-123-234
+word proportionnellement 235-1234-123-134
+word proposition	235-246
+word propositions	235-246-234
+word puisque		1234-12345
+word puissance		1234-14
+word puissances		1234-14-234
+
+word qualitatif		12345-123-2345-124
+word qualitatifs	12345-123-2345-124-234
+word qualitative	12345-123-2345-1236
+word qualitatives	12345-123-2345-1236-234
+word qualitativement	12345-123-2345-1236-134
+word qualité		12345-123-2345
+word qualités		12345-123-2345-234
+word quand		12345-145
+word quant		12345-2345
+word quantitatif	12345-2345-2345-124
+word quantitatifs	12345-2345-2345-124-234
+word quantitative	12345-2345-2345-1236
+word quantitatives	12345-2345-2345-1236-234
+word quantitativement	12345-2345-2345-1236-134
+word quantité		12345-2345-2345
+word quantités		12345-2345-2345-234
+word quel		12345-123
+word quels		12345-123-234
+word quelle		12345-123-123
+word quelles		12345-123-123-234
+word quelconque		12345-14
+word quelconques	12345-14-234
+word quelque		12345-12345
+word quelques		12345-12345-234
+word quelquefois	12345-124
+word question		12345-3456
+word questions		12345-3456-234
+word questionnaire	12345-3456-1235
+word questionnaires	12345-3456-1235-234
+word quiconque		12345-346
+word quoique		12345-15
+
+word raison		1235-346
+word raisons		1235-346-234
+word raisonnable	1235-346-45
+word raisonnables	1235-346-45-234
+word raisonnablement	1235-346-45-134
+word raisonnement	1235-346-134
+word raisonnements	1235-346-134-234
+word rapport		1235-1234
+word rapports		1235-1234-234
+word rapporteur		1235-1234-46
+word rapporteurs	1235-1234-46-234
+word rare		1235-1235
+word rares		1235-1235-234
+word rarement		1235-1235-134
+word rareté		1235-1235-2345
+word raretés		1235-1235-2345-234
+word réalisable		1235-123-45
+word réalisables	1235-123-45-234
+word réalisateur	1235-123-46
+word réalisateurs	1235-123-46-234
+word réalisation	1235-123-16
+word réalisations	1235-123-16-234
+word réalité		1235-123-2345
+word réalités		1235-123-2345-234
+word réel		1235-123
+word réels		1235-123-234
+word réelle		1235-123-123
+word réelles		1235-123-123-234
+word réellement		1235-123-134
+word réflexion		1235-124
+word réflexions		1235-124-234
+word regard		1235-1245
+word regards		1235-1245-234
+word regret		1235-12456
+word regrets		1235-12456-234
+word regrettable	1235-12456-45
+word regrettables	1235-12456-45-234
+word relatif		1235-2345-124
+word relatifs		1235-2345-124-234
+word relation		1235-2345
+word relations		1235-2345-234
+word relative		1235-2345-1236
+word relatives		1235-2345-1236-234
+word relativement	1235-2345-1236-134
+word relativité		1235-2345-1236-2345
+word relativités	1235-2345-1236-2345-234
+word remarquable	1235-12345-45
+word remarquables	1235-12345-45-234
+word remarquablement	1235-12345-45-134
+word remarque		1235-12345
+word remarques		1235-12345-234
+word remerciement	1235-134
+word remerciements	1235-134-234
+word renseignement	1235-26
+word renseignements	1235-26-234
+word rêve		1235-126
+word rêves		1235-126-234
+word rêveur		1235-126-46
+word rêveurs		1235-126-46-234
+word rêveuse		1235-126-234-15
+word rêveuses		1235-126-234-156
+word rêveusement	1235-126-234-134
+word rôle		1235-1456
+word rôles		1235-1456-234
+word route		1235-1256
+word routes		1235-1256-234
+word rythme		1235-13456
+word rythmes		1235-13456-234
+word rythmique		1235-13456-12345
+word rythmiques		1235-13456-12345-234
+word rythmiquement	1235-13456-12345-134
+
+word séculaire		234-14-1235
+word séculaires		234-14-1235-234
+word séculairement	234-14-1235-134
+word seigneur		234-2356
+word seigneurs		234-2356-234
+word semblable		234-12
+word semblables		234-12-234
+word semblablement	234-12-134
+word sentiment		234-2345-134
+word sentiments		234-2345-134-234
+word sentimental	234-2345-134-123
+word sentimentale	234-2345-134-123-15
+word sentimentales	234-2345-134-123-156
+word sentimentalement	234-2345-134-123-134
+word sentimentalité	234-2345-134-123-2345
+word sentimentalités	234-2345-134-123-2345-234
+word sentimentaux	234-2345-134-1346
+word ses		234-234
+word seul		234-123
+word seuls		234-123-234
+word seule		234-123-15
+word seules		234-123-156
+word seulement		234-123-134
+word siècle		234-14
+word siècles		234-14-234
+word simple		234-1246
+word simples		234-1246-234
+word simplement		234-1246-134
+word simplicité		234-1246-2345
+word simplicités	234-1246-2345-234
+word simplification	234-1246-16
+word simplifications	234-1246-16-234
+word soeur		234-1235
+word soeurs		234-1235-234
+word soin		234-35
+word soins		234-35-234
+word solitaire		234-123-1235
+word solitaires		234-123-1235-234
+word solitairement	234-123-1235-134
+word solitude		234-123-145
+word solitudes		234-123-145-234
+word sommaire		234-2456-1235
+word sommaires		234-2456-1235-234
+word sommairement	234-2456-1235-134
+word somme		234-2456
+word sommes		234-2456-234
+word sont		246-2345
+word sorte		234-135
+word sortes		234-135-234
+word soudain		234-145
+word soudains		234-145-234
+word soudaine		234-1345
+word soudaines		234-1345-234
+word soudainement	234-1345-134
+word soudaineté		234-1345-2345
+word soudainetés	234-1345-2345-234
+word souffrance		234-124-14
+word souffrances	234-124-14-234
+word souffrant		234-124
+word souffrants		234-124-234
+word souffrante		234-124-15
+word souffrantes	234-124-156
+word souvent		234-1236
+word subjectif		234-245-124
+word subjectifs		234-245-124-234
+word subjective		234-245-1236
+word subjectives	234-245-1236-234
+word subjectivement	234-245-1236-134
+word subjectivité	234-245-1236-2345
+word subjectivités	234-245-1236-2345-234
+word sujet		234-245
+word sujets		234-245-234
+word sujétion		234-245-3456
+word sujétions		234-245-3456-234
+word supérieur		234-1234
+word supérieurs		234-1234-234
+word supérieure		234-1234-15
+word supérieures	234-1234-156
+word supérieurement	234-1234-134
+word supériorité	234-1234-2345
+word supériorités	234-1234-2345-234
+word surtout		234-2345
+word systématique	234-13456-12345
+word systématiques	234-13456-12345-234
+word systématiquement	234-13456-12345-134
+word système		234-13456
+word systèmes		234-13456-234
+
+word tel		2345-123
+word tels		2345-123-234
+word telle		2345-123-123
+word telles		2345-123-123-234
+word tellement		2345-123-134
+word temporaire		2345-1234-1235
+word temporaires	2345-1234-1235-234
+word temporairement	2345-1234-1235-134
+word temporel		2345-1234-123
+word temporels		2345-1234-123-234
+word temporelle		2345-1234-123-123
+word temporelles	2345-1234-123-123-234
+word temps		2345-1234
+word tenir		2345-1345
+word terre		2345-1235
+word terres		2345-1235-234
+word tes		2345-234
+word tête		2345-126
+word têtes		2345-126-234
+word théorie		2345-125
+word théories		2345-125-234
+word théorique		2345-125-12345
+word théoriques		2345-125-12345-234
+word théoriquement	2345-125-12345-134
+word titre		2345-356
+word titres		2345-356-234
+word toujours		2345-245
+word toute		2345-2345
+word toutes		2345-2345-234
+word toutefois		2345-124
+word tragique		356-1245
+word tragiques		356-1245-234
+word tragiquement	356-1245-134
+word trajet		356-245
+word trajets		356-245-234
+word tranquille		356-12345
+word tranquilles	356-12345-234
+word tranquillement	356-12345-134
+word tranquillité	356-12345-2345
+word tranquillités	356-12345-2345-234
+word travail		356-123
+word travailleur	356-123-46
+word travailleurs	356-123-46-234
+word travailleuse	356-123-234-15
+word travailleuses	356-123-234-156
+word travaux		356-1346
+word travers		356-1236
+word très		356-234
+word trop		356-1234
+word type		2345-13456
+word types		2345-13456-234
+word typique		2345-13456-12345
+word typiques		2345-13456-12345-234
+word typiquement	2345-13456-12345-134
+
+word une		136-1345
+word unes		136-1345-234
+word unique		136-12345
+word uniques		136-12345-234
+word uniquement		136-12345-134
+word unitaire		136-1345-2345-1235
+word unitaires		136-1345-2345-1235-234
+word unité		136-1345-2345
+word unités		136-1345-2345-234
+word univers		136-1236
+word universalité	136-1236-123-2345
+word universalités	136-1236-123-2345-234
+word universel		136-1236-123
+word universels		136-1236-123-234
+word universelle	136-1236-123-123
+word universelles	136-1236-123-123-234
+word universellement	136-1236-123-134
+word universitaire	136-1236-2345-1235
+word universitaires	136-1236-2345-1235-234
+word université		136-1236-2345
+word universités	136-1236-2345-234
+word usage		136-1245
+word usages		136-1245-234
+word utile		136-123
+word utiles		136-123-234
+word utilement		136-123-134
+word utilisable		136-123-45
+word utilisables	136-123-45-234
+word utilisateur	136-123-46
+word utilisateurs	136-123-46-234
+word utilisation	136-123-16
+word utilisations	136-123-16-234
+word utilitaire		136-123-2345-1235
+word utilitaires	136-123-2345-1235-234
+word utilité		136-123-2345
+word utilités		136-123-2345-234
+
+word valeur		1236-46
+word valeurs		1236-46-234
+word venir		1236-1345
+word véritable		1236-1235
+word véritables		1236-1235-234
+word véritablement	1236-1235-134
+word vérité		1236-123456
+word vérités		1236-123456-234
+word vieux		1236-1346
+word vif		1236-124
+word vifs		1236-124-234
+word vive		1236-1236
+word vives		1236-1236-234
+word vivement		1236-1236-134
+word voici		1236-14
+word voilà		1236-123
+word volontaire		1236-135-1235
+word volontaires	1236-135-1235-234
+word volontairement	1236-135-1235-134
+word volonté		1236-135
+word volontés		1236-135-234
+word volontiers		1236-346
+word vos		1236-234
+word votre		1236-356
+word vôtre		1236-1456
+word vôtres		1236-1456-234
+word voyage		1236-1245
+word voyages		1236-1245-234
+word voyageur		1236-1245-46
+word voyageurs		1236-1245-46-234
+word voyageuse		1236-1245-234-15
+word voyageuses		1236-1245-234-156
+word vraiment		1236-134
+
+###
+### 4. Locutions
+###
+
+word à\scause 12356-456-14
+word à\smesure 12356-456-134
+word à\speine 12356-456-1234
+word à\speu\sprès 12356-456-1234-456-235
+word à\sprésent 12356-456-235
+word à\stravers 12356-456-356
+word au\scontraire 13-456-14
+word au-dessous 13-36-1256
+word au-dessus 13-36-145
+word aujourd'hui 13-3-125
+word autant\sque 13-456-12345
+word autre\schose 13-456-12356
+word autre\spart 13-456-1234
+word c'est-à-dire 14-3-15-36-12356-36-145
+word d'abord 145-3-1
+word de\ssuite 145-456-234
+word en\smesure 26-456-134
+word en\sréalité 26-456-1235
+word et\scétera 15-456-14
+word la\splupart 123-456-1246
+word non\sseulement 1345-456-234
+word parce\sque 1234-456-12345
+word par\sconséquent 1234-456-14
+word par-dessous 1234-36-1256
+word par-dessus 1234-36-145
+word par\sexemple 1234-456-15
+word par\ssuite 1234-456-234
+word peu\sà\speu 1234-456-12356-456-1234
+word peut-être 1234-36-126
+word plus\stard 1246-456-2345
+word plus\stôt 1246-456-1456
+word pour\sainsi\sdire 1234-456-1-456-145
+word quelque\schose 12345-456-12356
+word quelque\spart 12345-456-1234
+word quelque\stemps 12345-456-2345
+word sans\scesse 234-456-14
+word sans\sdoute 234-456-145
+word tandis\sque 2345-456-12345
+word tour\sà\stour 2345-456-12356-456-2345
+word tout\sà\scoup 16-456-12356-456-14
+word tout\sà\sfait 16-456-12356-456-124
+word très\sbien 356-456-12
+word vis-à-vis 1236-36-12356-36-1236
+
+sufword jusqu' 245-12345-3
+sufword lorsqu' 123-12345-3
+sufword parce\squ' 1234-456-12345-3
+
+# inline contraction of emoji descriptions
+emoji fr
diff --git a/Tables/Contraction/fr.ctb b/Tables/Contraction/fr.ctb
new file mode 100644
index 0000000..9e8527f
--- /dev/null
+++ b/Tables/Contraction/fr.ctb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - French
+
+include fr-g2.ctb
diff --git a/Tables/Contraction/ha.ctb b/Tables/Contraction/ha.ctb
new file mode 100644
index 0000000..61d8500
--- /dev/null
+++ b/Tables/Contraction/ha.ctb
@@ -0,0 +1,40 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Hausa (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always 'b 23
+always 'd 1246
+always kw 12345
+always k' 46
+always ng 346
+always t' 23456
+always ch 16
+always sh 146
+always 'y 3
+always au 246
+always ts 34
diff --git a/Tables/Contraction/id.ctb b/Tables/Contraction/id.ctb
new file mode 100644
index 0000000..c63d7ef
--- /dev/null
+++ b/Tables/Contraction/id.ctb
@@ -0,0 +1,50 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Indonesian (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always ai 34
+always au 246
+always sj 146
+always ng 346
+always ch 16
+
+always ? 26
+
+capsign 46
+
+numsign 6
+always 0 3456
+always 1 16
+always 2 126
+always 3 146
+always 4 1456
+always 5 156
+always 6 1246
+always 7 12456
+always 8 1256
+always 9 246
diff --git a/Tables/Contraction/ipa.ctb b/Tables/Contraction/ipa.ctb
new file mode 100644
index 0000000..daca8da
--- /dev/null
+++ b/Tables/Contraction/ipa.ctb
@@ -0,0 +1,225 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - International Phonetic Alphabet
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This is based on the ICEB Braille IPA Draft Table, Nov. 3, 2005
+
+#               Typographic Description   IPA No Articulatory Description
+# consonants
+always p 1234		# lowercase p                  101 voiceless bilabial          plosive
+always b 12		# lowercase b                  102 voiced    bilabial          plosive
+always t 2345		# lowercase t                  103 voiceless alveolar          plosive
+always d 145		# lowercase d                  104 voiced    alveolar          plosive
+always ʈ 256-2345	# right-tail t                 105 voiceless retroflex         plosive
+always ɖ 256-145	# right-tail d                 106 voiced    retroflex         plosive
+always c 14		# lowercase c                  107 voiceless palatal           plosive
+always ɟ 35-245		# barred dotless j             108 voiced    palatal           plosive
+always k 13		# lowercase k                  109 voiceless velar             plosive
+always ɡ 1245		# lowercase script g           110 voiced    velar             plosive
+always g 1245		# lowercase g                  110 voiced    velar             plosive (alt. glyph)
+always q 12345		# lowercase q                  111 voiceless uvular            plosive
+always ɢ 35-1245	# small capital g              112 voiced    uvular            plosive
+always ʔ 23		# glottal stop                 113 glottal                     plosive
+always m 134		# lowercase m                  114 voiced    bilabial          nasal
+always ɱ 235-134	# left-tail m (at right)       115 voiced    labiodental       nasal
+always n 1345		# lowercase n                  116 voiced    alveolar          nasal
+always ɳ 256-1345	# right-tail n                 117 voiced    retroflex         nasal
+always ɲ 123456		# left-tail n (at left)        118 voiced    palatal           nasal
+always ŋ 1246		# eng                          119 voiced    velar             nasal
+always ɴ 35-1345	# small capital n              120 voiced    uvular            nasal
+always ʙ 35-12		# small capital b              121 voiced    bilabial          trill
+always r 1235		# lowercase r                  122 voiced    alveolar          trill
+always ʀ 35-1235	# small capital r              123 voiced    uvular            trill
+#          235-1236	# right-hook v                               labiodental       flap
+always ɾ 235-1235	# fish-hook r                  124 voiced    alveolar          tap
+always ɽ 256-1235	# right-tail r                 125 voiced    retroflex         flap
+always ɸ 46-124		# phi                          126 voiceless bilabial          fricative
+always β 46-12		# beta                         127 voiced    bilabial          fricative
+always f 124		# lowercase f                  128 voiceless labiodental       fricative
+always v 1236		# lowercase v                  129 voiced    labiodental       fricative
+always θ 46-1456	# theta                        130 voiceless dental            fricative
+always ð 12456		# edh                          131 voiced    dental            fricative
+always s 234		# lowercase s                  132 voiceless alveolar          fricative
+always z 1356		# lowercase z                  133 voiced    alveolar          fricative
+always ʃ 156		# esh                          134 voiceless postalveolar      fricative
+always ʒ 2346		# yogh                         135 voiced    postalveolar      fricative
+always ʂ 256-234	# right-tail s (at left)       136 voiceless retroflex         fricative
+always ʐ 256-1356	# right-tail z                 137 voiced    retroflex         fricative
+always ç 235-14		# c cedilla                    138 voiceless palatal           fricative
+always ʝ 236-245	# curly-tail j                 139 voiced    palatal           fricative
+always x 1346		# lowercase x                  140 voiceless velar             fricative
+always ɣ 46-1245	# gamma                        141 voiced    velar             fricative
+always χ 46-12346	# chi                          142 voiceless uvular            fricative
+always ʁ 35-3456	# inverted small capital r     143 voiced    uvular            fricative
+always ħ 235-125	# crossed h                    144 voiceless pharyngeal        fricative
+always ʕ 235-23		# reversed glottal stop        145 voiced    pharyngeal        fricative
+always h 125		# lowercase h                  146 voiceless glottal           fricative
+always ɦ 236-125	# hooktop h                    147 voiced    glottal           fricative
+always ɬ 236-123	# belted l                     148 voiceless alveolar lateral  fricative
+always ɮ 123-5-2346	# l-yogh digraph              149 voiced    alveolar lateral  fricative
+always ʋ 236-1236	# script v                     150 voiced    labiodental       approximant
+always ɹ 3456		# turned r                     151 voiced    (post)alveolar    approximant
+always ɻ 256-3456	# turned r, right tail         152 voiced    retroflex         approximant
+always j 245		# lowercase j                  153 voiced    palatal           approximant
+always ɰ 236-134	# turned m, right leg          154 voiced    velar             approximant
+always l 123		# lowercase l                  155 voiced    alveolar lateral  approximant
+always ɭ 256-123	# right-tail l                 156 voiced    retroflex lateral approxiant
+always ʎ 236-13456	# turned y                     157 voiced    palatal lateral   approximant
+always ʟ 35-123		# small capital l              158 voiced    velar lateral     approximant
+always ɓ 236-12		# hooktop b                    160 voiced    bilabial          implosive
+always ɗ 236-145	# hooktop d                    162 voiced    dental/alveolar   implosive
+always ʄ 236-35-245	# hooktop barred dotless j    164 voiced    palatal           implosive
+always ɠ 236-1245	# hooktop g                    166 voiced    velar             implosive
+always ʛ 236-35-1245	# hooktop small capital g     168 voiced    uvular            implosive
+always ʍ 235-2456	# turned w                     169 voiceless labial-velar      fricative
+always w 2456		# lowercase w                  170 voiced    labial-velar      approximant
+always ɥ 256-125	# turned h                     171 voiced    labial-palatal    approximant
+always ʜ 35-125		# small capital h              172 voiceless epiglottal        fricative
+always ʡ 236-23		# barred glottal stop          173           epiglottal        plosive
+always ʢ 35-23		# barred reversed glottal stop 174 voiced    epiglottal        fricative
+always ɧ 236-1246	# hooked eng                   175 voiceless multiple-place    fricative
+always ʘ 12346-1234	# bull's eye                   176           bilabial          click
+always ǀ 12346-1456	# pipe                         177           dental            click
+always ǃ 12346-2345	# exclamation point            178           (post-)alveolar   click
+always ǂ 12346-156	# double-barred pipe           179           palatoalveolar    click
+always ǁ 12346-123	# double pipe                  180           alveolar lateral  click
+always ɺ 236-3456	# turned long-leg r            181 voiced    alveolar lateral  flap
+always ɕ 236-14		# curly-tail c                 182 voiceless alveolopalatal    fricative
+always ʑ 236-1356	# curly-tail z                 183 voiced    alveolopalatal    fricative
+always ɫ 235-123	# lowercase l with tilde       209 velarized voiced alveolar   lateral
+always ʣ 145-5-1356	# d-z digraph                     voiced    alveolar          affricate
+always ʤ 145-5-2346	# d-yogh digraph                  voiced    postalveolar      affricate
+always ʥ 145-5-236-1356	# d-curly-tail-z digraph         voiced    alveolopalatal    affricate
+always ʦ 2345-5-234	# t-s digraph                     voiceless alveolar          affricate
+always ʧ 2345-5-156	# t-esh digraph                   voiceless postalveolar      affricate
+always ʨ 2345-5-236-14	# t-curly-tail-c digraph         voiceless alveolopalatal    affricate
+
+# Voyels
+always i 24		# lowercase i                  301 close      front    unrounded vowel
+always e 15		# lowercase e                  302 close-mid  front    unrounded vowel
+always ɛ 345		# epsilon                      303 open-mid   front    unrounded vowel
+always a 1		# lowercase a                  304 open       front    unrounded vowel
+always ɑ 16		# script a                     305 open       back     unrounded vowel
+always ɔ 126		# open o                       306 open-mid   back       rounded vowel
+always o 135		# lowercase o                  307 close-mid  back       rounded vowel
+always u 136		# lowercase u                  308 close      back       rounded vowel
+always y 13456		# lowercase y                  309 close      front      rounded vowel
+always ø 1256		# slashed o                    310 close-mid  front      rounded vowel
+always œ 246		# o-e digraph                  311 open-mid   front      rounded vowel
+always ɶ 35-246		# small capital o-e digraph    312 open       front      rounded vowel
+always ɒ 235-16		# turned script a              313 open       back       rounded vowel
+always ʌ 346		# turned v (caret)             314 open-mid   back     unrounded vowel
+always ɤ 235-135	# ram's horns                  315 close-mid  back     unrounded vowel
+always ɯ 235-136	# turned m                     316 close      back     unrounded vowel
+always ɨ 356-24		# barred i                     317 close      central  unrounded vowel
+always ʉ 356-136	# barred u                     318 close      central    rounded vowel
+always ɪ 34		# small capital i              319 near-close front    unrounded vowel
+always ʏ 35-13456	# small capital y              320 near-close front      rounded vowel
+always ʊ 12356		# upsilon                      321 near-close back       rounded vowel
+always ə 26		# schwa                        322 mid        central  unrounded vowel
+always ɵ 356-135	# barred o                     323 close-mid  central    rounded vowel
+always ɐ 235-1		# turned a                     324 near-open  central  unrounded vowel
+always æ 146		# ash                          325 near-open  front    unrounded vowel
+always ɜ 235-345	# reversed epsilon             326 open-mid   central  unrounded vowel
+always ɚ 26-5-1235	# schwa with hook             327 rhotacized schwa
+always ɞ 236-345	# closed reversed epsilon      395 open-mid   central    rounded vowel
+always ɘ 235-15		# reversed e                   397 open-mid   central  unrounded vowel
+
+always ʼ 5-3		# apostrophe                   401 ejective
+always  ̥ 6-1246		# ring below                   402 voiceless
+always  ̊ 4-1246		# ring above                   402 voiceless
+always  ̬ 6-236		# wedge below                  403 voiced
+always ʰ 4-125		# superscript h                404 aspirated
+always  ̤ 6-25		# umlaut below                 405 breathy voiced
+always  ̰ 6-12456	# tilde below                  406 creaky voiced
+always  ̼ 6-12346	# seagull below                407 linguolabial
+always  ̪ 6-1456		# bridge below                 408 dental
+always  ̺ 6-235-1456	# inverted bridge below       409 apical
+always  ̻ 6-2356		# square below                 410 laminal
+always  ̹ 6-135		# right half-ring below        411 more rounded
+always  ̜ 6-246		# left half-ring below         412 less rounded
+always  ̟ 6-346		# plus below                   413 advanced
+always  ̠ 6-36		# minus below                  414 retracted
+always  ̈ 4-25		# umlaut above                 415 centralized
+always  ̽ 4-1346		# over-cross above             416 mid-centralized
+always  ̘ 6-156		# advancing sign below         417 advanced tongue root
+always  ̙ 6-234		# retracting sign below        418 retracted tongue root
+always ˞ 5-1235		# right hook                   419 rhotacized
+always ʷ 4-2456		# superscript w                420 labialized
+always ʲ 4-245		# superscript j                421 palatalized
+always ˠ 4-46-1245	# superscript gamma           422 velarized
+always ˤ 4-235-23	# superscript reversed glottal stop 423 pharyngealized
+always  ̃ 4-12456	# tiled above                  424 nasalized
+always ⁿ 4-1345		# superscript n                425 nasal release
+always ˡ 4-123		# superscript l                426 lateral release
+always  ̚ 4-145		# corner above                 427 no audible release
+always  ̴ 5-12456	# superimposed tilde           428 velarized or pharyngealized
+always  ̝ 6-345		# raising sign below           429 raised
+always  ̞ 6-126		# lowering sign below          430 lowered
+always  ̩ 6-23		# vertical line below          431 syllabic
+always  ̯ 6-23456	# arch below                   432 non-syllabic
+always  ͡ 5		# top tie bar                  433 double articulation
+always , 2		# comma                        491 (punctuation)
+
+always ˈ 456-12		# vertical stroke (superior)   501 (primary) stress mark
+always ˌ 456-23		# vertical stroke (inferior)   502 secondary stress mark
+always ː 25		# length mark                  503 length mark
+always ˑ 5-2		# half-length mark             504 half-length
+always  ̆ 4-12356	# breve above                  505 extra-short
+always . 3		# period                       506 syllable break
+always | 456-1256	# vertical line                507 minor (foot) group
+always ‖ 456-123456	# double vertical line         508 major (intonation) group
+always ‿ 456-123	# bottom tie bar               509 linking (absence of a break)
+always ↗ 456-145	# upward diagonal arrow        510 global rise
+always ↘ 456-356	# downward diagonal arrow      511 global fall
+always  ̋ 4-6-34		# double acute accent above   512 extra high tone
+always  ́ 4-34		# acute accent above           513 high tone
+always  ̄ 4-14		# macron above                 514 mid tone
+always  ̀ 4-16		# grave accent above           515 low tone
+always  ̏ 4-6-16		# double grave accent above   516 extra low tone
+always ↓ 456-2346	# down arrow                   517 downstep
+always ↑ 456-1246	# up arrow                     518 upstep
+always ˥ 456-4-14	# extra-high tone bar (55)    519 extra hight tone
+always ˦ 456-14		# high tone bar (44)           520 hight tone
+always ˧ 456-25		# mid tone bar (33)            521 mid tone
+always ˨ 456-36		# low tone bar (22)            522 low tone
+always ˩ 456-6-36	# extra-low tone bar (11)     523 extra low tone
+always  ̌ 4-236		# wedge above                  524 rising tone
+#          456-34	# rising tone bar (15)             rising tone
+always  ̂ 4-146		# circumflex above             525 falling tone
+#          456-16	# falling tone bar (15)            falling tone
+always  4-24		# macro-acute above            526 high-rising tone
+#          456-24	# high-rising tone bar (35)        high-rising tone
+always  4-35		# grave-macro above            527 low-rising tone
+#          456-35	# low-rising tone bar (13)         low-rising tone
+always  4-256		# grave-acute-grave above      528 rising-falling tone
+
+# also used for switch into/out into/out of phonetic code.
+always [ 45-12356	# left square bracket          901 opening phonetic brackets
+always ] 45-23456	# right square bracket         902 closing phonetic brackets
+
+always / 45-34		# slash                            phonemic enclosure
+always → 456-135	# rightward arrow                  becomes (is realized as)
+always - 36		# hyphen (dash)                    (punctuation)
+
+# 56		# The following symbol is non-IPA and should be read in accordance with the primary braille code of the document
+# 56-56		# The following passage is non-IPA and should be read in accordance with the primary braille code of the document
+# 56-23		# Terminates a passage of non-IPA texte (opened by 56-56); return to IPA code
diff --git a/Tables/Contraction/ja.ctb b/Tables/Contraction/ja.ctb
new file mode 100644
index 0000000..73c0389
--- /dev/null
+++ b/Tables/Contraction/ja.ctb
@@ -0,0 +1,490 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Japanese (uncontracted)
+#
+# Copyright (C) 2005 by Sun Microsystems Inc., All rights reserved.
+#
+# Table generated by Bill Haneman <billh@gnome.org>
+# for gnome-braille (cvs module gnome-braille, cvs.gnome.org)
+#
+# Adapted for BRLTTY by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# katakana
+always ァ 1
+always ア 1
+always イ 12
+always ゥ 14
+always ウ 14
+always ェ 124
+always エ 124
+always オ 24
+always カ 16
+always キ 126
+always ク 146
+always ケ 1246
+always コ 246
+always サ 156
+always シ 1256
+always ス 1456
+always セ 12456
+always ソ 2456
+always タ 135
+always チ 1235
+always ツ 1435
+always テ 12435
+always ト 2435
+always ナ 13
+always ニ 123
+always ヌ 143
+always ネ 1243
+always ノ 243
+always ハ 136
+always ヒ 1236
+always フ 1436
+always ヘ 12436
+always ホ 2436
+always マ 1356
+always ミ 12356
+always ム 14356
+always メ 124356
+always モ 24356
+always ヤ 34
+always ユ 346
+always ョ 345
+always ヨ 345
+always ラ 15
+always リ 125
+always ル 145
+always レ 1245
+always ロ 245
+always ワ 3
+always ヲ 35
+always ン 356
+always ッ 2
+always ガ 5-16
+always ギ 5-126
+always グ 5-146
+always ゲ 5-1246
+always ゴ 5-246
+always ザ 5-156
+always ジ 5-1256
+always ズ 5-1456
+always ゼ 5-12456
+always ゾ 5-2456
+always ダ 5-135
+always ヂ 5-1235
+always ヅ 5-1435
+always デ 5-12435
+always ド 5-2435
+always バ 5-136
+always ビ 5-1236
+always ブ 5-1436
+always ベ 5-12436
+always ボ 5-2436
+always パ 6-136
+always ピ 6-1236
+always プ 6-1436
+always ペ 6-12436
+always ポ 6-2436
+
+# halfwidth chars
+always ヲ 35
+always ア 1
+always ァ 1
+always イ 12
+always ィ 12
+always ウ 14
+always ゥ 14
+always エ 124
+always ェ 124
+always オ 24
+always ォ 24
+always カ 16
+always キ 126
+always ク 146
+always ケ 1246
+always コ 246
+always サ 156
+always シ 1256
+always ス 1456
+always セ 12456
+always ソ 2456
+always タ 135
+always チ 1235
+always ッ 1435
+always ツ 1435
+always テ 12435
+always ト 2435
+always ナ 13
+always ニ 123
+always ヌ 143
+always ネ 1243
+always ノ 243
+always ハ 136
+always ヒ 1236
+always フ 1436
+always ヘ 12436
+always ホ 2436
+always マ 1356
+always ミ 12356
+always ム 14356
+always メ 124356
+always モ 24356
+always ャ 34
+always ヤ 34
+always ュ 346
+always ユ 346
+always ョ 345
+always ヨ 345
+always ラ 15
+always リ 125
+always ル 145
+always レ 1245
+always ロ 245
+always ワ 3
+always ン 356
+
+# hiregana
+always あ 1
+always い 12
+always う 14
+always え 124
+always お 24
+always か 16
+always き 126
+always く 146
+always け 1246
+always こ 246
+always さ 156
+always し 1256
+always す 1456
+always せ 12456
+always そ 2456
+always た 135
+always ち 1235
+always つ 1435
+always て 12435
+always と 2435
+always な 13
+always に 123
+always ぬ 143
+always ね 1243
+always の 243
+always は 136
+always ひ 1236
+always ふ 1436
+always へ 12436
+always ほ 2436
+always ま 1356
+always み 12356
+always む 14356
+always め 124356
+always も 24356
+always や 34
+always ゆ 346
+always よ 345
+always ら 15
+always り 125
+always る 145
+always れ 1245
+always ろ 245
+always わ 3
+always を 35
+always ん 356
+always っ 2
+always が 5-16
+always ぎ 5-126
+always ぐ 5-146
+always げ 5-1246
+always ご 5-246
+always ざ 5-156
+always じ 5-1256
+always ず 5-1456
+always ぜ 5-12456
+always ぞ 5-2456
+always だ 5-135
+always ぢ 5-1235
+always づ 5-1435
+always で 5-12435
+always ど 5-2435
+always ば 5-136
+always び 5-1236
+always ぶ 5-1436
+always べ 5-12436
+always ぼ 5-2436
+always ぱ 6-136
+always ぴ 6-1236
+always ぷ 6-1436
+always ぺ 6-12436
+always ぽ 6-2436
+
+# check this: digraph 'yori'
+always ゟ 435-125
+
+always 。 256
+always 、 56
+always 、 56
+always 。 256
+always " 36
+always ? 26
+always ! 235
+always ( 2356
+always ) 2356
+always , 56
+always . 256
+always ー 25
+
+always ・ 5
+always ヿ 246-26
+
+numsign 3456
+
+# You-on for ya suffix, yu, etc.
+always カャ 4-16
+always サャ 4-156
+always タャ 4-135
+always ナャ 4-13
+always ハャ 4-136
+always マャ 4-1356
+always ラャ 4-15
+always ガャ 45-16
+always ザャ 45-156
+always ダャ 45-135
+always バャ 45-136
+always パャ 46-136
+
+always カャ   4-16
+always サャ   4-156
+always タャ   4-135
+always ナャ   4-13
+always ハャ   4-136
+always マャ   4-1356
+always ラャ   4-15
+
+always かゃ 4-16
+always さゃ 4-156
+always たゃ 4-135
+always なゃ 4-13
+always はゃ 4-136
+always まゃ 4-1356
+always らゃ 4-15
+always がゃ 45-16
+always ざゃ 45-156
+always だゃ 45-135
+always ばゃ 45-136
+always ぱゃ 46-136
+
+always クュ 4-146
+always スュ 4-1456
+always ツュ 4-1435
+always ヌュ 4-143
+always フュ 4-1436
+always ムュ 4-14356
+always ラュ 4-145
+always ガュ 45-146
+always ザュ 45-1456
+always ジュ 45-1435
+always バュ 45-1436
+always パュ 46-1436
+
+always クュ   4-146
+always スュ   4-1456
+always ツュ   4-1435
+always ヌュ   4-143
+always フュ   4-1436
+always ムュ   4-14356
+always ラュ   4-145
+
+always くゅ 4-146
+always すゅ 4-1456
+always つゅ 4-1435
+always ぬゅ 4-143
+always ふゅ 4-1436
+always むゅ 4-14356
+always らゅ 4-145
+always がゅ 45-146
+always ざゅ 45-1456
+always じゅ 45-1435
+always ばゅ 45-1436
+always ぱゅ 46-1436
+
+always コョ 4-246
+always ソョ 4-2456
+always トョ 4-2435
+always ノョ 4-243
+always ホョ 4-2436
+always モョ 4-24356
+always ロョ 4-245
+always ゴョ 45-246
+always ゾョ 45-2456
+always ヂョ 45-2435
+always ボョ 45-243
+always ポョ 46-243
+
+always コョ   4-246
+always ソョ   4-2456
+always トョ   4-2435
+always ノョ   4-243
+always ホョ   4-2436
+always モョ   4-24356
+
+always こょ 4-246
+always そょ 4-2456
+always とょ 4-2435
+always のょ 4-243
+always ほょ 4-2436
+always もょ 4-24356
+always ろょ 4-245
+always ごょ 45-246
+always ぞょ 45-2456
+always ぢょ 45-2435
+always ぼょ 45-243
+always ぽょ 46-243
+
+# vowel suffix contexts
+
+# ye
+always イェ 4-124
+always  イェ  4-124
+always いぇ 4-124
+
+# wi
+always ウィ 35-12
+always  ウィ  35-12
+always うぃ 35-12
+
+# we
+always ウェ 35-124
+always  ウェ  35-124
+always うぇ 35-124
+
+# wo
+always ウォ 35-24
+always  ウォ  35-24
+always うぉ 35-24
+
+# tsa
+always ツァ 26-135
+always  ツァ  26-135
+always つぁ 26-135
+
+# tsi
+always ツィ 26-1235
+always  ツィ  26-1235
+always つぃ 26-1235
+
+# tse
+always ツェ 26-12435
+always  ツェ  26-12435
+always つぇ 26-12435
+
+# tso
+always ツォ 26-2435
+always  ツォ  26-2435
+always つぉ 26-2435
+
+# fa
+always ファ 26-136
+always  ファ  26-136
+always ふぁ 26-136
+
+# fi
+always フィ 26-1236
+always  フィ  26-1236
+always ふぃ 26-1236
+
+# fe
+always フェ 26-12436
+always  フェ  26-12436
+always ふぇ 26-12436
+
+# fo
+always フォ 26-2436
+always  フォ  26-2436
+always ふぉ 26-2436
+
+# va
+always ブァ 256-136
+always ぶぁ 256-136
+
+# vi
+always ブィ 256-1236
+always ぶぃ 256-1236
+
+# vu 
+always ブゥ 2-14
+always ぶぅ 2-14
+
+# ve
+always ブェ 256-12436
+always ぶぇ 256-12436
+
+# vo
+always ブォ 256-2436
+always ぶぉ 256-2436
+
+# sye
+always シェ 4-12456
+always  シェ  4-12456
+always しぇ 4-12456
+
+# je
+always ジェ 45-12456
+always じぇ 45-12456
+
+# tye
+always チェ 4-12435
+always  チェ  4-12435
+always ちぇ 4-12435
+
+# tyi
+always ティ 4-1235
+always  ティ  4-1235
+always てぃ 4-1235
+
+# dyi
+always ディ 14-1235
+always でぃ 14-1235
+
+# twu
+always トゥ 26-1435
+always  トゥ  26-1435
+always とぅ 26-1435
+
+# dwu
+always ドゥ 256-1435
+always どぅ 256-1435
+
+# tyu
+always テュ 46-1435
+always  テュ  46-1435
+always てゅ 46-1435
+
+# dyu
+always デ 456-1435
+always で 456-1435
+
+# kwa
+always クァ 26-16
+always  クァ  26-16
+always くぁ 26-16
+
+# gwa
+always グァ 256-16
+always ぐぁ 256-16
diff --git a/Tables/Contraction/ko-g0.ctb b/Tables/Contraction/ko-g0.ctb
new file mode 100644
index 0000000..1dcf9dc
--- /dev/null
+++ b/Tables/Contraction/ko-g0.ctb
@@ -0,0 +1,11605 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Korean (uncontracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+#
+# This is missing CHITUEUM*, CEONGCHIEUMSIOS, PANSIOS, YESIEUNG, RINHIEUH and
+# ARAEA for which I couldn't find documentation.  Please contact me to fix
+# anything, as this table was generated from a C program.
+
+always ! 456
+always - 36
+always numsign 3456
+
+always ᄀ 4
+always ᄁ 4-4
+always ᄂ 14
+always ᄃ 24
+always ᄄ 24-24
+always ᄅ 5
+always ᄆ 15
+always ᄇ 45
+always ᄈ 45-45
+always ᄉ 6
+always ᄊ 6-6
+always ᄋ 2356
+always ᄌ 46
+always ᄍ 46-46
+always ᄎ 56
+always ᄏ 124
+always ᄐ 125
+always ᄑ 145
+always ᄒ 245
+always ᄓ 14-4
+always ᄔ 14-14
+always ᄕ 14-24
+always ᄖ 14-45
+always ᄗ 24-4
+always ᄘ 5-14
+always ᄙ 5-5
+always ᄚ 5-245
+always ᄛ 5
+always ᄜ 15-45
+always ᄝ 15
+always ᄞ 45-4
+always ᄟ 45-14
+always ᄠ 45-24
+always ᄡ 45-6
+always ᄢ 45-6-4
+always ᄣ 45-6-24
+always ᄤ 45-6-45
+always ᄥ 45-6-6
+always ᄦ 45-6-46
+always ᄧ 45-46
+always ᄨ 45-56
+always ᄩ 45-125
+always ᄪ 45-145
+always ᄫ 45
+always ᄬ 45-45
+always ᄭ 6-4
+always ᄮ 6-14
+always ᄯ 6-24
+always ᄰ 6-5
+always ᄱ 6-15
+always ᄲ 6-45
+always ᄳ 6-45-4
+always ᄴ 6-6-6
+always ᄵ 6-2356
+always ᄶ 6-46
+always ᄷ 6-56
+always ᄸ 6-124
+always ᄹ 6-125
+always ᄺ 6-145
+always ᄻ 6-245
+always ᅁ 4
+always ᅂ 24
+always ᅃ 15
+always ᅄ 45
+always ᅅ 6
+always ᅇ 2356-2356
+always ᅈ 46
+always ᅉ 56
+always ᅊ 125
+always ᅋ 145
+always ᅍ 46-2356
+always ᅒ 56-124
+always ᅓ 56-245
+always ᅖ 145-45
+always ᅗ 145
+always ᅘ 245-245
+always ᅟ 0
+always ᅠ 0
+always ᅡ 126
+always ᅢ 1235
+always ᅣ 345
+always ᅤ 345-1235
+always ᅥ 234
+always ᅦ 1345
+always ᅧ 156
+always ᅨ 34
+always ᅩ 136
+always ᅪ 1236
+always ᅫ 1236-1235
+always ᅬ 13456
+always ᅭ 346
+always ᅮ 134
+always ᅯ 1234
+always ᅰ 1234-1235
+always ᅱ 134-1235
+always ᅲ 146
+always ᅳ 246
+always ᅴ 2456
+always ᅵ 135
+always ᅶ 126-136
+always ᅷ 126-134
+always ᅸ 345-136
+always ᅹ 345-346
+always ᅺ 234-136
+always ᅻ 234-134
+always ᅼ 234-246
+always ᅽ 156-136
+always ᅾ 156-134
+always ᅿ 136-234
+always ᆀ 136-1345
+always ᆁ 136-34
+always ᆂ 136-136
+always ᆃ 136-134
+always ᆄ 346-345
+always ᆅ 346-345-1235
+always ᆆ 346-156
+always ᆇ 346-136
+always ᆈ 346-135
+always ᆉ 134-126
+always ᆊ 134-1235
+always ᆋ 134-234-246
+always ᆌ 134-34
+always ᆍ 134-134
+always ᆎ 146-126
+always ᆏ 146-234
+always ᆐ 146-1345
+always ᆑ 146-156
+always ᆒ 146-34
+always ᆓ 146-134
+always ᆔ 146-135
+always ᆕ 246-134
+always ᆖ 246-246
+always ᆗ 2456-134
+always ᆘ 135-126
+always ᆙ 135-345
+always ᆚ 135-136
+always ᆛ 135-134
+always ᆜ 135-246
+always ᆨ 1
+always ᆩ 1-1
+always ᆪ 1-3
+always ᆫ 25
+always ᆬ 25-13
+always ᆭ 25-356
+always ᆮ 35
+always ᆯ 2
+always ᆰ 2-1
+always ᆱ 2-26
+always ᆲ 2-12
+always ᆳ 2-3
+always ᆴ 2-236
+always ᆵ 2-256
+always ᆶ 2-356
+always ᆷ 26
+always ᆸ 12
+always ᆹ 12-3
+always ᆺ 3
+always ᆻ 3-3
+always ᆼ 2356
+always ᆽ 13
+always ᆾ 23
+always ᆿ 235
+always ᇀ 236
+always ᇁ 256
+always ᇂ 356
+always ᇃ 1-2
+always ᇄ 1-3-1
+always ᇅ 25-1
+always ᇆ 25-35
+always ᇇ 25-3
+always ᇉ 25-236
+always ᇊ 35-1
+always ᇋ 35-2
+always ᇌ 2-1-3
+always ᇍ 2-25
+always ᇎ 2-35
+always ᇏ 2-35-356
+always ᇐ 2-2
+always ᇑ 2-26-1
+always ᇒ 2-26-3
+always ᇓ 2-12-3
+always ᇔ 2-12-356
+always ᇕ 2-12
+always ᇖ 2-3-3
+always ᇘ 2-235
+always ᇚ 26-1
+always ᇛ 26-2
+always ᇜ 26-12
+always ᇝ 26-3
+always ᇞ 26-3-3
+always ᇠ 26-23
+always ᇡ 26-356
+always ᇢ 26
+always ᇣ 12-2
+always ᇤ 12-256
+always ᇥ 12-356
+always ᇦ 12
+always ᇧ 3-1
+always ᇨ 3-35
+always ᇩ 3-2
+always ᇪ 3-12
+always ᇬ 1
+always ᇭ 1-1
+always ᇮ 2356-2356
+always ᇯ 235
+always ᇳ 256-12
+always ᇴ 256
+always ᇵ 356-25
+always ᇶ 356-2
+always ᇷ 356-26
+always ᇸ 356-12
+always ㄱ 1
+always ㄲ 1-1
+always ㄳ 1-3
+always ㄴ 25
+always ㄵ 25-13
+always ㄶ 25-356
+always ㄷ 35
+always ㄸ 35-35
+always ㄹ 2
+always ㄺ 2-1
+always ㄻ 2-26
+always ㄼ 2-12
+always ㄽ 2-3
+always ㄾ 2-236
+always ㄿ 2-256
+always ㅀ 2-356
+always ㅁ 26
+always ㅂ 12
+always ㅃ 12-12
+always ㅄ 12-3
+always ㅅ 3
+always ㅆ 3-3
+always ㅇ 2356
+always ㅈ 13
+always ㅉ 13-13
+always ㅊ 23
+always ㅋ 235
+always ㅌ 236
+always ㅍ 256
+always ㅎ 356
+always ㅏ 126
+always ㅐ 1235
+always ㅑ 345
+always ㅒ 345-1235
+always ㅓ 234
+always ㅔ 1345
+always ㅕ 156
+always ㅖ 34
+always ㅗ 136
+always ㅘ 1236
+always ㅙ 1236-1235
+always ㅚ 13456
+always ㅛ 346
+always ㅜ 134
+always ㅝ 1234
+always ㅞ 1234-1235
+always ㅟ 134-1235
+always ㅠ 146
+always ㅡ 246
+always ㅢ 2456
+always ㅣ 135
+always ㅤ 0
+always ㅥ 25-25
+always ㅦ 25-35
+always ㅧ 25-3
+always ㅩ 2-1-3
+always ㅪ 2-35
+always ㅫ 2-12-3
+always ㅮ 26-12
+always ㅯ 26-3
+always ㅱ 26
+always ㅲ 12-1
+always ㅳ 12-35
+always ㅴ 12-3-1
+always ㅵ 12-3-35
+always ㅶ 12-13
+always ㅷ 12-236
+always ㅸ 12
+always ㅹ 12-12
+always ㅺ 3-1
+always ㅻ 3-25
+always ㅼ 3-35
+always ㅽ 3-12
+always ㅾ 3-13
+always ㆀ 2356-2356
+always ㆄ 256
+always ㆅ 356-356
+always ㆇ 346-345
+always ㆈ 346-345-1235
+always ㆉ 346-135
+always ㆊ 146-156
+always ㆋ 146-34
+always ㆌ 146-135
+always ㈀ 1
+always ㈁ 25
+always ㈂ 35
+always ㈃ 2
+always ㈄ 26
+always ㈅ 12
+always ㈆ 3
+always ㈇ 2356
+always ㈈ 13
+always ㈉ 23
+always ㈊ 235
+always ㈋ 236
+always ㈌ 256
+always ㈍ 356
+always ㈎ 1-126
+always ㈏ 25-126
+always ㈐ 35-126
+always ㈑ 2-126
+always ㈒ 26-126
+always ㈓ 12-126
+always ㈔ 3-126
+always ㈕ 126
+always ㈖ 13-126
+always ㈗ 23-126
+always ㈘ 235-126
+always ㈙ 236-126
+always ㈚ 256-126
+always ㈛ 356-126
+always ㈜ 13-134
+always ㉠ 1
+always ㉡ 25
+always ㉢ 35
+always ㉣ 2
+always ㉤ 26
+always ㉥ 12
+always ㉦ 3
+always ㉧ 2356
+always ㉨ 13
+always ㉩ 23
+always ㉪ 235
+always ㉫ 236
+always ㉬ 256
+always ㉭ 356
+always ㉮ 1-126
+always ㉯ 25-126
+always ㉰ 35-126
+always ㉱ 2-126
+always ㉲ 26-126
+always ㉳ 12-126
+always ㉴ 3-126
+always ㉵ 126
+always ㉶ 13-126
+always ㉷ 23-126
+always ㉸ 235-126
+always ㉹ 236-126
+always ㉺ 256-126
+always ㉻ 356-126
+always 가 4-126
+always 각 4-126-1
+always 갂 4-126-1-1
+always 갃 4-126-1-3
+always 간 4-126-25
+always 갅 4-126-25-13
+always 갆 4-126-25-356
+always 갇 4-126-35
+always 갈 4-126-2
+always 갉 4-126-2-1
+always 갊 4-126-2-26
+always 갋 4-126-2-12
+always 갌 4-126-2-3
+always 갍 4-126-2-236
+always 갎 4-126-2-256
+always 갏 4-126-2-356
+always 감 4-126-26
+always 갑 4-126-12
+always 값 4-126-12-3
+always 갓 4-126-3
+always 갔 4-126-3-3
+always 강 4-126-2356
+always 갖 4-126-13
+always 갗 4-126-23
+always 갘 4-126-235
+always 같 4-126-236
+always 갚 4-126-256
+always 갛 4-126-356
+always 개 4-1235
+always 객 4-1235-1
+always 갞 4-1235-1-1
+always 갟 4-1235-1-3
+always 갠 4-1235-25
+always 갡 4-1235-25-13
+always 갢 4-1235-25-356
+always 갣 4-1235-35
+always 갤 4-1235-2
+always 갥 4-1235-2-1
+always 갦 4-1235-2-26
+always 갧 4-1235-2-12
+always 갨 4-1235-2-3
+always 갩 4-1235-2-236
+always 갪 4-1235-2-256
+always 갫 4-1235-2-356
+always 갬 4-1235-26
+always 갭 4-1235-12
+always 갮 4-1235-12-3
+always 갯 4-1235-3
+always 갰 4-1235-3-3
+always 갱 4-1235-2356
+always 갲 4-1235-13
+always 갳 4-1235-23
+always 갴 4-1235-235
+always 갵 4-1235-236
+always 갶 4-1235-256
+always 갷 4-1235-356
+always 갸 4-345
+always 갹 4-345-1
+always 갺 4-345-1-1
+always 갻 4-345-1-3
+always 갼 4-345-25
+always 갽 4-345-25-13
+always 갾 4-345-25-356
+always 갿 4-345-35
+always 걀 4-345-2
+always 걁 4-345-2-1
+always 걂 4-345-2-26
+always 걃 4-345-2-12
+always 걄 4-345-2-3
+always 걅 4-345-2-236
+always 걆 4-345-2-256
+always 걇 4-345-2-356
+always 걈 4-345-26
+always 걉 4-345-12
+always 걊 4-345-12-3
+always 걋 4-345-3
+always 걌 4-345-3-3
+always 걍 4-345-2356
+always 걎 4-345-13
+always 걏 4-345-23
+always 걐 4-345-235
+always 걑 4-345-236
+always 걒 4-345-256
+always 걓 4-345-356
+always 걔 4-345-1235
+always 걕 4-345-1235-1
+always 걖 4-345-1235-1-1
+always 걗 4-345-1235-1-3
+always 걘 4-345-1235-25
+always 걙 4-345-1235-25-13
+always 걚 4-345-1235-25-356
+always 걛 4-345-1235-35
+always 걜 4-345-1235-2
+always 걝 4-345-1235-2-1
+always 걞 4-345-1235-2-26
+always 걟 4-345-1235-2-12
+always 걠 4-345-1235-2-3
+always 걡 4-345-1235-2-236
+always 걢 4-345-1235-2-256
+always 걣 4-345-1235-2-356
+always 걤 4-345-1235-26
+always 걥 4-345-1235-12
+always 걦 4-345-1235-12-3
+always 걧 4-345-1235-3
+always 걨 4-345-1235-3-3
+always 걩 4-345-1235-2356
+always 걪 4-345-1235-13
+always 걫 4-345-1235-23
+always 걬 4-345-1235-235
+always 걭 4-345-1235-236
+always 걮 4-345-1235-256
+always 걯 4-345-1235-356
+always 거 4-234
+always 걱 4-234-1
+always 걲 4-234-1-1
+always 걳 4-234-1-3
+always 건 4-234-25
+always 걵 4-234-25-13
+always 걶 4-234-25-356
+always 걷 4-234-35
+always 걸 4-234-2
+always 걹 4-234-2-1
+always 걺 4-234-2-26
+always 걻 4-234-2-12
+always 걼 4-234-2-3
+always 걽 4-234-2-236
+always 걾 4-234-2-256
+always 걿 4-234-2-356
+always 검 4-234-26
+always 겁 4-234-12
+always 겂 4-234-12-3
+always 것 4-234-3
+always 겄 4-234-3-3
+always 겅 4-234-2356
+always 겆 4-234-13
+always 겇 4-234-23
+always 겈 4-234-235
+always 겉 4-234-236
+always 겊 4-234-256
+always 겋 4-234-356
+always 게 4-1345
+always 겍 4-1345-1
+always 겎 4-1345-1-1
+always 겏 4-1345-1-3
+always 겐 4-1345-25
+always 겑 4-1345-25-13
+always 겒 4-1345-25-356
+always 겓 4-1345-35
+always 겔 4-1345-2
+always 겕 4-1345-2-1
+always 겖 4-1345-2-26
+always 겗 4-1345-2-12
+always 겘 4-1345-2-3
+always 겙 4-1345-2-236
+always 겚 4-1345-2-256
+always 겛 4-1345-2-356
+always 겜 4-1345-26
+always 겝 4-1345-12
+always 겞 4-1345-12-3
+always 겟 4-1345-3
+always 겠 4-1345-3-3
+always 겡 4-1345-2356
+always 겢 4-1345-13
+always 겣 4-1345-23
+always 겤 4-1345-235
+always 겥 4-1345-236
+always 겦 4-1345-256
+always 겧 4-1345-356
+always 겨 4-156
+always 격 4-156-1
+always 겪 4-156-1-1
+always 겫 4-156-1-3
+always 견 4-156-25
+always 겭 4-156-25-13
+always 겮 4-156-25-356
+always 겯 4-156-35
+always 결 4-156-2
+always 겱 4-156-2-1
+always 겲 4-156-2-26
+always 겳 4-156-2-12
+always 겴 4-156-2-3
+always 겵 4-156-2-236
+always 겶 4-156-2-256
+always 겷 4-156-2-356
+always 겸 4-156-26
+always 겹 4-156-12
+always 겺 4-156-12-3
+always 겻 4-156-3
+always 겼 4-156-3-3
+always 경 4-156-2356
+always 겾 4-156-13
+always 겿 4-156-23
+always 곀 4-156-235
+always 곁 4-156-236
+always 곂 4-156-256
+always 곃 4-156-356
+always 계 4-34
+always 곅 4-34-1
+always 곆 4-34-1-1
+always 곇 4-34-1-3
+always 곈 4-34-25
+always 곉 4-34-25-13
+always 곊 4-34-25-356
+always 곋 4-34-35
+always 곌 4-34-2
+always 곍 4-34-2-1
+always 곎 4-34-2-26
+always 곏 4-34-2-12
+always 곐 4-34-2-3
+always 곑 4-34-2-236
+always 곒 4-34-2-256
+always 곓 4-34-2-356
+always 곔 4-34-26
+always 곕 4-34-12
+always 곖 4-34-12-3
+always 곗 4-34-3
+always 곘 4-34-3-3
+always 곙 4-34-2356
+always 곚 4-34-13
+always 곛 4-34-23
+always 곜 4-34-235
+always 곝 4-34-236
+always 곞 4-34-256
+always 곟 4-34-356
+always 고 4-136
+always 곡 4-136-1
+always 곢 4-136-1-1
+always 곣 4-136-1-3
+always 곤 4-136-25
+always 곥 4-136-25-13
+always 곦 4-136-25-356
+always 곧 4-136-35
+always 골 4-136-2
+always 곩 4-136-2-1
+always 곪 4-136-2-26
+always 곫 4-136-2-12
+always 곬 4-136-2-3
+always 곭 4-136-2-236
+always 곮 4-136-2-256
+always 곯 4-136-2-356
+always 곰 4-136-26
+always 곱 4-136-12
+always 곲 4-136-12-3
+always 곳 4-136-3
+always 곴 4-136-3-3
+always 공 4-136-2356
+always 곶 4-136-13
+always 곷 4-136-23
+always 곸 4-136-235
+always 곹 4-136-236
+always 곺 4-136-256
+always 곻 4-136-356
+always 과 4-1236
+always 곽 4-1236-1
+always 곾 4-1236-1-1
+always 곿 4-1236-1-3
+always 관 4-1236-25
+always 괁 4-1236-25-13
+always 괂 4-1236-25-356
+always 괃 4-1236-35
+always 괄 4-1236-2
+always 괅 4-1236-2-1
+always 괆 4-1236-2-26
+always 괇 4-1236-2-12
+always 괈 4-1236-2-3
+always 괉 4-1236-2-236
+always 괊 4-1236-2-256
+always 괋 4-1236-2-356
+always 괌 4-1236-26
+always 괍 4-1236-12
+always 괎 4-1236-12-3
+always 괏 4-1236-3
+always 괐 4-1236-3-3
+always 광 4-1236-2356
+always 괒 4-1236-13
+always 괓 4-1236-23
+always 괔 4-1236-235
+always 괕 4-1236-236
+always 괖 4-1236-256
+always 괗 4-1236-356
+always 괘 4-1236-1235
+always 괙 4-1236-1235-1
+always 괚 4-1236-1235-1-1
+always 괛 4-1236-1235-1-3
+always 괜 4-1236-1235-25
+always 괝 4-1236-1235-25-13
+always 괞 4-1236-1235-25-356
+always 괟 4-1236-1235-35
+always 괠 4-1236-1235-2
+always 괡 4-1236-1235-2-1
+always 괢 4-1236-1235-2-26
+always 괣 4-1236-1235-2-12
+always 괤 4-1236-1235-2-3
+always 괥 4-1236-1235-2-236
+always 괦 4-1236-1235-2-256
+always 괧 4-1236-1235-2-356
+always 괨 4-1236-1235-26
+always 괩 4-1236-1235-12
+always 괪 4-1236-1235-12-3
+always 괫 4-1236-1235-3
+always 괬 4-1236-1235-3-3
+always 괭 4-1236-1235-2356
+always 괮 4-1236-1235-13
+always 괯 4-1236-1235-23
+always 괰 4-1236-1235-235
+always 괱 4-1236-1235-236
+always 괲 4-1236-1235-256
+always 괳 4-1236-1235-356
+always 괴 4-13456
+always 괵 4-13456-1
+always 괶 4-13456-1-1
+always 괷 4-13456-1-3
+always 괸 4-13456-25
+always 괹 4-13456-25-13
+always 괺 4-13456-25-356
+always 괻 4-13456-35
+always 괼 4-13456-2
+always 괽 4-13456-2-1
+always 괾 4-13456-2-26
+always 괿 4-13456-2-12
+always 굀 4-13456-2-3
+always 굁 4-13456-2-236
+always 굂 4-13456-2-256
+always 굃 4-13456-2-356
+always 굄 4-13456-26
+always 굅 4-13456-12
+always 굆 4-13456-12-3
+always 굇 4-13456-3
+always 굈 4-13456-3-3
+always 굉 4-13456-2356
+always 굊 4-13456-13
+always 굋 4-13456-23
+always 굌 4-13456-235
+always 굍 4-13456-236
+always 굎 4-13456-256
+always 굏 4-13456-356
+always 교 4-346
+always 굑 4-346-1
+always 굒 4-346-1-1
+always 굓 4-346-1-3
+always 굔 4-346-25
+always 굕 4-346-25-13
+always 굖 4-346-25-356
+always 굗 4-346-35
+always 굘 4-346-2
+always 굙 4-346-2-1
+always 굚 4-346-2-26
+always 굛 4-346-2-12
+always 굜 4-346-2-3
+always 굝 4-346-2-236
+always 굞 4-346-2-256
+always 굟 4-346-2-356
+always 굠 4-346-26
+always 굡 4-346-12
+always 굢 4-346-12-3
+always 굣 4-346-3
+always 굤 4-346-3-3
+always 굥 4-346-2356
+always 굦 4-346-13
+always 굧 4-346-23
+always 굨 4-346-235
+always 굩 4-346-236
+always 굪 4-346-256
+always 굫 4-346-356
+always 구 4-134
+always 국 4-134-1
+always 굮 4-134-1-1
+always 굯 4-134-1-3
+always 군 4-134-25
+always 굱 4-134-25-13
+always 굲 4-134-25-356
+always 굳 4-134-35
+always 굴 4-134-2
+always 굵 4-134-2-1
+always 굶 4-134-2-26
+always 굷 4-134-2-12
+always 굸 4-134-2-3
+always 굹 4-134-2-236
+always 굺 4-134-2-256
+always 굻 4-134-2-356
+always 굼 4-134-26
+always 굽 4-134-12
+always 굾 4-134-12-3
+always 굿 4-134-3
+always 궀 4-134-3-3
+always 궁 4-134-2356
+always 궂 4-134-13
+always 궃 4-134-23
+always 궄 4-134-235
+always 궅 4-134-236
+always 궆 4-134-256
+always 궇 4-134-356
+always 궈 4-1234
+always 궉 4-1234-1
+always 궊 4-1234-1-1
+always 궋 4-1234-1-3
+always 권 4-1234-25
+always 궍 4-1234-25-13
+always 궎 4-1234-25-356
+always 궏 4-1234-35
+always 궐 4-1234-2
+always 궑 4-1234-2-1
+always 궒 4-1234-2-26
+always 궓 4-1234-2-12
+always 궔 4-1234-2-3
+always 궕 4-1234-2-236
+always 궖 4-1234-2-256
+always 궗 4-1234-2-356
+always 궘 4-1234-26
+always 궙 4-1234-12
+always 궚 4-1234-12-3
+always 궛 4-1234-3
+always 궜 4-1234-3-3
+always 궝 4-1234-2356
+always 궞 4-1234-13
+always 궟 4-1234-23
+always 궠 4-1234-235
+always 궡 4-1234-236
+always 궢 4-1234-256
+always 궣 4-1234-356
+always 궤 4-1234-1235
+always 궥 4-1234-1235-1
+always 궦 4-1234-1235-1-1
+always 궧 4-1234-1235-1-3
+always 궨 4-1234-1235-25
+always 궩 4-1234-1235-25-13
+always 궪 4-1234-1235-25-356
+always 궫 4-1234-1235-35
+always 궬 4-1234-1235-2
+always 궭 4-1234-1235-2-1
+always 궮 4-1234-1235-2-26
+always 궯 4-1234-1235-2-12
+always 궰 4-1234-1235-2-3
+always 궱 4-1234-1235-2-236
+always 궲 4-1234-1235-2-256
+always 궳 4-1234-1235-2-356
+always 궴 4-1234-1235-26
+always 궵 4-1234-1235-12
+always 궶 4-1234-1235-12-3
+always 궷 4-1234-1235-3
+always 궸 4-1234-1235-3-3
+always 궹 4-1234-1235-2356
+always 궺 4-1234-1235-13
+always 궻 4-1234-1235-23
+always 궼 4-1234-1235-235
+always 궽 4-1234-1235-236
+always 궾 4-1234-1235-256
+always 궿 4-1234-1235-356
+always 귀 4-134-1235
+always 귁 4-134-1235-1
+always 귂 4-134-1235-1-1
+always 귃 4-134-1235-1-3
+always 귄 4-134-1235-25
+always 귅 4-134-1235-25-13
+always 귆 4-134-1235-25-356
+always 귇 4-134-1235-35
+always 귈 4-134-1235-2
+always 귉 4-134-1235-2-1
+always 귊 4-134-1235-2-26
+always 귋 4-134-1235-2-12
+always 귌 4-134-1235-2-3
+always 귍 4-134-1235-2-236
+always 귎 4-134-1235-2-256
+always 귏 4-134-1235-2-356
+always 귐 4-134-1235-26
+always 귑 4-134-1235-12
+always 귒 4-134-1235-12-3
+always 귓 4-134-1235-3
+always 귔 4-134-1235-3-3
+always 귕 4-134-1235-2356
+always 귖 4-134-1235-13
+always 귗 4-134-1235-23
+always 귘 4-134-1235-235
+always 귙 4-134-1235-236
+always 귚 4-134-1235-256
+always 귛 4-134-1235-356
+always 규 4-146
+always 귝 4-146-1
+always 귞 4-146-1-1
+always 귟 4-146-1-3
+always 균 4-146-25
+always 귡 4-146-25-13
+always 귢 4-146-25-356
+always 귣 4-146-35
+always 귤 4-146-2
+always 귥 4-146-2-1
+always 귦 4-146-2-26
+always 귧 4-146-2-12
+always 귨 4-146-2-3
+always 귩 4-146-2-236
+always 귪 4-146-2-256
+always 귫 4-146-2-356
+always 귬 4-146-26
+always 귭 4-146-12
+always 귮 4-146-12-3
+always 귯 4-146-3
+always 귰 4-146-3-3
+always 귱 4-146-2356
+always 귲 4-146-13
+always 귳 4-146-23
+always 귴 4-146-235
+always 귵 4-146-236
+always 귶 4-146-256
+always 귷 4-146-356
+always 그 4-246
+always 극 4-246-1
+always 귺 4-246-1-1
+always 귻 4-246-1-3
+always 근 4-246-25
+always 귽 4-246-25-13
+always 귾 4-246-25-356
+always 귿 4-246-35
+always 글 4-246-2
+always 긁 4-246-2-1
+always 긂 4-246-2-26
+always 긃 4-246-2-12
+always 긄 4-246-2-3
+always 긅 4-246-2-236
+always 긆 4-246-2-256
+always 긇 4-246-2-356
+always 금 4-246-26
+always 급 4-246-12
+always 긊 4-246-12-3
+always 긋 4-246-3
+always 긌 4-246-3-3
+always 긍 4-246-2356
+always 긎 4-246-13
+always 긏 4-246-23
+always 긐 4-246-235
+always 긑 4-246-236
+always 긒 4-246-256
+always 긓 4-246-356
+always 긔 4-2456
+always 긕 4-2456-1
+always 긖 4-2456-1-1
+always 긗 4-2456-1-3
+always 긘 4-2456-25
+always 긙 4-2456-25-13
+always 긚 4-2456-25-356
+always 긛 4-2456-35
+always 긜 4-2456-2
+always 긝 4-2456-2-1
+always 긞 4-2456-2-26
+always 긟 4-2456-2-12
+always 긠 4-2456-2-3
+always 긡 4-2456-2-236
+always 긢 4-2456-2-256
+always 긣 4-2456-2-356
+always 긤 4-2456-26
+always 긥 4-2456-12
+always 긦 4-2456-12-3
+always 긧 4-2456-3
+always 긨 4-2456-3-3
+always 긩 4-2456-2356
+always 긪 4-2456-13
+always 긫 4-2456-23
+always 긬 4-2456-235
+always 긭 4-2456-236
+always 긮 4-2456-256
+always 긯 4-2456-356
+always 기 4-135
+always 긱 4-135-1
+always 긲 4-135-1-1
+always 긳 4-135-1-3
+always 긴 4-135-25
+always 긵 4-135-25-13
+always 긶 4-135-25-356
+always 긷 4-135-35
+always 길 4-135-2
+always 긹 4-135-2-1
+always 긺 4-135-2-26
+always 긻 4-135-2-12
+always 긼 4-135-2-3
+always 긽 4-135-2-236
+always 긾 4-135-2-256
+always 긿 4-135-2-356
+always 김 4-135-26
+always 깁 4-135-12
+always 깂 4-135-12-3
+always 깃 4-135-3
+always 깄 4-135-3-3
+always 깅 4-135-2356
+always 깆 4-135-13
+always 깇 4-135-23
+always 깈 4-135-235
+always 깉 4-135-236
+always 깊 4-135-256
+always 깋 4-135-356
+always 까 4-4-126
+always 깍 4-4-126-1
+always 깎 4-4-126-1-1
+always 깏 4-4-126-1-3
+always 깐 4-4-126-25
+always 깑 4-4-126-25-13
+always 깒 4-4-126-25-356
+always 깓 4-4-126-35
+always 깔 4-4-126-2
+always 깕 4-4-126-2-1
+always 깖 4-4-126-2-26
+always 깗 4-4-126-2-12
+always 깘 4-4-126-2-3
+always 깙 4-4-126-2-236
+always 깚 4-4-126-2-256
+always 깛 4-4-126-2-356
+always 깜 4-4-126-26
+always 깝 4-4-126-12
+always 깞 4-4-126-12-3
+always 깟 4-4-126-3
+always 깠 4-4-126-3-3
+always 깡 4-4-126-2356
+always 깢 4-4-126-13
+always 깣 4-4-126-23
+always 깤 4-4-126-235
+always 깥 4-4-126-236
+always 깦 4-4-126-256
+always 깧 4-4-126-356
+always 깨 4-4-1235
+always 깩 4-4-1235-1
+always 깪 4-4-1235-1-1
+always 깫 4-4-1235-1-3
+always 깬 4-4-1235-25
+always 깭 4-4-1235-25-13
+always 깮 4-4-1235-25-356
+always 깯 4-4-1235-35
+always 깰 4-4-1235-2
+always 깱 4-4-1235-2-1
+always 깲 4-4-1235-2-26
+always 깳 4-4-1235-2-12
+always 깴 4-4-1235-2-3
+always 깵 4-4-1235-2-236
+always 깶 4-4-1235-2-256
+always 깷 4-4-1235-2-356
+always 깸 4-4-1235-26
+always 깹 4-4-1235-12
+always 깺 4-4-1235-12-3
+always 깻 4-4-1235-3
+always 깼 4-4-1235-3-3
+always 깽 4-4-1235-2356
+always 깾 4-4-1235-13
+always 깿 4-4-1235-23
+always 꺀 4-4-1235-235
+always 꺁 4-4-1235-236
+always 꺂 4-4-1235-256
+always 꺃 4-4-1235-356
+always 꺄 4-4-345
+always 꺅 4-4-345-1
+always 꺆 4-4-345-1-1
+always 꺇 4-4-345-1-3
+always 꺈 4-4-345-25
+always 꺉 4-4-345-25-13
+always 꺊 4-4-345-25-356
+always 꺋 4-4-345-35
+always 꺌 4-4-345-2
+always 꺍 4-4-345-2-1
+always 꺎 4-4-345-2-26
+always 꺏 4-4-345-2-12
+always 꺐 4-4-345-2-3
+always 꺑 4-4-345-2-236
+always 꺒 4-4-345-2-256
+always 꺓 4-4-345-2-356
+always 꺔 4-4-345-26
+always 꺕 4-4-345-12
+always 꺖 4-4-345-12-3
+always 꺗 4-4-345-3
+always 꺘 4-4-345-3-3
+always 꺙 4-4-345-2356
+always 꺚 4-4-345-13
+always 꺛 4-4-345-23
+always 꺜 4-4-345-235
+always 꺝 4-4-345-236
+always 꺞 4-4-345-256
+always 꺟 4-4-345-356
+always 꺠 4-4-345-1235
+always 꺡 4-4-345-1235-1
+always 꺢 4-4-345-1235-1-1
+always 꺣 4-4-345-1235-1-3
+always 꺤 4-4-345-1235-25
+always 꺥 4-4-345-1235-25-13
+always 꺦 4-4-345-1235-25-356
+always 꺧 4-4-345-1235-35
+always 꺨 4-4-345-1235-2
+always 꺩 4-4-345-1235-2-1
+always 꺪 4-4-345-1235-2-26
+always 꺫 4-4-345-1235-2-12
+always 꺬 4-4-345-1235-2-3
+always 꺭 4-4-345-1235-2-236
+always 꺮 4-4-345-1235-2-256
+always 꺯 4-4-345-1235-2-356
+always 꺰 4-4-345-1235-26
+always 꺱 4-4-345-1235-12
+always 꺲 4-4-345-1235-12-3
+always 꺳 4-4-345-1235-3
+always 꺴 4-4-345-1235-3-3
+always 꺵 4-4-345-1235-2356
+always 꺶 4-4-345-1235-13
+always 꺷 4-4-345-1235-23
+always 꺸 4-4-345-1235-235
+always 꺹 4-4-345-1235-236
+always 꺺 4-4-345-1235-256
+always 꺻 4-4-345-1235-356
+always 꺼 4-4-234
+always 꺽 4-4-234-1
+always 꺾 4-4-234-1-1
+always 꺿 4-4-234-1-3
+always 껀 4-4-234-25
+always 껁 4-4-234-25-13
+always 껂 4-4-234-25-356
+always 껃 4-4-234-35
+always 껄 4-4-234-2
+always 껅 4-4-234-2-1
+always 껆 4-4-234-2-26
+always 껇 4-4-234-2-12
+always 껈 4-4-234-2-3
+always 껉 4-4-234-2-236
+always 껊 4-4-234-2-256
+always 껋 4-4-234-2-356
+always 껌 4-4-234-26
+always 껍 4-4-234-12
+always 껎 4-4-234-12-3
+always 껏 4-4-234-3
+always 껐 4-4-234-3-3
+always 껑 4-4-234-2356
+always 껒 4-4-234-13
+always 껓 4-4-234-23
+always 껔 4-4-234-235
+always 껕 4-4-234-236
+always 껖 4-4-234-256
+always 껗 4-4-234-356
+always 께 4-4-1345
+always 껙 4-4-1345-1
+always 껚 4-4-1345-1-1
+always 껛 4-4-1345-1-3
+always 껜 4-4-1345-25
+always 껝 4-4-1345-25-13
+always 껞 4-4-1345-25-356
+always 껟 4-4-1345-35
+always 껠 4-4-1345-2
+always 껡 4-4-1345-2-1
+always 껢 4-4-1345-2-26
+always 껣 4-4-1345-2-12
+always 껤 4-4-1345-2-3
+always 껥 4-4-1345-2-236
+always 껦 4-4-1345-2-256
+always 껧 4-4-1345-2-356
+always 껨 4-4-1345-26
+always 껩 4-4-1345-12
+always 껪 4-4-1345-12-3
+always 껫 4-4-1345-3
+always 껬 4-4-1345-3-3
+always 껭 4-4-1345-2356
+always 껮 4-4-1345-13
+always 껯 4-4-1345-23
+always 껰 4-4-1345-235
+always 껱 4-4-1345-236
+always 껲 4-4-1345-256
+always 껳 4-4-1345-356
+always 껴 4-4-156
+always 껵 4-4-156-1
+always 껶 4-4-156-1-1
+always 껷 4-4-156-1-3
+always 껸 4-4-156-25
+always 껹 4-4-156-25-13
+always 껺 4-4-156-25-356
+always 껻 4-4-156-35
+always 껼 4-4-156-2
+always 껽 4-4-156-2-1
+always 껾 4-4-156-2-26
+always 껿 4-4-156-2-12
+always 꼀 4-4-156-2-3
+always 꼁 4-4-156-2-236
+always 꼂 4-4-156-2-256
+always 꼃 4-4-156-2-356
+always 꼄 4-4-156-26
+always 꼅 4-4-156-12
+always 꼆 4-4-156-12-3
+always 꼇 4-4-156-3
+always 꼈 4-4-156-3-3
+always 꼉 4-4-156-2356
+always 꼊 4-4-156-13
+always 꼋 4-4-156-23
+always 꼌 4-4-156-235
+always 꼍 4-4-156-236
+always 꼎 4-4-156-256
+always 꼏 4-4-156-356
+always 꼐 4-4-34
+always 꼑 4-4-34-1
+always 꼒 4-4-34-1-1
+always 꼓 4-4-34-1-3
+always 꼔 4-4-34-25
+always 꼕 4-4-34-25-13
+always 꼖 4-4-34-25-356
+always 꼗 4-4-34-35
+always 꼘 4-4-34-2
+always 꼙 4-4-34-2-1
+always 꼚 4-4-34-2-26
+always 꼛 4-4-34-2-12
+always 꼜 4-4-34-2-3
+always 꼝 4-4-34-2-236
+always 꼞 4-4-34-2-256
+always 꼟 4-4-34-2-356
+always 꼠 4-4-34-26
+always 꼡 4-4-34-12
+always 꼢 4-4-34-12-3
+always 꼣 4-4-34-3
+always 꼤 4-4-34-3-3
+always 꼥 4-4-34-2356
+always 꼦 4-4-34-13
+always 꼧 4-4-34-23
+always 꼨 4-4-34-235
+always 꼩 4-4-34-236
+always 꼪 4-4-34-256
+always 꼫 4-4-34-356
+always 꼬 4-4-136
+always 꼭 4-4-136-1
+always 꼮 4-4-136-1-1
+always 꼯 4-4-136-1-3
+always 꼰 4-4-136-25
+always 꼱 4-4-136-25-13
+always 꼲 4-4-136-25-356
+always 꼳 4-4-136-35
+always 꼴 4-4-136-2
+always 꼵 4-4-136-2-1
+always 꼶 4-4-136-2-26
+always 꼷 4-4-136-2-12
+always 꼸 4-4-136-2-3
+always 꼹 4-4-136-2-236
+always 꼺 4-4-136-2-256
+always 꼻 4-4-136-2-356
+always 꼼 4-4-136-26
+always 꼽 4-4-136-12
+always 꼾 4-4-136-12-3
+always 꼿 4-4-136-3
+always 꽀 4-4-136-3-3
+always 꽁 4-4-136-2356
+always 꽂 4-4-136-13
+always 꽃 4-4-136-23
+always 꽄 4-4-136-235
+always 꽅 4-4-136-236
+always 꽆 4-4-136-256
+always 꽇 4-4-136-356
+always 꽈 4-4-1236
+always 꽉 4-4-1236-1
+always 꽊 4-4-1236-1-1
+always 꽋 4-4-1236-1-3
+always 꽌 4-4-1236-25
+always 꽍 4-4-1236-25-13
+always 꽎 4-4-1236-25-356
+always 꽏 4-4-1236-35
+always 꽐 4-4-1236-2
+always 꽑 4-4-1236-2-1
+always 꽒 4-4-1236-2-26
+always 꽓 4-4-1236-2-12
+always 꽔 4-4-1236-2-3
+always 꽕 4-4-1236-2-236
+always 꽖 4-4-1236-2-256
+always 꽗 4-4-1236-2-356
+always 꽘 4-4-1236-26
+always 꽙 4-4-1236-12
+always 꽚 4-4-1236-12-3
+always 꽛 4-4-1236-3
+always 꽜 4-4-1236-3-3
+always 꽝 4-4-1236-2356
+always 꽞 4-4-1236-13
+always 꽟 4-4-1236-23
+always 꽠 4-4-1236-235
+always 꽡 4-4-1236-236
+always 꽢 4-4-1236-256
+always 꽣 4-4-1236-356
+always 꽤 4-4-1236-1235
+always 꽥 4-4-1236-1235-1
+always 꽦 4-4-1236-1235-1-1
+always 꽧 4-4-1236-1235-1-3
+always 꽨 4-4-1236-1235-25
+always 꽩 4-4-1236-1235-25-13
+always 꽪 4-4-1236-1235-25-356
+always 꽫 4-4-1236-1235-35
+always 꽬 4-4-1236-1235-2
+always 꽭 4-4-1236-1235-2-1
+always 꽮 4-4-1236-1235-2-26
+always 꽯 4-4-1236-1235-2-12
+always 꽰 4-4-1236-1235-2-3
+always 꽱 4-4-1236-1235-2-236
+always 꽲 4-4-1236-1235-2-256
+always 꽳 4-4-1236-1235-2-356
+always 꽴 4-4-1236-1235-26
+always 꽵 4-4-1236-1235-12
+always 꽶 4-4-1236-1235-12-3
+always 꽷 4-4-1236-1235-3
+always 꽸 4-4-1236-1235-3-3
+always 꽹 4-4-1236-1235-2356
+always 꽺 4-4-1236-1235-13
+always 꽻 4-4-1236-1235-23
+always 꽼 4-4-1236-1235-235
+always 꽽 4-4-1236-1235-236
+always 꽾 4-4-1236-1235-256
+always 꽿 4-4-1236-1235-356
+always 꾀 4-4-13456
+always 꾁 4-4-13456-1
+always 꾂 4-4-13456-1-1
+always 꾃 4-4-13456-1-3
+always 꾄 4-4-13456-25
+always 꾅 4-4-13456-25-13
+always 꾆 4-4-13456-25-356
+always 꾇 4-4-13456-35
+always 꾈 4-4-13456-2
+always 꾉 4-4-13456-2-1
+always 꾊 4-4-13456-2-26
+always 꾋 4-4-13456-2-12
+always 꾌 4-4-13456-2-3
+always 꾍 4-4-13456-2-236
+always 꾎 4-4-13456-2-256
+always 꾏 4-4-13456-2-356
+always 꾐 4-4-13456-26
+always 꾑 4-4-13456-12
+always 꾒 4-4-13456-12-3
+always 꾓 4-4-13456-3
+always 꾔 4-4-13456-3-3
+always 꾕 4-4-13456-2356
+always 꾖 4-4-13456-13
+always 꾗 4-4-13456-23
+always 꾘 4-4-13456-235
+always 꾙 4-4-13456-236
+always 꾚 4-4-13456-256
+always 꾛 4-4-13456-356
+always 꾜 4-4-346
+always 꾝 4-4-346-1
+always 꾞 4-4-346-1-1
+always 꾟 4-4-346-1-3
+always 꾠 4-4-346-25
+always 꾡 4-4-346-25-13
+always 꾢 4-4-346-25-356
+always 꾣 4-4-346-35
+always 꾤 4-4-346-2
+always 꾥 4-4-346-2-1
+always 꾦 4-4-346-2-26
+always 꾧 4-4-346-2-12
+always 꾨 4-4-346-2-3
+always 꾩 4-4-346-2-236
+always 꾪 4-4-346-2-256
+always 꾫 4-4-346-2-356
+always 꾬 4-4-346-26
+always 꾭 4-4-346-12
+always 꾮 4-4-346-12-3
+always 꾯 4-4-346-3
+always 꾰 4-4-346-3-3
+always 꾱 4-4-346-2356
+always 꾲 4-4-346-13
+always 꾳 4-4-346-23
+always 꾴 4-4-346-235
+always 꾵 4-4-346-236
+always 꾶 4-4-346-256
+always 꾷 4-4-346-356
+always 꾸 4-4-134
+always 꾹 4-4-134-1
+always 꾺 4-4-134-1-1
+always 꾻 4-4-134-1-3
+always 꾼 4-4-134-25
+always 꾽 4-4-134-25-13
+always 꾾 4-4-134-25-356
+always 꾿 4-4-134-35
+always 꿀 4-4-134-2
+always 꿁 4-4-134-2-1
+always 꿂 4-4-134-2-26
+always 꿃 4-4-134-2-12
+always 꿄 4-4-134-2-3
+always 꿅 4-4-134-2-236
+always 꿆 4-4-134-2-256
+always 꿇 4-4-134-2-356
+always 꿈 4-4-134-26
+always 꿉 4-4-134-12
+always 꿊 4-4-134-12-3
+always 꿋 4-4-134-3
+always 꿌 4-4-134-3-3
+always 꿍 4-4-134-2356
+always 꿎 4-4-134-13
+always 꿏 4-4-134-23
+always 꿐 4-4-134-235
+always 꿑 4-4-134-236
+always 꿒 4-4-134-256
+always 꿓 4-4-134-356
+always 꿔 4-4-1234
+always 꿕 4-4-1234-1
+always 꿖 4-4-1234-1-1
+always 꿗 4-4-1234-1-3
+always 꿘 4-4-1234-25
+always 꿙 4-4-1234-25-13
+always 꿚 4-4-1234-25-356
+always 꿛 4-4-1234-35
+always 꿜 4-4-1234-2
+always 꿝 4-4-1234-2-1
+always 꿞 4-4-1234-2-26
+always 꿟 4-4-1234-2-12
+always 꿠 4-4-1234-2-3
+always 꿡 4-4-1234-2-236
+always 꿢 4-4-1234-2-256
+always 꿣 4-4-1234-2-356
+always 꿤 4-4-1234-26
+always 꿥 4-4-1234-12
+always 꿦 4-4-1234-12-3
+always 꿧 4-4-1234-3
+always 꿨 4-4-1234-3-3
+always 꿩 4-4-1234-2356
+always 꿪 4-4-1234-13
+always 꿫 4-4-1234-23
+always 꿬 4-4-1234-235
+always 꿭 4-4-1234-236
+always 꿮 4-4-1234-256
+always 꿯 4-4-1234-356
+always 꿰 4-4-1234-1235
+always 꿱 4-4-1234-1235-1
+always 꿲 4-4-1234-1235-1-1
+always 꿳 4-4-1234-1235-1-3
+always 꿴 4-4-1234-1235-25
+always 꿵 4-4-1234-1235-25-13
+always 꿶 4-4-1234-1235-25-356
+always 꿷 4-4-1234-1235-35
+always 꿸 4-4-1234-1235-2
+always 꿹 4-4-1234-1235-2-1
+always 꿺 4-4-1234-1235-2-26
+always 꿻 4-4-1234-1235-2-12
+always 꿼 4-4-1234-1235-2-3
+always 꿽 4-4-1234-1235-2-236
+always 꿾 4-4-1234-1235-2-256
+always 꿿 4-4-1234-1235-2-356
+always 뀀 4-4-1234-1235-26
+always 뀁 4-4-1234-1235-12
+always 뀂 4-4-1234-1235-12-3
+always 뀃 4-4-1234-1235-3
+always 뀄 4-4-1234-1235-3-3
+always 뀅 4-4-1234-1235-2356
+always 뀆 4-4-1234-1235-13
+always 뀇 4-4-1234-1235-23
+always 뀈 4-4-1234-1235-235
+always 뀉 4-4-1234-1235-236
+always 뀊 4-4-1234-1235-256
+always 뀋 4-4-1234-1235-356
+always 뀌 4-4-134-1235
+always 뀍 4-4-134-1235-1
+always 뀎 4-4-134-1235-1-1
+always 뀏 4-4-134-1235-1-3
+always 뀐 4-4-134-1235-25
+always 뀑 4-4-134-1235-25-13
+always 뀒 4-4-134-1235-25-356
+always 뀓 4-4-134-1235-35
+always 뀔 4-4-134-1235-2
+always 뀕 4-4-134-1235-2-1
+always 뀖 4-4-134-1235-2-26
+always 뀗 4-4-134-1235-2-12
+always 뀘 4-4-134-1235-2-3
+always 뀙 4-4-134-1235-2-236
+always 뀚 4-4-134-1235-2-256
+always 뀛 4-4-134-1235-2-356
+always 뀜 4-4-134-1235-26
+always 뀝 4-4-134-1235-12
+always 뀞 4-4-134-1235-12-3
+always 뀟 4-4-134-1235-3
+always 뀠 4-4-134-1235-3-3
+always 뀡 4-4-134-1235-2356
+always 뀢 4-4-134-1235-13
+always 뀣 4-4-134-1235-23
+always 뀤 4-4-134-1235-235
+always 뀥 4-4-134-1235-236
+always 뀦 4-4-134-1235-256
+always 뀧 4-4-134-1235-356
+always 뀨 4-4-146
+always 뀩 4-4-146-1
+always 뀪 4-4-146-1-1
+always 뀫 4-4-146-1-3
+always 뀬 4-4-146-25
+always 뀭 4-4-146-25-13
+always 뀮 4-4-146-25-356
+always 뀯 4-4-146-35
+always 뀰 4-4-146-2
+always 뀱 4-4-146-2-1
+always 뀲 4-4-146-2-26
+always 뀳 4-4-146-2-12
+always 뀴 4-4-146-2-3
+always 뀵 4-4-146-2-236
+always 뀶 4-4-146-2-256
+always 뀷 4-4-146-2-356
+always 뀸 4-4-146-26
+always 뀹 4-4-146-12
+always 뀺 4-4-146-12-3
+always 뀻 4-4-146-3
+always 뀼 4-4-146-3-3
+always 뀽 4-4-146-2356
+always 뀾 4-4-146-13
+always 뀿 4-4-146-23
+always 끀 4-4-146-235
+always 끁 4-4-146-236
+always 끂 4-4-146-256
+always 끃 4-4-146-356
+always 끄 4-4-246
+always 끅 4-4-246-1
+always 끆 4-4-246-1-1
+always 끇 4-4-246-1-3
+always 끈 4-4-246-25
+always 끉 4-4-246-25-13
+always 끊 4-4-246-25-356
+always 끋 4-4-246-35
+always 끌 4-4-246-2
+always 끍 4-4-246-2-1
+always 끎 4-4-246-2-26
+always 끏 4-4-246-2-12
+always 끐 4-4-246-2-3
+always 끑 4-4-246-2-236
+always 끒 4-4-246-2-256
+always 끓 4-4-246-2-356
+always 끔 4-4-246-26
+always 끕 4-4-246-12
+always 끖 4-4-246-12-3
+always 끗 4-4-246-3
+always 끘 4-4-246-3-3
+always 끙 4-4-246-2356
+always 끚 4-4-246-13
+always 끛 4-4-246-23
+always 끜 4-4-246-235
+always 끝 4-4-246-236
+always 끞 4-4-246-256
+always 끟 4-4-246-356
+always 끠 4-4-2456
+always 끡 4-4-2456-1
+always 끢 4-4-2456-1-1
+always 끣 4-4-2456-1-3
+always 끤 4-4-2456-25
+always 끥 4-4-2456-25-13
+always 끦 4-4-2456-25-356
+always 끧 4-4-2456-35
+always 끨 4-4-2456-2
+always 끩 4-4-2456-2-1
+always 끪 4-4-2456-2-26
+always 끫 4-4-2456-2-12
+always 끬 4-4-2456-2-3
+always 끭 4-4-2456-2-236
+always 끮 4-4-2456-2-256
+always 끯 4-4-2456-2-356
+always 끰 4-4-2456-26
+always 끱 4-4-2456-12
+always 끲 4-4-2456-12-3
+always 끳 4-4-2456-3
+always 끴 4-4-2456-3-3
+always 끵 4-4-2456-2356
+always 끶 4-4-2456-13
+always 끷 4-4-2456-23
+always 끸 4-4-2456-235
+always 끹 4-4-2456-236
+always 끺 4-4-2456-256
+always 끻 4-4-2456-356
+always 끼 4-4-135
+always 끽 4-4-135-1
+always 끾 4-4-135-1-1
+always 끿 4-4-135-1-3
+always 낀 4-4-135-25
+always 낁 4-4-135-25-13
+always 낂 4-4-135-25-356
+always 낃 4-4-135-35
+always 낄 4-4-135-2
+always 낅 4-4-135-2-1
+always 낆 4-4-135-2-26
+always 낇 4-4-135-2-12
+always 낈 4-4-135-2-3
+always 낉 4-4-135-2-236
+always 낊 4-4-135-2-256
+always 낋 4-4-135-2-356
+always 낌 4-4-135-26
+always 낍 4-4-135-12
+always 낎 4-4-135-12-3
+always 낏 4-4-135-3
+always 낐 4-4-135-3-3
+always 낑 4-4-135-2356
+always 낒 4-4-135-13
+always 낓 4-4-135-23
+always 낔 4-4-135-235
+always 낕 4-4-135-236
+always 낖 4-4-135-256
+always 낗 4-4-135-356
+always 나 14-126
+always 낙 14-126-1
+always 낚 14-126-1-1
+always 낛 14-126-1-3
+always 난 14-126-25
+always 낝 14-126-25-13
+always 낞 14-126-25-356
+always 낟 14-126-35
+always 날 14-126-2
+always 낡 14-126-2-1
+always 낢 14-126-2-26
+always 낣 14-126-2-12
+always 낤 14-126-2-3
+always 낥 14-126-2-236
+always 낦 14-126-2-256
+always 낧 14-126-2-356
+always 남 14-126-26
+always 납 14-126-12
+always 낪 14-126-12-3
+always 낫 14-126-3
+always 났 14-126-3-3
+always 낭 14-126-2356
+always 낮 14-126-13
+always 낯 14-126-23
+always 낰 14-126-235
+always 낱 14-126-236
+always 낲 14-126-256
+always 낳 14-126-356
+always 내 14-1235
+always 낵 14-1235-1
+always 낶 14-1235-1-1
+always 낷 14-1235-1-3
+always 낸 14-1235-25
+always 낹 14-1235-25-13
+always 낺 14-1235-25-356
+always 낻 14-1235-35
+always 낼 14-1235-2
+always 낽 14-1235-2-1
+always 낾 14-1235-2-26
+always 낿 14-1235-2-12
+always 냀 14-1235-2-3
+always 냁 14-1235-2-236
+always 냂 14-1235-2-256
+always 냃 14-1235-2-356
+always 냄 14-1235-26
+always 냅 14-1235-12
+always 냆 14-1235-12-3
+always 냇 14-1235-3
+always 냈 14-1235-3-3
+always 냉 14-1235-2356
+always 냊 14-1235-13
+always 냋 14-1235-23
+always 냌 14-1235-235
+always 냍 14-1235-236
+always 냎 14-1235-256
+always 냏 14-1235-356
+always 냐 14-345
+always 냑 14-345-1
+always 냒 14-345-1-1
+always 냓 14-345-1-3
+always 냔 14-345-25
+always 냕 14-345-25-13
+always 냖 14-345-25-356
+always 냗 14-345-35
+always 냘 14-345-2
+always 냙 14-345-2-1
+always 냚 14-345-2-26
+always 냛 14-345-2-12
+always 냜 14-345-2-3
+always 냝 14-345-2-236
+always 냞 14-345-2-256
+always 냟 14-345-2-356
+always 냠 14-345-26
+always 냡 14-345-12
+always 냢 14-345-12-3
+always 냣 14-345-3
+always 냤 14-345-3-3
+always 냥 14-345-2356
+always 냦 14-345-13
+always 냧 14-345-23
+always 냨 14-345-235
+always 냩 14-345-236
+always 냪 14-345-256
+always 냫 14-345-356
+always 냬 14-345-1235
+always 냭 14-345-1235-1
+always 냮 14-345-1235-1-1
+always 냯 14-345-1235-1-3
+always 냰 14-345-1235-25
+always 냱 14-345-1235-25-13
+always 냲 14-345-1235-25-356
+always 냳 14-345-1235-35
+always 냴 14-345-1235-2
+always 냵 14-345-1235-2-1
+always 냶 14-345-1235-2-26
+always 냷 14-345-1235-2-12
+always 냸 14-345-1235-2-3
+always 냹 14-345-1235-2-236
+always 냺 14-345-1235-2-256
+always 냻 14-345-1235-2-356
+always 냼 14-345-1235-26
+always 냽 14-345-1235-12
+always 냾 14-345-1235-12-3
+always 냿 14-345-1235-3
+always 넀 14-345-1235-3-3
+always 넁 14-345-1235-2356
+always 넂 14-345-1235-13
+always 넃 14-345-1235-23
+always 넄 14-345-1235-235
+always 넅 14-345-1235-236
+always 넆 14-345-1235-256
+always 넇 14-345-1235-356
+always 너 14-234
+always 넉 14-234-1
+always 넊 14-234-1-1
+always 넋 14-234-1-3
+always 넌 14-234-25
+always 넍 14-234-25-13
+always 넎 14-234-25-356
+always 넏 14-234-35
+always 널 14-234-2
+always 넑 14-234-2-1
+always 넒 14-234-2-26
+always 넓 14-234-2-12
+always 넔 14-234-2-3
+always 넕 14-234-2-236
+always 넖 14-234-2-256
+always 넗 14-234-2-356
+always 넘 14-234-26
+always 넙 14-234-12
+always 넚 14-234-12-3
+always 넛 14-234-3
+always 넜 14-234-3-3
+always 넝 14-234-2356
+always 넞 14-234-13
+always 넟 14-234-23
+always 넠 14-234-235
+always 넡 14-234-236
+always 넢 14-234-256
+always 넣 14-234-356
+always 네 14-1345
+always 넥 14-1345-1
+always 넦 14-1345-1-1
+always 넧 14-1345-1-3
+always 넨 14-1345-25
+always 넩 14-1345-25-13
+always 넪 14-1345-25-356
+always 넫 14-1345-35
+always 넬 14-1345-2
+always 넭 14-1345-2-1
+always 넮 14-1345-2-26
+always 넯 14-1345-2-12
+always 넰 14-1345-2-3
+always 넱 14-1345-2-236
+always 넲 14-1345-2-256
+always 넳 14-1345-2-356
+always 넴 14-1345-26
+always 넵 14-1345-12
+always 넶 14-1345-12-3
+always 넷 14-1345-3
+always 넸 14-1345-3-3
+always 넹 14-1345-2356
+always 넺 14-1345-13
+always 넻 14-1345-23
+always 넼 14-1345-235
+always 넽 14-1345-236
+always 넾 14-1345-256
+always 넿 14-1345-356
+always 녀 14-156
+always 녁 14-156-1
+always 녂 14-156-1-1
+always 녃 14-156-1-3
+always 년 14-156-25
+always 녅 14-156-25-13
+always 녆 14-156-25-356
+always 녇 14-156-35
+always 녈 14-156-2
+always 녉 14-156-2-1
+always 녊 14-156-2-26
+always 녋 14-156-2-12
+always 녌 14-156-2-3
+always 녍 14-156-2-236
+always 녎 14-156-2-256
+always 녏 14-156-2-356
+always 념 14-156-26
+always 녑 14-156-12
+always 녒 14-156-12-3
+always 녓 14-156-3
+always 녔 14-156-3-3
+always 녕 14-156-2356
+always 녖 14-156-13
+always 녗 14-156-23
+always 녘 14-156-235
+always 녙 14-156-236
+always 녚 14-156-256
+always 녛 14-156-356
+always 녜 14-34
+always 녝 14-34-1
+always 녞 14-34-1-1
+always 녟 14-34-1-3
+always 녠 14-34-25
+always 녡 14-34-25-13
+always 녢 14-34-25-356
+always 녣 14-34-35
+always 녤 14-34-2
+always 녥 14-34-2-1
+always 녦 14-34-2-26
+always 녧 14-34-2-12
+always 녨 14-34-2-3
+always 녩 14-34-2-236
+always 녪 14-34-2-256
+always 녫 14-34-2-356
+always 녬 14-34-26
+always 녭 14-34-12
+always 녮 14-34-12-3
+always 녯 14-34-3
+always 녰 14-34-3-3
+always 녱 14-34-2356
+always 녲 14-34-13
+always 녳 14-34-23
+always 녴 14-34-235
+always 녵 14-34-236
+always 녶 14-34-256
+always 녷 14-34-356
+always 노 14-136
+always 녹 14-136-1
+always 녺 14-136-1-1
+always 녻 14-136-1-3
+always 논 14-136-25
+always 녽 14-136-25-13
+always 녾 14-136-25-356
+always 녿 14-136-35
+always 놀 14-136-2
+always 놁 14-136-2-1
+always 놂 14-136-2-26
+always 놃 14-136-2-12
+always 놄 14-136-2-3
+always 놅 14-136-2-236
+always 놆 14-136-2-256
+always 놇 14-136-2-356
+always 놈 14-136-26
+always 놉 14-136-12
+always 놊 14-136-12-3
+always 놋 14-136-3
+always 놌 14-136-3-3
+always 농 14-136-2356
+always 놎 14-136-13
+always 놏 14-136-23
+always 놐 14-136-235
+always 놑 14-136-236
+always 높 14-136-256
+always 놓 14-136-356
+always 놔 14-1236
+always 놕 14-1236-1
+always 놖 14-1236-1-1
+always 놗 14-1236-1-3
+always 놘 14-1236-25
+always 놙 14-1236-25-13
+always 놚 14-1236-25-356
+always 놛 14-1236-35
+always 놜 14-1236-2
+always 놝 14-1236-2-1
+always 놞 14-1236-2-26
+always 놟 14-1236-2-12
+always 놠 14-1236-2-3
+always 놡 14-1236-2-236
+always 놢 14-1236-2-256
+always 놣 14-1236-2-356
+always 놤 14-1236-26
+always 놥 14-1236-12
+always 놦 14-1236-12-3
+always 놧 14-1236-3
+always 놨 14-1236-3-3
+always 놩 14-1236-2356
+always 놪 14-1236-13
+always 놫 14-1236-23
+always 놬 14-1236-235
+always 놭 14-1236-236
+always 놮 14-1236-256
+always 놯 14-1236-356
+always 놰 14-1236-1235
+always 놱 14-1236-1235-1
+always 놲 14-1236-1235-1-1
+always 놳 14-1236-1235-1-3
+always 놴 14-1236-1235-25
+always 놵 14-1236-1235-25-13
+always 놶 14-1236-1235-25-356
+always 놷 14-1236-1235-35
+always 놸 14-1236-1235-2
+always 놹 14-1236-1235-2-1
+always 놺 14-1236-1235-2-26
+always 놻 14-1236-1235-2-12
+always 놼 14-1236-1235-2-3
+always 놽 14-1236-1235-2-236
+always 놾 14-1236-1235-2-256
+always 놿 14-1236-1235-2-356
+always 뇀 14-1236-1235-26
+always 뇁 14-1236-1235-12
+always 뇂 14-1236-1235-12-3
+always 뇃 14-1236-1235-3
+always 뇄 14-1236-1235-3-3
+always 뇅 14-1236-1235-2356
+always 뇆 14-1236-1235-13
+always 뇇 14-1236-1235-23
+always 뇈 14-1236-1235-235
+always 뇉 14-1236-1235-236
+always 뇊 14-1236-1235-256
+always 뇋 14-1236-1235-356
+always 뇌 14-13456
+always 뇍 14-13456-1
+always 뇎 14-13456-1-1
+always 뇏 14-13456-1-3
+always 뇐 14-13456-25
+always 뇑 14-13456-25-13
+always 뇒 14-13456-25-356
+always 뇓 14-13456-35
+always 뇔 14-13456-2
+always 뇕 14-13456-2-1
+always 뇖 14-13456-2-26
+always 뇗 14-13456-2-12
+always 뇘 14-13456-2-3
+always 뇙 14-13456-2-236
+always 뇚 14-13456-2-256
+always 뇛 14-13456-2-356
+always 뇜 14-13456-26
+always 뇝 14-13456-12
+always 뇞 14-13456-12-3
+always 뇟 14-13456-3
+always 뇠 14-13456-3-3
+always 뇡 14-13456-2356
+always 뇢 14-13456-13
+always 뇣 14-13456-23
+always 뇤 14-13456-235
+always 뇥 14-13456-236
+always 뇦 14-13456-256
+always 뇧 14-13456-356
+always 뇨 14-346
+always 뇩 14-346-1
+always 뇪 14-346-1-1
+always 뇫 14-346-1-3
+always 뇬 14-346-25
+always 뇭 14-346-25-13
+always 뇮 14-346-25-356
+always 뇯 14-346-35
+always 뇰 14-346-2
+always 뇱 14-346-2-1
+always 뇲 14-346-2-26
+always 뇳 14-346-2-12
+always 뇴 14-346-2-3
+always 뇵 14-346-2-236
+always 뇶 14-346-2-256
+always 뇷 14-346-2-356
+always 뇸 14-346-26
+always 뇹 14-346-12
+always 뇺 14-346-12-3
+always 뇻 14-346-3
+always 뇼 14-346-3-3
+always 뇽 14-346-2356
+always 뇾 14-346-13
+always 뇿 14-346-23
+always 눀 14-346-235
+always 눁 14-346-236
+always 눂 14-346-256
+always 눃 14-346-356
+always 누 14-134
+always 눅 14-134-1
+always 눆 14-134-1-1
+always 눇 14-134-1-3
+always 눈 14-134-25
+always 눉 14-134-25-13
+always 눊 14-134-25-356
+always 눋 14-134-35
+always 눌 14-134-2
+always 눍 14-134-2-1
+always 눎 14-134-2-26
+always 눏 14-134-2-12
+always 눐 14-134-2-3
+always 눑 14-134-2-236
+always 눒 14-134-2-256
+always 눓 14-134-2-356
+always 눔 14-134-26
+always 눕 14-134-12
+always 눖 14-134-12-3
+always 눗 14-134-3
+always 눘 14-134-3-3
+always 눙 14-134-2356
+always 눚 14-134-13
+always 눛 14-134-23
+always 눜 14-134-235
+always 눝 14-134-236
+always 눞 14-134-256
+always 눟 14-134-356
+always 눠 14-1234
+always 눡 14-1234-1
+always 눢 14-1234-1-1
+always 눣 14-1234-1-3
+always 눤 14-1234-25
+always 눥 14-1234-25-13
+always 눦 14-1234-25-356
+always 눧 14-1234-35
+always 눨 14-1234-2
+always 눩 14-1234-2-1
+always 눪 14-1234-2-26
+always 눫 14-1234-2-12
+always 눬 14-1234-2-3
+always 눭 14-1234-2-236
+always 눮 14-1234-2-256
+always 눯 14-1234-2-356
+always 눰 14-1234-26
+always 눱 14-1234-12
+always 눲 14-1234-12-3
+always 눳 14-1234-3
+always 눴 14-1234-3-3
+always 눵 14-1234-2356
+always 눶 14-1234-13
+always 눷 14-1234-23
+always 눸 14-1234-235
+always 눹 14-1234-236
+always 눺 14-1234-256
+always 눻 14-1234-356
+always 눼 14-1234-1235
+always 눽 14-1234-1235-1
+always 눾 14-1234-1235-1-1
+always 눿 14-1234-1235-1-3
+always 뉀 14-1234-1235-25
+always 뉁 14-1234-1235-25-13
+always 뉂 14-1234-1235-25-356
+always 뉃 14-1234-1235-35
+always 뉄 14-1234-1235-2
+always 뉅 14-1234-1235-2-1
+always 뉆 14-1234-1235-2-26
+always 뉇 14-1234-1235-2-12
+always 뉈 14-1234-1235-2-3
+always 뉉 14-1234-1235-2-236
+always 뉊 14-1234-1235-2-256
+always 뉋 14-1234-1235-2-356
+always 뉌 14-1234-1235-26
+always 뉍 14-1234-1235-12
+always 뉎 14-1234-1235-12-3
+always 뉏 14-1234-1235-3
+always 뉐 14-1234-1235-3-3
+always 뉑 14-1234-1235-2356
+always 뉒 14-1234-1235-13
+always 뉓 14-1234-1235-23
+always 뉔 14-1234-1235-235
+always 뉕 14-1234-1235-236
+always 뉖 14-1234-1235-256
+always 뉗 14-1234-1235-356
+always 뉘 14-134-1235
+always 뉙 14-134-1235-1
+always 뉚 14-134-1235-1-1
+always 뉛 14-134-1235-1-3
+always 뉜 14-134-1235-25
+always 뉝 14-134-1235-25-13
+always 뉞 14-134-1235-25-356
+always 뉟 14-134-1235-35
+always 뉠 14-134-1235-2
+always 뉡 14-134-1235-2-1
+always 뉢 14-134-1235-2-26
+always 뉣 14-134-1235-2-12
+always 뉤 14-134-1235-2-3
+always 뉥 14-134-1235-2-236
+always 뉦 14-134-1235-2-256
+always 뉧 14-134-1235-2-356
+always 뉨 14-134-1235-26
+always 뉩 14-134-1235-12
+always 뉪 14-134-1235-12-3
+always 뉫 14-134-1235-3
+always 뉬 14-134-1235-3-3
+always 뉭 14-134-1235-2356
+always 뉮 14-134-1235-13
+always 뉯 14-134-1235-23
+always 뉰 14-134-1235-235
+always 뉱 14-134-1235-236
+always 뉲 14-134-1235-256
+always 뉳 14-134-1235-356
+always 뉴 14-146
+always 뉵 14-146-1
+always 뉶 14-146-1-1
+always 뉷 14-146-1-3
+always 뉸 14-146-25
+always 뉹 14-146-25-13
+always 뉺 14-146-25-356
+always 뉻 14-146-35
+always 뉼 14-146-2
+always 뉽 14-146-2-1
+always 뉾 14-146-2-26
+always 뉿 14-146-2-12
+always 늀 14-146-2-3
+always 늁 14-146-2-236
+always 늂 14-146-2-256
+always 늃 14-146-2-356
+always 늄 14-146-26
+always 늅 14-146-12
+always 늆 14-146-12-3
+always 늇 14-146-3
+always 늈 14-146-3-3
+always 늉 14-146-2356
+always 늊 14-146-13
+always 늋 14-146-23
+always 늌 14-146-235
+always 늍 14-146-236
+always 늎 14-146-256
+always 늏 14-146-356
+always 느 14-246
+always 늑 14-246-1
+always 늒 14-246-1-1
+always 늓 14-246-1-3
+always 는 14-246-25
+always 늕 14-246-25-13
+always 늖 14-246-25-356
+always 늗 14-246-35
+always 늘 14-246-2
+always 늙 14-246-2-1
+always 늚 14-246-2-26
+always 늛 14-246-2-12
+always 늜 14-246-2-3
+always 늝 14-246-2-236
+always 늞 14-246-2-256
+always 늟 14-246-2-356
+always 늠 14-246-26
+always 늡 14-246-12
+always 늢 14-246-12-3
+always 늣 14-246-3
+always 늤 14-246-3-3
+always 능 14-246-2356
+always 늦 14-246-13
+always 늧 14-246-23
+always 늨 14-246-235
+always 늩 14-246-236
+always 늪 14-246-256
+always 늫 14-246-356
+always 늬 14-2456
+always 늭 14-2456-1
+always 늮 14-2456-1-1
+always 늯 14-2456-1-3
+always 늰 14-2456-25
+always 늱 14-2456-25-13
+always 늲 14-2456-25-356
+always 늳 14-2456-35
+always 늴 14-2456-2
+always 늵 14-2456-2-1
+always 늶 14-2456-2-26
+always 늷 14-2456-2-12
+always 늸 14-2456-2-3
+always 늹 14-2456-2-236
+always 늺 14-2456-2-256
+always 늻 14-2456-2-356
+always 늼 14-2456-26
+always 늽 14-2456-12
+always 늾 14-2456-12-3
+always 늿 14-2456-3
+always 닀 14-2456-3-3
+always 닁 14-2456-2356
+always 닂 14-2456-13
+always 닃 14-2456-23
+always 닄 14-2456-235
+always 닅 14-2456-236
+always 닆 14-2456-256
+always 닇 14-2456-356
+always 니 14-135
+always 닉 14-135-1
+always 닊 14-135-1-1
+always 닋 14-135-1-3
+always 닌 14-135-25
+always 닍 14-135-25-13
+always 닎 14-135-25-356
+always 닏 14-135-35
+always 닐 14-135-2
+always 닑 14-135-2-1
+always 닒 14-135-2-26
+always 닓 14-135-2-12
+always 닔 14-135-2-3
+always 닕 14-135-2-236
+always 닖 14-135-2-256
+always 닗 14-135-2-356
+always 님 14-135-26
+always 닙 14-135-12
+always 닚 14-135-12-3
+always 닛 14-135-3
+always 닜 14-135-3-3
+always 닝 14-135-2356
+always 닞 14-135-13
+always 닟 14-135-23
+always 닠 14-135-235
+always 닡 14-135-236
+always 닢 14-135-256
+always 닣 14-135-356
+always 다 24-126
+always 닥 24-126-1
+always 닦 24-126-1-1
+always 닧 24-126-1-3
+always 단 24-126-25
+always 닩 24-126-25-13
+always 닪 24-126-25-356
+always 닫 24-126-35
+always 달 24-126-2
+always 닭 24-126-2-1
+always 닮 24-126-2-26
+always 닯 24-126-2-12
+always 닰 24-126-2-3
+always 닱 24-126-2-236
+always 닲 24-126-2-256
+always 닳 24-126-2-356
+always 담 24-126-26
+always 답 24-126-12
+always 닶 24-126-12-3
+always 닷 24-126-3
+always 닸 24-126-3-3
+always 당 24-126-2356
+always 닺 24-126-13
+always 닻 24-126-23
+always 닼 24-126-235
+always 닽 24-126-236
+always 닾 24-126-256
+always 닿 24-126-356
+always 대 24-1235
+always 댁 24-1235-1
+always 댂 24-1235-1-1
+always 댃 24-1235-1-3
+always 댄 24-1235-25
+always 댅 24-1235-25-13
+always 댆 24-1235-25-356
+always 댇 24-1235-35
+always 댈 24-1235-2
+always 댉 24-1235-2-1
+always 댊 24-1235-2-26
+always 댋 24-1235-2-12
+always 댌 24-1235-2-3
+always 댍 24-1235-2-236
+always 댎 24-1235-2-256
+always 댏 24-1235-2-356
+always 댐 24-1235-26
+always 댑 24-1235-12
+always 댒 24-1235-12-3
+always 댓 24-1235-3
+always 댔 24-1235-3-3
+always 댕 24-1235-2356
+always 댖 24-1235-13
+always 댗 24-1235-23
+always 댘 24-1235-235
+always 댙 24-1235-236
+always 댚 24-1235-256
+always 댛 24-1235-356
+always 댜 24-345
+always 댝 24-345-1
+always 댞 24-345-1-1
+always 댟 24-345-1-3
+always 댠 24-345-25
+always 댡 24-345-25-13
+always 댢 24-345-25-356
+always 댣 24-345-35
+always 댤 24-345-2
+always 댥 24-345-2-1
+always 댦 24-345-2-26
+always 댧 24-345-2-12
+always 댨 24-345-2-3
+always 댩 24-345-2-236
+always 댪 24-345-2-256
+always 댫 24-345-2-356
+always 댬 24-345-26
+always 댭 24-345-12
+always 댮 24-345-12-3
+always 댯 24-345-3
+always 댰 24-345-3-3
+always 댱 24-345-2356
+always 댲 24-345-13
+always 댳 24-345-23
+always 댴 24-345-235
+always 댵 24-345-236
+always 댶 24-345-256
+always 댷 24-345-356
+always 댸 24-345-1235
+always 댹 24-345-1235-1
+always 댺 24-345-1235-1-1
+always 댻 24-345-1235-1-3
+always 댼 24-345-1235-25
+always 댽 24-345-1235-25-13
+always 댾 24-345-1235-25-356
+always 댿 24-345-1235-35
+always 덀 24-345-1235-2
+always 덁 24-345-1235-2-1
+always 덂 24-345-1235-2-26
+always 덃 24-345-1235-2-12
+always 덄 24-345-1235-2-3
+always 덅 24-345-1235-2-236
+always 덆 24-345-1235-2-256
+always 덇 24-345-1235-2-356
+always 덈 24-345-1235-26
+always 덉 24-345-1235-12
+always 덊 24-345-1235-12-3
+always 덋 24-345-1235-3
+always 덌 24-345-1235-3-3
+always 덍 24-345-1235-2356
+always 덎 24-345-1235-13
+always 덏 24-345-1235-23
+always 덐 24-345-1235-235
+always 덑 24-345-1235-236
+always 덒 24-345-1235-256
+always 덓 24-345-1235-356
+always 더 24-234
+always 덕 24-234-1
+always 덖 24-234-1-1
+always 덗 24-234-1-3
+always 던 24-234-25
+always 덙 24-234-25-13
+always 덚 24-234-25-356
+always 덛 24-234-35
+always 덜 24-234-2
+always 덝 24-234-2-1
+always 덞 24-234-2-26
+always 덟 24-234-2-12
+always 덠 24-234-2-3
+always 덡 24-234-2-236
+always 덢 24-234-2-256
+always 덣 24-234-2-356
+always 덤 24-234-26
+always 덥 24-234-12
+always 덦 24-234-12-3
+always 덧 24-234-3
+always 덨 24-234-3-3
+always 덩 24-234-2356
+always 덪 24-234-13
+always 덫 24-234-23
+always 덬 24-234-235
+always 덭 24-234-236
+always 덮 24-234-256
+always 덯 24-234-356
+always 데 24-1345
+always 덱 24-1345-1
+always 덲 24-1345-1-1
+always 덳 24-1345-1-3
+always 덴 24-1345-25
+always 덵 24-1345-25-13
+always 덶 24-1345-25-356
+always 덷 24-1345-35
+always 델 24-1345-2
+always 덹 24-1345-2-1
+always 덺 24-1345-2-26
+always 덻 24-1345-2-12
+always 덼 24-1345-2-3
+always 덽 24-1345-2-236
+always 덾 24-1345-2-256
+always 덿 24-1345-2-356
+always 뎀 24-1345-26
+always 뎁 24-1345-12
+always 뎂 24-1345-12-3
+always 뎃 24-1345-3
+always 뎄 24-1345-3-3
+always 뎅 24-1345-2356
+always 뎆 24-1345-13
+always 뎇 24-1345-23
+always 뎈 24-1345-235
+always 뎉 24-1345-236
+always 뎊 24-1345-256
+always 뎋 24-1345-356
+always 뎌 24-156
+always 뎍 24-156-1
+always 뎎 24-156-1-1
+always 뎏 24-156-1-3
+always 뎐 24-156-25
+always 뎑 24-156-25-13
+always 뎒 24-156-25-356
+always 뎓 24-156-35
+always 뎔 24-156-2
+always 뎕 24-156-2-1
+always 뎖 24-156-2-26
+always 뎗 24-156-2-12
+always 뎘 24-156-2-3
+always 뎙 24-156-2-236
+always 뎚 24-156-2-256
+always 뎛 24-156-2-356
+always 뎜 24-156-26
+always 뎝 24-156-12
+always 뎞 24-156-12-3
+always 뎟 24-156-3
+always 뎠 24-156-3-3
+always 뎡 24-156-2356
+always 뎢 24-156-13
+always 뎣 24-156-23
+always 뎤 24-156-235
+always 뎥 24-156-236
+always 뎦 24-156-256
+always 뎧 24-156-356
+always 뎨 24-34
+always 뎩 24-34-1
+always 뎪 24-34-1-1
+always 뎫 24-34-1-3
+always 뎬 24-34-25
+always 뎭 24-34-25-13
+always 뎮 24-34-25-356
+always 뎯 24-34-35
+always 뎰 24-34-2
+always 뎱 24-34-2-1
+always 뎲 24-34-2-26
+always 뎳 24-34-2-12
+always 뎴 24-34-2-3
+always 뎵 24-34-2-236
+always 뎶 24-34-2-256
+always 뎷 24-34-2-356
+always 뎸 24-34-26
+always 뎹 24-34-12
+always 뎺 24-34-12-3
+always 뎻 24-34-3
+always 뎼 24-34-3-3
+always 뎽 24-34-2356
+always 뎾 24-34-13
+always 뎿 24-34-23
+always 돀 24-34-235
+always 돁 24-34-236
+always 돂 24-34-256
+always 돃 24-34-356
+always 도 24-136
+always 독 24-136-1
+always 돆 24-136-1-1
+always 돇 24-136-1-3
+always 돈 24-136-25
+always 돉 24-136-25-13
+always 돊 24-136-25-356
+always 돋 24-136-35
+always 돌 24-136-2
+always 돍 24-136-2-1
+always 돎 24-136-2-26
+always 돏 24-136-2-12
+always 돐 24-136-2-3
+always 돑 24-136-2-236
+always 돒 24-136-2-256
+always 돓 24-136-2-356
+always 돔 24-136-26
+always 돕 24-136-12
+always 돖 24-136-12-3
+always 돗 24-136-3
+always 돘 24-136-3-3
+always 동 24-136-2356
+always 돚 24-136-13
+always 돛 24-136-23
+always 돜 24-136-235
+always 돝 24-136-236
+always 돞 24-136-256
+always 돟 24-136-356
+always 돠 24-1236
+always 돡 24-1236-1
+always 돢 24-1236-1-1
+always 돣 24-1236-1-3
+always 돤 24-1236-25
+always 돥 24-1236-25-13
+always 돦 24-1236-25-356
+always 돧 24-1236-35
+always 돨 24-1236-2
+always 돩 24-1236-2-1
+always 돪 24-1236-2-26
+always 돫 24-1236-2-12
+always 돬 24-1236-2-3
+always 돭 24-1236-2-236
+always 돮 24-1236-2-256
+always 돯 24-1236-2-356
+always 돰 24-1236-26
+always 돱 24-1236-12
+always 돲 24-1236-12-3
+always 돳 24-1236-3
+always 돴 24-1236-3-3
+always 돵 24-1236-2356
+always 돶 24-1236-13
+always 돷 24-1236-23
+always 돸 24-1236-235
+always 돹 24-1236-236
+always 돺 24-1236-256
+always 돻 24-1236-356
+always 돼 24-1236-1235
+always 돽 24-1236-1235-1
+always 돾 24-1236-1235-1-1
+always 돿 24-1236-1235-1-3
+always 됀 24-1236-1235-25
+always 됁 24-1236-1235-25-13
+always 됂 24-1236-1235-25-356
+always 됃 24-1236-1235-35
+always 됄 24-1236-1235-2
+always 됅 24-1236-1235-2-1
+always 됆 24-1236-1235-2-26
+always 됇 24-1236-1235-2-12
+always 됈 24-1236-1235-2-3
+always 됉 24-1236-1235-2-236
+always 됊 24-1236-1235-2-256
+always 됋 24-1236-1235-2-356
+always 됌 24-1236-1235-26
+always 됍 24-1236-1235-12
+always 됎 24-1236-1235-12-3
+always 됏 24-1236-1235-3
+always 됐 24-1236-1235-3-3
+always 됑 24-1236-1235-2356
+always 됒 24-1236-1235-13
+always 됓 24-1236-1235-23
+always 됔 24-1236-1235-235
+always 됕 24-1236-1235-236
+always 됖 24-1236-1235-256
+always 됗 24-1236-1235-356
+always 되 24-13456
+always 됙 24-13456-1
+always 됚 24-13456-1-1
+always 됛 24-13456-1-3
+always 된 24-13456-25
+always 됝 24-13456-25-13
+always 됞 24-13456-25-356
+always 됟 24-13456-35
+always 될 24-13456-2
+always 됡 24-13456-2-1
+always 됢 24-13456-2-26
+always 됣 24-13456-2-12
+always 됤 24-13456-2-3
+always 됥 24-13456-2-236
+always 됦 24-13456-2-256
+always 됧 24-13456-2-356
+always 됨 24-13456-26
+always 됩 24-13456-12
+always 됪 24-13456-12-3
+always 됫 24-13456-3
+always 됬 24-13456-3-3
+always 됭 24-13456-2356
+always 됮 24-13456-13
+always 됯 24-13456-23
+always 됰 24-13456-235
+always 됱 24-13456-236
+always 됲 24-13456-256
+always 됳 24-13456-356
+always 됴 24-346
+always 됵 24-346-1
+always 됶 24-346-1-1
+always 됷 24-346-1-3
+always 됸 24-346-25
+always 됹 24-346-25-13
+always 됺 24-346-25-356
+always 됻 24-346-35
+always 됼 24-346-2
+always 됽 24-346-2-1
+always 됾 24-346-2-26
+always 됿 24-346-2-12
+always 둀 24-346-2-3
+always 둁 24-346-2-236
+always 둂 24-346-2-256
+always 둃 24-346-2-356
+always 둄 24-346-26
+always 둅 24-346-12
+always 둆 24-346-12-3
+always 둇 24-346-3
+always 둈 24-346-3-3
+always 둉 24-346-2356
+always 둊 24-346-13
+always 둋 24-346-23
+always 둌 24-346-235
+always 둍 24-346-236
+always 둎 24-346-256
+always 둏 24-346-356
+always 두 24-134
+always 둑 24-134-1
+always 둒 24-134-1-1
+always 둓 24-134-1-3
+always 둔 24-134-25
+always 둕 24-134-25-13
+always 둖 24-134-25-356
+always 둗 24-134-35
+always 둘 24-134-2
+always 둙 24-134-2-1
+always 둚 24-134-2-26
+always 둛 24-134-2-12
+always 둜 24-134-2-3
+always 둝 24-134-2-236
+always 둞 24-134-2-256
+always 둟 24-134-2-356
+always 둠 24-134-26
+always 둡 24-134-12
+always 둢 24-134-12-3
+always 둣 24-134-3
+always 둤 24-134-3-3
+always 둥 24-134-2356
+always 둦 24-134-13
+always 둧 24-134-23
+always 둨 24-134-235
+always 둩 24-134-236
+always 둪 24-134-256
+always 둫 24-134-356
+always 둬 24-1234
+always 둭 24-1234-1
+always 둮 24-1234-1-1
+always 둯 24-1234-1-3
+always 둰 24-1234-25
+always 둱 24-1234-25-13
+always 둲 24-1234-25-356
+always 둳 24-1234-35
+always 둴 24-1234-2
+always 둵 24-1234-2-1
+always 둶 24-1234-2-26
+always 둷 24-1234-2-12
+always 둸 24-1234-2-3
+always 둹 24-1234-2-236
+always 둺 24-1234-2-256
+always 둻 24-1234-2-356
+always 둼 24-1234-26
+always 둽 24-1234-12
+always 둾 24-1234-12-3
+always 둿 24-1234-3
+always 뒀 24-1234-3-3
+always 뒁 24-1234-2356
+always 뒂 24-1234-13
+always 뒃 24-1234-23
+always 뒄 24-1234-235
+always 뒅 24-1234-236
+always 뒆 24-1234-256
+always 뒇 24-1234-356
+always 뒈 24-1234-1235
+always 뒉 24-1234-1235-1
+always 뒊 24-1234-1235-1-1
+always 뒋 24-1234-1235-1-3
+always 뒌 24-1234-1235-25
+always 뒍 24-1234-1235-25-13
+always 뒎 24-1234-1235-25-356
+always 뒏 24-1234-1235-35
+always 뒐 24-1234-1235-2
+always 뒑 24-1234-1235-2-1
+always 뒒 24-1234-1235-2-26
+always 뒓 24-1234-1235-2-12
+always 뒔 24-1234-1235-2-3
+always 뒕 24-1234-1235-2-236
+always 뒖 24-1234-1235-2-256
+always 뒗 24-1234-1235-2-356
+always 뒘 24-1234-1235-26
+always 뒙 24-1234-1235-12
+always 뒚 24-1234-1235-12-3
+always 뒛 24-1234-1235-3
+always 뒜 24-1234-1235-3-3
+always 뒝 24-1234-1235-2356
+always 뒞 24-1234-1235-13
+always 뒟 24-1234-1235-23
+always 뒠 24-1234-1235-235
+always 뒡 24-1234-1235-236
+always 뒢 24-1234-1235-256
+always 뒣 24-1234-1235-356
+always 뒤 24-134-1235
+always 뒥 24-134-1235-1
+always 뒦 24-134-1235-1-1
+always 뒧 24-134-1235-1-3
+always 뒨 24-134-1235-25
+always 뒩 24-134-1235-25-13
+always 뒪 24-134-1235-25-356
+always 뒫 24-134-1235-35
+always 뒬 24-134-1235-2
+always 뒭 24-134-1235-2-1
+always 뒮 24-134-1235-2-26
+always 뒯 24-134-1235-2-12
+always 뒰 24-134-1235-2-3
+always 뒱 24-134-1235-2-236
+always 뒲 24-134-1235-2-256
+always 뒳 24-134-1235-2-356
+always 뒴 24-134-1235-26
+always 뒵 24-134-1235-12
+always 뒶 24-134-1235-12-3
+always 뒷 24-134-1235-3
+always 뒸 24-134-1235-3-3
+always 뒹 24-134-1235-2356
+always 뒺 24-134-1235-13
+always 뒻 24-134-1235-23
+always 뒼 24-134-1235-235
+always 뒽 24-134-1235-236
+always 뒾 24-134-1235-256
+always 뒿 24-134-1235-356
+always 듀 24-146
+always 듁 24-146-1
+always 듂 24-146-1-1
+always 듃 24-146-1-3
+always 듄 24-146-25
+always 듅 24-146-25-13
+always 듆 24-146-25-356
+always 듇 24-146-35
+always 듈 24-146-2
+always 듉 24-146-2-1
+always 듊 24-146-2-26
+always 듋 24-146-2-12
+always 듌 24-146-2-3
+always 듍 24-146-2-236
+always 듎 24-146-2-256
+always 듏 24-146-2-356
+always 듐 24-146-26
+always 듑 24-146-12
+always 듒 24-146-12-3
+always 듓 24-146-3
+always 듔 24-146-3-3
+always 듕 24-146-2356
+always 듖 24-146-13
+always 듗 24-146-23
+always 듘 24-146-235
+always 듙 24-146-236
+always 듚 24-146-256
+always 듛 24-146-356
+always 드 24-246
+always 득 24-246-1
+always 듞 24-246-1-1
+always 듟 24-246-1-3
+always 든 24-246-25
+always 듡 24-246-25-13
+always 듢 24-246-25-356
+always 듣 24-246-35
+always 들 24-246-2
+always 듥 24-246-2-1
+always 듦 24-246-2-26
+always 듧 24-246-2-12
+always 듨 24-246-2-3
+always 듩 24-246-2-236
+always 듪 24-246-2-256
+always 듫 24-246-2-356
+always 듬 24-246-26
+always 듭 24-246-12
+always 듮 24-246-12-3
+always 듯 24-246-3
+always 듰 24-246-3-3
+always 등 24-246-2356
+always 듲 24-246-13
+always 듳 24-246-23
+always 듴 24-246-235
+always 듵 24-246-236
+always 듶 24-246-256
+always 듷 24-246-356
+always 듸 24-2456
+always 듹 24-2456-1
+always 듺 24-2456-1-1
+always 듻 24-2456-1-3
+always 듼 24-2456-25
+always 듽 24-2456-25-13
+always 듾 24-2456-25-356
+always 듿 24-2456-35
+always 딀 24-2456-2
+always 딁 24-2456-2-1
+always 딂 24-2456-2-26
+always 딃 24-2456-2-12
+always 딄 24-2456-2-3
+always 딅 24-2456-2-236
+always 딆 24-2456-2-256
+always 딇 24-2456-2-356
+always 딈 24-2456-26
+always 딉 24-2456-12
+always 딊 24-2456-12-3
+always 딋 24-2456-3
+always 딌 24-2456-3-3
+always 딍 24-2456-2356
+always 딎 24-2456-13
+always 딏 24-2456-23
+always 딐 24-2456-235
+always 딑 24-2456-236
+always 딒 24-2456-256
+always 딓 24-2456-356
+always 디 24-135
+always 딕 24-135-1
+always 딖 24-135-1-1
+always 딗 24-135-1-3
+always 딘 24-135-25
+always 딙 24-135-25-13
+always 딚 24-135-25-356
+always 딛 24-135-35
+always 딜 24-135-2
+always 딝 24-135-2-1
+always 딞 24-135-2-26
+always 딟 24-135-2-12
+always 딠 24-135-2-3
+always 딡 24-135-2-236
+always 딢 24-135-2-256
+always 딣 24-135-2-356
+always 딤 24-135-26
+always 딥 24-135-12
+always 딦 24-135-12-3
+always 딧 24-135-3
+always 딨 24-135-3-3
+always 딩 24-135-2356
+always 딪 24-135-13
+always 딫 24-135-23
+always 딬 24-135-235
+always 딭 24-135-236
+always 딮 24-135-256
+always 딯 24-135-356
+always 따 24-24-126
+always 딱 24-24-126-1
+always 딲 24-24-126-1-1
+always 딳 24-24-126-1-3
+always 딴 24-24-126-25
+always 딵 24-24-126-25-13
+always 딶 24-24-126-25-356
+always 딷 24-24-126-35
+always 딸 24-24-126-2
+always 딹 24-24-126-2-1
+always 딺 24-24-126-2-26
+always 딻 24-24-126-2-12
+always 딼 24-24-126-2-3
+always 딽 24-24-126-2-236
+always 딾 24-24-126-2-256
+always 딿 24-24-126-2-356
+always 땀 24-24-126-26
+always 땁 24-24-126-12
+always 땂 24-24-126-12-3
+always 땃 24-24-126-3
+always 땄 24-24-126-3-3
+always 땅 24-24-126-2356
+always 땆 24-24-126-13
+always 땇 24-24-126-23
+always 땈 24-24-126-235
+always 땉 24-24-126-236
+always 땊 24-24-126-256
+always 땋 24-24-126-356
+always 때 24-24-1235
+always 땍 24-24-1235-1
+always 땎 24-24-1235-1-1
+always 땏 24-24-1235-1-3
+always 땐 24-24-1235-25
+always 땑 24-24-1235-25-13
+always 땒 24-24-1235-25-356
+always 땓 24-24-1235-35
+always 땔 24-24-1235-2
+always 땕 24-24-1235-2-1
+always 땖 24-24-1235-2-26
+always 땗 24-24-1235-2-12
+always 땘 24-24-1235-2-3
+always 땙 24-24-1235-2-236
+always 땚 24-24-1235-2-256
+always 땛 24-24-1235-2-356
+always 땜 24-24-1235-26
+always 땝 24-24-1235-12
+always 땞 24-24-1235-12-3
+always 땟 24-24-1235-3
+always 땠 24-24-1235-3-3
+always 땡 24-24-1235-2356
+always 땢 24-24-1235-13
+always 땣 24-24-1235-23
+always 땤 24-24-1235-235
+always 땥 24-24-1235-236
+always 땦 24-24-1235-256
+always 땧 24-24-1235-356
+always 땨 24-24-345
+always 땩 24-24-345-1
+always 땪 24-24-345-1-1
+always 땫 24-24-345-1-3
+always 땬 24-24-345-25
+always 땭 24-24-345-25-13
+always 땮 24-24-345-25-356
+always 땯 24-24-345-35
+always 땰 24-24-345-2
+always 땱 24-24-345-2-1
+always 땲 24-24-345-2-26
+always 땳 24-24-345-2-12
+always 땴 24-24-345-2-3
+always 땵 24-24-345-2-236
+always 땶 24-24-345-2-256
+always 땷 24-24-345-2-356
+always 땸 24-24-345-26
+always 땹 24-24-345-12
+always 땺 24-24-345-12-3
+always 땻 24-24-345-3
+always 땼 24-24-345-3-3
+always 땽 24-24-345-2356
+always 땾 24-24-345-13
+always 땿 24-24-345-23
+always 떀 24-24-345-235
+always 떁 24-24-345-236
+always 떂 24-24-345-256
+always 떃 24-24-345-356
+always 떄 24-24-345-1235
+always 떅 24-24-345-1235-1
+always 떆 24-24-345-1235-1-1
+always 떇 24-24-345-1235-1-3
+always 떈 24-24-345-1235-25
+always 떉 24-24-345-1235-25-13
+always 떊 24-24-345-1235-25-356
+always 떋 24-24-345-1235-35
+always 떌 24-24-345-1235-2
+always 떍 24-24-345-1235-2-1
+always 떎 24-24-345-1235-2-26
+always 떏 24-24-345-1235-2-12
+always 떐 24-24-345-1235-2-3
+always 떑 24-24-345-1235-2-236
+always 떒 24-24-345-1235-2-256
+always 떓 24-24-345-1235-2-356
+always 떔 24-24-345-1235-26
+always 떕 24-24-345-1235-12
+always 떖 24-24-345-1235-12-3
+always 떗 24-24-345-1235-3
+always 떘 24-24-345-1235-3-3
+always 떙 24-24-345-1235-2356
+always 떚 24-24-345-1235-13
+always 떛 24-24-345-1235-23
+always 떜 24-24-345-1235-235
+always 떝 24-24-345-1235-236
+always 떞 24-24-345-1235-256
+always 떟 24-24-345-1235-356
+always 떠 24-24-234
+always 떡 24-24-234-1
+always 떢 24-24-234-1-1
+always 떣 24-24-234-1-3
+always 떤 24-24-234-25
+always 떥 24-24-234-25-13
+always 떦 24-24-234-25-356
+always 떧 24-24-234-35
+always 떨 24-24-234-2
+always 떩 24-24-234-2-1
+always 떪 24-24-234-2-26
+always 떫 24-24-234-2-12
+always 떬 24-24-234-2-3
+always 떭 24-24-234-2-236
+always 떮 24-24-234-2-256
+always 떯 24-24-234-2-356
+always 떰 24-24-234-26
+always 떱 24-24-234-12
+always 떲 24-24-234-12-3
+always 떳 24-24-234-3
+always 떴 24-24-234-3-3
+always 떵 24-24-234-2356
+always 떶 24-24-234-13
+always 떷 24-24-234-23
+always 떸 24-24-234-235
+always 떹 24-24-234-236
+always 떺 24-24-234-256
+always 떻 24-24-234-356
+always 떼 24-24-1345
+always 떽 24-24-1345-1
+always 떾 24-24-1345-1-1
+always 떿 24-24-1345-1-3
+always 뗀 24-24-1345-25
+always 뗁 24-24-1345-25-13
+always 뗂 24-24-1345-25-356
+always 뗃 24-24-1345-35
+always 뗄 24-24-1345-2
+always 뗅 24-24-1345-2-1
+always 뗆 24-24-1345-2-26
+always 뗇 24-24-1345-2-12
+always 뗈 24-24-1345-2-3
+always 뗉 24-24-1345-2-236
+always 뗊 24-24-1345-2-256
+always 뗋 24-24-1345-2-356
+always 뗌 24-24-1345-26
+always 뗍 24-24-1345-12
+always 뗎 24-24-1345-12-3
+always 뗏 24-24-1345-3
+always 뗐 24-24-1345-3-3
+always 뗑 24-24-1345-2356
+always 뗒 24-24-1345-13
+always 뗓 24-24-1345-23
+always 뗔 24-24-1345-235
+always 뗕 24-24-1345-236
+always 뗖 24-24-1345-256
+always 뗗 24-24-1345-356
+always 뗘 24-24-156
+always 뗙 24-24-156-1
+always 뗚 24-24-156-1-1
+always 뗛 24-24-156-1-3
+always 뗜 24-24-156-25
+always 뗝 24-24-156-25-13
+always 뗞 24-24-156-25-356
+always 뗟 24-24-156-35
+always 뗠 24-24-156-2
+always 뗡 24-24-156-2-1
+always 뗢 24-24-156-2-26
+always 뗣 24-24-156-2-12
+always 뗤 24-24-156-2-3
+always 뗥 24-24-156-2-236
+always 뗦 24-24-156-2-256
+always 뗧 24-24-156-2-356
+always 뗨 24-24-156-26
+always 뗩 24-24-156-12
+always 뗪 24-24-156-12-3
+always 뗫 24-24-156-3
+always 뗬 24-24-156-3-3
+always 뗭 24-24-156-2356
+always 뗮 24-24-156-13
+always 뗯 24-24-156-23
+always 뗰 24-24-156-235
+always 뗱 24-24-156-236
+always 뗲 24-24-156-256
+always 뗳 24-24-156-356
+always 뗴 24-24-34
+always 뗵 24-24-34-1
+always 뗶 24-24-34-1-1
+always 뗷 24-24-34-1-3
+always 뗸 24-24-34-25
+always 뗹 24-24-34-25-13
+always 뗺 24-24-34-25-356
+always 뗻 24-24-34-35
+always 뗼 24-24-34-2
+always 뗽 24-24-34-2-1
+always 뗾 24-24-34-2-26
+always 뗿 24-24-34-2-12
+always 똀 24-24-34-2-3
+always 똁 24-24-34-2-236
+always 똂 24-24-34-2-256
+always 똃 24-24-34-2-356
+always 똄 24-24-34-26
+always 똅 24-24-34-12
+always 똆 24-24-34-12-3
+always 똇 24-24-34-3
+always 똈 24-24-34-3-3
+always 똉 24-24-34-2356
+always 똊 24-24-34-13
+always 똋 24-24-34-23
+always 똌 24-24-34-235
+always 똍 24-24-34-236
+always 똎 24-24-34-256
+always 똏 24-24-34-356
+always 또 24-24-136
+always 똑 24-24-136-1
+always 똒 24-24-136-1-1
+always 똓 24-24-136-1-3
+always 똔 24-24-136-25
+always 똕 24-24-136-25-13
+always 똖 24-24-136-25-356
+always 똗 24-24-136-35
+always 똘 24-24-136-2
+always 똙 24-24-136-2-1
+always 똚 24-24-136-2-26
+always 똛 24-24-136-2-12
+always 똜 24-24-136-2-3
+always 똝 24-24-136-2-236
+always 똞 24-24-136-2-256
+always 똟 24-24-136-2-356
+always 똠 24-24-136-26
+always 똡 24-24-136-12
+always 똢 24-24-136-12-3
+always 똣 24-24-136-3
+always 똤 24-24-136-3-3
+always 똥 24-24-136-2356
+always 똦 24-24-136-13
+always 똧 24-24-136-23
+always 똨 24-24-136-235
+always 똩 24-24-136-236
+always 똪 24-24-136-256
+always 똫 24-24-136-356
+always 똬 24-24-1236
+always 똭 24-24-1236-1
+always 똮 24-24-1236-1-1
+always 똯 24-24-1236-1-3
+always 똰 24-24-1236-25
+always 똱 24-24-1236-25-13
+always 똲 24-24-1236-25-356
+always 똳 24-24-1236-35
+always 똴 24-24-1236-2
+always 똵 24-24-1236-2-1
+always 똶 24-24-1236-2-26
+always 똷 24-24-1236-2-12
+always 똸 24-24-1236-2-3
+always 똹 24-24-1236-2-236
+always 똺 24-24-1236-2-256
+always 똻 24-24-1236-2-356
+always 똼 24-24-1236-26
+always 똽 24-24-1236-12
+always 똾 24-24-1236-12-3
+always 똿 24-24-1236-3
+always 뙀 24-24-1236-3-3
+always 뙁 24-24-1236-2356
+always 뙂 24-24-1236-13
+always 뙃 24-24-1236-23
+always 뙄 24-24-1236-235
+always 뙅 24-24-1236-236
+always 뙆 24-24-1236-256
+always 뙇 24-24-1236-356
+always 뙈 24-24-1236-1235
+always 뙉 24-24-1236-1235-1
+always 뙊 24-24-1236-1235-1-1
+always 뙋 24-24-1236-1235-1-3
+always 뙌 24-24-1236-1235-25
+always 뙍 24-24-1236-1235-25-13
+always 뙎 24-24-1236-1235-25-356
+always 뙏 24-24-1236-1235-35
+always 뙐 24-24-1236-1235-2
+always 뙑 24-24-1236-1235-2-1
+always 뙒 24-24-1236-1235-2-26
+always 뙓 24-24-1236-1235-2-12
+always 뙔 24-24-1236-1235-2-3
+always 뙕 24-24-1236-1235-2-236
+always 뙖 24-24-1236-1235-2-256
+always 뙗 24-24-1236-1235-2-356
+always 뙘 24-24-1236-1235-26
+always 뙙 24-24-1236-1235-12
+always 뙚 24-24-1236-1235-12-3
+always 뙛 24-24-1236-1235-3
+always 뙜 24-24-1236-1235-3-3
+always 뙝 24-24-1236-1235-2356
+always 뙞 24-24-1236-1235-13
+always 뙟 24-24-1236-1235-23
+always 뙠 24-24-1236-1235-235
+always 뙡 24-24-1236-1235-236
+always 뙢 24-24-1236-1235-256
+always 뙣 24-24-1236-1235-356
+always 뙤 24-24-13456
+always 뙥 24-24-13456-1
+always 뙦 24-24-13456-1-1
+always 뙧 24-24-13456-1-3
+always 뙨 24-24-13456-25
+always 뙩 24-24-13456-25-13
+always 뙪 24-24-13456-25-356
+always 뙫 24-24-13456-35
+always 뙬 24-24-13456-2
+always 뙭 24-24-13456-2-1
+always 뙮 24-24-13456-2-26
+always 뙯 24-24-13456-2-12
+always 뙰 24-24-13456-2-3
+always 뙱 24-24-13456-2-236
+always 뙲 24-24-13456-2-256
+always 뙳 24-24-13456-2-356
+always 뙴 24-24-13456-26
+always 뙵 24-24-13456-12
+always 뙶 24-24-13456-12-3
+always 뙷 24-24-13456-3
+always 뙸 24-24-13456-3-3
+always 뙹 24-24-13456-2356
+always 뙺 24-24-13456-13
+always 뙻 24-24-13456-23
+always 뙼 24-24-13456-235
+always 뙽 24-24-13456-236
+always 뙾 24-24-13456-256
+always 뙿 24-24-13456-356
+always 뚀 24-24-346
+always 뚁 24-24-346-1
+always 뚂 24-24-346-1-1
+always 뚃 24-24-346-1-3
+always 뚄 24-24-346-25
+always 뚅 24-24-346-25-13
+always 뚆 24-24-346-25-356
+always 뚇 24-24-346-35
+always 뚈 24-24-346-2
+always 뚉 24-24-346-2-1
+always 뚊 24-24-346-2-26
+always 뚋 24-24-346-2-12
+always 뚌 24-24-346-2-3
+always 뚍 24-24-346-2-236
+always 뚎 24-24-346-2-256
+always 뚏 24-24-346-2-356
+always 뚐 24-24-346-26
+always 뚑 24-24-346-12
+always 뚒 24-24-346-12-3
+always 뚓 24-24-346-3
+always 뚔 24-24-346-3-3
+always 뚕 24-24-346-2356
+always 뚖 24-24-346-13
+always 뚗 24-24-346-23
+always 뚘 24-24-346-235
+always 뚙 24-24-346-236
+always 뚚 24-24-346-256
+always 뚛 24-24-346-356
+always 뚜 24-24-134
+always 뚝 24-24-134-1
+always 뚞 24-24-134-1-1
+always 뚟 24-24-134-1-3
+always 뚠 24-24-134-25
+always 뚡 24-24-134-25-13
+always 뚢 24-24-134-25-356
+always 뚣 24-24-134-35
+always 뚤 24-24-134-2
+always 뚥 24-24-134-2-1
+always 뚦 24-24-134-2-26
+always 뚧 24-24-134-2-12
+always 뚨 24-24-134-2-3
+always 뚩 24-24-134-2-236
+always 뚪 24-24-134-2-256
+always 뚫 24-24-134-2-356
+always 뚬 24-24-134-26
+always 뚭 24-24-134-12
+always 뚮 24-24-134-12-3
+always 뚯 24-24-134-3
+always 뚰 24-24-134-3-3
+always 뚱 24-24-134-2356
+always 뚲 24-24-134-13
+always 뚳 24-24-134-23
+always 뚴 24-24-134-235
+always 뚵 24-24-134-236
+always 뚶 24-24-134-256
+always 뚷 24-24-134-356
+always 뚸 24-24-1234
+always 뚹 24-24-1234-1
+always 뚺 24-24-1234-1-1
+always 뚻 24-24-1234-1-3
+always 뚼 24-24-1234-25
+always 뚽 24-24-1234-25-13
+always 뚾 24-24-1234-25-356
+always 뚿 24-24-1234-35
+always 뛀 24-24-1234-2
+always 뛁 24-24-1234-2-1
+always 뛂 24-24-1234-2-26
+always 뛃 24-24-1234-2-12
+always 뛄 24-24-1234-2-3
+always 뛅 24-24-1234-2-236
+always 뛆 24-24-1234-2-256
+always 뛇 24-24-1234-2-356
+always 뛈 24-24-1234-26
+always 뛉 24-24-1234-12
+always 뛊 24-24-1234-12-3
+always 뛋 24-24-1234-3
+always 뛌 24-24-1234-3-3
+always 뛍 24-24-1234-2356
+always 뛎 24-24-1234-13
+always 뛏 24-24-1234-23
+always 뛐 24-24-1234-235
+always 뛑 24-24-1234-236
+always 뛒 24-24-1234-256
+always 뛓 24-24-1234-356
+always 뛔 24-24-1234-1235
+always 뛕 24-24-1234-1235-1
+always 뛖 24-24-1234-1235-1-1
+always 뛗 24-24-1234-1235-1-3
+always 뛘 24-24-1234-1235-25
+always 뛙 24-24-1234-1235-25-13
+always 뛚 24-24-1234-1235-25-356
+always 뛛 24-24-1234-1235-35
+always 뛜 24-24-1234-1235-2
+always 뛝 24-24-1234-1235-2-1
+always 뛞 24-24-1234-1235-2-26
+always 뛟 24-24-1234-1235-2-12
+always 뛠 24-24-1234-1235-2-3
+always 뛡 24-24-1234-1235-2-236
+always 뛢 24-24-1234-1235-2-256
+always 뛣 24-24-1234-1235-2-356
+always 뛤 24-24-1234-1235-26
+always 뛥 24-24-1234-1235-12
+always 뛦 24-24-1234-1235-12-3
+always 뛧 24-24-1234-1235-3
+always 뛨 24-24-1234-1235-3-3
+always 뛩 24-24-1234-1235-2356
+always 뛪 24-24-1234-1235-13
+always 뛫 24-24-1234-1235-23
+always 뛬 24-24-1234-1235-235
+always 뛭 24-24-1234-1235-236
+always 뛮 24-24-1234-1235-256
+always 뛯 24-24-1234-1235-356
+always 뛰 24-24-134-1235
+always 뛱 24-24-134-1235-1
+always 뛲 24-24-134-1235-1-1
+always 뛳 24-24-134-1235-1-3
+always 뛴 24-24-134-1235-25
+always 뛵 24-24-134-1235-25-13
+always 뛶 24-24-134-1235-25-356
+always 뛷 24-24-134-1235-35
+always 뛸 24-24-134-1235-2
+always 뛹 24-24-134-1235-2-1
+always 뛺 24-24-134-1235-2-26
+always 뛻 24-24-134-1235-2-12
+always 뛼 24-24-134-1235-2-3
+always 뛽 24-24-134-1235-2-236
+always 뛾 24-24-134-1235-2-256
+always 뛿 24-24-134-1235-2-356
+always 뜀 24-24-134-1235-26
+always 뜁 24-24-134-1235-12
+always 뜂 24-24-134-1235-12-3
+always 뜃 24-24-134-1235-3
+always 뜄 24-24-134-1235-3-3
+always 뜅 24-24-134-1235-2356
+always 뜆 24-24-134-1235-13
+always 뜇 24-24-134-1235-23
+always 뜈 24-24-134-1235-235
+always 뜉 24-24-134-1235-236
+always 뜊 24-24-134-1235-256
+always 뜋 24-24-134-1235-356
+always 뜌 24-24-146
+always 뜍 24-24-146-1
+always 뜎 24-24-146-1-1
+always 뜏 24-24-146-1-3
+always 뜐 24-24-146-25
+always 뜑 24-24-146-25-13
+always 뜒 24-24-146-25-356
+always 뜓 24-24-146-35
+always 뜔 24-24-146-2
+always 뜕 24-24-146-2-1
+always 뜖 24-24-146-2-26
+always 뜗 24-24-146-2-12
+always 뜘 24-24-146-2-3
+always 뜙 24-24-146-2-236
+always 뜚 24-24-146-2-256
+always 뜛 24-24-146-2-356
+always 뜜 24-24-146-26
+always 뜝 24-24-146-12
+always 뜞 24-24-146-12-3
+always 뜟 24-24-146-3
+always 뜠 24-24-146-3-3
+always 뜡 24-24-146-2356
+always 뜢 24-24-146-13
+always 뜣 24-24-146-23
+always 뜤 24-24-146-235
+always 뜥 24-24-146-236
+always 뜦 24-24-146-256
+always 뜧 24-24-146-356
+always 뜨 24-24-246
+always 뜩 24-24-246-1
+always 뜪 24-24-246-1-1
+always 뜫 24-24-246-1-3
+always 뜬 24-24-246-25
+always 뜭 24-24-246-25-13
+always 뜮 24-24-246-25-356
+always 뜯 24-24-246-35
+always 뜰 24-24-246-2
+always 뜱 24-24-246-2-1
+always 뜲 24-24-246-2-26
+always 뜳 24-24-246-2-12
+always 뜴 24-24-246-2-3
+always 뜵 24-24-246-2-236
+always 뜶 24-24-246-2-256
+always 뜷 24-24-246-2-356
+always 뜸 24-24-246-26
+always 뜹 24-24-246-12
+always 뜺 24-24-246-12-3
+always 뜻 24-24-246-3
+always 뜼 24-24-246-3-3
+always 뜽 24-24-246-2356
+always 뜾 24-24-246-13
+always 뜿 24-24-246-23
+always 띀 24-24-246-235
+always 띁 24-24-246-236
+always 띂 24-24-246-256
+always 띃 24-24-246-356
+always 띄 24-24-2456
+always 띅 24-24-2456-1
+always 띆 24-24-2456-1-1
+always 띇 24-24-2456-1-3
+always 띈 24-24-2456-25
+always 띉 24-24-2456-25-13
+always 띊 24-24-2456-25-356
+always 띋 24-24-2456-35
+always 띌 24-24-2456-2
+always 띍 24-24-2456-2-1
+always 띎 24-24-2456-2-26
+always 띏 24-24-2456-2-12
+always 띐 24-24-2456-2-3
+always 띑 24-24-2456-2-236
+always 띒 24-24-2456-2-256
+always 띓 24-24-2456-2-356
+always 띔 24-24-2456-26
+always 띕 24-24-2456-12
+always 띖 24-24-2456-12-3
+always 띗 24-24-2456-3
+always 띘 24-24-2456-3-3
+always 띙 24-24-2456-2356
+always 띚 24-24-2456-13
+always 띛 24-24-2456-23
+always 띜 24-24-2456-235
+always 띝 24-24-2456-236
+always 띞 24-24-2456-256
+always 띟 24-24-2456-356
+always 띠 24-24-135
+always 띡 24-24-135-1
+always 띢 24-24-135-1-1
+always 띣 24-24-135-1-3
+always 띤 24-24-135-25
+always 띥 24-24-135-25-13
+always 띦 24-24-135-25-356
+always 띧 24-24-135-35
+always 띨 24-24-135-2
+always 띩 24-24-135-2-1
+always 띪 24-24-135-2-26
+always 띫 24-24-135-2-12
+always 띬 24-24-135-2-3
+always 띭 24-24-135-2-236
+always 띮 24-24-135-2-256
+always 띯 24-24-135-2-356
+always 띰 24-24-135-26
+always 띱 24-24-135-12
+always 띲 24-24-135-12-3
+always 띳 24-24-135-3
+always 띴 24-24-135-3-3
+always 띵 24-24-135-2356
+always 띶 24-24-135-13
+always 띷 24-24-135-23
+always 띸 24-24-135-235
+always 띹 24-24-135-236
+always 띺 24-24-135-256
+always 띻 24-24-135-356
+always 라 5-126
+always 락 5-126-1
+always 띾 5-126-1-1
+always 띿 5-126-1-3
+always 란 5-126-25
+always 랁 5-126-25-13
+always 랂 5-126-25-356
+always 랃 5-126-35
+always 랄 5-126-2
+always 랅 5-126-2-1
+always 랆 5-126-2-26
+always 랇 5-126-2-12
+always 랈 5-126-2-3
+always 랉 5-126-2-236
+always 랊 5-126-2-256
+always 랋 5-126-2-356
+always 람 5-126-26
+always 랍 5-126-12
+always 랎 5-126-12-3
+always 랏 5-126-3
+always 랐 5-126-3-3
+always 랑 5-126-2356
+always 랒 5-126-13
+always 랓 5-126-23
+always 랔 5-126-235
+always 랕 5-126-236
+always 랖 5-126-256
+always 랗 5-126-356
+always 래 5-1235
+always 랙 5-1235-1
+always 랚 5-1235-1-1
+always 랛 5-1235-1-3
+always 랜 5-1235-25
+always 랝 5-1235-25-13
+always 랞 5-1235-25-356
+always 랟 5-1235-35
+always 랠 5-1235-2
+always 랡 5-1235-2-1
+always 랢 5-1235-2-26
+always 랣 5-1235-2-12
+always 랤 5-1235-2-3
+always 랥 5-1235-2-236
+always 랦 5-1235-2-256
+always 랧 5-1235-2-356
+always 램 5-1235-26
+always 랩 5-1235-12
+always 랪 5-1235-12-3
+always 랫 5-1235-3
+always 랬 5-1235-3-3
+always 랭 5-1235-2356
+always 랮 5-1235-13
+always 랯 5-1235-23
+always 랰 5-1235-235
+always 랱 5-1235-236
+always 랲 5-1235-256
+always 랳 5-1235-356
+always 랴 5-345
+always 략 5-345-1
+always 랶 5-345-1-1
+always 랷 5-345-1-3
+always 랸 5-345-25
+always 랹 5-345-25-13
+always 랺 5-345-25-356
+always 랻 5-345-35
+always 랼 5-345-2
+always 랽 5-345-2-1
+always 랾 5-345-2-26
+always 랿 5-345-2-12
+always 럀 5-345-2-3
+always 럁 5-345-2-236
+always 럂 5-345-2-256
+always 럃 5-345-2-356
+always 럄 5-345-26
+always 럅 5-345-12
+always 럆 5-345-12-3
+always 럇 5-345-3
+always 럈 5-345-3-3
+always 량 5-345-2356
+always 럊 5-345-13
+always 럋 5-345-23
+always 럌 5-345-235
+always 럍 5-345-236
+always 럎 5-345-256
+always 럏 5-345-356
+always 럐 5-345-1235
+always 럑 5-345-1235-1
+always 럒 5-345-1235-1-1
+always 럓 5-345-1235-1-3
+always 럔 5-345-1235-25
+always 럕 5-345-1235-25-13
+always 럖 5-345-1235-25-356
+always 럗 5-345-1235-35
+always 럘 5-345-1235-2
+always 럙 5-345-1235-2-1
+always 럚 5-345-1235-2-26
+always 럛 5-345-1235-2-12
+always 럜 5-345-1235-2-3
+always 럝 5-345-1235-2-236
+always 럞 5-345-1235-2-256
+always 럟 5-345-1235-2-356
+always 럠 5-345-1235-26
+always 럡 5-345-1235-12
+always 럢 5-345-1235-12-3
+always 럣 5-345-1235-3
+always 럤 5-345-1235-3-3
+always 럥 5-345-1235-2356
+always 럦 5-345-1235-13
+always 럧 5-345-1235-23
+always 럨 5-345-1235-235
+always 럩 5-345-1235-236
+always 럪 5-345-1235-256
+always 럫 5-345-1235-356
+always 러 5-234
+always 럭 5-234-1
+always 럮 5-234-1-1
+always 럯 5-234-1-3
+always 런 5-234-25
+always 럱 5-234-25-13
+always 럲 5-234-25-356
+always 럳 5-234-35
+always 럴 5-234-2
+always 럵 5-234-2-1
+always 럶 5-234-2-26
+always 럷 5-234-2-12
+always 럸 5-234-2-3
+always 럹 5-234-2-236
+always 럺 5-234-2-256
+always 럻 5-234-2-356
+always 럼 5-234-26
+always 럽 5-234-12
+always 럾 5-234-12-3
+always 럿 5-234-3
+always 렀 5-234-3-3
+always 렁 5-234-2356
+always 렂 5-234-13
+always 렃 5-234-23
+always 렄 5-234-235
+always 렅 5-234-236
+always 렆 5-234-256
+always 렇 5-234-356
+always 레 5-1345
+always 렉 5-1345-1
+always 렊 5-1345-1-1
+always 렋 5-1345-1-3
+always 렌 5-1345-25
+always 렍 5-1345-25-13
+always 렎 5-1345-25-356
+always 렏 5-1345-35
+always 렐 5-1345-2
+always 렑 5-1345-2-1
+always 렒 5-1345-2-26
+always 렓 5-1345-2-12
+always 렔 5-1345-2-3
+always 렕 5-1345-2-236
+always 렖 5-1345-2-256
+always 렗 5-1345-2-356
+always 렘 5-1345-26
+always 렙 5-1345-12
+always 렚 5-1345-12-3
+always 렛 5-1345-3
+always 렜 5-1345-3-3
+always 렝 5-1345-2356
+always 렞 5-1345-13
+always 렟 5-1345-23
+always 렠 5-1345-235
+always 렡 5-1345-236
+always 렢 5-1345-256
+always 렣 5-1345-356
+always 려 5-156
+always 력 5-156-1
+always 렦 5-156-1-1
+always 렧 5-156-1-3
+always 련 5-156-25
+always 렩 5-156-25-13
+always 렪 5-156-25-356
+always 렫 5-156-35
+always 렬 5-156-2
+always 렭 5-156-2-1
+always 렮 5-156-2-26
+always 렯 5-156-2-12
+always 렰 5-156-2-3
+always 렱 5-156-2-236
+always 렲 5-156-2-256
+always 렳 5-156-2-356
+always 렴 5-156-26
+always 렵 5-156-12
+always 렶 5-156-12-3
+always 렷 5-156-3
+always 렸 5-156-3-3
+always 령 5-156-2356
+always 렺 5-156-13
+always 렻 5-156-23
+always 렼 5-156-235
+always 렽 5-156-236
+always 렾 5-156-256
+always 렿 5-156-356
+always 례 5-34
+always 롁 5-34-1
+always 롂 5-34-1-1
+always 롃 5-34-1-3
+always 롄 5-34-25
+always 롅 5-34-25-13
+always 롆 5-34-25-356
+always 롇 5-34-35
+always 롈 5-34-2
+always 롉 5-34-2-1
+always 롊 5-34-2-26
+always 롋 5-34-2-12
+always 롌 5-34-2-3
+always 롍 5-34-2-236
+always 롎 5-34-2-256
+always 롏 5-34-2-356
+always 롐 5-34-26
+always 롑 5-34-12
+always 롒 5-34-12-3
+always 롓 5-34-3
+always 롔 5-34-3-3
+always 롕 5-34-2356
+always 롖 5-34-13
+always 롗 5-34-23
+always 롘 5-34-235
+always 롙 5-34-236
+always 롚 5-34-256
+always 롛 5-34-356
+always 로 5-136
+always 록 5-136-1
+always 롞 5-136-1-1
+always 롟 5-136-1-3
+always 론 5-136-25
+always 롡 5-136-25-13
+always 롢 5-136-25-356
+always 롣 5-136-35
+always 롤 5-136-2
+always 롥 5-136-2-1
+always 롦 5-136-2-26
+always 롧 5-136-2-12
+always 롨 5-136-2-3
+always 롩 5-136-2-236
+always 롪 5-136-2-256
+always 롫 5-136-2-356
+always 롬 5-136-26
+always 롭 5-136-12
+always 롮 5-136-12-3
+always 롯 5-136-3
+always 롰 5-136-3-3
+always 롱 5-136-2356
+always 롲 5-136-13
+always 롳 5-136-23
+always 롴 5-136-235
+always 롵 5-136-236
+always 롶 5-136-256
+always 롷 5-136-356
+always 롸 5-1236
+always 롹 5-1236-1
+always 롺 5-1236-1-1
+always 롻 5-1236-1-3
+always 롼 5-1236-25
+always 롽 5-1236-25-13
+always 롾 5-1236-25-356
+always 롿 5-1236-35
+always 뢀 5-1236-2
+always 뢁 5-1236-2-1
+always 뢂 5-1236-2-26
+always 뢃 5-1236-2-12
+always 뢄 5-1236-2-3
+always 뢅 5-1236-2-236
+always 뢆 5-1236-2-256
+always 뢇 5-1236-2-356
+always 뢈 5-1236-26
+always 뢉 5-1236-12
+always 뢊 5-1236-12-3
+always 뢋 5-1236-3
+always 뢌 5-1236-3-3
+always 뢍 5-1236-2356
+always 뢎 5-1236-13
+always 뢏 5-1236-23
+always 뢐 5-1236-235
+always 뢑 5-1236-236
+always 뢒 5-1236-256
+always 뢓 5-1236-356
+always 뢔 5-1236-1235
+always 뢕 5-1236-1235-1
+always 뢖 5-1236-1235-1-1
+always 뢗 5-1236-1235-1-3
+always 뢘 5-1236-1235-25
+always 뢙 5-1236-1235-25-13
+always 뢚 5-1236-1235-25-356
+always 뢛 5-1236-1235-35
+always 뢜 5-1236-1235-2
+always 뢝 5-1236-1235-2-1
+always 뢞 5-1236-1235-2-26
+always 뢟 5-1236-1235-2-12
+always 뢠 5-1236-1235-2-3
+always 뢡 5-1236-1235-2-236
+always 뢢 5-1236-1235-2-256
+always 뢣 5-1236-1235-2-356
+always 뢤 5-1236-1235-26
+always 뢥 5-1236-1235-12
+always 뢦 5-1236-1235-12-3
+always 뢧 5-1236-1235-3
+always 뢨 5-1236-1235-3-3
+always 뢩 5-1236-1235-2356
+always 뢪 5-1236-1235-13
+always 뢫 5-1236-1235-23
+always 뢬 5-1236-1235-235
+always 뢭 5-1236-1235-236
+always 뢮 5-1236-1235-256
+always 뢯 5-1236-1235-356
+always 뢰 5-13456
+always 뢱 5-13456-1
+always 뢲 5-13456-1-1
+always 뢳 5-13456-1-3
+always 뢴 5-13456-25
+always 뢵 5-13456-25-13
+always 뢶 5-13456-25-356
+always 뢷 5-13456-35
+always 뢸 5-13456-2
+always 뢹 5-13456-2-1
+always 뢺 5-13456-2-26
+always 뢻 5-13456-2-12
+always 뢼 5-13456-2-3
+always 뢽 5-13456-2-236
+always 뢾 5-13456-2-256
+always 뢿 5-13456-2-356
+always 룀 5-13456-26
+always 룁 5-13456-12
+always 룂 5-13456-12-3
+always 룃 5-13456-3
+always 룄 5-13456-3-3
+always 룅 5-13456-2356
+always 룆 5-13456-13
+always 룇 5-13456-23
+always 룈 5-13456-235
+always 룉 5-13456-236
+always 룊 5-13456-256
+always 룋 5-13456-356
+always 료 5-346
+always 룍 5-346-1
+always 룎 5-346-1-1
+always 룏 5-346-1-3
+always 룐 5-346-25
+always 룑 5-346-25-13
+always 룒 5-346-25-356
+always 룓 5-346-35
+always 룔 5-346-2
+always 룕 5-346-2-1
+always 룖 5-346-2-26
+always 룗 5-346-2-12
+always 룘 5-346-2-3
+always 룙 5-346-2-236
+always 룚 5-346-2-256
+always 룛 5-346-2-356
+always 룜 5-346-26
+always 룝 5-346-12
+always 룞 5-346-12-3
+always 룟 5-346-3
+always 룠 5-346-3-3
+always 룡 5-346-2356
+always 룢 5-346-13
+always 룣 5-346-23
+always 룤 5-346-235
+always 룥 5-346-236
+always 룦 5-346-256
+always 룧 5-346-356
+always 루 5-134
+always 룩 5-134-1
+always 룪 5-134-1-1
+always 룫 5-134-1-3
+always 룬 5-134-25
+always 룭 5-134-25-13
+always 룮 5-134-25-356
+always 룯 5-134-35
+always 룰 5-134-2
+always 룱 5-134-2-1
+always 룲 5-134-2-26
+always 룳 5-134-2-12
+always 룴 5-134-2-3
+always 룵 5-134-2-236
+always 룶 5-134-2-256
+always 룷 5-134-2-356
+always 룸 5-134-26
+always 룹 5-134-12
+always 룺 5-134-12-3
+always 룻 5-134-3
+always 룼 5-134-3-3
+always 룽 5-134-2356
+always 룾 5-134-13
+always 룿 5-134-23
+always 뤀 5-134-235
+always 뤁 5-134-236
+always 뤂 5-134-256
+always 뤃 5-134-356
+always 뤄 5-1234
+always 뤅 5-1234-1
+always 뤆 5-1234-1-1
+always 뤇 5-1234-1-3
+always 뤈 5-1234-25
+always 뤉 5-1234-25-13
+always 뤊 5-1234-25-356
+always 뤋 5-1234-35
+always 뤌 5-1234-2
+always 뤍 5-1234-2-1
+always 뤎 5-1234-2-26
+always 뤏 5-1234-2-12
+always 뤐 5-1234-2-3
+always 뤑 5-1234-2-236
+always 뤒 5-1234-2-256
+always 뤓 5-1234-2-356
+always 뤔 5-1234-26
+always 뤕 5-1234-12
+always 뤖 5-1234-12-3
+always 뤗 5-1234-3
+always 뤘 5-1234-3-3
+always 뤙 5-1234-2356
+always 뤚 5-1234-13
+always 뤛 5-1234-23
+always 뤜 5-1234-235
+always 뤝 5-1234-236
+always 뤞 5-1234-256
+always 뤟 5-1234-356
+always 뤠 5-1234-1235
+always 뤡 5-1234-1235-1
+always 뤢 5-1234-1235-1-1
+always 뤣 5-1234-1235-1-3
+always 뤤 5-1234-1235-25
+always 뤥 5-1234-1235-25-13
+always 뤦 5-1234-1235-25-356
+always 뤧 5-1234-1235-35
+always 뤨 5-1234-1235-2
+always 뤩 5-1234-1235-2-1
+always 뤪 5-1234-1235-2-26
+always 뤫 5-1234-1235-2-12
+always 뤬 5-1234-1235-2-3
+always 뤭 5-1234-1235-2-236
+always 뤮 5-1234-1235-2-256
+always 뤯 5-1234-1235-2-356
+always 뤰 5-1234-1235-26
+always 뤱 5-1234-1235-12
+always 뤲 5-1234-1235-12-3
+always 뤳 5-1234-1235-3
+always 뤴 5-1234-1235-3-3
+always 뤵 5-1234-1235-2356
+always 뤶 5-1234-1235-13
+always 뤷 5-1234-1235-23
+always 뤸 5-1234-1235-235
+always 뤹 5-1234-1235-236
+always 뤺 5-1234-1235-256
+always 뤻 5-1234-1235-356
+always 뤼 5-134-1235
+always 뤽 5-134-1235-1
+always 뤾 5-134-1235-1-1
+always 뤿 5-134-1235-1-3
+always 륀 5-134-1235-25
+always 륁 5-134-1235-25-13
+always 륂 5-134-1235-25-356
+always 륃 5-134-1235-35
+always 륄 5-134-1235-2
+always 륅 5-134-1235-2-1
+always 륆 5-134-1235-2-26
+always 륇 5-134-1235-2-12
+always 륈 5-134-1235-2-3
+always 륉 5-134-1235-2-236
+always 륊 5-134-1235-2-256
+always 륋 5-134-1235-2-356
+always 륌 5-134-1235-26
+always 륍 5-134-1235-12
+always 륎 5-134-1235-12-3
+always 륏 5-134-1235-3
+always 륐 5-134-1235-3-3
+always 륑 5-134-1235-2356
+always 륒 5-134-1235-13
+always 륓 5-134-1235-23
+always 륔 5-134-1235-235
+always 륕 5-134-1235-236
+always 륖 5-134-1235-256
+always 륗 5-134-1235-356
+always 류 5-146
+always 륙 5-146-1
+always 륚 5-146-1-1
+always 륛 5-146-1-3
+always 륜 5-146-25
+always 륝 5-146-25-13
+always 륞 5-146-25-356
+always 륟 5-146-35
+always 률 5-146-2
+always 륡 5-146-2-1
+always 륢 5-146-2-26
+always 륣 5-146-2-12
+always 륤 5-146-2-3
+always 륥 5-146-2-236
+always 륦 5-146-2-256
+always 륧 5-146-2-356
+always 륨 5-146-26
+always 륩 5-146-12
+always 륪 5-146-12-3
+always 륫 5-146-3
+always 륬 5-146-3-3
+always 륭 5-146-2356
+always 륮 5-146-13
+always 륯 5-146-23
+always 륰 5-146-235
+always 륱 5-146-236
+always 륲 5-146-256
+always 륳 5-146-356
+always 르 5-246
+always 륵 5-246-1
+always 륶 5-246-1-1
+always 륷 5-246-1-3
+always 른 5-246-25
+always 륹 5-246-25-13
+always 륺 5-246-25-356
+always 륻 5-246-35
+always 를 5-246-2
+always 륽 5-246-2-1
+always 륾 5-246-2-26
+always 륿 5-246-2-12
+always 릀 5-246-2-3
+always 릁 5-246-2-236
+always 릂 5-246-2-256
+always 릃 5-246-2-356
+always 름 5-246-26
+always 릅 5-246-12
+always 릆 5-246-12-3
+always 릇 5-246-3
+always 릈 5-246-3-3
+always 릉 5-246-2356
+always 릊 5-246-13
+always 릋 5-246-23
+always 릌 5-246-235
+always 릍 5-246-236
+always 릎 5-246-256
+always 릏 5-246-356
+always 릐 5-2456
+always 릑 5-2456-1
+always 릒 5-2456-1-1
+always 릓 5-2456-1-3
+always 릔 5-2456-25
+always 릕 5-2456-25-13
+always 릖 5-2456-25-356
+always 릗 5-2456-35
+always 릘 5-2456-2
+always 릙 5-2456-2-1
+always 릚 5-2456-2-26
+always 릛 5-2456-2-12
+always 릜 5-2456-2-3
+always 릝 5-2456-2-236
+always 릞 5-2456-2-256
+always 릟 5-2456-2-356
+always 릠 5-2456-26
+always 릡 5-2456-12
+always 릢 5-2456-12-3
+always 릣 5-2456-3
+always 릤 5-2456-3-3
+always 릥 5-2456-2356
+always 릦 5-2456-13
+always 릧 5-2456-23
+always 릨 5-2456-235
+always 릩 5-2456-236
+always 릪 5-2456-256
+always 릫 5-2456-356
+always 리 5-135
+always 릭 5-135-1
+always 릮 5-135-1-1
+always 릯 5-135-1-3
+always 린 5-135-25
+always 릱 5-135-25-13
+always 릲 5-135-25-356
+always 릳 5-135-35
+always 릴 5-135-2
+always 릵 5-135-2-1
+always 릶 5-135-2-26
+always 릷 5-135-2-12
+always 릸 5-135-2-3
+always 릹 5-135-2-236
+always 릺 5-135-2-256
+always 릻 5-135-2-356
+always 림 5-135-26
+always 립 5-135-12
+always 릾 5-135-12-3
+always 릿 5-135-3
+always 맀 5-135-3-3
+always 링 5-135-2356
+always 맂 5-135-13
+always 맃 5-135-23
+always 맄 5-135-235
+always 맅 5-135-236
+always 맆 5-135-256
+always 맇 5-135-356
+always 마 15-126
+always 막 15-126-1
+always 맊 15-126-1-1
+always 맋 15-126-1-3
+always 만 15-126-25
+always 맍 15-126-25-13
+always 많 15-126-25-356
+always 맏 15-126-35
+always 말 15-126-2
+always 맑 15-126-2-1
+always 맒 15-126-2-26
+always 맓 15-126-2-12
+always 맔 15-126-2-3
+always 맕 15-126-2-236
+always 맖 15-126-2-256
+always 맗 15-126-2-356
+always 맘 15-126-26
+always 맙 15-126-12
+always 맚 15-126-12-3
+always 맛 15-126-3
+always 맜 15-126-3-3
+always 망 15-126-2356
+always 맞 15-126-13
+always 맟 15-126-23
+always 맠 15-126-235
+always 맡 15-126-236
+always 맢 15-126-256
+always 맣 15-126-356
+always 매 15-1235
+always 맥 15-1235-1
+always 맦 15-1235-1-1
+always 맧 15-1235-1-3
+always 맨 15-1235-25
+always 맩 15-1235-25-13
+always 맪 15-1235-25-356
+always 맫 15-1235-35
+always 맬 15-1235-2
+always 맭 15-1235-2-1
+always 맮 15-1235-2-26
+always 맯 15-1235-2-12
+always 맰 15-1235-2-3
+always 맱 15-1235-2-236
+always 맲 15-1235-2-256
+always 맳 15-1235-2-356
+always 맴 15-1235-26
+always 맵 15-1235-12
+always 맶 15-1235-12-3
+always 맷 15-1235-3
+always 맸 15-1235-3-3
+always 맹 15-1235-2356
+always 맺 15-1235-13
+always 맻 15-1235-23
+always 맼 15-1235-235
+always 맽 15-1235-236
+always 맾 15-1235-256
+always 맿 15-1235-356
+always 먀 15-345
+always 먁 15-345-1
+always 먂 15-345-1-1
+always 먃 15-345-1-3
+always 먄 15-345-25
+always 먅 15-345-25-13
+always 먆 15-345-25-356
+always 먇 15-345-35
+always 먈 15-345-2
+always 먉 15-345-2-1
+always 먊 15-345-2-26
+always 먋 15-345-2-12
+always 먌 15-345-2-3
+always 먍 15-345-2-236
+always 먎 15-345-2-256
+always 먏 15-345-2-356
+always 먐 15-345-26
+always 먑 15-345-12
+always 먒 15-345-12-3
+always 먓 15-345-3
+always 먔 15-345-3-3
+always 먕 15-345-2356
+always 먖 15-345-13
+always 먗 15-345-23
+always 먘 15-345-235
+always 먙 15-345-236
+always 먚 15-345-256
+always 먛 15-345-356
+always 먜 15-345-1235
+always 먝 15-345-1235-1
+always 먞 15-345-1235-1-1
+always 먟 15-345-1235-1-3
+always 먠 15-345-1235-25
+always 먡 15-345-1235-25-13
+always 먢 15-345-1235-25-356
+always 먣 15-345-1235-35
+always 먤 15-345-1235-2
+always 먥 15-345-1235-2-1
+always 먦 15-345-1235-2-26
+always 먧 15-345-1235-2-12
+always 먨 15-345-1235-2-3
+always 먩 15-345-1235-2-236
+always 먪 15-345-1235-2-256
+always 먫 15-345-1235-2-356
+always 먬 15-345-1235-26
+always 먭 15-345-1235-12
+always 먮 15-345-1235-12-3
+always 먯 15-345-1235-3
+always 먰 15-345-1235-3-3
+always 먱 15-345-1235-2356
+always 먲 15-345-1235-13
+always 먳 15-345-1235-23
+always 먴 15-345-1235-235
+always 먵 15-345-1235-236
+always 먶 15-345-1235-256
+always 먷 15-345-1235-356
+always 머 15-234
+always 먹 15-234-1
+always 먺 15-234-1-1
+always 먻 15-234-1-3
+always 먼 15-234-25
+always 먽 15-234-25-13
+always 먾 15-234-25-356
+always 먿 15-234-35
+always 멀 15-234-2
+always 멁 15-234-2-1
+always 멂 15-234-2-26
+always 멃 15-234-2-12
+always 멄 15-234-2-3
+always 멅 15-234-2-236
+always 멆 15-234-2-256
+always 멇 15-234-2-356
+always 멈 15-234-26
+always 멉 15-234-12
+always 멊 15-234-12-3
+always 멋 15-234-3
+always 멌 15-234-3-3
+always 멍 15-234-2356
+always 멎 15-234-13
+always 멏 15-234-23
+always 멐 15-234-235
+always 멑 15-234-236
+always 멒 15-234-256
+always 멓 15-234-356
+always 메 15-1345
+always 멕 15-1345-1
+always 멖 15-1345-1-1
+always 멗 15-1345-1-3
+always 멘 15-1345-25
+always 멙 15-1345-25-13
+always 멚 15-1345-25-356
+always 멛 15-1345-35
+always 멜 15-1345-2
+always 멝 15-1345-2-1
+always 멞 15-1345-2-26
+always 멟 15-1345-2-12
+always 멠 15-1345-2-3
+always 멡 15-1345-2-236
+always 멢 15-1345-2-256
+always 멣 15-1345-2-356
+always 멤 15-1345-26
+always 멥 15-1345-12
+always 멦 15-1345-12-3
+always 멧 15-1345-3
+always 멨 15-1345-3-3
+always 멩 15-1345-2356
+always 멪 15-1345-13
+always 멫 15-1345-23
+always 멬 15-1345-235
+always 멭 15-1345-236
+always 멮 15-1345-256
+always 멯 15-1345-356
+always 며 15-156
+always 멱 15-156-1
+always 멲 15-156-1-1
+always 멳 15-156-1-3
+always 면 15-156-25
+always 멵 15-156-25-13
+always 멶 15-156-25-356
+always 멷 15-156-35
+always 멸 15-156-2
+always 멹 15-156-2-1
+always 멺 15-156-2-26
+always 멻 15-156-2-12
+always 멼 15-156-2-3
+always 멽 15-156-2-236
+always 멾 15-156-2-256
+always 멿 15-156-2-356
+always 몀 15-156-26
+always 몁 15-156-12
+always 몂 15-156-12-3
+always 몃 15-156-3
+always 몄 15-156-3-3
+always 명 15-156-2356
+always 몆 15-156-13
+always 몇 15-156-23
+always 몈 15-156-235
+always 몉 15-156-236
+always 몊 15-156-256
+always 몋 15-156-356
+always 몌 15-34
+always 몍 15-34-1
+always 몎 15-34-1-1
+always 몏 15-34-1-3
+always 몐 15-34-25
+always 몑 15-34-25-13
+always 몒 15-34-25-356
+always 몓 15-34-35
+always 몔 15-34-2
+always 몕 15-34-2-1
+always 몖 15-34-2-26
+always 몗 15-34-2-12
+always 몘 15-34-2-3
+always 몙 15-34-2-236
+always 몚 15-34-2-256
+always 몛 15-34-2-356
+always 몜 15-34-26
+always 몝 15-34-12
+always 몞 15-34-12-3
+always 몟 15-34-3
+always 몠 15-34-3-3
+always 몡 15-34-2356
+always 몢 15-34-13
+always 몣 15-34-23
+always 몤 15-34-235
+always 몥 15-34-236
+always 몦 15-34-256
+always 몧 15-34-356
+always 모 15-136
+always 목 15-136-1
+always 몪 15-136-1-1
+always 몫 15-136-1-3
+always 몬 15-136-25
+always 몭 15-136-25-13
+always 몮 15-136-25-356
+always 몯 15-136-35
+always 몰 15-136-2
+always 몱 15-136-2-1
+always 몲 15-136-2-26
+always 몳 15-136-2-12
+always 몴 15-136-2-3
+always 몵 15-136-2-236
+always 몶 15-136-2-256
+always 몷 15-136-2-356
+always 몸 15-136-26
+always 몹 15-136-12
+always 몺 15-136-12-3
+always 못 15-136-3
+always 몼 15-136-3-3
+always 몽 15-136-2356
+always 몾 15-136-13
+always 몿 15-136-23
+always 뫀 15-136-235
+always 뫁 15-136-236
+always 뫂 15-136-256
+always 뫃 15-136-356
+always 뫄 15-1236
+always 뫅 15-1236-1
+always 뫆 15-1236-1-1
+always 뫇 15-1236-1-3
+always 뫈 15-1236-25
+always 뫉 15-1236-25-13
+always 뫊 15-1236-25-356
+always 뫋 15-1236-35
+always 뫌 15-1236-2
+always 뫍 15-1236-2-1
+always 뫎 15-1236-2-26
+always 뫏 15-1236-2-12
+always 뫐 15-1236-2-3
+always 뫑 15-1236-2-236
+always 뫒 15-1236-2-256
+always 뫓 15-1236-2-356
+always 뫔 15-1236-26
+always 뫕 15-1236-12
+always 뫖 15-1236-12-3
+always 뫗 15-1236-3
+always 뫘 15-1236-3-3
+always 뫙 15-1236-2356
+always 뫚 15-1236-13
+always 뫛 15-1236-23
+always 뫜 15-1236-235
+always 뫝 15-1236-236
+always 뫞 15-1236-256
+always 뫟 15-1236-356
+always 뫠 15-1236-1235
+always 뫡 15-1236-1235-1
+always 뫢 15-1236-1235-1-1
+always 뫣 15-1236-1235-1-3
+always 뫤 15-1236-1235-25
+always 뫥 15-1236-1235-25-13
+always 뫦 15-1236-1235-25-356
+always 뫧 15-1236-1235-35
+always 뫨 15-1236-1235-2
+always 뫩 15-1236-1235-2-1
+always 뫪 15-1236-1235-2-26
+always 뫫 15-1236-1235-2-12
+always 뫬 15-1236-1235-2-3
+always 뫭 15-1236-1235-2-236
+always 뫮 15-1236-1235-2-256
+always 뫯 15-1236-1235-2-356
+always 뫰 15-1236-1235-26
+always 뫱 15-1236-1235-12
+always 뫲 15-1236-1235-12-3
+always 뫳 15-1236-1235-3
+always 뫴 15-1236-1235-3-3
+always 뫵 15-1236-1235-2356
+always 뫶 15-1236-1235-13
+always 뫷 15-1236-1235-23
+always 뫸 15-1236-1235-235
+always 뫹 15-1236-1235-236
+always 뫺 15-1236-1235-256
+always 뫻 15-1236-1235-356
+always 뫼 15-13456
+always 뫽 15-13456-1
+always 뫾 15-13456-1-1
+always 뫿 15-13456-1-3
+always 묀 15-13456-25
+always 묁 15-13456-25-13
+always 묂 15-13456-25-356
+always 묃 15-13456-35
+always 묄 15-13456-2
+always 묅 15-13456-2-1
+always 묆 15-13456-2-26
+always 묇 15-13456-2-12
+always 묈 15-13456-2-3
+always 묉 15-13456-2-236
+always 묊 15-13456-2-256
+always 묋 15-13456-2-356
+always 묌 15-13456-26
+always 묍 15-13456-12
+always 묎 15-13456-12-3
+always 묏 15-13456-3
+always 묐 15-13456-3-3
+always 묑 15-13456-2356
+always 묒 15-13456-13
+always 묓 15-13456-23
+always 묔 15-13456-235
+always 묕 15-13456-236
+always 묖 15-13456-256
+always 묗 15-13456-356
+always 묘 15-346
+always 묙 15-346-1
+always 묚 15-346-1-1
+always 묛 15-346-1-3
+always 묜 15-346-25
+always 묝 15-346-25-13
+always 묞 15-346-25-356
+always 묟 15-346-35
+always 묠 15-346-2
+always 묡 15-346-2-1
+always 묢 15-346-2-26
+always 묣 15-346-2-12
+always 묤 15-346-2-3
+always 묥 15-346-2-236
+always 묦 15-346-2-256
+always 묧 15-346-2-356
+always 묨 15-346-26
+always 묩 15-346-12
+always 묪 15-346-12-3
+always 묫 15-346-3
+always 묬 15-346-3-3
+always 묭 15-346-2356
+always 묮 15-346-13
+always 묯 15-346-23
+always 묰 15-346-235
+always 묱 15-346-236
+always 묲 15-346-256
+always 묳 15-346-356
+always 무 15-134
+always 묵 15-134-1
+always 묶 15-134-1-1
+always 묷 15-134-1-3
+always 문 15-134-25
+always 묹 15-134-25-13
+always 묺 15-134-25-356
+always 묻 15-134-35
+always 물 15-134-2
+always 묽 15-134-2-1
+always 묾 15-134-2-26
+always 묿 15-134-2-12
+always 뭀 15-134-2-3
+always 뭁 15-134-2-236
+always 뭂 15-134-2-256
+always 뭃 15-134-2-356
+always 뭄 15-134-26
+always 뭅 15-134-12
+always 뭆 15-134-12-3
+always 뭇 15-134-3
+always 뭈 15-134-3-3
+always 뭉 15-134-2356
+always 뭊 15-134-13
+always 뭋 15-134-23
+always 뭌 15-134-235
+always 뭍 15-134-236
+always 뭎 15-134-256
+always 뭏 15-134-356
+always 뭐 15-1234
+always 뭑 15-1234-1
+always 뭒 15-1234-1-1
+always 뭓 15-1234-1-3
+always 뭔 15-1234-25
+always 뭕 15-1234-25-13
+always 뭖 15-1234-25-356
+always 뭗 15-1234-35
+always 뭘 15-1234-2
+always 뭙 15-1234-2-1
+always 뭚 15-1234-2-26
+always 뭛 15-1234-2-12
+always 뭜 15-1234-2-3
+always 뭝 15-1234-2-236
+always 뭞 15-1234-2-256
+always 뭟 15-1234-2-356
+always 뭠 15-1234-26
+always 뭡 15-1234-12
+always 뭢 15-1234-12-3
+always 뭣 15-1234-3
+always 뭤 15-1234-3-3
+always 뭥 15-1234-2356
+always 뭦 15-1234-13
+always 뭧 15-1234-23
+always 뭨 15-1234-235
+always 뭩 15-1234-236
+always 뭪 15-1234-256
+always 뭫 15-1234-356
+always 뭬 15-1234-1235
+always 뭭 15-1234-1235-1
+always 뭮 15-1234-1235-1-1
+always 뭯 15-1234-1235-1-3
+always 뭰 15-1234-1235-25
+always 뭱 15-1234-1235-25-13
+always 뭲 15-1234-1235-25-356
+always 뭳 15-1234-1235-35
+always 뭴 15-1234-1235-2
+always 뭵 15-1234-1235-2-1
+always 뭶 15-1234-1235-2-26
+always 뭷 15-1234-1235-2-12
+always 뭸 15-1234-1235-2-3
+always 뭹 15-1234-1235-2-236
+always 뭺 15-1234-1235-2-256
+always 뭻 15-1234-1235-2-356
+always 뭼 15-1234-1235-26
+always 뭽 15-1234-1235-12
+always 뭾 15-1234-1235-12-3
+always 뭿 15-1234-1235-3
+always 뮀 15-1234-1235-3-3
+always 뮁 15-1234-1235-2356
+always 뮂 15-1234-1235-13
+always 뮃 15-1234-1235-23
+always 뮄 15-1234-1235-235
+always 뮅 15-1234-1235-236
+always 뮆 15-1234-1235-256
+always 뮇 15-1234-1235-356
+always 뮈 15-134-1235
+always 뮉 15-134-1235-1
+always 뮊 15-134-1235-1-1
+always 뮋 15-134-1235-1-3
+always 뮌 15-134-1235-25
+always 뮍 15-134-1235-25-13
+always 뮎 15-134-1235-25-356
+always 뮏 15-134-1235-35
+always 뮐 15-134-1235-2
+always 뮑 15-134-1235-2-1
+always 뮒 15-134-1235-2-26
+always 뮓 15-134-1235-2-12
+always 뮔 15-134-1235-2-3
+always 뮕 15-134-1235-2-236
+always 뮖 15-134-1235-2-256
+always 뮗 15-134-1235-2-356
+always 뮘 15-134-1235-26
+always 뮙 15-134-1235-12
+always 뮚 15-134-1235-12-3
+always 뮛 15-134-1235-3
+always 뮜 15-134-1235-3-3
+always 뮝 15-134-1235-2356
+always 뮞 15-134-1235-13
+always 뮟 15-134-1235-23
+always 뮠 15-134-1235-235
+always 뮡 15-134-1235-236
+always 뮢 15-134-1235-256
+always 뮣 15-134-1235-356
+always 뮤 15-146
+always 뮥 15-146-1
+always 뮦 15-146-1-1
+always 뮧 15-146-1-3
+always 뮨 15-146-25
+always 뮩 15-146-25-13
+always 뮪 15-146-25-356
+always 뮫 15-146-35
+always 뮬 15-146-2
+always 뮭 15-146-2-1
+always 뮮 15-146-2-26
+always 뮯 15-146-2-12
+always 뮰 15-146-2-3
+always 뮱 15-146-2-236
+always 뮲 15-146-2-256
+always 뮳 15-146-2-356
+always 뮴 15-146-26
+always 뮵 15-146-12
+always 뮶 15-146-12-3
+always 뮷 15-146-3
+always 뮸 15-146-3-3
+always 뮹 15-146-2356
+always 뮺 15-146-13
+always 뮻 15-146-23
+always 뮼 15-146-235
+always 뮽 15-146-236
+always 뮾 15-146-256
+always 뮿 15-146-356
+always 므 15-246
+always 믁 15-246-1
+always 믂 15-246-1-1
+always 믃 15-246-1-3
+always 믄 15-246-25
+always 믅 15-246-25-13
+always 믆 15-246-25-356
+always 믇 15-246-35
+always 믈 15-246-2
+always 믉 15-246-2-1
+always 믊 15-246-2-26
+always 믋 15-246-2-12
+always 믌 15-246-2-3
+always 믍 15-246-2-236
+always 믎 15-246-2-256
+always 믏 15-246-2-356
+always 믐 15-246-26
+always 믑 15-246-12
+always 믒 15-246-12-3
+always 믓 15-246-3
+always 믔 15-246-3-3
+always 믕 15-246-2356
+always 믖 15-246-13
+always 믗 15-246-23
+always 믘 15-246-235
+always 믙 15-246-236
+always 믚 15-246-256
+always 믛 15-246-356
+always 믜 15-2456
+always 믝 15-2456-1
+always 믞 15-2456-1-1
+always 믟 15-2456-1-3
+always 믠 15-2456-25
+always 믡 15-2456-25-13
+always 믢 15-2456-25-356
+always 믣 15-2456-35
+always 믤 15-2456-2
+always 믥 15-2456-2-1
+always 믦 15-2456-2-26
+always 믧 15-2456-2-12
+always 믨 15-2456-2-3
+always 믩 15-2456-2-236
+always 믪 15-2456-2-256
+always 믫 15-2456-2-356
+always 믬 15-2456-26
+always 믭 15-2456-12
+always 믮 15-2456-12-3
+always 믯 15-2456-3
+always 믰 15-2456-3-3
+always 믱 15-2456-2356
+always 믲 15-2456-13
+always 믳 15-2456-23
+always 믴 15-2456-235
+always 믵 15-2456-236
+always 믶 15-2456-256
+always 믷 15-2456-356
+always 미 15-135
+always 믹 15-135-1
+always 믺 15-135-1-1
+always 믻 15-135-1-3
+always 민 15-135-25
+always 믽 15-135-25-13
+always 믾 15-135-25-356
+always 믿 15-135-35
+always 밀 15-135-2
+always 밁 15-135-2-1
+always 밂 15-135-2-26
+always 밃 15-135-2-12
+always 밄 15-135-2-3
+always 밅 15-135-2-236
+always 밆 15-135-2-256
+always 밇 15-135-2-356
+always 밈 15-135-26
+always 밉 15-135-12
+always 밊 15-135-12-3
+always 밋 15-135-3
+always 밌 15-135-3-3
+always 밍 15-135-2356
+always 밎 15-135-13
+always 및 15-135-23
+always 밐 15-135-235
+always 밑 15-135-236
+always 밒 15-135-256
+always 밓 15-135-356
+always 바 45-126
+always 박 45-126-1
+always 밖 45-126-1-1
+always 밗 45-126-1-3
+always 반 45-126-25
+always 밙 45-126-25-13
+always 밚 45-126-25-356
+always 받 45-126-35
+always 발 45-126-2
+always 밝 45-126-2-1
+always 밞 45-126-2-26
+always 밟 45-126-2-12
+always 밠 45-126-2-3
+always 밡 45-126-2-236
+always 밢 45-126-2-256
+always 밣 45-126-2-356
+always 밤 45-126-26
+always 밥 45-126-12
+always 밦 45-126-12-3
+always 밧 45-126-3
+always 밨 45-126-3-3
+always 방 45-126-2356
+always 밪 45-126-13
+always 밫 45-126-23
+always 밬 45-126-235
+always 밭 45-126-236
+always 밮 45-126-256
+always 밯 45-126-356
+always 배 45-1235
+always 백 45-1235-1
+always 밲 45-1235-1-1
+always 밳 45-1235-1-3
+always 밴 45-1235-25
+always 밵 45-1235-25-13
+always 밶 45-1235-25-356
+always 밷 45-1235-35
+always 밸 45-1235-2
+always 밹 45-1235-2-1
+always 밺 45-1235-2-26
+always 밻 45-1235-2-12
+always 밼 45-1235-2-3
+always 밽 45-1235-2-236
+always 밾 45-1235-2-256
+always 밿 45-1235-2-356
+always 뱀 45-1235-26
+always 뱁 45-1235-12
+always 뱂 45-1235-12-3
+always 뱃 45-1235-3
+always 뱄 45-1235-3-3
+always 뱅 45-1235-2356
+always 뱆 45-1235-13
+always 뱇 45-1235-23
+always 뱈 45-1235-235
+always 뱉 45-1235-236
+always 뱊 45-1235-256
+always 뱋 45-1235-356
+always 뱌 45-345
+always 뱍 45-345-1
+always 뱎 45-345-1-1
+always 뱏 45-345-1-3
+always 뱐 45-345-25
+always 뱑 45-345-25-13
+always 뱒 45-345-25-356
+always 뱓 45-345-35
+always 뱔 45-345-2
+always 뱕 45-345-2-1
+always 뱖 45-345-2-26
+always 뱗 45-345-2-12
+always 뱘 45-345-2-3
+always 뱙 45-345-2-236
+always 뱚 45-345-2-256
+always 뱛 45-345-2-356
+always 뱜 45-345-26
+always 뱝 45-345-12
+always 뱞 45-345-12-3
+always 뱟 45-345-3
+always 뱠 45-345-3-3
+always 뱡 45-345-2356
+always 뱢 45-345-13
+always 뱣 45-345-23
+always 뱤 45-345-235
+always 뱥 45-345-236
+always 뱦 45-345-256
+always 뱧 45-345-356
+always 뱨 45-345-1235
+always 뱩 45-345-1235-1
+always 뱪 45-345-1235-1-1
+always 뱫 45-345-1235-1-3
+always 뱬 45-345-1235-25
+always 뱭 45-345-1235-25-13
+always 뱮 45-345-1235-25-356
+always 뱯 45-345-1235-35
+always 뱰 45-345-1235-2
+always 뱱 45-345-1235-2-1
+always 뱲 45-345-1235-2-26
+always 뱳 45-345-1235-2-12
+always 뱴 45-345-1235-2-3
+always 뱵 45-345-1235-2-236
+always 뱶 45-345-1235-2-256
+always 뱷 45-345-1235-2-356
+always 뱸 45-345-1235-26
+always 뱹 45-345-1235-12
+always 뱺 45-345-1235-12-3
+always 뱻 45-345-1235-3
+always 뱼 45-345-1235-3-3
+always 뱽 45-345-1235-2356
+always 뱾 45-345-1235-13
+always 뱿 45-345-1235-23
+always 벀 45-345-1235-235
+always 벁 45-345-1235-236
+always 벂 45-345-1235-256
+always 벃 45-345-1235-356
+always 버 45-234
+always 벅 45-234-1
+always 벆 45-234-1-1
+always 벇 45-234-1-3
+always 번 45-234-25
+always 벉 45-234-25-13
+always 벊 45-234-25-356
+always 벋 45-234-35
+always 벌 45-234-2
+always 벍 45-234-2-1
+always 벎 45-234-2-26
+always 벏 45-234-2-12
+always 벐 45-234-2-3
+always 벑 45-234-2-236
+always 벒 45-234-2-256
+always 벓 45-234-2-356
+always 범 45-234-26
+always 법 45-234-12
+always 벖 45-234-12-3
+always 벗 45-234-3
+always 벘 45-234-3-3
+always 벙 45-234-2356
+always 벚 45-234-13
+always 벛 45-234-23
+always 벜 45-234-235
+always 벝 45-234-236
+always 벞 45-234-256
+always 벟 45-234-356
+always 베 45-1345
+always 벡 45-1345-1
+always 벢 45-1345-1-1
+always 벣 45-1345-1-3
+always 벤 45-1345-25
+always 벥 45-1345-25-13
+always 벦 45-1345-25-356
+always 벧 45-1345-35
+always 벨 45-1345-2
+always 벩 45-1345-2-1
+always 벪 45-1345-2-26
+always 벫 45-1345-2-12
+always 벬 45-1345-2-3
+always 벭 45-1345-2-236
+always 벮 45-1345-2-256
+always 벯 45-1345-2-356
+always 벰 45-1345-26
+always 벱 45-1345-12
+always 벲 45-1345-12-3
+always 벳 45-1345-3
+always 벴 45-1345-3-3
+always 벵 45-1345-2356
+always 벶 45-1345-13
+always 벷 45-1345-23
+always 벸 45-1345-235
+always 벹 45-1345-236
+always 벺 45-1345-256
+always 벻 45-1345-356
+always 벼 45-156
+always 벽 45-156-1
+always 벾 45-156-1-1
+always 벿 45-156-1-3
+always 변 45-156-25
+always 볁 45-156-25-13
+always 볂 45-156-25-356
+always 볃 45-156-35
+always 별 45-156-2
+always 볅 45-156-2-1
+always 볆 45-156-2-26
+always 볇 45-156-2-12
+always 볈 45-156-2-3
+always 볉 45-156-2-236
+always 볊 45-156-2-256
+always 볋 45-156-2-356
+always 볌 45-156-26
+always 볍 45-156-12
+always 볎 45-156-12-3
+always 볏 45-156-3
+always 볐 45-156-3-3
+always 병 45-156-2356
+always 볒 45-156-13
+always 볓 45-156-23
+always 볔 45-156-235
+always 볕 45-156-236
+always 볖 45-156-256
+always 볗 45-156-356
+always 볘 45-34
+always 볙 45-34-1
+always 볚 45-34-1-1
+always 볛 45-34-1-3
+always 볜 45-34-25
+always 볝 45-34-25-13
+always 볞 45-34-25-356
+always 볟 45-34-35
+always 볠 45-34-2
+always 볡 45-34-2-1
+always 볢 45-34-2-26
+always 볣 45-34-2-12
+always 볤 45-34-2-3
+always 볥 45-34-2-236
+always 볦 45-34-2-256
+always 볧 45-34-2-356
+always 볨 45-34-26
+always 볩 45-34-12
+always 볪 45-34-12-3
+always 볫 45-34-3
+always 볬 45-34-3-3
+always 볭 45-34-2356
+always 볮 45-34-13
+always 볯 45-34-23
+always 볰 45-34-235
+always 볱 45-34-236
+always 볲 45-34-256
+always 볳 45-34-356
+always 보 45-136
+always 복 45-136-1
+always 볶 45-136-1-1
+always 볷 45-136-1-3
+always 본 45-136-25
+always 볹 45-136-25-13
+always 볺 45-136-25-356
+always 볻 45-136-35
+always 볼 45-136-2
+always 볽 45-136-2-1
+always 볾 45-136-2-26
+always 볿 45-136-2-12
+always 봀 45-136-2-3
+always 봁 45-136-2-236
+always 봂 45-136-2-256
+always 봃 45-136-2-356
+always 봄 45-136-26
+always 봅 45-136-12
+always 봆 45-136-12-3
+always 봇 45-136-3
+always 봈 45-136-3-3
+always 봉 45-136-2356
+always 봊 45-136-13
+always 봋 45-136-23
+always 봌 45-136-235
+always 봍 45-136-236
+always 봎 45-136-256
+always 봏 45-136-356
+always 봐 45-1236
+always 봑 45-1236-1
+always 봒 45-1236-1-1
+always 봓 45-1236-1-3
+always 봔 45-1236-25
+always 봕 45-1236-25-13
+always 봖 45-1236-25-356
+always 봗 45-1236-35
+always 봘 45-1236-2
+always 봙 45-1236-2-1
+always 봚 45-1236-2-26
+always 봛 45-1236-2-12
+always 봜 45-1236-2-3
+always 봝 45-1236-2-236
+always 봞 45-1236-2-256
+always 봟 45-1236-2-356
+always 봠 45-1236-26
+always 봡 45-1236-12
+always 봢 45-1236-12-3
+always 봣 45-1236-3
+always 봤 45-1236-3-3
+always 봥 45-1236-2356
+always 봦 45-1236-13
+always 봧 45-1236-23
+always 봨 45-1236-235
+always 봩 45-1236-236
+always 봪 45-1236-256
+always 봫 45-1236-356
+always 봬 45-1236-1235
+always 봭 45-1236-1235-1
+always 봮 45-1236-1235-1-1
+always 봯 45-1236-1235-1-3
+always 봰 45-1236-1235-25
+always 봱 45-1236-1235-25-13
+always 봲 45-1236-1235-25-356
+always 봳 45-1236-1235-35
+always 봴 45-1236-1235-2
+always 봵 45-1236-1235-2-1
+always 봶 45-1236-1235-2-26
+always 봷 45-1236-1235-2-12
+always 봸 45-1236-1235-2-3
+always 봹 45-1236-1235-2-236
+always 봺 45-1236-1235-2-256
+always 봻 45-1236-1235-2-356
+always 봼 45-1236-1235-26
+always 봽 45-1236-1235-12
+always 봾 45-1236-1235-12-3
+always 봿 45-1236-1235-3
+always 뵀 45-1236-1235-3-3
+always 뵁 45-1236-1235-2356
+always 뵂 45-1236-1235-13
+always 뵃 45-1236-1235-23
+always 뵄 45-1236-1235-235
+always 뵅 45-1236-1235-236
+always 뵆 45-1236-1235-256
+always 뵇 45-1236-1235-356
+always 뵈 45-13456
+always 뵉 45-13456-1
+always 뵊 45-13456-1-1
+always 뵋 45-13456-1-3
+always 뵌 45-13456-25
+always 뵍 45-13456-25-13
+always 뵎 45-13456-25-356
+always 뵏 45-13456-35
+always 뵐 45-13456-2
+always 뵑 45-13456-2-1
+always 뵒 45-13456-2-26
+always 뵓 45-13456-2-12
+always 뵔 45-13456-2-3
+always 뵕 45-13456-2-236
+always 뵖 45-13456-2-256
+always 뵗 45-13456-2-356
+always 뵘 45-13456-26
+always 뵙 45-13456-12
+always 뵚 45-13456-12-3
+always 뵛 45-13456-3
+always 뵜 45-13456-3-3
+always 뵝 45-13456-2356
+always 뵞 45-13456-13
+always 뵟 45-13456-23
+always 뵠 45-13456-235
+always 뵡 45-13456-236
+always 뵢 45-13456-256
+always 뵣 45-13456-356
+always 뵤 45-346
+always 뵥 45-346-1
+always 뵦 45-346-1-1
+always 뵧 45-346-1-3
+always 뵨 45-346-25
+always 뵩 45-346-25-13
+always 뵪 45-346-25-356
+always 뵫 45-346-35
+always 뵬 45-346-2
+always 뵭 45-346-2-1
+always 뵮 45-346-2-26
+always 뵯 45-346-2-12
+always 뵰 45-346-2-3
+always 뵱 45-346-2-236
+always 뵲 45-346-2-256
+always 뵳 45-346-2-356
+always 뵴 45-346-26
+always 뵵 45-346-12
+always 뵶 45-346-12-3
+always 뵷 45-346-3
+always 뵸 45-346-3-3
+always 뵹 45-346-2356
+always 뵺 45-346-13
+always 뵻 45-346-23
+always 뵼 45-346-235
+always 뵽 45-346-236
+always 뵾 45-346-256
+always 뵿 45-346-356
+always 부 45-134
+always 북 45-134-1
+always 붂 45-134-1-1
+always 붃 45-134-1-3
+always 분 45-134-25
+always 붅 45-134-25-13
+always 붆 45-134-25-356
+always 붇 45-134-35
+always 불 45-134-2
+always 붉 45-134-2-1
+always 붊 45-134-2-26
+always 붋 45-134-2-12
+always 붌 45-134-2-3
+always 붍 45-134-2-236
+always 붎 45-134-2-256
+always 붏 45-134-2-356
+always 붐 45-134-26
+always 붑 45-134-12
+always 붒 45-134-12-3
+always 붓 45-134-3
+always 붔 45-134-3-3
+always 붕 45-134-2356
+always 붖 45-134-13
+always 붗 45-134-23
+always 붘 45-134-235
+always 붙 45-134-236
+always 붚 45-134-256
+always 붛 45-134-356
+always 붜 45-1234
+always 붝 45-1234-1
+always 붞 45-1234-1-1
+always 붟 45-1234-1-3
+always 붠 45-1234-25
+always 붡 45-1234-25-13
+always 붢 45-1234-25-356
+always 붣 45-1234-35
+always 붤 45-1234-2
+always 붥 45-1234-2-1
+always 붦 45-1234-2-26
+always 붧 45-1234-2-12
+always 붨 45-1234-2-3
+always 붩 45-1234-2-236
+always 붪 45-1234-2-256
+always 붫 45-1234-2-356
+always 붬 45-1234-26
+always 붭 45-1234-12
+always 붮 45-1234-12-3
+always 붯 45-1234-3
+always 붰 45-1234-3-3
+always 붱 45-1234-2356
+always 붲 45-1234-13
+always 붳 45-1234-23
+always 붴 45-1234-235
+always 붵 45-1234-236
+always 붶 45-1234-256
+always 붷 45-1234-356
+always 붸 45-1234-1235
+always 붹 45-1234-1235-1
+always 붺 45-1234-1235-1-1
+always 붻 45-1234-1235-1-3
+always 붼 45-1234-1235-25
+always 붽 45-1234-1235-25-13
+always 붾 45-1234-1235-25-356
+always 붿 45-1234-1235-35
+always 뷀 45-1234-1235-2
+always 뷁 45-1234-1235-2-1
+always 뷂 45-1234-1235-2-26
+always 뷃 45-1234-1235-2-12
+always 뷄 45-1234-1235-2-3
+always 뷅 45-1234-1235-2-236
+always 뷆 45-1234-1235-2-256
+always 뷇 45-1234-1235-2-356
+always 뷈 45-1234-1235-26
+always 뷉 45-1234-1235-12
+always 뷊 45-1234-1235-12-3
+always 뷋 45-1234-1235-3
+always 뷌 45-1234-1235-3-3
+always 뷍 45-1234-1235-2356
+always 뷎 45-1234-1235-13
+always 뷏 45-1234-1235-23
+always 뷐 45-1234-1235-235
+always 뷑 45-1234-1235-236
+always 뷒 45-1234-1235-256
+always 뷓 45-1234-1235-356
+always 뷔 45-134-1235
+always 뷕 45-134-1235-1
+always 뷖 45-134-1235-1-1
+always 뷗 45-134-1235-1-3
+always 뷘 45-134-1235-25
+always 뷙 45-134-1235-25-13
+always 뷚 45-134-1235-25-356
+always 뷛 45-134-1235-35
+always 뷜 45-134-1235-2
+always 뷝 45-134-1235-2-1
+always 뷞 45-134-1235-2-26
+always 뷟 45-134-1235-2-12
+always 뷠 45-134-1235-2-3
+always 뷡 45-134-1235-2-236
+always 뷢 45-134-1235-2-256
+always 뷣 45-134-1235-2-356
+always 뷤 45-134-1235-26
+always 뷥 45-134-1235-12
+always 뷦 45-134-1235-12-3
+always 뷧 45-134-1235-3
+always 뷨 45-134-1235-3-3
+always 뷩 45-134-1235-2356
+always 뷪 45-134-1235-13
+always 뷫 45-134-1235-23
+always 뷬 45-134-1235-235
+always 뷭 45-134-1235-236
+always 뷮 45-134-1235-256
+always 뷯 45-134-1235-356
+always 뷰 45-146
+always 뷱 45-146-1
+always 뷲 45-146-1-1
+always 뷳 45-146-1-3
+always 뷴 45-146-25
+always 뷵 45-146-25-13
+always 뷶 45-146-25-356
+always 뷷 45-146-35
+always 뷸 45-146-2
+always 뷹 45-146-2-1
+always 뷺 45-146-2-26
+always 뷻 45-146-2-12
+always 뷼 45-146-2-3
+always 뷽 45-146-2-236
+always 뷾 45-146-2-256
+always 뷿 45-146-2-356
+always 븀 45-146-26
+always 븁 45-146-12
+always 븂 45-146-12-3
+always 븃 45-146-3
+always 븄 45-146-3-3
+always 븅 45-146-2356
+always 븆 45-146-13
+always 븇 45-146-23
+always 븈 45-146-235
+always 븉 45-146-236
+always 븊 45-146-256
+always 븋 45-146-356
+always 브 45-246
+always 븍 45-246-1
+always 븎 45-246-1-1
+always 븏 45-246-1-3
+always 븐 45-246-25
+always 븑 45-246-25-13
+always 븒 45-246-25-356
+always 븓 45-246-35
+always 블 45-246-2
+always 븕 45-246-2-1
+always 븖 45-246-2-26
+always 븗 45-246-2-12
+always 븘 45-246-2-3
+always 븙 45-246-2-236
+always 븚 45-246-2-256
+always 븛 45-246-2-356
+always 븜 45-246-26
+always 븝 45-246-12
+always 븞 45-246-12-3
+always 븟 45-246-3
+always 븠 45-246-3-3
+always 븡 45-246-2356
+always 븢 45-246-13
+always 븣 45-246-23
+always 븤 45-246-235
+always 븥 45-246-236
+always 븦 45-246-256
+always 븧 45-246-356
+always 븨 45-2456
+always 븩 45-2456-1
+always 븪 45-2456-1-1
+always 븫 45-2456-1-3
+always 븬 45-2456-25
+always 븭 45-2456-25-13
+always 븮 45-2456-25-356
+always 븯 45-2456-35
+always 븰 45-2456-2
+always 븱 45-2456-2-1
+always 븲 45-2456-2-26
+always 븳 45-2456-2-12
+always 븴 45-2456-2-3
+always 븵 45-2456-2-236
+always 븶 45-2456-2-256
+always 븷 45-2456-2-356
+always 븸 45-2456-26
+always 븹 45-2456-12
+always 븺 45-2456-12-3
+always 븻 45-2456-3
+always 븼 45-2456-3-3
+always 븽 45-2456-2356
+always 븾 45-2456-13
+always 븿 45-2456-23
+always 빀 45-2456-235
+always 빁 45-2456-236
+always 빂 45-2456-256
+always 빃 45-2456-356
+always 비 45-135
+always 빅 45-135-1
+always 빆 45-135-1-1
+always 빇 45-135-1-3
+always 빈 45-135-25
+always 빉 45-135-25-13
+always 빊 45-135-25-356
+always 빋 45-135-35
+always 빌 45-135-2
+always 빍 45-135-2-1
+always 빎 45-135-2-26
+always 빏 45-135-2-12
+always 빐 45-135-2-3
+always 빑 45-135-2-236
+always 빒 45-135-2-256
+always 빓 45-135-2-356
+always 빔 45-135-26
+always 빕 45-135-12
+always 빖 45-135-12-3
+always 빗 45-135-3
+always 빘 45-135-3-3
+always 빙 45-135-2356
+always 빚 45-135-13
+always 빛 45-135-23
+always 빜 45-135-235
+always 빝 45-135-236
+always 빞 45-135-256
+always 빟 45-135-356
+always 빠 45-45-126
+always 빡 45-45-126-1
+always 빢 45-45-126-1-1
+always 빣 45-45-126-1-3
+always 빤 45-45-126-25
+always 빥 45-45-126-25-13
+always 빦 45-45-126-25-356
+always 빧 45-45-126-35
+always 빨 45-45-126-2
+always 빩 45-45-126-2-1
+always 빪 45-45-126-2-26
+always 빫 45-45-126-2-12
+always 빬 45-45-126-2-3
+always 빭 45-45-126-2-236
+always 빮 45-45-126-2-256
+always 빯 45-45-126-2-356
+always 빰 45-45-126-26
+always 빱 45-45-126-12
+always 빲 45-45-126-12-3
+always 빳 45-45-126-3
+always 빴 45-45-126-3-3
+always 빵 45-45-126-2356
+always 빶 45-45-126-13
+always 빷 45-45-126-23
+always 빸 45-45-126-235
+always 빹 45-45-126-236
+always 빺 45-45-126-256
+always 빻 45-45-126-356
+always 빼 45-45-1235
+always 빽 45-45-1235-1
+always 빾 45-45-1235-1-1
+always 빿 45-45-1235-1-3
+always 뺀 45-45-1235-25
+always 뺁 45-45-1235-25-13
+always 뺂 45-45-1235-25-356
+always 뺃 45-45-1235-35
+always 뺄 45-45-1235-2
+always 뺅 45-45-1235-2-1
+always 뺆 45-45-1235-2-26
+always 뺇 45-45-1235-2-12
+always 뺈 45-45-1235-2-3
+always 뺉 45-45-1235-2-236
+always 뺊 45-45-1235-2-256
+always 뺋 45-45-1235-2-356
+always 뺌 45-45-1235-26
+always 뺍 45-45-1235-12
+always 뺎 45-45-1235-12-3
+always 뺏 45-45-1235-3
+always 뺐 45-45-1235-3-3
+always 뺑 45-45-1235-2356
+always 뺒 45-45-1235-13
+always 뺓 45-45-1235-23
+always 뺔 45-45-1235-235
+always 뺕 45-45-1235-236
+always 뺖 45-45-1235-256
+always 뺗 45-45-1235-356
+always 뺘 45-45-345
+always 뺙 45-45-345-1
+always 뺚 45-45-345-1-1
+always 뺛 45-45-345-1-3
+always 뺜 45-45-345-25
+always 뺝 45-45-345-25-13
+always 뺞 45-45-345-25-356
+always 뺟 45-45-345-35
+always 뺠 45-45-345-2
+always 뺡 45-45-345-2-1
+always 뺢 45-45-345-2-26
+always 뺣 45-45-345-2-12
+always 뺤 45-45-345-2-3
+always 뺥 45-45-345-2-236
+always 뺦 45-45-345-2-256
+always 뺧 45-45-345-2-356
+always 뺨 45-45-345-26
+always 뺩 45-45-345-12
+always 뺪 45-45-345-12-3
+always 뺫 45-45-345-3
+always 뺬 45-45-345-3-3
+always 뺭 45-45-345-2356
+always 뺮 45-45-345-13
+always 뺯 45-45-345-23
+always 뺰 45-45-345-235
+always 뺱 45-45-345-236
+always 뺲 45-45-345-256
+always 뺳 45-45-345-356
+always 뺴 45-45-345-1235
+always 뺵 45-45-345-1235-1
+always 뺶 45-45-345-1235-1-1
+always 뺷 45-45-345-1235-1-3
+always 뺸 45-45-345-1235-25
+always 뺹 45-45-345-1235-25-13
+always 뺺 45-45-345-1235-25-356
+always 뺻 45-45-345-1235-35
+always 뺼 45-45-345-1235-2
+always 뺽 45-45-345-1235-2-1
+always 뺾 45-45-345-1235-2-26
+always 뺿 45-45-345-1235-2-12
+always 뻀 45-45-345-1235-2-3
+always 뻁 45-45-345-1235-2-236
+always 뻂 45-45-345-1235-2-256
+always 뻃 45-45-345-1235-2-356
+always 뻄 45-45-345-1235-26
+always 뻅 45-45-345-1235-12
+always 뻆 45-45-345-1235-12-3
+always 뻇 45-45-345-1235-3
+always 뻈 45-45-345-1235-3-3
+always 뻉 45-45-345-1235-2356
+always 뻊 45-45-345-1235-13
+always 뻋 45-45-345-1235-23
+always 뻌 45-45-345-1235-235
+always 뻍 45-45-345-1235-236
+always 뻎 45-45-345-1235-256
+always 뻏 45-45-345-1235-356
+always 뻐 45-45-234
+always 뻑 45-45-234-1
+always 뻒 45-45-234-1-1
+always 뻓 45-45-234-1-3
+always 뻔 45-45-234-25
+always 뻕 45-45-234-25-13
+always 뻖 45-45-234-25-356
+always 뻗 45-45-234-35
+always 뻘 45-45-234-2
+always 뻙 45-45-234-2-1
+always 뻚 45-45-234-2-26
+always 뻛 45-45-234-2-12
+always 뻜 45-45-234-2-3
+always 뻝 45-45-234-2-236
+always 뻞 45-45-234-2-256
+always 뻟 45-45-234-2-356
+always 뻠 45-45-234-26
+always 뻡 45-45-234-12
+always 뻢 45-45-234-12-3
+always 뻣 45-45-234-3
+always 뻤 45-45-234-3-3
+always 뻥 45-45-234-2356
+always 뻦 45-45-234-13
+always 뻧 45-45-234-23
+always 뻨 45-45-234-235
+always 뻩 45-45-234-236
+always 뻪 45-45-234-256
+always 뻫 45-45-234-356
+always 뻬 45-45-1345
+always 뻭 45-45-1345-1
+always 뻮 45-45-1345-1-1
+always 뻯 45-45-1345-1-3
+always 뻰 45-45-1345-25
+always 뻱 45-45-1345-25-13
+always 뻲 45-45-1345-25-356
+always 뻳 45-45-1345-35
+always 뻴 45-45-1345-2
+always 뻵 45-45-1345-2-1
+always 뻶 45-45-1345-2-26
+always 뻷 45-45-1345-2-12
+always 뻸 45-45-1345-2-3
+always 뻹 45-45-1345-2-236
+always 뻺 45-45-1345-2-256
+always 뻻 45-45-1345-2-356
+always 뻼 45-45-1345-26
+always 뻽 45-45-1345-12
+always 뻾 45-45-1345-12-3
+always 뻿 45-45-1345-3
+always 뼀 45-45-1345-3-3
+always 뼁 45-45-1345-2356
+always 뼂 45-45-1345-13
+always 뼃 45-45-1345-23
+always 뼄 45-45-1345-235
+always 뼅 45-45-1345-236
+always 뼆 45-45-1345-256
+always 뼇 45-45-1345-356
+always 뼈 45-45-156
+always 뼉 45-45-156-1
+always 뼊 45-45-156-1-1
+always 뼋 45-45-156-1-3
+always 뼌 45-45-156-25
+always 뼍 45-45-156-25-13
+always 뼎 45-45-156-25-356
+always 뼏 45-45-156-35
+always 뼐 45-45-156-2
+always 뼑 45-45-156-2-1
+always 뼒 45-45-156-2-26
+always 뼓 45-45-156-2-12
+always 뼔 45-45-156-2-3
+always 뼕 45-45-156-2-236
+always 뼖 45-45-156-2-256
+always 뼗 45-45-156-2-356
+always 뼘 45-45-156-26
+always 뼙 45-45-156-12
+always 뼚 45-45-156-12-3
+always 뼛 45-45-156-3
+always 뼜 45-45-156-3-3
+always 뼝 45-45-156-2356
+always 뼞 45-45-156-13
+always 뼟 45-45-156-23
+always 뼠 45-45-156-235
+always 뼡 45-45-156-236
+always 뼢 45-45-156-256
+always 뼣 45-45-156-356
+always 뼤 45-45-34
+always 뼥 45-45-34-1
+always 뼦 45-45-34-1-1
+always 뼧 45-45-34-1-3
+always 뼨 45-45-34-25
+always 뼩 45-45-34-25-13
+always 뼪 45-45-34-25-356
+always 뼫 45-45-34-35
+always 뼬 45-45-34-2
+always 뼭 45-45-34-2-1
+always 뼮 45-45-34-2-26
+always 뼯 45-45-34-2-12
+always 뼰 45-45-34-2-3
+always 뼱 45-45-34-2-236
+always 뼲 45-45-34-2-256
+always 뼳 45-45-34-2-356
+always 뼴 45-45-34-26
+always 뼵 45-45-34-12
+always 뼶 45-45-34-12-3
+always 뼷 45-45-34-3
+always 뼸 45-45-34-3-3
+always 뼹 45-45-34-2356
+always 뼺 45-45-34-13
+always 뼻 45-45-34-23
+always 뼼 45-45-34-235
+always 뼽 45-45-34-236
+always 뼾 45-45-34-256
+always 뼿 45-45-34-356
+always 뽀 45-45-136
+always 뽁 45-45-136-1
+always 뽂 45-45-136-1-1
+always 뽃 45-45-136-1-3
+always 뽄 45-45-136-25
+always 뽅 45-45-136-25-13
+always 뽆 45-45-136-25-356
+always 뽇 45-45-136-35
+always 뽈 45-45-136-2
+always 뽉 45-45-136-2-1
+always 뽊 45-45-136-2-26
+always 뽋 45-45-136-2-12
+always 뽌 45-45-136-2-3
+always 뽍 45-45-136-2-236
+always 뽎 45-45-136-2-256
+always 뽏 45-45-136-2-356
+always 뽐 45-45-136-26
+always 뽑 45-45-136-12
+always 뽒 45-45-136-12-3
+always 뽓 45-45-136-3
+always 뽔 45-45-136-3-3
+always 뽕 45-45-136-2356
+always 뽖 45-45-136-13
+always 뽗 45-45-136-23
+always 뽘 45-45-136-235
+always 뽙 45-45-136-236
+always 뽚 45-45-136-256
+always 뽛 45-45-136-356
+always 뽜 45-45-1236
+always 뽝 45-45-1236-1
+always 뽞 45-45-1236-1-1
+always 뽟 45-45-1236-1-3
+always 뽠 45-45-1236-25
+always 뽡 45-45-1236-25-13
+always 뽢 45-45-1236-25-356
+always 뽣 45-45-1236-35
+always 뽤 45-45-1236-2
+always 뽥 45-45-1236-2-1
+always 뽦 45-45-1236-2-26
+always 뽧 45-45-1236-2-12
+always 뽨 45-45-1236-2-3
+always 뽩 45-45-1236-2-236
+always 뽪 45-45-1236-2-256
+always 뽫 45-45-1236-2-356
+always 뽬 45-45-1236-26
+always 뽭 45-45-1236-12
+always 뽮 45-45-1236-12-3
+always 뽯 45-45-1236-3
+always 뽰 45-45-1236-3-3
+always 뽱 45-45-1236-2356
+always 뽲 45-45-1236-13
+always 뽳 45-45-1236-23
+always 뽴 45-45-1236-235
+always 뽵 45-45-1236-236
+always 뽶 45-45-1236-256
+always 뽷 45-45-1236-356
+always 뽸 45-45-1236-1235
+always 뽹 45-45-1236-1235-1
+always 뽺 45-45-1236-1235-1-1
+always 뽻 45-45-1236-1235-1-3
+always 뽼 45-45-1236-1235-25
+always 뽽 45-45-1236-1235-25-13
+always 뽾 45-45-1236-1235-25-356
+always 뽿 45-45-1236-1235-35
+always 뾀 45-45-1236-1235-2
+always 뾁 45-45-1236-1235-2-1
+always 뾂 45-45-1236-1235-2-26
+always 뾃 45-45-1236-1235-2-12
+always 뾄 45-45-1236-1235-2-3
+always 뾅 45-45-1236-1235-2-236
+always 뾆 45-45-1236-1235-2-256
+always 뾇 45-45-1236-1235-2-356
+always 뾈 45-45-1236-1235-26
+always 뾉 45-45-1236-1235-12
+always 뾊 45-45-1236-1235-12-3
+always 뾋 45-45-1236-1235-3
+always 뾌 45-45-1236-1235-3-3
+always 뾍 45-45-1236-1235-2356
+always 뾎 45-45-1236-1235-13
+always 뾏 45-45-1236-1235-23
+always 뾐 45-45-1236-1235-235
+always 뾑 45-45-1236-1235-236
+always 뾒 45-45-1236-1235-256
+always 뾓 45-45-1236-1235-356
+always 뾔 45-45-13456
+always 뾕 45-45-13456-1
+always 뾖 45-45-13456-1-1
+always 뾗 45-45-13456-1-3
+always 뾘 45-45-13456-25
+always 뾙 45-45-13456-25-13
+always 뾚 45-45-13456-25-356
+always 뾛 45-45-13456-35
+always 뾜 45-45-13456-2
+always 뾝 45-45-13456-2-1
+always 뾞 45-45-13456-2-26
+always 뾟 45-45-13456-2-12
+always 뾠 45-45-13456-2-3
+always 뾡 45-45-13456-2-236
+always 뾢 45-45-13456-2-256
+always 뾣 45-45-13456-2-356
+always 뾤 45-45-13456-26
+always 뾥 45-45-13456-12
+always 뾦 45-45-13456-12-3
+always 뾧 45-45-13456-3
+always 뾨 45-45-13456-3-3
+always 뾩 45-45-13456-2356
+always 뾪 45-45-13456-13
+always 뾫 45-45-13456-23
+always 뾬 45-45-13456-235
+always 뾭 45-45-13456-236
+always 뾮 45-45-13456-256
+always 뾯 45-45-13456-356
+always 뾰 45-45-346
+always 뾱 45-45-346-1
+always 뾲 45-45-346-1-1
+always 뾳 45-45-346-1-3
+always 뾴 45-45-346-25
+always 뾵 45-45-346-25-13
+always 뾶 45-45-346-25-356
+always 뾷 45-45-346-35
+always 뾸 45-45-346-2
+always 뾹 45-45-346-2-1
+always 뾺 45-45-346-2-26
+always 뾻 45-45-346-2-12
+always 뾼 45-45-346-2-3
+always 뾽 45-45-346-2-236
+always 뾾 45-45-346-2-256
+always 뾿 45-45-346-2-356
+always 뿀 45-45-346-26
+always 뿁 45-45-346-12
+always 뿂 45-45-346-12-3
+always 뿃 45-45-346-3
+always 뿄 45-45-346-3-3
+always 뿅 45-45-346-2356
+always 뿆 45-45-346-13
+always 뿇 45-45-346-23
+always 뿈 45-45-346-235
+always 뿉 45-45-346-236
+always 뿊 45-45-346-256
+always 뿋 45-45-346-356
+always 뿌 45-45-134
+always 뿍 45-45-134-1
+always 뿎 45-45-134-1-1
+always 뿏 45-45-134-1-3
+always 뿐 45-45-134-25
+always 뿑 45-45-134-25-13
+always 뿒 45-45-134-25-356
+always 뿓 45-45-134-35
+always 뿔 45-45-134-2
+always 뿕 45-45-134-2-1
+always 뿖 45-45-134-2-26
+always 뿗 45-45-134-2-12
+always 뿘 45-45-134-2-3
+always 뿙 45-45-134-2-236
+always 뿚 45-45-134-2-256
+always 뿛 45-45-134-2-356
+always 뿜 45-45-134-26
+always 뿝 45-45-134-12
+always 뿞 45-45-134-12-3
+always 뿟 45-45-134-3
+always 뿠 45-45-134-3-3
+always 뿡 45-45-134-2356
+always 뿢 45-45-134-13
+always 뿣 45-45-134-23
+always 뿤 45-45-134-235
+always 뿥 45-45-134-236
+always 뿦 45-45-134-256
+always 뿧 45-45-134-356
+always 뿨 45-45-1234
+always 뿩 45-45-1234-1
+always 뿪 45-45-1234-1-1
+always 뿫 45-45-1234-1-3
+always 뿬 45-45-1234-25
+always 뿭 45-45-1234-25-13
+always 뿮 45-45-1234-25-356
+always 뿯 45-45-1234-35
+always 뿰 45-45-1234-2
+always 뿱 45-45-1234-2-1
+always 뿲 45-45-1234-2-26
+always 뿳 45-45-1234-2-12
+always 뿴 45-45-1234-2-3
+always 뿵 45-45-1234-2-236
+always 뿶 45-45-1234-2-256
+always 뿷 45-45-1234-2-356
+always 뿸 45-45-1234-26
+always 뿹 45-45-1234-12
+always 뿺 45-45-1234-12-3
+always 뿻 45-45-1234-3
+always 뿼 45-45-1234-3-3
+always 뿽 45-45-1234-2356
+always 뿾 45-45-1234-13
+always 뿿 45-45-1234-23
+always 쀀 45-45-1234-235
+always 쀁 45-45-1234-236
+always 쀂 45-45-1234-256
+always 쀃 45-45-1234-356
+always 쀄 45-45-1234-1235
+always 쀅 45-45-1234-1235-1
+always 쀆 45-45-1234-1235-1-1
+always 쀇 45-45-1234-1235-1-3
+always 쀈 45-45-1234-1235-25
+always 쀉 45-45-1234-1235-25-13
+always 쀊 45-45-1234-1235-25-356
+always 쀋 45-45-1234-1235-35
+always 쀌 45-45-1234-1235-2
+always 쀍 45-45-1234-1235-2-1
+always 쀎 45-45-1234-1235-2-26
+always 쀏 45-45-1234-1235-2-12
+always 쀐 45-45-1234-1235-2-3
+always 쀑 45-45-1234-1235-2-236
+always 쀒 45-45-1234-1235-2-256
+always 쀓 45-45-1234-1235-2-356
+always 쀔 45-45-1234-1235-26
+always 쀕 45-45-1234-1235-12
+always 쀖 45-45-1234-1235-12-3
+always 쀗 45-45-1234-1235-3
+always 쀘 45-45-1234-1235-3-3
+always 쀙 45-45-1234-1235-2356
+always 쀚 45-45-1234-1235-13
+always 쀛 45-45-1234-1235-23
+always 쀜 45-45-1234-1235-235
+always 쀝 45-45-1234-1235-236
+always 쀞 45-45-1234-1235-256
+always 쀟 45-45-1234-1235-356
+always 쀠 45-45-134-1235
+always 쀡 45-45-134-1235-1
+always 쀢 45-45-134-1235-1-1
+always 쀣 45-45-134-1235-1-3
+always 쀤 45-45-134-1235-25
+always 쀥 45-45-134-1235-25-13
+always 쀦 45-45-134-1235-25-356
+always 쀧 45-45-134-1235-35
+always 쀨 45-45-134-1235-2
+always 쀩 45-45-134-1235-2-1
+always 쀪 45-45-134-1235-2-26
+always 쀫 45-45-134-1235-2-12
+always 쀬 45-45-134-1235-2-3
+always 쀭 45-45-134-1235-2-236
+always 쀮 45-45-134-1235-2-256
+always 쀯 45-45-134-1235-2-356
+always 쀰 45-45-134-1235-26
+always 쀱 45-45-134-1235-12
+always 쀲 45-45-134-1235-12-3
+always 쀳 45-45-134-1235-3
+always 쀴 45-45-134-1235-3-3
+always 쀵 45-45-134-1235-2356
+always 쀶 45-45-134-1235-13
+always 쀷 45-45-134-1235-23
+always 쀸 45-45-134-1235-235
+always 쀹 45-45-134-1235-236
+always 쀺 45-45-134-1235-256
+always 쀻 45-45-134-1235-356
+always 쀼 45-45-146
+always 쀽 45-45-146-1
+always 쀾 45-45-146-1-1
+always 쀿 45-45-146-1-3
+always 쁀 45-45-146-25
+always 쁁 45-45-146-25-13
+always 쁂 45-45-146-25-356
+always 쁃 45-45-146-35
+always 쁄 45-45-146-2
+always 쁅 45-45-146-2-1
+always 쁆 45-45-146-2-26
+always 쁇 45-45-146-2-12
+always 쁈 45-45-146-2-3
+always 쁉 45-45-146-2-236
+always 쁊 45-45-146-2-256
+always 쁋 45-45-146-2-356
+always 쁌 45-45-146-26
+always 쁍 45-45-146-12
+always 쁎 45-45-146-12-3
+always 쁏 45-45-146-3
+always 쁐 45-45-146-3-3
+always 쁑 45-45-146-2356
+always 쁒 45-45-146-13
+always 쁓 45-45-146-23
+always 쁔 45-45-146-235
+always 쁕 45-45-146-236
+always 쁖 45-45-146-256
+always 쁗 45-45-146-356
+always 쁘 45-45-246
+always 쁙 45-45-246-1
+always 쁚 45-45-246-1-1
+always 쁛 45-45-246-1-3
+always 쁜 45-45-246-25
+always 쁝 45-45-246-25-13
+always 쁞 45-45-246-25-356
+always 쁟 45-45-246-35
+always 쁠 45-45-246-2
+always 쁡 45-45-246-2-1
+always 쁢 45-45-246-2-26
+always 쁣 45-45-246-2-12
+always 쁤 45-45-246-2-3
+always 쁥 45-45-246-2-236
+always 쁦 45-45-246-2-256
+always 쁧 45-45-246-2-356
+always 쁨 45-45-246-26
+always 쁩 45-45-246-12
+always 쁪 45-45-246-12-3
+always 쁫 45-45-246-3
+always 쁬 45-45-246-3-3
+always 쁭 45-45-246-2356
+always 쁮 45-45-246-13
+always 쁯 45-45-246-23
+always 쁰 45-45-246-235
+always 쁱 45-45-246-236
+always 쁲 45-45-246-256
+always 쁳 45-45-246-356
+always 쁴 45-45-2456
+always 쁵 45-45-2456-1
+always 쁶 45-45-2456-1-1
+always 쁷 45-45-2456-1-3
+always 쁸 45-45-2456-25
+always 쁹 45-45-2456-25-13
+always 쁺 45-45-2456-25-356
+always 쁻 45-45-2456-35
+always 쁼 45-45-2456-2
+always 쁽 45-45-2456-2-1
+always 쁾 45-45-2456-2-26
+always 쁿 45-45-2456-2-12
+always 삀 45-45-2456-2-3
+always 삁 45-45-2456-2-236
+always 삂 45-45-2456-2-256
+always 삃 45-45-2456-2-356
+always 삄 45-45-2456-26
+always 삅 45-45-2456-12
+always 삆 45-45-2456-12-3
+always 삇 45-45-2456-3
+always 삈 45-45-2456-3-3
+always 삉 45-45-2456-2356
+always 삊 45-45-2456-13
+always 삋 45-45-2456-23
+always 삌 45-45-2456-235
+always 삍 45-45-2456-236
+always 삎 45-45-2456-256
+always 삏 45-45-2456-356
+always 삐 45-45-135
+always 삑 45-45-135-1
+always 삒 45-45-135-1-1
+always 삓 45-45-135-1-3
+always 삔 45-45-135-25
+always 삕 45-45-135-25-13
+always 삖 45-45-135-25-356
+always 삗 45-45-135-35
+always 삘 45-45-135-2
+always 삙 45-45-135-2-1
+always 삚 45-45-135-2-26
+always 삛 45-45-135-2-12
+always 삜 45-45-135-2-3
+always 삝 45-45-135-2-236
+always 삞 45-45-135-2-256
+always 삟 45-45-135-2-356
+always 삠 45-45-135-26
+always 삡 45-45-135-12
+always 삢 45-45-135-12-3
+always 삣 45-45-135-3
+always 삤 45-45-135-3-3
+always 삥 45-45-135-2356
+always 삦 45-45-135-13
+always 삧 45-45-135-23
+always 삨 45-45-135-235
+always 삩 45-45-135-236
+always 삪 45-45-135-256
+always 삫 45-45-135-356
+always 사 6-126
+always 삭 6-126-1
+always 삮 6-126-1-1
+always 삯 6-126-1-3
+always 산 6-126-25
+always 삱 6-126-25-13
+always 삲 6-126-25-356
+always 삳 6-126-35
+always 살 6-126-2
+always 삵 6-126-2-1
+always 삶 6-126-2-26
+always 삷 6-126-2-12
+always 삸 6-126-2-3
+always 삹 6-126-2-236
+always 삺 6-126-2-256
+always 삻 6-126-2-356
+always 삼 6-126-26
+always 삽 6-126-12
+always 삾 6-126-12-3
+always 삿 6-126-3
+always 샀 6-126-3-3
+always 상 6-126-2356
+always 샂 6-126-13
+always 샃 6-126-23
+always 샄 6-126-235
+always 샅 6-126-236
+always 샆 6-126-256
+always 샇 6-126-356
+always 새 6-1235
+always 색 6-1235-1
+always 샊 6-1235-1-1
+always 샋 6-1235-1-3
+always 샌 6-1235-25
+always 샍 6-1235-25-13
+always 샎 6-1235-25-356
+always 샏 6-1235-35
+always 샐 6-1235-2
+always 샑 6-1235-2-1
+always 샒 6-1235-2-26
+always 샓 6-1235-2-12
+always 샔 6-1235-2-3
+always 샕 6-1235-2-236
+always 샖 6-1235-2-256
+always 샗 6-1235-2-356
+always 샘 6-1235-26
+always 샙 6-1235-12
+always 샚 6-1235-12-3
+always 샛 6-1235-3
+always 샜 6-1235-3-3
+always 생 6-1235-2356
+always 샞 6-1235-13
+always 샟 6-1235-23
+always 샠 6-1235-235
+always 샡 6-1235-236
+always 샢 6-1235-256
+always 샣 6-1235-356
+always 샤 6-345
+always 샥 6-345-1
+always 샦 6-345-1-1
+always 샧 6-345-1-3
+always 샨 6-345-25
+always 샩 6-345-25-13
+always 샪 6-345-25-356
+always 샫 6-345-35
+always 샬 6-345-2
+always 샭 6-345-2-1
+always 샮 6-345-2-26
+always 샯 6-345-2-12
+always 샰 6-345-2-3
+always 샱 6-345-2-236
+always 샲 6-345-2-256
+always 샳 6-345-2-356
+always 샴 6-345-26
+always 샵 6-345-12
+always 샶 6-345-12-3
+always 샷 6-345-3
+always 샸 6-345-3-3
+always 샹 6-345-2356
+always 샺 6-345-13
+always 샻 6-345-23
+always 샼 6-345-235
+always 샽 6-345-236
+always 샾 6-345-256
+always 샿 6-345-356
+always 섀 6-345-1235
+always 섁 6-345-1235-1
+always 섂 6-345-1235-1-1
+always 섃 6-345-1235-1-3
+always 섄 6-345-1235-25
+always 섅 6-345-1235-25-13
+always 섆 6-345-1235-25-356
+always 섇 6-345-1235-35
+always 섈 6-345-1235-2
+always 섉 6-345-1235-2-1
+always 섊 6-345-1235-2-26
+always 섋 6-345-1235-2-12
+always 섌 6-345-1235-2-3
+always 섍 6-345-1235-2-236
+always 섎 6-345-1235-2-256
+always 섏 6-345-1235-2-356
+always 섐 6-345-1235-26
+always 섑 6-345-1235-12
+always 섒 6-345-1235-12-3
+always 섓 6-345-1235-3
+always 섔 6-345-1235-3-3
+always 섕 6-345-1235-2356
+always 섖 6-345-1235-13
+always 섗 6-345-1235-23
+always 섘 6-345-1235-235
+always 섙 6-345-1235-236
+always 섚 6-345-1235-256
+always 섛 6-345-1235-356
+always 서 6-234
+always 석 6-234-1
+always 섞 6-234-1-1
+always 섟 6-234-1-3
+always 선 6-234-25
+always 섡 6-234-25-13
+always 섢 6-234-25-356
+always 섣 6-234-35
+always 설 6-234-2
+always 섥 6-234-2-1
+always 섦 6-234-2-26
+always 섧 6-234-2-12
+always 섨 6-234-2-3
+always 섩 6-234-2-236
+always 섪 6-234-2-256
+always 섫 6-234-2-356
+always 섬 6-234-26
+always 섭 6-234-12
+always 섮 6-234-12-3
+always 섯 6-234-3
+always 섰 6-234-3-3
+always 성 6-234-2356
+always 섲 6-234-13
+always 섳 6-234-23
+always 섴 6-234-235
+always 섵 6-234-236
+always 섶 6-234-256
+always 섷 6-234-356
+always 세 6-1345
+always 섹 6-1345-1
+always 섺 6-1345-1-1
+always 섻 6-1345-1-3
+always 센 6-1345-25
+always 섽 6-1345-25-13
+always 섾 6-1345-25-356
+always 섿 6-1345-35
+always 셀 6-1345-2
+always 셁 6-1345-2-1
+always 셂 6-1345-2-26
+always 셃 6-1345-2-12
+always 셄 6-1345-2-3
+always 셅 6-1345-2-236
+always 셆 6-1345-2-256
+always 셇 6-1345-2-356
+always 셈 6-1345-26
+always 셉 6-1345-12
+always 셊 6-1345-12-3
+always 셋 6-1345-3
+always 셌 6-1345-3-3
+always 셍 6-1345-2356
+always 셎 6-1345-13
+always 셏 6-1345-23
+always 셐 6-1345-235
+always 셑 6-1345-236
+always 셒 6-1345-256
+always 셓 6-1345-356
+always 셔 6-156
+always 셕 6-156-1
+always 셖 6-156-1-1
+always 셗 6-156-1-3
+always 션 6-156-25
+always 셙 6-156-25-13
+always 셚 6-156-25-356
+always 셛 6-156-35
+always 셜 6-156-2
+always 셝 6-156-2-1
+always 셞 6-156-2-26
+always 셟 6-156-2-12
+always 셠 6-156-2-3
+always 셡 6-156-2-236
+always 셢 6-156-2-256
+always 셣 6-156-2-356
+always 셤 6-156-26
+always 셥 6-156-12
+always 셦 6-156-12-3
+always 셧 6-156-3
+always 셨 6-156-3-3
+always 셩 6-156-2356
+always 셪 6-156-13
+always 셫 6-156-23
+always 셬 6-156-235
+always 셭 6-156-236
+always 셮 6-156-256
+always 셯 6-156-356
+always 셰 6-34
+always 셱 6-34-1
+always 셲 6-34-1-1
+always 셳 6-34-1-3
+always 셴 6-34-25
+always 셵 6-34-25-13
+always 셶 6-34-25-356
+always 셷 6-34-35
+always 셸 6-34-2
+always 셹 6-34-2-1
+always 셺 6-34-2-26
+always 셻 6-34-2-12
+always 셼 6-34-2-3
+always 셽 6-34-2-236
+always 셾 6-34-2-256
+always 셿 6-34-2-356
+always 솀 6-34-26
+always 솁 6-34-12
+always 솂 6-34-12-3
+always 솃 6-34-3
+always 솄 6-34-3-3
+always 솅 6-34-2356
+always 솆 6-34-13
+always 솇 6-34-23
+always 솈 6-34-235
+always 솉 6-34-236
+always 솊 6-34-256
+always 솋 6-34-356
+always 소 6-136
+always 속 6-136-1
+always 솎 6-136-1-1
+always 솏 6-136-1-3
+always 손 6-136-25
+always 솑 6-136-25-13
+always 솒 6-136-25-356
+always 솓 6-136-35
+always 솔 6-136-2
+always 솕 6-136-2-1
+always 솖 6-136-2-26
+always 솗 6-136-2-12
+always 솘 6-136-2-3
+always 솙 6-136-2-236
+always 솚 6-136-2-256
+always 솛 6-136-2-356
+always 솜 6-136-26
+always 솝 6-136-12
+always 솞 6-136-12-3
+always 솟 6-136-3
+always 솠 6-136-3-3
+always 송 6-136-2356
+always 솢 6-136-13
+always 솣 6-136-23
+always 솤 6-136-235
+always 솥 6-136-236
+always 솦 6-136-256
+always 솧 6-136-356
+always 솨 6-1236
+always 솩 6-1236-1
+always 솪 6-1236-1-1
+always 솫 6-1236-1-3
+always 솬 6-1236-25
+always 솭 6-1236-25-13
+always 솮 6-1236-25-356
+always 솯 6-1236-35
+always 솰 6-1236-2
+always 솱 6-1236-2-1
+always 솲 6-1236-2-26
+always 솳 6-1236-2-12
+always 솴 6-1236-2-3
+always 솵 6-1236-2-236
+always 솶 6-1236-2-256
+always 솷 6-1236-2-356
+always 솸 6-1236-26
+always 솹 6-1236-12
+always 솺 6-1236-12-3
+always 솻 6-1236-3
+always 솼 6-1236-3-3
+always 솽 6-1236-2356
+always 솾 6-1236-13
+always 솿 6-1236-23
+always 쇀 6-1236-235
+always 쇁 6-1236-236
+always 쇂 6-1236-256
+always 쇃 6-1236-356
+always 쇄 6-1236-1235
+always 쇅 6-1236-1235-1
+always 쇆 6-1236-1235-1-1
+always 쇇 6-1236-1235-1-3
+always 쇈 6-1236-1235-25
+always 쇉 6-1236-1235-25-13
+always 쇊 6-1236-1235-25-356
+always 쇋 6-1236-1235-35
+always 쇌 6-1236-1235-2
+always 쇍 6-1236-1235-2-1
+always 쇎 6-1236-1235-2-26
+always 쇏 6-1236-1235-2-12
+always 쇐 6-1236-1235-2-3
+always 쇑 6-1236-1235-2-236
+always 쇒 6-1236-1235-2-256
+always 쇓 6-1236-1235-2-356
+always 쇔 6-1236-1235-26
+always 쇕 6-1236-1235-12
+always 쇖 6-1236-1235-12-3
+always 쇗 6-1236-1235-3
+always 쇘 6-1236-1235-3-3
+always 쇙 6-1236-1235-2356
+always 쇚 6-1236-1235-13
+always 쇛 6-1236-1235-23
+always 쇜 6-1236-1235-235
+always 쇝 6-1236-1235-236
+always 쇞 6-1236-1235-256
+always 쇟 6-1236-1235-356
+always 쇠 6-13456
+always 쇡 6-13456-1
+always 쇢 6-13456-1-1
+always 쇣 6-13456-1-3
+always 쇤 6-13456-25
+always 쇥 6-13456-25-13
+always 쇦 6-13456-25-356
+always 쇧 6-13456-35
+always 쇨 6-13456-2
+always 쇩 6-13456-2-1
+always 쇪 6-13456-2-26
+always 쇫 6-13456-2-12
+always 쇬 6-13456-2-3
+always 쇭 6-13456-2-236
+always 쇮 6-13456-2-256
+always 쇯 6-13456-2-356
+always 쇰 6-13456-26
+always 쇱 6-13456-12
+always 쇲 6-13456-12-3
+always 쇳 6-13456-3
+always 쇴 6-13456-3-3
+always 쇵 6-13456-2356
+always 쇶 6-13456-13
+always 쇷 6-13456-23
+always 쇸 6-13456-235
+always 쇹 6-13456-236
+always 쇺 6-13456-256
+always 쇻 6-13456-356
+always 쇼 6-346
+always 쇽 6-346-1
+always 쇾 6-346-1-1
+always 쇿 6-346-1-3
+always 숀 6-346-25
+always 숁 6-346-25-13
+always 숂 6-346-25-356
+always 숃 6-346-35
+always 숄 6-346-2
+always 숅 6-346-2-1
+always 숆 6-346-2-26
+always 숇 6-346-2-12
+always 숈 6-346-2-3
+always 숉 6-346-2-236
+always 숊 6-346-2-256
+always 숋 6-346-2-356
+always 숌 6-346-26
+always 숍 6-346-12
+always 숎 6-346-12-3
+always 숏 6-346-3
+always 숐 6-346-3-3
+always 숑 6-346-2356
+always 숒 6-346-13
+always 숓 6-346-23
+always 숔 6-346-235
+always 숕 6-346-236
+always 숖 6-346-256
+always 숗 6-346-356
+always 수 6-134
+always 숙 6-134-1
+always 숚 6-134-1-1
+always 숛 6-134-1-3
+always 순 6-134-25
+always 숝 6-134-25-13
+always 숞 6-134-25-356
+always 숟 6-134-35
+always 술 6-134-2
+always 숡 6-134-2-1
+always 숢 6-134-2-26
+always 숣 6-134-2-12
+always 숤 6-134-2-3
+always 숥 6-134-2-236
+always 숦 6-134-2-256
+always 숧 6-134-2-356
+always 숨 6-134-26
+always 숩 6-134-12
+always 숪 6-134-12-3
+always 숫 6-134-3
+always 숬 6-134-3-3
+always 숭 6-134-2356
+always 숮 6-134-13
+always 숯 6-134-23
+always 숰 6-134-235
+always 숱 6-134-236
+always 숲 6-134-256
+always 숳 6-134-356
+always 숴 6-1234
+always 숵 6-1234-1
+always 숶 6-1234-1-1
+always 숷 6-1234-1-3
+always 숸 6-1234-25
+always 숹 6-1234-25-13
+always 숺 6-1234-25-356
+always 숻 6-1234-35
+always 숼 6-1234-2
+always 숽 6-1234-2-1
+always 숾 6-1234-2-26
+always 숿 6-1234-2-12
+always 쉀 6-1234-2-3
+always 쉁 6-1234-2-236
+always 쉂 6-1234-2-256
+always 쉃 6-1234-2-356
+always 쉄 6-1234-26
+always 쉅 6-1234-12
+always 쉆 6-1234-12-3
+always 쉇 6-1234-3
+always 쉈 6-1234-3-3
+always 쉉 6-1234-2356
+always 쉊 6-1234-13
+always 쉋 6-1234-23
+always 쉌 6-1234-235
+always 쉍 6-1234-236
+always 쉎 6-1234-256
+always 쉏 6-1234-356
+always 쉐 6-1234-1235
+always 쉑 6-1234-1235-1
+always 쉒 6-1234-1235-1-1
+always 쉓 6-1234-1235-1-3
+always 쉔 6-1234-1235-25
+always 쉕 6-1234-1235-25-13
+always 쉖 6-1234-1235-25-356
+always 쉗 6-1234-1235-35
+always 쉘 6-1234-1235-2
+always 쉙 6-1234-1235-2-1
+always 쉚 6-1234-1235-2-26
+always 쉛 6-1234-1235-2-12
+always 쉜 6-1234-1235-2-3
+always 쉝 6-1234-1235-2-236
+always 쉞 6-1234-1235-2-256
+always 쉟 6-1234-1235-2-356
+always 쉠 6-1234-1235-26
+always 쉡 6-1234-1235-12
+always 쉢 6-1234-1235-12-3
+always 쉣 6-1234-1235-3
+always 쉤 6-1234-1235-3-3
+always 쉥 6-1234-1235-2356
+always 쉦 6-1234-1235-13
+always 쉧 6-1234-1235-23
+always 쉨 6-1234-1235-235
+always 쉩 6-1234-1235-236
+always 쉪 6-1234-1235-256
+always 쉫 6-1234-1235-356
+always 쉬 6-134-1235
+always 쉭 6-134-1235-1
+always 쉮 6-134-1235-1-1
+always 쉯 6-134-1235-1-3
+always 쉰 6-134-1235-25
+always 쉱 6-134-1235-25-13
+always 쉲 6-134-1235-25-356
+always 쉳 6-134-1235-35
+always 쉴 6-134-1235-2
+always 쉵 6-134-1235-2-1
+always 쉶 6-134-1235-2-26
+always 쉷 6-134-1235-2-12
+always 쉸 6-134-1235-2-3
+always 쉹 6-134-1235-2-236
+always 쉺 6-134-1235-2-256
+always 쉻 6-134-1235-2-356
+always 쉼 6-134-1235-26
+always 쉽 6-134-1235-12
+always 쉾 6-134-1235-12-3
+always 쉿 6-134-1235-3
+always 슀 6-134-1235-3-3
+always 슁 6-134-1235-2356
+always 슂 6-134-1235-13
+always 슃 6-134-1235-23
+always 슄 6-134-1235-235
+always 슅 6-134-1235-236
+always 슆 6-134-1235-256
+always 슇 6-134-1235-356
+always 슈 6-146
+always 슉 6-146-1
+always 슊 6-146-1-1
+always 슋 6-146-1-3
+always 슌 6-146-25
+always 슍 6-146-25-13
+always 슎 6-146-25-356
+always 슏 6-146-35
+always 슐 6-146-2
+always 슑 6-146-2-1
+always 슒 6-146-2-26
+always 슓 6-146-2-12
+always 슔 6-146-2-3
+always 슕 6-146-2-236
+always 슖 6-146-2-256
+always 슗 6-146-2-356
+always 슘 6-146-26
+always 슙 6-146-12
+always 슚 6-146-12-3
+always 슛 6-146-3
+always 슜 6-146-3-3
+always 슝 6-146-2356
+always 슞 6-146-13
+always 슟 6-146-23
+always 슠 6-146-235
+always 슡 6-146-236
+always 슢 6-146-256
+always 슣 6-146-356
+always 스 6-246
+always 슥 6-246-1
+always 슦 6-246-1-1
+always 슧 6-246-1-3
+always 슨 6-246-25
+always 슩 6-246-25-13
+always 슪 6-246-25-356
+always 슫 6-246-35
+always 슬 6-246-2
+always 슭 6-246-2-1
+always 슮 6-246-2-26
+always 슯 6-246-2-12
+always 슰 6-246-2-3
+always 슱 6-246-2-236
+always 슲 6-246-2-256
+always 슳 6-246-2-356
+always 슴 6-246-26
+always 습 6-246-12
+always 슶 6-246-12-3
+always 슷 6-246-3
+always 슸 6-246-3-3
+always 승 6-246-2356
+always 슺 6-246-13
+always 슻 6-246-23
+always 슼 6-246-235
+always 슽 6-246-236
+always 슾 6-246-256
+always 슿 6-246-356
+always 싀 6-2456
+always 싁 6-2456-1
+always 싂 6-2456-1-1
+always 싃 6-2456-1-3
+always 싄 6-2456-25
+always 싅 6-2456-25-13
+always 싆 6-2456-25-356
+always 싇 6-2456-35
+always 싈 6-2456-2
+always 싉 6-2456-2-1
+always 싊 6-2456-2-26
+always 싋 6-2456-2-12
+always 싌 6-2456-2-3
+always 싍 6-2456-2-236
+always 싎 6-2456-2-256
+always 싏 6-2456-2-356
+always 싐 6-2456-26
+always 싑 6-2456-12
+always 싒 6-2456-12-3
+always 싓 6-2456-3
+always 싔 6-2456-3-3
+always 싕 6-2456-2356
+always 싖 6-2456-13
+always 싗 6-2456-23
+always 싘 6-2456-235
+always 싙 6-2456-236
+always 싚 6-2456-256
+always 싛 6-2456-356
+always 시 6-135
+always 식 6-135-1
+always 싞 6-135-1-1
+always 싟 6-135-1-3
+always 신 6-135-25
+always 싡 6-135-25-13
+always 싢 6-135-25-356
+always 싣 6-135-35
+always 실 6-135-2
+always 싥 6-135-2-1
+always 싦 6-135-2-26
+always 싧 6-135-2-12
+always 싨 6-135-2-3
+always 싩 6-135-2-236
+always 싪 6-135-2-256
+always 싫 6-135-2-356
+always 심 6-135-26
+always 십 6-135-12
+always 싮 6-135-12-3
+always 싯 6-135-3
+always 싰 6-135-3-3
+always 싱 6-135-2356
+always 싲 6-135-13
+always 싳 6-135-23
+always 싴 6-135-235
+always 싵 6-135-236
+always 싶 6-135-256
+always 싷 6-135-356
+always 싸 6-6-126
+always 싹 6-6-126-1
+always 싺 6-6-126-1-1
+always 싻 6-6-126-1-3
+always 싼 6-6-126-25
+always 싽 6-6-126-25-13
+always 싾 6-6-126-25-356
+always 싿 6-6-126-35
+always 쌀 6-6-126-2
+always 쌁 6-6-126-2-1
+always 쌂 6-6-126-2-26
+always 쌃 6-6-126-2-12
+always 쌄 6-6-126-2-3
+always 쌅 6-6-126-2-236
+always 쌆 6-6-126-2-256
+always 쌇 6-6-126-2-356
+always 쌈 6-6-126-26
+always 쌉 6-6-126-12
+always 쌊 6-6-126-12-3
+always 쌋 6-6-126-3
+always 쌌 6-6-126-3-3
+always 쌍 6-6-126-2356
+always 쌎 6-6-126-13
+always 쌏 6-6-126-23
+always 쌐 6-6-126-235
+always 쌑 6-6-126-236
+always 쌒 6-6-126-256
+always 쌓 6-6-126-356
+always 쌔 6-6-1235
+always 쌕 6-6-1235-1
+always 쌖 6-6-1235-1-1
+always 쌗 6-6-1235-1-3
+always 쌘 6-6-1235-25
+always 쌙 6-6-1235-25-13
+always 쌚 6-6-1235-25-356
+always 쌛 6-6-1235-35
+always 쌜 6-6-1235-2
+always 쌝 6-6-1235-2-1
+always 쌞 6-6-1235-2-26
+always 쌟 6-6-1235-2-12
+always 쌠 6-6-1235-2-3
+always 쌡 6-6-1235-2-236
+always 쌢 6-6-1235-2-256
+always 쌣 6-6-1235-2-356
+always 쌤 6-6-1235-26
+always 쌥 6-6-1235-12
+always 쌦 6-6-1235-12-3
+always 쌧 6-6-1235-3
+always 쌨 6-6-1235-3-3
+always 쌩 6-6-1235-2356
+always 쌪 6-6-1235-13
+always 쌫 6-6-1235-23
+always 쌬 6-6-1235-235
+always 쌭 6-6-1235-236
+always 쌮 6-6-1235-256
+always 쌯 6-6-1235-356
+always 쌰 6-6-345
+always 쌱 6-6-345-1
+always 쌲 6-6-345-1-1
+always 쌳 6-6-345-1-3
+always 쌴 6-6-345-25
+always 쌵 6-6-345-25-13
+always 쌶 6-6-345-25-356
+always 쌷 6-6-345-35
+always 쌸 6-6-345-2
+always 쌹 6-6-345-2-1
+always 쌺 6-6-345-2-26
+always 쌻 6-6-345-2-12
+always 쌼 6-6-345-2-3
+always 쌽 6-6-345-2-236
+always 쌾 6-6-345-2-256
+always 쌿 6-6-345-2-356
+always 썀 6-6-345-26
+always 썁 6-6-345-12
+always 썂 6-6-345-12-3
+always 썃 6-6-345-3
+always 썄 6-6-345-3-3
+always 썅 6-6-345-2356
+always 썆 6-6-345-13
+always 썇 6-6-345-23
+always 썈 6-6-345-235
+always 썉 6-6-345-236
+always 썊 6-6-345-256
+always 썋 6-6-345-356
+always 썌 6-6-345-1235
+always 썍 6-6-345-1235-1
+always 썎 6-6-345-1235-1-1
+always 썏 6-6-345-1235-1-3
+always 썐 6-6-345-1235-25
+always 썑 6-6-345-1235-25-13
+always 썒 6-6-345-1235-25-356
+always 썓 6-6-345-1235-35
+always 썔 6-6-345-1235-2
+always 썕 6-6-345-1235-2-1
+always 썖 6-6-345-1235-2-26
+always 썗 6-6-345-1235-2-12
+always 썘 6-6-345-1235-2-3
+always 썙 6-6-345-1235-2-236
+always 썚 6-6-345-1235-2-256
+always 썛 6-6-345-1235-2-356
+always 썜 6-6-345-1235-26
+always 썝 6-6-345-1235-12
+always 썞 6-6-345-1235-12-3
+always 썟 6-6-345-1235-3
+always 썠 6-6-345-1235-3-3
+always 썡 6-6-345-1235-2356
+always 썢 6-6-345-1235-13
+always 썣 6-6-345-1235-23
+always 썤 6-6-345-1235-235
+always 썥 6-6-345-1235-236
+always 썦 6-6-345-1235-256
+always 썧 6-6-345-1235-356
+always 써 6-6-234
+always 썩 6-6-234-1
+always 썪 6-6-234-1-1
+always 썫 6-6-234-1-3
+always 썬 6-6-234-25
+always 썭 6-6-234-25-13
+always 썮 6-6-234-25-356
+always 썯 6-6-234-35
+always 썰 6-6-234-2
+always 썱 6-6-234-2-1
+always 썲 6-6-234-2-26
+always 썳 6-6-234-2-12
+always 썴 6-6-234-2-3
+always 썵 6-6-234-2-236
+always 썶 6-6-234-2-256
+always 썷 6-6-234-2-356
+always 썸 6-6-234-26
+always 썹 6-6-234-12
+always 썺 6-6-234-12-3
+always 썻 6-6-234-3
+always 썼 6-6-234-3-3
+always 썽 6-6-234-2356
+always 썾 6-6-234-13
+always 썿 6-6-234-23
+always 쎀 6-6-234-235
+always 쎁 6-6-234-236
+always 쎂 6-6-234-256
+always 쎃 6-6-234-356
+always 쎄 6-6-1345
+always 쎅 6-6-1345-1
+always 쎆 6-6-1345-1-1
+always 쎇 6-6-1345-1-3
+always 쎈 6-6-1345-25
+always 쎉 6-6-1345-25-13
+always 쎊 6-6-1345-25-356
+always 쎋 6-6-1345-35
+always 쎌 6-6-1345-2
+always 쎍 6-6-1345-2-1
+always 쎎 6-6-1345-2-26
+always 쎏 6-6-1345-2-12
+always 쎐 6-6-1345-2-3
+always 쎑 6-6-1345-2-236
+always 쎒 6-6-1345-2-256
+always 쎓 6-6-1345-2-356
+always 쎔 6-6-1345-26
+always 쎕 6-6-1345-12
+always 쎖 6-6-1345-12-3
+always 쎗 6-6-1345-3
+always 쎘 6-6-1345-3-3
+always 쎙 6-6-1345-2356
+always 쎚 6-6-1345-13
+always 쎛 6-6-1345-23
+always 쎜 6-6-1345-235
+always 쎝 6-6-1345-236
+always 쎞 6-6-1345-256
+always 쎟 6-6-1345-356
+always 쎠 6-6-156
+always 쎡 6-6-156-1
+always 쎢 6-6-156-1-1
+always 쎣 6-6-156-1-3
+always 쎤 6-6-156-25
+always 쎥 6-6-156-25-13
+always 쎦 6-6-156-25-356
+always 쎧 6-6-156-35
+always 쎨 6-6-156-2
+always 쎩 6-6-156-2-1
+always 쎪 6-6-156-2-26
+always 쎫 6-6-156-2-12
+always 쎬 6-6-156-2-3
+always 쎭 6-6-156-2-236
+always 쎮 6-6-156-2-256
+always 쎯 6-6-156-2-356
+always 쎰 6-6-156-26
+always 쎱 6-6-156-12
+always 쎲 6-6-156-12-3
+always 쎳 6-6-156-3
+always 쎴 6-6-156-3-3
+always 쎵 6-6-156-2356
+always 쎶 6-6-156-13
+always 쎷 6-6-156-23
+always 쎸 6-6-156-235
+always 쎹 6-6-156-236
+always 쎺 6-6-156-256
+always 쎻 6-6-156-356
+always 쎼 6-6-34
+always 쎽 6-6-34-1
+always 쎾 6-6-34-1-1
+always 쎿 6-6-34-1-3
+always 쏀 6-6-34-25
+always 쏁 6-6-34-25-13
+always 쏂 6-6-34-25-356
+always 쏃 6-6-34-35
+always 쏄 6-6-34-2
+always 쏅 6-6-34-2-1
+always 쏆 6-6-34-2-26
+always 쏇 6-6-34-2-12
+always 쏈 6-6-34-2-3
+always 쏉 6-6-34-2-236
+always 쏊 6-6-34-2-256
+always 쏋 6-6-34-2-356
+always 쏌 6-6-34-26
+always 쏍 6-6-34-12
+always 쏎 6-6-34-12-3
+always 쏏 6-6-34-3
+always 쏐 6-6-34-3-3
+always 쏑 6-6-34-2356
+always 쏒 6-6-34-13
+always 쏓 6-6-34-23
+always 쏔 6-6-34-235
+always 쏕 6-6-34-236
+always 쏖 6-6-34-256
+always 쏗 6-6-34-356
+always 쏘 6-6-136
+always 쏙 6-6-136-1
+always 쏚 6-6-136-1-1
+always 쏛 6-6-136-1-3
+always 쏜 6-6-136-25
+always 쏝 6-6-136-25-13
+always 쏞 6-6-136-25-356
+always 쏟 6-6-136-35
+always 쏠 6-6-136-2
+always 쏡 6-6-136-2-1
+always 쏢 6-6-136-2-26
+always 쏣 6-6-136-2-12
+always 쏤 6-6-136-2-3
+always 쏥 6-6-136-2-236
+always 쏦 6-6-136-2-256
+always 쏧 6-6-136-2-356
+always 쏨 6-6-136-26
+always 쏩 6-6-136-12
+always 쏪 6-6-136-12-3
+always 쏫 6-6-136-3
+always 쏬 6-6-136-3-3
+always 쏭 6-6-136-2356
+always 쏮 6-6-136-13
+always 쏯 6-6-136-23
+always 쏰 6-6-136-235
+always 쏱 6-6-136-236
+always 쏲 6-6-136-256
+always 쏳 6-6-136-356
+always 쏴 6-6-1236
+always 쏵 6-6-1236-1
+always 쏶 6-6-1236-1-1
+always 쏷 6-6-1236-1-3
+always 쏸 6-6-1236-25
+always 쏹 6-6-1236-25-13
+always 쏺 6-6-1236-25-356
+always 쏻 6-6-1236-35
+always 쏼 6-6-1236-2
+always 쏽 6-6-1236-2-1
+always 쏾 6-6-1236-2-26
+always 쏿 6-6-1236-2-12
+always 쐀 6-6-1236-2-3
+always 쐁 6-6-1236-2-236
+always 쐂 6-6-1236-2-256
+always 쐃 6-6-1236-2-356
+always 쐄 6-6-1236-26
+always 쐅 6-6-1236-12
+always 쐆 6-6-1236-12-3
+always 쐇 6-6-1236-3
+always 쐈 6-6-1236-3-3
+always 쐉 6-6-1236-2356
+always 쐊 6-6-1236-13
+always 쐋 6-6-1236-23
+always 쐌 6-6-1236-235
+always 쐍 6-6-1236-236
+always 쐎 6-6-1236-256
+always 쐏 6-6-1236-356
+always 쐐 6-6-1236-1235
+always 쐑 6-6-1236-1235-1
+always 쐒 6-6-1236-1235-1-1
+always 쐓 6-6-1236-1235-1-3
+always 쐔 6-6-1236-1235-25
+always 쐕 6-6-1236-1235-25-13
+always 쐖 6-6-1236-1235-25-356
+always 쐗 6-6-1236-1235-35
+always 쐘 6-6-1236-1235-2
+always 쐙 6-6-1236-1235-2-1
+always 쐚 6-6-1236-1235-2-26
+always 쐛 6-6-1236-1235-2-12
+always 쐜 6-6-1236-1235-2-3
+always 쐝 6-6-1236-1235-2-236
+always 쐞 6-6-1236-1235-2-256
+always 쐟 6-6-1236-1235-2-356
+always 쐠 6-6-1236-1235-26
+always 쐡 6-6-1236-1235-12
+always 쐢 6-6-1236-1235-12-3
+always 쐣 6-6-1236-1235-3
+always 쐤 6-6-1236-1235-3-3
+always 쐥 6-6-1236-1235-2356
+always 쐦 6-6-1236-1235-13
+always 쐧 6-6-1236-1235-23
+always 쐨 6-6-1236-1235-235
+always 쐩 6-6-1236-1235-236
+always 쐪 6-6-1236-1235-256
+always 쐫 6-6-1236-1235-356
+always 쐬 6-6-13456
+always 쐭 6-6-13456-1
+always 쐮 6-6-13456-1-1
+always 쐯 6-6-13456-1-3
+always 쐰 6-6-13456-25
+always 쐱 6-6-13456-25-13
+always 쐲 6-6-13456-25-356
+always 쐳 6-6-13456-35
+always 쐴 6-6-13456-2
+always 쐵 6-6-13456-2-1
+always 쐶 6-6-13456-2-26
+always 쐷 6-6-13456-2-12
+always 쐸 6-6-13456-2-3
+always 쐹 6-6-13456-2-236
+always 쐺 6-6-13456-2-256
+always 쐻 6-6-13456-2-356
+always 쐼 6-6-13456-26
+always 쐽 6-6-13456-12
+always 쐾 6-6-13456-12-3
+always 쐿 6-6-13456-3
+always 쑀 6-6-13456-3-3
+always 쑁 6-6-13456-2356
+always 쑂 6-6-13456-13
+always 쑃 6-6-13456-23
+always 쑄 6-6-13456-235
+always 쑅 6-6-13456-236
+always 쑆 6-6-13456-256
+always 쑇 6-6-13456-356
+always 쑈 6-6-346
+always 쑉 6-6-346-1
+always 쑊 6-6-346-1-1
+always 쑋 6-6-346-1-3
+always 쑌 6-6-346-25
+always 쑍 6-6-346-25-13
+always 쑎 6-6-346-25-356
+always 쑏 6-6-346-35
+always 쑐 6-6-346-2
+always 쑑 6-6-346-2-1
+always 쑒 6-6-346-2-26
+always 쑓 6-6-346-2-12
+always 쑔 6-6-346-2-3
+always 쑕 6-6-346-2-236
+always 쑖 6-6-346-2-256
+always 쑗 6-6-346-2-356
+always 쑘 6-6-346-26
+always 쑙 6-6-346-12
+always 쑚 6-6-346-12-3
+always 쑛 6-6-346-3
+always 쑜 6-6-346-3-3
+always 쑝 6-6-346-2356
+always 쑞 6-6-346-13
+always 쑟 6-6-346-23
+always 쑠 6-6-346-235
+always 쑡 6-6-346-236
+always 쑢 6-6-346-256
+always 쑣 6-6-346-356
+always 쑤 6-6-134
+always 쑥 6-6-134-1
+always 쑦 6-6-134-1-1
+always 쑧 6-6-134-1-3
+always 쑨 6-6-134-25
+always 쑩 6-6-134-25-13
+always 쑪 6-6-134-25-356
+always 쑫 6-6-134-35
+always 쑬 6-6-134-2
+always 쑭 6-6-134-2-1
+always 쑮 6-6-134-2-26
+always 쑯 6-6-134-2-12
+always 쑰 6-6-134-2-3
+always 쑱 6-6-134-2-236
+always 쑲 6-6-134-2-256
+always 쑳 6-6-134-2-356
+always 쑴 6-6-134-26
+always 쑵 6-6-134-12
+always 쑶 6-6-134-12-3
+always 쑷 6-6-134-3
+always 쑸 6-6-134-3-3
+always 쑹 6-6-134-2356
+always 쑺 6-6-134-13
+always 쑻 6-6-134-23
+always 쑼 6-6-134-235
+always 쑽 6-6-134-236
+always 쑾 6-6-134-256
+always 쑿 6-6-134-356
+always 쒀 6-6-1234
+always 쒁 6-6-1234-1
+always 쒂 6-6-1234-1-1
+always 쒃 6-6-1234-1-3
+always 쒄 6-6-1234-25
+always 쒅 6-6-1234-25-13
+always 쒆 6-6-1234-25-356
+always 쒇 6-6-1234-35
+always 쒈 6-6-1234-2
+always 쒉 6-6-1234-2-1
+always 쒊 6-6-1234-2-26
+always 쒋 6-6-1234-2-12
+always 쒌 6-6-1234-2-3
+always 쒍 6-6-1234-2-236
+always 쒎 6-6-1234-2-256
+always 쒏 6-6-1234-2-356
+always 쒐 6-6-1234-26
+always 쒑 6-6-1234-12
+always 쒒 6-6-1234-12-3
+always 쒓 6-6-1234-3
+always 쒔 6-6-1234-3-3
+always 쒕 6-6-1234-2356
+always 쒖 6-6-1234-13
+always 쒗 6-6-1234-23
+always 쒘 6-6-1234-235
+always 쒙 6-6-1234-236
+always 쒚 6-6-1234-256
+always 쒛 6-6-1234-356
+always 쒜 6-6-1234-1235
+always 쒝 6-6-1234-1235-1
+always 쒞 6-6-1234-1235-1-1
+always 쒟 6-6-1234-1235-1-3
+always 쒠 6-6-1234-1235-25
+always 쒡 6-6-1234-1235-25-13
+always 쒢 6-6-1234-1235-25-356
+always 쒣 6-6-1234-1235-35
+always 쒤 6-6-1234-1235-2
+always 쒥 6-6-1234-1235-2-1
+always 쒦 6-6-1234-1235-2-26
+always 쒧 6-6-1234-1235-2-12
+always 쒨 6-6-1234-1235-2-3
+always 쒩 6-6-1234-1235-2-236
+always 쒪 6-6-1234-1235-2-256
+always 쒫 6-6-1234-1235-2-356
+always 쒬 6-6-1234-1235-26
+always 쒭 6-6-1234-1235-12
+always 쒮 6-6-1234-1235-12-3
+always 쒯 6-6-1234-1235-3
+always 쒰 6-6-1234-1235-3-3
+always 쒱 6-6-1234-1235-2356
+always 쒲 6-6-1234-1235-13
+always 쒳 6-6-1234-1235-23
+always 쒴 6-6-1234-1235-235
+always 쒵 6-6-1234-1235-236
+always 쒶 6-6-1234-1235-256
+always 쒷 6-6-1234-1235-356
+always 쒸 6-6-134-1235
+always 쒹 6-6-134-1235-1
+always 쒺 6-6-134-1235-1-1
+always 쒻 6-6-134-1235-1-3
+always 쒼 6-6-134-1235-25
+always 쒽 6-6-134-1235-25-13
+always 쒾 6-6-134-1235-25-356
+always 쒿 6-6-134-1235-35
+always 쓀 6-6-134-1235-2
+always 쓁 6-6-134-1235-2-1
+always 쓂 6-6-134-1235-2-26
+always 쓃 6-6-134-1235-2-12
+always 쓄 6-6-134-1235-2-3
+always 쓅 6-6-134-1235-2-236
+always 쓆 6-6-134-1235-2-256
+always 쓇 6-6-134-1235-2-356
+always 쓈 6-6-134-1235-26
+always 쓉 6-6-134-1235-12
+always 쓊 6-6-134-1235-12-3
+always 쓋 6-6-134-1235-3
+always 쓌 6-6-134-1235-3-3
+always 쓍 6-6-134-1235-2356
+always 쓎 6-6-134-1235-13
+always 쓏 6-6-134-1235-23
+always 쓐 6-6-134-1235-235
+always 쓑 6-6-134-1235-236
+always 쓒 6-6-134-1235-256
+always 쓓 6-6-134-1235-356
+always 쓔 6-6-146
+always 쓕 6-6-146-1
+always 쓖 6-6-146-1-1
+always 쓗 6-6-146-1-3
+always 쓘 6-6-146-25
+always 쓙 6-6-146-25-13
+always 쓚 6-6-146-25-356
+always 쓛 6-6-146-35
+always 쓜 6-6-146-2
+always 쓝 6-6-146-2-1
+always 쓞 6-6-146-2-26
+always 쓟 6-6-146-2-12
+always 쓠 6-6-146-2-3
+always 쓡 6-6-146-2-236
+always 쓢 6-6-146-2-256
+always 쓣 6-6-146-2-356
+always 쓤 6-6-146-26
+always 쓥 6-6-146-12
+always 쓦 6-6-146-12-3
+always 쓧 6-6-146-3
+always 쓨 6-6-146-3-3
+always 쓩 6-6-146-2356
+always 쓪 6-6-146-13
+always 쓫 6-6-146-23
+always 쓬 6-6-146-235
+always 쓭 6-6-146-236
+always 쓮 6-6-146-256
+always 쓯 6-6-146-356
+always 쓰 6-6-246
+always 쓱 6-6-246-1
+always 쓲 6-6-246-1-1
+always 쓳 6-6-246-1-3
+always 쓴 6-6-246-25
+always 쓵 6-6-246-25-13
+always 쓶 6-6-246-25-356
+always 쓷 6-6-246-35
+always 쓸 6-6-246-2
+always 쓹 6-6-246-2-1
+always 쓺 6-6-246-2-26
+always 쓻 6-6-246-2-12
+always 쓼 6-6-246-2-3
+always 쓽 6-6-246-2-236
+always 쓾 6-6-246-2-256
+always 쓿 6-6-246-2-356
+always 씀 6-6-246-26
+always 씁 6-6-246-12
+always 씂 6-6-246-12-3
+always 씃 6-6-246-3
+always 씄 6-6-246-3-3
+always 씅 6-6-246-2356
+always 씆 6-6-246-13
+always 씇 6-6-246-23
+always 씈 6-6-246-235
+always 씉 6-6-246-236
+always 씊 6-6-246-256
+always 씋 6-6-246-356
+always 씌 6-6-2456
+always 씍 6-6-2456-1
+always 씎 6-6-2456-1-1
+always 씏 6-6-2456-1-3
+always 씐 6-6-2456-25
+always 씑 6-6-2456-25-13
+always 씒 6-6-2456-25-356
+always 씓 6-6-2456-35
+always 씔 6-6-2456-2
+always 씕 6-6-2456-2-1
+always 씖 6-6-2456-2-26
+always 씗 6-6-2456-2-12
+always 씘 6-6-2456-2-3
+always 씙 6-6-2456-2-236
+always 씚 6-6-2456-2-256
+always 씛 6-6-2456-2-356
+always 씜 6-6-2456-26
+always 씝 6-6-2456-12
+always 씞 6-6-2456-12-3
+always 씟 6-6-2456-3
+always 씠 6-6-2456-3-3
+always 씡 6-6-2456-2356
+always 씢 6-6-2456-13
+always 씣 6-6-2456-23
+always 씤 6-6-2456-235
+always 씥 6-6-2456-236
+always 씦 6-6-2456-256
+always 씧 6-6-2456-356
+always 씨 6-6-135
+always 씩 6-6-135-1
+always 씪 6-6-135-1-1
+always 씫 6-6-135-1-3
+always 씬 6-6-135-25
+always 씭 6-6-135-25-13
+always 씮 6-6-135-25-356
+always 씯 6-6-135-35
+always 씰 6-6-135-2
+always 씱 6-6-135-2-1
+always 씲 6-6-135-2-26
+always 씳 6-6-135-2-12
+always 씴 6-6-135-2-3
+always 씵 6-6-135-2-236
+always 씶 6-6-135-2-256
+always 씷 6-6-135-2-356
+always 씸 6-6-135-26
+always 씹 6-6-135-12
+always 씺 6-6-135-12-3
+always 씻 6-6-135-3
+always 씼 6-6-135-3-3
+always 씽 6-6-135-2356
+always 씾 6-6-135-13
+always 씿 6-6-135-23
+always 앀 6-6-135-235
+always 앁 6-6-135-236
+always 앂 6-6-135-256
+always 앃 6-6-135-356
+always 아 126
+always 악 126-1
+always 앆 126-1-1
+always 앇 126-1-3
+always 안 126-25
+always 앉 126-25-13
+always 않 126-25-356
+always 앋 126-35
+always 알 126-2
+always 앍 126-2-1
+always 앎 126-2-26
+always 앏 126-2-12
+always 앐 126-2-3
+always 앑 126-2-236
+always 앒 126-2-256
+always 앓 126-2-356
+always 암 126-26
+always 압 126-12
+always 앖 126-12-3
+always 앗 126-3
+always 았 126-3-3
+always 앙 126-2356
+always 앚 126-13
+always 앛 126-23
+always 앜 126-235
+always 앝 126-236
+always 앞 126-256
+always 앟 126-356
+always 애 1235
+always 액 1235-1
+always 앢 1235-1-1
+always 앣 1235-1-3
+always 앤 1235-25
+always 앥 1235-25-13
+always 앦 1235-25-356
+always 앧 1235-35
+always 앨 1235-2
+always 앩 1235-2-1
+always 앪 1235-2-26
+always 앫 1235-2-12
+always 앬 1235-2-3
+always 앭 1235-2-236
+always 앮 1235-2-256
+always 앯 1235-2-356
+always 앰 1235-26
+always 앱 1235-12
+always 앲 1235-12-3
+always 앳 1235-3
+always 앴 1235-3-3
+always 앵 1235-2356
+always 앶 1235-13
+always 앷 1235-23
+always 앸 1235-235
+always 앹 1235-236
+always 앺 1235-256
+always 앻 1235-356
+always 야 345
+always 약 345-1
+always 앾 345-1-1
+always 앿 345-1-3
+always 얀 345-25
+always 얁 345-25-13
+always 얂 345-25-356
+always 얃 345-35
+always 얄 345-2
+always 얅 345-2-1
+always 얆 345-2-26
+always 얇 345-2-12
+always 얈 345-2-3
+always 얉 345-2-236
+always 얊 345-2-256
+always 얋 345-2-356
+always 얌 345-26
+always 얍 345-12
+always 얎 345-12-3
+always 얏 345-3
+always 얐 345-3-3
+always 양 345-2356
+always 얒 345-13
+always 얓 345-23
+always 얔 345-235
+always 얕 345-236
+always 얖 345-256
+always 얗 345-356
+always 얘 345-1235
+always 얙 345-1235-1
+always 얚 345-1235-1-1
+always 얛 345-1235-1-3
+always 얜 345-1235-25
+always 얝 345-1235-25-13
+always 얞 345-1235-25-356
+always 얟 345-1235-35
+always 얠 345-1235-2
+always 얡 345-1235-2-1
+always 얢 345-1235-2-26
+always 얣 345-1235-2-12
+always 얤 345-1235-2-3
+always 얥 345-1235-2-236
+always 얦 345-1235-2-256
+always 얧 345-1235-2-356
+always 얨 345-1235-26
+always 얩 345-1235-12
+always 얪 345-1235-12-3
+always 얫 345-1235-3
+always 얬 345-1235-3-3
+always 얭 345-1235-2356
+always 얮 345-1235-13
+always 얯 345-1235-23
+always 얰 345-1235-235
+always 얱 345-1235-236
+always 얲 345-1235-256
+always 얳 345-1235-356
+always 어 234
+always 억 234-1
+always 얶 234-1-1
+always 얷 234-1-3
+always 언 234-25
+always 얹 234-25-13
+always 얺 234-25-356
+always 얻 234-35
+always 얼 234-2
+always 얽 234-2-1
+always 얾 234-2-26
+always 얿 234-2-12
+always 엀 234-2-3
+always 엁 234-2-236
+always 엂 234-2-256
+always 엃 234-2-356
+always 엄 234-26
+always 업 234-12
+always 없 234-12-3
+always 엇 234-3
+always 었 234-3-3
+always 엉 234-2356
+always 엊 234-13
+always 엋 234-23
+always 엌 234-235
+always 엍 234-236
+always 엎 234-256
+always 엏 234-356
+always 에 1345
+always 엑 1345-1
+always 엒 1345-1-1
+always 엓 1345-1-3
+always 엔 1345-25
+always 엕 1345-25-13
+always 엖 1345-25-356
+always 엗 1345-35
+always 엘 1345-2
+always 엙 1345-2-1
+always 엚 1345-2-26
+always 엛 1345-2-12
+always 엜 1345-2-3
+always 엝 1345-2-236
+always 엞 1345-2-256
+always 엟 1345-2-356
+always 엠 1345-26
+always 엡 1345-12
+always 엢 1345-12-3
+always 엣 1345-3
+always 엤 1345-3-3
+always 엥 1345-2356
+always 엦 1345-13
+always 엧 1345-23
+always 엨 1345-235
+always 엩 1345-236
+always 엪 1345-256
+always 엫 1345-356
+always 여 156
+always 역 156-1
+always 엮 156-1-1
+always 엯 156-1-3
+always 연 156-25
+always 엱 156-25-13
+always 엲 156-25-356
+always 엳 156-35
+always 열 156-2
+always 엵 156-2-1
+always 엶 156-2-26
+always 엷 156-2-12
+always 엸 156-2-3
+always 엹 156-2-236
+always 엺 156-2-256
+always 엻 156-2-356
+always 염 156-26
+always 엽 156-12
+always 엾 156-12-3
+always 엿 156-3
+always 였 156-3-3
+always 영 156-2356
+always 옂 156-13
+always 옃 156-23
+always 옄 156-235
+always 옅 156-236
+always 옆 156-256
+always 옇 156-356
+always 예 34
+always 옉 34-1
+always 옊 34-1-1
+always 옋 34-1-3
+always 옌 34-25
+always 옍 34-25-13
+always 옎 34-25-356
+always 옏 34-35
+always 옐 34-2
+always 옑 34-2-1
+always 옒 34-2-26
+always 옓 34-2-12
+always 옔 34-2-3
+always 옕 34-2-236
+always 옖 34-2-256
+always 옗 34-2-356
+always 옘 34-26
+always 옙 34-12
+always 옚 34-12-3
+always 옛 34-3
+always 옜 34-3-3
+always 옝 34-2356
+always 옞 34-13
+always 옟 34-23
+always 옠 34-235
+always 옡 34-236
+always 옢 34-256
+always 옣 34-356
+always 오 136
+always 옥 136-1
+always 옦 136-1-1
+always 옧 136-1-3
+always 온 136-25
+always 옩 136-25-13
+always 옪 136-25-356
+always 옫 136-35
+always 올 136-2
+always 옭 136-2-1
+always 옮 136-2-26
+always 옯 136-2-12
+always 옰 136-2-3
+always 옱 136-2-236
+always 옲 136-2-256
+always 옳 136-2-356
+always 옴 136-26
+always 옵 136-12
+always 옶 136-12-3
+always 옷 136-3
+always 옸 136-3-3
+always 옹 136-2356
+always 옺 136-13
+always 옻 136-23
+always 옼 136-235
+always 옽 136-236
+always 옾 136-256
+always 옿 136-356
+always 와 1236
+always 왁 1236-1
+always 왂 1236-1-1
+always 왃 1236-1-3
+always 완 1236-25
+always 왅 1236-25-13
+always 왆 1236-25-356
+always 왇 1236-35
+always 왈 1236-2
+always 왉 1236-2-1
+always 왊 1236-2-26
+always 왋 1236-2-12
+always 왌 1236-2-3
+always 왍 1236-2-236
+always 왎 1236-2-256
+always 왏 1236-2-356
+always 왐 1236-26
+always 왑 1236-12
+always 왒 1236-12-3
+always 왓 1236-3
+always 왔 1236-3-3
+always 왕 1236-2356
+always 왖 1236-13
+always 왗 1236-23
+always 왘 1236-235
+always 왙 1236-236
+always 왚 1236-256
+always 왛 1236-356
+always 왜 1236-1235
+always 왝 1236-1235-1
+always 왞 1236-1235-1-1
+always 왟 1236-1235-1-3
+always 왠 1236-1235-25
+always 왡 1236-1235-25-13
+always 왢 1236-1235-25-356
+always 왣 1236-1235-35
+always 왤 1236-1235-2
+always 왥 1236-1235-2-1
+always 왦 1236-1235-2-26
+always 왧 1236-1235-2-12
+always 왨 1236-1235-2-3
+always 왩 1236-1235-2-236
+always 왪 1236-1235-2-256
+always 왫 1236-1235-2-356
+always 왬 1236-1235-26
+always 왭 1236-1235-12
+always 왮 1236-1235-12-3
+always 왯 1236-1235-3
+always 왰 1236-1235-3-3
+always 왱 1236-1235-2356
+always 왲 1236-1235-13
+always 왳 1236-1235-23
+always 왴 1236-1235-235
+always 왵 1236-1235-236
+always 왶 1236-1235-256
+always 왷 1236-1235-356
+always 외 13456
+always 왹 13456-1
+always 왺 13456-1-1
+always 왻 13456-1-3
+always 왼 13456-25
+always 왽 13456-25-13
+always 왾 13456-25-356
+always 왿 13456-35
+always 욀 13456-2
+always 욁 13456-2-1
+always 욂 13456-2-26
+always 욃 13456-2-12
+always 욄 13456-2-3
+always 욅 13456-2-236
+always 욆 13456-2-256
+always 욇 13456-2-356
+always 욈 13456-26
+always 욉 13456-12
+always 욊 13456-12-3
+always 욋 13456-3
+always 욌 13456-3-3
+always 욍 13456-2356
+always 욎 13456-13
+always 욏 13456-23
+always 욐 13456-235
+always 욑 13456-236
+always 욒 13456-256
+always 욓 13456-356
+always 요 346
+always 욕 346-1
+always 욖 346-1-1
+always 욗 346-1-3
+always 욘 346-25
+always 욙 346-25-13
+always 욚 346-25-356
+always 욛 346-35
+always 욜 346-2
+always 욝 346-2-1
+always 욞 346-2-26
+always 욟 346-2-12
+always 욠 346-2-3
+always 욡 346-2-236
+always 욢 346-2-256
+always 욣 346-2-356
+always 욤 346-26
+always 욥 346-12
+always 욦 346-12-3
+always 욧 346-3
+always 욨 346-3-3
+always 용 346-2356
+always 욪 346-13
+always 욫 346-23
+always 욬 346-235
+always 욭 346-236
+always 욮 346-256
+always 욯 346-356
+always 우 134
+always 욱 134-1
+always 욲 134-1-1
+always 욳 134-1-3
+always 운 134-25
+always 욵 134-25-13
+always 욶 134-25-356
+always 욷 134-35
+always 울 134-2
+always 욹 134-2-1
+always 욺 134-2-26
+always 욻 134-2-12
+always 욼 134-2-3
+always 욽 134-2-236
+always 욾 134-2-256
+always 욿 134-2-356
+always 움 134-26
+always 웁 134-12
+always 웂 134-12-3
+always 웃 134-3
+always 웄 134-3-3
+always 웅 134-2356
+always 웆 134-13
+always 웇 134-23
+always 웈 134-235
+always 웉 134-236
+always 웊 134-256
+always 웋 134-356
+always 워 1234
+always 웍 1234-1
+always 웎 1234-1-1
+always 웏 1234-1-3
+always 원 1234-25
+always 웑 1234-25-13
+always 웒 1234-25-356
+always 웓 1234-35
+always 월 1234-2
+always 웕 1234-2-1
+always 웖 1234-2-26
+always 웗 1234-2-12
+always 웘 1234-2-3
+always 웙 1234-2-236
+always 웚 1234-2-256
+always 웛 1234-2-356
+always 웜 1234-26
+always 웝 1234-12
+always 웞 1234-12-3
+always 웟 1234-3
+always 웠 1234-3-3
+always 웡 1234-2356
+always 웢 1234-13
+always 웣 1234-23
+always 웤 1234-235
+always 웥 1234-236
+always 웦 1234-256
+always 웧 1234-356
+always 웨 1234-1235
+always 웩 1234-1235-1
+always 웪 1234-1235-1-1
+always 웫 1234-1235-1-3
+always 웬 1234-1235-25
+always 웭 1234-1235-25-13
+always 웮 1234-1235-25-356
+always 웯 1234-1235-35
+always 웰 1234-1235-2
+always 웱 1234-1235-2-1
+always 웲 1234-1235-2-26
+always 웳 1234-1235-2-12
+always 웴 1234-1235-2-3
+always 웵 1234-1235-2-236
+always 웶 1234-1235-2-256
+always 웷 1234-1235-2-356
+always 웸 1234-1235-26
+always 웹 1234-1235-12
+always 웺 1234-1235-12-3
+always 웻 1234-1235-3
+always 웼 1234-1235-3-3
+always 웽 1234-1235-2356
+always 웾 1234-1235-13
+always 웿 1234-1235-23
+always 윀 1234-1235-235
+always 윁 1234-1235-236
+always 윂 1234-1235-256
+always 윃 1234-1235-356
+always 위 134-1235
+always 윅 134-1235-1
+always 윆 134-1235-1-1
+always 윇 134-1235-1-3
+always 윈 134-1235-25
+always 윉 134-1235-25-13
+always 윊 134-1235-25-356
+always 윋 134-1235-35
+always 윌 134-1235-2
+always 윍 134-1235-2-1
+always 윎 134-1235-2-26
+always 윏 134-1235-2-12
+always 윐 134-1235-2-3
+always 윑 134-1235-2-236
+always 윒 134-1235-2-256
+always 윓 134-1235-2-356
+always 윔 134-1235-26
+always 윕 134-1235-12
+always 윖 134-1235-12-3
+always 윗 134-1235-3
+always 윘 134-1235-3-3
+always 윙 134-1235-2356
+always 윚 134-1235-13
+always 윛 134-1235-23
+always 윜 134-1235-235
+always 윝 134-1235-236
+always 윞 134-1235-256
+always 윟 134-1235-356
+always 유 146
+always 육 146-1
+always 윢 146-1-1
+always 윣 146-1-3
+always 윤 146-25
+always 윥 146-25-13
+always 윦 146-25-356
+always 윧 146-35
+always 율 146-2
+always 윩 146-2-1
+always 윪 146-2-26
+always 윫 146-2-12
+always 윬 146-2-3
+always 윭 146-2-236
+always 윮 146-2-256
+always 윯 146-2-356
+always 윰 146-26
+always 윱 146-12
+always 윲 146-12-3
+always 윳 146-3
+always 윴 146-3-3
+always 융 146-2356
+always 윶 146-13
+always 윷 146-23
+always 윸 146-235
+always 윹 146-236
+always 윺 146-256
+always 윻 146-356
+always 으 246
+always 윽 246-1
+always 윾 246-1-1
+always 윿 246-1-3
+always 은 246-25
+always 읁 246-25-13
+always 읂 246-25-356
+always 읃 246-35
+always 을 246-2
+always 읅 246-2-1
+always 읆 246-2-26
+always 읇 246-2-12
+always 읈 246-2-3
+always 읉 246-2-236
+always 읊 246-2-256
+always 읋 246-2-356
+always 음 246-26
+always 읍 246-12
+always 읎 246-12-3
+always 읏 246-3
+always 읐 246-3-3
+always 응 246-2356
+always 읒 246-13
+always 읓 246-23
+always 읔 246-235
+always 읕 246-236
+always 읖 246-256
+always 읗 246-356
+always 의 2456
+always 읙 2456-1
+always 읚 2456-1-1
+always 읛 2456-1-3
+always 읜 2456-25
+always 읝 2456-25-13
+always 읞 2456-25-356
+always 읟 2456-35
+always 읠 2456-2
+always 읡 2456-2-1
+always 읢 2456-2-26
+always 읣 2456-2-12
+always 읤 2456-2-3
+always 읥 2456-2-236
+always 읦 2456-2-256
+always 읧 2456-2-356
+always 읨 2456-26
+always 읩 2456-12
+always 읪 2456-12-3
+always 읫 2456-3
+always 읬 2456-3-3
+always 읭 2456-2356
+always 읮 2456-13
+always 읯 2456-23
+always 읰 2456-235
+always 읱 2456-236
+always 읲 2456-256
+always 읳 2456-356
+always 이 135
+always 익 135-1
+always 읶 135-1-1
+always 읷 135-1-3
+always 인 135-25
+always 읹 135-25-13
+always 읺 135-25-356
+always 읻 135-35
+always 일 135-2
+always 읽 135-2-1
+always 읾 135-2-26
+always 읿 135-2-12
+always 잀 135-2-3
+always 잁 135-2-236
+always 잂 135-2-256
+always 잃 135-2-356
+always 임 135-26
+always 입 135-12
+always 잆 135-12-3
+always 잇 135-3
+always 있 135-3-3
+always 잉 135-2356
+always 잊 135-13
+always 잋 135-23
+always 잌 135-235
+always 잍 135-236
+always 잎 135-256
+always 잏 135-356
+always 자 46-126
+always 작 46-126-1
+always 잒 46-126-1-1
+always 잓 46-126-1-3
+always 잔 46-126-25
+always 잕 46-126-25-13
+always 잖 46-126-25-356
+always 잗 46-126-35
+always 잘 46-126-2
+always 잙 46-126-2-1
+always 잚 46-126-2-26
+always 잛 46-126-2-12
+always 잜 46-126-2-3
+always 잝 46-126-2-236
+always 잞 46-126-2-256
+always 잟 46-126-2-356
+always 잠 46-126-26
+always 잡 46-126-12
+always 잢 46-126-12-3
+always 잣 46-126-3
+always 잤 46-126-3-3
+always 장 46-126-2356
+always 잦 46-126-13
+always 잧 46-126-23
+always 잨 46-126-235
+always 잩 46-126-236
+always 잪 46-126-256
+always 잫 46-126-356
+always 재 46-1235
+always 잭 46-1235-1
+always 잮 46-1235-1-1
+always 잯 46-1235-1-3
+always 잰 46-1235-25
+always 잱 46-1235-25-13
+always 잲 46-1235-25-356
+always 잳 46-1235-35
+always 잴 46-1235-2
+always 잵 46-1235-2-1
+always 잶 46-1235-2-26
+always 잷 46-1235-2-12
+always 잸 46-1235-2-3
+always 잹 46-1235-2-236
+always 잺 46-1235-2-256
+always 잻 46-1235-2-356
+always 잼 46-1235-26
+always 잽 46-1235-12
+always 잾 46-1235-12-3
+always 잿 46-1235-3
+always 쟀 46-1235-3-3
+always 쟁 46-1235-2356
+always 쟂 46-1235-13
+always 쟃 46-1235-23
+always 쟄 46-1235-235
+always 쟅 46-1235-236
+always 쟆 46-1235-256
+always 쟇 46-1235-356
+always 쟈 46-345
+always 쟉 46-345-1
+always 쟊 46-345-1-1
+always 쟋 46-345-1-3
+always 쟌 46-345-25
+always 쟍 46-345-25-13
+always 쟎 46-345-25-356
+always 쟏 46-345-35
+always 쟐 46-345-2
+always 쟑 46-345-2-1
+always 쟒 46-345-2-26
+always 쟓 46-345-2-12
+always 쟔 46-345-2-3
+always 쟕 46-345-2-236
+always 쟖 46-345-2-256
+always 쟗 46-345-2-356
+always 쟘 46-345-26
+always 쟙 46-345-12
+always 쟚 46-345-12-3
+always 쟛 46-345-3
+always 쟜 46-345-3-3
+always 쟝 46-345-2356
+always 쟞 46-345-13
+always 쟟 46-345-23
+always 쟠 46-345-235
+always 쟡 46-345-236
+always 쟢 46-345-256
+always 쟣 46-345-356
+always 쟤 46-345-1235
+always 쟥 46-345-1235-1
+always 쟦 46-345-1235-1-1
+always 쟧 46-345-1235-1-3
+always 쟨 46-345-1235-25
+always 쟩 46-345-1235-25-13
+always 쟪 46-345-1235-25-356
+always 쟫 46-345-1235-35
+always 쟬 46-345-1235-2
+always 쟭 46-345-1235-2-1
+always 쟮 46-345-1235-2-26
+always 쟯 46-345-1235-2-12
+always 쟰 46-345-1235-2-3
+always 쟱 46-345-1235-2-236
+always 쟲 46-345-1235-2-256
+always 쟳 46-345-1235-2-356
+always 쟴 46-345-1235-26
+always 쟵 46-345-1235-12
+always 쟶 46-345-1235-12-3
+always 쟷 46-345-1235-3
+always 쟸 46-345-1235-3-3
+always 쟹 46-345-1235-2356
+always 쟺 46-345-1235-13
+always 쟻 46-345-1235-23
+always 쟼 46-345-1235-235
+always 쟽 46-345-1235-236
+always 쟾 46-345-1235-256
+always 쟿 46-345-1235-356
+always 저 46-234
+always 적 46-234-1
+always 젂 46-234-1-1
+always 젃 46-234-1-3
+always 전 46-234-25
+always 젅 46-234-25-13
+always 젆 46-234-25-356
+always 젇 46-234-35
+always 절 46-234-2
+always 젉 46-234-2-1
+always 젊 46-234-2-26
+always 젋 46-234-2-12
+always 젌 46-234-2-3
+always 젍 46-234-2-236
+always 젎 46-234-2-256
+always 젏 46-234-2-356
+always 점 46-234-26
+always 접 46-234-12
+always 젒 46-234-12-3
+always 젓 46-234-3
+always 젔 46-234-3-3
+always 정 46-234-2356
+always 젖 46-234-13
+always 젗 46-234-23
+always 젘 46-234-235
+always 젙 46-234-236
+always 젚 46-234-256
+always 젛 46-234-356
+always 제 46-1345
+always 젝 46-1345-1
+always 젞 46-1345-1-1
+always 젟 46-1345-1-3
+always 젠 46-1345-25
+always 젡 46-1345-25-13
+always 젢 46-1345-25-356
+always 젣 46-1345-35
+always 젤 46-1345-2
+always 젥 46-1345-2-1
+always 젦 46-1345-2-26
+always 젧 46-1345-2-12
+always 젨 46-1345-2-3
+always 젩 46-1345-2-236
+always 젪 46-1345-2-256
+always 젫 46-1345-2-356
+always 젬 46-1345-26
+always 젭 46-1345-12
+always 젮 46-1345-12-3
+always 젯 46-1345-3
+always 젰 46-1345-3-3
+always 젱 46-1345-2356
+always 젲 46-1345-13
+always 젳 46-1345-23
+always 젴 46-1345-235
+always 젵 46-1345-236
+always 젶 46-1345-256
+always 젷 46-1345-356
+always 져 46-156
+always 젹 46-156-1
+always 젺 46-156-1-1
+always 젻 46-156-1-3
+always 젼 46-156-25
+always 젽 46-156-25-13
+always 젾 46-156-25-356
+always 젿 46-156-35
+always 졀 46-156-2
+always 졁 46-156-2-1
+always 졂 46-156-2-26
+always 졃 46-156-2-12
+always 졄 46-156-2-3
+always 졅 46-156-2-236
+always 졆 46-156-2-256
+always 졇 46-156-2-356
+always 졈 46-156-26
+always 졉 46-156-12
+always 졊 46-156-12-3
+always 졋 46-156-3
+always 졌 46-156-3-3
+always 졍 46-156-2356
+always 졎 46-156-13
+always 졏 46-156-23
+always 졐 46-156-235
+always 졑 46-156-236
+always 졒 46-156-256
+always 졓 46-156-356
+always 졔 46-34
+always 졕 46-34-1
+always 졖 46-34-1-1
+always 졗 46-34-1-3
+always 졘 46-34-25
+always 졙 46-34-25-13
+always 졚 46-34-25-356
+always 졛 46-34-35
+always 졜 46-34-2
+always 졝 46-34-2-1
+always 졞 46-34-2-26
+always 졟 46-34-2-12
+always 졠 46-34-2-3
+always 졡 46-34-2-236
+always 졢 46-34-2-256
+always 졣 46-34-2-356
+always 졤 46-34-26
+always 졥 46-34-12
+always 졦 46-34-12-3
+always 졧 46-34-3
+always 졨 46-34-3-3
+always 졩 46-34-2356
+always 졪 46-34-13
+always 졫 46-34-23
+always 졬 46-34-235
+always 졭 46-34-236
+always 졮 46-34-256
+always 졯 46-34-356
+always 조 46-136
+always 족 46-136-1
+always 졲 46-136-1-1
+always 졳 46-136-1-3
+always 존 46-136-25
+always 졵 46-136-25-13
+always 졶 46-136-25-356
+always 졷 46-136-35
+always 졸 46-136-2
+always 졹 46-136-2-1
+always 졺 46-136-2-26
+always 졻 46-136-2-12
+always 졼 46-136-2-3
+always 졽 46-136-2-236
+always 졾 46-136-2-256
+always 졿 46-136-2-356
+always 좀 46-136-26
+always 좁 46-136-12
+always 좂 46-136-12-3
+always 좃 46-136-3
+always 좄 46-136-3-3
+always 종 46-136-2356
+always 좆 46-136-13
+always 좇 46-136-23
+always 좈 46-136-235
+always 좉 46-136-236
+always 좊 46-136-256
+always 좋 46-136-356
+always 좌 46-1236
+always 좍 46-1236-1
+always 좎 46-1236-1-1
+always 좏 46-1236-1-3
+always 좐 46-1236-25
+always 좑 46-1236-25-13
+always 좒 46-1236-25-356
+always 좓 46-1236-35
+always 좔 46-1236-2
+always 좕 46-1236-2-1
+always 좖 46-1236-2-26
+always 좗 46-1236-2-12
+always 좘 46-1236-2-3
+always 좙 46-1236-2-236
+always 좚 46-1236-2-256
+always 좛 46-1236-2-356
+always 좜 46-1236-26
+always 좝 46-1236-12
+always 좞 46-1236-12-3
+always 좟 46-1236-3
+always 좠 46-1236-3-3
+always 좡 46-1236-2356
+always 좢 46-1236-13
+always 좣 46-1236-23
+always 좤 46-1236-235
+always 좥 46-1236-236
+always 좦 46-1236-256
+always 좧 46-1236-356
+always 좨 46-1236-1235
+always 좩 46-1236-1235-1
+always 좪 46-1236-1235-1-1
+always 좫 46-1236-1235-1-3
+always 좬 46-1236-1235-25
+always 좭 46-1236-1235-25-13
+always 좮 46-1236-1235-25-356
+always 좯 46-1236-1235-35
+always 좰 46-1236-1235-2
+always 좱 46-1236-1235-2-1
+always 좲 46-1236-1235-2-26
+always 좳 46-1236-1235-2-12
+always 좴 46-1236-1235-2-3
+always 좵 46-1236-1235-2-236
+always 좶 46-1236-1235-2-256
+always 좷 46-1236-1235-2-356
+always 좸 46-1236-1235-26
+always 좹 46-1236-1235-12
+always 좺 46-1236-1235-12-3
+always 좻 46-1236-1235-3
+always 좼 46-1236-1235-3-3
+always 좽 46-1236-1235-2356
+always 좾 46-1236-1235-13
+always 좿 46-1236-1235-23
+always 죀 46-1236-1235-235
+always 죁 46-1236-1235-236
+always 죂 46-1236-1235-256
+always 죃 46-1236-1235-356
+always 죄 46-13456
+always 죅 46-13456-1
+always 죆 46-13456-1-1
+always 죇 46-13456-1-3
+always 죈 46-13456-25
+always 죉 46-13456-25-13
+always 죊 46-13456-25-356
+always 죋 46-13456-35
+always 죌 46-13456-2
+always 죍 46-13456-2-1
+always 죎 46-13456-2-26
+always 죏 46-13456-2-12
+always 죐 46-13456-2-3
+always 죑 46-13456-2-236
+always 죒 46-13456-2-256
+always 죓 46-13456-2-356
+always 죔 46-13456-26
+always 죕 46-13456-12
+always 죖 46-13456-12-3
+always 죗 46-13456-3
+always 죘 46-13456-3-3
+always 죙 46-13456-2356
+always 죚 46-13456-13
+always 죛 46-13456-23
+always 죜 46-13456-235
+always 죝 46-13456-236
+always 죞 46-13456-256
+always 죟 46-13456-356
+always 죠 46-346
+always 죡 46-346-1
+always 죢 46-346-1-1
+always 죣 46-346-1-3
+always 죤 46-346-25
+always 죥 46-346-25-13
+always 죦 46-346-25-356
+always 죧 46-346-35
+always 죨 46-346-2
+always 죩 46-346-2-1
+always 죪 46-346-2-26
+always 죫 46-346-2-12
+always 죬 46-346-2-3
+always 죭 46-346-2-236
+always 죮 46-346-2-256
+always 죯 46-346-2-356
+always 죰 46-346-26
+always 죱 46-346-12
+always 죲 46-346-12-3
+always 죳 46-346-3
+always 죴 46-346-3-3
+always 죵 46-346-2356
+always 죶 46-346-13
+always 죷 46-346-23
+always 죸 46-346-235
+always 죹 46-346-236
+always 죺 46-346-256
+always 죻 46-346-356
+always 주 46-134
+always 죽 46-134-1
+always 죾 46-134-1-1
+always 죿 46-134-1-3
+always 준 46-134-25
+always 줁 46-134-25-13
+always 줂 46-134-25-356
+always 줃 46-134-35
+always 줄 46-134-2
+always 줅 46-134-2-1
+always 줆 46-134-2-26
+always 줇 46-134-2-12
+always 줈 46-134-2-3
+always 줉 46-134-2-236
+always 줊 46-134-2-256
+always 줋 46-134-2-356
+always 줌 46-134-26
+always 줍 46-134-12
+always 줎 46-134-12-3
+always 줏 46-134-3
+always 줐 46-134-3-3
+always 중 46-134-2356
+always 줒 46-134-13
+always 줓 46-134-23
+always 줔 46-134-235
+always 줕 46-134-236
+always 줖 46-134-256
+always 줗 46-134-356
+always 줘 46-1234
+always 줙 46-1234-1
+always 줚 46-1234-1-1
+always 줛 46-1234-1-3
+always 줜 46-1234-25
+always 줝 46-1234-25-13
+always 줞 46-1234-25-356
+always 줟 46-1234-35
+always 줠 46-1234-2
+always 줡 46-1234-2-1
+always 줢 46-1234-2-26
+always 줣 46-1234-2-12
+always 줤 46-1234-2-3
+always 줥 46-1234-2-236
+always 줦 46-1234-2-256
+always 줧 46-1234-2-356
+always 줨 46-1234-26
+always 줩 46-1234-12
+always 줪 46-1234-12-3
+always 줫 46-1234-3
+always 줬 46-1234-3-3
+always 줭 46-1234-2356
+always 줮 46-1234-13
+always 줯 46-1234-23
+always 줰 46-1234-235
+always 줱 46-1234-236
+always 줲 46-1234-256
+always 줳 46-1234-356
+always 줴 46-1234-1235
+always 줵 46-1234-1235-1
+always 줶 46-1234-1235-1-1
+always 줷 46-1234-1235-1-3
+always 줸 46-1234-1235-25
+always 줹 46-1234-1235-25-13
+always 줺 46-1234-1235-25-356
+always 줻 46-1234-1235-35
+always 줼 46-1234-1235-2
+always 줽 46-1234-1235-2-1
+always 줾 46-1234-1235-2-26
+always 줿 46-1234-1235-2-12
+always 쥀 46-1234-1235-2-3
+always 쥁 46-1234-1235-2-236
+always 쥂 46-1234-1235-2-256
+always 쥃 46-1234-1235-2-356
+always 쥄 46-1234-1235-26
+always 쥅 46-1234-1235-12
+always 쥆 46-1234-1235-12-3
+always 쥇 46-1234-1235-3
+always 쥈 46-1234-1235-3-3
+always 쥉 46-1234-1235-2356
+always 쥊 46-1234-1235-13
+always 쥋 46-1234-1235-23
+always 쥌 46-1234-1235-235
+always 쥍 46-1234-1235-236
+always 쥎 46-1234-1235-256
+always 쥏 46-1234-1235-356
+always 쥐 46-134-1235
+always 쥑 46-134-1235-1
+always 쥒 46-134-1235-1-1
+always 쥓 46-134-1235-1-3
+always 쥔 46-134-1235-25
+always 쥕 46-134-1235-25-13
+always 쥖 46-134-1235-25-356
+always 쥗 46-134-1235-35
+always 쥘 46-134-1235-2
+always 쥙 46-134-1235-2-1
+always 쥚 46-134-1235-2-26
+always 쥛 46-134-1235-2-12
+always 쥜 46-134-1235-2-3
+always 쥝 46-134-1235-2-236
+always 쥞 46-134-1235-2-256
+always 쥟 46-134-1235-2-356
+always 쥠 46-134-1235-26
+always 쥡 46-134-1235-12
+always 쥢 46-134-1235-12-3
+always 쥣 46-134-1235-3
+always 쥤 46-134-1235-3-3
+always 쥥 46-134-1235-2356
+always 쥦 46-134-1235-13
+always 쥧 46-134-1235-23
+always 쥨 46-134-1235-235
+always 쥩 46-134-1235-236
+always 쥪 46-134-1235-256
+always 쥫 46-134-1235-356
+always 쥬 46-146
+always 쥭 46-146-1
+always 쥮 46-146-1-1
+always 쥯 46-146-1-3
+always 쥰 46-146-25
+always 쥱 46-146-25-13
+always 쥲 46-146-25-356
+always 쥳 46-146-35
+always 쥴 46-146-2
+always 쥵 46-146-2-1
+always 쥶 46-146-2-26
+always 쥷 46-146-2-12
+always 쥸 46-146-2-3
+always 쥹 46-146-2-236
+always 쥺 46-146-2-256
+always 쥻 46-146-2-356
+always 쥼 46-146-26
+always 쥽 46-146-12
+always 쥾 46-146-12-3
+always 쥿 46-146-3
+always 즀 46-146-3-3
+always 즁 46-146-2356
+always 즂 46-146-13
+always 즃 46-146-23
+always 즄 46-146-235
+always 즅 46-146-236
+always 즆 46-146-256
+always 즇 46-146-356
+always 즈 46-246
+always 즉 46-246-1
+always 즊 46-246-1-1
+always 즋 46-246-1-3
+always 즌 46-246-25
+always 즍 46-246-25-13
+always 즎 46-246-25-356
+always 즏 46-246-35
+always 즐 46-246-2
+always 즑 46-246-2-1
+always 즒 46-246-2-26
+always 즓 46-246-2-12
+always 즔 46-246-2-3
+always 즕 46-246-2-236
+always 즖 46-246-2-256
+always 즗 46-246-2-356
+always 즘 46-246-26
+always 즙 46-246-12
+always 즚 46-246-12-3
+always 즛 46-246-3
+always 즜 46-246-3-3
+always 증 46-246-2356
+always 즞 46-246-13
+always 즟 46-246-23
+always 즠 46-246-235
+always 즡 46-246-236
+always 즢 46-246-256
+always 즣 46-246-356
+always 즤 46-2456
+always 즥 46-2456-1
+always 즦 46-2456-1-1
+always 즧 46-2456-1-3
+always 즨 46-2456-25
+always 즩 46-2456-25-13
+always 즪 46-2456-25-356
+always 즫 46-2456-35
+always 즬 46-2456-2
+always 즭 46-2456-2-1
+always 즮 46-2456-2-26
+always 즯 46-2456-2-12
+always 즰 46-2456-2-3
+always 즱 46-2456-2-236
+always 즲 46-2456-2-256
+always 즳 46-2456-2-356
+always 즴 46-2456-26
+always 즵 46-2456-12
+always 즶 46-2456-12-3
+always 즷 46-2456-3
+always 즸 46-2456-3-3
+always 즹 46-2456-2356
+always 즺 46-2456-13
+always 즻 46-2456-23
+always 즼 46-2456-235
+always 즽 46-2456-236
+always 즾 46-2456-256
+always 즿 46-2456-356
+always 지 46-135
+always 직 46-135-1
+always 짂 46-135-1-1
+always 짃 46-135-1-3
+always 진 46-135-25
+always 짅 46-135-25-13
+always 짆 46-135-25-356
+always 짇 46-135-35
+always 질 46-135-2
+always 짉 46-135-2-1
+always 짊 46-135-2-26
+always 짋 46-135-2-12
+always 짌 46-135-2-3
+always 짍 46-135-2-236
+always 짎 46-135-2-256
+always 짏 46-135-2-356
+always 짐 46-135-26
+always 집 46-135-12
+always 짒 46-135-12-3
+always 짓 46-135-3
+always 짔 46-135-3-3
+always 징 46-135-2356
+always 짖 46-135-13
+always 짗 46-135-23
+always 짘 46-135-235
+always 짙 46-135-236
+always 짚 46-135-256
+always 짛 46-135-356
+always 짜 46-46-126
+always 짝 46-46-126-1
+always 짞 46-46-126-1-1
+always 짟 46-46-126-1-3
+always 짠 46-46-126-25
+always 짡 46-46-126-25-13
+always 짢 46-46-126-25-356
+always 짣 46-46-126-35
+always 짤 46-46-126-2
+always 짥 46-46-126-2-1
+always 짦 46-46-126-2-26
+always 짧 46-46-126-2-12
+always 짨 46-46-126-2-3
+always 짩 46-46-126-2-236
+always 짪 46-46-126-2-256
+always 짫 46-46-126-2-356
+always 짬 46-46-126-26
+always 짭 46-46-126-12
+always 짮 46-46-126-12-3
+always 짯 46-46-126-3
+always 짰 46-46-126-3-3
+always 짱 46-46-126-2356
+always 짲 46-46-126-13
+always 짳 46-46-126-23
+always 짴 46-46-126-235
+always 짵 46-46-126-236
+always 짶 46-46-126-256
+always 짷 46-46-126-356
+always 째 46-46-1235
+always 짹 46-46-1235-1
+always 짺 46-46-1235-1-1
+always 짻 46-46-1235-1-3
+always 짼 46-46-1235-25
+always 짽 46-46-1235-25-13
+always 짾 46-46-1235-25-356
+always 짿 46-46-1235-35
+always 쨀 46-46-1235-2
+always 쨁 46-46-1235-2-1
+always 쨂 46-46-1235-2-26
+always 쨃 46-46-1235-2-12
+always 쨄 46-46-1235-2-3
+always 쨅 46-46-1235-2-236
+always 쨆 46-46-1235-2-256
+always 쨇 46-46-1235-2-356
+always 쨈 46-46-1235-26
+always 쨉 46-46-1235-12
+always 쨊 46-46-1235-12-3
+always 쨋 46-46-1235-3
+always 쨌 46-46-1235-3-3
+always 쨍 46-46-1235-2356
+always 쨎 46-46-1235-13
+always 쨏 46-46-1235-23
+always 쨐 46-46-1235-235
+always 쨑 46-46-1235-236
+always 쨒 46-46-1235-256
+always 쨓 46-46-1235-356
+always 쨔 46-46-345
+always 쨕 46-46-345-1
+always 쨖 46-46-345-1-1
+always 쨗 46-46-345-1-3
+always 쨘 46-46-345-25
+always 쨙 46-46-345-25-13
+always 쨚 46-46-345-25-356
+always 쨛 46-46-345-35
+always 쨜 46-46-345-2
+always 쨝 46-46-345-2-1
+always 쨞 46-46-345-2-26
+always 쨟 46-46-345-2-12
+always 쨠 46-46-345-2-3
+always 쨡 46-46-345-2-236
+always 쨢 46-46-345-2-256
+always 쨣 46-46-345-2-356
+always 쨤 46-46-345-26
+always 쨥 46-46-345-12
+always 쨦 46-46-345-12-3
+always 쨧 46-46-345-3
+always 쨨 46-46-345-3-3
+always 쨩 46-46-345-2356
+always 쨪 46-46-345-13
+always 쨫 46-46-345-23
+always 쨬 46-46-345-235
+always 쨭 46-46-345-236
+always 쨮 46-46-345-256
+always 쨯 46-46-345-356
+always 쨰 46-46-345-1235
+always 쨱 46-46-345-1235-1
+always 쨲 46-46-345-1235-1-1
+always 쨳 46-46-345-1235-1-3
+always 쨴 46-46-345-1235-25
+always 쨵 46-46-345-1235-25-13
+always 쨶 46-46-345-1235-25-356
+always 쨷 46-46-345-1235-35
+always 쨸 46-46-345-1235-2
+always 쨹 46-46-345-1235-2-1
+always 쨺 46-46-345-1235-2-26
+always 쨻 46-46-345-1235-2-12
+always 쨼 46-46-345-1235-2-3
+always 쨽 46-46-345-1235-2-236
+always 쨾 46-46-345-1235-2-256
+always 쨿 46-46-345-1235-2-356
+always 쩀 46-46-345-1235-26
+always 쩁 46-46-345-1235-12
+always 쩂 46-46-345-1235-12-3
+always 쩃 46-46-345-1235-3
+always 쩄 46-46-345-1235-3-3
+always 쩅 46-46-345-1235-2356
+always 쩆 46-46-345-1235-13
+always 쩇 46-46-345-1235-23
+always 쩈 46-46-345-1235-235
+always 쩉 46-46-345-1235-236
+always 쩊 46-46-345-1235-256
+always 쩋 46-46-345-1235-356
+always 쩌 46-46-234
+always 쩍 46-46-234-1
+always 쩎 46-46-234-1-1
+always 쩏 46-46-234-1-3
+always 쩐 46-46-234-25
+always 쩑 46-46-234-25-13
+always 쩒 46-46-234-25-356
+always 쩓 46-46-234-35
+always 쩔 46-46-234-2
+always 쩕 46-46-234-2-1
+always 쩖 46-46-234-2-26
+always 쩗 46-46-234-2-12
+always 쩘 46-46-234-2-3
+always 쩙 46-46-234-2-236
+always 쩚 46-46-234-2-256
+always 쩛 46-46-234-2-356
+always 쩜 46-46-234-26
+always 쩝 46-46-234-12
+always 쩞 46-46-234-12-3
+always 쩟 46-46-234-3
+always 쩠 46-46-234-3-3
+always 쩡 46-46-234-2356
+always 쩢 46-46-234-13
+always 쩣 46-46-234-23
+always 쩤 46-46-234-235
+always 쩥 46-46-234-236
+always 쩦 46-46-234-256
+always 쩧 46-46-234-356
+always 쩨 46-46-1345
+always 쩩 46-46-1345-1
+always 쩪 46-46-1345-1-1
+always 쩫 46-46-1345-1-3
+always 쩬 46-46-1345-25
+always 쩭 46-46-1345-25-13
+always 쩮 46-46-1345-25-356
+always 쩯 46-46-1345-35
+always 쩰 46-46-1345-2
+always 쩱 46-46-1345-2-1
+always 쩲 46-46-1345-2-26
+always 쩳 46-46-1345-2-12
+always 쩴 46-46-1345-2-3
+always 쩵 46-46-1345-2-236
+always 쩶 46-46-1345-2-256
+always 쩷 46-46-1345-2-356
+always 쩸 46-46-1345-26
+always 쩹 46-46-1345-12
+always 쩺 46-46-1345-12-3
+always 쩻 46-46-1345-3
+always 쩼 46-46-1345-3-3
+always 쩽 46-46-1345-2356
+always 쩾 46-46-1345-13
+always 쩿 46-46-1345-23
+always 쪀 46-46-1345-235
+always 쪁 46-46-1345-236
+always 쪂 46-46-1345-256
+always 쪃 46-46-1345-356
+always 쪄 46-46-156
+always 쪅 46-46-156-1
+always 쪆 46-46-156-1-1
+always 쪇 46-46-156-1-3
+always 쪈 46-46-156-25
+always 쪉 46-46-156-25-13
+always 쪊 46-46-156-25-356
+always 쪋 46-46-156-35
+always 쪌 46-46-156-2
+always 쪍 46-46-156-2-1
+always 쪎 46-46-156-2-26
+always 쪏 46-46-156-2-12
+always 쪐 46-46-156-2-3
+always 쪑 46-46-156-2-236
+always 쪒 46-46-156-2-256
+always 쪓 46-46-156-2-356
+always 쪔 46-46-156-26
+always 쪕 46-46-156-12
+always 쪖 46-46-156-12-3
+always 쪗 46-46-156-3
+always 쪘 46-46-156-3-3
+always 쪙 46-46-156-2356
+always 쪚 46-46-156-13
+always 쪛 46-46-156-23
+always 쪜 46-46-156-235
+always 쪝 46-46-156-236
+always 쪞 46-46-156-256
+always 쪟 46-46-156-356
+always 쪠 46-46-34
+always 쪡 46-46-34-1
+always 쪢 46-46-34-1-1
+always 쪣 46-46-34-1-3
+always 쪤 46-46-34-25
+always 쪥 46-46-34-25-13
+always 쪦 46-46-34-25-356
+always 쪧 46-46-34-35
+always 쪨 46-46-34-2
+always 쪩 46-46-34-2-1
+always 쪪 46-46-34-2-26
+always 쪫 46-46-34-2-12
+always 쪬 46-46-34-2-3
+always 쪭 46-46-34-2-236
+always 쪮 46-46-34-2-256
+always 쪯 46-46-34-2-356
+always 쪰 46-46-34-26
+always 쪱 46-46-34-12
+always 쪲 46-46-34-12-3
+always 쪳 46-46-34-3
+always 쪴 46-46-34-3-3
+always 쪵 46-46-34-2356
+always 쪶 46-46-34-13
+always 쪷 46-46-34-23
+always 쪸 46-46-34-235
+always 쪹 46-46-34-236
+always 쪺 46-46-34-256
+always 쪻 46-46-34-356
+always 쪼 46-46-136
+always 쪽 46-46-136-1
+always 쪾 46-46-136-1-1
+always 쪿 46-46-136-1-3
+always 쫀 46-46-136-25
+always 쫁 46-46-136-25-13
+always 쫂 46-46-136-25-356
+always 쫃 46-46-136-35
+always 쫄 46-46-136-2
+always 쫅 46-46-136-2-1
+always 쫆 46-46-136-2-26
+always 쫇 46-46-136-2-12
+always 쫈 46-46-136-2-3
+always 쫉 46-46-136-2-236
+always 쫊 46-46-136-2-256
+always 쫋 46-46-136-2-356
+always 쫌 46-46-136-26
+always 쫍 46-46-136-12
+always 쫎 46-46-136-12-3
+always 쫏 46-46-136-3
+always 쫐 46-46-136-3-3
+always 쫑 46-46-136-2356
+always 쫒 46-46-136-13
+always 쫓 46-46-136-23
+always 쫔 46-46-136-235
+always 쫕 46-46-136-236
+always 쫖 46-46-136-256
+always 쫗 46-46-136-356
+always 쫘 46-46-1236
+always 쫙 46-46-1236-1
+always 쫚 46-46-1236-1-1
+always 쫛 46-46-1236-1-3
+always 쫜 46-46-1236-25
+always 쫝 46-46-1236-25-13
+always 쫞 46-46-1236-25-356
+always 쫟 46-46-1236-35
+always 쫠 46-46-1236-2
+always 쫡 46-46-1236-2-1
+always 쫢 46-46-1236-2-26
+always 쫣 46-46-1236-2-12
+always 쫤 46-46-1236-2-3
+always 쫥 46-46-1236-2-236
+always 쫦 46-46-1236-2-256
+always 쫧 46-46-1236-2-356
+always 쫨 46-46-1236-26
+always 쫩 46-46-1236-12
+always 쫪 46-46-1236-12-3
+always 쫫 46-46-1236-3
+always 쫬 46-46-1236-3-3
+always 쫭 46-46-1236-2356
+always 쫮 46-46-1236-13
+always 쫯 46-46-1236-23
+always 쫰 46-46-1236-235
+always 쫱 46-46-1236-236
+always 쫲 46-46-1236-256
+always 쫳 46-46-1236-356
+always 쫴 46-46-1236-1235
+always 쫵 46-46-1236-1235-1
+always 쫶 46-46-1236-1235-1-1
+always 쫷 46-46-1236-1235-1-3
+always 쫸 46-46-1236-1235-25
+always 쫹 46-46-1236-1235-25-13
+always 쫺 46-46-1236-1235-25-356
+always 쫻 46-46-1236-1235-35
+always 쫼 46-46-1236-1235-2
+always 쫽 46-46-1236-1235-2-1
+always 쫾 46-46-1236-1235-2-26
+always 쫿 46-46-1236-1235-2-12
+always 쬀 46-46-1236-1235-2-3
+always 쬁 46-46-1236-1235-2-236
+always 쬂 46-46-1236-1235-2-256
+always 쬃 46-46-1236-1235-2-356
+always 쬄 46-46-1236-1235-26
+always 쬅 46-46-1236-1235-12
+always 쬆 46-46-1236-1235-12-3
+always 쬇 46-46-1236-1235-3
+always 쬈 46-46-1236-1235-3-3
+always 쬉 46-46-1236-1235-2356
+always 쬊 46-46-1236-1235-13
+always 쬋 46-46-1236-1235-23
+always 쬌 46-46-1236-1235-235
+always 쬍 46-46-1236-1235-236
+always 쬎 46-46-1236-1235-256
+always 쬏 46-46-1236-1235-356
+always 쬐 46-46-13456
+always 쬑 46-46-13456-1
+always 쬒 46-46-13456-1-1
+always 쬓 46-46-13456-1-3
+always 쬔 46-46-13456-25
+always 쬕 46-46-13456-25-13
+always 쬖 46-46-13456-25-356
+always 쬗 46-46-13456-35
+always 쬘 46-46-13456-2
+always 쬙 46-46-13456-2-1
+always 쬚 46-46-13456-2-26
+always 쬛 46-46-13456-2-12
+always 쬜 46-46-13456-2-3
+always 쬝 46-46-13456-2-236
+always 쬞 46-46-13456-2-256
+always 쬟 46-46-13456-2-356
+always 쬠 46-46-13456-26
+always 쬡 46-46-13456-12
+always 쬢 46-46-13456-12-3
+always 쬣 46-46-13456-3
+always 쬤 46-46-13456-3-3
+always 쬥 46-46-13456-2356
+always 쬦 46-46-13456-13
+always 쬧 46-46-13456-23
+always 쬨 46-46-13456-235
+always 쬩 46-46-13456-236
+always 쬪 46-46-13456-256
+always 쬫 46-46-13456-356
+always 쬬 46-46-346
+always 쬭 46-46-346-1
+always 쬮 46-46-346-1-1
+always 쬯 46-46-346-1-3
+always 쬰 46-46-346-25
+always 쬱 46-46-346-25-13
+always 쬲 46-46-346-25-356
+always 쬳 46-46-346-35
+always 쬴 46-46-346-2
+always 쬵 46-46-346-2-1
+always 쬶 46-46-346-2-26
+always 쬷 46-46-346-2-12
+always 쬸 46-46-346-2-3
+always 쬹 46-46-346-2-236
+always 쬺 46-46-346-2-256
+always 쬻 46-46-346-2-356
+always 쬼 46-46-346-26
+always 쬽 46-46-346-12
+always 쬾 46-46-346-12-3
+always 쬿 46-46-346-3
+always 쭀 46-46-346-3-3
+always 쭁 46-46-346-2356
+always 쭂 46-46-346-13
+always 쭃 46-46-346-23
+always 쭄 46-46-346-235
+always 쭅 46-46-346-236
+always 쭆 46-46-346-256
+always 쭇 46-46-346-356
+always 쭈 46-46-134
+always 쭉 46-46-134-1
+always 쭊 46-46-134-1-1
+always 쭋 46-46-134-1-3
+always 쭌 46-46-134-25
+always 쭍 46-46-134-25-13
+always 쭎 46-46-134-25-356
+always 쭏 46-46-134-35
+always 쭐 46-46-134-2
+always 쭑 46-46-134-2-1
+always 쭒 46-46-134-2-26
+always 쭓 46-46-134-2-12
+always 쭔 46-46-134-2-3
+always 쭕 46-46-134-2-236
+always 쭖 46-46-134-2-256
+always 쭗 46-46-134-2-356
+always 쭘 46-46-134-26
+always 쭙 46-46-134-12
+always 쭚 46-46-134-12-3
+always 쭛 46-46-134-3
+always 쭜 46-46-134-3-3
+always 쭝 46-46-134-2356
+always 쭞 46-46-134-13
+always 쭟 46-46-134-23
+always 쭠 46-46-134-235
+always 쭡 46-46-134-236
+always 쭢 46-46-134-256
+always 쭣 46-46-134-356
+always 쭤 46-46-1234
+always 쭥 46-46-1234-1
+always 쭦 46-46-1234-1-1
+always 쭧 46-46-1234-1-3
+always 쭨 46-46-1234-25
+always 쭩 46-46-1234-25-13
+always 쭪 46-46-1234-25-356
+always 쭫 46-46-1234-35
+always 쭬 46-46-1234-2
+always 쭭 46-46-1234-2-1
+always 쭮 46-46-1234-2-26
+always 쭯 46-46-1234-2-12
+always 쭰 46-46-1234-2-3
+always 쭱 46-46-1234-2-236
+always 쭲 46-46-1234-2-256
+always 쭳 46-46-1234-2-356
+always 쭴 46-46-1234-26
+always 쭵 46-46-1234-12
+always 쭶 46-46-1234-12-3
+always 쭷 46-46-1234-3
+always 쭸 46-46-1234-3-3
+always 쭹 46-46-1234-2356
+always 쭺 46-46-1234-13
+always 쭻 46-46-1234-23
+always 쭼 46-46-1234-235
+always 쭽 46-46-1234-236
+always 쭾 46-46-1234-256
+always 쭿 46-46-1234-356
+always 쮀 46-46-1234-1235
+always 쮁 46-46-1234-1235-1
+always 쮂 46-46-1234-1235-1-1
+always 쮃 46-46-1234-1235-1-3
+always 쮄 46-46-1234-1235-25
+always 쮅 46-46-1234-1235-25-13
+always 쮆 46-46-1234-1235-25-356
+always 쮇 46-46-1234-1235-35
+always 쮈 46-46-1234-1235-2
+always 쮉 46-46-1234-1235-2-1
+always 쮊 46-46-1234-1235-2-26
+always 쮋 46-46-1234-1235-2-12
+always 쮌 46-46-1234-1235-2-3
+always 쮍 46-46-1234-1235-2-236
+always 쮎 46-46-1234-1235-2-256
+always 쮏 46-46-1234-1235-2-356
+always 쮐 46-46-1234-1235-26
+always 쮑 46-46-1234-1235-12
+always 쮒 46-46-1234-1235-12-3
+always 쮓 46-46-1234-1235-3
+always 쮔 46-46-1234-1235-3-3
+always 쮕 46-46-1234-1235-2356
+always 쮖 46-46-1234-1235-13
+always 쮗 46-46-1234-1235-23
+always 쮘 46-46-1234-1235-235
+always 쮙 46-46-1234-1235-236
+always 쮚 46-46-1234-1235-256
+always 쮛 46-46-1234-1235-356
+always 쮜 46-46-134-1235
+always 쮝 46-46-134-1235-1
+always 쮞 46-46-134-1235-1-1
+always 쮟 46-46-134-1235-1-3
+always 쮠 46-46-134-1235-25
+always 쮡 46-46-134-1235-25-13
+always 쮢 46-46-134-1235-25-356
+always 쮣 46-46-134-1235-35
+always 쮤 46-46-134-1235-2
+always 쮥 46-46-134-1235-2-1
+always 쮦 46-46-134-1235-2-26
+always 쮧 46-46-134-1235-2-12
+always 쮨 46-46-134-1235-2-3
+always 쮩 46-46-134-1235-2-236
+always 쮪 46-46-134-1235-2-256
+always 쮫 46-46-134-1235-2-356
+always 쮬 46-46-134-1235-26
+always 쮭 46-46-134-1235-12
+always 쮮 46-46-134-1235-12-3
+always 쮯 46-46-134-1235-3
+always 쮰 46-46-134-1235-3-3
+always 쮱 46-46-134-1235-2356
+always 쮲 46-46-134-1235-13
+always 쮳 46-46-134-1235-23
+always 쮴 46-46-134-1235-235
+always 쮵 46-46-134-1235-236
+always 쮶 46-46-134-1235-256
+always 쮷 46-46-134-1235-356
+always 쮸 46-46-146
+always 쮹 46-46-146-1
+always 쮺 46-46-146-1-1
+always 쮻 46-46-146-1-3
+always 쮼 46-46-146-25
+always 쮽 46-46-146-25-13
+always 쮾 46-46-146-25-356
+always 쮿 46-46-146-35
+always 쯀 46-46-146-2
+always 쯁 46-46-146-2-1
+always 쯂 46-46-146-2-26
+always 쯃 46-46-146-2-12
+always 쯄 46-46-146-2-3
+always 쯅 46-46-146-2-236
+always 쯆 46-46-146-2-256
+always 쯇 46-46-146-2-356
+always 쯈 46-46-146-26
+always 쯉 46-46-146-12
+always 쯊 46-46-146-12-3
+always 쯋 46-46-146-3
+always 쯌 46-46-146-3-3
+always 쯍 46-46-146-2356
+always 쯎 46-46-146-13
+always 쯏 46-46-146-23
+always 쯐 46-46-146-235
+always 쯑 46-46-146-236
+always 쯒 46-46-146-256
+always 쯓 46-46-146-356
+always 쯔 46-46-246
+always 쯕 46-46-246-1
+always 쯖 46-46-246-1-1
+always 쯗 46-46-246-1-3
+always 쯘 46-46-246-25
+always 쯙 46-46-246-25-13
+always 쯚 46-46-246-25-356
+always 쯛 46-46-246-35
+always 쯜 46-46-246-2
+always 쯝 46-46-246-2-1
+always 쯞 46-46-246-2-26
+always 쯟 46-46-246-2-12
+always 쯠 46-46-246-2-3
+always 쯡 46-46-246-2-236
+always 쯢 46-46-246-2-256
+always 쯣 46-46-246-2-356
+always 쯤 46-46-246-26
+always 쯥 46-46-246-12
+always 쯦 46-46-246-12-3
+always 쯧 46-46-246-3
+always 쯨 46-46-246-3-3
+always 쯩 46-46-246-2356
+always 쯪 46-46-246-13
+always 쯫 46-46-246-23
+always 쯬 46-46-246-235
+always 쯭 46-46-246-236
+always 쯮 46-46-246-256
+always 쯯 46-46-246-356
+always 쯰 46-46-2456
+always 쯱 46-46-2456-1
+always 쯲 46-46-2456-1-1
+always 쯳 46-46-2456-1-3
+always 쯴 46-46-2456-25
+always 쯵 46-46-2456-25-13
+always 쯶 46-46-2456-25-356
+always 쯷 46-46-2456-35
+always 쯸 46-46-2456-2
+always 쯹 46-46-2456-2-1
+always 쯺 46-46-2456-2-26
+always 쯻 46-46-2456-2-12
+always 쯼 46-46-2456-2-3
+always 쯽 46-46-2456-2-236
+always 쯾 46-46-2456-2-256
+always 쯿 46-46-2456-2-356
+always 찀 46-46-2456-26
+always 찁 46-46-2456-12
+always 찂 46-46-2456-12-3
+always 찃 46-46-2456-3
+always 찄 46-46-2456-3-3
+always 찅 46-46-2456-2356
+always 찆 46-46-2456-13
+always 찇 46-46-2456-23
+always 찈 46-46-2456-235
+always 찉 46-46-2456-236
+always 찊 46-46-2456-256
+always 찋 46-46-2456-356
+always 찌 46-46-135
+always 찍 46-46-135-1
+always 찎 46-46-135-1-1
+always 찏 46-46-135-1-3
+always 찐 46-46-135-25
+always 찑 46-46-135-25-13
+always 찒 46-46-135-25-356
+always 찓 46-46-135-35
+always 찔 46-46-135-2
+always 찕 46-46-135-2-1
+always 찖 46-46-135-2-26
+always 찗 46-46-135-2-12
+always 찘 46-46-135-2-3
+always 찙 46-46-135-2-236
+always 찚 46-46-135-2-256
+always 찛 46-46-135-2-356
+always 찜 46-46-135-26
+always 찝 46-46-135-12
+always 찞 46-46-135-12-3
+always 찟 46-46-135-3
+always 찠 46-46-135-3-3
+always 찡 46-46-135-2356
+always 찢 46-46-135-13
+always 찣 46-46-135-23
+always 찤 46-46-135-235
+always 찥 46-46-135-236
+always 찦 46-46-135-256
+always 찧 46-46-135-356
+always 차 56-126
+always 착 56-126-1
+always 찪 56-126-1-1
+always 찫 56-126-1-3
+always 찬 56-126-25
+always 찭 56-126-25-13
+always 찮 56-126-25-356
+always 찯 56-126-35
+always 찰 56-126-2
+always 찱 56-126-2-1
+always 찲 56-126-2-26
+always 찳 56-126-2-12
+always 찴 56-126-2-3
+always 찵 56-126-2-236
+always 찶 56-126-2-256
+always 찷 56-126-2-356
+always 참 56-126-26
+always 찹 56-126-12
+always 찺 56-126-12-3
+always 찻 56-126-3
+always 찼 56-126-3-3
+always 창 56-126-2356
+always 찾 56-126-13
+always 찿 56-126-23
+always 챀 56-126-235
+always 챁 56-126-236
+always 챂 56-126-256
+always 챃 56-126-356
+always 채 56-1235
+always 책 56-1235-1
+always 챆 56-1235-1-1
+always 챇 56-1235-1-3
+always 챈 56-1235-25
+always 챉 56-1235-25-13
+always 챊 56-1235-25-356
+always 챋 56-1235-35
+always 챌 56-1235-2
+always 챍 56-1235-2-1
+always 챎 56-1235-2-26
+always 챏 56-1235-2-12
+always 챐 56-1235-2-3
+always 챑 56-1235-2-236
+always 챒 56-1235-2-256
+always 챓 56-1235-2-356
+always 챔 56-1235-26
+always 챕 56-1235-12
+always 챖 56-1235-12-3
+always 챗 56-1235-3
+always 챘 56-1235-3-3
+always 챙 56-1235-2356
+always 챚 56-1235-13
+always 챛 56-1235-23
+always 챜 56-1235-235
+always 챝 56-1235-236
+always 챞 56-1235-256
+always 챟 56-1235-356
+always 챠 56-345
+always 챡 56-345-1
+always 챢 56-345-1-1
+always 챣 56-345-1-3
+always 챤 56-345-25
+always 챥 56-345-25-13
+always 챦 56-345-25-356
+always 챧 56-345-35
+always 챨 56-345-2
+always 챩 56-345-2-1
+always 챪 56-345-2-26
+always 챫 56-345-2-12
+always 챬 56-345-2-3
+always 챭 56-345-2-236
+always 챮 56-345-2-256
+always 챯 56-345-2-356
+always 챰 56-345-26
+always 챱 56-345-12
+always 챲 56-345-12-3
+always 챳 56-345-3
+always 챴 56-345-3-3
+always 챵 56-345-2356
+always 챶 56-345-13
+always 챷 56-345-23
+always 챸 56-345-235
+always 챹 56-345-236
+always 챺 56-345-256
+always 챻 56-345-356
+always 챼 56-345-1235
+always 챽 56-345-1235-1
+always 챾 56-345-1235-1-1
+always 챿 56-345-1235-1-3
+always 첀 56-345-1235-25
+always 첁 56-345-1235-25-13
+always 첂 56-345-1235-25-356
+always 첃 56-345-1235-35
+always 첄 56-345-1235-2
+always 첅 56-345-1235-2-1
+always 첆 56-345-1235-2-26
+always 첇 56-345-1235-2-12
+always 첈 56-345-1235-2-3
+always 첉 56-345-1235-2-236
+always 첊 56-345-1235-2-256
+always 첋 56-345-1235-2-356
+always 첌 56-345-1235-26
+always 첍 56-345-1235-12
+always 첎 56-345-1235-12-3
+always 첏 56-345-1235-3
+always 첐 56-345-1235-3-3
+always 첑 56-345-1235-2356
+always 첒 56-345-1235-13
+always 첓 56-345-1235-23
+always 첔 56-345-1235-235
+always 첕 56-345-1235-236
+always 첖 56-345-1235-256
+always 첗 56-345-1235-356
+always 처 56-234
+always 척 56-234-1
+always 첚 56-234-1-1
+always 첛 56-234-1-3
+always 천 56-234-25
+always 첝 56-234-25-13
+always 첞 56-234-25-356
+always 첟 56-234-35
+always 철 56-234-2
+always 첡 56-234-2-1
+always 첢 56-234-2-26
+always 첣 56-234-2-12
+always 첤 56-234-2-3
+always 첥 56-234-2-236
+always 첦 56-234-2-256
+always 첧 56-234-2-356
+always 첨 56-234-26
+always 첩 56-234-12
+always 첪 56-234-12-3
+always 첫 56-234-3
+always 첬 56-234-3-3
+always 청 56-234-2356
+always 첮 56-234-13
+always 첯 56-234-23
+always 첰 56-234-235
+always 첱 56-234-236
+always 첲 56-234-256
+always 첳 56-234-356
+always 체 56-1345
+always 첵 56-1345-1
+always 첶 56-1345-1-1
+always 첷 56-1345-1-3
+always 첸 56-1345-25
+always 첹 56-1345-25-13
+always 첺 56-1345-25-356
+always 첻 56-1345-35
+always 첼 56-1345-2
+always 첽 56-1345-2-1
+always 첾 56-1345-2-26
+always 첿 56-1345-2-12
+always 쳀 56-1345-2-3
+always 쳁 56-1345-2-236
+always 쳂 56-1345-2-256
+always 쳃 56-1345-2-356
+always 쳄 56-1345-26
+always 쳅 56-1345-12
+always 쳆 56-1345-12-3
+always 쳇 56-1345-3
+always 쳈 56-1345-3-3
+always 쳉 56-1345-2356
+always 쳊 56-1345-13
+always 쳋 56-1345-23
+always 쳌 56-1345-235
+always 쳍 56-1345-236
+always 쳎 56-1345-256
+always 쳏 56-1345-356
+always 쳐 56-156
+always 쳑 56-156-1
+always 쳒 56-156-1-1
+always 쳓 56-156-1-3
+always 쳔 56-156-25
+always 쳕 56-156-25-13
+always 쳖 56-156-25-356
+always 쳗 56-156-35
+always 쳘 56-156-2
+always 쳙 56-156-2-1
+always 쳚 56-156-2-26
+always 쳛 56-156-2-12
+always 쳜 56-156-2-3
+always 쳝 56-156-2-236
+always 쳞 56-156-2-256
+always 쳟 56-156-2-356
+always 쳠 56-156-26
+always 쳡 56-156-12
+always 쳢 56-156-12-3
+always 쳣 56-156-3
+always 쳤 56-156-3-3
+always 쳥 56-156-2356
+always 쳦 56-156-13
+always 쳧 56-156-23
+always 쳨 56-156-235
+always 쳩 56-156-236
+always 쳪 56-156-256
+always 쳫 56-156-356
+always 쳬 56-34
+always 쳭 56-34-1
+always 쳮 56-34-1-1
+always 쳯 56-34-1-3
+always 쳰 56-34-25
+always 쳱 56-34-25-13
+always 쳲 56-34-25-356
+always 쳳 56-34-35
+always 쳴 56-34-2
+always 쳵 56-34-2-1
+always 쳶 56-34-2-26
+always 쳷 56-34-2-12
+always 쳸 56-34-2-3
+always 쳹 56-34-2-236
+always 쳺 56-34-2-256
+always 쳻 56-34-2-356
+always 쳼 56-34-26
+always 쳽 56-34-12
+always 쳾 56-34-12-3
+always 쳿 56-34-3
+always 촀 56-34-3-3
+always 촁 56-34-2356
+always 촂 56-34-13
+always 촃 56-34-23
+always 촄 56-34-235
+always 촅 56-34-236
+always 촆 56-34-256
+always 촇 56-34-356
+always 초 56-136
+always 촉 56-136-1
+always 촊 56-136-1-1
+always 촋 56-136-1-3
+always 촌 56-136-25
+always 촍 56-136-25-13
+always 촎 56-136-25-356
+always 촏 56-136-35
+always 촐 56-136-2
+always 촑 56-136-2-1
+always 촒 56-136-2-26
+always 촓 56-136-2-12
+always 촔 56-136-2-3
+always 촕 56-136-2-236
+always 촖 56-136-2-256
+always 촗 56-136-2-356
+always 촘 56-136-26
+always 촙 56-136-12
+always 촚 56-136-12-3
+always 촛 56-136-3
+always 촜 56-136-3-3
+always 총 56-136-2356
+always 촞 56-136-13
+always 촟 56-136-23
+always 촠 56-136-235
+always 촡 56-136-236
+always 촢 56-136-256
+always 촣 56-136-356
+always 촤 56-1236
+always 촥 56-1236-1
+always 촦 56-1236-1-1
+always 촧 56-1236-1-3
+always 촨 56-1236-25
+always 촩 56-1236-25-13
+always 촪 56-1236-25-356
+always 촫 56-1236-35
+always 촬 56-1236-2
+always 촭 56-1236-2-1
+always 촮 56-1236-2-26
+always 촯 56-1236-2-12
+always 촰 56-1236-2-3
+always 촱 56-1236-2-236
+always 촲 56-1236-2-256
+always 촳 56-1236-2-356
+always 촴 56-1236-26
+always 촵 56-1236-12
+always 촶 56-1236-12-3
+always 촷 56-1236-3
+always 촸 56-1236-3-3
+always 촹 56-1236-2356
+always 촺 56-1236-13
+always 촻 56-1236-23
+always 촼 56-1236-235
+always 촽 56-1236-236
+always 촾 56-1236-256
+always 촿 56-1236-356
+always 쵀 56-1236-1235
+always 쵁 56-1236-1235-1
+always 쵂 56-1236-1235-1-1
+always 쵃 56-1236-1235-1-3
+always 쵄 56-1236-1235-25
+always 쵅 56-1236-1235-25-13
+always 쵆 56-1236-1235-25-356
+always 쵇 56-1236-1235-35
+always 쵈 56-1236-1235-2
+always 쵉 56-1236-1235-2-1
+always 쵊 56-1236-1235-2-26
+always 쵋 56-1236-1235-2-12
+always 쵌 56-1236-1235-2-3
+always 쵍 56-1236-1235-2-236
+always 쵎 56-1236-1235-2-256
+always 쵏 56-1236-1235-2-356
+always 쵐 56-1236-1235-26
+always 쵑 56-1236-1235-12
+always 쵒 56-1236-1235-12-3
+always 쵓 56-1236-1235-3
+always 쵔 56-1236-1235-3-3
+always 쵕 56-1236-1235-2356
+always 쵖 56-1236-1235-13
+always 쵗 56-1236-1235-23
+always 쵘 56-1236-1235-235
+always 쵙 56-1236-1235-236
+always 쵚 56-1236-1235-256
+always 쵛 56-1236-1235-356
+always 최 56-13456
+always 쵝 56-13456-1
+always 쵞 56-13456-1-1
+always 쵟 56-13456-1-3
+always 쵠 56-13456-25
+always 쵡 56-13456-25-13
+always 쵢 56-13456-25-356
+always 쵣 56-13456-35
+always 쵤 56-13456-2
+always 쵥 56-13456-2-1
+always 쵦 56-13456-2-26
+always 쵧 56-13456-2-12
+always 쵨 56-13456-2-3
+always 쵩 56-13456-2-236
+always 쵪 56-13456-2-256
+always 쵫 56-13456-2-356
+always 쵬 56-13456-26
+always 쵭 56-13456-12
+always 쵮 56-13456-12-3
+always 쵯 56-13456-3
+always 쵰 56-13456-3-3
+always 쵱 56-13456-2356
+always 쵲 56-13456-13
+always 쵳 56-13456-23
+always 쵴 56-13456-235
+always 쵵 56-13456-236
+always 쵶 56-13456-256
+always 쵷 56-13456-356
+always 쵸 56-346
+always 쵹 56-346-1
+always 쵺 56-346-1-1
+always 쵻 56-346-1-3
+always 쵼 56-346-25
+always 쵽 56-346-25-13
+always 쵾 56-346-25-356
+always 쵿 56-346-35
+always 춀 56-346-2
+always 춁 56-346-2-1
+always 춂 56-346-2-26
+always 춃 56-346-2-12
+always 춄 56-346-2-3
+always 춅 56-346-2-236
+always 춆 56-346-2-256
+always 춇 56-346-2-356
+always 춈 56-346-26
+always 춉 56-346-12
+always 춊 56-346-12-3
+always 춋 56-346-3
+always 춌 56-346-3-3
+always 춍 56-346-2356
+always 춎 56-346-13
+always 춏 56-346-23
+always 춐 56-346-235
+always 춑 56-346-236
+always 춒 56-346-256
+always 춓 56-346-356
+always 추 56-134
+always 축 56-134-1
+always 춖 56-134-1-1
+always 춗 56-134-1-3
+always 춘 56-134-25
+always 춙 56-134-25-13
+always 춚 56-134-25-356
+always 춛 56-134-35
+always 출 56-134-2
+always 춝 56-134-2-1
+always 춞 56-134-2-26
+always 춟 56-134-2-12
+always 춠 56-134-2-3
+always 춡 56-134-2-236
+always 춢 56-134-2-256
+always 춣 56-134-2-356
+always 춤 56-134-26
+always 춥 56-134-12
+always 춦 56-134-12-3
+always 춧 56-134-3
+always 춨 56-134-3-3
+always 충 56-134-2356
+always 춪 56-134-13
+always 춫 56-134-23
+always 춬 56-134-235
+always 춭 56-134-236
+always 춮 56-134-256
+always 춯 56-134-356
+always 춰 56-1234
+always 춱 56-1234-1
+always 춲 56-1234-1-1
+always 춳 56-1234-1-3
+always 춴 56-1234-25
+always 춵 56-1234-25-13
+always 춶 56-1234-25-356
+always 춷 56-1234-35
+always 춸 56-1234-2
+always 춹 56-1234-2-1
+always 춺 56-1234-2-26
+always 춻 56-1234-2-12
+always 춼 56-1234-2-3
+always 춽 56-1234-2-236
+always 춾 56-1234-2-256
+always 춿 56-1234-2-356
+always 췀 56-1234-26
+always 췁 56-1234-12
+always 췂 56-1234-12-3
+always 췃 56-1234-3
+always 췄 56-1234-3-3
+always 췅 56-1234-2356
+always 췆 56-1234-13
+always 췇 56-1234-23
+always 췈 56-1234-235
+always 췉 56-1234-236
+always 췊 56-1234-256
+always 췋 56-1234-356
+always 췌 56-1234-1235
+always 췍 56-1234-1235-1
+always 췎 56-1234-1235-1-1
+always 췏 56-1234-1235-1-3
+always 췐 56-1234-1235-25
+always 췑 56-1234-1235-25-13
+always 췒 56-1234-1235-25-356
+always 췓 56-1234-1235-35
+always 췔 56-1234-1235-2
+always 췕 56-1234-1235-2-1
+always 췖 56-1234-1235-2-26
+always 췗 56-1234-1235-2-12
+always 췘 56-1234-1235-2-3
+always 췙 56-1234-1235-2-236
+always 췚 56-1234-1235-2-256
+always 췛 56-1234-1235-2-356
+always 췜 56-1234-1235-26
+always 췝 56-1234-1235-12
+always 췞 56-1234-1235-12-3
+always 췟 56-1234-1235-3
+always 췠 56-1234-1235-3-3
+always 췡 56-1234-1235-2356
+always 췢 56-1234-1235-13
+always 췣 56-1234-1235-23
+always 췤 56-1234-1235-235
+always 췥 56-1234-1235-236
+always 췦 56-1234-1235-256
+always 췧 56-1234-1235-356
+always 취 56-134-1235
+always 췩 56-134-1235-1
+always 췪 56-134-1235-1-1
+always 췫 56-134-1235-1-3
+always 췬 56-134-1235-25
+always 췭 56-134-1235-25-13
+always 췮 56-134-1235-25-356
+always 췯 56-134-1235-35
+always 췰 56-134-1235-2
+always 췱 56-134-1235-2-1
+always 췲 56-134-1235-2-26
+always 췳 56-134-1235-2-12
+always 췴 56-134-1235-2-3
+always 췵 56-134-1235-2-236
+always 췶 56-134-1235-2-256
+always 췷 56-134-1235-2-356
+always 췸 56-134-1235-26
+always 췹 56-134-1235-12
+always 췺 56-134-1235-12-3
+always 췻 56-134-1235-3
+always 췼 56-134-1235-3-3
+always 췽 56-134-1235-2356
+always 췾 56-134-1235-13
+always 췿 56-134-1235-23
+always 츀 56-134-1235-235
+always 츁 56-134-1235-236
+always 츂 56-134-1235-256
+always 츃 56-134-1235-356
+always 츄 56-146
+always 츅 56-146-1
+always 츆 56-146-1-1
+always 츇 56-146-1-3
+always 츈 56-146-25
+always 츉 56-146-25-13
+always 츊 56-146-25-356
+always 츋 56-146-35
+always 츌 56-146-2
+always 츍 56-146-2-1
+always 츎 56-146-2-26
+always 츏 56-146-2-12
+always 츐 56-146-2-3
+always 츑 56-146-2-236
+always 츒 56-146-2-256
+always 츓 56-146-2-356
+always 츔 56-146-26
+always 츕 56-146-12
+always 츖 56-146-12-3
+always 츗 56-146-3
+always 츘 56-146-3-3
+always 츙 56-146-2356
+always 츚 56-146-13
+always 츛 56-146-23
+always 츜 56-146-235
+always 츝 56-146-236
+always 츞 56-146-256
+always 츟 56-146-356
+always 츠 56-246
+always 측 56-246-1
+always 츢 56-246-1-1
+always 츣 56-246-1-3
+always 츤 56-246-25
+always 츥 56-246-25-13
+always 츦 56-246-25-356
+always 츧 56-246-35
+always 츨 56-246-2
+always 츩 56-246-2-1
+always 츪 56-246-2-26
+always 츫 56-246-2-12
+always 츬 56-246-2-3
+always 츭 56-246-2-236
+always 츮 56-246-2-256
+always 츯 56-246-2-356
+always 츰 56-246-26
+always 츱 56-246-12
+always 츲 56-246-12-3
+always 츳 56-246-3
+always 츴 56-246-3-3
+always 층 56-246-2356
+always 츶 56-246-13
+always 츷 56-246-23
+always 츸 56-246-235
+always 츹 56-246-236
+always 츺 56-246-256
+always 츻 56-246-356
+always 츼 56-2456
+always 츽 56-2456-1
+always 츾 56-2456-1-1
+always 츿 56-2456-1-3
+always 칀 56-2456-25
+always 칁 56-2456-25-13
+always 칂 56-2456-25-356
+always 칃 56-2456-35
+always 칄 56-2456-2
+always 칅 56-2456-2-1
+always 칆 56-2456-2-26
+always 칇 56-2456-2-12
+always 칈 56-2456-2-3
+always 칉 56-2456-2-236
+always 칊 56-2456-2-256
+always 칋 56-2456-2-356
+always 칌 56-2456-26
+always 칍 56-2456-12
+always 칎 56-2456-12-3
+always 칏 56-2456-3
+always 칐 56-2456-3-3
+always 칑 56-2456-2356
+always 칒 56-2456-13
+always 칓 56-2456-23
+always 칔 56-2456-235
+always 칕 56-2456-236
+always 칖 56-2456-256
+always 칗 56-2456-356
+always 치 56-135
+always 칙 56-135-1
+always 칚 56-135-1-1
+always 칛 56-135-1-3
+always 친 56-135-25
+always 칝 56-135-25-13
+always 칞 56-135-25-356
+always 칟 56-135-35
+always 칠 56-135-2
+always 칡 56-135-2-1
+always 칢 56-135-2-26
+always 칣 56-135-2-12
+always 칤 56-135-2-3
+always 칥 56-135-2-236
+always 칦 56-135-2-256
+always 칧 56-135-2-356
+always 침 56-135-26
+always 칩 56-135-12
+always 칪 56-135-12-3
+always 칫 56-135-3
+always 칬 56-135-3-3
+always 칭 56-135-2356
+always 칮 56-135-13
+always 칯 56-135-23
+always 칰 56-135-235
+always 칱 56-135-236
+always 칲 56-135-256
+always 칳 56-135-356
+always 카 124-126
+always 칵 124-126-1
+always 칶 124-126-1-1
+always 칷 124-126-1-3
+always 칸 124-126-25
+always 칹 124-126-25-13
+always 칺 124-126-25-356
+always 칻 124-126-35
+always 칼 124-126-2
+always 칽 124-126-2-1
+always 칾 124-126-2-26
+always 칿 124-126-2-12
+always 캀 124-126-2-3
+always 캁 124-126-2-236
+always 캂 124-126-2-256
+always 캃 124-126-2-356
+always 캄 124-126-26
+always 캅 124-126-12
+always 캆 124-126-12-3
+always 캇 124-126-3
+always 캈 124-126-3-3
+always 캉 124-126-2356
+always 캊 124-126-13
+always 캋 124-126-23
+always 캌 124-126-235
+always 캍 124-126-236
+always 캎 124-126-256
+always 캏 124-126-356
+always 캐 124-1235
+always 캑 124-1235-1
+always 캒 124-1235-1-1
+always 캓 124-1235-1-3
+always 캔 124-1235-25
+always 캕 124-1235-25-13
+always 캖 124-1235-25-356
+always 캗 124-1235-35
+always 캘 124-1235-2
+always 캙 124-1235-2-1
+always 캚 124-1235-2-26
+always 캛 124-1235-2-12
+always 캜 124-1235-2-3
+always 캝 124-1235-2-236
+always 캞 124-1235-2-256
+always 캟 124-1235-2-356
+always 캠 124-1235-26
+always 캡 124-1235-12
+always 캢 124-1235-12-3
+always 캣 124-1235-3
+always 캤 124-1235-3-3
+always 캥 124-1235-2356
+always 캦 124-1235-13
+always 캧 124-1235-23
+always 캨 124-1235-235
+always 캩 124-1235-236
+always 캪 124-1235-256
+always 캫 124-1235-356
+always 캬 124-345
+always 캭 124-345-1
+always 캮 124-345-1-1
+always 캯 124-345-1-3
+always 캰 124-345-25
+always 캱 124-345-25-13
+always 캲 124-345-25-356
+always 캳 124-345-35
+always 캴 124-345-2
+always 캵 124-345-2-1
+always 캶 124-345-2-26
+always 캷 124-345-2-12
+always 캸 124-345-2-3
+always 캹 124-345-2-236
+always 캺 124-345-2-256
+always 캻 124-345-2-356
+always 캼 124-345-26
+always 캽 124-345-12
+always 캾 124-345-12-3
+always 캿 124-345-3
+always 컀 124-345-3-3
+always 컁 124-345-2356
+always 컂 124-345-13
+always 컃 124-345-23
+always 컄 124-345-235
+always 컅 124-345-236
+always 컆 124-345-256
+always 컇 124-345-356
+always 컈 124-345-1235
+always 컉 124-345-1235-1
+always 컊 124-345-1235-1-1
+always 컋 124-345-1235-1-3
+always 컌 124-345-1235-25
+always 컍 124-345-1235-25-13
+always 컎 124-345-1235-25-356
+always 컏 124-345-1235-35
+always 컐 124-345-1235-2
+always 컑 124-345-1235-2-1
+always 컒 124-345-1235-2-26
+always 컓 124-345-1235-2-12
+always 컔 124-345-1235-2-3
+always 컕 124-345-1235-2-236
+always 컖 124-345-1235-2-256
+always 컗 124-345-1235-2-356
+always 컘 124-345-1235-26
+always 컙 124-345-1235-12
+always 컚 124-345-1235-12-3
+always 컛 124-345-1235-3
+always 컜 124-345-1235-3-3
+always 컝 124-345-1235-2356
+always 컞 124-345-1235-13
+always 컟 124-345-1235-23
+always 컠 124-345-1235-235
+always 컡 124-345-1235-236
+always 컢 124-345-1235-256
+always 컣 124-345-1235-356
+always 커 124-234
+always 컥 124-234-1
+always 컦 124-234-1-1
+always 컧 124-234-1-3
+always 컨 124-234-25
+always 컩 124-234-25-13
+always 컪 124-234-25-356
+always 컫 124-234-35
+always 컬 124-234-2
+always 컭 124-234-2-1
+always 컮 124-234-2-26
+always 컯 124-234-2-12
+always 컰 124-234-2-3
+always 컱 124-234-2-236
+always 컲 124-234-2-256
+always 컳 124-234-2-356
+always 컴 124-234-26
+always 컵 124-234-12
+always 컶 124-234-12-3
+always 컷 124-234-3
+always 컸 124-234-3-3
+always 컹 124-234-2356
+always 컺 124-234-13
+always 컻 124-234-23
+always 컼 124-234-235
+always 컽 124-234-236
+always 컾 124-234-256
+always 컿 124-234-356
+always 케 124-1345
+always 켁 124-1345-1
+always 켂 124-1345-1-1
+always 켃 124-1345-1-3
+always 켄 124-1345-25
+always 켅 124-1345-25-13
+always 켆 124-1345-25-356
+always 켇 124-1345-35
+always 켈 124-1345-2
+always 켉 124-1345-2-1
+always 켊 124-1345-2-26
+always 켋 124-1345-2-12
+always 켌 124-1345-2-3
+always 켍 124-1345-2-236
+always 켎 124-1345-2-256
+always 켏 124-1345-2-356
+always 켐 124-1345-26
+always 켑 124-1345-12
+always 켒 124-1345-12-3
+always 켓 124-1345-3
+always 켔 124-1345-3-3
+always 켕 124-1345-2356
+always 켖 124-1345-13
+always 켗 124-1345-23
+always 켘 124-1345-235
+always 켙 124-1345-236
+always 켚 124-1345-256
+always 켛 124-1345-356
+always 켜 124-156
+always 켝 124-156-1
+always 켞 124-156-1-1
+always 켟 124-156-1-3
+always 켠 124-156-25
+always 켡 124-156-25-13
+always 켢 124-156-25-356
+always 켣 124-156-35
+always 켤 124-156-2
+always 켥 124-156-2-1
+always 켦 124-156-2-26
+always 켧 124-156-2-12
+always 켨 124-156-2-3
+always 켩 124-156-2-236
+always 켪 124-156-2-256
+always 켫 124-156-2-356
+always 켬 124-156-26
+always 켭 124-156-12
+always 켮 124-156-12-3
+always 켯 124-156-3
+always 켰 124-156-3-3
+always 켱 124-156-2356
+always 켲 124-156-13
+always 켳 124-156-23
+always 켴 124-156-235
+always 켵 124-156-236
+always 켶 124-156-256
+always 켷 124-156-356
+always 켸 124-34
+always 켹 124-34-1
+always 켺 124-34-1-1
+always 켻 124-34-1-3
+always 켼 124-34-25
+always 켽 124-34-25-13
+always 켾 124-34-25-356
+always 켿 124-34-35
+always 콀 124-34-2
+always 콁 124-34-2-1
+always 콂 124-34-2-26
+always 콃 124-34-2-12
+always 콄 124-34-2-3
+always 콅 124-34-2-236
+always 콆 124-34-2-256
+always 콇 124-34-2-356
+always 콈 124-34-26
+always 콉 124-34-12
+always 콊 124-34-12-3
+always 콋 124-34-3
+always 콌 124-34-3-3
+always 콍 124-34-2356
+always 콎 124-34-13
+always 콏 124-34-23
+always 콐 124-34-235
+always 콑 124-34-236
+always 콒 124-34-256
+always 콓 124-34-356
+always 코 124-136
+always 콕 124-136-1
+always 콖 124-136-1-1
+always 콗 124-136-1-3
+always 콘 124-136-25
+always 콙 124-136-25-13
+always 콚 124-136-25-356
+always 콛 124-136-35
+always 콜 124-136-2
+always 콝 124-136-2-1
+always 콞 124-136-2-26
+always 콟 124-136-2-12
+always 콠 124-136-2-3
+always 콡 124-136-2-236
+always 콢 124-136-2-256
+always 콣 124-136-2-356
+always 콤 124-136-26
+always 콥 124-136-12
+always 콦 124-136-12-3
+always 콧 124-136-3
+always 콨 124-136-3-3
+always 콩 124-136-2356
+always 콪 124-136-13
+always 콫 124-136-23
+always 콬 124-136-235
+always 콭 124-136-236
+always 콮 124-136-256
+always 콯 124-136-356
+always 콰 124-1236
+always 콱 124-1236-1
+always 콲 124-1236-1-1
+always 콳 124-1236-1-3
+always 콴 124-1236-25
+always 콵 124-1236-25-13
+always 콶 124-1236-25-356
+always 콷 124-1236-35
+always 콸 124-1236-2
+always 콹 124-1236-2-1
+always 콺 124-1236-2-26
+always 콻 124-1236-2-12
+always 콼 124-1236-2-3
+always 콽 124-1236-2-236
+always 콾 124-1236-2-256
+always 콿 124-1236-2-356
+always 쾀 124-1236-26
+always 쾁 124-1236-12
+always 쾂 124-1236-12-3
+always 쾃 124-1236-3
+always 쾄 124-1236-3-3
+always 쾅 124-1236-2356
+always 쾆 124-1236-13
+always 쾇 124-1236-23
+always 쾈 124-1236-235
+always 쾉 124-1236-236
+always 쾊 124-1236-256
+always 쾋 124-1236-356
+always 쾌 124-1236-1235
+always 쾍 124-1236-1235-1
+always 쾎 124-1236-1235-1-1
+always 쾏 124-1236-1235-1-3
+always 쾐 124-1236-1235-25
+always 쾑 124-1236-1235-25-13
+always 쾒 124-1236-1235-25-356
+always 쾓 124-1236-1235-35
+always 쾔 124-1236-1235-2
+always 쾕 124-1236-1235-2-1
+always 쾖 124-1236-1235-2-26
+always 쾗 124-1236-1235-2-12
+always 쾘 124-1236-1235-2-3
+always 쾙 124-1236-1235-2-236
+always 쾚 124-1236-1235-2-256
+always 쾛 124-1236-1235-2-356
+always 쾜 124-1236-1235-26
+always 쾝 124-1236-1235-12
+always 쾞 124-1236-1235-12-3
+always 쾟 124-1236-1235-3
+always 쾠 124-1236-1235-3-3
+always 쾡 124-1236-1235-2356
+always 쾢 124-1236-1235-13
+always 쾣 124-1236-1235-23
+always 쾤 124-1236-1235-235
+always 쾥 124-1236-1235-236
+always 쾦 124-1236-1235-256
+always 쾧 124-1236-1235-356
+always 쾨 124-13456
+always 쾩 124-13456-1
+always 쾪 124-13456-1-1
+always 쾫 124-13456-1-3
+always 쾬 124-13456-25
+always 쾭 124-13456-25-13
+always 쾮 124-13456-25-356
+always 쾯 124-13456-35
+always 쾰 124-13456-2
+always 쾱 124-13456-2-1
+always 쾲 124-13456-2-26
+always 쾳 124-13456-2-12
+always 쾴 124-13456-2-3
+always 쾵 124-13456-2-236
+always 쾶 124-13456-2-256
+always 쾷 124-13456-2-356
+always 쾸 124-13456-26
+always 쾹 124-13456-12
+always 쾺 124-13456-12-3
+always 쾻 124-13456-3
+always 쾼 124-13456-3-3
+always 쾽 124-13456-2356
+always 쾾 124-13456-13
+always 쾿 124-13456-23
+always 쿀 124-13456-235
+always 쿁 124-13456-236
+always 쿂 124-13456-256
+always 쿃 124-13456-356
+always 쿄 124-346
+always 쿅 124-346-1
+always 쿆 124-346-1-1
+always 쿇 124-346-1-3
+always 쿈 124-346-25
+always 쿉 124-346-25-13
+always 쿊 124-346-25-356
+always 쿋 124-346-35
+always 쿌 124-346-2
+always 쿍 124-346-2-1
+always 쿎 124-346-2-26
+always 쿏 124-346-2-12
+always 쿐 124-346-2-3
+always 쿑 124-346-2-236
+always 쿒 124-346-2-256
+always 쿓 124-346-2-356
+always 쿔 124-346-26
+always 쿕 124-346-12
+always 쿖 124-346-12-3
+always 쿗 124-346-3
+always 쿘 124-346-3-3
+always 쿙 124-346-2356
+always 쿚 124-346-13
+always 쿛 124-346-23
+always 쿜 124-346-235
+always 쿝 124-346-236
+always 쿞 124-346-256
+always 쿟 124-346-356
+always 쿠 124-134
+always 쿡 124-134-1
+always 쿢 124-134-1-1
+always 쿣 124-134-1-3
+always 쿤 124-134-25
+always 쿥 124-134-25-13
+always 쿦 124-134-25-356
+always 쿧 124-134-35
+always 쿨 124-134-2
+always 쿩 124-134-2-1
+always 쿪 124-134-2-26
+always 쿫 124-134-2-12
+always 쿬 124-134-2-3
+always 쿭 124-134-2-236
+always 쿮 124-134-2-256
+always 쿯 124-134-2-356
+always 쿰 124-134-26
+always 쿱 124-134-12
+always 쿲 124-134-12-3
+always 쿳 124-134-3
+always 쿴 124-134-3-3
+always 쿵 124-134-2356
+always 쿶 124-134-13
+always 쿷 124-134-23
+always 쿸 124-134-235
+always 쿹 124-134-236
+always 쿺 124-134-256
+always 쿻 124-134-356
+always 쿼 124-1234
+always 쿽 124-1234-1
+always 쿾 124-1234-1-1
+always 쿿 124-1234-1-3
+always 퀀 124-1234-25
+always 퀁 124-1234-25-13
+always 퀂 124-1234-25-356
+always 퀃 124-1234-35
+always 퀄 124-1234-2
+always 퀅 124-1234-2-1
+always 퀆 124-1234-2-26
+always 퀇 124-1234-2-12
+always 퀈 124-1234-2-3
+always 퀉 124-1234-2-236
+always 퀊 124-1234-2-256
+always 퀋 124-1234-2-356
+always 퀌 124-1234-26
+always 퀍 124-1234-12
+always 퀎 124-1234-12-3
+always 퀏 124-1234-3
+always 퀐 124-1234-3-3
+always 퀑 124-1234-2356
+always 퀒 124-1234-13
+always 퀓 124-1234-23
+always 퀔 124-1234-235
+always 퀕 124-1234-236
+always 퀖 124-1234-256
+always 퀗 124-1234-356
+always 퀘 124-1234-1235
+always 퀙 124-1234-1235-1
+always 퀚 124-1234-1235-1-1
+always 퀛 124-1234-1235-1-3
+always 퀜 124-1234-1235-25
+always 퀝 124-1234-1235-25-13
+always 퀞 124-1234-1235-25-356
+always 퀟 124-1234-1235-35
+always 퀠 124-1234-1235-2
+always 퀡 124-1234-1235-2-1
+always 퀢 124-1234-1235-2-26
+always 퀣 124-1234-1235-2-12
+always 퀤 124-1234-1235-2-3
+always 퀥 124-1234-1235-2-236
+always 퀦 124-1234-1235-2-256
+always 퀧 124-1234-1235-2-356
+always 퀨 124-1234-1235-26
+always 퀩 124-1234-1235-12
+always 퀪 124-1234-1235-12-3
+always 퀫 124-1234-1235-3
+always 퀬 124-1234-1235-3-3
+always 퀭 124-1234-1235-2356
+always 퀮 124-1234-1235-13
+always 퀯 124-1234-1235-23
+always 퀰 124-1234-1235-235
+always 퀱 124-1234-1235-236
+always 퀲 124-1234-1235-256
+always 퀳 124-1234-1235-356
+always 퀴 124-134-1235
+always 퀵 124-134-1235-1
+always 퀶 124-134-1235-1-1
+always 퀷 124-134-1235-1-3
+always 퀸 124-134-1235-25
+always 퀹 124-134-1235-25-13
+always 퀺 124-134-1235-25-356
+always 퀻 124-134-1235-35
+always 퀼 124-134-1235-2
+always 퀽 124-134-1235-2-1
+always 퀾 124-134-1235-2-26
+always 퀿 124-134-1235-2-12
+always 큀 124-134-1235-2-3
+always 큁 124-134-1235-2-236
+always 큂 124-134-1235-2-256
+always 큃 124-134-1235-2-356
+always 큄 124-134-1235-26
+always 큅 124-134-1235-12
+always 큆 124-134-1235-12-3
+always 큇 124-134-1235-3
+always 큈 124-134-1235-3-3
+always 큉 124-134-1235-2356
+always 큊 124-134-1235-13
+always 큋 124-134-1235-23
+always 큌 124-134-1235-235
+always 큍 124-134-1235-236
+always 큎 124-134-1235-256
+always 큏 124-134-1235-356
+always 큐 124-146
+always 큑 124-146-1
+always 큒 124-146-1-1
+always 큓 124-146-1-3
+always 큔 124-146-25
+always 큕 124-146-25-13
+always 큖 124-146-25-356
+always 큗 124-146-35
+always 큘 124-146-2
+always 큙 124-146-2-1
+always 큚 124-146-2-26
+always 큛 124-146-2-12
+always 큜 124-146-2-3
+always 큝 124-146-2-236
+always 큞 124-146-2-256
+always 큟 124-146-2-356
+always 큠 124-146-26
+always 큡 124-146-12
+always 큢 124-146-12-3
+always 큣 124-146-3
+always 큤 124-146-3-3
+always 큥 124-146-2356
+always 큦 124-146-13
+always 큧 124-146-23
+always 큨 124-146-235
+always 큩 124-146-236
+always 큪 124-146-256
+always 큫 124-146-356
+always 크 124-246
+always 큭 124-246-1
+always 큮 124-246-1-1
+always 큯 124-246-1-3
+always 큰 124-246-25
+always 큱 124-246-25-13
+always 큲 124-246-25-356
+always 큳 124-246-35
+always 클 124-246-2
+always 큵 124-246-2-1
+always 큶 124-246-2-26
+always 큷 124-246-2-12
+always 큸 124-246-2-3
+always 큹 124-246-2-236
+always 큺 124-246-2-256
+always 큻 124-246-2-356
+always 큼 124-246-26
+always 큽 124-246-12
+always 큾 124-246-12-3
+always 큿 124-246-3
+always 킀 124-246-3-3
+always 킁 124-246-2356
+always 킂 124-246-13
+always 킃 124-246-23
+always 킄 124-246-235
+always 킅 124-246-236
+always 킆 124-246-256
+always 킇 124-246-356
+always 킈 124-2456
+always 킉 124-2456-1
+always 킊 124-2456-1-1
+always 킋 124-2456-1-3
+always 킌 124-2456-25
+always 킍 124-2456-25-13
+always 킎 124-2456-25-356
+always 킏 124-2456-35
+always 킐 124-2456-2
+always 킑 124-2456-2-1
+always 킒 124-2456-2-26
+always 킓 124-2456-2-12
+always 킔 124-2456-2-3
+always 킕 124-2456-2-236
+always 킖 124-2456-2-256
+always 킗 124-2456-2-356
+always 킘 124-2456-26
+always 킙 124-2456-12
+always 킚 124-2456-12-3
+always 킛 124-2456-3
+always 킜 124-2456-3-3
+always 킝 124-2456-2356
+always 킞 124-2456-13
+always 킟 124-2456-23
+always 킠 124-2456-235
+always 킡 124-2456-236
+always 킢 124-2456-256
+always 킣 124-2456-356
+always 키 124-135
+always 킥 124-135-1
+always 킦 124-135-1-1
+always 킧 124-135-1-3
+always 킨 124-135-25
+always 킩 124-135-25-13
+always 킪 124-135-25-356
+always 킫 124-135-35
+always 킬 124-135-2
+always 킭 124-135-2-1
+always 킮 124-135-2-26
+always 킯 124-135-2-12
+always 킰 124-135-2-3
+always 킱 124-135-2-236
+always 킲 124-135-2-256
+always 킳 124-135-2-356
+always 킴 124-135-26
+always 킵 124-135-12
+always 킶 124-135-12-3
+always 킷 124-135-3
+always 킸 124-135-3-3
+always 킹 124-135-2356
+always 킺 124-135-13
+always 킻 124-135-23
+always 킼 124-135-235
+always 킽 124-135-236
+always 킾 124-135-256
+always 킿 124-135-356
+always 타 125-126
+always 탁 125-126-1
+always 탂 125-126-1-1
+always 탃 125-126-1-3
+always 탄 125-126-25
+always 탅 125-126-25-13
+always 탆 125-126-25-356
+always 탇 125-126-35
+always 탈 125-126-2
+always 탉 125-126-2-1
+always 탊 125-126-2-26
+always 탋 125-126-2-12
+always 탌 125-126-2-3
+always 탍 125-126-2-236
+always 탎 125-126-2-256
+always 탏 125-126-2-356
+always 탐 125-126-26
+always 탑 125-126-12
+always 탒 125-126-12-3
+always 탓 125-126-3
+always 탔 125-126-3-3
+always 탕 125-126-2356
+always 탖 125-126-13
+always 탗 125-126-23
+always 탘 125-126-235
+always 탙 125-126-236
+always 탚 125-126-256
+always 탛 125-126-356
+always 태 125-1235
+always 택 125-1235-1
+always 탞 125-1235-1-1
+always 탟 125-1235-1-3
+always 탠 125-1235-25
+always 탡 125-1235-25-13
+always 탢 125-1235-25-356
+always 탣 125-1235-35
+always 탤 125-1235-2
+always 탥 125-1235-2-1
+always 탦 125-1235-2-26
+always 탧 125-1235-2-12
+always 탨 125-1235-2-3
+always 탩 125-1235-2-236
+always 탪 125-1235-2-256
+always 탫 125-1235-2-356
+always 탬 125-1235-26
+always 탭 125-1235-12
+always 탮 125-1235-12-3
+always 탯 125-1235-3
+always 탰 125-1235-3-3
+always 탱 125-1235-2356
+always 탲 125-1235-13
+always 탳 125-1235-23
+always 탴 125-1235-235
+always 탵 125-1235-236
+always 탶 125-1235-256
+always 탷 125-1235-356
+always 탸 125-345
+always 탹 125-345-1
+always 탺 125-345-1-1
+always 탻 125-345-1-3
+always 탼 125-345-25
+always 탽 125-345-25-13
+always 탾 125-345-25-356
+always 탿 125-345-35
+always 턀 125-345-2
+always 턁 125-345-2-1
+always 턂 125-345-2-26
+always 턃 125-345-2-12
+always 턄 125-345-2-3
+always 턅 125-345-2-236
+always 턆 125-345-2-256
+always 턇 125-345-2-356
+always 턈 125-345-26
+always 턉 125-345-12
+always 턊 125-345-12-3
+always 턋 125-345-3
+always 턌 125-345-3-3
+always 턍 125-345-2356
+always 턎 125-345-13
+always 턏 125-345-23
+always 턐 125-345-235
+always 턑 125-345-236
+always 턒 125-345-256
+always 턓 125-345-356
+always 턔 125-345-1235
+always 턕 125-345-1235-1
+always 턖 125-345-1235-1-1
+always 턗 125-345-1235-1-3
+always 턘 125-345-1235-25
+always 턙 125-345-1235-25-13
+always 턚 125-345-1235-25-356
+always 턛 125-345-1235-35
+always 턜 125-345-1235-2
+always 턝 125-345-1235-2-1
+always 턞 125-345-1235-2-26
+always 턟 125-345-1235-2-12
+always 턠 125-345-1235-2-3
+always 턡 125-345-1235-2-236
+always 턢 125-345-1235-2-256
+always 턣 125-345-1235-2-356
+always 턤 125-345-1235-26
+always 턥 125-345-1235-12
+always 턦 125-345-1235-12-3
+always 턧 125-345-1235-3
+always 턨 125-345-1235-3-3
+always 턩 125-345-1235-2356
+always 턪 125-345-1235-13
+always 턫 125-345-1235-23
+always 턬 125-345-1235-235
+always 턭 125-345-1235-236
+always 턮 125-345-1235-256
+always 턯 125-345-1235-356
+always 터 125-234
+always 턱 125-234-1
+always 턲 125-234-1-1
+always 턳 125-234-1-3
+always 턴 125-234-25
+always 턵 125-234-25-13
+always 턶 125-234-25-356
+always 턷 125-234-35
+always 털 125-234-2
+always 턹 125-234-2-1
+always 턺 125-234-2-26
+always 턻 125-234-2-12
+always 턼 125-234-2-3
+always 턽 125-234-2-236
+always 턾 125-234-2-256
+always 턿 125-234-2-356
+always 텀 125-234-26
+always 텁 125-234-12
+always 텂 125-234-12-3
+always 텃 125-234-3
+always 텄 125-234-3-3
+always 텅 125-234-2356
+always 텆 125-234-13
+always 텇 125-234-23
+always 텈 125-234-235
+always 텉 125-234-236
+always 텊 125-234-256
+always 텋 125-234-356
+always 테 125-1345
+always 텍 125-1345-1
+always 텎 125-1345-1-1
+always 텏 125-1345-1-3
+always 텐 125-1345-25
+always 텑 125-1345-25-13
+always 텒 125-1345-25-356
+always 텓 125-1345-35
+always 텔 125-1345-2
+always 텕 125-1345-2-1
+always 텖 125-1345-2-26
+always 텗 125-1345-2-12
+always 텘 125-1345-2-3
+always 텙 125-1345-2-236
+always 텚 125-1345-2-256
+always 텛 125-1345-2-356
+always 템 125-1345-26
+always 텝 125-1345-12
+always 텞 125-1345-12-3
+always 텟 125-1345-3
+always 텠 125-1345-3-3
+always 텡 125-1345-2356
+always 텢 125-1345-13
+always 텣 125-1345-23
+always 텤 125-1345-235
+always 텥 125-1345-236
+always 텦 125-1345-256
+always 텧 125-1345-356
+always 텨 125-156
+always 텩 125-156-1
+always 텪 125-156-1-1
+always 텫 125-156-1-3
+always 텬 125-156-25
+always 텭 125-156-25-13
+always 텮 125-156-25-356
+always 텯 125-156-35
+always 텰 125-156-2
+always 텱 125-156-2-1
+always 텲 125-156-2-26
+always 텳 125-156-2-12
+always 텴 125-156-2-3
+always 텵 125-156-2-236
+always 텶 125-156-2-256
+always 텷 125-156-2-356
+always 텸 125-156-26
+always 텹 125-156-12
+always 텺 125-156-12-3
+always 텻 125-156-3
+always 텼 125-156-3-3
+always 텽 125-156-2356
+always 텾 125-156-13
+always 텿 125-156-23
+always 톀 125-156-235
+always 톁 125-156-236
+always 톂 125-156-256
+always 톃 125-156-356
+always 톄 125-34
+always 톅 125-34-1
+always 톆 125-34-1-1
+always 톇 125-34-1-3
+always 톈 125-34-25
+always 톉 125-34-25-13
+always 톊 125-34-25-356
+always 톋 125-34-35
+always 톌 125-34-2
+always 톍 125-34-2-1
+always 톎 125-34-2-26
+always 톏 125-34-2-12
+always 톐 125-34-2-3
+always 톑 125-34-2-236
+always 톒 125-34-2-256
+always 톓 125-34-2-356
+always 톔 125-34-26
+always 톕 125-34-12
+always 톖 125-34-12-3
+always 톗 125-34-3
+always 톘 125-34-3-3
+always 톙 125-34-2356
+always 톚 125-34-13
+always 톛 125-34-23
+always 톜 125-34-235
+always 톝 125-34-236
+always 톞 125-34-256
+always 톟 125-34-356
+always 토 125-136
+always 톡 125-136-1
+always 톢 125-136-1-1
+always 톣 125-136-1-3
+always 톤 125-136-25
+always 톥 125-136-25-13
+always 톦 125-136-25-356
+always 톧 125-136-35
+always 톨 125-136-2
+always 톩 125-136-2-1
+always 톪 125-136-2-26
+always 톫 125-136-2-12
+always 톬 125-136-2-3
+always 톭 125-136-2-236
+always 톮 125-136-2-256
+always 톯 125-136-2-356
+always 톰 125-136-26
+always 톱 125-136-12
+always 톲 125-136-12-3
+always 톳 125-136-3
+always 톴 125-136-3-3
+always 통 125-136-2356
+always 톶 125-136-13
+always 톷 125-136-23
+always 톸 125-136-235
+always 톹 125-136-236
+always 톺 125-136-256
+always 톻 125-136-356
+always 톼 125-1236
+always 톽 125-1236-1
+always 톾 125-1236-1-1
+always 톿 125-1236-1-3
+always 퇀 125-1236-25
+always 퇁 125-1236-25-13
+always 퇂 125-1236-25-356
+always 퇃 125-1236-35
+always 퇄 125-1236-2
+always 퇅 125-1236-2-1
+always 퇆 125-1236-2-26
+always 퇇 125-1236-2-12
+always 퇈 125-1236-2-3
+always 퇉 125-1236-2-236
+always 퇊 125-1236-2-256
+always 퇋 125-1236-2-356
+always 퇌 125-1236-26
+always 퇍 125-1236-12
+always 퇎 125-1236-12-3
+always 퇏 125-1236-3
+always 퇐 125-1236-3-3
+always 퇑 125-1236-2356
+always 퇒 125-1236-13
+always 퇓 125-1236-23
+always 퇔 125-1236-235
+always 퇕 125-1236-236
+always 퇖 125-1236-256
+always 퇗 125-1236-356
+always 퇘 125-1236-1235
+always 퇙 125-1236-1235-1
+always 퇚 125-1236-1235-1-1
+always 퇛 125-1236-1235-1-3
+always 퇜 125-1236-1235-25
+always 퇝 125-1236-1235-25-13
+always 퇞 125-1236-1235-25-356
+always 퇟 125-1236-1235-35
+always 퇠 125-1236-1235-2
+always 퇡 125-1236-1235-2-1
+always 퇢 125-1236-1235-2-26
+always 퇣 125-1236-1235-2-12
+always 퇤 125-1236-1235-2-3
+always 퇥 125-1236-1235-2-236
+always 퇦 125-1236-1235-2-256
+always 퇧 125-1236-1235-2-356
+always 퇨 125-1236-1235-26
+always 퇩 125-1236-1235-12
+always 퇪 125-1236-1235-12-3
+always 퇫 125-1236-1235-3
+always 퇬 125-1236-1235-3-3
+always 퇭 125-1236-1235-2356
+always 퇮 125-1236-1235-13
+always 퇯 125-1236-1235-23
+always 퇰 125-1236-1235-235
+always 퇱 125-1236-1235-236
+always 퇲 125-1236-1235-256
+always 퇳 125-1236-1235-356
+always 퇴 125-13456
+always 퇵 125-13456-1
+always 퇶 125-13456-1-1
+always 퇷 125-13456-1-3
+always 퇸 125-13456-25
+always 퇹 125-13456-25-13
+always 퇺 125-13456-25-356
+always 퇻 125-13456-35
+always 퇼 125-13456-2
+always 퇽 125-13456-2-1
+always 퇾 125-13456-2-26
+always 퇿 125-13456-2-12
+always 툀 125-13456-2-3
+always 툁 125-13456-2-236
+always 툂 125-13456-2-256
+always 툃 125-13456-2-356
+always 툄 125-13456-26
+always 툅 125-13456-12
+always 툆 125-13456-12-3
+always 툇 125-13456-3
+always 툈 125-13456-3-3
+always 툉 125-13456-2356
+always 툊 125-13456-13
+always 툋 125-13456-23
+always 툌 125-13456-235
+always 툍 125-13456-236
+always 툎 125-13456-256
+always 툏 125-13456-356
+always 툐 125-346
+always 툑 125-346-1
+always 툒 125-346-1-1
+always 툓 125-346-1-3
+always 툔 125-346-25
+always 툕 125-346-25-13
+always 툖 125-346-25-356
+always 툗 125-346-35
+always 툘 125-346-2
+always 툙 125-346-2-1
+always 툚 125-346-2-26
+always 툛 125-346-2-12
+always 툜 125-346-2-3
+always 툝 125-346-2-236
+always 툞 125-346-2-256
+always 툟 125-346-2-356
+always 툠 125-346-26
+always 툡 125-346-12
+always 툢 125-346-12-3
+always 툣 125-346-3
+always 툤 125-346-3-3
+always 툥 125-346-2356
+always 툦 125-346-13
+always 툧 125-346-23
+always 툨 125-346-235
+always 툩 125-346-236
+always 툪 125-346-256
+always 툫 125-346-356
+always 투 125-134
+always 툭 125-134-1
+always 툮 125-134-1-1
+always 툯 125-134-1-3
+always 툰 125-134-25
+always 툱 125-134-25-13
+always 툲 125-134-25-356
+always 툳 125-134-35
+always 툴 125-134-2
+always 툵 125-134-2-1
+always 툶 125-134-2-26
+always 툷 125-134-2-12
+always 툸 125-134-2-3
+always 툹 125-134-2-236
+always 툺 125-134-2-256
+always 툻 125-134-2-356
+always 툼 125-134-26
+always 툽 125-134-12
+always 툾 125-134-12-3
+always 툿 125-134-3
+always 퉀 125-134-3-3
+always 퉁 125-134-2356
+always 퉂 125-134-13
+always 퉃 125-134-23
+always 퉄 125-134-235
+always 퉅 125-134-236
+always 퉆 125-134-256
+always 퉇 125-134-356
+always 퉈 125-1234
+always 퉉 125-1234-1
+always 퉊 125-1234-1-1
+always 퉋 125-1234-1-3
+always 퉌 125-1234-25
+always 퉍 125-1234-25-13
+always 퉎 125-1234-25-356
+always 퉏 125-1234-35
+always 퉐 125-1234-2
+always 퉑 125-1234-2-1
+always 퉒 125-1234-2-26
+always 퉓 125-1234-2-12
+always 퉔 125-1234-2-3
+always 퉕 125-1234-2-236
+always 퉖 125-1234-2-256
+always 퉗 125-1234-2-356
+always 퉘 125-1234-26
+always 퉙 125-1234-12
+always 퉚 125-1234-12-3
+always 퉛 125-1234-3
+always 퉜 125-1234-3-3
+always 퉝 125-1234-2356
+always 퉞 125-1234-13
+always 퉟 125-1234-23
+always 퉠 125-1234-235
+always 퉡 125-1234-236
+always 퉢 125-1234-256
+always 퉣 125-1234-356
+always 퉤 125-1234-1235
+always 퉥 125-1234-1235-1
+always 퉦 125-1234-1235-1-1
+always 퉧 125-1234-1235-1-3
+always 퉨 125-1234-1235-25
+always 퉩 125-1234-1235-25-13
+always 퉪 125-1234-1235-25-356
+always 퉫 125-1234-1235-35
+always 퉬 125-1234-1235-2
+always 퉭 125-1234-1235-2-1
+always 퉮 125-1234-1235-2-26
+always 퉯 125-1234-1235-2-12
+always 퉰 125-1234-1235-2-3
+always 퉱 125-1234-1235-2-236
+always 퉲 125-1234-1235-2-256
+always 퉳 125-1234-1235-2-356
+always 퉴 125-1234-1235-26
+always 퉵 125-1234-1235-12
+always 퉶 125-1234-1235-12-3
+always 퉷 125-1234-1235-3
+always 퉸 125-1234-1235-3-3
+always 퉹 125-1234-1235-2356
+always 퉺 125-1234-1235-13
+always 퉻 125-1234-1235-23
+always 퉼 125-1234-1235-235
+always 퉽 125-1234-1235-236
+always 퉾 125-1234-1235-256
+always 퉿 125-1234-1235-356
+always 튀 125-134-1235
+always 튁 125-134-1235-1
+always 튂 125-134-1235-1-1
+always 튃 125-134-1235-1-3
+always 튄 125-134-1235-25
+always 튅 125-134-1235-25-13
+always 튆 125-134-1235-25-356
+always 튇 125-134-1235-35
+always 튈 125-134-1235-2
+always 튉 125-134-1235-2-1
+always 튊 125-134-1235-2-26
+always 튋 125-134-1235-2-12
+always 튌 125-134-1235-2-3
+always 튍 125-134-1235-2-236
+always 튎 125-134-1235-2-256
+always 튏 125-134-1235-2-356
+always 튐 125-134-1235-26
+always 튑 125-134-1235-12
+always 튒 125-134-1235-12-3
+always 튓 125-134-1235-3
+always 튔 125-134-1235-3-3
+always 튕 125-134-1235-2356
+always 튖 125-134-1235-13
+always 튗 125-134-1235-23
+always 튘 125-134-1235-235
+always 튙 125-134-1235-236
+always 튚 125-134-1235-256
+always 튛 125-134-1235-356
+always 튜 125-146
+always 튝 125-146-1
+always 튞 125-146-1-1
+always 튟 125-146-1-3
+always 튠 125-146-25
+always 튡 125-146-25-13
+always 튢 125-146-25-356
+always 튣 125-146-35
+always 튤 125-146-2
+always 튥 125-146-2-1
+always 튦 125-146-2-26
+always 튧 125-146-2-12
+always 튨 125-146-2-3
+always 튩 125-146-2-236
+always 튪 125-146-2-256
+always 튫 125-146-2-356
+always 튬 125-146-26
+always 튭 125-146-12
+always 튮 125-146-12-3
+always 튯 125-146-3
+always 튰 125-146-3-3
+always 튱 125-146-2356
+always 튲 125-146-13
+always 튳 125-146-23
+always 튴 125-146-235
+always 튵 125-146-236
+always 튶 125-146-256
+always 튷 125-146-356
+always 트 125-246
+always 특 125-246-1
+always 튺 125-246-1-1
+always 튻 125-246-1-3
+always 튼 125-246-25
+always 튽 125-246-25-13
+always 튾 125-246-25-356
+always 튿 125-246-35
+always 틀 125-246-2
+always 틁 125-246-2-1
+always 틂 125-246-2-26
+always 틃 125-246-2-12
+always 틄 125-246-2-3
+always 틅 125-246-2-236
+always 틆 125-246-2-256
+always 틇 125-246-2-356
+always 틈 125-246-26
+always 틉 125-246-12
+always 틊 125-246-12-3
+always 틋 125-246-3
+always 틌 125-246-3-3
+always 틍 125-246-2356
+always 틎 125-246-13
+always 틏 125-246-23
+always 틐 125-246-235
+always 틑 125-246-236
+always 틒 125-246-256
+always 틓 125-246-356
+always 틔 125-2456
+always 틕 125-2456-1
+always 틖 125-2456-1-1
+always 틗 125-2456-1-3
+always 틘 125-2456-25
+always 틙 125-2456-25-13
+always 틚 125-2456-25-356
+always 틛 125-2456-35
+always 틜 125-2456-2
+always 틝 125-2456-2-1
+always 틞 125-2456-2-26
+always 틟 125-2456-2-12
+always 틠 125-2456-2-3
+always 틡 125-2456-2-236
+always 틢 125-2456-2-256
+always 틣 125-2456-2-356
+always 틤 125-2456-26
+always 틥 125-2456-12
+always 틦 125-2456-12-3
+always 틧 125-2456-3
+always 틨 125-2456-3-3
+always 틩 125-2456-2356
+always 틪 125-2456-13
+always 틫 125-2456-23
+always 틬 125-2456-235
+always 틭 125-2456-236
+always 틮 125-2456-256
+always 틯 125-2456-356
+always 티 125-135
+always 틱 125-135-1
+always 틲 125-135-1-1
+always 틳 125-135-1-3
+always 틴 125-135-25
+always 틵 125-135-25-13
+always 틶 125-135-25-356
+always 틷 125-135-35
+always 틸 125-135-2
+always 틹 125-135-2-1
+always 틺 125-135-2-26
+always 틻 125-135-2-12
+always 틼 125-135-2-3
+always 틽 125-135-2-236
+always 틾 125-135-2-256
+always 틿 125-135-2-356
+always 팀 125-135-26
+always 팁 125-135-12
+always 팂 125-135-12-3
+always 팃 125-135-3
+always 팄 125-135-3-3
+always 팅 125-135-2356
+always 팆 125-135-13
+always 팇 125-135-23
+always 팈 125-135-235
+always 팉 125-135-236
+always 팊 125-135-256
+always 팋 125-135-356
+always 파 145-126
+always 팍 145-126-1
+always 팎 145-126-1-1
+always 팏 145-126-1-3
+always 판 145-126-25
+always 팑 145-126-25-13
+always 팒 145-126-25-356
+always 팓 145-126-35
+always 팔 145-126-2
+always 팕 145-126-2-1
+always 팖 145-126-2-26
+always 팗 145-126-2-12
+always 팘 145-126-2-3
+always 팙 145-126-2-236
+always 팚 145-126-2-256
+always 팛 145-126-2-356
+always 팜 145-126-26
+always 팝 145-126-12
+always 팞 145-126-12-3
+always 팟 145-126-3
+always 팠 145-126-3-3
+always 팡 145-126-2356
+always 팢 145-126-13
+always 팣 145-126-23
+always 팤 145-126-235
+always 팥 145-126-236
+always 팦 145-126-256
+always 팧 145-126-356
+always 패 145-1235
+always 팩 145-1235-1
+always 팪 145-1235-1-1
+always 팫 145-1235-1-3
+always 팬 145-1235-25
+always 팭 145-1235-25-13
+always 팮 145-1235-25-356
+always 팯 145-1235-35
+always 팰 145-1235-2
+always 팱 145-1235-2-1
+always 팲 145-1235-2-26
+always 팳 145-1235-2-12
+always 팴 145-1235-2-3
+always 팵 145-1235-2-236
+always 팶 145-1235-2-256
+always 팷 145-1235-2-356
+always 팸 145-1235-26
+always 팹 145-1235-12
+always 팺 145-1235-12-3
+always 팻 145-1235-3
+always 팼 145-1235-3-3
+always 팽 145-1235-2356
+always 팾 145-1235-13
+always 팿 145-1235-23
+always 퍀 145-1235-235
+always 퍁 145-1235-236
+always 퍂 145-1235-256
+always 퍃 145-1235-356
+always 퍄 145-345
+always 퍅 145-345-1
+always 퍆 145-345-1-1
+always 퍇 145-345-1-3
+always 퍈 145-345-25
+always 퍉 145-345-25-13
+always 퍊 145-345-25-356
+always 퍋 145-345-35
+always 퍌 145-345-2
+always 퍍 145-345-2-1
+always 퍎 145-345-2-26
+always 퍏 145-345-2-12
+always 퍐 145-345-2-3
+always 퍑 145-345-2-236
+always 퍒 145-345-2-256
+always 퍓 145-345-2-356
+always 퍔 145-345-26
+always 퍕 145-345-12
+always 퍖 145-345-12-3
+always 퍗 145-345-3
+always 퍘 145-345-3-3
+always 퍙 145-345-2356
+always 퍚 145-345-13
+always 퍛 145-345-23
+always 퍜 145-345-235
+always 퍝 145-345-236
+always 퍞 145-345-256
+always 퍟 145-345-356
+always 퍠 145-345-1235
+always 퍡 145-345-1235-1
+always 퍢 145-345-1235-1-1
+always 퍣 145-345-1235-1-3
+always 퍤 145-345-1235-25
+always 퍥 145-345-1235-25-13
+always 퍦 145-345-1235-25-356
+always 퍧 145-345-1235-35
+always 퍨 145-345-1235-2
+always 퍩 145-345-1235-2-1
+always 퍪 145-345-1235-2-26
+always 퍫 145-345-1235-2-12
+always 퍬 145-345-1235-2-3
+always 퍭 145-345-1235-2-236
+always 퍮 145-345-1235-2-256
+always 퍯 145-345-1235-2-356
+always 퍰 145-345-1235-26
+always 퍱 145-345-1235-12
+always 퍲 145-345-1235-12-3
+always 퍳 145-345-1235-3
+always 퍴 145-345-1235-3-3
+always 퍵 145-345-1235-2356
+always 퍶 145-345-1235-13
+always 퍷 145-345-1235-23
+always 퍸 145-345-1235-235
+always 퍹 145-345-1235-236
+always 퍺 145-345-1235-256
+always 퍻 145-345-1235-356
+always 퍼 145-234
+always 퍽 145-234-1
+always 퍾 145-234-1-1
+always 퍿 145-234-1-3
+always 펀 145-234-25
+always 펁 145-234-25-13
+always 펂 145-234-25-356
+always 펃 145-234-35
+always 펄 145-234-2
+always 펅 145-234-2-1
+always 펆 145-234-2-26
+always 펇 145-234-2-12
+always 펈 145-234-2-3
+always 펉 145-234-2-236
+always 펊 145-234-2-256
+always 펋 145-234-2-356
+always 펌 145-234-26
+always 펍 145-234-12
+always 펎 145-234-12-3
+always 펏 145-234-3
+always 펐 145-234-3-3
+always 펑 145-234-2356
+always 펒 145-234-13
+always 펓 145-234-23
+always 펔 145-234-235
+always 펕 145-234-236
+always 펖 145-234-256
+always 펗 145-234-356
+always 페 145-1345
+always 펙 145-1345-1
+always 펚 145-1345-1-1
+always 펛 145-1345-1-3
+always 펜 145-1345-25
+always 펝 145-1345-25-13
+always 펞 145-1345-25-356
+always 펟 145-1345-35
+always 펠 145-1345-2
+always 펡 145-1345-2-1
+always 펢 145-1345-2-26
+always 펣 145-1345-2-12
+always 펤 145-1345-2-3
+always 펥 145-1345-2-236
+always 펦 145-1345-2-256
+always 펧 145-1345-2-356
+always 펨 145-1345-26
+always 펩 145-1345-12
+always 펪 145-1345-12-3
+always 펫 145-1345-3
+always 펬 145-1345-3-3
+always 펭 145-1345-2356
+always 펮 145-1345-13
+always 펯 145-1345-23
+always 펰 145-1345-235
+always 펱 145-1345-236
+always 펲 145-1345-256
+always 펳 145-1345-356
+always 펴 145-156
+always 펵 145-156-1
+always 펶 145-156-1-1
+always 펷 145-156-1-3
+always 편 145-156-25
+always 펹 145-156-25-13
+always 펺 145-156-25-356
+always 펻 145-156-35
+always 펼 145-156-2
+always 펽 145-156-2-1
+always 펾 145-156-2-26
+always 펿 145-156-2-12
+always 폀 145-156-2-3
+always 폁 145-156-2-236
+always 폂 145-156-2-256
+always 폃 145-156-2-356
+always 폄 145-156-26
+always 폅 145-156-12
+always 폆 145-156-12-3
+always 폇 145-156-3
+always 폈 145-156-3-3
+always 평 145-156-2356
+always 폊 145-156-13
+always 폋 145-156-23
+always 폌 145-156-235
+always 폍 145-156-236
+always 폎 145-156-256
+always 폏 145-156-356
+always 폐 145-34
+always 폑 145-34-1
+always 폒 145-34-1-1
+always 폓 145-34-1-3
+always 폔 145-34-25
+always 폕 145-34-25-13
+always 폖 145-34-25-356
+always 폗 145-34-35
+always 폘 145-34-2
+always 폙 145-34-2-1
+always 폚 145-34-2-26
+always 폛 145-34-2-12
+always 폜 145-34-2-3
+always 폝 145-34-2-236
+always 폞 145-34-2-256
+always 폟 145-34-2-356
+always 폠 145-34-26
+always 폡 145-34-12
+always 폢 145-34-12-3
+always 폣 145-34-3
+always 폤 145-34-3-3
+always 폥 145-34-2356
+always 폦 145-34-13
+always 폧 145-34-23
+always 폨 145-34-235
+always 폩 145-34-236
+always 폪 145-34-256
+always 폫 145-34-356
+always 포 145-136
+always 폭 145-136-1
+always 폮 145-136-1-1
+always 폯 145-136-1-3
+always 폰 145-136-25
+always 폱 145-136-25-13
+always 폲 145-136-25-356
+always 폳 145-136-35
+always 폴 145-136-2
+always 폵 145-136-2-1
+always 폶 145-136-2-26
+always 폷 145-136-2-12
+always 폸 145-136-2-3
+always 폹 145-136-2-236
+always 폺 145-136-2-256
+always 폻 145-136-2-356
+always 폼 145-136-26
+always 폽 145-136-12
+always 폾 145-136-12-3
+always 폿 145-136-3
+always 퐀 145-136-3-3
+always 퐁 145-136-2356
+always 퐂 145-136-13
+always 퐃 145-136-23
+always 퐄 145-136-235
+always 퐅 145-136-236
+always 퐆 145-136-256
+always 퐇 145-136-356
+always 퐈 145-1236
+always 퐉 145-1236-1
+always 퐊 145-1236-1-1
+always 퐋 145-1236-1-3
+always 퐌 145-1236-25
+always 퐍 145-1236-25-13
+always 퐎 145-1236-25-356
+always 퐏 145-1236-35
+always 퐐 145-1236-2
+always 퐑 145-1236-2-1
+always 퐒 145-1236-2-26
+always 퐓 145-1236-2-12
+always 퐔 145-1236-2-3
+always 퐕 145-1236-2-236
+always 퐖 145-1236-2-256
+always 퐗 145-1236-2-356
+always 퐘 145-1236-26
+always 퐙 145-1236-12
+always 퐚 145-1236-12-3
+always 퐛 145-1236-3
+always 퐜 145-1236-3-3
+always 퐝 145-1236-2356
+always 퐞 145-1236-13
+always 퐟 145-1236-23
+always 퐠 145-1236-235
+always 퐡 145-1236-236
+always 퐢 145-1236-256
+always 퐣 145-1236-356
+always 퐤 145-1236-1235
+always 퐥 145-1236-1235-1
+always 퐦 145-1236-1235-1-1
+always 퐧 145-1236-1235-1-3
+always 퐨 145-1236-1235-25
+always 퐩 145-1236-1235-25-13
+always 퐪 145-1236-1235-25-356
+always 퐫 145-1236-1235-35
+always 퐬 145-1236-1235-2
+always 퐭 145-1236-1235-2-1
+always 퐮 145-1236-1235-2-26
+always 퐯 145-1236-1235-2-12
+always 퐰 145-1236-1235-2-3
+always 퐱 145-1236-1235-2-236
+always 퐲 145-1236-1235-2-256
+always 퐳 145-1236-1235-2-356
+always 퐴 145-1236-1235-26
+always 퐵 145-1236-1235-12
+always 퐶 145-1236-1235-12-3
+always 퐷 145-1236-1235-3
+always 퐸 145-1236-1235-3-3
+always 퐹 145-1236-1235-2356
+always 퐺 145-1236-1235-13
+always 퐻 145-1236-1235-23
+always 퐼 145-1236-1235-235
+always 퐽 145-1236-1235-236
+always 퐾 145-1236-1235-256
+always 퐿 145-1236-1235-356
+always 푀 145-13456
+always 푁 145-13456-1
+always 푂 145-13456-1-1
+always 푃 145-13456-1-3
+always 푄 145-13456-25
+always 푅 145-13456-25-13
+always 푆 145-13456-25-356
+always 푇 145-13456-35
+always 푈 145-13456-2
+always 푉 145-13456-2-1
+always 푊 145-13456-2-26
+always 푋 145-13456-2-12
+always 푌 145-13456-2-3
+always 푍 145-13456-2-236
+always 푎 145-13456-2-256
+always 푏 145-13456-2-356
+always 푐 145-13456-26
+always 푑 145-13456-12
+always 푒 145-13456-12-3
+always 푓 145-13456-3
+always 푔 145-13456-3-3
+always 푕 145-13456-2356
+always 푖 145-13456-13
+always 푗 145-13456-23
+always 푘 145-13456-235
+always 푙 145-13456-236
+always 푚 145-13456-256
+always 푛 145-13456-356
+always 표 145-346
+always 푝 145-346-1
+always 푞 145-346-1-1
+always 푟 145-346-1-3
+always 푠 145-346-25
+always 푡 145-346-25-13
+always 푢 145-346-25-356
+always 푣 145-346-35
+always 푤 145-346-2
+always 푥 145-346-2-1
+always 푦 145-346-2-26
+always 푧 145-346-2-12
+always 푨 145-346-2-3
+always 푩 145-346-2-236
+always 푪 145-346-2-256
+always 푫 145-346-2-356
+always 푬 145-346-26
+always 푭 145-346-12
+always 푮 145-346-12-3
+always 푯 145-346-3
+always 푰 145-346-3-3
+always 푱 145-346-2356
+always 푲 145-346-13
+always 푳 145-346-23
+always 푴 145-346-235
+always 푵 145-346-236
+always 푶 145-346-256
+always 푷 145-346-356
+always 푸 145-134
+always 푹 145-134-1
+always 푺 145-134-1-1
+always 푻 145-134-1-3
+always 푼 145-134-25
+always 푽 145-134-25-13
+always 푾 145-134-25-356
+always 푿 145-134-35
+always 풀 145-134-2
+always 풁 145-134-2-1
+always 풂 145-134-2-26
+always 풃 145-134-2-12
+always 풄 145-134-2-3
+always 풅 145-134-2-236
+always 풆 145-134-2-256
+always 풇 145-134-2-356
+always 품 145-134-26
+always 풉 145-134-12
+always 풊 145-134-12-3
+always 풋 145-134-3
+always 풌 145-134-3-3
+always 풍 145-134-2356
+always 풎 145-134-13
+always 풏 145-134-23
+always 풐 145-134-235
+always 풑 145-134-236
+always 풒 145-134-256
+always 풓 145-134-356
+always 풔 145-1234
+always 풕 145-1234-1
+always 풖 145-1234-1-1
+always 풗 145-1234-1-3
+always 풘 145-1234-25
+always 풙 145-1234-25-13
+always 풚 145-1234-25-356
+always 풛 145-1234-35
+always 풜 145-1234-2
+always 풝 145-1234-2-1
+always 풞 145-1234-2-26
+always 풟 145-1234-2-12
+always 풠 145-1234-2-3
+always 풡 145-1234-2-236
+always 풢 145-1234-2-256
+always 풣 145-1234-2-356
+always 풤 145-1234-26
+always 풥 145-1234-12
+always 풦 145-1234-12-3
+always 풧 145-1234-3
+always 풨 145-1234-3-3
+always 풩 145-1234-2356
+always 풪 145-1234-13
+always 풫 145-1234-23
+always 풬 145-1234-235
+always 풭 145-1234-236
+always 풮 145-1234-256
+always 풯 145-1234-356
+always 풰 145-1234-1235
+always 풱 145-1234-1235-1
+always 풲 145-1234-1235-1-1
+always 풳 145-1234-1235-1-3
+always 풴 145-1234-1235-25
+always 풵 145-1234-1235-25-13
+always 풶 145-1234-1235-25-356
+always 풷 145-1234-1235-35
+always 풸 145-1234-1235-2
+always 풹 145-1234-1235-2-1
+always 풺 145-1234-1235-2-26
+always 풻 145-1234-1235-2-12
+always 풼 145-1234-1235-2-3
+always 풽 145-1234-1235-2-236
+always 풾 145-1234-1235-2-256
+always 풿 145-1234-1235-2-356
+always 퓀 145-1234-1235-26
+always 퓁 145-1234-1235-12
+always 퓂 145-1234-1235-12-3
+always 퓃 145-1234-1235-3
+always 퓄 145-1234-1235-3-3
+always 퓅 145-1234-1235-2356
+always 퓆 145-1234-1235-13
+always 퓇 145-1234-1235-23
+always 퓈 145-1234-1235-235
+always 퓉 145-1234-1235-236
+always 퓊 145-1234-1235-256
+always 퓋 145-1234-1235-356
+always 퓌 145-134-1235
+always 퓍 145-134-1235-1
+always 퓎 145-134-1235-1-1
+always 퓏 145-134-1235-1-3
+always 퓐 145-134-1235-25
+always 퓑 145-134-1235-25-13
+always 퓒 145-134-1235-25-356
+always 퓓 145-134-1235-35
+always 퓔 145-134-1235-2
+always 퓕 145-134-1235-2-1
+always 퓖 145-134-1235-2-26
+always 퓗 145-134-1235-2-12
+always 퓘 145-134-1235-2-3
+always 퓙 145-134-1235-2-236
+always 퓚 145-134-1235-2-256
+always 퓛 145-134-1235-2-356
+always 퓜 145-134-1235-26
+always 퓝 145-134-1235-12
+always 퓞 145-134-1235-12-3
+always 퓟 145-134-1235-3
+always 퓠 145-134-1235-3-3
+always 퓡 145-134-1235-2356
+always 퓢 145-134-1235-13
+always 퓣 145-134-1235-23
+always 퓤 145-134-1235-235
+always 퓥 145-134-1235-236
+always 퓦 145-134-1235-256
+always 퓧 145-134-1235-356
+always 퓨 145-146
+always 퓩 145-146-1
+always 퓪 145-146-1-1
+always 퓫 145-146-1-3
+always 퓬 145-146-25
+always 퓭 145-146-25-13
+always 퓮 145-146-25-356
+always 퓯 145-146-35
+always 퓰 145-146-2
+always 퓱 145-146-2-1
+always 퓲 145-146-2-26
+always 퓳 145-146-2-12
+always 퓴 145-146-2-3
+always 퓵 145-146-2-236
+always 퓶 145-146-2-256
+always 퓷 145-146-2-356
+always 퓸 145-146-26
+always 퓹 145-146-12
+always 퓺 145-146-12-3
+always 퓻 145-146-3
+always 퓼 145-146-3-3
+always 퓽 145-146-2356
+always 퓾 145-146-13
+always 퓿 145-146-23
+always 픀 145-146-235
+always 픁 145-146-236
+always 픂 145-146-256
+always 픃 145-146-356
+always 프 145-246
+always 픅 145-246-1
+always 픆 145-246-1-1
+always 픇 145-246-1-3
+always 픈 145-246-25
+always 픉 145-246-25-13
+always 픊 145-246-25-356
+always 픋 145-246-35
+always 플 145-246-2
+always 픍 145-246-2-1
+always 픎 145-246-2-26
+always 픏 145-246-2-12
+always 픐 145-246-2-3
+always 픑 145-246-2-236
+always 픒 145-246-2-256
+always 픓 145-246-2-356
+always 픔 145-246-26
+always 픕 145-246-12
+always 픖 145-246-12-3
+always 픗 145-246-3
+always 픘 145-246-3-3
+always 픙 145-246-2356
+always 픚 145-246-13
+always 픛 145-246-23
+always 픜 145-246-235
+always 픝 145-246-236
+always 픞 145-246-256
+always 픟 145-246-356
+always 픠 145-2456
+always 픡 145-2456-1
+always 픢 145-2456-1-1
+always 픣 145-2456-1-3
+always 픤 145-2456-25
+always 픥 145-2456-25-13
+always 픦 145-2456-25-356
+always 픧 145-2456-35
+always 픨 145-2456-2
+always 픩 145-2456-2-1
+always 픪 145-2456-2-26
+always 픫 145-2456-2-12
+always 픬 145-2456-2-3
+always 픭 145-2456-2-236
+always 픮 145-2456-2-256
+always 픯 145-2456-2-356
+always 픰 145-2456-26
+always 픱 145-2456-12
+always 픲 145-2456-12-3
+always 픳 145-2456-3
+always 픴 145-2456-3-3
+always 픵 145-2456-2356
+always 픶 145-2456-13
+always 픷 145-2456-23
+always 픸 145-2456-235
+always 픹 145-2456-236
+always 픺 145-2456-256
+always 픻 145-2456-356
+always 피 145-135
+always 픽 145-135-1
+always 픾 145-135-1-1
+always 픿 145-135-1-3
+always 핀 145-135-25
+always 핁 145-135-25-13
+always 핂 145-135-25-356
+always 핃 145-135-35
+always 필 145-135-2
+always 핅 145-135-2-1
+always 핆 145-135-2-26
+always 핇 145-135-2-12
+always 핈 145-135-2-3
+always 핉 145-135-2-236
+always 핊 145-135-2-256
+always 핋 145-135-2-356
+always 핌 145-135-26
+always 핍 145-135-12
+always 핎 145-135-12-3
+always 핏 145-135-3
+always 핐 145-135-3-3
+always 핑 145-135-2356
+always 핒 145-135-13
+always 핓 145-135-23
+always 핔 145-135-235
+always 핕 145-135-236
+always 핖 145-135-256
+always 핗 145-135-356
+always 하 245-126
+always 학 245-126-1
+always 핚 245-126-1-1
+always 핛 245-126-1-3
+always 한 245-126-25
+always 핝 245-126-25-13
+always 핞 245-126-25-356
+always 핟 245-126-35
+always 할 245-126-2
+always 핡 245-126-2-1
+always 핢 245-126-2-26
+always 핣 245-126-2-12
+always 핤 245-126-2-3
+always 핥 245-126-2-236
+always 핦 245-126-2-256
+always 핧 245-126-2-356
+always 함 245-126-26
+always 합 245-126-12
+always 핪 245-126-12-3
+always 핫 245-126-3
+always 핬 245-126-3-3
+always 항 245-126-2356
+always 핮 245-126-13
+always 핯 245-126-23
+always 핰 245-126-235
+always 핱 245-126-236
+always 핲 245-126-256
+always 핳 245-126-356
+always 해 245-1235
+always 핵 245-1235-1
+always 핶 245-1235-1-1
+always 핷 245-1235-1-3
+always 핸 245-1235-25
+always 핹 245-1235-25-13
+always 핺 245-1235-25-356
+always 핻 245-1235-35
+always 핼 245-1235-2
+always 핽 245-1235-2-1
+always 핾 245-1235-2-26
+always 핿 245-1235-2-12
+always 햀 245-1235-2-3
+always 햁 245-1235-2-236
+always 햂 245-1235-2-256
+always 햃 245-1235-2-356
+always 햄 245-1235-26
+always 햅 245-1235-12
+always 햆 245-1235-12-3
+always 햇 245-1235-3
+always 했 245-1235-3-3
+always 행 245-1235-2356
+always 햊 245-1235-13
+always 햋 245-1235-23
+always 햌 245-1235-235
+always 햍 245-1235-236
+always 햎 245-1235-256
+always 햏 245-1235-356
+always 햐 245-345
+always 햑 245-345-1
+always 햒 245-345-1-1
+always 햓 245-345-1-3
+always 햔 245-345-25
+always 햕 245-345-25-13
+always 햖 245-345-25-356
+always 햗 245-345-35
+always 햘 245-345-2
+always 햙 245-345-2-1
+always 햚 245-345-2-26
+always 햛 245-345-2-12
+always 햜 245-345-2-3
+always 햝 245-345-2-236
+always 햞 245-345-2-256
+always 햟 245-345-2-356
+always 햠 245-345-26
+always 햡 245-345-12
+always 햢 245-345-12-3
+always 햣 245-345-3
+always 햤 245-345-3-3
+always 향 245-345-2356
+always 햦 245-345-13
+always 햧 245-345-23
+always 햨 245-345-235
+always 햩 245-345-236
+always 햪 245-345-256
+always 햫 245-345-356
+always 햬 245-345-1235
+always 햭 245-345-1235-1
+always 햮 245-345-1235-1-1
+always 햯 245-345-1235-1-3
+always 햰 245-345-1235-25
+always 햱 245-345-1235-25-13
+always 햲 245-345-1235-25-356
+always 햳 245-345-1235-35
+always 햴 245-345-1235-2
+always 햵 245-345-1235-2-1
+always 햶 245-345-1235-2-26
+always 햷 245-345-1235-2-12
+always 햸 245-345-1235-2-3
+always 햹 245-345-1235-2-236
+always 햺 245-345-1235-2-256
+always 햻 245-345-1235-2-356
+always 햼 245-345-1235-26
+always 햽 245-345-1235-12
+always 햾 245-345-1235-12-3
+always 햿 245-345-1235-3
+always 헀 245-345-1235-3-3
+always 헁 245-345-1235-2356
+always 헂 245-345-1235-13
+always 헃 245-345-1235-23
+always 헄 245-345-1235-235
+always 헅 245-345-1235-236
+always 헆 245-345-1235-256
+always 헇 245-345-1235-356
+always 허 245-234
+always 헉 245-234-1
+always 헊 245-234-1-1
+always 헋 245-234-1-3
+always 헌 245-234-25
+always 헍 245-234-25-13
+always 헎 245-234-25-356
+always 헏 245-234-35
+always 헐 245-234-2
+always 헑 245-234-2-1
+always 헒 245-234-2-26
+always 헓 245-234-2-12
+always 헔 245-234-2-3
+always 헕 245-234-2-236
+always 헖 245-234-2-256
+always 헗 245-234-2-356
+always 험 245-234-26
+always 헙 245-234-12
+always 헚 245-234-12-3
+always 헛 245-234-3
+always 헜 245-234-3-3
+always 헝 245-234-2356
+always 헞 245-234-13
+always 헟 245-234-23
+always 헠 245-234-235
+always 헡 245-234-236
+always 헢 245-234-256
+always 헣 245-234-356
+always 헤 245-1345
+always 헥 245-1345-1
+always 헦 245-1345-1-1
+always 헧 245-1345-1-3
+always 헨 245-1345-25
+always 헩 245-1345-25-13
+always 헪 245-1345-25-356
+always 헫 245-1345-35
+always 헬 245-1345-2
+always 헭 245-1345-2-1
+always 헮 245-1345-2-26
+always 헯 245-1345-2-12
+always 헰 245-1345-2-3
+always 헱 245-1345-2-236
+always 헲 245-1345-2-256
+always 헳 245-1345-2-356
+always 헴 245-1345-26
+always 헵 245-1345-12
+always 헶 245-1345-12-3
+always 헷 245-1345-3
+always 헸 245-1345-3-3
+always 헹 245-1345-2356
+always 헺 245-1345-13
+always 헻 245-1345-23
+always 헼 245-1345-235
+always 헽 245-1345-236
+always 헾 245-1345-256
+always 헿 245-1345-356
+always 혀 245-156
+always 혁 245-156-1
+always 혂 245-156-1-1
+always 혃 245-156-1-3
+always 현 245-156-25
+always 혅 245-156-25-13
+always 혆 245-156-25-356
+always 혇 245-156-35
+always 혈 245-156-2
+always 혉 245-156-2-1
+always 혊 245-156-2-26
+always 혋 245-156-2-12
+always 혌 245-156-2-3
+always 혍 245-156-2-236
+always 혎 245-156-2-256
+always 혏 245-156-2-356
+always 혐 245-156-26
+always 협 245-156-12
+always 혒 245-156-12-3
+always 혓 245-156-3
+always 혔 245-156-3-3
+always 형 245-156-2356
+always 혖 245-156-13
+always 혗 245-156-23
+always 혘 245-156-235
+always 혙 245-156-236
+always 혚 245-156-256
+always 혛 245-156-356
+always 혜 245-34
+always 혝 245-34-1
+always 혞 245-34-1-1
+always 혟 245-34-1-3
+always 혠 245-34-25
+always 혡 245-34-25-13
+always 혢 245-34-25-356
+always 혣 245-34-35
+always 혤 245-34-2
+always 혥 245-34-2-1
+always 혦 245-34-2-26
+always 혧 245-34-2-12
+always 혨 245-34-2-3
+always 혩 245-34-2-236
+always 혪 245-34-2-256
+always 혫 245-34-2-356
+always 혬 245-34-26
+always 혭 245-34-12
+always 혮 245-34-12-3
+always 혯 245-34-3
+always 혰 245-34-3-3
+always 혱 245-34-2356
+always 혲 245-34-13
+always 혳 245-34-23
+always 혴 245-34-235
+always 혵 245-34-236
+always 혶 245-34-256
+always 혷 245-34-356
+always 호 245-136
+always 혹 245-136-1
+always 혺 245-136-1-1
+always 혻 245-136-1-3
+always 혼 245-136-25
+always 혽 245-136-25-13
+always 혾 245-136-25-356
+always 혿 245-136-35
+always 홀 245-136-2
+always 홁 245-136-2-1
+always 홂 245-136-2-26
+always 홃 245-136-2-12
+always 홄 245-136-2-3
+always 홅 245-136-2-236
+always 홆 245-136-2-256
+always 홇 245-136-2-356
+always 홈 245-136-26
+always 홉 245-136-12
+always 홊 245-136-12-3
+always 홋 245-136-3
+always 홌 245-136-3-3
+always 홍 245-136-2356
+always 홎 245-136-13
+always 홏 245-136-23
+always 홐 245-136-235
+always 홑 245-136-236
+always 홒 245-136-256
+always 홓 245-136-356
+always 화 245-1236
+always 확 245-1236-1
+always 홖 245-1236-1-1
+always 홗 245-1236-1-3
+always 환 245-1236-25
+always 홙 245-1236-25-13
+always 홚 245-1236-25-356
+always 홛 245-1236-35
+always 활 245-1236-2
+always 홝 245-1236-2-1
+always 홞 245-1236-2-26
+always 홟 245-1236-2-12
+always 홠 245-1236-2-3
+always 홡 245-1236-2-236
+always 홢 245-1236-2-256
+always 홣 245-1236-2-356
+always 홤 245-1236-26
+always 홥 245-1236-12
+always 홦 245-1236-12-3
+always 홧 245-1236-3
+always 홨 245-1236-3-3
+always 황 245-1236-2356
+always 홪 245-1236-13
+always 홫 245-1236-23
+always 홬 245-1236-235
+always 홭 245-1236-236
+always 홮 245-1236-256
+always 홯 245-1236-356
+always 홰 245-1236-1235
+always 홱 245-1236-1235-1
+always 홲 245-1236-1235-1-1
+always 홳 245-1236-1235-1-3
+always 홴 245-1236-1235-25
+always 홵 245-1236-1235-25-13
+always 홶 245-1236-1235-25-356
+always 홷 245-1236-1235-35
+always 홸 245-1236-1235-2
+always 홹 245-1236-1235-2-1
+always 홺 245-1236-1235-2-26
+always 홻 245-1236-1235-2-12
+always 홼 245-1236-1235-2-3
+always 홽 245-1236-1235-2-236
+always 홾 245-1236-1235-2-256
+always 홿 245-1236-1235-2-356
+always 횀 245-1236-1235-26
+always 횁 245-1236-1235-12
+always 횂 245-1236-1235-12-3
+always 횃 245-1236-1235-3
+always 횄 245-1236-1235-3-3
+always 횅 245-1236-1235-2356
+always 횆 245-1236-1235-13
+always 횇 245-1236-1235-23
+always 횈 245-1236-1235-235
+always 횉 245-1236-1235-236
+always 횊 245-1236-1235-256
+always 횋 245-1236-1235-356
+always 회 245-13456
+always 획 245-13456-1
+always 횎 245-13456-1-1
+always 횏 245-13456-1-3
+always 횐 245-13456-25
+always 횑 245-13456-25-13
+always 횒 245-13456-25-356
+always 횓 245-13456-35
+always 횔 245-13456-2
+always 횕 245-13456-2-1
+always 횖 245-13456-2-26
+always 횗 245-13456-2-12
+always 횘 245-13456-2-3
+always 횙 245-13456-2-236
+always 횚 245-13456-2-256
+always 횛 245-13456-2-356
+always 횜 245-13456-26
+always 횝 245-13456-12
+always 횞 245-13456-12-3
+always 횟 245-13456-3
+always 횠 245-13456-3-3
+always 횡 245-13456-2356
+always 횢 245-13456-13
+always 횣 245-13456-23
+always 횤 245-13456-235
+always 횥 245-13456-236
+always 횦 245-13456-256
+always 횧 245-13456-356
+always 효 245-346
+always 횩 245-346-1
+always 횪 245-346-1-1
+always 횫 245-346-1-3
+always 횬 245-346-25
+always 횭 245-346-25-13
+always 횮 245-346-25-356
+always 횯 245-346-35
+always 횰 245-346-2
+always 횱 245-346-2-1
+always 횲 245-346-2-26
+always 횳 245-346-2-12
+always 횴 245-346-2-3
+always 횵 245-346-2-236
+always 횶 245-346-2-256
+always 횷 245-346-2-356
+always 횸 245-346-26
+always 횹 245-346-12
+always 횺 245-346-12-3
+always 횻 245-346-3
+always 횼 245-346-3-3
+always 횽 245-346-2356
+always 횾 245-346-13
+always 횿 245-346-23
+always 훀 245-346-235
+always 훁 245-346-236
+always 훂 245-346-256
+always 훃 245-346-356
+always 후 245-134
+always 훅 245-134-1
+always 훆 245-134-1-1
+always 훇 245-134-1-3
+always 훈 245-134-25
+always 훉 245-134-25-13
+always 훊 245-134-25-356
+always 훋 245-134-35
+always 훌 245-134-2
+always 훍 245-134-2-1
+always 훎 245-134-2-26
+always 훏 245-134-2-12
+always 훐 245-134-2-3
+always 훑 245-134-2-236
+always 훒 245-134-2-256
+always 훓 245-134-2-356
+always 훔 245-134-26
+always 훕 245-134-12
+always 훖 245-134-12-3
+always 훗 245-134-3
+always 훘 245-134-3-3
+always 훙 245-134-2356
+always 훚 245-134-13
+always 훛 245-134-23
+always 훜 245-134-235
+always 훝 245-134-236
+always 훞 245-134-256
+always 훟 245-134-356
+always 훠 245-1234
+always 훡 245-1234-1
+always 훢 245-1234-1-1
+always 훣 245-1234-1-3
+always 훤 245-1234-25
+always 훥 245-1234-25-13
+always 훦 245-1234-25-356
+always 훧 245-1234-35
+always 훨 245-1234-2
+always 훩 245-1234-2-1
+always 훪 245-1234-2-26
+always 훫 245-1234-2-12
+always 훬 245-1234-2-3
+always 훭 245-1234-2-236
+always 훮 245-1234-2-256
+always 훯 245-1234-2-356
+always 훰 245-1234-26
+always 훱 245-1234-12
+always 훲 245-1234-12-3
+always 훳 245-1234-3
+always 훴 245-1234-3-3
+always 훵 245-1234-2356
+always 훶 245-1234-13
+always 훷 245-1234-23
+always 훸 245-1234-235
+always 훹 245-1234-236
+always 훺 245-1234-256
+always 훻 245-1234-356
+always 훼 245-1234-1235
+always 훽 245-1234-1235-1
+always 훾 245-1234-1235-1-1
+always 훿 245-1234-1235-1-3
+always 휀 245-1234-1235-25
+always 휁 245-1234-1235-25-13
+always 휂 245-1234-1235-25-356
+always 휃 245-1234-1235-35
+always 휄 245-1234-1235-2
+always 휅 245-1234-1235-2-1
+always 휆 245-1234-1235-2-26
+always 휇 245-1234-1235-2-12
+always 휈 245-1234-1235-2-3
+always 휉 245-1234-1235-2-236
+always 휊 245-1234-1235-2-256
+always 휋 245-1234-1235-2-356
+always 휌 245-1234-1235-26
+always 휍 245-1234-1235-12
+always 휎 245-1234-1235-12-3
+always 휏 245-1234-1235-3
+always 휐 245-1234-1235-3-3
+always 휑 245-1234-1235-2356
+always 휒 245-1234-1235-13
+always 휓 245-1234-1235-23
+always 휔 245-1234-1235-235
+always 휕 245-1234-1235-236
+always 휖 245-1234-1235-256
+always 휗 245-1234-1235-356
+always 휘 245-134-1235
+always 휙 245-134-1235-1
+always 휚 245-134-1235-1-1
+always 휛 245-134-1235-1-3
+always 휜 245-134-1235-25
+always 휝 245-134-1235-25-13
+always 휞 245-134-1235-25-356
+always 휟 245-134-1235-35
+always 휠 245-134-1235-2
+always 휡 245-134-1235-2-1
+always 휢 245-134-1235-2-26
+always 휣 245-134-1235-2-12
+always 휤 245-134-1235-2-3
+always 휥 245-134-1235-2-236
+always 휦 245-134-1235-2-256
+always 휧 245-134-1235-2-356
+always 휨 245-134-1235-26
+always 휩 245-134-1235-12
+always 휪 245-134-1235-12-3
+always 휫 245-134-1235-3
+always 휬 245-134-1235-3-3
+always 휭 245-134-1235-2356
+always 휮 245-134-1235-13
+always 휯 245-134-1235-23
+always 휰 245-134-1235-235
+always 휱 245-134-1235-236
+always 휲 245-134-1235-256
+always 휳 245-134-1235-356
+always 휴 245-146
+always 휵 245-146-1
+always 휶 245-146-1-1
+always 휷 245-146-1-3
+always 휸 245-146-25
+always 휹 245-146-25-13
+always 휺 245-146-25-356
+always 휻 245-146-35
+always 휼 245-146-2
+always 휽 245-146-2-1
+always 휾 245-146-2-26
+always 휿 245-146-2-12
+always 흀 245-146-2-3
+always 흁 245-146-2-236
+always 흂 245-146-2-256
+always 흃 245-146-2-356
+always 흄 245-146-26
+always 흅 245-146-12
+always 흆 245-146-12-3
+always 흇 245-146-3
+always 흈 245-146-3-3
+always 흉 245-146-2356
+always 흊 245-146-13
+always 흋 245-146-23
+always 흌 245-146-235
+always 흍 245-146-236
+always 흎 245-146-256
+always 흏 245-146-356
+always 흐 245-246
+always 흑 245-246-1
+always 흒 245-246-1-1
+always 흓 245-246-1-3
+always 흔 245-246-25
+always 흕 245-246-25-13
+always 흖 245-246-25-356
+always 흗 245-246-35
+always 흘 245-246-2
+always 흙 245-246-2-1
+always 흚 245-246-2-26
+always 흛 245-246-2-12
+always 흜 245-246-2-3
+always 흝 245-246-2-236
+always 흞 245-246-2-256
+always 흟 245-246-2-356
+always 흠 245-246-26
+always 흡 245-246-12
+always 흢 245-246-12-3
+always 흣 245-246-3
+always 흤 245-246-3-3
+always 흥 245-246-2356
+always 흦 245-246-13
+always 흧 245-246-23
+always 흨 245-246-235
+always 흩 245-246-236
+always 흪 245-246-256
+always 흫 245-246-356
+always 희 245-2456
+always 흭 245-2456-1
+always 흮 245-2456-1-1
+always 흯 245-2456-1-3
+always 흰 245-2456-25
+always 흱 245-2456-25-13
+always 흲 245-2456-25-356
+always 흳 245-2456-35
+always 흴 245-2456-2
+always 흵 245-2456-2-1
+always 흶 245-2456-2-26
+always 흷 245-2456-2-12
+always 흸 245-2456-2-3
+always 흹 245-2456-2-236
+always 흺 245-2456-2-256
+always 흻 245-2456-2-356
+always 흼 245-2456-26
+always 흽 245-2456-12
+always 흾 245-2456-12-3
+always 흿 245-2456-3
+always 힀 245-2456-3-3
+always 힁 245-2456-2356
+always 힂 245-2456-13
+always 힃 245-2456-23
+always 힄 245-2456-235
+always 힅 245-2456-236
+always 힆 245-2456-256
+always 힇 245-2456-356
+always 히 245-135
+always 힉 245-135-1
+always 힊 245-135-1-1
+always 힋 245-135-1-3
+always 힌 245-135-25
+always 힍 245-135-25-13
+always 힎 245-135-25-356
+always 힏 245-135-35
+always 힐 245-135-2
+always 힑 245-135-2-1
+always 힒 245-135-2-26
+always 힓 245-135-2-12
+always 힔 245-135-2-3
+always 힕 245-135-2-236
+always 힖 245-135-2-256
+always 힗 245-135-2-356
+always 힘 245-135-26
+always 힙 245-135-12
+always 힚 245-135-12-3
+always 힛 245-135-3
+always 힜 245-135-3-3
+always 힝 245-135-2356
+always 힞 245-135-13
+always 힟 245-135-23
+always 힠 245-135-235
+always 힡 245-135-236
+always 힢 245-135-256
+always 힣 245-135-356
+always ᅠ 0
+always ᄀ 1
+always ᄁ 1-1
+always ᆪ 1-3
+always ᄂ 25
+always ᆬ 25-13
+always ᆭ 25-356
+always ᄃ 35
+always ᄄ 35-35
+always ᄅ 2
+always ᆰ 2-1
+always ᆱ 2-26
+always ᆲ 2-12
+always ᆳ 2-3
+always ᆴ 2-236
+always ᆵ 2-256
+always ᄚ 2-356
+always ᄆ 26
+always ᄇ 12
+always ᄈ 12-12
+always ᄡ 12-3
+always ᄉ 3
+always ᄊ 3-3
+always ᄋ 2356
+always ᄌ 13
+always ᄍ 13-13
+always ᄎ 23
+always ᄏ 235
+always ᄐ 236
+always ᄑ 256
+always ᄒ 356
+always ᅡ 126
+always ᅢ 1235
+always ᅣ 345
+always ᅤ 345-1235
+always ᅥ 234
+always ᅦ 1345
+always ᅧ 156
+always ᅨ 34
+always ᅩ 136
+always ᅪ 1236
+always ᅫ 1236-1235
+always ᅬ 13456
+always ᅭ 346
+always ᅮ 134
+always ᅯ 1234
+always ᅰ 1234-1235
+always ᅱ 134-1235
+always ᅲ 146
+always ᅳ 246
+always ᅴ 2456
+always ᅵ 135
diff --git a/Tables/Contraction/ko-g1.ctb b/Tables/Contraction/ko-g1.ctb
new file mode 100644
index 0000000..5e64699
--- /dev/null
+++ b/Tables/Contraction/ko-g1.ctb
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Korean (grade 1)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+include ko-g0.ctb
+
+always 가 1246
+always 사 123
+always 것 456
+always 억 1456
+always 언 23456
+always 얼 2345
+always 연 16
+always 열 1256
+always 영 12456
+always 옥 1346
+always 온 12356
+always 옹 123456
+always 운 1245
+always 울 12346
+always 은 1356
+always 을 2346
+always 인 12345
diff --git a/Tables/Contraction/ko-g2.ctb b/Tables/Contraction/ko-g2.ctb
new file mode 100644
index 0000000..9f4b839
--- /dev/null
+++ b/Tables/Contraction/ko-g2.ctb
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Korean (grade 2)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+include ko-g1.ctb
+
+always 그래서	234
+always 그러나	14
+always 그러면	25
+always 그러므로	26
+always 그런데	1345
+always 그리고	136
+always 그리하여	156
+
+# inline contraction of emoji descriptions
+emoji ko
diff --git a/Tables/Contraction/ko.ctb b/Tables/Contraction/ko.ctb
new file mode 100644
index 0000000..833f6d9
--- /dev/null
+++ b/Tables/Contraction/ko.ctb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Korean
+
+include ko-g2.ctb
diff --git a/Tables/Contraction/latex-access.ctb b/Tables/Contraction/latex-access.ctb
new file mode 100755
index 0000000..2bd4e2f
--- /dev/null
+++ b/Tables/Contraction/latex-access.ctb
@@ -0,0 +1,216 @@
+#!/usr/bin/python3
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - latex-access (executable)
+
+import sys, os
+
+def putProgramMessage (message):
+  stream = sys.stderr
+  stream.write(os.path.basename(sys.argv[0]) + ": " + message + "\n")
+  stream.flush()
+
+def syntaxError (message):
+  putProgramMessage(message)
+  exit(2)
+
+def semanticError (message):
+  putProgramMessage(message)
+  exit(3)
+
+def systemError (message):
+  putProgramMessage(message)
+  exit(4)
+
+def missingPackage (name):
+  systemError("package not installed: " + name)
+
+def putResponseProperty (keyword, value):
+  value = unicode(value)
+  if programArguments.verbose: putProgramMessage("rsp: " + keyword + "=" + value)
+
+  stream = sys.stdout
+  stream.write(keyword + "=" + value.encode("UTF-8") + "\n")
+  stream.flush()
+
+def getRequest ():
+  request = {}
+
+  while True:
+    try:
+      if programArguments.interactive:
+        line = raw_input(packageName + "> ")
+      else:
+        line = raw_input()
+    except EOFError:
+      if len(request) == 0: return None
+      semanticError("unexpected end-of-file on standard input")
+
+    line = line.decode("UTF-8")
+    components = line.split("=", 1)
+
+    if len(components) > 1:
+      (keyword, value) = components
+      keyword = keyword.strip().lower()
+
+      if programArguments.verbose: putProgramMessage("req: " + keyword + "=" + value)
+      request[keyword] = value
+      if keyword == "text": break
+
+  return request
+
+def processRequest ():
+  request = getRequest()
+  if not request: return False
+
+  for attribute in ("displayLength", "cursorOffset", "expandCurrentWord", "consumedChars"):
+    if hasattr(brailleTranslator, attribute):
+      delattr(brailleTranslator, attribute)
+
+  if request.has_key("maximum-length"):
+    brailleTranslator.displayLength = int(request["maximum-length"])
+
+  if request.has_key("cursor-position"):
+    position = int(request["cursor-position"])
+
+    if position > 0:
+      brailleTranslator.cursorOffset = position - 1
+
+      if request.has_key("expand-current-word"):
+        brailleTranslator.expandCurrentWord = int(request["expand-current-word"]) != 0
+
+  brailleTranslator.capitalisation = "6dot"
+  if request.has_key("capitalization-mode"):
+    if int(request["capitalization-mode"]) != 1:
+      brailleTranslator.capitalisation = "8dot"
+
+  text = request["text"]
+  brf = brailleTranslator.translate(textPreprocessor.translate(text))
+
+  if hasattr(brailleTranslator, "consumedChars"):
+    consumedLength = brailleTranslator.consumedChars
+    putResponseProperty("consumed-length", consumedLength)
+  else:
+    consumedLength = len(text)
+
+  if hasattr(brailleTranslator, "src2trans"):
+    offsets = brailleTranslator.src2trans
+    if len(offsets) > consumedLength: offsets = offsets[:consumedLength]
+    putResponseProperty("output-offsets", ",".join((str(offset) for offset in offsets)))
+
+  putResponseProperty("brf", brf)
+  return True
+
+def parseProgramArguments ():
+  import argparse
+  parser = argparse.ArgumentParser(
+    prog = os.path.basename(sys.argv[0]),
+    usage = "%(prog)s [option ...]",
+    description = """
+      This is an executable contraction table for BRLTTY
+      that uses the latex-access package
+      to translate LaTeX mathematical notation into braille.
+      BRLTTY can be found at [http://brltty.app/].
+      latex-access can be found at [http://www.latex-access.sourceforge.net/].
+    """
+  )
+
+  parser.add_argument(
+    "-c", "--configuration",
+    action = "store",
+    nargs = 1,
+    dest = "configuration",
+    default = (
+      os.path.join(os.path.expanduser("~"), "." + packageName),
+      "/etc/" + packageName + ".conf"
+    ),
+    help = "the configuration file to use"
+  )
+
+  parser.add_argument(
+    "-n", "--nemeth",
+    action = "store_const",
+    const = "nemeth",
+    dest = "translator",
+    help = "translate into Nemeth Code"
+  )
+
+  parser.add_argument(
+    "-u", "--ueb",
+    action = "store_const",
+    const = "ueb",
+    dest = "translator",
+    help = "translate into Unified English Braille"
+  )
+
+  parser.add_argument(
+    "-i", "--interactive",
+    action = "store_true",
+    dest = "interactive",
+    help = "enable input editing and history"
+  )
+
+  parser.add_argument(
+    "-v", "--verbose",
+    action = "store_true",
+    dest = "verbose",
+    help = "show request and response properties on standard error"
+  )
+
+  return parser.parse_args()
+
+if __name__ == "__main__":
+  packageName = "latex-access"
+
+  programArguments = parseProgramArguments()
+
+  if programArguments.interactive:
+    import readline, atexit
+    historyFile=os.path.join(os.path.expanduser("~"), "." + packageName + ".history")
+    atexit.register(readline.write_history_file, historyFile)
+    readline.read_history_file(historyFile)
+
+  try:
+    from latex_access import nemeth, ueb, preprocessor, settings
+  except ImportError:
+    missingPackage(packageName)
+
+  for configurationFile in programArguments.configuration:
+    if os.path.exists(configurationFile):
+      if programArguments.verbose:
+        putProgramMessage("configuration file: " + configurationFile)
+
+      settings.loadSettings(configurationFile)
+      break
+
+  if programArguments.translator:
+    settings.settings["brailletable"] = programArguments.translator
+
+  brailleTranslator = settings.brailleTableToUse()
+  textPreprocessor = preprocessor.preprocessor()
+
+  settings.activateSettings(
+    {
+      "braille": brailleTranslator,
+      "preprocessor": textPreprocessor
+    }
+  )
+
+  while processRequest(): pass
+
diff --git a/Tables/Contraction/letters-latin.cti b/Tables/Contraction/letters-latin.cti
new file mode 100644
index 0000000..61cfefd
--- /dev/null
+++ b/Tables/Contraction/letters-latin.cti
@@ -0,0 +1,77 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY contraction subtable defines the standard braille representations
+# for the letters of the Latin alphabet.
+
+# lowercase letters
+always	\x61	1	LATIN SMALL LETTER A
+always	\x62	12	LATIN SMALL LETTER B
+always	\x63	14	LATIN SMALL LETTER C
+always	\x64	145	LATIN SMALL LETTER D
+always	\x65	15	LATIN SMALL LETTER E
+always	\x66	124	LATIN SMALL LETTER F
+always	\x67	1245	LATIN SMALL LETTER G
+always	\x68	125	LATIN SMALL LETTER H
+always	\x69	24	LATIN SMALL LETTER I
+always	\x6A	245	LATIN SMALL LETTER J
+always	\x6B	13	LATIN SMALL LETTER K
+always	\x6C	123	LATIN SMALL LETTER L
+always	\x6D	134	LATIN SMALL LETTER M
+always	\x6E	1345	LATIN SMALL LETTER N
+always	\x6F	135	LATIN SMALL LETTER O
+always	\x70	1234	LATIN SMALL LETTER P
+always	\x71	12345	LATIN SMALL LETTER Q
+always	\x72	1235	LATIN SMALL LETTER R
+always	\x73	234	LATIN SMALL LETTER S
+always	\x74	2345	LATIN SMALL LETTER T
+always	\x75	136	LATIN SMALL LETTER U
+always	\x76	1236	LATIN SMALL LETTER V
+always	\x77	2456	LATIN SMALL LETTER W
+always	\x78	1346	LATIN SMALL LETTER X
+always	\x79	13456	LATIN SMALL LETTER Y
+always	\x7A	1356	LATIN SMALL LETTER Z
+
+# uppercase letters
+always	\x41	1	LATIN CAPITAL LETTER A
+always	\x42	12	LATIN CAPITAL LETTER B
+always	\x43	14	LATIN CAPITAL LETTER C
+always	\x44	145	LATIN CAPITAL LETTER D
+always	\x45	15	LATIN CAPITAL LETTER E
+always	\x46	124	LATIN CAPITAL LETTER F
+always	\x47	1245	LATIN CAPITAL LETTER G
+always	\x48	125	LATIN CAPITAL LETTER H
+always	\x49	24	LATIN CAPITAL LETTER I
+always	\x4A	245	LATIN CAPITAL LETTER J
+always	\x4B	13	LATIN CAPITAL LETTER K
+always	\x4C	123	LATIN CAPITAL LETTER L
+always	\x4D	134	LATIN CAPITAL LETTER M
+always	\x4E	1345	LATIN CAPITAL LETTER N
+always	\x4F	135	LATIN CAPITAL LETTER O
+always	\x50	1234	LATIN CAPITAL LETTER P
+always	\x51	12345	LATIN CAPITAL LETTER Q
+always	\x52	1235	LATIN CAPITAL LETTER R
+always	\x53	234	LATIN CAPITAL LETTER S
+always	\x54	2345	LATIN CAPITAL LETTER T
+always	\x55	136	LATIN CAPITAL LETTER U
+always	\x56	1236	LATIN CAPITAL LETTER V
+always	\x57	2456	LATIN CAPITAL LETTER W
+always	\x58	1346	LATIN CAPITAL LETTER X
+always	\x59	13456	LATIN CAPITAL LETTER Y
+always	\x5A	1356	LATIN CAPITAL LETTER Z
+
diff --git a/Tables/Contraction/lt.ctb b/Tables/Contraction/lt.ctb
new file mode 100644
index 0000000..ae5b1cb
--- /dev/null
+++ b/Tables/Contraction/lt.ctb
@@ -0,0 +1,227 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Lithuanian (uncontracted)
+#
+# Copyright (C) 2017 Rimas Kudelis <rq@akl.lt>
+# Copyright (C) 2017 Tadas Matusevičius <tadas.matus@gmail.com>
+#
+# The Lithuanian 6-dot Braille alphabet is described in a decree
+# no. 878 of the Minister of Education, called "Dėl perėjimo prie
+# naujos lietuviškos Brailio rašto abėcėlės tvarkos ir programos",
+# which is in effect since 2000-07-08. Document number is 55-1620. At
+# the time of writing this file, the document was accessible at
+# https://www.e-tar.lt/portal/lt/legalAct/TAR.A110E8E6A83F .
+# The document is referred to as "the standard" below.
+#
+# The standard specifies only the mapping of Lithuanian letters to 6-dot
+# Braille writing system, no other characters are defined in it.
+# Definitions of some punctuation and other characters are informally
+# available on the Internet.
+#
+# This table builds on from these bits of information, but also adds
+# a number of other definitions to make it more useful in computing.
+#
+# This table is based on the respective liblouis table.
+
+###
+### WHITESPACE
+###
+
+include spaces.cti
+
+always \x09 0-0             U+0009 CHARACTER TABULATION
+
+
+###
+### LETTERS
+###
+
+# Base Latin letters
+include letters-latin.cti
+
+# Letters with diacritics which are part of the Lithuanian alphabet
+always ą 16
+always Ą 16
+always č 146
+always Č 146
+always ę 156
+always Ę 156
+always ė 345
+always Ė 345
+always į 246
+always Į 246
+always š 2346
+always Š 2346
+always ų 346
+always Ų 346
+always ū 1256
+always Ū 1256
+always ž 126
+always Ž 126
+
+
+###
+### DIGITS
+###
+
+always 1 1
+always 2 12
+always 3 14
+always 4 145
+always 5 15
+always 6 124
+always 7 1245
+always 8 125
+always 9 24
+always 0 245
+
+
+###
+### PUNCTUATION
+###
+
+always , 2
+always . 256
+always ? 26
+always ! 235
+always : 25
+always ; 23
+always " 4
+always ' 3
+# According to Unicode, this is the preferred character to use for apostrophe.
+always ’ 3                  U+2019 RIGHT SINGLE QUOTATION MARK
+
+always ( 2356
+always ) 2356
+always [ 12356
+always ] 23456
+always { 6-246
+always } 6-135
+
+always \x2D 36              U+002D - HYPHEN-MINUS
+always \u2013 6-36          U+2013 – EN DASH
+always \xAD 36              U+00AD [SOFT HYPHEN]
+always \u2010 36            U+2010 ‐ HYPHEN
+always \u2011 36            U+2011 ‑ NON-BREAKING HYPHEN
+always \u2012 36            U+2012 ‒ FIGURE DASH
+always \u2014 36            U+2014 — EM DASH
+always \u2015 36            U+2015 ― HORIZONTAL BAR
+
+# These are the typographically correct quotes in Lithuanian texts.
+always „ 236                U+201E DOUBLE LOW-9 QUOTATION MARK
+always “ 356                U+201C LEFT DOUBLE QUOTATION MARK
+
+# The following quotation characters should not be used in Lithuanian texts.
+always ” 6-4                U+201D RIGHT DOUBLE QUOTATION MARK
+always ” 6-4                U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+always « 6-4                U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+always » 6-4                U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+always ‹ 6-4                U+2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+always › 6-4                U+203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+
+always … 256-256-256        U+2026 HORIZONTAL ELLIPSIS
+repeatable … 256-256-256    U+2026 HORIZONTAL ELLIPSIS
+repeatable ... 256-256-256  Three times U+002E FULL STOP
+
+
+###
+### MATHEMATICAL SYMBOLS
+###
+
+always + 5-235
+always − 5-36               U+2212 MINUS SIGN
+always < 5-246
+always = 5-2356
+always > 5-135
+always ± 5-235-36
+always ∓ 5-36-235           U+2213 MINUS-OR-PLUS SIGN
+always × 5-3
+always ⋅ 5-3                U+22C5 DOT OPERATOR
+always ÷ 5-256
+always ∶ 5-256              U+2236 RATIO
+always ⁄ 34                 U+2044 FRACTION SLASH
+always ∕ 34                 U+2215 DIVISION SLASH
+
+
+###
+### OTHER CHARACTERS
+###
+
+always # 6-3456
+always $ 6-46
+always % 123456
+always & 12346
+always * 35
+always / 34
+always @ 6-345
+always \\ 6-34
+always ^ 6-256
+always _ 1456
+always | 6-456
+#always ¦ 6-1456
+always § 6-346
+#always ¬ 6-235
+always µ 6-134
+#always ¶ 6-1234
+
+always ` 6-3
+always ~ 6-26
+
+#always ¢ 6-14
+always £ 6-123
+always € 6-15               U+20AC EURO SIGN
+
+always • 6-35               U+2022 BULLET
+
+always © 2356-46-14-2356
+always ® 2356-46-1235-2356
+always ℗ 2356-46-1234-2356  U+2117 SOUND RECORDING COPYRIGHT
+
+always ← 246-25             U+2190 LEFTWARDS ARROW
+always → 25-135             U+2192 RIGHTWARDS ARROW
+
+# Middle dot is unlikely to appear in text, except perhaps as a multiplication sign (dot operator).
+always · 5-3                U+00B7 MIDDLE DOT
+
+always ° 5-356
+always ′ 5-35               U+2032 PRIME
+always ″ 5-35-35            U+2033 DOUBLE PRIME
+
+always ℃ 5-356-46-14        U+2103 DEGREE CELSIUS
+always ℉ 5-356-46-124       U+2109 DEGREE FAHRENHEIT
+
+
+###
+### INDICATOR AND SPECIAL SYMBOL DIRECTIVES
+###
+
+#always \uFFFD 7             U+FFFD � REPLACEMENT CHARACTER
+
+numsign 3456  number sign, just one operand
+letsign 56
+capsign 46
+begcaps 456
+endcaps 56
+
+midnum , 2
+midnum : 25
+midnum . 256
+
+# when a decimal begins with a period, it should be translated with a 
+# number sign followed by a decimal point, followed by the number.
diff --git a/Tables/Contraction/mg.ctb b/Tables/Contraction/mg.ctb
new file mode 100644
index 0000000..3f0c928
--- /dev/null
+++ b/Tables/Contraction/mg.ctb
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Malagasy (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always dz 245
+always ao 246
+always ai 34
diff --git a/Tables/Contraction/mun.ctb b/Tables/Contraction/mun.ctb
new file mode 100644
index 0000000..36c2809
--- /dev/null
+++ b/Tables/Contraction/mun.ctb
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Munda (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always ng 346
+always ch 16
+always ɛ 26
+always ɔ 246
+always ö 1256
diff --git a/Tables/Contraction/nabcc.cti b/Tables/Contraction/nabcc.cti
new file mode 100644
index 0000000..7deab08
--- /dev/null
+++ b/Tables/Contraction/nabcc.cti
@@ -0,0 +1,128 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY contraction subtable implements the North American Braille
+# Computer Code.
+
+include	letters-latin.cti
+
+always	\s	0
+
+always	a	1
+always	b	12
+always	c	14
+always	d	145
+always	e	15
+always	f	124
+always	g	1245
+always	h	125
+always	i	24
+always	j	245
+always	k	13
+always	l	123
+always	m	134
+always	n	1345
+always	o	135
+always	p	1234
+always	q	12345
+always	r	1235
+always	s	234
+always	t	2345
+always	u	136
+always	v	1236
+always	w	2456
+always	x	1346
+always	y	13456
+always	z	1356
+
+always	A	17
+always	B	127
+always	C	147
+always	D	1457
+always	E	157
+always	F	1247
+always	G	12457
+always	H	1257
+always	I	247
+always	J	2457
+always	K	137
+always	L	1237
+always	M	1347
+always	N	13457
+always	O	1357
+always	P	12347
+always	Q	123457
+always	R	12357
+always	S	2347
+always	T	23457
+always	U	1367
+always	V	12367
+always	W	24567
+always	X	13467
+always	Y	134567
+always	Z	13567
+
+always	1	2
+always	2	23
+always	3	25
+always	4	256
+always	5	26
+always	6	235
+always	7	2356
+always	8	236
+always	9	35
+always	0	356
+
+always	&	12346
+always	=	123456
+always	(	12356
+always	!	2346
+always	)	23456
+
+always	*	16
+always	<	126
+always	%	146
+always	?	1456
+always	:	156
+always	$	1246
+always	}	12456
+always	|	1256
+always	{	246
+
+always	]	124567
+always	\\	12567
+always	[	2467
+
+always	/	34
+always	+	346
+always	\#	3456
+always	>	345
+always	'	3
+always	-	36
+
+always	`	4
+always	~	45
+always	_	456
+always	"	5
+always	.	46
+always	;	56
+always	,	6
+
+always	@	47
+always	^	457
+
diff --git a/Tables/Contraction/nl.ctb b/Tables/Contraction/nl.ctb
new file mode 100644
index 0000000..884631d
--- /dev/null
+++ b/Tables/Contraction/nl.ctb
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Dutch (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+always ch 1456
+always ij 13456
+always oe 246
+always sch 156
+
+# inline contraction of emoji descriptions
+emoji nl
diff --git a/Tables/Contraction/none.ctb b/Tables/Contraction/none.ctb
new file mode 100644
index 0000000..7991915
--- /dev/null
+++ b/Tables/Contraction/none.ctb
@@ -0,0 +1,19 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - empty
diff --git a/Tables/Contraction/ny.ctb b/Tables/Contraction/ny.ctb
new file mode 100644
index 0000000..8bcd36e
--- /dev/null
+++ b/Tables/Contraction/ny.ctb
@@ -0,0 +1,33 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Chichewa (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always ng 346
+always ndi 12346
+always ch 16
+always th 1456
diff --git a/Tables/Contraction/pt.ctb b/Tables/Contraction/pt.ctb
new file mode 100644
index 0000000..6d3dd4d
--- /dev/null
+++ b/Tables/Contraction/pt.ctb
@@ -0,0 +1,489 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Portuguese (grade 2)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+class voy aeiouáâãèêìôòõú
+class con bcçdfghjklmnñpqrstvwxz
+
+word bem 12
+word cá 14
+word de 145
+word fim 124
+endword gue 1245
+word agora 1245
+word sobre 125
+word sôbre 125
+word ali 24
+always al 13
+word aos 13
+word ele 123
+word êle 123
+word me 134
+word não 1345
+word por 1234
+endword que 12345
+word que 12345
+word maior 1235
+word se 234
+word te 2345
+word um 136
+word vós 1236
+after con always ex 1346
+word mas 1346
+always os 13456
+word os 13456
+word ela 1356
+always ç 12346
+endword çao 12346
+word para 12346
+always é 123456
+always á 12356
+word lá 12356
+always è 2346
+midword ss 2346
+word assim 2346
+always ú 23456
+word até 23456
+always â 16
+word tudo 16
+always ê 126
+word mesmo 126
+always ì 146
+endword as 146
+word as 146
+always ô 1456
+word mais 1456
+always ù 156
+endword es 156
+word menos 156
+begmidword pl 1246
+always à 1246
+word à 1246
+begmidword gr 12456
+word grande 12456
+always ou 1256
+word ou 1256
+always õ 246
+word ainda 246
+always ò 2456
+word antes 2456
+always , 2
+begmidword br 23
+always ; 23
+begmidword con 25
+always : 25
+begmidword em 256
+always . 256
+word em 256
+begmidword en 26
+always ? 26
+word dentro 26
+begmidword pr 235
+always ! 235
+word nós 235
+always ( 2356
+midword nh 2356
+always ) 2356
+word nem 2356
+begword " 236
+midendword er 236
+word depressa 236
+begmidword in 35
+word isso 35
+begmidword tr 356
+endword " 356
+word contra 356
+begword re 3
+always ' 3
+word aõ 3
+begword com 36
+always - 36
+word com 36
+numsign 3456
+midendword ão 3456
+word são 3456
+always í 34
+word sim 34
+always ó 346
+begmidword am 345
+always ã 345
+word sem 345
+
+# terminations
+
+endword acidade 46-1
+endword antemente 56-1
+endword bilidade 46-12
+endword bilmente 56-12
+endword cional 5-14
+endword cionalidade 46-14
+endword cionalmente 56-14
+endword dor 4-145
+endword dora 5-4
+endword dade 46-145
+endword doramente 56-145
+endword eiro 4-15
+endword eira 5-15
+endword entemente 56-15
+endword fico 4-124
+endword fica 5-124
+endword ficamente 56-124
+endword gico 4-1245
+endword gica 5-1245
+endword gicamente 56-1245
+endword ismo 4-24
+endword ista 5-24
+endword ividade 46-24
+endword ivamente 56-24
+endword logo 4-123
+endword loga 5-123
+endword lidade 46-123
+endword logamente 56-123
+endword mento 4-134
+endword menta 5-134
+endword mente 56-134
+endword nico 4-1345
+endword nica 5-1345
+endword nicidade 46-1345
+endword nicamente 56-1345
+endword oiro 4-135
+endword oira 5-135
+endword osidade 46-135
+endword osamente 56-135
+endword posto 4-1234
+endword posta 5-1234
+endword postamente 56-1234
+endword roso 4-1235
+endword rosa 5-1235
+endword riedade 46-1235
+endword riamente 56-1235
+endword tico 4-2345
+endword tica 5-2345
+endword ticidade 46-2345
+endword ticamente 56-2345
+endword uro 4-136
+endword ura 5-136
+endword uridade 46-136
+endword uramente 56-136
+endword vel 5-1236
+endword velmente 56-1236
+endword zinho 4-1356
+endword zinha 5-1356
+endword ério 4-123456
+endword éria 5-123456
+endword ário 4-12356
+endword ária 5-12356
+endword ssimo 4-2346
+endword ssima 5-2346
+endword ssimamente 56-2346
+endword úrio 4-23456
+endword úria 5-23456
+endword âncio 4-16
+endword ância 5-16
+endword êncio 4-126
+endword ência 5-126
+endword grafo 4-12456
+endword grafa 5-12456
+endword ouro 4-1256
+endword oura 5-1256
+endword írio 4-14
+endword íria 5-14
+endword ório 4-346
+endword ória 5-346
+
+# two cell words
+
+word acolá 1-14
+word afim 1-124
+word alguém 1-1256
+word amor 1-134
+word apenas 1-1345
+word apesar 1-1234
+word aqui 1-12345
+word auxílio 1-1346
+word aliás 1-12356
+word após 1-346
+word amanhã 1-345
+word boca 12-14
+word bondoso 12-145
+word beijo 13-245
+word belo 12-123
+word Brazil 12-1235
+word baixo 12-1346
+word coisa 14-1
+word cabeça 14-12
+word cerca 14-14
+word cada 14-145
+word cede 14-15
+word cego 14-1245
+word cima 14-24
+word cujo 14-145
+word claro 14-123
+word como 14-134
+word corpo 14-1234
+word caro 14-1235
+word caso 14-234
+word carta 14-2345
+word certo 14-236
+word doce 145-14
+word desde 145-145
+word difícil 145-124
+word desejo 145-245
+word dele 145-123
+word duma 145-134
+word depois 145-1234
+word diante 145-2345
+word diverso 145-1234
+word esta 15-1
+word espécie 15-14
+word este 15-15
+word efeito 15-124
+word enorme 15-1345
+word evidente 15-1236
+word estes 15-156
+word exemplo 15-1246
+word fora 124-1
+word fácil 124-14
+word feliz 124-123
+word forma 124-134
+word força 124-135
+word fevereiro 124-1235
+word facto 124-2345
+word fato 124-2345
+word favor 124-1236
+word força 124-1456
+word gente 1245-15
+word geral 1245-1235
+word lha 125-1
+word hábito 125-12
+word lhe 125-15
+word hoje 125-245
+word homem 125-134
+word lho 125-135
+word hora 125-1235
+word história 125-2345
+word início 24-14
+word ideia 24-145
+word idéa 24-145
+word igual 24-1245
+word imediato 24-134
+word isto 24-2345
+word irmão 24-3456
+word irmã 24-1
+word julho 245-125
+word jamais 245-134
+word junto 245-1345
+word junho 245-135
+word janeiro 245-1235
+word justo 245-2345
+word juízo 245-1356
+word alfabeto 13-124
+word local 123-14
+word lado 123-145
+word logo 123-1245
+word lugar 123-1235
+word livro 123-1236
+word modo 134-145
+word mulher 134-125
+word maio 134-35
+word mim 134-134
+word menino 134-1345
+word melhor 134-1235
+word meus 134-234
+word muito 134-2345
+word março 134-12346
+word mínimo 134-34
+word manhã 134-345
+word nunca 1345-14
+word nada 1345-145
+word noite 1345-15
+word ninguém 1345-1245
+word nele 1345-123
+word numa 1345-134
+word número 1345-1235
+word nosso 1345-234
+word novo 1345-1236
+word noute 1345-1256
+word ordem 135-145
+word onde 135-15
+word objecto 135-245
+word ontem 135-2345
+word palavra 1234-1
+word pouco 1234-14
+word parte 1234-15
+word página 1234-1245
+word pelo 1234-123
+word porém 1234-134
+word pena 1234-1345
+word perto 1234-135
+word papel 1234-1234
+word porque 1234-12345
+word pior 1234-1235
+word pois 1234-234
+word ponto 1234-2345
+word povo 1234-1236
+word quando 12345-145
+word qual 12345-123
+word quem 12345-134
+word qualquer 12345-12345
+word quer 12345-1235
+word quais 12345-234
+word quanto 12345-2345
+word rapaz 1235-1234
+word raro 1235-1235
+word reto 1235-2345
+word razão 1235-1345
+word sua 234-1
+word século 234-14
+word saúde 234-145
+word suficiente 234-124
+word segundo 234-1245
+word sujeito 234-245
+word somente 234-134
+word senão 123-1345
+word sempre 234-1234
+word senhor 234-1235
+word seus 234-234
+word sobretudo 234-2345
+word simples 234-1246
+word também 2345-12
+word todo 2345-145
+word tempo 2345-1234
+word teus 2345-234
+word tanto 2345-2345
+word talvez 2345-1236
+word toda 2345-1456
+word vida 1236-1
+word verbo 1236-12
+word você 1236-14
+word verdade 1236-145
+word verão 1236-1235
+word vosso 1236-234
+word vivo 1236-1236
+word excelente 1346-123
+word este 126-15
+word outro 1256-135
+word outrora 1256-1235
+word braille 23-123
+word contudo 25-2345
+word embora 256-12
+word entre 26-15
+word enfim 26-124
+word primeiro 235-134
+word próprio 235-135
+
+# three cell words
+
+word abaixo 1-12-1346
+word acerca 1-14-14
+word acima 1-14-24
+word acaso 1-14-234
+word adiante 1-145-2345
+word afecto 1-124-2345
+word afeto 1-124-2345
+word algum 1-1245-134
+word agosto 1-1245-2345
+word além 1-123-134
+word amigo 1-134-1245
+word aonde 1-135-15
+word aonde 1-135-15
+word aquele 1-12345-15
+word aquém 1-12345-134
+word aquilo 1-12345-135
+word abril 1-23-123
+word brasileiro 12-1235-1235
+word comigo 14-134-1245
+word conosco 14-1345-14
+word capitulo 14-1234-2345
+word certamente 14-236-134
+word debaixo 145-12-1346
+word dificilmente 145-124-134
+word diferença 145-124-1235
+word domingo 145-134-1245
+word donde 145-135-15
+word dezembro 145-1356-12
+word evidentemente 15-1236-134
+word facilmente 124-14-134
+word futuro 124-2345-1235
+word geralmente 1245-1235-134
+word igualmente 24-1245-134
+word imediatamente 24-134-134
+word menor 134-1345-1235
+word minuto 134-1345-2345
+word metade 134-2356-145
+word minha 134-2356-1
+word necessidade 1345-14-145
+word necessário 1345-14-1235
+word novembro 1345-1236-12
+word nenhum 1345-2356-134
+word possibilidade 1234-12-145
+word perfeito 1234-124-2345
+word pequeno 1234-12345-1345
+word porquanto 1234-12345-2345
+word português 1234-2345-1245
+word portanto 1234-2345-2345
+word quaisquer 12345-234-12345
+word rapariga 1235-1234-1245
+word raramente 1235-1235-134
+word sábado 234-12-145
+word seguinte 234-1245-2345
+word semana 234-134-1345
+word superior 234-1234-1235
+word senhorita 234-1235-2345
+word setembro 234-2345-12
+word simplesmente 234-1246-134
+word todavia 2456-145-1236
+word exceto 1346-14-2345
+word externo 1346-2345-1345
+word exterior 1346-2345-1235
+word àquele 1246-12345-15
+word àquilo 1246-12345-135
+word outubro 1256-2345-12
+word outono 1256-2345-1345
+word connosco 25-1345-14
+word consigo 25-234-1245
+word contigo 25-2345-1245
+word convosco 25-1236-14
+word entretanto 26-15-2345-2345
+word enquanto 26-12345-2345
+word entanto 26-2345-2345
+word primavera 235-1236-1235
+word inferior 36-124-1235
+word interno 35-2345-1345
+word interior 35-2345-1235
+word inverno 35-1236-1345
+
+# compound words
+
+word quinta-feira 12345-1345-36-124
+word quarta-feira 12345-1235-36-124
+word segunda-feira 234-1245-36-124
+word sexta-feira 234-1346-36-124
+word terça-feira 2345-14-36-124
+
+# inline contraction of emoji descriptions
+emoji pt
diff --git a/Tables/Contraction/ru.ctb b/Tables/Contraction/ru.ctb
new file mode 100644
index 0000000..2855ce1
--- /dev/null
+++ b/Tables/Contraction/ru.ctb
@@ -0,0 +1,685 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Russian (grade 1)
+#By Kate <musicate@yandex.ru>
+#Based on Zubkov system
+
+class sch бвгджзйклмнопрстфхцчшщъыьэюя
+class con бвгджзйклмнпрстфхцчшщъь
+class vow аеёиоуыэюя
+class muf кпстфхцчшщ
+class voic бвгджзлмнр
+class con-r бвгджзйклмнпстфхцчшщъь
+class pred бвгмнп
+class mufs кпсфхцчшщъ
+class voicz блръ
+
+after vow midendword ы 6-2346
+after con midendword й 6-12346
+midendword ф 6-124
+midendword э 6-246
+always ё 5
+begword ю 6-1256
+
+#va
+sufword ваба =
+sufword ваца =
+sufword вада =
+sufword вага =
+sufword ваха =
+sufword важа =
+sufword вабе =
+sufword ваце =
+sufword ваде =
+sufword ваге =
+sufword вахе =
+sufword важе =
+sufword вами 3456-134-24
+sufword ваби =
+sufword ваци =
+sufword вади =
+sufword ваги =
+sufword вахи =
+sufword важи =
+
+
+
+always буди =
+always вельмож =
+always кобыл =
+always былое =
+always былого =
+
+always былому =
+always былым =
+always былом =
+
+
+#Letters
+always А 1
+always а 1
+always Б 12
+always б 12
+always В 2456
+always в 2456
+always Г 1245
+always г 1245
+always Д 145
+always д 145
+always Е 15
+always е 15
+always Ж 245
+always ж 245
+always З 1356
+always з 1356
+always И 24
+always и 24
+always Й 12346
+always й 12346
+always К 13
+always к 13
+always Л 123
+always л 123
+always М 134
+always м 134
+always Н 1345
+always н 1345
+always О 135
+always о 135
+always П 1234
+always п 1234
+always Р 1235
+always р 1235
+always С 234
+always с 234
+always Т 2345
+always т 2345
+always У 136
+always у 136
+always Ф 124
+always ф 124
+always Х 125
+always х 125
+always Ц 14
+always ц 14
+always Ч 12345
+always ч 12345
+always Ш 156
+always ш 156
+always Щ 1346
+always щ 1346
+always Ъ 12356
+always ъ 12356
+always Ы 2346
+always ы 2346
+always Ь 23456
+always ь 23456
+always Э 246
+always э 246
+always Ю 1256
+always ю 1256
+always Я 1246
+always я 1246
+
+
+#Table I
+
+always ва 3456
+begmidword ве 23456
+begmidword вё 23456
+always во 1236
+sufword вы 3
+always го 12456
+before sch always де 1346
+before sch always дё 1346
+always до 256
+always за 356
+always ка 346
+midendword ки 246
+always ко 1456
+always ла 4
+always ле 26
+always лё 26
+always ли 35
+always ло 456
+always ль 2356
+midword льн 2356
+midword льне 2356-16
+always ме 146
+always мё 146
+midendword ми 124
+always на 345
+always не 16
+always нё 16
+always ни 34
+always но 126
+endword ны 46
+always по 235
+sufword при 12346
+begword непри 16-12346
+after con midendword при 12346
+sufword про 12356
+begword непро 16-12356
+midendword ро 12356
+always ра 45
+always ре 2
+always ри 5
+sufword со 2346
+after vow midendword со 2346
+always ст 25
+before con-r begmidword ста 25
+always та 123456
+always те 23
+always тё 23
+always ти 56
+always то 13456
+always че 236
+always чё 236
+before sch begmidword щ 6-1346
+endword щ 6-1346
+midendword ъ 3
+midendword ь 3
+
+
+#Table II
+ 
+word без 12-1356
+before voic before vow begword небез 16-12-1356
+before voic before vow begword без 12-1356
+before voic before vow begword небезыз 16-12-1356-36
+begword безот 12-1356-1256
+before muf begword бес 12-234
+before muf begword небес 16-12-234
+begword беспри 12-234-12346
+word в\sзаключение 2456-0-356-15
+word вместо 2456-13456
+sufword внутри 2456-1235
+word вопреки 1236-246
+word в\sпродолжение 2456-0-12356-15
+word вследствие 2456-26-15
+word в\sтечение 2456-0-23-15
+word для 145
+word из 36
+before voic before vow  begword из 36
+before voic before vow  begword неиз 16-36
+word кроме 13-146
+sufword между 6-146
+word около 135-135
+sufword от 1256
+begword неот 16-1256
+begword безот 12-1356-16-1256
+begword пере 46-1234
+begword непере 16-46-1234
+begword переиз 46-1234-36
+sufword пред 1234-145
+begword переот 1234-145-1256
+begword непред 16-1234-145
+before voic before vow begword предыз 1234-145-36
+word предо 1234-256
+sufword прежде 1234-245
+word против 1234-12356-2456
+word напротив 345-12356-2456
+word через 12345-1356
+begword чрез 12345-1356
+begword черес 12345-234
+
+
+#Table III
+
+endword ам 134
+endword амся 134-234
+endword ям 134
+endword ямся 134-234
+endword ым 134
+endword ымся 134-234
+endword им 134
+endword имся 134-234
+endword ами 124
+endword амися 124-234
+endword ями 124
+endword ямися 124-234
+prfword ими 124
+endword имися 124-234
+endword ыми 124
+endword ымися 124-234
+endword ат 6
+endword атся 6-234
+endword ят 6
+endword ятся 6-234
+endword ах 125
+endword ахся 125-234
+endword ях 125
+endword яхся 125-234
+prfword их 125
+endword ихся 125-234
+endword ых 125
+endword ыхся 125-234
+midword ающ 36-1346
+midword яющ 36-1346
+endword ая 1356
+endword аясь 1356-234
+endword яя 1356
+endword яясь 1356-234
+endword ев 2456
+endword ов 2456
+midendword ева 3456
+midendword ова 3456
+midendword ива 3456
+midendword ыва 3456
+prfword его 12456
+endword ого 12456
+endword ее 156
+endword еесь 156-234
+endword её 156
+endword ое 156
+endword ей 13456
+endword ейся 13456-234
+endword ёй 13456
+endword ёйся 13456-234
+endword ой 13456
+endword ойся 13456-234
+endword ем 235
+endword емся 235-234
+endword ём 235
+endword ёмся 235-234
+endword аем 235
+endword аемся 235-234
+endword яем 235
+endword яемся 235-234
+endword оем 235
+endword оемся 235-234
+prfword ему 146
+endword емуся 146-234
+endword ому 146
+endword омуся 146-234
+midword ени 34
+endword ет 14
+endword ется 14-234
+endword ёт 14
+endword ётся 14-234
+endword ешь 36-156
+endword ешься 36-156-234
+endword ёшь 36-156
+endword ёшься 36-156-234
+midword жайш 6-245
+midword ижайш 6-245
+endword ие 14
+endword иесь 14-234
+endword ые 14
+endword ыесь 14-234
+midword изаци 6-14
+midendword изм 134
+endword ий 12346
+endword ийся 12346-234
+endword ый 12346
+endword ыйся 12346-234
+endword им 134
+endword имся 134-234
+midword ирова 6-3456
+midword ированн 6-3456-1345
+endword йрован 6-3456-1345
+midendword ист 2345
+midword истическ 6-13
+midword ическ 6-13
+midword ческ 6-13
+endword ит 45
+endword ится 45-234
+midendword ител 2356
+midendword итель 2356
+midendword ительн 2356
+midword ительност 2356-25
+midendword ительств 2356-2456
+midword ительственн 2356-2456-1345
+endword ишь 6-156
+endword ишься 6-156-234
+midword ост 25
+midword ност 25
+endword ствен 25-1345
+midword ственн 25-1345
+midword ственност 25-25
+endword сь 234
+endword ся 234
+endword ть 23456
+endword ться 23456-234
+endword ут 46
+endword утся 46-234
+endword ют 46
+endword ются 46-234
+endword ую 12356
+endword уюсь 12356-234
+endword юю 12356
+endword ююсь 12356-234
+midword ующ 6-1346
+midword юющ 6-1346
+midword чайш 6-12345
+midword ичайш 6-12345
+
+#Table IV
+
+word вас 3456
+word весь 23456
+always всё 156
+always все 156
+word всю 156-1256
+word вся 156-1246
+word всяк 2456
+begmidword всяк 2456
+word ее 26
+word её 26
+word ей 25
+begword кажд 13-145
+begword как 346
+begmidword котор 13
+word мы 134
+word он 1345
+sufword сам 14
+begword само 14
+begword себ 234
+prfword сколько 234-1456
+word столько 25-1456
+word та 123456
+sufword те 23
+sufword то 13456
+word ты 236
+sufword это 246
+begword эт 246
+
+#Table V
+
+word будто 12-13456
+word впрочем 2456-134
+word если 15
+word ибо 24-23
+word как 346
+word однако 135-1456
+word потому\sчто 1234-3-12345
+word также 123456-245
+word так\sкак 2345-3-13
+word тоже 13456-245
+word что 12345
+word чтоб 12345-12
+word чтобы 12345-23
+
+#Table VI
+
+
+prfword бы 23
+word всё-таки 156-36-246
+word все-таки 156-36-246
+word даже 145-245
+word именно 24-126
+endword -либо 36-35
+word лишь 123
+word неужели 16-35
+endword -нибудь 36-34
+word разве 1235-23456
+word спасибо 234-12
+word только 2356
+word уже 245
+
+#Table VII (A)
+
+begmidword буд 12
+begword будущ 12-1346
+always был 12
+begmidword бывш 12
+word быть 12-23456
+always мог 134
+begmidword мож 134
+begword могущ 134-1346
+word может\sбыть 134-3-12
+begword хоч 125
+begword хоте 125
+begword хоти 125
+begword хотя 125
+word хоть 125-23456
+prfword хотя 125-1246
+
+always явить 1246-23456
+
+word предъявить 1234-3-1246-23456
+always яв 1246
+always яви 1246
+begmidword явлени 1246-34
+always явит 1246-2345
+always явят 1246-2345
+word является 1246-234
+word являются 1246-234
+
+#Table VII (B)
+
+word везде 2456-1356
+word откуда 1256-13
+word вместе 2456-146
+word неоткуда 16-1256-13
+word ниоткуда 34-1256-13
+word отсюда 1256-234
+word вновь 2456-23456
+word оттуда 1256-2345
+word вообще 1236-1346
+word очень 135-236
+word впоследствии 2456-235-24
+word потому 1234
+word всегда 2456-1245
+word почему 1234-236
+word всюду 2456-145
+word поэтому 235-246
+word где 1245
+word негде 16-1245
+word нигде 34-1245
+word кое-где 1456-15
+word где-нибудь 1245-36-34
+word разумеется 1235-234
+word еще 1346
+word ещё 1346
+word сегодня 234-1245
+word здесь 1356
+word сейчас 234-234
+word иногда 24-1
+word сразу 234-136
+word когда 1456-1245
+word некогда 16-1456-1245
+word никогда 34-1456-1245
+word когда-то 1456-1245-36-34
+word сюда 234-145
+word куда 13-145
+word некуда 16-13-145
+word никуда 34-13-145
+word теперь 2345
+word однажды 135-2346
+word тогда 13456-1245
+word опять 135-23456
+
+#Table VII (V)
+
+
+word более 26-15
+word наиболее 345-26-15
+word больше 12-156
+begword больш 12-156
+word быстро 12-12356
+word вначале 	256-345-26
+word вскоре 2456-1456
+word вскорости 2456-1456-25-24
+word главным\sобразом 1245-1245-3-135
+word далеко 145-1456
+word далее 145-26
+word лучше 123-156
+begword лучш 123-156
+word менее 146-15
+word меньше 146-156
+begword меньш 146-156
+word наверно 345-126
+word наверное 345-126-15
+word наверняка 345-346
+word наконец 1345-14
+word наоборот 345-135-2345
+word например 345-1234
+word раньше 1235-156
+word слишком 234-35-134
+word так\sдалее 2345-3-145
+word так\sназываемый 2345-3-1345
+word таким\sобразом 2345-3-135
+word тому\sподобное 2345-3-1234
+
+#Table VII (G)
+
+
+word абсолютно 1-12-2345-126
+word активно 1-2456-126
+word актуально 1-13-2356-126
+word вероятно 23456-12356-126
+word возможно 1236-1356-134-126
+word невозможно 16-1236-1356-134-126
+word давно 145-126
+word недавно 16-145-126
+word достаточно 256-25-126
+word недостаточно 16-256-25-126
+word ежегодно 15-12456-126
+word ежемесячно 15-136-126
+word ежедневно 15-145-126
+word конечно 1456-126
+word можно 134-126
+word особенно 135-2346-126
+word очевидно 135-236-126
+word посредственно 235-234-126
+word непосредственно 16-235-234-126
+word принципиально 12346-1234-2356-126
+word беспринципно 12-12346-1234-126
+word своевременно 234-1236-3-126
+word следовательно 234-26-126
+word случайно 234-12346-126
+word совершенно 2346-23456-126
+word совсем 2346-156-134
+word типично 56-1234-126
+word формально 124-2356-126
+word характерно 125-1235-126
+word частично 12345-126
+word чрезвычайно 12345-1356-126
+
+#Table VIII
+
+before muf begword ис 36
+before muf begword неис 16-36
+before pred begword пред 1234
+begword предст 1234-25
+before con-r begword предста 1234-25
+before mufs begword рас 1235
+before voicz begword рас 1235
+before mufs begword нерас 16-1235
+midword ска 346
+midendword ски 246
+midword ско 1456
+
+
+
+
+
+# Digits
+always 1 1
+always 2 12
+always 3 14
+always 4 145
+always 5 15
+always 6 124
+always 7 1245
+always 8 125
+always 9 24
+always 0 245
+
+numsign 3456
+midnum , 2
+midnum : 25
+midnum . 256
+midnum / 34
+midnum - 36
+always ÷ 1256
+always = 2356
+always + 235
+#special character sequences
+literal :// URLs
+literal www.
+
+literal .com
+literal .edu
+literal .gov
+literal .mil
+literal .net
+literal .org
+include countries.cti
+
+literal .doc
+literal .htm
+literal .html
+literal .tex
+literal .txt
+
+literal .gif
+literal .jpg
+literal .png
+literal .wav
+
+literal .tar
+literal .zip
+
+include letters-latin.cti
+
+#Punctuation
+
+
+always ( 126
+always ) 345
+always : 25
+always ; 23
+
+
+always * 35
+always ! 235
+always ? 26
+always , 2
+
+
+always \s– 36
+always \s- 36
+always ­ 36
+repeatable ­­­ 36-36-36
+always \s­\s 36
+always \s— 36
+
+
+always … 256-256-256
+always ‹ 246
+always › 135
+prepunc „ 236
+postpunc „ 356
+prepunc “ 236
+postpunc “ 356
+always ‘ 236
+always ’ 356
+
+always « 236
+always » 356
+prepunc ‟ 236
+postpunc ‟ 356
+prepunc ” 236
+prepunc " 236
+postpunc ” 356
+postpunc " 356
+prepunc ' 236
+postpunc ' 356
diff --git a/Tables/Contraction/si.ctb b/Tables/Contraction/si.ctb
new file mode 100644
index 0000000..c71d7c7
--- /dev/null
+++ b/Tables/Contraction/si.ctb
@@ -0,0 +1,81 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Sinhalese (uncontracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+always අ 1	  SINHALA LETTER AYANNA
+always ආ 345	  SINHALA LETTER AAYANNA
+always ඇ 12356	  SINHALA LETTER AEYANNA
+always ඈ 12456	  SINHALA LETTER AEEYANNA
+always ඉ 24	  SINHALA LETTER IYANNA
+always ඊ 35	  SINHALA LETTER IIYANNA
+always උ 136	  SINHALA LETTER UYANNA
+always ඌ 1256	  SINHALA LETTER UUYANNA
+always ඍ 5-1235	  SINHALA LETTER IRUYANNA
+always ඎ 6-1235	  SINHALA LETTER IRUUYANNA
+always ඏ 5-123	  SINHALA LETTER ILUYANNA
+always ඐ 6-123	  SINHALA LETTER ILUUYANNA
+always එ 15	  SINHALA LETTER EYANNA
+always ඒ 26	  SINHALA LETTER EEYANNA
+always ඓ 34	  SINHALA LETTER AIYANNA
+always ඔ 135	  SINHALA LETTER OYANNA
+always ඕ 1346	  SINHALA LETTER OOYANNA
+always ඖ 246	  SINHALA LETTER AUYANNA
+always ක 13	  SINHALA LETTER ALPAPRAANA KAYANNA
+always ඛ 46	  SINHALA LETTER MAHAAPRAANA KAYANNA
+always ග 1245	  SINHALA LETTER ALPAPRAANA GAYANNA
+always ඝ 126	  SINHALA LETTER MAHAAPRAANA GAYANNA
+always ඞ 346	  SINHALA LETTER KANTAJA NAASIKYAYA
+always ච 14	  SINHALA LETTER ALPAPRAANA CAYANNA
+always ඡ 16	  SINHALA LETTER MAHAAPRAANA CAYANNA
+always ජ 245	  SINHALA LETTER ALPAPRAANA JAYANNA
+always ඣ 356	  SINHALA LETTER MAHAAPRAANA JAYANNA
+always ඤ 25	  SINHALA LETTER TAALUJA NAASIKYAYA
+always ට 23456	  SINHALA LETTER ALPAPRAANA TTAYANNA
+always ඨ 2456	  SINHALA LETTER MAHAAPRAANA TTAYANNA
+always ඩ 1246	  SINHALA LETTER ALPAPRAANA DDAYANNA
+always ඪ 123456	  SINHALA LETTER MAHAAPRAANA DDAYANNA
+always ණ 3456	  SINHALA LETTER MUURDHAJA NAYANNA
+always ත 2345	  SINHALA LETTER ALPAPRAANA TAYANNA
+always ථ 1456	  SINHALA LETTER MAHAAPRAANA TAYANNA
+always ද 145	  SINHALA LETTER ALPAPRAANA DAYANNA
+always ධ 2346	  SINHALA LETTER MAHAAPRAANA DAYANNA
+always න 1345	  SINHALA LETTER DANTAJA NAYANNA
+always ප 1234	  SINHALA LETTER ALPAPRAANA PAYANNA
+always ඵ 235	  SINHALA LETTER MAHAAPRAANA PAYANNA
+always බ 12	  SINHALA LETTER ALPAPRAANA BAYANNA
+always භ 45	  SINHALA LETTER MAHAAPRAANA BAYANNA
+always ම 134	  SINHALA LETTER MAYANNA
+always ඹ 56	  SINHALA LETTER AMBA BAYANNA
+always ය 13456	  SINHALA LETTER YAYANNA
+always ර 1235	  SINHALA LETTER RAYANNA
+always ල 123	  SINHALA LETTER DANTAJA LAYANNA
+always ව 1236	  SINHALA LETTER VAYANNA
+always ශ 12346	  SINHALA LETTER TAALUJA SAYANNA
+always ෂ 146	  SINHALA LETTER MUURDHAJA SAYANNA
+always ස 234	  SINHALA LETTER DANTAJA SAYANNA
+always හ 125	  SINHALA LETTER HAYANNA
+always ළ 456	  SINHALA LETTER MUURDHAJA LAYANNA
+always ෆ 124	  SINHALA LETTER FAYANNA
diff --git a/Tables/Contraction/spaces.cti b/Tables/Contraction/spaces.cti
new file mode 100644
index 0000000..171f4dd
--- /dev/null
+++ b/Tables/Contraction/spaces.cti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY contraction subtable defines all the space characters to be empty
+# braille cells.
+
+always	\x20	0	SPACE
+always	\xA0	0	NO-BREAK SPACE
+always	\u2002	0	EN SPACE
+always	\u2003	0	EM SPACE
+always	\u2004	0	THREE-PER-EM SPACE
+always	\u2005	0	FOUR-PER-EM SPACE
+always	\u2006	0	SIX-PER-EM SPACE
+always	\u2007	0	FIGURE SPACE
+always	\u2008	0	PuNCTUATION SPACE
+always	\u2009	0	THIN SPACE
+always	\u200A	0	HAIR SPACE
+always	\u202F	0	NARROW NO-BREAK SPACE
+always	\u205F	0	MEDIUM MATHEMATICAL SPACE
diff --git a/Tables/Contraction/sw.ctb b/Tables/Contraction/sw.ctb
new file mode 100644
index 0000000..c4ce254
--- /dev/null
+++ b/Tables/Contraction/sw.ctb
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Swahili (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always nd 1246
+always tw 156
+always kw 12345
+always ng' 346
+always ny 12456
+always ch 16
+always gh 126
+always gayn 126
+always sh 146
+always th 1456
+always dh 2346
+always aa 345
+always ee 26
+always ii 35
+always oo 246
+always uu 1256
+always st 34
+
+# inline contraction of emoji descriptions
+emoji sw
diff --git a/Tables/Contraction/th.ctb b/Tables/Contraction/th.ctb
new file mode 100644
index 0000000..0e2741b
--- /dev/null
+++ b/Tables/Contraction/th.ctb
@@ -0,0 +1,121 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Thai (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# consonants
+always ก 1245	  	THAI CHARACTER KO KAI
+always ข 346		THAI CHARACTER KHO KHAI
+always ฃ 2346		THAI CHARACTER KHO KHUAT
+always ค 136	  	THAI CHARACTER KHO KHWAI
+always ฅ 6-136	  	THAI CHARACTER KHO KHON
+always ฆ 36-136	  	THAI CHARACTER KHO RAKHANG
+always ง 12456	  	THAI CHARACTER NGO NGU
+always จ 245	  	THAI CHARACTER CHO CHAN
+always ฉ 34	  	THAI CHARACTER CHO CHING
+always ช 13	  	THAI CHARACTER CHO CHANG
+always ซ 36-13	  	THAI CHARACTER SO SO
+always ฌ 6-346	  	THAI CHARACTER CHO CHOE
+always ญ 6-13456  	THAI CHARACTER YO YING
+always ฎ 6-145	  	THAI CHARACTER DO CHADA
+always ฏ 6-1256  	THAI CHARACTER TO PATAK
+always ฐ 6-2345	  	THAI CHARACTER THO THAN
+always ฑ 6-23456  	THAI CHARACTER THO NANGMONTHO
+always ฒ 56-23456	THAI CHARACTER THO PHUTHAO
+always ณ 6-1345	  	THAI CHARACTER NO NEN
+always ด 145	  	THAI CHARACTER DO DEK
+always ต 1256	  	THAI CHARACTER TO TAO
+always ถ 2345	  	THAI CHARACTER THO THUNG
+always ท 23456	  	THAI CHARACTER THO THAHAN
+always ธ 356-23456	THAI CHARACTER THO THONG
+always น 1345	  	THAI CHARACTER NO NU
+always บ 1236	  	THAI CHARACTER BO BAIMAI
+always ป 12346	  	THAI CHARACTER PO PLA
+always ผ 1234	  	THAI CHARACTER PHO PHUNG
+always ฝ 1346	  	THAI CHARACTER FO FA
+always พ 1456	  	THAI CHARACTER PHO PHAN
+always ฟ 1246	  	THAI CHARACTER FO FAN
+always ภ 6-1456	  	THAI CHARACTER PHO SAMPHAO
+always ม 134	  	THAI CHARACTER MO MA
+always ย 13456	  	THAI CHARACTER YO YAK
+always ร 1235	  	THAI CHARACTER RO RUA
+always ล 123	  	THAI CHARACTER LO LING
+always ว 2456	  	THAI CHARACTER WO WAEN
+always ศ 6-234	  	THAI CHARACTER SO SALA
+always ษ 36-234	  	THAI CHARACTER SO RUSI
+always ส 234	  	THAI CHARACTER SO SUA
+always ห 125	  	THAI CHARACTER HO HIP
+always ฬ 36-123	  	THAI CHARACTER LO CHULA
+always อ 135	  	THAI CHARACTER O ANG
+always ฮ 123456	  	THAI CHARACTER HO NOKHUK
+
+# vowels
+always ะ 1	  	THAI CHARACTER SARA A
+always า 16	  	THAI CHARACTER SARA AA
+always ำ 1356	  	THAI CHARACTER SARA AM
+always  ิ 12	  	THAI CHARACTER SARA I
+always  ี 23	  	THAI CHARACTER SARA II
+always  ึ 246	  	THAI CHARACTER SARA UE
+always  ื 26	  	THAI CHARACTER SARA UEE
+always  ุ 14	  	THAI CHARACTER SARA U
+always  ู 25	  	THAI CHARACTER SARA UU
+always เ 124	  	THAI CHARACTER SARA E
+always แ 126	  	THAI CHARACTER SARA AE
+always โ 24	  	THAI CHARACTER SARA O
+always ใ 156-1	  	THAI CHARACTER SARA AI MAIMUAN
+always ไ 156	  	THAI CHARACTER SARA AI MAIMALAI
+
+# contractions
+always เๅะ 135-1
+always ออ 135
+always วั 15
+always เีย 12356
+always เี่อ 12345
+always เออ 146
+always เา 235
+
+# accents etc
+always ๆ 2	  	THAI CHARACTER MAIYAMOK
+always  ็ 6	  	THAI CHARACTER MAITAIKHU
+always  ่ 35	  	THAI CHARACTER MAI EK
+always  ้ 256	  	THAI CHARACTER MAI THO
+always  ๊ 2356	  	THAI CHARACTER MAI TRI
+always  ๋ 236	  	THAI CHARACTER MAI CHATTAWA
+always  ์ 356	  	THAI CHARACTER THANTHAKHAT
+
+# digits
+always ๐ 2458	  	THAI DIGIT ZERO
+always ๑ 18	  	THAI DIGIT ONE
+always ๒ 128	  	THAI DIGIT TWO
+always ๓ 148	  	THAI DIGIT THREE
+always ๔ 1458	  	THAI DIGIT FOUR
+always ๕ 158	  	THAI DIGIT FIVE
+always ๖ 1248	  	THAI DIGIT SIX
+always ๗ 12458	  	THAI DIGIT SEVEN
+always ๘ 1258	  	THAI DIGIT EIGHT
+always ๙ 248	  	THAI DIGIT NINE
+
+# inline contraction of emoji descriptions
+emoji th
diff --git a/Tables/Contraction/zh-tw.ctb b/Tables/Contraction/zh-tw.ctb
new file mode 100644
index 0000000..9f6b978
--- /dev/null
+++ b/Tables/Contraction/zh-tw.ctb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Chinese (Taiwan, uncontracted)
+# Deprecated - renamed to zh_TW
+
+include zh_TW.ctb
diff --git a/Tables/Contraction/zh-tw.cti b/Tables/Contraction/zh-tw.cti
new file mode 100644
index 0000000..d46300d
--- /dev/null
+++ b/Tables/Contraction/zh-tw.cti
@@ -0,0 +1,27183 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Chinese characters contains Traditional and Simpilified
+# Any comment please contact to Coscell Kao <coscell@gmail.com> (2019-2)
+
+always \u2ebc 1245-12356-5
+always \u3400 245-234-3
+always \u3401 124-2345-5
+always \u3404 123-35-5
+always \u3405 34-4
+always \u3406 1456-4
+always \u340C 16-2
+always \u3416 15-346-2
+always \u341C 245-234-2
+always \u341F 12345-356-3
+always \u3421 1345-25-5
+always \u3424 145-1236-3
+always \u3428 15-1256-5
+always \u3429 15-13456-2
+always \u342A 1235-2456-5
+always \u342B 15-235-3
+always \u342C 14-234-2
+always \u342D 14-1456-4
+always \u342E 15-46-3
+always \u342F 235-3
+always \u3430 15-1456-5
+always \u3431 1-136-4
+always \u3432 145-2456-5
+always \u3433 34-5
+always \u3434 1234-1236-3
+always \u3435 1245-34-2
+always \u3436 1235-35-3
+always \u3437 134-345-5
+always \u3438 245-2345-5
+always \u3439 16-5
+always \u343A 1-12346-5
+always \u343B 1345-356-5
+always \u343C 24-1356-3
+always \u343D 12345-1356-3
+always \u343E 45-2
+always \u3440 124-2456-5
+always \u3441 1-25-3
+always \u3442 12345-1346-4
+always \u3443 146-5
+always \u3444 34-4
+always \u3445 125-25-5
+always \u3446 123-1236-4
+always \u3447 1-12356-5
+always \u3448 145-12346-3
+always \u3449 15-34-5
+always \u344A 16-5
+always \u344B 245-235-5
+always \u344C 456-3
+always \u344D 14-356-5
+always \u344E 1345-146-4
+always \u344F 1-34-5
+always \u3450 24-34-2
+always \u3451 125-1236-2
+always \u3453 13-256-5
+always \u3454 15-1256-4
+always \u3457 24-136-3
+always \u3458 13-346-5
+always \u3459 145-346-2
+always \u345A 1345-25-2
+always \u345B 15-34-5
+always \u345C 16-5
+always \u345D 14-12346-5
+always \u345E 13456-5
+always \u345F 135-1356-4
+always \u3460 24-34-5
+always \u3462 13-236-2
+always \u3463 14-1236-2
+always \u3464 134-246-2
+always \u3465 16-5
+always \u3466 14-16-5
+always \u3467 13-16-5
+always \u3468 1235-12356-5
+always \u3469 14-25-2
+always \u346A 12-2456-2
+always \u346B 245-16-4
+always \u346D 135-16-4
+always \u346E 1235-123456-2
+always \u346F 15-1256-4
+always \u3470 1235-1246-5
+always \u3471 1245-146-4
+always \u3472 135-356-5
+always \u3473 1-12356-5
+always \u3474 245-1456-3
+always \u3475 13-16-2
+always \u3476 15-16-5
+always \u3477 124-2456-5
+always \u3478 246-2
+always \u3479 1235-1246-5
+always \u347A 13-256-5
+always \u347B 134-345-5
+always \u347C 14-236-5
+always \u347D 124-1346-2
+always \u347E 246-2
+always \u347F 1-146-5
+always \u3480 1-2346-2
+always \u3481 1256-4
+always \u3482 1-25-2
+always \u3483 156-5
+always \u3484 1245-1236-4
+always \u3485 245-16-4
+always \u3486 15-16-5
+always \u3487 134-34-5
+always \u3488 15-2345-5
+always \u3489 124-1346-4
+always \u348A 15-2346-5
+always \u348B 15-156-3
+always \u348C 245-235-2
+always \u348D 14-356-4
+always \u348E 15-345-5
+always \u3491 1235-1246-5
+always \u3492 1234-34-2
+always \u3493 124-345-5
+always \u3494 24-34-4
+always \u3495 46-3
+always \u3496 12356-4
+always \u3497 124-2456-2
+always \u3499 134-2345-2
+always \u349A 123456-4
+always \u349B 145-246-5
+always \u349C 1256-4
+always \u349D 134-346-5
+always \u349E 13-256-5
+always \u349F 1345-246-4
+always \u34A0 15-346-5
+always \u34A1 234-2
+always \u34A4 12-2346-5
+always \u34A5 12345-1356-3
+always \u34A6 14-356-4
+always \u34A7 14-16-5
+always \u34A8 15-2345-3
+always \u34A9 14-25-4
+always \u34AA 15-16-2
+always \u34AB 13-16-5
+always \u34AC 456-3
+always \u34AD 123-123456-3
+always \u34AF 346-5
+always \u34B0 245-45-2
+always \u34B2 245-2456-2
+always \u34B3 14-46-4
+always \u34B4 13-34-4
+always \u34B5 134-146-5
+always \u34B6 13-12346-3
+always \u34B7 15-13456-3
+always \u34B8 15-1246-5
+always \u34B9 145-2345-3
+always \u34BA 456-4
+always \u34BB 134-146-5
+always \u34BC 134-1236-2
+always \u34BD 245-45-3
+always \u34BE 24-156-5
+always \u34BF 14-16-2
+always \u34C1 456-4
+always \u34C2 123-12356-5
+always \u34C3 145-34-5
+always \u34C4 12-136-5
+always \u34C5 124-13456-3
+always \u34C7 25-5
+always \u34C8 135-13456-5
+always \u34C9 1235-25-5
+always \u34CA 145-12346-5
+always \u34CB 13-12346-5
+always \u34CC 12-1356-3
+always \u34CE 245-1456-3
+always \u34CF 13-235-4
+always \u34D0 14-34-5
+always \u34D1 15-13456-5
+always \u34D3 1345-1236-2
+always \u34D4 15-346-5
+always \u34D5 134-346-5
+always \u34D6 135-16-5
+always \u34D7 13-346-2
+always \u34D8 15-34-5
+always \u34DA 13-12346-3
+always \u34DB 13-12346-3
+always \u34DC 234-5
+always \u34DD 12-456-5
+always \u34DE 245-23456-5
+always \u34DF 1234-16-2
+always \u34E0 145-2345-5
+always \u34E1 12345-12356-4
+always \u34E2 14-25-5
+always \u34E3 245-23456-5
+always \u34E4 245-23456-5
+always \u34E5 124-12346-3
+always \u34E6 135-126-5
+always \u34E7 13-1236-3
+always \u34E8 245-156-5
+always \u34E9 13-346-2
+always \u34EA 14-1346-4
+always \u34EC 123-34-3
+always \u34ED 24-2346-2
+always \u34EE 13-2346-3
+always \u34EF 14-16-2
+always \u34F0 1235-35-5
+always \u34F1 24-34-3
+always \u34F2 1234-2345-3
+always \u34F3 145-16-3
+always \u34F4 1245-12456-4
+always \u34F5 2346-5
+always \u34F6 245-346-5
+always \u34F7 16-5
+always \u34F8 1-25-2
+always \u34F9 245-1246-5
+always \u34FA 13-2345-3
+always \u34FB 13-1346-3
+always \u34FC 12-156-5
+always \u34FD 12-12346-2
+always \u34FE 15-16-3
+always \u3500 14-46-5
+always \u3501 145-1356-3
+always \u3502 14-1456-2
+always \u3503 15-236-3
+always \u3504 15-34-5
+always \u3505 15-246-5
+always \u3506 125-1236-4
+always \u3507 1234-126-3
+always \u3509 1-34-2
+always \u350A 145-1236-4
+always \u350B 13-2345-3
+always \u350C 125-12356-5
+always \u350D 12-35-3
+always \u350E 15-236-3
+always \u350F 14-16-5
+always \u3510 245-2345-3
+always \u3511 12-156-5
+always \u3512 15-16-2
+always \u3513 13-2345-4
+always \u3515 13-16-2
+always \u3517 12345-356-5
+always \u3518 12-34-5
+always \u3519 135-1356-3
+always \u351A 123-12356-4
+always \u351C 135-126-2
+always \u351D 14-46-4
+always \u351E 123-2356-3
+always \u3520 1235-2346-2
+always \u3521 1234-16-5
+always \u3522 15-236-3
+always \u3523 14-356-5
+always \u3524 15-1456-5
+always \u3525 1234-16-2
+always \u3526 46-4
+always \u3527 14-1256-5
+always \u3528 135-356-5
+always \u3529 2346-5
+always \u352A 14-34-4
+always \u352C 1-25-2
+always \u352D 12-156-2
+always \u352E 1345-25-2
+always \u352F 15-45-2
+always \u3530 1235-1356-2
+always \u3531 1256-4
+always \u3532 13-1246-4
+always \u3533 13-1246-4
+always \u3534 16-5
+always \u3535 15-45-4
+always \u3536 13-1236-4
+always \u3537 14-12356-5
+always \u3538 124-16-3
+always \u3539 14-2346-5
+always \u353A 24-156-5
+always \u353B 1234-16-3
+always \u353C 15-123456-4
+always \u353D 234-4
+always \u353E 13-346-2
+always \u353F 125-12356-5
+always \u3540 234-2
+always \u3541 245-236-5
+always \u3542 1456-2
+always \u3543 15-16-3
+always \u3544 1-156-4
+always \u3545 13-23456-2
+always \u3546 1235-34-5
+always \u3547 14-345-3
+always \u3548 16-4
+always \u3549 123-2346-5
+always \u354A 12345-34-3
+always \u354B 245-1456-2
+always \u354C 2456-5
+always \u354D 145-1246-3
+always \u354E 123-2346-5
+always \u354F 12-34-2
+always \u3550 15-346-4
+always \u3551 12-34-2
+always \u3552 1246-2
+always \u3553 12-1236-2
+always \u3554 124-13456-3
+always \u3555 1235-12456-5
+always \u3556 15-34-5
+always \u3557 234-5
+always \u3558 245-1236-3
+always \u3559 13-256-5
+always \u355A 1-146-4
+always \u355B 246-2
+always \u355C 24-156-4
+always \u355E 24-35-3
+always \u355F 123-1246-5
+always \u3560 24-456-3
+always \u3561 1235-25-5
+always \u3562 13-2456-5
+always \u3563 2345-4
+always \u3564 245-234-2
+always \u3565 24-136-3
+always \u3566 1235-35-5
+always \u3567 15-16-3
+always \u3568 12345-1236-5
+always \u3569 1234-1346-5
+always \u356A 145-1236-4
+always \u356B 12345-1346-4
+always \u356C 13-12346-3
+always \u356D 146-3
+always \u356E 12345-34-4
+always \u356F 1345-345-5
+always \u3570 15-236-5
+always \u3571 234-2
+always \u3572 1235-35-2
+always \u3573 15-235-3
+always \u3574 12-136-2
+always \u3575 13-25-2
+always \u3576 134-126-5
+always \u3577 1234-345-3
+always \u3578 14-16-5
+always \u3579 12345-345-2
+always \u357A 1235-146-2
+always \u357B 1234-12356-4
+always \u357C 15-235-3
+always \u357D 15-156-5
+always \u3580 14-2346-5
+always \u3581 14-1456-5
+always \u3582 16-5
+always \u3583 1235-12356-4
+always \u3584 1-12356-3
+always \u3585 15-1256-5
+always \u3586 245-1256-2
+always \u3587 156-2
+always \u3588 14-146-4
+always \u3589 245-16-4
+always \u358A 15-256-2
+always \u358F 1345-346-5
+always \u3590 1246-4
+always \u3591 15-346-5
+always \u3592 124-16-2
+always \u3593 1235-12346-2
+always \u3594 124-123456-4
+always \u3595 1345-346-5
+always \u3596 1345-346-5
+always \u3597 1456-2
+always \u3598 1-136-3
+always \u359E 2356-3
+always \u359F 24-12356-5
+always \u35A0 1345-345-4
+always \u35A1 346-5
+always \u35A2 245-156-2
+always \u35A3 1234-12356-4
+always \u35A4 1235-1236-2
+always \u35A5 13-256-5
+always \u35A6 145-12346-4
+always \u35A7 123456-4
+always \u35A8 14-34-5
+always \u35A9 15-12356-4
+always \u35AA 1256-5
+always \u35AB 14-13456-2
+always \u35AD 124-2345-4
+always \u35AE 14-123456-2
+always \u35B4 123456-4
+always \u35B5 13-1256-5
+always \u35B6 1456-3
+always \u35B7 24-156-2
+always \u35B8 15-236-3
+always \u35B9 1234-136-2
+always \u35BA 12-123456-4
+always \u35BB 1245-12356-2
+always \u35BC 145-25-4
+always \u35BD 125-2346-2
+always \u35BE 2346-5
+always \u35BF 346-2
+always \u35C0 234-3
+always \u35C1 2346-5
+always \u35C2 24-1356-4
+always \u35C3 123456-4
+always \u35C4 134-1236-5
+always \u35C5 1235-34-2
+always \u35C6 13-2346-2
+always \u35C7 15-23456-3
+always \u35C8 134-1236-2
+always \u35C9 14-236-5
+always \u35CA 13-16-2
+always \u35CB 1235-12356-2
+always \u35CC 1-156-5
+always \u35CD 1-2456-3
+always \u35CE 13-23456-5
+always \u35CF 2356-3
+always \u35D0 123-2346-5
+always \u35D1 135-2456-1
+always \u35D2 2456-5
+always \u35D3 1-1246-3
+always \u35D4 245-2345-3
+always \u35D5 13-12356-5
+always \u35D6 145-1236-5
+always \u35D7 135-16-4
+always \u35D8 135-126-2
+always \u35D9 12-34-2
+always \u35DA 14-16-5
+always \u35DB 15-246-5
+always \u35DC 15-234-5
+always \u35DE 1234-126-5
+always \u35E2 1235-12346-2
+always \u35E3 124-16-5
+always \u35E4 245-16-3
+always \u35E5 123-25-5
+always \u35E6 14-146-2
+always \u35E7 1-156-5
+always \u35E8 2456-4
+always \u35E9 15-16-3
+always \u35EA 1234-16-3
+always \u35EB 245-346-5
+always \u35EC 12-345-3
+always \u35ED 15-16-3
+always \u35F0 245-12346-2
+always \u35F1 13-16-2
+always \u35F2 1235-35-5
+always \u35F3 145-345-3
+always \u35F4 2345-2
+always \u35F5 15-1256-5
+always \u35F6 1234-126-3
+always \u35F7 15-2456-3
+always \u35FA 15-345-5
+always \u35FB 13-25-3
+always \u35FC 346-5
+always \u35FD 15-46-4
+always \u35FE 15-236-3
+always \u35FF 1235-2346-4
+always \u3600 125-25-5
+always \u3601 16-5
+always \u3602 245-156-2
+always \u3603 1345-25-5
+always \u3604 14-1356-3
+always \u3605 15-2345-2
+always \u3606 124-2456-2
+always \u3607 1245-12346-2
+always \u3608 1345-16-4
+always \u3609 13-346-2
+always \u360A 16-5
+always \u360B 15-2345-2
+always \u360C 13-1256-5
+always \u360D 13-16-2
+always \u360E 1235-1236-4
+always \u3610 1234-146-5
+always \u3611 14-16-5
+always \u3613 14-1236-2
+always \u3614 15-2456-3
+always \u3615 1235-1236-4
+always \u3616 2345-2
+always \u3617 245-1256-3
+always \u3619 2345-2
+always \u361A 1235-1236-4
+always \u361B 123-1236-3
+always \u361C 12-12356-2
+always \u361D 1345-16-5
+always \u361E 1235-25-5
+always \u361F 12-456-3
+always \u3620 135-16-3
+always \u3621 15-23456-2
+always \u3622 12346-4
+always \u3623 15-45-3
+always \u3624 12456-3
+always \u3625 2346-2
+always \u3626 245-1456-2
+always \u3627 15-1256-5
+always \u3628 1345-346-5
+always \u3629 135-16-5
+always \u362A 1235-146-5
+always \u362B 13-13456-5
+always \u362C 146-3
+always \u362D 146-3
+always \u362F 12-1346-4
+always \u3630 1-136-3
+always \u3631 124-1236-3
+always \u3632 13-1256-2
+always \u3633 245-234-3
+always \u3634 125-25-5
+always \u3635 135-34-5
+always \u3636 13-346-2
+always \u3637 2456-5
+always \u3638 125-1346-5
+always \u3639 245-156-2
+always \u363A 12345-345-2
+always \u363B 1456-3
+always \u363D 1-156-5
+always \u363F 1345-346-5
+always \u3640 14-234-5
+always \u3641 134-356-2
+always \u3642 145-1246-5
+always \u3643 135-1346-3
+always \u3644 135-16-5
+always \u3645 135-146-4
+always \u3647 12-34-5
+always \u3648 15-23456-5
+always \u3649 124-2345-4
+always \u364A 1-1346-5
+always \u364B 245-16-2
+always \u364C 15-34-5
+always \u364D 145-25-3
+always \u364E 1246-3
+always \u364F 12345-34-5
+always \u3650 145-25-4
+always \u3651 1256-4
+always \u3652 346-4
+always \u3653 123-1246-2
+always \u3654 1235-1236-2
+always \u3655 123-2356-5
+always \u3657 1246-3
+always \u3658 246-3
+always \u3659 134-1356-4
+always \u365A 15-13456-3
+always \u365B 135-34-4
+always \u365C 124-2456-2
+always \u365D 15-346-2
+always \u365E 1345-346-5
+always \u365F 14-1346-4
+always \u3660 16-3
+always \u3661 125-12346-3
+always \u3662 134-1236-2
+always \u3663 1-1346-5
+always \u3664 15-23456-5
+always \u3665 13-123456-5
+always \u3666 15-346-2
+always \u3667 14-234-5
+always \u3668 13-16-3
+always \u3669 14-246-2
+always \u366A 36-5
+always \u366B 13-16-2
+always \u366C 1456-2
+always \u366D 13-236-2
+always \u366E 145-345-3
+always \u366F 16-5
+always \u3670 15-346-5
+always \u3671 1235-146-5
+always \u3672 235-3
+always \u3673 1235-1236-4
+always \u3674 12-1236-5
+always \u3675 124-2456-2
+always \u3676 124-1346-2
+always \u3677 1-156-2
+always \u3678 1234-34-2
+always \u3679 134-1356-2
+always \u367A 13-1246-5
+always \u367B 12-1236-2
+always \u367C 14-356-4
+always \u367E 15-236-2
+always \u3680 123-1246-3
+always \u3681 245-246-5
+always \u3682 1345-1346-5
+always \u3683 256-3
+always \u3684 1-156-5
+always \u3685 14-12346-2
+always \u3686 12345-34-5
+always \u3687 125-12346-3
+always \u3689 13-34-4
+always \u368A 123-2456-3
+always \u368B 1345-2345-4
+always \u368C 1235-25-4
+always \u368D 123-1246-4
+always \u368E 245-1256-5
+always \u368F 13-146-4
+always \u3690 124-146-5
+always \u3692 24-1236-4
+always \u3693 14-2456-4
+always \u3694 1345-346-5
+always \u3695 12345-34-2
+always \u3696 13-146-4
+always \u3697 245-346-2
+always \u3698 135-1236-5
+always \u3699 13-23456-3
+always \u369A 123-12346-3
+always \u369B 15-16-5
+always \u369C 15-1256-5
+always \u369D 123-1246-2
+always \u369E 24-136-4
+always \u369F 12-25-5
+always \u36A0 15-246-3
+always \u36A1 13-16-4
+always \u36A2 1345-34-2
+always \u36A3 13-246-4
+always \u36A4 16-5
+always \u36A5 1256-2
+always \u36A6 13-16-3
+always \u36A7 2345-4
+always \u36A8 24-136-3
+always \u36A9 1245-1236-4
+always \u36AA 1235-146-5
+always \u36AB 15-345-5
+always \u36AC 13-256-3
+always \u36AD 234-2
+always \u36AE 145-1236-3
+always \u36AF 15-1456-2
+always \u36B0 135-16-4
+always \u36B1 245-234-3
+always \u36B2 145-2345-4
+always \u36B3 2346-3
+always \u36B4 1234-34-5
+always \u36B5 145-12346-3
+always \u36B6 15-156-5
+always \u36B7 1345-2456-4
+always \u36B8 15-156-3
+always \u36B9 134-246-2
+always \u36BA 256-5
+always \u36BB 13-16-3
+always \u36BC 1235-146-4
+always \u36BD 245-246-4
+always \u36BE 15-235-3
+always \u36BF 1234-146-2
+always \u36C0 12-34-2
+always \u36C1 1234-1356-3
+always \u36C2 1345-25-4
+always \u36C3 13-346-2
+always \u36C4 16-3
+always \u36C5 156-5
+always \u36C6 145-25-4
+always \u36C9 15-16-4
+always \u36CA 145-25-4
+always \u36CD 245-346-5
+always \u36CE 14-1256-4
+always \u36CF 245-234-2
+always \u36D0 15-146-4
+always \u36D1 245-1236-5
+always \u36D2 145-12356-5
+always \u36D3 15-16-3
+always \u36D4 12345-1356-3
+always \u36D5 16-5
+always \u36D6 15-25-3
+always \u36D7 125-25-3
+always \u36D8 1234-126-5
+always \u36D9 15-1456-5
+always \u36DA 124-12346-4
+always \u36DB 24-136-3
+always \u36DC 234-2
+always \u36DD 135-356-5
+always \u36DE 14-12346-5
+always \u36E0 15-2345-2
+always \u36E1 12456-2
+always \u36E2 15-234-5
+always \u36E3 256-2
+always \u36E4 14-16-2
+always \u36E5 124-345-5
+always \u36E6 14-1236-4
+always \u36E7 134-1236-4
+always \u36E8 245-46-4
+always \u36E9 1-12356-2
+always \u36EA 1234-16-3
+always \u36EB 15-16-2
+always \u36EC 14-34-5
+always \u36ED 15-16-2
+always \u36EE 15-146-4
+always \u36EF 12345-1236-5
+always \u36F0 1235-123456-3
+always \u36F1 1246-3
+always \u36F2 12345-345-4
+always \u36F3 16-5
+always \u36F4 1345-146-4
+always \u36F5 12-1356-3
+always \u36F6 124-1236-5
+always \u36F7 13-16-3
+always \u36F8 24-34-5
+always \u36F9 1234-2345-2
+always \u36FA 1346-3
+always \u36FB 123-35-3
+always \u36FC 24-345-5
+always \u36FD 24-1236-3
+always \u36FE 15-2345-2
+always \u36FF 1-156-5
+always \u3701 1-345-3
+always \u3702 12345-1356-3
+always \u3703 14-2345-5
+always \u3704 15-256-2
+always \u3705 15-1256-5
+always \u3706 134-16-5
+always \u3707 1235-1246-5
+always \u3708 134-34-5
+always \u3709 235-3
+always \u370A 1-1236-4
+always \u370B 16-5
+always \u370C 13-12356-5
+always \u370D 124-1346-2
+always \u370E 15-16-3
+always \u370F 256-2
+always \u3710 24-34-5
+always \u3711 1234-126-2
+always \u3712 16-5
+always \u3713 145-345-2
+always \u3715 14-2345-2
+always \u3716 245-146-2
+always \u3717 245-1236-3
+always \u3718 13-346-3
+always \u3719 14-34-5
+always \u371A 15-34-5
+always \u371B 1345-136-5
+always \u371C 146-5
+always \u371D 1236-4
+always \u371E 245-1236-2
+always \u3720 245-1246-3
+always \u3721 245-12346-3
+always \u3722 14-1256-4
+always \u3723 1245-1236-2
+always \u3724 1345-2345-4
+always \u3725 134-2456-2
+always \u3726 15-256-2
+always \u3727 236-3
+always \u3728 1345-2456-2
+always \u3729 146-5
+always \u372A 15-2345-4
+always \u372B 134-356-4
+always \u372C 1-1236-3
+always \u372E 14-1236-5
+always \u372F 15-16-3
+always \u3730 236-5
+always \u3731 1-156-2
+always \u3732 13456-2
+always \u3733 1235-2356-2
+always \u3734 134-1356-5
+always \u3735 1345-246-4
+always \u3736 12345-1236-5
+always \u3737 134-16-2
+always \u3738 1345-346-5
+always \u3739 245-1256-2
+always \u373A 125-1236-5
+always \u373B 14-12456-2
+always \u373C 1-156-2
+always \u373D 125-156-1
+always \u373E 1235-2456-2
+always \u373F 15-1256-5
+always \u3740 1235-146-4
+always \u3741 15-45-3
+always \u3742 1-156-5
+always \u3743 134-2345-4
+always \u3744 12-123456-2
+always \u3745 13-12356-5
+always \u3747 12-123456-2
+always \u3748 14-12456-2
+always \u3749 1-34-5
+always \u374A 24-12356-4
+always \u374B 14-246-4
+always \u374C 13-234-5
+always \u374D 15-346-4
+always \u374E 145-13456-5
+always \u374F 13-346-5
+always \u3750 1245-12346-2
+always \u3751 12345-1346-2
+always \u3752 13-1256-3
+always \u3753 123-2346-5
+always \u3754 246-4
+always \u3755 1345-13456-2
+always \u3756 16-2
+always \u3757 14-1346-2
+always \u3758 235-2
+always \u3759 1456-2
+always \u375A 2345-2
+always \u375B 15-34-5
+always \u375C 15-16-2
+always \u375D 14-1456-2
+always \u375E 23456-5
+always \u375F 134-146-5
+always \u3760 134-13456-2
+always \u3761 125-1246-5
+always \u3762 1256-4
+always \u3763 16-5
+always \u3764 13-12356-5
+always \u3765 134-16-4
+always \u3766 13-256-5
+always \u3767 123456-4
+always \u3768 1235-12456-2
+always \u3769 123-1346-3
+always \u376A 145-2345-5
+always \u376B 14-12346-2
+always \u376C 12345-34-3
+always \u376D 15-13456-4
+always \u376E 245-1246-5
+always \u376F 245-246-2
+always \u3770 134-2345-2
+always \u3771 134-1356-5
+always \u3772 245-1456-4
+always \u3774 12456-2
+always \u3775 145-2346-2
+always \u3776 145-2346-2
+always \u3777 15-256-2
+always \u3778 135-2345-5
+always \u3779 1345-12356-2
+always \u377A 14-2345-2
+always \u377B 13-1456-5
+always \u377C 1256-3
+always \u377D 1-1246-4
+always \u377E 125-25-4
+always \u377F 135-126-4
+always \u3780 1235-1246-3
+always \u3781 246-5
+always \u3782 124-1246-5
+always \u3783 13-16-2
+always \u3784 1236-3
+always \u3785 14-25-5
+always \u3786 13-16-4
+always \u3787 1246-4
+always \u3788 134-126-5
+always \u3789 125-25-3
+always \u378A 15-1256-5
+always \u378B 1345-2345-4
+always \u378C 256-5
+always \u378E 135-345-4
+always \u378F 1-156-2
+always \u3790 13-1256-3
+always \u3791 1246-4
+always \u3792 15-346-5
+always \u3793 245-16-5
+always \u3794 12-156-4
+always \u3795 15-346-5
+always \u3796 245-156-5
+always \u3797 245-234-2
+always \u3798 124-123456-2
+always \u3799 1345-246-5
+always \u379A 1-345-4
+always \u379B 13-16-4
+always \u379C 124-1246-3
+always \u379E 15-12346-2
+always \u379F 145-2345-5
+always \u37A0 14-246-2
+always \u37A1 1-1236-4
+always \u37A2 1-156-3
+always \u37A3 12345-136-3
+always \u37A4 1456-2
+always \u37A5 245-136-2
+always \u37A6 13-16-4
+always \u37A7 1235-1246-5
+always \u37A8 125-156-4
+always \u37A9 14-1236-2
+always \u37AA 1345-146-2
+always \u37AB 125-12356-4
+always \u37AC 245-1456-4
+always \u37AD 145-2456-5
+always \u37AF 245-16-4
+always \u37B0 15-1256-4
+always \u37B1 245-12346-3
+always \u37B2 235-5
+always \u37B3 145-12356-4
+always \u37B4 12-156-2
+always \u37B5 124-146-3
+always \u37B6 134-1456-2
+always \u37B7 1235-456-2
+always \u37B8 15-1246-5
+always \u37B9 123-2346-4
+always \u37BA 125-34-2
+always \u37BB 1235-146-5
+always \u37BC 12-1356-2
+always \u37BD 15-236-5
+always \u37BE 1345-16-2
+always \u37BF 245-16-2
+always \u37C0 14-2345-2
+always \u37C1 1236-5
+always \u37C2 134-34-4
+always \u37C3 15-156-3
+always \u37C4 15-46-2
+always \u37C5 14-12346-2
+always \u37C6 1235-35-2
+always \u37C7 245-25-2
+always \u37C8 245-234-2
+always \u37C9 14-146-2
+always \u37CA 12345-34-2
+always \u37CB 145-1246-5
+always \u37CC 134-1346-2
+always \u37CD 14-1346-2
+always \u37CE 124-25-2
+always \u37CF 1235-1236-3
+always \u37D0 134-1346-2
+always \u37D1 135-126-2
+always \u37D2 245-256-3
+always \u37D3 245-16-2
+always \u37D4 1235-1236-3
+always \u37D6 14-12346-5
+always \u37D7 135-1456-3
+always \u37D8 124-246-2
+always \u37D9 125-2346-2
+always \u37DA 245-16-2
+always \u37DB 245-2345-3
+always \u37DC 134-16-2
+always \u37DD 1234-356-2
+always \u37DE 1-1236-5
+always \u37DF 15-46-4
+always \u37E0 13-1346-3
+always \u37E2 245-16-2
+always \u37E4 14-34-5
+always \u37E5 245-136-3
+always \u37E6 256-5
+always \u37E7 2346-5
+always \u37E8 145-12456-3
+always \u37E9 134-1456-2
+always \u37EA 1246-4
+always \u37EB 245-45-2
+always \u37EC 15-12356-4
+always \u37ED 134-1456-2
+always \u37EE 124-34-3
+always \u37EF 2346-5
+always \u37F0 134-13456-4
+always \u37F1 246-4
+always \u37F2 45-2
+always \u37F3 14-16-5
+always \u37F4 1246-2
+always \u37F5 13-1346-3
+always \u37F6 45-2
+always \u37F7 145-345-3
+always \u37F8 13-146-3
+always \u37F9 14-146-2
+always \u37FA 14-12356-2
+always \u37FB 245-2345-5
+always \u37FC 146-2
+always \u37FD 135-246-4
+always \u37FE 235-3
+always \u37FF 134-1346-2
+always \u3800 145-146-4
+always \u3801 245-136-3
+always \u3802 146-2
+always \u3804 15-16-2
+always \u3805 12345-34-5
+always \u3806 145-1236-3
+always \u3807 13-234-5
+always \u3808 1245-123456-5
+always \u3809 124-12346-2
+always \u380A 245-1256-3
+always \u380B 2346-5
+always \u380C 245-2345-3
+always \u380D 13-16-2
+always \u380E 13-346-2
+always \u380F 1235-35-2
+always \u3810 13-246-5
+always \u3811 125-1246-5
+always \u3812 135-246-4
+always \u3813 134-1356-2
+always \u3814 135-2456-5
+always \u3815 1246-4
+always \u3816 13-16-5
+always \u3817 146-5
+always \u3818 1256-4
+always \u3819 1235-146-2
+always \u381A 145-1246-5
+always \u381B 25-5
+always \u381C 1345-16-5
+always \u381D 245-12456-2
+always \u381F 14-16-2
+always \u3820 14-1256-2
+always \u3821 1345-246-4
+always \u3822 1235-2356-2
+always \u3823 14-16-5
+always \u3825 14-356-2
+always \u3826 12345-1356-3
+always \u3827 134-16-4
+always \u3828 1256-5
+always \u3829 1235-456-3
+always \u382A 13-1256-5
+always \u382D 1-1236-4
+always \u382E 13-1346-3
+always \u382F 16-4
+always \u3831 13-16-5
+always \u3832 135-16-4
+always \u3834 1245-136-5
+always \u3835 1235-456-3
+always \u3836 12345-1236-2
+always \u3837 13-2346-2
+always \u3838 12345-34-3
+always \u3839 13-346-5
+always \u383A 134-246-2
+always \u383B 15-16-3
+always \u383C 15-156-3
+always \u383D 124-12346-2
+always \u383E 45-3
+always \u383F 245-156-4
+always \u3840 135-16-5
+always \u3841 123-35-4
+always \u3842 14-16-5
+always \u3843 1235-456-3
+always \u3844 15-256-2
+always \u3845 1345-25-4
+always \u3846 1235-456-3
+always \u3847 13-346-3
+always \u3848 134-136-2
+always \u3849 15-2345-2
+always \u384A 245-23456-5
+always \u384B 1236-3
+always \u384C 134-146-5
+always \u384E 24-136-3
+always \u384F 24-34-3
+always \u3850 13-2345-3
+always \u3851 13-246-4
+always \u3852 1-123456-3
+always \u3853 123-123456-3
+always \u3854 34-5
+always \u3855 13456-3
+always \u3856 12-12346-2
+always \u3857 124-16-2
+always \u3858 14-2345-2
+always \u3859 135-16-3
+always \u385A 13-12356-3
+always \u385B 1235-456-3
+always \u385C 15-346-5
+always \u385D 12345-1356-5
+always \u385E 14-12356-2
+always \u385F 125-146-3
+always \u3860 1-1356-5
+always \u3861 12-34-2
+always \u3862 134-1236-5
+always \u3863 14-12346-2
+always \u3865 1456-5
+always \u3866 135-1456-3
+always \u3867 1-1356-5
+always \u3868 245-2345-3
+always \u3869 14-12456-2
+always \u386A 1345-356-2
+always \u386B 16-5
+always \u386D 13-16-5
+always \u386E 13-16-2
+always \u386F 1-2456-2
+always \u3870 1256-4
+always \u3871 13-234-4
+always \u3872 1235-12456-2
+always \u3873 145-16-4
+always \u3874 14-345-3
+always \u3875 14-13456-2
+always \u3876 1-156-4
+always \u3877 135-136-4
+always \u3878 1-345-4
+always \u3879 13-1256-3
+always \u387A 145-1236-5
+always \u387B 14-246-5
+always \u387C 15-346-5
+always \u387D 1-146-5
+always \u387E 15-2345-5
+always \u387F 12-156-5
+always \u3880 245-156-5
+always \u3881 12-156-4
+always \u3882 2345-4
+always \u3883 14-1346-2
+always \u3884 145-12356-5
+always \u3885 14-12346-5
+always \u3886 12-1236-2
+always \u3888 124-1246-2
+always \u3889 12-345-2
+always \u388A 2456-4
+always \u388B 12-156-4
+always \u388C 2346-3
+always \u388D 13456-4
+always \u388E 1-2346-2
+always \u388F 124-12356-2
+always \u3890 2346-3
+always \u3891 15-1246-3
+always \u3892 12-345-2
+always \u3893 1-1346-5
+always \u3894 125-12346-4
+always \u3895 235-3
+always \u3896 135-1236-3
+always \u3897 245-246-5
+always \u3898 14-2345-2
+always \u3899 13-1456-4
+always \u389A 14-34-4
+always \u389B 2345-5
+always \u389C 123-1346-3
+always \u389D 15-34-3
+always \u389E 16-5
+always \u389F 12-1236-4
+always \u38A0 13-235-4
+always \u38A1 13-46-4
+always \u38A2 135-16-5
+always \u38A3 245-13456-2
+always \u38A4 125-2456-3
+always \u38A5 145-12346-5
+always \u38A6 13-2346-3
+always \u38A7 13-45-4
+always \u38A8 1235-1236-5
+always \u38A9 145-16-5
+always \u38AA 34-3
+always \u38AC 1235-12346-2
+always \u38AD 124-146-3
+always \u38AE 12-156-2
+always \u38AF 134-1456-2
+always \u38B0 135-16-5
+always \u38B2 15-256-5
+always \u38B3 14-34-2
+always \u38B4 15-16-3
+always \u38B5 15-346-2
+always \u38B6 135-16-5
+always \u38B8 135-16-5
+always \u38BA 15-2345-2
+always \u38BB 1245-1246-5
+always \u38BC 135-346-5
+always \u38BD 156-4
+always \u38BE 13-45-5
+always \u38C0 1-136-5
+always \u38C1 135-356-5
+always \u38C2 2346-5
+always \u38C3 1256-5
+always \u38C4 245-1256-2
+always \u38C5 125-1236-5
+always \u38C6 134-16-4
+always \u38C7 145-16-5
+always \u38C8 15-156-5
+always \u38C9 13-12346-3
+always \u38CA 15-234-3
+always \u38CB 145-1236-3
+always \u38CC 24-1236-5
+always \u38CD 124-2456-2
+always \u38CE 134-34-5
+always \u38CF 13-13456-5
+always \u38D0 135-2345-5
+always \u38D1 235-2
+always \u38D2 245-1356-5
+always \u38D3 245-1236-5
+always \u38D4 145-13456-3
+always \u38D8 12345-34-4
+always \u38D9 145-16-2
+always \u38DA 124-12346-4
+always \u38DB 124-345-5
+always \u38DC 15-13456-2
+always \u38DD 15-12346-3
+always \u38DE 145-25-2
+always \u38DF 15-16-5
+always \u38E0 124-146-3
+always \u38E2 124-16-2
+always \u38E3 15-23456-2
+always \u38E4 13-2345-5
+always \u38E5 1-156-5
+always \u38E6 1246-3
+always \u38E7 1456-5
+always \u38EA 1235-12456-4
+always \u38EB 1-12346-4
+always \u38EC 245-16-5
+always \u38ED 125-12346-3
+always \u38EF 15-346-5
+always \u38F0 15-16-3
+always \u38F1 125-2346-2
+always \u38F2 1246-2
+always \u38F5 124-345-5
+always \u38F6 1-1236-3
+always \u38F7 1345-13456-5
+always \u38FA 15-1456-3
+always \u38FB 16-5
+always \u38FC 1245-136-4
+always \u38FD 24-34-5
+always \u38FE 12-345-5
+always \u38FF 1-25-2
+always \u3900 456-5
+always \u3901 124-2345-4
+always \u3902 13-16-2
+always \u3903 12345-1346-3
+always \u3904 1234-356-5
+always \u3905 2456-5
+always \u3906 12345-1236-5
+always \u3907 146-5
+always \u3908 245-1456-5
+always \u3909 245-23456-3
+always \u390A 15-246-5
+always \u390B 135-136-5
+always \u390C 13-1236-3
+always \u390D 245-246-3
+always \u390E 13-2346-3
+always \u390F 124-12346-2
+always \u3910 12-1236-3
+always \u3911 234-5
+always \u3912 13-146-3
+always \u3913 135-136-5
+always \u3914 12345-34-5
+always \u3915 12-34-5
+always \u3916 1-34-4
+always \u3917 124-2456-5
+always \u3918 12-146-4
+always \u3919 136-3
+always \u391A 1235-1346-2
+always \u391B 1245-136-5
+always \u391C 15-1256-5
+always \u391D 12-12346-3
+always \u391E 145-25-2
+always \u391F 123-12346-4
+always \u3920 14-16-5
+always \u3921 14-16-5
+always \u3922 1256-5
+always \u3923 1234-1356-3
+always \u3924 1256-2
+always \u3925 1235-2456-5
+always \u3926 14-16-5
+always \u3927 1235-12356-2
+always \u3928 13-12346-4
+always \u3929 123-2346-5
+always \u392A 45-5
+always \u392B 145-2346-2
+always \u392C 1235-1246-5
+always \u392D 13-246-3
+always \u392E 13-456-5
+always \u392F 13-235-3
+always \u3930 125-25-5
+always \u3931 12345-34-5
+always \u3932 245-346-5
+always \u3933 135-2456-4
+always \u3934 12-2346-5
+always \u3935 245-156-2
+always \u3936 134-1346-2
+always \u3937 1235-1236-3
+always \u3938 15-16-5
+always \u3939 245-234-2
+always \u393A 1235-456-3
+always \u393B 14-12456-4
+always \u393C 13-346-2
+always \u393D 12-12356-2
+always \u393E 124-1236-5
+always \u393F 2345-5
+always \u3940 145-2346-2
+always \u3941 145-2346-2
+always \u3942 124-2346-5
+always \u3943 134-136-3
+always \u3944 14-13456-2
+always \u3945 24-12356-5
+always \u3946 124-1246-5
+always \u3947 245-1236-2
+always \u3948 145-346-2
+always \u3949 12-156-5
+always \u394A 1234-1356-2
+always \u394B 16-3
+always \u394C 13-1256-2
+always \u394D 13-16-5
+always \u394E 14-16-2
+always \u394F 124-2345-4
+always \u3950 45-5
+always \u3951 234-3
+always \u3952 245-2456-4
+always \u3953 245-16-3
+always \u3954 1256-5
+always \u3955 14-2345-2
+always \u3956 245-12346-3
+always \u395A 1256-2
+always \u395B 13-16-2
+always \u395C 1246-5
+always \u395D 134-16-4
+always \u395E 15-1246-5
+always \u395F 15-346-2
+always \u3960 15-1256-4
+always \u3961 12-156-5
+always \u3962 245-234-2
+always \u3963 1235-1246-5
+always \u3964 13-1456-3
+always \u3965 1256-2
+always \u3966 245-346-5
+always \u3967 24-123456-5
+always \u3968 24-1246-5
+always \u3969 145-25-4
+always \u396A 14-12356-2
+always \u396B 145-123456-3
+always \u396C 1234-1346-2
+always \u396D 124-2456-5
+always \u396E 12-146-4
+always \u396F 1456-4
+always \u3970 15-146-3
+always \u3971 12345-356-4
+always \u3972 12-136-3
+always \u3973 45-2
+always \u3974 124-16-2
+always \u3975 1235-123456-5
+always \u3976 15-2346-5
+always \u3977 245-346-5
+always \u3978 134-1456-4
+always \u3979 12345-136-4
+always \u397A 1235-2346-2
+always \u397C 1456-5
+always \u397D 245-2346-5
+always \u397E 1345-16-5
+always \u397F 146-5
+always \u3980 1234-1356-2
+always \u3981 14-2345-2
+always \u3982 12-1346-2
+always \u3983 12-1236-4
+always \u3984 134-345-2
+always \u3985 145-346-2
+always \u3986 1235-34-3
+always \u3987 14-34-5
+always \u3989 16-5
+always \u398A 1235-35-2
+always \u398B 1-345-3
+always \u398C 1235-34-3
+always \u398D 2346-5
+always \u398E 1235-25-5
+always \u398F 15-45-5
+always \u3990 1345-16-5
+always \u3991 15-2345-5
+always \u3992 14-16-2
+always \u3993 15-2345-5
+always \u3994 2345-5
+always \u3995 14-12346-2
+always \u3996 134-136-5
+always \u3997 13-1456-3
+always \u3998 13-16-3
+always \u399A 135-2345-4
+always \u399B 1256-2
+always \u399C 1235-25-5
+always \u399D 134-246-4
+always \u399E 12-12356-2
+always \u399F 134-2456-2
+always \u39A0 145-1246-5
+always \u39A1 14-2346-5
+always \u39A2 13-346-2
+always \u39A3 1246-5
+always \u39A4 16-5
+always \u39A5 15-2345-4
+always \u39A6 15-16-5
+always \u39A7 245-1236-4
+always \u39A8 14-1236-4
+always \u39A9 1456-4
+always \u39AA 15-346-5
+always \u39AB 125-345-3
+always \u39AC 14-25-4
+always \u39AD 14-13456-2
+always \u39AE 245-2345-2
+always \u39AF 1235-25-5
+always \u39B0 13-2345-3
+always \u39B1 25-4
+always \u39B2 125-2456-3
+always \u39B3 1-156-5
+always \u39B4 13-2346-2
+always \u39B5 1-34-3
+always \u39B6 145-346-2
+always \u39B7 235-4
+always \u39B8 13-16-4
+always \u39B9 46-2
+always \u39BA 1245-34-5
+always \u39BB 15-16-2
+always \u39BC 24-456-5
+always \u39BD 1256-5
+always \u39BE 16-2
+always \u39BF 245-2345-4
+always \u39C0 13-16-2
+always \u39C1 245-1256-5
+always \u39C2 124-2345-2
+always \u39C3 24-12356-3
+always \u39C4 245-2345-4
+always \u39C5 134-34-5
+always \u39C6 13-1456-5
+always \u39C7 134-146-4
+always \u39C8 1456-4
+always \u39C9 13-2456-5
+always \u39CA 1234-126-3
+always \u39CB 15-45-4
+always \u39CC 134-146-5
+always \u39CD 12345-1346-4
+always \u39CE 245-23456-3
+always \u39CF 13-1346-3
+always \u39D0 15-12346-4
+always \u39D1 1235-1246-3
+always \u39D2 1256-5
+always \u39D3 13-35-3
+always \u39D4 13-2356-5
+always \u39D5 14-234-4
+always \u39D6 2346-5
+always \u39D7 125-156-4
+always \u39D8 125-156-5
+always \u39D9 135-16-5
+always \u39DA 35-4
+always \u39DB 14-1236-4
+always \u39DC 14-346-5
+always \u39DD 13-23456-3
+always \u39DE 135-345-2
+always \u39DF 123-2356-3
+always \u39E1 1235-2456-5
+always \u39E2 1456-3
+always \u39E3 1-34-3
+always \u39E4 12-12346-5
+always \u39E5 15-2345-4
+always \u39E6 15-45-5
+always \u39E7 24-34-3
+always \u39E8 245-234-2
+always \u39E9 1234-356-5
+always \u39EA 13-1246-4
+always \u39EB 156-2
+always \u39EC 13-12346-4
+always \u39ED 245-235-2
+always \u39EE 1235-34-3
+always \u39EF 14-146-4
+always \u39F0 14-16-5
+always \u39F1 1345-345-2
+always \u39F2 15-1236-4
+always \u39F3 1-25-5
+always \u39F4 2346-4
+always \u39F5 1234-12356-2
+always \u39F6 123-1356-3
+always \u39F7 124-123456-5
+always \u39F8 1234-1356-3
+always \u39F9 124-2346-5
+always \u39FA 124-345-5
+always \u39FB 1-25-2
+always \u39FC 135-246-4
+always \u39FD 13-34-5
+always \u39FE 123-34-3
+always \u39FF 12-456-3
+always \u3A00 135-13456-4
+always \u3A01 1-156-2
+always \u3A02 145-12346-4
+always \u3A03 145-1246-4
+always \u3A04 1-146-5
+always \u3A05 1345-356-5
+always \u3A06 14-1456-4
+always \u3A07 1234-1236-2
+always \u3A08 13-16-4
+always \u3A09 134-1456-2
+always \u3A0A 1246-4
+always \u3A0B 1-2346-4
+always \u3A0C 13-12356-5
+always \u3A0D 135-1346-3
+always \u3A0E 1245-34-5
+always \u3A0F 124-1236-3
+always \u3A10 135-34-4
+always \u3A11 125-12346-3
+always \u3A12 123-1246-3
+always \u3A13 14-146-2
+always \u3A14 1235-1236-5
+always \u3A15 13456-2
+always \u3A16 1-156-5
+always \u3A17 13-346-2
+always \u3A18 15-13456-4
+always \u3A19 15-346-2
+always \u3A1A 15-256-2
+always \u3A1B 24-1236-4
+always \u3A1C 245-2345-2
+always \u3A1D 15-346-5
+always \u3A1E 15-34-5
+always \u3A1F 1235-2456-2
+always \u3A20 134-2345-4
+always \u3A21 1235-123456-2
+always \u3A22 1234-16-3
+always \u3A23 235-4
+always \u3A24 123-2356-3
+always \u3A25 1345-345-5
+always \u3A26 15-12346-4
+always \u3A27 135-136-5
+always \u3A28 12-12356-3
+always \u3A29 13-346-2
+always \u3A2A 1235-456-5
+always \u3A2B 14-1236-4
+always \u3A2C 45-2
+always \u3A2D 1235-34-5
+always \u3A2E 145-12356-3
+always \u3A2F 123-25-5
+always \u3A30 13-123456-5
+always \u3A31 246-2
+always \u3A32 245-2346-5
+always \u3A33 13-1246-4
+always \u3A34 13-2345-5
+always \u3A35 13-2345-4
+always \u3A36 145-146-4
+always \u3A37 13-1456-5
+always \u3A38 134-345-5
+always \u3A39 125-1246-5
+always \u3A3A 134-2345-4
+always \u3A3B 245-1236-2
+always \u3A3C 14-236-5
+always \u3A3D 12345-34-5
+always \u3A3E 46-5
+always \u3A3F 13-1256-5
+always \u3A40 13-1256-5
+always \u3A41 245-236-5
+always \u3A43 245-2345-3
+always \u3A44 24-2456-3
+always \u3A45 12-12356-3
+always \u3A46 13-234-5
+always \u3A47 1235-35-2
+always \u3A48 256-4
+always \u3A49 15-16-3
+always \u3A4A 15-45-3
+always \u3A4B 15-34-5
+always \u3A4C 12345-356-5
+always \u3A4D 245-2346-5
+always \u3A4E 346-5
+always \u3A4F 1235-356-3
+always \u3A50 145-136-5
+always \u3A52 245-1456-2
+always \u3A53 1235-1246-4
+always \u3A54 124-123456-2
+always \u3A55 14-13456-3
+always \u3A56 245-46-2
+always \u3A57 15-16-3
+always \u3A58 16-4
+always \u3A59 15-2456-5
+always \u3A5A 134-1356-2
+always \u3A5B 124-12456-2
+always \u3A5C 14-1236-4
+always \u3A5D 1235-146-2
+always \u3A5E 245-2346-5
+always \u3A5F 1-2456-5
+always \u3A60 1234-246-4
+always \u3A61 14-25-4
+always \u3A62 134-16-2
+always \u3A63 1235-1246-3
+always \u3A64 12345-34-3
+always \u3A65 15-2345-3
+always \u3A66 15-16-3
+always \u3A67 135-126-2
+always \u3A68 1235-1246-5
+always \u3A69 245-13456-4
+always \u3A6A 13-346-2
+always \u3A6B 1-156-2
+always \u3A6C 15-16-3
+always \u3A6D 13-246-4
+always \u3A6E 245-2345-3
+always \u3A6F 1234-126-2
+always \u3A70 13-246-4
+always \u3A71 13-236-2
+always \u3A72 245-45-2
+always \u3A73 15-12346-4
+always \u3A74 13-1256-3
+always \u3A75 2346-5
+always \u3A76 1345-346-3
+always \u3A77 245-2345-3
+always \u3A78 145-346-2
+always \u3A79 145-346-2
+always \u3A7A 1234-16-3
+always \u3A7B 13-1246-4
+always \u3A7C 1-156-3
+always \u3A7D 245-16-2
+always \u3A7E 245-16-2
+always \u3A7F 123-34-3
+always \u3A80 1256-2
+always \u3A81 245-1456-2
+always \u3A82 123-34-3
+always \u3A83 1235-2346-2
+always \u3A84 12345-34-2
+always \u3A85 13-1356-5
+always \u3A86 145-16-4
+always \u3A87 15-2345-5
+always \u3A88 13-1246-5
+always \u3A89 1235-2346-2
+always \u3A8A 245-256-2
+always \u3A8B 1235-1236-5
+always \u3A8C 124-12346-4
+always \u3A8D 135-126-2
+always \u3A8E 1345-345-5
+always \u3A8F 135-16-4
+always \u3A90 14-34-5
+always \u3A91 346-5
+always \u3A92 1345-16-2
+always \u3A93 12-2456-2
+always \u3A94 15-1236-5
+always \u3A95 145-246-5
+always \u3A96 14-34-5
+always \u3A97 124-12356-4
+always \u3A98 14-2345-4
+always \u3A99 123-2346-4
+always \u3A9A 15-1236-5
+always \u3A9B 1-136-4
+always \u3A9C 145-25-4
+always \u3A9D 14-2345-5
+always \u3A9E 134-146-5
+always \u3A9F 145-123456-3
+always \u3AA0 245-2345-5
+always \u3AA1 123-2346-5
+always \u3AA2 24-146-3
+always \u3AA3 245-246-3
+always \u3AA4 135-16-5
+always \u3AA5 1-345-3
+always \u3AA6 1456-5
+always \u3AA7 15-16-3
+always \u3AA8 24-1236-5
+always \u3AA9 15-34-5
+always \u3AAA 15-345-5
+always \u3AAB 1245-1246-5
+always \u3AAC 1-25-2
+always \u3AAD 14-34-2
+always \u3AAE 14-13456-2
+always \u3AAF 13-1256-4
+always \u3AB0 1-2456-3
+always \u3AB1 1235-12456-5
+always \u3AB4 13-23456-2
+always \u3AB5 135-1236-5
+always \u3AB6 1235-34-2
+always \u3AB7 145-12356-4
+always \u3AB8 1-136-3
+always \u3AB9 14-12356-4
+always \u3ABA 13-1256-3
+always \u3ABB 13-45-5
+always \u3ABC 123-2346-4
+always \u3ABD 15-25-4
+always \u3ABE 13-2346-2
+always \u3ABF 1-2346-2
+always \u3AC0 145-13456-4
+always \u3AC1 145-12456-5
+always \u3AC2 1-34-5
+always \u3AC3 2345-4
+always \u3AC4 1234-1346-2
+always \u3AC5 245-16-2
+always \u3AC9 1345-16-4
+always \u3ACA 2346-4
+always \u3ACB 1-1236-3
+always \u3ACD 234-2
+always \u3ACE 13-123456-4
+always \u3ACF 246-4
+always \u3AD0 246-4
+always \u3AD1 24-156-2
+always \u3AD2 13-12346-3
+always \u3AD3 245-16-5
+always \u3AD4 13-136-5
+always \u3AD5 13-456-3
+always \u3AD6 1-156-4
+always \u3AD7 1235-12356-5
+always \u3AD8 134-16-5
+always \u3AD9 12345-34-2
+always \u3ADA 1235-34-3
+always \u3ADB 13-456-5
+always \u3ADC 145-1236-5
+always \u3ADD 145-16-3
+always \u3ADE 124-146-3
+always \u3ADF 2345-2
+always \u3AE0 135-1236-5
+always \u3AE1 145-12346-3
+always \u3AE2 245-1256-5
+always \u3AE4 12-1346-4
+always \u3AE5 134-13456-4
+always \u3AE6 124-146-3
+always \u3AE7 135-146-5
+always \u3AE8 1236-3
+always \u3AE9 12-123456-3
+always \u3AEA 12-12346-3
+always \u3AEB 15-2345-4
+always \u3AEC 15-256-2
+always \u3AEE 125-156-5
+always \u3AEF 134-146-5
+always \u3AF0 14-1346-4
+always \u3AF1 1345-1236-4
+always \u3AF2 135-356-5
+always \u3AF3 12-136-2
+always \u3AF4 15-246-5
+always \u3AF5 12345-356-2
+always \u3AF6 1-12356-4
+always \u3AF7 13-16-3
+always \u3AF8 13-346-3
+always \u3AF9 24-34-5
+always \u3AFA 15-16-2
+always \u3AFB 13-123456-5
+always \u3AFC 1-2346-2
+always \u3AFD 14-34-5
+always \u3B01 12-345-3
+always \u3B02 1256-2
+always \u3B03 124-2456-2
+always \u3B04 1345-1236-4
+always \u3B05 134-1236-5
+always \u3B06 134-1456-4
+always \u3B07 1235-12456-5
+always \u3B08 123456-3
+always \u3B09 1345-12456-4
+always \u3B0A 1235-12456-4
+always \u3B0B 1235-12356-2
+always \u3B0C 13-13456-4
+always \u3B0D 1234-34-5
+always \u3B0E 15-2345-4
+always \u3B0F 14-16-5
+always \u3B10 13-1456-5
+always \u3B11 1235-25-5
+always \u3B12 134-1346-4
+always \u3B13 1234-246-5
+always \u3B14 1235-146-2
+always \u3B15 46-2
+always \u3B17 1345-2345-5
+always \u3B18 15-34-5
+always \u3B19 1246-4
+always \u3B1A 12-2346-5
+always \u3B1B 15-16-3
+always \u3B1C 13-1456-5
+always \u3B1D 15-12346-3
+always \u3B1E 1235-2346-5
+always \u3B1F 12345-136-3
+always \u3B20 24-2456-5
+always \u3B21 14-13456-3
+always \u3B22 15-16-3
+always \u3B23 145-1246-5
+always \u3B24 245-16-3
+always \u3B25 135-146-5
+always \u3B26 236-5
+always \u3B27 135-146-5
+always \u3B29 1235-1246-5
+always \u3B2A 145-346-2
+always \u3B2B 2345-5
+always \u3B2C 13-1256-5
+always \u3B2D 13-246-5
+always \u3B2E 1345-1236-5
+always \u3B2F 14-346-5
+always \u3B30 1256-2
+always \u3B31 124-16-5
+always \u3B32 124-2345-3
+always \u3B33 34-4
+always \u3B34 1235-12346-4
+always \u3B35 13-246-3
+always \u3B36 1235-146-5
+always \u3B37 13-256-5
+always \u3B38 124-246-3
+always \u3B39 1-1356-3
+always \u3B3A 124-1356-2
+always \u3B3B 1235-456-3
+always \u3B3C 12345-34-5
+always \u3B3F 124-123456-3
+always \u3B41 1245-1356-2
+always \u3B42 13-246-4
+always \u3B43 13-1346-3
+always \u3B44 15-1456-5
+always \u3B47 45-5
+always \u3B48 13-236-2
+always \u3B49 1235-35-2
+always \u3B4A 15-16-3
+always \u3B4B 135-1346-5
+always \u3B4C 134-12356-2
+always \u3B4D 245-16-3
+always \u3B4E 13-1346-3
+always \u3B4F 1246-4
+always \u3B51 134-356-5
+always \u3B52 15-156-5
+always \u3B53 135-2345-5
+always \u3B54 14-34-2
+always \u3B55 245-1256-3
+always \u3B58 15-23456-2
+always \u3B59 1-2346-2
+always \u3B5A 14-1256-4
+always \u3B5B 1234-2456-5
+always \u3B5C 1245-12346-2
+always \u3B5D 245-234-2
+always \u3B5E 14-346-5
+always \u3B5F 13-12346-4
+always \u3B60 15-2345-4
+always \u3B61 15-1456-5
+always \u3B62 15-13456-3
+always \u3B64 1345-246-4
+always \u3B66 12-345-3
+always \u3B68 346-2
+always \u3B69 14-346-5
+always \u3B6A 12345-34-3
+always \u3B6B 245-25-2
+always \u3B6C 1-34-3
+always \u3B6D 135-356-5
+always \u3B6E 125-1236-4
+always \u3B6F 1-2346-2
+always \u3B70 125-1246-4
+always \u3B71 15-23456-2
+always \u3B72 13-16-2
+always \u3B74 13-2345-3
+always \u3B76 13-2356-5
+always \u3B78 124-34-2
+always \u3B79 15-2345-2
+always \u3B7A 2345-3
+always \u3B7B 124-1346-2
+always \u3B7C 124-345-5
+always \u3B7D 145-16-4
+always \u3B7E 13-236-2
+always \u3B7F 1346-2
+always \u3B80 1235-1236-2
+always \u3B81 15-246-2
+always \u3B82 13-1256-3
+always \u3B83 1246-3
+always \u3B84 135-1346-4
+always \u3B85 1-1246-3
+always \u3B86 1345-346-5
+always \u3B87 124-2345-5
+always \u3B88 1345-2456-5
+always \u3B8B 234-4
+always \u3B8C 134-2345-2
+always \u3B8D 13-2345-3
+always \u3B8E 135-356-3
+always \u3B8F 1345-2456-5
+always \u3B90 15-13456-4
+always \u3B91 12-345-3
+always \u3B92 1245-1236-4
+always \u3B93 13-136-5
+always \u3B94 124-12346-2
+always \u3B95 1245-12456-4
+always \u3B96 13-23456-2
+always \u3B97 245-1456-2
+always \u3B98 134-146-2
+always \u3B99 2346-5
+always \u3B9A 14-16-5
+always \u3B9B 12-156-2
+always \u3B9C 125-1346-3
+always \u3B9D 1235-2346-2
+always \u3B9E 13-346-2
+always \u3B9F 1345-2345-4
+always \u3BA1 13-12456-5
+always \u3BA2 1235-12356-2
+always \u3BA3 13-2456-5
+always \u3BA4 15-12346-3
+always \u3BA5 135-136-5
+always \u3BA6 15-25-4
+always \u3BA7 34-3
+always \u3BA8 13-16-5
+always \u3BA9 15-16-2
+always \u3BAA 245-235-2
+always \u3BAB 1235-2346-2
+always \u3BAC 12346-3
+always \u3BAD 15-2345-2
+always \u3BAE 13-1256-2
+always \u3BAF 1235-35-2
+always \u3BB0 135-346-2
+always \u3BB1 24-1356-3
+always \u3BB2 12-12356-3
+always \u3BB3 1-136-5
+always \u3BB4 15-146-3
+always \u3BB5 1-1236-3
+always \u3BB6 24-25-5
+always \u3BB7 13-16-3
+always \u3BB8 15-12346-5
+always \u3BB9 1-156-5
+always \u3BBA 135-136-4
+always \u3BBB 15-146-3
+always \u3BBC 24-1236-5
+always \u3BBE 14-1346-4
+always \u3BBF 135-16-5
+always \u3BC0 15-45-5
+always \u3BC1 1234-356-2
+always \u3BC2 145-2456-5
+always \u3BC3 245-16-3
+always \u3BC4 1-156-3
+always \u3BC5 1234-16-2
+always \u3BC6 12-1236-4
+always \u3BC7 135-16-5
+always \u3BC8 15-34-5
+always \u3BC9 1235-35-5
+always \u3BCA 1235-136-2
+always \u3BCB 13-235-4
+always \u3BCC 12-12456-2
+always \u3BCD 13-46-4
+always \u3BCE 1345-136-5
+always \u3BCF 13-34-4
+always \u3BD0 124-25-4
+always \u3BD3 124-345-5
+always \u3BD4 245-1246-5
+always \u3BD5 15-16-3
+always \u3BD6 1-2346-2
+always \u3BD7 15-2345-2
+always \u3BD8 123-12456-4
+always \u3BD9 1-2346-2
+always \u3BDA 124-345-3
+always \u3BDB 1235-34-2
+always \u3BDC 245-1246-5
+always \u3BDD 14-34-5
+always \u3BDE 13-45-5
+always \u3BDF 14-34-5
+always \u3BE0 245-2345-5
+always \u3BE1 1234-146-5
+always \u3BE2 1-136-5
+always \u3BE3 12345-136-3
+always \u3BE4 14-16-5
+always \u3BE5 125-146-3
+always \u3BE6 245-16-2
+always \u3BE9 124-16-5
+always \u3BEA 14-13456-2
+always \u3BEB 245-1256-2
+always \u3BEC 14-2345-4
+always \u3BED 14-34-4
+always \u3BEE 24-34-2
+always \u3BEF 145-1236-4
+always \u3BF0 1-2346-2
+always \u3BF1 1234-146-3
+always \u3BF2 13-1456-5
+always \u3BF3 245-13456-2
+always \u3BF4 1245-12346-2
+always \u3BF5 145-12346-4
+always \u3BF6 125-12346-3
+always \u3BF7 1234-34-2
+always \u3BF8 13-1456-4
+always \u3BF9 135-246-4
+always \u3BFA 13-2345-5
+always \u3BFB 13-123456-4
+always \u3BFD 135-1456-3
+always \u3BFE 125-146-3
+always \u3BFF 14-346-5
+always \u3C00 14-16-2
+always \u3C01 14-25-4
+always \u3C02 24-136-4
+always \u3C03 134-2345-5
+always \u3C04 13-2345-5
+always \u3C05 145-16-2
+always \u3C06 135-356-5
+always \u3C07 13-2345-3
+always \u3C08 14-2345-5
+always \u3C09 1-136-3
+always \u3C0A 15-2345-2
+always \u3C0B 1234-1456-2
+always \u3C0C 245-236-5
+always \u3C0D 14-12346-2
+always \u3C0E 125-1246-5
+always \u3C0F 13-146-3
+always \u3C10 124-25-4
+always \u3C11 24-1236-3
+always \u3C12 15-236-2
+always \u3C14 15-346-5
+always \u3C15 15-16-3
+always \u3C16 14-1236-4
+always \u3C17 245-34-5
+always \u3C18 16-2
+always \u3C19 1345-25-4
+always \u3C1A 14-16-2
+always \u3C1B 236-5
+always \u3C1D 16-4
+always \u3C1E 12-156-3
+always \u3C1F 13-16-5
+always \u3C20 1235-1346-3
+always \u3C21 15-346-5
+always \u3C22 123-1356-3
+always \u3C23 125-156-3
+always \u3C24 245-346-5
+always \u3C25 15-16-5
+always \u3C26 245-1256-3
+always \u3C27 1235-2456-3
+always \u3C28 24-136-4
+always \u3C29 1235-2456-3
+always \u3C2A 13-1246-3
+always \u3C2B 12-1236-3
+always \u3C2C 15-256-2
+always \u3C2D 15-1256-3
+always \u3C2E 24-136-5
+always \u3C2F 124-12356-5
+always \u3C30 245-346-5
+always \u3C31 24-345-5
+always \u3C32 15-1256-5
+always \u3C33 23456-3
+always \u3C34 1234-12356-5
+always \u3C35 125-34-2
+always \u3C36 234-4
+always \u3C37 125-156-5
+always \u3C38 14-2345-4
+always \u3C39 15-2345-3
+always \u3C3A 15-23456-5
+always \u3C3B 15-16-3
+always \u3C3C 245-346-5
+always \u3C3D 2345-5
+always \u3C3E 13-246-5
+always \u3C3F 15-16-3
+always \u3C40 12-156-4
+always \u3C41 15-1246-5
+always \u3C42 123-1346-3
+always \u3C43 1456-4
+always \u3C44 1235-356-3
+always \u3C45 16-5
+always \u3C46 15-16-3
+always \u3C47 15-2346-5
+always \u3C48 13-1456-5
+always \u3C49 346-5
+always \u3C4A 234-3
+always \u3C4B 245-236-5
+always \u3C4C 346-2
+always \u3C4D 14-12456-2
+always \u3C4E 123-123456-3
+always \u3C4F 1-1356-5
+always \u3C52 1235-2346-3
+always \u3C54 15-346-3
+always \u3C55 13-1246-3
+always \u3C56 245-1246-5
+always \u3C57 15-234-3
+always \u3C58 2345-4
+always \u3C59 15-234-4
+always \u3C5A 245-1236-2
+always \u3C5B 12-12456-4
+always \u3C5C 1-345-2
+always \u3C5E 14-345-3
+always \u3C5F 1234-16-4
+always \u3C60 123-34-3
+always \u3C61 24-1356-3
+always \u3C62 14-1346-2
+always \u3C63 124-1246-4
+always \u3C64 15-16-2
+always \u3C65 14-1356-5
+always \u3C66 245-16-3
+always \u3C67 25-5
+always \u3C68 14-2345-5
+always \u3C69 145-34-2
+always \u3C6A 1235-123456-3
+always \u3C6B 14-1236-5
+always \u3C6C 1246-4
+always \u3C6D 145-12456-5
+always \u3C6E 1235-1246-5
+always \u3C6F 2456-2
+always \u3C70 125-2456-4
+always \u3C71 1235-1246-5
+always \u3C72 16-5
+always \u3C73 134-126-5
+always \u3C74 125-156-5
+always \u3C75 12345-136-5
+always \u3C76 1234-1356-2
+always \u3C78 135-16-5
+always \u3C79 14-16-5
+always \u3C7A 14-34-2
+always \u3C7B 14-25-5
+always \u3C7C 1235-2456-3
+always \u3C7D 1-136-4
+always \u3C7E 123-2456-3
+always \u3C7F 245-236-5
+always \u3C80 12-136-2
+always \u3C81 123-12346-3
+always \u3C82 12-1356-2
+always \u3C83 13-234-5
+always \u3C84 13-236-2
+always \u3C85 13-16-5
+always \u3C86 14-13456-2
+always \u3C87 145-12346-3
+always \u3C88 24-146-2
+always \u3C89 245-236-5
+always \u3C8A 1245-1246-5
+always \u3C8B 12-25-5
+always \u3C8C 1345-13456-5
+always \u3C8D 12-156-5
+always \u3C8E 14-1256-2
+always \u3C8F 1234-146-3
+always \u3C91 24-1246-4
+always \u3C92 135-146-5
+always \u3C93 24-34-5
+always \u3C94 15-2345-3
+always \u3C95 14-356-5
+always \u3C96 15-246-3
+always \u3C97 12345-34-3
+always \u3C98 245-1256-2
+always \u3C99 24-34-3
+always \u3C9A 24-345-3
+always \u3C9B 1-156-4
+always \u3C9C 124-1236-4
+always \u3C9D 1245-12346-4
+always \u3C9E 125-34-2
+always \u3C9F 13456-4
+always \u3CA0 134-146-2
+always \u3CA1 1345-2456-5
+always \u3CA2 135-2345-5
+always \u3CA3 15-12356-3
+always \u3CA4 24-2356-3
+always \u3CA5 124-1346-2
+always \u3CA6 1235-1236-5
+always \u3CA7 15-146-5
+always \u3CA8 1245-12346-2
+always \u3CA9 124-345-5
+always \u3CAA 145-1356-3
+always \u3CAB 1234-34-2
+always \u3CAC 13-246-3
+always \u3CAD 124-1236-4
+always \u3CAF 14-1236-2
+always \u3CB0 1345-13456-2
+always \u3CB1 14-346-5
+always \u3CB2 145-346-2
+always \u3CB3 145-346-2
+always \u3CB4 1-12346-5
+always \u3CB5 15-246-3
+always \u3CB6 14-1256-5
+always \u3CB7 145-1236-5
+always \u3CB8 15-16-3
+always \u3CB9 13-16-4
+always \u3CBA 13-16-3
+always \u3CBB 1345-16-5
+always \u3CBC 16-5
+always \u3CBD 1345-2345-5
+always \u3CBE 1256-4
+always \u3CBF 456-4
+always \u3CC0 13-25-5
+always \u3CC1 125-2346-5
+always \u3CC2 2345-2
+always \u3CC3 245-1246-5
+always \u3CC4 15-2345-2
+always \u3CC5 14-234-2
+always \u3CC6 124-12356-4
+always \u3CC7 34-5
+always \u3CC8 1234-356-5
+always \u3CCA 15-16-5
+always \u3CCB 245-234-3
+always \u3CCC 23456-3
+always \u3CCD 135-34-5
+always \u3CCE 135-2345-5
+always \u3CCF 24-156-5
+always \u3CD0 12-345-2
+always \u3CD1 16-5
+always \u3CD2 12345-345-4
+always \u3CD3 234-5
+always \u3CD4 145-1246-5
+always \u3CD5 14-1236-2
+always \u3CD6 16-3
+always \u3CD7 12-2456-5
+always \u3CD8 12-12346-3
+always \u3CD9 13-45-3
+always \u3CDA 15-1256-5
+always \u3CDB 234-2
+always \u3CDC 15-234-3
+always \u3CDD 123-456-3
+always \u3CDE 12-12346-3
+always \u3CDF 13-12346-3
+always \u3CE0 124-345-5
+always \u3CE1 25-3
+always \u3CE2 245-246-2
+always \u3CE4 24-34-2
+always \u3CE5 14-12346-5
+always \u3CE6 15-346-5
+always \u3CE7 1245-2346-5
+always \u3CE8 125-345-2
+always \u3CE9 124-1236-3
+always \u3CEA 1234-16-5
+always \u3CEB 124-345-5
+always \u3CEC 15-45-2
+always \u3CED 15-2345-2
+always \u3CEE 1345-246-5
+always \u3CF0 13-12356-3
+always \u3CF3 123-2346-5
+always \u3CF4 134-16-5
+always \u3CF5 13-16-5
+always \u3CF6 1345-12356-4
+always \u3CF7 1235-34-3
+always \u3CF8 1235-35-3
+always \u3CF9 456-4
+always \u3CFA 234-2
+always \u3CFB 125-2346-2
+always \u3CFC 135-16-5
+always \u3CFD 134-16-2
+always \u3CFE 245-46-3
+always \u3CFF 15-346-5
+always \u3D00 12345-1236-5
+always \u3D01 16-5
+always \u3D02 124-1236-3
+always \u3D03 14-356-5
+always \u3D04 12346-4
+always \u3D05 15-246-3
+always \u3D06 13-1456-5
+always \u3D07 24-2346-5
+always \u3D08 1456-5
+always \u3D09 13-16-4
+always \u3D0A 45-3
+always \u3D0B 15-34-5
+always \u3D0E 1345-2456-5
+always \u3D0F 456-4
+always \u3D10 134-2345-5
+always \u3D11 15-34-5
+always \u3D12 16-5
+always \u3D13 24-2456-3
+always \u3D14 15-2346-5
+always \u3D15 13-16-2
+always \u3D16 1234-126-5
+always \u3D17 234-3
+always \u3D18 134-146-5
+always \u3D19 1-345-4
+always \u3D1A 15-1246-5
+always \u3D1B 1-156-5
+always \u3D1C 135-2345-5
+always \u3D1D 14-16-2
+always \u3D1E 124-146-3
+always \u3D20 1235-1236-2
+always \u3D21 15-46-3
+always \u3D25 245-246-5
+always \u3D26 13-12346-3
+always \u3D27 15-16-2
+always \u3D28 1-136-5
+always \u3D29 235-3
+always \u3D2A 1345-346-5
+always \u3D2B 13-256-5
+always \u3D2C 15-346-5
+always \u3D2D 246-4
+always \u3D2E 15-346-5
+always \u3D2F 1-156-3
+always \u3D30 1345-1356-2
+always \u3D31 24-136-3
+always \u3D32 15-156-3
+always \u3D33 134-1356-4
+always \u3D34 1-1236-5
+always \u3D35 134-16-5
+always \u3D36 245-236-5
+always \u3D37 145-1236-3
+always \u3D38 24-1236-4
+always \u3D39 134-16-2
+always \u3D3B 24-34-5
+always \u3D3C 15-34-5
+always \u3D3D 15-346-5
+always \u3D3E 135-126-2
+always \u3D3F 145-13456-4
+always \u3D40 125-34-2
+always \u3D41 1235-456-3
+always \u3D42 24-34-5
+always \u3D43 24-2346-2
+always \u3D44 1235-1236-5
+always \u3D45 124-1236-5
+always \u3D46 1235-146-5
+always \u3D4A 1345-345-5
+always \u3D4B 134-16-5
+always \u3D4C 15-256-2
+always \u3D4D 134-136-5
+always \u3D4E 13-2345-5
+always \u3D4F 245-1246-4
+always \u3D50 245-236-5
+always \u3D51 1235-2346-5
+always \u3D52 135-16-5
+always \u3D53 24-156-2
+always \u3D54 12-2346-4
+always \u3D55 24-136-5
+always \u3D56 1345-1256-5
+always \u3D57 1234-13456-2
+always \u3D58 134-1236-5
+always \u3D59 245-13456-3
+always \u3D5D 16-5
+always \u3D5E 12-12356-2
+always \u3D5F 1246-3
+always \u3D60 123-34-3
+always \u3D61 135-146-2
+always \u3D62 14-356-2
+always \u3D63 123-2346-4
+always \u3D64 24-345-5
+always \u3D65 134-16-5
+always \u3D66 15-1246-4
+always \u3D67 13-2346-2
+always \u3D68 135-126-5
+always \u3D69 16-5
+always \u3D6A 15-2345-2
+always \u3D6B 1345-16-5
+always \u3D6C 13456-2
+always \u3D6D 1-34-4
+always \u3D6E 12-123456-2
+always \u3D6F 12345-1356-2
+always \u3D70 15-1256-5
+always \u3D71 1234-246-3
+always \u3D72 34-4
+always \u3D73 14-246-2
+always \u3D74 245-1346-2
+always \u3D75 125-12356-5
+always \u3D76 125-25-3
+always \u3D77 135-2345-5
+always \u3D78 236-5
+always \u3D79 134-126-5
+always \u3D7A 1234-2456-5
+always \u3D7B 15-234-3
+always \u3D7D 14-356-4
+always \u3D7E 245-13456-5
+always \u3D7F 15-246-5
+always \u3D80 13-246-3
+always \u3D81 13-25-2
+always \u3D84 2345-2
+always \u3D85 15-236-2
+always \u3D86 12-34-2
+always \u3D87 1235-1356-2
+always \u3D88 13456-2
+always \u3D89 15-16-3
+always \u3D8C 14-2345-2
+always \u3D8D 15-2345-4
+always \u3D8E 1235-12456-2
+always \u3D91 14-2345-5
+always \u3D92 24-1236-4
+always \u3D93 245-1346-2
+always \u3D94 135-2456-5
+always \u3D95 13-2345-4
+always \u3D96 24-34-5
+always \u3D97 12345-1236-5
+always \u3D98 145-2345-5
+always \u3D9A 135-345-5
+always \u3D9B 1256-2
+always \u3D9C 45-3
+always \u3D9E 1345-1346-4
+always \u3D9F 14-356-4
+always \u3DA0 16-5
+always \u3DA1 1235-25-4
+always \u3DA3 12-1236-2
+always \u3DA4 12-146-4
+always \u3DA5 12-1236-2
+always \u3DA6 13-1456-5
+always \u3DA7 1345-136-5
+always \u3DA8 1234-16-3
+always \u3DA9 24-136-4
+always \u3DAA 1234-16-3
+always \u3DAB 14-246-5
+always \u3DAC 134-126-5
+always \u3DAD 234-4
+always \u3DAE 24-146-3
+always \u3DAF 14-234-5
+always \u3DB0 1235-1236-2
+always \u3DB2 235-5
+always \u3DB3 13-1456-5
+always \u3DB4 12-156-4
+always \u3DB5 1245-136-5
+always \u3DB6 1345-12346-2
+always \u3DB7 15-1256-3
+always \u3DB8 15-346-2
+always \u3DB9 1235-12346-5
+always \u3DBA 124-2345-5
+always \u3DBB 12345-1356-3
+always \u3DBC 2456-3
+always \u3DBE 135-246-3
+always \u3DBF 135-126-2
+always \u3DC0 245-235-2
+always \u3DC1 145-12346-5
+always \u3DC2 24-34-5
+always \u3DC3 12-1246-4
+always \u3DC4 1235-1246-4
+always \u3DC5 12-345-2
+always \u3DC6 12345-34-5
+always \u3DC7 1235-1246-3
+always \u3DC8 2346-5
+always \u3DC9 1246-5
+always \u3DCA 12345-136-2
+always \u3DCB 124-1236-2
+always \u3DCD 14-123456-2
+always \u3DCE 1235-2346-5
+always \u3DCF 235-4
+always \u3DD0 1235-1246-4
+always \u3DD1 2345-3
+always \u3DD2 1256-2
+always \u3DD3 125-12346-4
+always \u3DD4 2345-5
+always \u3DD5 245-234-2
+always \u3DD6 1-146-5
+always \u3DD7 13-13456-4
+always \u3DD8 124-2456-2
+always \u3DD9 13-2345-3
+always \u3DDA 124-13456-2
+always \u3DDB 135-146-3
+always \u3DDF 124-1246-3
+always \u3DE0 14-1456-2
+always \u3DE1 13-235-4
+always \u3DE2 1-345-4
+always \u3DE3 15-13456-3
+always \u3DE4 1235-34-5
+always \u3DE5 1-1356-3
+always \u3DE6 15-1256-5
+always \u3DE8 12345-1356-3
+always \u3DE9 15-16-3
+always \u3DEA 13-45-4
+always \u3DEB 245-13456-4
+always \u3DEC 134-126-5
+always \u3DED 12345-1356-3
+always \u3DEE 125-146-3
+always \u3DEF 1234-1356-4
+always \u3DF0 14-16-2
+always \u3DF3 2345-5
+always \u3DF4 14-16-5
+always \u3DF5 134-126-5
+always \u3DF6 135-16-5
+always \u3DF7 13-45-4
+always \u3DF8 346-5
+always \u3DF9 24-146-5
+always \u3DFB 34-2
+always \u3DFC 2345-5
+always \u3DFE 13-236-2
+always \u3DFF 15-2345-3
+always \u3E00 124-2456-2
+always \u3E01 1235-1236-5
+always \u3E02 15-1246-5
+always \u3E03 145-2345-4
+always \u3E04 13-16-5
+always \u3E05 13-346-2
+always \u3E06 123-146-4
+always \u3E07 125-12456-4
+always \u3E08 13-246-3
+always \u3E09 15-346-5
+always \u3E0A 14-345-5
+always \u3E0B 12345-1236-2
+always \u3E0C 1235-25-5
+always \u3E0D 15-16-5
+always \u3E0E 1345-346-5
+always \u3E0F 134-16-2
+always \u3E10 1245-1236-2
+always \u3E11 245-12456-5
+always \u3E12 1456-2
+always \u3E13 134-16-5
+always \u3E14 123-1236-5
+always \u3E15 13-236-2
+always \u3E16 245-1256-3
+always \u3E17 124-12346-2
+always \u3E18 12456-5
+always \u3E19 1-2346-3
+always \u3E1A 14-16-4
+always \u3E1B 24-146-2
+always \u3E1C 123-12346-5
+always \u3E1D 123-1236-4
+always \u3E1E 1-2346-2
+always \u3E1F 1-156-3
+always \u3E20 145-246-5
+always \u3E21 24-345-3
+always \u3E22 135-356-5
+always \u3E23 346-5
+always \u3E24 135-2345-5
+always \u3E25 12-1236-2
+always \u3E26 1235-34-5
+always \u3E27 1456-2
+always \u3E28 13-234-3
+always \u3E29 1236-2
+always \u3E2A 12-123456-2
+always \u3E2B 13-2345-3
+always \u3E2C 135-356-5
+always \u3E2D 135-345-3
+always \u3E2E 12345-136-2
+always \u3E2F 123-2346-3
+always \u3E30 124-25-2
+always \u3E31 124-25-2
+always \u3E32 125-25-2
+always \u3E33 14-13456-2
+always \u3E35 13-1246-4
+always \u3E36 2345-3
+always \u3E37 24-156-5
+always \u3E38 1235-12356-4
+always \u3E39 14-346-5
+always \u3E3A 24-345-3
+always \u3E3B 15-156-5
+always \u3E3C 12345-1356-3
+always \u3E3D 135-356-5
+always \u3E3E 1245-136-5
+always \u3E3F 145-34-2
+always \u3E40 125-156-5
+always \u3E41 14-46-2
+always \u3E42 245-2345-4
+always \u3E43 12345-356-5
+always \u3E44 13-16-5
+always \u3E45 125-12346-4
+always \u3E46 1235-1246-3
+always \u3E47 1235-2346-2
+always \u3E48 14-16-2
+always \u3E49 45-2
+always \u3E4A 236-5
+always \u3E4B 15-234-3
+always \u3E4C 12-1236-4
+always \u3E4D 145-16-2
+always \u3E4E 14-356-2
+always \u3E4F 13-1456-4
+always \u3E50 12-12346-2
+always \u3E51 15-156-5
+always \u3E52 1234-34-2
+always \u3E53 246-4
+always \u3E54 13-46-3
+always \u3E55 1235-12456-3
+always \u3E56 1235-12456-5
+always \u3E57 124-146-3
+always \u3E58 1245-34-5
+always \u3E59 12346-4
+always \u3E5A 12346-4
+always \u3E5B 1245-146-2
+always \u3E5C 1456-2
+always \u3E5D 24-156-5
+always \u3E5E 1456-2
+always \u3E5F 13-236-2
+always \u3E60 124-123456-2
+always \u3E61 13-45-5
+always \u3E62 13-23456-3
+always \u3E63 1-12346-3
+always \u3E64 245-346-5
+always \u3E65 1-34-5
+always \u3E66 145-246-3
+always \u3E67 46-3
+always \u3E68 234-5
+always \u3E6A 24-1236-3
+always \u3E6B 16-2
+always \u3E6C 24-156-4
+always \u3E6D 15-156-5
+always \u3E6E 134-126-5
+always \u3E6F 15-234-3
+always \u3E71 245-236-5
+always \u3E72 15-246-5
+always \u3E73 34-2
+always \u3E74 13-1356-3
+always \u3E75 13456-4
+always \u3E76 2345-2
+always \u3E77 15-16-3
+always \u3E78 134-146-5
+always \u3E79 13-1356-3
+always \u3E7A 124-345-5
+always \u3E7B 25-3
+always \u3E7C 13-1256-2
+always \u3E7D 12-1236-4
+always \u3E7E 1234-246-4
+always \u3E7F 1-146-5
+always \u3E80 1345-146-2
+always \u3E81 1345-146-4
+always \u3E82 13-1236-4
+always \u3E83 13-12356-4
+always \u3E84 1256-4
+always \u3E85 1235-12356-2
+always \u3E87 15-156-3
+always \u3E88 12-156-3
+always \u3E89 1235-34-5
+always \u3E8A 46-5
+always \u3E8B 12346-3
+always \u3E8C 15-2345-5
+always \u3E8D 135-2345-3
+always \u3E8E 235-2
+always \u3E8F 14-12356-2
+always \u3E90 14-146-4
+always \u3E91 24-1236-3
+always \u3E92 15-246-3
+always \u3E93 13-16-3
+always \u3E94 1235-2456-5
+always \u3E95 12345-1236-2
+always \u3E96 1235-1236-5
+always \u3E97 12-1236-3
+always \u3E98 1-1236-5
+always \u3E9A 124-345-5
+always \u3E9B 1-34-5
+always \u3E9C 1345-12346-2
+always \u3E9D 15-2345-5
+always \u3E9E 1256-2
+always \u3E9F 1-25-2
+always \u3EA0 234-5
+always \u3EA1 14-16-5
+always \u3EA2 1235-25-3
+always \u3EA3 15-16-3
+always \u3EA4 15-2345-3
+always \u3EA5 12-1236-2
+always \u3EA6 14-2345-2
+always \u3EA7 15-246-3
+always \u3EA8 15-156-3
+always \u3EA9 245-234-2
+always \u3EAA 1234-34-2
+always \u3EAB 245-234-2
+always \u3EAC 13-12346-4
+always \u3EAD 125-156-4
+always \u3EAE 1256-2
+always \u3EB0 24-156-3
+always \u3EB1 1245-1356-2
+always \u3EB2 1345-234-4
+always \u3EB3 134-356-2
+always \u3EB4 135-345-3
+always \u3EB5 13-234-4
+always \u3EB7 15-1256-5
+always \u3EB8 1234-13456-2
+always \u3EB9 135-2345-5
+always \u3EBA 134-146-5
+always \u3EBD 1234-356-3
+always \u3EBF 16-2
+always \u3EC0 1256-2
+always \u3EC1 13-1246-3
+always \u3EC2 1234-13456-2
+always \u3EC3 245-1256-3
+always \u3EC4 135-146-4
+always \u3EC5 1235-1246-5
+always \u3EC6 15-45-5
+always \u3EC7 245-45-2
+always \u3EC8 13-12356-4
+always \u3EC9 135-34-5
+always \u3ECA 134-136-2
+always \u3ECB 14-345-5
+always \u3ECC 124-34-2
+always \u3ECD 34-2
+always \u3ECE 15-2346-5
+always \u3ECF 14-13456-2
+always \u3ED1 13-16-5
+always \u3ED2 13-256-5
+always \u3ED3 125-12356-3
+always \u3ED4 145-25-4
+always \u3ED5 13-236-2
+always \u3ED6 145-2456-5
+always \u3ED7 135-356-5
+always \u3ED9 1256-5
+always \u3EDA 135-1356-3
+always \u3EDB 12-1346-5
+always \u3EDD 14-345-5
+always \u3EDE 135-1236-3
+always \u3EDF 15-1246-2
+always \u3EE0 124-34-2
+always \u3EE1 145-346-2
+always \u3EE2 24-156-3
+always \u3EE3 24-136-5
+always \u3EE7 145-25-5
+always \u3EE8 12456-5
+always \u3EEA 15-1246-5
+always \u3EEB 135-16-5
+always \u3EEC 124-34-2
+always \u3EED 15-2346-5
+always \u3EEE 245-1236-5
+always \u3EEF 124-34-2
+always \u3EF0 1-156-5
+always \u3EF1 12345-1356-2
+always \u3EF2 14-234-2
+always \u3EF5 1-1236-5
+always \u3EF6 135-16-4
+always \u3EF7 13-16-2
+always \u3EF8 125-136-3
+always \u3EF9 15-45-3
+always \u3EFA 14-16-5
+always \u3EFB 145-1246-5
+always \u3EFC 124-1236-2
+always \u3EFD 15-1246-5
+always \u3EFE 235-3
+always \u3EFF 24-34-4
+always \u3F01 16-2
+always \u3F02 1256-2
+always \u3F04 13-16-3
+always \u3F05 123-456-5
+always \u3F06 13456-2
+always \u3F07 1246-5
+always \u3F08 14-25-2
+always \u3F09 1-136-5
+always \u3F0A 124-123456-2
+always \u3F0B 13-34-3
+always \u3F0C 1256-4
+always \u3F0D 14-356-4
+always \u3F0E 135-126-2
+always \u3F0F 1345-356-4
+always \u3F10 1234-2345-2
+always \u3F11 14-2345-5
+always \u3F12 124-1346-4
+always \u3F13 14-2345-2
+always \u3F14 123456-3
+always \u3F15 145-1346-3
+always \u3F16 14-16-5
+always \u3F17 124-13456-2
+always \u3F18 35-4
+always \u3F19 1-12356-5
+always \u3F1A 13-1346-3
+always \u3F1B 15-13456-2
+always \u3F1C 1346-5
+always \u3F1D 12456-4
+always \u3F1E 1234-1356-5
+always \u3F1F 135-126-2
+always \u3F20 124-25-2
+always \u3F21 24-34-3
+always \u3F22 16-2
+always \u3F23 135-126-2
+always \u3F24 245-346-5
+always \u3F25 123-146-4
+always \u3F26 13-12346-4
+always \u3F27 124-12346-2
+always \u3F28 1235-1236-2
+always \u3F29 12-1356-2
+always \u3F2A 13-346-2
+always \u3F2B 1235-12456-5
+always \u3F2C 15-13456-5
+always \u3F2D 145-2345-5
+always \u3F2E 245-16-5
+always \u3F2F 145-12346-5
+always \u3F30 1234-16-2
+always \u3F31 1245-12456-4
+always \u3F32 14-346-5
+always \u3F33 24-1356-4
+always \u3F34 12356-4
+always \u3F35 145-16-5
+always \u3F36 1256-2
+always \u3F37 12-12456-2
+always \u3F38 1245-12346-2
+always \u3F39 123-1346-3
+always \u3F3A 124-1346-2
+always \u3F3B 245-12346-2
+always \u3F3C 1234-246-2
+always \u3F3D 24-456-4
+always \u3F3E 14-34-5
+always \u3F3F 124-12346-2
+always \u3F40 1-1356-5
+always \u3F41 13-2346-2
+always \u3F42 15-345-5
+always \u3F43 1234-1236-3
+always \u3F44 15-156-3
+always \u3F45 145-1356-3
+always \u3F46 145-1346-3
+always \u3F47 1235-34-2
+always \u3F48 16-5
+always \u3F49 15-2345-5
+always \u3F4A 15-346-5
+always \u3F4B 14-25-2
+always \u3F4C 14-234-5
+always \u3F4D 13-1236-3
+always \u3F4E 15-2345-3
+always \u3F4F 13-1236-5
+always \u3F50 15-2345-3
+always \u3F51 124-1236-2
+always \u3F52 24-1356-3
+always \u3F53 24-1356-3
+always \u3F55 234-2
+always \u3F56 1345-1236-2
+always \u3F57 134-34-4
+always \u3F58 13-1346-4
+always \u3F59 13-256-5
+always \u3F5A 12-156-5
+always \u3F5B 13-12356-3
+always \u3F5C 12456-4
+always \u3F5D 14-16-5
+always \u3F5E 14-234-2
+always \u3F5F 14-346-5
+always \u3F60 15-23456-2
+always \u3F61 135-356-3
+always \u3F62 1236-4
+always \u3F63 1256-5
+always \u3F64 13-1256-2
+always \u3F65 1245-12356-2
+always \u3F66 15-256-2
+always \u3F67 125-156-3
+always \u3F68 245-25-2
+always \u3F69 245-1236-5
+always \u3F6A 125-1356-4
+always \u3F6B 235-3
+always \u3F6C 135-16-5
+always \u3F6D 1245-12456-2
+always \u3F6E 15-13456-3
+always \u3F6F 245-16-2
+always \u3F70 24-34-3
+always \u3F71 13-246-4
+always \u3F72 13-246-4
+always \u3F73 15-1256-3
+always \u3F74 1-1346-5
+always \u3F75 1-456-3
+always \u3F77 24-1246-5
+always \u3F78 12-136-2
+always \u3F79 12345-1236-5
+always \u3F7A 13-16-2
+always \u3F7B 1-156-3
+always \u3F7D 13-34-5
+always \u3F7E 34-5
+always \u3F7F 13-25-3
+always \u3F80 245-346-5
+always \u3F81 24-34-5
+always \u3F82 1235-2456-3
+always \u3F83 124-25-2
+always \u3F84 145-34-2
+always \u3F85 125-156-4
+always \u3F86 145-1236-3
+always \u3F87 134-34-5
+always \u3F88 12345-34-5
+always \u3F89 14-13456-2
+always \u3F8A 13-16-2
+always \u3F8B 15-234-5
+always \u3F8C 15-2345-4
+always \u3F8D 1345-2456-2
+always \u3F8E 15-23456-3
+always \u3F8F 13-346-5
+always \u3F90 14-16-5
+always \u3F91 145-345-2
+always \u3F92 1245-34-2
+always \u3F93 45-3
+always \u3F94 14-1256-4
+always \u3F95 24-136-3
+always \u3F96 14-16-4
+always \u3F97 14-46-5
+always \u3F98 13-1356-4
+always \u3F99 15-1456-5
+always \u3F9A 15-346-3
+always \u3F9B 245-1456-4
+always \u3F9C 245-346-5
+always \u3F9D 12-2346-5
+always \u3F9E 234-2
+always \u3F9F 135-34-5
+always \u3FA0 123-456-2
+always \u3FA1 245-236-5
+always \u3FA2 14-2456-5
+always \u3FA3 245-1456-3
+always \u3FA4 245-46-3
+always \u3FA5 12-34-5
+always \u3FA6 1234-356-3
+always \u3FA7 123-2346-5
+always \u3FA8 245-16-4
+always \u3FA9 13-2356-3
+always \u3FAA 24-1356-4
+always \u3FAB 1234-2345-3
+always \u3FAC 13-346-3
+always \u3FAD 1-12356-5
+always \u3FAE 1235-456-2
+always \u3FAF 124-1246-2
+always \u3FB0 1235-34-2
+always \u3FB1 135-356-5
+always \u3FB4 1-345-3
+always \u3FB5 13-16-5
+always \u3FB6 13-34-4
+always \u3FB7 15-16-3
+always \u3FB8 13-146-4
+always \u3FB9 12-2456-2
+always \u3FBA 134-345-5
+always \u3FBB 12-34-2
+always \u3FBC 124-1246-4
+always \u3FBD 1-1246-5
+always \u3FBE 15-2345-3
+always \u3FBF 14-1346-2
+always \u3FC0 135-1236-3
+always \u3FC1 15-246-3 
+always \u3FC2 1-1356-3
+always \u3FC3 145-2456-5
+always \u3FC4 2456-5
+always \u3FC5 15-2345-4
+always \u3FC6 13-2346-3
+always \u3FC7 15-16-2
+always \u3FC8 235-3
+always \u3FC9 124-1246-2
+always \u3FCA 245-1236-4
+always \u3FCB 15-146-3
+always \u3FCC 15-2345-3
+always \u3FCD 13-346-5
+always \u3FCE 12345-136-5
+always \u3FCF 245-256-2
+always \u3FD1 236-5
+always \u3FD2 145-146-4
+always \u3FD3 13-23456-2
+always \u3FD4 14-356-4
+always \u3FD5 2345-2
+always \u3FD6 14-34-2
+always \u3FD7 124-1246-2
+always \u3FD8 13456-2
+always \u3FD9 135-356-5
+always \u3FDA 14-25-5
+always \u3FDB 14-16-5
+always \u3FDC 135-346-4
+always \u3FDD 15-46-3
+always \u3FDE 134-146-5
+always \u3FDF 135-126-2
+always \u3FE0 1235-456-5
+always \u3FE1 145-12356-3
+always \u3FE2 246-5
+always \u3FE3 1235-2346-3
+always \u3FE4 12-123456-4
+always \u3FE5 1235-34-2
+always \u3FE6 1345-13456-5
+always \u3FE7 12-12356-2
+always \u3FE8 14-16-5
+always \u3FE9 124-1346-4
+always \u3FEA 1235-12456-2
+always \u3FEB 135-16-5
+always \u3FEC 135-345-3
+always \u3FED 12-2346-5
+always \u3FEE 46-5
+always \u3FEF 145-345-2
+always \u3FF0 146-2
+always \u3FF1 15-236-2
+always \u3FF2 16-3
+always \u3FF3 125-156-3
+always \u3FF4 145-345-3
+always \u3FF5 1245-1236-4
+always \u3FF6 135-1346-3
+always \u3FF7 12-1246-5
+always \u3FF8 12456-4
+always \u3FF9 124-345-5
+always \u3FFA 135-126-3
+always \u3FFB 13-1236-3
+always \u3FFC 2345-2
+always \u3FFD 15-16-3
+always \u3FFE 1-34-5
+always \u3FFF 23456-4
+always \u4000 12345-1236-5
+always \u4001 234-5
+always \u4002 1236-3
+always \u4003 124-1246-2
+always \u4004 134-1356-2
+always \u4005 24-2346-5
+always \u4006 13-1456-5
+always \u4007 13-34-4
+always \u4008 13-16-5
+always \u4009 245-246-2
+always \u400A 13-246-4
+always \u400B 2345-2
+always \u400C 15-16-5
+always \u400D 123-1236-5
+always \u400E 134-2345-4
+always \u400F 15-45-5
+always \u4010 24-1236-3
+always \u4011 25-5
+always \u4012 245-2345-3
+always \u4013 1235-12456-5
+always \u4014 1245-136-5
+always \u4015 1-136-5
+always \u4016 124-2345-3
+always \u4017 13-236-2
+always \u4018 15-16-3
+always \u4019 245-16-5
+always \u401A 1346-2
+always \u401B 134-356-5
+always \u401C 134-356-5
+always \u401E 13-246-4
+always \u401F 12345-1236-2
+always \u4020 245-1256-2
+always \u4021 1-1236-3
+always \u4022 24-123456-5
+always \u4023 135-16-5
+always \u4024 134-12356-5
+always \u4025 24-25-5
+always \u4026 13-34-4
+always \u4027 1235-12346-4
+always \u4028 1235-35-5
+always \u4029 14-25-5
+always \u402A 1235-1346-2
+always \u402B 13-23456-2
+always \u402C 245-45-2
+always \u402D 13-2456-3
+always \u402E 1235-456-3
+always \u402F 135-34-4
+always \u4030 13-34-4
+always \u4031 12345-1356-3
+always \u4032 134-34-5
+always \u4033 14-345-5
+always \u4034 13456-4
+always \u4035 24-156-3
+always \u4036 14-46-5
+always \u4037 13-346-2
+always \u4038 145-16-5
+always \u4039 13-346-2
+always \u403A 124-146-3
+always \u403B 1234-13456-5
+always \u403C 1245-136-5
+always \u403D 2345-2
+always \u403E 145-34-4
+always \u403F 1-2346-5
+always \u4040 145-13456-4
+always \u4041 14-1346-4
+always \u4042 15-2345-5
+always \u4043 135-246-3
+always \u4044 15-13456-5
+always \u4045 134-1356-5
+always \u4046 1236-4
+always \u4047 134-16-5
+always \u4048 245-16-5
+always \u4049 245-16-5
+always \u404A 25-5
+always \u404B 15-346-2
+always \u404C 1256-5
+always \u404D 245-23456-5
+always \u404E 12-1356-2
+always \u404F 246-4
+always \u4050 13456-5
+always \u4051 24-156-5
+always \u4052 13-16-2
+always \u4053 13-346-5
+always \u4054 1235-1236-5
+always \u4055 134-1456-2
+always \u4056 14-12356-2
+always \u4057 123-2456-4
+always \u4058 246-2
+always \u4059 2345-5
+always \u405A 15-123456-4
+always \u405B 13-1246-5
+always \u405C 1235-456-4
+always \u405D 13456-4
+always \u405E 24-1356-4
+always \u405F 12-345-2
+always \u4060 14-2345-2
+always \u4061 24-1236-4
+always \u4062 15-45-2
+always \u4063 12-12456-2
+always \u4064 1235-1246-4
+always \u4065 1345-16-5
+always \u4066 245-1256-5
+always \u4067 134-246-2
+always \u4068 1235-25-5
+always \u4069 1256-2
+always \u406A 1-1236-4
+always \u406B 1235-34-2
+always \u406C 245-1356-2
+always \u406D 135-246-3
+always \u406E 245-2345-2
+always \u406F 15-16-3
+always \u4070 15-46-5
+always \u4071 123-12356-3
+always \u4072 134-2456-2
+always \u4073 134-1346-4
+always \u4074 1-1236-4
+always \u4075 135-2345-4
+always \u4076 13-16-3
+always \u4077 13-236-2
+always \u4078 1345-12346-2
+always \u4079 135-16-5
+always \u407A 24-156-5
+always \u407B 24-25-5
+always \u407C 134-126-5
+always \u407D 14-346-5
+always \u407E 134-346-5
+always \u407F 134-126-5
+always \u4080 15-16-3
+always \u4081 12-1236-2
+always \u4081 24-136-3
+always \u4082 245-1256-5
+always \u4083 13-246-5
+always \u4084 1235-25-5
+always \u4085 24-1236-3
+always \u4086 15-1256-5
+always \u4087 1345-234-4
+always \u4088 124-12346-2
+always \u4089 1235-12356-2
+always \u408A 1256-5
+always \u408C 12-12346-3
+always \u408D 135-126-2
+always \u408E 125-12456-4
+always \u408F 145-246-3
+always \u4090 1-25-3
+always \u4091 13-16-3
+always \u4092 13-346-2
+always \u4093 13-1246-3
+always \u4094 15-13456-5
+always \u4095 1235-1246-5
+always \u4096 24-156-2
+always \u4097 123-34-3
+always \u4098 12-2456-3
+always \u4099 145-1246-3
+always \u409A 246-2
+always \u409B 1256-2
+always \u409C 135-1346-5
+always \u409D 1-2346-2
+always \u409E 1-2346-5
+always \u409F 13-23456-3
+always \u40A0 24-156-4
+always \u40A1 145-16-4
+always \u40A2 145-12346-4
+always \u40A3 245-156-3
+always \u40A4 12345-34-5
+always \u40A5 134-1456-2
+always \u40A6 1-136-4
+always \u40A7 1-136-4
+always \u40A9 2345-5
+always \u40AA 145-246-5
+always \u40AB 1235-12346-2
+always \u40AC 13-12346-4
+always \u40AD 245-246-3
+always \u40AE 14-236-5
+always \u40AF 13-2356-5
+always \u40B0 14-345-5
+always \u40B1 1245-1246-5
+always \u40B2 12345-345-4
+always \u40B3 12-35-4
+always \u40B4 2345-2
+always \u40B5 13-12346-3
+always \u40B6 13-346-2
+always \u40B7 13-2356-3
+always \u40B8 13-25-2
+always \u40B9 15-25-4
+always \u40BA 123-2346-3
+always \u40BB 1-1356-5
+always \u40BC 1345-346-5
+always \u40BD 145-246-5
+always \u40BE 14-2456-4
+always \u40BF 124-345-5
+always \u40C0 245-1246-5
+always \u40C1 23456-3
+always \u40C2 13-123456-4
+always \u40C5 145-16-3
+always \u40C6 13-16-3
+always \u40C7 134-2345-2
+always \u40C8 13-346-3
+always \u40C9 134-1456-2
+always \u40CA 13-1256-4
+always \u40CB 1256-2
+always \u40CC 1-136-3
+always \u40CD 1-1246-5
+always \u40CE 1-345-5
+always \u40CF 15-13456-3
+always \u40D1 135-1236-3
+always \u40D2 1235-2346-2
+always \u40D3 13-12356-5
+always \u40D4 1235-12346-2
+always \u40D5 14-25-5
+always \u40D6 34-5
+always \u40D7 135-126-3
+always \u40D8 123-1356-3
+always \u40D9 14-34-5
+always \u40DA 245-34-5
+always \u40DB 14-2345-2
+always \u40DC 16-3
+always \u40DD 245-246-5
+always \u40DE 24-34-2
+always \u40E0 15-45-5
+always \u40E1 245-1456-2
+always \u40E2 245-1456-3
+always \u40E3 1235-1246-4
+always \u40E4 15-34-5
+always \u40E5 12-456-2
+always \u40E6 145-123456-3
+always \u40E7 14-12346-2
+always \u40E9 1345-146-2
+always \u40EA 124-1236-2
+always \u40EB 145-1236-4
+always \u40EC 123-1246-4
+always \u40ED 13-1236-4
+always \u40EE 145-345-2
+always \u40EF 14-16-5
+always \u40F0 245-345-3
+always \u40F1 13-1456-3
+always \u40F2 1234-1236-2
+always \u40F3 14-345-5
+always \u40F4 1-34-3
+always \u40F5 1345-246-4
+always \u40F6 1235-2356-2
+always \u40F7 13456-2
+always \u40F8 13-1456-3
+always \u40F9 14-1236-5
+always \u40FA 134-126-2
+always \u40FB 135-345-5
+always \u40FC 12-34-3
+always \u40FD 13-1246-4
+always \u40FE 135-16-4
+always \u40FF 12345-34-3
+always \u4100 1235-25-5
+always \u4101 16-5
+always \u4102 14-234-5
+always \u4103 46-3
+always \u4104 1456-3
+always \u4105 13-45-5
+always \u4106 1235-25-2
+always \u4107 12-1356-2
+always \u4108 145-12356-5
+always \u4109 2346-2
+always \u410B 2345-4
+always \u410C 1-1246-5
+always \u410D 1-345-5
+always \u410E 245-16-4
+always \u410F 246-3
+always \u4110 245-45-5
+always \u4111 1235-25-2
+always \u4112 1245-136-4
+always \u4113 1235-456-2
+always \u4114 13-1256-4
+always \u4115 24-2346-5
+always \u4116 1256-2
+always \u4117 45-5
+always \u4118 1234-1356-2
+always \u4119 134-13456-2
+always \u411A 245-146-2
+always \u411B 14-12356-2
+always \u411C 14-16-2
+always \u411D 12-456-3
+always \u411F 245-1246-5
+always \u4120 12-1236-2
+always \u4121 12-1236-3
+always \u4122 245-16-2
+always \u4124 14-2456-5
+always \u4125 14-13456-2
+always \u4126 14-246-4
+always \u4127 1245-1356-2
+always \u4128 1256-2
+always \u4129 16-5
+always \u412A 145-246-4
+always \u412B 245-16-4
+always \u412C 16-2
+always \u412D 1345-2345-2
+always \u412E 12345-34-3
+always \u412F 13-2345-4
+always \u4130 23456-2
+always \u4131 12345-1346-3
+always \u4132 1245-1246-5
+always \u4136 135-16-5
+always \u4137 24-156-2
+always \u4138 1234-126-5
+always \u4139 1345-2345-2
+always \u413A 1-156-5
+always \u413B 12-146-2
+always \u413C 124-2345-4
+always \u413D 124-2345-4
+always \u413E 1245-34-5
+always \u413F 16-5
+always \u4140 14-16-5
+always \u4141 1236-5
+always \u4142 1235-2346-5
+always \u4143 245-235-2
+always \u4144 14-16-5
+always \u4145 13-1246-3
+always \u4146 125-156-5
+always \u4147 15-34-5
+always \u4148 45-5
+always \u4149 23456-5
+always \u414A 12-345-2
+always \u414B 12456-4
+always \u414C 13-45-3
+always \u414D 124-13456-4
+always \u414E 234-4
+always \u414F 1246-4
+always \u4150 13-2345-4
+always \u4151 1245-1246-2
+always \u4152 134-1346-2
+always \u4153 13-1256-4
+always \u4154 125-156-3
+always \u4155 13-1256-3
+always \u4156 1236-3
+always \u4157 1245-1246-2
+always \u4158 14-2456-2
+always \u4159 1235-123456-5
+always \u415A 245-46-4
+always \u415B 12-1346-3
+always \u415C 145-25-5
+always \u415D 123-12346-3
+always \u415E 1345-2346-5
+always \u415F 24-1236-3
+always \u4160 124-16-2
+always \u4161 15-1256-4
+always \u4162 13-234-5
+always \u4163 1235-456-2
+always \u4164 245-16-5
+always \u4165 13-346-2
+always \u4166 134-146-2
+always \u4167 2345-3
+always \u4168 15-46-3
+always \u4169 1-156-4
+always \u416A 124-1246-2
+always \u416B 14-236-5
+always \u416C 2456-5
+always \u416D 1234-1346-2
+always \u416E 245-1346-5
+always \u416F 124-1346-2
+always \u4170 136-4
+always \u4171 136-5
+always \u4172 245-16-2
+always \u4173 12-34-2
+always \u4174 15-25-4
+always \u4175 1-25-2
+always \u4176 1345-12356-5
+always \u4177 124-34-2
+always \u4178 24-136-3
+always \u4179 14-12356-2
+always \u417A 135-246-3
+always \u417B 14-16-2
+always \u417C 134-1236-5
+always \u417D 13-34-4
+always \u417E 245-136-2
+always \u417F 1235-35-2
+always \u4180 134-356-4
+always \u4181 13-146-3
+always \u4182 15-2345-2
+always \u4183 145-146-5
+always \u4184 1-1236-4
+always \u4185 125-156-3
+always \u4188 1-156-5
+always \u4189 135-345-5
+always \u418A 245-1246-5
+always \u418B 245-234-3
+always \u418D 14-12346-2
+always \u418E 15-2345-3
+always \u418F 12345-356-5
+always \u4190 13-25-2
+always \u4191 12-1356-2
+always \u4192 13-234-5
+always \u4193 1245-12456-4
+always \u4194 12-12346-3
+always \u4195 236-5
+always \u4196 1235-12346-2
+always \u4197 13-246-5
+always \u4198 23456-3
+always \u4199 246-2
+always \u419A 124-12346-2
+always \u419B 1-345-5
+always \u419C 234-5
+always \u419D 1-34-2
+always \u419E 246-4
+always \u419F 123-2346-5
+always \u41A0 1235-12456-5
+always \u41A1 14-1346-2
+always \u41A2 236-5
+always \u41A3 12-136-2
+always \u41A4 12-12456-3
+always \u41A5 13-16-2
+always \u41A6 145-1236-5
+always \u41A7 25-3
+always \u41A8 1345-13456-2
+always \u41A9 134-13456-2
+always \u41AA 1235-12346-3
+always \u41AB 12-456-3
+always \u41AC 256-4
+always \u41AD 15-45-3
+always \u41AE 13-1456-5
+always \u41AF 1-25-2
+always \u41B0 1256-3
+always \u41B1 124-1236-3
+always \u41B2 123-1346-3
+always \u41B3 15-1246-5
+always \u41B5 12-1356-2
+always \u41B6 13-234-3
+always \u41B7 15-236-5
+always \u41B8 1-1356-3
+always \u41B9 12-12346-3
+always \u41BA 1234-1236-3
+always \u41BB 245-246-5
+always \u41BC 123-2346-3
+always \u41BD 245-1256-2
+always \u41BE 14-1236-2
+always \u41BF 16-5
+always \u41C0 1245-12346-2
+always \u41C1 15-156-3
+always \u41C2 245-2345-3
+always \u41C3 15-156-5
+always \u41C5 12345-345-3
+always \u41C6 145-12356-4
+always \u41C7 134-1346-2
+always \u41C8 1235-35-5
+always \u41C9 24-156-2
+always \u41CB 1235-2456-5
+always \u41CC 245-246-5
+always \u41CD 12-34-5
+always \u41CE 245-236-5
+always \u41CF 145-1246-4
+always \u41D0 14-16-5
+always \u41D1 135-345-5
+always \u41D2 13-346-5
+always \u41D3 15-1256-3
+always \u41D4 14-25-5
+always \u41D5 15-1256-3
+always \u41D6 256-4
+always \u41D7 1-12346-3
+always \u41D8 1235-34-5
+always \u41D9 1456-4
+always \u41DA 1234-34-3
+always \u41DB 1-156-4
+always \u41DC 245-2345-4
+always \u41DD 13-2345-3
+always \u41DE 13-1236-4
+always \u41DF 13-2345-5
+always \u41E0 1-34-5
+always \u41E1 1-34-5
+always \u41E2 123-34-4
+always \u41E3 1345-346-5
+always \u41E4 1245-1246-5
+always \u41E5 125-2346-2
+always \u41E6 1346-4
+always \u41E7 1-156-5
+always \u41E8 13-12346-5
+always \u41E9 16-5
+always \u41EA 12-156-3
+always \u41EB 13-16-3
+always \u41EC 12-456-4
+always \u41ED 14-146-4
+always \u41EE 1245-136-4
+always \u41EF 1245-12346-2
+always \u41F0 1-1356-3
+always \u41F1 1345-345-5
+always \u41F2 13-23456-2
+always \u41F3 13-2345-3
+always \u41F5 1456-2
+always \u41F6 13-236-2
+always \u41F7 135-346-2
+always \u41F8 12-1356-2
+always \u41F9 13-256-3
+always \u41FA 145-12356-5
+always \u41FB 1246-4
+always \u41FC 16-5
+always \u41FD 1-156-5
+always \u41FE 2345-2
+always \u4200 15-1236-3
+always \u4201 14-123456-2
+always \u4202 1234-13456-2
+always \u4203 1-146-4
+always \u4204 1235-1236-2
+always \u4205 1256-5
+always \u4206 145-2456-5
+always \u4207 1-146-5
+always \u4208 12345-356-2
+always \u4209 24-345-5
+always \u420A 14-13456-2
+always \u420B 124-345-5
+always \u420C 245-1256-3
+always \u420D 134-1356-2
+always \u420E 346-5
+always \u420F 135-126-2
+always \u4210 13-1246-5
+always \u4211 13-35-4
+always \u4212 1345-1236-4
+always \u4213 245-23456-5
+always \u4214 13-23456-3
+always \u4215 12-156-2
+always \u4216 123-2346-3
+always \u4217 15-25-4
+always \u4218 245-156-2
+always \u4219 1-12356-5
+always \u421A 12-156-2
+always \u421B 13-2356-5
+always \u421C 245-1456-5
+always \u421D 15-1256-3
+always \u421E 145-34-4
+always \u421F 245-2346-5
+always \u4220 45-5
+always \u4221 245-12346-5
+always \u4222 15-2456-4
+always \u4223 1-1356-5
+always \u4224 245-2345-2
+always \u4225 13-1456-3
+always \u4226 125-12346-3
+always \u4227 1246-4
+always \u422A 15-16-5
+always \u422B 1345-345-5
+always \u422C 1234-34-2
+always \u422D 1235-2356-2
+always \u422E 13-1256-5
+always \u422F 1-136-3
+always \u4230 24-146-3
+always \u4231 124-146-3
+always \u4232 135-1236-3
+always \u4233 124-345-5
+always \u4234 245-2345-5
+always \u4235 12346-3
+always \u4236 1245-12346-2
+always \u4237 14-25-5
+always \u4238 1235-34-2
+always \u4239 15-12356-4
+always \u423A 1-12346-3
+always \u423B 1234-34-2
+always \u423C 134-346-5
+always \u423D 13-1456-3
+always \u423E 24-146-3
+always \u423F 134-16-5
+always \u4240 24-34-5
+always \u4241 14-13456-2
+always \u4242 14-356-4
+always \u4243 13-46-4
+always \u4244 14-1356-2
+always \u4245 1-156-5
+always \u4246 145-246-4
+always \u4248 15-1236-5
+always \u4249 13-34-3
+always \u424A 12345-1236-5
+always \u424B 134-356-5
+always \u424C 15-1246-5
+always \u424D 13-2345-4
+always \u424E 124-1346-2
+always \u424F 15-346-5
+always \u4250 123-34-3
+always \u4251 34-2
+always \u4252 12345-1236-3
+always \u4253 14-25-5
+always \u4254 245-1236-3
+always \u4255 245-1356-2
+always \u4256 14-13456-2
+always \u4257 16-3
+always \u4258 24-456-5
+always \u4259 256-2
+always \u425A 134-1356-2
+always \u425B 146-4
+always \u425C 1-156-5
+always \u425D 16-4
+always \u425E 145-1236-4
+always \u425F 1235-25-5
+always \u4260 1246-2
+always \u4261 124-1236-2
+always \u4262 15-2346-5
+always \u4263 15-346-5
+always \u4264 15-12356-4
+always \u4265 15-12346-4
+always \u4266 245-2345-3
+always \u4267 14-234-2
+always \u4268 16-5
+always \u4269 12356-3
+always \u426A 14-356-5
+always \u426B 14-16-2
+always \u426C 12345-356-5
+always \u426D 14-346-5
+always \u426E 14-1456-5
+always \u426F 15-2345-5
+always \u4270 15-246-5
+always \u4271 12356-3
+always \u4272 134-16-2
+always \u4273 15-2345-3
+always \u4274 1245-1346-2
+always \u4275 1-12456-5
+always \u4276 24-456-3
+always \u4277 2345-2
+always \u4278 135-2345-5
+always \u4279 14-13456-2
+always \u427A 1235-12346-2
+always \u427B 245-16-2
+always \u427C 14-246-5
+always \u427D 135-1236-4
+always \u427E 134-16-5
+always \u427F 1235-34-2
+always \u4280 1235-34-2
+always \u4282 15-2346-5
+always \u4283 1234-356-5
+always \u4284 245-235-2
+always \u4285 134-13456-2
+always \u4286 13-234-5
+always \u4287 135-34-5
+always \u4288 134-356-2
+always \u4289 15-1236-4
+always \u428A 1246-4
+always \u428B 1-456-3
+always \u428D 14-16-2
+always \u428E 245-45-4
+always \u428F 12-146-4
+always \u4290 1235-123456-2
+always \u4291 15-46-4
+always \u4292 13-13456-3
+always \u4293 24-156-5
+always \u4294 13456-3
+always \u4295 13-2345-3
+always \u4296 1345-1236-4
+always \u4297 1235-456-2
+always \u4298 13-234-5
+always \u4299 2345-2
+always \u429A 145-1246-3
+always \u429B 15-345-5
+always \u429C 124-12456-2
+always \u429D 15-16-3
+always \u429E 1-2346-2
+always \u429F 134-136-2
+always \u42A0 15-16-5
+always \u42A1 134-1236-2
+always \u42A2 13-46-3
+always \u42A3 1235-456-2
+always \u42A4 124-1236-2
+always \u42A5 15-246-5
+always \u42A6 23456-2
+always \u42A7 1234-16-5
+always \u42A8 14-25-2
+always \u42A9 12345-1236-2
+always \u42AA 14-16-5
+always \u42AB 245-1246-4
+always \u42AC 12-35-4
+always \u42AD 12-12356-2
+always \u42AE 145-16-2
+always \u42AF 13-12346-4
+always \u42B0 12-34-2
+always \u42B1 15-2345-3
+always \u42B2 12-1236-4
+always \u42B3 134-126-2
+always \u42B4 245-2345-5
+always \u42B5 245-234-2
+always \u42B6 1-136-5
+always \u42B7 125-156-3
+always \u42B8 1256-3
+always \u42B9 245-2345-3
+always \u42BA 1235-34-5
+always \u42BB 13-1236-3
+always \u42BC 12-156-4
+always \u42BD 13-2356-5
+always \u42BE 134-34-5
+always \u42BF 12345-34-3
+always \u42C0 1235-35-5
+always \u42C1 13-1356-4
+always \u42C2 15-246-2
+always \u42C3 134-146-5
+always \u42C4 456-4
+always \u42C6 14-34-3
+always \u42C7 16-5
+always \u42C8 1245-34-2
+always \u42C9 15-236-5
+always \u42CA 1-1356-3
+always \u42CB 134-1456-2
+always \u42CC 13-46-4
+always \u42CD 2346-3
+always \u42CE 1-1236-5
+always \u42CF 125-25-2
+always \u42D0 236-5
+always \u42D1 135-13456-4
+always \u42D2 24-34-3
+always \u42D3 1-12356-5
+always \u42D4 135-16-5
+always \u42D5 1245-136-5
+always \u42D6 1256-5
+always \u42D7 13-46-4
+always \u42D8 1-1246-5
+always \u42D9 156-4
+always \u42DA 16-5
+always \u42DB 134-16-4
+always \u42DC 245-16-4
+always \u42DD 13456-3
+always \u42DE 456-4
+always \u42DF 13-16-5
+always \u42E0 135-34-4
+always \u42E1 24-34-3
+always \u42E2 1234-346-3
+always \u42E3 12345-1236-2
+always \u42E4 236-5
+always \u42E5 14-16-2
+always \u42E6 12345-1236-2
+always \u42E7 245-1256-2
+always \u42E8 12345-34-4
+always \u42E9 156-2
+always \u42EA 2346-3
+always \u42EB 1-1356-3
+always \u42EC 124-2345-3
+always \u42ED 1256-5
+always \u42EE 13-1456-3
+always \u42EF 245-16-4
+always \u42F0 13-1256-3
+always \u42F1 14-2456-2
+always \u42F2 24-1356-2
+always \u42F3 135-356-5
+always \u42F4 1345-234-5
+always \u42F5 16-5
+always \u42F6 15-1256-4
+always \u42F7 134-12356-2
+always \u42F8 15-256-2
+always \u42F9 12345-34-2
+always \u42FA 245-234-3
+always \u42FB 1345-1456-2
+always \u42FC 13456-2
+always \u42FD 135-1356-4
+always \u42FE 1345-345-5
+always \u42FF 1246-3
+always \u4300 123-2346-3
+always \u4301 246-3
+always \u4302 12356-5
+always \u4303 24-25-5
+always \u4304 13-1356-4
+always \u4305 124-1346-2
+always \u4306 13-1246-5
+always \u4307 15-25-4
+always \u4308 124-345-5
+always \u4309 13-1346-3
+always \u430A 246-2
+always \u430B 145-345-3
+always \u430C 245-346-5
+always \u430D 13-1456-4
+always \u430E 14-236-5
+always \u430F 134-16-5
+always \u4310 134-16-5
+always \u4311 13-2345-3
+always \u4312 14-34-5
+always \u4313 12345-1236-2
+always \u4314 12356-5
+always \u4315 134-16-2
+always \u4316 13-346-2
+always \u4317 12345-34-4
+always \u4318 135-346-5
+always \u4319 1235-456-5
+always \u431A 15-34-5
+always \u431B 246-2
+always \u431C 1345-346-5
+always \u431D 13-1456-5
+always \u431E 14-2345-4
+always \u431F 135-126-2
+always \u4320 13-2345-3
+always \u4321 124-16-4
+always \u4322 14-13456-2
+always \u4323 125-12456-4
+always \u4324 1-156-4
+always \u4325 1456-4
+always \u4326 145-16-2
+always \u4327 12-12356-2
+always \u4328 245-345-3
+always \u4329 134-346-5
+always \u432A 2345-4
+always \u432B 14-1236-4
+always \u432C 12-12346-2
+always \u432D 13-246-3
+always \u432E 24-456-3
+always \u432F 13-12456-5
+always \u4330 1345-346-5
+always \u4331 14-25-5
+always \u4332 15-256-3
+always \u4333 24-156-3
+always \u4334 14-25-5
+always \u4335 1-34-2
+always \u4336 125-156-3
+always \u4337 12-12356-2
+always \u4338 1-12456-5
+always \u4339 13-235-4
+always \u433A 156-4
+always \u433B 16-5
+always \u433C 145-1246-5
+always \u433D 245-2456-4
+always \u433E 1345-1456-2
+always \u433F 12345-34-2
+always \u4340 14-1236-2
+always \u4341 15-1246-5
+always \u4342 1256-2
+always \u4343 246-2
+always \u4344 145-2345-4
+always \u4345 14-13456-2
+always \u4346 1-34-4
+always \u4347 124-345-5
+always \u4348 1234-13456-2
+always \u4349 1-2456-4
+always \u434A 13-246-3
+always \u434B 1-1246-5
+always \u434C 1234-12356-4
+always \u434D 123-12356-5
+always \u434E 245-2345-5
+always \u434F 245-2346-5
+always \u4350 1235-1236-4
+always \u4351 1235-1236-4
+always \u4353 1235-34-5
+always \u4354 13-12346-3
+always \u4355 145-16-4
+always \u4356 12345-34-2
+always \u4357 15-45-5
+always \u4358 134-16-2
+always \u4359 134-356-2
+always \u435A 14-1346-5
+always \u435B 13-34-5
+always \u435C 1-146-5
+always \u435D 124-345-5
+always \u435E 1256-5
+always \u435F 125-12346-5
+always \u4360 14-16-2
+always \u4361 14-34-5
+always \u4362 34-2
+always \u4363 14-356-2
+always \u4364 13-16-4
+always \u4365 14-16-5
+always \u4366 14-16-2
+always \u4367 125-1346-3
+always \u4368 1234-126-5
+always \u4369 46-4
+always \u436A 35-5
+always \u436B 124-25-2
+always \u436C 1234-1356-3
+always \u436D 125-1346-3
+always \u436E 1-146-5
+always \u436F 13-1246-4
+always \u4370 2345-3
+always \u4371 15-1256-2
+always \u4372 1345-2456-2
+always \u4373 13-236-2
+always \u4374 1246-4
+always \u4375 1-1356-3
+always \u4376 145-12346-3
+always \u4377 1246-4
+always \u4378 135-126-2
+always \u4379 24-1236-3
+always \u437A 1235-12456-5
+always \u437B 15-45-5
+always \u437C 125-1236-3
+always \u437D 14-16-5
+always \u437E 45-4
+always \u437F 1235-456-2
+always \u4380 15-236-5
+always \u4381 1235-34-2
+always \u4382 135-146-4
+always \u4383 1245-1236-4
+always \u4384 124-246-2
+always \u4385 1234-126-5
+always \u4386 14-246-5
+always \u4387 1-12356-3
+always \u4388 16-5
+always \u4389 15-1256-5
+always \u438A 1234-126-5
+always \u438B 123-146-5
+always \u438C 12-34-5
+always \u438D 12345-34-3
+always \u438E 1345-345-5
+always \u438F 1235-1236-2
+always \u4390 12-146-4
+always \u4391 14-34-5
+always \u4392 1-1236-4
+always \u4393 124-345-5
+always \u4394 12345-34-3
+always \u4395 1235-12346-3
+always \u4396 125-1356-3
+always \u4397 245-246-2
+always \u4398 15-34-5
+always \u4399 1234-1456-3
+always \u439A 1235-1246-5
+always \u439B 14-146-4
+always \u439C 1235-123456-3
+always \u439D 1-34-5
+always \u439F 156-2
+always \u43A0 156-2
+always \u43A1 1245-12456-4
+always \u43A2 245-16-4
+always \u43A3 15-156-5
+always \u43A4 13-1256-2
+always \u43A6 2345-4
+always \u43A7 135-1346-5
+always \u43A8 346-5
+always \u43A9 125-156-3
+always \u43AA 1345-2346-5
+always \u43AB 125-12346-3
+always \u43AC 135-345-5
+always \u43AD 245-146-3
+always \u43AE 124-16-5
+always \u43AF 1235-1236-5
+always \u43B0 125-25-2
+always \u43B1 135-345-5
+always \u43B2 1-2346-2
+always \u43B3 236-5
+always \u43B4 13-1356-3
+always \u43B5 134-16-5
+always \u43B6 156-5
+always \u43B7 1-34-5
+always \u43B8 34-5
+always \u43B9 123456-2
+always \u43BA 1-156-5
+always \u43BB 1-12356-5
+always \u43BC 14-34-5
+always \u43BD 24-1356-3
+always \u43BE 13-123456-5
+always \u43BF 245-234-2
+always \u43C0 14-345-2
+always \u43C1 125-2456-4
+always \u43C2 15-12356-4
+always \u43C3 134-2345-2
+always \u43C4 145-16-4
+always \u43C5 245-16-5
+always \u43C6 245-146-2
+always \u43C7 1234-246-5
+always \u43C8 14-2345-2
+always \u43C9 24-156-3
+always \u43CA 14-12346-2
+always \u43CB 15-34-5
+always \u43CC 15-16-5
+always \u43CD 45-3
+always \u43CE 12345-1356-2
+always \u43CF 15-1256-3
+always \u43D0 13-236-2
+always \u43D1 1-156-5
+always \u43D2 1234-2345-5
+always \u43D3 13-12456-4
+always \u43D4 1345-1256-5
+always \u43D5 1245-136-4
+always \u43D6 1-136-5
+always \u43D7 13-2456-5
+always \u43D8 1234-16-4
+always \u43D9 124-1236-4
+always \u43DA 12-146-4
+always \u43DB 12-123456-4
+always \u43DC 1235-2346-3
+always \u43DD 1-12456-3
+always \u43DE 134-126-5
+always \u43DF 135-346-2
+always \u43E0 245-16-5
+always \u43E1 24-156-5
+always \u43E2 135-16-4
+always \u43E3 245-1256-3
+always \u43E4 15-1456-5
+always \u43E5 124-1236-3
+always \u43E6 13-35-3
+always \u43E7 1345-345-5
+always \u43E8 145-1246-3
+always \u43E9 15-346-2
+always \u43EA 156-5
+always \u43EB 15-234-3
+always \u43EC 134-12356-2
+always \u43EE 15-16-2
+always \u43EF 1-156-5
+always \u43F0 1245-136-4
+always \u43F1 13-1256-2
+always \u43F2 145-346-2
+always \u43F3 1-2346-5
+always \u43F4 24-146-5
+always \u43F5 134-1356-2
+always \u43F6 135-16-5
+always \u43F7 1235-1236-5
+always \u43F8 1256-2
+always \u43F9 15-2345-5
+always \u43FA 1234-1346-3
+always \u43FB 1345-1356-2
+always \u43FC 245-1236-2
+always \u43FD 135-34-5
+always \u43FE 1234-1346-3
+always \u43FF 245-16-4
+always \u4400 13-16-5
+always \u4401 1-25-2
+always \u4402 14-34-5
+always \u4403 13-256-4
+always \u4404 1235-1236-5
+always \u4405 15-16-3
+always \u4406 245-2456-4
+always \u4407 12-123456-2
+always \u4408 1-156-2
+always \u4409 125-156-5
+always \u440A 1235-123456-5
+always \u440B 245-12346-3
+always \u440C 124-2345-4
+always \u440D 1-12356-5
+always \u440E 145-16-3
+always \u440F 12-123456-4
+always \u4410 245-234-3
+always \u4411 1-2346-2
+always \u4412 1-345-3
+always \u4413 1245-12356-2
+always \u4414 135-2345-5
+always \u4415 13-16-2
+always \u4416 15-16-3
+always \u4417 145-34-4
+always \u4418 13-236-2
+always \u4419 13-2346-2
+always \u441A 13-16-2
+always \u441B 145-345-3
+always \u441C 12-136-3
+always \u441D 15-25-5
+always \u441E 1245-25-5
+always \u441F 13-12356-3
+always \u4420 1235-456-4
+always \u4421 245-16-2
+always \u4422 1-12356-5
+always \u4423 15-123456-4
+always \u4424 12-2456-3
+always \u4425 12346-4
+always \u4426 123-2346-3
+always \u4427 123-146-5
+always \u4428 13-34-4
+always \u4429 123-2456-4
+always \u442A 13-45-4
+always \u442B 245-12346-3
+always \u442C 245-146-2
+always \u442D 1-156-5
+always \u442E 12-1236-4
+always \u442F 14-356-2
+always \u4430 15-234-3
+always \u4431 1-2456-3
+always \u4432 1-2346-2
+always \u4433 1256-2
+always \u4434 13-2456-5
+always \u4435 1235-456-2
+always \u4436 13-1456-4
+always \u4437 145-1236-3
+always \u4438 1235-25-5
+always \u4439 15-12356-3
+always \u443A 124-1236-2
+always \u443B 13-34-3
+always \u443C 15-16-5
+always \u443D 134-1236-4
+always \u443E 145-25-2
+always \u443F 146-5
+always \u4440 1234-16-5
+always \u4441 34-5
+always \u4442 2456-4
+always \u4443 134-1356-2
+always \u4444 1234-16-5
+always \u4445 134-1356-2
+always \u4446 46-4
+always \u4447 1-156-2
+always \u4448 135-126-2
+always \u4449 13456-2
+always \u444A 1246-2
+always \u444B 1245-1346-4
+always \u444C 14-1236-2
+always \u444D 2345-3
+always \u444E 245-2345-3
+always \u444F 245-45-2
+always \u4450 1-136-4
+always \u4451 1234-34-2
+always \u4452 13-1456-5
+always \u4453 124-2456-2
+always \u4454 12345-356-5
+always \u4455 24-34-4
+always \u4457 145-1346-5
+always \u4458 245-25-2
+always \u4459 1245-1236-2
+always \u445A 124-2345-2
+always \u445B 24-156-5
+always \u445C 124-345-5
+always \u445D 13-23456-4
+always \u445E 24-123456-5
+always \u445F 1235-456-2
+always \u4460 14-246-4
+always \u4461 12-345-3
+always \u4462 34-5
+always \u4463 12-136-3
+always \u4464 13-1456-5
+always \u4465 2346-5
+always \u4466 13-12356-3
+always \u4467 135-34-5
+always \u4468 145-25-5
+always \u4469 13-34-4
+always \u446A 2346-5
+always \u446B 135-1356-3
+always \u446C 246-5
+always \u446D 145-16-5
+always \u446F 145-16-5
+always \u4470 135-34-5
+always \u4471 12456-4
+always \u4472 12-2346-5
+always \u4473 14-123456-2
+always \u4474 245-16-2
+always \u4475 134-34-5
+always \u4476 245-2345-5
+always \u4478 125-12346-3
+always \u4479 15-146-3
+always \u447A 12345-1236-2
+always \u447B 234-2
+always \u447C 125-12356-3
+always \u447D 124-345-5
+always \u447F 15-34-5
+always \u4480 135-34-5
+always \u4481 15-16-2
+always \u4482 13-46-4
+always \u4483 125-146-5
+always \u4484 12345-34-5
+always \u4485 124-1356-2
+always \u4486 12-2346-5
+always \u4487 12345-34-5
+always \u4488 12345-356-5
+always \u4489 34-4
+always \u448A 15-16-3
+always \u448B 46-4
+always \u448C 134-13456-5
+always \u448D 1234-1346-4
+always \u448E 134-1346-4
+always \u448F 15-1356-3
+always \u4490 134-1356-2
+always \u4491 245-146-4
+always \u4492 124-246-2
+always \u4493 123-2456-4
+always \u4494 135-2456-5
+always \u4495 15-246-4
+always \u4496 15-1456-5
+always \u4497 245-16-5
+always \u4498 15-1246-3
+always \u4499 245-1346-2
+always \u449A 24-146-4
+always \u449B 1235-12456-5
+always \u449C 1345-234-2
+always \u449D 15-246-2
+always \u449E 12-136-2
+always \u449F 145-1236-3
+always \u44A0 12345-1356-3
+always \u44A1 1456-4
+always \u44A2 1346-2
+always \u44A3 1245-1236-4
+always \u44A4 1245-156-5
+always \u44A5 134-1236-2
+always \u44A6 12345-1236-5
+always \u44A7 245-1256-5
+always \u44A8 24-156-4
+always \u44A9 1235-2346-2
+always \u44AA 135-2345-5
+always \u44AB 145-2456-5
+always \u44AC 134-126-5
+always \u44AD 145-1356-4
+always \u44AE 1235-1346-2
+always \u44B0 123-456-3
+always \u44B1 1-1356-3
+always \u44B2 1-345-5
+always \u44B3 145-25-4
+always \u44B4 234-4
+always \u44B5 1235-146-5
+always \u44B6 124-2345-3
+always \u44B7 13-35-3
+always \u44B8 15-236-5
+always \u44B9 14-356-5
+always \u44BA 13-1456-4
+always \u44BB 245-16-4
+always \u44BC 245-1256-3
+always \u44BD 456-4
+always \u44BE 16-3
+always \u44BF 14-246-2
+always \u44C1 145-1356-4
+always \u44C2 2345-2
+always \u44C3 16-5
+always \u44C4 2345-2
+always \u44C5 245-1456-2
+always \u44C6 1-2346-5
+always \u44C7 1235-2346-5
+always \u44C8 16-5
+always \u44C9 15-346-2
+always \u44CA 34-2
+always \u44CB 1-156-3
+always \u44CC 1-156-5
+always \u44CD 1235-1236-4
+always \u44CE 12-25-5
+always \u44CF 12345-12356-2
+always \u44D0 12-123456-2
+always \u44D1 1234-13456-2
+always \u44D2 123-2356-4
+always \u44D3 24-1236-5
+always \u44D5 1246-4
+always \u44D6 245-235-2
+always \u44D7 245-12346-3
+always \u44D8 13-234-5
+always \u44D9 13-2356-3
+always \u44DA 245-1256-3
+always \u44DB 13-34-4
+always \u44DC 1-156-3
+always \u44DD 134-1356-5
+always \u44DE 14-16-5
+always \u44DF 1-12356-3
+always \u44E0 124-345-5
+always \u44E1 1-156-3
+always \u44E2 13-34-5
+always \u44E3 14-46-4
+always \u44E4 1235-34-3
+always \u44E5 14-345-5
+always \u44E6 145-2345-4
+always \u44E7 13-16-2
+always \u44E8 13456-3
+always \u44EA 145-1346-5
+always \u44EB 245-16-2
+always \u44ED 24-345-5
+always \u44EE 134-146-5
+always \u44EF 145-34-2
+always \u44F0 1456-3
+always \u44F1 125-1246-3
+always \u44F2 1245-1246-5
+always \u44F3 1235-136-4
+always \u44F4 1245-12456-4
+always \u44F5 12345-34-3
+always \u44F6 14-345-5
+always \u44F7 15-13456-5
+always \u44F8 13-2345-3
+always \u44F9 16-5
+always \u44FA 134-16-2
+always \u44FB 123-12356-5
+always \u44FC 1235-2346-5
+always \u44FD 13-16-5
+always \u44FE 15-25-3
+always \u44FF 1235-1236-5
+always \u4500 1245-1246-2
+always \u4501 14-16-5
+always \u4502 125-156-4
+always \u4503 125-34-4
+always \u4504 246-5
+always \u4505 13-2346-3
+always \u4506 134-2456-2
+always \u4507 245-16-4
+always \u4508 13-12346-5
+always \u4509 14-16-5
+always \u450A 135-13456-3
+always \u450B 24-345-3
+always \u450C 13-146-3
+always \u450D 145-34-4
+always \u450E 15-34-5
+always \u450F 12-12356-5
+always \u4510 13-2345-3
+always \u4511 15-346-2
+always \u4512 135-356-5
+always \u4513 15-1256-4
+always \u4514 13-13456-5
+always \u4515 1234-34-2
+always \u4516 14-13456-2
+always \u4517 15-46-2
+always \u4518 125-25-5
+always \u4519 145-246-5
+always \u451A 12-123456-2
+always \u451B 245-13456-4
+always \u451C 1345-1236-2
+always \u451D 1-2456-3
+always \u451E 14-1256-5
+always \u451F 16-2
+always \u4520 24-146-4
+always \u4521 1256-2
+always \u4522 1235-35-2
+always \u4523 14-16-2
+always \u4524 1234-345-3
+always \u4525 15-246-3
+always \u4526 134-356-2
+always \u4527 14-16-2
+always \u4528 1246-2
+always \u4529 15-34-5
+always \u452A 24-456-4
+always \u452B 24-34-3
+always \u452C 16-5
+always \u452D 1345-13456-5
+always \u452E 15-156-3
+always \u452F 123-34-5
+always \u4530 12345-34-2
+always \u4531 16-3
+always \u4532 145-1356-3
+always \u4533 1245-1236-2
+always \u4534 245-2346-5
+always \u4535 13-2345-3
+always \u4536 124-16-2
+always \u4537 245-1456-2
+always \u4538 135-246-4
+always \u4539 15-1246-5
+always \u453A 1246-2
+always \u453B 145-123456-3
+always \u453C 13-16-2
+always \u453D 2456-5
+always \u453E 2346-5
+always \u453F 125-123456-4
+always \u4540 123-12456-4
+always \u4541 12345-356-4
+always \u4543 1456-5
+always \u4544 1-1356-3
+always \u4545 15-146-4
+always \u4546 145-12356-5
+always \u4547 1235-1246-5
+always \u4548 15-346-5
+always \u4549 125-2346-2
+always \u454A 124-1236-2
+always \u454B 124-1346-2
+always \u454C 1-156-5
+always \u454D 16-5
+always \u454E 12345-34-2
+always \u454F 2346-2
+always \u4551 15-1246-3
+always \u4552 13-23456-3
+always \u4553 12-345-2
+always \u4554 15-2345-2
+always \u4555 134-1236-5
+always \u4556 15-123456-3
+always \u4557 135-16-5
+always \u4558 14-13456-2
+always \u4559 13-346-2
+always \u455A 123-1246-5
+always \u455B 13-23456-2
+always \u455C 245-1246-5
+always \u455D 24-1356-5
+always \u455E 14-1346-5
+always \u455F 15-13456-3
+always \u4560 12345-356-5
+always \u4561 14-1256-2
+always \u4562 1-345-4
+always \u4563 1235-2346-2
+always \u4564 245-16-2
+always \u4565 1345-16-4
+always \u4566 13456-2
+always \u4567 15-246-5
+always \u4568 124-1356-2
+always \u4569 14-146-4
+always \u456A 1-2346-2
+always \u456B 123-1246-2
+always \u456C 13-46-3
+always \u456D 245-2345-2
+always \u456E 13-1256-2
+always \u456F 1234-246-2
+always \u4570 12345-1236-2
+always \u4571 124-12356-2
+always \u4572 14-1456-4
+always \u4573 134-16-2
+always \u4574 1-25-2
+always \u4575 15-346-2
+always \u4576 1235-34-5
+always \u4577 134-16-2
+always \u4578 13-346-3
+always \u4579 13-346-2
+always \u457A 245-12346-2
+always \u457B 14-16-5
+always \u457C 1245-1236-2
+always \u457D 1-34-2
+always \u457E 2345-2
+always \u457F 1235-1236-5
+always \u4580 15-246-3
+always \u4581 16-5
+always \u4582 14-45-2
+always \u4583 236-5
+always \u4584 1245-1236-2
+always \u4585 14-13456-2
+always \u4586 1345-46-5
+always \u4587 1256-5
+always \u4588 1345-236-5
+always \u4589 15-1256-3
+always \u458A 16-5
+always \u458B 1345-236-5
+always \u458C 16-5
+always \u458D 245-2345-2
+always \u458E 15-23456-2
+always \u458F 12-34-5
+always \u4590 1456-2
+always \u4591 134-16-5
+always \u4592 15-16-3
+always \u4593 1345-345-5
+always \u4594 1235-1236-5
+always \u4595 245-25-2
+always \u4596 15-23456-2
+always \u4597 2345-2
+always \u4598 124-34-2
+always \u4599 124-16-3
+always \u459A 34-3
+always \u459B 15-25-4
+always \u459C 1456-2
+always \u459D 1235-1246-4
+always \u459E 1-12356-4
+always \u459F 134-1356-2
+always \u45A0 45-2
+always \u45A1 1345-1256-5
+always \u45A2 134-246-2
+always \u45A3 125-146-4
+always \u45A4 12456-3
+always \u45A5 134-146-2
+always \u45A6 245-1256-3
+always \u45A7 1345-345-5
+always \u45A8 1-156-3
+always \u45A9 135-16-5
+always \u45AA 125-156-3
+always \u45AB 135-1346-5
+always \u45AC 13-23456-4
+always \u45AD 13-45-5
+always \u45AE 15-46-5
+always \u45AF 123-1246-2
+always \u45B0 1234-2456-5
+always \u45B1 123-456-3
+always \u45B2 15-256-2
+always \u45B3 1-345-5
+always \u45B4 246-2
+always \u45B5 123-123456-3
+always \u45B6 1235-1246-3
+always \u45B7 15-16-3
+always \u45B8 2346-2
+always \u45B9 46-2
+always \u45BA 124-246-2
+always \u45BB 234-2
+always \u45BC 13-236-2
+always \u45BD 14-16-2
+always \u45BF 14-16-2
+always \u45C0 12-1356-3
+always \u45C1 13-16-5
+always \u45C2 1235-34-4
+always \u45C3 1-1236-5
+always \u45C4 12345-34-4
+always \u45C5 12-1346-2
+always \u45C6 13-12456-4
+always \u45C7 13-1256-2
+always \u45C8 134-1356-2
+always \u45C9 12-1346-3
+always \u45CA 124-1236-5
+always \u45CB 134-12356-2
+always \u45CC 15-13456-3
+always \u45CD 14-16-2
+always \u45CE 2345-3
+always \u45CF 15-12356-3
+always \u45D0 24-156-3
+always \u45D1 16-5
+always \u45D2 135-13456-5
+always \u45D3 245-12346-3
+always \u45D4 1235-12356-5
+always \u45D5 12456-3
+always \u45D6 145-16-5
+always \u45D7 13-16-3
+always \u45D8 13-2346-4
+always \u45D9 1235-1236-2
+always \u45DA 135-126-2
+always \u45DB 15-234-3
+always \u45DC 14-234-2
+always \u45DD 245-1236-2
+always \u45DE 245-1236-2
+always \u45DF 16-5
+always \u45E0 15-45-2
+always \u45E1 2345-2
+always \u45E2 125-146-4
+always \u45E3 1235-1236-3
+always \u45E4 235-2
+always \u45E5 125-12346-3
+always \u45E6 12345-1356-3
+always \u45E7 123-1346-3
+always \u45E8 1256-2
+always \u45E9 245-16-3
+always \u45EA 1-2346-5
+always \u45EB 134-345-1
+always \u45EC 12345-1356-3
+always \u45EE 24-456-4
+always \u45EF 13-1456-5
+always \u45F0 13-12456-5
+always \u45F1 1234-34-2
+always \u45F2 14-1456-5
+always \u45F4 124-13456-2
+always \u45F5 13-46-3
+always \u45F6 14-345-5
+always \u45F7 16-5
+always \u45F8 235-3
+always \u45F9 245-156-5
+always \u45FA 2345-4
+always \u45FB 13-346-2
+always \u45FC 15-256-3
+always \u45FD 1246-5
+always \u45FE 15-2345-4
+always \u45FF 1345-13456-2
+always \u4600 12345-34-5
+always \u4601 13-346-2
+always \u4602 13-2345-3
+always \u4603 134-126-5
+always \u4604 1-34-5
+always \u4605 1345-2456-5
+always \u4606 245-2345-4
+always \u4607 123456-2
+always \u4608 14-16-5
+always \u4609 245-1236-2
+always \u460A 134-346-5
+always \u460B 13-2345-3
+always \u460C 1345-16-5
+always \u460D 12-2456-5
+always \u460E 12456-3
+always \u460F 15-1256-5
+always \u4610 1345-1256-5
+always \u4611 134-2456-5
+always \u4612 125-1246-3
+always \u4613 123-1236-5
+always \u4614 123-345-5
+always \u4615 1235-1346-2
+always \u4616 1256-5
+always \u4617 1235-1246-3
+always \u4618 1256-5
+always \u4619 1246-5
+always \u461A 125-34-2
+always \u461B 13-16-3
+always \u461C 13-1456-3
+always \u461D 16-5
+always \u461E 15-246-3
+always \u461F 145-246-3
+always \u4620 12345-34-2
+always \u4621 135-16-4
+always \u4622 1-34-4
+always \u4623 125-156-4
+always \u4624 24-34-5
+always \u4625 15-23456-2
+always \u4626 1345-16-2
+always \u4627 46-3
+always \u4628 13-246-4
+always \u4629 15-45-5
+always \u462A 12-12346-3
+always \u462B 1245-34-2
+always \u462C 1245-12346-2
+always \u462D 1-156-5
+always \u462E 15-1346-3
+always \u462F 24-146-3
+always \u4630 24-1236-3
+always \u4631 1256-5
+always \u4632 245-1456-3
+always \u4633 13-1456-3
+always \u4634 1-12346-3
+always \u4635 14-34-5
+always \u4636 1235-1236-5
+always \u4637 135-346-3
+always \u4638 1-156-5
+always \u4639 125-1246-5
+always \u463A 1-1236-5
+always \u463B 1256-5
+always \u463C 12456-4
+always \u463D 1345-16-2
+always \u463E 13-12456-4
+always \u463F 13-236-2
+always \u4640 135-1356-3
+always \u4641 245-1236-2
+always \u4642 1-12346-3
+always \u4643 145-25-5
+always \u4644 245-16-5
+always \u4645 246-5
+always \u4646 123-1246-5
+always \u4647 1345-12456-4
+always \u4648 1235-12356-2
+always \u4649 15-256-2
+always \u464A 1-25-2
+always \u464C 1235-1246-5
+always \u464E 15-346-2
+always \u464F 135-126-2
+always \u4650 123-2346-5
+always \u4651 245-1246-3
+always \u4652 15-1256-5
+always \u4653 135-2456-4
+always \u4654 12356-3
+always \u4655 125-12346-4
+always \u4656 135-1356-3
+always \u4657 124-16-5
+always \u4658 12-34-4
+always \u4659 12-156-2
+always \u465A 1345-246-4
+always \u465B 13-123456-4
+always \u465C 12345-1356-2
+always \u465D 15-346-5
+always \u465E 145-1356-3
+always \u465F 1246-2
+always \u4660 13-236-2
+always \u4661 1235-1246-5
+always \u4662 125-1356-5
+always \u4663 15-345-5
+always \u4664 145-25-4
+always \u4665 14-13456-2
+always \u4666 134-1356-2
+always \u4667 15-256-3
+always \u4668 13-25-4
+always \u4669 134-1356-2
+always \u466A 1234-1236-5
+always \u466C 13456-5
+always \u466D 245-2345-3
+always \u466E 13-12456-5
+always \u466F 12-34-4
+always \u4670 14-16-2
+always \u4671 24-34-2
+always \u4672 245-2345-3
+always \u4673 1234-246-5
+always \u4674 245-2345-3
+always \u4675 15-16-3
+always \u4676 1345-12346-2
+always \u4677 145-2346-2
+always \u4678 145-2346-2
+always \u4679 15-2345-5
+always \u467A 14-2345-2
+always \u467B 245-156-3
+always \u467C 24-146-5
+always \u467D 15-346-2
+always \u467E 24-156-3
+always \u467F 1246-5
+always \u4680 12-136-5
+always \u4681 12-136-3
+always \u4682 1235-2346-5
+always \u4683 234-2
+always \u4684 14-34-5
+always \u4685 14-2456-5
+always \u4686 13456-4
+always \u4687 24-1356-4
+always \u4688 13-45-5
+always \u4689 245-16-5
+always \u468A 13-2345-5
+always \u468B 256-5
+always \u468C 13-146-3
+always \u468D 245-16-5
+always \u468E 12-456-3
+always \u468F 14-1456-5
+always \u4690 13-16-2
+always \u4691 134-2456-2
+always \u4692 12-456-2
+always \u4693 24-136-4
+always \u4694 1234-1456-5
+always \u4695 14-16-5
+always \u4696 14-13456-2
+always \u4697 13-46-3
+always \u4698 12-1356-2
+always \u4699 125-345-3
+always \u469A 15-2345-4
+always \u469B 1235-34-2
+always \u469C 135-16-3
+always \u469D 125-34-2
+always \u469E 145-2456-4
+always \u469F 145-2456-4
+always \u46A0 1235-123456-5
+always \u46A1 15-2456-3
+always \u46A2 12-2346-5
+always \u46A3 124-16-2
+always \u46A5 1345-25-5
+always \u46A6 1-156-5
+always \u46A7 14-234-2
+always \u46A8 12345-356-5
+always \u46A9 13-246-4
+always \u46AA 13-12456-3
+always \u46AB 15-16-2
+always \u46AC 14-1456-2
+always \u46AD 15-45-3
+always \u46AE 1245-1356-2
+always \u46AF 124-146-4
+always \u46B0 1234-16-4
+always \u46B1 15-1456-5
+always \u46B2 24-1236-5
+always \u46B3 1-156-5
+always \u46B4 236-5
+always \u46B5 124-12356-4
+always \u46B6 124-2345-3
+always \u46B7 15-16-5
+always \u46B8 15-346-5
+always \u46B9 1234-16-4
+always \u46BA 246-2
+always \u46BB 246-2
+always \u46BC 1345-1256-5
+always \u46BD 1235-146-5
+always \u46BE 1345-1456-2
+always \u46BF 1456-5
+always \u46C0 12345-1236-4
+always \u46C1 1345-1236-2
+always \u46C2 12-156-4
+always \u46C3 12456-5
+always \u46C4 45-5
+always \u46C5 15-23456-2
+always \u46C6 1-12356-5
+always \u46C7 45-4
+always \u46C8 24-156-5
+always \u46C9 134-2345-5
+always \u46CA 15-16-3
+always \u46CB 13-16-5
+always \u46CC 1234-146-2
+always \u46CD 12345-356-5
+always \u46CE 15-236-5
+always \u46CF 1345-16-2
+always \u46D0 245-156-2
+always \u46D1 134-16-5
+always \u46D2 135-2345-5
+always \u46D3 13-2345-3
+always \u46D4 1345-345-2
+always \u46D5 1256-2
+always \u46D6 16-5
+always \u46D7 1-156-4
+always \u46D8 1345-1456-2
+always \u46D9 15-1256-5
+always \u46DA 14-236-5
+always \u46DB 1235-1246-5
+always \u46DC 15-256-5
+always \u46DD 1345-146-2
+always \u46DE 1235-1236-5
+always \u46DF 13-23456-2
+always \u46E0 145-12356-5
+always \u46E1 1235-35-5
+always \u46E2 124-34-3
+always \u46E3 1234-13456-3
+always \u46E4 245-34-5
+always \u46E5 15-16-5
+always \u46E6 15-12346-5
+always \u46E7 134-16-2
+always \u46E8 15-1456-5
+always \u46E9 34-5
+always \u46EA 245-235-5
+always \u46EB 1-1356-5
+always \u46EC 124-146-2
+always \u46ED 15-13456-5
+always \u46EE 13-234-5
+always \u46EF 13-1256-5
+always \u46F0 1235-123456-5
+always \u46F1 124-16-2
+always \u46F2 134-2345-2
+always \u46F3 2345-5
+always \u46F4 13-16-3
+always \u46F5 24-12356-5
+always \u46F6 14-356-4
+always \u46F7 12456-4
+always \u46F8 1-1236-3
+always \u46F9 245-2345-5
+always \u46FA 13-346-5
+always \u46FB 234-5
+always \u46FC 1235-1246-4
+always \u46FD 12-345-3
+always \u46FE 15-34-5
+always \u46FF 13-2346-2
+always \u4700 1345-146-4
+always \u4701 15-16-5
+always \u4702 1235-146-5
+always \u4703 145-1246-3
+always \u4704 12-156-2
+always \u4705 1246-2
+always \u4706 134-126-5
+always \u4707 13-123456-4
+always \u4708 12-146-3
+always \u4709 245-16-3
+always \u470A 245-146-2
+always \u470B 1235-1246-5
+always \u470C 14-12456-2
+always \u470D 14-246-2
+always \u470E 14-146-2
+always \u470F 124-35-3
+always \u4710 124-35-3
+always \u4711 34-5
+always \u4712 146-5
+always \u4713 145-346-2
+always \u4714 124-25-3
+always \u4715 134-2456-5
+always \u4716 124-1236-5
+always \u4717 15-1456-5
+always \u4718 13-13456-4
+always \u4719 1236-2
+always \u471A 124-345-5
+always \u471B 12-1236-2
+always \u471C 1246-5
+always \u471D 124-12456-4
+always \u471E 13-16-5
+always \u471F 12-136-5
+always \u4720 1-156-5
+always \u4721 1256-5
+always \u4722 15-2345-4
+always \u4723 15-1456-3
+always \u4724 1235-12346-5
+always \u4725 145-1236-5
+always \u4727 1345-146-4
+always \u4729 2345-5
+always \u472A 245-234-2
+always \u472B 1235-12346-2
+always \u472C 15-12346-4
+always \u472D 13-256-5
+always \u472E 14-246-2
+always \u472F 13-1256-2
+always \u4730 1235-146-3
+always \u4731 134-1236-2
+always \u4732 14-346-5
+always \u4734 12-156-4
+always \u4735 12-156-4
+always \u4736 15-46-2
+always \u4737 245-1456-3
+always \u4738 134-356-4
+always \u4739 15-34-3
+always \u473A 245-2346-5
+always \u473B 12-156-4
+always \u473C 134-34-5
+always \u473D 1256-2
+always \u473E 1456-3
+always \u473F 24-34-5
+always \u4740 14-234-2
+always \u4741 14-146-2
+always \u4742 24-34-5
+always \u4743 1-2346-2
+always \u4744 24-456-3
+always \u4745 1235-1246-3
+always \u4746 1235-34-3
+always \u4747 1235-1246-3
+always \u4748 2346-5
+always \u4749 134-1356-2
+always \u474A 24-345-5
+always \u474B 125-12346-5
+always \u474C 13-236-2
+always \u474D 13-256-5
+always \u474E 124-12456-3
+always \u474F 14-12356-2
+always \u4750 1246-4
+always \u4751 12-12346-3
+always \u4752 1-34-5
+always \u4753 14-346-5
+always \u4754 1235-12456-3
+always \u4755 1-2346-2
+always \u4756 1-146-4
+always \u4757 15-234-3
+always \u4758 16-5
+always \u4759 12-34-3
+always \u475A 1345-16-2
+always \u475B 135-126-3
+always \u475C 15-12456-3
+always \u475D 16-3
+always \u475E 1235-146-5
+always \u475F 23456-5
+always \u4760 45-2
+always \u4761 134-1236-5
+always \u4762 134-1236-5
+always \u4763 245-1256-2
+always \u4764 14-246-2
+always \u4765 1235-146-2
+always \u4766 1-12346-3
+always \u4767 134-1456-2
+always \u4768 15-2345-2
+always \u4769 1-136-5
+always \u476A 24-34-2
+always \u476B 125-25-2
+always \u476C 1-34-5
+always \u476D 13-12356-5
+always \u476E 15-45-5
+always \u476F 16-2
+always \u4770 1-156-5
+always \u4771 15-346-2
+always \u4772 13-1456-5
+always \u4773 245-1236-2
+always \u4774 13-16-3
+always \u4775 135-34-5
+always \u4776 14-46-2
+always \u4777 1-156-3
+always \u4778 13-16-5
+always \u4779 12456-4
+always \u477A 13-12456-5
+always \u477B 13-1256-3
+always \u477C 245-13456-2
+always \u477D 2456-5
+always \u477E 12345-34-5
+always \u477F 13-1246-5
+always \u4780 1235-12356-5
+always \u4781 2345-4
+always \u4782 1245-12456-4
+always \u4783 1-156-5
+always \u4784 135-246-4
+always \u4785 16-2
+always \u4786 15-25-4
+always \u4787 1-156-5
+always \u4788 13-1246-4
+always \u4789 24-1356-5
+always \u478A 15-256-5
+always \u478B 12-136-5
+always \u478C 24-156-2
+always \u478D 245-13456-2
+always \u478E 12-1346-3
+always \u4790 24-123456-4
+always \u4791 1235-12346-2
+always \u4792 145-12346-5
+always \u4793 12-1356-3
+always \u4794 1246-4
+always \u4795 1245-34-2
+always \u4796 24-34-4
+always \u4797 12-2456-3
+always \u4798 245-16-5
+always \u4799 125-345-3
+always \u479A 245-16-2
+always \u479B 34-3
+always \u479C 135-126-2
+always \u479D 1256-5
+always \u479E 12345-34-2
+always \u479F 1234-126-5
+always \u47A0 1-156-3
+always \u47A1 124-1236-4
+always \u47A2 125-25-5
+always \u47A3 245-346-5
+always \u47A4 245-1256-2
+always \u47A5 234-5
+always \u47A6 1235-2346-2
+always \u47A7 1235-12356-5
+always \u47A8 123-1246-4
+always \u47A9 2346-5
+always \u47AA 13-46-5
+always \u47AB 256-4
+always \u47AC 124-12356-5
+always \u47AD 245-234-5
+always \u47AE 124-34-3
+always \u47AF 12345-34-5
+always \u47B0 245-16-5
+always \u47B1 1235-34-2
+always \u47B3 135-126-2
+always \u47B4 1-146-3
+always \u47B5 13-236-2
+always \u47B6 124-16-5
+always \u47B7 13-236-2
+always \u47B8 135-126-2
+always \u47B9 1235-456-2
+always \u47BA 12-123456-3
+always \u47BB 235-4
+always \u47BC 12-1246-4
+always \u47BD 15-25-4
+always \u47BE 145-16-5
+always \u47BF 245-2345-3
+always \u47C0 245-2456-3
+always \u47C1 15-246-3
+always \u47C2 134-1236-2
+always \u47C3 245-1236-3
+always \u47C4 245-16-5
+always \u47C5 13-2345-5
+always \u47C6 135-16-5
+always \u47C7 13-16-3
+always \u47C8 1-156-5
+always \u47C9 1-34-2
+always \u47CA 245-1256-2
+always \u47CB 1-1236-4
+always \u47CC 13-16-2
+always \u47CD 135-2345-3
+always \u47CE 125-1236-4
+always \u47CF 14-16-5
+always \u47D0 14-16-5
+always \u47D1 236-5
+always \u47D2 245-45-2
+always \u47D3 145-13456-3
+always \u47D4 12345-34-5
+always \u47D5 12-345-5
+always \u47D6 1-156-4
+always \u47D7 24-156-5
+always \u47D8 1235-1346-5
+always \u47D9 245-346-5
+always \u47DA 245-16-2
+always \u47DB 135-126-2
+always \u47DC 1345-345-5
+always \u47DD 124-12356-5
+always \u47DE 12-34-2
+always \u47DF 245-34-5
+always \u47E0 236-5
+always \u47E1 1-156-3
+always \u47E2 12-136-5
+always \u47E3 12-34-5
+always \u47E4 135-16-5
+always \u47E5 134-146-2
+always \u47E6 135-345-2
+always \u47E7 124-2345-2
+always \u47E8 134-1456-2
+always \u47E9 14-346-4
+always \u47EA 12345-1236-4
+always \u47EB 12-1356-2
+always \u47EC 245-234-5
+always \u47ED 124-246-5
+always \u47EE 12345-34-2
+always \u47EF 123-25-5
+always \u47F0 13-2345-4
+always \u47F1 13-16-3
+always \u47F3 1256-4
+always \u47F4 1-136-5
+always \u47F5 245-234-2
+always \u47F6 125-25-5
+always \u47F7 12-156-5
+always \u47F8 123-1246-2
+always \u47F9 14-346-5
+always \u47FA 1234-356-5
+always \u47FB 145-34-5
+always \u47FC 34-4
+always \u47FD 24-34-3
+always \u47FE 1-25-2
+always \u47FF 14-34-5
+always \u4800 12-1346-4
+always \u4801 13-16-3
+always \u4802 12-34-4
+always \u4803 14-46-4
+always \u4804 124-2345-4
+always \u4805 123-123456-4
+always \u4806 12-1346-2
+always \u4807 13-236-2
+always \u4808 124-34-2
+always \u4809 1235-12456-5
+always \u480A 12345-356-5
+always \u480B 1234-2345-2
+always \u480D 15-23456-2
+always \u480E 25-5
+always \u480F 13-16-5
+always \u4810 245-1256-5
+always \u4811 1246-4
+always \u4812 1235-34-2
+always \u4813 245-234-3
+always \u4814 15-1246-5
+always \u4815 245-2456-3
+always \u4817 245-234-5
+always \u4818 1234-16-5
+always \u4819 1234-1346-2
+always \u481A 35-5
+always \u481B 246-2
+always \u481C 1245-12346-2
+always \u481D 15-256-3
+always \u481E 245-34-5
+always \u481F 145-346-2
+always \u4820 12-156-5
+always \u4821 245-25-2
+always \u4822 134-1356-5
+always \u4823 15-45-4
+always \u4824 145-25-5
+always \u4825 135-346-2
+always \u4826 1-156-5
+always \u4827 12-34-2
+always \u4828 12-1236-5
+always \u4829 13-1246-5
+always \u482A 145-12456-5
+always \u482B 125-12356-5
+always \u482C 145-1356-5
+always \u482D 14-2456-5
+always \u482E 124-1356-2
+always \u482F 236-5
+always \u4830 245-45-2
+always \u4831 1-34-2
+always \u4832 14-13456-2
+always \u4833 12-136-3
+always \u4834 12-136-4
+always \u4835 12345-34-5
+always \u4836 24-2346-5
+always \u4837 124-246-4
+always \u4838 123-35-3
+always \u4839 1235-2456-2
+always \u483B 245-235-5
+always \u483C 24-34-5
+always \u483D 1235-2456-2
+always \u483E 24-1236-4
+always \u483F 2356-5
+always \u4840 1-1236-4
+always \u4841 14-12346-4
+always \u4842 13-234-5
+always \u4843 14-16-5
+always \u4844 13-1246-4
+always \u4845 12-123456-3
+always \u4846 1245-12346-2
+always \u4847 236-5
+always \u4848 13-246-5
+always \u4849 123-1346-4
+always \u484A 12345-1236-4
+always \u484B 245-16-2
+always \u484C 1235-12346-2
+always \u484D 12345-34-2
+always \u484E 14-34-2
+always \u484F 1235-12346-2
+always \u4850 124-25-2
+always \u4851 134-1456-2
+always \u4852 124-2345-2
+always \u4853 13-45-5
+always \u4854 245-16-4
+always \u4855 1-1356-4
+always \u4856 245-13456-3
+always \u4857 13-12346-4
+always \u4858 124-2345-2
+always \u4859 14-46-2
+always \u485A 134-146-5
+always \u485B 1456-5
+always \u485C 14-34-5
+always \u485D 45-3
+always \u485E 13-1256-2
+always \u485F 1234-16-5
+always \u4860 123-1356-3
+always \u4861 15-346-2
+always \u4862 135-2345-4
+always \u4863 1235-123456-2
+always \u4864 1-34-3
+always \u4865 1245-12346-2
+always \u4866 15-1346-4
+always \u4867 34-4
+always \u4868 12-2456-2
+always \u4869 123-1356-3
+always \u486A 24-1236-5
+always \u486B 1234-1356-2
+always \u486C 134-1236-5
+always \u486D 15-234-3
+always \u486E 125-12346-3
+always \u486F 245-12346-3
+always \u4870 123-1356-4
+always \u4871 1-12456-4
+always \u4872 12-1236-2
+always \u4873 15-156-3
+always \u4874 12-12346-3
+always \u4875 15-1246-5
+always \u4876 1234-16-5
+always \u4877 123-2456-5
+always \u4878 134-346-5
+always \u4879 1-156-5
+always \u487A 1246-5
+always \u487B 134-1456-2
+always \u487C 14-13456-2
+always \u487D 125-12456-3
+always \u487E 1345-346-5
+always \u487F 14-13456-2
+always \u4880 245-16-5
+always \u4881 236-5
+always \u4882 13-1256-4
+always \u4883 16-5
+always \u4884 15-16-4
+always \u4885 12-136-2
+always \u4886 1245-34-5
+always \u4887 1245-12346-4
+always \u4888 12-136-2
+always \u4889 1345-12346-2
+always \u488A 234-2
+always \u488B 13-16-5
+always \u488C 1234-356-5
+always \u488D 12345-1346-5
+always \u488E 13-16-4
+always \u4890 245-34-2
+always \u4891 145-16-4
+always \u4892 13-246-3
+always \u4893 1256-2
+always \u4894 1235-2346-2
+always \u4895 15-1256-5
+always \u4896 14-1256-5
+always \u4897 245-1256-3
+always \u4899 135-2456-5
+always \u489A 1235-1346-2
+always \u489B 13-235-4
+always \u489C 13-1246-3
+always \u489D 23456-5
+always \u489E 24-34-5
+always \u489F 234-2
+always \u48A0 15-12346-5
+always \u48A1 346-5
+always \u48A2 245-1346-5
+always \u48A3 246-2
+always \u48A4 24-34-5
+always \u48A5 45-5
+always \u48A6 24-2356-5
+always \u48A7 14-246-5
+always \u48A8 245-12346-2
+always \u48A9 1256-5
+always \u48AA 245-246-3
+always \u48AB 15-1246-2
+always \u48AC 245-2345-3
+always \u48AD 2345-5
+always \u48AE 14-356-5
+always \u48AF 14-1456-2
+always \u48B0 124-16-5
+always \u48B1 145-34-2
+always \u48B2 236-5
+always \u48B3 13-16-4
+always \u48B4 245-2345-3
+always \u48B5 256-2
+always \u48B6 135-1346-3
+always \u48B7 1345-345-5
+always \u48B8 245-1256-3
+always \u48B9 13-1256-4
+always \u48BA 12-34-3
+always \u48BB 12-136-2
+always \u48BC 13-12346-3
+always \u48BD 15-46-5
+always \u48BE 15-2345-4
+always \u48BF 1236-3
+always \u48C0 13-1246-4
+always \u48C1 1256-4
+always \u48C2 14-356-4
+always \u48C3 15-346-5
+always \u48C4 124-34-2
+always \u48C5 12-136-2
+always \u48C6 15-13456-2
+always \u48C7 245-234-2
+always \u48C8 15-46-5
+always \u48CA 145-1346-4
+always \u48CB 245-2456-4
+always \u48CC 145-16-4
+always \u48CD 2345-4
+always \u48CE 125-156-3
+always \u48CF 13-12346-3
+always \u48D0 13456-3
+always \u48D1 12-1236-2
+always \u48D3 14-16-2
+always \u48D4 15-25-4
+always \u48D5 134-345-4
+always \u48D6 134-345-5
+always \u48D7 13-146-3
+always \u48D8 124-1346-2
+always \u48D9 1234-356-2
+always \u48DA 14-12356-2
+always \u48DB 245-16-3
+always \u48DC 245-25-2
+always \u48DD 124-34-2
+always \u48DE 2346-5
+always \u48DF 15-16-2
+always \u48E0 15-16-2
+always \u48E1 16-2
+always \u48E2 13-16-2
+always \u48E3 145-1346-4
+always \u48E4 245-236-5
+always \u48E5 135-16-4
+always \u48E6 14-356-5
+always \u48E7 16-5
+always \u48E8 12-123456-2
+always \u48E9 12-123456-2
+always \u48EA 1234-126-5
+always \u48EB 14-16-2
+always \u48EC 145-2456-5
+always \u48ED 234-2
+always \u48EE 135-126-2
+always \u48EF 124-2345-4
+always \u48F0 13-1256-5
+always \u48F1 15-1256-5
+always \u48F2 12345-1236-5
+always \u48F3 125-25-5
+always \u48F4 15-1256-5
+always \u48F5 156-5
+always \u48F6 1235-25-2
+always \u48F7 1-34-3
+always \u48F8 1345-1236-4
+always \u48F9 12345-345-3
+always \u48FA 13-45-5
+always \u48FB 1235-1236-3
+always \u48FC 14-46-2
+always \u48FD 1-156-3
+always \u48FE 134-16-5
+always \u48FF 1256-3
+always \u4901 1456-3
+always \u4902 134-356-2
+always \u4903 1456-3
+always \u4904 134-2345-4
+always \u4905 124-12356-2
+always \u4906 13-1246-4
+always \u4907 15-12356-3
+always \u4908 15-16-3
+always \u4909 134-16-5
+always \u490A 1345-16-5
+always \u490B 1256-5
+always \u490C 245-46-3
+always \u490D 134-16-2
+always \u490E 13-1256-2
+always \u490F 1234-16-4
+always \u4910 13-1456-4
+always \u4911 456-5
+always \u4912 13-16-4
+always \u4913 134-1356-2
+always \u4914 13-2345-5
+always \u4915 15-236-5
+always \u4916 135-146-5
+always \u4917 13-1236-4
+always \u4918 12-1236-4
+always \u4919 14-16-5
+always \u491A 14-16-4
+always \u491B 245-234-2
+always \u491C 145-123456-5
+always \u491D 245-235-2
+always \u491E 256-4
+always \u491F 12-136-2
+always \u4920 1-156-4
+always \u4921 1245-1236-4
+always \u4922 135-345-3
+always \u4923 14-236-5
+always \u4924 123-2456-3
+always \u4925 13-1246-4
+always \u4926 236-5
+always \u4927 1235-1246-5
+always \u4928 1234-16-3
+always \u4929 12-345-2
+always \u492A 145-25-5
+always \u492B 12-1236-2
+always \u492C 24-345-3
+always \u492D 24-156-5
+always \u492E 24-2346-5
+always \u492F 15-13456-2
+always \u4930 13456-2
+always \u4931 24-156-5
+always \u4932 12-156-5
+always \u4933 346-5
+always \u4934 1235-1236-2
+always \u4935 12345-356-5
+always \u4936 1236-3
+always \u4937 2345-4
+always \u4938 245-12346-3
+always \u4939 15-12356-3
+always \u493A 13-1456-3
+always \u493B 145-25-5
+always \u493C 245-45-2
+always \u493D 13-12456-3
+always \u493E 124-146-3
+always \u493F 245-346-5
+always \u4940 12-1236-4
+always \u4941 1235-1236-2
+always \u4942 134-1356-5
+always \u4943 236-5
+always \u4944 245-34-5
+always \u4945 245-1456-2
+always \u4946 13-1456-4
+always \u4947 24-1236-5
+always \u4948 134-34-4
+always \u4949 45-3
+always \u494A 245-46-3
+always \u494B 1234-1356-3
+always \u494C 1-1356-5
+always \u494D 1-156-5
+always \u494E 12-123456-2
+always \u494F 1256-4
+always \u4950 134-12356-2
+always \u4951 134-2456-5
+always \u4952 245-46-2
+always \u4953 245-16-3
+always \u4954 15-34-5
+always \u4955 1234-346-4
+always \u4956 145-2345-5
+always \u4957 123-12456-4
+always \u4958 245-25-5
+always \u4959 15-1246-5
+always \u495A 12-34-3
+always \u495B 13-346-2
+always \u495C 13-2345-5
+always \u495D 146-2
+always \u495E 13-246-4
+always \u495F 346-5
+always \u4960 1-136-3
+always \u4961 145-346-2
+always \u4962 14-12346-2
+always \u4963 125-146-2
+always \u4964 135-146-5
+always \u4965 14-2345-2
+always \u4966 145-25-5
+always \u4967 15-45-2
+always \u4968 14-1256-2
+always \u4969 1246-2
+always \u496A 15-2345-4
+always \u496B 124-346-4
+always \u496C 135-126-2
+always \u496D 1-1356-5
+always \u496E 1-34-2
+always \u496F 135-345-5
+always \u4970 134-1356-2
+always \u4971 15-346-4
+always \u4972 12356-3
+always \u4973 234-3
+always \u4974 13-45-3
+always \u4975 15-246-4
+always \u4976 13-2346-2
+always \u4977 1-345-2
+always \u4978 134-16-2
+always \u497A 346-2
+always \u497C 34-5
+always \u497D 1234-126-3
+always \u497E 15-346-4
+always \u497F 135-16-5
+always \u4980 1256-4
+always \u4981 135-1346-3
+always \u4982 24-1236-5
+always \u4983 1-25-2
+always \u4985 24-1236-5
+always \u4986 13-236-2
+always \u4987 13-16-5
+always \u4988 125-25-4
+always \u4989 15-156-5
+always \u498A 1345-246-4
+always \u498B 146-2
+always \u498C 12-34-5
+always \u498D 34-5
+always \u498E 13-12456-4
+always \u498F 15-346-5
+always \u4990 124-13456-4
+always \u4991 15-236-5
+always \u4992 145-1346-5
+always \u4993 12-1236-3
+always \u4994 124-1236-4
+always \u4995 1234-1356-3
+always \u4996 15-23456-2
+always \u4997 15-1256-5
+always \u4998 15-2345-5
+always \u4999 15-156-5
+always \u499A 123-25-5
+always \u499B 1-1356-5
+always \u499C 34-2
+always \u499D 1235-25-5
+always \u499E 1245-123456-5
+always \u499F 123456-4
+always \u49A0 24-156-4
+always \u49A1 1235-12456-2
+always \u49A2 123-25-5
+always \u49A3 12345-34-5
+always \u49A4 12-1246-5
+always \u49A5 15-2345-2
+always \u49A6 245-1456-2
+always \u49A7 15-16-5
+always \u49A8 14-1236-2
+always \u49A9 123456-2
+always \u49AA 23456-5
+always \u49AB 13456-3
+always \u49AC 245-236-5
+always \u49AD 1235-1346-3
+always \u49AE 12-123456-4
+always \u49AF 1-156-5
+always \u49B0 13-234-3
+always \u49B1 1246-4
+always \u49B2 245-2345-5
+always \u49B3 15-46-5
+always \u49B4 16-5
+always \u49B5 1345-16-4
+always \u49B6 1-1356-5
+always \u49B7 12-2356-5
+always \u49B9 24-156-2
+always \u49BA 145-13456-3
+always \u49BB 125-156-4
+always \u49BC 13-236-2
+always \u49BD 15-1256-5
+always \u49BE 45-2
+always \u49C0 13-25-3
+always \u49C1 15-1256-4
+always \u49C2 1-146-5
+always \u49C3 124-2345-2
+always \u49C4 13-2346-5
+always \u49C5 16-2
+always \u49C6 1235-12346-2
+always \u49C7 16-4
+always \u49C9 14-16-4
+always \u49CA 123-34-3
+always \u49CB 15-2345-4
+always \u49CC 15-1246-3
+always \u49CD 15-16-5
+always \u49CE 15-45-5
+always \u49CF 13-46-5
+always \u49D0 13-25-3
+always \u49D1 145-16-3
+always \u49D2 14-2456-2
+always \u49D3 1-12356-3
+always \u49D4 1345-2345-5
+always \u49D5 1256-5
+always \u49D6 13-2345-5
+always \u49D7 135-16-5
+always \u49D8 1-12456-5
+always \u49D9 14-13456-2
+always \u49DA 1235-146-5
+always \u49DB 135-1346-5
+always \u49DC 124-1346-2
+always \u49DD 1-156-5
+always \u49DE 12345-34-5
+always \u49DF 15-2345-5
+always \u49E0 1-12456-3
+always \u49E1 235-3
+always \u49E2 245-1256-3
+always \u49E3 1456-3
+always \u49E4 1234-34-2
+always \u49E5 1235-1246-5
+always \u49E6 1246-2
+always \u49E7 16-4
+always \u49E8 346-5
+always \u49EA 12-2346-5
+always \u49EB 1235-146-2
+always \u49EC 135-1456-3
+always \u49EE 15-2345-5
+always \u49EF 12-1236-2
+always \u49F0 1235-123456-5
+always \u49F1 13-234-3
+always \u49F2 13-1236-3
+always \u49F3 245-156-2
+always \u49F4 1-156-3
+always \u49F5 1-136-5
+always \u49F6 123-1246-2
+always \u49F7 1245-12356-2
+always \u49F8 13-34-3
+always \u49F9 13456-3
+always \u49FA 15-235-2
+always \u49FB 13-2346-3
+always \u49FC 1235-34-2
+always \u49FD 245-1246-4
+always \u49FE 24-34-3
+always \u49FF 245-236-5
+always \u4A00 145-16-2
+always \u4A01 34-5
+always \u4A02 245-234-3
+always \u4A03 2345-5
+always \u4A04 1236-3
+always \u4A05 14-246-2
+always \u4A06 135-16-2
+always \u4A07 24-456-3
+always \u4A08 135-1456-3
+always \u4A09 13456-3
+always \u4A0A 45-3
+always \u4A0B 1345-236-5
+always \u4A0C 135-146-2
+always \u4A0D 13456-4
+always \u4A0E 1235-12346-2
+always \u4A0F 245-156-2
+always \u4A10 15-23456-2
+always \u4A11 124-16-2
+always \u4A12 1256-4
+always \u4A13 14-356-2
+always \u4A14 135-146-2
+always \u4A15 1256-3
+always \u4A16 13-16-5
+always \u4A17 12345-34-2
+always \u4A18 15-2345-5
+always \u4A19 1456-2
+always \u4A1A 1235-34-3
+always \u4A1B 15-2346-5
+always \u4A1C 135-1356-3
+always \u4A1D 245-13456-3
+always \u4A1E 1256-4
+always \u4A1F 35-3
+always \u4A20 2456-4
+always \u4A21 1235-1236-2
+always \u4A22 145-1236-5
+always \u4A23 13-2346-2
+always \u4A24 145-16-2
+always \u4A25 24-456-3
+always \u4A26 1234-1346-2
+always \u4A27 1456-3
+always \u4A28 1-1246-3
+always \u4A29 14-13456-2
+always \u4A2A 134-2456-2
+always \u4A2B 134-126-5
+always \u4A2C 14-2345-2
+always \u4A2D 15-246-3
+always \u4A2E 15-236-4
+always \u4A2F 1-136-5
+always \u4A30 1234-126-5
+always \u4A31 12345-34-5
+always \u4A32 1345-12356-2
+always \u4A33 15-16-3
+always \u4A34 145-1246-5
+always \u4A35 145-1236-5
+always \u4A36 256-4
+always \u4A37 15-2345-5
+always \u4A38 1456-4
+always \u4A39 24-34-3
+always \u4A3A 145-1246-5
+always \u4A3B 135-1356-5
+always \u4A3C 1235-34-5
+always \u4A3D 12345-356-4
+always \u4A3E 12345-356-3
+always \u4A3F 125-345-2
+always \u4A40 135-16-5
+always \u4A41 12345-356-3
+always \u4A42 15-2345-3
+always \u4A43 24-156-5
+always \u4A44 124-2345-4
+always \u4A45 1-1236-4
+always \u4A46 1-345-4
+always \u4A47 145-2345-3
+always \u4A48 1235-1246-5
+always \u4A49 12345-34-4
+always \u4A4A 12456-4
+always \u4A4B 134-126-4
+always \u4A4C 245-246-2
+always \u4A4D 14-246-4
+always \u4A4F 134-346-5
+always \u4A50 1235-34-3
+always \u4A51 1235-12346-2
+always \u4A52 1256-2
+always \u4A53 245-16-2
+always \u4A54 145-25-5
+always \u4A55 1346-2
+always \u4A56 24-345-3
+always \u4A57 236-5
+always \u4A58 145-16-3
+always \u4A59 15-45-5
+always \u4A5A 145-13456-3
+always \u4A5B 135-16-5
+always \u4A5C 1-12356-5
+always \u4A5D 1234-146-2
+always \u4A5E 1345-2345-2
+always \u4A5F 16-2
+always \u4A60 124-13456-3
+always \u4A61 13-23456-2
+always \u4A62 1-156-5
+always \u4A63 124-34-2
+always \u4A64 15-346-2
+always \u4A65 145-1236-5
+always \u4A66 124-246-2
+always \u4A67 15-236-5
+always \u4A68 12-1346-5
+always \u4A69 45-3
+always \u4A6A 13-12456-4
+always \u4A6B 14-46-4
+always \u4A6C 135-1356-4
+always \u4A6D 13-16-3
+always \u4A6E 14-34-5
+always \u4A6F 13-16-2
+always \u4A70 13-16-2
+always \u4A71 24-34-5
+always \u4A72 145-34-3
+always \u4A73 15-12356-3
+always \u4A74 1235-34-2
+always \u4A75 256-5
+always \u4A76 12-1236-4
+always \u4A77 135-1346-3
+always \u4A78 1245-12346-2
+always \u4A79 2346-2
+always \u4A7A 12346-3
+always \u4A7B 135-345-5
+always \u4A7C 12345-1356-2
+always \u4A7D 1256-3
+always \u4A7E 1-2346-5
+always \u4A7F 12345-136-2
+always \u4A80 13-12456-4
+always \u4A81 135-34-4
+always \u4A82 13-2346-2
+always \u4A83 145-123456-3
+always \u4A84 1235-456-2
+always \u4A85 145-34-2
+always \u4A86 124-16-4
+always \u4A87 1234-12356-5
+always \u4A88 245-2345-5
+always \u4A89 14-346-5
+always \u4A8A 14-12346-2
+always \u4A8B 1246-5
+always \u4A8C 12-1236-2
+always \u4A8D 14-1236-2
+always \u4A8E 15-1246-3
+always \u4A8F 1345-345-5
+always \u4A90 135-16-5
+always \u4A91 124-25-2
+always \u4A92 1-34-5
+always \u4A93 145-346-2
+always \u4A94 135-34-4
+always \u4A95 13-1256-2
+always \u4A96 1234-126-5
+always \u4A97 15-23456-2
+always \u4A98 1246-4
+always \u4A99 1234-126-5
+always \u4A9A 1235-2346-5
+always \u4A9B 12345-1236-2
+always \u4A9C 12-1236-5
+always \u4A9D 1235-34-5
+always \u4A9E 125-345-2
+always \u4AA0 13-16-3
+always \u4AA1 13-16-3
+always \u4AA2 13-16-3
+always \u4AA3 13-16-3
+always \u4AA4 12345-1236-2
+always \u4AA5 15-346-5
+always \u4AA6 1235-12346-2
+always \u4AA7 12-156-2
+always \u4AA8 135-146-2
+always \u4AA9 1456-2
+always \u4AAB 13-13456-3
+always \u4AAC 135-126-2
+always \u4AAD 1245-12456-4
+always \u4AAE 12-12356-4
+always \u4AAF 13456-3
+always \u4AB0 16-3
+always \u4AB1 13-2456-4
+always \u4AB2 34-5
+always \u4AB3 256-4
+always \u4AB4 1-136-4
+always \u4AB5 23456-3
+always \u4AB6 13-1256-3
+always \u4AB7 1235-12356-5
+always \u4AB8 134-1456-2
+always \u4AB9 1234-356-2
+always \u4ABA 13-2346-2
+always \u4ABB 135-2345-5
+always \u4ABC 1-25-2
+always \u4ABD 1235-146-5
+always \u4ABE 1-136-4
+always \u4ABF 15-1456-5
+always \u4AC0 13-136-4
+always \u4AC1 135-16-5
+always \u4AC2 145-25-4
+always \u4AC3 12-123456-2
+always \u4AC4 12-35-5
+always \u4AC5 15-1236-5
+always \u4AC6 12-1356-2
+always \u4AC7 1245-1236-2
+always \u4AC8 125-136-5
+always \u4AC9 134-146-5
+always \u4ACA 1234-356-2
+always \u4ACB 124-1246-2
+always \u4ACC 1234-16-4
+always \u4ACD 12345-34-4
+always \u4ACE 1-25-2
+always \u4ACF 245-16-3
+always \u4AD0 14-1456-2
+always \u4AD1 245-16-3
+always \u4AD2 134-136-2
+always \u4AD3 34-3
+always \u4AD4 245-16-5
+always \u4AD5 145-346-2
+always \u4AD6 24-136-5
+always \u4AD7 15-23456-2
+always \u4AD8 1235-2346-2
+always \u4AD9 15-1346-4
+always \u4ADA 13-35-3
+always \u4ADB 1235-12356-2
+always \u4ADC 146-3
+always \u4ADD 12345-34-5
+always \u4ADE 245-246-3
+always \u4ADF 256-4
+always \u4AE0 1234-16-3
+always \u4AE1 2345-2
+always \u4AE2 15-156-3
+always \u4AE3 15-16-2
+always \u4AE4 134-13456-2
+always \u4AE5 123-1246-4
+always \u4AE6 13-2346-2
+always \u4AE7 13-146-4
+always \u4AE8 146-2
+always \u4AE9 15-1236-4
+always \u4AEA 24-456-4
+always \u4AEB 14-12356-2
+always \u4AEC 1-136-4
+always \u4AED 1235-1246-5
+always \u4AEE 12-1236-2
+always \u4AEF 16-3
+always \u4AF0 14-1456-5
+always \u4AF1 1245-34-2
+always \u4AF2 1235-1236-5
+always \u4AF3 145-34-2
+always \u4AF4 13-1456-5
+always \u4AF5 134-2345-2
+always \u4AF6 12345-1236-2
+always \u4AF7 2346-5
+always \u4AF8 1345-146-2
+always \u4AF9 1235-12346-2
+always \u4AFA 1235-12346-2
+always \u4AFB 1256-5
+always \u4AFC 15-236-5
+always \u4AFD 1234-146-3
+always \u4AFE 135-16-5
+always \u4AFF 12-146-3
+always \u4B00 234-4
+always \u4B01 16-2
+always \u4B02 15-236-5
+always \u4B03 15-345-5
+always \u4B04 15-1256-3
+always \u4B05 14-16-5
+always \u4B06 14-16-5
+always \u4B07 45-5
+always \u4B08 145-1246-5
+always \u4B09 1235-25-5
+always \u4B0A 24-345-5
+always \u4B0B 14-1356-2
+always \u4B0C 1234-12356-3
+always \u4B0D 1235-34-3
+always \u4B0E 13-25-2
+always \u4B0F 12345-12356-4
+always \u4B10 1245-1246-2
+always \u4B11 1246-5
+always \u4B12 15-12356-3
+always \u4B13 1236-5
+always \u4B14 1256-2
+always \u4B15 245-46-4
+always \u4B16 1235-1356-2
+always \u4B17 46-2
+always \u4B18 15-246-3
+always \u4B19 246-2
+always \u4B1A 12345-1236-2
+always \u4B1B 135-16-5
+always \u4B1C 12-156-3
+always \u4B1D 1235-1356-2
+always \u4B1E 124-146-2
+always \u4B1F 14-234-2
+always \u4B20 12345-356-3
+always \u4B21 1-34-5
+always \u4B22 124-146-3
+always \u4B23 245-16-5
+always \u4B24 1-1236-3
+always \u4B25 16-5
+always \u4B26 24-2346-5
+always \u4B27 45-2
+always \u4B28 13-234-5
+always \u4B29 1-2456-3
+always \u4B2A 135-126-2
+always \u4B2B 124-16-2
+always \u4B2C 46-4
+always \u4B2D 124-146-3
+always \u4B2E 16-2
+always \u4B2F 1345-2345-2
+always \u4B30 24-146-5
+always \u4B31 135-136-5
+always \u4B32 13-12356-3
+always \u4B33 135-1236-4
+always \u4B34 134-126-5
+always \u4B35 2456-5
+always \u4B36 136-5
+always \u4B37 24-2346-4
+always \u4B38 245-1236-3
+always \u4B39 1-156-5
+always \u4B3A 46-5
+always \u4B3B 13-2345-5
+always \u4B3C 45-5
+always \u4B3D 24-1246-5
+always \u4B3E 124-16-2
+always \u4B3F 1246-4
+always \u4B40 15-256-5
+always \u4B41 1-156-5
+always \u4B42 16-5
+always \u4B43 1245-136-4
+always \u4B44 24-156-5
+always \u4B45 1235-34-2
+always \u4B46 1345-2346-5
+always \u4B47 16-5
+always \u4B48 13-2345-3
+always \u4B49 15-1246-4
+always \u4B4A 13456-4
+always \u4B4B 135-146-4
+always \u4B4C 1235-34-2
+always \u4B4D 1235-34-2
+always \u4B4E 346-5
+always \u4B50 46-5
+always \u4B51 14-2345-4
+always \u4B52 15-16-2
+always \u4B53 136-5
+always \u4B54 145-1246-3
+always \u4B55 13-2345-4
+always \u4B56 1-34-5
+always \u4B57 13456-4
+always \u4B58 13456-4
+always \u4B59 13-1456-4
+always \u4B5A 12-456-2
+always \u4B5B 13-1236-4
+always \u4B5D 123-2356-5
+always \u4B5E 16-5
+always \u4B5F 346-5
+always \u4B60 13-2345-4
+always \u4B61 123456-5
+always \u4B62 1345-13456-2
+always \u4B63 245-156-2
+always \u4B64 245-2345-4
+always \u4B65 46-5
+always \u4B66 135-126-2
+always \u4B67 134-16-4
+always \u4B68 24-1246-5
+always \u4B69 134-16-4
+always \u4B6A 14-46-2
+always \u4B6B 13-16-3
+always \u4B6C 13-16-3
+always \u4B6D 24-12356-4
+always \u4B6E 12345-34-2
+always \u4B6F 135-126-2
+always \u4B70 135-1356-4
+always \u4B71 135-346-2
+always \u4B72 16-4
+always \u4B73 1246-5
+always \u4B74 1235-12456-2
+always \u4B75 12345-1236-2
+always \u4B76 245-16-2
+always \u4B77 134-146-2
+always \u4B78 12345-34-5
+always \u4B79 1346-4
+always \u4B7A 1346-4
+always \u4B7B 12345-136-3
+always \u4B7C 245-16-2
+always \u4B7D 245-256-2
+always \u4B7E 124-25-2
+always \u4B7F 16-5
+always \u4B80 135-126-2
+always \u4B81 1234-2345-2
+always \u4B82 135-345-2
+always \u4B83 245-1256-3
+always \u4B84 15-45-2
+always \u4B85 15-1256-5
+always \u4B86 1234-16-3
+always \u4B87 1256-5
+always \u4B88 12-156-2
+always \u4B89 14-1256-5
+always \u4B8A 16-2
+always \u4B8B 14-346-5
+always \u4B8C 15-234-3
+always \u4B8D 1345-246-4
+always \u4B8E 15-16-5
+always \u4B8F 34-2
+always \u4B90 13-235-3
+always \u4B91 14-356-5
+always \u4B92 1234-34-3
+always \u4B93 12-146-5
+always \u4B94 125-1246-3
+always \u4B95 12-25-5
+always \u4B96 12-1346-3
+always \u4B97 2345-5
+always \u4B98 156-2
+always \u4B99 1256-5
+always \u4B9A 14-13456-2
+always \u4B9B 12345-34-5
+always \u4B9C 24-345-5
+always \u4B9D 1235-123456-2
+always \u4B9E 12-123456-4
+always \u4B9F 15-12356-3
+always \u4BA0 135-16-3
+always \u4BA1 135-16-5
+always \u4BA2 24-345-5
+always \u4BA3 15-1346-3
+always \u4BA4 1235-2346-5
+always \u4BA5 14-16-5
+always \u4BA6 13-246-3
+always \u4BA7 1235-1236-5
+always \u4BA8 125-2456-4
+always \u4BA9 13-34-2
+always \u4BAA 12-1356-2
+always \u4BAB 14-12356-2
+always \u4BAC 134-126-5
+always \u4BAD 134-16-5
+always \u4BAE 134-126-5
+always \u4BAF 146-5
+always \u4BB0 1-2346-2
+always \u4BB1 1-34-2
+always \u4BB2 1235-456-2
+always \u4BB3 2345-2
+always \u4BB4 145-1356-5
+always \u4BB5 124-12346-2
+always \u4BB6 135-246-3
+always \u4BB7 145-34-2
+always \u4BB8 25-5
+always \u4BB9 13-1246-5
+always \u4BBA 13-16-5
+always \u4BBB 145-146-4
+always \u4BBC 14-1456-2
+always \u4BBD 135-246-3
+always \u4BBE 1234-1356-2
+always \u4BBF 13-2345-4
+always \u4BC0 1345-346-5
+always \u4BC1 14-25-2
+always \u4BC2 13-16-2
+always \u4BC3 1236-3
+always \u4BC4 13-35-3
+always \u4BC5 1345-346-5
+always \u4BC6 16-5
+always \u4BC7 123-34-3
+always \u4BC8 12456-2
+always \u4BC9 35-5
+always \u4BCA 123-2346-3
+always \u4BCB 135-126-2
+always \u4BCC 123-146-3
+always \u4BCD 14-13456-2
+always \u4BCE 13-1236-5
+always \u4BCF 13-35-3
+always \u4BD0 1235-2456-2
+always \u4BD1 123-456-3
+always \u4BD2 1235-1356-2
+always \u4BD3 123-1246-2
+always \u4BD4 125-2346-2
+always \u4BD5 124-13456-2
+always \u4BD6 14-1346-2
+always \u4BD7 135-16-5
+always \u4BD8 1235-12456-5
+always \u4BD9 1234-126-5
+always \u4BDA 246-4
+always \u4BDB 12456-5
+always \u4BDC 124-16-5
+always \u4BDD 15-1246-4
+always \u4BDE 123-35-3
+always \u4BDF 145-1246-5
+always \u4BE0 146-4
+always \u4BE1 13-2345-5
+always \u4BE2 134-126-2
+always \u4BE3 123-1246-5
+always \u4BE4 123-2356-5
+always \u4BE5 1236-5
+always \u4BE6 134-345-5
+always \u4BE7 245-13456-4
+always \u4BE8 1235-25-5
+always \u4BEA 123-146-4
+always \u4BEB 1235-146-5
+always \u4BEC 145-25-4
+always \u4BED 15-2345-3
+always \u4BEE 1345-2456-2
+always \u4BEF 15-25-3
+always \u4BF0 13-346-5
+always \u4BF1 12345-34-5
+always \u4BF2 1234-345-3
+always \u4BF3 15-12346-3
+always \u4BF4 12-1346-2
+always \u4BF5 1345-346-5
+always \u4BF6 134-1236-2
+always \u4BF7 15-12346-3
+always \u4BF8 245-156-5
+always \u4BF9 15-2345-3
+always \u4BFA 236-5
+always \u4BFC 145-16-2
+always \u4BFD 1234-12356-2
+always \u4BFE 124-246-2
+always \u4BFF 125-34-2
+always \u4C00 25-4
+always \u4C01 12345-356-5
+always \u4C02 245-2456-5
+always \u4C03 1234-1356-2
+always \u4C04 15-2456-3
+always \u4C05 15-1256-3
+always \u4C06 1245-12356-2
+always \u4C07 245-16-2
+always \u4C08 245-25-4
+always \u4C09 1234-1236-2
+always \u4C0A 135-126-2
+always \u4C0B 134-1236-2
+always \u4C0C 125-12346-4
+always \u4C0D 15-234-3
+always \u4C0E 123-1246-5
+always \u4C0F 13-346-2
+always \u4C10 14-1236-2
+always \u4C11 15-1256-3
+always \u4C12 134-1356-2
+always \u4C13 134-2345-2
+always \u4C14 1234-1236-2
+always \u4C15 14-34-2
+always \u4C16 125-1236-4
+always \u4C17 13-234-3
+always \u4C18 14-234-2
+always \u4C19 16-4
+always \u4C1A 123456-2
+always \u4C1B 13-2346-2
+always \u4C1C 13-2346-2
+always \u4C1D 125-1356-5
+always \u4C1E 1-34-4
+always \u4C1F 1235-123456-2
+always \u4C20 24-136-2
+always \u4C21 12-156-5
+always \u4C22 15-13456-5
+always \u4C23 456-4
+always \u4C24 145-12346-3
+always \u4C25 1256-5
+always \u4C26 1234-16-4
+always \u4C27 1235-34-3
+always \u4C28 134-356-5
+always \u4C29 145-34-3
+always \u4C2A 134-356-5
+always \u4C2B 12-146-2
+always \u4C2C 13-1256-2
+always \u4C2D 1245-34-2
+always \u4C2F 16-5
+always \u4C30 1245-34-2
+always \u4C31 14-13456-2
+always \u4C32 23456-5
+always \u4C34 245-16-5
+always \u4C35 125-156-3
+always \u4C37 135-1346-5
+always \u4C38 13-12346-3
+always \u4C39 125-2346-2
+always \u4C3A 13-346-5
+always \u4C3B 1256-2
+always \u4C3C 1456-2
+always \u4C3D 135-356-5
+always \u4C3E 135-345-5
+always \u4C3F 124-25-2
+always \u4C40 46-3
+always \u4C41 245-246-2
+always \u4C42 234-4
+always \u4C43 1-156-5
+always \u4C44 13-346-5
+always \u4C45 134-126-5
+always \u4C46 24-1356-2
+always \u4C47 24-1236-5
+always \u4C48 245-16-2
+always \u4C49 24-1236-5
+always \u4C4A 134-16-4
+always \u4C4B 13-12346-4
+always \u4C4C 16-2
+always \u4C4D 13-1356-3
+always \u4C4E 13-1356-3
+always \u4C4F 124-12356-4
+always \u4C50 12345-34-2
+always \u4C51 15-236-2
+always \u4C52 346-3
+always \u4C53 124-13456-2
+always \u4C54 124-246-2
+always \u4C55 134-12356-2
+always \u4C56 14-234-2
+always \u4C57 245-1236-3
+always \u4C58 14-16-2
+always \u4C59 24-34-2
+always \u4C5A 14-34-5
+always \u4C5B 16-5
+always \u4C5C 245-25-5
+always \u4C5D 135-2456-2
+always \u4C5E 1345-2456-5
+always \u4C5F 13-1256-5
+always \u4C60 12-1236-4
+always \u4C61 13-1256-2
+always \u4C62 1-1356-3
+always \u4C63 125-34-2
+always \u4C64 15-2345-5
+always \u4C65 1-156-5
+always \u4C66 1456-2
+always \u4C67 24-1236-5
+always \u4C68 1-156-5
+always \u4C69 456-4
+always \u4C6B 14-345-5
+always \u4C6C 15-1256-3
+always \u4C6D 13-1356-3
+always \u4C6E 2346-2
+always \u4C6F 134-126-2
+always \u4C70 1-12346-5
+always \u4C71 145-16-5
+always \u4C72 45-2
+always \u4C73 1-1236-3
+always \u4C74 13-1356-5
+always \u4C75 12346-3
+always \u4C76 14-1346-2
+always \u4C77 1256-2
+always \u4C78 245-234-3
+always \u4C79 1-345-4
+always \u4C7A 1235-2456-2
+always \u4C7B 1235-35-2
+always \u4C7C 1-1236-4
+always \u4C7D 12-1346-3
+always \u4C7E 14-12356-2
+always \u4C7F 12-1236-5
+always \u4C80 1-156-5
+always \u4C81 1246-5
+always \u4C82 15-45-2
+always \u4C83 125-146-4
+always \u4C84 134-2345-4
+always \u4C85 13-1246-3
+always \u4C86 15-34-3
+always \u4C88 134-345-2
+always \u4C89 15-156-3
+always \u4C8A 124-25-4
+always \u4C8B 245-136-2
+always \u4C8C 123-12456-4
+always \u4C8D 124-1356-2
+always \u4C8E 1345-356-4
+always \u4C8F 14-146-2
+always \u4C90 14-34-4
+always \u4C91 16-2
+always \u4C92 15-346-5
+always \u4C93 2345-4
+always \u4C94 13-13456-3
+always \u4C95 1234-34-3
+always \u4C96 12-12356-2
+always \u4C97 15-2345-2
+always \u4C98 13-12456-4
+always \u4C99 13-346-2
+always \u4C9A 14-2456-5
+always \u4C9B 134-1356-2
+always \u4C9C 346-5
+always \u4C9D 12-1346-3
+always \u4C9E 14-16-5
+always \u4C9F 1456-5
+always \u4CA0 12-123456-3
+always \u4CA1 245-234-3
+always \u4CA2 124-1356-2
+always \u4CA3 1256-2
+always \u4CA5 13-234-3
+always \u4CA6 145-345-5
+always \u4CA7 145-34-5
+always \u4CA8 1235-12346-2
+always \u4CA9 24-156-3
+always \u4CAA 15-16-5
+always \u4CAB 13-234-3
+always \u4CAC 245-16-2
+always \u4CAD 12-156-3
+always \u4CAE 45-2
+always \u4CAF 13-16-2
+always \u4CB0 256-5
+always \u4CB1 12345-1346-4
+always \u4CB2 13-12346-3
+always \u4CB3 1235-1346-2
+always \u4CB4 1-136-5
+always \u4CB5 245-236-5
+always \u4CB7 15-246-3
+always \u4CB8 13-346-5
+always \u4CB9 1234-356-2
+always \u4CBA 13-1236-5
+always \u4CBB 15-45-2
+always \u4CBC 24-1356-3
+always \u4CBD 145-146-4
+always \u4CBE 245-246-4
+always \u4CBF 245-156-2
+always \u4CC0 16-5
+always \u4CC1 135-345-2
+always \u4CC2 124-246-2
+always \u4CC3 12456-4
+always \u4CC4 245-156-2
+always \u4CC5 1-156-5
+always \u4CC6 135-126-2
+always \u4CC7 34-4
+always \u4CC8 135-146-4
+always \u4CC9 145-12346-3
+always \u4CCA 135-345-2
+always \u4CCB 124-12346-2
+always \u4CCC 13-45-3
+always \u4CCD 13-12346-3
+always \u4CCE 13-234-5
+always \u4CCF 13-1246-5
+always \u4CD0 245-156-5
+always \u4CD1 234-4
+always \u4CD2 45-3
+always \u4CD3 14-146-4
+always \u4CD4 13-234-5
+always \u4CD5 12345-34-2
+always \u4CD6 1345-346-5
+always \u4CD7 2346-2
+always \u4CD8 2346-2
+always \u4CD9 15-13456-4
+always \u4CDA 123-1236-5
+always \u4CDB 2345-5
+always \u4CDC 124-34-2
+always \u4CDD 1234-12356-4
+always \u4CDE 135-1356-4
+always \u4CDF 134-13456-2
+always \u4CE0 12-1246-2
+always \u4CE1 1-1246-3
+always \u4CE2 245-16-2
+always \u4CE3 45-2
+always \u4CE4 135-346-3
+always \u4CE5 14-13456-2
+always \u4CE6 15-45-3
+always \u4CE7 1235-12356-2
+always \u4CE8 1235-456-2
+always \u4CE9 246-3
+always \u4CEA 1-12456-3
+always \u4CEB 123-1246-2
+always \u4CEC 16-5
+always \u4CED 13-16-2
+always \u4CEE 134-126-5
+always \u4CEF 12-12346-4
+always \u4CF0 135-146-4
+always \u4CF1 34-5
+always \u4CF2 1-136-5
+always \u4CF3 15-1256-5
+always \u4CF4 124-345-5
+always \u4CF5 12-156-5
+always \u4CF6 13-16-3
+always \u4CF7 245-12346-2
+always \u4CF8 134-345-2
+always \u4CF9 123-12356-5
+always \u4CFA 1236-3
+always \u4CFB 245-1236-2
+always \u4CFC 12356-3
+always \u4CFD 1235-2346-5
+always \u4CFE 145-1356-3
+always \u4CFF 1245-1236-2
+always \u4D00 124-12346-2
+always \u4D01 1256-5
+always \u4D02 15-46-5
+always \u4D03 1345-146-2
+always \u4D04 24-123456-5
+always \u4D05 12345-136-2
+always \u4D06 1234-34-3
+always \u4D07 14-13456-2
+always \u4D08 146-4
+always \u4D09 15-45-2
+always \u4D0A 16-2
+always \u4D0B 15-45-2
+always \u4D0C 134-1356-2
+always \u4D0D 13456-3
+always \u4D0E 14-356-4
+always \u4D0F 2345-5
+always \u4D10 135-146-4
+always \u4D11 145-346-2
+always \u4D12 14-13456-2
+always \u4D13 24-156-3
+always \u4D14 13-246-3
+always \u4D15 14-346-5
+always \u4D16 13-13456-3
+always \u4D17 13-1256-2
+always \u4D18 124-16-2
+always \u4D19 1234-16-5
+always \u4D1A 13-1346-4
+always \u4D1B 15-246-3
+always \u4D1C 1235-2356-2
+always \u4D1D 12-2356-5
+always \u4D1E 145-16-2
+always \u4D1F 1235-12456-2
+always \u4D20 146-4
+always \u4D21 14-16-5
+always \u4D22 134-16-2
+always \u4D23 1235-34-3
+always \u4D24 24-1356-3
+always \u4D25 13-23456-3
+always \u4D26 1456-2
+always \u4D27 1246-3
+always \u4D29 1234-246-2
+always \u4D2A 14-34-5
+always \u4D2B 14-13456-2
+always \u4D2C 16-5
+always \u4D2D 245-2456-2
+always \u4D2E 24-1236-5
+always \u4D2F 1235-34-5
+always \u4D30 24-34-2
+always \u4D31 124-25-3
+always \u4D32 134-126-5
+always \u4D33 1235-35-2
+always \u4D34 124-346-5
+always \u4D35 135-13456-4
+always \u4D36 1234-1356-2
+always \u4D37 1235-123456-2
+always \u4D38 12345-34-3
+always \u4D39 13-25-4
+always \u4D3A 135-34-5
+always \u4D3B 14-16-2
+always \u4D3C 12-1236-5
+always \u4D3D 1234-16-2
+always \u4D3E 245-25-2
+always \u4D3F 134-1356-2
+always \u4D40 15-25-4
+always \u4D41 245-46-5
+always \u4D42 1-156-2
+always \u4D43 123-456-5
+always \u4D44 12345-1356-3
+always \u4D45 146-2
+always \u4D46 134-1356-2
+always \u4D47 15-2345-5
+always \u4D48 123-34-5
+always \u4D49 124-12356-2
+always \u4D4A 124-123456-3
+always \u4D4B 1235-1246-4
+always \u4D4C 15-2345-3
+always \u4D4D 124-123456-3
+always \u4D4E 124-12456-3
+always \u4D4F 14-146-4
+always \u4D50 12-1236-4
+always \u4D51 1345-16-5
+always \u4D52 1345-16-5
+always \u4D53 14-16-2
+always \u4D54 145-12346-4
+always \u4D55 13-1256-5
+always \u4D56 245-2345-5
+always \u4D57 135-126-2
+always \u4D58 24-2456-5
+always \u4D59 1-345-4
+always \u4D5A 124-146-4
+always \u4D5B 245-2345-5
+always \u4D5C 1345-12346-4
+always \u4D5D 23456-5
+always \u4D5E 245-13456-2
+always \u4D5F 13-1236-4
+always \u4D60 145-16-2
+always \u4D61 13-2345-4
+always \u4D62 134-356-5
+always \u4D63 145-345-2
+always \u4D64 13-2345-4
+always \u4D65 1256-5
+always \u4D66 34-3
+always \u4D67 125-2456-5
+always \u4D68 134-1346-2
+always \u4D69 14-16-2
+always \u4D6A 13-123456-4
+always \u4D6B 15-256-3
+always \u4D6C 124-345-5
+always \u4D6D 1-2346-5
+always \u4D6E 46-5
+always \u4D6F 124-12456-4
+always \u4D70 24-1346-3
+always \u4D71 15-16-5
+always \u4D72 245-246-3
+always \u4D73 1246-5
+always \u4D74 256-5
+always \u4D75 12-35-3
+always \u4D76 245-1256-2
+always \u4D77 35-3
+always \u4D78 245-34-5
+always \u4D79 1-156-3
+always \u4D7A 124-13456-4
+always \u4D7B 1235-1246-5
+always \u4D7C 24-1346-3
+always \u4D7D 245-345-5
+always \u4D7E 12345-34-2
+always \u4D7F 124-346-5
+always \u4D80 124-345-5
+always \u4D81 124-345-5
+always \u4D82 1-25-2
+always \u4D83 1235-1236-2
+always \u4D84 1234-13456-2
+always \u4D85 1235-2346-5
+always \u4D86 1-1246-3
+always \u4D87 1-12356-5
+always \u4D88 135-126-2
+always \u4D89 14-234-2
+always \u4D8A 1345-1256-5
+always \u4D8B 15-16-3
+always \u4D8C 1234-146-5
+always \u4D8D 124-16-5
+always \u4D8E 1235-2346-2
+always \u4D8F 124-16-5
+always \u4D90 1235-1246-5
+always \u4D91 124-16-5
+always \u4D92 245-16-2
+always \u4D93 13-16-5
+always \u4D94 12-156-2
+always \u4D95 135-345-3
+always \u4D96 13-1456-5
+always \u4D97 123-2346-5
+always \u4D98 14-16-5
+always \u4D99 13-1256-5
+always \u4D9A 245-1256-4
+always \u4D9B 14-345-5
+always \u4D9C 13-34-4
+always \u4D9D 15-346-2
+always \u4D9E 245-16-2
+always \u4D9F 15-2345-5
+always \u4DA0 15-2345-2
+always \u4DA1 24-156-2
+always \u4DA2 15-2345-2
+always \u4DA3 2456-2
+always \u4DA4 1235-35-2
+always \u4DA5 1-345-3
+always \u4DA6 125-2346-2
+always \u4DA7 246-4
+always \u4DA8 1-1236-3
+always \u4DA9 13-16-5
+always \u4DAA 12-345-2
+always \u4DAB 2345-5
+always \u4DAC 13-2345-3
+always \u4DAE 2345-4
+always \u4DAF 123-1246-3
+always \u4DB0 13-246-3
+always \u4DB1 124-12346-2
+always \u4DB2 1245-1236-2
+always \u4DB3 236-5
+always \u4DB4 12-1246-3
+always \u4DB5 12-156-2
+always \u4E00 16-3
+always \u4E01 145-13456-3
+always \u4E02 123-146-4
+always \u4E03 245-16-3
+always \u4E04 24-1346-5
+always \u4E05 15-23456-5
+always \u4E06 1235-1236-4
+always \u4E07 12456-5
+always \u4E08 1-1346-5
+always \u4E09 15-1236-3
+always \u4E0A 24-1346-5
+always \u4E0B 15-23456-5
+always \u4E0C 13-16-3
+always \u4E0D 135-34-5
+always \u4E0E 1256-4
+always \u4E0F 134-2345-4
+always \u4E10 13-2456-5
+always \u4E11 12-12356-4
+always \u4E12 12-12356-4
+always \u4E13 1-12456-3
+always \u4E14 245-346-4
+always \u4E15 1234-16-3
+always \u4E16 24-156-5
+always \u4E17 24-156-5
+always \u4E18 245-234-3
+always \u4E19 135-13456-4
+always \u4E1A 346-5
+always \u4E1B 245-12346-2
+always \u4E1C 145-12346-3
+always \u4E1D 15-156-3
+always \u4E1E 12-1356-2
+always \u4E1F 145-234-3
+always \u4E20 245-234-3
+always \u4E21 14-46-4
+always \u4E22 145-234-3
+always \u4E23 234-4
+always \u4E24 14-46-4
+always \u4E25 2345-2
+always \u4E26 135-13456-5
+always \u4E27 15-1346-5
+always \u4E28 13-123456-4
+always \u4E29 13-234-3
+always \u4E2A 13-2346-5
+always \u4E2B 23456-3
+always \u4E2C 1-456-5
+always \u4E2D 1-12346-3
+always \u4E2E 13-16-4
+always \u4E2F 13-346-5
+always \u4E30 12345-1356-3
+always \u4E31 13-12456-5
+always \u4E32 12-12456-5
+always \u4E33 12-1236-4
+always \u4E34 14-1456-2
+always \u4E35 1-25-2
+always \u4E36 1-34-4
+always \u4E37 135-345-3
+always \u4E38 12456-2
+always \u4E39 145-1236-3
+always \u4E3A 1246-2
+always \u4E3B 1-34-4
+always \u4E3C 13-13456-4
+always \u4E3D 14-16-5
+always \u4E3E 13-1256-4
+always \u4E3F 1234-346-4
+always \u4E40 12345-34-2
+always \u4E41 16-2
+always \u4E42 16-5
+always \u4E43 1345-2456-4
+always \u4E44 34-4
+always \u4E45 13-234-4
+always \u4E46 13-234-4
+always \u4E47 124-25-3
+always \u4E48 246-3
+always \u4E49 16-5
+always \u4E4A 16-3
+always \u4E4B 1-156-3
+always \u4E4C 34-3
+always \u4E4D 1-345-5
+always \u4E4E 1235-34-3
+always \u4E4F 12345-345-2
+always \u4E50 14-2346-5
+always \u4E51 1-12346-5
+always \u4E52 1234-13456-3
+always \u4E53 1234-1346-3
+always \u4E54 245-246-2
+always \u4E55 1235-34-4
+always \u4E56 13-2356-3
+always \u4E57 12-1356-2
+always \u4E58 12-1356-2
+always \u4E59 16-4
+always \u4E5A 16-4
+always \u4E5B 12456-3
+always \u4E5C 134-346-3
+always \u4E5D 13-234-4
+always \u4E5E 245-16-4
+always \u4E5F 346-4
+always \u4E60 15-16-2
+always \u4E61 15-46-3
+always \u4E62 13-2456-5
+always \u4E63 13-234-4
+always \u4E64 15-23456-5
+always \u4E65 1235-2346-2
+always \u4E66 24-34-3
+always \u4E67 145-12356-4
+always \u4E68 24-156-4
+always \u4E69 13-16-3
+always \u4E6A 1345-1346-2
+always \u4E6B 13-23456-3
+always \u4E6C 13-1256-5
+always \u4E6D 24-156-2
+always \u4E6F 1235-34-3
+always \u4E70 134-2456-4
+always \u4E71 14-12456-5
+always \u4E72 125-156-3
+always \u4E73 1245-34-4
+always \u4E74 15-236-2
+always \u4E75 2345-4
+always \u4E76 12345-34-4
+always \u4E77 24-345-3
+always \u4E78 1345-345-4
+always \u4E79 13-1236-3
+always \u4E7A 15-25-4
+always \u4E7B 156-5
+always \u4E7C 1-34-5
+always \u4E7D 245-2346-5
+always \u4E7E 13-1236-3
+always \u4E7F 12-156-5
+always \u4E80 13-1246-3
+always \u4E81 13-1236-3
+always \u4E82 14-12456-5
+always \u4E83 14-1456-2
+always \u4E84 16-5
+always \u4E85 13-236-2
+always \u4E86 14-2346-1
+always \u4E87 134-345-3
+always \u4E88 1256-4
+always \u4E89 1-1356-3
+always \u4E8A 24-156-5
+always \u4E8B 24-156-5
+always \u4E8C 156-5
+always \u4E8D 12-34-5
+always \u4E8E 1256-2
+always \u4E8F 123-1246-3
+always \u4E90 1256-2
+always \u4E91 256-2
+always \u4E92 1235-34-5
+always \u4E93 245-16-2
+always \u4E94 34-4
+always \u4E95 13-13456-4
+always \u4E96 15-156-5
+always \u4E97 15-1246-5
+always \u4E98 13-136-5
+always \u4E99 13-136-5
+always \u4E9A 23456-4
+always \u4E9B 15-346-3
+always \u4E9C 23456-4
+always \u4E9D 245-16-2
+always \u4E9E 23456-4
+always \u4E9F 13-16-2
+always \u4EA0 124-12356-2
+always \u4EA1 456-2
+always \u4EA2 123-1346-5
+always \u4EA3 124-345-5
+always \u4EA4 13-246-3
+always \u4EA5 1235-2456-5
+always \u4EA6 16-5
+always \u4EA7 12-1236-4
+always \u4EA8 1235-1356-3
+always \u4EA9 134-34-4
+always \u4EAA 346-5
+always \u4EAB 15-46-4
+always \u4EAC 13-13456-3
+always \u4EAD 124-13456-2
+always \u4EAE 14-46-5
+always \u4EAF 15-46-4
+always \u4EB0 13-13456-3
+always \u4EB1 346-5
+always \u4EB2 245-1456-3
+always \u4EB3 135-126-1
+always \u4EB4 234-5
+always \u4EB5 15-346-5
+always \u4EB6 145-1236-4
+always \u4EB7 14-2345-2
+always \u4EB8 145-25-4
+always \u4EB9 1246-4
+always \u4EBA 1245-136-2
+always \u4EBB 1245-136-2
+always \u4EBC 13-16-2
+always \u4EBD 13-16-2
+always \u4EBE 456-2
+always \u4EBF 16-5
+always \u4EC0 24-156-2
+always \u4EC1 1245-136-2
+always \u4EC2 14-2346-5
+always \u4EC3 145-13456-3
+always \u4EC4 125-2346-5
+always \u4EC5 13-1456-4
+always \u4EC6 1234-34-3
+always \u4EC7 12-12356-2
+always \u4EC8 135-345-3
+always \u4EC9 1-1346-4
+always \u4ECA 13-1456-3
+always \u4ECB 13-346-5
+always \u4ECC 135-13456-3
+always \u4ECD 1245-1356-2
+always \u4ECE 245-12346-2
+always \u4ECF 12345-126-2
+always \u4ED0 15-1236-4
+always \u4ED1 14-123456-2
+always \u4ED2 135-1456-3
+always \u4ED3 245-1346-3
+always \u4ED4 125-156-4
+always \u4ED5 24-156-5
+always \u4ED6 1247-345-3
+always \u4ED7 1-1346-5
+always \u4ED8 12345-34-5
+always \u4ED9 15-2345-3
+always \u4EDA 15-2345-3
+always \u4EDB 124-25-3
+always \u4EDC 1235-12346-2
+always \u4EDD 124-12346-2
+always \u4EDE 1245-136-5
+always \u4EDF 245-2345-3
+always \u4EE0 13-1236-4
+always \u4EE1 16-5
+always \u4EE2 145-16-2
+always \u4EE3 145-2456-5
+always \u4EE4 14-13456-5
+always \u4EE5 16-4
+always \u4EE6 12-146-5
+always \u4EE7 12-1346-2
+always \u4EE8 15-345-3
+always \u4EE9 24-1346-5
+always \u4EEA 16-2
+always \u4EEB 134-34-5
+always \u4EEC 134-136-1
+always \u4EED 1245-136-5
+always \u4EEE 13-23456-4
+always \u4EEF 12-146-5
+always \u4EF0 46-4
+always \u4EF1 245-2345-2
+always \u4EF2 1-12346-5
+always \u4EF3 1234-16-4
+always \u4EF4 12456-5
+always \u4EF5 34-4
+always \u4EF6 13-2345-5
+always \u4EF7 13-23456-5
+always \u4EF8 246-4
+always \u4EF9 12345-1356-3
+always \u4EFA 245-1346-3
+always \u4EFB 1245-136-5
+always \u4EFC 456-2
+always \u4EFD 12345-136-5
+always \u4EFE 145-16-3
+always \u4EFF 12345-1346-4
+always \u4F00 1-12346-3
+always \u4F01 245-16-5
+always \u4F02 1234-356-5
+always \u4F03 1256-2
+always \u4F04 145-246-5
+always \u4F05 124-123456-2
+always \u4F06 123456-5
+always \u4F07 16-5
+always \u4F08 15-1456-4
+always \u4F09 123-1346-5
+always \u4F0A 16-3
+always \u4F0B 13-16-2
+always \u4F0C 2456-5
+always \u4F0D 34-4
+always \u4F0E 13-16-5
+always \u4F0F 12345-34-2
+always \u4F10 12345-345-2
+always \u4F11 15-234-3
+always \u4F12 13-1456-5
+always \u4F13 135-356-3
+always \u4F14 12-136-2
+always \u4F15 12345-34-3
+always \u4F16 124-1346-4
+always \u4F17 1-12346-5
+always \u4F18 234-3
+always \u4F19 1235-25-4
+always \u4F1A 1235-1246-5
+always \u4F1B 1256-4
+always \u4F1C 245-1246-5
+always \u4F1D 256-2
+always \u4F1E 15-1236-4
+always \u4F1F 1246-4
+always \u4F20 12-12456-2
+always \u4F21 12-2346-3
+always \u4F22 23456-2
+always \u4F23 245-2345-5
+always \u4F24 24-1346-3
+always \u4F25 12-1346-3
+always \u4F26 14-123456-2
+always \u4F27 245-1346-3
+always \u4F28 15-256-5
+always \u4F29 15-1456-5
+always \u4F2A 1246-4
+always \u4F2B 1-34-5
+always \u4F2C 12-156-4
+always \u4F2D 15-45-2
+always \u4F2E 1345-34-2
+always \u4F2F 135-126-2
+always \u4F30 13-34-3
+always \u4F31 13457-16-4
+always \u4F32 1345-16-5
+always \u4F33 15-346-5
+always \u4F34 135-1236-5
+always \u4F35 15-1256-5
+always \u4F36 14-13456-2
+always \u4F37 1-12356-5
+always \u4F38 24-136-3
+always \u4F39 245-1256-3
+always \u4F3A 15-156-5
+always \u4F3B 135-1356-3
+always \u4F3C 15-156-5
+always \u4F3D 13-23456-3
+always \u4F3E 1234-16-3
+always \u4F3F 16-5
+always \u4F40 15-156-5
+always \u4F41 2456-4
+always \u4F42 1-1356-3
+always \u4F43 145-2345-5
+always \u4F44 1235-1236-2
+always \u4F45 134-2456-5
+always \u4F46 145-1236-5
+always \u4F47 1-34-5
+always \u4F48 135-34-5
+always \u4F49 245-1256-3
+always \u4F4A 135-16-4
+always \u4F4B 24-146-5
+always \u4F4C 245-156-4
+always \u4F4D 1246-5
+always \u4F4E 145-16-3
+always \u4F4F 1-34-5
+always \u4F50 125-25-4
+always \u4F51 234-5
+always \u4F52 46-4
+always \u4F53 124-16-4
+always \u4F54 1-1236-5
+always \u4F55 1235-2346-2
+always \u4F56 135-16-5
+always \u4F57 124-25-2
+always \u4F58 24-2346-2
+always \u4F59 1256-2
+always \u4F5A 16-5
+always \u4F5B 12345-126-2
+always \u4F5C 125-25-5
+always \u4F5D 123-12356-5
+always \u4F5E 1345-13456-5
+always \u4F5F 124-12346-2
+always \u4F60 13457-16-4
+always \u4F61 15-45-3
+always \u4F62 245-1256-2
+always \u4F63 235-5
+always \u4F64 35-4
+always \u4F65 245-2345-3
+always \u4F66 24-156-2
+always \u4F67 123-345-4
+always \u4F68 135-146-3
+always \u4F69 1234-356-5
+always \u4F6A 1235-1246-2
+always \u4F6B 1235-2346-5
+always \u4F6C 14-146-4
+always \u4F6D 15-46-2
+always \u4F6E 13-2346-2
+always \u4F6F 46-2
+always \u4F70 135-2456-4
+always \u4F71 12345-345-4
+always \u4F72 134-13456-2
+always \u4F73 13-23456-3
+always \u4F74 156-5
+always \u4F75 135-13456-5
+always \u4F76 13-16-2
+always \u4F77 1235-136-4
+always \u4F78 1235-25-2
+always \u4F79 13-1246-4
+always \u4F7A 245-45-2
+always \u4F7B 124-246-3
+always \u4F7C 13-246-4
+always \u4F7D 245-156-5
+always \u4F7E 16-5
+always \u4F7F 24-156-4
+always \u4F80 15-13456-2
+always \u4F81 24-136-3
+always \u4F82 124-25-3
+always \u4F83 123-1236-4
+always \u4F84 1-156-2
+always \u4F85 13-2456-3
+always \u4F86 14-2456-2
+always \u4F87 16-2
+always \u4F88 12-156-4
+always \u4F89 123-35-4
+always \u4F8A 13-456-3
+always \u4F8B 14-16-5
+always \u4F8C 1456-3
+always \u4F8D 24-156-5
+always \u4F8E 134-16-4
+always \u4F8F 1-34-3
+always \u4F90 15-1256-5
+always \u4F91 234-5
+always \u4F92 1236-3
+always \u4F93 14-34-5
+always \u4F94 134-12356-2
+always \u4F95 156-2
+always \u4F96 14-123456-2
+always \u4F97 145-12346-5
+always \u4F98 12-345-5
+always \u4F99 12-156-5
+always \u4F9A 15-256-5
+always \u4F9B 13-12346-3
+always \u4F9C 1-12356-3
+always \u4F9D 16-3
+always \u4F9E 1245-34-4
+always \u4F9F 13-2345-5
+always \u4FA0 15-23456-2
+always \u4FA1 13-23456-5
+always \u4FA2 125-2456-5
+always \u4FA3 14-1256-4
+always \u4FA4 123-345-3
+always \u4FA5 13-246-4
+always \u4FA6 1-136-3
+always \u4FA7 245-2346-5
+always \u4FA8 245-246-2
+always \u4FA9 123-2356-5
+always \u4FAA 12-2456-2
+always \u4FAB 1345-13456-5
+always \u4FAC 1345-12346-2
+always \u4FAD 13-1456-4
+always \u4FAE 34-4
+always \u4FAF 1235-12356-2
+always \u4FB0 13-235-4
+always \u4FB1 12-1356-4
+always \u4FB2 1-136-5
+always \u4FB3 245-25-5
+always \u4FB4 12-12356-4
+always \u4FB5 245-1456-3
+always \u4FB6 14-1256-4
+always \u4FB7 13-1256-2
+always \u4FB8 24-34-5
+always \u4FB9 124-13456-4
+always \u4FBA 24-136-5
+always \u4FBB 124-25-3
+always \u4FBC 135-126-2
+always \u4FBD 1345-1236-2
+always \u4FBE 1235-146-3
+always \u4FBF 135-2345-5
+always \u4FC0 124-1246-4
+always \u4FC1 1256-2
+always \u4FC2 15-16-5
+always \u4FC3 245-34-5
+always \u4FC4 2346-5
+always \u4FC5 245-234-2
+always \u4FC6 15-1256-2
+always \u4FC7 13-456-4
+always \u4FC8 123-34-5
+always \u4FC9 34-4
+always \u4FCA 13-256-5
+always \u4FCB 16-5
+always \u4FCC 12345-34-4
+always \u4FCD 14-46-2
+always \u4FCE 125-34-4
+always \u4FCF 245-246-5
+always \u4FD0 14-16-5
+always \u4FD1 235-4
+always \u4FD2 1235-123456-5
+always \u4FD3 13-13456-5
+always \u4FD4 245-2345-5
+always \u4FD5 15-1236-5
+always \u4FD6 1234-2456-4
+always \u4FD7 15-34-2
+always \u4FD8 12345-34-2
+always \u4FD9 15-16-3
+always \u4FDA 14-16-4
+always \u4FDB 12345-34-4
+always \u4FDC 1234-13456-3
+always \u4FDD 135-146-4
+always \u4FDE 1256-2
+always \u4FDF 15-156-5
+always \u4FE0 15-23456-2
+always \u4FE1 15-1456-5
+always \u4FE2 15-234-3
+always \u4FE3 1256-2
+always \u4FE4 124-16-5
+always \u4FE5 12-2346-3
+always \u4FE6 12-12356-2
+always \u4FE7 1-156-5
+always \u4FE8 2345-4
+always \u4FE9 14-23456-4
+always \u4FEA 14-16-5
+always \u4FEB 14-2456-2
+always \u4FEC 15-156-3
+always \u4FED 13-2345-4
+always \u4FEE 15-234-3
+always \u4FEF 12345-34-4
+always \u4FF0 1235-2346-5
+always \u4FF1 13-1256-5
+always \u4FF2 15-246-5
+always \u4FF3 1234-2456-2
+always \u4FF4 13-2345-5
+always \u4FF5 135-246-4
+always \u4FF6 12-34-5
+always \u4FF7 12345-356-5
+always \u4FF8 12345-1356-5
+always \u4FF9 23456-5
+always \u4FFA 1236-4
+always \u4FFB 135-356-5
+always \u4FFC 1256-5
+always \u4FFD 15-1456-3
+always \u4FFE 135-16-5
+always \u4FFF 12-156-2
+always \u5000 12-1346-3
+always \u5001 12-156-2
+always \u5002 135-13456-5
+always \u5003 13-234-5
+always \u5004 246-2
+always \u5005 245-1246-5
+always \u5006 14-23456-4
+always \u5007 12456-4
+always \u5008 14-2456-2
+always \u5009 245-1346-3
+always \u500A 125-12346-5
+always \u500B 13-2346-5
+always \u500C 13-12456-3
+always \u500D 135-356-5
+always \u500E 124-2345-4
+always \u500F 24-34-5
+always \u5010 24-34-5
+always \u5011 134-136-1
+always \u5012 145-146-4
+always \u5013 124-1236-2
+always \u5014 13-236-5
+always \u5015 12-1246-2
+always \u5016 15-13456-5
+always \u5017 1234-1356-2
+always \u5018 124-1346-4
+always \u5019 1235-12356-5
+always \u501A 16-4
+always \u501B 245-16-3
+always \u501C 124-16-5
+always \u501D 13-1236-5
+always \u501E 13-13456-5
+always \u501F 13-346-5
+always \u5020 1-1246-3
+always \u5021 12-1346-5
+always \u5022 13-346-2
+always \u5023 12345-1346-4
+always \u5024 1-156-2
+always \u5025 123-12346-4
+always \u5026 13-45-5
+always \u5027 125-12346-3
+always \u5028 13-1256-5
+always \u5029 245-2345-5
+always \u502A 1345-16-2
+always \u502B 14-123456-2
+always \u502C 1-25-2
+always \u502D 25-3
+always \u502E 14-25-4
+always \u502F 15-12346-3
+always \u5030 14-13456-2
+always \u5031 1235-123456-5
+always \u5032 145-12346-3
+always \u5033 125-156-5
+always \u5034 135-136-5
+always \u5035 34-4
+always \u5036 13-1256-5
+always \u5037 1345-2456-5
+always \u5038 245-2456-4
+always \u5039 13-2345-4
+always \u503A 1-2456-5
+always \u503B 346-3
+always \u503C 1-156-2
+always \u503D 24-345-2
+always \u503E 245-13456-3
+always \u503F 245-346-5
+always \u5040 13456-3
+always \u5041 12-1356-3
+always \u5042 13-2345-3
+always \u5043 2345-4
+always \u5044 1245-12456-4
+always \u5045 1-12346-4
+always \u5046 12-123456-4
+always \u5047 13-23456-4
+always \u5048 13-346-2
+always \u5049 1246-4
+always \u504A 1256-4
+always \u504B 135-13456-5
+always \u504C 1245-25-5
+always \u504D 124-16-2
+always \u504E 1246-3
+always \u504F 1234-2345-3
+always \u5050 2345-5
+always \u5051 12345-1356-3
+always \u5052 124-1346-4
+always \u5053 25-5
+always \u5054 2346-5
+always \u5055 15-346-2
+always \u5056 12-2346-4
+always \u5057 24-1356-4
+always \u5058 123-1236-4
+always \u5059 145-16-5
+always \u505A 125-25-5
+always \u505B 12-345-3
+always \u505C 124-13456-2
+always \u505D 135-356-5
+always \u505E 346-5
+always \u505F 1235-456-2
+always \u5060 246-4
+always \u5061 1-1236-5
+always \u5062 12-12356-4
+always \u5063 2345-3
+always \u5064 234-4
+always \u5065 13-2345-5
+always \u5066 15-1256-3
+always \u5067 1-345-3
+always \u5068 245-156-3
+always \u5069 12345-34-5
+always \u506A 135-16-3
+always \u506B 1-156-5
+always \u506C 125-12346-4
+always \u506D 134-2345-4
+always \u506E 13-16-2
+always \u506F 16-4
+always \u5070 15-346-5
+always \u5071 15-256-2
+always \u5072 15-156-3
+always \u5073 145-12456-3
+always \u5074 245-2346-5
+always \u5075 1-136-3
+always \u5076 12356-4
+always \u5077 124-12356-3
+always \u5078 124-12356-3
+always \u5079 135-356-5
+always \u507A 125-1236-2
+always \u507B 14-1256-4
+always \u507C 13-346-2
+always \u507D 1246-5
+always \u507E 12345-136-5
+always \u507F 12-1346-2
+always \u5080 123-1246-4
+always \u5081 15-12356-4
+always \u5082 12-156-4
+always \u5083 15-34-5
+always \u5084 15-23456-5
+always \u5085 12345-34-5
+always \u5086 45-5
+always \u5087 1245-12346-4
+always \u5088 14-16-5
+always \u5089 1245-34-5
+always \u508A 256-4
+always \u508B 13-12356-5
+always \u508C 134-345-5
+always \u508D 135-1346-3
+always \u508E 145-2345-3
+always \u508F 124-1346-2
+always \u5090 1235-146-5
+always \u5091 13-346-2
+always \u5092 15-16-3
+always \u5093 24-1236-5
+always \u5094 245-2345-5
+always \u5095 13-236-2
+always \u5096 245-1346-3
+always \u5097 12-34-5
+always \u5098 15-1236-4
+always \u5099 135-356-5
+always \u509A 15-246-5
+always \u509B 235-4
+always \u509C 246-2
+always \u509D 124-345-5
+always \u509E 15-25-3
+always \u509F 46-4
+always \u50A0 12345-345-3
+always \u50A1 135-13456-5
+always \u50A2 13-23456-3
+always \u50A3 145-2456-4
+always \u50A4 125-2456-5
+always \u50A5 124-1346-4
+always \u50A6 13-34-4
+always \u50A7 135-1456-3
+always \u50A8 12-34-2
+always \u50A9 1345-25-2
+always \u50AA 245-1236-3
+always \u50AB 14-356-4
+always \u50AC 245-1246-3
+always \u50AD 235-3
+always \u50AE 125-146-3
+always \u50AF 125-12346-4
+always \u50B0 1234-1356-2
+always \u50B1 15-12346-4
+always \u50B2 146-5
+always \u50B3 12-12456-2
+always \u50B4 1256-4
+always \u50B5 1-2456-5
+always \u50B6 245-16-3
+always \u50B7 24-1346-3
+always \u50B8 12-456-4
+always \u50B9 13-13456-5
+always \u50BA 12-156-5
+always \u50BB 24-345-4
+always \u50BC 1235-1236-5
+always \u50BD 1-1346-3
+always \u50BE 245-13456-3
+always \u50BF 2345-5
+always \u50C0 145-16-5
+always \u50C1 15-346-5
+always \u50C2 14-12356-2
+always \u50C3 135-356-5
+always \u50C4 1234-246-5
+always \u50C5 13-1456-4
+always \u50C6 14-2345-2
+always \u50C7 14-34-5
+always \u50C8 134-1236-5
+always \u50C9 245-2345-3
+always \u50CA 15-2345-3
+always \u50CB 124-1236-5
+always \u50CC 13456-2
+always \u50CD 145-12346-5
+always \u50CE 1-12456-5
+always \u50CF 15-46-5
+always \u50D0 24-1236-5
+always \u50D1 245-246-2
+always \u50D2 13-235-4
+always \u50D3 124-1246-4
+always \u50D4 125-123456-4
+always \u50D5 1234-34-2
+always \u50D6 15-16-3
+always \u50D7 14-146-2
+always \u50D8 12-1346-4
+always \u50D9 13-456-3
+always \u50DA 14-246-2
+always \u50DB 245-16-3
+always \u50DC 12-1356-3
+always \u50DD 12-1236-2
+always \u50DE 1246-5
+always \u50DF 13-16-3
+always \u50E0 12345-1236-3
+always \u50E1 1235-1246-5
+always \u50E2 12-12456-4
+always \u50E3 124-346-4
+always \u50E4 145-1236-5
+always \u50E5 13-246-4
+always \u50E6 13-234-5
+always \u50E7 15-1356-3
+always \u50E8 12345-136-5
+always \u50E9 15-2345-5
+always \u50EA 1256-5
+always \u50EB 2346-5
+always \u50EC 13-246-3
+always \u50ED 13-2345-5
+always \u50EE 124-12346-2
+always \u50EF 14-1456-4
+always \u50F0 135-126-2
+always \u50F1 13-34-5
+always \u50F2 15-2345-2
+always \u50F3 15-34-5
+always \u50F4 15-2345-5
+always \u50F5 13-46-3
+always \u50F6 134-1456-4
+always \u50F7 346-5
+always \u50F8 13-1456-5
+always \u50F9 13-23456-5
+always \u50FA 245-246-5
+always \u50FB 1234-16-5
+always \u50FC 12345-1356-3
+always \u50FD 1-12356-5
+always \u50FE 2456-5
+always \u50FF 15-2456-5
+always \u5100 16-2
+always \u5101 13-256-5
+always \u5102 1345-12346-2
+always \u5103 12-1236-2
+always \u5104 16-5
+always \u5105 145-1346-3
+always \u5106 13-13456-4
+always \u5107 15-45-3
+always \u5108 123-2356-5
+always \u5109 13-2345-4
+always \u510A 12-34-5
+always \u510B 145-1236-3
+always \u510C 13-246-4
+always \u510D 24-345-4
+always \u510E 125-2456-5
+always \u510F 245-1236-5
+always \u5110 135-1456-3
+always \u5111 1236-5
+always \u5112 1245-34-2
+always \u5113 124-2456-2
+always \u5114 12-12356-2
+always \u5115 12-2456-2
+always \u5116 14-1236-2
+always \u5117 1345-16-4
+always \u5118 13-1456-4
+always \u5119 245-2345-5
+always \u511A 134-1356-2
+always \u511B 34-4
+always \u511C 1345-1356-2
+always \u511D 245-235-2
+always \u511E 1345-16-4
+always \u511F 12-1346-2
+always \u5120 14-346-5
+always \u5121 14-356-4
+always \u5122 14-1256-4
+always \u5123 123-456-5
+always \u5124 135-146-5
+always \u5125 145-34-2
+always \u5126 135-246-3
+always \u5127 125-1236-4
+always \u5128 1-156-2
+always \u5129 15-156-5
+always \u512A 234-3
+always \u512B 1235-146-2
+always \u512C 12-136-5
+always \u512D 12-136-5
+always \u512E 14-16-5
+always \u512F 124-1356-2
+always \u5130 1246-4
+always \u5131 14-12346-4
+always \u5132 12-34-2
+always \u5133 12-1236-2
+always \u5134 1245-1346-2
+always \u5135 24-34-3
+always \u5136 15-16-3
+always \u5137 14-16-5
+always \u5138 14-25-2
+always \u5139 125-1236-4
+always \u513A 1345-25-2
+always \u513B 124-1346-4
+always \u513C 2345-4
+always \u513D 14-356-4
+always \u513E 1345-1346-5
+always \u513F 156-2
+always \u5140 34-5
+always \u5141 256-4
+always \u5142 125-1236-3
+always \u5143 45-2
+always \u5144 15-235-3
+always \u5145 12-12346-3
+always \u5146 1-146-5
+always \u5147 15-235-3
+always \u5148 15-2345-3
+always \u5149 13-456-3
+always \u514A 145-1246-5
+always \u514B 123-2346-5
+always \u514C 145-1246-5
+always \u514D 134-2345-4
+always \u514E 124-34-5
+always \u514F 12-1346-2
+always \u5150 156-2
+always \u5151 145-1246-5
+always \u5152 156-2
+always \u5153 15-1456-3
+always \u5154 124-34-5
+always \u5155 15-156-5
+always \u5156 2345-4
+always \u5157 2345-4
+always \u5158 24-156-4
+always \u5159 248-156-2-1238-2346-5
+always \u515A 145-1346-4
+always \u515B 2458-2345-3-1238-2346-5
+always \u515C 145-12356-3
+always \u515D 123458-136-3-1238-2346-5
+always \u515E 1348-146-2-1238-2346-5
+always \u515F 24-136-3
+always \u5160 145-12356-3
+always \u5161 1358-2456-4-1238-2346-5
+always \u5162 13-13456-3
+always \u5163 148-16-2-1238-2346-5
+always \u5164 1235-456-2
+always \u5165 1245-34-5
+always \u5166 456-2
+always \u5167 1345-356-5
+always \u5168 245-45-2
+always \u5169 14-46-4
+always \u516A 1256-2
+always \u516B 135-345-3
+always \u516C 13-12346-3
+always \u516D 14-234-5
+always \u516E 15-16-3
+always \u516F 1235-1236-2
+always \u5170 14-1236-2
+always \u5171 13-12346-5
+always \u5172 124-2345-3
+always \u5173 13-12456-3
+always \u5174 15-13456-3
+always \u5175 135-13456-3
+always \u5176 245-16-2
+always \u5177 13-1256-5
+always \u5178 145-2345-4
+always \u5179 125-156-3
+always \u517A 135-34-3
+always \u517B 46-4
+always \u517C 13-2345-3
+always \u517D 24-12356-5
+always \u517E 13-16-5
+always \u517F 16-5
+always \u5180 13-16-5
+always \u5181 12-1236-4
+always \u5182 13-235-3
+always \u5183 134-146-5
+always \u5184 1245-1236-4
+always \u5185 1345-356-5
+always \u5186 45-2
+always \u5187 234-4
+always \u5188 13-1346-3
+always \u5189 1245-1236-4
+always \u518A 245-2346-5
+always \u518B 13-235-3
+always \u518C 245-2346-5
+always \u518D 125-2456-5
+always \u518E 13-35-4
+always \u518F 13-235-4
+always \u5190 134-146-5
+always \u5191 1-12356-5
+always \u5192 134-146-5
+always \u5193 13-12356-5
+always \u5194 15-1256-4
+always \u5195 134-2345-4
+always \u5196 134-16-5
+always \u5197 1245-12346-4
+always \u5198 1456-2
+always \u5199 15-346-4
+always \u519A 123-1236-4
+always \u519B 13-256-3
+always \u519C 1345-12346-2
+always \u519D 16-2
+always \u519E 134-16-2
+always \u519F 24-156-5
+always \u51A0 13-12456-5
+always \u51A1 134-1356-2
+always \u51A2 1-12346-4
+always \u51A3 13-1256-5
+always \u51A4 45-3
+always \u51A5 134-13456-2
+always \u51A6 123-12356-5
+always \u51A7 1345-1456-2
+always \u51A8 12345-34-5
+always \u51A9 15-346-4
+always \u51AA 134-16-5
+always \u51AB 135-13456-3
+always \u51AC 145-12346-3
+always \u51AD 124-2456-2
+always \u51AE 13-1346-3
+always \u51AF 12345-1356-2
+always \u51B0 135-13456-3
+always \u51B1 1235-34-5
+always \u51B2 12-12346-3
+always \u51B3 13-236-2
+always \u51B4 1235-34-5
+always \u51B5 123-456-5
+always \u51B6 346-4
+always \u51B7 14-1356-4
+always \u51B8 1234-1236-5
+always \u51B9 12345-34-2
+always \u51BA 134-1456-4
+always \u51BB 145-12346-5
+always \u51BC 15-2345-4
+always \u51BD 14-346-5
+always \u51BE 15-23456-2
+always \u51BF 13-2345-3
+always \u51C0 13-13456-5
+always \u51C1 24-34-5
+always \u51C2 134-356-4
+always \u51C3 124-34-2
+always \u51C4 245-16-3
+always \u51C5 13-34-5
+always \u51C6 1-123456-4
+always \u51C7 15-12346-3
+always \u51C8 13-13456-5
+always \u51C9 14-46-2
+always \u51CA 245-13456-5
+always \u51CB 145-246-3
+always \u51CC 14-13456-2
+always \u51CD 145-12346-5
+always \u51CE 13-1236-5
+always \u51CF 13-2345-4
+always \u51D0 1456-3
+always \u51D1 245-12356-5
+always \u51D2 2456-2
+always \u51D3 14-16-5
+always \u51D4 245-1346-3
+always \u51D5 134-13456-4
+always \u51D6 1-123456-4
+always \u51D7 245-1246-3
+always \u51D8 15-156-3
+always \u51D9 145-25-2
+always \u51DA 13-1456-5
+always \u51DB 14-1456-4
+always \u51DC 14-1456-4
+always \u51DD 1345-13456-2
+always \u51DE 15-16-3
+always \u51DF 145-34-2
+always \u51E0 13-16-4
+always \u51E1 12345-1236-2
+always \u51E2 12345-1236-2
+always \u51E3 12345-1236-2
+always \u51E4 12345-1356-5
+always \u51E5 13-1256-3
+always \u51E6 12-34-5
+always \u51E7 1-1356-3
+always \u51E8 12345-1356-3
+always \u51E9 134-34-5
+always \u51EA 1-156-5
+always \u51EB 12345-34-2
+always \u51EC 12345-1356-3
+always \u51ED 1234-13456-2
+always \u51EE 12345-1356-3
+always \u51EF 123-2456-4
+always \u51F0 1235-456-2
+always \u51F1 123-2456-4
+always \u51F2 13-1236-3
+always \u51F3 145-1356-5
+always \u51F4 1234-13456-2
+always \u51F5 123-1236-4
+always \u51F6 15-235-3
+always \u51F7 123-2356-5
+always \u51F8 124-34-3
+always \u51F9 146-3
+always \u51FA 12-34-3
+always \u51FB 13-16-2
+always \u51FC 145-1346-5
+always \u51FD 1235-1236-2
+always \u51FE 1235-1236-2
+always \u51FF 125-146-2
+always \u5200 145-146-3
+always \u5201 145-246-3
+always \u5202 145-146-3
+always \u5203 1245-136-5
+always \u5204 1245-136-5
+always \u5205 12-456-3
+always \u5206 12345-136-3
+always \u5207 245-346-3
+always \u5208 16-5
+always \u5209 13-16-3
+always \u520A 123-1236-3
+always \u520B 245-2345-5
+always \u520C 245-123456-4
+always \u520D 12-34-2
+always \u520E 123456-4
+always \u520F 13-16-3
+always \u5210 145-1236-4
+always \u5211 15-13456-2
+always \u5212 1235-35-2
+always \u5213 12456-2
+always \u5214 13-236-2
+always \u5215 14-16-2
+always \u5216 236-5
+always \u5217 14-346-5
+always \u5218 14-234-2
+always \u5219 125-2346-2
+always \u521A 13-1346-3
+always \u521B 12-456-5
+always \u521C 12345-34-2
+always \u521D 12-34-3
+always \u521E 245-1256-5
+always \u521F 13-1256-3
+always \u5220 24-1236-3
+always \u5221 134-1456-4
+always \u5222 14-13456-2
+always \u5223 1-12346-3
+always \u5224 1234-1236-5
+always \u5225 135-346-2
+always \u5226 13-346-2
+always \u5227 13-346-2
+always \u5228 135-146-5
+always \u5229 14-16-5
+always \u522A 24-1236-3
+always \u522B 135-346-2
+always \u522C 12-1236-4
+always \u522D 13-13456-4
+always \u522E 13-35-3
+always \u522F 13-136-3
+always \u5230 145-146-5
+always \u5231 12-456-5
+always \u5232 123-1246-3
+always \u5233 123-34-3
+always \u5234 145-25-5
+always \u5235 156-5
+always \u5236 1-156-5
+always \u5237 24-35-3
+always \u5238 245-45-5
+always \u5239 24-345-3
+always \u523A 245-156-5
+always \u523B 123-2346-5
+always \u523C 13-346-2
+always \u523D 13-1246-5
+always \u523E 245-156-5
+always \u523F 13-1246-5
+always \u5240 123-2456-4
+always \u5241 145-25-5
+always \u5242 13-16-5
+always \u5243 124-16-5
+always \u5244 13-13456-4
+always \u5245 145-12356-3
+always \u5246 14-25-2
+always \u5247 125-2346-2
+always \u5248 45-3
+always \u5249 245-25-5
+always \u524A 15-246-3
+always \u524B 123-2346-5
+always \u524C 14-345-5
+always \u524D 245-2345-2
+always \u524E 24-345-3
+always \u524F 12-456-5
+always \u5250 13-35-4
+always \u5251 13-2345-5
+always \u5252 245-25-5
+always \u5253 14-16-2
+always \u5254 124-16-3
+always \u5255 12345-356-5
+always \u5256 1234-12356-4
+always \u5257 12-1236-4
+always \u5258 245-16-2
+always \u5259 12-456-5
+always \u525A 125-156-5
+always \u525B 13-1346-3
+always \u525C 12456-3
+always \u525D 135-126-3
+always \u525E 13-16-3
+always \u525F 145-25-3
+always \u5260 245-13456-2
+always \u5261 2345-4
+always \u5262 1-25-2
+always \u5263 13-2345-5
+always \u5264 13-16-5
+always \u5265 135-126-3
+always \u5266 2345-3
+always \u5267 13-1256-5
+always \u5268 1235-25-5
+always \u5269 24-1356-5
+always \u526A 13-2345-4
+always \u526B 145-25-2
+always \u526C 145-12456-3
+always \u526D 34-3
+always \u526E 13-35-4
+always \u526F 12345-34-5
+always \u5270 24-1356-5
+always \u5271 13-2345-5
+always \u5272 13-2346-3
+always \u5273 1-345-2
+always \u5274 123-2456-4
+always \u5275 12-456-5
+always \u5276 13-45-3
+always \u5277 12-1236-4
+always \u5278 1235-12456-2
+always \u5279 14-34-5
+always \u527A 14-16-2
+always \u527B 1234-1356-4
+always \u527C 24-1236-3
+always \u527D 1234-246-5
+always \u527E 123-12356-3
+always \u527F 13-246-4
+always \u5280 13-35-3
+always \u5281 245-246-3
+always \u5282 13-236-2
+always \u5283 1235-35-5
+always \u5284 1-345-2
+always \u5285 1-25-2
+always \u5286 14-2345-2
+always \u5287 13-1256-5
+always \u5288 1234-16-3
+always \u5289 14-234-2
+always \u528A 13-1246-5
+always \u528B 13-246-4
+always \u528C 13-1246-5
+always \u528D 13-2345-5
+always \u528E 13-2345-5
+always \u528F 124-1346-3
+always \u5290 1235-25-3
+always \u5291 13-16-5
+always \u5292 13-2345-5
+always \u5293 16-5
+always \u5294 13-2345-5
+always \u5295 1-156-2
+always \u5296 12-1236-2
+always \u5297 125-12456-3
+always \u5298 134-126-2
+always \u5299 14-16-2
+always \u529A 1-34-2
+always \u529B 14-16-5
+always \u529C 23456-3
+always \u529D 245-45-5
+always \u529E 135-1236-5
+always \u529F 13-12346-3
+always \u52A0 13-23456-3
+always \u52A1 34-5
+always \u52A2 134-2456-5
+always \u52A3 14-346-5
+always \u52A4 13-1456-5
+always \u52A5 123-1356-3
+always \u52A6 15-346-2
+always \u52A7 1-156-4
+always \u52A8 145-12346-5
+always \u52A9 1-34-5
+always \u52AA 1345-34-4
+always \u52AB 13-346-2
+always \u52AC 245-1256-2
+always \u52AD 24-146-5
+always \u52AE 16-5
+always \u52AF 1-34-3
+always \u52B0 134-246-4
+always \u52B1 14-16-5
+always \u52B2 13-1456-5
+always \u52B3 14-146-2
+always \u52B4 14-146-2
+always \u52B5 13-45-5
+always \u52B6 123-12356-4
+always \u52B7 46-2
+always \u52B8 35-3
+always \u52B9 15-246-5
+always \u52BA 134-12356-2
+always \u52BB 123-456-3
+always \u52BC 13-346-2
+always \u52BD 14-346-5
+always \u52BE 1235-2346-2
+always \u52BF 24-156-5
+always \u52C0 123-2346-5
+always \u52C1 13-1456-5
+always \u52C2 1235-146-2
+always \u52C3 135-126-2
+always \u52C4 134-1456-4
+always \u52C5 12-156-5
+always \u52C6 14-1346-2
+always \u52C7 235-4
+always \u52C8 235-4
+always \u52C9 134-2345-4
+always \u52CA 123-2346-5
+always \u52CB 15-256-3
+always \u52CC 13-45-5
+always \u52CD 245-13456-2
+always \u52CE 14-34-5
+always \u52CF 1234-12356-4
+always \u52D0 134-1356-4
+always \u52D1 12-156-5
+always \u52D2 14-2346-5
+always \u52D3 123-2456-5
+always \u52D4 134-2345-4
+always \u52D5 145-12346-5
+always \u52D6 15-1256-5
+always \u52D7 15-1256-5
+always \u52D8 123-1236-3
+always \u52D9 34-5
+always \u52DA 16-5
+always \u52DB 15-256-3
+always \u52DC 12346-4
+always \u52DD 24-1356-5
+always \u52DE 14-146-2
+always \u52DF 134-34-5
+always \u52E0 14-34-5
+always \u52E1 1234-246-5
+always \u52E2 24-156-5
+always \u52E3 13-16-3
+always \u52E4 245-1456-2
+always \u52E5 245-46-4
+always \u52E6 13-246-4
+always \u52E7 245-45-5
+always \u52E8 46-4
+always \u52E9 16-5
+always \u52EA 13-236-2
+always \u52EB 12345-1236-2
+always \u52EC 13-45-5
+always \u52ED 124-12346-2
+always \u52EE 13-1256-5
+always \u52EF 145-1236-3
+always \u52F0 15-346-2
+always \u52F1 134-2456-5
+always \u52F2 15-256-3
+always \u52F3 15-256-3
+always \u52F4 14-1256-5
+always \u52F5 14-16-5
+always \u52F6 12-2346-5
+always \u52F7 1245-1346-2
+always \u52F8 245-45-5
+always \u52F9 135-146-3
+always \u52FA 24-146-2
+always \u52FB 256-2
+always \u52FC 13-234-3
+always \u52FD 135-146-5
+always \u52FE 13-12356-3
+always \u52FF 34-5
+always \u5300 256-2
+always \u5301 14-46-4
+always \u5302 145-16-5
+always \u5303 13-2456-5
+always \u5304 13-2456-5
+always \u5305 135-146-3
+always \u5306 245-12346-3
+always \u5307 245-12346-3
+always \u5308 15-235-3
+always \u5309 1234-1356-3
+always \u530A 13-1256-2
+always \u530B 124-146-2
+always \u530C 13-2346-2
+always \u530D 1234-34-2
+always \u530E 2346-2
+always \u530F 1234-146-2
+always \u5310 12345-34-2
+always \u5311 13-12346-3
+always \u5312 145-345-2
+always \u5313 13-234-5
+always \u5314 245-235-3
+always \u5315 135-16-4
+always \u5316 1235-35-5
+always \u5317 135-356-4
+always \u5318 1345-146-4
+always \u5319 12-156-2
+always \u531A 12345-1346-3
+always \u531B 13-234-5
+always \u531C 16-2
+always \u531D 125-345-3
+always \u531E 13-46-5
+always \u531F 123-1346-5
+always \u5320 13-46-5
+always \u5321 123-456-3
+always \u5322 1235-34-3
+always \u5323 15-23456-2
+always \u5324 245-1256-3
+always \u5325 135-2345-5
+always \u5326 13-1246-4
+always \u5327 245-346-5
+always \u5328 125-1346-3
+always \u5329 123-456-3
+always \u532A 12345-356-4
+always \u532B 1235-34-3
+always \u532C 124-12356-2
+always \u532D 13-1246-4
+always \u532E 123-1246-5
+always \u532F 1235-1246-5
+always \u5330 145-1236-3
+always \u5331 123-1246-5
+always \u5332 14-2345-2
+always \u5333 14-2345-2
+always \u5334 15-12456-4
+always \u5335 145-34-2
+always \u5336 13-234-5
+always \u5337 245-1256-2
+always \u5338 15-16-5
+always \u5339 1234-16-4
+always \u533A 245-1256-3
+always \u533B 16-3
+always \u533C 1236-4
+always \u533D 2345-4
+always \u533E 135-2345-4
+always \u533F 1345-16-5
+always \u5340 245-1256-3
+always \u5341 24-156-2
+always \u5342 15-1456-5
+always \u5343 245-2345-3
+always \u5344 1345-2345-5
+always \u5345 15-345-5
+always \u5346 125-34-2
+always \u5347 24-1356-3
+always \u5348 34-4
+always \u5349 1235-1246-5
+always \u534A 135-1236-5
+always \u534B 24-156-5
+always \u534C 15-16-5
+always \u534D 12456-5
+always \u534E 1235-35-2
+always \u534F 15-346-2
+always \u5350 12456-5
+always \u5351 135-356-3
+always \u5352 125-34-2
+always \u5353 1-25-2
+always \u5354 15-346-2
+always \u5355 145-1236-3
+always \u5356 134-2456-5
+always \u5357 1345-1236-2
+always \u5358 145-1236-3
+always \u5359 13-16-2
+always \u535A 135-126-2
+always \u535B 14-1256-5
+always \u535C 135-34-4
+always \u535D 123-456-5
+always \u535E 135-2345-5
+always \u535F 135-34-4
+always \u5360 1-1236-5
+always \u5361 123-345-4
+always \u5362 14-34-2
+always \u5363 234-4
+always \u5364 14-34-4
+always \u5365 15-16-3
+always \u5366 13-35-5
+always \u5367 25-5
+always \u5368 15-346-5
+always \u5369 13-346-2
+always \u536A 13-346-2
+always \u536B 1246-5
+always \u536C 1346-2
+always \u536D 245-235-2
+always \u536E 1-156-3
+always \u536F 134-146-4
+always \u5370 1456-5
+always \u5371 1246-2
+always \u5372 24-146-5
+always \u5373 13-16-2
+always \u5374 245-236-5
+always \u5375 14-12456-4
+always \u5376 24-156-5
+always \u5377 13-45-4
+always \u5378 15-346-5
+always \u5379 15-1256-5
+always \u537A 13-1456-4
+always \u537B 245-236-5
+always \u537C 34-5
+always \u537D 13-16-2
+always \u537E 2346-5
+always \u537F 245-13456-3
+always \u5380 15-16-3
+always \u5381 15-1236-3
+always \u5382 12-1346-4
+always \u5383 1-1236-3
+always \u5384 2346-5
+always \u5385 124-13456-3
+always \u5386 14-16-5
+always \u5387 1-2346-2
+always \u5388 1235-1236-4
+always \u5389 14-16-5
+always \u538A 23456-4
+always \u538B 23456-3
+always \u538C 2345-5
+always \u538D 24-2346-5
+always \u538E 1-156-4
+always \u538F 1-345-4
+always \u5390 1234-1346-2
+always \u5391 23456-2
+always \u5392 1235-2346-2
+always \u5393 26-2
+always \u5394 1-156-5
+always \u5395 245-2346-5
+always \u5396 12345-1346-2
+always \u5397 124-16-2
+always \u5398 14-16-2
+always \u5399 24-2346-5
+always \u539A 1235-12356-5
+always \u539B 124-13456-3
+always \u539C 125-1246-3
+always \u539D 245-25-5
+always \u539E 12345-356-5
+always \u539F 45-2
+always \u53A0 245-2346-5
+always \u53A1 45-2
+always \u53A2 15-46-3
+always \u53A3 2345-4
+always \u53A4 14-16-5
+always \u53A5 13-236-2
+always \u53A6 15-23456-5
+always \u53A7 145-2345-3
+always \u53A8 12-34-2
+always \u53A9 13-234-5
+always \u53AA 13-1456-4
+always \u53AB 146-2
+always \u53AC 13-1246-4
+always \u53AD 2345-5
+always \u53AE 15-156-3
+always \u53AF 14-16-5
+always \u53B0 12-1346-4
+always \u53B1 14-1236-2
+always \u53B2 14-16-5
+always \u53B3 2345-2
+always \u53B4 2345-4
+always \u53B5 45-2
+always \u53B6 15-156-3
+always \u53B7 13-12346-3
+always \u53B8 14-1456-2
+always \u53B9 1245-12356-2
+always \u53BA 245-1256-5
+always \u53BB 245-1256-5
+always \u53BC 156-4
+always \u53BD 14-356-4
+always \u53BE 145-34-3
+always \u53BF 15-2345-5
+always \u53C0 1-12456-3
+always \u53C1 15-1236-3
+always \u53C2 245-1236-3
+always \u53C3 245-1236-3
+always \u53C4 15-1236-3
+always \u53C5 245-1236-3
+always \u53C6 2456-5
+always \u53C7 145-2456-5
+always \u53C8 234-5
+always \u53C9 12-345-3
+always \u53CA 13-16-2
+always \u53CB 234-4
+always \u53CC 24-456-3
+always \u53CD 12345-1236-4
+always \u53CE 24-12356-3
+always \u53CF 13-2356-5
+always \u53D0 135-345-2
+always \u53D1 12345-345-3
+always \u53D2 1245-25-5
+always \u53D3 24-156-5
+always \u53D4 24-34-2
+always \u53D5 1-25-2
+always \u53D6 245-1256-4
+always \u53D7 24-12356-5
+always \u53D8 135-2345-5
+always \u53D9 15-1256-5
+always \u53DA 13-23456-4
+always \u53DB 1234-1236-5
+always \u53DC 15-12356-4
+always \u53DD 13-146-5
+always \u53DE 256-5
+always \u53DF 15-12356-4
+always \u53E0 145-346-2
+always \u53E1 1245-1246-5
+always \u53E2 245-12346-2
+always \u53E3 123-12356-4
+always \u53E4 13-34-4
+always \u53E5 13-1256-5
+always \u53E6 14-13456-5
+always \u53E7 13-35-4
+always \u53E8 145-146-3
+always \u53E9 123-12356-5
+always \u53EA 1-156-4
+always \u53EB 13-246-5
+always \u53EC 1-146-5
+always \u53ED 135-345-3
+always \u53EE 145-13456-3
+always \u53EF 123-2346-4
+always \u53F0 124-2456-2
+always \u53F1 12-156-5
+always \u53F2 24-156-4
+always \u53F3 234-5
+always \u53F4 245-234-2
+always \u53F5 1234-126-4
+always \u53F6 346-5
+always \u53F7 1235-146-5
+always \u53F8 15-156-3
+always \u53F9 124-1236-5
+always \u53FA 12-156-4
+always \u53FB 14-2346-5
+always \u53FC 145-246-3
+always \u53FD 13-16-3
+always \u53FE 145-34-2
+always \u53FF 1235-12346-3
+always \u5400 134-346-3
+always \u5401 15-1256-3
+always \u5402 134-1346-2
+always \u5403 12-156-3
+always \u5404 13-2346-5
+always \u5405 15-45-3
+always \u5406 246-3
+always \u5407 125-156-4
+always \u5408 1235-2346-2
+always \u5409 13-16-2
+always \u540A 145-246-5
+always \u540B 245-123456-5
+always \u540C 124-12346-2
+always \u540D 134-13456-2
+always \u540E 1235-12356-5
+always \u540F 14-16-5
+always \u5410 124-34-4
+always \u5411 15-46-5
+always \u5412 1-345-5
+always \u5413 15-23456-5
+always \u5414 346-3
+always \u5415 14-1256-4
+always \u5416 345-3
+always \u5417 134-345-1
+always \u5418 12356-4
+always \u5419 15-236-3
+always \u541A 16-3
+always \u541B 13-256-3
+always \u541C 12-12356-4
+always \u541D 14-1456-5
+always \u541E 124-123456-3
+always \u541F 1456-2
+always \u5420 12345-356-5
+always \u5421 135-16-4
+always \u5422 245-1456-5
+always \u5423 245-1456-5
+always \u5424 13-346-5
+always \u5425 135-34-5
+always \u5426 12345-12356-4
+always \u5427 135-345-1
+always \u5428 124-123456-2
+always \u5429 12345-136-3
+always \u542A 2346-2
+always \u542B 1235-1236-2
+always \u542C 124-13456-3
+always \u542D 123-1356-3
+always \u542E 24-123456-4
+always \u542F 245-16-4
+always \u5430 1235-12346-2
+always \u5431 1-156-3
+always \u5432 1456-4
+always \u5433 34-2
+always \u5434 34-2
+always \u5435 12-146-4
+always \u5436 1345-345-5
+always \u5437 15-236-5
+always \u5438 15-16-3
+always \u5439 12-1246-3
+always \u543A 145-12356-3
+always \u543B 123456-4
+always \u543C 1235-12356-4
+always \u543D 1235-12346-3
+always \u543E 34-2
+always \u543F 13-146-5
+always \u5440 23456-1
+always \u5441 13-256-5
+always \u5442 14-1256-4
+always \u5443 2346-5
+always \u5444 13-2346-2
+always \u5445 134-356-2
+always \u5446 145-2456-3
+always \u5447 245-16-4
+always \u5448 12-1356-2
+always \u5449 34-2
+always \u544A 13-146-5
+always \u544B 12345-34-3
+always \u544C 13-246-5
+always \u544D 1235-12346-3
+always \u544E 12-156-4
+always \u544F 24-1356-3
+always \u5450 1345-345-5
+always \u5451 124-123456-3
+always \u5452 34-4
+always \u5453 16-5
+always \u5454 124-2456-5
+always \u5455 12356-4
+always \u5456 14-16-5
+always \u5457 135-356-1
+always \u5458 45-2
+always \u5459 123-2356-3
+always \u545A 123456-4
+always \u545B 245-46-5
+always \u545C 34-3
+always \u545D 2346-5
+always \u545E 24-156-3
+always \u545F 245-45-4
+always \u5460 1234-136-4
+always \u5461 123456-4
+always \u5462 1345-2346-1
+always \u5463 134-12356-2
+always \u5464 14-13456-5
+always \u5465 1245-1236-2
+always \u5466 234-3
+always \u5467 145-16-4
+always \u5468 1-12356-3
+always \u5469 24-156-5
+always \u546A 1-12356-5
+always \u546B 124-346-3
+always \u546C 15-16-5
+always \u546D 16-5
+always \u546E 245-16-5
+always \u546F 1234-13456-2
+always \u5470 125-156-4
+always \u5471 13-35-3
+always \u5472 125-156-3
+always \u5473 1246-5
+always \u5474 15-1256-4
+always \u5475 1235-2346-3
+always \u5476 1345-146-2
+always \u5477 15-23456-2
+always \u5478 1234-356-3
+always \u5479 16-5
+always \u547A 15-246-3
+always \u547B 24-136-3
+always \u547C 1235-34-3
+always \u547D 134-13456-5
+always \u547E 145-345-2
+always \u547F 245-1256-3
+always \u5480 13-1256-4
+always \u5481 13-1236-3
+always \u5482 125-345-3
+always \u5483 124-25-3
+always \u5484 145-25-3
+always \u5485 1234-12356-4
+always \u5486 1234-146-2
+always \u5487 135-346-2
+always \u5488 12345-34-2
+always \u5489 46-3
+always \u548A 1235-2346-2
+always \u548B 125-2346-2
+always \u548C 1235-2346-2
+always \u548D 1235-2456-3
+always \u548E 13-234-5
+always \u548F 235-4
+always \u5490 12345-34-5
+always \u5491 145-345-3
+always \u5492 1-12356-5
+always \u5493 35-4
+always \u5494 123-345-3
+always \u5495 13-34-3
+always \u5496 123-345-3
+always \u5497 125-25-4
+always \u5498 135-34-5
+always \u5499 14-12346-2
+always \u549A 145-12346-3
+always \u549B 1345-13456-2
+always \u549C 123-345-3
+always \u549D 15-156-3
+always \u549E 15-2345-5
+always \u549F 1235-25-5
+always \u54A0 245-16-3
+always \u54A1 156-5
+always \u54A2 2346-5
+always \u54A3 13-456-3
+always \u54A4 1-345-5
+always \u54A5 15-16-3
+always \u54A6 16-2
+always \u54A7 14-346-4
+always \u54A8 125-156-3
+always \u54A9 134-346-3
+always \u54AA 134-16-3
+always \u54AB 1-156-4
+always \u54AC 246-4
+always \u54AD 13-16-3
+always \u54AE 1-12356-5
+always \u54AF 13-2346-3
+always \u54B0 24-2356-5
+always \u54B1 125-1236-2
+always \u54B2 15-246-5
+always \u54B3 123-2346-2
+always \u54B4 1235-1246-3
+always \u54B5 123-35-3
+always \u54B6 1235-2356-5
+always \u54B7 124-146-2
+always \u54B8 15-2345-2
+always \u54B9 2346-5
+always \u54BA 15-45-4
+always \u54BB 15-234-3
+always \u54BC 123-2356-3
+always \u54BD 2345-5
+always \u54BE 14-146-4
+always \u54BF 16-3
+always \u54C0 2456-3
+always \u54C1 1234-1456-4
+always \u54C2 24-136-4
+always \u54C3 124-12346-2
+always \u54C4 1235-12346-4
+always \u54C5 15-235-3
+always \u54C6 145-25-3
+always \u54C7 35-3
+always \u54C8 1235-345-3
+always \u54C9 125-2456-3
+always \u54CA 1256-5
+always \u54CB 145-16-5
+always \u54CC 1234-2456-5
+always \u54CD 15-46-4
+always \u54CE 2456-3
+always \u54CF 13-136-2
+always \u54D0 123-456-3
+always \u54D1 23456-4
+always \u54D2 145-345-3
+always \u54D3 15-246-3
+always \u54D4 135-16-5
+always \u54D5 1235-1246-5
+always \u54D6 1345-2345-2
+always \u54D7 1235-35-2
+always \u54D8 15-13456-2
+always \u54D9 123-2356-5
+always \u54DA 145-25-4
+always \u54DB 1234-34-2
+always \u54DC 13-16-5
+always \u54DD 1345-12346-2
+always \u54DE 134-12356-2
+always \u54DF 356-3
+always \u54E0 1235-146-5
+always \u54E1 45-2
+always \u54E2 14-12346-5
+always \u54E3 1234-12356-4
+always \u54E4 134-1346-2
+always \u54E5 13-2346-3
+always \u54E6 126-2
+always \u54E7 12-156-3
+always \u54E8 24-146-5
+always \u54E9 14-16-4
+always \u54EA 1345-345-4
+always \u54EB 125-34-2
+always \u54EC 1235-2346-2
+always \u54ED 123-34-3
+always \u54EE 15-246-3
+always \u54EF 15-2345-5
+always \u54F0 14-146-2
+always \u54F1 1234-126-5
+always \u54F2 1-2346-2
+always \u54F3 1-345-3
+always \u54F4 14-46-5
+always \u54F5 135-345-3
+always \u54F6 134-346-3
+always \u54F7 14-2346-5
+always \u54F8 15-1246-3
+always \u54F9 12345-12356-2
+always \u54FA 135-34-4
+always \u54FB 1235-1236-5
+always \u54FC 1235-1356-3
+always \u54FD 13-1356-4
+always \u54FE 24-25-3
+always \u54FF 13-2346-4
+always \u5500 234-4
+always \u5501 2345-5
+always \u5502 13-34-4
+always \u5503 13-34-4
+always \u5504 135-356-1
+always \u5505 1235-1236-3
+always \u5506 15-25-3
+always \u5507 12-123456-2
+always \u5508 16-5
+always \u5509 2456-3
+always \u550A 13-23456-2
+always \u550B 124-34-2
+always \u550C 15-2345-2
+always \u550D 1235-12456-4
+always \u550E 14-16-5
+always \u550F 15-16-3
+always \u5510 124-1346-2
+always \u5511 125-25-5
+always \u5512 245-234-2
+always \u5513 12-2346-3
+always \u5514 34-2
+always \u5515 125-146-5
+always \u5516 23456-4
+always \u5517 145-12356-3
+always \u5518 245-16-4
+always \u5519 145-16-2
+always \u551A 245-1456-5
+always \u551B 134-345-5
+always \u551C 134-345-2
+always \u551D 1235-12346-4
+always \u551E 145-12356-4
+always \u551F 1235-2346-2
+always \u5520 14-146-2
+always \u5521 14-46-4
+always \u5522 15-25-4
+always \u5523 125-146-5
+always \u5524 1235-12456-5
+always \u5525 14-1356-2
+always \u5526 24-345-3
+always \u5527 13-16-3
+always \u5528 125-25-4
+always \u5529 25-3
+always \u552A 12345-1356-4
+always \u552B 1456-2
+always \u552C 1235-34-4
+always \u552D 245-16-5
+always \u552E 24-12356-5
+always \u552F 1246-2
+always \u5530 24-35-3
+always \u5531 12-1346-5
+always \u5532 156-2
+always \u5533 14-16-5
+always \u5534 245-46-5
+always \u5535 1236-4
+always \u5536 13-346-5
+always \u5537 356-3
+always \u5538 1345-2345-5
+always \u5539 1256-3
+always \u553A 15-2345-5
+always \u553B 14-2456-5
+always \u553C 24-345-5
+always \u553D 15-16-3
+always \u553E 124-25-5
+always \u553F 1235-34-3
+always \u5540 26-2
+always \u5541 1-12356-3
+always \u5542 1345-12356-4
+always \u5543 123-136-4
+always \u5544 1-25-2
+always \u5545 1-25-2
+always \u5546 24-1346-3
+always \u5547 24-156-5
+always \u5548 1235-1356-5
+always \u5549 1345-13456-2
+always \u554A 345-3
+always \u554B 15-246-3
+always \u554C 15-46-3
+always \u554D 124-123456-3
+always \u554E 34-4
+always \u554F 123456-5
+always \u5550 245-1246-5
+always \u5551 24-345-5
+always \u5552 1235-34-3
+always \u5553 245-16-4
+always \u5554 245-16-4
+always \u5555 124-146-2
+always \u5556 145-1236-5
+always \u5557 145-1236-5
+always \u5558 346-5
+always \u5559 125-156-4
+always \u555A 135-16-4
+always \u555B 245-1246-5
+always \u555C 12-25-5
+always \u555D 1235-2346-2
+always \u555E 23456-4
+always \u555F 245-16-4
+always \u5560 1-2346-2
+always \u5561 12345-356-3
+always \u5562 14-46-4
+always \u5563 15-2345-2
+always \u5564 1234-16-2
+always \u5565 24-345-2
+always \u5566 14-345-3
+always \u5567 125-2346-2
+always \u5568 245-13456-3
+always \u5569 13-35-5
+always \u556A 1234-345-3
+always \u556B 1-2346-4
+always \u556C 15-2346-5
+always \u556D 1-12456-5
+always \u556E 1345-346-5
+always \u556F 13-25-3
+always \u5570 14-25-3
+always \u5571 2345-3
+always \u5572 145-16-5
+always \u5573 245-45-2
+always \u5574 124-1236-3
+always \u5575 135-126-3
+always \u5576 145-13456-5
+always \u5577 14-1346-3
+always \u5578 15-246-5
+always \u5579 13-1256-2
+always \u557A 124-1346-2
+always \u557B 12-156-5
+always \u557C 124-16-2
+always \u557D 1236-2
+always \u557E 13-234-3
+always \u557F 145-1236-5
+always \u5580 123-345-5
+always \u5581 235-2
+always \u5582 1246-5
+always \u5583 1345-1236-2
+always \u5584 24-1236-5
+always \u5585 1256-5
+always \u5586 1-2346-2
+always \u5587 14-345-4
+always \u5588 13-346-3
+always \u5589 1235-12356-2
+always \u558A 1235-1236-4
+always \u558B 145-346-2
+always \u558C 1-12356-3
+always \u558D 12-2456-2
+always \u558E 123-2356-3
+always \u558F 1345-25-5
+always \u5590 1256-5
+always \u5591 1456-3
+always \u5592 125-1236-2
+always \u5593 246-3
+always \u5594 126-3
+always \u5595 134-2345-4
+always \u5596 1235-34-2
+always \u5597 256-4
+always \u5598 12-12456-4
+always \u5599 1235-1246-5
+always \u559A 1235-12456-5
+always \u559B 1235-12456-5
+always \u559C 15-16-4
+always \u559D 1235-2346-3
+always \u559E 13-16-3
+always \u559F 123-1246-5
+always \u55A0 1-12346-4
+always \u55A1 1246-4
+always \u55A2 24-345-5
+always \u55A3 15-1256-4
+always \u55A4 1235-456-2
+always \u55A5 145-25-2
+always \u55A6 1345-346-5
+always \u55A7 15-45-3
+always \u55A8 14-46-5
+always \u55A9 1256-5
+always \u55AA 15-1346-3
+always \u55AB 12-156-3
+always \u55AC 245-246-2
+always \u55AD 2345-5
+always \u55AE 145-1236-3
+always \u55AF 1234-136-3
+always \u55B0 245-1236-3
+always \u55B1 14-16-2
+always \u55B2 246-3
+always \u55B3 1-345-3
+always \u55B4 1246-3
+always \u55B5 134-246-3
+always \u55B6 13456-2
+always \u55B7 1234-136-3
+always \u55B8 1235-25-4
+always \u55B9 123-1246-2
+always \u55BA 15-16-5
+always \u55BB 1256-5
+always \u55BC 13-346-2
+always \u55BD 14-12356-2
+always \u55BE 123-34-5
+always \u55BF 125-146-5
+always \u55C0 1235-25-5
+always \u55C1 124-16-2
+always \u55C2 246-2
+always \u55C3 1235-2346-5
+always \u55C4 345-2
+always \u55C5 15-234-5
+always \u55C6 245-46-5
+always \u55C7 15-2346-5
+always \u55C8 235-3
+always \u55C9 15-34-5
+always \u55CA 1235-12346-4
+always \u55CB 15-346-2
+always \u55CC 16-5
+always \u55CD 15-25-3
+always \u55CE 134-345-1
+always \u55CF 12-345-3
+always \u55D0 1235-2456-5
+always \u55D1 123-2346-5
+always \u55D2 145-345-3
+always \u55D3 15-1346-4
+always \u55D4 12-136-3
+always \u55D5 1245-34-5
+always \u55D6 15-12356-3
+always \u55D7 35-3
+always \u55D8 13-16-3
+always \u55D9 1234-1346-4
+always \u55DA 34-3
+always \u55DB 15-2345-2
+always \u55DC 24-156-5
+always \u55DD 13-2346-2
+always \u55DE 125-156-3
+always \u55DF 13-346-3
+always \u55E0 14-25-5
+always \u55E1 12346-3
+always \u55E2 35-5
+always \u55E3 15-156-5
+always \u55E4 12-156-3
+always \u55E5 1235-146-2
+always \u55E6 15-25-3
+always \u55E7 138-23456-3-148-123456-2
+always \u55E8 1235-2456-3
+always \u55E9 15-25-4
+always \u55EA 245-1456-2
+always \u55EB 1345-346-5
+always \u55EC 1235-2346-3
+always \u55ED 125-156-3
+always \u55EE 15-2456-5
+always \u55EF 136-4
+always \u55F0 13-2346-4
+always \u55F1 1345-345-2
+always \u55F2 145-346-3
+always \u55F3 2456-5
+always \u55F4 245-46-3
+always \u55F5 124-12346-3
+always \u55F6 135-16-5
+always \u55F7 146-2
+always \u55F8 146-2
+always \u55F9 14-2345-2
+always \u55FA 15-1246-3
+always \u55FB 1-2346-5
+always \u55FC 134-126-5
+always \u55FD 15-12356-5
+always \u55FE 15-12356-4
+always \u55FF 124-1236-4
+always \u5600 145-16-2
+always \u5601 245-16-3
+always \u5602 13-246-5
+always \u5603 12-12346-3
+always \u5604 13-246-3
+always \u5605 123-2456-5
+always \u5606 124-1236-5
+always \u5607 15-1236-3
+always \u5608 245-146-2
+always \u5609 13-23456-3
+always \u560A 26-2
+always \u560B 15-246-3
+always \u560C 1234-246-5
+always \u560D 14-12356-2
+always \u560E 13-345-3
+always \u560F 13-34-4
+always \u5610 15-246-3
+always \u5611 1235-34-3
+always \u5612 1235-1246-5
+always \u5613 13-25-3
+always \u5614 12356-4
+always \u5615 15-2345-3
+always \u5616 125-2346-2
+always \u5617 12-1346-2
+always \u5618 15-1256-3
+always \u5619 1234-126-2
+always \u561A 145-2346-2
+always \u561B 134-345-1
+always \u561C 134-345-5
+always \u561D 1235-34-2
+always \u561E 14-356-3
+always \u561F 145-34-3
+always \u5620 13-345-3
+always \u5621 124-1346-3
+always \u5622 346-4
+always \u5623 135-1356-3
+always \u5624 13456-3
+always \u5625 15-2456-3
+always \u5626 13-246-5
+always \u5627 134-16-5
+always \u5628 15-246-5
+always \u5629 1235-35-2
+always \u562A 134-2456-4
+always \u562B 1245-1236-2
+always \u562C 125-25-3
+always \u562D 1234-1356-3
+always \u562E 14-146-2
+always \u562F 15-246-5
+always \u5630 13-16-3
+always \u5631 1-34-4
+always \u5632 12-146-2
+always \u5633 13-1246-5
+always \u5634 125-1246-4
+always \u5635 15-246-3
+always \u5636 15-156-3
+always \u5637 1235-146-2
+always \u5638 34-4
+always \u5639 14-246-2
+always \u563A 245-246-2
+always \u563B 15-16-3
+always \u563C 15-234-5
+always \u563D 124-1236-3
+always \u563E 124-1236-2
+always \u563F 1235-356-3
+always \u5640 15-256-5
+always \u5641 2346-4
+always \u5642 125-123456-4
+always \u5643 12345-1236-3
+always \u5644 12-156-3
+always \u5645 1235-1246-3
+always \u5646 245-1236-4
+always \u5647 12-456-2
+always \u5648 245-34-5
+always \u5649 145-1236-5
+always \u564A 1256-5
+always \u564B 124-123456-3
+always \u564C 245-1356-3
+always \u564D 13-246-5
+always \u564E 346-3
+always \u564F 15-16-3
+always \u5650 245-16-5
+always \u5651 1235-146-2
+always \u5652 14-2345-2
+always \u5653 15-1256-3
+always \u5654 145-1356-3
+always \u5655 1235-1246-3
+always \u5656 1456-2
+always \u5657 1234-34-3
+always \u5658 13-236-3
+always \u5659 245-1456-2
+always \u565A 15-256-2
+always \u565B 1345-346-5
+always \u565C 14-34-3
+always \u565D 15-156-3
+always \u565E 2345-4
+always \u565F 13456-5
+always \u5660 145-345-2
+always \u5661 1-1236-3
+always \u5662 12356-1
+always \u5663 1-12356-5
+always \u5664 13-1456-5
+always \u5665 1345-12346-2
+always \u5666 1235-1246-5
+always \u5667 15-346-5
+always \u5668 245-16-5
+always \u5669 2346-5
+always \u566A 125-146-5
+always \u566B 16-3
+always \u566C 24-156-5
+always \u566D 13-246-5
+always \u566E 45-5
+always \u566F 2456-5
+always \u5670 235-3
+always \u5671 15-236-3
+always \u5672 123-2356-5
+always \u5673 1256-4
+always \u5674 1234-136-3
+always \u5675 145-146-5
+always \u5676 13-345-2
+always \u5677 15-1456-3
+always \u5678 145-123456-5
+always \u5679 145-1346-3
+always \u567A 15-1456-3
+always \u567B 15-2456-3
+always \u567C 1234-16-3
+always \u567D 1234-16-4
+always \u567E 1456-3
+always \u567F 125-1246-4
+always \u5680 1345-13456-2
+always \u5681 145-16-2
+always \u5682 14-1236-5
+always \u5683 124-345-5
+always \u5684 1235-25-5
+always \u5685 1245-34-2
+always \u5686 1235-146-3
+always \u5687 15-23456-5
+always \u5688 23456-5
+always \u5689 145-25-3
+always \u568A 15-16-5
+always \u568B 12-12356-2
+always \u568C 13-16-5
+always \u568D 13-1456-5
+always \u568E 1235-146-2
+always \u568F 124-16-5
+always \u5690 12-1346-2
+always \u5691 15-256-3
+always \u5692 134-2346-1
+always \u5693 245-345-3
+always \u5694 124-16-5
+always \u5695 14-34-3
+always \u5696 1235-1246-5
+always \u5697 135-126-3
+always \u5698 234-3
+always \u5699 1345-346-5
+always \u569A 1456-2
+always \u569B 1235-34-5
+always \u569C 134-2346-1
+always \u569D 1235-456-3
+always \u569E 1-2346-2
+always \u569F 14-16-2
+always \u56A0 14-234-2
+always \u56A1 1235-2456-3
+always \u56A2 1345-1346-2
+always \u56A3 15-246-3
+always \u56A4 134-126-2
+always \u56A5 2345-5
+always \u56A6 14-16-5
+always \u56A7 14-34-2
+always \u56A8 14-12346-2
+always \u56A9 12345-34-2
+always \u56AA 145-1236-5
+always \u56AB 12-136-5
+always \u56AC 1234-1456-2
+always \u56AD 1234-16-4
+always \u56AE 15-46-5
+always \u56AF 1235-25-5
+always \u56B0 134-126-2
+always \u56B1 15-16-5
+always \u56B2 145-25-4
+always \u56B3 123-34-5
+always \u56B4 2345-2
+always \u56B5 12-1236-2
+always \u56B6 13456-3
+always \u56B7 1245-1346-4
+always \u56B8 145-2345-4
+always \u56B9 14-345-3
+always \u56BA 124-345-5
+always \u56BB 15-246-3
+always \u56BC 13-236-2
+always \u56BD 12-25-5
+always \u56BE 1235-12456-3
+always \u56BF 1235-25-5
+always \u56C0 1-12456-4
+always \u56C1 1345-346-5
+always \u56C2 15-246-3
+always \u56C3 245-345-5
+always \u56C4 14-16-2
+always \u56C5 12-1236-4
+always \u56C6 12-2456-5
+always \u56C7 14-16-5
+always \u56C8 16-5
+always \u56C9 14-25-3
+always \u56CA 1345-1346-2
+always \u56CB 125-1236-5
+always \u56CC 15-34-3
+always \u56CD 15-16-4
+always \u56CE 12345-136-5
+always \u56CF 13-2345-3
+always \u56D0 125-345-2
+always \u56D1 1-34-4
+always \u56D2 14-1236-2
+always \u56D3 1345-346-5
+always \u56D4 1345-1346-3
+always \u56D5 14-345-2
+always \u56D6 14-25-2
+always \u56D7 1246-2
+always \u56D8 1235-1246-2
+always \u56D9 1456-3
+always \u56DA 245-234-2
+always \u56DB 15-156-5
+always \u56DC 1345-1456-2
+always \u56DD 13-2345-4
+always \u56DE 1235-1246-2
+always \u56DF 15-1456-5
+always \u56E0 1456-3
+always \u56E1 1345-1236-3
+always \u56E2 124-12456-2
+always \u56E3 124-12456-2
+always \u56E4 124-123456-2
+always \u56E5 123-1346-5
+always \u56E6 45-3
+always \u56E7 13-235-4
+always \u56E8 1234-2345-3
+always \u56E9 256-5
+always \u56EA 245-12346-3
+always \u56EB 1235-34-2
+always \u56EC 1235-1246-2
+always \u56ED 45-2
+always \u56EE 2346-2
+always \u56EF 13-25-2
+always \u56F0 123-123456-5
+always \u56F1 245-12346-3
+always \u56F2 1246-2
+always \u56F3 124-34-2
+always \u56F4 1246-2
+always \u56F5 14-123456-2
+always \u56F6 13-25-2
+always \u56F7 13-256-3
+always \u56F8 1245-156-5
+always \u56F9 14-13456-2
+always \u56FA 13-34-5
+always \u56FB 13-25-2
+always \u56FC 124-2456-3
+always \u56FD 13-25-2
+always \u56FE 124-34-2
+always \u56FF 234-5
+always \u5700 13-25-2
+always \u5701 1456-2
+always \u5702 1235-123456-5
+always \u5703 1234-34-4
+always \u5704 1256-4
+always \u5705 1235-1236-2
+always \u5706 45-2
+always \u5707 14-123456-2
+always \u5708 245-45-3
+always \u5709 1256-4
+always \u570A 245-13456-3
+always \u570B 13-25-2
+always \u570C 12-1246-2
+always \u570D 1246-2
+always \u570E 45-2
+always \u570F 245-45-3
+always \u5710 123-34-3
+always \u5711 12345-34-5
+always \u5712 45-2
+always \u5713 45-2
+always \u5714 2346-5
+always \u5715 123-12456-4
+always \u5716 124-34-2
+always \u5717 124-34-2
+always \u5718 124-12456-2
+always \u5719 14-236-5
+always \u571A 1235-1246-5
+always \u571B 16-5
+always \u571C 1235-12456-2
+always \u571D 14-12456-2
+always \u571E 14-12456-2
+always \u571F 124-34-4
+always \u5720 23456-5
+always \u5721 124-34-4
+always \u5722 124-13456-4
+always \u5723 24-1356-5
+always \u5724 1234-34-2
+always \u5725 14-34-5
+always \u5726 123-2356-5
+always \u5727 23456-3
+always \u5728 125-2456-5
+always \u5729 15-1256-3
+always \u572A 13-2346-3
+always \u572B 1256-5
+always \u572C 34-3
+always \u572D 13-1246-3
+always \u572E 1234-16-4
+always \u572F 16-2
+always \u5730 145-16-5
+always \u5731 245-2345-3
+always \u5732 245-2345-3
+always \u5733 125-123456-5
+always \u5734 1-25-2
+always \u5735 145-1346-5
+always \u5736 245-23456-5
+always \u5737 15-46-5
+always \u5738 24-1236-3
+always \u5739 123-456-5
+always \u573A 12-1346-4
+always \u573B 245-16-2
+always \u573C 1345-346-5
+always \u573D 134-126-5
+always \u573E 15-2346-5
+always \u573F 13-23456-2
+always \u5740 1-156-4
+always \u5741 1-156-4
+always \u5742 135-1236-4
+always \u5743 15-256-3
+always \u5744 124-12356-2
+always \u5745 245-1456-4
+always \u5746 134-356-2
+always \u5747 13-256-3
+always \u5748 123-1356-3
+always \u5749 124-123456-2
+always \u574A 12345-1346-3
+always \u574B 12345-136-5
+always \u574C 135-136-5
+always \u574D 124-1236-3
+always \u574E 123-1236-4
+always \u574F 1235-2356-5
+always \u5750 125-25-5
+always \u5751 123-1356-3
+always \u5752 135-16-5
+always \u5753 15-13456-2
+always \u5754 145-16-5
+always \u5755 13-13456-3
+always \u5756 13-16-5
+always \u5757 123-2356-5
+always \u5758 145-16-4
+always \u5759 13-13456-3
+always \u575A 13-2345-3
+always \u575B 124-1236-2
+always \u575C 14-16-5
+always \u575D 135-345-5
+always \u575E 34-5
+always \u575F 12345-136-2
+always \u5760 1-1246-5
+always \u5761 1234-126-3
+always \u5762 1234-1236-4
+always \u5763 124-1346-2
+always \u5764 123-123456-3
+always \u5765 245-1256-3
+always \u5766 124-1236-4
+always \u5767 1-156-3
+always \u5768 124-25-2
+always \u5769 13-1236-3
+always \u576A 1234-13456-2
+always \u576B 145-2345-5
+always \u576C 13-35-5
+always \u576D 1345-16-2
+always \u576E 124-2456-2
+always \u576F 1234-356-3
+always \u5770 13-235-3
+always \u5771 46-4
+always \u5772 12345-126-2
+always \u5773 146-3
+always \u5774 14-34-5
+always \u5775 245-234-3
+always \u5776 134-34-5
+always \u5777 123-2346-4
+always \u5778 13-12356-5
+always \u5779 15-236-5
+always \u577A 135-345-2
+always \u577B 12-156-2
+always \u577C 12-2346-5
+always \u577D 14-13456-2
+always \u577E 1-34-5
+always \u577F 12345-34-5
+always \u5780 1235-34-3
+always \u5781 1-156-5
+always \u5782 12-1246-2
+always \u5783 14-2346-5
+always \u5784 14-12346-4
+always \u5785 14-12346-4
+always \u5786 14-34-2
+always \u5787 146-3
+always \u5788 145-2456-5
+always \u5789 1234-146-2
+always \u578A 1345-1456-2
+always \u578B 15-13456-2
+always \u578C 124-12346-5
+always \u578D 13-16-5
+always \u578E 123-2346-5
+always \u578F 14-34-5
+always \u5790 245-156-2
+always \u5791 12-156-4
+always \u5792 14-356-4
+always \u5793 13-2456-3
+always \u5794 1456-3
+always \u5795 1235-12356-5
+always \u5796 145-1246-3
+always \u5797 1-146-5
+always \u5798 12345-34-2
+always \u5799 13-456-3
+always \u579A 246-2
+always \u579B 145-25-5
+always \u579C 145-25-4
+always \u579D 13-1246-4
+always \u579E 12-345-2
+always \u579F 46-2
+always \u57A0 1456-2
+always \u57A1 12345-345-2
+always \u57A2 13-12356-5
+always \u57A3 45-2
+always \u57A4 145-346-2
+always \u57A5 15-346-2
+always \u57A6 123-136-4
+always \u57A7 24-1346-4
+always \u57A8 24-12356-4
+always \u57A9 2346-5
+always \u57AA 135-1456-5
+always \u57AB 145-2345-5
+always \u57AC 1235-12346-2
+always \u57AD 23456-5
+always \u57AE 123-35-4
+always \u57AF 145-345-3
+always \u57B0 124-345-4
+always \u57B1 145-1346-5
+always \u57B2 123-2456-4
+always \u57B3 1234-1346-2
+always \u57B4 1345-146-4
+always \u57B5 1236-3
+always \u57B6 15-13456-3
+always \u57B7 15-2345-5
+always \u57B8 1235-12456-2
+always \u57B9 135-1346-3
+always \u57BA 12345-34-2
+always \u57BB 135-345-5
+always \u57BC 16-5
+always \u57BD 1456-5
+always \u57BE 1236-5
+always \u57BF 15-1256-5
+always \u57C0 12-1246-2
+always \u57C1 245-136-2
+always \u57C2 13-1356-4
+always \u57C3 2456-3
+always \u57C4 12345-1356-3
+always \u57C5 12345-1346-2
+always \u57C6 245-236-5
+always \u57C7 235-4
+always \u57C8 13-256-5
+always \u57C9 13-23456-2
+always \u57CA 145-16-5
+always \u57CB 134-2456-2
+always \u57CC 14-1346-5
+always \u57CD 15-45-5
+always \u57CE 12-1356-2
+always \u57CF 2345-2
+always \u57D0 13-1456-3
+always \u57D1 1-2346-2
+always \u57D2 14-346-5
+always \u57D3 14-346-5
+always \u57D4 1234-34-4
+always \u57D5 12-1356-2
+always \u57D6 12345-345-3
+always \u57D7 135-34-5
+always \u57D8 24-156-2
+always \u57D9 15-256-3
+always \u57DA 13-25-3
+always \u57DB 13-235-3
+always \u57DC 346-4
+always \u57DD 1345-2345-5
+always \u57DE 145-16-4
+always \u57DF 1256-5
+always \u57E0 135-34-5
+always \u57E1 23456-5
+always \u57E2 13-45-4
+always \u57E3 15-1246-5
+always \u57E4 1234-16-2
+always \u57E5 12-1356-3
+always \u57E6 12456-4
+always \u57E7 13-1256-5
+always \u57E8 14-123456-2
+always \u57E9 1-1356-3
+always \u57EA 123-12346-3
+always \u57EB 12-12346-4
+always \u57EC 145-12346-3
+always \u57ED 145-2456-5
+always \u57EE 124-1236-5
+always \u57EF 1236-4
+always \u57F0 245-2456-5
+always \u57F1 24-34-2
+always \u57F2 135-1356-4
+always \u57F3 123-1236-4
+always \u57F4 1-156-2
+always \u57F5 145-25-4
+always \u57F6 16-5
+always \u57F7 1-156-2
+always \u57F8 16-5
+always \u57F9 1234-356-2
+always \u57FA 13-16-3
+always \u57FB 1-123456-4
+always \u57FC 245-16-2
+always \u57FD 15-146-5
+always \u57FE 13-1256-5
+always \u57FF 1345-16-2
+always \u5800 123-34-3
+always \u5801 123-2346-4
+always \u5802 124-1346-2
+always \u5803 123-123456-3
+always \u5804 1345-16-2
+always \u5805 13-2345-3
+always \u5806 145-1246-3
+always \u5807 13-1456-4
+always \u5808 13-1346-3
+always \u5809 1256-5
+always \u580A 2346-5
+always \u580B 1234-1356-2
+always \u580C 13-34-5
+always \u580D 124-34-5
+always \u580E 14-13456-2
+always \u580F 12345-1346-3
+always \u5810 2456-2
+always \u5811 245-2345-5
+always \u5812 123-123456-3
+always \u5813 1236-5
+always \u5814 12-136-3
+always \u5815 145-25-5
+always \u5816 1345-146-4
+always \u5817 124-34-3
+always \u5818 12-1356-2
+always \u5819 1456-3
+always \u581A 1235-123456-2
+always \u581B 135-16-5
+always \u581C 14-2345-5
+always \u581D 13-25-3
+always \u581E 145-346-2
+always \u581F 1-12456-5
+always \u5820 1235-12356-5
+always \u5821 135-146-4
+always \u5822 135-146-4
+always \u5823 1256-2
+always \u5824 124-16-2
+always \u5825 134-12356-2
+always \u5826 13-346-3
+always \u5827 1245-12456-2
+always \u5828 346-5
+always \u5829 13-1356-5
+always \u582A 123-1236-3
+always \u582B 125-12346-3
+always \u582C 1256-2
+always \u582D 1235-456-2
+always \u582E 2346-5
+always \u582F 246-2
+always \u5830 2345-5
+always \u5831 135-146-5
+always \u5832 13-16-2
+always \u5833 134-356-2
+always \u5834 12-1346-4
+always \u5835 145-34-4
+always \u5836 124-25-2
+always \u5837 1456-5
+always \u5838 12345-1356-2
+always \u5839 1-12346-5
+always \u583A 13-346-5
+always \u583B 1-136-3
+always \u583C 12345-1356-3
+always \u583D 13-1346-3
+always \u583E 12-12456-4
+always \u583F 13-2345-4
+always \u5840 1234-13456-2
+always \u5841 14-356-4
+always \u5842 15-46-5
+always \u5843 1235-456-3
+always \u5844 14-1356-2
+always \u5845 145-12456-5
+always \u5846 12456-3
+always \u5847 15-45-3
+always \u5848 13-16-5
+always \u5849 13-16-2
+always \u584A 123-2356-5
+always \u584B 13456-2
+always \u584C 124-345-3
+always \u584D 12-1356-2
+always \u584E 235-4
+always \u584F 123-2456-4
+always \u5850 15-34-5
+always \u5851 15-34-5
+always \u5852 24-156-2
+always \u5853 134-16-5
+always \u5854 124-345-4
+always \u5855 12346-4
+always \u5856 12-1356-2
+always \u5857 124-34-2
+always \u5858 124-1346-2
+always \u5859 245-236-5
+always \u585A 1-12346-4
+always \u585B 14-16-5
+always \u585C 1234-1356-2
+always \u585D 135-1346-5
+always \u585E 15-2456-5
+always \u585F 125-1346-5
+always \u5860 145-1246-3
+always \u5861 124-2345-2
+always \u5862 34-5
+always \u5863 12-1356-4
+always \u5864 15-256-3
+always \u5865 13-2346-2
+always \u5866 1-136-5
+always \u5867 2456-5
+always \u5868 13-12346-3
+always \u5869 2345-2
+always \u586A 123-1236-4
+always \u586B 124-2345-2
+always \u586C 45-2
+always \u586D 123456-3
+always \u586E 15-346-5
+always \u586F 14-234-5
+always \u5870 1235-2456-4
+always \u5871 14-1346-4
+always \u5872 12-1346-4
+always \u5873 1234-1356-2
+always \u5874 135-1356-5
+always \u5875 12-136-2
+always \u5876 14-34-5
+always \u5877 14-34-4
+always \u5878 12356-4
+always \u5879 245-2345-5
+always \u587A 134-356-2
+always \u587B 134-126-5
+always \u587C 1-12456-3
+always \u587D 24-456-4
+always \u587E 24-34-2
+always \u587F 14-12356-4
+always \u5880 12-156-2
+always \u5881 134-1236-5
+always \u5882 135-246-3
+always \u5883 13-13456-5
+always \u5884 245-16-3
+always \u5885 24-34-5
+always \u5886 145-16-5
+always \u5887 1-1346-5
+always \u5888 123-1236-5
+always \u5889 235-3
+always \u588A 145-2345-5
+always \u588B 12-136-4
+always \u588C 1-156-3
+always \u588D 15-16-5
+always \u588E 13-25-3
+always \u588F 245-46-4
+always \u5890 13-1456-5
+always \u5891 145-16-3
+always \u5892 24-1346-3
+always \u5893 134-34-5
+always \u5894 245-1246-3
+always \u5895 2345-5
+always \u5896 124-345-4
+always \u5897 125-1356-3
+always \u5898 245-16-2
+always \u5899 245-46-2
+always \u589A 14-46-2
+always \u589B 1246-5
+always \u589C 1-1246-5
+always \u589D 245-246-3
+always \u589E 125-1356-3
+always \u589F 15-1256-3
+always \u58A0 24-1236-5
+always \u58A1 24-1236-5
+always \u58A2 135-345-2
+always \u58A3 1234-34-2
+always \u58A4 124-1246-2
+always \u58A5 145-12346-4
+always \u58A6 12345-1236-2
+always \u58A7 245-236-5
+always \u58A8 134-126-5
+always \u58A9 145-123456-3
+always \u58AA 145-123456-3
+always \u58AB 125-123456-3
+always \u58AC 145-16-5
+always \u58AD 24-1356-5
+always \u58AE 145-25-5
+always \u58AF 145-25-5
+always \u58B0 124-1236-2
+always \u58B1 145-1356-5
+always \u58B2 34-4
+always \u58B3 12345-136-2
+always \u58B4 1235-456-2
+always \u58B5 124-1236-2
+always \u58B6 145-345-3
+always \u58B7 346-5
+always \u58B8 1-34-5
+always \u58B9 13-2345-5
+always \u58BA 146-5
+always \u58BB 245-46-2
+always \u58BC 13-16-3
+always \u58BD 245-246-3
+always \u58BE 123-136-4
+always \u58BF 16-5
+always \u58C0 1234-16-2
+always \u58C1 135-16-5
+always \u58C2 145-2345-5
+always \u58C3 13-46-3
+always \u58C4 346-4
+always \u58C5 235-3
+always \u58C6 15-236-2
+always \u58C7 124-1236-2
+always \u58C8 14-1236-4
+always \u58C9 13-1256-5
+always \u58CA 1235-2356-5
+always \u58CB 145-1346-5
+always \u58CC 1245-1346-4
+always \u58CD 245-2345-5
+always \u58CE 15-256-3
+always \u58CF 13-2345-5
+always \u58D0 15-16-4
+always \u58D1 1235-25-5
+always \u58D2 2456-5
+always \u58D3 23456-3
+always \u58D4 145-146-4
+always \u58D5 1235-146-2
+always \u58D6 1245-12456-2
+always \u58D7 13-1456-5
+always \u58D8 14-356-4
+always \u58D9 123-456-5
+always \u58DA 14-34-2
+always \u58DB 2345-2
+always \u58DC 124-1236-2
+always \u58DD 1246-4
+always \u58DE 1235-2356-5
+always \u58DF 14-12346-4
+always \u58E0 14-12346-4
+always \u58E1 1245-1246-5
+always \u58E2 14-16-5
+always \u58E3 14-1456-2
+always \u58E4 1245-1346-4
+always \u58E5 12-1236-2
+always \u58E6 15-256-3
+always \u58E7 2345-2
+always \u58E8 14-356-2
+always \u58E9 135-345-5
+always \u58EA 12456-3
+always \u58EB 24-156-5
+always \u58EC 1245-136-2
+always \u58ED 15-1236-3
+always \u58EE 1-456-5
+always \u58EF 1-456-5
+always \u58F0 24-1356-3
+always \u58F1 16-3
+always \u58F2 134-2456-5
+always \u58F3 123-2346-2
+always \u58F4 1-34-5
+always \u58F5 1-456-5
+always \u58F6 12345-34-2
+always \u58F7 1235-34-2
+always \u58F8 123-123456-4
+always \u58F9 16-3
+always \u58FA 1235-34-2
+always \u58FB 15-1256-5
+always \u58FC 123-123456-4
+always \u58FD 24-12356-5
+always \u58FE 134-1346-4
+always \u58FF 245-123456-2
+always \u5900 24-12356-5
+always \u5901 16-3
+always \u5902 1-156-4
+always \u5903 13-34-3
+always \u5904 12-34-5
+always \u5905 13-46-5
+always \u5906 12345-1356-2
+always \u5907 135-356-5
+always \u5908 1-2456-3
+always \u5909 135-2345-5
+always \u590A 15-1246-3
+always \u590B 245-256-3
+always \u590C 14-13456-2
+always \u590D 12345-34-5
+always \u590E 125-25-5
+always \u590F 15-23456-5
+always \u5910 15-235-5
+always \u5911 15-346-5
+always \u5912 1345-146-2
+always \u5913 15-23456-5
+always \u5914 123-1246-2
+always \u5915 15-16-5
+always \u5916 2356-5
+always \u5917 45-5
+always \u5918 134-146-4
+always \u5919 15-34-5
+always \u591A 145-25-3
+always \u591B 145-25-3
+always \u591C 346-5
+always \u591D 245-13456-2
+always \u591E 12356-2
+always \u591F 13-12356-5
+always \u5920 13-12356-5
+always \u5921 245-16-5
+always \u5922 134-1356-5
+always \u5923 134-1356-5
+always \u5924 1456-2
+always \u5925 1235-25-4
+always \u5926 12-136-5
+always \u5927 145-345-5
+always \u5928 125-2346-5
+always \u5929 124-2345-3
+always \u592A 124-2456-5
+always \u592B 12345-34-3
+always \u592C 13-2356-5
+always \u592D 246-3
+always \u592E 46-3
+always \u592F 1235-1346-3
+always \u5930 13-146-4
+always \u5931 24-156-3
+always \u5932 135-136-4
+always \u5933 124-2456-5
+always \u5934 124-12356-2
+always \u5935 2345-4
+always \u5936 135-16-4
+always \u5937 16-2
+always \u5938 123-35-3
+always \u5939 13-23456-2
+always \u593A 145-25-2
+always \u593B 1235-35-5
+always \u593C 13-456-4
+always \u593D 256-5
+always \u593E 13-23456-2
+always \u593F 1234-345-3
+always \u5940 136-3
+always \u5941 14-2345-2
+always \u5942 1235-12456-5
+always \u5943 145-16-5
+always \u5944 2345-4
+always \u5945 1234-146-5
+always \u5946 245-45-4
+always \u5947 245-16-2
+always \u5948 1345-2456-5
+always \u5949 12345-1356-5
+always \u594A 15-346-2
+always \u594B 12345-136-5
+always \u594C 145-2345-4
+always \u594D 246-4
+always \u594E 123-1246-2
+always \u594F 125-12356-5
+always \u5950 1235-12456-5
+always \u5951 245-16-5
+always \u5952 123-2456-3
+always \u5953 24-2346-3
+always \u5954 135-136-3
+always \u5955 16-5
+always \u5956 13-46-4
+always \u5957 124-146-5
+always \u5958 125-1346-5
+always \u5959 135-136-4
+always \u595A 15-16-3
+always \u595B 15-46-4
+always \u595C 12345-356-4
+always \u595D 145-246-3
+always \u595E 15-256-5
+always \u595F 123-1356-3
+always \u5960 145-2345-5
+always \u5961 146-5
+always \u5962 24-2346-3
+always \u5963 12346-4
+always \u5964 1234-1236-4
+always \u5965 146-5
+always \u5966 34-5
+always \u5967 146-5
+always \u5968 13-46-4
+always \u5969 14-2345-2
+always \u596A 145-25-2
+always \u596B 256-3
+always \u596C 13-46-4
+always \u596D 24-156-5
+always \u596E 12345-136-5
+always \u596F 1235-25-5
+always \u5970 135-356-5
+always \u5971 14-2345-2
+always \u5972 145-25-2
+always \u5973 1345-1256-4
+always \u5974 1345-34-2
+always \u5975 145-13456-3
+always \u5976 1345-2456-4
+always \u5977 245-2345-3
+always \u5978 13-2345-3
+always \u5979 1248-345-3
+always \u597A 13-234-4
+always \u597B 1345-1236-2
+always \u597C 12-345-5
+always \u597D 1235-146-4
+always \u597E 15-2345-3
+always \u597F 12345-1236-5
+always \u5980 13-16-4
+always \u5981 24-25-5
+always \u5982 1245-34-2
+always \u5983 12345-356-3
+always \u5984 456-5
+always \u5985 1235-12346-2
+always \u5986 1-456-3
+always \u5987 12345-34-5
+always \u5988 134-345-3
+always \u5989 145-1236-3
+always \u598A 1245-136-5
+always \u598B 12345-34-3
+always \u598C 13-13456-5
+always \u598D 2345-2
+always \u598E 1235-2456-5
+always \u598F 123456-5
+always \u5990 1-12346-3
+always \u5991 1234-345-3
+always \u5992 145-34-5
+always \u5993 13-16-5
+always \u5994 123-1356-3
+always \u5995 1-12346-5
+always \u5996 246-3
+always \u5997 13-1456-5
+always \u5998 256-2
+always \u5999 134-246-5
+always \u599A 1234-356-3
+always \u599B 12-156-3
+always \u599C 236-5
+always \u599D 1-456-3
+always \u599E 1345-234-3
+always \u599F 2345-5
+always \u59A0 1345-345-5
+always \u59A1 15-1456-3
+always \u59A2 12345-136-2
+always \u59A3 135-16-4
+always \u59A4 1256-2
+always \u59A5 124-25-4
+always \u59A6 12345-1356-3
+always \u59A7 45-2
+always \u59A8 12345-1346-2
+always \u59A9 34-4
+always \u59AA 1256-5
+always \u59AB 13-1246-3
+always \u59AC 145-34-5
+always \u59AD 135-345-2
+always \u59AE 1345-16-2
+always \u59AF 1-12356-2
+always \u59B0 1-25-2
+always \u59B1 1-146-3
+always \u59B2 145-345-2
+always \u59B3 13458-16-4
+always \u59B4 45-5
+always \u59B5 124-12356-4
+always \u59B6 15-45-2
+always \u59B7 1-156-2
+always \u59B8 2346-3
+always \u59B9 134-356-5
+always \u59BA 134-126-5
+always \u59BB 245-16-3
+always \u59BC 135-16-5
+always \u59BD 24-136-3
+always \u59BE 245-346-5
+always \u59BF 2346-3
+always \u59C0 1235-2346-2
+always \u59C1 15-1256-4
+always \u59C2 12345-345-2
+always \u59C3 1-1356-3
+always \u59C4 134-1456-2
+always \u59C5 135-1236-5
+always \u59C6 134-34-4
+always \u59C7 12345-34-3
+always \u59C8 14-13456-2
+always \u59C9 13-346-4
+always \u59CA 13-346-4
+always \u59CB 24-156-4
+always \u59CC 1245-1236-4
+always \u59CD 24-1236-3
+always \u59CE 46-3
+always \u59CF 1345-1236-2
+always \u59D0 13-346-4
+always \u59D1 13-34-3
+always \u59D2 15-156-5
+always \u59D3 15-13456-5
+always \u59D4 1246-4
+always \u59D5 125-156-3
+always \u59D6 13-1256-5
+always \u59D7 24-1236-3
+always \u59D8 1234-1456-3
+always \u59D9 1245-136-5
+always \u59DA 246-2
+always \u59DB 124-12346-4
+always \u59DC 13-46-3
+always \u59DD 24-34-3
+always \u59DE 13-16-2
+always \u59DF 13-2456-3
+always \u59E0 24-1346-5
+always \u59E1 1235-35-2
+always \u59E2 13-45-3
+always \u59E3 13-246-4
+always \u59E4 13-12356-5
+always \u59E5 14-146-4
+always \u59E6 13-2345-3
+always \u59E7 13-2345-3
+always \u59E8 16-2
+always \u59E9 1345-2345-5
+always \u59EA 1-156-2
+always \u59EB 1-136-4
+always \u59EC 13-16-3
+always \u59ED 15-2345-5
+always \u59EE 1235-1356-2
+always \u59EF 13-456-3
+always \u59F0 13-256-3
+always \u59F1 123-35-3
+always \u59F2 2345-5
+always \u59F3 134-13456-4
+always \u59F4 14-346-5
+always \u59F5 1234-356-5
+always \u59F6 2346-5
+always \u59F7 234-5
+always \u59F8 2345-2
+always \u59F9 12-345-5
+always \u59FA 15-2345-4
+always \u59FB 1456-3
+always \u59FC 24-156-2
+always \u59FD 13-1246-4
+always \u59FE 245-45-3
+always \u59FF 125-156-3
+always \u5A00 15-12346-3
+always \u5A01 1246-3
+always \u5A02 1235-12346-2
+always \u5A03 35-2
+always \u5A04 14-12356-2
+always \u5A05 23456-5
+always \u5A06 1245-146-2
+always \u5A07 13-246-3
+always \u5A08 14-12456-2
+always \u5A09 1234-13456-3
+always \u5A0A 15-2345-5
+always \u5A0B 24-146-5
+always \u5A0C 14-16-4
+always \u5A0D 12-1356-2
+always \u5A0E 15-246-5
+always \u5A0F 134-1346-2
+always \u5A10 12345-34-3
+always \u5A11 15-25-3
+always \u5A12 34-4
+always \u5A13 1246-4
+always \u5A14 123-2346-5
+always \u5A15 14-2456-5
+always \u5A16 12-25-5
+always \u5A17 124-13456-4
+always \u5A18 1345-46-2
+always \u5A19 15-13456-2
+always \u5A1A 1345-1236-2
+always \u5A1B 1256-2
+always \u5A1C 1345-345-5
+always \u5A1D 1234-356-3
+always \u5A1E 15-1246-3
+always \u5A1F 13-45-3
+always \u5A20 24-136-3
+always \u5A21 1-156-5
+always \u5A22 1235-1236-2
+always \u5A23 145-16-5
+always \u5A24 1-456-3
+always \u5A25 2346-2
+always \u5A26 1234-1456-2
+always \u5A27 124-1246-5
+always \u5A28 1235-1236-5
+always \u5A29 134-2345-4
+always \u5A2A 34-2
+always \u5A2B 2345-2
+always \u5A2C 34-4
+always \u5A2D 15-16-3
+always \u5A2E 2345-2
+always \u5A2F 1256-2
+always \u5A30 15-156-5
+always \u5A31 1256-2
+always \u5A32 35-3
+always \u5A33 14-16-5
+always \u5A34 15-2345-2
+always \u5A35 13-1256-3
+always \u5A36 245-1256-4
+always \u5A37 24-1246-5
+always \u5A38 245-16-3
+always \u5A39 15-2345-2
+always \u5A3A 1-1246-3
+always \u5A3B 145-12346-3
+always \u5A3C 12-1346-3
+always \u5A3D 14-34-5
+always \u5A3E 2456-4
+always \u5A3F 2346-3
+always \u5A40 2346-3
+always \u5A41 14-12356-2
+always \u5A42 134-2345-2
+always \u5A43 245-12346-2
+always \u5A44 1234-12356-4
+always \u5A45 13-1256-2
+always \u5A46 1234-126-2
+always \u5A47 245-2456-4
+always \u5A48 14-13456-2
+always \u5A49 12456-4
+always \u5A4A 135-246-4
+always \u5A4B 15-246-3
+always \u5A4C 24-34-4
+always \u5A4D 245-16-4
+always \u5A4E 1235-1246-3
+always \u5A4F 12345-34-5
+always \u5A50 25-4
+always \u5A51 25-4
+always \u5A52 124-1236-2
+always \u5A53 12345-356-3
+always \u5A54 12345-356-3
+always \u5A55 13-346-2
+always \u5A56 124-2345-3
+always \u5A57 1345-16-2
+always \u5A58 245-45-2
+always \u5A59 13-13456-5
+always \u5A5A 1235-123456-3
+always \u5A5B 13-13456-3
+always \u5A5C 245-2345-3
+always \u5A5D 145-2345-5
+always \u5A5E 15-13456-5
+always \u5A5F 1235-34-5
+always \u5A60 12456-3
+always \u5A61 14-2456-2
+always \u5A62 135-16-5
+always \u5A63 1456-3
+always \u5A64 1-12356-3
+always \u5A65 12-25-5
+always \u5A66 12345-34-5
+always \u5A67 245-13456-5
+always \u5A68 14-123456-2
+always \u5A69 1236-5
+always \u5A6A 14-1236-2
+always \u5A6B 123-123456-3
+always \u5A6C 1456-2
+always \u5A6D 23456-5
+always \u5A6E 13-1256-3
+always \u5A6F 14-16-5
+always \u5A70 145-2345-4
+always \u5A71 15-2345-2
+always \u5A72 12345-356-3
+always \u5A73 1235-35-5
+always \u5A74 13456-3
+always \u5A75 12-1236-2
+always \u5A76 24-136-4
+always \u5A77 124-13456-2
+always \u5A78 46-2
+always \u5A79 246-4
+always \u5A7A 34-5
+always \u5A7B 1345-1236-5
+always \u5A7C 1245-25-5
+always \u5A7D 13-23456-4
+always \u5A7E 124-12356-3
+always \u5A7F 15-1256-5
+always \u5A80 1256-2
+always \u5A81 1246-3
+always \u5A82 124-16-2
+always \u5A83 1245-12356-2
+always \u5A84 134-356-4
+always \u5A85 145-1236-3
+always \u5A86 1245-12456-4
+always \u5A87 245-1456-3
+always \u5A88 1235-1246-3
+always \u5A89 34-3
+always \u5A8A 245-2345-2
+always \u5A8B 12-123456-3
+always \u5A8C 134-246-2
+always \u5A8D 12345-34-5
+always \u5A8E 13-346-4
+always \u5A8F 145-12456-3
+always \u5A90 15-16-3
+always \u5A91 1-12346-5
+always \u5A92 134-356-2
+always \u5A93 1235-456-2
+always \u5A94 134-2345-2
+always \u5A95 1236-3
+always \u5A96 13456-3
+always \u5A97 15-45-3
+always \u5A98 13-346-3
+always \u5A99 1246-3
+always \u5A9A 134-356-5
+always \u5A9B 45-2
+always \u5A9C 1-136-3
+always \u5A9D 245-234-3
+always \u5A9E 124-16-2
+always \u5A9F 15-346-5
+always \u5AA0 145-25-5
+always \u5AA1 14-2345-5
+always \u5AA2 134-146-5
+always \u5AA3 1245-1236-4
+always \u5AA4 15-156-3
+always \u5AA5 1234-2345-3
+always \u5AA6 1246-5
+always \u5AA7 35-3
+always \u5AA8 13-234-5
+always \u5AA9 1235-34-2
+always \u5AAA 146-4
+always \u5AAB 13-346-2
+always \u5AAC 135-146-4
+always \u5AAD 15-1256-3
+always \u5AAE 124-12356-3
+always \u5AAF 13-1246-3
+always \u5AB0 125-12356-3
+always \u5AB1 246-2
+always \u5AB2 1234-16-5
+always \u5AB3 15-16-2
+always \u5AB4 45-2
+always \u5AB5 13456-5
+always \u5AB6 1245-12346-2
+always \u5AB7 1245-34-5
+always \u5AB8 12-156-3
+always \u5AB9 14-234-2
+always \u5ABA 134-356-4
+always \u5ABB 1234-1236-2
+always \u5ABC 146-4
+always \u5ABD 134-345-3
+always \u5ABE 13-12356-5
+always \u5ABF 123-1246-5
+always \u5AC0 245-1456-2
+always \u5AC1 13-23456-5
+always \u5AC2 15-146-4
+always \u5AC3 1-136-3
+always \u5AC4 45-2
+always \u5AC5 12-345-3
+always \u5AC6 235-2
+always \u5AC7 134-13456-2
+always \u5AC8 13456-3
+always \u5AC9 13-16-2
+always \u5ACA 15-34-5
+always \u5ACB 1345-246-4
+always \u5ACC 15-2345-2
+always \u5ACD 124-146-3
+always \u5ACE 1234-1346-2
+always \u5ACF 14-1346-2
+always \u5AD0 1345-146-4
+always \u5AD1 135-246-2
+always \u5AD2 2456-5
+always \u5AD3 1234-16-5
+always \u5AD4 1234-1456-2
+always \u5AD5 16-5
+always \u5AD6 1234-246-2
+always \u5AD7 1256-5
+always \u5AD8 14-356-2
+always \u5AD9 15-45-2
+always \u5ADA 134-1236-5
+always \u5ADB 16-3
+always \u5ADC 1-1346-3
+always \u5ADD 123-1346-3
+always \u5ADE 235-3
+always \u5ADF 1345-16-5
+always \u5AE0 14-16-2
+always \u5AE1 145-16-2
+always \u5AE2 13-1246-3
+always \u5AE3 2345-3
+always \u5AE4 13-1456-5
+always \u5AE5 1-12456-3
+always \u5AE6 12-1346-2
+always \u5AE7 245-2346-5
+always \u5AE8 1235-1236-3
+always \u5AE9 1345-136-5
+always \u5AEA 14-146-5
+always \u5AEB 134-126-2
+always \u5AEC 1-2346-3
+always \u5AED 1235-34-5
+always \u5AEE 1235-34-5
+always \u5AEF 146-5
+always \u5AF0 1345-136-5
+always \u5AF1 245-46-2
+always \u5AF2 134-345-2
+always \u5AF3 1234-346-5
+always \u5AF4 13-34-3
+always \u5AF5 34-4
+always \u5AF6 245-246-2
+always \u5AF7 124-25-4
+always \u5AF8 1-1236-4
+always \u5AF9 134-146-2
+always \u5AFA 15-2345-2
+always \u5AFB 15-2345-2
+always \u5AFC 134-126-5
+always \u5AFD 14-246-2
+always \u5AFE 14-2345-2
+always \u5AFF 1235-35-5
+always \u5B00 13-1246-3
+always \u5B01 145-1356-3
+always \u5B02 1-156-3
+always \u5B03 15-1256-3
+always \u5B04 16-3
+always \u5B05 1235-35-5
+always \u5B06 15-16-3
+always \u5B07 1235-1246-5
+always \u5B08 1245-146-4
+always \u5B09 15-16-3
+always \u5B0A 2345-5
+always \u5B0B 12-1236-2
+always \u5B0C 13-246-3
+always \u5B0D 134-356-4
+always \u5B0E 12345-1236-5
+always \u5B0F 12345-1236-3
+always \u5B10 15-2345-3
+always \u5B11 16-5
+always \u5B12 1246-5
+always \u5B13 13-246-5
+always \u5B14 12345-34-5
+always \u5B15 24-156-5
+always \u5B16 135-16-5
+always \u5B17 24-1236-5
+always \u5B18 15-1246-5
+always \u5B19 245-46-2
+always \u5B1A 14-2345-2
+always \u5B1B 1235-12456-2
+always \u5B1C 15-1456-3
+always \u5B1D 1345-246-4
+always \u5B1E 145-12346-4
+always \u5B1F 16-5
+always \u5B20 245-1236-2
+always \u5B21 2456-5
+always \u5B22 1345-46-2
+always \u5B23 1345-1356-2
+always \u5B24 134-345-3
+always \u5B25 124-246-4
+always \u5B26 12-12356-2
+always \u5B27 13-1456-5
+always \u5B28 245-156-2
+always \u5B29 1256-2
+always \u5B2A 1234-1456-2
+always \u5B2B 14-12346-2
+always \u5B2C 1245-34-2
+always \u5B2D 1345-2456-4
+always \u5B2E 2345-3
+always \u5B2F 124-2456-2
+always \u5B30 13456-3
+always \u5B31 245-1236-2
+always \u5B32 1345-246-4
+always \u5B33 236-5
+always \u5B34 13456-2
+always \u5B35 134-2345-2
+always \u5B36 135-16-2
+always \u5B37 134-126-2
+always \u5B38 24-136-4
+always \u5B39 15-13456-5
+always \u5B3A 1345-16-5
+always \u5B3B 145-34-2
+always \u5B3C 14-234-4
+always \u5B3D 45-3
+always \u5B3E 14-1236-4
+always \u5B3F 2345-4
+always \u5B40 24-456-3
+always \u5B41 14-13456-2
+always \u5B42 13-246-4
+always \u5B43 1345-46-2
+always \u5B44 14-1236-4
+always \u5B45 15-2345-3
+always \u5B46 13456-3
+always \u5B47 24-456-3
+always \u5B48 1235-1246-5
+always \u5B49 245-45-2
+always \u5B4A 134-16-4
+always \u5B4B 14-16-2
+always \u5B4C 14-12456-2
+always \u5B4D 2345-2
+always \u5B4E 1-34-4
+always \u5B4F 14-1236-4
+always \u5B50 125-156-1
+always \u5B51 13-346-2
+always \u5B52 13-236-2
+always \u5B53 13-236-2
+always \u5B54 123-12346-4
+always \u5B55 256-5
+always \u5B56 125-156-3
+always \u5B57 125-156-5
+always \u5B58 245-123456-2
+always \u5B59 15-123456-3
+always \u5B5A 12345-34-2
+always \u5B5B 135-126-2
+always \u5B5C 125-156-3
+always \u5B5D 15-246-5
+always \u5B5E 15-1456-5
+always \u5B5F 134-1356-5
+always \u5B60 15-156-5
+always \u5B61 124-2456-3
+always \u5B62 135-146-3
+always \u5B63 13-16-5
+always \u5B64 13-34-3
+always \u5B65 1345-34-2
+always \u5B66 15-236-2
+always \u5B67 234-5
+always \u5B68 1-12456-4
+always \u5B69 1235-2456-2
+always \u5B6A 14-12456-2
+always \u5B6B 15-123456-3
+always \u5B6C 1345-146-3
+always \u5B6D 134-346-3
+always \u5B6E 245-12346-2
+always \u5B6F 245-2345-3
+always \u5B70 24-34-2
+always \u5B71 12-1236-2
+always \u5B72 23456-3
+always \u5B73 125-156-3
+always \u5B74 1345-16-4
+always \u5B75 12345-34-3
+always \u5B76 125-156-3
+always \u5B77 14-16-2
+always \u5B78 15-236-2
+always \u5B79 135-126-5
+always \u5B7A 1245-34-2
+always \u5B7B 1345-2456-2
+always \u5B7C 1345-346-5
+always \u5B7D 1345-346-5
+always \u5B7E 13456-3
+always \u5B7F 14-12456-2
+always \u5B80 134-2345-2
+always \u5B81 1-34-5
+always \u5B82 1245-12346-4
+always \u5B83 124-345-3
+always \u5B84 13-1246-4
+always \u5B85 1-2456-2
+always \u5B86 245-235-2
+always \u5B87 1256-4
+always \u5B88 24-12356-4
+always \u5B89 1236-3
+always \u5B8A 124-34-2
+always \u5B8B 15-12346-5
+always \u5B8C 12456-2
+always \u5B8D 1245-12356-5
+always \u5B8E 246-3
+always \u5B8F 1235-12346-2
+always \u5B90 16-2
+always \u5B91 13-13456-4
+always \u5B92 1-123456-3
+always \u5B93 134-16-5
+always \u5B94 1-34-4
+always \u5B95 145-1346-5
+always \u5B96 1235-12346-2
+always \u5B97 125-12346-3
+always \u5B98 13-12456-3
+always \u5B99 1-12356-5
+always \u5B9A 145-13456-5
+always \u5B9B 12456-4
+always \u5B9C 16-2
+always \u5B9D 135-146-4
+always \u5B9E 24-156-2
+always \u5B9F 24-156-2
+always \u5BA0 12-12346-4
+always \u5BA1 24-136-4
+always \u5BA2 123-2346-5
+always \u5BA3 15-45-3
+always \u5BA4 24-156-5
+always \u5BA5 234-5
+always \u5BA6 1235-12456-5
+always \u5BA7 16-2
+always \u5BA8 124-246-4
+always \u5BA9 24-156-4
+always \u5BAA 15-2345-5
+always \u5BAB 13-12346-3
+always \u5BAC 12-1356-2
+always \u5BAD 245-256-2
+always \u5BAE 13-12346-3
+always \u5BAF 15-246-3
+always \u5BB0 125-2456-4
+always \u5BB1 1-345-5
+always \u5BB2 24-156-2
+always \u5BB3 1235-2456-5
+always \u5BB4 2345-5
+always \u5BB5 15-246-3
+always \u5BB6 13-23456-3
+always \u5BB7 24-136-4
+always \u5BB8 12-136-2
+always \u5BB9 1245-12346-2
+always \u5BBA 1235-456-4
+always \u5BBB 134-16-5
+always \u5BBC 123-12356-5
+always \u5BBD 123-12456-3
+always \u5BBE 135-1456-3
+always \u5BBF 15-34-5
+always \u5BC0 245-2456-4
+always \u5BC1 125-1236-4
+always \u5BC2 13-16-2
+always \u5BC3 45-3
+always \u5BC4 13-16-5
+always \u5BC5 1456-2
+always \u5BC6 134-16-5
+always \u5BC7 123-12356-5
+always \u5BC8 245-13456-3
+always \u5BC9 245-236-5
+always \u5BCA 1-136-3
+always \u5BCB 13-2345-4
+always \u5BCC 12345-34-5
+always \u5BCD 1345-13456-2
+always \u5BCE 135-13456-5
+always \u5BCF 1235-12456-2
+always \u5BD0 134-356-5
+always \u5BD1 245-1456-4
+always \u5BD2 1235-1236-2
+always \u5BD3 1256-5
+always \u5BD4 24-156-2
+always \u5BD5 1345-13456-2
+always \u5BD6 13-1456-5
+always \u5BD7 1345-13456-2
+always \u5BD8 1-156-5
+always \u5BD9 1256-4
+always \u5BDA 135-146-4
+always \u5BDB 123-12456-3
+always \u5BDC 1345-13456-2
+always \u5BDD 245-1456-4
+always \u5BDE 134-126-5
+always \u5BDF 12-345-2
+always \u5BE0 13-1256-5
+always \u5BE1 13-35-4
+always \u5BE2 245-1456-4
+always \u5BE3 1235-34-3
+always \u5BE4 34-5
+always \u5BE5 14-246-2
+always \u5BE6 24-156-2
+always \u5BE7 1345-13456-2
+always \u5BE8 1-2456-5
+always \u5BE9 24-136-4
+always \u5BEA 1246-4
+always \u5BEB 15-346-4
+always \u5BEC 123-12456-3
+always \u5BED 1235-1246-5
+always \u5BEE 14-246-2
+always \u5BEF 13-256-5
+always \u5BF0 1235-12456-2
+always \u5BF1 16-5
+always \u5BF2 16-2
+always \u5BF3 135-146-4
+always \u5BF4 245-1456-5
+always \u5BF5 12-12346-4
+always \u5BF6 135-146-4
+always \u5BF7 12345-1356-3
+always \u5BF8 245-123456-5
+always \u5BF9 145-1246-5
+always \u5BFA 15-156-5
+always \u5BFB 15-256-2
+always \u5BFC 145-146-4
+always \u5BFD 14-1256-5
+always \u5BFE 145-1246-5
+always \u5BFF 24-12356-5
+always \u5C00 1234-126-4
+always \u5C01 12345-1356-3
+always \u5C02 1-12456-3
+always \u5C03 12345-34-3
+always \u5C04 24-2346-5
+always \u5C05 123-2346-5
+always \u5C06 13-46-3
+always \u5C07 13-46-3
+always \u5C08 1-12456-3
+always \u5C09 1246-5
+always \u5C0A 125-123456-3
+always \u5C0B 15-256-2
+always \u5C0C 24-34-5
+always \u5C0D 145-1246-5
+always \u5C0E 145-146-4
+always \u5C0F 15-246-4
+always \u5C10 13-16-3
+always \u5C11 24-146-4
+always \u5C12 156-4
+always \u5C13 156-4
+always \u5C14 156-4
+always \u5C15 1345-2456-4
+always \u5C16 13-2345-3
+always \u5C17 24-34-2
+always \u5C18 12-136-2
+always \u5C19 24-1346-5
+always \u5C1A 24-1346-5
+always \u5C1B 134-126-2
+always \u5C1C 13-345-2
+always \u5C1D 12-1346-2
+always \u5C1E 14-246-5
+always \u5C1F 15-2345-4
+always \u5C20 15-2345-4
+always \u5C21 123-123456-3
+always \u5C22 456-3
+always \u5C23 456-3
+always \u5C24 234-2
+always \u5C25 14-246-5
+always \u5C26 14-246-5
+always \u5C27 246-2
+always \u5C28 134-1346-2
+always \u5C29 456-3
+always \u5C2A 456-3
+always \u5C2B 456-3
+always \u5C2C 13-345-5
+always \u5C2D 246-2
+always \u5C2E 145-25-5
+always \u5C2F 123-1246-5
+always \u5C30 1-12346-4
+always \u5C31 13-234-5
+always \u5C32 13-1236-3
+always \u5C33 13-34-4
+always \u5C34 13-1236-3
+always \u5C35 124-1246-2
+always \u5C36 13-1236-3
+always \u5C37 13-1236-3
+always \u5C38 24-156-3
+always \u5C39 1456-4
+always \u5C3A 12-156-4
+always \u5C3B 123-146-3
+always \u5C3C 1345-16-2
+always \u5C3D 13-1456-5
+always \u5C3E 1246-4
+always \u5C3F 1345-246-5
+always \u5C40 13-1256-2
+always \u5C41 1234-16-5
+always \u5C42 245-1356-2
+always \u5C43 15-16-5
+always \u5C44 135-16-3
+always \u5C45 13-1256-3
+always \u5C46 13-346-5
+always \u5C47 124-2345-2
+always \u5C48 245-1256-3
+always \u5C49 124-16-5
+always \u5C4A 13-346-5
+always \u5C4B 34-3
+always \u5C4C 145-246-4
+always \u5C4D 24-156-3
+always \u5C4E 24-156-4
+always \u5C4F 1234-13456-2
+always \u5C50 13-16-3
+always \u5C51 15-346-5
+always \u5C52 12-136-2
+always \u5C53 15-16-5
+always \u5C54 1345-16-2
+always \u5C55 1-1236-4
+always \u5C56 15-16-3
+always \u5C57 1246-4
+always \u5C58 134-1236-4
+always \u5C59 2346-3
+always \u5C5A 14-12356-5
+always \u5C5B 1234-13456-2
+always \u5C5C 124-16-5
+always \u5C5D 12345-356-5
+always \u5C5E 24-34-4
+always \u5C5F 15-346-5
+always \u5C60 124-34-2
+always \u5C61 14-1256-4
+always \u5C62 14-1256-4
+always \u5C63 15-16-4
+always \u5C64 245-1356-2
+always \u5C65 14-1256-4
+always \u5C66 13-1256-5
+always \u5C67 15-346-5
+always \u5C68 13-1256-5
+always \u5C69 13-236-2
+always \u5C6A 14-246-2
+always \u5C6B 13-236-2
+always \u5C6C 24-34-4
+always \u5C6D 15-16-5
+always \u5C6E 12-2346-5
+always \u5C6F 124-123456-2
+always \u5C70 1345-16-5
+always \u5C71 24-1236-3
+always \u5C72 35-3
+always \u5C73 15-2345-3
+always \u5C74 14-16-5
+always \u5C75 2346-5
+always \u5C76 145-146-3
+always \u5C77 1235-1246-5
+always \u5C78 14-12346-2
+always \u5C79 16-5
+always \u5C7A 245-16-4
+always \u5C7B 1245-136-5
+always \u5C7C 34-5
+always \u5C7D 1235-1236-5
+always \u5C7E 24-136-3
+always \u5C7F 1256-4
+always \u5C80 12-34-3
+always \u5C81 15-1246-5
+always \u5C82 245-16-4
+always \u5C83 1245-136-5
+always \u5C84 236-5
+always \u5C85 135-1236-4
+always \u5C86 246-4
+always \u5C87 1346-2
+always \u5C88 23456-2
+always \u5C89 34-5
+always \u5C8A 13-346-2
+always \u5C8B 2346-5
+always \u5C8C 13-16-2
+always \u5C8D 245-2345-3
+always \u5C8E 12345-136-3
+always \u5C8F 12456-2
+always \u5C90 245-16-2
+always \u5C91 245-136-2
+always \u5C92 245-2345-2
+always \u5C93 245-16-2
+always \u5C94 12-345-5
+always \u5C95 13-346-5
+always \u5C96 245-1256-3
+always \u5C97 13-1346-4
+always \u5C98 15-2345-5
+always \u5C99 146-5
+always \u5C9A 14-1236-2
+always \u5C9B 145-146-4
+always \u5C9C 135-345-3
+always \u5C9D 125-2346-2
+always \u5C9E 125-25-5
+always \u5C9F 46-4
+always \u5CA0 13-1256-5
+always \u5CA1 13-1346-3
+always \u5CA2 123-2346-4
+always \u5CA3 13-12356-4
+always \u5CA4 15-236-5
+always \u5CA5 1234-126-3
+always \u5CA6 14-16-5
+always \u5CA7 124-246-2
+always \u5CA8 245-1256-3
+always \u5CA9 2345-2
+always \u5CAA 12345-34-2
+always \u5CAB 15-234-5
+always \u5CAC 13-23456-4
+always \u5CAD 14-13456-2
+always \u5CAE 124-25-2
+always \u5CAF 1234-356-3
+always \u5CB0 234-4
+always \u5CB1 145-2456-5
+always \u5CB2 123-456-5
+always \u5CB3 236-5
+always \u5CB4 245-1256-3
+always \u5CB5 1235-34-5
+always \u5CB6 1234-126-5
+always \u5CB7 134-1456-2
+always \u5CB8 1236-5
+always \u5CB9 124-246-2
+always \u5CBA 14-13456-2
+always \u5CBB 12-156-2
+always \u5CBC 1234-13456-2
+always \u5CBD 145-12346-3
+always \u5CBE 1-1236-3
+always \u5CBF 123-1246-3
+always \u5CC0 15-234-5
+always \u5CC1 134-146-4
+always \u5CC2 124-12346-2
+always \u5CC3 15-236-2
+always \u5CC4 16-5
+always \u5CC5 135-2345-5
+always \u5CC6 1235-2346-2
+always \u5CC7 123-2346-3
+always \u5CC8 14-25-5
+always \u5CC9 2346-2
+always \u5CCA 12345-34-5
+always \u5CCB 15-256-2
+always \u5CCC 145-346-2
+always \u5CCD 14-34-5
+always \u5CCE 136-4
+always \u5CCF 156-4
+always \u5CD0 13-2456-3
+always \u5CD1 245-45-2
+always \u5CD2 145-12346-5
+always \u5CD3 16-2
+always \u5CD4 134-34-4
+always \u5CD5 24-156-2
+always \u5CD6 1236-3
+always \u5CD7 1246-2
+always \u5CD8 1235-12456-2
+always \u5CD9 1-156-5
+always \u5CDA 134-16-5
+always \u5CDB 14-16-4
+always \u5CDC 13-16-3
+always \u5CDD 124-12346-2
+always \u5CDE 1246-2
+always \u5CDF 234-5
+always \u5CE0 13-34-4
+always \u5CE1 15-23456-2
+always \u5CE2 14-16-4
+always \u5CE3 246-2
+always \u5CE4 13-246-5
+always \u5CE5 1-1356-3
+always \u5CE6 14-12456-2
+always \u5CE7 13-246-3
+always \u5CE8 2346-2
+always \u5CE9 2346-2
+always \u5CEA 1256-5
+always \u5CEB 346-2
+always \u5CEC 135-34-3
+always \u5CED 245-246-5
+always \u5CEE 245-256-3
+always \u5CEF 12345-1356-3
+always \u5CF0 12345-1356-3
+always \u5CF1 1345-146-2
+always \u5CF2 14-16-4
+always \u5CF3 234-2
+always \u5CF4 15-2345-5
+always \u5CF5 1235-12346-2
+always \u5CF6 145-146-4
+always \u5CF7 24-136-3
+always \u5CF8 12-1356-2
+always \u5CF9 124-34-2
+always \u5CFA 13-1356-4
+always \u5CFB 13-256-5
+always \u5CFC 1235-146-5
+always \u5CFD 15-23456-2
+always \u5CFE 1456-3
+always \u5CFF 1256-4
+always \u5D00 14-146-5
+always \u5D01 123-1236-4
+always \u5D02 14-146-2
+always \u5D03 14-2456-2
+always \u5D04 15-2345-4
+always \u5D05 245-236-5
+always \u5D06 123-12346-3
+always \u5D07 12-12346-2
+always \u5D08 12-12346-2
+always \u5D09 124-345-5
+always \u5D0A 14-13456-2
+always \u5D0B 1235-35-2
+always \u5D0C 13-1256-3
+always \u5D0D 14-2456-2
+always \u5D0E 245-16-2
+always \u5D0F 134-1456-2
+always \u5D10 123-123456-3
+always \u5D11 123-123456-3
+always \u5D12 125-34-2
+always \u5D13 13-34-5
+always \u5D14 245-1246-3
+always \u5D15 26-2
+always \u5D16 26-2
+always \u5D17 13-1346-4
+always \u5D18 14-123456-2
+always \u5D19 14-123456-2
+always \u5D1A 14-1356-2
+always \u5D1B 13-236-2
+always \u5D1C 145-25-3
+always \u5D1D 1-1356-3
+always \u5D1E 13-25-3
+always \u5D1F 1456-2
+always \u5D20 145-12346-3
+always \u5D21 1235-1236-2
+always \u5D22 1-1356-3
+always \u5D23 1246-4
+always \u5D24 246-2
+always \u5D25 1234-16-4
+always \u5D26 2345-3
+always \u5D27 15-12346-3
+always \u5D28 13-346-2
+always \u5D29 135-1356-3
+always \u5D2A 125-34-2
+always \u5D2B 13-236-2
+always \u5D2C 145-12346-3
+always \u5D2D 1-1236-4
+always \u5D2E 13-34-5
+always \u5D2F 1456-2
+always \u5D30 125-156-3
+always \u5D31 125-2346-2
+always \u5D32 1235-456-2
+always \u5D33 1256-2
+always \u5D34 1246-3
+always \u5D35 46-2
+always \u5D36 12345-1356-3
+always \u5D37 245-234-2
+always \u5D38 145-123456-5
+always \u5D39 124-16-2
+always \u5D3A 16-4
+always \u5D3B 1-156-5
+always \u5D3C 24-156-5
+always \u5D3D 125-2456-4
+always \u5D3E 246-4
+always \u5D3F 2346-5
+always \u5D40 1-34-5
+always \u5D41 123-1236-3
+always \u5D42 14-1256-5
+always \u5D43 2345-4
+always \u5D44 134-356-4
+always \u5D45 13-1236-3
+always \u5D46 13-16-3
+always \u5D47 13-16-3
+always \u5D48 1235-12456-4
+always \u5D49 124-13456-2
+always \u5D4A 24-1356-5
+always \u5D4B 134-356-2
+always \u5D4C 245-2345-3
+always \u5D4D 34-5
+always \u5D4E 1256-2
+always \u5D4F 125-12346-3
+always \u5D50 14-1236-2
+always \u5D51 1235-2346-2
+always \u5D52 2345-2
+always \u5D53 2345-2
+always \u5D54 1246-4
+always \u5D55 125-12346-3
+always \u5D56 12-345-2
+always \u5D57 15-1246-5
+always \u5D58 1245-12346-2
+always \u5D59 123-2346-3
+always \u5D5A 245-1456-3
+always \u5D5B 1256-2
+always \u5D5C 245-16-2
+always \u5D5D 14-12356-4
+always \u5D5E 124-34-2
+always \u5D5F 145-1246-3
+always \u5D60 15-16-3
+always \u5D61 12346-3
+always \u5D62 245-1346-3
+always \u5D63 124-1346-2
+always \u5D64 13456-2
+always \u5D65 13-346-2
+always \u5D66 2456-2
+always \u5D67 14-234-2
+always \u5D68 34-4
+always \u5D69 15-12346-3
+always \u5D6A 245-246-3
+always \u5D6B 125-156-3
+always \u5D6C 1246-2
+always \u5D6D 135-1356-3
+always \u5D6E 145-2345-3
+always \u5D6F 245-25-2
+always \u5D70 245-2345-4
+always \u5D71 235-4
+always \u5D72 1345-346-5
+always \u5D73 245-25-2
+always \u5D74 13-16-2
+always \u5D75 24-156-2
+always \u5D76 1245-25-5
+always \u5D77 15-12346-4
+always \u5D78 125-12346-3
+always \u5D79 13-46-5
+always \u5D7A 14-246-2
+always \u5D7B 123-1346-3
+always \u5D7C 12-1236-4
+always \u5D7D 145-346-2
+always \u5D7E 245-136-3
+always \u5D7F 145-13456-4
+always \u5D80 124-34-3
+always \u5D81 14-12356-4
+always \u5D82 1-1346-5
+always \u5D83 1-1236-4
+always \u5D84 1-1236-4
+always \u5D85 146-2
+always \u5D86 245-146-2
+always \u5D87 245-1256-3
+always \u5D88 245-46-3
+always \u5D89 1246-4
+always \u5D8A 125-1246-4
+always \u5D8B 145-146-4
+always \u5D8C 145-146-4
+always \u5D8D 15-16-2
+always \u5D8E 1256-5
+always \u5D8F 135-126-2
+always \u5D90 14-12346-2
+always \u5D91 15-46-4
+always \u5D92 245-1356-2
+always \u5D93 135-126-3
+always \u5D94 245-1456-3
+always \u5D95 13-246-3
+always \u5D96 2345-4
+always \u5D97 14-146-2
+always \u5D98 1-1236-5
+always \u5D99 14-1456-2
+always \u5D9A 14-246-2
+always \u5D9B 14-246-2
+always \u5D9C 13-1456-3
+always \u5D9D 145-1356-5
+always \u5D9E 145-25-5
+always \u5D9F 125-123456-3
+always \u5DA0 13-246-5
+always \u5DA1 13-1246-5
+always \u5DA2 246-2
+always \u5DA3 245-246-2
+always \u5DA4 246-2
+always \u5DA5 13-236-2
+always \u5DA6 1-1236-3
+always \u5DA7 16-5
+always \u5DA8 15-236-2
+always \u5DA9 1345-146-2
+always \u5DAA 346-5
+always \u5DAB 346-5
+always \u5DAC 16-2
+always \u5DAD 2346-5
+always \u5DAE 15-2345-4
+always \u5DAF 13-16-2
+always \u5DB0 15-346-5
+always \u5DB1 123-2346-4
+always \u5DB2 15-16-3
+always \u5DB3 145-16-5
+always \u5DB4 146-5
+always \u5DB5 125-1246-5
+always \u5DB6 1246-3
+always \u5DB7 16-2
+always \u5DB8 1245-12346-2
+always \u5DB9 145-146-4
+always \u5DBA 14-13456-4
+always \u5DBB 13-346-2
+always \u5DBC 1256-4
+always \u5DBD 236-5
+always \u5DBE 1456-4
+always \u5DBF 1245-34-3
+always \u5DC0 13-346-2
+always \u5DC1 14-16-5
+always \u5DC2 13-1246-3
+always \u5DC3 14-12346-2
+always \u5DC4 14-12346-2
+always \u5DC5 145-2345-3
+always \u5DC6 13456-2
+always \u5DC7 15-16-3
+always \u5DC8 13-1256-2
+always \u5DC9 12-1236-2
+always \u5DCA 13456-4
+always \u5DCB 123-1246-3
+always \u5DCC 2345-2
+always \u5DCD 1246-2
+always \u5DCE 1345-146-2
+always \u5DCF 245-45-2
+always \u5DD0 12-146-4
+always \u5DD1 245-12456-2
+always \u5DD2 14-12456-2
+always \u5DD3 145-2345-3
+always \u5DD4 145-2345-3
+always \u5DD5 1345-346-5
+always \u5DD6 2345-2
+always \u5DD7 2345-2
+always \u5DD8 2345-4
+always \u5DD9 1345-146-2
+always \u5DDA 2345-4
+always \u5DDB 12-12456-3
+always \u5DDC 123-2356-5
+always \u5DDD 12-12456-3
+always \u5DDE 1-12356-3
+always \u5DDF 1235-456-3
+always \u5DE0 13-13456-3
+always \u5DE1 15-256-2
+always \u5DE2 12-146-2
+always \u5DE3 12-146-2
+always \u5DE4 14-346-5
+always \u5DE5 13-12346-3
+always \u5DE6 125-25-4
+always \u5DE7 245-246-4
+always \u5DE8 13-1256-5
+always \u5DE9 13-12346-4
+always \u5DEA 13-1256-5
+always \u5DEB 34-3
+always \u5DEC 13-34-3
+always \u5DED 135-34-3
+always \u5DEE 12-345-3
+always \u5DEF 245-234-2
+always \u5DF0 245-234-2
+always \u5DF1 13-16-4
+always \u5DF2 16-4
+always \u5DF3 15-156-5
+always \u5DF4 135-345-3
+always \u5DF5 1-156-3
+always \u5DF6 1-146-3
+always \u5DF7 15-46-5
+always \u5DF8 16-2
+always \u5DF9 13-1456-4
+always \u5DFA 15-256-5
+always \u5DFB 13-45-4
+always \u5DFC 12345-345-2
+always \u5DFD 15-256-5
+always \u5DFE 13-1456-3
+always \u5DFF 12345-34-2
+always \u5E00 125-345-3
+always \u5E01 135-16-5
+always \u5E02 24-156-5
+always \u5E03 135-34-5
+always \u5E04 145-13456-3
+always \u5E05 24-2356-5
+always \u5E06 12345-1236-2
+always \u5E07 1345-346-5
+always \u5E08 24-156-3
+always \u5E09 12345-136-3
+always \u5E0A 1234-345-5
+always \u5E0B 1-156-4
+always \u5E0C 15-16-3
+always \u5E0D 1235-34-5
+always \u5E0E 145-1236-5
+always \u5E0F 1246-2
+always \u5E10 1-1346-5
+always \u5E11 124-1346-4
+always \u5E12 145-2456-5
+always \u5E13 35-5
+always \u5E14 1234-356-5
+always \u5E15 1234-345-5
+always \u5E16 124-346-4
+always \u5E17 12345-34-2
+always \u5E18 14-2345-2
+always \u5E19 1-156-5
+always \u5E1A 1-12356-4
+always \u5E1B 135-126-2
+always \u5E1C 1-156-5
+always \u5E1D 145-16-5
+always \u5E1E 134-126-5
+always \u5E1F 16-5
+always \u5E20 16-5
+always \u5E21 1234-13456-2
+always \u5E22 245-23456-5
+always \u5E23 13-45-5
+always \u5E24 1245-34-2
+always \u5E25 24-2356-5
+always \u5E26 145-2456-5
+always \u5E27 1-136-3
+always \u5E28 24-1246-5
+always \u5E29 245-246-5
+always \u5E2A 1-136-3
+always \u5E2B 24-156-3
+always \u5E2C 245-256-2
+always \u5E2D 15-16-2
+always \u5E2E 135-1346-3
+always \u5E2F 145-2456-5
+always \u5E30 13-1246-3
+always \u5E31 12-12356-2
+always \u5E32 1234-13456-2
+always \u5E33 1-1346-5
+always \u5E34 13-2345-4
+always \u5E35 12456-3
+always \u5E36 145-2456-5
+always \u5E37 1246-2
+always \u5E38 12-1346-2
+always \u5E39 24-345-5
+always \u5E3A 245-16-2
+always \u5E3B 125-2346-2
+always \u5E3C 13-25-2
+always \u5E3D 134-146-5
+always \u5E3E 145-34-4
+always \u5E3F 1235-12356-2
+always \u5E40 1-1356-5
+always \u5E41 15-1256-3
+always \u5E42 134-16-5
+always \u5E43 1246-2
+always \u5E44 25-5
+always \u5E45 12345-34-2
+always \u5E46 16-5
+always \u5E47 135-1346-3
+always \u5E48 1234-13456-2
+always \u5E49 145-346-2
+always \u5E4A 13-12346-3
+always \u5E4B 1234-1236-2
+always \u5E4C 1235-456-4
+always \u5E4D 124-146-3
+always \u5E4E 134-16-5
+always \u5E4F 13-23456-5
+always \u5E50 124-1356-2
+always \u5E51 1235-1246-3
+always \u5E52 1-12346-3
+always \u5E53 24-136-3
+always \u5E54 134-1236-5
+always \u5E55 134-34-5
+always \u5E56 135-246-3
+always \u5E57 13-25-2
+always \u5E58 125-2346-2
+always \u5E59 134-34-5
+always \u5E5A 135-1346-3
+always \u5E5B 1-1346-5
+always \u5E5C 13-235-4
+always \u5E5D 12-1236-4
+always \u5E5E 12345-34-2
+always \u5E5F 1-156-5
+always \u5E60 1235-34-3
+always \u5E61 12345-1236-3
+always \u5E62 12-456-2
+always \u5E63 135-16-5
+always \u5E64 145-16-5
+always \u5E65 1-1346-4
+always \u5E66 134-16-5
+always \u5E67 245-146-3
+always \u5E68 12-1236-3
+always \u5E69 12345-136-2
+always \u5E6A 134-1356-2
+always \u5E6B 135-1346-3
+always \u5E6C 12-12356-2
+always \u5E6D 134-346-5
+always \u5E6E 12-34-2
+always \u5E6F 13-346-2
+always \u5E70 15-2345-4
+always \u5E71 14-1236-2
+always \u5E72 13-1236-3
+always \u5E73 1234-13456-2
+always \u5E74 1345-2345-2
+always \u5E75 13-2345-3
+always \u5E76 135-13456-5
+always \u5E77 135-13456-5
+always \u5E78 15-13456-5
+always \u5E79 13-1236-5
+always \u5E7A 246-3
+always \u5E7B 1235-12456-5
+always \u5E7C 234-5
+always \u5E7D 234-3
+always \u5E7E 13-16-4
+always \u5E7F 13-456-4
+always \u5E80 1234-16-4
+always \u5E81 124-13456-3
+always \u5E82 125-2346-5
+always \u5E83 13-456-4
+always \u5E84 1-456-3
+always \u5E85 134-2346-1
+always \u5E86 245-13456-5
+always \u5E87 135-16-5
+always \u5E88 245-1456-2
+always \u5E89 145-123456-5
+always \u5E8A 12-456-2
+always \u5E8B 13-1246-4
+always \u5E8C 23456-4
+always \u5E8D 135-2456-5
+always \u5E8E 13-346-5
+always \u5E8F 15-1256-5
+always \u5E90 14-34-2
+always \u5E91 34-4
+always \u5E92 1-456-3
+always \u5E93 123-34-5
+always \u5E94 13456-3
+always \u5E95 145-16-4
+always \u5E96 1234-146-2
+always \u5E97 145-2345-5
+always \u5E98 23456-3
+always \u5E99 134-246-5
+always \u5E9A 13-1356-3
+always \u5E9B 245-156-5
+always \u5E9C 12345-34-4
+always \u5E9D 124-12346-2
+always \u5E9E 1234-1346-2
+always \u5E9F 12345-356-5
+always \u5EA0 15-46-2
+always \u5EA1 16-4
+always \u5EA2 1-156-5
+always \u5EA3 124-246-3
+always \u5EA4 1-156-5
+always \u5EA5 15-234-3
+always \u5EA6 145-34-5
+always \u5EA7 125-25-5
+always \u5EA8 15-246-3
+always \u5EA9 124-34-2
+always \u5EAA 13-1246-4
+always \u5EAB 123-34-5
+always \u5EAC 1234-1346-2
+always \u5EAD 124-13456-2
+always \u5EAE 234-4
+always \u5EAF 135-34-3
+always \u5EB0 135-13456-5
+always \u5EB1 12-1356-4
+always \u5EB2 14-2456-2
+always \u5EB3 135-356-3
+always \u5EB4 13-16-2
+always \u5EB5 1236-3
+always \u5EB6 24-34-5
+always \u5EB7 123-1346-3
+always \u5EB8 235-3
+always \u5EB9 124-25-2
+always \u5EBA 15-12346-3
+always \u5EBB 24-34-5
+always \u5EBC 245-13456-4
+always \u5EBD 1256-5
+always \u5EBE 1256-4
+always \u5EBF 134-246-5
+always \u5EC0 15-12356-3
+always \u5EC1 245-2346-5
+always \u5EC2 15-46-3
+always \u5EC3 12345-356-5
+always \u5EC4 13-234-5
+always \u5EC5 1235-2346-2
+always \u5EC6 1235-1246-5
+always \u5EC7 14-234-5
+always \u5EC8 15-23456-5
+always \u5EC9 14-2345-2
+always \u5ECA 14-1346-2
+always \u5ECB 15-12356-3
+always \u5ECC 1-156-5
+always \u5ECD 1234-12356-4
+always \u5ECE 245-13456-4
+always \u5ECF 13-234-5
+always \u5ED0 13-234-5
+always \u5ED1 13-1456-4
+always \u5ED2 146-2
+always \u5ED3 123-25-5
+always \u5ED4 14-12356-2
+always \u5ED5 1456-5
+always \u5ED6 14-246-5
+always \u5ED7 145-2456-5
+always \u5ED8 14-34-5
+always \u5ED9 16-5
+always \u5EDA 12-34-2
+always \u5EDB 12-1236-2
+always \u5EDC 124-34-3
+always \u5EDD 15-156-3
+always \u5EDE 245-1456-3
+always \u5EDF 134-246-5
+always \u5EE0 12-1346-4
+always \u5EE1 34-4
+always \u5EE2 12345-356-5
+always \u5EE3 13-456-4
+always \u5EE4 13-34-5
+always \u5EE5 123-2356-5
+always \u5EE6 135-16-5
+always \u5EE7 245-46-2
+always \u5EE8 15-346-5
+always \u5EE9 14-1456-4
+always \u5EEA 14-1456-4
+always \u5EEB 14-246-2
+always \u5EEC 14-34-2
+always \u5EED 13-16-5
+always \u5EEE 13456-2
+always \u5EEF 15-2345-3
+always \u5EF0 124-13456-3
+always \u5EF1 235-3
+always \u5EF2 14-16-2
+always \u5EF3 124-13456-3
+always \u5EF4 1456-4
+always \u5EF5 15-256-2
+always \u5EF6 2345-2
+always \u5EF7 124-13456-2
+always \u5EF8 145-16-2
+always \u5EF9 1234-126-5
+always \u5EFA 13-2345-5
+always \u5EFB 1235-1246-2
+always \u5EFC 1345-2456-4
+always \u5EFD 1235-1246-2
+always \u5EFE 13-12346-4
+always \u5EFF 1345-2345-5
+always \u5F00 123-2456-3
+always \u5F01 135-2345-5
+always \u5F02 16-5
+always \u5F03 245-16-5
+always \u5F04 1345-12346-5
+always \u5F05 12345-136-2
+always \u5F06 13-1256-4
+always \u5F07 2345-4
+always \u5F08 16-5
+always \u5F09 125-1346-5
+always \u5F0A 135-16-5
+always \u5F0B 16-5
+always \u5F0C 16-3
+always \u5F0D 156-5
+always \u5F0E 15-1236-3
+always \u5F0F 24-156-5
+always \u5F10 156-5
+always \u5F11 24-156-5
+always \u5F12 24-156-5
+always \u5F13 13-12346-3
+always \u5F14 145-246-5
+always \u5F15 1456-4
+always \u5F16 1235-34-5
+always \u5F17 12345-34-2
+always \u5F18 1235-12346-2
+always \u5F19 34-3
+always \u5F1A 123-1246-2
+always \u5F1B 12-156-2
+always \u5F1C 13-46-5
+always \u5F1D 135-345-5
+always \u5F1E 24-136-4
+always \u5F1F 145-16-5
+always \u5F20 1-1346-3
+always \u5F21 13-236-2
+always \u5F22 124-146-3
+always \u5F23 12345-34-4
+always \u5F24 145-16-4
+always \u5F25 134-16-2
+always \u5F26 15-2345-2
+always \u5F27 1235-34-2
+always \u5F28 12-146-3
+always \u5F29 1345-34-4
+always \u5F2A 13-13456-5
+always \u5F2B 1-136-4
+always \u5F2C 16-2
+always \u5F2D 134-16-4
+always \u5F2E 245-45-3
+always \u5F2F 12456-3
+always \u5F30 24-146-3
+always \u5F31 1245-25-5
+always \u5F32 15-45-3
+always \u5F33 13-13456-5
+always \u5F34 145-123456-3
+always \u5F35 1-1346-3
+always \u5F36 13-46-5
+always \u5F37 245-46-2
+always \u5F38 1234-1356-2
+always \u5F39 145-1236-5
+always \u5F3A 245-46-2
+always \u5F3B 135-16-5
+always \u5F3C 135-16-5
+always \u5F3D 24-2346-5
+always \u5F3E 145-1236-5
+always \u5F3F 13-2345-4
+always \u5F40 13-12356-5
+always \u5F41 13-2346-3
+always \u5F42 12345-345-3
+always \u5F43 135-16-5
+always \u5F44 123-12356-3
+always \u5F45 13-2345-4
+always \u5F46 135-346-5
+always \u5F47 15-246-3
+always \u5F48 124-1236-2
+always \u5F49 1235-25-5
+always \u5F4A 245-46-2
+always \u5F4B 1235-12346-2
+always \u5F4C 134-16-2
+always \u5F4D 123-25-5
+always \u5F4E 12456-3
+always \u5F4F 13-236-2
+always \u5F50 13-16-5
+always \u5F51 13-16-5
+always \u5F52 13-1246-3
+always \u5F53 145-1346-3
+always \u5F54 14-34-5
+always \u5F55 14-34-5
+always \u5F56 124-12456-5
+always \u5F57 1235-1246-5
+always \u5F58 1-156-5
+always \u5F59 1235-1246-5
+always \u5F5A 1235-1246-5
+always \u5F5B 16-2
+always \u5F5C 16-2
+always \u5F5D 16-2
+always \u5F5E 16-2
+always \u5F5F 1235-25-5
+always \u5F60 1235-25-5
+always \u5F61 24-1236-3
+always \u5F62 15-13456-2
+always \u5F63 123456-2
+always \u5F64 124-12346-2
+always \u5F65 2345-5
+always \u5F66 2345-5
+always \u5F67 1256-5
+always \u5F68 12-156-3
+always \u5F69 245-2456-4
+always \u5F6A 135-246-3
+always \u5F6B 145-246-3
+always \u5F6C 135-1456-3
+always \u5F6D 1234-1356-2
+always \u5F6E 235-4
+always \u5F6F 1234-246-3
+always \u5F70 1-1346-3
+always \u5F71 13456-4
+always \u5F72 12-156-3
+always \u5F73 12-156-5
+always \u5F74 135-126-2
+always \u5F75 124-25-4
+always \u5F76 13-16-2
+always \u5F77 12345-1346-4
+always \u5F78 1-12346-3
+always \u5F79 16-5
+always \u5F7A 456-2
+always \u5F7B 12-2346-5
+always \u5F7C 135-16-4
+always \u5F7D 145-16-3
+always \u5F7E 14-13456-4
+always \u5F7F 12345-34-2
+always \u5F80 456-4
+always \u5F81 1-1356-3
+always \u5F82 245-34-2
+always \u5F83 456-4
+always \u5F84 13-13456-5
+always \u5F85 145-2456-5
+always \u5F86 15-16-3
+always \u5F87 15-256-2
+always \u5F88 1235-136-4
+always \u5F89 46-2
+always \u5F8A 1235-1246-2
+always \u5F8B 14-1256-5
+always \u5F8C 1235-12356-5
+always \u5F8D 456-4
+always \u5F8E 12-1356-4
+always \u5F8F 1-156-5
+always \u5F90 15-1256-2
+always \u5F91 13-13456-5
+always \u5F92 124-34-2
+always \u5F93 245-12346-2
+always \u5F94 1-156-3
+always \u5F95 14-2456-2
+always \u5F96 245-12346-2
+always \u5F97 145-2346-2
+always \u5F98 1234-2456-2
+always \u5F99 15-16-4
+always \u5F9A 145-12346-3
+always \u5F9B 13-16-5
+always \u5F9C 12-1346-2
+always \u5F9D 1-156-5
+always \u5F9E 245-12346-2
+always \u5F9F 1-12356-3
+always \u5FA0 14-2456-2
+always \u5FA1 1256-5
+always \u5FA2 15-346-5
+always \u5FA3 13-346-5
+always \u5FA4 13-2345-5
+always \u5FA5 24-156-5
+always \u5FA6 13-23456-4
+always \u5FA7 135-2345-5
+always \u5FA8 1235-456-2
+always \u5FA9 12345-34-5
+always \u5FAA 15-256-2
+always \u5FAB 1246-4
+always \u5FAC 1234-1346-2
+always \u5FAD 246-2
+always \u5FAE 1246-2
+always \u5FAF 15-16-3
+always \u5FB0 1-1356-3
+always \u5FB1 1234-246-5
+always \u5FB2 12-156-2
+always \u5FB3 145-2346-2
+always \u5FB4 1-1356-3
+always \u5FB5 1-1356-3
+always \u5FB6 135-346-2
+always \u5FB7 145-2346-2
+always \u5FB8 1-12346-4
+always \u5FB9 12-2346-5
+always \u5FBA 13-246-4
+always \u5FBB 1246-5
+always \u5FBC 13-246-4
+always \u5FBD 1235-1246-3
+always \u5FBE 134-356-2
+always \u5FBF 14-12346-5
+always \u5FC0 15-46-3
+always \u5FC1 135-146-5
+always \u5FC2 245-1256-2
+always \u5FC3 15-1456-3
+always \u5FC4 15-1456-3
+always \u5FC5 135-16-5
+always \u5FC6 16-5
+always \u5FC7 14-2346-5
+always \u5FC8 1245-136-2
+always \u5FC9 145-146-3
+always \u5FCA 145-13456-5
+always \u5FCB 13-2456-4
+always \u5FCC 13-16-5
+always \u5FCD 1245-136-4
+always \u5FCE 1245-136-2
+always \u5FCF 245-2345-3
+always \u5FD0 124-1236-4
+always \u5FD1 124-2346-5
+always \u5FD2 124-2346-5
+always \u5FD3 13-1236-3
+always \u5FD4 245-16-5
+always \u5FD5 124-2456-5
+always \u5FD6 245-123456-4
+always \u5FD7 1-156-5
+always \u5FD8 456-5
+always \u5FD9 134-1346-2
+always \u5FDA 15-16-3
+always \u5FDB 12345-1236-2
+always \u5FDC 13456-3
+always \u5FDD 124-2345-4
+always \u5FDE 134-1456-2
+always \u5FDF 134-1456-2
+always \u5FE0 1-12346-3
+always \u5FE1 12-12346-3
+always \u5FE2 34-5
+always \u5FE3 13-16-2
+always \u5FE4 34-4
+always \u5FE5 15-16-5
+always \u5FE6 13-23456-2
+always \u5FE7 234-5
+always \u5FE8 12456-5
+always \u5FE9 245-12346-3
+always \u5FEA 15-12346-3
+always \u5FEB 123-2356-5
+always \u5FEC 1256-5
+always \u5FED 135-2345-5
+always \u5FEE 1-156-5
+always \u5FEF 245-16-2
+always \u5FF0 245-1246-5
+always \u5FF1 12-136-2
+always \u5FF2 124-2456-5
+always \u5FF3 124-123456-2
+always \u5FF4 245-2345-2
+always \u5FF5 1345-2345-5
+always \u5FF6 1235-123456-2
+always \u5FF7 15-235-3
+always \u5FF8 1345-234-4
+always \u5FF9 1245-136-5
+always \u5FFA 15-2345-3
+always \u5FFB 15-1456-3
+always \u5FFC 123-1346-3
+always \u5FFD 1235-34-3
+always \u5FFE 123-2456-4
+always \u5FFF 12345-136-5
+always \u6000 1235-2356-2
+always \u6001 124-2456-5
+always \u6002 15-12346-4
+always \u6003 34-4
+always \u6004 12356-5
+always \u6005 12-1346-5
+always \u6006 12-456-5
+always \u6007 13-1256-5
+always \u6008 16-5
+always \u6009 135-146-4
+always \u600A 12-146-3
+always \u600B 134-1456-2
+always \u600C 1234-356-3
+always \u600D 125-25-5
+always \u600E 125-136-4
+always \u600F 46-5
+always \u6010 123-12356-5
+always \u6011 135-1236-5
+always \u6012 1345-34-5
+always \u6013 1345-146-2
+always \u6014 1-1356-3
+always \u6015 1234-345-5
+always \u6016 135-34-5
+always \u6017 124-346-3
+always \u6018 13-34-5
+always \u6019 1235-34-5
+always \u601A 13-1256-5
+always \u601B 145-345-2
+always \u601C 14-13456-2
+always \u601D 15-156-3
+always \u601E 234-2
+always \u601F 145-16-5
+always \u6020 145-2456-5
+always \u6021 16-2
+always \u6022 124-34-2
+always \u6023 234-2
+always \u6024 12345-34-3
+always \u6025 13-16-2
+always \u6026 1234-1356-3
+always \u6027 15-13456-5
+always \u6028 45-5
+always \u6029 1345-16-2
+always \u602A 13-2356-5
+always \u602B 12345-34-2
+always \u602C 15-16-5
+always \u602D 135-16-5
+always \u602E 234-3
+always \u602F 245-346-5
+always \u6030 15-45-5
+always \u6031 245-12346-3
+always \u6032 135-13456-4
+always \u6033 1235-456-4
+always \u6034 15-1256-5
+always \u6035 12-34-5
+always \u6036 1234-16-3
+always \u6037 24-34-5
+always \u6038 15-16-3
+always \u6039 124-1236-3
+always \u603A 235-4
+always \u603B 125-12346-4
+always \u603C 145-1246-5
+always \u603D 134-126-5
+always \u603E 135-16-4
+always \u603F 16-5
+always \u6040 24-156-5
+always \u6041 1245-136-5
+always \u6042 15-256-2
+always \u6043 24-156-5
+always \u6044 15-16-5
+always \u6045 14-146-4
+always \u6046 1235-1356-2
+always \u6047 123-456-3
+always \u6048 134-34-2
+always \u6049 1-156-4
+always \u604A 15-346-2
+always \u604B 14-2345-5
+always \u604C 124-246-3
+always \u604D 1235-456-4
+always \u604E 145-346-2
+always \u604F 1235-146-5
+always \u6050 123-12346-4
+always \u6051 13-1246-4
+always \u6052 1235-1356-2
+always \u6053 15-16-3
+always \u6054 15-246-5
+always \u6055 24-34-5
+always \u6056 15-156-3
+always \u6057 123-35-4
+always \u6058 245-234-3
+always \u6059 46-5
+always \u605A 1235-1246-5
+always \u605B 1235-1246-2
+always \u605C 12-156-5
+always \u605D 13-23456-2
+always \u605E 16-2
+always \u605F 15-235-3
+always \u6060 13-2356-5
+always \u6061 14-1456-5
+always \u6062 1235-1246-3
+always \u6063 125-156-5
+always \u6064 15-1256-5
+always \u6065 12-156-4
+always \u6066 15-46-5
+always \u6067 1345-1256-5
+always \u6068 1235-136-5
+always \u6069 136-3
+always \u606A 123-2346-5
+always \u606B 145-12346-5
+always \u606C 124-2345-2
+always \u606D 13-12346-3
+always \u606E 245-45-2
+always \u606F 15-16-2
+always \u6070 245-23456-5
+always \u6071 236-5
+always \u6072 1234-1356-3
+always \u6073 123-136-4
+always \u6074 145-2346-2
+always \u6075 1235-1246-5
+always \u6076 2346-5
+always \u6077 15-246-3
+always \u6078 124-12346-5
+always \u6079 2345-3
+always \u607A 123-2456-4
+always \u607B 245-2346-5
+always \u607C 1345-146-4
+always \u607D 256-5
+always \u607E 134-1346-2
+always \u607F 235-4
+always \u6080 235-4
+always \u6081 13-45-3
+always \u6082 1234-16-3
+always \u6083 123-123456-4
+always \u6084 245-246-3
+always \u6085 236-5
+always \u6086 1256-5
+always \u6087 1256-5
+always \u6088 13-346-5
+always \u6089 15-16-3
+always \u608A 1-2346-2
+always \u608B 14-1456-5
+always \u608C 124-16-5
+always \u608D 1235-1236-5
+always \u608E 1235-146-5
+always \u608F 245-346-5
+always \u6090 124-16-5
+always \u6091 135-34-5
+always \u6092 16-5
+always \u6093 245-2345-5
+always \u6094 1235-1246-4
+always \u6095 15-16-3
+always \u6096 135-356-5
+always \u6097 134-1236-2
+always \u6098 16-3
+always \u6099 1235-1356-3
+always \u609A 15-12346-4
+always \u609B 245-45-3
+always \u609C 12-1356-4
+always \u609D 123-1246-3
+always \u609E 34-5
+always \u609F 34-5
+always \u60A0 234-3
+always \u60A1 14-16-2
+always \u60A2 14-46-5
+always \u60A3 1235-12456-5
+always \u60A4 245-12346-3
+always \u60A5 16-5
+always \u60A6 236-5
+always \u60A7 14-16-5
+always \u60A8 1345-1456-2
+always \u60A9 1345-146-4
+always \u60AA 2346-5
+always \u60AB 245-236-5
+always \u60AC 15-45-2
+always \u60AD 245-2345-3
+always \u60AE 34-5
+always \u60AF 134-1456-4
+always \u60B0 245-12346-2
+always \u60B1 12345-356-4
+always \u60B2 135-356-3
+always \u60B3 145-2346-2
+always \u60B4 245-1246-5
+always \u60B5 12-1346-5
+always \u60B6 134-136-3
+always \u60B7 14-16-5
+always \u60B8 13-16-5
+always \u60B9 13-12456-5
+always \u60BA 13-12456-5
+always \u60BB 15-13456-5
+always \u60BC 145-146-5
+always \u60BD 245-16-3
+always \u60BE 123-12346-3
+always \u60BF 124-2345-4
+always \u60C0 14-123456-2
+always \u60C1 15-16-3
+always \u60C2 123-1236-4
+always \u60C3 123-123456-3
+always \u60C4 1345-16-5
+always \u60C5 245-13456-2
+always \u60C6 12-12356-2
+always \u60C7 145-123456-3
+always \u60C8 13-25-4
+always \u60C9 12-1236-3
+always \u60CA 13-13456-3
+always \u60CB 12456-4
+always \u60CC 45-3
+always \u60CD 13-1456-3
+always \u60CE 13-16-5
+always \u60CF 14-1236-2
+always \u60D0 1256-5
+always \u60D1 1235-25-5
+always \u60D2 1235-2346-2
+always \u60D3 245-45-2
+always \u60D4 124-1236-2
+always \u60D5 124-16-5
+always \u60D6 124-16-5
+always \u60D7 1345-346-3
+always \u60D8 456-4
+always \u60D9 12-25-5
+always \u60DA 1235-34-3
+always \u60DB 1235-123456-3
+always \u60DC 15-16-2
+always \u60DD 12-1346-4
+always \u60DE 15-1456-3
+always \u60DF 1246-2
+always \u60E0 1235-1246-5
+always \u60E1 2346-5
+always \u60E2 15-25-4
+always \u60E3 125-12346-4
+always \u60E4 13-2345-3
+always \u60E5 235-4
+always \u60E6 145-2345-5
+always \u60E7 13-1256-5
+always \u60E8 245-1236-4
+always \u60E9 12-1356-2
+always \u60EA 145-2346-2
+always \u60EB 135-356-5
+always \u60EC 245-346-5
+always \u60ED 245-1236-2
+always \u60EE 145-1236-5
+always \u60EF 13-12456-5
+always \u60F0 145-25-5
+always \u60F1 1345-146-4
+always \u60F2 256-5
+always \u60F3 15-46-4
+always \u60F4 1-1246-5
+always \u60F5 145-346-5
+always \u60F6 1235-456-2
+always \u60F7 12-123456-4
+always \u60F8 245-235-2
+always \u60F9 1245-2346-4
+always \u60FA 15-13456-3
+always \u60FB 245-2346-5
+always \u60FC 135-2345-4
+always \u60FD 1235-123456-3
+always \u60FE 125-12346-3
+always \u60FF 124-16-2
+always \u6100 245-246-4
+always \u6101 12-12356-2
+always \u6102 135-356-5
+always \u6103 15-45-3
+always \u6104 1246-3
+always \u6105 13-2346-2
+always \u6106 245-2345-3
+always \u6107 1246-4
+always \u6108 1256-5
+always \u6109 1256-2
+always \u610A 135-16-5
+always \u610B 15-45-3
+always \u610C 1235-12456-5
+always \u610D 134-1456-4
+always \u610E 135-16-5
+always \u610F 16-5
+always \u6110 134-2345-4
+always \u6111 235-4
+always \u6112 123-2456-5
+always \u6113 145-1346-5
+always \u6114 1456-3
+always \u6115 2346-5
+always \u6116 12-136-2
+always \u6117 134-12356-5
+always \u6118 245-23456-5
+always \u6119 123-2346-5
+always \u611A 1256-2
+always \u611B 2456-5
+always \u611C 245-346-5
+always \u611D 2345-4
+always \u611E 1345-25-5
+always \u611F 13-1236-4
+always \u6120 256-5
+always \u6121 125-12346-4
+always \u6122 15-2456-3
+always \u6123 14-1356-5
+always \u6124 12345-136-5
+always \u6125 13456-3
+always \u6126 123-1246-5
+always \u6127 123-1246-5
+always \u6128 245-236-5
+always \u6129 13-12346-3
+always \u612A 256-2
+always \u612B 15-34-5
+always \u612C 15-34-5
+always \u612D 245-16-2
+always \u612E 246-2
+always \u612F 15-12346-4
+always \u6130 1235-456-5
+always \u6131 13-16-2
+always \u6132 13-34-4
+always \u6133 13-1256-5
+always \u6134 12-456-5
+always \u6135 1345-16-5
+always \u6136 15-346-2
+always \u6137 123-2456-4
+always \u6138 1-1356-4
+always \u6139 235-4
+always \u613A 245-146-4
+always \u613B 15-123456-5
+always \u613C 24-136-5
+always \u613D 135-126-2
+always \u613E 123-2456-4
+always \u613F 45-5
+always \u6140 15-346-2
+always \u6141 1235-123456-5
+always \u6142 235-4
+always \u6143 46-4
+always \u6144 14-16-5
+always \u6145 15-146-3
+always \u6146 124-146-3
+always \u6147 1456-3
+always \u6148 245-156-2
+always \u6149 15-1256-5
+always \u614A 245-2345-5
+always \u614B 124-2456-5
+always \u614C 1235-456-3
+always \u614D 256-5
+always \u614E 24-136-5
+always \u614F 134-13456-4
+always \u6150 13-12346-5
+always \u6151 24-2346-5
+always \u6152 245-12346-2
+always \u6153 1234-246-5
+always \u6154 134-34-5
+always \u6155 134-34-5
+always \u6156 13-25-2
+always \u6157 12-156-5
+always \u6158 245-1236-4
+always \u6159 245-1236-2
+always \u615A 245-1236-2
+always \u615B 245-1246-2
+always \u615C 134-1456-4
+always \u615D 124-2346-5
+always \u615E 1-1346-3
+always \u615F 124-12346-5
+always \u6160 146-5
+always \u6161 24-456-4
+always \u6162 134-1236-5
+always \u6163 13-12456-5
+always \u6164 245-236-5
+always \u6165 245-146-5
+always \u6166 13-234-5
+always \u6167 1235-1246-5
+always \u6168 123-2456-4
+always \u6169 14-2345-2
+always \u616A 12356-5
+always \u616B 15-12346-4
+always \u616C 13-1456-4
+always \u616D 1456-5
+always \u616E 14-1256-5
+always \u616F 24-1346-3
+always \u6170 1246-5
+always \u6171 124-12456-2
+always \u6172 134-1236-2
+always \u6173 245-2345-3
+always \u6174 24-2346-5
+always \u6175 235-3
+always \u6176 245-13456-5
+always \u6177 123-1346-3
+always \u6178 145-16-5
+always \u6179 1-156-2
+always \u617A 14-12356-2
+always \u617B 13-45-5
+always \u617C 245-16-3
+always \u617D 245-16-3
+always \u617E 1256-5
+always \u617F 1234-13456-2
+always \u6180 14-246-2
+always \u6181 245-12346-3
+always \u6182 234-3
+always \u6183 12-12346-3
+always \u6184 1-156-5
+always \u6185 124-12346-5
+always \u6186 12-1356-3
+always \u6187 245-16-5
+always \u6188 245-1256-3
+always \u6189 1234-1356-2
+always \u618A 135-356-5
+always \u618B 135-346-3
+always \u618C 12-123456-2
+always \u618D 13-246-3
+always \u618E 125-1356-3
+always \u618F 12-156-5
+always \u6190 14-2345-2
+always \u6191 1234-13456-2
+always \u6192 123-1246-5
+always \u6193 1235-1246-5
+always \u6194 245-246-2
+always \u6195 12-1356-2
+always \u6196 1456-5
+always \u6197 1456-5
+always \u6198 15-16-4
+always \u6199 15-16-4
+always \u619A 145-1236-5
+always \u619B 124-1236-2
+always \u619C 145-25-4
+always \u619D 145-1246-5
+always \u619E 145-1246-5
+always \u619F 15-34-5
+always \u61A0 13-236-2
+always \u61A1 245-2346-5
+always \u61A2 15-246-3
+always \u61A3 12345-1236-2
+always \u61A4 12345-136-5
+always \u61A5 14-146-2
+always \u61A6 14-146-5
+always \u61A7 12-12346-3
+always \u61A8 1235-1236-3
+always \u61A9 245-16-5
+always \u61AA 15-2345-2
+always \u61AB 134-1456-4
+always \u61AC 13-13456-4
+always \u61AD 14-246-4
+always \u61AE 34-4
+always \u61AF 245-1236-4
+always \u61B0 13-236-2
+always \u61B1 245-34-5
+always \u61B2 15-2345-5
+always \u61B3 124-1236-4
+always \u61B4 24-1356-2
+always \u61B5 1234-16-3
+always \u61B6 16-5
+always \u61B7 12-34-5
+always \u61B8 15-2345-3
+always \u61B9 1345-146-2
+always \u61BA 145-1236-5
+always \u61BB 124-1236-4
+always \u61BC 13-13456-4
+always \u61BD 15-12346-3
+always \u61BE 1235-1236-5
+always \u61BF 13-16-3
+always \u61C0 2356-5
+always \u61C1 15-45-3
+always \u61C2 145-12346-4
+always \u61C3 245-1456-2
+always \u61C4 245-1456-2
+always \u61C5 245-1256-2
+always \u61C6 245-146-4
+always \u61C7 123-136-4
+always \u61C8 15-346-5
+always \u61C9 13456-3
+always \u61CA 146-5
+always \u61CB 134-146-5
+always \u61CC 16-5
+always \u61CD 14-1456-4
+always \u61CE 15-2346-5
+always \u61CF 13-256-5
+always \u61D0 1235-2356-2
+always \u61D1 134-136-5
+always \u61D2 14-1236-4
+always \u61D3 2456-5
+always \u61D4 14-1456-4
+always \u61D5 2345-3
+always \u61D6 13-35-3
+always \u61D7 15-23456-5
+always \u61D8 12-156-5
+always \u61D9 1256-4
+always \u61DA 1456-5
+always \u61DB 145-2456-3
+always \u61DC 134-1356-5
+always \u61DD 2456-5
+always \u61DE 134-1356-2
+always \u61DF 145-1246-5
+always \u61E0 245-16-2
+always \u61E1 134-126-4
+always \u61E2 14-1236-2
+always \u61E3 134-136-5
+always \u61E4 12-12356-2
+always \u61E5 1-156-5
+always \u61E6 1345-25-5
+always \u61E7 1345-25-5
+always \u61E8 2345-3
+always \u61E9 46-4
+always \u61EA 135-126-2
+always \u61EB 1-156-2
+always \u61EC 123-456-5
+always \u61ED 123-456-5
+always \u61EE 234-4
+always \u61EF 12345-34-3
+always \u61F0 14-234-2
+always \u61F1 134-346-5
+always \u61F2 12-1356-2
+always \u61F3 1235-1246-5
+always \u61F4 12-1236-5
+always \u61F5 134-1356-2
+always \u61F6 14-1236-4
+always \u61F7 1235-2356-2
+always \u61F8 15-45-2
+always \u61F9 1245-1346-5
+always \u61FA 12-1236-5
+always \u61FB 13-16-5
+always \u61FC 13-1256-5
+always \u61FD 1235-12456-3
+always \u61FE 24-2346-5
+always \u61FF 16-5
+always \u6200 14-2345-5
+always \u6201 1345-1236-4
+always \u6202 134-16-2
+always \u6203 124-1346-4
+always \u6204 13-236-2
+always \u6205 13-1346-5
+always \u6206 1-456-5
+always \u6207 1-456-5
+always \u6208 13-2346-3
+always \u6209 236-5
+always \u620A 34-5
+always \u620B 13-2345-3
+always \u620C 15-1256-3
+always \u620D 24-34-5
+always \u620E 1245-12346-2
+always \u620F 15-16-5
+always \u6210 12-1356-2
+always \u6211 25-4
+always \u6212 13-346-5
+always \u6213 13-2346-3
+always \u6214 13-2345-3
+always \u6215 245-46-2
+always \u6216 1235-25-5
+always \u6217 245-46-3
+always \u6218 1-1236-5
+always \u6219 145-12346-5
+always \u621A 245-16-3
+always \u621B 13-23456-2
+always \u621C 145-346-2
+always \u621D 125-356-2
+always \u621E 13-23456-2
+always \u621F 13-16-4
+always \u6220 1-156-2
+always \u6221 123-1236-3
+always \u6222 13-16-2
+always \u6223 123-1246-2
+always \u6224 13-2456-5
+always \u6225 145-1356-4
+always \u6226 1-1236-5
+always \u6227 245-46-3
+always \u6228 13-2346-3
+always \u6229 13-2345-4
+always \u622A 13-346-2
+always \u622B 1256-5
+always \u622C 13-2345-4
+always \u622D 2345-4
+always \u622E 14-34-5
+always \u622F 15-16-5
+always \u6230 1-1236-5
+always \u6231 15-16-5
+always \u6232 15-16-5
+always \u6233 12-25-3
+always \u6234 145-2456-5
+always \u6235 245-1256-2
+always \u6236 1235-34-5
+always \u6237 1235-34-5
+always \u6238 1235-34-5
+always \u6239 2346-5
+always \u623A 24-156-5
+always \u623B 124-16-5
+always \u623C 134-146-4
+always \u623D 1235-34-5
+always \u623E 14-16-5
+always \u623F 12345-1346-2
+always \u6240 15-25-4
+always \u6241 135-2345-4
+always \u6242 145-2345-5
+always \u6243 13-235-3
+always \u6244 24-1346-4
+always \u6245 16-2
+always \u6246 16-4
+always \u6247 24-1236-5
+always \u6248 1235-34-5
+always \u6249 12345-356-3
+always \u624A 2345-4
+always \u624B 24-12356-4
+always \u624C 24-12356-4
+always \u624D 245-2456-2
+always \u624E 1-345-3
+always \u624F 245-234-2
+always \u6250 14-2346-5
+always \u6251 1234-34-3
+always \u6252 1234-345-2
+always \u6253 145-345-4
+always \u6254 1245-1356-3
+always \u6255 12345-34-2
+always \u6256 1245-34-5
+always \u6257 125-2456-5
+always \u6258 124-25-3
+always \u6259 1-1346-5
+always \u625A 145-246-4
+always \u625B 123-1346-2
+always \u625C 145-246-4
+always \u625D 123-34-3
+always \u625E 1235-1236-5
+always \u625F 24-136-3
+always \u6260 12-345-3
+always \u6261 16-4
+always \u6262 15-16-5
+always \u6263 123-12356-5
+always \u6264 34-5
+always \u6265 145-123456-5
+always \u6266 245-2345-3
+always \u6267 1-156-2
+always \u6268 1245-136-5
+always \u6269 123-25-5
+always \u626A 134-136-2
+always \u626B 15-146-4
+always \u626C 46-2
+always \u626D 1345-234-4
+always \u626E 135-1236-5
+always \u626F 12-2346-4
+always \u6270 234-5
+always \u6271 15-16-3
+always \u6272 245-2345-2
+always \u6273 135-1236-3
+always \u6274 13-23456-2
+always \u6275 1256-2
+always \u6276 12345-34-2
+always \u6277 146-5
+always \u6278 15-16-3
+always \u6279 1234-16-3
+always \u627A 1-156-4
+always \u627B 125-156-5
+always \u627C 2346-5
+always \u627D 145-123456-5
+always \u627E 1-146-4
+always \u627F 12-1356-2
+always \u6280 13-16-5
+always \u6281 2345-4
+always \u6282 123-456-2
+always \u6283 135-2345-5
+always \u6284 12-146-3
+always \u6285 13-1256-3
+always \u6286 123456-5
+always \u6287 1235-34-2
+always \u6288 236-5
+always \u6289 13-236-2
+always \u628A 135-345-4
+always \u628B 245-1456-5
+always \u628C 1-136-4
+always \u628D 1-1356-4
+always \u628E 256-4
+always \u628F 12456-2
+always \u6290 1345-136-5
+always \u6291 16-5
+always \u6292 24-34-3
+always \u6293 1-35-3
+always \u6294 1234-12356-2
+always \u6295 124-12356-2
+always \u6296 145-12356-4
+always \u6297 123-1346-5
+always \u6298 1-2346-2
+always \u6299 1234-12356-2
+always \u629A 12345-34-4
+always \u629B 1234-146-3
+always \u629C 135-345-2
+always \u629D 1345-234-5
+always \u629E 125-2346-2
+always \u629F 124-12456-2
+always \u62A0 123-12356-3
+always \u62A1 14-123456-3
+always \u62A2 245-46-4
+always \u62A3 256-2
+always \u62A4 1235-34-5
+always \u62A5 135-146-5
+always \u62A6 135-13456-4
+always \u62A7 1-156-4
+always \u62A8 1234-1356-3
+always \u62A9 124-1236-3
+always \u62AA 135-34-5
+always \u62AB 1234-16-3
+always \u62AC 124-2456-2
+always \u62AD 246-4
+always \u62AE 1-136-4
+always \u62AF 1-345-3
+always \u62B0 46-4
+always \u62B1 135-146-5
+always \u62B2 1235-2346-3
+always \u62B3 1345-16-4
+always \u62B4 16-5
+always \u62B5 145-16-4
+always \u62B6 12-156-5
+always \u62B7 1234-16-3
+always \u62B8 125-345-3
+always \u62B9 134-126-4
+always \u62BA 134-356-5
+always \u62BB 12-136-3
+always \u62BC 23456-3
+always \u62BD 12-12356-3
+always \u62BE 245-1256-3
+always \u62BF 134-1456-4
+always \u62C0 1-34-5
+always \u62C1 13-23456-3
+always \u62C2 12345-34-2
+always \u62C3 1-1236-4
+always \u62C4 1-34-4
+always \u62C5 145-1236-3
+always \u62C6 12-2456-3
+always \u62C7 134-34-4
+always \u62C8 1345-2345-2
+always \u62C9 14-345-3
+always \u62CA 12345-34-4
+always \u62CB 1234-146-3
+always \u62CC 135-1236-5
+always \u62CD 1234-2456-3
+always \u62CE 14-13456-3
+always \u62CF 1345-345-2
+always \u62D0 13-2356-4
+always \u62D1 245-2345-2
+always \u62D2 13-1256-5
+always \u62D3 124-25-5
+always \u62D4 135-345-2
+always \u62D5 124-25-3
+always \u62D6 124-25-3
+always \u62D7 1345-234-5
+always \u62D8 13-1256-3
+always \u62D9 1-25-2
+always \u62DA 1234-1456-3
+always \u62DB 1-146-3
+always \u62DC 135-2456-5
+always \u62DD 135-2456-5
+always \u62DE 145-16-4
+always \u62DF 1345-16-4
+always \u62E0 13-1256-5
+always \u62E1 123-25-5
+always \u62E2 14-12346-4
+always \u62E3 13-2345-4
+always \u62E4 245-23456-2
+always \u62E5 235-4
+always \u62E6 14-1236-2
+always \u62E7 1345-13456-2
+always \u62E8 135-126-3
+always \u62E9 125-2346-2
+always \u62EA 245-2345-3
+always \u62EB 1235-136-2
+always \u62EC 123-25-5
+always \u62ED 24-156-5
+always \u62EE 13-346-2
+always \u62EF 1-1356-4
+always \u62F0 1345-1456-4
+always \u62F1 13-12346-4
+always \u62F2 13-12346-4
+always \u62F3 245-45-2
+always \u62F4 24-12456-3
+always \u62F5 245-123456-2
+always \u62F6 125-1236-4
+always \u62F7 123-146-4
+always \u62F8 12-156-4
+always \u62F9 15-346-2
+always \u62FA 245-2346-5
+always \u62FB 1235-1246-3
+always \u62FC 1234-1456-3
+always \u62FD 1-2356-5
+always \u62FE 24-156-2
+always \u62FF 1345-345-2
+always \u6300 135-126-5
+always \u6301 12-156-2
+always \u6302 13-35-5
+always \u6303 1-156-5
+always \u6304 123-25-5
+always \u6305 145-25-4
+always \u6306 145-25-4
+always \u6307 1-156-4
+always \u6308 245-346-5
+always \u6309 1236-5
+always \u630A 1345-12346-5
+always \u630B 1-136-5
+always \u630C 13-2346-2
+always \u630D 13-246-5
+always \u630E 123-35-5
+always \u630F 145-12346-5
+always \u6310 1245-34-2
+always \u6311 124-246-3
+always \u6312 14-346-5
+always \u6313 1-345-3
+always \u6314 14-1256-4
+always \u6315 145-346-2
+always \u6316 35-3
+always \u6317 13-236-2
+always \u6318 14-346-4
+always \u6319 13-1256-4
+always \u631A 1-156-5
+always \u631B 14-12456-2
+always \u631C 23456-5
+always \u631D 25-3
+always \u631E 124-345-5
+always \u631F 15-346-2
+always \u6320 1345-146-2
+always \u6321 145-1346-4
+always \u6322 13-246-4
+always \u6323 1-1356-5
+always \u6324 13-16-4
+always \u6325 1235-1246-3
+always \u6326 15-256-2
+always \u6327 1256-4
+always \u6328 2456-3
+always \u6329 124-25-3
+always \u632A 1345-25-2
+always \u632B 245-25-5
+always \u632C 135-126-2
+always \u632D 13-1356-4
+always \u632E 124-16-5
+always \u632F 1-136-5
+always \u6330 12-1356-2
+always \u6331 15-25-3
+always \u6332 15-25-3
+always \u6333 123-1356-3
+always \u6334 134-356-4
+always \u6335 1345-12346-5
+always \u6336 13-1256-2
+always \u6337 1234-1356-2
+always \u6338 13-2345-4
+always \u6339 16-5
+always \u633A 124-13456-4
+always \u633B 24-1236-3
+always \u633C 1345-25-2
+always \u633D 12456-4
+always \u633E 15-346-2
+always \u633F 12-345-3
+always \u6340 12345-1356-2
+always \u6341 123-34-5
+always \u6342 34-4
+always \u6343 13-256-5
+always \u6344 13-234-5
+always \u6345 124-12346-4
+always \u6346 123-123456-4
+always \u6347 1235-25-5
+always \u6348 124-34-2
+always \u6349 1-25-3
+always \u634A 1234-12356-2
+always \u634B 14-1256-4
+always \u634C 135-345-3
+always \u634D 1235-1236-5
+always \u634E 24-146-3
+always \u634F 1345-346-3
+always \u6350 13-45-3
+always \u6351 125-2346-2
+always \u6352 15-12346-4
+always \u6353 346-2
+always \u6354 13-236-2
+always \u6355 135-34-4
+always \u6356 12456-2
+always \u6357 135-34-5
+always \u6358 125-123456-5
+always \u6359 16-5
+always \u635A 1-2456-3
+always \u635B 14-1256-4
+always \u635C 15-12356-3
+always \u635D 124-25-3
+always \u635E 14-146-3
+always \u635F 15-123456-4
+always \u6360 135-1346-3
+always \u6361 13-2345-4
+always \u6362 1235-12456-5
+always \u6363 145-146-4
+always \u6364 1246-4
+always \u6365 12456-5
+always \u6366 245-1456-2
+always \u6367 1234-1356-4
+always \u6368 24-2346-4
+always \u6369 14-346-5
+always \u636A 134-1456-2
+always \u636B 134-136-2
+always \u636C 12345-34-4
+always \u636D 135-2456-4
+always \u636E 13-1256-5
+always \u636F 145-146-2
+always \u6370 25-4
+always \u6371 2456-2
+always \u6372 13-45-4
+always \u6373 236-5
+always \u6374 125-12346-4
+always \u6375 12-136-3
+always \u6376 12-1246-2
+always \u6377 13-346-2
+always \u6378 124-34-3
+always \u6379 135-136-5
+always \u637A 1345-345-5
+always \u637B 1345-2345-4
+always \u637C 1345-25-2
+always \u637D 125-34-2
+always \u637E 25-5
+always \u637F 15-16-3
+always \u6380 15-2345-3
+always \u6381 12-1356-2
+always \u6382 145-2345-3
+always \u6383 15-146-4
+always \u6384 14-123456-3
+always \u6385 245-13456-5
+always \u6386 13-1346-3
+always \u6387 145-25-2
+always \u6388 24-12356-5
+always \u6389 145-246-5
+always \u638A 1234-12356-2
+always \u638B 145-16-4
+always \u638C 1-1346-4
+always \u638D 13-123456-4
+always \u638E 13-16-4
+always \u638F 124-146-3
+always \u6390 245-23456-3
+always \u6391 245-16-2
+always \u6392 1234-2456-2
+always \u6393 24-34-2
+always \u6394 245-2345-3
+always \u6395 14-13456-5
+always \u6396 346-5
+always \u6397 23456-5
+always \u6398 13-236-2
+always \u6399 1-1356-5
+always \u639A 14-46-4
+always \u639B 13-35-5
+always \u639C 16-4
+always \u639D 1235-25-5
+always \u639E 24-1236-5
+always \u639F 145-13456-5
+always \u63A0 14-236-5
+always \u63A1 245-2456-4
+always \u63A2 124-1236-5
+always \u63A3 12-2346-5
+always \u63A4 135-13456-3
+always \u63A5 13-346-3
+always \u63A6 124-16-5
+always \u63A7 123-12346-5
+always \u63A8 124-1246-3
+always \u63A9 2345-4
+always \u63AA 245-25-5
+always \u63AB 125-12356-3
+always \u63AC 13-1256-2
+always \u63AD 124-2345-5
+always \u63AE 245-2345-2
+always \u63AF 123-136-5
+always \u63B0 135-2456-3
+always \u63B1 24-12356-4
+always \u63B2 13-346-3
+always \u63B3 14-34-4
+always \u63B4 13-25-2
+always \u63B5 134-13456-5
+always \u63B6 13-346-2
+always \u63B7 1-156-5
+always \u63B8 145-1236-4
+always \u63B9 134-1356-3
+always \u63BA 12-1236-3
+always \u63BB 15-146-3
+always \u63BC 13-12456-5
+always \u63BD 1234-1356-5
+always \u63BE 45-5
+always \u63BF 1345-25-5
+always \u63C0 13-2345-4
+always \u63C1 1-136-3
+always \u63C2 13-234-3
+always \u63C3 13-2345-4
+always \u63C4 1256-2
+always \u63C5 2345-2
+always \u63C6 123-1246-2
+always \u63C7 1345-1236-4
+always \u63C8 1235-12346-3
+always \u63C9 1245-12356-2
+always \u63CA 1234-16-5
+always \u63CB 1246-3
+always \u63CC 15-2456-3
+always \u63CD 125-12356-5
+always \u63CE 15-45-3
+always \u63CF 134-246-2
+always \u63D0 124-16-2
+always \u63D1 1345-346-3
+always \u63D2 12-345-3
+always \u63D3 24-156-5
+always \u63D4 125-12346-4
+always \u63D5 1-136-5
+always \u63D6 16-3
+always \u63D7 24-123456-4
+always \u63D8 1235-1356-2
+always \u63D9 135-2345-5
+always \u63DA 46-2
+always \u63DB 1235-12456-5
+always \u63DC 2345-4
+always \u63DD 125-12456-5
+always \u63DE 1236-4
+always \u63DF 15-1256-3
+always \u63E0 23456-5
+always \u63E1 25-5
+always \u63E2 123-2346-5
+always \u63E3 12-2356-4
+always \u63E4 13-16-2
+always \u63E5 124-16-5
+always \u63E6 14-345-2
+always \u63E7 14-345-5
+always \u63E8 12-1356-2
+always \u63E9 123-2456-3
+always \u63EA 13-234-3
+always \u63EB 13-234-3
+always \u63EC 124-34-2
+always \u63ED 13-346-3
+always \u63EE 1235-1246-3
+always \u63EF 13-1356-3
+always \u63F0 12-12346-5
+always \u63F1 15-246-3
+always \u63F2 24-2346-2
+always \u63F3 15-346-5
+always \u63F4 45-2
+always \u63F5 245-2345-2
+always \u63F6 346-2
+always \u63F7 12-345-3
+always \u63F8 1-345-3
+always \u63F9 135-356-3
+always \u63FA 246-2
+always \u63FB 1246-3
+always \u63FC 135-1356-5
+always \u63FD 14-1236-4
+always \u63FE 123456-5
+always \u63FF 245-1456-5
+always \u6400 12-1236-3
+always \u6401 13-2346-3
+always \u6402 14-12356-4
+always \u6403 125-12346-4
+always \u6404 13-1356-3
+always \u6405 13-246-4
+always \u6406 13-12356-5
+always \u6407 245-1456-5
+always \u6408 235-4
+always \u6409 245-236-5
+always \u640A 12-12356-3
+always \u640B 12-2356-3
+always \u640C 1-1236-4
+always \u640D 15-123456-4
+always \u640E 15-123456-3
+always \u640F 135-126-2
+always \u6410 12-34-5
+always \u6411 1245-12346-4
+always \u6412 1234-1356-2
+always \u6413 245-25-3
+always \u6414 15-146-3
+always \u6415 123-2346-5
+always \u6416 246-2
+always \u6417 145-146-4
+always \u6418 1-156-3
+always \u6419 1345-34-5
+always \u641A 15-346-2
+always \u641B 13-2345-3
+always \u641C 15-12356-3
+always \u641D 245-234-4
+always \u641E 13-146-4
+always \u641F 15-2345-4
+always \u6420 24-25-5
+always \u6421 15-1346-4
+always \u6422 13-1456-5
+always \u6423 134-346-5
+always \u6424 2346-5
+always \u6425 12-1246-2
+always \u6426 1345-25-5
+always \u6427 24-1236-3
+always \u6428 124-345-5
+always \u6429 13-346-2
+always \u642A 124-1346-2
+always \u642B 1234-1236-2
+always \u642C 135-1236-3
+always \u642D 145-345-3
+always \u642E 14-16-5
+always \u642F 124-146-3
+always \u6430 1235-34-2
+always \u6431 1-156-5
+always \u6432 35-3
+always \u6433 15-23456-2
+always \u6434 245-2345-3
+always \u6435 123456-5
+always \u6436 245-46-4
+always \u6437 124-2345-2
+always \u6438 1-136-3
+always \u6439 2346-5
+always \u643A 15-16-3
+always \u643B 1345-25-5
+always \u643C 245-45-2
+always \u643D 12-345-2
+always \u643E 1-345-5
+always \u643F 13-2346-2
+always \u6440 34-4
+always \u6441 136-5
+always \u6442 24-2346-5
+always \u6443 13-1346-5
+always \u6444 24-2346-5
+always \u6445 24-34-3
+always \u6446 135-2456-4
+always \u6447 246-2
+always \u6448 135-1456-5
+always \u6449 15-12356-3
+always \u644A 124-1236-3
+always \u644B 15-345-5
+always \u644C 12-1236-4
+always \u644D 15-25-3
+always \u644E 13-234-3
+always \u644F 12-12346-3
+always \u6450 12-456-3
+always \u6451 13-25-2
+always \u6452 135-13456-5
+always \u6453 12345-1356-2
+always \u6454 24-2356-3
+always \u6455 145-16-5
+always \u6456 245-16-5
+always \u6457 15-12356-3
+always \u6458 1-2456-3
+always \u6459 14-2345-4
+always \u645A 124-1346-2
+always \u645B 12-156-3
+always \u645C 13-12456-5
+always \u645D 14-34-5
+always \u645E 14-25-5
+always \u645F 14-12356-4
+always \u6460 125-12346-4
+always \u6461 13-2456-5
+always \u6462 1235-34-5
+always \u6463 1-345-3
+always \u6464 12-456-4
+always \u6465 124-1346-5
+always \u6466 1235-35-5
+always \u6467 245-1246-3
+always \u6468 1345-2456-2
+always \u6469 134-126-2
+always \u646A 13-46-3
+always \u646B 13-1246-3
+always \u646C 13456-5
+always \u646D 1-156-2
+always \u646E 146-2
+always \u646F 1-156-5
+always \u6470 1345-346-5
+always \u6471 134-1236-2
+always \u6472 12-1236-5
+always \u6473 123-12356-3
+always \u6474 24-34-3
+always \u6475 15-25-4
+always \u6476 124-12456-2
+always \u6477 13-246-4
+always \u6478 134-126-3
+always \u6479 134-126-2
+always \u647A 1-2346-2
+always \u647B 12-1236-3
+always \u647C 123-1356-3
+always \u647D 135-246-5
+always \u647E 13-46-5
+always \u647F 1456-3
+always \u6480 13-12356-5
+always \u6481 245-2345-3
+always \u6482 14-246-5
+always \u6483 13-16-2
+always \u6484 13456-3
+always \u6485 13-236-3
+always \u6486 1234-346-3
+always \u6487 1234-346-4
+always \u6488 14-146-3
+always \u6489 145-123456-3
+always \u648A 15-2345-5
+always \u648B 1245-12456-2
+always \u648C 123-1246-5
+always \u648D 125-1236-4
+always \u648E 16-5
+always \u648F 15-256-2
+always \u6490 12-1356-3
+always \u6491 12-1356-3
+always \u6492 15-345-4
+always \u6493 1345-146-2
+always \u6494 1235-1356-5
+always \u6495 15-156-3
+always \u6496 1235-1236-5
+always \u6497 1235-456-2
+always \u6498 145-345-3
+always \u6499 125-123456-4
+always \u649A 1345-2345-4
+always \u649B 14-1456-4
+always \u649C 1-1356-4
+always \u649D 1235-1246-3
+always \u649E 1-456-5
+always \u649F 13-246-4
+always \u64A0 13-16-4
+always \u64A1 245-146-3
+always \u64A2 145-1236-4
+always \u64A3 145-1236-4
+always \u64A4 12-2346-5
+always \u64A5 135-126-3
+always \u64A6 12-2346-4
+always \u64A7 13-236-2
+always \u64A8 15-246-3
+always \u64A9 14-246-2
+always \u64AA 135-136-5
+always \u64AB 12345-34-4
+always \u64AC 245-246-5
+always \u64AD 135-126-5
+always \u64AE 245-25-3
+always \u64AF 1-25-2
+always \u64B0 1-12456-5
+always \u64B1 124-25-4
+always \u64B2 1234-34-3
+always \u64B3 245-1456-5
+always \u64B4 145-123456-3
+always \u64B5 1345-2345-4
+always \u64B6 1235-35-2
+always \u64B7 15-346-2
+always \u64B8 14-34-3
+always \u64B9 13-246-4
+always \u64BA 245-12456-3
+always \u64BB 124-345-5
+always \u64BC 1235-1236-5
+always \u64BD 245-246-5
+always \u64BE 1-35-3
+always \u64BF 13-2345-4
+always \u64C0 13-1236-4
+always \u64C1 235-4
+always \u64C2 14-356-2
+always \u64C3 1345-1346-4
+always \u64C4 14-34-4
+always \u64C5 24-1236-5
+always \u64C6 1-25-2
+always \u64C7 125-2346-2
+always \u64C8 1234-34-3
+always \u64C9 12-25-5
+always \u64CA 13-16-2
+always \u64CB 145-1346-4
+always \u64CC 15-25-4
+always \u64CD 245-146-3
+always \u64CE 245-13456-2
+always \u64CF 245-13456-2
+always \u64D0 1235-12456-5
+always \u64D1 13-346-3
+always \u64D2 245-1456-2
+always \u64D3 123-2356-3
+always \u64D4 145-1236-3
+always \u64D5 15-16-3
+always \u64D6 13-2346-5
+always \u64D7 1234-16-5
+always \u64D8 135-126-5
+always \u64D9 146-5
+always \u64DA 13-1256-5
+always \u64DB 346-5
+always \u64DC 2346-5
+always \u64DD 134-1356-3
+always \u64DE 15-12356-4
+always \u64DF 134-16-2
+always \u64E0 13-16-4
+always \u64E1 124-2456-2
+always \u64E2 1-25-2
+always \u64E3 145-146-4
+always \u64E4 15-13456-4
+always \u64E5 14-1236-4
+always \u64E6 245-345-3
+always \u64E7 13-1256-4
+always \u64E8 346-2
+always \u64E9 1245-34-5
+always \u64EA 346-5
+always \u64EB 346-5
+always \u64EC 1345-16-4
+always \u64ED 1235-34-5
+always \u64EE 13-16-2
+always \u64EF 135-1456-5
+always \u64F0 1345-13456-2
+always \u64F1 13-2346-3
+always \u64F2 1-156-2
+always \u64F3 13-346-2
+always \u64F4 123-25-5
+always \u64F5 134-126-2
+always \u64F6 13-2345-5
+always \u64F7 13-346-2
+always \u64F8 14-346-5
+always \u64F9 124-1236-3
+always \u64FA 135-2456-4
+always \u64FB 15-12356-4
+always \u64FC 14-34-3
+always \u64FD 14-236-5
+always \u64FE 1245-146-4
+always \u64FF 1-156-2
+always \u6500 1234-1236-3
+always \u6501 46-4
+always \u6502 14-356-2
+always \u6503 15-345-5
+always \u6504 24-34-3
+always \u6505 125-1236-4
+always \u6506 1345-2345-4
+always \u6507 15-2345-4
+always \u6508 13-256-5
+always \u6509 1235-25-3
+always \u650A 14-16-5
+always \u650B 14-345-5
+always \u650C 1235-1236-5
+always \u650D 13456-2
+always \u650E 14-34-2
+always \u650F 14-12346-4
+always \u6510 245-2345-3
+always \u6511 245-2345-3
+always \u6512 125-1236-4
+always \u6513 245-2345-3
+always \u6514 14-1236-2
+always \u6515 15-2345-3
+always \u6516 13456-3
+always \u6517 134-356-2
+always \u6518 1245-1346-4
+always \u6519 12-1236-3
+always \u651A 13456-2
+always \u651B 245-12456-3
+always \u651C 15-16-3
+always \u651D 24-2346-5
+always \u651E 14-25-3
+always \u651F 13-256-5
+always \u6520 134-16-2
+always \u6521 14-16-2
+always \u6522 125-1236-4
+always \u6523 14-12456-2
+always \u6524 124-1236-3
+always \u6525 125-12456-5
+always \u6526 14-16-5
+always \u6527 145-2345-3
+always \u6528 35-3
+always \u6529 145-1346-4
+always \u652A 13-246-4
+always \u652B 13-236-2
+always \u652C 14-1236-4
+always \u652D 14-16-5
+always \u652E 1345-1346-4
+always \u652F 1-156-3
+always \u6530 13-1246-5
+always \u6531 13-1246-4
+always \u6532 245-16-3
+always \u6533 15-1456-2
+always \u6534 1234-34-3
+always \u6535 1234-34-3
+always \u6536 24-12356-3
+always \u6537 123-146-4
+always \u6538 234-3
+always \u6539 13-2456-4
+always \u653A 16-4
+always \u653B 13-12346-3
+always \u653C 13-1236-3
+always \u653D 135-1236-3
+always \u653E 12345-1346-5
+always \u653F 1-1356-5
+always \u6540 135-126-2
+always \u6541 145-2345-3
+always \u6542 123-12356-5
+always \u6543 134-1456-4
+always \u6544 34-5
+always \u6545 13-34-5
+always \u6546 13-2346-2
+always \u6547 245-2346-5
+always \u6548 15-246-5
+always \u6549 134-16-4
+always \u654A 12-34-5
+always \u654B 13-2346-2
+always \u654C 145-16-2
+always \u654D 15-1256-5
+always \u654E 13-246-5
+always \u654F 134-1456-4
+always \u6550 12-136-2
+always \u6551 13-234-5
+always \u6552 1-136-5
+always \u6553 145-25-2
+always \u6554 1256-4
+always \u6555 12-156-5
+always \u6556 146-2
+always \u6557 135-2456-5
+always \u6558 15-1256-5
+always \u6559 13-246-5
+always \u655A 145-25-2
+always \u655B 14-2345-5
+always \u655C 1345-346-5
+always \u655D 135-16-5
+always \u655E 12-1346-4
+always \u655F 145-2345-4
+always \u6560 145-25-2
+always \u6561 16-5
+always \u6562 13-1236-4
+always \u6563 15-1236-5
+always \u6564 123-2346-4
+always \u6565 2345-5
+always \u6566 145-123456-3
+always \u6567 13-16-3
+always \u6568 124-12356-4
+always \u6569 15-246-5
+always \u656A 145-25-2
+always \u656B 13-246-4
+always \u656C 13-13456-5
+always \u656D 46-2
+always \u656E 15-23456-2
+always \u656F 134-1456-4
+always \u6570 24-34-5
+always \u6571 2456-2
+always \u6572 245-246-3
+always \u6573 2456-2
+always \u6574 1-1356-4
+always \u6575 145-16-2
+always \u6576 1-136-5
+always \u6577 12345-34-3
+always \u6578 24-34-5
+always \u6579 14-246-2
+always \u657A 245-1256-3
+always \u657B 15-235-5
+always \u657C 15-16-4
+always \u657D 13-246-4
+always \u657E 24-1236-5
+always \u657F 13-246-4
+always \u6580 1-25-2
+always \u6581 16-5
+always \u6582 14-2345-5
+always \u6583 135-16-5
+always \u6584 14-16-5
+always \u6585 15-246-5
+always \u6586 15-246-5
+always \u6587 123456-2
+always \u6588 15-236-2
+always \u6589 245-16-2
+always \u658A 245-16-2
+always \u658B 1-2456-3
+always \u658C 135-1456-3
+always \u658D 13-236-2
+always \u658E 1-2456-3
+always \u658F 14-1346-2
+always \u6590 12345-356-4
+always \u6591 135-1236-3
+always \u6592 135-1236-3
+always \u6593 14-1236-2
+always \u6594 1256-4
+always \u6595 14-1236-2
+always \u6596 1246-4
+always \u6597 145-12356-4
+always \u6598 24-1356-3
+always \u6599 14-246-5
+always \u659A 13-23456-4
+always \u659B 1235-34-2
+always \u659C 15-346-2
+always \u659D 13-23456-4
+always \u659E 1256-4
+always \u659F 1-136-3
+always \u65A0 13-246-5
+always \u65A1 25-5
+always \u65A2 124-12356-4
+always \u65A3 12-34-5
+always \u65A4 13-1456-3
+always \u65A5 12-156-5
+always \u65A6 1456-2
+always \u65A7 12345-34-4
+always \u65A8 245-46-3
+always \u65A9 1-1236-4
+always \u65AA 245-1256-2
+always \u65AB 1-25-2
+always \u65AC 1-1236-4
+always \u65AD 145-12456-5
+always \u65AE 1-25-2
+always \u65AF 15-156-3
+always \u65B0 15-1456-3
+always \u65B1 1-25-2
+always \u65B2 1-25-2
+always \u65B3 245-1456-2
+always \u65B4 14-1456-2
+always \u65B5 1-25-2
+always \u65B6 12-34-5
+always \u65B7 145-12456-5
+always \u65B8 1-34-4
+always \u65B9 12345-1346-3
+always \u65BA 15-346-5
+always \u65BB 1235-1346-2
+always \u65BC 1256-2
+always \u65BD 24-156-3
+always \u65BE 1234-356-5
+always \u65BF 234-2
+always \u65C0 1246-5
+always \u65C1 1234-1346-2
+always \u65C2 245-16-2
+always \u65C3 1-1236-3
+always \u65C4 134-146-2
+always \u65C5 14-1256-4
+always \u65C6 1234-356-5
+always \u65C7 1234-16-3
+always \u65C8 14-234-2
+always \u65C9 12345-34-3
+always \u65CA 12345-1346-4
+always \u65CB 15-45-2
+always \u65CC 13-13456-3
+always \u65CD 13-13456-3
+always \u65CE 1345-16-4
+always \u65CF 125-34-2
+always \u65D0 1-146-5
+always \u65D1 16-4
+always \u65D2 14-234-2
+always \u65D3 24-146-3
+always \u65D4 13-2345-5
+always \u65D5 2346-2
+always \u65D6 16-4
+always \u65D7 245-16-2
+always \u65D8 1-156-5
+always \u65D9 12345-1236-3
+always \u65DA 1234-246-3
+always \u65DB 12345-1236-3
+always \u65DC 1-1236-3
+always \u65DD 13-2356-5
+always \u65DE 15-1246-5
+always \u65DF 1256-2
+always \u65E0 34-2
+always \u65E1 13-16-5
+always \u65E2 13-16-5
+always \u65E3 13-16-5
+always \u65E4 1235-25-5
+always \u65E5 1245-156-5
+always \u65E6 145-1236-5
+always \u65E7 13-234-5
+always \u65E8 1-156-4
+always \u65E9 125-146-4
+always \u65EA 15-346-2
+always \u65EB 124-246-3
+always \u65EC 15-256-2
+always \u65ED 15-1256-5
+always \u65EE 13-345-3
+always \u65EF 14-345-2
+always \u65F0 13-1236-5
+always \u65F1 1235-1236-5
+always \u65F2 124-2456-2
+always \u65F3 145-16-5
+always \u65F4 15-1256-3
+always \u65F5 12-1236-4
+always \u65F6 24-156-2
+always \u65F7 123-456-5
+always \u65F8 46-2
+always \u65F9 24-156-2
+always \u65FA 456-5
+always \u65FB 134-1456-2
+always \u65FC 134-1456-2
+always \u65FD 124-123456-3
+always \u65FE 12-123456-3
+always \u65FF 34-4
+always \u6600 256-2
+always \u6601 135-356-5
+always \u6602 1346-2
+always \u6603 125-2346-5
+always \u6604 135-1236-4
+always \u6605 13-16-2
+always \u6606 123-123456-3
+always \u6607 24-1356-3
+always \u6608 1235-34-5
+always \u6609 12345-1346-4
+always \u660A 1235-146-5
+always \u660B 13-1246-5
+always \u660C 12-1346-3
+always \u660D 15-45-3
+always \u660E 134-13456-2
+always \u660F 1235-123456-3
+always \u6610 12345-136-3
+always \u6611 245-1456-4
+always \u6612 1235-34-3
+always \u6613 16-5
+always \u6614 15-16-2
+always \u6615 15-1456-3
+always \u6616 2345-2
+always \u6617 125-2346-5
+always \u6618 12345-1346-4
+always \u6619 124-1236-2
+always \u661A 24-136-5
+always \u661B 13-1256-5
+always \u661C 46-2
+always \u661D 125-1236-4
+always \u661E 135-13456-4
+always \u661F 15-13456-3
+always \u6620 13456-5
+always \u6621 15-45-5
+always \u6622 1234-126-5
+always \u6623 1-136-4
+always \u6624 14-13456-3
+always \u6625 12-123456-3
+always \u6626 1235-146-5
+always \u6627 134-356-5
+always \u6628 125-25-2
+always \u6629 134-126-5
+always \u662A 135-2345-5
+always \u662B 15-1256-5
+always \u662C 1235-123456-3
+always \u662D 1-146-3
+always \u662E 125-12346-5
+always \u662F 24-156-5
+always \u6630 24-156-5
+always \u6631 1256-5
+always \u6632 12345-356-5
+always \u6633 145-346-2
+always \u6634 134-146-4
+always \u6635 1345-16-5
+always \u6636 12-1346-4
+always \u6637 123456-3
+always \u6638 145-12346-3
+always \u6639 2456-4
+always \u663A 135-13456-4
+always \u663B 1346-2
+always \u663C 1-12356-5
+always \u663D 14-12346-2
+always \u663E 15-2345-4
+always \u663F 123-456-5
+always \u6640 124-246-4
+always \u6641 12-146-2
+always \u6642 24-156-2
+always \u6643 1235-456-5
+always \u6644 1235-456-4
+always \u6645 15-45-3
+always \u6646 123-1246-2
+always \u6647 15-1256-3
+always \u6648 13-246-4
+always \u6649 13-1456-5
+always \u664A 1-156-4
+always \u664B 13-1456-5
+always \u664C 24-1346-4
+always \u664D 124-12346-2
+always \u664E 1235-12346-4
+always \u664F 2345-5
+always \u6650 13-2456-3
+always \u6651 15-46-4
+always \u6652 24-2456-5
+always \u6653 15-246-4
+always \u6654 346-5
+always \u6655 256-3
+always \u6656 1235-1246-3
+always \u6657 1235-1236-2
+always \u6658 1235-1236-5
+always \u6659 13-256-5
+always \u665A 12456-4
+always \u665B 15-2345-5
+always \u665C 123-123456-3
+always \u665D 1-12356-5
+always \u665E 15-16-3
+always \u665F 24-1356-5
+always \u6660 24-1356-2
+always \u6661 135-34-3
+always \u6662 1-2346-2
+always \u6663 1-2346-3
+always \u6664 34-5
+always \u6665 1235-1236-5
+always \u6666 1235-1246-5
+always \u6667 1235-146-5
+always \u6668 12-136-2
+always \u6669 12456-4
+always \u666A 124-2345-4
+always \u666B 1-25-2
+always \u666C 125-1246-5
+always \u666D 1-12356-4
+always \u666E 1234-34-4
+always \u666F 13-13456-4
+always \u6670 15-16-3
+always \u6671 24-1236-4
+always \u6672 16-4
+always \u6673 15-16-3
+always \u6674 245-13456-2
+always \u6675 245-16-4
+always \u6676 13-13456-3
+always \u6677 13-1246-4
+always \u6678 1-136-4
+always \u6679 16-5
+always \u667A 1-156-5
+always \u667B 1236-4
+always \u667C 12456-4
+always \u667D 14-1456-2
+always \u667E 14-46-5
+always \u667F 12-1346-3
+always \u6680 456-5
+always \u6681 15-246-4
+always \u6682 1-1236-5
+always \u6683 12345-356-2
+always \u6684 15-45-3
+always \u6685 15-45-4
+always \u6686 16-2
+always \u6687 15-23456-2
+always \u6688 256-3
+always \u6689 1235-1246-3
+always \u668A 12345-34-4
+always \u668B 134-1456-4
+always \u668C 123-1246-2
+always \u668D 1235-2346-5
+always \u668E 13456-5
+always \u668F 145-34-4
+always \u6690 1246-4
+always \u6691 24-34-4
+always \u6692 245-13456-2
+always \u6693 134-146-5
+always \u6694 1345-1236-2
+always \u6695 13-2345-4
+always \u6696 1345-12456-4
+always \u6697 1236-5
+always \u6698 46-2
+always \u6699 12-123456-3
+always \u669A 246-2
+always \u669B 15-25-4
+always \u669C 13-1456-5
+always \u669D 134-13456-2
+always \u669E 13-246-4
+always \u669F 123-2456-4
+always \u66A0 13-146-4
+always \u66A1 12346-4
+always \u66A2 12-1346-5
+always \u66A3 245-16-5
+always \u66A4 1235-146-5
+always \u66A5 2345-5
+always \u66A6 14-16-5
+always \u66A7 2456-5
+always \u66A8 13-16-5
+always \u66A9 13-1246-5
+always \u66AA 134-136-4
+always \u66AB 1-1236-5
+always \u66AC 15-346-5
+always \u66AD 1235-146-5
+always \u66AE 134-34-5
+always \u66AF 134-126-5
+always \u66B0 245-12346-3
+always \u66B1 1345-16-5
+always \u66B2 1-1346-3
+always \u66B3 1235-1246-5
+always \u66B4 135-146-5
+always \u66B5 1235-1236-5
+always \u66B6 15-45-2
+always \u66B7 12-12456-2
+always \u66B8 14-246-2
+always \u66B9 15-2345-3
+always \u66BA 145-1236-5
+always \u66BB 13-13456-4
+always \u66BC 1234-346-3
+always \u66BD 14-1456-2
+always \u66BE 124-123456-3
+always \u66BF 15-16-4
+always \u66C0 16-5
+always \u66C1 13-16-5
+always \u66C2 1235-456-5
+always \u66C3 124-2456-5
+always \u66C4 346-5
+always \u66C5 346-5
+always \u66C6 14-16-5
+always \u66C7 124-1236-2
+always \u66C8 124-12346-2
+always \u66C9 15-246-4
+always \u66CA 12345-356-5
+always \u66CB 245-1456-4
+always \u66CC 1-146-5
+always \u66CD 1235-146-5
+always \u66CE 16-5
+always \u66CF 15-46-5
+always \u66D0 15-13456-3
+always \u66D1 245-1236-3
+always \u66D2 13-246-4
+always \u66D3 135-146-5
+always \u66D4 13-13456-5
+always \u66D5 2345-5
+always \u66D6 2456-5
+always \u66D7 346-5
+always \u66D8 1245-34-2
+always \u66D9 24-34-4
+always \u66DA 134-1356-2
+always \u66DB 15-256-3
+always \u66DC 246-5
+always \u66DD 1234-34-5
+always \u66DE 14-16-5
+always \u66DF 12-136-2
+always \u66E0 123-456-5
+always \u66E1 145-346-2
+always \u66E2 14-246-4
+always \u66E3 2345-5
+always \u66E4 1235-25-5
+always \u66E5 14-34-2
+always \u66E6 15-16-3
+always \u66E7 1245-12346-2
+always \u66E8 14-12346-2
+always \u66E9 1345-1346-4
+always \u66EA 14-25-4
+always \u66EB 14-12456-2
+always \u66EC 24-2456-5
+always \u66ED 124-1346-4
+always \u66EE 2345-4
+always \u66EF 12-34-2
+always \u66F0 236-3
+always \u66F1 23456-3
+always \u66F2 245-1256-4
+always \u66F3 16-5
+always \u66F4 13-1356-5
+always \u66F5 16-5
+always \u66F6 1235-34-3
+always \u66F7 1235-2346-2
+always \u66F8 24-34-3
+always \u66F9 245-146-2
+always \u66FA 245-146-2
+always \u66FB 24-1356-3
+always \u66FC 134-1236-5
+always \u66FD 245-1356-2
+always \u66FE 245-1356-2
+always \u66FF 124-16-5
+always \u6700 125-1246-5
+always \u6701 245-1236-4
+always \u6702 15-1256-5
+always \u6703 1235-1246-5
+always \u6704 1456-5
+always \u6705 245-346-5
+always \u6706 12345-136-3
+always \u6707 1234-16-2
+always \u6708 236-5
+always \u6709 234-4
+always \u670A 12456-4
+always \u670B 1234-1356-2
+always \u670C 135-1236-3
+always \u670D 12345-34-2
+always \u670E 14-13456-2
+always \u670F 12345-356-4
+always \u6710 245-1256-2
+always \u6711 124-16-5
+always \u6712 1345-1256-5
+always \u6713 124-246-4
+always \u6714 24-25-5
+always \u6715 1-136-5
+always \u6716 14-1346-4
+always \u6717 14-1346-4
+always \u6718 13-45-3
+always \u6719 134-13456-2
+always \u671A 1235-456-3
+always \u671B 456-5
+always \u671C 124-123456-3
+always \u671D 12-146-2
+always \u671E 13-16-3
+always \u671F 245-16-2
+always \u6720 13456-3
+always \u6721 125-12346-3
+always \u6722 456-5
+always \u6723 124-12346-2
+always \u6724 14-1346-4
+always \u6725 14-146-2
+always \u6726 134-1356-2
+always \u6727 14-12346-2
+always \u6728 134-34-5
+always \u6729 145-1356-4
+always \u672A 1246-5
+always \u672B 134-126-5
+always \u672C 135-136-4
+always \u672D 1-345-2
+always \u672E 24-34-5
+always \u672F 24-34-5
+always \u6730 1235-2346-2
+always \u6731 1-34-3
+always \u6732 1245-136-2
+always \u6733 135-345-3
+always \u6734 1234-34-2
+always \u6735 145-25-4
+always \u6736 145-25-4
+always \u6737 145-146-3
+always \u6738 14-16-5
+always \u6739 245-234-2
+always \u673A 13-16-3
+always \u673B 13-234-3
+always \u673C 135-16-4
+always \u673D 15-234-4
+always \u673E 124-13456-2
+always \u673F 245-156-5
+always \u6740 24-345-3
+always \u6741 1245-34-5
+always \u6742 125-345-2
+always \u6743 245-45-2
+always \u6744 245-2345-3
+always \u6745 1256-2
+always \u6746 13-1236-3
+always \u6747 34-3
+always \u6748 12-345-5
+always \u6749 24-1236-3
+always \u674A 15-256-2
+always \u674B 12345-1236-2
+always \u674C 34-5
+always \u674D 125-156-4
+always \u674E 14-16-4
+always \u674F 15-13456-5
+always \u6750 245-2456-2
+always \u6751 245-123456-3
+always \u6752 1245-136-5
+always \u6753 24-146-2
+always \u6754 124-25-3
+always \u6755 145-16-5
+always \u6756 1-1346-5
+always \u6757 134-1346-2
+always \u6758 12-156-3
+always \u6759 16-5
+always \u675A 13-34-4
+always \u675B 13-12346-3
+always \u675C 145-34-5
+always \u675D 16-2
+always \u675E 245-16-4
+always \u675F 24-34-5
+always \u6760 13-1346-5
+always \u6761 124-246-2
+always \u6762 13-46-5
+always \u6763 24-1236-3
+always \u6764 12456-5
+always \u6765 14-2456-2
+always \u6766 13-234-4
+always \u6767 134-1346-2
+always \u6768 46-2
+always \u6769 134-345-5
+always \u676A 134-246-4
+always \u676B 15-156-5
+always \u676C 45-2
+always \u676D 1235-1346-2
+always \u676E 12345-356-5
+always \u676F 135-356-3
+always \u6770 13-346-2
+always \u6771 145-12346-3
+always \u6772 13-146-4
+always \u6773 246-4
+always \u6774 15-2345-3
+always \u6775 12-34-4
+always \u6776 12-123456-3
+always \u6777 1234-345-2
+always \u6778 24-34-3
+always \u6779 1235-35-5
+always \u677A 15-1456-3
+always \u677B 1345-234-4
+always \u677C 1-34-5
+always \u677D 12-12356-4
+always \u677E 15-12346-3
+always \u677F 135-1236-4
+always \u6780 15-12346-3
+always \u6781 13-16-2
+always \u6782 236-5
+always \u6783 13-1456-5
+always \u6784 13-12356-5
+always \u6785 13-16-3
+always \u6786 134-146-2
+always \u6787 1234-16-2
+always \u6788 135-16-5
+always \u6789 456-4
+always \u678A 1346-5
+always \u678B 12345-1346-3
+always \u678C 12345-136-2
+always \u678D 16-5
+always \u678E 12345-34-2
+always \u678F 1345-1236-2
+always \u6790 15-16-3
+always \u6791 1235-34-5
+always \u6792 23456-2
+always \u6793 145-12356-4
+always \u6794 15-256-2
+always \u6795 1-136-4
+always \u6796 246-3
+always \u6797 14-1456-2
+always \u6798 1245-1246-5
+always \u6799 2346-2
+always \u679A 134-356-2
+always \u679B 1-146-5
+always \u679C 13-25-4
+always \u679D 1-156-3
+always \u679E 245-12346-3
+always \u679F 256-5
+always \u67A0 125-1246-5
+always \u67A1 145-12356-4
+always \u67A2 24-34-3
+always \u67A3 125-146-4
+always \u67A4 145-16-5
+always \u67A5 14-16-5
+always \u67A6 14-34-2
+always \u67A7 13-2345-4
+always \u67A8 12-1356-2
+always \u67A9 15-12346-3
+always \u67AA 245-46-3
+always \u67AB 12345-1356-3
+always \u67AC 1345-1236-2
+always \u67AD 15-246-3
+always \u67AE 15-2345-3
+always \u67AF 123-34-3
+always \u67B0 1234-13456-2
+always \u67B1 124-2456-2
+always \u67B2 15-16-4
+always \u67B3 1-156-4
+always \u67B4 13-2356-4
+always \u67B5 15-246-3
+always \u67B6 13-23456-5
+always \u67B7 13-23456-3
+always \u67B8 13-12356-4
+always \u67B9 135-146-3
+always \u67BA 134-126-5
+always \u67BB 16-5
+always \u67BC 346-5
+always \u67BD 346-5
+always \u67BE 24-156-5
+always \u67BF 1345-346-5
+always \u67C0 135-16-4
+always \u67C1 145-25-5
+always \u67C2 16-2
+always \u67C3 14-13456-2
+always \u67C4 135-13456-4
+always \u67C5 1345-16-4
+always \u67C6 14-345-3
+always \u67C7 1235-2346-2
+always \u67C8 1234-1236-2
+always \u67C9 12345-1236-2
+always \u67CA 1-12346-3
+always \u67CB 145-2456-5
+always \u67CC 245-156-2
+always \u67CD 46-3
+always \u67CE 12345-34-3
+always \u67CF 135-126-2
+always \u67D0 134-12356-4
+always \u67D1 13-1236-3
+always \u67D2 245-16-3
+always \u67D3 1245-1236-4
+always \u67D4 1245-12356-2
+always \u67D5 134-146-5
+always \u67D6 1-146-3
+always \u67D7 15-12346-3
+always \u67D8 1-2346-5
+always \u67D9 15-23456-2
+always \u67DA 234-5
+always \u67DB 24-136-3
+always \u67DC 13-1256-4
+always \u67DD 124-25-5
+always \u67DE 125-25-5
+always \u67DF 1345-1236-2
+always \u67E0 1345-13456-2
+always \u67E1 235-4
+always \u67E2 145-16-4
+always \u67E3 1-156-2
+always \u67E4 1-345-3
+always \u67E5 12-345-2
+always \u67E6 145-1236-5
+always \u67E7 13-34-3
+always \u67E8 135-34-5
+always \u67E9 13-234-5
+always \u67EA 146-3
+always \u67EB 12345-34-2
+always \u67EC 13-2345-4
+always \u67ED 135-345-2
+always \u67EE 145-25-5
+always \u67EF 123-2346-3
+always \u67F0 1345-2456-5
+always \u67F1 1-34-5
+always \u67F2 135-16-5
+always \u67F3 14-234-4
+always \u67F4 12-2456-2
+always \u67F5 1-345-5
+always \u67F6 15-156-5
+always \u67F7 12-34-5
+always \u67F8 1234-356-3
+always \u67F9 24-156-5
+always \u67FA 13-2356-4
+always \u67FB 12-345-2
+always \u67FC 246-4
+always \u67FD 12-1356-3
+always \u67FE 13-234-5
+always \u67FF 24-156-5
+always \u6800 1-156-3
+always \u6801 14-234-4
+always \u6802 134-356-2
+always \u6803 14-16-5
+always \u6804 1245-12346-2
+always \u6805 1-345-5
+always \u6806 125-146-3
+always \u6807 135-246-3
+always \u6808 1-1236-5
+always \u6809 1-156-5
+always \u680A 14-12346-2
+always \u680B 145-12346-5
+always \u680C 14-34-2
+always \u680D 24-1356-3
+always \u680E 14-16-5
+always \u680F 14-1236-2
+always \u6810 235-4
+always \u6811 24-34-5
+always \u6812 15-256-2
+always \u6813 24-12456-3
+always \u6814 245-16-5
+always \u6815 1-136-3
+always \u6816 245-16-3
+always \u6817 14-16-5
+always \u6818 16-2
+always \u6819 15-46-2
+always \u681A 1-136-5
+always \u681B 14-16-5
+always \u681C 15-34-5
+always \u681D 13-35-3
+always \u681E 123-1236-3
+always \u681F 135-13456-3
+always \u6820 1245-136-4
+always \u6821 15-246-5
+always \u6822 135-126-2
+always \u6823 1245-136-4
+always \u6824 135-13456-5
+always \u6825 125-156-3
+always \u6826 12-12356-2
+always \u6827 16-5
+always \u6828 13-346-2
+always \u6829 15-1256-4
+always \u682A 1-34-3
+always \u682B 13-2345-5
+always \u682C 125-1246-5
+always \u682D 156-2
+always \u682E 156-4
+always \u682F 234-4
+always \u6830 12345-345-2
+always \u6831 13-12346-4
+always \u6832 123-146-4
+always \u6833 14-146-4
+always \u6834 1-1236-3
+always \u6835 14-346-5
+always \u6836 1456-3
+always \u6837 46-5
+always \u6838 1235-2346-2
+always \u6839 13-136-3
+always \u683A 1-156-3
+always \u683B 24-156-5
+always \u683C 13-2346-2
+always \u683D 125-2456-3
+always \u683E 14-12456-2
+always \u683F 12345-34-2
+always \u6840 13-346-2
+always \u6841 1235-1356-2
+always \u6842 13-1246-5
+always \u6843 124-146-2
+always \u6844 13-456-3
+always \u6845 1246-2
+always \u6846 123-456-3
+always \u6847 1245-34-2
+always \u6848 1236-5
+always \u6849 1236-3
+always \u684A 13-45-5
+always \u684B 16-2
+always \u684C 1-25-3
+always \u684D 123-34-3
+always \u684E 1-156-5
+always \u684F 245-235-2
+always \u6850 124-12346-2
+always \u6851 15-1346-3
+always \u6852 15-1346-3
+always \u6853 1235-12456-2
+always \u6854 13-1256-2
+always \u6855 13-234-5
+always \u6856 15-236-5
+always \u6857 145-25-5
+always \u6858 1-1246-5
+always \u6859 1256-2
+always \u685A 125-1236-4
+always \u685B 123-345-4
+always \u685C 13456-3
+always \u685D 13-346-2
+always \u685E 14-234-4
+always \u685F 1-1236-5
+always \u6860 23456-3
+always \u6861 1345-146-2
+always \u6862 1-136-3
+always \u6863 145-1346-4
+always \u6864 245-16-3
+always \u6865 245-246-2
+always \u6866 1235-35-5
+always \u6867 1235-1246-5
+always \u6868 13-46-4
+always \u6869 1-456-3
+always \u686A 15-256-2
+always \u686B 15-25-3
+always \u686C 24-345-3
+always \u686D 1-136-3
+always \u686E 135-356-3
+always \u686F 124-13456-3
+always \u6870 13-35-3
+always \u6871 13-13456-5
+always \u6872 135-126-2
+always \u6873 135-136-5
+always \u6874 12345-34-2
+always \u6875 1245-1246-4
+always \u6876 124-12346-4
+always \u6877 13-236-2
+always \u6878 15-16-3
+always \u6879 14-1346-2
+always \u687A 14-234-4
+always \u687B 12345-1356-3
+always \u687C 245-16-3
+always \u687D 123456-4
+always \u687E 13-256-3
+always \u687F 13-1236-4
+always \u6880 245-34-5
+always \u6881 14-46-2
+always \u6882 245-234-2
+always \u6883 124-13456-4
+always \u6884 234-4
+always \u6885 134-356-2
+always \u6886 135-1346-3
+always \u6887 14-12346-5
+always \u6888 1234-1356-3
+always \u6889 1-456-3
+always \u688A 145-16-5
+always \u688B 15-45-3
+always \u688C 124-34-2
+always \u688D 125-146-5
+always \u688E 146-3
+always \u688F 13-34-5
+always \u6890 135-16-5
+always \u6891 145-16-2
+always \u6892 1235-1236-2
+always \u6893 125-156-4
+always \u6894 1-156-3
+always \u6895 1245-136-5
+always \u6896 135-356-5
+always \u6897 13-1356-4
+always \u6898 13-2345-4
+always \u6899 1235-12456-5
+always \u689A 12456-4
+always \u689B 1345-25-2
+always \u689C 13-23456-2
+always \u689D 124-246-2
+always \u689E 13-16-5
+always \u689F 15-246-3
+always \u68A0 14-1256-4
+always \u68A1 123-12456-4
+always \u68A2 24-146-3
+always \u68A3 245-136-2
+always \u68A4 12345-136-2
+always \u68A5 15-12346-3
+always \u68A6 134-1356-5
+always \u68A7 34-2
+always \u68A8 14-16-2
+always \u68A9 14-16-2
+always \u68AA 145-12356-5
+always \u68AB 245-136-3
+always \u68AC 13456-4
+always \u68AD 15-25-3
+always \u68AE 13-1256-2
+always \u68AF 124-16-3
+always \u68B0 15-346-5
+always \u68B1 123-123456-4
+always \u68B2 1-25-2
+always \u68B3 24-34-3
+always \u68B4 12-1236-3
+always \u68B5 12345-1236-5
+always \u68B6 1246-4
+always \u68B7 13-13456-5
+always \u68B8 14-16-2
+always \u68B9 135-1456-3
+always \u68BA 15-46-5
+always \u68BB 12345-126-2
+always \u68BC 124-146-2
+always \u68BD 1-156-5
+always \u68BE 14-2456-2
+always \u68BF 14-2345-2
+always \u68C0 13-2345-4
+always \u68C1 1-25-2
+always \u68C2 14-13456-2
+always \u68C3 14-16-2
+always \u68C4 245-16-5
+always \u68C5 135-13456-5
+always \u68C6 1-123456-3
+always \u68C7 245-12346-3
+always \u68C8 245-2345-5
+always \u68C9 134-2345-2
+always \u68CA 245-16-2
+always \u68CB 245-16-2
+always \u68CC 245-2456-4
+always \u68CD 13-123456-5
+always \u68CE 12-1236-2
+always \u68CF 1-2346-2
+always \u68D0 12345-356-4
+always \u68D1 1234-2456-2
+always \u68D2 135-1346-5
+always \u68D3 135-1346-5
+always \u68D4 1235-123456-3
+always \u68D5 125-12346-3
+always \u68D6 12-1356-2
+always \u68D7 125-146-4
+always \u68D8 13-16-2
+always \u68D9 14-16-5
+always \u68DA 1234-1356-2
+always \u68DB 1256-5
+always \u68DC 1256-5
+always \u68DD 13-34-5
+always \u68DE 1235-123456-2
+always \u68DF 145-12346-5
+always \u68E0 124-1346-2
+always \u68E1 13-1346-3
+always \u68E2 456-4
+always \u68E3 145-16-5
+always \u68E4 245-25-5
+always \u68E5 12345-1236-2
+always \u68E6 12-1356-3
+always \u68E7 1-1236-5
+always \u68E8 245-16-4
+always \u68E9 45-3
+always \u68EA 2345-4
+always \u68EB 1256-5
+always \u68EC 245-45-3
+always \u68ED 16-5
+always \u68EE 15-136-3
+always \u68EF 1245-136-4
+always \u68F0 12-1246-2
+always \u68F1 14-1356-2
+always \u68F2 245-16-3
+always \u68F3 1-25-2
+always \u68F4 12345-34-2
+always \u68F5 123-2346-3
+always \u68F6 14-2456-2
+always \u68F7 125-12356-3
+always \u68F8 125-12356-3
+always \u68F9 1-146-5
+always \u68FA 13-12456-3
+always \u68FB 12345-136-3
+always \u68FC 12345-136-2
+always \u68FD 12-136-3
+always \u68FE 245-235-2
+always \u68FF 1345-346-5
+always \u6900 12456-4
+always \u6901 13-25-4
+always \u6902 14-34-5
+always \u6903 1235-146-2
+always \u6904 13-346-3
+always \u6905 16-4
+always \u6906 12-12356-2
+always \u6907 13-1256-4
+always \u6908 13-1256-2
+always \u6909 12-1356-2
+always \u690A 245-1246-5
+always \u690B 14-46-2
+always \u690C 245-46-3
+always \u690D 1-156-2
+always \u690E 1-1246-3
+always \u690F 23456-3
+always \u6910 13-1256-3
+always \u6911 1234-16-2
+always \u6912 13-246-3
+always \u6913 1-25-2
+always \u6914 125-156-3
+always \u6915 135-1456-3
+always \u6916 1234-1356-2
+always \u6917 145-13456-5
+always \u6918 12-34-4
+always \u6919 12-1346-3
+always \u691A 134-136-3
+always \u691B 12345-345-3
+always \u691C 13-2345-4
+always \u691D 13-1246-3
+always \u691E 15-16-5
+always \u691F 145-34-2
+always \u6920 245-2345-5
+always \u6921 145-146-5
+always \u6922 13-1246-5
+always \u6923 145-2345-4
+always \u6924 14-25-2
+always \u6925 1-156-3
+always \u6926 245-45-3
+always \u6927 134-1356-2
+always \u6928 12345-34-4
+always \u6929 13-1356-3
+always \u692A 1234-1356-5
+always \u692B 1-1236-4
+always \u692C 16-2
+always \u692D 124-25-4
+always \u692E 15-136-3
+always \u692F 145-25-4
+always \u6930 346-2
+always \u6931 12345-34-5
+always \u6932 1246-4
+always \u6933 1246-3
+always \u6934 145-12456-5
+always \u6935 13-23456-4
+always \u6936 125-12346-3
+always \u6937 13-2345-3
+always \u6938 16-2
+always \u6939 24-136-5
+always \u693A 15-16-2
+always \u693B 2345-5
+always \u693C 2345-4
+always \u693D 12-12456-2
+always \u693E 13-2345-3
+always \u693F 12-123456-3
+always \u6940 1256-4
+always \u6941 13-2346-2
+always \u6942 12-345-2
+always \u6943 25-5
+always \u6944 1234-2345-2
+always \u6945 135-16-5
+always \u6946 246-3
+always \u6947 1235-25-5
+always \u6948 15-1256-3
+always \u6949 1245-25-5
+always \u694A 46-2
+always \u694B 14-345-5
+always \u694C 2345-2
+always \u694D 135-136-4
+always \u694E 1235-123456-2
+always \u694F 123-1246-2
+always \u6950 13-346-5
+always \u6951 123-1246-2
+always \u6952 15-156-3
+always \u6953 12345-1356-3
+always \u6954 15-346-5
+always \u6955 124-25-4
+always \u6956 13-16-2
+always \u6957 13-2345-5
+always \u6958 134-34-5
+always \u6959 134-146-5
+always \u695A 12-34-4
+always \u695B 1235-34-5
+always \u695C 1235-34-2
+always \u695D 14-2345-5
+always \u695E 14-1356-5
+always \u695F 124-13456-2
+always \u6960 1345-1236-2
+always \u6961 1256-2
+always \u6962 234-2
+always \u6963 134-356-2
+always \u6964 15-12346-4
+always \u6965 15-45-5
+always \u6966 15-45-5
+always \u6967 13456-3
+always \u6968 1-136-3
+always \u6969 1234-2345-2
+always \u696A 346-5
+always \u696B 13-16-2
+always \u696C 13-346-3
+always \u696D 346-5
+always \u696E 12-34-4
+always \u696F 24-123456-4
+always \u6970 1256-2
+always \u6971 245-12356-5
+always \u6972 1246-3
+always \u6973 134-356-2
+always \u6974 145-16-5
+always \u6975 13-16-2
+always \u6976 13-346-2
+always \u6977 123-2456-4
+always \u6978 245-234-3
+always \u6979 13456-2
+always \u697A 1245-12356-2
+always \u697B 1235-1356-2
+always \u697C 14-12356-2
+always \u697D 14-2346-5
+always \u697E 245-45-2
+always \u697F 15-46-3
+always \u6980 1234-1456-4
+always \u6981 24-156-4
+always \u6982 13-2456-5
+always \u6983 1234-1236-2
+always \u6984 14-1236-4
+always \u6985 256-2
+always \u6986 1256-2
+always \u6987 12-136-5
+always \u6988 14-1256-2
+always \u6989 13-1256-4
+always \u698A 24-136-2
+always \u698B 12-34-4
+always \u698C 135-16-3
+always \u698D 15-346-5
+always \u698E 13-23456-4
+always \u698F 16-5
+always \u6990 1-1236-4
+always \u6991 12345-34-2
+always \u6992 1345-2456-5
+always \u6993 134-16-5
+always \u6994 14-1346-2
+always \u6995 1245-12346-2
+always \u6996 13-34-4
+always \u6997 13-2345-5
+always \u6998 13-1256-4
+always \u6999 124-345-3
+always \u699A 246-4
+always \u699B 1-136-3
+always \u699C 135-1346-4
+always \u699D 24-345-3
+always \u699E 45-2
+always \u699F 125-156-4
+always \u69A0 134-13456-2
+always \u69A1 15-34-5
+always \u69A2 13-23456-5
+always \u69A3 246-2
+always \u69A4 13-346-2
+always \u69A5 1235-456-4
+always \u69A6 13-1236-5
+always \u69A7 12345-356-4
+always \u69A8 1-345-5
+always \u69A9 245-2345-2
+always \u69AA 134-345-5
+always \u69AB 15-123456-4
+always \u69AC 45-2
+always \u69AD 15-346-5
+always \u69AE 1245-12346-2
+always \u69AF 24-156-2
+always \u69B0 1-156-3
+always \u69B1 245-1246-3
+always \u69B2 256-5
+always \u69B3 124-13456-2
+always \u69B4 14-234-2
+always \u69B5 1245-12346-2
+always \u69B6 124-1346-2
+always \u69B7 245-236-5
+always \u69B8 1-2456-3
+always \u69B9 15-156-3
+always \u69BA 24-1356-5
+always \u69BB 124-345-5
+always \u69BC 123-2346-5
+always \u69BD 15-16-3
+always \u69BE 13-34-5
+always \u69BF 245-16-3
+always \u69C0 13-146-4
+always \u69C1 13-146-4
+always \u69C2 15-123456-3
+always \u69C3 1234-1236-2
+always \u69C4 124-146-3
+always \u69C5 13-2346-2
+always \u69C6 15-256-2
+always \u69C7 145-2345-3
+always \u69C8 1345-12356-5
+always \u69C9 13-16-2
+always \u69CA 24-25-5
+always \u69CB 13-12356-5
+always \u69CC 12-1246-2
+always \u69CD 245-46-3
+always \u69CE 12-345-2
+always \u69CF 245-2345-4
+always \u69D0 1235-2356-2
+always \u69D1 134-356-2
+always \u69D2 15-1256-5
+always \u69D3 13-1346-5
+always \u69D4 13-146-3
+always \u69D5 1-25-3
+always \u69D6 124-25-5
+always \u69D7 245-246-2
+always \u69D8 46-5
+always \u69D9 145-2345-3
+always \u69DA 13-23456-4
+always \u69DB 1235-1236-4
+always \u69DC 125-1246-5
+always \u69DD 145-146-4
+always \u69DE 14-12346-2
+always \u69DF 135-1456-3
+always \u69E0 1-34-3
+always \u69E1 15-1346-3
+always \u69E2 15-16-2
+always \u69E3 245-16-4
+always \u69E4 14-2345-2
+always \u69E5 1235-1246-5
+always \u69E6 235-2
+always \u69E7 145-2345-5
+always \u69E8 13-25-4
+always \u69E9 13-2456-5
+always \u69EA 13-2456-5
+always \u69EB 124-12456-2
+always \u69EC 1235-35-5
+always \u69ED 245-16-3
+always \u69EE 15-136-3
+always \u69EF 245-1246-3
+always \u69F0 135-1356-5
+always \u69F1 234-4
+always \u69F2 1235-34-2
+always \u69F3 13-46-4
+always \u69F4 1235-34-5
+always \u69F5 1235-12456-5
+always \u69F6 13-1246-5
+always \u69F7 1345-346-5
+always \u69F8 16-5
+always \u69F9 13-146-3
+always \u69FA 123-1346-3
+always \u69FB 13-1246-3
+always \u69FC 13-1246-3
+always \u69FD 245-146-2
+always \u69FE 134-1236-2
+always \u69FF 13-1456-4
+always \u6A00 145-16-5
+always \u6A01 1-456-3
+always \u6A02 14-2346-5
+always \u6A03 14-1346-2
+always \u6A04 12-136-2
+always \u6A05 245-12346-3
+always \u6A06 14-16-2
+always \u6A07 15-234-3
+always \u6A08 245-13456-2
+always \u6A09 24-456-4
+always \u6A0A 12345-1236-2
+always \u6A0B 124-12346-3
+always \u6A0C 13-12456-5
+always \u6A0D 13-16-3
+always \u6A0E 15-25-3
+always \u6A0F 14-356-4
+always \u6A10 14-34-4
+always \u6A11 14-46-2
+always \u6A12 134-16-5
+always \u6A13 14-12356-2
+always \u6A14 12-146-2
+always \u6A15 15-34-5
+always \u6A16 123-2346-3
+always \u6A17 12-34-3
+always \u6A18 12-1356-3
+always \u6A19 135-246-3
+always \u6A1A 14-34-5
+always \u6A1B 13-234-3
+always \u6A1C 24-34-5
+always \u6A1D 1-345-3
+always \u6A1E 24-34-3
+always \u6A1F 1-1346-3
+always \u6A20 134-136-2
+always \u6A21 134-126-2
+always \u6A22 1345-246-4
+always \u6A23 46-5
+always \u6A24 124-246-2
+always \u6A25 1234-1356-2
+always \u6A26 1-34-5
+always \u6A27 24-345-3
+always \u6A28 15-16-3
+always \u6A29 245-45-2
+always \u6A2A 1235-1356-2
+always \u6A2B 13-2345-3
+always \u6A2C 245-12346-3
+always \u6A2D 13-16-3
+always \u6A2E 2345-3
+always \u6A2F 245-46-2
+always \u6A30 15-236-4
+always \u6A31 13456-3
+always \u6A32 156-5
+always \u6A33 15-256-2
+always \u6A34 1-156-2
+always \u6A35 245-246-2
+always \u6A36 125-1246-3
+always \u6A37 245-12346-2
+always \u6A38 1234-34-2
+always \u6A39 24-34-5
+always \u6A3A 1235-35-5
+always \u6A3B 123-1246-5
+always \u6A3C 1-136-3
+always \u6A3D 125-123456-3
+always \u6A3E 236-5
+always \u6A3F 1-1236-4
+always \u6A40 15-16-3
+always \u6A41 12-123456-3
+always \u6A42 145-2345-5
+always \u6A43 12345-356-5
+always \u6A44 13-1236-4
+always \u6A45 134-126-2
+always \u6A46 34-4
+always \u6A47 245-246-3
+always \u6A48 1345-146-2
+always \u6A49 14-1456-5
+always \u6A4A 14-234-2
+always \u6A4B 245-246-2
+always \u6A4C 15-2345-5
+always \u6A4D 1245-123456-5
+always \u6A4E 12345-1236-2
+always \u6A4F 1-1236-4
+always \u6A50 124-25-2
+always \u6A51 14-146-4
+always \u6A52 256-2
+always \u6A53 24-123456-5
+always \u6A54 124-1246-2
+always \u6A55 12-1356-3
+always \u6A56 124-1346-2
+always \u6A57 134-1356-2
+always \u6A58 13-1256-2
+always \u6A59 12-1356-2
+always \u6A5A 15-34-5
+always \u6A5B 13-236-2
+always \u6A5C 13-236-2
+always \u6A5D 124-1236-3
+always \u6A5E 1235-1246-5
+always \u6A5F 13-16-3
+always \u6A60 1345-25-4
+always \u6A61 15-46-5
+always \u6A62 124-25-4
+always \u6A63 1345-13456-4
+always \u6A64 1245-1246-4
+always \u6A65 1-34-3
+always \u6A66 124-12346-2
+always \u6A67 125-1356-3
+always \u6A68 12345-136-5
+always \u6A69 245-235-2
+always \u6A6A 1245-1236-4
+always \u6A6B 1235-1356-2
+always \u6A6C 245-136-2
+always \u6A6D 13-34-3
+always \u6A6E 14-234-4
+always \u6A6F 14-146-5
+always \u6A70 13-146-3
+always \u6A71 12-34-2
+always \u6A72 15-16-4
+always \u6A73 24-1356-5
+always \u6A74 125-156-4
+always \u6A75 15-1236-4
+always \u6A76 13-16-4
+always \u6A77 145-12356-3
+always \u6A78 13-1456-3
+always \u6A79 14-34-4
+always \u6A7A 13-2345-5
+always \u6A7B 12-34-4
+always \u6A7C 45-2
+always \u6A7D 124-345-5
+always \u6A7E 24-34-3
+always \u6A7F 13-46-3
+always \u6A80 124-1236-2
+always \u6A81 14-1456-4
+always \u6A82 1345-12346-2
+always \u6A83 1456-4
+always \u6A84 15-16-2
+always \u6A85 15-1246-5
+always \u6A86 24-1236-3
+always \u6A87 125-1246-4
+always \u6A88 15-45-2
+always \u6A89 12-1356-3
+always \u6A8A 13-1236-5
+always \u6A8B 13-1256-3
+always \u6A8C 125-1246-5
+always \u6A8D 16-5
+always \u6A8E 245-1456-2
+always \u6A8F 1234-34-4
+always \u6A90 2345-2
+always \u6A91 14-356-2
+always \u6A92 12345-1356-3
+always \u6A93 1235-1246-4
+always \u6A94 145-1346-4
+always \u6A95 13-16-5
+always \u6A96 15-1246-5
+always \u6A97 135-126-5
+always \u6A98 135-16-5
+always \u6A99 145-13456-4
+always \u6A9A 12-34-4
+always \u6A9B 1-35-3
+always \u6A9C 123-2356-5
+always \u6A9D 13-16-2
+always \u6A9E 13-346-4
+always \u6A9F 13-23456-4
+always \u6AA0 245-13456-2
+always \u6AA1 1-2346-5
+always \u6AA2 13-2345-4
+always \u6AA3 245-46-2
+always \u6AA4 145-146-5
+always \u6AA5 16-4
+always \u6AA6 135-246-4
+always \u6AA7 15-12346-3
+always \u6AA8 24-2346-3
+always \u6AA9 14-1456-4
+always \u6AAA 14-16-5
+always \u6AAB 12-345-2
+always \u6AAC 134-1356-2
+always \u6AAD 1456-2
+always \u6AAE 124-146-2
+always \u6AAF 124-2456-2
+always \u6AB0 134-2345-2
+always \u6AB1 245-16-2
+always \u6AB2 124-12456-2
+always \u6AB3 135-1456-3
+always \u6AB4 1235-25-5
+always \u6AB5 13-16-5
+always \u6AB6 245-2345-3
+always \u6AB7 134-16-2
+always \u6AB8 1345-13456-2
+always \u6AB9 16-3
+always \u6ABA 13-146-4
+always \u6ABB 13-2345-5
+always \u6ABC 1456-5
+always \u6ABD 1345-12356-5
+always \u6ABE 245-13456-4
+always \u6ABF 2345-4
+always \u6AC0 245-16-2
+always \u6AC1 134-16-5
+always \u6AC2 1-146-5
+always \u6AC3 13-1246-5
+always \u6AC4 12-123456-3
+always \u6AC5 13-16-3
+always \u6AC6 123-1246-2
+always \u6AC7 1234-126-2
+always \u6AC8 145-1356-5
+always \u6AC9 12-34-2
+always \u6ACA 13-2346-2
+always \u6ACB 134-2345-2
+always \u6ACC 234-3
+always \u6ACD 1-156-5
+always \u6ACE 13-456-5
+always \u6ACF 245-2345-3
+always \u6AD0 14-356-4
+always \u6AD1 14-356-2
+always \u6AD2 15-345-5
+always \u6AD3 14-34-4
+always \u6AD4 14-16-5
+always \u6AD5 245-12456-2
+always \u6AD6 14-1256-2
+always \u6AD7 134-346-5
+always \u6AD8 1235-1246-5
+always \u6AD9 12356-3
+always \u6ADA 14-1256-2
+always \u6ADB 13-346-2
+always \u6ADC 13-146-3
+always \u6ADD 145-34-2
+always \u6ADE 45-2
+always \u6ADF 14-16-5
+always \u6AE0 12345-356-5
+always \u6AE1 1-25-2
+always \u6AE2 15-12356-4
+always \u6AE3 14-2345-2
+always \u6AE4 13-46-5
+always \u6AE5 12-34-2
+always \u6AE6 245-13456-5
+always \u6AE7 1-34-3
+always \u6AE8 14-34-2
+always \u6AE9 2345-2
+always \u6AEA 14-16-5
+always \u6AEB 1-34-3
+always \u6AEC 12-136-5
+always \u6AED 13-346-2
+always \u6AEE 2346-5
+always \u6AEF 15-34-3
+always \u6AF0 1235-2356-2
+always \u6AF1 1345-346-5
+always \u6AF2 1256-5
+always \u6AF3 14-12346-2
+always \u6AF4 14-2456-5
+always \u6AF5 13-246-2
+always \u6AF6 15-2345-4
+always \u6AF7 13-1246-3
+always \u6AF8 13-1256-4
+always \u6AF9 15-246-3
+always \u6AFA 14-13456-2
+always \u6AFB 13456-3
+always \u6AFC 13-2345-3
+always \u6AFD 1456-4
+always \u6AFE 234-2
+always \u6AFF 13456-2
+always \u6B00 15-46-3
+always \u6B01 1345-12346-2
+always \u6B02 135-126-2
+always \u6B03 12-1236-2
+always \u6B04 14-1236-2
+always \u6B05 13-1256-4
+always \u6B06 24-456-3
+always \u6B07 24-2346-5
+always \u6B08 1246-2
+always \u6B09 245-12346-5
+always \u6B0A 245-45-2
+always \u6B0B 245-1256-2
+always \u6B0C 245-1346-2
+always \u6B0D 13-234-4
+always \u6B0E 1256-5
+always \u6B0F 14-25-2
+always \u6B10 14-16-5
+always \u6B11 245-12456-2
+always \u6B12 14-12456-2
+always \u6B13 145-1346-4
+always \u6B14 13-236-2
+always \u6B15 2345-2
+always \u6B16 14-1236-4
+always \u6B17 14-1236-2
+always \u6B18 1-34-4
+always \u6B19 14-356-2
+always \u6B1A 14-16-4
+always \u6B1B 135-345-5
+always \u6B1C 1345-1346-2
+always \u6B1D 1256-5
+always \u6B1E 14-13456-2
+always \u6B1F 13-456-3
+always \u6B20 245-2345-5
+always \u6B21 245-156-5
+always \u6B22 1235-12456-3
+always \u6B23 15-1456-3
+always \u6B24 1256-2
+always \u6B25 1256-5
+always \u6B26 245-2345-3
+always \u6B27 12356-3
+always \u6B28 15-1256-3
+always \u6B29 12-146-3
+always \u6B2A 12-34-5
+always \u6B2B 12-156-3
+always \u6B2C 123-2456-5
+always \u6B2D 1456-3
+always \u6B2E 13-236-2
+always \u6B2F 15-16-2
+always \u6B30 15-1256-3
+always \u6B31 1235-2346-3
+always \u6B32 1256-5
+always \u6B33 123-2356-5
+always \u6B34 14-1346-2
+always \u6B35 123-12456-4
+always \u6B36 24-25-5
+always \u6B37 15-16-3
+always \u6B38 356-5
+always \u6B39 16-3
+always \u6B3A 245-16-3
+always \u6B3B 12-35-3
+always \u6B3C 12-156-4
+always \u6B3D 245-1456-3
+always \u6B3E 123-12456-4
+always \u6B3F 123-1236-4
+always \u6B40 123-12456-4
+always \u6B41 123-1236-4
+always \u6B42 12-12456-2
+always \u6B43 24-345-5
+always \u6B44 13-35-3
+always \u6B45 1456-3
+always \u6B46 15-1456-3
+always \u6B47 15-346-3
+always \u6B48 1256-2
+always \u6B49 245-2345-5
+always \u6B4A 15-246-3
+always \u6B4B 16-2
+always \u6B4C 13-2346-3
+always \u6B4D 34-3
+always \u6B4E 124-1236-5
+always \u6B4F 13-1456-5
+always \u6B50 12356-3
+always \u6B51 1235-34-3
+always \u6B52 124-16-5
+always \u6B53 1235-12456-3
+always \u6B54 15-1256-3
+always \u6B55 1234-136-3
+always \u6B56 15-16-3
+always \u6B57 15-246-5
+always \u6B58 15-1256-3
+always \u6B59 15-16-5
+always \u6B5A 24-1236-5
+always \u6B5B 14-2345-5
+always \u6B5C 12-34-5
+always \u6B5D 16-5
+always \u6B5E 2346-5
+always \u6B5F 1256-2
+always \u6B60 12-25-5
+always \u6B61 1235-12456-3
+always \u6B62 1-156-4
+always \u6B63 1-1356-5
+always \u6B64 245-156-4
+always \u6B65 135-34-5
+always \u6B66 34-4
+always \u6B67 245-16-2
+always \u6B68 135-34-5
+always \u6B69 135-34-5
+always \u6B6A 2356-3
+always \u6B6B 13-1256-5
+always \u6B6C 245-2345-2
+always \u6B6D 12-156-2
+always \u6B6E 15-2346-5
+always \u6B6F 12-156-4
+always \u6B70 15-2346-5
+always \u6B71 1-12346-4
+always \u6B72 15-1246-5
+always \u6B73 15-1246-5
+always \u6B74 14-16-5
+always \u6B75 245-25-5
+always \u6B76 1256-2
+always \u6B77 14-16-5
+always \u6B78 13-1246-3
+always \u6B79 145-2456-4
+always \u6B7A 145-2456-4
+always \u6B7B 15-156-4
+always \u6B7C 13-2345-3
+always \u6B7D 1-2346-2
+always \u6B7E 134-126-5
+always \u6B7F 134-126-5
+always \u6B80 246-4
+always \u6B81 134-126-5
+always \u6B82 245-34-2
+always \u6B83 46-3
+always \u6B84 124-2345-4
+always \u6B85 24-1356-3
+always \u6B86 145-2456-5
+always \u6B87 24-1346-3
+always \u6B88 15-1256-5
+always \u6B89 15-256-5
+always \u6B8A 24-34-3
+always \u6B8B 245-1236-2
+always \u6B8C 13-236-2
+always \u6B8D 1234-246-4
+always \u6B8E 245-23456-5
+always \u6B8F 245-234-2
+always \u6B90 15-34-5
+always \u6B91 245-13456-2
+always \u6B92 256-4
+always \u6B93 14-2345-5
+always \u6B94 16-5
+always \u6B95 12345-12356-4
+always \u6B96 1-156-2
+always \u6B97 346-5
+always \u6B98 245-1236-2
+always \u6B99 1235-123456-3
+always \u6B9A 145-1236-3
+always \u6B9B 13-16-2
+always \u6B9C 346-5
+always \u6B9D 1-136-3
+always \u6B9E 256-4
+always \u6B9F 123456-3
+always \u6BA0 12-12356-5
+always \u6BA1 135-1456-5
+always \u6BA2 124-16-5
+always \u6BA3 13-1456-5
+always \u6BA4 24-1346-3
+always \u6BA5 1456-2
+always \u6BA6 145-246-3
+always \u6BA7 245-34-5
+always \u6BA8 1235-1246-5
+always \u6BA9 245-12456-5
+always \u6BAA 16-5
+always \u6BAB 145-1236-3
+always \u6BAC 145-34-5
+always \u6BAD 13-46-3
+always \u6BAE 14-2345-5
+always \u6BAF 135-1456-5
+always \u6BB0 145-34-2
+always \u6BB1 13-2345-3
+always \u6BB2 13-2345-3
+always \u6BB3 24-34-3
+always \u6BB4 12356-3
+always \u6BB5 145-12456-5
+always \u6BB6 1-34-5
+always \u6BB7 1456-3
+always \u6BB8 245-13456-5
+always \u6BB9 16-3
+always \u6BBA 24-345-3
+always \u6BBB 123-2346-2
+always \u6BBC 123-2346-2
+always \u6BBD 246-2
+always \u6BBE 13-256-5
+always \u6BBF 145-2345-5
+always \u6BC0 1235-1246-4
+always \u6BC1 1235-1246-4
+always \u6BC2 13-34-4
+always \u6BC3 245-246-3
+always \u6BC4 13-16-3
+always \u6BC5 16-5
+always \u6BC6 12356-3
+always \u6BC7 1235-1246-4
+always \u6BC8 145-12456-5
+always \u6BC9 16-3
+always \u6BCA 15-246-3
+always \u6BCB 34-2
+always \u6BCC 13-12456-5
+always \u6BCD 134-34-4
+always \u6BCE 134-356-4
+always \u6BCF 134-356-4
+always \u6BD0 2456-4
+always \u6BD1 125-25-4
+always \u6BD2 145-34-2
+always \u6BD3 1256-5
+always \u6BD4 135-16-4
+always \u6BD5 135-16-5
+always \u6BD6 135-16-5
+always \u6BD7 1234-16-2
+always \u6BD8 1234-16-2
+always \u6BD9 135-16-5
+always \u6BDA 12-1236-2
+always \u6BDB 134-146-2
+always \u6BDC 1235-146-2
+always \u6BDD 24-1236-3
+always \u6BDE 1234-16-2
+always \u6BDF 14-346-4
+always \u6BE0 13-23456-3
+always \u6BE1 1-1236-3
+always \u6BE2 15-2456-3
+always \u6BE3 134-34-5
+always \u6BE4 124-25-5
+always \u6BE5 15-256-2
+always \u6BE6 156-5
+always \u6BE7 1245-12346-2
+always \u6BE8 15-2345-4
+always \u6BE9 13-1256-2
+always \u6BEA 134-34-2
+always \u6BEB 1235-146-2
+always \u6BEC 245-234-2
+always \u6BED 145-12356-5
+always \u6BEE 24-345-3
+always \u6BEF 124-1236-4
+always \u6BF0 1234-356-2
+always \u6BF1 13-1256-2
+always \u6BF2 145-25-2
+always \u6BF3 245-1246-5
+always \u6BF4 135-16-3
+always \u6BF5 15-1236-3
+always \u6BF6 15-1236-3
+always \u6BF7 134-146-5
+always \u6BF8 15-1246-3
+always \u6BF9 24-34-3
+always \u6BFA 1256-3
+always \u6BFB 124-25-5
+always \u6BFC 1235-2346-2
+always \u6BFD 13-2345-5
+always \u6BFE 124-345-5
+always \u6BFF 15-1236-3
+always \u6C00 14-1256-2
+always \u6C01 134-34-2
+always \u6C02 134-146-2
+always \u6C03 124-12346-2
+always \u6C04 1245-12346-4
+always \u6C05 12-1346-4
+always \u6C06 1234-34-4
+always \u6C07 14-34-4
+always \u6C08 1-1236-3
+always \u6C09 125-146-5
+always \u6C0A 1-1236-3
+always \u6C0B 134-1356-2
+always \u6C0C 14-34-4
+always \u6C0D 245-1256-2
+always \u6C0E 145-346-2
+always \u6C0F 24-156-5
+always \u6C10 145-16-3
+always \u6C11 134-1456-2
+always \u6C12 13-236-2
+always \u6C13 134-1346-2
+always \u6C14 245-16-5
+always \u6C15 1234-346-3
+always \u6C16 1345-2456-4
+always \u6C17 245-16-5
+always \u6C18 145-146-3
+always \u6C19 15-2345-3
+always \u6C1A 12-12456-3
+always \u6C1B 12345-136-3
+always \u6C1C 1245-156-5
+always \u6C1D 1345-356-5
+always \u6C1E 135-1456-5
+always \u6C1F 12345-34-2
+always \u6C20 24-136-3
+always \u6C21 145-12346-3
+always \u6C22 245-13456-3
+always \u6C23 245-16-5
+always \u6C24 1456-3
+always \u6C25 15-16-3
+always \u6C26 1235-2456-5
+always \u6C27 46-4
+always \u6C28 1236-3
+always \u6C29 23456-5
+always \u6C2A 123-2346-5
+always \u6C2B 245-13456-3
+always \u6C2C 23456-5
+always \u6C2D 145-12346-3
+always \u6C2E 145-1236-5
+always \u6C2F 14-1256-5
+always \u6C30 245-13456-2
+always \u6C31 46-4
+always \u6C32 256-3
+always \u6C33 256-3
+always \u6C34 24-1246-4
+always \u6C35 24-1246-4
+always \u6C36 1-1356-4
+always \u6C37 135-13456-3
+always \u6C38 235-4
+always \u6C39 145-1346-5
+always \u6C3A 24-1246-4
+always \u6C3B 14-2346-5
+always \u6C3C 1345-16-5
+always \u6C3D 124-123456-4
+always \u6C3E 12345-1236-5
+always \u6C3F 13-1246-4
+always \u6C40 124-13456-3
+always \u6C41 1-156-3
+always \u6C42 245-234-2
+always \u6C43 135-1456-3
+always \u6C44 125-2346-5
+always \u6C45 134-2345-4
+always \u6C46 245-12456-3
+always \u6C47 1235-1246-5
+always \u6C48 145-246-3
+always \u6C49 1235-1236-5
+always \u6C4A 12-345-5
+always \u6C4B 1-25-2
+always \u6C4C 12-12456-5
+always \u6C4D 12456-2
+always \u6C4E 12345-1236-5
+always \u6C4F 145-2456-5
+always \u6C50 15-16-5
+always \u6C51 124-25-3
+always \u6C52 134-1346-2
+always \u6C53 245-234-2
+always \u6C54 245-16-5
+always \u6C55 24-1236-5
+always \u6C56 1234-2456-5
+always \u6C57 1235-1236-5
+always \u6C58 245-2345-3
+always \u6C59 34-3
+always \u6C5A 34-3
+always \u6C5B 15-256-5
+always \u6C5C 15-156-5
+always \u6C5D 1245-34-4
+always \u6C5E 13-12346-4
+always \u6C5F 13-46-3
+always \u6C60 12-156-2
+always \u6C61 34-3
+always \u6C62 124-34-4
+always \u6C63 13-1246-4
+always \u6C64 124-1346-3
+always \u6C65 1-156-3
+always \u6C66 1-156-4
+always \u6C67 245-2345-3
+always \u6C68 134-16-5
+always \u6C69 13-34-4
+always \u6C6A 456-3
+always \u6C6B 13-13456-4
+always \u6C6C 13-13456-4
+always \u6C6D 1245-1246-5
+always \u6C6E 13-256-3
+always \u6C6F 1235-12346-2
+always \u6C70 124-2456-5
+always \u6C71 245-45-4
+always \u6C72 13-16-2
+always \u6C73 135-2345-5
+always \u6C74 135-2345-5
+always \u6C75 13-1236-5
+always \u6C76 123456-5
+always \u6C77 1-12346-3
+always \u6C78 12345-1346-3
+always \u6C79 15-235-3
+always \u6C7A 13-236-2
+always \u6C7B 1235-34-4
+always \u6C7C 1345-234-2
+always \u6C7D 245-16-5
+always \u6C7E 12345-136-2
+always \u6C7F 15-1256-5
+always \u6C80 15-1256-5
+always \u6C81 245-1456-5
+always \u6C82 16-2
+always \u6C83 25-5
+always \u6C84 256-2
+always \u6C85 45-2
+always \u6C86 1235-1346-5
+always \u6C87 2345-4
+always \u6C88 24-136-4
+always \u6C89 12-136-2
+always \u6C8A 145-1236-5
+always \u6C8B 234-2
+always \u6C8C 145-123456-5
+always \u6C8D 1235-34-5
+always \u6C8E 1235-25-5
+always \u6C8F 245-16-3
+always \u6C90 134-34-5
+always \u6C91 1245-12356-2
+always \u6C92 134-356-2
+always \u6C93 145-345-2
+always \u6C94 134-2345-4
+always \u6C95 34-5
+always \u6C96 12-12346-3
+always \u6C97 124-2345-3
+always \u6C98 135-16-4
+always \u6C99 24-345-3
+always \u6C9A 1-156-4
+always \u6C9B 1234-356-5
+always \u6C9C 1234-1236-5
+always \u6C9D 1-1246-4
+always \u6C9E 125-345-3
+always \u6C9F 13-12356-3
+always \u6CA0 14-234-2
+always \u6CA1 134-356-2
+always \u6CA2 125-2346-2
+always \u6CA3 12345-1356-3
+always \u6CA4 12356-5
+always \u6CA5 14-16-5
+always \u6CA6 14-123456-2
+always \u6CA7 245-1346-3
+always \u6CA8 12345-1356-2
+always \u6CA9 13-1246-3
+always \u6CAA 1235-34-5
+always \u6CAB 134-126-5
+always \u6CAC 134-356-5
+always \u6CAD 24-34-5
+always \u6CAE 13-1256-4
+always \u6CAF 125-1236-4
+always \u6CB0 124-25-3
+always \u6CB1 124-25-2
+always \u6CB2 124-25-2
+always \u6CB3 1235-2346-2
+always \u6CB4 14-16-5
+always \u6CB5 134-16-4
+always \u6CB6 16-2
+always \u6CB7 12345-345-3
+always \u6CB8 12345-356-5
+always \u6CB9 234-2
+always \u6CBA 124-2345-2
+always \u6CBB 1-156-5
+always \u6CBC 1-146-4
+always \u6CBD 13-34-3
+always \u6CBE 1-1236-3
+always \u6CBF 2345-2
+always \u6CC0 15-156-3
+always \u6CC1 123-456-5
+always \u6CC2 13-235-4
+always \u6CC3 13-1256-5
+always \u6CC4 15-346-5
+always \u6CC5 245-234-2
+always \u6CC6 16-3
+always \u6CC7 13-23456-3
+always \u6CC8 1-12346-3
+always \u6CC9 245-45-2
+always \u6CCA 135-126-2
+always \u6CCB 1235-1246-5
+always \u6CCC 134-16-5
+always \u6CCD 135-136-3
+always \u6CCE 1-25-2
+always \u6CCF 12-34-5
+always \u6CD0 14-2346-5
+always \u6CD1 234-4
+always \u6CD2 13-34-3
+always \u6CD3 1235-12346-2
+always \u6CD4 13-1236-3
+always \u6CD5 12345-345-4
+always \u6CD6 134-146-4
+always \u6CD7 15-156-5
+always \u6CD8 1235-34-3
+always \u6CD9 1234-13456-2
+always \u6CDA 245-156-4
+always \u6CDB 12345-1236-5
+always \u6CDC 1-156-3
+always \u6CDD 15-34-5
+always \u6CDE 12-34-4
+always \u6CDF 12-1356-3
+always \u6CE0 14-13456-2
+always \u6CE1 1234-146-5
+always \u6CE2 135-126-3
+always \u6CE3 245-16-5
+always \u6CE4 15-156-5
+always \u6CE5 1345-16-2
+always \u6CE6 13-1256-2
+always \u6CE7 236-5
+always \u6CE8 1-34-5
+always \u6CE9 24-1356-3
+always \u6CEA 14-356-5
+always \u6CEB 15-45-5
+always \u6CEC 15-236-5
+always \u6CED 12345-34-2
+always \u6CEE 1234-1236-5
+always \u6CEF 134-1456-4
+always \u6CF0 124-2456-5
+always \u6CF1 46-3
+always \u6CF2 13-16-4
+always \u6CF3 235-4
+always \u6CF4 13-12456-5
+always \u6CF5 135-1356-5
+always \u6CF6 15-236-2
+always \u6CF7 14-12346-2
+always \u6CF8 14-34-2
+always \u6CF9 145-1236-5
+always \u6CFA 14-25-5
+always \u6CFB 15-346-5
+always \u6CFC 1234-126-3
+always \u6CFD 125-2346-2
+always \u6CFE 13-13456-3
+always \u6CFF 1456-2
+always \u6D00 1-12356-3
+always \u6D01 13-346-2
+always \u6D02 16-5
+always \u6D03 1235-1246-3
+always \u6D04 1235-1246-2
+always \u6D05 125-1246-4
+always \u6D06 12-1356-2
+always \u6D07 1456-3
+always \u6D08 1246-2
+always \u6D09 1235-12356-5
+always \u6D0A 13-2345-5
+always \u6D0B 46-2
+always \u6D0C 14-346-5
+always \u6D0D 15-156-5
+always \u6D0E 13-16-5
+always \u6D0F 156-2
+always \u6D10 15-13456-2
+always \u6D11 12345-34-2
+always \u6D12 15-345-4
+always \u6D13 15-25-4
+always \u6D14 1-156-4
+always \u6D15 1456-3
+always \u6D16 34-2
+always \u6D17 15-16-4
+always \u6D18 123-146-4
+always \u6D19 1-34-3
+always \u6D1A 13-46-5
+always \u6D1B 14-25-5
+always \u6D1C 14-25-5
+always \u6D1D 1236-5
+always \u6D1E 145-12346-5
+always \u6D1F 16-2
+always \u6D20 134-12356-2
+always \u6D21 14-356-4
+always \u6D22 16-3
+always \u6D23 134-16-4
+always \u6D24 245-45-2
+always \u6D25 13-1456-3
+always \u6D26 134-126-5
+always \u6D27 1246-4
+always \u6D28 15-246-2
+always \u6D29 15-346-5
+always \u6D2A 1235-12346-2
+always \u6D2B 15-1256-5
+always \u6D2C 24-25-5
+always \u6D2D 123-456-3
+always \u6D2E 124-146-2
+always \u6D2F 245-346-5
+always \u6D30 13-1256-5
+always \u6D31 156-4
+always \u6D32 1-12356-3
+always \u6D33 1245-34-5
+always \u6D34 1234-13456-2
+always \u6D35 15-256-2
+always \u6D36 15-235-3
+always \u6D37 1-156-5
+always \u6D38 13-456-3
+always \u6D39 1235-12456-2
+always \u6D3A 134-13456-2
+always \u6D3B 1235-25-2
+always \u6D3C 35-3
+always \u6D3D 245-23456-5
+always \u6D3E 1234-2456-5
+always \u6D3F 34-3
+always \u6D40 245-1256-4
+always \u6D41 14-234-2
+always \u6D42 16-5
+always \u6D43 13-23456-2
+always \u6D44 13-13456-5
+always \u6D45 245-2345-4
+always \u6D46 13-46-3
+always \u6D47 13-246-3
+always \u6D48 1-1356-3
+always \u6D49 24-156-3
+always \u6D4A 1-25-2
+always \u6D4B 245-2346-5
+always \u6D4C 12345-345-2
+always \u6D4D 123-2356-5
+always \u6D4E 13-16-5
+always \u6D4F 14-234-2
+always \u6D50 12-1236-4
+always \u6D51 1235-123456-2
+always \u6D52 1235-34-4
+always \u6D53 1345-12346-2
+always \u6D54 15-256-2
+always \u6D55 13-1456-5
+always \u6D56 14-346-5
+always \u6D57 245-234-2
+always \u6D58 1246-4
+always \u6D59 1-2346-5
+always \u6D5A 13-256-5
+always \u6D5B 1235-1236-5
+always \u6D5C 135-1346-3
+always \u6D5D 134-1346-2
+always \u6D5E 1-25-2
+always \u6D5F 234-2
+always \u6D60 15-16-3
+always \u6D61 135-126-2
+always \u6D62 145-12356-5
+always \u6D63 1235-12456-4
+always \u6D64 1235-12346-2
+always \u6D65 16-5
+always \u6D66 1234-34-4
+always \u6D67 13456-4
+always \u6D68 14-1236-4
+always \u6D69 1235-146-5
+always \u6D6A 14-1346-5
+always \u6D6B 1235-1236-4
+always \u6D6C 14-16-4
+always \u6D6D 13-1356-3
+always \u6D6E 12345-34-2
+always \u6D6F 34-2
+always \u6D70 14-2345-5
+always \u6D71 12-123456-2
+always \u6D72 12345-1356-2
+always \u6D73 16-5
+always \u6D74 1256-5
+always \u6D75 124-12346-2
+always \u6D76 14-146-2
+always \u6D77 1235-2456-4
+always \u6D78 13-1456-5
+always \u6D79 13-23456-2
+always \u6D7A 12-12346-3
+always \u6D7B 12346-4
+always \u6D7C 134-356-4
+always \u6D7D 15-1246-3
+always \u6D7E 12-1356-3
+always \u6D7F 1234-356-5
+always \u6D80 15-2345-5
+always \u6D81 24-136-5
+always \u6D82 124-34-2
+always \u6D83 123-123456-5
+always \u6D84 1234-1456-3
+always \u6D85 1345-346-5
+always \u6D86 1235-1236-5
+always \u6D87 13-13456-3
+always \u6D88 15-246-3
+always \u6D89 24-2346-5
+always \u6D8A 1345-2345-4
+always \u6D8B 124-34-3
+always \u6D8C 235-4
+always \u6D8D 15-246-5
+always \u6D8E 15-2345-2
+always \u6D8F 124-13456-4
+always \u6D90 2346-2
+always \u6D91 15-34-5
+always \u6D92 124-123456-3
+always \u6D93 13-45-3
+always \u6D94 245-136-2
+always \u6D95 124-16-5
+always \u6D96 14-16-5
+always \u6D97 24-1246-5
+always \u6D98 15-156-5
+always \u6D99 14-356-5
+always \u6D9A 24-1246-5
+always \u6D9B 124-146-3
+always \u6D9C 145-34-2
+always \u6D9D 14-146-5
+always \u6D9E 14-2456-2
+always \u6D9F 14-2345-2
+always \u6DA0 1246-2
+always \u6DA1 25-3
+always \u6DA2 256-2
+always \u6DA3 1235-12456-5
+always \u6DA4 145-16-2
+always \u6DA5 135-1356-3
+always \u6DA6 1245-123456-5
+always \u6DA7 13-2345-5
+always \u6DA8 1-1346-4
+always \u6DA9 15-2346-5
+always \u6DAA 12345-34-2
+always \u6DAB 13-12456-5
+always \u6DAC 15-13456-5
+always \u6DAD 24-12356-5
+always \u6DAE 24-12456-5
+always \u6DAF 23456-2
+always \u6DB0 12-25-5
+always \u6DB1 1-1346-5
+always \u6DB2 16-5
+always \u6DB3 123-12346-3
+always \u6DB4 25-5
+always \u6DB5 1235-1236-2
+always \u6DB6 124-25-3
+always \u6DB7 145-12346-3
+always \u6DB8 1235-2346-2
+always \u6DB9 25-3
+always \u6DBA 13-1256-3
+always \u6DBB 24-2346-5
+always \u6DBC 14-46-2
+always \u6DBD 1235-123456-3
+always \u6DBE 124-345-5
+always \u6DBF 1-25-3
+always \u6DC0 145-2345-5
+always \u6DC1 245-346-5
+always \u6DC2 145-2346-2
+always \u6DC3 13-45-5
+always \u6DC4 125-156-3
+always \u6DC5 15-16-3
+always \u6DC6 246-2
+always \u6DC7 245-16-2
+always \u6DC8 13-34-4
+always \u6DC9 13-25-4
+always \u6DCA 1235-1236-5
+always \u6DCB 14-1456-2
+always \u6DCC 124-1346-4
+always \u6DCD 1-12356-3
+always \u6DCE 1234-1356-4
+always \u6DCF 1235-146-5
+always \u6DD0 12-1346-3
+always \u6DD1 24-34-2
+always \u6DD2 245-16-3
+always \u6DD3 12345-1346-3
+always \u6DD4 12-156-5
+always \u6DD5 14-34-5
+always \u6DD6 1345-146-5
+always \u6DD7 13-1256-2
+always \u6DD8 124-146-2
+always \u6DD9 245-12346-2
+always \u6DDA 14-356-5
+always \u6DDB 1-2346-5
+always \u6DDC 1234-1356-2
+always \u6DDD 12345-356-2
+always \u6DDE 15-12346-3
+always \u6DDF 124-2345-4
+always \u6DE0 1234-16-5
+always \u6DE1 145-1236-5
+always \u6DE2 1256-5
+always \u6DE3 1345-16-2
+always \u6DE4 1256-3
+always \u6DE5 14-34-5
+always \u6DE6 13-1236-5
+always \u6DE7 134-16-5
+always \u6DE8 13-13456-5
+always \u6DE9 14-13456-2
+always \u6DEA 14-123456-2
+always \u6DEB 1456-2
+always \u6DEC 245-1246-5
+always \u6DED 245-1256-2
+always \u6DEE 1235-2356-2
+always \u6DEF 1256-5
+always \u6DF0 1345-2345-4
+always \u6DF1 24-136-3
+always \u6DF2 1234-246-2
+always \u6DF3 12-123456-2
+always \u6DF4 35-5
+always \u6DF5 45-3
+always \u6DF6 14-2456-2
+always \u6DF7 1235-123456-5
+always \u6DF8 245-13456-3
+always \u6DF9 2345-3
+always \u6DFA 245-2345-4
+always \u6DFB 124-2345-3
+always \u6DFC 134-246-4
+always \u6DFD 1-156-4
+always \u6DFE 1456-4
+always \u6DFF 134-16-5
+always \u6E00 135-136-3
+always \u6E01 45-3
+always \u6E02 123456-5
+always \u6E03 1245-2346-5
+always \u6E04 12345-356-3
+always \u6E05 245-13456-3
+always \u6E06 45-3
+always \u6E07 123-2346-4
+always \u6E08 13-16-5
+always \u6E09 24-2346-5
+always \u6E0A 45-3
+always \u6E0B 15-2346-5
+always \u6E0C 14-34-5
+always \u6E0D 125-156-5
+always \u6E0E 145-34-2
+always \u6E0F 245-16-2
+always \u6E10 13-2345-5
+always \u6E11 134-2345-4
+always \u6E12 1234-16-5
+always \u6E13 15-16-3
+always \u6E14 1256-2
+always \u6E15 45-3
+always \u6E16 24-136-4
+always \u6E17 24-136-5
+always \u6E18 1245-12356-2
+always \u6E19 1235-12456-5
+always \u6E1A 1-34-4
+always \u6E1B 13-2345-4
+always \u6E1C 1345-12456-4
+always \u6E1D 1256-2
+always \u6E1E 245-234-2
+always \u6E1F 124-13456-2
+always \u6E20 245-1256-2
+always \u6E21 145-34-5
+always \u6E22 12345-1356-2
+always \u6E23 1-345-3
+always \u6E24 135-126-2
+always \u6E25 25-5
+always \u6E26 25-3
+always \u6E27 145-16-5
+always \u6E28 1246-3
+always \u6E29 123456-3
+always \u6E2A 1245-34-2
+always \u6E2B 15-346-5
+always \u6E2C 245-2346-5
+always \u6E2D 1246-5
+always \u6E2E 13-2346-3
+always \u6E2F 13-1346-4
+always \u6E30 2345-4
+always \u6E31 1235-12346-2
+always \u6E32 15-45-5
+always \u6E33 134-16-4
+always \u6E34 123-2346-4
+always \u6E35 134-146-2
+always \u6E36 13456-3
+always \u6E37 2345-4
+always \u6E38 234-2
+always \u6E39 1235-12346-3
+always \u6E3A 134-246-4
+always \u6E3B 15-13456-4
+always \u6E3C 134-356-4
+always \u6E3D 125-2456-3
+always \u6E3E 1235-123456-2
+always \u6E3F 1345-2456-5
+always \u6E40 123-1246-2
+always \u6E41 12-156-5
+always \u6E42 2346-5
+always \u6E43 1234-2456-5
+always \u6E44 134-356-2
+always \u6E45 14-2345-5
+always \u6E46 245-16-5
+always \u6E47 245-16-5
+always \u6E48 134-356-2
+always \u6E49 124-2345-2
+always \u6E4A 245-12356-5
+always \u6E4B 1246-2
+always \u6E4C 245-1236-3
+always \u6E4D 124-12456-3
+always \u6E4E 134-2345-4
+always \u6E4F 1235-1246-5
+always \u6E50 134-126-5
+always \u6E51 15-1256-4
+always \u6E52 13-16-2
+always \u6E53 1234-136-2
+always \u6E54 13-2345-3
+always \u6E55 13-2345-4
+always \u6E56 1235-34-2
+always \u6E57 12345-1356-5
+always \u6E58 15-46-3
+always \u6E59 16-5
+always \u6E5A 1456-5
+always \u6E5B 1-1236-5
+always \u6E5C 24-156-2
+always \u6E5D 13-346-3
+always \u6E5E 1-136-3
+always \u6E5F 1235-456-2
+always \u6E60 124-1236-5
+always \u6E61 1256-2
+always \u6E62 135-16-5
+always \u6E63 134-1456-4
+always \u6E64 24-156-3
+always \u6E65 124-34-2
+always \u6E66 24-1356-3
+always \u6E67 235-4
+always \u6E68 245-1256-5
+always \u6E69 145-12346-5
+always \u6E6A 124-12456-5
+always \u6E6B 245-234-3
+always \u6E6C 13-246-4
+always \u6E6D 245-234-2
+always \u6E6E 2345-3
+always \u6E6F 124-1346-3
+always \u6E70 14-12346-2
+always \u6E71 1235-25-5
+always \u6E72 45-2
+always \u6E73 1345-1236-4
+always \u6E74 135-1236-5
+always \u6E75 234-4
+always \u6E76 245-45-2
+always \u6E77 12-1246-2
+always \u6E78 14-46-5
+always \u6E79 12-1236-2
+always \u6E7A 2345-2
+always \u6E7B 12-123456-2
+always \u6E7C 1345-346-5
+always \u6E7D 125-156-3
+always \u6E7E 12456-3
+always \u6E7F 24-156-3
+always \u6E80 134-1236-4
+always \u6E81 13456-2
+always \u6E82 14-345-5
+always \u6E83 123-1246-5
+always \u6E84 12345-1356-3
+always \u6E85 13-2345-5
+always \u6E86 15-1256-5
+always \u6E87 14-12356-2
+always \u6E88 13-1246-3
+always \u6E89 13-2456-5
+always \u6E8A 15-23456-2
+always \u6E8B 13456-2
+always \u6E8C 1234-126-3
+always \u6E8D 13-1456-5
+always \u6E8E 13-1246-5
+always \u6E8F 124-1346-2
+always \u6E90 45-2
+always \u6E91 15-25-4
+always \u6E92 45-2
+always \u6E93 14-2345-2
+always \u6E94 246-4
+always \u6E95 134-1356-5
+always \u6E96 1-123456-4
+always \u6E97 24-1356-2
+always \u6E98 123-2346-5
+always \u6E99 124-2456-5
+always \u6E9A 145-345-2
+always \u6E9B 35-3
+always \u6E9C 14-234-3
+always \u6E9D 13-12356-3
+always \u6E9E 15-146-3
+always \u6E9F 134-13456-2
+always \u6EA0 1-345-5
+always \u6EA1 24-156-2
+always \u6EA2 16-5
+always \u6EA3 14-123456-2
+always \u6EA4 134-345-4
+always \u6EA5 1234-34-4
+always \u6EA6 1246-3
+always \u6EA7 14-16-5
+always \u6EA8 245-2456-2
+always \u6EA9 34-5
+always \u6EAA 15-16-3
+always \u6EAB 123456-3
+always \u6EAC 245-46-3
+always \u6EAD 125-2346-2
+always \u6EAE 24-156-3
+always \u6EAF 15-34-5
+always \u6EB0 16-3
+always \u6EB1 245-1456-2
+always \u6EB2 15-12356-3
+always \u6EB3 256-2
+always \u6EB4 15-234-5
+always \u6EB5 1456-3
+always \u6EB6 1245-12346-2
+always \u6EB7 1235-123456-5
+always \u6EB8 15-34-5
+always \u6EB9 15-34-5
+always \u6EBA 1345-16-5
+always \u6EBB 124-345-3
+always \u6EBC 24-156-3
+always \u6EBD 1245-34-5
+always \u6EBE 1246-3
+always \u6EBF 1234-1236-5
+always \u6EC0 12-34-5
+always \u6EC1 12-34-2
+always \u6EC2 1234-1346-3
+always \u6EC3 12346-4
+always \u6EC4 245-1346-3
+always \u6EC5 134-346-5
+always \u6EC6 1235-2346-2
+always \u6EC7 145-2345-3
+always \u6EC8 1235-146-5
+always \u6EC9 1235-456-4
+always \u6ECA 15-16-5
+always \u6ECB 125-156-3
+always \u6ECC 145-16-2
+always \u6ECD 1-156-4
+always \u6ECE 13456-2
+always \u6ECF 12345-34-4
+always \u6ED0 13-346-2
+always \u6ED1 1235-35-2
+always \u6ED2 13-2346-3
+always \u6ED3 125-156-4
+always \u6ED4 124-146-3
+always \u6ED5 124-1356-2
+always \u6ED6 15-1246-3
+always \u6ED7 135-16-5
+always \u6ED8 13-246-5
+always \u6ED9 1235-1246-5
+always \u6EDA 13-123456-4
+always \u6EDB 1456-2
+always \u6EDC 13-146-3
+always \u6EDD 14-12346-2
+always \u6EDE 1-156-5
+always \u6EDF 2345-5
+always \u6EE0 24-2346-5
+always \u6EE1 134-1236-4
+always \u6EE2 13456-2
+always \u6EE3 12-123456-2
+always \u6EE4 14-1256-5
+always \u6EE5 14-1236-5
+always \u6EE6 14-12456-2
+always \u6EE7 15-246-5
+always \u6EE8 135-1456-3
+always \u6EE9 124-1236-3
+always \u6EEA 1256-5
+always \u6EEB 15-234-5
+always \u6EEC 1235-34-5
+always \u6EED 135-16-5
+always \u6EEE 135-246-3
+always \u6EEF 1-156-5
+always \u6EF0 13-46-4
+always \u6EF1 123-12356-5
+always \u6EF2 24-136-5
+always \u6EF3 24-1346-3
+always \u6EF4 145-16-3
+always \u6EF5 134-16-5
+always \u6EF6 146-2
+always \u6EF7 14-34-4
+always \u6EF8 1235-34-4
+always \u6EF9 1235-34-3
+always \u6EFA 234-2
+always \u6EFB 12-1236-4
+always \u6EFC 12345-1236-5
+always \u6EFD 235-2
+always \u6EFE 13-123456-4
+always \u6EFF 134-1236-4
+always \u6F00 245-13456-5
+always \u6F01 1256-2
+always \u6F02 1234-246-3
+always \u6F03 13-16-2
+always \u6F04 23456-2
+always \u6F05 13-246-4
+always \u6F06 245-16-3
+always \u6F07 15-16-4
+always \u6F08 13-16-5
+always \u6F09 14-34-5
+always \u6F0A 14-12356-2
+always \u6F0B 14-12346-2
+always \u6F0C 13-1456-4
+always \u6F0D 13-25-2
+always \u6F0E 245-12346-2
+always \u6F0F 14-12356-5
+always \u6F10 1-156-2
+always \u6F11 13-2456-5
+always \u6F12 245-46-2
+always \u6F13 14-16-2
+always \u6F14 2345-4
+always \u6F15 245-146-2
+always \u6F16 13-246-5
+always \u6F17 245-12346-3
+always \u6F18 12-123456-2
+always \u6F19 124-12456-2
+always \u6F1A 12356-5
+always \u6F1B 124-1356-2
+always \u6F1C 346-4
+always \u6F1D 15-16-2
+always \u6F1E 134-16-5
+always \u6F1F 124-1346-2
+always \u6F20 134-126-5
+always \u6F21 24-1346-3
+always \u6F22 1235-1236-5
+always \u6F23 14-2345-2
+always \u6F24 14-1236-4
+always \u6F25 35-3
+always \u6F26 14-16-2
+always \u6F27 245-2345-2
+always \u6F28 12345-1356-2
+always \u6F29 15-45-2
+always \u6F2A 16-3
+always \u6F2B 134-1236-5
+always \u6F2C 125-156-5
+always \u6F2D 134-1346-4
+always \u6F2E 123-1346-3
+always \u6F2F 14-25-5
+always \u6F30 1234-1356-3
+always \u6F31 24-34-5
+always \u6F32 1-1346-4
+always \u6F33 1-1346-3
+always \u6F34 12-12346-2
+always \u6F35 15-1256-5
+always \u6F36 1235-12456-5
+always \u6F37 123-25-5
+always \u6F38 13-2345-5
+always \u6F39 2345-3
+always \u6F3A 12-456-4
+always \u6F3B 14-246-2
+always \u6F3C 245-1246-4
+always \u6F3D 124-16-2
+always \u6F3E 46-5
+always \u6F3F 13-46-3
+always \u6F40 245-12346-2
+always \u6F41 13456-4
+always \u6F42 1235-12346-2
+always \u6F43 15-234-5
+always \u6F44 24-34-5
+always \u6F45 13-12456-5
+always \u6F46 13456-2
+always \u6F47 15-246-3
+always \u6F48 124-12346-4
+always \u6F49 123-123456-3
+always \u6F4A 15-1256-5
+always \u6F4B 14-2345-5
+always \u6F4C 1-156-5
+always \u6F4D 1246-2
+always \u6F4E 1234-16-5
+always \u6F4F 13-236-2
+always \u6F50 13-246-5
+always \u6F51 1234-126-3
+always \u6F52 145-1346-5
+always \u6F53 1235-1246-5
+always \u6F54 13-346-2
+always \u6F55 34-4
+always \u6F56 1234-345-2
+always \u6F57 13-16-2
+always \u6F58 1234-1236-3
+always \u6F59 13-1246-3
+always \u6F5A 15-246-3
+always \u6F5B 245-2345-2
+always \u6F5C 245-2345-2
+always \u6F5D 15-16-5
+always \u6F5E 14-34-5
+always \u6F5F 15-16-5
+always \u6F60 15-123456-5
+always \u6F61 145-123456-5
+always \u6F62 1235-456-2
+always \u6F63 134-1456-4
+always \u6F64 1245-123456-5
+always \u6F65 15-34-5
+always \u6F66 14-246-2
+always \u6F67 125-1356-3
+always \u6F68 1-12346-3
+always \u6F69 16-5
+always \u6F6A 145-16-2
+always \u6F6B 12456-3
+always \u6F6C 145-1236-5
+always \u6F6D 124-1236-2
+always \u6F6E 12-146-2
+always \u6F6F 15-256-2
+always \u6F70 123-1246-5
+always \u6F71 346-3
+always \u6F72 24-146-5
+always \u6F73 124-34-2
+always \u6F74 1-34-3
+always \u6F75 15-1236-5
+always \u6F76 1235-356-3
+always \u6F77 135-16-5
+always \u6F78 24-1236-3
+always \u6F79 12-1236-2
+always \u6F7A 12-1236-2
+always \u6F7B 24-34-4
+always \u6F7C 124-12346-2
+always \u6F7D 1234-34-4
+always \u6F7E 14-1456-2
+always \u6F7F 1246-2
+always \u6F80 15-2346-5
+always \u6F81 15-2346-5
+always \u6F82 12-1356-2
+always \u6F83 13-235-5
+always \u6F84 12-1356-2
+always \u6F85 1235-35-5
+always \u6F86 13-246-3
+always \u6F87 14-146-5
+always \u6F88 12-2346-5
+always \u6F89 13-1236-4
+always \u6F8A 245-123456-3
+always \u6F8B 1235-12346-5
+always \u6F8C 15-156-3
+always \u6F8D 24-34-5
+always \u6F8E 1234-1356-2
+always \u6F8F 1235-1236-5
+always \u6F90 256-2
+always \u6F91 14-234-5
+always \u6F92 1235-12346-5
+always \u6F93 12345-34-2
+always \u6F94 1235-146-5
+always \u6F95 1235-2346-2
+always \u6F96 15-2345-3
+always \u6F97 13-2345-5
+always \u6F98 24-1236-3
+always \u6F99 15-16-5
+always \u6F9A 236-5
+always \u6F9B 14-34-4
+always \u6F9C 14-1236-2
+always \u6F9D 134-13456-5
+always \u6F9E 1256-2
+always \u6F9F 14-1456-4
+always \u6FA0 134-1456-4
+always \u6FA1 125-146-4
+always \u6FA2 145-1346-3
+always \u6FA3 1235-12456-4
+always \u6FA4 125-2346-2
+always \u6FA5 15-346-5
+always \u6FA6 1256-5
+always \u6FA7 14-16-4
+always \u6FA8 24-156-5
+always \u6FA9 15-236-2
+always \u6FAA 14-13456-2
+always \u6FAB 134-1236-5
+always \u6FAC 125-156-3
+always \u6FAD 235-3
+always \u6FAE 123-2356-5
+always \u6FAF 245-1236-5
+always \u6FB0 14-2345-5
+always \u6FB1 145-2345-5
+always \u6FB2 346-5
+always \u6FB3 146-5
+always \u6FB4 1235-12456-2
+always \u6FB5 1-136-3
+always \u6FB6 12-1236-2
+always \u6FB7 134-1236-5
+always \u6FB8 145-1236-4
+always \u6FB9 145-1236-5
+always \u6FBA 16-5
+always \u6FBB 15-1246-5
+always \u6FBC 1234-16-5
+always \u6FBD 13-1256-5
+always \u6FBE 124-345-5
+always \u6FBF 245-1456-2
+always \u6FC0 13-16-3
+always \u6FC1 1-25-2
+always \u6FC2 14-2345-2
+always \u6FC3 1345-12346-2
+always \u6FC4 25-3
+always \u6FC5 13-1456-5
+always \u6FC6 1234-136-3
+always \u6FC7 15-2346-5
+always \u6FC8 13-16-2
+always \u6FC9 15-1246-3
+always \u6FCA 1235-1246-5
+always \u6FCB 12-34-4
+always \u6FCC 124-345-5
+always \u6FCD 15-12346-3
+always \u6FCE 145-13456-4
+always \u6FCF 15-2346-5
+always \u6FD0 1-34-4
+always \u6FD1 14-2456-5
+always \u6FD2 135-1456-3
+always \u6FD3 14-2345-2
+always \u6FD4 134-16-4
+always \u6FD5 24-156-3
+always \u6FD6 24-34-5
+always \u6FD7 134-16-5
+always \u6FD8 1345-13456-5
+always \u6FD9 13456-2
+always \u6FDA 13456-2
+always \u6FDB 134-1356-2
+always \u6FDC 13-1456-5
+always \u6FDD 245-16-2
+always \u6FDE 1234-16-5
+always \u6FDF 13-16-5
+always \u6FE0 1235-146-2
+always \u6FE1 1245-34-2
+always \u6FE2 125-1246-4
+always \u6FE3 25-5
+always \u6FE4 124-146-3
+always \u6FE5 1456-5
+always \u6FE6 1456-4
+always \u6FE7 145-1246-5
+always \u6FE8 245-156-2
+always \u6FE9 1235-25-5
+always \u6FEA 13-13456-5
+always \u6FEB 14-1236-5
+always \u6FEC 13-256-5
+always \u6FED 2456-5
+always \u6FEE 1234-34-2
+always \u6FEF 1-25-2
+always \u6FF0 1246-2
+always \u6FF1 135-1456-3
+always \u6FF2 13-34-4
+always \u6FF3 245-2345-2
+always \u6FF4 15-13456-2
+always \u6FF5 135-1456-3
+always \u6FF6 123-25-5
+always \u6FF7 12345-356-5
+always \u6FF8 245-1346-3
+always \u6FF9 134-2346-5
+always \u6FFA 13-2345-5
+always \u6FFB 1246-4
+always \u6FFC 14-25-5
+always \u6FFD 125-1236-5
+always \u6FFE 14-1256-5
+always \u6FFF 14-16-5
+always \u7000 234-3
+always \u7001 46-5
+always \u7002 14-34-4
+always \u7003 15-156-5
+always \u7004 13-346-2
+always \u7005 13456-2
+always \u7006 145-34-2
+always \u7007 456-4
+always \u7008 1235-1246-3
+always \u7009 15-346-5
+always \u700A 1234-1236-2
+always \u700B 24-136-4
+always \u700C 135-246-3
+always \u700D 12-1236-2
+always \u700E 134-126-5
+always \u700F 14-234-2
+always \u7010 13-2345-3
+always \u7011 1234-34-5
+always \u7012 15-2346-5
+always \u7013 12-1356-2
+always \u7014 13-34-4
+always \u7015 135-1456-3
+always \u7016 1235-25-5
+always \u7017 15-2345-5
+always \u7018 14-34-2
+always \u7019 245-1456-3
+always \u701A 1235-1236-5
+always \u701B 13456-2
+always \u701C 1245-12346-2
+always \u701D 14-16-5
+always \u701E 13-13456-5
+always \u701F 15-246-3
+always \u7020 13456-2
+always \u7021 15-1246-4
+always \u7022 1246-2
+always \u7023 15-346-5
+always \u7024 1235-2356-2
+always \u7025 1235-146-5
+always \u7026 1-34-3
+always \u7027 14-12346-2
+always \u7028 14-2456-5
+always \u7029 145-1246-5
+always \u702A 12345-1236-2
+always \u702B 1235-34-2
+always \u702C 14-2456-5
+always \u702D 24-34-3
+always \u702E 14-13456-2
+always \u702F 13456-2
+always \u7030 134-16-2
+always \u7031 13-16-5
+always \u7032 14-2345-5
+always \u7033 13-2345-5
+always \u7034 13456-4
+always \u7035 12345-136-5
+always \u7036 14-1456-2
+always \u7037 16-5
+always \u7038 13-2345-3
+always \u7039 236-5
+always \u703A 12-1236-2
+always \u703B 145-2456-5
+always \u703C 1245-1346-2
+always \u703D 13-2345-4
+always \u703E 14-1236-2
+always \u703F 12345-1236-2
+always \u7040 24-456-5
+always \u7041 45-3
+always \u7042 1-25-2
+always \u7043 12345-1356-3
+always \u7044 24-2346-5
+always \u7045 14-356-4
+always \u7046 14-1236-2
+always \u7047 245-12346-2
+always \u7048 245-1256-2
+always \u7049 235-3
+always \u704A 245-2345-2
+always \u704B 12345-345-4
+always \u704C 13-12456-5
+always \u704D 245-236-5
+always \u704E 2345-5
+always \u704F 1235-146-5
+always \u7050 13456-2
+always \u7051 15-345-4
+always \u7052 125-1236-5
+always \u7053 14-12456-2
+always \u7054 2345-5
+always \u7055 14-16-2
+always \u7056 134-16-4
+always \u7057 24-1236-5
+always \u7058 124-1236-3
+always \u7059 145-1346-4
+always \u705A 13-246-4
+always \u705B 12-1236-4
+always \u705C 13456-2
+always \u705D 1235-146-5
+always \u705E 135-345-5
+always \u705F 1-34-2
+always \u7060 14-1236-5
+always \u7061 14-1236-2
+always \u7062 1345-1346-4
+always \u7063 12456-3
+always \u7064 14-12456-2
+always \u7065 15-256-2
+always \u7066 15-2345-4
+always \u7067 2345-5
+always \u7068 13-1236-4
+always \u7069 2345-5
+always \u706A 1256-5
+always \u706B 1235-25-4
+always \u706C 1235-25-4
+always \u706D 134-346-5
+always \u706E 13-456-3
+always \u706F 145-1356-3
+always \u7070 1235-1246-3
+always \u7071 15-246-3
+always \u7072 15-246-3
+always \u7073 1235-1246-3
+always \u7074 1235-12346-2
+always \u7075 14-13456-2
+always \u7076 125-146-5
+always \u7077 1-12456-5
+always \u7078 13-234-4
+always \u7079 1-345-5
+always \u707A 15-346-5
+always \u707B 12-156-5
+always \u707C 1-25-2
+always \u707D 125-2456-3
+always \u707E 125-2456-3
+always \u707F 245-1236-5
+always \u7080 46-2
+always \u7081 245-16-5
+always \u7082 1-12346-3
+always \u7083 12345-136-2
+always \u7084 1345-234-4
+always \u7085 13-235-4
+always \u7086 123456-2
+always \u7087 1234-126-5
+always \u7088 16-5
+always \u7089 14-34-2
+always \u708A 12-1246-3
+always \u708B 1234-16-3
+always \u708C 123-2456-5
+always \u708D 1234-1236-5
+always \u708E 2345-2
+always \u708F 123-2456-5
+always \u7090 1234-1346-5
+always \u7091 134-34-5
+always \u7092 12-146-4
+always \u7093 14-246-5
+always \u7094 245-236-3
+always \u7095 123-1346-5
+always \u7096 124-123456-2
+always \u7097 13-456-3
+always \u7098 15-1456-3
+always \u7099 1-156-5
+always \u709A 13-456-3
+always \u709B 13-456-3
+always \u709C 1246-4
+always \u709D 245-46-5
+always \u709E 145-2345-4
+always \u709F 145-345-2
+always \u70A0 15-23456-2
+always \u70A1 1-1356-3
+always \u70A2 1-34-2
+always \u70A3 123-2346-4
+always \u70A4 1-146-5
+always \u70A5 12345-34-2
+always \u70A6 135-345-2
+always \u70A7 145-25-5
+always \u70A8 145-25-5
+always \u70A9 14-13456-5
+always \u70AA 1-25-2
+always \u70AB 15-45-5
+always \u70AC 13-1256-5
+always \u70AD 124-1236-5
+always \u70AE 1234-146-5
+always \u70AF 13-235-4
+always \u70B0 1234-146-2
+always \u70B1 124-2456-2
+always \u70B2 124-2456-2
+always \u70B3 135-13456-4
+always \u70B4 46-4
+always \u70B5 124-12346-3
+always \u70B6 1235-1236-3
+always \u70B7 1-34-5
+always \u70B8 1-345-5
+always \u70B9 145-2345-4
+always \u70BA 1246-2
+always \u70BB 24-156-2
+always \u70BC 14-2345-5
+always \u70BD 12-156-5
+always \u70BE 1235-456-4
+always \u70BF 1-12356-3
+always \u70C0 1235-34-3
+always \u70C1 24-25-5
+always \u70C2 14-1236-5
+always \u70C3 245-13456-3
+always \u70C4 13-246-4
+always \u70C5 15-1256-5
+always \u70C6 15-13456-2
+always \u70C7 245-45-5
+always \u70C8 14-346-5
+always \u70C9 1235-12456-5
+always \u70CA 46-2
+always \u70CB 15-234-3
+always \u70CC 15-234-3
+always \u70CD 15-2345-4
+always \u70CE 1456-2
+always \u70CF 34-3
+always \u70D0 1-12356-3
+always \u70D1 246-2
+always \u70D2 24-156-5
+always \u70D3 1246-3
+always \u70D4 124-12346-2
+always \u70D5 15-236-5
+always \u70D6 125-2456-3
+always \u70D7 123-2456-5
+always \u70D8 1235-12346-3
+always \u70D9 14-146-5
+always \u70DA 15-23456-2
+always \u70DB 1-34-2
+always \u70DC 15-45-4
+always \u70DD 1-1356-3
+always \u70DE 1234-126-5
+always \u70DF 2345-3
+always \u70E0 1235-1246-4
+always \u70E1 13-456-3
+always \u70E2 1-2346-5
+always \u70E3 1235-1246-3
+always \u70E4 123-146-4
+always \u70E5 12-136-2
+always \u70E6 12345-1236-2
+always \u70E7 24-146-3
+always \u70E8 346-5
+always \u70E9 1235-1246-5
+always \u70EA 13-12456-3
+always \u70EB 124-1346-5
+always \u70EC 13-1456-5
+always \u70ED 1245-2346-5
+always \u70EE 14-346-5
+always \u70EF 15-16-3
+always \u70F0 12345-34-2
+always \u70F1 13-235-4
+always \u70F2 12-2346-5
+always \u70F3 1234-34-4
+always \u70F4 245-13456-3
+always \u70F5 1-25-2
+always \u70F6 124-13456-4
+always \u70F7 12456-2
+always \u70F8 1235-2456-4
+always \u70F9 1234-1356-3
+always \u70FA 14-1346-4
+always \u70FB 2345-5
+always \u70FC 1235-34-3
+always \u70FD 12345-1356-3
+always \u70FE 12-156-5
+always \u70FF 1245-12346-2
+always \u7100 1235-34-2
+always \u7101 15-16-3
+always \u7102 24-34-2
+always \u7103 1235-2346-5
+always \u7104 15-256-3
+always \u7105 123-34-5
+always \u7106 13-236-2
+always \u7107 15-246-3
+always \u7108 15-16-3
+always \u7109 2345-3
+always \u710A 1235-1236-5
+always \u710B 1-456-5
+always \u710C 13-256-5
+always \u710D 145-16-5
+always \u710E 15-346-5
+always \u710F 13-16-2
+always \u7110 34-5
+always \u7111 2345-3
+always \u7112 14-1256-4
+always \u7113 1235-1236-2
+always \u7114 2345-5
+always \u7115 1235-12456-5
+always \u7116 134-136-3
+always \u7117 13-1256-2
+always \u7118 145-146-5
+always \u7119 135-356-5
+always \u711A 12345-136-2
+always \u711B 14-1456-5
+always \u711C 123-123456-3
+always \u711D 1235-123456-5
+always \u711E 124-123456-3
+always \u711F 15-16-2
+always \u7120 245-1246-5
+always \u7121 34-2
+always \u7122 1235-12346-3
+always \u7123 13-1256-5
+always \u7124 12345-34-4
+always \u7125 25-5
+always \u7126 13-246-3
+always \u7127 245-12346-3
+always \u7128 12345-1356-5
+always \u7129 1234-13456-3
+always \u712A 245-235-3
+always \u712B 1245-25-5
+always \u712C 15-16-2
+always \u712D 245-235-2
+always \u712E 15-1456-5
+always \u712F 12-146-3
+always \u7130 2345-5
+always \u7131 2345-5
+always \u7132 16-5
+always \u7133 13-236-2
+always \u7134 1256-5
+always \u7135 13-1346-5
+always \u7136 1245-1236-2
+always \u7137 1234-16-2
+always \u7138 13-34-4
+always \u7139 456-4
+always \u713A 24-1356-3
+always \u713B 12-1346-5
+always \u713C 24-146-3
+always \u713D 13456-3
+always \u713E 134-2345-4
+always \u713F 13-1356-3
+always \u7140 1246-3
+always \u7141 12-136-2
+always \u7142 1235-2346-5
+always \u7143 123-1246-4
+always \u7144 1-12346-3
+always \u7145 145-12456-5
+always \u7146 15-23456-3
+always \u7147 1235-1246-3
+always \u7148 12345-1356-5
+always \u7149 14-2345-5
+always \u714A 15-45-3
+always \u714B 15-13456-3
+always \u714C 1235-456-2
+always \u714D 13-246-4
+always \u714E 13-2345-3
+always \u714F 135-16-5
+always \u7150 13456-3
+always \u7151 1-34-4
+always \u7152 1246-4
+always \u7153 124-12456-3
+always \u7154 124-2345-5
+always \u7155 15-16-3
+always \u7156 1345-12456-4
+always \u7157 1345-12456-4
+always \u7158 12-1236-2
+always \u7159 2345-3
+always \u715A 13-235-4
+always \u715B 13-235-4
+always \u715C 1256-5
+always \u715D 134-356-5
+always \u715E 24-345-5
+always \u715F 1246-5
+always \u7160 346-5
+always \u7161 13-1456-5
+always \u7162 245-235-2
+always \u7163 1245-12356-2
+always \u7164 134-356-2
+always \u7165 1235-12456-5
+always \u7166 15-1256-4
+always \u7167 1-146-5
+always \u7168 1246-3
+always \u7169 12345-1236-2
+always \u716A 245-234-2
+always \u716B 15-1246-5
+always \u716C 46-2
+always \u716D 14-346-5
+always \u716E 1-34-4
+always \u716F 13-346-3
+always \u7170 13-146-5
+always \u7171 13-35-3
+always \u7172 135-146-3
+always \u7173 1235-34-2
+always \u7174 256-3
+always \u7175 15-23456-3
+always \u7176 24-156-5
+always \u7177 14-46-5
+always \u7178 135-2345-3
+always \u7179 13-12356-5
+always \u717A 124-1246-5
+always \u717B 124-1346-2
+always \u717C 12-146-4
+always \u717D 24-1236-3
+always \u717E 136-3
+always \u717F 135-126-2
+always \u7180 1235-456-4
+always \u7181 15-346-2
+always \u7182 15-16-5
+always \u7183 34-5
+always \u7184 15-16-2
+always \u7185 256-3
+always \u7186 1235-2346-2
+always \u7187 15-246-3
+always \u7188 15-16-3
+always \u7189 256-2
+always \u718A 15-235-2
+always \u718B 1345-2456-2
+always \u718C 24-1236-5
+always \u718D 15-235-2
+always \u718E 246-5
+always \u718F 15-256-3
+always \u7190 134-13456-2
+always \u7191 14-2345-2
+always \u7192 13456-2
+always \u7193 34-4
+always \u7194 1245-12346-2
+always \u7195 13-12346-5
+always \u7196 2345-5
+always \u7197 245-46-5
+always \u7198 14-234-3
+always \u7199 15-16-3
+always \u719A 135-16-5
+always \u719B 135-246-3
+always \u719C 125-12346-4
+always \u719D 14-34-5
+always \u719E 13-2345-3
+always \u719F 24-34-2
+always \u71A0 16-5
+always \u71A1 14-12356-2
+always \u71A2 12345-1356-3
+always \u71A3 15-1246-3
+always \u71A4 16-5
+always \u71A5 124-12346-3
+always \u71A6 13-236-2
+always \u71A7 125-12346-3
+always \u71A8 256-5
+always \u71A9 1235-34-5
+always \u71AA 16-2
+always \u71AB 1-156-5
+always \u71AC 146-2
+always \u71AD 1246-5
+always \u71AE 14-246-2
+always \u71AF 1235-1236-5
+always \u71B0 12356-3
+always \u71B1 1245-2346-5
+always \u71B2 13-235-4
+always \u71B3 134-1236-5
+always \u71B4 123-123456-3
+always \u71B5 24-1346-3
+always \u71B6 245-12456-5
+always \u71B7 125-1356-3
+always \u71B8 13-2345-3
+always \u71B9 15-16-3
+always \u71BA 15-16-3
+always \u71BB 15-16-3
+always \u71BC 16-5
+always \u71BD 15-246-5
+always \u71BE 12-156-5
+always \u71BF 1235-456-2
+always \u71C0 12-1236-4
+always \u71C1 346-5
+always \u71C2 245-2345-2
+always \u71C3 1245-1236-2
+always \u71C4 2345-5
+always \u71C5 15-256-2
+always \u71C6 245-246-2
+always \u71C7 125-123456-5
+always \u71C8 145-1356-3
+always \u71C9 145-123456-5
+always \u71CA 24-136-3
+always \u71CB 13-246-3
+always \u71CC 12345-136-2
+always \u71CD 15-156-3
+always \u71CE 14-246-5
+always \u71CF 1256-5
+always \u71D0 14-1456-2
+always \u71D1 124-12346-2
+always \u71D2 24-146-3
+always \u71D3 12345-136-3
+always \u71D4 12345-1236-2
+always \u71D5 2345-5
+always \u71D6 15-256-2
+always \u71D7 14-1236-5
+always \u71D8 134-356-4
+always \u71D9 124-1346-5
+always \u71DA 16-3
+always \u71DB 13-13456-4
+always \u71DC 134-136-5
+always \u71DD 13-1456-4
+always \u71DE 13-246-4
+always \u71DF 13456-2
+always \u71E0 1256-5
+always \u71E1 16-5
+always \u71E2 15-236-2
+always \u71E3 14-1236-2
+always \u71E4 124-2456-5
+always \u71E5 125-146-5
+always \u71E6 245-1236-5
+always \u71E7 15-1246-5
+always \u71E8 15-16-3
+always \u71E9 245-236-5
+always \u71EA 245-12346-3
+always \u71EB 14-2345-2
+always \u71EC 1235-1246-4
+always \u71ED 1-34-2
+always \u71EE 15-346-5
+always \u71EF 14-13456-2
+always \u71F0 1246-3
+always \u71F1 16-5
+always \u71F2 15-346-2
+always \u71F3 1-146-5
+always \u71F4 1235-1246-5
+always \u71F5 145-345-2
+always \u71F6 1345-12346-2
+always \u71F7 14-1236-2
+always \u71F8 1245-34-2
+always \u71F9 15-2345-4
+always \u71FA 123-146-4
+always \u71FB 15-256-3
+always \u71FC 13-1456-5
+always \u71FD 12-12356-2
+always \u71FE 145-146-5
+always \u71FF 246-5
+always \u7200 1235-2346-5
+always \u7201 14-1236-5
+always \u7202 135-246-3
+always \u7203 1245-12346-2
+always \u7204 14-16-5
+always \u7205 134-126-5
+always \u7206 135-146-5
+always \u7207 1245-25-5
+always \u7208 14-1256-2
+always \u7209 14-345-5
+always \u720A 146-2
+always \u720B 15-256-5
+always \u720C 123-456-5
+always \u720D 24-25-5
+always \u720E 14-246-2
+always \u720F 14-16-5
+always \u7210 14-34-2
+always \u7211 13-236-2
+always \u7212 14-246-5
+always \u7213 2345-5
+always \u7214 15-16-3
+always \u7215 15-346-5
+always \u7216 14-12346-2
+always \u7217 346-5
+always \u7218 245-1236-5
+always \u7219 1245-1346-4
+always \u721A 236-5
+always \u721B 14-1236-5
+always \u721C 245-12346-2
+always \u721D 13-236-2
+always \u721E 124-12346-2
+always \u721F 13-12456-5
+always \u7220 245-1256-2
+always \u7221 12-2346-5
+always \u7222 134-16-2
+always \u7223 124-1346-4
+always \u7224 14-1236-5
+always \u7225 1-34-2
+always \u7226 14-1236-4
+always \u7227 14-13456-2
+always \u7228 245-12456-5
+always \u7229 1256-5
+always \u722A 1-35-4
+always \u722B 1-35-4
+always \u722C 1234-345-2
+always \u722D 1-1356-3
+always \u722E 1234-146-2
+always \u722F 12-1356-3
+always \u7230 45-2
+always \u7231 2456-5
+always \u7232 1246-2
+always \u7233 1235-1236-2
+always \u7234 13-236-2
+always \u7235 13-236-2
+always \u7236 12345-34-5
+always \u7237 346-2
+always \u7238 135-345-5
+always \u7239 145-346-3
+always \u723A 346-2
+always \u723B 246-2
+always \u723C 125-34-4
+always \u723D 24-456-4
+always \u723E 156-4
+always \u723F 245-46-2
+always \u7240 12-456-2
+always \u7241 13-2346-3
+always \u7242 125-1346-3
+always \u7243 145-346-2
+always \u7244 245-46-3
+always \u7245 235-2
+always \u7246 245-46-2
+always \u7247 1234-2345-5
+always \u7248 135-1236-4
+always \u7249 1234-1236-5
+always \u724A 24-146-2
+always \u724B 13-2345-3
+always \u724C 1234-2456-2
+always \u724D 145-34-2
+always \u724E 12-456-3
+always \u724F 1256-5
+always \u7250 1-345-2
+always \u7251 135-2345-3
+always \u7252 145-346-2
+always \u7253 135-1346-4
+always \u7254 135-126-2
+always \u7255 12-456-3
+always \u7256 234-4
+always \u7257 234-4
+always \u7258 145-34-2
+always \u7259 23456-2
+always \u725A 12-1356-5
+always \u725B 1345-234-2
+always \u725C 1345-234-2
+always \u725D 1234-1456-5
+always \u725E 13-234-3
+always \u725F 134-12356-2
+always \u7260 124-3457-3
+always \u7261 134-34-4
+always \u7262 14-146-2
+always \u7263 1245-136-5
+always \u7264 134-1346-2
+always \u7265 12345-1346-3
+always \u7266 134-146-2
+always \u7267 134-34-5
+always \u7268 13-1346-3
+always \u7269 34-5
+always \u726A 2345-5
+always \u726B 13-2346-3
+always \u726C 135-356-5
+always \u726D 15-156-5
+always \u726E 13-2345-5
+always \u726F 13-34-4
+always \u7270 234-5
+always \u7271 13-2346-3
+always \u7272 24-1356-3
+always \u7273 134-34-4
+always \u7274 145-16-4
+always \u7275 245-2345-3
+always \u7276 245-45-5
+always \u7277 245-45-2
+always \u7278 125-156-5
+always \u7279 124-2346-5
+always \u727A 15-16-3
+always \u727B 134-1346-2
+always \u727C 123-1356-3
+always \u727D 245-2345-3
+always \u727E 34-2
+always \u727F 13-34-5
+always \u7280 15-16-3
+always \u7281 14-16-2
+always \u7282 14-16-2
+always \u7283 1234-12356-4
+always \u7284 13-16-3
+always \u7285 13-1346-3
+always \u7286 1-156-2
+always \u7287 135-136-3
+always \u7288 245-45-2
+always \u7289 1245-123456-2
+always \u728A 145-34-2
+always \u728B 13-1256-5
+always \u728C 13-23456-3
+always \u728D 13-2345-3
+always \u728E 12345-1356-3
+always \u728F 1234-2345-3
+always \u7290 123-2346-3
+always \u7291 13-1256-2
+always \u7292 123-146-5
+always \u7293 12-34-2
+always \u7294 15-16-5
+always \u7295 135-356-5
+always \u7296 14-25-5
+always \u7297 13-346-5
+always \u7298 134-345-2
+always \u7299 15-1236-3
+always \u729A 1246-5
+always \u729B 14-16-2
+always \u729C 145-123456-3
+always \u729D 124-12346-2
+always \u729E 245-246-2
+always \u729F 13-46-5
+always \u72A0 15-16-3
+always \u72A1 14-16-5
+always \u72A2 145-34-2
+always \u72A3 14-346-5
+always \u72A4 1234-16-2
+always \u72A5 1234-246-4
+always \u72A6 135-146-5
+always \u72A7 15-16-3
+always \u72A8 12-12356-3
+always \u72A9 1246-2
+always \u72AA 123-1246-2
+always \u72AB 12-12356-3
+always \u72AC 245-45-4
+always \u72AD 245-45-4
+always \u72AE 135-345-2
+always \u72AF 12345-1236-5
+always \u72B0 245-234-2
+always \u72B1 13-16-4
+always \u72B2 245-2456-2
+always \u72B3 1-25-2
+always \u72B4 1235-1236-3
+always \u72B5 13-2346-3
+always \u72B6 1-456-5
+always \u72B7 13-456-4
+always \u72B8 134-345-4
+always \u72B9 234-2
+always \u72BA 123-1346-5
+always \u72BB 135-126-2
+always \u72BC 1235-12356-4
+always \u72BD 23456-2
+always \u72BE 1456-2
+always \u72BF 1235-12456-3
+always \u72C0 1-456-5
+always \u72C1 256-4
+always \u72C2 123-456-2
+always \u72C3 1345-234-4
+always \u72C4 145-16-2
+always \u72C5 245-13456-3
+always \u72C6 1-12346-5
+always \u72C7 134-34-5
+always \u72C8 135-356-5
+always \u72C9 1234-16-3
+always \u72CA 13-1256-2
+always \u72CB 16-2
+always \u72CC 24-1356-3
+always \u72CD 1234-146-2
+always \u72CE 15-23456-2
+always \u72CF 124-25-2
+always \u72D0 1235-34-2
+always \u72D1 14-13456-2
+always \u72D2 12345-356-5
+always \u72D3 1234-16-3
+always \u72D4 1345-16-4
+always \u72D5 146-4
+always \u72D6 234-5
+always \u72D7 13-12356-4
+always \u72D8 236-5
+always \u72D9 13-1256-3
+always \u72DA 145-1236-5
+always \u72DB 1234-126-5
+always \u72DC 13-34-4
+always \u72DD 15-2345-4
+always \u72DE 1345-13456-2
+always \u72DF 1235-12456-2
+always \u72E0 1235-136-4
+always \u72E1 13-246-4
+always \u72E2 1235-2346-2
+always \u72E3 1-146-5
+always \u72E4 13-16-2
+always \u72E5 15-256-2
+always \u72E6 24-1236-3
+always \u72E7 124-345-5
+always \u72E8 1245-12346-2
+always \u72E9 24-12356-5
+always \u72EA 124-12346-3
+always \u72EB 14-146-4
+always \u72EC 145-34-2
+always \u72ED 15-23456-2
+always \u72EE 24-156-3
+always \u72EF 123-2356-5
+always \u72F0 1-1356-3
+always \u72F1 1256-5
+always \u72F2 15-123456-3
+always \u72F3 1256-2
+always \u72F4 135-16-5
+always \u72F5 134-1346-2
+always \u72F6 15-16-3
+always \u72F7 13-45-5
+always \u72F8 14-16-2
+always \u72F9 15-23456-2
+always \u72FA 1456-2
+always \u72FB 15-12456-3
+always \u72FC 14-1346-2
+always \u72FD 135-356-5
+always \u72FE 1-156-5
+always \u72FF 2345-2
+always \u7300 24-345-3
+always \u7301 14-16-5
+always \u7302 1235-1236-5
+always \u7303 15-2345-4
+always \u7304 13-13456-3
+always \u7305 1234-2456-2
+always \u7306 12345-356-3
+always \u7307 246-2
+always \u7308 135-345-5
+always \u7309 245-16-2
+always \u730A 1345-16-2
+always \u730B 135-246-3
+always \u730C 1456-5
+always \u730D 14-2456-2
+always \u730E 15-16-2
+always \u730F 13-2345-3
+always \u7310 245-46-3
+always \u7311 123-123456-3
+always \u7312 2345-5
+always \u7313 13-25-4
+always \u7314 125-12346-5
+always \u7315 134-16-2
+always \u7316 12-1346-3
+always \u7317 16-3
+always \u7318 1-156-5
+always \u7319 1-1356-3
+always \u731A 23456-2
+always \u731B 134-1356-4
+always \u731C 245-2456-3
+always \u731D 245-34-5
+always \u731E 24-2346-3
+always \u731F 14-346-5
+always \u7320 145-2345-4
+always \u7321 14-25-2
+always \u7322 1235-34-2
+always \u7323 125-12346-3
+always \u7324 13-16-5
+always \u7325 1246-4
+always \u7326 12345-1356-3
+always \u7327 25-3
+always \u7328 45-2
+always \u7329 15-13456-3
+always \u732A 1-34-3
+always \u732B 134-146-3
+always \u732C 1246-5
+always \u732D 12-12456-5
+always \u732E 15-2345-5
+always \u732F 124-12456-3
+always \u7330 23456-5
+always \u7331 1345-146-2
+always \u7332 1235-2346-5
+always \u7333 13-23456-3
+always \u7334 1235-12356-2
+always \u7335 135-2345-3
+always \u7336 234-2
+always \u7337 234-2
+always \u7338 134-356-2
+always \u7339 1-345-3
+always \u733A 246-2
+always \u733B 15-123456-3
+always \u733C 135-126-2
+always \u733D 134-13456-2
+always \u733E 1235-35-2
+always \u733F 45-2
+always \u7340 15-12356-3
+always \u7341 134-345-4
+always \u7342 45-2
+always \u7343 145-2456-3
+always \u7344 1256-5
+always \u7345 24-156-3
+always \u7346 1235-146-2
+always \u7347 245-46-3
+always \u7348 16-5
+always \u7349 1-136-3
+always \u734A 12-456-5
+always \u734B 1235-146-2
+always \u734C 134-1236-5
+always \u734D 13-13456-5
+always \u734E 13-46-4
+always \u734F 134-34-2
+always \u7350 1-1346-3
+always \u7351 12-1236-2
+always \u7352 146-2
+always \u7353 146-2
+always \u7354 1235-146-2
+always \u7355 245-1246-3
+always \u7356 12345-136-2
+always \u7357 13-236-2
+always \u7358 135-16-5
+always \u7359 135-16-5
+always \u735A 1235-456-2
+always \u735B 135-34-4
+always \u735C 14-1456-2
+always \u735D 1256-5
+always \u735E 124-12346-2
+always \u735F 246-5
+always \u7360 14-246-2
+always \u7361 24-25-5
+always \u7362 15-246-3
+always \u7363 24-12356-5
+always \u7364 145-123456-3
+always \u7365 15-16-2
+always \u7366 13-2346-2
+always \u7367 13-45-5
+always \u7368 145-34-2
+always \u7369 1235-1246-5
+always \u736A 123-2356-5
+always \u736B 15-2345-4
+always \u736C 15-346-5
+always \u736D 124-345-5
+always \u736E 15-2345-4
+always \u736F 15-256-3
+always \u7370 1345-13456-2
+always \u7371 1234-1456-2
+always \u7372 1235-25-5
+always \u7373 1345-12356-2
+always \u7374 134-1356-2
+always \u7375 14-346-5
+always \u7376 1345-146-2
+always \u7377 13-456-4
+always \u7378 24-12356-5
+always \u7379 14-34-2
+always \u737A 124-345-5
+always \u737B 15-2345-5
+always \u737C 134-16-2
+always \u737D 1245-1346-2
+always \u737E 1235-12456-3
+always \u737F 1345-146-2
+always \u7380 14-25-2
+always \u7381 15-2345-4
+always \u7382 245-16-2
+always \u7383 13-236-2
+always \u7384 15-45-2
+always \u7385 134-246-5
+always \u7386 125-156-3
+always \u7387 14-1256-5
+always \u7388 14-34-2
+always \u7389 1256-5
+always \u738A 15-34-5
+always \u738B 456-2
+always \u738C 245-234-2
+always \u738D 13-345-4
+always \u738E 145-13456-3
+always \u738F 14-2346-5
+always \u7390 135-345-3
+always \u7391 13-16-3
+always \u7392 1235-12346-2
+always \u7393 145-16-5
+always \u7394 12-12456-5
+always \u7395 13-1236-3
+always \u7396 13-234-4
+always \u7397 1256-2
+always \u7398 13-16-4
+always \u7399 1256-2
+always \u739A 12-1346-5
+always \u739B 134-345-4
+always \u739C 13-12346-3
+always \u739D 34-4
+always \u739E 12345-34-3
+always \u739F 123456-2
+always \u73A0 13-346-5
+always \u73A1 23456-5
+always \u73A2 135-1456-3
+always \u73A3 135-2345-5
+always \u73A4 135-1346-5
+always \u73A5 236-5
+always \u73A6 13-236-2
+always \u73A7 256-4
+always \u73A8 13-236-2
+always \u73A9 12456-2
+always \u73AA 13-2345-3
+always \u73AB 134-356-2
+always \u73AC 145-1236-4
+always \u73AD 1234-1456-2
+always \u73AE 1246-4
+always \u73AF 1235-12456-2
+always \u73B0 15-2345-5
+always \u73B1 245-46-3
+always \u73B2 14-13456-2
+always \u73B3 145-2456-5
+always \u73B4 16-5
+always \u73B5 1236-2
+always \u73B6 1234-13456-2
+always \u73B7 145-2345-5
+always \u73B8 12345-34-2
+always \u73B9 15-45-2
+always \u73BA 15-16-4
+always \u73BB 135-126-3
+always \u73BC 245-156-4
+always \u73BD 13-12356-4
+always \u73BE 13-23456-4
+always \u73BF 24-146-2
+always \u73C0 1234-126-5
+always \u73C1 245-156-2
+always \u73C2 123-2346-3
+always \u73C3 1245-1236-4
+always \u73C4 24-1356-3
+always \u73C5 24-136-3
+always \u73C6 16-2
+always \u73C7 125-34-4
+always \u73C8 13-23456-3
+always \u73C9 134-1456-2
+always \u73CA 24-1236-3
+always \u73CB 14-234-4
+always \u73CC 135-16-5
+always \u73CD 1-136-3
+always \u73CE 1-136-3
+always \u73CF 13-236-2
+always \u73D0 12345-345-5
+always \u73D1 14-12346-2
+always \u73D2 13-1456-3
+always \u73D3 13-246-5
+always \u73D4 13-2345-5
+always \u73D5 14-16-5
+always \u73D6 13-456-3
+always \u73D7 15-2345-3
+always \u73D8 1-12356-3
+always \u73D9 13-12346-4
+always \u73DA 2345-3
+always \u73DB 15-234-5
+always \u73DC 46-2
+always \u73DD 15-1256-4
+always \u73DE 14-25-5
+always \u73DF 15-34-5
+always \u73E0 1-34-3
+always \u73E1 245-1456-2
+always \u73E2 123-136-5
+always \u73E3 15-256-2
+always \u73E4 135-146-4
+always \u73E5 156-4
+always \u73E6 15-46-5
+always \u73E7 246-2
+always \u73E8 15-23456-2
+always \u73E9 1235-1356-2
+always \u73EA 13-1246-3
+always \u73EB 12-12346-3
+always \u73EC 15-1256-5
+always \u73ED 135-1236-3
+always \u73EE 1234-356-5
+always \u73EF 14-146-4
+always \u73F0 145-1346-3
+always \u73F1 13456-3
+always \u73F2 1235-1246-3
+always \u73F3 123456-2
+always \u73F4 2346-2
+always \u73F5 12-1356-2
+always \u73F6 145-16-5
+always \u73F7 34-4
+always \u73F8 34-2
+always \u73F9 12-1356-2
+always \u73FA 13-256-5
+always \u73FB 134-356-2
+always \u73FC 135-356-5
+always \u73FD 124-13456-4
+always \u73FE 15-2345-5
+always \u73FF 12-25-5
+always \u7400 1235-1236-5
+always \u7401 15-45-2
+always \u7402 2345-2
+always \u7403 245-234-2
+always \u7404 245-45-4
+always \u7405 14-1346-2
+always \u7406 14-16-4
+always \u7407 15-234-5
+always \u7408 12345-34-2
+always \u7409 14-234-2
+always \u740A 23456-2
+always \u740B 15-16-3
+always \u740C 14-13456-2
+always \u740D 14-16-5
+always \u740E 13-1456-5
+always \u740F 14-2345-4
+always \u7410 15-25-4
+always \u7411 15-25-4
+always \u7412 12345-1356-3
+always \u7413 12456-2
+always \u7414 145-2345-5
+always \u7415 1234-1456-2
+always \u7416 1-1236-4
+always \u7417 245-1246-5
+always \u7418 134-1456-2
+always \u7419 1256-5
+always \u741A 13-1256-3
+always \u741B 12-136-3
+always \u741C 14-2456-2
+always \u741D 134-1456-2
+always \u741E 24-1356-5
+always \u741F 1246-2
+always \u7420 145-2345-4
+always \u7421 12-34-5
+always \u7422 1-25-2
+always \u7423 1234-356-4
+always \u7424 12-1356-3
+always \u7425 1235-34-4
+always \u7426 245-16-2
+always \u7427 2346-5
+always \u7428 123-123456-3
+always \u7429 12-1346-3
+always \u742A 245-16-2
+always \u742B 135-1356-4
+always \u742C 12456-4
+always \u742D 14-34-5
+always \u742E 245-12346-2
+always \u742F 13-12456-4
+always \u7430 2345-4
+always \u7431 145-246-3
+always \u7432 135-356-5
+always \u7433 14-1456-2
+always \u7434 245-1456-2
+always \u7435 1234-16-2
+always \u7436 1234-345-2
+always \u7437 245-236-5
+always \u7438 1-25-2
+always \u7439 245-1456-2
+always \u743A 12345-345-5
+always \u743B 13-1456-3
+always \u743C 245-235-2
+always \u743D 145-34-4
+always \u743E 13-346-5
+always \u743F 1235-1246-3
+always \u7440 1256-4
+always \u7441 134-146-5
+always \u7442 134-356-2
+always \u7443 12-123456-3
+always \u7444 15-45-3
+always \u7445 124-16-2
+always \u7446 15-13456-3
+always \u7447 145-2456-5
+always \u7448 1245-12356-2
+always \u7449 134-1456-2
+always \u744A 1-136-3
+always \u744B 1246-4
+always \u744C 1245-12456-4
+always \u744D 1235-12456-5
+always \u744E 15-346-2
+always \u744F 12-12456-3
+always \u7450 13-2345-4
+always \u7451 1-12456-5
+always \u7452 12-1346-5
+always \u7453 14-2345-5
+always \u7454 245-45-2
+always \u7455 15-23456-2
+always \u7456 145-12456-5
+always \u7457 45-5
+always \u7458 346-2
+always \u7459 1345-146-4
+always \u745A 1235-34-2
+always \u745B 13456-3
+always \u745C 1256-2
+always \u745D 1235-456-2
+always \u745E 1245-1246-5
+always \u745F 15-2346-5
+always \u7460 14-234-2
+always \u7461 24-156-3
+always \u7462 1245-12346-2
+always \u7463 15-25-4
+always \u7464 246-2
+always \u7465 123456-3
+always \u7466 34-3
+always \u7467 13-1456-3
+always \u7468 13-1456-5
+always \u7469 13456-2
+always \u746A 134-345-4
+always \u746B 124-146-3
+always \u746C 14-234-2
+always \u746D 124-1346-2
+always \u746E 14-16-5
+always \u746F 14-1346-2
+always \u7470 13-1246-3
+always \u7471 124-2345-5
+always \u7472 245-46-3
+always \u7473 245-25-3
+always \u7474 13-236-2
+always \u7475 1-146-4
+always \u7476 246-2
+always \u7477 2456-5
+always \u7478 135-1456-3
+always \u7479 124-34-2
+always \u747A 12-1346-2
+always \u747B 123-123456-3
+always \u747C 1-12456-3
+always \u747D 245-12346-3
+always \u747E 13-1456-4
+always \u747F 16-3
+always \u7480 245-1246-4
+always \u7481 245-12346-3
+always \u7482 245-16-2
+always \u7483 14-16-2
+always \u7484 13456-4
+always \u7485 15-25-4
+always \u7486 245-234-2
+always \u7487 15-45-2
+always \u7488 146-2
+always \u7489 14-2345-4
+always \u748A 134-136-2
+always \u748B 1-1346-3
+always \u748C 1456-2
+always \u748D 1235-35-2
+always \u748E 13456-3
+always \u748F 1-156-5
+always \u7490 14-34-5
+always \u7491 34-2
+always \u7492 145-1356-3
+always \u7493 15-234-5
+always \u7494 125-1356-3
+always \u7495 15-256-2
+always \u7496 245-1256-2
+always \u7497 145-1346-5
+always \u7498 14-1456-2
+always \u7499 14-246-2
+always \u749A 245-235-2
+always \u749B 15-34-5
+always \u749C 1235-456-2
+always \u749D 13-1246-3
+always \u749E 1234-34-2
+always \u749F 13-13456-4
+always \u74A0 12345-1236-2
+always \u74A1 13-1456-5
+always \u74A2 14-234-2
+always \u74A3 13-16-3
+always \u74A4 123-1246-5
+always \u74A5 13-13456-4
+always \u74A6 2456-5
+always \u74A7 135-16-5
+always \u74A8 245-1236-5
+always \u74A9 245-1256-2
+always \u74AA 125-146-4
+always \u74AB 145-1346-3
+always \u74AC 13-246-4
+always \u74AD 13-123456-5
+always \u74AE 124-1236-4
+always \u74AF 1235-1246-5
+always \u74B0 1235-12456-2
+always \u74B1 15-2346-5
+always \u74B2 15-1246-5
+always \u74B3 124-2345-2
+always \u74B4 12-34-4
+always \u74B5 1256-2
+always \u74B6 13-1456-5
+always \u74B7 1256-2
+always \u74B8 135-1456-3
+always \u74B9 24-12356-5
+always \u74BA 123456-5
+always \u74BB 125-1246-4
+always \u74BC 14-1236-2
+always \u74BD 15-16-4
+always \u74BE 13-16-5
+always \u74BF 15-45-2
+always \u74C0 1245-12456-4
+always \u74C1 1235-25-5
+always \u74C2 13-2456-5
+always \u74C3 14-356-2
+always \u74C4 145-34-2
+always \u74C5 14-16-5
+always \u74C6 1-156-2
+always \u74C7 1245-12356-2
+always \u74C8 14-16-2
+always \u74C9 125-1236-5
+always \u74CA 245-235-2
+always \u74CB 124-16-5
+always \u74CC 13-1246-3
+always \u74CD 15-1246-5
+always \u74CE 14-345-5
+always \u74CF 14-12346-2
+always \u74D0 14-34-2
+always \u74D1 14-16-5
+always \u74D2 125-1236-5
+always \u74D3 14-1236-5
+always \u74D4 13456-3
+always \u74D5 134-16-2
+always \u74D6 15-46-3
+always \u74D7 1246-5
+always \u74D8 13-12456-5
+always \u74D9 145-146-5
+always \u74DA 125-1236-5
+always \u74DB 1235-12456-2
+always \u74DC 13-35-3
+always \u74DD 135-126-2
+always \u74DE 145-346-2
+always \u74DF 135-126-2
+always \u74E0 1235-34-5
+always \u74E1 1-156-2
+always \u74E2 1234-246-2
+always \u74E3 135-1236-5
+always \u74E4 1245-1346-2
+always \u74E5 14-16-5
+always \u74E6 35-4
+always \u74E7 248-156-2-358-4
+always \u74E8 15-46-2
+always \u74E9 2458-2345-3-358-4
+always \u74EA 12345-1236-4
+always \u74EB 1234-136-2
+always \u74EC 12345-1346-4
+always \u74ED 145-1236-4
+always \u74EE 12346-5
+always \u74EF 12356-3
+always \u74F0 123458-136-3-358-4
+always \u74F1 1348-146-2-358-4
+always \u74F2 35-4
+always \u74F3 1235-34-2
+always \u74F4 14-13456-2
+always \u74F5 16-2
+always \u74F6 1234-13456-2
+always \u74F7 245-156-2
+always \u74F8 1358-2456-4-358-4
+always \u74F9 13-45-5
+always \u74FA 12-1346-2
+always \u74FB 12-156-3
+always \u74FC 148-16-4-358-4
+always \u74FD 145-1346-5
+always \u74FE 35-3
+always \u74FF 1234-12356-4
+always \u7500 1-1246-5
+always \u7501 1234-13456-2
+always \u7502 135-2345-3
+always \u7503 1-12356-5
+always \u7504 1-136-3
+always \u7505 148-16-2-358-4
+always \u7506 245-156-2
+always \u7507 13456-3
+always \u7508 245-16-5
+always \u7509 15-2345-2
+always \u750A 14-12356-4
+always \u750B 145-16-5
+always \u750C 12356-3
+always \u750D 134-1356-2
+always \u750E 1-12456-3
+always \u750F 1234-1356-5
+always \u7510 14-1456-5
+always \u7511 125-1356-5
+always \u7512 34-4
+always \u7513 1234-16-5
+always \u7514 145-1236-3
+always \u7515 12346-5
+always \u7516 13456-3
+always \u7517 2345-4
+always \u7518 13-1236-3
+always \u7519 145-2456-5
+always \u751A 24-136-5
+always \u751B 124-2345-2
+always \u751C 124-2345-2
+always \u751D 1235-1236-3
+always \u751E 12-1346-2
+always \u751F 24-1356-3
+always \u7520 245-13456-2
+always \u7521 24-136-3
+always \u7522 12-1236-4
+always \u7523 12-1236-4
+always \u7524 1245-1246-2
+always \u7525 24-1356-3
+always \u7526 15-34-3
+always \u7527 15-136-3
+always \u7528 235-5
+always \u7529 24-2356-4
+always \u752A 14-34-5
+always \u752B 12345-34-4
+always \u752C 235-4
+always \u752D 135-1356-2
+always \u752E 12345-1356-5
+always \u752F 1345-13456-5
+always \u7530 124-2345-2
+always \u7531 234-2
+always \u7532 13-23456-4
+always \u7533 24-136-3
+always \u7534 1-345-2
+always \u7535 145-2345-5
+always \u7536 12345-34-2
+always \u7537 1345-1236-2
+always \u7538 145-2345-5
+always \u7539 1234-13456-3
+always \u753A 145-13456-3
+always \u753B 1235-35-5
+always \u753C 124-13456-4
+always \u753D 245-45-4
+always \u753E 125-2456-3
+always \u753F 134-1356-2
+always \u7540 135-16-5
+always \u7541 245-16-2
+always \u7542 13-234-5
+always \u7543 15-256-2
+always \u7544 14-234-2
+always \u7545 12-1346-5
+always \u7546 134-34-4
+always \u7547 256-2
+always \u7548 12345-1236-5
+always \u7549 12345-34-2
+always \u754A 13-1356-3
+always \u754B 124-2345-2
+always \u754C 13-346-5
+always \u754D 13-346-5
+always \u754E 245-45-4
+always \u754F 1246-5
+always \u7550 12345-34-2
+always \u7551 124-2345-2
+always \u7552 134-34-4
+always \u7553 145-25-3
+always \u7554 1234-1236-5
+always \u7555 13-46-3
+always \u7556 35-3
+always \u7557 145-345-2
+always \u7558 1345-1236-2
+always \u7559 14-234-2
+always \u755A 135-136-4
+always \u755B 1-136-4
+always \u755C 12-34-5
+always \u755D 134-34-4
+always \u755E 134-34-4
+always \u755F 245-2346-5
+always \u7560 124-2345-2
+always \u7561 13-2456-3
+always \u7562 135-16-5
+always \u7563 145-345-2
+always \u7564 1-156-4
+always \u7565 14-236-5
+always \u7566 245-16-2
+always \u7567 14-236-5
+always \u7568 1234-1236-3
+always \u7569 16-3
+always \u756A 12345-1236-3
+always \u756B 1235-35-5
+always \u756C 24-2346-3
+always \u756D 1256-2
+always \u756E 134-34-4
+always \u756F 13-256-5
+always \u7570 16-5
+always \u7571 14-234-2
+always \u7572 24-2346-3
+always \u7573 145-346-2
+always \u7574 12-12356-2
+always \u7575 1235-35-5
+always \u7576 145-1346-3
+always \u7577 12-25-5
+always \u7578 13-16-3
+always \u7579 12456-4
+always \u757A 13-46-3
+always \u757B 24-1356-2
+always \u757C 12-1346-5
+always \u757D 124-12456-4
+always \u757E 14-356-2
+always \u757F 13-16-3
+always \u7580 12-345-3
+always \u7581 14-234-2
+always \u7582 145-346-2
+always \u7583 124-12456-4
+always \u7584 14-1456-2
+always \u7585 13-46-3
+always \u7586 13-46-3
+always \u7587 12-12356-2
+always \u7588 135-126-5
+always \u7589 145-346-2
+always \u758A 145-346-2
+always \u758B 1234-16-4
+always \u758C 245-346-5
+always \u758D 145-1236-5
+always \u758E 24-34-3
+always \u758F 24-34-3
+always \u7590 1-156-5
+always \u7591 16-2
+always \u7592 12-456-2
+always \u7593 1345-2456-4
+always \u7594 145-13456-3
+always \u7595 135-16-4
+always \u7596 13-346-3
+always \u7597 14-246-2
+always \u7598 13-12346-3
+always \u7599 13-2346-3
+always \u759A 13-234-5
+always \u759B 1-12356-4
+always \u759C 15-23456-5
+always \u759D 24-1236-5
+always \u759E 15-1256-3
+always \u759F 1345-236-5
+always \u75A0 14-16-5
+always \u75A1 46-2
+always \u75A2 12-136-5
+always \u75A3 234-2
+always \u75A4 135-345-3
+always \u75A5 13-346-5
+always \u75A6 13-236-2
+always \u75A7 1-156-3
+always \u75A8 15-23456-3
+always \u75A9 245-1246-5
+always \u75AA 135-16-5
+always \u75AB 16-5
+always \u75AC 14-16-5
+always \u75AD 125-12346-5
+always \u75AE 12-456-3
+always \u75AF 12345-1356-3
+always \u75B0 1-34-5
+always \u75B1 1234-146-5
+always \u75B2 1234-16-2
+always \u75B3 13-1236-3
+always \u75B4 2346-3
+always \u75B5 245-156-3
+always \u75B6 15-346-5
+always \u75B7 245-16-2
+always \u75B8 145-1236-4
+always \u75B9 1-136-4
+always \u75BA 12345-345-2
+always \u75BB 1-156-4
+always \u75BC 124-1356-2
+always \u75BD 13-1256-3
+always \u75BE 13-16-2
+always \u75BF 12345-356-5
+always \u75C0 13-1256-3
+always \u75C1 145-2345-5
+always \u75C2 13-23456-3
+always \u75C3 15-45-2
+always \u75C4 1-345-5
+always \u75C5 135-13456-5
+always \u75C6 1345-346-5
+always \u75C7 1-1356-5
+always \u75C8 235-3
+always \u75C9 13-13456-5
+always \u75CA 245-45-2
+always \u75CB 12-12346-2
+always \u75CC 124-12346-3
+always \u75CD 16-2
+always \u75CE 13-346-3
+always \u75CF 1246-4
+always \u75D0 1235-1246-2
+always \u75D1 145-25-5
+always \u75D2 46-2
+always \u75D3 12-156-5
+always \u75D4 1-156-5
+always \u75D5 1235-136-2
+always \u75D6 23456-4
+always \u75D7 134-356-5
+always \u75D8 145-12356-5
+always \u75D9 13-13456-5
+always \u75DA 15-246-3
+always \u75DB 124-12346-5
+always \u75DC 124-34-3
+always \u75DD 134-1346-2
+always \u75DE 1234-16-4
+always \u75DF 15-246-3
+always \u75E0 15-12456-3
+always \u75E1 1234-34-3
+always \u75E2 14-16-5
+always \u75E3 1-156-5
+always \u75E4 245-25-2
+always \u75E5 145-25-2
+always \u75E6 34-5
+always \u75E7 24-345-3
+always \u75E8 14-146-2
+always \u75E9 24-12356-5
+always \u75EA 1235-12456-5
+always \u75EB 15-2345-2
+always \u75EC 16-5
+always \u75ED 1234-1356-2
+always \u75EE 1-1346-5
+always \u75EF 13-12456-4
+always \u75F0 124-1236-2
+always \u75F1 12345-356-5
+always \u75F2 134-345-2
+always \u75F3 14-1456-2
+always \u75F4 12-156-3
+always \u75F5 13-16-5
+always \u75F6 145-2345-4
+always \u75F7 1236-3
+always \u75F8 12-156-5
+always \u75F9 135-16-5
+always \u75FA 135-16-5
+always \u75FB 134-1456-2
+always \u75FC 13-34-5
+always \u75FD 145-1246-3
+always \u75FE 2346-3
+always \u75FF 1246-4
+always \u7600 1256-3
+always \u7601 245-1246-5
+always \u7602 23456-4
+always \u7603 1-34-2
+always \u7604 245-34-5
+always \u7605 145-1236-5
+always \u7606 24-136-3
+always \u7607 1-12346-4
+always \u7608 13-16-5
+always \u7609 1256-5
+always \u760A 1235-12356-2
+always \u760B 12345-1356-3
+always \u760C 14-345-5
+always \u760D 46-2
+always \u760E 24-136-5
+always \u760F 124-34-2
+always \u7610 1256-4
+always \u7611 13-25-3
+always \u7612 123456-2
+always \u7613 1235-12456-5
+always \u7614 123-34-5
+always \u7615 13-23456-4
+always \u7616 1456-3
+always \u7617 16-5
+always \u7618 14-12356-5
+always \u7619 15-146-3
+always \u761A 13-236-2
+always \u761B 245-16-5
+always \u761C 15-16-2
+always \u761D 13-12456-3
+always \u761E 16-5
+always \u761F 123456-3
+always \u7620 13-16-2
+always \u7621 12-456-3
+always \u7622 135-1236-3
+always \u7623 1235-1246-5
+always \u7624 14-234-2
+always \u7625 245-25-2
+always \u7626 24-12356-5
+always \u7627 1345-236-5
+always \u7628 145-2345-3
+always \u7629 145-345-2
+always \u762A 135-346-4
+always \u762B 124-1236-3
+always \u762C 1-1346-5
+always \u762D 135-246-3
+always \u762E 24-136-3
+always \u762F 245-34-5
+always \u7630 14-25-4
+always \u7631 16-5
+always \u7632 125-12346-5
+always \u7633 12-12356-3
+always \u7634 1-1346-5
+always \u7635 1-2456-5
+always \u7636 15-12356-5
+always \u7637 15-25-4
+always \u7638 245-236-2
+always \u7639 145-246-5
+always \u763A 14-12356-5
+always \u763B 14-12356-5
+always \u763C 134-126-5
+always \u763D 13-1456-5
+always \u763E 1456-4
+always \u763F 13456-4
+always \u7640 1235-456-2
+always \u7641 12345-34-2
+always \u7642 14-246-2
+always \u7643 14-12346-2
+always \u7644 245-246-2
+always \u7645 14-234-2
+always \u7646 14-146-2
+always \u7647 15-2345-2
+always \u7648 12345-356-5
+always \u7649 145-1236-5
+always \u764A 1456-5
+always \u764B 1235-2346-5
+always \u764C 2456-2
+always \u764D 135-1236-3
+always \u764E 15-2345-2
+always \u764F 13-12456-3
+always \u7650 13-2356-5
+always \u7651 1345-12346-2
+always \u7652 1256-5
+always \u7653 1246-2
+always \u7654 16-5
+always \u7655 235-3
+always \u7656 1234-16-4
+always \u7657 14-356-4
+always \u7658 14-16-5
+always \u7659 24-34-4
+always \u765A 145-1236-5
+always \u765B 14-1456-4
+always \u765C 145-2345-5
+always \u765D 14-1456-4
+always \u765E 14-2456-5
+always \u765F 135-346-4
+always \u7660 13-16-5
+always \u7661 12-156-3
+always \u7662 46-4
+always \u7663 15-45-4
+always \u7664 13-346-3
+always \u7665 1-1356-5
+always \u7666 134-126-5
+always \u7667 14-16-5
+always \u7668 1235-25-5
+always \u7669 14-2456-5
+always \u766A 13-16-3
+always \u766B 145-2345-3
+always \u766C 15-2345-4
+always \u766D 13456-4
+always \u766E 1456-4
+always \u766F 245-1256-2
+always \u7670 235-3
+always \u7671 124-1236-3
+always \u7672 145-2345-3
+always \u7673 14-25-4
+always \u7674 14-12456-2
+always \u7675 14-12456-2
+always \u7676 135-126-3
+always \u7677 135-126-3
+always \u7678 13-1246-4
+always \u7679 1234-126-3
+always \u767A 12345-345-3
+always \u767B 145-1356-3
+always \u767C 12345-345-3
+always \u767D 135-2456-2
+always \u767E 135-2456-4
+always \u767F 245-346-2
+always \u7680 15-46-3
+always \u7681 125-146-5
+always \u7682 125-146-5
+always \u7683 134-146-5
+always \u7684 145-2346-1
+always \u7685 1234-345-3
+always \u7686 13-346-3
+always \u7687 1235-456-2
+always \u7688 13-1246-3
+always \u7689 245-156-4
+always \u768A 14-13456-2
+always \u768B 13-146-3
+always \u768C 134-126-5
+always \u768D 13-16-2
+always \u768E 13-246-4
+always \u768F 1234-1356-4
+always \u7690 13-146-3
+always \u7691 2456-2
+always \u7692 2346-2
+always \u7693 1235-146-5
+always \u7694 1235-1236-5
+always \u7695 135-16-5
+always \u7696 12456-4
+always \u7697 12-12356-2
+always \u7698 245-2345-5
+always \u7699 15-16-3
+always \u769A 2456-2
+always \u769B 13-235-4
+always \u769C 1235-146-5
+always \u769D 1235-456-4
+always \u769E 1235-146-5
+always \u769F 125-2346-2
+always \u76A0 245-1246-4
+always \u76A1 1235-146-5
+always \u76A2 15-246-4
+always \u76A3 346-5
+always \u76A4 1234-126-2
+always \u76A5 1235-146-5
+always \u76A6 13-246-4
+always \u76A7 2456-5
+always \u76A8 15-13456-3
+always \u76A9 1235-456-5
+always \u76AA 14-16-5
+always \u76AB 1234-246-4
+always \u76AC 1235-2346-5
+always \u76AD 13-246-5
+always \u76AE 1234-16-2
+always \u76AF 13-1236-4
+always \u76B0 1234-146-5
+always \u76B1 1-12356-5
+always \u76B2 13-256-3
+always \u76B3 245-234-2
+always \u76B4 245-123456-3
+always \u76B5 245-236-5
+always \u76B6 1-345-3
+always \u76B7 13-34-4
+always \u76B8 13-256-3
+always \u76B9 13-256-3
+always \u76BA 1-12356-5
+always \u76BB 1-345-3
+always \u76BC 13-34-4
+always \u76BD 1-1236-4
+always \u76BE 145-34-2
+always \u76BF 134-1456-4
+always \u76C0 245-16-4
+always \u76C1 13456-2
+always \u76C2 1256-2
+always \u76C3 135-356-3
+always \u76C4 1-146-3
+always \u76C5 1-12346-3
+always \u76C6 1234-136-2
+always \u76C7 1235-2346-2
+always \u76C8 13456-2
+always \u76C9 1235-2346-2
+always \u76CA 16-5
+always \u76CB 135-126-3
+always \u76CC 12456-4
+always \u76CD 1235-2346-2
+always \u76CE 1346-5
+always \u76CF 1-1236-4
+always \u76D0 2345-2
+always \u76D1 13-2345-3
+always \u76D2 1235-2346-2
+always \u76D3 1256-3
+always \u76D4 123-1246-3
+always \u76D5 12345-1236-5
+always \u76D6 13-2456-5
+always \u76D7 145-146-5
+always \u76D8 1234-1236-2
+always \u76D9 12345-34-4
+always \u76DA 245-234-2
+always \u76DB 24-1356-5
+always \u76DC 145-146-5
+always \u76DD 14-34-5
+always \u76DE 1-1236-4
+always \u76DF 134-1356-2
+always \u76E0 14-16-2
+always \u76E1 13-1456-5
+always \u76E2 15-1256-5
+always \u76E3 13-2345-3
+always \u76E4 1234-1236-2
+always \u76E5 13-12456-5
+always \u76E6 1236-3
+always \u76E7 14-34-2
+always \u76E8 15-1256-4
+always \u76E9 1-12356-3
+always \u76EA 145-1346-5
+always \u76EB 1236-3
+always \u76EC 13-34-4
+always \u76ED 14-16-5
+always \u76EE 134-34-5
+always \u76EF 145-13456-3
+always \u76F0 13-1236-4
+always \u76F1 15-1256-3
+always \u76F2 134-1346-2
+always \u76F3 134-1346-2
+always \u76F4 1-156-2
+always \u76F5 245-16-5
+always \u76F6 1245-12456-4
+always \u76F7 124-2345-2
+always \u76F8 15-46-3
+always \u76F9 145-123456-4
+always \u76FA 15-1456-3
+always \u76FB 15-16-5
+always \u76FC 1234-1236-5
+always \u76FD 12345-1356-3
+always \u76FE 145-123456-5
+always \u76FF 134-1456-2
+always \u7700 134-13456-2
+always \u7701 24-1356-4
+always \u7702 24-156-5
+always \u7703 256-2
+always \u7704 134-2345-4
+always \u7705 1234-1236-3
+always \u7706 12345-1346-4
+always \u7707 134-246-4
+always \u7708 145-1236-3
+always \u7709 134-356-2
+always \u770A 134-146-5
+always \u770B 123-1236-5
+always \u770C 15-2345-5
+always \u770D 12356-3
+always \u770E 24-156-5
+always \u770F 46-3
+always \u7710 1-1356-3
+always \u7711 246-4
+always \u7712 24-136-5
+always \u7713 1235-25-5
+always \u7714 145-345-5
+always \u7715 1-136-4
+always \u7716 123-456-5
+always \u7717 13-1256-3
+always \u7718 24-136-5
+always \u7719 16-2
+always \u771A 24-1356-4
+always \u771B 134-356-5
+always \u771C 134-126-5
+always \u771D 1-34-5
+always \u771E 1-136-3
+always \u771F 1-136-3
+always \u7720 134-2345-2
+always \u7721 24-156-5
+always \u7722 45-3
+always \u7723 145-346-2
+always \u7724 16-2
+always \u7725 125-156-5
+always \u7726 125-156-5
+always \u7727 12-146-4
+always \u7728 1-345-4
+always \u7729 15-45-5
+always \u772A 135-13456-4
+always \u772B 134-16-4
+always \u772C 14-12346-2
+always \u772D 15-1246-3
+always \u772E 145-12346-5
+always \u772F 134-16-3
+always \u7730 145-346-2
+always \u7731 16-2
+always \u7732 1345-2346-5
+always \u7733 134-13456-2
+always \u7734 15-45-5
+always \u7735 12-156-3
+always \u7736 123-456-5
+always \u7737 13-45-5
+always \u7738 134-12356-2
+always \u7739 1-136-5
+always \u773A 124-246-5
+always \u773B 46-2
+always \u773C 2345-4
+always \u773D 134-126-5
+always \u773E 1-12346-5
+always \u773F 134-2456-5
+always \u7740 1-2346-1
+always \u7741 1-1356-3
+always \u7742 134-356-2
+always \u7743 15-25-3
+always \u7744 24-146-5
+always \u7745 1235-1236-5
+always \u7746 1235-12456-4
+always \u7747 145-16-5
+always \u7748 12-1356-4
+always \u7749 245-25-2
+always \u774A 13-45-5
+always \u774B 2346-2
+always \u774C 12456-4
+always \u774D 15-2345-5
+always \u774E 15-16-3
+always \u774F 123-123456-5
+always \u7750 14-2456-5
+always \u7751 13-2345-4
+always \u7752 24-1236-4
+always \u7753 124-2345-4
+always \u7754 1235-12456-2
+always \u7755 12456-4
+always \u7756 14-13456-2
+always \u7757 24-156-5
+always \u7758 245-235-2
+always \u7759 14-346-5
+always \u775A 23456-2
+always \u775B 13-13456-3
+always \u775C 1-1356-3
+always \u775D 14-16-2
+always \u775E 14-2456-5
+always \u775F 15-1246-5
+always \u7760 13-45-5
+always \u7761 24-1246-5
+always \u7762 15-1246-3
+always \u7763 145-34-3
+always \u7764 135-16-5
+always \u7765 135-16-5
+always \u7766 134-34-5
+always \u7767 1235-123456-3
+always \u7768 1345-16-5
+always \u7769 14-34-5
+always \u776A 13-146-3
+always \u776B 13-346-2
+always \u776C 245-2456-4
+always \u776D 1-12356-4
+always \u776E 1256-2
+always \u776F 1235-123456-3
+always \u7770 134-345-5
+always \u7771 15-23456-5
+always \u7772 15-13456-4
+always \u7773 15-16-3
+always \u7774 13-123456-5
+always \u7775 125-2456-3
+always \u7776 12-123456-4
+always \u7777 13-2345-3
+always \u7778 134-356-5
+always \u7779 145-34-4
+always \u777A 1235-12356-2
+always \u777B 15-45-3
+always \u777C 124-16-2
+always \u777D 123-1246-2
+always \u777E 13-146-3
+always \u777F 1245-1246-5
+always \u7780 134-146-5
+always \u7781 15-1256-5
+always \u7782 12345-345-3
+always \u7783 123456-3
+always \u7784 134-246-2
+always \u7785 12-12356-4
+always \u7786 123-1246-5
+always \u7787 134-16-3
+always \u7788 12346-4
+always \u7789 13-16-5
+always \u778A 145-1346-5
+always \u778B 12-136-3
+always \u778C 123-2346-3
+always \u778D 15-12356-4
+always \u778E 15-23456-3
+always \u778F 245-235-2
+always \u7790 134-146-5
+always \u7791 134-13456-2
+always \u7792 134-1236-2
+always \u7793 24-1246-5
+always \u7794 125-2346-2
+always \u7795 1-1346-5
+always \u7796 16-5
+always \u7797 145-246-3
+always \u7798 123-12356-3
+always \u7799 134-126-5
+always \u779A 24-123456-5
+always \u779B 245-12346-3
+always \u779C 14-12356-2
+always \u779D 12-156-3
+always \u779E 134-1236-2
+always \u779F 1234-246-4
+always \u77A0 12-1356-3
+always \u77A1 13-16-5
+always \u77A2 134-1356-2
+always \u77A3 1235-12456-5
+always \u77A4 24-123456-5
+always \u77A5 1234-346-3
+always \u77A6 15-16-3
+always \u77A7 245-246-2
+always \u77A8 1234-34-2
+always \u77A9 1-34-4
+always \u77AA 145-1356-5
+always \u77AB 24-136-4
+always \u77AC 24-123456-5
+always \u77AD 14-246-4
+always \u77AE 12-2346-5
+always \u77AF 15-2345-2
+always \u77B0 123-1236-5
+always \u77B1 346-5
+always \u77B2 15-1256-5
+always \u77B3 124-12346-2
+always \u77B4 134-12356-2
+always \u77B5 14-1456-2
+always \u77B6 123-1246-5
+always \u77B7 13-2345-5
+always \u77B8 346-5
+always \u77B9 2456-5
+always \u77BA 1235-1246-5
+always \u77BB 1-1236-3
+always \u77BC 13-2345-4
+always \u77BD 13-34-4
+always \u77BE 1-146-5
+always \u77BF 245-1256-2
+always \u77C0 1246-2
+always \u77C1 12-12356-4
+always \u77C2 15-146-5
+always \u77C3 1345-13456-4
+always \u77C4 15-256-3
+always \u77C5 246-5
+always \u77C6 1235-25-5
+always \u77C7 134-1356-2
+always \u77C8 134-2345-2
+always \u77C9 1234-1456-2
+always \u77CA 134-2345-2
+always \u77CB 14-16-5
+always \u77CC 123-456-5
+always \u77CD 13-236-2
+always \u77CE 15-45-3
+always \u77CF 134-2345-2
+always \u77D0 1235-25-5
+always \u77D1 14-34-2
+always \u77D2 134-1356-2
+always \u77D3 14-12346-2
+always \u77D4 13-12456-5
+always \u77D5 134-1236-4
+always \u77D6 15-16-4
+always \u77D7 12-34-5
+always \u77D8 124-1346-4
+always \u77D9 123-1236-5
+always \u77DA 1-34-4
+always \u77DB 134-146-2
+always \u77DC 13-1456-3
+always \u77DD 14-1456-2
+always \u77DE 1256-5
+always \u77DF 24-25-5
+always \u77E0 245-2346-5
+always \u77E1 13-236-2
+always \u77E2 24-156-4
+always \u77E3 16-4
+always \u77E4 24-136-4
+always \u77E5 1-156-3
+always \u77E6 1235-12356-2
+always \u77E7 24-136-4
+always \u77E8 13456-4
+always \u77E9 13-1256-4
+always \u77EA 1-12356-3
+always \u77EB 13-246-4
+always \u77EC 245-25-2
+always \u77ED 145-12456-4
+always \u77EE 2456-4
+always \u77EF 13-246-4
+always \u77F0 125-1356-3
+always \u77F1 1235-25-5
+always \u77F2 135-345-5
+always \u77F3 24-156-2
+always \u77F4 145-13456-5
+always \u77F5 245-16-5
+always \u77F6 13-16-3
+always \u77F7 125-156-4
+always \u77F8 13-1236-3
+always \u77F9 34-5
+always \u77FA 1-2346-2
+always \u77FB 123-34-5
+always \u77FC 13-46-3
+always \u77FD 15-16-5
+always \u77FE 12345-1236-2
+always \u77FF 123-456-5
+always \u7800 145-1346-5
+always \u7801 134-345-4
+always \u7802 24-345-3
+always \u7803 145-1236-3
+always \u7804 13-236-2
+always \u7805 14-16-5
+always \u7806 12345-34-3
+always \u7807 134-1456-2
+always \u7808 2346-5
+always \u7809 1235-35-3
+always \u780A 123-1346-5
+always \u780B 1-156-4
+always \u780C 245-16-5
+always \u780D 123-1236-4
+always \u780E 13-346-5
+always \u780F 1234-1456-3
+always \u7810 2346-5
+always \u7811 23456-5
+always \u7812 1234-16-3
+always \u7813 1-2346-2
+always \u7814 2345-2
+always \u7815 15-1246-5
+always \u7816 1-12456-3
+always \u7817 12-2346-3
+always \u7818 145-123456-5
+always \u7819 1234-1236-3
+always \u781A 2345-5
+always \u781B 13-1456-3
+always \u781C 12345-1356-3
+always \u781D 12345-345-4
+always \u781E 134-126-5
+always \u781F 1-345-3
+always \u7820 245-1256-3
+always \u7821 1256-5
+always \u7822 14-25-4
+always \u7823 124-25-2
+always \u7824 124-25-2
+always \u7825 145-16-4
+always \u7826 1-2456-5
+always \u7827 1-136-3
+always \u7828 2456-5
+always \u7829 12345-34-2
+always \u782A 134-34-4
+always \u782B 1-34-4
+always \u782C 14-345-2
+always \u782D 135-2345-3
+always \u782E 1345-34-4
+always \u782F 1234-13456-3
+always \u7830 1234-1356-3
+always \u7831 14-13456-2
+always \u7832 1234-146-5
+always \u7833 14-2346-5
+always \u7834 1234-126-5
+always \u7835 135-126-3
+always \u7836 1234-126-5
+always \u7837 24-136-3
+always \u7838 125-345-2
+always \u7839 2456-5
+always \u783A 14-16-5
+always \u783B 14-12346-2
+always \u783C 124-12346-2
+always \u783D 235-5
+always \u783E 14-16-5
+always \u783F 123-456-5
+always \u7840 12-34-4
+always \u7841 123-1356-3
+always \u7842 245-45-2
+always \u7843 1-34-3
+always \u7844 123-456-3
+always \u7845 13-1246-3
+always \u7846 2346-5
+always \u7847 1345-146-2
+always \u7848 13-23456-2
+always \u7849 14-34-5
+always \u784A 1246-4
+always \u784B 2456-5
+always \u784C 13-2346-5
+always \u784D 123-136-5
+always \u784E 15-13456-2
+always \u784F 2345-2
+always \u7850 145-12346-5
+always \u7851 1234-1356-3
+always \u7852 15-16-3
+always \u7853 14-146-4
+always \u7854 1235-12346-2
+always \u7855 24-25-5
+always \u7856 15-23456-2
+always \u7857 245-246-3
+always \u7858 245-13456-2
+always \u7859 1246-5
+always \u785A 245-246-2
+always \u785B 125-2346-2
+always \u785C 123-1356-3
+always \u785D 15-246-3
+always \u785E 245-236-5
+always \u785F 12-1236-5
+always \u7860 14-1346-2
+always \u7861 1235-12346-2
+always \u7862 1256-2
+always \u7863 15-246-3
+always \u7864 15-23456-2
+always \u7865 134-1346-4
+always \u7866 14-12346-5
+always \u7867 235-4
+always \u7868 12-2346-3
+always \u7869 12-2346-5
+always \u786A 25-5
+always \u786B 14-234-2
+always \u786C 13456-5
+always \u786D 134-1346-2
+always \u786E 245-236-5
+always \u786F 2345-5
+always \u7870 24-345-3
+always \u7871 123-123456-4
+always \u7872 1256-5
+always \u7873 125-2346-2
+always \u7874 1235-35-3
+always \u7875 14-34-4
+always \u7876 12-136-4
+always \u7877 13-2345-4
+always \u7878 1345-236-5
+always \u7879 15-12346-3
+always \u787A 1-25-2
+always \u787B 123-1356-3
+always \u787C 1234-1356-2
+always \u787D 2345-4
+always \u787E 1-1246-5
+always \u787F 123-12346-3
+always \u7880 245-1356-2
+always \u7881 245-16-2
+always \u7882 125-12346-5
+always \u7883 245-13456-5
+always \u7884 14-1456-2
+always \u7885 13-256-3
+always \u7886 135-126-3
+always \u7887 145-13456-5
+always \u7888 134-1456-2
+always \u7889 145-246-3
+always \u788A 13-2345-3
+always \u788B 1235-2346-5
+always \u788C 14-34-5
+always \u788D 2456-5
+always \u788E 15-1246-5
+always \u788F 245-236-5
+always \u7890 14-13456-2
+always \u7891 135-356-3
+always \u7892 1456-2
+always \u7893 145-1246-5
+always \u7894 34-4
+always \u7895 245-16-2
+always \u7896 14-123456-5
+always \u7897 12456-4
+always \u7898 145-2345-4
+always \u7899 13-1346-3
+always \u789A 135-356-5
+always \u789B 245-16-5
+always \u789C 12-136-4
+always \u789D 1245-12456-4
+always \u789E 2345-2
+always \u789F 145-346-2
+always \u78A0 145-13456-5
+always \u78A1 1-12356-3
+always \u78A2 124-25-2
+always \u78A3 13-346-2
+always \u78A4 13456-3
+always \u78A5 135-2345-4
+always \u78A6 123-2346-5
+always \u78A7 135-16-5
+always \u78A8 1246-3
+always \u78A9 24-25-5
+always \u78AA 1-136-3
+always \u78AB 145-12456-5
+always \u78AC 15-23456-2
+always \u78AD 145-1346-5
+always \u78AE 124-16-2
+always \u78AF 1345-146-4
+always \u78B0 1234-1356-5
+always \u78B1 13-2345-4
+always \u78B2 145-16-5
+always \u78B3 124-1236-5
+always \u78B4 12-345-2
+always \u78B5 124-2345-2
+always \u78B6 245-16-5
+always \u78B7 145-123456-5
+always \u78B8 12345-1356-3
+always \u78B9 15-45-5
+always \u78BA 245-236-5
+always \u78BB 245-236-5
+always \u78BC 134-345-4
+always \u78BD 13-12346-3
+always \u78BE 1345-2345-4
+always \u78BF 15-34-5
+always \u78C0 2346-2
+always \u78C1 245-156-2
+always \u78C2 14-234-5
+always \u78C3 15-156-3
+always \u78C4 124-1346-2
+always \u78C5 135-1346-5
+always \u78C6 1235-35-2
+always \u78C7 1234-16-3
+always \u78C8 123-1246-4
+always \u78C9 15-1346-4
+always \u78CA 14-356-4
+always \u78CB 245-25-3
+always \u78CC 124-2345-2
+always \u78CD 15-23456-2
+always \u78CE 15-16-3
+always \u78CF 14-2345-2
+always \u78D0 1234-1236-2
+always \u78D1 1246-5
+always \u78D2 256-4
+always \u78D3 145-1246-3
+always \u78D4 1-2346-2
+always \u78D5 123-2346-3
+always \u78D6 14-345-2
+always \u78D7 1-12456-3
+always \u78D8 246-2
+always \u78D9 13-123456-4
+always \u78DA 1-12456-3
+always \u78DB 12-1236-2
+always \u78DC 245-16-5
+always \u78DD 245-246-3
+always \u78DE 1234-1356-3
+always \u78DF 14-34-5
+always \u78E0 14-34-4
+always \u78E1 123-1236-5
+always \u78E2 245-46-4
+always \u78E3 12-136-4
+always \u78E4 1456-4
+always \u78E5 14-356-4
+always \u78E6 135-246-3
+always \u78E7 245-16-5
+always \u78E8 134-126-2
+always \u78E9 245-16-3
+always \u78EA 245-1246-3
+always \u78EB 125-12346-3
+always \u78EC 245-13456-5
+always \u78ED 12-25-5
+always \u78EE 14-123456-2
+always \u78EF 13-16-3
+always \u78F0 24-1236-5
+always \u78F1 14-146-2
+always \u78F2 245-1256-2
+always \u78F3 125-1356-3
+always \u78F4 145-1356-5
+always \u78F5 13-2345-5
+always \u78F6 15-16-5
+always \u78F7 14-1456-2
+always \u78F8 145-13456-5
+always \u78F9 145-2345-5
+always \u78FA 1235-456-2
+always \u78FB 1234-1236-2
+always \u78FC 125-345-2
+always \u78FD 245-246-3
+always \u78FE 145-16-3
+always \u78FF 14-16-5
+always \u7900 13-2345-5
+always \u7901 13-246-3
+always \u7902 15-16-3
+always \u7903 1-1346-4
+always \u7904 245-246-2
+always \u7905 145-123456-3
+always \u7906 13-2345-4
+always \u7907 1256-5
+always \u7908 1-1246-5
+always \u7909 1235-2346-2
+always \u790A 1235-25-5
+always \u790B 1-2456-2
+always \u790C 14-356-5
+always \u790D 123-2346-4
+always \u790E 12-34-4
+always \u790F 13-16-2
+always \u7910 245-236-5
+always \u7911 145-1346-5
+always \u7912 16-4
+always \u7913 13-46-3
+always \u7914 1234-16-5
+always \u7915 1234-16-3
+always \u7916 1256-5
+always \u7917 1234-1456-3
+always \u7918 245-16-5
+always \u7919 2456-5
+always \u791A 123-2346-3
+always \u791B 13-2345-3
+always \u791C 1256-5
+always \u791D 1245-12456-4
+always \u791E 134-1356-2
+always \u791F 1234-146-5
+always \u7920 245-156-2
+always \u7921 135-126-2
+always \u7922 46-4
+always \u7923 134-346-5
+always \u7924 245-345-4
+always \u7925 15-2345-2
+always \u7926 123-456-5
+always \u7927 14-356-4
+always \u7928 14-356-4
+always \u7929 1-156-5
+always \u792A 14-16-5
+always \u792B 14-16-5
+always \u792C 12345-1236-2
+always \u792D 245-236-5
+always \u792E 1234-146-5
+always \u792F 13456-3
+always \u7930 14-16-5
+always \u7931 14-12346-2
+always \u7932 14-12346-2
+always \u7933 134-126-5
+always \u7934 135-126-2
+always \u7935 24-456-3
+always \u7936 13-12456-5
+always \u7937 14-1236-2
+always \u7938 125-1236-4
+always \u7939 2345-2
+always \u793A 24-156-5
+always \u793B 24-156-5
+always \u793C 14-16-4
+always \u793D 1245-1356-2
+always \u793E 24-2346-5
+always \u793F 236-5
+always \u7940 15-156-5
+always \u7941 245-16-2
+always \u7942 124-3458-3
+always \u7943 134-345-5
+always \u7944 15-346-5
+always \u7945 246-3
+always \u7946 15-2345-3
+always \u7947 1-156-4
+always \u7948 245-16-2
+always \u7949 1-156-4
+always \u794A 135-1356-3
+always \u794B 145-1246-5
+always \u794C 1-12346-5
+always \u794D 1245-136-5
+always \u794E 16-3
+always \u794F 24-156-2
+always \u7950 234-5
+always \u7951 1-156-5
+always \u7952 124-246-2
+always \u7953 12345-34-2
+always \u7954 12345-34-5
+always \u7955 134-16-5
+always \u7956 125-34-4
+always \u7957 1-156-3
+always \u7958 15-12456-5
+always \u7959 134-356-5
+always \u795A 125-25-5
+always \u795B 245-1256-3
+always \u795C 1235-34-5
+always \u795D 1-34-5
+always \u795E 24-136-2
+always \u795F 15-1246-5
+always \u7960 245-156-2
+always \u7961 12-2456-2
+always \u7962 1345-16-4
+always \u7963 14-1256-4
+always \u7964 1256-4
+always \u7965 15-46-2
+always \u7966 34-2
+always \u7967 124-246-3
+always \u7968 1234-246-5
+always \u7969 1-34-3
+always \u796A 13-1246-4
+always \u796B 15-23456-2
+always \u796C 1-156-3
+always \u796D 13-16-5
+always \u796E 13-146-5
+always \u796F 1-136-3
+always \u7970 13-146-5
+always \u7971 24-1246-5
+always \u7972 13-1456-3
+always \u7973 12-136-4
+always \u7974 13-2456-3
+always \u7975 123-123456-4
+always \u7976 145-16-5
+always \u7977 145-146-4
+always \u7978 1235-25-5
+always \u7979 124-146-2
+always \u797A 245-16-2
+always \u797B 13-34-5
+always \u797C 13-12456-5
+always \u797D 125-1246-5
+always \u797E 14-13456-2
+always \u797F 14-34-5
+always \u7980 135-13456-4
+always \u7981 13-1456-5
+always \u7982 145-146-4
+always \u7983 1-156-2
+always \u7984 14-34-5
+always \u7985 12-1236-2
+always \u7986 135-356-3
+always \u7987 12-34-4
+always \u7988 1235-1246-3
+always \u7989 234-4
+always \u798A 15-16-5
+always \u798B 1456-3
+always \u798C 125-156-3
+always \u798D 1235-25-5
+always \u798E 1-136-3
+always \u798F 12345-34-2
+always \u7990 45-5
+always \u7991 34-2
+always \u7992 15-2345-4
+always \u7993 46-2
+always \u7994 124-16-2
+always \u7995 16-3
+always \u7996 134-356-2
+always \u7997 15-156-3
+always \u7998 145-16-5
+always \u7999 135-356-5
+always \u799A 1-25-2
+always \u799B 1-136-3
+always \u799C 235-4
+always \u799D 13-16-2
+always \u799E 13-146-5
+always \u799F 124-1346-2
+always \u79A0 15-156-3
+always \u79A1 134-345-5
+always \u79A2 124-345-3
+always \u79A3 12345-34-5
+always \u79A4 15-45-3
+always \u79A5 245-16-2
+always \u79A6 1256-5
+always \u79A7 15-16-4
+always \u79A8 13-16-3
+always \u79A9 15-156-5
+always \u79AA 12-1236-2
+always \u79AB 124-1236-4
+always \u79AC 123-2356-5
+always \u79AD 15-1246-5
+always \u79AE 14-16-4
+always \u79AF 1345-12346-2
+always \u79B0 1345-16-4
+always \u79B1 145-146-4
+always \u79B2 14-16-5
+always \u79B3 1245-1346-2
+always \u79B4 236-5
+always \u79B5 124-16-2
+always \u79B6 125-1236-5
+always \u79B7 14-356-5
+always \u79B8 1245-12356-2
+always \u79B9 1256-4
+always \u79BA 1256-2
+always \u79BB 14-16-2
+always \u79BC 15-346-5
+always \u79BD 245-1456-2
+always \u79BE 1235-2346-2
+always \u79BF 124-34-3
+always \u79C0 15-234-5
+always \u79C1 15-156-3
+always \u79C2 1245-136-2
+always \u79C3 124-34-3
+always \u79C4 125-156-4
+always \u79C5 12-345-2
+always \u79C6 13-1236-4
+always \u79C7 16-5
+always \u79C8 15-2345-3
+always \u79C9 135-13456-4
+always \u79CA 1345-2345-2
+always \u79CB 245-234-3
+always \u79CC 245-234-3
+always \u79CD 1-12346-4
+always \u79CE 12345-136-5
+always \u79CF 1235-146-5
+always \u79D0 256-2
+always \u79D1 123-2346-3
+always \u79D2 134-246-4
+always \u79D3 1-156-3
+always \u79D4 13-1356-3
+always \u79D5 135-16-4
+always \u79D6 1-156-3
+always \u79D7 1256-5
+always \u79D8 134-16-5
+always \u79D9 123-34-5
+always \u79DA 135-1236-5
+always \u79DB 1234-16-3
+always \u79DC 1345-16-2
+always \u79DD 14-16-5
+always \u79DE 234-2
+always \u79DF 125-34-3
+always \u79E0 1234-16-3
+always \u79E1 135-345-2
+always \u79E2 14-13456-2
+always \u79E3 134-126-5
+always \u79E4 12-1356-5
+always \u79E5 1345-2345-2
+always \u79E6 245-1456-2
+always \u79E7 46-3
+always \u79E8 125-25-2
+always \u79E9 1-156-5
+always \u79EA 1-156-3
+always \u79EB 24-34-2
+always \u79EC 13-1256-5
+always \u79ED 125-156-4
+always \u79EE 1235-25-2
+always \u79EF 13-16-3
+always \u79F0 12-1356-3
+always \u79F1 124-12346-2
+always \u79F2 1-156-5
+always \u79F3 1235-25-2
+always \u79F4 1235-2346-2
+always \u79F5 1456-3
+always \u79F6 125-156-5
+always \u79F7 1-156-2
+always \u79F8 13-346-3
+always \u79F9 1245-136-4
+always \u79FA 145-34-5
+always \u79FB 16-2
+always \u79FC 1-34-3
+always \u79FD 1235-1246-5
+always \u79FE 1345-12346-2
+always \u79FF 12345-34-4
+always \u7A00 15-16-3
+always \u7A01 13-146-4
+always \u7A02 14-1346-2
+always \u7A03 12345-34-3
+always \u7A04 125-2346-5
+always \u7A05 24-1246-5
+always \u7A06 14-1256-4
+always \u7A07 123-123456-4
+always \u7A08 13-1236-4
+always \u7A09 13-1356-3
+always \u7A0A 124-16-2
+always \u7A0B 12-1356-2
+always \u7A0C 124-34-2
+always \u7A0D 24-146-3
+always \u7A0E 24-1246-5
+always \u7A0F 23456-5
+always \u7A10 14-123456-4
+always \u7A11 14-34-5
+always \u7A12 13-34-5
+always \u7A13 125-25-2
+always \u7A14 1245-136-4
+always \u7A15 1-123456-5
+always \u7A16 135-1346-5
+always \u7A17 135-2456-5
+always \u7A18 13-16-3
+always \u7A19 1-156-2
+always \u7A1A 1-156-5
+always \u7A1B 123-123456-4
+always \u7A1C 14-1356-2
+always \u7A1D 1234-1356-2
+always \u7A1E 123-2346-3
+always \u7A1F 135-13456-4
+always \u7A20 12-12356-2
+always \u7A21 125-34-2
+always \u7A22 1256-5
+always \u7A23 15-34-3
+always \u7A24 14-236-5
+always \u7A25 15-46-3
+always \u7A26 16-3
+always \u7A27 15-16-5
+always \u7A28 135-2345-3
+always \u7A29 13-16-5
+always \u7A2A 12345-34-5
+always \u7A2B 135-16-5
+always \u7A2C 1345-25-5
+always \u7A2D 13-346-3
+always \u7A2E 1-12346-4
+always \u7A2F 125-12346-3
+always \u7A30 15-1256-3
+always \u7A31 12-1356-3
+always \u7A32 145-146-5
+always \u7A33 123456-4
+always \u7A34 15-2345-2
+always \u7A35 125-156-3
+always \u7A36 1256-5
+always \u7A37 13-16-5
+always \u7A38 15-1256-5
+always \u7A39 1-136-4
+always \u7A3A 1-156-5
+always \u7A3B 145-146-5
+always \u7A3C 13-23456-5
+always \u7A3D 13-16-3
+always \u7A3E 13-146-4
+always \u7A3F 13-146-4
+always \u7A40 13-34-4
+always \u7A41 1245-12346-2
+always \u7A42 15-1246-5
+always \u7A43 235-3
+always \u7A44 13-16-5
+always \u7A45 123-1346-3
+always \u7A46 134-34-5
+always \u7A47 24-1236-3
+always \u7A48 134-136-2
+always \u7A49 1-156-5
+always \u7A4A 13-16-5
+always \u7A4B 14-34-5
+always \u7A4C 15-34-3
+always \u7A4D 13-16-3
+always \u7A4E 13456-4
+always \u7A4F 123456-4
+always \u7A50 245-234-3
+always \u7A51 15-2346-5
+always \u7A52 124-25-3
+always \u7A53 16-5
+always \u7A54 1235-456-2
+always \u7A55 245-346-5
+always \u7A56 13-16-4
+always \u7A57 15-1246-5
+always \u7A58 15-246-3
+always \u7A59 1234-34-2
+always \u7A5A 13-246-3
+always \u7A5B 1-25-3
+always \u7A5C 124-12346-2
+always \u7A5D 125-1246-5
+always \u7A5E 14-1256-4
+always \u7A5F 15-1246-5
+always \u7A60 1345-12346-2
+always \u7A61 15-2346-5
+always \u7A62 1235-1246-5
+always \u7A63 1245-1346-2
+always \u7A64 1345-25-5
+always \u7A65 1256-5
+always \u7A66 1234-1456-3
+always \u7A67 13-16-5
+always \u7A68 124-1246-2
+always \u7A69 123456-4
+always \u7A6A 12-1356-3
+always \u7A6B 1235-25-5
+always \u7A6C 13-12346-4
+always \u7A6D 14-1256-4
+always \u7A6E 135-246-3
+always \u7A6F 15-2346-5
+always \u7A70 1245-1346-2
+always \u7A71 1-25-3
+always \u7A72 14-16-2
+always \u7A73 125-1236-5
+always \u7A74 15-236-5
+always \u7A75 35-3
+always \u7A76 13-234-5
+always \u7A77 245-235-2
+always \u7A78 15-16-3
+always \u7A79 245-235-2
+always \u7A7A 123-12346-3
+always \u7A7B 1256-3
+always \u7A7C 15-136-3
+always \u7A7D 13-13456-4
+always \u7A7E 246-5
+always \u7A7F 12-12456-3
+always \u7A80 1-123456-3
+always \u7A81 124-34-2
+always \u7A82 14-146-2
+always \u7A83 245-346-5
+always \u7A84 1-2456-4
+always \u7A85 246-4
+always \u7A86 135-2345-4
+always \u7A87 135-146-2
+always \u7A88 246-4
+always \u7A89 135-13456-4
+always \u7A8A 35-3
+always \u7A8B 1-34-2
+always \u7A8C 13-246-5
+always \u7A8D 245-246-5
+always \u7A8E 145-246-5
+always \u7A8F 34-3
+always \u7A90 35-3
+always \u7A91 246-2
+always \u7A92 1-156-5
+always \u7A93 12-456-3
+always \u7A94 246-4
+always \u7A95 124-246-4
+always \u7A96 13-246-5
+always \u7A97 12-456-3
+always \u7A98 13-235-4
+always \u7A99 15-246-3
+always \u7A9A 12-1356-2
+always \u7A9B 123-12356-5
+always \u7A9C 245-12456-5
+always \u7A9D 25-3
+always \u7A9E 145-1236-5
+always \u7A9F 123-34-3
+always \u7AA0 123-2346-3
+always \u7AA1 1-1246-5
+always \u7AA2 15-1256-5
+always \u7AA3 15-34-5
+always \u7AA4 13-12456-3
+always \u7AA5 123-1246-3
+always \u7AA6 145-12356-5
+always \u7AA7 1-2346-3
+always \u7AA8 1456-5
+always \u7AA9 25-3
+always \u7AAA 35-3
+always \u7AAB 23456-5
+always \u7AAC 1256-2
+always \u7AAD 13-1256-5
+always \u7AAE 245-235-2
+always \u7AAF 246-2
+always \u7AB0 246-2
+always \u7AB1 124-246-4
+always \u7AB2 12-146-2
+always \u7AB3 1256-4
+always \u7AB4 124-2345-2
+always \u7AB5 145-246-5
+always \u7AB6 13-1256-5
+always \u7AB7 14-246-2
+always \u7AB8 15-16-3
+always \u7AB9 34-5
+always \u7ABA 123-1246-3
+always \u7ABB 12-456-3
+always \u7ABC 1-146-3
+always \u7ABD 123-12456-4
+always \u7ABE 123-12456-4
+always \u7ABF 14-12346-2
+always \u7AC0 12-1356-3
+always \u7AC1 245-1246-5
+always \u7AC2 1234-246-2
+always \u7AC3 125-146-5
+always \u7AC4 245-12456-5
+always \u7AC5 245-246-5
+always \u7AC6 245-235-2
+always \u7AC7 145-12356-5
+always \u7AC8 125-146-5
+always \u7AC9 14-12346-4
+always \u7ACA 245-346-5
+always \u7ACB 14-16-5
+always \u7ACC 12-34-5
+always \u7ACD 24-156-2
+always \u7ACE 12345-34-5
+always \u7ACF 245-2345-3
+always \u7AD0 12-34-5
+always \u7AD1 1235-12346-2
+always \u7AD2 245-16-2
+always \u7AD3 1235-146-2
+always \u7AD4 24-1356-3
+always \u7AD5 12345-136-3
+always \u7AD6 24-34-5
+always \u7AD7 134-246-5
+always \u7AD8 13-1256-4
+always \u7AD9 1-1236-5
+always \u7ADA 1-34-5
+always \u7ADB 14-13456-2
+always \u7ADC 14-12346-2
+always \u7ADD 135-13456-5
+always \u7ADE 13-13456-5
+always \u7ADF 13-13456-5
+always \u7AE0 1-1346-3
+always \u7AE1 135-2456-4
+always \u7AE2 15-156-5
+always \u7AE3 13-256-5
+always \u7AE4 1235-12346-2
+always \u7AE5 124-12346-2
+always \u7AE6 15-12346-4
+always \u7AE7 13-13456-5
+always \u7AE8 145-246-5
+always \u7AE9 16-5
+always \u7AEA 24-34-5
+always \u7AEB 13-13456-5
+always \u7AEC 245-1256-4
+always \u7AED 13-346-2
+always \u7AEE 1234-13456-2
+always \u7AEF 145-12456-3
+always \u7AF0 24-146-2
+always \u7AF1 1-12456-4
+always \u7AF2 245-1356-2
+always \u7AF3 145-1356-3
+always \u7AF4 245-1246-3
+always \u7AF5 2356-3
+always \u7AF6 13-13456-5
+always \u7AF7 123-1236-4
+always \u7AF8 13-13456-5
+always \u7AF9 1-34-2
+always \u7AFA 1-34-2
+always \u7AFB 14-2346-5
+always \u7AFC 1234-1356-2
+always \u7AFD 1256-2
+always \u7AFE 12-156-2
+always \u7AFF 13-1236-3
+always \u7B00 134-1346-2
+always \u7B01 1-34-2
+always \u7B02 12456-2
+always \u7B03 145-34-4
+always \u7B04 13-16-3
+always \u7B05 15-246-2
+always \u7B06 135-345-3
+always \u7B07 15-12456-5
+always \u7B08 13-16-2
+always \u7B09 1-136-4
+always \u7B0A 1-146-5
+always \u7B0B 15-123456-4
+always \u7B0C 23456-2
+always \u7B0D 1-1246-5
+always \u7B0E 45-2
+always \u7B0F 1235-34-5
+always \u7B10 13-1346-3
+always \u7B11 15-246-5
+always \u7B12 245-136-2
+always \u7B13 1234-16-2
+always \u7B14 135-16-4
+always \u7B15 13-2345-4
+always \u7B16 16-4
+always \u7B17 145-12346-3
+always \u7B18 24-1236-3
+always \u7B19 24-1356-3
+always \u7B1A 15-23456-2
+always \u7B1B 145-16-2
+always \u7B1C 1-34-2
+always \u7B1D 1345-345-5
+always \u7B1E 12-156-3
+always \u7B1F 13-34-3
+always \u7B20 14-16-5
+always \u7B21 245-346-5
+always \u7B22 134-1456-4
+always \u7B23 135-146-3
+always \u7B24 124-246-2
+always \u7B25 15-156-5
+always \u7B26 12345-34-2
+always \u7B27 245-2346-5
+always \u7B28 135-136-5
+always \u7B29 1234-356-5
+always \u7B2A 145-345-2
+always \u7B2B 125-156-4
+always \u7B2C 145-16-5
+always \u7B2D 14-13456-2
+always \u7B2E 125-2346-2
+always \u7B2F 1345-34-2
+always \u7B30 12345-34-2
+always \u7B31 13-12356-4
+always \u7B32 12345-1236-2
+always \u7B33 13-23456-3
+always \u7B34 13-2346-4
+always \u7B35 12345-1236-5
+always \u7B36 24-156-4
+always \u7B37 134-146-4
+always \u7B38 1234-126-4
+always \u7B39 15-246-4
+always \u7B3A 13-2345-3
+always \u7B3B 15-235-2
+always \u7B3C 14-12346-2
+always \u7B3D 134-1456-4
+always \u7B3E 135-2345-3
+always \u7B3F 14-25-5
+always \u7B40 13-1246-5
+always \u7B41 245-1256-4
+always \u7B42 12-156-2
+always \u7B43 1456-3
+always \u7B44 246-5
+always \u7B45 15-2345-4
+always \u7B46 135-16-4
+always \u7B47 245-235-2
+always \u7B48 13-35-3
+always \u7B49 145-1356-4
+always \u7B4A 13-246-4
+always \u7B4B 13-1456-3
+always \u7B4C 245-45-2
+always \u7B4D 15-123456-4
+always \u7B4E 1245-34-2
+always \u7B4F 12345-345-2
+always \u7B50 123-456-3
+always \u7B51 1-34-5
+always \u7B52 124-12346-4
+always \u7B53 13-16-3
+always \u7B54 145-345-2
+always \u7B55 15-13456-2
+always \u7B56 245-2346-5
+always \u7B57 1-12346-5
+always \u7B58 123-12356-5
+always \u7B59 14-2456-2
+always \u7B5A 135-16-5
+always \u7B5B 24-2456-3
+always \u7B5C 145-1346-3
+always \u7B5D 1-1356-3
+always \u7B5E 245-2346-5
+always \u7B5F 12345-34-3
+always \u7B60 256-2
+always \u7B61 124-34-2
+always \u7B62 1234-345-2
+always \u7B63 14-16-2
+always \u7B64 14-1346-2
+always \u7B65 13-1256-4
+always \u7B66 13-12456-4
+always \u7B67 13-2345-4
+always \u7B68 1235-1236-2
+always \u7B69 124-12346-4
+always \u7B6A 15-23456-2
+always \u7B6B 1-156-5
+always \u7B6C 12-1356-2
+always \u7B6D 15-12456-5
+always \u7B6E 24-156-5
+always \u7B6F 1-34-5
+always \u7B70 125-25-2
+always \u7B71 15-246-4
+always \u7B72 24-146-3
+always \u7B73 124-13456-2
+always \u7B74 13-23456-2
+always \u7B75 2345-2
+always \u7B76 13-146-4
+always \u7B77 123-2356-5
+always \u7B78 13-1236-3
+always \u7B79 12-12356-2
+always \u7B7A 123-456-3
+always \u7B7B 13-1346-5
+always \u7B7C 256-2
+always \u7B7D 25-3
+always \u7B7E 245-2345-3
+always \u7B7F 15-246-4
+always \u7B80 13-2345-4
+always \u7B81 1234-34-2
+always \u7B82 14-2456-2
+always \u7B83 125-12356-3
+always \u7B84 1234-2456-2
+always \u7B85 135-16-5
+always \u7B86 135-16-5
+always \u7B87 13-2346-5
+always \u7B88 12-156-2
+always \u7B89 13-2356-4
+always \u7B8A 1256-3
+always \u7B8B 13-2345-3
+always \u7B8C 1-146-5
+always \u7B8D 13-34-3
+always \u7B8E 12-156-2
+always \u7B8F 1-1356-3
+always \u7B90 245-13456-5
+always \u7B91 24-345-5
+always \u7B92 1-12356-4
+always \u7B93 14-34-5
+always \u7B94 135-126-2
+always \u7B95 13-16-3
+always \u7B96 14-1456-2
+always \u7B97 15-12456-5
+always \u7B98 13-256-5
+always \u7B99 12345-34-2
+always \u7B9A 1-345-2
+always \u7B9B 13-34-3
+always \u7B9C 123-12346-3
+always \u7B9D 245-2345-2
+always \u7B9E 245-45-3
+always \u7B9F 13-256-5
+always \u7BA0 12-1246-2
+always \u7BA1 13-12456-4
+always \u7BA2 45-3
+always \u7BA3 245-2346-5
+always \u7BA4 125-34-2
+always \u7BA5 135-126-4
+always \u7BA6 125-2346-2
+always \u7BA7 245-346-5
+always \u7BA8 124-25-5
+always \u7BA9 14-25-2
+always \u7BAA 145-1236-3
+always \u7BAB 15-246-3
+always \u7BAC 1245-25-5
+always \u7BAD 13-2345-5
+always \u7BAE 15-45-3
+always \u7BAF 135-2345-3
+always \u7BB0 15-123456-4
+always \u7BB1 15-46-3
+always \u7BB2 15-2345-4
+always \u7BB3 1234-13456-2
+always \u7BB4 1-136-3
+always \u7BB5 24-1356-4
+always \u7BB6 1235-34-2
+always \u7BB7 24-156-3
+always \u7BB8 1-34-5
+always \u7BB9 236-3
+always \u7BBA 12-123456-4
+always \u7BBB 14-1256-5
+always \u7BBC 34-3
+always \u7BBD 145-12346-4
+always \u7BBE 24-25-5
+always \u7BBF 13-16-2
+always \u7BC0 13-346-2
+always \u7BC1 1235-456-2
+always \u7BC2 15-13456-3
+always \u7BC3 134-356-5
+always \u7BC4 12345-1236-5
+always \u7BC5 12-1246-2
+always \u7BC6 1-12456-5
+always \u7BC7 1234-2345-3
+always \u7BC8 12345-1356-3
+always \u7BC9 1-34-2
+always \u7BCA 1235-12346-2
+always \u7BCB 245-346-5
+always \u7BCC 1235-12356-2
+always \u7BCD 245-234-3
+always \u7BCE 134-246-4
+always \u7BCF 245-2345-5
+always \u7BD0 13-34-3
+always \u7BD1 123-1246-5
+always \u7BD2 24-156-2
+always \u7BD3 14-12356-4
+always \u7BD4 256-2
+always \u7BD5 1235-2346-2
+always \u7BD6 124-1346-2
+always \u7BD7 236-5
+always \u7BD8 12-12356-3
+always \u7BD9 13-146-3
+always \u7BDA 12345-356-4
+always \u7BDB 1245-25-5
+always \u7BDC 1-1356-3
+always \u7BDD 13-12356-3
+always \u7BDE 1345-346-5
+always \u7BDF 245-2345-5
+always \u7BE0 15-246-4
+always \u7BE1 245-12456-5
+always \u7BE2 13-12346-3
+always \u7BE3 1234-1346-2
+always \u7BE4 145-34-4
+always \u7BE5 14-16-5
+always \u7BE6 135-16-5
+always \u7BE7 1-25-2
+always \u7BE8 12-34-2
+always \u7BE9 24-2456-3
+always \u7BEA 12-156-2
+always \u7BEB 1-34-2
+always \u7BEC 245-46-3
+always \u7BED 14-12346-2
+always \u7BEE 14-1236-2
+always \u7BEF 13-2345-3
+always \u7BF0 135-34-5
+always \u7BF1 14-16-2
+always \u7BF2 1235-1246-5
+always \u7BF3 135-16-5
+always \u7BF4 145-16-2
+always \u7BF5 245-12346-3
+always \u7BF6 2345-3
+always \u7BF7 1234-1356-2
+always \u7BF8 125-1236-3
+always \u7BF9 1-12456-5
+always \u7BFA 1234-2456-2
+always \u7BFB 1234-246-4
+always \u7BFC 145-12356-3
+always \u7BFD 1256-4
+always \u7BFE 134-346-5
+always \u7BFF 124-12456-2
+always \u7C00 125-2346-2
+always \u7C01 24-2456-3
+always \u7C02 13-25-2
+always \u7C03 16-2
+always \u7C04 1235-34-5
+always \u7C05 12-1236-4
+always \u7C06 123-12356-5
+always \u7C07 245-34-5
+always \u7C08 1234-13456-2
+always \u7C09 12-12356-5
+always \u7C0A 13-16-3
+always \u7C0B 13-1246-4
+always \u7C0C 15-34-5
+always \u7C0D 14-12356-4
+always \u7C0E 245-25-5
+always \u7C0F 14-34-5
+always \u7C10 1345-2345-4
+always \u7C11 15-25-3
+always \u7C12 245-12456-5
+always \u7C13 145-246-3
+always \u7C14 15-25-3
+always \u7C15 14-2346-5
+always \u7C16 145-12456-5
+always \u7C17 14-46-4
+always \u7C18 15-246-3
+always \u7C19 135-126-2
+always \u7C1A 134-16-5
+always \u7C1B 15-156-3
+always \u7C1C 145-1346-5
+always \u7C1D 14-246-2
+always \u7C1E 145-1236-3
+always \u7C1F 145-2345-5
+always \u7C20 12345-34-4
+always \u7C21 13-2345-4
+always \u7C22 134-1456-4
+always \u7C23 123-1246-5
+always \u7C24 145-2456-5
+always \u7C25 13-246-3
+always \u7C26 145-1356-3
+always \u7C27 1235-456-2
+always \u7C28 15-123456-4
+always \u7C29 14-146-2
+always \u7C2A 125-1236-3
+always \u7C2B 15-246-3
+always \u7C2C 14-34-5
+always \u7C2D 24-156-5
+always \u7C2E 125-1236-3
+always \u7C2F 245-16-4
+always \u7C30 1234-2456-2
+always \u7C31 245-16-2
+always \u7C32 1234-2456-2
+always \u7C33 13-1236-4
+always \u7C34 13-1256-5
+always \u7C35 145-34-5
+always \u7C36 14-34-5
+always \u7C37 2345-2
+always \u7C38 135-126-4
+always \u7C39 145-1346-3
+always \u7C3A 15-2456-5
+always \u7C3B 1-35-3
+always \u7C3C 14-12346-2
+always \u7C3D 245-2345-3
+always \u7C3E 14-2345-2
+always \u7C3F 135-34-5
+always \u7C40 1-12356-5
+always \u7C41 14-2456-5
+always \u7C42 24-156-5
+always \u7C43 14-1236-2
+always \u7C44 123-1246-5
+always \u7C45 1256-2
+always \u7C46 236-5
+always \u7C47 1235-146-2
+always \u7C48 1-136-3
+always \u7C49 124-2456-2
+always \u7C4A 124-16-5
+always \u7C4B 1345-346-5
+always \u7C4C 12-12356-2
+always \u7C4D 13-16-2
+always \u7C4E 16-2
+always \u7C4F 245-16-2
+always \u7C50 124-1356-2
+always \u7C51 1-12456-5
+always \u7C52 1-12356-5
+always \u7C53 12345-1236-3
+always \u7C54 15-12356-4
+always \u7C55 1-12356-5
+always \u7C56 245-2345-3
+always \u7C57 1-25-2
+always \u7C58 124-1356-2
+always \u7C59 14-34-5
+always \u7C5A 14-34-2
+always \u7C5B 13-2345-3
+always \u7C5C 124-25-5
+always \u7C5D 13456-2
+always \u7C5E 1256-5
+always \u7C5F 14-2456-5
+always \u7C60 14-12346-2
+always \u7C61 245-346-5
+always \u7C62 14-2345-2
+always \u7C63 14-1236-2
+always \u7C64 245-2345-3
+always \u7C65 236-5
+always \u7C66 1-12346-3
+always \u7C67 245-1256-2
+always \u7C68 14-2345-2
+always \u7C69 135-2345-3
+always \u7C6A 145-12456-5
+always \u7C6B 125-12456-4
+always \u7C6C 14-16-2
+always \u7C6D 15-156-3
+always \u7C6E 14-25-2
+always \u7C6F 13456-2
+always \u7C70 236-5
+always \u7C71 1-25-2
+always \u7C72 1256-5
+always \u7C73 134-16-4
+always \u7C74 145-16-2
+always \u7C75 12345-1236-2
+always \u7C76 24-136-3
+always \u7C77 1-2346-2
+always \u7C78 24-136-3
+always \u7C79 1345-1256-4
+always \u7C7A 15-346-2
+always \u7C7B 14-356-5
+always \u7C7C 15-2345-3
+always \u7C7D 125-156-4
+always \u7C7E 1345-16-2
+always \u7C7F 245-123456-5
+always \u7C80 1-1346-5
+always \u7C81 245-2345-3
+always \u7C82 1-2456-3
+always \u7C83 135-16-4
+always \u7C84 135-1236-4
+always \u7C85 34-5
+always \u7C86 24-345-3
+always \u7C87 123-1346-3
+always \u7C88 1245-12356-2
+always \u7C89 12345-136-4
+always \u7C8A 135-16-5
+always \u7C8B 245-1246-5
+always \u7C8C 1456-4
+always \u7C8D 14-16-2
+always \u7C8E 12-156-4
+always \u7C8F 124-2456-5
+always \u7C90 123-34-5
+always \u7C91 135-345-3
+always \u7C92 14-16-5
+always \u7C93 13-1236-3
+always \u7C94 13-1256-5
+always \u7C95 1234-126-5
+always \u7C96 134-126-5
+always \u7C97 245-34-3
+always \u7C98 1345-2345-2
+always \u7C99 1-12356-5
+always \u7C9A 14-16-2
+always \u7C9B 15-34-5
+always \u7C9C 124-246-5
+always \u7C9D 14-16-5
+always \u7C9E 15-16-3
+always \u7C9F 15-34-5
+always \u7CA0 1235-12346-2
+always \u7CA1 124-12346-2
+always \u7CA2 125-156-3
+always \u7CA3 245-2346-5
+always \u7CA4 236-5
+always \u7CA5 1-12356-3
+always \u7CA6 14-1456-2
+always \u7CA7 1-456-3
+always \u7CA8 135-2456-4
+always \u7CA9 14-146-3
+always \u7CAA 12345-136-5
+always \u7CAB 156-2
+always \u7CAC 245-1256-3
+always \u7CAD 1235-2346-2
+always \u7CAE 14-46-2
+always \u7CAF 15-2345-5
+always \u7CB0 12345-34-3
+always \u7CB1 14-46-2
+always \u7CB2 245-1236-5
+always \u7CB3 13-13456-3
+always \u7CB4 14-16-4
+always \u7CB5 236-5
+always \u7CB6 14-34-5
+always \u7CB7 13-1256-2
+always \u7CB8 245-16-2
+always \u7CB9 245-1246-5
+always \u7CBA 135-2456-5
+always \u7CBB 1-1346-3
+always \u7CBC 14-1456-2
+always \u7CBD 125-12346-5
+always \u7CBE 13-13456-3
+always \u7CBF 13-25-4
+always \u7CC0 1235-35-3
+always \u7CC1 15-1236-4
+always \u7CC2 15-1236-4
+always \u7CC3 124-1346-2
+always \u7CC4 135-2345-3
+always \u7CC5 1245-12356-4
+always \u7CC6 134-2345-5
+always \u7CC7 1235-12356-2
+always \u7CC8 15-1256-4
+always \u7CC9 125-12346-5
+always \u7CCA 1235-34-2
+always \u7CCB 13-2345-5
+always \u7CCC 125-1236-2
+always \u7CCD 245-156-2
+always \u7CCE 138-12346-3-123458-136-3
+always \u7CCF 15-346-5
+always \u7CD0 12345-34-3
+always \u7CD1 1345-16-5
+always \u7CD2 135-356-5
+always \u7CD3 13-34-4
+always \u7CD4 15-234-4
+always \u7CD5 13-146-3
+always \u7CD6 124-1346-2
+always \u7CD7 245-234-4
+always \u7CD8 13-23456-3
+always \u7CD9 245-146-3
+always \u7CDA 1-456-3
+always \u7CDB 124-1346-2
+always \u7CDC 134-16-2
+always \u7CDD 15-1236-4
+always \u7CDE 12345-136-5
+always \u7CDF 125-146-3
+always \u7CE0 123-1346-3
+always \u7CE1 13-46-5
+always \u7CE2 134-126-2
+always \u7CE3 15-1236-4
+always \u7CE4 15-1236-4
+always \u7CE5 1345-25-5
+always \u7CE6 15-16-3
+always \u7CE7 14-46-2
+always \u7CE8 13-46-5
+always \u7CE9 123-2356-5
+always \u7CEA 135-126-2
+always \u7CEB 1235-12456-2
+always \u7CEC 24-34-4
+always \u7CED 125-12346-5
+always \u7CEE 15-2345-5
+always \u7CEF 1345-25-5
+always \u7CF0 124-12456-2
+always \u7CF1 1345-346-5
+always \u7CF2 14-16-5
+always \u7CF3 125-25-5
+always \u7CF4 145-16-2
+always \u7CF5 1345-346-5
+always \u7CF6 124-246-5
+always \u7CF7 14-1236-2
+always \u7CF8 134-16-5
+always \u7CF9 134-16-5
+always \u7CFA 13-234-3
+always \u7CFB 15-16-5
+always \u7CFC 13-12346-3
+always \u7CFD 1-1356-4
+always \u7CFE 13-234-3
+always \u7CFF 234-5
+always \u7D00 13-16-5
+always \u7D01 12-345-5
+always \u7D02 1-12356-5
+always \u7D03 15-256-2
+always \u7D04 236-3
+always \u7D05 1235-12346-2
+always \u7D06 1256-3
+always \u7D07 1235-2346-2
+always \u7D08 12456-2
+always \u7D09 1245-136-5
+always \u7D0A 123456-5
+always \u7D0B 123456-2
+always \u7D0C 245-234-2
+always \u7D0D 1345-345-5
+always \u7D0E 125-156-3
+always \u7D0F 124-12356-4
+always \u7D10 1345-234-4
+always \u7D11 12345-12356-2
+always \u7D12 13-16-5
+always \u7D13 24-34-3
+always \u7D14 12-123456-2
+always \u7D15 1234-16-3
+always \u7D16 1-136-5
+always \u7D17 24-345-3
+always \u7D18 1235-12346-2
+always \u7D19 1-156-4
+always \u7D1A 13-16-2
+always \u7D1B 12345-136-3
+always \u7D1C 256-2
+always \u7D1D 1245-136-5
+always \u7D1E 145-1236-4
+always \u7D1F 13-1456-3
+always \u7D20 15-34-5
+always \u7D21 12345-1346-4
+always \u7D22 15-25-4
+always \u7D23 245-1246-5
+always \u7D24 13-234-4
+always \u7D25 125-345-3
+always \u7D26 135-345-3
+always \u7D27 13-1456-4
+always \u7D28 12345-34-5
+always \u7D29 1-156-5
+always \u7D2A 245-156-4
+always \u7D2B 125-156-4
+always \u7D2C 12-12356-2
+always \u7D2D 1235-12346-2
+always \u7D2E 125-345-3
+always \u7D2F 14-356-5
+always \u7D30 15-16-5
+always \u7D31 12345-34-2
+always \u7D32 15-346-5
+always \u7D33 24-136-3
+always \u7D34 135-356-5
+always \u7D35 1-34-5
+always \u7D36 245-1256-4
+always \u7D37 14-13456-2
+always \u7D38 1-34-5
+always \u7D39 24-146-5
+always \u7D3A 13-1236-5
+always \u7D3B 46-3
+always \u7D3C 12345-34-2
+always \u7D3D 124-25-2
+always \u7D3E 1-136-4
+always \u7D3F 145-2456-5
+always \u7D40 12-34-5
+always \u7D41 24-156-3
+always \u7D42 1-12346-3
+always \u7D43 15-2345-2
+always \u7D44 125-34-4
+always \u7D45 13-13456-4
+always \u7D46 135-1236-5
+always \u7D47 245-1256-2
+always \u7D48 134-126-5
+always \u7D49 24-34-5
+always \u7D4A 125-1246-5
+always \u7D4B 123-456-5
+always \u7D4C 13-13456-3
+always \u7D4D 1245-136-2
+always \u7D4E 1235-1356-2
+always \u7D4F 15-346-5
+always \u7D50 13-346-2
+always \u7D51 1-34-3
+always \u7D52 12-12356-2
+always \u7D53 13-35-5
+always \u7D54 135-2456-4
+always \u7D55 13-236-2
+always \u7D56 123-456-5
+always \u7D57 1235-34-2
+always \u7D58 245-156-5
+always \u7D59 13-1356-3
+always \u7D5A 13-1356-3
+always \u7D5B 124-146-3
+always \u7D5C 15-346-2
+always \u7D5D 123-34-5
+always \u7D5E 13-246-4
+always \u7D5F 245-45-3
+always \u7D60 13-2456-4
+always \u7D61 14-25-5
+always \u7D62 15-45-5
+always \u7D63 135-1356-3
+always \u7D64 15-2345-5
+always \u7D65 12345-34-2
+always \u7D66 13-356-4
+always \u7D67 124-12346-2
+always \u7D68 1245-12346-2
+always \u7D69 124-246-5
+always \u7D6A 1456-3
+always \u7D6B 14-356-4
+always \u7D6C 15-346-5
+always \u7D6D 13-45-5
+always \u7D6E 15-1256-5
+always \u7D6F 13-2456-3
+always \u7D70 145-346-2
+always \u7D71 124-12346-4
+always \u7D72 15-156-3
+always \u7D73 13-46-5
+always \u7D74 15-46-2
+always \u7D75 1235-1246-5
+always \u7D76 13-236-2
+always \u7D77 1-156-2
+always \u7D78 13-2345-4
+always \u7D79 13-45-5
+always \u7D7A 12-156-3
+always \u7D7B 123456-5
+always \u7D7C 1-136-4
+always \u7D7D 14-1256-4
+always \u7D7E 12-1356-2
+always \u7D7F 245-234-2
+always \u7D80 24-34-3
+always \u7D81 135-1346-4
+always \u7D82 124-12346-4
+always \u7D83 15-246-3
+always \u7D84 12456-5
+always \u7D85 245-1456-3
+always \u7D86 13-1356-4
+always \u7D87 15-234-4
+always \u7D88 124-16-2
+always \u7D89 15-234-5
+always \u7D8A 15-346-2
+always \u7D8B 1235-12346-2
+always \u7D8C 15-16-5
+always \u7D8D 12345-34-2
+always \u7D8E 124-13456-3
+always \u7D8F 15-1246-2
+always \u7D90 145-1246-5
+always \u7D91 123-123456-4
+always \u7D92 12345-34-3
+always \u7D93 13-13456-3
+always \u7D94 1235-34-5
+always \u7D95 1-156-3
+always \u7D96 2345-2
+always \u7D97 13-235-4
+always \u7D98 12345-1356-2
+always \u7D99 13-16-5
+always \u7D9A 15-1256-5
+always \u7D9B 1245-136-4
+always \u7D9C 125-12346-5
+always \u7D9D 12-136-3
+always \u7D9E 145-25-4
+always \u7D9F 14-16-5
+always \u7DA0 14-1256-5
+always \u7DA1 14-46-2
+always \u7DA2 12-12356-2
+always \u7DA3 245-45-4
+always \u7DA4 24-146-5
+always \u7DA5 245-16-2
+always \u7DA6 245-16-2
+always \u7DA7 1-123456-4
+always \u7DA8 245-16-2
+always \u7DA9 12456-4
+always \u7DAA 245-2345-5
+always \u7DAB 15-2345-5
+always \u7DAC 24-12356-5
+always \u7DAD 1246-2
+always \u7DAE 245-16-4
+always \u7DAF 124-146-2
+always \u7DB0 12456-4
+always \u7DB1 13-1346-3
+always \u7DB2 456-4
+always \u7DB3 135-1356-3
+always \u7DB4 1-1246-5
+always \u7DB5 245-2456-4
+always \u7DB6 13-25-4
+always \u7DB7 245-1246-5
+always \u7DB8 14-123456-2
+always \u7DB9 14-234-4
+always \u7DBA 245-16-4
+always \u7DBB 1-1236-5
+always \u7DBC 135-16-5
+always \u7DBD 12-25-5
+always \u7DBE 14-13456-2
+always \u7DBF 134-2345-2
+always \u7DC0 245-16-3
+always \u7DC1 245-346-5
+always \u7DC2 124-1236-3
+always \u7DC3 125-12346-3
+always \u7DC4 13-123456-4
+always \u7DC5 125-12356-3
+always \u7DC6 15-16-3
+always \u7DC7 125-156-3
+always \u7DC8 15-13456-5
+always \u7DC9 14-46-4
+always \u7DCA 13-1456-4
+always \u7DCB 12345-356-3
+always \u7DCC 1245-1246-2
+always \u7DCD 134-1456-2
+always \u7DCE 1256-5
+always \u7DCF 125-12346-4
+always \u7DD0 12345-1236-2
+always \u7DD1 14-1256-5
+always \u7DD2 15-1256-5
+always \u7DD3 13456-3
+always \u7DD4 24-1346-5
+always \u7DD5 245-16-2
+always \u7DD6 15-1256-5
+always \u7DD7 15-46-3
+always \u7DD8 13-2345-3
+always \u7DD9 123-2346-5
+always \u7DDA 15-2345-5
+always \u7DDB 1245-12456-4
+always \u7DDC 134-2345-2
+always \u7DDD 245-16-5
+always \u7DDE 145-12456-5
+always \u7DDF 1-12346-5
+always \u7DE0 145-16-5
+always \u7DE1 134-1456-2
+always \u7DE2 134-246-2
+always \u7DE3 45-2
+always \u7DE4 15-346-5
+always \u7DE5 135-146-4
+always \u7DE6 15-156-3
+always \u7DE7 245-234-3
+always \u7DE8 135-2345-3
+always \u7DE9 1235-12456-4
+always \u7DEA 13-1356-3
+always \u7DEB 125-12346-4
+always \u7DEC 134-2345-4
+always \u7DED 1246-5
+always \u7DEE 12345-34-5
+always \u7DEF 1246-4
+always \u7DF0 124-12356-3
+always \u7DF1 13-12356-3
+always \u7DF2 134-246-4
+always \u7DF3 15-346-2
+always \u7DF4 14-2345-5
+always \u7DF5 125-12346-3
+always \u7DF6 135-2345-5
+always \u7DF7 256-5
+always \u7DF8 1456-3
+always \u7DF9 124-16-2
+always \u7DFA 13-35-3
+always \u7DFB 1-156-5
+always \u7DFC 256-5
+always \u7DFD 12-1356-3
+always \u7DFE 12-1236-2
+always \u7DFF 145-2456-5
+always \u7E00 15-23456-2
+always \u7E01 45-2
+always \u7E02 125-12346-4
+always \u7E03 15-1256-3
+always \u7E04 24-1356-2
+always \u7E05 1246-3
+always \u7E06 13-1356-3
+always \u7E07 15-45-3
+always \u7E08 13456-2
+always \u7E09 13-1456-5
+always \u7E0A 16-5
+always \u7E0B 1-1246-5
+always \u7E0C 1345-16-5
+always \u7E0D 135-1346-3
+always \u7E0E 13-34-4
+always \u7E0F 1234-1236-2
+always \u7E10 1-12356-5
+always \u7E11 13-2345-3
+always \u7E12 245-156-3
+always \u7E13 245-45-5
+always \u7E14 15-1346-3
+always \u7E15 256-5
+always \u7E16 15-23456-5
+always \u7E17 245-1246-3
+always \u7E18 15-16-3
+always \u7E19 1245-12346-2
+always \u7E1A 124-146-3
+always \u7E1B 12345-34-2
+always \u7E1C 256-2
+always \u7E1D 1-136-4
+always \u7E1E 13-146-4
+always \u7E1F 1245-34-5
+always \u7E20 1235-34-2
+always \u7E21 125-2456-4
+always \u7E22 124-1356-2
+always \u7E23 15-2345-5
+always \u7E24 15-34-5
+always \u7E25 1-136-4
+always \u7E26 125-12346-5
+always \u7E27 124-146-3
+always \u7E28 1235-456-4
+always \u7E29 245-2456-5
+always \u7E2A 135-16-5
+always \u7E2B 12345-1356-2
+always \u7E2C 245-34-5
+always \u7E2D 14-16-2
+always \u7E2E 15-25-3
+always \u7E2F 2345-4
+always \u7E30 15-16-4
+always \u7E31 125-12346-5
+always \u7E32 14-356-2
+always \u7E33 1-12456-5
+always \u7E34 245-2345-5
+always \u7E35 134-1236-5
+always \u7E36 1-156-2
+always \u7E37 14-1256-4
+always \u7E38 134-126-5
+always \u7E39 1234-246-4
+always \u7E3A 14-2345-2
+always \u7E3B 134-16-2
+always \u7E3C 15-45-5
+always \u7E3D 125-12346-4
+always \u7E3E 13-16-3
+always \u7E3F 24-1236-3
+always \u7E40 15-1246-5
+always \u7E41 12345-1236-2
+always \u7E42 14-1256-5
+always \u7E43 135-1356-3
+always \u7E44 16-3
+always \u7E45 15-146-3
+always \u7E46 134-246-5
+always \u7E47 246-2
+always \u7E48 245-46-4
+always \u7E49 1235-123456-2
+always \u7E4A 15-2345-3
+always \u7E4B 15-16-5
+always \u7E4C 24-345-1
+always \u7E4D 15-234-5
+always \u7E4E 1245-1236-2
+always \u7E4F 15-45-5
+always \u7E50 15-1246-5
+always \u7E51 245-246-3
+always \u7E52 125-1356-3
+always \u7E53 125-25-4
+always \u7E54 1-156-3
+always \u7E55 24-1236-5
+always \u7E56 15-1236-4
+always \u7E57 14-1456-2
+always \u7E58 1256-5
+always \u7E59 12345-1236-3
+always \u7E5A 14-246-2
+always \u7E5B 12-25-5
+always \u7E5C 125-123456-3
+always \u7E5D 13-2345-5
+always \u7E5E 1245-146-5
+always \u7E5F 12-1236-4
+always \u7E60 1245-1246-4
+always \u7E61 15-234-5
+always \u7E62 1235-1246-5
+always \u7E63 1235-35-5
+always \u7E64 125-12456-4
+always \u7E65 15-16-3
+always \u7E66 245-46-4
+always \u7E67 256-3
+always \u7E68 145-345-2
+always \u7E69 24-1356-2
+always \u7E6A 1235-1246-5
+always \u7E6B 15-16-5
+always \u7E6C 15-2346-5
+always \u7E6D 13-2345-4
+always \u7E6E 13-46-3
+always \u7E6F 1235-12456-2
+always \u7E70 125-146-4
+always \u7E71 245-12346-3
+always \u7E72 13-346-5
+always \u7E73 13-246-4
+always \u7E74 135-16-5
+always \u7E75 12-1236-2
+always \u7E76 16-5
+always \u7E77 1345-12346-4
+always \u7E78 15-1246-5
+always \u7E79 16-5
+always \u7E7A 24-2456-4
+always \u7E7B 15-1256-3
+always \u7E7C 13-16-5
+always \u7E7D 135-1456-3
+always \u7E7E 245-2345-4
+always \u7E7F 14-1236-2
+always \u7E80 1234-34-2
+always \u7E81 15-256-3
+always \u7E82 125-12456-4
+always \u7E83 245-16-2
+always \u7E84 1234-1356-2
+always \u7E85 14-16-5
+always \u7E86 134-126-5
+always \u7E87 14-356-5
+always \u7E88 15-346-2
+always \u7E89 125-12456-4
+always \u7E8A 123-456-5
+always \u7E8B 234-3
+always \u7E8C 15-1256-5
+always \u7E8D 14-356-2
+always \u7E8E 15-2345-3
+always \u7E8F 12-1236-2
+always \u7E90 13-246-4
+always \u7E91 14-34-2
+always \u7E92 12-1236-2
+always \u7E93 13456-3
+always \u7E94 245-2456-2
+always \u7E95 15-46-3
+always \u7E96 15-2345-3
+always \u7E97 125-1246-3
+always \u7E98 125-12456-4
+always \u7E99 14-25-5
+always \u7E9A 24-156-4
+always \u7E9B 145-146-5
+always \u7E9C 14-1236-4
+always \u7E9D 14-356-2
+always \u7E9E 14-2345-5
+always \u7E9F 134-16-5
+always \u7EA0 13-234-3
+always \u7EA1 1256-3
+always \u7EA2 1235-12346-2
+always \u7EA3 1-12356-5
+always \u7EA4 15-2345-3
+always \u7EA5 1235-2346-2
+always \u7EA6 236-3
+always \u7EA7 13-16-2
+always \u7EA8 12456-2
+always \u7EA9 123-456-5
+always \u7EAA 13-16-5
+always \u7EAB 1245-136-5
+always \u7EAC 1246-4
+always \u7EAD 256-2
+always \u7EAE 1235-12346-2
+always \u7EAF 12-123456-2
+always \u7EB0 1234-16-3
+always \u7EB1 24-345-3
+always \u7EB2 13-1346-3
+always \u7EB3 1345-345-5
+always \u7EB4 1245-136-5
+always \u7EB5 125-12346-5
+always \u7EB6 14-123456-2
+always \u7EB7 12345-136-3
+always \u7EB8 1-156-4
+always \u7EB9 123456-2
+always \u7EBA 12345-1346-4
+always \u7EBB 1-34-5
+always \u7EBC 1-136-5
+always \u7EBD 1345-234-4
+always \u7EBE 24-34-3
+always \u7EBF 15-2345-5
+always \u7EC0 13-1236-5
+always \u7EC1 15-346-5
+always \u7EC2 12345-34-2
+always \u7EC3 14-2345-5
+always \u7EC4 125-34-4
+always \u7EC5 24-136-3
+always \u7EC6 15-16-5
+always \u7EC7 1-156-3
+always \u7EC8 1-12346-3
+always \u7EC9 1-12356-5
+always \u7ECA 135-1236-5
+always \u7ECB 12345-34-2
+always \u7ECC 12-34-5
+always \u7ECD 24-146-5
+always \u7ECE 16-5
+always \u7ECF 13-13456-3
+always \u7ED0 145-2456-5
+always \u7ED1 135-1346-4
+always \u7ED2 1245-12346-2
+always \u7ED3 13-346-2
+always \u7ED4 123-34-5
+always \u7ED5 1245-146-5
+always \u7ED6 145-346-2
+always \u7ED7 1235-1346-2
+always \u7ED8 1235-1246-5
+always \u7ED9 13-356-4
+always \u7EDA 15-45-5
+always \u7EDB 13-46-5
+always \u7EDC 14-25-5
+always \u7EDD 13-236-2
+always \u7EDE 13-246-4
+always \u7EDF 124-12346-4
+always \u7EE0 13-1356-4
+always \u7EE1 15-246-3
+always \u7EE2 13-45-5
+always \u7EE3 15-234-5
+always \u7EE4 15-16-5
+always \u7EE5 15-1246-3
+always \u7EE6 124-146-3
+always \u7EE7 13-16-5
+always \u7EE8 124-16-5
+always \u7EE9 13-16-3
+always \u7EEA 15-1256-5
+always \u7EEB 14-13456-2
+always \u7EEC 13456-3
+always \u7EED 15-1256-5
+always \u7EEE 245-16-4
+always \u7EEF 12345-356-3
+always \u7EF0 12-25-5
+always \u7EF1 24-1346-5
+always \u7EF2 13-123456-4
+always \u7EF3 24-1356-2
+always \u7EF4 1246-2
+always \u7EF5 134-2345-2
+always \u7EF6 24-12356-5
+always \u7EF7 135-1356-3
+always \u7EF8 12-12356-2
+always \u7EF9 124-146-2
+always \u7EFA 14-234-4
+always \u7EFB 245-45-4
+always \u7EFC 125-12346-5
+always \u7EFD 1-1236-5
+always \u7EFE 12456-4
+always \u7EFF 14-1256-5
+always \u7F00 1-1246-5
+always \u7F01 125-156-3
+always \u7F02 123-2346-5
+always \u7F03 15-46-3
+always \u7F04 13-2345-3
+always \u7F05 134-2345-4
+always \u7F06 14-1236-4
+always \u7F07 124-16-2
+always \u7F08 134-246-4
+always \u7F09 13-16-3
+always \u7F0A 256-5
+always \u7F0B 1235-1246-5
+always \u7F0C 15-156-3
+always \u7F0D 145-25-4
+always \u7F0E 145-12456-5
+always \u7F0F 135-2345-5
+always \u7F10 15-2345-5
+always \u7F11 13-12356-3
+always \u7F12 1-1246-5
+always \u7F13 1235-12456-4
+always \u7F14 145-16-5
+always \u7F15 14-1256-4
+always \u7F16 135-2345-3
+always \u7F17 134-1456-2
+always \u7F18 45-2
+always \u7F19 13-1456-5
+always \u7F1A 12345-34-5
+always \u7F1B 1245-34-5
+always \u7F1C 1-136-4
+always \u7F1D 12345-1356-5
+always \u7F1E 245-1246-3
+always \u7F1F 13-146-4
+always \u7F20 12-1236-2
+always \u7F21 14-16-2
+always \u7F22 16-5
+always \u7F23 13-2345-3
+always \u7F24 135-1456-3
+always \u7F25 1234-246-4
+always \u7F26 134-1236-5
+always \u7F27 14-356-2
+always \u7F28 13456-3
+always \u7F29 15-25-3
+always \u7F2A 134-246-5
+always \u7F2B 15-146-3
+always \u7F2C 15-346-2
+always \u7F2D 14-246-2
+always \u7F2E 24-1236-5
+always \u7F2F 125-1356-3
+always \u7F30 13-46-3
+always \u7F31 245-2345-4
+always \u7F32 125-146-4
+always \u7F33 1235-12456-2
+always \u7F34 13-246-4
+always \u7F35 125-12456-4
+always \u7F36 12345-12356-4
+always \u7F37 15-346-5
+always \u7F38 13-1346-3
+always \u7F39 12345-12356-4
+always \u7F3A 245-236-3
+always \u7F3B 12345-12356-4
+always \u7F3C 245-16-3
+always \u7F3D 135-126-3
+always \u7F3E 1234-13456-2
+always \u7F3F 15-46-5
+always \u7F40 1-146-5
+always \u7F41 13-1346-3
+always \u7F42 13456-3
+always \u7F43 13456-3
+always \u7F44 245-13456-5
+always \u7F45 15-23456-5
+always \u7F46 13-12456-5
+always \u7F47 125-123456-3
+always \u7F48 124-1236-2
+always \u7F49 245-1346-3
+always \u7F4A 245-16-5
+always \u7F4B 12346-5
+always \u7F4C 13456-3
+always \u7F4D 14-356-2
+always \u7F4E 124-1236-2
+always \u7F4F 14-34-2
+always \u7F50 13-12456-5
+always \u7F51 456-4
+always \u7F52 456-4
+always \u7F53 13-1346-3
+always \u7F54 456-4
+always \u7F55 1235-1236-4
+always \u7F56 134-345-1
+always \u7F57 14-25-2
+always \u7F58 12345-34-2
+always \u7F59 134-16-2
+always \u7F5A 12345-345-2
+always \u7F5B 13-34-3
+always \u7F5C 1-34-4
+always \u7F5D 13-1256-3
+always \u7F5E 134-146-2
+always \u7F5F 13-34-4
+always \u7F60 134-1456-2
+always \u7F61 13-1346-3
+always \u7F62 135-345-5
+always \u7F63 13-35-5
+always \u7F64 124-16-2
+always \u7F65 13-45-5
+always \u7F66 12345-34-2
+always \u7F67 24-136-5
+always \u7F68 2345-4
+always \u7F69 1-146-5
+always \u7F6A 125-1246-5
+always \u7F6B 13-35-5
+always \u7F6C 1-25-2
+always \u7F6D 1256-5
+always \u7F6E 1-156-5
+always \u7F6F 1236-4
+always \u7F70 12345-345-2
+always \u7F71 1345-1236-4
+always \u7F72 24-34-4
+always \u7F73 15-156-3
+always \u7F74 1234-16-2
+always \u7F75 134-345-5
+always \u7F76 14-234-4
+always \u7F77 135-345-5
+always \u7F78 12345-345-2
+always \u7F79 14-16-2
+always \u7F7A 12-146-3
+always \u7F7B 1246-5
+always \u7F7C 135-16-5
+always \u7F7D 13-16-5
+always \u7F7E 125-1356-3
+always \u7F7F 12-12346-3
+always \u7F80 14-234-4
+always \u7F81 13-16-3
+always \u7F82 13-45-5
+always \u7F83 134-16-5
+always \u7F84 1-146-5
+always \u7F85 14-25-2
+always \u7F86 1234-16-2
+always \u7F87 13-16-3
+always \u7F88 13-16-3
+always \u7F89 14-12456-2
+always \u7F8A 46-2
+always \u7F8B 134-346-3
+always \u7F8C 245-46-3
+always \u7F8D 124-345-5
+always \u7F8E 134-356-4
+always \u7F8F 46-2
+always \u7F90 234-4
+always \u7F91 234-4
+always \u7F92 12345-136-2
+always \u7F93 135-345-3
+always \u7F94 13-146-3
+always \u7F95 46-5
+always \u7F96 13-34-4
+always \u7F97 245-46-3
+always \u7F98 125-1346-3
+always \u7F99 13-146-3
+always \u7F9A 14-13456-2
+always \u7F9B 16-5
+always \u7F9C 1-34-5
+always \u7F9D 145-16-3
+always \u7F9E 15-234-3
+always \u7F9F 245-46-4
+always \u7FA0 16-2
+always \u7FA1 15-2345-5
+always \u7FA2 1245-12346-2
+always \u7FA3 245-256-2
+always \u7FA4 245-256-2
+always \u7FA5 245-46-4
+always \u7FA6 1235-12456-2
+always \u7FA7 15-25-3
+always \u7FA8 15-2345-5
+always \u7FA9 16-5
+always \u7FAA 46-4
+always \u7FAB 245-46-3
+always \u7FAC 2345-2
+always \u7FAD 1256-2
+always \u7FAE 13-1356-3
+always \u7FAF 13-346-2
+always \u7FB0 124-1346-3
+always \u7FB1 45-2
+always \u7FB2 15-16-3
+always \u7FB3 12345-1236-2
+always \u7FB4 24-1236-3
+always \u7FB5 12345-136-2
+always \u7FB6 24-1236-3
+always \u7FB7 14-2345-4
+always \u7FB8 14-356-2
+always \u7FB9 13-1356-3
+always \u7FBA 1345-12356-2
+always \u7FBB 245-46-5
+always \u7FBC 12-1236-5
+always \u7FBD 1256-4
+always \u7FBE 13-12346-5
+always \u7FBF 16-5
+always \u7FC0 12-12346-3
+always \u7FC1 12346-3
+always \u7FC2 12345-136-3
+always \u7FC3 1235-12346-2
+always \u7FC4 12-156-5
+always \u7FC5 12-156-5
+always \u7FC6 245-1246-5
+always \u7FC7 12345-34-2
+always \u7FC8 15-23456-2
+always \u7FC9 1234-136-4
+always \u7FCA 16-5
+always \u7FCB 14-345-3
+always \u7FCC 16-5
+always \u7FCD 1234-16-3
+always \u7FCE 14-13456-2
+always \u7FCF 14-246-2
+always \u7FD0 1-156-5
+always \u7FD1 245-1256-2
+always \u7FD2 15-16-2
+always \u7FD3 15-346-2
+always \u7FD4 15-46-2
+always \u7FD5 15-16-5
+always \u7FD6 15-16-5
+always \u7FD7 245-16-2
+always \u7FD8 245-246-5
+always \u7FD9 1235-1246-5
+always \u7FDA 1235-1246-3
+always \u7FDB 15-246-3
+always \u7FDC 15-2346-5
+always \u7FDD 1235-12346-2
+always \u7FDE 13-46-3
+always \u7FDF 1-2456-2
+always \u7FE0 245-1246-5
+always \u7FE1 12345-356-4
+always \u7FE2 124-146-3
+always \u7FE3 24-345-5
+always \u7FE4 12-156-5
+always \u7FE5 1-34-5
+always \u7FE6 13-2345-4
+always \u7FE7 15-45-3
+always \u7FE8 24-156-5
+always \u7FE9 1234-2345-3
+always \u7FEA 125-12346-3
+always \u7FEB 12456-5
+always \u7FEC 1235-1246-3
+always \u7FED 1235-12356-2
+always \u7FEE 1235-2346-2
+always \u7FEF 1235-2346-5
+always \u7FF0 1235-1236-5
+always \u7FF1 146-2
+always \u7FF2 1234-246-3
+always \u7FF3 16-5
+always \u7FF4 14-2345-2
+always \u7FF5 245-1256-2
+always \u7FF6 146-2
+always \u7FF7 14-1456-2
+always \u7FF8 1234-136-4
+always \u7FF9 245-246-5
+always \u7FFA 146-2
+always \u7FFB 12345-1236-3
+always \u7FFC 16-5
+always \u7FFD 1235-1246-5
+always \u7FFE 15-45-3
+always \u7FFF 145-146-5
+always \u8000 246-5
+always \u8001 14-146-4
+always \u8002 14-146-4
+always \u8003 123-146-4
+always \u8004 134-146-5
+always \u8005 1-2346-4
+always \u8006 245-16-2
+always \u8007 13-12356-4
+always \u8008 13-12356-4
+always \u8009 13-12356-4
+always \u800A 145-346-2
+always \u800B 145-346-2
+always \u800C 156-2
+always \u800D 24-35-4
+always \u800E 1245-12456-4
+always \u800F 156-2
+always \u8010 1345-2456-5
+always \u8011 1-12456-3
+always \u8012 14-356-4
+always \u8013 124-13456-3
+always \u8014 125-156-4
+always \u8015 13-1356-3
+always \u8016 12-146-5
+always \u8017 1235-146-5
+always \u8018 256-2
+always \u8019 1234-345-2
+always \u801A 1234-16-3
+always \u801B 12-156-2
+always \u801C 15-156-5
+always \u801D 12-34-2
+always \u801E 13-23456-3
+always \u801F 13-1256-5
+always \u8020 1235-25-3
+always \u8021 12-34-2
+always \u8022 14-146-5
+always \u8023 14-123456-4
+always \u8024 13-16-2
+always \u8025 124-1346-4
+always \u8026 12356-4
+always \u8027 14-12356-2
+always \u8028 1345-12356-5
+always \u8029 13-46-4
+always \u802A 1234-1346-4
+always \u802B 125-2346-2
+always \u802C 14-12356-2
+always \u802D 13-16-3
+always \u802E 14-146-5
+always \u802F 1235-25-5
+always \u8030 234-3
+always \u8031 134-126-5
+always \u8032 1235-2356-2
+always \u8033 156-4
+always \u8034 1-2346-2
+always \u8035 124-13456-3
+always \u8036 346-3
+always \u8037 145-345-3
+always \u8038 15-12346-4
+always \u8039 245-1456-2
+always \u803A 256-2
+always \u803B 12-156-4
+always \u803C 145-1236-3
+always \u803D 145-1236-3
+always \u803E 1235-12346-2
+always \u803F 13-1356-4
+always \u8040 1-156-2
+always \u8041 1234-1236-5
+always \u8042 1345-346-5
+always \u8043 145-1236-3
+always \u8044 1-136-4
+always \u8045 12-2346-5
+always \u8046 14-13456-2
+always \u8047 1-1356-3
+always \u8048 234-4
+always \u8049 35-3
+always \u804A 14-246-2
+always \u804B 14-12346-2
+always \u804C 1-156-2
+always \u804D 1345-13456-2
+always \u804E 124-246-3
+always \u804F 156-2
+always \u8050 23456-5
+always \u8051 145-346-2
+always \u8052 13-35-3
+always \u8053 15-1256-5
+always \u8054 14-2345-2
+always \u8055 1235-146-5
+always \u8056 24-1356-5
+always \u8057 14-346-5
+always \u8058 1234-1456-5
+always \u8059 13-13456-3
+always \u805A 13-1256-5
+always \u805B 135-16-5
+always \u805C 145-16-4
+always \u805D 13-25-2
+always \u805E 123456-2
+always \u805F 15-1256-5
+always \u8060 1234-13456-2
+always \u8061 245-12346-3
+always \u8062 145-13456-5
+always \u8063 1345-16-2
+always \u8064 124-13456-2
+always \u8065 1256-4
+always \u8066 245-12346-3
+always \u8067 123-1246-2
+always \u8068 14-2345-2
+always \u8069 123-1246-5
+always \u806A 245-12346-3
+always \u806B 14-2345-2
+always \u806C 12346-4
+always \u806D 123-1246-5
+always \u806E 14-2345-2
+always \u806F 14-2345-2
+always \u8070 245-12346-3
+always \u8071 146-2
+always \u8072 24-1356-3
+always \u8073 15-12346-4
+always \u8074 124-13456-3
+always \u8075 123-1246-5
+always \u8076 1345-346-5
+always \u8077 1-156-2
+always \u8078 145-1236-3
+always \u8079 1345-13456-2
+always \u807A 15-346-2
+always \u807B 13-16-3
+always \u807C 124-13456-3
+always \u807D 124-13456-3
+always \u807E 14-12346-2
+always \u807F 1256-5
+always \u8080 1345-346-5
+always \u8081 1-146-5
+always \u8082 15-156-5
+always \u8083 15-34-5
+always \u8084 16-5
+always \u8085 15-34-5
+always \u8086 15-156-5
+always \u8087 1-146-5
+always \u8088 1-146-5
+always \u8089 1245-12356-5
+always \u808A 16-5
+always \u808B 14-2346-5
+always \u808C 13-16-3
+always \u808D 245-234-2
+always \u808E 123-136-4
+always \u808F 245-146-5
+always \u8090 13-2346-3
+always \u8091 145-16-5
+always \u8092 1235-12456-5
+always \u8093 1235-456-3
+always \u8094 16-4
+always \u8095 1245-136-5
+always \u8096 15-246-5
+always \u8097 1245-34-4
+always \u8098 1-12356-4
+always \u8099 45-3
+always \u809A 145-34-5
+always \u809B 13-1346-3
+always \u809C 1245-12346-2
+always \u809D 13-1236-3
+always \u809E 12-345-3
+always \u809F 25-5
+always \u80A0 12-1346-2
+always \u80A1 13-34-4
+always \u80A2 1-156-3
+always \u80A3 1235-1236-2
+always \u80A4 12345-34-3
+always \u80A5 12345-356-2
+always \u80A6 12345-136-2
+always \u80A7 1234-356-3
+always \u80A8 1234-1346-5
+always \u80A9 13-2345-3
+always \u80AA 12345-1346-2
+always \u80AB 1-123456-3
+always \u80AC 234-2
+always \u80AD 1345-345-5
+always \u80AE 1235-1346-2
+always \u80AF 123-136-4
+always \u80B0 1245-1236-2
+always \u80B1 13-12346-3
+always \u80B2 1256-5
+always \u80B3 123456-4
+always \u80B4 246-2
+always \u80B5 245-16-2
+always \u80B6 1234-16-2
+always \u80B7 245-2345-4
+always \u80B8 15-16-5
+always \u80B9 15-16-3
+always \u80BA 12345-356-5
+always \u80BB 123-136-4
+always \u80BC 13-13456-4
+always \u80BD 124-2456-5
+always \u80BE 24-136-5
+always \u80BF 1-12346-4
+always \u80C0 1-1346-5
+always \u80C1 15-346-2
+always \u80C2 24-136-5
+always \u80C3 1246-5
+always \u80C4 1-12356-5
+always \u80C5 145-346-2
+always \u80C6 124-1236-2
+always \u80C7 12345-356-5
+always \u80C8 135-345-2
+always \u80C9 135-126-2
+always \u80CA 245-1256-2
+always \u80CB 124-2345-2
+always \u80CC 135-356-5
+always \u80CD 13-35-3
+always \u80CE 124-2456-3
+always \u80CF 125-156-4
+always \u80D0 123-34-3
+always \u80D1 1-156-3
+always \u80D2 1345-16-5
+always \u80D3 1234-13456-2
+always \u80D4 125-156-5
+always \u80D5 12345-34-3
+always \u80D6 1234-1346-5
+always \u80D7 1-136-3
+always \u80D8 15-2345-2
+always \u80D9 125-25-5
+always \u80DA 1234-356-3
+always \u80DB 13-23456-4
+always \u80DC 24-1356-5
+always \u80DD 1-156-3
+always \u80DE 135-146-3
+always \u80DF 134-34-4
+always \u80E0 245-1256-3
+always \u80E1 1235-34-2
+always \u80E2 123-2346-3
+always \u80E3 12-156-4
+always \u80E4 1456-5
+always \u80E5 15-1256-3
+always \u80E6 46-3
+always \u80E7 14-12346-2
+always \u80E8 145-12346-5
+always \u80E9 123-345-4
+always \u80EA 14-34-2
+always \u80EB 13-13456-5
+always \u80EC 1345-34-4
+always \u80ED 2345-3
+always \u80EE 135-1346-3
+always \u80EF 123-35-5
+always \u80F0 16-2
+always \u80F1 13-456-3
+always \u80F2 13-2456-3
+always \u80F3 13-2346-3
+always \u80F4 145-12346-5
+always \u80F5 12-156-5
+always \u80F6 13-246-3
+always \u80F7 15-235-3
+always \u80F8 15-235-3
+always \u80F9 156-2
+always \u80FA 1236-3
+always \u80FB 15-13456-2
+always \u80FC 1234-2345-2
+always \u80FD 1345-1356-2
+always \u80FE 125-156-5
+always \u80FF 123-1246-5
+always \u8100 12-1356-2
+always \u8101 124-246-5
+always \u8102 1-156-3
+always \u8103 245-1246-5
+always \u8104 134-356-2
+always \u8105 15-346-2
+always \u8106 245-1246-5
+always \u8107 15-346-2
+always \u8108 134-2456-5
+always \u8109 134-2456-5
+always \u810A 13-16-4
+always \u810B 15-346-2
+always \u810C 134-1456-2
+always \u810D 123-2356-5
+always \u810E 15-345-5
+always \u810F 125-1346-3
+always \u8110 245-16-2
+always \u8111 1345-146-4
+always \u8112 134-16-4
+always \u8113 1345-12346-2
+always \u8114 14-12456-2
+always \u8115 12456-5
+always \u8116 135-126-2
+always \u8117 123456-4
+always \u8118 12456-4
+always \u8119 15-234-3
+always \u811A 13-246-4
+always \u811B 13-13456-5
+always \u811C 1245-12356-2
+always \u811D 1235-1356-3
+always \u811E 245-25-4
+always \u811F 14-346-5
+always \u8120 24-1236-3
+always \u8121 124-13456-4
+always \u8122 134-356-2
+always \u8123 12-123456-2
+always \u8124 24-136-5
+always \u8125 13-23456-2
+always \u8126 124-2346-5
+always \u8127 125-1246-3
+always \u8128 245-34-5
+always \u8129 15-234-3
+always \u812A 15-1456-5
+always \u812B 124-25-3
+always \u812C 1234-146-3
+always \u812D 12-1356-2
+always \u812E 1345-356-4
+always \u812F 12345-34-4
+always \u8130 145-12356-5
+always \u8131 124-25-3
+always \u8132 1345-246-5
+always \u8133 1345-146-4
+always \u8134 1234-16-4
+always \u8135 13-34-4
+always \u8136 14-25-2
+always \u8137 14-16-5
+always \u8138 14-2345-4
+always \u8139 1-1346-5
+always \u813A 245-1246-5
+always \u813B 13-346-2
+always \u813C 14-46-4
+always \u813D 24-1246-2
+always \u813E 1234-16-2
+always \u813F 135-246-3
+always \u8140 14-123456-2
+always \u8141 1234-2345-2
+always \u8142 13-25-5
+always \u8143 123-1246-5
+always \u8144 12-1246-2
+always \u8145 145-1236-5
+always \u8146 124-2345-4
+always \u8147 1345-356-4
+always \u8148 13-13456-3
+always \u8149 1345-2456-2
+always \u814A 14-345-5
+always \u814B 346-5
+always \u814C 2345-3
+always \u814D 1245-136-4
+always \u814E 24-136-5
+always \u814F 12-25-5
+always \u8150 12345-34-4
+always \u8151 12345-34-4
+always \u8152 13-1256-3
+always \u8153 12345-356-2
+always \u8154 245-46-3
+always \u8155 12456-5
+always \u8156 145-12346-5
+always \u8157 1234-16-2
+always \u8158 13-25-2
+always \u8159 125-12346-3
+always \u815A 145-13456-5
+always \u815B 25-5
+always \u815C 134-356-2
+always \u815D 1245-12456-4
+always \u815E 1-12456-5
+always \u815F 1-156-5
+always \u8160 245-12356-5
+always \u8161 14-25-2
+always \u8162 12356-4
+always \u8163 145-16-5
+always \u8164 1236-3
+always \u8165 15-13456-3
+always \u8166 1345-146-4
+always \u8167 24-34-5
+always \u8168 12-12456-4
+always \u8169 1345-1236-4
+always \u816A 256-5
+always \u816B 1-12346-4
+always \u816C 1245-12356-2
+always \u816D 2346-5
+always \u816E 15-2456-3
+always \u816F 124-34-2
+always \u8170 246-3
+always \u8171 13-2345-5
+always \u8172 1246-4
+always \u8173 13-246-4
+always \u8174 1256-2
+always \u8175 13-23456-3
+always \u8176 145-12456-5
+always \u8177 135-16-5
+always \u8178 12-1346-2
+always \u8179 12345-34-5
+always \u817A 15-2345-5
+always \u817B 1345-16-5
+always \u817C 134-2345-4
+always \u817D 35-5
+always \u817E 124-1356-2
+always \u817F 124-1246-4
+always \u8180 135-1346-4
+always \u8181 245-2345-4
+always \u8182 14-1256-4
+always \u8183 35-5
+always \u8184 15-12356-5
+always \u8185 124-1346-2
+always \u8186 15-34-5
+always \u8187 1-1246-5
+always \u8188 13-2346-2
+always \u8189 16-5
+always \u818A 135-126-2
+always \u818B 14-246-2
+always \u818C 13-16-2
+always \u818D 1234-16-2
+always \u818E 15-346-2
+always \u818F 13-146-3
+always \u8190 14-1256-4
+always \u8191 135-1456-5
+always \u8192 12356-3
+always \u8193 12-1346-2
+always \u8194 14-34-5
+always \u8195 13-25-2
+always \u8196 1234-1346-3
+always \u8197 12-2356-2
+always \u8198 135-246-3
+always \u8199 13-46-4
+always \u819A 12345-34-3
+always \u819B 124-1346-2
+always \u819C 134-126-2
+always \u819D 15-16-3
+always \u819E 1-12456-3
+always \u819F 14-1256-5
+always \u81A0 13-246-3
+always \u81A1 13456-5
+always \u81A2 14-1256-2
+always \u81A3 1-156-5
+always \u81A4 15-236-4
+always \u81A5 12-123456-3
+always \u81A6 14-1456-2
+always \u81A7 124-12346-2
+always \u81A8 1234-1356-2
+always \u81A9 1345-16-5
+always \u81AA 12-2356-5
+always \u81AB 14-246-2
+always \u81AC 245-1246-5
+always \u81AD 13-1246-3
+always \u81AE 15-246-3
+always \u81AF 124-1356-3
+always \u81B0 12345-1236-2
+always \u81B1 1-156-2
+always \u81B2 13-246-3
+always \u81B3 24-1236-5
+always \u81B4 1235-34-3
+always \u81B5 245-1246-5
+always \u81B6 1245-123456-5
+always \u81B7 15-46-3
+always \u81B8 15-1246-4
+always \u81B9 12345-136-5
+always \u81BA 13456-3
+always \u81BB 24-1236-3
+always \u81BC 1-35-3
+always \u81BD 145-1236-4
+always \u81BE 123-2356-5
+always \u81BF 1345-12346-2
+always \u81C0 124-123456-2
+always \u81C1 14-2345-2
+always \u81C2 135-16-5
+always \u81C3 235-3
+always \u81C4 13-236-2
+always \u81C5 12-34-5
+always \u81C6 16-5
+always \u81C7 13-45-4
+always \u81C8 14-345-5
+always \u81C9 14-2345-4
+always \u81CA 15-146-3
+always \u81CB 124-123456-2
+always \u81CC 13-34-4
+always \u81CD 245-16-2
+always \u81CE 245-1246-5
+always \u81CF 135-1456-5
+always \u81D0 15-256-3
+always \u81D1 1245-34-2
+always \u81D2 1235-25-5
+always \u81D3 125-1346-5
+always \u81D4 15-2345-5
+always \u81D5 135-246-3
+always \u81D6 15-13456-5
+always \u81D7 123-12456-3
+always \u81D8 14-345-5
+always \u81D9 2345-3
+always \u81DA 14-34-2
+always \u81DB 1235-25-5
+always \u81DC 125-1346-3
+always \u81DD 14-25-4
+always \u81DE 245-1256-2
+always \u81DF 125-1346-5
+always \u81E0 14-12456-2
+always \u81E1 1345-16-2
+always \u81E2 125-1346-3
+always \u81E3 12-136-2
+always \u81E4 245-2345-3
+always \u81E5 25-5
+always \u81E6 13-456-5
+always \u81E7 125-1346-3
+always \u81E8 14-1456-2
+always \u81E9 13-456-5
+always \u81EA 125-156-5
+always \u81EB 13-246-4
+always \u81EC 1345-346-5
+always \u81ED 12-12356-5
+always \u81EE 13-16-5
+always \u81EF 13-146-3
+always \u81F0 12-12356-5
+always \u81F1 134-2345-2
+always \u81F2 1345-346-5
+always \u81F3 1-156-5
+always \u81F4 1-156-5
+always \u81F5 13-2346-2
+always \u81F6 13-2345-5
+always \u81F7 145-346-2
+always \u81F8 1-156-5
+always \u81F9 15-234-3
+always \u81FA 124-2456-2
+always \u81FB 1-136-3
+always \u81FC 13-234-5
+always \u81FD 15-2345-5
+always \u81FE 1256-2
+always \u81FF 12-345-3
+always \u8200 246-4
+always \u8201 1256-2
+always \u8202 12-12346-3
+always \u8203 15-16-5
+always \u8204 15-16-5
+always \u8205 13-234-5
+always \u8206 1256-2
+always \u8207 1256-4
+always \u8208 15-13456-3
+always \u8209 13-1256-4
+always \u820A 13-234-5
+always \u820B 15-1456-5
+always \u820C 24-2346-2
+always \u820D 24-2346-5
+always \u820E 24-2346-5
+always \u820F 13-234-4
+always \u8210 24-156-5
+always \u8211 1245-1236-2
+always \u8212 24-34-3
+always \u8213 24-156-5
+always \u8214 124-2345-4
+always \u8215 124-1236-5
+always \u8216 1234-34-3
+always \u8217 1234-34-5
+always \u8218 13-12456-4
+always \u8219 1235-35-5
+always \u821A 1245-1236-2
+always \u821B 12-12456-4
+always \u821C 24-123456-5
+always \u821D 15-23456-2
+always \u821E 34-4
+always \u821F 1-12356-3
+always \u8220 145-146-3
+always \u8221 15-46-3
+always \u8222 24-1236-3
+always \u8223 16-4
+always \u8224 12345-1236-2
+always \u8225 1234-345-3
+always \u8226 124-2456-5
+always \u8227 12345-1236-2
+always \u8228 135-1236-4
+always \u8229 12-12456-2
+always \u822A 1235-1346-2
+always \u822B 12345-1346-4
+always \u822C 135-1236-3
+always \u822D 145-16-4
+always \u822E 14-34-2
+always \u822F 1-12346-3
+always \u8230 13-2345-5
+always \u8231 245-1346-3
+always \u8232 14-13456-2
+always \u8233 1-34-2
+always \u8234 125-2346-2
+always \u8235 145-25-5
+always \u8236 135-126-2
+always \u8237 15-2345-2
+always \u8238 13-2346-4
+always \u8239 12-12456-2
+always \u823A 13-23456-4
+always \u823B 14-34-2
+always \u823C 245-235-2
+always \u823D 1234-1346-2
+always \u823E 15-16-3
+always \u823F 1235-35-2
+always \u8240 12345-34-2
+always \u8241 125-146-5
+always \u8242 12345-1356-2
+always \u8243 14-16-2
+always \u8244 24-146-3
+always \u8245 1256-2
+always \u8246 14-1346-2
+always \u8247 124-13456-4
+always \u8248 1256-5
+always \u8249 1246-4
+always \u824A 135-126-2
+always \u824B 134-1356-4
+always \u824C 1345-2345-5
+always \u824D 13-1256-3
+always \u824E 1235-456-2
+always \u824F 24-12356-4
+always \u8250 125-12346-3
+always \u8251 135-2345-5
+always \u8252 134-146-5
+always \u8253 145-346-2
+always \u8254 145-12356-4
+always \u8255 135-1346-5
+always \u8256 12-345-3
+always \u8257 16-5
+always \u8258 15-146-3
+always \u8259 245-1346-3
+always \u825A 245-146-2
+always \u825B 14-12356-2
+always \u825C 145-2456-5
+always \u825D 15-236-4
+always \u825E 246-5
+always \u825F 12-12346-3
+always \u8260 145-1356-3
+always \u8261 145-1346-3
+always \u8262 245-46-2
+always \u8263 14-34-4
+always \u8264 16-4
+always \u8265 13-16-2
+always \u8266 13-2345-5
+always \u8267 1235-25-5
+always \u8268 134-1356-2
+always \u8269 245-16-2
+always \u826A 14-34-4
+always \u826B 14-34-2
+always \u826C 12-1236-2
+always \u826D 24-456-3
+always \u826E 13-136-5
+always \u826F 14-46-2
+always \u8270 13-2345-3
+always \u8271 13-2345-3
+always \u8272 15-2346-5
+always \u8273 2345-5
+always \u8274 12345-34-2
+always \u8275 1234-13456-3
+always \u8276 2345-5
+always \u8277 2345-5
+always \u8278 245-146-4
+always \u8279 245-146-4
+always \u827A 16-5
+always \u827B 14-2346-5
+always \u827C 124-13456-3
+always \u827D 13-246-3
+always \u827E 2456-5
+always \u827F 1245-1356-2
+always \u8280 124-246-2
+always \u8281 13-246-3
+always \u8282 13-346-2
+always \u8283 1234-1356-2
+always \u8284 12456-2
+always \u8285 16-5
+always \u8286 12-2456-3
+always \u8287 134-2345-2
+always \u8288 134-16-4
+always \u8289 13-1236-3
+always \u828A 245-2345-3
+always \u828B 1256-5
+always \u828C 1256-5
+always \u828D 24-146-2
+always \u828E 245-235-3
+always \u828F 145-34-5
+always \u8290 1235-34-5
+always \u8291 245-16-4
+always \u8292 134-1346-2
+always \u8293 125-156-5
+always \u8294 1235-1246-5
+always \u8295 15-1246-3
+always \u8296 1-156-5
+always \u8297 15-46-3
+always \u8298 1234-16-2
+always \u8299 12345-34-2
+always \u829A 124-123456-2
+always \u829B 1246-4
+always \u829C 34-2
+always \u829D 1-156-3
+always \u829E 245-16-4
+always \u829F 24-1236-3
+always \u82A0 123456-2
+always \u82A1 245-2345-5
+always \u82A2 1245-136-2
+always \u82A3 12345-12356-2
+always \u82A4 123-12356-3
+always \u82A5 13-346-5
+always \u82A6 14-34-2
+always \u82A7 15-1256-5
+always \u82A8 13-16-3
+always \u82A9 245-1456-2
+always \u82AA 245-16-2
+always \u82AB 45-2
+always \u82AC 12345-136-3
+always \u82AD 135-345-3
+always \u82AE 1245-1246-5
+always \u82AF 15-1456-3
+always \u82B0 13-16-5
+always \u82B1 1235-35-3
+always \u82B2 1235-35-3
+always \u82B3 12345-1346-3
+always \u82B4 34-5
+always \u82B5 13-236-2
+always \u82B6 13-12356-3
+always \u82B7 1-156-4
+always \u82B8 256-2
+always \u82B9 245-1456-2
+always \u82BA 146-4
+always \u82BB 12-34-2
+always \u82BC 134-146-5
+always \u82BD 23456-2
+always \u82BE 12345-356-5
+always \u82BF 1245-1356-5
+always \u82C0 1235-1346-2
+always \u82C1 245-12346-3
+always \u82C2 1456-2
+always \u82C3 234-4
+always \u82C4 135-2345-5
+always \u82C5 16-5
+always \u82C6 245-346-3
+always \u82C7 1246-4
+always \u82C8 14-16-5
+always \u82C9 1234-16-4
+always \u82CA 2346-5
+always \u82CB 15-2345-5
+always \u82CC 12-1346-2
+always \u82CD 245-1346-3
+always \u82CE 1-34-5
+always \u82CF 15-34-3
+always \u82D0 16-2
+always \u82D1 45-5
+always \u82D2 1245-1236-4
+always \u82D3 14-13456-2
+always \u82D4 124-2456-2
+always \u82D5 124-246-2
+always \u82D6 145-16-2
+always \u82D7 134-246-2
+always \u82D8 245-13456-4
+always \u82D9 14-16-5
+always \u82DA 235-5
+always \u82DB 123-2346-3
+always \u82DC 134-34-5
+always \u82DD 1234-356-5
+always \u82DE 135-146-3
+always \u82DF 13-12356-4
+always \u82E0 134-1456-2
+always \u82E1 16-4
+always \u82E2 16-4
+always \u82E3 13-1256-5
+always \u82E4 1234-16-3
+always \u82E5 1245-25-5
+always \u82E6 123-34-4
+always \u82E7 1-34-5
+always \u82E8 1345-16-4
+always \u82E9 135-126-2
+always \u82EA 135-13456-4
+always \u82EB 24-1236-3
+always \u82EC 245-234-2
+always \u82ED 246-4
+always \u82EE 15-2345-3
+always \u82EF 135-136-4
+always \u82F0 1235-12346-2
+always \u82F1 13456-3
+always \u82F2 1-345-4
+always \u82F3 145-12346-3
+always \u82F4 13-1256-3
+always \u82F5 145-346-2
+always \u82F6 1345-346-2
+always \u82F7 13-1236-3
+always \u82F8 1235-34-3
+always \u82F9 1234-13456-2
+always \u82FA 134-356-2
+always \u82FB 12345-34-2
+always \u82FC 24-1356-3
+always \u82FD 13-34-3
+always \u82FE 135-16-5
+always \u82FF 1246-5
+always \u8300 12345-34-2
+always \u8301 1-25-2
+always \u8302 134-146-5
+always \u8303 12345-1236-5
+always \u8304 245-346-2
+always \u8305 134-146-2
+always \u8306 134-146-2
+always \u8307 135-345-2
+always \u8308 125-156-4
+always \u8309 134-126-5
+always \u830A 125-156-3
+always \u830B 145-16-4
+always \u830C 12-156-2
+always \u830D 13-16-2
+always \u830E 13-13456-3
+always \u830F 14-12346-2
+always \u8310 245-12346-3
+always \u8311 1345-246-4
+always \u8312 45-2
+always \u8313 15-236-2
+always \u8314 13456-2
+always \u8315 245-235-2
+always \u8316 13-2346-2
+always \u8317 134-13456-2
+always \u8318 14-16-5
+always \u8319 1245-12346-2
+always \u831A 1456-5
+always \u831B 13-136-5
+always \u831C 245-2345-5
+always \u831D 12-2456-4
+always \u831E 12-136-2
+always \u831F 1256-5
+always \u8320 15-234-3
+always \u8321 125-156-5
+always \u8322 14-346-5
+always \u8323 34-2
+always \u8324 13-16-5
+always \u8325 13-1246-3
+always \u8326 245-2346-5
+always \u8327 12-12346-2
+always \u8328 245-156-2
+always \u8329 13-12356-4
+always \u832A 13-456-3
+always \u832B 134-1346-2
+always \u832C 12-345-2
+always \u832D 13-246-3
+always \u832E 13-246-3
+always \u832F 12345-34-2
+always \u8330 1256-2
+always \u8331 1-34-3
+always \u8332 125-156-3
+always \u8333 13-46-3
+always \u8334 1235-1246-2
+always \u8335 1456-3
+always \u8336 12-345-2
+always \u8337 12345-345-2
+always \u8338 1245-12346-2
+always \u8339 1245-34-2
+always \u833A 12-12346-3
+always \u833B 134-1346-4
+always \u833C 124-12346-2
+always \u833D 1-12346-5
+always \u833E 245-2345-3
+always \u833F 1-34-2
+always \u8340 15-256-2
+always \u8341 1235-12456-2
+always \u8342 123-35-3
+always \u8343 245-45-2
+always \u8344 13-2456-3
+always \u8345 145-345-3
+always \u8346 13-13456-3
+always \u8347 15-13456-5
+always \u8348 12-12456-4
+always \u8349 245-146-4
+always \u834A 13-13456-3
+always \u834B 156-2
+always \u834C 1236-5
+always \u834D 245-246-2
+always \u834E 12-156-2
+always \u834F 1245-136-4
+always \u8350 13-2345-5
+always \u8351 16-2
+always \u8352 1235-456-3
+always \u8353 1234-13456-2
+always \u8354 14-16-5
+always \u8355 13-1456-3
+always \u8356 14-146-4
+always \u8357 24-34-5
+always \u8358 1-456-3
+always \u8359 145-345-2
+always \u835A 13-23456-2
+always \u835B 1245-146-2
+always \u835C 135-16-5
+always \u835D 125-2346-2
+always \u835E 245-246-2
+always \u835F 1235-1246-5
+always \u8360 13-16-5
+always \u8361 145-1346-5
+always \u8362 1256-2
+always \u8363 1245-12346-2
+always \u8364 1235-123456-3
+always \u8365 13456-2
+always \u8366 14-25-5
+always \u8367 13456-2
+always \u8368 245-2345-2
+always \u8369 13-1456-5
+always \u836A 15-123456-3
+always \u836B 1456-5
+always \u836C 134-2456-4
+always \u836D 1235-12346-2
+always \u836E 1-12356-5
+always \u836F 246-5
+always \u8370 145-34-5
+always \u8371 1246-4
+always \u8372 12-34-5
+always \u8373 145-12356-5
+always \u8374 12345-34-3
+always \u8375 1245-136-4
+always \u8376 1456-2
+always \u8377 1235-2346-2
+always \u8378 135-16-2
+always \u8379 135-34-5
+always \u837A 256-4
+always \u837B 145-16-2
+always \u837C 124-34-2
+always \u837D 15-1246-3
+always \u837E 15-1246-3
+always \u837F 12-1356-2
+always \u8380 12-136-2
+always \u8381 34-2
+always \u8382 135-346-2
+always \u8383 15-16-3
+always \u8384 13-1356-4
+always \u8385 14-16-5
+always \u8386 1234-34-2
+always \u8387 1-34-5
+always \u8388 134-126-5
+always \u8389 14-16-5
+always \u838A 1-456-3
+always \u838B 125-25-2
+always \u838C 145-25-2
+always \u838D 245-234-2
+always \u838E 24-345-3
+always \u838F 15-25-3
+always \u8390 12-136-2
+always \u8391 12345-1356-3
+always \u8392 13-1256-4
+always \u8393 134-356-2
+always \u8394 134-1356-2
+always \u8395 15-13456-5
+always \u8396 13-13456-3
+always \u8397 12-2346-3
+always \u8398 24-136-3
+always \u8399 13-256-3
+always \u839A 2345-2
+always \u839B 124-13456-2
+always \u839C 145-246-5
+always \u839D 245-25-5
+always \u839E 12456-4
+always \u839F 1235-1236-5
+always \u83A0 234-4
+always \u83A1 245-25-5
+always \u83A2 13-23456-2
+always \u83A3 456-2
+always \u83A4 15-34-5
+always \u83A5 1345-234-4
+always \u83A6 24-146-3
+always \u83A7 15-2345-5
+always \u83A8 14-1346-5
+always \u83A9 1234-246-4
+always \u83AA 2346-2
+always \u83AB 134-126-5
+always \u83AC 123456-5
+always \u83AD 13-346-2
+always \u83AE 1345-1236-2
+always \u83AF 134-34-5
+always \u83B0 123-1236-4
+always \u83B1 14-2456-2
+always \u83B2 14-2345-2
+always \u83B3 24-156-2
+always \u83B4 25-3
+always \u83B5 124-34-5
+always \u83B6 15-2345-3
+always \u83B7 1235-25-5
+always \u83B8 234-2
+always \u83B9 13456-2
+always \u83BA 13456-3
+always \u83BB 12356-3
+always \u83BC 12-123456-2
+always \u83BD 134-1346-4
+always \u83BE 134-1346-4
+always \u83BF 245-156-5
+always \u83C0 12456-4
+always \u83C1 13-13456-3
+always \u83C2 145-16-3
+always \u83C3 245-1256-2
+always \u83C4 145-12346-3
+always \u83C5 13-2345-3
+always \u83C6 125-12356-3
+always \u83C7 13-34-3
+always \u83C8 14-345-3
+always \u83C9 14-34-5
+always \u83CA 13-1256-2
+always \u83CB 1246-5
+always \u83CC 13-256-5
+always \u83CD 1345-346-5
+always \u83CE 123-123456-3
+always \u83CF 1235-2346-2
+always \u83D0 1234-34-2
+always \u83D1 125-156-3
+always \u83D2 13-146-4
+always \u83D3 13-25-4
+always \u83D4 12345-34-2
+always \u83D5 14-123456-2
+always \u83D6 12-1346-3
+always \u83D7 12-12356-2
+always \u83D8 15-12346-3
+always \u83D9 12-1246-2
+always \u83DA 1-1236-5
+always \u83DB 134-136-2
+always \u83DC 245-2456-5
+always \u83DD 135-345-2
+always \u83DE 14-16-2
+always \u83DF 124-34-5
+always \u83E0 135-126-3
+always \u83E1 1235-1236-5
+always \u83E2 135-146-5
+always \u83E3 245-1456-5
+always \u83E4 13-45-4
+always \u83E5 15-16-3
+always \u83E6 245-1456-2
+always \u83E7 145-16-4
+always \u83E8 13-346-3
+always \u83E9 1234-34-2
+always \u83EA 145-1346-5
+always \u83EB 13-1456-4
+always \u83EC 1-146-4
+always \u83ED 124-2456-2
+always \u83EE 13-1356-3
+always \u83EF 1235-35-2
+always \u83F0 13-34-3
+always \u83F1 14-13456-2
+always \u83F2 12345-356-3
+always \u83F3 13-1456-3
+always \u83F4 1236-3
+always \u83F5 456-4
+always \u83F6 135-1356-4
+always \u83F7 1-12356-4
+always \u83F8 2345-3
+always \u83F9 13-1256-3
+always \u83FA 13-2345-3
+always \u83FB 14-1456-4
+always \u83FC 124-1236-4
+always \u83FD 24-34-3
+always \u83FE 124-2345-2
+always \u83FF 145-146-5
+always \u8400 1235-34-4
+always \u8401 245-16-2
+always \u8402 1235-2346-2
+always \u8403 245-1246-5
+always \u8404 124-146-2
+always \u8405 12-123456-3
+always \u8406 135-16-5
+always \u8407 12-1346-2
+always \u8408 1235-12456-2
+always \u8409 12345-356-5
+always \u840A 14-2456-2
+always \u840B 245-16-3
+always \u840C 134-1356-2
+always \u840D 1234-13456-2
+always \u840E 1246-3
+always \u840F 145-1236-5
+always \u8410 24-345-5
+always \u8411 1235-12456-2
+always \u8412 2345-4
+always \u8413 16-2
+always \u8414 124-246-2
+always \u8415 245-16-2
+always \u8416 12456-4
+always \u8417 245-2346-5
+always \u8418 1345-2456-5
+always \u8419 1-136-4
+always \u841A 124-25-5
+always \u841B 13-234-3
+always \u841C 124-346-3
+always \u841D 14-25-2
+always \u841E 145-16-5
+always \u841F 16-5
+always \u8420 134-1356-2
+always \u8421 135-126-4
+always \u8422 1234-146-5
+always \u8423 145-13456-5
+always \u8424 13456-2
+always \u8425 13456-2
+always \u8426 13456-2
+always \u8427 15-246-3
+always \u8428 15-345-5
+always \u8429 245-234-3
+always \u842A 123-2346-3
+always \u842B 15-46-5
+always \u842C 12456-5
+always \u842D 1256-4
+always \u842E 1256-2
+always \u842F 12345-34-5
+always \u8430 14-2345-5
+always \u8431 15-45-3
+always \u8432 15-45-3
+always \u8433 1345-1236-2
+always \u8434 125-2346-2
+always \u8435 25-3
+always \u8436 12-123456-3
+always \u8437 15-246-3
+always \u8438 1256-2
+always \u8439 135-2345-3
+always \u843A 134-146-5
+always \u843B 1236-3
+always \u843C 2346-5
+always \u843D 14-25-5
+always \u843E 13456-2
+always \u843F 123-25-5
+always \u8440 13-35-3
+always \u8441 13-46-3
+always \u8442 134-2345-4
+always \u8443 125-25-5
+always \u8444 125-25-5
+always \u8445 13-1256-3
+always \u8446 135-146-4
+always \u8447 1245-12356-2
+always \u8448 15-16-4
+always \u8449 346-5
+always \u844A 1236-3
+always \u844B 245-1256-2
+always \u844C 13-2345-3
+always \u844D 12345-34-2
+always \u844E 14-1256-5
+always \u844F 13-13456-3
+always \u8450 1234-136-2
+always \u8451 12345-1356-3
+always \u8452 1235-12346-2
+always \u8453 1235-12346-2
+always \u8454 1235-12356-2
+always \u8455 2345-2
+always \u8456 124-34-2
+always \u8457 1-2346-1
+always \u8458 125-156-3
+always \u8459 15-46-3
+always \u845A 24-136-5
+always \u845B 13-2346-4
+always \u845C 245-23456-3
+always \u845D 245-13456-2
+always \u845E 134-16-4
+always \u845F 1235-456-2
+always \u8460 24-136-3
+always \u8461 1234-34-2
+always \u8462 13-2456-5
+always \u8463 145-12346-4
+always \u8464 1-12356-5
+always \u8465 245-2345-2
+always \u8466 1246-4
+always \u8467 135-126-2
+always \u8468 1246-3
+always \u8469 1234-345-3
+always \u846A 13-16-5
+always \u846B 1235-34-2
+always \u846C 125-1346-5
+always \u846D 13-23456-3
+always \u846E 145-12456-5
+always \u846F 246-5
+always \u8470 15-1246-3
+always \u8471 245-12346-3
+always \u8472 245-45-2
+always \u8473 1246-3
+always \u8474 1-136-3
+always \u8475 123-1246-2
+always \u8476 124-13456-2
+always \u8477 1235-123456-3
+always \u8478 15-16-4
+always \u8479 24-156-3
+always \u847A 245-16-5
+always \u847B 14-1236-2
+always \u847C 125-12346-3
+always \u847D 246-3
+always \u847E 45-3
+always \u847F 134-356-2
+always \u8480 256-3
+always \u8481 24-34-5
+always \u8482 145-16-5
+always \u8483 1-12456-5
+always \u8484 13-12456-3
+always \u8485 1245-1236-4
+always \u8486 15-236-3
+always \u8487 12-1236-4
+always \u8488 123-2456-4
+always \u8489 123-1246-5
+always \u848A 1235-35-3
+always \u848B 13-46-4
+always \u848C 14-12356-2
+always \u848D 1246-4
+always \u848E 124-2456-5
+always \u848F 234-4
+always \u8490 15-12356-3
+always \u8491 1456-3
+always \u8492 24-156-3
+always \u8493 12-123456-2
+always \u8494 24-156-2
+always \u8495 256-3
+always \u8496 1-136-3
+always \u8497 14-1346-5
+always \u8498 1245-34-2
+always \u8499 134-1356-2
+always \u849A 1235-2346-2
+always \u849B 245-236-3
+always \u849C 15-12456-5
+always \u849D 45-2
+always \u849E 14-16-5
+always \u849F 13-1256-4
+always \u84A0 15-16-2
+always \u84A1 135-1346-5
+always \u84A2 12-34-2
+always \u84A3 15-1256-2
+always \u84A4 124-34-2
+always \u84A5 14-234-2
+always \u84A6 25-5
+always \u84A7 1-136-3
+always \u84A8 245-2345-5
+always \u84A9 125-34-3
+always \u84AA 1234-126-5
+always \u84AB 245-25-3
+always \u84AC 45-3
+always \u84AD 12-34-2
+always \u84AE 1256-5
+always \u84AF 123-2356-4
+always \u84B0 1234-1236-2
+always \u84B1 1234-34-2
+always \u84B2 1234-34-2
+always \u84B3 1345-345-5
+always \u84B4 24-25-5
+always \u84B5 15-16-3
+always \u84B6 12345-136-2
+always \u84B7 256-2
+always \u84B8 1-1356-3
+always \u84B9 13-2345-3
+always \u84BA 13-16-2
+always \u84BB 1245-25-5
+always \u84BC 245-1346-3
+always \u84BD 136-3
+always \u84BE 134-16-2
+always \u84BF 1235-146-3
+always \u84C0 15-123456-3
+always \u84C1 1-136-3
+always \u84C2 134-13456-2
+always \u84C3 1235-25-5
+always \u84C4 15-1256-5
+always \u84C5 14-234-2
+always \u84C6 15-16-2
+always \u84C7 13-34-4
+always \u84C8 14-1346-2
+always \u84C9 1245-12346-2
+always \u84CA 12346-4
+always \u84CB 13-2456-5
+always \u84CC 245-25-5
+always \u84CD 24-156-3
+always \u84CE 124-1346-2
+always \u84CF 14-25-4
+always \u84D0 1245-34-5
+always \u84D1 15-25-3
+always \u84D2 15-2345-3
+always \u84D3 135-356-5
+always \u84D4 246-4
+always \u84D5 13-1246-5
+always \u84D6 135-16-5
+always \u84D7 125-12346-4
+always \u84D8 13-123456-4
+always \u84D9 125-25-5
+always \u84DA 15-234-3
+always \u84DB 245-2346-5
+always \u84DC 1234-356-5
+always \u84DD 14-1236-2
+always \u84DE 145-1236-5
+always \u84DF 13-16-5
+always \u84E0 14-16-2
+always \u84E1 24-136-3
+always \u84E2 14-1346-2
+always \u84E3 1256-5
+always \u84E4 14-13456-2
+always \u84E5 13456-2
+always \u84E6 134-126-5
+always \u84E7 145-246-5
+always \u84E8 124-246-3
+always \u84E9 134-146-5
+always \u84EA 124-12346-3
+always \u84EB 1-34-2
+always \u84EC 1234-1356-2
+always \u84ED 1236-3
+always \u84EE 14-2345-2
+always \u84EF 245-12346-3
+always \u84F0 15-16-4
+always \u84F1 1234-13456-2
+always \u84F2 12356-3
+always \u84F3 13-1456-5
+always \u84F4 12-123456-2
+always \u84F5 13-346-2
+always \u84F6 1246-2
+always \u84F7 124-1246-3
+always \u84F8 245-146-2
+always \u84F9 1256-5
+always \u84FA 16-5
+always \u84FB 13-16-2
+always \u84FC 14-246-4
+always \u84FD 135-16-5
+always \u84FE 14-34-4
+always \u84FF 15-1256-4
+always \u8500 135-34-5
+always \u8501 1-1346-3
+always \u8502 14-356-2
+always \u8503 13-46-5
+always \u8504 134-1236-5
+always \u8505 2345-2
+always \u8506 14-13456-2
+always \u8507 13-16-5
+always \u8508 135-246-3
+always \u8509 13-123456-4
+always \u850A 1235-1236-4
+always \u850B 145-16-2
+always \u850C 15-34-5
+always \u850D 14-34-5
+always \u850E 24-2346-5
+always \u850F 24-1346-3
+always \u8510 145-16-2
+always \u8511 134-346-5
+always \u8512 15-256-3
+always \u8513 134-1236-5
+always \u8514 135-126-1
+always \u8515 145-16-5
+always \u8516 245-25-2
+always \u8517 1-2346-5
+always \u8518 15-136-3
+always \u8519 15-45-5
+always \u851A 1246-5
+always \u851B 1235-34-2
+always \u851C 146-2
+always \u851D 134-16-4
+always \u851E 14-12356-2
+always \u851F 245-34-5
+always \u8520 1-12346-3
+always \u8521 245-2456-5
+always \u8522 1234-126-2
+always \u8523 13-46-4
+always \u8524 134-16-5
+always \u8525 245-12346-3
+always \u8526 1345-246-4
+always \u8527 1235-1246-5
+always \u8528 13-256-5
+always \u8529 1456-2
+always \u852A 13-2345-3
+always \u852B 2345-3
+always \u852C 24-34-3
+always \u852D 1456-5
+always \u852E 13-25-2
+always \u852F 12-136-2
+always \u8530 1235-34-5
+always \u8531 24-345-3
+always \u8532 123-12356-5
+always \u8533 245-2345-5
+always \u8534 134-345-2
+always \u8535 245-1346-2
+always \u8536 125-2346-2
+always \u8537 245-46-2
+always \u8538 145-12356-3
+always \u8539 14-2345-4
+always \u853A 14-1456-5
+always \u853B 123-12356-5
+always \u853C 2456-4
+always \u853D 135-16-5
+always \u853E 14-16-2
+always \u853F 1246-2
+always \u8540 13-16-2
+always \u8541 245-2345-2
+always \u8542 24-1356-5
+always \u8543 12345-1236-3
+always \u8544 134-1356-2
+always \u8545 12356-4
+always \u8546 12-1236-4
+always \u8547 145-2345-4
+always \u8548 15-256-5
+always \u8549 13-246-3
+always \u854A 1245-1246-4
+always \u854B 1245-1246-4
+always \u854C 14-356-4
+always \u854D 1256-2
+always \u854E 245-246-2
+always \u854F 12-34-2
+always \u8550 1235-35-2
+always \u8551 13-2345-3
+always \u8552 134-2456-4
+always \u8553 256-2
+always \u8554 135-146-3
+always \u8555 234-2
+always \u8556 245-1256-2
+always \u8557 14-34-5
+always \u8558 1245-146-2
+always \u8559 1235-1246-5
+always \u855A 2346-5
+always \u855B 124-16-2
+always \u855C 12345-356-4
+always \u855D 13-236-2
+always \u855E 125-1246-5
+always \u855F 12345-345-5
+always \u8560 1345-34-2
+always \u8561 12345-136-2
+always \u8562 123-1246-5
+always \u8563 24-123456-5
+always \u8564 1245-1246-2
+always \u8565 23456-4
+always \u8566 15-1256-3
+always \u8567 12345-34-5
+always \u8568 13-236-2
+always \u8569 145-1346-5
+always \u856A 34-2
+always \u856B 145-12346-4
+always \u856C 15-156-3
+always \u856D 15-246-3
+always \u856E 15-16-5
+always \u856F 15-345-5
+always \u8570 256-5
+always \u8571 24-146-3
+always \u8572 245-16-2
+always \u8573 13-2345-3
+always \u8574 256-5
+always \u8575 15-123456-3
+always \u8576 14-13456-2
+always \u8577 1256-5
+always \u8578 15-23456-2
+always \u8579 2346-5
+always \u857A 13-16-2
+always \u857B 1235-12346-2
+always \u857C 15-156-5
+always \u857D 1345-12346-2
+always \u857E 14-356-4
+always \u857F 15-45-3
+always \u8580 256-5
+always \u8581 1256-5
+always \u8582 15-16-2
+always \u8583 1235-146-5
+always \u8584 135-126-2
+always \u8585 1235-146-3
+always \u8586 2456-5
+always \u8587 1246-3
+always \u8588 1235-1246-5
+always \u8589 1235-1246-5
+always \u858A 13-16-5
+always \u858B 245-156-2
+always \u858C 15-46-3
+always \u858D 12456-5
+always \u858E 134-346-5
+always \u858F 16-5
+always \u8590 14-1356-2
+always \u8591 13-46-3
+always \u8592 245-1236-5
+always \u8593 24-136-3
+always \u8594 245-46-2
+always \u8595 14-2345-2
+always \u8596 123-2346-3
+always \u8597 45-2
+always \u8598 145-345-2
+always \u8599 124-16-5
+always \u859A 124-1346-2
+always \u859B 15-236-3
+always \u859C 135-16-5
+always \u859D 1-1236-3
+always \u859E 15-123456-3
+always \u859F 15-2345-3
+always \u85A0 12345-1236-2
+always \u85A1 145-13456-4
+always \u85A2 15-346-5
+always \u85A3 13-34-4
+always \u85A4 15-346-5
+always \u85A5 24-34-4
+always \u85A6 13-2345-5
+always \u85A7 1235-146-3
+always \u85A8 1235-12346-3
+always \u85A9 15-345-5
+always \u85AA 15-1456-3
+always \u85AB 15-256-3
+always \u85AC 246-5
+always \u85AD 2356-5
+always \u85AE 15-12356-4
+always \u85AF 24-34-4
+always \u85B0 15-256-3
+always \u85B1 145-1246-5
+always \u85B2 1234-1456-2
+always \u85B3 1246-4
+always \u85B4 1345-13456-2
+always \u85B5 12-12356-2
+always \u85B6 134-2456-2
+always \u85B7 1245-34-2
+always \u85B8 1234-246-2
+always \u85B9 124-2456-2
+always \u85BA 13-16-5
+always \u85BB 125-146-4
+always \u85BC 12-136-2
+always \u85BD 1-136-3
+always \u85BE 156-4
+always \u85BF 1345-16-4
+always \u85C0 13456-2
+always \u85C1 13-146-4
+always \u85C2 245-12346-2
+always \u85C3 15-246-3
+always \u85C4 245-16-2
+always \u85C5 12345-345-2
+always \u85C6 13-2345-4
+always \u85C7 15-1256-5
+always \u85C8 123-1246-3
+always \u85C9 13-346-5
+always \u85CA 135-2345-4
+always \u85CB 145-246-5
+always \u85CC 134-16-5
+always \u85CD 14-1236-2
+always \u85CE 13-1456-5
+always \u85CF 245-1346-2
+always \u85D0 134-246-4
+always \u85D1 245-235-2
+always \u85D2 245-346-5
+always \u85D3 15-2345-4
+always \u85D4 14-246-2
+always \u85D5 12356-4
+always \u85D6 15-2345-2
+always \u85D7 15-34-5
+always \u85D8 14-1256-2
+always \u85D9 16-5
+always \u85DA 15-1256-5
+always \u85DB 15-346-4
+always \u85DC 14-16-2
+always \u85DD 16-5
+always \u85DE 14-345-4
+always \u85DF 14-356-4
+always \u85E0 15-246-5
+always \u85E1 145-16-2
+always \u85E2 1-156-4
+always \u85E3 135-356-3
+always \u85E4 124-1356-2
+always \u85E5 246-5
+always \u85E6 134-126-5
+always \u85E7 1235-12456-4
+always \u85E8 135-246-3
+always \u85E9 12345-1236-2
+always \u85EA 15-12356-4
+always \u85EB 124-1236-2
+always \u85EC 124-1246-3
+always \u85ED 245-235-2
+always \u85EE 245-246-2
+always \u85EF 1246-5
+always \u85F0 14-234-2
+always \u85F1 1235-1246-5
+always \u85F2 12356-3
+always \u85F3 13-146-4
+always \u85F4 256-5
+always \u85F5 135-146-4
+always \u85F6 14-16-5
+always \u85F7 24-34-4
+always \u85F8 12-34-2
+always \u85F9 2456-4
+always \u85FA 14-1456-5
+always \u85FB 125-146-4
+always \u85FC 15-45-3
+always \u85FD 12-136-5
+always \u85FE 14-2456-5
+always \u85FF 1235-25-5
+always \u8600 124-25-5
+always \u8601 34-5
+always \u8602 1245-1246-4
+always \u8603 1245-1246-4
+always \u8604 245-16-2
+always \u8605 1235-1356-2
+always \u8606 14-34-2
+always \u8607 15-34-3
+always \u8608 124-1246-2
+always \u8609 134-1346-2
+always \u860A 256-5
+always \u860B 1234-13456-2
+always \u860C 1256-4
+always \u860D 15-256-3
+always \u860E 13-16-5
+always \u860F 13-235-3
+always \u8610 15-45-3
+always \u8611 134-126-2
+always \u8612 245-234-3
+always \u8613 15-34-3
+always \u8614 13-235-3
+always \u8615 12345-1356-5
+always \u8616 1345-346-5
+always \u8617 135-126-5
+always \u8618 1245-1346-2
+always \u8619 16-5
+always \u861A 15-2345-4
+always \u861B 1256-5
+always \u861C 13-1256-2
+always \u861D 14-2345-5
+always \u861E 14-2345-2
+always \u861F 1456-4
+always \u8620 245-46-2
+always \u8621 13456-3
+always \u8622 14-12346-2
+always \u8623 124-12356-4
+always \u8624 1235-35-3
+always \u8625 236-5
+always \u8626 14-13456-2
+always \u8627 245-1256-2
+always \u8628 246-2
+always \u8629 12345-1236-2
+always \u862A 134-16-2
+always \u862B 14-1236-2
+always \u862C 123-1246-3
+always \u862D 14-1236-2
+always \u862E 13-16-5
+always \u862F 145-1346-5
+always \u8630 12456-5
+always \u8631 14-356-5
+always \u8632 14-356-2
+always \u8633 1235-1246-3
+always \u8634 12345-1356-3
+always \u8635 1-156-2
+always \u8636 1246-5
+always \u8637 123-1246-2
+always \u8638 1-1236-5
+always \u8639 1235-2356-2
+always \u863A 14-16-2
+always \u863B 13-16-5
+always \u863C 134-16-2
+always \u863D 14-356-4
+always \u863E 1235-2356-5
+always \u863F 14-25-2
+always \u8640 13-16-3
+always \u8641 123-1246-2
+always \u8642 14-34-5
+always \u8643 13-2345-3
+always \u8644 15-345-5
+always \u8645 124-1356-2
+always \u8646 14-356-2
+always \u8647 245-45-4
+always \u8648 15-246-3
+always \u8649 16-5
+always \u864A 14-12456-2
+always \u864B 134-136-2
+always \u864C 135-346-3
+always \u864D 1235-34-3
+always \u864E 1235-34-4
+always \u864F 14-34-4
+always \u8650 1345-236-5
+always \u8651 14-1256-5
+always \u8652 15-156-3
+always \u8653 15-246-3
+always \u8654 245-2345-2
+always \u8655 12-34-5
+always \u8656 1235-34-3
+always \u8657 15-1256-3
+always \u8658 245-25-2
+always \u8659 12345-34-2
+always \u865A 15-1256-3
+always \u865B 15-1256-3
+always \u865C 14-34-4
+always \u865D 1235-34-4
+always \u865E 1256-2
+always \u865F 1235-146-5
+always \u8660 13-246-4
+always \u8661 13-1256-5
+always \u8662 13-25-2
+always \u8663 135-146-5
+always \u8664 2345-2
+always \u8665 1-1236-5
+always \u8666 1-1236-5
+always \u8667 123-1246-3
+always \u8668 135-1236-3
+always \u8669 15-16-5
+always \u866A 24-34-2
+always \u866B 12-12346-2
+always \u866C 245-234-2
+always \u866D 145-246-3
+always \u866E 13-16-4
+always \u866F 245-234-2
+always \u8670 145-13456-3
+always \u8671 24-156-3
+always \u8672 15-23456-3
+always \u8673 145-16-5
+always \u8674 1-2346-2
+always \u8675 24-2346-2
+always \u8676 1256-3
+always \u8677 13-1236-3
+always \u8678 125-156-4
+always \u8679 1235-12346-2
+always \u867A 1235-1246-4
+always \u867B 134-1356-2
+always \u867C 13-2346-5
+always \u867D 15-1246-3
+always \u867E 15-23456-3
+always \u867F 12-2456-5
+always \u8680 24-156-2
+always \u8681 16-4
+always \u8682 134-345-4
+always \u8683 15-46-5
+always \u8684 12345-1346-3
+always \u8685 2346-5
+always \u8686 135-345-3
+always \u8687 12-156-4
+always \u8688 245-2345-3
+always \u8689 123456-2
+always \u868A 123456-2
+always \u868B 1245-1246-5
+always \u868C 135-1346-5
+always \u868D 1234-16-2
+always \u868E 236-5
+always \u868F 236-5
+always \u8690 13-256-3
+always \u8691 245-16-2
+always \u8692 124-12346-2
+always \u8693 1456-4
+always \u8694 245-16-2
+always \u8695 124-2345-4
+always \u8696 45-2
+always \u8697 13-236-2
+always \u8698 1235-1246-2
+always \u8699 245-1456-2
+always \u869A 245-16-2
+always \u869B 1-12346-5
+always \u869C 23456-2
+always \u869D 1235-146-2
+always \u869E 134-34-5
+always \u869F 456-2
+always \u86A0 12345-136-2
+always \u86A1 12345-136-2
+always \u86A2 1235-1346-2
+always \u86A3 13-12346-3
+always \u86A4 125-146-4
+always \u86A5 12345-34-4
+always \u86A6 1245-1236-2
+always \u86A7 13-346-5
+always \u86A8 12345-34-2
+always \u86A9 12-156-3
+always \u86AA 145-12356-4
+always \u86AB 1234-246-2
+always \u86AC 15-45-4
+always \u86AD 1345-16-2
+always \u86AE 124-2346-5
+always \u86AF 245-234-3
+always \u86B0 234-2
+always \u86B1 1-345-5
+always \u86B2 1234-13456-2
+always \u86B3 12-156-2
+always \u86B4 234-5
+always \u86B5 2346-2
+always \u86B6 1235-1236-3
+always \u86B7 13-1256-5
+always \u86B8 14-16-5
+always \u86B9 12345-34-5
+always \u86BA 1245-1236-2
+always \u86BB 1-345-2
+always \u86BC 13-12356-4
+always \u86BD 1234-16-2
+always \u86BE 135-126-4
+always \u86BF 15-2345-2
+always \u86C0 1-34-5
+always \u86C1 145-246-3
+always \u86C2 135-346-2
+always \u86C3 135-13456-4
+always \u86C4 13-34-3
+always \u86C5 1-1236-3
+always \u86C6 245-1256-3
+always \u86C7 24-2346-2
+always \u86C8 124-346-4
+always \u86C9 14-13456-2
+always \u86CA 13-34-4
+always \u86CB 145-1236-5
+always \u86CC 13-34-4
+always \u86CD 13456-2
+always \u86CE 14-16-5
+always \u86CF 12-1356-3
+always \u86D0 245-1256-3
+always \u86D1 134-12356-2
+always \u86D2 13-2346-2
+always \u86D3 245-156-5
+always \u86D4 1235-1246-2
+always \u86D5 1235-1246-2
+always \u86D6 134-1346-2
+always \u86D7 12345-34-5
+always \u86D8 46-2
+always \u86D9 35-3
+always \u86DA 14-346-5
+always \u86DB 1-34-3
+always \u86DC 16-3
+always \u86DD 15-2345-2
+always \u86DE 123-25-5
+always \u86DF 13-246-3
+always \u86E0 14-16-5
+always \u86E1 16-5
+always \u86E2 1234-13456-2
+always \u86E3 13-346-2
+always \u86E4 13-2346-2
+always \u86E5 24-2346-2
+always \u86E6 124-16-2
+always \u86E7 456-4
+always \u86E8 134-126-5
+always \u86E9 245-235-2
+always \u86EA 245-346-5
+always \u86EB 13-1246-4
+always \u86EC 13-12346-4
+always \u86ED 1-156-5
+always \u86EE 134-1236-2
+always \u86EF 14-146-4
+always \u86F0 1-156-2
+always \u86F1 13-23456-2
+always \u86F2 1345-146-2
+always \u86F3 15-156-3
+always \u86F4 245-16-2
+always \u86F5 15-13456-3
+always \u86F6 14-346-5
+always \u86F7 245-234-2
+always \u86F8 15-246-3
+always \u86F9 235-4
+always \u86FA 13-23456-2
+always \u86FB 124-1246-5
+always \u86FC 12-2346-3
+always \u86FD 135-2456-5
+always \u86FE 2346-2
+always \u86FF 1235-1236-5
+always \u8700 24-34-4
+always \u8701 15-45-2
+always \u8702 12345-1356-3
+always \u8703 24-136-5
+always \u8704 1-136-5
+always \u8705 12345-34-4
+always \u8706 15-2345-4
+always \u8707 1-2346-2
+always \u8708 34-2
+always \u8709 12345-34-2
+always \u870A 14-16-2
+always \u870B 14-1346-2
+always \u870C 135-16-5
+always \u870D 12-34-2
+always \u870E 45-3
+always \u870F 234-4
+always \u8710 13-346-2
+always \u8711 145-1236-5
+always \u8712 2345-2
+always \u8713 124-13456-2
+always \u8714 145-2345-5
+always \u8715 124-1246-5
+always \u8716 1235-1246-2
+always \u8717 13-35-3
+always \u8718 1-156-3
+always \u8719 15-12346-3
+always \u871A 12345-356-4
+always \u871B 13-1256-3
+always \u871C 134-16-5
+always \u871D 245-16-2
+always \u871E 245-16-2
+always \u871F 1256-5
+always \u8720 13-256-5
+always \u8721 1-345-5
+always \u8722 134-1356-4
+always \u8723 245-46-3
+always \u8724 15-156-3
+always \u8725 15-16-3
+always \u8726 14-123456-2
+always \u8727 14-16-5
+always \u8728 145-346-2
+always \u8729 124-246-2
+always \u872A 124-146-2
+always \u872B 123-123456-3
+always \u872C 1235-1236-2
+always \u872D 1235-1236-5
+always \u872E 1256-5
+always \u872F 135-1346-5
+always \u8730 12345-356-2
+always \u8731 1234-16-2
+always \u8732 1246-3
+always \u8733 145-123456-3
+always \u8734 16-5
+always \u8735 45-3
+always \u8736 15-34-5
+always \u8737 245-45-2
+always \u8738 245-2345-4
+always \u8739 1245-1246-5
+always \u873A 1345-16-2
+always \u873B 245-13456-3
+always \u873C 1246-5
+always \u873D 14-46-4
+always \u873E 13-25-4
+always \u873F 12456-3
+always \u8740 145-12346-3
+always \u8741 2346-5
+always \u8742 135-1236-4
+always \u8743 145-16-5
+always \u8744 456-4
+always \u8745 245-1236-2
+always \u8746 134-16-4
+always \u8747 13456-2
+always \u8748 13-25-3
+always \u8749 12-1236-2
+always \u874A 145-13456-5
+always \u874B 14-345-5
+always \u874C 123-2346-3
+always \u874D 13-16-2
+always \u874E 1235-2346-2
+always \u874F 124-13456-2
+always \u8750 134-356-5
+always \u8751 15-1256-3
+always \u8752 134-2345-2
+always \u8753 1256-2
+always \u8754 13-346-3
+always \u8755 24-156-2
+always \u8756 15-45-3
+always \u8757 1235-456-2
+always \u8758 2345-4
+always \u8759 135-2345-3
+always \u875A 1245-12356-2
+always \u875B 1246-3
+always \u875C 12345-34-5
+always \u875D 45-2
+always \u875E 134-356-5
+always \u875F 1246-5
+always \u8760 12345-34-2
+always \u8761 1245-12456-4
+always \u8762 15-346-2
+always \u8763 234-2
+always \u8764 245-234-2
+always \u8765 134-146-2
+always \u8766 15-23456-3
+always \u8767 13456-3
+always \u8768 24-156-3
+always \u8769 12-12346-2
+always \u876A 124-1346-3
+always \u876B 1-34-3
+always \u876C 125-12346-3
+always \u876D 124-16-2
+always \u876E 12345-34-5
+always \u876F 45-2
+always \u8770 123-1246-3
+always \u8771 134-1356-2
+always \u8772 14-345-5
+always \u8773 145-34-2
+always \u8774 1235-34-2
+always \u8775 245-234-3
+always \u8776 145-346-2
+always \u8777 14-16-5
+always \u8778 13-35-3
+always \u8779 146-4
+always \u877A 13-1256-4
+always \u877B 1345-1236-4
+always \u877C 14-12356-2
+always \u877D 12-123456-3
+always \u877E 1245-12346-2
+always \u877F 13456-2
+always \u8780 13-46-3
+always \u8781 124-1246-5
+always \u8782 14-1346-2
+always \u8783 1234-1346-2
+always \u8784 15-156-3
+always \u8785 15-16-3
+always \u8786 245-156-5
+always \u8787 15-16-3
+always \u8788 45-2
+always \u8789 12346-3
+always \u878A 14-2345-2
+always \u878B 15-12356-3
+always \u878C 135-1236-3
+always \u878D 1245-12346-2
+always \u878E 1245-12346-2
+always \u878F 13-16-2
+always \u8790 34-3
+always \u8791 15-234-5
+always \u8792 1235-1236-5
+always \u8793 245-1456-2
+always \u8794 16-2
+always \u8795 135-16-3
+always \u8796 1235-35-2
+always \u8797 124-1346-2
+always \u8798 16-4
+always \u8799 145-34-5
+always \u879A 1345-2456-5
+always \u879B 1235-2346-2
+always \u879C 1235-34-2
+always \u879D 1235-1246-5
+always \u879E 134-345-4
+always \u879F 134-13456-2
+always \u87A0 16-5
+always \u87A1 123456-2
+always \u87A2 13456-2
+always \u87A3 124-1356-2
+always \u87A4 1-12346-3
+always \u87A5 245-1346-3
+always \u87A6 15-146-3
+always \u87A7 245-16-4
+always \u87A8 134-1236-4
+always \u87A9 124-246-2
+always \u87AA 24-1346-3
+always \u87AB 24-156-5
+always \u87AC 245-146-2
+always \u87AD 12-156-3
+always \u87AE 145-16-5
+always \u87AF 146-2
+always \u87B0 14-34-5
+always \u87B1 1246-5
+always \u87B2 1-156-5
+always \u87B3 124-1346-2
+always \u87B4 12-136-2
+always \u87B5 1234-246-3
+always \u87B6 245-1256-2
+always \u87B7 1234-16-2
+always \u87B8 1256-2
+always \u87B9 13-2345-5
+always \u87BA 14-25-2
+always \u87BB 14-12356-2
+always \u87BC 245-1456-4
+always \u87BD 1-12346-3
+always \u87BE 1456-4
+always \u87BF 13-46-3
+always \u87C0 24-2356-5
+always \u87C1 123456-2
+always \u87C2 13-246-3
+always \u87C3 12456-5
+always \u87C4 1-2346-2
+always \u87C5 1-2346-5
+always \u87C6 134-345-1
+always \u87C7 134-345-1
+always \u87C8 13-25-3
+always \u87C9 14-234-2
+always \u87CA 134-146-2
+always \u87CB 15-16-3
+always \u87CC 245-12346-3
+always \u87CD 14-16-2
+always \u87CE 134-1236-2
+always \u87CF 15-246-3
+always \u87D0 12-1346-2
+always \u87D1 1-1346-3
+always \u87D2 134-1346-4
+always \u87D3 15-46-5
+always \u87D4 134-126-5
+always \u87D5 125-1246-3
+always \u87D6 15-156-3
+always \u87D7 245-234-3
+always \u87D8 124-2346-5
+always \u87D9 1-156-2
+always \u87DA 1234-1356-2
+always \u87DB 1234-1356-2
+always \u87DC 13-246-4
+always \u87DD 245-1256-2
+always \u87DE 135-346-2
+always \u87DF 14-246-2
+always \u87E0 1234-1236-2
+always \u87E1 13-1246-4
+always \u87E2 15-16-4
+always \u87E3 13-16-4
+always \u87E4 1-12456-3
+always \u87E5 1235-456-2
+always \u87E6 12345-356-5
+always \u87E7 14-146-2
+always \u87E8 13-236-2
+always \u87E9 13-236-2
+always \u87EA 1235-1246-5
+always \u87EB 1456-2
+always \u87EC 12-1236-2
+always \u87ED 13-246-3
+always \u87EE 24-1236-5
+always \u87EF 1345-146-2
+always \u87F0 15-246-3
+always \u87F1 34-2
+always \u87F2 12-12346-2
+always \u87F3 15-256-2
+always \u87F4 15-156-3
+always \u87F5 12-34-2
+always \u87F6 12-1356-3
+always \u87F7 145-1346-3
+always \u87F8 14-16-4
+always \u87F9 15-346-5
+always \u87FA 24-1236-5
+always \u87FB 16-4
+always \u87FC 13-13456-4
+always \u87FD 145-345-2
+always \u87FE 12-1236-2
+always \u87FF 245-16-5
+always \u8800 245-156-3
+always \u8801 15-46-5
+always \u8802 24-2346-5
+always \u8803 14-25-4
+always \u8804 245-1456-2
+always \u8805 13456-2
+always \u8806 12-2456-5
+always \u8807 14-16-5
+always \u8808 125-2346-2
+always \u8809 15-45-3
+always \u880A 14-2345-2
+always \u880B 1-34-2
+always \u880C 125-2346-2
+always \u880D 15-346-3
+always \u880E 134-1346-4
+always \u880F 15-346-5
+always \u8810 245-16-2
+always \u8811 1245-12346-2
+always \u8812 13-2345-4
+always \u8813 134-1356-4
+always \u8814 1235-146-2
+always \u8815 1245-34-2
+always \u8816 1235-25-5
+always \u8817 1-25-2
+always \u8818 13-346-2
+always \u8819 135-1456-3
+always \u881A 1235-2346-5
+always \u881B 134-346-5
+always \u881C 12345-1236-2
+always \u881D 14-356-2
+always \u881E 13-346-2
+always \u881F 14-345-5
+always \u8820 134-16-5
+always \u8821 14-16-2
+always \u8822 12-123456-4
+always \u8823 14-16-5
+always \u8824 245-234-3
+always \u8825 1345-346-5
+always \u8826 14-34-2
+always \u8827 145-34-5
+always \u8828 15-246-3
+always \u8829 1-34-3
+always \u882A 14-12346-2
+always \u882B 14-16-5
+always \u882C 14-12346-2
+always \u882D 12345-1356-3
+always \u882E 346-3
+always \u882F 1234-16-2
+always \u8830 1345-1346-2
+always \u8831 13-34-4
+always \u8832 13-45-3
+always \u8833 13456-3
+always \u8834 24-34-4
+always \u8835 15-16-3
+always \u8836 245-1236-2
+always \u8837 245-1256-2
+always \u8838 245-45-2
+always \u8839 145-34-5
+always \u883A 245-1236-2
+always \u883B 134-1236-2
+always \u883C 13-236-2
+always \u883D 13-346-2
+always \u883E 1-34-2
+always \u883F 1-25-3
+always \u8840 15-346-4
+always \u8841 1235-456-3
+always \u8842 1345-1256-5
+always \u8843 1234-356-3
+always \u8844 1345-1256-5
+always \u8845 15-1456-5
+always \u8846 1-12346-5
+always \u8847 134-2456-5
+always \u8848 156-5
+always \u8849 123-2346-5
+always \u884A 134-346-5
+always \u884B 15-16-5
+always \u884C 15-13456-2
+always \u884D 2345-4
+always \u884E 123-1236-5
+always \u884F 45-5
+always \u8850 245-1256-2
+always \u8851 14-13456-2
+always \u8852 15-45-5
+always \u8853 24-34-5
+always \u8854 15-2345-2
+always \u8855 124-12346-5
+always \u8856 15-46-5
+always \u8857 13-346-3
+always \u8858 15-2345-2
+always \u8859 23456-2
+always \u885A 1235-34-2
+always \u885B 1246-5
+always \u885C 145-146-5
+always \u885D 12-12346-3
+always \u885E 1246-5
+always \u885F 145-146-5
+always \u8860 1-123456-3
+always \u8861 1235-1356-2
+always \u8862 245-1256-2
+always \u8863 16-3
+always \u8864 16-3
+always \u8865 135-34-4
+always \u8866 13-1236-4
+always \u8867 1256-2
+always \u8868 135-246-4
+always \u8869 12-345-5
+always \u886A 16-5
+always \u886B 24-1236-3
+always \u886C 12-136-5
+always \u886D 12345-34-3
+always \u886E 13-123456-4
+always \u886F 12345-136-3
+always \u8870 24-2356-3
+always \u8871 13-346-2
+always \u8872 1345-345-5
+always \u8873 1-12346-3
+always \u8874 145-1236-4
+always \u8875 1245-156-5
+always \u8876 1-12346-5
+always \u8877 1-12346-3
+always \u8878 15-346-5
+always \u8879 1-156-3
+always \u887A 15-346-2
+always \u887B 1245-1236-2
+always \u887C 1-156-3
+always \u887D 1245-136-5
+always \u887E 245-1456-3
+always \u887F 13-1456-3
+always \u8880 13-256-3
+always \u8881 45-2
+always \u8882 134-356-5
+always \u8883 12-2456-5
+always \u8884 146-4
+always \u8885 1345-246-4
+always \u8886 1235-1246-3
+always \u8887 1245-1236-2
+always \u8888 13-23456-3
+always \u8889 124-25-2
+always \u888A 14-13456-4
+always \u888B 145-2456-5
+always \u888C 135-146-5
+always \u888D 1234-146-2
+always \u888E 246-5
+always \u888F 125-25-5
+always \u8890 135-16-5
+always \u8891 24-146-5
+always \u8892 124-1236-4
+always \u8893 13-1256-4
+always \u8894 1235-2346-5
+always \u8895 15-236-5
+always \u8896 15-234-5
+always \u8897 1-136-4
+always \u8898 16-2
+always \u8899 1234-345-5
+always \u889A 12345-34-2
+always \u889B 145-16-3
+always \u889C 134-126-5
+always \u889D 12345-34-5
+always \u889E 13-123456-4
+always \u889F 1-156-5
+always \u88A0 1-156-5
+always \u88A1 1245-1236-2
+always \u88A2 1234-1236-5
+always \u88A3 16-5
+always \u88A4 134-146-5
+always \u88A5 124-25-3
+always \u88A6 1345-345-5
+always \u88A7 123-12356-3
+always \u88A8 15-45-5
+always \u88A9 12-1236-3
+always \u88AA 245-1256-3
+always \u88AB 135-356-5
+always \u88AC 1256-5
+always \u88AD 15-16-2
+always \u88AE 134-16-2
+always \u88AF 135-126-2
+always \u88B0 134-34-4
+always \u88B1 12345-34-2
+always \u88B2 16-2
+always \u88B3 12-156-4
+always \u88B4 123-34-5
+always \u88B5 1245-136-5
+always \u88B6 13-46-5
+always \u88B7 13-23456-2
+always \u88B8 245-123456-2
+always \u88B9 135-126-2
+always \u88BA 13-346-2
+always \u88BB 156-2
+always \u88BC 13-2346-5
+always \u88BD 1245-34-2
+always \u88BE 1-34-3
+always \u88BF 13-1246-3
+always \u88C0 1456-3
+always \u88C1 245-2456-2
+always \u88C2 14-346-5
+always \u88C3 123-345-4
+always \u88C4 15-13456-2
+always \u88C5 1-456-3
+always \u88C6 145-1346-3
+always \u88C7 15-1256-3
+always \u88C8 123-123456-3
+always \u88C9 123-136-5
+always \u88CA 1345-246-4
+always \u88CB 24-34-5
+always \u88CC 13-23456-2
+always \u88CD 123-123456-4
+always \u88CE 12-1356-2
+always \u88CF 14-16-4
+always \u88D0 13-45-3
+always \u88D1 24-136-3
+always \u88D2 1234-12356-2
+always \u88D3 13-2346-2
+always \u88D4 16-5
+always \u88D5 1256-5
+always \u88D6 1-136-4
+always \u88D7 14-234-2
+always \u88D8 245-234-2
+always \u88D9 245-256-2
+always \u88DA 13-16-5
+always \u88DB 16-5
+always \u88DC 135-34-4
+always \u88DD 1-456-3
+always \u88DE 24-1246-5
+always \u88DF 24-345-3
+always \u88E0 245-256-2
+always \u88E1 14-16-4
+always \u88E2 14-2345-2
+always \u88E3 14-2345-4
+always \u88E4 123-34-5
+always \u88E5 13-2345-4
+always \u88E6 135-146-3
+always \u88E7 12-1236-3
+always \u88E8 135-16-5
+always \u88E9 13-123456-3
+always \u88EA 124-146-2
+always \u88EB 45-5
+always \u88EC 14-13456-2
+always \u88ED 12-156-4
+always \u88EE 12-1346-3
+always \u88EF 12-12356-2
+always \u88F0 145-25-3
+always \u88F1 135-246-4
+always \u88F2 14-46-4
+always \u88F3 24-1346-3
+always \u88F4 1234-356-2
+always \u88F5 1234-356-2
+always \u88F6 12345-356-3
+always \u88F7 45-3
+always \u88F8 14-25-4
+always \u88F9 13-25-4
+always \u88FA 2345-4
+always \u88FB 145-34-4
+always \u88FC 124-16-5
+always \u88FD 1-156-5
+always \u88FE 13-1256-3
+always \u88FF 245-16-4
+always \u8900 13-16-5
+always \u8901 1-156-2
+always \u8902 13-35-5
+always \u8903 123-136-5
+always \u8904 245-16-3
+always \u8905 124-16-5
+always \u8906 124-16-2
+always \u8907 12345-34-5
+always \u8908 12-12346-2
+always \u8909 15-346-3
+always \u890A 135-2345-4
+always \u890B 145-346-2
+always \u890C 123-123456-3
+always \u890D 145-12456-3
+always \u890E 15-234-5
+always \u890F 15-234-5
+always \u8910 1235-2346-2
+always \u8911 45-5
+always \u8912 135-146-3
+always \u8913 135-146-4
+always \u8914 12345-34-5
+always \u8915 1256-2
+always \u8916 124-12456-5
+always \u8917 2345-4
+always \u8918 1235-1246-3
+always \u8919 135-356-5
+always \u891A 12-34-4
+always \u891B 14-1256-4
+always \u891C 1234-146-2
+always \u891D 145-1236-3
+always \u891E 256-4
+always \u891F 145-345-2
+always \u8920 13-12356-3
+always \u8921 145-345-3
+always \u8922 1235-2356-2
+always \u8923 1245-12346-2
+always \u8924 45-5
+always \u8925 1245-34-5
+always \u8926 1345-2456-5
+always \u8927 13-235-4
+always \u8928 15-25-4
+always \u8929 135-1236-3
+always \u892A 124-123456-5
+always \u892B 12-156-4
+always \u892C 15-1346-4
+always \u892D 1345-246-4
+always \u892E 13456-3
+always \u892F 13-346-5
+always \u8930 245-2345-3
+always \u8931 1235-2356-2
+always \u8932 123-34-5
+always \u8933 14-2345-2
+always \u8934 14-1236-2
+always \u8935 14-16-2
+always \u8936 1-2346-4
+always \u8937 24-156-3
+always \u8938 14-1256-4
+always \u8939 16-5
+always \u893A 145-346-2
+always \u893B 15-346-5
+always \u893C 15-2345-3
+always \u893D 1246-5
+always \u893E 135-246-4
+always \u893F 245-146-2
+always \u8940 13-16-3
+always \u8941 245-46-4
+always \u8942 24-1236-3
+always \u8943 135-146-3
+always \u8944 15-46-3
+always \u8945 145-16-5
+always \u8946 1234-34-2
+always \u8947 13-2345-4
+always \u8948 1-12456-5
+always \u8949 13-2345-4
+always \u894A 125-1246-5
+always \u894B 13-16-2
+always \u894C 145-1236-3
+always \u894D 125-345-2
+always \u894E 12345-1236-2
+always \u894F 135-126-2
+always \u8950 15-46-5
+always \u8951 15-1456-2
+always \u8952 135-346-2
+always \u8953 1245-146-2
+always \u8954 134-1236-4
+always \u8955 14-1236-2
+always \u8956 146-4
+always \u8957 145-25-2
+always \u8958 13-1246-5
+always \u8959 245-146-5
+always \u895A 15-1246-5
+always \u895B 1345-12346-2
+always \u895C 12-1236-3
+always \u895D 14-2345-5
+always \u895E 135-16-5
+always \u895F 13-1456-3
+always \u8960 145-1346-3
+always \u8961 24-34-2
+always \u8962 124-1236-4
+always \u8963 135-16-5
+always \u8964 14-1236-2
+always \u8965 1234-34-2
+always \u8966 1245-34-2
+always \u8967 1-156-4
+always \u8968 123-345-3
+always \u8969 24-34-4
+always \u896A 35-5
+always \u896B 24-156-5
+always \u896C 135-2456-4
+always \u896D 15-346-2
+always \u896E 135-126-2
+always \u896F 12-136-5
+always \u8970 14-2456-5
+always \u8971 14-12346-2
+always \u8972 15-16-2
+always \u8973 15-2345-3
+always \u8974 14-1236-2
+always \u8975 1-2346-2
+always \u8976 145-2456-5
+always \u8977 13-1256-4
+always \u8978 125-1236-5
+always \u8979 24-156-3
+always \u897A 13-2345-4
+always \u897B 1234-1236-5
+always \u897C 16-5
+always \u897D 14-1236-2
+always \u897E 23456-5
+always \u897F 15-16-3
+always \u8980 15-16-3
+always \u8981 246-5
+always \u8982 12345-1356-4
+always \u8983 124-1236-2
+always \u8984 12345-34-5
+always \u8985 12345-246-5
+always \u8986 12345-34-5
+always \u8987 135-345-5
+always \u8988 1235-2346-2
+always \u8989 13-16-3
+always \u898A 13-16-3
+always \u898B 13-2345-5
+always \u898C 13-12456-3
+always \u898D 135-2345-5
+always \u898E 2345-5
+always \u898F 13-1246-3
+always \u8990 13-236-2
+always \u8991 1234-2345-4
+always \u8992 134-146-2
+always \u8993 134-16-5
+always \u8994 134-16-5
+always \u8995 1234-346-3
+always \u8996 24-156-5
+always \u8997 15-156-3
+always \u8998 1-1236-3
+always \u8999 14-25-2
+always \u899A 13-236-2
+always \u899B 134-16-5
+always \u899C 124-246-5
+always \u899D 14-2345-2
+always \u899E 246-5
+always \u899F 1-156-5
+always \u89A0 13-256-3
+always \u89A1 15-16-2
+always \u89A2 24-1236-4
+always \u89A3 1246-3
+always \u89A4 15-16-5
+always \u89A5 124-2345-4
+always \u89A6 1256-2
+always \u89A7 14-1236-4
+always \u89A8 2346-5
+always \u89A9 145-34-4
+always \u89AA 245-1456-3
+always \u89AB 1234-1346-4
+always \u89AC 13-16-5
+always \u89AD 134-13456-2
+always \u89AE 13456-2
+always \u89AF 13-12356-5
+always \u89B0 245-1256-5
+always \u89B1 1-1236-5
+always \u89B2 13-1456-5
+always \u89B3 13-12456-3
+always \u89B4 145-1356-3
+always \u89B5 13-2345-5
+always \u89B6 14-25-2
+always \u89B7 245-1256-5
+always \u89B8 13-2345-5
+always \u89B9 1246-2
+always \u89BA 13-236-2
+always \u89BB 245-1256-5
+always \u89BC 14-25-2
+always \u89BD 14-1236-4
+always \u89BE 24-136-4
+always \u89BF 145-16-2
+always \u89C0 13-12456-3
+always \u89C1 13-2345-5
+always \u89C2 13-12456-3
+always \u89C3 2345-5
+always \u89C4 13-1246-3
+always \u89C5 134-16-5
+always \u89C6 24-156-5
+always \u89C7 12-1236-3
+always \u89C8 14-1236-4
+always \u89C9 13-236-2
+always \u89CA 13-16-5
+always \u89CB 15-16-2
+always \u89CC 145-16-2
+always \u89CD 124-2345-4
+always \u89CE 1256-2
+always \u89CF 13-12356-5
+always \u89D0 13-1456-5
+always \u89D1 245-1256-5
+always \u89D2 13-246-4
+always \u89D3 245-234-2
+always \u89D4 13-1456-3
+always \u89D5 245-34-3
+always \u89D6 13-236-2
+always \u89D7 1-156-5
+always \u89D8 12-146-5
+always \u89D9 13-16-2
+always \u89DA 13-34-3
+always \u89DB 145-1236-5
+always \u89DC 125-1246-4
+always \u89DD 145-16-4
+always \u89DE 24-1346-3
+always \u89DF 1235-35-5
+always \u89E0 245-45-2
+always \u89E1 13-2346-2
+always \u89E2 24-156-5
+always \u89E3 13-346-4
+always \u89E4 13-1246-4
+always \u89E5 13-12346-3
+always \u89E6 12-34-5
+always \u89E7 13-346-4
+always \u89E8 1235-123456-5
+always \u89E9 245-234-2
+always \u89EA 15-13456-3
+always \u89EB 15-34-5
+always \u89EC 1345-16-2
+always \u89ED 245-16-3
+always \u89EE 14-34-5
+always \u89EF 1-156-5
+always \u89F0 1-345-3
+always \u89F1 135-16-5
+always \u89F2 15-13456-3
+always \u89F3 1235-34-2
+always \u89F4 24-1346-3
+always \u89F5 13-12346-3
+always \u89F6 1-156-5
+always \u89F7 15-236-2
+always \u89F8 12-34-5
+always \u89F9 15-16-3
+always \u89FA 16-2
+always \u89FB 14-34-5
+always \u89FC 13-236-2
+always \u89FD 15-16-3
+always \u89FE 2345-5
+always \u89FF 15-16-3
+always \u8A00 2345-2
+always \u8A01 2345-2
+always \u8A02 145-13456-5
+always \u8A03 12345-34-5
+always \u8A04 245-234-2
+always \u8A05 245-234-2
+always \u8A06 13-246-5
+always \u8A07 1235-12346-3
+always \u8A08 13-16-5
+always \u8A09 12345-1236-5
+always \u8A0A 15-256-5
+always \u8A0B 145-246-5
+always \u8A0C 1235-12346-5
+always \u8A0D 12-345-5
+always \u8A0E 124-146-4
+always \u8A0F 15-1256-3
+always \u8A10 13-346-2
+always \u8A11 16-2
+always \u8A12 1245-136-5
+always \u8A13 15-256-5
+always \u8A14 1456-2
+always \u8A15 24-1236-5
+always \u8A16 245-16-5
+always \u8A17 124-25-3
+always \u8A18 13-16-5
+always \u8A19 15-256-5
+always \u8A1A 1456-2
+always \u8A1B 2346-2
+always \u8A1C 12345-136-3
+always \u8A1D 23456-5
+always \u8A1E 246-3
+always \u8A1F 15-12346-5
+always \u8A20 24-136-4
+always \u8A21 1456-2
+always \u8A22 15-1456-3
+always \u8A23 13-236-2
+always \u8A24 15-246-2
+always \u8A25 1345-2346-5
+always \u8A26 12-136-2
+always \u8A27 234-2
+always \u8A28 1-156-4
+always \u8A29 15-235-3
+always \u8A2A 12345-1346-4
+always \u8A2B 15-1456-5
+always \u8A2C 12-146-3
+always \u8A2D 24-2346-5
+always \u8A2E 15-2345-3
+always \u8A2F 24-345-4
+always \u8A30 1-123456-5
+always \u8A31 15-1256-4
+always \u8A32 16-5
+always \u8A33 16-5
+always \u8A34 15-34-5
+always \u8A35 12-156-3
+always \u8A36 1235-2346-3
+always \u8A37 24-136-3
+always \u8A38 1235-2346-2
+always \u8A39 15-1256-5
+always \u8A3A 1-136-4
+always \u8A3B 1-34-5
+always \u8A3C 1-1356-5
+always \u8A3D 13-12356-5
+always \u8A3E 125-156-4
+always \u8A3F 125-156-3
+always \u8A40 1-1236-3
+always \u8A41 13-34-4
+always \u8A42 12345-34-5
+always \u8A43 245-45-4
+always \u8A44 145-346-2
+always \u8A45 14-13456-2
+always \u8A46 145-16-4
+always \u8A47 46-5
+always \u8A48 14-16-5
+always \u8A49 1345-34-5
+always \u8A4A 1234-1236-5
+always \u8A4B 1-12356-5
+always \u8A4C 13-1236-5
+always \u8A4D 16-5
+always \u8A4E 13-1256-5
+always \u8A4F 146-5
+always \u8A50 1-345-5
+always \u8A51 124-25-2
+always \u8A52 16-2
+always \u8A53 245-1256-4
+always \u8A54 1-146-5
+always \u8A55 1234-13456-2
+always \u8A56 135-16-5
+always \u8A57 15-235-5
+always \u8A58 245-1256-3
+always \u8A59 135-345-2
+always \u8A5A 145-345-2
+always \u8A5B 125-34-4
+always \u8A5C 124-146-3
+always \u8A5D 1-34-4
+always \u8A5E 245-156-2
+always \u8A5F 1-2346-2
+always \u8A60 235-4
+always \u8A61 15-1256-4
+always \u8A62 15-256-2
+always \u8A63 16-5
+always \u8A64 1235-456-4
+always \u8A65 1235-2346-2
+always \u8A66 24-156-5
+always \u8A67 12-345-2
+always \u8A68 13-246-3
+always \u8A69 24-156-3
+always \u8A6A 1235-136-4
+always \u8A6B 12-345-5
+always \u8A6C 13-12356-5
+always \u8A6D 13-1246-4
+always \u8A6E 245-45-2
+always \u8A6F 1235-1246-5
+always \u8A70 13-346-2
+always \u8A71 1235-35-5
+always \u8A72 13-2456-3
+always \u8A73 15-46-2
+always \u8A74 1246-3
+always \u8A75 24-136-3
+always \u8A76 12-12356-2
+always \u8A77 145-12346-5
+always \u8A78 134-16-2
+always \u8A79 1-1236-3
+always \u8A7A 134-13456-5
+always \u8A7B 2346-5
+always \u8A7C 1235-1246-3
+always \u8A7D 2345-2
+always \u8A7E 15-235-3
+always \u8A7F 13-35-5
+always \u8A80 156-5
+always \u8A81 135-1356-4
+always \u8A82 124-246-4
+always \u8A83 12-156-4
+always \u8A84 14-356-4
+always \u8A85 1-34-3
+always \u8A86 123-456-3
+always \u8A87 123-35-3
+always \u8A88 34-2
+always \u8A89 1256-5
+always \u8A8A 124-1356-2
+always \u8A8B 13-16-5
+always \u8A8C 1-156-5
+always \u8A8D 1245-136-5
+always \u8A8E 15-34-5
+always \u8A8F 14-1346-4
+always \u8A90 2346-2
+always \u8A91 123-456-2
+always \u8A92 26-5
+always \u8A93 24-156-5
+always \u8A94 124-13456-4
+always \u8A95 145-1236-5
+always \u8A96 135-356-5
+always \u8A97 12-1236-2
+always \u8A98 234-5
+always \u8A99 123-1356-3
+always \u8A9A 245-246-5
+always \u8A9B 245-1456-3
+always \u8A9C 24-35-5
+always \u8A9D 1236-3
+always \u8A9E 1256-4
+always \u8A9F 15-246-5
+always \u8AA0 12-1356-2
+always \u8AA1 13-346-5
+always \u8AA2 15-2345-5
+always \u8AA3 34-3
+always \u8AA4 34-5
+always \u8AA5 13-146-5
+always \u8AA6 15-12346-5
+always \u8AA7 1234-34-4
+always \u8AA8 1235-1246-5
+always \u8AA9 13-13456-5
+always \u8AAA 24-25-3
+always \u8AAB 1-136-5
+always \u8AAC 24-25-3
+always \u8AAD 145-34-2
+always \u8AAE 1235-35-3
+always \u8AAF 12-1346-5
+always \u8AB0 24-356-2
+always \u8AB1 13-346-2
+always \u8AB2 123-2346-5
+always \u8AB3 245-1256-3
+always \u8AB4 245-12346-2
+always \u8AB5 15-246-2
+always \u8AB6 15-1246-5
+always \u8AB7 456-4
+always \u8AB8 15-2345-2
+always \u8AB9 12345-356-4
+always \u8ABA 12-156-3
+always \u8ABB 124-345-5
+always \u8ABC 16-2
+always \u8ABD 16-5
+always \u8ABE 1456-2
+always \u8ABF 145-246-5
+always \u8AC0 1234-16-4
+always \u8AC1 12-25-5
+always \u8AC2 12-1236-4
+always \u8AC3 12-136-3
+always \u8AC4 1-123456-3
+always \u8AC5 13-16-5
+always \u8AC6 245-16-3
+always \u8AC7 124-1236-2
+always \u8AC8 1-1246-5
+always \u8AC9 1246-4
+always \u8ACA 13-1256-2
+always \u8ACB 245-13456-4
+always \u8ACC 13-2345-5
+always \u8ACD 1-1356-5
+always \u8ACE 125-2346-2
+always \u8ACF 125-12356-3
+always \u8AD0 245-2345-3
+always \u8AD1 1-25-2
+always \u8AD2 14-46-5
+always \u8AD3 13-2345-5
+always \u8AD4 12-34-5
+always \u8AD5 1235-146-2
+always \u8AD6 14-123456-5
+always \u8AD7 24-136-4
+always \u8AD8 135-246-4
+always \u8AD9 1235-2356-5
+always \u8ADA 1234-2345-2
+always \u8ADB 1256-2
+always \u8ADC 145-346-2
+always \u8ADD 15-1256-4
+always \u8ADE 1234-2345-4
+always \u8ADF 24-156-5
+always \u8AE0 15-45-3
+always \u8AE1 24-156-5
+always \u8AE2 1235-123456-5
+always \u8AE3 1235-35-5
+always \u8AE4 2346-5
+always \u8AE5 1-12346-5
+always \u8AE6 145-16-5
+always \u8AE7 15-346-2
+always \u8AE8 12345-34-2
+always \u8AE9 1234-34-4
+always \u8AEA 124-13456-2
+always \u8AEB 13-2345-5
+always \u8AEC 245-16-4
+always \u8AED 1256-5
+always \u8AEE 125-156-3
+always \u8AEF 12-12456-2
+always \u8AF0 15-16-4
+always \u8AF1 1235-1246-5
+always \u8AF2 1456-3
+always \u8AF3 1236-3
+always \u8AF4 15-2345-2
+always \u8AF5 1345-1236-2
+always \u8AF6 12-136-2
+always \u8AF7 12345-1356-4
+always \u8AF8 1-34-3
+always \u8AF9 46-2
+always \u8AFA 2345-5
+always \u8AFB 1235-1356-3
+always \u8AFC 15-45-3
+always \u8AFD 13-2346-2
+always \u8AFE 1345-25-5
+always \u8AFF 245-16-5
+always \u8B00 134-12356-2
+always \u8B01 346-5
+always \u8B02 1246-5
+always \u8B03 15-13456-3
+always \u8B04 124-1356-2
+always \u8B05 125-12356-3
+always \u8B06 24-1236-5
+always \u8B07 13-2345-4
+always \u8B08 135-126-2
+always \u8B09 145-1246-5
+always \u8B0A 1235-456-4
+always \u8B0B 1235-25-5
+always \u8B0C 13-2346-3
+always \u8B0D 13456-2
+always \u8B0E 134-16-2
+always \u8B0F 15-246-4
+always \u8B10 134-16-5
+always \u8B11 15-16-5
+always \u8B12 245-46-3
+always \u8B13 12-136-3
+always \u8B14 1345-236-5
+always \u8B15 124-16-2
+always \u8B16 15-34-5
+always \u8B17 135-1346-5
+always \u8B18 12-156-2
+always \u8B19 245-2345-3
+always \u8B1A 24-156-5
+always \u8B1B 13-46-4
+always \u8B1C 45-5
+always \u8B1D 15-346-5
+always \u8B1E 1235-2346-5
+always \u8B1F 124-146-3
+always \u8B20 246-2
+always \u8B21 246-2
+always \u8B22 1235-34-5
+always \u8B23 1256-2
+always \u8B24 135-246-3
+always \u8B25 245-12346-5
+always \u8B26 245-13456-5
+always \u8B27 14-16-2
+always \u8B28 134-126-2
+always \u8B29 134-126-2
+always \u8B2A 24-1346-3
+always \u8B2B 1-2346-2
+always \u8B2C 134-234-5
+always \u8B2D 13-2345-4
+always \u8B2E 125-2346-2
+always \u8B2F 13-346-3
+always \u8B30 14-2345-2
+always \u8B31 14-12356-2
+always \u8B32 245-1236-3
+always \u8B33 12356-3
+always \u8B34 13-12456-5
+always \u8B35 15-16-2
+always \u8B36 1-25-2
+always \u8B37 146-2
+always \u8B38 146-2
+always \u8B39 13-1456-4
+always \u8B3A 1-2346-2
+always \u8B3B 16-2
+always \u8B3C 1235-34-3
+always \u8B3D 13-46-5
+always \u8B3E 134-1236-5
+always \u8B3F 12-146-2
+always \u8B40 1235-1236-5
+always \u8B41 1235-35-2
+always \u8B42 12-1236-4
+always \u8B43 15-1256-3
+always \u8B44 125-1356-3
+always \u8B45 15-2346-5
+always \u8B46 15-16-3
+always \u8B47 1-345-3
+always \u8B48 145-1246-5
+always \u8B49 1-1356-5
+always \u8B4A 1345-146-2
+always \u8B4B 14-1236-2
+always \u8B4C 2346-2
+always \u8B4D 13456-5
+always \u8B4E 13-236-2
+always \u8B4F 13-16-3
+always \u8B50 125-123456-4
+always \u8B51 13-246-4
+always \u8B52 135-126-5
+always \u8B53 1235-1246-5
+always \u8B54 1-12456-5
+always \u8B55 134-34-2
+always \u8B56 125-136-5
+always \u8B57 1-345-2
+always \u8B58 24-156-5
+always \u8B59 245-246-2
+always \u8B5A 124-1236-2
+always \u8B5B 125-136-5
+always \u8B5C 1234-34-4
+always \u8B5D 24-1356-2
+always \u8B5E 15-45-3
+always \u8B5F 125-146-5
+always \u8B60 1-1236-3
+always \u8B61 145-1346-4
+always \u8B62 15-1246-5
+always \u8B63 245-2345-3
+always \u8B64 13-16-3
+always \u8B65 13-246-5
+always \u8B66 13-13456-4
+always \u8B67 14-2345-2
+always \u8B68 1345-12356-2
+always \u8B69 16-3
+always \u8B6A 2456-5
+always \u8B6B 1-1236-3
+always \u8B6C 1234-16-5
+always \u8B6D 1235-1246-4
+always \u8B6E 1235-35-5
+always \u8B6F 16-5
+always \u8B70 16-5
+always \u8B71 24-1236-5
+always \u8B72 1245-1346-5
+always \u8B73 1345-12356-5
+always \u8B74 245-2345-4
+always \u8B75 1-1246-5
+always \u8B76 124-345-5
+always \u8B77 1235-34-5
+always \u8B78 1-12356-3
+always \u8B79 1235-146-2
+always \u8B7A 346-5
+always \u8B7B 13456-3
+always \u8B7C 13-2345-5
+always \u8B7D 1256-5
+always \u8B7E 13-2345-4
+always \u8B7F 1235-1246-5
+always \u8B80 145-34-2
+always \u8B81 1-2346-2
+always \u8B82 15-45-5
+always \u8B83 125-1236-5
+always \u8B84 14-356-4
+always \u8B85 24-136-4
+always \u8B86 1246-5
+always \u8B87 12-1236-4
+always \u8B88 14-16-5
+always \u8B89 16-2
+always \u8B8A 135-2345-5
+always \u8B8B 1-2346-2
+always \u8B8C 2345-5
+always \u8B8D 2346-5
+always \u8B8E 12-12356-2
+always \u8B8F 1246-5
+always \u8B90 12-12356-2
+always \u8B91 246-5
+always \u8B92 12-1236-2
+always \u8B93 1245-1346-5
+always \u8B94 1456-4
+always \u8B95 14-1236-2
+always \u8B96 12-136-5
+always \u8B97 1235-25-5
+always \u8B98 1-2346-2
+always \u8B99 1235-12456-3
+always \u8B9A 125-1236-5
+always \u8B9B 16-5
+always \u8B9C 145-1346-4
+always \u8B9D 1-1236-3
+always \u8B9E 2345-5
+always \u8B9F 145-34-2
+always \u8BA0 2345-2
+always \u8BA1 13-16-5
+always \u8BA2 145-13456-5
+always \u8BA3 12345-34-5
+always \u8BA4 1245-136-5
+always \u8BA5 13-16-3
+always \u8BA6 13-346-2
+always \u8BA7 1235-12346-5
+always \u8BA8 124-146-4
+always \u8BA9 1245-1346-5
+always \u8BAA 24-1236-5
+always \u8BAB 245-16-5
+always \u8BAC 124-25-3
+always \u8BAD 15-256-5
+always \u8BAE 16-5
+always \u8BAF 15-256-5
+always \u8BB0 13-16-5
+always \u8BB1 1245-136-5
+always \u8BB2 13-46-4
+always \u8BB3 1235-1246-5
+always \u8BB4 12356-3
+always \u8BB5 13-1256-5
+always \u8BB6 23456-5
+always \u8BB7 1345-2346-5
+always \u8BB8 15-1256-4
+always \u8BB9 2346-2
+always \u8BBA 14-123456-5
+always \u8BBB 15-235-3
+always \u8BBC 15-12346-5
+always \u8BBD 12345-1356-4
+always \u8BBE 24-2346-5
+always \u8BBF 12345-1346-4
+always \u8BC0 13-236-2
+always \u8BC1 1-1356-5
+always \u8BC2 13-34-4
+always \u8BC3 1235-2346-3
+always \u8BC4 1234-13456-2
+always \u8BC5 125-34-4
+always \u8BC6 24-156-5
+always \u8BC7 15-235-5
+always \u8BC8 1-345-5
+always \u8BC9 15-34-5
+always \u8BCA 1-136-4
+always \u8BCB 145-16-4
+always \u8BCC 1-12356-3
+always \u8BCD 245-156-2
+always \u8BCE 245-1256-3
+always \u8BCF 1-146-5
+always \u8BD0 135-16-5
+always \u8BD1 16-5
+always \u8BD2 16-2
+always \u8BD3 13-456-3
+always \u8BD4 14-356-4
+always \u8BD5 24-156-5
+always \u8BD6 13-35-5
+always \u8BD7 24-156-3
+always \u8BD8 13-346-2
+always \u8BD9 1235-1246-3
+always \u8BDA 12-1356-2
+always \u8BDB 1-34-3
+always \u8BDC 24-136-3
+always \u8BDD 1235-35-5
+always \u8BDE 145-1236-5
+always \u8BDF 13-12356-5
+always \u8BE0 245-45-2
+always \u8BE1 13-1246-4
+always \u8BE2 15-256-2
+always \u8BE3 16-5
+always \u8BE4 1-1356-5
+always \u8BE5 13-2456-3
+always \u8BE6 15-46-2
+always \u8BE7 12-345-5
+always \u8BE8 1235-123456-5
+always \u8BE9 15-1256-4
+always \u8BEA 1-12356-3
+always \u8BEB 13-346-5
+always \u8BEC 34-3
+always \u8BED 1256-4
+always \u8BEE 245-246-5
+always \u8BEF 34-5
+always \u8BF0 13-146-5
+always \u8BF1 234-5
+always \u8BF2 1235-1246-5
+always \u8BF3 123-456-2
+always \u8BF4 24-25-3
+always \u8BF5 15-12346-5
+always \u8BF6 2456-3
+always \u8BF7 245-13456-4
+always \u8BF8 1-34-3
+always \u8BF9 125-12356-3
+always \u8BFA 1345-25-5
+always \u8BFB 145-34-2
+always \u8BFC 1-25-2
+always \u8BFD 12345-356-4
+always \u8BFE 123-2346-5
+always \u8BFF 1246-4
+always \u8C00 1256-2
+always \u8C01 24-356-2
+always \u8C02 24-136-4
+always \u8C03 145-246-5
+always \u8C04 12-1236-4
+always \u8C05 14-46-5
+always \u8C06 1-123456-3
+always \u8C07 15-1246-5
+always \u8C08 124-1236-2
+always \u8C09 24-136-4
+always \u8C0A 16-5
+always \u8C0B 134-12356-2
+always \u8C0C 12-136-2
+always \u8C0D 145-346-2
+always \u8C0E 1235-456-4
+always \u8C0F 13-2345-5
+always \u8C10 15-346-2
+always \u8C11 15-236-5
+always \u8C12 346-5
+always \u8C13 1246-5
+always \u8C14 2346-5
+always \u8C15 1256-5
+always \u8C16 15-45-3
+always \u8C17 12-1236-2
+always \u8C18 125-156-3
+always \u8C19 1236-3
+always \u8C1A 2345-5
+always \u8C1B 145-16-5
+always \u8C1C 134-16-2
+always \u8C1D 1234-2345-4
+always \u8C1E 15-1256-4
+always \u8C1F 134-126-2
+always \u8C20 145-1346-4
+always \u8C21 15-34-5
+always \u8C22 15-346-5
+always \u8C23 246-2
+always \u8C24 135-1346-5
+always \u8C25 24-156-5
+always \u8C26 245-2345-3
+always \u8C27 134-16-5
+always \u8C28 13-1456-4
+always \u8C29 134-1236-5
+always \u8C2A 1-2346-2
+always \u8C2B 13-2345-4
+always \u8C2C 134-234-5
+always \u8C2D 124-1236-2
+always \u8C2E 125-136-5
+always \u8C2F 245-246-2
+always \u8C30 14-1236-2
+always \u8C31 1234-34-4
+always \u8C32 13-236-2
+always \u8C33 2345-5
+always \u8C34 245-2345-4
+always \u8C35 1-1236-3
+always \u8C36 12-136-5
+always \u8C37 13-34-4
+always \u8C38 245-2345-3
+always \u8C39 1235-12346-2
+always \u8C3A 15-23456-3
+always \u8C3B 13-236-2
+always \u8C3C 1235-12346-2
+always \u8C3D 1235-1236-3
+always \u8C3E 1235-12346-3
+always \u8C3F 15-16-3
+always \u8C40 15-16-3
+always \u8C41 1235-25-5
+always \u8C42 14-246-2
+always \u8C43 1235-1236-4
+always \u8C44 145-34-2
+always \u8C45 14-12346-2
+always \u8C46 145-12356-5
+always \u8C47 13-46-3
+always \u8C48 245-16-4
+always \u8C49 12-156-4
+always \u8C4A 14-16-4
+always \u8C4B 145-1356-3
+always \u8C4C 12456-3
+always \u8C4D 135-16-3
+always \u8C4E 24-34-5
+always \u8C4F 15-2345-5
+always \u8C50 12345-1356-3
+always \u8C51 1-156-5
+always \u8C52 1-156-5
+always \u8C53 2345-5
+always \u8C54 2345-5
+always \u8C55 24-156-4
+always \u8C56 12-34-5
+always \u8C57 1235-1246-3
+always \u8C58 124-123456-2
+always \u8C59 16-5
+always \u8C5A 124-123456-2
+always \u8C5B 16-5
+always \u8C5C 13-2345-3
+always \u8C5D 135-345-3
+always \u8C5E 1235-12356-5
+always \u8C5F 2346-5
+always \u8C60 245-34-2
+always \u8C61 15-46-5
+always \u8C62 1235-12456-5
+always \u8C63 13-2345-3
+always \u8C64 123-136-4
+always \u8C65 13-2456-3
+always \u8C66 245-1256-2
+always \u8C67 12345-34-3
+always \u8C68 15-16-3
+always \u8C69 135-1456-3
+always \u8C6A 1235-146-2
+always \u8C6B 1256-5
+always \u8C6C 1-34-3
+always \u8C6D 13-23456-3
+always \u8C6E 12345-136-2
+always \u8C6F 15-16-3
+always \u8C70 1235-34-5
+always \u8C71 123456-3
+always \u8C72 1235-12456-2
+always \u8C73 135-1456-3
+always \u8C74 145-16-2
+always \u8C75 125-12346-3
+always \u8C76 12345-136-2
+always \u8C77 16-5
+always \u8C78 1-156-5
+always \u8C79 135-146-5
+always \u8C7A 12-2456-2
+always \u8C7B 1235-1236-5
+always \u8C7C 1234-16-2
+always \u8C7D 1345-345-5
+always \u8C7E 1234-16-3
+always \u8C7F 13-12356-4
+always \u8C80 1345-345-5
+always \u8C81 234-5
+always \u8C82 145-246-3
+always \u8C83 134-126-5
+always \u8C84 15-156-5
+always \u8C85 15-234-3
+always \u8C86 1235-12456-2
+always \u8C87 123-136-4
+always \u8C88 1235-2346-2
+always \u8C89 1235-2346-2
+always \u8C8A 134-126-5
+always \u8C8B 1235-1236-5
+always \u8C8C 134-146-5
+always \u8C8D 14-16-2
+always \u8C8E 1345-16-2
+always \u8C8F 135-16-4
+always \u8C90 1256-4
+always \u8C91 13-23456-3
+always \u8C92 124-12456-3
+always \u8C93 134-146-3
+always \u8C94 1234-16-2
+always \u8C95 15-16-3
+always \u8C96 2346-5
+always \u8C97 13-1256-5
+always \u8C98 134-126-5
+always \u8C99 12-34-3
+always \u8C9A 124-1236-2
+always \u8C9B 1235-12456-3
+always \u8C9C 13-236-2
+always \u8C9D 135-356-5
+always \u8C9E 1-136-3
+always \u8C9F 45-2
+always \u8CA0 12345-34-5
+always \u8CA1 245-2456-2
+always \u8CA2 13-12346-5
+always \u8CA3 124-2346-5
+always \u8CA4 16-2
+always \u8CA5 1235-1346-2
+always \u8CA6 12456-5
+always \u8CA7 1234-1456-2
+always \u8CA8 1235-25-5
+always \u8CA9 12345-1236-5
+always \u8CAA 124-1236-3
+always \u8CAB 13-12456-5
+always \u8CAC 125-2346-2
+always \u8CAD 1-156-2
+always \u8CAE 156-5
+always \u8CAF 1-34-4
+always \u8CB0 24-156-5
+always \u8CB1 135-16-5
+always \u8CB2 125-156-3
+always \u8CB3 156-5
+always \u8CB4 13-1246-5
+always \u8CB5 1234-2345-4
+always \u8CB6 135-2345-4
+always \u8CB7 134-2456-4
+always \u8CB8 145-2456-5
+always \u8CB9 24-1356-5
+always \u8CBA 123-456-5
+always \u8CBB 12345-356-5
+always \u8CBC 124-346-3
+always \u8CBD 16-2
+always \u8CBE 12-156-2
+always \u8CBF 134-146-5
+always \u8CC0 1235-2346-5
+always \u8CC1 135-136-3
+always \u8CC2 14-34-5
+always \u8CC3 14-1456-5
+always \u8CC4 1235-1246-5
+always \u8CC5 13-2456-3
+always \u8CC6 1234-2345-2
+always \u8CC7 125-156-3
+always \u8CC8 13-23456-4
+always \u8CC9 15-1256-5
+always \u8CCA 125-356-2
+always \u8CCB 13-246-4
+always \u8CCC 13-2456-3
+always \u8CCD 125-1346-3
+always \u8CCE 13-2345-5
+always \u8CCF 13456-3
+always \u8CD0 15-256-5
+always \u8CD1 1-136-5
+always \u8CD2 24-2346-3
+always \u8CD3 135-1456-3
+always \u8CD4 135-1456-3
+always \u8CD5 245-234-2
+always \u8CD6 24-2346-3
+always \u8CD7 12-12456-5
+always \u8CD8 125-1346-3
+always \u8CD9 1-12356-3
+always \u8CDA 14-2456-5
+always \u8CDB 125-1236-5
+always \u8CDC 15-156-5
+always \u8CDD 12-136-3
+always \u8CDE 24-1346-4
+always \u8CDF 124-2345-4
+always \u8CE0 1234-356-2
+always \u8CE1 13-1356-3
+always \u8CE2 15-2345-2
+always \u8CE3 134-2456-5
+always \u8CE4 13-2345-5
+always \u8CE5 15-1246-5
+always \u8CE6 12345-34-5
+always \u8CE7 145-1236-4
+always \u8CE8 245-12346-2
+always \u8CE9 245-12346-2
+always \u8CEA 1-156-2
+always \u8CEB 13-16-3
+always \u8CEC 1-1346-5
+always \u8CED 145-34-4
+always \u8CEE 13-1456-5
+always \u8CEF 15-235-3
+always \u8CF0 24-123456-4
+always \u8CF1 256-4
+always \u8CF2 135-146-4
+always \u8CF3 125-2456-3
+always \u8CF4 14-2456-5
+always \u8CF5 12345-1356-5
+always \u8CF6 245-1346-5
+always \u8CF7 13-16-3
+always \u8CF8 24-1356-5
+always \u8CF9 2456-5
+always \u8CFA 1-12456-5
+always \u8CFB 12345-34-5
+always \u8CFC 13-12356-5
+always \u8CFD 15-2456-5
+always \u8CFE 125-2346-2
+always \u8CFF 14-246-2
+always \u8D00 1246-5
+always \u8D01 135-2456-5
+always \u8D02 12-136-4
+always \u8D03 1-12456-5
+always \u8D04 1-156-5
+always \u8D05 1-1246-5
+always \u8D06 135-246-3
+always \u8D07 256-3
+always \u8D08 125-1356-5
+always \u8D09 124-1236-4
+always \u8D0A 125-1236-5
+always \u8D0B 2345-5
+always \u8D0C 1234-34-4
+always \u8D0D 24-1236-5
+always \u8D0E 12456-5
+always \u8D0F 13456-2
+always \u8D10 13-1456-5
+always \u8D11 13-1236-5
+always \u8D12 15-2345-2
+always \u8D13 125-1346-3
+always \u8D14 135-16-5
+always \u8D15 145-34-2
+always \u8D16 24-34-2
+always \u8D17 2345-5
+always \u8D18 24-1346-4
+always \u8D19 15-45-5
+always \u8D1A 14-12346-5
+always \u8D1B 13-1236-5
+always \u8D1C 125-1346-3
+always \u8D1D 135-356-5
+always \u8D1E 1-136-3
+always \u8D1F 12345-34-5
+always \u8D20 45-2
+always \u8D21 13-12346-5
+always \u8D22 245-2456-2
+always \u8D23 125-2346-2
+always \u8D24 15-2345-2
+always \u8D25 135-2456-5
+always \u8D26 1-1346-5
+always \u8D27 1235-25-5
+always \u8D28 1-156-2
+always \u8D29 12345-1236-5
+always \u8D2A 124-1236-3
+always \u8D2B 1234-1456-2
+always \u8D2C 135-2345-4
+always \u8D2D 13-12356-5
+always \u8D2E 1-34-4
+always \u8D2F 13-12456-5
+always \u8D30 156-5
+always \u8D31 13-2345-5
+always \u8D32 135-136-3
+always \u8D33 24-156-5
+always \u8D34 124-346-3
+always \u8D35 13-1246-5
+always \u8D36 123-456-5
+always \u8D37 145-2456-5
+always \u8D38 134-146-5
+always \u8D39 12345-356-5
+always \u8D3A 1235-2346-5
+always \u8D3B 16-2
+always \u8D3C 125-356-2
+always \u8D3D 1-156-5
+always \u8D3E 13-23456-4
+always \u8D3F 1235-1246-5
+always \u8D40 125-156-3
+always \u8D41 14-1456-5
+always \u8D42 14-34-5
+always \u8D43 125-1346-3
+always \u8D44 125-156-3
+always \u8D45 13-2456-3
+always \u8D46 13-1456-5
+always \u8D47 245-234-2
+always \u8D48 1-136-5
+always \u8D49 14-2456-5
+always \u8D4A 24-2346-3
+always \u8D4B 12345-34-5
+always \u8D4C 145-34-4
+always \u8D4D 13-16-3
+always \u8D4E 24-34-2
+always \u8D4F 24-1346-4
+always \u8D50 245-156-5
+always \u8D51 135-16-5
+always \u8D52 1-12356-3
+always \u8D53 13-1356-3
+always \u8D54 1234-356-2
+always \u8D55 145-1236-4
+always \u8D56 14-2456-5
+always \u8D57 12345-1356-5
+always \u8D58 1-1246-5
+always \u8D59 12345-34-5
+always \u8D5A 1-12456-5
+always \u8D5B 15-2456-5
+always \u8D5C 125-2346-2
+always \u8D5D 2345-5
+always \u8D5E 125-1236-5
+always \u8D5F 256-3
+always \u8D60 125-1356-5
+always \u8D61 24-1236-5
+always \u8D62 13456-2
+always \u8D63 13-1236-5
+always \u8D64 12-156-5
+always \u8D65 15-16-5
+always \u8D66 24-2346-5
+always \u8D67 1345-1236-4
+always \u8D68 124-12346-2
+always \u8D69 15-16-5
+always \u8D6A 12-1356-3
+always \u8D6B 1235-2346-5
+always \u8D6C 12-1356-3
+always \u8D6D 1-2346-4
+always \u8D6E 15-23456-2
+always \u8D6F 124-1346-2
+always \u8D70 125-12356-4
+always \u8D71 125-12356-4
+always \u8D72 14-16-5
+always \u8D73 13-234-3
+always \u8D74 12345-34-5
+always \u8D75 1-146-5
+always \u8D76 245-2345-2
+always \u8D77 245-16-4
+always \u8D78 24-1236-5
+always \u8D79 245-235-2
+always \u8D7A 245-1456-2
+always \u8D7B 15-2345-4
+always \u8D7C 245-156-3
+always \u8D7D 13-236-2
+always \u8D7E 245-1456-4
+always \u8D7F 12-156-2
+always \u8D80 245-156-3
+always \u8D81 12-136-5
+always \u8D82 12-136-5
+always \u8D83 145-346-2
+always \u8D84 245-346-5
+always \u8D85 12-146-3
+always \u8D86 145-16-3
+always \u8D87 15-2346-5
+always \u8D88 1-1236-3
+always \u8D89 13-236-2
+always \u8D8A 236-5
+always \u8D8B 245-1256-3
+always \u8D8C 13-346-2
+always \u8D8D 245-1256-3
+always \u8D8E 12-34-2
+always \u8D8F 13-35-3
+always \u8D90 15-236-5
+always \u8D91 125-156-3
+always \u8D92 124-246-2
+always \u8D93 145-25-4
+always \u8D94 14-346-5
+always \u8D95 13-1236-4
+always \u8D96 15-25-3
+always \u8D97 245-34-5
+always \u8D98 15-16-2
+always \u8D99 1-146-5
+always \u8D9A 15-34-5
+always \u8D9B 1456-4
+always \u8D9C 13-1256-2
+always \u8D9D 13-2345-5
+always \u8D9E 245-236-5
+always \u8D9F 124-1346-5
+always \u8DA0 12-25-5
+always \u8DA1 245-1246-4
+always \u8DA2 14-34-5
+always \u8DA3 245-1256-5
+always \u8DA4 145-1346-5
+always \u8DA5 245-234-3
+always \u8DA6 125-156-3
+always \u8DA7 124-16-2
+always \u8DA8 245-1256-3
+always \u8DA9 12-156-5
+always \u8DAA 1235-456-2
+always \u8DAB 245-246-2
+always \u8DAC 245-246-3
+always \u8DAD 13-246-5
+always \u8DAE 125-146-5
+always \u8DAF 124-16-5
+always \u8DB0 156-4
+always \u8DB1 125-1236-4
+always \u8DB2 125-1236-4
+always \u8DB3 125-34-2
+always \u8DB4 1234-345-3
+always \u8DB5 135-146-5
+always \u8DB6 123-34-5
+always \u8DB7 123-2346-3
+always \u8DB8 145-123456-4
+always \u8DB9 13-236-2
+always \u8DBA 12345-34-3
+always \u8DBB 12-136-4
+always \u8DBC 13-2345-4
+always \u8DBD 12345-1346-5
+always \u8DBE 1-156-4
+always \u8DBF 15-345-5
+always \u8DC0 236-5
+always \u8DC1 135-345-5
+always \u8DC2 245-16-2
+always \u8DC3 236-5
+always \u8DC4 245-46-5
+always \u8DC5 124-25-5
+always \u8DC6 124-2456-2
+always \u8DC7 16-5
+always \u8DC8 1345-2345-4
+always \u8DC9 14-13456-2
+always \u8DCA 134-356-5
+always \u8DCB 135-345-2
+always \u8DCC 145-346-2
+always \u8DCD 123-34-3
+always \u8DCE 124-25-2
+always \u8DCF 13-23456-3
+always \u8DD0 245-156-4
+always \u8DD1 1234-146-4
+always \u8DD2 245-23456-4
+always \u8DD3 1-34-5
+always \u8DD4 12345-34-4
+always \u8DD5 145-346-2
+always \u8DD6 1-156-2
+always \u8DD7 12345-34-3
+always \u8DD8 1234-1236-2
+always \u8DD9 13-1256-4
+always \u8DDA 24-1236-3
+always \u8DDB 135-126-4
+always \u8DDC 1345-16-2
+always \u8DDD 13-1256-5
+always \u8DDE 14-16-5
+always \u8DDF 13-136-3
+always \u8DE0 16-2
+always \u8DE1 13-16-3
+always \u8DE2 145-25-5
+always \u8DE3 15-2345-4
+always \u8DE4 13-246-3
+always \u8DE5 145-25-5
+always \u8DE6 1-34-3
+always \u8DE7 245-45-2
+always \u8DE8 123-35-5
+always \u8DE9 1-2356-4
+always \u8DEA 13-1246-5
+always \u8DEB 245-235-2
+always \u8DEC 123-1246-4
+always \u8DED 15-46-2
+always \u8DEE 12-156-5
+always \u8DEF 14-34-5
+always \u8DF0 135-1356-5
+always \u8DF1 1-156-5
+always \u8DF2 13-23456-2
+always \u8DF3 124-246-5
+always \u8DF4 245-2456-4
+always \u8DF5 13-2345-5
+always \u8DF6 124-345-5
+always \u8DF7 245-246-3
+always \u8DF8 135-16-5
+always \u8DF9 15-2345-3
+always \u8DFA 145-25-5
+always \u8DFB 13-16-3
+always \u8DFC 13-1256-2
+always \u8DFD 13-16-5
+always \u8DFE 24-34-2
+always \u8DFF 124-34-2
+always \u8E00 12-34-5
+always \u8E01 13-13456-5
+always \u8E02 1345-346-5
+always \u8E03 15-246-3
+always \u8E04 135-126-2
+always \u8E05 15-236-2
+always \u8E06 245-256-3
+always \u8E07 134-34-4
+always \u8E08 24-34-3
+always \u8E09 14-46-5
+always \u8E0A 235-4
+always \u8E0B 13-246-4
+always \u8E0C 12-12356-2
+always \u8E0D 245-246-3
+always \u8E0E 134-12356-2
+always \u8E0F 124-345-5
+always \u8E10 13-2345-5
+always \u8E11 245-16-2
+always \u8E12 25-3
+always \u8E13 1246-4
+always \u8E14 1-25-2
+always \u8E15 13-346-2
+always \u8E16 13-16-2
+always \u8E17 1345-346-3
+always \u8E18 13-1256-2
+always \u8E19 1345-346-5
+always \u8E1A 14-123456-2
+always \u8E1B 14-34-5
+always \u8E1C 14-1356-5
+always \u8E1D 1235-2356-2
+always \u8E1E 13-1256-5
+always \u8E1F 12-156-2
+always \u8E20 12456-4
+always \u8E21 245-45-2
+always \u8E22 124-16-3
+always \u8E23 135-126-2
+always \u8E24 125-34-2
+always \u8E25 245-346-5
+always \u8E26 245-16-2
+always \u8E27 245-34-5
+always \u8E28 125-12346-3
+always \u8E29 245-2456-4
+always \u8E2A 125-12346-3
+always \u8E2B 1234-1356-5
+always \u8E2C 1-156-5
+always \u8E2D 1-1356-3
+always \u8E2E 145-2345-4
+always \u8E2F 1-156-2
+always \u8E30 1256-2
+always \u8E31 145-25-2
+always \u8E32 145-123456-5
+always \u8E33 12-123456-4
+always \u8E34 235-4
+always \u8E35 1-12346-4
+always \u8E36 145-16-5
+always \u8E37 1-2346-4
+always \u8E38 12-136-4
+always \u8E39 12-2356-5
+always \u8E3A 13-2345-5
+always \u8E3B 13-35-3
+always \u8E3C 124-1346-2
+always \u8E3D 13-1256-4
+always \u8E3E 12345-34-2
+always \u8E3F 125-34-2
+always \u8E40 145-346-2
+always \u8E41 1234-2345-2
+always \u8E42 1245-12356-2
+always \u8E43 1345-25-5
+always \u8E44 124-16-2
+always \u8E45 12-345-4
+always \u8E46 124-1246-4
+always \u8E47 13-2345-4
+always \u8E48 145-146-5
+always \u8E49 245-25-3
+always \u8E4A 15-16-3
+always \u8E4B 124-345-5
+always \u8E4C 245-46-3
+always \u8E4D 1-1236-4
+always \u8E4E 145-2345-3
+always \u8E4F 124-16-2
+always \u8E50 13-16-2
+always \u8E51 1345-346-5
+always \u8E52 1234-1236-2
+always \u8E53 14-234-5
+always \u8E54 125-1236-5
+always \u8E55 135-16-5
+always \u8E56 12-12346-3
+always \u8E57 14-34-5
+always \u8E58 14-246-2
+always \u8E59 245-34-5
+always \u8E5A 124-1346-3
+always \u8E5B 145-2456-5
+always \u8E5C 15-34-5
+always \u8E5D 15-16-4
+always \u8E5E 123-1246-4
+always \u8E5F 13-16-3
+always \u8E60 1-156-2
+always \u8E61 245-46-3
+always \u8E62 145-16-2
+always \u8E63 1234-1236-2
+always \u8E64 125-12346-3
+always \u8E65 14-2345-2
+always \u8E66 135-1356-5
+always \u8E67 125-146-3
+always \u8E68 1345-2345-4
+always \u8E69 135-346-2
+always \u8E6A 124-1246-2
+always \u8E6B 13-1256-2
+always \u8E6C 145-1356-5
+always \u8E6D 245-1356-5
+always \u8E6E 15-2345-3
+always \u8E6F 12345-1236-2
+always \u8E70 12-34-2
+always \u8E71 1-12346-3
+always \u8E72 145-123456-3
+always \u8E73 135-126-3
+always \u8E74 245-34-5
+always \u8E75 245-34-5
+always \u8E76 13-236-2
+always \u8E77 13-236-2
+always \u8E78 14-1456-5
+always \u8E79 124-345-5
+always \u8E7A 245-246-3
+always \u8E7B 245-246-3
+always \u8E7C 1234-34-2
+always \u8E7D 14-246-3
+always \u8E7E 145-123456-3
+always \u8E7F 245-12456-3
+always \u8E80 123-456-5
+always \u8E81 125-146-5
+always \u8E82 124-345-5
+always \u8E83 135-16-5
+always \u8E84 135-16-5
+always \u8E85 1-34-2
+always \u8E86 13-1256-5
+always \u8E87 12-34-2
+always \u8E88 245-246-5
+always \u8E89 145-123456-4
+always \u8E8A 12-12356-2
+always \u8E8B 13-16-3
+always \u8E8C 34-4
+always \u8E8D 236-5
+always \u8E8E 1345-2345-4
+always \u8E8F 14-1456-5
+always \u8E90 14-346-5
+always \u8E91 1-156-2
+always \u8E92 14-16-5
+always \u8E93 1-156-5
+always \u8E94 12-1236-2
+always \u8E95 12-34-2
+always \u8E96 145-12456-5
+always \u8E97 1246-5
+always \u8E98 14-12346-2
+always \u8E99 14-1456-5
+always \u8E9A 15-2345-3
+always \u8E9B 1246-5
+always \u8E9C 125-12456-3
+always \u8E9D 14-1236-2
+always \u8E9E 15-346-5
+always \u8E9F 1245-1346-2
+always \u8EA0 15-345-5
+always \u8EA1 1345-346-5
+always \u8EA2 124-345-5
+always \u8EA3 245-1256-2
+always \u8EA4 13-346-5
+always \u8EA5 245-12456-3
+always \u8EA6 125-12456-3
+always \u8EA7 15-16-4
+always \u8EA8 123-1246-2
+always \u8EA9 13-236-2
+always \u8EAA 14-1456-5
+always \u8EAB 24-136-3
+always \u8EAC 13-12346-3
+always \u8EAD 145-1236-3
+always \u8EAE 12345-136-3
+always \u8EAF 245-1256-3
+always \u8EB0 124-16-4
+always \u8EB1 145-25-4
+always \u8EB2 145-25-4
+always \u8EB3 13-12346-3
+always \u8EB4 14-1346-2
+always \u8EB5 1245-136-4
+always \u8EB6 14-25-4
+always \u8EB7 2456-4
+always \u8EB8 13-16-3
+always \u8EB9 13-1256-2
+always \u8EBA 124-1346-4
+always \u8EBB 123-12346-3
+always \u8EBC 14-146-5
+always \u8EBD 2345-4
+always \u8EBE 1345-356-4
+always \u8EBF 123-1346-3
+always \u8EC0 245-1256-3
+always \u8EC1 14-12356-2
+always \u8EC2 14-146-5
+always \u8EC3 145-25-4
+always \u8EC4 1-156-2
+always \u8EC5 2345-5
+always \u8EC6 124-16-4
+always \u8EC7 145-146-5
+always \u8EC8 13456-3
+always \u8EC9 1256-5
+always \u8ECA 12-2346-3
+always \u8ECB 23456-5
+always \u8ECC 13-1246-4
+always \u8ECD 13-256-3
+always \u8ECE 1246-5
+always \u8ECF 236-5
+always \u8ED0 15-1456-5
+always \u8ED1 145-16-5
+always \u8ED2 15-45-3
+always \u8ED3 12345-1236-5
+always \u8ED4 1245-136-5
+always \u8ED5 24-1236-3
+always \u8ED6 245-46-2
+always \u8ED7 24-34-3
+always \u8ED8 124-123456-2
+always \u8ED9 12-136-2
+always \u8EDA 145-2456-5
+always \u8EDB 2346-5
+always \u8EDC 1345-345-5
+always \u8EDD 245-16-2
+always \u8EDE 134-146-2
+always \u8EDF 1245-12456-4
+always \u8EE0 1245-136-5
+always \u8EE1 245-1456-2
+always \u8EE2 1-12456-4
+always \u8EE3 1235-12346-3
+always \u8EE4 1235-34-3
+always \u8EE5 245-1256-2
+always \u8EE6 1235-456-5
+always \u8EE7 145-16-4
+always \u8EE8 14-13456-2
+always \u8EE9 145-2456-5
+always \u8EEA 146-3
+always \u8EEB 1-136-4
+always \u8EEC 12345-1236-5
+always \u8EED 123-456-3
+always \u8EEE 1346-4
+always \u8EEF 1234-1356-3
+always \u8EF0 135-356-5
+always \u8EF1 13-34-3
+always \u8EF2 13-34-3
+always \u8EF3 1234-146-2
+always \u8EF4 1-34-5
+always \u8EF5 1245-12346-4
+always \u8EF6 2346-5
+always \u8EF7 135-345-2
+always \u8EF8 1-12356-2
+always \u8EF9 1-156-4
+always \u8EFA 246-2
+always \u8EFB 123-2346-3
+always \u8EFC 16-5
+always \u8EFD 245-13456-3
+always \u8EFE 24-156-5
+always \u8EFF 1234-13456-2
+always \u8F00 156-2
+always \u8F01 245-235-2
+always \u8F02 13-1256-2
+always \u8F03 13-246-5
+always \u8F04 13-456-3
+always \u8F05 14-34-5
+always \u8F06 123-2456-4
+always \u8F07 245-45-2
+always \u8F08 1-12356-3
+always \u8F09 125-2456-5
+always \u8F0A 1-156-5
+always \u8F0B 24-2346-2
+always \u8F0C 14-46-5
+always \u8F0D 1256-5
+always \u8F0E 24-146-3
+always \u8F0F 234-2
+always \u8F10 12456-5
+always \u8F11 256-4
+always \u8F12 1-2346-2
+always \u8F13 12456-4
+always \u8F14 12345-34-4
+always \u8F15 245-13456-3
+always \u8F16 1-12356-3
+always \u8F17 1345-16-2
+always \u8F18 14-13456-2
+always \u8F19 1-2346-2
+always \u8F1A 1-1236-5
+always \u8F1B 14-46-5
+always \u8F1C 125-156-3
+always \u8F1D 1235-1246-3
+always \u8F1E 456-4
+always \u8F1F 12-25-5
+always \u8F20 13-25-4
+always \u8F21 123-1236-4
+always \u8F22 16-4
+always \u8F23 1234-1356-2
+always \u8F24 245-2345-5
+always \u8F25 13-123456-4
+always \u8F26 1345-2345-4
+always \u8F27 1234-1356-3
+always \u8F28 13-12456-4
+always \u8F29 135-356-5
+always \u8F2A 14-123456-2
+always \u8F2B 1234-2456-2
+always \u8F2C 14-46-2
+always \u8F2D 1245-12456-4
+always \u8F2E 1245-12356-2
+always \u8F2F 13-16-2
+always \u8F30 46-2
+always \u8F31 15-2345-2
+always \u8F32 12-12456-2
+always \u8F33 245-12356-5
+always \u8F34 12-123456-3
+always \u8F35 13-2346-2
+always \u8F36 234-2
+always \u8F37 1235-12346-3
+always \u8F38 24-34-3
+always \u8F39 12345-34-5
+always \u8F3A 125-156-3
+always \u8F3B 12345-34-2
+always \u8F3C 123456-3
+always \u8F3D 135-136-5
+always \u8F3E 1345-2345-4
+always \u8F3F 1256-2
+always \u8F40 123456-3
+always \u8F41 124-146-3
+always \u8F42 13-34-4
+always \u8F43 1-136-3
+always \u8F44 15-23456-2
+always \u8F45 45-2
+always \u8F46 14-34-5
+always \u8F47 13-234-3
+always \u8F48 12-146-2
+always \u8F49 1-12456-4
+always \u8F4A 1246-5
+always \u8F4B 1235-123456-2
+always \u8F4C 15-236-4
+always \u8F4D 12-2346-5
+always \u8F4E 13-246-5
+always \u8F4F 1-1236-5
+always \u8F50 135-34-2
+always \u8F51 14-146-4
+always \u8F52 12345-136-2
+always \u8F53 12345-1236-3
+always \u8F54 14-1456-2
+always \u8F55 13-2346-2
+always \u8F56 15-2346-5
+always \u8F57 123-1236-4
+always \u8F58 1235-12456-5
+always \u8F59 16-4
+always \u8F5A 13-16-2
+always \u8F5B 145-1246-5
+always \u8F5C 156-2
+always \u8F5D 1256-2
+always \u8F5E 15-2345-5
+always \u8F5F 1235-12346-3
+always \u8F60 14-356-2
+always \u8F61 1234-356-5
+always \u8F62 14-16-5
+always \u8F63 14-16-5
+always \u8F64 14-34-2
+always \u8F65 14-1456-5
+always \u8F66 12-2346-3
+always \u8F67 23456-5
+always \u8F68 13-1246-4
+always \u8F69 15-45-3
+always \u8F6A 145-16-5
+always \u8F6B 1245-136-5
+always \u8F6C 1-12456-4
+always \u8F6D 2346-5
+always \u8F6E 14-123456-2
+always \u8F6F 1245-12456-4
+always \u8F70 1235-12346-3
+always \u8F71 123-34-3
+always \u8F72 123-2346-3
+always \u8F73 14-34-2
+always \u8F74 1-12356-2
+always \u8F75 1-156-4
+always \u8F76 16-5
+always \u8F77 1235-34-3
+always \u8F78 1-136-4
+always \u8F79 14-16-5
+always \u8F7A 246-2
+always \u8F7B 245-13456-3
+always \u8F7C 24-156-5
+always \u8F7D 125-2456-5
+always \u8F7E 1-156-5
+always \u8F7F 13-246-5
+always \u8F80 1-12356-3
+always \u8F81 245-45-2
+always \u8F82 14-34-5
+always \u8F83 13-246-5
+always \u8F84 1-2346-2
+always \u8F85 12345-34-4
+always \u8F86 14-46-5
+always \u8F87 1345-2345-4
+always \u8F88 135-356-5
+always \u8F89 1235-1246-3
+always \u8F8A 13-123456-4
+always \u8F8B 456-4
+always \u8F8C 14-46-2
+always \u8F8D 12-25-5
+always \u8F8E 125-156-3
+always \u8F8F 245-12356-5
+always \u8F90 12345-34-2
+always \u8F91 13-16-2
+always \u8F92 123456-3
+always \u8F93 24-34-3
+always \u8F94 1234-356-5
+always \u8F95 45-2
+always \u8F96 15-23456-2
+always \u8F97 1-1236-4
+always \u8F98 14-34-5
+always \u8F99 1-2346-2
+always \u8F9A 14-1456-2
+always \u8F9B 15-1456-3
+always \u8F9C 13-34-3
+always \u8F9D 245-156-2
+always \u8F9E 245-156-2
+always \u8F9F 1234-16-5
+always \u8FA0 125-1246-5
+always \u8FA1 135-2345-5
+always \u8FA2 14-345-5
+always \u8FA3 14-345-5
+always \u8FA4 245-156-2
+always \u8FA5 15-236-3
+always \u8FA6 135-1236-5
+always \u8FA7 135-2345-5
+always \u8FA8 135-2345-5
+always \u8FA9 135-2345-5
+always \u8FAA 15-236-3
+always \u8FAB 135-2345-5
+always \u8FAC 135-1236-3
+always \u8FAD 245-156-2
+always \u8FAE 135-2345-5
+always \u8FAF 135-2345-5
+always \u8FB0 12-136-2
+always \u8FB1 1245-34-5
+always \u8FB2 1345-12346-2
+always \u8FB3 1345-12346-2
+always \u8FB4 1-136-4
+always \u8FB5 12-25-5
+always \u8FB6 12-25-5
+always \u8FB7 16-3
+always \u8FB8 1245-1356-2
+always \u8FB9 135-2345-3
+always \u8FBA 135-2345-3
+always \u8FBB 24-156-2
+always \u8FBC 1245-34-5
+always \u8FBD 14-246-2
+always \u8FBE 145-345-2
+always \u8FBF 12-1236-3
+always \u8FC0 13-1236-3
+always \u8FC1 245-2345-3
+always \u8FC2 1256-3
+always \u8FC3 1256-3
+always \u8FC4 245-16-5
+always \u8FC5 15-256-5
+always \u8FC6 16-4
+always \u8FC7 13-25-5
+always \u8FC8 134-2456-5
+always \u8FC9 245-16-3
+always \u8FCA 125-345-3
+always \u8FCB 456-5
+always \u8FCC 124-34-5
+always \u8FCD 1-123456-3
+always \u8FCE 13456-2
+always \u8FCF 124-16-5
+always \u8FD0 256-5
+always \u8FD1 13-1456-5
+always \u8FD2 1235-1346-2
+always \u8FD3 23456-5
+always \u8FD4 12345-1236-4
+always \u8FD5 34-4
+always \u8FD6 124-16-5
+always \u8FD7 2346-2
+always \u8FD8 1235-2456-2
+always \u8FD9 1-2346-5
+always \u8FDA 1-12346-3
+always \u8FDB 13-1456-5
+always \u8FDC 45-4
+always \u8FDD 1246-2
+always \u8FDE 14-2345-2
+always \u8FDF 12-156-2
+always \u8FE0 12-2346-5
+always \u8FE1 1345-16-5
+always \u8FE2 124-246-2
+always \u8FE3 12-156-5
+always \u8FE4 16-4
+always \u8FE5 13-235-4
+always \u8FE6 13-23456-3
+always \u8FE7 12-136-2
+always \u8FE8 145-2456-5
+always \u8FE9 156-4
+always \u8FEA 145-16-2
+always \u8FEB 1234-126-5
+always \u8FEC 456-4
+always \u8FED 145-346-2
+always \u8FEE 125-2346-2
+always \u8FEF 124-146-2
+always \u8FF0 24-34-5
+always \u8FF1 124-25-2
+always \u8FF2 245-1256-5
+always \u8FF3 13-13456-5
+always \u8FF4 1235-1246-2
+always \u8FF5 124-12346-2
+always \u8FF6 234-5
+always \u8FF7 134-16-2
+always \u8FF8 135-1356-5
+always \u8FF9 13-16-3
+always \u8FFA 1345-2456-4
+always \u8FFB 16-2
+always \u8FFC 13-346-2
+always \u8FFD 1-1246-3
+always \u8FFE 14-346-5
+always \u8FFF 15-256-5
+always \u9000 124-1246-5
+always \u9001 15-12346-5
+always \u9002 24-156-5
+always \u9003 124-146-2
+always \u9004 1234-1346-2
+always \u9005 13-12356-5
+always \u9006 1345-16-5
+always \u9007 145-123456-5
+always \u9008 13-235-4
+always \u9009 15-45-4
+always \u900A 15-256-5
+always \u900B 135-34-3
+always \u900C 234-2
+always \u900D 15-246-3
+always \u900E 245-234-2
+always \u900F 124-12356-5
+always \u9010 1-34-2
+always \u9011 245-234-2
+always \u9012 145-16-5
+always \u9013 145-16-5
+always \u9014 124-34-2
+always \u9015 13-13456-5
+always \u9016 124-16-5
+always \u9017 145-12356-5
+always \u9018 16-4
+always \u9019 1-2346-5
+always \u901A 124-12346-3
+always \u901B 13-456-5
+always \u901C 34-5
+always \u901D 24-156-5
+always \u901E 12-1356-4
+always \u901F 15-34-5
+always \u9020 125-146-5
+always \u9021 245-256-3
+always \u9022 12345-1356-2
+always \u9023 14-2345-2
+always \u9024 15-25-5
+always \u9025 1235-1246-2
+always \u9026 14-16-4
+always \u9027 13-34-4
+always \u9028 14-2456-2
+always \u9029 135-136-5
+always \u902A 245-25-5
+always \u902B 13-236-2
+always \u902C 135-1356-5
+always \u902D 1235-12456-5
+always \u902E 145-2456-4
+always \u902F 14-34-5
+always \u9030 234-2
+always \u9031 1-12356-3
+always \u9032 13-1456-5
+always \u9033 1256-5
+always \u9034 12-25-3
+always \u9035 123-1246-2
+always \u9036 1246-3
+always \u9037 124-16-5
+always \u9038 16-5
+always \u9039 145-345-2
+always \u903A 45-4
+always \u903B 14-25-2
+always \u903C 135-16-3
+always \u903D 1345-25-5
+always \u903E 1256-2
+always \u903F 145-1346-5
+always \u9040 15-1246-2
+always \u9041 145-123456-5
+always \u9042 15-1246-5
+always \u9043 2345-4
+always \u9044 12-12456-2
+always \u9045 12-156-2
+always \u9046 124-16-2
+always \u9047 1256-5
+always \u9048 24-156-2
+always \u9049 1-136-3
+always \u904A 234-2
+always \u904B 256-5
+always \u904C 2346-5
+always \u904D 135-2345-5
+always \u904E 13-25-5
+always \u904F 2346-5
+always \u9050 15-23456-2
+always \u9051 1235-456-2
+always \u9052 245-234-2
+always \u9053 145-146-5
+always \u9054 145-345-2
+always \u9055 1246-2
+always \u9056 12456-2
+always \u9057 16-2
+always \u9058 13-12356-5
+always \u9059 246-2
+always \u905A 12-34-5
+always \u905B 14-234-5
+always \u905C 15-256-5
+always \u905D 124-345-5
+always \u905E 145-16-5
+always \u905F 12-156-2
+always \u9060 45-4
+always \u9061 15-34-5
+always \u9062 124-345-5
+always \u9063 245-2345-4
+always \u9064 134-345-4
+always \u9065 246-2
+always \u9066 13-12456-5
+always \u9067 1-1346-3
+always \u9068 146-2
+always \u9069 24-156-5
+always \u906A 245-2346-5
+always \u906B 12-156-5
+always \u906C 15-34-5
+always \u906D 125-146-3
+always \u906E 1-2346-3
+always \u906F 145-123456-5
+always \u9070 145-16-5
+always \u9071 14-12356-2
+always \u9072 12-156-2
+always \u9073 245-25-3
+always \u9074 14-1456-2
+always \u9075 125-123456-3
+always \u9076 1245-146-5
+always \u9077 245-2345-3
+always \u9078 15-45-4
+always \u9079 1256-5
+always \u907A 16-2
+always \u907B 2346-5
+always \u907C 14-246-2
+always \u907D 13-1256-5
+always \u907E 24-156-5
+always \u907F 135-16-5
+always \u9080 246-3
+always \u9081 134-2456-5
+always \u9082 15-346-5
+always \u9083 15-1246-5
+always \u9084 1235-2456-2
+always \u9085 1-1236-3
+always \u9086 124-1356-2
+always \u9087 156-4
+always \u9088 134-246-4
+always \u9089 135-2345-3
+always \u908A 135-2345-3
+always \u908B 14-345-3
+always \u908C 14-16-2
+always \u908D 45-2
+always \u908E 246-2
+always \u908F 14-25-2
+always \u9090 14-16-4
+always \u9091 16-5
+always \u9092 124-13456-2
+always \u9093 145-1356-5
+always \u9094 245-16-4
+always \u9095 235-3
+always \u9096 24-1236-3
+always \u9097 1235-1236-2
+always \u9098 1256-2
+always \u9099 134-1346-2
+always \u909A 1245-34-2
+always \u909B 245-235-2
+always \u909C 134-146-4
+always \u909D 123-456-5
+always \u909E 12345-34-3
+always \u909F 123-1346-5
+always \u90A0 135-1456-3
+always \u90A1 12345-1346-3
+always \u90A2 15-13456-2
+always \u90A3 1345-345-5
+always \u90A4 15-1456-3
+always \u90A5 24-136-4
+always \u90A6 135-1346-3
+always \u90A7 45-2
+always \u90A8 245-123456-3
+always \u90A9 1235-25-4
+always \u90AA 15-346-2
+always \u90AB 135-1346-3
+always \u90AC 34-3
+always \u90AD 13-1256-5
+always \u90AE 234-2
+always \u90AF 1235-1236-2
+always \u90B0 124-2456-2
+always \u90B1 245-234-3
+always \u90B2 135-16-5
+always \u90B3 1234-16-3
+always \u90B4 135-13456-4
+always \u90B5 24-146-5
+always \u90B6 135-356-5
+always \u90B7 35-4
+always \u90B8 145-16-4
+always \u90B9 125-12356-3
+always \u90BA 346-5
+always \u90BB 14-1456-2
+always \u90BC 123-456-3
+always \u90BD 13-1246-3
+always \u90BE 1-34-3
+always \u90BF 24-156-3
+always \u90C0 123-34-3
+always \u90C1 1256-5
+always \u90C2 13-2456-3
+always \u90C3 1235-2346-2
+always \u90C4 15-346-5
+always \u90C5 1-156-5
+always \u90C6 13-16-2
+always \u90C7 15-256-2
+always \u90C8 1235-12356-5
+always \u90C9 15-13456-2
+always \u90CA 13-246-3
+always \u90CB 15-16-2
+always \u90CC 13-1246-3
+always \u90CD 1345-25-2
+always \u90CE 14-1346-2
+always \u90CF 13-23456-2
+always \u90D0 123-2356-5
+always \u90D1 1-1356-5
+always \u90D2 14-1346-2
+always \u90D3 256-5
+always \u90D4 2345-2
+always \u90D5 12-1356-2
+always \u90D6 145-12356-3
+always \u90D7 15-16-3
+always \u90D8 14-1256-4
+always \u90D9 12345-34-4
+always \u90DA 34-2
+always \u90DB 12345-34-2
+always \u90DC 13-146-5
+always \u90DD 1235-146-4
+always \u90DE 14-1346-2
+always \u90DF 13-23456-2
+always \u90E0 13-1356-4
+always \u90E1 13-256-5
+always \u90E2 13456-4
+always \u90E3 135-126-2
+always \u90E4 15-16-5
+always \u90E5 135-356-5
+always \u90E6 14-16-5
+always \u90E7 256-2
+always \u90E8 135-34-5
+always \u90E9 15-246-2
+always \u90EA 245-16-3
+always \u90EB 1234-16-2
+always \u90EC 245-13456-3
+always \u90ED 13-25-3
+always \u90EE 1-12356-3
+always \u90EF 124-1236-2
+always \u90F0 125-12356-3
+always \u90F1 1234-13456-2
+always \u90F2 14-2456-2
+always \u90F3 1345-16-2
+always \u90F4 12-136-3
+always \u90F5 234-2
+always \u90F6 135-34-5
+always \u90F7 15-46-3
+always \u90F8 145-1236-3
+always \u90F9 13-1256-2
+always \u90FA 235-3
+always \u90FB 245-246-3
+always \u90FC 16-3
+always \u90FD 145-12356-3
+always \u90FE 2345-4
+always \u90FF 134-356-2
+always \u9100 1245-25-5
+always \u9101 135-356-5
+always \u9102 2346-5
+always \u9103 1256-2
+always \u9104 13-45-5
+always \u9105 1256-4
+always \u9106 256-5
+always \u9107 1235-12356-5
+always \u9108 123-1246-2
+always \u9109 15-46-3
+always \u910A 15-46-3
+always \u910B 15-12356-3
+always \u910C 124-1346-2
+always \u910D 134-13456-2
+always \u910E 15-16-3
+always \u910F 1245-34-5
+always \u9110 12-34-5
+always \u9111 125-156-3
+always \u9112 125-12356-3
+always \u9113 13-1256-2
+always \u9114 34-3
+always \u9115 15-46-3
+always \u9116 256-2
+always \u9117 1235-146-5
+always \u9118 235-3
+always \u9119 135-16-4
+always \u911A 134-126-5
+always \u911B 12-146-2
+always \u911C 12345-34-3
+always \u911D 14-246-4
+always \u911E 1456-2
+always \u911F 1-12456-3
+always \u9120 1235-34-5
+always \u9121 245-246-3
+always \u9122 2345-3
+always \u9123 1-1346-3
+always \u9124 134-1236-5
+always \u9125 245-246-3
+always \u9126 15-1256-4
+always \u9127 145-1356-5
+always \u9128 135-16-5
+always \u9129 15-256-2
+always \u912A 135-16-5
+always \u912B 245-1356-2
+always \u912C 1246-2
+always \u912D 1-1356-5
+always \u912E 134-146-5
+always \u912F 24-1236-5
+always \u9130 14-1456-2
+always \u9131 1234-126-2
+always \u9132 145-1236-3
+always \u9133 134-1356-2
+always \u9134 346-5
+always \u9135 245-146-3
+always \u9136 123-2356-5
+always \u9137 12345-1356-3
+always \u9138 134-1356-2
+always \u9139 125-12356-3
+always \u913A 123-456-5
+always \u913B 14-2345-4
+always \u913C 125-1236-5
+always \u913D 12-1236-2
+always \u913E 234-3
+always \u913F 245-16-2
+always \u9140 2345-3
+always \u9141 12-1236-2
+always \u9142 125-1236-5
+always \u9143 14-13456-2
+always \u9144 1235-12456-3
+always \u9145 15-16-3
+always \u9146 12345-1356-3
+always \u9147 125-1236-5
+always \u9148 14-16-5
+always \u9149 234-4
+always \u914A 145-13456-4
+always \u914B 245-234-2
+always \u914C 1-25-2
+always \u914D 1234-356-5
+always \u914E 1-12356-5
+always \u914F 1256-4
+always \u9150 13-1236-3
+always \u9151 1256-4
+always \u9152 13-234-4
+always \u9153 2345-4
+always \u9154 125-1246-5
+always \u9155 134-146-2
+always \u9156 145-1236-3
+always \u9157 15-1256-5
+always \u9158 124-12356-2
+always \u9159 1-136-3
+always \u915A 12345-136-3
+always \u915B 45-2
+always \u915C 12345-34-3
+always \u915D 256-5
+always \u915E 124-2456-5
+always \u915F 124-2345-3
+always \u9160 245-23456-4
+always \u9161 124-25-2
+always \u9162 125-25-5
+always \u9163 1235-1236-3
+always \u9164 13-34-3
+always \u9165 15-34-3
+always \u9166 1234-126-5
+always \u9167 12-12356-2
+always \u9168 125-2456-5
+always \u9169 134-13456-4
+always \u916A 14-25-5
+always \u916B 12-25-5
+always \u916C 12-12356-2
+always \u916D 234-5
+always \u916E 124-12346-2
+always \u916F 1-156-4
+always \u9170 15-2345-3
+always \u9171 13-46-5
+always \u9172 12-1356-2
+always \u9173 1456-5
+always \u9174 124-34-2
+always \u9175 15-246-5
+always \u9176 134-356-2
+always \u9177 123-34-5
+always \u9178 15-12456-3
+always \u9179 14-356-5
+always \u917A 1234-34-2
+always \u917B 125-1246-5
+always \u917C 1235-2456-4
+always \u917D 2345-5
+always \u917E 24-2456-3
+always \u917F 1345-46-5
+always \u9180 1246-2
+always \u9181 14-34-5
+always \u9182 14-1236-4
+always \u9183 2345-3
+always \u9184 124-146-2
+always \u9185 1234-356-3
+always \u9186 1-1236-4
+always \u9187 12-123456-2
+always \u9188 124-1236-2
+always \u9189 125-1246-5
+always \u918A 1-1246-5
+always \u918B 245-34-5
+always \u918C 123-123456-3
+always \u918D 124-16-2
+always \u918E 15-2345-2
+always \u918F 145-34-3
+always \u9190 1235-34-2
+always \u9191 15-1256-4
+always \u9192 15-13456-4
+always \u9193 124-1236-4
+always \u9194 13-234-3
+always \u9195 12-123456-2
+always \u9196 256-5
+always \u9197 1234-126-5
+always \u9198 123-2346-5
+always \u9199 15-12356-3
+always \u919A 134-16-2
+always \u919B 245-45-2
+always \u919C 12-12356-4
+always \u919D 245-25-2
+always \u919E 256-5
+always \u919F 235-5
+always \u91A0 1346-5
+always \u91A1 1-345-5
+always \u91A2 1235-2456-4
+always \u91A3 124-1346-2
+always \u91A4 13-46-5
+always \u91A5 1234-246-4
+always \u91A6 24-1236-4
+always \u91A7 1256-5
+always \u91A8 14-16-2
+always \u91A9 125-146-2
+always \u91AA 14-146-2
+always \u91AB 16-3
+always \u91AC 13-46-5
+always \u91AD 1234-34-2
+always \u91AE 13-246-5
+always \u91AF 15-16-3
+always \u91B0 124-1236-2
+always \u91B1 1234-126-5
+always \u91B2 1345-12346-2
+always \u91B3 16-5
+always \u91B4 14-16-4
+always \u91B5 13-1256-5
+always \u91B6 13-246-5
+always \u91B7 16-5
+always \u91B8 1345-46-5
+always \u91B9 1245-34-2
+always \u91BA 15-256-3
+always \u91BB 12-12356-2
+always \u91BC 2345-5
+always \u91BD 14-13456-2
+always \u91BE 134-16-2
+always \u91BF 134-16-2
+always \u91C0 1345-46-5
+always \u91C1 15-1456-5
+always \u91C2 13-246-5
+always \u91C3 24-156-3
+always \u91C4 134-16-2
+always \u91C5 2345-5
+always \u91C6 135-2345-5
+always \u91C7 245-2456-4
+always \u91C8 24-156-5
+always \u91C9 234-5
+always \u91CA 24-156-5
+always \u91CB 24-156-5
+always \u91CC 14-16-4
+always \u91CD 1-12346-5
+always \u91CE 346-4
+always \u91CF 14-46-5
+always \u91D0 14-16-2
+always \u91D1 13-1456-3
+always \u91D2 13-1456-3
+always \u91D3 13-345-3
+always \u91D4 16-4
+always \u91D5 14-246-4
+always \u91D6 145-146-3
+always \u91D7 1-146-3
+always \u91D8 145-13456-3
+always \u91D9 1234-126-5
+always \u91DA 245-234-2
+always \u91DB 1235-2346-2
+always \u91DC 12345-34-4
+always \u91DD 1-136-3
+always \u91DE 1-156-2
+always \u91DF 135-345-3
+always \u91E0 14-12456-5
+always \u91E1 12345-34-4
+always \u91E2 1345-2456-4
+always \u91E3 145-246-5
+always \u91E4 24-1236-3
+always \u91E5 245-246-4
+always \u91E6 123-12356-5
+always \u91E7 12-12456-5
+always \u91E8 125-156-4
+always \u91E9 12345-1236-2
+always \u91EA 1256-2
+always \u91EB 1235-35-2
+always \u91EC 1235-1236-5
+always \u91ED 13-1346-3
+always \u91EE 245-16-2
+always \u91EF 134-1346-2
+always \u91F0 1245-136-5
+always \u91F1 145-16-5
+always \u91F2 15-156-5
+always \u91F3 15-16-5
+always \u91F4 16-5
+always \u91F5 12-2456-3
+always \u91F6 24-156-3
+always \u91F7 124-34-4
+always \u91F8 15-16-5
+always \u91F9 1345-1256-4
+always \u91FA 245-2345-3
+always \u91FB 245-234-2
+always \u91FC 13-2345-5
+always \u91FD 1234-16-3
+always \u91FE 346-2
+always \u91FF 1456-2
+always \u9200 135-345-4
+always \u9201 12345-1346-3
+always \u9202 12-136-2
+always \u9203 15-13456-2
+always \u9204 124-12356-4
+always \u9205 236-5
+always \u9206 245-2345-3
+always \u9207 12345-34-3
+always \u9208 135-34-5
+always \u9209 1345-345-5
+always \u920A 15-1456-3
+always \u920B 2346-2
+always \u920C 13-236-2
+always \u920D 145-123456-5
+always \u920E 13-12356-3
+always \u920F 1456-4
+always \u9210 245-2345-2
+always \u9211 135-1236-4
+always \u9212 15-2346-5
+always \u9213 1245-136-2
+always \u9214 12-146-3
+always \u9215 1345-234-4
+always \u9216 12345-136-3
+always \u9217 256-4
+always \u9218 13-16-4
+always \u9219 245-1456-2
+always \u921A 1234-16-2
+always \u921B 13-25-3
+always \u921C 1235-12346-2
+always \u921D 1456-2
+always \u921E 13-256-3
+always \u921F 24-156-3
+always \u9220 16-5
+always \u9221 1-12346-3
+always \u9222 1345-346-3
+always \u9223 13-2456-5
+always \u9224 1245-156-5
+always \u9225 1235-25-4
+always \u9226 124-2456-5
+always \u9227 123-1346-5
+always \u9228 45-2
+always \u9229 14-34-2
+always \u922A 2346-5
+always \u922B 245-1456-2
+always \u922C 145-25-2
+always \u922D 125-156-3
+always \u922E 1345-16-2
+always \u922F 124-34-2
+always \u9230 24-156-5
+always \u9231 134-1456-2
+always \u9232 13-34-3
+always \u9233 123-2346-3
+always \u9234 14-13456-2
+always \u9235 135-13456-5
+always \u9236 15-156-5
+always \u9237 13-34-3
+always \u9238 135-126-2
+always \u9239 1234-16-2
+always \u923A 1256-5
+always \u923B 15-156-5
+always \u923C 125-25-2
+always \u923D 135-34-5
+always \u923E 234-2
+always \u923F 145-2345-5
+always \u9240 13-23456-4
+always \u9241 1-136-3
+always \u9242 24-156-4
+always \u9243 24-156-5
+always \u9244 124-346-4
+always \u9245 13-1256-5
+always \u9246 125-12456-3
+always \u9247 24-156-3
+always \u9248 124-345-4
+always \u9249 15-45-5
+always \u924A 1-146-3
+always \u924B 135-146-5
+always \u924C 1235-2346-2
+always \u924D 135-16-5
+always \u924E 24-1356-3
+always \u924F 12-34-2
+always \u9250 24-156-2
+always \u9251 135-126-2
+always \u9252 1-34-5
+always \u9253 12-156-5
+always \u9254 125-345-3
+always \u9255 1234-126-4
+always \u9256 124-12346-2
+always \u9257 245-2345-2
+always \u9258 12345-34-2
+always \u9259 1-2456-4
+always \u925A 134-146-4
+always \u925B 245-2345-3
+always \u925C 12345-34-2
+always \u925D 14-16-5
+always \u925E 236-5
+always \u925F 1234-16-3
+always \u9260 46-3
+always \u9261 135-1236-5
+always \u9262 135-126-3
+always \u9263 13-346-2
+always \u9264 13-12356-3
+always \u9265 24-34-5
+always \u9266 1-1356-3
+always \u9267 134-34-4
+always \u9268 1345-16-4
+always \u9269 15-16-4
+always \u926A 145-16-5
+always \u926B 13-23456-3
+always \u926C 134-34-5
+always \u926D 145-1236-5
+always \u926E 24-136-3
+always \u926F 16-4
+always \u9270 15-156-3
+always \u9271 123-456-5
+always \u9272 123-345-4
+always \u9273 135-356-4
+always \u9274 13-2345-5
+always \u9275 124-12346-2
+always \u9276 15-13456-2
+always \u9277 1235-12346-2
+always \u9278 13-246-4
+always \u9279 12-156-4
+always \u927A 156-4
+always \u927B 13-2346-5
+always \u927C 135-13456-4
+always \u927D 24-156-5
+always \u927E 134-12356-2
+always \u927F 1235-345-3
+always \u9280 1456-2
+always \u9281 13-256-3
+always \u9282 1-12356-3
+always \u9283 12-12346-5
+always \u9284 24-1346-5
+always \u9285 124-12346-2
+always \u9286 134-126-5
+always \u9287 14-356-5
+always \u9288 13-16-3
+always \u9289 1256-5
+always \u928A 15-1256-5
+always \u928B 1245-136-2
+always \u928C 125-123456-5
+always \u928D 1-156-5
+always \u928E 245-235-2
+always \u928F 24-1236-5
+always \u9290 12-156-5
+always \u9291 15-2345-4
+always \u9292 15-13456-2
+always \u9293 245-45-2
+always \u9294 1234-16-3
+always \u9295 124-346-4
+always \u9296 1-34-3
+always \u9297 15-46-5
+always \u9298 134-13456-2
+always \u9299 123-35-4
+always \u929A 145-246-5
+always \u929B 15-2345-3
+always \u929C 15-2345-2
+always \u929D 15-234-3
+always \u929E 13-256-3
+always \u929F 12-345-3
+always \u92A0 14-146-4
+always \u92A1 13-16-2
+always \u92A2 1234-16-4
+always \u92A3 1245-34-2
+always \u92A4 134-16-4
+always \u92A5 16-3
+always \u92A6 1456-3
+always \u92A7 13-456-3
+always \u92A8 1236-3
+always \u92A9 145-234-3
+always \u92AA 234-4
+always \u92AB 15-2346-5
+always \u92AC 123-146-5
+always \u92AD 245-2345-2
+always \u92AE 14-12456-2
+always \u92AF 15-156-3
+always \u92B0 2456-5
+always \u92B1 145-246-5
+always \u92B2 1235-1236-5
+always \u92B3 1245-1246-5
+always \u92B4 24-156-5
+always \u92B5 123-1356-3
+always \u92B6 245-234-2
+always \u92B7 15-246-3
+always \u92B8 1-2346-2
+always \u92B9 15-234-5
+always \u92BA 125-1346-5
+always \u92BB 124-16-3
+always \u92BC 245-25-5
+always \u92BD 13-35-3
+always \u92BE 13-12346-4
+always \u92BF 1-12346-3
+always \u92C0 145-12356-5
+always \u92C1 14-1256-4
+always \u92C2 134-356-2
+always \u92C3 14-1346-2
+always \u92C4 12456-4
+always \u92C5 15-1456-3
+always \u92C6 256-2
+always \u92C7 135-356-5
+always \u92C8 34-5
+always \u92C9 15-34-5
+always \u92CA 1256-5
+always \u92CB 12-1236-2
+always \u92CC 124-13456-4
+always \u92CD 135-126-2
+always \u92CE 1235-1236-5
+always \u92CF 13-23456-2
+always \u92D0 1235-12346-2
+always \u92D1 13-45-3
+always \u92D2 12345-1356-3
+always \u92D3 12-1236-3
+always \u92D4 12456-4
+always \u92D5 1-156-5
+always \u92D6 15-156-3
+always \u92D7 15-45-3
+always \u92D8 1235-35-2
+always \u92D9 34-2
+always \u92DA 124-246-2
+always \u92DB 13-12346-4
+always \u92DC 12-25-5
+always \u92DD 14-236-5
+always \u92DE 15-13456-2
+always \u92DF 245-1456-4
+always \u92E0 24-136-5
+always \u92E1 1235-1236-2
+always \u92E2 14-236-5
+always \u92E3 346-2
+always \u92E4 12-34-2
+always \u92E5 1-1356-5
+always \u92E6 13-1256-2
+always \u92E7 15-2345-5
+always \u92E8 2346-2
+always \u92E9 134-1346-2
+always \u92EA 1234-34-3
+always \u92EB 14-16-2
+always \u92EC 1234-1236-5
+always \u92ED 1245-1246-5
+always \u92EE 12-1356-2
+always \u92EF 13-146-5
+always \u92F0 14-16-4
+always \u92F1 124-2346-5
+always \u92F2 1456-3
+always \u92F3 1-34-5
+always \u92F4 1-136-5
+always \u92F5 124-34-3
+always \u92F6 14-234-4
+always \u92F7 125-1246-5
+always \u92F8 13-1256-5
+always \u92F9 12-1346-4
+always \u92FA 45-3
+always \u92FB 13-2345-5
+always \u92FC 13-1346-3
+always \u92FD 145-246-5
+always \u92FE 124-146-2
+always \u92FF 12-1346-2
+always \u9300 14-123456-2
+always \u9301 123-2346-5
+always \u9302 14-13456-2
+always \u9303 135-356-3
+always \u9304 14-34-5
+always \u9305 14-16-2
+always \u9306 245-46-3
+always \u9307 1234-356-2
+always \u9308 13-45-4
+always \u9309 134-1456-2
+always \u930A 125-1246-5
+always \u930B 1234-1356-2
+always \u930C 1236-5
+always \u930D 1234-16-2
+always \u930E 15-2345-5
+always \u930F 23456-5
+always \u9310 1-1246-3
+always \u9311 14-356-5
+always \u9312 345-3
+always \u9313 123-12346-3
+always \u9314 124-345-5
+always \u9315 123-123456-3
+always \u9316 145-34-3
+always \u9317 1246-5
+always \u9318 12-1246-2
+always \u9319 125-156-3
+always \u931A 1-1356-3
+always \u931B 135-136-3
+always \u931C 1345-346-3
+always \u931D 245-12346-2
+always \u931E 145-1246-5
+always \u931F 124-1236-2
+always \u9320 145-13456-5
+always \u9321 245-16-2
+always \u9322 245-2345-2
+always \u9323 1-1246-5
+always \u9324 13-16-3
+always \u9325 1256-5
+always \u9326 13-1456-4
+always \u9327 13-12456-4
+always \u9328 134-146-2
+always \u9329 12-1346-3
+always \u932A 124-2345-4
+always \u932B 15-16-2
+always \u932C 14-2345-5
+always \u932D 145-246-3
+always \u932E 13-34-5
+always \u932F 245-25-5
+always \u9330 24-34-5
+always \u9331 1-136-3
+always \u9332 14-34-5
+always \u9333 134-1356-4
+always \u9334 14-34-5
+always \u9335 1235-35-3
+always \u9336 135-246-4
+always \u9337 13-345-2
+always \u9338 14-2456-2
+always \u9339 123-136-4
+always \u933A 12345-1236-3
+always \u933B 34-4
+always \u933C 1345-2456-5
+always \u933D 12456-4
+always \u933E 125-1236-5
+always \u933F 34-4
+always \u9340 145-2346-2
+always \u9341 15-2345-3
+always \u9342 1234-2345-3
+always \u9343 1235-25-5
+always \u9344 14-46-5
+always \u9345 12345-345-4
+always \u9346 134-136-2
+always \u9347 123-2456-4
+always \u9348 46-3
+always \u9349 24-156-2
+always \u934A 14-2345-5
+always \u934B 13-25-3
+always \u934C 15-2345-4
+always \u934D 145-34-5
+always \u934E 124-34-2
+always \u934F 1246-2
+always \u9350 125-12346-3
+always \u9351 12345-34-5
+always \u9352 1245-12356-2
+always \u9353 13-16-2
+always \u9354 2346-5
+always \u9355 13-256-3
+always \u9356 12-136-4
+always \u9357 124-16-2
+always \u9358 1-345-2
+always \u9359 1235-12346-5
+always \u935A 46-2
+always \u935B 145-12456-5
+always \u935C 15-23456-2
+always \u935D 1256-2
+always \u935E 123-1356-3
+always \u935F 15-13456-3
+always \u9360 1235-456-2
+always \u9361 1246-4
+always \u9362 12345-34-5
+always \u9363 1-146-3
+always \u9364 12-345-3
+always \u9365 245-346-5
+always \u9366 24-2346-2
+always \u9367 1235-12346-3
+always \u9368 123-1246-2
+always \u9369 1345-25-5
+always \u936A 134-12356-2
+always \u936B 245-246-3
+always \u936C 245-246-3
+always \u936D 1235-12356-2
+always \u936E 124-12356-3
+always \u936F 245-12346-3
+always \u9370 1235-12456-2
+always \u9371 346-5
+always \u9372 134-1456-2
+always \u9373 13-2345-5
+always \u9374 145-12456-3
+always \u9375 13-2345-5
+always \u9376 15-156-3
+always \u9377 123-1246-3
+always \u9378 1235-34-2
+always \u9379 15-45-3
+always \u937A 1-2346-4
+always \u937B 13-346-2
+always \u937C 1-136-3
+always \u937D 135-2345-3
+always \u937E 1-12346-3
+always \u937F 125-156-3
+always \u9380 15-234-3
+always \u9381 346-2
+always \u9382 134-356-4
+always \u9383 1234-2456-5
+always \u9384 2456-3
+always \u9385 13-346-5
+always \u9386 245-2345-2
+always \u9387 134-356-2
+always \u9388 12-25-3
+always \u9389 124-345-5
+always \u938A 135-1346-5
+always \u938B 15-23456-2
+always \u938C 14-2345-2
+always \u938D 15-25-4
+always \u938E 15-16-5
+always \u938F 14-234-2
+always \u9390 125-34-2
+always \u9391 346-5
+always \u9392 1345-12356-5
+always \u9393 12346-3
+always \u9394 1245-12346-2
+always \u9395 124-1346-2
+always \u9396 15-25-4
+always \u9397 245-46-3
+always \u9398 13-2346-2
+always \u9399 24-25-5
+always \u939A 12-1246-2
+always \u939B 135-126-2
+always \u939C 1234-1236-2
+always \u939D 124-345-4
+always \u939E 135-16-5
+always \u939F 15-1346-4
+always \u93A0 13-1346-3
+always \u93A1 125-156-3
+always \u93A2 34-3
+always \u93A3 13456-2
+always \u93A4 1235-456-4
+always \u93A5 124-246-2
+always \u93A6 14-234-2
+always \u93A7 123-2456-4
+always \u93A8 15-123456-4
+always \u93A9 24-345-3
+always \u93AA 15-12356-3
+always \u93AB 12456-5
+always \u93AC 13-146-4
+always \u93AD 1-136-5
+always \u93AE 1-136-5
+always \u93AF 14-25-4
+always \u93B0 16-5
+always \u93B1 45-2
+always \u93B2 124-1346-4
+always \u93B3 1345-346-5
+always \u93B4 15-16-2
+always \u93B5 13-23456-3
+always \u93B6 13-2346-3
+always \u93B7 134-345-4
+always \u93B8 13-45-3
+always \u93B9 15-12346-5
+always \u93BA 125-34-4
+always \u93BB 15-25-4
+always \u93BC 15-46-5
+always \u93BD 12345-1356-3
+always \u93BE 123456-3
+always \u93BF 1345-345-2
+always \u93C0 14-34-4
+always \u93C1 15-25-4
+always \u93C2 12356-3
+always \u93C3 125-34-2
+always \u93C4 124-12456-2
+always \u93C5 15-234-3
+always \u93C6 13-12456-5
+always \u93C7 15-45-2
+always \u93C8 14-2345-5
+always \u93C9 24-12356-5
+always \u93CA 146-5
+always \u93CB 134-1236-4
+always \u93CC 134-126-5
+always \u93CD 14-25-2
+always \u93CE 135-16-5
+always \u93CF 1246-5
+always \u93D0 14-234-2
+always \u93D1 145-16-2
+always \u93D2 245-246-3
+always \u93D3 245-12346-3
+always \u93D4 16-2
+always \u93D5 14-34-5
+always \u93D6 146-2
+always \u93D7 123-1356-3
+always \u93D8 245-46-3
+always \u93D9 245-1246-3
+always \u93DA 245-16-5
+always \u93DB 12-1346-2
+always \u93DC 124-1346-3
+always \u93DD 134-1236-5
+always \u93DE 235-3
+always \u93DF 12-1236-4
+always \u93E0 12345-1356-3
+always \u93E1 13-13456-5
+always \u93E2 135-246-3
+always \u93E3 24-34-5
+always \u93E4 14-12356-5
+always \u93E5 15-234-5
+always \u93E6 245-12346-3
+always \u93E7 14-12346-2
+always \u93E8 125-1236-5
+always \u93E9 13-2345-5
+always \u93EA 245-146-2
+always \u93EB 14-16-2
+always \u93EC 15-23456-5
+always \u93ED 15-16-3
+always \u93EE 123-1346-3
+always \u93EF 24-456-4
+always \u93F0 135-1356-5
+always \u93F1 1-1346-3
+always \u93F2 245-2345-3
+always \u93F3 1-1356-3
+always \u93F4 14-34-5
+always \u93F5 1235-35-2
+always \u93F6 13-16-2
+always \u93F7 1234-34-2
+always \u93F8 1235-1246-5
+always \u93F9 245-46-4
+always \u93FA 1234-126-3
+always \u93FB 14-1456-2
+always \u93FC 15-25-4
+always \u93FD 15-234-5
+always \u93FE 15-1236-4
+always \u93FF 12-1356-3
+always \u9400 123-1246-5
+always \u9401 15-156-3
+always \u9402 14-234-5
+always \u9403 1345-146-2
+always \u9404 1235-1356-2
+always \u9405 1234-346-4
+always \u9406 15-1246-5
+always \u9407 12345-1236-2
+always \u9408 245-246-2
+always \u9409 245-45-3
+always \u940A 46-2
+always \u940B 124-1346-5
+always \u940C 15-46-5
+always \u940D 13-236-2
+always \u940E 13-246-3
+always \u940F 125-123456-3
+always \u9410 14-246-5
+always \u9411 13-346-2
+always \u9412 14-146-2
+always \u9413 145-123456-3
+always \u9414 124-1236-2
+always \u9415 125-1236-3
+always \u9416 13-16-3
+always \u9417 13-2345-4
+always \u9418 1-12346-3
+always \u9419 145-1356-5
+always \u941A 23456-5
+always \u941B 13456-5
+always \u941C 145-123456-3
+always \u941D 13-236-2
+always \u941E 1345-12356-5
+always \u941F 124-16-5
+always \u9420 1234-34-4
+always \u9421 124-346-4
+always \u9422 346-4
+always \u9423 12-1356-3
+always \u9424 145-13456-4
+always \u9425 24-1236-5
+always \u9426 123-2456-3
+always \u9427 13-2345-5
+always \u9428 12345-356-5
+always \u9429 15-1246-5
+always \u942A 14-34-4
+always \u942B 13-45-3
+always \u942C 1235-1246-5
+always \u942D 1256-5
+always \u942E 14-2345-2
+always \u942F 1-25-2
+always \u9430 245-246-3
+always \u9431 245-2345-3
+always \u9432 1-25-2
+always \u9433 14-356-2
+always \u9434 135-16-5
+always \u9435 124-346-4
+always \u9436 1235-12456-2
+always \u9437 346-5
+always \u9438 145-25-2
+always \u9439 13-25-4
+always \u943A 145-1346-3
+always \u943B 13-1256-5
+always \u943C 12345-136-2
+always \u943D 145-345-2
+always \u943E 135-356-5
+always \u943F 16-5
+always \u9440 2456-5
+always \u9441 125-12346-3
+always \u9442 15-256-5
+always \u9443 145-246-5
+always \u9444 1-34-5
+always \u9445 1235-1356-2
+always \u9446 1-1246-5
+always \u9447 13-16-3
+always \u9448 1345-346-3
+always \u9449 1235-2346-2
+always \u944A 1235-25-5
+always \u944B 245-13456-3
+always \u944C 135-1456-3
+always \u944D 13456-3
+always \u944E 123-1246-5
+always \u944F 1345-13456-2
+always \u9450 15-1256-3
+always \u9451 13-2345-5
+always \u9452 13-2345-5
+always \u9453 245-2345-4
+always \u9454 12-345-4
+always \u9455 1-156-5
+always \u9456 134-346-5
+always \u9457 14-16-2
+always \u9458 14-356-2
+always \u9459 13-16-3
+always \u945A 125-12456-3
+always \u945B 123-456-5
+always \u945C 24-1346-5
+always \u945D 1234-1356-2
+always \u945E 14-345-5
+always \u945F 145-34-2
+always \u9460 24-25-5
+always \u9461 12-25-5
+always \u9462 14-1256-5
+always \u9463 135-246-3
+always \u9464 135-146-5
+always \u9465 14-34-4
+always \u9466 15-2345-4
+always \u9467 123-12456-3
+always \u9468 14-12346-2
+always \u9469 2346-5
+always \u946A 14-34-2
+always \u946B 15-1456-3
+always \u946C 13-2345-5
+always \u946D 14-1236-2
+always \u946E 135-126-2
+always \u946F 13-2345-3
+always \u9470 246-5
+always \u9471 12-1236-2
+always \u9472 15-46-3
+always \u9473 13-2345-5
+always \u9474 15-16-3
+always \u9475 13-12456-5
+always \u9476 245-1346-2
+always \u9477 1345-346-5
+always \u9478 14-356-4
+always \u9479 245-12456-3
+always \u947A 245-1256-2
+always \u947B 1234-1236-5
+always \u947C 14-25-2
+always \u947D 125-12456-3
+always \u947E 14-12456-2
+always \u947F 125-146-2
+always \u9480 1345-346-5
+always \u9481 13-236-2
+always \u9482 124-1346-4
+always \u9483 1-34-2
+always \u9484 14-1236-2
+always \u9485 13-1456-3
+always \u9486 145-345-2
+always \u9487 16-4
+always \u9488 1-136-3
+always \u9489 145-13456-3
+always \u948A 1-146-3
+always \u948B 1234-126-5
+always \u948C 14-246-4
+always \u948D 124-34-4
+always \u948E 245-2345-3
+always \u948F 12-12456-5
+always \u9490 24-1236-3
+always \u9491 15-2346-5
+always \u9492 12345-1236-2
+always \u9493 145-246-5
+always \u9494 134-136-2
+always \u9495 1345-1256-4
+always \u9496 46-2
+always \u9497 12-2456-3
+always \u9498 15-13456-2
+always \u9499 13-2456-5
+always \u949A 135-34-5
+always \u949B 124-2456-5
+always \u949C 13-1256-5
+always \u949D 145-123456-5
+always \u949E 12-146-3
+always \u949F 1-12346-3
+always \u94A0 1345-345-5
+always \u94A1 135-356-5
+always \u94A2 13-1346-3
+always \u94A3 135-1236-4
+always \u94A4 245-2345-2
+always \u94A5 246-5
+always \u94A6 245-1456-3
+always \u94A7 13-256-3
+always \u94A8 34-3
+always \u94A9 13-12356-3
+always \u94AA 123-1346-5
+always \u94AB 12345-1346-3
+always \u94AC 1235-25-4
+always \u94AD 124-12356-4
+always \u94AE 1345-234-4
+always \u94AF 135-345-4
+always \u94B0 1256-5
+always \u94B1 245-2345-2
+always \u94B2 1-1356-3
+always \u94B3 245-2345-2
+always \u94B4 13-34-4
+always \u94B5 135-126-3
+always \u94B6 123-2346-3
+always \u94B7 1234-126-4
+always \u94B8 135-34-3
+always \u94B9 135-126-2
+always \u94BA 236-5
+always \u94BB 125-12456-3
+always \u94BC 134-34-5
+always \u94BD 145-1236-5
+always \u94BE 13-23456-4
+always \u94BF 145-2345-5
+always \u94C0 234-2
+always \u94C1 124-346-4
+always \u94C2 135-126-2
+always \u94C3 14-13456-2
+always \u94C4 24-25-5
+always \u94C5 245-2345-3
+always \u94C6 134-146-4
+always \u94C7 135-146-5
+always \u94C8 24-156-5
+always \u94C9 15-45-5
+always \u94CA 124-345-3
+always \u94CB 135-16-5
+always \u94CC 1345-16-4
+always \u94CD 1234-16-2
+always \u94CE 145-25-2
+always \u94CF 15-13456-2
+always \u94D0 123-146-5
+always \u94D1 14-146-4
+always \u94D2 156-4
+always \u94D3 134-1346-2
+always \u94D4 23456-5
+always \u94D5 234-4
+always \u94D6 12-1356-2
+always \u94D7 13-23456-2
+always \u94D8 346-2
+always \u94D9 1345-146-2
+always \u94DA 1-156-5
+always \u94DB 145-1346-3
+always \u94DC 124-12346-2
+always \u94DD 14-1256-4
+always \u94DE 145-246-5
+always \u94DF 1456-3
+always \u94E0 123-2456-4
+always \u94E1 1-345-2
+always \u94E2 1-34-3
+always \u94E3 15-2345-4
+always \u94E4 124-13456-4
+always \u94E5 145-234-3
+always \u94E6 15-2345-3
+always \u94E7 1235-35-2
+always \u94E8 245-45-2
+always \u94E9 24-345-3
+always \u94EA 1235-345-3
+always \u94EB 124-246-5
+always \u94EC 13-2346-5
+always \u94ED 134-13456-2
+always \u94EE 1-1356-3
+always \u94EF 15-2346-5
+always \u94F0 13-246-4
+always \u94F1 16-3
+always \u94F2 12-1236-4
+always \u94F3 12-12346-5
+always \u94F4 124-1346-5
+always \u94F5 1236-4
+always \u94F6 1456-2
+always \u94F7 1245-34-2
+always \u94F8 1-34-5
+always \u94F9 14-146-2
+always \u94FA 1234-34-5
+always \u94FB 34-2
+always \u94FC 14-2456-2
+always \u94FD 124-2346-5
+always \u94FE 14-2345-5
+always \u94FF 123-1356-3
+always \u9500 15-246-3
+always \u9501 15-25-4
+always \u9502 14-16-4
+always \u9503 1-1356-5
+always \u9504 12-34-2
+always \u9505 13-25-3
+always \u9506 13-146-5
+always \u9507 2346-2
+always \u9508 15-234-5
+always \u9509 245-25-5
+always \u950A 14-236-5
+always \u950B 12345-1356-3
+always \u950C 15-1456-3
+always \u950D 14-234-4
+always \u950E 123-2456-3
+always \u950F 13-2345-5
+always \u9510 1245-1246-5
+always \u9511 124-16-3
+always \u9512 14-1346-2
+always \u9513 245-1456-4
+always \u9514 13-1256-2
+always \u9515 345-3
+always \u9516 245-46-3
+always \u9517 1-2346-4
+always \u9518 1345-25-5
+always \u9519 245-25-5
+always \u951A 134-146-2
+always \u951B 135-136-3
+always \u951C 245-16-2
+always \u951D 145-2346-2
+always \u951E 123-2346-5
+always \u951F 123-123456-3
+always \u9520 12-1346-3
+always \u9521 15-16-3
+always \u9522 13-34-5
+always \u9523 14-25-2
+always \u9524 12-1246-2
+always \u9525 1-1246-3
+always \u9526 13-1456-4
+always \u9527 1-156-5
+always \u9528 15-2345-3
+always \u9529 13-45-4
+always \u952A 1235-25-5
+always \u952B 1234-356-2
+always \u952C 124-1236-2
+always \u952D 145-13456-5
+always \u952E 13-2345-5
+always \u952F 13-1256-5
+always \u9530 134-1356-4
+always \u9531 125-156-3
+always \u9532 245-346-5
+always \u9533 46-3
+always \u9534 123-2456-4
+always \u9535 245-46-3
+always \u9536 15-156-3
+always \u9537 2346-5
+always \u9538 12-345-3
+always \u9539 245-246-3
+always \u953A 1-12346-3
+always \u953B 145-12456-5
+always \u953C 15-12356-3
+always \u953D 1235-456-2
+always \u953E 1235-12456-2
+always \u953F 2456-3
+always \u9540 145-34-5
+always \u9541 134-356-4
+always \u9542 14-12356-5
+always \u9543 125-156-3
+always \u9544 12345-356-5
+always \u9545 134-356-2
+always \u9546 134-126-5
+always \u9547 1-136-5
+always \u9548 135-126-2
+always \u9549 13-2346-2
+always \u954A 1345-346-5
+always \u954B 124-1346-4
+always \u954C 13-45-3
+always \u954D 1345-346-5
+always \u954E 1345-345-2
+always \u954F 14-234-2
+always \u9550 13-146-4
+always \u9551 135-1346-5
+always \u9552 16-5
+always \u9553 13-23456-3
+always \u9554 135-1456-3
+always \u9555 1245-12346-2
+always \u9556 135-246-3
+always \u9557 124-1346-3
+always \u9558 134-1236-5
+always \u9559 14-25-2
+always \u955A 135-1356-5
+always \u955B 235-3
+always \u955C 13-13456-5
+always \u955D 145-16-2
+always \u955E 125-34-2
+always \u955F 15-45-5
+always \u9560 14-234-2
+always \u9561 124-1236-2
+always \u9562 13-236-2
+always \u9563 14-246-5
+always \u9564 1234-34-2
+always \u9565 14-34-4
+always \u9566 145-123456-3
+always \u9567 14-1236-2
+always \u9568 1234-34-4
+always \u9569 245-12456-3
+always \u956A 245-46-3
+always \u956B 145-1356-5
+always \u956C 1235-25-5
+always \u956D 14-356-2
+always \u956E 1235-12456-2
+always \u956F 1-25-2
+always \u9570 14-2345-2
+always \u9571 16-5
+always \u9572 12-345-4
+always \u9573 135-246-3
+always \u9574 14-345-5
+always \u9575 12-1236-2
+always \u9576 15-46-3
+always \u9577 12-1346-2
+always \u9578 12-1346-2
+always \u9579 13-234-4
+always \u957A 146-4
+always \u957B 145-346-2
+always \u957C 245-1256-3
+always \u957D 14-246-4
+always \u957E 134-16-2
+always \u957F 12-1346-2
+always \u9580 134-136-2
+always \u9581 134-345-5
+always \u9582 24-12456-3
+always \u9583 24-1236-4
+always \u9584 1235-25-5
+always \u9585 134-136-2
+always \u9586 2345-2
+always \u9587 135-16-5
+always \u9588 1235-1236-5
+always \u9589 135-16-5
+always \u958A 24-1236-3
+always \u958B 123-2456-3
+always \u958C 123-1346-3
+always \u958D 135-1356-3
+always \u958E 1235-12346-2
+always \u958F 1245-123456-5
+always \u9590 15-1236-5
+always \u9591 15-2345-2
+always \u9592 15-2345-2
+always \u9593 13-2345-3
+always \u9594 134-1456-4
+always \u9595 15-23456-3
+always \u9596 24-1246-4
+always \u9597 145-12356-5
+always \u9598 1-345-2
+always \u9599 1345-146-5
+always \u959A 1-1236-3
+always \u959B 1234-1356-3
+always \u959C 15-23456-4
+always \u959D 14-13456-2
+always \u959E 135-2345-5
+always \u959F 135-16-5
+always \u95A0 1245-123456-5
+always \u95A1 1235-2346-2
+always \u95A2 13-12456-3
+always \u95A3 13-2346-2
+always \u95A4 1235-2346-2
+always \u95A5 12345-345-2
+always \u95A6 12-34-5
+always \u95A7 1235-12346-5
+always \u95A8 13-1246-3
+always \u95A9 134-1456-4
+always \u95AA 15-2346-5
+always \u95AB 123-123456-4
+always \u95AC 14-1346-2
+always \u95AD 14-1256-2
+always \u95AE 124-13456-2
+always \u95AF 24-345-5
+always \u95B0 13-1256-2
+always \u95B1 236-5
+always \u95B2 236-5
+always \u95B3 12-1236-4
+always \u95B4 245-1256-5
+always \u95B5 14-1456-5
+always \u95B6 12-1346-3
+always \u95B7 24-345-3
+always \u95B8 123-123456-4
+always \u95B9 2345-3
+always \u95BA 123456-2
+always \u95BB 2345-2
+always \u95BC 2346-5
+always \u95BD 1235-123456-3
+always \u95BE 1256-5
+always \u95BF 123456-2
+always \u95C0 15-46-5
+always \u95C1 135-146-3
+always \u95C2 15-46-5
+always \u95C3 245-1256-5
+always \u95C4 246-4
+always \u95C5 123456-2
+always \u95C6 135-1236-4
+always \u95C7 1236-5
+always \u95C8 1246-2
+always \u95C9 1456-3
+always \u95CA 123-25-5
+always \u95CB 245-236-5
+always \u95CC 14-1236-2
+always \u95CD 145-34-3
+always \u95CE 245-45-3
+always \u95CF 12345-1356-3
+always \u95D0 124-2345-2
+always \u95D1 1345-346-5
+always \u95D2 124-345-5
+always \u95D3 123-2456-4
+always \u95D4 1235-2346-2
+always \u95D5 245-236-5
+always \u95D6 12-456-4
+always \u95D7 13-12456-3
+always \u95D8 145-12356-5
+always \u95D9 245-16-4
+always \u95DA 123-1246-3
+always \u95DB 124-1346-2
+always \u95DC 13-12456-3
+always \u95DD 1234-246-2
+always \u95DE 123-1236-5
+always \u95DF 15-16-5
+always \u95E0 1235-1246-5
+always \u95E1 12-1236-4
+always \u95E2 1234-16-5
+always \u95E3 145-1346-5
+always \u95E4 1235-12456-2
+always \u95E5 124-345-5
+always \u95E6 123456-2
+always \u95E7 124-345-3
+always \u95E8 134-136-2
+always \u95E9 24-12456-3
+always \u95EA 24-1236-4
+always \u95EB 2345-2
+always \u95EC 1235-1236-5
+always \u95ED 135-16-5
+always \u95EE 123456-5
+always \u95EF 12-456-4
+always \u95F0 1245-123456-5
+always \u95F1 1246-2
+always \u95F2 15-2345-2
+always \u95F3 1235-12346-2
+always \u95F4 13-2345-3
+always \u95F5 134-1456-4
+always \u95F6 123-1346-5
+always \u95F7 134-136-3
+always \u95F8 1-345-2
+always \u95F9 1345-146-5
+always \u95FA 13-1246-3
+always \u95FB 123456-2
+always \u95FC 124-345-5
+always \u95FD 134-1456-4
+always \u95FE 14-1256-2
+always \u95FF 123-2456-4
+always \u9600 12345-345-2
+always \u9601 13-2346-2
+always \u9602 1235-2346-2
+always \u9603 123-123456-4
+always \u9604 13-234-3
+always \u9605 236-5
+always \u9606 14-1346-2
+always \u9607 145-34-3
+always \u9608 1256-5
+always \u9609 2345-3
+always \u960A 12-1346-3
+always \u960B 15-16-5
+always \u960C 123456-2
+always \u960D 1235-123456-3
+always \u960E 2345-2
+always \u960F 2346-5
+always \u9610 12-1236-4
+always \u9611 14-1236-2
+always \u9612 245-1256-5
+always \u9613 1235-1246-5
+always \u9614 123-25-5
+always \u9615 245-236-5
+always \u9616 1235-2346-2
+always \u9617 124-2345-2
+always \u9618 124-345-5
+always \u9619 245-236-5
+always \u961A 123-1236-5
+always \u961B 1235-12456-2
+always \u961C 12345-34-5
+always \u961D 16-5
+always \u961E 14-2346-5
+always \u961F 145-1246-5
+always \u9620 15-1456-5
+always \u9621 245-2345-3
+always \u9622 34-5
+always \u9623 16-5
+always \u9624 16-4
+always \u9625 1456-3
+always \u9626 46-2
+always \u9627 145-12356-4
+always \u9628 2346-5
+always \u9629 24-1356-3
+always \u962A 135-1236-4
+always \u962B 1234-356-2
+always \u962C 123-1356-3
+always \u962D 256-4
+always \u962E 1245-12456-4
+always \u962F 1-156-4
+always \u9630 1234-16-2
+always \u9631 13-13456-4
+always \u9632 12345-1346-2
+always \u9633 46-2
+always \u9634 1456-3
+always \u9635 1-136-5
+always \u9636 13-346-3
+always \u9637 12-1356-3
+always \u9638 2346-5
+always \u9639 245-1256-3
+always \u963A 145-16-4
+always \u963B 125-34-4
+always \u963C 125-25-5
+always \u963D 145-2345-5
+always \u963E 14-13456-4
+always \u963F 345-3
+always \u9640 124-25-2
+always \u9641 124-25-2
+always \u9642 1234-16-2
+always \u9643 135-13456-4
+always \u9644 12345-34-5
+always \u9645 13-16-5
+always \u9646 14-34-5
+always \u9647 14-12346-4
+always \u9648 12-136-2
+always \u9649 15-13456-2
+always \u964A 145-25-5
+always \u964B 14-12356-5
+always \u964C 134-126-5
+always \u964D 13-46-5
+always \u964E 24-34-3
+always \u964F 145-25-5
+always \u9650 15-2345-5
+always \u9651 156-2
+always \u9652 13-1246-4
+always \u9653 1256-3
+always \u9654 13-2456-3
+always \u9655 24-1236-4
+always \u9656 13-256-5
+always \u9657 245-246-5
+always \u9658 15-13456-2
+always \u9659 12-123456-2
+always \u965A 12345-34-5
+always \u965B 135-16-5
+always \u965C 24-1236-4
+always \u965D 24-1236-4
+always \u965E 24-1356-3
+always \u965F 1-156-5
+always \u9660 1234-34-3
+always \u9661 145-12356-4
+always \u9662 45-5
+always \u9663 1-136-5
+always \u9664 12-34-2
+always \u9665 15-2345-5
+always \u9666 145-146-4
+always \u9667 1345-346-5
+always \u9668 256-4
+always \u9669 15-2345-4
+always \u966A 1234-356-2
+always \u966B 1234-356-2
+always \u966C 125-12356-3
+always \u966D 16-3
+always \u966E 145-1246-5
+always \u966F 14-123456-2
+always \u9670 1456-3
+always \u9671 13-1256-3
+always \u9672 12-1246-2
+always \u9673 12-136-2
+always \u9674 1234-16-2
+always \u9675 14-13456-2
+always \u9676 124-146-2
+always \u9677 15-2345-5
+always \u9678 14-34-5
+always \u9679 24-1356-3
+always \u967A 15-2345-4
+always \u967B 1456-3
+always \u967C 1-34-4
+always \u967D 46-2
+always \u967E 1245-1356-2
+always \u967F 15-23456-2
+always \u9680 12-12346-2
+always \u9681 2345-5
+always \u9682 1456-3
+always \u9683 1256-2
+always \u9684 124-16-2
+always \u9685 1256-2
+always \u9686 14-12346-2
+always \u9687 1246-3
+always \u9688 1246-3
+always \u9689 1345-346-5
+always \u968A 145-1246-5
+always \u968B 15-1246-2
+always \u968C 1236-4
+always \u968D 1235-456-2
+always \u968E 13-346-3
+always \u968F 15-1246-2
+always \u9690 1456-4
+always \u9691 13-2456-3
+always \u9692 2345-4
+always \u9693 1235-1246-3
+always \u9694 13-2346-2
+always \u9695 256-4
+always \u9696 34-5
+always \u9697 1246-4
+always \u9698 2456-5
+always \u9699 15-16-5
+always \u969A 124-1346-2
+always \u969B 13-16-5
+always \u969C 1-1346-5
+always \u969D 145-146-4
+always \u969E 146-2
+always \u969F 15-16-5
+always \u96A0 1456-4
+always \u96A1 15-345-3
+always \u96A2 1245-146-5
+always \u96A3 14-1456-2
+always \u96A4 124-1246-2
+always \u96A5 145-1356-5
+always \u96A6 1234-16-4
+always \u96A7 15-1246-5
+always \u96A8 15-1246-2
+always \u96A9 1256-5
+always \u96AA 15-2345-4
+always \u96AB 12345-136-2
+always \u96AC 1345-16-4
+always \u96AD 156-2
+always \u96AE 13-16-3
+always \u96AF 145-146-4
+always \u96B0 15-16-2
+always \u96B1 1456-4
+always \u96B2 2346-2
+always \u96B3 1235-1246-3
+always \u96B4 14-12346-4
+always \u96B5 15-16-3
+always \u96B6 14-16-5
+always \u96B7 14-16-5
+always \u96B8 14-16-5
+always \u96B9 1-1246-3
+always \u96BA 1235-2346-5
+always \u96BB 1-156-3
+always \u96BC 15-123456-4
+always \u96BD 13-45-5
+always \u96BE 1345-1236-2
+always \u96BF 16-5
+always \u96C0 245-236-5
+always \u96C1 2345-5
+always \u96C2 245-1456-2
+always \u96C3 245-2345-3
+always \u96C4 15-235-2
+always \u96C5 23456-4
+always \u96C6 13-16-2
+always \u96C7 13-34-5
+always \u96C8 1235-12456-2
+always \u96C9 1-156-5
+always \u96CA 13-12356-5
+always \u96CB 13-45-5
+always \u96CC 245-156-2
+always \u96CD 235-3
+always \u96CE 13-1256-3
+always \u96CF 12-34-2
+always \u96D0 1235-34-3
+always \u96D1 125-345-2
+always \u96D2 14-25-5
+always \u96D3 1256-2
+always \u96D4 12-12356-2
+always \u96D5 145-246-3
+always \u96D6 15-1246-3
+always \u96D7 1235-1236-5
+always \u96D8 25-5
+always \u96D9 24-456-3
+always \u96DA 1235-12456-2
+always \u96DB 12-34-2
+always \u96DC 125-345-2
+always \u96DD 235-3
+always \u96DE 13-16-3
+always \u96DF 15-16-3
+always \u96E0 12-12356-2
+always \u96E1 14-234-5
+always \u96E2 14-16-2
+always \u96E3 1345-1236-2
+always \u96E4 15-236-2
+always \u96E5 125-345-2
+always \u96E6 13-16-2
+always \u96E7 13-16-2
+always \u96E8 1256-4
+always \u96E9 1256-2
+always \u96EA 15-236-4
+always \u96EB 1345-345-4
+always \u96EC 12345-12356-4
+always \u96ED 15-2346-5
+always \u96EE 134-34-5
+always \u96EF 123456-2
+always \u96F0 12345-136-3
+always \u96F1 1234-1346-2
+always \u96F2 256-2
+always \u96F3 14-16-5
+always \u96F4 14-16-5
+always \u96F5 1346-4
+always \u96F6 14-13456-2
+always \u96F7 14-356-2
+always \u96F8 1236-2
+always \u96F9 135-146-2
+always \u96FA 34-5
+always \u96FB 145-2345-5
+always \u96FC 145-1346-5
+always \u96FD 1235-34-2
+always \u96FE 34-5
+always \u96FF 145-246-5
+always \u9700 15-1256-3
+always \u9701 13-16-5
+always \u9702 134-34-5
+always \u9703 12-136-2
+always \u9704 15-246-3
+always \u9705 1-345-2
+always \u9706 124-13456-2
+always \u9707 1-136-5
+always \u9708 1234-356-5
+always \u9709 134-356-2
+always \u970A 14-13456-2
+always \u970B 245-16-3
+always \u970C 12-12356-3
+always \u970D 1235-25-5
+always \u970E 24-345-5
+always \u970F 12345-356-3
+always \u9710 1235-12346-2
+always \u9711 1-1236-3
+always \u9712 1456-3
+always \u9713 1345-16-2
+always \u9714 1-34-5
+always \u9715 124-123456-2
+always \u9716 14-1456-2
+always \u9717 14-13456-2
+always \u9718 145-12346-5
+always \u9719 13456-3
+always \u971A 34-5
+always \u971B 14-13456-2
+always \u971C 24-456-3
+always \u971D 14-13456-2
+always \u971E 15-23456-2
+always \u971F 1235-12346-2
+always \u9720 1456-3
+always \u9721 134-126-5
+always \u9722 134-126-5
+always \u9723 256-4
+always \u9724 14-234-5
+always \u9725 134-1356-5
+always \u9726 135-1456-3
+always \u9727 34-5
+always \u9728 1246-5
+always \u9729 123-25-5
+always \u972A 1456-2
+always \u972B 15-16-2
+always \u972C 16-5
+always \u972D 2456-4
+always \u972E 145-1236-5
+always \u972F 145-1356-5
+always \u9730 15-2345-5
+always \u9731 1256-5
+always \u9732 14-34-5
+always \u9733 14-12346-2
+always \u9734 145-2456-5
+always \u9735 13-16-2
+always \u9736 1234-1346-3
+always \u9737 46-2
+always \u9738 135-345-5
+always \u9739 1234-16-3
+always \u973A 1246-2
+always \u973B 12345-1356-3
+always \u973C 15-16-4
+always \u973D 13-16-5
+always \u973E 134-2456-2
+always \u973F 134-1356-2
+always \u9740 134-1356-2
+always \u9741 14-356-2
+always \u9742 14-16-5
+always \u9743 1235-25-5
+always \u9744 2456-4
+always \u9745 12345-356-5
+always \u9746 145-2456-5
+always \u9747 14-12346-2
+always \u9748 14-13456-2
+always \u9749 2456-5
+always \u974A 12345-1356-3
+always \u974B 14-16-5
+always \u974C 135-146-4
+always \u974D 1235-2346-5
+always \u974E 1235-2346-5
+always \u974F 1235-2346-5
+always \u9750 135-13456-5
+always \u9751 245-13456-3
+always \u9752 245-13456-3
+always \u9753 14-46-5
+always \u9754 124-2345-3
+always \u9755 1-136-3
+always \u9756 13-13456-5
+always \u9757 12-1356-5
+always \u9758 245-13456-5
+always \u9759 13-13456-5
+always \u975A 14-46-5
+always \u975B 145-2345-5
+always \u975C 13-13456-5
+always \u975D 124-2345-3
+always \u975E 12345-356-3
+always \u975F 12345-356-3
+always \u9760 123-146-5
+always \u9761 134-16-4
+always \u9762 134-2345-5
+always \u9763 134-2345-5
+always \u9764 1234-146-5
+always \u9765 346-5
+always \u9766 124-2345-4
+always \u9767 1235-1246-5
+always \u9768 346-5
+always \u9769 13-2346-2
+always \u976A 145-13456-3
+always \u976B 12-345-3
+always \u976C 245-2345-2
+always \u976D 1245-136-5
+always \u976E 145-16-2
+always \u976F 145-34-5
+always \u9770 34-5
+always \u9771 1245-136-5
+always \u9772 245-1456-2
+always \u9773 13-1456-5
+always \u9774 15-236-3
+always \u9775 1345-234-4
+always \u9776 135-345-4
+always \u9777 1456-4
+always \u9778 15-345-4
+always \u9779 1345-345-5
+always \u977A 134-126-5
+always \u977B 125-34-4
+always \u977C 145-345-2
+always \u977D 135-1236-5
+always \u977E 16-5
+always \u977F 246-5
+always \u9780 124-146-2
+always \u9781 135-356-5
+always \u9782 13-23456-2
+always \u9783 1235-12346-2
+always \u9784 1234-146-2
+always \u9785 46-3
+always \u9786 145-13456-4
+always \u9787 1456-3
+always \u9788 13-23456-2
+always \u9789 124-146-2
+always \u978A 13-16-2
+always \u978B 15-346-2
+always \u978C 1236-3
+always \u978D 1236-3
+always \u978E 1235-136-2
+always \u978F 13-12346-4
+always \u9790 245-23456-4
+always \u9791 145-345-2
+always \u9792 245-246-3
+always \u9793 124-13456-3
+always \u9794 134-1236-2
+always \u9795 13456-5
+always \u9796 15-1246-3
+always \u9797 124-246-2
+always \u9798 245-246-5
+always \u9799 13-45-3
+always \u979A 123-12346-5
+always \u979B 135-1356-4
+always \u979C 124-345-5
+always \u979D 24-1346-5
+always \u979E 135-13456-4
+always \u979F 123-25-5
+always \u97A0 13-1256-2
+always \u97A1 14-345-3
+always \u97A2 15-346-5
+always \u97A3 1245-12356-2
+always \u97A4 135-1346-3
+always \u97A5 1356-3
+always \u97A6 245-234-3
+always \u97A7 245-234-3
+always \u97A8 1235-2346-2
+always \u97A9 15-246-5
+always \u97AA 134-12356-2
+always \u97AB 13-1256-3
+always \u97AC 13-2345-3
+always \u97AD 135-2345-3
+always \u97AE 145-16-3
+always \u97AF 13-2345-3
+always \u97B0 13-1256-3
+always \u97B1 124-146-3
+always \u97B2 13-12356-3
+always \u97B3 124-345-5
+always \u97B4 135-2456-5
+always \u97B5 15-346-2
+always \u97B6 1234-1236-2
+always \u97B7 13-2346-2
+always \u97B8 135-16-5
+always \u97B9 123-25-5
+always \u97BA 1234-1346-3
+always \u97BB 14-12356-5
+always \u97BC 13-1246-5
+always \u97BD 245-246-3
+always \u97BE 15-236-3
+always \u97BF 13-16-3
+always \u97C0 13-2345-3
+always \u97C1 13-46-3
+always \u97C2 12-1236-5
+always \u97C3 145-345-2
+always \u97C4 1235-25-5
+always \u97C5 15-2345-4
+always \u97C6 245-2345-3
+always \u97C7 145-34-2
+always \u97C8 35-5
+always \u97C9 13-2345-3
+always \u97CA 14-1236-2
+always \u97CB 1246-2
+always \u97CC 1245-136-5
+always \u97CD 12345-34-2
+always \u97CE 134-356-5
+always \u97CF 13-45-5
+always \u97D0 13-2346-2
+always \u97D1 1246-4
+always \u97D2 245-246-5
+always \u97D3 1235-1236-2
+always \u97D4 12-1346-5
+always \u97D5 1235-25-5
+always \u97D6 1245-12356-4
+always \u97D7 256-5
+always \u97D8 24-2346-5
+always \u97D9 1246-4
+always \u97DA 13-2346-2
+always \u97DB 135-2456-5
+always \u97DC 124-146-3
+always \u97DD 13-12356-3
+always \u97DE 256-5
+always \u97DF 13-146-3
+always \u97E0 135-16-5
+always \u97E1 1246-4
+always \u97E2 1235-1246-5
+always \u97E3 145-34-2
+always \u97E4 35-5
+always \u97E5 145-34-2
+always \u97E6 1246-2
+always \u97E7 1245-136-5
+always \u97E8 12345-34-2
+always \u97E9 1235-1236-2
+always \u97EA 1246-4
+always \u97EB 256-5
+always \u97EC 124-146-3
+always \u97ED 13-234-4
+always \u97EE 13-234-4
+always \u97EF 15-2345-3
+always \u97F0 15-346-5
+always \u97F1 15-2345-3
+always \u97F2 13-16-3
+always \u97F3 1456-3
+always \u97F4 125-345-2
+always \u97F5 256-5
+always \u97F6 24-146-2
+always \u97F7 14-2346-5
+always \u97F8 1234-1356-2
+always \u97F9 1235-1356-2
+always \u97FA 13456-3
+always \u97FB 256-5
+always \u97FC 1234-1356-2
+always \u97FD 1236-3
+always \u97FE 1456-3
+always \u97FF 15-46-4
+always \u9800 1235-34-5
+always \u9801 346-5
+always \u9802 145-13456-4
+always \u9803 245-13456-4
+always \u9804 123-1246-2
+always \u9805 15-46-5
+always \u9806 24-123456-5
+always \u9807 1235-1236-3
+always \u9808 15-1256-3
+always \u9809 16-2
+always \u980A 15-1256-3
+always \u980B 2346-4
+always \u980C 15-12346-5
+always \u980D 123-1246-4
+always \u980E 245-16-2
+always \u980F 1235-1346-2
+always \u9810 1256-5
+always \u9811 12456-2
+always \u9812 135-1236-3
+always \u9813 145-123456-5
+always \u9814 145-16-2
+always \u9815 145-1236-3
+always \u9816 1234-1236-5
+always \u9817 1234-126-4
+always \u9818 14-13456-4
+always \u9819 12-2346-5
+always \u981A 13-13456-4
+always \u981B 14-356-4
+always \u981C 13-2346-2
+always \u981D 245-246-3
+always \u981E 2346-5
+always \u981F 2346-2
+always \u9820 1246-4
+always \u9821 13-346-2
+always \u9822 123-25-5
+always \u9823 24-136-4
+always \u9824 16-2
+always \u9825 24-136-4
+always \u9826 1235-2456-2
+always \u9827 145-1246-3
+always \u9828 1234-2345-3
+always \u9829 1234-13456-3
+always \u982A 14-356-5
+always \u982B 12345-34-4
+always \u982C 13-23456-2
+always \u982D 124-12356-2
+always \u982E 1235-1246-5
+always \u982F 123-1246-2
+always \u9830 13-23456-2
+always \u9831 14-25-2
+always \u9832 124-13456-4
+always \u9833 12-1356-3
+always \u9834 13456-4
+always \u9835 13-256-3
+always \u9836 1235-34-2
+always \u9837 1235-1236-5
+always \u9838 13-13456-4
+always \u9839 124-1246-2
+always \u983A 124-1246-2
+always \u983B 1234-1456-2
+always \u983C 14-2456-5
+always \u983D 124-1246-2
+always \u983E 125-156-3
+always \u983F 125-156-3
+always \u9840 12-1246-2
+always \u9841 145-13456-5
+always \u9842 14-2456-5
+always \u9843 2345-2
+always \u9844 1235-1236-5
+always \u9845 13-2345-3
+always \u9846 123-2346-3
+always \u9847 245-1246-5
+always \u9848 13-235-4
+always \u9849 245-1456-3
+always \u984A 16-2
+always \u984B 15-2456-3
+always \u984C 124-16-2
+always \u984D 2346-2
+always \u984E 2346-5
+always \u984F 2345-2
+always \u9850 1235-123456-5
+always \u9851 123-1236-4
+always \u9852 235-2
+always \u9853 1-12456-3
+always \u9854 2345-2
+always \u9855 15-2345-4
+always \u9856 15-1456-5
+always \u9857 16-4
+always \u9858 45-5
+always \u9859 15-1346-4
+always \u985A 145-2345-3
+always \u985B 145-2345-3
+always \u985C 13-46-4
+always \u985D 123-34-3
+always \u985E 14-356-5
+always \u985F 14-246-2
+always \u9860 1234-246-4
+always \u9861 16-5
+always \u9862 134-1236-3
+always \u9863 245-34-5
+always \u9864 246-2
+always \u9865 1235-146-5
+always \u9866 245-246-2
+always \u9867 13-34-5
+always \u9868 15-256-5
+always \u9869 2345-4
+always \u986A 1235-1246-5
+always \u986B 12-1236-5
+always \u986C 1245-34-2
+always \u986D 1235-12346-3
+always \u986E 135-1456-3
+always \u986F 15-2345-4
+always \u9870 1234-1456-2
+always \u9871 14-34-2
+always \u9872 14-1456-4
+always \u9873 1345-346-5
+always \u9874 245-45-2
+always \u9875 346-5
+always \u9876 145-13456-4
+always \u9877 245-13456-4
+always \u9878 1235-1236-3
+always \u9879 15-46-5
+always \u987A 24-123456-5
+always \u987B 15-1256-3
+always \u987C 15-1256-3
+always \u987D 12456-2
+always \u987E 13-34-5
+always \u987F 145-123456-5
+always \u9880 245-16-2
+always \u9881 135-1236-3
+always \u9882 15-12346-5
+always \u9883 1235-1346-2
+always \u9884 1256-5
+always \u9885 14-34-2
+always \u9886 14-13456-4
+always \u9887 1234-126-4
+always \u9888 13-13456-4
+always \u9889 13-346-2
+always \u988A 13-23456-2
+always \u988B 124-13456-4
+always \u988C 1235-2346-2
+always \u988D 13456-4
+always \u988E 13-235-4
+always \u988F 1235-2346-3
+always \u9890 16-2
+always \u9891 1234-1456-2
+always \u9892 1235-1246-5
+always \u9893 124-1246-2
+always \u9894 1235-1236-5
+always \u9895 13456-4
+always \u9896 13456-4
+always \u9897 123-2346-3
+always \u9898 124-16-2
+always \u9899 235-2
+always \u989A 2346-5
+always \u989B 1-12456-3
+always \u989C 2345-2
+always \u989D 2346-2
+always \u989E 1345-346-5
+always \u989F 134-1236-3
+always \u98A0 145-2345-3
+always \u98A1 15-1346-4
+always \u98A2 1235-146-5
+always \u98A3 14-356-5
+always \u98A4 12-1236-5
+always \u98A5 1245-34-2
+always \u98A6 1234-1456-2
+always \u98A7 245-45-2
+always \u98A8 12345-1356-3
+always \u98A9 135-246-3
+always \u98AA 13-35-3
+always \u98AB 12345-34-2
+always \u98AC 15-23456-3
+always \u98AD 1-1236-4
+always \u98AE 135-246-3
+always \u98AF 15-345-5
+always \u98B0 135-345-2
+always \u98B1 124-2456-2
+always \u98B2 14-346-5
+always \u98B3 13-35-3
+always \u98B4 15-45-5
+always \u98B5 24-146-5
+always \u98B6 13-1256-5
+always \u98B7 135-246-3
+always \u98B8 15-156-3
+always \u98B9 1246-4
+always \u98BA 46-2
+always \u98BB 246-2
+always \u98BC 15-12356-3
+always \u98BD 123-2456-4
+always \u98BE 15-146-3
+always \u98BF 12345-1236-2
+always \u98C0 14-234-2
+always \u98C1 15-16-2
+always \u98C2 14-246-2
+always \u98C3 1234-246-3
+always \u98C4 1234-246-3
+always \u98C5 14-234-2
+always \u98C6 135-246-3
+always \u98C7 135-246-3
+always \u98C8 135-246-3
+always \u98C9 14-246-2
+always \u98CA 135-246-3
+always \u98CB 15-2346-5
+always \u98CC 12345-1356-3
+always \u98CD 15-234-3
+always \u98CE 12345-1356-3
+always \u98CF 46-2
+always \u98D0 1-1236-4
+always \u98D1 135-246-3
+always \u98D2 15-345-5
+always \u98D3 13-1256-5
+always \u98D4 15-156-3
+always \u98D5 15-12356-3
+always \u98D6 246-2
+always \u98D7 14-234-2
+always \u98D8 1234-246-3
+always \u98D9 135-246-3
+always \u98DA 135-246-3
+always \u98DB 12345-356-3
+always \u98DC 12345-1236-3
+always \u98DD 12345-356-3
+always \u98DE 12345-356-3
+always \u98DF 24-156-2
+always \u98E0 24-156-2
+always \u98E1 245-1236-3
+always \u98E2 13-16-3
+always \u98E3 145-13456-5
+always \u98E4 15-156-5
+always \u98E5 124-25-3
+always \u98E6 1-1236-3
+always \u98E7 15-123456-3
+always \u98E8 15-46-4
+always \u98E9 124-123456-2
+always \u98EA 1245-136-5
+always \u98EB 1256-5
+always \u98EC 13-45-5
+always \u98ED 12-156-5
+always \u98EE 1456-4
+always \u98EF 12345-1236-5
+always \u98F0 12345-1236-5
+always \u98F1 15-123456-3
+always \u98F2 1456-4
+always \u98F3 1-34-5
+always \u98F4 16-2
+always \u98F5 125-25-5
+always \u98F6 135-16-5
+always \u98F7 13-346-4
+always \u98F8 124-146-3
+always \u98F9 14-234-4
+always \u98FA 245-156-2
+always \u98FB 124-346-5
+always \u98FC 15-156-5
+always \u98FD 135-146-4
+always \u98FE 24-156-5
+always \u98FF 145-25-5
+always \u9900 1235-2456-5
+always \u9901 1245-136-5
+always \u9902 124-2345-4
+always \u9903 13-246-4
+always \u9904 13-23456-2
+always \u9905 135-13456-4
+always \u9906 246-2
+always \u9907 124-12346-2
+always \u9908 245-156-2
+always \u9909 15-46-4
+always \u990A 46-4
+always \u990B 46-4
+always \u990C 156-4
+always \u990D 2345-5
+always \u990E 14-2346-5
+always \u990F 15-16-3
+always \u9910 245-1236-3
+always \u9911 135-126-3
+always \u9912 1345-356-4
+always \u9913 2346-5
+always \u9914 135-34-3
+always \u9915 13-256-5
+always \u9916 145-12356-5
+always \u9917 15-34-5
+always \u9918 1256-2
+always \u9919 24-156-5
+always \u991A 246-2
+always \u991B 1235-123456-2
+always \u991C 13-25-4
+always \u991D 24-156-5
+always \u991E 13-2345-5
+always \u991F 1-1246-5
+always \u9920 135-13456-4
+always \u9921 15-2345-5
+always \u9922 135-34-5
+always \u9923 346-5
+always \u9924 124-1236-2
+always \u9925 12345-356-3
+always \u9926 1-1346-3
+always \u9927 1246-5
+always \u9928 13-12456-4
+always \u9929 2346-5
+always \u992A 1345-12456-4
+always \u992B 256-5
+always \u992C 1235-34-2
+always \u992D 1235-456-2
+always \u992E 124-346-5
+always \u992F 1235-1246-5
+always \u9930 13-2345-3
+always \u9931 1235-12356-2
+always \u9932 2456-5
+always \u9933 15-13456-2
+always \u9934 12345-136-3
+always \u9935 1246-5
+always \u9936 13-34-4
+always \u9937 12-345-3
+always \u9938 15-12346-5
+always \u9939 124-1346-2
+always \u993A 135-126-2
+always \u993B 13-146-3
+always \u993C 15-16-5
+always \u993D 123-1246-5
+always \u993E 14-234-2
+always \u993F 15-12356-3
+always \u9940 124-146-2
+always \u9941 346-5
+always \u9942 123456-3
+always \u9943 134-126-2
+always \u9944 124-1346-2
+always \u9945 134-1236-2
+always \u9946 135-16-5
+always \u9947 1256-5
+always \u9948 15-234-3
+always \u9949 13-1456-4
+always \u994A 15-1236-4
+always \u994B 123-1246-5
+always \u994C 1-12456-5
+always \u994D 24-1236-5
+always \u994E 12-156-5
+always \u994F 145-1236-5
+always \u9950 16-5
+always \u9951 13-16-3
+always \u9952 1245-146-2
+always \u9953 12-1356-3
+always \u9954 235-3
+always \u9955 124-146-3
+always \u9956 1235-1246-5
+always \u9957 15-46-4
+always \u9958 1-1236-3
+always \u9959 12345-136-3
+always \u995A 1235-2456-5
+always \u995B 134-1356-2
+always \u995C 2345-5
+always \u995D 134-126-2
+always \u995E 12-1236-2
+always \u995F 15-46-4
+always \u9960 14-25-2
+always \u9961 125-1236-5
+always \u9962 1345-1346-2
+always \u9963 24-156-2
+always \u9964 145-13456-5
+always \u9965 13-16-3
+always \u9966 124-25-3
+always \u9967 15-13456-2
+always \u9968 124-123456-2
+always \u9969 15-16-5
+always \u996A 1245-136-5
+always \u996B 1256-5
+always \u996C 12-156-5
+always \u996D 12345-1236-5
+always \u996E 1456-4
+always \u996F 13-2345-5
+always \u9970 24-156-5
+always \u9971 135-146-4
+always \u9972 15-156-5
+always \u9973 145-25-5
+always \u9974 16-2
+always \u9975 156-4
+always \u9976 1245-146-2
+always \u9977 15-46-4
+always \u9978 13-23456-2
+always \u9979 14-2346-5
+always \u997A 13-246-4
+always \u997B 15-16-3
+always \u997C 135-13456-4
+always \u997D 135-126-3
+always \u997E 145-12356-5
+always \u997F 2346-5
+always \u9980 1256-2
+always \u9981 1345-356-4
+always \u9982 13-256-5
+always \u9983 13-25-4
+always \u9984 1235-123456-2
+always \u9985 15-2345-5
+always \u9986 13-12456-4
+always \u9987 12-345-3
+always \u9988 123-1246-5
+always \u9989 13-34-4
+always \u998A 15-12356-3
+always \u998B 12-1236-2
+always \u998C 346-5
+always \u998D 134-126-2
+always \u998E 135-126-2
+always \u998F 14-234-2
+always \u9990 15-234-3
+always \u9991 13-1456-4
+always \u9992 134-1236-2
+always \u9993 15-1236-4
+always \u9994 1-12456-5
+always \u9995 1345-1346-2
+always \u9996 24-12356-4
+always \u9997 123-1246-2
+always \u9998 13-25-2
+always \u9999 15-46-3
+always \u999A 12345-136-2
+always \u999B 135-345-2
+always \u999C 1345-16-4
+always \u999D 135-16-5
+always \u999E 135-126-2
+always \u999F 124-34-2
+always \u99A0 1235-1236-3
+always \u99A1 12345-356-3
+always \u99A2 13-2345-3
+always \u99A3 1236-3
+always \u99A4 2456-4
+always \u99A5 12345-34-5
+always \u99A6 15-2345-3
+always \u99A7 123456-3
+always \u99A8 15-1456-3
+always \u99A9 12345-136-2
+always \u99AA 135-1456-3
+always \u99AB 15-13456-3
+always \u99AC 134-345-4
+always \u99AD 1256-5
+always \u99AE 12345-1356-2
+always \u99AF 1235-1236-5
+always \u99B0 145-16-5
+always \u99B1 124-25-2
+always \u99B2 1-2346-2
+always \u99B3 12-156-2
+always \u99B4 15-256-2
+always \u99B5 1-34-5
+always \u99B6 1-156-3
+always \u99B7 1234-356-5
+always \u99B8 15-1456-5
+always \u99B9 1245-156-5
+always \u99BA 15-345-5
+always \u99BB 256-4
+always \u99BC 123456-2
+always \u99BD 1-156-2
+always \u99BE 145-1236-5
+always \u99BF 14-1256-2
+always \u99C0 234-2
+always \u99C1 135-126-2
+always \u99C2 135-146-4
+always \u99C3 123-2356-5
+always \u99C4 124-25-2
+always \u99C5 16-5
+always \u99C6 245-1256-3
+always \u99C7 123456-2
+always \u99C8 245-1256-3
+always \u99C9 13-235-3
+always \u99CA 135-126-4
+always \u99CB 1-146-3
+always \u99CC 45-3
+always \u99CD 1234-1356-3
+always \u99CE 1-12356-5
+always \u99CF 13-1256-5
+always \u99D0 1-34-5
+always \u99D1 1345-34-2
+always \u99D2 13-1256-3
+always \u99D3 1234-16-3
+always \u99D4 125-1346-4
+always \u99D5 13-23456-5
+always \u99D6 14-13456-2
+always \u99D7 1-136-4
+always \u99D8 124-2456-2
+always \u99D9 12345-34-5
+always \u99DA 46-4
+always \u99DB 24-156-4
+always \u99DC 135-16-5
+always \u99DD 124-25-2
+always \u99DE 124-25-2
+always \u99DF 15-156-5
+always \u99E0 14-234-2
+always \u99E1 134-345-5
+always \u99E2 1234-2345-2
+always \u99E3 124-146-2
+always \u99E4 1-156-5
+always \u99E5 1245-12346-2
+always \u99E6 124-1356-2
+always \u99E7 145-12346-5
+always \u99E8 15-256-2
+always \u99E9 245-45-2
+always \u99EA 24-136-3
+always \u99EB 13-235-3
+always \u99EC 156-4
+always \u99ED 1235-2456-5
+always \u99EE 135-126-2
+always \u99EF 1-34-3
+always \u99F0 1456-3
+always \u99F1 14-25-5
+always \u99F2 1-12356-3
+always \u99F3 145-1236-5
+always \u99F4 15-346-5
+always \u99F5 14-234-2
+always \u99F6 13-1256-2
+always \u99F7 15-12346-4
+always \u99F8 245-1456-3
+always \u99F9 134-1346-2
+always \u99FA 14-46-2
+always \u99FB 1235-1236-5
+always \u99FC 124-34-2
+always \u99FD 15-45-3
+always \u99FE 124-1246-5
+always \u99FF 13-256-5
+always \u9A00 2346-2
+always \u9A01 12-1356-4
+always \u9A02 15-13456-3
+always \u9A03 2456-2
+always \u9A04 14-34-5
+always \u9A05 1-1246-3
+always \u9A06 1-12356-3
+always \u9A07 24-2346-5
+always \u9A08 1234-2345-2
+always \u9A09 123-123456-3
+always \u9A0A 124-146-2
+always \u9A0B 14-2456-2
+always \u9A0C 125-12346-3
+always \u9A0D 123-2346-5
+always \u9A0E 245-16-2
+always \u9A0F 245-16-2
+always \u9A10 2345-5
+always \u9A11 12345-356-3
+always \u9A12 15-146-3
+always \u9A13 2345-4
+always \u9A14 13-346-2
+always \u9A15 246-4
+always \u9A16 34-5
+always \u9A17 1234-2345-5
+always \u9A18 245-12346-3
+always \u9A19 1234-2345-5
+always \u9A1A 245-2345-2
+always \u9A1B 12345-356-3
+always \u9A1C 1235-456-2
+always \u9A1D 13-2345-3
+always \u9A1E 1235-25-5
+always \u9A1F 1256-5
+always \u9A20 124-16-2
+always \u9A21 245-45-2
+always \u9A22 15-23456-2
+always \u9A23 125-12346-3
+always \u9A24 123-1246-2
+always \u9A25 1245-12356-2
+always \u9A26 15-156-3
+always \u9A27 13-35-3
+always \u9A28 124-25-2
+always \u9A29 123-1246-5
+always \u9A2A 15-12356-3
+always \u9A2B 245-2345-3
+always \u9A2C 12-1356-2
+always \u9A2D 1-156-5
+always \u9A2E 14-234-2
+always \u9A2F 1234-1356-2
+always \u9A30 124-1356-2
+always \u9A31 15-16-3
+always \u9A32 245-146-4
+always \u9A33 145-34-2
+always \u9A34 2345-5
+always \u9A35 45-2
+always \u9A36 125-12356-3
+always \u9A37 15-146-3
+always \u9A38 24-1236-5
+always \u9A39 245-16-2
+always \u9A3A 1-156-5
+always \u9A3B 24-456-4
+always \u9A3C 14-34-5
+always \u9A3D 15-16-2
+always \u9A3E 14-25-2
+always \u9A3F 1-1346-3
+always \u9A40 134-126-5
+always \u9A41 146-5
+always \u9A42 245-1236-3
+always \u9A43 1234-246-5
+always \u9A44 245-12346-3
+always \u9A45 245-1256-3
+always \u9A46 135-16-5
+always \u9A47 1-156-5
+always \u9A48 1256-5
+always \u9A49 15-1256-3
+always \u9A4A 1235-35-2
+always \u9A4B 135-126-3
+always \u9A4C 15-34-5
+always \u9A4D 15-246-3
+always \u9A4E 14-1456-2
+always \u9A4F 12-1236-4
+always \u9A50 145-123456-3
+always \u9A51 14-234-2
+always \u9A52 124-25-2
+always \u9A53 125-1356-3
+always \u9A54 124-1236-2
+always \u9A55 13-246-3
+always \u9A56 124-346-4
+always \u9A57 2345-5
+always \u9A58 14-25-2
+always \u9A59 1-1236-3
+always \u9A5A 13-13456-3
+always \u9A5B 16-5
+always \u9A5C 346-5
+always \u9A5D 124-25-3
+always \u9A5E 135-1456-3
+always \u9A5F 125-12356-5
+always \u9A60 2345-5
+always \u9A61 1234-1356-2
+always \u9A62 14-1256-2
+always \u9A63 124-1356-2
+always \u9A64 15-46-3
+always \u9A65 13-16-5
+always \u9A66 24-456-3
+always \u9A67 13-1256-2
+always \u9A68 15-16-3
+always \u9A69 1235-12456-3
+always \u9A6A 14-16-2
+always \u9A6B 135-246-3
+always \u9A6C 134-345-4
+always \u9A6D 1256-5
+always \u9A6E 124-25-2
+always \u9A6F 15-256-5
+always \u9A70 12-156-2
+always \u9A71 245-1256-3
+always \u9A72 1245-156-5
+always \u9A73 135-126-2
+always \u9A74 14-1256-2
+always \u9A75 125-1346-4
+always \u9A76 24-156-4
+always \u9A77 15-156-5
+always \u9A78 12345-34-5
+always \u9A79 13-1256-3
+always \u9A7A 125-12356-3
+always \u9A7B 1-34-5
+always \u9A7C 124-25-2
+always \u9A7D 1345-34-2
+always \u9A7E 13-23456-5
+always \u9A7F 16-5
+always \u9A80 124-2456-2
+always \u9A81 15-246-3
+always \u9A82 134-345-5
+always \u9A83 1456-3
+always \u9A84 13-246-3
+always \u9A85 1235-35-2
+always \u9A86 14-25-5
+always \u9A87 1235-2456-5
+always \u9A88 1234-2345-2
+always \u9A89 135-246-3
+always \u9A8A 14-16-2
+always \u9A8B 12-1356-4
+always \u9A8C 2345-5
+always \u9A8D 15-13456-3
+always \u9A8E 245-1456-3
+always \u9A8F 13-256-5
+always \u9A90 245-16-2
+always \u9A91 245-16-2
+always \u9A92 123-2346-5
+always \u9A93 1-1246-3
+always \u9A94 125-12346-3
+always \u9A95 15-34-5
+always \u9A96 245-1236-3
+always \u9A97 1234-2345-5
+always \u9A98 1-156-5
+always \u9A99 123-1246-2
+always \u9A9A 15-146-3
+always \u9A9B 34-5
+always \u9A9C 146-5
+always \u9A9D 14-234-2
+always \u9A9E 245-2345-3
+always \u9A9F 24-1236-5
+always \u9AA0 1234-246-5
+always \u9AA1 14-25-2
+always \u9AA2 245-12346-3
+always \u9AA3 12-1236-4
+always \u9AA4 125-12356-5
+always \u9AA5 13-16-5
+always \u9AA6 24-456-3
+always \u9AA7 15-46-3
+always \u9AA8 13-34-4
+always \u9AA9 1246-4
+always \u9AAA 1246-4
+always \u9AAB 1246-4
+always \u9AAC 1256-2
+always \u9AAD 13-1236-5
+always \u9AAE 16-5
+always \u9AAF 1346-3
+always \u9AB0 24-2456-4
+always \u9AB1 15-346-5
+always \u9AB2 135-146-3
+always \u9AB3 135-16-5
+always \u9AB4 12-156-3
+always \u9AB5 124-16-4
+always \u9AB6 145-16-5
+always \u9AB7 123-34-3
+always \u9AB8 1235-2456-2
+always \u9AB9 245-246-3
+always \u9ABA 1235-12356-2
+always \u9ABB 123-35-5
+always \u9ABC 13-2346-2
+always \u9ABD 124-1246-4
+always \u9ABE 13-1356-4
+always \u9ABF 1234-2345-2
+always \u9AC0 135-16-5
+always \u9AC1 123-2346-3
+always \u9AC2 123-345-5
+always \u9AC3 1256-2
+always \u9AC4 15-1246-4
+always \u9AC5 14-12356-2
+always \u9AC6 135-126-2
+always \u9AC7 15-246-3
+always \u9AC8 135-1346-4
+always \u9AC9 135-126-3
+always \u9ACA 245-156-3
+always \u9ACB 123-12456-3
+always \u9ACC 135-1456-5
+always \u9ACD 134-126-2
+always \u9ACE 14-246-2
+always \u9ACF 14-12356-2
+always \u9AD0 15-246-3
+always \u9AD1 145-34-2
+always \u9AD2 125-1346-3
+always \u9AD3 15-1246-4
+always \u9AD4 124-16-4
+always \u9AD5 135-1456-5
+always \u9AD6 123-12456-3
+always \u9AD7 14-34-2
+always \u9AD8 13-146-3
+always \u9AD9 13-146-3
+always \u9ADA 245-246-5
+always \u9ADB 123-146-3
+always \u9ADC 245-246-3
+always \u9ADD 14-146-5
+always \u9ADE 125-146-5
+always \u9ADF 135-246-3
+always \u9AE0 123-123456-3
+always \u9AE1 123-123456-3
+always \u9AE2 124-16-5
+always \u9AE3 12345-1346-4
+always \u9AE4 15-234-3
+always \u9AE5 1245-1236-2
+always \u9AE6 134-146-2
+always \u9AE7 145-1236-5
+always \u9AE8 123-123456-3
+always \u9AE9 135-1456-5
+always \u9AEA 12345-345-4
+always \u9AEB 124-246-2
+always \u9AEC 1234-16-3
+always \u9AED 125-156-3
+always \u9AEE 12345-345-4
+always \u9AEF 1245-1236-2
+always \u9AF0 124-16-5
+always \u9AF1 1234-146-5
+always \u9AF2 135-16-5
+always \u9AF3 134-146-2
+always \u9AF4 12345-34-2
+always \u9AF5 156-2
+always \u9AF6 1245-12346-2
+always \u9AF7 245-1256-5
+always \u9AF8 13-12346-3
+always \u9AF9 15-234-3
+always \u9AFA 236-5
+always \u9AFB 13-16-5
+always \u9AFC 1234-1356-2
+always \u9AFD 1-35-3
+always \u9AFE 24-146-3
+always \u9AFF 15-25-3
+always \u9B00 124-16-5
+always \u9B01 14-16-5
+always \u9B02 135-1456-5
+always \u9B03 125-12346-3
+always \u9B04 124-16-5
+always \u9B05 1234-1356-2
+always \u9B06 15-12346-3
+always \u9B07 1-1356-3
+always \u9B08 245-45-2
+always \u9B09 125-12346-3
+always \u9B0A 24-123456-5
+always \u9B0B 13-2345-4
+always \u9B0C 145-25-4
+always \u9B0D 1235-34-2
+always \u9B0E 14-345-5
+always \u9B0F 13-234-3
+always \u9B10 245-16-2
+always \u9B11 14-2345-2
+always \u9B12 1-136-4
+always \u9B13 135-1456-5
+always \u9B14 1234-1356-2
+always \u9B15 134-126-5
+always \u9B16 15-1236-3
+always \u9B17 134-1236-2
+always \u9B18 134-1236-2
+always \u9B19 15-1356-3
+always \u9B1A 15-1256-3
+always \u9B1B 14-346-5
+always \u9B1C 245-2345-3
+always \u9B1D 245-2345-3
+always \u9B1E 1345-12346-2
+always \u9B1F 1235-12456-2
+always \u9B20 13-25-5
+always \u9B21 1345-13456-2
+always \u9B22 135-1456-5
+always \u9B23 14-346-5
+always \u9B24 1245-1346-2
+always \u9B25 145-12356-5
+always \u9B26 145-12356-5
+always \u9B27 1345-146-5
+always \u9B28 1235-12346-5
+always \u9B29 15-16-5
+always \u9B2A 145-12356-5
+always \u9B2B 1235-1236-4
+always \u9B2C 145-12356-5
+always \u9B2D 145-12356-5
+always \u9B2E 13-234-3
+always \u9B2F 12-1346-5
+always \u9B30 1256-5
+always \u9B31 1256-5
+always \u9B32 13-2346-2
+always \u9B33 13-45-5
+always \u9B34 12345-34-4
+always \u9B35 15-1456-2
+always \u9B36 13-1246-3
+always \u9B37 125-12346-3
+always \u9B38 14-234-5
+always \u9B39 13-1246-3
+always \u9B3A 24-1346-3
+always \u9B3B 1256-5
+always \u9B3C 13-1246-4
+always \u9B3D 134-356-5
+always \u9B3E 13-16-5
+always \u9B3F 245-16-2
+always \u9B40 13-346-5
+always \u9B41 123-1246-2
+always \u9B42 1235-123456-2
+always \u9B43 135-345-2
+always \u9B44 1234-126-5
+always \u9B45 134-356-5
+always \u9B46 15-1256-5
+always \u9B47 2345-4
+always \u9B48 15-246-3
+always \u9B49 14-46-4
+always \u9B4A 1256-5
+always \u9B4B 124-1246-2
+always \u9B4C 245-16-3
+always \u9B4D 456-4
+always \u9B4E 14-46-4
+always \u9B4F 1246-5
+always \u9B50 13-2345-3
+always \u9B51 12-156-3
+always \u9B52 1234-246-3
+always \u9B53 135-16-5
+always \u9B54 134-126-2
+always \u9B55 13-16-3
+always \u9B56 15-1256-3
+always \u9B57 12-12356-4
+always \u9B58 2345-4
+always \u9B59 1-1236-4
+always \u9B5A 1256-2
+always \u9B5B 145-146-3
+always \u9B5C 1245-136-2
+always \u9B5D 13-16-5
+always \u9B5E 135-345-3
+always \u9B5F 1235-12346-3
+always \u9B60 124-25-3
+always \u9B61 145-246-5
+always \u9B62 13-16-4
+always \u9B63 15-1256-5
+always \u9B64 2346-2
+always \u9B65 2346-5
+always \u9B66 24-345-3
+always \u9B67 1235-1346-2
+always \u9B68 124-123456-2
+always \u9B69 134-126-5
+always \u9B6A 13-346-5
+always \u9B6B 24-136-4
+always \u9B6C 135-1236-4
+always \u9B6D 45-2
+always \u9B6E 1234-16-2
+always \u9B6F 14-34-4
+always \u9B70 123456-2
+always \u9B71 1235-34-2
+always \u9B72 14-34-2
+always \u9B73 125-345-2
+always \u9B74 12345-1346-2
+always \u9B75 12345-136-5
+always \u9B76 1345-345-5
+always \u9B77 234-2
+always \u9B78 1234-2345-5
+always \u9B79 134-126-2
+always \u9B7A 1235-2346-2
+always \u9B7B 15-23456-2
+always \u9B7C 245-1256-3
+always \u9B7D 1235-1236-3
+always \u9B7E 1234-16-2
+always \u9B7F 14-13456-2
+always \u9B80 124-25-2
+always \u9B81 135-345-2
+always \u9B82 245-234-2
+always \u9B83 1234-13456-2
+always \u9B84 12345-34-2
+always \u9B85 135-16-5
+always \u9B86 13-16-5
+always \u9B87 1246-5
+always \u9B88 13-1256-3
+always \u9B89 145-246-3
+always \u9B8A 135-126-2
+always \u9B8B 234-2
+always \u9B8C 13-123456-4
+always \u9B8D 1234-16-3
+always \u9B8E 1345-2345-2
+always \u9B8F 15-13456-3
+always \u9B90 124-2456-2
+always \u9B91 135-146-5
+always \u9B92 12345-34-5
+always \u9B93 1-345-4
+always \u9B94 13-1256-5
+always \u9B95 13-34-3
+always \u9B96 24-156-2
+always \u9B97 145-12346-3
+always \u9B98 145-2456-5
+always \u9B99 124-345-5
+always \u9B9A 13-346-2
+always \u9B9B 24-34-2
+always \u9B9C 1235-12356-5
+always \u9B9D 15-46-4
+always \u9B9E 156-2
+always \u9B9F 1236-5
+always \u9BA0 1246-2
+always \u9BA1 1-146-5
+always \u9BA2 1-34-3
+always \u9BA3 1456-5
+always \u9BA4 14-346-5
+always \u9BA5 14-25-5
+always \u9BA6 124-12346-2
+always \u9BA7 16-2
+always \u9BA8 245-16-2
+always \u9BA9 135-13456-5
+always \u9BAA 1246-4
+always \u9BAB 13-246-3
+always \u9BAC 135-34-5
+always \u9BAD 13-1246-3
+always \u9BAE 15-2345-3
+always \u9BAF 1235-2346-2
+always \u9BB0 1235-1246-2
+always \u9BB1 14-146-4
+always \u9BB2 12345-34-2
+always \u9BB3 123-146-5
+always \u9BB4 15-234-3
+always \u9BB5 145-25-2
+always \u9BB6 13-256-3
+always \u9BB7 124-16-2
+always \u9BB8 134-2345-4
+always \u9BB9 24-146-3
+always \u9BBA 1-345-4
+always \u9BBB 24-345-3
+always \u9BBC 245-1456-3
+always \u9BBD 1256-2
+always \u9BBE 1345-356-4
+always \u9BBF 1-2346-2
+always \u9BC0 13-123456-4
+always \u9BC1 13-1356-4
+always \u9BC2 15-34-3
+always \u9BC3 34-2
+always \u9BC4 245-234-2
+always \u9BC5 124-13456-2
+always \u9BC6 1234-34-3
+always \u9BC7 1235-12456-5
+always \u9BC8 12-12356-2
+always \u9BC9 14-16-4
+always \u9BCA 24-345-3
+always \u9BCB 24-345-3
+always \u9BCC 13-146-5
+always \u9BCD 134-1356-2
+always \u9BCE 12-1356-2
+always \u9BCF 14-16-2
+always \u9BD0 125-12356-4
+always \u9BD1 15-16-3
+always \u9BD2 235-4
+always \u9BD3 24-136-3
+always \u9BD4 125-156-3
+always \u9BD5 245-16-2
+always \u9BD6 245-13456-3
+always \u9BD7 15-46-4
+always \u9BD8 1345-356-4
+always \u9BD9 12-123456-2
+always \u9BDA 13-16-5
+always \u9BDB 145-246-3
+always \u9BDC 245-346-5
+always \u9BDD 13-34-5
+always \u9BDE 1-12356-4
+always \u9BDF 145-12346-3
+always \u9BE0 14-2456-2
+always \u9BE1 12345-356-5
+always \u9BE2 1345-16-2
+always \u9BE3 16-5
+always \u9BE4 123-123456-3
+always \u9BE5 14-34-5
+always \u9BE6 13-234-5
+always \u9BE7 12-1346-3
+always \u9BE8 13-13456-3
+always \u9BE9 14-123456-2
+always \u9BEA 14-13456-2
+always \u9BEB 125-12356-3
+always \u9BEC 14-16-2
+always \u9BED 134-1356-4
+always \u9BEE 125-12346-3
+always \u9BEF 1-156-5
+always \u9BF0 1345-2345-2
+always \u9BF1 1235-34-4
+always \u9BF2 1256-2
+always \u9BF3 145-16-4
+always \u9BF4 24-156-3
+always \u9BF5 24-136-3
+always \u9BF6 1235-12456-5
+always \u9BF7 124-16-2
+always \u9BF8 1235-12356-2
+always \u9BF9 15-13456-3
+always \u9BFA 1-34-3
+always \u9BFB 14-345-5
+always \u9BFC 125-12346-3
+always \u9BFD 13-16-5
+always \u9BFE 135-2345-3
+always \u9BFF 135-2345-3
+always \u9C00 1235-12456-5
+always \u9C01 245-45-2
+always \u9C02 125-2346-2
+always \u9C03 1246-3
+always \u9C04 1246-3
+always \u9C05 1256-2
+always \u9C06 12-123456-3
+always \u9C07 1245-12356-2
+always \u9C08 145-346-2
+always \u9C09 1235-456-2
+always \u9C0A 14-2345-5
+always \u9C0B 2345-4
+always \u9C0C 245-234-3
+always \u9C0D 245-234-3
+always \u9C0E 13-2345-5
+always \u9C0F 135-16-5
+always \u9C10 2346-5
+always \u9C11 46-2
+always \u9C12 12345-34-5
+always \u9C13 15-2456-3
+always \u9C14 13-2345-4
+always \u9C15 15-23456-3
+always \u9C16 124-25-4
+always \u9C17 1235-34-2
+always \u9C18 24-156-5
+always \u9C19 1245-25-5
+always \u9C1A 15-45-3
+always \u9C1B 123456-3
+always \u9C1C 13-2345-3
+always \u9C1D 1235-146-5
+always \u9C1E 34-3
+always \u9C1F 12345-1346-2
+always \u9C20 15-146-3
+always \u9C21 14-234-2
+always \u9C22 134-345-4
+always \u9C23 24-156-2
+always \u9C24 24-156-3
+always \u9C25 13-12456-3
+always \u9C26 125-156-3
+always \u9C27 124-1356-2
+always \u9C28 124-345-4
+always \u9C29 246-2
+always \u9C2A 13-2346-2
+always \u9C2B 1245-12346-2
+always \u9C2C 245-2345-2
+always \u9C2D 245-16-2
+always \u9C2E 123456-3
+always \u9C2F 1245-25-5
+always \u9C30 24-136-2
+always \u9C31 14-2345-2
+always \u9C32 146-2
+always \u9C33 14-2346-5
+always \u9C34 1235-1246-3
+always \u9C35 134-2345-4
+always \u9C36 13-16-5
+always \u9C37 124-246-2
+always \u9C38 245-1256-3
+always \u9C39 13-2345-3
+always \u9C3A 15-146-3
+always \u9C3B 134-1236-2
+always \u9C3C 15-16-2
+always \u9C3D 245-234-2
+always \u9C3E 135-246-5
+always \u9C3F 13-16-5
+always \u9C40 13-16-5
+always \u9C41 1-34-2
+always \u9C42 13-46-3
+always \u9C43 245-234-3
+always \u9C44 1-12456-3
+always \u9C45 235-3
+always \u9C46 1-1346-3
+always \u9C47 123-1346-3
+always \u9C48 15-236-4
+always \u9C49 135-346-3
+always \u9C4A 1256-5
+always \u9C4B 245-1256-3
+always \u9C4C 15-46-5
+always \u9C4D 135-126-3
+always \u9C4E 13-246-4
+always \u9C4F 15-256-2
+always \u9C50 15-34-5
+always \u9C51 1235-456-2
+always \u9C52 125-123456-3
+always \u9C53 24-1236-5
+always \u9C54 24-1236-5
+always \u9C55 12345-1236-3
+always \u9C56 13-1246-5
+always \u9C57 14-1456-2
+always \u9C58 15-256-2
+always \u9C59 134-246-2
+always \u9C5A 15-16-4
+always \u9C5B 125-1356-3
+always \u9C5C 15-46-3
+always \u9C5D 12345-136-5
+always \u9C5E 13-12456-3
+always \u9C5F 1235-12356-5
+always \u9C60 123-2356-5
+always \u9C61 125-356-2
+always \u9C62 15-146-3
+always \u9C63 1-1236-3
+always \u9C64 13-1236-4
+always \u9C65 13-1246-5
+always \u9C66 24-1356-2
+always \u9C67 14-16-4
+always \u9C68 12-1346-2
+always \u9C69 14-356-2
+always \u9C6A 24-34-4
+always \u9C6B 2456-1
+always \u9C6C 1245-34-2
+always \u9C6D 13-16-5
+always \u9C6E 15-1256-5
+always \u9C6F 1235-34-5
+always \u9C70 24-34-4
+always \u9C71 14-16-5
+always \u9C72 14-346-5
+always \u9C73 14-25-5
+always \u9C74 134-346-5
+always \u9C75 1-136-3
+always \u9C76 15-46-4
+always \u9C77 2346-5
+always \u9C78 14-34-2
+always \u9C79 13-12456-5
+always \u9C7A 14-16-2
+always \u9C7B 15-2345-3
+always \u9C7C 1256-2
+always \u9C7D 145-146-3
+always \u9C7E 13-16-4
+always \u9C7F 234-2
+always \u9C80 124-123456-2
+always \u9C81 14-34-4
+always \u9C82 12345-1346-2
+always \u9C83 135-345-5
+always \u9C84 1235-2346-2
+always \u9C85 135-345-2
+always \u9C86 1234-13456-2
+always \u9C87 1345-2345-2
+always \u9C88 14-34-2
+always \u9C89 234-2
+always \u9C8A 1-345-4
+always \u9C8B 12345-34-5
+always \u9C8C 135-126-2
+always \u9C8D 135-146-5
+always \u9C8E 1235-12356-5
+always \u9C8F 1234-16-3
+always \u9C90 124-2456-2
+always \u9C91 13-1246-3
+always \u9C92 13-346-2
+always \u9C93 123-146-5
+always \u9C94 1246-4
+always \u9C95 156-2
+always \u9C96 124-12346-2
+always \u9C97 125-2346-2
+always \u9C98 1235-12356-5
+always \u9C99 123-2356-5
+always \u9C9A 13-16-5
+always \u9C9B 13-246-3
+always \u9C9C 15-2345-3
+always \u9C9D 1-345-4
+always \u9C9E 15-46-4
+always \u9C9F 15-256-2
+always \u9CA0 13-1356-4
+always \u9CA1 14-16-2
+always \u9CA2 14-2345-2
+always \u9CA3 13-2345-3
+always \u9CA4 14-16-4
+always \u9CA5 24-156-2
+always \u9CA6 124-246-2
+always \u9CA7 13-123456-4
+always \u9CA8 24-345-3
+always \u9CA9 1235-12456-5
+always \u9CAA 13-256-3
+always \u9CAB 13-16-5
+always \u9CAC 235-4
+always \u9CAD 245-13456-3
+always \u9CAE 14-13456-2
+always \u9CAF 245-16-2
+always \u9CB0 125-12356-3
+always \u9CB1 12345-356-3
+always \u9CB2 123-123456-3
+always \u9CB3 12-1346-3
+always \u9CB4 13-34-5
+always \u9CB5 1345-16-2
+always \u9CB6 1345-2345-2
+always \u9CB7 145-246-3
+always \u9CB8 13-13456-3
+always \u9CB9 24-136-3
+always \u9CBA 24-156-3
+always \u9CBB 125-156-3
+always \u9CBC 12345-136-5
+always \u9CBD 145-346-2
+always \u9CBE 135-16-5
+always \u9CBF 12-1346-2
+always \u9CC0 124-16-2
+always \u9CC1 123456-3
+always \u9CC2 1246-3
+always \u9CC3 15-2456-3
+always \u9CC4 2346-5
+always \u9CC5 245-234-3
+always \u9CC6 12345-34-5
+always \u9CC7 1235-456-2
+always \u9CC8 245-45-2
+always \u9CC9 13-46-3
+always \u9CCA 135-2345-3
+always \u9CCB 15-146-3
+always \u9CCC 146-2
+always \u9CCD 245-16-2
+always \u9CCE 124-345-4
+always \u9CCF 13-12456-3
+always \u9CD0 246-2
+always \u9CD1 12345-1346-2
+always \u9CD2 13-2345-3
+always \u9CD3 14-2346-5
+always \u9CD4 135-246-5
+always \u9CD5 15-236-4
+always \u9CD6 135-346-3
+always \u9CD7 134-1236-2
+always \u9CD8 134-2345-4
+always \u9CD9 235-3
+always \u9CDA 1246-5
+always \u9CDB 15-16-2
+always \u9CDC 13-1246-5
+always \u9CDD 24-1236-5
+always \u9CDE 14-1456-2
+always \u9CDF 125-123456-3
+always \u9CE0 1235-34-5
+always \u9CE1 13-1236-4
+always \u9CE2 14-16-4
+always \u9CE3 1-1236-3
+always \u9CE4 13-12456-4
+always \u9CE5 1345-246-4
+always \u9CE6 16-4
+always \u9CE7 12345-34-2
+always \u9CE8 14-16-5
+always \u9CE9 13-234-3
+always \u9CEA 135-34-4
+always \u9CEB 2345-5
+always \u9CEC 12345-34-2
+always \u9CED 145-246-3
+always \u9CEE 13-16-3
+always \u9CEF 12345-1356-5
+always \u9CF0 1245-34-5
+always \u9CF1 13-1236-3
+always \u9CF2 24-156-3
+always \u9CF3 12345-1356-5
+always \u9CF4 134-13456-2
+always \u9CF5 135-146-4
+always \u9CF6 45-3
+always \u9CF7 1-156-3
+always \u9CF8 1235-34-5
+always \u9CF9 245-1456-2
+always \u9CFA 13-1246-3
+always \u9CFB 12345-136-3
+always \u9CFC 123456-2
+always \u9CFD 2345-2
+always \u9CFE 24-156-3
+always \u9CFF 1256-5
+always \u9D00 12345-12356-4
+always \u9D01 246-3
+always \u9D02 13-236-2
+always \u9D03 13-236-2
+always \u9D04 1234-16-3
+always \u9D05 1235-12456-3
+always \u9D06 1-136-5
+always \u9D07 135-146-4
+always \u9D08 2345-5
+always \u9D09 23456-3
+always \u9D0A 1-1356-5
+always \u9D0B 12345-1346-3
+always \u9D0C 12345-1356-5
+always \u9D0D 123456-2
+always \u9D0E 12356-3
+always \u9D0F 124-2346-5
+always \u9D10 13-23456-3
+always \u9D11 1345-34-2
+always \u9D12 14-13456-2
+always \u9D13 134-346-5
+always \u9D14 12345-34-2
+always \u9D15 124-25-2
+always \u9D16 123456-2
+always \u9D17 14-16-5
+always \u9D18 135-2345-5
+always \u9D19 1-156-5
+always \u9D1A 13-2346-3
+always \u9D1B 45-3
+always \u9D1C 245-156-2
+always \u9D1D 245-1256-2
+always \u9D1E 15-246-3
+always \u9D1F 12-156-3
+always \u9D20 145-1236-5
+always \u9D21 13-1256-3
+always \u9D22 234-5
+always \u9D23 13-34-3
+always \u9D24 1-12346-3
+always \u9D25 1256-5
+always \u9D26 46-3
+always \u9D27 1256-5
+always \u9D28 23456-3
+always \u9D29 16-5
+always \u9D2A 1256-5
+always \u9D2B 124-2345-2
+always \u9D2C 13456-3
+always \u9D2D 1-1246-3
+always \u9D2E 34-3
+always \u9D2F 156-2
+always \u9D30 13-35-3
+always \u9D31 2456-5
+always \u9D32 1-156-3
+always \u9D33 2345-5
+always \u9D34 1235-1356-2
+always \u9D35 15-246-3
+always \u9D36 13-23456-2
+always \u9D37 14-346-5
+always \u9D38 1-34-3
+always \u9D39 1245-136-2
+always \u9D3A 16-2
+always \u9D3B 1235-12346-2
+always \u9D3C 14-25-5
+always \u9D3D 1245-34-2
+always \u9D3E 134-12356-2
+always \u9D3F 13-2346-3
+always \u9D40 1245-136-2
+always \u9D41 13-246-3
+always \u9D42 15-234-3
+always \u9D43 1-12356-3
+always \u9D44 12-156-3
+always \u9D45 14-25-5
+always \u9D46 124-1356-2
+always \u9D47 1345-2345-2
+always \u9D48 2346-5
+always \u9D49 14-12456-2
+always \u9D4A 13-23456-2
+always \u9D4B 13-16-5
+always \u9D4C 124-34-2
+always \u9D4D 1235-12456-3
+always \u9D4E 124-25-4
+always \u9D4F 135-34-3
+always \u9D50 34-2
+always \u9D51 13-45-3
+always \u9D52 1256-5
+always \u9D53 135-126-2
+always \u9D54 13-256-5
+always \u9D55 13-256-5
+always \u9D56 135-16-5
+always \u9D57 15-16-3
+always \u9D58 13-256-5
+always \u9D59 13-1256-2
+always \u9D5A 124-34-3
+always \u9D5B 13-13456-3
+always \u9D5C 124-16-2
+always \u9D5D 2346-2
+always \u9D5E 2346-2
+always \u9D5F 123-456-2
+always \u9D60 1235-34-2
+always \u9D61 34-4
+always \u9D62 24-136-3
+always \u9D63 14-2456-5
+always \u9D64 13-246-4
+always \u9D65 1234-1236-5
+always \u9D66 14-34-5
+always \u9D67 1234-13456-2
+always \u9D68 24-34-3
+always \u9D69 12345-34-2
+always \u9D6A 1236-3
+always \u9D6B 1-146-5
+always \u9D6C 1234-1356-2
+always \u9D6D 245-1456-2
+always \u9D6E 245-2345-3
+always \u9D6F 135-356-3
+always \u9D70 145-246-3
+always \u9D71 14-34-5
+always \u9D72 245-236-5
+always \u9D73 13-2345-3
+always \u9D74 13-1256-2
+always \u9D75 124-34-5
+always \u9D76 23456-3
+always \u9D77 45-3
+always \u9D78 245-16-2
+always \u9D79 14-16-2
+always \u9D7A 346-5
+always \u9D7B 1-1246-3
+always \u9D7C 123-12346-3
+always \u9D7D 145-25-5
+always \u9D7E 123-123456-3
+always \u9D7F 24-1356-3
+always \u9D80 245-16-2
+always \u9D81 13-13456-3
+always \u9D82 16-5
+always \u9D83 16-5
+always \u9D84 13-13456-3
+always \u9D85 125-156-3
+always \u9D86 14-2456-2
+always \u9D87 145-12346-3
+always \u9D88 245-16-3
+always \u9D89 12-123456-2
+always \u9D8A 13-1356-3
+always \u9D8B 13-1256-3
+always \u9D8C 245-1256-3
+always \u9D8D 16-5
+always \u9D8E 125-123456-3
+always \u9D8F 13-16-3
+always \u9D90 24-34-5
+always \u9D91 13456-3
+always \u9D92 12-156-5
+always \u9D93 134-246-2
+always \u9D94 1245-12356-2
+always \u9D95 1236-3
+always \u9D96 245-234-3
+always \u9D97 124-16-2
+always \u9D98 1235-34-2
+always \u9D99 124-16-2
+always \u9D9A 2346-5
+always \u9D9B 13-346-3
+always \u9D9C 134-146-2
+always \u9D9D 12345-34-2
+always \u9D9E 12-123456-3
+always \u9D9F 124-34-2
+always \u9DA0 2345-4
+always \u9DA1 1235-2346-2
+always \u9DA2 45-2
+always \u9DA3 1234-2345-3
+always \u9DA4 123-123456-3
+always \u9DA5 134-356-2
+always \u9DA6 1235-34-2
+always \u9DA7 13456-3
+always \u9DA8 12-12456-5
+always \u9DA9 34-5
+always \u9DAA 13-1256-2
+always \u9DAB 145-12346-3
+always \u9DAC 245-1346-3
+always \u9DAD 12345-1346-4
+always \u9DAE 13-34-5
+always \u9DAF 13456-3
+always \u9DB0 45-2
+always \u9DB1 15-2345-3
+always \u9DB2 12346-3
+always \u9DB3 24-156-3
+always \u9DB4 1235-2346-5
+always \u9DB5 12-34-2
+always \u9DB6 124-1346-2
+always \u9DB7 15-23456-2
+always \u9DB8 1245-25-5
+always \u9DB9 14-234-2
+always \u9DBA 13-16-2
+always \u9DBB 1235-34-2
+always \u9DBC 13-2345-3
+always \u9DBD 1-123456-4
+always \u9DBE 1235-1236-5
+always \u9DBF 245-156-2
+always \u9DC0 245-156-2
+always \u9DC1 16-5
+always \u9DC2 246-5
+always \u9DC3 2345-5
+always \u9DC4 13-16-3
+always \u9DC5 14-16-5
+always \u9DC6 124-2345-2
+always \u9DC7 123-12356-5
+always \u9DC8 124-16-2
+always \u9DC9 124-16-2
+always \u9DCA 16-5
+always \u9DCB 124-34-2
+always \u9DCC 134-345-4
+always \u9DCD 13-246-3
+always \u9DCE 13-146-3
+always \u9DCF 124-2345-2
+always \u9DD0 12-136-2
+always \u9DD1 13-16-2
+always \u9DD2 124-12456-2
+always \u9DD3 1-2346-5
+always \u9DD4 146-2
+always \u9DD5 246-4
+always \u9DD6 16-3
+always \u9DD7 12356-3
+always \u9DD8 12-156-5
+always \u9DD9 1-156-5
+always \u9DDA 14-234-5
+always \u9DDB 1245-12346-2
+always \u9DDC 14-12356-2
+always \u9DDD 135-16-5
+always \u9DDE 24-456-3
+always \u9DDF 1-25-2
+always \u9DE0 1256-2
+always \u9DE1 34-2
+always \u9DE2 13-236-2
+always \u9DE3 1456-2
+always \u9DE4 124-16-2
+always \u9DE5 15-156-3
+always \u9DE6 13-246-3
+always \u9DE7 16-5
+always \u9DE8 1235-35-3
+always \u9DE9 135-16-5
+always \u9DEA 13456-3
+always \u9DEB 15-34-5
+always \u9DEC 1235-456-2
+always \u9DED 12345-1236-2
+always \u9DEE 13-246-3
+always \u9DEF 14-246-2
+always \u9DF0 2345-5
+always \u9DF1 123-146-3
+always \u9DF2 13-234-5
+always \u9DF3 15-2345-2
+always \u9DF4 15-2345-2
+always \u9DF5 124-34-2
+always \u9DF6 134-2456-4
+always \u9DF7 125-123456-3
+always \u9DF8 1256-5
+always \u9DF9 13456-3
+always \u9DFA 14-34-5
+always \u9DFB 124-12456-2
+always \u9DFC 15-2345-2
+always \u9DFD 15-236-2
+always \u9DFE 16-5
+always \u9DFF 1234-16-5
+always \u9E00 24-34-2
+always \u9E01 14-25-2
+always \u9E02 15-16-3
+always \u9E03 16-2
+always \u9E04 13-16-3
+always \u9E05 1-2346-2
+always \u9E06 1256-2
+always \u9E07 1-1236-3
+always \u9E08 346-5
+always \u9E09 46-2
+always \u9E0A 1234-16-5
+always \u9E0B 1345-13456-2
+always \u9E0C 1235-34-5
+always \u9E0D 134-16-2
+always \u9E0E 13456-3
+always \u9E0F 134-1356-2
+always \u9E10 145-16-2
+always \u9E11 236-5
+always \u9E12 1256-5
+always \u9E13 14-356-4
+always \u9E14 135-126-2
+always \u9E15 14-34-2
+always \u9E16 1235-2346-5
+always \u9E17 14-12346-2
+always \u9E18 24-456-3
+always \u9E19 236-5
+always \u9E1A 13456-3
+always \u9E1B 13-12456-5
+always \u9E1C 245-1256-2
+always \u9E1D 14-16-2
+always \u9E1E 14-12456-2
+always \u9E1F 1345-246-4
+always \u9E20 13-234-3
+always \u9E21 13-16-3
+always \u9E22 45-3
+always \u9E23 134-13456-2
+always \u9E24 24-156-3
+always \u9E25 12356-3
+always \u9E26 23456-3
+always \u9E27 245-1346-3
+always \u9E28 135-146-4
+always \u9E29 1-136-5
+always \u9E2A 13-34-3
+always \u9E2B 145-12346-3
+always \u9E2C 14-34-2
+always \u9E2D 23456-3
+always \u9E2E 15-246-3
+always \u9E2F 46-3
+always \u9E30 14-13456-2
+always \u9E31 12-156-3
+always \u9E32 245-1256-2
+always \u9E33 45-3
+always \u9E34 15-236-2
+always \u9E35 124-25-2
+always \u9E36 15-156-3
+always \u9E37 1-156-5
+always \u9E38 156-2
+always \u9E39 13-35-3
+always \u9E3A 15-234-3
+always \u9E3B 1235-1356-2
+always \u9E3C 1-12356-3
+always \u9E3D 13-2346-3
+always \u9E3E 14-12456-2
+always \u9E3F 1235-12346-2
+always \u9E40 34-2
+always \u9E41 135-126-2
+always \u9E42 14-16-2
+always \u9E43 13-45-3
+always \u9E44 1235-34-2
+always \u9E45 2346-2
+always \u9E46 1256-5
+always \u9E47 15-2345-2
+always \u9E48 124-16-2
+always \u9E49 34-4
+always \u9E4A 245-236-5
+always \u9E4B 134-246-2
+always \u9E4C 1236-3
+always \u9E4D 123-123456-3
+always \u9E4E 135-356-3
+always \u9E4F 1234-1356-2
+always \u9E50 245-2345-3
+always \u9E51 12-123456-2
+always \u9E52 13-1356-3
+always \u9E53 45-3
+always \u9E54 15-34-5
+always \u9E55 1235-34-2
+always \u9E56 1235-2346-2
+always \u9E57 2346-5
+always \u9E58 1235-34-2
+always \u9E59 245-234-3
+always \u9E5A 245-156-2
+always \u9E5B 134-356-2
+always \u9E5C 34-5
+always \u9E5D 16-5
+always \u9E5E 246-5
+always \u9E5F 12346-3
+always \u9E60 14-234-2
+always \u9E61 13-16-2
+always \u9E62 16-5
+always \u9E63 13-2345-3
+always \u9E64 1235-2346-5
+always \u9E65 16-3
+always \u9E66 13456-3
+always \u9E67 1-2346-5
+always \u9E68 14-234-5
+always \u9E69 14-246-2
+always \u9E6A 13-246-3
+always \u9E6B 13-234-5
+always \u9E6C 1256-5
+always \u9E6D 14-34-5
+always \u9E6E 15-45-2
+always \u9E6F 1-1236-3
+always \u9E70 13456-3
+always \u9E71 1235-34-5
+always \u9E72 134-1356-2
+always \u9E73 13-12456-5
+always \u9E74 24-456-3
+always \u9E75 14-34-4
+always \u9E76 13-1456-3
+always \u9E77 14-13456-2
+always \u9E78 13-2345-4
+always \u9E79 15-2345-2
+always \u9E7A 245-25-2
+always \u9E7B 13-2345-4
+always \u9E7C 13-2345-4
+always \u9E7D 2345-2
+always \u9E7E 245-25-2
+always \u9E7F 14-34-5
+always \u9E80 234-3
+always \u9E81 245-34-3
+always \u9E82 13-16-4
+always \u9E83 135-246-3
+always \u9E84 245-34-3
+always \u9E85 135-246-3
+always \u9E86 1-34-5
+always \u9E87 13-256-3
+always \u9E88 1-34-4
+always \u9E89 13-2345-3
+always \u9E8A 134-16-2
+always \u9E8B 134-16-2
+always \u9E8C 1256-4
+always \u9E8D 14-234-2
+always \u9E8E 12-136-2
+always \u9E8F 13-256-3
+always \u9E90 14-1456-2
+always \u9E91 1345-16-2
+always \u9E92 245-16-2
+always \u9E93 14-34-5
+always \u9E94 13-234-5
+always \u9E95 13-256-3
+always \u9E96 13-13456-3
+always \u9E97 14-16-5
+always \u9E98 15-46-3
+always \u9E99 2345-2
+always \u9E9A 13-23456-3
+always \u9E9B 134-16-2
+always \u9E9C 14-16-5
+always \u9E9D 24-2346-5
+always \u9E9E 1-1346-3
+always \u9E9F 14-1456-2
+always \u9EA0 13-13456-3
+always \u9EA1 13-16-3
+always \u9EA2 14-13456-2
+always \u9EA3 2345-2
+always \u9EA4 245-34-3
+always \u9EA5 134-2456-5
+always \u9EA6 134-2456-5
+always \u9EA7 13-2346-3
+always \u9EA8 12-146-4
+always \u9EA9 12345-34-3
+always \u9EAA 134-2345-5
+always \u9EAB 134-2345-5
+always \u9EAC 12345-34-3
+always \u9EAD 1234-146-5
+always \u9EAE 245-1256-5
+always \u9EAF 245-1256-2
+always \u9EB0 134-12356-2
+always \u9EB1 12345-34-3
+always \u9EB2 15-2345-5
+always \u9EB3 14-2456-2
+always \u9EB4 245-1256-2
+always \u9EB5 134-2345-5
+always \u9EB6 12-156-3
+always \u9EB7 12345-1356-3
+always \u9EB8 12345-34-3
+always \u9EB9 245-1256-2
+always \u9EBA 134-2345-5
+always \u9EBB 134-345-2
+always \u9EBC 134-2346-1
+always \u9EBD 134-2346-1
+always \u9EBE 1235-1246-3
+always \u9EBF 134-126-4
+always \u9EC0 125-12356-3
+always \u9EC1 1345-136-3
+always \u9EC2 12345-136-2
+always \u9EC3 1235-456-2
+always \u9EC4 1235-456-2
+always \u9EC5 13-1456-3
+always \u9EC6 13-456-3
+always \u9EC7 124-2345-3
+always \u9EC8 124-12356-4
+always \u9EC9 1235-12346-2
+always \u9ECA 15-16-3
+always \u9ECB 123-456-5
+always \u9ECC 1235-12346-2
+always \u9ECD 24-34-4
+always \u9ECE 14-16-2
+always \u9ECF 1345-2345-2
+always \u9ED0 12-156-3
+always \u9ED1 1235-356-3
+always \u9ED2 1235-356-3
+always \u9ED3 16-5
+always \u9ED4 245-2345-2
+always \u9ED5 145-1236-4
+always \u9ED6 15-16-5
+always \u9ED7 124-12456-4
+always \u9ED8 134-126-5
+always \u9ED9 134-126-5
+always \u9EDA 245-2345-2
+always \u9EDB 145-2456-5
+always \u9EDC 12-34-5
+always \u9EDD 234-4
+always \u9EDE 145-2345-4
+always \u9EDF 16-3
+always \u9EE0 15-23456-2
+always \u9EE1 2345-4
+always \u9EE2 245-1256-5
+always \u9EE3 134-356-4
+always \u9EE4 2345-4
+always \u9EE5 245-13456-2
+always \u9EE6 1256-5
+always \u9EE7 14-16-2
+always \u9EE8 145-1346-4
+always \u9EE9 145-34-2
+always \u9EEA 245-1236-4
+always \u9EEB 1456-3
+always \u9EEC 1236-5
+always \u9EED 2345-4
+always \u9EEE 124-1236-4
+always \u9EEF 1236-5
+always \u9EF0 1-136-4
+always \u9EF1 145-2456-5
+always \u9EF2 245-1236-4
+always \u9EF3 16-3
+always \u9EF4 134-356-2
+always \u9EF5 145-1236-4
+always \u9EF6 2345-4
+always \u9EF7 145-34-2
+always \u9EF8 14-34-2
+always \u9EF9 1-156-4
+always \u9EFA 12345-136-4
+always \u9EFB 12345-34-2
+always \u9EFC 12345-34-4
+always \u9EFD 134-1456-4
+always \u9EFE 134-1456-4
+always \u9EFF 45-2
+always \u9F00 245-34-5
+always \u9F01 245-1256-5
+always \u9F02 12-146-2
+always \u9F03 35-3
+always \u9F04 1-34-3
+always \u9F05 1-156-3
+always \u9F06 134-1346-2
+always \u9F07 146-2
+always \u9F08 135-346-3
+always \u9F09 124-25-2
+always \u9F0A 135-16-5
+always \u9F0B 45-2
+always \u9F0C 12-146-2
+always \u9F0D 124-25-2
+always \u9F0E 145-13456-4
+always \u9F0F 134-16-5
+always \u9F10 1345-2456-5
+always \u9F11 145-13456-4
+always \u9F12 125-156-3
+always \u9F13 13-34-4
+always \u9F14 13-34-4
+always \u9F15 145-12346-3
+always \u9F16 12345-136-2
+always \u9F17 124-146-2
+always \u9F18 45-3
+always \u9F19 1234-16-2
+always \u9F1A 12-1346-3
+always \u9F1B 13-146-3
+always \u9F1C 245-16-5
+always \u9F1D 45-3
+always \u9F1E 124-1346-3
+always \u9F1F 124-1356-3
+always \u9F20 24-34-4
+always \u9F21 24-34-4
+always \u9F22 12345-136-2
+always \u9F23 12345-356-5
+always \u9F24 123456-2
+always \u9F25 135-345-2
+always \u9F26 145-246-3
+always \u9F27 124-25-2
+always \u9F28 1-12346-3
+always \u9F29 245-1256-2
+always \u9F2A 24-1356-3
+always \u9F2B 24-156-2
+always \u9F2C 234-5
+always \u9F2D 24-156-2
+always \u9F2E 124-13456-2
+always \u9F2F 34-2
+always \u9F30 13-1256-2
+always \u9F31 13-13456-3
+always \u9F32 1235-123456-2
+always \u9F33 13-1256-2
+always \u9F34 2345-4
+always \u9F35 124-34-2
+always \u9F36 15-156-3
+always \u9F37 15-16-3
+always \u9F38 15-2345-5
+always \u9F39 2345-4
+always \u9F3A 14-356-2
+always \u9F3B 135-16-2
+always \u9F3C 246-4
+always \u9F3D 245-234-2
+always \u9F3E 1235-1236-3
+always \u9F3F 34-3
+always \u9F40 34-5
+always \u9F41 1235-12356-3
+always \u9F42 15-16-5
+always \u9F43 1235-2346-2
+always \u9F44 1-345-3
+always \u9F45 15-234-5
+always \u9F46 12346-5
+always \u9F47 1-345-3
+always \u9F48 1345-12346-2
+always \u9F49 1345-1346-5
+always \u9F4A 245-16-2
+always \u9F4B 1-2456-3
+always \u9F4C 13-16-5
+always \u9F4D 125-156-3
+always \u9F4E 13-16-3
+always \u9F4F 13-16-3
+always \u9F50 245-16-2
+always \u9F51 13-16-3
+always \u9F52 12-156-4
+always \u9F53 12-136-5
+always \u9F54 12-136-5
+always \u9F55 1235-2346-2
+always \u9F56 23456-2
+always \u9F57 1456-2
+always \u9F58 15-346-5
+always \u9F59 135-146-3
+always \u9F5A 245-25-5
+always \u9F5B 15-346-5
+always \u9F5C 125-156-3
+always \u9F5D 12-156-3
+always \u9F5E 2345-4
+always \u9F5F 13-1256-4
+always \u9F60 124-246-2
+always \u9F61 14-13456-2
+always \u9F62 14-13456-2
+always \u9F63 12-34-3
+always \u9F64 245-45-2
+always \u9F65 15-346-5
+always \u9F66 1456-2
+always \u9F67 1345-346-5
+always \u9F68 13-234-5
+always \u9F69 246-4
+always \u9F6A 12-25-5
+always \u9F6B 123-123456-4
+always \u9F6C 1256-4
+always \u9F6D 12-34-4
+always \u9F6E 16-4
+always \u9F6F 1345-16-2
+always \u9F70 125-2346-2
+always \u9F71 125-12356-3
+always \u9F72 245-1256-4
+always \u9F73 45-4
+always \u9F74 2345-4
+always \u9F75 1256-2
+always \u9F76 2346-5
+always \u9F77 25-5
+always \u9F78 16-5
+always \u9F79 12-156-3
+always \u9F7A 125-12356-3
+always \u9F7B 145-2345-3
+always \u9F7C 12-34-4
+always \u9F7D 13-1456-5
+always \u9F7E 23456-5
+always \u9F7F 12-156-4
+always \u9F80 12-136-5
+always \u9F81 1235-2346-2
+always \u9F82 1456-2
+always \u9F83 13-1256-4
+always \u9F84 14-13456-2
+always \u9F85 135-146-3
+always \u9F86 124-246-2
+always \u9F87 125-156-3
+always \u9F88 1456-2
+always \u9F89 1256-4
+always \u9F8A 12-25-5
+always \u9F8B 245-1256-4
+always \u9F8C 25-5
+always \u9F8D 14-12346-2
+always \u9F8E 1234-1346-2
+always \u9F8F 13-12346-3
+always \u9F90 1234-1346-2
+always \u9F91 2345-4
+always \u9F92 14-12346-2
+always \u9F93 14-12346-2
+always \u9F94 13-12346-3
+always \u9F95 123-1236-3
+always \u9F96 124-345-5
+always \u9F97 14-13456-2
+always \u9F98 124-345-5
+always \u9F99 14-12346-2
+always \u9F9A 13-12346-3
+always \u9F9B 123-1236-3
+always \u9F9C 13-1246-3
+always \u9F9D 245-234-3
+always \u9F9E 135-346-3
+always \u9F9F 13-1246-3
+always \u9FA0 236-5
+always \u9FA1 12-1246-3
+always \u9FA2 1235-2346-2
+always \u9FA3 13-236-2
+always \u9FA4 15-346-2
+always \u9FA5 1256-5
+always \u9FA6 12-1346-2
+always \u9FA7 24-34-5
+always \u9FA8 1235-2356-2
+always \u9FA9 13-1456-5
+always \u9FAA 12345-34-5
+always \u9FAB 13-1236-5
+always \u9FAC 456-4
+always \u9FAD 16-5
+always \u9FAE 16-5
+always \u9FAF 236-5
+always \u9FB0 1-156-4
+always \u9FB1 15-1456-5
+always \u9FB2 123-34-5
+always \u9FB3 14-34-5
+always \u9FB4 16-4
+always \u9FB5 24-12356-4
+always \u9FB6 24-1356-3
+always \u9FB7 1345-2345-5
+always \u9FB8 12345-34-2
+always \u9FB9 13-45-5
+always \u9FBA 1-25-2
+always \u9FBB 14-12456-2
diff --git a/Tables/Contraction/zh_TW.ctb b/Tables/Contraction/zh_TW.ctb
new file mode 100644
index 0000000..51342ad
--- /dev/null
+++ b/Tables/Contraction/zh_TW.ctb
@@ -0,0 +1,3586 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Chinese (Taiwan, uncontracted)
+# Created by Coscell Kao <coscell@gmail.com> (2019-2)
+
+#English characters
+include nabcc.cti
+
+#Unicode symbols
+always \u00EF 4-24
+always \u00A0 0
+always \u00A1 34-2346
+always \u00A2 4-14
+always \u00A3 4-123
+always \u00A4 456-1246
+always \u00A5 36-134567-157-13457-36
+always \u00A7 1246-256
+always \u00A8 456-36
+always \u00A9 12356-147-23456
+always \u00AA 36-1247-157-1347-36
+always \u00AB 126-126
+always \u00AC 36-13457-1357-23457-36
+always \u00AD 36
+always \u00AE 12356-12357-23456
+always \u00AF 156-36
+always \u00B0 45-46-16
+always \u00B1 346-36
+always \u00B2 457-23
+always \u00B3 457-25
+always \u00B4 4
+always \u00B5 46-134
+always \u00B6 1246-1234
+always \u00B7 46
+always \u00B8 2346-4
+always \u00B9 457-2
+always \u00BA 36-1347-17-2347-36
+always \u00BB 345-345
+always \u00BC 2-34-256
+always \u00BD 2-34-23
+always \u00BE 25-34-256
+always \u00BF 34-1456
+always \u00C0 456-17
+always \u00C1 457-17
+always \u00C2 346-17
+always \u00C3 45-17
+always \u00C4 36-17
+always \u00C5 4-6-1
+always \u00C6 17-157
+always \u00D7 4-16
+always \u00F7 46-34
+always \u0251 46-1
+always \u02C7 34567-4
+always \u02C9 34567-3
+always \u02CA 34567-2
+always \u02CB 34567-5
+always \u02D9 34567-17
+always \u0391 46-17
+always \u0392 46-127
+always \u0393 46-12457
+always \u0394 46-1457
+always \u0395 46-157
+always \u0396 46-13567
+always \u0397 46-1567
+always \u0398 46-14567
+always \u0399 46-247
+always \u039A 46-137
+always \u039B 46-1237
+always \u039C 46-1347
+always \u039D 46-13457
+always \u039E 46-13467
+always \u039F 46-1357
+always \u03A0 46-12347
+always \u03A1 46-12357
+always \u03A3 46-2347
+always \u03A4 46-23457
+always \u03A5 46-1367
+always \u03A6 46-1247
+always \u03A7 46-123467
+always \u03A8 46-134567
+always \u03A9 46-24567
+always \u03B1 46-1
+always \u03B2 46-12
+always \u03B3 46-1245
+always \u03B4 46-145
+always \u03B5 46-15
+always \u03B6 46-1356
+always \u03B7 46-156
+always \u03B8 46-1456
+always \u03B9 46-24
+always \u03BA 46-13
+always \u03BB 46-123
+always \u03BC 46-134
+always \u03BD 46-1345
+always \u03BE 46-1346
+always \u03BF 46-135
+always \u03C0 46-1234
+always \u03C1 46-1235
+always \u03C3 46-234
+always \u03C4 46-2345
+always \u03C5 46-136
+always \u03C6 46-124
+always \u03C7 46-12346
+always \u03C8 46-13456
+always \u03C9 46-2456
+always \u200E 36-345
+always \u200F 126-36
+always \u2010 36
+always \u2011 5-2
+always \u2012 36
+always \u2013 36
+always \u2014 5-2
+always \u2015 36-36
+always \u2016 1246-123
+always \u2017 456-456
+always \u2018 4
+always \u2019 3
+always \u201C 236-236
+always \u201D 356-356
+always \u2020 346
+always \u2021 123456-123456-345
+always \u2022 36
+always \u2024 34567-1
+always \u2025 46-46
+always \u2026 5-5-5
+always \u2027 36
+always \u2028 1256
+always \u202F 0
+always \u2030 4-356-356
+always \u2032 3
+always \u2033 4-3456
+always \u2034 3-3-3
+always \u2035 3
+always \u2038 457
+always \u203B 46-16
+always \u203C 2346-2346
+always \u203D 1456-2346
+always \u203E 23467-578
+always \u2041 457
+always \u2042 16-16-16
+always \u2043 36
+always \u2044 34
+always \u2047 5
+always \u2048 1456-2346
+always \u2049 2346-1456
+always \u204E 16
+always \u2051 16-16
+always \u2052 36
+always \u2053 36
+always \u2056 46-46-46
+always \u2057 3-3-3-3
+always \u2058 46-46-46-46
+always \u2059 46-46-46-46-46
+always \u205A 46-46
+always \u205B 46-46-46-46
+always \u205F 0
+always \u2070 457-356
+always \u2071 457-24
+always \u2074 457-256
+always \u2075 457-26
+always \u2076 457-235
+always \u2077 457-2356
+always \u2078 457-236
+always \u2079 457-35
+always \u207A 457-346
+always \u207B 457-36
+always \u207C 457-123456
+always \u207D 457-12356
+always \u207E 457-23456
+always \u207F 457-1345
+always \u2080 56-356
+always \u2081 56-2
+always \u2082 56-23
+always \u2083 56-25
+always \u2084 56-256
+always \u2085 56-26
+always \u2086 56-235
+always \u2087 56-2356
+always \u2088 56-236
+always \u2089 56-35
+always \u208A 56-346
+always \u208B 56-36
+always \u208C 56-123456
+always \u208D 56-12356
+always \u208E 56-23456
+always \u2090 56-1
+always \u2091 56-15
+always \u2092 56-135
+always \u2093 56-1346
+always \u20A0 4-15
+always \u20A1 156
+always \u20AC 4-15
+always \u20B9 4-1235
+always \u2103 45-46-16-0-147
+always \u2109 45-46-16-0-1247
+always \u2122 12356-23457-1347-23456
+always \u2160 247
+always \u2161 247-247
+always \u2162 247-247-247
+always \u2163 247-12367
+always \u2164 12367
+always \u2165 12367-247
+always \u2166 12367-247-247
+always \u2167 12367-247-247-247
+always \u2168 247-13467
+always \u2169 13467
+always \u216A 13467-247
+always \u216B 13467-247-247
+always \u216C 13467-12367
+always \u2170 24
+always \u2171 24-24
+always \u2172 24-24-24
+always \u2173 24-1236
+always \u2174 1236
+always \u2175 1236-24
+always \u2176 1236-24-24
+always \u2177 1236-24-24-24
+always \u2178 24-1346
+always \u2179 1346
+always \u217A 1346-24
+always \u217B 1346-24-24
+always \u217C 1346-1236
+always \u2190 1246-246-25-25
+always \u2191 1246-126-25-25-135
+always \u2192 1246-25-25-135
+always \u2193 1246-146-25-25-135
+always \u2194 1246-246-25-25-135
+always \u2195 1246-126-246-25-25-135
+always \u2196 23467-348
+always \u2197 23467-3568
+always \u2198 23467-268
+always \u2199 23467-1568
+always \u21D2 123456-345
+always \u21B8 235678-2456
+always \u21B9 235678-1346
+always \u21E7 235678-1236
+always \u21E8 1246-25-25-135
+always \u2200 4-12346
+always \u2201 14-135-134-1234-123
+always \u2202 4-145
+always \u2203 4-123456
+always \u2204 2345-1345-15
+always \u2205 456-356
+always \u2206 24-1345-13
+always \u2207 46-1246
+always \u2208 4-15
+always \u2209 34-4-15
+always \u220A 234-15-123-134
+always \u220B 4-26
+always \u220C 34-4-26
+always \u220D 234-134-15-134
+always \u220e 15-135-1234
+always \u220F 46-12347
+always \u2211 46-2347
+always \u2212 36
+always \u2213 36-5-346
+always \u2215 34
+always \u2216 12467-127
+always \u2217 16
+always \u2218 45-46-16
+always \u221A 345-12456
+always \u221B 126-25-345-12456
+always \u221C 126-256-345-12456
+always \u221D 456-123456
+always \u221E 6-123456
+always \u221F 1246-246-46-1235-12456
+always \u2220 1246-246
+always \u2223 1256
+always \u2224 34-46-16
+always \u2225 1246-123
+always \u2226 34-1246-123
+always \u2227 4-146
+always \u2228 4-346
+always \u2229 46-146
+always \u222A 46-346
+always \u222B 23467-2368
+always \u222E 2346-4-1246-14-12456
+always \u2234 6-16
+always \u2235 4-34
+always \u2236 5-2
+always \u2237 1234-1235-135-1234
+always \u2238 46-36
+always \u223C 4-156-4-156
+always \u2242 156-4-156
+always \u2243 4-156-156
+always \u2244 34-4-156-156
+always \u2245 4-156-46-13
+always \u2248 4-156-4-156
+always \u2249 34-4-156-4-156
+always \u224C 4-156-123456
+always \u224D 1246-3-1246-1
+always \u2250 5-46-13-126-16-12456
+always \u2251 5-46-13-126-16-126-16-12456
+always \u2252 5-46-13-126-16-126-16-12456
+always \u2253 5-46-13-126-16-126-16-12456
+always \u225C 5-46-13-126-1246-2345-12456
+always \u2260 2346-123456
+always \u2261 456-123
+always \u2263 4-156-46-13
+always \u2264 126-123456
+always \u2265 345-123456
+always \u2266 126-123456
+always \u2267 345-123456
+always \u226A 5-13-4-5-13-12456
+always \u226B 46-2-4-46-2-12456
+always \u226D 2346-123456
+always \u226E 34-5-13
+always \u226F 34-46-2
+always \u2270 34-5-13-156
+always \u2271 34-46-2-156
+always \u2272 5-13-4-156
+always \u2273 46-2-4-156
+always \u2274 34-5-13-4-156
+always \u2275 34-46-2-4-156
+always \u2276 126-345
+always \u2277 345-126
+always \u2282 456-5-13
+always \u2283 456-46-2
+always \u2284 34-456-5-13
+always \u2285 34-456-46-2
+always \u2286 456-5-13-156
+always \u2287 456-46-2-156
+always \u2288 34-456-5-13-156
+always \u2289 34-456-46-2-156
+always \u2295 1246-14-456-1246-346-12456
+always \u2296 1246-14-456-1246-36-12456
+always \u2297 1246-14-456-1246-4-16-12456
+always \u2298 1246-14-456-1246-456-34-12456
+always \u2299 1246-14-456-1246-16-12456
+always \u22A5 1246-1234
+always \u22BF 1246-2345
+always \u22DC 123456-126
+always \u22DD 123456-345
+always \u22EF 5-5-5
+always \u2307 45
+always \u2460 246-2-135
+always \u2461 246-23-135
+always \u2462 246-25-135
+always \u2463 246-256-135
+always \u2464 246-26-135
+always \u2465 246-235-135
+always \u2466 246-2356-135
+always \u2467 246-236-135
+always \u2468 246-35-135
+always \u2469 246-2-356-135
+always \u246A 246-2-2-135
+always \u246B 246-2-23-135
+always \u246C 246-2-25-135
+always \u246D 246-2-256-135
+always \u246E 246-2-26-135
+always \u246F 246-2-235-135
+always \u2470 246-2-2356-135
+always \u2471 246-2-236-135
+always \u2472 246-2-35-135
+always \u2473 246-2-356-135
+always \u2474 12356-2-23456
+always \u2475 12356-23-23456
+always \u2476 12356-25-23456
+always \u2477 12356-256-23456
+always \u2478 12356-26-23456
+always \u2479 12356-235-23456
+always \u247A 12356-2356-23456
+always \u247B 12356-236-23456
+always \u247C 12356-35-23456
+always \u247D 12356-2-356-23456
+always \u247E 12356-2-2-23456
+always \u247F 12356-2-23-23456
+always \u2480 12356-2-25-23456
+always \u2481 12356-2-256-23456
+always \u2482 12356-2-26-23456
+always \u2483 12356-2-235-23456
+always \u2484 12356-2-2356-23456
+always \u2485 12356-2-236-23456
+always \u2486 12356-2-35-23456
+always \u2487 12356-23-356-23456
+always \u2488 2
+always \u2489 23
+always \u248A 25
+always \u248B 256
+always \u248C 26
+always \u248D 235
+always \u248E 2356
+always \u248F 236
+always \u2490 35
+always \u2491 2-356
+always \u2492 2-2
+always \u2493 2-23
+always \u2494 2-25
+always \u2495 2-256
+always \u2496 2-26
+always \u2497 2-235
+always \u2498 2-2356
+always \u2499 2-236
+always \u249A 2-35
+always \u249B 23-356
+always \u249C 12356-1-23456
+always \u249D 12356-12-23456
+always \u249E 12356-14-23456
+always \u249F 12356-145-23456
+always \u24A0 12356-15-23456
+always \u24A1 12356-124-23456
+always \u24A2 12356-1245-23456
+always \u24A3 12356-125-23456
+always \u24A4 12356-24-23456
+always \u24A5 12356-245-23456
+always \u24A6 12356-13-23456
+always \u24A7 12356-123-23456
+always \u24A8 12356-134-23456
+always \u24A9 12356-1345-23456
+always \u24AA 12356-135-23456
+always \u24AB 12356-1234-23456
+always \u24AC 12356-12345-23456
+always \u24AD 12356-1235-23456
+always \u24AE 12356-234-23456
+always \u24AF 12356-2345-23456
+always \u24B0 12356-136-23456
+always \u24B1 12356-1236-23456
+always \u24B2 12356-2456-23456
+always \u24B3 12356-1346-23456
+always \u24B4 12356-13456-23456
+always \u24B5 12356-1356-23456
+always \u24B6 246-17-135
+always \u24B7 246-127-135
+always \u24B8 246-147-135
+always \u24B9 246-1457-135
+always \u24BA 246-157-135
+always \u24BB 246-1247-135
+always \u24BC 246-12457-135
+always \u24BD 246-1257-135
+always \u24BE 246-247-135
+always \u24BF 246-2457-135
+always \u24C0 246-137-135
+always \u24C1 246-1237-135
+always \u24C2 246-1347-135
+always \u24C3 246-13457-135
+always \u24C4 246-1357-135
+always \u24C5 246-12347-135
+always \u24C6 246-123457-135
+always \u24C7 246-12357-135
+always \u24C8 246-2347-135
+always \u24C9 246-23457-135
+always \u24CA 246-1367-135
+always \u24CB 246-12367-135
+always \u24CC 246-24567-135
+always \u24CD 246-13467-135
+always \u24CE 246-134567-135
+always \u24CF 246-13567-135
+always \u24D0 246-1-135
+always \u24D1 246-12-135
+always \u24D2 246-14-135
+always \u24D3 246-145-135
+always \u24D4 246-15-135
+always \u24D5 246-124-135
+always \u24D6 246-1245-135
+always \u24D7 246-125-135
+always \u24D8 246-24-135
+always \u24D9 246-245-135
+always \u24DA 246-13-135
+always \u24DB 246-123-135
+always \u24DC 246-134-135
+always \u24DD 246-1345-135
+always \u24DE 246-135-135
+always \u24DF 246-1234-135
+always \u24E0 246-12345-135
+always \u24E1 246-1235-135
+always \u24E2 246-234-135
+always \u24E3 246-2345-135
+always \u24E4 246-136-135
+always \u24E5 246-1236-135
+always \u24E6 246-2456-135
+always \u24E7 246-1346-135
+always \u24E8 246-13456-135
+always \u24E9 246-1356-135
+always \u24EA 246-356-135
+always \u2500 36-36
+always \u2574 1256
+always \u2584 3678-3678
+always \u25a0 12345678-12345678
+always \u25A1 123478-145678
+always \u25AA 123456-123456
+always \u25B2 1246-2345
+always \u25B3 1246-2345
+always \u25B6 58-3
+always \u25B7 58-3
+always \u25BC 1246-2345
+always \u25BD 1246-2345
+always \u25C6 1246-145
+always \u25C7 1246-145
+always \u25CB 246-135
+always \u25CE 246-246-135-135
+always \u25CF 234568-123567
+always \u25E6 46
+always \u25FE 123456-123456
+always \u2605 16
+always \u2606 16
+always \u2713 12378-67
+always \u2714 2467-1346-124567
+always \u276F 345
+always \u2794 36-36-345
+always \u2E1F 5-4-156-146-16-12456
+always \u2F08 1245-136-2
+always \u2F1F 124-34-4
+always \u2F2F 13-12346-3
+always \u2F45 12345-1346-3
+always \u2FAF 134-2345-5
+always \u2FD1 245-16-2
+#Chinese characters
+always \u2f00 16-3
+always \u2f01 13-123456-4
+always \u2f02 1-34-4
+always \u2f03 1234-346-4
+always \u2f04 16-4
+always \u2f05 13-236-2
+always \u2f06 156-5
+always \u2f07 124-12356-2
+always \u2f08 1245-136-2
+always \u2f09 1245-136-2
+always \u2f0a 1245-34-5
+always \u2f0b 135-345-3
+always \u2f0c 13-6-3
+always \u2f0d 134-16-5
+always \u2f0e 135-13456-3
+always \u2f0f 13-16-4
+always \u2f10 123-1236-4
+always \u2f11 145-146-3
+always \u2f12 13-16-5
+always \u2f13 135-146-3
+always \u2f14 135-16-4
+always \u2f15 12345-1346-3
+always \u2f16 15-16-5
+always \u2f17 24-156-2
+always \u2f18 135-34-4
+always \u2f19 13-346-2
+always \u2f1a 12-1346-4
+always \u2f1b 15-156-3
+always \u2f1c 234-5
+always \u2f1d 123-12356-4
+always \u2f1e 1246-2
+always \u2f1f 124-34-4
+always \u2f20 24-156-5
+always \u2f21 1-156-4
+always \u2f22 15-1246-3
+always \u2f23 15-16-5
+always \u2f24 145-345-5
+always \u2f25 1345-1256-4
+always \u2f26 125-156-4
+always \u2f27 134-2345-2
+always \u2f28 245-123456-5
+always \u2f29 15-246-4
+always \u2f2a 456-3
+always \u2f2b 24-156-3
+always \u2f2c 12-2346-5
+always \u2f2d 24-1236-3
+always \u2f2e 12-12456-3
+always \u2f2f 13-12346-3
+always \u2f30 13-16-4
+always \u2f31 13-1456-3
+always \u2f32 13-1236-3
+always \u2f33 246-3
+always \u2f34 13-456-4
+always \u2f35 1456-4
+always \u2f36 13-12346-4
+always \u2f37 16-5
+always \u2f38 13-12346-3
+always \u2f39 13-16-5
+always \u2f3a 24-1236-3
+always \u2f3b 12-156-5
+always \u2f3c 15-1456-3
+always \u2f3d 13-2346-3
+always \u2f3e 1235-34-5
+always \u2f3f 24-12356-4
+always \u2f40 1-156-3
+always \u2f41 1234-34-3
+always \u2f42 123456-2
+always \u2f43 145-12356-4
+always \u2f44 13-1456-3
+always \u2f45 12345-1346-3
+always \u2f46 34-2
+always \u2f47 1245-156-5
+always \u2f48 236-3
+always \u2f49 236-5
+always \u2f4a 134-34-5
+always \u2f4b 245-2345-5
+always \u2f4c 1-156-4
+always \u2f4d 145-2456-4
+always \u2f4e 24-34-3
+always \u2f4f 34-2
+always \u2f50 135-16-4
+always \u2f51 134-146-2
+always \u2f52 24-156-5
+always \u2f53 245-16-5
+always \u2f54 24-1246-4
+always \u2f55 1235-25-4
+always \u2f56 1-35-4
+always \u2f57 12345-34-5
+always \u2f58 246-2
+always \u2f59 135-1236-5
+always \u2f5a 1234-2345-5
+always \u2f5b 23456-2
+always \u2f5c 1345-234-2
+always \u2f5d 245-45-4
+always \u2f5e 15-45-2
+always \u2f5f 1256-5
+always \u2f60 13-35-3
+always \u2f61 35-4
+always \u2f62 13-1236-3
+always \u2f63 24-1356-3
+always \u2f64 235-5
+always \u2f65 124-2345-2
+always \u2f66 1234-16-4
+always \u2f67 12-456-2
+always \u2f68 135-126-3
+always \u2f69 135-2456-2
+always \u2f6a 1234-16-2
+always \u2f6b 134-1456-4
+always \u2f6c 134-34-5
+always \u2f6d 134-146-2
+always \u2f6e 24-156-4
+always \u2f6f 24-156-2
+always \u2f70 24-156-5
+always \u2f71 1245-12356-2
+always \u2f72 1235-2346-2
+always \u2f73 15-236-5
+always \u2f74 13-16-5
+always \u2f75 1-34-2
+always \u2f76 134-16-4
+always \u2f77 134-16-5
+always \u2f78 12345-12356-4
+always \u2f79 456-4
+always \u2f7a 46-2
+always \u2f7b 1256-4
+always \u2f7c 13-146-4
+always \u2f7d 156-2
+always \u2f7e 13-356-4
+always \u2f7f 156-4
+always \u2f80 1256-5
+always \u2f81 1245-12356-5
+always \u2f82 12-136-2
+always \u2f83 125-156-5
+always \u2f84 1-156-5
+always \u2f85 13-234-5
+always \u2f86 24-2346-2
+always \u2f87 12-12456-4
+always \u2f88 1-12356-3
+always \u2f89 13-136-5
+always \u2f8a 15-2346-5
+always \u2f8b 245-146-4
+always \u2f8c 1235-34-3
+always \u2f8d 12-12346-2
+always \u2f8e 15-346-4
+always \u2f8f 15-13456-2
+always \u2f90 16-3
+always \u2f91 23456-5
+always \u2f92 13-2345-5
+always \u2f93 13-246-4
+always \u2f94 2345-2
+always \u2f95 13-34-4
+always \u2f96 145-12356-5
+always \u2f97 24-156-4
+always \u2f98 1-156-5
+always \u2f99 135-356-5
+always \u2f9a 12-156-5
+always \u2f9b 125-12356-4
+always \u2f9c 125-34-2
+always \u2f9d 24-136-3
+always \u2f9e 12-2346-3
+always \u2f9f 15-1456-3
+always \u2fa0 12-136-2
+always \u2fa1 12-25-5
+always \u2fa2 16-5
+always \u2fa3 234-4
+always \u2fa4 135-2345-5
+always \u2fa5 13-16-4
+always \u2fa6 13-1456-3
+always \u2fa7 12-1346-2
+always \u2fa8 134-136-2
+always \u2fa9 12345-34-5
+always \u2faa 13-16-5
+always \u2fab 1-1246-3
+always \u2fac 1256-4
+always \u2fad 245-13456-3
+always \u2fae 12345-356-3
+always \u2faf 134-2345-5
+always \u2fb0 13-2346-2
+always \u2fb1 1246-2
+always \u2fb2 13-234-4
+always \u2fb3 1456-3
+always \u2fb4 346-5
+always \u2fb5 12345-1356-3
+always \u2fb6 12345-356-3
+always \u2fb7 24-156-2
+always \u2fb8 24-12356-4
+always \u2fb9 15-46-3
+always \u2fba 134-345-4
+always \u2fbb 13-34-4
+always \u2fbc 13-146-3
+always \u2fbd 135-246-3
+always \u2fbe 145-12356-5
+always \u2fbf 12-1346-5
+always \u2fc0 13-2346-2
+always \u2fc1 13-1246-4
+always \u2fc2 1256-2
+always \u2fc3 1345-246-4
+always \u2fc4 13-34-4
+always \u2fc5 13-34-5
+always \u2fc6 134-2456-5
+always \u2fc7 134-345-2
+always \u2fc8 1235-456-2
+always \u2fc9 24-34-4
+always \u2fca 1235-356-3
+always \u2fcb 1-156-4
+always \u2fcc 134-1456-4
+always \u2fcd 145-13456-4
+always \u2fce 13-34-4
+always \u2fcf 24-34-4
+always \u2fd0 135-16-2
+always \u2fd1 245-16-2
+always \u2fd2 12-156-4
+always \u2fd3 13-12346-2
+always \u2fd4 13-1246-3
+always \u2fd5 236-5
+always \u3000 0
+always \u3001 6-0
+always \u3002 36-0
+always \u3003 6-3
+always \u3008 126
+always \u3009 345
+always \u300A 126-126
+always \u300B 345-345
+always \u300C 56-36
+always \u300D 36-23
+always \u300E 236-236
+always \u300F 356-356
+always \u3010 2467-0
+always \u3011 124567-0
+always \u3012 12347-1357
+always \u3013 12467-1467
+always \u3014 12346-0
+always \u3015 13456-0
+always \u301C 45
+always \u301D 3-3
+always \u301E 3-3
+always \u3021 24
+always \u3022 24-24
+always \u3023 24-24-24
+always \u3024 24-1236
+always \u3025 1236
+always \u3026 1236-24
+always \u3027 1236-24-24
+always \u3028 1236-24-24-24
+always \u3029 24-1346
+always \u3039 1346-1346
+always \u303A 1346-1346-1346
+always \u3042 1
+always \u3044 12
+always \u3046 14
+always \u3048 124
+always \u304A 24
+always \u304B 16
+always \u304C 5-16
+always \u304D 126
+always \u304E 5-126
+always \u304F 146
+always \u3050 5-146
+always \u3051 1246
+always \u3052 5-1246
+always \u3053 246
+always \u3054 5-246
+always \u3055 156
+always \u3056 5-156
+always \u3057 1256
+always \u3058 5-1256
+always \u305A 5-1456
+always \u305B 12456
+always \u305C 5-12456
+always \u305D 2456
+always \u305E 5-2456
+always \u305F 135
+always \u3060 5-135
+always \u3061 1235
+always \u3062 5-1235
+always \u3063 2
+always \u3064 1345
+always \u3065 5-1345
+always \u3066 12345
+always \u3067 5-12345
+always \u3068 2345
+always \u3069 5-2345
+always \u306A 13
+always \u306B 123
+always \u306C 134
+always \u306D 1234
+always \u306E 234
+always \u306F 136
+always \u3070 5-136
+always \u3071 6-136
+always \u3072 1236
+always \u3073 5-1236
+always \u3074 6-1236
+always \u3075 1346
+always \u3076 5-1346
+always \u3077 6-1346
+always \u3078 12346
+always \u3079 5-12346
+always \u307A 6-12346
+always \u307B 2346
+always \u307C 5-2346
+always \u307D 6-2346
+always \u307E 1356
+always \u307F 12356
+always \u3080 13456
+always \u3081 123456
+always \u3082 23456
+always \u3083 34678-245
+always \u3084 34
+always \u3085 34678-123
+always \u3086 346
+always \u3087 34678-1345
+always \u3088 345
+always \u3089 15
+always \u308A 125
+always \u308B 145
+always \u308C 1245
+always \u308D 245
+always \u308E 34678-136
+always \u308F 3
+always \u3090 34678-2456
+always \u3091 34678-1346
+always \u3092 35
+always \u3093 356
+always \u3094 235678-257
+always \u3095 16
+always \u3096 1246
+always \u30A1 1
+always \u30A2 1
+always \u30A3 12
+always \u30A4 12
+always \u30A5 14
+always \u30A6 14
+always \u30A7 124
+always \u30A8 124
+always \u30AA 24
+always \u30AB 16
+always \u30AC 5-16
+always \u30AD 126
+always \u30AE 5-126
+always \u30AF 146
+always \u30B0 5-146
+always \u30B1 1246
+always \u30B2 5-1246
+always \u30B3 246
+always \u30B4 5-246
+always \u30B5 156
+always \u30B6 5-156
+always \u30B7 1256
+always \u30B8 5-1256
+always \u30BA 5-1456
+always \u30BB 12456
+always \u30BC 5-12456
+always \u30BD 2456
+always \u30BE 5-2456
+always \u30BF 135
+always \u30C0 5-135
+always \u30C1 1235
+always \u30C2 5-1235
+always \u30C3 2
+always \u30C4 1345
+always \u30C5 5-1345
+always \u30C6 12345
+always \u30C7 5-12345
+always \u30C8 2345
+always \u30C9 5-2345
+always \u30CA 13
+always \u30CB 123
+always \u30CC 134
+always \u30CD 1234
+always \u30CE 234
+always \u30CF 136
+always \u30D0 5-136
+always \u30D1 6-136
+always \u30D2 1236
+always \u30D3 5-1236
+always \u30D4 6-1236
+always \u30D5 1346
+always \u30D6 5-1346
+always \u30D7 6-1346
+always \u30D8 12346
+always \u30D9 5-12346
+always \u30DA 6-12346
+always \u30DB 2346
+always \u30DC 5-2346
+always \u30DD 6-2346
+always \u30DE 1356
+always \u30DF 12356
+always \u30E0 13456
+always \u30E1 123456
+always \u30E2 23456
+always \u30E3 34678-345678
+always \u30E4 34
+always \u30E5 34678-168
+always \u30E6 346
+always \u30E7 345
+always \u30E8 345
+always \u30E9 15
+always \u30EA 125
+always \u30EB 145
+always \u30EC 1245
+always \u30ED 245
+always \u30EE 34678-238
+always \u30EF 3
+always \u30F0 34678-2368
+always \u30F1 34678-1468
+always \u30F2 35
+always \u30F3 356
+always \u30F4 34678-68
+always \u30F5 34678-468
+always \u30F6 34678-358
+always \u30F7 235678-37
+always \u30F8 235678-567
+always \u30F9 235678-2357
+always \u30FA 235678-467
+always \u30FB 5
+always \u30FC 25
+always \u30FD 378-15678
+always \u30FE 378-2678
+always \u30FF 246-26
+always \u3105 34567-135
+always \u3106 34567-1234
+always \u3107 34567-134
+always \u3108 34567-12345
+always \u3109 34567-145
+always \u310A 34567-124
+always \u310B 34567-1345
+always \u310C 34567-14
+always \u310D 34567-13
+always \u310E 34567-123
+always \u310F 34567-1235
+always \u3110 34567-137
+always \u3111 34567-2457
+always \u3112 34567-157
+always \u3113 34567-1
+always \u3114 34567-12
+always \u3115 34567-24
+always \u3116 34567-1245
+always \u3117 34567-125
+always \u3118 34567-245
+always \u3119 34567-15
+always \u311A 34567-345
+always \u311B 34567-126
+always \u311C 34567-2346
+always \u311D 34567-26
+always \u311E 34567-2456
+always \u311F 34567-356
+always \u3120 34567-146
+always \u3121 34567-12356
+always \u3122 34567-1236
+always \u3123 34567-136
+always \u3124 34567-1346
+always \u3125 34567-1356
+always \u3126 34567-156
+always \u3127 34567-16
+always \u3128 34567-34
+always \u3129 34567-1256
+always \u3192 16-3
+always \u3193 156-5
+always \u3194 15-1236-3
+always \u3195 15-156-5
+always \u3196 24-1346-5
+always \u3197 1-12346-3
+always \u3198 15-23456-5
+always \u3199 13-23456-4
+always \u319A 16-4
+always \u319B 135-13456-4
+always \u319C 145-13456-3
+always \u319D 124-2345-3
+always \u319E 145-16-5
+always \u319F 1245-136-2
+always \u31F0 146
+always \u31F1 1256
+always \u31F3 2345
+always \u31F4 134
+always \u31F5 136
+always \u31F6 1236
+always \u31F7 1346
+always \u31F8 12346
+always \u31F9 2346
+always \u31FA 13456
+always \u31FB 15
+always \u31FC 125
+always \u31FD 145
+always \u31FE 1245
+always \u31FF 245
+always \u3220 12356-16-3-23456
+always \u3221 12356-156-5-23456
+always \u3222 12356-15-1236-3-23456
+always \u3223 12356-15-156-5-23456
+always \u3224 12356-34-4-23456
+always \u3225 12356-14-234-5-23456
+always \u3226 12356-245-16-3-23456
+always \u3227 12356-135-345-3-23456
+always \u3228 12356-13-234-4-23456
+always \u3229 12356-24-156-2-23456
+always \u322A 12356-236-5-23456
+always \u322B 12356-1235-25-4-23456
+always \u322C 12356-24-1246-4-23456
+always \u322D 12356-134-34-5-23456
+always \u322E 12356-13-1456-3-23456
+always \u322F 12356-124-34-4-23456
+always \u3230 12356-1245-156-5-23456
+always \u3231 12356-1-34-3-23456
+always \u3232 12356-234-4-23456
+always \u3233 12356-24-2346-5-23456
+always \u3234 12356-134-13456-2-23456
+always \u3235 12356-124-2346-5-23456
+always \u3236 12356-245-2456-2-23456
+always \u3237 12356-1-34-5-23456
+always \u3238 12356-14-146-2-23456
+always \u3239 12356-145-2456-5-23456
+always \u323A 12356-1235-34-3-23456
+always \u323B 12356-15-236-2-23456
+always \u323C 12356-13-2345-3-23456
+always \u323D 12356-245-16-5-23456
+always \u323E 12356-125-156-3-23456
+always \u323F 12356-15-346-2-23456
+always \u3240 12356-13-16-5-23456
+always \u3241 12356-15-234-3-23456
+always \u3242 12356-125-156-5-23456
+always \u3243 12356-1-156-5-23456
+always \u3251 246-23-2-135
+always \u3252 246-23-23-135
+always \u3253 246-23-25-135
+always \u3254 246-23-256-135
+always \u3255 246-23-26-135
+always \u3256 246-23-235-135
+always \u3257 246-23-2356-135
+always \u3258 246-23-236-135
+always \u3259 246-23-35-135
+always \u325A 246-25-356-135
+always \u325B 246-25-2-135
+always \u325C 246-25-23-135
+always \u325D 246-25-25-135
+always \u325E 246-25-256-135
+always \u325F 246-25-26-135
+always \u3280 246-16-3-135
+always \u3281 246-156-5-135
+always \u3282 246-15-1236-3-135
+always \u3283 246-15-156-5-135
+always \u3284 246-34-4-135
+always \u3285 246-14-234-5-135
+always \u3286 246-245-16-3-135
+always \u3287 246-135-345-3-135
+always \u3288 246-13-234-4-135
+always \u3289 246-24-156-2-135
+always \u328A 246-236-5-135
+always \u328B 246-1235-25-4-135
+always \u328C 246-24-1246-4-135
+always \u328D 246-134-34-5-135
+always \u328E 246-13-1456-3-135
+always \u328F 246-124-34-4-135
+always \u3290 246-1245-156-5-135
+always \u3291 246-1-34-3-135
+always \u3292 246-234-4-135
+always \u3293 246-24-2346-5-135
+always \u3294 246-134-13456-2-135
+always \u3295 246-124-2346-5-135
+always \u3296 246-245-2456-2-135
+always \u3297 246-1-34-5-135
+always \u3298 246-14-146-2-135
+always \u3299 246-134-16-5-135
+always \u329A 246-1345-1236-2-135
+always \u329B 246-1345-1256-4-135
+always \u329C 246-24-156-5-135
+always \u329D 246-234-3-135
+always \u329E 246-1456-5-135
+always \u329F 246-1-34-5-135
+always \u32A0 246-15-46-5-135
+always \u32A1 246-15-234-3-135
+always \u32A2 246-15-346-4-135
+always \u32A3 246-1-1356-5-135
+always \u32A4 246-24-1346-5-135
+always \u32A5 246-1-12346-3-135
+always \u32A6 246-15-23456-5-135
+always \u32A7 246-125-25-4-135
+always \u32A8 246-234-5-135
+always \u32A9 246-16-3-135
+always \u32AA 246-125-12346-3-135
+always \u32AB 246-15-236-2-135
+always \u32AC 246-13-2345-3-135
+always \u32AD 246-245-16-5-135
+always \u32AE 246-125-156-3-135
+always \u32AF 246-15-346-2-135
+always \u32B0 246-346-5-135
+always \u32B1 246-25-235-135
+always \u32B2 246-25-2356-135
+always \u32B3 246-25-236-135
+always \u32B4 246-25-35-135
+always \u32B5 246-256-356-135
+always \u32B6 246-256-2-135
+always \u32B7 246-256-23-135
+always \u32B8 246-256-25-135
+always \u32B9 246-256-256-135
+always \u32BA 246-256-26-135
+always \u32BB 246-256-235-135
+always \u32BC 246-256-2356-135
+always \u32BD 246-256-236-135
+always \u32BE 246-256-35-135
+always \u32BF 246-26-356-135
+always \u32C0 245-1-1346-46
+always \u32C1 124-15-12-46
+always \u32C2 134-1-1235-46
+always \u32C3 1-1234-1235-46
+always \u32C4 134-1-13456-46
+always \u32C5 245-136-1345-46
+always \u32C6 245-136-123-46
+always \u32C7 1-136-1245-46
+always \u32C8 234-15-1234-46
+always \u32C9 135-14-2345-46
+always \u32CA 1345-135-1236-46
+always \u32CB 145-15-14-46
+always \u32D0 246-1-135
+always \u32D1 246-12-135
+always \u32D2 246-14-135
+always \u32D3 246-124-135
+always \u32D4 246-24-135
+always \u32D5 246-16-135
+always \u32D6 246-126-135
+always \u32D7 246-146-135
+always \u32D8 246-1246-135
+always \u32D9 246-246-135
+always \u32DA 246-156-135
+always \u32DB 246-1256-135
+always \u32DC 246-1456-135
+always \u32DD 246-12456-135
+always \u32DE 246-2456-135
+always \u32DF 246-135-135
+always \u32E0 246-1235-135
+always \u32E1 246-1345-135
+always \u32E2 246-12345-135
+always \u32E3 246-2345-135
+always \u32E4 246-13-135
+always \u32E5 246-123-135
+always \u32E6 246-134-135
+always \u32E7 246-1234-135
+always \u32E8 246-234-135
+always \u32E9 246-136-135
+always \u32EA 246-1236-135
+always \u32EB 246-1346-135
+always \u32EC 246-12346-135
+always \u32ED 246-2346-135
+always \u32EE 246-1356-135
+always \u32EF 246-12356-135
+always \u32F0 246-13456-135
+always \u32F1 246-123456-135
+always \u32F2 246-23456-135
+always \u32F3 246-34-135
+always \u32F4 246-346-135
+always \u32F5 246-345-135
+always \u32F6 246-15-135
+always \u32F7 246-125-135
+always \u32F8 246-145-135
+always \u32F9 246-1245-135
+always \u32FA 246-245-135
+always \u32FB 246-3-135
+always \u32FC 246-12-135
+always \u32FD 246-14-135
+always \u32FE 246-35-135
+always \u3358 356-125
+always \u3359 2-125
+always \u335A 23-125
+always \u335B 25-125
+always \u335C 256-125
+always \u335D 26-125
+always \u335E 235-125
+always \u335F 2356-125
+always \u3360 236-125
+always \u3361 35-125
+always \u3362 2-356-125
+always \u3363 2-2-125
+always \u3364 2-23-125
+always \u3365 2-25-125
+always \u3366 2-256-125
+always \u3367 2-26-125
+always \u3368 2-235-125
+always \u3369 2-2356-125
+always \u336A 2-236-125
+always \u336B 2-35-125
+always \u336C 23-356-125
+always \u336D 23-2-125
+always \u336E 23-23-125
+always \u336F 23-25-125
+always \u3370 23-256-125
+always \u3380 1234-1-12346
+always \u3381 1345-1
+always \u3382 134-136-1
+always \u3383 134-1
+always \u3384 13-1
+always \u3385 13-12
+always \u3386 134-12
+always \u3387 1245-12
+always \u3388 14-1-123
+always \u3389 13-14-1-123
+always \u338A 1234-124
+always \u338B 1345-124
+always \u338C 134-136-124
+always \u338D 134-136-1245
+always \u338E 134-1245
+always \u338F 13-1245
+always \u3390 125-1356
+always \u3391 13-125-1356
+always \u3392 134-125-1356
+always \u3393 1245-125-1356
+always \u3394 2345-125-1356
+always \u3395 134-136-123
+always \u3396 134-123
+always \u3397 145-123
+always \u3398 13-123
+always \u3399 124-134
+always \u339A 1345-134
+always \u339B 134-136-134
+always \u339C 134-134
+always \u339D 14-134
+always \u339E 13-134
+always \u339F 134-134-457-23
+always \u33A0 14-134-457-23
+always \u33A1 134-457-23
+always \u33A2 13-134-457-23
+always \u33A3 134-134-457-25
+always \u33A4 14-134-457-25
+always \u33A5 134-457-25
+always \u33A6 13-134-457-25
+always \u33D5 134-24-123
+always \u33E0 2-145
+always \u33E1 23-145
+always \u33E2 25-145
+always \u33E3 256-145
+always \u33E4 26-145
+always \u33E5 235-145
+always \u33E6 2356-145
+always \u33E7 236-145
+always \u33E8 35-145
+always \u33E9 2-356-145
+always \u33EA 2-2-145
+always \u33EB 2-23-145
+always \u33EC 2-25-145
+always \u33ED 2-256-145
+always \u33EE 2-26-145
+always \u33EF 2-235-145
+always \u33F0 2-2356-145
+always \u33F1 2-236-145
+always \u33F2 2-35-145
+always \u33F3 23-356-145
+always \u33F4 23-2-145
+always \u33F5 23-23-145
+always \u33F6 23-25-145
+always \u33F7 23-256-145
+always \u33F8 23-26-145
+always \u33F9 23-235-145
+always \u33FA 23-2356-145
+always \u33FB 23-236-145
+always \u33FC 23-35-145
+always \u33FD 25-356-145
+always \u33FE 25-2-145
+always \u67D0 134-12356-4
+always \uFB01 124-24
+always \uFB02 124-123
+
+include zh-tw.cti
+
+always \uE18C 12356-17-23456
+always \uE18D 12356-127-23456
+always \uE18E 12356-147-23456
+always \uE18F 12356-1457-23456
+always \uF900 245-16-4
+always \uF901 13-1356-5
+always \uF902 12-2346-3
+always \uF903 13-23456-4
+always \uF904 1235-35-2
+always \uF905 12-12456-5
+always \uF906 13-1256-5
+always \uF907 13-1246-3
+always \uF908 13-1246-3
+always \uF909 245-16-5
+always \uF90A 13-1456-3
+always \uF90B 14-345-4
+always \uF90C 1345-2456-5
+always \uF90D 14-1236-4
+always \uF90E 14-2456-5
+always \uF90F 14-25-2
+always \uF910 14-25-2
+always \uF911 14-25-2
+always \uF912 14-25-4
+always \uF913 14-25-2
+always \uF914 14-2346-5
+always \uF915 14-146-5
+always \uF916 14-25-5
+always \uF917 14-25-5
+always \uF918 14-25-5
+always \uF919 14-146-5
+always \uF91A 14-25-5
+always \uF91B 14-12456-5
+always \uF91C 14-12456-4
+always \uF91D 14-1236-2
+always \uF91E 14-1236-5
+always \uF91F 14-1236-2
+always \uF920 14-12456-2
+always \uF921 14-1236-2
+always \uF922 14-1236-5
+always \uF923 14-1236-2
+always \uF924 14-1236-2
+always \uF925 14-345-3
+always \uF926 14-345-5
+always \uF927 14-345-5
+always \uF928 14-1346-2
+always \uF929 14-1346-4
+always \uF92A 14-1346-5
+always \uF92B 14-1346-2
+always \uF92C 14-1346-2
+always \uF92D 14-2456-2
+always \uF92E 14-1356-4
+always \uF92F 14-146-2
+always \uF930 14-34-4
+always \uF931 14-34-4
+always \uF932 14-34-2
+always \uF933 14-34-2
+always \uF934 14-146-4
+always \uF935 14-34-2
+always \uF936 14-34-4
+always \uF937 14-34-5
+always \uF938 14-34-5
+always \uF939 14-34-4
+always \uF93A 14-34-5
+always \uF93B 14-34-5
+always \uF93C 14-34-5
+always \uF93D 14-1256-5
+always \uF93E 14-34-5
+always \uF93F 14-34-5
+always \uF940 14-34-5
+always \uF941 14-123456-5
+always \uF942 14-12346-4
+always \uF943 1345-12346-5
+always \uF944 14-12346-2
+always \uF945 14-12346-2
+always \uF946 14-146-2
+always \uF947 14-356-4
+always \uF948 14-34-5
+always \uF949 14-356-2
+always \uF94A 14-356-4
+always \uF94B 14-1256-4
+always \uF94C 14-12356-2
+always \uF94D 14-356-5
+always \uF94E 14-12356-5
+always \uF94F 14-356-5
+always \uF950 14-1256-4
+always \uF951 14-12356-5
+always \uF952 14-2346-5
+always \uF953 14-356-5
+always \uF954 14-1456-4
+always \uF955 14-13456-2
+always \uF956 14-1356-2
+always \uF957 14-13456-2
+always \uF958 14-13456-2
+always \uF959 14-13456-2
+always \uF95A 145-34-2
+always \uF95B 1345-345-2
+always \uF95C 14-2346-5
+always \uF95D 1345-25-5
+always \uF95E 145-1236-3
+always \uF95F 1345-13456-2
+always \uF960 1345-34-5
+always \uF961 14-1256-5
+always \uF962 16-5
+always \uF963 135-356-4
+always \uF964 1234-1236-2
+always \uF965 135-2345-5
+always \uF966 12345-34-5
+always \uF967 135-34-5
+always \uF968 134-16-5
+always \uF969 24-34-5
+always \uF96A 15-25-4
+always \uF96B 245-1236-3
+always \uF96C 15-2346-5
+always \uF96D 24-1356-4
+always \uF96E 346-5
+always \uF96F 24-25-3
+always \uF970 24-345-3
+always \uF971 12-136-2
+always \uF972 24-136-4
+always \uF973 24-156-2
+always \uF974 1245-25-5
+always \uF975 14-236-5
+always \uF976 14-236-5
+always \uF977 14-46-5
+always \uF978 14-46-4
+always \uF979 14-46-2
+always \uF97A 14-46-2
+always \uF97B 14-46-2
+always \uF97C 14-46-2
+always \uF97D 14-46-5
+always \uF97E 14-46-5
+always \uF97F 14-16-5
+always \uF980 14-1256-4
+always \uF981 1345-1256-4
+always \uF982 14-34-2
+always \uF983 14-1256-4
+always \uF984 14-1256-5
+always \uF985 14-16-5
+always \uF986 14-1256-2
+always \uF987 14-16-2
+always \uF988 14-16-5
+always \uF989 14-16-2
+always \uF98A 14-16-5
+always \uF98B 14-16-5
+always \uF98C 14-16-5
+always \uF98D 14-16-5
+always \uF98E 1345-2345-2
+always \uF98F 14-2345-2
+always \uF990 14-2345-5
+always \uF991 1345-2345-4
+always \uF992 14-2345-2
+always \uF993 14-2345-5
+always \uF994 14-2345-4
+always \uF995 1345-2345-2
+always \uF996 14-2345-5
+always \uF997 14-2345-2
+always \uF998 1345-2345-4
+always \uF999 14-2345-2
+always \uF99A 14-2345-2
+always \uF99B 14-2345-5
+always \uF99C 14-346-5
+always \uF99D 14-346-5
+always \uF99E 2345-5
+always \uF99F 14-346-5
+always \uF9A0 14-346-5
+always \uF9A1 24-25-3
+always \uF9A2 14-2345-2
+always \uF9A3 1345-2345-5
+always \uF9A4 1345-2345-4
+always \uF9A5 14-2345-5
+always \uF9A6 14-2345-2
+always \uF9A7 14-346-5
+always \uF9A8 14-13456-5
+always \uF9A9 14-13456-2
+always \uF9AA 1345-13456-2
+always \uF9AB 14-13456-4
+always \uF9AC 14-2345-2
+always \uF9AD 14-13456-2
+always \uF9AE 13456-2
+always \uF9AF 14-13456-2
+always \uF9B0 14-13456-2
+always \uF9B1 14-13456-2
+always \uF9B2 14-13456-2
+always \uF9B3 14-13456-2
+always \uF9B4 14-13456-4
+always \uF9B5 14-16-5
+always \uF9B6 14-16-4
+always \uF9B7 14-16-4
+always \uF9B8 14-16-5
+always \uF9B9 2346-5
+always \uF9BA 14-2346-1
+always \uF9BB 14-246-2
+always \uF9BC 14-246-2
+always \uF9BD 1345-246-5
+always \uF9BE 14-246-5
+always \uF9BF 14-2346-5
+always \uF9C0 14-246-4
+always \uF9C1 14-246-2
+always \uF9C2 14-246-4
+always \uF9C3 14-246-2
+always \uF9C4 14-12346-2
+always \uF9C5 256-3
+always \uF9C6 1245-12456-4
+always \uF9C7 14-234-2
+always \uF9C8 12-12356-4
+always \uF9C9 14-234-4
+always \uF9CA 14-234-2
+always \uF9CB 14-234-3
+always \uF9CC 14-234-2
+always \uF9CD 14-234-2
+always \uF9CE 14-234-2
+always \uF9CF 1345-234-4
+always \uF9D0 14-356-5
+always \uF9D1 14-234-5
+always \uF9D2 14-34-5
+always \uF9D3 14-34-5
+always \uF9D4 14-123456-2
+always \uF9D5 14-123456-2
+always \uF9D6 14-123456-2
+always \uF9D7 14-123456-2
+always \uF9D8 14-1256-5
+always \uF9D9 14-16-5
+always \uF9DA 14-16-5
+always \uF9DB 14-1256-5
+always \uF9DC 14-12346-2
+always \uF9DD 14-16-5
+always \uF9DE 14-16-5
+always \uF9DF 14-1256-4
+always \uF9E0 16-5
+always \uF9E1 14-16-4
+always \uF9E2 14-16-2
+always \uF9E3 1345-16-2
+always \uF9E4 14-16-4
+always \uF9E5 14-16-5
+always \uF9E6 14-16-2
+always \uF9E7 14-16-4
+always \uF9E8 14-16-4
+always \uF9E9 14-16-4
+always \uF9EA 14-16-2
+always \uF9EB 1345-16-5
+always \uF9EC 1345-16-5
+always \uF9ED 14-1456-5
+always \uF9EE 14-1456-2
+always \uF9EF 14-1456-2
+always \uF9F0 14-1456-5
+always \uF9F1 14-1456-2
+always \uF9F2 14-1456-2
+always \uF9F3 14-1456-2
+always \uF9F4 14-1456-2
+always \uF9F5 14-1456-2
+always \uF9F6 14-1456-2
+always \uF9F7 14-16-5
+always \uF9F8 14-16-5
+always \uF9F9 14-16-5
+always \uF9FA 1-456-5
+always \uF9FB 1-156-5
+always \uF9FC 24-156-2
+always \uF9FD 24-156-2
+always \uF9FE 12-345-2
+always \uF9FF 245-156-5
+always \uFA00 245-346-3
+always \uFA01 145-34-5
+always \uFA02 124-25-5
+always \uFA03 124-1346-2
+always \uFA04 1-2456-2
+always \uFA05 145-12346-5
+always \uFA06 135-146-5
+always \uFA07 12345-34-2
+always \uFA08 15-13456-2
+always \uFA09 13-46-5
+always \uFA0A 13-2345-5
+always \uFA0B 123-25-5
+always \uFA0C 34-5
+always \uFA0D 1235-25-5
+always \uFA0E 24-456-3
+always \uFA0F 15-1256-5
+always \uFA10 1-12346-4
+always \uFA11 245-16-2
+always \uFA12 245-13456-2
+always \uFA13 12345-34-2
+always \uFA14 13-1256-4
+always \uFA15 15-16-3
+always \uFA16 1-34-3
+always \uFA17 16-5
+always \uFA18 14-16-4
+always \uFA19 24-136-2
+always \uFA1A 15-46-2
+always \uFA1B 12345-34-2
+always \uFA1C 13-13456-5
+always \uFA1D 13-13456-3
+always \uFA1E 1256-4
+always \uFA1F 2456-1
+always \uFA20 245-234-3
+always \uFA21 24-1356-3
+always \uFA22 1-34-3
+always \uFA23 145-25-3
+always \uFA24 13-16-2
+always \uFA25 16-5
+always \uFA26 145-12356-3
+always \uFA27 15-246-5
+always \uFA28 15-1456-5
+always \uFA29 145-146-4
+always \uFA2A 12345-1236-5
+always \uFA2B 15-156-5
+always \uFA2C 13-12456-4
+always \uFA2D 1235-2346-5
+always \uFA30 34-4
+always \uFA31 15-1356-3
+always \uFA32 134-2345-4
+always \uFA33 134-2345-4
+always \uFA34 245-1456-2
+always \uFA35 135-356-3
+always \uFA36 1235-2346-3
+always \uFA37 124-1236-5
+always \uFA38 245-16-5
+always \uFA3A 134-126-5
+always \uFA3B 245-1356-2
+always \uFA3C 245-146-4
+always \uFA3D 1235-1246-4
+always \uFA3E 123-2456-4
+always \uFA3F 125-1356-3
+always \uFA40 12-1356-2
+always \uFA41 134-1456-4
+always \uFA42 13-16-5
+always \uFA43 24-34-4
+always \uFA44 134-356-2
+always \uFA45 1235-2456-4
+always \uFA46 1-34-4
+always \uFA47 1235-1236-5
+always \uFA48 1-34-4
+always \uFA4A 1-25-2
+always \uFA4B 135-356-3
+always \uFA4C 24-2346-5
+always \uFA4D 1-156-4
+always \uFA4E 245-16-2
+always \uFA4F 234-5
+always \uFA50 125-34-4
+always \uFA51 1-34-5
+always \uFA52 1235-25-5
+always \uFA53 1-136-3
+always \uFA54 13-34-4
+always \uFA55 124-34-3
+always \uFA56 13-346-2
+always \uFA57 14-2345-5
+always \uFA58 13-1456-5
+always \uFA59 12345-1236-2
+always \uFA5A 24-34-4
+always \uFA5B 1-2346-4
+always \uFA5C 12-12356-5
+always \uFA5D 245-146-4
+always \uFA5E 245-146-4
+always \uFA5F 1-2346-1
+always \uFA60 1235-2346-5
+always \uFA61 24-156-5
+always \uFA62 346-5
+always \uFA63 13-1456-4
+always \uFA64 135-1456-3
+always \uFA65 125-1356-5
+always \uFA66 1-156-3
+always \uFA67 16-5
+always \uFA68 1345-1236-2
+always \uFA69 15-46-4
+always \uFA6A 1234-1456-2
+always \uFA6F 1-34-5
+always \uFA70 135-13456-5
+always \uFA71 123-456-5
+always \uFA72 245-45-2
+always \uFA73 15-13456-2
+always \uFA74 12-12346-3
+always \uFA75 13-16-5
+always \uFA76 235-4
+always \uFA77 24-146-2
+always \uFA78 1235-2346-3
+always \uFA79 124-146-2
+always \uFA7A 1235-1246-5
+always \uFA7B 35-5
+always \uFA7C 1-12346-4
+always \uFA7D 12345-136-2
+always \uFA7E 2345-4
+always \uFA7F 135-136-5
+always \uFA80 135-16-5
+always \uFA81 245-156-2
+always \uFA82 146-2
+always \uFA83 16-5
+always \uFA84 245-2456-4
+always \uFA85 246-2
+always \uFA86 456-4
+always \uFA87 24-136-5
+always \uFA88 1256-5
+always \uFA89 125-1356-3
+always \uFA8A 146-5
+always \uFA8B 12-1356-2
+always \uFA8C 145-2456-5
+always \uFA8D 1256-2
+always \uFA8E 15-12356-3
+always \uFA8F 135-13456-5
+always \uFA90 146-2
+always \uFA91 245-13456-2
+always \uFA92 14-1346-4
+always \uFA93 456-5
+always \uFA94 1-1346-5
+always \uFA95 145-2456-4
+always \uFA96 24-345-3
+always \uFA97 14-234-2
+always \uFA98 1456-2
+always \uFA99 125-156-3
+always \uFA9A 1235-1236-5
+always \uFA9B 13-1456-5
+always \uFA9C 1-34-4
+always \uFA9D 245-246-2
+always \uFA9E 13-236-2
+always \uFA9F 12345-1236-5
+always \uFAA0 1-34-3
+always \uFAA1 124-2345-5
+always \uFAA2 245-156-2
+always \uFAA3 13-35-5
+always \uFAA4 13-12456-3
+always \uFAA5 123456-3
+always \uFAA6 16-5
+always \uFAA7 24-1356-5
+always \uFAA8 1-156-2
+always \uFAA9 13-45-5
+always \uFAAA 1-2346-1
+always \uFAAB 1-136-3
+always \uFAAC 124-246-5
+always \uFAAD 13-346-2
+always \uFAAE 14-356-5
+always \uFAAF 124-146-3
+always \uFAB0 14-2345-5
+always \uFAB1 124-13456-2
+always \uFAB2 1-2346-4
+always \uFAB3 1235-456-3
+always \uFAB4 1235-35-2
+always \uFAB5 1456-3
+always \uFAB6 245-46-4
+always \uFAB7 12345-34-5
+always \uFAB8 24-156-5
+always \uFAB9 145-246-5
+always \uFABA 1-34-3
+always \uFABB 245-13456-4
+always \uFABC 346-5
+always \uFABD 1345-25-5
+always \uFABE 1256-5
+always \uFABF 13-1456-4
+always \uFAC0 135-2345-5
+always \uFAC1 125-1356-5
+always \uFAC2 24-34-3
+always \uFAC3 12-156-2
+always \uFAC4 15-12356-3
+always \uFAC5 15-13456-2
+always \uFAC6 1-34-4
+always \uFAC7 1345-1236-2
+always \uFAC8 13-13456-5
+always \uFAC9 135-356-5
+always \uFACA 15-46-4
+always \uFACB 145-34-5
+always \uFACC 1234-1456-2
+always \uFACD 1-136-4
+always \uFACE 13-1246-3
+always \uFAD2 1235-2346-2
+always \uFAD3 15-16-2
+always \uFAD4 13-23456-2
+always \uFAD5 245-25-5
+always \uFAD7 13-346-2
+always \uFAD8 13-2346-2
+always \uFAD9 1234-1346-2
+always \uFE30 25-25
+always \uFE31 1256-0
+always \uFE33 456
+always \uFE34 45
+always \uFE35 246-0
+always \uFE36 135-0
+always \uFE37 246-0
+always \uFE38 12456-0
+always \uFE39 2467-0
+always \uFE3A 124567-0
+always \uFE3B 2467-0
+always \uFE3C 124567-0
+always \uFE3D 126-126
+always \uFE3E 345-345
+always \uFE3F 126
+always \uFE40 345
+always \uFE41 56-36
+always \uFE42 36-23
+always \uFE43 236-236
+always \uFE44 356-356
+always \uFE4F 45
+always \uFE50 23-0
+always \uFE51 6-0
+always \uFE52 36-0
+always \uFE54 56-0
+always \uFE55 156-0
+always \uFE56 135-0
+always \uFE57 2346-0
+always \uFE59 12356
+always \uFE5A 23456
+always \uFE5B 246-0
+always \uFE5C 12456-0
+always \uFE5D 12346-0
+always \uFE5E 13456-0
+always \uFE5F 3456
+always \uFE60 456-12346
+always \uFE61 16
+always \uFE62 346
+always \uFE63 36
+always \uFE64 126
+always \uFE65 345
+always \uFE66 123456
+always \uFE68 12467-127
+always \uFE69 1246
+always \uFE6A 146
+always \uFE6B 47
+always \uFF01 123-0
+always \uFF02 5-0
+always \uFF03 3456-0
+always \uFF04 1246-0
+always \uFF05 146-0
+always \uFF06 12346-0
+always \uFF07 3-0
+always \uFF08 246-0
+always \uFF09 135-0
+always \uFF0A 16-0
+always \uFF0B 346-0
+always \uFF0C 23-0
+always \uFF0D 36-0
+always \uFF0E 46-0
+always \uFF0F 34-0
+always \uFF10 356-0
+always \uFF11 2-0
+always \uFF12 23-0
+always \uFF13 25-0
+always \uFF14 256-0
+always \uFF15 26-0
+always \uFF16 235-0
+always \uFF17 2356-0
+always \uFF18 236-0
+always \uFF19 35-0
+always \uFF1A 25-25
+always \uFF1B 56-0
+always \uFF1C 126-0
+always \uFF1D 123456-0
+always \uFF1E 345-0
+always \uFF1F 1456-0
+always \uFF20 47-0
+always \uFF21 17-0
+always \uFF22 127-0
+always \uFF23 147-0
+always \uFF24 1457-0
+always \uFF25 157-0
+always \uFF26 1247-0
+always \uFF27 12457-0
+always \uFF28 1257-0
+always \uFF29 247-0
+always \uFF2A 2457-0
+always \uFF2B 137-0
+always \uFF2C 1237-0
+always \uFF2D 1347-0
+always \uFF2E 13457-0
+always \uFF2F 1357-0
+always \uFF30 12347-0
+always \uFF31 123457-0
+always \uFF32 12357-0
+always \uFF33 2347-0
+always \uFF34 23457-0
+always \uFF35 1367-0
+always \uFF36 12367-0
+always \uFF37 24567-0
+always \uFF38 13467-0
+always \uFF39 134567-0
+always \uFF3A 13567-0
+always \uFF3B 2467-0
+always \uFF3C 12567-0
+always \uFF3D 124567-0
+always \uFF3F 456-0
+always \uFF40 4-0
+always \uFF41 1-0
+always \uFF42 12-0
+always \uFF43 14-0
+always \uFF44 145-0
+always \uFF45 15-0
+always \uFF46 124-0
+always \uFF47 1245-0
+always \uFF48 125-0
+always \uFF49 24-0
+always \uFF4A 245-0
+always \uFF4B 13-0
+always \uFF4C 123-0
+always \uFF4D 134-0
+always \uFF4E 1345-0
+always \uFF4F 135-0
+always \uFF50 1234-0
+always \uFF51 12345-0
+always \uFF52 1235-0
+always \uFF53 234-0
+always \uFF54 2345-0
+always \uFF55 136-0
+always \uFF56 1236-0
+always \uFF57 2456-0
+always \uFF58 1346-0
+always \uFF59 13456-0
+always \uFF5A 1356-0
+always \uFF5B 246-0
+always \uFF5C 1256-0
+always \uFF5D 12456-0
+always \uFF5E 45-0
+always \uFF5F 12356-0
+always \uFF60 23456-0
+always \uFF61 46
+always \uFF62 126
+always \uFF63 345
+always \uFF64 6
+always \uFF65 256
+always \uFF67 1
+always \uFF68 12
+always \uFF69 14
+always \uFF6A 124
+always \uFF6B 24
+always \uFF6C 34
+always \uFF6D 346
+always \uFF6E 345
+always \uFF70 25
+always \uFF71 1
+always \uFF72 12
+always \uFF73 14
+always \uFF74 124
+always \uFF75 24
+always \uFF76 16
+always \uFF77 126
+always \uFF78 146
+always \uFF79 1246
+always \uFF7A 246
+always \uFF7B 156
+always \uFF7C 1256
+always \uFF7E 12456
+always \uFF7F 2456
+always \uFF80 135
+always \uFF81 1235
+always \uFF82 1345
+always \uFF83 12345
+always \uFF84 2345
+always \uFF85 13
+always \uFF86 123
+always \uFF87 134
+always \uFF88 1234
+always \uFF89 234
+always \uFF8A 136
+always \uFF8B 1236
+always \uFF8C 1346
+always \uFF8D 12346
+always \uFF8E 2346
+always \uFF8F 1356
+always \uFF90 12356
+always \uFF91 13456
+always \uFF92 123456
+always \uFF93 23456
+always \uFF94 34
+always \uFF95 346
+always \uFF96 345
+always \uFF97 15
+always \uFF98 125
+always \uFF99 145
+always \uFF9A 1245
+always \uFF9B 245
+always \uFF9C 3
+always \uFF9D 356
+always \uFF9E 25
+always \uFF9F 2
+always \uFFE0 36-14-36
+always \uFFE1 36-1237-36
+always \uFFE5 36-134567-157-13457-36
+always \uFFE9 126-36
+always \uFFEA 1246-126-135
+always \uFFEB 36-345
+always \uFFEC 1246-146-135
+always \uFFEE 246-135
+
+#Chinese phrases
+always \u4E00\u5207 16-3-245-346-5
+always \u4e00\u4e86 16-3-14-246-4
+always \u4e00\u5339 16-3-1234-16-4
+always \u4e00\u64ae 16-3-245-25-5
+always \u4e00\u66b4 16-3-1234-34-5
+always \u4e00\u671d 16-3-1-146-3
+always \u4e00\u8457 16-3-1-25-2
+always \u4e00\u884c 16-3-1235-1346-2
+always \u4e00\u89ba 16-3-13-246-5
+always \u4e09\u66f4 15-1236-3-13-13456-3
+always \u4e09\u7701 15-1236-3-15-13456-4
+always \u4e09\u884c 15-1236-3-1235-1346-2
+always \u4e09\u91cd 15-1236-3-12-12346-2
+always \u4e0a\u5c07 24-1346-5-13-46-5
+always \u4e0a\u7576 24-1346-5-145-1346-5
+always \u4e0a\u76f8 24-1346-5-15-46-5
+always \u4e0a\u8072 24-1346-4-24-1356-3
+always \u4e0b\u5b50 15-23456-5-125-156-1
+always \u4e0d\u4e86 135-34-5-14-246-4
+always \u4e0d\u52dd 135-34-5-24-1356-3
+always \u4e0d\u7576 135-34-5-145-1346-5
+always \u4e0d\u7701 135-34-5-15-13456-4
+always \u4e0d\u7981 135-34-5-13-1456-3
+always \u4e0d\u820d 135-34-5-24-2346-4
+always \u4e0d\u8457 135-34-5-1-146-2
+always \u4e0d\u963f 135-34-5-2346-3
+always \u4e0d\u9bae 135-34-5-15-2345-4
+always \u4e1e\u76f8 12-1356-2-15-46-5
+always \u4e2d\u50b7 1-12346-5-24-1346-3
+always \u4e2d\u610f 1-12346-5-16-5
+always \u4e2d\u6691 1-12346-5-24-34-4
+always \u4e2d\u6bd2 1-12346-5-145-34-2
+always \u4e2d\u734e 1-12346-5-13-46-4
+always \u4e2d\u8209 1-12346-5-13-1256-4
+always \u4e2d\u8a08 1-12346-5-13-16-5
+always \u4e2d\u98a8 1-12346-5-12345-1356-3
+always \u4e38\u5b50 12456-2-125-156-1
+always \u4e3a\u4e86 1246-5-14-2346-1
+always \u4e3b\u5c07 1-34-4-13-46-5
+always \u4e58\u8208 12-1356-2-15-13456-5
+always \u4e5d\u91cd 13-234-4-12-12346-2
+always \u4e7e\u5609 245-2345-2-13-23456-3
+always \u4e7e\u5764 245-2345-2-123-123456-3
+always \u4e7e\u9686 245-2345-2-14-12346-2
+always \u4e82\u5b50 14-12456-5-125-156-1
+always \u4e86\u4e86 14-246-4-14-246-4
+always \u4e86\u4e8b 14-246-4-24-156-5
+always \u4e86\u5f97 14-246-4-145-2346-2
+always \u4e86\u609f 14-246-4-34-5
+always \u4e86\u65b7 14-246-4-145-12456-5
+always \u4e86\u7136 14-246-4-1245-1236-2
+always \u4e86\u7d50 14-246-4-13-346-2
+always \u4e86\u89e3 14-246-4-13-346-4
+always \u4e94\u66f4 34-4-13-13456-3
+always \u4ea4\u5377 13-246-3-13-45-5
+always \u4ea4\u60e1 13-246-3-34-5
+always \u4ea4\u81c2 13-246-3-135-16-5
+always \u4ea4\u9084 13-246-3-1235-12456-2
+always \u4eac\u90fd 13-13456-3-145-34-3
+always \u4ead\u5b50 124-13456-2-125-156-1
+always \u4eae\u76f8 14-46-5-15-46-5
+always \u4eb6\u7236 145-1236-4-12345-34-4
+always \u4eba\u53c3 1245-136-2-24-136-3
+always \u4ec0\u4e48 24-2346-2-134-2346-1
+always \u4ec0\u9ebc 24-2346-2-134-2346-1
+always \u4f11\u5047 15-234-3-13-23456-5
+always \u4f19\u5b50 1235-25-4-125-156-1
+always \u4f3a\u5019 245-156-5-1235-12356-5
+always \u4f4d\u5b50 1246-5-125-156-1
+always \u4f5d\u50c2 123-12356-5-14-12356-2
+always \u4f86\u964d 14-2456-2-15-46-2
+always \u4f8b\u5047 14-16-5-13-23456-5
+always \u4f8b\u5b50 14-16-5-125-156-1
+always \u4f9b\u61c9 13-12346-3-13456-5
+always \u4f9b\u990a 13-12346-5-46-5
+always \u4f9d\u508d 16-3-135-1346-5
+always \u4fbf\u5b9c 1234-2345-2-16-2
+always \u4fe1\u5dee 15-1456-5-12-2456-3
+always \u4fef\u755c 12345-34-4-15-1256-5
+always \u4ff8\u7d66 12345-1356-5-13-16-4
+always \u5009\u5352 245-1346-3-245-34-5
+always \u5009\u9821 245-1346-3-13-346-2
+always \u500b\u5b50 13-2346-5-125-156-1
+always \u5014\u5f37 13-236-2-13-46-5
+always \u5047\u65e5 13-23456-5-1245-156-5
+always \u5047\u671f 13-23456-5-245-16-2
+always \u504f\u597d 1234-2345-3-1235-146-5
+always \u5065\u5c07 13-2345-5-13-46-5
+always \u50b3\u8a18 1-12456-5-13-16-5
+always \u5110\u76f8 135-1456-3-15-46-5
+always \u5112\u5c07 1245-34-2-13-46-5
+always \u511f\u9084 12-1346-2-1235-12456-2
+always \u5141\u7576 256-4-145-1346-5
+always \u5144\u9577 15-235-3-1-1346-4
+always \u5145\u5206 12-12346-3-12345-136-5
+always \u5145\u585e 12-12346-3-15-2346-5
+always \u514b\u96e3 123-2346-5-1345-1236-5
+always \u5152\u5b50 156-2-125-156-1
+always \u5154\u5b50 124-34-5-125-156-1
+always \u5165\u76f8 1245-34-5-15-46-5
+always \u5167\u61c9 1345-356-5-13456-5
+always \u5167\u76f8 1345-356-5-15-46-5
+always \u5167\u7701 1345-356-5-15-13456-4
+always \u5167\u884c 1345-356-5-1235-1346-2
+always \u516c\u5dee 13-12346-3-12-2456-3
+always \u516c\u64ae 13-12346-3-245-25-5
+always \u5171\u8655 13-12346-5-12-34-4
+always \u5174\u8da3 15-13456-5-245-1256-5
+always \u5178\u7576 145-2345-4-145-1346-5
+always \u517c\u5dee 13-2345-3-12-2456-3
+always \u518a\u5b50 245-2346-5-125-156-1
+always \u51a0\u5195 13-12456-3-134-2345-4
+always \u51a0\u84cb 13-12456-3-13-2456-5
+always \u51f1\u6492 123-2456-4-15-345-3
+always \u51f3\u5b50 145-1356-5-125-156-1
+always \u51fa\u5c07 12-34-3-13-46-5
+always \u51fa\u5dee 12-34-3-12-2456-3
+always \u51fa\u6c92 12-34-3-134-126-5
+always \u5200\u5b50 145-146-3-125-156-1
+always \u5206\u5167 12345-136-5-1345-356-5
+always \u5206\u5916 12345-136-5-2356-5
+always \u5206\u5b50 12345-136-5-125-156-4
+always \u5206\u884c 12345-136-3-1235-1346-2
+always \u5206\u91cf 12345-136-5-14-46-5
+always \u5217\u50b3 14-346-5-1-12456-5
+always \u5228\u571f 1234-146-2-124-34-4
+always \u5228\u5730 1234-146-2-145-16-5
+always \u5228\u5b50 1234-146-2-125-156-1
+always \u5237\u5b50 24-35-3-125-156-1
+always \u524e\u8eca 24-345-5-12-2346-3
+always \u524e\u90a3 12-345-5-1345-25-2
+always \u525d\u524a 135-126-3-15-236-3
+always \u526f\u5c07 12345-34-5-13-46-5
+always \u5275\u75d5 12-456-3-1235-136-2
+always \u52a9\u9577 1-34-5-1-1346-4
+always \u52c7\u5c07 235-4-13-46-5
+always \u52c9\u5f37 134-2345-4-245-46-4
+always \u52d2\u5012 14-356-3-145-146-4
+always \u52d2\u50b7 14-356-3-24-1346-3
+always \u52d2\u660f 14-356-3-1235-123456-3
+always \u52d2\u6b7b 14-356-3-15-156-4
+always \u52d2\u75d5 14-356-3-1235-136-2
+always \u52d5\u8108 145-12346-5-134-2456-5
+always \u52dd\u4efb 24-1356-3-1245-136-5
+always \u52dd\u6578 24-1356-3-24-34-4
+always \u52de\u8ecd 14-146-5-13-256-3
+always \u52f8\u964d 245-45-5-15-46-2
+always \u52fa\u5b50 24-146-2-125-156-1
+always \u52fb\u7a31 256-2-12-136-5
+always \u52fe\u7576 13-12356-3-145-1346-5
+always \u5305\u5b50 135-146-3-125-156-1
+always \u5315\u898b 135-16-4-15-2345-5
+always \u5316\u5b50 1235-35-3-125-156-1
+always \u5317\u4f10 135-356-4-12345-345-3
+always \u5323\u5b50 15-23456-2-125-156-1
+always \u5339\u592b 1234-16-4-12345-34-3
+always \u5339\u5a66 1234-16-4-12345-34-5
+always \u5339\u6575 1234-16-4-145-16-2
+always \u5339\u914d 1234-16-4-1234-356-5
+always \u5340\u9577 245-1256-3-1-1346-4
+always \u5341\u884c 24-156-2-1235-1346-2
+always \u5343\u4e58 245-2345-3-24-1356-5
+always \u5348\u89ba 34-4-13-246-5
+always \u534a\u8f09 135-1236-5-125-2456-4
+always \u5352\u5b50 125-34-2-125-156-1
+always \u5353\u8457 1-25-2-1-34-5
+always \u5354\u8abf 15-346-2-124-246-2
+always \u5357\u7121 1345-1236-2-134-126-2
+always \u5360\u535c 1-1236-3-135-34-4
+always \u5360\u661f 1-1236-3-15-13456-3
+always \u5371\u96e3 1246-2-1345-1236-5
+always \u5377\u5b50 13-45-5-125-156-1
+always \u5377\u5b97 13-45-5-125-12346-3
+always \u537f\u76f8 245-13456-3-15-46-5
+always \u539f\u8457 45-2-1-34-5
+always \u53C3\u3001 15-1236-3-6-0
+always \u53ad\u60e1 2345-5-34-5
+always \u53bb\u8108 245-1256-5-134-2456-5
+always \u53c3\u5546 24-136-3-24-1346-3
+always \u53c3\u5c07 245-1236-3-13-46-5
+always \u53c3\u5dee 245-136-3-245-156-3
+always \u53c3\u8207 245-1236-3-1256-5
+always \u53c9\u5b50 12-345-3-125-156-1
+always \u53cd\u61c9 12345-1236-4-13456-5
+always \u53cd\u76f8 12345-1236-4-15-46-5
+always \u53cd\u7701 12345-1236-4-15-13456-4
+always \u53cd\u9593 12345-1236-4-13-2345-5
+always \u53d7\u964d 24-12356-5-15-46-2
+always \u53d7\u96e3 24-12356-5-1345-1236-5
+always \u53e3\u5403 123-12356-4-13-16-2
+always \u53e3\u5b50 123-12356-4-125-156-1
+always \u53e4\u73a9 13-34-4-12456-5
+always \u53e5\u5b50 13-1256-5-125-156-1
+always \u53e5\u8e10 13-12356-3-13-2345-5
+always \u53ef\u60e1 123-2346-4-34-5
+always \u53ef\u6578 123-2346-4-24-34-4
+always \u53ef\u6c57 123-2346-5-1235-1236-2
+always \u53f0\u5b50 124-2456-2-125-156-1
+always \u53f8\u9577 15-156-3-1-1346-4
+always \u5404\u884c 13-2346-5-1235-1346-2
+always \u5406\u559d 246-3-1235-2346-5
+always \u540a\u5b50 145-246-5-125-156-1
+always \u540d\u5206 134-13456-2-12345-136-5
+always \u540d\u5c07 134-13456-2-13-46-5
+always \u540d\u8457 134-13456-2-1-34-5
+always \u5410\u8543 124-34-4-12345-1236-3
+always \u541e\u6c92 124-123456-3-134-126-5
+always \u5426\u6975 1234-16-4-13-16-2
+always \u542b\u6df7 1235-1236-2-1235-123456-4
+always \u5446\u5b50 145-2456-3-125-156-1
+always \u544a\u5047 13-146-5-13-23456-5
+always \u5462\u5462 1345-16-2-1345-16-2
+always \u5462\u5583 1345-16-2-1345-1236-2
+always \u5462\u7d68 1345-16-2-1245-12346-2
+always \u547c\u61c9 1235-34-3-13456-5
+always \u547d\u8108 134-13456-5-134-2456-5
+always \u5486\u54ee 1234-146-2-15-246-3
+always \u548b\u820c 125-2346-2-24-2346-2
+always \u54b6\u566a 13-35-3-125-146-5
+always \u54bd\u5589 2345-3-1235-12356-2
+always \u54c0\u865f 2456-3-1235-146-2
+always \u54c1\u884c 1234-1456-4-15-13456-5
+always \u54c4\u9a19 1235-12346-4-1234-2345-5
+always \u54c6\u55e6 145-25-3-15-25-3
+always \u54c8\u5587 123-345-3-14-345-1
+always \u54e8\u5b50 24-146-5-125-156-1
+always \u54e9\u5695 14-16-3-14-34-3
+always \u54ed\u865f 123-34-3-1235-146-2
+always \u54ee\u5598 15-246-3-12-12456-4
+always \u54fd\u54bd 13-1356-4-346-5
+always \u5531\u548c 12-1346-5-1235-2346-5
+always \u5546\u884c 24-1346-3-1235-1346-2
+always \u5546\u8cc8 24-1346-3-13-34-4
+always \u5546\u91cf 24-1346-3-14-46-2
+always \u554f\u5377 123456-5-13-45-5
+always \u555e\u5427 23456-4-135-345-3
+always \u555e\u555e 23456-3-23456-3
+always \u5580\u4ec0 123-345-5-24-156-2
+always \u5581\u5581 1256-2-1256-2
+always \u559c\u597d 15-16-4-1235-146-5
+always \u559d\u91c7 1235-2346-5-245-2456-4
+always \u55ae\u4e8e 12-1236-2-1256-2
+always \u55ae\u5b50 145-1236-3-125-156-1
+always \u55ce\u5561 134-345-4-12345-356-3
+always \u55d3\u5b50 15-1346-4-125-156-1
+always \u55da\u54bd 34-3-346-5
+always \u55dc\u597d 24-156-5-1235-146-5
+always \u5600\u5495 145-16-2-13-34-3
+always \u5614\u5410 12356-4-124-34-5
+always \u5614\u6c23 12356-5-245-16-5
+always \u5629\u5566 1235-35-3-14-345-3
+always \u5630\u54e9 13-16-3-14-16-3
+always \u5668\u6a02 245-16-5-236-5
+always \u5687\u963b 1235-2346-5-125-34-4
+always \u56db\u884c 15-156-5-1235-1346-2
+always \u56de\u5e94 1235-1246-2-13456-5
+always \u56de\u61c9 1235-1246-2-13456-5
+always \u56e0\u4e3a 1456-3-1246-5
+always \u56e0\u61c9 1456-3-13456-5
+always \u56e0\u70ba 1456-3-1246-5
+always \u56fa\u8457 13-34-5-1-25-2
+always \u5708\u5b50 245-45-3-125-156-1
+always \u570b\u6a02 13-25-2-236-5
+always \u570b\u90fd 13-25-2-145-34-3
+always \u570b\u96e3 13-25-2-1345-1236-5
+always \u5718\u9577 124-12456-2-1-1346-4
+always \u571f\u8457 124-34-4-1-34-5
+always \u571f\u9577 124-34-4-1-1346-4
+always \u5728\u884c 125-2456-5-1235-1346-2
+always \u5730\u92ea 145-16-5-1234-34-5
+always \u574e\u5777 123-1236-4-123-2346-4
+always \u5750\u9a0e 125-25-5-13-16-5
+always \u5766\u7387 124-1236-4-24-2356-5
+always \u57cb\u6c92 134-2456-2-134-126-5
+always \u57f7\u8457 1-156-2-1-25-2
+always \u5831\u61c9 135-146-5-13456-5
+always \u5834\u5408 12-1346-2-1235-2346-2
+always \u5834\u9762 12-1346-2-134-2345-5
+always \u585e\u4f4f 15-2456-3-1-34-5
+always \u585e\u5b50 15-2456-3-125-156-1
+always \u585e\u6eff 15-2456-3-134-1236-4
+always \u585e\u7259 15-2456-3-23456-2
+always \u588a\u5b50 145-2345-5-125-156-1
+always \u589c\u5b50 1-1246-5-125-156-1
+always \u589e\u9577 125-1356-3-1-1346-4
+always \u58a8\u7fdf 134-126-5-145-16-2
+always \u58c5\u585e 235-4-15-2346-5
+always \u58e9\u5b50 135-345-5-125-156-1
+always \u5904\u7406 12-34-4-14-16-4
+always \u5916\u76f8 2356-5-15-46-5
+always \u5916\u884c 2356-5-1235-1346-2
+always \u5916\u9577 2356-5-1-1346-4
+always \u591a\u91cd 145-25-3-12-12346-2
+always \u5927\u5b9b 145-345-5-45-3
+always \u5927\u5c07 145-345-5-13-46-5
+always \u5927\u7387 145-345-5-24-2356-5
+always \u5927\u96e3 145-345-5-1345-1236-5
+always \u5929\u5206 124-2345-3-12345-136-5
+always \u5929\u6daf 124-2345-3-26-2
+always \u5929\u76f8 124-2345-3-15-46-5
+always \u592a\u76e3 124-2456-5-13-2345-5
+always \u592a\u884c 124-2456-5-1235-1346-2
+always \u592b\u5dee 12345-34-3-12-2456-3
+always \u592d\u6298 246-4-1-2346-2
+always \u5931\u7576 24-156-3-145-1346-5
+always \u5931\u8abf 24-156-3-124-246-2
+always \u593e\u5b50 13-23456-2-125-156-1
+always \u593e\u7e2b 13-23456-2-12345-1356-5
+always \u5947\u6578 13-16-3-24-34-5
+always \u5949\u9084 12345-1356-5-1235-12456-2
+always \u5949\u990a 12345-1356-5-46-5
+always \u594f\u6a02 125-12356-5-236-5
+always \u596e\u81c2 12345-136-5-135-16-5
+always \u5973\u5c07 1345-1256-4-13-46-5
+always \u597d\u52d5 1235-146-5-145-12346-5
+always \u597d\u52dd 1235-146-5-24-1356-5
+always \u597d\u554f 1235-146-5-123456-5
+always \u597d\u5947 1235-146-5-245-16-2
+always \u597d\u60e1 1235-146-5-34-5
+always \u597d\u6230 1235-146-5-1-1236-5
+always \u597d\u8005 1235-146-5-1-2346-4
+always \u597d\u9b25 1235-146-5-145-12356-5
+always \u598a\u5a20 1245-136-5-24-136-3
+always \u59a5\u7576 124-25-4-145-1346-5
+always \u59ae\u5b50 1345-16-2-125-156-1
+always \u59d4\u66f2 1246-4-245-1256-3
+always \u59e8\u5b50 16-2-125-156-1
+always \u59ea\u5b50 1-156-2-125-156-1
+always \u5a01\u5687 1246-3-1235-2346-5
+always \u5a03\u5a03 35-2-35-3
+always \u5a1c\u5a1c 1345-25-4-1345-25-4
+always \u5a40\u5a1c 2346-3-1345-25-4
+always \u5a46\u5b50 1234-126-2-125-156-1
+always \u5ac2\u5b50 15-146-4-125-156-1
+always \u5acb\u5a1c 1345-246-4-1345-25-2
+always \u5acc\u60e1 15-2345-2-34-5
+always \u5ae1\u9577 145-16-2-1-1346-4
+always \u5b0c\u5a03 13-246-3-35-3
+always \u5b57\u5e16 125-156-5-124-346-5
+always \u5b5d\u990a 15-246-5-46-5
+always \u5b69\u5b50 1235-2456-2-125-156-1
+always \u5b6b\u5b50 15-123456-3-125-156-1
+always \u5b78\u9577 15-236-2-1-1346-4
+always \u5b85\u5b50 1-2456-2-125-156-1
+always \u5b88\u5206 24-12356-4-12345-136-5
+always \u5b88\u5c07 24-12356-4-13-46-5
+always \u5b89\u5206 1236-3-12345-136-5
+always \u5b8f\u90fd 1235-12346-2-145-34-3
+always \u5b98\u9577 13-12456-3-1-1346-4
+always \u5b9a\u7701 145-13456-5-15-13456-4
+always \u5b9a\u90fd 145-13456-5-145-34-3
+always \u5bb0\u76f8 125-2456-4-15-46-5
+always \u5bb3\u81ca 1235-2456-5-15-146-5
+always \u5bb6\u5b50 13-23456-3-125-156-1
+always \u5bb6\u7576 13-23456-3-145-1346-5
+always \u5bb6\u7d2f 13-23456-3-14-356-4
+always \u5bb6\u9577 13-23456-3-1-1346-4
+always \u5bd2\u5047 1235-1236-2-13-23456-5
+always \u5be9\u5ea6 24-136-4-145-25-5
+always \u5c07\u4ee4 13-46-5-14-13456-5
+always \u5c07\u4f50 13-46-5-125-25-4
+always \u5c07\u58eb 13-46-5-24-156-5
+always \u5c07\u5b98 13-46-5-13-12456-3
+always \u5c07\u5e25 13-46-5-24-2356-5
+always \u5c07\u6750 13-46-5-245-2456-2
+always \u5c07\u6821 13-46-5-15-246-5
+always \u5c07\u76f8 13-46-5-15-46-5
+always \u5c07\u9580 13-46-5-134-136-2
+always \u5c07\u9818 13-46-5-14-13456-4
+always \u5c08\u6a6b 1-12456-3-1235-1356-5
+always \u5c08\u8457 1-12456-3-1-34-5
+always \u5c0a\u9577 125-123456-3-1-1346-4
+always \u5c0d\u61c9 145-1246-5-13456-5
+always \u5c0d\u7a31 145-1246-5-12-136-5
+always \u5c0e\u64ad 145-146-4-135-126-3
+always \u5c0f\u50b3 15-246-4-1-12456-5
+always \u5c11\u58ef 24-146-5-1-456-5
+always \u5c11\u5973 24-146-5-1345-1256-4
+always \u5c11\u5a66 24-146-5-12345-34-5
+always \u5c11\u5b89 24-146-3-1236-3
+always \u5c11\u5c07 24-146-5-13-46-5
+always \u5c11\u5e74 24-146-5-1345-2345-2
+always \u5c11\u5eb7 24-146-5-123-1346-3
+always \u5c11\u6797 24-146-5-14-1456-2
+always \u5c11\u723a 24-146-5-346-2
+always \u5c31\u5f97 13-234-5-145-356-4
+always \u5c3e\u5df4 16-4-135-345-3
+always \u5c40\u9577 13-1256-2-1-1346-4
+always \u5c4b\u5b50 34-3-125-156-1
+always \u5c4f\u606f 135-13456-4-15-16-2
+always \u5c4f\u9000 135-13456-4-124-1246-5
+always \u5c91\u53c3 245-136-2-24-136-3
+always \u5c94\u5b50 12-345-5-125-156-1
+always \u5de1\u66f4 15-256-2-13-1356-3
+always \u5de6\u50b3 125-25-4-1-12456-5
+always \u5de8\u8457 13-1256-5-1-34-5
+always \u5dee\u4e0d 12-345-5-135-34-5
+always \u5dee\u4e8b 12-2456-3-24-156-5
+always \u5dee\u4f7f 12-2456-3-24-156-4
+always \u5dee\u5f79 12-2456-3-16-5
+always \u5dee\u9063 12-2456-3-245-2345-4
+always \u5dee\u9ede 12-345-5-145-2345-4
+always \u5df4\u7b54 135-345-3-145-345-3
+always \u5df7\u5b50 15-46-5-125-156-1
+always \u5e02\u9577 24-156-5-1-1346-4
+always \u5e03\u5339 135-34-5-1234-16-4
+always \u5e16\u5b50 124-346-4-125-156-1
+always \u5e2b\u9577 24-156-3-1-1346-4
+always \u5e3d\u5b50 134-146-5-125-156-1
+always \u5e4c\u5b50 1235-456-4-125-156-1
+always \u5e6b\u5b50 135-1346-3-125-156-1
+always \u5e72\u5c07 13-1236-3-13-46-5
+always \u5e74\u5047 1345-2345-2-13-23456-5
+always \u5e74\u5c11 1345-2345-2-24-146-5
+always \u5e74\u9577 1345-2345-2-1-1346-4
+always \u5e79\u561b 13-1236-5-134-345-2
+always \u5e79\u9ebc 13-1236-5-134-345-2
+always \u5e7e\u4e4e 13-16-3-1235-34-3
+always \u5e8a\u92ea 12-456-2-1234-34-5
+always \u5e94\u7528 13456-5-235-5
+always \u5e95\u5b50 145-16-4-125-156-1
+always \u5e97\u92ea 145-2345-5-1234-34-5
+always \u5e97\u94fa 145-2345-5-1234-34-5
+always \u5ea6\u5047 145-34-5-13-23456-5
+always \u5ead\u9577 124-13456-2-1-1346-4
+always \u5eda\u5b50 12-34-2-125-156-1
+always \u5ee0\u9577 12-1346-4-1-1346-4
+always \u5ef3\u9577 124-13456-3-1-1346-4
+always \u5efa\u90fd 13-2345-5-145-34-3
+always \u5f26\u6a02 15-2345-2-236-5
+always \u5f37\u5236 245-46-4-1-156-5
+always \u5f37\u5360 245-46-4-1-1236-5
+always \u5f37\u5c07 245-46-2-13-46-5
+always \u5f37\u5e79 245-46-4-13-1236-5
+always \u5f37\u6a6b 245-46-2-1235-1356-5
+always \u5f37\u6c42 245-46-4-245-234-2
+always \u5f37\u8a18 245-46-4-13-16-5
+always \u5f4c\u6492 134-16-2-15-345-3
+always \u5f4e\u66f2 12456-3-245-1256-3
+always \u5f71\u5b50 13456-4-125-156-1
+always \u5f77\u5fa8 1234-1346-2-1235-456-2
+always \u5f80\u9084 456-4-1235-12456-2
+always \u5f81\u4f10 1-1356-3-12345-345-3
+always \u5f98\u5f8a 1234-2456-2-1235-2356-2
+always \u5fb7\u884c 145-2346-2-15-13456-5
+always \u5fc5\u5f97 135-16-5-145-356-4
+always \u5fc5\u61c9 135-16-5-13456-5
+always \u5fd6\u5ea6 245-123456-4-145-25-5
+always \u5fe0\u544a 1-12346-3-13-34-5
+always \u600e\u4e48 125-136-4-134-2346-1
+always \u6012\u865f 1345-34-5-1235-146-2
+always \u601d\u6c57 15-156-3-1235-1236-2
+always \u601d\u91cf 15-156-3-14-46-2
+always \u6025\u96e3 13-16-2-1345-1236-5
+always \u6027\u5b50 15-13456-5-125-156-1
+always \u602f\u5834 245-236-5-12-1346-4
+always \u602f\u61e6 245-236-5-1345-25-5
+always \u6050\u5687 123-12346-4-1235-2346-5
+always \u606b\u5687 145-12346-5-1235-2346-5
+always \u6070\u7576 245-23456-5-145-1346-5
+always \u60a3\u96e3 1235-12456-5-1345-1236-5
+always \u60b6\u60b6 134-136-5-134-136-5
+always \u60c5\u5206 245-13456-2-12345-136-5
+always \u60e1\u52de 34-5-14-146-2
+always \u60e1\u5c11 2346-5-24-146-5
+always \u611b\u597d 2456-5-1235-146-5
+always \u611f\u61c9 13-1236-4-13456-5
+always \u6170\u52de 1246-5-14-146-5
+always \u618e\u60e1 125-1356-3-34-5
+always \u61c9\u4ed8 13456-5-12345-34-5
+always \u61c9\u5141 13456-5-256-4
+always \u61c9\u547d 13456-5-134-13456-5
+always \u61c9\u548c 13456-5-1235-2346-2
+always \u61c9\u5c0d 13456-5-145-1246-5
+always \u61c9\u5fb5 13456-5-1-1356-3
+always \u61c9\u63a5 13456-5-13-346-3
+always \u61c9\u6642 13456-5-24-156-2
+always \u61c9\u666f 13456-5-13-13456-4
+always \u61c9\u6c42 13456-5-245-234-2
+always \u61c9\u7528 13456-5-235-5
+always \u61c9\u8003 13456-5-123-146-4
+always \u61c9\u8072 13456-5-24-1356-3
+always \u61c9\u8a31 13456-5-15-1256-4
+always \u61c9\u8a66 13456-5-24-156-5
+always \u61c9\u8b8a 13456-5-135-2345-5
+always \u61c9\u904b 13456-5-256-5
+always \u61c9\u9080 13456-5-246-3
+always \u61c9\u916c 13456-5-12-12356-2
+always \u61c9\u9a57 13456-5-2345-5
+always \u61ca\u55aa 146-5-15-1346-5
+always \u61ca\u6094 146-5-1235-1246-4
+always \u61ca\u60f1 146-5-1345-146-4
+always \u61f2\u8655 12-1356-2-12-34-4
+always \u61f5\u61c2 134-1356-4-145-12346-4
+always \u61f6\u6563 14-1236-4-15-1236-4
+always \u6210\u5206 12-1356-2-12345-136-5
+always \u6210\u90fd 12-1356-2-145-34-3
+always \u6210\u9577 12-1356-2-1-1346-4
+always \u6210\u957f 12-1356-2-1-1346-4
+always \u6230\u5c07 1-1236-5-13-46-5
+always \u6232\u5b50 15-16-5-125-156-1
+always \u6232\u8b14 15-16-5-1345-236-5
+always \u6236\u9577 1235-34-5-1-1346-4
+always \u623f\u5b50 12345-1346-2-125-156-1
+always \u6240\u8457 15-25-4-1-34-5
+always \u6240\u9577 15-25-4-1-1346-4
+always \u6241\u64d4 135-2345-4-145-1236-5
+always \u6241\u821f 1234-2345-3-1-12356-3
+always \u6241\u947d 135-2345-4-125-12456-5
+always \u6247\u5b50 24-1236-5-125-156-1
+always \u624b\u76f8 24-12356-4-15-46-5
+always \u624e\u5be6 1-345-3-24-156-2
+always \u624e\u624b 1-345-3-24-12356-4
+always \u624e\u6839 1-345-3-13-136-3
+always \u624e\u773c 1-345-3-2345-4
+always \u624e\u91dd 1-345-3-1-136-3
+always \u6253\u76f9 145-345-4-145-123456-4
+always \u6263\u5b50 123-12356-5-125-156-1
+always \u6267\u8457 1-156-2-1-25-2
+always \u626d\u66f2 1345-234-4-245-1256-3
+always \u626e\u76f8 135-1236-5-15-46-5
+always \u627e\u8457 1-146-4-1-146-2
+always \u6284\u6c92 12-146-3-134-126-5
+always \u6295\u964d 124-12356-2-15-46-2
+always \u62b9\u715e 134-126-4-24-345-3
+always \u62bd\u7a7a 12-12356-3-123-12346-5
+always \u62cd\u5b50 1234-2456-3-125-156-1
+always \u62d4\u90fd 135-345-2-145-34-3
+always \u62d7\u53e3 146-5-123-12356-4
+always \u62d8\u6ce5 13-1256-3-1345-16-5
+always \u62d9\u8457 1-25-2-1-34-5
+always \u6307\u6458 1-156-4-1-2346-2
+always \u6311\u5254 124-246-3-124-16-5
+always \u6311\u5f04 124-246-4-1345-12346-5
+always \u6311\u6230 124-246-4-1-1236-5
+always \u6311\u64a5 124-246-4-135-126-3
+always \u6311\u64d4 124-246-3-145-1236-5
+always \u6311\u71c8 124-246-4-145-1356-3
+always \u6311\u9017 124-246-4-145-12356-5
+always \u6311\u91c1 124-246-4-15-1456-5
+always \u632f\u81c2 1-136-5-135-16-5
+always \u6383\u5e1a 15-146-5-1-12356-4
+always \u6392\u6bd4 1234-2456-2-135-16-5
+always \u6392\u884c 1234-2456-2-1235-1346-2
+always \u6392\u9577 1234-2456-2-1-1346-4
+always \u6399\u9322 1-1356-5-245-2345-2
+always \u63a2\u5b50 124-1236-5-125-156-1
+always \u63a5\u61c9 13-346-3-13456-5
+always \u63a5\u7e2b 13-346-3-12345-1356-5
+always \u63a8\u78e8 124-1246-3-134-126-5
+always \u63a9\u6c92 2345-4-134-126-5
+always \u63db\u884c 1235-12456-5-1235-1346-2
+always \u63e3\u5ea6 12-2356-4-145-25-5
+always \u63e9\u6cb9 123-2456-3-234-2
+always \u63ed\u6aeb 13-346-2-1-34-3
+always \u6416\u6643 246-2-1235-456-5
+always \u642a\u585e 124-1346-2-15-2346-5
+always \u645f\u9322 14-12356-3-245-2345-2
+always \u6487\u5b50 1234-346-4-125-156-1
+always \u6492\u4f46 15-345-3-145-1236-5
+always \u6492\u5b0c 15-345-3-13-246-3
+always \u6492\u5c3f 15-345-3-1345-246-5
+always \u6492\u624b 15-345-3-24-12356-4
+always \u6492\u817f 15-345-3-124-1246-4
+always \u6492\u8b0a 15-345-3-1235-456-4
+always \u6492\u8cf4 15-345-3-14-2456-5
+always \u6492\u91ce 15-345-3-346-4
+always \u6492\u958b 15-345-3-123-2456-3
+always \u64ad\u6620 135-126-3-13456-5
+always \u64ad\u97f3 135-126-3-1456-3
+always \u64cd\u884c 245-146-3-15-13456-5
+always \u64d4\u5b50 145-1236-5-125-156-1
+always \u64f2\u9084 1-156-2-1235-12456-2
+always \u6518\u5937 1245-1346-2-16-2
+always \u6518\u596a 1245-1346-2-145-25-2
+always \u6524\u5b50 124-1236-3-125-156-1
+always \u6524\u9084 124-1236-3-1235-12456-2
+always \u652a\u548c 13-246-4-1235-25-5
+always \u6539\u884c 13-2456-4-1235-1346-2
+always \u653e\u5047 12345-1346-5-13-23456-5
+always \u6548\u61c9 15-246-5-13456-5
+always \u6551\u96e3 13-234-5-1345-1236-5
+always \u6557\u8208 135-2456-5-15-13456-5
+always \u6563\u5149 15-1236-4-13-456-3
+always \u6563\u5175 15-1236-4-135-13456-3
+always \u6563\u6587 15-1236-4-123456-2
+always \u6563\u66f2 15-1236-4-245-1256-4
+always \u6563\u6c99 15-1236-4-24-345-3
+always \u6578\u4e00 24-34-4-16-3
+always \u6578\u4e0d 24-34-4-135-34-5
+always \u6578\u4e8c 24-34-4-156-5
+always \u6578\u5178 24-34-4-145-2345-4
+always \u6578\u6578 24-34-4-24-34-5
+always \u6578\u843d 24-34-4-14-25-5
+always \u6578\u8aaa 24-34-4-24-25-3
+always \u6578\u9322 24-34-4-245-2345-2
+always \u6587\u904e 123456-5-13-25-5
+always \u6587\u98fe 123456-5-24-156-5
+always \u6597\u91cf 145-12356-4-14-46-2
+always \u6599\u4e2d 14-246-5-1-12346-5
+always \u6599\u5b50 14-246-5-125-156-1
+always \u65a7\u5b50 12345-34-4-125-156-1
+always \u65c5\u9577 14-1256-4-1-1346-4
+always \u65cb\u98a8 15-45-5-12345-1356-3
+always \u65cf\u9577 125-34-2-1-1346-4
+always \u65d7\u5b50 245-16-2-125-156-1
+always \u65e5\u5b50 1245-156-5-125-156-1
+always \u65e5\u6688 1245-156-5-256-5
+always \u65e5\u6c92 1245-156-5-134-126-5
+always \u6613\u50b3 16-5-1-12456-5
+always \u661f\u5bbf 15-13456-3-15-234-5
+always \u661f\u76f8 15-13456-3-15-46-5
+always \u6625\u5047 12-123456-3-13-23456-5
+always \u662d\u8457 1-146-3-1-34-5
+always \u6641\u932f 12-146-2-245-25-5
+always \u6643\u8166 1235-456-5-1345-146-4
+always \u6643\u8569 1235-456-5-145-1346-5
+always \u6687\u7d66 15-23456-2-13-16-4
+always \u6691\u5047 24-34-4-13-23456-5
+always \u6696\u548c 1345-12456-4-1235-25-1
+always \u66b4\u9732 1234-34-5-14-34-5
+always \u66f2\u5b50 245-1256-4-125-156-1
+always \u66f2\u5c3a 245-1256-3-12-156-4
+always \u66f2\u5ea6 245-1256-3-145-34-5
+always \u66f2\u6298 245-1256-3-1-2346-2
+always \u66f2\u66f2 245-1256-3-245-1256-3
+always \u66f2\u6c83 245-1256-3-25-5
+always \u66f2\u76f4 245-1256-3-1-156-2
+always \u66f2\u7dda 245-1256-3-15-2345-5
+always \u66f2\u89e3 245-1256-3-13-346-4
+always \u66f2\u961c 245-1256-3-12345-34-5
+always \u66f4\u4e8b 13-1356-3-24-156-5
+always \u66f4\u52d5 13-1356-3-145-12346-5
+always \u66f4\u540d 13-1356-3-134-13456-2
+always \u66f4\u5f35 13-1356-3-1-1346-3
+always \u66f4\u5f97 13-1356-5-145-356-4
+always \u66f4\u63db 13-1356-3-1235-12456-5
+always \u66f4\u6539 13-1356-3-13-2456-4
+always \u66f4\u65b0 13-1356-3-15-1456-3
+always \u66f4\u6613 13-1356-3-16-5
+always \u66f4\u66ff 13-1356-3-124-16-5
+always \u66f4\u6b63 13-1356-3-1-1356-5
+always \u66f4\u751f 13-1356-3-24-1356-3
+always \u66f4\u865f 13-1356-3-1235-146-5
+always \u66f4\u8863 13-1356-3-16-3
+always \u66f4\u8fed 13-1356-3-145-346-2
+always \u66f8\u5377 24-34-3-13-45-5
+always \u66f9\u53c3 245-146-2-24-136-3
+always \u66fe\u53c3 125-1356-3-24-136-3
+always \u66fe\u5b50 125-1356-3-125-156-4
+always \u66fe\u5b6b 125-1356-3-15-123456-3
+always \u66fe\u6c0f 125-1356-3-24-156-5
+always \u66fe\u7956 125-1356-3-125-34-4
+always \u66fe\u978f 125-1356-3-13-12346-4
+always \u6703\u5152 1235-1246-4-156-3
+always \u6703\u7a3d 13-1246-5-13-16-3
+always \u6703\u8a08 123-2356-5-13-16-5
+always \u6703\u9577 1235-1246-5-1-1346-4
+always \u6708\u5b50 236-5-125-156-1
+always \u6708\u6688 236-5-256-5
+always \u6708\u7d2f 236-5-14-356-4
+always \u6709\u5206 234-4-12345-136-5
+always \u6709\u671d 234-4-1-146-3
+always \u670d\u5e16 12345-34-2-124-346-3
+always \u671d\u4e09 1-146-3-15-1236-3
+always \u671d\u4e0d 1-146-3-135-34-5
+always \u671d\u4ee4 1-146-3-14-13456-5
+always \u671d\u5915 1-146-3-15-16-5
+always \u671d\u66e6 1-146-3-15-16-3
+always \u671d\u6703 1-146-3-1235-1246-5
+always \u671d\u671d 1-146-3-1-146-3
+always \u671d\u6c23 1-146-3-245-16-5
+always \u671d\u767c 1-146-3-12345-345-3
+always \u671d\u79e6 1-146-3-245-1456-2
+always \u671d\u967d 1-146-3-46-2
+always \u671d\u9732 1-146-3-14-34-5
+always \u671f\u5e74 13-16-3-1345-2345-2
+always \u671f\u6708 13-16-3-236-5
+always \u671f\u670d 13-16-3-12345-34-2
+always \u6728\u585e 134-34-5-15-2456-3
+always \u672a\u4e86 1246-5-14-246-4
+always \u672b\u4e86 134-126-5-14-246-4
+always \u672c\u5206 135-136-4-12345-136-5
+always \u672c\u5b50 135-136-4-125-156-1
+always \u672c\u884c 135-136-4-1235-1346-2
+always \u674e\u5b50 14-16-4-125-156-1
+always \u6751\u5b50 245-123456-3-125-156-1
+always \u6751\u9577 245-123456-3-1-1346-4
+always \u676f\u5b50 135-356-3-125-156-1
+always \u677e\u6fe4 15-12346-3-124-146-3
+always \u679c\u5b50 13-25-4-125-156-1
+always \u67af\u840e 123-34-3-1246-3
+always \u67b6\u5b50 13-23456-5-125-156-1
+always \u67da\u5b50 234-5-125-156-1
+always \u67e5\u52d8 12-345-2-123-1236-5
+always \u67f1\u5b50 1-34-5-125-156-1
+always \u67fa\u5b50 13-2356-4-125-156-1
+always \u67ff\u5b50 24-156-5-125-156-1
+always \u6813\u585e 24-12456-3-15-2456-3
+always \u6813\u5b50 24-12456-3-125-156-1
+always \u6821\u52d8 13-246-5-123-1236-3
+always \u6821\u5c0d 13-246-5-145-1246-5
+always \u6821\u6b63 13-246-5-1-1356-5
+always \u6821\u6e96 13-246-5-1-123456-4
+always \u6821\u8a02 13-246-5-145-13456-5
+always \u6821\u9577 15-246-5-1-1346-4
+always \u6821\u95b1 13-246-5-236-5
+always \u683c\u5b50 13-2346-2-125-156-1
+always \u683d\u7a2e 125-2456-3-1-12346-5
+always \u6843\u5b50 124-146-2-125-156-1
+always \u6846\u5b50 123-456-3-125-156-1
+always \u6848\u5377 1236-5-13-45-5
+always \u6848\u5b50 1236-5-125-156-1
+always \u684c\u5b50 1-25-3-125-156-1
+always \u6876\u5b50 124-12346-4-125-156-1
+always \u687f\u5b50 13-1236-4-125-156-1
+always \u6885\u5b50 134-356-2-125-156-1
+always \u6886\u5b50 135-1346-3-125-156-1
+always \u6897\u585e 13-1356-4-15-2346-5
+always \u689d\u5b50 124-246-2-125-156-1
+always \u68af\u5b50 124-16-3-125-156-1
+always \u68b3\u5b50 24-34-3-125-156-1
+always \u68cd\u5b50 13-123456-5-125-156-1
+always \u68d2\u559d 135-1346-5-1235-2346-5
+always \u68d2\u5b50 135-1346-5-125-156-1
+always \u68da\u5b50 1234-1356-2-125-156-1
+always \u6905\u5b50 16-4-125-156-1
+always \u6930\u5b50 346-2-125-156-1
+always \u6954\u5b50 15-346-5-125-156-1
+always \u69d3\u5b50 13-1346-5-125-156-1
+always \u6a02\u5287 236-5-13-1256-5
+always \u6a02\u5668 236-5-245-16-5
+always \u6a02\u5718 236-5-124-12456-2
+always \u6a02\u58c7 236-5-124-1236-2
+always \u6a02\u5b98 236-5-13-12456-3
+always \u6a02\u5e2b 236-5-24-156-3
+always \u6a02\u5e9c 236-5-12345-34-4
+always \u6a02\u624b 236-5-24-12356-4
+always \u6a02\u66f2 236-5-245-1256-4
+always \u6a02\u6bb5 236-5-145-12456-5
+always \u6a02\u6c34 246-5-24-1246-4
+always \u6a02\u6d3e 236-5-1234-2456-5
+always \u6a02\u7406 236-5-14-16-4
+always \u6a02\u7ae0 236-5-1-1346-3
+always \u6a02\u7c4d 236-5-13-16-2
+always \u6a02\u8b5c 236-5-1234-34-4
+always \u6a02\u8ff7 236-5-134-16-2
+always \u6a02\u968a 236-5-145-1246-5
+always \u6a02\u97f3 236-5-1456-3
+always \u6a02\u98a8 236-5-12345-1356-3
+always \u6a13\u5b50 14-12356-2-125-156-1
+always \u6a21\u5b50 134-126-2-125-156-1
+always \u6a21\u6a23 134-34-2-46-5
+always \u6a23\u5b50 46-5-125-156-1
+always \u6a58\u5b50 13-1256-2-125-156-1
+always \u6a6b\u66b4 1235-1356-5-135-146-5
+always \u6a6b\u6b7b 1235-1356-5-15-156-4
+always \u6a6b\u798d 1235-1356-5-1235-25-5
+always \u6a6b\u8089 1235-1356-5-1245-12356-5
+always \u6a6b\u884c 1235-1356-5-15-13456-2
+always \u6a6b\u8ca1 1235-1356-5-245-2456-2
+always \u6ac3\u5b50 13-1246-5-125-156-1
+always \u6adb\u6bd4 13-346-2-135-16-5
+always \u6b21\u9577 245-156-5-1-1346-4
+always \u6b23\u7fa1 15-1456-3-15-2345-5
+always \u6b38\u4e43 2456-4-1345-2456-4
+always \u6b3d\u5dee 245-1456-3-12-2456-3
+always \u6b3e\u5b50 123-12456-4-125-156-1
+always \u6b65\u5b50 135-34-5-125-156-1
+always \u6b66\u5c07 34-4-13-46-5
+always \u6b6a\u66f2 2356-3-245-1256-3
+always \u6b78\u9084 13-1246-3-1235-12456-2
+always \u6b78\u964d 13-1246-3-15-46-2
+always \u6b7b\u7576 15-156-4-145-1346-5
+always \u6b7b\u96e3 15-156-4-1345-1236-5
+always \u6b89\u96e3 15-256-5-1345-1236-5
+always \u6b9e\u6c92 256-4-134-126-5
+always \u6bba\u4f10 24-345-3-12345-345-3
+always \u6bbc\u5b50 123-2346-2-125-156-1
+always \u6bcf\u884c 134-356-4-1235-1346-2
+always \u6bd4\u53ca 135-16-5-13-16-2
+always \u6bd4\u6bd4 135-16-5-135-16-5
+always \u6bd4\u80a9 135-16-5-13-2345-3
+always \u6bd4\u9130 135-16-5-14-1456-2
+always \u6bef\u5b50 124-1236-4-125-156-1
+always \u6bfd\u5b50 13-2345-5-125-156-1
+always \u6c08\u5b50 1-1236-3-125-156-1
+always \u6c34\u5206 24-1246-4-12345-136-5
+always \u6c57\u4f4d 1235-1236-2-1246-5
+always \u6c57\u570b 1235-1236-2-13-25-2
+always \u6c57\u738b 1235-1236-2-456-2
+always \u6c5f\u90fd 13-46-3-145-34-3
+always \u6c60\u5b50 12-156-2-125-156-1
+always \u6c88\u62ec 24-136-4-13-35-3
+always \u6c88\u6c92 12-136-2-134-126-5
+always \u6c88\u8457 12-136-2-1-25-2
+always \u6c8f\u8336 245-16-5-12-345-2
+always \u6c92\u4e16 134-126-5-24-156-5
+always \u6c92\u5165 134-126-5-1245-34-5
+always \u6c92\u6536 134-126-5-24-12356-3
+always \u6c92\u6c92 134-126-5-134-126-5
+always \u6c92\u843d 134-126-5-14-25-5
+always \u6c92\u85e5 134-126-5-246-5
+always \u6c92\u9802 134-126-5-145-13456-4
+always \u6c92\u98f2 134-126-5-1456-4
+always \u6c92\u9f52 134-126-5-12-156-4
+always \u6c99\u5b50 24-345-3-125-156-1
+always \u6cd5\u570b 12345-345-5-13-25-2
+always \u6cd5\u5b50 12345-345-2-125-156-1
+always \u6cd5\u76f8 12345-345-4-15-46-5
+always \u6cd5\u862d 12345-345-5-14-1236-2
+always \u6cd5\u8a9e 12345-345-5-1256-4
+always \u6cef\u6c92 134-1456-4-134-126-5
+always \u6d0b\u76f8 46-2-15-46-5
+always \u6d0b\u884c 46-2-1235-1346-2
+always \u6d3b\u585e 1235-25-2-15-2456-3
+always \u6d77\u53c3 1235-2456-4-24-136-3
+always \u6d77\u96e3 1235-2456-4-1345-1236-5
+always \u6d88\u9577 15-246-3-1-1346-4
+always \u6dd6\u7d04 12-25-5-236-3
+always \u6de4\u585e 1256-3-15-2346-5
+always \u6df1\u60e1 24-136-3-34-5
+always \u6df1\u66f4 24-136-3-13-13456-3
+always \u6df1\u7701 24-136-3-15-13456-4
+always \u6df7\u4e82 1235-123456-4-14-12456-5
+always \u6df7\u6c34 1235-123456-2-24-1246-4
+always \u6df7\u6dc6 1235-123456-4-246-2
+always \u6df7\u6fc1 1235-123456-2-1-25-2
+always \u6df9\u6c92 2345-3-134-126-5
+always \u6e21\u5047 145-34-5-13-23456-5
+always \u6e23\u5b50 1-345-3-125-156-1
+always \u6e2c\u5ea6 245-2346-5-145-25-5
+always \u6e2c\u91cf 245-2346-5-14-46-2
+always \u6e38\u8aaa 234-2-24-1246-5
+always \u6e6e\u6c92 2345-3-134-126-5
+always \u6e6e\u6ec5 1456-3-134-346-5
+always \u6e6f\u6e6f 24-1346-3-24-1346-3
+always \u6ecb\u9577 125-156-3-1-1346-4
+always \u6ed1\u7a3d 13-34-4-13-16-3
+always \u6ef4\u7b54 145-16-3-145-345-3
+always \u6f02\u4eae 1234-246-5-14-46-5
+always \u6f02\u767d 1234-246-4-135-2456-2
+always \u6f0f\u5b50 14-12356-5-125-156-1
+always \u6f15\u904b 245-146-2-256-5
+always \u6f2b\u5929 134-1236-2-124-2345-3
+always \u6f8e\u6e43 1234-1356-3-1234-2456-5
+always \u6fc0\u5c07 13-16-3-13-46-5
+always \u6fdf\u5357 13-16-4-1345-1236-2
+always \u707d\u96e3 125-2456-3-1345-1236-5
+always \u70ae\u88fd 1234-146-2-1-156-5
+always \u70b8\u91ac 1-345-2-13-46-5
+always \u70ba\u4e86 1246-5-14-2346-1
+always \u70ba\u4ec0 1246-5-24-2346-2
+always \u70ba\u4ed6 1246-5-124-345-3
+always \u70ba\u4f55 1246-5-1235-2346-2
+always \u70ba\u4f60 1246-5-1345-16-4
+always \u70ba\u570b 1246-5-13-25-2
+always \u70ba\u5979 1246-5-124-345-3
+always \u70ba\u59b3 1246-5-1345-16-4
+always \u70ba\u60a8 1246-5-1345-1456-2
+always \u70ba\u6c11 1246-5-134-1456-2
+always \u70ba\u864e 1246-5-1235-34-4
+always \u70d9\u5370 14-146-5-1456-5
+always \u70d9\u9435 14-146-5-124-346-4
+always \u70d9\u9905 14-146-5-135-13456-4
+always \u70f9\u8abf 1234-1356-3-124-246-2
+always \u710a\u63a5 1235-1236-5-13-346-3
+always \u710a\u689d 1235-1236-5-124-246-2
+always \u7121\u7684 34-2-145-16-5
+always \u7121\u7e2b 34-2-12345-1356-5
+always \u7126\u6fdf 13-246-3-1-156-4
+always \u715e\u4f4f 24-345-3-1-34-5
+always \u715e\u8eca 24-345-3-12-2346-3
+always \u7167\u61c9 1-146-5-13456-5
+always \u7167\u76f8 1-146-5-15-46-5
+always \u71ce\u539f 14-246-5-45-2
+always \u71d5\u4eac 2345-3-13-13456-3
+always \u71d5\u5b50 2345-5-125-156-1
+always \u71df\u9577 13456-2-1-1346-4
+always \u7210\u5b50 14-34-2-125-156-1
+always \u722a\u5b50 1-35-4-125-156-1
+always \u722a\u7259 1-146-4-23456-2
+always \u7247\u5b50 1234-2345-5-125-156-1
+always \u724c\u5b50 1234-2456-2-125-156-1
+always \u725b\u4ed4 1345-234-2-125-2456-4
+always \u7292\u52de 123-146-5-14-146-5
+always \u729b\u725b 134-146-2-1345-234-2
+always \u72af\u96e3 12345-1236-5-1345-1236-5
+always \u72c0\u5b50 1-456-5-125-156-1
+always \u72d7\u4ed4 13-12356-4-125-2456-4
+always \u72e9\u7375 24-12356-5-14-346-5
+always \u72fc\u85c9 14-1346-2-13-16-2
+always \u72fc\u865f 14-1346-2-1235-146-2
+always \u731b\u5c07 134-1356-4-13-46-5
+always \u731c\u5ea6 245-2456-3-145-25-5
+always \u7334\u5b50 1235-12356-2-125-156-1
+always \u7343\u5b50 145-2456-3-125-156-1
+always \u7345\u5b50 24-156-3-125-156-1
+always \u7368\u8655 145-34-2-12-34-4
+always \u7387\u5148 24-2356-5-15-2345-3
+always \u7387\u5175 24-2356-5-135-13456-3
+always \u7387\u540c 24-2356-5-124-12346-2
+always \u7387\u5718 24-2356-5-124-12456-2
+always \u7387\u5e2b 24-2356-5-24-156-3
+always \u7387\u6027 24-2356-5-15-13456-5
+always \u7387\u610f 24-2356-5-16-5
+always \u7387\u7136 24-2356-5-1245-1236-2
+always \u7387\u76f4 24-2356-5-1-156-2
+always \u7387\u771f 24-2356-5-1-136-3
+always \u7387\u773e 24-2356-5-1-12346-5
+always \u7387\u8ecd 24-2356-5-13-256-3
+always \u7387\u9818 24-2356-5-14-13456-4
+always \u738b\u51a0 456-2-13-12456-3
+always \u738b\u90fd 456-2-145-34-3
+always \u73a9\u5473 12456-5-1246-5
+always \u73a9\u5ffd 12456-5-1235-34-3
+always \u73e0\u5b50 1-34-3-125-156-1
+always \u73ed\u5b50 135-1236-3-125-156-1
+always \u73ed\u9577 135-1236-3-1-1346-4
+always \u7405\u90aa 14-1346-2-346-2
+always \u743a\u746f 12345-345-5-14-1236-2
+always \u745c\u4f3d 1256-2-13-23456-3
+always \u745f\u7e2e 15-2346-5-15-34-5
+always \u74a7\u9084 135-16-5-1235-12456-2
+always \u74f6\u585e 1234-13456-2-15-2456-3
+always \u74f6\u5b50 1234-13456-2-125-156-1
+always \u751a\u9ebc 24-2346-2-134-2346-1
+always \u751f\u9084 24-1356-3-1235-12456-2
+always \u751f\u9577 24-1356-3-1-1346-4
+always \u752f\u621a 1345-13456-2-245-16-3
+always \u755c\u7267 15-1256-5-134-34-5
+always \u755c\u7522 15-1256-5-12-1236-4
+always \u755c\u7a4d 15-1256-5-13-16-3
+always \u755c\u8b00 15-1256-5-134-12356-2
+always \u755c\u990a 15-1256-5-46-4
+always \u7576\u5dee 145-1346-3-12-2456-3
+always \u7576\u6389 145-1346-5-145-246-5
+always \u7576\u6a5f 145-1346-5-13-16-3
+always \u7576\u7576 145-1346-5-145-1346-5
+always \u7576\u7968 145-1346-5-1234-246-5
+always \u7576\u8eca 145-1346-3-13-1256-3
+always \u7576\u92ea 145-1346-5-1234-34-5
+always \u7599\u7629 13-2346-3-145-345-1
+always \u75b9\u5b50 1-136-4-125-156-1
+always \u75c0\u50c2 13-1256-3-14-12356-2
+always \u75c5\u5047 135-13456-5-13-23456-5
+always \u75db\u60e1 124-12346-5-34-5
+always \u75f1\u5b50 12345-356-5-125-156-1
+always \u760b\u5b50 12345-1356-3-125-156-1
+always \u7626\u524a 24-12356-5-15-236-3
+always \u7626\u5b50 24-12356-5-125-156-1
+always \u7638\u5b50 245-236-2-125-156-1
+always \u7656\u597d 1234-16-4-1235-146-5
+always \u7669\u5b50 14-2456-5-125-156-1
+always \u767c\u9084 12345-345-3-1235-12456-2
+always \u767c\u96e3 12345-345-3-1345-1236-5
+always \u767d\u5377 135-2456-2-13-45-5
+always \u767e\u4e58 135-2456-4-24-1356-5
+always \u767e\u4e86 135-2456-4-14-246-4
+always \u7684\u786e 145-16-2-245-236-5
+always \u7687\u51a0 1235-456-2-13-12456-3
+always \u768b\u9676 13-146-3-246-2
+always \u76ae\u76f8 1234-16-2-15-46-5
+always \u76c6\u5b50 1234-136-2-125-156-1
+always \u76d2\u5b50 1235-2346-2-125-156-1
+always \u76e4\u5b50 1234-1236-2-125-156-1
+always \u76ee\u7684 134-34-5-145-16-5
+always \u76f4\u7387 1-156-2-24-2356-5
+always \u76f8\u4f4d 15-46-5-1246-5
+always \u76f8\u5055 15-46-3-13-346-3
+always \u76f8\u570b 15-46-5-13-25-2
+always \u76f8\u592b 15-46-5-12345-34-3
+always \u76f8\u61c9 15-46-3-13456-5
+always \u76f8\u6a5f 15-46-5-13-16-3
+always \u76f8\u7247 15-46-5-1234-2345-5
+always \u76f8\u7387 15-46-3-24-2356-5
+always \u76f8\u7a31 15-46-3-12-136-5
+always \u76f8\u7c3f 15-46-5-135-34-5
+always \u76f8\u8072 15-46-5-24-1356-3
+always \u76f8\u8655 15-46-3-12-34-4
+always \u76f8\u8853 15-46-5-24-34-5
+always \u76f8\u8c8c 15-46-5-134-146-5
+always \u76f8\u9762 15-46-5-134-2345-5
+always \u7701\u5206 24-1356-4-12345-136-5
+always \u7701\u5bdf 15-13456-4-12-345-2
+always \u7701\u601d 15-13456-4-15-156-3
+always \u7701\u609f 15-13456-4-34-5
+always \u7701\u89aa 15-13456-4-245-1456-3
+always \u7701\u9577 24-1356-4-1-1346-4
+always \u770b\u4e2d 123-1236-5-1-12346-5
+always \u770b\u5b88 123-1236-3-24-12356-4
+always \u770b\u5bb6 123-1236-3-13-23456-3
+always \u770b\u76f8 123-1236-5-15-46-5
+always \u770b\u7ba1 123-1236-3-13-12456-4
+always \u770b\u8b77 123-1236-3-1235-34-5
+always \u770b\u9580 123-1236-3-134-136-2
+always \u771f\u7387 1-136-3-24-2356-5
+always \u771f\u76f8 1-136-3-15-46-5
+always \u7738\u5b50 134-12356-2-125-156-1
+always \u773c\u6688 2345-4-256-5
+always \u773c\u7736 2345-4-123-456-3
+always \u7761\u8457 24-1246-5-1-146-2
+always \u7761\u89ba 24-1246-5-13-246-5
+always \u7763\u7387 145-34-3-24-2356-5
+always \u778e\u5b50 15-23456-3-125-156-1
+always \u77ad\u671b 14-246-5-456-5
+always \u77e5\u4e86 1-156-3-14-246-4
+always \u77ee\u5b50 2456-4-125-156-1
+always \u77f3\u5b50 24-156-2-125-156-1
+always \u77f3\u8108 24-156-2-134-2456-5
+always \u7802\u5b50 24-345-3-125-156-1
+always \u780d\u4f10 123-1236-4-12345-345-3
+always \u7834\u76f8 1234-126-5-15-46-5
+always \u7891\u5e16 135-356-3-124-346-5
+always \u789f\u5b50 145-346-2-125-156-1
+always \u78bc\u5b50 134-345-4-125-156-1
+always \u78c5\u7921 1234-1346-3-135-126-2
+always \u78e8\u96e3 134-126-2-1345-1236-5
+always \u7926\u8108 123-456-5-134-2456-5
+always \u793e\u9577 24-2346-5-1-1346-4
+always \u7957\u6709 1-156-4-234-4
+always \u7957\u80fd 1-156-4-1345-1356-2
+always \u7957\u8981 1-156-4-246-5
+always \u795e\u7947 24-136-2-245-16-2
+always \u7968\u5b50 1234-246-5-125-156-1
+always \u7981\u4e0d 13-1456-3-135-34-5
+always \u798f\u76f8 12345-34-2-15-46-5
+always \u79aa\u8b93 24-1236-5-1245-1346-5
+always \u79ae\u6a02 14-16-4-236-5
+always \u79bf\u5b50 124-34-3-125-156-1
+always \u79d1\u9577 123-2346-3-1-1346-4
+always \u7A31\u8077 12-1356-5-1-156-2
+always \u7a2e\u690d 1-12346-5-1-156-2
+always \u7a2e\u6a39 1-12346-5-24-34-5
+always \u7a31\u5fc3 12-136-5-15-1456-3
+always \u7a31\u610f 12-136-5-16-5
+always \u7a3b\u5b50 145-146-5-125-156-1
+always \u7a3f\u5b50 13-146-4-125-156-1
+always \u7a40\u5b50 13-34-4-125-156-1
+always \u7a4d\u7d2f 13-16-3-14-356-4
+always \u7a69\u7576 123456-4-145-1346-5
+always \u7a7a\u5730 123-12346-5-145-16-5
+always \u7a7a\u683c 123-12346-5-13-2346-2
+always \u7a7a\u9592 123-12346-5-15-2345-2
+always \u7a7a\u9699 123-12346-5-15-16-5
+always \u7a7a\u96e3 123-12346-3-1345-1236-5
+always \u7a7a\u984d 123-12346-5-2346-2
+always \u7a7f\u8457 12-12456-3-1-25-2
+always \u7a97\u5b50 12-456-3-125-156-1
+always \u7aae\u76f8 245-235-2-15-46-5
+always \u7aaf\u5b50 246-2-125-156-1
+always \u7ad9\u9577 1-1236-5-1-1346-4
+always \u7ad9\u957f 1-1236-5-1-1346-4
+always \u7b1b\u5b50 145-16-2-125-156-1
+always \u7b26\u61c9 12345-34-2-13456-5
+always \u7b46\u4f10 135-16-4-12345-345-3
+always \u7b49\u5206 145-1356-4-12345-136-5
+always \u7b54\u61c9 145-345-3-13456-5
+always \u7b54\u7406 145-345-3-14-16-4
+always \u7b54\u7b54 145-345-3-145-345-3
+always \u7b56\u5212 245-2346-5-1235-35-5
+always \u7b56\u61c9 245-2346-5-13456-5
+always \u7b77\u5b50 123-2356-5-125-156-1
+always \u7ba1\u5b50 13-12456-4-125-156-1
+always \u7ba1\u6a02 13-12456-4-236-5
+always \u7bad\u93c3 13-2345-5-245-34-5
+always \u7bb1\u5b50 15-46-3-125-156-1
+always \u7be9\u5b50 24-2456-3-125-156-1
+always \u7c1e\u98df 145-1236-3-15-156-5
+always \u7c2a\u5b50 125-1236-3-125-156-1
+always \u7c3d\u7f72 245-2345-3-24-34-5
+always \u7c3e\u5b50 14-2345-2-125-156-1
+always \u7c3f\u5b50 135-34-5-125-156-1
+always \u7c43\u5b50 14-1236-2-125-156-1
+always \u7c4d\u6c92 13-16-2-134-126-5
+always \u7c60\u5b50 14-12346-2-125-156-1
+always \u7c73\u884c 134-16-4-1235-1346-2
+always \u7c97\u7377 245-34-3-123-456-5
+always \u7c97\u7387 245-34-3-24-2356-5
+always \u7c98\u8cbc 1-1236-3-124-346-3
+always \u7cbd\u5b50 125-12346-5-125-156-1
+always \u7cd9\u7c73 245-146-5-134-16-4
+always \u7cf0\u5b50 124-12456-2-125-156-1
+always \u7d00\u50b3 13-16-5-1-12456-5
+always \u7d13\u96e3 24-34-3-1345-1236-5
+always \u7d20\u884c 15-34-5-15-13456-5
+always \u7d2f\u4e16 14-356-4-24-156-5
+always \u7d2f\u52a0 14-356-4-13-23456-3
+always \u7d2f\u5375 14-356-4-14-12456-4
+always \u7d2f\u5b98 14-356-4-13-12456-3
+always \u7d2f\u65e5 14-356-4-1245-156-5
+always \u7d2f\u6708 14-356-4-236-5
+always \u7d2f\u6b21 14-356-4-245-156-5
+always \u7d2f\u72af 14-356-4-12345-1236-5
+always \u7d2f\u7a4d 14-356-4-13-16-3
+always \u7d2f\u7d2f 14-356-4-14-356-4
+always \u7d2f\u8d05 14-356-4-1-1246-5
+always \u7d2f\u9032 14-356-4-13-1456-5
+always \u7d42\u4e86 1-12346-3-14-246-4
+always \u7d44\u9577 125-34-4-1-1346-4
+always \u7d50\u5be6 13-346-3-24-156-2
+always \u7d50\u5df4 13-346-3-135-345-3
+always \u7d66\u4ed8 13-16-4-12345-34-5
+always \u7d68\u5462 1245-12346-2-1345-16-2
+always \u7d71\u7387 124-12346-4-24-2356-5
+always \u7d93\u50b3 13-13456-3-1-12456-5
+always \u7d93\u8108 13-13456-3-134-2456-5
+always \u7db2\u5b50 456-4-125-156-1
+always \u7db8\u5dfe 13-12456-3-13-1456-3
+always \u7de3\u5206 45-2-12345-136-5
+always \u7de8\u8457 135-2345-3-1-34-5
+always \u7e23\u5206 15-2345-5-12345-136-5
+always \u7e23\u9577 15-2345-5-1-1346-4
+always \u7e2b\u9699 12345-1356-5-15-16-5
+always \u7e31\u6a6b 125-12346-3-1235-1356-2
+always \u7e31\u8cab 125-12346-3-13-12456-5
+always \u7e3d\u5f97 125-12346-4-145-356-4
+always \u7e3d\u884c 125-12346-4-1235-1346-2
+always \u7e3d\u9577 125-12346-4-1-1346-4
+always \u7e43\u5b50 135-1356-3-125-156-1
+always \u7e69\u5b50 24-1356-2-125-156-1
+always \u7e8c\u5047 15-1256-5-13-23456-5
+always \u7f3a\u7a7a 245-236-3-123-12346-5
+always \u7f48\u5b50 124-1236-2-125-156-1
+always \u7f50\u5b50 13-12456-5-125-156-1
+always \u7f69\u5b50 1-146-5-125-156-1
+always \u7f72\u540d 24-34-5-134-13456-2
+always \u7f79\u96e3 14-16-2-1345-1236-5
+always \u7f9e\u602f 15-234-3-245-236-5
+always \u7f9e\u60e1 15-234-3-34-5
+always \u7ff9\u695a 245-246-2-12-34-4
+always \u7ff9\u9996 245-246-2-24-12356-4
+always \u8001\u5c07 14-146-4-13-46-5
+always \u8001\u5c11 14-146-4-24-146-5
+always \u8003\u5377 123-146-4-13-45-5
+always \u8003\u91cf 123-146-4-14-46-2
+always \u8015\u7a2e 13-1356-3-1-12346-5
+always \u8017\u5b50 1235-146-5-125-156-1
+always \u805e\u9054 123456-5-145-345-2
+always \u8072\u6a02 24-1356-3-236-5
+always \u8077\u5206 1-156-2-12345-136-5
+always \u807d\u5929 124-13456-5-124-2345-3
+always \u807d\u5dee 124-13456-3-12-2456-3
+always \u8086\u61c9 15-156-5-13456-5
+always \u8098\u5b50 1-12356-4-125-156-1
+always \u809a\u5b50 145-34-5-125-156-1
+always \u80a1\u5206 13-34-4-12345-136-5
+always \u80c6\u5b50 145-1236-4-125-156-1
+always \u80d6\u5b50 1234-1346-5-125-156-1
+always \u80da\u5b50 1234-356-3-125-156-1
+always \u80f0\u5b50 16-2-125-156-1
+always \u8108\u5bec 134-2456-5-123-12456-3
+always \u8108\u5e45 134-2456-5-12345-34-2
+always \u8108\u640f 134-2456-5-135-126-2
+always \u8108\u7406 134-2456-5-14-16-4
+always \u8108\u78bc 134-2456-5-134-345-4
+always \u8108\u7d61 134-2456-5-14-25-5
+always \u8108\u983b 134-2456-5-1234-1456-2
+always \u8116\u5b50 135-126-2-125-156-1
+always \u8166\u5b50 1345-146-4-125-156-1
+always \u8178\u5b50 12-1346-2-125-156-1
+always \u817f\u5b50 124-1246-4-125-156-1
+always \u8180\u5b50 135-1346-4-125-156-1
+always \u8180\u80f1 1234-1346-2-13-456-3
+always \u8180\u81c2 135-1346-4-135-16-5
+always \u819c\u62dc 134-126-2-135-2456-5
+always \u81bd\u5b50 145-1236-4-125-156-1
+always \u81bd\u602f 145-1236-4-245-236-5
+always \u81c6\u5ea6 16-5-145-25-5
+always \u81e5\u92ea 25-5-1234-34-5
+always \u81e7\u5426 125-1346-3-1234-16-4
+always \u81e8\u6d2e 14-1456-2-246-2
+always \u81e8\u96e3 14-1456-2-1345-1236-5
+always \u81ea\u50b3 125-156-5-1-12456-5
+always \u81ea\u7701 125-156-5-15-13456-4
+always \u81ea\u7d66 125-156-5-13-16-4
+always \u8205\u5b50 13-234-5-125-156-1
+always \u8208\u5473 15-13456-5-1246-5
+always \u8208\u7dfb 15-13456-5-1-156-5
+always \u8208\u8208 15-13456-5-15-13456-5
+always \u8208\u8da3 15-13456-5-245-1256-5
+always \u820a\u90fd 13-234-5-145-34-3
+always \u820c\u82d4 24-2346-2-124-2456-3
+always \u820d\u68c4 24-2346-4-245-16-5
+always \u8216\u5f35 1234-34-3-1-1346-3
+always \u8216\u6392 1234-34-3-1234-2456-2
+always \u822c\u82e5 135-126-3-1245-2346-4
+always \u8239\u9577 12-12456-2-1-1346-4
+always \u8239\u96e3 12-12456-2-1345-1236-5
+always \u8266\u9577 13-2345-5-1-1346-4
+always \u826f\u5c07 14-46-2-13-46-5
+always \u8272\u76f8 15-2346-5-15-46-5
+always \u82b1\u51a0 1235-35-3-13-12456-3
+always \u82e5\u5e79 1245-25-5-13-1236-3
+always \u82e6\u96e3 123-34-4-1345-1236-5
+always \u82f1\u6cd5 13456-3-12345-345-5
+always \u8304\u5b50 245-346-2-125-156-1
+always \u8305\u585e 134-146-2-15-2346-5
+always \u8349\u7387 245-146-4-24-2356-5
+always \u8378\u85ba 135-16-2-245-16-2
+always \u83f2\u8584 12345-356-4-135-126-2
+always \u840e\u7e2e 1246-3-15-25-3
+always \u842c\u4e58 12456-5-24-1356-5
+always \u842c\u5377 12456-5-13-45-5
+always \u843d\u96e3 14-25-5-1345-1236-5
+always \u843d\u9b44 14-25-5-124-25-5
+always \u8449\u5b50 346-5-125-156-1
+always \u8449\u8108 346-5-134-2456-5
+always \u8457\u4f5c 1-34-5-125-25-5
+always \u8457\u529b 1-25-2-14-16-5
+always \u8457\u540d 1-34-5-134-13456-2
+always \u8457\u5be6 1-25-2-24-156-2
+always \u8457\u5e8a 1-25-2-12-456-2
+always \u8457\u6025 1-146-3-13-16-2
+always \u8457\u60f3 1-146-2-15-46-4
+always \u8457\u614c 1-146-2-1235-456-3
+always \u8457\u624b 1-25-2-24-12356-4
+always \u8457\u66f8 1-34-5-24-34-3
+always \u8457\u6709 1-34-5-234-4
+always \u8457\u68cb 1-25-2-245-16-2
+always \u8457\u6dbc 1-146-3-14-46-2
+always \u8457\u706b 1-146-2-1235-25-4
+always \u8457\u8005 1-34-5-1-2346-4
+always \u8457\u8272 1-25-2-15-2346-5
+always \u8457\u843d 1-146-2-14-25-5
+always \u8457\u8457 1-25-2-1-2346-1
+always \u8457\u8863 1-25-2-16-3
+always \u8457\u8ff0 1-34-5-24-34-5
+always \u8457\u91cd 1-25-2-1-12346-5
+always \u8457\u9678 1-25-2-14-34-5
+always \u8499\u96e3 134-1356-2-1345-1236-5
+always \u84c6\u5b50 15-16-2-125-156-1
+always \u84cb\u5b50 13-2456-5-125-156-1
+always \u84fc\u83aa 14-34-5-2346-2
+always \u8584\u8377 135-126-5-1235-2346-2
+always \u8584\u884c 135-126-2-15-13456-5
+always \u85c9\u85c9 13-16-2-13-16-2
+always \u85e4\u5b50 124-1356-2-125-156-1
+always \u85e5\u884c 246-5-1235-1346-2
+always \u85e5\u92ea 246-5-1234-34-5
+always \u8655\u4e8b 12-34-4-24-156-5
+always \u8655\u4e8e 12-34-4-1256-2
+always \u8655\u5206 12-34-4-12345-136-5
+always \u8655\u5883 12-34-4-13-13456-5
+always \u8655\u65bc 12-34-4-1256-2
+always \u8655\u7406 12-34-4-14-16-4
+always \u8655\u7f70 12-34-4-12345-345-2
+always \u8655\u9577 12-34-5-1-1346-4
+always \u865f\u54ed 1235-146-2-123-34-3
+always \u868a\u5b50 123456-2-125-156-1
+always \u86e4\u868c 13-2346-4-135-1346-5
+always \u86e4\u86a7 13-2346-4-13-346-5
+always \u86e4\u870a 13-2346-4-14-16-5
+always \u86fb\u5316 124-1246-5-1235-35-5
+always \u8766\u87c6 1235-345-2-134-345-1
+always \u8768\u5b50 24-156-3-125-156-1
+always \u87ec\u86fb 12-1236-2-124-1246-5
+always \u87f2\u5b50 12-12346-2-125-156-1
+always \u883b\u6a6b 134-1236-2-1235-1356-5
+always \u8840\u6688 15-346-4-256-5
+always \u8840\u9084 15-346-4-1235-12456-2
+always \u884c\u4e1a 1235-1346-2-346-5
+always \u884c\u4f0d 1235-1346-2-34-4
+always \u884c\u5217 1235-1346-2-14-346-5
+always \u884c\u54e1 1235-1346-2-45-2
+always \u884c\u5bb6 1235-1346-2-13-23456-3
+always \u884c\u5eab 1235-1346-2-123-34-5
+always \u884c\u60c5 1235-1346-2-245-13456-2
+always \u884c\u6578 1235-1346-2-24-34-5
+always \u884c\u6703 1235-1346-2-1235-1246-5
+always \u884c\u696d 1235-1346-2-346-5
+always \u884c\u6b3e 1235-1346-2-123-12456-4
+always \u884c\u72c0 15-13456-5-1-456-5
+always \u884c\u865f 1235-1346-2-1235-146-5
+always \u884c\u884c 1235-1346-2-15-13456-2
+always \u884c\u898f 1235-1346-2-13-1246-3
+always \u884c\u8a71 1235-1346-2-1235-35-5
+always \u884c\u9593 1235-1346-2-13-2345-3
+always \u885d\u51a0 12-12346-3-13-12456-3
+always \u8861\u91cf 1235-1356-2-14-46-2
+always \u8863\u51a0 16-3-13-12456-3
+always \u8863\u8457 16-3-1-25-2
+always \u8863\u88f3 16-3-24-1346-1
+always \u8868\u7387 135-246-4-24-2356-5
+always \u8868\u76f8 135-246-4-15-46-5
+always \u888b\u5b50 145-2456-5-125-156-1
+always \u8896\u5b50 15-234-5-125-156-1
+always \u88ab\u5b50 135-356-5-125-156-1
+always \u88c1\u5ea6 245-2456-2-145-25-5
+always \u88c1\u91cf 245-2456-2-14-46-2
+always \u88c2\u7e2b 14-346-5-12345-1356-5
+always \u88d9\u5b50 245-256-2-125-156-1
+always \u88dc\u5047 135-34-4-13-23456-5
+always \u88dc\u7d66 135-34-4-13-16-4
+always \u88dc\u9084 135-34-4-1235-12456-2
+always \u88dc\u95d5 135-34-4-245-236-3
+always \u88e1\u5b50 14-16-4-125-156-1
+always \u8902\u5b50 13-35-5-125-156-1
+always \u8932\u5b50 123-34-5-125-156-1
+always \u893b\u73a9 15-346-5-12456-5
+always \u896a\u5b50 35-5-125-156-1
+always \u897f\u6a02 15-16-3-236-5
+always \u8981\u633e 246-3-15-346-2
+always \u8981\u6c42 246-3-245-234-2
+always \u8981\u8105 246-3-15-346-2
+always \u8986\u6821 12345-34-5-13-246-5
+always \u8986\u6c92 12345-34-5-134-126-5
+always \u89aa\u5bb6 245-13456-5-13-23456-3
+always \u8a0e\u4f10 124-146-4-12345-345-3
+always \u8a3a\u8108 1-136-4-134-2456-5
+always \u8a50\u964d 1-345-5-15-46-2
+always \u8a55\u50b3 1234-13456-2-1-12456-5
+always \u8a55\u91cf 1234-13456-2-14-46-2
+always \u8a66\u5377 24-156-5-13-45-5
+always \u8a72\u884c 13-2456-3-1235-1346-2
+always \u8a86\u9a19 123-456-3-1234-2345-5
+always \u8aaa\u5ba2 24-1246-5-123-2346-5
+always \u8aaa\u670d 24-1246-5-12345-34-2
+always \u8abf\u505c 124-246-2-124-13456-2
+always \u8abf\u5408 124-246-2-1235-2346-2
+always \u8abf\u5b50 145-246-5-125-156-1
+always \u8abf\u6574 124-246-2-1-1356-4
+always \u8abf\u7bc0 124-246-2-13-346-2
+always \u8abf\u89e3 124-246-2-13-346-4
+always \u8abf\u9577 124-246-2-1-1346-4
+always \u8abf\u990a 124-246-2-46-4
+always \u8acb\u5047 245-13456-4-13-23456-5
+always \u8ad6\u8457 14-123456-5-1-34-5
+always \u8ad6\u8a9e 14-123456-2-1256-4
+always \u8b1d\u6713 15-346-5-124-246-5
+always \u8b3e\u7f75 134-1236-5-134-345-5
+always \u8b58\u76f8 24-156-5-15-46-5
+always \u8b5c\u5b50 1234-34-4-125-156-1
+always \u8b70\u8655 16-5-12-34-4
+always \u8b70\u9577 16-5-1-1346-4
+always \u8b8a\u66f4 135-2345-5-13-1356-3
+always \u8b8a\u76f8 135-2345-5-15-46-5
+always \u8ba1\u5212 13-16-5-1235-35-5
+always \u8c46\u5b50 145-12356-5-125-156-1
+always \u8c46\u8c49 145-12356-5-12-156-4
+always \u8c6c\u4ed4 1-34-3-125-2456-4
+always \u8c6c\u5708 1-34-3-13-45-5
+always \u8c79\u5b50 135-146-5-125-156-1
+always \u8c8c\u76f8 134-146-5-15-46-5
+always \u8ca0\u7d2f 12345-34-5-14-356-4
+always \u8ca0\u8377 12345-34-5-1235-2346-5
+always \u8ca1\u76f8 245-2456-2-15-46-5
+always \u8ca1\u9577 245-2456-2-1-1346-4
+always \u8ca9\u5b50 12345-1236-5-125-156-1
+always \u8cde\u73a9 24-1346-4-12456-5
+always \u8ce2\u76f8 15-2345-2-15-46-5
+always \u8d74\u96e3 12345-34-5-1345-1236-5
+always \u8d77\u5b50 245-16-4-125-156-1
+always \u8ddb\u5b50 135-126-4-125-156-1
+always \u8def\u5b50 14-34-5-125-156-1
+always \u8df3\u884c 124-246-5-1235-1346-2
+always \u8e09\u8e4c 14-46-5-245-46-5
+always \u8e44\u5b50 124-16-2-125-156-1
+always \u8e4a\u8e7a 15-16-3-245-246-5
+always \u8eab\u5206 24-136-3-12345-136-5
+always \u8eab\u5b50 24-136-3-125-156-1
+always \u8eca\u5b50 12-2346-3-125-156-1
+always \u8eca\u884c 12-2346-3-1235-1346-2
+always \u8eca\u9577 12-2346-3-1-1346-4
+always \u8ecb\u6232 13-345-3-15-16-5
+always \u8ecd\u6a02 13-256-3-236-5
+always \u8ecd\u9577 13-256-3-1-1346-4
+always \u8f15\u7387 245-13456-3-24-2356-5
+always \u8f29\u5206 135-356-5-12345-136-5
+always \u8f29\u5b50 135-356-5-125-156-1
+always \u8f2a\u5b50 14-123456-2-125-156-1
+always \u8fae\u5b50 135-2345-5-125-156-1
+always \u8fb1\u6c92 1245-34-5-134-126-5
+always \u8fd4\u9084 12345-1236-4-1235-12456-2
+always \u9000\u9084 124-1246-5-1235-12456-2
+always \u9002\u5408 24-156-5-1235-2346-2
+always \u9003\u96e3 124-146-2-1345-1236-5
+always \u9023\u9577 14-2345-2-1-1346-4
+always \u903e\u5206 1256-2-12345-136-5
+always \u9047\u96e3 1256-5-1345-1236-5
+always \u904a\u8aaa 234-2-24-1246-5
+always \u904e\u5206 13-25-5-12345-136-5
+always \u904e\u7576 13-25-5-145-1346-5
+always \u9053\u89c0 145-146-5-13-12456-5
+always \u905b\u9054 14-234-3-145-345-3
+always \u9063\u5c07 245-2345-4-13-46-5
+always \u9069\u61c9 24-156-5-13456-5
+always \u9069\u7576 24-156-5-145-1346-5
+always \u906d\u96e3 125-146-3-1345-1236-5
+always \u9077\u90fd 245-2345-3-145-34-3
+always \u907f\u96e3 135-16-5-1345-1236-5
+always \u9084\u4fd7 1235-12456-2-15-34-2
+always \u9084\u50f9 1235-12456-2-13-23456-5
+always \u9084\u539f 1235-12456-2-45-2
+always \u9084\u624b 1235-12456-2-24-12356-4
+always \u9084\u672c 1235-12456-2-135-136-4
+always \u9084\u7259 1235-12456-2-23456-2
+always \u9084\u773c 1235-12456-2-2345-4
+always \u9084\u79ae 1235-12456-2-14-16-4
+always \u9084\u81f3 1235-12456-2-1-156-5
+always \u9084\u9109 1235-12456-2-15-46-3
+always \u9084\u9858 1235-12456-2-45-5
+always \u9084\u9b42 1235-12456-2-1235-123456-2
+always \u908a\u5fbc 135-2345-3-13-246-5
+always \u90a3\u4e48 1345-345-5-134-2346-1
+always \u90a3\u582a 1345-345-4-123-1236-3
+always \u90a3\u6709 1345-345-4-234-4
+always \u90a3\u80fd 1345-345-4-1345-1356-2
+always \u90aa\u884c 15-346-2-15-13456-5
+always \u90e1\u9577 13-256-5-1-1346-4
+always \u90e2\u90fd 13456-4-145-34-3
+always \u90e8\u5206 135-34-5-12345-136-5
+always \u90e8\u5c07 135-34-5-13-46-5
+always \u90e8\u9577 135-34-5-1-1346-4
+always \u90f5\u5dee 234-2-12-2456-3
+always \u90fd\u5175 145-34-3-135-13456-3
+always \u90fd\u53f8 145-34-3-15-156-3
+always \u90fd\u57ce 145-34-3-12-1356-2
+always \u90fd\u5bdf 145-34-3-12-345-2
+always \u90fd\u5c09 145-34-3-1246-5
+always \u90fd\u5e02 145-34-3-24-156-5
+always \u90fd\u5ec1 145-34-3-245-2346-5
+always \u90fd\u723e 145-34-3-156-4
+always \u90fd\u7763 145-34-3-145-34-3
+always \u90fd\u7d71 145-34-3-124-12346-4
+always \u90fd\u8ecd 145-34-3-13-256-3
+always \u90fd\u9091 145-34-3-16-5
+always \u9109\u5c0e 15-46-5-145-146-4
+always \u9109\u9577 15-46-3-1-1346-4
+always \u914b\u9577 245-234-2-1-1346-4
+always \u914d\u6a02 1234-356-5-236-5
+always \u914d\u7d66 1234-356-5-13-16-4
+always \u916c\u61c9 12-12356-2-13456-5
+always \u91cb\u5377 24-156-5-13-45-5
+always \u91cc\u9577 14-16-4-1-1346-4
+always \u91cd\u4e5d 12-12346-2-13-234-4
+always \u91cd\u4f30 12-12346-2-13-34-3
+always \u91cd\u4f86 12-12346-2-14-2456-2
+always \u91cd\u4fee 12-12346-2-15-234-3
+always \u91cd\u5144 12-12346-2-15-235-3
+always \u91cd\u5149 12-12346-2-13-456-3
+always \u91cd\u5165 12-12346-2-1245-34-5
+always \u91cd\u520a 12-12346-2-123-1236-3
+always \u91cd\u5370 12-12346-2-1456-5
+always \u91cd\u56de 12-12346-2-1235-1246-2
+always \u91cd\u570d 12-12346-2-1246-2
+always \u91cd\u594f 12-12346-2-125-12356-5
+always \u91cd\u5a5a 12-12346-2-1235-123456-3
+always \u91cd\u5b6b 12-12346-2-15-123456-3
+always \u91cd\u5b9a 12-12346-2-145-13456-5
+always \u91cd\u5beb 12-12346-2-15-346-4
+always \u91cd\u5efa 12-12346-2-13-2345-5
+always \u91cd\u5fa9 12-12346-2-12345-34-5
+always \u91cd\u6176 12-12346-2-245-13456-5
+always \u91cd\u6284 12-12346-2-12-146-3
+always \u91cd\u632f 12-12346-2-1-136-5
+always \u91cd\u6574 12-12346-2-1-1356-4
+always \u91cd\u65b0 12-12346-2-15-1456-3
+always \u91cd\u6d0b 12-12346-2-46-2
+always \u91cd\u6e2c 12-12346-2-245-2346-5
+always \u91cd\u6f14 12-12346-2-2345-4
+always \u91cd\u7372 12-12346-2-1235-25-5
+always \u91cd\u73fe 12-12346-2-15-2345-5
+always \u91cd\u7533 12-12346-2-24-136-3
+always \u91cd\u758a 12-12346-2-145-346-2
+always \u91cd\u7d44 12-12346-2-125-34-4
+always \u91cd\u7f6e 12-12346-2-1-156-5
+always \u91cd\u8003 12-12346-2-123-146-4
+always \u91cd\u8907 12-12346-2-12345-34-5
+always \u91cd\u8a2d 12-12346-2-24-2346-5
+always \u91cd\u8e48 12-12346-2-145-146-5
+always \u91cd\u8fd4 12-12346-2-12345-1236-4
+always \u91cd\u8ff0 12-12346-2-24-34-5
+always \u91cd\u9022 12-12346-2-12345-1356-2
+always \u91cd\u91cd 12-12346-2-12-12346-2
+always \u91cd\u967d 12-12346-2-46-2
+always \u91d1\u5b50 13-1456-3-125-156-1
+always \u91d8\u5b50 145-13456-3-125-156-1
+always \u91e6\u5b50 123-12356-5-125-156-1
+always \u9245\u8457 13-1256-5-1-34-5
+always \u9257\u5b50 245-2345-2-125-156-1
+always \u9264\u5b50 13-12356-3-125-156-1
+always \u9280\u5b50 1456-2-125-156-1
+always \u9280\u884c 1456-2-1235-1346-2
+always \u92b7\u5047 15-246-3-13-23456-5
+always \u92ea\u4f4d 1234-34-5-1246-5
+always \u92ea\u5b50 1234-34-5-125-156-1
+always \u92ea\u9762 1234-34-5-134-2345-5
+always \u92f8\u5b50 13-1256-5-125-156-1
+always \u9320\u5b50 145-13456-5-125-156-1
+always \u934a\u5b50 14-2345-5-125-156-1
+always \u934b\u5b50 13-25-3-125-156-1
+always \u939a\u5b50 12-1246-2-125-156-1
+always \u93ac\u4eac 1235-146-5-13-13456-3
+always \u93e1\u5b50 13-13456-5-125-156-1
+always \u9435\u9a0e 124-346-4-13-16-5
+always \u9470\u5319 246-5-24-156-4
+always \u9472\u5d4c 15-46-3-245-2345-5
+always \u947d\u6212 125-12456-5-13-346-5
+always \u947d\u77f3 125-12456-5-24-156-2
+always \u947f\u5b50 125-146-2-125-156-1
+always \u9577\u4f7f 1-1346-4-24-156-4
+always \u9577\u5047 12-1346-2-13-23456-5
+always \u9577\u50cf 1-1346-4-15-46-5
+always \u9577\u5144 1-1346-4-15-235-3
+always \u9577\u5927 1-1346-4-145-345-5
+always \u9577\u5973 1-1346-4-1345-1256-4
+always \u9577\u5b50 1-1346-4-125-156-4
+always \u9577\u5b6b 1-1346-4-15-123456-3
+always \u9577\u5b98 1-1346-4-13-12456-3
+always \u9577\u5e7c 1-1346-4-234-5
+always \u9577\u623f 1-1346-4-12345-1346-2
+always \u9577\u7537 1-1346-4-1345-1236-2
+always \u9577\u8001 1-1346-4-14-146-4
+always \u9577\u8005 1-1346-4-1-2346-4
+always \u9577\u8457 1-1346-4-1-2346-1
+always \u9577\u865f 12-1346-2-1235-146-2
+always \u9577\u8f29 1-1346-4-135-356-5
+always \u9577\u9032 1-1346-4-13-1456-5
+always \u9580\u5b50 134-136-2-125-156-1
+always \u9580\u6846 134-136-2-123-456-5
+always \u9580\u6abb 134-136-2-123-1236-4
+always \u9580\u7e2b 134-136-2-12345-1356-5
+always \u9589\u585e 135-16-5-15-2346-5
+always \u958b\u8869 123-2456-3-12-345-5
+always \u9592\u6563 15-2345-2-15-1236-4
+always \u9592\u7a7a 15-2345-2-123-12346-5
+always \u9593\u63a5 13-2345-5-13-346-3
+always \u9593\u65b7 13-2345-5-145-12456-5
+always \u9593\u6b47 13-2345-5-15-346-3
+always \u9593\u8adc 13-2345-5-145-346-2
+always \u9593\u9694 13-2345-5-13-2346-2
+always \u9593\u9699 13-2345-5-15-16-5
+always \u95a3\u5b50 13-2346-2-125-156-1
+always \u95b1\u5377 236-5-13-45-5
+always \u95d5\u5931 245-236-3-24-156-3
+always \u95d5\u5982 245-236-3-1245-34-2
+always \u95d5\u6f0f 245-236-3-14-12356-5
+always \u95d5\u7591 245-236-3-16-2
+always \u963b\u5687 125-34-4-1235-2346-5
+always \u963b\u585e 125-34-4-15-2346-5
+always \u963f\u4fd7 2346-3-15-34-2
+always \u963f\u8adb 2346-3-1256-2
+always \u9644\u548c 12345-34-5-1235-2346-5
+always \u9644\u8457 12345-34-5-1-25-2
+always \u964d\u4f0f 15-46-2-12345-34-2
+always \u964d\u5c07 15-46-2-13-46-5
+always \u964d\u6575 15-46-2-145-16-2
+always \u964d\u66f8 15-46-2-24-34-3
+always \u964d\u670d 15-46-2-12345-34-2
+always \u964d\u9f8d 15-46-2-14-12346-2
+always \u9662\u5b50 45-5-125-156-1
+always \u9662\u9577 45-5-1-1346-4
+always \u9663\u5b50 1-136-5-125-156-1
+always \u9678\u3001 14-234-5-6-0
+always \u968a\u9577 145-1246-5-1-1346-4
+always \u96a8\u8208 15-1246-2-15-13456-5
+always \u96b1\u6c92 1456-4-134-126-5
+always \u96c5\u6a02 23456-4-236-5
+always \u96d9\u91cd 24-456-3-12-12346-2
+always \u96e2\u9593 14-16-2-13-2345-5
+always \u96e3\u5730 1345-1236-5-145-16-5
+always \u96e3\u6c11 1345-1236-5-134-1456-2
+always \u96e3\u80f8 1345-1236-5-15-235-3
+always \u96ea\u8304 15-236-4-13-23456-3
+always \u96fb\u710a 145-2345-5-1235-1236-5
+always \u9732\u767d 14-12356-5-135-2456-2
+always \u9732\u76f8 14-12356-5-15-46-5
+always \u9732\u81c9 14-12356-5-14-2345-4
+always \u9732\u9762 14-12356-5-134-2345-5
+always \u9756\u96e3 13-13456-5-1345-1236-5
+always \u975c\u8108 13-13456-5-134-2456-5
+always \u975e\u5206 12345-356-3-12345-136-5
+always \u975e\u96e3 12345-356-3-1345-1236-5
+always \u9762\u5b50 134-2345-5-125-156-1
+always \u9774\u5b50 15-236-3-125-156-1
+always \u978b\u5b50 15-346-2-125-156-1
+always \u978d\u5b50 1236-3-125-156-1
+always \u97f3\u6a02 1456-3-236-5
+always \u97ff\u61c9 15-46-4-13456-5
+always \u9806\u61c9 24-123456-5-13456-5
+always \u9818\u5b50 14-13456-4-125-156-1
+always \u982d\u5b50 124-12356-2-125-156-1
+always \u9838\u5b50 13-13456-4-125-156-1
+always \u986f\u8457 15-2345-4-1-34-5
+always \u98db\u6f32 12345-356-3-1-1346-5
+always \u9903\u5b50 13-246-4-125-156-1
+always \u990a\u5206 46-4-12345-136-5
+always \u9918\u8208 1256-2-15-13456-5
+always \u9928\u5b50 13-12456-4-125-156-1
+always \u9928\u9577 13-12456-4-1-1346-4
+always \u9996\u76f8 24-12356-4-15-46-5
+always \u9996\u90fd 24-12356-4-145-34-3
+always \u9996\u9577 24-12356-4-1-1346-4
+always \u99ac\u5b50 134-345-4-125-156-1
+always \u99ac\u864e 134-345-4-1235-34-3
+always \u99ae\u6cb3 1234-13456-2-1235-2346-2
+always \u9a0e\u5175 13-16-5-135-13456-3
+always \u9a19\u5b50 1234-2345-5-125-156-1
+always \u9a3e\u5b50 14-25-2-125-156-1
+always \u9a4d\u9a0e 15-246-3-13-16-5
+always \u9a55\u6a6b 13-246-3-1235-1356-5
+always \u9aa8\u5b50 13-34-4-125-156-1
+always \u9aa8\u76f8 13-34-4-15-46-5
+always \u9ad4\u80d6 124-16-4-1234-1236-2
+always \u9ad8\u66f4 13-146-3-13-1356-3
+always \u9ad8\u8208 13-146-3-15-13456-5
+always \u9ad8\u9e97 13-146-3-14-16-2
+always \u9b06\u6563 15-12346-3-15-1236-4
+always \u9b0d\u5b50 1235-34-2-125-156-1
+always \u9b1a\u5b50 15-1256-3-125-156-1
+always \u9b3c\u5b50 13-1246-4-125-156-1
+always \u9bae\u5c11 15-2345-4-24-146-4
+always \u9bae\u6065 15-2345-4-12-156-4
+always \u9d28\u5b50 23456-3-125-156-1
+always \u9d3b\u722a 1235-12346-2-1-146-4
+always \u9d3f\u5b50 13-2346-3-125-156-1
+always \u9d60\u7684 1235-34-2-145-16-5
+always \u9ea5\u5b50 134-2456-5-125-156-1
+always \u9ebb\u5b50 134-345-2-125-156-1
+always \u9ec3\u51a0 1235-456-2-13-12456-3
+always \u9ede\u5b50 145-2345-4-125-156-1
+always \u9f13\u8b5f 13-34-4-125-146-5
+always \u9f3b\u5b50 135-16-2-125-156-1
+always \u9f9c\u8332 245-234-3-245-156-2
+always \u9f9c\u88c2 13-256-3-14-346-5
diff --git a/Tables/Contraction/zu.ctb b/Tables/Contraction/zu.ctb
new file mode 100644
index 0000000..f03bbd3
--- /dev/null
+++ b/Tables/Contraction/zu.ctb
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Contraction Table - Zulu (contracted)
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include letters-latin.cti
+
+always 'b 23
+always gq 126
+
+# inline contraction of emoji descriptions
+emoji zu
diff --git a/Tables/Input/al/abt_basic.kti b/Tables/Input/al/abt_basic.kti
new file mode 100644
index 0000000..4761b73
--- /dev/null
+++ b/Tables/Input/al/abt_basic.kti
@@ -0,0 +1,97 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the basic ABT/Delphi keys.
+note The two long keys are named Left and Right.
+note The small keys immediately to their left and right are named Up and Down.
+note The three small keys at the left are named Prog, Home, and Cursor.
+
+
+####################
+# Default Bindings #
+####################
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left LNBEG
+bind Home+!Right LNEND
+bind Cursor+!Left HWINLT
+bind Cursor+!Right HWINRT
+bind Prog+!Left CHRLT
+bind Prog+!Right CHRRT
+
+bind !Up LNUP
+bind !Down LNDN
+bind Home TOP_LEFT
+bind Home+!Up TOP
+bind Home+!Down BOT
+bind Cursor+!Up ATTRUP
+bind Cursor+!Down ATTRDN
+bind Home+Cursor+!Up PRDIFLN
+bind Home+Cursor+!Down NXDIFLN
+bind Prog+Home+!Up PRPROMPT
+
+bind Home+Cursor CSRTRK
+bind Cursor RETURN
+
+bind !RoutingKey1 ROUTE
+bind Prog+Home+!RoutingKey1 DESCCHAR
+bind Home+Cursor+!RoutingKey1 SETLEFT
+
+bind Prog+!RoutingKey1 CLIP_NEW
+bind Home+!RoutingKey1 COPY_RECT
+bind Prog+Home+!Down PASTE
+
+bind Prog HELP
+bind Prog+Home DISPMD
+bind Prog+Cursor PREFMENU
+bind Prog+!Up INFO
+bind Prog+!Down FREEZE
+
+bind !Status1A CAPBLINK
+bind !Status1B CSRVIS
+bind !Status1C CSRBLINK
+bind Cursor+!Status1A SIXDOTS
+bind Cursor+!Status1B CSRSIZE
+bind Cursor+!Status1C SLIDEWIN
+
+bind Home+Cursor+!Left MUTE
+bind Home+Cursor+!Right SAY_LINE
+bind Prog+Home+!Left RESTARTSPEECH
+bind Prog+Home+!Right SAY_BELOW
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind !Up MENU_PREV_ITEM
+bind !Down MENU_NEXT_ITEM
+bind Home+!Up MENU_FIRST_ITEM
+bind Home+!Down MENU_LAST_ITEM
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left PREFLOAD
+bind Home+!Right PREFSAVE
+
+bind Prog PREFMENU
+bind Home MENU_PREV_SETTING
+bind Cursor MENU_NEXT_SETTING
diff --git a/Tables/Input/al/abt_extra.kti b/Tables/Input/al/abt_extra.kti
new file mode 100644
index 0000000..bec556d
--- /dev/null
+++ b/Tables/Input/al/abt_extra.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the extra ABT/Delphi keys.
+note The three small keys at the right are named Cursor2, Home2, and Prog2.
+
diff --git a/Tables/Input/al/abt_large.ktb b/Tables/Input/al/abt_large.ktb
new file mode 100644
index 0000000..d83eccd
--- /dev/null
+++ b/Tables/Input/al/abt_large.ktb
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva ABT 380 / Delphi 480
+
+context default
+include abt_basic.kti
+
+context default
+include abt_extra.kti
+
diff --git a/Tables/Input/al/abt_small.ktb b/Tables/Input/al/abt_small.ktb
new file mode 100644
index 0000000..9080d94
--- /dev/null
+++ b/Tables/Input/al/abt_small.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva ABT 320,340 / Delphi 420,440
+
+context default
+include abt_basic.kti
+
diff --git a/Tables/Input/al/bc-etouch.kti b/Tables/Input/al/bc-etouch.kti
new file mode 100644
index 0000000..143f6e7
--- /dev/null
+++ b/Tables/Input/al/bc-etouch.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There are four ETouch keys, two at each end of the text cell area.
+note * Each is subnamed according to the side it's on (Left, Right),
+note * and according to its position on that side (Rear, Front).
+note * The two Rear keys are identified by two vertical bars.
+note * The two Front keys are identified by one horizontal bar.
diff --git a/Tables/Input/al/bc-smartpad.kti b/Tables/Input/al/bc-smartpad.kti
new file mode 100644
index 0000000..05cdaf4
--- /dev/null
+++ b/Tables/Input/al/bc-smartpad.kti
@@ -0,0 +1,85 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note * The outer key on the left, identified by three horizontal bars, is subnamed F1.
+note * The inner key on the left, identified by one horizontal bar, is subnamed F2.
+note * The inner key on the right, identified by one vertical bar, is subnamed F3.
+note * The outer key on the right, identified by three vertical bars, is subnamed F4.
+note * The keys in the middle form a five-way directional pad:
+note + The round key in the middle is subnamed Enter.
+note + The short, thin keys that form a square around it are subnamed Left, Right, Up, and Down.
+
+
+####################
+# Default Bindings #
+####################
+
+bind SmartpadLeft KEY_CURSOR_LEFT
+bind SmartpadRight KEY_CURSOR_RIGHT
+bind SmartpadUp KEY_CURSOR_UP
+bind SmartpadDown KEY_CURSOR_DOWN
+bind SmartpadEnter PASTE
+
+bind SmartpadF2 TIME
+
+bind SmartpadF1+SmartpadLeft SWITCHVT_PREV
+bind SmartpadF1+SmartpadRight SWITCHVT_NEXT
+bind SmartpadF1+SmartpadEnter KEY_INSERT
+bind SmartpadF1+!RoutingKey1 SWITCHVT
+
+bind SmartpadF2+SmartpadUp KEY_PAGE_UP
+bind SmartpadF2+SmartpadDown KEY_PAGE_DOWN
+bind SmartpadF2+SmartpadLeft KEY_HOME
+bind SmartpadF2+SmartpadRight KEY_END
+bind SmartpadF2+SmartpadEnter KEY_DELETE
+bind SmartpadF2+!RoutingKey1 KEY_FUNCTION
+
+bind SmartpadF3+SmartpadF4 MUTE
+
+bind SmartpadF3+SmartpadRight SAY_LINE
+bind SmartpadF3+SmartpadUp SAY_ABOVE
+bind SmartpadF3+SmartpadDown SAY_BELOW
+bind SmartpadF3+SmartpadEnter SPKHOME
+
+bind SmartpadF4+SmartpadLeft SAY_SLOWER
+bind SmartpadF4+SmartpadRight SAY_FASTER
+bind SmartpadF4+SmartpadDown SAY_SOFTER
+bind SmartpadF4+SmartpadUp SAY_LOUDER
+bind SmartpadF4+SmartpadEnter AUTOSPEAK
+
+bind SmartpadF1+SmartpadF2+SmartpadEnter RESTARTBRL
+bind SmartpadF3+SmartpadF4+SmartpadEnter RESTARTSPEECH
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind SmartpadF1 PREFMENU
+bind SmartpadF2 MENU_PREV_LEVEL
+bind SmartpadF4 PREFLOAD
+
+bind SmartpadUp MENU_PREV_ITEM
+bind SmartpadDown MENU_NEXT_ITEM
+bind SmartpadLeft MENU_PREV_SETTING
+bind SmartpadRight MENU_NEXT_SETTING
+bind SmartpadEnter PREFSAVE
+
+
diff --git a/Tables/Input/al/bc-thumb.kti b/Tables/Input/al/bc-thumb.kti
new file mode 100644
index 0000000..b103fac
--- /dev/null
+++ b/Tables/Input/al/bc-thumb.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note * The Home key is identified by a vertical bar in the middle.
+note * The Left key is identified by a vertical bar near its left edge.
+note * The Right key is identified by a vertical bar near its right edge.
+note * The Up key is identified by a horizontal bar along its top edge.
+note * The Down key is identified by a horizontal bar along its bottom edge.
diff --git a/Tables/Input/al/bc.kti b/Tables/Input/al/bc.kti
new file mode 100644
index 0000000..ea1200d
--- /dev/null
+++ b/Tables/Input/al/bc.kti
@@ -0,0 +1,120 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+bind ETouchLeftRear+ETouchRightRear HELP
+bind ETouchLeftRear+ETouchRightFront LEARN
+bind ETouchLeftFront+ETouchRightRear INFO
+bind ETouchLeftFront+ETouchRightFront PREFMENU
+
+bind ThumbUp+!ThumbDown CSRTRK+on
+bind ThumbDown+!ThumbUp CSRTRK+off
+bind ThumbHome RETURN
+bind ThumbLeft+ThumbRight CSRJMP_VERT
+
+bind ThumbUp LNUP
+bind ThumbDown LNDN
+bind ThumbLeft FWINLT
+bind ThumbRight FWINRT
+
+bind ThumbHome+ThumbUp PRDIFLN
+bind ThumbHome+ThumbDown NXDIFLN
+bind ThumbHome+ThumbLeft FWINLTSKIP
+bind ThumbHome+ThumbRight FWINRTSKIP
+
+bind ThumbLeft+ThumbUp TOP_LEFT
+bind ThumbLeft+ThumbDown BOT_LEFT
+bind ThumbRight+ThumbUp ATTRUP
+bind ThumbRight+ThumbDown ATTRDN
+
+bind ETouchLeftRear+ThumbUp PRPROMPT
+bind ETouchLeftRear+ThumbDown NXPROMPT
+bind ETouchLeftFront+ThumbUp PRPGRPH
+bind ETouchLeftFront+ThumbDown NXPGRPH
+bind ETouchRightRear+ThumbUp PRSEARCH
+bind ETouchRightRear+ThumbDown NXSEARCH
+
+bind ETouchLeftRear LNBEG
+bind ETouchRightRear LNEND
+bind ETouchLeftFront CHRLT
+bind ETouchRightFront CHRRT
+
+bind RoutingKey1 ROUTE
+
+bind ETouchLeftRear+ETouchLeftFront+RoutingKey1 DESCCHAR
+bind RoutingKey1+!RoutingKey1 CLIP_COPY
+bind ETouchLeftRear+RoutingKey1 CLIP_NEW
+bind ETouchLeftFront+RoutingKey1 CLIP_ADD
+bind ETouchRightRear+RoutingKey1 COPY_LINE
+bind ETouchRightFront+RoutingKey1 COPY_RECT
+bind ETouchRightRear+ETouchRightFront PASTE
+
+bind ThumbLeft+RoutingKey1 PRINDENT
+bind ThumbRight+RoutingKey1 NXINDENT
+bind ThumbUp+RoutingKey1 PRDIFCHAR
+bind ThumbDown+RoutingKey1 NXDIFCHAR
+bind ThumbHome+RoutingKey1 SETLEFT
+
+bind ETouchLeftRear+ETouchLeftFront+ETouchRightRear FREEZE
+bind ETouchLeftRear+ETouchRightRear+ETouchRightFront DISPMD
+
+bind ThumbLeft+ETouchLeftRear CSRVIS
+bind ThumbLeft+ETouchLeftFront ATTRVIS
+bind ThumbLeft+ETouchRightFront SIXDOTS
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+map Control CONTROL
+map Alt META
+bind Enter KEY_ENTER
+bind Windows KEY_ESCAPE
+
+assign chord Space+
+include ../chords.kti
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind ThumbLeft FWINLT
+bind ThumbRight FWINRT
+bind ThumbUp MENU_PREV_ITEM
+bind ThumbDown MENU_NEXT_ITEM
+bind ETouchLeftRear MENU_FIRST_ITEM
+bind ETouchLeftFront MENU_LAST_ITEM
+bind ETouchRightRear MENU_PREV_SETTING
+bind ETouchRightFront MENU_NEXT_SETTING
+
+bind ThumbHome PREFMENU
+bind ETouchLeftRear+ETouchLeftFront PREFLOAD
+bind ETouchRightRear+ETouchRightFront PREFSAVE
+
+
diff --git a/Tables/Input/al/bc640.ktb b/Tables/Input/al/bc640.ktb
new file mode 100644
index 0000000..5022012
--- /dev/null
+++ b/Tables/Input/al/bc640.ktb
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva BC640, BC624
+
+include bc-etouch.kti
+
+note There's a Smartpad in front of the text cell area.
+include bc-smartpad.kti
+
+note The five Thumb keys on the front, from left to right, are subnamed:
+note * Left, Up, Home, Down, Right.
+include bc-thumb.kti
+
+include bc.kti
diff --git a/Tables/Input/al/bc680.ktb b/Tables/Input/al/bc680.ktb
new file mode 100644
index 0000000..9bb9d37
--- /dev/null
+++ b/Tables/Input/al/bc680.ktb
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva BC680
+
+include bc-etouch.kti
+
+note There are two Smartpads in front of the text cell area.
+include bc-smartpad.kti
+
+note There's a group of five Thumb keys at each end of the front.
+note * The outer key of each group is subnamed Home.
+note * The four inner keys of each group, from left to right, are subnamed:
+note * Left, Up, Down, Right.
+include bc-thumb.kti
+
+include bc.kti
diff --git a/Tables/Input/al/el.ktb b/Tables/Input/al/el.ktb
new file mode 100644
index 0000000..40554e7
--- /dev/null
+++ b/Tables/Input/al/el.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Optelec EasyLink 12 Touch
+
+include ../bp/all.kti
diff --git a/Tables/Input/al/sat_common.kti b/Tables/Input/al/sat_common.kti
new file mode 100644
index 0000000..1640d22
--- /dev/null
+++ b/Tables/Input/al/sat_common.kti
@@ -0,0 +1,98 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva models which have the basic Satellite keys.
+note The two long, flat keys are named Up and Down.
+note The two long, bent keys are named Left and Right.
+note The two round keys are named Home and Cursor.
+note The row of keys immediately behind the braille cells is named RoutingKey1,
+note * and the row of keys further back is named RoutingKey2.
+note The keypad at the left side of the top is named SpeechPad,
+note * and the one at the right side of the top is named NavPad.
+note * They can be swapped via the internal menu.
+note * The four inner keys of each are subnamed Left, Right, Up, and Down.
+note * The two outer keys of each are subnamed F1 and F2.
+
+
+####################
+# Default Bindings #
+####################
+
+note Enter the internal menu by pressing:
+note * SpeechPadF1 + SpeechPadF2 + NavPadF1 + NavPadF2
+bind SpeechPadF1+SpeechPadF2+NavPadF1+NavPadF2 NOOP
+
+include sat_speech.kti
+include sat_nav.kti
+
+bind !Up LNUP
+bind !Down LNDN
+bind Home+!Up TOP_LEFT
+bind Home+!Down BOT_LEFT
+bind Cursor+!Up TOP
+bind Cursor+!Down BOT
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left LNBEG
+bind Home+!Right LNEND
+bind Cursor+!Left FWINLTSKIP
+bind Cursor+!Right FWINRTSKIP
+
+bind RoutingKey1 ROUTE
+bind RoutingKey2 DESCCHAR
+
+bind RoutingKey1+!RoutingKey1 CLIP_COPY
+bind RoutingKey2+!RoutingKey2 CLIP_APPEND
+
+bind Home+!RoutingKey2 SETMARK
+bind Home+!RoutingKey1 GOTOMARK
+bind Cursor+!RoutingKey2 PRINDENT
+bind Cursor+!RoutingKey1 NXINDENT
+
+bind !Status1A CSRVIS
+bind !Status2A SKPIDLNS
+bind !Status1B ATTRVIS
+bind !Status2B DISPMD
+bind !Status1C CAPBLINK
+bind !Status2C SKPBLNKWINS
+
+bind Home BACK
+bind Cursor HOME
+bind Home+Cursor CSRTRK
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind !Up MENU_PREV_ITEM
+bind !Down MENU_NEXT_ITEM
+bind Home+!Up MENU_FIRST_ITEM
+bind Home+!Down MENU_LAST_ITEM
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind Home+!Left PREFLOAD
+bind Home+!Right PREFSAVE
+
+bind Home MENU_PREV_SETTING
+bind Cursor MENU_NEXT_SETTING
+bind Home+Cursor PREFMENU
diff --git a/Tables/Input/al/sat_large.ktb b/Tables/Input/al/sat_large.ktb
new file mode 100644
index 0000000..cbe770c
--- /dev/null
+++ b/Tables/Input/al/sat_large.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva Satellite 570,584
+
+include sat_tumblers.kti
+include sat_common.kti
diff --git a/Tables/Input/al/sat_nav.kti b/Tables/Input/al/sat_nav.kti
new file mode 100644
index 0000000..1d0279d
--- /dev/null
+++ b/Tables/Input/al/sat_nav.kti
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for the Alva Satellite nav keypad.
+
+bind NavPadF1+!Up PRDIFLN
+bind NavPadF1+!Down NXDIFLN
+bind NavPadF2+!Up ATTRUP
+bind NavPadF2+!Down ATTRDN
+
+bind NavPadF1+!Left CHRLT
+bind NavPadF1+!Right CHRRT
+bind NavPadF2+!Left HWINLT
+bind NavPadF2+!Right HWINRT
+
+bind NavPadF1+!RoutingKey1 CLIP_NEW
+bind NavPadF1+!RoutingKey2 CLIP_ADD
+bind NavPadF2+!RoutingKey1 COPY_RECT
+bind NavPadF2+!RoutingKey2 COPY_LINE
+
+bind NavPadLeft PREFMENU
+bind NavPadRight INFO
+bind NavPadF1+NavPadLeft FREEZE
+bind NavPadF1+NavPadRight SIXDOTS
+bind NavPadF2+NavPadLeft PASTE
+bind NavPadF2+NavPadRight CSRJMP_VERT
+
+bind NavPadUp PRPROMPT
+bind NavPadDown NXPROMPT
+bind NavPadF1+NavPadUp PRPGRPH
+bind NavPadF1+NavPadDown NXPGRPH
+bind NavPadF2+NavPadUp PRSEARCH
+bind NavPadF2+NavPadDown NXSEARCH
+
+bind NavPadF1 HELP
+bind NavPadF2 LEARN
+bind NavPadF1+NavPadF2 RESTARTBRL
+
diff --git a/Tables/Input/al/sat_small.ktb b/Tables/Input/al/sat_small.ktb
new file mode 100644
index 0000000..4a7194a
--- /dev/null
+++ b/Tables/Input/al/sat_small.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Alva Satellite 544
+
+include sat_common.kti
diff --git a/Tables/Input/al/sat_speech.kti b/Tables/Input/al/sat_speech.kti
new file mode 100644
index 0000000..2248e83
--- /dev/null
+++ b/Tables/Input/al/sat_speech.kti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for the Alva Satellite speech keypad.
+
+bind SpeechPadLeft MUTE
+bind SpeechPadRight SAY_LINE
+bind SpeechPadUp SAY_ABOVE
+bind SpeechPadDown SAY_BELOW
+
+bind SpeechPadF2+SpeechPadLeft SAY_SLOWER
+bind SpeechPadF2+SpeechPadRight SAY_FASTER
+bind SpeechPadF2+SpeechPadDown SAY_SOFTER
+bind SpeechPadF2+SpeechPadUp SAY_LOUDER
+
+bind SpeechPadF1 SPKHOME
+bind SpeechPadF2 AUTOSPEAK
+bind SpeechPadF1+SpeechPadF2 RESTARTSPEECH
+
diff --git a/Tables/Input/al/sat_tumblers.kti b/Tables/Input/al/sat_tumblers.kti
new file mode 100644
index 0000000..9f021fb
--- /dev/null
+++ b/Tables/Input/al/sat_tumblers.kti
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Alva Satellite models which have tumbler keys.
+note The two three-position, sliding keys are named LeftTumbler and RightTumbler.
+
+bind LeftTumblerLeft CHRLT
+bind LeftTumblerRight CHRRT
+bind RightTumblerLeft LNBEG     
+bind RightTumblerRight LNEND
+
diff --git a/Tables/Input/al/voyager.ktb b/Tables/Input/al/voyager.ktb
new file mode 100644
index 0000000..e74090d
--- /dev/null
+++ b/Tables/Input/al/voyager.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Voyager Protocol Converter
+
+include ../vo/all.kti
diff --git a/Tables/Input/android-chords.kti b/Tables/Input/android-chords.kti
new file mode 100644
index 0000000..f9f4f91
--- /dev/null
+++ b/Tables/Input/android-chords.kti
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign prefix \{chord}Dot7+Dot8
+bind \{prefix}+Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 GUI_BRL_ACTIONS
+
+bind \{prefix}+\{a} TXTSEL_ALL
+bind \{prefix}+Dot4 TXTSEL_CLEAR
+bind \{prefix}+\{c} HOST_COPY
+bind \{prefix}+\{v} HOST_PASTE
+bind \{prefix}+\{x} HOST_CUT
+
+bind \{prefix}+\{b} GUI_BACK
+bind \{prefix}+\{h} GUI_HOME
+bind \{prefix}+\{i} INDICATORS
+bind \{prefix}+\{m} GUI_APP_MENU
+bind \{prefix}+\{n} GUI_APP_ALERTS
+bind \{prefix}+\{o} GUI_DEV_OPTIONS
+bind \{prefix}+\{r} GUI_APP_LIST
+bind \{prefix}+\{s} GUI_DEV_SETTINGS
+bind \{prefix}+\{t} GUI_TITLE
+
+bind \{prefix}+Dot3+Dot6 GUI_AREA_ACTV
+bind \{prefix}+Dot3 GUI_AREA_PREV
+bind \{prefix}+Dot6 GUI_AREA_NEXT
+
+bind \{prefix}+Dot2 GUI_ITEM_PREV
+bind \{prefix}+Dot5 GUI_ITEM_NEXT
+
+bind \{prefix}+Dot2+Dot3 GUI_ITEM_FRST
+bind \{prefix}+Dot5+Dot6 GUI_ITEM_LAST
+
+bind \{prefix}+Dot2+Dot5 CONTEXT+chrome
+context chrome Web Page Navigation
+bind Dot7+Dot8 CONTEXT+default
+
+superimpose meta
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
diff --git a/Tables/Input/at/all.ktb b/Tables/Input/at/all.ktb
new file mode 100644
index 0000000..719ffae
--- /dev/null
+++ b/Tables/Input/at/all.ktb
@@ -0,0 +1,95 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Albatross
+
+
+####################
+# Default Bindings #
+####################
+
+bind Home1 TOP_LEFT
+bind Home2 TOP_LEFT
+
+bind End1 BOT_LEFT
+bind End2 BOT_LEFT
+
+bind ExtraCursor1 BACK
+bind ExtraCursor2 BACK
+
+bind Cursor1 HOME
+bind Cursor2 HOME
+
+bind !Up1 LNUP
+bind !Up2 LNUP
+bind !Up3 LNUP
+
+bind !Down1 LNDN
+bind !Down2 LNDN
+bind !Down3 LNDN
+
+bind !Left FWINLT
+bind !Right FWINRT
+
+bind F1 ATTRDN
+bind F2 ATTRUP
+bind !F3 PASTE
+bind !F4 CSRTRK
+bind !F5 HELP
+bind !F6 LEARN
+bind F7 PRPROMPT
+bind F8 NXPROMPT
+
+bind F9 NXDIFLN
+bind F10 PRDIFLN
+bind !F11 CSRJMP_VERT
+bind !F12 SIXDOTS
+bind !F13 PREFMENU
+bind !F14 INFO
+bind F15 PRPGRPH
+bind F16 NXPGRPH
+
+bind Attribute1 FREEZE
+bind Attribute2 DISPMD
+bind Attribute3 CSRVIS
+bind Attribute4 ATTRVIS
+
+bind !LeftWheelLeft CHRLT
+bind !RightWheelLeft CHRLT
+
+bind !LeftWheelRight CHRRT
+bind !RightWheelRight CHRRT
+
+bind !LeftWheelUp LNUP
+bind !RightWheelUp LNUP
+
+bind !LeftWheelDown LNDN
+bind !RightWheelDown LNDN
+
+bind !RoutingKey1 ROUTE
+bind Attribute2+!RoutingKey1 CLIP_NEW
+bind Attribute1+!RoutingKey1 CLIP_ADD
+bind Attribute4+!RoutingKey1 COPY_LINE
+bind Attribute3+!RoutingKey1 COPY_RECT
+
+bind !RoutingKey2 DESCCHAR
+bind Attribute2+!RoutingKey2 PRINDENT
+bind Attribute1+!RoutingKey2 NXINDENT
+bind Attribute4+!RoutingKey2 PRDIFCHAR
+bind Attribute3+!RoutingKey2 NXDIFCHAR
+
diff --git a/Tables/Input/ba/all.txt b/Tables/Input/ba/all.txt
new file mode 100644
index 0000000..05345a4
--- /dev/null
+++ b/Tables/Input/ba/all.txt
@@ -0,0 +1,3 @@
+Help: BrlAPI (client)
+
+No help available for this driver.
diff --git a/Tables/Input/bd/all.txt b/Tables/Input/bd/all.txt
new file mode 100644
index 0000000..00c4811
--- /dev/null
+++ b/Tables/Input/bd/all.txt
@@ -0,0 +1,14 @@
+Help: Braudi
+
+1: go to top-left corner
+2: go left one window
+3: go down one line
+4: go up one line
+5: go right one window
+6: go to bottom-left corner
+23: go to beginning of line
+56: go to end of line
+14: toggle cursor visibility
+25: toggle attributes display
+26: toggle status display
+36: go to cursor
diff --git a/Tables/Input/bg/all.ktb b/Tables/Input/bg/all.ktb
new file mode 100644
index 0000000..5ba2ff5
--- /dev/null
+++ b/Tables/Input/bg/all.ktb
@@ -0,0 +1,118 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title B2G
+
+note The eight, rectangular, concave keys (in two groups of four) near the back are a braille keyboard.
+note * The four on the left, from left to right, are: Dot7, Dot3, Dot2, Dot1.
+note * The four on the right, from left to right, are: Dot4, Dot5, Dot6, Dot8.
+note The square pad with a small, round button in the middle, in between Dot1 and Dot4, is the D-Pad (directional pad).
+note * Its four edges are the Up, Down, Left, and Right keys.
+note * The button in the middle is the Center key.
+note The long, rectangular key in front of Dot1, the D-Pad, and Dot4 is the Space bar.
+note The small, round buttons just behind each cell are the routing keys.
+note The convex, square button to the left of the routing keys is the Backward key.
+note The convex, square button to the right of the routing keys is the Forward key.
+note The two small, round buttons on the right side near the front are the volume keys.
+note * From front to back, they are: Softer, Louder.
+
+
+####################
+# Default Bindings #
+####################
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+
+map Up UPPER
+map Down CONTROL
+map Left META
+map Right ALTGR
+map Center GUI
+
+assign chord Space+
+include ../chords.kti
+
+bind Backward FWINLT
+bind Forward FWINRT
+
+bind Space+Backward FWINLTSKIP
+bind Space+Forward FWINRTSKIP
+
+bind Center HOME
+bind Space+Center BACK
+
+bind Up LNUP
+bind Down LNDN
+bind Left LNBEG
+bind Right LNEND
+
+bind Space+Up TOP
+bind Space+Down BOT
+bind Space+Left CHRLT
+bind Space+Right CHRRT
+
+bind Backward+Center PRSEARCH
+bind Forward+Center NXSEARCH
+
+bind Backward+Up PRDIFLN
+bind Backward+Down NXDIFLN
+bind Backward+Left ATTRUP
+bind Backward+Right ATTRDN
+
+bind Forward+Up PRPROMPT
+bind Forward+Down NXPROMPT
+bind Forward+Left PRPGRPH
+bind Forward+Right NXPGRPH
+
+bind RoutingKey ROUTE
+bind RoutingKey+RoutingKey CLIP_COPY
+bind RoutingKey+RoutingKey+Space CLIP_APPEND
+
+bind Dot7+RoutingKey CLIP_NEW
+bind Dot3+RoutingKey CLIP_ADD
+bind Dot6+RoutingKey COPY_LINE
+bind Dot8+RoutingKey COPY_RECT
+
+bind Dot2+RoutingKey SWITCHVT
+bind Dot5+RoutingKey SELECTVT
+
+bind Dot1+RoutingKey SETMARK
+bind Dot4+RoutingKey GOTOMARK
+
+bind Space+RoutingKey KEY_FUNCTION
+
+bind Backward+RoutingKey SETLEFT
+bind Forward+RoutingKey DESCCHAR
+
+bind Center+RoutingKey PASTE_HISTORY
+bind Up+RoutingKey PRINDENT
+bind Down+RoutingKey NXINDENT
+bind Left+RoutingKey PRDIFCHAR
+bind Right+RoutingKey NXDIFCHAR
+
+bind Louder+Softer BRL_STOP
+
+
diff --git a/Tables/Input/bl/18.txt b/Tables/Input/bl/18.txt
new file mode 100644
index 0000000..79c0cea
--- /dev/null
+++ b/Tables/Input/bl/18.txt
@@ -0,0 +1,92 @@
+Help: Braille Lite 18
+
+Advance bar left/right: full window left/right
+1: line up / 4: line down
+3: character left / 6: character right
+235: line start / 256: line end
+123: top left / 456: bottom left
+1236: next virtual terminal / 3456: previous VT
+234: search forward / 156: search backward (use cut to set search string)
+12356: next prompt / 23456: previous prompt (same prompt as current line)
+1345: next paragraph / 1246: previous paragraph (line after blank line)
+
+2: keyboard left arrow / 5: keyboard right arrow
+23: keyboard up arrow / 56: keyboard down arrow
+12 chord: keyboard backspace
+145 chord: keyboard delete
+2345 chord: keyboard tab
+46 chord: keyboard return
+246 chord: keyboard escape
+
+125: home (goto cursor)
+1256: goto cursor previous position
+14: cursor tracking (toggle)
+124 chord: freeze screen (toggle)
+1456: help mode (toggle)
+12456: learn mode (key describer) (toggle)
+34: status mode (toggle)
+24: attribute display (toggle)
+2346: six dots mode (toggle)
+
+245 chord: route cursor to beginning of line
+45 chord: route cursor to current line
+236 chord: cut start
+126 chord: cut append
+356 chord: cut end rectangular
+16 chord: cut end linear
+1234 chord: paste
+
+36: speak line
+134: mute speech
+
+1235 chord: restart braille driver
+
+25 chord: preferences options:
+  134: menu
+  1235: reset
+  234: save
+  1356: cancel
+
+Chord required for *dangerous* commands, e.g. routing, cut/paste,
+restart driver, etc.
+
+Internal:
+
+1245 chord: position internal cursor for column-specific function:
+  To move internal cursor:
+    125: centre of window
+    235: left end of window
+    256: right end of window
+    3: character left
+    6: character right
+    Advance bar left/right: quarter window left/right
+  To terminate:
+    236 chord: cut start
+    126 chord: cut append
+    356 chord: cut end rectangular
+    16 chord: cut end linear
+    24: describe attributes of selected character
+    1356: cancel
+
+k chord toggles keyboard emulation mode - initially off.
+When on, unchorded characters passed directly to inskey(), unless prefixed
+by:
+
+u chord: uppercase (twice to lock)
+q chord: unlock
+26 chord: add 8th dot to next char typed
+35 chord: prepend ESCAPE to next character (ALT)
+x chord: control character
+z chord: cancel preceding meta/control
+
+Other internals:
+
+2356 chord: rotate BrailleLite by 180 degrees
+o chord: set repeat count for following command (up to 2digits).
+  Type numbers, then type movement command or character (if in
+    keyboard emulation).
+  z chord: cancels special context.
+  e chord: confirms count and waits for key to repeat.
+Goto virtual terminal: use o chord, followed by VT number,
+  followed by v chord or # chord.
+Set/goto mark: use o chord followed by # followed by s to set or m to go.
diff --git a/Tables/Input/bl/40_m20_m40.txt b/Tables/Input/bl/40_m20_m40.txt
new file mode 100644
index 0000000..616866e
--- /dev/null
+++ b/Tables/Input/bl/40_m20_m40.txt
@@ -0,0 +1,122 @@
+Help: Braille Lite 40, M20, and M40
+
+left bar left: full window left
+left bar right: line up
+right bar left: line down
+right bar right: full window right
+
+1: line up / 4: line down
+3: character left / 6: character right
+235: line start / 256: line end
+123: top left / 456: bottom left
+1236: next virtual terminal / 3456: previous VT
+234: search forward / 156: search backward (use cut to set search string)
+12356: next prompt / 23456: previous prompt (same prompt as current line)
+1345: next paragraph / 1246: previous paragraph (line after blank line)
+
+2: keyboard left arrow / 5: keyboard right arrow
+23: keyboard up arrow / 56: keyboard down arrow
+12 chord: keyboard backspace
+145 chord: keyboard delete
+2345 chord: keyboard tab
+46 chord: keyboard return
+246 chord: keyboard escape
+
+125: home (goto cursor)
+1256: goto cursor previous position
+14: cursor tracking (toggle)
+124 chord: freeze screen (toggle)
+1456: help mode (toggle)
+12456: learn mode (key describer) (toggle)
+34: status mode (toggle)
+24: attribute display (toggle)
+2346: six dots mode (toggle)
+
+245 chord: route cursor to beginning of line
+45 chord: route cursor to current line
+236 chord: cut start
+126 chord: cut append
+356 chord: cut end rectangular
+16 chord: cut end linear
+1234 chord: paste
+
+36: speak line
+134: mute speech
+
+1235 chord: restart braille driver
+
+25 chord: preferences options:
+  134: menu
+  1235: reset
+  234: save
+  1356: cancel
+
+Chord required for *dangerous* commands, e.g. routing, cut/paste,
+restart driver, etc.
+
+Advance Bar Combinations:
+Left   Right  Description
+center ------ half window left
+------ center half window right
+left   left   up to line with different content
+left   right  down to line with different content
+right  left   up to line with different highlighting
+right  right  down to line with different highlighting
+center left   up to top line
+center right  down to bottom line
+
+Whiz Wheels (Millennium models):
+Wheel Motion Description
+left  up     one line up
+left  down   one line down
+right up     full window left
+right down   full window right
+left  press  attribute underlining (toggle)
+right press  show cursor (toggle)
+
+Internal:
+
+1245 chord: position internal cursor for column-specific function:
+  To move internal cursor:
+    125: centre of window
+    235: left end of window
+    256: right end of window
+    3: character left
+    6: character right
+    left bar left: quarter window left
+    right bar right: quarter window right
+    routing key: go to character
+  To terminate:
+    236 chord: cut start
+    126 chord: cut append
+    356 chord: cut end rectangular
+    16 chord: cut end linear
+    24: describe attributes of selected character
+    1356: cancel
+
+k chord toggles keyboard emulation mode - initially off.
+When on, unchorded characters passed directly to inskey(), unless prefixed
+by:
+
+u chord: uppercase (twice to lock)
+q chord: unlock
+26 chord: add 8th dot to next char typed
+35 chord: prepend ESCAPE to next character (ALT)
+x chord: control character
+z chord: cancel preceding meta/control
+
+Combinations with:
+dot 7: shift
+dot 78: control
+
+Other internals:
+
+2356 chord: rotate BrailleLite by 180 degrees
+o chord: set repeat count for following command (up to 2digits).
+  Type numbers, then type movement command or character (if in
+    keyboard emulation).
+  z chord: cancels special context.
+  e chord: confirms count and waits for key to repeat.
+Goto virtual terminal: use o chord, followed by VT number,
+  followed by v chord or # chord.
+Set/goto mark: use o chord followed by # followed by s to set or m to go.
diff --git a/Tables/Input/bm/NLS_Zoomax.ktb b/Tables/Input/bm/NLS_Zoomax.ktb
new file mode 100644
index 0000000..c4f12f6
--- /dev/null
+++ b/Tables/Input/bm/NLS_Zoomax.ktb
@@ -0,0 +1,107 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NLS eReader Zoomax
+
+note An eight-dot standard braille keyboard is along the back edge of the top.
+note * From left to right, its keys are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+note * The Space key is in between Dot1 and Dot4.
+
+note The two round keys at each end of the front are system keys. From left to right:
+note * the two on the left are S1 and S2, and the two on the right are S3 and S4.
+
+assign space Space
+include navpad.kti
+note There's a rectangular key on either side of the navigation pad.
+note * The one to the left is named BL, and the one to the right is named BR.
+
+bind BL KEY_BACKSPACE
+bind BL+RoutingKey SETLEFT
+
+map S1 GUI
+map S2 CONTROL
+map S3 META
+map S4 ALTGR
+
+bind S1 HELP
+bind S2 TIME
+bind S3 INFO
+bind S4 LEARN
+
+bind BL+Select PREFMENU
+bind BL+Up PREFLOAD
+bind BL+Down PREFSAVE
+
+bind BL+Left PRNBWIN
+bind BL+Right NXNBWIN
+
+bind Select+Left FWINLTSKIP
+bind Select+Right FWINRTSKIP
+
+bind Left+Up TOP_LEFT
+bind Left+Down BOT_LEFT
+
+bind Right+Up TOP
+bind Right+Down BOT
+
+bind S1+Up ATTRUP
+bind S1+Down ATTRDN
+
+bind S2+Up PRDIFLN
+bind S2+Down NXDIFLN
+
+bind S3+Up PRPROMPT
+bind S3+Down NXPROMPT
+
+bind S4+Up PRPGRPH
+bind S4+Down NXPGRPH
+
+bind S1+Left LNBEG
+bind S1+Right LNEND
+
+bind S2+Left CHRLT
+bind S2+Right CHRRT
+
+bind S3+Left HWINLT
+bind S3+Right HWINRT
+
+bind S4+Left PRSEARCH
+bind S4+Right NXSEARCH
+
+bind S1+RoutingKey PRDIFCHAR
+bind S2+RoutingKey NXDIFCHAR
+
+bind S3+RoutingKey PRINDENT
+bind S4+RoutingKey NXINDENT
+
+bind Select+S1 ATTRVIS
+bind Select+S2 CSRVIS
+bind Select+S3 CSRTRK
+bind Select+S4 SIXDOTS
+
+bind BL+S3 DISPMD
+bind BL+S4 FREEZE
+
+bind Left+Right PASTE
+bind S2+S3 CSRJMP_VERT
+
+include display6.kti
+include routing6.kti
+
+assign speech BL+
+include ../speech.kti
diff --git a/Tables/Input/bm/b2g.ktb b/Tables/Input/bm/b2g.ktb
new file mode 100644
index 0000000..a8b876c
--- /dev/null
+++ b/Tables/Input/bm/b2g.ktb
@@ -0,0 +1,47 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NBP B2G
+
+note Press Space while holding Backward to switch to navigation mode.
+note In navigation mode: Backward is F1, Forward is F4, Dots1-6 are display keys 1-6, Dot7 is F2, Dot8 is F3.
+note Press Space while holding Forward to switch to keyboard mode.
+note In keyboard mode: Backward is B9, Forward is B10, Space is B11.
+
+include d6.kti
+include routing6.kti
+
+bind F1 FWINLT
+bind F4 FWINRT
+
+bind B9 FWINLT
+bind B10 FWINRT
+
+assign space B11
+assign press Select
+include navpad.kti
+
+bind B9+RoutingKey SETLEFT
+bind B10+RoutingKey SWITCHVT
+
+bind B11+Up TOP
+bind B11+Down BOT
+bind B11+Left FWINLTSKIP
+bind B11+Right FWINRTSKIP
+bind B11+Select PASTE
+
diff --git a/Tables/Input/bm/b9b10.kti b/Tables/Input/bm/b9b10.kti
new file mode 100644
index 0000000..68102d1
--- /dev/null
+++ b/Tables/Input/bm/b9b10.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note B9 and B10 are the keys immediately to the left and right of the joystick.
+
+map B9 SPACE
+assign space B10
+include joystick.kti
+bind B9+RoutingKey SETLEFT
diff --git a/Tables/Input/bm/b9b11b10.kti b/Tables/Input/bm/b9b11b10.kti
new file mode 100644
index 0000000..f59f7ea
--- /dev/null
+++ b/Tables/Input/bm/b9b11b10.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note B9 and B10 are the keys immediately to the left and right of the joystick.
+note * If either or both are used in a combination that includes at least
+note * one of the Dot1-6 keys while the device is in 8-dot braille mode
+note * then they become the Dot7 and Dot8 keys.
+note B11 is the key between the Dot1 and Dot4 keys.
+
+bind B9 KEY_BACKSPACE
+bind B10 KEY_ENTER
+
+assign space B11
+include joystick.kti
+bind B9+RoutingKey SETLEFT
+bind B10+RoutingKey SWITCHVT
+
+bind B11+Up TOP
+bind B11+Down BOT
+bind B11+Left FWINLTSKIP
+bind B11+Right FWINRTSKIP
+bind B11+Press PASTE
+
diff --git a/Tables/Input/bm/command.kti b/Tables/Input/bm/command.kti
new file mode 100644
index 0000000..42bdd0b
--- /dev/null
+++ b/Tables/Input/bm/command.kti
@@ -0,0 +1,36 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have command keys.
+note The command keys are in the middle, just behind the cursor routing keys,
+note and, from left to right, are named Command1 through Command7.
+
+map Command4 SPACE
+map Command3 DOT1
+map Command2 DOT2
+map Command1 DOT3
+map Command5 DOT4
+map Command6 DOT5
+map Command7 DOT6
+
+bind Command4+Command5 KEY_ENTER
+bind Command4+Command2 KEY_BACKSPACE
+bind Command4+Command6 KEY_TAB
+bind Command4+Command1 KEY_CURSOR_LEFT
+bind Command4+Command7 KEY_CURSOR_RIGHT
+
diff --git a/Tables/Input/bm/connect.ktb b/Tables/Input/bm/connect.ktb
new file mode 100644
index 0000000..d4e5c5b
--- /dev/null
+++ b/Tables/Input/bm/connect.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum VarioConnect / HWG BrailleConnect
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables/Input/bm/conny.ktb b/Tables/Input/bm/conny.ktb
new file mode 100644
index 0000000..5fc2f03
--- /dev/null
+++ b/Tables/Input/bm/conny.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Conny
+
+include display6.kti
+include routing6.kti
+include b9b11b10.kti
diff --git a/Tables/Input/bm/d6.kti b/Tables/Input/bm/d6.kti
new file mode 100644
index 0000000..83da489
--- /dev/null
+++ b/Tables/Input/bm/d6.kti
@@ -0,0 +1,86 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Display2 FWINLT
+bind Display5 FWINRT
+
+bind Display1+Display3 CHRLT
+bind Display4+Display6 CHRRT
+
+bind Display1+Display2+Display3 LNBEG
+bind Display4+Display5+Display6 LNEND
+
+bind Display4 LNUP
+bind Display6 LNDN
+
+bind Display1+Display4 TOP
+bind Display3+Display6 BOT
+
+bind Display2+Display4 TOP_LEFT
+bind Display2+Display6 BOT_LEFT
+
+bind Display5+Display4 PRDIFLN
+bind Display5+Display6 NXDIFLN
+
+bind Display2+Display1 ATTRUP
+bind Display2+Display3 ATTRDN
+
+bind Display2+Display5+Display1+Display4 PRPROMPT
+bind Display2+Display5+Display3+Display6 NXPROMPT
+
+bind Display2+Display5+Display4 PRPGRPH
+bind Display2+Display5+Display6 NXPGRPH
+
+bind Display2+Display4+Display6+Display1 PRSEARCH
+bind Display2+Display4+Display6+Display3 NXSEARCH
+
+bind Display1 CSRTRK+on
+bind Display3 CSRTRK+off
+
+bind Display1+Display6 BACK
+
+bind Display2+Display4+Display6 HOME
+bind Display1+Display3+Display5 SPKHOME
+
+bind Display1+Display3+Display4+Display6 CSRJMP_VERT
+bind Display2+Display5 INFO
+
+bind Display1+Display4+Display5 DISPMD
+bind Display1+Display5 TIME
+bind Display1+Display2+Display4 FREEZE
+bind Display1+Display2+Display5 HELP
+bind Display1+Display3+Display4 PREFMENU
+bind Display1+Display2+Display3+Display4 PASTE
+bind Display1+Display2+Display3+Display5 PREFLOAD
+bind Display2+Display3+Display4 RESTARTSPEECH
+bind Display2+Display3+Display4+Display5 ATTRVIS
+bind Display2+Display4+Display5+Display6 PREFSAVE
+
+bind Display2+Display3+Display5 SIXDOTS+on
+bind Display2+Display3+Display6 SIXDOTS+off
+
+bind Display1+Display4+Display5+Display6 LEARN
+bind Display1+Display2+Display3+Display6 SWITCHVT_NEXT
+bind Display3+Display4+Display5+Display6 SWITCHVT_PREV
+
+bind Display3+Display4 MUTE
+bind Display3+Display5 SAY_LINE
+bind Display3+Display5+Display4 SAY_ABOVE
+bind Display3+Display5+Display6 SAY_BELOW
+bind Display3+Display4+Display6 AUTOSPEAK
+
diff --git a/Tables/Input/bm/default.ktb b/Tables/Input/bm/default.ktb
new file mode 100644
index 0000000..71bdce5
--- /dev/null
+++ b/Tables/Input/bm/default.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum (default)
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables/Input/bm/display6.kti b/Tables/Input/bm/display6.kti
new file mode 100644
index 0000000..08cee8c
--- /dev/null
+++ b/Tables/Input/bm/display6.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There are three display keys at each end of the braille cells.
+note * From top to bottom:
+note + The three at the left are named Display1, Display2, and Display3.
+note + The three at the right are named Display4, Display5, and Display6.
+
+include d6.kti
diff --git a/Tables/Input/bm/display7.kti b/Tables/Input/bm/display7.kti
new file mode 100644
index 0000000..f1b6ecb
--- /dev/null
+++ b/Tables/Input/bm/display7.kti
@@ -0,0 +1,66 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 7 display keys.
+note The display keys are at both ends of the text cell area, just above it.
+note * The two at the left are named Display1 and Display2.
+note * The large key at the right is named Display5.
+note * The two immediately to its left are named Display3 and Display4.
+note * The two immediately to its right are named Display6 and Display7.
+
+bind Display5 RETURN
+bind Display3 LNUP
+bind Display4 LNDN
+bind Display6 FWINLT
+bind Display7 FWINRT
+
+bind Display1 TOP_LEFT
+bind Display2 BOT_LEFT
+
+bind Display5+Display3 PRDIFLN
+bind Display5+Display4 NXDIFLN
+bind Display5+Display6 ATTRUP
+bind Display5+Display7 ATTRDN
+
+bind Display2+Display5 FREEZE
+bind Display2+Display3 PRPROMPT
+bind Display2+Display4 NXPROMPT
+bind Display2+Display6 PRPGRPH
+bind Display2+Display7 NXPGRPH
+
+bind Display1+Display5 CSRTRK
+bind Display1+Display3 DISPMD
+bind Display1+Display4 SIXDOTS
+bind Display1+Display6 ATTRVIS
+bind Display1+Display7 CSRVIS
+
+bind Display1+Display2+Display5 AUTOSPEAK
+bind Display1+Display2+Display3 MUTE
+bind Display1+Display2+Display4 SAY_LINE
+bind Display1+Display2+Display6 SAY_ABOVE
+bind Display1+Display2+Display7 SAY_BELOW
+
+bind Display1+Display2 HELP
+bind Display3+Display4 LEARN
+bind Display6+Display7 PREFMENU
+bind Display3+Display6 PREFLOAD
+bind Display4+Display7 PREFSAVE
+
+bind Display5+Display3+Display6 CSRJMP_VERT
+bind Display5+Display4+Display7 PASTE
+
diff --git a/Tables/Input/bm/dm80p.ktb b/Tables/Input/bm/dm80p.ktb
new file mode 100644
index 0000000..b5d941a
--- /dev/null
+++ b/Tables/Input/bm/dm80p.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum DM 80 Plus
+
+include display7.kti
+include routing7.kti
diff --git a/Tables/Input/bm/emulate6.kti b/Tables/Input/bm/emulate6.kti
new file mode 100644
index 0000000..2b305d7
--- /dev/null
+++ b/Tables/Input/bm/emulate6.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Any display key combination can be emulated by holding \{press}
+note * while typing the corresponding combination of dots 1-6.
+include d6.kti
diff --git a/Tables/Input/bm/front10.kti b/Tables/Input/bm/front10.kti
new file mode 100644
index 0000000..3ed04bf
--- /dev/null
+++ b/Tables/Input/bm/front10.kti
@@ -0,0 +1,63 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 10 front keys.
+note The keys in the upper row on the front are named:
+note * Front1, Front3, Front5, Front7, Front9.
+note The keys in the lower row on the front are named:
+note * Front2, Front4, Front6, Front8, Front10.
+note The keys in the upper row on the back are named:
+note * Back1, Back3, Back5, Back7, Back9.
+note The keys in the lower row on the back are named:
+note * Back2, Back4, Back6, Back8, Back10.
+
+bind Front1 LNUP
+bind Front2 LNDN
+
+bind Front3 LNUP
+bind Front4 LNDN
+
+bind Front5 LNUP
+bind Front6 LNDN
+
+bind Front7 LNUP
+bind Front8 LNDN
+
+bind Front9 LNUP
+bind Front10 LNDN
+
+bind Front5+Front1 TOP_LEFT
+bind Front5+Front2 BOT_LEFT
+bind Front6+Front1 TOP
+bind Front6+Front2 BOT
+
+bind Front5+Front3 PRPROMPT
+bind Front5+Front4 NXPROMPT
+bind Front6+Front3 PRPGRPH
+bind Front6+Front4 NXPGRPH
+
+#bind Front5+Front7 LNUP
+#bind Front5+Front8 LNDN
+#bind Front6+Front7 LNUP
+#bind Front6+Front8 LNDN
+
+bind Front5+Front9 PRDIFLN
+bind Front5+Front10 NXDIFLN
+bind Front6+Front9 ATTRUP
+bind Front6+Front10 ATTRDN
+
diff --git a/Tables/Input/bm/front6.kti b/Tables/Input/bm/front6.kti
new file mode 100644
index 0000000..90daa5b
--- /dev/null
+++ b/Tables/Input/bm/front6.kti
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have 6 front keys.
+note The keys in the upper row on the front are named:
+note * Front1, Front3, Front5.
+note The keys in the lower row on the front are named:
+note * Front2, Front4, Front6.
+note The keys in the upper row on the back are named:
+note * Back1, Back3, Back5.
+note The keys in the lower row on the back are named:
+note * Back2, Back4, Back6.
+
diff --git a/Tables/Input/bm/horizontal.kti b/Tables/Input/bm/horizontal.kti
new file mode 100644
index 0000000..642995b
--- /dev/null
+++ b/Tables/Input/bm/horizontal.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have horizontal sensors.
+
+bind !HorizontalSensor ROUTE
+
+bind Display1+!HorizontalSensor CLIP_NEW
+bind Display2+!HorizontalSensor CLIP_ADD
+bind Display4+!HorizontalSensor COPY_LINE
+bind Display5+!HorizontalSensor COPY_RECT
+
+bind Display3+!HorizontalSensor DESCCHAR
+bind Display6+!HorizontalSensor SETLEFT
+
+bind Display2+Display1+!HorizontalSensor PRINDENT
+bind Display2+Display3+!HorizontalSensor NXINDENT
+
+bind Display5+Display4+!HorizontalSensor PRDIFCHAR
+bind Display5+Display6+!HorizontalSensor NXDIFCHAR
+
+bind Display1+Display3+!HorizontalSensor SETMARK
+bind Display4+Display6+!HorizontalSensor GOTOMARK
+
diff --git a/Tables/Input/bm/inka.ktb b/Tables/Input/bm/inka.ktb
new file mode 100644
index 0000000..489240f
--- /dev/null
+++ b/Tables/Input/bm/inka.ktb
@@ -0,0 +1,29 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Inka
+
+include display6.kti
+include horizontal.kti
+include vertical.kti
+
+note The switches are off when to the left and on when to the right.
+note * Switch1 (upper-left): disable all sensors.
+note * Switch2 (lower-left): scaled vertical sensor line selection.
+note * Switch3 (upper-right): show selected horizontal sensor (all dots raised).
+note * Switch4 (lower-right): enable braille keyboard.
diff --git a/Tables/Input/bm/joystick.kti b/Tables/Input/bm/joystick.kti
new file mode 100644
index 0000000..e90f170
--- /dev/null
+++ b/Tables/Input/bm/joystick.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault press Press
+note The five joystick motions are named Left, Right, Up, Down, and \{press}.
+include keyboard.kti
diff --git a/Tables/Input/bm/keyboard.kti b/Tables/Input/bm/keyboard.kti
new file mode 100644
index 0000000..113a5b5
--- /dev/null
+++ b/Tables/Input/bm/keyboard.kti
@@ -0,0 +1,66 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map \{space} SPACE
+
+assign chord \{space}+
+include ../chords.kti
+
+bind \{press} RETURN
+include ../nav.kti
+
+ifKey RoutingKey
+bind \{space}+RoutingKey KEY_FUNCTION
+bind \{space}+RoutingKey+!RoutingKey CLIP_APPEND
+
+bind RoutingKey+\{press} DESCCHAR
+bind RoutingKey+Left CLIP_NEW
+bind RoutingKey+Up CLIP_ADD
+bind RoutingKey+Right COPY_LINE
+bind RoutingKey+Down COPY_RECT
+endIf
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind \{press} PREFMENU
+bind Up MENU_PREV_ITEM
+bind Down MENU_NEXT_ITEM
+bind Left MENU_PREV_SETTING
+bind Right MENU_NEXT_SETTING
+
+bind \{space} MENU_PREV_LEVEL
+bind \{space}+\{press} PREFSAVE
+bind \{space}+Up MENU_FIRST_ITEM
+bind \{space}+Down MENU_LAST_ITEM
+bind \{space}+Left FWINLT
+bind \{space}+Right FWINRT
+
+context default
diff --git a/Tables/Input/bm/navpad.kti b/Tables/Input/bm/navpad.kti
new file mode 100644
index 0000000..8b2f652
--- /dev/null
+++ b/Tables/Input/bm/navpad.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault press Select
+note The five navigation pad keys are named Left, Right, Up, Down, and \{press}.
+include keyboard.kti
diff --git a/Tables/Input/bm/orbit.ktb b/Tables/Input/bm/orbit.ktb
new file mode 100644
index 0000000..f6de220
--- /dev/null
+++ b/Tables/Input/bm/orbit.ktb
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Orbit Reader
+
+note A six-dot standard braille keyboard is along the back edge of the top.
+note * From left to right, its keys are: Dot3, Dot2, Dot1 - Dot4, Dot5, Dot6.
+note There's a five-key navigation pad in between Dot1 and Dot4.
+note Space is the long key in the middle, just in front of the navigation pad.
+note Dot7 and Dot8 are the keys immediately to the left and right of Space.
+
+assign space Space
+include navpad.kti
+
+assign press Select
+include emulate6.kti
+include routing.kti
+
+note The rocker to the left of the braille cells emulates the Display2 key,
+note * and the one to their right emulates the Display5 key.
diff --git a/Tables/Input/bm/pro.ktb b/Tables/Input/bm/pro.ktb
new file mode 100644
index 0000000..ffe7ee0
--- /dev/null
+++ b/Tables/Input/bm/pro.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario Pro
+
+include display6.kti
+include wheels.kti
+include status.kti
+include routing6.kti
diff --git a/Tables/Input/bm/pronto.ktb b/Tables/Input/bm/pronto.ktb
new file mode 100644
index 0000000..08aa489
--- /dev/null
+++ b/Tables/Input/bm/pronto.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Pronto!
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables/Input/bm/pv.ktb b/Tables/Input/bm/pv.ktb
new file mode 100644
index 0000000..7b64a46
--- /dev/null
+++ b/Tables/Input/bm/pv.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum PocketVario
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables/Input/bm/rb.ktb b/Tables/Input/bm/rb.ktb
new file mode 100644
index 0000000..b0d9ab6
--- /dev/null
+++ b/Tables/Input/bm/rb.ktb
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Refreshabraille
+
+note B9 is the long key in the middle, just in front of the joystick.
+note Dot7 and Dot8 are the keys immediately to the left and right of B9.
+note B10 is the narrow key between and just behind the six-dot keyboard.
+
+assign press Press
+include emulate6.kti
+include routing6.kti
+
+assign space B9
+include joystick.kti
diff --git a/Tables/Input/bm/routing.kti b/Tables/Input/bm/routing.kti
new file mode 100644
index 0000000..f88bc47
--- /dev/null
+++ b/Tables/Input/bm/routing.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note RoutingKey refers to any of the keys immediately behind the braille cells.
+
+bind RoutingKey ROUTE
diff --git a/Tables/Input/bm/routing6.kti b/Tables/Input/bm/routing6.kti
new file mode 100644
index 0000000..c32b366
--- /dev/null
+++ b/Tables/Input/bm/routing6.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have routing keys and 6 display keys.
+
+include routing.kti
+
+bind Display1+RoutingKey CLIP_NEW
+bind Display2+RoutingKey CLIP_ADD
+bind Display4+RoutingKey COPY_LINE
+bind Display5+RoutingKey COPY_RECT
+
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind Display2+RoutingKey+!RoutingKey CLIP_APPEND
+
+bind Display3+RoutingKey DESCCHAR
+bind Display6+RoutingKey SETLEFT
+
+bind Display2+Display1+RoutingKey PRINDENT
+bind Display2+Display3+RoutingKey NXINDENT
+
+bind Display5+Display4+RoutingKey PRDIFCHAR
+bind Display5+Display6+RoutingKey NXDIFCHAR
+
+bind Display1+Display3+RoutingKey SETMARK
+bind Display4+Display6+RoutingKey GOTOMARK
+
+bind Display4+Display5+Display6+RoutingKey SWITCHVT
+
diff --git a/Tables/Input/bm/routing7.kti b/Tables/Input/bm/routing7.kti
new file mode 100644
index 0000000..dbeb640
--- /dev/null
+++ b/Tables/Input/bm/routing7.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have routing keys and 7 display keys.
+
+include routing.kti
+
+bind Display5+!RoutingKey DESCCHAR
+
+bind Display3+!RoutingKey CLIP_NEW
+bind Display4+!RoutingKey CLIP_ADD
+bind Display6+!RoutingKey COPY_LINE
+bind Display7+!RoutingKey COPY_RECT
+
+bind Display5+Display3+!RoutingKey PRINDENT
+bind Display5+Display4+!RoutingKey NXINDENT
+
+bind Display5+Display6+!RoutingKey PRDIFCHAR
+bind Display5+Display7+!RoutingKey NXDIFCHAR
+
+bind Display1+!RoutingKey SETMARK
+bind Display2+!RoutingKey GOTOMARK
+bind Display1+Display2+!RoutingKey SETLEFT
+
diff --git a/Tables/Input/bm/status.kti b/Tables/Input/bm/status.kti
new file mode 100644
index 0000000..4f0b466
--- /dev/null
+++ b/Tables/Input/bm/status.kti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have a status module.
+note The larger controls on the status module, from left to right,
+note are named StatusButton1 through StatusButton4.
+note The smaller controls on the status module, from left to right,
+note are named StatusKey1 through StatusKey4.
+
+bind StatusButton1 HELP
+bind StatusButton2 LEARN
+bind StatusButton3 INFO
+bind StatusButton4 PREFMENU
+
+bind StatusKey1 CSRVIS
+bind StatusKey2 ATTRVIS
+bind StatusKey3 FREEZE
+bind StatusKey4 SIXDOTS
+
diff --git a/Tables/Input/bm/sv.ktb b/Tables/Input/bm/sv.ktb
new file mode 100644
index 0000000..42a3c3f
--- /dev/null
+++ b/Tables/Input/bm/sv.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum SuperVario / HWG Brailliant
+
+include display6.kti
+include routing6.kti
diff --git a/Tables/Input/bm/ultra.ktb b/Tables/Input/bm/ultra.ktb
new file mode 100644
index 0000000..ff121e7
--- /dev/null
+++ b/Tables/Input/bm/ultra.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum VarioUltra
+
+include display6.kti
+include routing6.kti
+include b9b10.kti
diff --git a/Tables/Input/bm/v40.ktb b/Tables/Input/bm/v40.ktb
new file mode 100644
index 0000000..21757c0
--- /dev/null
+++ b/Tables/Input/bm/v40.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario 40
+
+include display6.kti
+include routing6.kti
diff --git a/Tables/Input/bm/v80.ktb b/Tables/Input/bm/v80.ktb
new file mode 100644
index 0000000..a5e884f
--- /dev/null
+++ b/Tables/Input/bm/v80.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum Vario 80
+
+include display6.kti
+include command.kti
+include front10.kti
+include routing6.kti
diff --git a/Tables/Input/bm/vertical.kti b/Tables/Input/bm/vertical.kti
new file mode 100644
index 0000000..2b8163f
--- /dev/null
+++ b/Tables/Input/bm/vertical.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have vertical sensors.
+
+bind !LeftSensor GOTOLINE+toleft
+bind !RightSensor GOTOLINE
+bind !ScaledLeftSensor GOTOLINE+toleft+scaled
+bind !ScaledRightSensor GOTOLINE+scaled
diff --git a/Tables/Input/bm/vk.ktb b/Tables/Input/bm/vk.ktb
new file mode 100644
index 0000000..b2aff18
--- /dev/null
+++ b/Tables/Input/bm/vk.ktb
@@ -0,0 +1,42 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Baum (variokeys=yes)
+
+bind Display2 FWINLT
+bind Display5 FWINRT
+
+bind Display1 LNUP
+bind Display3 LNDN
+
+bind Display4 KEY_CURSOR_UP
+bind Display6 KEY_CURSOR_DOWN
+
+bind Display1+Display3 HOME
+bind Display2+Display1 TOP_LEFT
+bind Display2+Display3 BOT_LEFT
+
+bind Display1+Display4 PREFMENU
+bind Display1+Display2+Display4 FREEZE
+bind Display1+Display2+Display5 HELP
+bind Display2+Display4 INFO
+bind Display2+Display3+Display4+Display5 CSRTRK
+bind Display1+Display3+Display6 ATTRVIS
+bind Display1+Display2+Display3+Display6 DISPMD
+
+include routing6.kti
diff --git a/Tables/Input/bm/wheels.kti b/Tables/Input/bm/wheels.kti
new file mode 100644
index 0000000..a8a35e0
--- /dev/null
+++ b/Tables/Input/bm/wheels.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Baum displays which have wheels.
+note The wheels on the display module are identified by their ordinal position
+note (first through fourth) from the left.
+
+bind !FirstWheelUp LNUP
+bind !FirstWheelDown LNDN
+bind !FirstWheelPress HOME
+
+bind !SecondWheelUp LNUP
+bind !SecondWheelDown LNDN
+bind !SecondWheelPress HOME
+
+bind !ThirdWheelUp LNUP
+bind !ThirdWheelDown LNDN
+bind !ThirdWheelPress HOME
+
+bind !FourthWheelUp LNUP
+bind !FourthWheelDown LNDN
+bind !FourthWheelPress HOME
+
diff --git a/Tables/Input/bn/all.ktb b/Tables/Input/bn/all.ktb
new file mode 100644
index 0000000..ca76c8b
--- /dev/null
+++ b/Tables/Input/bn/all.ktb
@@ -0,0 +1,212 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Braille Note
+
+bind Previous FWINLT
+bind Next FWINRT
+bind Back LNUP
+bind Advance LNDN
+bind Previous+Back LNBEG
+bind Next+Advance LNEND
+bind Previous+Advance TOP_LEFT
+bind Previous+Next BOT_LEFT
+bind Back+Advance BACK
+bind Back+Next CSRTRK
+
+bind RoutingKey ROUTE
+
+bind Dot1 CHRLT
+bind Dot1+Dot2 HWINLT
+bind Dot2 FWINLT
+bind Dot2+Dot3 FWINLTSKIP
+bind Dot3 LNBEG
+bind Dot1+Dot3 LNUP
+bind Dot1+Dot2+Dot3 TOP_LEFT
+bind Dot4 CHRRT
+bind Dot4+Dot5 HWINRT
+bind Dot5 FWINRT
+bind Dot5+Dot6 FWINRTSKIP
+bind Dot6 LNEND
+bind Dot4+Dot6 LNDN
+bind Dot4+Dot5+Dot6 BOT_LEFT
+bind Dot1+Dot4 TOP
+bind Dot2+Dot5 HOME
+bind Dot3+Dot6 BOT
+bind Dot1+Dot4+Dot5 PRDIFLN
+bind Dot2+Dot5+Dot6 NXDIFLN
+bind Dot1+Dot2+Dot4 PRSEARCH
+bind Dot2+Dot3+Dot5 NXSEARCH
+bind Dot1+Dot2+Dot5 ATTRUP
+bind Dot2+Dot3+Dot6 ATTRDN
+bind Dot2+Dot4 CONTEXT+PRINDENT
+bind Dot3+Dot5 CONTEXT+NXINDENT
+bind Dot2+Dot4+Dot5 WINUP
+bind Dot3+Dot5+Dot6 WINDN
+
+bind Space HOME
+bind Space+Dot1+Dot4+Dot6 NOOP # acknowledge alarm
+bind Space+Dot2+Dot3+Dot5 NOOP # go to task menu
+bind Space+Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 NOOP # go to main menu
+bind Space+Dot1+Dot4 PREFMENU
+bind Space+Dot1+Dot4+Dot5 PREFLOAD
+bind Space+Dot1+Dot5 NOOP # exit current operation
+bind Space+Dot1+Dot2+Dot5 NOOP # help for current operation
+bind Space+Dot1+Dot2+Dot3 CONTEXT+SETLEFT
+bind Space+Dot1+Dot3+Dot4 MUTE
+bind Space+Dot1+Dot3+Dot4+Dot5 CONTEXT+default # navigation mode
+bind Space+Dot1+Dot3+Dot5 NOOP # go to options menu
+bind Space+Dot1+Dot2+Dot3+Dot4 PASTE
+bind Space+Dot1+Dot2+Dot3+Dot5 NOOP # repeat current prompt
+bind Space+Dot2+Dot3+Dot4 SAY_LINE
+bind Space+Dot1+Dot3+Dot6 NOOP # uppercase for computer braille
+bind Space+Dot2+Dot4+Dot5+Dot6 PREFSAVE
+bind Space+Dot1+Dot3+Dot5+Dot6 NOOP # exit current operation
+bind Space+Dot1+Dot2+Dot3+Dot5+Dot6 CONTEXT+CLIP_NEW
+bind Space+Dot2+Dot4+Dot6 CONTEXT+CLIP_ADD
+bind Space+Dot2+Dot3+Dot4+Dot5+Dot6 CONTEXT+COPY_RECT
+bind Space+Dot1+Dot2+Dot4+Dot5+Dot6 CONTEXT+COPY_LINE
+bind Space+Dot1+Dot2+Dot5+Dot6 CSRJMP_VERT
+bind Space+Dot1+Dot4+Dot5+Dot6 LEARN
+bind Space+Dot2+Dot3+Dot5+Dot6 KEY_TAB
+bind Space+Dot2+Dot3 KEY_CURSOR_LEFT
+bind Space+Dot5+Dot6 KEY_CURSOR_RIGHT
+bind Space+Dot2+Dot5 KEY_CURSOR_UP
+bind Space+Dot3+Dot6 KEY_CURSOR_DOWN
+bind Space+Dot2 KEY_HOME
+bind Space+Dot3 KEY_END
+bind Space+Dot5 KEY_PAGE_UP
+bind Space+Dot6 KEY_PAGE_DOWN
+bind Space+Dot3+Dot5 KEY_INSERT
+bind Space+Dot2+Dot5+Dot6 KEY_DELETE
+bind Space+Dot2+Dot6 KEY_ESCAPE
+
+bind Backspace KEY_BACKSPACE
+bind Backspace+Dot1 DISPMD+on
+bind Backspace+Dot1+Dot2 SKPBLNKWINS+off
+bind Backspace+Dot1+Dot4+Dot5 CONTEXT+DESCCHAR
+bind Backspace+Dot1+Dot2+Dot5 HELP
+bind Backspace+Dot2+Dot4 SKPIDLNS+off
+bind Backspace+Dot1+Dot3+Dot4 CONTEXT+SETMARK
+bind Backspace+Dot2+Dot3+Dot4 INFO
+bind Backspace+Dot2+Dot3+Dot4+Dot5 DISPMD+off
+bind Backspace+Dot1+Dot2+Dot3+Dot6 SWITCHVT_PREV
+bind Backspace+Dot2+Dot4+Dot5+Dot6 SLIDEWIN+off
+bind Backspace+Dot2+Dot3+Dot5 SIXDOTS+on
+bind Backspace+Dot2+Dot3+Dot6 SIXDOTS+off
+bind Backspace+Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 RESTARTSPEECH
+
+bind Enter KEY_ENTER
+bind Enter+Dot1 NOOP # decrease speech volume
+bind Enter+Dot4 NOOP # increase speech volume
+bind Enter+Dot2 NOOP # decrease speech pitch
+bind Enter+Dot5 NOOP # increase speech pitch
+bind Enter+Dot3 NOOP # decrease speech speed
+bind Enter+Dot6 NOOP # increase speech speed
+bind Enter+Dot1+Dot2 SKPBLNKWINS+on
+bind Enter+Dot1+Dot4+Dot5 NOOP # display the date
+bind Enter+Dot1+Dot2+Dot4 FREEZE
+bind Enter+Dot1+Dot2+Dot5 NOOP # hear punctuation in current prompt
+bind Enter+Dot2+Dot4 SKPIDLNS+on
+bind Enter+Dot1+Dot3+Dot4 CONTEXT+GOTOMARK
+bind Enter+Dot2+Dot3+Dot4 NOOP # spell name in current prompt
+bind Enter+Dot2+Dot3+Dot4+Dot5 NOOP # display the time
+bind Enter+Dot1+Dot2+Dot3+Dot6 SWITCHVT_NEXT
+bind Enter+Dot2+Dot4+Dot5+Dot6 SLIDEWIN+on
+bind Enter+Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 RESTARTBRL
+
+note To temporarily (for the next character) switch to an input mode,
+note use [4] together with combinations of [3] and ]6].
+note To permanently switch to an input mode,
+note use [45] together with combinations of [3] and ]6].
+note To turn on dot 7, add [3].  To turn on dot 8, add [6].
+note To switch back to (n)avigation mode, use space+[1345].
+
+hide on
+bind Space+Dot4 CONTEXT+temporaryInput
+bind Space+Dot4+Dot5 CONTEXT+persistentInput
+bind Space+Dot4+Dot3 CONTEXT+temporaryInput7
+bind Space+Dot4+Dot5+Dot3 CONTEXT+persistentInput7
+bind Space+Dot4+Dot6 CONTEXT+temporaryInput8
+bind Space+Dot4+Dot5+Dot6 CONTEXT+persistentInput8
+bind Space+Dot4+Dot3+Dot6 CONTEXT+temporaryInput78
+bind Space+Dot4+Dot5+Dot3+Dot6 CONTEXT+persistentInput78
+
+context temporaryInput
+include input.kti
+
+context persistentInput Input Mode
+include input.kti
+
+context temporaryInput7
+include input.kti
+superimpose DOT7
+
+context persistentInput7 Input Mode (dot 7)
+include input.kti
+superimpose DOT7
+
+context temporaryInput8
+include input.kti
+superimpose DOT8
+
+context persistentInput8 Input Mode (dot 8)
+include input.kti
+superimpose DOT8
+
+context temporaryInput78
+include input.kti
+superimpose DOT7
+superimpose DOT8
+
+context persistentInput78 Input Mode (dots 7 and 8)
+include input.kti
+superimpose DOT7
+superimpose DOT8
+hide off
+
+context DESCCHAR
+bind RoutingKey DESCCHAR
+
+context SETLEFT
+bind RoutingKey SETLEFT
+
+context CLIP_NEW
+bind RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind RoutingKey CLIP_ADD
+
+context COPY_RECT
+bind RoutingKey COPY_RECT
+
+context COPY_LINE
+bind RoutingKey COPY_LINE
+
+context SETMARK
+bind RoutingKey SETMARK
+
+context GOTOMARK
+bind RoutingKey GOTOMARK
+
+context PRINDENT
+bind RoutingKey PRINDENT
+
+context NXINDENT
+bind RoutingKey NXINDENT
+
diff --git a/Tables/Input/bn/input.kti b/Tables/Input/bn/input.kti
new file mode 100644
index 0000000..7b6fd4c
--- /dev/null
+++ b/Tables/Input/bn/input.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Braille Note input modes.
+
+map Space SPACE
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
diff --git a/Tables/Input/bp/all.kti b/Tables/Input/bp/all.kti
new file mode 100644
index 0000000..b2e2f66
--- /dev/null
+++ b/Tables/Input/bp/all.kti
@@ -0,0 +1,196 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The six round keys near the back are a braille keyboard.
+note From left to right, they're named: Dot3, Dot2, Dot1, Dot4, Dot5, Dot6.
+note From left to right, the three long function keys are named: Shift, Space, Control.
+note This table maps Shift to Dot7, and Control to Dot8.
+note The joystick between the dot and function keys has five positions named: Up, Down, Left, Right, Enter.
+note The round scroll keys at either end of the braille display are named: Left, Right.
+
+
+####################
+# Default Bindings #
+####################
+
+ifKey RoutingKey bind RoutingKey ROUTE
+
+bind ScrollLeft+ScrollRight HOME
+bind ScrollLeft FWINLT
+bind ScrollRight FWINRT
+bind ScrollLeft+Space LNUP
+bind ScrollRight+Space LNDN
+bind ScrollLeft+Shift FWINLTSKIP
+bind ScrollRight+Control FWINRTSKIP
+
+bind ScrollLeft+JoystickEnter SETLEFT
+bind ScrollLeft+JoystickLeft CHRLT
+bind ScrollLeft+JoystickRight CHRRT
+bind ScrollLeft+JoystickUp PRDIFLN
+bind ScrollLeft+JoystickDown NXDIFLN
+
+bind ScrollRight+JoystickEnter DESCCHAR
+bind ScrollRight+JoystickLeft HWINLT
+bind ScrollRight+JoystickRight HWINRT
+bind ScrollRight+JoystickUp ATTRUP
+bind ScrollRight+JoystickDown ATTRDN
+
+bind ScrollLeft+ScrollRight+JoystickEnter BACK
+bind ScrollLeft+ScrollRight+JoystickLeft PRPGRPH
+bind ScrollLeft+ScrollRight+JoystickRight NXPGRPH
+bind ScrollLeft+ScrollRight+JoystickUp PRPROMPT
+bind ScrollLeft+ScrollRight+JoystickDown NXPROMPT
+
+bind Shift+JoystickEnter CSRJMP_VERT
+bind Shift+JoystickLeft LNBEG
+bind Shift+JoystickRight LNEND
+bind Shift+JoystickUp TOP
+bind Shift+JoystickDown BOT
+
+bind Control+JoystickLeft CLIP_NEW
+bind Control+JoystickUp CLIP_ADD
+bind Control+JoystickRight COPY_LINE
+bind Control+JoystickDown COPY_RECT
+bind Control+JoystickEnter PASTE
+
+bind Dot1+JoystickEnter DISPMD
+bind Dot1+JoystickLeft DISPMD+off
+bind Dot1+JoystickRight DISPMD+on
+bind Dot1+Dot2+JoystickEnter SKPBLNKWINS
+bind Dot1+Dot2+JoystickLeft SKPBLNKWINS+off
+bind Dot1+Dot2+JoystickRight SKPBLNKWINS+on
+bind Dot1+Dot4+JoystickEnter CSRVIS
+bind Dot1+Dot4+JoystickLeft CSRVIS+off
+bind Dot1+Dot4+JoystickRight CSRVIS+on
+bind Dot1+Dot2+Dot4+JoystickEnter FREEZE
+bind Dot1+Dot2+Dot5+JoystickEnter HELP
+bind Dot2+Dot4+JoystickEnter SKPIDLNS
+bind Dot2+Dot4+JoystickLeft SKPIDLNS+off
+bind Dot2+Dot4+JoystickRight SKPIDLNS+on
+bind Dot1+Dot2+Dot3+JoystickEnter LEARN
+bind Dot1+Dot2+Dot3+Dot4+JoystickEnter PREFMENU
+bind Dot1+Dot2+Dot3+Dot4+JoystickLeft PREFLOAD
+bind Dot1+Dot2+Dot3+Dot4+JoystickRight PREFSAVE
+bind Dot2+Dot3+Dot4+JoystickEnter INFO
+bind Dot2+Dot3+Dot4+Dot5+JoystickEnter CSRTRK
+bind Dot2+Dot3+Dot4+Dot5+JoystickLeft CSRTRK+off
+bind Dot2+Dot3+Dot4+Dot5+JoystickRight CSRTRK+on
+bind Dot1+Dot3+Dot6+JoystickEnter ATTRVIS
+bind Dot1+Dot3+Dot6+JoystickLeft ATTRVIS+off
+bind Dot1+Dot3+Dot6+JoystickRight ATTRVIS+on
+bind Dot2+Dot4+Dot5+Dot6+JoystickEnter SLIDEWIN
+bind Dot2+Dot4+Dot5+Dot6+JoystickLeft SLIDEWIN+off
+bind Dot2+Dot4+Dot5+Dot6+JoystickRight SLIDEWIN+on
+bind Dot2+Dot3+Dot5+JoystickEnter SIXDOTS+on
+bind Dot2+Dot3+Dot6+JoystickEnter SIXDOTS+off
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Shift DOT7
+map Control DOT8
+map Space SPACE
+
+bind Space+Shift KEY_BACKSPACE
+bind Space+Control KEY_ENTER
+bind Space+Shift+Control KEY_ESCAPE
+
+bind Space+Dot1 KEY_CURSOR_LEFT
+bind Space+Dot4 KEY_CURSOR_RIGHT
+bind Space+Dot2 KEY_CURSOR_UP
+bind Space+Dot5 KEY_CURSOR_DOWN
+bind Space+Dot3 KEY_PAGE_UP
+bind Space+Dot6 KEY_PAGE_DOWN
+
+bind Space+Dot1+Dot2 KEY_BACKSPACE
+bind Space+Dot1+Dot4+Dot5 KEY_DELETE
+bind Space+Dot1+Dot5 KEY_END
+bind Space+Dot1+Dot2+Dot5 KEY_HOME
+bind Space+Dot2+Dot4 KEY_INSERT
+bind Space+Dot1+Dot2+Dot3+Dot5 KEY_ENTER
+bind Space+Dot2+Dot3+Dot4+Dot5 KEY_TAB
+bind Space+Dot1+Dot3+Dot4+Dot6 KEY_ESCAPE
+
+bind JoystickEnter ROUTE
+bind JoystickLeft KEY_CURSOR_LEFT
+bind JoystickRight KEY_CURSOR_RIGHT
+bind JoystickUp KEY_CURSOR_UP
+bind JoystickDown KEY_CURSOR_DOWN
+
+bind Space+JoystickEnter KEY_INSERT
+bind Space+JoystickLeft KEY_HOME
+bind Space+JoystickRight KEY_END
+bind Space+JoystickUp KEY_PAGE_UP
+bind Space+JoystickDown KEY_PAGE_DOWN
+
+bind Space+Shift+Dot1 KEY_FUNCTION+0
+bind Space+Shift+Dot1+Dot2 KEY_FUNCTION+1
+bind Space+Shift+Dot1+Dot4 KEY_FUNCTION+2
+bind Space+Shift+Dot1+Dot4+Dot5 KEY_FUNCTION+3
+bind Space+Shift+Dot1+Dot5 KEY_FUNCTION+4
+bind Space+Shift+Dot1+Dot2+Dot4 KEY_FUNCTION+5
+bind Space+Shift+Dot1+Dot2+Dot4+Dot5 KEY_FUNCTION+6
+bind Space+Shift+Dot1+Dot2+Dot5 KEY_FUNCTION+7
+bind Space+Shift+Dot2+Dot4 KEY_FUNCTION+8
+bind Space+Shift+Dot2+Dot4+Dot5 KEY_FUNCTION+9
+bind Space+Shift+Dot1+Dot3 KEY_FUNCTION+10
+bind Space+Shift+Dot1+Dot2+Dot3 KEY_FUNCTION+11
+bind Space+Shift+Dot1+Dot3+Dot4 KEY_FUNCTION+12
+bind Space+Shift+Dot1+Dot3+Dot4+Dot5 KEY_FUNCTION+13
+bind Space+Shift+Dot1+Dot3+Dot5 KEY_FUNCTION+14
+bind Space+Shift+Dot1+Dot2+Dot3+Dot4 KEY_FUNCTION+15
+bind Space+Shift+Dot1+Dot2+Dot3+Dot4+Dot5 KEY_FUNCTION+16
+bind Space+Shift+Dot1+Dot2+Dot3+Dot5 KEY_FUNCTION+17
+bind Space+Shift+Dot2+Dot3+Dot4 KEY_FUNCTION+18
+bind Space+Shift+Dot2+Dot3+Dot4+Dot5 KEY_FUNCTION+19
+
+bind Space+Control+Dot1 SWITCHVT+0
+bind Space+Control+Dot1+Dot2 SWITCHVT+1
+bind Space+Control+Dot1+Dot4 SWITCHVT+2
+bind Space+Control+Dot1+Dot4+Dot5 SWITCHVT+3
+bind Space+Control+Dot1+Dot5 SWITCHVT+4
+bind Space+Control+Dot1+Dot2+Dot4 SWITCHVT+5
+bind Space+Control+Dot1+Dot2+Dot4+Dot5 SWITCHVT+6
+bind Space+Control+Dot1+Dot2+Dot5 SWITCHVT+7
+bind Space+Control+Dot2+Dot4 SWITCHVT+8
+bind Space+Control+Dot2+Dot4+Dot5 SWITCHVT+9
+bind Space+Control+Dot1+Dot3 SWITCHVT+10
+bind Space+Control+Dot1+Dot2+Dot3 SWITCHVT+11
+bind Space+Control+Dot1+Dot3+Dot4 SWITCHVT+12
+bind Space+Control+Dot1+Dot3+Dot4+Dot5 SWITCHVT+13
+bind Space+Control+Dot1+Dot3+Dot5 SWITCHVT+14
+bind Space+Control+Dot1+Dot2+Dot3+Dot4 SWITCHVT+15
+bind Space+Control+Dot1+Dot2+Dot3+Dot4+Dot5 SWITCHVT+16
+bind Space+Control+Dot1+Dot2+Dot3+Dot5 SWITCHVT+17
+bind Space+Control+Dot2+Dot3+Dot4 SWITCHVT+18
+bind Space+Control+Dot2+Dot3+Dot4+Dot5 SWITCHVT+19
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+bind JoystickUp MENU_PREV_ITEM
+bind JoystickDown MENU_NEXT_ITEM
+bind JoystickLeft MENU_PREV_SETTING
+bind JoystickRight MENU_NEXT_SETTING
+bind JoystickEnter PREFSAVE
diff --git a/Tables/Input/cb/all.ktb b/Tables/Input/cb/all.ktb
new file mode 100644
index 0000000..5fc4fa8
--- /dev/null
+++ b/Tables/Input/cb/all.ktb
@@ -0,0 +1,108 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title CombiBraille
+
+
+####################
+# Default Bindings #
+####################
+
+bind Thumb1 FWINLT
+bind Thumb2 LNUP
+bind Thumb3 CSRTRK
+bind Thumb4 LNDN
+bind Thumb5 FWINRT
+
+bind Thumb1+Thumb2 TOP_LEFT
+bind Thumb2+Thumb3 TOP
+bind Thumb3+Thumb4 BOT
+bind Thumb4+Thumb5 BOT_LEFT
+
+bind Thumb1+Thumb3 LNBEG
+bind Thumb3+Thumb5 LNEND
+
+bind Thumb1+Thumb4 HWINLT
+bind Thumb2+Thumb5 HWINRT
+
+bind Thumb1+Thumb5 SAY_LINE
+bind Thumb2+Thumb4 MUTE
+
+bind Dot1 LNUP
+bind Dot4 LNDN
+bind Dot1+Dot2+Dot3 TOP
+bind Dot4+Dot5+Dot6 BOT
+bind Dot1+Dot2 WINUP
+bind Dot4+Dot5 WINDN
+
+bind Dot3 FWINLT
+bind Dot6 FWINRT
+bind Dot2+Dot4+Dot6 HWINLT
+bind Dot1+Dot3+Dot5 HWINRT
+bind Dot1+Dot3 CHRLT
+bind Dot4+Dot6 CHRRT
+bind Dot2+Dot3 LNBEG
+bind Dot5+Dot6 LNEND
+
+bind Dot3+Dot6 HOME
+bind Dot1+Dot4 CSRTRK
+bind Dot5 CSRVIS
+bind Dot2+Dot4+Dot5 SKPIDLNS
+bind Dot2+Dot4+Dot5+Dot6 SLIDEWIN
+bind Dot2+Dot3+Dot5 SIXDOTS
+bind Dot2+Dot3+Dot4 TUNES
+
+bind Dot1+Dot2+Dot4 FREEZE
+bind Dot1+Dot3+Dot4 DISPMD
+
+bind Dot1+Dot2+Dot5 HELP
+bind Dot3+Dot4 INFO
+bind Dot1+Dot3+Dot4+Dot6 PREFMENU
+bind Dot1+Dot2+Dot3+Dot5 PREFLOAD
+bind Dot1+Dot2+Dot3+Dot4+Dot5+Dot6 PREFSAVE
+
+bind Dot1+Dot2+Dot3+Dot4 PASTE
+
+bind Dot1+Dot6 MUTE
+bind Dot1+Dot5+Dot6 SAY_LINE
+
+bind Status1 CONTEXT+CLIP_NEW
+bind Status2 CONTEXT+CLIP_ADD
+bind Status3 CONTEXT+COPY_LINE
+bind Status4 CONTEXT+COPY_RECT
+bind Status5 LEARN
+bind Status6 HELP
+
+bind RoutingKey ROUTE
+
+
+############################
+# Routing Key Alternatives #
+############################
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
diff --git a/Tables/Input/ce/all.ktb b/Tables/Input/ce/all.ktb
new file mode 100644
index 0000000..3a348ee
--- /dev/null
+++ b/Tables/Input/ce/all.ktb
@@ -0,0 +1,126 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Cebra
+
+
+####################
+# Default Bindings #
+####################
+
+bind PadCenter1 HOME
+bind PadLeft1 FWINLT
+bind PadRight1 FWINRT
+bind PadUp1 LNUP
+bind PadDown1 LNDN
+
+bind LeftUpper1 TOP_LEFT
+bind LeftLower1 BOT_LEFT
+bind RightUpper1 PRDIFLN
+bind RightLower1 NXDIFLN
+bind LeftMiddle1 SIXDOTS
+bind RightMiddle1 CSRTRK
+
+bind LeftMiddle1+PadCenter1 BACK
+bind LeftMiddle1+PadLeft1 LNBEG
+bind LeftMiddle1+PadRight1 LNEND
+bind LeftMiddle1+PadUp1 ATTRUP
+bind LeftMiddle1+PadDown1 ATTRDN
+
+bind LeftUpper1+PadCenter1 PASTE
+bind LeftUpper1+PadLeft1 PRSEARCH
+bind LeftUpper1+PadRight1 NXSEARCH
+bind LeftUpper1+PadUp1 PRPGRPH
+bind LeftUpper1+PadDown1 NXPGRPH
+
+bind LeftLower1+PadCenter1 CSRJMP_VERT
+bind LeftLower1+PadLeft1 FWINLTSKIP
+bind LeftLower1+PadRight1 FWINRTSKIP
+bind LeftLower1+PadUp1 PRPROMPT
+bind LeftLower1+PadDown1 NXPROMPT
+
+bind RightMiddle1+PadCenter1 NOOP
+bind RightMiddle1+PadLeft1 CHRLT
+bind RightMiddle1+PadRight1 CHRRT
+bind RightMiddle1+PadUp1 TOP
+bind RightMiddle1+PadDown1 BOT
+
+bind RightUpper1+PadCenter1 NOOP
+bind RightUpper1+PadLeft1 ATTRVIS
+bind RightUpper1+PadRight1 CSRVIS
+bind RightUpper1+PadUp1 FREEZE
+bind RightUpper1+PadDown1 DISPMD
+
+bind RightLower1+PadCenter1 NOOP
+bind RightLower1+PadLeft1 PREFLOAD
+bind RightLower1+PadRight1 PREFSAVE
+bind RightLower1+PadUp1 AUTOSPEAK
+bind RightLower1+PadDown1 AUTOREPEAT
+
+bind PadCenter1+PadLeft1 HELP
+bind PadCenter1+PadRight1 LEARN
+bind PadCenter1+PadUp1 INFO
+bind PadCenter1+PadDown1 PREFMENU
+
+bind !RoutingKey ROUTE
+bind PadCenter1+!RoutingKey DESCCHAR
+bind PadUp1+!RoutingKey PRINDENT
+bind PadDown1+!RoutingKey NXINDENT
+bind PadLeft1+!RoutingKey PRDIFCHAR
+bind PadRight1+!RoutingKey NXDIFCHAR
+bind LeftMiddle1+!RoutingKey SETLEFT
+bind LeftUpper1+!RoutingKey CLIP_NEW
+bind LeftLower1+!RoutingKey CLIP_ADD
+bind RightUpper1+!RoutingKey COPY_LINE
+bind RightLower1+!RoutingKey COPY_RECT
+
+hide on
+bind RightMiddle1+!RoutingKey ROUTE+80
+bind PadCenter1+RightMiddle1+!RoutingKey DESCCHAR+80
+bind PadUp1+RightMiddle1+!RoutingKey PRINDENT+80
+bind PadDown1+RightMiddle1+!RoutingKey NXINDENT+80
+bind PadLeft1+RightMiddle1+!RoutingKey PRDIFCHAR+80
+bind PadRight1+RightMiddle1+!RoutingKey NXDIFCHAR+80
+bind LeftMiddle1+RightMiddle1+!RoutingKey SETLEFT+80
+bind LeftUpper1+RightMiddle1+!RoutingKey CLIP_NEW+80
+bind LeftLower1+RightMiddle1+!RoutingKey CLIP_ADD+80
+bind RightUpper1+RightMiddle1+!RoutingKey COPY_LINE+80
+bind RightLower1+RightMiddle1+!RoutingKey COPY_RECT+80
+hide off
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind PadUp1 MENU_PREV_ITEM
+bind PadDown1 MENU_NEXT_ITEM
+bind PadLeft1 MENU_PREV_SETTING
+bind PadRight1 MENU_NEXT_SETTING
+bind PadCenter1 PREFMENU
+
+bind LeftMiddle1 FWINLT
+bind RightMiddle1 FWINRT
+bind LeftUpper1 MENU_FIRST_ITEM
+bind LeftLower1 MENU_LAST_ITEM
+bind RightUpper1 PREFLOAD
+bind RightLower1 PREFSAVE
+
+
diff --git a/Tables/Input/ce/novem.ktb b/Tables/Input/ce/novem.ktb
new file mode 100644
index 0000000..2af0aa1
--- /dev/null
+++ b/Tables/Input/ce/novem.ktb
@@ -0,0 +1,31 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Novem Braille Keyboard
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map LeftSpace SPACE
+bind RightSpace KEY_ENTER
+
diff --git a/Tables/Input/chords.kti b/Tables/Input/chords.kti
new file mode 100644
index 0000000..060df38
--- /dev/null
+++ b/Tables/Input/chords.kti
@@ -0,0 +1,175 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign a Dot1
+assign b Dot1+Dot2
+assign c Dot1+Dot4
+assign d Dot1+Dot4+Dot5
+assign e Dot1+Dot5
+assign f Dot1+Dot2+Dot4
+assign g Dot1+Dot2+Dot4+Dot5
+assign h Dot1+Dot2+Dot5
+assign i Dot2+Dot4
+assign j Dot2+Dot4+Dot5
+assign k Dot1+Dot3
+assign l Dot1+Dot2+Dot3
+assign m Dot1+Dot3+Dot4
+assign n Dot1+Dot3+Dot4+Dot5
+assign o Dot1+Dot3+Dot5
+assign p Dot1+Dot2+Dot3+Dot4
+assign q Dot1+Dot2+Dot3+Dot4+Dot5
+assign r Dot1+Dot2+Dot3+Dot5
+assign s Dot2+Dot3+Dot4
+assign t Dot2+Dot3+Dot4+Dot5
+assign u Dot1+Dot3+Dot6
+assign v Dot1+Dot2+Dot3+Dot6
+assign w Dot2+Dot4+Dot5+Dot6
+assign x Dot1+Dot3+Dot4+Dot6
+assign y Dot1+Dot3+Dot4+Dot5+Dot6
+assign z Dot1+Dot3+Dot5+Dot6
+
+assign toggleOff Dot7
+assign toggleOn Dot8
+
+beginVariables
+assign toggleKeys \{chord}\{b}
+assign toggleCommand SKPBLNKWINS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{c}
+assign toggleCommand CSRVIS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{d}
+assign toggleCommand DISPMD
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{f}
+assign toggleCommand FREEZE
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{g}
+assign toggleCommand CONTRACTED
+include toggle.kti
+endVariables
+
+bind \{chord}\{h} HELP
+
+beginVariables
+assign toggleKeys \{chord}\{i}
+assign toggleCommand SKPIDLNS
+include toggle.kti
+endVariables
+
+bind \{chord}\{l} LEARN
+
+bind \{chord}\{p} PREFMENU
+bind \{chord}\{p}+\{toggleOff} PREFLOAD
+bind \{chord}\{p}+\{toggleOn} PREFSAVE
+bind \{chord}\{p}+\{toggleOff}+\{toggleOn} PREFRESET
+
+beginVariables
+assign toggleKeys \{chord}\{r}
+assign toggleCommand AUTOREPEAT
+include toggle.kti
+endVariables
+
+bind \{chord}\{s} INFO
+
+beginVariables
+assign toggleKeys \{chord}\{t}
+assign toggleCommand CSRTRK
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys \{chord}\{u}
+assign toggleCommand ATTRVIS
+include toggle.kti
+endVariables
+
+bind \{chord}\{v} CSRJMP_VERT
+bind \{chord}\{v}+\{toggleOff} SWITCHVT_PREV
+bind \{chord}\{v}+\{toggleOn} SWITCHVT_NEXT
+
+beginVariables
+assign toggleKeys \{chord}\{w}
+assign toggleCommand SLIDEWIN
+include toggle.kti
+endVariables
+
+bind \{chord}\{x} PASTE
+bind \{chord}\{x}+\{toggleOff} CLIP_RESTORE
+bind \{chord}\{x}+\{toggleOn} CLIP_SAVE
+
+bind \{chord}Dot2+Dot3+Dot5 COMPBRL6+on
+bind \{chord}Dot2+Dot3+Dot6 COMPBRL6+off
+
+bind \{chord}Dot1+Dot3+\{toggleOff} BRLKBD+off
+bind \{chord}Dot1+Dot3+\{toggleOn} BRLKBD+on
+
+bind \{chord}Dot4+Dot6+\{toggleOff} BRLUCDOTS+off
+bind \{chord}Dot4+Dot6+\{toggleOn} BRLUCDOTS+on
+
+bind \{chord}Dot3 KEY_CURSOR_LEFT
+bind \{chord}Dot6 KEY_CURSOR_RIGHT
+bind \{chord}Dot2 KEY_HOME
+bind \{chord}Dot5 KEY_END
+bind \{chord}Dot1 KEY_CURSOR_UP
+bind \{chord}Dot4 KEY_CURSOR_DOWN
+
+bind \{chord}Dot2+Dot3 KEY_PAGE_UP
+bind \{chord}Dot5+Dot6 KEY_PAGE_DOWN
+bind \{chord}Dot4+Dot5 KEY_TAB
+
+bind \{chord}Dot2+Dot5+Dot6 KEY_DELETE
+bind \{chord}Dot2+Dot6 KEY_ESCAPE
+bind \{chord}Dot3+Dot5 KEY_INSERT
+
+bind \{chord}Dot1+Dot8 GUI
+bind \{chord}Dot4+Dot8 SHIFT
+bind \{chord}Dot2+Dot8 META
+bind \{chord}Dot5+Dot8 ALTGR
+bind \{chord}Dot3+Dot8 CONTROL
+bind \{chord}Dot6+Dot8 UPPER
+bind \{chord}Dot7+Dot8 UNSTICK
+
+ifNotVar noUnchorded
+assignDefault commandDot7 KEY_BACKSPACE
+assignDefault commandDot8 KEY_ENTER
+
+bind Dot7 \{commandDot7}
+bind Dot8 \{commandDot8}
+
+bind \{chord}Dot7 PASSDOTS+dot7
+bind \{chord}Dot8 PASSDOTS+dot8
+
+include menu.kti
+endIf
+
+ifPlatform android
+include android-chords.kti
+endif
diff --git a/Tables/Input/cn/all.ktb b/Tables/Input/cn/all.ktb
new file mode 100644
index 0000000..61a7f7a
--- /dev/null
+++ b/Tables/Input/cn/all.ktb
@@ -0,0 +1,63 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Canute
+
+note The keys on the front, from left to right, are: Back, Menu, Forward.
+note The triangular keys to the left of each row of cells are selection keys.
+note + From top to bottom, they're named Line1 through Line9.
+note + The round key above them (labelled H) is named Help.
+note + The square key below them (labelled 0) is named Refresh.
+
+bind Help HELP
+bind Help+Menu PREFMENU
+bind Help+Back PREFLOAD
+bind Help+Forward PREFSAVE
+
+bind Menu HOME
+bind Back WINUP
+bind Forward WINDN
+
+bind Menu+Back FWINLT
+bind Menu+Forward FWINRT
+
+bind Refresh+Back TOP_LEFT
+bind Refresh+Menu LNBEG
+bind Refresh+Forward BOT_LEFT
+
+bind Line1 ROUTE_LINE+0
+bind Line2 ROUTE_LINE+1
+bind Line3 ROUTE_LINE+2
+bind Line4 ROUTE_LINE+3
+bind Line5 ROUTE_LINE+4
+bind Line6 ROUTE_LINE+5
+bind Line7 ROUTE_LINE+6
+bind Line8 ROUTE_LINE+7
+bind Line9 ROUTE_LINE+8
+
+bind Refresh+Help REFRESH
+bind Refresh+Line1 REFRESH_LINE+0
+bind Refresh+Line2 REFRESH_LINE+1
+bind Refresh+Line3 REFRESH_LINE+2
+bind Refresh+Line4 REFRESH_LINE+3
+bind Refresh+Line5 REFRESH_LINE+4
+bind Refresh+Line6 REFRESH_LINE+5
+bind Refresh+Line7 REFRESH_LINE+6
+bind Refresh+Line8 REFRESH_LINE+7
+bind Refresh+Line9 REFRESH_LINE+8
+
diff --git a/Tables/Input/dp/all.ktb b/Tables/Input/dp/all.ktb
new file mode 100644
index 0000000..3ea7125
--- /dev/null
+++ b/Tables/Input/dp/all.ktb
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title DotPad
+
+ifKey RightNext
+include scroll.kti
+endIf
+
+ifKey Space
+include keyboard.kti
+endIf
+
+ifKey PanLeft
+include panning.kti
+endIf
+
+ifKey NavCenter
+include navigation.kti
+endIf
+
+ifKey RoutingKey
+include routing.kti
+endIf
+
+ifKey FunctionKey
+include function.kti
+endIf
+
diff --git a/Tables/Input/dp/function.kti b/Tables/Input/dp/function.kti
new file mode 100644
index 0000000..e6758d3
--- /dev/null
+++ b/Tables/Input/dp/function.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind FunctionKey.1 LNUP:TOP
+bind FunctionKey.4 LNDN:BOT
+
+bind FunctionKey.2 PRDIFLN:ATTRUP
+bind FunctionKey.3 NXDIFLN:ATTRDN
+
+bind FunctionKey.1+FunctionKey.2 PRPROMPT:HELP
+bind FunctionKey.2+FunctionKey.4 NXPROMPT
+
+bind FunctionKey.3+FunctionKey.1 PRPGRPH
+bind FunctionKey.3+FunctionKey.4 NXPGRPH:LEARN
+
+bind FunctionKey.1+FunctionKey.4 HOME:BACK
+bind FunctionKey.2+FunctionKey.3 TIME:PREFMENU
+
+context menu
+bind FunctionKey.1 MENU_PREV_ITEM:MENU_FIRST_ITEM
+bind FunctionKey.2 MENU_PREV_SETTING:PREFLOAD
+bind FunctionKey.3 MENU_NEXT_SETTING:PREFSAVE
+bind FunctionKey.4 MENU_NEXT_ITEM:MENU_LAST_ITEM
+
diff --git a/Tables/Input/dp/keyboard.kti b/Tables/Input/dp/keyboard.kti
new file mode 100644
index 0000000..f4d7f67
--- /dev/null
+++ b/Tables/Input/dp/keyboard.kti
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Space SPACE
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+assign chord Space+
+include ../chords.kti
diff --git a/Tables/Input/dp/navigation.kti b/Tables/Input/dp/navigation.kti
new file mode 100644
index 0000000..ae39483
--- /dev/null
+++ b/Tables/Input/dp/navigation.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind NavLeft FWINLT:LNBEG
+bind NavRight FWINRT:LNEND
+bind NavUp LNUP:TOP
+bind NavDown LNDN:BOT
+bind NavCenter HOME:BACK
+
diff --git a/Tables/Input/dp/panfn4.ktb b/Tables/Input/dp/panfn4.ktb
new file mode 100644
index 0000000..5778c0d
--- /dev/null
+++ b/Tables/Input/dp/panfn4.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title DotPad with Panning Keys and 4 Function Keys
+
+include panning.kti
+include function.kti
diff --git a/Tables/Input/dp/panning.kti b/Tables/Input/dp/panning.kti
new file mode 100644
index 0000000..0f873df
--- /dev/null
+++ b/Tables/Input/dp/panning.kti
@@ -0,0 +1,30 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind PanLeft FWINLT:LNBEG
+bind PanRight FWINRT:LNEND
+
+# A short press of both panning keys is expected to refresh the whole display.
+# A long press of both panning keys is imlemented internally;
+# it gives haptic feedback regarding how charged the battery is.
+bind PanLeft+PanRight REFRESH:NOOP
+
+context menu
+bind PanLeft FWINLT:MENU_PREV_LEVEL
+bind PanRight FWINRT:PREFMENU
+
diff --git a/Tables/Input/dp/routing.kti b/Tables/Input/dp/routing.kti
new file mode 100644
index 0000000..72c4e99
--- /dev/null
+++ b/Tables/Input/dp/routing.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RoutingKey ROUTE
+
diff --git a/Tables/Input/dp/scroll.kti b/Tables/Input/dp/scroll.kti
new file mode 100644
index 0000000..8792d43
--- /dev/null
+++ b/Tables/Input/dp/scroll.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind LeftPrev LNUP:TOP
+bind LeftNext LNDN:BOT
+
+bind RightPrev FWINLT:LNBEG
+bind RightNext FWINRT:LNEND
+
diff --git a/Tables/Input/ec/all.txt b/Tables/Input/ec/all.txt
new file mode 100644
index 0000000..d1e1f55
--- /dev/null
+++ b/Tables/Input/ec/all.txt
@@ -0,0 +1,26 @@
+Help: EcoBraille (English)
+From left to right, front keys are:
+  UP   LEFT   CLICK   RIGHT   DOWN
+
+Movement keys:
+UP                up one line
+LEFT              left one full window
+RIGHT             right one full window
+DOWN              down one line
+CLICK             goto cursor position (only Eco20 model)
+CURSOR + LEFT     left one half window
+CURSOR + RIGHT    right one half window
+CURSOR + UP       top of full window
+CURSOR + DOWN     bottom of full window
+
+
+Other functions:
+STATUS SENSOR 1   toggle help display
+STATUS SENSOR 2   activate preferences menu
+STATUS SENSOR 3   toggle attribute display (only Eco40 and Eco80)
+STATUS SENSOR 4   get status information (only Eco40 and Eco80)
+
+F2                goto cursor position
+F5                toggle cursor visibility
+F8                toggle six-dots mode
+SHIFT + F8        toggle cursor tracking
diff --git a/Tables/Input/ec/spanish.txt b/Tables/Input/ec/spanish.txt
new file mode 100644
index 0000000..4444834
--- /dev/null
+++ b/Tables/Input/ec/spanish.txt
@@ -0,0 +1,25 @@
+Help: EcoBraille (Spanish)
+Teclado frontal de izquierda a derecha
+  ARRIBA   IZQUIERDA   CENTRO   DERECHA   ABAJO
+
+Teclas de movimiento:
+ARRIBA		    una linea hacia arriba
+IZQUIERDA           se mueve a las celdas anteriores
+DERECHA		    se mueve a las celdas siguientes
+ABAJO		    una linea hacia abajo
+CENTRO              sincroniza cursores, solo en la Eco20
+CENTRO + IZQUIERDA  principio de linea
+CENTRO + DERECHA    final de linea
+CENTRO + ARRIBA     principio de pagina
+CENTRO + ABAJO      final de pagina
+
+Otras funciones:
+Sensor de estado 1  cambia a la ventana de ayuda
+Sensor de estado 2  Activa el menu de configuracion
+Sensor de estado 3  visualiza atributos (solo en modelos Eco40 y Eco80)
+Sensor de estado 4  coge la informacion de estado (Eco40 y Eco80)
+
+F2                  sincroniza cursores
+F5                  cursor visible
+F8                  cambia modo 6 puntos
+SHIFT + F8          cambia el seguimiento de cursor
diff --git a/Tables/Input/eu/all.txt b/Tables/Input/eu/all.txt
new file mode 100644
index 0000000..135dbc4
--- /dev/null
+++ b/Tables/Input/eu/all.txt
@@ -0,0 +1,28 @@
+Help: EuroBraille
+      Driver developped by Yannick PLASSIARD and Olivier BERT
+
+To enter in the Learn-Mode, press #+L (on notebraille/clio), Alpha+L8 (for 
+Scriba) or Level1+L7 (on Iris - Level1 may be performed by pressing 
+FG+FB simultaneously).
+For Esys put LeftJoystick in the "Right" position and RightJoystick in the 
+"Up" position.
+
+Using Cut And Paste
+-------------------
+	To begin a block, press "*E" or Beta+L1 or Layer2+L1 depending on your
+braille display and then click on the cell where you want to start the block.
+To end a block press "*M" or Beta+L9 or Layer2+l8 depending on your braille 
+display, and click on the cell where you want to end the block. 
+When you do this, the block is copied into the BRLTTY clipboard, waiting to 
+be pasted anywhere you want. To paste a block,
+press the "*L" or Beta+L8 or Layer2+L7 depending on your braille display. 
+A separate help file for each model will be created soon.
+
+Note
+----
+Please note that the README file contains also version information and
+copyright notice, so if you find a bug that was not listed out in the README
+file, feel free to send an e-mail to me (yan@mistigri.org), because it's
+hard to test all possible functions, even if I use the driver 10 hours a day. 
+
+Thank you.
diff --git a/Tables/Input/eu/braille.kti b/Tables/Input/eu/braille.kti
new file mode 100644
index 0000000..6c52474
--- /dev/null
+++ b/Tables/Input/eu/braille.kti
@@ -0,0 +1,70 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The eight dot keys of the braille keyboard are behind the cursor routing keys.
+note From left to right, they are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+note The two keys of the braille keyboard in front of the braille cells, from left to right, are: Backspace, Space.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map Space SPACE
+bind Backspace KEY_BACKSPACE
+bind Backspace+Space KEY_ENTER
+
+bind Space+Dot7 SHIFT
+bind Space+Dot8 META
+bind Space+Dot7+Dot8 CONTROL
+
+bind Space+Dot2+Dot5+Dot6 KEY_TAB
+bind Space+Dot2+Dot3+Dot6 KEY_TAB+shift
+
+bind Space+Dot4 KEY_CURSOR_UP
+bind Space+Dot6 KEY_CURSOR_DOWN
+bind Space+Dot2 KEY_CURSOR_LEFT
+bind Space+Dot5 KEY_CURSOR_RIGHT
+
+bind Space+Dot1+Dot3 KEY_PAGE_UP
+bind Space+Dot4+Dot6 KEY_PAGE_DOWN
+
+bind Space+Dot1+Dot2+Dot3 KEY_HOME
+bind Space+Dot4+Dot5+Dot6 KEY_END
+
+bind Space+Dot3+Dot6 KEY_DELETE
+bind Space+Dot1+Dot2+Dot4+Dot5 KEY_ESCAPE
+bind Space+Dot1+Dot3+Dot5 KEY_INSERT
+
+bind Backspace+Dot1 KEY_FUNCTION+0
+bind Backspace+Dot1+Dot2 KEY_FUNCTION+1
+bind Backspace+Dot1+Dot4 KEY_FUNCTION+2
+bind Backspace+Dot1+Dot4+Dot5 KEY_FUNCTION+3
+bind Backspace+Dot1+Dot5 KEY_FUNCTION+4
+bind Backspace+Dot1+Dot2+Dot4 KEY_FUNCTION+5
+bind Backspace+Dot1+Dot2+Dot4+Dot5 KEY_FUNCTION+6
+bind Backspace+Dot1+Dot2+Dot5 KEY_FUNCTION+7
+bind Backspace+Dot2+Dot4 KEY_FUNCTION+8
+bind Backspace+Dot2+Dot4+Dot5 KEY_FUNCTION+9
+bind Backspace+Dot1+Dot3 KEY_FUNCTION+10
+bind Backspace+Dot1+Dot2+Dot3 KEY_FUNCTION+11
+
diff --git a/Tables/Input/eu/clio.ktb b/Tables/Input/eu/clio.ktb
new file mode 100644
index 0000000..14a2456
--- /dev/null
+++ b/Tables/Input/eu/clio.ktb
@@ -0,0 +1,102 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille AzerBraille, Clio, NoteBraille, PupiBraille, Scriba
+
+include braille.kti
+
+bind !Star CONTEXT+alt1
+bind !Sharp CONTEXT+alt2
+bind !RoutingKey ROUTE
+
+bind !Up KEY_CURSOR_UP
+bind !Left KEY_CURSOR_LEFT
+bind !Right KEY_CURSOR_Right
+bind !Down KEY_CURSOR_DOWN
+
+bind !Five HOME
+bind !One TOP_LEFT
+bind !Seven BOT_LEFT
+bind !Three PRDIFLN
+bind !Nine NXDIFLN
+bind !Zero CSRTRK
+
+bind !A FREEZE
+
+bind !E FWINLT
+bind !F LNUP
+bind !G PRPROMPT
+bind !H PREFMENU
+bind !I INFO
+bind !K NXPROMPT
+bind !L LNDN
+bind !M FWINRT
+
+
+context alt1
+bind !Star CONTEXT+default
+
+bind !Down CSRTRK
+bind !Right TUNES
+
+bind !E CONTEXT+CLIP_NEW
+bind !F CONTEXT+CLIP_ADD
+bind !G CSRVIS
+bind !K CONTEXT+COPY_RECT
+bind !L PASTE
+bind !M CONTEXT+COPY_LINE
+
+
+context alt2
+bind !Sharp CONTEXT+default
+
+bind !Left LNBEG
+bind !Right LNEND
+bind !Up HOME
+bind !Down BACK
+
+bind !One LEARN
+bind !Three TOP_LEFT
+bind !Nine BOT_LEFT
+
+bind !A DISPMD
+
+bind !E TOP_LEFT
+bind !G PRSEARCH
+bind !H HELP
+bind !K NXSEARCH
+bind !L LEARN
+bind !M BOT_LEFT
+
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+
diff --git a/Tables/Input/eu/common.kti b/Tables/Input/eu/common.kti
new file mode 100644
index 0000000..04f6748
--- /dev/null
+++ b/Tables/Input/eu/common.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/eu/esys_large.ktb b/Tables/Input/eu/esys_large.ktb
new file mode 100644
index 0000000..9594fe7
--- /dev/null
+++ b/Tables/Input/eu/esys_large.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 80
+
+include common.kti
+include joysticks.kti
+include sw12.kti
+include sw34.kti
+include sw56.kti
diff --git a/Tables/Input/eu/esys_medium.ktb b/Tables/Input/eu/esys_medium.ktb
new file mode 100644
index 0000000..558e618
--- /dev/null
+++ b/Tables/Input/eu/esys_medium.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 40,64
+
+include common.kti
+include joysticks.kti
+include sw12.kti
+include sw34.kti
diff --git a/Tables/Input/eu/esys_small.ktb b/Tables/Input/eu/esys_small.ktb
new file mode 100644
index 0000000..6ac4a2d
--- /dev/null
+++ b/Tables/Input/eu/esys_small.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esys 12,24
+
+include common.kti
+include joysticks.kti
+include sw12.kti
diff --git a/Tables/Input/eu/esytime.ktb b/Tables/Input/eu/esytime.ktb
new file mode 100644
index 0000000..b1fd68a
--- /dev/null
+++ b/Tables/Input/eu/esytime.ktb
@@ -0,0 +1,126 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Esytime
+
+include common.kti
+
+note The four keys below the left joystick, from top to bottom, are:
+note * L1, Escape, Tab, Shift.
+note The four keys below the right joystick, from top to bottom, are:
+note * L8, Insert, Alt, Control.
+note Some key combinations have been reserved for internal use:
+note + LeftJoystickDown: the Alt key
+note + LeftJoystickPress: the Alt key pressed twice in a row
+note + LeftJoystickDown + RightJoystick(Right/Left): enable/disable braille functions
+note + LeftJoystickDown + RightJoystick(Up/Down): switch to internal/external (Esytime/PC) USB interface
+note + LeftJoystickDown + L1 + Escape: switch to single-hand braille keyboard 1
+note + LeftJoystickDown + Shift + Tab: switch to single-hand braille keyboard 2
+note + LeftJoystickDown + Insert + L8: switch to standard (two-hand) braille keyboard
+note + RightJoystickPress: the Enter key
+note + RightJoystick(left/right/up/down): the Arrow Left/Right/Up/Down key
+
+bind L1+L8 HOME
+bind LeftJoystickPress+RightJoystickPress BACK
+
+bind L1 FWINLT
+bind L8 FWINRT
+
+bind LeftJoystickLeft LNUP
+bind LeftJoystickRight LNDN
+bind LeftJoystickUp CSRTRK
+bind LeftJoystickDown NOOP # internal: the Alt key
+bind LeftJoystickPress NOOP # internal: the Alt key pressed twice in a row
+
+bind RightJoystickLeft NOOP # internal: the Arrow Left key
+bind RightJoystickRight NOOP # internal: the Arrow Right key
+bind RightJoystickUp NOOP # internal: the Arrow Up key
+bind RightJoystickDown NOOP # internal: the Arrow Down key
+bind RightJoystickPress NOOP # internal: the Enter key
+
+bind LeftJoystickLeft+RightJoystickPress TOP
+bind LeftJoystickLeft+RightJoystickLeft PRPROMPT
+bind LeftJoystickLeft+RightJoystickRight PRPGRPH
+bind LeftJoystickLeft+RightJoystickUp PRDIFLN
+bind LeftJoystickLeft+RightJoystickDown ATTRUP
+
+bind LeftJoystickRight+RightJoystickPress BOT
+bind LeftJoystickRight+RightJoystickLeft NXPROMPT
+bind LeftJoystickRight+RightJoystickRight NXPGRPH
+bind LeftJoystickRight+RightJoystickUp NXDIFLN
+bind LeftJoystickRight+RightJoystickDown ATTRDN
+
+bind LeftJoystickUp+RightJoystickPress DISPMD
+bind LeftJoystickUp+RightJoystickLeft CSRVIS
+bind LeftJoystickUp+RightJoystickRight ATTRVIS
+bind LeftJoystickUp+RightJoystickUp SIXDOTS+on
+bind LeftJoystickUp+RightJoystickDown SIXDOTS+off
+
+bind LeftJoystickDown+RightJoystickPress INFO
+bind LeftJoystickDown+RightJoystickLeft NOOP # internal: disable braille functions
+bind LeftJoystickDown+RightJoystickRight NOOP # internal: enable braille functions
+bind LeftJoystickDown+RightJoystickUp NOOP # internal: switch to internal (Esytime) USB interface
+bind LeftJoystickDown+RightJoystickDown NOOP # internal: switch to external (PC) USB interface
+
+bind LeftJoystickPress+RightJoystickLeft NOOP
+bind LeftJoystickPress+RightJoystickRight NOOP
+bind LeftJoystickPress+RightJoystickUp NOOP
+bind LeftJoystickPress+RightJoystickDown NOOP
+
+bind LeftJoystickPress+RoutingKey1 SETLEFT
+bind LeftJoystickLeft+RoutingKey1 PRDIFCHAR
+bind LeftJoystickRight+RoutingKey1 NXDIFCHAR
+bind LeftJoystickUp+RoutingKey1 PRINDENT
+bind LeftJoystickDown+RoutingKey1 NXINDENT
+
+bind RightJoystickPress+RoutingKey1 DESCCHAR
+bind RightJoystickLeft+RoutingKey1 CLIP_NEW
+bind RightJoystickUp+RoutingKey1 CLIP_ADD
+bind RightJoystickRight+RoutingKey1 COPY_LINE
+bind RightJoystickDown+RoutingKey1 COPY_RECT
+
+bind L1+LeftJoystickPress TIME
+bind L1+LeftJoystickLeft CHRLT
+bind L1+LeftJoystickRight CHRRT
+bind L1+LeftJoystickUp PRSEARCH
+bind L1+LeftJoystickDown NXSEARCH
+
+bind L8+LeftJoystickPress CSRJMP_VERT
+bind L8+LeftJoystickLeft FWINLTSKIP
+bind L8+LeftJoystickRight FWINRTSKIP
+bind L8+LeftJoystickUp LNBEG
+bind L8+LeftJoystickDown LNEND
+
+bind L1+RightJoystickPress PREFMENU
+bind L1+RightJoystickLeft PREFLOAD
+bind L1+RightJoystickRight PREFSAVE
+bind L1+RightJoystickUp HELP
+bind L1+RightJoystickDown LEARN
+
+bind L8+RightJoystickPress PASTE
+bind L8+RightJoystickLeft CLIP_RESTORE
+bind L8+RightJoystickRight CLIP_SAVE
+bind L8+RightJoystickUp FREEZE
+bind L8+RightJoystickDown AUTOREPEAT
+
+context menu
+bind L8+RightJoystickUp MENU_PREV_ITEM
+bind L8+RightJoystickDown MENU_NEXT_ITEM
+bind L8+RightJoystickLeft MENU_PREV_SETTING
+bind L8+RightJoystickRight MENU_NEXT_SETTING
+bind L8+RightJoystickPress MENU_PREV_LEVEL
diff --git a/Tables/Input/eu/iris.ktb b/Tables/Input/eu/iris.ktb
new file mode 100644
index 0000000..a6edc5f
--- /dev/null
+++ b/Tables/Input/eu/iris.ktb
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title EuroBraille Iris
+
+include common.kti
+note The Menu key is the small, round button to the left of the cursor routing keys.
+note The Z key is the small, round button to the right of the cursor routing keys.
+
+bind Down+Left CONTEXT+alt1
+bind Down+Right CONTEXT+alt2
+
+bind L1 FWINLT
+bind L2 LNUP
+bind L3 PRPROMPT
+bind L4 PREFMENU
+bind L5 INFO
+bind L6 NXPROMPT
+bind L7 LNDN
+bind L8 FWINRT
+bind Left KEY_CURSOR_LEFT
+bind Right KEY_CURSOR_RIGHT
+bind Up KEY_CURSOR_UP
+bind Down KEY_CURSOR_DOWN
+bind L1+L2 TOP_LEFT
+bind L3+L4 FREEZE
+bind L6+L7 HOME
+bind L7+L8 BOT_LEFT
+bind L1+L2+L3+L4 RESTARTBRL
+bind L5+L6+L7+L8 RESTARTSPEECH
+
+
+context alt1
+bind L1 TOP_LEFT
+bind L3 PRSEARCH
+bind L4 HELP
+bind L5 LEARN
+bind L6 NXSEARCH
+bind L8 BOT_LEFT
+bind Left LNBEG
+bind Right LNEND
+bind Up HOME
+bind Down BACK
+
+
+context alt2
+bind L1 CONTEXT+CLIP_NEW
+bind L2 CONTEXT+CLIP_ADD
+bind L3 CSRVIS
+bind L6 CONTEXT+COPY_RECT
+bind L7 PASTE
+bind L8 CONTEXT+COPY_LINE
+bind Up PREFMENU
+bind Down CSRTRK
+bind Right TUNES
+
+
+context CLIP_NEW
+bind RoutingKey1 CLIP_NEW
+
+
+context CLIP_ADD
+bind RoutingKey1 CLIP_ADD
+
+
+context COPY_RECT
+bind RoutingKey1 COPY_RECT
+
+
+context COPY_LINE
+bind RoutingKey1 COPY_LINE
+
+
diff --git a/Tables/Input/eu/joysticks.kti b/Tables/Input/eu/joysticks.kti
new file mode 100644
index 0000000..abb659f
--- /dev/null
+++ b/Tables/Input/eu/joysticks.kti
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The joysticks are the small, round, five-way (left, right, up, down, press) controls to the left and right of the cursor routing keys.
+
+bind RightJoystickPress HOME
+bind RightJoystickLeft FWINLT
+bind RightJoystickRight FWINRT
+bind RightJoystickUp LNUP
+bind RightJoystickDown LNDN
+
+bind LeftJoystickLeft PRPROMPT
+bind LeftJoystickRight NXPROMPT
+bind LeftJoystickUp TOP_LEFT
+bind LeftJoystickDown BOT_LEFT
+
+bind LeftJoystickDown+RightJoystickLeft CONTEXT+CLIP_NEW
+bind LeftJoystickDown+RightJoystickUp CONTEXT+CLIP_ADD
+bind LeftJoystickDown+RightJoystickDown CONTEXT+COPY_RECT
+bind LeftJoystickDown+RightJoystickRight CONTEXT+COPY_LINE
+bind LeftJoystickDown+RightJoystickPress PASTE
+
+bind LeftJoystickLeft+RightJoystickPress SAY_LINE
+bind LeftJoystickLeft+RightJoystickUp SAY_ABOVE
+bind LeftJoystickLeft+RightJoystickDown SAY_BELOW
+bind LeftJoystickLeft+RightJoystickLeft SAY_SOFTER
+bind LeftJoystickLeft+RightJoystickRight SAY_LOUDER
+
+bind LeftJoystickRight+RightJoystickUp LEARN
+bind LeftJoystickRight+RightJoystickDown HELP
+
+
+context CLIP_NEW
+bind RoutingKey1 CLIP_NEW
+
+
+context CLIP_ADD
+bind RoutingKey1 CLIP_ADD
+
+
+context COPY_RECT
+bind RoutingKey1 COPY_RECT
+
+
+context COPY_LINE
+bind RoutingKey1 COPY_LINE
+
+
+context default
diff --git a/Tables/Input/eu/routing.kti b/Tables/Input/eu/routing.kti
new file mode 100644
index 0000000..3f4dd15
--- /dev/null
+++ b/Tables/Input/eu/routing.kti
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The cursor routing keys are the small, round buttons just behind the braille cells.
+note RoutingKey1 means pressing a cursor routing key once (a single click).
+note RoutingKey2 means pressing a cursor routing key twice quickly (a double click).
+
+bind RoutingKey1 ROUTE
+bind RoutingKey2 DESCCHAR
+
diff --git a/Tables/Input/eu/sw12.kti b/Tables/Input/eu/sw12.kti
new file mode 100644
index 0000000..2c39134
--- /dev/null
+++ b/Tables/Input/eu/sw12.kti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The switches are three-way (left, right, press) controls on the front surface.
+note Switch1 is the left outer switch.
+note Switch2 is the right outer switch.
+
+bind Switch1Left FWINLT
+bind Switch1Right FWINRT
+
+bind LeftJoystickLeft+Switch1Left+Switch1Right MUTE
+bind LeftJoystickLeft+Switch1Left SAY_SLOWER
+bind LeftJoystickLeft+Switch1Right SAY_FASTER
+
+bind LeftJoystickRight+Switch1Left LNBEG
+bind LeftJoystickRight+Switch1Right LNEND
+
+bind LeftJoystickRight+Switch1Left+Switch1Right PREFMENU
+bind LeftJoystickRight+Switch2Left+Switch2Right CSRTRK
+
diff --git a/Tables/Input/eu/sw34.kti b/Tables/Input/eu/sw34.kti
new file mode 100644
index 0000000..d0ed966
--- /dev/null
+++ b/Tables/Input/eu/sw34.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Switch3 is the left inner switch.
+note Switch4 is the right inner switch.
+
diff --git a/Tables/Input/eu/sw56.kti b/Tables/Input/eu/sw56.kti
new file mode 100644
index 0000000..dc43aa2
--- /dev/null
+++ b/Tables/Input/eu/sw56.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Switch5 is between Switch1 and Switch3.
+note Switch6 is between Switch2 and Switch4.
+
diff --git a/Tables/Input/fa/all.ktb b/Tables/Input/fa/all.ktb
new file mode 100644
index 0000000..c56ebba
--- /dev/null
+++ b/Tables/Input/fa/all.ktb
@@ -0,0 +1,151 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Frank Audiodata
+
+note RoutingKey means any of the small keys just behind each of the text cells.
+note From left to right:
+note + The three convex keys to the left of the routing keys are: F1, F2, F3.
+note + The three convex keys to the right of the routing keys are: F4, F5, F6.
+note + The three concave keys at the left along the front are: K1, K2, K3.
+note + The three concave keys in the middle along the front are: K4, K5, K6.
+note + The three concave keys at the right along the front are: K7, K8, K9.
+
+bind F1 HELP
+bind F2 PREFMENU
+bind F3 LEARN
+
+bind F2+F1 PREFLOAD
+bind F2+F3 PREFSAVE
+
+bind F1+F3 INFO
+bind F1+F2+F3 TIME
+
+bind F4 CSRVIS
+bind F5 CSRTRK
+bind F6 SIXDOTS
+
+bind F5+F4 ATTRVIS
+bind F5+F6 DISPMD
+
+bind F4+F6 AUTOREPEAT
+bind F4+F5+F6 FREEZE
+
+bind K4 FWINLT
+bind K6 FWINRT
+bind K4+K6 SKPBLNKWINS
+
+bind K5+K4 LNBEG
+bind K5+K6 LNEND
+
+bind K5 HOME:BACK
+bind K4+K5+K6 CSRJMP_VERT
+
+bind K1+K4 HWINLT
+bind K1+K6 HWINRT
+
+bind K2+K4 FWINLTSKIP
+bind K2+K6 FWINRTSKIP
+
+bind K3+K4 PRNBWIN
+bind K3+K6 NXNBWIN
+
+bind K1+K5+K4 CHRLT
+bind K1+K5+K6 CHRRT
+
+bind K2+K5+K4 CLIP_RESTORE
+bind K2+K5+K6 CLIP_SAVE
+
+bind K3+K5+K4 PRSEARCH
+bind K3+K5+K6 NXSEARCH
+
+bind K7 LNUP
+bind K9 LNDN
+bind K7+K9 SKPIDLNS
+
+bind K8+K7 TOP
+bind K8+K9 BOT
+
+bind K8 SAY_LINE:MUTE
+bind K7+K8+K9 PASTE
+
+bind K1+K7 PRPROMPT
+bind K1+K9 NXPROMPT
+
+bind K2+K7 PRDIFLN
+bind K2+K9 NXDIFLN
+
+bind K3+K7 PRPGRPH
+bind K3+K9 NXPGRPH
+
+bind K1+K8+K7 TOP_LEFT
+bind K1+K8+K9 BOT_LEFT
+
+bind K2+K8+K7 ATTRUP
+bind K2+K8+K9 ATTRDN
+
+bind K3+K8+K7 NOOP
+bind K3+K8+K9 NOOP
+
+bind K8+K2 AUTOSPEAK
+bind K8+K1 SAY_SOFTER
+bind K8+K3 SAY_LOUDER
+bind K8+K2+K1 SAY_SLOWER
+bind K8+K2+K3 SAY_FASTER
+
+bind K8+K5 SPKHOME
+bind K8+K4 SAY_ABOVE
+bind K8+K6 SAY_BELOW
+
+bind RoutingKey ROUTE
+bind RoutingKey+RoutingKey CLIP_COPY
+
+bind F1+RoutingKey CLIP_NEW
+bind F6+RoutingKey COPY_LINE
+
+bind F2+RoutingKey CLIP_APPEND
+bind F5+RoutingKey COPY_RECT
+
+bind F3+RoutingKey SETLEFT
+bind F4+RoutingKey DESCCHAR
+
+bind F4+F6+RoutingKey SWITCHVT
+bind F4+F6+F1 SWITCHVT_PREV
+bind F4+F6+F3 SWITCHVT_NEXT
+
+bind F4+F5+RoutingKey SETMARK
+bind F5+F6+RoutingKey GOTOMARK
+bind F4+F5+F6+RoutingKey KEY_FUNCTION
+
+bind K7+RoutingKey PRINDENT
+bind K9+RoutingKey NXINDENT
+bind K8+K7+RoutingKey PRDIFCHAR
+bind K8+K9+RoutingKey NXDIFCHAR
+
+bind SLIDER GOTOLINE+scaled
+
+
+context menu
+bind F4 MENU_PREV_ITEM
+bind F6 MENU_NEXT_ITEM
+bind F5+F4 MENU_FIRST_ITEM
+bind F5+F6 MENU_LAST_ITEM
+bind F5 MENU_NEXT_SETTING
+bind F4+F6 MENU_PREV_SETTING
+bind F4+F5+F6 MENU_PREV_LEVEL
+
diff --git a/Tables/Input/fs/bumpers.kti b/Tables/Input/fs/bumpers.kti
new file mode 100644
index 0000000..d7bfc8e
--- /dev/null
+++ b/Tables/Input/fs/bumpers.kti
@@ -0,0 +1,54 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for FreedomScientific displays which have bumper bars.
+
+note The left and right bumpers are long, up/down, rocker bars.
+note * Newer models refer to them as panning rockers.
+note * On some models, each bumper is a pair of shorter bars.
+
+bind LeftBumperUp FWINLT
+bind LeftBumperDown FWINRT
+bind RightBumperUp FWINLT
+bind RightBumperDown FWINRT
+
+bind LeftBumperUp+PanLeft CHRLT
+bind LeftBumperDown+PanLeft CHRRT
+bind RightBumperUp+PanLeft HWINLT
+bind RightBumperDown+PanLeft HWINRT
+
+bind LeftBumperUp+LeftSelector FWINLTSKIP
+bind LeftBumperDown+LeftSelector FWINRTSKIP
+bind RightBumperUp+LeftSelector PRNBWIN
+bind RightBumperDown+LeftSelector NXNBWIN
+
+bind LeftBumperUp+RightSelector PRPROMPT
+bind LeftBumperDown+RightSelector NXPROMPT
+bind RightBumperUp+RightSelector PRPGRPH
+bind RightBumperDown+RightSelector NXPGRPH
+
+bind LeftBumperUp+PanRight FREEZE+on
+bind LeftBumperDown+PanRight FREEZE+off
+bind RightBumperUp+PanRight DISPMD+on
+bind RightBumperDown+PanRight DISPMD+off
+
+bind LeftBumperUp+RoutingKey PRDIFCHAR
+bind LeftBumperDown+RoutingKey NXDIFCHAR
+bind RightBumperUp+RoutingKey PRINDENT
+bind RightBumperDown+RoutingKey NXINDENT
+
diff --git a/Tables/Input/fs/common.kti b/Tables/Input/fs/common.kti
new file mode 100644
index 0000000..ca6f469
--- /dev/null
+++ b/Tables/Input/fs/common.kti
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Left\{navKeyType}Press LNBEG
+bind Right\{navKeyType}Press LNEND
+
+bind LeftSelector BACK:TOP_LEFT
+bind RightSelector HOME:BOT_LEFT
+bind LeftSelector+RightSelector PASTE
+
+bind PanLeft FWINLT
+bind PanRight FWINRT
+bind PanLeft+PanRight CSRJMP_VERT
+
+bind LeftSelector+PanLeft TOP_LEFT
+bind LeftSelector+PanRight BOT_LEFT
+
+bind RightSelector+PanLeft TOP
+bind RightSelector+PanRight BOT
+
+bind !Left\{navKeyType}Up LNUP
+bind !Left\{navKeyType}Down LNDN
+
+bind !Right\{navKeyType}Up FWINLT
+bind !Right\{navKeyType}Down FWINRT
+
+bind Left\{navKeyType}Press+!Left\{navKeyType}Up PRDIFLN
+bind Left\{navKeyType}Press+!Left\{navKeyType}Down NXDIFLN
+
+bind Right\{navKeyType}Press+!Right\{navKeyType}Up CHRLT
+bind Right\{navKeyType}Press+!Right\{navKeyType}Down CHRRT
+
+bind PanLeft+!Left\{navKeyType}Up PRPROMPT
+bind PanLeft+!Left\{navKeyType}Down NXPROMPT
+
+bind PanRight+!Left\{navKeyType}Up PRPGRPH
+bind PanRight+!Left\{navKeyType}Down NXPGRPH
+
+bind LeftSelector+!Left\{navKeyType}Up ATTRUP
+bind LeftSelector+!Left\{navKeyType}Down ATTRDN
+
+bind RightSelector+!Left\{navKeyType}Up PRSEARCH
+bind RightSelector+!Left\{navKeyType}Down NXSEARCH
+
+bind RoutingKey ROUTE
+bind Left\{navKeyType}Press+RoutingKey SETLEFT
+bind Right\{navKeyType}Press+RoutingKey DESCCHAR
+
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind PanLeft+RoutingKey CLIP_NEW
+bind PanRight+RoutingKey COPY_RECT
+bind LeftSelector+RoutingKey CLIP_ADD
+bind RightSelector+RoutingKey COPY_LINE
diff --git a/Tables/Input/fs/focus.kti b/Tables/Input/fs/focus.kti
new file mode 100644
index 0000000..fbdbd0f
--- /dev/null
+++ b/Tables/Input/fs/focus.kti
@@ -0,0 +1,52 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for all FreedomScientific Focus displays.
+
+note This description is general because the key layout differs from model to model.
+
+note There is a routing key immediately behind each braille cell.
+note * Some models have a navrow key immediately behind each routing key.
+note * On others, a navrow key can be emulated by long-pressing the corresponding routing key.
+
+note There are two Nav controls - one at each end of the braille cells.
+note * Each has three actions: Up, Down, and Press.
+note * On newer models, each is a rocker combined with a button.
+note * On older models, each is a wheel that can be both rolled and pressed.
+
+note The keys on the front, from left to right, are:
+note * \{frontKeysLeft} - \{frontKeysRight}.
+
+note The left and right pan keys are short bars.
+note * On some models, each has a raised double-arrow.
+
+note The left and right selectors are round.
+note * On older models, they're known as GDF (General Display Function) keys.
+
+assign navKeyType Nav
+include common.kti
+include keyboard.kti
+include speech.kti
+
+bind NavrowKey DESCCHAR
+bind PanLeft+NavrowKey PRINDENT
+bind PanRight+NavrowKey NXINDENT
+bind LeftSelector+NavrowKey PRDIFCHAR
+bind RightSelector+NavrowKey NXDIFCHAR
+bind NavrowKey+!NavrowKey CLIP_APPEND
+
diff --git a/Tables/Input/fs/focus1.ktb b/Tables/Input/fs/focus1.ktb
new file mode 100644
index 0000000..7cf94f5
--- /dev/null
+++ b/Tables/Input/fs/focus1.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus1 (original)
+
+assign frontKeysLeft pan\sleft,\sleft\sselector,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sselector,\span\sright
+
+include focus.kti
diff --git a/Tables/Input/fs/focus14.ktb b/Tables/Input/fs/focus14.ktb
new file mode 100644
index 0000000..f1d5964
--- /dev/null
+++ b/Tables/Input/fs/focus14.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 14
+
+assign frontKeysLeft left\sselector,\sleft\srocker,\span\sleft,\sleft\sshift
+assign frontKeysRight right\sshift,\span\sright,\sright\srocker,\sright\sselector
+
+include focus.kti
+include rockers.kti
diff --git a/Tables/Input/fs/focus40.ktb b/Tables/Input/fs/focus40.ktb
new file mode 100644
index 0000000..5adb7a4
--- /dev/null
+++ b/Tables/Input/fs/focus40.ktb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 40
+
+assign frontKeysLeft pan\sleft,\sleft\srocker,\sleft\sselector,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sselector,\sright\srocker,\span\sright
+
+include focus.kti
+include rockers.kti
diff --git a/Tables/Input/fs/focus80.ktb b/Tables/Input/fs/focus80.ktb
new file mode 100644
index 0000000..4fe92c2
--- /dev/null
+++ b/Tables/Input/fs/focus80.ktb
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific Focus 80
+
+assign frontKeysLeft pan\sleft,\sleft\srocker,\sleft\sselector,\sleft\sbumper,\sleft\sshift
+assign frontKeysRight right\sshift,\sright\sbumper,\sright\sselector,\sright\srocker,\span\sright
+
+include focus.kti
+include rockers.kti
+include bumpers.kti
diff --git a/Tables/Input/fs/keyboard.kti b/Tables/Input/fs/keyboard.kti
new file mode 100644
index 0000000..d219d91
--- /dev/null
+++ b/Tables/Input/fs/keyboard.kti
@@ -0,0 +1,48 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note An eight-dot, Perkins-style, braille keyboard is near the rear of the top.
+note * Its keys, From left to right, are: Dot7, Dot3, Dot2, Dot1 - Dot4, Dot5, Dot6, Dot8.
+note * The space bar is in front of the braille cells.
+note * On some models, the eight dot keys are in a straight line
+note * behind the space bar but in front of the braille cells.
+
+note The left and right shift keys are squarish.
+note * On some models, they're at the center of the front.
+note * On others, they're on either side of the space bar.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map Space SPACE
+map LeftShift CONTROL
+map RightShift META
+
+assign chord Space+
+include ../chords.kti
+
+bind Space+RoutingKey KEY_FUNCTION
+bind LeftShift+RoutingKey SETLEFT
+bind RightShift+RoutingKey SWITCHVT
+
diff --git a/Tables/Input/fs/pacmate.ktb b/Tables/Input/fs/pacmate.ktb
new file mode 100644
index 0000000..e459fc5
--- /dev/null
+++ b/Tables/Input/fs/pacmate.ktb
@@ -0,0 +1,46 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title FreedomScientific PAC Mate
+
+note The rear row of cursor routing keys is actually a set of navigation keys.
+note The 10 middle keys (5 on each side of the center) are special.
+note The two outer ones are named LeftSelector and RightSelector.
+note The eight inner ones are named Hot1 through Hot8.
+note The PanLeft key is any key to the left of the special keys.
+note The PanRight key is any key to the right of the special keys.
+
+bind Hot1 SKPIDLNS
+bind RightSelector+Hot1 SKPBLNKWINS
+bind Hot2 DISPMD
+bind RightSelector+Hot2 ATTRVIS
+bind Hot3 CSRTRK
+bind RightSelector+Hot3 CSRVIS
+bind Hot4 SIXDOTS
+bind RightSelector+Hot4 AUTOREPEAT
+bind Hot5 HELP
+bind RightSelector+Hot5 FREEZE
+bind Hot6 LEARN
+bind RightSelector+Hot6 PREFLOAD
+bind Hot7 PREFMENU
+bind RightSelector+Hot7 PREFSAVE
+bind Hot8 INFO
+bind RightSelector+Hot8 CSRJMP_VERT
+
+assign navKeyType Wheel
+include common.kti
diff --git a/Tables/Input/fs/rockers.kti b/Tables/Input/fs/rockers.kti
new file mode 100644
index 0000000..9050e97
--- /dev/null
+++ b/Tables/Input/fs/rockers.kti
@@ -0,0 +1,58 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for FreedomScientific displays which have rockers.
+
+note The left and right rockers are short, up/down, rocker keys.
+
+bind LeftRockerUp KEY_CURSOR_UP
+bind LeftRockerDown KEY_CURSOR_DOWN
+
+bind LeftRockerUp+PanLeft KEY_CURSOR_LEFT
+bind LeftRockerDown+PanLeft KEY_CURSOR_RIGHT
+
+bind LeftRockerUp+LeftSelector KEY_HOME
+bind LeftRockerDown+LeftSelector KEY_END
+
+bind LeftRockerUp+RightSelector KEY_PAGE_UP
+bind LeftRockerDown+RightSelector KEY_PAGE_DOWN
+
+bind LeftRockerUp+PanRight CSRTRK+off
+bind LeftRockerDown+PanRight CSRTRK+on
+
+bind RightRockerUp LNUP
+bind RightRockerDown LNDN
+
+bind RightRockerUp+PanLeft LNBEG
+bind RightRockerDown+PanLeft LNEND
+
+bind RightRockerUp+LeftSelector PRSEARCH
+bind RightRockerDown+LeftSelector NXSEARCH
+
+bind RightRockerUp+RightSelector ATTRUP
+bind RightRockerDown+RightSelector ATTRDN
+
+bind RightRockerUp+PanRight SIXDOTS+off
+bind RightRockerDown+PanRight SIXDOTS+on
+
+bind RightRockerUp+RoutingKey SETMARK
+bind RightRockerDown+RoutingKey GOTOMARK
+
+bind LeftRockerUp+RightRockerUp INFO
+bind LeftRockerDown+RightRockerDown TIME
+
diff --git a/Tables/Input/fs/speech.kti b/Tables/Input/fs/speech.kti
new file mode 100644
index 0000000..abdd820
--- /dev/null
+++ b/Tables/Input/fs/speech.kti
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind PanLeft+Space SPEAK_CURR_CHAR:DESC_CURR_CHAR
+bind PanLeft+LeftShift SPEAK_PREV_CHAR
+bind PanLeft+RightShift SPEAK_NEXT_CHAR
+bind PanLeft+LeftShift+Space SPEAK_FRST_CHAR
+bind PanLeft+RightShift+Space SPEAK_LAST_CHAR
+
+bind LeftSelector+Space SPEAK_CURR_WORD:SPELL_CURR_WORD
+bind LeftSelector+LeftShift SPEAK_PREV_WORD
+bind LeftSelector+RightShift SPEAK_NEXT_WORD
+bind LeftSelector+LeftShift+Space SPEAK_CURR_LOCN
+bind LeftSelector+RightShift+Space ROUTE_CURR_LOCN
+
+bind RightSelector+Space SPEAK_CURR_LINE
+bind RightSelector+LeftShift SPEAK_PREV_LINE
+bind RightSelector+RightShift SPEAK_NEXT_LINE
+bind RightSelector+LeftShift+Space SPEAK_FRST_LINE
+bind RightSelector+RightShift+Space SPEAK_LAST_LINE
+
+bind PanRight+Space AUTOSPEAK
+bind PanRight+LeftShift MUTE
+bind PanRight+RightShift SAY_LINE
+
+bind PanRight+LeftShift+Space SAY_ABOVE
+bind PanRight+RightShift+Space SAY_BELOW
+bind PanRight+LeftShift+RightShift+Space SAY_ALL
+
+bind Space+Left\{navKeyType}Down SAY_SOFTER
+bind Space+Left\{navKeyType}Up SAY_Louder
+
+bind LeftShift+Left\{navKeyType}Down SAY_SLOWER
+bind LeftShift+Left\{navKeyType}Up SAY_FASTER
+
+bind RightShift+Left\{navKeyType}Down SAY_LOWER
+bind RightShift+Left\{navKeyType}Up SAY_HIGHER
+
diff --git a/Tables/Input/hd/mbl.ktb b/Tables/Input/hd/mbl.ktb
new file mode 100644
index 0000000..7b1d141
--- /dev/null
+++ b/Tables/Input/hd/mbl.ktb
@@ -0,0 +1,115 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Hedo MobilLine
+
+note The keys behind the text cells are named routing keys.
+note The rectangular navigation keys are named K1 through K3.
+note The square navigation keys are named B1 through B6.
+note From left to right, the navigation keys are: B3 B2 B1 K1 K2 K3 B4 B5 B6
+
+bind K2+K1 HELP
+bind K2+K3 LEARN
+
+bind K1+K3 PREFMENU
+bind K1+K3+B1 PREFLOAD
+bind K1+K3+B4 PREFSAVE
+
+bind K2 HOME
+bind K1 FWINLT
+bind K3 FWINRT
+
+bind B3 TOP
+bind B6 BOT
+
+bind B2 LNUP
+bind B5 LNDN
+
+bind B1 PRDIFLN
+bind B4 NXDIFLN
+
+bind B1+B3 ATTRUP
+bind B4+B6 ATTRDN
+
+bind B1+B2 PRPGRPH
+bind B4+B5 NXPGRPH
+
+bind B2+B3 PRPROMPT
+bind B5+B6 NXPROMPT
+
+bind B1+B2+B3 PRSEARCH
+bind B4+B5+B6 NXSEARCH
+
+bind B1+K1 CHRLT
+bind B1+K3 CHRRT
+
+bind B2+K1 FWINLTSKIP
+bind B2+K3 FWINRTSKIP
+
+bind B3+K1 LNBEG
+bind B3+K3 LNEND
+
+bind B4+K1 PASTE
+bind B4+K3 CSRJMP_VERT
+
+bind B5+K1 TIME
+bind B5+K3 INFO
+
+bind B6+K1 CLIP_RESTORE
+bind B6+K3 CLIP_SAVE
+
+assign toggleOff K1
+assign toggleOn K3
+
+assign toggleKeys B1+K2
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+assign toggleKeys B2+K2
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys B3+K2
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys B4+K2
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+assign toggleKeys B5+K2
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys B6+K2
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+bind RoutingKey ROUTE
+
+bind B3+RoutingKey CLIP_NEW
+bind B2+RoutingKey CLIP_ADD
+bind B5+RoutingKey COPY_RECT
+bind B6+RoutingKey COPY_LINE
+
+bind B1+RoutingKey PRINDENT
+bind B4+RoutingKey NXINDENT
+
+bind K1+RoutingKey SETLEFT
+bind K2+RoutingKey DESCCHAR
+
diff --git a/Tables/Input/hd/pfl.ktb b/Tables/Input/hd/pfl.ktb
new file mode 100644
index 0000000..d822c44
--- /dev/null
+++ b/Tables/Input/hd/pfl.ktb
@@ -0,0 +1,118 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Hedo ProfiLine
+
+note The keys behind the text cells are named routing keys.
+note The rectangular navigation keys are named K1 through K3.
+note The square navigation keys are named B1 through B8.
+note From left to right, the navigation keys are: K1 B7 B3 B2 B1 K1 K2 K3 B4 B5 B6 B8 K3
+note Both K1 keys are hard-wired together.
+note Both K3 keys are hard-wired together.
+note The K2 and B1 keys are hard-wired together.
+
+bind K2+K1 HELP
+bind K2+K3 LEARN
+
+bind K1+K3 PREFMENU
+bind K1+K3+B2 PREFLOAD
+bind K1+K3+B5 PREFSAVE
+
+bind K2 HOME
+bind K1 FWINLT
+bind K3 FWINRT
+
+bind B7 TOP
+bind B8 BOT
+
+bind B3 LNUP
+bind B6 LNDN
+
+bind B2 PRDIFLN
+bind B5 NXDIFLN
+
+bind B2+B7 ATTRUP
+bind B5+B8 ATTRDN
+
+bind B2+B3 PRPGRPH
+bind B5+B6 NXPGRPH
+
+bind B3+B7 PRPROMPT
+bind B6+B8 NXPROMPT
+
+bind B2+B3+B7 PRSEARCH
+bind B5+B6+B8 NXSEARCH
+
+bind B2+K1 CHRLT
+bind B2+K3 CHRRT
+
+bind B3+K1 FWINLTSKIP
+bind B3+K3 FWINRTSKIP
+
+bind B7+K1 LNBEG
+bind B7+K3 LNEND
+
+bind B5+K1 PASTE
+bind B5+K3 CSRJMP_VERT
+
+bind B6+K1 TIME
+bind B6+K3 INFO
+
+bind B8+K1 CLIP_RESTORE
+bind B8+K3 CLIP_SAVE
+
+assign toggleOff K1
+assign toggleOn K3
+
+assign toggleKeys B2+K2
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+assign toggleKeys B3+K2
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys B7+K2
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys B5+K2
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+assign toggleKeys B6+K2
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys B8+K2
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+bind RoutingKey ROUTE
+
+bind B7+RoutingKey CLIP_NEW
+bind B3+RoutingKey CLIP_ADD
+bind B6+RoutingKey COPY_RECT
+bind B8+RoutingKey COPY_LINE
+
+bind B2+RoutingKey PRINDENT
+bind B5+RoutingKey NXINDENT
+
+bind K1+RoutingKey SETLEFT
+bind K2+RoutingKey DESCCHAR
+
diff --git a/Tables/Input/hm/beetle.ktb b/Tables/Input/hm/beetle.ktb
new file mode 100644
index 0000000..11b4d8e
--- /dev/null
+++ b/Tables/Input/hm/beetle.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Smart Beetle
+
+include common.kti
+include braille.kti
+include f14.kti
+include pan.kti
diff --git a/Tables/Input/hm/braille.kti b/Tables/Input/hm/braille.kti
new file mode 100644
index 0000000..3c56b8c
--- /dev/null
+++ b/Tables/Input/hm/braille.kti
@@ -0,0 +1,37 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+
+assignDefault metaModifier F2
+map \{metaModifier} META
+
+assignDefault controlModifier F3
+map \{controlModifier} CONTROL
+
+assign chord Space+
+include ../chords.kti
+
diff --git a/Tables/Input/hm/common.kti b/Tables/Input/hm/common.kti
new file mode 100644
index 0000000..2219353
--- /dev/null
+++ b/Tables/Input/hm/common.kti
@@ -0,0 +1,19 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RoutingKey ROUTE
diff --git a/Tables/Input/hm/contexts.kti b/Tables/Input/hm/contexts.kti
new file mode 100644
index 0000000..de2f4ab
--- /dev/null
+++ b/Tables/Input/hm/contexts.kti
@@ -0,0 +1,43 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+############################
+# Routing Key Alternatives #
+############################
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+context SETLEFT
+bind !RoutingKey SETLEFT
+
+context DESCCHAR
+bind !RoutingKey DESCCHAR
+
+context KEY_FUNCTION
+bind RoutingKey KEY_FUNCTION
+
diff --git a/Tables/Input/hm/edge.ktb b/Tables/Input/hm/edge.ktb
new file mode 100644
index 0000000..5bc374d
--- /dev/null
+++ b/Tables/Input/hm/edge.ktb
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Edge and QBrailleXL
+
+include common.kti
+include scroll.kti
+include left.kti
+include right.kti
+include f18.kti
+include contexts.kti
+
+assign metaModifier F4
+assign controlModifier F5
+include braille.kti
+
+
+context PRDIFCHAR
+bind RoutingKey PRDIFCHAR
+
+context NXDIFCHAR
+bind RoutingKey NXDIFCHAR
+
+context PRINDENT
+bind RoutingKey PRINDENT
+
+context NXINDENT
+bind RoutingKey NXINDENT
+
diff --git a/Tables/Input/hm/f14.kti b/Tables/Input/hm/f14.kti
new file mode 100644
index 0000000..62458fc
--- /dev/null
+++ b/Tables/Input/hm/f14.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind F4 RETURN
+bind F1+F4 CONTEXT+KEY_FUNCTION
+bind F2+F3 CSRJMP_VERT
+
+bind F2 LNUP
+bind F3 LNDN
+
+bind F1+F2 FWINLTSKIP
+bind F3+F4 FWINRTSKIP
+
+bind F1+F3+F4 CONTEXT+CLIP_NEW
+bind F2+F3+F4 CONTEXT+CLIP_ADD
+bind F1+F2+F3 CONTEXT+COPY_LINE
+bind F1+F2+F4 CONTEXT+COPY_RECT
+bind F1+F2+F3+F4 PASTE
+
+bind F1+F3 CONTEXT+SETLEFT
+bind F2+F4 CONTEXT+DESCCHAR
+
+include contexts.kti
diff --git a/Tables/Input/hm/f18.kti b/Tables/Input/hm/f18.kti
new file mode 100644
index 0000000..08fc62a
--- /dev/null
+++ b/Tables/Input/hm/f18.kti
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind F1 PRPROMPT
+bind F2 NXPROMPT
+bind F3 PRPGRPH
+bind F4 NXPGRPH
+
+bind F5 LEARN
+bind F6 TIME
+bind F7 CHRLT
+bind F8 CHRRT
+
+bind Space+F1 BRLKBD
+bind Space+F2 BRLUCDOTS
+bind Space+F3 SKPIDLNS
+bind Space+F4 CONTEXT+KEY_FUNCTION
+
+bind Space+F5 CONTEXT+SWITCHVT
+bind Space+F6 SKPBLNKWINS
+bind Space+F7 ATTRVIS
+bind Space+F8 CSRVIS
+
+context SWITCHVT
+bind RoutingKey SWITCHVT
+
diff --git a/Tables/Input/hm/fnkey.kti b/Tables/Input/hm/fnkey.kti
new file mode 100644
index 0000000..7a2c26d
--- /dev/null
+++ b/Tables/Input/hm/fnkey.kti
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note Function Key Bindings
+note + Function-b: skip blank windows on/off
+note + Function-c: cursor show/hide
+note + Function-d: display mode attributes/text
+note + Function-f: screen image frozen/unfrozen
+note + Function-h: help screen enter/leave
+note + Function-i: skip identical lines on/off
+note + Function-l: learn mode enter/leave
+note + Function-p: preferences menu enter/leave
+note + Function-s: status line enter/leave
+note + Function-t: cursor tracking on/off
+note + Function-u: highlight underline on/off
+note + Function-v: bring cursor to current line
+note + Function-w: sliding window on/off
+
+hide on
+assign function F2+Space
+bind \{function}+Dot1+Dot2 SKPBLNKWINS
+bind \{function}+Dot1+Dot4 CSRVIS
+bind \{function}+Dot1+Dot4+Dot5 DISPMD
+bind \{function}+Dot1+Dot2+Dot4 FREEZE
+bind \{function}+Dot1+Dot2+Dot5 HELP
+bind \{function}+Dot2+Dot4 SKPIDLNS
+bind \{function}+Dot1+Dot2+Dot3 LEARN
+bind \{function}+Dot1+Dot2+Dot3+Dot4 PREFMENU
+bind \{function}+Dot2+Dot3+Dot4 INFO
+bind \{function}+Dot2+Dot3+Dot4+Dot5 CSRTRK
+bind \{function}+Dot1+Dot3+Dot6 ATTRVIS
+bind \{function}+Dot1+Dot2+Dot3+Dot6 CSRJMP_VERT
+bind \{function}+Dot2+Dot4+Dot5+Dot6 SLIDEWIN
+hide off
diff --git a/Tables/Input/hm/left.kti b/Tables/Input/hm/left.kti
new file mode 100644
index 0000000..02621d4
--- /dev/null
+++ b/Tables/Input/hm/left.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind LeftPadLeft+LeftPadRight CSRJMP_VERT
+bind LeftPadUp+LeftPadDown CONTEXT+SETLEFT
+
+bind LeftPadUp ATTRUP
+bind LeftPadDown ATTRDN
+bind LeftPadLeft PRSEARCH
+bind LeftPadRight NXSEARCH
+
+bind LeftPadLeft+LeftPadUp CONTEXT+PRDIFCHAR
+bind LeftPadLeft+LeftPadDown CONTEXT+NXDIFCHAR
+
+bind LeftPadRight+LeftPadUp CONTEXT+PRINDENT
+bind LeftPadRight+LeftPadDown CONTEXT+NXINDENT
+
diff --git a/Tables/Input/hm/letters.kti b/Tables/Input/hm/letters.kti
new file mode 100644
index 0000000..ac7826a
--- /dev/null
+++ b/Tables/Input/hm/letters.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{keys}Dot1 PASSDOTS\{flags}+dot1
+bind \{keys}Dot1+Dot2 PASSDOTS\{flags}+dot1+dot2
+bind \{keys}Dot1+Dot4 PASSDOTS\{flags}+dot1+dot4
+bind \{keys}Dot1+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot4+dot5
+bind \{keys}Dot1+Dot5 PASSDOTS\{flags}+dot1+dot5
+bind \{keys}Dot1+Dot2+Dot4 PASSDOTS\{flags}+dot1+dot2+dot4
+bind \{keys}Dot1+Dot2+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot2+dot4+dot5
+bind \{keys}Dot1+Dot2+Dot5 PASSDOTS\{flags}+dot1+dot2+dot5
+bind \{keys}Dot2+Dot4 PASSDOTS\{flags}+dot2+dot4
+bind \{keys}Dot2+Dot4+Dot5 PASSDOTS\{flags}+dot2+dot4+dot5
+bind \{keys}Dot1+Dot3 PASSDOTS\{flags}+dot1+dot3
+bind \{keys}Dot1+Dot2+Dot3 PASSDOTS\{flags}+dot1+dot2+dot3
+bind \{keys}Dot1+Dot3+Dot4 PASSDOTS\{flags}+dot1+dot3+dot4
+bind \{keys}Dot1+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot3+dot4+dot5
+bind \{keys}Dot1+Dot3+Dot5 PASSDOTS\{flags}+dot1+dot3+dot5
+bind \{keys}Dot1+Dot2+Dot3+Dot4 PASSDOTS\{flags}+dot1+dot2+dot3+dot4
+bind \{keys}Dot1+Dot2+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot1+dot2+dot3+dot4+dot5
+bind \{keys}Dot1+Dot2+Dot3+Dot5 PASSDOTS\{flags}+dot1+dot2+dot3+dot5
+bind \{keys}Dot2+Dot3+Dot4 PASSDOTS\{flags}+dot2+dot3+dot4
+bind \{keys}Dot2+Dot3+Dot4+Dot5 PASSDOTS\{flags}+dot2+dot3+dot4+dot5
+bind \{keys}Dot1+Dot3+Dot6 PASSDOTS\{flags}+dot1+dot3+dot6
+bind \{keys}Dot1+Dot2+Dot3+Dot6 PASSDOTS\{flags}+dot1+dot2+dot3+dot6
+bind \{keys}Dot2+Dot4+Dot5+Dot6 PASSDOTS\{flags}+dot2+dot4+dot5+dot6
+bind \{keys}Dot1+Dot3+Dot4+Dot6 PASSDOTS\{flags}+dot1+dot3+dot4+dot6
+bind \{keys}Dot1+Dot3+Dot4+Dot5+Dot6 PASSDOTS\{flags}+dot1+dot3+dot4+dot5+dot6
+bind \{keys}Dot1+Dot3+Dot5+Dot6 PASSDOTS\{flags}+dot1+dot3+dot5+dot6
diff --git a/Tables/Input/hm/pan.ktb b/Tables/Input/hm/pan.ktb
new file mode 100644
index 0000000..71d6f0c
--- /dev/null
+++ b/Tables/Input/hm/pan.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with two scroll keys)
+
+include common.kti
+include braille.kti
+include f14.kti
+include pan.kti
diff --git a/Tables/Input/hm/pan.kti b/Tables/Input/hm/pan.kti
new file mode 100644
index 0000000..0b917d7
--- /dev/null
+++ b/Tables/Input/hm/pan.kti
@@ -0,0 +1,40 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Backward+Forward LNBEG
+
+bind F1+Backward PRPROMPT
+bind F1+Forward NXPROMPT
+
+bind F2+Backward PRDIFLN
+bind F2+Forward NXDIFLN
+
+bind F3+Backward ATTRUP
+bind F3+Forward ATTRDN
+
+bind F4+Backward PRPGRPH
+bind F4+Forward NXPGRPH
+
+bind F1+F2+Backward TOP_LEFT
+bind F1+F2+Forward BOT_LEFT
+
+bind F3+F4+Backward CHRLT
+bind F3+F4+Forward CHRRT
+
diff --git a/Tables/Input/hm/qwerty.ktb b/Tables/Input/hm/qwerty.ktb
new file mode 100644
index 0000000..9646a18
--- /dev/null
+++ b/Tables/Input/hm/qwerty.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with QWERTY keyboard)
+
+include common.kti
+include scroll.kti
+include qwerty.kti
diff --git a/Tables/Input/hm/qwerty.kti b/Tables/Input/hm/qwerty.kti
new file mode 100644
index 0000000..f1e1cc5
--- /dev/null
+++ b/Tables/Input/hm/qwerty.kti
@@ -0,0 +1,156 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include fnkey.kti
+hide on
+
+bind Dot7 KEY_BACKSPACE
+bind Dot8 KEY_ENTER
+
+bind Space+Dot1+Dot3+Dot6 UPPER
+bind Space+Dot1+Dot3+Dot4 META
+
+bind Space+Dot1+Dot5 KEY_ESCAPE
+bind Space+Dot2+Dot4 KEY_INSERT
+bind Space+Dot1+Dot4+Dot5 KEY_DELETE
+
+bind F2+Dot1+Dot2+Dot3+Dot4+Dot8 KEY_INSERT+control
+bind F1+Dot1+Dot2+Dot3+Dot4+Dot8 KEY_INSERT+shift+control
+
+bind Space+Dot4+Dot5 KEY_TAB
+bind Space+Dot1+Dot2 KEY_TAB+shift
+bind Space+Dot1+Dot2+Dot8 KEY_TAB+control
+bind F2+F3 KEY_TAB+meta
+bind F1+F2+F3 KEY_TAB+shift+meta
+
+bind Space+Dot1+Dot3 KEY_HOME
+bind Space+Dot4+Dot6 KEY_END
+
+bind Space+Dot1+Dot2+Dot3 KEY_HOME+control
+bind Space+Dot4+Dot5+Dot6 KEY_END+control
+
+bind Space+Dot1+Dot2+Dot6 KEY_PAGE_UP
+bind Space+Dot3+Dot4+Dot5 KEY_PAGE_DOWN
+
+bind Space+Dot1+Dot2+Dot6+Dot8 KEY_PAGE_UP+control
+bind Space+Dot3+Dot4+Dot5+Dot8 KEY_PAGE_DOWN+control
+
+bind Space+Dot1 KEY_CURSOR_UP
+bind Space+Dot4 KEY_CURSOR_DOWN
+
+bind Space+RightScrollUp KEY_CURSOR_UP+shift
+bind Space+RightScrollDown KEY_CURSOR_DOWN+shift
+
+bind Space+Dot2+Dot3 KEY_CURSOR_UP+control
+bind Space+Dot5+Dot6 KEY_CURSOR_DOWN+control
+
+bind F1+Space+Dot2+Dot3+Dot8 KEY_CURSOR_UP+shift+control
+bind F1+Space+Dot5+Dot6+Dot8 KEY_CURSOR_DOWN+shift+control
+
+bind Dot2+Dot3+Dot7 KEY_CURSOR_UP+meta
+bind Dot5+Dot6+Dot7 KEY_CURSOR_DOWN+meta
+
+bind F1+Dot2+Dot3+Dot7 KEY_CURSOR_UP+shift+meta
+bind F1+Dot5+Dot6+Dot7 KEY_CURSOR_DOWN+shift+meta
+
+bind Space+Dot3 KEY_CURSOR_LEFT
+bind Space+Dot6 KEY_CURSOR_RIGHT
+
+bind Space+Dot2 KEY_CURSOR_LEFT+control
+bind Space+Dot5 KEY_CURSOR_RIGHT+control
+
+bind F1+Space+Dot2+Dot8 KEY_CURSOR_LEFT+shift+control
+bind F1+Space+Dot5+Dot8 KEY_CURSOR_RIGHT+shift+control
+
+bind Dot2+Dot7 KEY_CURSOR_LEFT+meta
+bind Dot5+Dot7 KEY_CURSOR_RIGHT+meta
+
+bind F1+Dot2+Dot7 KEY_CURSOR_LEFT+shift+meta
+bind F1+Dot5+Dot7 KEY_CURSOR_RIGHT+shift+meta
+
+bind Space+Dot1+Dot2+Dot5 KEY_FUNCTION+0
+bind F4+Space+Dot1+Dot2 KEY_FUNCTION+1
+bind F4+Dot1+Dot2+Dot4+Dot8 KEY_FUNCTION+2
+bind F3+Dot7 KEY_FUNCTION+3
+bind F2+Dot7 KEY_FUNCTION+4
+bind F4+Dot7 KEY_FUNCTION+5
+bind F4+Space KEY_FUNCTION+6
+bind F4+Dot8 KEY_FUNCTION+7
+bind Dot3+Dot4+Dot5+Dot6+Dot7 KEY_FUNCTION+8
+bind Space+Dot1+Dot3+Dot5 KEY_FUNCTION+9
+bind Dot1+Dot4+Dot5+Dot6+Dot7 KEY_FUNCTION+10
+bind F4+Dot1+Dot2+Dot4+Dot5+Dot8 KEY_FUNCTION+11
+
+bind F1+Dot7 KEY_FUNCTION+5+shift
+bind F1+Space KEY_FUNCTION+6+shift
+bind F1+Dot8 KEY_FUNCTION+7+shift
+bind F1+F4 KEY_FUNCTION+9+meta
+
+map Space SPACE
+map Dot1 dot1
+map Dot2 dot2
+map Dot3 dot3
+map Dot4 dot4
+map Dot5 dot5
+map Dot6 dot6
+
+bind Space+Dot7+Dot4 PASSDOTS+dot4
+bind Space+Dot7+Dot2+Dot4+Dot6 PASSDOTS+dot2+dot4+dot6
+bind Space+Dot7+Dot1+Dot2+Dot5+Dot6 PASSDOTS+dot1+dot2+dot5+dot6
+bind Space+Dot7+Dot2+Dot3+Dot4+Dot5+Dot6 PASSDOTS+dot2+dot3+dot4+dot5+dot6
+bind Space+Dot7+Dot4+Dot5 PASSDOTS+dot4+dot5
+
+beginVariables
+assign keys
+assign flags
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Space+Dot7+
+assign flags +shift
+include letters.kti
+endVariables
+
+beginVariables
+assign keys F1+Dot7+
+assign flags +shift+meta
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Dot7+
+# these conflict with shifted letters
+#assign flags +meta
+assign flags +shift
+include letters.kti
+endVariables
+
+beginVariables
+assign keys Dot8+
+assign flags +control
+include letters.kti
+endVariables
+
+beginVariables
+assign keys F1+
+assign flags +gui
+include letters.kti
+endVariables
+
+hide off
diff --git a/Tables/Input/hm/right.kti b/Tables/Input/hm/right.kti
new file mode 100644
index 0000000..6568316
--- /dev/null
+++ b/Tables/Input/hm/right.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RightPadUp PRDIFLN
+bind RightPadDown NXDIFLN
+bind RightPadLeft FWINLTSKIP
+bind RightPadRight FWINRTSKIP
+
+bind RightPadLeft+RightPadUp CONTEXT+CLIP_NEW
+bind RightPadLeft+RightPadDown CONTEXT+CLIP_ADD
+
+bind RightPadRight+RightPadUp CONTEXT+COPY_LINE
+bind RightPadRight+RightPadDown CONTEXT+COPY_RECT
+bind RightPadLeft+RightPadRight PASTE
+
+bind RightPadUp+RightPadDown CONTEXT+DESCCHAR
+
diff --git a/Tables/Input/hm/scroll.ktb b/Tables/Input/hm/scroll.ktb
new file mode 100644
index 0000000..b46b574
--- /dev/null
+++ b/Tables/Input/hm/scroll.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS Braille Sense (with four scroll keys)
+
+include common.kti
+include braille.kti
+include f14.kti
+include scroll.kti
diff --git a/Tables/Input/hm/scroll.kti b/Tables/Input/hm/scroll.kti
new file mode 100644
index 0000000..9ddc091
--- /dev/null
+++ b/Tables/Input/hm/scroll.kti
@@ -0,0 +1,71 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+
+####################
+# Default Bindings #
+####################
+
+bind LeftScrollUp LNUP
+bind LeftScrollDown LNDN
+
+bind RightScrollUp FWINLT
+bind RightScrollDown FWINRT
+
+bind LeftScrollUp+LeftScrollDown LNBEG
+bind RightScrollUp+RightScrollDown RETURN
+
+bind LeftScrollUp+RightScrollUp CSRTRK
+bind LeftScrollUp+RightScrollDown SIXDOTS
+bind LeftScrollDown+RightScrollUp FREEZE
+bind LeftScrollDown+RightScrollDown DISPMD
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp TOP
+bind LeftScrollUp+LeftScrollDown+RightScrollDown BOT
+
+bind RightScrollUp+RightScrollDown+LeftScrollUp INFO
+bind RightScrollUp+RightScrollDown+LeftScrollDown PREFMENU
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp+RightScrollDown HELP
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind LeftScrollUp MENU_PREV_ITEM
+bind LeftScrollDown MENU_NEXT_ITEM
+
+bind RightScrollUp MENU_PREV_SETTING
+bind RightScrollDown MENU_NEXT_SETTING
+
+bind LeftScrollUp+LeftScrollDown FWINLT
+bind RightScrollUp+RightScrollDown FWINRT
+
+bind LeftScrollUp+RightScrollUp MENU_FIRST_ITEM
+bind LeftScrollDown+RightScrollDown MENU_LAST_ITEM
+
+bind LeftScrollUp+LeftScrollDown+RightScrollUp PREFLOAD
+bind LeftScrollUp+LeftScrollDown+RightScrollDown PREFSAVE
+
+bind RightScrollUp+RightScrollDown+LeftScrollUp MENU_PREV_LEVEL
+bind RightScrollUp+RightScrollDown+LeftScrollDown PREFMENU
+
+
diff --git a/Tables/Input/hm/sync.ktb b/Tables/Input/hm/sync.ktb
new file mode 100644
index 0000000..4e65e7d
--- /dev/null
+++ b/Tables/Input/hm/sync.ktb
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HIMS SyncBraille
+
+
+####################
+# Default Bindings #
+####################
+
+include common.kti
+include scroll.kti
diff --git a/Tables/Input/ht/ab.ktb b/Tables/Input/ht/ab.ktb
new file mode 100644
index 0000000..6b484ba
--- /dev/null
+++ b/Tables/Input/ht/ab.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Braille
+
+include ab.kti
diff --git a/Tables/Input/ht/ab.kti b/Tables/Input/ht/ab.kti
new file mode 100644
index 0000000..4f768f6
--- /dev/null
+++ b/Tables/Input/ht/ab.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Active Braille
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include bs.kti
diff --git a/Tables/Input/ht/ab_s.ktb b/Tables/Input/ht/ab_s.ktb
new file mode 100644
index 0000000..162e402
--- /dev/null
+++ b/Tables/Input/ht/ab_s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Braille S
+
+include ab.kti
+include joystick.kti
diff --git a/Tables/Input/ht/ac4.ktb b/Tables/Input/ht/ac4.ktb
new file mode 100644
index 0000000..f404354
--- /dev/null
+++ b/Tables/Input/ht/ac4.ktb
@@ -0,0 +1,52 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Activator
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include joystick.kti
+
+bind SpaceLeft+RoutingKey PRINDENT
+bind SpaceRight+RoutingKey NXINDENT
+
+bind SpaceLeft FWINLT
+bind SpaceRight FWINRT
+bind SpaceLeft+SpaceRight PASTE
+
+bind B1+SpaceLeft LNBEG
+bind B1+SpaceRight LNEND
+bind B2+SpaceLeft TOP
+bind B2+SpaceRight BOT
+bind B3+SpaceLeft HWINLT
+bind B3+SpaceRight HWINRT
+bind B6+SpaceLeft CHRLT
+bind B6+SpaceRight CHRRT
+bind B2+B3+SpaceLeft MUTE
+bind B2+B3+SpaceRight SAY_LINE
+
+include dots.kti
+
+assign brailleOn B1+B8+SpaceRight
+assign brailleOff B1+B8+SpaceLeft
+assign space SpaceLeft
+assign enter SpaceRight
+include input.kti
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables/Input/ht/alo.ktb b/Tables/Input/ht/alo.ktb
new file mode 100644
index 0000000..94140a9
--- /dev/null
+++ b/Tables/Input/ht/alo.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Actilino
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include joystick.kti
+include bs.kti
diff --git a/Tables/Input/ht/as40.ktb b/Tables/Input/ht/as40.ktb
new file mode 100644
index 0000000..ed64fe3
--- /dev/null
+++ b/Tables/Input/ht/as40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Active Star 40
+
+bind B1+B4+SpaceLeft TOUCH_NAV
+
+include bs.kti
diff --git a/Tables/Input/ht/bb.ktb b/Tables/Input/ht/bb.ktb
new file mode 100644
index 0000000..62a4ae2
--- /dev/null
+++ b/Tables/Input/ht/bb.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Basic Braille
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables/Input/ht/bbp.ktb b/Tables/Input/ht/bbp.ktb
new file mode 100644
index 0000000..698451d
--- /dev/null
+++ b/Tables/Input/ht/bbp.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Basic Braille Plus
+
+include bs.kti
diff --git a/Tables/Input/ht/bkwm.ktb b/Tables/Input/ht/bkwm.ktb
new file mode 100644
index 0000000..0668a8a
--- /dev/null
+++ b/Tables/Input/ht/bkwm.ktb
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Bookworm
+
+
+####################
+# Default Bindings #
+####################
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Escape CSRTRK
+bind Escape+Backward BACK
+bind Escape+Forward DISPMD
+bind Enter ROUTE
+bind Enter+Backward LNUP
+bind Enter+Forward LNDN
+bind Escape+Enter PREFMENU
+bind Escape+Enter+Backward LNBEG
+bind Escape+Enter+Forward LNEND
+bind Backward+Forward HELP
+bind Backward+Forward+Escape CSRSIZE
+bind Backward+Forward+Enter FREEZE
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind Backward FWINLT
+bind Forward FWINRT
+bind Escape PREFLOAD
+bind Escape+Backward MENU_PREV_SETTING
+bind Escape+Forward MENU_NEXT_SETTING
+bind Enter PREFMENU
+bind Enter+Backward MENU_PREV_ITEM
+bind Enter+Forward MENU_NEXT_ITEM
+bind Escape+Enter PREFSAVE
+bind Escape+Enter+Backward MENU_FIRST_ITEM
+bind Escape+Enter+Forward MENU_LAST_ITEM
+bind Backward+Forward NOOP
+bind Backward+Forward+Escape NOOP
+bind Backward+Forward+Enter NOOP
diff --git a/Tables/Input/ht/brln.ktb b/Tables/Input/ht/brln.ktb
new file mode 100644
index 0000000..14af612
--- /dev/null
+++ b/Tables/Input/ht/brln.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braillino
+
+include bs.kti
diff --git a/Tables/Input/ht/bs.kti b/Tables/Input/ht/bs.kti
new file mode 100644
index 0000000..67fe489
--- /dev/null
+++ b/Tables/Input/ht/bs.kti
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Braille Star
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind SpaceLeft+RoutingKey PRINDENT
+bind SpaceRight+RoutingKey NXINDENT
+
+bind SpaceLeft FWINLT
+bind SpaceRight FWINRT
+bind SpaceLeft+SpaceRight PASTE
+
+bind B1+SpaceLeft LNBEG
+bind B1+SpaceRight LNEND
+bind B2+SpaceLeft TOP
+bind B2+SpaceRight BOT
+bind B3+SpaceLeft HWINLT
+bind B3+SpaceRight HWINRT
+bind B6+SpaceLeft CHRLT
+bind B6+SpaceRight CHRRT
+bind B2+B3+SpaceLeft MUTE
+bind B2+B3+SpaceRight SAY_LINE
+
+include dots.kti
+include rockers.kti
+
+assign brailleOn B1+B8+SpaceRight
+assign brailleOff B1+B8+SpaceLeft
+assign space SpaceLeft
+assign enter SpaceRight
+include input.kti
diff --git a/Tables/Input/ht/bs40.ktb b/Tables/Input/ht/bs40.ktb
new file mode 100644
index 0000000..75fadfa
--- /dev/null
+++ b/Tables/Input/ht/bs40.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Star 40
+
+include bs.kti
diff --git a/Tables/Input/ht/bs80.ktb b/Tables/Input/ht/bs80.ktb
new file mode 100644
index 0000000..b36515e
--- /dev/null
+++ b/Tables/Input/ht/bs80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Star 80
+
+include bs.kti
+include keypad.kti
diff --git a/Tables/Input/ht/cb40.ktb b/Tables/Input/ht/cb40.ktb
new file mode 100644
index 0000000..d9672d5
--- /dev/null
+++ b/Tables/Input/ht/cb40.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Connect Braille 40
+
+include bs.kti
diff --git a/Tables/Input/ht/dots.kti b/Tables/Input/ht/dots.kti
new file mode 100644
index 0000000..45f2f45
--- /dev/null
+++ b/Tables/Input/ht/dots.kti
@@ -0,0 +1,65 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech braille dot keys
+
+bind B1 HOME
+bind B2 TOP_LEFT
+bind B3 BACK
+bind B4 LNUP
+bind B5 LNDN
+bind B1+B4 PRPGRPH
+bind B1+B5 NXPGRPH
+bind B2+B4 PRPROMPT
+bind B2+B5 NXPROMPT
+bind B3+B4 PRSEARCH
+bind B3+B5 NXSEARCH
+bind B4+B5 LEARN
+bind B6+B4 ATTRUP
+bind B6+B5 ATTRDN
+bind B7+B4 WINUP
+bind B7+B5 WINDN
+bind B8+B4 PRDIFLN
+bind B8+B5 NXDIFLN
+bind B8 HELP
+bind B8+B1 CSRTRK
+bind B8+B2 CSRVIS
+bind B8+B3 ATTRVIS
+bind B8+B6 FREEZE
+bind B8+B7 TUNES
+bind B7 SIXDOTS
+bind B7+B1 PREFMENU
+bind B7+B2 PREFLOAD
+bind B7+B3 PREFSAVE
+bind B7+B6 INFO
+bind B6 DISPMD
+bind B6+B1 SKPIDLNS
+bind B6+B2 SKPBLNKWINS
+bind B6+B3 SLIDEWIN
+
+bind B2+B3+B5+B6 TIME
+
+bind B1+RoutingKey SETLEFT
+bind B2+RoutingKey DESCCHAR
+bind B3+RoutingKey CLIP_ADD
+bind B4+RoutingKey CLIP_NEW
+bind B5+RoutingKey COPY_RECT
+bind B6+RoutingKey COPY_LINE
+bind B7+RoutingKey SETMARK
+bind B8+RoutingKey GOTOMARK
+
diff --git a/Tables/Input/ht/easy.ktb b/Tables/Input/ht/easy.ktb
new file mode 100644
index 0000000..029bb9d
--- /dev/null
+++ b/Tables/Input/ht/easy.ktb
@@ -0,0 +1,43 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Easy Braille
+
+bind Left FWINLT
+bind Right FWINRT
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+include dots.kti
diff --git a/Tables/Input/ht/input.kti b/Tables/Input/ht/input.kti
new file mode 100644
index 0000000..f98f10c
--- /dev/null
+++ b/Tables/Input/ht/input.kti
@@ -0,0 +1,37 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context braille Braille Input Mode
+map B4 DOT1
+map B3 DOT2
+map B2 DOT3
+map B5 DOT4
+map B6 DOT5
+map B7 DOT6
+map B1 DOT7
+map B8 DOT8
+bind \{space} PASSDOTS
+bind \{enter} KEY_ENTER
+bind \{space}+B1 KEY_BACKSPACE
+map \{space} CONTROL
+map \{enter} META
+bind \{brailleOff} CONTEXT+default
+
+context default
+bind \{brailleOn} CONTEXT+braille
+bind \{brailleOff} CONTEXT+default
diff --git a/Tables/Input/ht/joystick.kti b/Tables/Input/ht/joystick.kti
new file mode 100644
index 0000000..74c3f2c
--- /dev/null
+++ b/Tables/Input/ht/joystick.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech: Actilino, Activator
+
+bind Left KEY_CURSOR_LEFT
+bind Right KEY_CURSOR_RIGHT
+bind Up KEY_CURSOR_UP
+bind Down KEY_CURSOR_DOWN
+bind Action KEY_ENTER
+
+
diff --git a/Tables/Input/ht/keypad.kti b/Tables/Input/ht/keypad.kti
new file mode 100644
index 0000000..28e37d6
--- /dev/null
+++ b/Tables/Input/ht/keypad.kti
@@ -0,0 +1,104 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech keypad keys
+note The 16-key pad is arranged in four columns and four rows.
+note * The keys in the first (top) row are named: B9, One, Two, Three.
+note * The keys in the second row are named: B10, Four, Five, Six.
+note * The keys in the third row are named: B11, Seven, Eight, Nine.
+note * The keys in the fourth (bottom) row are named: B12, B13, Zero, B14.
+
+bind B9 SAY_ABOVE
+bind B10 SAY_LINE
+bind B11 SAY_BELOW
+bind B12 MUTE
+bind Zero SPKHOME
+bind B13 SWITCHVT_PREV
+bind B14 SWITCHVT_NEXT
+bind Eight MENU_PREV_ITEM
+bind Nine MENU_FIRST_ITEM
+bind Four MENU_PREV_SETTING
+bind Five PREFSAVE
+bind Six MENU_NEXT_SETTING
+bind One PREFMENU
+bind Two MENU_NEXT_ITEM
+bind Three MENU_LAST_ITEM
+
+bind Zero+Seven KEY_HOME
+bind Zero+Eight KEY_CURSOR_UP
+bind Zero+Nine KEY_PAGE_UP
+bind Zero+Four KEY_CURSOR_LEFT
+bind Zero+Six KEY_CURSOR_RIGHT
+bind Zero+One KEY_END
+bind Zero+Two KEY_CURSOR_DOWN
+bind Zero+Three KEY_PAGE_DOWN
+bind Zero+B13 KEY_INSERT
+bind Zero+B14 KEY_DELETE
+
+bind B9+One SETMARK+0
+bind B9+Two SETMARK+1
+bind B9+Three SETMARK+2
+bind B9+Four SETMARK+3
+bind B9+Five SETMARK+4
+bind B9+Six SETMARK+5
+bind B9+Seven SETMARK+6
+bind B9+Eight SETMARK+7
+bind B9+Nine SETMARK+8
+bind B9+Zero SETMARK+9
+bind B9+B13 SETMARK+10
+bind B9+B14 SETMARK+11
+
+bind B10+One GOTOMARK+0
+bind B10+Two GOTOMARK+1
+bind B10+Three GOTOMARK+2
+bind B10+Four GOTOMARK+3
+bind B10+Five GOTOMARK+4
+bind B10+Six GOTOMARK+5
+bind B10+Seven GOTOMARK+6
+bind B10+Eight GOTOMARK+7
+bind B10+Nine GOTOMARK+8
+bind B10+Zero GOTOMARK+9
+bind B10+B13 GOTOMARK+10
+bind B10+B14 GOTOMARK+11
+
+bind B11+One SWITCHVT+0
+bind B11+Two SWITCHVT+1
+bind B11+Three SWITCHVT+2
+bind B11+Four SWITCHVT+3
+bind B11+Five SWITCHVT+4
+bind B11+Six SWITCHVT+5
+bind B11+Seven SWITCHVT+6
+bind B11+Eight SWITCHVT+7
+bind B11+Nine SWITCHVT+8
+bind B11+Zero SWITCHVT+9
+bind B11+B13 SWITCHVT+10
+bind B11+B14 SWITCHVT+11
+
+bind B12+One KEY_FUNCTION+0
+bind B12+Two KEY_FUNCTION+1
+bind B12+Three KEY_FUNCTION+2
+bind B12+Four KEY_FUNCTION+3
+bind B12+Five KEY_FUNCTION+4
+bind B12+Six KEY_FUNCTION+5
+bind B12+Seven KEY_FUNCTION+6
+bind B12+Eight KEY_FUNCTION+7
+bind B12+Nine KEY_FUNCTION+8
+bind B12+Zero KEY_FUNCTION+9
+bind B12+B13 KEY_FUNCTION+10
+bind B12+B14 KEY_FUNCTION+11
+
diff --git a/Tables/Input/ht/mc88.ktb b/Tables/Input/ht/mc88.ktb
new file mode 100644
index 0000000..495a431
--- /dev/null
+++ b/Tables/Input/ht/mc88.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Connect 88
+
+include me.kti
+include keypad.kti
diff --git a/Tables/Input/ht/mdlr.ktb b/Tables/Input/ht/mdlr.ktb
new file mode 100644
index 0000000..cb1b25d
--- /dev/null
+++ b/Tables/Input/ht/mdlr.ktb
@@ -0,0 +1,48 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left FWINLT
+bind Right FWINRT
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+bind Status1 HELP
+bind Status2 PREFMENU
+bind Status3 INFO
+bind Status4 FREEZE
+
+include dots.kti
+include keypad.kti
diff --git a/Tables/Input/ht/me.kti b/Tables/Input/ht/me.kti
new file mode 100644
index 0000000..896b457
--- /dev/null
+++ b/Tables/Input/ht/me.kti
@@ -0,0 +1,46 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech Modular Evolution
+
+bind Left FWINLT
+bind Right FWINRT
+include rockers.kti
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+bind B1+Left LNBEG
+bind B1+Right LNEND
+bind B2+Left TOP
+bind B2+Right BOT
+bind B3+Left HWINLT
+bind B3+Right HWINRT
+bind B6+Left CHRLT
+bind B6+Right CHRRT
+bind B2+B3+Left MUTE
+bind B2+B3+Right SAY_LINE
+
+bind B1+B4+Left TOUCH_NAV
+
+include dots.kti
diff --git a/Tables/Input/ht/me64.ktb b/Tables/Input/ht/me64.ktb
new file mode 100644
index 0000000..e75f4eb
--- /dev/null
+++ b/Tables/Input/ht/me64.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Evolution 64
+
+include me.kti
diff --git a/Tables/Input/ht/me88.ktb b/Tables/Input/ht/me88.ktb
new file mode 100644
index 0000000..ef1b579
--- /dev/null
+++ b/Tables/Input/ht/me88.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Modular Evolution 88
+
+include me.kti
+include keypad.kti
diff --git a/Tables/Input/ht/rockers.kti b/Tables/Input/ht/rockers.kti
new file mode 100644
index 0000000..cff2410
--- /dev/null
+++ b/Tables/Input/ht/rockers.kti
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# Key subtable for HandyTech rocker keys
+
+bind LeftRockerTop KEY_CURSOR_UP
+bind LeftRockerBottom KEY_CURSOR_DOWN
+bind RightRockerTop LNUP
+bind RightRockerBottom LNDN
+bind LeftRockerTop+LeftRockerBottom FWINLT
+bind RightRockerTop+RightRockerBottom FWINRT
+bind LeftRockerTop+LeftRockerBottom+RightRockerTop+RightRockerBottom HOME
+bind LeftRockerTop+LeftRockerBottom+B3 HOME
+bind RightRockerTop+RightRockerBottom+B6 HOME
+bind RightRockerTop+RightRockerBottom+LeftRockerTop TOP_LEFT
+bind RightRockerTop+RightRockerBottom+B5 TOP_LEFT
+bind LeftRockerTop+B3 TOP_LEFT
+bind RightRockerTop+RightRockerBottom+LeftRockerBottom BOT_LEFT
+bind RightRockerTop+RightRockerBottom+B7 BOT_LEFT
+bind LeftRockerBottom+B3 BOT_LEFT
+bind LeftRockerTop+LeftRockerBottom+RightRockerTop TOP
+bind LeftRockerTop+LeftRockerBottom+B4 TOP
+bind RightRockerTop+B6 TOP
+bind LeftRockerTop+LeftRockerBottom+RightRockerBottom BOT
+bind LeftRockerTop+LeftRockerBottom+B2 BOT
+bind RightRockerBottom+B6 BOT
+bind LeftRockerTop+RightRockerTop PRDIFLN
+bind LeftRockerTop+B4 PRDIFLN
+bind RightRockerTop+B5 PRDIFLN
+bind LeftRockerTop+RightRockerBottom NXDIFLN
+bind LeftRockerTop+B2 NXDIFLN
+bind RightRockerBottom+B5 NXDIFLN
+bind LeftRockerBottom+RightRockerTop ATTRUP
+bind LeftRockerBottom+B4 ATTRUP
+bind RightRockerTop+B7 ATTRUP
+bind LeftRockerBottom+RightRockerBottom ATTRDN
+bind LeftRockerBottom+B2 ATTRDN
+bind RightRockerBottom+B7 ATTRDN
+
+bind LeftRockerTop+RoutingKey CLIP_NEW
+bind LeftRockerTop+LeftRockerBottom+RoutingKey KEY_FUNCTION
+bind LeftRockerBottom+RoutingKey CLIP_ADD
+bind RightRockerTop+RoutingKey COPY_LINE
+bind RightRockerTop+RightRockerBottom+RoutingKey SWITCHVT
+bind RightRockerBottom+RoutingKey COPY_RECT
+
+
+context menu
+bind RightRockerTop MENU_PREV_ITEM
+bind RightRockerBottom MENU_NEXT_ITEM
+bind LeftRockerTop MENU_PREV_SETTING
+bind LeftRockerBottom MENU_NEXT_SETTING
+
diff --git a/Tables/Input/ht/wave.ktb b/Tables/Input/ht/wave.ktb
new file mode 100644
index 0000000..c60e663
--- /dev/null
+++ b/Tables/Input/ht/wave.ktb
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HandyTech Braille Wave
+
+bind Left FWINLT
+bind Right FWINRT
+
+bind RoutingKey ROUTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+
+bind Left+RoutingKey PRINDENT
+bind Right+RoutingKey NXINDENT
+
+bind Left+Right PASTE
+
+include dots.kti
+
+bind Escape+Space+Return INFO
+
+assign brailleOn B1+B8+Right
+assign brailleOff B1+B8+Left
+assign space Space
+assign enter Return
+include input.kti
+
diff --git a/Tables/Input/hw/B80.ktb b/Tables/Input/hw/B80.ktb
new file mode 100644
index 0000000..61bb205
--- /dev/null
+++ b/Tables/Input/hw/B80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant B 80
+
+include thumb.kti
+include command.kti
diff --git a/Tables/Input/hw/BI14.ktb b/Tables/Input/hw/BI14.ktb
new file mode 100644
index 0000000..e1f2a03
--- /dev/null
+++ b/Tables/Input/hw/BI14.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 14
+
+include thumb.kti
+include braille.kti
+include joystick.kti
+include routing.kti
diff --git a/Tables/Input/hw/BI20X.ktb b/Tables/Input/hw/BI20X.ktb
new file mode 100644
index 0000000..24e7107
--- /dev/null
+++ b/Tables/Input/hw/BI20X.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 20X
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/hw/BI32.ktb b/Tables/Input/hw/BI32.ktb
new file mode 100644
index 0000000..6d54540
--- /dev/null
+++ b/Tables/Input/hw/BI32.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 32
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables/Input/hw/BI40.ktb b/Tables/Input/hw/BI40.ktb
new file mode 100644
index 0000000..b06acbf
--- /dev/null
+++ b/Tables/Input/hw/BI40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 40
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables/Input/hw/BI40X.ktb b/Tables/Input/hw/BI40X.ktb
new file mode 100644
index 0000000..757ffcb
--- /dev/null
+++ b/Tables/Input/hw/BI40X.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare Brailliant BI 40X
+
+include thumb.kti
+include braille.kti
+include command.kti
diff --git a/Tables/Input/hw/C20.ktb b/Tables/Input/hw/C20.ktb
new file mode 100644
index 0000000..706ac28
--- /dev/null
+++ b/Tables/Input/hw/C20.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Chameleon 20
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/hw/M40.ktb b/Tables/Input/hw/M40.ktb
new file mode 100644
index 0000000..c5e5be9
--- /dev/null
+++ b/Tables/Input/hw/M40.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title APH Mantis Q40
+
+include thumb.kti
+include routing.kti
diff --git a/Tables/Input/hw/NLS.ktb b/Tables/Input/hw/NLS.ktb
new file mode 100644
index 0000000..c1a41aa
--- /dev/null
+++ b/Tables/Input/hw/NLS.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NLS eReader
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/hw/braille.kti b/Tables/Input/hw/braille.kti
new file mode 100644
index 0000000..d735dbb
--- /dev/null
+++ b/Tables/Input/hw/braille.kti
@@ -0,0 +1,41 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There's an eight-key braille keyboard near the back of the top.
+note * From left to right, its keys are: Dot7, Dot3, Dot2, Dot1, Dot4, Dot5, Dot6, Dot8.
+
+assign chord Space+
+include ../chords.kti
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+map ThumbLeft META
+map ThumbRight CONTROL
+
+bind Space+RoutingKey KEY_FUNCTION
+bind ThumbLeft+Space+RoutingKey KEY_FUNCTION+meta
+bind ThumbRight+Space+RoutingKey KEY_FUNCTION+control
+bind ThumbLeft+ThumbRight+Space+RoutingKey KEY_FUNCTION+meta+control
+
diff --git a/Tables/Input/hw/command.kti b/Tables/Input/hw/command.kti
new file mode 100644
index 0000000..00029ec
--- /dev/null
+++ b/Tables/Input/hw/command.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+include ../bm/display6.kti
+include ../bm/routing6.kti
diff --git a/Tables/Input/hw/joystick.kti b/Tables/Input/hw/joystick.kti
new file mode 100644
index 0000000..acf8a44
--- /dev/null
+++ b/Tables/Input/hw/joystick.kti
@@ -0,0 +1,82 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There's a five-way joystick between the Dot1 and Dot4 keys.
+note * The four directions are intuitively named: Up, Down, Left, Right.
+note * Pressing the joystick is named: Action.
+
+bind Up LNUP
+bind Down LNDN
+bind Left FWINLT
+bind Right FWINRT
+bind Action HOME
+
+bind Dot1+Up TOP
+bind Dot1+Down BOT
+bind Dot1+Left LNBEG
+bind Dot1+Right LNEND
+bind Dot1+Action BACK
+
+bind Dot2+Up KEY_CURSOR_UP
+bind Dot2+Down KEY_CURSOR_DOWN
+bind Dot2+Left KEY_CURSOR_LEFT
+bind Dot2+Right KEY_CURSOR_RIGHT
+bind Dot2+Action KEY_DELETE
+
+bind Dot3+Up KEY_PAGE_UP
+bind Dot3+Down KEY_PAGE_DOWN
+bind Dot3+Left KEY_HOME
+bind Dot3+Right KEY_END
+bind Dot3+Action KEY_INSERT
+
+bind Dot4+Up PRPROMPT
+bind Dot4+Down NXPROMPT
+bind Dot4+Left PRPGRPH
+bind Dot4+Right NXPGRPH
+bind Dot4+Action CSRTRK
+
+bind Dot5+Up PRDIFLN
+bind Dot5+Down NXDIFLN
+bind Dot5+Left FWINLTSKIP
+bind Dot5+Right FWINRTSKIP
+bind Dot5+Action CSRVIS
+
+bind Dot6+Up ATTRUP
+bind Dot6+Down ATTRDN
+bind Dot6+Left CHRLT
+bind Dot6+Right CHRRT
+bind Dot6+Action ATTRVIS
+
+bind Dot7+Up SAY_ABOVE
+bind Dot7+Down SAY_BELOW
+bind Dot7+Left MUTE
+bind Dot7+Right SAY_LINE
+bind Dot7+Action AUTOSPEAK
+
+bind Dot8+Up SAY_LOUDER
+bind Dot8+Down SAY_SOFTER
+bind Dot8+Left SAY_SLOWER
+bind Dot8+Right SAY_FASTER
+bind Dot8+Action SPKHOME
+
+bind RoutingKey+Up PRINDENT
+bind RoutingKey+Down NXINDENT
+bind RoutingKey+Left PRDIFCHAR
+bind RoutingKey+Right NXDIFCHAR
+bind RoutingKey+Action DESCCHAR
+
diff --git a/Tables/Input/hw/one.ktb b/Tables/Input/hw/one.ktb
new file mode 100644
index 0000000..de00c27
--- /dev/null
+++ b/Tables/Input/hw/one.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare BrailleOne
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/hw/routing.kti b/Tables/Input/hw/routing.kti
new file mode 100644
index 0000000..f88bc47
--- /dev/null
+++ b/Tables/Input/hw/routing.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note RoutingKey refers to any of the keys immediately behind the braille cells.
+
+bind RoutingKey ROUTE
diff --git a/Tables/Input/hw/thumb.kti b/Tables/Input/hw/thumb.kti
new file mode 100644
index 0000000..dae2186
--- /dev/null
+++ b/Tables/Input/hw/thumb.kti
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assignDefault previous ThumbPrevious
+assignDefault left ThumbLeft
+assignDefault right ThumbRight
+assignDefault next ThumbNext
+
+note There are four rectangular thumb keys on the front. From left to right:
+note * The outer ones are named ThumbPrevious and ThumbNext.
+note * The inner ones are named ThumbLeft and ThumbRight.
+note * Some models have a round key in the middle - it's the internal menu key.
+
+####################
+# Default Bindings #
+####################
+
+bind ThumbLeft+ThumbRight HOME
+bind \{left} FWINLT
+bind \{right} FWINRT
+bind \{previous} LNUP
+bind \{next} LNDN
+
+bind \{left}+\{previous} TOP_LEFT
+bind \{left}+\{next} BOT_LEFT
+bind \{right}+\{previous} PRDIFLN
+bind \{right}+\{next} NXDIFLN
+
+bind ThumbPrevious+RoutingKey CLIP_NEW
+bind ThumbLeft+RoutingKey CLIP_ADD
+bind \{right}+RoutingKey COPY_LINE
+bind \{next}+RoutingKey COPY_RECT
+bind ThumbPrevious+ThumbNext PASTE
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind \{left} FWINLT
+bind \{right} FWINRT
+bind \{previous} MENU_PREV_ITEM
+bind \{next} MENU_NEXT_ITEM
+bind \{left}+\{previous} MENU_FIRST_ITEM
+bind \{left}+\{next} MENU_LAST_ITEM
+bind \{right}+\{previous} MENU_PREV_SETTING
+bind \{right}+\{next} MENU_NEXT_SETTING
+bind ThumbLeft+ThumbRight PREFMENU
+bind ThumbLeft+ThumbRight+ThumbPrevious PREFLOAD
+bind ThumbLeft+ThumbRight+ThumbNext PREFSAVE
+
diff --git a/Tables/Input/hw/thumb_legacy.kti b/Tables/Input/hw/thumb_legacy.kti
new file mode 100644
index 0000000..c4b0cf2
--- /dev/null
+++ b/Tables/Input/hw/thumb_legacy.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# If you'd like to have the old (pre-6.4) thumb key bindings then
+# copy this file into the user customizations directory like this:
+# cp thumb_legacy.kti /etc/xdg/brltty/thumb.kti
+
+assign previous ThumbLeft
+assign left ThumbPrevious
+assign right ThumbNext
+assign next ThumbRight
+include thumb.kti
diff --git a/Tables/Input/hw/touch.ktb b/Tables/Input/hw/touch.ktb
new file mode 100644
index 0000000..60e94f4
--- /dev/null
+++ b/Tables/Input/hw/touch.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title HumanWare BrailleNote Touch
+
+include thumb.kti
+include braille.kti
+include routing.kti
diff --git a/Tables/Input/ic/bb.ktb b/Tables/Input/ic/bb.ktb
new file mode 100644
index 0000000..62fd55a
--- /dev/null
+++ b/Tables/Input/ic/bb.ktb
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title BrailleMe (in BrailleBack mode)
+
+include common.kti
+include chords.kti
+include route.kti
+
+bind Space+Dot1 TOP
+bind Space+Dot4 BOT
+
+bind Space+Dot4+Dot3 LNBEG
+bind Space+Dot4+Dot6 LNEND
+
+bind Space+Dot1+Dot2 CONTEXT+chords
+bind Space+Dot2+Dot4 RETURN
+bind Space+Dot1+Dot2+Dot3+Dot5 CONTEXT+routingKeys
+
+bind Dot8+Dot1+Dot2+Dot4 CSRTRK
+bind Dot8+Dot1+Dot2+Dot3 HELP
+bind Dot8+Dot2+Dot3+Dot4 PREFMENU
+
diff --git a/Tables/Input/ic/chords.kti b/Tables/Input/ic/chords.kti
new file mode 100644
index 0000000..48a661c
--- /dev/null
+++ b/Tables/Input/ic/chords.kti
@@ -0,0 +1,126 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context chords
+bind Dot7 CONTEXT+toggleOff
+bind Dot8 CONTEXT+toggleOn
+
+bind Dot1+Dot2+Dot5 HELP
+bind Dot1+Dot2+Dot3 LEARN
+bind Dot2+Dot3+Dot4 INFO
+   
+bind Dot1+Dot2+Dot3+Dot4 PREFMENU
+bind Dot1+Dot2+Dot3+Dot6 CSRJMP_VERT
+bind Dot1+Dot3+Dot4+Dot6 PASTE
+
+bind Dot2+Dot3+Dot5 SIXDOTS+on
+bind Dot2+Dot3+Dot6 SIXDOTS+off
+
+beginVariables
+assign toggleKeys Dot1+Dot2
+assign toggleCommand SKPBLNKWINS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot1+Dot4
+assign toggleCommand CSRVIS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot1+Dot4+Dot5
+assign toggleCommand DISPMD
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot1+Dot2+Dot4
+assign toggleCommand FREEZE
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot2+Dot4
+assign toggleCommand SKPIDLNS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot1+Dot2+Dot3+Dot5
+assign toggleCommand AUTOREPEAT
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot2+Dot3+Dot4+Dot5
+assign toggleCommand CSRTRK
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot1+Dot3+Dot6
+assign toggleCommand ATTRVIS
+include toggle.kti
+endVariables
+
+beginVariables
+assign toggleKeys Dot2+Dot4+Dot5+Dot6
+assign toggleCommand SLIDEWIN
+include toggle.kti
+endVariables
+
+bind Dot3 KEY_CURSOR_LEFT
+bind Dot6 KEY_CURSOR_RIGHT
+bind Dot2 KEY_HOME
+bind Dot5 KEY_END
+bind Dot1 KEY_CURSOR_UP
+bind Dot4 KEY_CURSOR_DOWN
+
+bind Dot2+Dot3 KEY_PAGE_UP
+bind Dot5+Dot6 KEY_PAGE_DOWN
+bind Dot4+Dot5 KEY_TAB
+
+bind Dot2+Dot5+Dot6 KEY_DELETE
+bind Dot2+Dot6 KEY_ESCAPE
+bind Dot3+Dot5 KEY_INSERT
+
+context toggleOff
+bind Dot1+Dot2+Dot3+Dot4 PREFLOAD
+bind Dot1+Dot2+Dot3+Dot6 SWITCHVT_PREV
+bind Dot1+Dot3+Dot4+Dot6 CLIP_RESTORE
+
+bind Dot1+Dot3 BRLKBD+off
+bind Dot4+Dot6 BRLUCDOTS+off
+
+context toggleOn
+bind Dot1+Dot2+Dot3+Dot4 PREFSAVE
+bind Dot1+Dot2+Dot3+Dot6 SWITCHVT_NEXT
+bind Dot1+Dot3+Dot4+Dot6 CLIP_SAVE
+
+bind Dot1+Dot3 BRLKBD+on
+bind Dot4+Dot6 BRLUCDOTS+on
+
+bind Dot1 GUI
+bind Dot2 META
+bind Dot3 CONTROL
+bind Dot4 SHIFT
+bind Dot5 ALTGR
+bind Dot6 UPPER
+bind Dot7 UNSTICK
+
diff --git a/Tables/Input/ic/common.kti b/Tables/Input/ic/common.kti
new file mode 100644
index 0000000..cf1d6a4
--- /dev/null
+++ b/Tables/Input/ic/common.kti
@@ -0,0 +1,51 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The three keys along the front edge of the top are: Dot7, Space, Dot8.
+note Behind them is a six-dot braille keyboard.
+note * From left to right, they are: Dot3, Dot2, Dot1, Dot4, Dot5, Dot6.
+note * There's a gap the width of the spacebar between the Dot1 and Dot4 keys.
+note Behind the braille keyboard is the row of cursor routing keys,
+note * and behind it are the braille cells.
+note To the left of the braille cells are the MoveUp and MoveDown keys,
+note * and to their right are the PanLeft and PanRight keys.
+note * MoveUp is behind MoveDown, and PanLeft is behind PanRight.
+note * The Move and Pan key pairs can be swapped via the internal Settings menu.
+
+map Space SPACE
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+bind Dot7 KEY_BACKSPACE
+bind Dot8 KEY_ENTER
+
+bind MoveUp LNUP
+bind MoveDown LNDN
+
+bind PanLeft FWINLT
+bind PanRight FWINRT
+
+bind RoutingKey ROUTE
+
+include ../menu.kti
diff --git a/Tables/Input/ic/nvda.ktb b/Tables/Input/ic/nvda.ktb
new file mode 100644
index 0000000..6e46ee3
--- /dev/null
+++ b/Tables/Input/ic/nvda.ktb
@@ -0,0 +1,72 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title BrailleMe (in NVDA mode)
+
+include common.kti
+include chords.kti
+include route.kti
+
+bind Space+MoveUp PRDIFLN
+bind Space+MoveDown NXDIFLN
+
+bind Dot8+MoveUp TOP
+bind Dot8+MoveDown BOT
+
+bind Dot8+Dot1 LNBEG
+bind Dot8+Dot4 LNEND
+
+bind Space+PanLeft FWINLTSKIP
+bind Space+PanRight FWINRTSKIP
+
+bind Space+Dot3 PRNBWIN
+bind Space+Dot6 NXNBWIN
+
+bind Space+Dot2+Dot3 PRPROMPT
+bind Space+Dot5+Dot6 NXPROMPT
+
+bind Space+Dot1+Dot2+Dot3 PRPGRPH
+bind Space+Dot4+Dot5+Dot6 NXPGRPH
+
+bind Space+Dot1+Dot4+Dot5+Dot6 PRSEARCH
+bind Space+Dot3+Dot4+Dot5+Dot6 NXSEARCH
+
+bind Space+Dot1+Dot2 CONTEXT+chords
+bind Space+Dot1+Dot4+Dot5 NOOP
+bind Space+Dot1+Dot5 NOOP
+bind Space+Dot1+Dot2+Dot4 FREEZE
+bind Space+Dot1+Dot2+Dot5 HELP
+bind Space+Dot1+Dot3+Dot4+Dot5 NOOP
+bind Space+Dot1+Dot2+Dot3+Dot5 CONTEXT+routingKeys
+bind Space+Dot2+Dot3+Dot4 NOOP
+bind Space+Dot2+Dot3+Dot4+Dot5 CSRTRK
+bind Space+Dot2+Dot4+Dot5+Dot6 NOOP
+
+bind Space+Dot1+Dot2+Dot5+Dot6 CSRJMP_VERT
+bind Space+Dot2+Dot5 RETURN
+bind Space+Dot4 UPPER
+bind Space+Dot4+Dot6 CONTROL
+bind Space+Dot8 TIME
+
+bind Dot7+Dot6 PREFMENU
+
+bind Dot8+RoutingKey.20 CSRVIS
+bind Dot8+RoutingKey.19 CSRSIZE
+bind Dot8+RoutingKey.18 CSRBLINK
+bind Dot8+RoutingKey.17 CAPBLINK
+
diff --git a/Tables/Input/ic/route.kti b/Tables/Input/ic/route.kti
new file mode 100644
index 0000000..98ba269
--- /dev/null
+++ b/Tables/Input/ic/route.kti
@@ -0,0 +1,75 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context routingKeys
+bind RoutingKey KEY_FUNCTION
+
+bind Dot1 CONTEXT+clipNew
+bind Dot2 CONTEXT+clipAdd
+bind Dot4 CONTEXT+copyLinear
+bind Dot5 CONTEXT+copyRectangular
+bind Dot3 CONTEXT+setLeft
+bind Dot6 CONTEXT+describeCharacter
+bind Dot7 CONTEXT+setMark
+bind Dot8 CONTEXT+goToMark
+bind Space CONTEXT+switchVirtualTerminal
+
+bind MoveUp CONTEXT+upSameIndent
+bind MoveDown CONTEXT+downSameIndent
+bind PanLeft CONTEXT+upDifferentCharacter
+bind PanRight CONTEXT+downDifferentCharacter
+
+context clipNew
+bind RoutingKey CLIP_NEW
+
+context clipAdd
+bind RoutingKey CLIP_ADD
+
+context copyLinear
+bind RoutingKey COPY_LINE
+
+context copyRectangular
+bind RoutingKey COPY_RECT
+
+context setLeft
+bind RoutingKey SETLEFT
+
+context describeCharacter
+bind RoutingKey DESCCHAR
+
+context setMark
+bind RoutingKey SETMARK
+
+context goToMark
+bind RoutingKey GOTOMARK
+
+context switchVirtualTerminal
+bind RoutingKey SWITCHVT
+
+context upSameIndent
+bind RoutingKey PRINDENT
+
+context downSameIndent
+bind RoutingKey NXINDENT
+
+context upDifferentCharacter
+bind RoutingKey PRDIFCHAR
+
+context downDifferentCharacter
+bind RoutingKey NXDIFCHAR
+
diff --git a/Tables/Input/ic/toggle.kti b/Tables/Input/ic/toggle.kti
new file mode 100644
index 0000000..7d008ab
--- /dev/null
+++ b/Tables/Input/ic/toggle.kti
@@ -0,0 +1,27 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context chords
+bind \{toggleKeys} \{toggleCommand}
+
+context toggleOff
+bind \{toggleKeys} \{toggleCommand}+off
+
+context toggleOn
+bind \{toggleKeys} \{toggleCommand}+on
+
diff --git a/Tables/Input/ir/all.kti b/Tables/Input/ir/all.kti
new file mode 100644
index 0000000..b3e5e7a
--- /dev/null
+++ b/Tables/Input/ir/all.kti
@@ -0,0 +1,39 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for bindings common to all Iris displays.
+note RoutingKey refers to any of the keys behind the text cells.
+
+bind \{Dot1}+\{Dot2}+\{Dot4} FREEZE
+bind \{Dot1}+\{Dot2}+\{Dot5} HELP
+bind \{Dot2}+\{Dot4} INFO
+bind \{Dot1}+\{Dot2}+\{Dot3} LEARN
+bind \{Dot1}+\{Dot2}+\{Dot3}+\{Dot4} PREFMENU
+bind \{Dot2}+\{Dot3}+\{Dot5} SIXDOTS+on
+bind \{Dot2}+\{Dot3}+\{Dot6} SIXDOTS+off
+
+bind !RoutingKey ROUTE
+bind L1+!RoutingKey CLIP_NEW
+bind L2+!RoutingKey CLIP_ADD
+bind L8+!RoutingKey COPY_LINE
+bind L7+!RoutingKey COPY_RECT
+bind L3+!RoutingKey SETLEFT
+bind L6+!RoutingKey DESCCHAR
+
+context PASTE_HISTORY
+bind RoutingKey PASTE_HISTORY
diff --git a/Tables/Input/ir/brl.ktb b/Tables/Input/ir/brl.ktb
new file mode 100644
index 0000000..8c63172
--- /dev/null
+++ b/Tables/Input/ir/brl.ktb
@@ -0,0 +1,114 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Iris (with braille keyboard)
+
+note The braille keyboard has two thumb keys rather than a single space bar.
+note * The left one is named Backspace, and the right one is named Space.
+note The eight keys behind the braille keyboard are known as linear keys.
+note * From left to right, they're named L1 through L8.
+
+assign Dot1 L4
+assign Dot2 L3
+assign Dot3 L2
+assign Dot4 L5
+assign Dot5 L6
+assign Dot6 L7
+assign Dot7 L1
+assign Dot8 L8
+include all.kti
+
+####################
+# Default Bindings #
+####################
+
+bind L1 FWINLT
+bind L2 LNUP
+bind L3 PRPROMPT
+bind L4 PREFMENU
+bind L5 INFO
+bind L6 NXPROMPT
+bind L7 LNDN
+bind L8 FWINRT
+bind L1+L2 TOP
+bind L7+L8 BOT
+bind L2+L3 CSRVIS
+bind L6+L7 TUNES
+bind L6+L8 RESTARTSPEECH
+bind L5+L7 PASTE
+bind L5+L6+L7 CONTEXT+PASTE_HISTORY
+
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Space SPACE
+map L4 CONTROL
+map L5 META
+map L3 UPPER
+
+bind Backspace KEY_BACKSPACE
+bind Backspace+Space KEY_ENTER
+
+bind Space+Dot2 KEY_CURSOR_LEFT
+bind Space+Dot5 KEY_CURSOR_RIGHT
+bind Space+Dot4 KEY_CURSOR_UP
+bind Space+Dot6 KEY_CURSOR_DOWN
+bind Space+Dot4+Dot5 KEY_PAGE_UP
+bind Space+Dot5+Dot6 KEY_PAGE_DOWN
+bind Space+Dot1 KEY_HOME
+bind Space+Dot3 KEY_END
+
+bind Space+Dot1+Dot4+Dot5 KEY_DELETE
+bind Space+Dot1+Dot5 KEY_ESCAPE
+bind Space+Dot2+Dot4 KEY_INSERT
+bind Space+Dot2+Dot3+Dot4+Dot5 KEY_TAB
+
+bind Backspace+Dot1 KEY_FUNCTION+0
+bind Backspace+Dot1+Dot2 KEY_FUNCTION+1
+bind Backspace+Dot1+Dot4 KEY_FUNCTION+2
+bind Backspace+Dot1+Dot4+Dot5 KEY_FUNCTION+3
+bind Backspace+Dot1+Dot5 KEY_FUNCTION+4
+bind Backspace+Dot1+Dot2+Dot4 KEY_FUNCTION+5
+bind Backspace+Dot1+Dot2+Dot4+Dot5 KEY_FUNCTION+6
+bind Backspace+Dot1+Dot2+Dot5 KEY_FUNCTION+7
+bind Backspace+Dot2+Dot4 KEY_FUNCTION+8
+bind Backspace+Dot2+Dot4+Dot5 KEY_FUNCTION+9
+bind Backspace+Dot1+Dot3 KEY_FUNCTION+10
+bind Backspace+Dot1+Dot2+Dot3 KEY_FUNCTION+11
+
+bind Backspace+Dot2 SWITCHVT+0
+bind Backspace+Dot2+Dot3 SWITCHVT+1
+bind Backspace+Dot2+Dot5 SWITCHVT+2
+bind Backspace+Dot2+Dot5+Dot6 SWITCHVT+3
+bind Backspace+Dot2+Dot6 SWITCHVT+4
+bind Backspace+Dot2+Dot3+Dot5 SWITCHVT+5
+bind Backspace+Dot2+Dot3+Dot5+Dot6 SWITCHVT+6
+bind Backspace+Dot2+Dot3+Dot6 SWITCHVT+7
+bind Backspace+Dot3+Dot5 SWITCHVT+8
+bind Backspace+Dot3+Dot5+Dot6 SWITCHVT+9
+bind Backspace+Dot2+Dot7 SWITCHVT+10
+bind Backspace+Dot2+Dot3+Dot7 SWITCHVT+11
+bind Backspace+Dot3 SWITCHVT_PREV
+bind Backspace+Dot6 SWITCHVT_NEXT
+
diff --git a/Tables/Input/ir/pc.ktb b/Tables/Input/ir/pc.ktb
new file mode 100644
index 0000000..5316335
--- /dev/null
+++ b/Tables/Input/ir/pc.ktb
@@ -0,0 +1,54 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Iris (with PC keyboard)
+
+note The eight extra keys at the ends of the keyboard are known as linear keys.
+note * The four at the left, from top to bottom, are named L1 through L4.
+note * The four at the right, from bottom to top, are named L5 through L8.
+
+assign Dot1 L1
+assign Dot2 L2
+assign Dot3 L3
+assign Dot4 L8
+assign Dot5 L7
+assign Dot6 L6
+assign Dot7 L4
+assign Dot8 L5
+include all.kti
+
+bind L1 FWINLTSKIP
+bind L2+L3 PASTE
+bind L2+L3+L4 CONTEXT+PASTE_HISTORY
+bind L4 CSRVIS
+bind L8 FWINRTSKIP
+bind L7 TOP_LEFT
+bind L6 BOT_LEFT
+bind L7+L6 HOME
+bind L1+L2 TUNES
+bind L4+L5 SIXDOTS
+
+bind !Xt PASSXT
+bind !XtE0 PASSXT+emul0
+bind !XtE1 PASSXT+emul1
+
+# The XT key numbers need to be 1 greater than the actual XT key codes.
+bind L5+!XtE0.0X4C FWINLT
+bind L5+!XtE0.0X4E FWINRT
+bind L5+!XtE0.0X49 LNUP
+bind L5+!XtE0.0X51 LNDN
diff --git a/Tables/Input/lb/all.txt b/Tables/Input/lb/all.txt
new file mode 100644
index 0000000..3bdf3e0
--- /dev/null
+++ b/Tables/Input/lb/all.txt
@@ -0,0 +1,3 @@
+Help: Libbraille (wrapper)
+
+No help available for this driver.
diff --git a/Tables/Input/lt/all.txt b/Tables/Input/lt/all.txt
new file mode 100644
index 0000000..6698cda
--- /dev/null
+++ b/Tables/Input/lt/all.txt
@@ -0,0 +1,35 @@
+Help: LogText (Danish)
+
+Specielle kommandoer til brltty startes med dot 37,
+efterfulgt af nedenstående koder.
+Fx. 37 12357 for genstart af brltty.
+
+124: f Frys skærm
+1247: F Optø skærm
+
+368: - Forrige virtuelle konsol
+2358: + Næste virtuelle konsol
+18: 1 1. virtuelle konsol
+128: 2 2. virtuelle konsol
+148: 3 3. virtuelle konsol
+1458: 4 4. virtuelle konsol
+158: 5 5. virtuelle konsol
+1248: 6 6. virtuelle konsol
+
+Download data fra computer til LogText:
+1: Tast dot 37 og derefter et stort D (dot 1457)
+2: Skriv stien til filen der skal downloades og tast enter
+3: Tast Ctrl+5 på LogTexten og tast på enter 2 gange
+
+247: I Information om brltty
+12357: R Genstart brltty
+1457: D Download menu
+
+12347: P Opsætning
+2347: S Gem opsætning
+1237: L Hent opsætning
+
+136: u Side op (page up)
+145: d Side ned (page down)
+
+LogText is only sold in Denmark. Help only in Danish.
diff --git a/Tables/Input/mb/all.txt b/Tables/Input/mb/all.txt
new file mode 100644
index 0000000..ddc747f
--- /dev/null
+++ b/Tables/Input/mb/all.txt
@@ -0,0 +1,55 @@
+Help: MultiBraille
+
+Movement keys:
+321          top of screen
+    456      bottom of screen
+ 21          up several lines
+    45       down several lines
+  1     (B)  up one line
+    4   (D)  down one line
+3     6 (CC) cursor position
+32           beginning of line
+     56      end of line
+3 1          left one character
+    4 6      right one character
+ 2  4 6      left one half window
+3 1  5       right one half window
+3       (A)  left one full window
+      6 (E)  right one full window
+
+Other functions:
+  1  56      speak current line
+  1   6       mute speech
+ 21 45       route cursor to start of window
+32    6      cut start
+3    56      cut end
+321 4        paste
+     5       cursor visibility on/off
+  1 4   (C)  cursor tracking on/off
+ 2           cursor blink on/off
+  1 4 6      capital letter blink on/off
+ 2   5       block/underline cursor
+32   5       six/eight dot braille text
+ 2  456      sliding window on/off
+ 2  45       skip identical lines on/off
+32  4        audio signals on/off
+3 1 4        attribute display on/off
+ 21 4        freeze mode on/off
+ 21  5       help display on/off
+3   4        status mode on/off
+
+Preferences control:
+321 456      save preferences
+3 1 4 6      enter preferences menu
+321  5       restore preferences
+
+Status information (cell 5):
+Dot Number   Dot Present Means
+    1        The screen is frozen
+    2        Attribute display is on
+    3        Audio signals are on
+    4        The cursor is visible
+    5        Cursor shape is block
+    6        Cursor blink is on
+    7        Cursor tracking is on
+    8        Sliding window is on
diff --git a/Tables/Input/md/common.kti b/Tables/Input/md/common.kti
new file mode 100644
index 0000000..c464899
--- /dev/null
+++ b/Tables/Input/md/common.kti
@@ -0,0 +1,56 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note RoutingKey refers to any of the keys immediately behind the text cells.
+note The long key in the center of the front surface is named Shift.
+note * The two round keys to its left, from left to right, are named: Left, Up.
+note * The two round keys to its right, from left to right, are named: Down, Right.
+
+bind Left FWINLT
+bind Right FWINRT
+bind Up LNUP
+bind Down LNDN
+
+bind Shift+Left KEY_CURSOR_LEFT
+bind Shift+Right KEY_CURSOR_RIGHT
+bind Shift+Up KEY_CURSOR_UP
+bind Shift+Down KEY_CURSOR_DOWN
+
+bind Long+Left LNBEG
+bind Long+Right LNEND
+bind Long+Up TOP
+bind Long+Down BOT
+
+bind Shift+Long+Left PRPGRPH
+bind Shift+Long+Right NXPGRPH
+bind Shift+Long+Up PRPROMPT
+bind Shift+Long+Down NXPROMPT
+
+bind RoutingKey ROUTE
+bind RoutingKey+Left CLIP_NEW
+bind RoutingKey+Up CLIP_APPEND
+bind RoutingKey+Right COPY_LINE
+bind RoutingKey+Down COPY_RECT
+bind RoutingKey+RoutingKey CLIP_COPY
+bind RoutingKey.2+RoutingKey.3 PASTE
+
+context menu
+
+bind Up MENU_PREV_ITEM
+bind Down MENU_NEXT_ITEM
+
diff --git a/Tables/Input/md/default.ktb b/Tables/Input/md/default.ktb
new file mode 100644
index 0000000..9ff1923
--- /dev/null
+++ b/Tables/Input/md/default.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title MDV (unrecognized model - all keys defined)
+
+include common.kti
+include keyboard.kti
+include fkeys.kti
+include status.kti
diff --git a/Tables/Input/md/fk.ktb b/Tables/Input/md/fk.ktb
new file mode 100644
index 0000000..ea75bb1
--- /dev/null
+++ b/Tables/Input/md/fk.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title MDV (with function keys)
+
+include common.kti
+include fkeys.kti
diff --git a/Tables/Input/md/fk_s.ktb b/Tables/Input/md/fk_s.ktb
new file mode 100644
index 0000000..315c0ad
--- /dev/null
+++ b/Tables/Input/md/fk_s.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title MDV (with function keys and status cells)
+
+include common.kti
+include fkeys.kti
+include status.kti
diff --git a/Tables/Input/md/fkeys.kti b/Tables/Input/md/fkeys.kti
new file mode 100644
index 0000000..e875eb8
--- /dev/null
+++ b/Tables/Input/md/fkeys.kti
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note There are five square keys at each end of the front surface.
+note * The ones at the left, from left to right, are named: F5, F4, F3, F2, F1.
+note * The ones at the right, from left to right, are named: F6, F7, F8, F9, F10.
+
+bind F3 CHRLT
+bind F8 CHRRT
+
+bind Shift+F3 HWINLT
+bind Shift+F8 HWINRT
+
+bind F1 TOP_LEFT
+bind F2 BOT_LEFT
+
+bind Shift+F1 FREEZE
+bind Shift+F2 INFO
+
+bind F4 HOME
+bind Shift+F4 CSRSIZE
+bind Long+F4 CSRBLINK
+
+bind F5 CSRTRK
+bind Shift+F5 CSRVIS
+bind Long+F5 CAPBLINK
+
+bind F6 SKPIDLNS
+bind Shift+F6 DISPMD
+bind Shift+Long+F6 ATTRVIS
+bind Long+F6 ATTRBLINK
+
+bind F7 SKPBLNKWINS
+
+bind F9 HELP
+bind Shift+F9 LEARN
+bind Long+F9 TIME
+
+bind F10 PREFMENU
+bind Shift+F10 PASTE
+bind long+F10 PREFLOAD
+bind Shift+long+F10 PREFSAVE
+
+context menu
+
+bind F9 MENU_NEXT_SETTING
+bind Shift+F9 MENU_PREV_SETTING
+bind Shift+F10 MENU_PREV_LEVEL
+
diff --git a/Tables/Input/md/kbd.ktb b/Tables/Input/md/kbd.ktb
new file mode 100644
index 0000000..0d41b3a
--- /dev/null
+++ b/Tables/Input/md/kbd.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title MDV (with keyboard)
+
+include common.kti
+include keyboard.kti
diff --git a/Tables/Input/md/keyboard.kti b/Tables/Input/md/keyboard.kti
new file mode 100644
index 0000000..b3d22cc
--- /dev/null
+++ b/Tables/Input/md/keyboard.kti
@@ -0,0 +1,31 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+map Space SPACE
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+assign chord Space+
+include ../chords.kti
+
diff --git a/Tables/Input/md/status.kti b/Tables/Input/md/status.kti
new file mode 100644
index 0000000..3239842
--- /dev/null
+++ b/Tables/Input/md/status.kti
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note StatusKey refers to any of the keys immediately behind the status cells.
+
+hotkey StatusKey.1 CSRHIDE+on CSRHIDE+off
+
diff --git a/Tables/Input/menu.kti b/Tables/Input/menu.kti
new file mode 100644
index 0000000..7832fc8
--- /dev/null
+++ b/Tables/Input/menu.kti
@@ -0,0 +1,32 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context menu
+
+bind Dot1 MENU_PREV_ITEM
+bind Dot4 MENU_NEXT_ITEM
+
+bind Dot2 MENU_FIRST_ITEM
+bind Dot5 MENU_LAST_ITEM
+
+bind Dot3 MENU_PREV_SETTING
+bind Dot6 MENU_NEXT_SETTING
+
+bind Dot7 MENU_PREV_LEVEL
+bind Dot8 PREFMENU
+
diff --git a/Tables/Input/mm/common.kti b/Tables/Input/mm/common.kti
new file mode 100644
index 0000000..a7041c9
--- /dev/null
+++ b/Tables/Input/mm/common.kti
@@ -0,0 +1,100 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+context default
+
+bind RoutingKey ROUTE
+
+bind ArrowUp LNUP
+bind ArrowDown LNDN
+bind PanLeft FWINLT
+bind PanRight FWINRT
+
+bind Control+ArrowUp TOP_LEFT
+bind Control+ArrowDown BOT_LEFT
+bind Control+PanLeft PRSEARCH
+bind Control+PanRight NXSEARCH
+
+bind Alt+ArrowUp ATTRUP
+bind Alt+ArrowDown ATTRDN
+bind Alt+PanLeft CHRLT
+bind Alt+PanRight CHRRT
+
+bind Select+ArrowUp TOP
+bind Select+ArrowDown BOT
+bind Select+PanLeft LNBEG
+bind Select+PanRight LNEND
+
+bind Read+ArrowUp PRDIFLN
+bind Read+ArrowDown NXDIFLN
+bind Read+PanLeft FWINLTSKIP
+bind Read+PanRight FWINRTSKIP
+
+bind Control+Alt+ArrowUp PRPGRPH
+bind Control+Alt+ArrowDown NXPGRPH
+
+bind Select+Read+ArrowUp PRPROMPT
+bind Select+Read+ArrowDown NXPROMPT
+
+bind Control TIME
+bind Alt INFO
+
+bind Control+RoutingKey CLIP_NEW
+bind Alt+RoutingKey CLIP_ADD
+bind Select+RoutingKey COPY_LINE
+bind Read+RoutingKey COPY_RECT
+bind Control+Alt PASTE
+
+bind Control+Alt+RoutingKey SETLEFT
+bind Select+Read+RoutingKey DESCCHAR
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map Control CONTROL
+map Alt META
+
+bind Insert KEY_INSERT
+bind Delete KEY_DELETE
+bind Backspace KEY_BACKSPACE
+
+bind Backward KEY_HOME
+bind Forward KEY_END
+
+bind ScrollLeft KEY_PAGE_UP
+bind ScrollRight KEY_PAGE_DOWN
+
+bind ArrowLeft KEY_CURSOR_LEFT
+bind ArrowRight KEY_CURSOR_RIGHT
+
+bind Escape KEY_ESCAPE
+
+assign chord Read+
+assign commandDot7 KEY_ENTER
+assign commandDot8 PASSDOTS+space
+include ../chords.kti
diff --git a/Tables/Input/mm/pocket.ktb b/Tables/Input/mm/pocket.ktb
new file mode 100644
index 0000000..2e68182
--- /dev/null
+++ b/Tables/Input/mm/pocket.ktb
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Braille Memo Pocket
+
+note The six round keys along the top are a braille keyboard.
+note * From left to right, their names are: Dot3, Dot2, Dot1, Dot4, Dot5, Dot6.
+note The two keys to the left of Dot3, from top to bottom, are named: Control, Alt.
+note The two keys to the right of Dot6, from top to bottom, are named: Select, Read.
+note The three keys below Dot1 and Dot4, from left to right, are named: Dot7, Extension, Dot8.
+note At the bottom left is a group of four keys arranged in a cross.
+note * Starting with the leftmost key and moving clockwise, their names are:
+note * ArrowLeft, ArrowUp, ArrowRight, ArrowDown.
+note A driver implementation constraint requires that some of the bindings
+note * refer to the ArrowLeft and ArrowRight keys as PanLeft and PanRight.
+note Some keys are entered by pressing a set of keys simultaneously:
+note + Return: Dot7
+note + Space: Dot8
+note + ArrowUp: Extension + Dot1
+note + ArrowDown: Extension + Dot4
+note + ArrowLeft: Extension + Dot2
+note + ArrowRight: Extension + Dot5
+note + Insert: Extension + Dot3
+note + Change: Extension + Dot6
+note + Backspace: Extension + Dot7
+note + Delete: Extension + Dot8
+note + Escape: Extension + ArrowUp
+note + Info: Extension + ArrowDown
+note + Backward: Extension + ArrowRight + Dot1
+note + Forward: Extension + ArrowRight + Dot4
+note + ScrollLeft: Extension + ArrowRight + Dot2
+note + ScrollRight: Extension + ArrowRight + Dot5
+note + OK: Extension + ArrowRight + Dot7
+note + Set: Extension + ArrowRight + Dot8
+
+bind ArrowUp+ArrowDown CSRJMP_VERT
+bind PanLeft+PanRight RETURN
+
+include common.kti
+context default
+
+bind ArrowUp+ArrowLeft CSRVIS
+bind ArrowUp+ArrowRight ATTRVIS
+
+bind ArrowDown+ArrowLeft CSRTRK
+bind ArrowDown+ArrowRight SIXDOTS
+
+bind Extension KEY_TAB
+bind Extension+RoutingKey KEY_FUNCTION
diff --git a/Tables/Input/mm/smart.ktb b/Tables/Input/mm/smart.ktb
new file mode 100644
index 0000000..d7afcea
--- /dev/null
+++ b/Tables/Input/mm/smart.ktb
@@ -0,0 +1,70 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Braille Memo Smart
+
+note The six slightly angled keys along the top are a braille keyboard.
+note * From left to right, their names are: Dot3, Dot2, Dot1, Dot4, Dot5, Dot6.
+note The two keys to the left of Dot3, from top to bottom, are named: Control, Alt.
+note The two keys to the right of Dot6, from top to bottom, are named: Select, Read.
+note The key immediately below Dot2 is named Backspace.
+note The key immediately below Dot5 is named Delete.
+note The three keys below Dot1 and Dot4, from left to right, are named: Dot7, Extension, Dot8.
+note The keys to the left and right of the braille cells are named: PanLeft, PanRight.
+note The two keys on the front at the left are named: ArrowUp, ArrowDown.
+note The two keys on the front at the right are named: ArrowLeft, ArrowRight.
+note Some keys are entered by pressing a set of keys simultaneously:
+note + Return: Dot7
+note + Space: Dot8
+note + ArrowUp: Extension + Dot1
+note + ArrowDown: Extension + Dot4
+note + Control+ArrowLeft: Extension + Dot2
+note + Control+ArrowRight: Extension + Dot5
+note + ArrowLeft: Extension + Dot3
+note + ArrowRight: Extension + Dot6
+note + OK: Extension + Dot7
+note + Set: Extension + Dot8
+note + Info (Tab): Extension + Dot4 + Dot5
+note + Select+Info (Shift+Tab): Extension + Dot1 + Dot2
+note + Escape: Extension + Dot1 + Dot5
+note + Insert: Extension + Dot2 + Dot4
+note + Backward: Extension + ArrowUp
+note + Forward: Extension + ArrowDown
+note + ScrollLeft: Extension + ArrowLeft
+note + ScrollRight: Extension + ArrowRight
+
+hide on
+bind Info KEY_TAB
+bind Select+Info KEY_TAB+shift
+hide off
+
+bind Read HOME
+bind Select BACK
+bind Select+Read CSRJMP_VERT
+
+include common.kti
+context default
+
+bind Control+ArrowLeft CSRVIS
+bind Control+ArrowRight ATTRVIS
+
+bind Alt+ArrowLeft CSRTRK
+bind Alt+ArrowRight SIXDOTS
+
+bind PanLeft+RoutingKey SWITCHVT
+bind PanRight+RoutingKey KEY_FUNCTION
diff --git a/Tables/Input/mn/all.txt b/Tables/Input/mn/all.txt
new file mode 100644
index 0000000..b8c5510
--- /dev/null
+++ b/Tables/Input/mn/all.txt
@@ -0,0 +1,68 @@
+Help: MiniBraille
+
+Keys (from left to right): F1 F2 Left Up Center Down Right
+
+Basic Mode:
+F1: initiate two-level mode selection (next key selects mode)
+F2: select F2 mode
+Left/Right: go left/right one window
+Up/Down: go up/down one line
+Center: go to cursor or undo unexpected cursor tracking motion
+
+F2 Mode:
+F1/F2: go to beginning of top/bottom line
+Left/Right: go to beginning/end of current line
+Up/Down: go to top/bottom line
+Center: toggle cursor tracking
+
+F1-F1 Mode;
+F1: toggle help screen
+F2: toggle learn mode
+Left: toggle status line
+Up: restore preferences from disk
+Center: save preferences to disk
+Down: enter/leave preferences menu
+Right: show date and time
+
+F1-F2 Mode:
+F1: toggle frozen screen
+F2: toggle text/attributes display
+Left: toggle attribute underlining
+Up: toggle skip blank windows
+Center: toggle six-dot braille
+Down: toggle skip identical lines
+Right: toggle cursor visibility
+
+F1-Up Mode:
+F1/F2: search screen backward/forward for cut text
+Left/Right: go to previous/next line wit different highlighting
+Up/Down: go to previous/next paragraph (blank line separation)
+Center: route cursor to current line
+
+F1-Down Mode:
+F1/F2: go to previous/next prompt (same prompt as current line)
+Left/Right: go to previous/next non-blank window
+Up/Down: go to previous/next line with different content
+Center: paste cut text at cursor
+
+F1-Right (Speech) Mode
+F2: toggle autospeak
+Left: speak from top of screen to current line
+Up: stop speaking
+Center: go to current (most recent) speech position
+Down: speak current line
+Right: speak from current line to bottom of screen
+
+F1-Center (Char) Mode
+F1: select Char-F1 mode
+Left/Right: move character selection cursor left/right one position
+Up/Down: go up/down one line
+
+Char-F1 Mode
+F1: position left end of window at selected character
+F2: describe selected character
+Left: append to existing cut buffer from selected character
+Up: start new cut buffer at selected character
+Center: route cursor to selected character
+Down: rectangular cut to selected character
+Right: linear cut to selected character
diff --git a/Tables/Input/mt/bd1_3.ktb b/Tables/Input/mt/bd1_3.ktb
new file mode 100644
index 0000000..1e56a69
--- /dev/null
+++ b/Tables/Input/mt/bd1_3.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Metec BD I (with 3 keys)
+
+include bd1_3.kti
diff --git a/Tables/Input/mt/bd1_3.kti b/Tables/Input/mt/bd1_3.kti
new file mode 100644
index 0000000..77095c4
--- /dev/null
+++ b/Tables/Input/mt/bd1_3.kti
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+bind Up LNUP
+bind Down LNDN
+
+bind Select FWINRT
+bind Up+Down FWINRT
+
+bind Select+Up TOP
+bind Select+Down BOT
+
+bind Select+Up+Down RETURN
+
+bind !RoutingKey ROUTE
+bind Select+!RoutingKey.1 HELP
+bind Select+!RoutingKey.2 LEARN
+bind Select+!RoutingKey.3 PREFMENU
+bind Select+!RoutingKey.4 INFO
+
+bind Up+!RoutingKey CLIP_NEW
+bind Up+Select+!RoutingKey CLIP_APPEND
+bind Down+!RoutingKey COPY_LINE
+bind Down+Select+!RoutingKey COPY_RECT
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind Up MENU_PREV_ITEM
+bind Down MENU_NEXT_ITEM
+
+bind Select+Up MENU_FIRST_ITEM
+bind Select+Down MENU_LAST_ITEM
+
+bind Select MENU_NEXT_SETTING
+bind Up+Down MENU_PREV_SETTING
+
+bind Select+Up+Down PREFMENU
+
+
+context default
diff --git a/Tables/Input/mt/bd1_3s.ktb b/Tables/Input/mt/bd1_3s.ktb
new file mode 100644
index 0000000..d9b9545
--- /dev/null
+++ b/Tables/Input/mt/bd1_3s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Metec BD I (with 3 keys and status cells)
+
+include bd1_3.kti
+include status.kti
diff --git a/Tables/Input/mt/bd1_6.ktb b/Tables/Input/mt/bd1_6.ktb
new file mode 100644
index 0000000..057c887
--- /dev/null
+++ b/Tables/Input/mt/bd1_6.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Metec BD I (with 6 keys)
+
+include bd1_6.kti
diff --git a/Tables/Input/mt/bd1_6.kti b/Tables/Input/mt/bd1_6.kti
new file mode 100644
index 0000000..1318490
--- /dev/null
+++ b/Tables/Input/mt/bd1_6.kti
@@ -0,0 +1,64 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+bind LeftUp TOP
+bind LeftDown BOT
+
+bind RightUp LNUP
+bind RightDown LNDN
+
+bind LeftSelect FWINLT
+bind RightSelect FWINRT
+
+bind LeftSelect+LeftUp+LeftDown BACK
+bind RightSelect+RightUp+RightDown HOME
+
+bind !RoutingKey ROUTE
+
+bind LeftUp+!RoutingKey CLIP_NEW
+bind LeftSelect+!RoutingKey CLIP_APPEND
+bind RightUp+!RoutingKey COPY_LINE
+bind RightSelect+!RoutingKey COPY_RECT
+
+bind LeftDown+!RoutingKey SETLEFT
+bind RightDown+!RoutingKey DESCCHAR
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind LeftUp MENU_FIRST_ITEM
+bind LeftDown MENU_LAST_ITEM
+
+bind RightUp MENU_PREV_ITEM
+bind RightDown MENU_NEXT_ITEM
+
+bind LeftSelect MENU_PREV_SETTING
+bind RightSelect MENU_NEXT_SETTING
+
+bind LeftSelect+LeftUp+LeftDown PREFMENU
+
+
+context default
diff --git a/Tables/Input/mt/bd1_6s.ktb b/Tables/Input/mt/bd1_6s.ktb
new file mode 100644
index 0000000..26281f2
--- /dev/null
+++ b/Tables/Input/mt/bd1_6s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Metec BD I (with 6 keys and status cells)
+
+include bd1_6.kti
+include status.kti
diff --git a/Tables/Input/mt/bd2.ktb b/Tables/Input/mt/bd2.ktb
new file mode 100644
index 0000000..c9621f9
--- /dev/null
+++ b/Tables/Input/mt/bd2.ktb
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Metec BD II
+
diff --git a/Tables/Input/mt/status.kti b/Tables/Input/mt/status.kti
new file mode 100644
index 0000000..37733ac
--- /dev/null
+++ b/Tables/Input/mt/status.kti
@@ -0,0 +1,19 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind StatusKey.1 SIXDOTS
diff --git a/Tables/Input/nav.kti b/Tables/Input/nav.kti
new file mode 100644
index 0000000..a4ad695
--- /dev/null
+++ b/Tables/Input/nav.kti
@@ -0,0 +1,88 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind Up LNUP
+bind Down LNDN
+bind Left FWINLT
+bind Right FWINRT
+
+bind Dot4+Up TOP
+bind Dot4+Down BOT
+bind Dot4+Left LNBEG
+bind Dot4+Right LNEND
+
+bind Dot5+Up PRDIFLN
+bind Dot5+Down NXDIFLN
+bind Dot5+Left FWINLTSKIP
+bind Dot5+Right FWINRTSKIP
+
+bind Dot6+Up ATTRUP
+bind Dot6+Down ATTRDN
+bind Dot6+Left CHRLT
+bind Dot6+Right CHRRT
+
+Bind Dot4+Dot6+Up PRPGRPH
+Bind Dot4+Dot6+Down NXPGRPH
+Bind Dot4+Dot6+Left PRPROMPT
+Bind Dot4+Dot6+Right NXPROMPT
+
+Bind Dot4+Dot5+Dot6+Up PREFMENU
+Bind Dot4+Dot5+Dot6+Down TIME
+Bind Dot4+Dot5+Dot6+Left PREFLOAD
+Bind Dot4+Dot5+Dot6+Right PREFSAVE
+
+Bind Dot4+Dot5+Up FREEZE
+Bind Dot4+Dot5+Down DISPMD
+Bind Dot4+Dot5+Left COMPBRL6
+Bind Dot4+Dot5+Right CONTRACTED
+
+Bind Dot5+Dot6+Up PRSEARCH
+Bind Dot5+Dot6+Down NXSEARCH
+Bind Dot5+Dot6+Left SWITCHVT_PREV
+Bind Dot5+Dot6+Right SWITCHVT_NEXT
+
+bind Dot1+Up KEY_CURSOR_UP
+bind Dot1+Down KEY_CURSOR_DOWN
+bind Dot1+Left KEY_CURSOR_LEFT
+bind Dot1+Right KEY_CURSOR_RIGHT
+
+bind Dot2+Up KEY_PAGE_UP
+bind Dot2+Down KEY_PAGE_DOWN
+bind Dot2+Left KEY_HOME
+bind Dot2+Right KEY_END
+
+bind Dot3+Up KEY_ESCAPE
+bind Dot3+Down KEY_TAB
+bind Dot3+Left KEY_DELETE
+bind Dot3+Right KEY_INSERT
+
+bind Dot1+Dot2+Up SAY_ABOVE
+bind Dot1+Dot2+Down SAY_BELOW
+bind Dot1+Dot2+Left MUTE
+bind Dot1+Dot2+Right SAY_LINE
+
+bind Dot2+Dot3+Up SAY_LOUDER
+bind Dot2+Dot3+Down SAY_SOFTER
+bind Dot2+Dot3+Left SAY_SLOWER
+bind Dot2+Dot3+Right SAY_FASTER
+
+bind Dot1+Dot3+Up AUTOSPEAK
+bind Dot1+Dot3+Down SPKHOME
+bind Dot1+Dot3+Left SPEAK_INDENT
+bind Dot1+Dot3+Right SAY_ALL
+
diff --git a/Tables/Input/no/all.txt b/Tables/Input/no/all.txt
new file mode 100644
index 0000000..a50fd8b
--- /dev/null
+++ b/Tables/Input/no/all.txt
@@ -0,0 +1 @@
+no braille display
diff --git a/Tables/Input/np/all.ktb b/Tables/Input/np/all.ktb
new file mode 100644
index 0000000..762655b
--- /dev/null
+++ b/Tables/Input/np/all.ktb
@@ -0,0 +1,45 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title NinePoint
+
+bind RoutingKey ROUTE
+
+bind PadLeft FWINLT
+bind PadRight FWINRT
+bind PadUp LNUP
+bind PadDown LNDN
+bind PadCenter HOME
+
+bind NavLeft FWINLT
+bind NavRight FWINRT
+
+bind Space+Brl1+Brl6+Brl7+Brl8 PREFMENU
+bind Space+Brl2+Brl7+Brl8 HELP
+
+map Brl1 DOT4
+map Brl2 DOT5
+map Brl3 DOT6
+map Brl4 DOT8
+map Brl5 DOT7
+map Brl6 DOT3
+map Brl7 DOT2
+map Brl8 DOT1
+map Space SPACE
+bind Enter KEY_ENTER
+
diff --git a/Tables/Input/pg/all.ktb b/Tables/Input/pg/all.ktb
new file mode 100644
index 0000000..f190bbd
--- /dev/null
+++ b/Tables/Input/pg/all.ktb
@@ -0,0 +1,107 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Pegasus
+
+
+####################
+# Default Bindings #
+####################
+
+bind !Status1 HELP
+bind !Status2 LEARN
+
+bind !RoutingKey ROUTE
+
+bind !Left FWINLT
+bind !Right FWINRT
+bind !Up LNUP
+bind !Down LNDN
+
+bind !Home TOP_LEFT
+bind !Enter BOT_LEFT
+bind !End RETURN
+bind !Escape CSRTRK
+
+bind LeftControl+!Left ATTRUP
+bind LeftControl+!Right ATTRDN
+bind LeftControl+!Up PRDIFLN
+bind LeftControl+!Down NXDIFLN
+
+bind LeftControl+!Enter FREEZE
+bind LeftControl+!End PREFMENU
+bind LeftControl+!Escape INFO
+
+bind LeftShift+!Left DISPMD
+bind LeftShift+!Right SIXDOTS
+bind LeftShift+!Down CSRJMP_VERT
+
+bind LeftShift+!Home PRPROMPT
+bind LeftShift+!Enter NXPROMPT
+bind LeftShift+!End PRPGRPH
+bind LeftShift+!Escape NXPGRPH
+
+bind RightShift+!Left CONTEXT+SETLEFT
+bind RightShift+!Right PASTE
+bind RightShift+!Down CONTEXT+DESCCHAR
+
+bind RightShift+!Home CONTEXT+CLIP_NEW
+bind RightShift+!Enter CONTEXT+CLIP_ADD
+bind RightShift+!End CONTEXT+COPY_LINE
+bind RightShift+!Escape CONTEXT+COPY_RECT
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind Left FWINLT
+bind Right FWINRT
+bind Up MENU_PREV_ITEM
+bind Down MENU_NEXT_ITEM
+
+bind Home MENU_FIRST_ITEM
+bind Enter MENU_LAST_ITEM
+bind End MENU_PREV_SETTING
+bind Escape MENU_NEXT_SETTING
+
+
+############################
+# Routing Key Alternatives #
+############################
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+context SETLEFT
+bind !RoutingKey SETLEFT
+
+context DESCCHAR
+bind !RoutingKey DESCCHAR
+
diff --git a/Tables/Input/pm/2d_l.ktb b/Tables/Input/pm/2d_l.ktb
new file mode 100644
index 0000000..53b8f4f
--- /dev/null
+++ b/Tables/Input/pm/2d_l.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX 2D Lite (plus)
+
+assign statusKeys 13
+include front9.kti
diff --git a/Tables/Input/pm/2d_s.ktb b/Tables/Input/pm/2d_s.ktb
new file mode 100644
index 0000000..e05bf6d
--- /dev/null
+++ b/Tables/Input/pm/2d_s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX 2D Screen Soft
+
+assign statusKeys 22
+include front13.kti
diff --git a/Tables/Input/pm/bar.kti b/Tables/Input/pm/bar.kti
new file mode 100644
index 0000000..9e8be61
--- /dev/null
+++ b/Tables/Input/pm/bar.kti
@@ -0,0 +1,98 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have an Easy Access Bar.
+
+note The long key on the front is the Easy Access Bar.
+
+ifVar hasSingleStepBar
+  note * It can only be moved one step in each direction (left, right, up, down).
+  note * To emulate the second step, also press any of the lower routing keys
+  note * (those in the row just behind the text cells).
+else
+  note * It can be moved two steps in each direction (left, right, up, down).
+endIf
+
+bind BarUp1 LNUP
+bind BarDown1 LNDN
+bind BarLeft1 FWINLT
+bind BarRight1 FWINRT
+
+bind BarUp2 TOP
+bind BarDown2 BOT
+bind BarLeft2 LNBEG
+bind BarRight2 LNEND
+
+hide on
+bind BarUp1+BarUp2 TOP
+bind BarDown1+BarDown2 BOT
+bind BarLeft1+BarLeft2 LNBEG
+bind BarRight1+BarRight2 LNEND
+hide off
+
+include routing.kti
+
+ifNotVar hasSingleStepBar
+  bind BarUp1+RoutingKey1 PRINDENT
+  bind BarDown1+RoutingKey1 NXINDENT
+  bind BarLeft1+RoutingKey1 CLIP_ADD
+  bind BarRight1+RoutingKey1 COPY_LINE
+
+  bind BarUp2+RoutingKey1 SETLEFT
+  bind BarDown2+RoutingKey1 DESCCHAR
+  bind BarLeft2+RoutingKey1 CLIP_NEW
+  bind BarRight2+RoutingKey1 COPY_RECT
+
+  hide on
+  bind BarUp1+BarUp2+RoutingKey1 SETLEFT
+  bind BarDown1+BarDown2+RoutingKey1 DESCCHAR
+  bind BarLeft1+BarLeft2+RoutingKey1 CLIP_NEW
+  bind BarRight1+BarRight2+RoutingKey1 COPY_RECT
+  hide off
+endIf
+
+assign toggleOff BarLeft1
+assign toggleOn BarRight1
+include status\{statusKeys}.kti
+
+include keys.kti
+
+ifKey RoutingKey2
+endIf
+
+ifKey StatusKey2 bind !StatusKey2 GOTOLINE
+
+
+context menu
+bind BarUp1 MENU_PREV_ITEM
+bind BarDown1 MENU_NEXT_ITEM
+bind BarUp2 MENU_FIRST_ITEM
+bind BarDown2 MENU_LAST_ITEM
+bind BarLeft1 MENU_PREV_SETTING
+bind BarRight1 MENU_NEXT_SETTING
+bind BarLeft2 PREFLOAD
+bind BarRight2 PREFSAVE
+
+hide on
+bind BarUp1+BarUp2 MENU_FIRST_ITEM
+bind BarDown1+BarDown2 MENU_LAST_ITEM
+bind BarLeft1+BarLeft2 PREFLOAD
+bind BarRight1+BarRight2 PREFSAVE
+hide off
+
+
diff --git a/Tables/Input/pm/c.ktb b/Tables/Input/pm/c.ktb
new file mode 100644
index 0000000..aae0039
--- /dev/null
+++ b/Tables/Input/pm/c.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Compact/Tiny
+
+assign statusKeys 0
+include front9.kti
diff --git a/Tables/Input/pm/c_486.ktb b/Tables/Input/pm/c_486.ktb
new file mode 100644
index 0000000..4387182
--- /dev/null
+++ b/Tables/Input/pm/c_486.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Compact 486
+
+assign statusKeys 0
+include front9.kti
diff --git a/Tables/Input/pm/el2d_80s.ktb b/Tables/Input/pm/el2d_80s.ktb
new file mode 100644
index 0000000..bb05540
--- /dev/null
+++ b/Tables/Input/pm/el2d_80s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL2D-80s
+
+assign statusKeys 20
+include bar.kti
diff --git a/Tables/Input/pm/el40c.ktb b/Tables/Input/pm/el40c.ktb
new file mode 100644
index 0000000..81f303f
--- /dev/null
+++ b/Tables/Input/pm/el40c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL40c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el40s.ktb b/Tables/Input/pm/el40s.ktb
new file mode 100644
index 0000000..9c743a4
--- /dev/null
+++ b/Tables/Input/pm/el40s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL40s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el60c.ktb b/Tables/Input/pm/el60c.ktb
new file mode 100644
index 0000000..69e8c97
--- /dev/null
+++ b/Tables/Input/pm/el60c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL60c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el66s.ktb b/Tables/Input/pm/el66s.ktb
new file mode 100644
index 0000000..33cfb61
--- /dev/null
+++ b/Tables/Input/pm/el66s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL66s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el70s.ktb b/Tables/Input/pm/el70s.ktb
new file mode 100644
index 0000000..4946713
--- /dev/null
+++ b/Tables/Input/pm/el70s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL70s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el80_ii.ktb b/Tables/Input/pm/el80_ii.ktb
new file mode 100644
index 0000000..f85d229
--- /dev/null
+++ b/Tables/Input/pm/el80_ii.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80-II
+
+assign statusKeys 2
+include bar.kti
diff --git a/Tables/Input/pm/el80c.ktb b/Tables/Input/pm/el80c.ktb
new file mode 100644
index 0000000..fecbab9
--- /dev/null
+++ b/Tables/Input/pm/el80c.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80c
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el80s.ktb b/Tables/Input/pm/el80s.ktb
new file mode 100644
index 0000000..94b61bc
--- /dev/null
+++ b/Tables/Input/pm/el80s.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL80s
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/el_2d_40.ktb b/Tables/Input/pm/el_2d_40.ktb
new file mode 100644
index 0000000..12b3b83
--- /dev/null
+++ b/Tables/Input/pm/el_2d_40.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-40
+
+assign statusKeys 13
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/el_2d_66.ktb b/Tables/Input/pm/el_2d_66.ktb
new file mode 100644
index 0000000..be2e3af
--- /dev/null
+++ b/Tables/Input/pm/el_2d_66.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-66
+
+assign statusKeys 13
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/el_2d_80.ktb b/Tables/Input/pm/el_2d_80.ktb
new file mode 100644
index 0000000..7a5eca9
--- /dev/null
+++ b/Tables/Input/pm/el_2d_80.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 2D-80
+
+assign statusKeys 20
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/el_40_p.ktb b/Tables/Input/pm/el_40_p.ktb
new file mode 100644
index 0000000..9214688
--- /dev/null
+++ b/Tables/Input/pm/el_40_p.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 40 P
+
+assign noRightKey
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/el_80.ktb b/Tables/Input/pm/el_80.ktb
new file mode 100644
index 0000000..1953ddd
--- /dev/null
+++ b/Tables/Input/pm/el_80.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX EL 80
+
+assign statusKeys 2
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/elb_tr_20.ktb b/Tables/Input/pm/elb_tr_20.ktb
new file mode 100644
index 0000000..01dcbd4
--- /dev/null
+++ b/Tables/Input/pm/elb_tr_20.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba (Trio 20)
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/elb_tr_32.ktb b/Tables/Input/pm/elb_tr_32.ktb
new file mode 100644
index 0000000..b8bc370
--- /dev/null
+++ b/Tables/Input/pm/elb_tr_32.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba (Trio 32)
+
+assign statusKeys 0
+include bar.kti
diff --git a/Tables/Input/pm/elba_20.ktb b/Tables/Input/pm/elba_20.ktb
new file mode 100644
index 0000000..0dad0a1
--- /dev/null
+++ b/Tables/Input/pm/elba_20.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba 20
+
+assign keyEmulation
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/elba_32.ktb b/Tables/Input/pm/elba_32.ktb
new file mode 100644
index 0000000..a06c35a
--- /dev/null
+++ b/Tables/Input/pm/elba_32.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Elba 32
+
+assign keyEmulation
+assign statusKeys 0
+include bar.kti
+include switches.kti
diff --git a/Tables/Input/pm/front13.kti b/Tables/Input/pm/front13.kti
new file mode 100644
index 0000000..83d032f
--- /dev/null
+++ b/Tables/Input/pm/front13.kti
@@ -0,0 +1,151 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 13 front keys.
+note There are 13 keys on the front.
+note * The rectangular key at the center is named Shift.
+note * The square keys immediately to its left and right are named Home and End.
+note * The left and right bars are named Up and Down.
+note * The four square keys at the very left are named: Dot7, Dot3, Dot2, Dot1.
+note * The four square keys at the very right are named: Dot4, Dot5, Dot6, Dot8.
+
+bind !Shift HOME
+bind !Home TOP
+bind !End BOT
+bind !Up LNUP
+bind !Down LNDN
+bind Dot1 PRDIFLN
+bind Dot4 NXDIFLN
+bind Dot2 ATTRUP
+bind Dot5 ATTRDN
+bind Dot3 PRPGRPH
+bind Dot6 NXPGRPH
+bind Dot7 PRPROMPT
+bind Dot8 NXPROMPT
+
+bind Dot1+Dot2 WINUP
+bind Dot4+Dot5 WINDN
+bind Dot3+Dot7 PRSEARCH
+bind Dot6+Dot8 NXSEARCH
+
+bind Dot1+Dot2+!RoutingKey1 PRDIFCHAR
+bind Dot4+Dot5+!RoutingKey1 NXDIFCHAR
+bind Dot3+Dot7+!RoutingKey1 PRINDENT
+bind Dot6+Dot8+!RoutingKey1 NXINDENT
+
+bind Dot1+!Up FWINLT
+bind Dot1+!Down FWINRT
+bind Dot1+!Home TOP_LEFT
+bind Dot1+!End BOT_LEFT
+
+bind Dot4+!Up HWINLT
+bind Dot4+!Down HWINRT
+bind Dot4+!Home CHRLT
+bind Dot4+!End CHRRT
+
+bind Dot1+!Shift LNBEG
+bind Dot4+!Shift LNEND
+
+bind Dot1+!RoutingKey1 SETLEFT
+bind Dot4+!RoutingKey1 DESCCHAR
+
+bind Dot2+!Shift KEY_TAB
+bind Dot2+!Home KEY_CURSOR_LEFT
+bind Dot2+!End KEY_CURSOR_RIGHT
+bind Dot2+!Up KEY_CURSOR_UP
+bind Dot2+!Down KEY_CURSOR_DOWN
+bind Dot2+!RoutingKey1 KEY_FUNCTION
+
+bind Dot5+!Shift KEY_INSERT
+bind Dot5+!Home KEY_HOME
+bind Dot5+!End KEY_END
+bind Dot5+!Up KEY_PAGE_UP
+bind Dot5+!Down KEY_PAGE_DOWN
+bind Dot5+!RoutingKey1 SWITCHVT
+
+bind Dot6+!Shift UNSTICK
+bind Dot6+!Home META
+bind Dot6+!End GUI
+bind Dot6+!Up SHIFT
+bind Dot6+!Down CONTROL
+
+bind Dot7+!Shift SPKHOME
+bind Dot7+!Home SAY_ABOVE
+bind Dot7+!End SAY_BELOW
+bind Dot7+!Up MUTE
+bind Dot7+!Down SAY_LINE
+
+bind Dot8+!Shift RESTARTSPEECH
+bind Dot8+!Home SAY_SLOWER
+bind Dot8+!End SAY_FASTER
+bind Dot8+!Up SAY_SOFTER
+bind Dot8+!Down SAY_LOUDER
+
+bind Dot7+!RoutingKey1 CLIP_NEW
+bind Dot3+!RoutingKey1 CLIP_ADD
+bind Dot6+!RoutingKey1 COPY_LINE
+bind Dot8+!RoutingKey1 COPY_RECT
+
+bind Dot1+Dot2+Dot3+Dot7 TIME
+
+include routing.kti
+
+assign toggleOff Dot7
+assign toggleOn Dot8
+include status\{statusKeys}.kti
+
+
+bind Dot2+Dot3+!Shift CONTEXT+default
+bind Dot2+Dot3+!Home CONTEXT+chords
+bind Dot2+Dot3+!End CONTEXT+braille
+
+
+####################
+# Chord Input Mode #
+####################
+
+context chords Chorded Commands Mode
+
+assign noUnchorded
+assign chord
+include ../chords.kti
+include ../menu.kti
+
+
+######################
+# Braille Input Mode #
+######################
+
+context braille Braille Input Mode
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+bind Dot3+!Shift KEY_ESCAPE
+bind Dot3+!Home KEY_BACKSPACE
+bind Dot3+!End KEY_DELETE
+bind Dot3+!Up KEY_ENTER
+bind Dot3+!Down PASSDOTS
+
+
diff --git a/Tables/Input/pm/front9.kti b/Tables/Input/pm/front9.kti
new file mode 100644
index 0000000..523ebf9
--- /dev/null
+++ b/Tables/Input/pm/front9.kti
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 9 front keys.
+
+bind !Home HOME
+bind !Up WINUP
+bind !Down WINDN
+bind !Backward LNUP
+bind !Forward LNDN
+bind Cursor HWINLT
+bind Braille HWINRT
+bind Function FWINLT
+bind Attribute FWINRT
+
+bind Function+!Home LNBEG
+bind Attribute+!Home LNEND
+bind Cursor+!Home CHRLT
+bind Braille+!Home CHRRT
+
+bind Function+!Up PRDIFLN
+bind Attribute+!Up ATTRUP
+bind Cursor+!Up PRPGRPH
+bind Braille+!Up PRSEARCH
+bind Function+!Down NXDIFLN
+bind Attribute+!Down ATTRDN
+bind Cursor+!Down NXPGRPH
+bind Braille+!Down NXSEARCH
+
+bind Function+!Backward TOP_LEFT
+bind Attribute+!Backward TOP
+bind Function+!Forward BOT_LEFT
+bind Attribute+!Forward BOT
+
+bind Function+!RoutingKey1 CLIP_NEW
+bind Attribute+!RoutingKey1 COPY_RECT
+bind Cursor+!RoutingKey1 PRINDENT
+bind Braille+!RoutingKey1 NXINDENT
+
+bind Function+Attribute PASTE
+
+include routing.kti
+
+assign toggleOff Function
+assign toggleOn Attribute
+include status\{statusKeys}.kti
diff --git a/Tables/Input/pm/ib_80.ktb b/Tables/Input/pm/ib_80.ktb
new file mode 100644
index 0000000..f373aab
--- /dev/null
+++ b/Tables/Input/pm/ib_80.ktb
@@ -0,0 +1,22 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX IB 80 CR Soft
+
+assign statusKeys 4
+include front9.kti
diff --git a/Tables/Input/pm/keyboard.kti b/Tables/Input/pm/keyboard.kti
new file mode 100644
index 0000000..d191015
--- /dev/null
+++ b/Tables/Input/pm/keyboard.kti
@@ -0,0 +1,45 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have a braille keyboard.
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+
+map LeftSpace SPACE
+map RightSpace SPACE
+map LeftThumb CONTROL
+map RightThumb META
+
+assign chord Space+
+include ../chords.kti
+assign noUnchorded
+
+hide on
+assign chord LeftSpace+
+include ../chords.kti
+
+assign chord RightSpace+
+include ../chords.kti
+hide off
diff --git a/Tables/Input/pm/keys.kti b/Tables/Input/pm/keys.kti
new file mode 100644
index 0000000..d0c3491
--- /dev/null
+++ b/Tables/Input/pm/keys.kti
@@ -0,0 +1,114 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have keys.
+
+ifVar keyEmulation
+  note The left and right keys are emulated. Enter key emulation mode by
+  note * pressing Thumb1 + Thumb2 (Left Windows + Right Windows). Then press:
+  note + Left Key Rear: Left Thumb (Left Windows)
+  note + Left Key Front: Space + Left Thumb (Context + Left Windows)
+  note + Right Key Rear: Right Thumb (Right Windows)
+  note + Right Key Front: Space + Right Thumb (Context + Right Windows)
+else
+  ifVar noRightKey
+    note The rocker at the left side of the top that doesn't stay when pressed
+    note * is named the Left Key.
+  else
+    note The rockers at each side of the top that don't stay when pressed
+    note * are named the Left and Right Key.
+  endIf
+endIf
+
+bind LeftKeyRear BACK
+bind LeftKeyFront HOME
+
+bind LeftKeyRear+BarLeft1 DISPMD
+bind LeftKeyRear+BarRight1 CSRTRK
+bind LeftKeyRear+BarUp1 SIXDOTS
+bind LeftKeyRear+BarDown1 PASTE
+
+bind LeftKeyRear+BarLeft2 ATTRVIS
+bind LeftKeyRear+BarRight2 CSRVIS
+bind LeftKeyRear+BarUp2 CAPBLINK
+bind LeftKeyRear+BarDown2 CSRJMP_VERT
+
+hide on
+bind LeftKeyRear+BarLeft1+BarLeft2 ATTRVIS
+bind LeftKeyRear+BarRight1+BarRight2 CSRVIS
+bind LeftKeyRear+BarUp1+BarUp2 CAPBLINK
+bind LeftKeyRear+BarDown1+BarDown2 CSRJMP_VERT
+hide off
+
+bind LeftKeyFront+BarLeft1 INFO
+bind LeftKeyFront+BarRight1 PREFMENU
+bind LeftKeyFront+BarUp1 AUTOSPEAK
+bind LeftKeyFront+BarDown1 AUTOREPEAT
+
+bind LeftKeyFront+BarLeft2 PREFLOAD
+bind LeftKeyFront+BarRight2 PREFSAVE
+bind LeftKeyFront+BarUp2 RESTARTBRL
+bind LeftKeyFront+BarDown2 FREEZE
+
+hide on
+bind LeftKeyFront+BarLeft1+BarLeft2 PREFLOAD
+bind LeftKeyFront+BarRight1+BarRight2 PREFSAVE
+bind LeftKeyFront+BarUp1+BarUp2 RESTARTBRL
+bind LeftKeyFront+BarDown1+BarDown2 FREEZE
+hide off
+
+ifNotVar noRightKey
+  bind RightKeyRear HELP
+  bind RightKeyFront LEARN
+
+  bind RightKeyRear+BarLeft1 MUTE
+  bind RightKeyRear+BarRight1 SAY_LINE
+  bind RightKeyRear+BarUp1 SAY_ABOVE
+  bind RightKeyRear+BarDown1 SAY_BELOW
+
+  bind RightKeyRear+BarLeft2 SAY_SLOWER
+  bind RightKeyRear+BarRight2 SAY_FASTER
+  bind RightKeyRear+BarUp2 SAY_LOUDER
+  bind RightKeyRear+BarDown2 SAY_SOFTER
+
+  hide on
+  bind RightKeyRear+BarLeft1+BarLeft2 SAY_SLOWER
+  bind RightKeyRear+BarRight1+BarRight2 SAY_FASTER
+  bind RightKeyRear+BarUp1+BarUp2 SAY_LOUDER
+  bind RightKeyRear+BarDown1+BarDown2 SAY_SOFTER
+  hide off
+
+  bind RightKeyFront+BarLeft1 SKPIDLNS
+  bind RightKeyFront+BarRight1 SKPBLNKWINS
+  bind RightKeyFront+BarUp1 SPKHOME
+  bind RightKeyFront+BarDown1 TUNES
+
+  bind RightKeyFront+BarUp2 RESTARTSPEECH
+  bind RightKeyFront+BarRight2 SLIDEWIN
+
+  hide on
+  bind RightKeyFront+BarUp1+BarUp2 RESTARTSPEECH
+  bind RightKeyFront+BarRight1+BarRight2 SLIDEWIN
+  hide off
+
+  bind LeftKeyRear+RoutingKey1 CLIP_NEW
+  bind LeftKeyFront+RoutingKey1 CLIP_ADD
+  bind RightKeyRear+RoutingKey1 COPY_LINE
+  bind RightKeyFront+RoutingKey1 COPY_RECT
+endIf
+
diff --git a/Tables/Input/pm/live.ktb b/Tables/Input/pm/live.ktb
new file mode 100644
index 0000000..3ae3da8
--- /dev/null
+++ b/Tables/Input/pm/live.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Live
+
+assign hasSingleStepBar
+assign statusKeys 0
+include bar.kti
+include keyboard.kti
diff --git a/Tables/Input/pm/routing.kti b/Tables/Input/pm/routing.kti
new file mode 100644
index 0000000..4f8400a
--- /dev/null
+++ b/Tables/Input/pm/routing.kti
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have primary routing keys.
+
+note RoutingKey1 refers to the row of keys just behind the text cells.
+bind RoutingKey1 ROUTE
+
+ifKey RoutingKey2
+  note RoutingKey2 refers to the row of keys behind the RoutingKey1 row.
+  bind RoutingKey2 DESCCHAR
+endIf
+
diff --git a/Tables/Input/pm/status0.kti b/Tables/Input/pm/status0.kti
new file mode 100644
index 0000000..ea8f98a
--- /dev/null
+++ b/Tables/Input/pm/status0.kti
@@ -0,0 +1,20 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have no status keys.
+
diff --git a/Tables/Input/pm/status13.kti b/Tables/Input/pm/status13.kti
new file mode 100644
index 0000000..cd4a18d
--- /dev/null
+++ b/Tables/Input/pm/status13.kti
@@ -0,0 +1,51 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 13 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.13.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFMENU
+
+assign toggleKeys Status.10
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.11
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+bind !Status.12 TIME
+bind !Status.13 PASTE
diff --git a/Tables/Input/pm/status2.kti b/Tables/Input/pm/status2.kti
new file mode 100644
index 0000000..22b20ab
--- /dev/null
+++ b/Tables/Input/pm/status2.kti
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 2 status keys.
+note The keys behind the status cells are named Status.1 and Status.2.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
diff --git a/Tables/Input/pm/status20.kti b/Tables/Input/pm/status20.kti
new file mode 100644
index 0000000..b0dc9a5
--- /dev/null
+++ b/Tables/Input/pm/status20.kti
@@ -0,0 +1,71 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 20 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.20.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFLOAD
+bind !Status.10 PREFMENU
+bind !Status.11 PREFSAVE
+
+assign toggleKeys Status.12
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.13
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.14
+assign toggleCommand SKPIDLNS
+include ../toggle.kti
+
+assign toggleKeys Status.15
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+bind Status.16 RESTARTBRL
+
+assign toggleKeys Status.17
+assign toggleCommand AUTOSPEAK
+include ../toggle.kti
+
+assign toggleKeys Status.18
+assign toggleCommand AUTOREPEAT
+include ../toggle.kti
+
+bind Status.19 TIME
+bind !Status.20 PASTE
diff --git a/Tables/Input/pm/status22.kti b/Tables/Input/pm/status22.kti
new file mode 100644
index 0000000..b496f3c
--- /dev/null
+++ b/Tables/Input/pm/status22.kti
@@ -0,0 +1,76 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 22 status keys.
+note The keys to the left of the status cells are named Status.1 through Status.22.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
+bind !Status.5 BACK
+
+assign toggleKeys Status.6
+assign toggleCommand CSRTRK
+include ../toggle.kti
+
+assign toggleKeys Status.7
+assign toggleCommand DISPMD
+include ../toggle.kti
+
+assign toggleKeys Status.8
+assign toggleCommand FREEZE
+include ../toggle.kti
+
+bind !Status.9 PREFLOAD
+bind !Status.10 PREFMENU
+bind !Status.11 PREFSAVE
+
+assign toggleKeys Status.12
+assign toggleCommand CSRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.13
+assign toggleCommand ATTRVIS
+include ../toggle.kti
+
+assign toggleKeys Status.14
+assign toggleCommand SKPIDLNS
+include ../toggle.kti
+
+assign toggleKeys Status.15
+assign toggleCommand SIXDOTS
+include ../toggle.kti
+
+bind !Status.16 RESTARTBRL
+bind !Status.17 RESTARTSPEECH
+
+assign toggleKeys Status.18
+assign toggleCommand AUTOSPEAK
+include ../toggle.kti
+
+assign toggleKeys Status.19
+assign toggleCommand AUTOREPEAT
+include ../toggle.kti
+
+assign toggleKeys Status.20
+assign toggleCommand BRLUCDOTS
+include ../toggle.kti
+
+bind !Status.21 TIME
+bind !Status.22 PASTE
diff --git a/Tables/Input/pm/status4.kti b/Tables/Input/pm/status4.kti
new file mode 100644
index 0000000..f7b3fa1
--- /dev/null
+++ b/Tables/Input/pm/status4.kti
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have 4 status keys.
+note The keys behind the status cells are named Status.1 through Status.4.
+
+bind !Status.1 HELP
+bind !Status.2 LEARN
+bind !Status.3 CSRJMP_VERT
+bind !Status.4 INFO
diff --git a/Tables/Input/pm/switches.kti b/Tables/Input/pm/switches.kti
new file mode 100644
index 0000000..f74cdac
--- /dev/null
+++ b/Tables/Input/pm/switches.kti
@@ -0,0 +1,164 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This key subtable is for Papenmeier displays which have switches.
+
+ifVar keyEmulation
+  note The left and right switches are emulated. Enter key emulation mode by
+  note * pressing Thumb1 + Thumb2 (Left Windows + Right Windows). Then press:
+  note + Left Switch Rear: Dot 1 (f)
+  note + Left Switch Center: Dot 2 (d)
+  note + Left Switch Front: Dot 3 (s)
+  note + Right Switch Rear: Dot 4 (j)
+  note + Right Switch Center: Dot 5 (k)
+  note + Right Switch Front: Dot 6 (l)
+  note + center both switches: Dot 7 (a)
+else
+  note The rockers at each side of the top that stay when pressed
+  note * are named the Left and Right Switch.
+endIf
+
+context switchesRearCenter Advanced Vertical Navigation (left switch rear)
+
+bind BarUp1 PRDIFLN
+bind BarDown1 NXDIFLN
+bind BarUp2 ATTRUP
+bind BarDown2 ATTRDN
+bind BarLeft1 PRPROMPT
+bind BarRight1 NXPROMPT
+bind BarLeft2 PRPGRPH
+bind BarRight2 NXPGRPH
+
+hide on
+bind BarUp1+BarUp2 ATTRUP
+bind BarDown1+BarDown2 ATTRDN
+bind BarLeft1+BarLeft2 PRPGRPH
+bind BarRight1+BarRight2 NXPGRPH
+hide off
+
+
+context switchesFrontCenter Specialized Navigation (left switch front)
+
+bind BarUp1 PRSEARCH
+bind BarDown1 NXSEARCH
+bind BarUp2 HELP
+bind BarDown2 LEARN
+bind BarLeft1 CHRLT
+bind BarRight1 CHRRT
+bind BarLeft2 HWINLT
+bind BarRight2 HWINRT
+
+hide on
+bind BarUp1+BarUp2 HELP
+bind BarDown1+BarDown2 LEARN
+bind BarLeft1+BarLeft2 HWINLT
+bind BarRight1+BarRight2 HWINRT
+hide off
+
+
+context switchesCenterRear Content-based Navigation (right switch rear)
+
+bind BarUp1 KEY_CURSOR_UP
+bind BarDown1 KEY_CURSOR_DOWN
+bind BarUp2 KEY_PAGE_UP
+bind BarDown2 KEY_PAGE_DOWN
+bind BarLeft1 FWINLT+route
+bind BarRight1 FWINRT+route
+bind BarLeft2 LNBEG+route
+bind BarRight2 LNEND+route
+
+hide on
+bind BarUp1+BarUp2 KEY_PAGE_UP
+bind BarDown1+BarDown2 KEY_PAGE_DOWN
+bind BarLeft1+BarLeft2 LNBEG+route
+bind BarRight1+BarRight2 LNEND+route
+hide off
+
+
+context switchesCenterFront Function Key Emulation (right switch front)
+
+bind BarUp1 KEY_CURSOR_UP
+bind BarDown1 KEY_CURSOR_DOWN
+bind BarUp2 KEY_PAGE_UP
+bind BarDown2 KEY_PAGE_DOWN
+bind BarLeft1 KEY_CURSOR_LEFT
+bind BarRight1 KEY_CURSOR_RIGHT
+bind BarLeft2 KEY_HOME
+bind BarRight2 KEY_END
+
+hide on
+bind BarUp1+BarUp2 KEY_PAGE_UP
+bind BarDown1+BarDown2 KEY_PAGE_DOWN
+bind BarLeft1+BarLeft2 KEY_HOME
+bind BarRight1+BarRight2 KEY_END
+hide off
+
+
+context switchesRearRear Unused (left switch rear, right switch rear)
+
+
+context switchesRearFront Unused (left switch rear, right switch front)
+
+
+context switchesFrontRear Unused (left switch front, right switch rear)
+
+
+context switchesFrontFront Unused (left switch front, right switch front)
+
+
+context switchesRearCenter
+hotkey LeftSwitchRear NOOP CONTEXT+default
+hotkey RightSwitchRear CONTEXT+switchesRearRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesRearFront NOOP
+
+context switchesFrontCenter
+hotkey LeftSwitchFront NOOP CONTEXT+default
+hotkey RightSwitchRear CONTEXT+switchesFrontRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesFrontFront NOOP
+
+context switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+default
+hotkey LeftSwitchRear CONTEXT+switchesRearRear NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontRear NOOP
+
+context switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+default
+hotkey LeftSwitchRear CONTEXT+switchesRearFront NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontFront NOOP
+
+context switchesRearRear
+hotkey LeftSwitchRear NOOP CONTEXT+switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+switchesRearCenter
+
+context switchesRearFront
+hotkey LeftSwitchRear NOOP CONTEXT+switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+switchesRearCenter
+
+context switchesFrontRear
+hotkey LeftSwitchFront NOOP CONTEXT+switchesCenterRear
+hotkey RightSwitchRear NOOP CONTEXT+switchesFrontCenter
+
+context switchesFrontFront
+hotkey LeftSwitchFront NOOP CONTEXT+switchesCenterFront
+hotkey RightSwitchFront NOOP CONTEXT+switchesFrontCenter
+
+context default
+hotkey LeftSwitchRear CONTEXT+switchesRearCenter NOOP
+hotkey LeftSwitchFront CONTEXT+switchesFrontCenter NOOP
+hotkey RightSwitchRear CONTEXT+switchesCenterRear NOOP
+hotkey RightSwitchFront CONTEXT+switchesCenterFront NOOP
diff --git a/Tables/Input/pm/trio.ktb b/Tables/Input/pm/trio.ktb
new file mode 100644
index 0000000..3702556
--- /dev/null
+++ b/Tables/Input/pm/trio.ktb
@@ -0,0 +1,23 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Papenmeier BrailleX Trio
+
+assign statusKeys 0
+include bar.kti
+include keyboard.kti
diff --git a/Tables/Input/sk/bdp.ktb b/Tables/Input/sk/bdp.ktb
new file mode 100644
index 0000000..685119b
--- /dev/null
+++ b/Tables/Input/sk/bdp.ktb
@@ -0,0 +1,138 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Seika Braille Displays
+
+note The round keys to the left/right of the braille cells are named K1 and K8.
+note The left/right ends of the left rocker are named K2 and K3.
+note The long keys to the left/right of the center are named K4 and K5.
+note The left/right ends of the right rocker are named K6 and K7.
+
+####################
+# Default Bindings #
+####################
+
+bind !RoutingKey ROUTE
+
+bind K1 FWINLT
+bind K8 FWINRT
+
+bind K2 LNUP
+bind K3 LNDN
+bind K2+K3 LNBEG
+
+bind K6 FWINLTSKIP
+bind K7 FWINRTSKIP
+bind K6+K7 PASTE
+
+bind K4 CSRTRK
+bind K5 RETURN
+bind K4+K5 CSRJMP_VERT
+
+bind K6+K2 TOP_LEFT
+bind K6+K3 BOT_LEFT
+bind K7+K2 TOP
+bind K7+K3 BOT
+
+bind K4+K2 ATTRUP
+bind K4+K3 ATTRDN
+bind K5+K2 PRDIFLN
+bind K5+K3 NXDIFLN
+bind K4+K6 PRPROMPT
+bind K4+K7 NXPROMPT
+bind K5+K6 PRPGRPH
+bind K5+K7 NXPGRPH
+
+bind K1+K8+K2 CONTEXT+PRINDENT
+bind K1+K8+K3 CONTEXT+NXINDENT
+bind K1+K8+K4 CONTEXT+SETLEFT
+bind K1+K8+K5 CONTEXT+DESCCHAR
+bind K1+K8+K6 CONTEXT+PRDIFCHAR
+bind K1+K8+K7 CONTEXT+NXDIFCHAR
+
+bind K1+K8+K2+K6 CONTEXT+CLIP_NEW
+bind K1+K8+K2+K7 CONTEXT+CLIP_ADD
+bind K1+K8+K3+K6 CONTEXT+COPY_LINE
+bind K1+K8+K3+K7 CONTEXT+COPY_RECT
+
+bind K1+K2 HELP
+bind K1+K3 LEARN
+bind K1+K4 PREFLOAD
+bind K1+K5 PREFSAVE
+bind K1+K6 PREFMENU
+bind K1+K7 INFO
+
+bind K8+K2 DISPMD
+bind K8+K3 FREEZE
+bind K8+K6 SIXDOTS
+bind K8+K7 SKPIDLNS
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind K1 FWINLT
+bind K8 FWINRT
+
+bind K2 MENU_PREV_ITEM
+bind K3 MENU_NEXT_ITEM
+
+bind K6 MENU_PREV_SETTING
+bind K7 MENU_NEXT_SETTING
+
+bind K4 MENU_FIRST_ITEM
+bind K5 MENU_LAST_ITEM
+
+
+############################
+# Routing Key Alternatives #
+############################
+
+context CLIP_NEW
+bind !RoutingKey CLIP_NEW
+
+context CLIP_ADD
+bind !RoutingKey CLIP_ADD
+
+context COPY_LINE
+bind !RoutingKey COPY_LINE
+
+context COPY_RECT
+bind !RoutingKey COPY_RECT
+
+context SETLEFT
+bind !RoutingKey SETLEFT
+
+context DESCCHAR
+bind !RoutingKey DESCCHAR
+
+context PRINDENT
+bind !RoutingKey PRINDENT
+
+context NXINDENT
+bind !RoutingKey NXINDENT
+
+context PRDIFCHAR
+bind !RoutingKey PRDIFCHar
+
+context NXDIFCHAR
+bind !RoutingKey NXDIFCHar
+
diff --git a/Tables/Input/sk/ntk.ktb b/Tables/Input/sk/ntk.ktb
new file mode 100644
index 0000000..0ef1275
--- /dev/null
+++ b/Tables/Input/sk/ntk.ktb
@@ -0,0 +1,144 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Seika Note Takers
+
+####################
+# Default Bindings #
+####################
+
+bind LeftJoystickPress HELP
+bind RightJoystickPress LEARN
+
+bind LeftButton+RightButton HOME
+bind LeftButton FWINLT
+bind RightButton FWINRT
+
+bind LeftJoystickUp top
+bind LeftJoystickDown BOT
+bind LeftJoystickLeft LNBEG
+bind LeftJoystickRight LNEND
+
+bind RightJoystickUp LNUP
+bind RightJoystickDown LNDN
+bind RightJoystickLeft CHRLT
+bind RightJoystickRight CHRRT
+
+bind LeftJoystickLeft+RightJoystickUp ATTRUP
+bind LeftJoystickLeft+RightJoystickDown ATTRDN
+bind LeftJoystickRight+RightJoystickUp PRDIFLN
+bind LeftJoystickRight+RightJoystickDown NXDIFLN
+bind LeftJoystickUp+RightJoystickUp PRPROMPT
+bind LeftJoystickUp+RightJoystickDown NXPROMPT
+bind LeftJoystickDown+RightJoystickUp PRPGRPH
+bind LeftJoystickDown+RightJoystickDown NXPGRPH
+
+bind LeftJoystickLeft+RightJoystickLeft HWINLT
+bind LeftJoystickLeft+RightJoystickRight HWINRT
+bind LeftJoystickRight+RightJoystickLeft FWINLTSKIP
+bind LeftJoystickRight+RightJoystickRight FWINRTSKIP
+bind LeftJoystickUp+RightJoystickLeft PRSEARCH
+bind LeftJoystickUp+RightJoystickRight NXSEARCH
+bind LeftJoystickDown+RightJoystickLeft BACK
+bind LeftJoystickDown+RightJoystickRight CSRJMP_VERT
+
+bind LeftJoystickPress+RightJoystickUp INFO
+bind LeftJoystickPress+RightJoystickDown PREFMENU
+bind LeftJoystickPress+RightJoystickLeft PREFLOAD
+bind LeftJoystickPress+RightJoystickRight PREFSAVE
+
+bind RoutingKey ROUTE
+bind LeftButton+RoutingKey SETLEFT
+bind RightButton+RoutingKey DESCCHAR
+
+bind RightJoystickUp+RoutingKey PRINDENT
+bind RightJoystickDown+RoutingKey NXINDENT
+bind RightJoystickLeft+RoutingKey PRDIFCHAR
+bind RightJoystickRight+RoutingKey NXDIFCHAR
+
+bind LeftJoystickPress+RoutingKey SETMARK
+bind RightJoystickPress+RoutingKey GOTOMARK
+
+bind LeftJoystickPress+RightJoystickPress PASTE
+bind RoutingKey+RoutingKey CLIP_COPY
+bind LeftJoystickUp+RoutingKey+RoutingKey CLIP_APPEND
+bind LeftJoystickLeft+RoutingKey CLIP_NEW
+bind LeftJoystickUp+RoutingKey CLIP_ADD
+bind LeftJoystickRight+RoutingKey COPY_LINE
+bind LeftJoystickDown+RoutingKey COPY_RECT
+
+assign chord Space+
+include ../chords.kti
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Backspace META
+map Space SPACE
+
+bind Space+LeftJoystickUp KEY_PAGE_UP
+bind Space+LeftJoystickDown KEY_PAGE_DOWN
+bind Space+LeftJoystickLeft KEY_HOME
+bind Space+LeftJoystickRight KEY_END
+bind Space+LeftJoystickPress KEY_INSERT
+
+bind Space+RightJoystickUp KEY_CURSOR_UP
+bind Space+RightJoystickDown KEY_CURSOR_DOWN
+bind Space+RightJoystickLeft KEY_CURSOR_LEFT
+bind Space+RightJoystickRight KEY_CURSOR_RIGHT
+bind Space+RightJoystickPress KEY_DELETE
+
+bind Space+RoutingKey KEY_FUNCTION
+bind Backspace+RoutingKey SWITCHVT
+
+bind Backspace+LeftJoystickUp SAY_LOUDER
+bind Backspace+LeftJoystickDown SAY_SOFTER
+bind Backspace+LeftJoystickLeft SAY_SLOWER
+bind Backspace+LeftJoystickRight SAY_FASTER
+bind Backspace+LeftJoystickPress AUTOSPEAK
+
+bind Backspace+RightJoystickUp SAY_ABOVE
+bind Backspace+RightJoystickDown SAY_BELOW
+bind Backspace+RightJoystickLeft MUTE
+bind Backspace+RightJoystickRight SAY_LINE
+bind Backspace+RightJoystickPress SPKHOME
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind RightJoystickPress PREFMENU
+bind RightJoystickUp MENU_PREV_ITEM
+bind RightJoystickDown MENU_NEXT_ITEM
+bind RightJoystickLeft MENU_PREV_SETTING
+bind RightJoystickRight MENU_NEXT_SETTING
+
+bind LeftJoystickUp MENU_FIRST_ITEM
+bind LeftJoystickDown MENU_LAST_ITEM
+bind LeftJoystickLeft PREFLOAD
+bind LeftJoystickRight PREFSAVE
+
+
diff --git a/Tables/Input/speech.kti b/Tables/Input/speech.kti
new file mode 100644
index 0000000..a67f1a1
--- /dev/null
+++ b/Tables/Input/speech.kti
@@ -0,0 +1,57 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{speech}Dot1 AUTOSPEAK+on
+bind \{speech}Dot2 MUTE
+bind \{speech}Dot3 AUTOSPEAK+off
+bind \{speech}Dot7 SPEAK_CURR_LOCN
+
+bind \{speech}Dot4 SAY_ABOVE
+bind \{speech}Dot5 SAY_LINE
+bind \{speech}Dot6 SAY_BELOW
+bind \{speech}Dot8 SAY_ALL
+
+bind \{speech}Dot1+Dot4 SPEAK_PREV_LINE
+bind \{speech}Dot1+Dot5 SPEAK_CURR_LINE
+bind \{speech}Dot1+Dot6 SPEAK_NEXT_LINE
+bind \{speech}Dot1+Dot8 SPEAK_INDENT
+
+bind \{speech}Dot2+Dot4 SPEAK_PREV_WORD
+bind \{speech}Dot2+Dot5 SPEAK_CURR_WORD
+bind \{speech}Dot2+Dot6 SPEAK_NEXT_WORD
+bind \{speech}Dot2+Dot8 SPELL_CURR_WORD
+
+bind \{speech}Dot3+Dot4 SPEAK_PREV_CHAR
+bind \{speech}Dot3+Dot5 SPEAK_CURR_CHAR
+bind \{speech}Dot3+Dot6 SPEAK_NEXT_CHAR
+bind \{speech}Dot3+Dot8 DESC_CURR_CHAR
+
+bind \{speech}Dot7+Dot4 SPEAK_FRST_LINE
+bind \{speech}Dot7+Dot5 SPEAK_FRST_CHAR
+bind \{speech}Dot7+Dot6 SPEAK_LAST_CHAR
+bind \{speech}Dot7+Dot8 SPEAK_LAST_LINE
+
+bind \{speech}Space+Dot1 SAY_SOFTER
+bind \{speech}Space+Dot4 SAY_LOUDER
+bind \{speech}Space+Dot2 SAY_SLOWER
+bind \{speech}Space+Dot5 SAY_FASTER
+bind \{speech}Space+Dot3 SAY_LOWER
+bind \{speech}Space+Dot6 SAY_HIGHER
+bind \{speech}Space+Dot7 ROUTE_CURR_LOCN
+bind \{speech}Space+Dot8 RETURN
+
diff --git a/Tables/Input/tn/all.txt b/Tables/Input/tn/all.txt
new file mode 100644
index 0000000..35a58f4
--- /dev/null
+++ b/Tables/Input/tn/all.txt
@@ -0,0 +1,51 @@
+Help: TechniBraille
+
+Combinations of dots 1-8: Keyboard input.
+Key below dot 7: Escape key.
+Key below dot 8: Tab key.
+Left Thumb: Backspace key.
+Right Thumb: Space bar.
+Both Thumbs: Enter key.
+
+The two keys at the left rear (2 columns, 1 row):
+ESC [1,1]: Enter/leave learn mode.
+M [2,1]: Enter/leave preferences menu.
+
+The four keys at the left middle (cross):
+Top/Bottom: Go up/down one line.
+Left/Right: Go left/right one window.
+
+The six keys at the left front (2 columns, 3 row):
+Ins [1,1]: Go to cursor (or undo cursor tracking motion).
+E [2,1]: Go to top line.
+Supp [1,2]: Toggle cursor tracking.
+L [2,2]: Go to bottom line.
+X1 [1,3]: Go left one character.
+X2 [2,3]: Go right one character.
+
+The one key at the right rear (1 column, 1 row):
+X3 [1,1]: Enter/leave status display.
+
+The two keys at the right rear (1 column, 2 rows):
+X4 [1,1]: Go up to line with different content.
+X5 [1,2]: Go down to line with different content.
+
+The four keys at the right rear (1 column, 4 rows):
+/ [1,1}: Toggle frozen screen.
+* [1,2}: Toggle attributes display.
+- [1,3}: Toggle highlight underline.
++ [1,4}: Toggle cursor visibility.
+
+The twelve keys of the numeric pad (3 columns, 4 rows):
+7 [1,1]: Home key.
+8 [2,1]: CursorUp key.
+9 [3,1]: PageUp key.
+4 [1,2]: CursorLeft key.
+5 [2,2]: Route cursor to current line.
+6 [3,2]: CursorRight key.
+1 [1,3]: End key.
+2 [2,3]: CursorDown key.
+3 [3,3]: PageDown key.
+VerrN3m [1,4]: Toggle six-dot braille.
+0 [2,4]: Insert key.
+. [3,4]: Delete key.
diff --git a/Tables/Input/toggle.kti b/Tables/Input/toggle.kti
new file mode 100644
index 0000000..65615d9
--- /dev/null
+++ b/Tables/Input/toggle.kti
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{toggleKeys} \{toggleCommand}
+bind \{toggleKeys}+\{toggleOn} \{toggleCommand}+on
+bind \{toggleKeys}+\{toggleOff} \{toggleCommand}+off
diff --git a/Tables/Input/ts/nav.kti b/Tables/Input/ts/nav.kti
new file mode 100644
index 0000000..a105aed
--- /dev/null
+++ b/Tables/Input/ts/nav.kti
@@ -0,0 +1,124 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{keyNavUp} LNUP
+bind \{keyNavDown} LNDN
+
+bind \{keyNavLeft} FWINLT
+bind \{keyNavRight} FWINRT
+
+bind \{keyThumbRight} HOME
+bind \{keyThumbRight}+\{keyCursorUp} BACK
+
+bind \{keyThumbLeft} CSRTRK
+
+bind \{keyNavLeft}+\{keyNavUp} TOP_LEFT
+bind \{keyNavLeft}+\{keyNavDown} BOT_LEFT
+
+bind \{keyThumbRight}+\{keyNavUp} PRDIFLN
+bind \{keyThumbRight}+\{keyNavDown} NXDIFLN
+
+bind \{keyThumbLeft}+\{keyNavUp} ATTRUP
+bind \{keyThumbLeft}+\{keyNavDown} ATTRDN
+
+ifVar keyCursorLeft  bind \{keyThumbLeft}+\{keyCursorLeft} CHRLT
+ifVar keyCursorRight  bind \{keyThumbLeft}+\{keyCursorRight} CHRRT
+
+ifVar keyCursorLeft  bind \{keyCursorUp}+\{keyCursorLeft} HWINLT
+ifVar keyCursorRight  bind \{keyCursorUp}+\{keyCursorRight} HWINRT
+
+bind \{keyThumbLeft}+\{keyCursorUp} WINUP
+bind \{keyThumbLeft}+\{keyCursorDown} WINDN
+
+bind \{keyCursorUp}+\{keyNavLeft} LNBEG
+bind \{keyCursorUp}+\{keyNavRight} LNEND
+
+ifVar keyCursorLeft  bind \{keyCursorLeft} KEY_CURSOR_LEFT
+ifVar keyCursorRight  bind \{keyCursorRight} KEY_CURSOR_RIGHT
+bind \{keyCursorUp} KEY_CURSOR_UP
+bind \{keyCursorDown} KEY_CURSOR_DOWN
+
+ifVar keyCursorLeft
+  ifVar keyCursorRight
+    bind \{keyCursorLeft}+\{keyCursorRight} HELP
+    bind \{keyCursorLeft}+\{keyCursorRight}+\{keyCursorUp}+\{keyCursorDown} LEARN
+  endIf
+endIf
+
+bind \{keyThumbLeft}+\{keyThumbRight} FREEZE
+bind \{keyCursorUp}+\{keyNavDown} INFO
+bind \{keyCursorDown}+\{keyNavUp} ATTRVIS
+bind \{keyCursorDown}+\{keyNavUp}+\{keyThumbLeft} DISPMD
+
+bind \{keyCursorDown}+\{keyNavDown} CSRJMP_VERT
+
+bind \{keyNavRight}+\{keyNavDown} SAY_LINE
+bind \{keyNavLeft}+\{keyNavRight}+\{keyNavDown} SAY_BELOW
+bind \{keyThumbRight}+\{keyNavRight} SPKHOME
+bind \{keyNavRight}+\{keyNavUp} MUTE
+bind \{keyCursorUp}+\{keyNavUp}+\{keyNavLeft}+\{keyNavRight} RESTARTSPEECH
+
+ifVar keyCursorRight
+  bind \{keyCursorRight}+\{keyNavLeft} SAY_LINE
+  bind \{keyCursorRight}+\{keyCursorUp}+\{keyNavLeft}+\{keyNavUp} MUTE
+endIf
+
+bind \{keyNavLeft}+\{keyNavRight} PREFMENU
+bind \{keyNavLeft}+\{keyNavRight}+\{keyThumbRight} PREFSAVE
+bind \{keyNavLeft}+\{keyNavRight}+\{keyThumbLeft}+\{keyThumbRight} PREFLOAD
+
+bind \{keyNavLeft}+\{keyNavRight}+\{keyNavDown}+\{keyThumbLeft} SKPIDLNS
+bind \{keyNavLeft}+\{keyNavRight}+\{keyNavDown}+\{keyThumbRight} SKPIDLNS
+bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorUp} CSRVIS
+bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorUp}+\{keyThumbLeft} CSRBLINK
+bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorDown} SIXDOTS
+bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorDown}+\{keyThumbLeft} CAPBLINK
+bind \{keyNavLeft}+\{keyNavRight}+\{keyNavUp}+\{keyThumbLeft} ATTRBLINK
+
+ifVar keyCursorLeft
+  bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorLeft} SLIDEWIN
+  bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorLeft}+\{keyThumbLeft} TUNES
+endIf
+
+ifVar keyCursorRight
+  bind \{keyNavLeft}+\{keyNavRight}+\{keyCursorRight}+\{keyThumbLeft} ATTRBLINK
+endIf
+
+ifKey RoutingKey
+  assign keyRoutingUp \{keyNavUp}
+  assign keyRoutingDown \{keyNavDown}
+  include routing.kti
+
+  bind RoutingKey+\{keyNavLeft} CLIP_NEW
+  ifVar keyCursorLeft  bind RoutingKey+\{keyCursorLeft} CLIP_ADD
+  bind RoutingKey+\{keyNavRight} COPY_RECT
+  ifVar keyCursorRight  bind RoutingKey+\{keyCursorRight} COPY_LINE
+
+  bind RoutingKey+\{keyCursorUp} SETLEFT
+  bind RoutingKey+\{keyCursorDown} DESCCHAR
+
+  bind RoutingKey+\{keyThumbLeft} SETMARK
+  bind RoutingKey+\{keyThumbRight} GOTOMARK
+endIf
+
+context menu
+bind \{keyThumbLeft} MENU_PREV_SETTING
+bind \{keyThumbRight} MENU_NEXT_SETTING
+bind \{keyCursorUp} MENU_PREV_SETTING
+bind \{keyCursorDown} MENU_NEXT_SETTING
+
diff --git a/Tables/Input/ts/nav20.ktb b/Tables/Input/ts/nav20.ktb
new file mode 100644
index 0000000..8806ab7
--- /dev/null
+++ b/Tables/Input/ts/nav20.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Navigator 20
+
+include nav_small.kti
diff --git a/Tables/Input/ts/nav40.ktb b/Tables/Input/ts/nav40.ktb
new file mode 100644
index 0000000..7a04220
--- /dev/null
+++ b/Tables/Input/ts/nav40.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Navigator 40
+
+include nav_small.kti
diff --git a/Tables/Input/ts/nav80.ktb b/Tables/Input/ts/nav80.ktb
new file mode 100644
index 0000000..19f32d2
--- /dev/null
+++ b/Tables/Input/ts/nav80.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Navigator 80
+
+assign routingRight1 80
+assign routingRight2 79
+assign routingRight3 78
+include nav_large.kti
diff --git a/Tables/Input/ts/nav_large.kti b/Tables/Input/ts/nav_large.kti
new file mode 100644
index 0000000..c943dfd
--- /dev/null
+++ b/Tables/Input/ts/nav_large.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign keyCursorLeft CursorLeft
+assign keyCursorRight CursorRight
+assign keyCursorUp CursorUp
+assign keyCursorDown CursorDown
+
+assign keyNavLeft LeftOuter
+assign keyNavRight RightOuter
+assign keyNavUp LeftInner
+assign keyNavDown RightInner
+
+assign keyThumbLeft LeftThumb
+assign keyThumbRight RightThumb
+
+note The keys of the four-key pad in the middle are named:
+note \{keyCursorLeft}, \{keyCursorRight}, \{keyCursorUp}, \{keyCursorDown}.
+note The keys on each side of the pad are named: \{keyThumbLeft}, \{keyThumbRight}.
+note The outer end keys are named: \{keyNavLeft}, \{keyNavRight}.
+note The inner end keys are named: \{keyNavUp}, \{keyNavDown}.
+
+include nav.kti
diff --git a/Tables/Input/ts/nav_small.kti b/Tables/Input/ts/nav_small.kti
new file mode 100644
index 0000000..e9b4877
--- /dev/null
+++ b/Tables/Input/ts/nav_small.kti
@@ -0,0 +1,38 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign keyCursorLeft CursorLeft
+assign keyCursorRight CursorRight
+assign keyCursorUp CursorUp
+assign keyCursorDown CursorDown
+
+assign keyNavLeft NavLeft
+assign keyNavRight NavRight
+assign keyNavUp NavUp
+assign keyNavDown NavDown
+
+assign keyThumbLeft ThumbLeft
+assign keyThumbRight ThumbRight
+
+note The keys of the four-key pad on the left are named:
+note \{keyCursorLeft}, \{keyCursorRight}, \{keyCursorUp}, \{keyCursorDown}.
+note The keys of the four-key pad on the right are named:
+note \{keyNavLeft}, \{keyNavRight}, \{keyNavUp}, \{keyNavDown}.
+note The keys in between the pads are named: \{keyThumbLeft}, \{keyThumbRight}.
+
+include nav.kti
diff --git a/Tables/Input/ts/pb.kti b/Tables/Input/ts/pb.kti
new file mode 100644
index 0000000..fd00f5a
--- /dev/null
+++ b/Tables/Input/ts/pb.kti
@@ -0,0 +1,121 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RightRockerUp LNUP
+bind RightRockerDown LNDN
+
+bind Bar1 LNUP
+bind Bar2 LNDN
+bind Bar3 LNDN
+bind Bar4 LNDN
+
+bind Button3 FWINLT
+bind Button4 FWINRT
+
+bind Concave HOME
+bind Concave+LeftRockerUp BACK
+
+bind Convex CSRTRK
+
+bind Button1+Bar1 TOP_LEFT
+bind Button1+Bar2 BOT_LEFT
+
+bind Button2+Bar1 PRDIFLN
+bind Button2+Bar2 NXDIFLN
+bind Button2+Bar3 NXDIFLN
+bind Button2+Bar4 NXDIFLN
+
+bind Button2+RightRockerUp ATTRUP
+bind Button2+RightRockerDown ATTRDN
+
+bind Convex+Button1 CHRLT
+bind Convex+Button2 CHRRT
+
+bind LeftRockerUp+Button1 HWINLT
+bind LeftRockerUp+Button2 HWINRT
+
+bind Convex+LeftRockerUp WINUP
+bind Convex+LeftRockerDown WINDN
+
+bind Button1+Button2+Bar1 WINUP
+bind Button1+Button2+Bar2 WINDN
+
+bind LeftRockerUp+Button3 LNBEG
+bind LeftRockerUp+Button4 LNEND
+
+bind Button1 KEY_CURSOR_LEFT
+bind Button2 KEY_CURSOR_RIGHT
+bind LeftRockerUp KEY_CURSOR_UP
+bind LeftRockerDown KEY_CURSOR_DOWN
+
+bind Button1+Button2 HELP
+bind Button1+Button2+Button3+Button4 LEARN
+bind Button3+Button4 INFO
+
+bind LeftRockerDown+RightRockerDown CSRJMP_VERT
+
+bind Bar2+RightRockerDown SAY_LINE
+bind Bar1+Bar2+RightRockerDown SAY_BELOW
+bind Concave+Bar2 SPKHOME
+bind Bar2+RightRockerUp MUTE
+bind Bar1+Bar2+LeftRockerUp+RightRockerUp RESTARTSPEECH
+
+bind Bar1+Bar2 PREFMENU
+bind Bar1+Bar2+Concave PREFSAVE
+bind Bar1+Bar2+Convex+Concave PREFLOAD
+
+bind Bar1+Bar2+Button1 CSRVIS
+bind Bar1+Bar2+Button1+Convex CSRBLINK
+bind Bar1+Bar2+Button2 TUNES
+bind Bar1+Bar2+Button2+Convex CAPBLINK
+bind Bar1+Bar2+LeftRockerDown SIXDOTS
+
+bind Switch1Up ATTRVIS+on
+bind Switch1Down ATTRVIS+off
+
+bind Switch1Up+Convex ATTRBLINK+on
+bind Switch1Down+Convex ATTRBLINK+off
+
+bind Switch2Up FREEZE+on
+bind Switch2Down FREEZE+off
+
+bind Switch3Up SKPIDLNS+on
+bind Switch3Down SKPIDLNS+off
+
+bind Switch4Up DISPMD+on
+bind Switch4Down DISPMD+off
+
+assign keyRoutingUp RightRockerUp
+assign keyRoutingDown RightRockerDown
+include routing.kti
+
+bind RoutingKey+Button1 CLIP_NEW
+bind RoutingKey+Button2 CLIP_ADD
+bind RoutingKey+Button3 COPY_RECT
+bind RoutingKey+Button4 COPY_LINE
+
+bind RoutingKey+LeftRockerUp SETLEFT
+bind RoutingKey+LeftRockerDown SWITCHVT
+
+bind RoutingKey+Convex SETMARK
+bind RoutingKey+Concave GOTOMARK
+
+context menu
+bind Convex MENU_PREV_SETTING
+bind Concave MENU_NEXT_SETTING
+
diff --git a/Tables/Input/ts/pb40.ktb b/Tables/Input/ts/pb40.ktb
new file mode 100644
index 0000000..f8a9f6b
--- /dev/null
+++ b/Tables/Input/ts/pb40.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Power Braille 40
+
+assign routingRight1 40
+assign routingRight2 39
+assign routingRight3 38
+include pb_small.kti
diff --git a/Tables/Input/ts/pb65.ktb b/Tables/Input/ts/pb65.ktb
new file mode 100644
index 0000000..023ad5a
--- /dev/null
+++ b/Tables/Input/ts/pb65.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Power Braille 65
+
+assign routingRight1 65
+assign routingRight2 64
+assign routingRight3 63
+include pb_large.kti
diff --git a/Tables/Input/ts/pb80.ktb b/Tables/Input/ts/pb80.ktb
new file mode 100644
index 0000000..147a813
--- /dev/null
+++ b/Tables/Input/ts/pb80.ktb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Power Braille 80
+
+assign routingRight1 81
+assign routingRight2 80
+assign routingRight3 79
+include pb_large.kti
diff --git a/Tables/Input/ts/pb_large.kti b/Tables/Input/ts/pb_large.kti
new file mode 100644
index 0000000..65bc8f0
--- /dev/null
+++ b/Tables/Input/ts/pb_large.kti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+note The keys on the top, from left to right, are named:
+note Button1, Button2, Bar1, Bar2, Bar3, Bar4, Button3, Button4.
+note The keys on the front, from left to right, are named:
+note Switch1, Switch2, LeftRocker, Convex, Concave, RightRocker, Switch3, Switch4.
+
+include pb.kti
diff --git a/Tables/Input/ts/pb_small.kti b/Tables/Input/ts/pb_small.kti
new file mode 100644
index 0000000..b43d805
--- /dev/null
+++ b/Tables/Input/ts/pb_small.kti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+assign keyCursorUp LeftRockerUp
+assign keyCursorDown LeftRockerDown
+
+assign keyNavLeft Backward
+assign keyNavRight Forward
+assign keyNavUp RightRockerUp
+assign keyNavDown RightRockerDown
+
+assign keyThumbLeft Convex
+assign keyThumbRight Concave
+
+note The positions of the left bar are named: \{keyCursorUp}, \{keyCursorDown}.
+note The positions of the right bar are named: \{keyNavUp}, \{keyNavDown}.
+note The keys in the middle are named: \{keyThumbLeft}, \{keyThumbRight}.
+note The keys on the top are named: \{keyNavLeft} (on the left), \{keyNavRight} (on the right).
+
+include nav.kti
diff --git a/Tables/Input/ts/routing.kti b/Tables/Input/ts/routing.kti
new file mode 100644
index 0000000..99512d9
--- /dev/null
+++ b/Tables/Input/ts/routing.kti
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind RoutingKey ROUTE
+bind RoutingKey+RoutingKey CLIP_COPY
+
+bind RoutingKey.1+RoutingKey.\{routingRight1} HELP
+bind RoutingKey.1+RoutingKey.2+RoutingKey.\{routingRight2}+RoutingKey.\{routingRight1} LEARN
+
+bind RoutingKey.1+RoutingKey.2 CHRLT
+bind RoutingKey.\{routingRight2}+RoutingKey.\{routingRight1} CHRRT
+
+bind RoutingKey.1+RoutingKey.3 HWINLT
+bind RoutingKey.\{routingRight3}+RoutingKey.\{routingRight1} HWINRT
+
+bind RoutingKey.2+RoutingKey.3 PASTE
+
+bind RoutingKey+\{keyRoutingUp} PRINDENT
+bind RoutingKey+\{keyRoutingDown} NXINDENT
+
+bind RoutingKey.1+RoutingKey.2+\{keyRoutingUp} PRPGRPH
+bind RoutingKey.1+RoutingKey.2+\{keyRoutingDown} NXPGRPH
+
+bind RoutingKey.1+RoutingKey.3+\{keyRoutingUp} PRSEARCH
+bind RoutingKey.1+RoutingKey.3+\{keyRoutingDown} NXSEARCH
+
+bind RoutingKey.2+RoutingKey.3+\{keyRoutingUp} PRPROMPT
+bind RoutingKey.2+RoutingKey.3+\{keyRoutingDown} NXPROMPT
+
diff --git a/Tables/Input/tt/all.txt b/Tables/Input/tt/all.txt
new file mode 100644
index 0000000..3d933b5
--- /dev/null
+++ b/Tables/Input/tt/all.txt
@@ -0,0 +1,23 @@
+Help: TTY
+
+Basic Navigation:
+Left/Right Arrow: Go left/right one window.
+Up/Down Arrow: Go up/down one line.
+Previous/Next Page: Go to top/bottom  line.
+Home/End: Go to top-left/bottom-left corner.
+Insert: Go to cursor.
+
+Advanced Navigation:
+F5/F6: Go to previous/next line with different content.
+F7/F8: Go to previous/next line with different highlighting.
+F9/F12: Go to beginning/end of line.
+F10/F11: Go left/right one character.
+F17/F18: Go to previous/next command prompt.
+F19/F20: Go to nearest line of previous/next paragraph.
+
+Modes and Features:
+F1: Help screen (toggle).
+F2: Command learn mode (toggle).
+F3: Status line (toggle).
+F4: Preferences menu (toggle).
+Delete: Cursor tracking (toggle).
diff --git a/Tables/Input/vd/all.txt b/Tables/Input/vd/all.txt
new file mode 100644
index 0000000..ddb4a53
--- /dev/null
+++ b/Tables/Input/vd/all.txt
@@ -0,0 +1,26 @@
+Help: VideoBraille
+
+Movement keys:
+    Up  Up one line
+    Left+Up  Top left corner of screen
+    Left  Go left one window
+    Down  Down one line
+    Right+Down  Bottom left corner of screen
+    Right  Go right one window
+    Edit  Go back to cursor
+    Left+Curs  One character left
+    Right+Edit  One character right
+
+Other functions:
+    Curs  Toggle cursor tracking
+    Menu  Enter preferences menu
+    Attr  Toggle attributes
+    Menu+Curs  View info
+    Attr+Down  View help
+
+Cut & Paste
+    Small button+Edit  Mark beginning of block
+    Small button+Menu  Mark the end of block
+    Attr+Menu  Paste
+Warning: small button stands for one of the 40 small buttons on the display.
+Warning: don't press Edit or Menu before the small button.
diff --git a/Tables/Input/vo/all.ktb b/Tables/Input/vo/all.ktb
new file mode 100644
index 0000000..953928e
--- /dev/null
+++ b/Tables/Input/vo/all.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Voyager
+
+include all.kti
diff --git a/Tables/Input/vo/all.kti b/Tables/Input/vo/all.kti
new file mode 100644
index 0000000..5e5573b
--- /dev/null
+++ b/Tables/Input/vo/all.kti
@@ -0,0 +1,118 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+####################
+# Default Bindings #
+####################
+
+bind Thumb1+Thumb4 TIME
+
+bind RoutingKey ROUTE
+
+bind Thumb1 FWINLT
+bind Thumb4 FWINRT
+bind Thumb2 LNUP
+bind Thumb3 LNDN
+
+bind Thumb1+Thumb2 TOP_LEFT
+bind Thumb1+Thumb3 BOT_LEFT
+bind Thumb4+Thumb2 TOP
+bind Thumb4+Thumb3 BOT
+
+bind Up KEY_CURSOR_UP
+bind Down KEY_CURSOR_DOWN
+
+bind Left BACK
+bind Right HOME
+bind Left+Right CSRTRK
+
+bind Left+Up DISPMD
+bind Left+Down SIXDOTS
+bind Right+Up AUTOREPEAT
+bind Right+Down AUTOSPEAK
+
+assign chord Thumb2+
+include ../chords.kti
+
+bind Thumb1+RoutingKey CLIP_NEW
+bind Thumb2+RoutingKey CLIP_ADD
+bind Thumb3+RoutingKey COPY_LINE
+bind Thumb4+RoutingKey COPY_RECT
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind Thumb2+Thumb3 PASTE
+
+bind Thumb1+Up ATTRUP
+bind Thumb1+Down ATTRDN
+bind Thumb2+Up PRDIFLN
+bind Thumb2+Down NXDIFLN
+bind Thumb3+Up PRPROMPT
+bind Thumb3+Down NXPROMPT
+bind Thumb4+Up PRPGRPH
+bind Thumb4+Down NXPGRPH
+
+bind Thumb1+Left PRSEARCH
+bind Thumb1+Right NXSEARCH
+bind Thumb2+Left FWINLTSKIP
+bind Thumb2+Right FWINRTSKIP
+bind Thumb3+Left CHRLT
+bind Thumb3+Right CHRRT
+bind Thumb4+Left LNBEG
+bind Thumb4+Right LNEND
+
+Bind Left+RoutingKey SETLEFT
+Bind Right+RoutingKey DESCCHAR
+bind Up+RoutingKey SWITCHVT
+bind Down+RoutingKey KEY_FUNCTION
+
+bind Thumb1+Thumb2+RoutingKey PRINDENT
+bind Thumb1+Thumb3+RoutingKey NXINDENT
+bind Thumb4+Thumb2+RoutingKey PRDIFCHAR
+bind Thumb4+Thumb3+RoutingKey NXDIFCHAR
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map Thumb2 SPACE
+map Thumb3 SPACE
+map Left UPPER
+map Right CONTROL
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind Thumb2 MENU_PREV_ITEM
+bind Thumb3 MENU_NEXT_ITEM
+
+bind Up MENU_FIRST_ITEM
+bind Down MENU_LAST_ITEM
+
+bind Left MENU_PREV_SETTING
+bind Right MENU_NEXT_SETTING
+
+bind Left+Right PREFMENU
+bind Left+Up PREFLOAD
+bind Left+Down PREFSAVE
diff --git a/Tables/Input/vo/bp.ktb b/Tables/Input/vo/bp.ktb
new file mode 100644
index 0000000..4f6ceab
--- /dev/null
+++ b/Tables/Input/vo/bp.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Braille Pen 12
+
+include ../bp/all.kti
diff --git a/Tables/Input/vr/all.txt b/Tables/Input/vr/all.txt
new file mode 100644
index 0000000..beb63f4
--- /dev/null
+++ b/Tables/Input/vr/all.txt
@@ -0,0 +1,3 @@
+Help: Virtual (client/server)
+
+No help available for this driver.
diff --git a/Tables/Input/vs/all.txt b/Tables/Input/vs/all.txt
new file mode 100644
index 0000000..1b25ddc
--- /dev/null
+++ b/Tables/Input/vs/all.txt
@@ -0,0 +1,9 @@
+Help: VisioBraille
+
+Les touches du clavier auxiliaire s'utilisent comme sous DOS/Windows.
+Les opérations de changement de mode d'affichage (6-8 points), de simulation de
+touches... se font également comme sous DOS/Windows.
+En outre, quelques commandes ~~ suivi d'une lettre sont disponibles,
+reportez-vous au fichier README du répertoire VisioBraille pour en savoir
+plus.
+ 
diff --git a/Tables/Input/xw/all.txt b/Tables/Input/xw/all.txt
new file mode 100644
index 0000000..7020244
--- /dev/null
+++ b/Tables/Input/xw/all.txt
@@ -0,0 +1,3 @@
+Help: XWindow (graphical user interface)
+
+No help available for this driver.
diff --git a/Tables/Keyboard/braille.ktb b/Tables/Keyboard/braille.ktb
new file mode 100644
index 0000000..e429798
--- /dev/null
+++ b/Tables/Keyboard/braille.ktb
@@ -0,0 +1,60 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Bindings for Braille Keyboards
+
+bind Backward FWINLTSKIP
+bind Forward FWINRTSKIP
+
+bind Backward+ArrowLeft LNBEG
+bind Backward+ArrowRight LNEND
+bind Backward+ArrowUp TOP
+bind Backward+ArrowDown BOT
+bind Backward+Select BACK
+
+bind Forward+ArrowLeft FWINLT
+bind Forward+ArrowRight FWINRT
+bind Forward+ArrowUp LNUP
+bind Forward+ArrowDown LNDN
+bind Forward+Select HOME
+
+bind RoutingKey ROUTE
+bind Backward+RoutingKey SETLEFT
+bind Forward+RoutingKey DESCCHAR
+
+bind Backward+Forward PASTE
+bind RoutingKey+!RoutingKey CLIP_COPY
+bind ArrowLeft+RoutingKey CLIP_NEW
+bind ArrowUp+RoutingKey CLIP_ADD
+bind ArrowRight+RoutingKey COPY_LINE
+bind ArrowDown+RoutingKey COPY_RECT
+
+map Dot1 DOT1
+map Dot2 DOT2
+map Dot3 DOT3
+map Dot4 DOT4
+map Dot5 DOT5
+map Dot6 DOT6
+map Dot7 DOT7
+map Dot8 DOT8
+map BRAILLE_Space SPACE
+
+assign chord BRAILLE_Space+
+include ../Input/chords.kti
+bind BRAILLE_Space+RoutingKey KEY_FUNCTION
+
diff --git a/Tables/Keyboard/braille.kti b/Tables/Keyboard/braille.kti
new file mode 100644
index 0000000..31fbb7a
--- /dev/null
+++ b/Tables/Keyboard/braille.kti
@@ -0,0 +1,70 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+context braille Braille Input Mode
+
+ignore Grave
+ignore Minus
+ignore Equals
+ignore Backslash
+
+ignore q
+ignore w
+ignore e
+ignore r
+ignore t
+ignore y
+ignore u
+ignore i
+ignore o
+ignore p
+ignore LeftBracket
+ignore RightBracket
+
+map a DOT7
+map s DOT3
+map d DOT2
+map f DOT1
+map g SPACE
+map h SPACE
+map j DOT4
+map k DOT5
+map l DOT6
+map Semicolon DOT8
+ignore Apostrophe
+
+ignore z
+ignore x
+ignore c
+ignore v
+ignore b
+ignore n
+ignore m
+ignore Comma
+ignore Period
+ignore Slash
+
+map ShiftLeft UPPER
+map ShiftRight UPPER
+map ControlLeft CONTROL
+map ControlRight CONTROL
+map AltLeft META
+
+context default
+bind \{brailleOn} CONTEXT+braille
+bind \{brailleOff} CONTEXT+default
diff --git a/Tables/Keyboard/desktop.ktb b/Tables/Keyboard/desktop.ktb
new file mode 100644
index 0000000..2da131d
--- /dev/null
+++ b/Tables/Keyboard/desktop.ktb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Bindings for Full Keyboards
+
+include desktop.kti
diff --git a/Tables/Keyboard/desktop.kti b/Tables/Keyboard/desktop.kti
new file mode 100644
index 0000000..834c24d
--- /dev/null
+++ b/Tables/Keyboard/desktop.kti
@@ -0,0 +1,62 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind KP0+!KP5 SAY_LINE
+bind KP0+!KP8 LNUP
+bind KP0+!KP2 LNDN
+bind ShiftLeft+KP0+!KP8 PRPROMPT
+bind ShiftLeft+KP0+!KP2 NXPROMPT
+bind ShiftLeft+KP0+!KP7 LNBEG
+bind ShiftLeft+KP0+!KP1 LNEND
+bind ControlLeft+KP0+!KP8 PRPGRPH
+bind ControlLeft+KP0+!KP2 NXPGRPH
+bind ControlLeft+KP0+!KP5 DESCCHAR
+bind KP0+!KP7 TOP_LEFT
+bind KP0+!KP1 BOT_LEFT
+bind KP0+!KP9 SAY_ABOVE
+bind KP0+!KP3 SAY_BELOW
+bind KP0+!KP4 MUTE
+bind ControlLeft MUTE
+bind ControlRight MUTE
+bind KP0+!KP6 AUTOsPEAK
+bind KP0+!KPPeriod CSRTRK
+bind KP0+!KPEnter HOME
+bind KP0+!KPDivide CLIP_NEW
+bind KP0+!KPMultiply COPY_LINE
+bind KP0+!KPMinus COPY_RECT
+bind KP0+!KPPlus PASTE
+bind ControlLeft+KP0+!KPPlus CLIP_ADD
+bind ControlLeft+ShiftLeft+KP0+!KP2 PREFMENU
+bind ControlLeft+ShiftLeft+KP0+!KP4 PREFLOAD
+bind ControlLeft+ShiftLeft+KP0+!KP6 PREFSAVE
+bind ControlLeft+ShiftLeft+KP0+!KP7 HELP
+bind ControlLeft+ShiftLeft+KP0+!KP8 INFO
+bind ControlLeft+ShiftLeft+KP0+!KP9 LEARN
+bind ControlLeft+ShiftLeft+KP0+!KP1 RESTARTBRL
+bind ControlLeft+ShiftLeft+KP0+!KP3 RESTARTSPEECH
+bind KP0+T TIME
+bind KP0+!S SETMARK
+bind KP0+!G GOTOMARK
+bind ControlRight+KP0+!One SETMARK+1
+bind KP0+!One GOTOMARK+1
+bind ControlRight+KP0+!Two SETMARK+2
+bind KP0+!Two GOTOMARK+2
+bind ControlRight+KP0+!Three SETMARK+3
+bind KP0+!Three GOTOMARK+3
+bind ControlRight+KP0+!Four SETMARK+4
+bind KP0+!Four GOTOMARK+4
diff --git a/Tables/Keyboard/keypad.ktb b/Tables/Keyboard/keypad.ktb
new file mode 100644
index 0000000..be1eb21
--- /dev/null
+++ b/Tables/Keyboard/keypad.ktb
@@ -0,0 +1,135 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Bindings for Keypad-based Navigation
+
+
+####################
+# Default Bindings #
+####################
+
+bind KP0+!KP5 HOME
+bind KP0+!KP4 FWINLT
+bind KP0+!KP6 FWINRT
+bind KP0+!KP8 LNUP
+bind KP0+!KP2 LNDN
+bind KP0+!KP7 TOP_LEFT
+bind KP0+!KP1 BOT_LEFT
+bind KP0+!KP9 PRDIFLN
+bind KP0+!KP3 NXDIFLN
+bind KP0+!KPDivide CSRTRK
+bind KP0+!KPMultiply DISPMD
+bind KP0+!KPMinus SIXDOTS+off
+bind KP0+!KPPlus SIXDOTS+on
+
+bind KPPeriod+!KP5 BACK
+bind KPPeriod+!KP4 FWINLTSKIP
+bind KPPeriod+!KP6 FWINRTSKIP
+bind KPPeriod+!KP8 PRPGRPH
+bind KPPeriod+!KP2 NXPGRPH
+bind KPPeriod+!KP7 PRPROMPT
+bind KPPeriod+!KP1 NXPROMPT
+bind KPPeriod+!KP9 ATTRUP
+bind KPPeriod+!KP3 ATTRDN
+bind KPPeriod+!KPDivide CSRVIS
+bind KPPeriod+!KPMultiply ATTRVIS
+bind KPPeriod+!KPMinus BRLUCDOTS+off
+bind KPPeriod+!KPPlus BRLUCDOTS+on
+
+bind KP0+KPPeriod+!KP1 CHRLT
+bind KP0+KPPeriod+!KP2 CSRJMP_VERT
+bind KP0+KPPeriod+!KP3 CHRRT
+bind KP0+KPPeriod+!KP4 LNBEG
+bind KP0+KPPeriod+!KP5 SETLEFT
+bind KP0+KPPeriod+!KP6 LNEND
+
+bind KPMinus+!KP5 PREFMENU
+bind KPMinus+!KP4 PREFLOAD
+bind KPMinus+!KP6 PREFSAVE
+bind KPMinus+!KP8 INFO
+bind KPMinus+!KP7 HELP
+bind KPMinus+!KP9 LEARN
+bind KPMinus+!KP2 FREEZE
+bind KPMinus+!KP1 TIME
+bind KPMinus+!KP3 DESCCHAR
+bind KPMinus+!KPDivide RESTARTBRL
+bind KPMinus+!KPMultiply RESTARTSPEECH
+
+bind KPMultiply+!KP5 PASTE
+bind KPMultiply+!KP7 CLIP_NEW
+bind KPMultiply+!KP1 CLIP_ADD
+bind KPMultiply+!KP9 COPY_LINE
+bind KPMultiply+!KP3 COPY_RECT
+bind KPMultiply+!KP8 PRINDENT
+bind KPMultiply+!KP2 NXINDENT
+bind KPMultiply+!KP4 PRDIFCHAR
+bind KPMultiply+!KP6 NXDIFCHAR
+bind KPMultiply+!KPMinus PRSEARCH
+bind KPMultiply+!KPPlus NXSEARCH
+
+assign kpAlt KP0
+assign kpOne KP1
+assign kpTwo KP2
+assign kpThree KP3
+assign kpFour KP4
+assign kpFive KP5
+assign kpSix KP6
+assign kpSeven KP7
+assign kpEight KP8
+assign kpNine KP9
+assign kpDivide KPDivide
+assign kpMultiply KPMultiply
+assign kpMinus KPMinus
+
+assign kpSay KPEnter
+include kp_say.kti
+
+assign kpSpeak KPPlus
+include kp_speak.kti
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind KP0+!KP4 FWINLT
+bind KP0+!KP6 FWINRT
+bind KP0+!KP8 MENU_PREV_ITEM
+bind KP0+!KP2 MENU_NEXT_ITEM
+bind KP0+!KP7 MENU_FIRST_ITEM
+bind KP0+!KP1 MENU_LAST_ITEM
+bind KP0+!KP9 MENU_PREV_SETTING
+bind KP0+!KP3 MENU_NEXT_SETTING
+bind KP0+!KP5 MENU_PREV_LEVEL
+
+bind KP0+!KPEnter PREFMENU
+bind KP0+!KPPlus PREFSAVE
+bind KP0+!KPMinus PREFLOAD
+
+
+#################
+# Braille Input #
+#################
+
+assign brailleOff KPEnter+!KPMinus
+assign brailleOn KPEnter+!KPPlus
+include braille.kti
+
+
diff --git a/Tables/Keyboard/kp_say.kti b/Tables/Keyboard/kp_say.kti
new file mode 100644
index 0000000..989d51d
--- /dev/null
+++ b/Tables/Keyboard/kp_say.kti
@@ -0,0 +1,36 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{kpSay}+!\{kpFive} SAY_LINE
+bind \{kpSay}+!\{kpEight} SAY_ABOVE
+bind \{kpSay}+!\{kpTwo} SAY_BELOW
+bind \{kpSay}+!\{kpFour} MUTE
+bind \{kpSay}+!\{kpSix} AUTOSPEAK
+bind \{kpSay}+!\{kpSeven} SAY_SLOWER
+bind \{kpSay}+!\{kpNine} SAY_FASTER
+bind \{kpSay}+!\{kpOne} SAY_SOFTER
+bind \{kpSay}+!\{kpThree} SAY_LOUDER
+bind \{kpSay}+!\{kpDivide} SPKHOME
+
+bind \{kpSay}+\{kpAlt}+!\{kpOne} ASPK_DEL_CHARS
+bind \{kpSay}+\{kpAlt}+!\{kpTwo} ASPK_REP_CHARS
+bind \{kpSay}+\{kpAlt}+!\{kpThree} ASPK_INS_CHARS
+bind \{kpSay}+\{kpAlt}+!\{kpFour} ASPK_SEL_CHAR
+bind \{kpSay}+\{kpAlt}+!\{kpFive} ASPK_SEL_LINE
+bind \{kpSay}+\{kpAlt}+!\{kpSix} ASPK_CMP_WORDS
+
diff --git a/Tables/Keyboard/kp_speak.kti b/Tables/Keyboard/kp_speak.kti
new file mode 100644
index 0000000..5c61ee9
--- /dev/null
+++ b/Tables/Keyboard/kp_speak.kti
@@ -0,0 +1,43 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+bind \{kpSpeak}+!\{kpTwo} SPEAK_CURR_CHAR
+bind \{kpSpeak}+\{kpAlt}+!\{kpTwo} DESC_CURR_CHAR
+bind \{kpSpeak}+!\{kpOne} SPEAK_PREV_CHAR
+bind \{kpSpeak}+\{kpAlt}+!\{kpOne} SPEAK_FRST_CHAR
+bind \{kpSpeak}+!\{kpThree} SPEAK_NEXT_CHAR
+bind \{kpSpeak}+\{kpAlt}+!\{kpThree} SPEAK_LAST_CHAR
+
+bind \{kpSpeak}+!\{kpFive} SPEAK_CURR_WORD
+bind \{kpSpeak}+\{kpAlt}+!\{kpFive} SPELL_CURR_WORD
+bind \{kpSpeak}+!\{kpFour} SPEAK_PREV_WORD
+bind \{kpSpeak}+!\{kpSix} SPEAK_NEXT_WORD
+
+bind \{kpSpeak}+!\{kpEight} SPEAK_CURR_LINE
+bind \{kpSpeak}+!\{kpSeven} SPEAK_PREV_LINE
+bind \{kpSpeak}+\{kpAlt}+!\{kpSeven} SPEAK_FRST_LINE
+bind \{kpSpeak}+!\{kpNine} SPEAK_NEXT_LINE
+bind \{kpSpeak}+\{kpAlt}+!\{kpNine} SPEAK_LAST_LINE
+
+bind \{kpSpeak}+!\{kpDivide} SPEAK_CURR_LOCN
+bind \{kpSpeak}+\{kpAlt}+!\{kpDivide} SHOW_CURR_LOCN
+bind \{kpSpeak}+!\{kpMultiply} ROUTE_CURR_LOCN
+
+bind \{kpSpeak}+!\{kpMinus} SPEAK_INDENT
+bind \{kpSpeak}+\{kpAlt}+!\{kpMinus} ASPK_INDENT
+
diff --git a/Tables/Keyboard/laptop.ktb b/Tables/Keyboard/laptop.ktb
new file mode 100644
index 0000000..de8782d
--- /dev/null
+++ b/Tables/Keyboard/laptop.ktb
@@ -0,0 +1,138 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Bindings for Keyboards without a Keypad
+
+
+####################
+# Default Bindings #
+####################
+
+bind CapsLock+!F1 HELP
+bind CapsLock+ShiftLeft+!F1 LEARN
+
+bind CapsLock+!F2 PREFMENU
+bind CapsLock+ShiftLeft+!F2 PREFSAVE
+
+bind CapsLock+!F5 SAY_SLOWER
+bind CapsLock+!F6 SAY_FASTER
+bind CapsLock+!F7 SAY_SOFTER
+bind CapsLock+!F8 SAY_LOUDER
+
+bind CapsLock+!F9 AUTOSPEAK
+
+bind CapsLock+!ArrowUp LNUP
+bind CapsLock+!ArrowDown LNDN
+bind CapsLock+!ArrowLeft CHRLT
+bind CapsLock+!ArrowRight CHRRT
+bind CapsLock+!Home TOP_LEFT
+bind CapsLock+!End BOT_LEFT
+
+bind CapsLock+ShiftLeft+!ArrowUp PRDIFLN
+bind CapsLock+ShiftLeft+!ArrowDown NXDIFLN
+bind CapsLock+ShiftLeft+!ArrowLeft LNBEG
+bind CapsLock+ShiftLeft+!ArrowRight LNEND
+bind CapsLock+ShiftLeft+!Home TOP
+bind CapsLock+ShiftLeft+!End BOT
+
+bind CapsLock+!Enter CSRTRK
+bind CapsLock+ShiftLeft+!Enter CSRTRK+off
+bind CapsLock+!DeleteBackward BACK
+
+bind CapsLock+!D DESCCHAR
+bind CapsLock+!F FREEZE
+bind CapsLock+!R RESTARTBRL
+bind CapsLock+!T TIME
+
+# marks
+bind CapsLock+!M SETMARK
+bind CapsLock+!J GOTOMARK
+bind CapsLock+ShiftLeft+!One SETMARK+1
+bind CapsLock+!One GOTOMARK+1
+bind CapsLock+ShiftLeft+!Two SETMARK+2
+bind CapsLock+!Two GOTOMARK+2
+bind CapsLock+ShiftLeft+!Three SETMARK+3
+bind CapsLock+!Three GOTOMARK+3
+bind CapsLock+ShiftLeft+!Four SETMARK+4
+bind CapsLock+!Four GOTOMARK+4
+
+# copy and paste
+bind CapsLock+!X CLIP_NEW
+bind CapsLock+ShiftLeft+!X CLIP_ADD
+bind CapsLock+!C COPY_RECT
+bind CapsLock+ShiftLeft+!C COPY_LINE
+bind CapsLock+!V PASTE
+
+# speech
+bind ControlLeft MUTE
+bind ControlRight MUTE
+bind CapsLock+!Space SAY_LINE
+bind CapsLock+!PageUp SAY_ABOVE
+bind CapsLock+!PageDown SAY_BELOW
+
+bind ShiftRight+!ArrowLeft SPEAK_PREV_WORD
+bind ShiftRight+!ArrowRight SPEAK_NEXT_WORD
+bind ShiftRight+!ArrowUp SPEAK_PREV_LINE
+bind ShiftRight+!ArrowDown SPEAK_NEXT_LINE
+bind ControlRight+!ArrowLeft SPEAK_FRST_CHAR
+bind ControlRight+!ArrowRight SPEAK_LAST_CHAR
+bind ControlRight+!ArrowUp SPEAK_FRST_LINE
+bind ControlRight+!ArrowDown SPEAK_LAST_LINE
+bind ControlRight+ShiftRight ROUTE_CURR_LOCN
+bind ShiftRight+!Enter SPEAK_CURR_LOCN
+
+assign kpAlt ControlLeft
+assign kpOne M
+assign kpTwo Comma
+assign kpThree Period
+assign kpFour J
+assign kpFive K
+assign kpSix L
+assign kpSeven U
+assign kpEight I
+assign kpNine O
+assign kpDivide Eight
+assign kpMultiply Nine
+assign kpMinus Zero
+
+assign kpSay CapsLock+ShiftLeft
+include kp_say.kti
+
+assign kpSpeak Tab
+include kp_speak.kti
+
+
+#################
+# Menu Bindings #
+#################
+
+context menu
+
+bind CapsLock+!ArrowUp MENU_PREV_ITEM
+bind CapsLock+!ArrowDown MENU_NEXT_ITEM
+bind CapsLock+!ArrowLeft MENU_PREV_SETTING
+bind CapsLock+!ArrowRight MENU_NEXT_SETTING
+bind CapsLock+!Home MENU_FIRST_ITEM
+bind CapsLock+!End MENU_LAST_ITEM
+
+bind CapsLock+ShiftLeft+!ArrowUp MENU_PREV_LEVEL
+bind CapsLock+ShiftLeft+!ArrowDown PREFMENU
+bind CapsLock+ShiftLeft+!ArrowLeft PREFLOAD
+bind CapsLock+ShiftLeft+!ArrowRight PREFSAVE
+
+
diff --git a/Tables/Keyboard/sun_type6.ktb b/Tables/Keyboard/sun_type6.ktb
new file mode 100644
index 0000000..bc218d3
--- /dev/null
+++ b/Tables/Keyboard/sun_type6.ktb
@@ -0,0 +1,45 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+title Bindings for Sun Microsystems, Inc. Type 6 Keyboards
+
+include desktop.kti
+
+bind MEDIA_Mute MUTE
+bind ControlLeft+!MEDIA_Mute TUNES
+bind MEDIA_VolumeDown SAY_SOFTER
+bind MEDIA_VolumeUp SAY_LOUDER
+bind ACTION_Power RESTARTBRL
+bind ControlLeft+!ACTION_Power RESTARTSPEECH
+
+bind ACTION_Stop FREEZE+on
+bind ACTION_Again FREEZE+off
+bind ACTION_Stop+!ACTION_Again FREEZE
+hide on
+bind ACTION_Again+!ACTION_Stop FREEZE
+hide off
+bind !ACTION_Undo RETURN
+bind ControlLeft+!ACTION_Undo BACK
+bind !ACTION_Paste PASTE
+bind ControlLeft+!ACTION_Paste CLIP_RESTORE
+bind ShiftLeft+!ACTION_Paste CLIP_SAVE
+bind !ACTION_Find NXSEARCH
+bind ControlLeft+!ACTION_Find PRSEARCH
+
+bind !ACTION_Help HELP
+bind ControlLeft+ACTION_Help LEARN
diff --git a/Tables/Text/alias.tti b/Tables/Text/alias.tti
new file mode 100644
index 0000000..4d0d39d
--- /dev/null
+++ b/Tables/Text/alias.tti
@@ -0,0 +1,75 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines several useful character aliases
+
+# ensure that space is defined
+ifNotGlyph \s  glyph \s 0
+ifNotInput 0  input \s 0
+
+# characters that represent a space
+alias	\xA0	\s	# no-break space
+alias	\u2002	\s	# en space
+alias	\u2003	\s	# em space
+alias	\u2004	\s	# three-per-em space
+alias	\u2005	\s	# four-per-em space
+alias	\u2006	\s	# six-per-em space
+alias	\u2007	\s	# figure space
+alias	\u2008	\s	# punctuation space
+alias	\u2009	\s	# thin space
+alias	\u200A	\s	# hair space
+alias	\u202F	\s	# narrow no-break space
+alias	\u205F	\s	# medium mathematical space
+
+ifGlyph -
+  # characters that represent a dash
+  alias	\u2012	-	# figure dash
+  alias	\u2013	-	# en dash
+  alias	\u2014	-	# em dash
+  alias	\u2448	-	# ocr dash
+  alias	\u301C	-	# wave dash
+  alias	\u3030	-	# wavy dash
+  alias	\uFE58	-	# small em dash
+
+  # characters that represent a hyphen
+  alias	\xAD	-	# soft hyphen
+  alias	\u2010	-	# hyphen
+  alias	\u2011	-	# non-breaking hyphen
+
+  # characters that represent a minus sign
+  alias	\u2052	-	# commercial minus sign
+  alias	\u2212	-	# minus sign
+
+  # characters that can represent either a hyphen or a minus sign
+  alias	\uFE63	-	# small hyphen-minus
+  alias	\uFF0D	-	# fullwidth hyphen-minus
+
+  # other characters that can look like a dash
+  alias	\u2015	-	# horizontal bar
+endIf
+
+ifGlyph '
+  # characters commonly (mis)used to represent an apostrophe
+  alias	\u2018	'	# left single quotation mark
+  alias	\u2019	'	# right single quotation mark
+endIf
+
+# degree signs
+ifGlyph	C	alias	\u2103	C	# degree Celsius
+ifGlyph	F	alias	\u2109	F	# degree Fahrenheit
+
diff --git a/Tables/Text/ar.ttb b/Tables/Text/ar.ttb
new file mode 100644
index 0000000..bb29fc0
--- /dev/null
+++ b/Tables/Text/ar.ttb
@@ -0,0 +1,184 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Arabic (generic)
+#
+# This text table was converted from LibLouis's ar-ar-comp8 table.
+# Maintainer: Dave Mielke <Dave@Mielke.cc>
+# Advisor: Ikrami Ahmad <Ikrami@blind.gov.qa>
+
+char	\x21	( 23 5   )	# ⠖ ! [EXCLAMATION MARK]
+char	\x22	( 23 56  )	# ⠶ " [QUOTATION MARK]
+char	\x23	(  3456  )	# ⠼ # [NUMBER SIGN]
+char	\x24	(12 4 6 8)	# ⢫ $ [DOLLAR SIGN]
+char	\x25	(1  4 6 8)	# ⢩ % [PERCENT SIGN]
+char	\x26	(1234 67 )	# ⡯ & [AMPERSAND]
+char	\x27	(  3   7 )	# ⡄ ' [APOSTROPHE]
+char	\x28	( 23  67 )	# ⡦ ( [LEFT PARENTHESIS]
+char	\x29	(  3 56 8)	# ⢴ ) [RIGHT PARENTHESIS]
+char	\x2A	(  3 5  8)	# ⢔ * [ASTERISK]
+char	\x2B	( 23 5  8)	# ⢖ + [PLUS SIGN]
+char	\x2C	( 2    7 )	# ⡂ , [COMMA]
+char	\x2D	(  3  6  )	# ⠤ - [HYPHEN-MINUS]
+char	\x2E	( 2  56  )	# ⠲ . [FULL STOP]
+char	\x2F	(  34   8)	# ⢌ / [SOLIDUS]
+char	\x3A	( 2  5 7 )	# ⡒ : [COLON]
+char	\x3B	(    5678)	# ⣰ ; [SEMICOLON]
+char	\x3C	( 2 4 6 8)	# ⢪ < [LESS-THAN SIGN]
+char	\x3D	( 23 5678)	# ⣶ = [EQUALS SIGN]
+char	\x3E	(1 3 5  8)	# ⢕ > [GREATER-THAN SIGN]
+char	\x3F	( 23  678)	# ⣦ ? [QUESTION MARK]
+char	\x40	(   4  7 )	# ⡈ @ [COMMERCIAL AT]
+char	\x5B	(123 567 )	# ⡷ [ [LEFT SQUARE BRACKET]
+char	\x5C	(  34  7 )	# ⡌ \ [REVERSE SOLIDUS]
+char	\x5D	( 23456 8)	# ⢾ ] [RIGHT SQUARE BRACKET]
+char	\x5E	( 234 6 8)	# ⢮ ^ [CIRCUMFLEX ACCENT]
+char	\x5F	(   4567 )	# ⡸ _ [LOW LINE]
+char	\x60	(     6 8)	# ⢠ ` [GRAVE ACCENT]
+char	\x7B	( 23 567 )	# ⡶ { [LEFT CURLY BRACKET]
+char	\x7C	(12  56 8)	# ⢳ | [VERTICAL LINE]
+char	\x7D	( 23 56 8)	# ⢶ } [RIGHT CURLY BRACKET]
+char	\x7E	(  345  8)	# ⢜ ~ [TILDE]
+
+char	\xA2	(12 4 67 )	# ⡫ ¢ [CENT SIGN]
+char	\xA7	( 234   8)	# ⢎ § [SECTION SIGN]
+char	\xA9	(1234 6 8)	# ⢯ © [COPYRIGHT SIGN]
+char	\xAB	(1234567 )	# ⡿ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char	\xAE	(123 5  8)	# ⢗ ® [REGISTERED SIGN]
+char	\xB0	(12345  8)	# ⢟ ° [DEGREE SIGN]
+char	\xB5	(1 34   8)	# ⢍ µ [MICRO SIGN]
+char	\xB6	(1234   8)	# ⢏ ¶ [PILCROW SIGN]
+char	\xBB	(123456 8)	# ⢿ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+glyph	\xBF	( 23  678)	# ⣦ ¿ [INVERTED QUESTION MARK]
+char	\xD7	( 23  6 8)	# ⢦ × [MULTIPLICATION SIGN]
+char	\xF7	( 2  56 8)	# ⢲ ÷ [DIVISION SIGN]
+
+char	\u060C	(    5 7 )	# ⡐ ، [ARABIC COMMA]
+char	\u061B	(    567 )	# ⡰ ؛ [ARABIC SEMICOLON]
+char	\u061F	( 23  6  )	# ⠦ ؟ [ARABIC QUESTION MARK]
+char	\u0621	(  3     )	# ⠄ ء [ARABIC LETTER HAMZA]
+char	\u0622	(  345   )	# ⠜ آ [ARABIC LETTER ALEF WITH MADDA ABOVE]
+char	\u0623	(  34    )	# ⠌ أ [ARABIC LETTER ALEF WITH HAMZA ABOVE]
+char	\u0624	(12  56  )	# ⠳ ؤ [ARABIC LETTER WAW WITH HAMZA ABOVE]
+char	\u0625	(   4 6  )	# ⠨ إ [ARABIC LETTER ALEF WITH HAMZA BELOW]
+char	\u0626	(1 3456  )	# ⠽ ئ [ARABIC LETTER YEH WITH HAMZA ABOVE]
+char	\u0627	(1       )	# ⠁ ا [ARABIC LETTER ALEF]
+char	\u0628	(12      )	# ⠃ ب [ARABIC LETTER BEH]
+char	\u0629	(1    6  )	# ⠡ ة [ARABIC LETTER TEH MARBUTA]
+char	\u062A	( 2345   )	# ⠞ ت [ARABIC LETTER TEH]
+char	\u062B	(1  456  )	# ⠹ ث [ARABIC LETTER THEH]
+char	\u062C	( 2 45   )	# ⠚ ج [ARABIC LETTER JEEM]
+char	\u062D	(1   56  )	# ⠱ ح [ARABIC LETTER HAH]
+char	\u062E	(1 34 6  )	# ⠭ خ [ARABIC LETTER KHAH]
+char	\u062F	(1  45   )	# ⠙ د [ARABIC LETTER DAL]
+char	\u0630	( 234 6  )	# ⠮ ذ [ARABIC LETTER THAL]
+char	\u0631	(123 5   )	# ⠗ ر [ARABIC LETTER REH]
+char	\u0632	(1 3 56  )	# ⠵ ز [ARABIC LETTER ZAIN]
+char	\u0633	( 234    )	# ⠎ س [ARABIC LETTER SEEN]
+char	\u0634	(1  4 6  )	# ⠩ ش [ARABIC LETTER SHEEN]
+char	\u0635	(1234 6  )	# ⠯ ص [ARABIC LETTER SAD]
+char	\u0636	(12 4 6  )	# ⠫ ض [ARABIC LETTER DAD]
+char	\u0637	( 23456  )	# ⠾ ط [ARABIC LETTER TAH]
+char	\u0638	(123456  )	# ⠿ ظ [ARABIC LETTER ZAH]
+char	\u0639	(123 56  )	# ⠷ ع [ARABIC LETTER AIN]
+char	\u063A	(12   6  )	# ⠣ غ [ARABIC LETTER GHAIN]
+char	\u0641	(12 4    )	# ⠋ ف [ARABIC LETTER FEH]
+char	\u0642	(12345   )	# ⠟ ق [ARABIC LETTER QAF]
+char	\u0643	(1 3     )	# ⠅ ك [ARABIC LETTER KAF]
+char	\u0644	(123     )	# ⠇ ل [ARABIC LETTER LAM]
+char	\u0645	(1 34    )	# ⠍ م [ARABIC LETTER MEEM]
+char	\u0646	(1 345   )	# ⠝ ن [ARABIC LETTER NOON]
+char	\u0647	(12  5   )	# ⠓ ه [ARABIC LETTER HEH]
+char	\u0648	( 2 456  )	# ⠺ و [ARABIC LETTER WAW]
+char	\u0649	(1 3 5   )	# ⠕ ى [ARABIC LETTER ALEF MAKSURA]
+char	\u064A	( 2 4    )	# ⠊ ي [ARABIC LETTER YEH]
+char	\u064B	( 23     )	# ⠆ ً [ARABIC FATHATAN]
+char	\u064C	( 2   6  )	# ⠢ ٌ [ARABIC DAMMATAN]
+char	\u064D	(  3 5   )	# ⠔ ٍ [ARABIC KASRATAN]
+char	\u064E	( 2      )	# ⠂ َ [ARABIC FATHA]
+char	\u064F	(1 3  6  )	# ⠥ ُ [ARABIC DAMMA]
+char	\u0650	(1   5   )	# ⠑ ِ [ARABIC KASRA]
+char	\u0651	(     6  )	# ⠠ ّ [ARABIC SHADDA]
+char	\u0652	( 2  5   )	# ⠒ ْ [ARABIC SUKUN]
+char	\u067E	(1234    )	# ⠏ پ [ARABIC LETTER PEH]
+char	\u0686	(1  4    )	# ⠉ چ [ARABIC LETTER TCHEH]
+glyph	\u0698	(1 3 56  )	# ⠵ ژ [ARABIC LETTER JEH]
+char	\u06A4	(123  6 8)	# ⢧ ڤ [ARABIC LETTER VEH]
+char	\u06AF	(12 45   )	# ⠛ گ [ARABIC LETTER GAF]
+glyph	\u06D4	( 2  56  )	# ⠲ ۔ [ARABIC FULL STOP]
+char	\u2026	( 2  5678)	# ⣲ … [HORIZONTAL ELLIPSIS]
+char	\u2212	(  3  6 8)	# ⢤ − [MINUS SIGN]
+char	\uFEFC	(123  6  )	# ⠧ ﻼ [ARABIC LIGATURE LAM WITH ALEF FINAL FORM]
+
+# The English letters must be defined after the Arabic ones so that
+# typing on a braille keyboard will yield the Arabic ones.
+include ltr-latin.tti
+
+# The English digits are their corresponding letters with dot 8 added.
+include num-dot8.tti
+alias	\u0660	0	# ٠ [ARABIC-INDIC DIGIT ZERO]
+alias	\u0661	1	# ١ [ARABIC-INDIC DIGIT ONE]
+alias	\u0662	2	# ٢ [ARABIC-INDIC DIGIT TWO]
+alias	\u0663	3	# ٣ [ARABIC-INDIC DIGIT THREE]
+alias	\u0664	4	# ٤ [ARABIC-INDIC DIGIT FOUR]
+alias	\u0665	5	# ٥ [ARABIC-INDIC DIGIT FIVE]
+alias	\u0666	6	# ٦ [ARABIC-INDIC DIGIT SIX]
+alias	\u0667	7	# ٧ [ARABIC-INDIC DIGIT SEVEN]
+alias	\u0668	8	# ٨ [ARABIC-INDIC DIGIT EIGHT]
+alias	\u0669	9	# ٩ [ARABIC-INDIC DIGIT NINE]
+
+alias	\xAD	-	# ­ [SOFT HYPHEN]
+alias	\u2010	-	# ‐ [HYPHEN]
+alias	\u2011	-	# ‑ [NON-BREAKING HYPHEN]
+
+alias	\u2018	"	# ‘ [LEFT SINGLE QUOTATION MARK]
+alias	\u2019	"	# ’ [RIGHT SINGLE QUOTATION MARK]
+alias	\u201C	"	# “ [LEFT DOUBLE QUOTATION MARK]
+alias	\u201D	"	# ” [RIGHT DOUBLE QUOTATION MARK]
+alias	\u201E	"	# „ [DOUBLE LOW-9 QUOTATION MARK]
+alias	\u201F	"	# ‟ [DOUBLE HIGH-REVERSED-9 QUOTATION MARK]
+
+# English letters can be typed by adding both dots 7 and 8.
+input	a	(1     78)
+input	b	(12    78)
+input	c	(1  4  78)
+input	d	(1  45 78)
+input	e	(1   5 78)
+input	f	(12 4  78)
+input	g	(12 45 78)
+input	h	(12  5 78)
+input	i	( 2 4  78)
+input	j	( 2 45 78)
+input	k	(1 3   78)
+input	l	(123   78)
+input	m	(1 34  78)
+input	n	(1 345 78)
+input	o	(1 3 5 78)
+input	p	(1234  78)
+input	q	(12345 78)
+input	r	(123 5 78)
+input	s	( 234  78)
+input	t	( 2345 78)
+input	u	(1 3  678)
+input	v	(123  678)
+input	w	( 2 45678)
+input	x	(1 34 678)
+input	y	(1 345678)
+input	z	(1 3 5678)
+
+include common.tti
diff --git a/Tables/Text/as.ttb b/Tables/Text/as.ttb
new file mode 100644
index 0000000..f7ff1f2
--- /dev/null
+++ b/Tables/Text/as.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Assamese
+
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/ascii-basic.tti b/Tables/Text/ascii-basic.tti
new file mode 100644
index 0000000..5b5eb22
--- /dev/null
+++ b/Tables/Text/ascii-basic.tti
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines a set of braille representations for the 
+# ASCII character set designed for languages which don't use them themselves.
+
+include ltr-latin.tti
+include num-dot8.tti
+include punc-basic.tti
diff --git a/Tables/Text/awa.ttb b/Tables/Text/awa.ttb
new file mode 100644
index 0000000..44a58e4
--- /dev/null
+++ b/Tables/Text/awa.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Awadhi
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/bengali.tti b/Tables/Text/bengali.tti
new file mode 100644
index 0000000..72af924
--- /dev/null
+++ b/Tables/Text/bengali.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Bengali script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0981	(  3     )  # ⠄ ঁ [BENGALI SIGN CANDRABINDU]
+char \u0982	(    56  )  # ⠰ ং [BENGALI SIGN ANUSVARA]
+char \u0983	(     6  )  # ⠠ ঃ [BENGALI SIGN VISARGA]
+char \u0985	(1       )  # ⠁ অ [BENGALI LETTER A]
+char \u0986	(  345   )  # ⠜ আ [BENGALI LETTER AA]
+char \u0987	( 2 4    )  # ⠊ ই [BENGALI LETTER I]
+char \u0988	(  3 5   )  # ⠔ ঈ [BENGALI LETTER II]
+char \u0989	(1 3  6  )  # ⠥ উ [BENGALI LETTER U]
+char \u098A	(12  56  )  # ⠳ ঊ [BENGALI LETTER UU]
+
+
+char \u098F	(1   5   )  # ⠑ এ [BENGALI LETTER E]
+char \u0990	(  34    )  # ⠌ ঐ [BENGALI LETTER AI]
+
+
+char \u0993	(1 3 5   )  # ⠕ ও [BENGALI LETTER O]
+char \u0994	( 2 4 6  )  # ⠪ ঔ [BENGALI LETTER AU]
+char \u0995	(1 3     )  # ⠅ ক [BENGALI LETTER KA]
+char \u0996	(   4 6  )  # ⠨ খ [BENGALI LETTER KHA]
+char \u0997	(1234    )  # ⠏ গ [BENGALI LETTER GA]
+char \u0998	(12   6  )  # ⠣ ঘ [BENGALI LETTER GHA]
+char \u0999	(  34 6  )  # ⠬ ঙ [BENGALI LETTER NGA]
+char \u099A	(1  4    )  # ⠉ চ [BENGALI LETTER CA]
+char \u099B	(1    6  )  # ⠡ ছ [BENGALI LETTER CHA]
+char \u099C	( 2 45   )  # ⠚ জ [BENGALI LETTER JA]
+char \u099D	(  3 56  )  # ⠴ ঝ [BENGALI LETTER JHA]
+char \u099E	( 2  5   )  # ⠒ ঞ [BENGALI LETTER NYA]
+char \u099F	( 23456  )  # ⠾ ট [BENGALI LETTER TTA]
+char \u09A0	( 2 456  )  # ⠺ ঠ [BENGALI LETTER TTHA]
+char \u09A1	(12 4 6  )  # ⠫ ড [BENGALI LETTER DDA]
+char \u09A2	(123456  )  # ⠿ ঢ [BENGALI LETTER DDHA]
+char \u09A3	(  3456  )  # ⠼ ণ [BENGALI LETTER NNA]
+char \u09A4	( 2345   )  # ⠞ ত [BENGALI LETTER TA]
+char \u09A5	(1  456  )  # ⠹ থ [BENGALI LETTER THA]
+char \u09A6	(1  45   )  # ⠙ দ [BENGALI LETTER DA]
+char \u09A7	( 234 6  )  # ⠮ ধ [BENGALI LETTER DHA]
+char \u09A8	(1 345   )  # ⠝ ন [BENGALI LETTER NA]
+
+char \u09AA	(1234    )  # ⠏ প [BENGALI LETTER PA]
+char \u09AB	( 23 5   )  # ⠖ ফ [BENGALI LETTER PHA]
+char \u09AC	(12      )  # ⠃ ব [BENGALI LETTER BA]
+char \u09AD	(   45   )  # ⠘ ভ [BENGALI LETTER BHA]
+char \u09AE	(1 34    )  # ⠍ ম [BENGALI LETTER MA]
+char \u09AF	(1 3456  )  # ⠽ য [BENGALI LETTER YA]
+char \u09B0	(123 5   )  # ⠗ র [BENGALI LETTER RA]
+
+char \u09B2	(123     )  # ⠇ ল [BENGALI LETTER LA]
+
+
+
+char \u09B6	(1  4 6  )  # ⠩ শ [BENGALI LETTER SHA]
+char \u09B7	(1234 6  )  # ⠯ ষ [BENGALI LETTER SSA]
+char \u09B8	( 234    )  # ⠎ স [BENGALI LETTER SA]
+char \u09B9	(12  5   )  # ⠓ হ [BENGALI LETTER HA]
+char \u09BD	( 2      )  # ⠂ ঽ [BENGALI SIGN AVAGRAHA]
+char \u09BE	(  345   )  # ⠜ া [BENGALI VOWEL SIGN AA]
+char \u09BF	( 2 4    )  # ⠊ ি [BENGALI VOWEL SIGN I]
+char \u09C0	(  3 5   )  # ⠔ ী [BENGALI VOWEL SIGN II]
+char \u09C1	(1 3  6  )  # ⠥ ু [BENGALI VOWEL SIGN U]
+char \u09C2	(12  56  )  # ⠳ ূ [BENGALI VOWEL SIGN UU]
+
+
+char \u09C7	(1   5   )  # ⠑ ে [BENGALI VOWEL SIGN E]
+char \u09C8	(  34    )  # ⠌ ৈ [BENGALI VOWEL SIGN AI]
+
+
+char \u09CB	(1 3 5   )  # ⠕ ো [BENGALI VOWEL SIGN O]
+char \u09CC	( 2 4 6  )  # ⠪ ৌ [BENGALI VOWEL SIGN AU]
+char \u09CD	(   4    )  # ⠈ ্ [BENGALI SIGN VIRAMA]
+
+char \u09E6	( 2 45   )  # ⠚ ০ [BENGALI DIGIT ZERO]
+char \u09E7	(1       )  # ⠁ ১ [BENGALI DIGIT ONE]
+char \u09E8	(12      )  # ⠃ ২ [BENGALI DIGIT TWO]
+char \u09E9	(1  4    )  # ⠉ ৩ [BENGALI DIGIT THREE]
+char \u09EA	(1  45   )  # ⠙ ৪ [BENGALI DIGIT FOUR]
+char \u09EB	(1   5   )  # ⠑ ৫ [BENGALI DIGIT FIVE]
+char \u09EC	(12 4    )  # ⠋ ৬ [BENGALI DIGIT SIX]
+char \u09ED	(12 45   )  # ⠛ ৭ [BENGALI DIGIT SEVEN]
+char \u09EE	(12  5   )  # ⠓ ৮ [BENGALI DIGIT EIGHT]
+char \u09EF	( 2 4    )  # ⠊ ৯ [BENGALI DIGIT NINE]
diff --git a/Tables/Text/bg.ttb b/Tables/Text/bg.ttb
new file mode 100644
index 0000000..91d0aa6
--- /dev/null
+++ b/Tables/Text/bg.ttb
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Bulgarian
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Cyrillic alphabet
+include ltr-cyrillic.tti
+
+include ltr-dot8.tti
+include num-nemeth.tti
+include punc-alternate.tti
+include common.tti
diff --git a/Tables/Text/bh.ttb b/Tables/Text/bh.ttb
new file mode 100644
index 0000000..ad93aa6
--- /dev/null
+++ b/Tables/Text/bh.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Bihari
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/blocks.tti b/Tables/Text/blocks.tti
new file mode 100644
index 0000000..f2f222d
--- /dev/null
+++ b/Tables/Text/blocks.tti
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements a portable representation of the block
+# element characters.
+
+glyph \u2580	(12 45   )  # ⠛ ▀ [UPPER HALF BLOCK]
+glyph \u2582	(      78)  # ⣀ ▂ [LOWER ONE QUARTER BLOCK]
+glyph \u2584	(  3  678)  # ⣤ ▄ [LOWER HALF BLOCK]
+glyph \u2586	( 23 5678)  # ⣶ ▆ [LOWER THREE QUARTERS BLOCK]
+glyph \u2588	(12345678)  # ⣿ █ [FULL BLOCK]
+glyph \u258C	(123   7 )  # ⡇ ▌ [LEFT HALF BLOCK]
+glyph \u2590	(   456 8)  # ⢸ ▐ [RIGHT HALF BLOCK]
diff --git a/Tables/Text/bn.ttb b/Tables/Text/bn.ttb
new file mode 100644
index 0000000..7cf43d8
--- /dev/null
+++ b/Tables/Text/bn.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Bengali
+
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/bo.ttb b/Tables/Text/bo.ttb
new file mode 100644
index 0000000..18c3c6f
--- /dev/null
+++ b/Tables/Text/bo.ttb
@@ -0,0 +1,28 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Tibetan
+
+include ltr-tibetan.tti
+include punc-tibetan.tti
+
+include ltr-dot8.tti
+include num-nemeth.tti
+include punc-alternate.tti
+
+include common.tti
diff --git a/Tables/Text/boxes.tti b/Tables/Text/boxes.tti
new file mode 100644
index 0000000..84afc17
--- /dev/null
+++ b/Tables/Text/boxes.tti
@@ -0,0 +1,168 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements a portable representation of the box
+# drawing characters.aNo distinction is made between the three types of boxes:
+# light, heavy, and double. Characters with no vertical component (horizontal
+# edges) are represented by the four middle dots [2356]. Those with a vertical
+# component above the center (e.g. bottom corners) add the two top dots [14],
+# and those with a vertical component below the center (e.g. top corners) add
+# the two bottom dots [78]. This means, for example, that side edges and the
+# cross are represented by a full cell [12345678].
+
+# For diagonals within boxes, the top-left and bottom-right dots [18] are used
+# to represent the line which descends toward the right, and the top-right and
+# bottom-left dots [47] are used to represent the line which ascends toward the
+# right. The X is represented by all four of these dots [1478].
+
+glyph \u2500	( 23 56  )  # ⠶ ─ [BOX DRAWINGS LIGHT HORIZONTAL]
+glyph \u254C	( 23 56  )  # ⠶ ╌ [BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL]
+glyph \u2504	( 23 56  )  # ⠶ ┄ [BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL]
+glyph \u2508	( 23 56  )  # ⠶ ┈ [BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL]
+glyph \u2502	(12345678)  # ⣿ │ [BOX DRAWINGS LIGHT VERTICAL]
+glyph \u254E	(12345678)  # ⣿ ╎ [BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL]
+glyph \u2506	(12345678)  # ⣿ ┆ [BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL]
+glyph \u250A	(12345678)  # ⣿ ┊ [BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL]
+glyph \u250C	( 23 5678)  # ⣶ ┌ [BOX DRAWINGS LIGHT DOWN AND RIGHT]
+glyph \u2510	( 23 5678)  # ⣶ ┐ [BOX DRAWINGS LIGHT DOWN AND LEFT]
+glyph \u2514	(123456  )  # ⠿ └ [BOX DRAWINGS LIGHT UP AND RIGHT]
+glyph \u2518	(123456  )  # ⠿ ┘ [BOX DRAWINGS LIGHT UP AND LEFT]
+glyph \u251C	(12345678)  # ⣿ ├ [BOX DRAWINGS LIGHT VERTICAL AND RIGHT]
+glyph \u2524	(12345678)  # ⣿ ┤ [BOX DRAWINGS LIGHT VERTICAL AND LEFT]
+glyph \u252C	( 23 5678)  # ⣶ ┬ [BOX DRAWINGS LIGHT DOWN AND HORIZONTAL]
+glyph \u2534	(123456  )  # ⠿ ┴ [BOX DRAWINGS LIGHT UP AND HORIZONTAL]
+glyph \u253C	(12345678)  # ⣿ ┼ [BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL]
+
+glyph \u2501	( 23 56  )  # ⠶ ━ [BOX DRAWINGS HEAVY HORIZONTAL]
+glyph \u254D	( 23 56  )  # ⠶ ╍ [BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL]
+glyph \u2505	( 23 56  )  # ⠶ ┅ [BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL]
+glyph \u2509	( 23 56  )  # ⠶ ┉ [BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL]
+glyph \u2503	(12345678)  # ⣿ ┃ [BOX DRAWINGS HEAVY VERTICAL]
+glyph \u254F	(12345678)  # ⣿ ╏ [BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL]
+glyph \u2507	(12345678)  # ⣿ ┇ [BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL]
+glyph \u250B	(12345678)  # ⣿ ┋ [BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL]
+glyph \u250F	( 23 5678)  # ⣶ ┏ [BOX DRAWINGS HEAVY DOWN AND RIGHT]
+glyph \u2513	( 23 5678)  # ⣶ ┓ [BOX DRAWINGS HEAVY DOWN AND LEFT]
+glyph \u2517	(123456  )  # ⠿ ┗ [BOX DRAWINGS HEAVY UP AND RIGHT]
+glyph \u251B	(123456  )  # ⠿ ┛ [BOX DRAWINGS HEAVY UP AND LEFT]
+glyph \u2523	(12345678)  # ⣿ ┣ [BOX DRAWINGS HEAVY VERTICAL AND RIGHT]
+glyph \u252B	(12345678)  # ⣿ ┫ [BOX DRAWINGS HEAVY VERTICAL AND LEFT]
+glyph \u2533	( 23 5678)  # ⣶ ┳ [BOX DRAWINGS HEAVY DOWN AND HORIZONTAL]
+glyph \u253B	(123456  )  # ⠿ ┻ [BOX DRAWINGS HEAVY UP AND HORIZONTAL]
+glyph \u254B	(12345678)  # ⣿ ╋ [BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL]
+
+glyph \u2550	( 23 56  )  # ⠶ ═ [BOX DRAWINGS DOUBLE HORIZONTAL]
+glyph \u2551	(12345678)  # ⣿ ║ [BOX DRAWINGS DOUBLE VERTICAL]
+glyph \u2554	( 23 5678)  # ⣶ ╔ [BOX DRAWINGS DOUBLE DOWN AND RIGHT]
+glyph \u2557	( 23 5678)  # ⣶ ╗ [BOX DRAWINGS DOUBLE DOWN AND LEFT]
+glyph \u255A	(123456  )  # ⠿ ╚ [BOX DRAWINGS DOUBLE UP AND RIGHT]
+glyph \u255D	(123456  )  # ⠿ ╝ [BOX DRAWINGS DOUBLE UP AND LEFT]
+glyph \u2560	(12345678)  # ⣿ ╠ [BOX DRAWINGS DOUBLE VERTICAL AND RIGHT]
+glyph \u2563	(12345678)  # ⣿ ╣ [BOX DRAWINGS DOUBLE VERTICAL AND LEFT]
+glyph \u2566	( 23 5678)  # ⣶ ╦ [BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL]
+glyph \u2569	(123456  )  # ⠿ ╩ [BOX DRAWINGS DOUBLE UP AND HORIZONTAL]
+glyph \u256C	(12345678)  # ⣿ ╬ [BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL]
+
+glyph \u250D	( 23 5678)  # ⣶ ┍ [BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY]
+glyph \u250E	( 23 5678)  # ⣶ ┎ [BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT]
+glyph \u2511	( 23 5678)  # ⣶ ┑ [BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY]
+glyph \u2512	( 23 5678)  # ⣶ ┒ [BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT]
+glyph \u2515	(123456  )  # ⠿ ┕ [BOX DRAWINGS UP LIGHT AND RIGHT HEAVY]
+glyph \u2516	(123456  )  # ⠿ ┖ [BOX DRAWINGS UP HEAVY AND RIGHT LIGHT]
+glyph \u2519	(123456  )  # ⠿ ┙ [BOX DRAWINGS UP LIGHT AND LEFT HEAVY]
+glyph \u251A	(123456  )  # ⠿ ┚ [BOX DRAWINGS UP HEAVY AND LEFT LIGHT]
+glyph \u251D	(12345678)  # ⣿ ┝ [BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY]
+glyph \u251E	(12345678)  # ⣿ ┞ [BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT]
+glyph \u251F	(12345678)  # ⣿ ┟ [BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT]
+glyph \u2520	(12345678)  # ⣿ ┠ [BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT]
+glyph \u2521	(12345678)  # ⣿ ┡ [BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY]
+glyph \u2522	(12345678)  # ⣿ ┢ [BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY]
+glyph \u2525	(12345678)  # ⣿ ┥ [BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY]
+glyph \u2526	(12345678)  # ⣿ ┦ [BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT]
+glyph \u2527	(12345678)  # ⣿ ┧ [BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT]
+glyph \u2528	(12345678)  # ⣿ ┨ [BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT]
+glyph \u2529	(12345678)  # ⣿ ┩ [BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY]
+glyph \u252A	(12345678)  # ⣿ ┪ [BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY]
+glyph \u252D	( 23 5678)  # ⣶ ┭ [BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT]
+glyph \u252E	( 23 5678)  # ⣶ ┮ [BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT]
+glyph \u252F	( 23 5678)  # ⣶ ┯ [BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY]
+glyph \u2530	( 23 5678)  # ⣶ ┰ [BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT]
+glyph \u2531	( 23 5678)  # ⣶ ┱ [BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY]
+glyph \u2532	( 23 5678)  # ⣶ ┲ [BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY]
+glyph \u2535	(123456  )  # ⠿ ┵ [BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT]
+glyph \u2536	(123456  )  # ⠿ ┶ [BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT]
+glyph \u2537	(123456  )  # ⠿ ┷ [BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY]
+glyph \u2538	(123456  )  # ⠿ ┸ [BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT]
+glyph \u2539	(123456  )  # ⠿ ┹ [BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY]
+glyph \u253A	(123456  )  # ⠿ ┺ [BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY]
+glyph \u253D	(12345678)  # ⣿ ┽ [BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT]
+glyph \u253E	(12345678)  # ⣿ ┾ [BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT]
+glyph \u253F	(12345678)  # ⣿ ┿ [BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY]
+glyph \u2540	(12345678)  # ⣿ ╀ [BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT]
+glyph \u2541	(12345678)  # ⣿ ╁ [BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT]
+glyph \u2542	(12345678)  # ⣿ ╂ [BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT]
+glyph \u2543	(12345678)  # ⣿ ╃ [BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT]
+glyph \u2544	(12345678)  # ⣿ ╄ [BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT]
+glyph \u2545	(12345678)  # ⣿ ╅ [BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT]
+glyph \u2546	(12345678)  # ⣿ ╆ [BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT]
+glyph \u2547	(12345678)  # ⣿ ╇ [BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY]
+glyph \u2548	(12345678)  # ⣿ ╈ [BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY]
+glyph \u2549	(12345678)  # ⣿ ╉ [BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY]
+glyph \u254A	(12345678)  # ⣿ ╊ [BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY]
+
+glyph \u2552	( 23 5678)  # ⣶ ╒ [BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE]
+glyph \u2553	( 23 5678)  # ⣶ ╓ [BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE]
+glyph \u2555	( 23 5678)  # ⣶ ╕ [BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE]
+glyph \u2556	( 23 5678)  # ⣶ ╖ [BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE]
+glyph \u2558	(123456  )  # ⠿ ╘ [BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE]
+glyph \u2559	(123456  )  # ⠿ ╙ [BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE]
+glyph \u255B	(123456  )  # ⠿ ╛ [BOX DRAWINGS UP SINGLE AND LEFT DOUBLE]
+glyph \u255C	(123456  )  # ⠿ ╜ [BOX DRAWINGS UP DOUBLE AND LEFT SINGLE]
+glyph \u255E	(12345678)  # ⣿ ╞ [BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE]
+glyph \u255F	(12345678)  # ⣿ ╟ [BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE]
+glyph \u2561	(12345678)  # ⣿ ╡ [BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE]
+glyph \u2562	(12345678)  # ⣿ ╢ [BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE]
+glyph \u2564	( 23 5678)  # ⣶ ╤ [BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE]
+glyph \u2565	( 23 5678)  # ⣶ ╥ [BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE]
+glyph \u2567	(123456  )  # ⠿ ╧ [BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE]
+glyph \u2568	(123456  )  # ⠿ ╨ [BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE]
+glyph \u256A	(12345678)  # ⣿ ╪ [BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE]
+glyph \u256B	(12345678)  # ⣿ ╫ [BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE]
+
+glyph \u256D	( 23 5678)  # ⣶ ╭ [BOX DRAWINGS LIGHT ARC DOWN AND RIGHT]
+glyph \u256E	( 23 5678)  # ⣶ ╮ [BOX DRAWINGS LIGHT ARC DOWN AND LEFT]
+glyph \u256F	(123456  )  # ⠿ ╯ [BOX DRAWINGS LIGHT ARC UP AND LEFT]
+glyph \u2570	(123456  )  # ⠿ ╰ [BOX DRAWINGS LIGHT ARC UP AND RIGHT]
+
+glyph \u2574	( 23 56  )  # ⠶ ╴ [BOX DRAWINGS LIGHT LEFT]
+glyph \u2575	(123456  )  # ⠿ ╵ [BOX DRAWINGS LIGHT UP]
+glyph \u2576	( 23 56  )  # ⠶ ╶ [BOX DRAWINGS LIGHT RIGHT]
+glyph \u2577	( 23 5678)  # ⣶ ╷ [BOX DRAWINGS LIGHT DOWN]
+glyph \u2578	( 23 56  )  # ⠶ ╸ [BOX DRAWINGS HEAVY LEFT]
+glyph \u2579	(123456  )  # ⠿ ╹ [BOX DRAWINGS HEAVY UP]
+glyph \u257A	( 23 56  )  # ⠶ ╺ [BOX DRAWINGS HEAVY RIGHT]
+glyph \u257B	( 23 5678)  # ⣶ ╻ [BOX DRAWINGS HEAVY DOWN]
+
+glyph \u257C	( 23 56  )  # ⠶ ╼ [BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT]
+glyph \u257D	(12345678)  # ⣿ ╽ [BOX DRAWINGS LIGHT UP AND HEAVY DOWN]
+glyph \u257E	( 23 56  )  # ⠶ ╾ [BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT]
+glyph \u257F	(12345678)  # ⣿ ╿ [BOX DRAWINGS HEAVY UP AND LIGHT DOWN]
+
+glyph \u2571	(   4  7 )  # ⡈ ╱ [BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT]
+glyph \u2572	(1      8)  # ⢁ ╲ [BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT]
+glyph \u2573	(1  4  78)  # ⣉ ╳ [BOX DRAWINGS LIGHT DIAGONAL CROSS]
diff --git a/Tables/Text/bra.ttb b/Tables/Text/bra.ttb
new file mode 100644
index 0000000..ad56783
--- /dev/null
+++ b/Tables/Text/bra.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Braj
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/brf.ttb b/Tables/Text/brf.ttb
new file mode 100644
index 0000000..05f7a69
--- /dev/null
+++ b/Tables/Text/brf.ttb
@@ -0,0 +1,131 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Braille Ready Format (for viewing .brf files within an editor or pager)
+
+char	\s	(      )
+
+glyph	A	(1     )
+glyph	B	(12    )
+glyph	C	(1  4  )
+glyph	D	(1  45 )
+glyph	E	(1   5 )
+glyph	F	(12 4  )
+glyph	G	(12 45 )
+glyph	H	(12  5 )
+glyph	I	( 2 4  )
+glyph	J	( 2 45 )
+
+glyph	K	(1 3   )
+glyph	L	(123   )
+glyph	M	(1 34  )
+glyph	N	(1 345 )
+glyph	O	(1 3 5 )
+glyph	P	(1234  )
+glyph	Q	(12345 )
+glyph	R	(123 5 )
+glyph	S	( 234  )
+glyph	T	( 2345 )
+
+glyph	U	(1 3  6)
+glyph	V	(123  6)
+glyph	X	(1 34 6)
+glyph	Y	(1 3456)
+glyph	Z	(1 3 56)
+char	&	(1234 6)
+char	=	(123456)
+char	(	(123 56)
+char	!	( 234 6)
+char	)	( 23456)
+
+char	*	(1    6)
+char	<	(12   6)
+char	%	(1  4 6)
+char	?	(1  456)
+char	:	(1   56)
+char	$	(12 4 6)
+glyph	]	(12 456)
+glyph	\\	(12  56)
+glyph	[	( 2 4 6)
+glyph	W	( 2 456)
+
+char	1	( 2    )
+char	2	( 23   )
+char	3	( 2  5 )
+char	4	( 2  56)
+char	5	( 2   6)
+char	6	( 23 5 )
+char	7	( 23 56)
+char	8	( 23  6)
+char	9	(  3 5 )
+char	0	(  3 56)
+
+char	/	(  34  )
+char	+	(  34 6)
+char	\#	(  3456)
+char	>	(  345 )
+char	'	(  3   )
+char	-	(  3  6)
+
+glyph	@	(   4  )
+glyph	^	(   45 )
+char	_	(   456)
+char	"	(    5 )
+char	.	(   4 6)
+char	;	(    56)
+char	,	(     6)
+
+# For convenience, lowercase alphabetic characters are also defined
+# so as to have readable text when not viewing a .brf file
+# (like BRLTTY's config menu for switching to another braille table).
+char	a	(1     )
+char	b	(12    )
+char	c	(1  4  )
+char	d	(1  45 )
+char	e	(1   5 )
+char	f	(12 4  )
+char	g	(12 45 )
+char	h	(12  5 )
+char	i	( 2 4  )
+char	j	( 2 45 )
+char	k	(1 3   )
+char	l	(123   )
+char	m	(1 34  )
+char	n	(1 345 )
+char	o	(1 3 5 )
+char	p	(1234  )
+char	q	(12345 )
+char	r	(123 5 )
+char	s	( 234  )
+char	t	( 2345 )
+char	u	(1 3  6)
+char	v	(123  6)
+char	w	( 2 456)
+char	x	(1 34 6)
+char	y	(1 3456)
+char	z	(1 3 56)
+
+# The following additional characters need to be defined
+# because some .brf files contain non-standard characters
+# and because some braille translators output non-standard characters.
+char	{	( 2 4 6)
+char	}	(12 456)
+char	|	(12  56)
+char	~	(   45 )
+char	`	(   4  )
+glyph	\X7F	(   456)
diff --git a/Tables/Text/common.tti b/Tables/Text/common.tti
new file mode 100644
index 0000000..070d8f6
--- /dev/null
+++ b/Tables/Text/common.tti
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable is included by all general purpose text tables. Its
+# sole purpose is to be a common place wherein to include text subtables which
+# define braille representations that are generally useful in all contexts.
+
+include alias.tti
+include boxes.tti
+include blocks.tti
+include win-1252.tti
diff --git a/Tables/Text/cs.ttb b/Tables/Text/cs.ttb
new file mode 100644
index 0000000..c128016
--- /dev/null
+++ b/Tables/Text/cs.ttb
@@ -0,0 +1,176 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Czech
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-2
+char \x01	(1     78)  # 01 ⣁   [START OF HEADING]
+char \x02	(12    78)  # 02 ⣃   [START OF TEXT]
+char \x03	(1  4  78)  # 03 ⣉   [END OF TEXT]
+char \x04	(1  45 78)  # 04 ⣙   [END OF TRANSMISSION]
+char \x05	(1   5 78)  # 05 ⣑   [ENQUIRY]
+char \x06	(12 4  78)  # 06 ⣋   [ACKNOWLEDGE]
+char \x07	(12 45 78)  # 07 ⣛   [BELL]
+char \x08	(12  5 78)  # 08 ⣓   [BACKSPACE]
+char \x09	( 2 4  78)  # 09 ⣊   [CHARACTER TABULATION]
+char \x0A	( 2 45 78)  # 0A ⣚   [LINE FEED (LF)]
+char \x0B	(1 3   78)  # 0B ⣅   [LINE TABULATION]
+char \x0C	(123   78)  # 0C ⣇   [FORM FEED (FF)]
+char \x0D	(1 34  78)  # 0D ⣍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345 78)  # 0E ⣝   [SHIFT OUT]
+char \x0F	(1 3 5 78)  # 0F ⣕   [SHIFT IN]
+char \x10	(1234  78)  # 10 ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # 11 ⣟   [DEVICE CONTROL ONE]
+char \x12	(123 5 78)  # 12 ⣗   [DEVICE CONTROL TWO]
+char \x13	( 234  78)  # 13 ⣎   [DEVICE CONTROL THREE]
+char \x14	( 2345 78)  # 14 ⣞   [DEVICE CONTROL FOUR]
+char \x15	(1 3  678)  # 15 ⣥   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  678)  # 16 ⣧   [SYNCHRONOUS IDLE]
+char \x17	(123 5678)  # 17 ⣷   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 678)  # 18 ⣭   [CANCEL]
+char \x19	(1 345678)  # 19 ⣽   [END OF MEDIUM]
+char \x1A	(1 3 5678)  # 1A ⣵   [SUBSTITUTE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  34567 )  # 23 ⡼ # [NUMBER SIGN]
+char \x24	( 23 5  8)  # 24 ⢖ $ [DOLLAR SIGN]
+char \x25	(1234   8)  # 25 ⢏ % [PERCENT SIGN]
+char \x26	( 23 5 78)  # 26 ⣖ & [AMPERSAND]
+char \x27	(   4    )  # 27 ⠈ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 2  56  )  # 2B ⠲ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(12 456  )  # 2F ⠻ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23    8)  # 3C ⢆ < [LESS-THAN SIGN]
+char \x3D	( 23 5678)  # 3D ⣶ = [EQUALS SIGN]
+char \x3E	(    567 )  # 3E ⡰ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(12 456 8)  # 40 ⢻ @ [COMMERCIAL AT]
+char \x41	(1     7 )  # 41 ⡁ A [LATIN CAPITAL LETTER A]
+char \x42	(12    7 )  # 42 ⡃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4  7 )  # 43 ⡉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45 7 )  # 44 ⡙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5 7 )  # 45 ⡑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4  7 )  # 46 ⡋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45 7 )  # 47 ⡛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5 7 )  # 48 ⡓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4  7 )  # 49 ⡊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45 7 )  # 4A ⡚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3   7 )  # 4B ⡅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123   7 )  # 4C ⡇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34  7 )  # 4D ⡍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345 7 )  # 4E ⡝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5 7 )  # 4F ⡕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234  7 )  # 50 ⡏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345 7 )  # 51 ⡟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5 7 )  # 52 ⡗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234  7 )  # 53 ⡎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345 7 )  # 54 ⡞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  67 )  # 55 ⡥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  67 )  # 56 ⡧ V [LATIN CAPITAL LETTER V]
+char \x57	(123 567 )  # 57 ⡷ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 67 )  # 58 ⡭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 34567 )  # 59 ⡽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 567 )  # 5A ⡵ Z [LATIN CAPITAL LETTER Z]
+char \x5B	( 23  67 )  # 5B ⡦ [ [LEFT SQUARE BRACKET]
+char \x5C	( 23  6 8)  # 5C ⢦ \ [REVERSE SOLIDUS]
+char \x5D	(  3 567 )  # 5D ⡴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3  67 )  # 5F ⡤ _ [LOW LINE]
+char \x60	(   4  7 )  # 60 ⡈ ` [GRAVE ACCENT]
+char \x61	(1       )  # 61 ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # 62 ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # 63 ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1  45   )  # 64 ⠙ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # 65 ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # 66 ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # 67 ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # 68 ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # 69 ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # 6A ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # 6B ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # 6C ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # 6D ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # 6E ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # 6F ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # 70 ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # 71 ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # 72 ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # 73 ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # 74 ⠞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6  )  # 75 ⠥ u [LATIN SMALL LETTER U]
+char \x76	(123  6  )  # 76 ⠧ v [LATIN SMALL LETTER V]
+char \x77	(123 56  )  # 77 ⠷ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # 78 ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # 79 ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56  )  # 7A ⠵ z [LATIN SMALL LETTER Z]
+char \x7B	( 23  678)  # 7B ⣦ { [LEFT CURLY BRACKET]
+char \x7C	(   456  )  # 7C ⠸ | [VERTICAL LINE]
+char \x7D	(  3 5678)  # 7D ⣴ } [RIGHT CURLY BRACKET]
+char \x7E	(    5 7 )  # 7E ⡐ ~ [TILDE]
+char \x7F	( 234 678)  # 7F ⣮   [DELETE]
+char \u0160	(1   567 )  # A9 ⡱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u0164	(12  567 )  # AB ⡳ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char \u017D	( 234 67 )  # AE ⡮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u0161	(1   56  )  # B9 ⠱ š [LATIN SMALL LETTER S WITH CARON]
+char \u0165	(12  56  )  # BB ⠳ ť [LATIN SMALL LETTER T WITH CARON]
+char \u017E	( 234 6  )  # BE ⠮ ž [LATIN SMALL LETTER Z WITH CARON]
+char \xC1	(1    67 )  # C1 ⡡ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC4	(  345 78)  # C4 ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \u010C	(1  4 67 )  # C8 ⡩ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \xC9	(  345 7 )  # C9 ⡜ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \u011A	(12   67 )  # CC ⡣ Ě [LATIN CAPITAL LETTER E WITH CARON]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \u010E	(1  4567 )  # CF ⡹ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char \u0147	(12 4 67 )  # D2 ⡫ Ň [LATIN CAPITAL LETTER N WITH CARON]
+char \xD3	( 2 4 67 )  # D3 ⡪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \u0158	( 2 4567 )  # D8 ⡺ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char \u016E	( 234567 )  # D9 ⡾ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char \xDA	(  34 67 )  # DA ⡬ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDC	(12  5678)  # DC ⣳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1234 67 )  # DD ⡯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE1	(1    6  )  # E1 ⠡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \u010D	(1  4 6  )  # E8 ⠩ č [LATIN SMALL LETTER C WITH CARON]
+char \xE9	(  345   )  # E9 ⠜ é [LATIN SMALL LETTER E WITH ACUTE]
+char \u011B	(12   6  )  # EC ⠣ ě [LATIN SMALL LETTER E WITH CARON]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \u010F	(1  456  )  # EF ⠹ ď [LATIN SMALL LETTER D WITH CARON]
+char \u0148	(12 4 6  )  # F2 ⠫ ň [LATIN SMALL LETTER N WITH CARON]
+char \xF3	( 2 4 6  )  # F3 ⠪ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \u0159	( 2 456  )  # F8 ⠺ ř [LATIN SMALL LETTER R WITH CARON]
+char \u016F	( 23456  )  # F9 ⠾ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char \xFA	(  34 6  )  # FA ⠬ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1234 6  )  # FD ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+
+include common.tti
diff --git a/Tables/Text/ctl-latin.tti b/Tables/Text/ctl-latin.tti
new file mode 100644
index 0000000..6db55c7
--- /dev/null
+++ b/Tables/Text/ctl-latin.tti
@@ -0,0 +1,47 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the standard braille representations for
+# the 26 Latin control characters.
+
+char \x01	(1     78)  # 01 ⣁   [START OF HEADING]
+char \x02	(12    78)  # 02 ⣃   [START OF TEXT]
+char \x03	(1  4  78)  # 03 ⣉   [END OF TEXT]
+char \x04	(1  45 78)  # 04 ⣙   [END OF TRANSMISSION]
+char \x05	(1   5 78)  # 05 ⣑   [ENQUIRY]
+char \x06	(12 4  78)  # 06 ⣋   [ACKNOWLEDGE]
+char \x07	(12 45 78)  # 07 ⣛   [BELL]
+char \x08	(12  5 78)  # 08 ⣓   [BACKSPACE]
+char \x09	( 2 4  78)  # 09 ⣊   [CHARACTER TABULATION]
+char \x0A	( 2 45 78)  # 0A ⣚   [LINE FEED (LF)]
+char \x0B	(1 3   78)  # 0B ⣅   [LINE TABULATION]
+char \x0C	(123   78)  # 0C ⣇   [FORM FEED (FF)]
+char \x0D	(1 34  78)  # 0D ⣍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345 78)  # 0E ⣝   [SHIFT OUT]
+char \x0F	(1 3 5 78)  # 0F ⣕   [SHIFT IN]
+char \x10	(1234  78)  # 10 ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # 11 ⣟   [DEVICE CONTROL ONE]
+char \x12	(123 5 78)  # 12 ⣗   [DEVICE CONTROL TWO]
+char \x13	( 234  78)  # 13 ⣎   [DEVICE CONTROL THREE]
+char \x14	( 2345 78)  # 14 ⣞   [DEVICE CONTROL FOUR]
+char \x15	(1 3  678)  # 15 ⣥   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  678)  # 16 ⣧   [SYNCHRONOUS IDLE]
+char \x17	( 2 45678)  # 17 ⣺   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 678)  # 18 ⣭   [CANCEL]
+char \x19	(1 345678)  # 19 ⣽   [END OF MEDIUM]
+char \x1A	(1 3 5678)  # 1A ⣵   [SUBSTITUTE]
diff --git a/Tables/Text/cy.ttb b/Tables/Text/cy.ttb
new file mode 100644
index 0000000..54dbc84
--- /dev/null
+++ b/Tables/Text/cy.ttb
@@ -0,0 +1,97 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Welsh
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest: charset=iso-8859-14
+include ltr-latin.tti
+
+char \x2E	( 2      )  # 2E ⠂ . [FULL STOP]
+char \x30	( 2 45   )  # 30 ⠚ 0 [DIGIT ZERO]
+char \x31	(1       )  # 31 ⠁ 1 [DIGIT ONE]
+char \x32	(12      )  # 32 ⠃ 2 [DIGIT TWO]
+char \x33	(1  4    )  # 33 ⠉ 3 [DIGIT THREE]
+char \x34	(1  45   )  # 34 ⠙ 4 [DIGIT FOUR]
+char \x35	(1   5   )  # 35 ⠑ 5 [DIGIT FIVE]
+char \x36	(12 4    )  # 36 ⠋ 6 [DIGIT SIX]
+char \x37	(12 45   )  # 37 ⠛ 7 [DIGIT SEVEN]
+char \x38	(12  5   )  # 38 ⠓ 8 [DIGIT EIGHT]
+char \x39	( 2 4    )  # 39 ⠊ 9 [DIGIT NINE]
+char \u1EF2	(1 3456  )  # AC ⠽ Ỳ [LATIN CAPITAL LETTER Y WITH GRAVE]
+char \u0178	(1 3456  )  # AF ⠽ Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+char \u1EF3	(1 3456  )  # BC ⠽ ỳ [LATIN SMALL LETTER Y WITH GRAVE]
+char \xC0	(1       )  # C0 ⠁ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(1       )  # C1 ⠁ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1       )  # C2 ⠁ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC4	(1       )  # C4 ⠁ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC6	(1       )  # C6 ⠁ Æ [LATIN CAPITAL LETTER AE]
+char \xC8	(1   5   )  # C8 ⠑ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1   5   )  # C9 ⠑ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1   5   )  # CA ⠑ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(1   5   )  # CB ⠑ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	( 2 4    )  # CC ⠊ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 2 4    )  # CD ⠊ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	( 2 4    )  # CE ⠊ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	( 2 4    )  # CF ⠊ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \u0174	( 2 456  )  # D0 ⠺ Ŵ [LATIN CAPITAL LETTER W WITH CIRCUMFLEX]
+char \xD2	(1 3 5   )  # D2 ⠕ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(1 3 5   )  # D3 ⠕ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1 3 5   )  # D4 ⠕ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD6	(1 3 5   )  # D6 ⠕ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD9	(1 3  6  )  # D9 ⠥ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(1 3  6  )  # DA ⠥ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1 3  6  )  # DB ⠥ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(1 3  6  )  # DC ⠥ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1 3456  )  # DD ⠽ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \u0176	(1 3456  )  # DE ⠽ Ŷ [LATIN CAPITAL LETTER Y WITH CIRCUMFLEX]
+char \xDF	( 234 6  )  # DF ⠮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(1       )  # E0 ⠁ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1       )  # E1 ⠁ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1       )  # E2 ⠁ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE4	(1       )  # E4 ⠁ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE6	(1       )  # E6 ⠁ æ [LATIN SMALL LETTER AE]
+char \xE8	(1   5   )  # E8 ⠑ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(1   5   )  # E9 ⠑ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(1   5   )  # EA ⠑ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(1   5   )  # EB ⠑ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 2 4    )  # EC ⠊ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	( 2 4    )  # ED ⠊ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	( 2 4    )  # EE ⠊ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	( 2 4    )  # EF ⠊ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \u0175	( 2 456  )  # F0 ⠺ ŵ [LATIN SMALL LETTER W WITH CIRCUMFLEX]
+char \xF2	(1 3 5   )  # F2 ⠕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1 3 5   )  # F3 ⠕ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1 3 5   )  # F4 ⠕ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF6	(1 3 5   )  # F6 ⠕ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF9	(1 3  6  )  # F9 ⠥ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1 3  6  )  # FA ⠥ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1 3  6  )  # FB ⠥ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(1 3  6  )  # FC ⠥ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 3456  )  # FD ⠽ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \u0177	(1 3456  )  # FE ⠽ ŷ [LATIN SMALL LETTER Y WITH CIRCUMFLEX]
+char \xFF	(1 3456  )  # FF ⠽ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+char \u2011	( 234  78)  #    ⣎ ‑ [NON-BREAKING HYPHEN]
+
+include common.tti
diff --git a/Tables/Text/da-1252.ttb b/Tables/Text/da-1252.ttb
new file mode 100644
index 0000000..68719d7
--- /dev/null
+++ b/Tables/Text/da-1252.ttb
@@ -0,0 +1,226 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Danish (Svend Thougaard, 2002-11-18)
+
+# This is the standard table defined by Svend Thougaard [2002-11-18]. It is
+# primarily defined for use with the Windows-1252 character table. It is only
+# in the BRLTTY package for reference as most of the control characters have
+# been mapped to unusual braille patterns. The left brace ({) and percent sign
+# (%) don't have logical representations.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=cp1252
+char \x00	(       8)  # 00 ⢀   [NULL]
+char \x01	(1     78)  # 01 ⣁   [START OF HEADING]
+char \x02	(12    78)  # 02 ⣃   [START OF TEXT]
+char \x03	(1  4  78)  # 03 ⣉   [END OF TEXT]
+char \x04	(1  45 78)  # 04 ⣙   [END OF TRANSMISSION]
+char \x05	( 2 456 8)  # 05 ⢺   [ENQUIRY]
+char \x06	(12 4  78)  # 06 ⣋   [ACKNOWLEDGE]
+char \x07	(12 45 78)  # 07 ⣛   [BELL]
+char \x08	(12  5 78)  # 08 ⣓   [BACKSPACE]
+char \x09	( 2 4  78)  # 09 ⣊   [CHARACTER TABULATION]
+char \x0A	(     678)  # 0A ⣠   [LINE FEED (LF)]
+char \x0B	(1 3  6 8)  # 0B ⢥   [LINE TABULATION]
+char \x0C	(123   78)  # 0C ⣇   [FORM FEED (FF)]
+char \x0D	( 2  5 7 )  # 0D ⡒   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345 78)  # 0E ⣝   [SHIFT OUT]
+char \x0F	(123 5  8)  # 0F ⢗   [SHIFT IN]
+char \x10	(1234  78)  # 10 ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # 11 ⣟   [DEVICE CONTROL ONE]
+char \x12	(1 3 56 8)  # 12 ⢵   [DEVICE CONTROL TWO]
+char \x13	(   45 78)  # 13 ⣘   [DEVICE CONTROL THREE]
+char \x14	( 2   6 8)  # 14 ⢢   [DEVICE CONTROL FOUR]
+char \x15	(1 3  678)  # 15 ⣥   [NEGATIVE ACKNOWLEDGE]
+char \x16	( 2    78)  # 16 ⣂   [SYNCHRONOUS IDLE]
+char \x17	(  3 5 78)  # 17 ⣔   [END OF TRANSMISSION BLOCK]
+char \x18	(      78)  # 18 ⣀   [CANCEL]
+char \x19	(     6 8)  # 19 ⢠   [END OF MEDIUM]
+char \x1A	(1 3 5678)  # 1A ⣵   [SUBSTITUTE]
+char \x1B	( 2   678)  # 1B ⣢   [ESCAPE]
+char \x1C	(   45678)  # 1C ⣸   [INFORMATION SEPARATOR FOUR]
+char \x1D	(123  6 8)  # 1D ⢧   [INFORMATION SEPARATOR THREE]
+char \x1E	(1234 678)  # 1E ⣯   [INFORMATION SEPARATOR TWO]
+char \x1F	( 23 5678)  # 1F ⣶   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  3456 8)  # 23 ⢼ # [NUMBER SIGN]
+char \x24	( 2  5678)  # 24 ⣲ $ [DOLLAR SIGN]
+char \x25	( 2 45 78)  # 25 ⣚ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(   4    )  # 27 ⠈ ' [APOSTROPHE]
+char \x28	( 23  6 8)  # 28 ⢦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56 8)  # 29 ⢴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5  8)  # 2B ⢖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6 8)  # 2D ⢤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34   8)  # 2F ⢌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(  3 5  8)  # 3C ⢔ < [LESS-THAN SIGN]
+char \x3D	( 23 56 8)  # 3D ⢶ = [EQUALS SIGN]
+char \x3E	( 2   67 )  # 3E ⡢ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4  78)  # 40 ⣈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  678)  # 5B ⣦ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # 5C ⡌ \ [REVERSE SOLIDUS]
+char \x5D	(  3 5678)  # 5D ⣴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(1234   8)  # 5E ⢏ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3  678)  # 5F ⣤ _ [LOW LINE]
+char \x60	(    5   )  # 60 ⠐ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123  678)  # 7B ⣧ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(  345678)  # 7D ⣼ } [RIGHT CURLY BRACKET]
+char \x7E	(   4 67 )  # 7E ⡨ ~ [TILDE]
+char \x7F	(12345678)  # 7F ⣿   [DELETE]
+char \u20AC	(1   5 78)  # 80 ⣑ € [EURO SIGN]
+char \u201A	(   45 7 )  # 82 ⡘ ‚ [SINGLE LOW-9 QUOTATION MARK]
+char \u0192	(    5  8)  # 83 ⢐ ƒ [LATIN SMALL LETTER F WITH HOOK]
+char \u201E	( 23   78)  # 84 ⣆ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u2026	(     6  )  # 85 ⠠ … [HORIZONTAL ELLIPSIS]
+char \u2020	( 23 5 7 )  # 86 ⡖ † [DAGGER]
+char \u2021	( 23 5 78)  # 87 ⣖ ‡ [DOUBLE DAGGER]
+char \u02C6	(    5678)  # 88 ⣰ ˆ [MODIFIER LETTER CIRCUMFLEX ACCENT]
+char \u2030	( 2 45678)  # 89 ⣺ ‰ [PER MILLE SIGN]
+char \u0160	( 234  78)  # 8A ⣎ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u2039	(   456  )  # 8B ⠸ ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+char \u0152	(1 3 5 78)  # 8C ⣕ Œ [LATIN CAPITAL LIGATURE OE]
+char \u017D	(  34 67 )  # 8E ⡬ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u2018	(   4  7 )  # 91 ⡈ ‘ [LEFT SINGLE QUOTATION MARK]
+char \u2019	(   4   8)  # 92 ⢈ ’ [RIGHT SINGLE QUOTATION MARK]
+char \u201C	( 23   7 )  # 93 ⡆ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	(    56 8)  # 94 ⢰ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u2022	(  3   7 )  # 95 ⡄ • [BULLET]
+char \u2013	(  3  6  )  # 96 ⠤ – [EN DASH]
+char \u2014	(  3  67 )  # 97 ⡤ — [EM DASH]
+char \u02DC	(   4 6  )  # 98 ⠨ ˜ [SMALL TILDE]
+char \u2122	( 2345 78)  # 99 ⣞ ™ [TRADE MARK SIGN]
+char \u0161	( 234   8)  # 9A ⢎ š [LATIN SMALL LETTER S WITH CARON]
+char \u203A	(   4567 )  # 9B ⡸ › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+char \u0153	(1 3 5  8)  # 9C ⢕ œ [LATIN SMALL LIGATURE OE]
+char \u017E	(  34 6  )  # 9E ⠬ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u0178	( 2345678)  # 9F ⣾ Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+char \xA1	( 2  56  )  # A1 ⠲ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5 78)  # A2 ⣒ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	( 23  67 )  # A4 ⡦ ¤ [CURRENCY SIGN]
+char \xA5	(     67 )  # A5 ⡠ ¥ [YEN SIGN]
+char \xA6	(  34  78)  # A6 ⣌ ¦ [BROKEN BAR]
+char \xA7	(    5 78)  # A7 ⣐ § [SECTION SIGN]
+char \xA8	(    56  )  # A8 ⠰ ¨ [DIAERESIS]
+char \xA9	(1 34 678)  # A9 ⣭ © [COPYRIGHT SIGN]
+char \xAA	( 234 678)  # AA ⣮ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5 7 )  # AB ⡐ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  34567 )  # AC ⡼ ¬ [NOT SIGN]
+char \xAD	(  3   78)  # AD ⣄ ­ [SOFT HYPHEN]
+char \xAE	(123 5 78)  # AE ⣗ ® [REGISTERED SIGN]
+char \xAF	( 23 567 )  # AF ⡶ ¯ [MACRON]
+char \xB0	(  3 56  )  # B0 ⠴ ° [DEGREE SIGN]
+char \xB1	(12345  8)  # B1 ⢟ ± [PLUS-MINUS SIGN]
+char \xB2	( 23    8)  # B2 ⢆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5  8)  # B3 ⢒ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4 6 8)  # B4 ⢨ ´ [ACUTE ACCENT]
+char \xB5	( 23  6  )  # B5 ⠦ µ [MICRO SIGN]
+char \xB6	(123456 8)  # B6 ⢿ ¶ [PILCROW SIGN]
+char \xB7	(  3    8)  # B7 ⢄ · [MIDDLE DOT]
+char \xB8	(   4 678)  # B8 ⣨ ¸ [CEDILLA]
+char \xB9	( 2     8)  # B9 ⢂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(      7 )  # BA ⡀ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    567 )  # BB ⡰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 345  8)  # BC ⢝ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   45  8)  # BD ⢘ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  3456  )  # BE ⠼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  34    )  # BF ⠌ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 567 )  # C0 ⡷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 5678)  # C1 ⣷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # C2 ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 678)  # C3 ⣩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 78)  # C4 ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(1   5678)  # CC ⣱ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(12   678)  # CD ⣣ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(1 345678)  # D0 ⣽ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(12 4 678)  # D2 ⣫ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 678)  # D3 ⣬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  45678)  # D5 ⣹ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # D7 ⢭ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 67 )  # D8 ⡪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 234567 )  # D9 ⡾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12  5678)  # DA ⣳ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1 34  78)  # DD ⣍ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(1 3   78)  # DE ⣅ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56 8)  # E1 ⢷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # E2 ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # E3 ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1   56 8)  # EC ⢱ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(12   6 8)  # ED ⢣ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1 3456 8)  # F0 ⢽ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(12 4 6 8)  # F2 ⢫ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6 8)  # F3 ⢬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1  456 8)  # F5 ⢹ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56 8)  # F7 ⢲ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6  )  # F8 ⠪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(12  56 8)  # FA ⢳ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 34   8)  # FD ⢍ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1 3    8)  # FE ⢅ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 23456 8)  # FF ⢾ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/da-lt.ttb b/Tables/Text/da-lt.ttb
new file mode 100644
index 0000000..7c371bf
--- /dev/null
+++ b/Tables/Text/da-lt.ttb
@@ -0,0 +1,207 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Danish (LogText)
+
+# Danish LogText users are accustomed to the percent sign (%) and the
+# exclamation point (!) being mapped to patterns which differ from the new 1252
+# standard. LogText users will be more comfortable with this table.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(12345678)  # 00 ⣿   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(  345 78)  # 1B ⣜   [ESCAPE]
+char \x1C	(   45678)  # 1C ⣸   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2    78)  # 1D ⣂   [INFORMATION SEPARATOR THREE]
+char \x1E	(1234 678)  # 1E ⣯   [INFORMATION SEPARATOR TWO]
+char \x1F	( 23 5678)  # 1F ⣶   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	(    56  )  # 21 ⠰ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  3456 8)  # 23 ⢼ # [NUMBER SIGN]
+char \x24	(1   56 8)  # 24 ⢱ $ [DOLLAR SIGN]
+char \x25	(  3 56 8)  # 25 ⢴ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	(12   6 8)  # 28 ⢣ ( [LEFT PARENTHESIS]
+char \x29	(  345  8)  # 29 ⢜ ) [RIGHT PARENTHESIS]
+char \x2A	(1 34 6 8)  # 2A ⢭ * [ASTERISK]
+char \x2B	( 23 5  8)  # 2B ⢖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6 8)  # 2D ⢤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  5  8)  # 2F ⢒ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(1 34   8)  # 3C ⢍ < [LESS-THAN SIGN]
+char \x3D	( 23 56 8)  # 3D ⢶ = [EQUALS SIGN]
+char \x3E	( 234   8)  # 3E ⢎ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4  78)  # 40 ⣈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123  6 8)  # 5B ⢧ [ [LEFT SQUARE BRACKET]
+char \x5C	(1    6 8)  # 5C ⢡ \ [REVERSE SOLIDUS]
+char \x5D	(1  456 8)  # 5D ⢹ ] [RIGHT SQUARE BRACKET]
+char \x5E	(     67 )  # 5E ⡠ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(      78)  # 5F ⣀ _ [LOW LINE]
+char \x60	( 23 567 )  # 60 ⡶ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 2 4 6 8)  # 7B ⢪ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(1 3 5  8)  # 7D ⢕ } [RIGHT CURLY BRACKET]
+char \x7E	(     6 8)  # 7E ⢠ ~ [TILDE]
+char \x7F	(       8)  # 7F ⢀   [DELETE]
+char \x80	(   4    )  # 80 ⠈   [<control-0080>]
+char \x81	(   45   )  # 81 ⠘   [<control-0081>]
+char \x82	(   45 7 )  # 82 ⡘   [BREAK PERMITTED HERE]
+char \x83	(    5   )  # 83 ⠐   [NO BREAK HERE]
+char \x84	( 23   78)  # 84 ⣆   [<control-0084>]
+char \x85	( 23 5   )  # 85 ⠖   [NEXT LINE (NEL)]
+char \x86	( 23 5 7 )  # 86 ⡖   [START OF SELECTED AREA]
+char \x87	( 23 5 78)  # 87 ⣖   [END OF SELECTED AREA]
+char \x88	(    5678)  # 88 ⣰   [CHARACTER TABULATION SET]
+char \x89	(     678)  # 89 ⣠   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	( 23  6 8)  # 8A ⢦   [LINE TABULATION SET]
+char \x8B	(   456  )  # 8B ⠸   [PARTIAL LINE FORWARD]
+char \x8C	(  34  7 )  # 8C ⡌   [PARTIAL LINE BACKWARD]
+char \x8D	(  3 567 )  # 8D ⡴   [REVERSE LINE FEED]
+char \x8E	(  34 67 )  # 8E ⡬   [SINGLE SHIFT TWO]
+char \x8F	( 2    7 )  # 8F ⡂   [SINGLE SHIFT THREE]
+char \x90	(  3 5 7 )  # 90 ⡔   [DEVICE CONTROL STRING]
+char \x91	(   4  7 )  # 91 ⡈   [PRIVATE USE ONE]
+char \x92	(   4   8)  # 92 ⢈   [PRIVATE USE TWO]
+char \x93	( 23   7 )  # 93 ⡆   [SET TRANSMIT STATE]
+char \x94	(    56 8)  # 94 ⢰   [CANCEL CHARACTER]
+char \x95	(  3   7 )  # 95 ⡄   [MESSAGE WAITING]
+char \x96	(  3  6  )  # 96 ⠤   [START OF GUARDED AREA]
+char \x97	(  3  67 )  # 97 ⡤   [END OF GUARDED AREA]
+char \x98	(   4 6  )  # 98 ⠨   [START OF STRING]
+char \x99	( 2  5 7 )  # 99 ⡒   [<control-0099>]
+char \x9A	(  34    )  # 9A ⠌   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(   4567 )  # 9B ⡸   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(   4 67 )  # 9C ⡨   [STRING TERMINATOR]
+char \x9D	( 2345  8)  # 9D ⢞   [OPERATING SYSTEM COMMAND]
+char \x9E	(  34 6  )  # 9E ⠬   [PRIVACY MESSAGE]
+char \x9F	( 2345678)  # 9F ⣾   [APPLICATION PROGRAM COMMAND]
+char \xA1	( 2  56  )  # A1 ⠲ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5 78)  # A2 ⣒ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	( 23  67 )  # A4 ⡦ ¤ [CURRENCY SIGN]
+char \xA5	( 2  5678)  # A5 ⣲ ¥ [YEN SIGN]
+char \xA6	(  34  78)  # A6 ⣌ ¦ [BROKEN BAR]
+char \xA7	(    5 78)  # A7 ⣐ § [SECTION SIGN]
+char \xA8	( 23  678)  # A8 ⣦ ¨ [DIAERESIS]
+char \xA9	(  345678)  # A9 ⣼ © [COPYRIGHT SIGN]
+char \xAA	( 234 678)  # AA ⣮ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5 7 )  # AB ⡐ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  34567 )  # AC ⡼ ¬ [NOT SIGN]
+char \xAD	(  3   78)  # AD ⣄ ­ [SOFT HYPHEN]
+char \xAE	(   45 78)  # AE ⣘ ® [REGISTERED SIGN]
+char \xAF	( 2   67 )  # AF ⡢ ¯ [MACRON]
+char \xB0	(  3 56  )  # B0 ⠴ ° [DEGREE SIGN]
+char \xB1	(12345  8)  # B1 ⢟ ± [PLUS-MINUS SIGN]
+char \xB2	( 23    8)  # B2 ⢆ ² [SUPERSCRIPT TWO]
+char \xB3	(1234   8)  # B3 ⢏ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4 6 8)  # B4 ⢨ ´ [ACUTE ACCENT]
+char \xB5	( 23  6  )  # B5 ⠦ µ [MICRO SIGN]
+char \xB6	(123456 8)  # B6 ⢿ ¶ [PILCROW SIGN]
+char \xB7	(  3    8)  # B7 ⢄ · [MIDDLE DOT]
+char \xB8	(   4 678)  # B8 ⣨ ¸ [CEDILLA]
+char \xB9	( 2     8)  # B9 ⢂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(      7 )  # BA ⡀ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    567 )  # BB ⡰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 345  8)  # BC ⢝ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   45  8)  # BD ⢘ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  3456  )  # BE ⠼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  34   8)  # BF ⢌ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 567 )  # C0 ⡷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 5678)  # C1 ⣷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # C2 ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 678)  # C3 ⣩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  3 5  8)  # C4 ⢔ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(1   5678)  # CC ⣱ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(12   678)  # CD ⣣ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  3 5678)  # D0 ⣴ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(12 4 678)  # D2 ⣫ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 678)  # D3 ⣬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  45678)  # D5 ⣹ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 3 56 8)  # D7 ⢵ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 67 )  # D8 ⡪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 234567 )  # D9 ⡾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12  5678)  # DA ⣳ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(  3 5 78)  # DD ⣔ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	( 2   678)  # DE ⣢ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56 8)  # E1 ⢷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	( 2   6 8)  # E2 ⢢ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # E3 ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(    5  8)  # E4 ⢐ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  3  678)  # EC ⣤ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(1 3  6 8)  # ED ⢥ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1 3456 8)  # F0 ⢽ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(12 4 6 8)  # F2 ⢫ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6 8)  # F3 ⢬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(123 5  8)  # F5 ⢗ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(  3 5   )  # F6 ⠔ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56 8)  # F7 ⢲ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6  )  # F8 ⠪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(12  56 8)  # FA ⢳ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 2 456 8)  # FD ⢺ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1 3    8)  # FE ⢅ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 23456 8)  # FF ⢾ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/da.ttb b/Tables/Text/da.ttb
new file mode 100644
index 0000000..21493c4
--- /dev/null
+++ b/Tables/Text/da.ttb
@@ -0,0 +1,207 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Danish
+
+# This is the table which comes closest to the Danish standard 1252 table. All
+# control characters are mapped as their corresponding capital letters with
+# dot-8 added. Most Danish braille users should use this table.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(12345678)  # 00 ⣿   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2   678)  # 1B ⣢   [ESCAPE]
+char \x1C	(   45678)  # 1C ⣸   [INFORMATION SEPARATOR FOUR]
+char \x1D	(123  6 8)  # 1D ⢧   [INFORMATION SEPARATOR THREE]
+char \x1E	(1234 678)  # 1E ⣯   [INFORMATION SEPARATOR TWO]
+char \x1F	( 23 5678)  # 1F ⣶   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  3456 8)  # 23 ⢼ # [NUMBER SIGN]
+char \x24	( 2  5678)  # 24 ⣲ $ [DOLLAR SIGN]
+char \x25	(     678)  # 25 ⣠ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(   4    )  # 27 ⠈ ' [APOSTROPHE]
+char \x28	( 23  6 8)  # 28 ⢦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56 8)  # 29 ⢴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5  8)  # 2B ⢖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6 8)  # 2D ⢤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(  3 5  8)  # 3C ⢔ < [LESS-THAN SIGN]
+char \x3D	( 23 56 8)  # 3D ⢶ = [EQUALS SIGN]
+char \x3E	( 2   67 )  # 3E ⡢ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4  78)  # 40 ⣈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  678)  # 5B ⣦ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # 5C ⡌ \ [REVERSE SOLIDUS]
+char \x5D	(  3 5678)  # 5D ⣴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(1234   8)  # 5E ⢏ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3  678)  # 5F ⣤ _ [LOW LINE]
+char \x60	(    5  8)  # 60 ⢐ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 2    78)  # 7B ⣂ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(  345678)  # 7D ⣼ } [RIGHT CURLY BRACKET]
+char \x7E	(   4 67 )  # 7E ⡨ ~ [TILDE]
+char \x7F	(       8)  # 7F ⢀   [DELETE]
+char \x80	( 2 456 8)  # 80 ⢺   [<control-0080>]
+char \x81	(   45   )  # 81 ⠘   [<control-0081>]
+char \x82	(   45 7 )  # 82 ⡘   [BREAK PERMITTED HERE]
+char \x83	(    5   )  # 83 ⠐   [NO BREAK HERE]
+char \x84	( 23   78)  # 84 ⣆   [<control-0084>]
+char \x85	(     6  )  # 85 ⠠   [NEXT LINE (NEL)]
+char \x86	( 23 5 7 )  # 86 ⡖   [START OF SELECTED AREA]
+char \x87	( 23 5 78)  # 87 ⣖   [END OF SELECTED AREA]
+char \x88	(    5678)  # 88 ⣰   [CHARACTER TABULATION SET]
+char \x89	(  3 5 78)  # 89 ⣔   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	(   45 78)  # 8A ⣘   [LINE TABULATION SET]
+char \x8B	(   456  )  # 8B ⠸   [PARTIAL LINE FORWARD]
+char \x8C	(123 5  8)  # 8C ⢗   [PARTIAL LINE BACKWARD]
+char \x8D	(  3 567 )  # 8D ⡴   [REVERSE LINE FEED]
+char \x8E	(  34 67 )  # 8E ⡬   [SINGLE SHIFT TWO]
+char \x8F	( 2    7 )  # 8F ⡂   [SINGLE SHIFT THREE]
+char \x90	(  3 5 7 )  # 90 ⡔   [DEVICE CONTROL STRING]
+char \x91	(   4  7 )  # 91 ⡈   [PRIVATE USE ONE]
+char \x92	(   4   8)  # 92 ⢈   [PRIVATE USE TWO]
+char \x93	( 23   7 )  # 93 ⡆   [SET TRANSMIT STATE]
+char \x94	(    56 8)  # 94 ⢰   [CANCEL CHARACTER]
+char \x95	(  3   7 )  # 95 ⡄   [MESSAGE WAITING]
+char \x96	(  3  6  )  # 96 ⠤   [START OF GUARDED AREA]
+char \x97	(  3  67 )  # 97 ⡤   [END OF GUARDED AREA]
+char \x98	(   4 6  )  # 98 ⠨   [START OF STRING]
+char \x99	( 2   6 8)  # 99 ⢢   [<control-0099>]
+char \x9A	( 234   8)  # 9A ⢎   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(   4567 )  # 9B ⡸   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(1 3 5  8)  # 9C ⢕   [STRING TERMINATOR]
+char \x9D	( 2345  8)  # 9D ⢞   [OPERATING SYSTEM COMMAND]
+char \x9E	(  34 6  )  # 9E ⠬   [PRIVACY MESSAGE]
+char \x9F	( 2345678)  # 9F ⣾   [APPLICATION PROGRAM COMMAND]
+char \xA1	( 2  56  )  # A1 ⠲ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5 78)  # A2 ⣒ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	( 23  67 )  # A4 ⡦ ¤ [CURRENCY SIGN]
+char \xA5	(     67 )  # A5 ⡠ ¥ [YEN SIGN]
+char \xA6	(  34  78)  # A6 ⣌ ¦ [BROKEN BAR]
+char \xA7	(    5 78)  # A7 ⣐ § [SECTION SIGN]
+char \xA8	(    56  )  # A8 ⠰ ¨ [DIAERESIS]
+char \xA9	(      78)  # A9 ⣀ © [COPYRIGHT SIGN]
+char \xAA	( 234 678)  # AA ⣮ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5 7 )  # AB ⡐ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  34567 )  # AC ⡼ ¬ [NOT SIGN]
+char \xAD	(  3   78)  # AD ⣄ ­ [SOFT HYPHEN]
+char \xAE	(1 3 56 8)  # AE ⢵ ® [REGISTERED SIGN]
+char \xAF	( 23 567 )  # AF ⡶ ¯ [MACRON]
+char \xB0	(  3 56  )  # B0 ⠴ ° [DEGREE SIGN]
+char \xB1	(12345  8)  # B1 ⢟ ± [PLUS-MINUS SIGN]
+char \xB2	( 23    8)  # B2 ⢆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5  8)  # B3 ⢒ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4 6 8)  # B4 ⢨ ´ [ACUTE ACCENT]
+char \xB5	( 23  6  )  # B5 ⠦ µ [MICRO SIGN]
+char \xB6	(123456 8)  # B6 ⢿ ¶ [PILCROW SIGN]
+char \xB7	(  3    8)  # B7 ⢄ · [MIDDLE DOT]
+char \xB8	(   4 678)  # B8 ⣨ ¸ [CEDILLA]
+char \xB9	( 2     8)  # B9 ⢂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(      7 )  # BA ⡀ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    567 )  # BB ⡰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 345  8)  # BC ⢝ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   45  8)  # BD ⢘ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  3456  )  # BE ⠼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  34   8)  # BF ⢌ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 567 )  # C0 ⡷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 5678)  # C1 ⣷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # C2 ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 678)  # C3 ⣩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 78)  # C4 ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(1   5678)  # CC ⣱ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(12   678)  # CD ⣣ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(     6 8)  # D0 ⢠ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(12 4 678)  # D2 ⣫ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 678)  # D3 ⣬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  45678)  # D5 ⣹ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # D7 ⢭ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 67 )  # D8 ⡪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 234567 )  # D9 ⡾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12  5678)  # DA ⣳ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 2  5 7 )  # DD ⡒ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(1 3  6 8)  # DE ⢥ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56 8)  # E1 ⢷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # E2 ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # E3 ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1   56 8)  # EC ⢱ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(12   6 8)  # ED ⢣ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1 3456 8)  # F0 ⢽ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(12 4 6 8)  # F2 ⢫ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6 8)  # F3 ⢬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1  456 8)  # F5 ⢹ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56 8)  # F7 ⢲ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6  )  # F8 ⠪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(12  56 8)  # FA ⢳ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 34   8)  # FD ⢍ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1 3    8)  # FE ⢅ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 23456 8)  # FF ⢾ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/de-chess.tti b/Tables/Text/de-chess.tti
new file mode 100644
index 0000000..6a17ca3
--- /dev/null
+++ b/Tables/Text/de-chess.tti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This text subtable defines braille representations for the chess figures in
+# terms of the letters that are used for them in the German language.
+
+# See: http://en.wikipedia.org/wiki/Chess_symbols_in_Unicode
+
+alias \u2654 K # ♔ [WHITE CHESS KING]
+alias \u2655 D # ♕ [WHITE CHESS QUEEN]
+alias \u2656 T # ♖ [WHITE CHESS ROOK]
+alias \u2657 L # ♗ [WHITE CHESS BISHOP]
+alias \u2658 S # ♘ [WHITE CHESS KNIGHT]
+alias \u2659 B # ♙ [WHITE CHESS PAWN]
+alias \u265A k # ♚ [BLACK CHESS KING]
+alias \u265B d # ♛ [BLACK CHESS QUEEN]
+alias \u265C t # ♜ [BLACK CHESS ROOK]
+alias \u265D l # ♝ [BLACK CHESS BISHOP]
+alias \u265E s # ♞ [BLACK CHESS KNIGHT]
+alias \u265F b # ♟ [BLACK CHESS PAWN]
diff --git a/Tables/Text/de.ttb b/Tables/Text/de.ttb
new file mode 100644
index 0000000..4ab42bc
--- /dev/null
+++ b/Tables/Text/de.ttb
@@ -0,0 +1,183 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - German
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 346
+include num-dot6.tti
+
+# special characters
+char \u20AC	(   45 7 )  # ⡘ € [EURO SIGN]
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(  345 78)  # 00 ⣜   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(123 5678)  # 1B ⣷   [ESCAPE]
+char \x1C	(  34  78)  # 1C ⣌   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2345678)  # 1D ⣾   [INFORMATION SEPARATOR THREE]
+char \x1E	( 234 678)  # 1E ⣮   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	(    5   )  # 21 ⠐ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # 22 ⠈ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(   4 6  )  # 24 ⠨ $ [DOLLAR SIGN]
+char \x25	(123456  )  # 25 ⠿ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # 3C ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(   45   )  # 3E ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345 7 )  # 40 ⡜ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 567 )  # 5B ⡷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # 5C ⡌ \ [REVERSE SOLIDUS]
+char \x5D	( 234567 )  # 5D ⡾ ] [RIGHT SQUARE BRACKET]
+char \x5E	( 234 67 )  # 5E ⡮ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # 5F ⡸ _ [LOW LINE]
+char \x60	(  345   )  # 60 ⠜ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 56  )  # 7B ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(  34    )  # 7C ⠌ | [VERTICAL LINE]
+char \x7D	( 23456  )  # 7D ⠾ } [RIGHT CURLY BRACKET]
+char \x7E	( 234 6  )  # 7E ⠮ ~ [TILDE]
+char \x7F	(   456  )  # 7F ⠸   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(    5  8)  # A2 ⢐ ¢ [CENT SIGN]
+char \xA3	(   4 67 )  # A3 ⡨ £ [POUND SIGN]
+char \xA4	(  3  678)  # A4 ⣤ ¤ [CURRENCY SIGN]
+char \xA5	(   4 6 8)  # A5 ⢨ ¥ [YEN SIGN]
+char \xA6	( 2   678)  # A6 ⣢ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	( 2    78)  # A8 ⣂ ¨ [DIAERESIS]
+char \xA9	(1  4567 )  # A9 ⡹ © [COPYRIGHT SIGN]
+char \xAA	(12  5  8)  # AA ⢓ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5678)  # AB ⣰ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  5678)  # AC ⣲ ¬ [NOT SIGN]
+char \xAD	(12 4   8)  # AD ⢋ ­ [SOFT HYPHEN]
+char \xAE	(   4 678)  # AE ⣨ ® [REGISTERED SIGN]
+char \xAF	(12 45  8)  # AF ⢛ ¯ [MACRON]
+char \xB0	(   456 8)  # B0 ⢸ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	(12     8)  # B2 ⢃ ² [SUPERSCRIPT TWO]
+char \xB3	( 23   7 )  # B3 ⡆ ³ [SUPERSCRIPT THREE]
+char \xB4	(1 3 56 8)  # B4 ⢵ ´ [ACUTE ACCENT]
+char \xB5	(1 34   8)  # B5 ⢍ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(  3   7 )  # B7 ⡄ · [MIDDLE DOT]
+char \xB8	(1  4   8)  # B8 ⢉ ¸ [CEDILLA]
+char \xB9	(   45 7 )  # B9 ⡘ ¹ [SUPERSCRIPT ONE]
+char \xBA	( 2 45  8)  # BA ⢚ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(   45 78)  # BB ⣘ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 3  6 8)  # BC ⢥ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(123  6 8)  # BD ⢧ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  34 678)  # BE ⣬ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  3    8)  # BF ⢄ ¿ [INVERTED QUESTION MARK]
+char \xC0	(  3   78)  # C0 ⣄ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1 3    8)  # C2 ⢅ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	( 23  67 )  # C3 ⡦ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  34567 )  # C5 ⡼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(   4  7 )  # C6 ⡈ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 23   78)  # C8 ⣆ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 23    8)  # C9 ⢆ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(       8)  # CA ⢀ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12345  8)  # CB ⢟ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(123    8)  # CC ⢇ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 2  5 78)  # CD ⣒ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(12 45678)  # CE ⣻ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(1    67 )  # CF ⡡ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(    5 7 )  # D0 ⡐ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	( 2  567 )  # D1 ⡲ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2     8)  # D2 ⢂ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	( 2 4 678)  # D3 ⣪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2 4   8)  # D4 ⢊ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1234 6 8)  # D5 ⢯ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(  3 5  8)  # D6 ⢔ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(123 5  8)  # D7 ⢗ × [MULTIPLICATION SIGN]
+char \xD8	(  3 567 )  # D8 ⡴ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(     678)  # D9 ⣠ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2  5 7 )  # DA ⡒ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(12345678)  # DB ⣿ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	( 23  6 8)  # DC ⢦ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 23  678)  # DD ⣦ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  3 5678)  # DE ⣴ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(  3456 8)  # DF ⢼ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6 8)  # E1 ⢡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1234   8)  # E3 ⢏ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  345678)  # E5 ⣼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(   4  78)  # E6 ⣈ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 678)  # E7 ⣯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(12   6 8)  # E9 ⢣ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   678)  # EA ⣣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(1  4 6 8)  # ED ⢩ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 23 56 8)  # F0 ⢶ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1 345  8)  # F1 ⢝ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1  456 8)  # F3 ⢹ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1   567 )  # F5 ⡱ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(12  5678)  # F7 ⣳ ÷ [DIVISION SIGN]
+char \xF8	(      78)  # F8 ⣀ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1   56 8)  # FA ⢱ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   5678)  # FB ⣱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23 5678)  # FD ⣶ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1234567 )  # FE ⡿ þ [LATIN SMALL LETTER THORN]
+char \xFF	(123456 8)  # FF ⢿ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+alias	\u2019	\x27	# ’ [RIGHT SINGLE QUOTATION MARK]
+alias	\u2295	\x2B	# ⊕ [CIRCLED PLUS]
+alias	\u2296	\x2D	# ⊖ [CIRCLED MINUS]
+alias	\u2298	\x2F	# ⊘ [CIRCLED DIVISION SLASH]
+alias	\u229B	\x2A	# ⊛ [CIRCLED ASTERISK OPERATOR]
+alias	\u229C	\x3D	# ⊜ [CIRCLED EQUALS]
+
+include de-chess.tti
+include common.tti
diff --git a/Tables/Text/devanagari.tti b/Tables/Text/devanagari.tti
new file mode 100644
index 0000000..d4f9cd0
--- /dev/null
+++ b/Tables/Text/devanagari.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Devanagari script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0901	(  3     )  # ⠄ ँ [DEVANAGARI SIGN CANDRABINDU]
+char \u0902	(    56  )  # ⠰ ं [DEVANAGARI SIGN ANUSVARA]
+char \u0903	(     6  )  # ⠠ ः [DEVANAGARI SIGN VISARGA]
+char \u0905	(1       )  # ⠁ अ [DEVANAGARI LETTER A]
+char \u0906	(  345   )  # ⠜ आ [DEVANAGARI LETTER AA]
+char \u0907	( 2 4    )  # ⠊ इ [DEVANAGARI LETTER I]
+char \u0908	(  3 5   )  # ⠔ ई [DEVANAGARI LETTER II]
+char \u0909	(1 3  6  )  # ⠥ उ [DEVANAGARI LETTER U]
+char \u090A	(12  56  )  # ⠳ ऊ [DEVANAGARI LETTER UU]
+char \u090D	(1   5 7 )  # ⡑ ऍ [DEVANAGARI LETTER CANDRA E]
+char \u090E	(  34  7 )  # ⡌ ऎ [DEVANAGARI LETTER SHORT E]
+char \u090F	(1   5   )  # ⠑ ए [DEVANAGARI LETTER E]
+char \u0910	(  34    )  # ⠌ ऐ [DEVANAGARI LETTER AI]
+char \u0911	(1 3 5 7 )  # ⡕ ऑ [DEVANAGARI LETTER CANDRA O]
+char \u0912	( 2 4 67 )  # ⡪ ऒ [DEVANAGARI LETTER SHORT O]
+char \u0913	(1 3 5   )  # ⠕ ओ [DEVANAGARI LETTER O]
+char \u0914	( 2 4 6  )  # ⠪ औ [DEVANAGARI LETTER AU]
+char \u0915	(1 3     )  # ⠅ क [DEVANAGARI LETTER KA]
+char \u0916	(   4 6  )  # ⠨ ख [DEVANAGARI LETTER KHA]
+char \u0917	(1234    )  # ⠏ ग [DEVANAGARI LETTER GA]
+char \u0918	(12   6  )  # ⠣ घ [DEVANAGARI LETTER GHA]
+char \u0919	(  34 6  )  # ⠬ ङ [DEVANAGARI LETTER NGA]
+char \u091A	(1  4    )  # ⠉ च [DEVANAGARI LETTER CA]
+char \u091B	(1    6  )  # ⠡ छ [DEVANAGARI LETTER CHA]
+char \u091C	( 2 45   )  # ⠚ ज [DEVANAGARI LETTER JA]
+char \u091D	(  3 56  )  # ⠴ झ [DEVANAGARI LETTER JHA]
+char \u091E	( 2  5   )  # ⠒ ञ [DEVANAGARI LETTER NYA]
+char \u091F	( 23456  )  # ⠾ ट [DEVANAGARI LETTER TTA]
+char \u0920	( 2 456  )  # ⠺ ठ [DEVANAGARI LETTER TTHA]
+char \u0921	(12 4 6  )  # ⠫ ड [DEVANAGARI LETTER DDA]
+char \u0922	(123456  )  # ⠿ ढ [DEVANAGARI LETTER DDHA]
+char \u0923	(  3456  )  # ⠼ ण [DEVANAGARI LETTER NNA]
+char \u0924	( 2345   )  # ⠞ त [DEVANAGARI LETTER TA]
+char \u0925	(1  456  )  # ⠹ थ [DEVANAGARI LETTER THA]
+char \u0926	(1  45   )  # ⠙ द [DEVANAGARI LETTER DA]
+char \u0927	( 234 6  )  # ⠮ ध [DEVANAGARI LETTER DHA]
+char \u0928	(1 345   )  # ⠝ न [DEVANAGARI LETTER NA]
+char \u0929	(    56  )  # ⠰ ऩ [DEVANAGARI LETTER NNNA]
+char \u092A	(1234    )  # ⠏ प [DEVANAGARI LETTER PA]
+char \u092B	( 23 5   )  # ⠖ फ [DEVANAGARI LETTER PHA]
+char \u092C	(12      )  # ⠃ ब [DEVANAGARI LETTER BA]
+char \u092D	(   45   )  # ⠘ भ [DEVANAGARI LETTER BHA]
+char \u092E	(1 34    )  # ⠍ म [DEVANAGARI LETTER MA]
+char \u092F	(1 3456  )  # ⠽ य [DEVANAGARI LETTER YA]
+char \u0930	(123 5   )  # ⠗ र [DEVANAGARI LETTER RA]
+char \u0931	(123 5 7 )  # ⡗ ऱ [DEVANAGARI LETTER RRA]
+char \u0932	(123     )  # ⠇ ल [DEVANAGARI LETTER LA]
+char \u0933	(123   7 )  # ⡇ ळ [DEVANAGARI LETTER LLA]
+char \u0934	(123   78)  # ⣇ ऴ [DEVANAGARI LETTER LLLA]
+char \u0935	(123  6  )  # ⠧ व [DEVANAGARI LETTER VA]
+char \u0936	(1  4 6  )  # ⠩ श [DEVANAGARI LETTER SHA]
+char \u0937	(1234 6  )  # ⠯ ष [DEVANAGARI LETTER SSA]
+char \u0938	( 234    )  # ⠎ स [DEVANAGARI LETTER SA]
+char \u0939	(12  5   )  # ⠓ ह [DEVANAGARI LETTER HA]
+char \u093D	( 2      )  # ⠂ ऽ [DEVANAGARI SIGN AVAGRAHA]
+char \u093E	(  345   )  # ⠜ ा [DEVANAGARI VOWEL SIGN AA]
+char \u093F	( 2 4    )  # ⠊ ि [DEVANAGARI VOWEL SIGN I]
+char \u0940	(  3 5   )  # ⠔ ी [DEVANAGARI VOWEL SIGN II]
+char \u0941	(1 3  6  )  # ⠥ ु [DEVANAGARI VOWEL SIGN U]
+char \u0942	(12  56  )  # ⠳ ू [DEVANAGARI VOWEL SIGN UU]
+char \u0945	(1   5 7 )  # ⡑ ॅ [DEVANAGARI VOWEL SIGN CANDRA E]
+char \u0946	(  34  7 )  # ⡌ ॆ [DEVANAGARI VOWEL SIGN SHORT E]
+char \u0947	(1   5   )  # ⠑ े [DEVANAGARI VOWEL SIGN E]
+char \u0948	(  34    )  # ⠌ ै [DEVANAGARI VOWEL SIGN AI]
+char \u0949	(1 3 5 7 )  # ⡕ ॉ [DEVANAGARI VOWEL SIGN CANDRA O]
+char \u094A	( 2 4 67 )  # ⡪ ॊ [DEVANAGARI VOWEL SIGN SHORT O]
+char \u094B	(1 3 5   )  # ⠕ ो [DEVANAGARI VOWEL SIGN O]
+char \u094C	( 2 4 6  )  # ⠪ ौ [DEVANAGARI VOWEL SIGN AU]
+char \u094D	(   4    )  # ⠈ ् [DEVANAGARI SIGN VIRAMA]
+char \u0964	( 2  56  )  # ⠲ । [DEVANAGARI DANDA]
+char \u0966	( 2 45   )  # ⠚ ० [DEVANAGARI DIGIT ZERO]
+char \u0967	(1       )  # ⠁ १ [DEVANAGARI DIGIT ONE]
+char \u0968	(12      )  # ⠃ २ [DEVANAGARI DIGIT TWO]
+char \u0969	(1  4    )  # ⠉ ३ [DEVANAGARI DIGIT THREE]
+char \u096A	(1  45   )  # ⠙ ४ [DEVANAGARI DIGIT FOUR]
+char \u096B	(1   5   )  # ⠑ ५ [DEVANAGARI DIGIT FIVE]
+char \u096C	(12 4    )  # ⠋ ६ [DEVANAGARI DIGIT SIX]
+char \u096D	(12 45   )  # ⠛ ७ [DEVANAGARI DIGIT SEVEN]
+char \u096E	(12  5   )  # ⠓ ८ [DEVANAGARI DIGIT EIGHT]
+char \u096F	( 2 4    )  # ⠊ ९ [DEVANAGARI DIGIT NINE]
diff --git a/Tables/Text/dra.ttb b/Tables/Text/dra.ttb
new file mode 100644
index 0000000..07bb1d7
--- /dev/null
+++ b/Tables/Text/dra.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Dravidian
+
+include tamil.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/el.ttb b/Tables/Text/el.ttb
new file mode 100644
index 0000000..9a2285f
--- /dev/null
+++ b/Tables/Text/el.ttb
@@ -0,0 +1,95 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Greek
+
+# Created by Leon Ungier <Leon.Ungier@ViewPlus.com>.
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+include greek.tti
+include ltr-latin.tti
+
+char	\x2D	(  3  6  )	# ⠤ - [HYPHEN-MINUS]
+
+char	\x27	(  3     )	# ⠄ ' [APOSTROPHE]
+char	\x2C	( 2      )	# ⠂ , [COMMA]
+char	\x3A	( 2  5   )	# ⠒ : [COLON]
+char	\x2E	( 2  56  )	# ⠲ . [FULL STOP]
+char	\x21	( 23 5   )	# ⠖ ! [EXCLAMATION MARK]
+
+char	\x28	(123 56  )	# ⠷ ( [LEFT PARENTHESIS]
+char	\x29	( 23456  )	# ⠾ ) [RIGHT PARENTHESIS]
+
+char	\x5B	(123 5678)	⣷ À [ [LEFT SQUARE BRACKET]
+char	\x5D	( 2345678)	# ⣾ ] [RIGHT SQUARE BRACKET]
+
+char	\x7B	(1234 678)	# ⣯ { [LEFT CURLY BRACKET]
+char	\x7D	(1 345678)	# ⣽ } [RIGHT CURLY BRACKET]
+
+char	\x22	( 23 56  )	# ⠶ " [QUOTATION MARK]
+char	\xAB	( 23  6  )	# ⠦ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char	\xBB	(  3 56  )	# ⠴ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+
+char	\x2F	(  34    )	# ⠌ / [SOLIDUS]
+char	\x5C	(1    6  )	# ⠡ \ [REVERSE SOLIDUS]
+
+char	\x60	(   4    )	# ⠈ ` [GRAVE ACCENT]
+char	\x5E	(     6  )	# ⠠ ^ [CIRCUMFLEX ACCENT]
+char	\x7E	(   45   )	# ⠘ ~ [TILDE]
+char	\x5F	(   456  )	# ⠸ _ [LOW LINE]
+
+###################################################
+# The characters below have not yet been audited. #
+###################################################
+
+char	\x23	(  34567 )	# ⠼ # [NUMBER SIGN]
+char	\x26	(1234 6  )	# ⠯ & [AMPERSAND]
+char	\x2A	(  3 5   )	# ⠔ * [ASTERISK]
+char	\x2B	( 2   6  )	# ⠢ + [PLUS SIGN]
+char	\x3B	( 2   6  )	# ⠢ ; [SEMICOLON]
+char	\x3D	(  3  6  )	# ⠤ = [EQUALS SIGN]
+char	\x3F	( 23  6  )	# ⠦ ? [QUESTION MARK]
+char	\x40	(  345   )	# ⠜ @ [COMMERCIAL AT]
+char	\x7C	(12  56  )	# ⠳ | [VERTICAL LINE]
+
+char \x30	( 2 45   )  # ⠚ 0 [DIGIT ZERO]
+char \x31	(1       )  # ⠁ 1 [DIGIT ONE]
+char \x32	(12      )  # ⠃ 2 [DIGIT TWO]
+char \x33	(1  4    )  # ⠉ 3 [DIGIT THREE]
+char \x34	(1  45   )  # ⠙ 4 [DIGIT FOUR]
+char \x35	(1   5   )  # ⠑ 5 [DIGIT FIVE]
+char \x36	(12 4    )  # ⠋ 6 [DIGIT SIX]
+char \x37	(12 45   )  # ⠛ 7 [DIGIT SEVEN]
+char \x38	(12  5   )  # ⠓ 8 [DIGIT EIGHT]
+char \x39	( 2 4    )  # ⠊ 9 [DIGIT NINE]
+
+char \u2018	(  3     )  # ⠄ ‘ [LEFT SINGLE QUOTATION MARK]
+char \u2019	(  3     )  # ⠄ ’ [RIGHT SINGLE QUOTATION MARK]
+char \u20AC	(1   5   )  # ⠑ € [EURO SIGN]
+char \xA7	(  34 6  )  # ⠬ § [SECTION SIGN]
+char \xB0	(  3 56  )  # ⠴ ° [DEGREE SIGN]
+char \xB7	( 23     )  # ⠆ · [MIDDLE DOT]
+char \xA2	(1  4    )  # ⠉ ¢ [CENT SIGN]
+char \xA5	(1 3456  )  # ⠽ ¥ [YEN SIGN]
+char \xAA	(   4 6  )  # ⠨ ª [FEMININE ORDINAL INDICATOR]
+char \xB5	(123456  )  # ⠿ µ [MICRO SIGN]
+char \xB8	(   45   )  # ⠘ ¸ [CEDILLA]
+char \xBF	(  3 5   )  # ⠔ ¿ [INVERTED QUESTION MARK]
+
+include common.tti
diff --git a/Tables/Text/en-chess.tti b/Tables/Text/en-chess.tti
new file mode 100644
index 0000000..5951e58
--- /dev/null
+++ b/Tables/Text/en-chess.tti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This text subtable defines braille representations for the chess figures in
+# terms of the letters that are used for them in the English language.
+
+# See: http://en.wikipedia.org/wiki/Chess_symbols_in_Unicode
+
+alias \u2654 K # ♔ [WHITE CHESS KING]
+alias \u2655 Q # ♕ [WHITE CHESS QUEEN]
+alias \u2656 R # ♖ [WHITE CHESS ROOK]
+alias \u2657 B # ♗ [WHITE CHESS BISHOP]
+alias \u2658 N # ♘ [WHITE CHESS KNIGHT]
+alias \u2659 P # ♙ [WHITE CHESS PAWN]
+alias \u265A k # ♚ [BLACK CHESS KING]
+alias \u265B q # ♛ [BLACK CHESS QUEEN]
+alias \u265C r # ♜ [BLACK CHESS ROOK]
+alias \u265D b # ♝ [BLACK CHESS BISHOP]
+alias \u265E n # ♞ [BLACK CHESS KNIGHT]
+alias \u265F p # ♟ [BLACK CHESS PAWN]
diff --git a/Tables/Text/en-na-ascii.tti b/Tables/Text/en-na-ascii.tti
new file mode 100644
index 0000000..fd6eda9
--- /dev/null
+++ b/Tables/Text/en-na-ascii.tti
@@ -0,0 +1,63 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations that are used
+# by English-speaking North American users for the ASCII character set.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 0-9 are represented using the Nemeth (lower numbers) scheme
+include num-nemeth.tti
+
+char \x20	(        )  # ⠀   [SPACE]
+char \x21	( 234 6  )  # ⠮ ! [EXCLAMATION MARK]
+char \x22	(    5   )  # ⠐ " [QUOTATION MARK]
+char \x23	(  3456  )  # ⠼ # [NUMBER SIGN]
+char \x24	(12 4 6  )  # ⠫ $ [DOLLAR SIGN]
+char \x25	(1  4 6  )  # ⠩ % [PERCENT SIGN]
+char \x26	(1234 6  )  # ⠯ & [AMPERSAND]
+char \x27	(  3     )  # ⠄ ' [APOSTROPHE]
+char \x28	(123 56  )  # ⠷ ( [LEFT PARENTHESIS]
+char \x29	( 23456  )  # ⠾ ) [RIGHT PARENTHESIS]
+char \x2A	(1    6  )  # ⠡ * [ASTERISK]
+char \x2B	(  34 6  )  # ⠬ + [PLUS SIGN]
+char \x2C	(     6  )  # ⠠ , [COMMA]
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char \x2E	(   4 6  )  # ⠨ . [FULL STOP]
+char \x2F	(  34    )  # ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	(1   56  )  # ⠱ : [COLON]
+char \x3B	(    56  )  # ⠰ ; [SEMICOLON]
+char \x3C	(12   6  )  # ⠣ < [LESS-THAN SIGN]
+char \x3D	(123456  )  # ⠿ = [EQUALS SIGN]
+char \x3E	(  345   )  # ⠜ > [GREATER-THAN SIGN]
+char \x3F	(1  456  )  # ⠹ ? [QUESTION MARK]
+char \x40	(   4  7 )  # ⡈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 2 4 67 )  # ⡪ [ [LEFT SQUARE BRACKET]
+char \x5C	(12  567 )  # ⡳ \ [REVERSE SOLIDUS]
+char \x5D	(12 4567 )  # ⡻ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # ⠸ _ [LOW LINE]
+char \x60	(   4    )  # ⠈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 2 4 6  )  # ⠪ { [LEFT CURLY BRACKET]
+char \x7C	(12  56  )  # ⠳ | [VERTICAL LINE]
+char \x7D	(12 456  )  # ⠻ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # ⠘ ~ [TILDE]
diff --git a/Tables/Text/en-nabcc.ttb b/Tables/Text/en-nabcc.ttb
new file mode 100644
index 0000000..ac24dc4
--- /dev/null
+++ b/Tables/Text/en-nabcc.ttb
@@ -0,0 +1,421 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - English (North American Braille Computer Code)
+
+# This is a description of the default text table used by BRLTTY.  
+# It's based on the North American Braille Computer Code, but defines the full
+# Latin1 (ISO-8859-1) character set.
+
+# The 95 printable characters of the standard 7-bit US-ASCII character set
+# (32-126) are identical to their representations within the North American
+# Braille Computer Code (these are the only characters which the NABCC actually
+# defines). Characters from literary braille, symbols from The Nemeth Braille
+# Code for Mathematics and Science Notation, and a bit of human imagination
+# have all been combined to create an easy-to-remember, one-to-one mapping
+# between each character and its braille counterpart. All possible combinations
+# involving only the original 6 braille dots are used, but that only allows for
+# 64 out of the required 95 character representations. The presence or absence
+# of dot 7 is used to differentiate between pairs of characters which either
+# are very closely related in meaning or, in a few cases where a more intuitive
+# reason couldn't be found, have a very close logical relationship within the
+# US-ASCII code. Dot 8 isn't used at all.
+
+# The space and the 26 lowercase letters (a-z) are the same as in literary 
+# braille:
+
+     #Hex    Dots       Dec Char Description
+char \X20 (        )  #  32      space
+char \X61 (1       )  #  97   a  latin small letter a
+char \X62 (12      )  #  98   b  latin small letter b
+char \X63 (1  4    )  #  99   c  latin small letter c
+char \X64 (1  45   )  # 100   d  latin small letter d
+char \X65 (1   5   )  # 101   e  latin small letter e
+char \X66 (12 4    )  # 102   f  latin small letter f
+char \X67 (12 45   )  # 103   g  latin small letter g
+char \X68 (12  5   )  # 104   h  latin small letter h
+char \X69 ( 2 4    )  # 105   i  latin small letter i
+char \X6A ( 2 45   )  # 106   j  latin small letter j
+char \X6B (1 3     )  # 107   k  latin small letter k
+char \X6C (123     )  # 108   l  latin small letter l
+char \X6D (1 34    )  # 109   m  latin small letter m
+char \X6E (1 345   )  # 110   n  latin small letter n
+char \X6F (1 3 5   )  # 111   o  latin small letter o
+char \X70 (1234    )  # 112   p  latin small letter p
+char \X71 (12345   )  # 113   q  latin small letter q
+char \X72 (123 5   )  # 114   r  latin small letter r
+char \X73 ( 234    )  # 115   s  latin small letter s
+char \X74 ( 2345   )  # 116   t  latin small letter t
+char \X75 (1 3  6  )  # 117   u  latin small letter u
+char \X76 (123  6  )  # 118   v  latin small letter v
+char \X77 ( 2 456  )  # 119   w  latin small letter w
+char \X78 (1 34 6  )  # 120   x  latin small letter x
+char \X79 (1 3456  )  # 121   y  latin small letter y
+char \X7A (1 3 56  )  # 122   z  latin small letter z
+
+# The 26 uppercase letters (A-Z) are the same as their lowercase counterparts 
+# except that dot 7 is added:
+
+     #Hex    Dots       Dec Char Description
+char \X41 (1     7 )  #  65   A  latin capital letter a
+char \X42 (12    7 )  #  66   B  latin capital letter b
+char \X43 (1  4  7 )  #  67   C  latin capital letter c
+char \X44 (1  45 7 )  #  68   D  latin capital letter d
+char \X45 (1   5 7 )  #  69   E  latin capital letter e
+char \X46 (12 4  7 )  #  70   F  latin capital letter f
+char \X47 (12 45 7 )  #  71   G  latin capital letter g
+char \X48 (12  5 7 )  #  72   H  latin capital letter h
+char \X49 ( 2 4  7 )  #  73   I  latin capital letter i
+char \X4A ( 2 45 7 )  #  74   J  latin capital letter j
+char \X4B (1 3   7 )  #  75   K  latin capital letter k
+char \X4C (123   7 )  #  76   L  latin capital letter l
+char \X4D (1 34  7 )  #  77   M  latin capital letter m
+char \X4E (1 345 7 )  #  78   N  latin capital letter n
+char \X4F (1 3 5 7 )  #  79   O  latin capital letter o
+char \X50 (1234  7 )  #  80   P  latin capital letter p
+char \X51 (12345 7 )  #  81   Q  latin capital letter q
+char \X52 (123 5 7 )  #  82   R  latin capital letter r
+char \X53 ( 234  7 )  #  83   S  latin capital letter s
+char \X54 ( 2345 7 )  #  84   T  latin capital letter t
+char \X55 (1 3  67 )  #  85   U  latin capital letter u
+char \X56 (123  67 )  #  86   V  latin capital letter v
+char \X57 ( 2 4567 )  #  87   W  latin capital letter w
+char \X58 (1 34 67 )  #  88   X  latin capital letter x
+char \X59 (1 34567 )  #  89   Y  latin capital letter y
+char \X5A (1 3 567 )  #  90   Z  latin capital letter z
+
+# The 10 decimal digits (0-9) are the same as in the Nemeth Code:
+
+     #Hex    Dots       Dec Char Description
+char \X30 (  3 56  )  #  48   0  digit zero
+char \X31 ( 2      )  #  49   1  digit one
+char \X32 ( 23     )  #  50   2  digit two
+char \X33 ( 2  5   )  #  51   3  digit three
+char \X34 ( 2  56  )  #  52   4  digit four
+char \X35 ( 2   6  )  #  53   5  digit five
+char \X36 ( 23 5   )  #  54   6  digit six
+char \X37 ( 23 56  )  #  55   7  digit seven
+char \X38 ( 23  6  )  #  56   8  digit eight
+char \X39 (  3 5   )  #  57   9  digit nine
+
+# Common symbols used within mathematical expressions by popular computer
+# programming languages are the same as in the Nemeth Code:
+
+     #Hex    Dots       Dec Char Description
+char \X2E (   4 6  )  #  46   .  full stop
+char \X2B (  34 6  )  #  43   +  plus sign
+char \X2D (  3  6  )  #  45   -  hyphen-minus
+char \X2A (1    6  )  #  42   *  asterisk
+char \X2F (  34    )  #  47   /  solidus
+char \X28 (123 56  )  #  40   (  left parenthesis
+char \X29 ( 23456  )  #  41   )  right parenthesis
+
+# With all of these major considerations having been taken into account, 
+# convenient representations were still available, and are used, for some of 
+# the remaining characters:
+
+     #Hex    Dots       Dec Char Description
+char \X26 (1234 6  )  #  38   &  ampersand
+char \X23 (  3456  )  #  35   #  number sign
+
+# The remaining characters are what they are. Dot 7 isn't used either within 
+# the number block (32-63) or, with the exception of the DEL control character
+# (127), within the lowercase block (96-127). With the exception of the
+# underscore (95), dot 7 is used for every character within the uppercase block
+# (64-95). Adding dot 7 to any character within the lowercase block (96-127)
+# yields its corresponding character within the uppercase block (64-95) except
+# that removing dot 7 from the DEL control character yields the underscore.
+
+     #Hex    Dots       Dec Char Description
+char \X2C (     6  )  #  44   ,  comma
+char \X3B (    56  )  #  59   ;  semicolon
+char \X3A (1   56  )  #  58   :  colon
+char \X21 ( 234 6  )  #  33   !  exclamation mark
+char \X3F (1  456  )  #  63   ?  question mark
+char \X22 (    5   )  #  34   "  quotation mark
+char \X27 (  3     )  #  39   '  apostrophe
+char \X60 (   4    )  #  96   `  grave accent
+char \X5E (   45 7 )  #  94   ^  circumflex accent
+char \X7E (   45   )  # 126   ~  tilde
+char \X5B ( 2 4 67 )  #  91   [  left square bracket
+char \X5D (12 4567 )  #  93   ]  right square bracket
+char \X7B ( 2 4 6  )  # 123   {  left curly bracket
+char \X7D (12 456  )  # 125   }  right curly bracket
+char \X3D (123456  )  #  61   =  equals sign
+char \X3C (12   6  )  #  60   <  less-than sign
+char \X3E (  345   )  #  62   >  greater-than sign
+char \X24 (12 4 6  )  #  36   $  dollar sign
+char \X25 (1  4 6  )  #  37   %  percent sign
+char \X40 (   4  7 )  #  64   @  commercial at
+char \X7C (12  56  )  # 124   |  vertical line
+char \X5C (12  567 )  #  92   \  reverse solidus
+char \X5F (   456  )  #  95   _  low line
+
+# Each of the characters within the basic control character block (0-31) is the
+# same as its corresponding character within both the uppercase block (64-95)
+# and the lowercase block (96-127) except that dots 7 and 8 are both used.
+
+     #Hex    Dots       Dec Char Description
+char \X00 (   4  78)  #   0  ^@  null
+char \X01 (1     78)  #   1  ^A  start of heading
+char \X02 (12    78)  #   2  ^B  start of text
+char \X03 (1  4  78)  #   3  ^C  end of text
+char \X04 (1  45 78)  #   4  ^D  end of transmission
+char \X05 (1   5 78)  #   5  ^E  enquiry
+char \X06 (12 4  78)  #   6  ^F  acknowledge
+char \X07 (12 45 78)  #   7  ^G  bell
+char \X08 (12  5 78)  #   8  ^H  backspace
+char \X09 ( 2 4  78)  #   9  ^I  horizontal tabulation
+char \X0A ( 2 45 78)  #  10  ^J  line feed
+char \X0B (1 3   78)  #  11  ^K  vertical tabulation
+char \X0C (123   78)  #  12  ^L  form feed
+char \X0D (1 34  78)  #  13  ^M  carriage return
+char \X0E (1 345 78)  #  14  ^N  shift out
+char \X0F (1 3 5 78)  #  15  ^O  shift in
+char \X10 (1234  78)  #  16  ^P  data link escape
+char \X11 (12345 78)  #  17  ^Q  device control one
+char \X12 (123 5 78)  #  18  ^R  device control two
+char \X13 ( 234  78)  #  19  ^S  device control three
+char \X14 ( 2345 78)  #  20  ^T  device control four
+char \X15 (1 3  678)  #  21  ^U  negative acknowledge
+char \X16 (123  678)  #  22  ^V  synchronous idle
+char \X17 ( 2 45678)  #  23  ^W  end of transmission block
+char \X18 (1 34 678)  #  24  ^X  cancel
+char \X19 (1 345678)  #  25  ^Y  end of medium
+char \X1A (1 3 5678)  #  26  ^Z  substitute
+char \X1B ( 2 4 678)  #  27  ^[  escape
+char \X1C (12  5678)  #  28  ^\  file separator
+char \X1D (12 45678)  #  29  ^]  group separator
+char \X1E (   45 78)  #  30  ^^  record separator
+char \X1F (   45678)  #  31  ^_  unit separator
+
+# Each of the characters within the extended control character block (128-159)  
+# is the same as its corresponding character within the basic control character
+# block (0-31) except that only dot 8 is used.
+
+     #Hex    Dots       Dec Char Description
+char \X80 (   4   8)  # 128  ~@  <control>
+char \X81 (1      8)  # 129  ~A  <control>
+char \X82 (12     8)  # 130  ~B  break permitted here
+char \X83 (1  4   8)  # 131  ~C  no break here
+char \X84 (1  45  8)  # 132  ~D  <control>
+char \X85 (1   5  8)  # 133  ~E  next line
+char \X86 (12 4   8)  # 134  ~F  start of selected area
+char \X87 (12 45  8)  # 135  ~G  end of selected area
+char \X88 (12  5  8)  # 136  ~H  character tabulation set
+char \X89 ( 2 4   8)  # 137  ~I  character tabulation with justification
+char \X8A ( 2 45  8)  # 138  ~J  line tabulation set
+char \X8B (1 3    8)  # 139  ~K  partial line down
+char \X8C (123    8)  # 140  ~L  partial line up
+char \X8D (1 34   8)  # 141  ~M  reverse line feed
+char \X8E (1 345  8)  # 142  ~N  single shift two
+char \X8F (1 3 5  8)  # 143  ~O  single shift three
+char \X90 (1234   8)  # 144  ~P  device control string
+char \X91 (12345  8)  # 145  ~Q  private use one
+char \X92 (123 5  8)  # 146  ~R  private use two
+char \X93 ( 234   8)  # 147  ~S  set transmit state
+char \X94 ( 2345  8)  # 148  ~T  cancel character
+char \X95 (1 3  6 8)  # 149  ~U  message waiting
+char \X96 (123  6 8)  # 150  ~V  start of guarded area
+char \X97 ( 2 456 8)  # 151  ~W  end of guarded area
+char \X98 (1 34 6 8)  # 152  ~X  start of string
+char \X99 (1 3456 8)  # 153  ~Y  <control>
+char \X9A (1 3 56 8)  # 154  ~Z  single character introducer
+char \X9B ( 2 4 6 8)  # 155  ~[  control sequence introducer
+char \X9C (12  56 8)  # 156  ~\  string terminator
+char \X9D (12 456 8)  # 157  ~]  operating system command
+char \X9E (   45  8)  # 158  ~^  privacy message
+char \X9F (   456 8)  # 159  ~_  application program command
+
+# Representations for the uppercase accented letters are drawn from the
+# remaining combinations which use both dots 7 and 8. The representation for a
+# lowercase accented letter is the same as its uppercase counterpart except
+# that dot 7 isn't used. This scheme retains the use of dot 7 as the modifier
+# for a capitalized letter. The only exception to these rules is that, due to
+# the nature of the Latin1 character set, the German lowercase double-s is
+# treated as though it were an uppercase y-dieresis (neither has an uppercase
+# definition). These representations have been gathered, as much as possible,
+# into logical groupings.
+
+# The 5 letters with a circumflex accent (^) use the [1-5] dot combinations:
+
+     #Hex    Dots       Dec Char Description
+char \XC2 ( 2    78)  # 194   Â  latin capital letter a with circumflex
+char \XCA ( 23   78)  # 202   Ê  latin capital letter e with circumflex
+char \XCE ( 2  5 78)  # 206   Î  latin capital letter i with circumflex
+char \XD4 ( 2  5678)  # 212   Ô  latin capital letter o with circumflex
+char \XDB ( 2   678)  # 219   Û  latin capital letter u with circumflex
+char \XE2 ( 2     8)  # 226   â  latin small letter a with circumflex
+char \XEA ( 23    8)  # 234   ê  latin small letter e with circumflex
+char \XEE ( 2  5  8)  # 238   î  latin small letter i with circumflex
+char \XF4 ( 2  56 8)  # 244   ô  latin small letter o with circumflex
+char \XFB ( 2   6 8)  # 251   û  latin small letter u with circumflex
+
+# The 5 letters with a grave accent (`) use the [6-0] dot combinations:
+
+     #Hex    Dots       Dec Char Description
+char \XC0 ( 23 5 78)  # 192   À  latin capital letter a with grave
+char \XC8 ( 23 5678)  # 200   È  latin capital letter e with grave
+char \XCC ( 23  678)  # 204   Ì  latin capital letter i with grave
+char \XD2 (  3 5 78)  # 210   Ò  latin capital letter o with grave
+char \XD9 (  3 5678)  # 217   Ù  latin capital letter u with grave
+char \XE0 ( 23 5  8)  # 224   à  latin small letter a with grave
+char \XE8 ( 23 56 8)  # 232   è  latin small letter e with grave
+char \XEC ( 23  6 8)  # 236   ì  latin small letter i with grave
+char \XF2 (  3 5  8)  # 242   ò  latin small letter o with grave
+char \XF9 (  3 56 8)  # 249   ù  latin small letter u with grave
+
+# The 6 letters with an acute accent (') use the [a-f] dot combinations with
+# dots 3 and 6 added:
+
+     #Hex    Dots       Dec Char Description
+char \XC1 (1    678)  # 193   Á  latin capital letter a with acute
+char \XC9 (12   678)  # 201   É  latin capital letter e with acute
+char \XCD (1  4 678)  # 205   Í  latin capital letter i with acute
+char \XD3 (1  45678)  # 211   Ó  latin capital letter o with acute
+char \XDA (1   5678)  # 218   Ú  latin capital letter u with acute
+char \XDD (12 4 678)  # 221   Ý  latin capital letter y with acute
+char \XE1 (1    6 8)  # 225   á  latin small letter a with acute
+char \XE9 (12   6 8)  # 233   é  latin small letter e with acute
+char \XED (1  4 6 8)  # 237   í  latin small letter i with acute
+char \XF3 (1  456 8)  # 243   ó  latin small letter o with acute
+char \XFA (1   56 8)  # 250   ú  latin small letter u with acute
+char \XFD (12 4 6 8)  # 253   ý  latin small letter y with acute
+
+# The 6 letters with a dieresis accent (") use the [f-j] dot combinations with
+# dots 3 and 6 added, and the number sign (because it fits the sequence
+# reasonably well):
+
+     #Hex    Dots       Dec Char Description
+char \XC4 (1234 678)  # 196   Ä  latin capital letter a with diaeresis
+char \XCB (12345678)  # 203   Ë  latin capital letter e with diaeresis
+char \XCF (123 5678)  # 207   Ï  latin capital letter i with diaeresis
+char \XD6 ( 234 678)  # 214   Ö  latin capital letter o with diaeresis
+char \XDC ( 2345678)  # 220   Ü  latin capital letter u with diaeresis
+char \XE4 (1234 6 8)  # 228   ä  latin small letter a with diaeresis
+char \XEB (123456 8)  # 235   ë  latin small letter e with diaeresis
+char \XEF (123 56 8)  # 239   ï  latin small letter i with diaeresis
+char \XF6 ( 234 6 8)  # 246   ö  latin small letter o with diaeresis
+char \XFC ( 23456 8)  # 252   ü  latin small letter u with diaeresis
+char \XFF (  3456 8)  # 255   ÿ  latin small letter y with diaeresis
+
+# There is no uppercase y-dieresis in the Latin1 character set. The German
+# lowercase double-s, which also doesn't have an uppercase counterpart in the
+# Latin1 character set, uses its representation:
+
+     #Hex    Dots       Dec Char Description
+char \XDF (  345678)  # 223   ß  latin small letter sharp s
+
+# The remaining accented letters are:
+
+     #Hex    Dots       Dec Char Description
+char \XC3 (    5 78)  # 195   Ã  latin capital letter a with tilde
+char \XD1 (   4 678)  # 209   Ñ  latin capital letter n with tilde
+char \XD5 (    5678)  # 213   Õ  latin capital letter o with tilde
+char \XC5 (  345 78)  # 197   Å  latin capital letter a with ring above
+char \XC7 (  34 678)  # 199   Ç  latin capital letter c with cedilla
+char \XD8 (  34  78)  # 216   Ø  latin capital letter o with stroke
+char \XC6 (  3   78)  # 198   Æ  latin capital letter ae
+char \XD0 (     678)  # 208   Ð  latin capital letter eth
+char \XDE (  3  678)  # 222   Þ  latin capital letter thorn
+char \XE3 (    5  8)  # 227   ã  latin small letter a with tilde
+char \XF1 (   4 6 8)  # 241   ñ  latin small letter n with tilde
+char \XF5 (    56 8)  # 245   õ  latin small letter o with tilde
+char \XE5 (  345  8)  # 229   å  latin small letter a with ring above
+char \XE7 (  34 6 8)  # 231   ç  latin small letter c with cedilla
+char \XF8 (  34   8)  # 248   ø  latin small letter o with stroke
+char \XE6 (  3    8)  # 230   æ  latin small letter ae
+char \XF0 (     6 8)  # 240   ð  latin small letter eth
+char \XFE (  3  6 8)  # 254   þ  latin small letter thorn
+
+# Some characters are the same as other characters which they resemble but with
+# dot 7 added:
+
+     #Hex    Dots       Dec Char Description
+char \XAD (  3  67 )  # 173   ­  soft hyphen
+char \XAB (12   67 )  # 171   «  left-pointing double angle quotation mark
+char \XBB (  345 7 )  # 187   »  right-pointing double angle quotation mark
+char \XA6 (1   567 )  # 166   ¦  broken bar
+char \XB9 ( 2    7 )  # 185   ¹  superscript one
+char \XB2 ( 23   7 )  # 178   ²  superscript two
+char \XB3 ( 2  5 7 )  # 179   ³  superscript three
+char \XB1 (  34 67 )  # 177   ±  plus-minus sign
+char \XD7 (1    67 )  # 215   ×  multiplication sign
+char \XF7 (  34  7 )  # 247   ÷  division sign
+char \XB7 (   4 67 )  # 183   ·  middle dot
+char \XA1 ( 234 67 )  # 161   ¡  inverted exclamation mark
+char \XBF (1  4567 )  # 191   ¿  inverted question mark
+char \XA2 (12 4 67 )  # 162   ¢  cent sign
+char \XA3 (  34567 )  # 163   £  pound sign
+
+# A few more characters follow this same convention but their relationships
+# to their base characters is a bit obscure:
+
+     #Hex    Dots       Dec Char Description
+char \XA4 (1  4 67 )  # 164   ¤  currency sign
+char \XA5 (1234 67 )  # 165   ¥  yen sign
+
+# Some characters are represented by the first letters of their names lowered
+# by one row of dots:
+
+     #Hex    Dots       Dec Char Description
+char \XAC ( 2  567 )  # 172   ¬  not sign
+char \XB6 ( 23 5 7 )  # 182   ¶  pilcrow sign
+char \XA9 ( 23 567 )  # 169   ©  copyright sign
+char \XAE ( 23  67 )  # 174   ®  registered sign
+char \XA7 (  3 5 7 )  # 167   §  section sign
+char \XB0 (  3 567 )  # 176   °  degree sign
+
+# The three fraction characters use combinations of dots 1 and 4 (which
+# progress from left to right as the value of the fraction increases) together
+# with dots 2,3,5,6,7:
+
+     #Hex    Dots       Dec Char Description
+char \XBC (123 567 )  # 188   ¼  vulgar fraction one quarter
+char \XBD (1234567 )  # 189   ½  vulgar fraction one half
+char \XBE ( 234567 )  # 190   ¾  vulgar fraction three quarters
+
+# Each of the three extended accent characters is the same as its conventional
+# compose character but with dot 7 added:
+
+     #Hex    Dots       Dec Char Description
+char \XB4 (  3   7 )  # 180   ´  acute accent
+char \XB8 (     67 )  # 184   ¸  cedilla
+char \XA8 (    5 7 )  # 168   ¨  diaeresis
+
+# The two gender symbols are:
+
+     #Hex    Dots       Dec Char Description
+char \XBA (      7 )  # 186   º  masculine ordinal indicator
+char \XAA (       8)  # 170   ª  feminine ordinal indicator
+
+# The three remaining characters are:
+
+     #Hex    Dots       Dec Char Description
+char \XAF ( 2   67 )  # 175   ¯  macron
+char \XB5 (    567 )  # 181   µ  micro sign
+
+# The nonbreaking space is dots 7 and 8 because this presents a sequence of
+# nonbreaking spaces as a smooth low line segment.
+
+     #Hex    Dots       Dec Char Description
+char \X7F (   4567 )  # 127  ^?  delete
+
+include ltr-alias.tti
+include num-alias.tti
+include common.tti
diff --git a/Tables/Text/en.ttb b/Tables/Text/en.ttb
new file mode 100644
index 0000000..b537e47
--- /dev/null
+++ b/Tables/Text/en.ttb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - English
+
+include en-nabcc.ttb
diff --git a/Tables/Text/en_CA.ttb b/Tables/Text/en_CA.ttb
new file mode 100644
index 0000000..57b14fa
--- /dev/null
+++ b/Tables/Text/en_CA.ttb
@@ -0,0 +1,136 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - English (Canada)
+# Dave Mielke <dave@mielke.cc>
+
+# This text table implements North American style English braille, the
+# additional letters of the French, German, and Italian alphabets, and
+# easy-to-remember representations for many common extra symbols.
+
+# ASCII character representations as used by English-speaking North Americans
+include en-na-ascii.tti
+
+# the control letters
+char \x01	(1     78)  # ⣁   [START OF HEADING]
+char \x02	(12    78)  # ⣃   [START OF TEXT]
+char \x03	(1  4  78)  # ⣉   [END OF TEXT]
+char \x04	(1  45 78)  # ⣙   [END OF TRANSMISSION]
+char \x05	(1   5 78)  # ⣑   [ENQUIRY]
+char \x06	(12 4  78)  # ⣋   [ACKNOWLEDGE]
+char \x07	(12 45 78)  # ⣛   [BELL]
+char \x08	(12  5 78)  # ⣓   [BACKSPACE]
+char \x09	( 2 4  78)  # ⣊   [CHARACTER TABULATION]
+char \x0A	( 2 45 78)  # ⣚   [LINE FEED (LF)]
+char \x0B	(1 3   78)  # ⣅   [LINE TABULATION]
+char \x0C	(123   78)  # ⣇   [FORM FEED (FF)]
+char \x0D	(1 34  78)  # ⣍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345 78)  # ⣝   [SHIFT OUT]
+char \x0F	(1 3 5 78)  # ⣕   [SHIFT IN]
+char \x10	(1234  78)  # ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # ⣟   [DEVICE CONTROL ONE]
+char \x12	(123 5 78)  # ⣗   [DEVICE CONTROL TWO]
+char \x13	( 234  78)  # ⣎   [DEVICE CONTROL THREE]
+char \x14	( 2345 78)  # ⣞   [DEVICE CONTROL FOUR]
+char \x15	(1 3  678)  # ⣥   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  678)  # ⣧   [SYNCHRONOUS IDLE]
+char \x17	( 2 45678)  # ⣺   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 678)  # ⣭   [CANCEL]
+char \x19	(1 345678)  # ⣽   [END OF MEDIUM]
+char \x1A	(1 3 5678)  # ⣵   [SUBSTITUTE]
+
+# equivalents to \x2D [HYPHEN-MINUS]
+
+# equivalents to \x7C [VERTICAL LINE]
+char \xA6	(12  56  )  # ⠳ ¦ [BROKEN BAR]
+
+# symbols represented using Letters with dot 8 added
+char \xA9	(1  4   8)  # ⢉ © [COPYRIGHT SIGN]
+char \xB0	(1  45  8)  # ⢙ ° [DEGREE SIGN]
+char \u20AC	(1   5  8)  # ⢑ € [EURO SIGN]
+char \xB5	(1 34   8)  # ⢍ µ [MICRO SIGN]
+char \xAC	(1 345  8)  # ⢝ ¬ [NOT SIGN]
+char \xB6	(1234   8)  # ⢏ ¶ [PILCROW SIGN]
+char \xAE	(123 5  8)  # ⢗ ® [REGISTERED SIGN]
+char \xA7	( 234   8)  # ⢎ § [SECTION SIGN]
+char \u2122	( 2345  8)  # ⢞ ™ [TRADE MARK SIGN]
+char \xA5	(1 3456 8)  # ⢽ ¥ [YEN SIGN]
+
+# symbols represented using punctuation with dot 7 added
+char \xA1	( 234 67 )  # ⡮ ¡ [INVERTED EXCLAMATION MARK]
+char \xBF	(1  4567 )  # ⡹ ¿ [INVERTED QUESTION MARK]
+char \xB7	(   4 67 )  # ⡨ · [MIDDLE DOT]
+char \xAB	(123 567 )  # ⡷ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBB	( 234567 )  # ⡾ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xB1	(  34 67 )  # ⡬ ± [PLUS-MINUS SIGN]
+char \xD7	(1    67 )  # ⡡ × [MULTIPLICATION SIGN]
+char \xF7	(  34  7 )  # ⡌ ÷ [DIVISION SIGN]
+char \u2260	(1234567 )  # ⡿ ≠ [NOT EQUAL TO]
+char \u2264	(12   67 )  # ⡣ ≤ [LESS-THAN OR EQUAL TO]
+char \u2265	(  345 7 )  # ⡜ ≥ [GREATER-THAN OR EQUAL TO]
+char \xA2	(12 4 67 )  # ⡫ ¢ [CENT SIGN]
+char \xA3	(  34567 )  # ⡼ £ [POUND SIGN]
+
+# French lowercase accented letters
+char \xE7	(1234 6 8)  # ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE9	(123456 8)  # ⢿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xE0	(123 56 8)  # ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE8	( 234 6 8)  # ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xF9	( 23456 8)  # ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xE2	(1    6 8)  # ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xEA	(12   6 8)  # ⢣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEE	(1  4 6 8)  # ⢩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xF4	(1  456 8)  # ⢹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xFB	(1   56 8)  # ⢱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEF	(12 456 8)  # ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+
+# French uppercase accented letters
+char \xC7	(1234 678)  # ⣯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC9	(12345678)  # ⣿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xC0	(123 5678)  # ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC8	( 234 678)  # ⣮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xD9	( 2345678)  # ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xC2	(1    678)  # ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xCA	(12   678)  # ⣣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCE	(1  4 678)  # ⣩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xD4	(1  45678)  # ⣹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xDB	(1   5678)  # ⣱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xCB	(12 4 678)  # ⣫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCF	(12 45678)  # ⣻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+
+# German lowercase accented letters
+char \xE4	(  345  8)  # ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xF6	( 2 4 6 8)  # ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xFC	(12  56 8)  # ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xDF	(  3456 8)  # ⢼ ß [LATIN SMALL LETTER SHARP S]
+
+# German uppercase accented letters
+char \xC4	(  345 78)  # ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xD6	( 2 4 678)  # ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xDC	(12  5678)  # ⣳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+
+# Italian lowercase accented letters
+char \xEC	(  34   8)  # ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xF2	(  34 6 8)  # ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+
+# Italian uppercase accented letters
+char \xCC	(  34  78)  # ⣌ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xD2	(  34 678)  # ⣬ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+
+include common.tti
diff --git a/Tables/Text/en_GB.ttb b/Tables/Text/en_GB.ttb
new file mode 100644
index 0000000..cac7387
--- /dev/null
+++ b/Tables/Text/en_GB.ttb
@@ -0,0 +1,177 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - English (United Kingdom)
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	( 234 678)  # 00 ⣮   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(123 5678)  # 1B ⣷   [ESCAPE]
+char \x1C	(  34 678)  # 1C ⣬   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2345678)  # 1D ⣾   [INFORMATION SEPARATOR THREE]
+char \x1E	(     678)  # 1E ⣠   [INFORMATION SEPARATOR TWO]
+char \x1F	(    5 78)  # 1F ⣐   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	(  3456  )  # 21 ⠼ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # 22 ⠈ " [QUOTATION MARK]
+char \x23	(    56  )  # 23 ⠰ # [NUMBER SIGN]
+char \x24	(   456  )  # 24 ⠸ $ [DOLLAR SIGN]
+char \x25	(   4 6  )  # 25 ⠨ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(   45   )  # 28 ⠘ ( [LEFT PARENTHESIS]
+char \x29	(  345   )  # 29 ⠜ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+char \x30	(123456  )  # 30 ⠿ 0 [DIGIT ZERO]
+char \x31	(1    6  )  # 31 ⠡ 1 [DIGIT ONE]
+char \x32	(12   6  )  # 32 ⠣ 2 [DIGIT TWO]
+char \x33	(1  4 6  )  # 33 ⠩ 3 [DIGIT THREE]
+char \x34	(1  456  )  # 34 ⠹ 4 [DIGIT FOUR]
+char \x35	(1   56  )  # 35 ⠱ 5 [DIGIT FIVE]
+char \x36	(12 4 6  )  # 36 ⠫ 6 [DIGIT SIX]
+char \x37	(12 456  )  # 37 ⠻ 7 [DIGIT SEVEN]
+char \x38	(12  56  )  # 38 ⠳ 8 [DIGIT EIGHT]
+char \x39	( 2 4 6  )  # 39 ⠪ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	( 234 67 )  # 40 ⡮ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 567 )  # 5B ⡷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34 67 )  # 5C ⡬ \ [REVERSE SOLIDUS]
+char \x5D	( 234567 )  # 5D ⡾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(     67 )  # 5E ⡠ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(    5   )  # 5F ⠐ _ [LOW LINE]
+char \x60	( 234 6  )  # 60 ⠮ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 56  )  # 7B ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(  34 6  )  # 7C ⠬ | [VERTICAL LINE]
+char \x7D	( 23456  )  # 7D ⠾ } [RIGHT CURLY BRACKET]
+char \x7E	(     6  )  # 7E ⠠ ~ [TILDE]
+char \x7F	(    5 7 )  # 7F ⡐   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(    5  8)  # A2 ⢐ ¢ [CENT SIGN]
+char \xA3	(   4 67 )  # A3 ⡨ £ [POUND SIGN]
+char \xA4	(  3  678)  # A4 ⣤ ¤ [CURRENCY SIGN]
+char \xA5	(   4 6 8)  # A5 ⢨ ¥ [YEN SIGN]
+char \xA6	( 2   678)  # A6 ⣢ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	( 2    78)  # A8 ⣂ ¨ [DIAERESIS]
+char \xA9	(1  4567 )  # A9 ⡹ © [COPYRIGHT SIGN]
+char \xAA	(12  5  8)  # AA ⢓ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5678)  # AB ⣰ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  5678)  # AC ⣲ ¬ [NOT SIGN]
+char \xAD	(12 4   8)  # AD ⢋ ­ [SOFT HYPHEN]
+char \xAE	(   4 678)  # AE ⣨ ® [REGISTERED SIGN]
+char \xAF	(12 45  8)  # AF ⢛ ¯ [MACRON]
+char \xB0	(   456 8)  # B0 ⢸ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	(12     8)  # B2 ⢃ ² [SUPERSCRIPT TWO]
+char \xB3	( 23   7 )  # B3 ⡆ ³ [SUPERSCRIPT THREE]
+char \xB4	(1 3 56 8)  # B4 ⢵ ´ [ACUTE ACCENT]
+char \xB5	(1 34   8)  # B5 ⢍ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(  3   7 )  # B7 ⡄ · [MIDDLE DOT]
+char \xB8	(1  4   8)  # B8 ⢉ ¸ [CEDILLA]
+char \xB9	(   45 7 )  # B9 ⡘ ¹ [SUPERSCRIPT ONE]
+char \xBA	( 2 45  8)  # BA ⢚ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(   45 78)  # BB ⣘ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 3  6 8)  # BC ⢥ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(123  6 8)  # BD ⢧ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  34  78)  # BE ⣌ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  3    8)  # BF ⢄ ¿ [INVERTED QUESTION MARK]
+char \xC0	(  3   78)  # C0 ⣄ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1 3    8)  # C2 ⢅ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	( 23  67 )  # C3 ⡦ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  34567 )  # C5 ⡼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(   4  7 )  # C6 ⡈ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 23   78)  # C8 ⣆ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 23    8)  # C9 ⢆ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(       8)  # CA ⢀ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12345  8)  # CB ⢟ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(123    8)  # CC ⢇ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 2  5 78)  # CD ⣒ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(12 45678)  # CE ⣻ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(1    67 )  # CF ⡡ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  345 7 )  # D0 ⡜ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	( 2  567 )  # D1 ⡲ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2     8)  # D2 ⢂ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	( 2 4 678)  # D3 ⣪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2 4   8)  # D4 ⢊ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1234 6 8)  # D5 ⢯ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(  3 5  8)  # D6 ⢔ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(123 5  8)  # D7 ⢗ × [MULTIPLICATION SIGN]
+char \xD8	(  3 567 )  # D8 ⡴ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(   45678)  # D9 ⣸ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2  5 7 )  # DA ⡒ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(12345678)  # DB ⣿ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	( 23  6 8)  # DC ⢦ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 23  678)  # DD ⣦ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  3 5678)  # DE ⣴ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(  3456 8)  # DF ⢼ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6 8)  # E1 ⢡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1234   8)  # E3 ⢏ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  345678)  # E5 ⣼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(   4  78)  # E6 ⣈ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 678)  # E7 ⣯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(12   6 8)  # E9 ⢣ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   678)  # EA ⣣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(1  4 6 8)  # ED ⢩ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 23 56 8)  # F0 ⢶ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1 345  8)  # F1 ⢝ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1  456 8)  # F3 ⢹ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1   567 )  # F5 ⡱ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(12  5678)  # F7 ⣳ ÷ [DIVISION SIGN]
+char \xF8	(      78)  # F8 ⣀ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1   56 8)  # FA ⢱ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   5678)  # FB ⣱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23 5678)  # FD ⣶ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1234567 )  # FE ⡿ þ [LATIN SMALL LETTER THORN]
+char \xFF	(123456 8)  # FF ⢿ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/en_US.ttb b/Tables/Text/en_US.ttb
new file mode 100644
index 0000000..1e9382f
--- /dev/null
+++ b/Tables/Text/en_US.ttb
@@ -0,0 +1,171 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - English (United States)
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented using the Nemeth (lower numbers) scheme
+include num-nemeth.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   4  78)  # 00 ⣈   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2 4 678)  # 1B ⣪   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12 45678)  # 1D ⣻   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 234 6  )  # 21 ⠮ ! [EXCLAMATION MARK]
+char \x22	(    5   )  # 22 ⠐ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(12 4 6  )  # 24 ⠫ $ [DOLLAR SIGN]
+char \x25	(1  4 6  )  # 25 ⠩ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(123 56  )  # 28 ⠷ ( [LEFT PARENTHESIS]
+char \x29	( 23456  )  # 29 ⠾ ) [RIGHT PARENTHESIS]
+char \x2A	(1    6  )  # 2A ⠡ * [ASTERISK]
+char \x2B	(  34 6  )  # 2B ⠬ + [PLUS SIGN]
+char \x2C	(     6  )  # 2C ⠠ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(   4 6  )  # 2E ⠨ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	(1   56  )  # 3A ⠱ : [COLON]
+char \x3B	(    56  )  # 3B ⠰ ; [SEMICOLON]
+char \x3C	(12   6  )  # 3C ⠣ < [LESS-THAN SIGN]
+char \x3D	(123456  )  # 3D ⠿ = [EQUALS SIGN]
+char \x3E	(  345   )  # 3E ⠜ > [GREATER-THAN SIGN]
+char \x3F	(1  456  )  # 3F ⠹ ? [QUESTION MARK]
+char \x40	(   4  7 )  # 40 ⡈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 2 4 67 )  # 5B ⡪ [ [LEFT SQUARE BRACKET]
+char \x5C	(12  567 )  # 5C ⡳ \ [REVERSE SOLIDUS]
+char \x5D	(12 4567 )  # 5D ⡻ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 2 4 6  )  # 7B ⠪ { [LEFT CURLY BRACKET]
+char \x7C	(12  56  )  # 7C ⠳ | [VERTICAL LINE]
+char \x7D	(12 456  )  # 7D ⠻ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \x7F	(   4567 )  # 7F ⡸   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(    5  8)  # A2 ⢐ ¢ [CENT SIGN]
+char \xA3	(   4 67 )  # A3 ⡨ £ [POUND SIGN]
+char \xA4	(  3  678)  # A4 ⣤ ¤ [CURRENCY SIGN]
+char \xA5	(   4 6 8)  # A5 ⢨ ¥ [YEN SIGN]
+char \xA6	( 2   678)  # A6 ⣢ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	( 2    78)  # A8 ⣂ ¨ [DIAERESIS]
+char \xA9	(1  4567 )  # A9 ⡹ © [COPYRIGHT SIGN]
+char \xAA	(12  5  8)  # AA ⢓ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(123 5678)  # AB ⣷ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  5678)  # AC ⣲ ¬ [NOT SIGN]
+char \xAD	( 234 67 )  # AD ⡮ ­ [SOFT HYPHEN]
+char \xAE	(   4 678)  # AE ⣨ ® [REGISTERED SIGN]
+char \xAF	(12 45  8)  # AF ⢛ ¯ [MACRON]
+char \xB0	(   456 8)  # B0 ⢸ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	(12     8)  # B2 ⢃ ² [SUPERSCRIPT TWO]
+char \xB3	( 23   7 )  # B3 ⡆ ³ [SUPERSCRIPT THREE]
+char \xB4	(1 3 56 8)  # B4 ⢵ ´ [ACUTE ACCENT]
+char \xB5	(1 34   8)  # B5 ⢍ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(  3   7 )  # B7 ⡄ · [MIDDLE DOT]
+char \xB8	(1  4   8)  # B8 ⢉ ¸ [CEDILLA]
+char \xB9	(123 567 )  # B9 ⡷ ¹ [SUPERSCRIPT ONE]
+char \xBA	( 2 45  8)  # BA ⢚ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	( 2345678)  # BB ⣾ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 3  6 8)  # BC ⢥ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(123  6 8)  # BD ⢧ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  34 678)  # BE ⣬ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  3    8)  # BF ⢄ ¿ [INVERTED QUESTION MARK]
+char \xC0	(  3   78)  # C0 ⣄ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1 3    8)  # C2 ⢅ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	( 23  67 )  # C3 ⡦ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  34567 )  # C5 ⡼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 23   78)  # C8 ⣆ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 23    8)  # C9 ⢆ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(       8)  # CA ⢀ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12345  8)  # CB ⢟ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(12 4   8)  # CC ⢋ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 2  5 78)  # CD ⣒ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(    5678)  # CE ⣰ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(1    67 )  # CF ⡡ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(    5 7 )  # D0 ⡐ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	( 2  567 )  # D1 ⡲ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2     8)  # D2 ⢂ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(123    8)  # D3 ⢇ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2 4   8)  # D4 ⢊ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1234 6 8)  # D5 ⢯ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(  3 5  8)  # D6 ⢔ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(123 5  8)  # D7 ⢗ × [MULTIPLICATION SIGN]
+char \xD8	(  3 567 )  # D8 ⡴ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(     678)  # D9 ⣠ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2  5 7 )  # DA ⡒ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(12345678)  # DB ⣿ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	( 23  6 8)  # DC ⢦ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 23  678)  # DD ⣦ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  3 5678)  # DE ⣴ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(  3456 8)  # DF ⢼ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6 8)  # E1 ⢡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1234   8)  # E3 ⢏ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  345678)  # E5 ⣼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345 78)  # E6 ⣜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 678)  # E7 ⣯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(12   6 8)  # E9 ⢣ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   678)  # EA ⣣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(1  4 6 8)  # ED ⢩ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 23 56 8)  # F0 ⢶ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1 345  8)  # F1 ⢝ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1  456 8)  # F3 ⢹ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1   567 )  # F5 ⡱ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(  34  78)  # F7 ⣌ ÷ [DIVISION SIGN]
+char \xF8	(      78)  # F8 ⣀ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1   56 8)  # FA ⢱ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   5678)  # FB ⣱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23 5678)  # FD ⣶ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1234567 )  # FE ⡿ þ [LATIN SMALL LETTER THORN]
+char \xFF	(123456 8)  # FF ⢿ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/eo.ttb b/Tables/Text/eo.ttb
new file mode 100644
index 0000000..98cb1d7
--- /dev/null
+++ b/Tables/Text/eo.ttb
@@ -0,0 +1,55 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Esperanto
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+#
+# Based on the document from Musée national de l'Espéranto à Gray
+# "Maison pour tous", 19 rue Victor Hugo 70100 GRAY
+# esperanto-muzeo@wanadoo.fr
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# w is non-standard because dots 2456 is j with circumflex
+char \x77	( 23456  )  # ⠾ w [LATIN SMALL LETTER W]
+char \x57	( 234567 )  # ⡾ W [LATIN CAPITAL LETTER W]
+
+# lowercase accented letters
+char \u0109	(1  4 6  )  # ⠩ ĉ [LATIN SMALL LETTER C WITH CIRCUMFLEX]
+char \u011D	(12 456  )  # ⠻ ĝ [LATIN SMALL LETTER G WITH CIRCUMFLEX]
+char \u0125	(12  56  )  # ⠳ ĥ [LATIN SMALL LETTER H WITH CIRCUMFLEX]
+char \u0135	( 2 456  )  # ⠺ ĵ [LATIN SMALL LETTER J WITH CIRCUMFLEX]
+char \u015D	( 234 6  )  # ⠮ ŝ [LATIN SMALL LETTER S WITH CIRCUMFLEX]
+char \u016D	(  34 6  )  # ⠬ ŭ [LATIN SMALL LETTER U WITH BREVE]
+
+# uppercase accented letters
+char \u0108	(1  4 67 )  # ⡩ Ĉ [LATIN CAPITAL LETTER C WITH CIRCUMFLEX]
+char \u011C	(12 4567 )  # ⡻ Ĝ [LATIN CAPITAL LETTER G WITH CIRCUMFLEX]
+char \u0124	(12  567 )  # ⡳ Ĥ [LATIN CAPITAL LETTER H WITH CIRCUMFLEX]
+char \u0134	( 2 4567 )  # ⡺ Ĵ [LATIN CAPITAL LETTER J WITH CIRCUMFLEX]
+char \u015C	( 234 67 )  # ⡮ Ŝ [LATIN CAPITAL LETTER S WITH CIRCUMFLEX]
+char \u016C	(  34 67 )  # ⡬ Ŭ [LATIN CAPITAL LETTER U WITH BREVE]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+
+include common.tti
diff --git a/Tables/Text/es.ttb b/Tables/Text/es.ttb
new file mode 100644
index 0000000..a0a555a
--- /dev/null
+++ b/Tables/Text/es.ttb
@@ -0,0 +1,199 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Spanish
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(    5 78)  # 00 ⣐   [NULL]
+char \x01	( 2    78)  # 01 ⣂   [START OF HEADING]
+char \x02	(12    78)  # 02 ⣃   [START OF TEXT]
+char \x03	(1  4  78)  # 03 ⣉   [END OF TEXT]
+char \x04	(1  45 78)  # 04 ⣙   [END OF TRANSMISSION]
+char \x05	( 2   678)  # 05 ⣢   [ENQUIRY]
+char \x06	(12 4  78)  # 06 ⣋   [ACKNOWLEDGE]
+char \x07	(12 45 78)  # 07 ⣛   [BELL]
+char \x08	(12  5 78)  # 08 ⣓   [BACKSPACE]
+char \x09	(  3 5 78)  # 09 ⣔   [CHARACTER TABULATION]
+char \x0A	( 2 45 78)  # 0A ⣚   [LINE FEED (LF)]
+char \x0B	(1 3   78)  # 0B ⣅   [LINE TABULATION]
+char \x0C	(123   78)  # 0C ⣇   [FORM FEED (FF)]
+char \x0D	(1 34  78)  # 0D ⣍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345 78)  # 0E ⣝   [SHIFT OUT]
+char \x0F	( 23  678)  # 0F ⣦   [SHIFT IN]
+char \x10	(1234  78)  # 10 ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # 11 ⣟   [DEVICE CONTROL ONE]
+char \x12	(123 5 78)  # 12 ⣗   [DEVICE CONTROL TWO]
+char \x13	( 234  78)  # 13 ⣎   [DEVICE CONTROL THREE]
+char \x14	( 2345 78)  # 14 ⣞   [DEVICE CONTROL FOUR]
+char \x15	(  3 5678)  # 15 ⣴   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  678)  # 16 ⣧   [SYNCHRONOUS IDLE]
+char \x17	( 2 45678)  # 17 ⣺   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 678)  # 18 ⣭   [CANCEL]
+char \x19	(1 345678)  # 19 ⣽   [END OF MEDIUM]
+char \x1A	(1 3 5678)  # 1A ⣵   [SUBSTITUTE]
+char \x1B	(  345678)  # 1B ⣼   [ESCAPE]
+char \x1C	(12345678)  # 1C ⣿   [INFORMATION SEPARATOR FOUR]
+char \x1D	(   4 678)  # 1D ⣨   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(     678)  # 1F ⣠   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5 7 )  # 21 ⡖ ! [EXCLAMATION MARK]
+char \x22	(    56  )  # 22 ⠰ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(1234 67 )  # 24 ⡯ $ [DOLLAR SIGN]
+char \x25	(   456  )  # 25 ⠸ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(   4    )  # 27 ⠈ ' [APOSTROPHE]
+char \x28	(12   6 8)  # 28 ⢣ ( [LEFT PARENTHESIS]
+char \x29	(  345 7 )  # 29 ⡜ ) [RIGHT PARENTHESIS]
+char \x2A	( 2  56  )  # 2A ⠲ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34  78)  # 2F ⣌ / [SOLIDUS]
+char \x30	(  34 678)  # 30 ⣬ 0 [DIGIT ZERO]
+char \x31	(1    6  )  # 31 ⠡ 1 [DIGIT ONE]
+char \x32	(12   6  )  # 32 ⠣ 2 [DIGIT TWO]
+char \x33	(1  4 6  )  # 33 ⠩ 3 [DIGIT THREE]
+char \x34	(1  456  )  # 34 ⠹ 4 [DIGIT FOUR]
+char \x35	(1   56  )  # 35 ⠱ 5 [DIGIT FIVE]
+char \x36	(12 4 6  )  # 36 ⠫ 6 [DIGIT SIX]
+char \x37	(12 456  )  # 37 ⠻ 7 [DIGIT SEVEN]
+char \x38	(12  56  )  # 38 ⠳ 8 [DIGIT EIGHT]
+char \x39	( 2 4 6  )  # 39 ⠪ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(    5   )  # 40 ⠐ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  67 )  # 5B ⡦ [ [LEFT SQUARE BRACKET]
+char \x5C	(123456  )  # 5C ⠿ \ [REVERSE SOLIDUS]
+char \x5D	(  3 56 8)  # 5D ⢴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45   )  # 5E ⠘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(     6  )  # 5F ⠠ _ [LOW LINE]
+char \x60	(    5  8)  # 60 ⢐ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(   4 6  )  # 7B ⠨ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(  3 5   )  # 7D ⠔ } [RIGHT CURLY BRACKET]
+char \x7E	(    5 7 )  # 7E ⡐ ~ [TILDE]
+char \x7F	(      7 )  # 7F ⡀   [DELETE]
+char \xA1	( 23 5 78)  # A1 ⣖ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(12   678)  # A2 ⣣ ¢ [CENT SIGN]
+char \xA3	( 23   78)  # A3 ⣆ £ [POUND SIGN]
+char \xA4	( 2  567 )  # A4 ⡲ ¤ [CURRENCY SIGN]
+char \xA5	(1  45678)  # A5 ⣹ ¥ [YEN SIGN]
+char \xA6	(   4  78)  # A6 ⣈ ¦ [BROKEN BAR]
+char \xA7	(1    67 )  # A7 ⡡ § [SECTION SIGN]
+char \xA8	( 2   6 8)  # A8 ⢢ ¨ [DIAERESIS]
+char \xA9	( 23 567 )  # A9 ⡶ © [COPYRIGHT SIGN]
+char \xAA	( 23 56 8)  # AA ⢶ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23    8)  # AB ⢆ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  5 7 )  # AC ⡒ ¬ [NOT SIGN]
+char \xAD	( 23 5  8)  # AD ⢖ ­ [SOFT HYPHEN]
+char \xAE	( 23  6 8)  # AE ⢦ ® [REGISTERED SIGN]
+char \xAF	( 2 456 8)  # AF ⢺ ¯ [MACRON]
+char \xB0	(       8)  # B0 ⢀ ° [DEGREE SIGN]
+char \xB1	(12 4 67 )  # B1 ⡫ ± [PLUS-MINUS SIGN]
+char \xB2	(   4  7 )  # B2 ⡈ ² [SUPERSCRIPT TWO]
+char \xB3	(    56 8)  # B3 ⢰ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4 67 )  # B4 ⡨ ´ [ACUTE ACCENT]
+char \xB5	(1 34 6 8)  # B5 ⢭ µ [MICRO SIGN]
+char \xB6	(1  4567 )  # B6 ⡹ ¶ [PILCROW SIGN]
+char \xB7	(1  4 678)  # B7 ⣩ · [MIDDLE DOT]
+char \xB8	(1234567 )  # B8 ⡿ ¸ [CEDILLA]
+char \xB9	( 23   7 )  # B9 ⡆ ¹ [SUPERSCRIPT ONE]
+char \xBA	(    5678)  # BA ⣰ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    567 )  # BB ⡰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1  4 67 )  # BC ⡩ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   4 6 8)  # BD ⢨ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(   4   8)  # BE ⢈ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   67 )  # BF ⡢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 5678)  # C0 ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 567 )  # C1 ⡷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1     78)  # C2 ⣁ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  3 567 )  # C3 ⡴ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 78)  # C4 ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(123  6 8)  # C5 ⢧ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  3    8)  # C6 ⢄ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 678)  # C7 ⣯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 678)  # C8 ⣮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 234 67 )  # C9 ⡮ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1   5 78)  # CA ⣑ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 678)  # CB ⣫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(  345   )  # CC ⠜ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	( 2 4  78)  # CE ⣊ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 45678)  # CF ⣻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(1 345  8)  # D0 ⢝ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 4567 )  # D1 ⡻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2 45  8)  # D2 ⢚ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1 3 5 78)  # D4 ⣕ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(12 45  8)  # D5 ⢛ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1    678)  # D7 ⣡ × [MULTIPLICATION SIGN]
+char \xD8	(  34567 )  # D8 ⡼ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 2345678)  # D9 ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 234567 )  # DA ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1 3  678)  # DB ⣥ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  5678)  # DC ⣳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1   567 )  # DD ⡱ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12345  8)  # DE ⢟ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(12     8)  # DF ⢃ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1      8)  # E2 ⢁ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1    6 8)  # E3 ⢡ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1   5678)  # E5 ⣱ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(1 34   8)  # E6 ⢍ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	( 234 6  )  # E9 ⠮ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(1   5  8)  # EA ⢑ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	( 2 4   8)  # EE ⢊ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	( 2  5  8)  # EF ⢒ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 23 5678)  # F0 ⣶ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1 3 5  8)  # F4 ⢕ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(   4567 )  # F5 ⡸ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  5 78)  # F7 ⣒ ÷ [DIVISION SIGN]
+char \xF8	(   45 7 )  # F8 ⡘ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1 3  6 8)  # FB ⢥ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 234   8)  # FD ⢎ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(1   56 8)  # FE ⢱ þ [LATIN SMALL LETTER THORN]
+char \xFF	(     67 )  # FF ⡠ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/et.ttb b/Tables/Text/et.ttb
new file mode 100644
index 0000000..34b4244
--- /dev/null
+++ b/Tables/Text/et.ttb
@@ -0,0 +1,48 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Estonian
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# lowercase accented letters
+char \xE4	(  345   )  # ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xF5	( 234 6  )  # ⠮ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xFC	(12  56  )  # ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+
+# uppercase accented letters
+char \xC4	(  345 7 )  # ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xD5	( 234 67 )  # ⡮ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xDC	(12  567 )  # ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+
+include common.tti
diff --git a/Tables/Text/fi.ttb b/Tables/Text/fi.ttb
new file mode 100644
index 0000000..3a7b6fd
--- /dev/null
+++ b/Tables/Text/fi.ttb
@@ -0,0 +1,172 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Finnish
+
+# This table is very close to the official Finnish six dotbraille character set.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   4  78)  # 00 ⣈   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(12   678)  # 1B ⣣   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(  3 5 78)  # 1D ⣔   [INFORMATION SEPARATOR THREE]
+char \x1E	( 2    78)  # 1E ⣂   [INFORMATION SEPARATOR TWO]
+char \x1F	(    5 78)  # 1F ⣐   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 2  56  )  # 21 ⠲ ! [EXCLAMATION MARK]
+char \x22	(    56  )  # 22 ⠰ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	( 234 6  )  # 24 ⠮ $ [DOLLAR SIGN]
+char \x25	(1  456  )  # 25 ⠹ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(    5   )  # 27 ⠐ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(12   6  )  # 3C ⠣ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(1   56  )  # 3E ⠱ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4    )  # 40 ⠈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5C	(1  4 6  )  # 5C ⠩ \ [REVERSE SOLIDUS]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  34 6  )  # 5F ⠬ _ [LOW LINE]
+char \x60	(   4  7 )  # 60 ⡈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(12 4 6  )  # 7B ⠫ { [LEFT CURLY BRACKET]
+char \x7C	(   456  )  # 7C ⠸ | [VERTICAL LINE]
+char \x7D	(12 456  )  # 7D ⠻ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \xA1	( 23 5  8)  # A1 ⢖ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(1 3 56 8)  # A2 ⢵ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	(1   5 78)  # A4 ⣑ ¤ [CURRENCY SIGN]
+char \xA5	(1 3  6 8)  # A5 ⢥ ¥ [YEN SIGN]
+char \xA6	(   4 6 8)  # A6 ⢨ ¦ [BROKEN BAR]
+char \xA7	(   4 678)  # A7 ⣨ § [SECTION SIGN]
+char \xA8	( 2  5  8)  # A8 ⢒ ¨ [DIAERESIS]
+char \xA9	(1 3    8)  # A9 ⢅ © [COPYRIGHT SIGN]
+char \xAA	(   4  78)  # AA ⣈ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23   78)  # AB ⣆ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  3 5 7 )  # AC ⡔ ¬ [NOT SIGN]
+char \xAD	(       8)  # AD ⢀ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	( 23 56 8)  # AF ⢶ ¯ [MACRON]
+char \xB0	(    56 8)  # B0 ⢰ ° [DEGREE SIGN]
+char \xB1	(  3    8)  # B1 ⢄ ± [PLUS-MINUS SIGN]
+char \xB2	( 23   7 )  # B2 ⡆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5 7 )  # B3 ⡒ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4   8)  # B4 ⢈ ´ [ACUTE ACCENT]
+char \xB5	( 23  6 8)  # B5 ⢦ µ [MICRO SIGN]
+char \xB6	(   4 67 )  # B6 ⡨ ¶ [PILCROW SIGN]
+char \xB7	(  3   78)  # B7 ⣄ · [MIDDLE DOT]
+char \xB8	(  3 5678)  # B8 ⣴ ¸ [CEDILLA]
+char \xB9	( 2    7 )  # B9 ⡂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(1 3 5  8)  # BA ⢕ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    5678)  # BB ⣰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(  3456 8)  # BC ⢼ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  34567 )  # BD ⡼ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  345678)  # BE ⣼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   6 8)  # BF ⢢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 5678)  # C0 ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 567 )  # C1 ⡷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # C2 ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 678)  # C3 ⣩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 7 )  # C4 ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 78)  # C6 ⣜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 678)  # C8 ⣮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 234 67 )  # C9 ⡮ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(  34  78)  # CC ⣌ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(1   5678)  # D0 ⣱ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(  34 678)  # D2 ⣬ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  45678)  # D5 ⣹ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(  3   7 )  # D7 ⡄ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 678)  # D8 ⣪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 2345678)  # D9 ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 234567 )  # DA ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1234 678)  # DD ⣯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12 4 6 8)  # DE ⢫ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234   8)  # DF ⢎ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # E3 ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345  8)  # E6 ⢜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	( 234 6  )  # E9 ⠮ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1   56 8)  # F0 ⢱ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1  456 8)  # F5 ⢹ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # F6 ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  567 )  # F7 ⡲ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6 8)  # F8 ⢪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1234 6 8)  # FD ⢯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(12 4 678)  # FE ⣫ þ [LATIN SMALL LETTER THORN]
+char \xFF	(1 3456 8)  # FF ⢽ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/fr-2007.ttb b/Tables/Text/fr-2007.ttb
new file mode 100644
index 0000000..a781794
--- /dev/null
+++ b/Tables/Text/fr-2007.ttb
@@ -0,0 +1,226 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French (unified 2007)
+
+# This is the 2007 unification of french computer braille tables, based on the
+# cp1252 character set
+#
+# http://www.avh.asso.fr/rubriques/infos_braille/table_braille_informatique.php
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 3456
+include num-french.tti
+
+# generated by ttbtest: charset=cp1252
+char \x00	(12345  8)  # 00 ⢟   [NULL]
+char \x01	(1234 678)  # 01 ⣯   [START OF HEADING]
+char \x02	(12  5  8)  # 02 ⢓   [START OF TEXT]
+char \x03	(1234 6 8)  # 03 ⢯   [END OF TEXT]
+char \x04	(1  45 78)  # 04 ⣙   [END OF TRANSMISSION]
+char \x05	(1   5  8)  # 05 ⢑   [ENQUIRY]
+char \x06	(12 4  78)  # 06 ⣋   [ACKNOWLEDGE]
+char \x07	(12 45  8)  # 07 ⢛   [BELL]
+char \x08	(12  5 78)  # 08 ⣓   [BACKSPACE]
+char \x09	(12 4 678)  # 09 ⣫   [CHARACTER TABULATION]
+char \x0A	( 2 456 8)  # 0A ⢺   [LINE FEED (LF)]
+char \x0B	(1 3    8)  # 0B ⢅   [LINE TABULATION]
+char \x0C	(123   78)  # 0C ⣇   [FORM FEED (FF)]
+char \x0D	(1 34  78)  # 0D ⣍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345  8)  # 0E ⢝   [SHIFT OUT]
+char \x0F	( 23  6 8)  # 0F ⢦   [SHIFT IN]
+char \x10	(1234  78)  # 10 ⣏   [DATA LINK ESCAPE]
+char \x11	(12345 78)  # 11 ⣟   [DEVICE CONTROL ONE]
+char \x12	(123 5 78)  # 12 ⣗   [DEVICE CONTROL TWO]
+char \x13	( 234  78)  # 13 ⣎   [DEVICE CONTROL THREE]
+char \x14	( 2345 78)  # 14 ⣞   [DEVICE CONTROL FOUR]
+char \x15	(1 3  678)  # 15 ⣥   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  678)  # 16 ⣧   [SYNCHRONOUS IDLE]
+char \x17	( 2 45678)  # 17 ⣺   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 678)  # 18 ⣭   [CANCEL]
+char \x19	(123  6 8)  # 19 ⢧   [END OF MEDIUM]
+char \x1A	(1   5678)  # 1A ⣱   [SUBSTITUTE]
+char \x1B	(12  5678)  # 1B ⣳   [ESCAPE]
+char \x1C	(1  4 678)  # 1C ⣩   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12   678)  # 1D ⣣   [INFORMATION SEPARATOR THREE]
+char \x1E	( 23 5 7 )  # 1E ⡖   [INFORMATION SEPARATOR TWO]
+char \x1F	( 23  67 )  # 1F ⡦   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  3456 8)  # 23 ⢼ # [NUMBER SIGN]
+char \x24	(  3 5 7 )  # 24 ⡔ $ [DOLLAR SIGN]
+char \x25	(  34 6 8)  # 25 ⢬ % [PERCENT SIGN]
+char \x26	(123456 8)  # 26 ⢿ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 78)  # 2B ⣖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23    8)  # 3C ⢆ < [LESS-THAN SIGN]
+char \x3D	( 23 5678)  # 3D ⣶ = [EQUALS SIGN]
+char \x3E	(    567 )  # 3E ⡰ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345   )  # 40 ⠜ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  678)  # 5B ⣦ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34   8)  # 5C ⢌ \ [REVERSE SOLIDUS]
+char \x5D	(  3 5678)  # 5D ⣴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   4    )  # 5E ⠈ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(    5 78)  # 5F ⣐ _ [LOW LINE]
+char \x60	(     6  )  # 60 ⠠ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 23   78)  # 7B ⣆ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(    5678)  # 7D ⣰ } [RIGHT CURLY BRACKET]
+char \x7E	(  3    8)  # 7E ⢄ ~ [TILDE]
+char \x7F	(123    8)  # 7F ⢇   [DELETE]
+char \u20AC	(1   5 78)  # 80 ⣑ € [EURO SIGN]
+char \u201A	(     67 )  # 82 ⡠ ‚ [SINGLE LOW-9 QUOTATION MARK]
+char \u0192	(12 4   8)  # 83 ⢋ ƒ [LATIN SMALL LETTER F WITH HOOK]
+char \u201E	(    56  )  # 84 ⠰ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u2026	(  3  6 8)  # 85 ⢤ … [HORIZONTAL ELLIPSIS]
+char \u2020	(  3 56 8)  # 86 ⢴ † [DAGGER]
+char \u2021	(  3 567 )  # 87 ⡴ ‡ [DOUBLE DAGGER]
+char \u02C6	(   4   8)  # 88 ⢈ ˆ [MODIFIER LETTER CIRCUMFLEX ACCENT]
+char \u2030	(  34 678)  # 89 ⣬ ‰ [PER MILLE SIGN]
+char \u0160	( 234 678)  # 8A ⣮ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u2039	(    5 7 )  # 8B ⡐ ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+char \u0152	( 2 4 67 )  # 8C ⡪ Œ [LATIN CAPITAL LIGATURE OE]
+char \u017D	(1 3 5678)  # 8E ⣵ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u2018	(     6 8)  # 91 ⢠ ‘ [LEFT SINGLE QUOTATION MARK]
+char \u2019	(  3   7 )  # 92 ⡄ ’ [RIGHT SINGLE QUOTATION MARK]
+char \u201C	(  3   78)  # 93 ⣄ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	(     678)  # 94 ⣠ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u2022	(12 45 78)  # 95 ⣛ • [BULLET]
+char \u2013	(   4  78)  # 96 ⣈ – [EN DASH]
+char \u2014	(   45 78)  # 97 ⣘ — [EM DASH]
+char \u02DC	(   4 67 )  # 98 ⡨ ˜ [SMALL TILDE]
+char \u2122	( 2345  8)  # 99 ⢞ ™ [TRADE MARK SIGN]
+char \u0161	( 234 6 8)  # 9A ⢮ š [LATIN SMALL LETTER S WITH CARON]
+char \u203A	(    5  8)  # 9B ⢐ › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+char \u0153	( 2 4 6 8)  # 9C ⢪ œ [LATIN SMALL LIGATURE OE]
+char \u017E	(1 3 56 8)  # 9E ⢵ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u0178	(1  45678)  # 9F ⣹ Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+char \xA1	( 23 5  8)  # A1 ⢖ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(1  4  78)  # A2 ⣉ ¢ [CENT SIGN]
+char \xA3	( 23   7 )  # A3 ⡆ £ [POUND SIGN]
+char \xA4	(   45   )  # A4 ⠘ ¤ [CURRENCY SIGN]
+char \xA5	( 2  5678)  # A5 ⣲ ¥ [YEN SIGN]
+char \xA6	(   45  8)  # A6 ⢘ ¦ [BROKEN BAR]
+char \xA7	(1234   8)  # A7 ⢏ § [SECTION SIGN]
+char \xA8	(   4 6  )  # A8 ⠨ ¨ [DIAERESIS]
+char \xA9	(1  4   8)  # A9 ⢉ © [COPYRIGHT SIGN]
+char \xAA	(1    678)  # AA ⣡ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23 56 8)  # AB ⢶ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  567 )  # AC ⡲ ¬ [NOT SIGN]
+char \xAD	(      78)  # AD ⣀ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	(1 34   8)  # AF ⢍ ¯ [MACRON]
+char \xB0	( 2   67 )  # B0 ⡢ ° [DEGREE SIGN]
+char \xB1	(  3  678)  # B1 ⣤ ± [PLUS-MINUS SIGN]
+char \xB2	(   45 7 )  # B2 ⡘ ² [SUPERSCRIPT TWO]
+char \xB3	(   4567 )  # B3 ⡸ ³ [SUPERSCRIPT THREE]
+char \xB4	(    5   )  # B4 ⠐ ´ [ACUTE ACCENT]
+char \xB5	( 2  5 7 )  # B5 ⡒ µ [MICRO SIGN]
+char \xB6	(   45678)  # B6 ⣸ ¶ [PILCROW SIGN]
+char \xB7	(       8)  # B7 ⢀ · [MIDDLE DOT]
+char \xB8	(   456  )  # B8 ⠸ ¸ [CEDILLA]
+char \xB9	(   4  7 )  # B9 ⡈ ¹ [SUPERSCRIPT ONE]
+char \xBA	( 2   678)  # BA ⣢ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	( 23 567 )  # BB ⡶ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 3  6 8)  # BC ⢥ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   4 6 8)  # BD ⢨ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(1 34 6 8)  # BE ⢭ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   6 8)  # BF ⢢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 567 )  # C0 ⡷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 5678)  # C1 ⣷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    67 )  # C2 ⡡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1     78)  # C3 ⣁ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  34567 )  # C4 ⡼ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	( 2    7 )  # C5 ⡂ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	( 2 4  78)  # CC ⣊ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(12    78)  # D0 ⣃ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(1 345 78)  # D1 ⣝ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(1 3 5 78)  # D2 ⣕ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1 3   78)  # D5 ⣅ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(  3 5 78)  # D7 ⣔ × [MULTIPLICATION SIGN]
+char \xD8	(  345678)  # D8 ⣼ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 234567 )  # D9 ⡾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2345678)  # DA ⣾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1 345678)  # DD ⣽ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	( 2 45 78)  # DE ⣚ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234   8)  # DF ⢎ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56 8)  # E1 ⢷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # E2 ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1      8)  # E3 ⢁ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345 78)  # E4 ⣜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	( 2     8)  # E5 ⢂ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345  8)  # E6 ⢜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6 8)  # EA ⢣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 2 4   8)  # EC ⢊ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34  78)  # ED ⣌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6 8)  # EE ⢩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(12     8)  # F0 ⢃ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 45678)  # F1 ⣻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3 5  8)  # F2 ⢕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456 8)  # F4 ⢹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(   4 678)  # F5 ⣨ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(  3 5  8)  # F6 ⢔ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  5 78)  # F7 ⣒ ÷ [DIVISION SIGN]
+char \xF8	(    56 8)  # F8 ⢰ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456 8)  # FA ⢾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56 8)  # FB ⢱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 3456 8)  # FD ⢽ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	( 2 45  8)  # FE ⢚ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 2  56 8)  # FF ⢲ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/fr-cbifs.ttb b/Tables/Text/fr-cbifs.ttb
new file mode 100644
index 0000000..43629d1
--- /dev/null
+++ b/Tables/Text/fr-cbifs.ttb
@@ -0,0 +1,204 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French (Code Braille Informatique Français Standard)
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 3456
+include num-french.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(  345 78)  # 00 ⣜   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2 4 678)  # 1B ⣪   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12 45678)  # 1D ⣻   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  34 6  )  # 23 ⠬ # [NUMBER SIGN]
+char \x24	(   456  )  # 24 ⠸ $ [DOLLAR SIGN]
+char \x25	(1234 6  )  # 25 ⠯ % [PERCENT SIGN]
+char \x26	(123456  )  # 26 ⠿ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 78)  # 2B ⣖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # 3C ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 5678)  # 3D ⣶ = [EQUALS SIGN]
+char \x3E	(   45   )  # 3E ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345   )  # 40 ⠜ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5C	( 234 6  )  # 5C ⠮ \ [REVERSE SOLIDUS]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(  34    )  # 5E ⠌ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # 5F ⡸ _ [LOW LINE]
+char \x60	(  345 7 )  # 60 ⡜ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 567 )  # 7B ⡷ { [LEFT CURLY BRACKET]
+char \x7C	( 234 67 )  # 7C ⡮ | [VERTICAL LINE]
+char \x7D	( 234567 )  # 7D ⡾ } [RIGHT CURLY BRACKET]
+char \x7E	(  34  7 )  # 7E ⡌ ~ [TILDE]
+char \x7F	(   456 8)  # 7F ⢸   [DELETE]
+char \x80	(1234 67 )  # 80 ⡯   [<control-0080>]
+char \x81	(12  56 8)  # 81 ⢳   [<control-0081>]
+char \x82	(123456 8)  # 82 ⢿   [BREAK PERMITTED HERE]
+char \x83	(1    678)  # 83 ⣡   [NO BREAK HERE]
+char \x84	(   4   8)  # 84 ⢈   [<control-0084>]
+char \x85	(123 56 8)  # 85 ⢷   [NEXT LINE (NEL)]
+char \x86	(1  4 6 8)  # 86 ⢩   [START OF SELECTED AREA]
+char \x87	(1234 6 8)  # 87 ⢯   [END OF SELECTED AREA]
+char \x88	(12   678)  # 88 ⣣   [CHARACTER TABULATION SET]
+char \x89	(12 4 6 8)  # 89 ⢫   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	( 234 6 8)  # 8A ⢮   [LINE TABULATION SET]
+char \x8B	(12 456 8)  # 8B ⢻   [PARTIAL LINE FORWARD]
+char \x8C	(1  4 678)  # 8C ⣩   [PARTIAL LINE BACKWARD]
+char \x8D	(1  4 6 8)  # 8D ⢩   [REVERSE LINE FEED]
+char \x8E	(12 4 678)  # 8E ⣫   [SINGLE SHIFT TWO]
+char \x8F	(1  4 678)  # 8F ⣩   [SINGLE SHIFT THREE]
+char \x90	( 2345678)  # 90 ⣾   [DEVICE CONTROL STRING]
+char \x91	( 2   67 )  # 91 ⡢   [PRIVATE USE ONE]
+char \x92	(1234 678)  # 92 ⣯   [PRIVATE USE TWO]
+char \x93	(1  45678)  # 93 ⣹   [SET TRANSMIT STATE]
+char \x94	( 23 56 8)  # 94 ⢶   [CANCEL CHARACTER]
+char \x95	(1  4 6 8)  # 95 ⢩   [MESSAGE WAITING]
+char \x96	(1   5678)  # 96 ⣱   [START OF GUARDED AREA]
+char \x97	( 23456 8)  # 97 ⢾   [END OF GUARDED AREA]
+char \x98	(1  45678)  # 98 ⣹   [START OF STRING]
+char \x99	( 23 5 78)  # 99 ⣖   [<control-0099>]
+char \x9A	(12   678)  # 9A ⣣   [SINGLE CHARACTER INTRODUCER]
+char \x9B	( 2  5  8)  # 9B ⢒   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	( 23 567 )  # 9C ⡶   [STRING TERMINATOR]
+char \x9D	( 2    78)  # 9D ⣂   [OPERATING SYSTEM COMMAND]
+char \x9E	(   45  8)  # 9E ⢘   [PRIVACY MESSAGE]
+char \x9F	(   456 8)  # 9F ⢸   [APPLICATION PROGRAM COMMAND]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2 4 6 8)  # A2 ⢪ ¢ [CENT SIGN]
+char \xA3	(12  56 8)  # A3 ⢳ £ [POUND SIGN]
+char \xA4	( 2    7 )  # A4 ⡂ ¤ [CURRENCY SIGN]
+char \xA5	(12 456 8)  # A5 ⢻ ¥ [YEN SIGN]
+char \xA6	(12   67 )  # A6 ⡣ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	(1  4567 )  # A8 ⡹ ¨ [DIAERESIS]
+char \xA9	( 234567 )  # A9 ⡾ © [COPYRIGHT SIGN]
+char \xAA	(1234 67 )  # AA ⡯ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(   4 67 )  # AB ⡨ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(1    67 )  # AC ⡡ ¬ [NOT SIGN]
+char \xAD	(  3  6 8)  # AD ⢤ ­ [SOFT HYPHEN]
+char \xAE	(1234567 )  # AE ⡿ ® [REGISTERED SIGN]
+char \xAF	(    567 )  # AF ⡰ ¯ [MACRON]
+char \xB0	( 23  6 8)  # B0 ⢦ ° [DEGREE SIGN]
+char \xB1	( 2     8)  # B1 ⢂ ± [PLUS-MINUS SIGN]
+char \xB2	(123456 8)  # B2 ⢿ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5 7 )  # B3 ⡒ ³ [SUPERSCRIPT THREE]
+char \xB4	( 2  567 )  # B4 ⡲ ´ [ACUTE ACCENT]
+char \xB5	(1234 6 8)  # B5 ⢯ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(1   56 8)  # B7 ⢱ · [MIDDLE DOT]
+char \xB8	( 23  67 )  # B8 ⡦ ¸ [CEDILLA]
+char \xB9	(  3 5 7 )  # B9 ⡔ ¹ [SUPERSCRIPT ONE]
+char \xBA	(  3   7 )  # BA ⡄ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(  34  7 )  # BB ⡌ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(     67 )  # BC ⡠ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  34 67 )  # BD ⡬ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  345 7 )  # BE ⡜ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(123 567 )  # BF ⡷ ¿ [INVERTED QUESTION MARK]
+char \xC0	(      78)  # C0 ⣀ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 234 678)  # C1 ⣮ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(    5 78)  # C2 ⣐ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  345678)  # C3 ⣼ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(1 345  8)  # C4 ⢝ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1 3 5  8)  # C5 ⢕ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(123 5  8)  # C6 ⢗ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(   4   8)  # C7 ⢈ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(123 5678)  # C8 ⣷ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234   8)  # C9 ⢏ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1    678)  # CA ⣡ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(  34 678)  # CB ⣬ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(     678)  # CC ⣠ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  3  678)  # CD ⣤ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(   4 678)  # CE ⣨ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(  34  78)  # CF ⣌ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  3 5678)  # D0 ⣴ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(1  4 67 )  # D1 ⡩ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 23   78)  # D2 ⣆ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	( 2  5 78)  # D3 ⣒ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2  5678)  # D4 ⣲ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 2   678)  # D5 ⣢ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(1 3456 8)  # D6 ⢽ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23 5678)  # D7 ⣶ × [MULTIPLICATION SIGN]
+char \xD8	( 23  678)  # D8 ⣦ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(  3 5 78)  # D9 ⣔ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(1   5678)  # DA ⣱ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(    5678)  # DB ⣰ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(1 3 56 8)  # DC ⢵ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(12345678)  # DD ⣿ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  345 78)  # DE ⣜ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(      7 )  # E1 ⡀ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  3456 8)  # E3 ⢼ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(1  45  8)  # E4 ⢙ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(12 4   8)  # E5 ⢋ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(12345  8)  # E6 ⢟ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1 34   8)  # EC ⢍ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	( 234 67 )  # ED ⡮ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(  3 56 8)  # F0 ⢴ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 4 67 )  # F1 ⡫ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3  6 8)  # F2 ⢥ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(    5 7 )  # F3 ⡐ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 2   6 8)  # F5 ⢢ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2345  8)  # F6 ⢞ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 23 5  8)  # F7 ⢖ ÷ [DIVISION SIGN]
+char \xF8	(1 3 5 7 )  # F8 ⡕ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(  34567 )  # FA ⡼ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23   7 )  # FD ⡆ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  345  8)  # FE ⢜ þ [LATIN SMALL LETTER THORN]
+char \xFF	(1   567 )  # FF ⡱ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/fr-vs.ttb b/Tables/Text/fr-vs.ttb
new file mode 100644
index 0000000..ea3886b
--- /dev/null
+++ b/Tables/Text/fr-vs.ttb
@@ -0,0 +1,241 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French (VisioBraille)
+
+# This table is the default one for VisioBraille braille terminals.
+# It was first used by Sagem printers, before being adopted by Handialog as
+# the default braille table.
+# Although it is a bit old, VisioBraille users may find this table more
+# comfortable than standard French braille tables.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 3456
+include num-french.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(  345 78)  # 00 ⣜   [NULL]
+char \x01	(1      8)  # 01 ⢁   [START OF HEADING]
+char \x02	(12     8)  # 02 ⢃   [START OF TEXT]
+char \x03	(1  4   8)  # 03 ⢉   [END OF TEXT]
+char \x04	(1  45  8)  # 04 ⢙   [END OF TRANSMISSION]
+char \x05	(1   5  8)  # 05 ⢑   [ENQUIRY]
+char \x06	(12 4   8)  # 06 ⢋   [ACKNOWLEDGE]
+char \x07	(12 45  8)  # 07 ⢛   [BELL]
+char \x08	(12  5  8)  # 08 ⢓   [BACKSPACE]
+char \x09	( 2 4   8)  # 09 ⢊   [CHARACTER TABULATION]
+char \x0A	( 2 45  8)  # 0A ⢚   [LINE FEED (LF)]
+char \x0B	(1 3    8)  # 0B ⢅   [LINE TABULATION]
+char \x0C	(123    8)  # 0C ⢇   [FORM FEED (FF)]
+char \x0D	(1 34   8)  # 0D ⢍   [CARRIAGE RETURN (CR)]
+char \x0E	(1 345  8)  # 0E ⢝   [SHIFT OUT]
+char \x0F	(1 3 5  8)  # 0F ⢕   [SHIFT IN]
+char \x10	(1234   8)  # 10 ⢏   [DATA LINK ESCAPE]
+char \x11	(12345  8)  # 11 ⢟   [DEVICE CONTROL ONE]
+char \x12	(123 5  8)  # 12 ⢗   [DEVICE CONTROL TWO]
+char \x13	( 234   8)  # 13 ⢎   [DEVICE CONTROL THREE]
+char \x14	( 2345  8)  # 14 ⢞   [DEVICE CONTROL FOUR]
+char \x15	(1 3  6 8)  # 15 ⢥   [NEGATIVE ACKNOWLEDGE]
+char \x16	(123  6 8)  # 16 ⢧   [SYNCHRONOUS IDLE]
+char \x17	( 2 456 8)  # 17 ⢺   [END OF TRANSMISSION BLOCK]
+char \x18	(1 34 6 8)  # 18 ⢭   [CANCEL]
+char \x19	(1 3456 8)  # 19 ⢽   [END OF MEDIUM]
+char \x1A	(1 3 56 8)  # 1A ⢵   [SUBSTITUTE]
+char \x1B	(  345678)  # 1B ⣼   [ESCAPE]
+char \x1C	(1    6 8)  # 1C ⢡   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12   6 8)  # 1D ⢣   [INFORMATION SEPARATOR THREE]
+char \x1E	(1  4 6 8)  # 1E ⢩   [INFORMATION SEPARATOR TWO]
+char \x1F	(1  456 8)  # 1F ⢹   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # 22 ⠈ " [QUOTATION MARK]
+char \x23	(   4 6  )  # 23 ⠨ # [NUMBER SIGN]
+char \x24	(   456  )  # 24 ⠸ $ [DOLLAR SIGN]
+char \x25	(   45   )  # 25 ⠘ % [PERCENT SIGN]
+char \x26	(  345   )  # 26 ⠜ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(     6  )  # 28 ⠠ ( [LEFT PARENTHESIS]
+char \x29	(  3 5   )  # 29 ⠔ ) [RIGHT PARENTHESIS]
+char \x2A	(  34    )  # 2A ⠌ * [ASTERISK]
+char \x2B	(    56  )  # 2B ⠰ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(123456  )  # 2F ⠿ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(1234 6  )  # 40 ⠯ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5C	( 234 6  )  # 5C ⠮ \ [REVERSE SOLIDUS]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(  34 6  )  # 5E ⠬ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3    8)  # 5F ⢄ _ [LOW LINE]
+char \x60	(  3   7 )  # 60 ⡄ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(     67 )  # 7B ⡠ { [LEFT CURLY BRACKET]
+char \x7C	( 23 5  8)  # 7C ⢖ | [VERTICAL LINE]
+char \x7D	(  3 5  8)  # 7D ⢔ } [RIGHT CURLY BRACKET]
+char \x7E	(    5 78)  # 7E ⣐ ~ [TILDE]
+char \x7F	(        )  # 7F ⠀   [DELETE]
+char \x80	(1234 678)  # 80 ⣯   [<control-0080>]
+char \x81	(12  56 8)  # 81 ⢳   [<control-0081>]
+char \x82	(        )  # 82 ⠀   [BREAK PERMITTED HERE]
+char \x83	(1    6 8)  # 83 ⢡   [NO BREAK HERE]
+char \x84	(  345  8)  # 84 ⢜   [<control-0084>]
+char \x85	(123 56 8)  # 85 ⢷   [NEXT LINE (NEL)]
+char \x86	(        )  # 86 ⠀   [START OF SELECTED AREA]
+char \x87	(1234 6 8)  # 87 ⢯   [END OF SELECTED AREA]
+char \x88	(12   6 8)  # 88 ⢣   [CHARACTER TABULATION SET]
+char \x89	(12 4 6 8)  # 89 ⢫   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	( 234 6 8)  # 8A ⢮   [LINE TABULATION SET]
+char \x8B	(12 456 8)  # 8B ⢻   [PARTIAL LINE FORWARD]
+char \x8C	(1  4 6 8)  # 8C ⢩   [PARTIAL LINE BACKWARD]
+char \x8D	(        )  # 8D ⠀   [REVERSE LINE FEED]
+char \x8E	(  345 78)  # 8E ⣜   [SINGLE SHIFT TWO]
+char \x8F	(        )  # 8F ⠀   [SINGLE SHIFT THREE]
+char \x90	(12345678)  # 90 ⣿   [DEVICE CONTROL STRING]
+char \x91	(        )  # 91 ⠀   [PRIVATE USE ONE]
+char \x92	(        )  # 92 ⠀   [PRIVATE USE TWO]
+char \x93	(1  456 8)  # 93 ⢹   [SET TRANSMIT STATE]
+char \x94	( 2 4 6 8)  # 94 ⢪   [CANCEL CHARACTER]
+char \x95	(        )  # 95 ⠀   [MESSAGE WAITING]
+char \x96	(1   56 8)  # 96 ⢱   [START OF GUARDED AREA]
+char \x97	( 23456 8)  # 97 ⢾   [END OF GUARDED AREA]
+char \x98	(12 456 8)  # 98 ⢻   [START OF STRING]
+char \x99	( 2 4 678)  # 99 ⣪   [<control-0099>]
+char \x9A	(12  5678)  # 9A ⣳   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(        )  # 9B ⠀   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(        )  # 9C ⠀   [STRING TERMINATOR]
+char \x9D	(        )  # 9D ⠀   [OPERATING SYSTEM COMMAND]
+char \x9E	(        )  # 9E ⠀   [PRIVACY MESSAGE]
+char \x9F	(        )  # 9F ⠀   [APPLICATION PROGRAM COMMAND]
+char \xA0	(        )  # A0 ⠀   [NO-BREAK SPACE]
+char \xA1	(        )  # A1 ⠀ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(        )  # A2 ⠀ ¢ [CENT SIGN]
+char \xA3	(        )  # A3 ⠀ £ [POUND SIGN]
+char \xA4	(        )  # A4 ⠀ ¤ [CURRENCY SIGN]
+char \xA5	(        )  # A5 ⠀ ¥ [YEN SIGN]
+char \xA6	(        )  # A6 ⠀ ¦ [BROKEN BAR]
+char \xA7	(        )  # A7 ⠀ § [SECTION SIGN]
+char \xA8	(        )  # A8 ⠀ ¨ [DIAERESIS]
+char \xA9	(        )  # A9 ⠀ © [COPYRIGHT SIGN]
+char \xAA	(        )  # AA ⠀ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23  6 8)  # AB ⢦ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(        )  # AC ⠀ ¬ [NOT SIGN]
+char \xAD	(        )  # AD ⠀ ­ [SOFT HYPHEN]
+char \xAE	(        )  # AE ⠀ ® [REGISTERED SIGN]
+char \xAF	(        )  # AF ⠀ ¯ [MACRON]
+char \xB0	(        )  # B0 ⠀ ° [DEGREE SIGN]
+char \xB1	(        )  # B1 ⠀ ± [PLUS-MINUS SIGN]
+char \xB2	(        )  # B2 ⠀ ² [SUPERSCRIPT TWO]
+char \xB3	(        )  # B3 ⠀ ³ [SUPERSCRIPT THREE]
+char \xB4	(        )  # B4 ⠀ ´ [ACUTE ACCENT]
+char \xB5	(        )  # B5 ⠀ µ [MICRO SIGN]
+char \xB6	(        )  # B6 ⠀ ¶ [PILCROW SIGN]
+char \xB7	(        )  # B7 ⠀ · [MIDDLE DOT]
+char \xB8	(        )  # B8 ⠀ ¸ [CEDILLA]
+char \xB9	(        )  # B9 ⠀ ¹ [SUPERSCRIPT ONE]
+char \xBA	(        )  # BA ⠀ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(  3 567 )  # BB ⡴ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(        )  # BC ⠀ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(        )  # BD ⠀ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(        )  # BE ⠀ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(        )  # BF ⠀ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 5678)  # C0 ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(        )  # C1 ⠀ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1     78)  # C2 ⣁ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(        )  # C3 ⠀ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  3 5 78)  # C4 ⣔ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(        )  # C5 ⠀ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(        )  # C6 ⠀ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 678)  # C7 ⣯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(        )  # C8 ⠀ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(12345678)  # C9 ⣿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   678)  # CA ⣣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 678)  # CB ⣫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(        )  # CC ⠀ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(        )  # CD ⠀ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 678)  # CE ⣩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 45678)  # CF ⣻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(        )  # D0 ⠀ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(        )  # D1 ⠀ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(        )  # D2 ⠀ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(        )  # D3 ⠀ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1 3 5678)  # D4 ⣵ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(        )  # D5 ⠀ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(        )  # D7 ⠀ × [MULTIPLICATION SIGN]
+char \xD8	(        )  # D8 ⠀ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 2345678)  # D9 ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(        )  # DA ⠀ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   5678)  # DB ⣱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  5678)  # DC ⣳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(        )  # DD ⠀ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(        )  # DE ⠀ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(        )  # DF ⠀ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(        )  # E1 ⠀ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # E2 ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(        )  # E3 ⠀ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(        )  # E5 ⠀ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(        )  # E6 ⠀ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456 8)  # E9 ⢿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6 8)  # EA ⢣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(        )  # EC ⠀ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(        )  # ED ⠀ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6 8)  # EE ⢩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(        )  # F0 ⠀ ð [LATIN SMALL LETTER ETH]
+char \xF1	(        )  # F1 ⠀ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(        )  # F2 ⠀ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(        )  # F3 ⠀ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456 8)  # F4 ⢹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(        )  # F5 ⠀ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(        )  # F7 ⠀ ÷ [DIVISION SIGN]
+char \xF8	(        )  # F8 ⠀ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(        )  # FA ⠀ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56 8)  # FB ⢱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(        )  # FD ⠀ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(        )  # FE ⠀ þ [LATIN SMALL LETTER THORN]
+char \xFF	(        )  # FF ⠀ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+char \u0153	( 2 4 6 8)  # ⢪ œ [LATIN SMALL LIGATURE OE]
+
+glyph \u2019	(  3     )  # ⠄ ’ [RIGHT SINGLE QUOTATION MARK]
+char \u20AC	(1   5  8)  # ⢑ € [EURO SIGN]
+char \xA7	(1234 6 8)  # ⢯ § [SECTION SIGN]
+char \xB7 (    5 7 )  # B7 ◈ · [MIDDLE DOT]
+char \xBD	(1 34567 )  # ⡽ ½ [VULGAR FRACTION ONE HALF]
+
+include common.tti
diff --git a/Tables/Text/fr.ttb b/Tables/Text/fr.ttb
new file mode 100644
index 0000000..f47b0fb
--- /dev/null
+++ b/Tables/Text/fr.ttb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French
+
+include fr-2007.ttb
diff --git a/Tables/Text/fr_CA.ttb b/Tables/Text/fr_CA.ttb
new file mode 100644
index 0000000..2ed1eee
--- /dev/null
+++ b/Tables/Text/fr_CA.ttb
@@ -0,0 +1,170 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French (Canada)
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   4  78)  # 00 ⣈   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2 4 678)  # 1B ⣪   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12 45678)  # 1D ⣻   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(1  4   8)  # 22 ⢉ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(12 4 6  )  # 24 ⠫ $ [DOLLAR SIGN]
+char \x25	(1  4 6 8)  # 25 ⢩ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(123 567 )  # 28 ⡷ ( [LEFT PARENTHESIS]
+char \x29	( 234567 )  # 29 ⡾ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 7 )  # 2B ⡖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34  7 )  # 2F ⡌ / [SOLIDUS]
+
+include num-nemd8.tti
+
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	(1234567 )  # 3D ⡿ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	(1  456  )  # 3F ⠹ ? [QUESTION MARK]
+char \x40	(   4  7 )  # 40 ⡈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 2 4 67 )  # 5B ⡪ [ [LEFT SQUARE BRACKET]
+char \x5C	(1    67 )  # 5C ⡡ \ [REVERSE SOLIDUS]
+char \x5D	(12 4567 )  # 5D ⡻ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 2 4 6  )  # 7B ⠪ { [LEFT CURLY BRACKET]
+char \x7C	(12  56  )  # 7C ⠳ | [VERTICAL LINE]
+char \x7D	(12 456  )  # 7D ⠻ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \x7F	(   4567 )  # 7F ⡸   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2 4 6 8)  # A2 ⢪ ¢ [CENT SIGN]
+char \xA3	(12  56 8)  # A3 ⢳ £ [POUND SIGN]
+char \xA4	( 2    7 )  # A4 ⡂ ¤ [CURRENCY SIGN]
+char \xA5	(12 456 8)  # A5 ⢻ ¥ [YEN SIGN]
+char \xA6	(12   67 )  # A6 ⡣ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	(1  4567 )  # A8 ⡹ ¨ [DIAERESIS]
+char \xA9	( 234567 )  # A9 ⡾ © [COPYRIGHT SIGN]
+char \xAA	(1234 67 )  # AA ⡯ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(   4 67 )  # AB ⡨ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(1    67 )  # AC ⡡ ¬ [NOT SIGN]
+char \xAD	(  3  6 8)  # AD ⢤ ­ [SOFT HYPHEN]
+char \xAE	(1234567 )  # AE ⡿ ® [REGISTERED SIGN]
+char \xAF	(    567 )  # AF ⡰ ¯ [MACRON]
+char \xB0	( 23  6 8)  # B0 ⢦ ° [DEGREE SIGN]
+char \xB1	( 2     8)  # B1 ⢂ ± [PLUS-MINUS SIGN]
+char \xB2	(123456 8)  # B2 ⢿ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5 7 )  # B3 ⡒ ³ [SUPERSCRIPT THREE]
+char \xB4	( 2  567 )  # B4 ⡲ ´ [ACUTE ACCENT]
+char \xB5	(1234 6 8)  # B5 ⢯ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(1   56 8)  # B7 ⢱ · [MIDDLE DOT]
+char \xB8	( 23  67 )  # B8 ⡦ ¸ [CEDILLA]
+char \xB9	(  3 5 7 )  # B9 ⡔ ¹ [SUPERSCRIPT ONE]
+char \xBA	(  3   7 )  # BA ⡄ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(  34  7 )  # BB ⡌ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(     67 )  # BC ⡠ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  34 67 )  # BD ⡬ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  345 7 )  # BE ⡜ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(123 567 )  # BF ⡷ ¿ [INVERTED QUESTION MARK]
+char \xC0	(      78)  # C0 ⣀ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 234 678)  # C1 ⣮ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(    5 78)  # C2 ⣐ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  345678)  # C3 ⣼ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(1 345  8)  # C4 ⢝ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1 3 5  8)  # C5 ⢕ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(123 5  8)  # C6 ⢗ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(   4   8)  # C7 ⢈ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(123 5678)  # C8 ⣷ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234   8)  # C9 ⢏ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1    678)  # CA ⣡ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(  34 678)  # CB ⣬ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(     678)  # CC ⣠ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  3  678)  # CD ⣤ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(   4 678)  # CE ⣨ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(  34  78)  # CF ⣌ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  3 5678)  # D0 ⣴ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(1  4 67 )  # D1 ⡩ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 23   78)  # D2 ⣆ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	( 2  5 78)  # D3 ⣒ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2  5678)  # D4 ⣲ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 2   678)  # D5 ⣢ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(1 3456 8)  # D6 ⢽ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23 5678)  # D7 ⣶ × [MULTIPLICATION SIGN]
+char \xD8	( 23  678)  # D8 ⣦ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(  3 5 78)  # D9 ⣔ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(1   5678)  # DA ⣱ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(    5678)  # DB ⣰ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(1 3 56 8)  # DC ⢵ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(12345678)  # DD ⣿ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  345 78)  # DE ⣜ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # DF ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(      7 )  # E1 ⡀ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  3456 8)  # E3 ⢼ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(1  45  8)  # E4 ⢙ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(12 4   8)  # E5 ⢋ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(12345  8)  # E6 ⢟ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1 34   8)  # EC ⢍ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	( 234 67 )  # ED ⡮ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(  3 56 8)  # F0 ⢴ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 4 67 )  # F1 ⡫ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3  6 8)  # F2 ⢥ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(    5 7 )  # F3 ⡐ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 2   6 8)  # F5 ⢢ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2345  8)  # F6 ⢞ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 23 5  8)  # F7 ⢖ ÷ [DIVISION SIGN]
+char \xF8	(  3 567 )  # F8 ⡴ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(  34567 )  # FA ⡼ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23   7 )  # FD ⡆ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  345  8)  # FE ⢜ þ [LATIN SMALL LETTER THORN]
+char \xFF	(1   567 )  # FF ⡱ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/fr_FR.ttb b/Tables/Text/fr_FR.ttb
new file mode 100644
index 0000000..958416f
--- /dev/null
+++ b/Tables/Text/fr_FR.ttb
@@ -0,0 +1,175 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - French (France)
+
+# This is the reimplementation of the fr-cbifs table using the
+# ISO-8859-1 (Latin 1) character set.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 3456
+include num-french.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(  345 78)  # 00 ⣜   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(   4 678)  # 1B ⣨   [ESCAPE]
+char \x1C	(    5 78)  # 1C ⣐   [INFORMATION SEPARATOR FOUR]
+char \x1D	(    5678)  # 1D ⣰   [INFORMATION SEPARATOR THREE]
+char \x1E	(     678)  # 1E ⣠   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  34 6  )  # 23 ⠬ # [NUMBER SIGN]
+char \x24	(   456  )  # 24 ⠸ $ [DOLLAR SIGN]
+char \x25	(1234 6  )  # 25 ⠯ % [PERCENT SIGN]
+char \x26	(123456  )  # 26 ⠿ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 78)  # 2B ⣖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # 3C ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 5678)  # 3D ⣶ = [EQUALS SIGN]
+char \x3E	(   45   )  # 3E ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345   )  # 40 ⠜ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5C	( 234 6  )  # 5C ⠮ \ [REVERSE SOLIDUS]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(  34    )  # 5E ⠌ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # 5F ⡸ _ [LOW LINE]
+char \x60	(  345 7 )  # 60 ⡜ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 567 )  # 7B ⡷ { [LEFT CURLY BRACKET]
+char \x7C	( 234 67 )  # 7C ⡮ | [VERTICAL LINE]
+char \x7D	( 234567 )  # 7D ⡾ } [RIGHT CURLY BRACKET]
+char \x7E	(  34  7 )  # 7E ⡌ ~ [TILDE]
+char \x7F	(   4 67 )  # 7F ⡨   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(1  4   8)  # A2 ⢉ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \u20AC	(1   5  8)  # ⢑ € [EURO SIGN]
+char \xA5	(1 3456 8)  # A5 ⢽ ¥ [YEN SIGN]
+char \xA6	(  3 5 78)  # A6 ⣔ ¦ [BROKEN BAR]
+char \xA7	( 234   8)  # A7 ⢎ § [SECTION SIGN]
+char \xA8	(    5   )  # A8 ⠐ ¨ [DIAERESIS]
+char \xA9	(12345  8)  # A9 ⢟ © [COPYRIGHT SIGN]
+char \xAA	(12 4   8)  # AA ⢋ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23  678)  # AB ⣦ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(1 345  8)  # AC ⢝ ¬ [NOT SIGN]
+char \xAD	(  3  678)  # AD ⣤ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	(12345678)  # AF ⣿ ¯ [MACRON]
+char \xB0	(1 3 5  8)  # B0 ⢕ ° [DEGREE SIGN]
+char \xB1	(1 34 6 8)  # B1 ⢭ ± [PLUS-MINUS SIGN]
+char \xB2	(   45 78)  # B2 ⣘ ² [SUPERSCRIPT TWO]
+char \xB3	(  3   78)  # B3 ⣄ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4    )  # B4 ⠈ ´ [ACUTE ACCENT]
+char \xB5	(1 3  6 8)  # B5 ⢥ µ [MICRO SIGN]
+char \xB6	(1234   8)  # B6 ⢏ ¶ [PILCROW SIGN]
+char \xB7	(    5 7 )  # B7 ⡐ · [MIDDLE DOT]
+char \xB8	(   4 6  )  # B8 ⠨ ¸ [CEDILLA]
+char \xB9	(   4  78)  # B9 ⣈ ¹ [SUPERSCRIPT ONE]
+char \xBA	(1 34   8)  # BA ⢍ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(  3 5678)  # BB ⣴ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(  34  78)  # BC ⣌ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  34 678)  # BD ⣬ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  345678)  # BE ⣼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  34   8)  # BF ⢌ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 5678)  # C0 ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	( 2    78)  # C2 ⣂ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	( 2     8)  # C3 ⢂ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(   4  7 )  # C4 ⡈ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  34567 )  # C5 ⡼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(   45 7 )  # C6 ⡘ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 678)  # C8 ⣮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	( 23   78)  # CA ⣆ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	( 23 5  8)  # CB ⢖ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	( 2  5  8)  # CC ⢒ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 2  5 7 )  # CD ⡒ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	( 2  5 78)  # CE ⣒ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	( 23 56 8)  # CF ⢶ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	( 23    8)  # D0 ⢆ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 4567 )  # D1 ⡻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2  56 8)  # D2 ⢲ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	( 2  567 )  # D3 ⡲ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2  5678)  # D4 ⣲ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(12 4 67 )  # D5 ⡫ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 2345  8)  # D7 ⢞ × [MULTIPLICATION SIGN]
+char \xD8	( 2   6 8)  # D8 ⢢ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 2345678)  # D9 ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2   67 )  # DA ⡢ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	( 2   678)  # DB ⣢ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 23   7 )  # DD ⡆ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  34 67 )  # DE ⡬ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 2 4 678)  # DF ⣪ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    67 )  # E1 ⡡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1    6 8)  # E3 ⢡ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(   4   8)  # E4 ⢈ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  3456 8)  # E5 ⢼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(   45  8)  # E6 ⢘ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6 8)  # E8 ⢮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456 8)  # E9 ⢿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   678)  # EA ⣣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1  4 6 8)  # EC ⢩ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(1  4 67 )  # ED ⡩ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(12   6 8)  # F0 ⢣ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 45678)  # F1 ⣻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1  456 8)  # F2 ⢹ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1  4567 )  # F3 ⡹ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(12 4 678)  # F5 ⣫ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(1  45  8)  # F7 ⢙ ÷ [DIVISION SIGN]
+char \xF8	(1   56 8)  # F8 ⢱ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1   567 )  # FA ⡱ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   5678)  # FB ⣱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(12   67 )  # FD ⡣ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  34 6 8)  # FE ⢬ þ [LATIN SMALL LETTER THORN]
+char \xFF	(12  5678)  # FF ⣳ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/ga.ttb b/Tables/Text/ga.ttb
new file mode 100644
index 0000000..af022b1
--- /dev/null
+++ b/Tables/Text/ga.ttb
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Irish
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# generated by ttbtest: charset=latin1
+char \xC1	(1234 67 )  # C1 ⡯ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCD	(123 567 )  # CD ⡷ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xD3	( 234 67 )  # D3 ⡮ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xDA	( 234567 )  # DA ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xE1	(1234 6  )  # E1 ⠯ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xED	(123 56  )  # ED ⠷ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xF3	( 234 6  )  # F3 ⠮ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+
+include common.tti
diff --git a/Tables/Text/gd.ttb b/Tables/Text/gd.ttb
new file mode 100644
index 0000000..6c789d2
--- /dev/null
+++ b/Tables/Text/gd.ttb
@@ -0,0 +1,68 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Gaelic
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# lowercase accented letters
+char \xE1	(1234 6  )  # ⠯ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE9	(123456  )  # ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xED	(123 56  )  # ⠷ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xF3	( 234 6  )  # ⠮ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xFA	( 23456  )  # ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \u1E03	(12   6  )  # ⠣ ḃ [LATIN SMALL LETTER B WITH DOT ABOVE]
+char \u010B	(1  4 6  )  # ⠩ ċ [LATIN SMALL LETTER C WITH DOT ABOVE]
+char \u1E0B	(1  456  )  # ⠹ ḋ [LATIN SMALL LETTER D WITH DOT ABOVE]
+char \u1E1F	(1   56  )  # ⠱ ḟ [LATIN SMALL LETTER F WITH DOT ABOVE]
+char \u0121	(12 4 6  )  # ⠫ ġ [LATIN SMALL LETTER G WITH DOT ABOVE]
+char \u1E41	(12 456  )  # ⠻ ṁ [LATIN SMALL LETTER M WITH DOT ABOVE]
+char \u1E57	(12  56  )  # ⠳ ṗ [LATIN SMALL LETTER P WITH DOT ABOVE]
+char \u1E61	( 2 4 6  )  # ⠪ ṡ [LATIN SMALL LETTER S WITH DOT ABOVE]
+char \u1E6B	( 2 456  )  # ⠺ ṫ [LATIN SMALL LETTER T WITH DOT ABOVE]
+
+# uppercase accented letters
+char \xC1	(1234 67 )  # ⡯ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC9	(1234567 )  # ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCD	(123 567 )  # ⡷ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xD3	( 234 67 )  # ⡮ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xDA	( 234567 )  # ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \u1E02	(12   67 )  # ⡣ Ḃ [LATIN CAPITAL LETTER B WITH DOT ABOVE]
+char \u010A	(1  4 67 )  # ⡩ Ċ [LATIN CAPITAL LETTER C WITH DOT ABOVE]
+char \u1E0A	(1  4567 )  # ⡹ Ḋ [LATIN CAPITAL LETTER D WITH DOT ABOVE]
+char \u1E1E	(1   567 )  # ⡱ Ḟ [LATIN CAPITAL LETTER F WITH DOT ABOVE]
+char \u0120	(12 4 67 )  # ⡫ Ġ [LATIN CAPITAL LETTER G WITH DOT ABOVE]
+char \u1E40	(12 4567 )  # ⡻ Ṁ [LATIN CAPITAL LETTER M WITH DOT ABOVE]
+char \u1E56	(12  567 )  # ⡳ Ṗ [LATIN CAPITAL LETTER P WITH DOT ABOVE]
+char \u1E60	( 2 4 67 )  # ⡪ Ṡ [LATIN CAPITAL LETTER S WITH DOT ABOVE]
+char \u1E6A	( 2 4567 )  # ⡺ Ṫ [LATIN CAPITAL LETTER T WITH DOT ABOVE]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+
+include common.tti
diff --git a/Tables/Text/gon.ttb b/Tables/Text/gon.ttb
new file mode 100644
index 0000000..4989535
--- /dev/null
+++ b/Tables/Text/gon.ttb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Gondi
+
+include devanagari.tti
+include telugu.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/greek.tti b/Tables/Text/greek.tti
new file mode 100644
index 0000000..b42001c
--- /dev/null
+++ b/Tables/Text/greek.tti
@@ -0,0 +1,323 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for characters that are unique to the Greek language.
+# Dot 7 represents uppercase.
+# Dot 8 represents the acute (tonos or oxia) accent.
+# Maintained by: Dave Mielke <dave@mielke.cc>
+
+char	\u037E	( 2   6  )	# ⠢ ; [GREEK QUESTION MARK]
+glyph	\u0384	(    5   )	# ⠐ ΄ [GREEK TONOS]
+char	\u0386	(1     78)	# ⣁ Ά [GREEK CAPITAL LETTER ALPHA WITH TONOS]
+char	\u0387	( 23     )	# ⠆ · [GREEK ANO TELEIA]
+char	\u0388	(1   5 78)	# ⣑ Έ [GREEK CAPITAL LETTER EPSILON WITH TONOS]
+char	\u0389	(  345 78)	# ⣜ Ή [GREEK CAPITAL LETTER ETA WITH TONOS]
+char	\u038A	( 2 4  78)	# ⣊ Ί [GREEK CAPITAL LETTER IOTA WITH TONOS]
+char	\u038C	(1 3 5 78)	# ⣕ Ό [GREEK CAPITAL LETTER OMICRON WITH TONOS]
+char	\u038E	(1 345678)	# ⣽ Ύ [GREEK CAPITAL LETTER UPSILON WITH TONOS]
+char	\u038F	( 2 45 78)	# ⣚ Ώ [GREEK CAPITAL LETTER OMEGA WITH TONOS]
+glyph	\u0390	( 2 4   8)	# ⢊ ΐ [GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS]
+char	\u0391	(1     7 )	# ⡁ Α [GREEK CAPITAL LETTER ALPHA]
+char	\u0392	(12    7 )	# ⡃ Β [GREEK CAPITAL LETTER BETA]
+char	\u0393	(12 45 7 )	# ⡛ Γ [GREEK CAPITAL LETTER GAMMA]
+char	\u0394	(1  45 7 )	# ⡙ Δ [GREEK CAPITAL LETTER DELTA]
+char	\u0395	(1   5 7 )	# ⡑ Ε [GREEK CAPITAL LETTER EPSILON]
+char	\u0396	(1 3 567 )	# ⡵ Ζ [GREEK CAPITAL LETTER ZETA]
+char	\u0397	(  345 7 )	# ⡜ Η [GREEK CAPITAL LETTER ETA]
+char	\u0398	(1  4567 )	# ⡹ Θ [GREEK CAPITAL LETTER THETA]
+char	\u0399	( 2 4  7 )	# ⡊ Ι [GREEK CAPITAL LETTER IOTA]
+char	\u039A	(1 3   7 )	# ⡅ Κ [GREEK CAPITAL LETTER KAPPA]
+char	\u039B	(123   7 )	# ⡇ Λ [GREEK CAPITAL LETTER LAMDA]
+char	\u039C	(1 34  7 )	# ⡍ Μ [GREEK CAPITAL LETTER MU]
+char	\u039D	(1 345 7 )	# ⡝ Ν [GREEK CAPITAL LETTER NU]
+char	\u039E	(1 34 67 )	# ⡭ Ξ [GREEK CAPITAL LETTER XI]
+char	\u039F	(1 3 5 7 )	# ⡕ Ο [GREEK CAPITAL LETTER OMICRON]
+char	\u03A0	(1234  7 )	# ⡏ Π [GREEK CAPITAL LETTER PI]
+char	\u03A1	(123 5 7 )	# ⡗ Ρ [GREEK CAPITAL LETTER RHO]
+char	\u03A3	( 234  7 )	# ⡎ Σ [GREEK CAPITAL LETTER SIGMA]
+char	\u03A4	( 2345 7 )	# ⡞ Τ [GREEK CAPITAL LETTER TAU]
+char	\u03A5	(1 34567 )	# ⡽ Υ [GREEK CAPITAL LETTER UPSILON]
+char	\u03A6	(12 4  7 )	# ⡋ Φ [GREEK CAPITAL LETTER PHI]
+char	\u03A7	(12  5 7 )	# ⡓ Χ [GREEK CAPITAL LETTER CHI]
+char	\u03A8	(1234 67 )	# ⡯ Ψ [GREEK CAPITAL LETTER PSI]
+char	\u03A9	( 2 45 7 )	# ⡚ Ω [GREEK CAPITAL LETTER OMEGA]
+glyph	\u03AA	( 2 4  7 )	# ⡊ Ϊ [GREEK CAPITAL LETTER IOTA WITH DIALYTIKA]
+glyph	\u03AB	(1 34567 )	# ⡽ Ϋ [GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA]
+char	\u03AC	(1      8)	# ⢁ ά [GREEK SMALL LETTER ALPHA WITH TONOS]
+char	\u03AD	(1   5  8)	# ⢑ έ [GREEK SMALL LETTER EPSILON WITH TONOS]
+char	\u03AE	(  345  8)	# ⢜ ή [GREEK SMALL LETTER ETA WITH TONOS]
+char	\u03AF	( 2 4   8)	# ⢊ ί [GREEK SMALL LETTER IOTA WITH TONOS]
+glyph	\u03B0	(1 3456 8)	# ⢽ ΰ [GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS]
+char	\u03B1	(1       )	# ⠁ α [GREEK SMALL LETTER ALPHA]
+char	\u03B2	(12      )	# ⠃ β [GREEK SMALL LETTER BETA]
+char	\u03B3	(12 45   )	# ⠛ γ [GREEK SMALL LETTER GAMMA]
+char	\u03B4	(1  45   )	# ⠙ δ [GREEK SMALL LETTER DELTA]
+char	\u03B5	(1   5   )	# ⠑ ε [GREEK SMALL LETTER EPSILON]
+char	\u03B6	(1 3 56  )	# ⠵ ζ [GREEK SMALL LETTER ZETA]
+char	\u03B7	(  345   )	# ⠜ η [GREEK SMALL LETTER ETA]
+char	\u03B8	(1  456  )	# ⠹ θ [GREEK SMALL LETTER THETA]
+char	\u03B9	( 2 4    )	# ⠊ ι [GREEK SMALL LETTER IOTA]
+char	\u03BA	(1 3     )	# ⠅ κ [GREEK SMALL LETTER KAPPA]
+char	\u03BB	(123     )	# ⠇ λ [GREEK SMALL LETTER LAMDA]
+char	\u03BC	(1 34    )	# ⠍ μ [GREEK SMALL LETTER MU]
+char	\u03BD	(1 345   )	# ⠝ ν [GREEK SMALL LETTER NU]
+char	\u03BE	(1 34 6  )	# ⠭ ξ [GREEK SMALL LETTER XI]
+char	\u03BF	(1 3 5   )	# ⠕ ο [GREEK SMALL LETTER OMICRON]
+char	\u03C0	(1234    )	# ⠏ π [GREEK SMALL LETTER PI]
+char	\u03C1	(123 5   )	# ⠗ ρ [GREEK SMALL LETTER RHO]
+glyph	\u03C2	( 234    )	# ⠎ ς [GREEK SMALL LETTER FINAL SIGMA]
+char	\u03C3	( 234    )	# ⠎ σ [GREEK SMALL LETTER SIGMA]
+char	\u03C4	( 2345   )	# ⠞ τ [GREEK SMALL LETTER TAU]
+char	\u03C5	(1 3456  )	# ⠽ υ [GREEK SMALL LETTER UPSILON]
+char	\u03C6	(12 4    )	# ⠋ φ [GREEK SMALL LETTER PHI]
+char	\u03C7	(12  5   )	# ⠓ χ [GREEK SMALL LETTER CHI]
+char	\u03C8	(1234 6  )	# ⠯ ψ [GREEK SMALL LETTER PSI]
+char	\u03C9	( 2 45   )	# ⠚ ω [GREEK SMALL LETTER OMEGA]
+glyph	\u03CA	( 2 4    )	# ⠊ ϊ [GREEK SMALL LETTER IOTA WITH DIALYTIKA]
+glyph	\u03CB	(1 3456  )	# ⠽ ϋ [GREEK SMALL LETTER UPSILON WITH DIALYTIKA]
+char	\u03CC	(1 3 5  8)	# ⢕ ό [GREEK SMALL LETTER OMICRON WITH TONOS]
+char	\u03CD	(1 3456 8)	# ⢽ ύ [GREEK SMALL LETTER UPSILON WITH TONOS]
+char	\u03CE	( 2 45  8)	# ⢚ ώ [GREEK SMALL LETTER OMEGA WITH TONOS]
+glyph	\u1F00	(1       )	# ⠁ ἀ [GREEK SMALL LETTER ALPHA WITH PSILI]
+glyph	\u1F01	(1       )	# ⠁ ἁ [GREEK SMALL LETTER ALPHA WITH DASIA]
+glyph	\u1F02	(1       )	# ⠁ ἂ [GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA]
+glyph	\u1F03	(1       )	# ⠁ ἃ [GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA]
+glyph	\u1F04	(1      8)	# ⢁ ἄ [GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA]
+glyph	\u1F05	(1      8)	# ⢁ ἅ [GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA]
+glyph	\u1F06	(1       )	# ⠁ ἆ [GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI]
+glyph	\u1F07	(1       )	# ⠁ ἇ [GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI]
+glyph	\u1F08	(1     7 )	# ⡁ Ἀ [GREEK CAPITAL LETTER ALPHA WITH PSILI]
+glyph	\u1F09	(1     7 )	# ⡁ Ἁ [GREEK CAPITAL LETTER ALPHA WITH DASIA]
+glyph	\u1F0A	(1     7 )	# ⡁ Ἂ [GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA]
+glyph	\u1F0B	(1     7 )	# ⡁ Ἃ [GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA]
+glyph	\u1F0C	(1     78)	# ⣁ Ἄ [GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA]
+glyph	\u1F0D	(1     78)	# ⣁ Ἅ [GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA]
+glyph	\u1F0E	(1     7 )	# ⡁ Ἆ [GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI]
+glyph	\u1F0F	(1     7 )	# ⡁ Ἇ [GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI]
+glyph	\u1F10	(1   5   )	# ⠑ ἐ [GREEK SMALL LETTER EPSILON WITH PSILI]
+glyph	\u1F11	(1   5   )	# ⠑ ἑ [GREEK SMALL LETTER EPSILON WITH DASIA]
+glyph	\u1F12	(1   5   )	# ⠑ ἒ [GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA]
+glyph	\u1F13	(1   5   )	# ⠑ ἓ [GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA]
+glyph	\u1F14	(1   5  8)	# ⢑ ἔ [GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA]
+glyph	\u1F15	(1   5  8)	# ⢑ ἕ [GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA]
+glyph	\u1F18	(1   5 7 )	# ⡑ Ἐ [GREEK CAPITAL LETTER EPSILON WITH PSILI]
+glyph	\u1F19	(1   5 7 )	# ⡑ Ἑ [GREEK CAPITAL LETTER EPSILON WITH DASIA]
+glyph	\u1F1A	(1   5 7 )	# ⡑ Ἒ [GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA]
+glyph	\u1F1B	(1   5 7 )	# ⡑ Ἓ [GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA]
+glyph	\u1F1C	(1   5 78)	# ⣑ Ἔ [GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA]
+glyph	\u1F1D	(1   5 78)	# ⣑ Ἕ [GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA]
+glyph	\u1F20	(  345   )	# ⠜ ἠ [GREEK SMALL LETTER ETA WITH PSILI]
+glyph	\u1F21	(  345   )	# ⠜ ἡ [GREEK SMALL LETTER ETA WITH DASIA]
+glyph	\u1F22	(  345   )	# ⠜ ἢ [GREEK SMALL LETTER ETA WITH PSILI AND VARIA]
+glyph	\u1F23	(  345   )	# ⠜ ἣ [GREEK SMALL LETTER ETA WITH DASIA AND VARIA]
+glyph	\u1F24	(  345  8)	# ⢜ ἤ [GREEK SMALL LETTER ETA WITH PSILI AND OXIA]
+glyph	\u1F25	(  345  8)	# ⢜ ἥ [GREEK SMALL LETTER ETA WITH DASIA AND OXIA]
+glyph	\u1F26	(  345   )	# ⠜ ἦ [GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI]
+glyph	\u1F27	(  345   )	# ⠜ ἧ [GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI]
+glyph	\u1F28	(  345 7 )	# ⡜ Ἠ [GREEK CAPITAL LETTER ETA WITH PSILI]
+glyph	\u1F29	(  345 7 )	# ⡜ Ἡ [GREEK CAPITAL LETTER ETA WITH DASIA]
+glyph	\u1F2A	(  345 7 )	# ⡜ Ἢ [GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA]
+glyph	\u1F2B	(  345 7 )	# ⡜ Ἣ [GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA]
+glyph	\u1F2C	(  345 78)	# ⣜ Ἤ [GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA]
+glyph	\u1F2D	(  345 78)	# ⣜ Ἥ [GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA]
+glyph	\u1F2E	(  345 7 )	# ⡜ Ἦ [GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI]
+glyph	\u1F2F	(  345 7 )	# ⡜ Ἧ [GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI]
+glyph	\u1F30	( 2 4    )	# ⠊ ἰ [GREEK SMALL LETTER IOTA WITH PSILI]
+glyph	\u1F31	( 2 4    )	# ⠊ ἱ [GREEK SMALL LETTER IOTA WITH DASIA]
+glyph	\u1F32	( 2 4    )	# ⠊ ἲ [GREEK SMALL LETTER IOTA WITH PSILI AND VARIA]
+glyph	\u1F33	( 2 4    )	# ⠊ ἳ [GREEK SMALL LETTER IOTA WITH DASIA AND VARIA]
+glyph	\u1F34	( 2 4   8)	# ⢊ ἴ [GREEK SMALL LETTER IOTA WITH PSILI AND OXIA]
+glyph	\u1F35	( 2 4   8)	# ⢊ ἵ [GREEK SMALL LETTER IOTA WITH DASIA AND OXIA]
+glyph	\u1F36	( 2 4    )	# ⠊ ἶ [GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI]
+glyph	\u1F37	( 2 4    )	# ⠊ ἷ [GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI]
+glyph	\u1F38	( 2 4  7 )	# ⡊ Ἰ [GREEK CAPITAL LETTER IOTA WITH PSILI]
+glyph	\u1F39	( 2 4  7 )	# ⡊ Ἱ [GREEK CAPITAL LETTER IOTA WITH DASIA]
+glyph	\u1F3A	( 2 4  7 )	# ⡊ Ἲ [GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA]
+glyph	\u1F3B	( 2 4  7 )	# ⡊ Ἳ [GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA]
+glyph	\u1F3C	( 2 4  78)	# ⣊ Ἴ [GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA]
+glyph	\u1F3D	( 2 4  78)	# ⣊ Ἵ [GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA]
+glyph	\u1F3E	( 2 4  7 )	# ⡊ Ἶ [GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI]
+glyph	\u1F3F	( 2 4  7 )	# ⡊ Ἷ [GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI]
+glyph	\u1F40	(1 3 5   )	# ⠕ ὀ [GREEK SMALL LETTER OMICRON WITH PSILI]
+glyph	\u1F41	(1 3 5   )	# ⠕ ὁ [GREEK SMALL LETTER OMICRON WITH DASIA]
+glyph	\u1F42	(1 3 5   )	# ⠕ ὂ [GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA]
+glyph	\u1F43	(1 3 5   )	# ⠕ ὃ [GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA]
+glyph	\u1F44	(1 3 5  8)	# ⢕ ὄ [GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA]
+glyph	\u1F45	(1 3 5  8)	# ⢕ ὅ [GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA]
+glyph	\u1F48	(1 3 5 7 )	# ⡕ Ὀ [GREEK CAPITAL LETTER OMICRON WITH PSILI]
+glyph	\u1F49	(1 3 5 7 )	# ⡕ Ὁ [GREEK CAPITAL LETTER OMICRON WITH DASIA]
+glyph	\u1F4A	(1 3 5 7 )	# ⡕ Ὂ [GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA]
+glyph	\u1F4B	(1 3 5 7 )	# ⡕ Ὃ [GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA]
+glyph	\u1F4C	(1 3 5 78)	# ⣕ Ὄ [GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA]
+glyph	\u1F4D	(1 3 5 78)	# ⣕ Ὅ [GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA]
+glyph	\u1F50	(1 3456  )	# ⠽ ὐ [GREEK SMALL LETTER UPSILON WITH PSILI]
+glyph	\u1F51	(1 3456  )	# ⠽ ὑ [GREEK SMALL LETTER UPSILON WITH DASIA]
+glyph	\u1F52	(1 3456  )	# ⠽ ὒ [GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA]
+glyph	\u1F53	(1 3456  )	# ⠽ ὓ [GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA]
+glyph	\u1F54	(1 3456 8)	# ⢽ ὔ [GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA]
+glyph	\u1F55	(1 3456 8)	# ⢽ ὕ [GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA]
+glyph	\u1F56	(1 3456  )	# ⠽ ὖ [GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI]
+glyph	\u1F57	(1 3456  )	# ⠽ ὗ [GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI]
+glyph	\u1F59	(1 34567 )	# ⡽ Ὑ [GREEK CAPITAL LETTER UPSILON WITH DASIA]
+glyph	\u1F5B	(1 34567 )	# ⡽ Ὓ [GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA]
+glyph	\u1F5D	(1 345678)	# ⣽ Ὕ [GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA]
+glyph	\u1F5F	(1 34567 )	# ⡽ Ὗ [GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI]
+glyph	\u1F60	( 2 45   )	# ⠚ ὠ [GREEK SMALL LETTER OMEGA WITH PSILI]
+glyph	\u1F61	( 2 45   )	# ⠚ ὡ [GREEK SMALL LETTER OMEGA WITH DASIA]
+glyph	\u1F62	( 2 45   )	# ⠚ ὢ [GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA]
+glyph	\u1F63	( 2 45   )	# ⠚ ὣ [GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA]
+glyph	\u1F64	( 2 45  8)	# ⢚ ὤ [GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA]
+glyph	\u1F65	( 2 45  8)	# ⢚ ὥ [GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA]
+glyph	\u1F66	( 2 45   )	# ⠚ ὦ [GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI]
+glyph	\u1F67	( 2 45   )	# ⠚ ὧ [GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI]
+glyph	\u1F68	( 2 45 7 )	# ⡚ Ὠ [GREEK CAPITAL LETTER OMEGA WITH PSILI]
+glyph	\u1F69	( 2 45 7 )	# ⡚ Ὡ [GREEK CAPITAL LETTER OMEGA WITH DASIA]
+glyph	\u1F6A	( 2 45 7 )	# ⡚ Ὢ [GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA]
+glyph	\u1F6B	( 2 45 7 )	# ⡚ Ὣ [GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA]
+glyph	\u1F6C	( 2 45 78)	# ⣚ Ὤ [GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA]
+glyph	\u1F6D	( 2 45 78)	# ⣚ Ὥ [GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA]
+glyph	\u1F6E	( 2 45 7 )	# ⡚ Ὦ [GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI]
+glyph	\u1F6F	( 2 45 7 )	# ⡚ Ὧ [GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI]
+glyph	\u1F70	(1       )	# ⠁ ὰ [GREEK SMALL LETTER ALPHA WITH VARIA]
+glyph	\u1F71	(1      8)	# ⢁ ά [GREEK SMALL LETTER ALPHA WITH OXIA]
+glyph	\u1F72	(1   5   )	# ⠑ ὲ [GREEK SMALL LETTER EPSILON WITH VARIA]
+glyph	\u1F73	(1   5  8)	# ⢑ έ [GREEK SMALL LETTER EPSILON WITH OXIA]
+glyph	\u1F74	(  345   )	# ⠜ ὴ [GREEK SMALL LETTER ETA WITH VARIA]
+glyph	\u1F75	(  345  8)	# ⢜ ή [GREEK SMALL LETTER ETA WITH OXIA]
+glyph	\u1F76	( 2 4    )	# ⠊ ὶ [GREEK SMALL LETTER IOTA WITH VARIA]
+glyph	\u1F77	( 2 4   8)	# ⢊ ί [GREEK SMALL LETTER IOTA WITH OXIA]
+glyph	\u1F78	(1 3 5   )	# ⠕ ὸ [GREEK SMALL LETTER OMICRON WITH VARIA]
+glyph	\u1F79	(1 3 5  8)	# ⢕ ό [GREEK SMALL LETTER OMICRON WITH OXIA]
+glyph	\u1F7A	(1 3456  )	# ⠽ ὺ [GREEK SMALL LETTER UPSILON WITH VARIA]
+glyph	\u1F7B	(1 3456 8)	# ⢽ ύ [GREEK SMALL LETTER UPSILON WITH OXIA]
+glyph	\u1F7C	( 2 45   )	# ⠚ ὼ [GREEK SMALL LETTER OMEGA WITH VARIA]
+glyph	\u1F7D	( 2 45  8)	# ⢚ ώ [GREEK SMALL LETTER OMEGA WITH OXIA]
+glyph	\u1F80	(  3 5   )	# ⠔ ᾀ [GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI]
+glyph	\u1F81	(  3 5   )	# ⠔ ᾁ [GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI]
+glyph	\u1F82	(  3 5   )	# ⠔ ᾂ [GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1F83	(  3 5   )	# ⠔ ᾃ [GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1F84	(  3 5  8)	# ⢔ ᾄ [GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1F85	(  3 5  8)	# ⢔ ᾅ [GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1F86	(  3 5   )	# ⠔ ᾆ
+glyph	\u1F87	(  3 5   )	# ⠔ ᾇ
+glyph	\u1F88	(  3 5 7 )	# ⡔ ᾈ [GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI]
+glyph	\u1F89	(  3 5 7 )	# ⡔ ᾉ [GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI]
+glyph	\u1F8A	(1     7 )	# ⡁ ᾊ
+glyph	\u1F8B	(1     7 )	# ⡁ ᾋ
+glyph	\u1F8C	(1     78)	# ⣁ ᾌ
+glyph	\u1F8D	(1     78)	# ⣁ ᾍ
+glyph	\u1F8E	(1     7 )	# ⡁ ᾎ
+glyph	\u1F8F	(1     7 )	# ⡁ ᾏ
+glyph	\u1F90	(  3456  )	# ⠼ ᾐ [GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI]
+glyph	\u1F91	(  3456  )	# ⠼ ᾑ [GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI]
+glyph	\u1F92	(  3456  )	# ⠼ ᾒ [GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1F93	(  3456  )	# ⠼ ᾓ [GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1F94	(  3456 8)	# ⢼ ᾔ [GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1F95	(  3456 8)	# ⢼ ᾕ [GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1F96	(  3456  )	# ⠼ ᾖ
+glyph	\u1F97	(  3456  )	# ⠼ ᾗ
+glyph	\u1F98	(  34567 )	# ⡼ ᾘ [GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI]
+glyph	\u1F99	(  34567 )	# ⡼ ᾙ [GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI]
+glyph	\u1F9A	(  34567 )	# ⡼ ᾚ [GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI]
+glyph	\u1F9B	(  34567 )	# ⡼ ᾛ [GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI]
+glyph	\u1F9C	(  345678)	# ⣼ ᾜ [GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI]
+glyph	\u1F9D	(  345678)	# ⣼ ᾝ [GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI]
+glyph	\u1F9E	(  345 7 )	# ⡜ ᾞ
+glyph	\u1F9F	(  345 7 )	# ⡜ ᾟ
+glyph	\u1FA0	( 2 456  )	# ⠺ ᾠ [GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI]
+glyph	\u1FA1	( 2 456  )	# ⠺ ᾡ [GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI]
+glyph	\u1FA2	( 2 456  )	# ⠺ ᾢ [GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1FA3	( 2 456  )	# ⠺ ᾣ [GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI]
+glyph	\u1FA4	( 2 456 8)	# ⢺ ᾤ [GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1FA5	( 2 456 8)	# ⢺ ᾥ [GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI]
+glyph	\u1FA6	( 2 456  )	# ⠺ ᾦ
+glyph	\u1FA7	( 2 456  )	# ⠺ ᾧ
+glyph	\u1FA8	( 2 4567 )	# ⡺ ᾨ [GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI]
+glyph	\u1FA9	( 2 4567 )	# ⡺ ᾩ [GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI]
+glyph	\u1FAA	( 2 45 7 )	# ⡚ ᾪ
+glyph	\u1FAB	( 2 45 7 )	# ⡚ ᾫ
+glyph	\u1FAC	( 2 45 78)	# ⣚ ᾬ
+glyph	\u1FAD	( 2 45 78)	# ⣚ ᾭ
+glyph	\u1FAE	( 2 45 7 )	# ⡚ ᾮ
+glyph	\u1FAF	( 2 45 7 )	# ⡚ ᾯ
+glyph	\u1FB0	(1       )	# ⠁ ᾰ [GREEK SMALL LETTER ALPHA WITH VRACHY]
+glyph	\u1FB1	(1       )	# ⠁ ᾱ [GREEK SMALL LETTER ALPHA WITH MACRON]
+glyph	\u1FB2	(  3 5   )	# ⠔ ᾲ [GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI]
+char	\u1FB3	(  3 5   )	# ⠔ ᾳ [GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI]
+glyph	\u1FB4	(  3 5  8)	# ⢔ ᾴ [GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI]
+glyph	\u1FB6	(1       )	# ⠁ ᾶ [GREEK SMALL LETTER ALPHA WITH PERISPOMENI]
+glyph	\u1FB7	(  3 5   )	# ⠔ ᾷ [GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI]
+glyph	\u1FB8	(1     7 )	# ⡁ Ᾰ [GREEK CAPITAL LETTER ALPHA WITH VRACHY]
+glyph	\u1FB9	(1     7 )	# ⡁ Ᾱ [GREEK CAPITAL LETTER ALPHA WITH MACRON]
+glyph	\u1FBA	(1     7 )	# ⡁ Ὰ [GREEK CAPITAL LETTER ALPHA WITH VARIA]
+glyph	\u1FBB	(1     78)	# ⣁ Ά [GREEK CAPITAL LETTER ALPHA WITH OXIA]
+char	\u1FBC	(  3 5 7 )	# ⡔ ᾼ [GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI]
+glyph	\u1FC0	(     6  )	# ⠠ ῀ [GREEK PERISPOMENI]
+glyph	\u1FC2	(  3456  )	# ⠼ ῂ [GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI]
+char	\u1FC3	(  3456  )	# ⠼ ῃ [GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI]
+glyph	\u1FC4	(  3456 8)	# ⢼ ῄ [GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI]
+glyph	\u1FC6	(  345   )	# ⠜ ῆ [GREEK SMALL LETTER ETA WITH PERISPOMENI]
+glyph	\u1FC7	(  3456  )	# ⠼ ῇ [GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI]
+glyph	\u1FC8	(1   5 7 )	# ⡑ Ὲ [GREEK CAPITAL LETTER EPSILON WITH VARIA]
+glyph	\u1FC9	(1   5 78)	# ⣑ Έ [GREEK CAPITAL LETTER EPSILON WITH OXIA]
+glyph	\u1FCA	(  345 7 )	# ⡜ Ὴ [GREEK CAPITAL LETTER ETA WITH VARIA]
+glyph	\u1FCB	(  345 78)	# ⣜ Ή [GREEK CAPITAL LETTER ETA WITH OXIA]
+char	\u1FCC	(  34567 )	# ⡼ ῌ [GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI]
+glyph	\u1FCD	( 23456  )	# ⠾ ῍ [GREEK PSILI AND VARIA]
+glyph	\u1FCE	(  3 56  )	# ⠴ ῎ [GREEK PSILI AND OXIA]
+glyph	\u1FCF	( 2  56  )	# ⠲ ῏ [GREEK PSILI AND PERISPOMENI]
+glyph	\u1FD0	( 2 4    )	# ⠊ ῐ [GREEK SMALL LETTER IOTA WITH VRACHY]
+glyph	\u1FD1	( 2 4    )	# ⠊ ῑ [GREEK SMALL LETTER IOTA WITH MACRON]
+glyph	\u1FD2	( 2 4    )	# ⠊ ῒ [GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA]
+glyph	\u1FD3	( 2 4   8)	# ⢊ ΐ [GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA]
+glyph	\u1FD6	( 2 4    )	# ⠊ ῖ [GREEK SMALL LETTER IOTA WITH PERISPOMENI]
+glyph	\u1FD7	( 2 4    )	# ⠊ ῗ [GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI]
+glyph	\u1FD8	( 2 4  7 )	# ⡊ Ῐ [GREEK CAPITAL LETTER IOTA WITH VRACHY]
+glyph	\u1FD9	( 2 4  7 )	# ⡊ Ῑ [GREEK CAPITAL LETTER IOTA WITH MACRON]
+glyph	\u1FDA	( 2 4  7 )	# ⡊ Ὶ [GREEK CAPITAL LETTER IOTA WITH VARIA]
+glyph	\u1FDB	( 2 4  78)	# ⣊ Ί [GREEK CAPITAL LETTER IOTA WITH OXIA]
+glyph	\u1FDD	(123 56  )	# ⠷ ῝ [GREEK DASIA AND VARIA]
+glyph	\u1FDE	( 2   6  )	# ⠢ ῞ [GREEK DASIA AND OXIA]
+glyph	\u1FDF	( 23 5   )	# ⠖ ῟ [GREEK DASIA AND PERISPOMENI]
+glyph	\u1FE0	(1 3456  )	# ⠽ ῠ [GREEK SMALL LETTER UPSILON WITH VRACHY]
+glyph	\u1FE1	(1 3456  )	# ⠽ ῡ [GREEK SMALL LETTER UPSILON WITH MACRON]
+glyph	\u1FE2	(1 3456  )	# ⠽ ῢ [GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA]
+glyph	\u1FE3	(1 3456 8)	# ⢽ ΰ [GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA]
+glyph	\u1FE4	(123 5   )	# ⠗ ῤ [GREEK SMALL LETTER RHO WITH PSILI]
+glyph	\u1FE5	(123 5   )	# ⠗ ῥ [GREEK SMALL LETTER RHO WITH DASIA]
+glyph	\u1FE6	(1 3456  )	# ⠽ ῦ [GREEK SMALL LETTER UPSILON WITH PERISPOMENI]
+glyph	\u1FE7	(1 3456  )	# ⠽ ῧ [GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI]
+glyph	\u1FE8	(1 34567 )	# ⡽ Ῠ [GREEK CAPITAL LETTER UPSILON WITH VRACHY]
+glyph	\u1FE9	(1 34567 )	# ⡽ Ῡ [GREEK CAPITAL LETTER UPSILON WITH MACRON]
+glyph	\u1FEA	(1 34567 )	# ⡽ Ὺ [GREEK CAPITAL LETTER UPSILON WITH VARIA]
+glyph	\u1FEB	(1 345678)	# ⣽ Ύ [GREEK CAPITAL LETTER UPSILON WITH OXIA]
+glyph	\u1FEC	(123 5 7 )	# ⡗ Ῥ [GREEK CAPITAL LETTER RHO WITH DASIA]
+glyph	\u1FEF	(   4    )	# ⠈ ` [GREEK VARIA]
+glyph	\u1FF2	( 2 456  )	# ⠺ ῲ [GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI]
+char	\u1FF3	( 2 456  )	# ⠺ ῳ [GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI]
+glyph	\u1FF4	( 2 456 8)	# ⢺ ῴ [GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI]
+glyph	\u1FF6	( 2 45   )	# ⠚ ῶ [GREEK SMALL LETTER OMEGA WITH PERISPOMENI]
+glyph	\u1FF7	( 2 456  )	# ⠺ ῷ [GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI]
+glyph	\u1FF8	(1 3 5 7 )	# ⡕ Ὸ [GREEK CAPITAL LETTER OMICRON WITH VARIA]
+glyph	\u1FF9	(1 3 5 78)	# ⣕ Ό [GREEK CAPITAL LETTER OMICRON WITH OXIA]
+glyph	\u1FFA	( 2 45 7 )	# ⡚ Ὼ [GREEK CAPITAL LETTER OMEGA WITH VARIA]
+glyph	\u1FFB	( 2 45 78)	# ⣚ Ώ [GREEK CAPITAL LETTER OMEGA WITH OXIA]
+char	\u1FFC	( 2 4567 )	# ⡺ ῼ [GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI]
+glyph	\u1FFD	(    5   )	# ⠐ ´ [GREEK OXIA]
+glyph	\u1FFE	(123  6  )	# ⠧ ῾ [GREEK DASIA]
diff --git a/Tables/Text/gu.ttb b/Tables/Text/gu.ttb
new file mode 100644
index 0000000..4c390fc
--- /dev/null
+++ b/Tables/Text/gu.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Gujarati
+
+include gujarati.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/gujarati.tti b/Tables/Text/gujarati.tti
new file mode 100644
index 0000000..0ea8b63
--- /dev/null
+++ b/Tables/Text/gujarati.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Gujarati script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0A81	(  3     )  # ⠄ ઁ [GUJARATI SIGN CANDRABINDU]
+char \u0A82	(    56  )  # ⠰ ં [GUJARATI SIGN ANUSVARA]
+char \u0A83	(     6  )  # ⠠ ઃ [GUJARATI SIGN VISARGA]
+char \u0A85	(1       )  # ⠁ અ [GUJARATI LETTER A]
+char \u0A86	(  345   )  # ⠜ આ [GUJARATI LETTER AA]
+char \u0A87	( 2 4    )  # ⠊ ઇ [GUJARATI LETTER I]
+char \u0A88	(  3 5   )  # ⠔ ઈ [GUJARATI LETTER II]
+char \u0A89	(1 3  6  )  # ⠥ ઉ [GUJARATI LETTER U]
+char \u0A8A	(12  56  )  # ⠳ ઊ [GUJARATI LETTER UU]
+char \u0A8D	(1   5 7 )  # ⡑ ઍ [GUJARATI VOWEL CANDRA E]
+
+char \u0A8F	(1   5   )  # ⠑ એ [GUJARATI LETTER E]
+char \u0A90	(  34    )  # ⠌ ઐ [GUJARATI LETTER AI]
+char \u0A91	(1 3 5 7 )  # ⡕ ઑ [GUJARATI VOWEL CANDRA O]
+
+char \u0A93	(1 3 5   )  # ⠕ ઓ [GUJARATI LETTER O]
+char \u0A94	( 2 4 6  )  # ⠪ ઔ [GUJARATI LETTER AU]
+char \u0A95	(1 3     )  # ⠅ ક [GUJARATI LETTER KA]
+char \u0A96	(   4 6  )  # ⠨ ખ [GUJARATI LETTER KHA]
+char \u0A97	(1234    )  # ⠏ ગ [GUJARATI LETTER GA]
+char \u0A98	(12   6  )  # ⠣ ઘ [GUJARATI LETTER GHA]
+char \u0A99	(  34 6  )  # ⠬ ઙ [GUJARATI LETTER NGA]
+char \u0A9A	(1  4    )  # ⠉ ચ [GUJARATI LETTER CA]
+char \u0A9B	(1    6  )  # ⠡ છ [GUJARATI LETTER CHA]
+char \u0A9C	( 2 45   )  # ⠚ જ [GUJARATI LETTER JA]
+char \u0A9D	(  3 56  )  # ⠴ ઝ [GUJARATI LETTER JHA]
+char \u0A9E	( 2  5   )  # ⠒ ઞ [GUJARATI LETTER NYA]
+char \u0A9F	( 23456  )  # ⠾ ટ [GUJARATI LETTER TTA]
+char \u0AA0	( 2 456  )  # ⠺ ઠ [GUJARATI LETTER TTHA]
+char \u0AA1	(12 4 6  )  # ⠫ ડ [GUJARATI LETTER DDA]
+char \u0AA2	(123456  )  # ⠿ ઢ [GUJARATI LETTER DDHA]
+char \u0AA3	(  3456  )  # ⠼ ણ [GUJARATI LETTER NNA]
+char \u0AA4	( 2345   )  # ⠞ ત [GUJARATI LETTER TA]
+char \u0AA5	(1  456  )  # ⠹ થ [GUJARATI LETTER THA]
+char \u0AA6	(1  45   )  # ⠙ દ [GUJARATI LETTER DA]
+char \u0AA7	( 234 6  )  # ⠮ ધ [GUJARATI LETTER DHA]
+char \u0AA8	(1 345   )  # ⠝ ન [GUJARATI LETTER NA]
+
+char \u0AAA	(1234    )  # ⠏ પ [GUJARATI LETTER PA]
+char \u0AAB	( 23 5   )  # ⠖ ફ [GUJARATI LETTER PHA]
+char \u0AAC	(12      )  # ⠃ બ [GUJARATI LETTER BA]
+char \u0AAD	(   45   )  # ⠘ ભ [GUJARATI LETTER BHA]
+char \u0AAE	(1 34    )  # ⠍ મ [GUJARATI LETTER MA]
+char \u0AAF	(1 3456  )  # ⠽ ય [GUJARATI LETTER YA]
+char \u0AB0	(123 5   )  # ⠗ ર [GUJARATI LETTER RA]
+
+char \u0AB2	(123     )  # ⠇ લ [GUJARATI LETTER LA]
+char \u0AB3	(123   7 )  # ⡇ ળ [GUJARATI LETTER LLA]
+
+char \u0AB5	(123  6  )  # ⠧ વ [GUJARATI LETTER VA]
+char \u0AB6	(1  4 6  )  # ⠩ શ [GUJARATI LETTER SHA]
+char \u0AB7	(1234 6  )  # ⠯ ષ [GUJARATI LETTER SSA]
+char \u0AB8	( 234    )  # ⠎ સ [GUJARATI LETTER SA]
+char \u0AB9	(12  5   )  # ⠓ હ [GUJARATI LETTER HA]
+char \u0ABD	( 2      )  # ⠂ ઽ [GUJARATI SIGN AVAGRAHA]
+char \u0ABE	(  345   )  # ⠜ ા [GUJARATI VOWEL SIGN AA]
+char \u0ABF	( 2 4    )  # ⠊ િ [GUJARATI VOWEL SIGN I]
+char \u0AC0	(  3 5   )  # ⠔ ી [GUJARATI VOWEL SIGN II]
+char \u0AC1	(1 3  6  )  # ⠥ ુ [GUJARATI VOWEL SIGN U]
+char \u0AC2	(12  56  )  # ⠳ ૂ [GUJARATI VOWEL SIGN UU]
+char \u0AC5	(1   5 7 )  # ⡑ ૅ [GUJARATI VOWEL SIGN CANDRA E]
+
+char \u0AC7	(1   5   )  # ⠑ ે [GUJARATI VOWEL SIGN E]
+char \u0AC8	(  34    )  # ⠌ ૈ [GUJARATI VOWEL SIGN AI]
+char \u0AC9	(1 3 5 7 )  # ⡕ ૉ [GUJARATI VOWEL SIGN CANDRA O]
+
+char \u0ACB	(1 3 5   )  # ⠕ ો [GUJARATI VOWEL SIGN O]
+char \u0ACC	( 2 4 6  )  # ⠪ ૌ [GUJARATI VOWEL SIGN AU]
+char \u0ACD	(   4    )  # ⠈ ્ [GUJARATI SIGN VIRAMA]
+
+char \u0AE6	( 2 45   )  # ⠚ ૦ [GUJARATI DIGIT ZERO]
+char \u0AE7	(1       )  # ⠁ ૧ [GUJARATI DIGIT ONE]
+char \u0AE8	(12      )  # ⠃ ૨ [GUJARATI DIGIT TWO]
+char \u0AE9	(1  4    )  # ⠉ ૩ [GUJARATI DIGIT THREE]
+char \u0AEA	(1  45   )  # ⠙ ૪ [GUJARATI DIGIT FOUR]
+char \u0AEB	(1   5   )  # ⠑ ૫ [GUJARATI DIGIT FIVE]
+char \u0AEC	(12 4    )  # ⠋ ૬ [GUJARATI DIGIT SIX]
+char \u0AED	(12 45   )  # ⠛ ૭ [GUJARATI DIGIT SEVEN]
+char \u0AEE	(12  5   )  # ⠓ ૮ [GUJARATI DIGIT EIGHT]
+char \u0AEF	( 2 4    )  # ⠊ ૯ [GUJARATI DIGIT NINE]
diff --git a/Tables/Text/gurmukhi.tti b/Tables/Text/gurmukhi.tti
new file mode 100644
index 0000000..a807716
--- /dev/null
+++ b/Tables/Text/gurmukhi.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Gurmukhi script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0A01	(  3     )  # ⠄ ਁ [GURMUKHI SIGN ADAK BINDI]
+char \u0A02	(    56  )  # ⠰ ਂ [GURMUKHI SIGN BINDI]
+char \u0A03	(     6  )  # ⠠ ਃ [GURMUKHI SIGN VISARGA]
+char \u0A05	(1       )  # ⠁ ਅ [GURMUKHI LETTER A]
+char \u0A06	(  345   )  # ⠜ ਆ [GURMUKHI LETTER AA]
+char \u0A07	( 2 4    )  # ⠊ ਇ [GURMUKHI LETTER I]
+char \u0A08	(  3 5   )  # ⠔ ਈ [GURMUKHI LETTER II]
+char \u0A09	(1 3  6  )  # ⠥ ਉ [GURMUKHI LETTER U]
+char \u0A0A	(12  56  )  # ⠳ ਊ [GURMUKHI LETTER UU]
+
+
+char \u0A0F	(1   5   )  # ⠑ ਏ [GURMUKHI LETTER EE]
+char \u0A10	(  34    )  # ⠌ ਐ [GURMUKHI LETTER AI]
+
+
+char \u0A13	(1 3 5   )  # ⠕ ਓ [GURMUKHI LETTER OO]
+char \u0A14	( 2 4 6  )  # ⠪ ਔ [GURMUKHI LETTER AU]
+char \u0A15	(1 3     )  # ⠅ ਕ [GURMUKHI LETTER KA]
+char \u0A16	(   4 6  )  # ⠨ ਖ [GURMUKHI LETTER KHA]
+char \u0A17	(1234    )  # ⠏ ਗ [GURMUKHI LETTER GA]
+char \u0A18	(12   6  )  # ⠣ ਘ [GURMUKHI LETTER GHA]
+char \u0A19	(  34 6  )  # ⠬ ਙ [GURMUKHI LETTER NGA]
+char \u0A1A	(1  4    )  # ⠉ ਚ [GURMUKHI LETTER CA]
+char \u0A1B	(1    6  )  # ⠡ ਛ [GURMUKHI LETTER CHA]
+char \u0A1C	( 2 45   )  # ⠚ ਜ [GURMUKHI LETTER JA]
+char \u0A1D	(  3 56  )  # ⠴ ਝ [GURMUKHI LETTER JHA]
+char \u0A1E	( 2  5   )  # ⠒ ਞ [GURMUKHI LETTER NYA]
+char \u0A1F	( 23456  )  # ⠾ ਟ [GURMUKHI LETTER TTA]
+char \u0A20	( 2 456  )  # ⠺ ਠ [GURMUKHI LETTER TTHA]
+char \u0A21	(12 4 6  )  # ⠫ ਡ [GURMUKHI LETTER DDA]
+char \u0A22	(123456  )  # ⠿ ਢ [GURMUKHI LETTER DDHA]
+char \u0A23	(  3456  )  # ⠼ ਣ [GURMUKHI LETTER NNA]
+char \u0A24	( 2345   )  # ⠞ ਤ [GURMUKHI LETTER TA]
+char \u0A25	(1  456  )  # ⠹ ਥ [GURMUKHI LETTER THA]
+char \u0A26	(1  45   )  # ⠙ ਦ [GURMUKHI LETTER DA]
+char \u0A27	( 234 6  )  # ⠮ ਧ [GURMUKHI LETTER DHA]
+char \u0A28	(1 345   )  # ⠝ ਨ [GURMUKHI LETTER NA]
+
+char \u0A2A	(1234    )  # ⠏ ਪ [GURMUKHI LETTER PA]
+char \u0A2B	( 23 5   )  # ⠖ ਫ [GURMUKHI LETTER PHA]
+char \u0A2C	(12      )  # ⠃ ਬ [GURMUKHI LETTER BA]
+char \u0A2D	(   45   )  # ⠘ ਭ [GURMUKHI LETTER BHA]
+char \u0A2E	(1 34    )  # ⠍ ਮ [GURMUKHI LETTER MA]
+char \u0A2F	(1 3456  )  # ⠽ ਯ [GURMUKHI LETTER YA]
+char \u0A30	(123 5   )  # ⠗ ਰ [GURMUKHI LETTER RA]
+
+char \u0A32	(123     )  # ⠇ ਲ [GURMUKHI LETTER LA]
+char \u0A33	(123   7 )  # ⡇ ਲ਼ [GURMUKHI LETTER LLA]
+
+char \u0A35	(123  6  )  # ⠧ ਵ [GURMUKHI LETTER VA]
+char \u0A36	(1  4 6  )  # ⠩ ਸ਼ [GURMUKHI LETTER SHA]
+
+char \u0A38	( 234    )  # ⠎ ਸ [GURMUKHI LETTER SA]
+char \u0A39	(12  5   )  # ⠓ ਹ [GURMUKHI LETTER HA]
+
+char \u0A3E	(  345   )  # ⠜ ਾ [GURMUKHI VOWEL SIGN AA]
+char \u0A3F	( 2 4    )  # ⠊ ਿ [GURMUKHI VOWEL SIGN I]
+char \u0A40	(  3 5   )  # ⠔ ੀ [GURMUKHI VOWEL SIGN II]
+char \u0A41	(1 3  6  )  # ⠥ ੁ [GURMUKHI VOWEL SIGN U]
+char \u0A42	(12  56  )  # ⠳ ੂ [GURMUKHI VOWEL SIGN UU]
+
+
+char \u0A47	(1   5   )  # ⠑ ੇ [GURMUKHI VOWEL SIGN EE]
+char \u0A48	(  34    )  # ⠌ ੈ [GURMUKHI VOWEL SIGN AI]
+
+
+char \u0A4B	(1 3 5   )  # ⠕ ੋ [GURMUKHI VOWEL SIGN OO]
+char \u0A4C	( 2 4 6  )  # ⠪ ੌ [GURMUKHI VOWEL SIGN AU]
+char \u0A4D	(   4    )  # ⠈ ੍ [GURMUKHI SIGN VIRAMA]
+
+char \u0A66	( 2 45   )  # ⠚ ੦ [GURMUKHI DIGIT ZERO]
+char \u0A67	(1       )  # ⠁ ੧ [GURMUKHI DIGIT ONE]
+char \u0A68	(12      )  # ⠃ ੨ [GURMUKHI DIGIT TWO]
+char \u0A69	(1  4    )  # ⠉ ੩ [GURMUKHI DIGIT THREE]
+char \u0A6A	(1  45   )  # ⠙ ੪ [GURMUKHI DIGIT FOUR]
+char \u0A6B	(1   5   )  # ⠑ ੫ [GURMUKHI DIGIT FIVE]
+char \u0A6C	(12 4    )  # ⠋ ੬ [GURMUKHI DIGIT SIX]
+char \u0A6D	(12 45   )  # ⠛ ੭ [GURMUKHI DIGIT SEVEN]
+char \u0A6E	(12  5   )  # ⠓ ੮ [GURMUKHI DIGIT EIGHT]
+char \u0A6F	( 2 4    )  # ⠊ ੯ [GURMUKHI DIGIT NINE]
diff --git a/Tables/Text/he.ttb b/Tables/Text/he.ttb
new file mode 100644
index 0000000..ce3fcb4
--- /dev/null
+++ b/Tables/Text/he.ttb
@@ -0,0 +1,108 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Hebrew
+#
+# Adi Kushnir <adikushnir@gmail.com>
+
+include ltr-latin.tti
+include num-nemeth.tti
+
+char	\x21	( 23 5  8)  # ⢖ ! [EXCLAMATION MARK]
+char	\x22	(    5   )  # ⠐ " [QUOTATION MARK]
+char	\x23	(  3456  )  # ⠼ # [NUMBER SIGN]
+char	\x24	(12 4 6  )  # ⠫ $ [DOLLAR SIGN]
+char	\x25	(1  4 67 )  # ⡩ % [PERCENT SIGN]
+char	\x26	( 234 6  )  # ⠮ & [AMPERSAND]
+char	\x27	(  3     )  # ⠄ ' [APOSTROPHE]
+char	\x28	(123 56  )  # ⠷ ( [LEFT PARENTHESIS]
+char	\x29	( 23456  )  # ⠾ ) [RIGHT PARENTHESIS]
+char	\x2A	(1    6  )  # ⠡ * [ASTERISK]
+char	\x2B	(  34 6  )  # ⠬ + [PLUS SIGN]
+char	\x2C	(     6  )  # ⠠ , [COMMA]
+char	\x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char	\x2E	(   4 6  )  # ⠨ . [FULL STOP]
+char	\x2F	(  34    )  # ⠌ / [SOLIDUS]
+char	\x3A	(1   56  )  # ⠱ : [COLON]
+char	\x3B	(    56  )  # ⠰ ; [SEMICOLON]
+char	\x3C	(12   6  )  # ⠣ < [LESS-THAN SIGN]
+char	\x3D	(123456  )  # ⠿ = [EQUALS SIGN]
+char	\x3E	(  345   )  # ⠜ > [GREATER-THAN SIGN]
+char	\x3F	( 2   6 8)  # ⢢ ? [QUESTION MARK]
+char	\x40	(   4  7 )  # ⡈ @ [COMMERCIAL AT]
+char	\x5B	( 2 4 67 )  # ⡪ [ [LEFT SQUARE BRACKET]
+char	\x5C	(12  567 )  # ⡳ \ [REVERSE SOLIDUS]
+char	\x5D	(12 4567 )  # ⡻ ] [RIGHT SQUARE BRACKET]
+char	\x5E	(   45 7 )  # ⡘ ^ [CIRCUMFLEX ACCENT]
+char	\x5F	(   4567 )  # ⡸ _ [LOW LINE]
+char	\x60	(   4    )  # ⠈ ` [GRAVE ACCENT]
+glyph	\x7B	( 23  6  )  # ⠦ { [LEFT CURLY BRACKET]
+char	\x7C	(12  56  )  # ⠳ | [VERTICAL LINE]
+char	\x7D	(12 456  )  # ⠻ } [RIGHT CURLY BRACKET]
+char	\x7E	(   45   )  # ⠘ ~ [TILDE]
+
+glyph	\u05B1	( 2   6  )  # ⠢ ֱ [HEBREW POINT HATAF SEGOL]
+glyph	\u05B2	( 2  5   )  # ⠒ ֲ [HEBREW POINT HATAF PATAH]
+glyph	\u05B3	(  345   )  # ⠜ ֳ [HEBREW POINT HATAF QAMATS]
+glyph	\u05B4	( 2 4    )  # ⠊ ִ [HEBREW POINT HIRIQ]
+glyph	\u05B5	(  34    )  # ⠌ ֵ [HEBREW POINT TSERE]
+glyph	\u05B6	(1   5   )  # ⠑ ֶ [HEBREW POINT SEGOL]
+glyph	\u05B7	(1  4    )  # ⠉ ַ [HEBREW POINT PATAH]
+glyph	\u05B8	(12   6  )  # ⠣ ָ [HEBREW POINT QAMATS]
+glyph	\u05B9	(1 3 5   )  # ⠕ ֹ [HEBREW POINT HOLAM]
+glyph	\u05BB	(1 3  6  )  # ⠥ ֻ [HEBREW POINT QUBUTS]
+glyph	\u05D0	(1       )  # ⠁ א [HEBREW LETTER ALEF]
+glyph	\u05D1	(123  6  )  # ⠧ ב [HEBREW LETTER BET]
+glyph	\u05D2	(12 45   )  # ⠛ ג [HEBREW LETTER GIMEL]
+glyph	\u05D3	(1  45   )  # ⠙ ד [HEBREW LETTER DALET]
+glyph	\u05D4	(12  5   )  # ⠓ ה [HEBREW LETTER HE]
+glyph	\u05D5	( 2 456  )  # ⠺ ו [HEBREW LETTER VAV]
+glyph	\u05D6	(1 3 56  )  # ⠵ ז [HEBREW LETTER ZAYIN]
+glyph	\u05D7	(1 34 6  )  # ⠭ ח [HEBREW LETTER HET]
+glyph	\u05D8	( 2345   )  # ⠞ ט [HEBREW LETTER TET]
+glyph	\u05D9	( 2 45   )  # ⠚ י [HEBREW LETTER YOD]
+char	\u05DA	(1    6 8)  # ⢡ ך [HEBREW LETTER FINAL KAF]
+glyph	\u05DB	(1    6  )  # ⠡ כ [HEBREW LETTER KAF]
+glyph	\u05DC	(123     )  # ⠇ ל [HEBREW LETTER LAMED]
+char	\u05DD	(1 34   8)  # ⢍ ם [HEBREW LETTER FINAL MEM]
+glyph	\u05DE	(1 34    )  # ⠍ מ [HEBREW LETTER MEM]
+char	\u05DF	(1 345  8)  # ⢝ ן [HEBREW LETTER FINAL NUN]
+glyph	\u05E0	(1 345   )  # ⠝ נ [HEBREW LETTER NUN]
+glyph	\u05E1	( 234    )  # ⠎ ס [HEBREW LETTER SAMEKH]
+glyph	\u05E2	(12 4 6  )  # ⠫ ע [HEBREW LETTER AYIN]
+char	\u05E3	(12 4   8)  # ⢋ ף [HEBREW LETTER FINAL PE]
+glyph	\u05E4	(1234    )  # ⠏ פ [HEBREW LETTER PE]
+char	\u05E5	( 234 6 8)  # ⢮ ץ [HEBREW LETTER FINAL TSADI]
+glyph	\u05E6	( 234 6  )  # ⠮ צ [HEBREW LETTER TSADI]
+glyph	\u05E7	(12345   )  # ⠟ ק [HEBREW LETTER QOF]
+glyph	\u05E8	(123 5   )  # ⠗ ר [HEBREW LETTER RESH]
+char	\u05E9	(1  4 6  )  # ⠩ ש [HEBREW LETTER SHIN]
+char	\u05EA	(1  456  )  # ⠹ ת [HEBREW LETTER TAV]
+
+glyph	\uFB1D	(  3 5   )  # ⠔ יִ [HEBREW LETTER YOD WITH HIRIQ]
+glyph	\uFB2A	(1  4 6  )  # ⠩ שׁ [HEBREW LETTER SHIN WITH SHIN DOT]
+glyph	\uFB2B	(1   56  )  # ⠱ שׂ [HEBREW LETTER SHIN WITH SIN DOT]
+glyph	\uFB31	(12      )  # ⠃ בּ [HEBREW LETTER BET WITH DAGESH]
+glyph	\uFB35	(  34 6  )  # ⠬ וּ [HEBREW LETTER VAV WITH DAGESH]
+glyph	\uFB3A	(1 3     )  # ⠅ ךּ [HEBREW LETTER FINAL KAF WITH DAGESH]
+glyph	\uFB3B	(1 3     )  # ⠅ כּ [HEBREW LETTER KAF WITH DAGESH]
+glyph	\uFB43	(1234    )  # ⠏ ףּ [HEBREW LETTER FINAL PE WITH DAGESH]
+glyph	\uFB44	(1234    )  # ⠏ פּ [HEBREW LETTER PE WITH DAGESH]
+glyph	\uFB4A	(12  56  )  # ⠳ תּ [HEBREW LETTER TAV WITH DAGESH]
+
+include common.tti
diff --git a/Tables/Text/hi.ttb b/Tables/Text/hi.ttb
new file mode 100644
index 0000000..95f2f7d
--- /dev/null
+++ b/Tables/Text/hi.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Hindi
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/hr.ttb b/Tables/Text/hr.ttb
new file mode 100644
index 0000000..973c732
--- /dev/null
+++ b/Tables/Text/hr.ttb
@@ -0,0 +1,205 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Croatian
+#
+# Copyright (C) 2005 by Sebastien Sable, All rights reserved.
+
+# Table generated by Sébastien Sablé <sable@users.sourceforge.net> for
+# libbraille http://libbraille.org and gnome-braille
+# http://cvs.gnome.org/viewcvs/gnome-braille/
+#
+# Table adapted for BRLTTY by Samuel Thibault <samuel.thibault@ens-lyon.org>
+#
+# *Many thanks to Danko Butorac <danko at ipsis.hr> for his help*
+# Table based on "Croatian code page for computer 8-dot Braille
+# alphabet ANSI-1250" from the "Croatian Association of the Blind" at
+# http://www.savez-slijepih.hr/en/download.htm
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# generated by ttbtest: charset=iso-8859-2
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x24	( 234 6  )  # 24 ⠮ $ [DOLLAR SIGN]
+char \x25	(   4 6  )  # 25 ⠨ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	(12   6  )  # 28 ⠣ ( [LEFT PARENTHESIS]
+char \x29	(  345   )  # 29 ⠜ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+char \x30	(  34 6  )  # 30 ⠬ 0 [DIGIT ZERO]
+char \x31	(1    6  )  # 31 ⠡ 1 [DIGIT ONE]
+char \x32	(  34    )  # 32 ⠌ 2 [DIGIT TWO]
+char \x33	(1  4 6  )  # 33 ⠩ 3 [DIGIT THREE]
+char \x34	(1  456  )  # 34 ⠹ 4 [DIGIT FOUR]
+char \x35	(1   56  )  # 35 ⠱ 5 [DIGIT FIVE]
+char \x36	(12 4 6  )  # 36 ⠫ 6 [DIGIT SIX]
+char \x37	(12 456  )  # 37 ⠻ 7 [DIGIT SEVEN]
+char \x38	(12  56  )  # 38 ⠳ 8 [DIGIT EIGHT]
+char \x39	( 2 4 6  )  # 39 ⠪ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	(123456  )  # 3D ⠿ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4  7 )  # 40 ⡈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 567 )  # 5B ⡷ [ [LEFT SQUARE BRACKET]
+char \x5C	(   45 7 )  # 5C ⡘ \ [REVERSE SOLIDUS]
+char \x5D	( 234567 )  # 5D ⡾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(    567 )  # 5E ⡰ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # 5F ⡸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 56  )  # 7B ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(   45   )  # 7C ⠘ | [VERTICAL LINE]
+char \x7D	( 23456  )  # 7D ⠾ } [RIGHT CURLY BRACKET]
+char \x7E	(    56  )  # 7E ⠰ ~ [TILDE]
+char \u0104	(  345 78)  # A1 ⣜ Ą [LATIN CAPITAL LETTER A WITH OGONEK]
+char \u02D8	(   4 67 )  # A2 ⡨ ˘ [BREVE]
+char \u0141	(123  6 8)  # A3 ⢧ Ł [LATIN CAPITAL LETTER L WITH STROKE]
+char \xA4	(   4 678)  # A4 ⣨ ¤ [CURRENCY SIGN]
+char \u013D	(   456 8)  # A5 ⢸ Ľ [LATIN CAPITAL LETTER L WITH CARON]
+char \u015A	( 234   8)  # A6 ⢎ Ś [LATIN CAPITAL LETTER S WITH ACUTE]
+char \xA7	(  3 5 78)  # A7 ⣔ § [SECTION SIGN]
+char \xA8	(       8)  # A8 ⢀ ¨ [DIAERESIS]
+char \u0160	(1   567 )  # A9 ⡱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u015E	( 23456 8)  # AA ⢾ Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
+char \u0164	(12345  8)  # AB ⢟ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char \u0179	(1 3 56 8)  # AC ⢵ Ź [LATIN CAPITAL LETTER Z WITH ACUTE]
+char \xAD	(      78)  # AD ⣀ ­ [SOFT HYPHEN]
+char \u017D	( 234 67 )  # AE ⡮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u017B	(12   678)  # AF ⣣ Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+char \xB0	(    5 78)  # B0 ⣐ ° [DEGREE SIGN]
+char \u0105	(    567 )  # B1 ⡰ ą [LATIN SMALL LETTER A WITH OGONEK]
+char \u02DB	(    5  8)  # B2 ⢐ ˛ [OGONEK]
+char \u0142	( 23   78)  # B3 ⣆ ł [LATIN SMALL LETTER L WITH STROKE]
+char \xB4	(   4   8)  # B4 ⢈ ´ [ACUTE ACCENT]
+char \u013E	(12     8)  # B5 ⢃ ľ [LATIN SMALL LETTER L WITH CARON]
+char \u015B	(  3 5 7 )  # B6 ⡔ ś [LATIN SMALL LETTER S WITH ACUTE]
+char \u02C7	(   45  8)  # B7 ⢘ ˇ [CARON]
+char \xB8	(    56 8)  # B8 ⢰ ¸ [CEDILLA]
+char \u0161	(1   56 8)  # B9 ⢱ š [LATIN SMALL LETTER S WITH CARON]
+char \u015F	(  3 5678)  # BA ⣴ ş [LATIN SMALL LETTER S WITH CEDILLA]
+char \u0165	( 23 567 )  # BB ⡶ ť [LATIN SMALL LETTER T WITH CARON]
+char \u017A	( 2   678)  # BC ⣢ ź [LATIN SMALL LETTER Z WITH ACUTE]
+char \u02DD	(     6 8)  # BD ⢠ ˝ [DOUBLE ACUTE ACCENT]
+char \u017E	( 234 6 8)  # BE ⢮ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u017C	(1    678)  # BF ⣡ ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
+char \u0154	(123 5  8)  # C0 ⢗ Ŕ [LATIN CAPITAL LETTER R WITH ACUTE]
+char \xC1	(1 3    8)  # C1 ⢅ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(123 56 8)  # C2 ⢷ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \u0102	(1      8)  # C3 ⢁ Ă [LATIN CAPITAL LETTER A WITH BREVE]
+char \xC4	(  345 7 )  # C4 ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \u0139	(123    8)  # C5 ⢇ Ĺ [LATIN CAPITAL LETTER L WITH ACUTE]
+char \u0106	(1  4 67 )  # C6 ⡩ Ć [LATIN CAPITAL LETTER C WITH ACUTE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \u010C	(1    67 )  # C8 ⡡ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \u0118	(1   5678)  # CA ⣱ Ę [LATIN CAPITAL LETTER E WITH OGONEK]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \u011A	(12   67 )  # CC ⡣ Ě [LATIN CAPITAL LETTER E WITH CARON]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(12 4567 )  # CE ⡻ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \u010E	(1  45  8)  # CF ⢙ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char \u0110	(1  4567 )  # D0 ⡹ Đ [LATIN CAPITAL LETTER D WITH STROKE]
+char \u0143	(1 345  8)  # D1 ⢝ Ń [LATIN CAPITAL LETTER N WITH ACUTE]
+char \u0147	(12 4 678)  # D2 ⣫ Ň [LATIN CAPITAL LETTER N WITH CARON]
+char \xD3	(1 3 5  8)  # D3 ⢕ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  45678)  # D4 ⣹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \u0150	( 2 4 678)  # D5 ⣪ Ő [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # D7 ⢭ × [MULTIPLICATION SIGN]
+char \u0158	( 2 456 8)  # D8 ⢺ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char \u016E	(  34 67 )  # D9 ⡬ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char \xDA	(1 3  6 8)  # DA ⢥ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \u0170	(  34 678)  # DB ⣬ Ű [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1 3456 8)  # DD ⢽ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \u0162	( 2345  8)  # DE ⢞ Ţ [LATIN CAPITAL LETTER T WITH CEDILLA]
+char \xDF	( 234 678)  # DF ⣮ ß [LATIN SMALL LETTER SHARP S]
+char \u0155	( 23  67 )  # E0 ⡦ ŕ [LATIN SMALL LETTER R WITH ACUTE]
+char \xE1	( 2    7 )  # E1 ⡂ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	( 23  678)  # E2 ⣦ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \u0103	( 2     8)  # E3 ⢂ ă [LATIN SMALL LETTER A WITH BREVE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \u013A	( 23   7 )  # E5 ⡆ ĺ [LATIN SMALL LETTER L WITH ACUTE]
+char \u0107	(1  4 6 8)  # E6 ⢩ ć [LATIN SMALL LETTER C WITH ACUTE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \u010D	(1    6 8)  # E8 ⢡ č [LATIN SMALL LETTER C WITH CARON]
+char \xE9	(123456 8)  # E9 ⢿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \u0119	( 2   6 8)  # EA ⢢ ę [LATIN SMALL LETTER E WITH OGONEK]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \u011B	( 23    8)  # EC ⢆ ě [LATIN SMALL LETTER E WITH CARON]
+char \xED	(  34   8)  # ED ⢌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(12 456 8)  # EE ⢻ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \u010F	(  3  6 8)  # EF ⢤ ď [LATIN SMALL LETTER D WITH CARON]
+char \u0111	(1  456 8)  # F0 ⢹ đ [LATIN SMALL LETTER D WITH STROKE]
+char \u0144	( 2  567 )  # F1 ⡲ ń [LATIN SMALL LETTER N WITH ACUTE]
+char \u0148	( 23 5  8)  # F2 ⢖ ň [LATIN SMALL LETTER N WITH CARON]
+char \xF3	( 2   67 )  # F3 ⡢ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	( 2  56 8)  # F4 ⢲ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \u0151	(  3 5  8)  # F5 ⢔ ő [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  5 78)  # F7 ⣒ ÷ [DIVISION SIGN]
+char \u0159	(  3 56 8)  # F8 ⢴ ř [LATIN SMALL LETTER R WITH CARON]
+char \u016F	(  34 6 8)  # F9 ⢬ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char \xFA	( 2    78)  # FA ⣂ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \u0171	(1  4 678)  # FB ⣩ ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 2  5678)  # FD ⣲ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \u0163	(  3 567 )  # FE ⡴ ţ [LATIN SMALL LETTER T WITH CEDILLA]
+char \u02D9	(     67 )  # FF ⡠ ˙ [DOT ABOVE]
+
+char \xA6	(   4 6 8)  #    ⢨ ¦ [BROKEN BAR]
+char \xA9	(12 45  8)  #    ⢛ © [COPYRIGHT SIGN]
+char \xAB	(  3   78)  #    ⣄ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 23 5 7 )  #    ⡖ ¬ [NOT SIGN]
+char \xAE	(12 45678)  #    ⣻ ® [REGISTERED SIGN]
+char \xB1	(1234 678)  #    ⣯ ± [PLUS-MINUS SIGN]
+char \xB5	(  34567 )  #    ⡼ µ [MICRO SIGN]
+char \xB6	(1234   8)  #    ⢏ ¶ [PILCROW SIGN]
+char \xB7	(  3    8)  #    ⢄ · [MIDDLE DOT]
+char \xBB	(     678)  #    ⣠ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \u2013	(1  4   8)  #    ⢉ – [EN DASH]
+char \u2014	(  3  678)  #    ⣤ — [EM DASH]
+char \u2018	(12 4   8)  #    ⢋ ‘ [LEFT SINGLE QUOTATION MARK]
+char \u2019	(1   5  8)  #    ⢑ ’ [RIGHT SINGLE QUOTATION MARK]
+char \u201A	( 23 5678)  #    ⣶ ‚ [SINGLE LOW-9 QUOTATION MARK]
+char \u201C	(12  5  8)  #    ⢓ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	( 2 45  8)  #    ⢚ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u201E	( 23 56 8)  #    ⢶ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u2020	(  3  67 )  #    ⡤ † [DAGGER]
+char \u2021	( 23 5 78)  #    ⣖ ‡ [DOUBLE DAGGER]
+char \u2022	(12345678)  #    ⣿ • [BULLET]
+char \u2026	( 2 4   8)  #    ⢊ … [HORIZONTAL ELLIPSIS]
+char \u2030	(1 34   8)  #    ⢍ ‰ [PER MILLE SIGN]
+char \u2039	( 2  5 7 )  #    ⡒ ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+char \u203A	( 2  5  8)  #    ⢒ › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+char \u20AC	(12  5678)  #    ⣳ € [EURO SIGN]
+char \u2122	( 23  6 8)  #    ⢦ ™ [TRADE MARK SIGN]
+
+include common.tti
diff --git a/Tables/Text/hu.ttb b/Tables/Text/hu.ttb
new file mode 100644
index 0000000..a12521d
--- /dev/null
+++ b/Tables/Text/hu.ttb
@@ -0,0 +1,92 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Hungarian
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# Updated by Zsolt Torma <torma.zsolt@infoalap.hu>
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# generated by ttbtest: charset=latin2
+char \xC1	(   4  78)  # C1 ⣈ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC4	(   45 78)  # C4 ⣘ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC9	(1    678)  # C9 ⣡ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCD	(  34  78)  # CD ⣌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xD3	( 2 4 678)  # D3 ⣪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \u0150	(12 45678)  # D5 ⣻ Ő [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
+char \xD6	(12345 78)  # D6 ⣟ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xDA	(  34 678)  # DA ⣬ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \u0170	( 2345678)  # DB ⣾ Ű [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
+char \xDC	(123 5678)  # DC ⣷ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xE1	(   4   8)  # E1 ⢈ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE4	(   45  8)  # E4 ⢘ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE9	(1    6 8)  # E9 ⢡ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xED	(  34   8)  # ED ⢌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xF3	( 2 4 6 8)  # F3 ⢪ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \u0151	(12 456 8)  # F5 ⢻ ő [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
+char \xF6	(12345  8)  # F6 ⢟ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xFA	(  34 6 8)  # FA ⢬ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \u0171	( 23456 8)  # FB ⢾ ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
+char \xFC	(123 56 8)  # FC ⢷ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+#char \u025F	(1  456  )  #    ⠹ ɟ [LATIN SMALL LETTER DOTLESS J WITH STROKE]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 6 added
+include num-dot6.tti
+
+
+include common.tti
+char \x21	(    5   )  # ⠐ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # ⠈ " [QUOTATION MARK]
+char \x23	(  3456  )  # ⠼ # [NUMBER SIGN]
+char \x24	(   4 6  )  # ⠨ $ [DOLLAR SIGN]
+char \x25	(123456  )  # ⠿ % [PERCENT SIGN]
+char \x26	(1234 6  )  # ⠯ & [AMPERSAND]
+char \x27	(     6  )  # ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # ⠂ , [COMMA]
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # ⠲ / [SOLIDUS]
+char \x3A	( 2  5   )  # ⠒ : [COLON]
+char \x3B	( 23     )  # ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # ⠶ = [EQUALS SIGN]
+char \x3E	(   45   )  # ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+char \x40	(  345 7 )  # ⡜ @ [COMMERCIAL AT]
+char \x5B	(123 567 )  # ⡷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # ⡌ \ [REVERSE SOLIDUS]
+char \x5D	( 234567 )  # ⡾ ] [RIGHT SQUARE BRACKET]
+char \x5E	( 234 67 )  # ⡮ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # ⡸ _ [LOW LINE]
+char \x60	(  345   )  # ⠜ ` [GRAVE ACCENT]
+char \x7B	(123 56  )  # ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(  34    )  # ⠌ | [VERTICAL LINE]
+char \x7D	( 23456  )  # ⠾ } [RIGHT CURLY BRACKET]
+char \x7E	( 234 6  )  # ⠮ ~ [TILDE]
+char \x7F	(   456  )  # ⠸   [DELETE]
diff --git a/Tables/Text/hy.ttb b/Tables/Text/hy.ttb
new file mode 100644
index 0000000..08d90ff
--- /dev/null
+++ b/Tables/Text/hy.ttb
@@ -0,0 +1,115 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Armenian
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# lowercase letters
+char \u0561	(1       )  # ⠁ ա [ARMENIAN SMALL LETTER AYB]
+char \u0562	(1234    )  # ⠏ բ [ARMENIAN SMALL LETTER BEN]
+char \u0563	(12345   )  # ⠟ գ [ARMENIAN SMALL LETTER GIM]
+char \u0564	(1  456  )  # ⠹ դ [ARMENIAN SMALL LETTER DA]
+char \u0565	(1 3456  )  # ⠽ ե [ARMENIAN SMALL LETTER ECH]
+char \u0566	(1 3 56  )  # ⠵ զ [ARMENIAN SMALL LETTER ZA]
+char \u0567	(1   5   )  # ⠑ է [ARMENIAN SMALL LETTER EH]
+char \u0568	(1 3  6  )  # ⠥ ը [ARMENIAN SMALL LETTER ET]
+char \u0569	( 2345   )  # ⠞ թ [ARMENIAN SMALL LETTER TO]
+char \u056A	(  345   )  # ⠜ ժ [ARMENIAN SMALL LETTER ZHE]
+char \u056B	( 2 4    )  # ⠊ ի [ARMENIAN SMALL LETTER INI]
+char \u056C	(123     )  # ⠇ լ [ARMENIAN SMALL LETTER LIWN]
+char \u056D	(1 34 6  )  # ⠭ խ [ARMENIAN SMALL LETTER XEH]
+char \u056E	(  34    )  # ⠌ ծ [ARMENIAN SMALL LETTER CA]
+char \u056F	(12 45   )  # ⠛ կ [ARMENIAN SMALL LETTER KEN]
+char \u0570	(12  5   )  # ⠓ հ [ARMENIAN SMALL LETTER HO]
+char \u0571	(  34 6  )  # ⠬ ձ [ARMENIAN SMALL LETTER JA]
+char \u0572	(12   6  )  # ⠣ ղ [ARMENIAN SMALL LETTER GHAD]
+char \u0573	( 2 45   )  # ⠚ ճ [ARMENIAN SMALL LETTER CHEH]
+char \u0574	(1 34    )  # ⠍ մ [ARMENIAN SMALL LETTER MEN]
+char \u0575	(1   56  )  # ⠱ յ [ARMENIAN SMALL LETTER YI]
+char \u0576	(1 345   )  # ⠝ ն [ARMENIAN SMALL LETTER NOW]
+char \u0577	(1  4 6  )  # ⠩ շ [ARMENIAN SMALL LETTER SHA]
+char \u0578	( 2 4 6  )  # ⠪ ո [ARMENIAN SMALL LETTER VO]
+char \u0579	(1    6  )  # ⠡ չ [ARMENIAN SMALL LETTER CHA]
+char \u057A	(12      )  # ⠃ պ [ARMENIAN SMALL LETTER PEH]
+char \u057B	(1  4    )  # ⠉ ջ [ARMENIAN SMALL LETTER JHEH]
+char \u057C	(12 456  )  # ⠻ ռ [ARMENIAN SMALL LETTER RA]
+char \u057D	( 234    )  # ⠎ ս [ARMENIAN SMALL LETTER SEH]
+char \u057E	(123  6  )  # ⠧ վ [ARMENIAN SMALL LETTER VEW]
+char \u057F	(1  45   )  # ⠙ տ [ARMENIAN SMALL LETTER TIWN]
+char \u0580	(123 5   )  # ⠗ ր [ARMENIAN SMALL LETTER REH]
+char \u0581	( 234 6  )  # ⠮ ց [ARMENIAN SMALL LETTER CO]
+char \u0582	( 2 456  )  # ⠺ ւ [ARMENIAN SMALL LETTER YIWN]
+char \u0583	(12 4 6  )  # ⠫ փ [ARMENIAN SMALL LETTER PIWR]
+char \u0584	(1 3     )  # ⠅ ք [ARMENIAN SMALL LETTER KEH]
+char \u0585	(1 3 5   )  # ⠕ օ [ARMENIAN SMALL LETTER OH]
+char \u0586	(12 4    )  # ⠋ ֆ [ARMENIAN SMALL LETTER FEH]
+char \u0587	(1234 6  )  # ⠯ և [ARMENIAN SMALL LIGATURE ECH YIWN]
+
+# uppercase letters
+char \u0531	(1     7 )  # ⡁ Ա [ARMENIAN CAPITAL LETTER AYB]
+char \u0532	(1234  7 )  # ⡏ Բ [ARMENIAN CAPITAL LETTER BEN]
+char \u0533	(12345 7 )  # ⡟ Գ [ARMENIAN CAPITAL LETTER GIM]
+char \u0534	(1  4567 )  # ⡹ Դ [ARMENIAN CAPITAL LETTER DA]
+char \u0535	(1 34567 )  # ⡽ Ե [ARMENIAN CAPITAL LETTER ECH]
+char \u0536	(1 3 567 )  # ⡵ Զ [ARMENIAN CAPITAL LETTER ZA]
+char \u0537	(1   5 7 )  # ⡑ Է [ARMENIAN CAPITAL LETTER EH]
+char \u0538	(1 3  67 )  # ⡥ Ը [ARMENIAN CAPITAL LETTER ET]
+char \u0539	( 2345 7 )  # ⡞ Թ [ARMENIAN CAPITAL LETTER TO]
+char \u053A	(  345 7 )  # ⡜ Ժ [ARMENIAN CAPITAL LETTER ZHE]
+char \u053B	( 2 4  7 )  # ⡊ Ի [ARMENIAN CAPITAL LETTER INI]
+char \u053C	(123   7 )  # ⡇ Լ [ARMENIAN CAPITAL LETTER LIWN]
+char \u053D	(1 34 67 )  # ⡭ Խ [ARMENIAN CAPITAL LETTER XEH]
+char \u053E	(  34  7 )  # ⡌ Ծ [ARMENIAN CAPITAL LETTER CA]
+char \u053F	(12 45 7 )  # ⡛ Կ [ARMENIAN CAPITAL LETTER KEN]
+char \u0540	(12  5 7 )  # ⡓ Հ [ARMENIAN CAPITAL LETTER HO]
+char \u0541	(  34 67 )  # ⡬ Ձ [ARMENIAN CAPITAL LETTER JA]
+char \u0542	(12   67 )  # ⡣ Ղ [ARMENIAN CAPITAL LETTER GHAD]
+char \u0543	( 2 45 7 )  # ⡚ Ճ [ARMENIAN CAPITAL LETTER CHEH]
+char \u0544	(1 34  7 )  # ⡍ Մ [ARMENIAN CAPITAL LETTER MEN]
+char \u0545	(1   567 )  # ⡱ Յ [ARMENIAN CAPITAL LETTER YI]
+char \u0546	(1 345 7 )  # ⡝ Ն [ARMENIAN CAPITAL LETTER NOW]
+char \u0547	(1  4 67 )  # ⡩ Շ [ARMENIAN CAPITAL LETTER SHA]
+char \u0548	( 2 4 67 )  # ⡪ Ո [ARMENIAN CAPITAL LETTER VO]
+char \u0549	(1    67 )  # ⡡ Չ [ARMENIAN CAPITAL LETTER CHA]
+char \u054A	(12    7 )  # ⡃ Պ [ARMENIAN CAPITAL LETTER PEH]
+char \u054B	(1  4  7 )  # ⡉ Ջ [ARMENIAN CAPITAL LETTER JHEH]
+char \u054C	(12 4567 )  # ⡻ Ռ [ARMENIAN CAPITAL LETTER RA]
+char \u054D	( 234  7 )  # ⡎ Ս [ARMENIAN CAPITAL LETTER SEH]
+char \u054E	(123  67 )  # ⡧ Վ [ARMENIAN CAPITAL LETTER VEW]
+char \u054F	(1  45 7 )  # ⡙ Տ [ARMENIAN CAPITAL LETTER TIWN]
+char \u0550	(123 5 7 )  # ⡗ Ր [ARMENIAN CAPITAL LETTER REH]
+char \u0551	( 234 67 )  # ⡮ Ց [ARMENIAN CAPITAL LETTER CO]
+char \u0552	( 2 4567 )  # ⡺ Ւ [ARMENIAN CAPITAL LETTER YIWN]
+char \u0553	(12 4 67 )  # ⡫ Փ [ARMENIAN CAPITAL LETTER PIWR]
+char \u0554	(1 3   7 )  # ⡅ Ք [ARMENIAN CAPITAL LETTER KEH]
+char \u0555	(1 3 5 7 )  # ⡕ Օ [ARMENIAN CAPITAL LETTER OH]
+char \u0556	(12 4  7 )  # ⡋ Ֆ [ARMENIAN CAPITAL LETTER FEH]
+
+# punctuation
+char \u055D	(     6  )  # ⠠ ՝ [ARMENIAN COMMA]
+
+include ltr-dot8.tti
+include num-nemeth.tti
+include punc-alternate.tti
+include common.tti
diff --git a/Tables/Text/is.ttb b/Tables/Text/is.ttb
new file mode 100644
index 0000000..d6d6722
--- /dev/null
+++ b/Tables/Text/is.ttb
@@ -0,0 +1,185 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Icelandic
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This conforms to the IceBraille standard, v1.0.
+# Birkir Gunnarsson, birkir@midstod.is
+# Special thanks to Ben Van Popel for help with drafting the standard.
+
+# control characters
+char \x00       (  345 78)  # ⣜ 00 [NULL]
+include ctl-latin.tti
+char \x1B       (123 5678)  # ⣷ 1B [ESCAPE]
+char \x1C       (  34  78)  # ⣌ 1C [INFORMATION SEPARATOR FOUR]
+char \x1D       ( 2345678)  # ⣾ 1D [INFORMATION SEPARATOR THREE]
+char \x1E       ( 234 678)  # ⣮ 1E [INFORMATION SEPARATOR TWO]
+char \x1F       (   45678)  # ⣸ 1F [INFORMATION SEPARATOR ONE]
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+
+char \x21	( 23 5 7 )  # ⡖ ! [EXCLAMATION MARK]
+char \x22	(    56  )  # ⠰ " [QUOTATION MARK]
+char \x23	(  3456  )  # ⠼ # [NUMBER SIGN]
+char \x24	(1  45 78)  # ⣙ $ [DOLLAR SIGN]
+char \x25	(   4 6  )  # ⠨ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # ⢯ & [AMPERSAND]
+char \x27	(    5   )  # ⠐ ' [APOSTROPHE]
+char \x2A	(  3 5   )  # ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # ⠖ + [PLUS SIGN]
+char \x2E	(  3     )  # ⠄ . [FULL STOP]
+char \x2F	(  34    )  # ⠌ / [SOLIDUS]
+char \x3C	(  3 5  8)  # ⢔ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # ⠶ = [EQUALS SIGN]
+char \x3E	( 2   67 )  # ⡢ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+char \x40	(123456  )  # ⠿ @ [COMMERCIAL AT]
+
+char \x5B	(123 56  )  # ⠷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # ⡌ \ [REVERSE SOLIDUS]
+char \x5D	( 23456  )  # ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(  34 6  )  # ⠬ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3  678)  # ⣤ _ [LOW LINE]
+char \x60	(   4    )  # ⠈ ` [GRAVE ACCENT]
+
+char \x7B	(123 567 )  # ⡷ { [LEFT CURLY BRACKET]
+char \x7C	(   456  )  # ⠸ | [VERTICAL LINE]
+char \x7D	( 23456 8)  # ⢾ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # ⠘ ~ [TILDE]
+char \x7F	(      7 )  # ⡀   [DELETE]
+
+char \xA1	(  3 56 8)  # ⢴ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5 78)  # ⣒ ¢ [CENT SIGN]
+char \xA3	(1234  78)  # ⣏ £ [POUND SIGN]
+char \xA4	(1 34 678)  # ⣭ ¤ [CURRENCY SIGN]
+char \xA5	(1 345678)  # ⣽ ¥ [YEN SIGN]
+char \xA6	(  34  78)  # ⣌ ¦ [BROKEN BAR]
+char \xA7	( 234   8)  # ⢎ § [SECTION SIGN]
+char \xA8	(    5 78)  # ⣐ ¨ [DIAERESIS]
+char \xA9	(1  4  78)  # ⣉ © [COPYRIGHT SIGN]
+char \xAA	( 234 678)  # ⣮ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5 7 )  # ⡐ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  34567 )  # ⡼ ¬ [NOT SIGN]
+char \xAD	(  3   78)  # ⣄ ­ [SOFT HYPHEN]
+char \xAE	(123 5 78)  # ⣗ ® [REGISTERED SIGN]
+char \xAF	( 23 567 )  # ⡶ ¯ [MACRON]
+char \xB0	( 2  56  )  # ⠲ ° [DEGREE SIGN]
+char \xB1	(12345  8)  # ⢟ ± [PLUS-MINUS SIGN]
+char \xB2	( 23    8)  # ⢆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5  8)  # ⢒ ³ [SUPERSCRIPT THREE]
+char \xB4	(   4 6 8)  # ⢨ ´ [ACUTE ACCENT]
+char \xB5	( 23  678)  # ⣦ µ [MICRO SIGN]
+char \xB6	(  345678)  # ⣼ ¶ [PILCROW SIGN]
+char \xB7	(  3    8)  # ⢄ · [MIDDLE DOT]
+char \xB8	( 2 45678)  # ⣺ ¸ [CEDILLA]
+char \xB9	( 2     8)  # ⢂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(12345678)  # ⣿ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    567 )  # ⡰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 345  8)  # ⢝ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(   45  8)  # ⢘ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  3456 8)  # ⢼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(     67 )  # ⡠ ¿ [INVERTED QUESTION MARK]
+char \xC0	(1 3  678)  # ⣥ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(1    67 )  # ⡡ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 678)  # ⣩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 78)  # ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(123 5678)  # ⣷ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(12  5678)  # ⣳ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(   4  78)  # ⣈ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 234 67 )  # ⡮ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1234567 )  # ⡿ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(  3 5678)  # ⣴ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(1   5678)  # ⣱ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(12   67 )  # ⡣ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12   678)  # ⣣ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(1   567 )  # ⡱ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(12 4 678)  # ⣫ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(1  4567 )  # ⡹ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(  34 678)  # ⣬ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  45678)  # ⣹ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # ⢭ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 678)  # ⣪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 234567 )  # ⡾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12 4567 )  # ⡻ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(  34   8)  # ⢌ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1234 67 )  # ⡯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12 4 67 )  # ⡫ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6 8)  # ⢮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(1 3   78)  # ⣅ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6  )  # ⠡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(123 56 8)  # ⢷ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1 34   8)  # ⢍ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 23 56 8)  # ⢶ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	( 234 6  )  # ⠮ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6 8)  # ⢣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(1 3    8)  # ⢅ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1   56 8)  # ⢱ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(12   6  )  # ⠣ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	( 2 45 78)  # ⣚ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12  56 8)  # ⢳ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1   56  )  # ⠱ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(12 4 6 8)  # ⢫ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(1  456  )  # ⠹ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(  34 6 8)  # ⢬ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1  456 8)  # ⢹ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(12  56  )  # ⠳ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6 8)  # ⢪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23  6 8)  # ⢦ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(12 456  )  # ⠻ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1234   8)  # ⢏ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1234 6  )  # ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(12 4 6  )  # ⠫ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 23456 8)  # ⢾ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+char \u0152	(1 3 5 78)  # ⣕ Œ [LATIN CAPITAL LIGATURE OE]
+char \u0192	(    5  8)  # ⢐ ƒ [LATIN SMALL LETTER F WITH HOOK]
+
+char \u2013	(  3  6 8)  # ⢤ – [EN DASH]
+char \u2014	(  3  67 )  # ⡤ — [EM DASH]
+char \u201C	( 23   7 )  # ⡆ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	(   45  8)  # ⢘ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u2020	( 23 5  8)  # ⢖ † [DAGGER]
+char \u2021	( 23 5 78)  # ⣖ ‡ [DOUBLE DAGGER]
+char \u2022	(  3   7 )  # ⡄ • [BULLET]
+char \u2026	(     6  )  # ⠠ … [HORIZONTAL ELLIPSIS]
+char \u2030	(   4 678)  # ⣨ ‰ [PER MILLE SIGN]
+char \u20AC     (1   5 78)  # ⣑ € [EURO SIGN]
+char \u2122	( 2345 78)  # ⣞ ™ [TRADE MARK SIGN]
+
+include common.tti
diff --git a/Tables/Text/it.ttb b/Tables/Text/it.ttb
new file mode 100644
index 0000000..c14d568
--- /dev/null
+++ b/Tables/Text/it.ttb
@@ -0,0 +1,172 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Italian
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 346
+include num-dot6.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(  345 78)  # 00 ⣜   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(123 5678)  # 1B ⣷   [ESCAPE]
+char \x1C	(  34  78)  # 1C ⣌   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2345678)  # 1D ⣾   [INFORMATION SEPARATOR THREE]
+char \x1E	( 234 678)  # 1E ⣮   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	(    5   )  # 21 ⠐ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # 22 ⠈ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(   4 6  )  # 24 ⠨ $ [DOLLAR SIGN]
+char \x25	(123456  )  # 25 ⠿ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # 3C ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(   45   )  # 3E ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  34 67 )  # 40 ⡬ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  67 )  # 5B ⡦ [ [LEFT SQUARE BRACKET]
+char \x5C	( 2  567 )  # 5C ⡲ \ [REVERSE SOLIDUS]
+char \x5D	(  3 567 )  # 5D ⡴ ] [RIGHT SQUARE BRACKET]
+char \x5E	( 234 67 )  # 5E ⡮ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(  345   )  # 60 ⠜ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 23  6 8)  # 7B ⢦ { [LEFT CURLY BRACKET]
+char \x7C	( 2  56 8)  # 7C ⢲ | [VERTICAL LINE]
+char \x7D	(  3 56 8)  # 7D ⢴ } [RIGHT CURLY BRACKET]
+char \x7E	( 234 6 8)  # 7E ⢮ ~ [TILDE]
+char \x7F	(      78)  # 7F ⣀   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(1  4 6 8)  # A2 ⢩ ¢ [CENT SIGN]
+char \xA3	(   4 67 )  # A3 ⡨ £ [POUND SIGN]
+char \xA4	(12345678)  # A4 ⣿ ¤ [CURRENCY SIGN]
+char \xA5	(   4 6 8)  # A5 ⢨ ¥ [YEN SIGN]
+char \xA6	( 23 56  )  # A6 ⠶ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	(  3  6  )  # A8 ⠤ ¨ [DIAERESIS]
+char \xA9	(  3  6  )  # A9 ⠤ © [COPYRIGHT SIGN]
+char \xAA	(12  5  8)  # AA ⢓ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(    5678)  # AB ⣰ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  3  6  )  # AC ⠤ ¬ [NOT SIGN]
+char \xAD	(    5 78)  # AD ⣐ ­ [SOFT HYPHEN]
+char \xAE	(  3  6  )  # AE ⠤ ® [REGISTERED SIGN]
+char \xAF	( 23 56  )  # AF ⠶ ¯ [MACRON]
+char \xB0	(  34   8)  # B0 ⢌ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	(12     8)  # B2 ⢃ ² [SUPERSCRIPT TWO]
+char \xB3	(   456 8)  # B3 ⢸ ³ [SUPERSCRIPT THREE]
+char \xB4	(   456 8)  # B4 ⢸ ´ [ACUTE ACCENT]
+char \xB5	(1 34 6 8)  # B5 ⢭ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(      7 )  # B7 ⡀ · [MIDDLE DOT]
+char \xB8	( 23 56  )  # B8 ⠶ ¸ [CEDILLA]
+char \xB9	(   456 8)  # B9 ⢸ ¹ [SUPERSCRIPT ONE]
+char \xBA	( 2 45  8)  # BA ⢚ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(   45 78)  # BB ⣘ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(123  6 8)  # BC ⢧ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  3   7 )  # BD ⡄ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	( 23 56  )  # BE ⠶ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(     6 8)  # BF ⢠ ¿ [INVERTED QUESTION MARK]
+char \xC0	(  3  6  )  # C0 ⠤ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(  3  6  )  # C1 ⠤ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(  3  6  )  # C2 ⠤ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(   456 8)  # C3 ⢸ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(123 567 )  # C4 ⡷ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  345678)  # C5 ⣼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(   4  78)  # C6 ⣈ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 23 56  )  # C8 ⠶ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 2   678)  # C9 ⣢ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	( 23 56  )  # CA ⠶ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	( 23 56  )  # CB ⠶ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(   456 8)  # CC ⢸ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 23 56  )  # CD ⠶ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(   456 8)  # CE ⢸ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	( 23 56  )  # CF ⠶ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  3  6  )  # D0 ⠤ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(1 34   8)  # D1 ⢍ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(  3  6  )  # D2 ⠤ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  3  6  )  # D3 ⠤ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 23 56  )  # D4 ⠶ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 23 56  )  # D5 ⠶ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(  34  7 )  # D6 ⡌ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(   456 8)  # D7 ⢸ × [MULTIPLICATION SIGN]
+char \xD8	(   456 8)  # D8 ⢸ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(  3  6  )  # D9 ⠤ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(  3  6  )  # DA ⠤ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(12345678)  # DB ⣿ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	( 234567 )  # DC ⡾ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(   456 8)  # DD ⢸ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(   456 8)  # DE ⢸ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234 6  )  # DF ⠮ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(1      8)  # E0 ⢁ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6 8)  # E1 ⢡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1234   8)  # E3 ⢏ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(123 56  )  # E4 ⠷ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  34567 )  # E5 ⡼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(   4  7 )  # E6 ⡈ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 678)  # E7 ⣯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	(1   5  8)  # E8 ⢑ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(1   56 8)  # E9 ⢱ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   678)  # EA ⣣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 2 4   8)  # EC ⢊ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  3 5 7 )  # ED ⡔ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 67 )  # EE ⡩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456 8)  # EF ⢻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(  3  6 8)  # F0 ⢤ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1 345  8)  # F1 ⢝ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3 5  8)  # F2 ⢕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	( 2 4 6 8)  # F3 ⢪ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(  345  8)  # F5 ⢜ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(  34    )  # F6 ⠌ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(12  5678)  # F7 ⣳ ÷ [DIVISION SIGN]
+char \xF8	(12345678)  # F8 ⣿ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	(1 3  6 8)  # F9 ⢥ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1 3  678)  # FA ⣥ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   5678)  # FB ⣱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	( 23456  )  # FC ⠾ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(12345678)  # FD ⣿ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  3  678)  # FE ⣤ þ [LATIN SMALL LETTER THORN]
+char \xFF	(   456 8)  # FF ⢸ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/kannada.tti b/Tables/Text/kannada.tti
new file mode 100644
index 0000000..f602ada
--- /dev/null
+++ b/Tables/Text/kannada.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Kannada script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+
+char \u0C82	(    56  )  # ⠰ ಂ [KANNADA SIGN ANUSVARA]
+char \u0C83	(     6  )  # ⠠ ಃ [KANNADA SIGN VISARGA]
+char \u0C85	(1       )  # ⠁ ಅ [KANNADA LETTER A]
+char \u0C86	(  345   )  # ⠜ ಆ [KANNADA LETTER AA]
+char \u0C87	( 2 4    )  # ⠊ ಇ [KANNADA LETTER I]
+char \u0C88	(  3 5   )  # ⠔ ಈ [KANNADA LETTER II]
+char \u0C89	(1 3  6  )  # ⠥ ಉ [KANNADA LETTER U]
+char \u0C8A	(12  56  )  # ⠳ ಊ [KANNADA LETTER UU]
+
+char \u0C8E	(  34  7 )  # ⡌ ಎ [KANNADA LETTER E]
+char \u0C8F	(1   5   )  # ⠑ ಏ [KANNADA LETTER EE]
+char \u0C90	(  34    )  # ⠌ ಐ [KANNADA LETTER AI]
+
+char \u0C92	( 2 4 67 )  # ⡪ ಒ [KANNADA LETTER O]
+char \u0C93	(1 3 5   )  # ⠕ ಓ [KANNADA LETTER OO]
+char \u0C94	( 2 4 6  )  # ⠪ ಔ [KANNADA LETTER AU]
+char \u0C95	(1 3     )  # ⠅ ಕ [KANNADA LETTER KA]
+char \u0C96	(   4 6  )  # ⠨ ಖ [KANNADA LETTER KHA]
+char \u0C97	(1234    )  # ⠏ ಗ [KANNADA LETTER GA]
+char \u0C98	(12   6  )  # ⠣ ಘ [KANNADA LETTER GHA]
+char \u0C99	(  34 6  )  # ⠬ ಙ [KANNADA LETTER NGA]
+char \u0C9A	(1  4    )  # ⠉ ಚ [KANNADA LETTER CA]
+char \u0C9B	(1    6  )  # ⠡ ಛ [KANNADA LETTER CHA]
+char \u0C9C	( 2 45   )  # ⠚ ಜ [KANNADA LETTER JA]
+char \u0C9D	(  3 56  )  # ⠴ ಝ [KANNADA LETTER JHA]
+char \u0C9E	( 2  5   )  # ⠒ ಞ [KANNADA LETTER NYA]
+char \u0C9F	( 23456  )  # ⠾ ಟ [KANNADA LETTER TTA]
+char \u0CA0	( 2 456  )  # ⠺ ಠ [KANNADA LETTER TTHA]
+char \u0CA1	(12 4 6  )  # ⠫ ಡ [KANNADA LETTER DDA]
+char \u0CA2	(123456  )  # ⠿ ಢ [KANNADA LETTER DDHA]
+char \u0CA3	(  3456  )  # ⠼ ಣ [KANNADA LETTER NNA]
+char \u0CA4	( 2345   )  # ⠞ ತ [KANNADA LETTER TA]
+char \u0CA5	(1  456  )  # ⠹ ಥ [KANNADA LETTER THA]
+char \u0CA6	(1  45   )  # ⠙ ದ [KANNADA LETTER DA]
+char \u0CA7	( 234 6  )  # ⠮ ಧ [KANNADA LETTER DHA]
+char \u0CA8	(1 345   )  # ⠝ ನ [KANNADA LETTER NA]
+
+char \u0CAA	(1234    )  # ⠏ ಪ [KANNADA LETTER PA]
+char \u0CAB	( 23 5   )  # ⠖ ಫ [KANNADA LETTER PHA]
+char \u0CAC	(12      )  # ⠃ ಬ [KANNADA LETTER BA]
+char \u0CAD	(   45   )  # ⠘ ಭ [KANNADA LETTER BHA]
+char \u0CAE	(1 34    )  # ⠍ ಮ [KANNADA LETTER MA]
+char \u0CAF	(1 3456  )  # ⠽ ಯ [KANNADA LETTER YA]
+char \u0CB0	(123 5   )  # ⠗ ರ [KANNADA LETTER RA]
+char \u0CB1	(123 5 7 )  # ⡗ ಱ [KANNADA LETTER RRA]
+char \u0CB2	(123     )  # ⠇ ಲ [KANNADA LETTER LA]
+char \u0CB3	(123   7 )  # ⡇ ಳ [KANNADA LETTER LLA]
+
+char \u0CB5	(123  6  )  # ⠧ ವ [KANNADA LETTER VA]
+char \u0CB6	(1  4 6  )  # ⠩ ಶ [KANNADA LETTER SHA]
+char \u0CB7	(1234 6  )  # ⠯ ಷ [KANNADA LETTER SSA]
+char \u0CB8	( 234    )  # ⠎ ಸ [KANNADA LETTER SA]
+char \u0CB9	(12  5   )  # ⠓ ಹ [KANNADA LETTER HA]
+char \u0CBD	( 2      )  # ⠂ ಽ [KANNADA SIGN AVAGRAHA]
+char \u0CBE	(  345   )  # ⠜ ಾ [KANNADA VOWEL SIGN AA]
+char \u0CBF	( 2 4    )  # ⠊ ಿ [KANNADA VOWEL SIGN I]
+char \u0CC0	(  3 5   )  # ⠔ ೀ [KANNADA VOWEL SIGN II]
+char \u0CC1	(1 3  6  )  # ⠥ ು [KANNADA VOWEL SIGN U]
+char \u0CC2	(12  56  )  # ⠳ ೂ [KANNADA VOWEL SIGN UU]
+
+char \u0CC6	(  34  7 )  # ⡌ ೆ [KANNADA VOWEL SIGN E]
+char \u0CC7	(1   5   )  # ⠑ ೇ [KANNADA VOWEL SIGN EE]
+char \u0CC8	(  34    )  # ⠌ ೈ [KANNADA VOWEL SIGN AI]
+
+char \u0CCA	( 2 4 67 )  # ⡪ ೊ [KANNADA VOWEL SIGN O]
+char \u0CCB	(1 3 5   )  # ⠕ ೋ [KANNADA VOWEL SIGN OO]
+char \u0CCC	( 2 4 6  )  # ⠪ ೌ [KANNADA VOWEL SIGN AU]
+char \u0CCD	(   4    )  # ⠈ ್ [KANNADA SIGN VIRAMA]
+
+char \u0CE6	( 2 45   )  # ⠚ ೦ [KANNADA DIGIT ZERO]
+char \u0CE7	(1       )  # ⠁ ೧ [KANNADA DIGIT ONE]
+char \u0CE8	(12      )  # ⠃ ೨ [KANNADA DIGIT TWO]
+char \u0CE9	(1  4    )  # ⠉ ೩ [KANNADA DIGIT THREE]
+char \u0CEA	(1  45   )  # ⠙ ೪ [KANNADA DIGIT FOUR]
+char \u0CEB	(1   5   )  # ⠑ ೫ [KANNADA DIGIT FIVE]
+char \u0CEC	(12 4    )  # ⠋ ೬ [KANNADA DIGIT SIX]
+char \u0CED	(12 45   )  # ⠛ ೭ [KANNADA DIGIT SEVEN]
+char \u0CEE	(12  5   )  # ⠓ ೮ [KANNADA DIGIT EIGHT]
+char \u0CEF	( 2 4    )  # ⠊ ೯ [KANNADA DIGIT NINE]
diff --git a/Tables/Text/kha.ttb b/Tables/Text/kha.ttb
new file mode 100644
index 0000000..9d97acc
--- /dev/null
+++ b/Tables/Text/kha.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Khasi
+
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/kn.ttb b/Tables/Text/kn.ttb
new file mode 100644
index 0000000..6b37582
--- /dev/null
+++ b/Tables/Text/kn.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Kannada
+
+include kannada.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/kok.ttb b/Tables/Text/kok.ttb
new file mode 100644
index 0000000..0a049ea
--- /dev/null
+++ b/Tables/Text/kok.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Konkani
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/kru.ttb b/Tables/Text/kru.ttb
new file mode 100644
index 0000000..535a008
--- /dev/null
+++ b/Tables/Text/kru.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Kurukh
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/lt.ttb b/Tables/Text/lt.ttb
new file mode 100644
index 0000000..2580433
--- /dev/null
+++ b/Tables/Text/lt.ttb
@@ -0,0 +1,183 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Lituanian
+#
+# Copyright (C) 2017 Rimas Kudelis <rq@akl.lt>
+#
+# The Lithuanian 8-dot Braille writing system is described in a decree of the Minister of
+# Social Security and Labor, called "Dėl vieningos aštuonių taškų Brailio rašto sistemos
+# naudojimo tvarkos aprašo patvirtinimo", which is in effect since 2011-04-13. Document
+# number is A1-183. At the time of writing this file, the document was accessible at
+# https://www.e-tar.lt/portal/lt/legalAct/TAR.443D667CA047 .
+# The document is referred to as "the standard" below.
+#
+# The standard maps ISO-8859-13 character set to 8-dot Braille writing system. However,
+# even though it defines different mappings for literary and computer braille modes, it
+# doesn't seem like the authors had a good understanding of why these two modes exist
+# and how they differ. Furthermore, the standard contains a few errors (incorrectly named
+# characters as well as mapping conflicts). I would say it needs further improvements.
+#
+# This file is based on the standard, but does not exactly follow it. Some standard
+# definitions are commented out, some changed, and some extra ones are added.
+#
+# This table is based on the respective liblouis table.
+
+
+###
+### LETTERS
+###
+
+# Standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# Lowercase accented letters
+char \u0105	(1    6  )  # ⠡ ą [LATIN SMALL LETTER A WITH OGONEK]
+char \u010D	(1  4 6  )  # ⠩ č [LATIN SMALL LETTER C WITH CARON]
+char \u0119	(1   56  )  # ⠱ ę [LATIN SMALL LETTER E WITH OGONEK]
+char \u0117	(  345   )  # ⠜ ė [LATIN SMALL LETTER E WITH DOT ABOVE]
+char \u012F	( 2 4 6  )  # ⠪ į [LATIN SMALL LETTER I WITH OGONEK]
+char \u0161	( 234 6  )  # ⠮ š [LATIN SMALL LETTER S WITH CARON]
+char \u0173	(  34 6  )  # ⠬ ų [LATIN SMALL LETTER U WITH OGONEK]
+char \u016B	(12  56  )  # ⠳ ū [LATIN SMALL LETTER U WITH MACRON]
+char \u017E	(12   6  )  # ⠣ ž [LATIN SMALL LETTER Z WITH MACRON]
+
+# Uppercase accented letters
+char \u0104	(1    67 )  # ⡡ Ą [LATIN CAPITAL LETTER A WITH OGONEK]
+char \u010C	(1  4 67 )  # ⡩ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \u0118	(1   567 )  # ⡱ Ę [LATIN CAPITAL LETTER E WITH OGONEK]
+char \u0116	(  345 7 )  # ⡜ Ė [LATIN CAPITAL LETTER E WITH DOT ABOVE]
+char \u012E	( 2 4 67 )  # ⡪ Į [LATIN CAPITAL LETTER I WITH OGONEK]
+char \u0160	( 234 67 )  # ⡮ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u0172	(  34 67 )  # ⡬ Ų [LATIN CAPITAL LETTER U WITH OGONEK]
+char \u016A	(12  567 )  # ⡳ Ū [LATIN CAPITAL LETTER U WITH MACRON]
+char \u017D	(12   67 )  # ⡣ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+
+
+###
+### DIGITS
+###
+
+# Digits 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+
+###
+### PUNCTUATION
+###
+
+char \x2C	( 2      )  # ⠂ , [COMMA]
+char \x2E	( 2  56  )  # ⠲ . [FULL STOP]
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+char \x21	( 23 5   )  # ⠖ ! [EXCLAMATION MARK]
+char \x3A	( 2  5   )  # ⠒ : [COLON]
+char \x3B	( 23     )  # ⠆ ; [SEMICOLON]
+char \x22	(   4    )  # ⠈ " [QUOTATION MARK]
+char \x27	(  3     )  # ⠄ ' [APOSTROPHE]
+# The following character is defined as 134568 in the standard, but that is hardly useful.
+# According to Unicode, it is the preferred character to use for apostrophe, hence
+# defining it as one here.
+alias \u2019	\x27        #   ’ [RIGHT SINGLE QUOTATION MARK]
+
+char \x28	( 23 567 )  # ⡶ ( [LEFT PARENTHESIS]
+char \x29	( 23 56 8)  # ⢶ ) [RIGHT PARENTHESIS]
+char \x5B	(123 56  )  # ⠷ [ LEFT SQUARE BRACKET
+char \x5D	( 23456  )  # ⠾ ] RIGHT SQUARE BRACKET
+char \x7B	( 2 4 678)  # ⣪ { LEFT CURLY BRACKET
+char \x7D	(1 3 5 78)  # ⣕ } RIGHT CURLY BRACKET
+
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+# Soft hyphen is defined as 368 in the standard.
+char \xAD	(  3  6 8)  # ⢤ ­ [SOFT HYPHEN]
+
+# The following characters are not defined in the standard.
+
+# These are the typographically correct quotes in Lithuanian texts.
+char \u201E	( 23  67 )  # ⡦ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u201C	(  3 567 )  # ⡴ “ [LEFT DOUBLE QUOTATION MARK]
+# The following characters are defined in the standard, because they exist in ISO-8859-13
+# character set, even though they should not be used in Lithuanian texts.
+char \u201D	(  3  67 )  # ⡤ ” [RIGHT DOUBLE QUOTATION MARK]
+char \xAB	(    5678)  # ⣰ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBB	(   45 78)  # ⣘ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+
+
+###
+### MATHEMATICAL SYMBOLS
+###
+
+char \x2B	( 23 5  8)  # ⢖ + [PLUS SIGN]
+# Real minus is not defined in the standard.
+char \x3C	( 2 4 6 8)  # ⢪ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # ⠶ = [EQUALS SIGN]
+char \x3E	(1 3 5  8)  # ⢕ > [GREATER-THAN SIGN]
+char \xB1	( 23 5 78)  # ⣖ ± [PLUS-MINUS SIGN]
+char \xD7	( 234   8)  # ⢎ × MULTIPLICATION SIGN]
+char \xF7	(12  5678)  # ⣳ ÷ [DIVISION SIGN]
+
+
+###
+### OTHER CHARACTERS
+###
+
+char \x23	(  3456  )  # ⠼ # [NUMBER SIGN]
+char \x24	(   4 6  )  # ⠨ $ [DOLLAR SIGN]
+char \x25	(123456  )  # ⠿ % [PERCENT SIGN]
+char \x26	(1234 6  )  # ⠯ & [AMPERSAND]
+char \x2A	(  3 5   )  # ⠔ * [ASTERISK]
+char \x2F	(  34    )  # ⠌ / [SOLIDUS]
+char \x40	(  345 78)  # ⣜ @ [COMMERCIAL AT]
+char \x5C	(  34  7 )  # ⡌ \ [REVERSE SOLIDUS]
+char \x5E	( 2  56 8)  # ⢲ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # ⡸ _ [LOW LINE]
+char \x7C	(   456  )  # ⠸ | [VERTICAL LINE]
+char \xA6	(1  456  )  # ⠹ ¦ [BROKEN BAR]
+char \xA7	(  34 6 8)  # ⢬ § [SECTION SIGN]
+char \xAC	(    5   )  # ⠐ ¬ [NOT SIGN]
+char \xB5	(1 34   8)  # ⢍ µ [MICRO SIGN]
+char \xB6	(1234   8)  # ⢏ ¶ [PILCROW SIGN]
+
+char \x60	(     6  )  # ⠠ ` [GRAVE ACCENT]
+char \x7E	( 2   6 8)  # ⢢ ~ [TILDE]
+
+char \xA2	(    5  8)  # ⢐ ¢ [CENT SIGN]
+char \xA3	(   4 67 )  # ⡨ £ [POUND SIGN]
+# Euro sign is not defined in the standard, but codepoint 0x80 of ISO-8859-13 is.
+# In Windows-1257, 0x80 is the Euro sign.
+# The unofficially distributed Lithuanian JAWS table specified all characters as
+# ANSI codes, thus rendering Euro as 457.
+# Not sure if I want to replicate that here though: who knows how this table will
+# end up being used and for how long. Aliasing to E instead.
+# char \u20AC	(   45 7 )  # ⡘ € [EURO SIGN]
+alias \u20AC	\x45        #   € [EURO SIGN]
+
+char \xA4	(   4 678)  # ⣨ ¤ [CURRENCY SIGN]
+
+char \xA9	(1234 6 8)  # ⢯ © COPYRIGHT SIGN
+char \xAE	(123 5  8)  # ⢗ ® [REGISTERED SIGN]
+
+# Middle dot is unlikely to appear in text, except perhaps as a multiplication sign (dot operator).
+char \xB7	(  3   7 )  # ⡄ · [MIDDLE DOT]
+
+char \xB0	(   456 8)  # ⢸ ° [DEGREE SIGN]
+
+char \xB9	(1     78)  # ⣁ ¹ [SUPERSCRIPT ONE]
+char \xB2	(12    78)  # ⣃ ² [SUPERSCRIPT TWO]
+char \xB3	(1  4  78)  # ⣉ ³ [SUPERSCRIPT THREE]
+
+include common.tti
diff --git a/Tables/Text/ltr-alias.tti b/Tables/Text/ltr-alias.tti
new file mode 100644
index 0000000..3d4e416
--- /dev/null
+++ b/Tables/Text/ltr-alias.tti
@@ -0,0 +1,964 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines aliases for additional Latin alphabet styles.
+
+alias	\u249C	\x61	# ⒜ [PARENTHESIZED LATIN SMALL LETTER A]
+alias	\u249D	\x62	# ⒝ [PARENTHESIZED LATIN SMALL LETTER B]
+alias	\u249E	\x63	# ⒞ [PARENTHESIZED LATIN SMALL LETTER C]
+alias	\u249F	\x64	# ⒟ [PARENTHESIZED LATIN SMALL LETTER D]
+alias	\u24A0	\x65	# ⒠ [PARENTHESIZED LATIN SMALL LETTER E]
+alias	\u24A1	\x66	# ⒡ [PARENTHESIZED LATIN SMALL LETTER F]
+alias	\u24A2	\x67	# ⒢ [PARENTHESIZED LATIN SMALL LETTER G]
+alias	\u24A3	\x68	# ⒣ [PARENTHESIZED LATIN SMALL LETTER H]
+alias	\u24A4	\x69	# ⒤ [PARENTHESIZED LATIN SMALL LETTER I]
+alias	\u24A5	\x6A	# ⒥ [PARENTHESIZED LATIN SMALL LETTER J]
+alias	\u24A6	\x6B	# ⒦ [PARENTHESIZED LATIN SMALL LETTER K]
+alias	\u24A7	\x6C	# ⒧ [PARENTHESIZED LATIN SMALL LETTER L]
+alias	\u24A8	\x6D	# ⒨ [PARENTHESIZED LATIN SMALL LETTER M]
+alias	\u24A9	\x6E	# ⒩ [PARENTHESIZED LATIN SMALL LETTER N]
+alias	\u24AA	\x6F	# ⒪ [PARENTHESIZED LATIN SMALL LETTER O]
+alias	\u24AB	\x70	# ⒫ [PARENTHESIZED LATIN SMALL LETTER P]
+alias	\u24AC	\x71	# ⒬ [PARENTHESIZED LATIN SMALL LETTER Q]
+alias	\u24AD	\x72	# ⒭ [PARENTHESIZED LATIN SMALL LETTER R]
+alias	\u24AE	\x73	# ⒮ [PARENTHESIZED LATIN SMALL LETTER S]
+alias	\u24AF	\x74	# ⒯ [PARENTHESIZED LATIN SMALL LETTER T]
+alias	\u24B0	\x75	# ⒰ [PARENTHESIZED LATIN SMALL LETTER U]
+alias	\u24B1	\x76	# ⒱ [PARENTHESIZED LATIN SMALL LETTER V]
+alias	\u24B2	\x77	# ⒲ [PARENTHESIZED LATIN SMALL LETTER W]
+alias	\u24B3	\x78	# ⒳ [PARENTHESIZED LATIN SMALL LETTER X]
+alias	\u24B4	\x79	# ⒴ [PARENTHESIZED LATIN SMALL LETTER Y]
+alias	\u24B5	\x7A	# ⒵ [PARENTHESIZED LATIN SMALL LETTER Z]
+
+alias	\u24B6	\x41	# Ⓐ [CIRCLED LATIN CAPITAL LETTER A]
+alias	\u24B7	\x42	# Ⓑ [CIRCLED LATIN CAPITAL LETTER B]
+alias	\u24B8	\x43	# Ⓒ [CIRCLED LATIN CAPITAL LETTER C]
+alias	\u24B9	\x44	# Ⓓ [CIRCLED LATIN CAPITAL LETTER D]
+alias	\u24BA	\x45	# Ⓔ [CIRCLED LATIN CAPITAL LETTER E]
+alias	\u24BB	\x46	# Ⓕ [CIRCLED LATIN CAPITAL LETTER F]
+alias	\u24BC	\x47	# Ⓖ [CIRCLED LATIN CAPITAL LETTER G]
+alias	\u24BD	\x48	# Ⓗ [CIRCLED LATIN CAPITAL LETTER H]
+alias	\u24BE	\x49	# Ⓘ [CIRCLED LATIN CAPITAL LETTER I]
+alias	\u24BF	\x4A	# Ⓙ [CIRCLED LATIN CAPITAL LETTER J]
+alias	\u24C0	\x4B	# Ⓚ [CIRCLED LATIN CAPITAL LETTER K]
+alias	\u24C1	\x4C	# Ⓛ [CIRCLED LATIN CAPITAL LETTER L]
+alias	\u24C2	\x4D	# Ⓜ [CIRCLED LATIN CAPITAL LETTER M]
+alias	\u24C3	\x4E	# Ⓝ [CIRCLED LATIN CAPITAL LETTER N]
+alias	\u24C4	\x4F	# Ⓞ [CIRCLED LATIN CAPITAL LETTER O]
+alias	\u24C5	\x50	# Ⓟ [CIRCLED LATIN CAPITAL LETTER P]
+alias	\u24C6	\x51	# Ⓠ [CIRCLED LATIN CAPITAL LETTER Q]
+alias	\u24C7	\x52	# Ⓡ [CIRCLED LATIN CAPITAL LETTER R]
+alias	\u24C8	\x53	# Ⓢ [CIRCLED LATIN CAPITAL LETTER S]
+alias	\u24C9	\x54	# Ⓣ [CIRCLED LATIN CAPITAL LETTER T]
+alias	\u24CA	\x55	# Ⓤ [CIRCLED LATIN CAPITAL LETTER U]
+alias	\u24CB	\x56	# Ⓥ [CIRCLED LATIN CAPITAL LETTER V]
+alias	\u24CC	\x57	# Ⓦ [CIRCLED LATIN CAPITAL LETTER W]
+alias	\u24CD	\x58	# Ⓧ [CIRCLED LATIN CAPITAL LETTER X]
+alias	\u24CE	\x59	# Ⓨ [CIRCLED LATIN CAPITAL LETTER Y]
+alias	\u24CF	\x5A	# Ⓩ [CIRCLED LATIN CAPITAL LETTER Z]
+
+alias	\u24D0	\x61	# ⓐ [CIRCLED LATIN SMALL LETTER A]
+alias	\u24D1	\x62	# ⓑ [CIRCLED LATIN SMALL LETTER B]
+alias	\u24D2	\x63	# ⓒ [CIRCLED LATIN SMALL LETTER C]
+alias	\u24D3	\x64	# ⓓ [CIRCLED LATIN SMALL LETTER D]
+alias	\u24D4	\x65	# ⓔ [CIRCLED LATIN SMALL LETTER E]
+alias	\u24D5	\x66	# ⓕ [CIRCLED LATIN SMALL LETTER F]
+alias	\u24D6	\x67	# ⓖ [CIRCLED LATIN SMALL LETTER G]
+alias	\u24D7	\x68	# ⓗ [CIRCLED LATIN SMALL LETTER H]
+alias	\u24D8	\x69	# ⓘ [CIRCLED LATIN SMALL LETTER I]
+alias	\u24D9	\x6A	# ⓙ [CIRCLED LATIN SMALL LETTER J]
+alias	\u24DA	\x6B	# ⓚ [CIRCLED LATIN SMALL LETTER K]
+alias	\u24DB	\x6C	# ⓛ [CIRCLED LATIN SMALL LETTER L]
+alias	\u24DC	\x6D	# ⓜ [CIRCLED LATIN SMALL LETTER M]
+alias	\u24DD	\x6E	# ⓝ [CIRCLED LATIN SMALL LETTER N]
+alias	\u24DE	\x6F	# ⓞ [CIRCLED LATIN SMALL LETTER O]
+alias	\u24DF	\x70	# ⓟ [CIRCLED LATIN SMALL LETTER P]
+alias	\u24E0	\x71	# ⓠ [CIRCLED LATIN SMALL LETTER Q]
+alias	\u24E1	\x72	# ⓡ [CIRCLED LATIN SMALL LETTER R]
+alias	\u24E2	\x73	# ⓢ [CIRCLED LATIN SMALL LETTER S]
+alias	\u24E3	\x74	# ⓣ [CIRCLED LATIN SMALL LETTER T]
+alias	\u24E4	\x75	# ⓤ [CIRCLED LATIN SMALL LETTER U]
+alias	\u24E5	\x76	# ⓥ [CIRCLED LATIN SMALL LETTER V]
+alias	\u24E6	\x77	# ⓦ [CIRCLED LATIN SMALL LETTER W]
+alias	\u24E7	\x78	# ⓧ [CIRCLED LATIN SMALL LETTER X]
+alias	\u24E8	\x79	# ⓨ [CIRCLED LATIN SMALL LETTER Y]
+alias	\u24E9	\x7A	# ⓩ [CIRCLED LATIN SMALL LETTER Z]
+
+alias	\uFF21	\x41	# A [FULLWIDTH LATIN CAPITAL LETTER A]
+alias	\uFF22	\x42	# B [FULLWIDTH LATIN CAPITAL LETTER B]
+alias	\uFF23	\x43	# C [FULLWIDTH LATIN CAPITAL LETTER C]
+alias	\uFF24	\x44	# D [FULLWIDTH LATIN CAPITAL LETTER D]
+alias	\uFF25	\x45	# E [FULLWIDTH LATIN CAPITAL LETTER E]
+alias	\uFF26	\x46	# F [FULLWIDTH LATIN CAPITAL LETTER F]
+alias	\uFF27	\x47	# G [FULLWIDTH LATIN CAPITAL LETTER G]
+alias	\uFF28	\x48	# H [FULLWIDTH LATIN CAPITAL LETTER H]
+alias	\uFF29	\x49	# I [FULLWIDTH LATIN CAPITAL LETTER I]
+alias	\uFF2A	\x4A	# J [FULLWIDTH LATIN CAPITAL LETTER J]
+alias	\uFF2B	\x4B	# K [FULLWIDTH LATIN CAPITAL LETTER K]
+alias	\uFF2C	\x4C	# L [FULLWIDTH LATIN CAPITAL LETTER L]
+alias	\uFF2D	\x4D	# M [FULLWIDTH LATIN CAPITAL LETTER M]
+alias	\uFF2E	\x4E	# N [FULLWIDTH LATIN CAPITAL LETTER N]
+alias	\uFF2F	\x4F	# O [FULLWIDTH LATIN CAPITAL LETTER O]
+alias	\uFF30	\x50	# P [FULLWIDTH LATIN CAPITAL LETTER P]
+alias	\uFF31	\x51	# Q [FULLWIDTH LATIN CAPITAL LETTER Q]
+alias	\uFF32	\x52	# R [FULLWIDTH LATIN CAPITAL LETTER R]
+alias	\uFF33	\x53	# S [FULLWIDTH LATIN CAPITAL LETTER S]
+alias	\uFF34	\x54	# T [FULLWIDTH LATIN CAPITAL LETTER T]
+alias	\uFF35	\x55	# U [FULLWIDTH LATIN CAPITAL LETTER U]
+alias	\uFF36	\x56	# V [FULLWIDTH LATIN CAPITAL LETTER V]
+alias	\uFF37	\x57	# W [FULLWIDTH LATIN CAPITAL LETTER W]
+alias	\uFF38	\x58	# X [FULLWIDTH LATIN CAPITAL LETTER X]
+alias	\uFF39	\x59	# Y [FULLWIDTH LATIN CAPITAL LETTER Y]
+alias	\uFF3A	\x5A	# Z [FULLWIDTH LATIN CAPITAL LETTER Z]
+
+alias	\uFF41	\x61	# a [FULLWIDTH LATIN SMALL LETTER A]
+alias	\uFF42	\x62	# b [FULLWIDTH LATIN SMALL LETTER B]
+alias	\uFF43	\x63	# c [FULLWIDTH LATIN SMALL LETTER C]
+alias	\uFF44	\x64	# d [FULLWIDTH LATIN SMALL LETTER D]
+alias	\uFF45	\x65	# e [FULLWIDTH LATIN SMALL LETTER E]
+alias	\uFF46	\x66	# f [FULLWIDTH LATIN SMALL LETTER F]
+alias	\uFF47	\x67	# g [FULLWIDTH LATIN SMALL LETTER G]
+alias	\uFF48	\x68	# h [FULLWIDTH LATIN SMALL LETTER H]
+alias	\uFF49	\x69	# i [FULLWIDTH LATIN SMALL LETTER I]
+alias	\uFF4A	\x6A	# j [FULLWIDTH LATIN SMALL LETTER J]
+alias	\uFF4B	\x6B	# k [FULLWIDTH LATIN SMALL LETTER K]
+alias	\uFF4C	\x6C	# l [FULLWIDTH LATIN SMALL LETTER L]
+alias	\uFF4D	\x6D	# m [FULLWIDTH LATIN SMALL LETTER M]
+alias	\uFF4E	\x6E	# n [FULLWIDTH LATIN SMALL LETTER N]
+alias	\uFF4F	\x6F	# o [FULLWIDTH LATIN SMALL LETTER O]
+alias	\uFF50	\x70	# p [FULLWIDTH LATIN SMALL LETTER P]
+alias	\uFF51	\x71	# q [FULLWIDTH LATIN SMALL LETTER Q]
+alias	\uFF52	\x72	# r [FULLWIDTH LATIN SMALL LETTER R]
+alias	\uFF53	\x73	# s [FULLWIDTH LATIN SMALL LETTER S]
+alias	\uFF54	\x74	# t [FULLWIDTH LATIN SMALL LETTER T]
+alias	\uFF55	\x75	# u [FULLWIDTH LATIN SMALL LETTER U]
+alias	\uFF56	\x76	# v [FULLWIDTH LATIN SMALL LETTER V]
+alias	\uFF57	\x77	# w [FULLWIDTH LATIN SMALL LETTER W]
+alias	\uFF58	\x78	# x [FULLWIDTH LATIN SMALL LETTER X]
+alias	\uFF59	\x79	# y [FULLWIDTH LATIN SMALL LETTER Y]
+alias	\uFF5A	\x7A	# z [FULLWIDTH LATIN SMALL LETTER Z]
+
+alias	\U0001D400	\x41	# 𝐀 [MATHEMATICAL BOLD CAPITAL A]
+alias	\U0001D401	\x42	# 𝐁 [MATHEMATICAL BOLD CAPITAL B]
+alias	\U0001D402	\x43	# 𝐂 [MATHEMATICAL BOLD CAPITAL C]
+alias	\U0001D403	\x44	# 𝐃 [MATHEMATICAL BOLD CAPITAL D]
+alias	\U0001D404	\x45	# 𝐄 [MATHEMATICAL BOLD CAPITAL E]
+alias	\U0001D405	\x46	# 𝐅 [MATHEMATICAL BOLD CAPITAL F]
+alias	\U0001D406	\x47	# 𝐆 [MATHEMATICAL BOLD CAPITAL G]
+alias	\U0001D407	\x48	# 𝐇 [MATHEMATICAL BOLD CAPITAL H]
+alias	\U0001D408	\x49	# 𝐈 [MATHEMATICAL BOLD CAPITAL I]
+alias	\U0001D409	\x4A	# 𝐉 [MATHEMATICAL BOLD CAPITAL J]
+alias	\U0001D40A	\x4B	# 𝐊 [MATHEMATICAL BOLD CAPITAL K]
+alias	\U0001D40B	\x4C	# 𝐋 [MATHEMATICAL BOLD CAPITAL L]
+alias	\U0001D40C	\x4D	# 𝐌 [MATHEMATICAL BOLD CAPITAL M]
+alias	\U0001D40D	\x4E	# 𝐍 [MATHEMATICAL BOLD CAPITAL N]
+alias	\U0001D40E	\x4F	# 𝐎 [MATHEMATICAL BOLD CAPITAL O]
+alias	\U0001D40F	\x50	# 𝐏 [MATHEMATICAL BOLD CAPITAL P]
+alias	\U0001D410	\x51	# 𝐐 [MATHEMATICAL BOLD CAPITAL Q]
+alias	\U0001D411	\x52	# 𝐑 [MATHEMATICAL BOLD CAPITAL R]
+alias	\U0001D412	\x53	# 𝐒 [MATHEMATICAL BOLD CAPITAL S]
+alias	\U0001D413	\x54	# 𝐓 [MATHEMATICAL BOLD CAPITAL T]
+alias	\U0001D414	\x55	# 𝐔 [MATHEMATICAL BOLD CAPITAL U]
+alias	\U0001D415	\x56	# 𝐕 [MATHEMATICAL BOLD CAPITAL V]
+alias	\U0001D416	\x57	# 𝐖 [MATHEMATICAL BOLD CAPITAL W]
+alias	\U0001D417	\x58	# 𝐗 [MATHEMATICAL BOLD CAPITAL X]
+alias	\U0001D418	\x59	# 𝐘 [MATHEMATICAL BOLD CAPITAL Y]
+alias	\U0001D419	\x5A	# 𝐙 [MATHEMATICAL BOLD CAPITAL Z]
+
+alias	\U0001D41A	\x61	# 𝐚 [MATHEMATICAL BOLD SMALL A]
+alias	\U0001D41B	\x62	# 𝐛 [MATHEMATICAL BOLD SMALL B]
+alias	\U0001D41C	\x63	# 𝐜 [MATHEMATICAL BOLD SMALL C]
+alias	\U0001D41D	\x64	# 𝐝 [MATHEMATICAL BOLD SMALL D]
+alias	\U0001D41E	\x65	# 𝐞 [MATHEMATICAL BOLD SMALL E]
+alias	\U0001D41F	\x66	# 𝐟 [MATHEMATICAL BOLD SMALL F]
+alias	\U0001D420	\x67	# 𝐠 [MATHEMATICAL BOLD SMALL G]
+alias	\U0001D421	\x68	# 𝐡 [MATHEMATICAL BOLD SMALL H]
+alias	\U0001D422	\x69	# 𝐢 [MATHEMATICAL BOLD SMALL I]
+alias	\U0001D423	\x6A	# 𝐣 [MATHEMATICAL BOLD SMALL J]
+alias	\U0001D424	\x6B	# 𝐤 [MATHEMATICAL BOLD SMALL K]
+alias	\U0001D425	\x6C	# 𝐥 [MATHEMATICAL BOLD SMALL L]
+alias	\U0001D426	\x6D	# 𝐦 [MATHEMATICAL BOLD SMALL M]
+alias	\U0001D427	\x6E	# 𝐧 [MATHEMATICAL BOLD SMALL N]
+alias	\U0001D428	\x6F	# 𝐨 [MATHEMATICAL BOLD SMALL O]
+alias	\U0001D429	\x70	# 𝐩 [MATHEMATICAL BOLD SMALL P]
+alias	\U0001D42A	\x71	# 𝐪 [MATHEMATICAL BOLD SMALL Q]
+alias	\U0001D42B	\x72	# 𝐫 [MATHEMATICAL BOLD SMALL R]
+alias	\U0001D42C	\x73	# 𝐬 [MATHEMATICAL BOLD SMALL S]
+alias	\U0001D42D	\x74	# 𝐭 [MATHEMATICAL BOLD SMALL T]
+alias	\U0001D42E	\x75	# 𝐮 [MATHEMATICAL BOLD SMALL U]
+alias	\U0001D42F	\x76	# 𝐯 [MATHEMATICAL BOLD SMALL V]
+alias	\U0001D430	\x77	# 𝐰 [MATHEMATICAL BOLD SMALL W]
+alias	\U0001D431	\x78	# 𝐱 [MATHEMATICAL BOLD SMALL X]
+alias	\U0001D432	\x79	# 𝐲 [MATHEMATICAL BOLD SMALL Y]
+alias	\U0001D433	\x7A	# 𝐳 [MATHEMATICAL BOLD SMALL Z]
+
+alias	\U0001D434	\x41	# 𝐴 [MATHEMATICAL ITALIC CAPITAL A]
+alias	\U0001D435	\x42	# 𝐵 [MATHEMATICAL ITALIC CAPITAL B]
+alias	\U0001D436	\x43	# 𝐶 [MATHEMATICAL ITALIC CAPITAL C]
+alias	\U0001D437	\x44	# 𝐷 [MATHEMATICAL ITALIC CAPITAL D]
+alias	\U0001D438	\x45	# 𝐸 [MATHEMATICAL ITALIC CAPITAL E]
+alias	\U0001D439	\x46	# 𝐹 [MATHEMATICAL ITALIC CAPITAL F]
+alias	\U0001D43A	\x47	# 𝐺 [MATHEMATICAL ITALIC CAPITAL G]
+alias	\U0001D43B	\x48	# 𝐻 [MATHEMATICAL ITALIC CAPITAL H]
+alias	\U0001D43C	\x49	# 𝐼 [MATHEMATICAL ITALIC CAPITAL I]
+alias	\U0001D43D	\x4A	# 𝐽 [MATHEMATICAL ITALIC CAPITAL J]
+alias	\U0001D43E	\x4B	# 𝐾 [MATHEMATICAL ITALIC CAPITAL K]
+alias	\U0001D43F	\x4C	# 𝐿 [MATHEMATICAL ITALIC CAPITAL L]
+alias	\U0001D440	\x4D	# 𝑀 [MATHEMATICAL ITALIC CAPITAL M]
+alias	\U0001D441	\x4E	# 𝑁 [MATHEMATICAL ITALIC CAPITAL N]
+alias	\U0001D442	\x4F	# 𝑂 [MATHEMATICAL ITALIC CAPITAL O]
+alias	\U0001D443	\x50	# 𝑃 [MATHEMATICAL ITALIC CAPITAL P]
+alias	\U0001D444	\x51	# 𝑄 [MATHEMATICAL ITALIC CAPITAL Q]
+alias	\U0001D445	\x52	# 𝑅 [MATHEMATICAL ITALIC CAPITAL R]
+alias	\U0001D446	\x53	# 𝑆 [MATHEMATICAL ITALIC CAPITAL S]
+alias	\U0001D447	\x54	# 𝑇 [MATHEMATICAL ITALIC CAPITAL T]
+alias	\U0001D448	\x55	# 𝑈 [MATHEMATICAL ITALIC CAPITAL U]
+alias	\U0001D449	\x56	# 𝑉 [MATHEMATICAL ITALIC CAPITAL V]
+alias	\U0001D44A	\x57	# 𝑊 [MATHEMATICAL ITALIC CAPITAL W]
+alias	\U0001D44B	\x58	# 𝑋 [MATHEMATICAL ITALIC CAPITAL X]
+alias	\U0001D44C	\x59	# 𝑌 [MATHEMATICAL ITALIC CAPITAL Y]
+alias	\U0001D44D	\x5A	# 𝑍 [MATHEMATICAL ITALIC CAPITAL Z]
+
+alias	\U0001D44E	\x61	# 𝑎 [MATHEMATICAL ITALIC SMALL A]
+alias	\U0001D44F	\x62	# 𝑏 [MATHEMATICAL ITALIC SMALL B]
+alias	\U0001D450	\x63	# 𝑐 [MATHEMATICAL ITALIC SMALL C]
+alias	\U0001D451	\x64	# 𝑑 [MATHEMATICAL ITALIC SMALL D]
+alias	\U0001D452	\x65	# 𝑒 [MATHEMATICAL ITALIC SMALL E]
+alias	\U0001D453	\x66	# 𝑓 [MATHEMATICAL ITALIC SMALL F]
+alias	\U0001D454	\x67	# 𝑔 [MATHEMATICAL ITALIC SMALL G]
+alias	\U0001D455	\x68	#   [<unassigned-1D455>]
+alias	\U0001D456	\x69	# 𝑖 [MATHEMATICAL ITALIC SMALL I]
+alias	\U0001D457	\x6A	# 𝑗 [MATHEMATICAL ITALIC SMALL J]
+alias	\U0001D458	\x6B	# 𝑘 [MATHEMATICAL ITALIC SMALL K]
+alias	\U0001D459	\x6C	# 𝑙 [MATHEMATICAL ITALIC SMALL L]
+alias	\U0001D45A	\x6D	# 𝑚 [MATHEMATICAL ITALIC SMALL M]
+alias	\U0001D45B	\x6E	# 𝑛 [MATHEMATICAL ITALIC SMALL N]
+alias	\U0001D45C	\x6F	# 𝑜 [MATHEMATICAL ITALIC SMALL O]
+alias	\U0001D45D	\x70	# 𝑝 [MATHEMATICAL ITALIC SMALL P]
+alias	\U0001D45E	\x71	# 𝑞 [MATHEMATICAL ITALIC SMALL Q]
+alias	\U0001D45F	\x72	# 𝑟 [MATHEMATICAL ITALIC SMALL R]
+alias	\U0001D460	\x73	# 𝑠 [MATHEMATICAL ITALIC SMALL S]
+alias	\U0001D461	\x74	# 𝑡 [MATHEMATICAL ITALIC SMALL T]
+alias	\U0001D462	\x75	# 𝑢 [MATHEMATICAL ITALIC SMALL U]
+alias	\U0001D463	\x76	# 𝑣 [MATHEMATICAL ITALIC SMALL V]
+alias	\U0001D464	\x77	# 𝑤 [MATHEMATICAL ITALIC SMALL W]
+alias	\U0001D465	\x78	# 𝑥 [MATHEMATICAL ITALIC SMALL X]
+alias	\U0001D466	\x79	# 𝑦 [MATHEMATICAL ITALIC SMALL Y]
+alias	\U0001D467	\x7A	# 𝑧 [MATHEMATICAL ITALIC SMALL Z]
+
+alias	\U0001D468	\x41	# 𝑨 [MATHEMATICAL BOLD ITALIC CAPITAL A]
+alias	\U0001D469	\x42	# 𝑩 [MATHEMATICAL BOLD ITALIC CAPITAL B]
+alias	\U0001D46A	\x43	# 𝑪 [MATHEMATICAL BOLD ITALIC CAPITAL C]
+alias	\U0001D46B	\x44	# 𝑫 [MATHEMATICAL BOLD ITALIC CAPITAL D]
+alias	\U0001D46C	\x45	# 𝑬 [MATHEMATICAL BOLD ITALIC CAPITAL E]
+alias	\U0001D46D	\x46	# 𝑭 [MATHEMATICAL BOLD ITALIC CAPITAL F]
+alias	\U0001D46E	\x47	# 𝑮 [MATHEMATICAL BOLD ITALIC CAPITAL G]
+alias	\U0001D46F	\x48	# 𝑯 [MATHEMATICAL BOLD ITALIC CAPITAL H]
+alias	\U0001D470	\x49	# 𝑰 [MATHEMATICAL BOLD ITALIC CAPITAL I]
+alias	\U0001D471	\x4A	# 𝑱 [MATHEMATICAL BOLD ITALIC CAPITAL J]
+alias	\U0001D472	\x4B	# 𝑲 [MATHEMATICAL BOLD ITALIC CAPITAL K]
+alias	\U0001D473	\x4C	# 𝑳 [MATHEMATICAL BOLD ITALIC CAPITAL L]
+alias	\U0001D474	\x4D	# 𝑴 [MATHEMATICAL BOLD ITALIC CAPITAL M]
+alias	\U0001D475	\x4E	# 𝑵 [MATHEMATICAL BOLD ITALIC CAPITAL N]
+alias	\U0001D476	\x4F	# 𝑶 [MATHEMATICAL BOLD ITALIC CAPITAL O]
+alias	\U0001D477	\x50	# 𝑷 [MATHEMATICAL BOLD ITALIC CAPITAL P]
+alias	\U0001D478	\x51	# 𝑸 [MATHEMATICAL BOLD ITALIC CAPITAL Q]
+alias	\U0001D479	\x52	# 𝑹 [MATHEMATICAL BOLD ITALIC CAPITAL R]
+alias	\U0001D47A	\x53	# 𝑺 [MATHEMATICAL BOLD ITALIC CAPITAL S]
+alias	\U0001D47B	\x54	# 𝑻 [MATHEMATICAL BOLD ITALIC CAPITAL T]
+alias	\U0001D47C	\x55	# 𝑼 [MATHEMATICAL BOLD ITALIC CAPITAL U]
+alias	\U0001D47D	\x56	# 𝑽 [MATHEMATICAL BOLD ITALIC CAPITAL V]
+alias	\U0001D47E	\x57	# 𝑾 [MATHEMATICAL BOLD ITALIC CAPITAL W]
+alias	\U0001D47F	\x58	# 𝑿 [MATHEMATICAL BOLD ITALIC CAPITAL X]
+alias	\U0001D480	\x59	# 𝒀 [MATHEMATICAL BOLD ITALIC CAPITAL Y]
+alias	\U0001D481	\x5A	# 𝒁 [MATHEMATICAL BOLD ITALIC CAPITAL Z]
+
+alias	\U0001D482	\x61	# 𝒂 [MATHEMATICAL BOLD ITALIC SMALL A]
+alias	\U0001D483	\x62	# 𝒃 [MATHEMATICAL BOLD ITALIC SMALL B]
+alias	\U0001D484	\x63	# 𝒄 [MATHEMATICAL BOLD ITALIC SMALL C]
+alias	\U0001D485	\x64	# 𝒅 [MATHEMATICAL BOLD ITALIC SMALL D]
+alias	\U0001D486	\x65	# 𝒆 [MATHEMATICAL BOLD ITALIC SMALL E]
+alias	\U0001D487	\x66	# 𝒇 [MATHEMATICAL BOLD ITALIC SMALL F]
+alias	\U0001D488	\x67	# 𝒈 [MATHEMATICAL BOLD ITALIC SMALL G]
+alias	\U0001D489	\x68	# 𝒉 [MATHEMATICAL BOLD ITALIC SMALL H]
+alias	\U0001D48A	\x69	# 𝒊 [MATHEMATICAL BOLD ITALIC SMALL I]
+alias	\U0001D48B	\x6A	# 𝒋 [MATHEMATICAL BOLD ITALIC SMALL J]
+alias	\U0001D48C	\x6B	# 𝒌 [MATHEMATICAL BOLD ITALIC SMALL K]
+alias	\U0001D48D	\x6C	# 𝒍 [MATHEMATICAL BOLD ITALIC SMALL L]
+alias	\U0001D48E	\x6D	# 𝒎 [MATHEMATICAL BOLD ITALIC SMALL M]
+alias	\U0001D48F	\x6E	# 𝒏 [MATHEMATICAL BOLD ITALIC SMALL N]
+alias	\U0001D490	\x6F	# 𝒐 [MATHEMATICAL BOLD ITALIC SMALL O]
+alias	\U0001D491	\x70	# 𝒑 [MATHEMATICAL BOLD ITALIC SMALL P]
+alias	\U0001D492	\x71	# 𝒒 [MATHEMATICAL BOLD ITALIC SMALL Q]
+alias	\U0001D493	\x72	# 𝒓 [MATHEMATICAL BOLD ITALIC SMALL R]
+alias	\U0001D494	\x73	# 𝒔 [MATHEMATICAL BOLD ITALIC SMALL S]
+alias	\U0001D495	\x74	# 𝒕 [MATHEMATICAL BOLD ITALIC SMALL T]
+alias	\U0001D496	\x75	# 𝒖 [MATHEMATICAL BOLD ITALIC SMALL U]
+alias	\U0001D497	\x76	# 𝒗 [MATHEMATICAL BOLD ITALIC SMALL V]
+alias	\U0001D498	\x77	# 𝒘 [MATHEMATICAL BOLD ITALIC SMALL W]
+alias	\U0001D499	\x78	# 𝒙 [MATHEMATICAL BOLD ITALIC SMALL X]
+alias	\U0001D49A	\x79	# 𝒚 [MATHEMATICAL BOLD ITALIC SMALL Y]
+alias	\U0001D49B	\x7A	# 𝒛 [MATHEMATICAL BOLD ITALIC SMALL Z]
+
+alias	\U0001D49C	\x41	# 𝒜 [MATHEMATICAL SCRIPT CAPITAL A]
+alias	\U0001D49D	\x42	#   [<unassigned-1D49D>]
+alias	\U0001D49E	\x43	# 𝒞 [MATHEMATICAL SCRIPT CAPITAL C]
+alias	\U0001D49F	\x44	# 𝒟 [MATHEMATICAL SCRIPT CAPITAL D]
+alias	\U0001D4A0	\x45	#   [<unassigned-1D4A0>]
+alias	\U0001D4A1	\x46	#   [<unassigned-1D4A1>]
+alias	\U0001D4A2	\x47	# 𝒢 [MATHEMATICAL SCRIPT CAPITAL G]
+alias	\U0001D4A3	\x48	#   [<unassigned-1D4A3>]
+alias	\U0001D4A4	\x49	#   [<unassigned-1D4A4>]
+alias	\U0001D4A5	\x4A	# 𝒥 [MATHEMATICAL SCRIPT CAPITAL J]
+alias	\U0001D4A6	\x4B	# 𝒦 [MATHEMATICAL SCRIPT CAPITAL K]
+alias	\U0001D4A7	\x4C	#   [<unassigned-1D4A7>]
+alias	\U0001D4A8	\x4D	#   [<unassigned-1D4A8>]
+alias	\U0001D4A9	\x4E	# 𝒩 [MATHEMATICAL SCRIPT CAPITAL N]
+alias	\U0001D4AA	\x4F	# 𝒪 [MATHEMATICAL SCRIPT CAPITAL O]
+alias	\U0001D4AB	\x50	# 𝒫 [MATHEMATICAL SCRIPT CAPITAL P]
+alias	\U0001D4AC	\x51	# 𝒬 [MATHEMATICAL SCRIPT CAPITAL Q]
+alias	\U0001D4AD	\x52	#   [<unassigned-1D4AD>]
+alias	\U0001D4AE	\x53	# 𝒮 [MATHEMATICAL SCRIPT CAPITAL S]
+alias	\U0001D4AF	\x54	# 𝒯 [MATHEMATICAL SCRIPT CAPITAL T]
+alias	\U0001D4B0	\x55	# 𝒰 [MATHEMATICAL SCRIPT CAPITAL U]
+alias	\U0001D4B1	\x56	# 𝒱 [MATHEMATICAL SCRIPT CAPITAL V]
+alias	\U0001D4B2	\x57	# 𝒲 [MATHEMATICAL SCRIPT CAPITAL W]
+alias	\U0001D4B3	\x58	# 𝒳 [MATHEMATICAL SCRIPT CAPITAL X]
+alias	\U0001D4B4	\x59	# 𝒴 [MATHEMATICAL SCRIPT CAPITAL Y]
+alias	\U0001D4B5	\x5A	# 𝒵 [MATHEMATICAL SCRIPT CAPITAL Z]
+
+alias	\U0001D4B6	\x61	# 𝒶 [MATHEMATICAL SCRIPT SMALL A]
+alias	\U0001D4B7	\x62	# 𝒷 [MATHEMATICAL SCRIPT SMALL B]
+alias	\U0001D4B8	\x63	# 𝒸 [MATHEMATICAL SCRIPT SMALL C]
+alias	\U0001D4B9	\x64	# 𝒹 [MATHEMATICAL SCRIPT SMALL D]
+alias	\U0001D4BA	\x65	#   [<unassigned-1D4BA>]
+alias	\U0001D4BB	\x66	# 𝒻 [MATHEMATICAL SCRIPT SMALL F]
+alias	\U0001D4BC	\x67	#   [<unassigned-1D4BC>]
+alias	\U0001D4BD	\x68	# 𝒽 [MATHEMATICAL SCRIPT SMALL H]
+alias	\U0001D4BE	\x69	# 𝒾 [MATHEMATICAL SCRIPT SMALL I]
+alias	\U0001D4BF	\x6A	# 𝒿 [MATHEMATICAL SCRIPT SMALL J]
+alias	\U0001D4C0	\x6B	# 𝓀 [MATHEMATICAL SCRIPT SMALL K]
+alias	\U0001D4C1	\x6C	# 𝓁 [MATHEMATICAL SCRIPT SMALL L]
+alias	\U0001D4C2	\x6D	# 𝓂 [MATHEMATICAL SCRIPT SMALL M]
+alias	\U0001D4C3	\x6E	# 𝓃 [MATHEMATICAL SCRIPT SMALL N]
+alias	\U0001D4C4	\x6F	#   [<unassigned-1D4C4>]
+alias	\U0001D4C5	\x70	# 𝓅 [MATHEMATICAL SCRIPT SMALL P]
+alias	\U0001D4C6	\x71	# 𝓆 [MATHEMATICAL SCRIPT SMALL Q]
+alias	\U0001D4C7	\x72	# 𝓇 [MATHEMATICAL SCRIPT SMALL R]
+alias	\U0001D4C8	\x73	# 𝓈 [MATHEMATICAL SCRIPT SMALL S]
+alias	\U0001D4C9	\x74	# 𝓉 [MATHEMATICAL SCRIPT SMALL T]
+alias	\U0001D4CA	\x75	# 𝓊 [MATHEMATICAL SCRIPT SMALL U]
+alias	\U0001D4CB	\x76	# 𝓋 [MATHEMATICAL SCRIPT SMALL V]
+alias	\U0001D4CC	\x77	# 𝓌 [MATHEMATICAL SCRIPT SMALL W]
+alias	\U0001D4CD	\x78	# 𝓍 [MATHEMATICAL SCRIPT SMALL X]
+alias	\U0001D4CE	\x79	# 𝓎 [MATHEMATICAL SCRIPT SMALL Y]
+alias	\U0001D4CF	\x7A	# 𝓏 [MATHEMATICAL SCRIPT SMALL Z]
+
+alias	\U0001D4D0	\x41	# 𝓐 [MATHEMATICAL BOLD SCRIPT CAPITAL A]
+alias	\U0001D4D1	\x42	# 𝓑 [MATHEMATICAL BOLD SCRIPT CAPITAL B]
+alias	\U0001D4D2	\x43	# 𝓒 [MATHEMATICAL BOLD SCRIPT CAPITAL C]
+alias	\U0001D4D3	\x44	# 𝓓 [MATHEMATICAL BOLD SCRIPT CAPITAL D]
+alias	\U0001D4D4	\x45	# 𝓔 [MATHEMATICAL BOLD SCRIPT CAPITAL E]
+alias	\U0001D4D5	\x46	# 𝓕 [MATHEMATICAL BOLD SCRIPT CAPITAL F]
+alias	\U0001D4D6	\x47	# 𝓖 [MATHEMATICAL BOLD SCRIPT CAPITAL G]
+alias	\U0001D4D7	\x48	# 𝓗 [MATHEMATICAL BOLD SCRIPT CAPITAL H]
+alias	\U0001D4D8	\x49	# 𝓘 [MATHEMATICAL BOLD SCRIPT CAPITAL I]
+alias	\U0001D4D9	\x4A	# 𝓙 [MATHEMATICAL BOLD SCRIPT CAPITAL J]
+alias	\U0001D4DA	\x4B	# 𝓚 [MATHEMATICAL BOLD SCRIPT CAPITAL K]
+alias	\U0001D4DB	\x4C	# 𝓛 [MATHEMATICAL BOLD SCRIPT CAPITAL L]
+alias	\U0001D4DC	\x4D	# 𝓜 [MATHEMATICAL BOLD SCRIPT CAPITAL M]
+alias	\U0001D4DD	\x4E	# 𝓝 [MATHEMATICAL BOLD SCRIPT CAPITAL N]
+alias	\U0001D4DE	\x4F	# 𝓞 [MATHEMATICAL BOLD SCRIPT CAPITAL O]
+alias	\U0001D4DF	\x50	# 𝓟 [MATHEMATICAL BOLD SCRIPT CAPITAL P]
+alias	\U0001D4E0	\x51	# 𝓠 [MATHEMATICAL BOLD SCRIPT CAPITAL Q]
+alias	\U0001D4E1	\x52	# 𝓡 [MATHEMATICAL BOLD SCRIPT CAPITAL R]
+alias	\U0001D4E2	\x53	# 𝓢 [MATHEMATICAL BOLD SCRIPT CAPITAL S]
+alias	\U0001D4E3	\x54	# 𝓣 [MATHEMATICAL BOLD SCRIPT CAPITAL T]
+alias	\U0001D4E4	\x55	# 𝓤 [MATHEMATICAL BOLD SCRIPT CAPITAL U]
+alias	\U0001D4E5	\x56	# 𝓥 [MATHEMATICAL BOLD SCRIPT CAPITAL V]
+alias	\U0001D4E6	\x57	# 𝓦 [MATHEMATICAL BOLD SCRIPT CAPITAL W]
+alias	\U0001D4E7	\x58	# 𝓧 [MATHEMATICAL BOLD SCRIPT CAPITAL X]
+alias	\U0001D4E8	\x59	# 𝓨 [MATHEMATICAL BOLD SCRIPT CAPITAL Y]
+alias	\U0001D4E9	\x5A	# 𝓩 [MATHEMATICAL BOLD SCRIPT CAPITAL Z]
+
+alias	\U0001D4EA	\x61	# 𝓪 [MATHEMATICAL BOLD SCRIPT SMALL A]
+alias	\U0001D4EB	\x62	# 𝓫 [MATHEMATICAL BOLD SCRIPT SMALL B]
+alias	\U0001D4EC	\x63	# 𝓬 [MATHEMATICAL BOLD SCRIPT SMALL C]
+alias	\U0001D4ED	\x64	# 𝓭 [MATHEMATICAL BOLD SCRIPT SMALL D]
+alias	\U0001D4EE	\x65	# 𝓮 [MATHEMATICAL BOLD SCRIPT SMALL E]
+alias	\U0001D4EF	\x66	# 𝓯 [MATHEMATICAL BOLD SCRIPT SMALL F]
+alias	\U0001D4F0	\x67	# 𝓰 [MATHEMATICAL BOLD SCRIPT SMALL G]
+alias	\U0001D4F1	\x68	# 𝓱 [MATHEMATICAL BOLD SCRIPT SMALL H]
+alias	\U0001D4F2	\x69	# 𝓲 [MATHEMATICAL BOLD SCRIPT SMALL I]
+alias	\U0001D4F3	\x6A	# 𝓳 [MATHEMATICAL BOLD SCRIPT SMALL J]
+alias	\U0001D4F4	\x6B	# 𝓴 [MATHEMATICAL BOLD SCRIPT SMALL K]
+alias	\U0001D4F5	\x6C	# 𝓵 [MATHEMATICAL BOLD SCRIPT SMALL L]
+alias	\U0001D4F6	\x6D	# 𝓶 [MATHEMATICAL BOLD SCRIPT SMALL M]
+alias	\U0001D4F7	\x6E	# 𝓷 [MATHEMATICAL BOLD SCRIPT SMALL N]
+alias	\U0001D4F8	\x6F	# 𝓸 [MATHEMATICAL BOLD SCRIPT SMALL O]
+alias	\U0001D4F9	\x70	# 𝓹 [MATHEMATICAL BOLD SCRIPT SMALL P]
+alias	\U0001D4FA	\x71	# 𝓺 [MATHEMATICAL BOLD SCRIPT SMALL Q]
+alias	\U0001D4FB	\x72	# 𝓻 [MATHEMATICAL BOLD SCRIPT SMALL R]
+alias	\U0001D4FC	\x73	# 𝓼 [MATHEMATICAL BOLD SCRIPT SMALL S]
+alias	\U0001D4FD	\x74	# 𝓽 [MATHEMATICAL BOLD SCRIPT SMALL T]
+alias	\U0001D4FE	\x75	# 𝓾 [MATHEMATICAL BOLD SCRIPT SMALL U]
+alias	\U0001D4FF	\x76	# 𝓿 [MATHEMATICAL BOLD SCRIPT SMALL V]
+alias	\U0001D500	\x77	# 𝔀 [MATHEMATICAL BOLD SCRIPT SMALL W]
+alias	\U0001D501	\x78	# 𝔁 [MATHEMATICAL BOLD SCRIPT SMALL X]
+alias	\U0001D502	\x79	# 𝔂 [MATHEMATICAL BOLD SCRIPT SMALL Y]
+alias	\U0001D503	\x7A	# 𝔃 [MATHEMATICAL BOLD SCRIPT SMALL Z]
+
+alias	\U0001D504	\x41	# 𝔄 [MATHEMATICAL FRAKTUR CAPITAL A]
+alias	\U0001D505	\x42	# 𝔅 [MATHEMATICAL FRAKTUR CAPITAL B]
+alias	\U0001D506	\x43	#   [<unassigned-1D506>]
+alias	\U0001D507	\x44	# 𝔇 [MATHEMATICAL FRAKTUR CAPITAL D]
+alias	\U0001D508	\x45	# 𝔈 [MATHEMATICAL FRAKTUR CAPITAL E]
+alias	\U0001D509	\x46	# 𝔉 [MATHEMATICAL FRAKTUR CAPITAL F]
+alias	\U0001D50A	\x47	# 𝔊 [MATHEMATICAL FRAKTUR CAPITAL G]
+alias	\U0001D50B	\x48	#   [<unassigned-1D50B>]
+alias	\U0001D50C	\x49	#   [<unassigned-1D50C>]
+alias	\U0001D50D	\x4A	# 𝔍 [MATHEMATICAL FRAKTUR CAPITAL J]
+alias	\U0001D50E	\x4B	# 𝔎 [MATHEMATICAL FRAKTUR CAPITAL K]
+alias	\U0001D50F	\x4C	# 𝔏 [MATHEMATICAL FRAKTUR CAPITAL L]
+alias	\U0001D510	\x4D	# 𝔐 [MATHEMATICAL FRAKTUR CAPITAL M]
+alias	\U0001D511	\x4E	# 𝔑 [MATHEMATICAL FRAKTUR CAPITAL N]
+alias	\U0001D512	\x4F	# 𝔒 [MATHEMATICAL FRAKTUR CAPITAL O]
+alias	\U0001D513	\x50	# 𝔓 [MATHEMATICAL FRAKTUR CAPITAL P]
+alias	\U0001D514	\x51	# 𝔔 [MATHEMATICAL FRAKTUR CAPITAL Q]
+alias	\U0001D515	\x52	#   [<unassigned-1D515>]
+alias	\U0001D516	\x53	# 𝔖 [MATHEMATICAL FRAKTUR CAPITAL S]
+alias	\U0001D517	\x54	# 𝔗 [MATHEMATICAL FRAKTUR CAPITAL T]
+alias	\U0001D518	\x55	# 𝔘 [MATHEMATICAL FRAKTUR CAPITAL U]
+alias	\U0001D519	\x56	# 𝔙 [MATHEMATICAL FRAKTUR CAPITAL V]
+alias	\U0001D51A	\x57	# 𝔚 [MATHEMATICAL FRAKTUR CAPITAL W]
+alias	\U0001D51B	\x58	# 𝔛 [MATHEMATICAL FRAKTUR CAPITAL X]
+alias	\U0001D51C	\x59	# 𝔜 [MATHEMATICAL FRAKTUR CAPITAL Y]
+alias	\U0001D51D	\x5A	#   [<unassigned-1D51D>]
+
+alias	\U0001D51E	\x61	# 𝔞 [MATHEMATICAL FRAKTUR SMALL A]
+alias	\U0001D51F	\x62	# 𝔟 [MATHEMATICAL FRAKTUR SMALL B]
+alias	\U0001D520	\x63	# 𝔠 [MATHEMATICAL FRAKTUR SMALL C]
+alias	\U0001D521	\x64	# 𝔡 [MATHEMATICAL FRAKTUR SMALL D]
+alias	\U0001D522	\x65	# 𝔢 [MATHEMATICAL FRAKTUR SMALL E]
+alias	\U0001D523	\x66	# 𝔣 [MATHEMATICAL FRAKTUR SMALL F]
+alias	\U0001D524	\x67	# 𝔤 [MATHEMATICAL FRAKTUR SMALL G]
+alias	\U0001D525	\x68	# 𝔥 [MATHEMATICAL FRAKTUR SMALL H]
+alias	\U0001D526	\x69	# 𝔦 [MATHEMATICAL FRAKTUR SMALL I]
+alias	\U0001D527	\x6A	# 𝔧 [MATHEMATICAL FRAKTUR SMALL J]
+alias	\U0001D528	\x6B	# 𝔨 [MATHEMATICAL FRAKTUR SMALL K]
+alias	\U0001D529	\x6C	# 𝔩 [MATHEMATICAL FRAKTUR SMALL L]
+alias	\U0001D52A	\x6D	# 𝔪 [MATHEMATICAL FRAKTUR SMALL M]
+alias	\U0001D52B	\x6E	# 𝔫 [MATHEMATICAL FRAKTUR SMALL N]
+alias	\U0001D52C	\x6F	# 𝔬 [MATHEMATICAL FRAKTUR SMALL O]
+alias	\U0001D52D	\x70	# 𝔭 [MATHEMATICAL FRAKTUR SMALL P]
+alias	\U0001D52E	\x71	# 𝔮 [MATHEMATICAL FRAKTUR SMALL Q]
+alias	\U0001D52F	\x72	# 𝔯 [MATHEMATICAL FRAKTUR SMALL R]
+alias	\U0001D530	\x73	# 𝔰 [MATHEMATICAL FRAKTUR SMALL S]
+alias	\U0001D531	\x74	# 𝔱 [MATHEMATICAL FRAKTUR SMALL T]
+alias	\U0001D532	\x75	# 𝔲 [MATHEMATICAL FRAKTUR SMALL U]
+alias	\U0001D533	\x76	# 𝔳 [MATHEMATICAL FRAKTUR SMALL V]
+alias	\U0001D534	\x77	# 𝔴 [MATHEMATICAL FRAKTUR SMALL W]
+alias	\U0001D535	\x78	# 𝔵 [MATHEMATICAL FRAKTUR SMALL X]
+alias	\U0001D536	\x79	# 𝔶 [MATHEMATICAL FRAKTUR SMALL Y]
+alias	\U0001D537	\x7A	# 𝔷 [MATHEMATICAL FRAKTUR SMALL Z]
+
+alias	\U0001D538	\x41	# 𝔸 [MATHEMATICAL DOUBLE-STRUCK CAPITAL A]
+alias	\U0001D539	\x42	# 𝔹 [MATHEMATICAL DOUBLE-STRUCK CAPITAL B]
+alias	\U0001D53A	\x43	#   [<unassigned-1D53A>]
+alias	\U0001D53B	\x44	# 𝔻 [MATHEMATICAL DOUBLE-STRUCK CAPITAL D]
+alias	\U0001D53C	\x45	# 𝔼 [MATHEMATICAL DOUBLE-STRUCK CAPITAL E]
+alias	\U0001D53D	\x46	# 𝔽 [MATHEMATICAL DOUBLE-STRUCK CAPITAL F]
+alias	\U0001D53E	\x47	# 𝔾 [MATHEMATICAL DOUBLE-STRUCK CAPITAL G]
+alias	\U0001D53F	\x48	#   [<unassigned-1D53F>]
+alias	\U0001D540	\x49	# 𝕀 [MATHEMATICAL DOUBLE-STRUCK CAPITAL I]
+alias	\U0001D541	\x4A	# 𝕁 [MATHEMATICAL DOUBLE-STRUCK CAPITAL J]
+alias	\U0001D542	\x4B	# 𝕂 [MATHEMATICAL DOUBLE-STRUCK CAPITAL K]
+alias	\U0001D543	\x4C	# 𝕃 [MATHEMATICAL DOUBLE-STRUCK CAPITAL L]
+alias	\U0001D544	\x4D	# 𝕄 [MATHEMATICAL DOUBLE-STRUCK CAPITAL M]
+alias	\U0001D545	\x4E	#   [<unassigned-1D545>]
+alias	\U0001D546	\x4F	# 𝕆 [MATHEMATICAL DOUBLE-STRUCK CAPITAL O]
+alias	\U0001D547	\x50	#   [<unassigned-1D547>]
+alias	\U0001D548	\x51	#   [<unassigned-1D548>]
+alias	\U0001D549	\x52	#   [<unassigned-1D549>]
+alias	\U0001D54A	\x53	# 𝕊 [MATHEMATICAL DOUBLE-STRUCK CAPITAL S]
+alias	\U0001D54B	\x54	# 𝕋 [MATHEMATICAL DOUBLE-STRUCK CAPITAL T]
+alias	\U0001D54C	\x55	# 𝕌 [MATHEMATICAL DOUBLE-STRUCK CAPITAL U]
+alias	\U0001D54D	\x56	# 𝕍 [MATHEMATICAL DOUBLE-STRUCK CAPITAL V]
+alias	\U0001D54E	\x57	# 𝕎 [MATHEMATICAL DOUBLE-STRUCK CAPITAL W]
+alias	\U0001D54F	\x58	# 𝕏 [MATHEMATICAL DOUBLE-STRUCK CAPITAL X]
+alias	\U0001D550	\x59	# 𝕐 [MATHEMATICAL DOUBLE-STRUCK CAPITAL Y]
+alias	\U0001D551	\x5A	#   [<unassigned-1D551>]
+
+alias	\U0001D552	\x61	# 𝕒 [MATHEMATICAL DOUBLE-STRUCK SMALL A]
+alias	\U0001D553	\x62	# 𝕓 [MATHEMATICAL DOUBLE-STRUCK SMALL B]
+alias	\U0001D554	\x63	# 𝕔 [MATHEMATICAL DOUBLE-STRUCK SMALL C]
+alias	\U0001D555	\x64	# 𝕕 [MATHEMATICAL DOUBLE-STRUCK SMALL D]
+alias	\U0001D556	\x65	# 𝕖 [MATHEMATICAL DOUBLE-STRUCK SMALL E]
+alias	\U0001D557	\x66	# 𝕗 [MATHEMATICAL DOUBLE-STRUCK SMALL F]
+alias	\U0001D558	\x67	# 𝕘 [MATHEMATICAL DOUBLE-STRUCK SMALL G]
+alias	\U0001D559	\x68	# 𝕙 [MATHEMATICAL DOUBLE-STRUCK SMALL H]
+alias	\U0001D55A	\x69	# 𝕚 [MATHEMATICAL DOUBLE-STRUCK SMALL I]
+alias	\U0001D55B	\x6A	# 𝕛 [MATHEMATICAL DOUBLE-STRUCK SMALL J]
+alias	\U0001D55C	\x6B	# 𝕜 [MATHEMATICAL DOUBLE-STRUCK SMALL K]
+alias	\U0001D55D	\x6C	# 𝕝 [MATHEMATICAL DOUBLE-STRUCK SMALL L]
+alias	\U0001D55E	\x6D	# 𝕞 [MATHEMATICAL DOUBLE-STRUCK SMALL M]
+alias	\U0001D55F	\x6E	# 𝕟 [MATHEMATICAL DOUBLE-STRUCK SMALL N]
+alias	\U0001D560	\x6F	# 𝕠 [MATHEMATICAL DOUBLE-STRUCK SMALL O]
+alias	\U0001D561	\x70	# 𝕡 [MATHEMATICAL DOUBLE-STRUCK SMALL P]
+alias	\U0001D562	\x71	# 𝕢 [MATHEMATICAL DOUBLE-STRUCK SMALL Q]
+alias	\U0001D563	\x72	# 𝕣 [MATHEMATICAL DOUBLE-STRUCK SMALL R]
+alias	\U0001D564	\x73	# 𝕤 [MATHEMATICAL DOUBLE-STRUCK SMALL S]
+alias	\U0001D565	\x74	# 𝕥 [MATHEMATICAL DOUBLE-STRUCK SMALL T]
+alias	\U0001D566	\x75	# 𝕦 [MATHEMATICAL DOUBLE-STRUCK SMALL U]
+alias	\U0001D567	\x76	# 𝕧 [MATHEMATICAL DOUBLE-STRUCK SMALL V]
+alias	\U0001D568	\x77	# 𝕨 [MATHEMATICAL DOUBLE-STRUCK SMALL W]
+alias	\U0001D569	\x78	# 𝕩 [MATHEMATICAL DOUBLE-STRUCK SMALL X]
+alias	\U0001D56A	\x79	# 𝕪 [MATHEMATICAL DOUBLE-STRUCK SMALL Y]
+alias	\U0001D56B	\x7A	# 𝕫 [MATHEMATICAL DOUBLE-STRUCK SMALL Z]
+
+alias	\U0001D56C	\x41	# 𝕬 [MATHEMATICAL BOLD FRAKTUR CAPITAL A]
+alias	\U0001D56D	\x42	# 𝕭 [MATHEMATICAL BOLD FRAKTUR CAPITAL B]
+alias	\U0001D56E	\x43	# 𝕮 [MATHEMATICAL BOLD FRAKTUR CAPITAL C]
+alias	\U0001D56F	\x44	# 𝕯 [MATHEMATICAL BOLD FRAKTUR CAPITAL D]
+alias	\U0001D570	\x45	# 𝕰 [MATHEMATICAL BOLD FRAKTUR CAPITAL E]
+alias	\U0001D571	\x46	# 𝕱 [MATHEMATICAL BOLD FRAKTUR CAPITAL F]
+alias	\U0001D572	\x47	# 𝕲 [MATHEMATICAL BOLD FRAKTUR CAPITAL G]
+alias	\U0001D573	\x48	# 𝕳 [MATHEMATICAL BOLD FRAKTUR CAPITAL H]
+alias	\U0001D574	\x49	# 𝕴 [MATHEMATICAL BOLD FRAKTUR CAPITAL I]
+alias	\U0001D575	\x4A	# 𝕵 [MATHEMATICAL BOLD FRAKTUR CAPITAL J]
+alias	\U0001D576	\x4B	# 𝕶 [MATHEMATICAL BOLD FRAKTUR CAPITAL K]
+alias	\U0001D577	\x4C	# 𝕷 [MATHEMATICAL BOLD FRAKTUR CAPITAL L]
+alias	\U0001D578	\x4D	# 𝕸 [MATHEMATICAL BOLD FRAKTUR CAPITAL M]
+alias	\U0001D579	\x4E	# 𝕹 [MATHEMATICAL BOLD FRAKTUR CAPITAL N]
+alias	\U0001D57A	\x4F	# 𝕺 [MATHEMATICAL BOLD FRAKTUR CAPITAL O]
+alias	\U0001D57B	\x50	# 𝕻 [MATHEMATICAL BOLD FRAKTUR CAPITAL P]
+alias	\U0001D57C	\x51	# 𝕼 [MATHEMATICAL BOLD FRAKTUR CAPITAL Q]
+alias	\U0001D57D	\x52	# 𝕽 [MATHEMATICAL BOLD FRAKTUR CAPITAL R]
+alias	\U0001D57E	\x53	# 𝕾 [MATHEMATICAL BOLD FRAKTUR CAPITAL S]
+alias	\U0001D57F	\x54	# 𝕿 [MATHEMATICAL BOLD FRAKTUR CAPITAL T]
+alias	\U0001D580	\x55	# 𝖀 [MATHEMATICAL BOLD FRAKTUR CAPITAL U]
+alias	\U0001D581	\x56	# 𝖁 [MATHEMATICAL BOLD FRAKTUR CAPITAL V]
+alias	\U0001D582	\x57	# 𝖂 [MATHEMATICAL BOLD FRAKTUR CAPITAL W]
+alias	\U0001D583	\x58	# 𝖃 [MATHEMATICAL BOLD FRAKTUR CAPITAL X]
+alias	\U0001D584	\x59	# 𝖄 [MATHEMATICAL BOLD FRAKTUR CAPITAL Y]
+alias	\U0001D585	\x5A	# 𝖅 [MATHEMATICAL BOLD FRAKTUR CAPITAL Z]
+
+alias	\U0001D586	\x61	# 𝖆 [MATHEMATICAL BOLD FRAKTUR SMALL A]
+alias	\U0001D587	\x62	# 𝖇 [MATHEMATICAL BOLD FRAKTUR SMALL B]
+alias	\U0001D588	\x63	# 𝖈 [MATHEMATICAL BOLD FRAKTUR SMALL C]
+alias	\U0001D589	\x64	# 𝖉 [MATHEMATICAL BOLD FRAKTUR SMALL D]
+alias	\U0001D58A	\x65	# 𝖊 [MATHEMATICAL BOLD FRAKTUR SMALL E]
+alias	\U0001D58B	\x66	# 𝖋 [MATHEMATICAL BOLD FRAKTUR SMALL F]
+alias	\U0001D58C	\x67	# 𝖌 [MATHEMATICAL BOLD FRAKTUR SMALL G]
+alias	\U0001D58D	\x68	# 𝖍 [MATHEMATICAL BOLD FRAKTUR SMALL H]
+alias	\U0001D58E	\x69	# 𝖎 [MATHEMATICAL BOLD FRAKTUR SMALL I]
+alias	\U0001D58F	\x6A	# 𝖏 [MATHEMATICAL BOLD FRAKTUR SMALL J]
+alias	\U0001D590	\x6B	# 𝖐 [MATHEMATICAL BOLD FRAKTUR SMALL K]
+alias	\U0001D591	\x6C	# 𝖑 [MATHEMATICAL BOLD FRAKTUR SMALL L]
+alias	\U0001D592	\x6D	# 𝖒 [MATHEMATICAL BOLD FRAKTUR SMALL M]
+alias	\U0001D593	\x6E	# 𝖓 [MATHEMATICAL BOLD FRAKTUR SMALL N]
+alias	\U0001D594	\x6F	# 𝖔 [MATHEMATICAL BOLD FRAKTUR SMALL O]
+alias	\U0001D595	\x70	# 𝖕 [MATHEMATICAL BOLD FRAKTUR SMALL P]
+alias	\U0001D596	\x71	# 𝖖 [MATHEMATICAL BOLD FRAKTUR SMALL Q]
+alias	\U0001D597	\x72	# 𝖗 [MATHEMATICAL BOLD FRAKTUR SMALL R]
+alias	\U0001D598	\x73	# 𝖘 [MATHEMATICAL BOLD FRAKTUR SMALL S]
+alias	\U0001D599	\x74	# 𝖙 [MATHEMATICAL BOLD FRAKTUR SMALL T]
+alias	\U0001D59A	\x75	# 𝖚 [MATHEMATICAL BOLD FRAKTUR SMALL U]
+alias	\U0001D59B	\x76	# 𝖛 [MATHEMATICAL BOLD FRAKTUR SMALL V]
+alias	\U0001D59C	\x77	# 𝖜 [MATHEMATICAL BOLD FRAKTUR SMALL W]
+alias	\U0001D59D	\x78	# 𝖝 [MATHEMATICAL BOLD FRAKTUR SMALL X]
+alias	\U0001D59E	\x79	# 𝖞 [MATHEMATICAL BOLD FRAKTUR SMALL Y]
+alias	\U0001D59F	\x7A	# 𝖟 [MATHEMATICAL BOLD FRAKTUR SMALL Z]
+
+alias	\U0001D5A0	\x41	# 𝖠 [MATHEMATICAL SANS-SERIF CAPITAL A]
+alias	\U0001D5A1	\x42	# 𝖡 [MATHEMATICAL SANS-SERIF CAPITAL B]
+alias	\U0001D5A2	\x43	# 𝖢 [MATHEMATICAL SANS-SERIF CAPITAL C]
+alias	\U0001D5A3	\x44	# 𝖣 [MATHEMATICAL SANS-SERIF CAPITAL D]
+alias	\U0001D5A4	\x45	# 𝖤 [MATHEMATICAL SANS-SERIF CAPITAL E]
+alias	\U0001D5A5	\x46	# 𝖥 [MATHEMATICAL SANS-SERIF CAPITAL F]
+alias	\U0001D5A6	\x47	# 𝖦 [MATHEMATICAL SANS-SERIF CAPITAL G]
+alias	\U0001D5A7	\x48	# 𝖧 [MATHEMATICAL SANS-SERIF CAPITAL H]
+alias	\U0001D5A8	\x49	# 𝖨 [MATHEMATICAL SANS-SERIF CAPITAL I]
+alias	\U0001D5A9	\x4A	# 𝖩 [MATHEMATICAL SANS-SERIF CAPITAL J]
+alias	\U0001D5AA	\x4B	# 𝖪 [MATHEMATICAL SANS-SERIF CAPITAL K]
+alias	\U0001D5AB	\x4C	# 𝖫 [MATHEMATICAL SANS-SERIF CAPITAL L]
+alias	\U0001D5AC	\x4D	# 𝖬 [MATHEMATICAL SANS-SERIF CAPITAL M]
+alias	\U0001D5AD	\x4E	# 𝖭 [MATHEMATICAL SANS-SERIF CAPITAL N]
+alias	\U0001D5AE	\x4F	# 𝖮 [MATHEMATICAL SANS-SERIF CAPITAL O]
+alias	\U0001D5AF	\x50	# 𝖯 [MATHEMATICAL SANS-SERIF CAPITAL P]
+alias	\U0001D5B0	\x51	# 𝖰 [MATHEMATICAL SANS-SERIF CAPITAL Q]
+alias	\U0001D5B1	\x52	# 𝖱 [MATHEMATICAL SANS-SERIF CAPITAL R]
+alias	\U0001D5B2	\x53	# 𝖲 [MATHEMATICAL SANS-SERIF CAPITAL S]
+alias	\U0001D5B3	\x54	# 𝖳 [MATHEMATICAL SANS-SERIF CAPITAL T]
+alias	\U0001D5B4	\x55	# 𝖴 [MATHEMATICAL SANS-SERIF CAPITAL U]
+alias	\U0001D5B5	\x56	# 𝖵 [MATHEMATICAL SANS-SERIF CAPITAL V]
+alias	\U0001D5B6	\x57	# 𝖶 [MATHEMATICAL SANS-SERIF CAPITAL W]
+alias	\U0001D5B7	\x58	# 𝖷 [MATHEMATICAL SANS-SERIF CAPITAL X]
+alias	\U0001D5B8	\x59	# 𝖸 [MATHEMATICAL SANS-SERIF CAPITAL Y]
+alias	\U0001D5B9	\x5A	# 𝖹 [MATHEMATICAL SANS-SERIF CAPITAL Z]
+
+alias	\U0001D5BA	\x61	# 𝖺 [MATHEMATICAL SANS-SERIF SMALL A]
+alias	\U0001D5BB	\x62	# 𝖻 [MATHEMATICAL SANS-SERIF SMALL B]
+alias	\U0001D5BC	\x63	# 𝖼 [MATHEMATICAL SANS-SERIF SMALL C]
+alias	\U0001D5BD	\x64	# 𝖽 [MATHEMATICAL SANS-SERIF SMALL D]
+alias	\U0001D5BE	\x65	# 𝖾 [MATHEMATICAL SANS-SERIF SMALL E]
+alias	\U0001D5BF	\x66	# 𝖿 [MATHEMATICAL SANS-SERIF SMALL F]
+alias	\U0001D5C0	\x67	# 𝗀 [MATHEMATICAL SANS-SERIF SMALL G]
+alias	\U0001D5C1	\x68	# 𝗁 [MATHEMATICAL SANS-SERIF SMALL H]
+alias	\U0001D5C2	\x69	# 𝗂 [MATHEMATICAL SANS-SERIF SMALL I]
+alias	\U0001D5C3	\x6A	# 𝗃 [MATHEMATICAL SANS-SERIF SMALL J]
+alias	\U0001D5C4	\x6B	# 𝗄 [MATHEMATICAL SANS-SERIF SMALL K]
+alias	\U0001D5C5	\x6C	# 𝗅 [MATHEMATICAL SANS-SERIF SMALL L]
+alias	\U0001D5C6	\x6D	# 𝗆 [MATHEMATICAL SANS-SERIF SMALL M]
+alias	\U0001D5C7	\x6E	# 𝗇 [MATHEMATICAL SANS-SERIF SMALL N]
+alias	\U0001D5C8	\x6F	# 𝗈 [MATHEMATICAL SANS-SERIF SMALL O]
+alias	\U0001D5C9	\x70	# 𝗉 [MATHEMATICAL SANS-SERIF SMALL P]
+alias	\U0001D5CA	\x71	# 𝗊 [MATHEMATICAL SANS-SERIF SMALL Q]
+alias	\U0001D5CB	\x72	# 𝗋 [MATHEMATICAL SANS-SERIF SMALL R]
+alias	\U0001D5CC	\x73	# 𝗌 [MATHEMATICAL SANS-SERIF SMALL S]
+alias	\U0001D5CD	\x74	# 𝗍 [MATHEMATICAL SANS-SERIF SMALL T]
+alias	\U0001D5CE	\x75	# 𝗎 [MATHEMATICAL SANS-SERIF SMALL U]
+alias	\U0001D5CF	\x76	# 𝗏 [MATHEMATICAL SANS-SERIF SMALL V]
+alias	\U0001D5D0	\x77	# 𝗐 [MATHEMATICAL SANS-SERIF SMALL W]
+alias	\U0001D5D1	\x78	# 𝗑 [MATHEMATICAL SANS-SERIF SMALL X]
+alias	\U0001D5D2	\x79	# 𝗒 [MATHEMATICAL SANS-SERIF SMALL Y]
+alias	\U0001D5D3	\x7A	# 𝗓 [MATHEMATICAL SANS-SERIF SMALL Z]
+
+alias	\U0001D5D4	\x41	# 𝗔 [MATHEMATICAL SANS-SERIF BOLD CAPITAL A]
+alias	\U0001D5D5	\x42	# 𝗕 [MATHEMATICAL SANS-SERIF BOLD CAPITAL B]
+alias	\U0001D5D6	\x43	# 𝗖 [MATHEMATICAL SANS-SERIF BOLD CAPITAL C]
+alias	\U0001D5D7	\x44	# 𝗗 [MATHEMATICAL SANS-SERIF BOLD CAPITAL D]
+alias	\U0001D5D8	\x45	# 𝗘 [MATHEMATICAL SANS-SERIF BOLD CAPITAL E]
+alias	\U0001D5D9	\x46	# 𝗙 [MATHEMATICAL SANS-SERIF BOLD CAPITAL F]
+alias	\U0001D5DA	\x47	# 𝗚 [MATHEMATICAL SANS-SERIF BOLD CAPITAL G]
+alias	\U0001D5DB	\x48	# 𝗛 [MATHEMATICAL SANS-SERIF BOLD CAPITAL H]
+alias	\U0001D5DC	\x49	# 𝗜 [MATHEMATICAL SANS-SERIF BOLD CAPITAL I]
+alias	\U0001D5DD	\x4A	# 𝗝 [MATHEMATICAL SANS-SERIF BOLD CAPITAL J]
+alias	\U0001D5DE	\x4B	# 𝗞 [MATHEMATICAL SANS-SERIF BOLD CAPITAL K]
+alias	\U0001D5DF	\x4C	# 𝗟 [MATHEMATICAL SANS-SERIF BOLD CAPITAL L]
+alias	\U0001D5E0	\x4D	# 𝗠 [MATHEMATICAL SANS-SERIF BOLD CAPITAL M]
+alias	\U0001D5E1	\x4E	# 𝗡 [MATHEMATICAL SANS-SERIF BOLD CAPITAL N]
+alias	\U0001D5E2	\x4F	# 𝗢 [MATHEMATICAL SANS-SERIF BOLD CAPITAL O]
+alias	\U0001D5E3	\x50	# 𝗣 [MATHEMATICAL SANS-SERIF BOLD CAPITAL P]
+alias	\U0001D5E4	\x51	# 𝗤 [MATHEMATICAL SANS-SERIF BOLD CAPITAL Q]
+alias	\U0001D5E5	\x52	# 𝗥 [MATHEMATICAL SANS-SERIF BOLD CAPITAL R]
+alias	\U0001D5E6	\x53	# 𝗦 [MATHEMATICAL SANS-SERIF BOLD CAPITAL S]
+alias	\U0001D5E7	\x54	# 𝗧 [MATHEMATICAL SANS-SERIF BOLD CAPITAL T]
+alias	\U0001D5E8	\x55	# 𝗨 [MATHEMATICAL SANS-SERIF BOLD CAPITAL U]
+alias	\U0001D5E9	\x56	# 𝗩 [MATHEMATICAL SANS-SERIF BOLD CAPITAL V]
+alias	\U0001D5EA	\x57	# 𝗪 [MATHEMATICAL SANS-SERIF BOLD CAPITAL W]
+alias	\U0001D5EB	\x58	# 𝗫 [MATHEMATICAL SANS-SERIF BOLD CAPITAL X]
+alias	\U0001D5EC	\x59	# 𝗬 [MATHEMATICAL SANS-SERIF BOLD CAPITAL Y]
+alias	\U0001D5ED	\x5A	# 𝗭 [MATHEMATICAL SANS-SERIF BOLD CAPITAL Z]
+
+alias	\U0001D5EE	\x61	# 𝗮 [MATHEMATICAL SANS-SERIF BOLD SMALL A]
+alias	\U0001D5EF	\x62	# 𝗯 [MATHEMATICAL SANS-SERIF BOLD SMALL B]
+alias	\U0001D5F0	\x63	# 𝗰 [MATHEMATICAL SANS-SERIF BOLD SMALL C]
+alias	\U0001D5F1	\x64	# 𝗱 [MATHEMATICAL SANS-SERIF BOLD SMALL D]
+alias	\U0001D5F2	\x65	# 𝗲 [MATHEMATICAL SANS-SERIF BOLD SMALL E]
+alias	\U0001D5F3	\x66	# 𝗳 [MATHEMATICAL SANS-SERIF BOLD SMALL F]
+alias	\U0001D5F4	\x67	# 𝗴 [MATHEMATICAL SANS-SERIF BOLD SMALL G]
+alias	\U0001D5F5	\x68	# 𝗵 [MATHEMATICAL SANS-SERIF BOLD SMALL H]
+alias	\U0001D5F6	\x69	# 𝗶 [MATHEMATICAL SANS-SERIF BOLD SMALL I]
+alias	\U0001D5F7	\x6A	# 𝗷 [MATHEMATICAL SANS-SERIF BOLD SMALL J]
+alias	\U0001D5F8	\x6B	# 𝗸 [MATHEMATICAL SANS-SERIF BOLD SMALL K]
+alias	\U0001D5F9	\x6C	# 𝗹 [MATHEMATICAL SANS-SERIF BOLD SMALL L]
+alias	\U0001D5FA	\x6D	# 𝗺 [MATHEMATICAL SANS-SERIF BOLD SMALL M]
+alias	\U0001D5FB	\x6E	# 𝗻 [MATHEMATICAL SANS-SERIF BOLD SMALL N]
+alias	\U0001D5FC	\x6F	# 𝗼 [MATHEMATICAL SANS-SERIF BOLD SMALL O]
+alias	\U0001D5FD	\x70	# 𝗽 [MATHEMATICAL SANS-SERIF BOLD SMALL P]
+alias	\U0001D5FE	\x71	# 𝗾 [MATHEMATICAL SANS-SERIF BOLD SMALL Q]
+alias	\U0001D5FF	\x72	# 𝗿 [MATHEMATICAL SANS-SERIF BOLD SMALL R]
+alias	\U0001D600	\x73	# 𝘀 [MATHEMATICAL SANS-SERIF BOLD SMALL S]
+alias	\U0001D601	\x74	# 𝘁 [MATHEMATICAL SANS-SERIF BOLD SMALL T]
+alias	\U0001D602	\x75	# 𝘂 [MATHEMATICAL SANS-SERIF BOLD SMALL U]
+alias	\U0001D603	\x76	# 𝘃 [MATHEMATICAL SANS-SERIF BOLD SMALL V]
+alias	\U0001D604	\x77	# 𝘄 [MATHEMATICAL SANS-SERIF BOLD SMALL W]
+alias	\U0001D605	\x78	# 𝘅 [MATHEMATICAL SANS-SERIF BOLD SMALL X]
+alias	\U0001D606	\x79	# 𝘆 [MATHEMATICAL SANS-SERIF BOLD SMALL Y]
+alias	\U0001D607	\x7A	# 𝘇 [MATHEMATICAL SANS-SERIF BOLD SMALL Z]
+
+alias	\U0001D608	\x41	# 𝘈 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL A]
+alias	\U0001D609	\x42	# 𝘉 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL B]
+alias	\U0001D60A	\x43	# 𝘊 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL C]
+alias	\U0001D60B	\x44	# 𝘋 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL D]
+alias	\U0001D60C	\x45	# 𝘌 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL E]
+alias	\U0001D60D	\x46	# 𝘍 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL F]
+alias	\U0001D60E	\x47	# 𝘎 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL G]
+alias	\U0001D60F	\x48	# 𝘏 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL H]
+alias	\U0001D610	\x49	# 𝘐 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL I]
+alias	\U0001D611	\x4A	# 𝘑 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL J]
+alias	\U0001D612	\x4B	# 𝘒 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL K]
+alias	\U0001D613	\x4C	# 𝘓 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL L]
+alias	\U0001D614	\x4D	# 𝘔 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL M]
+alias	\U0001D615	\x4E	# 𝘕 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL N]
+alias	\U0001D616	\x4F	# 𝘖 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL O]
+alias	\U0001D617	\x50	# 𝘗 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL P]
+alias	\U0001D618	\x51	# 𝘘 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL Q]
+alias	\U0001D619	\x52	# 𝘙 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL R]
+alias	\U0001D61A	\x53	# 𝘚 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL S]
+alias	\U0001D61B	\x54	# 𝘛 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL T]
+alias	\U0001D61C	\x55	# 𝘜 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL U]
+alias	\U0001D61D	\x56	# 𝘝 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL V]
+alias	\U0001D61E	\x57	# 𝘞 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL W]
+alias	\U0001D61F	\x58	# 𝘟 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL X]
+alias	\U0001D620	\x59	# 𝘠 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL Y]
+alias	\U0001D621	\x5A	# 𝘡 [MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z]
+
+alias	\U0001D622	\x61	# 𝘢 [MATHEMATICAL SANS-SERIF ITALIC SMALL A]
+alias	\U0001D623	\x62	# 𝘣 [MATHEMATICAL SANS-SERIF ITALIC SMALL B]
+alias	\U0001D624	\x63	# 𝘤 [MATHEMATICAL SANS-SERIF ITALIC SMALL C]
+alias	\U0001D625	\x64	# 𝘥 [MATHEMATICAL SANS-SERIF ITALIC SMALL D]
+alias	\U0001D626	\x65	# 𝘦 [MATHEMATICAL SANS-SERIF ITALIC SMALL E]
+alias	\U0001D627	\x66	# 𝘧 [MATHEMATICAL SANS-SERIF ITALIC SMALL F]
+alias	\U0001D628	\x67	# 𝘨 [MATHEMATICAL SANS-SERIF ITALIC SMALL G]
+alias	\U0001D629	\x68	# 𝘩 [MATHEMATICAL SANS-SERIF ITALIC SMALL H]
+alias	\U0001D62A	\x69	# 𝘪 [MATHEMATICAL SANS-SERIF ITALIC SMALL I]
+alias	\U0001D62B	\x6A	# 𝘫 [MATHEMATICAL SANS-SERIF ITALIC SMALL J]
+alias	\U0001D62C	\x6B	# 𝘬 [MATHEMATICAL SANS-SERIF ITALIC SMALL K]
+alias	\U0001D62D	\x6C	# 𝘭 [MATHEMATICAL SANS-SERIF ITALIC SMALL L]
+alias	\U0001D62E	\x6D	# 𝘮 [MATHEMATICAL SANS-SERIF ITALIC SMALL M]
+alias	\U0001D62F	\x6E	# 𝘯 [MATHEMATICAL SANS-SERIF ITALIC SMALL N]
+alias	\U0001D630	\x6F	# 𝘰 [MATHEMATICAL SANS-SERIF ITALIC SMALL O]
+alias	\U0001D631	\x70	# 𝘱 [MATHEMATICAL SANS-SERIF ITALIC SMALL P]
+alias	\U0001D632	\x71	# 𝘲 [MATHEMATICAL SANS-SERIF ITALIC SMALL Q]
+alias	\U0001D633	\x72	# 𝘳 [MATHEMATICAL SANS-SERIF ITALIC SMALL R]
+alias	\U0001D634	\x73	# 𝘴 [MATHEMATICAL SANS-SERIF ITALIC SMALL S]
+alias	\U0001D635	\x74	# 𝘵 [MATHEMATICAL SANS-SERIF ITALIC SMALL T]
+alias	\U0001D636	\x75	# 𝘶 [MATHEMATICAL SANS-SERIF ITALIC SMALL U]
+alias	\U0001D637	\x76	# 𝘷 [MATHEMATICAL SANS-SERIF ITALIC SMALL V]
+alias	\U0001D638	\x77	# 𝘸 [MATHEMATICAL SANS-SERIF ITALIC SMALL W]
+alias	\U0001D639	\x78	# 𝘹 [MATHEMATICAL SANS-SERIF ITALIC SMALL X]
+alias	\U0001D63A	\x79	# 𝘺 [MATHEMATICAL SANS-SERIF ITALIC SMALL Y]
+alias	\U0001D63B	\x7A	# 𝘻 [MATHEMATICAL SANS-SERIF ITALIC SMALL Z]
+
+alias	\U0001D63C	\x41	# 𝘼 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A]
+alias	\U0001D63D	\x42	# 𝘽 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL B]
+alias	\U0001D63E	\x43	# 𝘾 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL C]
+alias	\U0001D63F	\x44	# 𝘿 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL D]
+alias	\U0001D640	\x45	# 𝙀 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL E]
+alias	\U0001D641	\x46	# 𝙁 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL F]
+alias	\U0001D642	\x47	# 𝙂 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL G]
+alias	\U0001D643	\x48	# 𝙃 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL H]
+alias	\U0001D644	\x49	# 𝙄 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL I]
+alias	\U0001D645	\x4A	# 𝙅 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL J]
+alias	\U0001D646	\x4B	# 𝙆 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL K]
+alias	\U0001D647	\x4C	# 𝙇 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL L]
+alias	\U0001D648	\x4D	# 𝙈 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL M]
+alias	\U0001D649	\x4E	# 𝙉 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL N]
+alias	\U0001D64A	\x4F	# 𝙊 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL O]
+alias	\U0001D64B	\x50	# 𝙋 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL P]
+alias	\U0001D64C	\x51	# 𝙌 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Q]
+alias	\U0001D64D	\x52	# 𝙍 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL R]
+alias	\U0001D64E	\x53	# 𝙎 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL S]
+alias	\U0001D64F	\x54	# 𝙏 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL T]
+alias	\U0001D650	\x55	# 𝙐 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL U]
+alias	\U0001D651	\x56	# 𝙑 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL V]
+alias	\U0001D652	\x57	# 𝙒 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL W]
+alias	\U0001D653	\x58	# 𝙓 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL X]
+alias	\U0001D654	\x59	# 𝙔 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Y]
+alias	\U0001D655	\x5A	# 𝙕 [MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z]
+
+alias	\U0001D656	\x61	# 𝙖 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A]
+alias	\U0001D657	\x62	# 𝙗 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL B]
+alias	\U0001D658	\x63	# 𝙘 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL C]
+alias	\U0001D659	\x64	# 𝙙 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL D]
+alias	\U0001D65A	\x65	# 𝙚 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL E]
+alias	\U0001D65B	\x66	# 𝙛 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL F]
+alias	\U0001D65C	\x67	# 𝙜 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL G]
+alias	\U0001D65D	\x68	# 𝙝 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL H]
+alias	\U0001D65E	\x69	# 𝙞 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I]
+alias	\U0001D65F	\x6A	# 𝙟 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J]
+alias	\U0001D660	\x6B	# 𝙠 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL K]
+alias	\U0001D661	\x6C	# 𝙡 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL L]
+alias	\U0001D662	\x6D	# 𝙢 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL M]
+alias	\U0001D663	\x6E	# 𝙣 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL N]
+alias	\U0001D664	\x6F	# 𝙤 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL O]
+alias	\U0001D665	\x70	# 𝙥 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P]
+alias	\U0001D666	\x71	# 𝙦 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Q]
+alias	\U0001D667	\x72	# 𝙧 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL R]
+alias	\U0001D668	\x73	# 𝙨 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL S]
+alias	\U0001D669	\x74	# 𝙩 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL T]
+alias	\U0001D66A	\x75	# 𝙪 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL U]
+alias	\U0001D66B	\x76	# 𝙫 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL V]
+alias	\U0001D66C	\x77	# 𝙬 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W]
+alias	\U0001D66D	\x78	# 𝙭 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL X]
+alias	\U0001D66E	\x79	# 𝙮 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Y]
+alias	\U0001D66F	\x7A	# 𝙯 [MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z]
+
+alias	\U0001D670	\x41	# 𝙰 [MATHEMATICAL MONOSPACE CAPITAL A]
+alias	\U0001D671	\x42	# 𝙱 [MATHEMATICAL MONOSPACE CAPITAL B]
+alias	\U0001D672	\x43	# 𝙲 [MATHEMATICAL MONOSPACE CAPITAL C]
+alias	\U0001D673	\x44	# 𝙳 [MATHEMATICAL MONOSPACE CAPITAL D]
+alias	\U0001D674	\x45	# 𝙴 [MATHEMATICAL MONOSPACE CAPITAL E]
+alias	\U0001D675	\x46	# 𝙵 [MATHEMATICAL MONOSPACE CAPITAL F]
+alias	\U0001D676	\x47	# 𝙶 [MATHEMATICAL MONOSPACE CAPITAL G]
+alias	\U0001D677	\x48	# 𝙷 [MATHEMATICAL MONOSPACE CAPITAL H]
+alias	\U0001D678	\x49	# 𝙸 [MATHEMATICAL MONOSPACE CAPITAL I]
+alias	\U0001D679	\x4A	# 𝙹 [MATHEMATICAL MONOSPACE CAPITAL J]
+alias	\U0001D67A	\x4B	# 𝙺 [MATHEMATICAL MONOSPACE CAPITAL K]
+alias	\U0001D67B	\x4C	# 𝙻 [MATHEMATICAL MONOSPACE CAPITAL L]
+alias	\U0001D67C	\x4D	# 𝙼 [MATHEMATICAL MONOSPACE CAPITAL M]
+alias	\U0001D67D	\x4E	# 𝙽 [MATHEMATICAL MONOSPACE CAPITAL N]
+alias	\U0001D67E	\x4F	# 𝙾 [MATHEMATICAL MONOSPACE CAPITAL O]
+alias	\U0001D67F	\x50	# 𝙿 [MATHEMATICAL MONOSPACE CAPITAL P]
+alias	\U0001D680	\x51	# 𝚀 [MATHEMATICAL MONOSPACE CAPITAL Q]
+alias	\U0001D681	\x52	# 𝚁 [MATHEMATICAL MONOSPACE CAPITAL R]
+alias	\U0001D682	\x53	# 𝚂 [MATHEMATICAL MONOSPACE CAPITAL S]
+alias	\U0001D683	\x54	# 𝚃 [MATHEMATICAL MONOSPACE CAPITAL T]
+alias	\U0001D684	\x55	# 𝚄 [MATHEMATICAL MONOSPACE CAPITAL U]
+alias	\U0001D685	\x56	# 𝚅 [MATHEMATICAL MONOSPACE CAPITAL V]
+alias	\U0001D686	\x57	# 𝚆 [MATHEMATICAL MONOSPACE CAPITAL W]
+alias	\U0001D687	\x58	# 𝚇 [MATHEMATICAL MONOSPACE CAPITAL X]
+alias	\U0001D688	\x59	# 𝚈 [MATHEMATICAL MONOSPACE CAPITAL Y]
+alias	\U0001D689	\x5A	# 𝚉 [MATHEMATICAL MONOSPACE CAPITAL Z]
+
+alias	\U0001D68A	\x61	# 𝚊 [MATHEMATICAL MONOSPACE SMALL A]
+alias	\U0001D68B	\x62	# 𝚋 [MATHEMATICAL MONOSPACE SMALL B]
+alias	\U0001D68C	\x63	# 𝚌 [MATHEMATICAL MONOSPACE SMALL C]
+alias	\U0001D68D	\x64	# 𝚍 [MATHEMATICAL MONOSPACE SMALL D]
+alias	\U0001D68E	\x65	# 𝚎 [MATHEMATICAL MONOSPACE SMALL E]
+alias	\U0001D68F	\x66	# 𝚏 [MATHEMATICAL MONOSPACE SMALL F]
+alias	\U0001D690	\x67	# 𝚐 [MATHEMATICAL MONOSPACE SMALL G]
+alias	\U0001D691	\x68	# 𝚑 [MATHEMATICAL MONOSPACE SMALL H]
+alias	\U0001D692	\x69	# 𝚒 [MATHEMATICAL MONOSPACE SMALL I]
+alias	\U0001D693	\x6A	# 𝚓 [MATHEMATICAL MONOSPACE SMALL J]
+alias	\U0001D694	\x6B	# 𝚔 [MATHEMATICAL MONOSPACE SMALL K]
+alias	\U0001D695	\x6C	# 𝚕 [MATHEMATICAL MONOSPACE SMALL L]
+alias	\U0001D696	\x6D	# 𝚖 [MATHEMATICAL MONOSPACE SMALL M]
+alias	\U0001D697	\x6E	# 𝚗 [MATHEMATICAL MONOSPACE SMALL N]
+alias	\U0001D698	\x6F	# 𝚘 [MATHEMATICAL MONOSPACE SMALL O]
+alias	\U0001D699	\x70	# 𝚙 [MATHEMATICAL MONOSPACE SMALL P]
+alias	\U0001D69A	\x71	# 𝚚 [MATHEMATICAL MONOSPACE SMALL Q]
+alias	\U0001D69B	\x72	# 𝚛 [MATHEMATICAL MONOSPACE SMALL R]
+alias	\U0001D69C	\x73	# 𝚜 [MATHEMATICAL MONOSPACE SMALL S]
+alias	\U0001D69D	\x74	# 𝚝 [MATHEMATICAL MONOSPACE SMALL T]
+alias	\U0001D69E	\x75	# 𝚞 [MATHEMATICAL MONOSPACE SMALL U]
+alias	\U0001D69F	\x76	# 𝚟 [MATHEMATICAL MONOSPACE SMALL V]
+alias	\U0001D6A0	\x77	# 𝚠 [MATHEMATICAL MONOSPACE SMALL W]
+alias	\U0001D6A1	\x78	# 𝚡 [MATHEMATICAL MONOSPACE SMALL X]
+alias	\U0001D6A2	\x79	# 𝚢 [MATHEMATICAL MONOSPACE SMALL Y]
+alias	\U0001D6A3	\x7A	# 𝚣 [MATHEMATICAL MONOSPACE SMALL Z]
+
+alias	\U0001F110	\x41	#   [PARENTHESIZED LATIN CAPITAL LETTER A]
+alias	\U0001F111	\x42	#   [PARENTHESIZED LATIN CAPITAL LETTER B]
+alias	\U0001F112	\x43	#   [PARENTHESIZED LATIN CAPITAL LETTER C]
+alias	\U0001F113	\x44	#   [PARENTHESIZED LATIN CAPITAL LETTER D]
+alias	\U0001F114	\x45	#   [PARENTHESIZED LATIN CAPITAL LETTER E]
+alias	\U0001F115	\x46	#   [PARENTHESIZED LATIN CAPITAL LETTER F]
+alias	\U0001F116	\x47	#   [PARENTHESIZED LATIN CAPITAL LETTER G]
+alias	\U0001F117	\x48	#   [PARENTHESIZED LATIN CAPITAL LETTER H]
+alias	\U0001F118	\x49	#   [PARENTHESIZED LATIN CAPITAL LETTER I]
+alias	\U0001F119	\x4A	#   [PARENTHESIZED LATIN CAPITAL LETTER J]
+alias	\U0001F11A	\x4B	#   [PARENTHESIZED LATIN CAPITAL LETTER K]
+alias	\U0001F11B	\x4C	#   [PARENTHESIZED LATIN CAPITAL LETTER L]
+alias	\U0001F11C	\x4D	#   [PARENTHESIZED LATIN CAPITAL LETTER M]
+alias	\U0001F11D	\x4E	#   [PARENTHESIZED LATIN CAPITAL LETTER N]
+alias	\U0001F11E	\x4F	#   [PARENTHESIZED LATIN CAPITAL LETTER O]
+alias	\U0001F11F	\x50	#   [PARENTHESIZED LATIN CAPITAL LETTER P]
+alias	\U0001F120	\x51	#   [PARENTHESIZED LATIN CAPITAL LETTER Q]
+alias	\U0001F121	\x52	#   [PARENTHESIZED LATIN CAPITAL LETTER R]
+alias	\U0001F122	\x53	#   [PARENTHESIZED LATIN CAPITAL LETTER S]
+alias	\U0001F123	\x54	#   [PARENTHESIZED LATIN CAPITAL LETTER T]
+alias	\U0001F124	\x55	#   [PARENTHESIZED LATIN CAPITAL LETTER U]
+alias	\U0001F125	\x56	#   [PARENTHESIZED LATIN CAPITAL LETTER V]
+alias	\U0001F126	\x57	#   [PARENTHESIZED LATIN CAPITAL LETTER W]
+alias	\U0001F127	\x58	#   [PARENTHESIZED LATIN CAPITAL LETTER X]
+alias	\U0001F128	\x59	#   [PARENTHESIZED LATIN CAPITAL LETTER Y]
+alias	\U0001F129	\x5A	#   [PARENTHESIZED LATIN CAPITAL LETTER Z]
+
+alias	\U0001F130	\x41	#   [SQUARED LATIN CAPITAL LETTER A]
+alias	\U0001F131	\x42	#   [SQUARED LATIN CAPITAL LETTER B]
+alias	\U0001F132	\x43	#   [SQUARED LATIN CAPITAL LETTER C]
+alias	\U0001F133	\x44	#   [SQUARED LATIN CAPITAL LETTER D]
+alias	\U0001F134	\x45	#   [SQUARED LATIN CAPITAL LETTER E]
+alias	\U0001F135	\x46	#   [SQUARED LATIN CAPITAL LETTER F]
+alias	\U0001F136	\x47	#   [SQUARED LATIN CAPITAL LETTER G]
+alias	\U0001F137	\x48	#   [SQUARED LATIN CAPITAL LETTER H]
+alias	\U0001F138	\x49	#   [SQUARED LATIN CAPITAL LETTER I]
+alias	\U0001F139	\x4A	#   [SQUARED LATIN CAPITAL LETTER J]
+alias	\U0001F13A	\x4B	#   [SQUARED LATIN CAPITAL LETTER K]
+alias	\U0001F13B	\x4C	#   [SQUARED LATIN CAPITAL LETTER L]
+alias	\U0001F13C	\x4D	#   [SQUARED LATIN CAPITAL LETTER M]
+alias	\U0001F13D	\x4E	#   [SQUARED LATIN CAPITAL LETTER N]
+alias	\U0001F13E	\x4F	#   [SQUARED LATIN CAPITAL LETTER O]
+alias	\U0001F13F	\x50	#   [SQUARED LATIN CAPITAL LETTER P]
+alias	\U0001F140	\x51	#   [SQUARED LATIN CAPITAL LETTER Q]
+alias	\U0001F141	\x52	#   [SQUARED LATIN CAPITAL LETTER R]
+alias	\U0001F142	\x53	#   [SQUARED LATIN CAPITAL LETTER S]
+alias	\U0001F143	\x54	#   [SQUARED LATIN CAPITAL LETTER T]
+alias	\U0001F144	\x55	#   [SQUARED LATIN CAPITAL LETTER U]
+alias	\U0001F145	\x56	#   [SQUARED LATIN CAPITAL LETTER V]
+alias	\U0001F146	\x57	#   [SQUARED LATIN CAPITAL LETTER W]
+alias	\U0001F147	\x58	#   [SQUARED LATIN CAPITAL LETTER X]
+alias	\U0001F148	\x59	#   [SQUARED LATIN CAPITAL LETTER Y]
+alias	\U0001F149	\x5A	#   [SQUARED LATIN CAPITAL LETTER Z]
+
+alias	\U0001F150	\x41	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER A]
+alias	\U0001F151	\x42	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER B]
+alias	\U0001F152	\x43	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER C]
+alias	\U0001F153	\x44	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER D]
+alias	\U0001F154	\x45	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER E]
+alias	\U0001F155	\x46	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER F]
+alias	\U0001F156	\x47	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER G]
+alias	\U0001F157	\x48	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER H]
+alias	\U0001F158	\x49	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER I]
+alias	\U0001F159	\x4A	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER J]
+alias	\U0001F15A	\x4B	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER K]
+alias	\U0001F15B	\x4C	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER L]
+alias	\U0001F15C	\x4D	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER M]
+alias	\U0001F15D	\x4E	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER N]
+alias	\U0001F15E	\x4F	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER O]
+alias	\U0001F15F	\x50	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER P]
+alias	\U0001F160	\x51	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER Q]
+alias	\U0001F161	\x52	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER R]
+alias	\U0001F162	\x53	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER S]
+alias	\U0001F163	\x54	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER T]
+alias	\U0001F164	\x55	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER U]
+alias	\U0001F165	\x56	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER V]
+alias	\U0001F166	\x57	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER W]
+alias	\U0001F167	\x58	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER X]
+alias	\U0001F168	\x59	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER Y]
+alias	\U0001F169	\x5A	#   [NEGATIVE CIRCLED LATIN CAPITAL LETTER Z]
+
+alias	\U0001F170	\x41	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER A]
+alias	\U0001F171	\x42	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER B]
+alias	\U0001F172	\x43	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER C]
+alias	\U0001F173	\x44	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER D]
+alias	\U0001F174	\x45	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER E]
+alias	\U0001F175	\x46	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER F]
+alias	\U0001F176	\x47	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER G]
+alias	\U0001F177	\x48	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER H]
+alias	\U0001F178	\x49	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER I]
+alias	\U0001F179	\x4A	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER J]
+alias	\U0001F17A	\x4B	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER K]
+alias	\U0001F17B	\x4C	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER L]
+alias	\U0001F17C	\x4D	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER M]
+alias	\U0001F17D	\x4E	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER N]
+alias	\U0001F17E	\x4F	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER O]
+alias	\U0001F17F	\x50	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER P]
+alias	\U0001F180	\x51	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER Q]
+alias	\U0001F181	\x52	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER R]
+alias	\U0001F182	\x53	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER S]
+alias	\U0001F183	\x54	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER T]
+alias	\U0001F184	\x55	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER U]
+alias	\U0001F185	\x56	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER V]
+alias	\U0001F186	\x57	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER W]
+alias	\U0001F187	\x58	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER X]
+alias	\U0001F188	\x59	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER Y]
+alias	\U0001F189	\x5A	#   [NEGATIVE SQUARED LATIN CAPITAL LETTER Z]
diff --git a/Tables/Text/ltr-cyrillic.tti b/Tables/Text/ltr-cyrillic.tti
new file mode 100644
index 0000000..f8b9fda
--- /dev/null
+++ b/Tables/Text/ltr-cyrillic.tti
@@ -0,0 +1,107 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the standard braille representations
+# for the letters of the Cyrillic alphabet.
+
+# lowercase letters
+char \u0430	(1       )  # ⠁ а [CYRILLIC SMALL LETTER A]
+char \u0431	(12      )  # ⠃ б [CYRILLIC SMALL LETTER BE]
+char \u0432	( 2 456  )  # ⠺ в [CYRILLIC SMALL LETTER VE]
+char \u0433	(12 45   )  # ⠛ г [CYRILLIC SMALL LETTER GHE]
+char \u0434	(1  45   )  # ⠙ д [CYRILLIC SMALL LETTER DE]
+char \u0435	(1   5   )  # ⠑ е [CYRILLIC SMALL LETTER IE]
+char \u0436	( 2 45   )  # ⠚ ж [CYRILLIC SMALL LETTER ZHE]
+char \u0437	(1 3 56  )  # ⠵ з [CYRILLIC SMALL LETTER ZE]
+char \u0438	( 2 4    )  # ⠊ и [CYRILLIC SMALL LETTER I]
+char \u0439	(1234 6  )  # ⠯ й [CYRILLIC SMALL LETTER SHORT I]
+char \u043A	(1 3     )  # ⠅ к [CYRILLIC SMALL LETTER KA]
+char \u043B	(123     )  # ⠇ л [CYRILLIC SMALL LETTER EL]
+char \u043C	(1 34    )  # ⠍ м [CYRILLIC SMALL LETTER EM]
+char \u043D	(1 345   )  # ⠝ н [CYRILLIC SMALL LETTER EN]
+char \u043E	(1 3 5   )  # ⠕ о [CYRILLIC SMALL LETTER O]
+char \u043F	(1234    )  # ⠏ п [CYRILLIC SMALL LETTER PE]
+char \u0440	(123 5   )  # ⠗ р [CYRILLIC SMALL LETTER ER]
+char \u0441	( 234    )  # ⠎ с [CYRILLIC SMALL LETTER ES]
+char \u0442	( 2345   )  # ⠞ т [CYRILLIC SMALL LETTER TE]
+char \u0443	(1 3  6  )  # ⠥ у [CYRILLIC SMALL LETTER U]
+char \u0444	(12 4    )  # ⠋ ф [CYRILLIC SMALL LETTER EF]
+char \u0445	(12  5   )  # ⠓ х [CYRILLIC SMALL LETTER HA]
+char \u0446	(1  4    )  # ⠉ ц [CYRILLIC SMALL LETTER TSE]
+char \u0447	(12345   )  # ⠟ ч [CYRILLIC SMALL LETTER CHE]
+char \u0448	(1   56  )  # ⠱ ш [CYRILLIC SMALL LETTER SHA]
+char \u0449	(1 34 6  )  # ⠭ щ [CYRILLIC SMALL LETTER SHCHA]
+char \u044A	(123 56  )  # ⠷ ъ [CYRILLIC SMALL LETTER HARD SIGN]
+char \u044B	( 234 6  )  # ⠮ ы [CYRILLIC SMALL LETTER YERU]
+char \u044C	( 23456  )  # ⠾ ь [CYRILLIC SMALL LETTER SOFT SIGN]
+char \u044D	( 2 4 6  )  # ⠪ э [CYRILLIC SMALL LETTER E]
+char \u044E	(12  56  )  # ⠳ ю [CYRILLIC SMALL LETTER YU]
+char \u044F	(12 4 6  )  # ⠫ я [CYRILLIC SMALL LETTER YA]
+char \u0451	(1    6  )  # ⠡ ё [CYRILLIC SMALL LETTER IO]
+char \u0454	(  345   )  # ⠜ є [CYRILLIC SMALL LETTER UKRAINIAN IE]
+char \u0456	(1 3456  )  # ⠽ і [CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I]
+char \u0457	(1  456  )  # ⠹ ї [CYRILLIC SMALL LETTER YI]
+char \u0491	(12 456  )  # ⠻ ґ [CYRILLIC SMALL LETTER GHE WITH UPTURN]
+
+# uppercase letters
+char \u0410	(1     7 )  # ⡁ А [CYRILLIC CAPITAL LETTER A]
+char \u0411	(12    7 )  # ⡃ Б [CYRILLIC CAPITAL LETTER BE]
+char \u0412	( 2 4567 )  # ⡺ В [CYRILLIC CAPITAL LETTER VE]
+char \u0413	(12 45 7 )  # ⡛ Г [CYRILLIC CAPITAL LETTER GHE]
+char \u0414	(1  45 7 )  # ⡙ Д [CYRILLIC CAPITAL LETTER DE]
+char \u0415	(1   5 7 )  # ⡑ Е [CYRILLIC CAPITAL LETTER IE]
+char \u0416	( 2 45 7 )  # ⡚ Ж [CYRILLIC CAPITAL LETTER ZHE]
+char \u0417	(1 3 567 )  # ⡵ З [CYRILLIC CAPITAL LETTER ZE]
+char \u0418	( 2 4  7 )  # ⡊ И [CYRILLIC CAPITAL LETTER I]
+char \u0419	(1234 67 )  # ⡯ Й [CYRILLIC CAPITAL LETTER SHORT I]
+char \u041A	(1 3   7 )  # ⡅ К [CYRILLIC CAPITAL LETTER KA]
+char \u041B	(123   7 )  # ⡇ Л [CYRILLIC CAPITAL LETTER EL]
+char \u041C	(1 34  7 )  # ⡍ М [CYRILLIC CAPITAL LETTER EM]
+char \u041D	(1 345 7 )  # ⡝ Н [CYRILLIC CAPITAL LETTER EN]
+char \u041E	(1 3 5 7 )  # ⡕ О [CYRILLIC CAPITAL LETTER O]
+char \u041F	(1234  7 )  # ⡏ П [CYRILLIC CAPITAL LETTER PE]
+char \u0420	(123 5 7 )  # ⡗ Р [CYRILLIC CAPITAL LETTER ER]
+char \u0421	( 234  7 )  # ⡎ С [CYRILLIC CAPITAL LETTER ES]
+char \u0422	( 2345 7 )  # ⡞ Т [CYRILLIC CAPITAL LETTER TE]
+char \u0423	(1 3  67 )  # ⡥ У [CYRILLIC CAPITAL LETTER U]
+char \u0424	(12 4  7 )  # ⡋ Ф [CYRILLIC CAPITAL LETTER EF]
+char \u0425	(12  5 7 )  # ⡓ Х [CYRILLIC CAPITAL LETTER HA]
+char \u0426	(1  4  7 )  # ⡉ Ц [CYRILLIC CAPITAL LETTER TSE]
+char \u0427	(12345 7 )  # ⡟ Ч [CYRILLIC CAPITAL LETTER CHE]
+char \u0428	(1   567 )  # ⡱ Ш [CYRILLIC CAPITAL LETTER SHA]
+char \u0429	(1 34 67 )  # ⡭ Щ [CYRILLIC CAPITAL LETTER SHCHA]
+char \u042A	(123 567 )  # ⡷ Ъ [CYRILLIC CAPITAL LETTER HARD SIGN]
+char \u042B	( 234 67 )  # ⡮ Ы [CYRILLIC CAPITAL LETTER YERU]
+char \u042C	( 234567 )  # ⡾ Ь [CYRILLIC CAPITAL LETTER SOFT SIGN]
+char \u042D	( 2 4 67 )  # ⡪ Э [CYRILLIC CAPITAL LETTER E]
+char \u042E	(12  567 )  # ⡳ Ю [CYRILLIC CAPITAL LETTER YU]
+char \u042F	(12 4 67 )  # ⡫ Я [CYRILLIC CAPITAL LETTER YA]
+char \u0401	(1    67 )  # ⡡ Ё [CYRILLIC CAPITAL LETTER IO]
+char \u0404	(  345 7 )  # ⡜ Є [CYRILLIC CAPITAL LETTER UKRAINIAN IE]
+char \u0406	(1 34567 )  # ⡽ І [CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I]
+char \u0407	(1  4567 )  # ⡹ Ї [CYRILLIC CAPITAL LETTER YI]
+char \u0490	(12 4567 )  # ⡻ Ґ [CYRILLIC CAPITAL LETTER GHE WITH UPTURN]
+
+# obsolete lowercase letters
+char \u0463	(  345   )  # ⠜ ѣ [CYRILLIC SMALL LETTER YAT]
+char \u046B	( 2 4 6  )  # ⠪ ѫ [CYRILLIC SMALL LETTER BIG YUS]
+
+# obsolete uppercase letters
+char \u0462	(  345 7 )  # ⡜ Ѣ [CYRILLIC CAPITAL LETTER YAT]
+char \u046A	( 2 4 67 )  # ⡪ Ѫ [CYRILLIC CAPITAL LETTER BIG YUS]
+
diff --git a/Tables/Text/ltr-dot8.tti b/Tables/Text/ltr-dot8.tti
new file mode 100644
index 0000000..8e9edbc
--- /dev/null
+++ b/Tables/Text/ltr-dot8.tti
@@ -0,0 +1,77 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations for the letters
+# of the Latin alphabet by adding dot 8 to their standard representations.
+
+# lowercase letters
+char \x61	(1      8)  # ⢁ a [LATIN SMALL LETTER A]
+char \x62	(12     8)  # ⢃ b [LATIN SMALL LETTER B]
+char \x63	(1  4   8)  # ⢉ c [LATIN SMALL LETTER C]
+char \x64	(1  45  8)  # ⢙ d [LATIN SMALL LETTER D]
+char \x65	(1   5  8)  # ⢑ e [LATIN SMALL LETTER E]
+char \x66	(12 4   8)  # ⢋ f [LATIN SMALL LETTER F]
+char \x67	(12 45  8)  # ⢛ g [LATIN SMALL LETTER G]
+char \x68	(12  5  8)  # ⢓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4   8)  # ⢊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45  8)  # ⢚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3    8)  # ⢅ k [LATIN SMALL LETTER K]
+char \x6C	(123    8)  # ⢇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34   8)  # ⢍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345  8)  # ⢝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5  8)  # ⢕ o [LATIN SMALL LETTER O]
+char \x70	(1234   8)  # ⢏ p [LATIN SMALL LETTER P]
+char \x71	(12345  8)  # ⢟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5  8)  # ⢗ r [LATIN SMALL LETTER R]
+char \x73	( 234   8)  # ⢎ s [LATIN SMALL LETTER S]
+char \x74	( 2345  8)  # ⢞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6 8)  # ⢥ u [LATIN SMALL LETTER U]
+char \x76	(123  6 8)  # ⢧ v [LATIN SMALL LETTER V]
+char \x77	( 2 456 8)  # ⢺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6 8)  # ⢭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456 8)  # ⢽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56 8)  # ⢵ z [LATIN SMALL LETTER Z]
+
+# uppercase letters
+char \x41	(1     78)  # ⣁ A [LATIN CAPITAL LETTER A]
+char \x42	(12    78)  # ⣃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4  78)  # ⣉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45 78)  # ⣙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5 78)  # ⣑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4  78)  # ⣋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45 78)  # ⣛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5 78)  # ⣓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4  78)  # ⣊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45 78)  # ⣚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3   78)  # ⣅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123   78)  # ⣇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34  78)  # ⣍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345 78)  # ⣝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5 78)  # ⣕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234  78)  # ⣏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345 78)  # ⣟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5 78)  # ⣗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234  78)  # ⣎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345 78)  # ⣞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  678)  # ⣥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  678)  # ⣧ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 45678)  # ⣺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 678)  # ⣭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 345678)  # ⣽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 5678)  # ⣵ Z [LATIN CAPITAL LETTER Z]
+
diff --git a/Tables/Text/ltr-latin.tti b/Tables/Text/ltr-latin.tti
new file mode 100644
index 0000000..81e16fd
--- /dev/null
+++ b/Tables/Text/ltr-latin.tti
@@ -0,0 +1,76 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the standard braille representations
+# for the letters of the Latin alphabet.
+
+char \x41	(1     7 )  # ⡁ A [LATIN CAPITAL LETTER A]
+char \x42	(12    7 )  # ⡃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4  7 )  # ⡉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45 7 )  # ⡙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5 7 )  # ⡑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4  7 )  # ⡋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45 7 )  # ⡛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5 7 )  # ⡓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4  7 )  # ⡊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45 7 )  # ⡚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3   7 )  # ⡅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123   7 )  # ⡇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34  7 )  # ⡍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345 7 )  # ⡝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5 7 )  # ⡕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234  7 )  # ⡏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345 7 )  # ⡟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5 7 )  # ⡗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234  7 )  # ⡎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345 7 )  # ⡞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  67 )  # ⡥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  67 )  # ⡧ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 4567 )  # ⡺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 67 )  # ⡭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 34567 )  # ⡽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 567 )  # ⡵ Z [LATIN CAPITAL LETTER Z]
+
+char \x61	(1       )  # ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1  45   )  # ⠙ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # ⠞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6  )  # ⠥ u [LATIN SMALL LETTER U]
+char \x76	(123  6  )  # ⠧ v [LATIN SMALL LETTER V]
+char \x77	( 2 456  )  # ⠺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56  )  # ⠵ z [LATIN SMALL LETTER Z]
+
+include ltr-alias.tti
diff --git a/Tables/Text/ltr-tibetan.tti b/Tables/Text/ltr-tibetan.tti
new file mode 100644
index 0000000..78598b1
--- /dev/null
+++ b/Tables/Text/ltr-tibetan.tti
@@ -0,0 +1,92 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the standard braille representations
+# for the letters of the Tibetan alphabet.
+#
+# Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+
+# non subjoined letters (dot 7 added)
+char \u0F40	(1 3   7 )  # ⡅ ཀ [TIBETAN LETTER KA]
+char \u0F41	(1  4  7 )  # ⡉ ཁ [TIBETAN LETTER KHA]
+char \u0F42	(12 45 7 )  # ⡛ ག [TIBETAN LETTER GA]
+char \u0F44	( 23 567 )  # ⡶ ང [TIBETAN LETTER NGA]
+char \u0F45	(1  4567 )  # ⡹ ཅ [TIBETAN LETTER CA]
+char \u0F46	(1 34567 )  # ⡽ ཆ [TIBETAN LETTER CHA]
+char \u0F47	(12345 7 )  # ⡟ ཇ [TIBETAN LETTER JA]
+char \u0F49	(  345 7 )  # ⡜ ཉ [TIBETAN LETTER NYA]
+char \u0F4F	( 2345 7 )  # ⡞ ཏ [TIBETAN LETTER TA]
+char \u0F50	( 234567 )  # ⡾ ཐ [TIBETAN LETTER THA]
+char \u0F51	(1  45 7 )  # ⡙ ད [TIBETAN LETTER DA]
+char \u0F53	(1 345 7 )  # ⡝ ན [TIBETAN LETTER NA]
+char \u0F54	(1234  7 )  # ⡏ པ [TIBETAN LETTER PA]
+char \u0F55	(1234 67 )  # ⡯ ཕ [TIBETAN LETTER PHA]
+char \u0F56	(12    7 )  # ⡃ བ [TIBETAN LETTER BA]
+char \u0F58	(1 34  7 )  # ⡍ མ [TIBETAN LETTER MA]
+char \u0F59	(1 34 67 )  # ⡭ ཙ [TIBETAN LETTER TSA]
+char \u0F5A	(1 3 567 )  # ⡵ ཚ [TIBETAN LETTER TSHA]
+char \u0F5B	(1234567 )  # ⡿ ཛ [TIBETAN LETTER DZA]
+char \u0F5D	( 2 4567 )  # ⡺ ཝ [TIBETAN LETTER WA]
+char \u0F5E	(1  4 67 )  # ⡩ ཞ [TIBETAN LETTER ZHA]
+char \u0F5F	( 234  7 )  # ⡎ ཟ [TIBETAN LETTER ZA]
+char \u0F60	(12 4 67 )  # ⡫ འ [TIBETAN LETTER -A]
+char \u0F61	( 2 45 7 )  # ⡚ ཡ [TIBETAN LETTER YA]
+char \u0F62	(123 5 7 )  # ⡗ ར [TIBETAN LETTER RA]
+char \u0F63	(123   7 )  # ⡇ ལ [TIBETAN LETTER LA]
+char \u0F64	(1   567 )  # ⡱ ཤ [TIBETAN LETTER SHA]
+char \u0F66	( 234 67 )  # ⡮ ས [TIBETAN LETTER SA]
+char \u0F67	(12  5 7 )  # ⡓ ཧ [TIBETAN LETTER HA]
+char \u0F68	(1     7 )  # ⡁ ཨ [TIBETAN LETTER A]
+
+# subjoined letters
+char \u0F90	(1 3     )  # ⠅  ྐ [TIBETAN SUBJOINED LETTER KA]
+char \u0F91	(1  4    )  # ⠉  ྑ [TIBETAN SUBJOINED LETTER KHA]
+char \u0F92	(12 45   )  # ⠛  ྒ [TIBETAN SUBJOINED LETTER GA]
+char \u0F94	( 23 56  )  # ⠶  ྔ [TIBETAN SUBJOINED LETTER NGA]
+char \u0F95	(1  456  )  # ⠹  ྕ [TIBETAN SUBJOINED LETTER CA]
+char \u0F96	(1 3456  )  # ⠽  ྖ [TIBETAN SUBJOINED LETTER CHA]
+char \u0F97	(12345   )  # ⠟  ྗ [TIBETAN SUBJOINED LETTER JA]
+char \u0F99	(  345   )  # ⠜  ྙ [TIBETAN SUBJOINED LETTER NYA]
+char \u0F9F	( 2345   )  # ⠞  ྟ [TIBETAN SUBJOINED LETTER TA]
+char \u0FA0	( 23456  )  # ⠾  ྠ [TIBETAN SUBJOINED LETTER THA]
+char \u0FA1	(1  45   )  # ⠙  ྡ [TIBETAN SUBJOINED LETTER DA]
+char \u0FA3	(1 345   )  # ⠝  ྣ [TIBETAN SUBJOINED LETTER NA]
+char \u0FA4	(1234    )  # ⠏  ྤ [TIBETAN SUBJOINED LETTER PA]
+char \u0FA5	(1234 6  )  # ⠯  ྥ [TIBETAN SUBJOINED LETTER PHA]
+char \u0FA6	(12      )  # ⠃  ྦ [TIBETAN SUBJOINED LETTER BA]
+char \u0FA8	(1 34    )  # ⠍  ྨ [TIBETAN SUBJOINED LETTER MA]
+char \u0FA9	(1 34 6  )  # ⠭  ྩ [TIBETAN SUBJOINED LETTER TSA]
+char \u0FAA	(1 3 56  )  # ⠵  ྪ [TIBETAN SUBJOINED LETTER TSHA]
+char \u0FAB	(123456  )  # ⠿  ྫ [TIBETAN SUBJOINED LETTER DZA]
+char \u0FAD	( 2 456  )  # ⠺  ྭ [TIBETAN SUBJOINED LETTER WA]
+char \u0FAE	(1  4 6  )  # ⠩  ྮ [TIBETAN SUBJOINED LETTER ZHA]
+char \u0FAF	( 234    )  # ⠎  ྯ [TIBETAN SUBJOINED LETTER ZA]
+char \u0FB0	(12 4 6  )  # ⠫  ྰ [TIBETAN SUBJOINED LETTER -A]
+char \u0FB1	( 2 45   )  # ⠚  ྱ [TIBETAN SUBJOINED LETTER YA]
+char \u0FB2	(123 5   )  # ⠗  ྲ [TIBETAN SUBJOINED LETTER RA]
+char \u0FB3	(123     )  # ⠇  ླ [TIBETAN SUBJOINED LETTER LA]
+char \u0FB4	(1   56  )  # ⠱  ྴ [TIBETAN SUBJOINED LETTER SHA]
+char \u0FB6	( 234 6  )  # ⠮  ྶ [TIBETAN SUBJOINED LETTER SA]
+char \u0FB7	(12  5   )  # ⠓  ྷ [TIBETAN SUBJOINED LETTER HA]
+char \u0FB8	(1       )  # ⠁  ྸ [TIBETAN SUBJOINED LETTER A]
+
+# vowels
+char \u0F72	( 2 4    )  # ⠊ ི [TIBETAN VOWEL SIGN I]
+char \u0F74	(1 3  6  )  # ⠥ ུ [TIBETAN VOWEL SIGN U]
+char \u0F7A	(1   5   )  # ⠑ ེ [TIBETAN VOWEL SIGN E]
+char \u0F7C	(1 3 5   )  # ⠕ ོ [TIBETAN VOWEL SIGN O]
diff --git a/Tables/Text/lv.ttb b/Tables/Text/lv.ttb
new file mode 100644
index 0000000..14e294d
--- /dev/null
+++ b/Tables/Text/lv.ttb
@@ -0,0 +1,219 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Latvian
+
+# Created & maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>.
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest: charset=latin7
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(  3 56  )  # 22 ⠴ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	( 23 56  )  # 28 ⠶ ( [LEFT PARENTHESIS]
+char \x29	( 23 56  )  # 29 ⠶ ) [RIGHT PARENTHESIS]
+char \x2C	(     6  )  # 2C ⠠ , [COMMA]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+char \x30	( 2 45   )  # 30 ⠚ 0 [DIGIT ZERO]
+char \x31	(1       )  # 31 ⠁ 1 [DIGIT ONE]
+char \x32	(12      )  # 32 ⠃ 2 [DIGIT TWO]
+char \x33	(1  4    )  # 33 ⠉ 3 [DIGIT THREE]
+char \x34	(1  45   )  # 34 ⠙ 4 [DIGIT FOUR]
+char \x35	(1   5   )  # 35 ⠑ 5 [DIGIT FIVE]
+char \x36	(12 4    )  # 36 ⠋ 6 [DIGIT SIX]
+char \x37	(12 45   )  # 37 ⠛ 7 [DIGIT SEVEN]
+char \x38	(12  5   )  # 38 ⠓ 8 [DIGIT EIGHT]
+char \x39	( 2 4    )  # 39 ⠊ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(12   6  )  # 3C ⠣ < [LESS-THAN SIGN]
+char \x3D	(123456  )  # 3D ⠿ = [EQUALS SIGN]
+char \x3E	(  345   )  # 3E ⠜ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x41	(1       )  # 41 ⠁ A [LATIN CAPITAL LETTER A]
+char \x42	(12      )  # 42 ⠃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4    )  # 43 ⠉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45   )  # 44 ⠙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5   )  # 45 ⠑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4    )  # 46 ⠋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45   )  # 47 ⠛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5   )  # 48 ⠓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4    )  # 49 ⠊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45   )  # 4A ⠚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3     )  # 4B ⠅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123     )  # 4C ⠇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34    )  # 4D ⠍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345   )  # 4E ⠝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5   )  # 4F ⠕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234    )  # 50 ⠏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345   )  # 51 ⠟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5   )  # 52 ⠗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234    )  # 53 ⠎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345   )  # 54 ⠞ T [LATIN CAPITAL LETTER T]
+char \x55	(  34    )  # 55 ⠌ U [LATIN CAPITAL LETTER U]
+char \x56	( 2 456  )  # 56 ⠺ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 456  )  # 57 ⠺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 6  )  # 58 ⠭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 3456  )  # 59 ⠽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(  345   )  # 5A ⠜ Z [LATIN CAPITAL LETTER Z]
+char \x5E	(   45   )  # 5E ⠘ ^ [CIRCUMFLEX ACCENT]
+char \x61	(1       )  # 61 ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # 62 ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # 63 ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1  45   )  # 64 ⠙ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # 65 ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # 66 ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # 67 ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # 68 ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # 69 ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # 6A ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # 6B ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # 6C ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # 6D ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # 6E ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # 6F ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # 70 ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # 71 ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # 72 ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # 73 ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # 74 ⠞ t [LATIN SMALL LETTER T]
+char \x75	(  34    )  # 75 ⠌ u [LATIN SMALL LETTER U]
+char \x76	( 2 456  )  # 76 ⠺ v [LATIN SMALL LETTER V]
+char \x77	( 2 456  )  # 77 ⠺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # 78 ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # 79 ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(  345   )  # 7A ⠜ z [LATIN SMALL LETTER Z]
+char \x7C	(   456  )  # 7C ⠸ | [VERTICAL LINE]
+char \x7E	(    5   )  # 7E ⠐ ~ [TILDE]
+char \u201D	(  3 56  )  # A1 ⠴ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u201E	( 23  6  )  # A5 ⠦ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \xA7	(  34 6  )  # A7 ⠬ § [SECTION SIGN]
+char \xD8	( 2 4 6  )  # A8 ⠪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \u201C	( 23  6  )  # B4 ⠦ “ [LEFT DOUBLE QUOTATION MARK]
+char \xF8	( 2 4 6  )  # B8 ⠪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \u0104	(1    6  )  # C0 ⠡ Ą [LATIN CAPITAL LETTER A WITH OGONEK]
+char \u0100	(1    6  )  # C2 ⠡ Ā [LATIN CAPITAL LETTER A WITH MACRON]
+char \u0106	(1  4 6  )  # C3 ⠩ Ć [LATIN CAPITAL LETTER C WITH ACUTE]
+char \xC4	(  345   )  # C4 ⠜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    6  )  # C5 ⠡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \u0118	(1   56  )  # C6 ⠱ Ę [LATIN CAPITAL LETTER E WITH OGONEK]
+char \u0112	(1   56  )  # C7 ⠱ Ē [LATIN CAPITAL LETTER E WITH MACRON]
+char \u010C	(1  4 6  )  # C8 ⠩ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \xC9	(  345   )  # C9 ⠜ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \u0179	( 234 6  )  # CA ⠮ Ź [LATIN CAPITAL LETTER Z WITH ACUTE]
+char \u0122	(12 456  )  # CC ⠻ Ģ [LATIN CAPITAL LETTER G WITH CEDILLA]
+char \u0136	(1 3  6  )  # CD ⠥ Ķ [LATIN CAPITAL LETTER K WITH CEDILLA]
+char \u012A	( 2 4 6  )  # CE ⠪ Ī [LATIN CAPITAL LETTER I WITH MACRON]
+char \u013B	(123  6  )  # CF ⠧ Ļ [LATIN CAPITAL LETTER L WITH CEDILLA]
+char \u0160	(1   56  )  # D0 ⠱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u0143	(1  456  )  # D1 ⠹ Ń [LATIN CAPITAL LETTER N WITH ACUTE]
+char \u0145	(1 3456  )  # D2 ⠽ Ņ [LATIN CAPITAL LETTER N WITH CEDILLA]
+char \xD3	( 2 4 6  )  # D3 ⠪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \u014C	(1 3 56  )  # D4 ⠵ Ō [LATIN CAPITAL LETTER O WITH MACRON]
+char \xD5	( 2 4 6  )  # D5 ⠪ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 6  )  # D6 ⠪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23  6  )  # D7 ⠦ × [MULTIPLICATION SIGN]
+char \u0141	(12   6  )  # D9 ⠣ Ł [LATIN CAPITAL LETTER L WITH STROKE]
+char \u015A	( 2 4 6  )  # DA ⠪ Ś [LATIN CAPITAL LETTER S WITH ACUTE]
+char \u016A	(  34 6  )  # DB ⠬ Ū [LATIN CAPITAL LETTER U WITH MACRON]
+char \xDC	(12  56  )  # DC ⠳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \u017B	(1234 6  )  # DD ⠯ Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+char \u017D	( 234 6  )  # DE ⠮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u0105	(1    6  )  # E0 ⠡ ą [LATIN SMALL LETTER A WITH OGONEK]
+char \u0101	(1    6  )  # E2 ⠡ ā [LATIN SMALL LETTER A WITH MACRON]
+char \u0107	(1  4 6  )  # E3 ⠩ ć [LATIN SMALL LETTER C WITH ACUTE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \u0119	(1   56  )  # E6 ⠱ ę [LATIN SMALL LETTER E WITH OGONEK]
+char \u0113	(1   56  )  # E7 ⠱ ē [LATIN SMALL LETTER E WITH MACRON]
+char \u010D	(1  4 6  )  # E8 ⠩ č [LATIN SMALL LETTER C WITH CARON]
+char \xE9	(  345   )  # E9 ⠜ é [LATIN SMALL LETTER E WITH ACUTE]
+char \u017A	( 234 6  )  # EA ⠮ ź [LATIN SMALL LETTER Z WITH ACUTE]
+char \u0123	(12 456  )  # EC ⠻ ģ [LATIN SMALL LETTER G WITH CEDILLA]
+char \u0137	(1 3  6  )  # ED ⠥ ķ [LATIN SMALL LETTER K WITH CEDILLA]
+char \u012B	( 2 4 6  )  # EE ⠪ ī [LATIN SMALL LETTER I WITH MACRON]
+char \u013C	(123  6  )  # EF ⠧ ļ [LATIN SMALL LETTER L WITH CEDILLA]
+char \u0161	(1   56  )  # F0 ⠱ š [LATIN SMALL LETTER S WITH CARON]
+char \u0144	(1  456  )  # F1 ⠹ ń [LATIN SMALL LETTER N WITH ACUTE]
+char \u0146	(1 3456  )  # F2 ⠽ ņ [LATIN SMALL LETTER N WITH CEDILLA]
+char \xF3	( 2 4 6  )  # F3 ⠪ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \u014D	(1 3 56  )  # F4 ⠵ ō [LATIN SMALL LETTER O WITH MACRON]
+char \xF5	( 2 4 6  )  # F5 ⠪ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # F6 ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56  )  # F7 ⠲ ÷ [DIVISION SIGN]
+char \u0142	(12   6  )  # F9 ⠣ ł [LATIN SMALL LETTER L WITH STROKE]
+char \u015B	( 2 4 6  )  # FA ⠪ ś [LATIN SMALL LETTER S WITH ACUTE]
+char \u016B	(  34 6  )  # FB ⠬ ū [LATIN SMALL LETTER U WITH MACRON]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \u017C	(1234 6  )  # FD ⠯ ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
+char \u017E	( 234 6  )  # FE ⠮ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u2019	(  3     )  # FF ⠄ ’ [RIGHT SINGLE QUOTATION MARK]
+char \xC0	(123 56  )  #    ⠷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(1    6  )  #    ⠡ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    6  )  #    ⠡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(12   6  )  #    ⠣ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC7	(1234 6  )  #    ⠯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 6  )  #    ⠮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xCA	(12   6  )  #    ⠣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 6  )  #    ⠫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCD	(  34    )  #    ⠌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 6  )  #    ⠩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 456  )  #    ⠻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD4	(1  456  )  #    ⠹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xDA	(  34 6  )  #    ⠬ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   56  )  #    ⠱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDD	(1234 6  )  #    ⠯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xE0	(123 56  )  #    ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1    6  )  #    ⠡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  #    ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(12   6  )  #    ⠣ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE7	(1234 6  )  #    ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  #    ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xEA	(12   6  )  #    ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  #    ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xED	(  34    )  #    ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  #    ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  #    ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF4	(1  456  )  #    ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xFA	(  34 6  )  #    ⠬ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  #    ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFD	(1234 6  )  #    ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \u010E	(1  456  )  #    ⠹ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char \u010F	(1  456  )  #    ⠹ ď [LATIN SMALL LETTER D WITH CARON]
+char \u011A	(12   6  )  #    ⠣ Ě [LATIN CAPITAL LETTER E WITH CARON]
+char \u011B	(12   6  )  #    ⠣ ě [LATIN SMALL LETTER E WITH CARON]
+char \u0147	(12 4 6  )  #    ⠫ Ň [LATIN CAPITAL LETTER N WITH CARON]
+char \u0148	(12 4 6  )  #    ⠫ ň [LATIN SMALL LETTER N WITH CARON]
+char \u0156	(123 56  )  #    ⠷ Ŗ [LATIN SMALL LETTER R WITH CEDILLA]
+char \u0157	(123 56  )  #    ⠷ ŗ [LATIN SMALL LETTER R WITH CEDILLA]
+char \u0158	( 2 456  )  #    ⠺ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char \u0159	( 2 456  )  #    ⠺ ř [LATIN SMALL LETTER R WITH CARON]
+char \u0164	(12  56  )  #    ⠳ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char \u0165	(12  56  )  #    ⠳ ť [LATIN SMALL LETTER T WITH CARON]
+char \u016C	( 23456  )  #    ⠾ Ŭ [LATIN CAPITAL LETTER U WITH BREVE]
+char \u016D	( 23456  )  #    ⠾ ŭ [LATIN SMALL LETTER U WITH BREVE]
+char \u016E	( 23456  )  #    ⠾ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char \u016F	( 23456  )  #    ⠾ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char \u2018	(  3     )  #    ⠄ ‘ [LEFT SINGLE QUOTATION MARK]
+char \u201F	(  3 56  )  #    ⠴ ‟ [DOUBLE HIGH-REVERSED-9 QUOTATION MARK]
+
+include common.tti
diff --git a/Tables/Text/malayalam.tti b/Tables/Text/malayalam.tti
new file mode 100644
index 0000000..156a4e3
--- /dev/null
+++ b/Tables/Text/malayalam.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Malayalam script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+
+char \u0D02	(    56  )  # ⠰ ം [MALAYALAM SIGN ANUSVARA]
+char \u0D03	(     6  )  # ⠠ ഃ [MALAYALAM SIGN VISARGA]
+char \u0D05	(1       )  # ⠁ അ [MALAYALAM LETTER A]
+char \u0D06	(  345   )  # ⠜ ആ [MALAYALAM LETTER AA]
+char \u0D07	( 2 4    )  # ⠊ ഇ [MALAYALAM LETTER I]
+char \u0D08	(  3 5   )  # ⠔ ഈ [MALAYALAM LETTER II]
+char \u0D09	(1 3  6  )  # ⠥ ഉ [MALAYALAM LETTER U]
+char \u0D0A	(12  56  )  # ⠳ ഊ [MALAYALAM LETTER UU]
+
+char \u0D0E	(  34  7 )  # ⡌ എ [MALAYALAM LETTER E]
+char \u0D0F	(1   5   )  # ⠑ ഏ [MALAYALAM LETTER EE]
+char \u0D10	(  34    )  # ⠌ ഐ [MALAYALAM LETTER AI]
+
+char \u0D12	( 2 4 67 )  # ⡪ ഒ [MALAYALAM LETTER O]
+char \u0D13	(1 3 5   )  # ⠕ ഓ [MALAYALAM LETTER OO]
+char \u0D14	( 2 4 6  )  # ⠪ ഔ [MALAYALAM LETTER AU]
+char \u0D15	(1 3     )  # ⠅ ക [MALAYALAM LETTER KA]
+char \u0D16	(   4 6  )  # ⠨ ഖ [MALAYALAM LETTER KHA]
+char \u0D17	(1234    )  # ⠏ ഗ [MALAYALAM LETTER GA]
+char \u0D18	(12   6  )  # ⠣ ഘ [MALAYALAM LETTER GHA]
+char \u0D19	(  34 6  )  # ⠬ ങ [MALAYALAM LETTER NGA]
+char \u0D1A	(1  4    )  # ⠉ ച [MALAYALAM LETTER CA]
+char \u0D1B	(1    6  )  # ⠡ ഛ [MALAYALAM LETTER CHA]
+char \u0D1C	( 2 45   )  # ⠚ ജ [MALAYALAM LETTER JA]
+char \u0D1D	(  3 56  )  # ⠴ ഝ [MALAYALAM LETTER JHA]
+char \u0D1E	( 2  5   )  # ⠒ ഞ [MALAYALAM LETTER NYA]
+char \u0D1F	( 23456  )  # ⠾ ട [MALAYALAM LETTER TTA]
+char \u0D20	( 2 456  )  # ⠺ ഠ [MALAYALAM LETTER TTHA]
+char \u0D21	(12 4 6  )  # ⠫ ഡ [MALAYALAM LETTER DDA]
+char \u0D22	(123456  )  # ⠿ ഢ [MALAYALAM LETTER DDHA]
+char \u0D23	(  3456  )  # ⠼ ണ [MALAYALAM LETTER NNA]
+char \u0D24	( 2345   )  # ⠞ ത [MALAYALAM LETTER TA]
+char \u0D25	(1  456  )  # ⠹ ഥ [MALAYALAM LETTER THA]
+char \u0D26	(1  45   )  # ⠙ ദ [MALAYALAM LETTER DA]
+char \u0D27	( 234 6  )  # ⠮ ധ [MALAYALAM LETTER DHA]
+char \u0D28	(1 345   )  # ⠝ ന [MALAYALAM LETTER NA]
+
+char \u0D2A	(1234    )  # ⠏ പ [MALAYALAM LETTER PA]
+char \u0D2B	( 23 5   )  # ⠖ ഫ [MALAYALAM LETTER PHA]
+char \u0D2C	(12      )  # ⠃ ബ [MALAYALAM LETTER BA]
+char \u0D2D	(   45   )  # ⠘ ഭ [MALAYALAM LETTER BHA]
+char \u0D2E	(1 34    )  # ⠍ മ [MALAYALAM LETTER MA]
+char \u0D2F	(1 3456  )  # ⠽ യ [MALAYALAM LETTER YA]
+char \u0D30	(123 5   )  # ⠗ ര [MALAYALAM LETTER RA]
+char \u0D31	(123 5 7 )  # ⡗ റ [MALAYALAM LETTER RRA]
+char \u0D32	(123     )  # ⠇ ല [MALAYALAM LETTER LA]
+char \u0D33	(123   7 )  # ⡇ ള [MALAYALAM LETTER LLA]
+char \u0D34	(123   78)  # ⣇ ഴ [MALAYALAM LETTER LLLA]
+char \u0D35	(123  6  )  # ⠧ വ [MALAYALAM LETTER VA]
+char \u0D36	(1  4 6  )  # ⠩ ശ [MALAYALAM LETTER SHA]
+char \u0D37	(1234 6  )  # ⠯ ഷ [MALAYALAM LETTER SSA]
+char \u0D38	( 234    )  # ⠎ സ [MALAYALAM LETTER SA]
+char \u0D39	(12  5   )  # ⠓ ഹ [MALAYALAM LETTER HA]
+
+char \u0D3E	(  345   )  # ⠜ ാ [MALAYALAM VOWEL SIGN AA]
+char \u0D3F	( 2 4    )  # ⠊ ി [MALAYALAM VOWEL SIGN I]
+char \u0D40	(  3 5   )  # ⠔ ീ [MALAYALAM VOWEL SIGN II]
+char \u0D41	(1 3  6  )  # ⠥ ു [MALAYALAM VOWEL SIGN U]
+char \u0D42	(12  56  )  # ⠳ ൂ [MALAYALAM VOWEL SIGN UU]
+
+char \u0D46	(  34  7 )  # ⡌ െ [MALAYALAM VOWEL SIGN E]
+char \u0D47	(1   5   )  # ⠑ േ [MALAYALAM VOWEL SIGN EE]
+char \u0D48	(  34    )  # ⠌ ൈ [MALAYALAM VOWEL SIGN AI]
+
+char \u0D4A	( 2 4 67 )  # ⡪ ൊ [MALAYALAM VOWEL SIGN O]
+char \u0D4B	(1 3 5   )  # ⠕ ോ [MALAYALAM VOWEL SIGN OO]
+char \u0D4C	( 2 4 6  )  # ⠪ ൌ [MALAYALAM VOWEL SIGN AU]
+char \u0D4D	(   4    )  # ⠈ ് [MALAYALAM SIGN VIRAMA]
+
+char \u0D66	( 2 45   )  # ⠚ ൦ [MALAYALAM DIGIT ZERO]
+char \u0D67	(1       )  # ⠁ ൧ [MALAYALAM DIGIT ONE]
+char \u0D68	(12      )  # ⠃ ൨ [MALAYALAM DIGIT TWO]
+char \u0D69	(1  4    )  # ⠉ ൩ [MALAYALAM DIGIT THREE]
+char \u0D6A	(1  45   )  # ⠙ ൪ [MALAYALAM DIGIT FOUR]
+char \u0D6B	(1   5   )  # ⠑ ൫ [MALAYALAM DIGIT FIVE]
+char \u0D6C	(12 4    )  # ⠋ ൬ [MALAYALAM DIGIT SIX]
+char \u0D6D	(12 45   )  # ⠛ ൭ [MALAYALAM DIGIT SEVEN]
+char \u0D6E	(12  5   )  # ⠓ ൮ [MALAYALAM DIGIT EIGHT]
+char \u0D6F	( 2 4    )  # ⠊ ൯ [MALAYALAM DIGIT NINE]
diff --git a/Tables/Text/mg.ttb b/Tables/Text/mg.ttb
new file mode 100644
index 0000000..8c8971d
--- /dev/null
+++ b/Tables/Text/mg.ttb
@@ -0,0 +1,31 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Malagasy
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include ltr-latin.tti
+include num-dot8.tti
+include punc-basic.tti
+include common.tti
diff --git a/Tables/Text/mi.ttb b/Tables/Text/mi.ttb
new file mode 100644
index 0000000..7bf35f8
--- /dev/null
+++ b/Tables/Text/mi.ttb
@@ -0,0 +1,31 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Maori
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include ltr-latin.tti
+include num-dot8.tti
+include punc-basic.tti
+include common.tti
diff --git a/Tables/Text/ml.ttb b/Tables/Text/ml.ttb
new file mode 100644
index 0000000..d3e6183
--- /dev/null
+++ b/Tables/Text/ml.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Malayalam
+
+include malayalam.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/mni.ttb b/Tables/Text/mni.ttb
new file mode 100644
index 0000000..aaca883
--- /dev/null
+++ b/Tables/Text/mni.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Manipuri
+
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/mr.ttb b/Tables/Text/mr.ttb
new file mode 100644
index 0000000..c62c53a
--- /dev/null
+++ b/Tables/Text/mr.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Marathi
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/mt.ttb b/Tables/Text/mt.ttb
new file mode 100644
index 0000000..04ad418
--- /dev/null
+++ b/Tables/Text/mt.ttb
@@ -0,0 +1,49 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Maltese
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# generated by ttbtest: charset=latin3
+char \x4A	(1 34567 )  # 4A ⡽ J [LATIN CAPITAL LETTER J]
+char \u0126	(1   567 )  # A1 ⡱ Ħ [LATIN CAPITAL LETTER H WITH STROKE]
+char \u017B	(1 3 567 )  # AF ⡵ Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+char \u010A	(1    67 )  # C5 ⡡ Ċ [LATIN CAPITAL LETTER C WITH DOT ABOVE]
+char \u0120	( 2 45 7 )  # D5 ⡚ Ġ [LATIN CAPITAL LETTER G WITH DOT ABOVE]
+char \x6A	(1 3456  )  # 6A ⠽ j [LATIN SMALL LETTER J]
+char \u0127	(1   56  )  # B1 ⠱ ħ [LATIN SMALL LETTER H WITH STROKE]
+char \u017C	(1 3 56  )  # BF ⠵ ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
+char \u010B	(1    6  )  # E5 ⠡ ċ [LATIN SMALL LETTER C WITH DOT ABOVE]
+char \u0121	( 2 45   )  # F5 ⠚ ġ [LATIN SMALL LETTER G WITH DOT ABOVE]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+char \x3F	( 23  6  )  # 3F ⠦ ? [QUESTION MARK]
+
+include common.tti
diff --git a/Tables/Text/mun.ttb b/Tables/Text/mun.ttb
new file mode 100644
index 0000000..39ad4e8
--- /dev/null
+++ b/Tables/Text/mun.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Munda
+
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/mwr.ttb b/Tables/Text/mwr.ttb
new file mode 100644
index 0000000..a8a2092
--- /dev/null
+++ b/Tables/Text/mwr.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Marwari
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/ne.ttb b/Tables/Text/ne.ttb
new file mode 100644
index 0000000..97cb483
--- /dev/null
+++ b/Tables/Text/ne.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Nepali
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/new.ttb b/Tables/Text/new.ttb
new file mode 100644
index 0000000..093a777
--- /dev/null
+++ b/Tables/Text/new.ttb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Newari
+
+include devanagari.tti
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/nl.ttb b/Tables/Text/nl.ttb
new file mode 100644
index 0000000..76932ce
--- /dev/null
+++ b/Tables/Text/nl.ttb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Dutch
+
+include nl_NL.ttb
diff --git a/Tables/Text/nl_BE.ttb b/Tables/Text/nl_BE.ttb
new file mode 100644
index 0000000..f540a3b
--- /dev/null
+++ b/Tables/Text/nl_BE.ttb
@@ -0,0 +1,179 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Dutch (Belgium)
+
+# Created by Leon Ungier <Leon.Ungier@ViewPlus.com>.
+# Edited by Eric <eric@integra-belgium.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest: charset=iso-8859-15
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(    5   )  # 23 ⠐ # [NUMBER SIGN]
+char \x24	(1  45   )  # 24 ⠙ $ [DOLLAR SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+char \x30	( 2 45   )  # 30 ⠚ 0 [DIGIT ZERO]
+char \x31	(1       )  # 31 ⠁ 1 [DIGIT ONE]
+char \x32	(12      )  # 32 ⠃ 2 [DIGIT TWO]
+char \x33	(1  4    )  # 33 ⠉ 3 [DIGIT THREE]
+char \x34	(1  45   )  # 34 ⠙ 4 [DIGIT FOUR]
+char \x35	(1   5   )  # 35 ⠑ 5 [DIGIT FIVE]
+char \x36	(12 4    )  # 36 ⠋ 6 [DIGIT SIX]
+char \x37	(12 45   )  # 37 ⠛ 7 [DIGIT SEVEN]
+char \x38	(12  5   )  # 38 ⠓ 8 [DIGIT EIGHT]
+char \x39	( 2 4    )  # 39 ⠊ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345   )  # 40 ⠜ @ [COMMERCIAL AT]
+char \x41	(1       )  # 41 ⠁ A [LATIN CAPITAL LETTER A]
+char \x42	(12      )  # 42 ⠃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4    )  # 43 ⠉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45   )  # 44 ⠙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5   )  # 45 ⠑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4    )  # 46 ⠋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45   )  # 47 ⠛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5   )  # 48 ⠓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4    )  # 49 ⠊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45   )  # 4A ⠚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3     )  # 4B ⠅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123     )  # 4C ⠇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34    )  # 4D ⠍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345   )  # 4E ⠝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5   )  # 4F ⠕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234    )  # 50 ⠏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345   )  # 51 ⠟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5   )  # 52 ⠗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234    )  # 53 ⠎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345   )  # 54 ⠞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  6  )  # 55 ⠥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  6  )  # 56 ⠧ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 456  )  # 57 ⠺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 6  )  # 58 ⠭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 3456  )  # 59 ⠽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 56  )  # 5A ⠵ Z [LATIN CAPITAL LETTER Z]
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(  34 6  )  # 5E ⠬ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(     6  )  # 60 ⠠ ` [GRAVE ACCENT]
+char \x61	(1       )  # 61 ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # 62 ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # 63 ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1  45   )  # 64 ⠙ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # 65 ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # 66 ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # 67 ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # 68 ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # 69 ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # 6A ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # 6B ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # 6C ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # 6D ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # 6E ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # 6F ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # 70 ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # 71 ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # 72 ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # 73 ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # 74 ⠞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6  )  # 75 ⠥ u [LATIN SMALL LETTER U]
+char \x76	(123  6  )  # 76 ⠧ v [LATIN SMALL LETTER V]
+char \x77	( 2 456  )  # 77 ⠺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # 78 ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # 79 ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56  )  # 7A ⠵ z [LATIN SMALL LETTER Z]
+char \x7B	(123 56  )  # 7B ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(123456  )  # 7C ⠿ | [VERTICAL LINE]
+char \x7D	( 23456  )  # 7D ⠾ } [RIGHT CURLY BRACKET]
+char \xA1	(   4    )  # A1 ⠈ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(1  4    )  # A2 ⠉ ¢ [CENT SIGN]
+char \xA3	(1234    )  # A3 ⠏ £ [POUND SIGN]
+char \u20AC	(1   5   )  # A4 ⠑ € [EURO SIGN]
+char \xA5	(1 3456  )  # A5 ⠽ ¥ [YEN SIGN]
+char \xA7	(  34 6  )  # A7 ⠬ § [SECTION SIGN]
+char \xAA	(   4 6  )  # AA ⠨ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23 56  )  # AB ⠶ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xB5	(123456  )  # B5 ⠿ µ [MICRO SIGN]
+char \xB7	(    56  )  # B7 ⠰ · [MIDDLE DOT]
+char \xBB	( 23 56  )  # BB ⠶ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBF	(  3 5   )  # BF ⠔ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 56  )  # C0 ⠷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(1       )  # C1 ⠁ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    6  )  # C2 ⠡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  345   )  # C3 ⠜ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345   )  # C4 ⠜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	( 2 4 6  )  # C5 ⠪ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345   )  # C6 ⠜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 6  )  # C7 ⠯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 6  )  # C8 ⠮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(123456  )  # C9 ⠿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   6  )  # CA ⠣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 6  )  # CB ⠫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xD1	(12 456  )  # D1 ⠻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD6	( 2 4 6  )  # D6 ⠪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23  6  )  # D7 ⠦ × [MULTIPLICATION SIGN]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(1       )  # E1 ⠁ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  345   )  # E3 ⠜ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	( 2 4 6  )  # E5 ⠪ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 2 4    )  # EC ⠊ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	( 2 4    )  # ED ⠊ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF1	(12 456  )  # F1 ⠻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3 5   )  # F2 ⠕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 2 4 6  )  # F5 ⠪ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # F6 ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56  )  # F7 ⠲ ÷ [DIVISION SIGN]
+char \xF9	(123 56  )  # F9 ⠷ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1 3  6  )  # FA ⠥ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xB8	(   45   )  #    ⠘ ¸ [CEDILLA]
+char \u0192	(12 4    )  #    ⠋ ƒ [LATIN SMALL LETTER F WITH HOOK]
+char \u201C	( 23  6  )  #    ⠦ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	(  3 56  )  #    ⠴ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u201E	( 23  6  )  #    ⠦ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u201F	(  3 56  )  #    ⠴ ‟ [DOUBLE HIGH-REVERSED-9 QUOTATION MARK]
+char \u20AC	(1   5  8)  # ⢑ € [EURO SIGN]
+
+include common.tti
diff --git a/Tables/Text/nl_NL.ttb b/Tables/Text/nl_NL.ttb
new file mode 100644
index 0000000..fffdc80
--- /dev/null
+++ b/Tables/Text/nl_NL.ttb
@@ -0,0 +1,170 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Dutch (Netherlands)
+
+# Created by Leon Ungier <Leon.Ungier@ViewPlus.com>.
+# Compilation June 22, 2006
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest: charset=iso-8859-15
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # 22 ⠶ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(1  45   )  # 24 ⠙ $ [DOLLAR SIGN]
+char \x26	(  3 56  )  # 26 ⠴ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+char \x30	( 2 45   )  # 30 ⠚ 0 [DIGIT ZERO]
+char \x31	(1       )  # 31 ⠁ 1 [DIGIT ONE]
+char \x32	(12      )  # 32 ⠃ 2 [DIGIT TWO]
+char \x33	(1  4    )  # 33 ⠉ 3 [DIGIT THREE]
+char \x34	(1  45   )  # 34 ⠙ 4 [DIGIT FOUR]
+char \x35	(1   5   )  # 35 ⠑ 5 [DIGIT FIVE]
+char \x36	(12 4    )  # 36 ⠋ 6 [DIGIT SIX]
+char \x37	(12 45   )  # 37 ⠛ 7 [DIGIT SEVEN]
+char \x38	(12  5   )  # 38 ⠓ 8 [DIGIT EIGHT]
+char \x39	( 2 4    )  # 39 ⠊ 9 [DIGIT NINE]
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345   )  # 40 ⠜ @ [COMMERCIAL AT]
+char \x41	(1       )  # 41 ⠁ A [LATIN CAPITAL LETTER A]
+char \x42	(12      )  # 42 ⠃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4    )  # 43 ⠉ C [LATIN CAPITAL LETTER C]
+char \x44	(1  45   )  # 44 ⠙ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5   )  # 45 ⠑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4    )  # 46 ⠋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45   )  # 47 ⠛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5   )  # 48 ⠓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4    )  # 49 ⠊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45   )  # 4A ⠚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3     )  # 4B ⠅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123     )  # 4C ⠇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34    )  # 4D ⠍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345   )  # 4E ⠝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5   )  # 4F ⠕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234    )  # 50 ⠏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345   )  # 51 ⠟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5   )  # 52 ⠗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234    )  # 53 ⠎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345   )  # 54 ⠞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  6  )  # 55 ⠥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  6  )  # 56 ⠧ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 456  )  # 57 ⠺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 6  )  # 58 ⠭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 3456  )  # 59 ⠽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 56  )  # 5A ⠵ Z [LATIN CAPITAL LETTER Z]
+char \x5B	(123 56  )  # 5B ⠷ [ [LEFT SQUARE BRACKET]
+char \x5D	( 23456  )  # 5D ⠾ ] [RIGHT SQUARE BRACKET]
+char \x5E	( 23 5   )  # 5E ⠖ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+char \x61	(1       )  # 61 ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # 62 ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # 63 ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1  45   )  # 64 ⠙ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # 65 ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # 66 ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # 67 ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # 68 ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # 69 ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # 6A ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # 6B ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # 6C ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # 6D ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # 6E ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # 6F ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # 70 ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # 71 ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # 72 ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # 73 ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # 74 ⠞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6  )  # 75 ⠥ u [LATIN SMALL LETTER U]
+char \x76	(123  6  )  # 76 ⠧ v [LATIN SMALL LETTER V]
+char \x77	( 2 456  )  # 77 ⠺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # 78 ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # 79 ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56  )  # 7A ⠵ z [LATIN SMALL LETTER Z]
+char \xA3	(1234    )  # A3 ⠏ £ [POUND SIGN]
+char \u20AC	(1   5   )  # A4 ⠑ € [EURO SIGN]
+char \xA5	(1 3456  )  # A5 ⠽ ¥ [YEN SIGN]
+char \xA7	(  34 6  )  # A7 ⠬ § [SECTION SIGN]
+char \xB7	(    56  )  # B7 ⠰ · [MIDDLE DOT]
+char \xC0	(123 56  )  # C0 ⠷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 56  )  # C1 ⠷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    6  )  # C2 ⠡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  345   )  # C3 ⠜ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345   )  # C4 ⠜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    6  )  # C5 ⠡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345   )  # C6 ⠜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 6  )  # C7 ⠯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 6  )  # C8 ⠮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(123456  )  # C9 ⠿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   6  )  # CA ⠣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 6  )  # CB ⠫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xD1	(12 456  )  # D1 ⠻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD6	(123456  )  # D6 ⠿ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23  6  )  # D7 ⠦ × [MULTIPLICATION SIGN]
+char \xDC	(12  56  )  # DC ⠳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  345   )  # E3 ⠜ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 2 4    )  # EC ⠊ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF1	(12 456  )  # F1 ⠻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3 5   )  # F2 ⠕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 2 4 6  )  # F5 ⠪ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(123456  )  # F6 ⠿ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56  )  # F7 ⠲ ÷ [DIVISION SIGN]
+char \xF9	(123 56  )  # F9 ⠷ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xB8	(   45   )  #    ⠘ ¸ [CEDILLA]
+char \u0192	(12 4    )  #    ⠋ ƒ [LATIN SMALL LETTER F WITH HOOK]
+char \u201C	( 23  6  )  #    ⠦ “ [LEFT DOUBLE QUOTATION MARK]
+char \u201D	(  3 56  )  #    ⠴ ” [RIGHT DOUBLE QUOTATION MARK]
+char \u201E	( 23  6  )  #    ⠦ „ [DOUBLE LOW-9 QUOTATION MARK]
+char \u201F	(  3 56  )  #    ⠴ ‟ [DOUBLE HIGH-REVERSED-9 QUOTATION MARK]
+char \u20AC	(1   5  8)  # ⢑ € [EURO SIGN]
+
+include common.tti
diff --git a/Tables/Text/no-generic.ttb b/Tables/Text/no-generic.ttb
new file mode 100644
index 0000000..5f5e2fa
--- /dev/null
+++ b/Tables/Text/no-generic.ttb
@@ -0,0 +1,206 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Norwegian (with support for other languages)
+# Some unofficial character representations to accommodate multilingual usage.
+# For example, numbers are represented with dot 6 rather than with dot 8.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 1-9 are represented by the letters a-i with dot 6 added
+# the number 0 is represented by dots 346
+include num-dot6.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   45 78)  # 00 ⣘   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(123 5678)  # 1B ⣷   [ESCAPE]
+char \x1C	(  34  78)  # 1C ⣌   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2345678)  # 1D ⣾   [INFORMATION SEPARATOR THREE]
+char \x1E	( 234 678)  # 1E ⣮   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	(    5   )  # 21 ⠐ ! [EXCLAMATION MARK]
+char \x22	(   4    )  # 22 ⠈ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(   4 6  )  # 24 ⠨ $ [DOLLAR SIGN]
+char \x25	(123456  )  # 25 ⠿ % [PERCENT SIGN]
+char \x26	(1234 6  )  # 26 ⠯ & [AMPERSAND]
+char \x27	(     6  )  # 27 ⠠ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	( 2  56  )  # 2F ⠲ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(    56  )  # 3C ⠰ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(   45   )  # 3E ⠘ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(  345 7 )  # 40 ⡜ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 567 )  # 5B ⡷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  34  7 )  # 5C ⡌ \ [REVERSE SOLIDUS]
+char \x5D	( 234567 )  # 5D ⡾ ] [RIGHT SQUARE BRACKET]
+char \x5E	( 234 67 )  # 5E ⡮ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(  345   )  # 60 ⠜ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123 56  )  # 7B ⠷ { [LEFT CURLY BRACKET]
+char \x7C	(  34    )  # 7C ⠌ | [VERTICAL LINE]
+char \x7D	( 23456  )  # 7D ⠾ } [RIGHT CURLY BRACKET]
+char \x7E	( 234 6  )  # 7E ⠮ ~ [TILDE]
+char \x7F	(   456 8)  # 7F ⢸   [DELETE]
+char \x80	( 23  67 )  # 80 ⡦   [<control-0080>]
+char \x81	(    56 8)  # 81 ⢰   [<control-0081>]
+char \x82	(  3  67 )  # 82 ⡤   [BREAK PERMITTED HERE]
+char \x83	( 23  678)  # 83 ⣦   [NO BREAK HERE]
+char \x84	( 23 5678)  # 84 ⣶   [<control-0084>]
+char \x85	(12 45  8)  # 85 ⢛   [NEXT LINE (NEL)]
+char \x86	(123    8)  # 86 ⢇   [START OF SELECTED AREA]
+char \x87	(1234567 )  # 87 ⡿   [END OF SELECTED AREA]
+char \x88	(12345678)  # 88 ⣿   [CHARACTER TABULATION SET]
+char \x89	(     678)  # 89 ⣠   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	(1234 678)  # 8A ⣯   [LINE TABULATION SET]
+char \x8B	(  3 56 8)  # 8B ⢴   [PARTIAL LINE FORWARD]
+char \x8C	(1   5678)  # 8C ⣱   [PARTIAL LINE BACKWARD]
+char \x8D	(  34 678)  # 8D ⣬   [REVERSE LINE FEED]
+char \x8E	(      78)  # 8E ⣀   [SINGLE SHIFT TWO]
+char \x8F	( 2  5 78)  # 8F ⣒   [SINGLE SHIFT THREE]
+char \x90	( 23 567 )  # 90 ⡶   [DEVICE CONTROL STRING]
+char \x91	(1 34   8)  # 91 ⢍   [PRIVATE USE ONE]
+char \x92	( 2 456 8)  # 92 ⢺   [PRIVATE USE TWO]
+char \x93	( 234 6 8)  # 93 ⢮   [SET TRANSMIT STATE]
+char \x94	( 23 5 78)  # 94 ⣖   [CANCEL CHARACTER]
+char \x95	(   4567 )  # 95 ⡸   [MESSAGE WAITING]
+char \x96	(123456 8)  # 96 ⢿   [START OF GUARDED AREA]
+char \x97	(  3  6 8)  # 97 ⢤   [END OF GUARDED AREA]
+char \x98	(1 3 56 8)  # 98 ⢵   [START OF STRING]
+char \x99	(  3  678)  # 99 ⣤   [<control-0099>]
+char \x9A	(  345678)  # 9A ⣼   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(    5 78)  # 9B ⣐   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(       8)  # 9C ⢀   [STRING TERMINATOR]
+char \x9D	(   4 678)  # 9D ⣨   [OPERATING SYSTEM COMMAND]
+char \x9E	(1 34 6 8)  # 9E ⢭   [PRIVACY MESSAGE]
+char \x9F	(12 4   8)  # 9F ⢋   [APPLICATION PROGRAM COMMAND]
+char \xA1	(    5  8)  # A1 ⢐ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5  8)  # A2 ⢒ ¢ [CENT SIGN]
+char \xA3	(  3456 8)  # A3 ⢼ £ [POUND SIGN]
+char \xA4	(  34567 )  # A4 ⡼ ¤ [CURRENCY SIGN]
+char \xA5	(1  45678)  # A5 ⣹ ¥ [YEN SIGN]
+char \xA6	(  34   8)  # A6 ⢌ ¦ [BROKEN BAR]
+char \xA7	(123  6 8)  # A7 ⢧ § [SECTION SIGN]
+char \xA8	(      7 )  # A8 ⡀ ¨ [DIAERESIS]
+char \xA9	(1  4   8)  # A9 ⢉ © [COPYRIGHT SIGN]
+char \xAA	(  3   7 )  # AA ⡄ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(   4  7 )  # AB ⡈ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(     67 )  # AC ⡠ ¬ [NOT SIGN]
+char \xAD	(  3    8)  # AD ⢄ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	( 23 56 8)  # AF ⢶ ¯ [MACRON]
+char \xB0	(12 45678)  # B0 ⣻ ° [DEGREE SIGN]
+char \xB1	(1234   8)  # B1 ⢏ ± [PLUS-MINUS SIGN]
+char \xB2	(  3 5 78)  # B2 ⣔ ² [SUPERSCRIPT TWO]
+char \xB3	(1  4 678)  # B3 ⣩ ³ [SUPERSCRIPT THREE]
+char \xB4	(12 4 678)  # B4 ⣫ ´ [ACUTE ACCENT]
+char \xB5	(12   678)  # B5 ⣣ µ [MICRO SIGN]
+char \xB6	(12345  8)  # B6 ⢟ ¶ [PILCROW SIGN]
+char \xB7	(  3   78)  # B7 ⣄ · [MIDDLE DOT]
+char \xB8	(  3 5678)  # B8 ⣴ ¸ [CEDILLA]
+char \xB9	(    5678)  # B9 ⣰ ¹ [SUPERSCRIPT ONE]
+char \xBA	(     6 8)  # BA ⢠ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(   4   8)  # BB ⢈ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	( 2  5 7 )  # BC ⡒ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(1  45  8)  # BD ⢙ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(1 345  8)  # BE ⢝ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   6 8)  # BF ⢢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(    5 7 )  # C0 ⡐ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    67 )  # C2 ⡡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1  4 67 )  # C3 ⡩ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    678)  # C5 ⣡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 78)  # C6 ⣜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(   4 67 )  # C8 ⡨ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	( 2   678)  # C9 ⣢ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(1   567 )  # CA ⡱ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	( 23   7 )  # CC ⡆ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	( 23   78)  # CD ⣆ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(   45 7 )  # CE ⡘ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(  3 5 7 )  # CF ⡔ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	( 2  567 )  # D0 ⡲ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 4567 )  # D1 ⡻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2   67 )  # D2 ⡢ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(12   67 )  # D3 ⡣ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 23 5 7 )  # D5 ⡖ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(12  5  8)  # D7 ⢓ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 678)  # D8 ⣪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(12  5678)  # D9 ⣳ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2    78)  # DA ⣂ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(  34 67 )  # DB ⡬ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 2  5678)  # DD ⣲ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	( 2345  8)  # DE ⢞ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234   8)  # DF ⢎ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	( 2     8)  # E1 ⢂ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1 3    8)  # E2 ⢅ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1  4 6 8)  # E3 ⢩ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(1      8)  # E4 ⢁ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6 8)  # E5 ⢡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345  8)  # E6 ⢜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	(   4 6 8)  # E8 ⢨ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(1   5  8)  # E9 ⢑ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(1   56 8)  # EA ⢱ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	( 23    8)  # EC ⢆ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(12     8)  # ED ⢃ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(   45  8)  # EE ⢘ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	( 2 4   8)  # EF ⢊ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 2  56 8)  # F0 ⢲ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(1 3 5  8)  # F2 ⢕ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(12   6 8)  # F3 ⢣ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456 8)  # F4 ⢹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 23 5  8)  # F5 ⢖ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(  3 5  8)  # F6 ⢔ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2 45  8)  # F7 ⢚ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6 8)  # F8 ⢪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23  6 8)  # F9 ⢦ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	(1 3  6 8)  # FA ⢥ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(  34 6 8)  # FB ⢬ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 3456 8)  # FD ⢽ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  3 567 )  # FE ⡴ þ [LATIN SMALL LETTER THORN]
+char \xFF	( 23456 8)  # FF ⢾ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/no-oup.ttb b/Tables/Text/no-oup.ttb
new file mode 100644
index 0000000..184a53b
--- /dev/null
+++ b/Tables/Text/no-oup.ttb
@@ -0,0 +1,206 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Norwegian (Offentlig utvalg for punktskrift, OUP)
+# based on the Offentlig utvalg for punktskrift (Public Commission for
+# Braille) translation table for Windows 1252.
+# Updated January 2008.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   45 78)  # 00 ⣘   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(1234   8)  # 1B ⢏   [ESCAPE]
+char \x1C	(12345  8)  # 1C ⢟   [INFORMATION SEPARATOR FOUR]
+char \x1D	(1 3  6 8)  # 1D ⢥   [INFORMATION SEPARATOR THREE]
+char \x1E	( 2 456 8)  # 1E ⢺   [INFORMATION SEPARATOR TWO]
+char \x1F	(1 3 56 8)  # 1F ⢵   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	( 2  56  )  # 22 ⠲ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	( 2  5678)  # 24 ⣲ $ [DOLLAR SIGN]
+char \x25	(   4 6  )  # 25 ⠨ % [PERCENT SIGN]
+char \x26	(    5 78)  # 26 ⣐ & [AMPERSAND]
+char \x27	(    5   )  # 27 ⠐ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 7 )  # 2B ⡖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(    5 7 )  # 2F ⡐ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(  3 5  8)  # 3C ⢔ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	( 2   67 )  # 3E ⡢ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4    )  # 40 ⠈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23  678)  # 5B ⣦ [ [LEFT SQUARE BRACKET]
+char \x5C	( 2     8)  # 5C ⢂ \ [REVERSE SOLIDUS]
+char \x5D	(  3 5678)  # 5D ⣴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(    5  8)  # 5E ⢐ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(   45   )  # 60 ⠘ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 23    8)  # 7B ⢆ { [LEFT CURLY BRACKET]
+char \x7C	(    56  )  # 7C ⠰ | [VERTICAL LINE]
+char \x7D	(    567 )  # 7D ⡰ } [RIGHT CURLY BRACKET]
+char \x7E	(  3    8)  # 7E ⢄ ~ [TILDE]
+char \x7F	(  3 5 78)  # 7F ⣔   [DELETE]
+char \x80	( 2   678)  # 80 ⣢   [<control-0080>]
+char \x81	(1 345  8)  # 81 ⢝   [<control-0081>]
+char \x82	(     67 )  # 82 ⡠   [BREAK PERMITTED HERE]
+char \x83	(   45678)  # 83 ⣸   [NO BREAK HERE]
+char \x84	(  3 567 )  # 84 ⡴   [<control-0084>]
+char \x85	(  3   7 )  # 85 ⡄   [NEXT LINE (NEL)]
+char \x86	(   4  7 )  # 86 ⡈   [START OF SELECTED AREA]
+char \x87	(   45 7 )  # 87 ⡘   [END OF SELECTED AREA]
+char \x88	(   4 6 8)  # 88 ⢨   [CHARACTER TABULATION SET]
+char \x89	(   456 8)  # 89 ⢸   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	(1   5678)  # 8A ⣱   [LINE TABULATION SET]
+char \x8B	(  3   78)  # 8B ⣄   [PARTIAL LINE FORWARD]
+char \x8C	(12345678)  # 8C ⣿   [PARTIAL LINE BACKWARD]
+char \x8D	(   4  78)  # 8D ⣈   [REVERSE LINE FEED]
+char \x8E	( 234 678)  # 8E ⣮   [SINGLE SHIFT TWO]
+char \x8F	( 23  67 )  # 8F ⡦   [SINGLE SHIFT THREE]
+char \x90	( 23  6 8)  # 90 ⢦   [DEVICE CONTROL STRING]
+char \x91	(  3  67 )  # 91 ⡤   [PRIVATE USE ONE]
+char \x92	(  3  6 8)  # 92 ⢤   [PRIVATE USE TWO]
+char \x93	( 23 567 )  # 93 ⡶   [SET TRANSMIT STATE]
+char \x94	( 23 56 8)  # 94 ⢶   [CANCEL CHARACTER]
+char \x95	(  3  678)  # 95 ⣤   [MESSAGE WAITING]
+char \x96	(     6 8)  # 96 ⢠   [START OF GUARDED AREA]
+char \x97	(    56 8)  # 97 ⢰   [END OF GUARDED AREA]
+char \x98	( 2  56 8)  # 98 ⢲   [START OF STRING]
+char \x99	( 2345  8)  # 99 ⢞   [<control-0099>]
+char \x9A	(1   56 8)  # 9A ⢱   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(     678)  # 9B ⣠   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(123456 8)  # 9C ⢿   [STRING TERMINATOR]
+char \x9D	( 23 5678)  # 9D ⣶   [OPERATING SYSTEM COMMAND]
+char \x9E	( 234 6 8)  # 9E ⢮   [PRIVACY MESSAGE]
+char \x9F	(12  5678)  # 9F ⣳   [APPLICATION PROGRAM COMMAND]
+char \xA1	( 23 5  8)  # A1 ⢖ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 2  5 78)  # A2 ⣒ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	(123  6 8)  # A4 ⢧ ¤ [CURRENCY SIGN]
+char \xA5	(1 3456 8)  # A5 ⢽ ¥ [YEN SIGN]
+char \xA6	(   4567 )  # A6 ⡸ ¦ [BROKEN BAR]
+char \xA7	(   4 678)  # A7 ⣨ § [SECTION SIGN]
+char \xA8	( 2  5  8)  # A8 ⢒ ¨ [DIAERESIS]
+char \xA9	(1 3    8)  # A9 ⢅ © [COPYRIGHT SIGN]
+char \xAA	( 2    78)  # AA ⣂ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23   78)  # AB ⣆ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(  3 5 7 )  # AC ⡔ ¬ [NOT SIGN]
+char \xAD	(      78)  # AD ⣀ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	(   45  8)  # AF ⢘ ¯ [MACRON]
+char \xB0	(  3 56 8)  # B0 ⢴ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	( 23   7 )  # B2 ⡆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5 7 )  # B3 ⡒ ³ [SUPERSCRIPT THREE]
+char \xB4	(     6  )  # B4 ⠠ ´ [ACUTE ACCENT]
+char \xB5	(1 34   8)  # B5 ⢍ µ [MICRO SIGN]
+char \xB6	(   4 67 )  # B6 ⡨ ¶ [PILCROW SIGN]
+char \xB7	(      7 )  # B7 ⡀ · [MIDDLE DOT]
+char \xB8	(   4   8)  # B8 ⢈ ¸ [CEDILLA]
+char \xB9	( 2    7 )  # B9 ⡂ ¹ [SUPERSCRIPT ONE]
+char \xBA	(1 3 5  8)  # BA ⢕ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    5678)  # BB ⣰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(  3456 8)  # BC ⢼ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(  34567 )  # BD ⡼ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  345678)  # BE ⣼ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   6 8)  # BF ⢢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(123 5678)  # C0 ⣷ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 567 )  # C1 ⡷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    678)  # C2 ⣡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(12   678)  # C3 ⣣ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 78)  # C4 ⣜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 7 )  # C6 ⡜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 678)  # C7 ⣯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12 4 67 )  # CB ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(  34  78)  # CC ⣌ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 67 )  # CE ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(1  45678)  # D0 ⣹ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(  34 678)  # D2 ⣬ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(1  4 678)  # D5 ⣩ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 678)  # D6 ⣪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # D7 ⢭ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 67 )  # D8 ⡪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	( 2345678)  # D9 ⣾ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 234567 )  # DA ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   567 )  # DB ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1234 67 )  # DD ⡯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12 4 678)  # DE ⣫ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234   8)  # DF ⢎ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56 8)  # E0 ⢷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6 8)  # E2 ⢡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(12   6 8)  # E3 ⢣ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345   )  # E6 ⠜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6 8)  # E7 ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34   8)  # EC ⢌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(1  456 8)  # F0 ⢹ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6 8)  # F2 ⢬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(1  4 6 8)  # F5 ⢩ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  567 )  # F7 ⡲ ÷ [DIVISION SIGN]
+char \xF8	( 2 4 6  )  # F8 ⠪ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456 8)  # F9 ⢾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1234 6  )  # FD ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(12 4 6 8)  # FE ⢫ þ [LATIN SMALL LETTER THORN]
+char \xFF	(12  56 8)  # FF ⢳ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/no.ttb b/Tables/Text/no.ttb
new file mode 100644
index 0000000..8bc1b43
--- /dev/null
+++ b/Tables/Text/no.ttb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Norwegian
+
+include no-oup.ttb
diff --git a/Tables/Text/num-alias.tti b/Tables/Text/num-alias.tti
new file mode 100644
index 0000000..c91fa4e
--- /dev/null
+++ b/Tables/Text/num-alias.tti
@@ -0,0 +1,96 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines aliases for additional dedimal digit styles.
+
+alias	\u2460	\x31	# ① [CIRCLED DIGIT ONE]
+alias	\u2461	\x32	# ② [CIRCLED DIGIT TWO]
+alias	\u2462	\x33	# ③ [CIRCLED DIGIT THREE]
+alias	\u2463	\x34	# ④ [CIRCLED DIGIT FOUR]
+alias	\u2464	\x35	# ⑤ [CIRCLED DIGIT FIVE]
+alias	\u2465	\x36	# ⑥ [CIRCLED DIGIT SIX]
+alias	\u2466	\x37	# ⑦ [CIRCLED DIGIT SEVEN]
+alias	\u2467	\x38	# ⑧ [CIRCLED DIGIT EIGHT]
+alias	\u2468	\x39	# ⑨ [CIRCLED DIGIT NINE]
+alias	\u24EA	\x30	# ⓪ [CIRCLED DIGIT ZERO]
+
+alias	\uFF10	\x30	# 0 [FULLWIDTH DIGIT ZERO]
+alias	\uFF11	\x31	# 1 [FULLWIDTH DIGIT ONE]
+alias	\uFF12	\x32	# 2 [FULLWIDTH DIGIT TWO]
+alias	\uFF13	\x33	# 3 [FULLWIDTH DIGIT THREE]
+alias	\uFF14	\x34	# 4 [FULLWIDTH DIGIT FOUR]
+alias	\uFF15	\x35	# 5 [FULLWIDTH DIGIT FIVE]
+alias	\uFF16	\x36	# 6 [FULLWIDTH DIGIT SIX]
+alias	\uFF17	\x37	# 7 [FULLWIDTH DIGIT SEVEN]
+alias	\uFF18	\x38	# 8 [FULLWIDTH DIGIT EIGHT]
+alias	\uFF19	\x39	# 9 [FULLWIDTH DIGIT NINE]
+
+alias	\U0001D7CE	\x30	# 𝟎 [MATHEMATICAL BOLD DIGIT ZERO]
+alias	\U0001D7CF	\x31	# 𝟏 [MATHEMATICAL BOLD DIGIT ONE]
+alias	\U0001D7D0	\x32	# 𝟐 [MATHEMATICAL BOLD DIGIT TWO]
+alias	\U0001D7D1	\x33	# 𝟑 [MATHEMATICAL BOLD DIGIT THREE]
+alias	\U0001D7D2	\x34	# 𝟒 [MATHEMATICAL BOLD DIGIT FOUR]
+alias	\U0001D7D3	\x35	# 𝟓 [MATHEMATICAL BOLD DIGIT FIVE]
+alias	\U0001D7D4	\x36	# 𝟔 [MATHEMATICAL BOLD DIGIT SIX]
+alias	\U0001D7D5	\x37	# 𝟕 [MATHEMATICAL BOLD DIGIT SEVEN]
+alias	\U0001D7D6	\x38	# 𝟖 [MATHEMATICAL BOLD DIGIT EIGHT]
+alias	\U0001D7D7	\x39	# 𝟗 [MATHEMATICAL BOLD DIGIT NINE]
+
+alias	\U0001D7D8	\x30	# 𝟘 [MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO]
+alias	\U0001D7D9	\x31	# 𝟙 [MATHEMATICAL DOUBLE-STRUCK DIGIT ONE]
+alias	\U0001D7DA	\x32	# 𝟚 [MATHEMATICAL DOUBLE-STRUCK DIGIT TWO]
+alias	\U0001D7DB	\x33	# 𝟛 [MATHEMATICAL DOUBLE-STRUCK DIGIT THREE]
+alias	\U0001D7DC	\x34	# 𝟜 [MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR]
+alias	\U0001D7DD	\x35	# 𝟝 [MATHEMATICAL DOUBLE-STRUCK DIGIT FIVE]
+alias	\U0001D7DE	\x36	# 𝟞 [MATHEMATICAL DOUBLE-STRUCK DIGIT SIX]
+alias	\U0001D7DF	\x37	# 𝟟 [MATHEMATICAL DOUBLE-STRUCK DIGIT SEVEN]
+alias	\U0001D7E0	\x38	# 𝟠 [MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT]
+alias	\U0001D7E1	\x39	# 𝟡 [MATHEMATICAL DOUBLE-STRUCK DIGIT NINE]
+
+alias	\U0001D7E2	\x30	# 𝟢 [MATHEMATICAL SANS-SERIF DIGIT ZERO]
+alias	\U0001D7E3	\x31	# 𝟣 [MATHEMATICAL SANS-SERIF DIGIT ONE]
+alias	\U0001D7E4	\x32	# 𝟤 [MATHEMATICAL SANS-SERIF DIGIT TWO]
+alias	\U0001D7E5	\x33	# 𝟥 [MATHEMATICAL SANS-SERIF DIGIT THREE]
+alias	\U0001D7E6	\x34	# 𝟦 [MATHEMATICAL SANS-SERIF DIGIT FOUR]
+alias	\U0001D7E7	\x35	# 𝟧 [MATHEMATICAL SANS-SERIF DIGIT FIVE]
+alias	\U0001D7E8	\x36	# 𝟨 [MATHEMATICAL SANS-SERIF DIGIT SIX]
+alias	\U0001D7E9	\x37	# 𝟩 [MATHEMATICAL SANS-SERIF DIGIT SEVEN]
+alias	\U0001D7EA	\x38	# 𝟪 [MATHEMATICAL SANS-SERIF DIGIT EIGHT]
+alias	\U0001D7EB	\x39	# 𝟫 [MATHEMATICAL SANS-SERIF DIGIT NINE]
+
+alias	\U0001D7EC	\x30	# 𝟬 [MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO]
+alias	\U0001D7ED	\x31	# 𝟭 [MATHEMATICAL SANS-SERIF BOLD DIGIT ONE]
+alias	\U0001D7EE	\x32	# 𝟮 [MATHEMATICAL SANS-SERIF BOLD DIGIT TWO]
+alias	\U0001D7EF	\x33	# 𝟯 [MATHEMATICAL SANS-SERIF BOLD DIGIT THREE]
+alias	\U0001D7F0	\x34	# 𝟰 [MATHEMATICAL SANS-SERIF BOLD DIGIT FOUR]
+alias	\U0001D7F1	\x35	# 𝟱 [MATHEMATICAL SANS-SERIF BOLD DIGIT FIVE]
+alias	\U0001D7F2	\x36	# 𝟲 [MATHEMATICAL SANS-SERIF BOLD DIGIT SIX]
+alias	\U0001D7F3	\x37	# 𝟳 [MATHEMATICAL SANS-SERIF BOLD DIGIT SEVEN]
+alias	\U0001D7F4	\x38	# 𝟴 [MATHEMATICAL SANS-SERIF BOLD DIGIT EIGHT]
+alias	\U0001D7F5	\x39	# 𝟵 [MATHEMATICAL SANS-SERIF BOLD DIGIT NINE]
+
+alias	\U0001D7F6	\x30	# 𝟶 [MATHEMATICAL MONOSPACE DIGIT ZERO]
+alias	\U0001D7F7	\x31	# 𝟷 [MATHEMATICAL MONOSPACE DIGIT ONE]
+alias	\U0001D7F8	\x32	# 𝟸 [MATHEMATICAL MONOSPACE DIGIT TWO]
+alias	\U0001D7F9	\x33	# 𝟹 [MATHEMATICAL MONOSPACE DIGIT THREE]
+alias	\U0001D7FA	\x34	# 𝟺 [MATHEMATICAL MONOSPACE DIGIT FOUR]
+alias	\U0001D7FB	\x35	# 𝟻 [MATHEMATICAL MONOSPACE DIGIT FIVE]
+alias	\U0001D7FC	\x36	# 𝟼 [MATHEMATICAL MONOSPACE DIGIT SIX]
+alias	\U0001D7FD	\x37	# 𝟽 [MATHEMATICAL MONOSPACE DIGIT SEVEN]
+alias	\U0001D7FE	\x38	# 𝟾 [MATHEMATICAL MONOSPACE DIGIT EIGHT]
+alias	\U0001D7FF	\x39	# 𝟿 [MATHEMATICAL MONOSPACE DIGIT NINE]
diff --git a/Tables/Text/num-dot6.tti b/Tables/Text/num-dot6.tti
new file mode 100644
index 0000000..c84daf9
--- /dev/null
+++ b/Tables/Text/num-dot6.tti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements a common scheme for representing the 10
+# Hindu-Arabic numerals in braille. The digit 0 is represented by dots 346, and
+# the digits 1-9 are represented by the letters a-i with dot 6 added.
+
+char \x30	(  34 6  )  # ⠬ 0 [DIGIT ZERO]
+char \x31	(1    6  )  # ⠡ 1 [DIGIT ONE]
+char \x32	(12   6  )  # ⠣ 2 [DIGIT TWO]
+char \x33	(1  4 6  )  # ⠩ 3 [DIGIT THREE]
+char \x34	(1  456  )  # ⠹ 4 [DIGIT FOUR]
+char \x35	(1   56  )  # ⠱ 5 [DIGIT FIVE]
+char \x36	(12 4 6  )  # ⠫ 6 [DIGIT SIX]
+char \x37	(12 456  )  # ⠻ 7 [DIGIT SEVEN]
+char \x38	(12  56  )  # ⠳ 8 [DIGIT EIGHT]
+char \x39	( 2 4 6  )  # ⠪ 9 [DIGIT NINE]
+
+include num-alias.tti
diff --git a/Tables/Text/num-dot8.tti b/Tables/Text/num-dot8.tti
new file mode 100644
index 0000000..e8c8fb5
--- /dev/null
+++ b/Tables/Text/num-dot8.tti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements a common scheme for representing the 10
+# Hindu-Arabic numerals in braille. Dot 8 is added to the letters a-j, with the
+# letters a-i representing the digits 1-9, and with the letter j representing
+# the digit 0.
+
+char \x30	( 2 45  8)  # ⢚ 0 [DIGIT ZERO]
+char \x31	(1      8)  # ⢁ 1 [DIGIT ONE]
+char \x32	(12     8)  # ⢃ 2 [DIGIT TWO]
+char \x33	(1  4   8)  # ⢉ 3 [DIGIT THREE]
+char \x34	(1  45  8)  # ⢙ 4 [DIGIT FOUR]
+char \x35	(1   5  8)  # ⢑ 5 [DIGIT FIVE]
+char \x36	(12 4   8)  # ⢋ 6 [DIGIT SIX]
+char \x37	(12 45  8)  # ⢛ 7 [DIGIT SEVEN]
+char \x38	(12  5  8)  # ⢓ 8 [DIGIT EIGHT]
+char \x39	( 2 4   8)  # ⢊ 9 [DIGIT NINE]
+
+include num-alias.tti
diff --git a/Tables/Text/num-french.tti b/Tables/Text/num-french.tti
new file mode 100644
index 0000000..84521a6
--- /dev/null
+++ b/Tables/Text/num-french.tti
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements the French scheme for representing the
+# 10 Hindu-Arabic numerals in braille. The digit 0 is represented by dots 3456,
+# and the digits 1-9 are represented by the letters a-i with dot 6 added.
+
+char \x30	(  3456  )  # ⠼ 0 [DIGIT ZERO]
+char \x31	(1    6  )  # ⠡ 1 [DIGIT ONE]
+char \x32	(12   6  )  # ⠣ 2 [DIGIT TWO]
+char \x33	(1  4 6  )  # ⠩ 3 [DIGIT THREE]
+char \x34	(1  456  )  # ⠹ 4 [DIGIT FOUR]
+char \x35	(1   56  )  # ⠱ 5 [DIGIT FIVE]
+char \x36	(12 4 6  )  # ⠫ 6 [DIGIT SIX]
+char \x37	(12 456  )  # ⠻ 7 [DIGIT SEVEN]
+char \x38	(12  56  )  # ⠳ 8 [DIGIT EIGHT]
+char \x39	( 2 4 6  )  # ⠪ 9 [DIGIT NINE]
+
+include num-alias.tti
diff --git a/Tables/Text/num-nemd8.tti b/Tables/Text/num-nemd8.tti
new file mode 100644
index 0000000..8abfd7e
--- /dev/null
+++ b/Tables/Text/num-nemd8.tti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements a common scheme for representing the
+# 10 Hindu-Arabic numerals in braille. The letters a-j are lowered by one dot
+# position, with the letters a-i representing the digits 1-9, and with the
+# letter j representing the digit 0.  Additionally, dot 8 is added.
+
+char	\x30	(  3 56 8)  # ⢴ 0 [DIGIT ZERO]
+char	\x31	( 2     8)  # ⢂ 1 [DIGIT ONE]
+char	\x32	( 23    8)  # ⢆ 2 [DIGIT TWO]
+char	\x33	( 2  5  8)  # ⢒ 3 [DIGIT THREE]
+char	\x34	( 2  56 8)  # ⢲ 4 [DIGIT FOUR]
+char	\x35	( 2   6 8)  # ⢢ 5 [DIGIT FIVE]
+char	\x36	( 23 5  8)  # ⢖ 6 [DIGIT SIX]
+char	\x37	( 23 56 8)  # ⢶ 7 [DIGIT SEVEN]
+char	\x38	( 23  6 8)  # ⢦ 8 [DIGIT EIGHT]
+char	\x39	(  3 5  8)  # ⢔ 9 [DIGIT NINE]
+
+include num-alias.tti
diff --git a/Tables/Text/num-nemeth.tti b/Tables/Text/num-nemeth.tti
new file mode 100644
index 0000000..0255d17
--- /dev/null
+++ b/Tables/Text/num-nemeth.tti
@@ -0,0 +1,35 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable implements the Nemeth scheme for representing the
+# 10 Hindu-Arabic numerals in braille. The letters a-j are lowered by one dot
+# position, with the letters a-i representing the digits 1-9, and with the
+# letter j representing the digit 0.
+
+char \x30	(  3 56  )  # ⠴ 0 [DIGIT ZERO]
+char \x31	( 2      )  # ⠂ 1 [DIGIT ONE]
+char \x32	( 23     )  # ⠆ 2 [DIGIT TWO]
+char \x33	( 2  5   )  # ⠒ 3 [DIGIT THREE]
+char \x34	( 2  56  )  # ⠲ 4 [DIGIT FOUR]
+char \x35	( 2   6  )  # ⠢ 5 [DIGIT FIVE]
+char \x36	( 23 5   )  # ⠖ 6 [DIGIT SIX]
+char \x37	( 23 56  )  # ⠶ 7 [DIGIT SEVEN]
+char \x38	( 23  6  )  # ⠦ 8 [DIGIT EIGHT]
+char \x39	(  3 5   )  # ⠔ 9 [DIGIT NINE]
+
+include num-alias.tti
diff --git a/Tables/Text/nwc.ttb b/Tables/Text/nwc.ttb
new file mode 100644
index 0000000..ae458cc
--- /dev/null
+++ b/Tables/Text/nwc.ttb
@@ -0,0 +1,25 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Newari (old)
+
+include devanagari.tti
+include bengali.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/or.ttb b/Tables/Text/or.ttb
new file mode 100644
index 0000000..8d75e2e
--- /dev/null
+++ b/Tables/Text/or.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Oriya
+
+include oriya.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/oriya.tti b/Tables/Text/oriya.tti
new file mode 100644
index 0000000..9278a14
--- /dev/null
+++ b/Tables/Text/oriya.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Oriya script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0B01	(  3     )  # ⠄ ଁ [ORIYA SIGN CANDRABINDU]
+char \u0B02	(    56  )  # ⠰ ଂ [ORIYA SIGN ANUSVARA]
+char \u0B03	(     6  )  # ⠠ ଃ [ORIYA SIGN VISARGA]
+char \u0B05	(1       )  # ⠁ ଅ [ORIYA LETTER A]
+char \u0B06	(  345   )  # ⠜ ଆ [ORIYA LETTER AA]
+char \u0B07	( 2 4    )  # ⠊ ଇ [ORIYA LETTER I]
+char \u0B08	(  3 5   )  # ⠔ ଈ [ORIYA LETTER II]
+char \u0B09	(1 3  6  )  # ⠥ ଉ [ORIYA LETTER U]
+char \u0B0A	(12  56  )  # ⠳ ଊ [ORIYA LETTER UU]
+
+
+char \u0B0F	(1   5   )  # ⠑ ଏ [ORIYA LETTER E]
+char \u0B10	(  34    )  # ⠌ ଐ [ORIYA LETTER AI]
+
+
+char \u0B13	(1 3 5   )  # ⠕ ଓ [ORIYA LETTER O]
+char \u0B14	( 2 4 6  )  # ⠪ ଔ [ORIYA LETTER AU]
+char \u0B15	(1 3     )  # ⠅ କ [ORIYA LETTER KA]
+char \u0B16	(   4 6  )  # ⠨ ଖ [ORIYA LETTER KHA]
+char \u0B17	(1234    )  # ⠏ ଗ [ORIYA LETTER GA]
+char \u0B18	(12   6  )  # ⠣ ଘ [ORIYA LETTER GHA]
+char \u0B19	(  34 6  )  # ⠬ ଙ [ORIYA LETTER NGA]
+char \u0B1A	(1  4    )  # ⠉ ଚ [ORIYA LETTER CA]
+char \u0B1B	(1    6  )  # ⠡ ଛ [ORIYA LETTER CHA]
+char \u0B1C	( 2 45   )  # ⠚ ଜ [ORIYA LETTER JA]
+char \u0B1D	(  3 56  )  # ⠴ ଝ [ORIYA LETTER JHA]
+char \u0B1E	( 2  5   )  # ⠒ ଞ [ORIYA LETTER NYA]
+char \u0B1F	( 23456  )  # ⠾ ଟ [ORIYA LETTER TTA]
+char \u0B20	( 2 456  )  # ⠺ ଠ [ORIYA LETTER TTHA]
+char \u0B21	(12 4 6  )  # ⠫ ଡ [ORIYA LETTER DDA]
+char \u0B22	(123456  )  # ⠿ ଢ [ORIYA LETTER DDHA]
+char \u0B23	(  3456  )  # ⠼ ଣ [ORIYA LETTER NNA]
+char \u0B24	( 2345   )  # ⠞ ତ [ORIYA LETTER TA]
+char \u0B25	(1  456  )  # ⠹ ଥ [ORIYA LETTER THA]
+char \u0B26	(1  45   )  # ⠙ ଦ [ORIYA LETTER DA]
+char \u0B27	( 234 6  )  # ⠮ ଧ [ORIYA LETTER DHA]
+char \u0B28	(1 345   )  # ⠝ ନ [ORIYA LETTER NA]
+
+char \u0B2A	(1234    )  # ⠏ ପ [ORIYA LETTER PA]
+char \u0B2B	( 23 5   )  # ⠖ ଫ [ORIYA LETTER PHA]
+char \u0B2C	(12      )  # ⠃ ବ [ORIYA LETTER BA]
+char \u0B2D	(   45   )  # ⠘ ଭ [ORIYA LETTER BHA]
+char \u0B2E	(1 34    )  # ⠍ ମ [ORIYA LETTER MA]
+char \u0B2F	(1 3456  )  # ⠽ ଯ [ORIYA LETTER YA]
+char \u0B30	(123 5   )  # ⠗ ର [ORIYA LETTER RA]
+
+char \u0B32	(123     )  # ⠇ ଲ [ORIYA LETTER LA]
+char \u0B33	(123   7 )  # ⡇ ଳ [ORIYA LETTER LLA]
+
+char \u0B35	(123  6  )  # ⠧ ଵ [ORIYA LETTER VA]
+char \u0B36	(1  4 6  )  # ⠩ ଶ [ORIYA LETTER SHA]
+char \u0B37	(1234 6  )  # ⠯ ଷ [ORIYA LETTER SSA]
+char \u0B38	( 234    )  # ⠎ ସ [ORIYA LETTER SA]
+char \u0B39	(12  5   )  # ⠓ ହ [ORIYA LETTER HA]
+char \u0B3D	( 2      )  # ⠂ ଽ [ORIYA SIGN AVAGRAHA]
+char \u0B3E	(  345   )  # ⠜ ା [ORIYA VOWEL SIGN AA]
+char \u0B3F	( 2 4    )  # ⠊ ି [ORIYA VOWEL SIGN I]
+char \u0B40	(  3 5   )  # ⠔ ୀ [ORIYA VOWEL SIGN II]
+char \u0B41	(1 3  6  )  # ⠥ ୁ [ORIYA VOWEL SIGN U]
+char \u0B42	(12  56  )  # ⠳ ୂ [ORIYA VOWEL SIGN UU]
+
+
+char \u0B47	(1   5   )  # ⠑ େ [ORIYA VOWEL SIGN E]
+char \u0B48	(  34    )  # ⠌ ୈ [ORIYA VOWEL SIGN AI]
+
+
+char \u0B4B	(1 3 5   )  # ⠕ ୋ [ORIYA VOWEL SIGN O]
+char \u0B4C	( 2 4 6  )  # ⠪ ୌ [ORIYA VOWEL SIGN AU]
+char \u0B4D	(   4    )  # ⠈ ୍ [ORIYA SIGN VIRAMA]
+
+char \u0B66	( 2 45   )  # ⠚ ୦ [ORIYA DIGIT ZERO]
+char \u0B67	(1       )  # ⠁ ୧ [ORIYA DIGIT ONE]
+char \u0B68	(12      )  # ⠃ ୨ [ORIYA DIGIT TWO]
+char \u0B69	(1  4    )  # ⠉ ୩ [ORIYA DIGIT THREE]
+char \u0B6A	(1  45   )  # ⠙ ୪ [ORIYA DIGIT FOUR]
+char \u0B6B	(1   5   )  # ⠑ ୫ [ORIYA DIGIT FIVE]
+char \u0B6C	(12 4    )  # ⠋ ୬ [ORIYA DIGIT SIX]
+char \u0B6D	(12 45   )  # ⠛ ୭ [ORIYA DIGIT SEVEN]
+char \u0B6E	(12  5   )  # ⠓ ୮ [ORIYA DIGIT EIGHT]
+char \u0B6F	( 2 4    )  # ⠊ ୯ [ORIYA DIGIT NINE]
diff --git a/Tables/Text/pa.ttb b/Tables/Text/pa.ttb
new file mode 100644
index 0000000..fbbe401
--- /dev/null
+++ b/Tables/Text/pa.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Panjabi
+
+include gurmukhi.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/pi.ttb b/Tables/Text/pi.ttb
new file mode 100644
index 0000000..c398482
--- /dev/null
+++ b/Tables/Text/pi.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Pali
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/pl.ttb b/Tables/Text/pl.ttb
new file mode 100644
index 0000000..19c3c66
--- /dev/null
+++ b/Tables/Text/pl.ttb
@@ -0,0 +1,170 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Polish
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-2
+char \x00	(   4  78)  # 00 ⣈   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2 4 678)  # 1B ⣪   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12 45678)  # 1D ⣻   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(    5   )  # 22 ⠐ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(12 4 6  )  # 24 ⠫ $ [DOLLAR SIGN]
+char \x25	(1  4 6 8)  # 25 ⢩ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(123 56  )  # 28 ⠷ ( [LEFT PARENTHESIS]
+char \x29	( 23456  )  # 29 ⠾ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(12   6 8)  # 3C ⢣ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  345  8)  # 3E ⢜ > [GREATER-THAN SIGN]
+char \x3F	( 23  6  )  # 3F ⠦ ? [QUESTION MARK]
+char \x40	(   4  7 )  # 40 ⡈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 2 4 6 8)  # 5B ⢪ [ [LEFT SQUARE BRACKET]
+char \x5C	(12  567 )  # 5C ⡳ \ [REVERSE SOLIDUS]
+char \x5D	(12 4567 )  # 5D ⡻ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # 5F ⠸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 23 567 )  # 7B ⡶ { [LEFT CURLY BRACKET]
+char \x7C	(12  56  )  # 7C ⠳ | [VERTICAL LINE]
+char \x7D	( 23 56 8)  # 7D ⢶ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \x7F	(   4567 )  # 7F ⡸   [DELETE]
+char \u0104	(1    67 )  # A1 ⡡ Ą [LATIN CAPITAL LETTER A WITH OGONEK]
+char \u02D8	(    5  8)  # A2 ⢐ ˘ [BREVE]
+char \u0141	(12   67 )  # A3 ⡣ Ł [LATIN CAPITAL LETTER L WITH STROKE]
+char \xA4	(  3  678)  # A4 ⣤ ¤ [CURRENCY SIGN]
+char \u013D	(   4 6 8)  # A5 ⢨ Ľ [LATIN CAPITAL LETTER L WITH CARON]
+char \u015A	( 2 4 67 )  # A6 ⡪ Ś [LATIN CAPITAL LETTER S WITH ACUTE]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	( 2    78)  # A8 ⣂ ¨ [DIAERESIS]
+char \u0160	(1  4567 )  # A9 ⡹ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u015E	(12  5  8)  # AA ⢓ Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
+char \u0164	(123 5678)  # AB ⣷ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char \u0179	( 234 67 )  # AC ⡮ Ź [LATIN CAPITAL LETTER Z WITH ACUTE]
+char \u017D	(   4 678)  # AE ⣨ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char \u017B	(1234 6 8)  # AF ⢯ Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+char \xB0	(  3 56  )  # B0 ⠴ ° [DEGREE SIGN]
+char \u0105	(1    6  )  # B1 ⠡ ą [LATIN SMALL LETTER A WITH OGONEK]
+char \u02DB	(       8)  # B2 ⢀ ˛ [OGONEK]
+char \u0142	(12   6  )  # B3 ⠣ ł [LATIN SMALL LETTER L WITH STROKE]
+char \xB4	(   4    )  # B4 ⠈ ´ [ACUTE ACCENT]
+char \u013E	(1 34   8)  # B5 ⢍ ľ [LATIN SMALL LETTER L WITH CARON]
+char \u015B	( 2 4 6  )  # B6 ⠪ ś [LATIN SMALL LETTER S WITH ACUTE]
+char \u02C7	(  3   7 )  # B7 ⡄ ˇ [CARON]
+char \xB8	(       8)  # B8 ⢀ ¸ [CEDILLA]
+char \u0161	(123 567 )  # B9 ⡷ š [LATIN SMALL LETTER S WITH CARON]
+char \u015F	( 2 45  8)  # BA ⢚ ş [LATIN SMALL LETTER S WITH CEDILLA]
+char \u0165	( 2345678)  # BB ⣾ ť [LATIN SMALL LETTER T WITH CARON]
+char \u017A	( 234 6  )  # BC ⠮ ź [LATIN SMALL LETTER Z WITH ACUTE]
+char \u02DD	(   45   )  # BD ⠘ ˝ [DOUBLE ACUTE ACCENT]
+char \u017E	(  34 678)  # BE ⣬ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u017C	(1234 6  )  # BF ⠯ ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
+char \u0154	(  3   78)  # C0 ⣄ Ŕ [LATIN CAPITAL LETTER R WITH ACUTE]
+char \xC1	( 2    7 )  # C1 ⡂ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1 3    8)  # C2 ⢅ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \u0102	( 23  67 )  # C3 ⡦ Ă [LATIN CAPITAL LETTER A WITH BREVE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \u0139	(  34567 )  # C5 ⡼ Ĺ [LATIN CAPITAL LETTER L WITH ACUTE]
+char \u0106	(1  4 67 )  # C6 ⡩ Ć [LATIN CAPITAL LETTER C WITH ACUTE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \u010C	( 23   78)  # C8 ⣆ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \xC9	( 23    8)  # C9 ⢆ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \u0118	(1   567 )  # CA ⡱ Ę [LATIN CAPITAL LETTER E WITH OGONEK]
+char \xCB	(12345  8)  # CB ⢟ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \u011A	(12 4   8)  # CC ⢋ Ě [LATIN CAPITAL LETTER E WITH CARON]
+char \xCD	( 2  5 78)  # CD ⣒ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(    5678)  # CE ⣰ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \u010E	(1    67 )  # CF ⡡ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char \u0110	(    5 7 )  # D0 ⡐ Đ [LATIN CAPITAL LETTER D WITH STROKE]
+char \u0143	(1  4567 )  # D1 ⡹ Ń [LATIN CAPITAL LETTER N WITH ACUTE]
+char \u0147	( 2     8)  # D2 ⢂ Ň [LATIN CAPITAL LETTER N WITH CARON]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2 4   8)  # D4 ⢊ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \u0150	(1234 6 8)  # D5 ⢯ Ő [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
+char \xD6	(  3 5  8)  # D6 ⢔ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23  6  )  # D7 ⠦ × [MULTIPLICATION SIGN]
+char \u0158	(  3 567 )  # D8 ⡴ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char \u016E	(     678)  # D9 ⣠ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char \xDA	( 2  5 7 )  # DA ⡒ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \u0170	(12345678)  # DB ⣿ Ű [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
+char \xDC	( 23  6 8)  # DC ⢦ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	( 23  678)  # DD ⣦ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \u0162	(  3 5678)  # DE ⣴ Ţ [LATIN CAPITAL LETTER T WITH CEDILLA]
+char \xDF	(  3456 8)  # DF ⢼ ß [LATIN SMALL LETTER SHARP S]
+char \u0155	(123 56 8)  # E0 ⢷ ŕ [LATIN SMALL LETTER R WITH ACUTE]
+char \xE1	(1    6 8)  # E1 ⢡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \u0103	(1234   8)  # E3 ⢏ ă [LATIN SMALL LETTER A WITH BREVE]
+char \xE4	(  345  8)  # E4 ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \u013A	(  345678)  # E5 ⣼ ĺ [LATIN SMALL LETTER L WITH ACUTE]
+char \u0107	(1  4 6  )  # E6 ⠩ ć [LATIN SMALL LETTER C WITH ACUTE]
+char \xE7	(1234 678)  # E7 ⣯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \u010D	( 234 6 8)  # E8 ⢮ č [LATIN SMALL LETTER C WITH CARON]
+char \xE9	(12   6 8)  # E9 ⢣ é [LATIN SMALL LETTER E WITH ACUTE]
+char \u0119	(1   56  )  # EA ⠱ ę [LATIN SMALL LETTER E WITH OGONEK]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \u011B	(  34   8)  # EC ⢌ ě [LATIN SMALL LETTER E WITH CARON]
+char \xED	(1  4 6 8)  # ED ⢩ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \u010F	(12 456 8)  # EF ⢻ ď [LATIN SMALL LETTER D WITH CARON]
+char \u0111	( 23 56 8)  # F0 ⢶ đ [LATIN SMALL LETTER D WITH STROKE]
+char \u0144	(1  456  )  # F1 ⠹ ń [LATIN SMALL LETTER N WITH ACUTE]
+char \u0148	(  34 6 8)  # F2 ⢬ ň [LATIN SMALL LETTER N WITH CARON]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  45678)  # F4 ⣹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \u0151	(1   567 )  # F5 ⡱ ő [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
+char \xF6	( 2 4 6 8)  # F6 ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(12  5678)  # F7 ⣳ ÷ [DIVISION SIGN]
+char \u0159	(      78)  # F8 ⣀ ř [LATIN SMALL LETTER R WITH CARON]
+char \u016F	( 23456 8)  # F9 ⢾ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char \xFA	(1   56 8)  # FA ⢱ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \u0171	(1   5678)  # FB ⣱ ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
+char \xFC	(12  56 8)  # FC ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	( 23 5678)  # FD ⣶ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \u0163	(1234567 )  # FE ⡿ ţ [LATIN SMALL LETTER T WITH CEDILLA]
+char \u02D9	(       8)  # FF ⢀ ˙ [DOT ABOVE]
+
+include common.tti
diff --git a/Tables/Text/pt.ttb b/Tables/Text/pt.ttb
new file mode 100644
index 0000000..f7fb8db
--- /dev/null
+++ b/Tables/Text/pt.ttb
@@ -0,0 +1,171 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Portuguese
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	( 234 678)  # 00 ⣮   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(123 5678)  # 1B ⣷   [ESCAPE]
+char \x1C	(  34 678)  # 1C ⣬   [INFORMATION SEPARATOR FOUR]
+char \x1D	( 2345678)  # 1D ⣾   [INFORMATION SEPARATOR THREE]
+char \x1E	(     678)  # 1E ⣠   [INFORMATION SEPARATOR TWO]
+char \x1F	(    5 78)  # 1F ⣐   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5  8)  # 21 ⢖ ! [EXCLAMATION MARK]
+char \x22	( 23  6  )  # 22 ⠦ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(    56  )  # 24 ⠰ $ [DOLLAR SIGN]
+char \x25	(  3 5678)  # 25 ⣴ % [PERCENT SIGN]
+char \x26	(1234 6 8)  # 26 ⢯ & [AMPERSAND]
+char \x27	(  3   7 )  # 27 ⡄ ' [APOSTROPHE]
+char \x28	(12   6 8)  # 28 ⢣ ( [LEFT PARENTHESIS]
+char \x29	(  345  8)  # 29 ⢜ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5   )  # 2B ⠖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(     67 )  # 2F ⡠ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 2 4 6 8)  # 3C ⢪ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(1 3 5  8)  # 3E ⢕ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(1   56 8)  # 40 ⢱ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56 8)  # 5B ⢷ [ [LEFT SQUARE BRACKET]
+char \x5C	(  3    8)  # 5C ⢄ \ [REVERSE SOLIDUS]
+char \x5D	( 23456 8)  # 5D ⢾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   4    )  # 5E ⠈ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3 5  8)  # 5F ⢔ _ [LOW LINE]
+char \x60	( 2   6 8)  # 60 ⢢ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(123    8)  # 7B ⢇ { [LEFT CURLY BRACKET]
+char \x7C	(   456  )  # 7C ⠸ | [VERTICAL LINE]
+char \x7D	(   4567 )  # 7D ⡸ } [RIGHT CURLY BRACKET]
+char \x7E	(    5   )  # 7E ⠐ ~ [TILDE]
+char \x7F	(    5 7 )  # 7F ⡐   [DELETE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(    56 8)  # A2 ⢰ ¢ [CENT SIGN]
+char \xA3	(   4 6  )  # A3 ⠨ £ [POUND SIGN]
+char \xA4	(  3  678)  # A4 ⣤ ¤ [CURRENCY SIGN]
+char \xA5	(   4 6 8)  # A5 ⢨ ¥ [YEN SIGN]
+char \xA6	( 2   678)  # A6 ⣢ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	( 2    78)  # A8 ⣂ ¨ [DIAERESIS]
+char \xA9	(1  4  7 )  # A9 ⡉ © [COPYRIGHT SIGN]
+char \xAA	(   4  7 )  # AA ⡈ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23  67 )  # AB ⡦ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	( 2  5678)  # AC ⣲ ¬ [NOT SIGN]
+char \xAD	(12 4   8)  # AD ⢋ ­ [SOFT HYPHEN]
+char \xAE	(123 5  8)  # AE ⢗ ® [REGISTERED SIGN]
+char \xAF	(    5  8)  # AF ⢐ ¯ [MACRON]
+char \xB0	(  3 567 )  # B0 ⡴ ° [DEGREE SIGN]
+char \xB1	( 23 5 78)  # B1 ⣖ ± [PLUS-MINUS SIGN]
+char \xB2	( 23    8)  # B2 ⢆ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  5  8)  # B3 ⢒ ³ [SUPERSCRIPT THREE]
+char \xB4	(  3 5 7 )  # B4 ⡔ ´ [ACUTE ACCENT]
+char \xB5	(1 34   8)  # B5 ⢍ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(  3   7 )  # B7 ⡄ · [MIDDLE DOT]
+char \xB8	(1  4   8)  # B8 ⢉ ¸ [CEDILLA]
+char \xB9	(   45 7 )  # B9 ⡘ ¹ [SUPERSCRIPT ONE]
+char \xBA	(  34 6 8)  # BA ⢬ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	( 23  6 8)  # BB ⢦ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(1 3  6 8)  # BC ⢥ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(123  6 8)  # BD ⢧ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  34  78)  # BE ⣌ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  3    8)  # BF ⢄ ¿ [INVERTED QUESTION MARK]
+char \xC0	(12 4 67 )  # C0 ⡫ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(123 567 )  # C1 ⡷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(1    67 )  # C2 ⡡ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  3 567 )  # C3 ⡴ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(    567 )  # C4 ⡰ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(  34567 )  # C5 ⡼ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(   4  7 )  # C6 ⡈ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	( 234 67 )  # C8 ⡮ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(12   67 )  # CA ⡣ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(12   678)  # CB ⣣ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(1  4 67 )  # CC ⡩ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  34  7 )  # CD ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1  4 678)  # CE ⣩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(12 4567 )  # CF ⡻ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(  345 7 )  # D0 ⡜ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	( 2 456 8)  # D2 ⢺ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  34 67 )  # D3 ⡬ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(1  4567 )  # D4 ⡹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 2 4 67 )  # D5 ⡪ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(1  45678)  # D6 ⣹ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	( 23  678)  # D7 ⣦ × [MULTIPLICATION SIGN]
+char \xD8	(  3 567 )  # D8 ⡴ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(1   567 )  # D9 ⡱ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 234567 )  # DA ⡾ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(1   5678)  # DB ⣱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(12  5678)  # DD ⣳ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(  3 5678)  # DE ⣴ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(  3456 8)  # DF ⢼ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(12 4 6  )  # E0 ⠫ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  345   )  # E3 ⠜ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345 78)  # E4 ⣜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(  345678)  # E5 ⣼ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(   4  78)  # E6 ⣈ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6 8)  # EB ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(1  4 6  )  # EC ⠩ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 678)  # EE ⣩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	( 23 56 8)  # F0 ⢶ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1 345  8)  # F1 ⢝ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	( 2 456 8)  # F2 ⢺ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 2 4 6  )  # F5 ⠪ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 678)  # F6 ⣪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56  )  # F7 ⠲ ÷ [DIVISION SIGN]
+char \xF8	(   4 6 8)  # F8 ⢨ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	(1   56  )  # F9 ⠱ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56 8)  # FB ⢱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(12  56 8)  # FD ⢳ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(  3    8)  # FE ⢄ þ [LATIN SMALL LETTER THORN]
+char \xFF	(1 3456 8)  # FF ⢽ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/punc-alternate.tti b/Tables/Text/punc-alternate.tti
new file mode 100644
index 0000000..572ea6a
--- /dev/null
+++ b/Tables/Text/punc-alternate.tti
@@ -0,0 +1,53 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines alternate braille representations for the
+# ASCII punctuation characters.
+
+char \x21	( 23 5  8)  # ⢖ ! [EXCLAMATION MARK]
+char \x22	( 23 56 8)  # ⢶ " [QUOTATION MARK]
+char \x23	(  3456 8)  # ⢼ # [NUMBER SIGN]
+char \x24	(12 4 6 8)  # ⢫ $ [DOLLAR SIGN]
+char \x25	(1  4 6 8)  # ⢩ % [PERCENT SIGN]
+char \x26	( 234 6 8)  # ⢮ & [AMPERSAND]
+char \x27	(  3     )  # ⠄ ' [APOSTROPHE]
+char \x28	( 23  6 8)  # ⢦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56 8)  # ⢴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5  8)  # ⢔ * [ASTERISK]
+char \x2B	(  34 6 8)  # ⢬ + [PLUS SIGN]
+char \x2C	( 2     8)  # ⢂ , [COMMA]
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56 8)  # ⢲ . [FULL STOP]
+char \x2F	(  34   8)  # ⢌ / [SOLIDUS]
+char \x3A	( 2  5  8)  # ⢒ : [COLON]
+char \x3B	( 23    8)  # ⢆ ; [SEMICOLON]
+char \x3C	(12   6 8)  # ⢣ < [LESS-THAN SIGN]
+char \x3D	(123456 8)  # ⢿ = [EQUALS SIGN]
+char \x3E	(  345  8)  # ⢜ > [GREATER-THAN SIGN]
+char \x3F	( 2   6 8)  # ⢢ ? [QUESTION MARK]
+char \x40	(   4  7 )  # ⡈ @ [COMMERCIAL AT]
+char \x5B	(123 56 8)  # ⢷ [ [LEFT SQUARE BRACKET]
+char \x5C	(1    6 8)  # ⢡ \ [REVERSE SOLIDUS]
+char \x5D	( 23456 8)  # ⢾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   456  )  # ⠸ _ [LOW LINE]
+char \x60	(   4    )  # ⠈ ` [GRAVE ACCENT]
+char \x7B	( 23  678)  # ⣦ { [LEFT CURLY BRACKET]
+char \x7C	(12  56 8)  # ⢳ | [VERTICAL LINE]
+char \x7D	(  3 5678)  # ⣴ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # ⠘ ~ [TILDE]
diff --git a/Tables/Text/punc-basic.tti b/Tables/Text/punc-basic.tti
new file mode 100644
index 0000000..6af0a82
--- /dev/null
+++ b/Tables/Text/punc-basic.tti
@@ -0,0 +1,33 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations for the basic
+# English punctuation marks.
+
+char \x21	( 23 5   )  # ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # ⠶ " [QUOTATION MARK]
+char \x27	(  3     )  # ⠄ ' [APOSTROPHE]
+char \x28	( 23  6  )  # ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # ⠴ ) [RIGHT PARENTHESIS]
+char \x2C	( 2      )  # ⠂ , [COMMA]
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # ⠲ . [FULL STOP]
+char \x3A	( 2  5   )  # ⠒ : [COLON]
+char \x3B	( 23     )  # ⠆ ; [SEMICOLON]
+char \x3F	( 23  6  )  # ⠦ ? [QUESTION MARK]
+char \xA0	(        )  # ⠀   [NO-BREAK SPACE]
diff --git a/Tables/Text/punc-tibetan.tti b/Tables/Text/punc-tibetan.tti
new file mode 100644
index 0000000..9a89d09
--- /dev/null
+++ b/Tables/Text/punc-tibetan.tti
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the standard braille representations
+# for Tibetan punctuation marks.
+#
+# Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>
+
+char \u0F0B	(  3     )  # ⠄ ་ [TIBETAN MARK INTERSYLLABIC TSHEG]
+char \u0F0D	(12345678)  # ⣿ ། [TIBETAN MARK SHAD]
+
diff --git a/Tables/Text/ro.ttb b/Tables/Text/ro.ttb
new file mode 100644
index 0000000..ac3d9e5
--- /dev/null
+++ b/Tables/Text/ro.ttb
@@ -0,0 +1,57 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Romanian
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# lowercase accented letters
+char \u0103	(1    6  )  # ⠡ ă [LATIN SMALL LETTER A WITH BREVE]
+char \xE2	(12   6  )  # ⠣ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xEE	(1  4 6  )  # ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \u0219	(1   56  )  # ⠱ ș [LATIN SMALL LETTER S WITH COMMA BELOW]
+char \u021B	( 2 4 6  )  # ⠪ ț [LATIN SMALL LETTER T WITH COMMA BELOW]
+
+# uppercase accented letters
+char \xC2	(12   67 )  # ⡣ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xCE	(1  4 67 )  # ⡩ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \u0102	(1    67 )  # ⡡ Ă [LATIN CAPITAL LETTER A WITH BREVE]
+char \u0218	(1   567 )  # ⡱ Ș [LATIN CAPITAL LETTER S WITH COMMA BELOW]
+char \u021A	( 2 4 67 )  # ⡪ Ț [LATIN CAPITAL LETTER T WITH COMMA BELOW]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+
+# alias the letters with a comma below to their cedilla variants.
+alias \u015F	\u0219	# ⠱ ş [LATIN SMALL LETTER S WITH CEDILLA]
+alias \u0163	\u021B	# ⠪ ţ [LATIN SMALL LETTER T WITH CEDILLA]
+alias \u015E	\u0218	# ⡱ Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
+alias \u0162	\u021A	# ⡪ Ţ [LATIN CAPITAL LETTER T WITH CEDILLA]
+
+include common.tti
diff --git a/Tables/Text/ru.ttb b/Tables/Text/ru.ttb
new file mode 100644
index 0000000..d3f2da2
--- /dev/null
+++ b/Tables/Text/ru.ttb
@@ -0,0 +1,760 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Russian
+# This is a copy of ru.ctb from LibLouis-3.20 - edited as needed for BRLTTY syntax
+
+#-index-name: Russian, computer
+#-display-name: Russian computer braille
+
+#+locale: ru
+#+type: computer
+#+dots: 8
+#+direction: both
+
+# Copyright (C) 1995-2008 by The BRLTTY Developers.
+# Copyright (C) 2020-2021 by Andrey Yakuboy
+#
+#  This file is part of liblouis.
+#
+#  liblouis 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.
+#
+#  liblouis 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 liblouis. If not, see
+#  <http://www.gnu.org/licenses/>.
+
+# Russian Computer (8-Dots) Braille Table
+# by Hans Schou <chlor@schou.dk> and Dave Mielke <dave@mielke.cc>
+# Improved by Andrey Yakuboy <andrewia2002@yandex.ru>
+
+# This is the Russian computer braille table. It is based on the Unicode character set.
+#
+# As the Russian cyrillic definition conflicts with the latin definition, some
+# decisions had to be taken. Russians need to type both latin for the command
+# prompt and cyrillic while reading and writing documents and mail.
+#
+# In the following, latin letters are quoted with apostrophes like in 'a', and
+# cyrillic letters are enclosed within brackets like in [a].
+#
+# Dot 1 in the cyrillic definition is the cyrillic letter which looks and
+# sounds like 'a'. The problem is that in the Unicode character set, there is
+# both a latin 'a' and a cyrillic [a]. In decimal, their character numbers are
+# 97 and 193 respectively.  To handle conflicts like these, we have prioritized
+# which characters are most important to match the standard.
+#
+# RULES:
+#
+# 1. All cyrillic characters must follow the Russian standard. Unicode
+#    character 193 [a] must be dot-1, and so on.
+# 2. Capital cyrillic letters have dot 7 on.
+# 3. The latin alphabet is implemented to follow the international standard
+#    except it has dot 8 on.
+# 4. Capital latin letters have dots 7 and 8 on.
+# 5. Other alphabets and deacritics are basically implemented to follow the international standard
+#    with rare exceptions. Dots 7 and 8 are also added to these letters
+# 6. Numbers are defined as in the American standard. This means dot-2 for
+#    number '1', and so on.
+# 7. Special characters like !"#¤%&/()=? follow the standard that's accepted in
+#    Russian computer Braille tables in some programs, such as JAWS, TSS and
+#    others.
+
+# Comments from Russians are very welcome.
+
+#-maintainer-name: Andrey Yakuboy
+
+# General
+char \s 0		SPACE
+char \t 0		CHARACTER TABULATION
+char \n 78		LINE FEED (LF)
+char \xa0 0		NO-BREAK SPACE
+char \v 0		LINE TABULATION
+char \f 0		FORM FEED (FF)
+char \r 0		CARRIAGE RETURN (CR)
+char ! 5		EXCLAMATION MARK
+char " 4		QUOTATION MARK
+char # 3456		NUMBER SIGN
+char $ 467		DOLLAR SIGN
+char % 146		PERCENT SIGN
+char & 1234678		AMPERSAND
+char ' 47		APOSTROPHE
+char ( 126		LEFT PARENTHESIS
+char ) 345		RIGHT PARENTHESIS
+char * 357		ASTERISK
+char + 2357		PLUS SIGN
+char , 6		COMMA
+char - 36		HYPHEN-MINUS
+char . 3		FULL STOP
+char / 34		SOLIDUS
+# digits
+include num-nemeth.tti
+char : 46		COLON
+char ; 237		SEMICOLON
+char < 56		LESS-THAN SIGN
+char = 123456		EQUALS SIGN
+char > 45		GREATER-THAN SIGN
+char ? 1456		QUESTION MARK
+char @ 3457		COMMERCIAL AT
+char A 178		LATIN CAPITAL LETTER A
+char B 1278		LATIN CAPITAL LETTER B
+char C 1478		LATIN CAPITAL LETTER C
+char D 14578		LATIN CAPITAL LETTER D
+char E 1578		LATIN CAPITAL LETTER E
+char F 12478		LATIN CAPITAL LETTER F
+char G 124578		LATIN CAPITAL LETTER G
+char H 12578		LATIN CAPITAL LETTER H
+char I 2478		LATIN CAPITAL LETTER I
+char J 24578		LATIN CAPITAL LETTER J
+char K 1378		LATIN CAPITAL LETTER K
+char L 12378		LATIN CAPITAL LETTER L
+char M 13478		LATIN CAPITAL LETTER M
+char N 134578		LATIN CAPITAL LETTER N
+char O 13578		LATIN CAPITAL LETTER O
+char P 123478		LATIN CAPITAL LETTER P
+char Q 1234578		LATIN CAPITAL LETTER Q
+char R 123578		LATIN CAPITAL LETTER R
+char S 23478		LATIN CAPITAL LETTER S
+char T 234578		LATIN CAPITAL LETTER T
+char U 13678		LATIN CAPITAL LETTER U
+char V 123678		LATIN CAPITAL LETTER V
+char W 245678		LATIN CAPITAL LETTER W
+char X 134678		LATIN CAPITAL LETTER X
+char Y 1345678		LATIN CAPITAL LETTER Y
+char Z 135678		LATIN CAPITAL LETTER Z
+char [ 1235678		LEFT SQUARE BRACKET
+char \\ 3478		REVERSE SOLIDUS
+char ] 2345678		RIGHT SQUARE BRACKET
+char ^ 4578		CIRCUMFLEX ACCENT
+char _ 456		LOW LINE
+char ` 346		GRAVE ACCENT
+char a 18		LATIN SMALL LETTER A
+char b 128		LATIN SMALL LETTER B
+char c 148		LATIN SMALL LETTER C
+char d 1458		LATIN SMALL LETTER D
+char e 158		LATIN SMALL LETTER E
+char f 1248		LATIN SMALL LETTER F
+char g 12458		LATIN SMALL LETTER G
+char h 1258		LATIN SMALL LETTER H
+char i 248		LATIN SMALL LETTER I
+char j 2458		LATIN SMALL LETTER J
+char k 138		LATIN SMALL LETTER K
+char l 1238		LATIN SMALL LETTER L
+char m 1348		LATIN SMALL LETTER M
+char n 13458		LATIN SMALL LETTER N
+char o 1358		LATIN SMALL LETTER O
+char p 12348		LATIN SMALL LETTER P
+char q 123458		LATIN SMALL LETTER Q
+char r 12358		LATIN SMALL LETTER R
+char s 2348		LATIN SMALL LETTER S
+char t 23458		LATIN SMALL LETTER T
+char u 1368		LATIN SMALL LETTER U
+char v 12368		LATIN SMALL LETTER V
+char w 24568		LATIN SMALL LETTER W
+char x 13468		LATIN SMALL LETTER X
+char y 134568		LATIN SMALL LETTER Y
+char z 13568		LATIN SMALL LETTER Z
+char { 12678		LEFT CURLY BRACKET
+char | 4567		VERTICAL LINE
+char } 34578		RIGHT CURLY BRACKET
+char ~ 12456		TILDE
+char \xa9 123468		COPYRIGHT SIGN
+char \xb0 4568		DEGREE SIGN
+char \xb9 18		SUPERSCRIPT ONE
+char \xb2 128		SUPERSCRIPT TWO
+char \xb3 148		SUPERSCRIPT THREE
+char \xb7 37		MIDDLE DOT
+char \xf7 125678		DIVISION SIGN
+# Cyrillic alphabet
+char \u0401 167		CYRILLIC CAPITAL LETTER IO
+char \u0410 17		CYRILLIC CAPITAL LETTER A
+char \u0411 127		CYRILLIC CAPITAL LETTER BE
+char \u0412 24567		CYRILLIC CAPITAL LETTER VE
+char \u0413 12457		CYRILLIC CAPITAL LETTER GHE
+char \u0414 1457		CYRILLIC CAPITAL LETTER DE
+char \u0415 157		CYRILLIC CAPITAL LETTER IE
+char \u0416 2457		CYRILLIC CAPITAL LETTER ZHE
+char \u0417 13567		CYRILLIC CAPITAL LETTER ZE
+char \u0418 247		CYRILLIC CAPITAL LETTER I
+char \u0419 123467		CYRILLIC CAPITAL LETTER SHORT I
+char \u041a 137		CYRILLIC CAPITAL LETTER KA
+char \u041b 1237		CYRILLIC CAPITAL LETTER EL
+char \u041c 1347		CYRILLIC CAPITAL LETTER EM
+char \u041d 13457		CYRILLIC CAPITAL LETTER EN
+char \u041e 1357		CYRILLIC CAPITAL LETTER O
+char \u041f 12347		CYRILLIC CAPITAL LETTER PE
+char \u0420 12357		CYRILLIC CAPITAL LETTER ER
+char \u0421 2347		CYRILLIC CAPITAL LETTER ES
+char \u0422 23457		CYRILLIC CAPITAL LETTER TE
+char \u0423 1367		CYRILLIC CAPITAL LETTER U
+char \u0424 1247		CYRILLIC CAPITAL LETTER EF
+char \u0425 1257		CYRILLIC CAPITAL LETTER HA
+char \u0426 147		CYRILLIC CAPITAL LETTER TSE
+char \u0427 123457		CYRILLIC CAPITAL LETTER CHE
+char \u0428 1567		CYRILLIC CAPITAL LETTER SHA
+char \u0429 13467		CYRILLIC CAPITAL LETTER SHCHA
+char \u042a 123567		CYRILLIC CAPITAL LETTER HARD SIGN
+char \u042b 23467		CYRILLIC CAPITAL LETTER YERU
+char \u042c 234567		CYRILLIC CAPITAL LETTER SOFT SIGN
+char \u042d 2467		CYRILLIC CAPITAL LETTER E
+char \u042e 12567		CYRILLIC CAPITAL LETTER YU
+char \u042f 12467		CYRILLIC CAPITAL LETTER YA
+char \u0430 1		CYRILLIC SMALL LETTER A
+char \u0431 12		CYRILLIC SMALL LETTER BE
+char \u0432 2456		CYRILLIC SMALL LETTER VE
+char \u0433 1245		CYRILLIC SMALL LETTER GHE
+char \u0434 145		CYRILLIC SMALL LETTER DE
+char \u0435 15		CYRILLIC SMALL LETTER IE
+char \u0436 245		CYRILLIC SMALL LETTER ZHE
+char \u0437 1356		CYRILLIC SMALL LETTER ZE
+char \u0438 24		CYRILLIC SMALL LETTER I
+char \u0439 12346		CYRILLIC SMALL LETTER SHORT I
+char \u043a 13		CYRILLIC SMALL LETTER KA
+char \u043b 123		CYRILLIC SMALL LETTER EL
+char \u043c 134		CYRILLIC SMALL LETTER EM
+char \u043d 1345		CYRILLIC SMALL LETTER EN
+char \u043e 135		CYRILLIC SMALL LETTER O
+char \u043f 1234		CYRILLIC SMALL LETTER PE
+char \u0440 1235		CYRILLIC SMALL LETTER ER
+char \u0441 234		CYRILLIC SMALL LETTER ES
+char \u0442 2345		CYRILLIC SMALL LETTER TE
+char \u0443 136		CYRILLIC SMALL LETTER U
+char \u0444 124		CYRILLIC SMALL LETTER EF
+char \u0445 125		CYRILLIC SMALL LETTER HA
+char \u0446 14		CYRILLIC SMALL LETTER TSE
+char \u0447 12345		CYRILLIC SMALL LETTER CHE
+char \u0448 156		CYRILLIC SMALL LETTER SHA
+char \u0449 1346		CYRILLIC SMALL LETTER SHCHA
+char \u044a 12356		CYRILLIC SMALL LETTER HARD SIGN
+char \u044b 2346		CYRILLIC SMALL LETTER YERU
+char \u044c 23456		CYRILLIC SMALL LETTER SOFT SIGN
+char \u044d 246		CYRILLIC SMALL LETTER E
+char \u044e 1256		CYRILLIC SMALL LETTER YU
+char \u044f 1246		CYRILLIC SMALL LETTER YA
+char \u0451 16		CYRILLIC SMALL LETTER IO
+char \u0404 3457		CYRILLIC CAPITAL LETTER UKRAINIAN IE
+char \u0406 134567		CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+char \u0407 14567		CYRILLIC CAPITAL LETTER YI
+char \u0408 134567		CYRILLIC CAPITAL LETTER JE
+char \u0409 1267		CYRILLIC CAPITAL LETTER LJE
+char \u040a 12467		CYRILLIC CAPITAL LETTER NJE
+char \u040e 3467		CYRILLIC CAPITAL LETTER SHORT U
+char \u0452 1456		CYRILLIC SMALL LETTER DJE
+char \u0454 345		CYRILLIC SMALL LETTER UKRAINIAN IE
+char \u0456 13456		CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+char \u0457 1456		CYRILLIC SMALL LETTER YI
+char \u0458 13456		CYRILLIC SMALL LETTER JE
+char \u0459 126		CYRILLIC SMALL LETTER LJE
+char \u045a 1246		CYRILLIC SMALL LETTER NJE
+char \u045e 346		CYRILLIC SMALL LETTER SHORT U
+char \u0462 3457		CYRILLIC CAPITAL LETTER YAT
+char \u0463 345		CYRILLIC SMALL LETTER YAT
+char \u0466 17		CYRILLIC CAPITAL LETTER LITTLE YUS
+char \u0467 1		CYRILLIC SMALL LETTER LITTLE YUS
+char \u046a 124567		CYRILLIC CAPITAL LETTER BIG YUS
+char \u046b 12456		CYRILLIC SMALL LETTER BIG YUS
+char \u0472 12367		CYRILLIC CAPITAL LETTER FITA
+char \u0473 1236		CYRILLIC SMALL LETTER FITA
+char \u0474 14567		CYRILLIC CAPITAL LETTER IZHITSA
+char \u0475 1456		CYRILLIC SMALL LETTER IZHITSA
+char \u0479 3467		CYRILLIC CAPITAL LETTER UK
+char \u0479 346		CYRILLIC SMALL LETTER UK
+char \u0484 478		COMBINING CYRILLIC PALATALIZATION
+char \u0490 124567		CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+char \u0491 12456		CYRILLIC SMALL LETTER GHE WITH UPTURN
+char \u0492 124567		CYRILLIC CAPITAL LETTER GHE WITH STROKE
+char \u0493 12456		CYRILLIC SMALL LETTER GHE WITH STROKE
+char \u0498 3467		CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+char \u0499 346		CYRILLIC SMALL LETTER ZE WITH DESCENDER
+char \u049a 134567		CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+char \u049b 13456		CYRILLIC SMALL LETTER KA WITH DESCENDER
+char \u04a0 1467		CYRILLIC CAPITAL LETTER BASHKIR KA
+char \u04a1 146		CYRILLIC SMALL LETTER BASHKIR KA
+char \u04a2 14567		CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+char \u04a3 1456		CYRILLIC SMALL LETTER EN WITH DESCENDER
+char \u04aa 347		CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+char \u04ab 34		CYRILLIC SMALL LETTER ES WITH DESCENDER
+char \u04ae 134567		CYRILLIC CAPITAL LETTER STRAIGHT U
+char \u04af 13456		CYRILLIC SMALL LETTER STRAIGHT U
+char \u04b2 14567		CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+char \u04b3 1456		CYRILLIC SMALL LETTER HA WITH DESCENDER
+char \u04ba 12367		CYRILLIC CAPITAL LETTER SHHA
+char \u04bb 1236		CYRILLIC SMALL LETTER SHHA
+char \u04d8 3457		CYRILLIC CAPITAL LETTER SCHWA
+char \u04d9 345		CYRILLIC SMALL LETTER SCHWA
+char \u04e8 1267		CYRILLIC CAPITAL LETTER BARRED O
+char \u04e9 126		CYRILLIC SMALL LETTER BARRED O
+# Unicode Greek
+char \u0374 3456		GREEK NUMERAL SIGN
+char \u0375 5678		GREEK LOWER NUMERAL SIGN
+char \u037e 26		GREEK QUESTION MARK
+char \u0384 4		GREEK TONOS
+char \u0385 45		GREEK DIALYTIKA TONOS
+char \u0386 34578		GREEK CAPITAL LETTER ALPHA WITH TONOS
+char \u0387 467		GREEK ANO TELEIA
+char \u0388 124678		GREEK CAPITAL LETTER EPSILON WITH TONOS
+char \u0389 12345678		GREEK CAPITAL LETTER ETA WITH TONOS
+char \u038a 1245678		GREEK CAPITAL LETTER IOTA WITH TONOS
+char \u038c 24678		GREEK CAPITAL LETTER OMICRON WITH TONOS
+char \u038e 125678		GREEK CAPITAL LETTER UPSILON WITH TONOS
+char \u038f 24578		GREEK CAPITAL LETTER OMEGA WITH TONOS
+char \u0390 248		GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+char \u0391 178		GREEK CAPITAL LETTER ALPHA
+char \u0392 1278		GREEK CAPITAL LETTER BETA
+char \u0393 124578		GREEK CAPITAL LETTER GAMMA
+char \u0394 14578		GREEK CAPITAL LETTER DELTA
+char \u0395 1578		GREEK CAPITAL LETTER EPSILON
+char \u0396 135678		GREEK CAPITAL LETTER ZETA
+char \u0397 24578		GREEK CAPITAL LETTER ETA
+char \u0398 12578		GREEK CAPITAL LETTER THETA
+char \u0399 2478		GREEK CAPITAL LETTER IOTA
+char \u039a 1378		GREEK CAPITAL LETTER KAPPA
+char \u039b 12378		GREEK CAPITAL LETTER LAMDA
+char \u039c 13478		GREEK CAPITAL LETTER MU
+char \u039d 134578		GREEK CAPITAL LETTER NU
+char \u039e 134678		GREEK CAPITAL LETTER XI
+char \u039f 13578		GREEK CAPITAL LETTER OMICRON
+char \u03a0 123478		GREEK CAPITAL LETTER PI
+char \u03a1 123578		GREEK CAPITAL LETTER RHO
+char \u03a3 23478		GREEK CAPITAL LETTER SIGMA
+char \u03a4 234578		GREEK CAPITAL LETTER TAU
+char \u03a5 13678		GREEK CAPITAL LETTER UPSILON
+char \u03a6 12478		GREEK CAPITAL LETTER PHI
+char \u03a7 1478		GREEK CAPITAL LETTER CHI
+char \u03a8 1345678		GREEK CAPITAL LETTER PSI
+char \u03a9 245678		GREEK CAPITAL LETTER OMEGA
+char \u03aa 2478		GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+char \u03ab 1345678		GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+char \u03ac 3458		GREEK SMALL LETTER ALPHA WITH TONOS
+char \u03ad 12468		GREEK SMALL LETTER EPSILON WITH TONOS
+char \u03ae 1234568		GREEK SMALL LETTER ETA WITH TONOS
+char \u03af 124568		GREEK SMALL LETTER IOTA WITH TONOS
+char \u03b0 134568		GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+char \u03b1 18		GREEK SMALL LETTER ALPHA
+char \u03b2 128		GREEK SMALL LETTER BETA
+char \u03b3 12458		GREEK SMALL LETTER GAMMA
+char \u03b4 1458		GREEK SMALL LETTER DELTA
+char \u03b5 158		GREEK SMALL LETTER EPSILON
+char \u03b6 13568		GREEK SMALL LETTER ZETA
+char \u03b7 2458		GREEK SMALL LETTER ETA
+char \u03b8 1258		GREEK SMALL LETTER THETA
+char \u03b9 248		GREEK SMALL LETTER IOTA
+char \u03ba 138		GREEK SMALL LETTER KAPPA
+char \u03bb 1238		GREEK SMALL LETTER LAMDA
+char \u03bc 1348		GREEK SMALL LETTER MU
+char \u03bd 13458		GREEK SMALL LETTER NU
+char \u03be 13468		GREEK SMALL LETTER XI
+char \u03bf 1358		GREEK SMALL LETTER OMICRON
+char \u03c0 12348		GREEK SMALL LETTER PI
+char \u03c1 12358		GREEK SMALL LETTER RHO
+char \u03c2 2348		GREEK SMALL LETTER FINAL SIGMA
+char \u03c3 2348		GREEK SMALL LETTER SIGMA
+char \u03c4 23458		GREEK SMALL LETTER TAU
+char \u03c5 1368		GREEK SMALL LETTER UPSILON
+char \u03c6 1248		GREEK SMALL LETTER PHI
+char \u03c7 148		GREEK SMALL LETTER CHI
+char \u03c8 134568		GREEK SMALL LETTER PSI
+char \u03c9 24568		GREEK SMALL LETTER OMEGA
+char \u03ca 348		GREEK SMALL LETTER IOTA WITH DIALYTIKA
+char \u03cb 234568		GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+char \u03cc 2468		GREEK SMALL LETTER OMICRON WITH TONOS
+char \u03cd 12568		GREEK SMALL LETTER UPSILON WITH TONOS
+char \u03ce 2458		GREEK SMALL LETTER OMEGA WITH TONOS
+char \u03d0 1278		GREEK BETA SYMBOL
+char \u03d1 12578		GREEK THETA SYMBOL
+char \u03d2 12368		GREEK UPSILON WITH HOOK SYMBOL
+char \u03d3 234568		GREEK UPSILON WITH ACUTE AND HOOK SYMBOL
+char \u03d4 1368		GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL
+char \u03d5 12478		GREEK PHI SYMBOL
+char \u03d6 123478		GREEK PI SYMBOL
+char \u03d7 123468		GREEK KAI SYMBOL
+char \u03d8 1234578		GREEK LETTER ARCHAIC KOPPA
+char \u03d9 123458		GREEK SMALL LETTER ARCHAIC KOPPA
+char \u03da 123678		GREEK LETTER STIGMA
+char \u03db 12368		GREEK SMALL LETTER STIGMA
+char \u03dc 145678		GREEK LETTER DIGAMMA
+char \u03dd 12368		GREEK SMALL LETTER DIGAMMA
+char \u03de 1234578		GREEK LETTER KOPPA
+char \u03df 123458		GREEK SMALL LETTER KOPPA
+char \u03e0 234678		GREEK LETTER SAMPI
+char \u03e1 23468		GREEK SMALL LETTER SAMPI
+char \u03f0 1378		GREEK KAPPA SYMBOL
+char \u03f1 123578		GREEK RHO SYMBOL
+char \u03f2 23478		GREEK LUNATE SIGMA SYMBOL
+char \u03f4 12578		GREEK CAPITAL THETA SYMBOL
+char \u03f5 1578		GREEK LUNATE EPSILON SYMBOL
+char \u03fa 234678		GREEK CAPITAL LETTER SAN
+char \u03fb 23468		GREEK SMALL LETTER SAN
+# Hebrew Unicode
+char \u05d0 18		HEBREW LETTER ALEF
+char \u05d1 12368		HEBREW LETTER BET
+char \u05d2 12458		HEBREW LETTER GIMEL
+char \u05d3 1458		HEBREW LETTER DALET
+char \u05d4 1258		HEBREW LETTER HE
+char \u05d5 24568		HEBREW LETTER VAV
+char \u05d6 13568		HEBREW LETTER ZAYIN
+char \u05d7 13468		HEBREW LETTER HET
+char \u05d8 23458		HEBREW LETTER TET
+char \u05d9 2458		HEBREW LETTER YOD
+char \u05da 138		HEBREW LETTER FINAL KAF
+char \u05db 168		HEBREW LETTER KAF
+char \u05dc 1238		HEBREW LETTER LAMED
+char \u05dd 1348		HEBREW LETTER FINAL MEM
+char \u05de 1348		HEBREW LETTER MEM
+char \u05df 13458		HEBREW LETTER FINAL NUN
+char \u05e0 13458		HEBREW LETTER NUN
+char \u05e1 2348		HEBREW LETTER SAMEKH
+char \u05e2 12468		HEBREW LETTER AYIN
+char \u05e3 1248		HEBREW LETTER FINAL PE
+char \u05e4 12348		HEBREW LETTER PE
+char \u05e5 23468		HEBREW LETTER FINAL TSADI
+char \u05e6 23468		HEBREW LETTER TSADI
+char \u05e7 123458		HEBREW LETTER QOF
+char \u05e8 12358		HEBREW LETTER RESH
+char \u05e9 1468		HEBREW LETTER SHIN
+char \u05ea 14568		HEBREW LETTER TAV
+char \u05b0 3		HEBREW POINT SHEVA
+char \u05b1 26		HEBREW POINT HATAF SEGOL
+char \u05b2 25		HEBREW POINT HATAF PATAH
+char \u05b3 345		HEBREW POINT HATAF QAMATS
+char \u05b4 24		HEBREW POINT HIRIQ
+char \u05b5 34		HEBREW POINT TSERE
+char \u05b6 15		HEBREW POINT SEGOL
+char \u05b7 14		HEBREW POINT PATAH
+char \u05b8 126		HEBREW POINT QAMATS
+char \u05b9 135		HEBREW POINT HOLAM
+char \u05bb 136		HEBREW POINT QUBUTS
+char \u05bc 5		HEBREW POINT DAGESH OR MAPIQ
+char \u05bd 4		HEBREW POINT METEG
+char \u05c1 1568		HEBREW POINT SHIN DOT
+char \u05c2 2348		HEBREW POINT SIN DOT
+# Other symbols
+char \x00 12345678		NULL
+char \x01 1678		START OF HEADING
+char \x02 124678		START OF TEXT
+char \x03 14678		END OF TEXT
+char \x04 1467		END OF TRANSMISSION
+char \x05 15678		ENQUIRY
+char \x06 2358		ACKNOWLEDGE
+char \x07 235678		BELL
+char \x08 378		BACKSPACE
+char \x0e 134567		SHIFT OUT
+char \x0f 1235678		SHIFT IN
+char \x10 34568		DATA LINK ESCAPE
+char \x11 124568		DEVICE CONTROL ONE
+char \x12 23468		DEVICE CONTROL TWO
+char \x13 4578		DEVICE CONTROL THREE
+char \x14 3458		DEVICE CONTROL FOUR
+char \x15 23578		NEGATIVE ACKNOWLEDGE
+char \x16 1236		SYNCHRONOUS IDLE
+char \x17 25678		END OF TRANSMISSION BLOCK
+char \x18 3478		CANCEL
+char \x19 13456		END OF MEDIUM
+char \x1a 4678		SUBSTITUTE
+char \x1b 24678		ESCAPE
+char \x1c 125678		INFORMATION SEPARATOR FOUR
+char \x1d 1245678		INFORMATION SEPARATOR THREE
+char \x1e 1234678		INFORMATION SEPARATOR TWO
+char \x1f 45678		INFORMATION SEPARATOR ONE
+char \x7f 4568		DELETE
+char \x81 8		<CONTROL>
+char \x86 1248		START OF SELECTED AREA
+char \x89 248		CHARACTER TABULATION WITH JUSTIFICATION
+char \x8d 257		REVERSE LINE FEED
+char \x8f 124678		SINGLE SHIFT THREE
+char \x90 124678		DEVICE CONTROL STRING
+char \x97 13568		END OF GUARDED AREA
+char \x9b 123458		CONTROL SEQUENCE INTRODUCER
+char \x9d 124567		OPERATING SYSTEM COMMAND
+char \x9e 235678		PRIVACY MESSAGE
+char \xa1 367		INVERTED EXCLAMATION MARK
+char \xa2 58		CENT SIGN
+char \xa3 467		POUND SIGN
+char \xa4 4678		CURRENCY SIGN
+char \xa5 468		YEN SIGN
+char \xa6 158		BROKEN BAR
+char \xa7 357		SECTION SIGN
+char \xa8 48		DIAERESIS
+char \xaa 1258		FEMININE ORDINAL INDICATOR
+char \xab 5678		LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+char \xac 25678		NOT SIGN
+char \xad 368		SOFT HYPHEN
+char \xae 12358		REGISTERED SIGN
+char \xaf 458		MACRON
+char \xb1 23578		PLUS MINUS SYMBOL
+char \xb4 568		ACUTE ACCENT
+char \xb5 1348		MICRO SIGN
+char \xb6 1458		PILCROW SIGN
+char \xb8 68		CEDILLA
+char \xba 2458		MASCULINE ORDINAL INDICATOR
+char \xbb 4578		RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+char \xbc 1368		VULGAR FRACTION ONE QUARTER
+char \xbd 12368		VULGAR FRACTION ONE HALF
+char \xbe 13468		VULGAR FRACTION THREE QUARTERS
+char \xbf 38		INVERTED QUESTION MARK
+char \xc0 1235678		LATIN CAPITAL LETTER A WITH GRAVE
+char \xc1 1678		LATIN CAPITAL LETTER A WITH ACUTE
+char \xc2 1678		LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+char \xc3 34678		LATIN CAPITAL LETTER A WITH TILDE
+char \xc4 34578		LATIN CAPITAL LETTER A WITH DIAERESIS
+char \xc5 345678		LATIN CAPITAL LETTER A WITH RING ABOVE
+char \xc6 478		LATIN CAPITAL LETTER AE
+char \xc7 1234678		LATIN CAPITAL LETTER C WITH CEDILLA
+char \xc8 234678		LATIN CAPITAL LETTER E WITH GRAVE
+char \xc9 12345678		LATIN CAPITAL LETTER E WITH ACUTE
+char \xca 12678		LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+char \xcb 124678		LATIN CAPITAL LETTER E WITH DIAERESIS
+char \xcc 3478		LATIN CAPITAL LETTER I WITH GRAVE
+char \xcd 14678		LATIN CAPITAL LETTER I WITH ACUTE
+char \xce 14678		LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+char \xcf 1245678		LATIN CAPITAL LETTER I WITH DIAERESIS
+char \xd0 35678		LATIN CAPITAL LETTER ETH
+char \xd1 25678		LATIN CAPITAL LETTER N WITH TILDE
+char \xd2 34678		LATIN CAPITAL LETTER O WITH GRAVE
+char \xd3 145678		LATIN CAPITAL LETTER O WITH ACUTE
+char \xd4 145678		LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+char \xd5 2678		LATIN CAPITAL LETTER O WITH TILDE
+char \xd6 24678		LATIN CAPITAL LETTER O WITH DIAERESIS
+char \xd7 2348		MULTIPLICATION SIGN
+char \xd8 24678		LATIN CAPITAL LETTER O WITH STROKE
+char \xd9 2345678		LATIN CAPITAL LETTER U WITH GRAVE
+char \xda 15678		LATIN CAPITAL LETTER U WITH ACUTE
+char \xdb 15678		LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+char \xdc 125678		LATIN CAPITAL LETTER U WITH DIAERESIS
+char \xdd 245678		LATIN CAPITAL LETTER Y WITH ACUTE
+char \xde 23578		LATIN CAPITAL LETTER THORN
+char \xdf 34568		LATIN SMALL LETTER SHARP S
+char \xe0 123568		LATIN SMALL LETTER A WITH GRAVE
+char \xe1 168		LATIN SMALL LETTER A WITH ACUTE
+char \xe2 168		LATIN SMALL LETTER A WITH CIRCUMFLEX
+char \xe3 3468		LATIN SMALL LETTER A WITH TILDE
+char \xe4 3458		LATIN SMALL LETTER A WITH DIAERESIS
+char \xe5 34568		LATIN SMALL LETTER A WITH RING ABOVE
+char \xe6 48		LATIN SMALL LETTER AE
+char \xe7 123468		LATIN SMALL LETTER C WITH CEDILLA
+char \xe8 23468		LATIN SMALL LETTER E WITH GRAVE
+char \xe9 1234568		LATIN SMALL LETTER E WITH ACUTE
+char \xea 1268		LATIN SMALL LETTER E WITH CIRCUMFLEX
+char \xeb 12468		LATIN SMALL LETTER E WITH DIAERESIS
+char \xec 348		LATIN SMALL LETTER I WITH GRAVE
+char \xed 1468		LATIN SMALL LETTER I WITH ACUTE
+char \xee 1468		LATIN SMALL LETTER I WITH CIRCUMFLEX
+char \xef 124568		LATIN SMALL LETTER I WITH DIAERESIS
+char \xf0 3568		LATIN SMALL LETTER ETH
+char \xf1 2568		LATIN SMALL LETTER N WITH TILDE
+char \xf2 3468		LATIN SMALL LETTER O WITH GRAVE
+char \xf3 14568		LATIN SMALL LETTER O WITH ACUTE
+char \xf4 14568		LATIN SMALL LETTER O WITH CIRCUMFLEX
+char \xf5 268		LATIN SMALL LETTER O WITH TILDE
+char \xf6 2468		LATIN SMALL LETTER O WITH DIAERESIS
+char \xf7 125678		DIVISION SIGN
+char \xf8 2468		LATIN SMALL LETTER O WITH STROKE
+char \xf9 234568		LATIN SMALL LETTER U WITH GRAVE
+char \xfa 1568		LATIN SMALL LETTER U WITH ACUTE
+char \xfb 1568		LATIN SMALL LETTER U WITH CIRCUMFLEX
+char \xfc 12568		U WITH TWO DOTS
+char \xfd 24568		LATIN SMALL LETTER Y WITH ACUTE
+char \xfe 2358		LATIN SMALL LETTER THORN
+char \xff 134568		LATIN SMALL LETTER Y WITH DIAERESIS
+char \u0100 1678		LATIN CAPITAL LETTER A WITH MACRON
+char \u0101 168		LATIN SMALL LETTER A WITH MACRON
+char \u0102 1235678		LATIN CAPITAL LETTER A WITH BREVE
+char \u0103 123568		LATIN SMALL LETTER A WITH BREVE
+char \u0104 1345678		LATIN CAPITAL LETTER A WITH OGONEK
+char \u0105 134568		LATIN SMALL LETTER A WITH OGONEK
+char \u0106 14678		LATIN CAPITAL LETTER C WITH ACUTE
+char \u0107 1468		LATIN SMALL LETTER C WITH ACUTE
+char \u0108 14678		LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+char \u0109 1468		LATIN SMALL LETTER C WITH CIRCUMFLEX
+char \u010c 14678		LATIN CAPITAL LETTER C WITH CARON
+char \u010d 1468		LATIN SMALL LETTER C WITH CARON
+char \u0110 145678		LATIN CAPITAL LETTER D WITH STROKE
+char \u0111 14568		LATIN SMALL LETTER D WITH STROKE
+char \u0112 15678		LATIN CAPITAL LETTER E WITH MACRON
+char \u0113 1568		LATIN SMALL LETTER E WITH MACRON
+char \u0118 12678		LATIN CAPITAL LETTER E WITH OGONEK
+char \u0119 1268		LATIN CAPITAL LETTER E WITH OGONEK
+char \u011a 12678		LATIN CAPITAL LETTER E WITH CARON
+char \u011b 1268		LATIN SMALL LETTER E WITH CARON
+char \u011c 1245678		LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+char \u011d 124568		LATIN SMALL LETTER G WITH CIRCUMFLEX
+char \u011e 1245678		LATIN CAPITAL LETTER G WITH BREVE
+char \u011f 124568		LATIN CAPITAL LETTER G WITH BREVE
+char \u0122 1245678		LATIN CAPITAL LETTER G WITH CEDILLA
+char \u0123 124568		LATIN SMALL LETTER G WITH CEDILLA
+char \u0124 125678		LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+char \u0125 12568		LATIN SMALL LETTER H WITH CIRCUMFLEX
+char \u012a 24678		LATIN CAPITAL LETTER I WITH MACRON
+char \u012b 2468		LATIN SMALL LETTER I WITH MACRON
+char \u0130 3478		LATIN CAPITAL LETTER I WITH DOT ABOVE
+char \u0131 348		LATIN SMALL LETTER I WITH DOT ABOVE
+char \u0134 245678		LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+char \u0135 24568		LATIN SMALL LETTER J WITH CIRCUMFLEX
+char \u0136 13678		LATIN CAPITAL LETTER K WITH CEDILLA
+char \u0137 1368		LATIN SMALL LETTER K WITH CEDILLA
+char \u013b 123678		LATIN CAPITAL LETTER L WITH CEDILLA
+char \u013c 12368		LATIN SMALL LETTER L WITH CEDILLA
+char \u0141 1345678		LATIN CAPITAL LETTER L WITH STROKE
+char \u0142 134568		LATIN SMALL LETTER L WITH STROKE
+char \u0143 145678		LATIN CAPITAL LETTER N WITH ACUTE
+char \u0144 14568		LATIN SMALL LETTER N WITH ACUTE
+char \u0145 1345678		LATIN CAPITAL LETTER N WITH CEDILLA
+char \u0146 134568		LATIN SMALL LETTER N WITH CEDILLA
+char \u0150 1245678		LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+char \u0151 124568		LATIN SMALL LETTER O WITH DOUBLE ACUTE
+char \u0152 24678		LATIN CAPITAL LIGATURE OE
+char \u0153 2468		LATIN SMALL LIGATURE OE
+char \u0156 1235678		LATIN CAPITAL LETTER R WITH CEDILLA
+char \u0157 123568		LATIN SMALL LETTER R WITH CEDILLA
+char \u0158 245678		LATIN CAPITAL LETTER R WITH CARON
+char \u0159 24568		LATIN SMALL LETTER R WITH CARON
+char \u015a 1345678		LATIN CAPITAL LETTER S WITH ACUTE
+char \u015b 134568		LATIN SMALL LETTER S WITH ACUTE
+char \u015c 234678		LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+char \u015d 23468		LATIN SMALL LETTER S WITH CIRCUMFLEX
+char \u015e 1345678		LATIN CAPITAL LETTER S WITH CEDILLA
+char \u015f 134568		LATIN SMALL LETTER S WITH CEDILLA
+char \u0160 678		LATIN CAPITAL LETTER S WITH CARON
+char \u0161 68		LATIN SMALL LETTER S WITH CARON
+char \u0162 234578		LATIN CAPITAL LETTER T WITH CEDILLA
+char \u0163 23458		LATIN SMALL LETTER T WITH CEDILLA
+char \u0164 125678		LATIN CAPITAL LETTER T WITH CARON
+char \u0165 12568		LATIN SMALL LETTER T WITH CARON
+char \u016a 34678		LATIN CAPITAL LETTER U WITH MACRON
+char \u016b 3468		LATIN SMALL LETTER U WITH MACRON
+char \u016c 34678		LATIN CAPITAL LETTER U WITH BREVE
+char \u016d 3468		LATIN SMALL LETTER U WITH BREVE
+char \u016e 2345678		LATIN CAPITAL LETTER U WITH RING ABOVE
+char \u016f 234568		LATIN SMALL LETTER U WITH RING ABOVE
+char \u0170 2345678		LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+char \u0171 234568		LATIN SMALL LETTER U WITH DOUBLE ACUTE
+char \u0178 1345678		LATIN CAPITAL LETTER Y WITH DIAERESIS
+char \u0179 134568		LATIN SMALL LETTER Y WITH DIAERESIS
+char \u0179 1345678		LATIN CAPITAL LETTER Z WITH ACUTE
+char \u017a 134568		LATIN SMALL LETTER Z WITH ACUTE
+char \u017b 1234678		LATIN CAPITAL LETTER Z WITH DOT ABOVE
+char \u017c 123468		LATIN SMALL LETTER Z WITH DOT ABOVE
+char \u017d 125678		LATIN CAPITAL LETTER Z WITH CARON
+char \u017e 12568		LATIN SMALL LETTER Z WITH CARON
+char \u0191 78		LATIN CAPITAL LETTER F WITH HOOK
+char \u0192 8		LATIN SMALL LETTER F WITH HOOK
+char \u01a0 123678		LATIN CAPITAL LETTER O WITH HORN
+char \u01a1 12368		LATIN SMALL LETTER O WITH HORN
+char \ua7ab 135678		LATIN CAPITAL LETTER REVERSED OPEN E
+char \u025c 13568		LATIN SMALL LETTER REVERSED OPEN E
+char \u02c6 378		MODIFIER LETTER CIRCUMFLEX ACCENT
+char \u02c7 134568		CARON
+char \u02d8 134568		BREVE
+char \u02d9 5		DOT ABOVE
+char \u02db 134568		OGONEK
+char \u02dc 2578		SMALL TILDE
+char \u2003 0		EM SPACE
+char \u2011 36		NON-BREAKING HYPHEN
+char \u2013 368		EN DASH
+char \u2014 36		EM DASH
+char \u2015 36		HORIZONTAL BAR
+char \u2017 36		DOUBLE LOW LINE
+char \u2018 2367		LEFT SINGLE QUOTATION MARK
+char \u2019 47		RIGHT SINGLE QUOTATION MARK
+char \u2029 1458		PARAGRAPH SEPARATOR
+char \u201a 3678		SINGLE LOW-9 QUOTATION MARK
+char \u201c 138		LEFT DOUBLE QUOTATION MARK
+char \u201d 1238		RIGHT DOUBLE QUOTATION MARK
+char \u201e 1268		DOUBLE LOW-9 QUOTATION MARK
+char \u2020 1248		DAGGER
+char \u2021 12458		DOUBLE DAGGER
+char \u2022 35		BULLET
+char \u2026 238		HORIZONTAL ELLIPSIS
+char \u2030 248		PER MILLE SIGN
+char \u2039 27		SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+char \u203a 123458		SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+char \u203c 58		DOUBLE EXCLAMATION MARK
+char \u207f 13467		SUPERSCRIPT LATIN SMALL LETTER N
+char \u20ac 457		EURO SIGN
+char \u20af 145678		DRACHMA SIGN
+char \u20bd 4578		RUBLE SYMBOL
+char \u2116 3456		NUMERO SIGN
+char \u2219 48		BULLET OPERATOR
+char \u2122 1245678		TRADE MARK SIGN
+char \u221a 1467		SQUARE ROOT
+char \u221e 234678		INFINITY
+char \u2229 578		INTERSECTION
+char \u2248 3578		ALMOST EQUAL TO
+char \u2261 23568		IDENTICAL
+char \u2264 568		LESS-THAN OR EQUAL TO
+char \u2265 458		GREATER-THAN OR EQUAL TO
+char \u2310 14567		REVERSED NOT SIGN
+char \u2320 347		TOP HALF INTEGRAL
+char \u2321 15678		BOTTOM HALF INTEGRAL
+char \u2500 67		BOX DRAWINGS LIGHT HORIZONTAL
+char \u2502 237		BOX DRAWINGS LIGHT VERTICAL
+char \u250C 257		BOX DRAWINGS LIGHT DOWN AND RIGHT
+char \u2510 278		BOX DRAWINGS LIGHT DOWN AND LEFT
+char \u2514 378		BOX DRAWINGS LIGHT UP AND RIGHT
+char \u2518 678		BOX DRAWINGS LIGHT UP AND LEFT
+char \u251c 2367		BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+char \u2524 13568		BOX DRAWINGS LIGHT VERTICAL AND LEFT
+char \u252c 138		BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+char \u2534 27		BOX DRAWINGS LIGHT UP AND HORIZONTAL
+char \u2550 2578		BOX DRAWINGS DOUBLE HORIZONTAL
+char \u2551 1234568		BOX DRAWINGS DOUBLE VERTICAL
+char \u2552 123468		BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+char \u2553 1358		BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+char \u2554 23567		BOX DRAWINGS DOUBLE DOWN AND RIGHT
+char \u2555 148		BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+char \u2556 368		BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+char \u2557 12458		BOX DRAWINGS DOUBLE DOWN AND LEFT
+char \u2558 248		BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+char \u2559 1238		BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+char \u255a 2378		BOX DRAWINGS DOUBLE UP AND RIGHT
+char \u255b 34678		BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+char \u255c 4678		BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+char \u255d 2678		BOX DRAWINGS DOUBLE UP AND LEFT
+char \u255e 68		BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+char \u255f 257		BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+char \u2560 1248		BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+char \u2561 268		BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+char \u2562 24568		BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+char \u2563 123567		BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+char \u2564 3467		BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+char \u2565 28		BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+char \u2566 123458		BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+char \u2567 167		BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+char \u2568 57		BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+char \u2569 8		BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+char \u256a 3567		BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+char \u256b 12358		BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+char \u256c 5678		BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+char \u2580 234567		UPPER HALF BLOCK
+char \u2584 1267		LOWER HALF BLOCK
+char \u2588 12345678		FULL BLOCK
+char \u258c 23678		LEFT HALF BLOCK
+char \u2590 35678		RIGHT HALF BLOCK
+char \u2591 78		LIGHT SHADE
+char \u2592 3678		MEDIUM SHADE
+char \u2593 235678		DARK SHADE
+char \u25a0 1234567		BLACK SQUARE
+char \u25cf 35		BLACK CIRCLE
+
+include common.tti
diff --git a/Tables/Text/sa.ttb b/Tables/Text/sa.ttb
new file mode 100644
index 0000000..3e5a271
--- /dev/null
+++ b/Tables/Text/sa.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Sanskrit
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/sat.ttb b/Tables/Text/sat.ttb
new file mode 100644
index 0000000..94007d0
--- /dev/null
+++ b/Tables/Text/sat.ttb
@@ -0,0 +1,26 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Santali
+
+include devanagari.tti
+include bengali.tti
+include oriya.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/sd.ttb b/Tables/Text/sd.ttb
new file mode 100644
index 0000000..aa21110
--- /dev/null
+++ b/Tables/Text/sd.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Sindhi
+
+include devanagari.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/se.ttb b/Tables/Text/se.ttb
new file mode 100644
index 0000000..f56c5f4
--- /dev/null
+++ b/Tables/Text/se.ttb
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Sami (Northern)
+# Tomas Mårdsjö <tomas.mardsjo@icap.nu>
+# see: https://en.wikipedia.org/wiki/Northern_Sami_Braille
+
+char	\xC1	(123 567 )	# ⡷ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char	\xE1	(123 56  )	# ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+
+char	\u010C	(1  4 67 )	# ⡩ Č [LATIN CAPITAL LETTER C WITH CARON]
+char	\u010D	(1  4 6  )	# ⠩ č [LATIN SMALL LETTER C WITH CARON]
+
+char	\u0110	(1  4567 )	# ⡹ Đ [LATIN CAPITAL LETTER D WITH STROKE]
+char	\u0111	(1  456  )	# ⠹ đ [LATIN SMALL LETTER D WITH STROKE]
+
+char	\u014A	(12 4 67 )	# ⡫ Ŋ [LATIN CAPITAL LETTER ENG]
+char	\u014B	(12 4 6  )	# ⠫ ŋ [LATIN SMALL LETTER ENG]
+
+char	\u0160	(1   567 )	# ⡱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char	\u0161	(1   56  )	# ⠱ š [LATIN SMALL LETTER S WITH CARON]
+
+char	\u0166	(12  567 )	# ⡳ Ŧ [LATIN CAPITAL LETTER T WITH STROKE]
+char	\u0167	(12  56  )	# ⠳ ŧ [LATIN SMALL LETTER T WITH STROKE]
+
+char	\u017D	( 234 67 )	# ⡮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char	\u017E	( 234 6  )	# ⠮ ž [LATIN SMALL LETTER Z WITH CARON]
+
+include no-oup.ttb
diff --git a/Tables/Text/sk.ttb b/Tables/Text/sk.ttb
new file mode 100644
index 0000000..7ece464
--- /dev/null
+++ b/Tables/Text/sk.ttb
@@ -0,0 +1,108 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Slovak
+
+# Created and maintained by Mike Sivill #<mike.sivill@viewplus.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# Modified by Dave Mielke <dave@mielke.cc>
+# with advice from Jan Buchal <buchal@brailcom.org>
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# Slovak letters with accents
+char \xE1	(1    6  )  # ⠡ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xC1	(1    67 )  # ⡡ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xE4	(   4    )  # ⠈ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xC4	(   4  7 )  # ⡈ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \u010D	(1  4 6  )  # ⠩ č [LATIN SMALL LETTER C WITH CARON]
+char \u010C	(1  4 67 )  # ⡩ Č [LATIN CAPITAL LETTER C WITH CARON]
+char \u010F	(1  456  )  # ⠹ ď [LATIN SMALL LETTER D WITH CARON]
+char \u010E	(1  4567 )  # ⡹ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char \xE9	(  345   )  # ⠜ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xC9	(  345 7 )  # ⡜ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \u011B	(12   6  )  # ⠣ ě [LATIN SMALL LETTER E WITH CARON]
+char \u011A	(12   67 )  # ⡣ Ě [LATIN CAPITAL LETTER E WITH CARON]
+char \xED	(  34    )  # ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xCD	(  34  7 )  # ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \u013A	(   4 6  )  # ⠨ ĺ [LATIN SMALL LETTER L WITH ACUTE]
+char \u0139	(   4 67 )  # ⡨ Ĺ [LATIN CAPITAL LETTER L WITH ACUTE]
+char \u013E	(   456  )  # ⠸ ľ [LATIN SMALL LETTER L WITH CARON]
+char \u013D	(   4567 )  # ⡸ Ľ [LATIN CAPITAL LETTER L WITH CARON]
+char \u0148	(12 4 6  )  # ⠫ ň [LATIN SMALL LETTER N WITH CARON]
+char \u0147	(12 4 67 )  # ⡫ Ň [LATIN CAPITAL LETTER N WITH CARON]
+char \xF3	( 2 4 6  )  # ⠪ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xD3	( 2 4 67 )  # ⡪ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xF4	( 23456  )  # ⠾ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xD4	( 234567 )  # ⡾ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \u0155	(123 56  )  # ⠷ ŕ [LATIN SMALL LETTER R WITH ACUTE]
+char \u0154	(123 567 )  # ⡷ Ŕ [LATIN CAPITAL LETTER R WITH ACUTE]
+char \u0159	(123456  )  # ⠿ ř [LATIN SMALL LETTER R WITH CARON]
+char \u0158	(1234567 )  # ⡿ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char \u0161	(1   56  )  # ⠱ š [LATIN SMALL LETTER S WITH CARON]
+char \u0160	(1   567 )  # ⡱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char \u0165	(12  56  )  # ⠳ ť [LATIN SMALL LETTER T WITH CARON]
+char \u0164	(12  567 )  # ⡳ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char \xFA	(  34 6  )  # ⠬ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xDA	(  34 67 )  # ⡬ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \u016F	(  3456  )  # ⠼ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char \u016E	(  34567 )  # ⡼ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char \xFD	(1234 6  )  # ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xDD	(1234 67 )  # ⡯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \u017E	( 234 6  )  # ⠮ ž [LATIN SMALL LETTER Z WITH CARON]
+char \u017D	( 234 67 )  # ⡮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+
+char \x21	( 23 5   )  # ⠖ ! [EXCLAMATION MARK]
+char \x22	( 23 56  )  # ⠶ " [QUOTATION MARK]
+char \x23	(  345678)  # ⣼ # [NUMBER SIGN]
+char \x24	( 23 5  8)  # ⢖ $ [DOLLAR SIGN]
+char \x25	(1234   8)  # ⢏ % [PERCENT SIGN]
+char \x26	( 23 5 78)  # ⣖ & [AMPERSAND]
+char \x27	(   4   8)  # ⢈ ' [APOSTROPHE]
+char \x28	( 23  6  )  # ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # ⠔ * [ASTERISK]
+char \x2B	( 2  56  )  # ⠲ + [PLUS SIGN]
+char \x2C	( 2      )  # ⠂ , [COMMA]
+char \x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # ⠄ . [FULL STOP]
+char \x2F	(12 456  )  # ⠻ / [SOLIDUS]
+char \x3A	( 2  5   )  # ⠒ : [COLON]
+char \x3B	( 23     )  # ⠆ ; [SEMICOLON]
+char \x3C	( 23    8)  # ⢆ < [LESS-THAN SIGN]
+char \x3D	( 23 5678)  # ⣶ = [EQUALS SIGN]
+char \x3E	(    567 )  # ⡰ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+char \x40	(12 456 8)  # ⢻ @ [COMMERCIAL AT]
+char \x5B	( 23  67 )  # ⡦ [ [LEFT SQUARE BRACKET]
+char \x5C	( 23  6 8)  # ⢦ \ [REVERSE SOLIDUS]
+char \x5D	(  3 567 )  # ⡴ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(  3  67 )  # ⡤ _ [LOW LINE]
+char \x60	(   4  78)  # ⣈ ` [GRAVE ACCENT]
+char \x7B	( 23  678)  # ⣦ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # ⢸ | [VERTICAL LINE]
+char \x7D	(  3 5678)  # ⣴ } [RIGHT CURLY BRACKET]
+char \x7E	(    5 7 )  # ⡐ ~ [TILDE]
+
+include common.tti
diff --git a/Tables/Text/sl.ttb b/Tables/Text/sl.ttb
new file mode 100644
index 0000000..55bb51a
--- /dev/null
+++ b/Tables/Text/sl.ttb
@@ -0,0 +1,179 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Slovenian
+#
+# Copyright (C) 2005 by Sebastien Sable, All rights reserved.
+
+# Table generated by Sébastien Sablé <sable@users.sourceforge.net> for
+# libbraille http://libbraille.org and gnome-braille
+# http://cvs.gnome.org/viewcvs/gnome-braille/
+#
+# Table adapted for BRLTTY by Samuel Thibault <samuel.thibault@ens-lyon.org>
+#
+# *Many thanks to Danko Butorac <danko at ipsis.hr> for his help*
+# Table based on "Croatian code page for computer 8-dot Braille
+# alphabet ANSI-1250" from the "Croatian Association of the Blind" at
+# http://www.savez-slijepih.hr/en/download.htm
+# Characters were changed by Jožef Gregorc
+# Data about characters has been provided by Mateja Jenčič
+
+# generated by brltty-ttb:
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+char	\x22	( 23 56  )  # ⠶ " [QUOTATION MARK]
+char	\x24	(   4 6  )  # ⠨ $ [DOLLAR SIGN]
+char	\x25	(123456  )  # ⠿ % [PERCENT SIGN]
+char	\x26	(1234 6  )  # ⠯ & [AMPERSAND]
+char	\x27	(  3     )  # ⠄ ' [APOSTROPHE]
+char	\x28	( 23  6  )  # ⠦ ( [LEFT PARENTHESIS]
+char	\x29	(  3 56  )  # ⠴ ) [RIGHT PARENTHESIS]
+char	\x2A	(  3 5   )  # ⠔ * [ASTERISK]
+char	\x2B	( 23 5  8)  # ⢖ + [PLUS SIGN]
+char	\x2C	( 2      )  # ⠂ , [COMMA]
+char	\x2D	(  3  6  )  # ⠤ - [HYPHEN-MINUS]
+char	\x2E	( 2  56  )  # ⠲ . [FULL STOP]
+char	\x2F	( 2  56 8)  # ⢲ / [SOLIDUS]
+char	\x3A	( 2  5   )  # ⠒ : [COLON]
+char	\x3B	( 23     )  # ⠆ ; [SEMICOLON]
+char	\x3C	( 2 4 6 8)  # ⢪ < [LESS-THAN SIGN]
+char	\x3D	( 23 56 8)  # ⢶ = [EQUALS SIGN]
+char	\x3E	(1 3 5  8)  # ⢕ > [GREATER-THAN SIGN]
+char	\x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+char	\x40	(1  456  )  # ⠹ @ [COMMERCIAL AT]
+char	\x5B	(1234 6 8)  # ⢯ [ [LEFT SQUARE BRACKET]
+char	\x5C	(1  456 8)  # ⢹ \ [REVERSE SOLIDUS]
+char	\x5D	(1 3456 8)  # ⢽ ] [RIGHT SQUARE BRACKET]
+char	\x5E	(    567 )  # ⡰ ^ [CIRCUMFLEX ACCENT]
+char	\x5F	(   456 8)  # ⢸ _ [LOW LINE]
+char	\x60	(   4    )  # ⠈ ` [GRAVE ACCENT]
+char	\x7B	( 23    8)  # ⢆ { [LEFT CURLY BRACKET]
+char	\x7C	(   45   )  # ⠘ | [VERTICAL LINE]
+glyph	\x7D	(    567 )  # ⡰ } [RIGHT CURLY BRACKET]
+char	\x7E	(    56  )  # ⠰ ~ [TILDE]
+char	\xA4	(   4 678)  # ⣨ ¤ [CURRENCY SIGN]
+char	\xA6	(   4 6 8)  # ⢨ ¦ [BROKEN BAR]
+char	\xA7	(  3 5 78)  # ⣔ § [SECTION SIGN]
+char	\xA8	(       8)  # ⢀ ¨ [DIAERESIS]
+glyph	\xA9	(12 45  8)  # ⢛ © [COPYRIGHT SIGN]
+char	\xAB	(  3   78)  # ⣄ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char	\xAC	( 23 5 7 )  # ⡖ ¬ [NOT SIGN]
+char	\xAD	(      78)  # ⣀ ­ [SOFT HYPHEN]
+char	\xAE	(12 45678)  # ⣻ ® [REGISTERED SIGN]
+char	\xB0	(    5 78)  # ⣐ ° [DEGREE SIGN]
+char	\xB1	(1234 678)  # ⣯ ± [PLUS-MINUS SIGN]
+char	\xB4	(   4   8)  # ⢈ ´ [ACUTE ACCENT]
+char	\xB5	(  34567 )  # ⡼ µ [MICRO SIGN]
+char	\xB6	(1234   8)  # ⢏ ¶ [PILCROW SIGN]
+char	\xB7	(  3    8)  # ⢄ · [MIDDLE DOT]
+char	\xB8	(    56 8)  # ⢰ ¸ [CEDILLA]
+char	\xBB	(     678)  # ⣠ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char	\xC1	(1 3    8)  # ⢅ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char	\xC2	(123 56 8)  # ⢷ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char	\xC4	(  345 7 )  # ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char	\xC7	(1234 67 )  # ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char	\xC9	(1234567 )  # ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char	\xCB	(12 4 67 )  # ⡫ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char	\xCD	(  34  7 )  # ⡌ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char	\xCE	(12 4567 )  # ⡻ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+glyph	\xD3	(1 3 5  8)  # ⢕ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char	\xD4	(1  45678)  # ⣹ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char	\xD6	( 2 4 67 )  # ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char	\xD7	(1 34 6 8)  # ⢭ × [MULTIPLICATION SIGN]
+char	\xDA	(1 3  6 8)  # ⢥ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char	\xDC	(12  567 )  # ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+glyph	\xDD	(1 3456 8)  # ⢽ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char	\xDF	( 234 678)  # ⣮ ß [LATIN SMALL LETTER SHARP S]
+char	\xE1	( 2    7 )  # ⡂ á [LATIN SMALL LETTER A WITH ACUTE]
+char	\xE2	( 23  678)  # ⣦ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char	\xE4	(  345  8)  # ⢜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+glyph	\xE7	(1234 6 8)  # ⢯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char	\xE9	(123456 8)  # ⢿ é [LATIN SMALL LETTER E WITH ACUTE]
+char	\xEB	(12 4 6 8)  # ⢫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char	\xED	(  34   8)  # ⢌ í [LATIN SMALL LETTER I WITH ACUTE]
+char	\xEE	(12 456 8)  # ⢻ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char	\xF3	( 2   67 )  # ⡢ ó [LATIN SMALL LETTER O WITH ACUTE]
+glyph	\xF4	( 2  56 8)  # ⢲ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+glyph	\xF6	( 2 4 6 8)  # ⢪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char	\xF7	( 2  5 78)  # ⣒ ÷ [DIVISION SIGN]
+char	\xFA	( 2    78)  # ⣂ ú [LATIN SMALL LETTER U WITH ACUTE]
+char	\xFC	(12  56 8)  # ⢳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char	\xFD	( 2  5678)  # ⣲ ý [LATIN SMALL LETTER Y WITH ACUTE]
+glyph	\u0102	(1      8)  # ⢁ Ă [LATIN CAPITAL LETTER A WITH BREVE]
+char	\u0103	( 2     8)  # ⢂ ă [LATIN SMALL LETTER A WITH BREVE]
+char	\u0104	(  345 78)  # ⣜ Ą [LATIN CAPITAL LETTER A WITH OGONEK]
+glyph	\u0105	(    567 )  # ⡰ ą [LATIN SMALL LETTER A WITH OGONEK]
+char	\u0106	(1  4 67 )  # ⡩ Ć [LATIN CAPITAL LETTER C WITH ACUTE]
+char	\u0107	(1  4 6 8)  # ⢩ ć [LATIN SMALL LETTER C WITH ACUTE]
+char	\u010C	(1    67 )  # ⡡ Č [LATIN CAPITAL LETTER C WITH CARON]
+char	\u010D	(1    6  )  # ⠡ č [LATIN SMALL LETTER C WITH CARON]
+glyph	\u010E	(1  45  8)  # ⢙ Ď [LATIN CAPITAL LETTER D WITH CARON]
+char	\u010F	(  3  6 8)  # ⢤ ď [LATIN SMALL LETTER D WITH CARON]
+char	\u0110	(1  4567 )  # ⡹ Đ [LATIN CAPITAL LETTER D WITH STROKE]
+glyph	\u0111	(1  456 8)  # ⢹ đ [LATIN SMALL LETTER D WITH STROKE]
+char	\u0118	(1   5678)  # ⣱ Ę [LATIN CAPITAL LETTER E WITH OGONEK]
+char	\u0119	( 2   6 8)  # ⢢ ę [LATIN SMALL LETTER E WITH OGONEK]
+char	\u011A	(12   67 )  # ⡣ Ě [LATIN CAPITAL LETTER E WITH CARON]
+glyph	\u011B	( 23    8)  # ⢆ ě [LATIN SMALL LETTER E WITH CARON]
+char	\u0139	(123    8)  # ⢇ Ĺ [LATIN CAPITAL LETTER L WITH ACUTE]
+char	\u013A	( 23   7 )  # ⡆ ĺ [LATIN SMALL LETTER L WITH ACUTE]
+glyph	\u013D	(   456 8)  # ⢸ Ľ [LATIN CAPITAL LETTER L WITH CARON]
+glyph	\u013E	(12     8)  # ⢃ ľ [LATIN SMALL LETTER L WITH CARON]
+char	\u0141	(123  6 8)  # ⢧ Ł [LATIN CAPITAL LETTER L WITH STROKE]
+char	\u0142	( 23   78)  # ⣆ ł [LATIN SMALL LETTER L WITH STROKE]
+char	\u0143	(1 345  8)  # ⢝ Ń [LATIN CAPITAL LETTER N WITH ACUTE]
+char	\u0144	( 2  567 )  # ⡲ ń [LATIN SMALL LETTER N WITH ACUTE]
+char	\u0147	(12 4 678)  # ⣫ Ň [LATIN CAPITAL LETTER N WITH CARON]
+glyph	\u0148	( 23 5  8)  # ⢖ ň [LATIN SMALL LETTER N WITH CARON]
+char	\u0150	( 2 4 678)  # ⣪ Ő [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
+char	\u0151	(  3 5  8)  # ⢔ ő [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
+char	\u0154	(123 5  8)  # ⢗ Ŕ [LATIN CAPITAL LETTER R WITH ACUTE]
+char	\u0155	( 23  67 )  # ⡦ ŕ [LATIN SMALL LETTER R WITH ACUTE]
+char	\u0158	( 2 456 8)  # ⢺ Ř [LATIN CAPITAL LETTER R WITH CARON]
+char	\u0159	(  3 56 8)  # ⢴ ř [LATIN SMALL LETTER R WITH CARON]
+char	\u015A	( 234   8)  # ⢎ Ś [LATIN CAPITAL LETTER S WITH ACUTE]
+char	\u015B	(  3 5 7 )  # ⡔ ś [LATIN SMALL LETTER S WITH ACUTE]
+char	\u015E	( 23456 8)  # ⢾ Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
+char	\u015F	(  3 5678)  # ⣴ ş [LATIN SMALL LETTER S WITH CEDILLA]
+char	\u0160	(1   567 )  # ⡱ Š [LATIN CAPITAL LETTER S WITH CARON]
+char	\u0161	(1   56  )  # ⠱ š [LATIN SMALL LETTER S WITH CARON]
+char	\u0162	( 2345  8)  # ⢞ Ţ [LATIN CAPITAL LETTER T WITH CEDILLA]
+char	\u0163	(  3 567 )  # ⡴ ţ [LATIN SMALL LETTER T WITH CEDILLA]
+char	\u0164	(12345  8)  # ⢟ Ť [LATIN CAPITAL LETTER T WITH CARON]
+char	\u0165	( 23 567 )  # ⡶ ť [LATIN SMALL LETTER T WITH CARON]
+char	\u016E	(  34 67 )  # ⡬ Ů [LATIN CAPITAL LETTER U WITH RING ABOVE]
+char	\u016F	(  34 6 8)  # ⢬ ů [LATIN SMALL LETTER U WITH RING ABOVE]
+char	\u0170	(  34 678)  # ⣬ Ű [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
+char	\u0171	(1  4 678)  # ⣩ ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
+char	\u0179	(1 3 56 8)  # ⢵ Ź [LATIN CAPITAL LETTER Z WITH ACUTE]
+char	\u017A	( 2   678)  # ⣢ ź [LATIN SMALL LETTER Z WITH ACUTE]
+char	\u017B	(12   678)  # ⣣ Ż [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+char	\u017C	(1    678)  # ⣡ ż [LATIN SMALL LETTER Z WITH DOT ABOVE]
+char	\u017D	( 234 67 )  # ⡮ Ž [LATIN CAPITAL LETTER Z WITH CARON]
+char	\u017E	( 234 6  )  # ⠮ ž [LATIN SMALL LETTER Z WITH CARON]
+char	\u02C7	(   45  8)  # ⢘ ˇ [CARON]
+char	\u02D8	(   4 67 )  # ⡨ ˘ [BREVE]
+char	\u02D9	(     67 )  # ⡠ ˙ [DOT ABOVE]
+char	\u02DB	(    5  8)  # ⢐ ˛ [OGONEK]
+char	\u02DD	(     6 8)  # ⢠ ˝ [DOUBLE ACUTE ACCENT]
+
+include common.tti
diff --git a/Tables/Text/sv-1989.ttb b/Tables/Text/sv-1989.ttb
new file mode 100644
index 0000000..e1ded1a
--- /dev/null
+++ b/Tables/Text/sv-1989.ttb
@@ -0,0 +1,203 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Swedish (1989 standard)
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(        )  # 00 ⠀   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	(  345 78)  # 1B ⣜   [ESCAPE]
+char \x1C	( 2 4 678)  # 1C ⣪   [INFORMATION SEPARATOR FOUR]
+char \x1D	(1    678)  # 1D ⣡   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(  3  678)  # 1F ⣤   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(    56  )  # 22 ⠰ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	( 2  56  )  # 24 ⠲ $ [DOLLAR SIGN]
+char \x25	(   4 678)  # 25 ⣨ % [PERCENT SIGN]
+char \x26	(  34 6  )  # 26 ⠬ & [AMPERSAND]
+char \x27	(    5   )  # 27 ⠐ ' [APOSTROPHE]
+char \x28	( 23  678)  # 28 ⣦ ( [LEFT PARENTHESIS]
+char \x29	(  3 5678)  # 29 ⣴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 23 5 7 )  # 2B ⡖ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 2   6 8)  # 3C ⢢ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  3 5 7 )  # 3E ⡔ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4 67 )  # 40 ⡨ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	( 23 5 78)  # 5B ⣖ [ [LEFT SQUARE BRACKET]
+char \x5C	(   4567 )  # 5C ⡸ \ [REVERSE SOLIDUS]
+char \x5D	( 2  5678)  # 5D ⣲ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(      78)  # 5F ⣀ _ [LOW LINE]
+char \x60	(   4 6  )  # 60 ⠨ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	(  3   78)  # 7B ⣄ { [LEFT CURLY BRACKET]
+char \x7C	(       8)  # 7C ⢀ | [VERTICAL LINE]
+char \x7D	(     678)  # 7D ⣠ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \x7F	(   456 8)  # 7F ⢸   [DELETE]
+char \x80	(       8)  # 80 ⢀   [<control-0080>]
+char \x81	(       8)  # 81 ⢀   [<control-0081>]
+char \x82	(       8)  # 82 ⢀   [BREAK PERMITTED HERE]
+char \x83	(       8)  # 83 ⢀   [NO BREAK HERE]
+char \x84	(       8)  # 84 ⢀   [<control-0084>]
+char \x85	(       8)  # 85 ⢀   [NEXT LINE (NEL)]
+char \x86	(       8)  # 86 ⢀   [START OF SELECTED AREA]
+char \x87	(       8)  # 87 ⢀   [END OF SELECTED AREA]
+char \x88	(       8)  # 88 ⢀   [CHARACTER TABULATION SET]
+char \x89	(       8)  # 89 ⢀   [CHARACTER TABULATION WITH JUSTIFICATION]
+char \x8A	(       8)  # 8A ⢀   [LINE TABULATION SET]
+char \x8B	(       8)  # 8B ⢀   [PARTIAL LINE FORWARD]
+char \x8C	(       8)  # 8C ⢀   [PARTIAL LINE BACKWARD]
+char \x8D	(       8)  # 8D ⢀   [REVERSE LINE FEED]
+char \x8E	(       8)  # 8E ⢀   [SINGLE SHIFT TWO]
+char \x8F	(       8)  # 8F ⢀   [SINGLE SHIFT THREE]
+char \x90	(       8)  # 90 ⢀   [DEVICE CONTROL STRING]
+char \x91	(       8)  # 91 ⢀   [PRIVATE USE ONE]
+char \x92	(       8)  # 92 ⢀   [PRIVATE USE TWO]
+char \x93	(       8)  # 93 ⢀   [SET TRANSMIT STATE]
+char \x94	(       8)  # 94 ⢀   [CANCEL CHARACTER]
+char \x95	(       8)  # 95 ⢀   [MESSAGE WAITING]
+char \x96	(       8)  # 96 ⢀   [START OF GUARDED AREA]
+char \x97	(       8)  # 97 ⢀   [END OF GUARDED AREA]
+char \x98	(       8)  # 98 ⢀   [START OF STRING]
+char \x99	(       8)  # 99 ⢀   [<control-0099>]
+char \x9A	(       8)  # 9A ⢀   [SINGLE CHARACTER INTRODUCER]
+char \x9B	(       8)  # 9B ⢀   [CONTROL SEQUENCE INTRODUCER]
+char \x9C	(       8)  # 9C ⢀   [STRING TERMINATOR]
+char \x9D	(       8)  # 9D ⢀   [OPERATING SYSTEM COMMAND]
+char \x9E	(       8)  # 9E ⢀   [PRIVACY MESSAGE]
+char \x9F	(       8)  # 9F ⢀   [APPLICATION PROGRAM COMMAND]
+char \xA1	(       8)  # A1 ⢀ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(       8)  # A2 ⢀ ¢ [CENT SIGN]
+char \xA3	(       8)  # A3 ⢀ £ [POUND SIGN]
+char \xA4	(       8)  # A4 ⢀ ¤ [CURRENCY SIGN]
+char \xA5	(       8)  # A5 ⢀ ¥ [YEN SIGN]
+char \xA6	(       8)  # A6 ⢀ ¦ [BROKEN BAR]
+char \xA7	(       8)  # A7 ⢀ § [SECTION SIGN]
+char \xA8	(       8)  # A8 ⢀ ¨ [DIAERESIS]
+char \xA9	(       8)  # A9 ⢀ © [COPYRIGHT SIGN]
+char \xAA	(       8)  # AA ⢀ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	(       8)  # AB ⢀ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(       8)  # AC ⢀ ¬ [NOT SIGN]
+char \xAD	(       8)  # AD ⢀ ­ [SOFT HYPHEN]
+char \xAE	(       8)  # AE ⢀ ® [REGISTERED SIGN]
+char \xAF	(       8)  # AF ⢀ ¯ [MACRON]
+char \xB0	(       8)  # B0 ⢀ ° [DEGREE SIGN]
+char \xB1	(       8)  # B1 ⢀ ± [PLUS-MINUS SIGN]
+char \xB2	(       8)  # B2 ⢀ ² [SUPERSCRIPT TWO]
+char \xB3	(       8)  # B3 ⢀ ³ [SUPERSCRIPT THREE]
+char \xB4	(       8)  # B4 ⢀ ´ [ACUTE ACCENT]
+char \xB5	(       8)  # B5 ⢀ µ [MICRO SIGN]
+char \xB6	(       8)  # B6 ⢀ ¶ [PILCROW SIGN]
+char \xB7	(       8)  # B7 ⢀ · [MIDDLE DOT]
+char \xB8	(       8)  # B8 ⢀ ¸ [CEDILLA]
+char \xB9	(       8)  # B9 ⢀ ¹ [SUPERSCRIPT ONE]
+char \xBA	(       8)  # BA ⢀ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(       8)  # BB ⢀ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(       8)  # BC ⢀ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(       8)  # BD ⢀ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(       8)  # BE ⢀ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	( 2   6  )  # BF ⠢ ¿ [INVERTED QUESTION MARK]
+char \xC0	(       8)  # C0 ⢀ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(       8)  # C1 ⢀ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(       8)  # C2 ⢀ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(       8)  # C3 ⢀ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 7 )  # C4 ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345  8)  # C6 ⢜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	( 2   67 )  # C7 ⡢ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(       8)  # C8 ⢀ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(       8)  # CA ⢀ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(       8)  # CB ⢀ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(       8)  # CC ⢀ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(       8)  # CD ⢀ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(       8)  # CE ⢀ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(       8)  # CF ⢀ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(       8)  # D0 ⢀ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 45678)  # D1 ⣻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(       8)  # D2 ⢀ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(1  4567 )  # D3 ⡹ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(       8)  # D4 ⢀ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(       8)  # D5 ⢀ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(       8)  # D7 ⢀ × [MULTIPLICATION SIGN]
+char \xD8	( 2 4 6 8)  # D8 ⢪ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(       8)  # D9 ⢀ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12 4567 )  # DA ⡻ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	(       8)  # DB ⢀ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1234 67 )  # DD ⡯ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12 4 6  )  # DE ⠫ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(       8)  # DF ⢀ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 56  )  # E1 ⠷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    6  )  # E2 ⠡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(       8)  # E3 ⢀ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(    567 )  # E6 ⡰ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34    )  # EC ⠌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34    )  # ED ⠌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(       8)  # F0 ⢀ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456  )  # F1 ⠻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 6  )  # F2 ⠬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 6  )  # F3 ⠬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(       8)  # F5 ⢀ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # F6 ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(       8)  # F7 ⢀ ÷ [DIVISION SIGN]
+char \xF8	(  3 5  8)  # F8 ⢔ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 23456  )  # FA ⠾ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1234 6  )  # FD ⠯ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(12 4 67 )  # FE ⡫ þ [LATIN SMALL LETTER THORN]
+char \xFF	(       8)  # FF ⢀ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/sv-1996.ttb b/Tables/Text/sv-1996.ttb
new file mode 100644
index 0000000..004b967
--- /dev/null
+++ b/Tables/Text/sv-1996.ttb
@@ -0,0 +1,165 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Swedish (1996 standard)
+
+# This file contains the ISO-8859-1 compatible version of the Swedish 8-dot
+# braille standard as it was defined by the Swedish Braille Authority
+# (www.punktskriftsnamnden.se) on October 8, 1996. As of December 2005, this
+# is the most current braille standard in Sweden.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(    56  )  # 22 ⠰ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	( 23 5  8)  # 24 ⢖ $ [DOLLAR SIGN]
+char \x25	(   4 678)  # 25 ⣨ % [PERCENT SIGN]
+char \x26	(  34 6 8)  # 26 ⢬ & [AMPERSAND]
+char \x27	(    5   )  # 27 ⠐ ' [APOSTROPHE]
+char \x28	( 23  6  )  # 28 ⠦ ( [LEFT PARENTHESIS]
+char \x29	(  3 56  )  # 29 ⠴ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	( 2  56  )  # 2B ⠲ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	(  3     )  # 2E ⠄ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+# Hindu-Arabic numerals     # 30-39
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	(  3 5  8)  # 3C ⢔ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	( 2   67 )  # 3E ⡢ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4   8)  # 40 ⢈ @ [COMMERCIAL AT]
+# uppercase Latin alphabet  # 41-5A
+char \x5B	(123 56 8)  # 5B ⢷ [ [LEFT SQUARE BRACKET]
+char \x5C	(1    6 8)  # 5C ⢡ \ [REVERSE SOLIDUS]
+char \x5D	( 23456 8)  # 5D ⢾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(    5 78)  # 5E ⣐ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(     6  )  # 5F ⠠ _ [LOW LINE]
+char \x60	(   4 6  )  # 60 ⠨ ` [GRAVE ACCENT]
+# lowercase Latin alphabet  # 61-7A
+char \x7B	( 23  678)  # 7B ⣦ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	(  3 5678)  # 7D ⣴ } [RIGHT CURLY BRACKET]
+char \x7E	( 2   6 8)  # 7E ⢢ ~ [TILDE]
+char \xA1	(  3  67 )  # A1 ⡤ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	( 234 6 8)  # A2 ⢮ ¢ [CENT SIGN]
+char \xA3	(123    8)  # A3 ⢇ £ [POUND SIGN]
+char \xA4	( 2 4  78)  # A4 ⣊ ¤ [CURRENCY SIGN]
+char \xA5	( 2 4 678)  # A5 ⣪ ¥ [YEN SIGN]
+char \xA6	(12   678)  # A6 ⣣ ¦ [BROKEN BAR]
+char \xA7	(  34 6  )  # A7 ⠬ § [SECTION SIGN]
+char \xA8	(  3  678)  # A8 ⣤ ¨ [DIAERESIS]
+char \xA9	(1  456 8)  # A9 ⢹ © [COPYRIGHT SIGN]
+char \xAA	( 2345678)  # AA ⣾ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23   78)  # AB ⣆ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(1 345  8)  # AC ⢝ ¬ [NOT SIGN]
+char \xAD	( 23 56 8)  # AD ⢶ ­ [SOFT HYPHEN]
+char \xAE	(12 4 6 8)  # AE ⢫ ® [REGISTERED SIGN]
+char \xAF	(1   5 78)  # AF ⣑ ¯ [MACRON]
+char \xB0	(1   5678)  # B0 ⣱ ° [DEGREE SIGN]
+char \xB1	(1234 6 8)  # B1 ⢯ ± [PLUS-MINUS SIGN]
+char \xB2	(  3   7 )  # B2 ⡄ ² [SUPERSCRIPT TWO]
+char \xB3	( 2  567 )  # B3 ⡲ ³ [SUPERSCRIPT THREE]
+char \xB4	( 2  5  8)  # B4 ⢒ ´ [ACUTE ACCENT]
+char \xB5	(1 34  78)  # B5 ⣍ µ [MICRO SIGN]
+char \xB6	(12   67 )  # B6 ⡣ ¶ [PILCROW SIGN]
+char \xB7	(       8)  # B7 ⢀ · [MIDDLE DOT]
+char \xB8	(12  56 8)  # B8 ⢳ ¸ [CEDILLA]
+char \xB9	(1  4 6 8)  # B9 ⢩ ¹ [SUPERSCRIPT ONE]
+char \xBA	(1 3 5  8)  # BA ⢕ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(    5678)  # BB ⣰ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(123 5  8)  # BC ⢗ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(1 3  6 8)  # BD ⢥ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(  3 5 78)  # BE ⣔ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(  3    8)  # BF ⢄ ¿ [INVERTED QUESTION MARK]
+char \xC0	(1  4 67 )  # C0 ⡩ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(1 3 5678)  # C1 ⣵ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	( 2 45 78)  # C2 ⣚ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(1 3 5 78)  # C3 ⣕ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(  345 7 )  # C4 ⡜ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	(1    67 )  # C5 ⡡ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	(  345 78)  # C6 ⣜ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(123   78)  # C8 ⣇ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(1234567 )  # C9 ⡿ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(     6 8)  # CA ⢠ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	( 2    7 )  # CB ⡂ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(   45 7 )  # CC ⡘ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(1 34   8)  # CD ⢍ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(   45  8)  # CE ⢘ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(     67 )  # CF ⡠ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	( 234 67 )  # D0 ⡮ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(12 4567 )  # D1 ⡻ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(1234  78)  # D2 ⣏ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(1     78)  # D3 ⣁ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	(12 45 78)  # D4 ⣛ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	( 234  78)  # D5 ⣎ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	( 2 4 67 )  # D6 ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(1 34 6 8)  # D7 ⢭ × [MULTIPLICATION SIGN]
+char \xD8	(1   567 )  # D8 ⡱ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(1  45 78)  # D9 ⣙ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	(12  5 78)  # DA ⣓ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	( 2 45678)  # DB ⣺ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(12  567 )  # DC ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(12 4  78)  # DD ⣋ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(1234 678)  # DE ⣯ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	(12    78)  # DF ⣃ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(123 56  )  # E0 ⠷ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(123 5678)  # E1 ⣷ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(1    678)  # E2 ⣡ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(1 3  678)  # E3 ⣥ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(  345   )  # E4 ⠜ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	(1    6  )  # E5 ⠡ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	(  345  8)  # E6 ⢜ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	( 234 6  )  # E8 ⠮ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(123456  )  # E9 ⠿ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(12   6  )  # EA ⠣ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(12 4 6  )  # EB ⠫ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(  34  78)  # EC ⣌ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  34   8)  # ED ⢌ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(1  4 6  )  # EE ⠩ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(12 456  )  # EF ⠻ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(    56 8)  # F0 ⢰ ð [LATIN SMALL LETTER ETH]
+char \xF1	(12 456 8)  # F1 ⢻ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(  34 678)  # F2 ⣬ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  34 67 )  # F3 ⡬ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	(1  456  )  # F4 ⠹ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	( 234   8)  # F5 ⢎ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	( 2 4 6  )  # F6 ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	( 2  56 8)  # F7 ⢲ ÷ [DIVISION SIGN]
+char \xF8	(   45 78)  # F8 ⣘ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	( 23456  )  # F9 ⠾ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 2  5678)  # FA ⣲ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	(1   56  )  # FB ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(12  56  )  # FC ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(  3456 8)  # FD ⢼ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	( 2345 78)  # FE ⣞ þ [LATIN SMALL LETTER THORN]
+char \xFF	(12   6 8)  # FF ⢣ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/sv.ttb b/Tables/Text/sv.ttb
new file mode 100644
index 0000000..bbbfc29
--- /dev/null
+++ b/Tables/Text/sv.ttb
@@ -0,0 +1,21 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Swedish
+
+include sv-1996.ttb
diff --git a/Tables/Text/sw.ttb b/Tables/Text/sw.ttb
new file mode 100644
index 0000000..a4739b3
--- /dev/null
+++ b/Tables/Text/sw.ttb
@@ -0,0 +1,31 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Swahili
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+include ltr-latin.tti
+include num-dot8.tti
+include punc-basic.tti
+include common.tti
diff --git a/Tables/Text/ta.ttb b/Tables/Text/ta.ttb
new file mode 100644
index 0000000..40955a6
--- /dev/null
+++ b/Tables/Text/ta.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Tamil
+
+include tamil.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/tamil.tti b/Tables/Text/tamil.tti
new file mode 100644
index 0000000..1ccffc9
--- /dev/null
+++ b/Tables/Text/tamil.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Tamil script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+
+char \u0B82	(    56  )  # ⠰ ஂ [TAMIL SIGN ANUSVARA]
+char \u0B83	(     6  )  # ⠠ ஃ [TAMIL SIGN VISARGA]
+char \u0B85	(1       )  # ⠁ அ [TAMIL LETTER A]
+char \u0B86	(  345   )  # ⠜ ஆ [TAMIL LETTER AA]
+char \u0B87	( 2 4    )  # ⠊ இ [TAMIL LETTER I]
+char \u0B88	(  3 5   )  # ⠔ ஈ [TAMIL LETTER II]
+char \u0B89	(1 3  6  )  # ⠥ உ [TAMIL LETTER U]
+char \u0B8A	(12  56  )  # ⠳ ஊ [TAMIL LETTER UU]
+
+char \u0B8E	(  34  7 )  # ⡌ எ [TAMIL LETTER E]
+char \u0B8F	(1   5   )  # ⠑ ஏ [TAMIL LETTER EE]
+char \u0B90	(  34    )  # ⠌ ஐ [TAMIL LETTER AI]
+
+char \u0B92	( 2 4 67 )  # ⡪ ஒ [TAMIL LETTER O]
+char \u0B93	(1 3 5   )  # ⠕ ஓ [TAMIL LETTER OO]
+char \u0B94	( 2 4 6  )  # ⠪ ஔ [TAMIL LETTER AU]
+char \u0B95	(1 3     )  # ⠅ க [TAMIL LETTER KA]
+
+
+
+char \u0B99	(  34 6  )  # ⠬ ங [TAMIL LETTER NGA]
+char \u0B9A	(1  4    )  # ⠉ ச [TAMIL LETTER CA]
+
+char \u0B9C	( 2 45   )  # ⠚ ஜ [TAMIL LETTER JA]
+
+char \u0B9E	( 2  5   )  # ⠒ ஞ [TAMIL LETTER NYA]
+char \u0B9F	( 23456  )  # ⠾ ட [TAMIL LETTER TTA]
+
+
+
+char \u0BA3	(  3456  )  # ⠼ ண [TAMIL LETTER NNA]
+char \u0BA4	( 2345   )  # ⠞ த [TAMIL LETTER TA]
+
+
+
+char \u0BA8	(1 345   )  # ⠝ ந [TAMIL LETTER NA]
+char \u0BA9	(    56  )  # ⠰ ன [TAMIL LETTER NNNA]
+char \u0BAA	(1234    )  # ⠏ ப [TAMIL LETTER PA]
+
+
+
+char \u0BAE	(1 34    )  # ⠍ ம [TAMIL LETTER MA]
+char \u0BAF	(1 3456  )  # ⠽ ய [TAMIL LETTER YA]
+char \u0BB0	(123 5   )  # ⠗ ர [TAMIL LETTER RA]
+char \u0BB1	(123 5 7 )  # ⡗ ற [TAMIL LETTER RRA]
+char \u0BB2	(123     )  # ⠇ ல [TAMIL LETTER LA]
+char \u0BB3	(123   7 )  # ⡇ ள [TAMIL LETTER LLA]
+char \u0BB4	(123   78)  # ⣇ ழ [TAMIL LETTER LLLA]
+char \u0BB5	(123  6  )  # ⠧ வ [TAMIL LETTER VA]
+char \u0BB6	(1  4 6  )  # ⠩ ஶ [TAMIL LETTER SHA]
+char \u0BB7	(1234 6  )  # ⠯ ஷ [TAMIL LETTER SSA]
+char \u0BB8	( 234    )  # ⠎ ஸ [TAMIL LETTER SA]
+char \u0BB9	(12  5   )  # ⠓ ஹ [TAMIL LETTER HA]
+
+char \u0BBE	(  345   )  # ⠜ ா [TAMIL VOWEL SIGN AA]
+char \u0BBF	( 2 4    )  # ⠊ ி [TAMIL VOWEL SIGN I]
+char \u0BC0	(  3 5   )  # ⠔ ீ [TAMIL VOWEL SIGN II]
+char \u0BC1	(1 3  6  )  # ⠥ ு [TAMIL VOWEL SIGN U]
+char \u0BC2	(12  56  )  # ⠳ ூ [TAMIL VOWEL SIGN UU]
+
+char \u0BC6	(  34  7 )  # ⡌ ெ [TAMIL VOWEL SIGN E]
+char \u0BC7	(1   5   )  # ⠑ ே [TAMIL VOWEL SIGN EE]
+char \u0BC8	(  34    )  # ⠌ ை [TAMIL VOWEL SIGN AI]
+
+char \u0BCA	( 2 4 67 )  # ⡪ ொ [TAMIL VOWEL SIGN O]
+char \u0BCB	(1 3 5   )  # ⠕ ோ [TAMIL VOWEL SIGN OO]
+char \u0BCC	( 2 4 6  )  # ⠪ ௌ [TAMIL VOWEL SIGN AU]
+char \u0BCD	(   4    )  # ⠈ ் [TAMIL SIGN VIRAMA]
+
+char \u0BE6	( 2 45   )  # ⠚ ௦ [TAMIL DIGIT ZERO]
+char \u0BE7	(1       )  # ⠁ ௧ [TAMIL DIGIT ONE]
+char \u0BE8	(12      )  # ⠃ ௨ [TAMIL DIGIT TWO]
+char \u0BE9	(1  4    )  # ⠉ ௩ [TAMIL DIGIT THREE]
+char \u0BEA	(1  45   )  # ⠙ ௪ [TAMIL DIGIT FOUR]
+char \u0BEB	(1   5   )  # ⠑ ௫ [TAMIL DIGIT FIVE]
+char \u0BEC	(12 4    )  # ⠋ ௬ [TAMIL DIGIT SIX]
+char \u0BED	(12 45   )  # ⠛ ௭ [TAMIL DIGIT SEVEN]
+char \u0BEE	(12  5   )  # ⠓ ௮ [TAMIL DIGIT EIGHT]
+char \u0BEF	( 2 4    )  # ⠊ ௯ [TAMIL DIGIT NINE]
diff --git a/Tables/Text/te.ttb b/Tables/Text/te.ttb
new file mode 100644
index 0000000..c3b65b8
--- /dev/null
+++ b/Tables/Text/te.ttb
@@ -0,0 +1,24 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Telugu
+
+include telugu.tti
+include ascii-basic.tti
+
+include common.tti
diff --git a/Tables/Text/telugu.tti b/Tables/Text/telugu.tti
new file mode 100644
index 0000000..dda2fb5
--- /dev/null
+++ b/Tables/Text/telugu.tti
@@ -0,0 +1,109 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 2008-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines the braille representations
+# for the Telugu script.
+
+# Maintained by John J. Boyer, director@chpi.org, www.chpi.org
+#
+# This table is built and maintained by Leon Ungier <Leon.Ungier@ViewPlus.com>
+# with help and guidance from Mohammed R. Ramadan <mramadan@nattiq.com>
+#
+# Converted from liblouis table by Samuel Thibault <samuel.thibault@ens-lyon.org>
+
+# generated by ttbtest:
+char \u0C01	(  3     )  # ⠄ ఁ [TELUGU SIGN CANDRABINDU]
+char \u0C02	(    56  )  # ⠰ ం [TELUGU SIGN ANUSVARA]
+char \u0C03	(     6  )  # ⠠ ః [TELUGU SIGN VISARGA]
+char \u0C05	(1       )  # ⠁ అ [TELUGU LETTER A]
+char \u0C06	(  345   )  # ⠜ ఆ [TELUGU LETTER AA]
+char \u0C07	( 2 4    )  # ⠊ ఇ [TELUGU LETTER I]
+char \u0C08	(  3 5   )  # ⠔ ఈ [TELUGU LETTER II]
+char \u0C09	(1 3  6  )  # ⠥ ఉ [TELUGU LETTER U]
+char \u0C0A	(12  56  )  # ⠳ ఊ [TELUGU LETTER UU]
+
+char \u0C0E	(  34  7 )  # ⡌ ఎ [TELUGU LETTER E]
+char \u0C0F	(1   5   )  # ⠑ ఏ [TELUGU LETTER EE]
+char \u0C10	(  34    )  # ⠌ ఐ [TELUGU LETTER AI]
+
+char \u0C12	( 2 4 67 )  # ⡪ ఒ [TELUGU LETTER O]
+char \u0C13	(1 3 5   )  # ⠕ ఓ [TELUGU LETTER OO]
+char \u0C14	( 2 4 6  )  # ⠪ ఔ [TELUGU LETTER AU]
+char \u0C15	(1 3     )  # ⠅ క [TELUGU LETTER KA]
+char \u0C16	(   4 6  )  # ⠨ ఖ [TELUGU LETTER KHA]
+char \u0C17	(1234    )  # ⠏ గ [TELUGU LETTER GA]
+char \u0C18	(12   6  )  # ⠣ ఘ [TELUGU LETTER GHA]
+char \u0C19	(  34 6  )  # ⠬ ఙ [TELUGU LETTER NGA]
+char \u0C1A	(1  4    )  # ⠉ చ [TELUGU LETTER CA]
+char \u0C1B	(1    6  )  # ⠡ ఛ [TELUGU LETTER CHA]
+char \u0C1C	( 2 45   )  # ⠚ జ [TELUGU LETTER JA]
+char \u0C1D	(  3 56  )  # ⠴ ఝ [TELUGU LETTER JHA]
+char \u0C1E	( 2  5   )  # ⠒ ఞ [TELUGU LETTER NYA]
+char \u0C1F	( 23456  )  # ⠾ ట [TELUGU LETTER TTA]
+char \u0C20	( 2 456  )  # ⠺ ఠ [TELUGU LETTER TTHA]
+char \u0C21	(12 4 6  )  # ⠫ డ [TELUGU LETTER DDA]
+char \u0C22	(123456  )  # ⠿ ఢ [TELUGU LETTER DDHA]
+char \u0C23	(  3456  )  # ⠼ ణ [TELUGU LETTER NNA]
+char \u0C24	( 2345   )  # ⠞ త [TELUGU LETTER TA]
+char \u0C25	(1  456  )  # ⠹ థ [TELUGU LETTER THA]
+char \u0C26	(1  45   )  # ⠙ ద [TELUGU LETTER DA]
+char \u0C27	( 234 6  )  # ⠮ ధ [TELUGU LETTER DHA]
+char \u0C28	(1 345   )  # ⠝ న [TELUGU LETTER NA]
+
+char \u0C2A	(1234    )  # ⠏ ప [TELUGU LETTER PA]
+char \u0C2B	( 23 5   )  # ⠖ ఫ [TELUGU LETTER PHA]
+char \u0C2C	(12      )  # ⠃ బ [TELUGU LETTER BA]
+char \u0C2D	(   45   )  # ⠘ భ [TELUGU LETTER BHA]
+char \u0C2E	(1 34    )  # ⠍ మ [TELUGU LETTER MA]
+char \u0C2F	(1 3456  )  # ⠽ య [TELUGU LETTER YA]
+char \u0C30	(123 5   )  # ⠗ ర [TELUGU LETTER RA]
+char \u0C31	(123 5 7 )  # ⡗ ఱ [TELUGU LETTER RRA]
+char \u0C32	(123     )  # ⠇ ల [TELUGU LETTER LA]
+char \u0C33	(123   7 )  # ⡇ ళ [TELUGU LETTER LLA]
+
+char \u0C35	(123  6  )  # ⠧ వ [TELUGU LETTER VA]
+char \u0C36	(1  4 6  )  # ⠩ శ [TELUGU LETTER SHA]
+char \u0C37	(1234 6  )  # ⠯ ష [TELUGU LETTER SSA]
+char \u0C38	( 234    )  # ⠎ స [TELUGU LETTER SA]
+char \u0C39	(12  5   )  # ⠓ హ [TELUGU LETTER HA]
+
+char \u0C3E	(  345   )  # ⠜ ా [TELUGU VOWEL SIGN AA]
+char \u0C3F	( 2 4    )  # ⠊ ి [TELUGU VOWEL SIGN I]
+char \u0C40	(  3 5   )  # ⠔ ీ [TELUGU VOWEL SIGN II]
+char \u0C41	(1 3  6  )  # ⠥ ు [TELUGU VOWEL SIGN U]
+char \u0C42	(12  56  )  # ⠳ ూ [TELUGU VOWEL SIGN UU]
+
+char \u0C46	(  34  7 )  # ⡌ ె [TELUGU VOWEL SIGN E]
+char \u0C47	(1   5   )  # ⠑ ే [TELUGU VOWEL SIGN EE]
+char \u0C48	(  34    )  # ⠌ ై [TELUGU VOWEL SIGN AI]
+
+char \u0C4A	( 2 4 67 )  # ⡪ ొ [TELUGU VOWEL SIGN O]
+char \u0C4B	(1 3 5   )  # ⠕ ో [TELUGU VOWEL SIGN OO]
+char \u0C4C	( 2 4 6  )  # ⠪ ౌ [TELUGU VOWEL SIGN AU]
+char \u0C4D	(   4    )  # ⠈ ్ [TELUGU SIGN VIRAMA]
+
+char \u0C66	( 2 45   )  # ⠚ ౦ [TELUGU DIGIT ZERO]
+char \u0C67	(1       )  # ⠁ ౧ [TELUGU DIGIT ONE]
+char \u0C68	(12      )  # ⠃ ౨ [TELUGU DIGIT TWO]
+char \u0C69	(1  4    )  # ⠉ ౩ [TELUGU DIGIT THREE]
+char \u0C6A	(1  45   )  # ⠙ ౪ [TELUGU DIGIT FOUR]
+char \u0C6B	(1   5   )  # ⠑ ౫ [TELUGU DIGIT FIVE]
+char \u0C6C	(12 4    )  # ⠋ ౬ [TELUGU DIGIT SIX]
+char \u0C6D	(12 45   )  # ⠛ ౭ [TELUGU DIGIT SEVEN]
+char \u0C6E	(12  5   )  # ⠓ ౮ [TELUGU DIGIT EIGHT]
+char \u0C6F	( 2 4    )  # ⠊ ౯ [TELUGU DIGIT NINE]
diff --git a/Tables/Text/tr.ttb b/Tables/Text/tr.ttb
new file mode 100644
index 0000000..7c27d1b
--- /dev/null
+++ b/Tables/Text/tr.ttb
@@ -0,0 +1,61 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Turkish
+#
+# Samuel Thibault <samuel.thibault@ens-lyon.org>
+# 
+# This table is based on the Unesco report on the progress of unification of
+# braille writing « L'ÉCRITURE BRAILLE DANS LE MONDE », by Sir Clutha
+# MACKENZIE: http://unesdoc.unesco.org/images/0013/001352/135251fo.pdf
+# The document is dated 1954, so this table may be quite outdated.
+
+# the standard representations for the letters of the Latin alphabet
+include ltr-latin.tti
+char \u0131	(  3 5   )  # ⠔ ı [LATIN SMALL LETTER DOTLESS I]
+char \x49	(  3 5 7 )  # ⡔ I [LATIN CAPITAL LETTER I]
+char \x69	( 2 4    )  # ⠊ i [LATIN SMALL LETTER I]
+char \u0130	( 2 4  7 )  # ⡊ İ [LATIN CAPITAL LETTER I WITH DOT ABOVE]
+
+# lowercase accented letters
+char \xE7	(1    6  )  # ⠡ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \u011F	(12   6  )  # ⠣ ğ [LATIN SMALL LETTER G WITH BREVE]
+char \xF6	( 2 4 6  )  # ⠪ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \u015F	(1  4 6  )  # ⠩ ş [LATIN SMALL LETTER S WITH CEDILLA]
+char \xFC	(12  56  )  # ⠳ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xE2	(  345   )  # ⠜ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xEE	(  34    )  # ⠌ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xFB	(1   56  )  # ⠱ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+
+# uppercase accented letters
+char \xC7	(1    67 )  # ⡡ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \u011E	(12   67 )  # ⡣ Ğ [LATIN CAPITAL LETTER G WITH BREVE]
+char \xD6	( 2 4 67 )  # ⡪ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \u015E	(1  4 67 )  # ⡩ Ş [LATIN CAPITAL LETTER S WITH CEDILLA]
+char \xDC	(12  567 )  # ⡳ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xC2	(  345 7 )  # ⡜ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xCE	(  34  7 )  # ⡌ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xDB	(1   567 )  # ⡱ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+
+# the numbers 0-9 are represented by the letters j,a-i with dot 8 added
+include num-dot8.tti
+
+include punc-basic.tti
+char \x3F	( 2   6  )  # ⠢ ? [QUESTION MARK]
+
+include common.tti
diff --git a/Tables/Text/uk.ttb b/Tables/Text/uk.ttb
new file mode 100644
index 0000000..563c63c
--- /dev/null
+++ b/Tables/Text/uk.ttb
@@ -0,0 +1,67 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Ukrainian
+# modeled from Russian Braille table by Victor Tsaran <vtsaran@gmail.com>
+
+# This is the Ukrainian braille table. It is based on the KOI8-U character set
+# which is the most used in Ukraine.
+
+# As the Ukrainian cyrillic definition conflicts with the latin definition, some
+# decisions had to be taken. Users of this table need to type both latin for the command
+# prompt and cyrillic while reading and writing documents and mail.
+
+# In the following, latin letters are quoted with apostrophes like in 'a', and
+# cyrillic letters are enclosed within brackets like in [a].
+
+# Dot 1 in the cyrillic definition is the cyrillic letter which looks and
+# sounds like 'a'. The problem is that in the KOI8-U character set, there is
+# both a latin 'a' and a cyrillic [a]. In decimal, their character numbers are
+# 97 and 193 respectively.  To handle conflicts like these, we have prioritized
+# which characters are most important to match the standard.
+
+# RULES:
+# 1. All cyrillic characters must follow the Ukrainian standard. KOI8-U character 
+#    193 [a] must be dot-1, and so on.
+# 2. Capital cyrillic letters have dot 7 on.
+# 3. The latin alphabet is implemented to follow the international standard
+#    except it has dot 8 on.
+# 4. Capital latin letters have dots 7 and 8 on.
+# 5. Numbers are defined as in the American standard. This means dot-2 for
+#    number '1', and so on. This will conflict with the cyrillic comma which is
+#    also dot-2.
+# 4. Special characters like !"#¤%&/()=? follow the American standard if 
+#    possible.
+# 2. Control characters are often used on Linux. These are not very well 
+#    implemented. More work should be done.
+
+# Comments from Ukrainians are very welcome.
+
+include ltr-cyrillic.tti
+include ltr-dot8.tti
+include num-nemeth.tti
+include punc-alternate.tti
+
+# generated by ttbtest: charset=koi8-u
+char \u2219	( 23    8)  # 95 ⢆ ∙ [BULLET OPERATOR]
+char \xB0	( 23 567 )  # 9C ⡶ ° [DEGREE SIGN]
+char \xB2	( 2   6 8)  # 9D ⢢ ² [SUPERSCRIPT TWO]
+char \xB7	(  345 78)  # 9E ⣜ · [MIDDLE DOT]
+char \xA9	(  34 678)  # BF ⣬ © [COPYRIGHT SIGN]
+
+include common.tti
diff --git a/Tables/Text/vi.ttb b/Tables/Text/vi.ttb
new file mode 100644
index 0000000..7387c2c
--- /dev/null
+++ b/Tables/Text/vi.ttb
@@ -0,0 +1,217 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# BRLTTY Text Table - Vietnamese
+
+# the standard representations for the Latin control characters
+include ctl-latin.tti
+
+# generated by ttbtest: charset=iso-8859-1
+char \x00	(   4  78)  # 00 ⣈   [NULL]
+# Latin control characters  # 01-1A
+char \x1B	( 2 4 678)  # 1B ⣪   [ESCAPE]
+char \x1C	(12  5678)  # 1C ⣳   [INFORMATION SEPARATOR FOUR]
+char \x1D	(12 45678)  # 1D ⣻   [INFORMATION SEPARATOR THREE]
+char \x1E	(   45 78)  # 1E ⣘   [INFORMATION SEPARATOR TWO]
+char \x1F	(   45678)  # 1F ⣸   [INFORMATION SEPARATOR ONE]
+char \x20	(        )  # 20 ⠀   [SPACE]
+char \x21	( 23 5   )  # 21 ⠖ ! [EXCLAMATION MARK]
+char \x22	(     678)  # 22 ⣠ " [QUOTATION MARK]
+char \x23	(  3456  )  # 23 ⠼ # [NUMBER SIGN]
+char \x24	(  345   )  # 24 ⠜ $ [DOLLAR SIGN]
+char \x25	( 2 4 6  )  # 25 ⠪ % [PERCENT SIGN]
+char \x26	(  34 6 8)  # 26 ⢬ & [AMPERSAND]
+char \x27	(  3     )  # 27 ⠄ ' [APOSTROPHE]
+char \x28	(123 567 )  # 28 ⡷ ( [LEFT PARENTHESIS]
+char \x29	( 234567 )  # 29 ⡾ ) [RIGHT PARENTHESIS]
+char \x2A	(  3 5   )  # 2A ⠔ * [ASTERISK]
+char \x2B	(  34 6  )  # 2B ⠬ + [PLUS SIGN]
+char \x2C	( 2      )  # 2C ⠂ , [COMMA]
+char \x2D	(  3  6  )  # 2D ⠤ - [HYPHEN-MINUS]
+char \x2E	( 2  56  )  # 2E ⠲ . [FULL STOP]
+char \x2F	(  34    )  # 2F ⠌ / [SOLIDUS]
+
+include num-nemd8.tti
+
+char \x3A	( 2  5   )  # 3A ⠒ : [COLON]
+char \x3B	( 23     )  # 3B ⠆ ; [SEMICOLON]
+char \x3C	( 23  6  )  # 3C ⠦ < [LESS-THAN SIGN]
+char \x3D	( 23 56  )  # 3D ⠶ = [EQUALS SIGN]
+char \x3E	(  3 56  )  # 3E ⠴ > [GREATER-THAN SIGN]
+char \x3F	( 2   6  )  # 3F ⠢ ? [QUESTION MARK]
+char \x40	(   4  7 )  # 40 ⡈ @ [COMMERCIAL AT]
+char \x41	(1     7 )  # 41 ⡁ A [LATIN CAPITAL LETTER A]
+char \x42	(12    7 )  # 42 ⡃ B [LATIN CAPITAL LETTER B]
+char \x43	(1  4  7 )  # 43 ⡉ C [LATIN CAPITAL LETTER C]
+char \x44	(1 3 567 )  # 44 ⡵ D [LATIN CAPITAL LETTER D]
+char \x45	(1   5 7 )  # 45 ⡑ E [LATIN CAPITAL LETTER E]
+char \x46	(12 4  7 )  # 46 ⡋ F [LATIN CAPITAL LETTER F]
+char \x47	(12 45 7 )  # 47 ⡛ G [LATIN CAPITAL LETTER G]
+char \x48	(12  5 7 )  # 48 ⡓ H [LATIN CAPITAL LETTER H]
+char \x49	( 2 4  7 )  # 49 ⡊ I [LATIN CAPITAL LETTER I]
+char \x4A	( 2 45 7 )  # 4A ⡚ J [LATIN CAPITAL LETTER J]
+char \x4B	(1 3   7 )  # 4B ⡅ K [LATIN CAPITAL LETTER K]
+char \x4C	(123   7 )  # 4C ⡇ L [LATIN CAPITAL LETTER L]
+char \x4D	(1 34  7 )  # 4D ⡍ M [LATIN CAPITAL LETTER M]
+char \x4E	(1 345 7 )  # 4E ⡝ N [LATIN CAPITAL LETTER N]
+char \x4F	(1 3 5 7 )  # 4F ⡕ O [LATIN CAPITAL LETTER O]
+char \x50	(1234  7 )  # 50 ⡏ P [LATIN CAPITAL LETTER P]
+char \x51	(12345 7 )  # 51 ⡟ Q [LATIN CAPITAL LETTER Q]
+char \x52	(123 5 7 )  # 52 ⡗ R [LATIN CAPITAL LETTER R]
+char \x53	( 234  7 )  # 53 ⡎ S [LATIN CAPITAL LETTER S]
+char \x54	( 2345 7 )  # 54 ⡞ T [LATIN CAPITAL LETTER T]
+char \x55	(1 3  67 )  # 55 ⡥ U [LATIN CAPITAL LETTER U]
+char \x56	(123  67 )  # 56 ⡧ V [LATIN CAPITAL LETTER V]
+char \x57	( 2 4567 )  # 57 ⡺ W [LATIN CAPITAL LETTER W]
+char \x58	(1 34 67 )  # 58 ⡭ X [LATIN CAPITAL LETTER X]
+char \x59	(1 34567 )  # 59 ⡽ Y [LATIN CAPITAL LETTER Y]
+char \x5A	(1 3 567 )  # 5A ⡵ Z [LATIN CAPITAL LETTER Z]
+char \x5B	(123 56 8)  # 5B ⢷ [ [LEFT SQUARE BRACKET]
+char \x5C	(1    6 8)  # 5C ⢡ \ [REVERSE SOLIDUS]
+char \x5D	( 23456 8)  # 5D ⢾ ] [RIGHT SQUARE BRACKET]
+char \x5E	(   45 7 )  # 5E ⡘ ^ [CIRCUMFLEX ACCENT]
+char \x5F	(   4567 )  # 5F ⡸ _ [LOW LINE]
+char \x60	(   4    )  # 60 ⠈ ` [GRAVE ACCENT]
+char \x61	(1       )  # 61 ⠁ a [LATIN SMALL LETTER A]
+char \x62	(12      )  # 62 ⠃ b [LATIN SMALL LETTER B]
+char \x63	(1  4    )  # 63 ⠉ c [LATIN SMALL LETTER C]
+char \x64	(1 3 56  )  # 64 ⠵ d [LATIN SMALL LETTER D]
+char \x65	(1   5   )  # 65 ⠑ e [LATIN SMALL LETTER E]
+char \x66	(12 4    )  # 66 ⠋ f [LATIN SMALL LETTER F]
+char \x67	(12 45   )  # 67 ⠛ g [LATIN SMALL LETTER G]
+char \x68	(12  5   )  # 68 ⠓ h [LATIN SMALL LETTER H]
+char \x69	( 2 4    )  # 69 ⠊ i [LATIN SMALL LETTER I]
+char \x6A	( 2 45   )  # 6A ⠚ j [LATIN SMALL LETTER J]
+char \x6B	(1 3     )  # 6B ⠅ k [LATIN SMALL LETTER K]
+char \x6C	(123     )  # 6C ⠇ l [LATIN SMALL LETTER L]
+char \x6D	(1 34    )  # 6D ⠍ m [LATIN SMALL LETTER M]
+char \x6E	(1 345   )  # 6E ⠝ n [LATIN SMALL LETTER N]
+char \x6F	(1 3 5   )  # 6F ⠕ o [LATIN SMALL LETTER O]
+char \x70	(1234    )  # 70 ⠏ p [LATIN SMALL LETTER P]
+char \x71	(12345   )  # 71 ⠟ q [LATIN SMALL LETTER Q]
+char \x72	(123 5   )  # 72 ⠗ r [LATIN SMALL LETTER R]
+char \x73	( 234    )  # 73 ⠎ s [LATIN SMALL LETTER S]
+char \x74	( 2345   )  # 74 ⠞ t [LATIN SMALL LETTER T]
+char \x75	(1 3  6  )  # 75 ⠥ u [LATIN SMALL LETTER U]
+char \x76	(123  6  )  # 76 ⠧ v [LATIN SMALL LETTER V]
+char \x77	( 2 456  )  # 77 ⠺ w [LATIN SMALL LETTER W]
+char \x78	(1 34 6  )  # 78 ⠭ x [LATIN SMALL LETTER X]
+char \x79	(1 3456  )  # 79 ⠽ y [LATIN SMALL LETTER Y]
+char \x7A	(1 3 56  )  # 7A ⠵ z [LATIN SMALL LETTER Z]
+char \x7B	(123 5678)  # 7B ⣷ { [LEFT CURLY BRACKET]
+char \x7C	(   456 8)  # 7C ⢸ | [VERTICAL LINE]
+char \x7D	( 2345678)  # 7D ⣾ } [RIGHT CURLY BRACKET]
+char \x7E	(   45   )  # 7E ⠘ ~ [TILDE]
+char \x7F	(   456  )  # 7F ⠸   [DELETE]
+char \xA1	(12345678)  # A1 ⣿ ¡ [INVERTED EXCLAMATION MARK]
+char \xA2	(12345678)  # A2 ⣿ ¢ [CENT SIGN]
+char \xA3	(12345678)  # A3 ⣿ £ [POUND SIGN]
+char \xA4	(12345678)  # A4 ⣿ ¤ [CURRENCY SIGN]
+char \xA5	(12345678)  # A5 ⣿ ¥ [YEN SIGN]
+char \xA6	(12345678)  # A6 ⣿ ¦ [BROKEN BAR]
+char \xA7	(12345678)  # A7 ⣿ § [SECTION SIGN]
+char \xA8	(12345678)  # A8 ⣿ ¨ [DIAERESIS]
+char \xA9	(1  4  7 )  # A9 ⡉ © [COPYRIGHT SIGN]
+char \xAA	(12345678)  # AA ⣿ ª [FEMININE ORDINAL INDICATOR]
+char \xAB	( 23  67 )  # AB ⡦ « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xAC	(12345678)  # AC ⣿ ¬ [NOT SIGN]
+char \xAD	(12345678)  # AD ⣿ ­ [SOFT HYPHEN]
+char \xAE	(1234  7 )  # AE ⡏ ® [REGISTERED SIGN]
+char \xAF	(12345678)  # AF ⣿ ¯ [MACRON]
+char \xB0	(12345678)  # B0 ⣿ ° [DEGREE SIGN]
+char \xB1	(12345678)  # B1 ⣿ ± [PLUS-MINUS SIGN]
+char \xB2	(12345678)  # B2 ⣿ ² [SUPERSCRIPT TWO]
+char \xB3	(12345678)  # B3 ⣿ ³ [SUPERSCRIPT THREE]
+char \xB4	(12345678)  # B4 ⣿ ´ [ACUTE ACCENT]
+char \xB5	(12345678)  # B5 ⣿ µ [MICRO SIGN]
+char \xB6	(12345678)  # B6 ⣿ ¶ [PILCROW SIGN]
+char \xB7	(  3 5   )  # B7 ⠔ · [MIDDLE DOT]
+char \xB8	(12345678)  # B8 ⣿ ¸ [CEDILLA]
+char \xB9	(12345678)  # B9 ⣿ ¹ [SUPERSCRIPT ONE]
+char \xBA	(12345678)  # BA ⣿ º [MASCULINE ORDINAL INDICATOR]
+char \xBB	(  3 567 )  # BB ⡴ » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+char \xBC	(12345678)  # BC ⣿ ¼ [VULGAR FRACTION ONE QUARTER]
+char \xBD	(12345678)  # BD ⣿ ½ [VULGAR FRACTION ONE HALF]
+char \xBE	(12345678)  # BE ⣿ ¾ [VULGAR FRACTION THREE QUARTERS]
+char \xBF	(12345678)  # BF ⣿ ¿ [INVERTED QUESTION MARK]
+char \xC0	(    567 )  # C0 ⡰ À [LATIN CAPITAL LETTER A WITH GRAVE]
+char \xC1	(  3 5 7 )  # C1 ⡔ Á [LATIN CAPITAL LETTER A WITH ACUTE]
+char \xC2	(      7 )  # C2 ⡀ Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+char \xC3	(  3  67 )  # C3 ⡤ Ã [LATIN CAPITAL LETTER A WITH TILDE]
+char \xC4	(     67 )  # C4 ⡠ Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
+char \xC5	( 2   67 )  # C5 ⡢ Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
+char \xC6	( 2   67 )  # C6 ⡢ Æ [LATIN CAPITAL LETTER AE]
+char \xC7	(1234 67 )  # C7 ⡯ Ç [LATIN CAPITAL LETTER C WITH CEDILLA]
+char \xC8	(    56 8)  # C8 ⢰ È [LATIN CAPITAL LETTER E WITH GRAVE]
+char \xC9	(  3 5  8)  # C9 ⢔ É [LATIN CAPITAL LETTER E WITH ACUTE]
+char \xCA	(       8)  # CA ⢀ Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+char \xCB	(     6 8)  # CB ⢠ Ë [LATIN CAPITAL LETTER E WITH DIAERESIS]
+char \xCC	(    567 )  # CC ⡰ Ì [LATIN CAPITAL LETTER I WITH GRAVE]
+char \xCD	(  3 5 7 )  # CD ⡔ Í [LATIN CAPITAL LETTER I WITH ACUTE]
+char \xCE	(1234567 )  # CE ⡿ Î [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+char \xCF	(     6  )  # CF ⠠ Ï [LATIN CAPITAL LETTER I WITH DIAERESIS]
+char \xD0	(12345678)  # D0 ⣿ Ð [LATIN CAPITAL LETTER ETH]
+char \xD1	(1  45 7 )  # D1 ⡙ Ñ [LATIN CAPITAL LETTER N WITH TILDE]
+char \xD2	(     67 )  # D2 ⡠ Ò [LATIN CAPITAL LETTER O WITH GRAVE]
+char \xD3	(  3  67 )  # D3 ⡤ Ó [LATIN CAPITAL LETTER O WITH ACUTE]
+char \xD4	( 2 4 67 )  # D4 ⡪ Ô [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+char \xD5	(  3  6  )  # D5 ⠤ Õ [LATIN CAPITAL LETTER O WITH TILDE]
+char \xD6	(12  567 )  # D6 ⡳ Ö [LATIN CAPITAL LETTER O WITH DIAERESIS]
+char \xD7	(  3 5   )  # D7 ⠔ × [MULTIPLICATION SIGN]
+char \xD8	(    56  )  # D8 ⠰ Ø [LATIN CAPITAL LETTER O WITH STROKE]
+char \xD9	(  3 5   )  # D9 ⠔ Ù [LATIN CAPITAL LETTER U WITH GRAVE]
+char \xDA	( 2   6 8)  # DA ⢢ Ú [LATIN CAPITAL LETTER U WITH ACUTE]
+char \xDB	( 2   6  )  # DB ⠢ Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+char \xDC	(  3  6 8)  # DC ⢤ Ü [LATIN CAPITAL LETTER U WITH DIAERESIS]
+char \xDD	(1 34567 )  # DD ⡽ Ý [LATIN CAPITAL LETTER Y WITH ACUTE]
+char \xDE	(12345678)  # DE ⣿ Þ [LATIN CAPITAL LETTER THORN]
+char \xDF	( 234    )  # DF ⠎ ß [LATIN SMALL LETTER SHARP S]
+char \xE0	(    567 )  # E0 ⡰ à [LATIN SMALL LETTER A WITH GRAVE]
+char \xE1	(  3 5 7 )  # E1 ⡔ á [LATIN SMALL LETTER A WITH ACUTE]
+char \xE2	(      7 )  # E2 ⡀ â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+char \xE3	(  3  67 )  # E3 ⡤ ã [LATIN SMALL LETTER A WITH TILDE]
+char \xE4	(     67 )  # E4 ⡠ ä [LATIN SMALL LETTER A WITH DIAERESIS]
+char \xE5	( 2   67 )  # E5 ⡢ å [LATIN SMALL LETTER A WITH RING ABOVE]
+char \xE6	( 2   6  )  # E6 ⠢ æ [LATIN SMALL LETTER AE]
+char \xE7	(1234 6  )  # E7 ⠯ ç [LATIN SMALL LETTER C WITH CEDILLA]
+char \xE8	(    56 8)  # E8 ⢰ è [LATIN SMALL LETTER E WITH GRAVE]
+char \xE9	(  3 5  8)  # E9 ⢔ é [LATIN SMALL LETTER E WITH ACUTE]
+char \xEA	(       8)  # EA ⢀ ê [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+char \xEB	(     6 8)  # EB ⢠ ë [LATIN SMALL LETTER E WITH DIAERESIS]
+char \xEC	(    56  )  # EC ⠰ ì [LATIN SMALL LETTER I WITH GRAVE]
+char \xED	(  3 5   )  # ED ⠔ í [LATIN SMALL LETTER I WITH ACUTE]
+char \xEE	(123456  )  # EE ⠿ î [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+char \xEF	(     6  )  # EF ⠠ ï [LATIN SMALL LETTER I WITH DIAERESIS]
+char \xF0	(12345678)  # F0 ⣿ ð [LATIN SMALL LETTER ETH]
+char \xF1	(1  45   )  # F1 ⠙ ñ [LATIN SMALL LETTER N WITH TILDE]
+char \xF2	(     6  )  # F2 ⠠ ò [LATIN SMALL LETTER O WITH GRAVE]
+char \xF3	(  3  6  )  # F3 ⠤ ó [LATIN SMALL LETTER O WITH ACUTE]
+char \xF4	( 2 4 6  )  # F4 ⠪ ô [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+char \xF5	(  3  6  )  # F5 ⠤ õ [LATIN SMALL LETTER O WITH TILDE]
+char \xF6	(12  56  )  # F6 ⠳ ö [LATIN SMALL LETTER O WITH DIAERESIS]
+char \xF7	(  34    )  # F7 ⠌ ÷ [DIVISION SIGN]
+char \xF8	(    56  )  # F8 ⠰ ø [LATIN SMALL LETTER O WITH STROKE]
+char \xF9	(  3 5   )  # F9 ⠔ ù [LATIN SMALL LETTER U WITH GRAVE]
+char \xFA	( 2   6 8)  # FA ⢢ ú [LATIN SMALL LETTER U WITH ACUTE]
+char \xFB	( 2   6  )  # FB ⠢ û [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+char \xFC	(  3  6 8)  # FC ⢤ ü [LATIN SMALL LETTER U WITH DIAERESIS]
+char \xFD	(1 3456  )  # FD ⠽ ý [LATIN SMALL LETTER Y WITH ACUTE]
+char \xFE	(12345678)  # FE ⣿ þ [LATIN SMALL LETTER THORN]
+char \xFF	(1 3456  )  # FF ⠽ ÿ [LATIN SMALL LETTER Y WITH DIAERESIS]
+
+include common.tti
diff --git a/Tables/Text/win-1252.tti b/Tables/Text/win-1252.tti
new file mode 100644
index 0000000..e5227f9
--- /dev/null
+++ b/Tables/Text/win-1252.tti
@@ -0,0 +1,120 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+# This BRLTTY text subtable defines aliases to/from Windows-1252 characters.
+# Dave Mielke <dave@mielke.cc>
+
+################
+# Introduction #
+################
+
+# Windows-1252 defines characters within the ISO-8859-1 C1 range (\x80-\x9F).
+# Since BRLTTY uses Unicode characters internally, the traditional use of
+# Windows-1252 characters has caused confusion within at least two areas:
+#
+# *  Some braille standards have been developed around, and, therefore, their
+#    tables have been written using, Windows-1252 character values.
+#
+# *  Some programs write Windows-1252 characters to the screen.
+#
+# This text subtable seeks to resolve these issues (see its sections below):
+
+# Windows-1252 doesn't define five of the 32 ISO-8859-1 C1 characters:
+#
+#    \x81, \x8D, \x8F, \x90, \x9D
+#
+# Wikipedia says:
+# 
+#    “According to the information on Microsoft's and the Unicode Consortium's
+#    websites, positions 81, 8D, 8F, 90, and 9D are unused; however, the
+#    Windows API MultiByteToWideChar maps these to the corresponding C1 control
+#    codes. The "best fit" mapping documents this behavior, too.”
+
+
+##############################################################
+# Braille Tables Written Using Windows-1252 Character Values #
+##############################################################
+
+# Alias each relevant Unicode character to its corresponding Windows-1252
+# value so that its braille representation will be found.
+
+ifGlyph	\x80	alias	\u20AC	\x80	# € [EURO SIGN]
+ifGlyph	\x82	alias	\u201A	\x82	# ‚ [SINGLE LOW-9 QUOTATION MARK]
+ifGlyph	\x83	alias	\u0192	\x83	# ƒ [LATIN SMALL LETTER F WITH HOOK]
+ifGlyph	\x84	alias	\u201E	\x84	# „ [DOUBLE LOW-9 QUOTATION MARK]
+ifGlyph	\x85	alias	\u2026	\x85	# … [HORIZONTAL ELLIPSIS]
+ifGlyph	\x86	alias	\u2020	\x86	# † [DAGGER]
+ifGlyph	\x87	alias	\u2021	\x87	# ‡ [DOUBLE DAGGER]
+ifGlyph	\x88	alias	\u02C6	\x88	# ˆ [MODIFIER LETTER CIRCUMFLEX ACCENT]
+ifGlyph	\x89	alias	\u2030	\x89	# ‰ [PER MILLE SIGN]
+ifGlyph	\x8A	alias	\u0160	\x8A	# Š [LATIN CAPITAL LETTER S WITH CARON]
+ifGlyph	\x8B	alias	\u2039	\x8B	# ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+ifGlyph	\x8C	alias	\u0152	\x8C	# Œ [LATIN CAPITAL LIGATURE OE]
+ifGlyph	\x8E	alias	\u017D	\x8E	# Ž [LATIN CAPITAL LETTER Z WITH CARON]
+ifGlyph	\x91	alias	\u2018	\x91	# ‘ [LEFT SINGLE QUOTATION MARK]
+ifGlyph	\x92	alias	\u2019	\x92	# ’ [RIGHT SINGLE QUOTATION MARK]
+ifGlyph	\x93	alias	\u201C	\x93	# “ [LEFT DOUBLE QUOTATION MARK]
+ifGlyph	\x94	alias	\u201D	\x94	# ” [RIGHT DOUBLE QUOTATION MARK]
+ifGlyph	\x95	alias	\u2022	\x95	# • [BULLET]
+ifGlyph	\x96	alias	\u2013	\x96	# – [EN DASH]
+ifGlyph	\x97	alias	\u2014	\x97	# — [EM DASH]
+ifGlyph	\x98	alias	\u02DC	\x98	# ˜ [SMALL TILDE]
+ifGlyph	\x99	alias	\u2122	\x99	# ™ [TRADE MARK SIGN]
+ifGlyph	\x9A	alias	\u0161	\x9A	# š [LATIN SMALL LETTER S WITH CARON]
+ifGlyph	\x9B	alias	\u203A	\x9B	# › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+ifGlyph	\x9C	alias	\u0153	\x9C	# œ [LATIN SMALL LIGATURE OE]
+ifGlyph	\x9E	alias	\u017E	\x9E	# ž [LATIN SMALL LETTER Z WITH CARON]
+ifGlyph	\x9F	alias	\u0178	\x9F	# Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+
+
+#################################################
+# Windows-1252 characters Written to the Screen #
+#################################################
+
+# Alias the Windows-1252 characters to their corresponding Unicode values so
+# that their braille representations can be found.
+
+alias	\x80	\u20AC	# € [EURO SIGN]
+alias	\x82	\u201A	# ‚ [SINGLE LOW-9 QUOTATION MARK]
+alias	\x83	\u0192	# ƒ [LATIN SMALL LETTER F WITH HOOK]
+alias	\x84	\u201E	# „ [DOUBLE LOW-9 QUOTATION MARK]
+alias	\x85	\u2026	# … [HORIZONTAL ELLIPSIS]
+alias	\x86	\u2020	# † [DAGGER]
+alias	\x87	\u2021	# ‡ [DOUBLE DAGGER]
+alias	\x88	\u02C6	# ˆ [MODIFIER LETTER CIRCUMFLEX ACCENT]
+alias	\x89	\u2030	# ‰ [PER MILLE SIGN]
+alias	\x8A	\u0160	# Š [LATIN CAPITAL LETTER S WITH CARON]
+alias	\x8B	\u2039	# ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+alias	\x8C	\u0152	# Œ [LATIN CAPITAL LIGATURE OE]
+alias	\x8E	\u017D	# Ž [LATIN CAPITAL LETTER Z WITH CARON]
+alias	\x91	\u2018	# ‘ [LEFT SINGLE QUOTATION MARK]
+alias	\x92	\u2019	# ’ [RIGHT SINGLE QUOTATION MARK]
+alias	\x93	\u201C	# “ [LEFT DOUBLE QUOTATION MARK]
+alias	\x94	\u201D	# ” [RIGHT DOUBLE QUOTATION MARK]
+alias	\x95	\u2022	# • [BULLET]
+alias	\x96	\u2013	# – [EN DASH]
+alias	\x97	\u2014	# — [EM DASH]
+alias	\x98	\u02DC	# ˜ [SMALL TILDE]
+alias	\x99	\u2122	# ™ [TRADE MARK SIGN]
+alias	\x9A	\u0161	# š [LATIN SMALL LETTER S WITH CARON]
+alias	\x9B	\u203A	# › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+alias	\x9C	\u0153	# œ [LATIN SMALL LIGATURE OE]
+alias	\x9E	\u017E	# ž [LATIN SMALL LETTER Z WITH CARON]
+alias	\x9F	\u0178	# Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+
+
diff --git a/Tools/braille.tcl b/Tools/braille.tcl
new file mode 100644
index 0000000..6bdbac6
--- /dev/null
+++ b/Tools/braille.tcl
@@ -0,0 +1,210 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set brlErrorCategory braille
+set brlUnicodeRow [expr {0X2800 + 0}]
+
+set brfDotsTable {
+   0
+   2346
+   5
+   3456
+   1246
+   146
+   12346
+   3
+   12356
+   23456
+   16
+   346
+   6
+   36
+   46
+   34
+   356
+   2
+   23
+   25
+   256
+   26
+   235
+   2356
+   236
+   35
+   156
+   56
+   126
+   123456
+   345
+   1456
+   4
+   1
+   12
+   14
+   145
+   15
+   124
+   1245
+   125
+   24
+   245
+   13
+   123
+   134
+   1345
+   135
+   1234
+   12345
+   1235
+   234
+   2345
+   136
+   1236
+   2456
+   1346
+   13456
+   1356
+   246
+   1256
+   12456
+   45
+   456
+}
+
+proc brlThrowError {code problem {value ""}} {
+   set message $problem
+
+   if {[string length $value] > 0} {
+      append message ": $value"
+   }
+
+   return -code error -errorcode [list $::brlErrorCategory $code $problem $value] $message
+}
+
+proc brlHexadecimalToDecimal {hexadecimal} {
+   if {![string is xdigit -strict $hexadecimal]} {
+      brlThrowError not-hex "not a hexadecimal number" $hexadecimal
+   }
+
+   scan $hexadecimal "%x" decimal
+   return $decimal
+}
+
+proc brlCharacterToCodepoint {character} {
+   if {[string length $character] != 1} {
+      brlThrowError not-char "not a single character" $character
+   }
+
+   scan $character "%c" codepoint
+   return $codepoint
+}
+
+proc brlCodepointToCharacter {codepoint} {
+   if {![string is integer -strict $codepoint]} {
+      brlThrowError not-int "not an integer" $codepoint
+   }
+
+   return [format "%c" "$codepoint"]
+}
+
+proc brlCharacterToDots {character} {
+   if {[string length $character] == 1} {
+      global brlUnicodeRow
+
+      set codepoint [brlCharacterToCodepoint $character]
+      set bits [expr {$codepoint & 0XFF}]
+
+      if {($codepoint - $bits) == $brlUnicodeRow} {
+         set dots ""
+         set dot 1
+         set bit 0X01
+
+         while {$bits != 0} {
+            if {($bits & $bit) != 0} {
+               set bits [expr {$bits & ~$bit}]
+               append dots $dot
+            }
+
+            set bit [expr {$bit << 1}]
+            incr dot
+         }
+
+         if {[string length $dots] > 0} {
+            return $dots
+         }
+
+         return "0"
+      }
+
+      global brfDotsTable
+      set index [expr {$codepoint - 0X20}]
+
+      if {($codepoint >= 0X60) && ($codepoint <= 0X7E)} {
+         incr index -0X20
+      }
+
+      if {($index >= 0) && ($index < [llength $brfDotsTable])} {
+         return [lindex $brfDotsTable $index]
+      }
+   }
+
+   brlThrowError not-brl "not a braille character" $character
+}
+
+proc brlDotsToCharacter {dots} {
+   if {[string length $dots] == 0} {
+      brlThrowError no-dots "no dot numbers"
+   }
+
+   global brlUnicodeRow
+   set codepoint $brlUnicodeRow
+
+   if {![string equal $dots "0"]} {
+      foreach dot [split $dots ""] {
+         if {[string first $dot "12345678"] == -1} {
+            brlThrowError not-dot "Not a dot number" $dot
+         }
+
+         set bit [expr {1 << ($dot - 1)}]
+
+         if {($codepoint & $bit) != 0} {
+            brlThrowError dup-dot "duplicate dot number" $dot
+         }
+
+         incr codepoint $bit
+      }
+   }
+
+   return [brlCodepointToCharacter $codepoint]
+}
+
+proc brlFormatCodepoint {codepoint} {
+   set digits [format "%X" $codepoint]
+   set length [string length $digits]
+   set letter "z"
+
+   foreach {count letter} {2 x 4 u 8 U} {
+      if {$length <= $count} {
+         set digits "[string repeat "0" [expr {$count - $length}]]$digits"
+         break
+      }
+   }
+
+   return "\\$letter$digits"
+}
+
diff --git a/Tools/brlapi.d/fuzz-dict b/Tools/brlapi.d/fuzz-dict
new file mode 100644
index 0000000..44c3ef8
--- /dev/null
+++ b/Tools/brlapi.d/fuzz-dict
@@ -0,0 +1,256 @@
+0="\x00\x00\x00\x00"
+1="\x00\x00\x00\x01"
+2="\x00\x00\x00\x02"
+3="\x00\x00\x00\x03"
+4="\x00\x00\x00\x04"
+5="\x00\x00\x00\x05"
+6="\x00\x00\x00\x06"
+7="\x00\x00\x00\x07"
+8="\x00\x00\x00\x08"
+9="\x00\x00\x00\x09"
+10="\x00\x00\x00\x0A"
+11="\x00\x00\x00\x0B"
+12="\x00\x00\x00\x0C"
+13="\x00\x00\x00\x0D"
+14="\x00\x00\x00\x0E"
+15="\x00\x00\x00\x0F"
+16="\x00\x00\x00\x10"
+17="\x00\x00\x00\x11"
+18="\x00\x00\x00\x12"
+19="\x00\x00\x00\x13"
+20="\x00\x00\x00\x14"
+21="\x00\x00\x00\x15"
+22="\x00\x00\x00\x16"
+23="\x00\x00\x00\x17"
+24="\x00\x00\x00\x18"
+25="\x00\x00\x00\x19"
+26="\x00\x00\x00\x1A"
+27="\x00\x00\x00\x1B"
+28="\x00\x00\x00\x1C"
+29="\x00\x00\x00\x1D"
+30="\x00\x00\x00\x1E"
+31="\x00\x00\x00\x1F"
+32="\x00\x00\x00\x20"
+33="\x00\x00\x00\x21"
+34="\x00\x00\x00\x22"
+35="\x00\x00\x00\x23"
+36="\x00\x00\x00\x24"
+37="\x00\x00\x00\x25"
+38="\x00\x00\x00\x26"
+39="\x00\x00\x00\x27"
+40="\x00\x00\x00\x28"
+41="\x00\x00\x00\x29"
+42="\x00\x00\x00\x2A"
+43="\x00\x00\x00\x2B"
+44="\x00\x00\x00\x2C"
+45="\x00\x00\x00\x2D"
+46="\x00\x00\x00\x2E"
+47="\x00\x00\x00\x2F"
+48="\x00\x00\x00\x30"
+49="\x00\x00\x00\x31"
+50="\x00\x00\x00\x32"
+51="\x00\x00\x00\x33"
+52="\x00\x00\x00\x34"
+53="\x00\x00\x00\x35"
+54="\x00\x00\x00\x36"
+55="\x00\x00\x00\x37"
+56="\x00\x00\x00\x38"
+57="\x00\x00\x00\x39"
+58="\x00\x00\x00\x3A"
+59="\x00\x00\x00\x3B"
+60="\x00\x00\x00\x3C"
+61="\x00\x00\x00\x3D"
+62="\x00\x00\x00\x3E"
+63="\x00\x00\x00\x3F"
+64="\x00\x00\x00\x40"
+65="\x00\x00\x00\x41"
+66="\x00\x00\x00\x42"
+67="\x00\x00\x00\x43"
+68="\x00\x00\x00\x44"
+69="\x00\x00\x00\x45"
+70="\x00\x00\x00\x46"
+71="\x00\x00\x00\x47"
+72="\x00\x00\x00\x48"
+73="\x00\x00\x00\x49"
+74="\x00\x00\x00\x4A"
+75="\x00\x00\x00\x4B"
+76="\x00\x00\x00\x4C"
+77="\x00\x00\x00\x4D"
+78="\x00\x00\x00\x4E"
+79="\x00\x00\x00\x4F"
+80="\x00\x00\x00\x50"
+81="\x00\x00\x00\x51"
+82="\x00\x00\x00\x52"
+83="\x00\x00\x00\x53"
+84="\x00\x00\x00\x54"
+85="\x00\x00\x00\x55"
+86="\x00\x00\x00\x56"
+87="\x00\x00\x00\x57"
+88="\x00\x00\x00\x58"
+89="\x00\x00\x00\x59"
+90="\x00\x00\x00\x5A"
+91="\x00\x00\x00\x5B"
+92="\x00\x00\x00\x5C"
+93="\x00\x00\x00\x5D"
+94="\x00\x00\x00\x5E"
+95="\x00\x00\x00\x5F"
+96="\x00\x00\x00\x60"
+97="\x00\x00\x00\x61"
+98="\x00\x00\x00\x62"
+99="\x00\x00\x00\x63"
+100="\x00\x00\x00\x64"
+101="\x00\x00\x00\x65"
+102="\x00\x00\x00\x66"
+103="\x00\x00\x00\x67"
+104="\x00\x00\x00\x68"
+105="\x00\x00\x00\x69"
+106="\x00\x00\x00\x6A"
+107="\x00\x00\x00\x6B"
+108="\x00\x00\x00\x6C"
+109="\x00\x00\x00\x6D"
+110="\x00\x00\x00\x6E"
+111="\x00\x00\x00\x6F"
+112="\x00\x00\x00\x70"
+113="\x00\x00\x00\x71"
+114="\x00\x00\x00\x72"
+115="\x00\x00\x00\x73"
+116="\x00\x00\x00\x74"
+117="\x00\x00\x00\x75"
+118="\x00\x00\x00\x76"
+119="\x00\x00\x00\x77"
+120="\x00\x00\x00\x78"
+121="\x00\x00\x00\x79"
+122="\x00\x00\x00\x7A"
+123="\x00\x00\x00\x7B"
+124="\x00\x00\x00\x7C"
+125="\x00\x00\x00\x7D"
+126="\x00\x00\x00\x7E"
+127="\x00\x00\x00\x7F"
+128="\x00\x00\x00\x80"
+129="\x00\x00\x00\x81"
+130="\x00\x00\x00\x82"
+131="\x00\x00\x00\x83"
+132="\x00\x00\x00\x84"
+133="\x00\x00\x00\x85"
+134="\x00\x00\x00\x86"
+135="\x00\x00\x00\x87"
+136="\x00\x00\x00\x88"
+137="\x00\x00\x00\x89"
+138="\x00\x00\x00\x8A"
+139="\x00\x00\x00\x8B"
+140="\x00\x00\x00\x8C"
+141="\x00\x00\x00\x8D"
+142="\x00\x00\x00\x8E"
+143="\x00\x00\x00\x8F"
+144="\x00\x00\x00\x90"
+145="\x00\x00\x00\x91"
+146="\x00\x00\x00\x92"
+147="\x00\x00\x00\x93"
+148="\x00\x00\x00\x94"
+149="\x00\x00\x00\x95"
+150="\x00\x00\x00\x96"
+151="\x00\x00\x00\x97"
+152="\x00\x00\x00\x98"
+153="\x00\x00\x00\x99"
+154="\x00\x00\x00\x9A"
+155="\x00\x00\x00\x9B"
+156="\x00\x00\x00\x9C"
+157="\x00\x00\x00\x9D"
+158="\x00\x00\x00\x9E"
+159="\x00\x00\x00\x9F"
+160="\x00\x00\x00\xA0"
+161="\x00\x00\x00\xA1"
+162="\x00\x00\x00\xA2"
+163="\x00\x00\x00\xA3"
+164="\x00\x00\x00\xA4"
+165="\x00\x00\x00\xA5"
+166="\x00\x00\x00\xA6"
+167="\x00\x00\x00\xA7"
+168="\x00\x00\x00\xA8"
+169="\x00\x00\x00\xA9"
+170="\x00\x00\x00\xAA"
+171="\x00\x00\x00\xAB"
+172="\x00\x00\x00\xAC"
+173="\x00\x00\x00\xAD"
+174="\x00\x00\x00\xAE"
+175="\x00\x00\x00\xAF"
+176="\x00\x00\x00\xB0"
+177="\x00\x00\x00\xB1"
+178="\x00\x00\x00\xB2"
+179="\x00\x00\x00\xB3"
+180="\x00\x00\x00\xB4"
+181="\x00\x00\x00\xB5"
+182="\x00\x00\x00\xB6"
+183="\x00\x00\x00\xB7"
+184="\x00\x00\x00\xB8"
+185="\x00\x00\x00\xB9"
+186="\x00\x00\x00\xBA"
+187="\x00\x00\x00\xBB"
+188="\x00\x00\x00\xBC"
+189="\x00\x00\x00\xBD"
+190="\x00\x00\x00\xBE"
+191="\x00\x00\x00\xBF"
+192="\x00\x00\x00\xC0"
+193="\x00\x00\x00\xC1"
+194="\x00\x00\x00\xC2"
+195="\x00\x00\x00\xC3"
+196="\x00\x00\x00\xC4"
+197="\x00\x00\x00\xC5"
+198="\x00\x00\x00\xC6"
+199="\x00\x00\x00\xC7"
+200="\x00\x00\x00\xC8"
+201="\x00\x00\x00\xC9"
+202="\x00\x00\x00\xCA"
+203="\x00\x00\x00\xCB"
+204="\x00\x00\x00\xCC"
+205="\x00\x00\x00\xCD"
+206="\x00\x00\x00\xCE"
+207="\x00\x00\x00\xCF"
+208="\x00\x00\x00\xD0"
+209="\x00\x00\x00\xD1"
+210="\x00\x00\x00\xD2"
+211="\x00\x00\x00\xD3"
+212="\x00\x00\x00\xD4"
+213="\x00\x00\x00\xD5"
+214="\x00\x00\x00\xD6"
+215="\x00\x00\x00\xD7"
+216="\x00\x00\x00\xD8"
+217="\x00\x00\x00\xD9"
+218="\x00\x00\x00\xDA"
+219="\x00\x00\x00\xDB"
+220="\x00\x00\x00\xDC"
+221="\x00\x00\x00\xDD"
+222="\x00\x00\x00\xDE"
+223="\x00\x00\x00\xDF"
+224="\x00\x00\x00\xE0"
+225="\x00\x00\x00\xE1"
+226="\x00\x00\x00\xE2"
+227="\x00\x00\x00\xE3"
+228="\x00\x00\x00\xE4"
+229="\x00\x00\x00\xE5"
+230="\x00\x00\x00\xE6"
+231="\x00\x00\x00\xE7"
+232="\x00\x00\x00\xE8"
+233="\x00\x00\x00\xE9"
+234="\x00\x00\x00\xEA"
+235="\x00\x00\x00\xEB"
+236="\x00\x00\x00\xEC"
+237="\x00\x00\x00\xED"
+238="\x00\x00\x00\xEE"
+239="\x00\x00\x00\xEF"
+240="\x00\x00\x00\xF0"
+241="\x00\x00\x00\xF1"
+242="\x00\x00\x00\xF2"
+243="\x00\x00\x00\xF3"
+244="\x00\x00\x00\xF4"
+245="\x00\x00\x00\xF5"
+246="\x00\x00\x00\xF6"
+247="\x00\x00\x00\xF7"
+248="\x00\x00\x00\xF8"
+249="\x00\x00\x00\xF9"
+250="\x00\x00\x00\xFA"
+251="\x00\x00\x00\xFB"
+252="\x00\x00\x00\xFC"
+253="\x00\x00\x00\xFD"
+254="\x00\x00\x00\xFE"
+255="\x00\x00\x00\xFF"
diff --git a/Tools/cfbrltty b/Tools/cfbrltty
new file mode 100755
index 0000000..bd9e823
--- /dev/null
+++ b/Tools/cfbrltty
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../prologue.sh"
+set -e
+
+setSourceRoot
+cd "${sourceRoot}"
+
+revisionIdentifier="$(./getrevid .)" || {
+   revisionHeader="${programsSubdirectory}/revision_identifier.auto.h"
+
+   revisionIdentifier="$(sed -e 's/"//g' "${revisionHeader}")" || {
+      revisionIdentifier="$(date +'%Y%m%d-%H%M%S')"
+   }
+}
+
+configureOptions=(
+   --with-execute-root="/brltty/REV-${revisionIdentifier}"
+   --without-libbraille
+   --without-mikropuhe
+   --without-swift
+   --without-theta
+)
+
+./configure --quiet "${configureOptions[@]}" "${@}"
+exit 0
diff --git a/Tools/csv.tcl b/Tools/csv.tcl
new file mode 100644
index 0000000..8def573
--- /dev/null
+++ b/Tools/csv.tcl
@@ -0,0 +1,176 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+proc csvNormalizeRow {columns width} {
+   if {[set count [llength $columns]] > $width} {
+      set columns [lrange $columns 0 $width-1]
+   } elseif {$count < $width} {
+      lvarcat columns [lrepeat [expr {$width - $count}] ""]
+   }
+
+   return $columns
+}
+
+proc csvNormalizeRows {rows width} {
+   set result [list]
+
+   foreach columns $rows {
+      lappend result [csvNormalizeRow $columns $width]
+   }
+
+   return $result
+}
+
+proc csvQuoteText {text} {
+   return "\"[regsub -all {(")} $text {\1\1}]\""
+}
+
+proc csvMakeLine {columns} {
+   return [join [lmap cell $columns {csvQuoteText $cell}] ,]
+}
+
+proc csvMakeTable {rows} {
+   set table ""
+
+   foreach columns $rows {
+      append table [csvMakeLine $columns]
+      append table "\n"
+   }
+
+   return $table
+}
+
+proc csvApplyHeaders {table} {
+   set rows [list]
+   set headers [lindex $table 0]
+
+   foreach columns [lrange $table 1 end] {
+      set row [dict create]
+      set count [llength $columns]
+      set index 0
+
+      foreach header $headers {
+         if {$index == $count} {
+            set column ""
+         } else {
+            set column [lindex $columns $index]
+            incr index
+         }
+
+         dict set row $header $column
+      }
+
+      lappend rows $row
+   }
+
+   return $rows
+}
+
+proc csvParseString {string} {
+   set rows [list]
+   set columns [list]
+   set column ""
+
+   set finishColumn {
+      lappend columns $column
+      set column ""
+   }
+
+   set finishRow {
+      if {([llength $columns] > 0) || ([string length $column] > 0)} {
+         eval $finishColumn
+         lappend rows $columns
+         set columns [list]
+      }
+   }
+
+   set state unquoted
+   set length [string length $string]
+   set index 0
+
+   while {$index < $length} {
+      set character [string index $string $index]
+      set isQuote [string equal $character {"}]
+      incr index
+
+      switch -exact $state {
+         unquoted {
+            if {$isQuote} {
+               set state inQuotes
+               continue
+            }
+
+            switch -exact $character {
+               "," {
+                  eval $finishColumn
+                  continue
+               }
+
+               "\n" {
+                  eval $finishRow
+                  continue
+               }
+
+               "\r" {
+                  eval $finishRow
+                  set state wasCarriageReturn
+                  continue
+               }
+            }
+         }
+
+         inQuotes {
+            if {$isQuote} {
+               set state wasQuote
+               continue
+            }
+         }
+
+         wasQuote {
+            if {!$isQuote} {
+               set state unquoted
+               incr index -1
+               continue
+            }
+
+            set state inQuotes
+         }
+
+         wasCarriageReturn {
+            if {[string equal $character "\n"]} {
+               set state unquoted
+               continue
+            }
+         }
+
+         default {
+            return -code error "unrecognized csv parse state: $state"
+         }
+      }
+
+      append column $character
+   }
+
+   eval $finishRow
+   return $rows
+}
+
+proc csvLoadFile {file} {
+   return [csvApplyHeaders [csvParseString [readFile $file]]]
+}
+
diff --git a/Tools/gendeps b/Tools/gendeps
new file mode 100755
index 0000000..8ccad67
--- /dev/null
+++ b/Tools/gendeps
@@ -0,0 +1,236 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. prologue.tcl]
+cd $sourceRoot
+
+proc getBuildDirectories {{root ""}} {
+   set directories [list]
+
+   if {[string length $root] == 0} {
+      set root .
+      set setPath {
+         set path $name
+      }
+   } else {
+      set setPath {
+         set path [file join $root $name]
+      }
+   }
+
+   if {[file exists [file join $root Makefile.in]]} {
+      lappend directories $root
+   }
+
+   foreach name [readdir $root] {
+      eval $setPath
+      set type [file type $path]
+
+      if {[string equal $type link]} {
+         continue
+      }
+
+      if {[string equal $type directory]} {
+         lvarcat directories [getBuildDirectories $path]
+      }
+   }
+
+   return $directories
+}
+
+proc generatedFile {file} {
+   set name [file tail $file]
+
+   if {[string equal [file extension [file rootname $name]] .auto]} {
+      return 1
+   }
+
+   if {[regexp {\.tab\.c$} $name]} {
+      return 1
+   }
+
+   if {[lcontain {brlapi_constants.h} $name]} {
+      return 1
+   }
+
+   return 0
+}
+
+proc sourceFile {file} {
+   set name [lindex [set components [file split $file]] end]
+   regsub {^forbuild(\.)} $name {config\1} name
+   set path [eval file join [lreplace $components end end "$name.in"]]
+
+   if {[file exists $path]} {
+      return $path
+   }
+
+   return ""
+}
+
+proc dependencyExists {file} {
+   if {[file exists $file]} {
+      return 1
+   }
+
+   if {[string length [set source [sourceFile $file]]] > 0} {
+      if {[file exists $source]} {
+         return 1
+      }
+   }
+
+   return 0
+}
+
+proc getDependencies {file} {
+   upvar #0 dependencies($file) dependencies
+
+   if {![info exists dependencies]} {
+      lappend dependencies $file
+
+      if {[string length [set source [sourceFile $file]]] == 0} {
+         set source $file
+      }
+      set stream [open $source {RDONLY}]
+
+      set includes [list]
+      while {[gets $stream line] >= 0} {
+         if {[regexp {^ *# *include *"([^"]*)"} $line x header]} {
+            lappend includes $header
+         }
+      }
+      close $stream; unset stream
+
+      if {[string equal [set directory [file dirname $file]] .]} {
+         set directory ""
+      }
+
+      foreach include $includes {
+         if {[generatedFile $include]} {
+            if {[string first / $include] < 0} {
+               set include [file join $directory $include]
+            }
+
+            lappend dependencies $include
+         } else {
+            set found 0
+            foreach location [list $directory Programs Headers ""] {
+               if {[dependencyExists [set path [file join $location $include]]]} {
+                  lvarcat dependencies [lindex [intersect3 $dependencies [getDependencies $path]] 2]
+                  set found 1
+                  break
+               }
+            }
+            if {!$found} {
+               writeProgramMessage "missing dependency: $file includes $include"
+            }
+         }
+      }
+   }
+
+   return $dependencies
+}
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions
+
+set sourceExtensions {c cc y}
+set sourceExtensionsGlob "{[join $sourceExtensions ,]}"
+
+foreach buildDirectory [set buildDirectories [getBuildDirectories]] {
+   foreach sourceFile [glob -nocomplain [file join $buildDirectory "*.$sourceExtensionsGlob"]] {
+      regsub {^(\./)+} $sourceFile {} sourceFile
+      if {![generatedFile $sourceFile]} {
+         lappend sourceFiles($buildDirectory) $sourceFile
+         getDependencies $sourceFile
+      }
+   }
+}
+
+set generatedDependencies [list]
+set objectFiles [list]
+set absoluteStream [open absdeps.mk {WRONLY CREAT TRUNC}]
+foreach buildDirectory [lsort $buildDirectories] {
+   set relativeStream [open [file join $buildDirectory reldeps.mk] {WRONLY CREAT TRUNC}]
+
+   if {[info exists sourceFiles($buildDirectory)]} {
+      foreach sourceFile [lsort $sourceFiles($buildDirectory)] {
+         switch -exact -- [file extension $sourceFile] {
+            ".y" {
+               set objectExtension ".tab.c"
+            }
+
+            default {
+               set objectExtension ".\$O"
+            }
+         }
+
+         set objectFile "[file rootname $sourceFile]$objectExtension"
+         set objectName [file tail $objectFile]
+
+         puts $absoluteStream "# Dependencies for $objectFile:"
+         puts $relativeStream "# Dependencies for $objectName:"
+         lappend objectFiles $objectFile
+
+         foreach dependency $dependencies($sourceFile) {
+            if {[set generated [generatedFile $dependency]]} {
+               lappend generatedDependencies $dependency
+            }
+
+            if {$generated || ([string length [sourceFile $dependency]] > 0)} {
+               set tree BLD
+            } else {
+               set tree SRC
+            }
+
+            puts $absoluteStream "\$(BLD_TOP)$objectFile: \$(${tree}_TOP)$dependency"
+            puts -nonewline $relativeStream "$objectName: "
+
+            if {[string equal $buildDirectory [file dirname $dependency]]} {
+               if {![string equal $tree BLD]} {
+                  puts -nonewline $relativeStream "\$(${tree}_DIR)/"
+               }
+               puts $relativeStream [file tail $dependency]
+            } else {
+               puts $relativeStream "\$(${tree}_TOP)$dependency"
+            }
+         }
+
+         puts $absoluteStream "\tcd \$(@D) && \$(MAKE) \$(@F)"
+         puts $absoluteStream ""
+         puts $relativeStream ""
+      }
+   }
+
+   close $relativeStream; unset relativeStream
+}
+
+if {![lempty [set generatedDependencies [lindex [intersect3 [lrmdups $generatedDependencies] $objectFiles] 0]]]} {
+   puts $absoluteStream "# Generated dependencies:"
+   foreach dependency [lsort $generatedDependencies] {
+      puts $absoluteStream "\$(BLD_TOP)$dependency:"
+      puts $absoluteStream "\tcd \$(@D) && \$(MAKE) \$(@F)"
+   }
+   puts $absoluteStream ""
+}
+close $absoluteStream; unset absoluteStream
+
+exit 0
diff --git a/Tools/javacmd b/Tools/javacmd
new file mode 100755
index 0000000..88c5189
--- /dev/null
+++ b/Tools/javacmd
@@ -0,0 +1,120 @@
+#!/usr/bin/env -S java --source 11
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.ArrayList;
+
+/* This is the class that contains the main() method.
+ * Don't be misled by the fact that it seems to have a required name.
+ * It can actually have any name so we chose to give it a descriptive one.
+ * The only rule is that the main() method must be within the first class.
+ */
+public class ScriptImplementation extends ScriptHelper {
+  public static void main (String[] arguments) {
+    var operands = new ScriptOperands(arguments);
+
+    String action = operands.next();
+    if (action == null) syntaxError("action not specified");
+
+    switch (action) {
+      case "jvmpath": {
+        verifyNoMoreParameters(operands);
+
+        String path = getProcessInformation().command().orElse(null);
+        if (path != null) putScriptOutput("%s", path);
+
+        break;
+      }
+
+      default: {
+        syntaxError("unknown action: %s", action);
+      }
+    }
+  }
+}
+
+public class ScriptHelper {
+  public static ProcessHandle.Info getProcessInformation () {
+    return ProcessHandle.current().info();
+  }
+
+  public static String getScriptPath () {
+    String[] arguments = getProcessInformation().arguments().orElse(null);
+    if (arguments == null) return null;
+
+    final int index = 2;
+    if (arguments.length <= index) return null;
+    return arguments[index];
+  }
+
+  public static File getScriptFile () {
+    String path = getScriptPath();
+    if (path == null) return null;
+    return new File(path);
+  }
+
+  public static String getScriptName () {
+    File file = getScriptFile();
+    if (file == null) return null;
+    return file.getName();
+  }
+
+  public static void putScriptMessage (String format, Object... arguments) {
+    String message = String.format(format, arguments);
+    String tag = getScriptName();
+
+    if ((tag != null) && !tag.isEmpty()) {
+      String prefix = tag + ": ";
+      message = prefix + message;
+
+      prefix = " ".repeat(prefix.length());
+      message = message.replaceAll("(\n)", ("$1" + prefix));
+    }
+
+    System.err.println(message);
+  }
+
+  public static void syntaxError (String format, Object... arguments) {
+    putScriptMessage(format, arguments);
+    System.exit(2);
+  }
+
+  public static void putTooManyParameters () {
+    syntaxError("too many parameters");
+  }
+
+  public static void verifyNoMoreParameters (ScriptOperands operands) {
+    if (!operands.isEmpty()) putTooManyParameters();
+  }
+
+  public static void putScriptOutput (String format, Object... arguments) {
+    System.out.println(String.format(format, arguments));
+  }
+}
+
+public class ScriptOperands extends ArrayList<String> {
+  public ScriptOperands (String... operands) {
+    super(Arrays.asList(operands));
+  }
+
+  public String next () {
+    return isEmpty()? null: remove(0);
+  }
+}
diff --git a/Tools/ktbcheck b/Tools/ktbcheck
new file mode 100755
index 0000000..ee51ad7
--- /dev/null
+++ b/Tools/ktbcheck
@@ -0,0 +1,27 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../prologue.sh"
+setBuildRoot
+
+set -e
+cd "${buildRoot}/Programs"
+make --silent brltty-ktb
+./brltty-ktb -T../Tables -D../lib -a "${@}"
+exit 0
diff --git a/Tools/luacmd b/Tools/luacmd
new file mode 100755
index 0000000..1bb0452
--- /dev/null
+++ b/Tools/luacmd
@@ -0,0 +1,22 @@
+#!/bin/bash
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/../brltty-prologue.sh"
+export LUA_PATH="${programDirectory}/../?.lua;;"
+exec lua "${programDirectory}/${programName}.lua" "${@}"
+exit "${?}"
diff --git a/Tools/luacmd.lua b/Tools/luacmd.lua
new file mode 100644
index 0000000..a5abca8
--- /dev/null
+++ b/Tools/luacmd.lua
@@ -0,0 +1,46 @@
+--[[
+  libbrlapi - A library providing access to braille terminals for applications.
+ 
+  Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ 
+  libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ 
+  This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ 
+  Web Page: http://brltty.app/
+ 
+  This software is maintained by Dave Mielke <dave@mielke.cc>.
+]]
+
+require("brltty-prologue")
+
+function showLibraryDirectory ()
+  for number, component in ipairs(splitString(package.cpath, ";")) do
+    local directory, name = component:match("^(.*)/(.-)$")
+    if name ~= "?.so" then goto next end
+
+    if not directory then goto next end
+    if #directory == 0 then goto next end
+
+    if directory:sub(1,1) ~= "/" then goto next end
+    if stringContains(directory, "?") then goto next end
+
+    print(directory)
+    break
+
+  ::next::
+  end
+end
+
+action = nextProgramArgument("action")
+
+if action == "libdir" then
+  showLibraryDirectory()
+else
+  syntaxError(string.format("unknown action: %s", action))
+end
+
+os.exit()
diff --git a/Tools/mkbrltty b/Tools/mkbrltty
new file mode 100755
index 0000000..a43c02b
--- /dev/null
+++ b/Tools/mkbrltty
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../prologue.sh"
+parseProgramArguments "${@}"
+
+set -e
+setBuildRoot
+cd "${buildRoot}"
+
+makeTargets=(
+   brltty braille-drivers speech-drivers screen-drivers
+   brltty-trtxt brltty-ttb brltty-ctb brltty-atb brltty-ktb
+   brltty-tune brltty-morse brltty-pty
+   brltty-lscmds brltty-hid
+   brltty-cldr brltty-lsinc
+   brltest spktest scrtest crctest msgtest
+   all-api-bindings brltty-clip xbrlapi apitest
+)
+
+gmake -s -C "${programsSubdirectory}" "${makeTargets[@]}"
+exit 0
diff --git a/Tools/mkctbchars b/Tools/mkctbchars
new file mode 100755
index 0000000..00ae966
--- /dev/null
+++ b/Tools/mkctbchars
@@ -0,0 +1,91 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] ".." "prologue.tcl"]
+source [file join $scriptDirectory braille.tcl]
+
+set optionDefinitions {
+}
+
+set inputStream stdin
+set outputStream stdout
+processProgramArguments optionValues $optionDefinitions
+
+proc processCharacter {text braille description} {
+   if {[string length $text] == 1} {
+      set codepoint [brlCharacterToCodepoint $text]
+   } else {
+      set codepoint [brlHexadecimalToDecimal $text]
+      set text [brlCodepointToCharacter $codepoint]
+   }
+
+   set numbers ""
+   set cells ""
+
+   foreach character [split $braille ""] {
+      if {[string length $numbers] > 0} {
+         append numbers "-"
+      }
+
+      set dots [brlCharacterToDots $character]
+      append numbers $dots
+      append cells [brlDotsToCharacter $dots]
+   }
+
+   global outputStream
+   puts $outputStream "always\t[brlFormatCodepoint $codepoint]\t$numbers\t# $text $cells\t$description"
+}
+
+proc processInput {} {
+   global inputStream
+
+   while {[gets $inputStream line] >= 0} {
+      set line [string trim $line]
+
+      if {[string length $line] == 0} {
+         continue
+      }
+
+      if {[string index $line 0] == "#"} {
+         continue
+      }
+
+      regsub -all {\s+} $line " " line
+      set description [join [lassign [split $line " "] text braille] " "]
+
+      if {[string length $text] == 0} {
+         set problem "missing textual representation"
+      } elseif {[string length $braille] == 0} {
+         set problem "missing braille representation"
+      } elseif {[string length $description] == 0} {
+         set problem "missing character description"
+      } else {
+         try {
+            processCharacter $text $braille $description
+            continue
+         } trap [list $::brlErrorCategory] {problem} {
+         }
+      }
+
+      writeProgramMessage "$problem: $line"
+   }
+}
+
+processInput
+exit 0
diff --git a/Tools/packages.d/apk.sh b/Tools/packages.d/apk.sh
new file mode 100644
index 0000000..c909a48
--- /dev/null
+++ b/Tools/packages.d/apk.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   apk list --installed | awk '
+      {
+         gsub(/-[0-9].*/, "", $1)
+         print $1
+      }
+   ' | sort -u
+}
+
+installPackages() {
+   apk add --quiet --no-progress -- "${@}"
+}
+
+removePackages() {
+   apk del --quiet --no-progress -- "${@}"
+}
+
+describePackage() {
+   apk info -- "${1}"
+}
+
+whichPackage() {
+   unsupportedPackageAction which
+}
+
+searchPackage() {
+   apk search --description -- "${1}"
+}
+
diff --git a/Tools/packages.d/apt.sh b/Tools/packages.d/apt.sh
new file mode 100644
index 0000000..c6813f8
--- /dev/null
+++ b/Tools/packages.d/apt.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   dpkg-query --show --showformat '${Package}\n'
+}
+
+installPackages() {
+   apt --yes --quiet --quiet --quiet install -- "${@}"
+}
+
+removePackages() {
+   apt --yes --quiet --quiet --quiet remove -- "${@}"
+}
+
+describePackage() {
+   apt-cache show -- "${1}"
+}
+
+whichPackage() {
+   dpkg --search -- "${1}"
+}
+
+searchPackage() {
+   apt-cache search -- '(^|[^[:alpha:]])'"${1}"'($|[^[:alpha:]])'
+}
+
diff --git a/Tools/packages.d/brew.sh b/Tools/packages.d/brew.sh
new file mode 100644
index 0000000..2bc5132
--- /dev/null
+++ b/Tools/packages.d/brew.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   brew list -1 --full-name
+}
+
+installPackages() {
+   brew install "${@}"
+}
+
+removePackages() {
+   brew uninstall "${@}"
+}
+
+describePackage() {
+   brew desc --eval-all "${1}"
+}
+
+whichPackage() {
+   brew which-formula --explain "${1}"
+}
+
+searchPackage() {
+   brew search "${1}"
+}
+
+export HOMEBREW_NO_ENV_HINTS=1
+export HOMEBREW_NO_INSTALL_CLEANUP=1
diff --git a/Tools/packages.d/dnf.sh b/Tools/packages.d/dnf.sh
new file mode 100644
index 0000000..e2020d9
--- /dev/null
+++ b/Tools/packages.d/dnf.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   rpm --query --all --queryformat '%{NAME}\n'
+}
+
+installPackages() {
+   dnf -y --quiet install "${@}"
+}
+
+removePackages() {
+   dnf -y --quiet remove "${@}"
+}
+
+describePackage() {
+   dnf --quiet info "${1}"
+}
+
+whichPackage() {
+   dnf --quiet whatprovides "${1}"
+}
+
+searchPackage() {
+   dnf --quiet search "${1}"
+}
+
diff --git a/Tools/packages.d/pacman.sh b/Tools/packages.d/pacman.sh
new file mode 100644
index 0000000..5efe8f5
--- /dev/null
+++ b/Tools/packages.d/pacman.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   pacman --query --native | awk '{print $1}'
+}
+
+installPackages() {
+   pacman --sync --quiet --noprogressbar --noconfirm -- "${@}"
+}
+
+removePackages() {
+   pacman --remove --noprogressbar --noconfirm -- "${@}"
+}
+
+describePackage() {
+   pacman --sync --info -- "${1}"
+}
+
+whichPackage() {
+   pacman --files -- "${1}"
+}
+
+searchPackage() {
+   pacman --sync --search -- "${1}"
+}
+
diff --git a/Tools/packages.d/pkg.sh b/Tools/packages.d/pkg.sh
new file mode 100644
index 0000000..a06d182
--- /dev/null
+++ b/Tools/packages.d/pkg.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   pkg query --all -- "%n"
+}
+
+installPackages() {
+   pkg install --quiet --yes -- "${@}"
+}
+
+removePackages() {
+   pkg delete --quiet --yes -- "${@}"
+}
+
+describePackage() {
+   pkg rquery -- '%n %v %q\n%c\n%e' "${1}"
+}
+
+whichPackage() {
+   pkg which -- "${1}"
+}
+
+searchPackage() {
+   pkg search --search description --label pkg-name --query-modifier comment -- "${1}"
+}
+
diff --git a/Tools/packages.d/pkg_info.sh b/Tools/packages.d/pkg_info.sh
new file mode 100644
index 0000000..8303c56
--- /dev/null
+++ b/Tools/packages.d/pkg_info.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   pkg_info -q -a | sed -e 's/^\(.*\)-.*$/\1/'
+}
+
+installPackages() {
+   pkg_add -I -x "${@}"
+}
+
+removePackages() {
+   pkg_delete -I -x "${@}"
+}
+
+describePackage() {
+   pkg_info "${1}"
+}
+
+whichPackage() {
+   pkg_info -E "${1}"
+}
+
+searchPackage() {
+   pkg_info -Q -q "${1}"
+}
+
diff --git a/Tools/packages.d/stubs.sh b/Tools/packages.d/stubs.sh
new file mode 100644
index 0000000..8c51886
--- /dev/null
+++ b/Tools/packages.d/stubs.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   unsupportedPackageAction list
+}
+
+installPackages() {
+   unsupportedPackageAction install
+}
+
+removePackages() {
+   unsupportedPackageAction remove
+}
+
+describePackage() {
+   unsupportedPackageAction describe
+}
+
+whichPackage() {
+   unsupportedPackageAction which
+}
+
+searchPackage() {
+   unsupportedPackageAction search
+}
+
diff --git a/Tools/packages.d/xbps.sh b/Tools/packages.d/xbps.sh
new file mode 100644
index 0000000..2e2c018
--- /dev/null
+++ b/Tools/packages.d/xbps.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   xbps-query --list-pkgs | awk '
+      {
+         sub(/-[0-9].*/, "", $2)
+         print $2
+      }
+   '
+}
+
+installPackages() {
+   xbps-install --yes -- "${@}"
+}
+
+removePackages() {
+   xbps-remove --yes -- "${@}"
+}
+
+describePackage() {
+   xbps-query --property pkgver,architecture,homepage,state,short_desc --show "${1}"
+}
+
+whichPackage() {
+   xbps-query --ownedby "${1}"
+}
+
+searchPackage() {
+   xbps-query --search "${1}"
+}
+
diff --git a/Tools/packages.d/zypper.sh b/Tools/packages.d/zypper.sh
new file mode 100644
index 0000000..3c5a17a
--- /dev/null
+++ b/Tools/packages.d/zypper.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+listInstalledPackages() {
+   zypper --quiet packages --installed-only | awk --field-separator "|" '
+      NR > 2 {
+         sub(/^\s*/, "", $3)
+         sub(/\s*$/, "", $3)
+         print $3
+      }
+   ' | sort -u
+}
+
+installPackages() {
+   zypper --quiet install --no-confirm -- "${@}"
+}
+
+removePackages() {
+   zypper --quiet remove --no-confirm -- "${@}"
+}
+
+describePackage() {
+   zypper --quiet info -- "${1}"
+}
+
+whichPackage() {
+   zypper --quiet what-provides -- "${1}"
+}
+
+searchPackage() {
+   zypper --quiet search --sort-by-name --search-descriptions -- "${1}"
+}
+
diff --git a/Tools/packages.sh b/Tools/packages.sh
new file mode 100644
index 0000000..76f329e
--- /dev/null
+++ b/Tools/packages.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+setPackageManager() {
+   local -r commands=(
+      /usr/local/bin/brew
+
+      /sbin/apk
+      /usr/bin/apt
+      /usr/bin/dnf
+      /usr/sbin/pacman
+      /usr/sbin/pkg
+      /usr/sbin/pkg_info
+      /sbin/xbps-install
+      /usr/bin/zypper
+   )
+
+   local command
+   for command in "${commands[@]}"
+   do
+      [ -x "${command}" ] && {
+         packageManager="${command##*/}"
+         packageManager="${packageManager%%-*}"
+         return 0
+      }
+   done
+
+   semanticError "unknown package manager"
+}
+
+normalizePackageList() {
+   sort | uniq
+}
+
+unsupportedPackageAction() {
+   local action="${1}"
+   semanticError "unsupported package action: ${action}"
+}
+
+setPackageManager
+logNote "package manager: ${packageManager}"
+. "${programDirectory}/packages.d/${packageManager}.sh"
diff --git a/Tools/pkg-describe b/Tools/pkg-describe
new file mode 100755
index 0000000..5885671
--- /dev/null
+++ b/Tools/pkg-describe
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+addProgramParameter "package" packageName "the package to describe"
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+describePackage "${packageName}"
+exit 0
diff --git a/Tools/pkg-install b/Tools/pkg-install
new file mode 100755
index 0000000..86ba429
--- /dev/null
+++ b/Tools/pkg-install
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+processExtraProgramParameters() {
+   declare -g additionalPackages=( "${@}" )
+}
+
+addProgramParameter "package" packageName "the package to install"
+optionalProgramParameters "packages" "additional packages to install"
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+installPackages "${packageName}" "${additionalPackages[@]}"
+exit 0
diff --git a/Tools/pkg-list b/Tools/pkg-list
new file mode 100755
index 0000000..5bc8913
--- /dev/null
+++ b/Tools/pkg-list
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+installedPackages=( $(listInstalledPackages) )
+
+for package in "${installedPackages[@]}"
+do
+   echo "${package}"
+done | normalizePackageList
+
+exit 0
diff --git a/Tools/pkg-remove b/Tools/pkg-remove
new file mode 100755
index 0000000..f47d878
--- /dev/null
+++ b/Tools/pkg-remove
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+processExtraProgramParameters() {
+   declare -g additionalPackages=( "${@}" )
+}
+
+addProgramParameter "package" packageName "the package to remove"
+optionalProgramParameters "packages" "additional packages to remove"
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+removePackages "${packageName}" "${additionalPackages[@]}"
+exit 0
diff --git a/Tools/pkg-search b/Tools/pkg-search
new file mode 100755
index 0000000..05cb2e5
--- /dev/null
+++ b/Tools/pkg-search
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+addProgramParameter "string" searchString "the string to search for"
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+searchPackage "${searchString}"
+exit 0
diff --git a/Tools/pkg-which b/Tools/pkg-which
new file mode 100755
index 0000000..719a936
--- /dev/null
+++ b/Tools/pkg-which
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+addProgramParameter "file" filePath "the path of the file to be located"
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+whichPackage "${filePath}"
+exit 0
diff --git a/Tools/pkgvars b/Tools/pkgvars
new file mode 100755
index 0000000..d26750b
--- /dev/null
+++ b/Tools/pkgvars
@@ -0,0 +1,60 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../prologue.sh"
+
+defaultPackageName="brltty"
+variableNames=()
+
+processExtraProgramParameters() {
+   variableNames+=( "${@}" )
+}
+
+addProgramOption b string.directory buildRoot "the root directory of the build"
+addProgramOption p string.name packageName "the name of the package" "${defaultPackageName}"
+optionalProgramParameters
+parseProgramArguments "${@}"
+
+if [ -n "${buildRoot}" ]
+then
+   verifyInputDirectory "${buildRoot}"
+else
+   setBuildRoot
+fi
+
+[ -n "${packageName}" ] || packageName="${defaultPackageName}"
+
+pkgConfig() {
+   pkg-config "${@}" -- "${packageName}" || return "${?}"
+}
+
+export PKG_CONFIG_PATH="${buildRoot}"
+pkgConfig --exists || semanticError "package not found: ${packageName}"
+
+if [ "${#variableNames[*]}" -eq 0 ]
+then
+   pkgConfig --print-variables | sort
+else
+   for variableName in "${variableNames[@]}"
+   do
+      pkgConfig --variable="${variableName}"
+   done
+fi
+
+exit 0
diff --git a/Tools/pythoncmd b/Tools/pythoncmd
new file mode 100755
index 0000000..cd29aa3
--- /dev/null
+++ b/Tools/pythoncmd
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2005-2023 by
+#   Alexis Robert <alexissoft@free.fr>
+#   Samuel Thibault <Samuel.Thibault@ens-lyon.org>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+import sys
+import os
+
+def putProgramMessage(message):
+  sys.stderr.write("%s: %s%s" % (programName, message, os.linesep))
+
+def syntaxError(message):
+  putProgramMessage(message)
+  sys.exit(2)
+
+def nextProgramArgument():
+  return sys.argv.pop(0)
+
+def verifyNoMoreProgramArguments():
+  if (len(sys.argv) > 0):
+    syntaxError("too many parameters")
+
+programPath = nextProgramArgument()
+programName = os.path.basename(programPath)
+
+try:
+  actionName = nextProgramArgument()
+except IndexError:
+  syntaxError("action not specified")
+
+try:
+  import sysconfig
+# putProgramMessage("sysconfig")
+  if hasattr(sysconfig, 'get_default_scheme'):
+    scheme = sysconfig.get_default_scheme()
+  else:
+    scheme = sysconfig._get_default_scheme()
+
+  if scheme == 'posix_local':
+    # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/
+    scheme = 'posix_prefix'
+
+  def getIncludeDirectory():
+    return sysconfig.get_path("include", scheme)
+
+  def getLibraryDirectory():
+    return sysconfig.get_path("stdlib", scheme)
+
+  def getPackageDirectory():
+    return sysconfig.get_path("platlib", scheme)
+
+  def getConfigurationVariable(name):
+    return sysconfig.get_config_var(name)
+
+except ModuleNotFoundError:
+  from distutils import sysconfig
+# putProgramMessage("distutils")
+
+  def getIncludeDirectory():
+    return sysconfig.get_python_inc()
+
+  def getLibraryDirectory():
+    return sysconfig.get_python_lib(0, 1)
+
+  def getPackageDirectory():
+    return sysconfig.get_python_lib(True, False)
+
+  def getConfigurationVariable(name):
+    return sysconfig.get_config_var(name)
+
+class ActionHanlers:
+  def version():
+    verifyNoMoreProgramArguments()
+    print(sys.version_info[0])
+
+  def incdir():
+    verifyNoMoreProgramArguments()
+    print(getIncludeDirectory())
+
+  def libdir():
+    verifyNoMoreProgramArguments()
+    print(getLibraryDirectory())
+
+  def pkgdir():
+    verifyNoMoreProgramArguments()
+    print(getPackageDirectory())
+
+  def libopts():
+    verifyNoMoreProgramArguments()
+    print(getConfigurationVariable("LIBS"))
+
+  def linkopts():
+    verifyNoMoreProgramArguments()
+    print(getConfigurationVariable("LINKFORSHARED"))
+
+actionHandlers = globals()["ActionHanlers"]
+
+try:
+  actionHandler = getattr(actionHandlers, actionName)
+except AttributeError:
+  syntaxError("unknown action: %s" % actionName)
+
+actionHandler()
+sys.exit(0)
diff --git a/Tools/reqmnts.d/freebsd b/Tools/reqmnts.d/freebsd
new file mode 100644
index 0000000..c2a040f
--- /dev/null
+++ b/Tools/reqmnts.d/freebsd
@@ -0,0 +1,2 @@
+fdesc           /dev/fd         fdescfs rw      0       0
+proc            /proc           procfs  rw      0       0
diff --git a/Tools/reqpkgs b/Tools/reqpkgs
new file mode 100755
index 0000000..fbdbf14
--- /dev/null
+++ b/Tools/reqpkgs
@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../brltty-prologue.sh"
+
+processExtraProgramParameters() {
+   requiredPackageNames=( "${@}" )
+}
+
+addProgramOption i flag installNeededPackages "install the needed packages"
+addProgramOption l flag listNeededPackages "list the needed packages"
+optionalProgramParameters
+parseProgramArguments "${@}"
+
+set -e
+. "${programDirectory}/packages.sh"
+
+[ "${#requiredPackageNames[@]}" -gt 0 ] || {
+   loadRequiredPackageNames() {
+      local file="${programDirectory}/${programName}.d/${platformName}"
+      [ -e "${file}" ] || semanticError "platform package list not found: ${file}"
+      [ -f "${file}" ] || semanticError "not a file: ${file}"
+      [ -r "${file}" ] || semanticError "platform package list not readable: ${file}"
+
+      local name
+      while read name
+      do
+         [ -n "${name}" ] || continue
+         [ "${name:0:1}" = "#" ] && continue
+         requiredPackageNames[${#requiredPackageNames[*]}]="${name}"
+      done < <(cat "${file}")
+
+      [ "${#requiredPackageNames[@]}" -gt 0 ] || semanticError "empty package list"
+   }
+
+   case "${packageManager}"
+   in
+      apk)
+         platformName="alpine"
+         ;;
+
+      apt)
+         if [ "$(uname -s)" = "GNU" ]
+         then
+            platformName="hurd"
+         else
+            platformName="debian"
+         fi
+         ;;
+
+      brew)
+         platformName="darwin"
+         ;;
+
+      dnf)
+         platformName="fedora"
+         ;;
+
+      pacman)
+         platformName="arch"
+         ;;
+
+      pkg)
+         platformName="freebsd"
+         ;;
+
+      pkg_info)
+         platformName="openbsd"
+         ;;
+
+      xbps)
+         platformName="void"
+         ;;
+
+      zypper)
+         platformName="opensuse"
+         ;;
+
+      *) semanticError "unknown host platform: ${packageManager}";;
+   esac
+
+   logNote "platform name: ${platformName}"
+   loadRequiredPackageNames
+}
+
+listRequiredPackages() {
+   local name
+
+   for name in "${requiredPackageNames[@]}"
+   do
+      echo "${name}"
+   done
+}
+
+neededPackages=(
+   $(
+      diff <(listInstalledPackages | normalizePackageList) \
+           <(listRequiredPackages | normalizePackageList) |
+      sed -n -e '
+         /^--- /b
+         /^+++ /b
+         s/^+/> /
+	 s/^> //p
+      '
+   )
+)
+
+if [ "${#neededPackages[@]}" -gt 0 ]
+then
+   logMessage task "packages needed: ${#neededPackages[@]}"
+
+   "${listNeededPackages}" && {
+      for package in "${neededPackages[@]}"
+      do
+         echo "${package}"
+      done
+   }
+
+   "${installNeededPackages}" && {
+      installPackages "${neededPackages[@]}"
+   }
+else
+   logMessage task "no needed packages"
+fi
+
+exit 0
diff --git a/Tools/reqpkgs.d/alpine b/Tools/reqpkgs.d/alpine
new file mode 100644
index 0000000..da616a2
--- /dev/null
+++ b/Tools/reqpkgs.d/alpine
@@ -0,0 +1,51 @@
+#espeak-ng
+#espeak-ng-dev
+#festival
+#liblouis-dev
+#linuxdoc-tools
+#speech-dispatcher
+#speech-dispatcher-dev
+alsa-lib-dev
+at-spi2-core-dev
+autoconf
+automake
+bluez-dev
+cldr-emoji-annotation
+cython
+dbus-dev
+doxygen
+emacs
+espeak
+espeak-dev
+expat-dev
+flite
+flite-dev
+gawk
+gcc
+gettext
+gpm-dev
+groff
+icu-dev
+libc-dev
+libcap-dev
+libx11-dev
+libxaw-dev
+libxext-dev
+libxfixes-dev
+libxt-dev
+libxtst-dev
+lua5.4
+lua5.4-dev
+make
+ncurses-dev
+ocaml
+ocaml-findlib
+openjdk11
+pcre2-dev
+pkgconf
+polkit-dev
+py3-docutils
+py3-setuptools
+python3-dev
+tcl
+tcl-dev
diff --git a/Tools/reqpkgs.d/arch b/Tools/reqpkgs.d/arch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tools/reqpkgs.d/arch
diff --git a/Tools/reqpkgs.d/darwin b/Tools/reqpkgs.d/darwin
new file mode 100644
index 0000000..bbf6ccc
--- /dev/null
+++ b/Tools/reqpkgs.d/darwin
@@ -0,0 +1,25 @@
+autoconf
+automake
+bash
+cython
+doxygen
+emacs
+espeak
+expat
+gawk
+gettext
+groff
+liblouis
+libx11
+libxfixes
+libxt
+libxtst
+lua
+ncurses
+ocaml
+ocaml-findlib
+openjdk
+pcre
+pkg-config
+tcl-tk
+xorgproto
diff --git a/Tools/reqpkgs.d/debian b/Tools/reqpkgs.d/debian
new file mode 100644
index 0000000..49aee8c
--- /dev/null
+++ b/Tools/reqpkgs.d/debian
@@ -0,0 +1,51 @@
+autoconf
+automake
+binutils
+coreutils
+cython3
+default-jdk
+doxygen
+emacs
+festival
+flite1-dev
+gawk
+gcc
+gettext
+groff
+libasound2-dev
+libatspi2.0-dev
+libbluetooth-dev
+libc6-dev
+libcap-dev
+libdbus-1-dev
+libespeak-ng-libespeak-dev
+libexpat1-dev
+libgpm-dev
+libicu-dev
+liblouis-dev
+liblua5.4-dev
+libncursesw5-dev
+libpcre3-dev
+libpolkit-gobject-1-dev
+libspeechd-dev
+libsystemd-dev
+libudev-dev
+libx11-dev
+libxaw7-dev
+libxt-dev
+libxtst-dev
+linuxdoc-tools
+lua5.4
+make
+ocaml
+ocaml-findlib
+pkg-config
+python3-all-dev
+python3-docutils
+python3-setuptools
+python-all-dev
+systemd
+tcl-dev
+udev
+unicode-cldr-core
+x11proto-kb-dev
diff --git a/Tools/reqpkgs.d/fedora b/Tools/reqpkgs.d/fedora
new file mode 100644
index 0000000..7776a71
--- /dev/null
+++ b/Tools/reqpkgs.d/fedora
@@ -0,0 +1,56 @@
+#at-spi-devel
+alsa-lib-devel
+at-spi2-core-devel
+autoconf
+automake
+binutils
+bluez-libs-devel
+cldr-emoji-annotation
+coreutils
+dbus-devel
+doxygen
+emacs-devel
+espeak-devel
+espeak-ng-devel
+expat-devel
+festival
+flite-devel
+gawk
+gcc
+gettext
+glib2-devel
+glibc-devel
+gpm-devel
+groff
+java-11-openjdk
+java-11-openjdk-devel
+libcap-devel
+libicu-devel
+liblouis-devel
+libX11-devel
+libXt-devel
+libXtst-devel
+linuxdoc-tools
+lua-devel
+make
+ncurses-devel
+neXtaw-devel
+ocaml
+ocaml-findlib
+pcre2-devel
+pkgconf-pkg-config
+polkit-devel
+python3
+python3-Cython
+python3-devel
+python3-docutils
+python3-setuptools
+rst2txt
+speech-dispatcher-devel
+systemd
+systemd-devel
+systemd-rpm-macros
+systemd-udev
+tcl
+tcl-devel
+xorg-x11-proto-devel
diff --git a/Tools/reqpkgs.d/freebsd b/Tools/reqpkgs.d/freebsd
new file mode 100644
index 0000000..19bcca3
--- /dev/null
+++ b/Tools/reqpkgs.d/freebsd
@@ -0,0 +1,38 @@
+alsa-lib
+at-spi2-core
+autoconf
+automake
+bash
+binutils
+cldr-emoji-annotation
+coreutils
+dbus
+doxygen
+espeak
+expat
+festival
+flite
+gawk
+gcc
+gettext
+gmake
+groff
+icu
+liblouis
+libX11
+libXaw
+libXt
+libXtst
+linuxdoc-tools
+ocaml
+ocaml-findlib
+openjdk11
+pcre2
+pkgconf
+polkit
+python39
+py39-cython
+py39-docutils
+speech-dispatcher
+tcl87
+xorgproto
diff --git a/Tools/reqpkgs.d/hurd b/Tools/reqpkgs.d/hurd
new file mode 100644
index 0000000..1bc1e1e
--- /dev/null
+++ b/Tools/reqpkgs.d/hurd
@@ -0,0 +1,38 @@
+autoconf
+automake
+binutils
+coreutils
+cython3
+doxygen
+festival
+flite1-dev
+gawk
+gcc
+gettext
+groff
+libatspi2.0-dev
+libc0.3-dev
+libdbus-1-dev
+libespeak-ng-libespeak-dev
+libexpat1-dev
+libicu-dev
+liblouis-dev
+libncursesw5-dev
+liboss4-salsa-dev
+libpcre3-dev
+libpolkit-gobject-1-dev
+libspeechd-dev
+libx11-dev
+libxaw7-dev
+libxt-dev
+libxtst-dev
+linuxdoc-tools
+make
+ocaml
+ocaml-findlib
+pkg-config
+python3-all-dev
+python3-docutils
+tcl-dev
+unicode-cldr-core
+x11proto-kb-dev
diff --git a/Tools/reqpkgs.d/openbsd b/Tools/reqpkgs.d/openbsd
new file mode 100644
index 0000000..7763cdb
--- /dev/null
+++ b/Tools/reqpkgs.d/openbsd
@@ -0,0 +1,7 @@
+autoconf
+automake
+bash
+gmake
+libiconv
+python
+tcl
diff --git a/Tools/reqpkgs.d/opensuse b/Tools/reqpkgs.d/opensuse
new file mode 100644
index 0000000..5ba5dc4
--- /dev/null
+++ b/Tools/reqpkgs.d/opensuse
@@ -0,0 +1,58 @@
+#at-spi-devel
+#linuxdoc-tools
+#neXtaw-devel
+#rst2txt
+alsa-devel
+at-spi2-core-devel
+autoconf
+automake
+binutils
+bluez-devel
+cldr-emoji-annotation
+coreutils
+dbus-1-devel
+doxygen
+emacs-el
+espeak
+espeak-devel
+espeak-ng
+espeak-ng-devel
+festival
+gawk
+gcc
+gettext-runtime
+glib2-devel
+glibc-devel
+gpm-devel
+groff
+java-11-openjdk
+java-11-openjdk-devel
+libX11-devel
+libXext-devel
+libXfixes-devel
+libXt-devel
+libXtst-devel
+libcap-devel
+libexpat-devel
+libicu-devel
+liblouis-devel
+libspeechd-devel
+lua54-devel
+make
+ncurses-devel
+ocaml
+ocaml-findlib
+pcre2-devel
+pkgconf-pkg-config
+polkit-devel
+python310
+python310-Cython
+python310-devel
+python310-docutils
+python310-setuptools
+speech-dispatcher
+systemd
+systemd-devel
+tcl
+tcl-devel
+xcb-proto-devel
diff --git a/Tools/reqpkgs.d/void b/Tools/reqpkgs.d/void
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tools/reqpkgs.d/void
diff --git a/Tools/static.fedora b/Tools/static.fedora
new file mode 100644
index 0000000..12fcfcc
--- /dev/null
+++ b/Tools/static.fedora
@@ -0,0 +1,6 @@
+expat-static
+glib2-static
+glibc-static
+gpm-static
+libcap-static
+ncurses-static
diff --git a/Tools/tclcmd b/Tools/tclcmd
new file mode 100755
index 0000000..77b90fb
--- /dev/null
+++ b/Tools/tclcmd
@@ -0,0 +1,53 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] ".." "prologue.tcl"]
+
+proc putLibraryDirectory {} {
+  foreach directory [concat $::tcl_library [lindex [::tcl::pkgconfig get libdir,install] 0] $::auto_path] {
+     set file [file join $directory tclConfig.sh]
+
+     if {[file isfile $file]} {
+        puts $file
+        break
+     }
+  }
+}
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions positionalArguments "\{config\}"
+
+if {![nextElement positionalArguments action]} {
+  syntaxError "action not specified"
+}
+
+switch -exact "$action" {
+  config {
+    noMorePositionalArguments $positionalArguments
+    putLibraryDirectory
+  }
+
+  default {
+    syntaxError "unknown action: $action"
+  }
+}
+
+exit 0
diff --git a/Tools/tcltest b/Tools/tcltest
new file mode 100755
index 0000000..0e3a213
--- /dev/null
+++ b/Tools/tcltest
@@ -0,0 +1,41 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. prologue.tcl]
+
+set optionDefinitions {
+}
+
+processProgramArguments optionValues $optionDefinitions positionalArguments "\[property ...\]"
+
+foreach path [glob -directory $scriptDirectory *.tcl] {
+   source $path
+}
+
+if {[lempty $positionalArguments]} {
+   proc primaryPrompt {} {
+      return "[getProgramName]> "
+   }
+
+   commandloop -prompt1 primaryPrompt
+} else {
+   puts stdout [eval $positionalArguments]
+}
+
+exit 0
diff --git a/Tools/ttytest b/Tools/ttytest
new file mode 100755
index 0000000..31e1d6e
--- /dev/null
+++ b/Tools/ttytest
@@ -0,0 +1,270 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. brltty-prologue.tcl]
+
+set serialProperties [dict create]
+set devicePath "/dev/ttyS0"
+
+proc getSerialPropertyNames {} {
+   global serialProperties
+   return [dict keys $serialProperties]
+}
+
+proc isSerialPropertyDefined {name} {
+   return [lcontain [getSerialPropertyNames] $name]
+}
+
+proc getSerialPropertyLabel {name} {
+   global serialProperties
+   return [dict get $serialProperties $name label]
+}
+
+proc getSerialPropertyChoices {name} {
+   global serialProperties
+   return [dict get $serialProperties $name choices]
+}
+
+proc getSerialPropertyOptions {name} {
+   global serialProperties
+   return [dict get $serialProperties $name options]
+}
+
+proc getSerialPropertyDefault {name} {
+   global serialProperties
+   return [dict get $serialProperties $name default]
+}
+
+proc getSerialPropertySetting {name} {
+   global serialProperties
+   return [dict get $serialProperties $name setting]
+}
+
+proc verifySerialPropertyChoice {name value} {
+   if {![lcontain [getSerialPropertyChoices $name] $value]} {
+      return -code error "unknown serial [getSerialPropertyLabel $name]: $value"
+   }
+}
+
+proc setSerialPropertyField {name field value} {
+   global serialProperties
+   dict set serialProperties $name $field $value
+}
+
+proc defineSerialProperty {name label default args} {
+   if {[isSerialPropertyDefined $name]} {
+      return -code error "serial property already defined: $name"
+   }
+
+   global serialProperties
+   dict set serialProperties $name [dict create label $label choices $args options [dict create]]
+   verifySerialPropertyChoice $name $default
+   setSerialPropertyField $name default $default
+   setSerialPropertyField $name setting $default
+}
+
+proc addSerialOption {option property args} {
+   if {![isSerialPropertyDefined $property]} {
+      return -code error "serial property not defined: $property"
+   }
+
+   global serialProperties
+
+   foreach value $args {
+      verifySerialPropertyChoice $property $value
+   }
+
+   dict set serialProperties $property options $option $args
+}
+
+defineSerialProperty lineMode "mode" local local modem
+defineSerialProperty lineBaud "baud" 38400 50 75 110 134 150 200 300 600 1200 1800 2400 4800 9600 19200 38400 57600 115200 230400 460800 500000 576000 921600 1000000 1152000 1500000 2000000 2500000 3000000 3500000 4000000
+defineSerialProperty flowControl "flow control" no no hardware software
+defineSerialProperty dataParity "parity" none none odd even mark space
+defineSerialProperty dataBits "data bits" 8 5 6 7 8
+defineSerialProperty stopBits "stop bits" 1 1 2
+
+addSerialOption clocal lineMode local
+addSerialOption crtscts flowControl hardware
+addSerialOption ixon flowControl software
+addSerialOption parenb dataParity odd even mark space
+addSerialOption parodd dataParity odd mark
+addSerialOption cmspar dataParity mark space
+addSerialOption cstopb stopBits 2
+
+proc logSerialConfiguration {} {
+   global devicePath
+   set log "serial cofiguration: $devicePath"
+
+   foreach name [getSerialPropertyNames] {
+      append log ", [getSerialPropertySetting $name]"
+      append log " [getSerialPropertyLabel $name]"
+   }
+
+   logMessage task $log
+}
+
+proc configureSerialProperty {name value} {
+   verifySerialPropertyChoice $name $value
+   setSerialPropertyField $name setting $value
+}
+
+proc verifySerialDevice {path} {
+   foreach {test word} {exists "found" readable "readable" writable "writable"} {
+      if {![file $test $path]} {
+         semanticError "device not $word: $path"
+      }
+   }
+
+   if {![string equal [file type $path] characterSpecial]} {
+      semanticError "not a serial device: $path"
+   }
+}
+
+proc configureSerialDevice {{options {-g}}} {
+   global devicePath
+
+   set command [list]
+   lappend command stty -F $devicePath
+   eval lappend command $options
+   logNote "command: [join $command " "]"
+
+   lvarpush command exec
+   lappend command 2>@ stderr
+
+   if {[catch $command response] != 0} {
+      exit 9
+   }
+
+   return $response
+}
+
+proc addSettingUsage {linesVariable label specification default} {
+   upvar 1 $linesVariable lines
+   lappend lines "To set the $label, specify $specification (the default is $default)."
+}
+
+proc getArgumentsUsageSummary {} {
+   set lines [list]
+
+   global devicePath
+   addSettingUsage lines "port" "the absolute path to the corresponding serial device" $devicePath
+
+   foreach name [getSerialPropertyNames] {
+      set label [getSerialPropertyLabel $name]
+      set choices [getSerialPropertyChoices $name]
+      set default [getSerialPropertyDefault $name]
+      addSettingUsage lines $label [formatChoicesPhrase $choices] $default
+   }
+
+   return $lines
+}
+
+proc parseCommandLine {} {
+   set optionDefinitions {
+   }
+
+   processProgramArguments optionValues $optionDefinitions argumentValues "\[setting ...\]" getArgumentsUsageSummary
+   return $argumentValues
+}
+
+proc getRequestedConfiguration {} {
+   set configuration [list raw -echo -echoe -echok -echonl]
+   lappend configuration [getSerialPropertySetting lineBaud]
+   lappend configuration cs[getSerialPropertySetting dataBits]
+
+   foreach property [getSerialPropertyNames] {
+      set setting [getSerialPropertySetting $property]
+      set options [getSerialPropertyOptions $property]
+
+      foreach option [dict keys $options] {
+         if {![lcontain [dict get $options $option] $setting]} {
+            set option "-$option"
+         }
+
+         lappend configuration $option
+      }
+   }
+
+   return $configuration
+}
+
+foreach argument [parseCommandLine] {
+   if {[cequal [file pathtype $argument] absolute]} {
+      set devicePath $argument
+   } else {
+      set found 0
+
+      foreach name [getSerialPropertyNames] {
+         if {[lcontain [getSerialPropertyChoices $name] $argument]} {
+            set found 1
+            configureSerialProperty $name $argument
+            break
+         }
+      }
+
+      if {!$found} {
+         syntaxError "unrecognized serial property: $argument"
+      }
+   }
+}
+
+verifySerialDevice $devicePath
+logSerialConfiguration
+
+set originalConfiguration [configureSerialDevice]
+set requestedConfiguration [getRequestedConfiguration]
+
+try {
+   configureSerialDevice $requestedConfiguration
+
+   try {
+      withChannel deviceChannel [open $devicePath {RDWR NOCTTY NONBLOCK}] {
+         fconfigure $deviceChannel -buffering none -translation binary
+
+         fileevent $deviceChannel readable {
+            foreach byte [split [read $deviceChannel] ""] {
+               scan $byte %c number
+               puts -nonewline stdout [format "%02x " $number]
+            }
+
+            flush stdout
+         }
+
+         fileevent stdin readable {
+            if {[set length [gets stdin line]] == 0} {
+               set exitStatus 0
+            } elseif {$length > 0} {
+               puts -nonewline $deviceChannel [subst -nocommands -novariables $line]
+               flush $deviceChannel
+            } elseif {[eof stdin]} {
+               set exitStatus 0
+            }
+         }
+
+         vwait exitStatus
+      }
+   } trap {POSIX} {problem} {
+      semanticError $message
+   }
+} finally {
+   configureSerialDevice $originalConfiguration
+}
+
+exit $exitStatus
diff --git a/Tools/updcmdref b/Tools/updcmdref
new file mode 100755
index 0000000..1c38cb6
--- /dev/null
+++ b/Tools/updcmdref
@@ -0,0 +1,53 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/../prologue.sh"
+parseProgramArguments "${@}"
+
+setSourceRoot
+set -e
+
+cd "${sourceRoot}/${programsSubdirectory}"
+make --silent brltty-lscmds
+newText="$(./brltty-lscmds)"
+
+cd "${sourceRoot}/${documentsSubdirectory}"
+outputFile="README.CommandReference"
+
+if [ -e "${outputFile}" ]
+then
+   [ -f "${outputFile}" ] || semanticError "not a file: ${outputFile}"
+   [ -r "${outputFile}" ] || semanticError "file not readable: ${outputFile}"
+   oldText="$(cat "${outputFile}")"
+
+   if [ "${oldText}" = "${newText}" ]
+   then
+      logTask "not updated"
+      exit 0
+   fi
+
+   [ -w "${outputFile}" ] || semanticError "file not writable: ${outputFile}"
+   fileVerb="updating"
+else
+   fileVerb="adding"
+fi
+
+logNotice "${fileVerb} file: ${outputFile}"
+echo "${newText}" >"${outputFile}"
+exit 0
diff --git a/Tools/updcsvs b/Tools/updcsvs
new file mode 100755
index 0000000..a508e8f
--- /dev/null
+++ b/Tools/updcsvs
@@ -0,0 +1,109 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. prologue.tcl]
+source [file join $scriptDirectory csv.tcl]
+
+set optionDefinitions {
+   {test      flag "don't update the files"}
+}
+
+processProgramArguments optionValues $optionDefinitions
+cd [file join $sourceRoot $documentsSubdirectory]
+
+array set tableHeaders {
+   braille-driver {code name aliases others}
+   speech-driver  {code name aliases}
+   screen-driver  {code name}
+
+   text-table        {name description}
+   contraction-table {name description}
+   attributes-table  {name description}
+   keyboard-table    {name description}
+}
+
+proc makeLists {file} {
+   set lists [dict create]
+   set pattern {^#(\w+-(?:driver|table))\s+([-\w]+)\s*#\s*(.*?)\s*$}
+
+   forEachLine line $file {
+      if {[regexp $pattern $line x directive operand comment]} {
+         dict lappend lists [string tolower $directive] [linsert [lmap column [split $comment ";"] {string trim $column}] 0 $operand]
+      }
+   }
+
+   return $lists
+}
+
+proc updateTable {lists name} {
+   global tableHeaders
+
+   if {[info exists tableHeaders($name)]} {
+      set headers $tableHeaders($name)
+      set rows [csvNormalizeRows [linsert [dict get $lists $name] 0 $headers] [llength $headers]]
+
+      if {[regexp {^(.*)-driver$} $name x driverType]} {
+         set usageColumn [list usage]
+         set driversDirectory [file join $::sourceRoot $::driversSubdirectory [string totitle $driverType]]
+
+         foreach columns [lrange $rows 1 end] {
+            set driverName [lindex $columns 1]
+            set makeFile [file join $driversDirectory $driverName Makefile.in]
+
+            if {[file exists $makeFile]} {
+               set driverUsage [getMakeFileProperty $makeFile DRIVER_USAGE]
+            } else {
+               set driverUsage ""
+            }
+
+            lappend usageColumn $driverUsage
+         }
+
+         set rows [lmap columns $rows usage $usageColumn {
+            linsert $columns end $usage
+         }]
+      }
+
+      if {[updateFile "$name.csv" [csvMakeTable $rows] $::optionValues(test)]} {
+         return 1
+      }
+   } else {
+      logWarning "table headers not defined: $name"
+   }
+
+   return 0
+}
+
+proc updateTables {lists} {
+   set updated 0
+
+   foreach name [dict keys $lists] {
+      if {[updateTable $lists $name]} {
+         set updated 1
+      }
+   }
+
+   return $updated
+}
+
+if {![updateTables [makeLists "brltty.conf.in"]]} {
+   logMessage task "no files updated"
+}
+
+exit 0
diff --git a/Tools/updfiles b/Tools/updfiles
new file mode 100755
index 0000000..0a725fe
--- /dev/null
+++ b/Tools/updfiles
@@ -0,0 +1,50 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/../prologue.sh"
+setSourceRoot
+cd $sourceRoot
+
+addProgramOption c flag updateCommandReference "update the command reference"
+addProgramOption s flag updateAndroidSettings "update the Android settings resources"
+addProgramOption t flag updateCSVTables "update the CSV tables"
+addProgramOption u flag updateUSBDevices "update the USB device lists"
+parseProgramArguments "${@}"
+
+updateAll=true
+set -- updateAndroidSettings updateCommandReference updateCSVTables updateUSBDevices
+
+for variable
+do
+   "${!variable}" && updateAll=false
+done
+
+"${updateAll}" && {
+   for variable
+   do
+      setVariable "${variable}" true
+   done
+}
+
+"${updateAndroidSettings}" && Android/Tools/mksettings
+"${updateCommandReference}" && Tools/updcmdref
+"${updateCSVTables}" && Tools/updcsvs
+"${updateUSBDevices}" && Tools/updusbdevs
+
+exit 0
diff --git a/Tools/updusbdevs b/Tools/updusbdevs
new file mode 100755
index 0000000..13f57bd
--- /dev/null
+++ b/Tools/updusbdevs
@@ -0,0 +1,747 @@
+#!/usr/bin/env tclsh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] .. prologue.tcl]
+
+proc logSourceWarning {location message} {
+   logWarning "$message: [dict get $location file]\[[dict get $location line]\]"
+}
+
+proc makeDecimalNumber {value} {
+   return [expr {$value + 0}]
+}
+
+proc formatDeviceIdentifier {vendor product} {
+   return [format "%04X:%04X" [makeDecimalNumber $vendor] [makeDecimalNumber $product]]
+}
+
+proc makeGenericDeviceTable {definitions} {
+   set table [dict create]
+
+   foreach definition $definitions {
+      set entry [dict create]
+
+      foreach {index property} {
+         2 vendor
+         3 product
+      } {
+         if {$index < [llength $definition]} {
+            if {[string length [set value [lindex $definition $index]]] > 0} {
+               dict set entry $property $value
+            }
+         }
+      }
+
+      dict set table [formatDeviceIdentifier [lindex $definition 0] [lindex $definition 1]] $entry
+   }
+
+   return $table
+}
+
+proc parseChannelDefinition {channelVariable lines location stringsArray} {
+   upvar 1 $stringsArray strings
+
+   set channel [dict create]
+   set ok 1
+
+   foreach line $lines {
+      if {[regexp {\{\s*/\*\s*(.*?)\s*\*/\s*$} $line x model]} {
+         dict set channel model $model
+         continue
+      }
+
+      foreach property {vendor product parentVendor parentProduct} {
+         set prefix {\.}
+         set suffix {\s*=\s*(\S*?)\s*,}
+         set pattern "${prefix}${property}${suffix}"
+
+         if {[regexp $pattern $line x identifier]} {
+            dict set channel $property $identifier
+         }
+      }
+
+      foreach property {manufacturers products} {
+         set prefix {\.}
+         set suffix {\s*=\s*(\S*?)\s*,}
+         set pattern "${prefix}${property}${suffix}"
+
+         if {[regexp $pattern $line x name]} {
+            dict set channel $property $strings($name)
+         }
+      }
+   }
+
+   foreach property {model vendor product} {
+      if {![dict exists $channel $property]} {
+         logSourceWarning $location "property not defined: $property"
+         set ok 0
+      }
+   }
+
+   if {$ok} {
+      foreach property {vendor product} {
+         if {[string is integer -strict [set value [dict get $channel $property]]]} {
+            dict set channel $property [makeDecimalNumber $value]
+         } else {
+            logSourceWarning $location "$property is not numeric: $value"
+            dict unset channel $property
+            set ok 0
+         }
+      }
+
+      if {$ok} {
+         uplevel 1 [list set $channelVariable $channel]
+         return 1
+      }
+   }
+
+   return 0
+}
+
+proc parseChannelDefinitions {channelsVariable lines location stringsArray} {
+   upvar 1 $channelsVariable channels
+   upvar 1 $stringsArray strings
+
+   set lineNumber [dict get $location line]
+
+   foreach lineText $lines {
+      if {[info exists definition]} {
+         if {[regexp {\}} $lineText]} {
+            if {[parseChannelDefinition channel $definition $location strings]} {
+               lappend channels $channel
+            }
+
+            unset definition
+         } else {
+            lappend definition $lineText
+         }
+      } elseif {[regexp {\{} $lineText]} {
+         set definition [list $lineText]
+         dict set location line $lineNumber
+      }
+
+      incr lineNumber
+   }
+}
+
+proc parseSourceFile {channelsVariable file} {
+   upvar 1 $channelsVariable channels
+
+   if {[catch [list open $file {RDONLY}] stream] == 0} {
+      set location [dict create]
+      dict set location file $file
+      set lineNumber 0
+
+      while {[gets $stream lineText] >= 0} {
+         incr lineNumber
+
+         if {[info exists definitions]} {
+            if {[regexp {^\s*END_USB_CHANNEL_DEFINITIONS} $lineText]} {
+               parseChannelDefinitions channels $definitions $location strings
+               unset definitions
+            } else {
+               lappend definitions $lineText
+            }
+         } elseif {[info exists stringsList]} {
+            if {[regexp {^\s*END_USB_STRING_LIST} $lineText]} {
+               set strings($stringsName) $stringsList
+               unset stringsList
+               unset stringsName
+            } elseif {[regexp {^\s*"(.*)"\s*,} $lineText x string]} {
+               lappend stringsList $string
+            }
+         } elseif {[regexp {^\s*BEGIN_USB_CHANNEL_DEFINITIONS} $lineText]} {
+            set definitions [list]
+            dict set location line [expr {$lineNumber + 1}]
+         } elseif {[regexp {^\s*BEGIN_USB_STRING_LIST\s*\(\s*(.*?)\s*\)} $lineText x stringsName]} {
+            set stringsList [list]
+         }
+      }
+
+      close $stream
+   } else {
+      writeProgramMessage $stream
+   }
+}
+
+proc parseSourceFiles {channelsVariable driverDirectory} {
+   upvar 1 $channelsVariable channels
+
+   foreach file [glob -directory $driverDirectory -nocomplain {*.[ch]}] {
+      parseSourceFile channels $file
+   }
+
+   return $channels
+}
+
+proc parseDriver {directory} {
+   set driver [dict create]
+   dict set driver name [set name [file tail $directory]]
+
+   set channels [list]
+
+   if {[string length [set code [getBrailleDriverCode $name]]] > 0} {
+      dict set driver code $code
+      parseSourceFiles channels $directory
+   } else {
+      logWarning "driver code not defined: $name"
+   }
+
+   dict set driver channels $channels
+   return $driver
+}
+
+proc parseDrivers {} {
+   global sourceRoot
+
+   set drivers [list]
+
+   foreach directory [lsort [glob -directory [file join $sourceRoot Drivers Braille] -types {d r x} -nocomplain *]] {
+      if {[llength [dict get [set driver [parseDriver $directory]] channels]] > 0} {
+         lappend drivers $driver
+      }
+   }
+
+   return $drivers
+}
+
+proc formatDeviceDescription {device} {
+   return "[dict get $device name] \[[dict get $device channel model]\]"
+}
+
+proc makeDeviceTable {drivers} {
+   global genericDevices
+   set deviceTable [dict create]
+   dict set deviceTable vendors [dict create]
+
+   foreach driver $drivers {
+      dict with driver {
+         foreach channel $channels {
+            dict with channel {
+               set identifier [formatDeviceIdentifier $vendor $product]
+               set device [dict create code $code name $name channel $channel]
+
+               if {[dict exists $deviceTable vendors $vendor products $product]} {
+                  set devices [dict get $deviceTable vendors $vendor products $product devices]
+                  logNote "device specified more than once: $identifier: [formatDeviceDescription [lindex $devices 0]] & [formatDeviceDescription $device]"
+               } else {
+                  set devices [list]
+                  dict set deviceTable vendors $vendor products $product generic [dict exists $genericDevices $identifier]
+               }
+
+               lappend devices $device
+               dict set deviceTable vendors $vendor products $product devices $devices
+
+               if {[dict get $deviceTable vendors $vendor products $product generic]} {
+                  logNote "generic device identifier: $identifier: [formatDeviceDescription $device]"
+               }
+            }
+         }
+      }
+   }
+
+   return $deviceTable
+}
+
+proc getSchemes {} {
+   set schemes [list]
+   set length [string length [set prefix makeLines_]]
+
+   foreach command [info procs $prefix*] {
+      lappend schemes [string range $command $length end]
+   }
+
+   return $schemes
+}
+
+proc parseFileArguments {filesArray schemes arguments} {
+   upvar 1 $filesArray files
+
+   foreach argument $arguments {
+      if {[set index [string first : $argument]] < 0} {
+         syntaxError "scheme not specified: $argument"
+      }
+
+      if {[string length [set scheme [string range $argument 0 $index-1]]] == 0} {
+         syntaxError "scheme not specified: $argument"
+      }
+
+      if {![lcontain $schemes $scheme]} {
+         syntaxError "unknown scheme: $scheme"
+      }
+
+      if {[string length [set path [string range $argument $index+1 end]]] == 0} {
+         syntaxError "path not specified: $argument"
+      }
+
+      if {![file exists $path]} {
+         semanticError "file not found: $path"
+      }
+
+      if {![file isfile $path]} {
+         semanticError "not a file: $path"
+      }
+
+      if {![file readable $path]} {
+         semanticError "file not readable: $path"
+      }
+
+      if {![file writable $path]} {
+         semanticError "file not writable: $path"
+      }
+
+      lappend files($scheme) $path
+   }
+}
+
+proc compareFilters {filter1 filter2} {
+   set length1 [llength $filter1]
+   set length2 [llength $filter2]
+
+   if {$length1 > $length2} {
+      return -1
+   }
+
+   if {$length1 < $length2} {
+      return 1
+   }
+
+   return [string compare $filter1 $filter2]
+}
+
+proc xmlMakeComment {comment} {
+   return "<!-- $comment -->"
+}
+
+proc makeComment_android {comment} {
+   return [xmlMakeComment $comment]
+}
+
+proc makeLines_android {vendor product codes descriptions exclude channels} {
+   set lines [list]
+
+   foreach description $descriptions {
+      lappend lines [makeComment_android $description]
+   }
+
+   set line "<usb-device vendor-id=\"$vendor\" product-id=\"$product\" />"
+   if {$exclude} {
+      set line [makeComment_android $line]
+   }
+   lappend lines $line
+
+   return $lines
+}
+
+proc makeComment_c {comment} {
+   return "// $comment"
+}
+
+proc makeLines_c {vendor product codes descriptions exclude channels} {
+   set lines [list]
+
+   foreach description $descriptions {
+      lappend lines [makeComment_c $description]
+   }
+
+   set codes [lmap code $codes {
+      set code "\"$code\""
+   }]
+
+   lappend lines [format "USB_DEVICE_ENTRY(0X%04X, 0X%04X, [join $codes ", "])," $vendor $product]
+   return $lines
+}
+
+proc makeComment_hotplug {comment} {
+   return "# $comment"
+}
+
+proc makeLines_hotplug {vendor product codes descriptions exclude channels} {
+   set lines [list]
+
+   foreach description $descriptions {
+      lappend lines [makeComment_hotplug $description]
+   }
+
+   set line [format "brltty 0x%04x 0x%04x 0x%04x" 3 $vendor $product]
+   if {$exclude} {
+      set line [makeComment_hotplug $line]
+   }
+   lappend lines $line
+
+   return $lines
+}
+
+proc makeComment_inf {comment} {
+   return "; $comment"
+}
+
+proc makeLines_inf {vendor product codes descriptions exclude channels} {
+   set lines [list]
+   set line [format "\"\$1: %s\"=\$2, USB\\VID_%04X&PID_%04X" [join $descriptions ", "] $vendor $product]
+
+   if {$exclude} {
+      set line [makeComment_inf $line]
+   }
+
+   lappend lines $line
+   return $lines
+}
+
+proc makeComment_metainfo {comment} {
+   return [xmlMakeComment $comment]
+}
+
+proc makeLines_metainfo {vendor product codes descriptions exclude channels} {
+   set lines [list]
+
+   foreach description $descriptions {
+      lappend lines [makeComment_metainfo $description]
+   }
+
+   set line [format "<modalias>usb:v%04Xp%04X*</modalias>" $vendor $product]
+   if {$exclude} {
+      set line [makeComment_metainfo $line]
+   }
+   lappend lines $line
+
+   return $lines
+}
+
+proc makeComment_udev {comment} {
+   return "# $comment"
+}
+
+proc makeLines_udev {vendor product codes descriptions exclude channels} {
+   set lines [list]
+
+   foreach description $descriptions {
+      lappend lines [makeComment_udev $description]
+   }
+
+   set baseFilter [list]
+   lappend baseFilter [format "ENV{PRODUCT}==\"%x/%x/*\"" $vendor $product]
+   set baseLength [llength $baseFilter]
+
+   foreach channel $channels {
+      set filter $baseFilter
+
+      foreach {property attribute} {
+         manufacturers manufacturer
+         products      product
+      } {
+         if {[dict exists $channel $property]} {
+            foreach string [dict get $channel $property] {
+               lappend filter "ATTR{$attribute}==\"$string\""
+            }
+         }
+      }
+
+      foreach {property attribute} {
+         parentVendor  idVendor
+         parentProduct idProduct
+      } {
+         if {[dict exists $channel $property]} {
+            set identifier [format "%04x" [dict get $channel $property]]
+            lappend filter "ATTRS{$attribute}==\"$identifier\""
+         }
+      }
+
+      lappend filters($filter) [dict get $channel code]
+   }
+
+   foreach filter [lsort -command compareFilters [array names filters]] {
+      set filterLength [llength $filter]
+      set codes [lsort -unique $filters($filter)]
+
+      set rule $filter
+      lappend rule "ENV{BRLTTY_BRAILLE_DRIVER}=\"[join $codes ,]\""
+      lappend rule "GOTO=\"brltty_usb_run\""
+      set line [join $rule ", "]
+
+      if {$exclude} {
+         set line [makeComment_udev $line]
+      }
+
+      lappend lines $line
+   }
+
+   return $lines
+}
+
+proc makeLines {linesArray schemes deviceTable} {
+   global genericDevices
+   upvar #0 optionValues(nogeneric) noGenericDevices
+   upvar #0 optionValues(onlygeneric) onlyGenericDevices
+   upvar #0 optionValues(keep) keepExcludedDevices
+
+   foreach scheme $schemes {
+      set makeLines makeLines_$scheme
+      set makeComment makeComment_$scheme
+
+      upvar 1 ${linesArray}($scheme) lines
+      set lines [list ""]
+
+      set vendors [dict get $deviceTable vendors]
+
+      foreach vendor [lsort -integer [dict keys $vendors]] {
+         set vendorEntry [dict get $vendors $vendor]
+         set products [dict get $vendorEntry products]
+
+         foreach product [lsort -integer [dict keys $products]] {
+            set productEntry [dict get $products $product]
+
+            set isGeneric [dict get $productEntry generic]
+            set excludeDevice [expr {$isGeneric? $noGenericDevices: $onlyGenericDevices}]
+
+            if {$excludeDevice && !$keepExcludedDevices} {
+               continue
+            }
+
+            set identifier [formatDeviceIdentifier $vendor $product]
+            lappend lines [$makeComment "Device: $identifier"]
+
+            set codes [list]
+            set descriptions [list]
+            set channels [list]
+
+            foreach deviceEntry [dict get $productEntry devices] {
+               lappend codes [set code [dict get $deviceEntry code]]
+               lappend descriptions [set description [formatDeviceDescription $deviceEntry]]
+
+               set channel [dict get $deviceEntry channel]
+               dict set channel code $code
+               dict set channel description $description
+               lappend channels $channel
+            }
+
+            set codes [lsort -unique $codes]
+            set descriptions [lsort $descriptions]
+
+            if {$isGeneric} {
+               lappend lines [$makeComment "Generic Identifier"]
+               set generic [dict get $genericDevices $identifier]
+
+               foreach {property header} {
+                  vendor Vendor
+                  product Product
+               } {
+                  if {[dict exists $generic $property]} {
+                     lappend lines [$makeComment "$header: [dict get $generic $property]"]
+                  }
+               }
+            }
+
+            eval [list lappend lines] [$makeLines $vendor $product $codes $descriptions $excludeDevice $channels]
+            lappend lines ""
+         }
+      }
+   }
+}
+
+proc updateFile {file newLines} {
+   upvar #0 optionValues(test) testMode
+
+   set lines [readLines $file]
+   set markerSuffix "_USB_BRAILLE_DEVICES"
+   set beginMarker "BEGIN$markerSuffix"
+   set endMarker "END$markerSuffix"
+
+   set ranges [list]
+   set range [list]
+   set index 0
+
+   foreach line $lines {
+      set location "$file\[[expr {$index + 1}]\]"
+
+      if {[regexp "\\s${beginMarker}(?:\\s+(.*?)\\s*)?\$" $line x arguments]} {
+         if {[llength $range] > 0} {
+            writeProgramMessage "nested begin marker: $location"
+            return 0
+         }
+
+         lappend range $index [lindex [regexp -inline -- {^\s*} $line] 0] [regexp -inline -all {\S+} $arguments]
+      } elseif {[regexp "\\s${endMarker}(?:\\s+.*)?\$" $line]} {
+         if {[llength $range] == 0} {
+            writeProgramMessage "missing begin marker: $location"
+            return 0
+         }
+
+         lappend range $index
+         lappend ranges $range
+         set range [list]
+      }
+
+      incr index
+   }
+
+   if {[llength $range] > 0} {
+      writeProgramMessage "missing end marker: $file\[[lindex $range 0]\]"
+      return 0
+   }
+
+   if {[llength $ranges] == 0} {
+      writeProgramMessage "no region(s) found: $file"
+      return 0
+   }
+
+   set hasChanged 0
+
+   foreach range [lreverse $ranges] {
+      set first [expr {[lindex $range 0] + 1}]
+      set prefix [lindex $range 1]
+      set argumentCount [llength [set argumentList [lindex $range 2]]]
+      set last [expr {[lindex $range 3] - 1}]
+      set count [expr {$last - $first + 1}]
+
+      set replacement [list]
+      foreach line $newLines {
+         foreach match [lreverse [regexp -inline -all -indices {\$[1-9][0-9]*} $line]] {
+            set from [lindex $match 0]
+            set to [lindex $match 1]
+            set argumentIndex [string range $line $from+1 $to]
+
+            if {[incr argumentIndex -1] < $argumentCount} {
+               set line [string replace $line $from $to [lindex $argumentList $argumentIndex]]
+            }
+         }
+
+         if {[string length $line] > 0} {
+            set line "$prefix$line"
+         }
+
+         lappend replacement "$line"
+      }
+
+      if {!$hasChanged} {
+         if {[llength $newLines] != $count} {
+            set hasChanged 1
+         } else {
+            set index 0
+
+            while {$index < $count} {
+               if {[string compare [lindex $lines $first+$index] [lindex $replacement $index]] != 0} {
+                  set hasChanged 1
+                  break
+               }
+
+               incr index
+            }
+         }
+      }
+
+      if {$hasChanged} {
+         if {$count == 0} {
+            set lines [eval [list linsert $lines $first] $replacement]
+         } else {
+            set lines [eval [list lreplace $lines $first $last] $replacement]
+         }
+      }
+   }
+
+   if {!$hasChanged} {
+      return 0
+   }
+
+   if {$testMode} {
+      writeProgramMessage "test mode - file not updated: $file"
+   } else {
+      logMessage notice "updating file: $file"
+      replaceFile $file "[join $lines \n]\n"
+   }
+
+   return 1
+}
+
+proc updateFiles {filesArray linesArray} {
+   upvar 1 $filesArray files
+   upvar 1 $linesArray lines
+   set updated 0
+
+   foreach scheme [array names files] {
+      foreach file $files($scheme) {
+         if {[updateFile $file $lines($scheme)]} {
+            set updated 1
+         }
+      }
+   }
+
+   return $updated
+}
+
+set genericDevices [makeGenericDeviceTable {
+   {  0X0403 0X6001
+      "Future Technology Devices International, Ltd."
+      "FT232 USB-Serial (UART) IC"
+   }
+
+   {  0X0403 0X6010
+      "Future Technology Devices International, Ltd"
+      "FT2232C/D/H Dual UART/FIFO IC"
+   }
+
+   {  0X10C4 0XEA60
+      "Cygnal Integrated Products, Inc."
+      "CP210x UART Bridge / myAVR mySmartUSB light"
+   }
+
+   {  0X10C4 0XEA80
+      "Cygnal Integrated Products, Inc."
+      "CP210x UART Bridge"
+   }
+
+   {  0X1A86 0X7523
+      "Jiangsu QinHeng, Ltd."
+      "CH341 USB Bridge Controller"
+   }
+}]
+
+set optionDefinitions {
+   {keep          flag "keep commented out rules for excluded devices"}
+   {nogeneric     flag "don't include generic devices"}
+   {onlygeneric   flag "only include generic devices"}
+   {test          flag "don't update the files"}
+}
+
+processProgramArguments optionValues $optionDefinitions positionalArguments "\[scheme:file ...\]"
+
+if {[llength $positionalArguments] == 0} {
+   lappend positionalArguments "android:[file join $sourceRoot Android Gradle app src main res xml usb_devices.xml]"
+   lappend positionalArguments "c:[file join $sourceRoot Programs usb_devices.c]"
+   lappend positionalArguments "hotplug:[file join $sourceRoot Autostart Hotplug brltty.usermap]"
+   lappend positionalArguments "metainfo:[file join $sourceRoot Autostart AppStream org.a11y.brltty.metainfo.xml]"
+
+   foreach file [glob -directory [file join $sourceRoot Windows] -nocomplain "*.inf"] {
+      lappend positionalArguments "inf:$file"
+   }
+}
+
+if {[llength $positionalArguments] == 0} {
+   syntaxError "files not specified"
+}
+
+set schemes [getSchemes]
+parseFileArguments files $schemes $positionalArguments
+makeLines lines [array names files] [makeDeviceTable [parseDrivers]]
+
+if {![updateFiles files lines]} {
+   logMessage task "no files updated"
+}
+
+exit 0
diff --git a/Windows/LibUSB-1.0.symlinks b/Windows/LibUSB-1.0.symlinks
new file mode 100644
index 0000000..024328b
--- /dev/null
+++ b/Windows/LibUSB-1.0.symlinks
@@ -0,0 +1,3 @@
+include/libusbx-1.0          /mingw/include/libusb-1.0
+MinGW32/dll/libusb-1.0.dll   /mingw/bin/
+MinGW32/dll/libusb-1.0.dll.a /mingw/lib/
diff --git a/Windows/LibUSB-Win32.symlinks b/Windows/LibUSB-Win32.symlinks
new file mode 100644
index 0000000..5c3b200
--- /dev/null
+++ b/Windows/LibUSB-Win32.symlinks
@@ -0,0 +1,3 @@
+include/lusb0_usb.h     /mingw/include/usb.h
+bin/x86/libusb0_x86.dll /mingw/bin/libusb0.dll
+lib/gcc/libusb.a        /mingw/lib/
diff --git a/Windows/affinity.patch b/Windows/affinity.patch
new file mode 100644
index 0000000..1528296
--- /dev/null
+++ b/Windows/affinity.patch
@@ -0,0 +1,49 @@
+Index: Programs/usb_libusb-1.0.c
+===================================================================
+--- Programs/usb_libusb-1.0.c.orig
++++ Programs/usb_libusb-1.0.c
+@@ -94,6 +94,19 @@ static int
+ usbGetHandle (UsbDeviceExtension *devx) {
+   if (!devx->handle) {
+     int result;
++    DWORD mask, dummy;
++
++    if (GetProcessAffinityMask(GetCurrentProcess(), &mask, &dummy)) {
++      int i;
++      DWORD newmask = 0;
++      for (i = 0; i < sizeof(DWORD)*8; i++)
++        if (mask & (1<<i)) {
++	  newmask = 1<<i;
++	  break;
++	}
++      if (newmask)
++	SetProcessAffinityMask(GetCurrentProcess(), newmask);
++    }
+ 
+     if ((result = libusb_open(devx->device, &devx->handle)) != LIBUSB_SUCCESS) {
+       usbSetErrno(result, "libusb_open");
+Index: Programs/usb_libusb.c
+===================================================================
+--- Programs/usb_libusb.c.orig
++++ Programs/usb_libusb.c
+@@ -378,6 +378,20 @@ usbFindDevice (UsbDeviceChooser chooser,
+   UsbDevice *device = NULL;
+   int result;
+ 
++    DWORD mask, dummy;
++
++    if (GetProcessAffinityMask(GetCurrentProcess(), &mask, &dummy)) {
++      int i;
++      DWORD newmask = 0;
++      for (i = 0; i < sizeof(DWORD)*8; i++)
++        if (mask & (1<<i)) {
++	  newmask = 1<<i;
++	  break;
++	}
++      if (newmask)
++	SetProcessAffinityMask(GetCurrentProcess(), newmask);
++    }
++
+   {
+     static int initialized = 0;
+     if (!initialized) {
diff --git a/Windows/brltty.cat b/Windows/brltty.cat
new file mode 100644
index 0000000..1d716ef
--- /dev/null
+++ b/Windows/brltty.cat
@@ -0,0 +1 @@
+not yet
diff --git a/Windows/brltty.nsi b/Windows/brltty.nsi
new file mode 100644
index 0000000..0d0e57f
--- /dev/null
+++ b/Windows/brltty.nsi
@@ -0,0 +1,232 @@
+;BRLTTY installer
+;author: James Teh <jamie@jantrid.net>
+;Based on the NSIS Modern User Interface Start Menu Folder Selection Example Script
+;Written by Joost Verburg
+
+;--------------------------------
+;Include Modern UI
+
+	!include "MUI2.nsh"
+	!include "Library.nsh"
+
+;--------------------------------
+;Product Info
+
+	!define PRODUCT "BRLTTY"
+	!ifndef VERSION
+		!error "VERSION is not defined"
+	!endif
+
+ !define MUI_WELCOMEPAGE_TITLE $(msg_WelcomePageTitle)
+
+;--------------------------------
+;General
+
+	;Name and file
+	Name "${PRODUCT}"
+	Caption "${PRODUCT} ${VERSION} Setup"
+	!ifdef OUTFILE
+		OutFile "${OUTFILE}"
+	!else
+		OutFile "brltty-win-${VERSION}.exe"
+	!endif
+
+	!ifndef DISTDIR
+		!define DISTDIR "brltty-win-${VERSION}-${VARIANT}"
+	!endif
+
+	SetCompressor /SOLID LZMA
+	SetOverwrite IfNewer
+
+	;Default installation folder
+	InstallDir "$PROGRAMFILES\${PRODUCT}"
+
+	;Get installation folder from registry if available
+	InstallDirRegKey HKLM "Software\${PRODUCT}" ""
+
+	;Request application privileges for Windows Vista
+	RequestExecutionLevel admin
+
+;--------------------------------
+;Variables
+
+	Var StartMenuFolder
+
+;--------------------------------
+;Interface Settings
+
+	!define MUI_ABORTWARNING
+
+;--------------------------------
+;Pages
+
+	!insertmacro MUI_PAGE_WELCOME
+	!insertmacro MUI_PAGE_LICENSE "${DISTDIR}\LICENSE-LGPL.txt"
+	!insertmacro MUI_PAGE_DIRECTORY
+
+;--------------------------------
+
+	;Start Menu Folder Page Configuration
+	!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
+	!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${PRODUCT}"
+	!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
+
+	!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+
+	!insertmacro MUI_PAGE_INSTFILES
+	!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
+	!insertmacro MUI_PAGE_FINISH
+
+	!insertmacro MUI_UNPAGE_CONFIRM
+	!insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+!define UNINSTALLOG_LOCALIZE ; necessary for localization of messages from the uninstallation log file
+
+;Remember the installer language
+!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
+!define MUI_LANGDLL_REGISTRY_KEY "Software\${PRODUCT}"
+!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
+
+!insertmacro MUI_LANGUAGE "English" ; default language
+!insertmacro MUI_LANGUAGE "French"
+!insertmacro MUI_LANGUAGE "German"
+!insertmacro MUI_LANGUAGE "Spanish"
+!insertmacro MUI_LANGUAGE "SimpChinese"
+!insertmacro MUI_LANGUAGE "TradChinese"
+!insertmacro MUI_LANGUAGE "Japanese"
+;!insertmacro MUI_LANGUAGE "Korean"
+!insertmacro MUI_LANGUAGE "Italian"
+;!insertmacro MUI_LANGUAGE "Dutch"
+;!insertmacro MUI_LANGUAGE "Danish"
+!insertmacro MUI_LANGUAGE "Swedish"
+;!insertmacro MUI_LANGUAGE "Norwegian"
+;!insertmacro MUI_LANGUAGE "NorwegianNynorsk"
+!insertmacro MUI_LANGUAGE "Finnish"
+;!insertmacro MUI_LANGUAGE "Greek"
+!insertmacro MUI_LANGUAGE "Russian"
+!insertmacro MUI_LANGUAGE "Portuguese"
+!insertmacro MUI_LANGUAGE "PortugueseBR"
+!insertmacro MUI_LANGUAGE "Polish"
+;!insertmacro MUI_LANGUAGE "Ukrainian"
+!insertmacro MUI_LANGUAGE "Czech"
+!insertmacro MUI_LANGUAGE "Slovak"
+!insertmacro MUI_LANGUAGE "Croatian"
+;!insertmacro MUI_LANGUAGE "Bulgarian"
+!insertmacro MUI_LANGUAGE "Hungarian"
+!insertmacro MUI_LANGUAGE "Thai"
+;!insertmacro MUI_LANGUAGE "Romanian"
+;!insertmacro MUI_LANGUAGE "Latvian"
+;!insertmacro MUI_LANGUAGE "Macedonian"
+;!insertmacro MUI_LANGUAGE "Estonian"
+;!insertmacro MUI_LANGUAGE "Turkish"
+;!insertmacro MUI_LANGUAGE "Lithuanian"
+;!insertmacro MUI_LANGUAGE "Catalan"
+;!insertmacro MUI_LANGUAGE "Slovenian"
+;!insertmacro MUI_LANGUAGE "Serbian"
+;!insertmacro MUI_LANGUAGE "SerbianLatin"
+;!insertmacro MUI_LANGUAGE "Arabic"
+;!insertmacro MUI_LANGUAGE "Farsi"
+;!insertmacro MUI_LANGUAGE "Hebrew"
+;!insertmacro MUI_LANGUAGE "Indonesian"
+;!insertmacro MUI_LANGUAGE "Mongolian"
+;!insertmacro MUI_LANGUAGE "Luxembourgish"
+;!insertmacro MUI_LANGUAGE "Albanian"
+;!insertmacro MUI_LANGUAGE "Breton"
+;!insertmacro MUI_LANGUAGE "Belarusian"
+;!insertmacro MUI_LANGUAGE "Icelandic"
+;!insertmacro MUI_LANGUAGE "Malay"
+;!insertmacro MUI_LANGUAGE "Bosnian"
+;!insertmacro MUI_LANGUAGE "Kurdish"
+;!insertmacro MUI_LANGUAGE "Irish"
+;!insertmacro MUI_LANGUAGE "Uzbek"
+!insertmacro MUI_LANGUAGE "Galician"
+;--------------------------------
+
+;----------------------------
+; Language strings
+!include "${DISTDIR}\nsistrings.txt"
+
+;--------------------------------
+;Installer Sections
+
+Section "install"
+
+	SetOutPath "$INSTDIR"
+	SetShellVarContext all
+
+	;Stop the BRLTTY service if it is running.
+	ExecWait "$SYSDIR\net.exe stop brlapi"
+
+	File /r /x brltty.conf /x nsistrings.txt "${DISTDIR}\*"
+	# Install the config file separately so we can avoid overwriting it.
+	SetOverwrite off
+	File /oname=etc\brltty.conf "${DISTDIR}\etc\brltty.conf"
+	SetOverwrite IfNewer
+
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\brlapi-0.8.dll" "$SYSDIR\brlapi-0.8.dll" "$SYSDIR"
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\msvcr90.dll" "$SYSDIR\msvcr90.dll" "$SYSDIR"
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\libiconv-2.dll" "$SYSDIR\libiconv-2.dll" "$SYSDIR"
+	!insertmacro BrlttyInstall
+
+	;Store installation folder
+	WriteRegStr HKLM "Software\${PRODUCT}" "" $INSTDIR
+
+	;Create uninstaller
+	WriteUninstaller "$INSTDIR\Uninstall.exe"
+	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}" "UninstallString" '"$INSTDIR\uninstall.exe"'
+	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}" "DisplayName" "${PRODUCT}"
+	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}" "DisplayVersion" "${VERSION}"
+	WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}" "NoModify" "1"
+	WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}" "NoRepair" "1"
+
+	!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+
+		;Create shortcuts
+		CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_brlttycnf).lnk" "$INSTDIR\brlttycnf.exe" -r
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_brlttydebug).lnk" "$INSTDIR\debug-brltty.bat"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_inssrv).lnk" "$INSTDIR\install.bat"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_rmvsrv).lnk" "$INSTDIR\uninstall.bat"
+		CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_uninstall).lnk" "$INSTDIR\Uninstall.exe"
+		!insertmacro BrlttyShortcut
+
+	!insertmacro MUI_STARTMENU_WRITE_END
+
+	;Remove the BRLTTY service if it exists.
+	ExecWait "$INSTDIR\bin\brltty.exe -R"
+	;Install usb inf.
+	ExecWait "$INSTDIR\brlttycnf.exe"
+	!insertmacro BrlttyTweak
+	;Install and start the BRLTTY service
+	ExecWait "$INSTDIR\bin\brltty.exe -I"
+	ExecWait "$SYSDIR\net.exe start brlapi"
+
+SectionEnd
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+
+	SetShellVarContext all
+
+	;Stop and remove the BRLTTY service
+	ExecWait "$SYSDIR\net.exe stop brlapi"
+	ExecWait "$INSTDIR\bin\brltty.exe -R"
+	!insertmacro BrlttyUninstall
+
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\brlapi-0.8.dll"
+
+	RMDir /r "$INSTDIR"
+
+	!insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
+
+	RMDir /r "$SMPROGRAMS\$StartMenuFolder"
+
+	DeleteRegKey HKLM "Software\${PRODUCT}"
+	DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}"
+
+SectionEnd
diff --git a/Windows/brltty_x64.cat b/Windows/brltty_x64.cat
new file mode 100644
index 0000000..1d716ef
--- /dev/null
+++ b/Windows/brltty_x64.cat
@@ -0,0 +1 @@
+not yet
diff --git a/Windows/brlttycnf.ahk b/Windows/brlttycnf.ahk
new file mode 100644
index 0000000..46a238b
--- /dev/null
+++ b/Windows/brlttycnf.ahk
@@ -0,0 +1,256 @@
+Version = 0.5
+
+
+;----------------------------------------------------------------------------------------------------------------------------------------
+; Platform:         Windows XP/Vista
+; Author:           Michel Such
+; AutoHotkey     	Version: 1.0.47
+; Version :			Alpha release 1
+;
+; Script Function:
+;	Facility to configure Braille device and communication port
+;  for Windows version of BRLTTY
+
+#SingleInstance
+#NoTrayIcon
+
+#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
+SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
+SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
+
+CnfFile = %A_ScriptDir%\etc\brltty.conf ; Configuration file for BRLTTY
+Gosub SetLanguage
+
+IfNotExist, %CnfFile%
+{
+  Msgbox, 0, %ScriptName%, %CnfFileNotFoundMsg%
+  ExitApp
+}
+
+Gosub SelectPortAndDisplay
+Return
+
+SelectPortAndDisplay:
+
+  ; Fill a listbox for port selection
+   ComPorts = USB:,BLUETOOTH:|USB:|BLUETOOTH:
+   ComPorts = %ComPorts%|COM1|COM2|COM3|COM4|COM5|COM6|COM7|COM8|COM9
+   ComPorts = %ComPorts%|COM10|COM11|COM12|COM13|COM14|COM15|COM16|
+
+  ; Fill a listbox for terminal selection
+  ; getting data from brltty.conf
+   Loop, read, %CnfFile%
+   {
+     IfInString A_LoopReadLine, #braille-driver
+     {
+       Term := SubStr(A_LoopReadLine, 17)
+       StringReplace, Term, Term, #, -, 1
+       TermList = %TermList%%Term%|
+     }
+   }
+
+  ; Draw and display the window
+   Gui, Font, S12 CDefault, Arial
+   Gui, Add, Text, x6 y6 w800 h160, %HelpText%
+
+   Gui, Add, Text, x6 y166 w400 h20, %SelectBrailleDisplayText%
+   Gui, Add, ListBox, vTerminal x6 y190 w400 h150 hscroll vscroll , %TermList%
+
+   Gui, Add, Text,  x412 y166 w400 h20, %SelectBraillePortText%
+   Gui, Add, ListBox, vComPort x412 y190 w400 h150 hscroll vscroll , %ComPorts%
+   Gui, Add, Button, vSend gValidate x640 y352 w100 h24 +Default, %OKBtn%
+   Gui, Add, Button, vCancel gGuiClose x750 y352 w100 h24, %CancelBtn%
+
+   Gui, Show, AutoSize Center,   %ScriptName%
+
+   ;Use Escape to immediately leave the program
+   Escape::
+        Gosub GuiClose
+   return
+return
+
+
+;--------------------------------------------------------------------------------------------
+; Functions
+;------------------------------------------------------------------------------------------
+
+
+Validate:
+   GuiControlGet, Terminal
+   if Terminal =
+   {
+     Msgbox 0, %ScriptName%, %NeedToChooseAFileMsg%
+     ControlFocus, %SelectBrailleDisplayText%, %ScriptName%
+     Return
+   }
+   GuiControlGet, ComPort
+   if ComPort =
+   {
+     Msgbox 0, %ScriptName%, %NeedToChooseAPortMsg%
+     ControlFocus, %SelectBraillePortText%, %ScriptName%
+     Return
+   }
+
+   SetTimer, ChangeButtonNames, 50
+   Msgbox 1, %ScriptName%, %YouHaveSelectedMsg% %Terminal%`n%ConnectedToPortMsg% %ComPort%
+   IfMsgBox OK
+   {
+     IfInString ComPort, COM
+     {
+       FileAppend braille-device serial:%ComPort%`n, %CnfFile%
+     }
+     Else
+     {
+       FileAppend braille-device %ComPort%`n, %CnfFile%
+     }
+     StringReplace Terminal, Terminal, -, #
+     FileAppend braille-driver %Terminal%`n, %CnfFile%
+
+     ; Restart BRLTTY if the -r parameter was passed
+     If 1 = -r
+     {
+       SplitPath, ComSpec,, SysDir
+       ; Stop BrlAPI and BRLTTY if started
+       RunWait, %SysDir%\net.exe stop BrlApi
+       RunWait, %A_ScriptDir%\bin\brltty.exe -R
+
+       ; Restart BRLTTY and BrlAPI
+       RunWait, %A_ScriptDir%\bin\brltty.exe -I
+       RunWait, %SysDir%\net.exe start BrlApi
+     }
+     Gosub GuiClose
+   }
+   ControlFocus, %SelectBrailleDisplayText%, %ScriptName%
+return
+
+;----------------------------------------------------------------------------------------------------------------------------------------
+
+;End the program
+
+GuiClose:
+   Gui, cancel
+   GuiEscape:
+   ExitApp
+
+;----------------------------------------------------------------------------------------------------------------------------------------
+; Support for different languages
+
+SetLanguage:
+
+; This procedure uses the AutoHotkey A_Language variable
+; which contains the language code of the system the program is running on.
+; To get a list of A_Language values and corresponding languages, goto:
+; http://www.autohotkey.com/docs/misc/Languages.htm
+
+  languageCode_040c = French
+  languageCode_080c = French
+  languageCode_0c0c = French
+  languageCode_100c = French
+  languageCode_140c = French
+  languageCode_180c = French
+
+  languageCode_0416 = Portuguese
+  languageCode_0816 = Portuguese
+
+  the_language := languageCode_%A_Language%  ; Get the name of the system's default language.
+  if the_language =
+  {
+    the_language := "English"
+  }
+  Gosub %the_language%
+Return
+
+
+ChangeButtonNames:
+  IfWinNotExist, %ScriptName%
+ 	return  ; Keep waiting.
+  SetTimer, ChangeButtonNames, off
+  WinActivate
+  ControlSetText, Button1, %OKBtn%
+ControlSetText, Button2, %CancelBtn%
+return
+
+English: ; English translation (default)
+
+  ScriptName =  BRLTTY Configurator %Version%
+  CnfFileNotFoundMsg = File %CnfFile% not found.
+  NeedToChooseAFileMsg = You must select a braille display
+  NeedToChooseAPortMsg = You must select a communication port
+  YouHaveSelectedMsg = You selected terminal
+  ConnectedToPortMsg = attached to port
+
+  HelpText =
+  (
+  Please choose the braille display type and communication port.
+  - If your braille device is USB,
+    * if you have installed your manufacturer's driver, you should rather select its virtual COM port if it provides one,
+      else select "USB:" to use libusb-win32's filter,
+    * if you do not want or can not install your manufacturer's driver, you can select "USB:" here and install
+      libusb-win32's driver.
+  - If your braille device is serial, is connect through a serial to USB converter, or through bluetooth, just select the
+    proper COM port.  Make sure to select the proper braille display as incorrect probing seems to possibly brick some devices.
+  )
+
+  SelectBrailleDisplayText = Select braille display
+  SelectBraillePortText = Select communication port
+
+  OKBtn = &OK
+  CancelBtn = &Cancel
+Return
+
+French: ; French translation
+
+  ScriptName =  Configurateur BRLTTY %Version%
+  CnfFileNotFoundMsg = Le fichier %CnfFile% est introuvable
+  NeedToChooseAFileMsg = Vous devez choisir un afficheur braille
+  NeedToChooseAPortMsg = Vous devez choisir un port de communication
+  YouHaveSelectedMsg = Vous avez choisi l'afficheur
+  ConnectedToPortMsg = connecté au port
+
+  HelpText =
+  (
+  Veuillez choisir le type d'afficheur braille et le port de communication.
+  - Si votre afficheur utilise un port USB,
+    * si vous avez installé le pilote de votre fabriquant, vous devriez choisir son port COM virtuel s'il en fournit un, sinon
+      choisissez "USB:" pour utiliser le filtre libusb-win32,
+    * si vous ne voulez pas ou ne pouvez pas installer le pilote de votre fabriquant, vous pouvez choisir "USB:" ici et
+      installer le pilote libusb-win32.
+  - Si votre afficheur braille utilise un port série, ou est connecté via un adaptateur USB vers série, ou via bluetooth, choisissez simplement
+    le port COM approprié. Assurez-vous de choisir le bon afficheur car un choix incorrect semble parfois planter certains matériels.
+  )
+
+  SelectBrailleDisplayText = Choisissez l'afficheur braille
+  SelectBraillePortText = Choisissez le port de communication
+
+  OKBtn = &Accepter
+  CancelBtn = A&nnuler
+Return
+
+
+Portuguese: ; Portuguese translation
+
+  ScriptName =  Configurador do BRLTTY %Version%
+  CnfFileNotFoundMsg = O ficheiro %CnfFile% não foi encontrado
+  NeedToChooseAFileMsg = Tem que seleccionar uma linha Braille
+  NeedToChooseAPortMsg = Tem que seleccionar uma porta de mcomunicação
+  YouHaveSelectedMsg = Seleccionou o terminal
+  ConnectedToPortMsg = conectado à porta
+
+  HelpText =
+  (
+  Por favor escolha o tipo da linha Braille e a porta de comunicação.
+  - Se o seu dispositivo Braille é USB,
+  * se tem instalado o driver do fabricante, deverá seleccionar a porta de série respectiva se existir, caso contrário
+  	seleccione "USB:" para usar o filtro libusb-win32's.
+  * se não pretende ou não consegue instalar o driver do fabricante, pode seleccionar aqui "USB:" e instalar
+  	o driver libusb-win32's.
+  - Se o seu dispositivo braille trabalha via porta série, seleccione a apropriada. Tenha a certeza que selecciona a linha Braille correcta visto que
+  	utilizar drivers errados poderá danificar alguns dispositivos...
+  )
+
+  SelectBrailleDisplayText = Seleccione a linha Braille
+  SelectBraillePortText = Seleccione a porta de comunicação
+
+  OKBtn = &OK
+  CancelBtn = &Cancelar
+Return
diff --git a/Windows/debug-brltty.bat b/Windows/debug-brltty.bat
new file mode 100755
index 0000000..303fd78
--- /dev/null
+++ b/Windows/debug-brltty.bat
@@ -0,0 +1,8 @@
+@echo off
+
+setlocal
+set programDirectory=%~dp0
+set logLevel=debug,serial,usb,bt,inpkts,outpkts,brldrv,scrdrv
+
+call "%programDirectory%run-brltty" -l "%logLevel%" %*
+exit /B %ERRORLEVEL%
diff --git a/Windows/disable-brlapi.bat b/Windows/disable-brlapi.bat
new file mode 100755
index 0000000..313c5d6
--- /dev/null
+++ b/Windows/disable-brlapi.bat
@@ -0,0 +1,12 @@
+@echo off
+
+setlocal
+set programDirectory=%~dp0
+
+call "%programDirectory%setvars-brlapi"
+net stop %serviceName%
+
+set programName=disable
+call "%programDirectory%run-brltty" -R %*
+
+exit /B 0
diff --git a/Windows/enable-brlapi.bat b/Windows/enable-brlapi.bat
new file mode 100755
index 0000000..bed0a1b
--- /dev/null
+++ b/Windows/enable-brlapi.bat
@@ -0,0 +1,47 @@
+@echo off
+
+setlocal EnableDelayedExpansion
+set programDirectory=%~dp0
+call "%programDirectory%setvars-brlapi"
+
+set serviceState=
+for /F "usebackq tokens=1,4" %%A in (
+   `sc query "%serviceName%"`
+) do (
+   if "%%A" == "STATE" (
+      set serviceState=%%B
+      break
+   )
+)
+
+if "%serviceState%" == "RUNNING" (
+   @echo The %serviceName% service is already installed and running.
+   exit /B 0
+)
+
+if "%serviceState%" == "" (
+   set programName=enable
+   call "%programDirectory%run-brltty" -I %*
+   if !ERRORLEVEL! NEQ 0  exit /B !ERRORLEVEL!
+
+   set programName=service
+   call "%programDirectory%setvars-brltty"
+   set logLevel=info
+
+   for /F "usebackq tokens=1,2,*" %%A in (
+      `reg query "%serviceKey%" /V "%commandValue%"`
+   ) do (
+      if "%%A" == "%commandValue%" (
+         set commandData=%%C -U "!updatableDirectory!" -W "!writableDirectory!" -L "!logFile!" -l "!logLevel!"
+         set commandData=!commandData:"="^""!
+         reg add "%serviceKey%" /F /V "%commandValue%" /D "!commandData!"
+         if !ERRORLEVEL! NEQ 0  exit /B !ERRORLEVEL!
+         break
+      )
+   )
+) else (
+   @echo The %serviceName% service is already installed.
+)
+
+net start "%serviceName%"
+exit /B %ERRORLEVEL%
diff --git a/Windows/kill-brltty.bat b/Windows/kill-brltty.bat
new file mode 100644
index 0000000..0110206
--- /dev/null
+++ b/Windows/kill-brltty.bat
@@ -0,0 +1,17 @@
+@echo off
+
+setlocal EnableDelayedExpansion
+set programDirectory=%~dp0
+call "%programDirectory%setvars-brltty"
+
+if exist "%pidFile%" (
+   set /P pid= < "%pidFile%"
+
+   if "!pid!" NEQ "" (
+      taskkill /PID "!pid!" /F /T
+      exit /B %ERRORLEVEL%
+   )
+)
+
+@echo %packageName% isn't running
+exit /B 1
diff --git a/Windows/libusb-1.0.inf b/Windows/libusb-1.0.inf
new file mode 100644
index 0000000..89048e9
--- /dev/null
+++ b/Windows/libusb-1.0.inf
@@ -0,0 +1,882 @@
+[Strings]
+ProviderName  = "BRLTTY"
+DriverDate    = "12/21/2021"
+DriverVersion = "6.4.0.228"
+ClassName     = "USB Braille Devices"
+ClassGUID     = "{1720fa8e-ed15-4346-b73d-909eb41e89ef}"
+DeviceGUID    = "{ffe1ce84-18a1-4cf9-ac16-df74b7958daa}"
+
+WinUSB_SvcDesc = "WinUSB Driver Service"
+DiskName = "libusb (WinUSB) Device Install Disk"
+
+; ====================== Version ======================
+
+[Version]
+DriverVer = %DriverDate%,%DriverVersion%
+Signature = "$Windows NT$"
+Class = %ClassName%
+ClassGuid = %ClassGUID%
+Provider = %ProviderName%
+CatalogFile = libusb_device.cat
+
+; =================== Class section ===================
+
+; Since the device is not a standard USB device, we define a new class for it.
+[ClassInstall32]
+Addreg = WinUSBDeviceClassReg
+
+[WinUSBDeviceClassReg]
+HKR,,,0,%ClassName%
+; -20 is for the USB icon
+HKR,,Icon,,-20
+
+; =========== Manufacturer/Models sections ============
+
+[Manufacturer]
+%ProviderName% = libusbDevice_WinUSB,NTx86,NTamd64
+
+[libusbDevice_WinUSB.NTx86]
+; BEGIN_USB_BRAILLE_DEVICES LibUSB-1.0 LIBUSB_DEV
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"LibUSB-1.0: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"LibUSB-1.0: DotPad [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"LibUSB-1.0: Hedo [MobilLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"LibUSB-1.0: Hedo [ProfiLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"LibUSB-1.0: Papenmeier [all models]"=LIBUSB_DEV, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"LibUSB-1.0: Baum [Vario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"LibUSB-1.0: Baum [PocketVario (24 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"LibUSB-1.0: Baum [SuperVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"LibUSB-1.0: Baum [SuperVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"LibUSB-1.0: Baum [SuperVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"LibUSB-1.0: Baum [SuperVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"LibUSB-1.0: Baum [VarioPro 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"LibUSB-1.0: Baum [VarioPro 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"LibUSB-1.0: Metec [all models]"=LIBUSB_DEV, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"LibUSB-1.0: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=LIBUSB_DEV, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"LibUSB-1.0: HIMS [Braille Edge and QBrailleXL]"=LIBUSB_DEV, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"LibUSB-1.0: Baum [Orbit Reader 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"LibUSB-1.0: Baum [Orbit Reader 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"LibUSB-1.0: Alva [Satellite (5nn)]"=LIBUSB_DEV, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"LibUSB-1.0: Voyager [all models]"=LIBUSB_DEV, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"LibUSB-1.0: Alva [Voyager Protocol Converter]"=LIBUSB_DEV, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"LibUSB-1.0: Alva [BC624]"=LIBUSB_DEV, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"LibUSB-1.0: Alva [BC640]"=LIBUSB_DEV, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"LibUSB-1.0: Alva [BC680]"=LIBUSB_DEV, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"LibUSB-1.0: FrankAudiodata [B2K84 (before firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"LibUSB-1.0: FrankAudiodata [B2K84 (after firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"LibUSB-1.0: Baum [VarioPro 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"LibUSB-1.0: Baum [EcoVario 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"LibUSB-1.0: Baum [EcoVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"LibUSB-1.0: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"LibUSB-1.0: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"LibUSB-1.0: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"LibUSB-1.0: Baum [VarioConnect 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"LibUSB-1.0: Baum [VarioConnect 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"LibUSB-1.0: Baum [EcoVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"LibUSB-1.0: Baum [EcoVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"LibUSB-1.0: Baum [EcoVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"LibUSB-1.0: Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"LibUSB-1.0: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"LibUSB-1.0: Baum [Pronto! V3 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"LibUSB-1.0: Baum [Pronto! V3 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"LibUSB-1.0: Baum [Pronto! V4 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"LibUSB-1.0: Baum [Pronto! V4 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"LibUSB-1.0: Baum [SuperVario2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"LibUSB-1.0: Baum [PocketVario2 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"LibUSB-1.0: Baum [SuperVario2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"LibUSB-1.0: Baum [SuperVario2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"LibUSB-1.0: Baum [SuperVario2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"LibUSB-1.0: Baum [Brailliant2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"LibUSB-1.0: Baum [Brailliant2 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"LibUSB-1.0: Baum [Brailliant2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"LibUSB-1.0: Baum [Brailliant2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"LibUSB-1.0: Baum [Brailliant2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"LibUSB-1.0: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"LibUSB-1.0: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"LibUSB-1.0: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"LibUSB-1.0: Baum [VarioUltra 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"LibUSB-1.0: Baum [VarioUltra 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"LibUSB-1.0: Baum [VarioUltra 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"LibUSB-1.0: HandyTech [GoHubs chip]"=LIBUSB_DEV, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"LibUSB-1.0: FreedomScientific [Focus 1]"=LIBUSB_DEV, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"LibUSB-1.0: FreedomScientific [PAC Mate]"=LIBUSB_DEV, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"LibUSB-1.0: FreedomScientific [Focus 2]"=LIBUSB_DEV, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"LibUSB-1.0: FreedomScientific [Focus 3+]"=LIBUSB_DEV, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"LibUSB-1.0: BrailleMemo [Pocket], Seika [Braille Display]"=LIBUSB_DEV, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"LibUSB-1.0: Seika [Note Taker]"=LIBUSB_DEV, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"LibUSB-1.0: BrailleMemo [Smart]"=LIBUSB_DEV, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"LibUSB-1.0: Inceptor [all models]"=LIBUSB_DEV, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"LibUSB-1.0: Canute [all models]"=LIBUSB_DEV, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"LibUSB-1.0: Baum [NLS eReader Zoomax (20 cells)]"=LIBUSB_DEV, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"LibUSB-1.0: BrailleNote [HumanWare APEX]"=LIBUSB_DEV, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"LibUSB-1.0: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"LibUSB-1.0: HumanWare [non-Touch models (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"LibUSB-1.0: HumanWare [BrailleNote Touch (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"LibUSB-1.0: HumanWare [Brailliant BI 14 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"LibUSB-1.0: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"LibUSB-1.0: HumanWare [APH Chameleon 20 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"LibUSB-1.0: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"LibUSB-1.0: HumanWare [APH Mantis Q40 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"LibUSB-1.0: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"LibUSB-1.0: HumanWare [Humanware BrailleOne (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"LibUSB-1.0: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"LibUSB-1.0: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"LibUSB-1.0: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"LibUSB-1.0: HumanWare [NLS eReader (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"LibUSB-1.0: HandyTech [USB-HID adapter]"=LIBUSB_DEV, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"LibUSB-1.0: HandyTech [Easy Braille (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"LibUSB-1.0: HandyTech [Active Braille]"=LIBUSB_DEV, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"LibUSB-1.0: HandyTech [Connect Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"LibUSB-1.0: HandyTech [Actilino]"=LIBUSB_DEV, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"LibUSB-1.0: HandyTech [Active Star 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"LibUSB-1.0: HandyTech [Braille Star 40 (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"LibUSB-1.0: HandyTech [Basic Braille 16]"=LIBUSB_DEV, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"LibUSB-1.0: HandyTech [Basic Braille 20]"=LIBUSB_DEV, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"LibUSB-1.0: HandyTech [Basic Braille 32]"=LIBUSB_DEV, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"LibUSB-1.0: HandyTech [Basic Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"LibUSB-1.0: HandyTech [Basic Braille 64]"=LIBUSB_DEV, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"LibUSB-1.0: HandyTech [Basic Braille 80]"=LIBUSB_DEV, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"LibUSB-1.0: HandyTech [Basic Braille 48]"=LIBUSB_DEV, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"LibUSB-1.0: HandyTech [Basic Braille 160]"=LIBUSB_DEV, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"LibUSB-1.0: HandyTech [Activator]"=LIBUSB_DEV, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"LibUSB-1.0: Pegasus [all models]"=LIBUSB_DEV, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"LibUSB-1.0: EuroBraille [Esys (version < 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"LibUSB-1.0: EuroBraille [Esys (version < 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"LibUSB-1.0: EuroBraille [Esys (version >= 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"LibUSB-1.0: EuroBraille [Esys (version >= 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"LibUSB-1.0: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=LIBUSB_DEV, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"LibUSB-1.0: Microsoft Narrator"=LIBUSB_DEV, USB\NarratorOverride
+
+[libusbDevice_WinUSB.NTamd64]
+; BEGIN_USB_BRAILLE_DEVICES LibUSB-1.0 LIBUSB_DEV
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"LibUSB-1.0: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"LibUSB-1.0: DotPad [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"LibUSB-1.0: Hedo [MobilLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"LibUSB-1.0: Hedo [ProfiLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"LibUSB-1.0: Papenmeier [all models]"=LIBUSB_DEV, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"LibUSB-1.0: Baum [Vario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"LibUSB-1.0: Baum [PocketVario (24 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"LibUSB-1.0: Baum [SuperVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"LibUSB-1.0: Baum [SuperVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"LibUSB-1.0: Baum [SuperVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"LibUSB-1.0: Baum [SuperVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"LibUSB-1.0: Baum [VarioPro 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"LibUSB-1.0: Baum [VarioPro 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"LibUSB-1.0: Metec [all models]"=LIBUSB_DEV, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"LibUSB-1.0: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=LIBUSB_DEV, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"LibUSB-1.0: HIMS [Braille Edge and QBrailleXL]"=LIBUSB_DEV, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"LibUSB-1.0: Baum [Orbit Reader 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"LibUSB-1.0: Baum [Orbit Reader 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"LibUSB-1.0: Alva [Satellite (5nn)]"=LIBUSB_DEV, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"LibUSB-1.0: Voyager [all models]"=LIBUSB_DEV, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"LibUSB-1.0: Alva [Voyager Protocol Converter]"=LIBUSB_DEV, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"LibUSB-1.0: Alva [BC624]"=LIBUSB_DEV, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"LibUSB-1.0: Alva [BC640]"=LIBUSB_DEV, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"LibUSB-1.0: Alva [BC680]"=LIBUSB_DEV, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"LibUSB-1.0: FrankAudiodata [B2K84 (before firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"LibUSB-1.0: FrankAudiodata [B2K84 (after firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"LibUSB-1.0: Baum [VarioPro 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"LibUSB-1.0: Baum [EcoVario 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"LibUSB-1.0: Baum [EcoVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"LibUSB-1.0: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"LibUSB-1.0: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"LibUSB-1.0: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"LibUSB-1.0: Baum [VarioConnect 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"LibUSB-1.0: Baum [VarioConnect 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"LibUSB-1.0: Baum [EcoVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"LibUSB-1.0: Baum [EcoVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"LibUSB-1.0: Baum [EcoVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"LibUSB-1.0: Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"LibUSB-1.0: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"LibUSB-1.0: Baum [Pronto! V3 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"LibUSB-1.0: Baum [Pronto! V3 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"LibUSB-1.0: Baum [Pronto! V4 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"LibUSB-1.0: Baum [Pronto! V4 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"LibUSB-1.0: Baum [SuperVario2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"LibUSB-1.0: Baum [PocketVario2 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"LibUSB-1.0: Baum [SuperVario2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"LibUSB-1.0: Baum [SuperVario2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"LibUSB-1.0: Baum [SuperVario2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"LibUSB-1.0: Baum [Brailliant2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"LibUSB-1.0: Baum [Brailliant2 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"LibUSB-1.0: Baum [Brailliant2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"LibUSB-1.0: Baum [Brailliant2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"LibUSB-1.0: Baum [Brailliant2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"LibUSB-1.0: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"LibUSB-1.0: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"LibUSB-1.0: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"LibUSB-1.0: Baum [VarioUltra 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"LibUSB-1.0: Baum [VarioUltra 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"LibUSB-1.0: Baum [VarioUltra 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"LibUSB-1.0: HandyTech [GoHubs chip]"=LIBUSB_DEV, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"LibUSB-1.0: FreedomScientific [Focus 1]"=LIBUSB_DEV, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"LibUSB-1.0: FreedomScientific [PAC Mate]"=LIBUSB_DEV, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"LibUSB-1.0: FreedomScientific [Focus 2]"=LIBUSB_DEV, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"LibUSB-1.0: FreedomScientific [Focus 3+]"=LIBUSB_DEV, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"LibUSB-1.0: BrailleMemo [Pocket], Seika [Braille Display]"=LIBUSB_DEV, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"LibUSB-1.0: Seika [Note Taker]"=LIBUSB_DEV, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"LibUSB-1.0: BrailleMemo [Smart]"=LIBUSB_DEV, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"LibUSB-1.0: Inceptor [all models]"=LIBUSB_DEV, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"LibUSB-1.0: Canute [all models]"=LIBUSB_DEV, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"LibUSB-1.0: Baum [NLS eReader Zoomax (20 cells)]"=LIBUSB_DEV, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"LibUSB-1.0: BrailleNote [HumanWare APEX]"=LIBUSB_DEV, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"LibUSB-1.0: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"LibUSB-1.0: HumanWare [non-Touch models (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"LibUSB-1.0: HumanWare [BrailleNote Touch (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"LibUSB-1.0: HumanWare [Brailliant BI 14 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"LibUSB-1.0: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"LibUSB-1.0: HumanWare [APH Chameleon 20 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"LibUSB-1.0: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"LibUSB-1.0: HumanWare [APH Mantis Q40 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"LibUSB-1.0: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"LibUSB-1.0: HumanWare [Humanware BrailleOne (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"LibUSB-1.0: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"LibUSB-1.0: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"LibUSB-1.0: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"LibUSB-1.0: HumanWare [NLS eReader (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"LibUSB-1.0: HandyTech [USB-HID adapter]"=LIBUSB_DEV, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"LibUSB-1.0: HandyTech [Easy Braille (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"LibUSB-1.0: HandyTech [Active Braille]"=LIBUSB_DEV, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"LibUSB-1.0: HandyTech [Connect Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"LibUSB-1.0: HandyTech [Actilino]"=LIBUSB_DEV, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"LibUSB-1.0: HandyTech [Active Star 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"LibUSB-1.0: HandyTech [Braille Star 40 (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"LibUSB-1.0: HandyTech [Basic Braille 16]"=LIBUSB_DEV, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"LibUSB-1.0: HandyTech [Basic Braille 20]"=LIBUSB_DEV, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"LibUSB-1.0: HandyTech [Basic Braille 32]"=LIBUSB_DEV, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"LibUSB-1.0: HandyTech [Basic Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"LibUSB-1.0: HandyTech [Basic Braille 64]"=LIBUSB_DEV, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"LibUSB-1.0: HandyTech [Basic Braille 80]"=LIBUSB_DEV, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"LibUSB-1.0: HandyTech [Basic Braille 48]"=LIBUSB_DEV, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"LibUSB-1.0: HandyTech [Basic Braille 160]"=LIBUSB_DEV, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"LibUSB-1.0: HandyTech [Activator]"=LIBUSB_DEV, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"LibUSB-1.0: Pegasus [all models]"=LIBUSB_DEV, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"LibUSB-1.0: EuroBraille [Esys (version < 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"LibUSB-1.0: EuroBraille [Esys (version < 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"LibUSB-1.0: EuroBraille [Esys (version >= 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"LibUSB-1.0: EuroBraille [Esys (version >= 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"LibUSB-1.0: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=LIBUSB_DEV, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"LibUSB-1.0: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"LibUSB-1.0: Microsoft Narrator"=LIBUSB_DEV, USB\NarratorOverride
+
+; ==================== Installation ===================
+
+; The Include and Needs directives in the LIBUSB_DEV section are required for 
+; installing WinUSB on Windows Vista systems. Windows XP systems ignore these 
+; directives. These directives should not be modified.
+[LIBUSB_DEV]
+Include=winusb.inf
+Needs=WINUSB.NT
+
+; The Include directive in the LIBUSB_DEV.Services section includes the system-
+; supplied INF for WinUSB. This INF is installed by the WinUSB co-installer if 
+; it is not already on the target system. The AddService directive specifies 
+; WinUsb.sys as the device's function driver. These directives should not be 
+; modified.
+[LIBUSB_DEV.Services]
+Include=winusb.inf
+AddService=WinUSB,0x00000002,WinUSB_ServiceInstall
+
+; The WinUSB_ServiceInstall section contains the data for installing WinUsb.sys 
+; as a service. This section should not be modified.
+[WinUSB_ServiceInstall]
+DisplayName     = %WinUSB_SvcDesc%
+ServiceType     = 1
+StartType       = 3
+ErrorControl    = 1
+ServiceBinary   = %12%\WinUSB.sys
+
+; The KmdfService directive installs WinUsb.sys as a kernel-mode service. The 
+; referenced WinLIBUSB_DEV section specifies the KMDF library version. 
+; Usually, the version can be derived from the WdfCoInstallerxxyyy.dll with 
+; xx = major, yyy = minor
+[LIBUSB_DEV.Wdf]
+KmdfService=WINUSB, WinLIBUSB_DEV
+
+[WinLIBUSB_DEV]
+KmdfLibraryVersion=1.9
+
+; LIBUSB_DEV.HW is the key section in the INF. It specifies the device 
+; interface globally unique identifier (GUID) for your device. The AddReg 
+; directive puts the interface GUID in a standard registry value. When 
+; WinUsb.sys is loaded as the device's function driver, it reads the registry
+; value and uses the specified GUID to represent the device interface. You 
+; should replace the GUID in this example with one that you create specifically
+; for your device. If the protocols for the device change, you should create a
+; new device interface GUID.
+[LIBUSB_DEV.HW]
+AddReg=Dev_AddReg
+
+[Dev_AddReg]
+HKR,,DeviceInterfaceGUIDs,0x10000,%DeviceGUID%
+
+; The LIBUSB_DEV.CoInstallers section, including the referenced AddReg and 
+; CopyFiles sections, contains data and instructions to install the WinUSB and 
+; KMDF co installers and associate them with the device. Most USB devices can 
+; use these sections and directives without modification.
+[LIBUSB_DEV.CoInstallers]
+AddReg=CoInstallers_AddReg
+CopyFiles=CoInstallers_CopyFiles
+
+[CoInstallers_AddReg]
+HKR,,CoInstallers32,0x00010000,"WdfCoInstaller01009.dll,WdfCoInstaller","WinUSBCoInstaller2.dll"
+
+[CoInstallers_CopyFiles]
+WinUSBCoInstaller2.dll
+WdfCoInstaller01009.dll
+
+[DestinationDirs]
+CoInstallers_CopyFiles=11
+
+; =============== Source Media Section ================
+ 
+; The x86 and x64 versions of Windows have separate co installers. This example 
+; stores them on the installation disk in folders that are named x86 and amd64
+[SourceDisksNames]
+1 = %DiskName%,,,\x86
+2 = %DiskName%,,,\amd64
+
+[SourceDisksFiles.x86]
+WinUSBCoInstaller2.dll=1
+WdfCoInstaller01009.dll=1
+
+[SourceDisksFiles.amd64]
+WinUSBCoInstaller2.dll=2
+WdfCoInstaller01009.dll=2
+
diff --git a/Windows/libusb-1.0.nsi b/Windows/libusb-1.0.nsi
new file mode 100644
index 0000000..c4c1702
--- /dev/null
+++ b/Windows/libusb-1.0.nsi
@@ -0,0 +1,25 @@
+!define VARIANT "libusb-1.0"
+
+!macro BrlttyInstall
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\libusb-1.0.dll" "$SYSDIR\libusb-1.0.dll" "$SYSDIR"
+!macroend
+
+!macro BrlttyShortcut
+!macroend
+
+!macro BrlttyTweak
+!define sysSetupCopyOEMInf "setupapi::SetupCopyOEMInf(t, t, i, i, i, i, *i, t) i"
+	System::Get '${sysSetupCopyOEMInf}'
+	Pop $0
+	StrCmp $0 'error' nodriver
+	MessageBox MB_YESNO|MB_DEFBUTTON2 $(msg_inslibusb) IDNO nodriver
+	System::Call '${sysSetupCopyOEMInf}?e ("$INSTDIR\bin\brltty-libusb-1.0.inf", "$INSTDIR\bin", 1, 0, 0, 0, 0, 0) .r0'
+nodriver:
+!macroend
+
+!macro BrlttyUninstall
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\WdfCoInstaller01009.dll"
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\winusbcoinstaller2.dll"
+!macroend
+
+!include brltty.nsi
diff --git a/Windows/libusb.inf b/Windows/libusb.inf
new file mode 100644
index 0000000..486a535
--- /dev/null
+++ b/Windows/libusb.inf
@@ -0,0 +1,872 @@
+[Strings]
+ProviderName  = "BRLTTY"
+DriverDate    = "12/21/2021"
+DriverVersion = "6.4.0.228"
+ClassName     = "USB Braille Devices"
+ClassGUID     = "{1720fa8e-ed15-4346-b73d-909eb41e89ef}"
+
+[Version]
+Signature = "$Chicago$"
+provider  = %ProviderName%
+DriverVer = %DriverDate%,%DriverVersion%
+CatalogFile = brltty.cat
+CatalogFile.NT = brltty.cat
+CatalogFile.NTamd64 = brltty_x64.cat
+
+Class     = %ClassName%
+ClassGUID = %ClassGUID%
+
+[ClassInstall32]
+AddReg=libusb_class_install_add_reg
+
+[libusb_class_install_add_reg]
+HKR,,,,"LibUSB-Win32 Devices"
+HKR,,Icon,,"-20"
+
+[Manufacturer]
+%ProviderName%=Devices,NTx86,NTamd64
+
+;--------------------------------------------------------------------------
+; Files
+;--------------------------------------------------------------------------
+
+[SourceDisksNames]
+1 = "Libusb-Win32 Driver Installation Disk",,
+
+[SourceDisksFiles]
+libusb0.sys = 1,,
+libusb0.dll = 1,,
+libusb0_x64.sys = 1,,
+libusb0_x64.dll = 1,,
+
+[DestinationDirs]
+libusb_files_sys = 10,system32\drivers
+libusb_files_sys_x64 = 10,system32\drivers
+libusb_files_dll = 10,system32
+libusb_files_dll_wow64 = 10,syswow64
+libusb_files_dll_x64 = 10,system32
+
+[libusb_files_sys]
+libusb0.sys
+
+[libusb_files_sys_x64]
+libusb0_x64.sys
+
+[libusb_files_dll]
+libusb0.dll
+
+[libusb_files_dll_wow64]
+libusb0.dll
+
+[libusb_files_dll_x64]
+libusb0_x64.dll
+
+;--------------------------------------------------------------------------
+; Device driver
+;--------------------------------------------------------------------------
+
+[LIBUSB_DEV.NT]
+CopyFiles = libusb_files_sys, libusb_files_dll
+
+[LIBUSB_DEV.NTamd64]
+CopyFiles = libusb_files_sys_x64, libusb_files_dll_wow64, libusb_files_dll_x64
+
+[LIBUSB_DEV.NT.HW]
+AddReg = libusb_add_reg_hw
+
+[LIBUSB_DEV.NTamd64.HW]
+AddReg = libusb_add_reg_hw
+
+[LIBUSB_DEV.NT.Services]
+AddService = libusb0, 0x00000002, libusb_add_service
+
+[LIBUSB_DEV.NTamd64.Services]
+AddService = libusb0, 0x00000002, libusb_add_service_amd64
+
+; Device properties
+[libusb_add_reg_hw]
+HKR,,SurpriseRemovalOK, 0x00010001, 1
+
+;--------------------------------------------------------------------------
+; Services
+;--------------------------------------------------------------------------
+
+[libusb_add_service]
+DisplayName    = "LibUsb-Win32 - Kernel Driver 03/31/2007, 0.1.12.1"
+ServiceType    = 1
+StartType      = 3
+ErrorControl   = 0
+ServiceBinary  = %12%\libusb0.sys
+
+[libusb_add_service_amd64]
+DisplayName    = "LibUsb-Win32 - Kernel Driver 03/31/2007, 0.1.12.1"
+ServiceType    = 1
+StartType      = 3
+ErrorControl   = 0
+ServiceBinary  = %12%\libusb0_x64.sys
+
+
+;--------------------------------------------------------------------------
+; Devices
+;--------------------------------------------------------------------------
+
+[Devices.NTx86]
+; BEGIN_USB_BRAILLE_DEVICES LibUSB-Win32 LIBUSB_DEV
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"LibUSB-Win32: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"LibUSB-Win32: DotPad [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"LibUSB-Win32: Hedo [MobilLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"LibUSB-Win32: Hedo [ProfiLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"LibUSB-Win32: Papenmeier [all models]"=LIBUSB_DEV, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"LibUSB-Win32: Baum [Vario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"LibUSB-Win32: Baum [PocketVario (24 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"LibUSB-Win32: Baum [SuperVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"LibUSB-Win32: Baum [SuperVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"LibUSB-Win32: Baum [SuperVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"LibUSB-Win32: Baum [SuperVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"LibUSB-Win32: Baum [VarioPro 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"LibUSB-Win32: Baum [VarioPro 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"LibUSB-Win32: Metec [all models]"=LIBUSB_DEV, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"LibUSB-Win32: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=LIBUSB_DEV, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"LibUSB-Win32: HIMS [Braille Edge and QBrailleXL]"=LIBUSB_DEV, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"LibUSB-Win32: Baum [Orbit Reader 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"LibUSB-Win32: Baum [Orbit Reader 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"LibUSB-Win32: Alva [Satellite (5nn)]"=LIBUSB_DEV, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"LibUSB-Win32: Voyager [all models]"=LIBUSB_DEV, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"LibUSB-Win32: Alva [Voyager Protocol Converter]"=LIBUSB_DEV, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"LibUSB-Win32: Alva [BC624]"=LIBUSB_DEV, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"LibUSB-Win32: Alva [BC640]"=LIBUSB_DEV, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"LibUSB-Win32: Alva [BC680]"=LIBUSB_DEV, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"LibUSB-Win32: FrankAudiodata [B2K84 (before firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"LibUSB-Win32: FrankAudiodata [B2K84 (after firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"LibUSB-Win32: Baum [VarioPro 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"LibUSB-Win32: Baum [EcoVario 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"LibUSB-Win32: Baum [EcoVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"LibUSB-Win32: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"LibUSB-Win32: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"LibUSB-Win32: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"LibUSB-Win32: Baum [VarioConnect 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"LibUSB-Win32: Baum [VarioConnect 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"LibUSB-Win32: Baum [EcoVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"LibUSB-Win32: Baum [EcoVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"LibUSB-Win32: Baum [EcoVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"LibUSB-Win32: Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"LibUSB-Win32: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"LibUSB-Win32: Baum [Pronto! V3 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"LibUSB-Win32: Baum [Pronto! V3 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"LibUSB-Win32: Baum [Pronto! V4 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"LibUSB-Win32: Baum [Pronto! V4 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"LibUSB-Win32: Baum [SuperVario2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"LibUSB-Win32: Baum [PocketVario2 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"LibUSB-Win32: Baum [SuperVario2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"LibUSB-Win32: Baum [SuperVario2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"LibUSB-Win32: Baum [SuperVario2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"LibUSB-Win32: Baum [Brailliant2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"LibUSB-Win32: Baum [Brailliant2 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"LibUSB-Win32: Baum [Brailliant2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"LibUSB-Win32: Baum [Brailliant2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"LibUSB-Win32: Baum [Brailliant2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"LibUSB-Win32: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"LibUSB-Win32: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"LibUSB-Win32: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"LibUSB-Win32: Baum [VarioUltra 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"LibUSB-Win32: Baum [VarioUltra 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"LibUSB-Win32: Baum [VarioUltra 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"LibUSB-Win32: HandyTech [GoHubs chip]"=LIBUSB_DEV, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"LibUSB-Win32: FreedomScientific [Focus 1]"=LIBUSB_DEV, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"LibUSB-Win32: FreedomScientific [PAC Mate]"=LIBUSB_DEV, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"LibUSB-Win32: FreedomScientific [Focus 2]"=LIBUSB_DEV, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"LibUSB-Win32: FreedomScientific [Focus 3+]"=LIBUSB_DEV, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"LibUSB-Win32: BrailleMemo [Pocket], Seika [Braille Display]"=LIBUSB_DEV, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"LibUSB-Win32: Seika [Note Taker]"=LIBUSB_DEV, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"LibUSB-Win32: BrailleMemo [Smart]"=LIBUSB_DEV, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"LibUSB-Win32: Inceptor [all models]"=LIBUSB_DEV, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"LibUSB-Win32: Canute [all models]"=LIBUSB_DEV, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"LibUSB-Win32: Baum [NLS eReader Zoomax (20 cells)]"=LIBUSB_DEV, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"LibUSB-Win32: BrailleNote [HumanWare APEX]"=LIBUSB_DEV, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"LibUSB-Win32: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"LibUSB-Win32: HumanWare [non-Touch models (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"LibUSB-Win32: HumanWare [BrailleNote Touch (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"LibUSB-Win32: HumanWare [Brailliant BI 14 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"LibUSB-Win32: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"LibUSB-Win32: HumanWare [APH Chameleon 20 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"LibUSB-Win32: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"LibUSB-Win32: HumanWare [APH Mantis Q40 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"LibUSB-Win32: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"LibUSB-Win32: HumanWare [Humanware BrailleOne (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"LibUSB-Win32: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"LibUSB-Win32: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"LibUSB-Win32: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"LibUSB-Win32: HumanWare [NLS eReader (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"LibUSB-Win32: HandyTech [USB-HID adapter]"=LIBUSB_DEV, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"LibUSB-Win32: HandyTech [Easy Braille (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"LibUSB-Win32: HandyTech [Active Braille]"=LIBUSB_DEV, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"LibUSB-Win32: HandyTech [Connect Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"LibUSB-Win32: HandyTech [Actilino]"=LIBUSB_DEV, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"LibUSB-Win32: HandyTech [Active Star 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"LibUSB-Win32: HandyTech [Braille Star 40 (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"LibUSB-Win32: HandyTech [Basic Braille 16]"=LIBUSB_DEV, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"LibUSB-Win32: HandyTech [Basic Braille 20]"=LIBUSB_DEV, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"LibUSB-Win32: HandyTech [Basic Braille 32]"=LIBUSB_DEV, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"LibUSB-Win32: HandyTech [Basic Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"LibUSB-Win32: HandyTech [Basic Braille 64]"=LIBUSB_DEV, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"LibUSB-Win32: HandyTech [Basic Braille 80]"=LIBUSB_DEV, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"LibUSB-Win32: HandyTech [Basic Braille 48]"=LIBUSB_DEV, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"LibUSB-Win32: HandyTech [Basic Braille 160]"=LIBUSB_DEV, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"LibUSB-Win32: HandyTech [Activator]"=LIBUSB_DEV, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"LibUSB-Win32: Pegasus [all models]"=LIBUSB_DEV, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"LibUSB-Win32: EuroBraille [Esys (version < 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"LibUSB-Win32: EuroBraille [Esys (version < 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"LibUSB-Win32: EuroBraille [Esys (version >= 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"LibUSB-Win32: EuroBraille [Esys (version >= 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"LibUSB-Win32: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=LIBUSB_DEV, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"LibUSB-Win32: Microsoft Narrator"=LIBUSB_DEV, USB\NarratorOverride
+
+[Devices.NTamd64]
+; BEGIN_USB_BRAILLE_DEVICES LibUSB-Win32 LIBUSB_DEV
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"LibUSB-Win32: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"LibUSB-Win32: DotPad [all models]"=LIBUSB_DEV, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"LibUSB-Win32: Hedo [MobilLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"LibUSB-Win32: Hedo [ProfiLine]"=LIBUSB_DEV, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"LibUSB-Win32: Papenmeier [all models]"=LIBUSB_DEV, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"LibUSB-Win32: Baum [Vario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"LibUSB-Win32: Baum [PocketVario (24 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"LibUSB-Win32: Baum [SuperVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"LibUSB-Win32: Baum [SuperVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"LibUSB-Win32: Baum [SuperVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"LibUSB-Win32: Baum [SuperVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"LibUSB-Win32: Baum [VarioPro 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"LibUSB-Win32: Baum [VarioPro 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"LibUSB-Win32: Metec [all models]"=LIBUSB_DEV, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"LibUSB-Win32: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=LIBUSB_DEV, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"LibUSB-Win32: HIMS [Braille Edge and QBrailleXL]"=LIBUSB_DEV, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"LibUSB-Win32: Baum [Orbit Reader 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"LibUSB-Win32: Baum [Orbit Reader 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"LibUSB-Win32: Alva [Satellite (5nn)]"=LIBUSB_DEV, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"LibUSB-Win32: Voyager [all models]"=LIBUSB_DEV, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"LibUSB-Win32: Alva [Voyager Protocol Converter]"=LIBUSB_DEV, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"LibUSB-Win32: Alva [BC624]"=LIBUSB_DEV, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"LibUSB-Win32: Alva [BC640]"=LIBUSB_DEV, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"LibUSB-Win32: Alva [BC680]"=LIBUSB_DEV, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"LibUSB-Win32: FrankAudiodata [B2K84 (before firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"LibUSB-Win32: FrankAudiodata [B2K84 (after firmware installation)]"=LIBUSB_DEV, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"LibUSB-Win32: Baum [VarioPro 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"LibUSB-Win32: Baum [EcoVario 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"LibUSB-Win32: Baum [EcoVario 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"LibUSB-Win32: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"LibUSB-Win32: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"LibUSB-Win32: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"LibUSB-Win32: Baum [VarioConnect 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"LibUSB-Win32: Baum [VarioConnect 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"LibUSB-Win32: Baum [EcoVario 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"LibUSB-Win32: Baum [EcoVario 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"LibUSB-Win32: Baum [EcoVario 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"LibUSB-Win32: Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"LibUSB-Win32: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"LibUSB-Win32: Baum [Pronto! V3 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"LibUSB-Win32: Baum [Pronto! V3 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"LibUSB-Win32: Baum [Pronto! V4 18 (18 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"LibUSB-Win32: Baum [Pronto! V4 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"LibUSB-Win32: Baum [SuperVario2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"LibUSB-Win32: Baum [PocketVario2 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"LibUSB-Win32: Baum [SuperVario2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"LibUSB-Win32: Baum [SuperVario2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"LibUSB-Win32: Baum [SuperVario2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"LibUSB-Win32: Baum [Brailliant2 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"LibUSB-Win32: Baum [Brailliant2 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"LibUSB-Win32: Baum [Brailliant2 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"LibUSB-Win32: Baum [Brailliant2 64 (64 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"LibUSB-Win32: Baum [Brailliant2 80 (80 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"LibUSB-Win32: Baum [VarioConnect 24 (24 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"LibUSB-Win32: Baum [VarioConnect 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"LibUSB-Win32: Baum [VarioConnect 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"LibUSB-Win32: Baum [VarioUltra 20 (20 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"LibUSB-Win32: Baum [VarioUltra 40 (40 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"LibUSB-Win32: Baum [VarioUltra 32 (32 cells)]"=LIBUSB_DEV, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"LibUSB-Win32: HandyTech [GoHubs chip]"=LIBUSB_DEV, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"LibUSB-Win32: FreedomScientific [Focus 1]"=LIBUSB_DEV, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"LibUSB-Win32: FreedomScientific [PAC Mate]"=LIBUSB_DEV, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"LibUSB-Win32: FreedomScientific [Focus 2]"=LIBUSB_DEV, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"LibUSB-Win32: FreedomScientific [Focus 3+]"=LIBUSB_DEV, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"LibUSB-Win32: BrailleMemo [Pocket], Seika [Braille Display]"=LIBUSB_DEV, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"LibUSB-Win32: Seika [Note Taker]"=LIBUSB_DEV, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"LibUSB-Win32: BrailleMemo [Smart]"=LIBUSB_DEV, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"LibUSB-Win32: Inceptor [all models]"=LIBUSB_DEV, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"LibUSB-Win32: Canute [all models]"=LIBUSB_DEV, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"LibUSB-Win32: Baum [NLS eReader Zoomax (20 cells)]"=LIBUSB_DEV, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"LibUSB-Win32: BrailleNote [HumanWare APEX]"=LIBUSB_DEV, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"LibUSB-Win32: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"LibUSB-Win32: HumanWare [non-Touch models (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"LibUSB-Win32: HumanWare [BrailleNote Touch (HID protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"LibUSB-Win32: HumanWare [Brailliant BI 14 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"LibUSB-Win32: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"LibUSB-Win32: HumanWare [APH Chameleon 20 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"LibUSB-Win32: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"LibUSB-Win32: HumanWare [APH Mantis Q40 (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"LibUSB-Win32: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"LibUSB-Win32: HumanWare [Humanware BrailleOne (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"LibUSB-Win32: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"LibUSB-Win32: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"LibUSB-Win32: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"LibUSB-Win32: HumanWare [NLS eReader (serial protocol)]"=LIBUSB_DEV, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"LibUSB-Win32: HandyTech [USB-HID adapter]"=LIBUSB_DEV, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"LibUSB-Win32: HandyTech [Easy Braille (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"LibUSB-Win32: HandyTech [Active Braille]"=LIBUSB_DEV, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"LibUSB-Win32: HandyTech [Connect Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"LibUSB-Win32: HandyTech [Actilino]"=LIBUSB_DEV, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"LibUSB-Win32: HandyTech [Active Star 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"LibUSB-Win32: HandyTech [Braille Star 40 (HID)]"=LIBUSB_DEV, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"LibUSB-Win32: HandyTech [Basic Braille 16]"=LIBUSB_DEV, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"LibUSB-Win32: HandyTech [Basic Braille 20]"=LIBUSB_DEV, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"LibUSB-Win32: HandyTech [Basic Braille 32]"=LIBUSB_DEV, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"LibUSB-Win32: HandyTech [Basic Braille 40]"=LIBUSB_DEV, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"LibUSB-Win32: HandyTech [Basic Braille 64]"=LIBUSB_DEV, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"LibUSB-Win32: HandyTech [Basic Braille 80]"=LIBUSB_DEV, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"LibUSB-Win32: HandyTech [Basic Braille 48]"=LIBUSB_DEV, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"LibUSB-Win32: HandyTech [Basic Braille 160]"=LIBUSB_DEV, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"LibUSB-Win32: HandyTech [Activator]"=LIBUSB_DEV, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"LibUSB-Win32: Pegasus [all models]"=LIBUSB_DEV, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"LibUSB-Win32: EuroBraille [Esys (version < 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"LibUSB-Win32: EuroBraille [Esys (version < 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"LibUSB-Win32: EuroBraille [Esys (version >= 3.0, no SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"LibUSB-Win32: EuroBraille [Esys (version >= 3.0, with SD card)]"=LIBUSB_DEV, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"LibUSB-Win32: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=LIBUSB_DEV, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"LibUSB-Win32: EuroBraille [reserved]"=LIBUSB_DEV, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"LibUSB-Win32: Microsoft Narrator"=LIBUSB_DEV, USB\NarratorOverride
+
diff --git a/Windows/libusb.nsi b/Windows/libusb.nsi
new file mode 100644
index 0000000..5948f81
--- /dev/null
+++ b/Windows/libusb.nsi
@@ -0,0 +1,28 @@
+!define VARIANT "libusb"
+
+!macro BrlttyInstall
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\libusb0.dll" "$SYSDIR\libusb0.dll" "$SYSDIR"
+	!insertmacro InstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "${DISTDIR}\bin\libusb0.sys" "$SYSDIR\drivers\libusb0.sys" "$SYSDIR\drivers"
+!macroend
+
+!macro BrlttyShortcut
+	CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_inslibusb).lnk" "$SYSDIR\rundll32" "libusb0.dll,usb_install_driver_np_rundll $INSTDIR\bin\brltty-libusb.inf"
+	CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_inslibusbfilter).lnk" "$SYSDIR\rundll32" "libusb0.dll,usb_install_service_np_rundll"
+	CreateShortCut "$SMPROGRAMS\$StartMenuFolder\$(shortcut_uninslibusbfilter).lnk" "$SYSDIR\rundll32" "libusb0.dll,usb_uninstall_service_np_rundll"
+!macroend
+
+!macro BrlttyTweak
+	MessageBox MB_YESNO|MB_DEFBUTTON2 $(msg_inslibusb32filter) IDNO nofilter
+	ExecWait "rundll32 libusb0.dll,usb_install_service_np_rundll"
+nofilter:
+	MessageBox MB_YESNO|MB_DEFBUTTON2 $(msg_inslibusb32) IDNO nodriver
+	ExecWait "rundll32 libusb0.dll,usb_install_driver_np_rundll $INSTDIR\bin\brltty-libusb.inf"
+nodriver:
+!macroend
+
+!macro BrlttyUninstall
+	ExecWait "rundll32 libusb0.dll,usb_uninstall_service_np_rundll"
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\libusb0.dll"
+!macroend
+
+!include brltty.nsi
diff --git a/Windows/mingw.sh b/Windows/mingw.sh
new file mode 100644
index 0000000..0c57fb1
--- /dev/null
+++ b/Windows/mingw.sh
@@ -0,0 +1,265 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+ahkName="AutoHotkey"
+ahkLocation="/c/Program Files (x86)/AutoHotkey"
+ahkDownload="http://www.autohotkey.com/"
+
+nsisName="NSIS"
+nsisLocation="/c/Program Files (x86)/NSIS"
+nsisDownload="http://nsis.sourceforge.net/"
+
+libusb0Name="LibUSB (aka LibUSB-Win32)"
+libusb0Version="1.2.6.0"
+libusb0Location="/c/LibUSB-Win32"
+libusb0Files="bin/libusb-win32-bin-README.txt"
+libusb0Download="http://libusb-win32.sourceforge.net/"
+libusb0Pattern="usb0"
+
+libusb1Name="LibUSB-1.0 (aka LibUSBx)"
+libusb1Version="1.0.18"
+libusb1Location="/c/LibUSB-1.0"
+libusb1Files="libusb-1.0.def"
+libusb1Download="http://www.libusb.org/wiki/windows_backend"
+libusb1Pattern="usb-1.0"
+
+winusbName="LibUSB-1.0 WinUSB Coinstallers"
+winusbLocation="/c/WinUSB"
+winusbFiles="libusb_device.inf"
+winusbDownload="http://www.libusb.org/wiki/windows_backend"
+
+msvcName="MSVC"
+msvcLocation=""
+msvcFiles="lib.exe link.exe"
+msvcDownload=""
+
+pythonName="Python"
+pythonLocation=""
+pythonFiles="Lib/site-packages Scripts"
+pythonDownload="http://www.python.org/"
+
+cythonName="Cython"
+cythonLocation=""
+cythonDownload="http://www.cython.org/"
+
+icuName="ICU"
+icuLocation="/c/ICU"
+icuFiles="include/unicode"
+icuDownload="http://icu-project.org/"
+
+pthreadsName="mingw32-pthreads-w32"
+pthreadsLocation="/"
+pthreadsFiles="mingw/include/pthread.h"
+
+cursesName="mingw32-pdcurses"
+cursesLocation="/"
+cursesFiles="mingw/include/curses.h"
+
+zipPackage="msys-zip"
+unix2dosPackage="msys-dos2unix"
+groffPackage="msys-groff"
+
+verifyPackage() {
+   local package="${1}"
+   shift 1
+
+   local name="$(getVariable "${package}Name")"
+
+   local root="$(getVariable "${package}Root")"
+   [ -n "${root}" ] || root="$(getVariable "${package}Location")"
+
+   if [ -z "${root}" ]
+   then
+      logWarning "package location not defined: ${name}"
+      root=""
+   elif [ ! -e "${root}" ]
+   then
+      logWarning "directory not found: ${root}"
+      root=""
+   elif [ ! -d "${root}" ]
+   then
+      logWarning "not a directory: ${root}"
+      root=""
+   else
+      set -- $(getVariable "${package}Files")
+      local prefix="${root%/}"
+      local file
+
+      for file
+      do
+         local path="${prefix}/${file}"
+
+         [ -e "${path}" ] || {
+            logWarning "${name} file not found: ${path}"
+            root=""
+         }
+      done
+   fi
+
+   setVariable "${package}Root" "${root}"
+   [ -n "${root}" ] || return 1
+   return 0
+}
+
+addWindowsPackageOption() {
+   local letter="${1}"
+   local package="${2}"
+
+   local name="$(getVariable "${package}Name")"
+   local location="$(getVariable "${package}Location")"
+   addProgramOption "${letter}" string.directory "${package}Root" "where the ${name} package has been installed" "${location}"
+}
+
+verifyWindowsPackage() {
+   local package="${1}"
+
+   verifyPackage "${package}" || {
+      local message="Windows package not installed:"
+
+      local name="$(getVariable "${package}Name")"
+      [ -z "${name}" ] || message="${message} ${name}"
+
+      local download="$(getVariable "${package}Download")"
+      [ -z "${download}" ] || message="${message} (download from ${download})"
+
+      logWarning "${message}"
+      return 1
+   }
+
+   return 0
+}
+
+verifyWindowsPackages() {
+   local result=0
+
+   while [ "${#}" -gt 0 ]
+   do
+      local package="${1}"
+      shift 1
+
+      verifyWindowsPackage "${package}" || result=1
+   done
+
+   return "${result}"
+}
+
+verifyMingwPackage() {
+   local package="${1}"
+
+   verifyPackage "${package}" || {
+      local message="MinGW package not installed:"
+
+      local name="$(getVariable "${package}Name")"
+      [ -z "${name}" ] || message="${message} ${name}"
+
+      logWarning "${message}"
+      return 1
+   }
+
+   return 0
+}
+
+verifyMingwPackages() {
+   local result=0
+
+   while [ "${#}" -gt 0 ]
+   do
+      local package="${1}"
+      shift 1
+
+      verifyMingwPackage "${package}" || result=1
+   done
+
+   return "${result}"
+}
+
+findHostCommand() {
+   local command="${1}"
+
+   local path="$(command -v "${command}" 2>/dev/null || :)"
+   setVariable "${command}Path" "${path}"
+   [ -n "${path}" ] || return 1
+   return 0
+}
+
+verifyWindowsCommand() {
+   local command="${1}"
+
+   findHostCommand "${command}" || {
+      local message="Windows command not found: ${command}"
+
+      local download="$(getVariable "${command}Download")"
+      [ -z "${download}" ] || {
+         message="${message} (install"
+         local name="$(getVariable "${command}Name")"
+         [ -z "${name}" ] || message="${message} ${name}"
+         message="${message} from ${download})"
+      }
+
+      logWarning "${message}"
+      return 1
+   }
+
+   return 0
+}
+
+verifyWindowsCommands() {
+   local result=0
+
+   while [ "${#}" -gt 0 ]
+   do
+      local command="${1}"
+      shift 1
+
+      verifyWindowsCommand "${command}" || result=1
+   done
+
+   return "${result}"
+}
+
+verifyMingwCommand() {
+   local command="${1}"
+
+   findHostCommand "${command}" || {
+      local message="MinGW command not found: ${command}"
+
+      local package="$(getVariable "${command}Package")"
+      [ -z "${package}" ] || message="${message} (install package ${package})"
+
+      logWarning "${message}"
+      return 1
+   }
+
+   return 0
+}
+
+verifyMingwCommands() {
+   local result=0
+
+   while [ "${#}" -gt 0 ]
+   do
+      local command="${1}"
+      shift 1
+
+      verifyMingwCommand "${command}" || result=1
+   done
+
+   return "${result}"
+}
+
+[ "${MSYSTEM}" = "MINGW32" ] || semanticError "this script is for MinGW only"
diff --git a/Windows/mkwin b/Windows/mkwin
new file mode 100755
index 0000000..0094c5e
--- /dev/null
+++ b/Windows/mkwin
@@ -0,0 +1,576 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+umask 022
+export MAKEFLAGS=
+
+copyProperties() {
+   local from="${1}"
+   local to="${2}"
+   shift 2
+
+   for suffix in ${*}
+   do
+      setVariable "${to}${suffix}" "$(getVariable "${from}${suffix}")"
+   done
+}
+
+installFile() {
+   local source="${1}"
+   local target="${2}"
+
+   [ "${target%/}" = "${target}" ] || target="${target}${source##*/}"
+
+   if [ -e "${source}" ]
+   then
+      local path="${installRoot}/${target}"
+      mkdir -p "${path%/*}"
+      cp "${source}" "${path}"
+   else
+      logWarning "file not installed: ${target}"
+   fi
+}
+
+installFiles() {
+   local target="${1}"
+   shift 1
+   local source
+
+   for source
+   do
+      installFile "${source}" "${target}"
+   done
+}
+
+installPackages() {
+   while [ "${#}" -gt 0 ]
+   do
+      local package="${1}"
+      shift 1
+
+      local name="$(getVariable "${package}Name")"
+      logMessage task "installing package files: ${name}"
+      "installPackage_${package}"
+   done
+}
+
+installPackage_libusb0() {
+   if [ -n "${libusb0Root}" ]
+   then
+      cd "${libusb0Root}/bin"
+      installFile "x86/libusb0.sys" "bin/libusb0.sys"
+      installFile "x86/libusb0_x86.dll" "bin/libusb0.dll"
+      installFile "amd64/libusb0.sys" "bin/libusb0_x64.sys"
+      installFile "amd64/libusb0.dll" "bin/libusb0_x64.dll"
+   else
+      logWarning "${libusb0Name} not installable"
+   fi
+}
+
+installPackage_libusb1() {
+   if [ -n "${libusb1Root}" ]
+   then
+      cd "${libusb1Root}"
+      installFile "MinGW32/dll/libusb-1.0.dll" "bin/"
+   else
+      logWarning "${libusb1Name} not installable"
+   fi
+}
+
+installPackage_winusb() {
+   if [ -n "${winusbRoot}" ]
+   then
+      cd "${winusbRoot}"
+      installFile "x86/winusbcoinstaller2.dll" "bin/x86/"
+      installFile "amd64/winusbcoinstaller2.dll" "bin/amd64/"
+      installFile "x86/WdfCoInstaller01009.dll" "bin/x86/"
+      installFile "amd64/WdfCoInstaller01009.dll" "bin/amd64/"
+   else
+      logWarning "${winusbName} not installable"
+   fi
+}
+
+. "`dirname "${0}"`/../brltty-prologue.sh"
+. "${programDirectory}/mingw.sh"
+
+defaultUsbPackage="libusb"
+defaultTemporaryDirectory="${TMPDIR:-/tmp}/brltty-${programName}"
+
+addProgramOption u string.package usbPackage "which USB package to use" "${defaultUsbPackage}"
+addProgramOption s flag invokeShell "invoke interactive shell to inspect/modify result"
+addProgramOption t string.directory temporaryDirectory "the temporary directory to use for the build" "${defaultTemporaryDirectory}"
+addProgramOption k flag keepBuild "keep (do not remove) the temporary directory"
+addWindowsPackageOption M msvc
+addWindowsPackageOption A ahk
+addWindowsPackageOption N nsis
+addWindowsPackageOption U libusb0
+addWindowsPackageOption X libusb1
+addWindowsPackageOption W winusb
+addWindowsPackageOption P python
+addWindowsPackageOption I icu
+addProgramOption C string.directory cygwinRoot "the root directory of a Cygwin installation"
+addProgramParameter source sourceRoot "the top-level directory of the source tree"
+optionalProgramParameters
+addProgramParameter revision buildRevision "the revision of the build"
+parseProgramArguments "${@}"
+
+sourceRoot="$(cd "${sourceRoot}" && pwd -W)"
+[ -f "${sourceRoot}/configure" ] || semanticError "not a source tree: ${sourceRoot}"
+
+revisionIdentifier="$("${sourceRoot}/getrevid" "${sourceRoot}")"
+logMessage task "revision identifier: ${revisionIdentifier}"
+
+[ -n "${buildRevision}" ] || {
+   buildRevision="${revisionIdentifier%-g*}-"
+   buildRevision="${buildRevision#*-}"
+   buildRevision="${buildRevision#*-}"
+
+   if [ -n "${buildRevision}" ]
+   then
+      buildRevision="${buildRevision%-}"
+   else
+      buildRevision="0"
+   fi
+}
+
+[ -n "${cygwinRoot}" ] || {
+   directory=/cygwin
+   [ -e "${directory}" ] && cygwinRoot="${directory}"
+}
+
+[ -n "${cygwinRoot}" ] && {
+   if [ -f "${cygwinRoot}/cygwin.ico" ]
+   then
+      export PATH="${PATH}:${cygwinRoot}/bin"
+      export nodosfilewarning=1
+   else
+      logWarning "not a Cygwin root directory: ${cygwinRoot}"
+   fi
+}
+
+[ -n "${usbPackage}" ] || usbPackage="${defaultUsbPackage}"
+
+case "${usbPackage}"
+in
+   winusb) usbWindowsPackages=();;
+   libusb) usbWindowsPackages=("libusb0");;
+   libusb-1.0) usbWindowsPackages=("libusb1" "winusb");;
+   *) syntaxError "unrecognized USB package: ${usbPackage}"
+esac
+
+winusbFilter=""
+
+libusb0Filter="\
+    select "USB:", and install the ${libusb0Name} filter by doing one of the
+    following:
+    + run Install ${libusb0Name} Filter from the Start Menu
+    + answer yes to the setup prompt for installing the ${libusb0Name} filter
+    + install it by hand, from ${libusb0Download}"
+
+libusb1Filter="\
+    you will need to use ${libusb0Name} instead because
+    ${libusb1Name} does not support this requirement."
+
+set -- "${usbWindowsPackages[@]}"
+copyProperties "${1}" usb Name Location Download Version Pattern Filter
+
+[ -z "${msvcRoot}" ] || {
+   if verifyWindowsPackage msvc
+   then
+      export PATH="${msvcRoot}:${PATH}"
+   fi
+}
+
+[ -n "${pythonRoot}" ] || {
+   pythonLocationFile="${programDirectory}/python-location"
+
+   if [ -f "${pythonLocationFile}" ]
+   then
+      read pythonRoot <"${pythonLocationFile}"
+   fi
+}
+
+[ -z "${pythonRoot}" ] || {
+   if verifyWindowsPackage python
+   then
+      export PATH="${pythonRoot}:${pythonRoot}/Scripts:${PATH}"
+      export PYTHON="${pythonRoot}/python"
+   fi
+}
+
+verifyWindowsPackages ahk nsis icu "${usbWindowsPackages[@]}" || :
+verifyWindowsCommands lib python cython || :
+verifyMingwPackages pthreads curses || :
+verifyMingwCommands zip unix2dos linuxdoc doxygen groff || :
+
+if [ -z "${temporaryDirectory}" ]
+then
+   temporaryDirectory="${defaultTemporaryDirectory}"
+   rm -f -r "${temporaryDirectory}"
+elif [ -e "${temporaryDirectory}" ]
+then
+   semanticError "directory already exists: ${temporaryDirectory}"
+fi
+
+mkdir -p "${temporaryDirectory}"
+cd "${temporaryDirectory}"
+temporaryDirectory="$(pwd -W)"
+
+logMessage task "copying source tree"
+newSourceRoot="${temporaryDirectory}/source"
+cp -a "${sourceRoot}" "${newSourceRoot}"
+cd "${newSourceRoot}"
+sourceRoot="${newSourceRoot}"
+
+logMessage task "applying patches"
+for patch in affinity
+do
+   patch -p0 <"Windows/${patch}.patch"
+done
+
+infFile="${usbPackage}.inf"
+infPath="Windows/${infFile}"
+Tools/updusbdevs -q "c:Programs/usb_devices.c" "inf:${infPath}"
+Tools/updcsvs -q
+
+buildRoot="${temporaryDirectory}/build"
+mkdir -p "${buildRoot}"
+cd "${buildRoot}"
+
+logMessage task "configuring build"
+"../source/cfg-windows" \
+   --prefix=/ \
+   --enable-relocatable-install \
+   --with-usb-package="${usbPackage}" \
+   --quiet
+
+. ./brltty-config.sh
+buildVersion="${BRLTTY_VERSION}-${buildRevision}"
+buildName="${BRLTTY_TARNAME}-win-${buildVersion}-${usbPackage}"
+
+logMessage task "building programs"
+make -s
+make -s -C Drivers/BrlAPI/WindowEyes we-dynamic-library-windows
+
+logMessage task "building documents"
+make -s -C Documents
+
+logMessage task "installing files"
+installRoot="${temporaryDirectory}/install/${buildName}"
+make -s install install-messages \
+   JAVA_JAR_DIR=/lib JAVA_JNI_DIR=/lib \
+   PYTHON_PREFIX="Python" \
+   INSTALL_ROOT="${installRoot}"
+
+logMessage task "updating files"
+documentDirectory="doc"
+
+readmeHeader="\
+This build of ${BRLTTY_NAME} ${BRLTTY_VERSION} includes Windows-specific patches.
+
+You should probably read doc/BRLTTY.txt and doc/Windows.txt for information
+about BRLTTY.
+
+Here are some notes on how to get started:
+
+- BRLTTY only gives access to text consoles. For the rest of the Windows
+  environment, you need to also install and run NVDA.
+- Either use the BRLTTY configurator (brlttycnf.exe) or manually uncomment the
+  appropriate lines in etc/brltty.conf.
+- For Python support (e.g. for NVDA), run Brlapi-${BRLAPI_RELEASE}.win32.exe
+  (which will install the .pyd and .egg-info files).
+- For sighted users, use the xw braille driver to get a virtual braille box.
+"
+
+readmeFooter="\
+
+If you are having problems, please run debug-brltty.bat and send us the
+debug.log and brltty.conf files.
+
+Documentation can be found in the doc/ subdirectory.
+
+==============================
+Technical notes on this build:
+
+- Source Revision: ${revisionIdentifier}
+- BRLTTY Version: ${BRLTTY_VERSION}
+- BrlAPI Version: ${BRLAPI_RELEASE}
+- Some MinGW-specific path and file name changes have been made.
+- To make life easier for Windows users, the BrlAPI server was modified to:
+  * not use authentication by default (BRLAPI_DEFAUTH set to none)
+  * only use local sockets (:0+127.0.0.1:0 changed to :0)
+- *${usbPattern}* files come from ${usbName} ${usbVersion}, which can be found at:
+  ${usbDownload}
+- Python bindings are provided by: Brlapi-${BRLAPI_RELEASE}.win32.exe
+- C bindings are provided in: include/, and lib/
+  A .lib file is provided for linking in (for example) Visual C. Then you can
+  just ship bin/brlapi${BRLAPI_RELEASE%.*}.dll alongside your .exe application.
+"
+
+cd "${buildRoot}"
+installFile "Documents/brltty.conf" "etc/"
+installFile "Drivers/BrlAPI/WindowEyes/webrloem109.dll" "WindowEyes/"
+
+installFiles "Debug/" config.h config.mk config.log
+installFiles "Debug/" Programs/*.exe Programs/*.dll
+
+set -- Bindings/Python/dist/Brlapi-${BRLAPI_RELEASE}.*.exe
+if [ "${#}" -gt 0 ]
+then
+   for file
+   do
+      installFile "${file}" "Python/"
+   done
+else
+   logWarning "Python bindings installer not found"
+fi
+
+cd "${sourceRoot}"
+installFile "LICENSE-LGPL" "LICENSE-LGPL.txt"
+installFile "README" "${documentDirectory}/BRLTTY.txt"
+installFile "Drivers/Braille/XWindow/UBraille.ttf" "Fonts/"
+installFile "Drivers/BrlAPI/WindowEyes/README" "${documentDirectory}/WindowEyes.txt"
+installFile "${infPath}" "bin/brltty-${infFile}"
+installFiles "etc/" Documents/*.csv
+
+for document in ChangeLog HISTORY TODO
+do
+   installFile "Documents/${document}" "${documentDirectory}/${document}.txt"
+done
+
+for document in Windows BrailleDots TextTables ContractionTables AttributesTables KeyTables
+do
+   installFile "Documents/README.${document}" "${documentDirectory}/${document}.txt"
+done
+
+for root in "${sourceRoot}" "${buildRoot}"
+do
+   cd "${root}/Documents"
+
+   for manual in Manual-BRLTTY Manual-BrlAPI BrlAPIref
+   do
+      [ -d "${manual}" ] || continue
+
+      for file in $(find "${manual}" -type f -print)
+      do
+         name="${file##*/}"
+         extension="${name##*.}"
+
+         case "${extension}"
+         in
+            txt | html | htm | doc | pdf)
+               installFile "${file}" "${documentDirectory}/${file}"
+               ;;
+
+            *);;
+         esac
+      done
+   done
+done
+
+cd "${sourceRoot}/Drivers"
+for document in $(find Braille Speech -type f -name "README*" -print)
+do
+   installFile "${document}" "${documentDirectory}/Drivers/${document}.txt"
+done
+
+cd "${programDirectory}"
+installFiles "bin/" *.dll *.cat
+installFiles "/" *.bat
+
+logMessage task "USB package: ${usbPackage}"
+installPackages "${usbWindowsPackages[@]}"
+
+cd /mingw/bin
+for file in libgcc_s_dw2-1.dll libiconv-2.dll libpdcurses*.dll libintl-8.dll
+do
+   installFile "${file}" "bin/"
+done
+
+cd "${installRoot}"
+rm -f "bin/brltty-config"
+rm -f "etc/brlapi.key"
+
+echo "${revisionIdentifier}" >"REVISION.txt"
+
+for source in $(find "share/man" -type f -name "*.[1-9]" -print)
+do
+   [ -z "${groffPath}" ] || {
+      target="${source%.*}.txt"
+
+      "${groffPath}" -T ascii -mandoc 2>/dev/null <"${source}" |
+      sed -e 's/'$'\033''\[[0-9]*m//g' >"${target}"
+
+      [ -s "${target}" ] || rm -f "${target}"
+   }
+
+   rm -f "${source}"
+done
+
+cat >"README.txt" <<END-OF-README
+${readmeHeader}
+- To register BRLTTY as a service so that it will get started automatically at
+  boot, run enable-brlapi.bat. To unregister it, run disable-brlapi.bat.
+- If your braille device uses a USB connection:
+  * If you either cannot, or would prefer not to, install its
+    manufacturer's driver, then you can select "USB:", and install
+    the ${usbName} driver by doing one of the following:
+    + right-clicking on bin/brltty-${usbPackage}.inf and selecting install
+    + answering yes to the setup prompt for installing the ${usbName} driver
+  * If you have installed its manufacturer's driver, and if that driver defines
+    a virtual COM port, then select that virtual COM port.
+  * If you need to install its manufacturer's driver, but
+    that driver does not define a virtual COM port, then 
+${usbFilter}
+- If your braille device uses a serial connection, or if it is connected via a
+  serial to USB converter, then select the correct COM port. Make sure to
+  select the correct braille driver as well, because serial autodetection may
+  brick some devices.
+- If your braille device uses a Bluetooth connection, you can either use the
+  Windows Bluetooth facilities to create a virtual COM port which you can then
+  select, or manually configure the braille-device line in brltty.conf.
+${readmeFooter}
+END-OF-README
+
+if [ -n "${libPath}" ]
+then
+   cd "${installRoot}/lib"
+   "${libPath}" //nologo /def:brlapi.def /name:brlapi-${BRLAPI_VERSION}.dll /out:brlapi.lib /machine:x86
+else
+   logWarning "import library not creatable"
+fi
+
+cd "${installRoot}/.."
+cat >"README.txt" <<END-OF-README
+${readmeHeader}
+${readmeFooter}
+END-OF-README
+
+# for the installer but before text file conversion
+nsisScript="${usbPackage}.nsi"
+nsisStrings="nsistrings.txt"
+
+for file in "${nsisScript}" "brltty.nsi" "${nsisStrings}"
+do
+   path="${programDirectory}/${file}"
+
+   if [ -f "${path}" ]
+   then
+      cp "${path}" .
+   else
+      logWarning "installer source file not found: ${file}"
+      nsisRoot=""
+   fi
+done
+
+if [ -n "${ahkRoot}" ]
+then
+   logMessage task "creating configurator"
+   cd "${installRoot}"
+   cp "${programDirectory}/brlttycnf.ahk" .
+   "${ahkRoot}/Compiler/Ahk2Exe.exe" //in brlttycnf.ahk //out brlttycnf.exe
+   rm brlttycnf.ahk
+else
+   logWarning "configurator not creatable"
+fi
+
+if [ -n "${unix2dosPath}" ]
+then
+   logMessage task "converting text files"
+   cd "${installRoot}/.."
+
+   find . -print |
+      while read path
+      do
+         handle="${path#.}"
+         [ -n "${handle}" ] || continue
+         handle="${handle#/}"
+
+         name="${path##*/}"
+         extension="${name##*.}"
+
+         if [ -f "${path}" ]
+         then
+            if [ "${extension}" != "${name}" ]
+            then
+               case "${extension}"
+               in
+                  bash | bat | cat | conf | csv | h | htm | html | inf | log | lua | mk | nsi | pc | policy | sh | tcl | txt | xml | [tcak]t[bi])
+                     "${unix2dosPath}" -q -o "${path}"
+                     ;;
+
+                  a | def | dll | doc | egg-info | exe | exp | lib | mo | pdf | pyd | sys | ttf);;
+                  *) logWarning "unexpected file extension: ${handle}";;
+               esac
+            fi
+         elif [ ! -d "${path}" ]
+         then
+            logWarning "unsupported special file: ${handle}"
+         fi
+      done
+else
+   logWarning "text files not convertable"
+fi
+
+if "${invokeShell}"
+then
+   logMessage task "invoking shell"
+   cd "${installRoot}"
+   "${SHELL:-/bin/sh}" || :
+fi
+
+if [ -n "${zipPath}" ]
+then
+   logMessage task "creating archive"
+   archiveFile="${initialDirectory}/${buildName}.zip"
+   rm -f "${archiveFile}"
+   cd "${installRoot}/.."
+   "${zipPath}" -q -A -r "${archiveFile}" "${buildName}"
+else
+   logWarning "archive not creatable"
+fi
+
+if [ -n "${nsisRoot}" ]
+then
+   logMessage task "creating installer"
+   cd "${installRoot}/.."
+   mv "${nsisStrings}" "${buildName}"
+   installerFile="${buildName}.exe"
+
+   "${nsisRoot}/makensis" -V2 \
+      -DVERSION="${buildVersion}" \
+      -DDISTDIR="${buildName}" \
+      -DOUTFILE="${installerFile}" \
+      "${nsisScript}"
+
+   rm -f "${initialDirectory}/${installerFile}"
+   cp "${installerFile}" "${initialDirectory}/${installerFile}"
+else
+   logWarning "installer not creatable"
+fi
+
+"${keepBuild}" || {
+   logMessage task "cleaning up"
+   cd "${initialDirectory}"
+   rm -f -r "${temporaryDirectory}"
+}
+
+logMessage task "done"
+exit 0
diff --git a/Windows/msvcr90.dll b/Windows/msvcr90.dll
new file mode 100755
index 0000000..a68249a
--- /dev/null
+++ b/Windows/msvcr90.dll
Binary files differ
diff --git a/Windows/nsistrings.txt b/Windows/nsistrings.txt
new file mode 100644
index 0000000..d5860ae
--- /dev/null
+++ b/Windows/nsistrings.txt
@@ -0,0 +1,320 @@
+; Some translations for the brltty.nsi installer
+;Czech
+LangString msg_WelcomePageTitle ${LANG_CZECH} "Instalace ${PRODUCT}, verze ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_CZECH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_CZECH} "Chcete nainstalovat ovlada? LibUSB-Win32?$\r$\n(užite?né jen pokud nemáte nainstalován USB ovlada? dodávaný výrobcem pro vaše za?ízení)"
+LangString msg_inslibusb ${LANG_CZECH} "Chcete nainstalovat ovlada? LibUSB ?"
+LangString msg_inswinusb ${LANG_CZECH} "Chcete nainstalovat ovlada? WinUSB ?"
+
+LangString shortcut_brlttycnf $(LANG_CZECH) "Nastavení BRLTTY"
+LangString shortcut_brlttydebug $(LANG_CZECH) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_CZECH) "Instalace služby BRLTTY"
+LangString shortcut_rmvsrv $(LANG_CZECH) "Odinstalace služby BLRTTY"
+LangString shortcut_uninstall $(LANG_CZECH) "Odinstalace"
+LangString shortcut_inslibusbfilter $(LANG_CZECH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_CZECH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_CZECH) "Instalace ovlada?e LibUSB-Win32"
+
+;German
+LangString msg_WelcomePageTitle ${LANG_GERMAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_GERMAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_GERMAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_GERMAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_GERMAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_GERMAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_GERMAN) "Run BRLTTY in debugging mode"
+LangString shortcut_InsSrv $(LANG_GERMAN) "Install BRLTTY Service"
+LangString shortcut_RmvSrv $(LANG_GERMAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_GERMAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_GERMAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_GERMAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_GERMAN) "Install LibUSB-Win32 driver"
+
+;English
+LangString msg_WelcomePageTitle ${LANG_ENGLISH} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_ENGLISH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_ENGLISH} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_ENGLISH} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_ENGLISH} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_ENGLISH) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_ENGLISH) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_ENGLISH) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_ENGLISH) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_ENGLISH) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_ENGLISH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_ENGLISH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_ENGLISH) "Install LibUSB-Win32 driver"
+
+;Spanish
+LangString msg_WelcomePageTitle ${LANG_SPANISH} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_SPANISH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_SPANISH} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_SPANISH} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_SPANISH} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_SPANISH) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_SPANISH) "Run BRLTTY in debugging mode"
+LangString shortcut_InsSrv $(LANG_SPANISH) "Install BRLTTY Service"
+LangString shortcut_RmvSrv $(LANG_SPANISH) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_SPANISH) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_SPANISH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_SPANISH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_SPANISH) "Install LibUSB-Win32 driver"
+
+;Finnish
+LangString msg_WelcomePageTitle ${LANG_FINNISH} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_FINNISH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_FINNISH} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_FINNISH} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_FINNISH} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_FINNISH) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_FINNISH) "Run BRLTTY in debugging mode"
+LangString shortcut_InsSrv $(LANG_FINNISH) "Install BRLTTY Service"
+LangString shortcut_RmvSrv $(LANG_FINNISH) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_FINNISH) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_FINNISH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_FINNISH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_FINNISH) "Install LibUSB-Win32 driver"
+
+;French
+LangString msg_WelcomePageTitle ${LANG_FRENCH} "Installation de ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_FRENCH} "Voulez-vous installer le filtre LibUSB-Win32 ?$\r$\n(utile seulement pour les plages USB et si vous avez déjà installé le pilote fourni par le constructeur de votre unité USB. Ne l'installez pas si votre windows est 64bit, ou Vista, ou 7)"
+LangString msg_inslibusb32 ${LANG_FRENCH} "Voulez-vous installer le pilote LibUSB-Win32?$\r$\n(utile seulement si vous n'avez pas installé le pilote fourni par le constructeur de votre unité USB)"
+LangString msg_inslibusb ${LANG_FRENCH} "Voulez-vous installer le pilote LibUSB ?"
+LangString msg_inswinusb ${LANG_FRENCH} "Voulez-vous installer le pilote WinUSB ?"
+
+LangString shortcut_brlttycnf $(LANG_FRENCH) "Configurer BRLTTY"
+LangString shortcut_brlttydebug $(LANG_FRENCH) "Lancer BRLTTY en mode debug"
+LangString shortcut_InsSrv $(LANG_FRENCH) "Installer le Service BRLTTY"
+LangString shortcut_RmvSrv $(LANG_FRENCH) "Désinstaller le Service BRLTTY"
+LangString shortcut_uninstall $(LANG_FRENCH) "Désinstaller"
+LangString shortcut_inslibusbfilter $(LANG_FRENCH) "Installer le filtre LibUSB-Win32"
+LangString shortcut_uninslibusbfilter $(LANG_FERNCH) "Désinstaller le filtre LibUSB-Win32"
+LangString shortcut_inslibusb $(LANG_FRENCH) "Installer le pilote LibUSB-Win32"
+
+;Galician
+LangString msg_WelcomePageTitle ${LANG_GALICIAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_GALICIAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_GALICIAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_GALICIAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_GALICIAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_GALICIAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_GALICIAN) "Run BRLTTY in debugging mode"
+LangString shortcut_InsSrv $(LANG_GALICIAN) "Install BRLTTY Service"
+LangString shortcut_RmvSrv $(LANG_GALICIAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_GALICIAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_GALICIAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_GALICIAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_GALICIAN) "Install LibUSB-Win32 driver"
+
+;Croatian
+LangString msg_WelcomePageTitle ${LANG_CROATIAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_CROATIAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_CROATIAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_CROATIAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_CROATIAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_CROATIAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_CROATIAN) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_CROATIAN) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_CROATIAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_CROATIAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_CROATIAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_CROATIAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_CROATIAN) "Install LibUSB-Win32 driver"
+
+;Hungarian
+LangString msg_WelcomePageTitle ${LANG_HUNGARIAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_HUGARIAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_HUNGARIAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_HUNGARIAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_HUNGARIAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_HUNGARIAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_HUNGARIAN) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_HUNGARIAN) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_HUNGARIAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_HUNGARIAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_HUNGARIAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_HUGARIAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_HUNGARIAN) "Install LibUSB-Win32 driver"
+
+;Italian
+LangString msg_WelcomePageTitle ${LANG_ITALIAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_ITALIAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_ITALIAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_ITALIAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_ITALIAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_ITALIAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_ITALIAN) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_ITALIAN) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_ITALIAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_ITALIAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_ITALIAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_ITALIAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_ITALIAN) "Install LibUSB-Win32 driver"
+
+;Japanese
+LangString msg_WelcomePageTitle ${LANG_JAPANESE} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_JAPANESE} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_JAPANESE} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_JAPANESE} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_JAPANESE} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_JAPANESE) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_JAPANESE) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_JAPANESE) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_JAPANESE) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_JAPANESE) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_JAPANESE) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_JAPANESE) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_JAPANESE) "Install LibUSB-Win32 driver"
+
+;Polish
+LangString msg_WelcomePageTitle ${LANG_POLISH} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_POLISH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_POLISH} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_POLISH} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_POLISH} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_POLISH) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_POLISH) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_POLISH) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_POLISH) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_POLISH) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_POLISH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_POLISH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_POLISH) "Install LibUSB-Win32 driver"
+
+;PortugueseBr
+LangString msg_WelcomePageTitle ${LANG_PORTUGUESEBR} "Setup para o ${PRODUCT}, Versão ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_PORTUGUESEBR} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_PORTUGUESEBR} "Pretende instalar o driver LibUSB-Win32?$\r$\n(somente útil se não tiver instalado drivers do fabricante para o seu dispositivo USB)"
+LangString msg_inslibusb ${LANG_PORTUGUESEBR} "Pretende instalar o driver LibUSB ?"
+LangString msg_inswinusb ${LANG_PORTUGUESEBR} "Pretende instalar o driver WinUSB ?"
+
+LangString shortcut_brlttycnf $(LANG_PORTUGUESEBR) "Configurar o BRLTTY"
+LangString shortcut_brlttydebug $(LANG_PORTUGUESEBR) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_PORTUGUESEBR) "Instalar O Serviço BRLTTY"
+LangString shortcut_rmvsrv $(LANG_PORTUGUESEBR) "Desinstalar O Serviço BRLTTY"
+LangString shortcut_uninstall $(LANG_PORTUGUESEBR) "Desinstalar"
+LangString shortcut_inslibusbfilter $(LANG_PORTUGUESEBR) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_PORTUGUESEBR) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_PORTUGUESEBR) "Instalar o driver LibUSB-Win32"
+
+;Portuguese
+LangString msg_WelcomePageTitle ${LANG_PORTUGUESE} "Setup para o ${PRODUCT}, Versão ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_PORTUGUESE} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_PORTUGUESE} "Pretende instalar o driver LibUSB-Win32?$\r$\n(somente útil se não tiver instalado drivers do fabricante para o seu dispositivo USB)"
+LangString msg_inslibusb ${LANG_PORTUGUESE} "Pretende instalar o driver LibUSB?"
+LangString msg_inswinusb ${LANG_PORTUGUESE} "Pretende instalar o driver WinUSB?"
+
+LangString shortcut_brlttycnf $(LANG_PORTUGUESE) "Configurar o BRLTTY"
+LangString shortcut_brlttydebug $(LANG_PORTUGUESE) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_PORTUGUESE) "Instalar O Serviço BRLTTY"
+LangString shortcut_rmvsrv $(LANG_PORTUGUESE) "Desinstalar O Serviço BRLTTY"
+LangString shortcut_uninstall $(LANG_PORTUGUESE) "Desinstalar"
+LangString shortcut_inslibusbfilter $(LANG_PORTUGUESE) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_PORTUGUESE) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_PORTUGUESE) "Instalar o driver LibUSB-Win32"
+
+;Russian
+LangString msg_WelcomePageTitle ${LANG_RUSSIAN} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_RUSSIAN} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_RUSSIAN} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_RUSSIAN} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_RUSSIAN} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_RUSSIAN) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_RUSSIAN) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_RUSSIAN) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_RUSSIAN) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_RUSSIAN) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_RUSSIAN) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_RUSSIAN) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_RUSSIAN) "Install LibUSB-Win32 driver"
+
+;Swedish
+LangString msg_WelcomePageTitle ${LANG_SWEDISH} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_SWEDISH} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_SWEDISH} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_SWEDISH} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_SWEDISH} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_SWEDISH) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_SWEDISH) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_SWEDISH) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_SWEDISH) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_SWEDISH) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_SWEDISH) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_SWEDISH) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_SWEDISH) "Install LibUSB-Win32 driver"
+
+;Slovak
+LangString msg_WelcomePageTitle ${LANG_SLOVAK} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_SLOVAK} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_SLOVAK} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_SLOVAK} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_SLOVAK} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_SLOVAK) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_SLOVAK) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_SLOVAK) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_SLOVAK) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_SLOVAK) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_SLOVAK) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_SLOVAK) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_SLOVAK) "Install LibUSB-Win32 driver"
+
+;Thai
+LangString msg_WelcomePageTitle ${LANG_THAI} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_THAI} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_THAI} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_THAI} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_THAI} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_THAI) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_THAI) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_THAI) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_THAI) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_THAI) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_THAI) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_THAI) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_THAI) "Install LibUSB-Win32 driver"
+
+;Chinese Simplified
+LangString msg_WelcomePageTitle ${LANG_SIMPCHINESE} "Setup for ${PRODUCT}, Version ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_SIMPCHINESE} "Would you like to install the LibUSB-Win32 filter?$\r$\n(only useful for USB devices and if you have already installed drivers from your manufacturer for your USB device. Do not install it if you are running windows 64bit, or Vista, or 7)"
+LangString msg_inslibusb32 ${LANG_SIMPCHINESE} "Would you like to install the LibUSB-Win32 driver?$\r$\n(only useful if you do not have installed drivers from your manufacturer for your USB device)"
+LangString msg_inslibusb ${LANG_SIMPCHINESE} "Would you like to install the LibUSB driver?"
+LangString msg_inswinusb ${LANG_SIMPCHINESE} "Would you like to install the WinUSB driver?"
+
+LangString shortcut_brlttycnf $(LANG_SIMPCHINESE) "Configure BRLTTY"
+LangString shortcut_brlttydebug $(LANG_SIMPCHINESE) "Run BRLTTY in debugging mode"
+LangString shortcut_inssrv $(LANG_SIMPCHINESE) "Install BRLTTY Service"
+LangString shortcut_rmvsrv $(LANG_SIMPCHINESE) "Uninstall BRLTTY Service"
+LangString shortcut_uninstall $(LANG_SIMPCHINESE) "Uninstall"
+LangString shortcut_inslibusbfilter $(LANG_SIMPCHINESE) "Install LibUSB-Win32 filter"
+LangString shortcut_uninslibusbfilter $(LANG_SIMPCHINESE) "Uninstall LibUSB-Win32 filter"
+LangString shortcut_inslibusb $(LANG_SIMPCHINESE) "Install LibUSB-Win32 driver"
+
+;Chinese Traditional
+LangString msg_WelcomePageTitle ${LANG_TRADCHINESE} "¦w¸Ë ${PRODUCT}, ª©¥» ${VERSION}"
+LangString msg_inslibusb32filter ${LANG_TRADCHINESE} "¬O§_­n¦w¸Ë LibUSB-Win32 ¿z¿ï¾¹¡H$\r$\n¡]¦³¦w¸Ë­ì¼t USB ÅX°Êµ{¦¡¤~»Ý­n¡C¦pªG¬O 64 ¦ì¤¸ Windows, ©Î Vista, ©Î win7, ½Ð§O¦w¸Ë¡^"
+LangString msg_inslibusb32 ${LANG_TRADCHINESE} "¬O§_­n¦w¸Ë LibUSB-Win32 ÅX°Êµ{¦¡¡H\r$\n¡]¨S¦³¦w¸Ë­ì¼t USB ÅX°Êµ{¦¡¤~»Ý­n¡^"
+LangString msg_inslibusb ${LANG_TRADCHINESE} "¬O§_­n¦w¸Ë LibUSB ÅX°Êµ{¦¡¡"
+LangString msg_inswinusb ${LANG_TRADCHINESE} "¬O§_­n¦w¸Ë WinUSB ÅX°Êµ{¦¡¡"
+
+LangString shortcut_brlttycnf $(LANG_TRADCHINESE) "³]©w BRLTTY"
+LangString shortcut_brlttydebug $(LANG_TRADCHINESE) "¥H°»¿ù¼Ò¦¡°õ¦æ BRLTTY"
+LangString shortcut_inssrv $(LANG_TRADCHINESE) "¦w¸Ë BRLTTY ªA°È"
+LangString shortcut_rmvsrv $(LANG_TRADCHINESE) "²¾°£ BRLTTY ªA°È"
+LangString shortcut_uninstall $(LANG_TRADCHINESE) "²¾°£"
+LangString shortcut_inslibusbfilter $(LANG_TRADCHINESE) "¦w¸Ë LibUSB-Win32 ¿z¿ï¾¹"
+LangString shortcut_uninslibusbfilter $(LANG_TRADCHINESE) "²¾°£ LibUSB-Win32 ¿z¿ï¾¹"
+LangString shortcut_inslibusb $(LANG_TRADCHINESE) "¦w¸Ë LibUSB-Win32 ÅX°Êµ{¦¡"
diff --git a/Windows/regquery-brlapi.bat b/Windows/regquery-brlapi.bat
new file mode 100644
index 0000000..6df79a1
--- /dev/null
+++ b/Windows/regquery-brlapi.bat
@@ -0,0 +1,7 @@
+@echo off
+
+setlocal EnableDelayedExpansion
+call "%programDirectory%setvars-brlapi"
+
+reg query "%serviceKey%"
+exit /B %ERRORLEVEL%
diff --git a/Windows/run-brltty.bat b/Windows/run-brltty.bat
new file mode 100755
index 0000000..9945dd9
--- /dev/null
+++ b/Windows/run-brltty.bat
@@ -0,0 +1,14 @@
+@echo off
+
+setlocal
+set programDirectory=%~dp0
+call "%programDirectory%setvars-brltty"
+set logLevel=info
+
+if "%programName%" == "%packageName%" (
+   echo Running %programName%. When done, close this window ^(for example, by using the
+   echo Alt-Space menu^). The log file is "%logFile%".
+)
+
+"%programDirectory%bin\brltty" -n -N -U "%updatableDirectory%" -W "%writableDirectory%" -P "%pidFile%" -L "%logFile%" -l "%logLevel%" -f "%configurationFile%" %*
+exit /B %ERRORLEVEL%
diff --git a/Windows/setvars-brlapi.bat b/Windows/setvars-brlapi.bat
new file mode 100644
index 0000000..4a42bbf
--- /dev/null
+++ b/Windows/setvars-brlapi.bat
@@ -0,0 +1,7 @@
+@echo off
+
+set serviceName=BrlAPI
+set serviceKey=HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\%serviceName%
+set commandValue=ImagePath
+
+exit /B 0
diff --git a/Windows/setvars-brltty.bat b/Windows/setvars-brltty.bat
new file mode 100644
index 0000000..573c438
--- /dev/null
+++ b/Windows/setvars-brltty.bat
@@ -0,0 +1,13 @@
+@echo off
+
+set packageName=brltty
+if "%programName%" == ""  set programName=%packageName%
+
+set updatableDirectory=%programDirectory%etc
+set writableDirectory=%programDirectory%run
+
+set configurationFile=%programDirectory%etc\%packageName%.conf
+set logFile=%programDirectory%%programName%.log
+set pidFile=%writableDirectory%\%programName%.pid
+
+exit /B 0
diff --git a/Windows/tasks-brltty.bat b/Windows/tasks-brltty.bat
new file mode 100644
index 0000000..294dfad
--- /dev/null
+++ b/Windows/tasks-brltty.bat
@@ -0,0 +1,8 @@
+@echo off
+
+setlocal
+set programDirectory=%~dp0
+call "%programDirectory%setvars-brltty"
+
+tasklist /V /FI "imageName eq %packageName%.*"
+exit /B %ERRORLEVEL%
diff --git a/Windows/winsetup b/Windows/winsetup
new file mode 100755
index 0000000..c2553ba
--- /dev/null
+++ b/Windows/winsetup
@@ -0,0 +1,350 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/../brltty-prologue.sh"
+. "${programDirectory}/mingw.sh"
+
+set -e
+umask 022
+
+readonly defaultArchiveRoot="http://brltty.app/archive/Windows"
+readonly defaultTemporaryDirectory="${TMPDIR:-/tmp}/brltty-${programName}"
+
+havePath() {
+   local path="${1}"
+
+   [ -n "${path}" ] || return 1
+   [ -e "${path}" ] || return 2
+   return 0
+}
+
+verifyDirectory() {
+   local path="${1}"
+
+   [ -e "${path}" ] || semanticError "directory not found: ${path}"
+   [ -d "${path}" ] || semanticError "not a directory: ${path}"
+   [ -w "${path}" ] || semanticError "directory not writable: ${path}"
+   [ -r "${path}" ] || semanticError "directory not readable: ${path}"
+   [ -x "${path}" ] || semanticError "directory not searchable: ${path}"
+}
+
+findCommand() {
+   local variable="${1}"
+   shift 1
+
+   for name
+   do
+      local path="$(command -v "${name}")"
+      [ -n "${path}" ] && {
+         setVariable "${variable}" "${path}"
+         return 0
+      }
+   done
+
+   return 1
+}
+
+executeCommand() {
+   logMessage detail "executing command: ${*}"
+
+   exitStatus=0
+   "${@}" || exitStatus="${?}"
+
+   [ "${exitStatus}" -eq 0 ] || logWarning "command failed: ${1}: exit status ${exitStatus}"
+   return "${exitStatus}"
+}
+
+downloadFile() {
+   local source="${1}"
+   local target="${2}"
+
+   logNote "downloading file: ${source}"
+   [ -z "${target}" ] && target="${source##*/}"
+   executeCommand wget -nv -O "${target}" "${archiveRoot}/${source}"
+}
+
+unpackArchive() {
+   local path="${1}"
+
+   case "${path}"
+   in
+      *.zip) set -- unzip -q;;
+      *.tar.gz) set -- tar xfz;;
+      *) semanticError "unsupported archive: ${path}";;
+   esac
+
+   executeCommand "${@}" "${path}"
+}
+
+applyPatch() {
+   local patch="${1}"
+   local directory="${2}"
+   local file="${3}"
+   local pattern="${4}"
+
+   local path="${directory}/${file}"
+
+   if [ -f "${path}" ]
+   then
+      grep -q "${pattern}" "${path}" || {
+         local name="${patch##*/}"
+         logMessage task "applying patch: ${name}"
+
+         "${dryRun}" || {
+            downloadFile "${patch}"
+            executeCommand patch -N -i "${temporaryDirectory}/${name}" -d "${directory}" "${file}"
+         }
+      }
+   else
+      logWarning "file not found: ${path}"
+   fi
+}
+
+installFile() {
+   local file="${1}"
+   local directory="${2}"
+
+   local source="${file##*/}"
+   local target="${directory}/${source}"
+
+   [ -e "${target}" ] || {
+      logMessage task "installing file: ${target}"
+
+      "${dryRun}" || {
+         downloadFile "${file}"
+         [ -d "${directory}" ] || executeCommand mkdir -p "${directory}"
+         executeCommand cp "${source}" "${target}"
+      }
+   }
+}
+
+installLink() {
+   local to="${1}"
+   local directory="${2}"
+   local name="${3}"
+
+   [ -n "${name}" ] || name="${to##*/}"
+   local from="${directory}/${name}"
+
+   havePath "${from}" || {
+      logMessage task "installing link: ${from} -> ${to}"
+      [ -e "${to}" ] || semanticError "link target not found: ${to}"
+      "${dryRun}" || executeCommand ln -s "${to}" "${from}"
+   }
+}
+
+installWindowsArchive() {
+   local archive="${1}"
+   local path="${2}"
+
+   havePath "${path}" || {
+      logMessage task "installing Windows archive: ${archive}"
+
+      "${dryRun}" || {
+         downloadFile "${archive}"
+
+         local directory="content-${archive}"
+         executeCommand mkdir "${directory}"
+         executeCommand cd "${directory}"
+
+         unpackArchive "../${archive}"
+         set -- *
+
+         if [ "${#}" -eq 1 -a -d "${1}" ]
+         then
+            executeCommand mv "${1}" "${path}"
+            executeCommand cd ..
+            executeCommand rmdir "${directory}"
+         else
+            executeCommand cd ..
+            executeCommand mv "${directory}" "${path}"
+         fi
+      }
+   }
+}
+
+installMingwArchive() {
+   local archive="${1}"
+   local path="${2}"
+
+   havePath "${mingwDirectory}/${path}" || {
+      logMessage task "installing MinGW archive: ${archive}"
+
+      "${dryRun}" || {
+         downloadFile "MinGW/${archive}"
+         executeCommand cd "${mingwDirectory}"
+         unpackArchive "${temporaryDirectory}/${archive}"
+         executeCommand cd "${temporaryDirectory}"
+      }
+   }
+}
+
+installMingwPackage() {
+   local package="${1}"
+   local path="${2}"
+
+   havePath "${path}" || {
+      logMessage task "installing MinGW package: ${package}"
+      "${dryRun}" || executeCommand mingw-get --verbose=0 install "${package}"
+   }
+}
+
+installMicrosoftVisualCTools() {
+   local file
+
+   for file in lib.exe link.exe mspdb100.dll msvcr100.dll
+   do
+      installFile "MSVC/${file}" "/usr/local/bin"
+   done
+}
+
+installPackageConfigurationFiles() {
+   local package
+
+   for package in libusb libusb-1.0
+   do
+      installFile "pkgconfig/${package}.pc" "${librariesDirectory}/pkgconfig"
+   done
+}
+
+installBluetoothHeaders() {
+   local name
+
+   for name in ws2bth bthdef bthsdpdef
+   do
+      installFile "Bluetooth/${name}.h" "${headersDirectory}"
+   done
+}
+
+installLibUSB0() {
+   local directory="${windowsDirectory}/LibUSB-Win32"
+   installWindowsArchive libusb-win32-bin-1.2.6.0.zip "${directory}"
+   installLink "${directory}/include/lusb0_usb.h" "${headersDirectory}" usb.h
+   installLink "${directory}/lib/gcc/libusb.a" "${librariesDirectory}"
+   installLink "${directory}/bin/x86/libusb0_x86.dll" "${commandsDirectory}" libusb0.dll
+}
+
+installLibUSB1() {
+   local directory="${windowsDirectory}/LibUSB-1.0"
+   installWindowsArchive libusbx-1.0.18-win.tar.gz "${directory}"
+   installLink "${directory}/include/libusbx-1.0" "${headersDirectory}" libusb-1.0
+   installLink "${directory}/MinGW32/dll/libusb-1.0.dll.a" "${librariesDirectory}"
+   installLink "${directory}/MinGW32/dll/libusb-1.0.dll" "${commandsDirectory}"
+   installWindowsArchive winusb.zip "${windowsDirectory}/WinUSB"
+}
+
+installCython() {
+   local python
+
+   if findCommand python python py
+   then
+      export PYTHONIOENCODING="utf-8"
+      local directory="${python%/*}"
+
+      findCommand cython cython || {
+         logMessage task "installing Cython"
+         "${dryRun}" || executeCommand pip -q install cython
+      }
+
+      installFile Cython/vcruntime140.dll "${directory}/libs"
+      installFile Cython/distutils.cfg "${directory}/lib/distutils"
+      applyPatch "Cython/PythonCygwinCCompiler.patch" "${directory}/lib/distutils" "cygwinccompiler.py" "vcruntime140"
+   else
+      logWarning "Python not found"
+   fi
+}
+
+addProgramOption a string.URL archiveRoot "the URL for BRLTTY's Windows archive" "${defaultArchiveRoot}"
+addProgramOption d flag dryRun "dry run - don't actually install anything"
+addProgramOption k flag keepFiles "keep (do not remove) the temporary directory"
+addProgramOption p string.directory pythonDirectory "the directory where Python has been installed"
+addProgramOption t string.directory temporaryDirectory "the temporary directory to use" "${defaultTemporaryDirectory}"
+parseProgramArguments "${@}"
+
+[ -n "${archiveRoot}" ] || archiveRoot="${defaultArchiveRoot}"
+
+[ -n "${pythonDirectory}" ] && {
+   [ -d "${pythonDirectory}" ] || semanticError "Python directory not found: ${pythonDirectory}"
+   PATH="${pythonDirectory}:${pythonDirectory}/scripts:${PATH}"
+}
+
+readonly windowsDirectory="/c"
+verifyDirectory "${windowsDirectory}"
+
+readonly mingwDirectory="/mingw"
+verifyDirectory "${mingwDirectory}"
+
+readonly commandsDirectory="${mingwDirectory}/bin"
+verifyDirectory "${commandsDirectory}"
+
+readonly librariesDirectory="${mingwDirectory}/lib"
+verifyDirectory "${librariesDirectory}"
+
+readonly headersDirectory="${mingwDirectory}/include"
+verifyDirectory "${headersDirectory}"
+
+if [ -z "${temporaryDirectory}" ]
+then
+   temporaryDirectory="${defaultTemporaryDirectory}"
+   rm -f -r "${temporaryDirectory}"
+elif [ -e "${temporaryDirectory}" ]
+then
+   semanticError "directory already exists: ${temporaryDirectory}"
+fi
+
+mkdir -p "${temporaryDirectory}"
+cd "${temporaryDirectory}"
+temporaryDirectory="$(pwd -W)"
+
+installMingwPackage msys-bison /bin/bison
+installMingwPackage msys-dos2unix /bin/dos2unix
+installMingwPackage msys-groff /bin/groff
+installMingwPackage msys-m4 /bin/m4
+installMingwPackage msys-tar /bin/tar
+installMingwPackage msys-unzip /bin/unzip
+installMingwPackage msys-wget /bin/wget
+installMingwPackage msys-zip /bin/zip
+
+installMingwPackage mingw32-libpdcurses /mingw/include/curses.h
+installMingwPackage mingw32-pthreads-w32 /mingw/include/pthread.h
+installMingwPackage mingw32-tcl /mingw/bin/tclsh
+
+installMicrosoftVisualCTools
+installPackageConfigurationFiles
+installBluetoothHeaders
+
+installMingwArchive pkg-config_0.28-1_win32.zip "bin/pkg-config.exe"
+installMingwArchive glib_2.34.3-1_win32.zip "bin/libgobject-2.0-0.dll"
+
+installWindowsArchive AutoHotkey104805.tar.gz "${ahkLocation}"
+installWindowsArchive nsis-3.0b0.tar.gz "${nsisLocation}"
+installWindowsArchive icu4c-53_1-Win32-msvc10.zip "${icuLocation}"
+
+installLibUSB0
+installLibUSB1
+installCython
+
+"${keepFiles}" || {
+   logMessage task "cleaning up"
+   cd "${initialDirectory}"
+   rm -f -r "${temporaryDirectory}"
+}
+
+logMessage task "done"
+exit 0
diff --git a/Windows/winusb.inf b/Windows/winusb.inf
new file mode 100644
index 0000000..5d90348
--- /dev/null
+++ b/Windows/winusb.inf
@@ -0,0 +1,810 @@
+[Strings]
+ProviderName  = "BRLTTY"
+DriverDate    = "12/21/2021"
+DriverVersion = "6.4.0.228"
+ClassName     = "USB Braille Devices"
+ClassGUID     = "{1720fa8e-ed15-4346-b73d-909eb41e89ef}"
+
+REG_MULTI_SZ = 0x00010000
+
+[Version]
+Signature = "$Windows NT$"
+Class     = %ClassName%
+ClassGUID = %ClassGUID%
+Provider  = %ProviderName%
+CatalogFile = WinUSBInstallation.cat
+DriverVer = %DriverDate%,%DriverVersion%
+
+; ========== Manufacturer/Models sections ===========
+
+[Manufacturer]
+%ProviderName% = Standard,NTx86,NTamd64
+
+[Standard.NTx86]
+; BEGIN_USB_BRAILLE_DEVICES winusb USB_Install
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"winusb: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=USB_Install, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"winusb: DotPad [all models]"=USB_Install, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"winusb: Hedo [MobilLine]"=USB_Install, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"winusb: Hedo [ProfiLine]"=USB_Install, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"winusb: Papenmeier [all models]"=USB_Install, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"winusb: Baum [Vario 40 (40 cells)]"=USB_Install, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"winusb: Baum [PocketVario (24 cells)]"=USB_Install, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"winusb: Baum [SuperVario 40 (40 cells)]"=USB_Install, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"winusb: Baum [SuperVario 32 (32 cells)]"=USB_Install, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"winusb: Baum [SuperVario 64 (64 cells)]"=USB_Install, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"winusb: Baum [SuperVario 80 (80 cells)]"=USB_Install, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"winusb: Baum [VarioPro 80 (80 cells)]"=USB_Install, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"winusb: Baum [VarioPro 64 (64 cells)]"=USB_Install, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"winusb: Metec [all models]"=USB_Install, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"winusb: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=USB_Install, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"winusb: HIMS [Braille Edge and QBrailleXL]"=USB_Install, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"winusb: Baum [Orbit Reader 20 (20 cells)]"=USB_Install, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"winusb: Baum [Orbit Reader 40 (40 cells)]"=USB_Install, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"winusb: Alva [Satellite (5nn)]"=USB_Install, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"winusb: Voyager [all models]"=USB_Install, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"winusb: Alva [Voyager Protocol Converter]"=USB_Install, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"winusb: Alva [BC624]"=USB_Install, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"winusb: Alva [BC640]"=USB_Install, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"winusb: Alva [BC680]"=USB_Install, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"winusb: FrankAudiodata [B2K84 (before firmware installation)]"=USB_Install, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"winusb: FrankAudiodata [B2K84 (after firmware installation)]"=USB_Install, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"winusb: Baum [VarioPro 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"winusb: Baum [EcoVario 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"winusb: Baum [EcoVario 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"winusb: Baum [VarioConnect 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"winusb: Baum [VarioConnect 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"winusb: Baum [VarioConnect 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"winusb: Baum [VarioConnect 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"winusb: Baum [VarioConnect 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"winusb: Baum [EcoVario 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"winusb: Baum [EcoVario 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"winusb: Baum [EcoVario 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"winusb: Baum [Refreshabraille 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"winusb: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"winusb: Baum [Pronto! V3 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"winusb: Baum [Pronto! V3 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"winusb: Baum [Pronto! V4 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"winusb: Baum [Pronto! V4 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"winusb: Baum [SuperVario2 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"winusb: Baum [PocketVario2 (24 cells)]"=USB_Install, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"winusb: Baum [SuperVario2 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"winusb: Baum [SuperVario2 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"winusb: Baum [SuperVario2 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"winusb: Baum [Brailliant2 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"winusb: Baum [Brailliant2 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"winusb: Baum [Brailliant2 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"winusb: Baum [Brailliant2 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"winusb: Baum [Brailliant2 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"winusb: Baum [VarioConnect 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"winusb: Baum [VarioConnect 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"winusb: Baum [VarioConnect 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"winusb: Baum [VarioUltra 20 (20 cells)]"=USB_Install, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"winusb: Baum [VarioUltra 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"winusb: Baum [VarioUltra 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"winusb: HandyTech [GoHubs chip]"=USB_Install, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"winusb: FreedomScientific [Focus 1]"=USB_Install, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"winusb: FreedomScientific [PAC Mate]"=USB_Install, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"winusb: FreedomScientific [Focus 2]"=USB_Install, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"winusb: FreedomScientific [Focus 3+]"=USB_Install, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"winusb: BrailleMemo [Pocket], Seika [Braille Display]"=USB_Install, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"winusb: Seika [Note Taker]"=USB_Install, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"winusb: BrailleMemo [Smart]"=USB_Install, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"winusb: Inceptor [all models]"=USB_Install, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"winusb: Canute [all models]"=USB_Install, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"winusb: Baum [NLS eReader Zoomax (20 cells)]"=USB_Install, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"winusb: BrailleNote [HumanWare APEX]"=USB_Install, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"winusb: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"winusb: HumanWare [non-Touch models (HID protocol)]"=USB_Install, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"winusb: HumanWare [BrailleNote Touch (HID protocol)]"=USB_Install, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"winusb: HumanWare [Brailliant BI 14 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"winusb: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"winusb: HumanWare [APH Chameleon 20 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"winusb: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"winusb: HumanWare [APH Mantis Q40 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"winusb: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"winusb: HumanWare [Humanware BrailleOne (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"winusb: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"winusb: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"winusb: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"winusb: HumanWare [NLS eReader (serial protocol)]"=USB_Install, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"winusb: HandyTech [USB-HID adapter]"=USB_Install, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"winusb: HandyTech [Easy Braille (HID)]"=USB_Install, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"winusb: HandyTech [Active Braille]"=USB_Install, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"winusb: HandyTech [Connect Braille 40]"=USB_Install, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"winusb: HandyTech [Actilino]"=USB_Install, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"winusb: HandyTech [Active Star 40]"=USB_Install, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"winusb: HandyTech [Braille Star 40 (HID)]"=USB_Install, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"winusb: HandyTech [Basic Braille 16]"=USB_Install, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"winusb: HandyTech [Basic Braille 20]"=USB_Install, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"winusb: HandyTech [Basic Braille 32]"=USB_Install, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"winusb: HandyTech [Basic Braille 40]"=USB_Install, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"winusb: HandyTech [Basic Braille 64]"=USB_Install, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"winusb: HandyTech [Basic Braille 80]"=USB_Install, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"winusb: HandyTech [Basic Braille 48]"=USB_Install, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"winusb: HandyTech [Basic Braille 160]"=USB_Install, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"winusb: HandyTech [Activator]"=USB_Install, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"winusb: Pegasus [all models]"=USB_Install, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"winusb: EuroBraille [Esys (version < 3.0, no SD card)]"=USB_Install, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"winusb: EuroBraille [Esys (version < 3.0, with SD card)]"=USB_Install, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"winusb: EuroBraille [Esys (version >= 3.0, no SD card)]"=USB_Install, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"winusb: EuroBraille [Esys (version >= 3.0, with SD card)]"=USB_Install, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"winusb: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=USB_Install, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"WinUSB: Microsoft Narrator"=USB_Install, USB\NarratorOverride
+
+[Standard.NTamd64]
+; BEGIN_USB_BRAILLE_DEVICES winusb USB_Install
+
+; Device: 0403:6001
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd.
+; Product: FT232 USB-Serial (UART) IC
+"winusb: Albatross [all models], Cebra [all models], HIMS [Sync Braille], HandyTech [FTDI chip], Hedo [MobilLine], MDV [all models]"=USB_Install, USB\VID_0403&PID_6001
+
+; Device: 0403:6010
+; Generic Identifier
+; Vendor: Future Technology Devices International, Ltd
+; Product: FT2232C/D/H Dual UART/FIFO IC
+"winusb: DotPad [all models]"=USB_Install, USB\VID_0403&PID_6010
+
+; Device: 0403:DE58
+"winusb: Hedo [MobilLine]"=USB_Install, USB\VID_0403&PID_DE58
+
+; Device: 0403:DE59
+"winusb: Hedo [ProfiLine]"=USB_Install, USB\VID_0403&PID_DE59
+
+; Device: 0403:F208
+"winusb: Papenmeier [all models]"=USB_Install, USB\VID_0403&PID_F208
+
+; Device: 0403:FE70
+"winusb: Baum [Vario 40 (40 cells)]"=USB_Install, USB\VID_0403&PID_FE70
+
+; Device: 0403:FE71
+"winusb: Baum [PocketVario (24 cells)]"=USB_Install, USB\VID_0403&PID_FE71
+
+; Device: 0403:FE72
+"winusb: Baum [SuperVario 40 (40 cells)]"=USB_Install, USB\VID_0403&PID_FE72
+
+; Device: 0403:FE73
+"winusb: Baum [SuperVario 32 (32 cells)]"=USB_Install, USB\VID_0403&PID_FE73
+
+; Device: 0403:FE74
+"winusb: Baum [SuperVario 64 (64 cells)]"=USB_Install, USB\VID_0403&PID_FE74
+
+; Device: 0403:FE75
+"winusb: Baum [SuperVario 80 (80 cells)]"=USB_Install, USB\VID_0403&PID_FE75
+
+; Device: 0403:FE76
+"winusb: Baum [VarioPro 80 (80 cells)]"=USB_Install, USB\VID_0403&PID_FE76
+
+; Device: 0403:FE77
+"winusb: Baum [VarioPro 64 (64 cells)]"=USB_Install, USB\VID_0403&PID_FE77
+
+; Device: 0452:0100
+"winusb: Metec [all models]"=USB_Install, USB\VID_0452&PID_0100
+
+; Device: 045E:930A
+"winusb: HIMS [Braille Sense (USB 1.1)], HIMS [Braille Sense (USB 2.0)], HIMS [Braille Sense U2 (USB 2.0)]"=USB_Install, USB\VID_045E&PID_930A
+
+; Device: 045E:930B
+"winusb: HIMS [Braille Edge and QBrailleXL]"=USB_Install, USB\VID_045E&PID_930B
+
+; Device: 0483:A1D3
+"winusb: Baum [Orbit Reader 20 (20 cells)]"=USB_Install, USB\VID_0483&PID_A1D3
+
+; Device: 0483:A366
+"winusb: Baum [Orbit Reader 40 (40 cells)]"=USB_Install, USB\VID_0483&PID_A366
+
+; Device: 06B0:0001
+"winusb: Alva [Satellite (5nn)]"=USB_Install, USB\VID_06B0&PID_0001
+
+; Device: 0798:0001
+"winusb: Voyager [all models]"=USB_Install, USB\VID_0798&PID_0001
+
+; Device: 0798:0600
+"winusb: Alva [Voyager Protocol Converter]"=USB_Install, USB\VID_0798&PID_0600
+
+; Device: 0798:0624
+"winusb: Alva [BC624]"=USB_Install, USB\VID_0798&PID_0624
+
+; Device: 0798:0640
+"winusb: Alva [BC640]"=USB_Install, USB\VID_0798&PID_0640
+
+; Device: 0798:0680
+"winusb: Alva [BC680]"=USB_Install, USB\VID_0798&PID_0680
+
+; Device: 0904:1016
+"winusb: FrankAudiodata [B2K84 (before firmware installation)]"=USB_Install, USB\VID_0904&PID_1016
+
+; Device: 0904:1017
+"winusb: FrankAudiodata [B2K84 (after firmware installation)]"=USB_Install, USB\VID_0904&PID_1017
+
+; Device: 0904:2000
+"winusb: Baum [VarioPro 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2000
+
+; Device: 0904:2001
+"winusb: Baum [EcoVario 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_2001
+
+; Device: 0904:2002
+"winusb: Baum [EcoVario 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2002
+
+; Device: 0904:2007
+"winusb: Baum [VarioConnect 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_2007
+
+; Device: 0904:2008
+"winusb: Baum [VarioConnect 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_2008
+
+; Device: 0904:2009
+"winusb: Baum [VarioConnect 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_2009
+
+; Device: 0904:2010
+"winusb: Baum [VarioConnect 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_2010
+
+; Device: 0904:2011
+"winusb: Baum [VarioConnect 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_2011
+
+; Device: 0904:2014
+"winusb: Baum [EcoVario 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_2014
+
+; Device: 0904:2015
+"winusb: Baum [EcoVario 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_2015
+
+; Device: 0904:2016
+"winusb: Baum [EcoVario 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_2016
+
+; Device: 0904:3000
+"winusb: Baum [Refreshabraille 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_3000
+
+; Device: 0904:3001
+"winusb: Baum [Orbit in Refreshabraille Emulation Mode (18 cells)], Baum [Refreshabraille 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_3001
+
+; Device: 0904:4004
+"winusb: Baum [Pronto! V3 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_4004
+
+; Device: 0904:4005
+"winusb: Baum [Pronto! V3 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_4005
+
+; Device: 0904:4007
+"winusb: Baum [Pronto! V4 18 (18 cells)]"=USB_Install, USB\VID_0904&PID_4007
+
+; Device: 0904:4008
+"winusb: Baum [Pronto! V4 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_4008
+
+; Device: 0904:6001
+"winusb: Baum [SuperVario2 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6001
+
+; Device: 0904:6002
+"winusb: Baum [PocketVario2 (24 cells)]"=USB_Install, USB\VID_0904&PID_6002
+
+; Device: 0904:6003
+"winusb: Baum [SuperVario2 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6003
+
+; Device: 0904:6004
+"winusb: Baum [SuperVario2 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_6004
+
+; Device: 0904:6005
+"winusb: Baum [SuperVario2 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_6005
+
+; Device: 0904:6006
+"winusb: Baum [Brailliant2 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6006
+
+; Device: 0904:6007
+"winusb: Baum [Brailliant2 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_6007
+
+; Device: 0904:6008
+"winusb: Baum [Brailliant2 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6008
+
+; Device: 0904:6009
+"winusb: Baum [Brailliant2 64 (64 cells)]"=USB_Install, USB\VID_0904&PID_6009
+
+; Device: 0904:600A
+"winusb: Baum [Brailliant2 80 (80 cells)]"=USB_Install, USB\VID_0904&PID_600A
+
+; Device: 0904:6011
+"winusb: Baum [VarioConnect 24 (24 cells)]"=USB_Install, USB\VID_0904&PID_6011
+
+; Device: 0904:6012
+"winusb: Baum [VarioConnect 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6012
+
+; Device: 0904:6013
+"winusb: Baum [VarioConnect 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6013
+
+; Device: 0904:6101
+"winusb: Baum [VarioUltra 20 (20 cells)]"=USB_Install, USB\VID_0904&PID_6101
+
+; Device: 0904:6102
+"winusb: Baum [VarioUltra 40 (40 cells)]"=USB_Install, USB\VID_0904&PID_6102
+
+; Device: 0904:6103
+"winusb: Baum [VarioUltra 32 (32 cells)]"=USB_Install, USB\VID_0904&PID_6103
+
+; Device: 0921:1200
+"winusb: HandyTech [GoHubs chip]"=USB_Install, USB\VID_0921&PID_1200
+
+; Device: 0F4E:0100
+"winusb: FreedomScientific [Focus 1]"=USB_Install, USB\VID_0F4E&PID_0100
+
+; Device: 0F4E:0111
+"winusb: FreedomScientific [PAC Mate]"=USB_Install, USB\VID_0F4E&PID_0111
+
+; Device: 0F4E:0112
+"winusb: FreedomScientific [Focus 2]"=USB_Install, USB\VID_0F4E&PID_0112
+
+; Device: 0F4E:0114
+"winusb: FreedomScientific [Focus 3+]"=USB_Install, USB\VID_0F4E&PID_0114
+
+; Device: 10C4:EA60
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge / myAVR mySmartUSB light
+"winusb: BrailleMemo [Pocket], Seika [Braille Display]"=USB_Install, USB\VID_10C4&PID_EA60
+
+; Device: 10C4:EA80
+; Generic Identifier
+; Vendor: Cygnal Integrated Products, Inc.
+; Product: CP210x UART Bridge
+"winusb: Seika [Note Taker]"=USB_Install, USB\VID_10C4&PID_EA80
+
+; Device: 1148:0301
+"winusb: BrailleMemo [Smart]"=USB_Install, USB\VID_1148&PID_0301
+
+; Device: 1209:ABC0
+"winusb: Inceptor [all models]"=USB_Install, USB\VID_1209&PID_ABC0
+
+; Device: 16C0:05E1
+"winusb: Canute [all models]"=USB_Install, USB\VID_16C0&PID_05E1
+
+; Device: 1A86:7523
+; Generic Identifier
+; Vendor: Jiangsu QinHeng, Ltd.
+; Product: CH341 USB Bridge Controller
+"winusb: Baum [NLS eReader Zoomax (20 cells)]"=USB_Install, USB\VID_1A86&PID_7523
+
+; Device: 1C71:C004
+"winusb: BrailleNote [HumanWare APEX]"=USB_Install, USB\VID_1C71&PID_C004
+
+; Device: 1C71:C005
+"winusb: HumanWare [Brailliant BI 32/40, Brailliant B 80 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C005
+
+; Device: 1C71:C006
+"winusb: HumanWare [non-Touch models (HID protocol)]"=USB_Install, USB\VID_1C71&PID_C006
+
+; Device: 1C71:C00A
+"winusb: HumanWare [BrailleNote Touch (HID protocol)]"=USB_Install, USB\VID_1C71&PID_C00A
+
+; Device: 1C71:C021
+"winusb: HumanWare [Brailliant BI 14 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C021
+
+; Device: 1C71:C101
+"winusb: HumanWare [APH Chameleon 20 (HID protocol, firmware 1.0)], HumanWare [APH Chameleon 20 (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C101
+
+; Device: 1C71:C104
+"winusb: HumanWare [APH Chameleon 20 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C104
+
+; Device: 1C71:C111
+"winusb: HumanWare [APH Mantis Q40 (HID protocol, firmware 1.0)], HumanWare [APH Mantis Q40 (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C111
+
+; Device: 1C71:C114
+"winusb: HumanWare [APH Mantis Q40 (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C114
+
+; Device: 1C71:C121
+"winusb: HumanWare [Humanware BrailleOne (HID protocol, firmware 1.0)], HumanWare [Humanware BrailleOne (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C121
+
+; Device: 1C71:C124
+"winusb: HumanWare [Humanware BrailleOne (serial protocol)]"=USB_Install, USB\VID_1C71&PID_C124
+
+; Device: 1C71:C131
+"winusb: HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 40X (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C131
+
+; Device: 1C71:C141
+"winusb: HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.0)], HumanWare [Humanware Brailliant BI 20X (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_C141
+
+; Device: 1C71:CE01
+"winusb: HumanWare [NLS eReader (HID protocol, firmware 1.0)], HumanWare [NLS eReader (HID protocol, firmware 1.1)]"=USB_Install, USB\VID_1C71&PID_CE01
+
+; Device: 1C71:CE04
+"winusb: HumanWare [NLS eReader (serial protocol)]"=USB_Install, USB\VID_1C71&PID_CE04
+
+; Device: 1FE4:0003
+"winusb: HandyTech [USB-HID adapter]"=USB_Install, USB\VID_1FE4&PID_0003
+
+; Device: 1FE4:0044
+"winusb: HandyTech [Easy Braille (HID)]"=USB_Install, USB\VID_1FE4&PID_0044
+
+; Device: 1FE4:0054
+"winusb: HandyTech [Active Braille]"=USB_Install, USB\VID_1FE4&PID_0054
+
+; Device: 1FE4:0055
+"winusb: HandyTech [Connect Braille 40]"=USB_Install, USB\VID_1FE4&PID_0055
+
+; Device: 1FE4:0061
+"winusb: HandyTech [Actilino]"=USB_Install, USB\VID_1FE4&PID_0061
+
+; Device: 1FE4:0064
+"winusb: HandyTech [Active Star 40]"=USB_Install, USB\VID_1FE4&PID_0064
+
+; Device: 1FE4:0074
+"winusb: HandyTech [Braille Star 40 (HID)]"=USB_Install, USB\VID_1FE4&PID_0074
+
+; Device: 1FE4:0081
+"winusb: HandyTech [Basic Braille 16]"=USB_Install, USB\VID_1FE4&PID_0081
+
+; Device: 1FE4:0082
+"winusb: HandyTech [Basic Braille 20]"=USB_Install, USB\VID_1FE4&PID_0082
+
+; Device: 1FE4:0083
+"winusb: HandyTech [Basic Braille 32]"=USB_Install, USB\VID_1FE4&PID_0083
+
+; Device: 1FE4:0084
+"winusb: HandyTech [Basic Braille 40]"=USB_Install, USB\VID_1FE4&PID_0084
+
+; Device: 1FE4:0086
+"winusb: HandyTech [Basic Braille 64]"=USB_Install, USB\VID_1FE4&PID_0086
+
+; Device: 1FE4:0087
+"winusb: HandyTech [Basic Braille 80]"=USB_Install, USB\VID_1FE4&PID_0087
+
+; Device: 1FE4:008A
+"winusb: HandyTech [Basic Braille 48]"=USB_Install, USB\VID_1FE4&PID_008A
+
+; Device: 1FE4:008B
+"winusb: HandyTech [Basic Braille 160]"=USB_Install, USB\VID_1FE4&PID_008B
+
+; Device: 1FE4:00A4
+"winusb: HandyTech [Activator]"=USB_Install, USB\VID_1FE4&PID_00A4
+
+; Device: 4242:0001
+"winusb: Pegasus [all models]"=USB_Install, USB\VID_4242&PID_0001
+
+; Device: C251:1122
+"winusb: EuroBraille [Esys (version < 3.0, no SD card)]"=USB_Install, USB\VID_C251&PID_1122
+
+; Device: C251:1123
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1123
+
+; Device: C251:1124
+"winusb: EuroBraille [Esys (version < 3.0, with SD card)]"=USB_Install, USB\VID_C251&PID_1124
+
+; Device: C251:1125
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1125
+
+; Device: C251:1126
+"winusb: EuroBraille [Esys (version >= 3.0, no SD card)]"=USB_Install, USB\VID_C251&PID_1126
+
+; Device: C251:1127
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1127
+
+; Device: C251:1128
+"winusb: EuroBraille [Esys (version >= 3.0, with SD card)]"=USB_Install, USB\VID_C251&PID_1128
+
+; Device: C251:1129
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1129
+
+; Device: C251:112A
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112A
+
+; Device: C251:112B
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112B
+
+; Device: C251:112C
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112C
+
+; Device: C251:112D
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112D
+
+; Device: C251:112E
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112E
+
+; Device: C251:112F
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_112F
+
+; Device: C251:1130
+"winusb: EuroBraille [Esytime (firmware 1.03, 2014-03-31)], EuroBraille [Esytime]"=USB_Install, USB\VID_C251&PID_1130
+
+; Device: C251:1131
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1131
+
+; Device: C251:1132
+"winusb: EuroBraille [reserved]"=USB_Install, USB\VID_C251&PID_1132
+
+; END_USB_BRAILLE_DEVICES
+
+; Microsoft Narrator Device
+"WinUSB: Microsoft Narrator"=USB_Install, USB\NarratorOverride
+
+; ========== Class definition (for Windows 8 and ealier versions)===========
+
+; =================== Installation ===================
+
+[USB_Install]
+Include = winusb.inf
+Needs   = WINUSB.NT
+
+[USB_Install.Services]
+Include =winusb.inf
+Needs   = WINUSB.NT.Services
+
+[USB_Install.HW]
+AddReg=Dev_AddReg
+
+[USB_Install.Wdf]
+KmdfService=WINUSB, WinUsb_Install
+
+[WinUsb_Install]
+KmdfLibraryVersion=1.11
+
+[Dev_AddReg]
+HKR,,DeviceInterfaceGUIDs,0x10000,"{9f543223-cede-4fa3-b376-a25ce9a30e74}"
+
+; [DestinationDirs]
+; If your INF needs to copy files, you must not use the DefaultDestDir directive here.
+; You must explicitly reference all file-list-section names in this section.
+
diff --git a/Windows/winusb.nsi b/Windows/winusb.nsi
new file mode 100644
index 0000000..1dc31ed
--- /dev/null
+++ b/Windows/winusb.nsi
@@ -0,0 +1,24 @@
+!define VARIANT "winusb"
+
+!macro BrlttyInstall
+!macroend
+
+!macro BrlttyShortcut
+!macroend
+
+!macro BrlttyTweak
+!define sysSetupCopyOEMInf "setupapi::SetupCopyOEMInf(t, t, i, i, i, i, *i, t) i"
+	System::Get '${sysSetupCopyOEMInf}'
+	Pop $0
+	StrCmp $0 'error' nodriver
+	MessageBox MB_YESNO|MB_DEFBUTTON2 $(msg_inswinusb) IDNO nodriver
+	System::Call '${sysSetupCopyOEMInf}?e ("$INSTDIR\bin\brltty-winusb.inf", "$INSTDIR\bin", 1, 0, 0, 0, 0, 0) .r0'
+nodriver:
+!macroend
+
+!macro BrlttyUninstall
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\WdfCoInstaller01009.dll"
+	!insertmacro UninstallLib DLL NOTSHARED REBOOT_NOTPROTECTED "$SYSDIR\winusbcoinstaller2.dll"
+!macroend
+
+!include brltty.nsi
diff --git a/acdir/config.guess b/acdir/config.guess
new file mode 100755
index 0000000..16fc625
--- /dev/null
+++ b/acdir/config.guess
@@ -0,0 +1,1768 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright 1992-2023 Free Software Foundation, Inc.
+
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2022-05-25'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program.  This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+#
+# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
+#
+# You can get the latest version of this script from:
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
+#
+# Please send patches to <config-patches@gnu.org>.
+
+
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX.  However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Options:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright 1992-2023 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+# Just in case it came from the environment.
+GUESS=
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+tmp=
+# shellcheck disable=SC2172
+trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
+
+set_cc_for_build() {
+    # prevent multiple calls if $tmp is already set
+    test "$tmp" && return 0
+    : "${TMPDIR=/tmp}"
+    # shellcheck disable=SC2039,SC3028
+    { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+	{ test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
+	{ tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+	{ echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
+    dummy=$tmp/dummy
+    case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
+	,,)    echo "int x;" > "$dummy.c"
+	       for driver in cc gcc c89 c99 ; do
+		   if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
+		       CC_FOR_BUILD=$driver
+		       break
+		   fi
+	       done
+	       if test x"$CC_FOR_BUILD" = x ; then
+		   CC_FOR_BUILD=no_compiler_found
+	       fi
+	       ;;
+	,,*)   CC_FOR_BUILD=$CC ;;
+	,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+    esac
+}
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if test -f /.attbin/uname ; then
+	PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+case $UNAME_SYSTEM in
+Linux|GNU|GNU/*)
+	LIBC=unknown
+
+	set_cc_for_build
+	cat <<-EOF > "$dummy.c"
+	#include <features.h>
+	#if defined(__UCLIBC__)
+	LIBC=uclibc
+	#elif defined(__dietlibc__)
+	LIBC=dietlibc
+	#elif defined(__GLIBC__)
+	LIBC=gnu
+	#else
+	#include <stdarg.h>
+	/* First heuristic to detect musl libc.  */
+	#ifdef __DEFINED_va_list
+	LIBC=musl
+	#endif
+	#endif
+	EOF
+	cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+	eval "$cc_set_libc"
+
+	# Second heuristic to detect musl libc.
+	if [ "$LIBC" = unknown ] &&
+	   command -v ldd >/dev/null &&
+	   ldd --version 2>&1 | grep -q ^musl; then
+		LIBC=musl
+	fi
+
+	# If the system lacks a compiler, then just pick glibc.
+	# We could probably try harder.
+	if [ "$LIBC" = unknown ]; then
+		LIBC=gnu
+	fi
+	;;
+esac
+
+# Note: order is significant - the case branches are not exclusive.
+
+case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in
+    *:NetBSD:*:*)
+	# NetBSD (nbsd) targets should (where applicable) match one or
+	# more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
+	# *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+	# switched to ELF, *-*-netbsd* would select the old
+	# object file format.  This provides both forward
+	# compatibility and a consistent mechanism for selecting the
+	# object file format.
+	#
+	# Note: NetBSD doesn't particularly care about the vendor
+	# portion of the name.  We always set it to "unknown".
+	UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
+	    /sbin/sysctl -n hw.machine_arch 2>/dev/null || \
+	    /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
+	    echo unknown)`
+	case $UNAME_MACHINE_ARCH in
+	    aarch64eb) machine=aarch64_be-unknown ;;
+	    armeb) machine=armeb-unknown ;;
+	    arm*) machine=arm-unknown ;;
+	    sh3el) machine=shl-unknown ;;
+	    sh3eb) machine=sh-unknown ;;
+	    sh5el) machine=sh5le-unknown ;;
+	    earmv*)
+		arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
+		endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'`
+		machine=${arch}${endian}-unknown
+		;;
+	    *) machine=$UNAME_MACHINE_ARCH-unknown ;;
+	esac
+	# The Operating System including object format, if it has switched
+	# to ELF recently (or will in the future) and ABI.
+	case $UNAME_MACHINE_ARCH in
+	    earm*)
+		os=netbsdelf
+		;;
+	    arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+		set_cc_for_build
+		if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+			| grep -q __ELF__
+		then
+		    # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+		    # Return netbsd for either.  FIX?
+		    os=netbsd
+		else
+		    os=netbsdelf
+		fi
+		;;
+	    *)
+		os=netbsd
+		;;
+	esac
+	# Determine ABI tags.
+	case $UNAME_MACHINE_ARCH in
+	    earm*)
+		expr='s/^earmv[0-9]/-eabi/;s/eb$//'
+		abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"`
+		;;
+	esac
+	# The OS release
+	# Debian GNU/NetBSD machines have a different userland, and
+	# thus, need a distinct triplet. However, they do not need
+	# kernel version information, so it can be replaced with a
+	# suitable tag, in the style of linux-gnu.
+	case $UNAME_VERSION in
+	    Debian*)
+		release='-gnu'
+		;;
+	    *)
+		release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2`
+		;;
+	esac
+	# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+	# contains redundant information, the shorter form:
+	# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+	GUESS=$machine-${os}${release}${abi-}
+	;;
+    *:Bitrig:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE
+	;;
+    *:OpenBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE
+	;;
+    *:SecBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE
+	;;
+    *:LibertyBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE
+	;;
+    *:MidnightBSD:*:*)
+	GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE
+	;;
+    *:ekkoBSD:*:*)
+	GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE
+	;;
+    *:SolidBSD:*:*)
+	GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE
+	;;
+    *:OS108:*:*)
+	GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE
+	;;
+    macppc:MirBSD:*:*)
+	GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE
+	;;
+    *:MirBSD:*:*)
+	GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE
+	;;
+    *:Sortix:*:*)
+	GUESS=$UNAME_MACHINE-unknown-sortix
+	;;
+    *:Twizzler:*:*)
+	GUESS=$UNAME_MACHINE-unknown-twizzler
+	;;
+    *:Redox:*:*)
+	GUESS=$UNAME_MACHINE-unknown-redox
+	;;
+    mips:OSF1:*.*)
+	GUESS=mips-dec-osf1
+	;;
+    alpha:OSF1:*:*)
+	# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+	trap '' 0
+	case $UNAME_RELEASE in
+	*4.0)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+		;;
+	*5.*)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+		;;
+	esac
+	# According to Compaq, /usr/sbin/psrinfo has been available on
+	# OSF/1 and Tru64 systems produced since 1995.  I hope that
+	# covers most systems running today.  This code pipes the CPU
+	# types through head -n 1, so we only detect the type of CPU 0.
+	ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+	case $ALPHA_CPU_TYPE in
+	    "EV4 (21064)")
+		UNAME_MACHINE=alpha ;;
+	    "EV4.5 (21064)")
+		UNAME_MACHINE=alpha ;;
+	    "LCA4 (21066/21068)")
+		UNAME_MACHINE=alpha ;;
+	    "EV5 (21164)")
+		UNAME_MACHINE=alphaev5 ;;
+	    "EV5.6 (21164A)")
+		UNAME_MACHINE=alphaev56 ;;
+	    "EV5.6 (21164PC)")
+		UNAME_MACHINE=alphapca56 ;;
+	    "EV5.7 (21164PC)")
+		UNAME_MACHINE=alphapca57 ;;
+	    "EV6 (21264)")
+		UNAME_MACHINE=alphaev6 ;;
+	    "EV6.7 (21264A)")
+		UNAME_MACHINE=alphaev67 ;;
+	    "EV6.8CB (21264C)")
+		UNAME_MACHINE=alphaev68 ;;
+	    "EV6.8AL (21264B)")
+		UNAME_MACHINE=alphaev68 ;;
+	    "EV6.8CX (21264D)")
+		UNAME_MACHINE=alphaev68 ;;
+	    "EV6.9A (21264/EV69A)")
+		UNAME_MACHINE=alphaev69 ;;
+	    "EV7 (21364)")
+		UNAME_MACHINE=alphaev7 ;;
+	    "EV7.9 (21364A)")
+		UNAME_MACHINE=alphaev79 ;;
+	esac
+	# A Pn.n version is a patched version.
+	# A Vn.n version is a released version.
+	# A Tn.n version is a released field test version.
+	# A Xn.n version is an unreleased experimental baselevel.
+	# 1.2 uses "1.2" for uname -r.
+	OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+	GUESS=$UNAME_MACHINE-dec-osf$OSF_REL
+	;;
+    Amiga*:UNIX_System_V:4.0:*)
+	GUESS=m68k-unknown-sysv4
+	;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+	GUESS=$UNAME_MACHINE-unknown-amigaos
+	;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+	GUESS=$UNAME_MACHINE-unknown-morphos
+	;;
+    *:OS/390:*:*)
+	GUESS=i370-ibm-openedition
+	;;
+    *:z/VM:*:*)
+	GUESS=s390-ibm-zvmoe
+	;;
+    *:OS400:*:*)
+	GUESS=powerpc-ibm-os400
+	;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+	GUESS=arm-acorn-riscix$UNAME_RELEASE
+	;;
+    arm*:riscos:*:*|arm*:RISCOS:*:*)
+	GUESS=arm-unknown-riscos
+	;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+	GUESS=hppa1.1-hitachi-hiuxmpp
+	;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+	# akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+	case `(/bin/universe) 2>/dev/null` in
+	    att) GUESS=pyramid-pyramid-sysv3 ;;
+	    *)   GUESS=pyramid-pyramid-bsd   ;;
+	esac
+	;;
+    NILE*:*:*:dcosx)
+	GUESS=pyramid-pyramid-svr4
+	;;
+    DRS?6000:unix:4.0:6*)
+	GUESS=sparc-icl-nx6
+	;;
+    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+	case `/usr/bin/uname -p` in
+	    sparc) GUESS=sparc-icl-nx7 ;;
+	esac
+	;;
+    s390x:SunOS:*:*)
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL
+	;;
+    sun4H:SunOS:5.*:*)
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-hal-solaris2$SUN_REL
+	;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-sun-solaris2$SUN_REL
+	;;
+    i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+	GUESS=i386-pc-auroraux$UNAME_RELEASE
+	;;
+    i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+	set_cc_for_build
+	SUN_ARCH=i386
+	# If there is a compiler, see if it is configured for 64-bit objects.
+	# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+	# This test works for both compilers.
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
+	    if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+		(CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \
+		grep IS_64BIT_ARCH >/dev/null
+	    then
+		SUN_ARCH=x86_64
+	    fi
+	fi
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=$SUN_ARCH-pc-solaris2$SUN_REL
+	;;
+    sun4*:SunOS:6*:*)
+	# According to config.sub, this is the proper way to canonicalize
+	# SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+	# it's likely to be more like Solaris than SunOS4.
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-sun-solaris3$SUN_REL
+	;;
+    sun4*:SunOS:*:*)
+	case `/usr/bin/arch -k` in
+	    Series*|S4*)
+		UNAME_RELEASE=`uname -v`
+		;;
+	esac
+	# Japanese Language versions have a version number like `4.1.3-JL'.
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
+	GUESS=sparc-sun-sunos$SUN_REL
+	;;
+    sun3*:SunOS:*:*)
+	GUESS=m68k-sun-sunos$UNAME_RELEASE
+	;;
+    sun*:*:4.2BSD:*)
+	UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+	test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
+	case `/bin/arch` in
+	    sun3)
+		GUESS=m68k-sun-sunos$UNAME_RELEASE
+		;;
+	    sun4)
+		GUESS=sparc-sun-sunos$UNAME_RELEASE
+		;;
+	esac
+	;;
+    aushp:SunOS:*:*)
+	GUESS=sparc-auspex-sunos$UNAME_RELEASE
+	;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+	GUESS=m68k-milan-mint$UNAME_RELEASE
+	;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+	GUESS=m68k-hades-mint$UNAME_RELEASE
+	;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+	GUESS=m68k-unknown-mint$UNAME_RELEASE
+	;;
+    m68k:machten:*:*)
+	GUESS=m68k-apple-machten$UNAME_RELEASE
+	;;
+    powerpc:machten:*:*)
+	GUESS=powerpc-apple-machten$UNAME_RELEASE
+	;;
+    RISC*:Mach:*:*)
+	GUESS=mips-dec-mach_bsd4.3
+	;;
+    RISC*:ULTRIX:*:*)
+	GUESS=mips-dec-ultrix$UNAME_RELEASE
+	;;
+    VAX*:ULTRIX*:*:*)
+	GUESS=vax-dec-ultrix$UNAME_RELEASE
+	;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+	GUESS=clipper-intergraph-clix$UNAME_RELEASE
+	;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+	set_cc_for_build
+	sed 's/^	//' << EOF > "$dummy.c"
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+	int main (int argc, char *argv[]) {
+#else
+	int main (argc, argv) int argc; char *argv[]; {
+#endif
+	#if defined (host_mips) && defined (MIPSEB)
+	#if defined (SYSTYPE_SYSV)
+	  printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_SVR4)
+	  printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+	  printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
+	#endif
+	#endif
+	  exit (-1);
+	}
+EOF
+	$CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
+	  dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+	  SYSTEM_NAME=`"$dummy" "$dummyarg"` &&
+	    { echo "$SYSTEM_NAME"; exit; }
+	GUESS=mips-mips-riscos$UNAME_RELEASE
+	;;
+    Motorola:PowerMAX_OS:*:*)
+	GUESS=powerpc-motorola-powermax
+	;;
+    Motorola:*:4.3:PL8-*)
+	GUESS=powerpc-harris-powermax
+	;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+	GUESS=powerpc-harris-powermax
+	;;
+    Night_Hawk:Power_UNIX:*:*)
+	GUESS=powerpc-harris-powerunix
+	;;
+    m88k:CX/UX:7*:*)
+	GUESS=m88k-harris-cxux7
+	;;
+    m88k:*:4*:R4*)
+	GUESS=m88k-motorola-sysv4
+	;;
+    m88k:*:3*:R3*)
+	GUESS=m88k-motorola-sysv3
+	;;
+    AViiON:dgux:*:*)
+	# DG/UX returns AViiON for all architectures
+	UNAME_PROCESSOR=`/usr/bin/uname -p`
+	if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
+	then
+	    if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
+	       test "$TARGET_BINARY_INTERFACE"x = x
+	    then
+		GUESS=m88k-dg-dgux$UNAME_RELEASE
+	    else
+		GUESS=m88k-dg-dguxbcs$UNAME_RELEASE
+	    fi
+	else
+	    GUESS=i586-dg-dgux$UNAME_RELEASE
+	fi
+	;;
+    M88*:DolphinOS:*:*)	# DolphinOS (SVR3)
+	GUESS=m88k-dolphin-sysv3
+	;;
+    M88*:*:R3*:*)
+	# Delta 88k system running SVR3
+	GUESS=m88k-motorola-sysv3
+	;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+	GUESS=m88k-tektronix-sysv3
+	;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+	GUESS=m68k-tektronix-bsd
+	;;
+    *:IRIX*:*:*)
+	IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'`
+	GUESS=mips-sgi-irix$IRIX_REL
+	;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+	GUESS=romp-ibm-aix    # uname -m gives an 8 hex-code CPU id
+	;;                    # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+	GUESS=i386-ibm-aix
+	;;
+    ia64:AIX:*:*)
+	if test -x /usr/bin/oslevel ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
+	fi
+	GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV
+	;;
+    *:AIX:2:3)
+	if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+		set_cc_for_build
+		sed 's/^		//' << EOF > "$dummy.c"
+		#include <sys/systemcfg.h>
+
+		main()
+			{
+			if (!__power_pc())
+				exit(1);
+			puts("powerpc-ibm-aix3.2.5");
+			exit(0);
+			}
+EOF
+		if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"`
+		then
+			GUESS=$SYSTEM_NAME
+		else
+			GUESS=rs6000-ibm-aix3.2.5
+		fi
+	elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+		GUESS=rs6000-ibm-aix3.2.4
+	else
+		GUESS=rs6000-ibm-aix3.2
+	fi
+	;;
+    *:AIX:*:[4567])
+	IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+	if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
+		IBM_ARCH=rs6000
+	else
+		IBM_ARCH=powerpc
+	fi
+	if test -x /usr/bin/lslpp ; then
+		IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \
+			   awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
+	else
+		IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
+	fi
+	GUESS=$IBM_ARCH-ibm-aix$IBM_REV
+	;;
+    *:AIX:*:*)
+	GUESS=rs6000-ibm-aix
+	;;
+    ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
+	GUESS=romp-ibm-bsd4.4
+	;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+	GUESS=romp-ibm-bsd$UNAME_RELEASE    # 4.3 with uname added to
+	;;                                  # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+	GUESS=rs6000-bull-bosx
+	;;
+    DPX/2?00:B.O.S.:*:*)
+	GUESS=m68k-bull-sysv3
+	;;
+    9000/[34]??:4.3bsd:1.*:*)
+	GUESS=m68k-hp-bsd
+	;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+	GUESS=m68k-hp-bsd4.4
+	;;
+    9000/[34678]??:HP-UX:*:*)
+	HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+	case $UNAME_MACHINE in
+	    9000/31?)            HP_ARCH=m68000 ;;
+	    9000/[34]??)         HP_ARCH=m68k ;;
+	    9000/[678][0-9][0-9])
+		if test -x /usr/bin/getconf; then
+		    sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+		    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+		    case $sc_cpu_version in
+		      523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
+		      528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
+		      532)                      # CPU_PA_RISC2_0
+			case $sc_kernel_bits in
+			  32) HP_ARCH=hppa2.0n ;;
+			  64) HP_ARCH=hppa2.0w ;;
+			  '') HP_ARCH=hppa2.0 ;;   # HP-UX 10.20
+			esac ;;
+		    esac
+		fi
+		if test "$HP_ARCH" = ""; then
+		    set_cc_for_build
+		    sed 's/^		//' << EOF > "$dummy.c"
+
+		#define _HPUX_SOURCE
+		#include <stdlib.h>
+		#include <unistd.h>
+
+		int main ()
+		{
+		#if defined(_SC_KERNEL_BITS)
+		    long bits = sysconf(_SC_KERNEL_BITS);
+		#endif
+		    long cpu  = sysconf (_SC_CPU_VERSION);
+
+		    switch (cpu)
+			{
+			case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+			case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+			case CPU_PA_RISC2_0:
+		#if defined(_SC_KERNEL_BITS)
+			    switch (bits)
+				{
+				case 64: puts ("hppa2.0w"); break;
+				case 32: puts ("hppa2.0n"); break;
+				default: puts ("hppa2.0"); break;
+				} break;
+		#else  /* !defined(_SC_KERNEL_BITS) */
+			    puts ("hppa2.0"); break;
+		#endif
+			default: puts ("hppa1.0"); break;
+			}
+		    exit (0);
+		}
+EOF
+		    (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"`
+		    test -z "$HP_ARCH" && HP_ARCH=hppa
+		fi ;;
+	esac
+	if test "$HP_ARCH" = hppa2.0w
+	then
+	    set_cc_for_build
+
+	    # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+	    # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
+	    # generating 64-bit code.  GNU and HP use different nomenclature:
+	    #
+	    # $ CC_FOR_BUILD=cc ./config.guess
+	    # => hppa2.0w-hp-hpux11.23
+	    # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+	    # => hppa64-hp-hpux11.23
+
+	    if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
+		grep -q __LP64__
+	    then
+		HP_ARCH=hppa2.0w
+	    else
+		HP_ARCH=hppa64
+	    fi
+	fi
+	GUESS=$HP_ARCH-hp-hpux$HPUX_REV
+	;;
+    ia64:HP-UX:*:*)
+	HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+	GUESS=ia64-hp-hpux$HPUX_REV
+	;;
+    3050*:HI-UX:*:*)
+	set_cc_for_build
+	sed 's/^	//' << EOF > "$dummy.c"
+	#include <unistd.h>
+	int
+	main ()
+	{
+	  long cpu = sysconf (_SC_CPU_VERSION);
+	  /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+	     true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+	     results, however.  */
+	  if (CPU_IS_PA_RISC (cpu))
+	    {
+	      switch (cpu)
+		{
+		  case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+		  default: puts ("hppa-hitachi-hiuxwe2"); break;
+		}
+	    }
+	  else if (CPU_IS_HP_MC68K (cpu))
+	    puts ("m68k-hitachi-hiuxwe2");
+	  else puts ("unknown-hitachi-hiuxwe2");
+	  exit (0);
+	}
+EOF
+	$CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` &&
+		{ echo "$SYSTEM_NAME"; exit; }
+	GUESS=unknown-hitachi-hiuxwe2
+	;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
+	GUESS=hppa1.1-hp-bsd
+	;;
+    9000/8??:4.3bsd:*:*)
+	GUESS=hppa1.0-hp-bsd
+	;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+	GUESS=hppa1.0-hp-mpeix
+	;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
+	GUESS=hppa1.1-hp-osf
+	;;
+    hp8??:OSF1:*:*)
+	GUESS=hppa1.0-hp-osf
+	;;
+    i*86:OSF1:*:*)
+	if test -x /usr/sbin/sysversion ; then
+	    GUESS=$UNAME_MACHINE-unknown-osf1mk
+	else
+	    GUESS=$UNAME_MACHINE-unknown-osf1
+	fi
+	;;
+    parisc*:Lites*:*:*)
+	GUESS=hppa1.1-hp-lites
+	;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+	GUESS=c1-convex-bsd
+	;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+	exit ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+	GUESS=c34-convex-bsd
+	;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+	GUESS=c38-convex-bsd
+	;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+	GUESS=c4-convex-bsd
+	;;
+    CRAY*Y-MP:*:*:*)
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=ymp-cray-unicos$CRAY_REL
+	;;
+    CRAY*[A-Z]90:*:*:*)
+	echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
+	| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+	      -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+	      -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*TS:*:*:*)
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=t90-cray-unicos$CRAY_REL
+	;;
+    CRAY*T3E:*:*:*)
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=alphaev5-cray-unicosmk$CRAY_REL
+	;;
+    CRAY*SV1:*:*:*)
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=sv1-cray-unicos$CRAY_REL
+	;;
+    *:UNICOS/mp:*:*)
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=craynv-cray-unicosmp$CRAY_REL
+	;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+	FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+	FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+	FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'`
+	GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+	;;
+    5000:UNIX_System_V:4.*:*)
+	FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+	FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
+	GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+	;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+	GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE
+	;;
+    sparc*:BSD/OS:*:*)
+	GUESS=sparc-unknown-bsdi$UNAME_RELEASE
+	;;
+    *:BSD/OS:*:*)
+	GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE
+	;;
+    arm:FreeBSD:*:*)
+	UNAME_PROCESSOR=`uname -p`
+	set_cc_for_build
+	if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+	    | grep -q __ARM_PCS_VFP
+	then
+	    FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	    GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi
+	else
+	    FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	    GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf
+	fi
+	;;
+    *:FreeBSD:*:*)
+	UNAME_PROCESSOR=`/usr/bin/uname -p`
+	case $UNAME_PROCESSOR in
+	    amd64)
+		UNAME_PROCESSOR=x86_64 ;;
+	    i386)
+		UNAME_PROCESSOR=i586 ;;
+	esac
+	FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL
+	;;
+    i*:CYGWIN*:*)
+	GUESS=$UNAME_MACHINE-pc-cygwin
+	;;
+    *:MINGW64*:*)
+	GUESS=$UNAME_MACHINE-pc-mingw64
+	;;
+    *:MINGW*:*)
+	GUESS=$UNAME_MACHINE-pc-mingw32
+	;;
+    *:MSYS*:*)
+	GUESS=$UNAME_MACHINE-pc-msys
+	;;
+    i*:PW*:*)
+	GUESS=$UNAME_MACHINE-pc-pw32
+	;;
+    *:SerenityOS:*:*)
+        GUESS=$UNAME_MACHINE-pc-serenity
+        ;;
+    *:Interix*:*)
+	case $UNAME_MACHINE in
+	    x86)
+		GUESS=i586-pc-interix$UNAME_RELEASE
+		;;
+	    authenticamd | genuineintel | EM64T)
+		GUESS=x86_64-unknown-interix$UNAME_RELEASE
+		;;
+	    IA64)
+		GUESS=ia64-unknown-interix$UNAME_RELEASE
+		;;
+	esac ;;
+    i*:UWIN*:*)
+	GUESS=$UNAME_MACHINE-pc-uwin
+	;;
+    amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+	GUESS=x86_64-pc-cygwin
+	;;
+    prep*:SunOS:5.*:*)
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=powerpcle-unknown-solaris2$SUN_REL
+	;;
+    *:GNU:*:*)
+	# the GNU system
+	GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'`
+	GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'`
+	GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL
+	;;
+    *:GNU/*:*:*)
+	# other systems with GNU libc and userland
+	GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"`
+	GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC
+	;;
+    *:Minix:*:*)
+	GUESS=$UNAME_MACHINE-unknown-minix
+	;;
+    aarch64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    aarch64_be:Linux:*:*)
+	UNAME_MACHINE=aarch64_be
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    alpha:Linux:*:*)
+	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in
+	  EV5)   UNAME_MACHINE=alphaev5 ;;
+	  EV56)  UNAME_MACHINE=alphaev56 ;;
+	  PCA56) UNAME_MACHINE=alphapca56 ;;
+	  PCA57) UNAME_MACHINE=alphapca56 ;;
+	  EV6)   UNAME_MACHINE=alphaev6 ;;
+	  EV67)  UNAME_MACHINE=alphaev67 ;;
+	  EV68*) UNAME_MACHINE=alphaev68 ;;
+	esac
+	objdump --private-headers /bin/sh | grep -q ld.so.1
+	if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    arm*:Linux:*:*)
+	set_cc_for_build
+	if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+	    | grep -q __ARM_EABI__
+	then
+	    GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	else
+	    if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+		| grep -q __ARM_PCS_VFP
+	    then
+		GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi
+	    else
+		GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf
+	    fi
+	fi
+	;;
+    avr32*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    cris:Linux:*:*)
+	GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+	;;
+    crisv32:Linux:*:*)
+	GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+	;;
+    e2k:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    frv:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    hexagon:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    i*86:Linux:*:*)
+	GUESS=$UNAME_MACHINE-pc-linux-$LIBC
+	;;
+    ia64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    k1om:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    m32r*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    m68*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    mips:Linux:*:* | mips64:Linux:*:*)
+	set_cc_for_build
+	IS_GLIBC=0
+	test x"${LIBC}" = xgnu && IS_GLIBC=1
+	sed 's/^	//' << EOF > "$dummy.c"
+	#undef CPU
+	#undef mips
+	#undef mipsel
+	#undef mips64
+	#undef mips64el
+	#if ${IS_GLIBC} && defined(_ABI64)
+	LIBCABI=gnuabi64
+	#else
+	#if ${IS_GLIBC} && defined(_ABIN32)
+	LIBCABI=gnuabin32
+	#else
+	LIBCABI=${LIBC}
+	#endif
+	#endif
+
+	#if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+	CPU=mipsisa64r6
+	#else
+	#if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+	CPU=mipsisa32r6
+	#else
+	#if defined(__mips64)
+	CPU=mips64
+	#else
+	CPU=mips
+	#endif
+	#endif
+	#endif
+
+	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+	MIPS_ENDIAN=el
+	#else
+	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+	MIPS_ENDIAN=
+	#else
+	MIPS_ENDIAN=
+	#endif
+	#endif
+EOF
+	cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`
+	eval "$cc_set_vars"
+	test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
+	;;
+    mips64el:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    openrisc*:Linux:*:*)
+	GUESS=or1k-unknown-linux-$LIBC
+	;;
+    or32:Linux:*:* | or1k*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    padre:Linux:*:*)
+	GUESS=sparc-unknown-linux-$LIBC
+	;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+	GUESS=hppa64-unknown-linux-$LIBC
+	;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+	# Look for CPU level
+	case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+	  PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;;
+	  PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;;
+	  *)    GUESS=hppa-unknown-linux-$LIBC ;;
+	esac
+	;;
+    ppc64:Linux:*:*)
+	GUESS=powerpc64-unknown-linux-$LIBC
+	;;
+    ppc:Linux:*:*)
+	GUESS=powerpc-unknown-linux-$LIBC
+	;;
+    ppc64le:Linux:*:*)
+	GUESS=powerpc64le-unknown-linux-$LIBC
+	;;
+    ppcle:Linux:*:*)
+	GUESS=powerpcle-unknown-linux-$LIBC
+	;;
+    riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+	GUESS=$UNAME_MACHINE-ibm-linux-$LIBC
+	;;
+    sh64*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    sh*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    tile*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    vax:Linux:*:*)
+	GUESS=$UNAME_MACHINE-dec-linux-$LIBC
+	;;
+    x86_64:Linux:*:*)
+	set_cc_for_build
+	CPU=$UNAME_MACHINE
+	LIBCABI=$LIBC
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
+	    ABI=64
+	    sed 's/^	    //' << EOF > "$dummy.c"
+	    #ifdef __i386__
+	    ABI=x86
+	    #else
+	    #ifdef __ILP32__
+	    ABI=x32
+	    #endif
+	    #endif
+EOF
+	    cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'`
+	    eval "$cc_set_abi"
+	    case $ABI in
+		x86) CPU=i686 ;;
+		x32) LIBCABI=${LIBC}x32 ;;
+	    esac
+	fi
+	GUESS=$CPU-pc-linux-$LIBCABI
+	;;
+    xtensa*:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    i*86:DYNIX/ptx:4*:*)
+	# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+	# earlier versions are messed up and put the nodename in both
+	# sysname and nodename.
+	GUESS=i386-sequent-sysv4
+	;;
+    i*86:UNIX_SV:4.2MP:2.*)
+	# Unixware is an offshoot of SVR4, but it has its own version
+	# number series starting with 2...
+	# I am not positive that other SVR4 systems won't match this,
+	# I just have to hope.  -- rms.
+	# Use sysv4.2uw... so that sysv4* matches it.
+	GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
+	;;
+    i*86:OS/2:*:*)
+	# If we were able to find `uname', then EMX Unix compatibility
+	# is probably installed.
+	GUESS=$UNAME_MACHINE-pc-os2-emx
+	;;
+    i*86:XTS-300:*:STOP)
+	GUESS=$UNAME_MACHINE-unknown-stop
+	;;
+    i*86:atheos:*:*)
+	GUESS=$UNAME_MACHINE-unknown-atheos
+	;;
+    i*86:syllable:*:*)
+	GUESS=$UNAME_MACHINE-pc-syllable
+	;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+	GUESS=i386-unknown-lynxos$UNAME_RELEASE
+	;;
+    i*86:*DOS:*:*)
+	GUESS=$UNAME_MACHINE-pc-msdosdjgpp
+	;;
+    i*86:*:4.*:*)
+	UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'`
+	if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+		GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL
+	else
+		GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL
+	fi
+	;;
+    i*86:*:5:[678]*)
+	# UnixWare 7.x, OpenUNIX and OpenServer 6.
+	case `/bin/uname -X | grep "^Machine"` in
+	    *486*)	     UNAME_MACHINE=i486 ;;
+	    *Pentium)	     UNAME_MACHINE=i586 ;;
+	    *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+	esac
+	GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+	;;
+    i*86:*:3.2:*)
+	if test -f /usr/options/cb.name; then
+		UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+		GUESS=$UNAME_MACHINE-pc-isc$UNAME_REL
+	elif /bin/uname -X 2>/dev/null >/dev/null ; then
+		UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+		(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+		(/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+			&& UNAME_MACHINE=i586
+		(/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL
+	else
+		GUESS=$UNAME_MACHINE-pc-sysv32
+	fi
+	;;
+    pc:*:*:*)
+	# Left here for compatibility:
+	# uname -m prints for DJGPP always 'pc', but it prints nothing about
+	# the processor, so we play safe by assuming i586.
+	# Note: whatever this is, it MUST be the same as what config.sub
+	# prints for the "djgpp" host, or else GDB configure will decide that
+	# this is a cross-build.
+	GUESS=i586-pc-msdosdjgpp
+	;;
+    Intel:Mach:3*:*)
+	GUESS=i386-pc-mach3
+	;;
+    paragon:*:*:*)
+	GUESS=i860-intel-osf1
+	;;
+    i860:*:4.*:*) # i860-SVR4
+	if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+	  GUESS=i860-stardent-sysv$UNAME_RELEASE    # Stardent Vistra i860-SVR4
+	else # Add other i860-SVR4 vendors below as they are discovered.
+	  GUESS=i860-unknown-sysv$UNAME_RELEASE     # Unknown i860-SVR4
+	fi
+	;;
+    mini*:CTIX:SYS*5:*)
+	# "miniframe"
+	GUESS=m68010-convergent-sysv
+	;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+	GUESS=m68k-convergent-sysv
+	;;
+    M680?0:D-NIX:5.3:*)
+	GUESS=m68k-diab-dnix
+	;;
+    M68*:*:R3V[5678]*:*)
+	test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+	OS_REL=''
+	test -r /etc/.relid \
+	&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	  && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && { echo i486-ncr-sysv4; exit; } ;;
+    NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+	OS_REL='.3'
+	test -r /etc/.relid \
+	    && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	    && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	    && { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+	    && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+	GUESS=m68k-unknown-lynxos$UNAME_RELEASE
+	;;
+    mc68030:UNIX_System_V:4.*:*)
+	GUESS=m68k-atari-sysv4
+	;;
+    TSUNAMI:LynxOS:2.*:*)
+	GUESS=sparc-unknown-lynxos$UNAME_RELEASE
+	;;
+    rs6000:LynxOS:2.*:*)
+	GUESS=rs6000-unknown-lynxos$UNAME_RELEASE
+	;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+	GUESS=powerpc-unknown-lynxos$UNAME_RELEASE
+	;;
+    SM[BE]S:UNIX_SV:*:*)
+	GUESS=mips-dde-sysv$UNAME_RELEASE
+	;;
+    RM*:ReliantUNIX-*:*:*)
+	GUESS=mips-sni-sysv4
+	;;
+    RM*:SINIX-*:*:*)
+	GUESS=mips-sni-sysv4
+	;;
+    *:SINIX-*:*:*)
+	if uname -p 2>/dev/null >/dev/null ; then
+		UNAME_MACHINE=`(uname -p) 2>/dev/null`
+		GUESS=$UNAME_MACHINE-sni-sysv4
+	else
+		GUESS=ns32k-sni-sysv
+	fi
+	;;
+    PENTIUM:*:4.0*:*)	# Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+			# says <Richard.M.Bartel@ccMail.Census.GOV>
+	GUESS=i586-unisys-sysv4
+	;;
+    *:UNIX_System_V:4*:FTX*)
+	# From Gerald Hewes <hewes@openmarket.com>.
+	# How about differentiating between stratus architectures? -djm
+	GUESS=hppa1.1-stratus-sysv4
+	;;
+    *:*:*:FTX*)
+	# From seanf@swdc.stratus.com.
+	GUESS=i860-stratus-sysv4
+	;;
+    i*86:VOS:*:*)
+	# From Paul.Green@stratus.com.
+	GUESS=$UNAME_MACHINE-stratus-vos
+	;;
+    *:VOS:*:*)
+	# From Paul.Green@stratus.com.
+	GUESS=hppa1.1-stratus-vos
+	;;
+    mc68*:A/UX:*:*)
+	GUESS=m68k-apple-aux$UNAME_RELEASE
+	;;
+    news*:NEWS-OS:6*:*)
+	GUESS=mips-sony-newsos6
+	;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+	if test -d /usr/nec; then
+		GUESS=mips-nec-sysv$UNAME_RELEASE
+	else
+		GUESS=mips-unknown-sysv$UNAME_RELEASE
+	fi
+	;;
+    BeBox:BeOS:*:*)	# BeOS running on hardware made by Be, PPC only.
+	GUESS=powerpc-be-beos
+	;;
+    BeMac:BeOS:*:*)	# BeOS running on Mac or Mac clone, PPC only.
+	GUESS=powerpc-apple-beos
+	;;
+    BePC:BeOS:*:*)	# BeOS running on Intel PC compatible.
+	GUESS=i586-pc-beos
+	;;
+    BePC:Haiku:*:*)	# Haiku running on Intel PC compatible.
+	GUESS=i586-pc-haiku
+	;;
+    ppc:Haiku:*:*)	# Haiku running on Apple PowerPC
+	GUESS=powerpc-apple-haiku
+	;;
+    *:Haiku:*:*)	# Haiku modern gcc (not bound by BeOS compat)
+	GUESS=$UNAME_MACHINE-unknown-haiku
+	;;
+    SX-4:SUPER-UX:*:*)
+	GUESS=sx4-nec-superux$UNAME_RELEASE
+	;;
+    SX-5:SUPER-UX:*:*)
+	GUESS=sx5-nec-superux$UNAME_RELEASE
+	;;
+    SX-6:SUPER-UX:*:*)
+	GUESS=sx6-nec-superux$UNAME_RELEASE
+	;;
+    SX-7:SUPER-UX:*:*)
+	GUESS=sx7-nec-superux$UNAME_RELEASE
+	;;
+    SX-8:SUPER-UX:*:*)
+	GUESS=sx8-nec-superux$UNAME_RELEASE
+	;;
+    SX-8R:SUPER-UX:*:*)
+	GUESS=sx8r-nec-superux$UNAME_RELEASE
+	;;
+    SX-ACE:SUPER-UX:*:*)
+	GUESS=sxace-nec-superux$UNAME_RELEASE
+	;;
+    Power*:Rhapsody:*:*)
+	GUESS=powerpc-apple-rhapsody$UNAME_RELEASE
+	;;
+    *:Rhapsody:*:*)
+	GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE
+	;;
+    arm64:Darwin:*:*)
+	GUESS=aarch64-apple-darwin$UNAME_RELEASE
+	;;
+    *:Darwin:*:*)
+	UNAME_PROCESSOR=`uname -p`
+	case $UNAME_PROCESSOR in
+	    unknown) UNAME_PROCESSOR=powerpc ;;
+	esac
+	if command -v xcode-select > /dev/null 2> /dev/null && \
+		! xcode-select --print-path > /dev/null 2> /dev/null ; then
+	    # Avoid executing cc if there is no toolchain installed as
+	    # cc will be a stub that puts up a graphical alert
+	    # prompting the user to install developer tools.
+	    CC_FOR_BUILD=no_compiler_found
+	else
+	    set_cc_for_build
+	fi
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
+	    if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+		   (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+		   grep IS_64BIT_ARCH >/dev/null
+	    then
+		case $UNAME_PROCESSOR in
+		    i386) UNAME_PROCESSOR=x86_64 ;;
+		    powerpc) UNAME_PROCESSOR=powerpc64 ;;
+		esac
+	    fi
+	    # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
+	    if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
+		   (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+		   grep IS_PPC >/dev/null
+	    then
+		UNAME_PROCESSOR=powerpc
+	    fi
+	elif test "$UNAME_PROCESSOR" = i386 ; then
+	    # uname -m returns i386 or x86_64
+	    UNAME_PROCESSOR=$UNAME_MACHINE
+	fi
+	GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE
+	;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+	UNAME_PROCESSOR=`uname -p`
+	if test "$UNAME_PROCESSOR" = x86; then
+		UNAME_PROCESSOR=i386
+		UNAME_MACHINE=pc
+	fi
+	GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE
+	;;
+    *:QNX:*:4*)
+	GUESS=i386-pc-qnx
+	;;
+    NEO-*:NONSTOP_KERNEL:*:*)
+	GUESS=neo-tandem-nsk$UNAME_RELEASE
+	;;
+    NSE-*:NONSTOP_KERNEL:*:*)
+	GUESS=nse-tandem-nsk$UNAME_RELEASE
+	;;
+    NSR-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsr-tandem-nsk$UNAME_RELEASE
+	;;
+    NSV-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsv-tandem-nsk$UNAME_RELEASE
+	;;
+    NSX-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsx-tandem-nsk$UNAME_RELEASE
+	;;
+    *:NonStop-UX:*:*)
+	GUESS=mips-compaq-nonstopux
+	;;
+    BS2000:POSIX*:*:*)
+	GUESS=bs2000-siemens-sysv
+	;;
+    DS/*:UNIX_System_V:*:*)
+	GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE
+	;;
+    *:Plan9:*:*)
+	# "uname -m" is not consistent, so use $cputype instead. 386
+	# is converted to i386 for consistency with other x86
+	# operating systems.
+	if test "${cputype-}" = 386; then
+	    UNAME_MACHINE=i386
+	elif test "x${cputype-}" != x; then
+	    UNAME_MACHINE=$cputype
+	fi
+	GUESS=$UNAME_MACHINE-unknown-plan9
+	;;
+    *:TOPS-10:*:*)
+	GUESS=pdp10-unknown-tops10
+	;;
+    *:TENEX:*:*)
+	GUESS=pdp10-unknown-tenex
+	;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+	GUESS=pdp10-dec-tops20
+	;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+	GUESS=pdp10-xkl-tops20
+	;;
+    *:TOPS-20:*:*)
+	GUESS=pdp10-unknown-tops20
+	;;
+    *:ITS:*:*)
+	GUESS=pdp10-unknown-its
+	;;
+    SEI:*:*:SEIUX)
+	GUESS=mips-sei-seiux$UNAME_RELEASE
+	;;
+    *:DragonFly:*:*)
+	DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL
+	;;
+    *:*VMS:*:*)
+	UNAME_MACHINE=`(uname -p) 2>/dev/null`
+	case $UNAME_MACHINE in
+	    A*) GUESS=alpha-dec-vms ;;
+	    I*) GUESS=ia64-dec-vms ;;
+	    V*) GUESS=vax-dec-vms ;;
+	esac ;;
+    *:XENIX:*:SysV)
+	GUESS=i386-pc-xenix
+	;;
+    i*86:skyos:*:*)
+	SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`
+	GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL
+	;;
+    i*86:rdos:*:*)
+	GUESS=$UNAME_MACHINE-pc-rdos
+	;;
+    i*86:Fiwix:*:*)
+	GUESS=$UNAME_MACHINE-pc-fiwix
+	;;
+    *:AROS:*:*)
+	GUESS=$UNAME_MACHINE-unknown-aros
+	;;
+    x86_64:VMkernel:*:*)
+	GUESS=$UNAME_MACHINE-unknown-esx
+	;;
+    amd64:Isilon\ OneFS:*:*)
+	GUESS=x86_64-unknown-onefs
+	;;
+    *:Unleashed:*:*)
+	GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
+	;;
+esac
+
+# Do we have a guess based on uname results?
+if test "x$GUESS" != x; then
+    echo "$GUESS"
+    exit
+fi
+
+# No uname command or uname output not recognized.
+set_cc_for_build
+cat > "$dummy.c" <<EOF
+#ifdef _SEQUENT_
+#include <sys/types.h>
+#include <sys/utsname.h>
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#include <signal.h>
+#if defined(_SIZE_T_) || defined(SIGLOST)
+#include <sys/utsname.h>
+#endif
+#endif
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+  "4"
+#else
+  ""
+#endif
+  ); exit (0);
+#endif
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+  struct utsname un;
+
+  uname(&un);
+  if (strncmp(un.version, "V2", 2) == 0) {
+    printf ("i386-sequent-ptx2\n"); exit (0);
+  }
+  if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+    printf ("i386-sequent-ptx1\n"); exit (0);
+  }
+  printf ("i386-sequent-ptx\n"); exit (0);
+#endif
+
+#if defined (vax)
+#if !defined (ultrix)
+#include <sys/param.h>
+#if defined (BSD)
+#if BSD == 43
+  printf ("vax-dec-bsd4.3\n"); exit (0);
+#else
+#if BSD == 199006
+  printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#else
+  printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#endif
+#else
+  printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#else
+#if defined(_SIZE_T_) || defined(SIGLOST)
+  struct utsname un;
+  uname (&un);
+  printf ("vax-dec-ultrix%s\n", un.release); exit (0);
+#else
+  printf ("vax-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#if defined(_SIZE_T_) || defined(SIGLOST)
+  struct utsname *un;
+  uname (&un);
+  printf ("mips-dec-ultrix%s\n", un.release); exit (0);
+#else
+  printf ("mips-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` &&
+	{ echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
+
+echo "$0: unable to guess system type" >&2
+
+case $UNAME_MACHINE:$UNAME_SYSTEM in
+    mips:Linux | mips64:Linux)
+	# If we got here on MIPS GNU/Linux, output extra information.
+	cat >&2 <<EOF
+
+NOTE: MIPS GNU/Linux systems require a C compiler to fully recognize
+the system type. Please install a C compiler and try again.
+EOF
+	;;
+esac
+
+cat >&2 <<EOF
+
+This script (version $timestamp), has failed to recognize the
+operating system you are using. If your script is old, overwrite *all*
+copies of config.guess and config.sub with the latest versions from:
+
+  https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
+and
+  https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
+EOF
+
+our_year=`echo $timestamp | sed 's,-.*,,'`
+thisyear=`date +%Y`
+# shellcheck disable=SC2003
+script_age=`expr "$thisyear" - "$our_year"`
+if test "$script_age" -lt 3 ; then
+   cat >&2 <<EOF
+
+If $0 has already been updated, send the following data and any
+information you think might be pertinent to config-patches@gnu.org to
+provide the necessary information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = "$UNAME_MACHINE"
+UNAME_RELEASE = "$UNAME_RELEASE"
+UNAME_SYSTEM  = "$UNAME_SYSTEM"
+UNAME_VERSION = "$UNAME_VERSION"
+EOF
+fi
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/acdir/config.sub b/acdir/config.sub
new file mode 100755
index 0000000..bda1066
--- /dev/null
+++ b/acdir/config.sub
@@ -0,0 +1,1890 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright 1992-2023 Free Software Foundation, Inc.
+
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2022-01-03'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program.  This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+
+
+# Please send patches to <config-patches@gnu.org>.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#	CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#	CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX.  However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
+
+Canonicalize a configuration name.
+
+Options:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright 1992-2023 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo "$1"
+       exit ;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Split fields of configuration type
+# shellcheck disable=SC2162
+saved_IFS=$IFS
+IFS="-" read field1 field2 field3 field4 <<EOF
+$1
+EOF
+IFS=$saved_IFS
+
+# Separate into logical components for further validation
+case $1 in
+	*-*-*-*-*)
+		echo Invalid configuration \`"$1"\': more than four components >&2
+		exit 1
+		;;
+	*-*-*-*)
+		basic_machine=$field1-$field2
+		basic_os=$field3-$field4
+		;;
+	*-*-*)
+		# Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
+		# parts
+		maybe_os=$field2-$field3
+		case $maybe_os in
+			nto-qnx* | linux-* | uclinux-uclibc* \
+			| uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+			| netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+			| storm-chaos* | os2-emx* | rtmk-nova*)
+				basic_machine=$field1
+				basic_os=$maybe_os
+				;;
+			android-linux)
+				basic_machine=$field1-unknown
+				basic_os=linux-android
+				;;
+			*)
+				basic_machine=$field1-$field2
+				basic_os=$field3
+				;;
+		esac
+		;;
+	*-*)
+		# A lone config we happen to match not fitting any pattern
+		case $field1-$field2 in
+			decstation-3100)
+				basic_machine=mips-dec
+				basic_os=
+				;;
+			*-*)
+				# Second component is usually, but not always the OS
+				case $field2 in
+					# Prevent following clause from handling this valid os
+					sun*os*)
+						basic_machine=$field1
+						basic_os=$field2
+						;;
+					zephyr*)
+						basic_machine=$field1-unknown
+						basic_os=$field2
+						;;
+					# Manufacturers
+					dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
+					| att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
+					| unicom* | ibm* | next | hp | isi* | apollo | altos* \
+					| convergent* | ncr* | news | 32* | 3600* | 3100* \
+					| hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
+					| ultra | tti* | harris | dolphin | highlevel | gould \
+					| cbm | ns | masscomp | apple | axis | knuth | cray \
+					| microblaze* | sim | cisco \
+					| oki | wec | wrs | winbond)
+						basic_machine=$field1-$field2
+						basic_os=
+						;;
+					*)
+						basic_machine=$field1
+						basic_os=$field2
+						;;
+				esac
+			;;
+		esac
+		;;
+	*)
+		# Convert single-component short-hands not valid as part of
+		# multi-component configurations.
+		case $field1 in
+			386bsd)
+				basic_machine=i386-pc
+				basic_os=bsd
+				;;
+			a29khif)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			adobe68k)
+				basic_machine=m68010-adobe
+				basic_os=scout
+				;;
+			alliant)
+				basic_machine=fx80-alliant
+				basic_os=
+				;;
+			altos | altos3068)
+				basic_machine=m68k-altos
+				basic_os=
+				;;
+			am29k)
+				basic_machine=a29k-none
+				basic_os=bsd
+				;;
+			amdahl)
+				basic_machine=580-amdahl
+				basic_os=sysv
+				;;
+			amiga)
+				basic_machine=m68k-unknown
+				basic_os=
+				;;
+			amigaos | amigados)
+				basic_machine=m68k-unknown
+				basic_os=amigaos
+				;;
+			amigaunix | amix)
+				basic_machine=m68k-unknown
+				basic_os=sysv4
+				;;
+			apollo68)
+				basic_machine=m68k-apollo
+				basic_os=sysv
+				;;
+			apollo68bsd)
+				basic_machine=m68k-apollo
+				basic_os=bsd
+				;;
+			aros)
+				basic_machine=i386-pc
+				basic_os=aros
+				;;
+			aux)
+				basic_machine=m68k-apple
+				basic_os=aux
+				;;
+			balance)
+				basic_machine=ns32k-sequent
+				basic_os=dynix
+				;;
+			blackfin)
+				basic_machine=bfin-unknown
+				basic_os=linux
+				;;
+			cegcc)
+				basic_machine=arm-unknown
+				basic_os=cegcc
+				;;
+			convex-c1)
+				basic_machine=c1-convex
+				basic_os=bsd
+				;;
+			convex-c2)
+				basic_machine=c2-convex
+				basic_os=bsd
+				;;
+			convex-c32)
+				basic_machine=c32-convex
+				basic_os=bsd
+				;;
+			convex-c34)
+				basic_machine=c34-convex
+				basic_os=bsd
+				;;
+			convex-c38)
+				basic_machine=c38-convex
+				basic_os=bsd
+				;;
+			cray)
+				basic_machine=j90-cray
+				basic_os=unicos
+				;;
+			crds | unos)
+				basic_machine=m68k-crds
+				basic_os=
+				;;
+			da30)
+				basic_machine=m68k-da30
+				basic_os=
+				;;
+			decstation | pmax | pmin | dec3100 | decstatn)
+				basic_machine=mips-dec
+				basic_os=
+				;;
+			delta88)
+				basic_machine=m88k-motorola
+				basic_os=sysv3
+				;;
+			dicos)
+				basic_machine=i686-pc
+				basic_os=dicos
+				;;
+			djgpp)
+				basic_machine=i586-pc
+				basic_os=msdosdjgpp
+				;;
+			ebmon29k)
+				basic_machine=a29k-amd
+				basic_os=ebmon
+				;;
+			es1800 | OSE68k | ose68k | ose | OSE)
+				basic_machine=m68k-ericsson
+				basic_os=ose
+				;;
+			gmicro)
+				basic_machine=tron-gmicro
+				basic_os=sysv
+				;;
+			go32)
+				basic_machine=i386-pc
+				basic_os=go32
+				;;
+			h8300hms)
+				basic_machine=h8300-hitachi
+				basic_os=hms
+				;;
+			h8300xray)
+				basic_machine=h8300-hitachi
+				basic_os=xray
+				;;
+			h8500hms)
+				basic_machine=h8500-hitachi
+				basic_os=hms
+				;;
+			harris)
+				basic_machine=m88k-harris
+				basic_os=sysv3
+				;;
+			hp300 | hp300hpux)
+				basic_machine=m68k-hp
+				basic_os=hpux
+				;;
+			hp300bsd)
+				basic_machine=m68k-hp
+				basic_os=bsd
+				;;
+			hppaosf)
+				basic_machine=hppa1.1-hp
+				basic_os=osf
+				;;
+			hppro)
+				basic_machine=hppa1.1-hp
+				basic_os=proelf
+				;;
+			i386mach)
+				basic_machine=i386-mach
+				basic_os=mach
+				;;
+			isi68 | isi)
+				basic_machine=m68k-isi
+				basic_os=sysv
+				;;
+			m68knommu)
+				basic_machine=m68k-unknown
+				basic_os=linux
+				;;
+			magnum | m3230)
+				basic_machine=mips-mips
+				basic_os=sysv
+				;;
+			merlin)
+				basic_machine=ns32k-utek
+				basic_os=sysv
+				;;
+			mingw64)
+				basic_machine=x86_64-pc
+				basic_os=mingw64
+				;;
+			mingw32)
+				basic_machine=i686-pc
+				basic_os=mingw32
+				;;
+			mingw32ce)
+				basic_machine=arm-unknown
+				basic_os=mingw32ce
+				;;
+			monitor)
+				basic_machine=m68k-rom68k
+				basic_os=coff
+				;;
+			morphos)
+				basic_machine=powerpc-unknown
+				basic_os=morphos
+				;;
+			moxiebox)
+				basic_machine=moxie-unknown
+				basic_os=moxiebox
+				;;
+			msdos)
+				basic_machine=i386-pc
+				basic_os=msdos
+				;;
+			msys)
+				basic_machine=i686-pc
+				basic_os=msys
+				;;
+			mvs)
+				basic_machine=i370-ibm
+				basic_os=mvs
+				;;
+			nacl)
+				basic_machine=le32-unknown
+				basic_os=nacl
+				;;
+			ncr3000)
+				basic_machine=i486-ncr
+				basic_os=sysv4
+				;;
+			netbsd386)
+				basic_machine=i386-pc
+				basic_os=netbsd
+				;;
+			netwinder)
+				basic_machine=armv4l-rebel
+				basic_os=linux
+				;;
+			news | news700 | news800 | news900)
+				basic_machine=m68k-sony
+				basic_os=newsos
+				;;
+			news1000)
+				basic_machine=m68030-sony
+				basic_os=newsos
+				;;
+			necv70)
+				basic_machine=v70-nec
+				basic_os=sysv
+				;;
+			nh3000)
+				basic_machine=m68k-harris
+				basic_os=cxux
+				;;
+			nh[45]000)
+				basic_machine=m88k-harris
+				basic_os=cxux
+				;;
+			nindy960)
+				basic_machine=i960-intel
+				basic_os=nindy
+				;;
+			mon960)
+				basic_machine=i960-intel
+				basic_os=mon960
+				;;
+			nonstopux)
+				basic_machine=mips-compaq
+				basic_os=nonstopux
+				;;
+			os400)
+				basic_machine=powerpc-ibm
+				basic_os=os400
+				;;
+			OSE68000 | ose68000)
+				basic_machine=m68000-ericsson
+				basic_os=ose
+				;;
+			os68k)
+				basic_machine=m68k-none
+				basic_os=os68k
+				;;
+			paragon)
+				basic_machine=i860-intel
+				basic_os=osf
+				;;
+			parisc)
+				basic_machine=hppa-unknown
+				basic_os=linux
+				;;
+			psp)
+				basic_machine=mipsallegrexel-sony
+				basic_os=psp
+				;;
+			pw32)
+				basic_machine=i586-unknown
+				basic_os=pw32
+				;;
+			rdos | rdos64)
+				basic_machine=x86_64-pc
+				basic_os=rdos
+				;;
+			rdos32)
+				basic_machine=i386-pc
+				basic_os=rdos
+				;;
+			rom68k)
+				basic_machine=m68k-rom68k
+				basic_os=coff
+				;;
+			sa29200)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			sei)
+				basic_machine=mips-sei
+				basic_os=seiux
+				;;
+			sequent)
+				basic_machine=i386-sequent
+				basic_os=
+				;;
+			sps7)
+				basic_machine=m68k-bull
+				basic_os=sysv2
+				;;
+			st2000)
+				basic_machine=m68k-tandem
+				basic_os=
+				;;
+			stratus)
+				basic_machine=i860-stratus
+				basic_os=sysv4
+				;;
+			sun2)
+				basic_machine=m68000-sun
+				basic_os=
+				;;
+			sun2os3)
+				basic_machine=m68000-sun
+				basic_os=sunos3
+				;;
+			sun2os4)
+				basic_machine=m68000-sun
+				basic_os=sunos4
+				;;
+			sun3)
+				basic_machine=m68k-sun
+				basic_os=
+				;;
+			sun3os3)
+				basic_machine=m68k-sun
+				basic_os=sunos3
+				;;
+			sun3os4)
+				basic_machine=m68k-sun
+				basic_os=sunos4
+				;;
+			sun4)
+				basic_machine=sparc-sun
+				basic_os=
+				;;
+			sun4os3)
+				basic_machine=sparc-sun
+				basic_os=sunos3
+				;;
+			sun4os4)
+				basic_machine=sparc-sun
+				basic_os=sunos4
+				;;
+			sun4sol2)
+				basic_machine=sparc-sun
+				basic_os=solaris2
+				;;
+			sun386 | sun386i | roadrunner)
+				basic_machine=i386-sun
+				basic_os=
+				;;
+			sv1)
+				basic_machine=sv1-cray
+				basic_os=unicos
+				;;
+			symmetry)
+				basic_machine=i386-sequent
+				basic_os=dynix
+				;;
+			t3e)
+				basic_machine=alphaev5-cray
+				basic_os=unicos
+				;;
+			t90)
+				basic_machine=t90-cray
+				basic_os=unicos
+				;;
+			toad1)
+				basic_machine=pdp10-xkl
+				basic_os=tops20
+				;;
+			tpf)
+				basic_machine=s390x-ibm
+				basic_os=tpf
+				;;
+			udi29k)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			ultra3)
+				basic_machine=a29k-nyu
+				basic_os=sym1
+				;;
+			v810 | necv810)
+				basic_machine=v810-nec
+				basic_os=none
+				;;
+			vaxv)
+				basic_machine=vax-dec
+				basic_os=sysv
+				;;
+			vms)
+				basic_machine=vax-dec
+				basic_os=vms
+				;;
+			vsta)
+				basic_machine=i386-pc
+				basic_os=vsta
+				;;
+			vxworks960)
+				basic_machine=i960-wrs
+				basic_os=vxworks
+				;;
+			vxworks68)
+				basic_machine=m68k-wrs
+				basic_os=vxworks
+				;;
+			vxworks29k)
+				basic_machine=a29k-wrs
+				basic_os=vxworks
+				;;
+			xbox)
+				basic_machine=i686-pc
+				basic_os=mingw32
+				;;
+			ymp)
+				basic_machine=ymp-cray
+				basic_os=unicos
+				;;
+			*)
+				basic_machine=$1
+				basic_os=
+				;;
+		esac
+		;;
+esac
+
+# Decode 1-component or ad-hoc basic machines
+case $basic_machine in
+	# Here we handle the default manufacturer of certain CPU types.  It is in
+	# some cases the only manufacturer, in others, it is the most popular.
+	w89k)
+		cpu=hppa1.1
+		vendor=winbond
+		;;
+	op50n)
+		cpu=hppa1.1
+		vendor=oki
+		;;
+	op60c)
+		cpu=hppa1.1
+		vendor=oki
+		;;
+	ibm*)
+		cpu=i370
+		vendor=ibm
+		;;
+	orion105)
+		cpu=clipper
+		vendor=highlevel
+		;;
+	mac | mpw | mac-mpw)
+		cpu=m68k
+		vendor=apple
+		;;
+	pmac | pmac-mpw)
+		cpu=powerpc
+		vendor=apple
+		;;
+
+	# Recognize the various machine names and aliases which stand
+	# for a CPU type and a company and sometimes even an OS.
+	3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+		cpu=m68000
+		vendor=att
+		;;
+	3b*)
+		cpu=we32k
+		vendor=att
+		;;
+	bluegene*)
+		cpu=powerpc
+		vendor=ibm
+		basic_os=cnk
+		;;
+	decsystem10* | dec10*)
+		cpu=pdp10
+		vendor=dec
+		basic_os=tops10
+		;;
+	decsystem20* | dec20*)
+		cpu=pdp10
+		vendor=dec
+		basic_os=tops20
+		;;
+	delta | 3300 | motorola-3300 | motorola-delta \
+	      | 3300-motorola | delta-motorola)
+		cpu=m68k
+		vendor=motorola
+		;;
+	dpx2*)
+		cpu=m68k
+		vendor=bull
+		basic_os=sysv3
+		;;
+	encore | umax | mmax)
+		cpu=ns32k
+		vendor=encore
+		;;
+	elxsi)
+		cpu=elxsi
+		vendor=elxsi
+		basic_os=${basic_os:-bsd}
+		;;
+	fx2800)
+		cpu=i860
+		vendor=alliant
+		;;
+	genix)
+		cpu=ns32k
+		vendor=ns
+		;;
+	h3050r* | hiux*)
+		cpu=hppa1.1
+		vendor=hitachi
+		basic_os=hiuxwe2
+		;;
+	hp3k9[0-9][0-9] | hp9[0-9][0-9])
+		cpu=hppa1.0
+		vendor=hp
+		;;
+	hp9k2[0-9][0-9] | hp9k31[0-9])
+		cpu=m68000
+		vendor=hp
+		;;
+	hp9k3[2-9][0-9])
+		cpu=m68k
+		vendor=hp
+		;;
+	hp9k6[0-9][0-9] | hp6[0-9][0-9])
+		cpu=hppa1.0
+		vendor=hp
+		;;
+	hp9k7[0-79][0-9] | hp7[0-79][0-9])
+		cpu=hppa1.1
+		vendor=hp
+		;;
+	hp9k78[0-9] | hp78[0-9])
+		# FIXME: really hppa2.0-hp
+		cpu=hppa1.1
+		vendor=hp
+		;;
+	hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+		# FIXME: really hppa2.0-hp
+		cpu=hppa1.1
+		vendor=hp
+		;;
+	hp9k8[0-9][13679] | hp8[0-9][13679])
+		cpu=hppa1.1
+		vendor=hp
+		;;
+	hp9k8[0-9][0-9] | hp8[0-9][0-9])
+		cpu=hppa1.0
+		vendor=hp
+		;;
+	i*86v32)
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv32
+		;;
+	i*86v4*)
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv4
+		;;
+	i*86v)
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv
+		;;
+	i*86sol2)
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=solaris2
+		;;
+	j90 | j90-cray)
+		cpu=j90
+		vendor=cray
+		basic_os=${basic_os:-unicos}
+		;;
+	iris | iris4d)
+		cpu=mips
+		vendor=sgi
+		case $basic_os in
+		    irix*)
+			;;
+		    *)
+			basic_os=irix4
+			;;
+		esac
+		;;
+	miniframe)
+		cpu=m68000
+		vendor=convergent
+		;;
+	*mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
+		cpu=m68k
+		vendor=atari
+		basic_os=mint
+		;;
+	news-3600 | risc-news)
+		cpu=mips
+		vendor=sony
+		basic_os=newsos
+		;;
+	next | m*-next)
+		cpu=m68k
+		vendor=next
+		case $basic_os in
+		    openstep*)
+		        ;;
+		    nextstep*)
+			;;
+		    ns2*)
+		      basic_os=nextstep2
+			;;
+		    *)
+		      basic_os=nextstep3
+			;;
+		esac
+		;;
+	np1)
+		cpu=np1
+		vendor=gould
+		;;
+	op50n-* | op60c-*)
+		cpu=hppa1.1
+		vendor=oki
+		basic_os=proelf
+		;;
+	pa-hitachi)
+		cpu=hppa1.1
+		vendor=hitachi
+		basic_os=hiuxwe2
+		;;
+	pbd)
+		cpu=sparc
+		vendor=tti
+		;;
+	pbb)
+		cpu=m68k
+		vendor=tti
+		;;
+	pc532)
+		cpu=ns32k
+		vendor=pc532
+		;;
+	pn)
+		cpu=pn
+		vendor=gould
+		;;
+	power)
+		cpu=power
+		vendor=ibm
+		;;
+	ps2)
+		cpu=i386
+		vendor=ibm
+		;;
+	rm[46]00)
+		cpu=mips
+		vendor=siemens
+		;;
+	rtpc | rtpc-*)
+		cpu=romp
+		vendor=ibm
+		;;
+	sde)
+		cpu=mipsisa32
+		vendor=sde
+		basic_os=${basic_os:-elf}
+		;;
+	simso-wrs)
+		cpu=sparclite
+		vendor=wrs
+		basic_os=vxworks
+		;;
+	tower | tower-32)
+		cpu=m68k
+		vendor=ncr
+		;;
+	vpp*|vx|vx-*)
+		cpu=f301
+		vendor=fujitsu
+		;;
+	w65)
+		cpu=w65
+		vendor=wdc
+		;;
+	w89k-*)
+		cpu=hppa1.1
+		vendor=winbond
+		basic_os=proelf
+		;;
+	none)
+		cpu=none
+		vendor=none
+		;;
+	leon|leon[3-9])
+		cpu=sparc
+		vendor=$basic_machine
+		;;
+	leon-*|leon[3-9]-*)
+		cpu=sparc
+		vendor=`echo "$basic_machine" | sed 's/-.*//'`
+		;;
+
+	*-*)
+		# shellcheck disable=SC2162
+		saved_IFS=$IFS
+		IFS="-" read cpu vendor <<EOF
+$basic_machine
+EOF
+		IFS=$saved_IFS
+		;;
+	# We use `pc' rather than `unknown'
+	# because (1) that's what they normally are, and
+	# (2) the word "unknown" tends to confuse beginning users.
+	i*86 | x86_64)
+		cpu=$basic_machine
+		vendor=pc
+		;;
+	# These rules are duplicated from below for sake of the special case above;
+	# i.e. things that normalized to x86 arches should also default to "pc"
+	pc98)
+		cpu=i386
+		vendor=pc
+		;;
+	x64 | amd64)
+		cpu=x86_64
+		vendor=pc
+		;;
+	# Recognize the basic CPU types without company name.
+	*)
+		cpu=$basic_machine
+		vendor=unknown
+		;;
+esac
+
+unset -v basic_machine
+
+# Decode basic machines in the full and proper CPU-Company form.
+case $cpu-$vendor in
+	# Here we handle the default manufacturer of certain CPU types in canonical form. It is in
+	# some cases the only manufacturer, in others, it is the most popular.
+	craynv-unknown)
+		vendor=cray
+		basic_os=${basic_os:-unicosmp}
+		;;
+	c90-unknown | c90-cray)
+		vendor=cray
+		basic_os=${Basic_os:-unicos}
+		;;
+	fx80-unknown)
+		vendor=alliant
+		;;
+	romp-unknown)
+		vendor=ibm
+		;;
+	mmix-unknown)
+		vendor=knuth
+		;;
+	microblaze-unknown | microblazeel-unknown)
+		vendor=xilinx
+		;;
+	rs6000-unknown)
+		vendor=ibm
+		;;
+	vax-unknown)
+		vendor=dec
+		;;
+	pdp11-unknown)
+		vendor=dec
+		;;
+	we32k-unknown)
+		vendor=att
+		;;
+	cydra-unknown)
+		vendor=cydrome
+		;;
+	i370-ibm*)
+		vendor=ibm
+		;;
+	orion-unknown)
+		vendor=highlevel
+		;;
+	xps-unknown | xps100-unknown)
+		cpu=xps100
+		vendor=honeywell
+		;;
+
+	# Here we normalize CPU types with a missing or matching vendor
+	armh-unknown | armh-alt)
+		cpu=armv7l
+		vendor=alt
+		basic_os=${basic_os:-linux-gnueabihf}
+		;;
+	dpx20-unknown | dpx20-bull)
+		cpu=rs6000
+		vendor=bull
+		basic_os=${basic_os:-bosx}
+		;;
+
+	# Here we normalize CPU types irrespective of the vendor
+	amd64-*)
+		cpu=x86_64
+		;;
+	blackfin-*)
+		cpu=bfin
+		basic_os=linux
+		;;
+	c54x-*)
+		cpu=tic54x
+		;;
+	c55x-*)
+		cpu=tic55x
+		;;
+	c6x-*)
+		cpu=tic6x
+		;;
+	e500v[12]-*)
+		cpu=powerpc
+		basic_os=${basic_os}"spe"
+		;;
+	mips3*-*)
+		cpu=mips64
+		;;
+	ms1-*)
+		cpu=mt
+		;;
+	m68knommu-*)
+		cpu=m68k
+		basic_os=linux
+		;;
+	m9s12z-* | m68hcs12z-* | hcs12z-* | s12z-*)
+		cpu=s12z
+		;;
+	openrisc-*)
+		cpu=or32
+		;;
+	parisc-*)
+		cpu=hppa
+		basic_os=linux
+		;;
+	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+		cpu=i586
+		;;
+	pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
+		cpu=i686
+		;;
+	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+		cpu=i686
+		;;
+	pentium4-*)
+		cpu=i786
+		;;
+	pc98-*)
+		cpu=i386
+		;;
+	ppc-* | ppcbe-*)
+		cpu=powerpc
+		;;
+	ppcle-* | powerpclittle-*)
+		cpu=powerpcle
+		;;
+	ppc64-*)
+		cpu=powerpc64
+		;;
+	ppc64le-* | powerpc64little-*)
+		cpu=powerpc64le
+		;;
+	sb1-*)
+		cpu=mipsisa64sb1
+		;;
+	sb1el-*)
+		cpu=mipsisa64sb1el
+		;;
+	sh5e[lb]-*)
+		cpu=`echo "$cpu" | sed 's/^\(sh.\)e\(.\)$/\1\2e/'`
+		;;
+	spur-*)
+		cpu=spur
+		;;
+	strongarm-* | thumb-*)
+		cpu=arm
+		;;
+	tx39-*)
+		cpu=mipstx39
+		;;
+	tx39el-*)
+		cpu=mipstx39el
+		;;
+	x64-*)
+		cpu=x86_64
+		;;
+	xscale-* | xscalee[bl]-*)
+		cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
+		;;
+	arm64-* | aarch64le-*)
+		cpu=aarch64
+		;;
+
+	# Recognize the canonical CPU Types that limit and/or modify the
+	# company names they are paired with.
+	cr16-*)
+		basic_os=${basic_os:-elf}
+		;;
+	crisv32-* | etraxfs*-*)
+		cpu=crisv32
+		vendor=axis
+		;;
+	cris-* | etrax*-*)
+		cpu=cris
+		vendor=axis
+		;;
+	crx-*)
+		basic_os=${basic_os:-elf}
+		;;
+	neo-tandem)
+		cpu=neo
+		vendor=tandem
+		;;
+	nse-tandem)
+		cpu=nse
+		vendor=tandem
+		;;
+	nsr-tandem)
+		cpu=nsr
+		vendor=tandem
+		;;
+	nsv-tandem)
+		cpu=nsv
+		vendor=tandem
+		;;
+	nsx-tandem)
+		cpu=nsx
+		vendor=tandem
+		;;
+	mipsallegrexel-sony)
+		cpu=mipsallegrexel
+		vendor=sony
+		;;
+	tile*-*)
+		basic_os=${basic_os:-linux-gnu}
+		;;
+
+	*)
+		# Recognize the canonical CPU types that are allowed with any
+		# company name.
+		case $cpu in
+			1750a | 580 \
+			| a29k \
+			| aarch64 | aarch64_be \
+			| abacus \
+			| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+			| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+			| alphapca5[67] | alpha64pca5[67] \
+			| am33_2.0 \
+			| amdgcn \
+			| arc | arceb | arc32 | arc64 \
+			| arm | arm[lb]e | arme[lb] | armv* \
+			| avr | avr32 \
+			| asmjs \
+			| ba \
+			| be32 | be64 \
+			| bfin | bpf | bs2000 \
+			| c[123]* | c30 | [cjt]90 | c4x \
+			| c8051 | clipper | craynv | csky | cydra \
+			| d10v | d30v | dlx | dsp16xx \
+			| e2k | elxsi | epiphany \
+			| f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
+			| h8300 | h8500 \
+			| hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+			| hexagon \
+			| i370 | i*86 | i860 | i960 | ia16 | ia64 \
+			| ip2k | iq2000 \
+			| k1om \
+			| le32 | le64 \
+			| lm32 \
+			| loongarch32 | loongarch64 | loongarchx32 \
+			| m32c | m32r | m32rle \
+			| m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
+			| m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+			| m88110 | m88k | maxq | mb | mcore | mep | metag \
+			| microblaze | microblazeel \
+			| mips | mipsbe | mipseb | mipsel | mipsle \
+			| mips16 \
+			| mips64 | mips64eb | mips64el \
+			| mips64octeon | mips64octeonel \
+			| mips64orion | mips64orionel \
+			| mips64r5900 | mips64r5900el \
+			| mips64vr | mips64vrel \
+			| mips64vr4100 | mips64vr4100el \
+			| mips64vr4300 | mips64vr4300el \
+			| mips64vr5000 | mips64vr5000el \
+			| mips64vr5900 | mips64vr5900el \
+			| mipsisa32 | mipsisa32el \
+			| mipsisa32r2 | mipsisa32r2el \
+			| mipsisa32r3 | mipsisa32r3el \
+			| mipsisa32r5 | mipsisa32r5el \
+			| mipsisa32r6 | mipsisa32r6el \
+			| mipsisa64 | mipsisa64el \
+			| mipsisa64r2 | mipsisa64r2el \
+			| mipsisa64r3 | mipsisa64r3el \
+			| mipsisa64r5 | mipsisa64r5el \
+			| mipsisa64r6 | mipsisa64r6el \
+			| mipsisa64sb1 | mipsisa64sb1el \
+			| mipsisa64sr71k | mipsisa64sr71kel \
+			| mipsr5900 | mipsr5900el \
+			| mipstx39 | mipstx39el \
+			| mmix \
+			| mn10200 | mn10300 \
+			| moxie \
+			| mt \
+			| msp430 \
+			| nds32 | nds32le | nds32be \
+			| nfp \
+			| nios | nios2 | nios2eb | nios2el \
+			| none | np1 | ns16k | ns32k | nvptx \
+			| open8 \
+			| or1k* \
+			| or32 \
+			| orion \
+			| picochip \
+			| pdp10 | pdp11 | pj | pjl | pn | power \
+			| powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
+			| pru \
+			| pyramid \
+			| riscv | riscv32 | riscv32be | riscv64 | riscv64be \
+			| rl78 | romp | rs6000 | rx \
+			| s390 | s390x \
+			| score \
+			| sh | shl \
+			| sh[1234] | sh[24]a | sh[24]ae[lb] | sh[23]e | she[lb] | sh[lb]e \
+			| sh[1234]e[lb] |  sh[12345][lb]e | sh[23]ele | sh64 | sh64le \
+			| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet \
+			| sparclite \
+			| sparcv8 | sparcv9 | sparcv9b | sparcv9v | sv1 | sx* \
+			| spu \
+			| tahoe \
+			| thumbv7* \
+			| tic30 | tic4x | tic54x | tic55x | tic6x | tic80 \
+			| tron \
+			| ubicom32 \
+			| v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+			| vax \
+			| visium \
+			| w65 \
+			| wasm32 | wasm64 \
+			| we32k \
+			| x86 | x86_64 | xc16x | xgate | xps100 \
+			| xstormy16 | xtensa* \
+			| ymp \
+			| z8k | z80)
+				;;
+
+			*)
+				echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
+				exit 1
+				;;
+		esac
+		;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $vendor in
+	digital*)
+		vendor=dec
+		;;
+	commodore*)
+		vendor=cbm
+		;;
+	*)
+		;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if test x$basic_os != x
+then
+
+# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
+# set os.
+case $basic_os in
+	gnu/linux*)
+		kernel=linux
+		os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'`
+		;;
+	os2-emx)
+		kernel=os2
+		os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'`
+		;;
+	nto-qnx*)
+		kernel=nto
+		os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'`
+		;;
+	*-*)
+		# shellcheck disable=SC2162
+		saved_IFS=$IFS
+		IFS="-" read kernel os <<EOF
+$basic_os
+EOF
+		IFS=$saved_IFS
+		;;
+	# Default OS when just kernel was specified
+	nto*)
+		kernel=nto
+		os=`echo "$basic_os" | sed -e 's|nto|qnx|'`
+		;;
+	linux*)
+		kernel=linux
+		os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
+		;;
+	*)
+		kernel=
+		os=$basic_os
+		;;
+esac
+
+# Now, normalize the OS (knowing we just have one component, it's not a kernel,
+# etc.)
+case $os in
+	# First match some system type aliases that might get confused
+	# with valid system types.
+	# solaris* is a basic system type, with this one exception.
+	auroraux)
+		os=auroraux
+		;;
+	bluegene*)
+		os=cnk
+		;;
+	solaris1 | solaris1.*)
+		os=`echo "$os" | sed -e 's|solaris1|sunos4|'`
+		;;
+	solaris)
+		os=solaris2
+		;;
+	unixware*)
+		os=sysv4.2uw
+		;;
+	# es1800 is here to avoid being matched by es* (a different OS)
+	es1800*)
+		os=ose
+		;;
+	# Some version numbers need modification
+	chorusos*)
+		os=chorusos
+		;;
+	isc)
+		os=isc2.2
+		;;
+	sco6)
+		os=sco5v6
+		;;
+	sco5)
+		os=sco3.2v5
+		;;
+	sco4)
+		os=sco3.2v4
+		;;
+	sco3.2.[4-9]*)
+		os=`echo "$os" | sed -e 's/sco3.2./sco3.2v/'`
+		;;
+	sco*v* | scout)
+		# Don't match below
+		;;
+	sco*)
+		os=sco3.2v2
+		;;
+	psos*)
+		os=psos
+		;;
+	qnx*)
+		os=qnx
+		;;
+	hiux*)
+		os=hiuxwe2
+		;;
+	lynx*178)
+		os=lynxos178
+		;;
+	lynx*5)
+		os=lynxos5
+		;;
+	lynxos*)
+		# don't get caught up in next wildcard
+		;;
+	lynx*)
+		os=lynxos
+		;;
+	mac[0-9]*)
+		os=`echo "$os" | sed -e 's|mac|macos|'`
+		;;
+	opened*)
+		os=openedition
+		;;
+	os400*)
+		os=os400
+		;;
+	sunos5*)
+		os=`echo "$os" | sed -e 's|sunos5|solaris2|'`
+		;;
+	sunos6*)
+		os=`echo "$os" | sed -e 's|sunos6|solaris3|'`
+		;;
+	wince*)
+		os=wince
+		;;
+	utek*)
+		os=bsd
+		;;
+	dynix*)
+		os=bsd
+		;;
+	acis*)
+		os=aos
+		;;
+	atheos*)
+		os=atheos
+		;;
+	syllable*)
+		os=syllable
+		;;
+	386bsd)
+		os=bsd
+		;;
+	ctix* | uts*)
+		os=sysv
+		;;
+	nova*)
+		os=rtmk-nova
+		;;
+	ns2)
+		os=nextstep2
+		;;
+	# Preserve the version number of sinix5.
+	sinix5.*)
+		os=`echo "$os" | sed -e 's|sinix|sysv|'`
+		;;
+	sinix*)
+		os=sysv4
+		;;
+	tpf*)
+		os=tpf
+		;;
+	triton*)
+		os=sysv3
+		;;
+	oss*)
+		os=sysv3
+		;;
+	svr4*)
+		os=sysv4
+		;;
+	svr3)
+		os=sysv3
+		;;
+	sysvr4)
+		os=sysv4
+		;;
+	ose*)
+		os=ose
+		;;
+	*mint | mint[0-9]* | *MiNT | MiNT[0-9]*)
+		os=mint
+		;;
+	dicos*)
+		os=dicos
+		;;
+	pikeos*)
+		# Until real need of OS specific support for
+		# particular features comes up, bare metal
+		# configurations are quite functional.
+		case $cpu in
+		    arm*)
+			os=eabi
+			;;
+		    *)
+			os=elf
+			;;
+		esac
+		;;
+	*)
+		# No normalization, but not necessarily accepted, that comes below.
+		;;
+esac
+
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+kernel=
+case $cpu-$vendor in
+	score-*)
+		os=elf
+		;;
+	spu-*)
+		os=elf
+		;;
+	*-acorn)
+		os=riscix1.2
+		;;
+	arm*-rebel)
+		kernel=linux
+		os=gnu
+		;;
+	arm*-semi)
+		os=aout
+		;;
+	c4x-* | tic4x-*)
+		os=coff
+		;;
+	c8051-*)
+		os=elf
+		;;
+	clipper-intergraph)
+		os=clix
+		;;
+	hexagon-*)
+		os=elf
+		;;
+	tic54x-*)
+		os=coff
+		;;
+	tic55x-*)
+		os=coff
+		;;
+	tic6x-*)
+		os=coff
+		;;
+	# This must come before the *-dec entry.
+	pdp10-*)
+		os=tops20
+		;;
+	pdp11-*)
+		os=none
+		;;
+	*-dec | vax-*)
+		os=ultrix4.2
+		;;
+	m68*-apollo)
+		os=domain
+		;;
+	i386-sun)
+		os=sunos4.0.2
+		;;
+	m68000-sun)
+		os=sunos3
+		;;
+	m68*-cisco)
+		os=aout
+		;;
+	mep-*)
+		os=elf
+		;;
+	mips*-cisco)
+		os=elf
+		;;
+	mips*-*)
+		os=elf
+		;;
+	or32-*)
+		os=coff
+		;;
+	*-tti)	# must be before sparc entry or we get the wrong os.
+		os=sysv3
+		;;
+	sparc-* | *-sun)
+		os=sunos4.1.1
+		;;
+	pru-*)
+		os=elf
+		;;
+	*-be)
+		os=beos
+		;;
+	*-ibm)
+		os=aix
+		;;
+	*-knuth)
+		os=mmixware
+		;;
+	*-wec)
+		os=proelf
+		;;
+	*-winbond)
+		os=proelf
+		;;
+	*-oki)
+		os=proelf
+		;;
+	*-hp)
+		os=hpux
+		;;
+	*-hitachi)
+		os=hiux
+		;;
+	i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+		os=sysv
+		;;
+	*-cbm)
+		os=amigaos
+		;;
+	*-dg)
+		os=dgux
+		;;
+	*-dolphin)
+		os=sysv3
+		;;
+	m68k-ccur)
+		os=rtu
+		;;
+	m88k-omron*)
+		os=luna
+		;;
+	*-next)
+		os=nextstep
+		;;
+	*-sequent)
+		os=ptx
+		;;
+	*-crds)
+		os=unos
+		;;
+	*-ns)
+		os=genix
+		;;
+	i370-*)
+		os=mvs
+		;;
+	*-gould)
+		os=sysv
+		;;
+	*-highlevel)
+		os=bsd
+		;;
+	*-encore)
+		os=bsd
+		;;
+	*-sgi)
+		os=irix
+		;;
+	*-siemens)
+		os=sysv4
+		;;
+	*-masscomp)
+		os=rtu
+		;;
+	f30[01]-fujitsu | f700-fujitsu)
+		os=uxpv
+		;;
+	*-rom68k)
+		os=coff
+		;;
+	*-*bug)
+		os=coff
+		;;
+	*-apple)
+		os=macos
+		;;
+	*-atari*)
+		os=mint
+		;;
+	*-wrs)
+		os=vxworks
+		;;
+	*)
+		os=none
+		;;
+esac
+
+fi
+
+# Now, validate our (potentially fixed-up) OS.
+case $os in
+	# Sometimes we do "kernel-libc", so those need to count as OSes.
+	musl* | newlib* | relibc* | uclibc*)
+		;;
+	# Likewise for "kernel-abi"
+	eabi* | gnueabi*)
+		;;
+	# VxWorks passes extra cpu info in the 4th filed.
+	simlinux | simwindows | spe)
+		;;
+	# Now accept the basic system types.
+	# The portable systems comes first.
+	# Each alternative MUST end in a * to match a version number.
+	gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \
+	     | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \
+	     | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+	     | sym* |  plan9* | psp* | sim* | xray* | os68k* | v88r* \
+	     | hiux* | abug | nacl* | netware* | windows* \
+	     | os9* | macos* | osx* | ios* \
+	     | mpw* | magic* | mmixware* | mon960* | lnews* \
+	     | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+	     | aos* | aros* | cloudabi* | sortix* | twizzler* \
+	     | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
+	     | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
+	     | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+	     | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+	     | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+	     | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+	     | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
+	     | udi* | lites* | ieee* | go32* | aux* | hcos* \
+	     | chorusrdb* | cegcc* | glidix* | serenity* \
+	     | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
+	     | midipix* | mingw32* | mingw64* | mint* \
+	     | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+	     | interix* | uwin* | mks* | rhapsody* | darwin* \
+	     | openstep* | oskit* | conix* | pw32* | nonstopux* \
+	     | storm-chaos* | tops10* | tenex* | tops20* | its* \
+	     | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \
+	     | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \
+	     | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \
+	     | skyos* | haiku* | rdos* | toppers* | drops* | es* \
+	     | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+	     | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+	     | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
+	     | fiwix* )
+		;;
+	# This one is extra strict with allowed versions
+	sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+		# Don't forget version if it is 3.2v4 or newer.
+		;;
+	none)
+		;;
+	*)
+		echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
+		exit 1
+		;;
+esac
+
+# As a final step for OS-related things, validate the OS-kernel combination
+# (given a valid OS), if there is a kernel.
+case $kernel-$os in
+	linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+		   | linux-musl* | linux-relibc* | linux-uclibc* )
+		;;
+	uclinux-uclibc* )
+		;;
+	-dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
+		# These are just libc implementations, not actual OSes, and thus
+		# require a kernel.
+		echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
+		exit 1
+		;;
+	kfreebsd*-gnu* | kopensolaris*-gnu*)
+		;;
+	vxworks-simlinux | vxworks-simwindows | vxworks-spe)
+		;;
+	nto-qnx*)
+		;;
+	os2-emx)
+		;;
+	*-eabi* | *-gnueabi*)
+		;;
+	-*)
+		# Blank kernel with real OS is always fine.
+		;;
+	*-*)
+		echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
+		exit 1
+		;;
+esac
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+case $vendor in
+	unknown)
+		case $cpu-$os in
+			*-riscix*)
+				vendor=acorn
+				;;
+			*-sunos*)
+				vendor=sun
+				;;
+			*-cnk* | *-aix*)
+				vendor=ibm
+				;;
+			*-beos*)
+				vendor=be
+				;;
+			*-hpux*)
+				vendor=hp
+				;;
+			*-mpeix*)
+				vendor=hp
+				;;
+			*-hiux*)
+				vendor=hitachi
+				;;
+			*-unos*)
+				vendor=crds
+				;;
+			*-dgux*)
+				vendor=dg
+				;;
+			*-luna*)
+				vendor=omron
+				;;
+			*-genix*)
+				vendor=ns
+				;;
+			*-clix*)
+				vendor=intergraph
+				;;
+			*-mvs* | *-opened*)
+				vendor=ibm
+				;;
+			*-os400*)
+				vendor=ibm
+				;;
+			s390-* | s390x-*)
+				vendor=ibm
+				;;
+			*-ptx*)
+				vendor=sequent
+				;;
+			*-tpf*)
+				vendor=ibm
+				;;
+			*-vxsim* | *-vxworks* | *-windiss*)
+				vendor=wrs
+				;;
+			*-aux*)
+				vendor=apple
+				;;
+			*-hms*)
+				vendor=hitachi
+				;;
+			*-mpw* | *-macos*)
+				vendor=apple
+				;;
+			*-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
+				vendor=atari
+				;;
+			*-vos*)
+				vendor=stratus
+				;;
+		esac
+		;;
+esac
+
+echo "$cpu-$vendor-${kernel:+$kernel-}$os"
+exit
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/acdir/install-sh b/acdir/install-sh
new file mode 100755
index 0000000..2a99c33
--- /dev/null
+++ b/acdir/install-sh
@@ -0,0 +1,191 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+umask 077
+
+programName="`echo "${0}" | sed -e 's%^.*/%%'`"
+programMessage() {
+   echo >&2 "${programName}: ${1}"
+}
+
+syntaxError() {
+   programMessage "${1}"
+   exit 2
+}
+
+defaultMode="0755"
+defaultStripCommand="strip"
+
+copyCommand="cp"
+directoryCommand="mkdir -p"
+groupCommand="chgrp"
+modeCommand="chmod"
+ownerCommand="chown"
+stripCommand="${defaultStripCommand}"
+
+createDirectories=false
+newGroup=""
+newMode="${defaultMode}"
+newOwner=""
+showActions=false
+showUsage=false
+stripSymbols=false
+
+while [ "${#}" -gt 0 ]
+do
+   option="${1}"
+   operand=""
+
+   case "${option}"
+   in
+      -c) ;;
+      -d|--directory) createDirectories=true;;
+      -g|--group) operand=newGroup;;
+      -h|--help) showUsage=true;;
+      -m|--mode) operand=newMode;;
+      -o|--owner) operand=newOwner;;
+      -s|--strip) stripSymbols=true;;
+      --strip-program) operand=stripCommand;;
+      -v|--verbose) showActions=true;;
+      -*) syntaxError "unknown option: ${option}";;
+      *) break;;
+   esac
+
+   shift
+   [ -z "${operand}" ] && continue
+   [ "${#}" -eq 0 ] && syntaxError "missing operand: ${option}"
+   eval "${operand}"'="${1}"'
+   shift
+done
+
+"${showUsage}" && {
+   cat <<END_OF_USAGE
+Usage:
+   ${programName} [-option ...] source destination
+   ${programName} [-option ...] source ... destination-directory
+   ${programName} -d [-option ...] new-directory ...
+
+Options:
+   -c                ignored (for backward compatibility)
+   -d|--directory    create all components of specified directories
+   -g|--group group  set owning group
+   -h|--help         display usage summary (this text), and then exit
+   -m|--mode mode    set permission mode (default is ${defaultMode})
+   -o|--owner owner  set owning user
+   -s|--strip        strip symbols from executables
+   --strip-program   set symbol stripping command (default is ${defaultStripCommand})
+   -v|--verbose      re[prt each successful action
+END_OF_USAGE
+
+   exit 0
+}
+
+changeAttributes='
+   [ "${newMode}" != "" ] && {
+      ${modeCommand} "${newMode}" "${path}" || exit 3
+      "${showActions}" && programMessage "mode changed: ${path}"
+   }
+
+   [ "${newGroup}" != "" ] && {
+      ${groupCommand} "${newGroup}" "${path}" || exit 3
+      "${showActions}" && programMessage "group changed: ${path}"
+   }
+
+   [ "${newOwner}" != "" ] && {
+      ${ownerCommand} "${newOwner}" "${path}" || exit 3
+      "${showActions}" && programMessage "owner changed: ${path}"
+   }
+'
+
+"${createDirectories}" && {
+   "${stripSymbols}" && syntaxError "cannot strip a directory."
+   [ "${#}" -eq 0 ] && syntaxError "directory not specified."
+
+   for destination
+   do
+      [ "${destination}" = "" ] && {
+         programMessage "null directory."
+         continue
+      }
+
+      directory=""
+      paths=""
+      for component in `echo "${destination}" | sed -e 's%/% %g' -e 's%^ %/ %'  -e 's% *$%%' -e 's%  *% %g'`
+      do
+         directory="${directory}${component}"
+         [ "${component}" = "/" ] || directory="${directory}/"
+
+         [ -d "${directory}" ] || {
+            ${directoryCommand} "${directory}" || exit 3
+            "${showActions}" && programMessage "directory created: ${directory}"
+            paths="${directory} ${paths}"
+         }
+      done
+
+      [ -n "${paths}" ] && {
+         for path in $paths
+         do
+            eval "${changeAttributes}"
+         done
+      }
+   done
+
+   exit 0
+}
+
+[ "${#}" -eq 0 ] && syntaxError "source not specified."
+[ "${#}" -eq 1 ] && syntaxError "destination not specified."
+
+count="`expr "${#}" - 1`"
+destination="`( shift "${count}"; echo "${1}" )`"
+
+if [ -d "${destination}" ]
+then
+   toDirectory=true
+else
+   [ "${#}" -eq 2 ] || syntaxError "more than one source - destination not a directory: ${destination}"
+   toDirectory=false
+fi
+
+while [ "${#}" -gt 1 ]
+do
+   source="${1}"
+   shift
+
+   if [ -f "${source}" ]
+   then
+      type=file
+   else
+      syntaxError "source not a file: ${source}"
+   fi
+
+   path="${destination}"
+   "${toDirectory}" && path="${path}/`basename ${source}`"
+   ${copyCommand} "${source}" "${path}" || exit 3
+   "${showActions}" && programMessage "${type} copied: ${source} -> ${path}"
+
+   "${stripSymbols}" && {
+      ${stripCommand} "${path}" || exit 3
+      "${showActions}" && programMessage "symbols stripped: ${path}"
+   }
+
+   eval "${changeAttributes}"
+done
+
+exit 0
diff --git a/apitest.sh b/apitest.sh
new file mode 100644
index 0000000..415cde4
--- /dev/null
+++ b/apitest.sh
@@ -0,0 +1,25 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+programName="$(basename "${0}")"
+cd "$(dirname "${0}")"
+programDirectory="$(pwd)"
+
+[ -z "${LD_LIBRARY_PATH}" ] || LD_LIBRARY_PATH=":${LD_LIBRARY_PATH}"
+export LD_LIBRARY_PATH="${programDirectory}${LD_LIBRARY_PATH}"
+export LD_PRELOAD="${programDirectory}/../../Programs/libbrlapi.so"
+
diff --git a/autogen b/autogen
new file mode 100755
index 0000000..7993a85
--- /dev/null
+++ b/autogen
@@ -0,0 +1,31 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+cd `dirname "${0}"`
+directory="`pwd`"
+
+Tools/gendeps
+[ ! -f Makefile ] || make -s distclean
+aclocal -I "${directory}/m4"
+autoconf
+rm -fr autom4te*.cache || :
+
+[ "${#}" -gt 0 ] && ./configure "${@}"
+exit 0
diff --git a/bindings.mk b/bindings.mk
new file mode 100644
index 0000000..3911c3f
--- /dev/null
+++ b/bindings.mk
@@ -0,0 +1,37 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+API_NAME = brlapi
+API_DIR = $(BLD_TOP)$(PGM_DIR)
+API_LIB = $(API_DIR)/$(LIB_PFX)$(API_NAME).$(LIB_EXT)
+API_LDFLAGS = -L$(API_DIR) -l$(API_NAME)
+
+API_HDR = $(API_DIR)/$(API_NAME).h
+API_AWK = $(SRC_TOP)$(PGM_DIR)/$(API_NAME).awk
+
+API_KEYCODES = $(SRC_TOP)$(PGM_DIR)/$(API_NAME)_keycodes.h
+API_PARAM = $(SRC_TOP)$(PGM_DIR)/$(API_NAME)_param.h
+API_CONSTANTS = $(API_DIR)/$(API_NAME)_constants.h
+API_HDRS = $(API_HDR) $(API_KEYCODES) $(API_PARAM) $(API_CONSTANTS)
+
+CONSTANTS_OPTIONS = -f $(API_AWK) -f $(SRC_DIR)/constants.awk
+CONSTANTS_SCRIPTS = $(CONSTANTS_OPTIONS:-f=)
+CONSTANTS_SOURCES = $(API_HDR) $(API_KEYCODES) $(API_PARAM)
+CONSTANTS_DEPENDENCIES = $(CONSTANTS_SCRIPTS) $(CONSTANTS_SOURCES) $(COMMANDS_DEPENDENCIES)
+CONSTANTS_ARGUMENTS = $(CONSTANTS_OPTIONS) $(COMMANDS_OPTIONS) $(CONSTANTS_SOURCES) $(COMMANDS_SOURCES)
+
diff --git a/bluze.textproto b/bluze.textproto
new file mode 100644
index 0000000..57a1cad
--- /dev/null
+++ b/bluze.textproto
@@ -0,0 +1,44 @@
+# proto-file: devtools/blueprint/blueprint_file.proto
+# proto-message: BlueprintFile
+# DO NOT EDIT! Regenerate the contents by running go/bluze after changing any BUILD file or the Blueprint.
+# Override the default values in third_party.brltty.blueprint instead.
+
+buildable_unit: {
+  name: "third_party.brltty"
+  build_pattern: "//third_party/brltty/..."
+  test_pattern: "//third_party/brltty/..."
+  test_tag_filter: "-nofastbuild"
+  build_tag_filter: "-nofastbuild"
+  enable_coverage: true
+  enable_presubmit: true
+  enable_continuous_build: true
+  continuous_build_email: {
+    build_cop_email_id: "talkback-build-messages+buildgardener@google.com"
+  }
+  enable_release: false
+}
+buildable_unit: {
+  name: "third_party.brltty.opt"
+  test_pattern: "//third_party/brltty/..."
+  test_tag_filter: "-noopt"
+  build_flag: "--compilation_mode=opt"
+  enable_coverage: false
+  enable_presubmit: false
+  enable_continuous_build: false
+  continuous_build_email: {
+    build_cop_email_id: "talkback-build-messages+buildgardener@google.com"
+  }
+  enable_release: false
+  [tap.tap_settings]: {
+    on_demand: true
+    on_demand_frequency: EVERY_4_HOURS
+  }
+}
+continuous_tests: {
+  name: "third_party.brltty"
+  buildable_unit_name: "third_party.brltty"
+}
+continuous_tests: {
+  name: "third_party.brltty.opt"
+  buildable_unit_name: "third_party.brltty.opt"
+}
diff --git a/braille.mk b/braille.mk
new file mode 100644
index 0000000..7ad8e7c
--- /dev/null
+++ b/braille.mk
@@ -0,0 +1,44 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+TXT2HLP = $(BLD_TOP)$(PGM_DIR)/txt2hlp$X
+$(TXT2HLP)$X:
+	cd $(@D) && $(MAKE) $(@F)
+
+BRL_DEFS = '-DDRIVER_NAME=$(DRIVER_NAME)' '-DDRIVER_CODE=$(DRIVER_CODE)' '-DDRIVER_COMMENT="$(DRIVER_COMMENT)"' '-DDRIVER_VERSION="$(DRIVER_VERSION)"' '-DDRIVER_DEVELOPERS="$(DRIVER_DEVELOPERS)"'
+BRL_CFLAGS = $(LIBCFLAGS) $(BRL_DEFS)
+BRL_CXXFLAGS = $(LIBCXXFLAGS) $(BRL_DEFS)
+BRL_MOD_NAME = $(BLD_TOP)$(DRV_DIR)/$(MOD_NAME)b$(DRIVER_CODE)
+BRL_MOD_FILE = $(BRL_MOD_NAME).$(MOD_EXT)
+$(BRL_MOD_FILE): braille.$O
+	$(INSTALL_DIRECTORY) $(@D)
+	$(MKSHR) $(@) braille.$O $(BRL_OBJS)
+braille-driver: $(BRL_MOD_FILE)
+
+install-api::
+	$(INSTALL_DIRECTORY) $(INSTALL_ROOT)$(INCLUDE_DIRECTORY)
+	for file in $(SRC_DIR)/*-$(DRIVER_CODE).h; do test -f $$file && $(INSTALL_DATA) $$file $(INSTALL_ROOT)$(INCLUDE_DIRECTORY); done || :
+
+install:: $(INSTALL_API)
+
+uninstall::
+	rm -f $(INSTALL_ROOT)$(INCLUDE_DIRECTORY)/*-$(DRIVER_CODE).h
+
+clean::
+	-rm -f $(BRL_MOD_NAME).*
+	-rm -f $(BLD_TOP)$(DAT_DIR)/brltty-$(DRIVER_CODE)[-.]*
diff --git a/brltty-config.sh.in b/brltty-config.sh.in
new file mode 100644
index 0000000..5168598
--- /dev/null
+++ b/brltty-config.sh.in
@@ -0,0 +1,131 @@
+#!/bin/sh
+# @configure_input@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+brlttyConfig_assignVariable() {
+   eval "${1}"'="${2}"'
+}
+
+brlttyConfig_defineVariable() {
+   local name="${1}"
+   local value="${2}"
+
+   while true
+   do
+      eval local 'new="'${value}'"'
+      [ "${new}" != "${value}" ] || break
+      value="${new}"
+   done
+
+   brlttyConfig_assignVariable "${name}" "${value}"
+}
+
+brlttyConfig_defineVariables() {
+   local prefix='@prefix@'
+   local sysconfdir='@sysconfdir@'
+   local datarootdir='@datarootdir@'
+   local datadir='@datadir@'
+   local sharedstatedir='@sharedstatedir@'
+   local localstatedir='@localstatedir@'
+   local includedir='@includedir@'
+   local oldincludedir='@oldincludedir@'
+
+   local exec_prefix='@exec_prefix@'
+   local bindir='@bindir@'
+   local sbindir='@sbindir@'
+   local libdir='@libdir@'
+   local libexecdir='@libexecdir@'
+
+   local localedir='@localedir@'
+   local mandir='@mandir@'
+   local docdir='@docdir@'
+   local infodir='@infodir@'
+   local htmldir='@htmldir@'
+   local pdfdir='@pdfdir@'
+   local psdir='@psdir@'
+   local dvidir='@dvidir@'
+
+   local PACKAGE_TARNAME='@PACKAGE_TARNAME@'
+   local PACKAGE_NAME='@PACKAGE_NAME@'
+   local PACKAGE_VERSION='@PACKAGE_VERSION@'
+   local PACKAGE_URL='@PACKAGE_URL@'
+   local PACKAGE_BUGREPORT='@PACKAGE_BUGREPORT@'
+
+   local execute_root='@execute_root@'
+   local program_directory='@program_directory@'
+   local drivers_directory='@drivers_directory@'
+   local tables_directory='@tables_directory@'
+   local updatable_directory='@updatable_directory@'
+   local writable_directory='@writable_directory@'
+   local manpage_directory='@manpage_directory@'
+   local include_directory='@include_directory@'
+
+   local privilege_parameters='@privilege_parameters@'
+
+   local api_version='@api_version@'
+   local api_release='@api_release@'
+   local api_authkeyfile='@api_authkeyfile@'
+
+   brlttyConfig_defineVariable BRLTTY_PREFIX "${prefix}"
+   brlttyConfig_defineVariable BRLTTY_SYSCONFDIR "${sysconfdir}"
+   brlttyConfig_defineVariable BRLTTY_DATAROOTDIR "${datarootdir}"
+   brlttyConfig_defineVariable BRLTTY_DATADIR "${datadir}"
+   brlttyConfig_defineVariable BRLTTY_SHAREDSTATEDIR "${sharedstatedir}"
+   brlttyConfig_defineVariable BRLTTY_LOCALSTATEDIR "${localstatedir}"
+   brlttyConfig_defineVariable BRLTTY_INCLUDEDIR "${includedir}"
+   brlttyConfig_defineVariable BRLTTY_OLDINCLUDEDIR "${oldincludedir}"
+
+   brlttyConfig_defineVariable BRLTTY_EXEC_PREFIX "${exec_prefix}"
+   brlttyConfig_defineVariable BRLTTY_BINDIR "${bindir}"
+   brlttyConfig_defineVariable BRLTTY_SBINDIR "${sbindir}"
+   brlttyConfig_defineVariable BRLTTY_LIBDIR "${libdir}"
+   brlttyConfig_defineVariable BRLTTY_LIBEXECDIR "${libexecdir}"
+
+   brlttyConfig_defineVariable BRLTTY_LOCALEDIR "${localedir}"
+   brlttyConfig_defineVariable BRLTTY_MANDIR "${mandir}"
+   brlttyConfig_defineVariable BRLTTY_INFODIR "${infodir}"
+   brlttyConfig_defineVariable BRLTTY_DOCDIR "${docdir}"
+   brlttyConfig_defineVariable BRLTTY_HTMLDIR "${htmldir}"
+   brlttyConfig_defineVariable BRLTTY_PDFDIR "${pdfdir}"
+   brlttyConfig_defineVariable BRLTTY_PSDIR "${psdir}"
+   brlttyConfig_defineVariable BRLTTY_DVIDIR "${dvidir}"
+
+   brlttyConfig_defineVariable BRLTTY_TARNAME "${PACKAGE_TARNAME}"
+   brlttyConfig_defineVariable BRLTTY_NAME "${PACKAGE_NAME}"
+   brlttyConfig_defineVariable BRLTTY_VERSION "${PACKAGE_VERSION}"
+   brlttyConfig_defineVariable BRLTTY_URL "${PACKAGE_URL}"
+   brlttyConfig_defineVariable BRLTTY_BUGREPORT "${PACKAGE_BUGREPORT}"
+
+   brlttyConfig_defineVariable BRLTTY_ROOT "${execute_root}"
+   brlttyConfig_defineVariable BRLTTY_PROGRAMS_DIRECTORY "${program_directory}"
+   brlttyConfig_defineVariable BRLTTY_DRIVERS_DIRECTORY "${drivers_directory}"
+   brlttyConfig_defineVariable BRLTTY_TABLES_DIRECTORY "${tables_directory}"
+   brlttyConfig_defineVariable BRLTTY_UPDATABLE_DIRECTORY "${updatable_directory}"
+   brlttyConfig_defineVariable BRLTTY_WRITABLE_DIRECTORY "${writable_directory}"
+   brlttyConfig_defineVariable BRLTTY_MANPAGE_DIRECTORY "${manpage_directory}"
+   brlttyConfig_defineVariable BRLTTY_INCLUDE_DIRECTORY "${include_directory}"
+
+   brlttyConfig_defineVariable BRLTTY_PRIVILEGE_PARAMETERS "${privilege_parameters}"
+
+   brlttyConfig_defineVariable BRLAPI_VERSION "${api_version}"
+   brlttyConfig_defineVariable BRLAPI_RELEASE "${api_release}"
+   brlttyConfig_defineVariable BRLAPI_KEY_FILE "${sysconfdir}/${api_authkeyfile}"
+}
+
+brlttyConfig_defineVariables
diff --git a/brltty-genkey b/brltty-genkey
new file mode 100755
index 0000000..ecaf170
--- /dev/null
+++ b/brltty-genkey
@@ -0,0 +1,62 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+umask 077
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+. "${programDirectory}/brltty-config.sh"
+defaultKeyFile="${BRLTTY_EXECUTE_ROOT}${BRLAPI_KEY_FILE}"
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Generate the BrlAPI access key.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+addProgramOption f string.path keyFile "the BrlAPI key file" "${defaultKeyFile}"
+parseProgramArguments "${@}"
+
+try() {
+   ("${@}"; exit "${?}") 2>/dev/null && {
+      logMessage task "key generated"
+      exit 0
+   }
+}
+
+[ -n "${keyFile}" ] || keyFile="${defaultKeyFile}"
+
+logNote "trying mcookie"
+try mcookie >"${keyFile}"
+
+for device in "/dev/urandom" "/dev/random"
+do
+   [ -c "${device}" -a -r "${device}" ] && {
+      logNote "trying ${device}"
+      try dd if="${device}" of="${keyFile}" bs=32 count=1
+   }
+done
+
+logNote "trying \$RANDOM"
+key="${RANDOM}"
+[ -n "${key}" ] && try echo >"${keyFile}" "${key}"
+
+rm -f -- "${keyFile}"
+semanticError "no supported mechanism is available"
+exit 3
diff --git a/brltty-mkuser b/brltty-mkuser
new file mode 100755
index 0000000..75f031c
--- /dev/null
+++ b/brltty-mkuser
@@ -0,0 +1,162 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+. "`dirname "${0}"`/brltty-prologue.sh"
+. "${programDirectory}/brltty-config.sh"
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Simplify the job of Creating and making changes to BRLTTY's unprivileged user.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+declare -A privilegeParameters
+parseParameterString privilegeParameters "${BRLTTY_PRIVILEGE_PARAMETERS}" lx
+
+readonly defaultUserName="${privilegeParameters["USER"]}"
+readonly defaultUserInformation="Braille Device Daemon"
+readonly defaultHomeDirectory="${BRLTTY_UPDATABLE_DIRECTORY}"
+readonly defaultLoginShell="/sbin/nologin"
+
+showProgramUsageNotes() {
+cat <<END_OF_PROGRAM_USAGE_NOTES
+When creating a new user, these options have the following defaults:
+  -d  ${defaultHomeDirectory:-the home directory isn't defined}
+  -g  a new group with the same name as the user
+  -i  ${defaultUserInformation}
+  -l  ${defaultLoginShell}
+These defaults don't apply when making changes to an existing user.
+END_OF_PROGRAM_USAGE_NOTES
+}
+
+executeCommand() {
+   "${useSudo}" && set -- sudo -- "${@}"
+
+   if "${testMode}"
+   then
+      echo "${*}"
+   else
+      "${@}"
+   fi
+}
+
+groupList=()
+addGroups() {
+   while [ "${#}" -gt 0 ]
+   do
+      local group="${1}"
+      shift 1
+      [ -n "${group}" ] || continue
+
+      [ "${group#*/}" = "${group}" ] || {
+         group="$(stat -c "%G" "${group}")" || :
+         [ -n "${group}" ] || continue
+      }
+
+      local g
+      for g in "${groupList[@]}"
+      do
+         [ "${g}" = "${group}" ] && {
+            group=""
+            break
+         }
+      done
+
+      [ -n "${group}" ] && groupList[${#groupList[*]}]="${group}"
+   done
+
+   return 0
+}
+
+addProgramOption a flag noALSA "don't allow playing sound via the ALSA framework"
+addProgramOption b flag noBrlapi "don't allow reading BrlAPI's authorization key file"
+addProgramOption c flag noConsoles "don't allow access to the virtual consoles"
+addProgramOption d string.path homeDirectory "set/change the home (login) directory"
+addProgramOption g string.group primaryGroup "set/change the primary (login) group"
+addProgramOption i string.text userInformation "set/change the user information (gecos) text"
+addProgramOption k flag noKeyboards "don't allow keyboard monitoring"
+addProgramOption l string.path loginShell "set/change the login shell"
+addProgramOption p flag noPulse "don't allow playing sound via the Pulse Audio server"
+addProgramOption s flag noSerial "don't allow access to serial devices"
+addProgramOption u flag noUSB "don't allow access to USB devices"
+addProgramOption E flag allowChanges "allow changes to an existing user"
+addProgramOption G flag noGroups "don't set/change the supplementary group list"
+addProgramOption N flag allowCreate "allow creating a new user"
+addProgramOption S flag useSudo "use sudo to execute the commands as root"
+addProgramOption T flag testMode "test mode - show the commands that would be executed"
+addProgramOption U string.user userName "the user to be created/changed" "${defaultUserName}"
+parseProgramArguments "${@}"
+
+[ -n "${userName}" ] || userName="${defaultUserName}"
+[ -n "${userName}" ] || syntaxError "unprivileged user not configured - use -U (user) to specify"
+
+if id -u "${userName}" >/dev/null 2>&1
+then
+   "${allowChanges}" || semanticError "user already exists: ${userName} - use -E (existing) to allow"
+   command=(usermod)
+else
+   "${allowCreate}" || semanticError "user doesn't exist: ${userName} - use -N (new) to allow"
+   command=(useradd --system)
+   [ -n "${primaryGroup}" ] || command+=(--user-group)
+
+   [ -n "${userInformation}" ] || userInformation="${defaultUserInformation}"
+   [ -n "${homeDirectory}" ] || homeDirectory="${defaultHomeDirectory}"
+   [ -n "${loginShell}" ] || loginShell="${defaultLoginShell}"
+
+   if [ -n "${homeDirectory}" ]
+   then
+      command+=(--create-home)
+   else
+      command+=(--no-create-home)
+   fi
+fi
+
+[ -n "${homeDirectory}" ] && command+=(--home "${homeDirectory}")
+[ -n "${primaryGroup}" ] && command+=(--gid "${primaryGroup}")
+[ -n "${userInformation}" ] && command+=(--comment "${userInformation}")
+[ -n "${loginShell}" ] && command+=(--shell "${loginShell}")
+
+"${noGroups}" || {
+   "${noALSA}" || addGroups audio
+   "${noBrlapi}" || addGroups "${BRLAPI_KEY_FILE}"
+   "${noConsoles}" || addGroups tty /dev/vcs1 /dev/tty1
+   "${noKeyboards}" || addGroups input
+   "${noPulse}" || addGroups pulse-access
+   "${noSerial}" || addGroups /dev/ttyS0
+   "${noUSB}" || addGroups /dev/bus/usb
+
+   groupsOperand="${groupList[*]}"
+   groupsOperand="${groupsOperand// /,}"
+   command+=(--groups "${groupsOperand}")
+}
+
+"${testMode}" || {
+   if "${useSudo}"
+   then
+      sudo -v
+   elif [ "$(id -u)" -ne 0 ]
+   then
+      semanticError "not executing as root"
+   fi
+}
+
+command+=(-- "${userName}")
+executeCommand "${command[@]}"
+exit 0
diff --git a/brltty-prologue.bash b/brltty-prologue.bash
new file mode 100644
index 0000000..9360fad
--- /dev/null
+++ b/brltty-prologue.bash
@@ -0,0 +1,540 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${BASH_SOURCE[0]}")/brltty-prologue.sh"
+
+getElement() {
+   eval setVariable "\${1}" "\"\${${2}[\"${3}\"]}\""
+}
+
+setElement() {
+   eval setVariable "\${1}[\"${2}\"]" "\"\${3}\""
+}
+
+setElements() {
+   local _array="${1}"
+   shift 1
+   eval "${_array}=(\"\${@}\")"
+}
+
+appendElements() {
+   local _array="${1}"
+   shift 1
+   eval "${_array}+=(\"\${@}\")"
+}
+
+prependElements() {
+   local _array="${1}"
+   shift 1
+   eval setElements "\${_array}" "\"\${@}\" \"\${${_array}[@]}\""
+}
+
+shiftElements() {
+   local _array="${1}"
+   local _count="${2}"
+
+   eval set -- "\"\${${_array}[@]}\""
+   shift "${_count}"
+   setElements "${_array}" "${@}"
+}
+
+getElementCount() {
+   eval setVariable "\${1}" "\${#${2}[*]}"
+}
+
+getElementNames() {
+   eval "${1}=(\"\${!${2}[@]}\")"
+}
+
+forElements() {
+   local _array="${1}"
+   shift 1
+
+   local _names=()
+   getElementNames _names "${_array}"
+
+   local name
+   for name in "${_names[@]}"
+   do
+      local value
+      getElement value "${_array}" "${name}"
+      "${@}" "${name}" "${value}"
+   done
+}
+
+getElements() {
+   local _toArray="${1}"
+   local _fromArray="${2}"
+   forElements "${_fromArray}" setElement "${_toArray}"
+}
+
+listElement() {
+   local array="${1}"
+   local name="${2}"
+   local value="${3}"
+   echo "${array}[${name}]: ${value}"
+}
+
+listElements() {
+   local _array="${1}"
+   forElements "${_array}" listElement "${_array}" | sort
+}
+
+writeElement() {
+   local name="${1}"
+   local value="${2}"
+   echo "${name} ${value}"
+}
+
+writeElements() {
+   local _array="${1}"
+   forElements "${_array}" writeElement | sort
+}
+
+readElements() {
+   local _array="${1}"
+
+   local name value
+   while read name value
+   do
+      setElement "${_array}" "${name}" "${value}"
+   done
+}
+
+toRelativePath() {
+   local toPath="${1}"
+   local variable="${2}"
+
+   [ -n "${toPath}" ] || toPath="."
+   resolveDirectory "${toPath}" toPath
+   local fromPath="$(pwd)"
+
+   [ "${fromPath%/}" = "${fromPath}" ] && fromPath+="/"
+   [ "${toPath%/}" = "${toPath}" ] && toPath+="/"
+
+   local fromLength="${#fromPath}"
+   local toLength="${#toPath}"
+
+   local limit=$(( (fromLength < toLength)? fromLength: toLength ))
+   local index=0
+   local start=0
+
+   while (( index < limit ))
+   do
+      local fromChar="${fromPath:index:1}"
+      local toChar="${toPath:index:1}"
+
+      [ "${fromChar}" = "${toChar}" ] || break
+      [ "${fromChar}" = "/" ] && start="$((index + 1))"
+      let "index += 1"
+   done
+
+   fromPath="${fromPath:start}"
+   toPath="${toPath:start}"
+
+   while [ "${#fromPath}" -gt 0 ]
+   do
+      toPath="../${toPath}"
+      fromPath="${fromPath#*/}"
+   done
+
+   [ -n "${toPath}" ] || toPath="."
+
+   if [ -n "${variable}" ]
+   then
+      setVariable "${variable}" "${toPath}"
+   else
+      echo "${toPath}"
+   fi
+}
+
+programConfigurationFilePrefixArray=()
+makeProgramConfigurationFilePrefixArray() {
+   [ "${#programConfigurationFilePrefixArray[*]}" -gt 0 ] || {
+      programConfigurationFilePrefixArray+=("${programDirectory}/.")
+
+      [ -n "${HOME}" ] && {
+         programConfigurationFilePrefixArray+=("${HOME}/.config/${programName}/")
+         programConfigurationFilePrefixArray+=("${HOME}/.")
+      }
+
+      programConfigurationFilePrefixArray+=("/etc/${programName}/")
+      programConfigurationFilePrefixArray+=("/etc/xdg/${programName}/")
+      programConfigurationFilePrefixArray+=("/etc/")
+   }
+}
+
+findProgramConfigurationFile() {
+   local fileVariable="${1}"
+   shift 1
+
+   [ "${#}" -gt 0 ] || set -- conf cfg
+   local fileExtensions=("${@}")
+
+   makeProgramConfigurationFilePrefixArray
+   local prefix
+
+   for prefix in "${programConfigurationFilePrefixArray[@]}"
+   do
+      local extension
+
+      for extension in "${fileExtensions[@]}"
+      do
+         local _file="${prefix}${programName}.${extension}"
+         [ -f "${_file}" ] || continue
+         [ -r "${_file}" ] || continue
+
+         setVariable "${fileVariable}" "${_file}"
+         return 0
+      done
+   done
+
+   return 1
+}
+
+programComponentDirectoryArray=()
+makeProgramComponentDirectoryArray() {
+   [ "${#programComponentDirectoryArray[*]}" -gt 0 ] || {
+      local subdirectory="libexec"
+
+      programComponentDirectoryArray+=("${PWD}")
+      programComponentDirectoryArray+=("${PWD}/../${subdirectory}")
+
+      programComponentDirectoryArray+=("${programDirectory}")
+      programComponentDirectoryArray+=("${programDirectory}/../${subdirectory}")
+
+      [ -n "${HOME}" ] && {
+         programComponentDirectoryArray+=("${HOME}/.config/${programName}")
+         programComponentDirectoryArray+=("${HOME}/${subdirectory}")
+      }
+
+      programComponentDirectoryArray+=("/usr/${subdirectory}")
+      programComponentDirectoryArray+=("/${subdirectory}")
+
+      programComponentDirectoryArray+=("/etc/${programName}")
+      programComponentDirectoryArray+=("/etc/xdg/${programName}")
+   }
+}
+
+findProgramComponent() {
+   local fileVariable="${1}"
+   local name="${2}"
+   shift 2
+   local fileExtensions=("${@}")
+
+   if [ "${#fileExtensions[*]}" -eq 0 ]
+   then
+      fileExtensions=(bash sh)
+   else
+      name="${programName}.${name}"
+   fi
+
+   makeProgramComponentDirectoryArray
+   local directory
+
+   for directory in "${programComponentDirectoryArray[@]}"
+   do
+      local extension
+
+      for extension in "${fileExtensions[@]}"
+      do
+         local _file="${directory}/${name}.${extension}"
+         [ -f "${_file}" ] || continue
+         [ -r "${_file}" ] || continue
+
+         setVariable "${fileVariable}" "${_file}"
+         return 0
+      done
+   done
+
+   return 1
+}
+
+includeProgramComponent() {
+   local name="${1}"
+
+   local component
+   findProgramComponent component "${name}" || {
+      logWarning "program component not found: ${name}"
+      return 1
+   }
+
+   . "${component}" || {
+      logWarning "problem including program component: ${component}"
+      return 2
+   }
+
+   logNote "program component included: ${component}"
+   return 0
+}
+
+declare -A persistentProgramSettingsArray=()
+persistentProgramSettingsChanged=false
+readonly persistentProgramSettingsExtension="conf"
+
+restorePersistentProgramSettins() {
+   local settingsFile="${1}"
+   local found=false
+
+   if [ -n "${settingsFile}" ]
+   then
+      [ -e "${settingsFile}" ] && found=true
+   elif findProgramConfigurationFile settingsFile "${persistentProgramSettingsExtension}"
+   then
+      found=true
+   fi
+
+   "${found}" && {
+      logNote "restoring persistent program settings: ${settingsFile}"
+      persistentProgramSettingsArray=()
+      readElements persistentProgramSettingsArray <"${settingsFile}"
+      persistentProgramSettingsChanged=false
+   }
+}
+
+savePersistentProgramSettins() {
+   local settingsFile="${1}"
+
+   "${persistentProgramSettingsChanged}" && {
+      [ -n "${settingsFile}" ] || {
+         findProgramConfigurationFile settingsFile "${persistentProgramSettingsExtension}" && [ -f "${settingsFile}" -a -w "${settingsFile}" ] || {
+            settingsFile=""
+            local prefix
+
+            for prefix in "${programConfigurationFilePrefixArray[@]}"
+            do
+               local directory="${prefix%/}"
+               [ "${directory}" = "${prefix}" ] && continue
+
+               if [ -e "${directory}" ]
+               then
+                  [ -d "${directory}" ] || continue
+               else
+                  mkdir --parents -- "${directory}" || continue
+                  logNote "program configuration directory created: ${directory}"
+               fi
+
+               [ -w "${directory}" ] || continue
+               settingsFile="${directory}/${programName}.${persistentProgramSettingsExtension}"
+               break
+            done
+
+            [ -n "${settingsFile}" ] || {
+               logWarning "no eligible program configuration directory"
+               return 1
+            }
+         }
+      }
+
+      logNote "saving persistent program settings: ${settingsFile}"
+      writeElements persistentProgramSettingsArray >"${settingsFile}"
+      persistentProgramSettingsChanged=false
+   }
+
+   return 0
+}
+
+getPersistentProgramSetting() {
+   local variable="${1}"
+   local name="${2}"
+
+   setVariable "${variable}" "${persistentProgramSettingsArray["${name}"]}"
+}
+
+changePersistentProgramSetting() {
+   local name="${1}"
+   local value="${2}"
+   local -n setting="persistentProgramSettingsArray[\"${name}\"]"
+
+   [ "${value}" = "${setting}" ] || {
+      setting="${value}"
+      persistentProgramSettingsChanged=true
+   }
+}
+
+evaluateExpression() {
+   local resultVariable="${1}"
+   local expression="${2}"
+
+   local _result
+   _result="$(bc --quiet <<<"${expression}")" || return 1
+   [ -n "${_result}" ] || return 1
+
+   setVariable "${resultVariable}" "${_result}"
+   return 0
+}
+
+if [ -n "${SRANDOM}" ]
+then
+   declare -n bashRandomNumberVariable="SRANDOM"
+else
+   declare -n bashRandomNumberVariable="RANDOM"
+fi
+
+getRandomInteger() {
+   local resultVariable="${1}"
+   local maximumValue="${2}"
+   local minimumValue="${3:-1}"
+
+   setVariable "${resultVariable}" $(( (bashRandomNumberVariable % (maximumValue - minimumValue + 1)) + minimumValue ))
+}
+
+convertUnit() {
+   local resultVariable="${1}"
+   local from="${2}"
+   local to="${3}"
+   local precision="${4}"
+
+   local toValue
+   toValue="$(units --terse --output-format "%.${precision:-0}f" "${from}" "${to}")" || return 1
+
+   [ "${toValue}" = "${toValue%.*}" ] || {
+      toValue="${toValue%%*(0)}"
+      toValue="${toValue%.}"
+   }
+
+   setVariable "${resultVariable}" "${toValue}"
+   return 0
+}
+
+convertSimpleUnit() {
+   local resultVariable="${1}"
+   local fromValue="${2}"
+   local fromUnit="${3}"
+   local toUnit="${4}"
+   local precision="${5}"
+
+   convertUnit "${resultVariable}" "${fromValue}${fromUnit}" "${toUnit}" "${precision}" || return 1
+   return 0
+}
+
+formatSimpleUnit() {
+   local variable="${1}"
+   local fromUnit="${2}"
+   local toUnit="${3}"
+   local precision="${4}"
+
+   local value="${!variable}"
+   convertSimpleUnit value "${value}" "${fromUnit}" "${toUnit}" "${precision}" || return 1
+
+   setVariable "${variable}" "${value}${toUnit}"
+   return 0
+}
+
+convertComplexUnit() {
+   local resultVariable="${1}"
+   local fromValue="${2}"
+   local fromUnit="${3}"
+   local toUnit="${4}"
+   local unitType="${5}"
+   local precision="${6}"
+
+   convertUnit "${resultVariable}" "${unitType}${fromUnit}(${fromValue})" "${unitType}${toUnit}" "${precision}" || return 1
+   return 0
+}
+
+formatComplexUnit() {
+   local variable="${1}"
+   local fromUnit="${2}"
+   local toUnit="${3}"
+   local unitType="${4}"
+   local precision="${5}"
+
+   local value="${!variable}"
+   convertComplexUnit value "${value}" "${fromUnit}" "${toUnit}" "${unitType}" "${precision}" || return 1
+
+   setVariable "${variable}" "${value}${toUnit}"
+   return 0
+}
+
+isAbbreviation() {
+   local abbreviation="${1}"
+   shift 1
+
+   local length="${#abbreviation}"
+   local word
+
+   for word
+   do
+      [ "${length}" -le "${#word}" ] || continue
+      [ "${abbreviation}" = "${word:0:length}" ] && return 0
+   done
+
+   return 1
+}
+
+verifyChoice() {
+   local label="${1}"
+   local valueVariable="${2}"
+   shift 2
+
+   local value
+   getVariable "${valueVariable}" value
+
+   local candidates=()
+   local choice
+
+   for choice
+   do
+      [ "${value}" = "${choice}" ] && return 0
+      isAbbreviation "${value}" "${choice}" || continue
+      candidates+=("${choice}")
+   done
+
+   local count="${#candidates[*]}"
+   [ "${count}" -gt 0 ] || syntaxError "invalid ${label}: ${value}"
+
+   [ "${count}" -eq 1 ] || {
+      local message="ambiguous ${label}: ${value}"
+      local delimiter=" ("
+
+      for choice in "${candidates[@]}"
+      do
+         message+="${delimiter}${choice}"
+         delimiter=", "
+      done
+
+      message+=")"
+      syntaxError "${message}"
+   }
+
+   setVariable "${valueVariable}" "${candidates[0]}"
+}
+
+confirmAction() {
+   local prompt="${1}"
+
+   local noWord="no"
+   local yesWord="yes"
+   local response
+
+   while read -p "${programName}: ${prompt} ([${noWord}] | ${yesWord})? " -r response
+   do
+      [ -n "${response}" ] || return 1
+      response="${response,,*}"
+
+      isAbbreviation "${response}" "${noWord}" && return 1 ||
+      isAbbreviation "${response}" "${yesWord}" && return 0 ||
+      programMessage "unrecognized response"
+   done
+
+   echo >&2 ""
+   return 2
+}
+
diff --git a/brltty-prologue.lua b/brltty-prologue.lua
new file mode 100644
index 0000000..1ed67f4
--- /dev/null
+++ b/brltty-prologue.lua
@@ -0,0 +1,69 @@
+--[[
+  libbrlapi - A library providing access to braille terminals for applications.
+ 
+  Copyright (C) 2006-2023 by Dave Mielke <dave@mielke.cc>
+ 
+  libbrlapi comes with ABSOLUTELY NO WARRANTY.
+ 
+  This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ 
+  Web Page: http://brltty.app/
+ 
+  This software is maintained by Dave Mielke <dave@mielke.cc>.
+]]
+
+programName = string.match(arg[0], "([^/]*)$")
+programArgumentCount = #arg
+programArgumentNumber = 1
+
+function writeProgramMessage (message)
+  io.stderr:write(string.format("%s: %s\n", programName, message))
+end
+
+function syntaxError (message)
+  writeProgramMessage(message)
+  os.exit(2)
+end
+
+function haveMoreProgramArguments () 
+  return programArgumentNumber <= programArgumentCount
+end
+
+function nextProgramArgument (label)
+  if not haveMoreProgramArguments() then
+    syntaxError(string.format("missing %s", label))
+  end
+
+  local argument = arg[programArgumentNumber]
+  programArgumentNumber = programArgumentNumber + 1
+  return argument
+end
+
+function stringContains (string, substring)
+  return not not string:find(substring, 1, true)
+end
+
+function splitString (string, delimiter)
+  local components = {}
+  local count = 0
+  local oldPosition = 1
+
+  while true do
+    local newPosition = string:find(delimiter, oldPosition, true)
+    if not newPosition then break end
+
+    count = count + 1
+    components[count] = string:sub(oldPosition, newPosition-1)
+
+    oldPosition = newPosition + #delimiter
+  end
+
+  count = count + 1
+  components[count] = string:sub(oldPosition)
+
+  return components
+end
+
diff --git a/brltty-prologue.sh b/brltty-prologue.sh
new file mode 100644
index 0000000..1fd0b7a
--- /dev/null
+++ b/brltty-prologue.sh
@@ -0,0 +1,838 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+testMode=false
+
+readonly initialDirectory="$(pwd)"
+readonly programName="$(basename "${0}")"
+
+programMessage() {
+   local message="${1}"
+
+   [ -z "${message}" ] || echo >&2 "${programName}: ${message}"
+}
+
+setVariable() {
+   eval "${1}"'="${2}"'
+}
+
+getVariable() {
+   if [ -n "${2}" ]
+   then
+      eval "${2}"'="${'"${1}"'}"'
+   else
+      eval 'echo "${'"${1}"'}"'
+   fi
+}
+
+defineEnumeration() {
+   local prefix="${1}"
+   shift 1
+
+   local name
+   local value=1
+
+   for name
+   do
+      local variable="${prefix}${name}"
+      readonly "${variable}"="${value}"
+      value=$((value + 1))
+   done
+}
+
+defineEnumeration programLogLevel_ error warning notice task note detail
+programLogLevel=$((${programLogLevel_task}))
+
+logMessage() {
+   local level="${1}"
+   local message="${2}"
+
+   local variable="programLogLevel_${level}"
+   local value=$((${variable}))
+
+   [ "${value}" -gt 0 ] || programMessage "unknown log level: ${level}"
+   [ "${value}" -gt "${programLogLevel}" ] || programMessage "${message}"
+}
+
+logError() {
+   logMessage error "${@}"
+}
+
+logWarning() {
+   logMessage warning "${@}"
+}
+
+logNotice() {
+   logMessage notice "${@}"
+}
+
+logTask() {
+   logMessage task "${@}"
+}
+
+logNote() {
+   logMessage note "${@}"
+}
+
+logDetail() {
+   logMessage detail "${@}"
+}
+
+programTerminationCommandCount=0
+
+runProgramTerminationCommands() {
+   set +e
+
+   while [ "${programTerminationCommandCount}" -gt 0 ]
+   do
+      set -- $(getVariable "programTerminationCommand${programTerminationCommandCount}")
+      programTerminationCommandCount=$((programTerminationCommandCount - 1))
+
+      local process="${1}"
+      local directory="${2}"
+      shift 2
+
+      [ "${process}" = "${$}" ] && {
+         cd "${directory}"
+         "${@}"
+      }
+   done
+}
+
+pushProgramTerminationCommand() {
+   [ "${programTerminationCommandCount}" -gt 0 ] || trap runProgramTerminationCommands exit
+   setVariable "programTerminationCommand$((programTerminationCommandCount += 1))" "${$} $(pwd) ${*}"
+}
+
+needTemporaryDirectory() {
+   local variable="${1:-temporaryDirectory}"
+
+   local _directory
+   getVariable "${variable}" _directory
+
+   [ -n "${_directory}" ] || {
+      [ -n "${TMPDIR}" -a -d "${TMPDIR}" -a -r "${TMPDIR}" -a -w "${TMPDIR}" -a -x "${TMPDIR}" ] || export TMPDIR="/tmp"
+      _directory="$(mktemp -d "${TMPDIR}/${programName}.$(date +"%Y%m%d-%H%M%S").XXXXXX")"
+      pushProgramTerminationCommand rm -f -r -- "${_directory}"
+      cd "${_directory}"
+      setVariable "${variable}" "${_directory}"
+   }
+}
+
+resolveDirectory() {
+   local path="${1}"
+   local variable="${2}"
+   local absolute="$(cd "${path}" && pwd)"
+
+   if [ -n "${variable}" ]
+   then
+      setVariable "${variable}" "${absolute}"
+   else
+      echo "${absolute}"
+   fi
+}
+
+programDirectory="$(dirname "${0}")"
+readonly programDirectory="$(resolveDirectory "${programDirectory}")"
+
+parseParameterString() {
+   local valuesArray="${1}"
+   local parameters="${2}"
+   local code="${3}"
+
+   set -- ${parameters//,/ }
+   local parameter
+
+   for parameter
+   do
+      local name="${parameter%%=*}"
+      [ "${name}" = "${parameter}" ] && continue
+      [ -n "${name}" ] || continue
+      local value="${parameter#*=}"
+
+      local qualifier="${name%%:*}"
+      [ "${qualifier}" = "${name}" ] || {
+         [ -n "${qualifier}" ] || continue
+         [ "${qualifier}" = "${code}" ] || continue
+         name="${name#*:}"
+      }
+
+      setVariable "${valuesArray}[${name^^*}]" "${value}"
+   done
+}
+
+stringHead() {
+   local string="${1}"
+   local length="${2}"
+
+   [ "${length}" -eq 0 ] || expr substr "${string}" 1 "${length}"
+}
+
+stringTail() {
+   local string="${1}"
+   local start="${2}"
+
+   local length=$((${#string} - start))
+   [ "${length}" -eq 0 ] || expr substr "${string}" $((start + 1)) "${length}"
+}
+
+stringReplace() {
+   local string="${1}"
+   local pattern="${2}"
+   local replacement="${3}"
+   local flags="${4}"
+
+   echo "${string}" | sed -e "s/${pattern}/${replacement}/${flags}"
+}
+
+stringReplaceAll() {
+   local string="${1}"
+   local pattern="${2}"
+   local replacement="${3}"
+
+   stringReplace "${string}" "${pattern}" "${replacement}" "g"
+}
+
+stringQuoted() {
+   local string="${1}"
+
+   local pattern="'"
+   local replacement="'"'"'"'"'"'"'"
+   string="$(stringReplaceAll "${string}" "${pattern}" "${replacement}")"
+   echo "'${string}'"
+}
+
+stringWrapped() {
+   local string="${1}"
+   local width="${2}"
+
+   local result=""
+   local paragraph=""
+
+   while true
+   do
+      local length="$(expr "${string}" : $'[^\n]*\n')"
+      local line
+
+      if [ "${length}" -eq 0 ]
+      then
+         line="${string}"
+         string=""
+      else
+         line="$(stringHead "${string}" $((length - 1)))"
+         string="$(stringTail "${string}" "${length}")"
+      fi
+
+      [ -z "${line}" ] || [ "${line}" != "${line# }" ] || {
+         [ -z "${paragraph}" ] || paragraph="${paragraph} "
+         paragraph="${paragraph}${line}"
+         continue
+      }
+
+      while [ "${#paragraph}" -gt "${width}" ]
+      do
+         local head="$(stringHead "${paragraph}" $((width + 1)))"
+         head="${head% *}"
+
+         [ "${#head}" -le "${width}" ] || {
+            head="${paragraph%% *}"
+            [ "${head}" != "${paragraph}" ] || break
+         }
+
+         result="${result} $(stringQuoted "${head}")"
+         paragraph="$(stringTail "${paragraph}" $((${#head} + 1)))"
+      done
+
+      [ -z "${paragraph}" ] || {
+         result="${result} $(stringQuoted "${paragraph}")"
+         paragraph=""
+      }
+
+      [ -n "${string}" ] || {
+         [ -z "${line}" ] || result="${result} $(stringQuoted "${line}")"
+         break
+      }
+
+      result="${result} $(stringQuoted "${line}")"
+   done
+
+   echo "${result}"
+}
+
+syntaxError() {
+   local message="${1}"
+
+   logError "${message}"
+   exit 2
+}
+
+semanticError() {
+   local message="${1}"
+
+   logError "${message}"
+   exit 3
+}
+
+internalError() {
+   local message="${1}"
+
+   logError "${message}"
+   exit 4
+}
+
+findSiblingCommand() {
+   local resultVariable="${1}"
+   shift 1
+
+   local command
+   for command in "${@}"
+   do
+      local path="${programDirectory}/${command}"
+      [ -f "${path}" ] || continue
+      [ -x "${path}" ] || continue
+
+      setVariable "${resultVariable}" "${path}"
+      return 0
+   done
+
+   return 1
+}
+
+findHostCommand() {
+   local pathVariable="${1}"
+   local command="${2}"
+
+   local path="$(which "${command}")"
+   [ -n "${path}" ] || return 1
+
+   setVariable "${pathVariable}" "${path}"
+   return 0
+}
+
+verifyHostCommand() {
+   local pathVariable="${1}"
+   local command="${2}"
+
+   findHostCommand "${pathVariable}" "${command}" || {
+      semanticError "host command not found: ${command}"
+   }
+}
+
+executeHostCommand() {
+   logDetail "executing host command: ${*}"
+
+   "${testMode}" || "${@}" || {
+      local status="${?}"
+      logWarning "host command failed with exit status ${status}: ${*}"
+      return "${status}"
+   }
+}
+
+verifyActionFlags() {
+   local allFlag="${1}"
+   shift 1
+
+   local allRequested
+   getVariable "${allFlag}" allRequested
+   local actionFlag
+
+   for actionFlag in "${@}"
+   do
+      local actionRequested
+      getVariable "${actionFlag}" actionRequested
+
+      "${actionRequested}" && {
+         "${allRequested}" && syntaxError "conflicting actions"
+         return
+      }
+   done
+
+   "${allRequested}" || syntaxError "no actions"
+
+   for actionFlag in "${@}"
+   do
+      setVariable "${actionFlag}" true
+   done
+}
+
+testInteger() {
+   local value="${1}"
+
+   [ "${value}" = "0" ] || {
+      value="${value#-}"
+      [ -n "${value}" ] || return 1
+      [ "$(expr "${value}" : '^[1-9][0-9]*$')" -eq "${#value}" ] || return 1
+   }
+
+   return 0
+}
+
+verifyInteger() {
+   local label="${1}"
+   local value="${2}"
+   local minimum="${3}"
+   local maximum="${4}"
+
+   testInteger "${value}" || {
+      semanticError "${label} not an integer: ${value}"
+   }
+
+   [ -n "${minimum}" ] && {
+      [ "${value}" -lt "${minimum}" ] && {
+         semanticError "${label} out of range: ${value} < ${minimum}"
+      }
+   }
+
+   [ -n "${maximum}" ] && {
+      [ "${value}" -gt "${maximum}" ] && {
+         semanticError "${label} out of range: ${value} > ${maximum}"
+      }
+   }
+}
+
+testContainingDirectory() {
+   local directory="${1}"
+   shift 1
+
+   local path
+   for path
+   do
+      [ -e "${directory}/${path}" ] || return 1
+   done
+
+   return 0
+}
+
+findContainingDirectory() {
+   local variable="${1}"
+   local directory="${2}"
+   shift 2
+
+   local value
+   getVariable "${variable}" value
+   [ -n "${value}" ] && return 0
+
+   while :
+   do
+      testContainingDirectory "${directory}" "${@}" && break
+      local parent="$(dirname "${directory}")"
+      [ "${parent}" = "${directory}" ] && return 1
+      directory="${parent}"
+   done
+
+   export "${variable}"="${directory}"
+}
+
+testDirectory() {
+   local path="${1}"
+
+   [ -e "${path}" ] || return 1
+   [ -d "${path}" ] || semanticError "not a directory: ${path}"
+   return 0
+}
+
+verifyWritableDirectory() {
+   local path="${1}"
+
+   testDirectory "${path}" || semanticError "directory not found: ${path}"
+   [ -w "${path}" ] || semanticError "directory not writable: ${path}"
+}
+
+testFile() {
+   local path="${1}"
+
+   [ -e "${path}" ] || return 1
+   [ -f "${path}" ] || semanticError "not a file: ${path}"
+   return 0
+}
+
+verifyInputFile() {
+   local path="${1}"
+
+   testFile "${path}" || semanticError "file not found: ${path}"
+   [ -r "${path}" ] || semanticError "file not readable: ${path}"
+}
+
+verifyOutputFile() {
+   local path="${1}"
+
+   if testFile "${path}"
+   then
+      [ -w "${path}" ] || semanticError "file not writable: ${path}"
+   else
+      verifyWritableDirectory "$(dirname "${path}")"
+   fi
+}
+
+verifyExecutableFile() {
+   local path="${1}"
+
+   testFile "${path}" || semanticError "file not found: ${path}"
+   [ -x "${path}" ] || semanticError "file not executable: ${path}"
+}
+
+verifyInputDirectory() {
+   local path="${1}"
+
+   testDirectory "${path}" || semanticError "directory not found: ${path}"
+}
+
+verifyOutputDirectory() {
+   local path="${1}"
+
+   if testDirectory "${path}"
+   then
+      [ -w "${path}" ] || semanticError "directory not writable: ${path}"
+      rm -f -r -- "${path}/"*
+   else
+      mkdir -p "${path}"
+   fi
+}
+
+programParameterCount=0
+programParameterCountMinimum=-1
+programParameterLabelWidth=0
+
+addProgramParameter() {
+   local label="${1}"
+   local variable="${2}"
+   local usage="${3}"
+   local default="${4}"
+
+   setVariable "programParameterLabel_${programParameterCount}" "${label}"
+   setVariable "programParameterVariable_${programParameterCount}" "${variable}"
+   setVariable "programParameterUsage_${programParameterCount}" "${usage}"
+   setVariable "programParameterDefault_${programParameterCount}" "${default}"
+
+   local length="${#label}"
+   [ "${length}" -le "${programParameterLabelWidth}" ] || programParameterLabelWidth="${length}"
+
+   setVariable "${variable}" ""
+   programParameterCount=$((programParameterCount + 1))
+}
+
+optionalProgramParameters() {
+   if [ "${programParameterCountMinimum}" -lt 0 ]
+   then
+      programParameterCountMinimum="${programParameterCount}"
+      optionalProgramParameterLabel="${1}"
+      optionalProgramParameterUsage="${2}"
+   else
+      logWarning "program parameters are already optional"
+   fi
+}
+
+tooManyProgramParameters() {
+   syntaxError "too many parameters"
+}
+
+programOptionLetters=""
+programOptionString=""
+programOptionOperandWidth=0
+
+programOptionValue_counter=0
+programOptionValue_flag=false
+programOptionValue_list=""
+programOptionValue_string=""
+
+addProgramOption() {
+   local letter="${1}"
+   local type="${2}"
+   local variable="${3}"
+   local usage="${4}"
+   local default="${5}"
+
+   [ "$(expr "${letter}" : '[[:alnum:]]*$')" -eq 1 ] || internalError "invalid program option: -${letter}"
+   [ -z "$(getVariable "programOptionType_${letter}")" ] || internalError "duplicate program option definition: -${letter}"
+
+   local operand
+   case "${type}"
+   in
+      flag | counter)
+         operand=""
+         ;;
+
+      string.* | list.*)
+         operand="${type#*.}"
+         type="${type%%.*}"
+         [ -n "${operand}" ] || internalError "missing program option operand type: -${letter}"
+         ;;
+
+      *) internalError "invalid program option type: ${type} (-${letter})";;
+   esac
+
+   setVariable "programOptionType_${letter}" "${type}"
+   setVariable "programOptionVariable_${letter}" "${variable}"
+   setVariable "programOptionOperand_${letter}" "${operand}"
+   setVariable "programOptionUsage_${letter}" "${usage}"
+   setVariable "programOptionDefault_${letter}" "${default}"
+
+   local value="$(getVariable "programOptionValue_${type}")"
+   setVariable "${variable}" "${value}"
+
+   local length="${#operand}"
+   [ "${length}" -le "${programOptionOperandWidth}" ] || programOptionOperandWidth="${length}"
+
+   programOptionLetters="${programOptionLetters} ${letter}"
+   programOptionString="${programOptionString}${letter}"
+   [ "${length}" -eq 0 ] || programOptionString="${programOptionString}:"
+}
+
+addTestModeOption() {
+   addProgramOption t flag testMode "test mode - log (but don't execute) the host commands"
+}
+
+programUsageLineCount=0
+programUsageLineWidth="${COLUMNS:-72}"
+
+addProgramUsageLine() {
+   local line="${1}"
+
+   setVariable "programUsageLine_${programUsageLineCount}" "${line}"
+   programUsageLineCount=$((programUsageLineCount + 1))
+}
+
+addProgramUsageText() {
+   local text="${1}"
+   local prefix="${2}"
+
+   local width=$((programUsageLineWidth - ${#prefix}))
+
+   while [ "${width}" -lt 1 ]
+   do
+      [ "${prefix%  }" != "${prefix}" ] || break
+      prefix="${prefix%?}"
+      width=$((width + 1))
+   done
+
+   local indent="$(stringReplaceAll "${prefix}" '.' ' ')"
+
+   [ "${width}" -gt 0 ] || {
+      addProgramUsageLine "${prefix}"
+      indent="$(stringTail "${indent}" $((-width + 1)))"
+      prefix="${indent}"
+      width=1
+   }
+
+   eval set -- "$(stringWrapped "${text}" "${width}")"
+   for line
+   do
+      addProgramUsageLine "${prefix}${line}"
+      prefix="${indent}"
+   done
+}
+
+writeProgramUsageLines() {
+   local index=0
+
+   while [ "${index}" -lt "${programUsageLineCount}" ]
+   do
+      getVariable "programUsageLine_${index}"
+      index=$((index + 1))
+   done
+}
+
+showProgramUsageSummary() {
+   set -- ${programOptionLetters}
+
+   local purpose="$(showProgramUsagePurpose)"
+   [ -z "${purpose}" ] || {
+      addProgramUsageText "${purpose}"
+      addProgramUsageLine
+   }
+
+   local line="Syntax: ${programName}"
+   [ "${#}" -eq 0 ] || line="${line} [-option ...]"
+
+   local index=0
+   local suffix=""
+
+   while [ "${index}" -lt "${programParameterCount}" ]
+   do
+      line="${line} "
+
+      [ "${index}" -lt "${programParameterCountMinimum}" ] || {
+         line="${line}["
+         suffix="${suffix}]"
+      }
+
+      line="${line}$(getVariable "programParameterLabel_${index}")"
+      index=$((index + 1))
+   done
+
+   [ -z "${optionalProgramParameterLabel}" ] || {
+      line="${line} [${optionalProgramParameterLabel} ...]"
+
+      [ -z "${optionalProgramParameterUsage}" ] || {
+         addProgramParameter "${optionalProgramParameterLabel} ..." optionalProgramParameterVariable "${optionalProgramParameterUsage}"
+      }
+   }
+
+   line="${line}${suffix}"
+   addProgramUsageLine "${line}"
+
+   [ "${programParameterCount}" -eq 0 ] || {
+      addProgramUsageLine
+      addProgramUsageLine "Parameters:"
+
+      local indent=$((programParameterLabelWidth + 2))
+      local index=0
+
+      while [ "${index}" -lt "${programParameterCount}" ]
+      do
+         local line="$(getVariable "programParameterLabel_${index}")"
+
+         while [ "${#line}" -lt "${indent}" ]
+         do
+            line="${line} "
+         done
+
+         local usage="$(getVariable "programParameterUsage_${index}")"
+         local default="$(getVariable "programParameterDefault_${index}")"
+         [ -z "${default}" ] || usage="${usage} - the default is ${default}"
+         addProgramUsageText "${usage}" "  ${line}"
+
+         index=$((index + 1))
+      done
+   }
+
+   [ "${#}" -eq 0 ] || {
+      addProgramUsageLine
+      addProgramUsageLine "Options:"
+
+      local indent=$((3 + programOptionOperandWidth + 2))
+      local letter
+
+      for letter
+      do
+         local line="-${letter} $(getVariable "programOptionOperand_${letter}")"
+
+         while [ "${#line}" -lt "${indent}" ]
+         do
+            line="${line} "
+         done
+
+         usage="$(getVariable "programOptionUsage_${letter}")"
+         local default="$(getVariable "programOptionDefault_${letter}")"
+         [ -z "${default}" ] || usage="${usage} - the default is ${default}"
+         addProgramUsageText "${usage}" "  ${line}"
+      done
+   }
+
+   local notes="$(showProgramUsageNotes)"
+   [ -z "${notes}" ] || {
+      addProgramUsageLine
+      addProgramUsageText "${notes}"
+   }
+
+   writeProgramUsageLines
+}
+
+addProgramOption h flag programOption_showUsageSummary "show this usage summary, and then exit"
+addProgramOption q counter programOption_quietCount "decrease output verbosity"
+addProgramOption v counter programOption_verboseCount "increase output verbosity"
+
+parseProgramOptions() {
+   local letter
+
+   while getopts ":${programOptionString}" letter
+   do
+      case "${letter}"
+      in
+        \?) syntaxError "unrecognized option: -${OPTARG}";;
+         :) syntaxError "missing operand: -${OPTARG}";;
+
+         *) local variable type
+            setVariable variable "$(getVariable "programOptionVariable_${letter}")"
+            setVariable type "$(getVariable "programOptionType_${letter}")"
+
+            case "${type}"
+            in
+               counter) setVariable "${variable}" $((${variable} + 1));;
+               flag) setVariable "${variable}" true;;
+               list) setVariable "${variable}" "$(getVariable "${variable}") $(stringQuoted "${OPTARG}")";;
+               string) setVariable "${variable}" "${OPTARG}";;
+               *) internalError "unimplemented program option type: ${type} (-${letter})";;
+            esac
+            ;;
+      esac
+   done
+}
+
+parseProgramArguments() {
+   [ "${programParameterCountMinimum}" -ge 0 ] || programParameterCountMinimum="${programParameterCount}"
+
+   parseProgramOptions "${@}"
+   shift $((OPTIND - 1))
+
+   if "${programOption_showUsageSummary}"
+   then
+      showProgramUsageSummary
+      exit 0
+   fi
+
+   local programParameterIndex=0
+   while [ "${#}" -gt 0 ]
+   do
+      [ "${programParameterIndex}" -lt "${programParameterCount}" ] || break
+      setVariable "$(getVariable "programParameterVariable_${programParameterIndex}")" "${1}"
+      shift 1
+      programParameterIndex=$((programParameterIndex + 1))
+   done
+
+   [ "${programParameterIndex}" -ge "${programParameterCountMinimum}" ] || {
+      syntaxError "$(getVariable "programParameterLabel_${programParameterIndex}") not specified"
+   }
+
+   readonly programLogLevel=$((programLogLevel + programOption_verboseCount - programOption_quietCount))
+   processExtraProgramParameters "${@}"
+}
+
+handleInitialHelpOption() {
+   if [ "${#}" -gt 0 ]
+   then
+      if [ "${1}" = "-h" ]
+      then
+         addProgramUsageText "$(cat)"
+         writeProgramUsageLines
+         exit 0
+      fi
+   fi
+}
+
+####################################################################
+# The following functions are stubs that may be copied into the    #
+# main script and augmented. They need to be defined after this    #
+# prologue is embeded and before the program arguments are parsed. #
+####################################################################
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+showProgramUsageNotes() {
+cat <<END_OF_PROGRAM_USAGE_NOTES
+END_OF_PROGRAM_USAGE_NOTES
+}
+
+processExtraProgramParameters() {
+   [ "${#}" -eq 0 ] || tooManyProgramParameters
+}
+
diff --git a/brltty-prologue.tcl b/brltty-prologue.tcl
new file mode 100644
index 0000000..f0d3ad3
--- /dev/null
+++ b/brltty-prologue.tcl
@@ -0,0 +1,698 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set initialDirectory [pwd]
+set scriptDirectory [file normalize [file dirname $::argv0]]
+set prologueDirectory [file normalize [file dirname [info script]]]
+
+try {
+   package require Tclx
+} trap {TCL PACKAGE UNFOUND} {} {
+   proc intersect3 {list1 list2} {
+      foreach number {1 2} {
+         set length$number [llength [set list$number [lsort [set list$number]]]]
+         set index$number 0
+         set only$number [list]
+      }
+      set both [list]
+
+      while {1} {
+         if {$index1 == $length1} {
+            if {$index2 < $length2} {
+               lvarcat only2 [lrange $list2 $index2 end]
+            }
+
+            break
+         }
+
+         if {$index2 == $length2} {
+            lvarcat only1 [lrange $list1 $index1 end]
+            break
+         }
+
+         switch -exact -- [string compare [lindex $list1 $index1] [lindex $list2 $index2]] {
+           -1 {
+               lappend only1 [lindex $list1 $index1]
+               incr index1
+            }
+
+            1 {
+               lappend only2 [lindex $list2 $index2]
+               incr index2
+            }
+
+            0 {
+               lappend both [lindex $list1 $index1]
+               incr index1
+               incr index2
+            }
+         }
+      }
+
+      return [list $only1 $both $only2]
+   }
+
+   proc lcontain {list element} {
+      return [expr {[lsearch -exact $list $element] >= 0}]
+   }
+
+   proc lempty {list} {
+      return [expr {[llength $list] == 0}]
+   }
+
+   proc lrmdups {list} {
+      return [lsort -unique $list]
+   }
+
+   proc lvarcat {listVariable first args} {
+      upvar 1 $listVariable list
+      eval lappend list $first
+
+      foreach arg $args {
+         eval lappend list $arg
+      }
+
+      return $list
+   }
+
+   proc lvarpop {listVariable {index 0}} {
+      upvar 1 $listVariable list
+      set result [lindex $list $index]
+      set list [lreplace $list $index $index]
+      return $result
+   }
+
+   proc lvarpush {listVariable string {index 0}} {
+      upvar 1 $listVariable list
+      set list [linsert $list $index $string]
+      return ""
+   }
+
+   if {[package vcompare $tcl_version 8.4] < 0} {
+      proc readdir {directory} {
+         set workingDirectory [pwd]
+         cd $directory
+         set names [glob -nocomplain *]
+         cd $workingDirectory
+         return $names
+      }
+   } else {
+      proc readdir {directory} {
+         return [glob -directory $directory -tails -nocomplain *]
+      }
+   }
+}
+
+proc makeEnumeration {arrayVariable elements} {
+   upvar 1 $arrayVariable array
+   set value 0
+
+   foreach element $elements {
+      set array($element) $value
+      incr value
+   }
+}
+
+proc getProgramName {} {
+   return [file tail [info script]]
+}
+
+proc writeProgramMessage {message} {
+   if {[string length $message] > 0} {
+      set stream stderr
+      puts $stream "[getProgramName]: $message"
+      flush $stream
+   }
+}
+
+makeEnumeration logLevels {error warning notice task note detail}
+set logLevel $logLevels(task)
+
+proc logMessage {level message} {
+   global logLevels logLevel
+
+   if {$logLevels($level) <= $logLevel} {
+      writeProgramMessage $message
+   }
+}
+
+proc logError {{message ""}} {
+   logMessage error $message
+}
+
+proc logWarning {{message ""}} {
+   logMessage warning $message
+}
+
+proc logNote {{message ""}} {
+   logMessage note $message
+}
+
+proc logDetail {{message ""}} {
+   logMessage detail $message
+}
+
+proc syntaxError {{message ""}} {
+   logError $message
+   exit 2
+}
+
+proc semanticError {{message ""}} {
+   logError $message
+   exit 3
+}
+
+proc toRelativePath {to {from .}} {
+   set variables {from to}
+
+   foreach variable $variables {
+      set $variable [file split [file normalize [set $variable]]]
+   }
+
+   set count [expr {min([llength $from], [llength $to])}]
+
+   while {$count > 0} {
+      if {![string equal [lindex $from 0] [lindex $to 0]]} {
+         break
+      }
+
+      foreach variable $variables {
+         lvarpop $variable
+      }
+
+      incr count -1
+   }
+
+   if {[llength [set to [concat [lrepeat [llength $from] ..] $to]]] == 0} {
+      set to {.}
+   }
+
+   return [eval file join $to]
+}
+
+proc testContainingDirectory {directory names} {
+   foreach name $names {
+      if {![file exists [file join $directory $name]]} {
+         return 0
+      }
+   }
+
+   return 1
+}
+
+proc findContainingDirectory {variable directory names} {
+   if {[info exists $variable]} {
+      if {[string length [set $variable]] > 0} {
+         return 1
+      }
+   }
+
+   set directory [file normalize $directory]
+
+   while {1} {
+      if {[testContainingDirectory $directory $names]} {
+         break
+      }
+
+      if {[string equal [set parent [file dirname $directory]] $directory]} {
+         return 0
+      }
+
+      set directory $parent
+   }
+
+   set $variable $directory
+   return 1
+}
+
+proc withChannel {variable channel body} {
+   uplevel 1 [list set $variable $channel]
+
+   try {
+      try {
+         uplevel 1 $body
+      } on return {result} {
+         return -level 2 $result
+      }
+   } finally {
+      close $channel
+      uplevel 1 [list unset $variable]
+   }
+}
+
+proc readFile {file} {
+   withChannel channel [open $file {RDONLY}] {
+      return [read $channel]
+   }
+}
+
+proc writeFile {file data} {
+   withChannel channel [open $file {WRONLY CREAT TRUNC}] {
+      puts -nonewline $channel $data
+   }
+}
+
+proc replaceFile {file data} {
+   set components [file split $file]
+
+   if {[llength $components] == 0} {
+      return -code error "file argument is empty"
+   }
+
+   set name ".[lindex $components end]"
+   set path "[eval file join [lreplace $components end end] $name]"
+
+   set oldFile "$path.old"
+   set newFile "$path.new"
+
+   file delete $newFile
+   writeFile $newFile $data
+
+   if {[file exists $file]} {
+      file delete $oldFile
+      file rename $file $oldFile
+   }
+
+   file rename $newFile $file
+   file delete $oldFile
+}
+
+proc updateFile {file data {inTestMode 0}} {
+   if {![file exists $file]} {
+      if {$inTestMode} {
+         logMessage notice "test mode - file not created: $file"
+      } else {
+         logMessage notice "creating file: $file"
+         writeFile $file $data
+      }
+   } elseif {![string equal $data [readFile $file]]} {
+      if {$inTestMode} {
+         logMessage notice "test mode - file not updated: $file"
+      } else {
+         logMessage notice "updating file: $file"
+         replaceFile $file $data
+      }
+   } else {
+      return 0
+   }
+
+   return 1
+}
+
+proc forEachLine {lineVariable file body} {
+   upvar 1 $lineVariable line
+
+   try {
+      withChannel channel [open $file {RDONLY}] {
+         while {[gets $channel line] >= 0} {
+            uplevel 1 $body
+         }
+      }
+   } on return {result} {
+      return -level 2 $result
+   }
+}
+
+proc readLines {file} {
+   set lines [list]
+
+   forEachLine line $file {
+      lappend lines $line
+   }
+
+   return $lines
+}
+
+proc isWhitespace {string} {
+   return [string is space $string]
+}
+
+proc wrapLine {line width} {
+   set lines [list]
+
+   while {[set length [string length [set line [string trimleft $line]]]] > 0} {
+      if {$width < $length} {
+         set index $width
+
+         while {$index >= 0} {
+            if {[isWhitespace [string index $line $index]]} {
+               break
+            }
+
+            incr index -1
+         }
+
+         if {$index < 0} {
+            set index $width
+
+            while {[incr index] < $length} {
+               if {[isWhitespace [string index $line $index]]} {
+                  break
+               }
+            }
+         }
+      } else {
+         set index $length
+      }
+
+      lappend lines [string trimright [string range $line 0 $index-1]]
+      set line [string range $line $index end]
+   }
+
+   return $lines
+}
+
+proc formatLines {lines {width 79}} {
+   set result [list]
+   set paragraph ""
+
+   set finishParagraph {
+      if {[string length $paragraph] > 0} {
+         lvarcat result [wrapLine $paragraph $width]
+         set paragraph ""
+      }
+   }
+
+   foreach line $lines {
+      if {[set length [string length [string trimright $line]]] > 0} {
+         if {![string equal [string index $line 0] " "]} {
+            if {[string length $paragraph] > 0} {
+               append paragraph " "
+            }
+
+            append paragraph $line
+            continue
+         }
+      }
+
+      eval $finishParagraph
+      lappend result $line
+   }
+
+   eval $finishParagraph
+   return [join $result \n]
+}
+
+proc formatColumns {rows} {
+   set spacer [string repeat " " 2]
+   set lines [list]
+
+   foreach row $rows {
+      set index 0
+
+      foreach cell $row {
+         if {![info exists widths($index)]} {
+            set widths($index) 0
+         }
+
+         set widths($index) [max $widths($index) [string length $cell]]
+         incr index
+      }
+   }
+
+   foreach row $rows {
+      set line ""
+      set index 0
+
+      foreach cell $row {
+         if {[set width $widths($index)] > 0} {
+            append line $cell
+            append line [string repeat " " [expr {$width - [string length $cell]}]]
+            append line $spacer
+         }
+
+         incr index
+      }
+
+      if {[string length [set line [string trimright $line]]] > 0} {
+         if {[string length $lines] > 0} {
+            append lines "\n"
+         }
+
+         append lines $line
+      }
+   }
+
+   return $lines
+}
+
+proc formatChoicesPhrase {choices} {
+   set separator " "
+
+   if {[set count [llength $choices]] > 2} {
+      set separator ",$separator"
+   }
+
+   if {$count > 1} {
+      lappend choices "or [lvarpop choices end]"
+   }
+
+   return [join $choices $separator]
+}
+
+proc nextElement {listVariable {elementVariable ""}} {
+   upvar 1 $listVariable list
+
+   if {[llength $list] == 0} {
+      return 0
+   }
+
+   if {[string length $elementVariable] > 0} {
+      uplevel 1 [list set $elementVariable [lindex $list 0]]
+      set list [lreplace $list 0 0]
+   }
+
+   return 1
+}
+
+proc processCommandOptions {valuesArray argumentsVariable definitions {optionsVariable ""}} {
+   upvar 1 $valuesArray values
+   upvar 1 $argumentsVariable arguments
+
+   set prefix -
+   set options [dict create]
+   set optionIndex 0
+
+   foreach definition $definitions {
+      set description "option\[$optionIndex\]"
+
+      if {![nextElement definition name]} {
+         return -code error "name not specified for $description"
+      }
+
+      if {[dict exists $options $name]} {
+         return -code error "duplicate name for $description: $name"
+      }
+
+      if {![nextElement definition type]} {
+         return -code error "type not specified for $description"
+      }
+
+      if {[lcontain {counter flag toggle} $type]} {
+         set values($name) 0
+      } else {
+         if {[set index [string first . $type]] < 0} {
+            set operand $type
+         } else {
+            set operand [string range $type $index+1 end]
+            set type [string range $type 0 $index-1]
+         }
+
+         if {![string equal $type untyped]} {
+            if {[catch [list string is $type ""]] != 0} {
+               return -code error "invalid type for $description: $type"
+            }
+         }
+
+         dict set options $name operand $operand
+      }
+
+      dict set options $name type $type
+
+      if {[nextElement definition usage]} {
+         dict set options $name usage $usage
+      }
+
+      incr optionIndex
+   }
+
+   if {[string length $optionsVariable] > 0} {
+      uplevel 1 [list set $optionsVariable $options]
+   }
+
+   while {[llength $arguments] > 0} {
+      if {[string length [set argument [lindex $arguments 0]]] == 0} {
+         break
+      }
+
+      if {![string equal [string index $argument 0] $prefix]} {
+         break
+      }
+
+      set arguments [lreplace $arguments 0 0]
+
+      if {[string equal $argument $prefix]} {
+         break
+      }
+
+      if {[string equal [set name [string range $argument 1 end]] $prefix]} {
+         break
+      }
+
+      if {[set count [dict size [set subset [dict filter $options key $name*]]]] == 0} {
+         logError "unknown option: $prefix$name"
+         return 0
+      }
+
+      if {$count > 1} {
+         logError "ambiguous option: $prefix$name ([join [lsort [dict keys $subset]] ", "])"
+         return 0
+      }
+
+      set name [lindex [dict keys $subset] 0]
+      set option [dict get $subset $name]
+
+      switch -exact [set type [dict get $option type]] {
+         counter {
+            set value [expr {$values($name) + 1}]
+         }
+
+         flag {
+            set value 1
+         }
+
+         toggle {
+            set value [expr {!$values($name)}]
+         }
+
+         default {
+            if {[llength $arguments] == 0} {
+               logError "missing operand: $prefix$name"
+               return 0
+            }
+
+            set value [lindex $arguments 0]
+            set arguments [lreplace $arguments 0 0]
+
+            if {![string equal $type untyped]} {
+               if {[catch [list string is $type -strict $value] result] != 0} {
+                  return -code error "unimplemented option type: $type"
+               }
+
+               if {!$result} {
+                  logError "operand not $type: $prefix$name $value"
+                  return 0
+               }
+            }
+         }
+      }
+
+      set values($name) $value
+   }
+
+   return 1
+}
+
+proc formatCommandOptionsUsage {options} {
+   set rows [list]
+
+   foreach name [lsort [dict keys $options]] {
+      set option [dict get $options $name]
+      set row [list]
+      lappend row "-$name"
+
+      foreach property {operand usage} {
+         if {[dict exists $option $property]} {
+            lappend row [dict get $option $property]
+         } else {
+            lappend row ""
+         }
+      }
+
+      lappend rows $row
+   }
+
+   return [formatColumns $rows]
+}
+
+proc showCommandUsage {name optionsDescriptor argumentsUsage getArgumentsUsageSummary} {
+   set optionsUsage [formatCommandOptionsUsage $optionsDescriptor]
+   set usage "Syntax: $name"
+
+   if {[string length $optionsUsage] > 0} {
+      append usage " \[-option ...\]"
+   }
+
+   if {[string length $argumentsUsage] > 0} {
+      append usage " $argumentsUsage"
+   }
+
+   if {[string length $getArgumentsUsageSummary] > 0} {
+      if {[string length [set lines [formatLines [$getArgumentsUsageSummary]]]] > 0} {
+         append usage \n
+         append usage $lines
+      }
+   }
+
+   if {[string length $optionsUsage] > 0} {
+      append usage \n
+      append usage "The following options may be specified:\n$optionsUsage"
+   }
+
+   puts stdout $usage
+}
+
+proc noMorePositionalArguments {arguments} {
+   if {[nextElement arguments]} {
+      syntaxError "excess positional arguments: [join $arguments " "]"
+   }
+}
+
+proc processProgramArguments {
+   optionValuesArray optionDefinitions
+   {argumentValuesList ""}
+   {argumentsUsage ""}
+   {getArgumentsUsageSummary ""}
+} {
+   upvar 1 $optionValuesArray optionValues
+   set argumentValues $::argv
+
+   lappend optionDefinitions {help flag "show this usage summary and then exit"}
+   lappend optionDefinitions {quiet counter "decrease verbosity"}
+   lappend optionDefinitions {verbose counter "increase verbosity"}
+
+   if {![processCommandOptions optionValues argumentValues $optionDefinitions optionsDescriptor]} {
+      syntaxError
+   }
+
+   global logLevel
+   incr logLevel $optionValues(verbose)
+   incr logLevel -$optionValues(quiet)
+
+   if {$optionValues(help)} {
+      showCommandUsage [getProgramName] $optionsDescriptor $argumentsUsage $getArgumentsUsageSummary
+      exit 0
+   }
+
+   if {[string length $argumentValuesList] > 0} {
+      uplevel 1 [list set $argumentValuesList $argumentValues]
+   } else {
+      noMorePositionalArguments $argumentValues
+   }
+}
+
diff --git a/brltty-setcaps b/brltty-setcaps
new file mode 100755
index 0000000..ca2f9a4
--- /dev/null
+++ b/brltty-setcaps
@@ -0,0 +1,126 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Simplify the job of assigning capabilities to the BRLTTY executable.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+executeCommand() {
+   "${useSudo}" && set -- sudo -- "${@}"
+
+   if "${testMode}"
+   then
+      echo "${*}"
+   else
+      "${@}"
+   fi
+}
+
+setOwner() {
+   local type="${1}"
+   local root="${2}"
+   local command="${3}"
+
+   if "${root}"
+   then
+      local owner=0
+   else
+      local owner="$(id -"${type}")"
+   fi
+
+   executeCommand "${command}" "${owner}" -- "${executablePath}"
+}
+
+setMode() {
+   local type="${1}"
+   local set="${2}"
+
+   if "${set}"
+   then
+      local operator="+"
+   else
+      local operator="-"
+   fi
+
+   executeCommand chmod "${type}${operator}s" "${executablePath}"
+}
+
+capabilitiesList=()
+addCapability() {
+   capabilitiesList[${#capabilitiesList[*]}]="${1}"
+}
+
+addProgramOption c flag noCreation "don't allow creating missing state directories"
+addProgramOption d flag noDevices "don't allow creating needed but missing device files"
+addProgramOption g flag noGroups "don't allow switching to the writable group or joining the required groups"
+addProgramOption i flag noInput "don't allow injecting input characters"
+addProgramOption m flag noModules "don't allow installing kernel modules"
+addProgramOption o flag noOwnership "don't allow claiming ownership of the state directories"
+addProgramOption p flag noPermissions "don't allow adding group permissions to the state directories"
+addProgramOption s flag noSpeaker "don't allow using the built-in PC speaker"
+addProgramOption C flag noCapabilities "don't set the capabilities"
+addProgramOption G flag rootGroup "set group root execution"
+addProgramOption S flag useSudo "use sudo to execute the commands as root"
+addProgramOption T flag testMode "test mode - show the commands that would be executed"
+addProgramOption U flag rootUser "set user root execution"
+addProgramParameter executable executablePath "the path to the executable"
+parseProgramArguments "${@}"
+
+verifyExecutableFile "${executablePath}"
+
+"${testMode}" || {
+   if "${useSudo}"
+   then
+      sudo -v
+   elif [ "$(id -u)" -ne 0 ]
+   then
+      semanticError "not executing as root"
+   fi
+}
+
+setOwner u "${rootUser}" chown
+setOwner g "${rootGroup}" chgrp
+
+setMode u "${rootUser}"
+setMode g "${rootGroup}"
+
+"${noCapabilities}" || {
+   "${noCreation}" || addCapability "cap_dac_override"
+   "${noDevices}" || addCapability "cap_mknod"
+   "${noGroups}" || addCapability "cap_setgid"
+   "${noInput}" || addCapability "cap_sys_admin"
+   "${noModules}" || addCapability "cap_sys_module"
+   "${noOwnership}" || addCapability "cap_chown"
+   "${noPermissions}" || addCapability "cap_fowner"
+   "${noSpeaker}" || addCapability "cap_sys_tty_config"
+
+   [ "${#capabilitiesList[*]}" -eq 0 ] || {
+      capabilitiesOperand="${capabilitiesList[*]}"
+      capabilitiesOperand="${capabilitiesOperand// /,}"
+      capabilitiesOperand+="+p"
+      executeCommand setcap "${capabilitiesOperand}" "${executablePath}"
+   }
+}
+
+exit 0
diff --git a/brltty-term b/brltty-term
new file mode 100755
index 0000000..38f20fb
--- /dev/null
+++ b/brltty-term
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/brltty-prologue.sh"
+
+usageSummary="\
+Run a shell or terminal manager via the brltty-pty terminal emulator
+and brltty using its Terminal Emulator screen driver.
+
+Syntax: ${programName} [shell] [-option ...]
+
+This script is especially useful on platforms that don't support a native screen driver.
+This newer method may (should) be used in preference to the older method of
+running brltty in conjunction with its provided patch to the screen program.
+
+All of the arguments to this script are interpreted as brltty options,
+with the following exceptions:
+* If the first argument is -h then this usage summary is displayed.
+* If the first argument doesn't begin with - (i.e. it isn't an option)
+  then it's interpreted as the shell to run, and the rest of the arguments
+  are interpreted as brltty options.
+
+The shell may be specified via either its name or its path. It's also okay
+to specify a terminal manager rather than a shell. For example:
+* ${programName} screen
+* ${programName} tmux
+
+If the shell isn't specified on the command line then, in decreasing
+precedence, one of the following is used:
+1: If set, the \$BRLTTY_TERM_SHELL environment variable.
+2: If set, the \$SHELL environment variable.
+3: The default shell (sh).
+
+Brltty is run as root so that it can access the braille device. If its
+set-user-id bit is set and/or if this script is already running as root
+then it's run directly. If neither, it's run via sudo. In this case, you
+may wish to consider configuring sudo so that it won't prompt for the
+user's password since the braille device wouldn't be operational yet.
+
+If the copy of this script in its installed location is used then the
+installed brltty is run. If, however, the copy in the top-level directory
+of the source tree is used then brltty in the build tree (which needn't
+be installed) is run.
+
+"
+
+shellCommand=""
+
+[ "${#}" -eq 0 ] || {
+   argument="${1}"
+
+   [ "${argument}" = "-h" ] && {
+      echo -n "${usageSummary}"
+      exit 0
+   }
+
+   [ "${#argument}" -eq 0 ] || {
+      [ "${argument:0:1}" = "-" ] || {
+         shellCommand="${argument}"
+         shift 1
+      }
+   }
+}
+
+[ -t 0 ] || semanticError "standard input isn't a terminal"
+[ -t 1 ] || semanticError "standard output isn't a terminal"
+
+findSiblingCommand brlttyPath brltty run-brltty || {
+   semanticError "brltty not found"
+}
+
+[ -n "${shellCommand}" ] || shellCommand="${BRLTTY_TERM_SHELL}"
+[ -n "${shellCommand}" ] || shellCommand="${SHELL}"
+[ -n "${shellCommand}" ] || shellCommand="sh"
+
+findHostCommand shellPath "${shellCommand}" || {
+   semanticError "shell not found: ${shellCommand}"
+}
+
+screenDriver="em"
+screenParameters=()
+
+addScreenParameter() {
+   local name="${1}"
+   local value="${2}"
+
+  screenParameters+=("-X${screenDriver}:${name}=${value}")
+}
+
+currentUser="$(id -u)"
+currentGroup="$(id -g)"
+
+addScreenParameter shell "${shellPath}"
+addScreenParameter user "${currentUser}"
+addScreenParameter group "${currentGroup}"
+addScreenParameter directory "${PWD}"
+addScreenParameter home "${HOME}"
+
+SUDO="sudo --bell --preserve-env --preserve-env=USER,LOGNAME --"
+[ -u "${brlttyPath}" ] && SUDO=""
+[ "${currentUser}" -eq 0 ] && SUDO=""
+
+exec ${SUDO} "${brlttyPath}" -n -z -x"${screenDriver}" "${screenParameters[@]}" "${@}"
+exit "${?}"
diff --git a/brltty-ttysize b/brltty-ttysize
new file mode 100755
index 0000000..38b9a32
--- /dev/null
+++ b/brltty-ttysize
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/brltty-prologue.sh"
+ttyDefaultPath="/dev/tty"
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Inspect or change the size of the current virtual terminal's screen.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+addProgramOption p string.path ttyPath "the path to the device" "${ttyDefaultPath}"
+addProgramOption c string.count ttyColumns "set the number of columns"
+addProgramOption l string.count ttyLines "set the number of lines"
+parseProgramArguments "${@}"
+
+[ -n "${ttyPath}" ] || ttyPath="${ttyDefaultPath}"
+showSize=true
+
+if [ -n "${ttyColumns}" ]
+then
+   verifyInteger "column count" "${ttyColumns}" 1 255
+   showSize=false
+else
+   ttyColumns="$(tput cols)"
+   [ -n "${ttyColumns}" ] || semanticError "column count not available: ${TERM}"
+fi
+
+if [ -n "${ttyLines}" ]
+then
+   verifyInteger "line count" "${ttyLines}" 1 255
+   showSize=false
+else
+   ttyLines="$(tput lines)"
+   [ -n "${ttyLines}" ] || semanticError "line count not available: ${TERM}"
+fi
+
+if "${showSize}"
+then
+   echo "${ttyColumns}x${ttyLines}"
+elif [ "${TERM#xterm}" != "${TERM}" ]
+then
+   echo >"${ttyPath}" -n -e "\\e[8;${ttyLines};${ttyColumns}t"
+else
+   stty -F "${ttyPath}" columns "${ttyColumns}" rows "${ttyLines}"
+fi
+
+exit 0
diff --git a/brltty.pc.in b/brltty.pc.in
new file mode 100644
index 0000000..b39d630
--- /dev/null
+++ b/brltty.pc.in
@@ -0,0 +1,163 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+Name: @PACKAGE_TARNAME@
+Version: @PACKAGE_VERSION@
+URL: @PACKAGE_URL@
+Description: Braille Device Daemon
+
+package_name=@PACKAGE_TARNAME@
+package_version=@PACKAGE_VERSION@
+package_url=@PACKAGE_URL@
+
+display_name=@PACKAGE_NAME@
+mailing_list=@PACKAGE_BUGREPORT@
+
+prefix=@prefix@
+sysconfdir=@sysconfdir@
+datarootdir=@datarootdir@
+
+exec_prefix=@exec_prefix@
+bindir=@bindir@
+sbindir=@sbindir@
+libdir=@libdir@
+libexecdir=@libexecdir@
+
+updatable_directory=@updatable_directory@
+writable_directory=@writable_directory@
+
+execute_root=@execute_root@
+program_directory=${execute_root}@program_directory@
+commands_directory=${execute_root}@commands_directory@
+drivers_directory=${execute_root}@drivers_directory@
+tables_directory=${execute_root}@tables_directory@
+include_directory=${execute_root}@include_directory@
+manpage_directory=${execute_root}@manpage_directory@
+
+configuration_file=@CONFIGURATION_FILE@
+preferences_file=@PREFERENCES_FILE@
+
+text_table=@text_table@
+attributes_table=@attributes_table@
+
+have_liblouis=@brltty_enabled_liblouis@
+liblouis_tables_directory=@louis_tables_directory@
+
+braille_parameters=@braille_parameters@
+braille_device=@braille_device@
+
+speech_parameters=@speech_parameters@
+have_speech=@brltty_enabled_speech_support@
+
+screen_parameters=@screen_parameters@
+screen_driver=@default_screen_driver@
+
+all_braille_driver_codes=@brltty_item_codes_braille@
+all_braille_driver_names=@brltty_item_names_braille@
+internal_braille_driver_codes=@brltty_internal_codes_braille@
+internal_braille_driver_names=@brltty_internal_names_braille@
+external_braille_driver_codes=@brltty_external_codes_braille@
+external_braille_driver_names=@brltty_external_names_braille@
+
+all_speech_driver_codes=@brltty_item_codes_speech@
+all_speech_driver_names=@brltty_item_names_speech@
+internal_speech_driver_codes=@brltty_internal_codes_speech@
+internal_speech_driver_names=@brltty_internal_names_speech@
+external_speech_driver_codes=@brltty_external_codes_speech@
+external_speech_driver_names=@brltty_external_names_speech@
+
+all_screen_driver_codes=@brltty_item_codes_screen@
+all_screen_driver_names=@brltty_item_names_screen@
+internal_screen_driver_codes=@brltty_internal_codes_screen@
+internal_screen_driver_names=@brltty_internal_names_screen@
+external_screen_driver_codes=@brltty_external_codes_screen@
+external_screen_driver_names=@brltty_external_names_screen@
+
+libbraille_root=@libbraille_root@
+espeak_root=@espeak_root@
+espeak_ng_root=@espeak_ng_root@
+flite_root=@flite_root@
+flite_language=@flite_language@
+flite_lexicon=@flite_lexicon@
+flite_voice=@flite_voice@
+mikropuhe_root=@mikropuhe_root@
+speechd_root=@speechd_root@
+swift_root=@swift_root@
+theta_root=@theta_root@
+
+privilege_parameters=@privilege_parameters@
+relocatable_install=@brltty_enabled_relocatable_install@
+static_executables=@brltty_enabled_standalone_programs@
+stripped_executables=@brltty_enabled_stripping@
+
+have_expat=@brltty_enabled_expat@
+have_gpm=@brltty_enabled_gpm@
+have_i18n=@brltty_enabled_i18n@
+have_iconv=@brltty_enabled_iconv@
+have_icu=@brltty_enabled_icu@
+have_polkit=@brltty_enabled_polkit@
+
+have_x=@brltty_enabled_x@
+toolkit_package=@gui_toolkit_package@
+curses_package=@curses_package@
+
+system_package=@system_package@
+pgmpath_package=@pgmpath_package@
+pgmprivs_package=@pgmprivs_package@
+service_package=@service_package@
+params_package=@params_package@
+dynld_package=@dynld_package@
+rgx_package=@rgx_package@
+charset_package=@charset_package@
+hostcmd_package=@hostcmd_package@
+mntpt_package=@mntpt_package@
+mntfs_package=@mntfs_package@
+kbd_package=@kbd_package@
+bell_package=@bell_package@
+leds_package=@leds_package@
+beep_package=@beep_package@
+pcm_package=@pcm_package@
+midi_package=@midi_package@
+fm_package=@fm_package@
+serial_package=@serial_package@
+usb_package=@usb_package@
+bluetooth_package=@bluetooth_package@
+ports_package=@ports_package@
+
+init_path=@init_path@
+stderr_path=@stderr_path@
+
+have_api=@brltty_enabled_api@
+have_java_bindings=@brltty_enabled_java_bindings@
+have_lisp_bindings=@brltty_enabled_lisp_bindings@
+have_ocaml_bindings=@brltty_enabled_ocaml_bindings@
+have_python_bindings=@brltty_enabled_python_bindings@
+have_tcl_bindings=@brltty_enabled_tcl_bindings@
+
+api_name=@api_name@
+api_version=@api_version@
+api_release=@api_release@
+
+api_major=@api_major@
+api_minor=@api_minor@
+api_revision=@api_revision@
+
+api_parameters=@api_parameters@
+api_socket_path=@BRLAPI_SOCKETPATH@
+api_authkey_file=@BRLAPI_ETCDIR@/@api_authkeyfile@
+
diff --git a/brltty.spec.in b/brltty.spec.in
new file mode 100644
index 0000000..2b4158f
--- /dev/null
+++ b/brltty.spec.in
@@ -0,0 +1,816 @@
+# @configure_input@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+Name: @PACKAGE_TARNAME@
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Vendor: The BRLTTY Developers
+Packager: Dave Mielke <dave@mielke.cc>
+URL: @PACKAGE_URL@
+Source: @PACKAGE_URL@/archive/%{name}-%{version}.tar.gz
+
+BuildRoot: %{_tmppath}/%{name}-%{version}-InstallRoot
+%define _pkglibdir %{_libdir}/%{name}
+%define _pkglibexecdir %{_libexecdir}/%{name}
+
+Requires: cldr-emoji-annotation
+Requires: dbus-libs
+Requires: expat
+Requires: libcap
+Requires: libicu
+Requires: liblouis
+Requires: pcre2-utf32
+Requires: polkit-libs
+
+BuildRequires: systemd-rpm-macros
+
+BuildRequires: /bin/sh
+BuildRequires: /usr/bin/env
+
+BuildRequires: bash
+BuildRequires: gawk
+BuildRequires: coreutils
+BuildRequires: binutils
+BuildRequires: tcl
+
+BuildRequires: autoconf >= 2.53
+BuildRequires: automake
+BuildRequires: make
+BuildRequires: pkgconf-pkg-config
+BuildRequires: gcc
+
+BuildRequires: alsa-lib-devel
+BuildRequires: bluez-libs-devel
+BuildRequires: dbus-devel
+BuildRequires: expat-devel
+BuildRequires: glibc-devel
+BuildRequires: glib2-devel
+BuildRequires: gpm-devel
+BuildRequires: libcap-devel
+BuildRequires: libicu-devel
+BuildRequires: liblouis-devel
+BuildRequires: ncurses-devel
+BuildRequires: pcre2-devel
+BuildRequires: polkit-devel
+BuildRequires: systemd-devel
+
+BuildRequires: gettext
+BuildRequires: groff
+BuildRequires: doxygen
+BuildRequires: linuxdoc-tools
+BuildRequires: python3-docutils
+BuildRequires: rst2txt
+
+AutoProv: no
+AutoReq: yes
+
+Provides: brlapi-server
+Provides: config(%{name}) = %{version}
+
+Summary: Braille display driver for Linux/Unix.
+%description
+BRLTTY is a background process (daemon) which provides access to
+the console screen (when in text mode) for a blind person using a
+refreshable braille display.  It drives the braille display, and
+provides complete screen review functionality.  Some speech capability
+has also been incorporated.
+
+Install this package if you use a refreshable braille display.
+
+
+%package -n brltty-braille-brlapi
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+AutoProv: no
+AutoReq: yes
+
+Summary: BrlAPI braille driver for BRLTTY.
+%description -n brltty-braille-brlapi
+This package provides the BrlAPI braille driver for BRLTTY.
+
+Install this package if you need to communicate with
+another instance of BRLTTY which is actually in control
+of the braille device.
+
+
+%package -n brltty-braille-xwindow
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: libX11
+
+BuildRequires: xorg-x11-proto-devel
+BuildRequires: libX11-devel
+BuildRequires: libXt-devel
+BuildRequires: neXtaw-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: XWindow braille driver for BRLTTY.
+%description -n brltty-braille-xwindow
+This package provides the XWindow braille driver for BRLTTY.
+This driver presents a virtual braille device within a graphical
+user interface. It is primarily intended for sighted developers
+who don't have access to a real braille device but still wish to
+assess the accessibility of their applications.
+
+Install this package if you would like to see how usable an
+application would be by a blind person using a braille device.
+
+
+%package -n brltty-speech-espeak
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: espeak
+
+BuildRequires: espeak-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: eSpeak speech driver for BRLTTY.
+%description -n brltty-speech-espeak
+This package provides the eSpeak speech driver for BRLTTY.
+
+Install this package if you would like to be able to use the
+eSpeak text-to-speech engine.
+
+
+%package -n brltty-speech-espeak-ng
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: espeak-ng
+
+BuildRequires: espeak-ng-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: eSpeak-NG speech driver for BRLTTY.
+%description -n brltty-speech-espeak-ng
+This package provides the eSpeak-NG speech driver for BRLTTY.
+
+Install this package if you would like to be able to use the
+eSpeak-NG text-to-speech engine.
+
+
+%package -n brltty-speech-festival
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: festival
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Festival speech driver for BRLTTY.
+%description -n brltty-speech-festival
+This package provides the Festival speech driver for BRLTTY.
+
+Install this package if you would like to be able to use the
+Festival text-to-speech engine.
+
+
+%package -n brltty-speech-flite
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: flite
+
+BuildRequires: flite-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Festival Lite speech driver for BRLTTY.
+%description -n brltty-speech-flite
+This package provides the Festival Lite speech driver for BRLTTY.
+
+Install this package if you would like to be able to use the
+Festival Lite text-to-speech engine.
+
+
+%package -n brltty-speech-speechd
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: speech-dispatcher
+
+BuildRequires: speech-dispatcher-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Speech Dispatcher speech driver for BRLTTY.
+%description -n brltty-speech-speechd
+This package provides the Speech Dispatcher speech driver for BRLTTY.
+
+Install this package if you would like to be able to use the
+Speech Dispatcher text-to-speech server.
+
+
+%package -n brltty-screen-atspi2
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+Requires: dbus-libs
+
+BuildRequires: at-spi2-core-devel
+BuildRequires: xorg-x11-proto-devel
+BuildRequires: dbus-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: AT-SPI2 screen driver for BRLTTY.
+%description -n brltty-screen-atspi2
+This package provides the AT-SPI2 screen driver for BRLTTY.
+
+Install this package if you would like to be able to
+access graphical applications via the D-Bus port of
+the Assistive Technology Service Provider Interface.
+
+
+%package -n brltty-screen-screen
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+
+AutoProv: no
+AutoReq: yes
+Requires: screen
+
+Summary: Screen screen driver for BRLTTY.
+%description -n brltty-screen-screen
+This package provides the Screen screen driver for BRLTTY.
+
+Install this package if you would like to be able to
+directly access session windows maintained by the
+screen program.
+
+
+%package -n brltty-utils
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: Development/Tools
+License: LGPL
+
+Requires: libicu
+Requires: expat
+Requires: cldr-emoji-annotation
+
+BuildRequires: libicu-devel
+BuildRequires: expat-devel
+BuildRequires: ncurses-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Commands for maintaining BRLTTY data files.
+%description -n brltty-utils
+This package provides a set of commands for maintaining BRLTTY's datafiles.
+
+
+%package -n brltty-devel
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: Development/Tools
+License: LGPL
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Headers for BRLTTY.
+%description -n brltty-devel
+This package provides many of the header files for BRLTTY's core, as well as
+header files which define the packets and keys for several braille devices.
+
+Install this package if you're developing or maintaining an application which
+needs to (or wishes to) use functionality provided by BRLTTY's core, which
+needs to interpret and/or create raw braille device packets, or which needs to
+interpret key codes as delivered by BrlAPI's raw mode.
+
+
+%package -n brltty-systemd
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+BuildArch: noarch
+
+AutoProv: no
+AutoReq: yes
+Requires: systemd
+
+Summary: Systemd units and wrappers for BRLTTY.
+%description -n brltty-systemd
+This package provides the units and wrapper for managing BRLTTY via Systemd.
+
+Install this package if you'd like to manage BRLTTY processes via Systemd.
+
+
+%package -n brltty-udev
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+BuildArch: noarch
+
+AutoProv: no
+AutoReq: yes
+Requires: systemd-udev
+
+Summary: BRLTTY Udev rules.
+%description -n brltty-udev
+This package provides the rules and the wrapper for managing BRLTTY via Udev.
+
+Install this package if you'd like to manage BRLTTY processes via Udev.
+It doesn't include the rules for braille devices that use generic USB to serial adapters.
+
+
+%package -n brltty-udev-generic
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+BuildArch: noarch
+
+AutoProv: no
+AutoReq: yes
+Requires: systemd-udev
+
+Summary: BRLTTY Udev rules for braille devices that use a generic USB to serial adapter.
+%description -n brltty-udev-generic
+This package provides additional rules for managing BRLTTY via Udev.
+
+Install this package in order to support braille devices that use a generic USB to serial adapter.
+
+
+%package -n brltty-dracut
+Version: @PACKAGE_VERSION@
+Release: 1
+Group: System Environment/Daemons
+License: LGPL
+BuildArch: noarch
+
+AutoProv: no
+AutoReq: yes
+Requires: dracut
+Requires: brltty-utils
+
+Summary: Dracut module for BRLTTY.
+%description -n brltty-dracut
+This package provides the module for adding BRLTTY to an initial ramdisk image.
+
+Install this package if you'd like to have braille accessibility BRLTTY processes via Udev.
+
+
+%package -n brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+AutoProv: yes
+AutoReq: yes
+
+Summary: Appliation Programming Interface for BRLTTY.
+%description -n brlapi
+This package provides the run-time support for the Application
+Programming Interface to BRLTTY.
+
+Install this package if you have an application
+which directly accesses a refreshable braille display.
+
+
+%package -n brlapi-utils
+Version: @api_release@
+Release: 1
+Group: Applications/System
+License: LGPL
+
+Requires: libX11
+
+BuildRequires: xorg-x11-proto-devel
+BuildRequires: libX11-devel
+BuildRequires: libXtst-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Appliation Programming Interface for BRLTTY.
+%description -n brlapi-utils
+This package provides a set of commands that use the BrlAPI interface.
+
+
+%package -n brlapi-devel
+Version: @api_release@
+Release: 1
+Group: Development/Libraries
+License: LGPL
+
+AutoProv: yes
+AutoReq: yes
+
+Summary: Headers, static archive, and documentation for BrlAPI.
+%description -n brlapi-devel
+This package provides the header files, static archive, shared object
+linker reference, and reference documentation for BrlAPI (the
+Application Programming Interface to BRLTTY).  It enables the
+implementation of applications which take direct advantage of a
+refreshable braille display in order to present information in ways
+which are more appropriate for blind users and/or to provide user
+interfaces which are more specifically atuned to their needs.
+
+Install this package if you're developing or maintaining an application
+which directly accesses a refreshable braille display.
+
+
+%package -n java-brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+BuildRequires: java
+BuildRequires: java-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Java bindings for BrlAPI.
+%description -n java-brlapi
+This package provides the Java bindings for BrlAPI,
+which is the Application Programming Interface to BRLTTY.
+
+Install this package if you have a Java application
+which directly accesses a refreshable braille display.
+
+
+%package -n lua-brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+BuildRequires: lua
+BuildRequires: lua-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Lua bindings for BrlAPI.
+%description -n lua-brlapi
+This package provides the Lua bindings for BrlAPI,
+which is the Application Programming Interface to BRLTTY.
+
+Install this package if you have an Lua application
+which directly accesses a refreshable braille display.
+
+
+%package -n ocaml-brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+BuildRequires: ocaml
+BuildRequires: ocaml-findlib
+
+AutoProv: no
+AutoReq: yes
+
+Summary: OCaml bindings for BrlAPI.
+%description -n ocaml-brlapi
+This package provides the OCaml bindings for BrlAPI,
+which is the Application Programming Interface to BRLTTY.
+
+Install this package if you have an OCaml application
+which directly accesses a refreshable braille display.
+
+
+%package -n python-brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+BuildRequires: python3
+BuildRequires: python3-devel
+BuildRequires: python3-Cython
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Python bindings for BrlAPI.
+%description -n python-brlapi
+This package provides the Python bindings for BrlAPI,
+which is the Application Programming Interface to BRLTTY.
+
+Install this package if you have a Python application
+which directly accesses a refreshable braille display.
+
+
+%package -n tcl-brlapi
+Version: @api_release@
+Release: 1
+Group: System Environment/Libraries
+License: LGPL
+
+BuildRequires: tcl
+BuildRequires: tcl-devel
+
+AutoProv: no
+AutoReq: yes
+
+Summary: Tcl bindings for BrlAPI.
+%description -n tcl-brlapi
+This package provides the Tcl bindings for BrlAPI,
+which is the Application Programming Interface to BRLTTY.
+
+Install this package if you have a Tcl application
+which directly accesses a refreshable braille display.
+
+
+%prep
+# %setup -n %{name}-%{version}
+%setup -n @PACKAGE_TARNAME@-@PACKAGE_VERSION@
+
+%build
+export PYTHON=python3
+%configure --disable-relocatable-install --with-install-root="${RPM_BUILD_ROOT}" --disable-gpm --without-mikropuhe --without-swift --without-theta --without-viavoice --without-libbraille --with-braille-driver=-tt,-vr
+make all
+
+%install
+make install
+make install-messages
+make install-appstream
+make install-systemd
+make install-udev UDEV_RULES_LOCATION=%{_udevrulesdir}
+make install-polkit
+make install-dracut
+make install-documents
+install -m 644 Documents/brltty.conf "${RPM_BUILD_ROOT}%{_sysconfdir}"
+%find_lang %{name}
+
+%clean
+rm -fr "${RPM_BUILD_ROOT}"
+
+
+%files -f %{name}.lang
+%defattr(-,root,root)
+%{_bindir}/brltty
+%{_pkglibdir}
+%{_pkglibexecdir}/brltty-pty
+%exclude %{_pkglibdir}/libbrlttybba.so
+%exclude %{_pkglibdir}/libbrlttybxw.so
+%exclude %{_pkglibdir}/libbrlttysen.so
+%exclude %{_pkglibdir}/libbrlttyses.so
+%exclude %{_pkglibdir}/libbrlttysfl.so
+%exclude %{_pkglibdir}/libbrlttysfv.so
+%exclude %{_pkglibdir}/libbrlttyssd.so
+%exclude %{_pkglibdir}/libbrlttyxa2.so
+%exclude %{_pkglibdir}/libbrlttyxsc.so
+%exclude %{_unitdir}
+%exclude %{_udevrulesdir}
+%{_sysconfdir}/brltty
+%exclude %{_sysconfdir}/brltty/Initramfs/dracut.conf
+%exclude %{_sysconfdir}/brltty/Initramfs/cmdline
+%{_datadir}/metainfo/org.a11y.brltty.metainfo.xml
+%{_libdir}/pkgconfig/brltty.*
+%doc %{_mandir}/man1/brltty.1.gz
+%doc %{_docdir}/*/LICENSE*
+%doc %{_docdir}/*/README*
+%doc %{_docdir}/*/*.html
+%doc %{_docdir}/*/BUGS
+%doc %{_docdir}/*/ChangeLog
+%doc %{_docdir}/*/CONTRIBUTORS
+%doc %{_docdir}/*/HISTORY
+%doc %{_docdir}/*/TODO
+%doc %{_docdir}/*/*.csv
+%doc %{_docdir}/*/brltty.conf
+%doc %{_docdir}/*/KeyTables
+%doc %{_docdir}/*/Manual-BRLTTY
+%config(noreplace) %verify(not size md5 mtime) %{_sysconfdir}/brltty.conf
+
+%files -n brltty-braille-brlapi
+%{_pkglibdir}/libbrlttybba.so
+
+%files -n brltty-braille-xwindow
+%{_pkglibdir}/libbrlttybxw.so
+
+%files -n brltty-speech-espeak
+%{_pkglibdir}/libbrlttyses.so
+
+%files -n brltty-speech-espeak-ng
+%{_pkglibdir}/libbrlttysen.so
+
+%files -n brltty-speech-festival
+%{_pkglibdir}/libbrlttysfv.so
+
+%files -n brltty-speech-flite
+%{_pkglibdir}/libbrlttysfl.so
+
+%files -n brltty-speech-speechd
+%{_pkglibdir}/libbrlttyssd.so
+
+%files -n brltty-screen-atspi2
+%{_pkglibdir}/libbrlttyxa2.so
+
+%files -n brltty-screen-screen
+%{_pkglibdir}/libbrlttyxsc.so
+
+%files -n brltty-utils
+%{_bindir}/brltty-*
+
+%files -n brltty-devel
+%{_includedir}/brltty
+
+%files -n brltty-systemd
+%{_unitdir}/*
+%{_sysusersdir}/*
+%{_tmpfilesdir}/*
+%{_pkglibexecdir}/systemd-*
+
+%files -n brltty-udev
+%{_udevrulesdir}/*
+%exclude %{_udevrulesdir}/*-generic*
+%{_pkglibexecdir}/udev-*
+
+%files -n brltty-udev-generic
+%{_udevrulesdir}/*-generic*
+
+%files -n brltty-dracut
+/usr/lib/dracut/modules.d/99brltty
+%config(noreplace) %verify(not size md5 mtime) %{_sysconfdir}/brltty/Initramfs/dracut.conf
+%config(noreplace) %verify(not size md5 mtime) %{_sysconfdir}/brltty/Initramfs/cmdline
+
+%files -n brlapi
+%defattr(-,root,root)
+%{_libdir}/libbrlapi.so.*
+%{_datadir}/polkit-1/*/org.a11y.brlapi.*
+%doc %{_docdir}/*/Manual-BrlAPI
+
+%files -n brlapi-utils
+%{_bindir}/xbrlapi
+%doc %{_mandir}/man1/xbrlapi.1.gz
+%{_datadir}/gdm/greeter/autostart/xbrlapi.desktop
+%{_sysconfdir}/X11/Xsession.d/90xbrlapi
+%{_bindir}/eutp
+%doc %{_mandir}/man1/eutp.1.gz
+%{_bindir}/vstp
+%doc %{_mandir}/man1/vstp.1.gz
+
+%files -n brlapi-devel
+%defattr(-,root,root)
+%{_libdir}/libbrlapi.a
+%{_libdir}/libbrlapi.so
+%{_includedir}/brlapi.h
+%{_includedir}/brlapi_*.h
+%doc %{_mandir}/man3/*
+%doc %{_docdir}/*/BrlAPIref
+
+%files -n java-brlapi
+@JAVA_JAR_DIR@/*
+@JAVA_JNI_DIR@/*
+
+%files -n lua-brlapi
+@LUA_LIBRARY_DIRECTORY@/
+
+%files -n ocaml-brlapi
+@OCAMLLIB@/brlapi
+
+%files -n python-brlapi
+@PYTHON_SITE_PKG@/[bB]rlapi[-.]*
+
+%files -n tcl-brlapi
+@TCL_DIR@/brlapi-@api_release@/libbrlapi_tcl.so
+@TCL_DIR@/brlapi-@api_release@/pkgIndex.tcl
+
+
+%changelog
+* Sat Jul 22 2023 Dave Mielke <Dave@Mielke.cc> 6.6
+General changes:
+   The -H (uppercase) option and the concept of hidden options have been removed.
+   For consistency (with -A, -B, -S, -X), the -z and -Z short options have been swapped:
+      -z is now --stay-privileged
+      -Z is now --privilege-parameters
+   Multi-line contracted braille is now supported.
+   The cursor style can now be set to no dots.
+   A rectangular append to the clipboard now ensures a leading newline.
+   README.CommandReference has been added.
+Speech changes:
+   Speech can now be interrupted when writing new text to the input FIFO.
+   Autospeak now provides correct character highlighting to the speech driver.
+   A word is now a whitespace-delimited (rather than an alphanumeric) sequence of characters.
+   Word completion for single-character words now works correctly.
+   The ROUTE_SPEECH command (go to the speech cursor) has been added.
+   The DESC_CURR_CHAR command now speaks phonetically.
+Preferences Menu changes:
+   The PREFRESET command has been added.
+   Space + p + Dots 7-8 is bound to the new PREFRESET command.
+   If the preferences file can't be found then try loading /etc/brltty/default.prefs.
+   Save on Exit now also saves the preferences on program termination.
+   The Speak Key Context preference has been added.
+   The Speak Modifier Key preference has been added.
+Baum braille driver changes:
+   More recent Orbit Reader models have routing keys.
+BrlAPI braille driver changes:
+   The speechChanges (no, yes) parameter has been added.
+CombiBraille braille driver changes:
+   Upgraded to use generic (rather than direct serial) I/O.
+   The routing keys are now supported.
+DotPad braille driver:
+   New - its two-letter driver code is dp.
+   A multi-line text display is supported within its graphic area.
+Freedom Scientific braille driver changes:
+   Minor Focus key binding changes:
+      LeftShift + RoutingKey -> SETLEFT
+      RightShift + RoutingKey -> SWITCHVT
+      LeftSelector + NavrowKey -> PRDIFCHAR
+      RightSelector + NavrowKey -> NXDIFCHAR
+HandyTech braille driver changes:
+   Bluetooth service discovery now needs to be performed (for newer models).
+External Speech speech driver changes:
+   The volume and pitch can now be set.
+   Now disconnects from the server when a write error or timeout occurs.
+   Now tries to reconnect to the server if disconnected.
+   Now correctly handles the receipt of tracking data.
+AtSpi2 screen driver changes:
+   A potential crash on receipt of a cursor routing request has been resolved.
+File Viewer screen driver:
+   New. Its two-letter driver code is fv.
+   A virtual screen presents the specified file (via its file parameter).
+   The height of the screen is the number of lines within the file.
+   The width of the screen is the length of the file's longest line.
+Linux screen driver changes:
+   A message is shown on the braille display if character injection (TIOCSTI) is disabled.
+Terminal Emulator screen driver:
+   New. Its two-letter driver code is em.
+   It monitors a shared memory segment maintained by a terminal emulator
+   for screen content and size, cursor position, etc.
+   It uses a message queue to know when the segment has been updated
+   and to send input typed on the braille device to the terminal emulator.
+   BRLTTY's provided terminal emulator is the new brltty-pty command (in libexec/brltty/).
+   The new brltty-term command runs a shell or terminal manager via brltty-pty,
+   and also runs BRLTTY with its Terminal Emulator screen driver to interact with it.
+BrlAPI changes:
+   The version is now 0.8.5 (it was 0.8.4).
+   Fuzzing support has been added to the server (see README.APIFuzzing).
+   Various fuzzer-detected issues have been resolved.
+Contraction tables:
+   The en-ueb-g1 (English, Unified, Grade 1) contraction table has been added.
+Key tables:
+   A possible stack overflow during table compilation has been resolved.
+   The run directive has been added.
+   The macro directive has been added.
+   The isolated directive has been added.
+   The ktbcheck tool has been added.
+Windows changes:
+   The batch scripts have been improved.
+   The tasks-brltty.bat script has been added.
+   The regquery-brlapi.bat script has been added.
+   The build now includes the Debug\ directory, which contains:
+      The build's config.h, config.mk, and config.log files.
+      Unstripped executables (.exe) and libraries (.dll).
+
diff --git a/cfg-android b/cfg-android
new file mode 100755
index 0000000..7e761bf
--- /dev/null
+++ b/cfg-android
@@ -0,0 +1,120 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+ndkRootVariable=ANDROID_NDK
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Configure a BRLTTY build for the Android platform.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+showProgramUsageNotes() {
+cat <<END_OF_PROGRAM_USAGE_NOTES
+All of the options are passed through, unmodified, as additional options to the configure script.
+The ${ndkRootVariable} environment variable must point to the root directory for the Android NDK (native development kit).
+END_OF_PROGRAM_USAGE_NOTES
+}
+
+defaultToplevelABI="armeabi-v7a"
+
+addProgramOption a string.ABI toplevelABI "the top-level's ABI" "${defaultToplevelABI}"
+addProgramOption n flag noToplevelConfigure "don't configure the top-level"
+addProgramOption o flag overwriteBuildProperties "overwrite existing ABI build properties"
+parseProgramArguments "${@}"
+set -e
+
+sourceRoot="${programDirectory}"
+buildRoot="${initialDirectory}"
+
+androidRoot="${buildRoot}/Android"
+ndkRoot="${androidRoot}/NDK"
+
+[ -d "${ndkRoot}" ] || {
+   [ -n "${!ndkRootVariable}" ] || {
+      semanticError "Android NDK directory not defined: ${ndkRoot}"
+   }
+
+   [  -f "${!ndkRootVariable}/ndk-build" ] || {
+      semanticError "not an Android NDK: ${!ndkRootVariable}"
+   }
+
+   logNote "creating symbolic link: ${ndkRoot} -> ${!ndkRootVariable}"
+   ln -s "${!ndkRootVariable}" "${ndkRoot}" || exit "${?}"
+}
+
+architecturesRoot="${androidRoot}/ABI"
+androidPlatform=android-21
+
+prepareArchitecture() {
+   local architecture="${1}"
+   local system="${2}"
+   local abi="${3}"
+   local cFlags="${4}"
+   local ldFlags="${5}"
+
+   local abiRoot="${architecturesRoot}/${abi}"
+   mkdir -p -- "${abiRoot}"
+   local toolchainRoot="${abiRoot}/ToolChain"
+
+   (  set -e
+      cd "${abiRoot}"
+      local propertiesFile="build.properties"
+
+      if [ -f "${propertiesFile}" ]
+      then
+         if "${overwriteBuildProperties}"
+         then
+            logWarning "overwriting ABI build properties: ${abi}"
+         else
+            logNote "ABI build properties already exist: ${abi}"
+            return 0
+         fi
+      else
+         logNote "creating ABI build properties: ${abi}"
+      fi
+
+      : >"${propertiesFile}"
+      set -- SOURCE "${sourceRoot}" NDK "${ndkRoot}" PLATFORM "${androidPlatform}" ARCH "${architecture}" ABI "${abi}" SYSTEM "${system}" TOOLCHAIN "${toolchainRoot}" CFLAGS "${cFlags}" LDFLAGS "${ldFlags}"
+
+      while [ "${#}" -gt 0 ]
+      do
+         local variable="${1}"
+         local value="${2}"
+         shift 2
+         echo >>"${propertiesFile}" "BRLTTY_ANDROID_${variable}=${value}"
+      done
+   )
+}
+
+logMessage task "preparing ABI build trees"
+prepareArchitecture arm arm-linux-androideabi armeabi-v7a "-mthumb" "-Wl,--fix-cortex-a8"
+prepareArchitecture arm64 aarch64-linux-android arm64-v8a
+prepareArchitecture x86 i686-linux-android x86
+prepareArchitecture x86_64 x86_64-linux-android x86_64
+
+"${noToplevelConfigure}" || {
+   [ -n "${toplevelABI}" ] || toplevelABI="${defaultToplevelABI}"
+   logMessage task "configuring top-level for ${toplevelABI}"
+   "${sourceRoot}/cfg-android-abi" -f "${toplevelABI}"
+}
+
+exit 0
diff --git a/cfg-android-abi b/cfg-android-abi
new file mode 100755
index 0000000..a4a2ad0
--- /dev/null
+++ b/cfg-android-abi
@@ -0,0 +1,203 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+showProgramUsagePurpose() {
+cat <<END_OF_PROGRAM_USAGE_PURPOSE
+Configure a BRLTTY build for a specific ABI (application binary interface) of the Android platform.
+END_OF_PROGRAM_USAGE_PURPOSE
+}
+
+showProgramUsageNotes() {
+cat <<END_OF_PROGRAM_USAGE_NOTES
+This script is used by cfg-android, and is for internal use only.
+END_OF_PROGRAM_USAGE_NOTES
+}
+
+# Braille drivers already disabled:
+# lb (by --without-libbraille)
+# xw (by --disable-x)
+readonly configureOptions=(
+   --disable-expat
+   --disable-gpm
+   --disable-iconv
+   --disable-icu
+   --disable-liblouis
+   --disable-polkit
+   --disable-x
+
+   --without-libbraille
+   --with-braille-driver=-ba,-bg,-tt,-vr,al,at,bm,bn,ce,eu,fs,hm,ht,hw,ic,ir,md,mm,mt,np,pg,pm,sk,vo
+
+   --without-espeak
+   --without-espeak-ng
+   --without-flite
+   --without-mikropuhe
+   --without-speechd
+   --without-swift
+   --without-theta
+   --with-speech-driver=an,-all
+
+   --with-screen-driver=an,-all
+
+   --without-pgmpath-package
+   --without-pgmprivs-package
+   --without-service-package
+   --without-params-package
+   --without-charset-package
+   --without-rgx-package
+   --without-hostcmd-package
+   --without-mntpt-package
+   --without-mntfs-package
+   --without-bell-package
+   --without-leds-package
+   --without-beep-package
+   --without-midi-package
+   --without-fm-package
+   --without-hid-package
+   --without-ports-package
+
+   --disable-emacs-bindings
+   --disable-lua-bindings
+)
+
+setProperties() {
+   declare -g -A properties
+   local ok=true
+   local property
+
+   for property in ARCH -CFLAGS -LDFLAGS NDK PLATFORM SOURCE SYSTEM TOOLCHAIN
+   do
+      local optional=false
+
+      [ "${property:0:1}" != "-" ] || {
+         optional=true
+         property="${property:1}"
+      }
+
+      local variable="BRLTTY_ANDROID_${property}"
+      local value="${!variable}"
+
+      "${optional}" || {
+         [ -n "${value}" ] || {
+            logError "property not set: ${property}"
+            ok=false
+         }
+      }
+
+      properties["${property}"]="${value}"
+   done
+
+   "${ok}" || exit 9
+}
+
+makeToolChain() {
+   [ -d "${properties[TOOLCHAIN]}" ] || {
+      logMessage task "installing tool chain for ABI: ${abiName}"
+
+      VERBOSE=no "${properties[NDK]}/build/tools/make-standalone-toolchain.sh" \
+         --platform="${properties[PLATFORM]}" \
+         --arch="${properties[ARCH]}" \
+         --install-dir="${properties[TOOLCHAIN]}" || exit "${?}"
+   }
+}
+
+linkMissingHeaders() {
+   local buildInclude="/usr/include"
+   local hostInclude="${properties[TOOLCHAIN]}/sysroot${buildInclude}"
+   local header
+
+   for header in bluetooth
+   do
+      local hostHeader="${hostInclude}/${header}"
+      [ ! -e "${hostHeader}" ] || continue
+
+      local buildHeader="${buildInclude}/${header}"
+      [ -e "${buildHeader}" ] || continue
+
+      logNote "adding symbolic link for header: ${abiName}: ${header}"
+      ln -s "${buildHeader}" "${hostHeader}" || exit "${?}"
+   done
+}
+
+findTool() {
+   local variable="${1}"
+   local name="${2}"
+
+   logDetail "finding tool: ${name}"
+   local path="${toolsDirectory}/${properties["SYSTEM"]}-${name}"
+
+   [ -f "${path}" ] || {
+      logError "tool not found: ${path}"
+      exit 8
+   }
+
+   logNote "tool found: ${path}"
+   setVariable "${variable}" "${path}"
+   export "${variable}"
+}
+
+addProgramOption f flag forceReconfigure "force a reconfigure"
+addProgramParameter ABI abiName "name of ABI to configure" "the source tree"
+parseProgramArguments "${@}"
+set -e
+
+abiRoot="${programDirectory}/Android/ABI/${abiName}"
+abiPropertiesFile="${abiRoot}/build.properties"
+[ -f "${abiPropertiesFile}" ] || syntaxError "unrecognized ABI: ${abiName}"
+. "${abiPropertiesFile}"
+setProperties
+
+makeToolChain
+linkMissingHeaders
+
+buildDirectory="$(pwd)"
+buildMakeFile="${buildDirectory}/Makefile"
+
+if [ -f "${buildMakeFile}" ]
+then
+   if "${forceReconfigure}"
+   then
+      logWarning "reconfiguring build: ${buildDirectory}"
+   else
+      logNote "build already configured: ${buildDirectory}"
+      exit 0
+   fi
+else
+   logMessage task "configuring build: ${buildDirectory}"
+fi
+
+toolsDirectory="${properties[TOOLCHAIN]}/bin"
+export PATH="${PATH}:${toolsDirectory}"
+findTool CC gcc
+findTool LD ld
+findTool STRIP strip
+findTool RANLIB ranlib
+
+sysrootDirectory="${properties[TOOLCHAIN]}/sysroot"
+export PKG_CONFIG_SYSROOT_DIR="${sysrootDirectory}"
+export PKG_CONFIG_LIBDIR="${sysrootDirectory}/usr/lib/pkgconfig:${sysrootDirectory}/usr/share/pkgconfig"
+export PKG_CONFIG_PATH=""
+
+export CFLAGS="${properties[CFLAGS]}"
+export LDFLAGS="${properties[LDFLAGS]}"
+
+"${properties[SOURCE]}/configure" --quiet --host="${properties[SYSTEM]}" "${configureOptions[@]}"
+exit 0
diff --git a/cfg-darwin b/cfg-darwin
new file mode 100755
index 0000000..bc3c2e7
--- /dev/null
+++ b/cfg-darwin
@@ -0,0 +1,71 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for the Darwin (Mac OS X) platform.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+END-OF-HELP
+
+configureOptions=(
+   --prefix=/usr/local 
+
+   --disable-gpm
+   --disable-iconv
+   --disable-icu
+   --disable-polkit
+   --disable-x
+
+   --without-libbraille
+
+   --without-espeak
+   --without-espeak-ng
+   --without-flite
+   --without-mikropuhe
+   --without-speechd
+   --without-swift
+   --without-theta
+
+   --without-pgmpath-package
+   --without-pgmprivs-package
+   --without-service-package
+   --without-params-package
+   --without-charset-package
+   --without-mntpt-package
+   --without-mntfs-package
+   --without-kbd-package
+   --without-bell-package
+   --without-leds-package
+   --without-beep-package
+   --without-pcm-package
+   --without-midi-package
+   --without-fm-package
+   --without-hid-package
+   --without-ports-package
+
+   --disable-java-bindings
+   --disable-python-bindings
+)
+
+"${programDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit "${?}"
diff --git a/cfg-dos b/cfg-dos
new file mode 100755
index 0000000..e4b0f0b
--- /dev/null
+++ b/cfg-dos
@@ -0,0 +1,108 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+toolsSystem="i586-pc-msdosdjgpp"
+toolsRootVariable=DOS_TOOLS_ROOT
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for the DOS platform.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+The ${toolsRootVariable} environment variable must point to the root directory for the DJGPP tols.
+END-OF-HELP
+
+toolsRoot="${!toolsRootVariable}"
+[ -n "${toolsRoot}" ] || semanticError "${toolsRootVariable} not set"
+[  -f "${toolsRoot}/bin/${toolsSystem}-gcc" ] || semanticError "not a DOS tools root: ${toolsRoot}"
+export toolsDirectory="${toolsRoot}/bin"
+export PATH="${toolsDirectory}:${PATH}"
+
+checkTool() {
+   eval export "${1}=\`set -- \"${toolsDirectory}/\"*\"-$2\" && echo \"\${1}\"\`"
+}
+
+checkTool CC gcc
+checkTool LD ld
+checkTool STRIP strip
+checkTool RANLIB ranlib
+
+export CFLAGS="-fgnu89-inline"
+export LDFLAGS=""
+
+# Braille drivers already disabled:
+# lb (by --without-libbraille)
+# xw (by --disable-x)
+
+configureOptions=(
+   --with-configuration-file=brltty.cfg
+   --with-writable-directory='${prefix}/'
+   --enable-relocatable-install
+
+   --disable-api
+   --disable-expat
+   --disable-gpm
+   --disable-icu
+   --disable-liblouis
+   --disable-polkit
+   --disable-x
+
+   --without-libbraille
+   --with-braille-driver=-vr,-cn,all
+
+   --without-espeak
+   --without-espeak-ng
+   --without-flite
+   --without-mikropuhe
+   --without-speechd
+   --without-swift
+   --without-theta
+   --with-speech-driver=-xs,all
+
+   --with-screen-driver=pb,-all
+
+   --without-bell-package
+   --without-leds-package
+
+   --without-pcm-package
+   --without-midi-package
+   --without-fm-package
+
+   --without-pgmpath-package
+   --without-pgmprivs-package
+   --without-service-package
+   --without-params-package
+   --without-rgx-package
+   --without-hostcmd-package
+   --without-mntpt-package
+   --without-mntfs-package
+   --without-kbd-package
+
+   --without-usb-package
+   --without-bluetooth-package
+   --without-hid-package
+
+   --host="${toolsSystem}"
+)
+
+"${programDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit 0
diff --git a/cfg-grub b/cfg-grub
new file mode 100755
index 0000000..7f178f3
--- /dev/null
+++ b/cfg-grub
@@ -0,0 +1,59 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for running within the Grub boot loader.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+END-OF-HELP
+
+# Braille drivers already disabled:
+# ba (by --disable-api)
+# il (not on Linux)
+# lb (by --without-libbraille)
+# xw (by --disable-x)
+
+configureOptions=(
+   --disable-api
+   --disable-icu
+   --disable-gpm
+   --disable-polkit
+   --disable-x
+
+   --without-libbraille
+   --with-braille-driver=-lt,-tt,-vd,-vr
+
+   --disable-speech-support
+
+   --without-bell-package
+   --without-leds-package
+
+   --without-pcm-package
+   --without-midi-package
+   --without-fm-package
+
+   --host=i386-elf
+)
+
+"${programDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit "${?}"
diff --git a/cfg-iris b/cfg-iris
new file mode 100755
index 0000000..aa78945
--- /dev/null
+++ b/cfg-iris
@@ -0,0 +1,58 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for running on an Iris braille display.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+END-OF-HELP
+
+configureOptions=(
+   --enable-standalone-programs
+   --without-dynld-package
+
+   --disable-api
+   --disable-gpm
+   --disable-icu
+   --disable-polkit
+   --disable-x
+   --without-curses
+
+   --without-libbraille
+   --with-braille-driver=ir,-all
+   --disable-speech-support
+   --with-screen-driver=lx,-all
+
+   --without-usb-package
+   --without-bluetooth-package
+
+   --without-pcm-package
+   --without-midi-package
+
+   --without-service-package
+   --without-mntpt-package
+)
+
+export CC="gcc -m32"
+echo "${programDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit "${?}"
diff --git a/cfg-openbsd b/cfg-openbsd
new file mode 100755
index 0000000..19251c5
--- /dev/null
+++ b/cfg-openbsd
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for the OpenBSD platform.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+END-OF-HELP
+
+configureOptions=(
+   --disable-icu
+   --disable-liblouis
+   --disable-polkit
+
+   --without-libbraille
+   --without-espeak
+   --without-espeak-ng
+   --without-flite
+   --without-mikropuhe
+   --without-speechd
+   --without-swift
+   --without-theta
+
+   --disable-emacs-bindings
+   --disable-java-bindings
+   --disable-lua-bindings
+   --disable-ocaml-bindings
+   --disable-python-bindings
+
+   --without-pgmpath-package
+   --without-pgmprivs-package
+   --without-service-package
+   --without-params-package
+   --without-charset-package
+   --without-rgx-package
+   --without-mntpt-package
+   --without-mntfs-package
+   --without-kbd-package
+   --without-bell-package
+   --without-leds-package
+   --without-pcm-package
+   --without-midi-package
+   --without-bluetooth-package
+   --without-hid-package
+   --without-ports-package
+)
+
+"${programDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit "${?}"
diff --git a/cfg-windows b/cfg-windows
new file mode 100755
index 0000000..d30e264
--- /dev/null
+++ b/cfg-windows
@@ -0,0 +1,63 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.bash"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Configure a BRLTTY build for the Windows platform.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through, unmodified, as additional options to the configure script.
+END-OF-HELP
+
+configureOptions=(
+   --enable-relocatable-install
+
+   --disable-expat
+   --disable-gpm
+   --disable-i18n
+   --disable-icu
+   --disable-liblouis
+   --disable-polkit
+   --disable-x
+
+   --without-libbraille
+   --disable-speech-support
+
+   --without-pgmprivs-package
+   --without-params-package
+   --without-rgx-package
+   --without-mntpt-package
+   --without-mntfs-package
+   --without-kbd-package
+   --without-bell-package
+   --without-leds-package
+   --without-hid-package
+
+   --disable-emacs-bindings
+   --disable-java-bindings
+   --disable-lua-bindings
+   --disable-ocaml-bindings
+   --disable-tcl-bindings
+)
+
+toRelativePath "${programDirectory}" configureDirectory
+"${configureDirectory}/configure" --quiet "${configureOptions[@]}" "${@}"
+exit "${?}"
diff --git a/chkhdrs b/chkhdrs
new file mode 100755
index 0000000..adbfd44
--- /dev/null
+++ b/chkhdrs
@@ -0,0 +1,32 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+addProgramParameter directory headersDirectory "the directory containing the headers"
+parseProgramArguments "${@}"
+
+set -e
+cd "${headersDirectory}"
+
+diff \
+   <(ls -1 *.h | sort -u) \
+   <(sed -n -e 's/^ *# *include *"\(.*\)" *$/\1/p' *.h | sort -u) \
+   | sed -n -e 's/^> *//p' | sort -u
+
+exit 0
diff --git a/common.mk b/common.mk
new file mode 100644
index 0000000..97ded78
--- /dev/null
+++ b/common.mk
@@ -0,0 +1,148 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+default: all
+
+FORCE:
+
+include $(BLD_TOP)config.mk
+include $(BLD_TOP)forbuild.mk
+include $(SRC_TOP)absdeps.mk
+
+%.$B: $(SRC_DIR)/%.c
+	$(CC_FOR_BUILD) -DFOR_BUILD $(CFLAGS_FOR_BUILD) -o $@ -c $<
+
+API_NAME = brlapi
+API_ARC = $(ARC_PFX)$(API_NAME).$(ARC_EXT)
+API_LIB = $(LIB_PFX)$(API_NAME).$(LIB_EXT)
+API_LIB_VERSIONED = $(API_LIB).$(API_VERSION)
+
+API_DLL = $(LIB_PFX)$(API_NAME)-$(API_VERSION).$(LIB_EXT)
+API_IMPLIB = $(ARC_PFX)$(API_NAME).$(LIB_EXT).$(ARC_EXT)
+API_IMPLIB_VERSIONED = $(ARC_PFX)$(API_NAME)-$(API_RELEASE).$(LIB_EXT).$(ARC_EXT)
+API_DEF = $(API_NAME).def
+
+COMMANDS_OPTIONS = -f $(SRC_TOP)$(PGM_DIR)/brl_cmds.awk
+COMMANDS_SCRIPTS = $(COMMANDS_OPTIONS:-f=)
+COMMANDS_SOURCES = $(SRC_TOP)$(HDR_DIR)/brl_cmds.h $(SRC_TOP)$(HDR_DIR)/brl_custom.h
+COMMANDS_DEPENDENCIES = $(COMMANDS_SCRIPTS) $(COMMANDS_SOURCES)
+COMMANDS_ARGUMENTS = $(COMMANDS_OPTIONS) $(COMMANDS_SOURCES)
+
+brlapi:
+	cd $(BLD_TOP)$(PGM_DIR) && $(MAKE) api
+
+$(BLD_TOP)$(BRL_DIR)/EuroBraille/braille.$O: $(BLD_TOP)$(BRL_DIR)/EuroBraille/eu_braille.$O
+$(BLD_TOP)$(BRL_DIR)/EuroBraille/braille.$O: $(BLD_TOP)$(BRL_DIR)/EuroBraille/eu_clio.$O
+$(BLD_TOP)$(BRL_DIR)/EuroBraille/braille.$O: $(BLD_TOP)$(BRL_DIR)/EuroBraille/eu_esysiris.$O
+	cd $(@D) && $(MAKE) $(@F)
+
+INSTALL_PROGRAM_DIRECTORY = $(INSTALL_ROOT)$(PROGRAM_DIRECTORY)
+install-program-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_PROGRAM_DIRECTORY)
+
+INSTALL_COMMANDS_DIRECTORY = $(INSTALL_ROOT)$(COMMANDS_DIRECTORY)
+install-commands-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_COMMANDS_DIRECTORY)
+
+INSTALL_DRIVERS_DIRECTORY = $(INSTALL_ROOT)$(DRIVERS_DIRECTORY)
+install-drivers-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_DRIVERS_DIRECTORY)
+
+INSTALL_TABLES_DIRECTORY = $(INSTALL_ROOT)$(TABLES_DIRECTORY)
+install-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_TABLES_DIRECTORY)
+
+INSTALL_TEXT_TABLES_DIRECTORY = $(INSTALL_TABLES_DIRECTORY)/$(TEXT_TABLES_SUBDIRECTORY)
+install-text-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_TEXT_TABLES_DIRECTORY)
+
+INSTALL_CONTRACTION_TABLES_DIRECTORY = $(INSTALL_TABLES_DIRECTORY)/$(CONTRACTION_TABLES_SUBDIRECTORY)
+install-contraction-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_CONTRACTION_TABLES_DIRECTORY)
+
+INSTALL_ATTRIBUTES_TABLES_DIRECTORY = $(INSTALL_TABLES_DIRECTORY)/$(ATTRIBUTES_TABLES_SUBDIRECTORY)
+install-attributes-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_ATTRIBUTES_TABLES_DIRECTORY)
+
+INSTALL_KEYBOARD_TABLES_DIRECTORY = $(INSTALL_TABLES_DIRECTORY)/$(KEYBOARD_TABLES_SUBDIRECTORY)
+install-keyboard-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_KEYBOARD_TABLES_DIRECTORY)
+
+INSTALL_INPUT_TABLES_DIRECTORY = $(INSTALL_TABLES_DIRECTORY)/$(INPUT_TABLES_SUBDIRECTORY)
+install-input-tables-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_INPUT_TABLES_DIRECTORY)
+
+INSTALL_LOCALE_DIRECTORY = $(INSTALL_ROOT)$(localedir)
+install-locale-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_LOCALE_DIRECTORY)
+
+INSTALL_MAN_DIRECTORY = $(INSTALL_ROOT)$(MANPAGE_DIRECTORY)
+
+INSTALL_MAN1_DIRECTORY = $(INSTALL_MAN_DIRECTORY)/man1
+install-man1-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_MAN1_DIRECTORY)
+
+INSTALL_MAN3_DIRECTORY = $(INSTALL_MAN_DIRECTORY)/man3
+install-man3-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_MAN3_DIRECTORY)
+
+INSTALL_DOCUMENT_DIRECTORY = $(INSTALL_ROOT)$(docdir)
+install-document-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_DOCUMENT_DIRECTORY)
+
+INSTALL_INCLUDE_DIRECTORY = $(INSTALL_ROOT)$(INCLUDE_DIRECTORY)
+install-include-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_INCLUDE_DIRECTORY)
+
+INSTALL_PKGCONFIG_DIRECTORY = $(INSTALL_ROOT)$(libdir)/pkgconfig
+install-pkgconfig-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_PKGCONFIG_DIRECTORY)
+
+INSTALL_APILIB_DIRECTORY = $(INSTALL_ROOT)$(libdir)
+install-apilib-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_APILIB_DIRECTORY)
+
+INSTALL_APIHDR_DIRECTORY = $(INSTALL_ROOT)$(includedir)
+install-apihdr-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_APIHDR_DIRECTORY)
+
+INSTALL_APISOC_DIRECTORY = $(INSTALL_ROOT)$(API_SOCKET_PATH)
+install-apisoc-directory:
+	-$(INSTALL_DIRECTORY) -m 1777 $(INSTALL_APISOC_DIRECTORY)
+
+INSTALL_X11_AUTOSTART_DIRECTORY = $(INSTALL_ROOT)$(sysconfdir)/X11/Xsession.d
+install-x11-autostart-directory:
+	-$(INSTALL_DIRECTORY) $(INSTALL_X11_AUTOSTART_DIRECTORY)
+
+INSTALL_GDM_AUTOSTART_DIRECTORY = $(INSTALL_ROOT)$(datadir)/gdm/greeter/autostart
+install-gdm-autostart-directory:
+	$(INSTALL_DIRECTORY) $(INSTALL_GDM_AUTOSTART_DIRECTORY)
+
+clean::
+	-test -f core && rm -f core
+	-rm -f *.$O *.auto.h *.auto.c implib.a
+
+distclean::
+	-rm -f *~ *orig \#*\# *.rej ? a.out
+	-rm -f Makefile
+
+src:
+	$(SYMLINK) $(SRC_DIR) src
+
+.DELETE_ON_ERROR:
+
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..e4207b8
--- /dev/null
+++ b/config.h
@@ -0,0 +1,693 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CONFIG
+#define BRLTTY_INCLUDED_CONFIG
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Define this to be a string containing the copyright notice. */
+#define PACKAGE_COPYRIGHT "© 1995-2023 by The BRLTTY Developers"
+
+/* Define this to be a string containing the full name of the package. */
+#define PACKAGE_NAME "BRLTTY"
+
+/* Define this to be a string containing the version of the package. */
+#define PACKAGE_VERSION "6.6"
+
+/* Define this to be a string containing the full name and version of the package. */
+#define PACKAGE_STRING "BRLTTY 6.6"
+
+/* Define this to be a string containing the URL of the home page of the package. */
+#define PACKAGE_URL "https://brltty.app/"
+
+/* Define this to be a string containing the short name of the package. */
+#define PACKAGE_TARNAME "brltty"
+
+/* Define this to be a string containing the address where bug reports should be sent. */
+#define PACKAGE_BUGREPORT "BRLTTY@brltty.app"
+
+/* Define this to be a string containing the base module name. */
+#define MODULE_NAME "libbrltty"
+
+/* Define this to be a string containing the module extension. */
+#define MODULE_EXTENSION "so"
+
+/* Define this to be a string containing the library extension. */
+#define LIBRARY_EXTENSION "so"
+
+/* Define only one of the following program path packages. */
+#define USE_PKG_PGMPATH_NONE 1
+/* #undef USE_PKG_PGMPATH_LINUX */
+/* #undef USE_PKG_PGMPATH_SOLARIS */
+/* #undef USE_PKG_PGMPATH_WINDOWS */
+
+/* Define only one of the following system service packages. */
+#define USE_PKG_SERVICE_NONE 1
+/* #undef USE_PKG_SERVICE_WINDOWS */
+
+/* Define only one of the following boot parameters packages. */
+#define USE_PKG_PARAMS_NONE 1
+/* #undef USE_PKG_PARAMS_LINUX */
+
+/* Define only one of the following dynamic loading packages. */
+/* #undef USE_PKG_DYNLD_NONE */
+#define USE_PKG_DYNLD_DLFCN 1
+/* #undef USE_PKG_DYNLD_DYLD */
+/* #undef USE_PKG_DYNLD_GRUB */
+/* #undef USE_PKG_DYNLD_SHL */
+/* #undef USE_PKG_DYNLD_WINDOWS */
+
+/* Define only one of the following regular expression packages. */
+#define USE_PKG_RGX_NONE 1
+/* #undef USE_PKG_RGX_LIBPCRE32 */
+/* #undef USE_PKG_RGX_LIBPCRE2_32 */
+
+/* Define only one of the following character set packages. */
+#define USE_PKG_CHARSET_NONE 1
+/* #undef USE_PKG_CHARSET_GRUB */
+/* #undef USE_PKG_CHARSET_ICONV */
+/* #undef USE_PKG_CHARSET_MSDOS */
+/* #undef USE_PKG_CHARSET_WINDOWS */
+
+/* Define only one of the following host command packages. */
+#define USE_PKG_HOSTCMD_NONE 1
+/* #undef USE_PKG_HOSTCMD_UNIX */
+/* #undef USE_PKG_HOSTCMD_WINDOWS */
+
+/* Define only one of the following mount point packages. */
+#define USE_PKG_MNTPT_NONE 1
+/* #undef USE_PKG_MNTPT_MNTENT */
+/* #undef USE_PKG_MNTPT_MNTTAB */
+
+/* Define only one of the following mount file system packages. */
+#define USE_PKG_MNTFS_NONE 1
+/* #undef USE_PKG_MNTFS_LINUX */
+
+/* Define only one of the following keyboard packages. */
+/* #undef USE_PKG_KBD_NONE */
+#define USE_PKG_KBD_ANDROID 1
+/* #undef USE_PKG_KBD_LINUX */
+
+/* Define only one of the following console bell packages. */
+#define USE_PKG_BELL_NONE 1
+/* #undef USE_PKG_BELL_LINUX */
+
+/* Define only one of the following keyboard LEDs packages. */
+#define USE_PKG_LEDS_NONE 1
+/* #undef USE_PKG_LEDS_LINUX */
+
+/* Define only one of the following beeper packages. */
+#define USE_PKG_BEEP_NONE 1
+/* #undef USE_PKG_BEEP_LINUX */
+/* #undef USE_PKG_BEEP_MSDOS */
+/* #undef USE_PKG_BEEP_SOLARIS */
+/* #undef USE_PKG_BEEP_SPKR */
+/* #undef USE_PKG_BEEP_WINDOWS */
+/* #undef USE_PKG_BEEP_WSKBD */
+
+/* Define only one of the following PCM packages. */
+/* #undef USE_PKG_PCM_NONE */
+/* #undef USE_PKG_PCM_ALSA */
+#define USE_PKG_PCM_ANDROID 1
+/* #undef USE_PKG_PCM_AUDIO */
+/* #undef USE_PKG_PCM_HPUX */
+/* #undef USE_PKG_PCM_OSS */
+/* #undef USE_PKG_PCM_QSA */
+/* #undef USE_PKG_PCM_WINDOWS */
+
+/* Define only one of the following MIDI packages. */
+#define USE_PKG_MIDI_NONE 1
+/* #undef USE_PKG_MIDI_ALSA */
+/* #undef USE_PKG_MIDI_DARWIN */
+/* #undef USE_PKG_MIDI_OSS */
+/* #undef USE_PKG_MIDI_WINDOWS */
+
+/* Define only one of the following FM packages. */
+#define USE_PKG_FM_NONE 1
+/* #undef USE_PKG_FM_ADLIB */
+
+/* Define only one of the following serial I/O packages. */
+/* #undef USE_PKG_SERIAL_NONE */
+/* #undef USE_PKG_SERIAL_GRUB */
+/* #undef USE_PKG_SERIAL_MSDOS */
+#define USE_PKG_SERIAL_TERMIOS 1
+/* #undef USE_PKG_SERIAL_WINDOWS */
+
+/* Define only one of the following USB I/O packages. */
+/* #undef USE_PKG_USB_NONE */
+#define USE_PKG_USB_ANDROID 1
+/* #undef USE_PKG_USB_DARWIN */
+/* #undef USE_PKG_USB_FREEBSD */
+/* #undef USE_PKG_USB_GRUB */
+/* #undef USE_PKG_USB_KFREEBSD */
+/* #undef USE_PKG_USB_LIBUSB */
+/* #undef USE_PKG_USB_LIBUSB_1_0 */
+/* #undef USE_PKG_USB_LINUX */
+/* #undef USE_PKG_USB_NETBSD */
+/* #undef USE_PKG_USB_OPENBSD */
+/* #undef USE_PKG_USB_SOLARIS */
+
+/* Define only one of the following Bluetooth I/O packages. */
+/* #undef USE_PKG_BLUETOOTH_NONE */
+#define USE_PKG_BLUETOOTH_ANDROID 1
+/* #undef USE_PKG_BLUETOOTH_DARWIN */
+/* #undef USE_PKG_BLUETOOTH_LINUX */
+/* #undef USE_PKG_BLUETOOTH_WINDOWS */
+
+/* Define only one of the following HID I/O packages. */
+#define USE_PKG_HID_NONE 1
+/* #undef USE_PKG_HID_LINUX */
+
+/* Define only one of the following I/O ports packages. */
+#define USE_PKG_PORTS_NONE 1
+/* #undef USE_PKG_PORTS_GLIBC */
+/* #undef USE_PKG_PORTS_GRUB */
+/* #undef USE_PKG_PORTS_KFREEBSD */
+/* #undef USE_PKG_PORTS_MSDOS */
+/* #undef USE_PKG_PORTS_WINDOWS */
+
+/* Define this if the function addmntent exists. */
+/* #undef HAVE_ADDMNTENT */
+
+/* Define this if the function clock_gettime exists. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define this if the function clock_settime exists. */
+#define HAVE_CLOCK_SETTIME 1
+
+/* Define this if the function fchdir exists. */
+#define HAVE_FCHDIR 1
+
+/* Define this if the function fchmod exists. */
+#define HAVE_FCHMOD 1
+
+/* Define this if the function gai_strerror exists. */
+#define HAVE_GAI_STRERROR 1
+
+/* Define this if the function getaddrinfo exists. */
+#define HAVE_GETADDRINFO 1
+
+/* Define this if the function getnameinfo exists. */
+#define HAVE_GETNAMEINFO 1
+
+/* Define this if the function getopt_long exists. */
+#define HAVE_GETOPT_LONG 1
+
+/* Define this if the function getpeereid exists. */
+/* #undef HAVE_GETPEEREID */
+
+/* Define this if the function getpeerucred exists. */
+/* #undef HAVE_GETPEERUCRED */
+
+/* Define this if the function gettimeofday exists. */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define this if the function getzoneid exists. */
+/* #undef HAVE_GETZONEID */
+
+/* Define this if the function hstrerror exists. */
+#define HAVE_HSTRERROR 1
+
+/* Define this if the function mempcpy exists. */
+/* #undef HAVE_MEMPCPY */
+
+/* Define this if the function nanosleep exists. */
+#define HAVE_NANOSLEEP 1
+
+/* Define this if the function pause exists. */
+#define HAVE_PAUSE 1
+
+/* Define this if the function readlink exists. */
+#define HAVE_READLINK 1
+
+/* Define this if the function realpath exists. */
+#define HAVE_REALPATH 1
+
+/* Define this if the function settimeofday exists. */
+#define HAVE_SETTIMEOFDAY 1
+
+/* Define this if the function shl_load is available. */
+/* #undef HAVE_SHL_LOAD */
+
+/* Define this if the function shm_open exists. */
+/* #undef HAVE_SHM_OPEN */
+
+/* Define this if the function shmget exists. */
+/* #undef HAVE_SHMGET */
+
+/* Define this if the function stime exists. */
+/* #undef HAVE_STIME */
+
+/* Define this if the function tcdrain exists. */
+#define HAVE_TCDRAIN 1
+
+/* Define this if the function time exists. */
+#define HAVE_TIME 1
+
+/* Define this if the function vsyslog exists. */
+#define HAVE_VSYSLOG 1
+
+/* Define this if the function wmempcpy exists. */
+/* #undef HAVE_WMEMPCPY */
+
+/* Define this if the header file alloca.h exists. */
+#define HAVE_ALLOCA_H 1
+
+/* Define this if the header file execinfo.h exists. */
+/* #undef HAVE_EXECINFO_H */
+
+/* Define this if the header file getopt.h exists. */
+#define HAVE_GETOPT_H 1
+
+/* Define this if the header file glob.h exists. */
+#define HAVE_GLOB_H 1
+
+/* Define this if the function glob exists. */
+/* #undef HAVE_GLOB */
+
+/* Define this if the header file grp.h exists. */
+#define HAVE_GRP_H 1
+
+/* Define this if the header file iconv.h exists. */
+/* #undef HAVE_ICONV_H */
+
+/* Define this if the header file langinfo.h exists. */
+#define HAVE_LANGINFO_H 1
+
+/* Define this if the function nl_langinfo exists. */
+/* #undef HAVE_NL_LANGINFO */
+
+/* Define this if the header file pwd.h exists. */
+#define HAVE_PWD_H 1
+
+/* Define this if the header file regex.h exists. */
+#define HAVE_REGEX_H 1
+
+/* Define this if the header file sched.h exists. */
+#define HAVE_SCHED_H 1
+
+/* Define this if the header file sdkddkver.h exists. */
+/* #undef HAVE_SDKDDKVER_H */
+
+/* Define this if the header file signal.h exists. */
+#define HAVE_SIGNAL_H 1
+
+/* Define this if the function sigaction exists. */
+#define HAVE_SIGACTION 1
+
+/* Define this if the header file syslog.h exists. */
+#define HAVE_SYSLOG_H 1
+
+/* Define this if the header file wchar.h exists. */
+#define HAVE_WCHAR_H 1
+
+/* Define this if the header file sys/capability.h exists. */
+#define HAVE_SYS_CAPABILITY_H 1
+
+/* Define this if the header file sys/file.h exists. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define this if the header file sys/io.h exists. */
+/* #undef HAVE_SYS_IO_H */
+
+/* Define this if the header file sys/modem.h exists. */
+/* #undef HAVE_SYS_MODEM_H */
+
+/* Define this if the header file sys/prctl.h exists. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define this if the header file sys/signalfd.h exists. */
+#define HAVE_SYS_SIGNALFD_H 1
+
+/* Define this if the header file sys/socket.h exists. */
+#define HAVE_SYS_SOCKET_H 1
+
+#ifndef __MINGW32__
+/* Define this if the header file sys/poll.h exists. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define this if the function poll exists. */
+#define HAVE_POLL 1
+
+/* Define this if the header file sys/select.h exists. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define this if the function select exists. */
+#define HAVE_SELECT 1
+#endif /* __MINGW32__ */
+
+/* Define this if the header file sys/wait.h exists,
+ * but not for DOS since it wouldn't make sense. 
+ */
+#ifndef __MSDOS__
+#define HAVE_SYS_WAIT_H 1
+#endif /* __MSDOS__ */
+
+/* Define this if the header file dev/speaker/speaker.h exists. */
+/* #undef HAVE_DEV_SPEAKER_SPEAKER_H */
+
+/* Define this if the header file legacy/dev/usb/usb.h exists. */
+/* #undef HAVE_LEGACY_DEV_USB_USB_H */
+
+/* Define this if the header file linux/audit.h exists. */
+#define HAVE_LINUX_AUDIT_H 1
+
+/* Define this if the header file linux/filter.h exists. */
+#define HAVE_LINUX_FILTER_H 1
+
+/* Define this if the header file linux/input.h exists. */
+#define HAVE_LINUX_INPUT_H 1
+
+/* Define this if the header file linux/seccomp.h exists. */
+#define HAVE_LINUX_SECCOMP_H 1
+
+/* Define this if the header file linux/uinput.h exists. */
+#define HAVE_LINUX_UINPUT_H 1
+
+/* Define this if the header file linux/vt.h exists. */
+#define HAVE_LINUX_VT_H 1
+
+/* Define this if the header file machine/speaker.h exists. */
+/* #undef HAVE_MACHINE_SPEAKER_H */
+
+/* Define this if the header file X11/extensions/Xfixes.h exists. */
+/* #undef HAVE_X11_EXTENSIONS_XFIXES_H */
+
+/* Define this if the header file X11/extensions/XKB.h exists. */
+/* #undef HAVE_X11_EXTENSIONS_XKB_H */
+
+/* Define this if the header file X11/extensions/XTest.h exists. */
+/* #undef HAVE_X11_EXTENSIONS_XTEST_H */
+
+/* Define this if the header file X11/keysym.h exists. */
+/* #undef HAVE_X11_KEYSYM_H */
+
+/* Define this if the function XSetIOErrorExitHandler exists.  */
+/* #undef HAVE_XSETIOERROREXITHANDLER */
+
+/* Define this if the function atspi_get_a11y_bus exists in atspi2. */
+/* #undef HAVE_ATSPI_GET_A11Y_BUS */
+
+/* Define this if XML parsing support is to be included. */
+/* #undef HAVE_EXPAT */
+
+/* Define this if Unicode-based internationalization support is to be included. */
+/* #undef HAVE_ICU */
+
+/* Define this if the header file unicode/unorm2.h exists. */
+/* #undef HAVE_UNICODE_UNORM2_H */
+
+/* Define this if the Polkit authorization manager is to be used. */
+/* #undef HAVE_POLKIT */
+
+/* Define this if posix threads are supported. */
+#define HAVE_POSIX_THREADS 1
+
+/* Define this if thread-local variables are supported. */
+#define THREAD_LOCAL _Thread_local
+
+/* Define this if the function pthread_atfork exists. */
+#define HAVE_PTHREAD_ATFORK 1
+
+/* Define this if the function pthread_getname_np exists. */
+/* #undef HAVE_PTHREAD_GETNAME_NP */
+
+/* Define this if the function sd_session_get_vt is available in libsystemd. */
+/* #undef HAVE_SD_SESSION_GET_VT */
+
+/* Define this if the bluetooth library is available. */
+/* #undef HAVE_LIBBLUETOOTH */
+
+/* Define this if the cap(abilities) library is available. */
+/* #undef HAVE_LIBCAP */
+
+/* Define this if GPM is to be used. */
+/* #undef HAVE_LIBGPM */
+
+/* Define this if the package dbus is available. */
+/* #undef HAVE_PKG_DBUS */
+
+/* Define only one of the following curses packages. */
+/* #undef HAVE_PKG_CURSES */
+/* #undef HAVE_PKG_NCURSES */
+/* #undef HAVE_PKG_NCURSESW */
+/* #undef HAVE_PKG_PDCURSES */
+/* #undef HAVE_PKG_PDCURSESU */
+/* #undef HAVE_PKG_PDCURSESW */
+
+/* Define this if the package x11 is available. */
+/* #undef HAVE_PKG_X11 */
+
+/* Define this if X is not available. */
+/* #undef X_DISPLAY_MISSING */
+
+/* Define only one of the following Xaw packages. */
+/* #undef HAVE_PKG_XAW */
+/* #undef HAVE_PKG_XAW3D */
+/* #undef HAVE_PKG_NEXTAW */
+/* #undef HAVE_PKG_XAWPLUS */
+/* #undef HAVE_PKG_XM */
+
+/* Define this if the compiler doesn't fully support the const keyword. */
+/* #undef const */
+
+/* Define this if the compiler doesn't fully support the inline keyword. */
+/* #undef inline */
+
+/* Define this if the packed variable attribute is supported. */
+#define HAVE_VAR_ATTRIBUTE_PACKED 1
+
+/* Define this if the unused variable attribute is supported. */
+#define HAVE_VAR_ATTRIBUTE_UNUSED 1
+
+/* Define this if the format function attribute is supported. */
+#define HAVE_FUNC_ATTRIBUTE_FORMAT 1
+
+/* Define this if the format_arg function attribute is supported. */
+#define HAVE_FUNC_ATTRIBUTE_FORMAT_ARG 1
+
+/* Define this if the noreturn function attribute is supported. */
+#define HAVE_FUNC_ATTRIBUTE_NORETURN 1
+
+/* Define this if the unused function attribute is supported. */
+#define HAVE_FUNC_ATTRIBUTE_UNUSED 1
+
+/* Define this if the __alignof__ operator is supported. */
+#define HAVE_OPERATOR_ALIGNOF 1
+#ifndef HAVE_OPERATOR_ALIGNOF
+#define __alignof__(x) 8
+#endif /* HAVE_OPERATOR_ALIGNOF */
+
+/* Define this if the host is big endian. */
+/* #undef WORDS_BIGENDIAN */
+
+/* Define this to be a string containing the size of the wchar_t type. */
+#define SIZEOF_WCHAR_T_STR "4"
+
+/* Define this if the function localtime_r is declared. */
+#define HAVE_DECL_LOCALTIME_R 1
+
+/* Define this if the type PROCESS_INFORMATION_CLASS exists. */
+/* #undef HAVE_PROCESS_INFORMATION_CLASS */
+
+/* Define this if the value ProcessUserModeIOPL exists. */
+/* #undef HAVE_PROCESSUSERMODEIOPL */
+
+/* Define this if BRLTTY is to be run as init. */
+/* #undef INIT_PATH */
+
+/* Define this if standard error is to be redirected to a file. */
+/* #undef STDERR_PATH */
+
+/* Define this to be a string containing the path to the configuration directory. */
+#define CONFIGURATION_DIRECTORY "/etc"
+
+/* Define this to be a string containing the name of the default configuration file. */
+#define CONFIGURATION_FILE "brltty.conf"
+
+/* Define this to be a string containing the path to the locale directory. */
+#define LOCALE_DIRECTORY "/usr/share/locale"
+
+/* Define this to be a string containing the path to a writable directory. */
+#define WRITABLE_DIRECTORY "/run/brltty"
+
+/* Define this to be a string containing the path to a directory which contains files that can be updated. */
+#define UPDATABLE_DIRECTORY "/var/lib/brltty"
+
+/* Define this to be a string containing the name of the default preferences file. */
+#define PREFERENCES_FILE "brltty.prefs"
+
+/* Define this to be a string containing the path to the helper commands
+ * directory. */
+#define COMMANDS_DIRECTORY "/libexec/brltty"
+
+/* Define this to be a string containing the path to the drivers directory. */
+#define DRIVERS_DIRECTORY "/lib/brltty"
+
+/* Define this to be a string containing the path to the tables directory. */
+#define TABLES_DIRECTORY "/etc/brltty"
+
+/* Define this to be a string containing the path to the LibLouis tables directory. */
+/* #undef LOUIS_TABLES_DIRECTORY */
+
+/* Define this to be a string containing the default parameters for the privilege establishment stage. */
+#define PRIVILEGE_PARAMETERS ""
+
+/* Define this to be a string containing a list of the braille driver codes. */
+#define BRAILLE_DRIVER_CODES                                                 \
+  "al at bm bn ce eu fs hm ht hw ic ir md mm mt np pg pm sk vo bc bd bl cb " \
+  "cn dp ec fa hd lt mb mn tn ts vd vs"
+
+/* Define this to be a string containing the default parameters for the braille driver(s). */
+#define BRAILLE_PARAMETERS ""
+
+/* Define this to be a string containing the path to the directory containing the devices. */
+#define DEVICE_DIRECTORY "/dev"
+
+/* Define this to be a string containing the path to the default braille device. */
+#define BRAILLE_DEVICE "usb:,bluetooth:"
+
+/* Define this to be a string containing the path to the first serial device. */
+#define SERIAL_FIRST_DEVICE "ttyS0"
+
+/* Define this to be a string containing a list of the speech driver codes. */
+#define SPEECH_DRIVER_CODES "an"
+
+/* Define this to be a string containing the default parameters for the speech driver(s). */
+#define SPEECH_PARAMETERS ""
+
+/* Define this to be a string containing a list of the screen driver codes. */
+#define SCREEN_DRIVER_CODES "an"
+
+/* Define this to be a string containing the default screen driver code. */
+#define DEFAULT_SCREEN_DRIVER "an"
+
+/* Define this to be a string containing the default parameters for the screen driver(s). */
+#define SCREEN_PARAMETERS ""
+
+/* Define this to be a string containing the name of the default text table. */
+#define TEXT_TABLE "en-nabcc"
+
+/* Define this to be a string containing the name of the default contraction table. */
+#define CONTRACTION_TABLE "none"
+
+/* Define this to be a string containing the name of the default attributes table. */
+#define ATTRIBUTES_TABLE "left_right"
+
+/* Define this to include speech synthesizer and text-to-speech engine support. */
+#define ENABLE_SPEECH_SUPPORT 1
+
+/* Define this to be a string containing the path to the root of the FestivalLite package. */
+/* #undef FLITE_ROOT */
+
+/* Define this to be a string containing the path to the root of the Mikropuhe package. */
+/* #undef MIKROPUHE_ROOT */
+
+/* Define this to be a string containing the path to the root of the SpeechDispatcher package. */
+/* #undef SPEECHD_ROOT */
+
+/* Define this to be a string containing the path to the root of the Swift package. */
+/* #undef SWIFT_ROOT */
+
+/* Define this to be a string containing the path to the root of the Theta package. */
+/* #undef THETA_ROOT */
+
+/* Define this if the application programming interface is to be included. */
+#define ENABLE_API 1
+
+/* Define this if the application programming interface is to be fuzzed. */
+/* #undef ENABLE_API_FUZZING */
+
+/* Define this to be a string containing the default parameters for the application programming interface. */
+#define API_PARAMETERS ""
+
+/* Define this to be a string containing the path to BrlAPI's local sockets directory. */
+#define BRLAPI_SOCKETPATH "/var/lib/BrlAPI"
+
+/* Define this to be a string containing the path to BrlAPI's data files directory. */
+#define BRLAPI_ETCDIR "/etc"
+
+/* Define this to be a string containing the name of BrlAPI's authorization key file. */
+#define BRLAPI_AUTHKEYFILE "brlapi.key"
+
+/* Define this if internationalization support is to be included. */
+/* #undef ENABLE_I18N_SUPPORT */
+
+/* Define this if shared object support is to be included. */
+#define ENABLE_SHARED_OBJECTS 1
+
+/* Define this if HP-UX audio support is available. */
+/* #undef HAVE_HPUX_AUDIO */
+
+/* Define this to be a string containing the subdirectory for text tables. */
+#define TEXT_TABLES_SUBDIRECTORY "Text"
+
+/* Define this to be a string containing the subdirectory for contraction tables. */
+#define CONTRACTION_TABLES_SUBDIRECTORY "Contraction"
+
+/* Define this to be a string containing the subdirectory for attributes tables. */
+#define ATTRIBUTES_TABLES_SUBDIRECTORY "Attributes"
+
+/* Define this to be a string containing the subdirectory for keyboard tables. */
+#define KEYBOARD_TABLES_SUBDIRECTORY "Keyboard"
+
+/* Define this to be a string containing the subdirectory for input tables. */
+#define INPUT_TABLES_SUBDIRECTORY "Input"
+
+/* Define this to be a string containing the extension for text tables. */
+#define TEXT_TABLE_EXTENSION ".ttb"
+
+/* Define this to be a string containing the extension for text subtables. */
+#define TEXT_SUBTABLE_EXTENSION ".tti"
+
+/* Define this to be a string containing the extension for contraction tables. */
+#define CONTRACTION_TABLE_EXTENSION ".ctb"
+
+/* Define this to be a string containing the extension for contraction subtables. */
+#define CONTRACTION_SUBTABLE_EXTENSION ".cti"
+
+/* Define this to be a string containing the extension for attributes tables. */
+#define ATTRIBUTES_TABLE_EXTENSION ".atb"
+
+/* Define this to be a string containing the extension for attributes subtables. */
+#define ATTRIBUTES_SUBTABLE_EXTENSION ".ati"
+
+/* Define this to be a string containing the extension for key tables. */
+#define KEY_TABLE_EXTENSION ".ktb"
+
+/* Define this to be a string containing the extension for key subtables. */
+#define KEY_SUBTABLE_EXTENSION ".kti"
+
+/* Define this to be a string containing the extension for key help files. */
+#define KEY_HELP_EXTENSION ".txt"
+
+/* Define this to be a string containing the subdirectory for profiles. */
+#define PROFILES_SUBDIRECTORY "Profiles"
+
+/* Define this to be a string containing the extension for language profiles. */
+#define LANGUAGE_PROFILE_EXTENSION ".lpf"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CONFIG */
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..a399a9f
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,689 @@
+/*
+ * BRLTTY - A background process providing access to the console screen (when in
+ *          text mode) for a blind person using a refreshable braille display.
+ *
+ * Copyright (C) 1995-2023 by The BRLTTY Developers.
+ *
+ * BRLTTY comes with ABSOLUTELY NO WARRANTY.
+ *
+ * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+ *
+ * Web Page: http://brltty.app/
+ *
+ * This software is maintained by Dave Mielke <dave@mielke.cc>.
+ */
+
+#ifndef BRLTTY_INCLUDED_CONFIG
+#define BRLTTY_INCLUDED_CONFIG
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Define this to be a string containing the copyright notice. */
+#undef PACKAGE_COPYRIGHT
+
+/* Define this to be a string containing the full name of the package. */
+#undef PACKAGE_NAME
+
+/* Define this to be a string containing the version of the package. */
+#undef PACKAGE_VERSION
+
+/* Define this to be a string containing the full name and version of the package. */
+#undef PACKAGE_STRING
+
+/* Define this to be a string containing the URL of the home page of the package. */
+#undef PACKAGE_URL
+
+/* Define this to be a string containing the short name of the package. */
+#undef PACKAGE_TARNAME
+
+/* Define this to be a string containing the address where bug reports should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define this to be a string containing the base module name. */
+#undef MODULE_NAME
+
+/* Define this to be a string containing the module extension. */
+#undef MODULE_EXTENSION
+
+/* Define this to be a string containing the library extension. */
+#undef LIBRARY_EXTENSION
+
+/* Define only one of the following program path packages. */
+#undef USE_PKG_PGMPATH_NONE
+#undef USE_PKG_PGMPATH_LINUX
+#undef USE_PKG_PGMPATH_SOLARIS
+#undef USE_PKG_PGMPATH_WINDOWS
+
+/* Define only one of the following system service packages. */
+#undef USE_PKG_SERVICE_NONE
+#undef USE_PKG_SERVICE_WINDOWS
+
+/* Define only one of the following boot parameters packages. */
+#undef USE_PKG_PARAMS_NONE
+#undef USE_PKG_PARAMS_LINUX
+
+/* Define only one of the following dynamic loading packages. */
+#undef USE_PKG_DYNLD_NONE
+#undef USE_PKG_DYNLD_DLFCN
+#undef USE_PKG_DYNLD_DYLD
+#undef USE_PKG_DYNLD_GRUB
+#undef USE_PKG_DYNLD_SHL
+#undef USE_PKG_DYNLD_WINDOWS
+
+/* Define only one of the following regular expression packages. */
+#undef USE_PKG_RGX_NONE
+#undef USE_PKG_RGX_LIBPCRE32
+#undef USE_PKG_RGX_LIBPCRE2_32
+
+/* Define only one of the following character set packages. */
+#undef USE_PKG_CHARSET_NONE
+#undef USE_PKG_CHARSET_GRUB
+#undef USE_PKG_CHARSET_ICONV
+#undef USE_PKG_CHARSET_MSDOS
+#undef USE_PKG_CHARSET_WINDOWS
+
+/* Define only one of the following host command packages. */
+#undef USE_PKG_HOSTCMD_NONE
+#undef USE_PKG_HOSTCMD_UNIX
+#undef USE_PKG_HOSTCMD_WINDOWS
+
+/* Define only one of the following mount point packages. */
+#undef USE_PKG_MNTPT_NONE
+#undef USE_PKG_MNTPT_MNTENT
+#undef USE_PKG_MNTPT_MNTTAB
+
+/* Define only one of the following mount file system packages. */
+#undef USE_PKG_MNTFS_NONE
+#undef USE_PKG_MNTFS_LINUX
+
+/* Define only one of the following keyboard packages. */
+#undef USE_PKG_KBD_NONE
+#undef USE_PKG_KBD_ANDROID
+#undef USE_PKG_KBD_LINUX
+
+/* Define only one of the following console bell packages. */
+#undef USE_PKG_BELL_NONE
+#undef USE_PKG_BELL_LINUX
+
+/* Define only one of the following keyboard LEDs packages. */
+#undef USE_PKG_LEDS_NONE
+#undef USE_PKG_LEDS_LINUX
+
+/* Define only one of the following beeper packages. */
+#undef USE_PKG_BEEP_NONE
+#undef USE_PKG_BEEP_LINUX
+#undef USE_PKG_BEEP_MSDOS
+#undef USE_PKG_BEEP_SOLARIS
+#undef USE_PKG_BEEP_SPKR
+#undef USE_PKG_BEEP_WINDOWS
+#undef USE_PKG_BEEP_WSKBD
+
+/* Define only one of the following PCM packages. */
+#undef USE_PKG_PCM_NONE
+#undef USE_PKG_PCM_ALSA
+#undef USE_PKG_PCM_ANDROID
+#undef USE_PKG_PCM_AUDIO
+#undef USE_PKG_PCM_HPUX
+#undef USE_PKG_PCM_OSS
+#undef USE_PKG_PCM_QSA
+#undef USE_PKG_PCM_WINDOWS
+
+/* Define only one of the following MIDI packages. */
+#undef USE_PKG_MIDI_NONE
+#undef USE_PKG_MIDI_ALSA
+#undef USE_PKG_MIDI_DARWIN
+#undef USE_PKG_MIDI_OSS
+#undef USE_PKG_MIDI_WINDOWS
+
+/* Define only one of the following FM packages. */
+#undef USE_PKG_FM_NONE
+#undef USE_PKG_FM_ADLIB
+
+/* Define only one of the following serial I/O packages. */
+#undef USE_PKG_SERIAL_NONE
+#undef USE_PKG_SERIAL_GRUB
+#undef USE_PKG_SERIAL_MSDOS
+#undef USE_PKG_SERIAL_TERMIOS
+#undef USE_PKG_SERIAL_WINDOWS
+
+/* Define only one of the following USB I/O packages. */
+#undef USE_PKG_USB_NONE
+#undef USE_PKG_USB_ANDROID
+#undef USE_PKG_USB_DARWIN
+#undef USE_PKG_USB_FREEBSD
+#undef USE_PKG_USB_GRUB
+#undef USE_PKG_USB_KFREEBSD
+#undef USE_PKG_USB_LIBUSB
+#undef USE_PKG_USB_LIBUSB_1_0
+#undef USE_PKG_USB_LINUX
+#undef USE_PKG_USB_NETBSD
+#undef USE_PKG_USB_OPENBSD
+#undef USE_PKG_USB_SOLARIS
+
+/* Define only one of the following Bluetooth I/O packages. */
+#undef USE_PKG_BLUETOOTH_NONE
+#undef USE_PKG_BLUETOOTH_ANDROID
+#undef USE_PKG_BLUETOOTH_DARWIN
+#undef USE_PKG_BLUETOOTH_LINUX
+#undef USE_PKG_BLUETOOTH_WINDOWS
+
+/* Define only one of the following HID I/O packages. */
+#undef USE_PKG_HID_NONE
+#undef USE_PKG_HID_LINUX
+
+/* Define only one of the following I/O ports packages. */
+#undef USE_PKG_PORTS_NONE
+#undef USE_PKG_PORTS_GLIBC
+#undef USE_PKG_PORTS_GRUB
+#undef USE_PKG_PORTS_KFREEBSD
+#undef USE_PKG_PORTS_MSDOS
+#undef USE_PKG_PORTS_WINDOWS
+
+/* Define this if the function addmntent exists. */
+#undef HAVE_ADDMNTENT
+
+/* Define this if the function clock_gettime exists. */
+#undef HAVE_CLOCK_GETTIME
+
+/* Define this if the function clock_settime exists. */
+#undef HAVE_CLOCK_SETTIME
+
+/* Define this if the function fchdir exists. */
+#undef HAVE_FCHDIR
+
+/* Define this if the function fchmod exists. */
+#undef HAVE_FCHMOD
+
+/* Define this if the function gai_strerror exists. */
+#undef HAVE_GAI_STRERROR
+
+/* Define this if the function getaddrinfo exists. */
+#undef HAVE_GETADDRINFO
+
+/* Define this if the function getnameinfo exists. */
+#undef HAVE_GETNAMEINFO
+
+/* Define this if the function getopt_long exists. */
+#undef HAVE_GETOPT_LONG
+
+/* Define this if the function getpeereid exists. */
+#undef HAVE_GETPEEREID
+
+/* Define this if the function getpeerucred exists. */
+#undef HAVE_GETPEERUCRED
+
+/* Define this if the function gettimeofday exists. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define this if the function getzoneid exists. */
+#undef HAVE_GETZONEID
+
+/* Define this if the function hstrerror exists. */
+#undef HAVE_HSTRERROR
+
+/* Define this if the function mempcpy exists. */
+#undef HAVE_MEMPCPY
+
+/* Define this if the function nanosleep exists. */
+#undef HAVE_NANOSLEEP
+
+/* Define this if the function pause exists. */
+#undef HAVE_PAUSE
+
+/* Define this if the function readlink exists. */
+#undef HAVE_READLINK
+
+/* Define this if the function realpath exists. */
+#undef HAVE_REALPATH
+
+/* Define this if the function settimeofday exists. */
+#undef HAVE_SETTIMEOFDAY
+
+/* Define this if the function shl_load is available. */
+#undef HAVE_SHL_LOAD
+
+/* Define this if the function shm_open exists. */
+#undef HAVE_SHM_OPEN
+
+/* Define this if the function shmget exists. */
+#undef HAVE_SHMGET
+
+/* Define this if the function stime exists. */
+#undef HAVE_STIME
+
+/* Define this if the function tcdrain exists. */
+#undef HAVE_TCDRAIN
+
+/* Define this if the function time exists. */
+#undef HAVE_TIME
+
+/* Define this if the function vsyslog exists. */
+#undef HAVE_VSYSLOG
+
+/* Define this if the function wmempcpy exists. */
+#undef HAVE_WMEMPCPY
+
+/* Define this if the header file alloca.h exists. */
+#undef HAVE_ALLOCA_H
+
+/* Define this if the header file execinfo.h exists. */
+#undef HAVE_EXECINFO_H
+
+/* Define this if the header file getopt.h exists. */
+#undef HAVE_GETOPT_H
+
+/* Define this if the header file glob.h exists. */
+#undef HAVE_GLOB_H
+
+/* Define this if the function glob exists. */
+#undef HAVE_GLOB
+
+/* Define this if the header file grp.h exists. */
+#undef HAVE_GRP_H
+
+/* Define this if the header file iconv.h exists. */
+#undef HAVE_ICONV_H
+
+/* Define this if the header file langinfo.h exists. */
+#undef HAVE_LANGINFO_H
+
+/* Define this if the function nl_langinfo exists. */
+#undef HAVE_NL_LANGINFO
+
+/* Define this if the header file pwd.h exists. */
+#undef HAVE_PWD_H
+
+/* Define this if the header file regex.h exists. */
+#undef HAVE_REGEX_H
+
+/* Define this if the header file sched.h exists. */
+#undef HAVE_SCHED_H
+
+/* Define this if the header file sdkddkver.h exists. */
+#undef HAVE_SDKDDKVER_H
+
+/* Define this if the header file signal.h exists. */
+#undef HAVE_SIGNAL_H
+
+/* Define this if the function sigaction exists. */
+#undef HAVE_SIGACTION
+
+/* Define this if the header file syslog.h exists. */
+#undef HAVE_SYSLOG_H
+
+/* Define this if the header file wchar.h exists. */
+#undef HAVE_WCHAR_H
+
+/* Define this if the header file sys/capability.h exists. */
+#undef HAVE_SYS_CAPABILITY_H
+
+/* Define this if the header file sys/file.h exists. */
+#undef HAVE_SYS_FILE_H
+
+/* Define this if the header file sys/io.h exists. */
+#undef HAVE_SYS_IO_H
+
+/* Define this if the header file sys/modem.h exists. */
+#undef HAVE_SYS_MODEM_H
+
+/* Define this if the header file sys/prctl.h exists. */
+#undef HAVE_SYS_PRCTL_H
+
+/* Define this if the header file sys/signalfd.h exists. */
+#undef HAVE_SYS_SIGNALFD_H
+
+/* Define this if the header file sys/socket.h exists. */
+#undef HAVE_SYS_SOCKET_H
+
+#ifndef __MINGW32__
+/* Define this if the header file sys/poll.h exists. */
+#undef HAVE_SYS_POLL_H
+
+/* Define this if the function poll exists. */
+#undef HAVE_POLL
+
+/* Define this if the header file sys/select.h exists. */
+#undef HAVE_SYS_SELECT_H
+
+/* Define this if the function select exists. */
+#undef HAVE_SELECT
+#endif /* __MINGW32__ */
+
+/* Define this if the header file sys/wait.h exists,
+ * but not for DOS since it wouldn't make sense. 
+ */
+#ifndef __MSDOS__
+#undef HAVE_SYS_WAIT_H
+#endif /* __MSDOS__ */
+
+/* Define this if the header file dev/speaker/speaker.h exists. */
+#undef HAVE_DEV_SPEAKER_SPEAKER_H
+
+/* Define this if the header file legacy/dev/usb/usb.h exists. */
+#undef HAVE_LEGACY_DEV_USB_USB_H
+
+/* Define this if the header file linux/audit.h exists. */
+#undef HAVE_LINUX_AUDIT_H
+
+/* Define this if the header file linux/filter.h exists. */
+#undef HAVE_LINUX_FILTER_H
+
+/* Define this if the header file linux/input.h exists. */
+#undef HAVE_LINUX_INPUT_H
+
+/* Define this if the header file linux/seccomp.h exists. */
+#undef HAVE_LINUX_SECCOMP_H
+
+/* Define this if the header file linux/uinput.h exists. */
+#undef HAVE_LINUX_UINPUT_H
+
+/* Define this if the header file linux/vt.h exists. */
+#undef HAVE_LINUX_VT_H
+
+/* Define this if the header file machine/speaker.h exists. */
+#undef HAVE_MACHINE_SPEAKER_H
+
+/* Define this if the header file X11/extensions/Xfixes.h exists. */
+#undef HAVE_X11_EXTENSIONS_XFIXES_H
+
+/* Define this if the header file X11/extensions/XKB.h exists. */
+#undef HAVE_X11_EXTENSIONS_XKB_H
+
+/* Define this if the header file X11/extensions/XTest.h exists. */
+#undef HAVE_X11_EXTENSIONS_XTEST_H
+
+/* Define this if the header file X11/keysym.h exists. */
+#undef HAVE_X11_KEYSYM_H
+
+/* Define this if the function XSetIOErrorExitHandler exists.  */
+#undef HAVE_XSETIOERROREXITHANDLER
+
+/* Define this if the function atspi_get_a11y_bus exists in atspi2. */
+#undef HAVE_ATSPI_GET_A11Y_BUS
+
+/* Define this if XML parsing support is to be included. */
+#undef HAVE_EXPAT
+
+/* Define this if Unicode-based internationalization support is to be included. */
+#undef HAVE_ICU
+
+/* Define this if the header file unicode/unorm2.h exists. */
+#undef HAVE_UNICODE_UNORM2_H
+
+/* Define this if the Polkit authorization manager is to be used. */
+#undef HAVE_POLKIT
+
+/* Define this if posix threads are supported. */
+#undef HAVE_POSIX_THREADS
+
+/* Define this if thread-local variables are supported. */
+#undef THREAD_LOCAL
+
+/* Define this if the function pthread_atfork exists. */
+#undef HAVE_PTHREAD_ATFORK
+
+/* Define this if the function pthread_getname_np exists. */
+#undef HAVE_PTHREAD_GETNAME_NP
+
+/* Define this if the function sd_session_get_vt is available in libsystemd. */
+#undef HAVE_SD_SESSION_GET_VT
+
+/* Define this if the bluetooth library is available. */
+#undef HAVE_LIBBLUETOOTH
+
+/* Define this if the cap(abilities) library is available. */
+#undef HAVE_LIBCAP
+
+/* Define this if GPM is to be used. */
+#undef HAVE_LIBGPM
+
+/* Define this if the package dbus is available. */
+#undef HAVE_PKG_DBUS
+
+/* Define only one of the following curses packages. */
+#undef HAVE_PKG_CURSES
+#undef HAVE_PKG_NCURSES
+#undef HAVE_PKG_NCURSESW
+#undef HAVE_PKG_PDCURSES
+#undef HAVE_PKG_PDCURSESU
+#undef HAVE_PKG_PDCURSESW
+
+/* Define this if the package x11 is available. */
+#undef HAVE_PKG_X11
+
+/* Define this if X is not available. */
+#undef X_DISPLAY_MISSING
+
+/* Define only one of the following Xaw packages. */
+#undef HAVE_PKG_XAW
+#undef HAVE_PKG_XAW3D
+#undef HAVE_PKG_NEXTAW
+#undef HAVE_PKG_XAWPLUS
+#undef HAVE_PKG_XM
+
+/* Define this if the compiler doesn't fully support the const keyword. */
+#undef const
+
+/* Define this if the compiler doesn't fully support the inline keyword. */
+#undef inline
+
+/* Define this if the packed variable attribute is supported. */
+#undef HAVE_VAR_ATTRIBUTE_PACKED
+
+/* Define this if the unused variable attribute is supported. */
+#undef HAVE_VAR_ATTRIBUTE_UNUSED
+
+/* Define this if the format function attribute is supported. */
+#undef HAVE_FUNC_ATTRIBUTE_FORMAT
+
+/* Define this if the format_arg function attribute is supported. */
+#undef HAVE_FUNC_ATTRIBUTE_FORMAT_ARG
+
+/* Define this if the noreturn function attribute is supported. */
+#undef HAVE_FUNC_ATTRIBUTE_NORETURN
+
+/* Define this if the unused function attribute is supported. */
+#undef HAVE_FUNC_ATTRIBUTE_UNUSED
+
+/* Define this if the __alignof__ operator is supported. */
+#undef HAVE_OPERATOR_ALIGNOF
+#ifndef HAVE_OPERATOR_ALIGNOF
+#define __alignof__(x) 8
+#endif /* HAVE_OPERATOR_ALIGNOF */
+
+/* Define this if the host is big endian. */
+#undef WORDS_BIGENDIAN
+
+/* Define this to be a string containing the size of the wchar_t type. */
+#undef SIZEOF_WCHAR_T_STR
+
+/* Define this if the function localtime_r is declared. */
+#undef HAVE_DECL_LOCALTIME_R
+
+/* Define this if the type PROCESS_INFORMATION_CLASS exists. */
+#undef HAVE_PROCESS_INFORMATION_CLASS
+
+/* Define this if the value ProcessUserModeIOPL exists. */
+#undef HAVE_PROCESSUSERMODEIOPL
+
+/* Define this if BRLTTY is to be run as init. */
+#undef INIT_PATH
+
+/* Define this if standard error is to be redirected to a file. */
+#undef STDERR_PATH
+
+/* Define this to be a string containing the path to the configuration directory. */
+#undef CONFIGURATION_DIRECTORY
+
+/* Define this to be a string containing the name of the default configuration file. */
+#undef CONFIGURATION_FILE
+
+/* Define this to be a string containing the path to the locale directory. */
+#undef LOCALE_DIRECTORY
+
+/* Define this to be a string containing the path to a writable directory. */
+#undef WRITABLE_DIRECTORY
+
+/* Define this to be a string containing the path to a directory which contains files that can be updated. */
+#undef UPDATABLE_DIRECTORY
+
+/* Define this to be a string containing the name of the default preferences file. */
+#undef PREFERENCES_FILE
+
+/* Define this to be a string containing the path to the helper commands directory. */
+#undef COMMANDS_DIRECTORY
+
+/* Define this to be a string containing the path to the drivers directory. */
+#undef DRIVERS_DIRECTORY
+
+/* Define this to be a string containing the path to the tables directory. */
+#undef TABLES_DIRECTORY
+
+/* Define this to be a string containing the path to the LibLouis tables directory. */
+#undef LOUIS_TABLES_DIRECTORY
+
+/* Define this to be a string containing the default parameters for the privilege establishment stage. */
+#undef PRIVILEGE_PARAMETERS
+
+/* Define this to be a string containing a list of the braille driver codes. */
+#undef BRAILLE_DRIVER_CODES
+
+/* Define this to be a string containing the default parameters for the braille driver(s). */
+#undef BRAILLE_PARAMETERS
+
+/* Define this to be a string containing the path to the directory containing the devices. */
+#undef DEVICE_DIRECTORY
+
+/* Define this to be a string containing the path to the default braille device. */
+#undef BRAILLE_DEVICE
+
+/* Define this to be a string containing the path to the first serial device. */
+#undef SERIAL_FIRST_DEVICE
+
+/* Define this to be a string containing a list of the speech driver codes. */
+#undef SPEECH_DRIVER_CODES
+
+/* Define this to be a string containing the default parameters for the speech driver(s). */
+#undef SPEECH_PARAMETERS
+
+/* Define this to be a string containing a list of the screen driver codes. */
+#undef SCREEN_DRIVER_CODES
+
+/* Define this to be a string containing the default screen driver code. */
+#undef DEFAULT_SCREEN_DRIVER
+
+/* Define this to be a string containing the default parameters for the screen driver(s). */
+#undef SCREEN_PARAMETERS
+
+/* Define this to be a string containing the name of the default text table. */
+#undef TEXT_TABLE
+
+/* Define this to be a string containing the name of the default contraction table. */
+#undef CONTRACTION_TABLE
+
+/* Define this to be a string containing the name of the default attributes table. */
+#undef ATTRIBUTES_TABLE
+
+/* Define this to include speech synthesizer and text-to-speech engine support. */
+#undef ENABLE_SPEECH_SUPPORT
+
+/* Define this to be a string containing the path to the root of the FestivalLite package. */
+#undef FLITE_ROOT
+
+/* Define this to be a string containing the path to the root of the Mikropuhe package. */
+#undef MIKROPUHE_ROOT
+
+/* Define this to be a string containing the path to the root of the SpeechDispatcher package. */
+#undef SPEECHD_ROOT
+
+/* Define this to be a string containing the path to the root of the Swift package. */
+#undef SWIFT_ROOT
+
+/* Define this to be a string containing the path to the root of the Theta package. */
+#undef THETA_ROOT
+
+/* Define this if the application programming interface is to be included. */
+#undef ENABLE_API
+
+/* Define this if the application programming interface is to be fuzzed. */
+#undef ENABLE_API_FUZZING
+
+/* Define this to be a string containing the default parameters for the application programming interface. */
+#undef API_PARAMETERS
+
+/* Define this to be a string containing the path to BrlAPI's local sockets directory. */
+#undef BRLAPI_SOCKETPATH
+
+/* Define this to be a string containing the path to BrlAPI's data files directory. */
+#undef BRLAPI_ETCDIR
+
+/* Define this to be a string containing the name of BrlAPI's authorization key file. */
+#undef BRLAPI_AUTHKEYFILE
+
+/* Define this if internationalization support is to be included. */
+#undef ENABLE_I18N_SUPPORT
+
+/* Define this if shared object support is to be included. */
+#undef ENABLE_SHARED_OBJECTS
+
+/* Define this if HP-UX audio support is available. */
+#undef HAVE_HPUX_AUDIO
+
+/* Define this to be a string containing the subdirectory for text tables. */
+#undef TEXT_TABLES_SUBDIRECTORY
+
+/* Define this to be a string containing the subdirectory for contraction tables. */
+#undef CONTRACTION_TABLES_SUBDIRECTORY
+
+/* Define this to be a string containing the subdirectory for attributes tables. */
+#undef ATTRIBUTES_TABLES_SUBDIRECTORY
+
+/* Define this to be a string containing the subdirectory for keyboard tables. */
+#undef KEYBOARD_TABLES_SUBDIRECTORY
+
+/* Define this to be a string containing the subdirectory for input tables. */
+#undef INPUT_TABLES_SUBDIRECTORY
+
+/* Define this to be a string containing the extension for text tables. */
+#undef TEXT_TABLE_EXTENSION
+
+/* Define this to be a string containing the extension for text subtables. */
+#undef TEXT_SUBTABLE_EXTENSION
+
+/* Define this to be a string containing the extension for contraction tables. */
+#undef CONTRACTION_TABLE_EXTENSION
+
+/* Define this to be a string containing the extension for contraction subtables. */
+#undef CONTRACTION_SUBTABLE_EXTENSION
+
+/* Define this to be a string containing the extension for attributes tables. */
+#undef ATTRIBUTES_TABLE_EXTENSION
+
+/* Define this to be a string containing the extension for attributes subtables. */
+#undef ATTRIBUTES_SUBTABLE_EXTENSION
+
+/* Define this to be a string containing the extension for key tables. */
+#undef KEY_TABLE_EXTENSION
+
+/* Define this to be a string containing the extension for key subtables. */
+#undef KEY_SUBTABLE_EXTENSION
+
+/* Define this to be a string containing the extension for key help files. */
+#undef KEY_HELP_EXTENSION
+
+/* Define this to be a string containing the subdirectory for profiles. */
+#undef PROFILES_SUBDIRECTORY
+
+/* Define this to be a string containing the extension for language profiles. */
+#undef LANGUAGE_PROFILE_EXTENSION
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BRLTTY_INCLUDED_CONFIG */
diff --git a/config.mk.in b/config.mk.in
new file mode 100644
index 0000000..cd21207
--- /dev/null
+++ b/config.mk.in
@@ -0,0 +1,460 @@
+# @configure_input@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+@SET_MAKE@
+
+BUILD_ALIAS = @build_alias@
+BUILD_SYSTEM = @build@
+BUILD_CPU = @build_cpu@
+BUILD_VENDOR = @build_vendor@
+BUILD_OS = @build_os@
+
+HOST_ALIAS = @host_alias@
+HOST_SYSTEM = @host@
+HOST_CPU = @host_cpu@
+HOST_VENDOR = @host_vendor@
+HOST_OS = @host_os@
+
+TARGET_ALIAS = @target_alias@
+TARGET_SYSTEM = @target@
+TARGET_CPU = @target_cpu@
+TARGET_VENDOR = @target_vendor@
+TARGET_OS = @target_os@
+
+prefix = @prefix@
+sysconfdir = @sysconfdir@
+sharedstatedir = @sharedstatedir@
+localstatedir = @localstatedir@
+includedir = @includedir@
+oldincludedir = @oldincludedir@
+
+datarootdir = @datarootdir@
+localedir = @localedir@
+mandir = @mandir@
+infodir = @infodir@
+datadir = @datadir@
+
+docdir = @docdir@
+psdir = @psdir@
+pdfdir = @pdfdir@
+htmldir = @htmldir@
+dvidir = @dvidir@
+
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+libdir = @libdir@
+libexecdir = @libexecdir@
+
+O = @OBJEXT@
+X = @EXEEXT@
+
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+
+PGM_DIR = Programs
+HDR_DIR = Headers
+DOC_DIR = Documents
+MSG_DIR = Messages
+TBL_DIR = Tables
+BND_DIR = Bindings
+BRL_DIR = Drivers/Braille
+SPK_DIR = Drivers/Speech
+SCR_DIR = Drivers/Screen
+
+DRV_DIR = lib
+API_LIBS = -L$(BLD_TOP)$(PGM_DIR) -lbrlapi
+
+INSTALL_ROOT = @install_root@
+EXECUTE_ROOT = @execute_root@
+
+PROGRAM_DIRECTORY = @program_directory@
+COMMANDS_DIRECTORY = @commands_directory@
+DRIVERS_DIRECTORY = @drivers_directory@
+UPDATABLE_DIRECTORY = @updatable_directory@
+WRITABLE_DIRECTORY = @writable_directory@
+TABLES_DIRECTORY = @tables_directory@
+MANPAGE_DIRECTORY = @manpage_directory@
+INCLUDE_DIRECTORY = @include_directory@
+
+MANUAL_DIRECTORIES = @brltty_manual_directories@
+
+POLKIT_POLICY_DIR = @POLKIT_POLICY_DIR@
+POLKIT_RULE_DIR = @POLKIT_RULE_DIR@
+SYSTEMD_SYSUSERS_DIR = @SYSTEMD_SYSUSERS_DIR@
+SYSTEMD_TMPFILES_DIR = @SYSTEMD_TMPFILES_DIR@
+SYSTEMD_UNIT_DIR = @SYSTEMD_UNIT_DIR@
+SYSTEMD_CONF_DIR = @SYSTEMD_CONF_DIR@
+
+EXPAT_INCLUDES = @expat_includes@
+EXPAT_LIBS = @expat_libs@
+
+ICU_INCLUDES = @icu_includes@
+ICU_LIBS = @icu_libs@
+
+LOUIS_INCLUDES = @louis_includes@
+LOUIS_LIBS = @louis_libs@
+
+GLIB2_PACKAGE = @glib2_package@
+GLIB2_INCLUDES = @glib2_includes@
+GLIB2_LIBS = @glib2_libs@
+
+DBUS_PACKAGE = @dbus_package@
+DBUS_INCLUDES = @dbus_includes@
+DBUS_LIBS = @dbus_libs@
+
+POLKIT_PACKAGE = @polkit_package@
+POLKIT_INCLUDES = @polkit_includes@
+POLKIT_LIBS = @polkit_libs@
+
+CURSES_PACKAGE = @curses_package@
+CURSES_LIBS = @curses_libs@
+
+GUI_TOOLKIT_PACKAGE = @gui_toolkit_package@
+
+X11_PACKAGE = @x11_package@
+X11_INCLUDES = @x11_includes@
+X11_LIBS = @x11_libs@
+
+XEXT_PACKAGE = @xext_package@
+XEXT_INCLUDES = @xext_includes@
+XEXT_LIBS = @xext_libs@
+
+XTST_PACKAGE = @xtst_package@
+XTST_INCLUDES = @xtst_includes@
+XTST_LIBS = @xtst_libs@
+
+XT_PACKAGE = @xt_package@
+XT_INCLUDES = @xt_includes@
+XT_LIBS = @xt_libs@
+
+XKB_LIBS = @xkb_libs@
+XTK_LIBS = @xtk_libs@
+XFIXES_LIBS = @xfixes_libs@
+
+CSPI_PACKAGE = @cspi_package@
+CSPI_INCLUDES = @cspi_includes@
+CSPI_LIBS = @cspi_libs@
+
+ATSPI2_PACKAGE = @atspi2_package@
+ATSPI2_INCLUDES = @atspi2_includes@
+ATSPI2_LIBS = @atspi2_libs@
+
+SYSTEM_PACKAGE = @system_package@
+SYSTEM_OBJECT = system_$(SYSTEM_PACKAGE)
+SYSTEM_OBJECTS = $(SYSTEM_OBJECT).$O $(HOSTCMD_OBJECTS)
+SYSTEM_INCLUDES = @system_includes@
+SYSTEM_LIBS = @system_libs@
+
+PGMPATH_PACKAGE = @pgmpath_package@
+PGMPATH_OBJECT = pgmpath_$(PGMPATH_PACKAGE)
+PGMPATH_OBJECTS = $(PGMPATH_OBJECT).$O
+PGMPATH_INCLUDES = @pgmpath_includes@
+PGMPATH_LIBS = @pgmpath_libs@
+
+PGMPRIVS_PACKAGE = @pgmprivs_package@
+PGMPRIVS_OBJECT = pgmprivs_$(PGMPRIVS_PACKAGE)
+PGMPRIVS_OBJECTS = $(PGMPRIVS_OBJECT).$O
+PGMPRIVS_INCLUDES = @pgmprivs_includes@
+PGMPRIVS_LIBS = @pgmprivs_libs@
+
+SERVICE_PACKAGE = @service_package@
+SERVICE_OBJECT = service_$(SERVICE_PACKAGE)
+SERVICE_OBJECTS = $(SERVICE_OBJECT).$O
+SERVICE_INCLUDES = @service_includes@
+SERVICE_LIBS = @service_libs@
+
+PARAMS_PACKAGE = @params_package@
+PARAMS_OBJECT = params_$(PARAMS_PACKAGE)
+PARAMS_OBJECTS = $(PARAMS_OBJECT).$O
+PARAMS_INCLUDES = @params_includes@
+PARAMS_LIBS = @params_libs@
+
+DYNLD_PACKAGE = @dynld_package@
+DYNLD_OBJECT = dynld_$(DYNLD_PACKAGE)
+DYNLD_OBJECTS = $(DYNLD_OBJECT).$O
+DYNLD_INCLUDES = @dynld_includes@
+DYNLD_LIBS = @dynld_libs@
+
+RGX_PACKAGE = @rgx_package@
+RGX_OBJECT = rgx_$(RGX_PACKAGE)
+RGX_OBJECTS = rgx.$O $(RGX_OBJECT).$O
+RGX_INCLUDES = @rgx_includes@
+RGX_LIBS = @rgx_libs@
+
+CHARSET_PACKAGE = @charset_package@
+CHARSET_OBJECT = charset_$(CHARSET_PACKAGE)
+CHARSET_OBJECTS = charset.$O $(CHARSET_OBJECT).$O
+CHARSET_INCLUDES = @charset_includes@
+CHARSET_LIBS = @charset_libs@
+
+HOSTCMD_PACKAGE = @hostcmd_package@
+HOSTCMD_OBJECT = hostcmd_$(HOSTCMD_PACKAGE)
+HOSTCMD_OBJECTS = hostcmd.$O $(HOSTCMD_OBJECT).$O
+HOSTCMD_INCLUDES = @hostcmd_includes@
+HOSTCMD_LIBS = @hostcmd_libs@
+
+MNTPT_PACKAGE = @mntpt_package@
+MNTPT_OBJECT = mntpt_$(MNTPT_PACKAGE)
+MNTPT_OBJECTS = mntpt.$O $(MNTPT_OBJECT).$O
+MNTPT_INCLUDES = @mntpt_includes@
+MNTPT_LIBS = @mntpt_libs@
+
+MNTFS_PACKAGE = @mntfs_package@
+MNTFS_OBJECT = mntfs_$(MNTFS_PACKAGE)
+MNTFS_OBJECTS = $(MNTFS_OBJECT).$O
+MNTFS_INCLUDES = @mntfs_includes@
+MNTFS_LIBS = @mntfs_libs@
+
+KBD_PACKAGE = @kbd_package@
+KBD_OBJECT = kbd_$(KBD_PACKAGE)
+KBD_OBJECTS = kbd.$O $(KBD_OBJECT).$O
+KBD_INCLUDES = @kbd_includes@
+KBD_LIBS = @kbd_libs@
+
+BELL_PACKAGE = @bell_package@
+BELL_OBJECT = bell_$(BELL_PACKAGE)
+BELL_OBJECTS = bell.$O $(BELL_OBJECT).$O
+BELL_INCLUDES = @bell_includes@
+BELL_LIBS = @bell_libs@
+
+LEDS_PACKAGE = @leds_package@
+LEDS_OBJECT = leds_$(LEDS_PACKAGE)
+LEDS_OBJECTS = leds.$O $(LEDS_OBJECT).$O
+LEDS_INCLUDES = @leds_includes@
+LEDS_LIBS = @leds_libs@
+
+BEEP_PACKAGE = @beep_package@
+BEEP_OBJECT = beep_$(BEEP_PACKAGE)
+BEEP_OBJECTS = notes_beep.$O beep.$O $(BEEP_OBJECT).$O
+BEEP_INCLUDES = @beep_includes@
+BEEP_LIBS = @beep_libs@
+
+PCM_PACKAGE = @pcm_package@
+PCM_OBJECT = pcm_$(PCM_PACKAGE)
+PCM_OBJECTS = notes_pcm.$O pcm.$O $(PCM_OBJECT).$O
+PCM_INCLUDES = @pcm_includes@
+PCM_LIBS = @pcm_libs@
+
+MIDI_PACKAGE = @midi_package@
+MIDI_OBJECT = midi_$(MIDI_PACKAGE)
+MIDI_OBJECTS = notes_midi.$O midi.$O $(MIDI_OBJECT).$O
+MIDI_INCLUDES = @midi_includes@
+MIDI_LIBS = @midi_libs@
+
+FM_PACKAGE = @fm_package@
+FM_OBJECT = fm_$(FM_PACKAGE)
+FM_OBJECTS = notes_fm.$O $(FM_OBJECT).$O
+FM_INCLUDES = @fm_includes@
+FM_LIBS = @fm_libs@
+
+SERIAL_PACKAGE = @serial_package@
+SERIAL_OBJECT = serial_$(SERIAL_PACKAGE)
+SERIAL_OBJECTS = serial.$O $(SERIAL_OBJECT).$O
+SERIAL_INCLUDES = @serial_includes@
+SERIAL_LIBS = @serial_libs@
+
+USB_PACKAGE = @usb_package@
+USB_OBJECT = usb_$(USB_PACKAGE)
+USB_OBJECTS = usb.$O usb_hid.$O usb_devices.$O usb_serial.$O usb_adapters.$O usb_cdc_acm.$O usb_belkin.$O usb_ch341.$O usb_cp2101.$O usb_cp2110.$O usb_ftdi.$O $(USB_OBJECT).$O
+USB_INCLUDES = @usb_includes@
+USB_LIBS = @usb_libs@
+
+BLUETOOTH_PACKAGE = @bluetooth_package@
+BLUETOOTH_OBJECT = bluetooth_$(BLUETOOTH_PACKAGE)
+BLUETOOTH_OBJECTS = bluetooth.$O bluetooth_names.$O $(BLUETOOTH_OBJECT).$O
+BLUETOOTH_INCLUDES = @bluetooth_includes@
+BLUETOOTH_LIBS = @bluetooth_libs@
+
+HID_PACKAGE = @hid_package@
+HID_OBJECT = hid_$(HID_PACKAGE)
+HID_OBJECTS = hid.$O hid_items.$O hid_braille.$O $(HID_OBJECT).$O
+HID_INCLUDES = @hid_includes@
+HID_LIBS = @hid_libs@
+
+PORTS_PACKAGE = @ports_package@
+PORTS_OBJECT = ports_$(PORTS_PACKAGE)
+PORTS_OBJECTS = $(PORTS_OBJECT).$O
+PORTS_INCLUDES = @ports_includes@
+PORTS_LIBS = @ports_libs@
+
+FUZZER_LIBS = @fuzzer_libs@
+
+CC = @CC@
+CPPFLAGS = -I$(BLD_DIR) -I$(SRC_DIR) -I$(BLD_TOP:/=)/$(PGM_DIR) -I$(SRC_TOP:/=)/$(PGM_DIR) -I$(SRC_TOP:/=)/$(HDR_DIR) -I$(BLD_TOP:/=) -I$(SRC_TOP:/=) @CPPFLAGS@ @DEFS@
+CFLAGS = $(CPPFLAGS) @CFLAGS@
+LIBCFLAGS = $(CFLAGS) @LIBCFLAGS@
+
+CXX = @CXX@
+CXXFLAGS = $(CPPFLAGS) @CXXFLAGS@
+LIBCXXFLAGS = $(CXXFLAGS) @LIBCXXFLAGS@
+
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDLIBS = $(ICU_LIBS) $(SYSTEM_LIBS) $(FUZZER_LIBS) @LIBS@
+
+MKSHR = @MKSHR@
+MKREL = @MKREL@
+MKLIB = @MKLIB@
+
+STRIP = @STRIP@
+RANLIB = @RANLIB@
+
+AWK = @AWK@
+SYMLINK = @LN_S@
+DOXYGEN = @DOXYGEN@
+
+PKG_CONFIG = @PKG_CONFIG@
+MSGMERGE = @MSGMERGE@
+MSGFMT = @MSGFMT@
+
+INSTALL = @INSTALL@
+INSTALL_SCRIPT = $(INSTALL)
+INSTALL_PROGRAM = $(INSTALL_SCRIPT) @INSTALL_OPTION_STRIP@
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_DIRECTORY = $(INSTALL) -d
+
+ARC_PFX = @archive_prefix@
+ARC_EXT = @archive_extension@
+
+LIB_PFX = @library_prefix@
+LIB_EXT = @library_extension@
+
+MOD_NAME = @module_name@
+MOD_EXT = @module_extension@
+
+CONFLIBDIR = @CONFLIBDIR@
+LIB_VAR = @libsearch_variable@
+
+TEXT_TABLES_SUBDIRECTORY = @TEXT_TABLES_SUBDIRECTORY@
+CONTRACTION_TABLES_SUBDIRECTORY = @CONTRACTION_TABLES_SUBDIRECTORY@
+ATTRIBUTES_TABLES_SUBDIRECTORY = @ATTRIBUTES_TABLES_SUBDIRECTORY@
+KEYBOARD_TABLES_SUBDIRECTORY = @KEYBOARD_TABLES_SUBDIRECTORY@
+INPUT_TABLES_SUBDIRECTORY = @INPUT_TABLES_SUBDIRECTORY@
+
+TEXT_TABLE_EXTENSION = @TEXT_TABLE_EXTENSION@
+TEXT_SUBTABLE_EXTENSION = @TEXT_SUBTABLE_EXTENSION@
+
+CONTRACTION_TABLE_EXTENSION = @CONTRACTION_TABLE_EXTENSION@
+CONTRACTION_SUBTABLE_EXTENSION = @CONTRACTION_SUBTABLE_EXTENSION@
+
+ATTRIBUTES_TABLE_EXTENSION = @ATTRIBUTES_TABLE_EXTENSION@
+ATTRIBUTES_SUBTABLE_EXTENSION = @ATTRIBUTES_SUBTABLE_EXTENSION@
+
+KEY_TABLE_EXTENSION = @KEY_TABLE_EXTENSION@
+KEY_SUBTABLE_EXTENSION = @KEY_SUBTABLE_EXTENSION@
+KEY_HELP_EXTENSION = @KEY_HELP_EXTENSION@
+
+PROFILES_SUBDIRECTORY = @PROFILES_SUBDIRECTORY@
+LANGUAGE_PROFILE_EXTENSION = @LANGUAGE_PROFILE_EXTENSION@
+
+BRAILLE_DRIVER_CODES = @brltty_item_codes_braille@
+BRAILLE_DRIVER_NAMES = @brltty_item_names_braille@
+BRAILLE_INTERNAL_DRIVER_CODES = @brltty_internal_codes_braille@
+BRAILLE_INTERNAL_DRIVER_NAMES = @brltty_internal_names_braille@
+BRAILLE_EXTERNAL_DRIVER_CODES = @brltty_external_codes_braille@
+BRAILLE_EXTERNAL_DRIVER_NAMES = @brltty_external_names_braille@
+BRAILLE_DRIVER_OBJECTS = @braille_driver_objects@
+BRAILLE_DRIVER_LIBRARIES = @braille_driver_libraries@
+BRAILLE_DRIVERS = @braille_drivers@
+
+SPEECH_DRIVER_CODES = @brltty_item_codes_speech@
+SPEECH_DRIVER_NAMES = @brltty_item_names_speech@
+SPEECH_INTERNAL_DRIVER_CODES = @brltty_internal_codes_speech@
+SPEECH_INTERNAL_DRIVER_NAMES = @brltty_internal_names_speech@
+SPEECH_EXTERNAL_DRIVER_CODES = @brltty_external_codes_speech@
+SPEECH_EXTERNAL_DRIVER_NAMES = @brltty_external_names_speech@
+SPEECH_DRIVER_OBJECTS = @speech_driver_objects@
+SPEECH_DRIVER_LIBRARIES = @speech_driver_libraries@
+SPEECH_DRIVERS = @speech_drivers@
+
+SCREEN_DRIVER_CODES = @brltty_item_codes_screen@
+SCREEN_DRIVER_NAMES = @brltty_item_names_screen@
+SCREEN_INTERNAL_DRIVER_CODES = @brltty_internal_codes_screen@
+SCREEN_INTERNAL_DRIVER_NAMES = @brltty_internal_names_screen@
+SCREEN_EXTERNAL_DRIVER_CODES = @brltty_external_codes_screen@
+SCREEN_EXTERNAL_DRIVER_NAMES = @brltty_external_names_screen@
+SCREEN_DRIVER_OBJECTS = @screen_driver_objects@
+SCREEN_DRIVER_LIBRARIES = @screen_driver_libraries@
+SCREEN_DRIVERS = @screen_drivers@
+
+LIBBRAILLE_ROOT = @libbraille_root@
+ESPEAK_ROOT = @espeak_root@
+ESPEAK_NG_ROOT = @espeak_ng_root@
+FLITE_ROOT = @flite_root@
+FLITE_LANGUAGE = @flite_language@
+FLITE_LEXICON = @flite_lexicon@
+FLITE_VOICE = @flite_voice@
+MIKROPUHE_ROOT = @mikropuhe_root@
+SPEECHD_INCLUDES = @speechd_includes@
+SPEECHD_LIBS = @speechd_libs@
+SWIFT_ROOT = @swift_root@
+THETA_ROOT = @theta_root@
+
+INSTALL_TEXT_TABLES = @install_text_tables@
+INSTALL_CONTRACTION_TABLES = @install_contraction_tables@
+INSTALL_ATTRIBUTES_TABLES = @install_attributes_tables@
+INSTALL_DRIVERS = @install_drivers@
+INSTALL_MESSAGES = @install_messages@
+
+TEXT_TABLE = @text_table@
+CONTRACTION_TABLE = @contraction_table@
+ATTRIBUTES_TABLE = @attributes_table@
+
+CTB_OBJECTS = @contracted_braille_objects@
+SPEECH_OBJECT = @speech_support_object@
+XSEL_OBJECT = @xsel_object@
+
+API_VERSION = @api_version@
+API_RELEASE = @api_release@
+API_SERVER_OBJECTS = @api_server_objects@
+API_LIBRARIES = @api_libraries@
+ALL_API = @all_api@
+BUILD_API = @build_api@
+INSTALL_API = @install_api@
+API_REF = @api_ref@
+API_DYNAMIC_LIBRARY = @api_dynamic_library@
+INSTALL_API_LIBRARIES = @install_api_libraries@
+UNINSTALL_API_LIBRARIES = @uninstall_api_libraries@
+API_AUTHFILE = @api_authkeyfile@
+API_SOCKET_PATH = @BRLAPI_SOCKETPATH@
+
+API_BINDINGS = @api_bindings@
+ALL_API_BINDINGS = @all_api_bindings@
+INSTALL_API_BINDINGS = @install_api_bindings@
+
+ALL_XBRLAPI = @all_xbrlapi@
+INSTALL_XBRLAPI = @install_xbrlapi@
+
+ALL_BRLTTY_PTY = @all_brltty_pty@
+INSTALL_BRLTTY_PTY = @install_brltty_pty@
+
+MOUNT_OBJECTS = $(MNTPT_OBJECTS) $(MNTFS_OBJECTS)
+GIO_OBJECTS = gio.$O gio_serial.$O gio_usb.$O gio_bluetooth.$O gio_hid.$O gio_null.$O
+IO_OBJECTS = io_log.$O $(SERIAL_OBJECTS) $(USB_OBJECTS) $(BLUETOOTH_OBJECTS) $(HID_OBJECTS) $(GIO_OBJECTS) $(MOUNT_OBJECTS)
+TUNE_OBJECTS = tune.$O notes.$O $(BEEP_OBJECTS) $(PCM_OBJECTS) $(MIDI_OBJECTS) $(FM_OBJECTS)
+ASYNC_OBJECTS = async_handle.$O async_data.$O async_wait.$O async_alarm.$O async_task.$O async_io.$O async_event.$O async_signal.$O thread.$O
+BASE_OBJECTS = messages.$O log.$O log_history.$O addresses.$O file.$O device.$O parse.$O variables.$O datafile.$O unicode.$O utf8.$O timing.$O $(ASYNC_OBJECTS) io_misc.$O queue.$O lock.$O $(DYNLD_OBJECTS) $(PORTS_OBJECTS) $(SYSTEM_OBJECTS)
+CMDLINE_OBJECTS = cmdline.$O $(PARAMS_OBJECTS)
+PROGRAM_OBJECTS = program.$O $(PGMPATH_OBJECTS) pid.$O $(CMDLINE_OBJECTS) $(BASE_OBJECTS)
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..5208b1a
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,2054 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_PREREQ([2.64])
+AC_INIT([BRLTTY], [6.6], [BRLTTY@brltty.app], [brltty], [https://brltty.app/])
+AC_CONFIG_SRCDIR([Programs/brltty.c])
+
+AC_CONFIG_MACRO_DIR([m4])
+m4_include([Bindings/Emacs/bindings.m4])
+m4_include([Bindings/Java/bindings.m4])
+m4_include([Bindings/Lisp/bindings.m4])
+m4_include([Bindings/Lua/bindings.m4])
+m4_include([Bindings/OCaml/bindings.m4])
+m4_include([Bindings/Python/bindings.m4])
+m4_include([Bindings/Tcl/bindings.m4])
+
+copyright="© 1995-2023 by The BRLTTY Developers"
+AC_SUBST([PACKAGE_COPYRIGHT], ["${copyright}"])
+AC_DEFINE_UNQUOTED([PACKAGE_COPYRIGHT], ["${copyright}"],
+                   [Define this to be a string containing the copyright notice.])
+
+AC_SUBST([api_major], [0])
+AC_SUBST([api_minor], [8])
+AC_SUBST([api_revision], [5])
+
+AC_SUBST([api_name], [BrlAPI])
+AC_SUBST([api_version], [${api_major}.${api_minor}])
+AC_SUBST([api_release], [${api_version}.${api_revision}])
+
+AC_DEFINE_UNQUOTED([BRLAPI_RELEASE], ["${api_release}"],
+                   [Define this to be a string containing the API library version.])
+AC_DEFINE_UNQUOTED([BRLAPI_MAJOR], [${api_major}],
+                   [Define this to be the API library major version number.])
+AC_DEFINE_UNQUOTED([BRLAPI_MINOR], [${api_minor}],
+                   [Define this to be the API library minor version number.])
+AC_DEFINE_UNQUOTED([BRLAPI_REVISION], [${api_revision}],
+                   [Define this to be the API library revision number.])
+
+BRLTTY_SUMMARY_BEGIN
+brltty_build_directories=". Programs Documents Messages"
+brltty_source_directory="`dirname "${0}"`"
+brltty_manual_directories=""
+
+for brltty_directory in Autostart Authorization Initramfs Android
+do
+   for brltty_subdirectory in `cd "${brltty_source_directory}/${brltty_directory}" && echo *`
+   do
+      brltty_path="${brltty_directory}/${brltty_subdirectory}"
+      test -f "${brltty_source_directory}/${brltty_path}/Makefile.in" && {
+         brltty_build_directories="${brltty_build_directories} ${brltty_path}"
+      }
+   done
+done
+
+for brltty_manual_directory in `(cd "${brltty_source_directory}" && echo Documents/Manual-*/*)`
+do
+   test -f "${brltty_source_directory}/${brltty_manual_directory}/Makefile.in" && {
+      brltty_manual_directories="${brltty_manual_directories} ${brltty_manual_directory}"
+   }
+done
+
+AC_SUBST([brltty_manual_directories])
+brltty_build_directories="${brltty_build_directories} ${brltty_manual_directories}"
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_HEADERS([Programs/brlapi.h])
+AC_PREFIX_DEFAULT([])
+AC_CONFIG_AUX_DIR([acdir])
+AC_CANONICAL_TARGET
+
+brltty_default_execute_root="/${PACKAGE_TARNAME}-${PACKAGE_VERSION}"
+BRLTTY_ARG_WITH(
+   [execute-root], [DIRECTORY],
+   [where the package is to be anchored at run-time],
+   [execute_root], [""]
+)
+if test "${execute_root}" = "no"
+then
+   execute_root=""
+elif test "${execute_root}" = "yes"
+then
+   execute_root="${brltty_default_execute_root}"
+fi
+AC_SUBST([execute_root])
+BRLTTY_SUMMARY_ITEM([execute-root], [execute_root])
+
+brltty_default_install_root="/tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION}"
+BRLTTY_ARG_WITH(
+   [install-root], [DIRECTORY],
+   [where to install the package],
+   [install_root], ["${execute_root}"]
+)
+if test "${install_root}" = "no"
+then
+   install_root="${execute_root}"
+elif test "${install_root}" = "yes"
+then
+   install_root="${brltty_default_install_root}"
+fi
+AC_SUBST([install_root])
+BRLTTY_SUMMARY_ITEM([install-root], [install_root])
+
+BRLTTY_ARG_WITH(
+   [configuration-file], [FILE],
+   [name of configuration file],
+   [CONFIGURATION_FILE], ["${PACKAGE_TARNAME}.conf"]
+)
+AC_SUBST([CONFIGURATION_FILE])
+BRLTTY_DEFINE_EXPANDED([CONFIGURATION_FILE], ["${CONFIGURATION_FILE}"],
+                       [Define this to be a string containing the name of the default configuration file.])
+
+AC_SUBST([PREFERENCES_FILE], ["${PACKAGE_TARNAME}.prefs"])
+BRLTTY_DEFINE_EXPANDED([PREFERENCES_FILE], ["${PREFERENCES_FILE}"],
+                       [Define this to be a string containing the name of the default preferences file.])
+
+BRLTTY_PORTABLE_DIRECTORY([includedir], [/usr])
+BRLTTY_PORTABLE_DIRECTORY([datarootdir], [/usr])
+BRLTTY_PORTABLE_DIRECTORY([localstatedir], [])
+
+BRLTTY_SUMMARY_ITEM([libdir], [libdir])
+BRLTTY_SUMMARY_ITEM([sysconfdir], [sysconfdir])
+
+BRLTTY_ARG_REQUIRED(
+   [program-directory], [DIRECTORY],
+   [path to directory containing executables],
+   [program_directory], ["${bindir}"]
+)
+
+BRLTTY_ARG_REQUIRED(
+   [commands-directory], [DIRECTORY],
+   [path to directory containing helper commands],
+   [commands_directory], ["${libexecdir}/${PACKAGE_TARNAME}"]
+)
+
+BRLTTY_ARG_REQUIRED(
+   [drivers-directory], [DIRECTORY],
+   [path to directory containing drivers],
+   [drivers_directory], ["${libdir}/${PACKAGE_TARNAME}"]
+)
+
+BRLTTY_ARG_WITH(
+   [updatable-directory], [DIRECTORY],
+   [path to directory which contains files that can be updated],
+   [updatable_directory], ["yes"]
+)
+if test "${updatable_directory}" = "no"
+then
+   updatable_directory=""
+elif test "${updatable_directory}" = "yes"
+then
+   updatable_directory="${localstatedir}/lib/${PACKAGE_TARNAME}"
+fi
+AC_SUBST([updatable_directory])
+BRLTTY_SUMMARY_ITEM([updatable-directory], [updatable_directory])
+
+BRLTTY_ARG_WITH(
+   [writable-directory], [DIRECTORY],
+   [path to directory which can be written to],
+   [writable_directory], ["yes"]
+)
+if test "${writable_directory}" = "no"
+then
+   writable_directory=""
+elif test "${writable_directory}" = "yes"
+then
+   writable_directory="/run/${PACKAGE_TARNAME}"
+fi
+AC_SUBST([writable_directory])
+BRLTTY_SUMMARY_ITEM([writable-directory], [writable_directory])
+
+BRLTTY_ARG_REQUIRED(
+   [tables-directory], [DIRECTORY],
+   [path to directory containing data files],
+   [tables_directory], ["${sysconfdir}/${PACKAGE_TARNAME}"]
+)
+
+BRLTTY_DEFINE_STRING([TEXT_TABLES_SUBDIRECTORY], [Text], [the subdirectory for text tables])
+BRLTTY_DEFINE_STRING([CONTRACTION_TABLES_SUBDIRECTORY], [Contraction], [the subdirectory for contraction tables])
+BRLTTY_DEFINE_STRING([ATTRIBUTES_TABLES_SUBDIRECTORY], [Attributes], [the subdirectory for attributes tables])
+BRLTTY_DEFINE_STRING([KEYBOARD_TABLES_SUBDIRECTORY], [Keyboard], [the subdirectory for keyboard key tables])
+BRLTTY_DEFINE_STRING([INPUT_TABLES_SUBDIRECTORY], [Input], [the subdirectory for braille device key tables])
+
+BRLTTY_DEFINE_STRING([TEXT_TABLE_EXTENSION], [.ttb], [the extension for text tables])
+BRLTTY_DEFINE_STRING([TEXT_SUBTABLE_EXTENSION], [.tti], [the extension for text subtables])
+
+BRLTTY_DEFINE_STRING([CONTRACTION_TABLE_EXTENSION], [.ctb], [the extension for contraction tables])
+BRLTTY_DEFINE_STRING([CONTRACTION_SUBTABLE_EXTENSION], [.cti], [the extension for contraction subtables])
+
+BRLTTY_DEFINE_STRING([ATTRIBUTES_TABLE_EXTENSION], [.atb], [the extension for attributes tables])
+BRLTTY_DEFINE_STRING([ATTRIBUTES_SUBTABLE_EXTENSION], [.ati], [the extension for attributes subtables])
+
+BRLTTY_DEFINE_STRING([KEY_TABLE_EXTENSION], [.ktb], [the extension for key tables])
+BRLTTY_DEFINE_STRING([KEY_SUBTABLE_EXTENSION], [.kti], [the extension for key subtables])
+BRLTTY_DEFINE_STRING([KEY_HELP_EXTENSION], [.txt], [the extension for key help files])
+
+BRLTTY_DEFINE_STRING([PROFILES_SUBDIRECTORY], [Profiles], [the subdirectory for profiles])
+BRLTTY_DEFINE_STRING([LANGUAGE_PROFILE_EXTENSION], [.lpf], [the extension for language profiles])
+
+BRLTTY_ARG_REQUIRED(
+   [manpage-directory], [DIRECTORY],
+   [path to directory containing manual pages],
+   [manpage_directory], ["${mandir}"]
+)
+
+BRLTTY_ARG_REQUIRED(
+   [include-directory], [DIRECTORY],
+   [path to directory containing header files],
+   [include_directory], ["${includedir}/${PACKAGE_TARNAME}"]
+)
+
+brltty_default_init_path=/sbin/init_real
+BRLTTY_ARG_WITH(
+   [init-path], [FILE],
+   [path to real init program],
+   [init_path], [""]
+)
+if test "${init_path}" = "no"
+then
+   init_path=""
+elif test "${init_path}" = "yes"
+then
+   init_path="${brltty_default_init_path}"
+fi
+if test -n "${init_path}"
+then
+   AC_DEFINE_UNQUOTED([INIT_PATH], ["${init_path}"],
+                      [Define this if BRLTTY is to be run as init.])
+fi
+BRLTTY_SUMMARY_ITEM([init-path], [init_path])
+AC_SUBST([init_path])
+
+BRLTTY_ARG_PARAMETERS([privilege], [privilege establishment stage], [PLATFORM:], [])
+
+brltty_default_stderr_path=/tmp/brltty.log
+BRLTTY_ARG_WITH(
+   [stderr-path], [FILE],
+   [path to standard error log file],
+   [stderr_path], [""]
+)
+if test "${stderr_path}" = "no"
+then
+   stderr_path=""
+elif test "${stderr_path}" = "yes"
+then
+   stderr_path="${brltty_default_stderr_path}"
+fi
+if test -n "${stderr_path}"
+then
+   AC_DEFINE_UNQUOTED([STDERR_PATH], ["${stderr_path}"],
+                      [Define this if standard error is to be redirected to a file.])
+fi
+BRLTTY_SUMMARY_ITEM([stderr-path], [stderr_path])
+AC_SUBST([stderr_path])
+
+AC_PROG_MAKE_SET
+
+AC_PROG_CC
+AC_PROG_CXX
+
+brltty_gcc_cflags="-std=gnu99 -Wall"
+brltty_gcc_cppflags=""
+
+case "${host_os}"
+in
+   darwin*)
+      brltty_gcc_cflags="${brltty_gcc_cflags} -Wno-initializer-overrides -Wno-unused-const-variable -Wno-pointer-bool-conversion"
+      ;;
+   elf*)
+      brltty_gcc_cppflags="${brltty_gcc_cppflags} -ffreestanding -nostdinc -nostdlib"
+      CPPFLAGS="${CPPFLAGS} -DGRUB_RUNTIME -DGRUB_FILE=__FILE__ -DNESTED_FUNC_ATTR="
+
+      brltty_include_directory=`$CC -print-file-name=include`
+      CPPFLAGS="${CPPFLAGS} -isystem $brltty_include_directory"
+
+      brltty_grub_root="`pwd`/grub-root"
+      test -d $brltty_grub_root || {
+         AC_MSG_ERROR([symbolic link not found: $brltty_grub_root])
+      }
+      CPPFLAGS="${CPPFLAGS} -I$brltty_grub_root -I$brltty_grub_root/grub-core/lib/posix_wrap/include -I$brltty_grub_root/include"
+      ;;
+esac
+
+test "${GCC}" = "yes" && {
+   CFLAGS="${CFLAGS} ${brltty_gcc_cflags}"
+   CPPFLAGS="${CPPFLAGS} ${brltty_gcc_cppflags}"
+
+   extraCFlags=""
+   for flag in unknown-warning-option
+   do
+      AX_CHECK_COMPILE_FLAG([-Werror=${flag}], [dnl
+         extraCFlags="${extraCFlags} -Werror=${flag}"
+      ])
+   done
+
+   # warnings that should be errors
+   for flag in  format-security
+   do
+      AX_CHECK_COMPILE_FLAG([-Werror=${flag}], [dnl
+         CFLAGS="${CFLAGS} -Werror=${flag}"
+      ], [:], [${extraCFlags}])
+   done
+
+   # warnings that should be suppressed
+   for flag in address-of-packed-member stringop-truncation initializer-overrides
+   do
+      AX_CHECK_COMPILE_FLAG([-Werror=${flag}], [dnl
+         CFLAGS="${CFLAGS} -Wno-${flag}"
+      ], [:], [${extraCFlags}])
+   done
+
+   AX_GCC_VAR_ATTRIBUTE([packed])
+   AX_GCC_VAR_ATTRIBUTE([unused])
+
+   AX_GCC_FUNC_ATTRIBUTE([format])
+   AX_GCC_FUNC_ATTRIBUTE([format_arg])
+   AX_GCC_FUNC_ATTRIBUTE([noreturn])
+   AX_GCC_FUNC_ATTRIBUTE([unused])
+}
+
+test "${GXX}" = "yes" && {
+   CXXFLAGS="${CXXFLAGS} ${brltty_gcc_cflags}"
+}
+
+case "${host_os}"
+in
+   linux*|gnu*|kfreebsd*)
+      brltty_prog_cc_sysflags="-D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=2 -D_BSD_SOURCE -D_XOPEN_SOURCE=500 -D_XOPEN_SOURCE_EXTENDED -D_GNU_SOURCE"
+      ;;
+   solaris2.1?*) # Solaris 10 & 11 require _XOPEN_SOURCE=600 if using C99
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE=600 -D__EXTENSIONS__"
+      ;;
+   solaris*)
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE=500 -D__EXTENSIONS__"
+      ;;
+   hpux*)
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE_EXTENDED -D_HPUX_SOURCE"
+      ;;
+   osf*)
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE -D_OSF_SOURCE"
+      ;;
+   cygwin*)
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE_EXTENDED -D_GNU_SOURCE"
+      ;;
+   mingw*)
+      brltty_prog_cc_sysflags="-D_XOPEN_SOURCE_EXTENDED -D_GNU_SOURCE"
+      ;;
+   *)
+      brltty_prog_cc_sysflags=""
+      ;;
+esac
+CPPFLAGS="${CPPFLAGS} ${brltty_prog_cc_sysflags}"
+
+if test "${GCC}" = "yes"
+then
+   case "${host_os}"
+   in
+      cygwin*|mingw*)
+         LIBCFLAGS=""
+         ;;
+      msdos*)
+         LIBCFLAGS=""
+         ;;
+      darwin*)
+         LIBCFLAGS=""
+         ;;
+      *)
+         LIBCFLAGS="-fPIC"
+         ;;
+   esac
+else
+   case "${host_os}"
+   in
+      *)
+         LIBCFLAGS=""
+         AC_MSG_WARN([library flags not configured for ${host_os}])
+         ;;
+   esac
+fi
+AC_SUBST([LIBCFLAGS])
+
+if test "$GXX" = "yes"
+then
+   LIBCXXFLAGS="-fPIC"
+else
+   case "${host_os}"
+   in
+      *)
+         LIBCXXFLAGS=""
+         ;;
+   esac
+fi
+AC_SUBST([LIBCXXFLAGS])
+
+AC_CHECK_TOOL([LD], [ld])
+AC_CHECK_TOOL([STRIP], [strip])
+AC_CHECK_TOOL([RANLIB], [ranlib])
+
+AC_PROG_AWK
+AC_PROG_LN_S
+
+m4_ifdef([PKG_PROG_PKG_CONFIG], [dnl
+   PKG_PROG_PKG_CONFIG
+], [dnl
+   AC_PATH_PROG([PKG_CONFIG], [pkg-config])
+])
+
+if test -z "${PKG_CONFIG}"
+then
+   PKG_CONFIG=false
+fi
+
+AC_PATH_PROG([MSGMERGE], [msgmerge], [false])
+AC_PATH_PROG([MSGFMT], [msgfmt], [false])
+
+AC_PATH_PROG([LINUXDOC], [linuxdoc], [false])
+AC_PATH_PROG([DOXYGEN], [doxygen], [false])
+
+case "${host_os}"
+in
+   cygwin*  ) LDFLAGS_DYNAMIC="--export-all-symbols --enable-auto-import";;
+   darwin*  ) LDFLAGS_DYNAMIC="";;
+   elf*     ) LDFLAGS_DYNAMIC="";;
+   freebsd* ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   gnu*     ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   haiku*   ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   hpux*    ) LDFLAGS_DYNAMIC="-E +s";;
+   kfreebsd*) LDFLAGS_DYNAMIC="-export-dynamic";;
+   linux*   ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   mingw*   ) LDFLAGS_DYNAMIC="--export-all-symbols --enable-auto-import";;
+   msdos*   ) LDFLAGS_DYNAMIC="";;
+   netbsd*  ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   openbsd* ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   osf*     ) LDFLAGS_DYNAMIC="";;
+   *qnx*    ) LDFLAGS_DYNAMIC="-export-dynamic";;
+   solaris* ) LDFLAGS_DYNAMIC="";;
+   *) AC_MSG_ERROR([dynamic linkage not supported for ${host_os}]);;
+esac
+LDFLAGS_DYNAMIC="BRLTTY_OPTIONS_LD2CC([${LDFLAGS_DYNAMIC}])"
+
+if test "${GCC}" = "yes"
+then
+   LDFLAGS_STATIC="-static"
+else
+   case "${host_os}"
+   in
+      cygwin*  ) LDFLAGS_STATIC="-static";;
+      elf*     ) LDFLAGS_STATIC="-static";;
+      freebsd* ) LDFLAGS_STATIC="-static";;
+      gnu*     ) LDFLAGS_STATIC="-static";;
+      hpux*    ) LDFLAGS_STATIC="-Bstatic";;
+      kfreebsd*) LDFLAGS_STATIC="-static";;
+      linux*   ) LDFLAGS_STATIC="-static";;
+      mingw*   ) LDFLAGS_STATIC="-static";;
+      netbsd*  ) LDFLAGS_STATIC="-Bstatic";;
+      openbsd* ) LDFLAGS_STATIC="-static";;
+      osf*     ) LDFLAGS_STATIC="-non_shared";;
+      *qnx*    ) LDFLAGS_STATIC="-static";;
+      solaris* ) LDFLAGS_STATIC="-dn";;
+      *) AC_MSG_ERROR([static linkage not supported for ${host_os}]);;
+   esac
+   LDFLAGS_STATIC="BRLTTY_OPTIONS_LD2CC([${LDFLAGS_STATIC}])"
+fi
+
+pkgconfig_flags_libs="--libs"
+
+BRLTTY_ARG_ENABLE(
+   [standalone-programs],
+   [statically linked executables],
+   [],
+[dnl
+   LDFLAGS="${LDFLAGS} ${LDFLAGS_STATIC}"
+   brltty_standalone_programs="yes"
+   pkgconfig_flags_libs="${pkgconfig_flags_libs} --static"
+], [dnl
+   LDFLAGS="${LDFLAGS} ${LDFLAGS_DYNAMIC}"
+
+   AC_DEFINE([ENABLE_SHARED_OBJECTS], [1],
+             [Define this if shared object support is to be included.])
+])
+
+AC_PROG_INSTALL
+BRLTTY_EXECUTABLE_PATH([INSTALL])
+
+test "${LINUXDOC}" = "false" && can_make_manual=no || can_make_manual=yes
+AC_SUBST([can_make_manual])
+
+test "${DOXYGEN}" = "false" && can_make_BrlAPIref=no || can_make_BrlAPIref=yes
+AC_SUBST([can_make_BrlAPIref])
+
+AC_CACHE_CHECK([for shared object creation command], [brltty_cv_prog_make_object_shared], [dnl
+   case "${host_os}"
+   in
+      linux*|gnu*|openbsd*|freebsd*|kfreebsd*|netbsd*|*qnx*|cygwin*|mingw*|msdos*|elf*|haiku*)
+         brltty_mkshr_ld_make="-shared"
+         ;;
+      solaris*)
+         brltty_mkshr_ld_make="-G"
+         ;;
+      hpux*)
+         brltty_mkshr_ld_make="-b"
+         ;;
+      osf*)
+         brltty_mkshr_ld_make="-shared"
+         ;;
+      darwin*)
+         brltty_mkshr_ld_make="-bundle"
+         brltty_mkshr_ld_options="-flat_namespace -undefined suppress"
+         brltty_mkshr_gcc_make="-bundle"
+         ;;
+      *)
+         AC_MSG_ERROR([loadable module creation command not configured for ${host_os}])
+         brltty_mkshr_ld_make="NOT_SUPPORTED"
+         brltty_mkshr_ld_options="NOT_SUPPORTED"
+         brltty_mkshr_gcc_make="NOT_SUPPORTED"
+         ;;
+   esac
+
+   if test "${GCC}" = "yes"
+   then
+      brltty_cv_prog_make_object_shared="\$(CC) ${brltty_mkshr_gcc_make=-shared} BRLTTY_OPTIONS_LD2CC([${brltty_mkshr_ld_options}])"
+   else
+      brltty_cv_prog_make_object_shared="\$(LD) ${brltty_mkshr_ld_make} ${brltty_mkshr_ld_options}"
+   fi
+
+   brltty_cv_prog_make_object_shared="${brltty_cv_prog_make_object_shared} ${LDFLAGS} -o"
+])
+AC_SUBST([MKSHR], ["${brltty_cv_prog_make_object_shared}"])
+
+AC_CACHE_CHECK([for make relocatable object command], [brltty_cv_prog_make_object_relocatable], [dnl
+case "${host_os}"
+in
+   *)
+      brltty_cv_prog_make_object_relocatable="\$(LD) -r -o"
+      ;;
+esac])
+MKREL="${brltty_cv_prog_make_object_relocatable}"
+AC_SUBST([MKREL])
+
+AC_CACHE_CHECK([for dynamic library creation command], [brltty_cv_prog_mklib], [dnl
+case "${host_os}"
+in
+   linux*|gnu*|openbsd*|freebsd*|kfreebsd*|netbsd*|*qnx*|elf*|haiku*)
+      brltty_mklib_ld_make="-shared"
+      brltty_mklib_ld_options="-soname"
+      ;;
+   solaris*)
+      brltty_mklib_ld_make="-G"
+      brltty_mklib_ld_options="-h"
+      ;;
+   hpux*)
+      brltty_mklib_ld_make="-b"
+      brltty_mklib_ld_options="+h"
+      ;;
+   osf*)
+      brltty_mklib_ld_make="-shared"
+      brltty_mklib_ld_options="-expect_unresolved '*' -soname"
+      ;;
+   cygwin*|mingw*)
+      brltty_mklib_ld_make="-shared"
+      brltty_mklib_ld_options="--out-implib implib.a --output-def lib.def -soname"
+      ;;
+   msdos*)
+      brltty_mklib_ld_make="not_supported_on_msdos"
+      brltty_mklib_ld_options="not_supported_on_msdos"
+      brltty_mklib_gcc_make="not_supported_on_msdos"
+      ;;
+   darwin*)
+      dylibFound=false
+      dylibPath=/usr/lib/dylib1.o
+      set -- "${dylibPath}"
+
+      sdkRootPrefix="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX"
+      sdkRootSuffix=".sdk/${dylibPath}"
+      sdkVersion=`sw_vers -productVersion`
+
+      while true
+      do
+         set -- "${@}" "${sdkRootPrefix}${sdkVersion}${sdkRootSuffix}"
+         sdkNext="${sdkVersion%.*}"
+         test "${sdkNext}" != "${sdkVersion}" || break
+         sdkVersion="${sdkNext}"
+      done
+
+      set -- "${@}" "${sdkRootPrefix}"*"${sdkRootSuffix}"
+      for sdkPath
+      do
+         test ! -f "${sdkPath}" || {
+            dylibPath="${sdkPath}"
+            dylibFound=true
+            break
+         }
+      done
+      "${dylibFound}" || AC_MSG_WARN([cannot find ${dylibPath}])
+
+      brltty_mklib_ld_make="-dylib"
+      brltty_mklib_ld_options="${dylibPath} -single_module -install_name"
+      brltty_mklib_gcc_make="-dynamiclib"
+      ;;
+   *)
+      AC_MSG_ERROR([dynamic library creation command not configured for ${host_os}])
+      ;;
+esac
+if test "${GCC}" = "yes"
+then
+   brltty_cv_prog_mklib="\$(CC) \$(LDFLAGS) ${brltty_mklib_gcc_make=-shared} BRLTTY_OPTIONS_LD2CC([${brltty_mklib_ld_options+${brltty_mklib_ld_options} <name>}]) -o"
+else
+   brltty_cv_prog_mklib="\$(LD) \$(LDFLAGS) ${brltty_mklib_ld_make} ${brltty_mklib_ld_options+${brltty_mklib_ld_options} <name>} -o"
+fi])
+AC_SUBST([MKLIB], ["${brltty_cv_prog_mklib}"])
+
+AC_CACHE_CHECK([for configure shared object directory command], [brltty_cv_prog_conflibdir], [dnl
+case "${host_os}"
+in
+   linux*|gnu*|kfreebsd*|elf*)
+      brltty_cv_prog_conflibdir="/sbin/ldconfig -n"
+      ;;
+   *)
+      brltty_cv_prog_conflibdir=":"
+      ;;
+esac])
+AC_SUBST([CONFLIBDIR], ["${brltty_cv_prog_conflibdir}"])
+
+case "${host_os}"
+in
+   mingw*)
+      BRLTTY_HAVE_WINDOWS_LIBRARY([ws2_32])
+      BRLTTY_HAVE_WINDOWS_LIBRARY([gdi32])
+      BRLTTY_HAVE_WINDOWS_LIBRARY([winmm])
+      ;;
+   cygwin*)
+      BRLTTY_HAVE_WINDOWS_LIBRARY([gdi32])
+      ;;
+   *)
+      AC_SEARCH_LIBS([socket], [socket network])
+      AC_SEARCH_LIBS([inet_ntoa], [nsl])
+      ;;
+esac
+
+install_messages=
+BRLTTY_ARG_DISABLE(
+   [i18n],
+   [support for internationalization],
+   [],
+[dnl
+   test "${MSGMERGE}" = "false" || {
+      install_messages=install-messages
+
+      AC_CHECK_HEADER([libintl.h], [dnl
+         BRLTTY_HAVE_LIBRARY([intl])
+         AC_DEFINE([ENABLE_I18N_SUPPORT], [1],
+                   [Define this if internationalization support is to be included.])
+      ])
+   }
+])
+AC_SUBST([install_messages])
+
+AC_CHECK_FUNC([wcslen], [AC_CHECK_HEADERS([wchar.h], [dnl
+   AC_CHECK_FUNCS([wcwidth])
+])])
+
+AC_CHECK_HEADERS([glob.h], [dnl
+   AC_CHECK_FUNCS([glob])
+])
+
+AC_CHECK_HEADERS([langinfo.h], [dnl
+   AC_CHECK_FUNCS([nl_langinfo])
+])
+
+icu_includes=""
+icu_libs=""
+BRLTTY_ARG_DISABLE(
+   [icu],
+   [support for Unicode-based internationalization],
+   [],
+[dnl
+   BRLTTY_HAVE_PACKAGE([icu], [icu-uc], [dnl
+      AC_CHECK_HEADERS([unicode/unorm2.h])
+      AC_DEFINE_UNQUOTED([HAVE_ICU], [1],
+                         [Define this if Unicode-based internationalization support is to be included.])
+
+      if test "${brltty_standalone_programs}" = "yes"
+      then
+         icu_libs="`echo ${icu_libs} | sed -e 's/\(-l\)\(icu\)/\1s\2/g'`"
+      fi
+   ])
+])
+AC_SUBST([icu_includes])
+AC_SUBST([icu_libs])
+
+BRLTTY_ARG_DISABLE(
+   [iconv],
+   [support for character encoding conversion],
+   [],
+[dnl
+   AC_CHECK_HEADERS([iconv.h], [
+      BRLTTY_HAVE_LIBRARY([iconv])
+   ])
+])
+
+BRLTTY_ARG_DISABLE(
+   [polkit],
+   [support for the Polkit authorization manager],
+   [],
+[dnl
+   BRLTTY_HAVE_PACKAGE([polkit], [polkit-gobject-1], [dnl
+      AC_DEFINE(HAVE_POLKIT, 1, [if the Polkit authorization manager should be used])
+   ])
+])
+
+BRLTTY_ARG_DISABLE(
+   [api],
+   [the application programming interface],
+   [],
+[dnl
+   BRLTTY_IF_PTHREADS([dnl
+      AC_DEFINE([ENABLE_API], [1],
+                [Define this if the application programming interface is to be included.])
+      BRLTTY_ARG_PARAMETERS([api], [application programming interface])
+
+      api_server_objects='brlapi_server.$O brlapi_keyranges.$O auth.$O'
+      api_libraries="${LIBS}"
+
+      all_api="all-api"
+      build_api="api"
+      install_api="install-api"
+      api_ref='$(API_LIBS)'
+
+      api_dynamic_library="api-dynamic-library"
+      install_api_libraries="install-api-libraries"
+      uninstall_api_libraries="uninstall-api-libraries"
+      default_api_socket_path="${localstatedir}/lib/BrlAPI"
+
+      case "${host_os}"
+      in
+         cygwin*|mingw*)
+            AC_DEFINE([BRLAPI_WIN32], [1],
+                      [Define this if the Win32 programming interface is available.])
+            default_api_socket_path='\\\\.\\pipe\\BrlAPI'
+            api_dynamic_library="api-dynamic-library-windows"
+            install_api_libraries="install-api-libraries-windows"
+            uninstall_api_libraries="uninstall-api-libraries-windows"
+            ;;
+      esac
+
+      BRLTTY_ARG_WITH(
+          [api-socket-path], [PATH],
+          [where local sockets for teh api are stored],
+          [api_socket_path], ["${default_api_socket_path}"]
+      )
+
+      BRLTTY_BINDINGS([Emacs])
+      BRLTTY_BINDINGS([Java])
+      BRLTTY_BINDINGS([Lisp])
+      BRLTTY_BINDINGS([Lua])
+      BRLTTY_BINDINGS([OCaml])
+      BRLTTY_BINDINGS([Python])
+      BRLTTY_BINDINGS([Tcl])
+
+      test -n "${api_bindings}" && {
+         for brltty_bindings_directory in ${api_bindings}
+         do
+            brltty_build_directories="${brltty_build_directories} Bindings/${brltty_bindings_directory}"
+         done
+
+         all_api_bindings="all-api-bindings"
+         install_api_bindings="install-api-bindings"
+      }
+   ])
+])
+AC_SUBST([api_server_objects])
+AC_SUBST([api_libraries])
+AC_SUBST([api_bindings])
+AC_SUBST([all_api_bindings])
+AC_SUBST([install_api_bindings])
+AC_SUBST([all_api])
+AC_SUBST([build_api])
+AC_SUBST([install_api])
+AC_SUBST([api_ref])
+AC_SUBST([api_dynamic_library])
+AC_SUBST([install_api_libraries])
+AC_SUBST([uninstall_api_libraries])
+AC_SUBST([api_authkeyfile], [brlapi.key])
+AC_SUBST([api_socket_path])
+BRLTTY_SUMMARY_ITEM([api-socket-path], [api_socket_path])
+BRLTTY_DEFINE_EXPANDED([BRLAPI_AUTHKEYFILE], ["${api_authkeyfile}"],
+                       [Define this to be a string containing the name of BrlAPI's authorization key file.])
+
+BRLTTY_ARG_ENABLE(
+   [api-fuzzing],
+   [fuzzing the application programming interface],
+   [],
+[dnl
+  AC_CACHE_CHECK([for LLVM fuzzer], [brltty_cv_llvm_fuzzer], [dnl
+    HOST_CPU="`$CC -print-multiarch | sed -e 's/-.*//'`"
+    fuzzer_libs="`$CC -print-runtime-dir`/libclang_rt.fuzzer_no_main-$HOST_CPU.a -lstdc++ -lm"
+    CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link -fsanitize=address,undefined -fstack-protector-strong"
+    LIBS="$LIBS -fsanitize=address,undefined -lubsan"
+    OLD_LIBS="$LIBS"
+    LIBS="$LIBS $fuzzer_libs"
+    AC_COMPILE_IFELSE(
+      [AC_LANG_PROGRAM([[]], [[]])],
+      [], [AC_MSG_ERROR([Compiler does not support fuzzing, did you set CC=clang?])])
+    LIBS="$OLD_LIBS"
+  ])
+  AC_DEFINE([ENABLE_API_FUZZING], [1],
+    [Define this if the application programming interface is to be fuzzed.])
+])
+AC_SUBST(fuzzer_libs)
+
+brltty_curses_libs_save="${LIBS}"
+BRLTTY_PACKAGE_CHOOSE([curses],
+   [ncursesw ncursesw/ncurses.h],
+   [pdcursesw curses.h],
+   [ncurses ncurses.h],
+   [pdcursesu curses.h],
+   [curses curses.h],
+   [pdcurses curses.h])
+if test -n "${curses_package}"
+then
+   AC_CHECK_LIB([tinfo], [intrflush])
+fi
+curses_libs="${LIBS%${brltty_curses_libs_save}}"
+LIBS="${brltty_curses_libs_save}"
+AC_SUBST([curses_libs])
+
+xkb_libs=""
+xtk_libs=""
+all_xbrlapi=""
+install_xbrlapi=""
+xsel_object=""
+BRLTTY_ARG_DISABLE(
+   [x],
+   [support for X11],
+   [],
+[dnl
+   BRLTTY_HAVE_PACKAGE([x11], [x11], [dnl
+      all_xbrlapi="all-xbrlapi"
+      install_xbrlapi="install-xbrlapi"
+      xsel_object='xsel.$O'
+
+      AC_CHECK_HEADERS([X11/keysym.h])
+
+      BRLTTY_HAVE_PACKAGE([xext], [xext], [dnl
+         xkb_libs="${xext_libs} ${xkb_libs}"
+
+         BRLTTY_HAVE_PACKAGE([xtst], [xtst], [dnl
+            xkb_libs="${xtst_libs} ${xkb_libs}"
+             
+            AC_CHECK_HEADERS([X11/extensions/XTest.h])
+            AC_CHECK_HEADERS([X11/extensions/XKB.h])
+         ])
+
+         BRLTTY_HAVE_PACKAGE([xfixes], [xfixes], [dnl
+            AC_CHECK_HEADERS([X11/extensions/Xfixes.h])
+         ])
+      ])
+
+      brltty_cppflags_save="${CPPFLAGS}"
+      CPPFLAGS="${CPPFLAGS} ${x11_includes}"
+      brltty_libs_save="${LIBS}"
+      LIBS="${x11_libs} ${LIBS}"
+
+      AC_CHECK_FUNCS([XSetIOErrorExitHandler])
+
+      CPPFLAGS="${brltty_cppflags_save}"
+      LIBS="${brltty_libs_save}"
+
+      BRLTTY_HAVE_PACKAGE([xt], [xt], [dnl
+         xtk_libs="${xt_libs} ${xtk_libs}"
+
+         brltty_cppflags_save="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${x11_includes}"
+
+         brltty_libs_save="${LIBS}"
+         LIBS="${xtk_libs} ${x11_libs} ${LIBS}"
+
+         BRLTTY_PACKAGE_CHOOSE([gui_toolkit],
+            [Xaw X11/Xaw/Form.h X11/Xaw/Paned.h X11/Xaw/Label.h X11/Xaw/Command.h X11/Xaw/Repeater.h],
+            [Xaw3d X11/Xaw3d/Form.h X11/Xaw3d/Paned.h X11/Xaw3d/Label.h X11/Xaw3d/Command.h X11/Xaw3d/Repeater.h],
+            [neXtaw X11/neXtaw/Form.h X11/neXtaw/Paned.h X11/neXtaw/Label.h X11/neXtaw/Command.h X11/neXtaw/Repeater.h],
+            [XawPlus X11/XawPlus/Form.h X11/XawPlus/Paned.h X11/XawPlus/Label.h X11/XawPlus/Command.h X11/XawPlus/Repeater.h],
+            [Xm Xm/Xm.h Xm/Form.h Xm/PanedW.h Xm/Label.h Xm/PushB.h])
+
+         xtk_libs="${LIBS%${x11_libs} ${brltty_libs_save}}"
+
+         CPPFLAGS="${brltty_cppflags_save}"
+         LIBS="${brltty_libs_save}"
+      ])
+   ])
+])
+AC_SUBST([xkb_libs])
+AC_SUBST([xtk_libs])
+AC_SUBST([all_xbrlapi])
+AC_SUBST([install_xbrlapi])
+AC_SUBST([xsel_object])
+
+test -z "$gui_toolkit_package" && {
+   case "${host_os}"
+   in
+      cygwin*|mingw*)
+         gui_toolkit_package="windows"
+         ;;
+   esac
+}
+
+AC_CHECK_FUNCS([time stime gettimeofday settimeofday nanosleep])
+BRLTTY_CHECK_FUNCTION([clock_gettime], [time.h], [rt])
+BRLTTY_CHECK_FUNCTION([clock_settime], [time.h], [rt])
+
+BRLTTY_CHECK_FUNCTION([pthread_getname_np], [pthread.h], [pthread])
+BRLTTY_CHECK_FUNCTION([pthread_atfork], [pthread.h], [pthread])
+
+AC_CHECK_DECLS([localtime_r], [], [], [dnl
+#include <time.h>
+])
+
+AC_CHECK_HEADERS([sys/poll.h sys/select.h sys/wait.h])
+AC_CHECK_FUNCS([select])
+AC_CHECK_FUNCS([poll])
+
+AC_CHECK_HEADERS([sys/capability.h sys/prctl.h sched.h])
+AC_CHECK_HEADERS([linux/seccomp.h linux/filter.h linux/audit.h])
+
+AC_CHECK_HEADERS([signal.h sys/signalfd.h])
+AC_CHECK_FUNCS([sigaction])
+
+AC_CHECK_HEADERS([alloca.h getopt.h regex.h])
+AC_CHECK_HEADERS([syslog.h])
+AC_CHECK_HEADERS([sys/file.h sys/socket.h])
+AC_CHECK_HEADERS([pwd.h grp.h])
+AC_CHECK_HEADERS([sys/io.h sys/modem.h machine/speaker.h dev/speaker/speaker.h linux/vt.h])
+AC_CHECK_HEADERS([sdkddkver.h])
+
+AC_CHECK_HEADERS([execinfo.h], [BRLTTY_HAVE_LIBRARY([execinfo])])
+
+AC_CHECK_HEADERS([linux/input.h], [
+   AC_CHECK_HEADERS([linux/uinput.h], [], [], [
+#include <linux/input.h>
+])])
+
+AC_CHECK_FUNCS([getopt_long hstrerror vsyslog])
+AC_CHECK_FUNCS([realpath readlink])
+AC_CHECK_FUNCS([pause])
+AC_CHECK_FUNCS([fchdir fchmod])
+AC_CHECK_FUNCS([shmget shm_open])
+AC_CHECK_FUNCS([getpeereid getpeerucred getzoneid])
+AC_CHECK_FUNCS([mempcpy wmempcpy])
+
+case "${host_os}"
+in
+   cygwin*|mingw*)
+      if test "${brltty_cv_dll_ws2_32}" = "yes"
+      then
+         BRLTTY_HAVE_WINDOWS_FUNCTION([getaddrinfo], [ws2_32])
+         BRLTTY_HAVE_WINDOWS_FUNCTION([getnameinfo], [ws2_32])
+         AC_CHECK_FUNCS([gai_strerror])
+      fi
+      AC_CHECK_TYPES([PROCESS_INFORMATION_CLASS], [], [], [
+#include <windows.h>
+])
+      AC_CHECK_DECLS([ProcessUserModeIOPL], [], [], [
+#include <windows.h>
+])
+      ;;
+   *)
+      AC_CHECK_FUNCS([gai_strerror getaddrinfo getnameinfo])
+      ;;
+esac
+
+AC_CHECK_SIZEOF([wchar_t], [], [
+#include <wchar.h>
+])
+AC_DEFINE_UNQUOTED([SIZEOF_WCHAR_T_STR], ["${ac_cv_sizeof_wchar_t}"],
+                   [Define this to be a string containing the size of the wchar_t type.])
+
+BRLTTY_IF_PTHREADS([dnl
+   AC_DEFINE([HAVE_POSIX_THREADS], [1],
+             [Define this if posix threads are supported.])
+])
+
+AC_C_BIGENDIAN
+AC_C_CONST
+AC_C_INLINE
+AC_OBJEXT
+AC_EXEEXT
+
+AC_CACHE_CHECK(
+   [if the compiler understands the __alignof__ operator],
+   [brltty_cv_c_operator_alignof],
+   [
+      AC_COMPILE_IFELSE(
+         [
+            AC_LANG_PROGRAM(
+               [[
+                  int a;
+                  int b = __alignof__(a);
+               ]], [[
+               ]]
+            )
+         ], [
+            brltty_cv_c_operator_alignof=yes
+         ], [
+            brltty_cv_c_operator_alignof=no
+         ]
+      )
+   ]
+)
+if test "${brltty_cv_c_operator_alignof}" = "yes"
+then
+   AC_DEFINE([HAVE_OPERATOR_ALIGNOF],  [1],
+             [Define this if the __alignof__ operator is supported.])
+fi
+
+AC_CACHE_CHECK(
+   [if the compiler supports thread-local variables],
+   [brltty_cv_c_thread_local],
+   [
+      brltty_cv_c_thread_local=""
+      for thread_local_qualifier in "_Thread_local" "__thread" "__declspec(thread)"
+      do
+         AC_COMPILE_IFELSE(
+            [
+               AC_LANG_PROGRAM(
+                  [[
+                     static ${thread_local_qualifier} int i;
+                  ]], [[
+                  ]]
+               )
+            ], [
+               brltty_cv_c_thread_local="${thread_local_qualifier}"
+               break
+            ]
+         )
+      done
+   ]
+)
+if test -n "${brltty_cv_c_thread_local}"
+then
+   AC_DEFINE_UNQUOTED([THREAD_LOCAL], [${brltty_cv_c_thread_local}],
+                      [Define this if thread-local variables are supported.])
+fi
+
+AC_SUBST([archive_prefix], [lib])
+AC_SUBST([archive_extension], [a])
+
+case "${host_os}"
+in
+   cygwin*)
+      library_prefix="cyg"
+      ;;
+   mingw*)
+      library_prefix=""
+      ;;
+   *)
+      library_prefix="${archive_prefix}"
+      ;;
+esac
+AC_SUBST([library_prefix])
+
+case "${host_os}"
+in
+   hpux*)
+      library_extension="sl"
+      ;;
+   cygwin*|mingw*)
+      library_extension="dll"
+      ;;
+   darwin*)
+      library_extension="dylib"
+      ;;
+   *)
+      library_extension="so"
+      ;;
+esac
+AC_SUBST([library_extension])
+AC_DEFINE_UNQUOTED([LIBRARY_EXTENSION], ["${library_extension}"],
+                   [Define this to be a string containing the library extension.])
+
+AC_SUBST([module_name], ["${library_prefix}${PACKAGE_TARNAME}"])
+AC_DEFINE_UNQUOTED([MODULE_NAME], ["${module_name}"],
+                   [Define this to be a string containing the base module name.])
+
+case "${host_os}"
+in
+   darwin*)
+      module_extension="bundle"
+      ;;
+   *)
+      module_extension="${library_extension}"
+      ;;
+esac
+AC_SUBST([module_extension])
+AC_DEFINE_UNQUOTED([MODULE_EXTENSION], ["${module_extension}"],
+                   [Define this to be a string containing the module extension.])
+
+AC_CACHE_CHECK([for static archive extension], [brltty_cv_ext_arc], [dnl
+case "${host_os}"
+in
+   *)
+      brltty_cv_ext_arc="a"
+      ;;
+esac])
+archive_extension="${brltty_cv_ext_arc}"
+AC_SUBST([archive_extension])
+
+AC_CACHE_CHECK([for shared object run-time search path environment variable], [brltty_cv_env_libsearch], [dnl
+case "${host_os}"
+in
+   hpux*)
+      brltty_cv_env_libsearch="SHLIB_PATH"
+      ;;
+   darwin*)
+      brltty_cv_env_libsearch="DYLD_LIBRARY_PATH"
+      ;;
+   *)
+      brltty_cv_env_libsearch="LD_LIBRARY_PATH"
+      ;;
+esac])
+libsearch_variable="${brltty_cv_env_libsearch}"
+AC_SUBST([libsearch_variable])
+
+BRLTTY_PKGCONFIG_VARIABLE([POLKIT_POLICY_DIR], [polkit-gobject-1], [policydir], [/usr/share/polkit-1/actions])
+BRLTTY_PKGCONFIG_VARIABLE([POLKIT_RULE_DIR], [polkit-gobject-1], [ruledir], [/usr/share/polkit-1/rules.d])
+BRLTTY_PKGCONFIG_VARIABLE([SYSTEMD_SYSUSERS_DIR], [systemd], [sysusersdir], [/usr/lib/sysusers.d])
+BRLTTY_PKGCONFIG_VARIABLE([SYSTEMD_TMPFILES_DIR], [systemd], [tmpfilesdir], [/usr/lib/tmpfiles.d])
+BRLTTY_PKGCONFIG_VARIABLE([SYSTEMD_UNIT_DIR], [systemd], [systemdsystemunitdir], [/usr/lib/systemd/system])
+BRLTTY_PKGCONFIG_VARIABLE([SYSTEMD_CONF_DIR], [systemd], [systemdsystemconfdir], [/usr/lib/systemd/system])
+
+BRLTTY_ARG_PACKAGE([system], [base system], [], [dnl
+   *android*)
+      system_package="java"
+      system_libs="-llog"
+      ;;
+   *qnx*)
+      AC_CHECK_LIB([asound], [snd_pcm_open])
+      ;;
+   cygwin*)
+      system_package="windows"
+      ;;
+   darwin*)
+      system_package="darwin"
+      system_includes="-x objective-c"
+      system_libs="-framework IOKit -framework Foundation -framework CoreFoundation -lobjc"
+      ;;
+   hpux*)
+      if test -d /opt/audio/include
+      then
+         CPPFLAGS="${CPPFLAGS} -I/opt/audio/include"
+         system_libs="-L/opt/audio/lib -lAlib"
+         AC_DEFINE([HAVE_HPUX_AUDIO], [1],
+                   [Define this if HP-UX audio support is available.])
+      fi
+      ;;
+   linux*)
+      system_package="linux"
+      ;;
+   mingw*)
+      system_package="windows"
+      ;;
+   msdos*)
+      system_package="msdos"
+      system_libs="-ldbg"
+      ;;
+])
+
+expat_includes=""
+expat_libs=""
+BRLTTY_ARG_DISABLE(
+   [expat],
+   [support for XML parsing (CLDR)],
+   [],
+[dnl
+   BRLTTY_HAVE_PACKAGE([expat], [expat], [dnl
+      AC_DEFINE_UNQUOTED([HAVE_EXPAT], [1],
+                         [Define this if XML parsing support is to be included.])
+   ])
+])
+AC_SUBST([expat_includes])
+AC_SUBST([expat_libs])
+
+contracted_braille_objects='ctb_compile.$O cldr.$O ctb_translate.$O ctb_native.$O ctb_external.$O'
+louis_includes=""
+louis_libs=""
+BRLTTY_ARG_DISABLE(
+   [liblouis],
+   [support for LibLouis-based contracted braille],
+   [],
+[dnl
+   BRLTTY_HAVE_PACKAGE([louis], [liblouis], [dnl
+      louisTablesDirectory=`${PKG_CONFIG} --variable=tablesdir ${louis_package}`
+      if test -n "${louisTablesDirectory}"
+      then
+         AC_SUBST([louis_tables_directory], ["${louisTablesDirectory}"])
+         AC_DEFINE_UNQUOTED([LOUIS_TABLES_DIRECTORY], ["${louisTablesDirectory}"],
+                            [Define this to be a string containing the path to the LibLouis tables directory.])
+         contracted_braille_objects="${contracted_braille_objects} ctb_louis.\$O"
+      fi
+   ])
+])
+AC_SUBST([louis_includes])
+AC_SUBST([louis_libs])
+AC_SUBST([contracted_braille_objects])
+
+BRLTTY_ARG_DISABLE(
+   [gpm],
+   [mouse tracking via GPM],
+   [],
+[dnl
+   AC_CHECK_LIB([gpm], [Gpm_Open])
+])
+
+case "${host_os}"
+in
+   hpux*)
+      BRLTTY_SEARCH_LIBS([shl_load], [dld])
+      ;;
+   *)
+      ;;
+esac
+
+BRLTTY_ARG_PACKAGE([pgmpath], [program path], [], [dnl
+   cygwin*)
+      pgmpath_package="windows"
+      ;;
+   freebsd*)
+      pgmpath_package="freebsd"
+      ;;
+   linux*)
+      pgmpath_package="linux"
+      ;;
+   gnu*)
+      pgmpath_package="hurd"
+      ;;
+   mingw*)
+      pgmpath_package="windows"
+      ;;
+   solaris*)
+      pgmpath_package="solaris"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([pgmprivs], [program privileges], [], [dnl
+   linux*)
+      pgmprivs_package="linux"
+      BRLTTY_HAVE_LIBRARY([cap])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([service], [system service], [libsystemd], [dnl
+   mingw*)
+      service_package="windows"
+      ;;
+])
+
+AC_SUBST([SYSTEMD_SERVICE_TYPE], ["forking"])
+AC_SUBST([SYSTEMD_SERVICE_ARGUMENTS], [""])
+
+case "${service_package}"
+in
+   libsystemd)
+      SYSTEMD_SERVICE_TYPE="notify"
+      SYSTEMD_SERVICE_ARGUMENTS="-n"
+      ;;
+esac
+
+BRLTTY_HAVE_PACKAGE([systemd], [libsystemd], [
+   brltty_libs_save="${LIBS}"
+   LIBS="${LIBS} ${systemd_libs}"
+   AC_CHECK_FUNC([sd_session_get_vt], [dnl
+      AC_DEFINE([HAVE_SD_SESSION_GET_VT], [1],
+                [Define this if the function sd_session_get_vt is available in libsystemd.])
+      api_libraries="${api_libraries} ${systemd_libs}"
+   ], [dnl
+      AC_MSG_WARN([function not available: systemd:sd_session_get_vt()])
+   ])
+   LIBS="${brltty_libs_save}"
+], [:])
+
+BRLTTY_ARG_PACKAGE([params], [boot parameters], [], [dnl
+   linux*)
+      params_package="linux"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([dynld], [dynamic loading], [dlfcn dyld], [dnl
+   elf*)
+      dynld_package="grub"
+      ;;
+   hpux*)
+      dynld_package="shl"
+      ;;
+   mingw*)
+      dynld_package="windows"
+      ;;
+], [dnl
+   dlfcn)
+      AC_CHECK_HEADERS([dlfcn.h], [
+         AC_SEARCH_LIBS([dlopen], [dl], [dnl
+            dynld_package="dlfcn"
+         ])
+      ])
+      ;;
+   dyld)
+      AC_CHECK_HEADERS([mach-o/dyld.h], [
+         dynld_package="dyld"
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([rgx], [regular expression], [libpcre2-32 libpcre32], [dnl
+])
+
+BRLTTY_ARG_PACKAGE([charset], [character set], [iconv], [dnl
+   elf*)
+      charset_package=grub
+      ;;
+   mingw*)
+      charset_package=windows
+      ;;
+   msdos*)
+      charset_package=msdos
+      ;;
+], [dnl
+   iconv)
+      if test "${brltty_enabled_iconv}" = "yes"
+      then
+         AC_CHECK_HEADER([iconv.h], [dnl
+            charset_package=iconv
+         ])
+      fi
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([hostcmd], [host command], [unix], [dnl
+   mingw*)
+      hostcmd_package=windows
+      ;;
+], [dnl
+   unix)
+      AC_CHECK_DECL([fork], [dnl
+         AC_CHECK_HEADER([signal.h], [dnl
+            AC_CHECK_DECL([SIGCHLD], [dnl
+               hostcmd_package=unix
+            ], [], [dnl
+#include <signal.h>
+            ])
+         ])
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([mntpt], [mount point], [mntent mnttab], [dnl
+], [dnl
+   mntent)
+      AC_CHECK_HEADER([mntent.h], [dnl
+         AC_CHECK_FUNC([setmntent], [dnl
+            mntpt_package=mntent
+            AC_CHECK_FUNCS([addmntent])
+         ])
+      ])
+      ;;
+   mnttab)
+      AC_CHECK_HEADER([sys/mnttab.h], [dnl
+         mntpt_package=mnttab
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([mntfs], [mount file system], [], [dnl
+   linux*)
+      mntfs_package="linux"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([kbd], [keyboard], [], [dnl
+   *android*) kbd_package="android";;
+   linux*) kbd_package="linux";;
+])
+
+BRLTTY_ARG_PACKAGE([bell], [console bell], [], [dnl
+   linux*)
+      bell_package="linux"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([leds], [keyboard LEDs], [], [dnl
+   linux*)
+      leds_package="linux"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([beep], [beeper], [], [dnl
+   *android*)
+      beep_package="none"
+      ;;
+   cygwin*)
+      beep_package="windows"
+      ;;
+   freebsd*)
+      beep_package="spkr"
+      ;;
+   kfreebsd*)
+      beep_package="spkr"
+      ;;
+   linux*)
+      beep_package="linux"
+      ;;
+   mingw*)
+      beep_package="windows"
+      ;;
+   msdos*)
+      beep_package="msdos"
+      ;;
+   netbsd*)
+      beep_package="wskbd"
+      ;;
+   openbsd*)
+      beep_package="wskbd"
+      ;;
+   solaris*)
+      beep_package="solaris"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([pcm], [PCm], [alsa oss], [dnl
+   *android*)
+      pcm_package="android"
+      ;;
+   *qnx*)
+      pcm_package="qsa"
+      ;;
+   hpux*)
+      pcm_package="hpux"
+      ;;
+   mingw*)
+      pcm_package="windows"
+      ;;
+   msdos*)
+      pcm_package="none"
+      ;;
+   netbsd*)
+      pcm_package="audio"
+      ;;
+   openbsd*)
+      pcm_package="audio"
+      ;;
+   solaris*)
+      pcm_package="audio"
+      ;;
+], [dnl
+   oss)
+      AC_CHECK_HEADER([sys/soundcard.h], [dnl
+         pcm_package="oss"
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([midi], [MIDI], [alsa oss], [dnl
+   darwin*)
+      midi_package="darwin"
+      midi_libs="-framework AudioUnit -framework AudioToolbox"
+      ;;
+   mingw*)
+      midi_package="windows"
+      ;;
+   msdos*)
+      midi_package="none"
+      ;;
+], [dnl
+   oss)
+      AC_CHECK_HEADER([sys/soundcard.h], [dnl
+         midi_package="oss"
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([fm], [FM], [adlib], [dnl
+], [dnl
+   adlib)
+      fm_package="adlib"
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([serial], [serial I/O], [termios], [dnl
+   elf*)
+      serial_package="grub"
+      ;;
+   mingw*)
+      serial_package="windows"
+      ;;
+   msdos*)
+      serial_package="msdos"
+      ;;
+], [dnl
+   termios)
+      AC_CHECK_HEADER([termios.h], [dnl
+         serial_package="termios"
+         AC_CHECK_FUNCS([tcdrain])
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([usb], [USB I/O], [winusb libusb-1.0 libusb], [dnl
+   *android*)
+      usb_package="android"
+      ;;
+   darwin*)
+      usb_package="darwin"
+      usb_includes="-x objective-c"
+      ;;
+   elf*)
+      usb_package="grub"
+      ;;
+   freebsd*)
+      usb_package="freebsd"
+      ;;
+   kfreebsd*)
+      usb_package="kfreebsd"
+      AC_CHECK_HEADERS([legacy/dev/usb/usb.h])
+      ;;
+   linux*)
+      usb_package="linux"
+      ;;
+   netbsd*)
+      usb_package="netbsd"
+      ;;
+   openbsd*)
+      usb_package="openbsd"
+      ;;
+   solaris*)
+      usb_package="solaris"
+      BRLTTY_HAVE_LIBRARY([aio])
+      BRLTTY_HAVE_LIBRARY([rt])
+      ;;
+], [dnl
+   winusb)
+      BRLTTY_HAVE_HEADER([winusb.h], [dnl
+         usb_package="winusb"
+         usb_libs="-lwinusb"
+      ])
+      ;;
+   libusb)
+      BRLTTY_HAVE_HEADER([usb.h], [dnl
+         usb_package="libusb"
+         usb_libs="-lusb"
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([bluetooth], [Bluetooth I/O], [], [dnl
+   *android*)
+      bluetooth_package="android"
+      ;;
+   darwin*)
+      bluetooth_package="darwin"
+      bluetooth_includes="-x objective-c"
+      bluetooth_libs="-framework IOBluetooth"
+      ;;
+   cygwin*|mingw*)
+      AC_CHECK_HEADER([ws2bth.h], [dnl
+         bluetooth_package="windows"
+         bluetooth_libs="-lbthprops -lws2_32"
+      ], , [[#include <windows.h>]])
+      ;;
+   linux*)
+      AC_CHECK_HEADER([bluetooth/bluetooth.h], [dnl
+         bluetooth_package="linux"
+         BRLTTY_HAVE_LIBRARY([bluetooth])
+
+         BRLTTY_IF_DBUS([dnl
+            bluetooth_includes="${dbus_includes}"
+            bluetooth_libs="${dbus_libs}"
+         ])
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([hid], [Human Interface Device I/O], [], [dnl
+   linux*)
+      AC_CHECK_HEADER([linux/hidraw.h], [dnl
+         BRLTTY_HAVE_PACKAGE([libudev], [libudev], [dnl
+            hid_package="linux"
+            hid_includes="${libudev_includes}"
+            hid_libs="${libudev_libs}"
+         ])
+         BRLTTY_HAVE_LIBRARY([bluetooth])
+      ])
+      ;;
+])
+
+BRLTTY_ARG_PACKAGE([ports], [I/O ports], [], [dnl
+   cygwin*)
+      ports_package="windows"
+      ;;
+   elf*)
+      ports_package="grub"
+      ;;
+   gnu*)
+      ports_package="glibc"
+      ;;
+   kfreebsd*)
+      ports_package="kfreebsd"
+      ;;
+   linux*)
+      AC_CHECK_FUNC([ioperm], [
+         ports_package="glibc"
+      ])
+      ;;
+   mingw*)
+      ports_package="windows"
+      ;;
+   msdos*)
+      ports_package="msdos"
+      ;;
+])
+
+BRLTTY_BRAILLE_DRIVER([al], [Alva])
+BRLTTY_BRAILLE_DRIVER([at], [Albatross])
+test -n "${api_server_objects}" && {
+   BRLTTY_BRAILLE_DRIVER([ba], [BrlAPI], [$(API_LIBS)])
+}
+BRLTTY_BRAILLE_DRIVER([bc], [BrailComm])
+BRLTTY_BRAILLE_DRIVER([bd], [Braudi])
+BRLTTY_BRAILLE_DRIVER([bl], [BrailleLite])
+BRLTTY_BRAILLE_DRIVER([bm], [Baum])
+BRLTTY_BRAILLE_DRIVER([bn], [BrailleNote])
+BRLTTY_BRAILLE_DRIVER([cb], [CombiBraille])
+BRLTTY_BRAILLE_DRIVER([ce], [Cebra])
+BRLTTY_BRAILLE_DRIVER([cn], [Canute])
+BRLTTY_BRAILLE_DRIVER([dp], [DotPad])
+BRLTTY_BRAILLE_DRIVER([ec], [EcoBraille])
+BRLTTY_BRAILLE_DRIVER([eu], [EuroBraille])
+BRLTTY_BRAILLE_DRIVER([fa], [FrankAudiodata])
+BRLTTY_BRAILLE_DRIVER([fs], [FreedomScientific])
+BRLTTY_BRAILLE_DRIVER([hd], [Hedo])
+BRLTTY_BRAILLE_DRIVER([hm], [HIMS])
+BRLTTY_BRAILLE_DRIVER([ht], [HandyTech])
+BRLTTY_BRAILLE_DRIVER([hw], [HumanWare])
+BRLTTY_BRAILLE_DRIVER([ir], [Iris])
+BRLTTY_BRAILLE_DRIVER([ic], [Inceptor])
+BRLTTY_IF_PACKAGE([Libbraille], [libbraille], [include/braille.h], [dnl
+   BRLTTY_BRAILLE_DRIVER([lb], [Libbraille], [-L$(LIBBRAILLE_ROOT)/lib -lbraille])
+])
+BRLTTY_BRAILLE_DRIVER([lt], [LogText])
+BRLTTY_BRAILLE_DRIVER([mb], [MultiBraille])
+BRLTTY_BRAILLE_DRIVER([md], [MDV])
+BRLTTY_BRAILLE_DRIVER([mm], [BrailleMemo])
+BRLTTY_BRAILLE_DRIVER([mn], [MiniBraille])
+BRLTTY_BRAILLE_DRIVER([mt], [Metec])
+BRLTTY_BRAILLE_DRIVER([np], [NinePoint])
+BRLTTY_BRAILLE_DRIVER([pg], [Pegasus])
+BRLTTY_BRAILLE_DRIVER([pm], [Papenmeier])
+BRLTTY_BRAILLE_DRIVER([sk], [Seika])
+BRLTTY_BRAILLE_DRIVER([tn], [TechniBraille])
+BRLTTY_BRAILLE_DRIVER([ts], [TSI])
+BRLTTY_BRAILLE_DRIVER([tt], [TTY], [$(CURSES_LIBS)])
+BRLTTY_BRAILLE_DRIVER([vd], [VideoBraille])
+BRLTTY_BRAILLE_DRIVER([vo], [Voyager])
+BRLTTY_BRAILLE_DRIVER([vr], [Virtual])
+BRLTTY_BRAILLE_DRIVER([vs], [VisioBraille])
+test -n "${gui_toolkit_package}" && {
+   BRLTTY_BRAILLE_DRIVER([xw], [XWindow], [$(XTK_LIBS) $(X11_LIBS)])
+}
+case "${host_os}"
+in
+   linux*)
+      BRLTTY_BRAILLE_DRIVER([bg], [B2G])
+      ;;
+   *) ;;
+esac
+BRLTTY_ARG_DRIVER([braille], [Braille])
+
+AC_CACHE_CHECK([for device directory], [brltty_cv_device_directory], [dnl
+case "${host_os}"
+in
+   mingw*)
+      brltty_cv_device_directory="//."
+      ;;
+   elf*|msdos*)
+      brltty_cv_device_directory=""
+      ;;
+   *)
+      brltty_cv_device_directory="/dev"
+      ;;
+esac])
+AC_DEFINE_UNQUOTED([DEVICE_DIRECTORY], ["${brltty_cv_device_directory}"],
+                   [Define this to be a string containing the path to the directory containing the devices.])
+
+AC_CACHE_CHECK([for first serial device], [brltty_cv_device_serial_first], [dnl
+case "${host_os}"
+in
+   linux*)
+      brltty_cv_device_serial_first="ttyS0"
+      ;;
+   gnu*)
+      brltty_cv_device_serial_first="com0"
+      ;;
+   solaris*)
+      brltty_cv_device_serial_first="ttya"
+      ;;
+   haiku*)
+      brltty_cv_device_serial_first="ports/pc_serial0"
+      ;;
+   hpux*)
+      brltty_cv_device_serial_first="tty0p0"
+      ;;
+   openbsd*)
+      brltty_cv_device_serial_first="cua00"
+      ;;
+   freebsd*)
+      brltty_cv_device_serial_first="cuaa0"
+      ;;
+   kfreebsd*)
+      brltty_cv_device_serial_first="cuaa0"
+      ;;
+   netbsd*)
+      brltty_cv_device_serial_first="dty00"
+      ;;
+   osf*)
+      brltty_cv_device_serial_first="tty00"
+      ;;
+   *qnx*)
+      brltty_cv_device_serial_first="ser1"
+      ;;
+   cygwin*)
+      brltty_cv_device_serial_first="ttyS0"
+      ;;
+   mingw*|msdos*)
+      brltty_cv_device_serial_first="COM1"
+      ;;
+   elf*)
+      brltty_cv_device_serial_first="com0"
+      ;;
+   *)
+      brltty_cv_device_serial_first=""
+      AC_MSG_WARN([primary serial device not configured for ${host_os}])
+      ;;
+esac])
+AC_SUBST([serial_first_device], [${brltty_cv_device_serial_first}])
+AC_DEFINE_UNQUOTED([SERIAL_FIRST_DEVICE], ["${serial_first_device}"],
+                   [Define this to be a string containing the path to the first serial device.])
+
+BRLTTY_ARG_WITH(
+   [braille-device], [DEVICE],
+   [default braille device],
+   [braille_device], ["yes"]
+)
+if test "${braille_device}" = "no"
+then
+   braille_device=""
+elif test "${braille_device}" = "yes"
+then
+   braille_device=""
+   test "${usb_package}" != "none" && braille_device="${braille_device},usb:"
+   test "${bluetooth_package}" != "none" && braille_device="${braille_device},bluetooth:"
+
+   if test -n "${braille_device}"
+   then
+      braille_device="${braille_device#,}"
+   else
+      braille_device="serial:${serial_first_device}"
+   fi
+fi
+AC_SUBST([braille_device])
+AC_DEFINE_UNQUOTED([BRAILLE_DEVICE], ["${braille_device}"],
+                   [Define this to be a string containing the path to the default braille device.])
+BRLTTY_SUMMARY_ITEM([braille-device], [braille_device])
+
+BRLTTY_ARG_TABLE([text], [en-nabcc], [ttb])
+BRLTTY_ARG_TABLE([contraction], [none], [ctb])
+BRLTTY_ARG_TABLE([attributes], [left_right], [atb])
+
+BRLTTY_ARG_DISABLE(
+   [speech-support],
+   [support for speech synthesizers and text-to-speech engines],
+   [],
+[dnl
+   AC_DEFINE([ENABLE_SPEECH_SUPPORT], [1],
+             [Define this to include speech synthesizer and text-to-speech engine support.])
+   speech_support_object='spk.$O'
+
+   BRLTTY_SPEECH_DRIVER([al], [Alva])
+   BRLTTY_SPEECH_DRIVER([bl], [BrailleLite])
+   BRLTTY_SPEECH_DRIVER([cb], [CombiBraille])
+
+   BRLTTY_IF_PACKAGE([eSpeak-NG], [espeak_ng], [include/espeak-ng/speak_lib.h], [dnl
+      BRLTTY_SPEECH_DRIVER([en], [eSpeak-NG], [-L$(ESPEAK_NG_ROOT)/lib -lespeak-ng])
+   ])
+
+   BRLTTY_IF_PACKAGE([eSpeak], [espeak], [include/espeak/speak_lib.h], [dnl
+      BRLTTY_SPEECH_DRIVER([es], [eSpeak], [-L$(ESPEAK_ROOT)/lib -lespeak])
+   ])
+
+   BRLTTY_IF_PACKAGE([FestivalLite], [flite], [include/flite/flite.h], [dnl
+      BRLTTY_ARG_REQUIRED(
+         [flite-language], [LANGUAGE],
+         [the FestivalLite language to use],
+         [flite_language], ["usenglish"]
+      )
+
+      BRLTTY_ARG_REQUIRED(
+         [flite-lexicon], [LEXICON],
+         [the FestivalLite lexicon to use],
+         [flite_lexicon], ["cmulex"]
+      )
+
+      BRLTTY_ARG_REQUIRED(
+         [flite-voice], [VOICE],
+         [the FestivalLite voice to use],
+         [flite_voice], ["cmu_us_kal"]
+      )
+
+      BRLTTY_SPEECH_DRIVER([fl], [FestivalLite], [-L$(FLITE_ROOT)/lib -lflite_$(FLITE_VOICE) -lflite_$(FLITE_LEXICON) -lflite_$(FLITE_LANGUAGE) -lflite -lm])
+   ])
+
+   BRLTTY_SPEECH_DRIVER([fv], [Festival])
+   BRLTTY_SPEECH_DRIVER([gs], [GenericSay])
+
+   BRLTTY_IF_PACKAGE([Mikropuhe], [mikropuhe], [mpwrfile.h], [dnl
+      BRLTTY_IF_PTHREADS([dnl
+         BRLTTY_SPEECH_DRIVER([mp], [Mikropuhe], [-L$(MPLINUX_ROOT) -lmplinux])
+      ])
+   ])
+
+   BRLTTY_IF_PACKAGE([speech-dispatcher], [speechd], [include/libspeechd.h], [dnl
+      BRLTTY_SPEECH_DRIVER([sd], [SpeechDispatcher])
+   ], [include], [lib], [speechd])
+
+   BRLTTY_IF_PACKAGE([Swift], [swift], [include/swift.h], [dnl
+      BRLTTY_SPEECH_DRIVER([sw], [Swift], [-L$(SWIFT_ROOT)/lib -lswift -lm])
+   ])
+
+   BRLTTY_IF_PACKAGE([Theta], [theta], [include/theta.h], [dnl
+      BRLTTY_SPEECH_DRIVER([th], [Theta], [-L$(THETA_ROOT)/lib -ltheta])
+   ])
+
+   AC_CHECK_HEADER([eci.h], [dnl
+      BRLTTY_SPEECH_DRIVER([vv], [ViaVoice], [-libmeci])
+   ])
+
+   BRLTTY_SPEECH_DRIVER([xs], [ExternalSpeech])
+
+   case "${host_os}"
+   in
+      *android*)
+         BRLTTY_SPEECH_DRIVER([an], [Android])
+         ;;
+      *) ;;
+   esac
+])
+AC_SUBST([speech_support_object])
+BRLTTY_ARG_DRIVER([speech], [Speech])
+
+case "${host_os}"
+in
+   *android*)
+      BRLTTY_SCREEN_DRIVER([an], [Android])
+      ;;
+   linux*)
+      BRLTTY_SCREEN_DRIVER([lx], [Linux])
+      ;;
+   gnu*)
+      BRLTTY_SCREEN_DRIVER([hd], [Hurd])
+      ;;
+   cygwin*|mingw*)
+      BRLTTY_SCREEN_DRIVER([wn], [Windows])
+      brltty_build_directories="${brltty_build_directories} Drivers/BrlAPI/WindowEyes"
+      ;;
+   msdos*)
+      BRLTTY_SCREEN_DRIVER([pb], [PcBios])
+      ;;
+   elf*)
+      BRLTTY_SCREEN_DRIVER([gb], [Grub])
+      ;;
+   *)
+      AC_MSG_WARN([no native screen driver for ${host_os}])
+      ;;
+esac
+
+BRLTTY_SCREEN_DRIVER([fv], [FileViewer])
+
+all_brltty_pty=""
+install_brltty_pty=""
+AC_CHECK_HEADER([sys/shm.h], [dnl
+   BRLTTY_SCREEN_DRIVER([sc], [Screen])
+
+   AC_CHECK_HEADER([sys/msg.h], [dnl
+      BRLTTY_SCREEN_DRIVER([em], [TerminalEmulator])
+
+      if test -n "${curses_package}"
+      then
+         all_brltty_pty="all-brltty-pty"
+         install_brltty_pty="install-brltty-pty"
+      fi
+   ])
+])
+AC_SUBST([all_brltty_pty])
+AC_SUBST([install_brltty_pty])
+
+if test "${brltty_enabled_x}" = "yes"
+then
+   BRLTTY_HAVE_PACKAGE([cspi], [cspi-1.0], [dnl
+      AC_CHECK_HEADER([X11/keysym.h], [dnl
+         AC_CHECK_HEADER([X11/Xlib.h], [dnl
+            BRLTTY_SCREEN_DRIVER([as], [AtSpi], [$(X11_LIBS) $(CSPI_LIBS)])
+         ])
+      ])
+   ], [:])
+fi
+
+BRLTTY_IF_DBUS([dnl
+   AC_CHECK_HEADER([X11/keysym.h], [dnl
+      BRLTTY_HAVE_PACKAGE([atspi2], [atspi-2], [dnl
+         brltty_cppflags_save="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${atspi2_cflags}"
+         BRLTTY_SCREEN_DRIVER([a2], [AtSpi2], [$(DBUS_LIBS) $(ATSPI2_LIBS) $(GLIB2_LIBS) $(X11_LIBS) $(XFIXES_LIBS)])
+
+         brltty_libs_save="${LIBS}"
+         LIBS="${atspi2_libs} ${LIBS}"
+
+         AC_CHECK_FUNCS([atspi_get_a11y_bus])
+
+         CPPFLAGS="${brltty_cppflags_save}"
+         LIBS="${brltty_libs_save}"
+
+         BRLTTY_HAVE_PACKAGE([glib2], [glib-2.0])
+      ])
+   ])
+])
+
+BRLTTY_ARG_DRIVER([screen], [Screen])
+BRLTTY_SUMMARY_ITEM([screen-driver], [default_screen_driver])
+
+BRLTTY_ARG_ENABLE(
+   [relocatable-install],
+   [installation using paths relative to the program directory])
+
+case "${host_os}"
+in
+   darwin*)
+      INSTALL_OPTION_STRIP=""
+      ;;
+   *)
+      INSTALL_OPTION_STRIP="-s"
+      ;;
+esac
+
+if test "${cross_compiling}" != "no"
+then
+   if test -n "${INSTALL_OPTION_STRIP}"
+   then
+      INSTALL_OPTION_STRIP="${INSTALL_OPTION_STRIP} --strip-program ${STRIP}"
+   fi
+fi
+
+BRLTTY_ARG_DISABLE(
+   [stripping],
+   [stripping of executables and shared objects during installation],
+   [],
+   [],
+   [INSTALL_OPTION_STRIP=""]
+)
+AC_SUBST([INSTALL_OPTION_STRIP])
+
+original_prefix="${prefix}"
+test "${prefix}" = "NONE" && prefix=""
+original_exec_prefix="${exec_prefix}"
+test "${exec_prefix}" = "NONE" && exec_prefix="${prefix}"
+BRLTTY_VAR_EXPAND([brltty_reference_directory], [${execute_root}${program_directory}])
+
+BRLTTY_DEFINE_DIRECTORY([LOCALE_DIRECTORY], [${execute_root}${localedir}],
+                        [Define this to be a string containing the path to the locale directory.])
+
+BRLTTY_DEFINE_DIRECTORY([TABLES_DIRECTORY], [${execute_root}${tables_directory}],
+                        [Define this to be a string containing the path to the data files directory.])
+
+BRLTTY_DEFINE_DIRECTORY([DRIVERS_DIRECTORY], [${execute_root}${drivers_directory}],
+                        [Define this to be a string containing the path to the drivers directory.])
+
+BRLTTY_DEFINE_DIRECTORY([COMMANDS_DIRECTORY], [${execute_root}${commands_directory}],
+                        [Define this to be a string containing the path to the helper commands directory.])
+
+BRLTTY_PUBLIC_DIRECTORY([CONFIGURATION_DIRECTORY], [${sysconfdir}],
+                        [Define this to be a string containing the path to the configuration directory.])
+
+if test -n "${updatable_directory}"
+then
+   BRLTTY_PUBLIC_DIRECTORY([UPDATABLE_DIRECTORY], [${updatable_directory}],
+                           [Define this to be a string containing the path to a directory which contains files that can be updated.])
+fi
+
+if test -n "${writable_directory}"
+then
+   BRLTTY_PUBLIC_DIRECTORY([WRITABLE_DIRECTORY], [${writable_directory}],
+                           [Define this to be a string containing the path to a writable directory.])
+fi
+
+BRLTTY_PUBLIC_DIRECTORY([BRLAPI_ETCDIR], [${CONFIGURATION_DIRECTORY}],
+                        [Define this to be a string containing the path to BrlAPI's data files directory.])
+
+BRLTTY_PUBLIC_DIRECTORY([BRLAPI_SOCKETPATH], [${api_socket_path}],
+                        [Define this to be a string containing the path to BrlAPI's local sockets directory.])
+
+prefix="${original_prefix}"
+exec_prefix="${original_exec_prefix}"
+
+AC_SUBST([install_drivers])
+BRLTTY_SUMMARY_END
+
+[brltty_make_files="`echo "${brltty_build_directories}" | sed -e '
+s%\([^ ][^ ]*\)%\1/Makefile:prologue.mk:\1/Makefile.in%g
+'`"]
+
+AC_CONFIG_FILES([
+   config.mk
+   brltty.spec
+   brltty-config.sh
+   brltty.pc
+   Documents/brltty.conf
+   Documents/brltty.1
+   Documents/xbrlapi.1
+   Documents/BrlAPIref.doxy
+   Bindings/Emacs/add_directory.el
+   Bindings/Lisp/brlapi.asd
+   Bindings/Lisp/brlapi_config.lisp
+   Bindings/Python/setup.py
+   Autostart/Systemd/brltty@.service
+   Autostart/Systemd/tmpfiles
+   Autostart/Udev/usb-template.rules
+   Autostart/X11/90xbrlapi
+   Android/Gradle/config.properties
+   ${brltty_make_files}
+])
+
+AC_CONFIG_COMMANDS([build-configure], [dnl
+   "${srcdir}/mk4build"
+])
+
+AC_OUTPUT
diff --git a/document.mk b/document.mk
new file mode 100644
index 0000000..1dca707
--- /dev/null
+++ b/document.mk
@@ -0,0 +1,40 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+LOCALE = C
+SETLOCALE = LC_ALL=$(LOCALE)
+
+all: all-yes
+all-yes: txt html
+all-no:
+	@echo linuxdoc is not installed - document will not be made
+
+txt: $(DOCUMENT_NAME).txt
+html: html.made
+
+$(DOCUMENT_NAME).txt: $(SRC_DIR)/$(DOCUMENT_NAME).sgml
+	$(SETLOCALE) linuxdoc -B txt -f -l $(DOCUMENT_LANGUAGE) -c latin $<
+	sed -e 's/\x1B\[[0-9][0-9]*m//g' -i $@
+
+html.made: $(SRC_DIR)/$(DOCUMENT_NAME).sgml
+	$(SETLOCALE) linuxdoc -B html -l $(DOCUMENT_LANGUAGE) -c ascii $<
+	touch $@
+
+clean::
+	-rm -f -- *.txt *.html *.made
+
diff --git a/docutils.conf b/docutils.conf
new file mode 100644
index 0000000..f32190b
--- /dev/null
+++ b/docutils.conf
@@ -0,0 +1,11 @@
+[general]
+input-encoding: UTF-8
+output-encoding: UTF-8
+toc-backlinks: entry
+
+[html4css1 writer]
+initial-header-level: 2
+
+[text writer]
+wrap-width: 80
+
diff --git a/getrevid b/getrevid
new file mode 100755
index 0000000..6697139
--- /dev/null
+++ b/getrevid
@@ -0,0 +1,111 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+defaultModifiedIndicator="+"
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+addProgramOption a string.anything appendText "text to append to the revision identifier"
+addProgramOption d string.anything defaultIdentifier "the default revision identifier"
+addProgramOption f string.file outputFile "the file in which to write the revision identifier" "standard output"
+addProgramOption i flag asInteger "render the revision identifier as a 32-bit integer"
+addProgramOption m string.anything modifiedIndicator "the modified indicator" "${defaultModifiedIndicator}"
+addProgramOption p string.anything prependText "text to prepend to the revision identifier"
+addProgramOption r string.anything revisionIdentifier "the revision identifier"
+addProgramOption s flag asString "render the revision identifier as a quoted string"
+addProgramParameter source sourceRoot "the top-level directory of the source tree"
+parseProgramArguments "${@}"
+
+[ -e "${sourceRoot}" ] || semanticError "directory not found: ${sourceRoot}"
+[ -d "${sourceRoot}" ] || semanticError "not a directory: ${sourceRoot}"
+
+[ -n "${modifiedIndicator}" ] || modifiedIndicator="${defaultModifiedIndicator}"
+
+[ -n "${revisionIdentifier}" ] || {
+  if [ -e "${sourceRoot}/.git" ]
+  then
+     revisionIdentifier="`git -C "${sourceRoot}" describe --tags --abbrev=8 --dirty="${modifiedIndicator}" --match="BRLTTY-*" 2>/dev/null`"
+  elif [ -d "${sourceRoot}/.svn" ]
+  then
+     revisionIdentifier="`svnversion -n "${sourceRoot}" 2>/dev/null`"
+     [ "${revisionIdentifier}" != "exported" ] || revisionIdentifier=""
+  else
+     revisionFile="${sourceRoot}/REVISION"
+     [ -f "${revisionFile}" ] && read <"${revisionFile}" revisionIdentifier
+  fi
+
+  [ -n "${revisionIdentifier}" ] || logWarning "unrecognized source repository type: ${sourceRoot}"
+}
+
+[ -n "${revisionIdentifier}" ] || {
+  [ -n "${defaultIdentifier}" ] || semanticError "revision identifier not known"
+  revisionIdentifier="${defaultIdentifier}"
+}
+
+"${asInteger}" && {
+  makeInteger() {
+    set -- `echo "${revisionIdentifier}" | sed -e 's/-/ /g'`
+    local commitCount="${3:-0}"
+
+    revisionIdentifier=0
+    local width=6
+    local shift=30
+    local number
+
+    for number in `echo "${2}" | sed -e 's/\./ /g'`
+    do
+      shift=$((shift - width))
+      revisionIdentifier=$((revisionIdentifier | (number << shift)))
+    done
+
+    revisionIdentifier=$((revisionIdentifier | commitCount))
+  }
+
+  makeInteger
+}
+
+[ -n "${prependText}" ] && revisionIdentifier="${prependText}${revisionIdentifier}"
+[ -n "${appendText}" ] && revisionIdentifier="${revisionIdentifier}${appendText}"
+"${asString}" && revisionIdentifier='"'"${revisionIdentifier}"'"'
+
+if [ -z "${outputFile}" ]
+then
+   echo "${revisionIdentifier}"
+else
+   if [ -e "${outputFile}" ]
+   then
+      [ -f "${outputFile}" ] || semanticError "not a file: ${outputFile}"
+      [ -r "${outputFile}" ] || semanticError "file not readable: ${outputFile}"
+
+      exec 3<"${outputFile}"
+      read <&3 -r firstLine && {
+         [ "${firstLine}" != "${revisionIdentifier}" ] || read <&3 -r secondLine || exit 0
+      }
+      exec 3<&-
+
+      [ -w "${outputFile}" ] || semanticError "file not writable: ${outputFile}"
+   else
+      outputDirectory="$(dirname "${outputFile}")"
+      [ -d "${outputDirectory}" ] || semanticError "not a directory: ${outputDirectory}"
+      [ -w "${outputDirectory}" ] || semanticError "directory not writable: ${outputDirectory}"
+   fi
+
+   echo "${revisionIdentifier}" >"${outputFile}"
+fi
+
+exit 0
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..bd753b3
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,53 @@
+# ===========================================================================
+#  https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+#   Check whether the given FLAG works with the current language's compiler
+#   or gives an error.  (Warnings, however, are ignored)
+#
+#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+#   success/failure.
+#
+#   If EXTRA-FLAGS is defined, it is added to the current language's default
+#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with
+#   the flags: "CFLAGS EXTRA-FLAGS FLAG".  This can for example be used to
+#   force the compiler to issue an error when a bad flag is given.
+#
+#   INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved.  This file is offered as-is, without any
+#   warranty.
+
+#serial 6
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+  _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+  AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+    [AS_VAR_SET(CACHEVAR,[yes])],
+    [AS_VAR_SET(CACHEVAR,[no])])
+  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+  [m4_default([$2], :)],
+  [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_gcc_func_attribute.m4 b/m4/ax_gcc_func_attribute.m4
new file mode 100644
index 0000000..da2b1ac
--- /dev/null
+++ b/m4/ax_gcc_func_attribute.m4
@@ -0,0 +1,242 @@
+# ===========================================================================
+#  https://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE)
+#
+# DESCRIPTION
+#
+#   This macro checks if the compiler supports one of GCC's function
+#   attributes; many other compilers also provide function attributes with
+#   the same syntax. Compiler warnings are used to detect supported
+#   attributes as unsupported ones are ignored by default so quieting
+#   warnings when using this macro will yield false positives.
+#
+#   The ATTRIBUTE parameter holds the name of the attribute to be checked.
+#
+#   If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_<ATTRIBUTE>.
+#
+#   The macro caches its result in the ax_cv_have_func_attribute_<attribute>
+#   variable.
+#
+#   The macro currently supports the following function attributes:
+#
+#    alias
+#    aligned
+#    alloc_size
+#    always_inline
+#    artificial
+#    cold
+#    const
+#    constructor
+#    constructor_priority for constructor attribute with priority
+#    deprecated
+#    destructor
+#    dllexport
+#    dllimport
+#    error
+#    externally_visible
+#    fallthrough
+#    flatten
+#    format
+#    format_arg
+#    gnu_format
+#    gnu_inline
+#    hot
+#    ifunc
+#    leaf
+#    malloc
+#    noclone
+#    noinline
+#    nonnull
+#    noreturn
+#    nothrow
+#    optimize
+#    pure
+#    sentinel
+#    sentinel_position
+#    unused
+#    used
+#    visibility
+#    warning
+#    warn_unused_result
+#    weak
+#    weakref
+#
+#   Unsupported function attributes will be tested with a prototype
+#   returning an int and not accepting any arguments and the result of the
+#   check might be wrong or meaningless so use with care.
+#
+# LICENSE
+#
+#   Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved.  This file is offered as-is, without any
+#   warranty.
+
+#serial 12
+
+AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [
+    AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1])
+
+    AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [
+        AC_LINK_IFELSE([AC_LANG_PROGRAM([
+            m4_case([$1],
+                [alias], [
+                    int foo( void ) { return 0; }
+                    int bar( void ) __attribute__(($1("foo")));
+                ],
+                [aligned], [
+                    int foo( void ) __attribute__(($1(32)));
+                ],
+                [alloc_size], [
+                    void *foo(int a) __attribute__(($1(1)));
+                ],
+                [always_inline], [
+                    inline __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [artificial], [
+                    inline __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [cold], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [const], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [constructor_priority], [
+                    int foo( void ) __attribute__((__constructor__(65535/2)));
+                ],
+                [constructor], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [deprecated], [
+                    int foo( void ) __attribute__(($1("")));
+                ],
+                [destructor], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [dllexport], [
+                    __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [dllimport], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [error], [
+                    int foo( void ) __attribute__(($1("")));
+                ],
+                [externally_visible], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [fallthrough], [
+                    int foo( void ) {switch (0) { case 1: __attribute__(($1)); case 2: break ; }};
+                ],
+                [flatten], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [format], [
+                    int foo(const char *p, ...) __attribute__(($1(printf, 1, 2)));
+                ],
+                [gnu_format], [
+                    int foo(const char *p, ...) __attribute__((format(gnu_printf, 1, 2)));
+                ],
+                [format_arg], [
+                    char *foo(const char *p) __attribute__(($1(1)));
+                ],
+                [gnu_inline], [
+                    inline __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [hot], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [ifunc], [
+                    int my_foo( void ) { return 0; }
+                    static int (*resolve_foo(void))(void) { return my_foo; }
+                    int foo( void ) __attribute__(($1("resolve_foo")));
+                ],
+                [leaf], [
+                    __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [malloc], [
+                    void *foo( void ) __attribute__(($1));
+                ],
+                [noclone], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [noinline], [
+                    __attribute__(($1)) int foo( void ) { return 0; }
+                ],
+                [nonnull], [
+                    int foo(char *p) __attribute__(($1(1)));
+                ],
+                [noreturn], [
+                    void foo( void ) __attribute__(($1));
+                ],
+                [nothrow], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [optimize], [
+                    __attribute__(($1(3))) int foo( void ) { return 0; }
+                ],
+                [pure], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [sentinel], [
+                    int foo(void *p, ...) __attribute__(($1));
+                ],
+                [sentinel_position], [
+                    int foo(void *p, ...) __attribute__(($1(1)));
+                ],
+                [returns_nonnull], [
+                    void *foo( void ) __attribute__(($1));
+                ],
+                [unused], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [used], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [visibility], [
+                    int foo_def( void ) __attribute__(($1("default")));
+                    int foo_hid( void ) __attribute__(($1("hidden")));
+                    int foo_int( void ) __attribute__(($1("internal")));
+                    int foo_pro( void ) __attribute__(($1("protected")));
+                ],
+                [warning], [
+                    int foo( void ) __attribute__(($1("")));
+                ],
+                [warn_unused_result], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [weak], [
+                    int foo( void ) __attribute__(($1));
+                ],
+                [weakref], [
+                    static int foo( void ) { return 0; }
+                    static int bar( void ) __attribute__(($1("foo")));
+                ],
+                [
+                 m4_warn([syntax], [Unsupported attribute $1, the test may fail])
+                 int foo( void ) __attribute__(($1));
+                ]
+            )], [])
+            ],
+            dnl GCC doesn't exit with an error if an unknown attribute is
+            dnl provided but only outputs a warning, so accept the attribute
+            dnl only if no warning were issued.
+            [AS_IF([grep -- -Wattributes conftest.err],
+                [AS_VAR_SET([ac_var], [no])],
+                [AS_VAR_SET([ac_var], [yes])])],
+            [AS_VAR_SET([ac_var], [no])])
+    ])
+
+    AS_IF([test yes = AS_VAR_GET([ac_var])],
+        [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1,
+            [Define to 1 if the system has the `$1' function attribute])], [])
+
+    AS_VAR_POPDEF([ac_var])
+])
diff --git a/m4/ax_gcc_var_attribute.m4 b/m4/ax_gcc_var_attribute.m4
new file mode 100644
index 0000000..47635d4
--- /dev/null
+++ b/m4/ax_gcc_var_attribute.m4
@@ -0,0 +1,141 @@
+# ===========================================================================
+#   https://www.gnu.org/software/autoconf-archive/ax_gcc_var_attribute.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_GCC_VAR_ATTRIBUTE(ATTRIBUTE)
+#
+# DESCRIPTION
+#
+#   This macro checks if the compiler supports one of GCC's variable
+#   attributes; many other compilers also provide variable attributes with
+#   the same syntax. Compiler warnings are used to detect supported
+#   attributes as unsupported ones are ignored by default so quieting
+#   warnings when using this macro will yield false positives.
+#
+#   The ATTRIBUTE parameter holds the name of the attribute to be checked.
+#
+#   If ATTRIBUTE is supported define HAVE_VAR_ATTRIBUTE_<ATTRIBUTE>.
+#
+#   The macro caches its result in the ax_cv_have_var_attribute_<attribute>
+#   variable.
+#
+#   The macro currently supports the following variable attributes:
+#
+#    aligned
+#    cleanup
+#    common
+#    nocommon
+#    deprecated
+#    mode
+#    packed
+#    tls_model
+#    unused
+#    used
+#    vector_size
+#    weak
+#    dllimport
+#    dllexport
+#    init_priority
+#
+#   Unsupported variable attributes will be tested against a global integer
+#   variable and without any arguments given to the attribute itself; the
+#   result of this check might be wrong or meaningless so use with care.
+#
+# LICENSE
+#
+#   Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved.  This file is offered as-is, without any
+#   warranty.
+
+#serial 5
+
+AC_DEFUN([AX_GCC_VAR_ATTRIBUTE], [
+    AS_VAR_PUSHDEF([ac_var], [ax_cv_have_var_attribute_$1])
+
+    AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [
+        AC_LINK_IFELSE([AC_LANG_PROGRAM([
+            m4_case([$1],
+                [aligned], [
+                    int foo __attribute__(($1(32)));
+                ],
+                [cleanup], [
+                    int bar(int *t) { return *t; };
+                ],
+                [common], [
+                    int foo __attribute__(($1));
+                ],
+                [nocommon], [
+                    int foo __attribute__(($1));
+                ],
+                [deprecated], [
+                    int foo __attribute__(($1)) = 0;
+                ],
+                [mode], [
+                    long foo __attribute__(($1(word)));
+                ],
+                [packed], [
+                    struct bar {
+                        int baz __attribute__(($1));
+                    };
+                ],
+                [tls_model], [
+                    __thread int bar1 __attribute__(($1("global-dynamic")));
+                    __thread int bar2 __attribute__(($1("local-dynamic")));
+                    __thread int bar3 __attribute__(($1("initial-exec")));
+                    __thread int bar4 __attribute__(($1("local-exec")));
+                ],
+                [unused], [
+                    int foo __attribute__(($1));
+                ],
+                [used], [
+                    int foo __attribute__(($1));
+                ],
+                [vector_size], [
+                    int foo __attribute__(($1(16)));
+                ],
+                [weak], [
+                    int foo __attribute__(($1));
+                ],
+                [dllimport], [
+                    int foo __attribute__(($1));
+                ],
+                [dllexport], [
+                    int foo __attribute__(($1));
+                ],
+                [init_priority], [
+                    struct bar { bar() {} ~bar() {} };
+                    bar b __attribute__(($1(65535/2)));
+                ],
+                [
+                 m4_warn([syntax], [Unsupported attribute $1, the test may fail])
+                 int foo __attribute__(($1));
+                ]
+            )], [
+            m4_case([$1],
+                [cleanup], [
+                    int foo __attribute__(($1(bar))) = 0;
+                    foo = foo + 1;
+                ],
+                []
+            )])
+            ],
+            dnl GCC doesn't exit with an error if an unknown attribute is
+            dnl provided but only outputs a warning, so accept the attribute
+            dnl only if no warning were issued.
+            [AS_IF([test -s conftest.err],
+                [AS_VAR_SET([ac_var], [no])],
+                [AS_VAR_SET([ac_var], [yes])])],
+            [AS_VAR_SET([ac_var], [no])])
+    ])
+
+    AS_IF([test yes = AS_VAR_GET([ac_var])],
+        [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_VAR_ATTRIBUTE_$1), 1,
+            [Define to 1 if the system has the `$1' variable attribute])], [])
+
+    AS_VAR_POPDEF([ac_var])
+])
diff --git a/m4/brltty.m4 b/m4/brltty.m4
new file mode 100644
index 0000000..3e0ae8f
--- /dev/null
+++ b/m4/brltty.m4
@@ -0,0 +1,847 @@
+###############################################################################
+# libbrlapi - A library providing access to braille terminals for applications.
+#
+# Copyright (C) 1995-2023 by Dave Mielke <dave@mielke.cc>
+#
+# libbrlapi comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+AC_DEFUN([BRLTTY_UPPERCASE_TRANSLATE], [translit([$1], [a-z], [A-Z])])
+
+AC_DEFUN([BRLTTY_TRANSLATE_ASSIGN], [$1=`echo "$2" | sed -e 'y%$3%$4%'`])
+AC_DEFUN([BRLTTY_UPPERCASE_ASSIGN], BRLTTY_TRANSLATE_ASSIGN([$1], [$2], [abcdefghijklmnopqrstuvwxyz$3], [ABCDEFGHIJKLMNOPQRSTUVWXYZ$4]))
+AC_DEFUN([BRLTTY_LOWERCASE_ASSIGN], [BRLTTY_TRANSLATE_ASSIGN([$1], [$2], [ABCDEFGHIJKLMNOPQRSTUVWXYZ$3], [abcdefghijklmnopqrstuvwxyz$4])])
+
+AC_DEFUN([BRLTTY_INCLUDE_HEADERS], [dnl
+ifelse(len([$1]), 0, [], [
+[#]include <$1>
+])ifelse($#, 1, [], [BRLTTY_INCLUDE_HEADERS(m4_shift($@))])])
+
+AC_DEFUN([BRLTTY_CHECK_FUNCTION], [dnl
+   AC_CHECK_DECL([$1], [dnl
+      AC_SEARCH_LIBS([$1], [$3], [dnl
+         BRLTTY_UPPERCASE_ASSIGN([brltty_define], [have_$1])
+         AC_DEFINE_UNQUOTED(${brltty_define}, [1], [Define this if the function $1 is available.])
+      ])
+   ], [], [BRLTTY_INCLUDE_HEADERS(m4_translit([$2], [ ], [,]))])
+])
+
+AC_DEFUN([BRLTTY_SEARCH_LIBS], [dnl
+BRLTTY_UPPERCASE_ASSIGN([brltty_uc], [$1])
+AC_SEARCH_LIBS([$1], [$2], [AC_DEFINE_UNQUOTED(HAVE_${brltty_uc}, [1], [Define this if the function $1 is available.])])])
+
+AC_DEFUN([BRLTTY_VAR_TRIM], [dnl
+$1="`echo "${$1}" | sed -e 's/^ *//' -e 's/ *$//'`"
+])
+
+AC_DEFUN([BRLTTY_VAR_EXPAND], [dnl
+$1='$2'
+while test "${$1}" != "${$1%\$?*}"
+do
+   eval '$1="'"${$1}"'"'
+done
+])
+
+AC_DEFUN([BRLTTY_SUBST_EXPANDED], [dnl
+BRLTTY_VAR_EXPAND([brltty_expanded], [$2])
+AC_SUBST([$1], ["${brltty_expanded}"])])
+
+AC_DEFUN([BRLTTY_DEFINE_STRING], [dnl
+   AC_SUBST([$1], [$2])
+   AC_DEFINE([$1], ["$2"], [Define this to be a string containing $3.])
+])
+
+AC_DEFUN([BRLTTY_DEFINE_EXPANDED], [dnl
+BRLTTY_VAR_EXPAND([brltty_expanded], [$2])
+AC_DEFINE_UNQUOTED([$1], ["${brltty_expanded}"], [$3])])
+
+AC_DEFUN([BRLTTY_RELATIVE_PATH], [dnl
+AC_REQUIRE([AC_PROG_AWK])
+eval '$1="`"${AWK}" -v path="'"$2"'" -v reference="'"$3"'" -f "${srcdir}/relpath.awk"`"'])
+
+AC_DEFUN([BRLTTY_DEFINE_DIRECTORY], [dnl
+BRLTTY_VAR_EXPAND([$1], [$2])
+brltty_path="${$1}"
+
+ifelse([$4], [public], [], [dnl
+  test "${brltty_enabled_relocatable_install}" = "yes" && {
+     test "${$1}" = "${$1#\\}" && {
+        BRLTTY_RELATIVE_PATH([brltty_path], [${$1}], [${brltty_reference_directory}])
+     }
+  }
+])
+
+BRLTTY_DEFINE_EXPANDED([$1], [${brltty_path}], [$3])
+# resolve escaped characters
+eval '$1="'"${$1}"'"'
+AC_SUBST([$1])
+])
+
+AC_DEFUN([BRLTTY_PUBLIC_DIRECTORY], [dnl
+BRLTTY_DEFINE_DIRECTORY([$1], [$2], [$3], [public])
+])
+
+AC_DEFUN([BRLTTY_ARG_WITH], [dnl
+AC_ARG_WITH([$1], BRLTTY_HELP_STRING([--with-$1=$2], [$3]), [$4="${withval}"], [$4=$5])])
+
+AC_DEFUN([BRLTTY_ARG_REQUIRED], [dnl
+BRLTTY_ARG_WITH([$1], [$2], [$3], [$4], ["yes"])
+if test "${$4}" = "no"
+then
+   AC_MSG_ERROR([$1 not specified])
+elif test "${$4}" = "yes"
+then
+   $4=$5
+fi
+AC_SUBST([$4])
+BRLTTY_SUMMARY_ITEM([$1], [$4])])
+
+AC_DEFUN([BRLTTY_ARG_TABLE], [dnl
+brltty_default_table="$2"
+BRLTTY_ARG_WITH(
+   [$1-table], [NAME],
+   [built-in $1 table],
+   [$1_table], ["${brltty_default_table}"]
+)
+install_$1_tables=install-$1-tables
+if test "${$1_table}" = "no"
+then
+   install_$1_tables=
+   $1_table="${brltty_default_table}"
+elif test "${$1_table}" = "yes"
+then
+   $1_table="${brltty_default_table}"
+fi
+AC_SUBST([install_$1_tables])
+BRLTTY_SUMMARY_ITEM([$1-table], [$1_table])
+AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([$1_table]), ["${$1_table}"],
+                   [Define this to be a string containing the path to the default $1 table.])
+AC_SUBST([$1_table])])
+
+AC_DEFUN([BRLTTY_ARG_PARAMETERS], [dnl
+BRLTTY_ARG_WITH(
+   [$1-parameters], [$3NAME=VALUE... (comma-separated)],
+   [default parameters for the $2],
+   [$1_parameters], ["yes"]
+)
+if test "${$1_parameters}" = "no"
+then
+   $1_parameters=""
+elif test "${$1_parameters}" = "yes"
+then
+   $1_parameters="$4"
+fi
+AC_SUBST([$1_parameters])
+AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([$1_parameters]), ["${$1_parameters}"],
+                   [Define this to be a string containing the default parameters for the $2.])
+BRLTTY_SUMMARY_ITEM([$1-parameters], [$1_parameters])])
+
+AC_DEFUN([BRLTTY_HAVE_HEADER], [
+AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include <$1>]])], [$2], [$3])
+])
+
+AC_DEFUN([BRLTTY_HAVE_LIBRARY], [AC_CHECK_LIB([$1], [main], [$2], [$3])])
+
+AC_DEFUN([BRLTTY_ARG_PACKAGE], [dnl
+$1_package=""
+$1_includes=""
+$1_libs=""
+
+BRLTTY_ARG_WITH(
+   [$1-package], [PACKAGE],
+   [which $2 package to use],
+   [packages], ["yes"]
+)
+
+if test "${packages}" = "no"
+then
+   $1_package="none"
+elif test "${packages}" = "yes"
+then
+   packages="$3"
+   ifelse(len([$4]), 0, [], [dnl
+      case "${host_os}"
+      in
+         $4
+         *);;
+      esac
+   ])
+else
+   packages=`echo "${packages}" | sed -e 'y/,/ /'`
+fi
+
+test -z "${$1_package}" && {
+   test -n "${packages}" && {
+      for package in ${packages}
+      do
+         BRLTTY_HAVE_PACKAGE([$1], [${package}], [], [:])
+         test -n "${$1_package}" && break
+
+         ifelse(len([$5]), 0, [], [dnl
+            case "${package}"
+            in
+               $5
+               *);;
+            esac
+
+            test -n "${$1_package}" && break
+         ])
+
+         AC_MSG_NOTICE([$2 package not available: ${package}])
+      done
+   }
+}
+
+test -z "${$1_package}" && {
+   $1_package="none"
+   AC_MSG_WARN([$2 support not available on this platform])
+}
+
+AC_SUBST([$1_package])
+AC_SUBST([$1_includes])
+AC_SUBST([$1_libs])
+AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([$1_package]), [${$1_package}], [Define this to the name of the selected $2 package.])
+BRLTTY_UPPERCASE_ASSIGN([brltty_uc], [use_pkg_$1_${$1_package}], [-.], [__])
+AC_DEFINE_UNQUOTED([${brltty_uc}])
+BRLTTY_SUMMARY_ITEM([$1-package], [$1_package])])
+
+AC_DEFUN([BRLTTY_ARG_ENABLE], [dnl
+BRLTTY_ARG_FEATURE([$1], [$2], [enable], [no], [$3], [$4], [$5])])
+
+AC_DEFUN([BRLTTY_ARG_DISABLE], [dnl
+BRLTTY_ARG_FEATURE([$1], [$2], [disable], [yes], [$3], [$4], [$5])])
+
+AC_DEFUN([BRLTTY_ARG_FEATURE], [dnl
+AC_ARG_ENABLE([$1], BRLTTY_HELP_STRING([--$3-$1], [$2]), [], [enableval="$4"])
+
+pushdef([var], brltty_enabled_[]translit([$1], [-], [_]))dnl
+AC_SUBST(var, ["${enableval}"])
+BRLTTY_SUMMARY_ITEM([$1], var)dnl
+popdef([var])
+
+if test "${enableval}" = "no"
+then
+   ifelse(len([$7]), 0, [:], [$7])
+else
+ifelse(len([$5]), 0, [], [dnl
+   set -- [$5]
+])dnl
+   if test "${enableval}" = "yes"
+   then
+      brltty_ok=true
+ifelse(len([$5]), 0, [], [dnl
+      test "${#}" -gt 0 && enableval="${1}"
+])dnl
+   else
+      brltty_ok=false
+ifelse(len([$5]), 0, [], [dnl
+      test "${#}" -gt 0 && {
+         for brltty_selection
+         do
+            test "${brltty_selection}" = "${enableval}" && {
+               brltty_ok=true
+               break
+            }
+         done
+      }
+])dnl
+   fi
+
+   if "${brltty_ok}"
+   then
+ifelse(len([$5]), 0, [], [dnl
+      test "${#}" -gt 0 && {
+         BRLTTY_UPPERCASE_ASSIGN([brltty_uc], [use_$1_${enableval}], [-], [_])
+         AC_DEFINE_UNQUOTED([${brltty_uc}])
+      }
+])dnl
+      ifelse(len([$6]), 0, [:], [$6])
+   else
+      AC_MSG_ERROR([invalid selection: --enable-$1=${enableval}])
+   fi
+fi])
+
+AC_DEFUN([BRLTTY_HELP_STRING], [dnl
+AS_HELP_STRING([$1], patsubst([$2], [
+.*$]), m4_defn([brltty_help_prefix]))dnl
+patsubst(patsubst([$2], [\`[^
+]*]), [
+], [\&brltty_help_prefix])[]dnl
+])
+m4_define([brltty_help_indent], 32)
+m4_define([brltty_help_prefix], m4_format([%]brltty_help_indent[s], []))
+m4_define([brltty_help_width], m4_eval(79-brltty_help_indent))
+
+AC_DEFUN([BRLTTY_ITEM], [dnl
+define([brltty_item_list_$1], ifdef([brltty_item_list_$1], [brltty_item_list_$1])[
+m4_text_wrap([$3], [      ], [- $2  ], brltty_help_width)])dnl
+brltty_item_entries_$1="${brltty_item_entries_$1} $2-$3"
+brltty_item_codes_$1="${brltty_item_codes_$1} $2"
+brltty_item_names_$1="${brltty_item_names_$1} $3"
+AC_SUBST([$1_libraries_$2], ['$4'])])
+
+AC_DEFUN([BRLTTY_BRAILLE_DRIVER], [dnl
+BRLTTY_ITEM([braille], [$1], [$2], [$3])])
+
+AC_DEFUN([BRLTTY_SPEECH_DRIVER], [dnl
+BRLTTY_ITEM([speech], [$1], [$2], [$3])])
+
+AC_DEFUN([BRLTTY_SCREEN_DRIVER], [dnl
+BRLTTY_ITEM([screen], [$1], [$2], [$3])])
+
+AC_DEFUN([BRLTTY_ARG_ITEM], [dnl
+BRLTTY_VAR_TRIM([brltty_item_codes_$1])
+AC_SUBST([brltty_item_codes_$1])
+
+BRLTTY_VAR_TRIM([brltty_item_names_$1])
+AC_SUBST([brltty_item_names_$1])
+
+case "${host_os}"
+in
+   cygwin*|mingw*)
+      brltty_default="all"
+      ;;
+   *)
+      brltty_default="yes"
+      ;;
+esac
+
+BRLTTY_ARG_WITH(
+   [$1-$2], BRLTTY_UPPERCASE_TRANSLATE([$2]),
+   [$1 $2(s) to build in]brltty_item_list_$1,
+   [brltty_items], ["${brltty_default}"]
+)
+
+if test "${brltty_items}" = "no"
+then
+   brltty_external_codes_$1=""
+   brltty_external_names_$1=""
+   brltty_internal_codes_$1=""
+   brltty_internal_names_$1=""
+else
+   brltty_items_left_$1=" ${brltty_item_entries_$1} "
+   brltty_external_codes_$1=" ${brltty_item_codes_$1} "
+   brltty_external_names_$1=" ${brltty_item_names_$1} "
+   brltty_internal_codes_$1=""
+   brltty_internal_names_$1=""
+
+   if test "${brltty_items}" != "yes"
+   then
+      while :
+      do
+         [brltty_delimiter="`expr "${brltty_items}" : '[^,]*,'`"]
+         if test "${brltty_delimiter}" -eq 0
+         then
+            brltty_item="${brltty_items}"
+            brltty_items=""
+            if test "${brltty_item}" = "all"
+            then
+               brltty_item_all=true
+               brltty_item_include=true
+            elif test "${brltty_item}" = "-all"
+            then
+               brltty_item_all=true
+               brltty_item_include=false
+            else
+               brltty_item_all=false
+            fi
+            if "${brltty_item_all}"
+            then
+               if "${brltty_item_include}"
+               then
+                  brltty_internal_codes_$1="${brltty_internal_codes_$1}${brltty_external_codes_$1}"
+                  brltty_internal_names_$1="${brltty_internal_names_$1}${brltty_external_names_$1}"
+               fi
+               brltty_external_codes_$1=""
+               brltty_external_names_$1=""
+               break
+            fi
+         else
+            [brltty_item="`expr "${brltty_items}" : '\([^,]*\)'`"]
+            [brltty_items="`expr "${brltty_items}" : '[^,]*,\(.*\)'`"]
+         fi
+         brltty_item_suffix="${brltty_item#-}"
+         if test "${brltty_item}" = "${brltty_item_suffix}"
+         then
+            brltty_item_include=true
+         else
+            brltty_item_include=false
+            brltty_item="${brltty_item_suffix}"
+         fi
+
+         BRLTTY_ITEM_RESOLVE([$1])
+         "${brltty_item_unknown}" && {
+            AC_MSG_ERROR([unknown $1 $2: ${brltty_item}])
+         }
+
+         brltty_item_found="`expr "${brltty_external_codes_$1}" : ".* ${brltty_item_code} "`"
+         test "${brltty_item_found}" -eq 0 && {
+            AC_MSG_ERROR([duplicate $1 $2: ${brltty_item}])
+         }
+
+         brltty_items_left_$1="`echo "${brltty_items_left_$1}" | sed -e "s% ${brltty_item_entry} % %"`"
+         brltty_external_codes_$1="`echo "${brltty_external_codes_$1}" | sed -e "s% ${brltty_item_code} % %"`"
+         brltty_external_names_$1="`echo "${brltty_external_names_$1}" | sed -e "s% ${brltty_item_name} % %"`"
+
+         "${brltty_item_include}" && {
+            brltty_internal_codes_$1="${brltty_internal_codes_$1} ${brltty_item_code}"
+            brltty_internal_names_$1="${brltty_internal_names_$1} ${brltty_item_name}"
+         }
+
+         BRLTTY_ITEM_RESOLVE([$1])
+         "${brltty_item_unknown}" || {
+            AC_MSG_ERROR([ambiguous $1 $2: ${brltty_item}])
+         }
+
+         test "${brltty_delimiter}" -eq 0 && break
+      done
+   fi
+
+   BRLTTY_VAR_TRIM([brltty_external_codes_$1])
+   BRLTTY_VAR_TRIM([brltty_external_names_$1])
+   BRLTTY_VAR_TRIM([brltty_internal_codes_$1])
+   BRLTTY_VAR_TRIM([brltty_internal_names_$1])
+fi
+
+AC_SUBST([brltty_external_codes_$1])
+AC_SUBST([brltty_external_names_$1])
+AC_SUBST([brltty_internal_codes_$1])
+AC_SUBST([brltty_internal_names_$1])
+
+set -- ${brltty_internal_codes_$1} ${brltty_external_codes_$1}
+AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([$1_$2_codes]), ["${*}"], 
+                   [Define this to be a string containing a list of the $1 $2 codes.])
+
+AC_SUBST([default_$1_$2], ["${1}"])
+AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([default_$1_$2]), ["${1}"], 
+                   [Define this to be a string containing the default $1 $2 code.])
+
+$1_driver_libraries=""
+if test -n "${brltty_internal_codes_$1}"
+then
+   for brltty_driver in ${brltty_internal_codes_$1}
+   do
+      eval 'brltty_libraries="${$1_libraries_'"${brltty_driver}"'}"'
+      if test -n "${brltty_libraries}"
+      then
+         $1_driver_libraries="${$1_driver_libraries} ${brltty_libraries}"
+      fi
+   done
+fi
+BRLTTY_VAR_TRIM([$1_driver_libraries])
+AC_SUBST([$1_driver_libraries])
+])
+
+AC_DEFUN([BRLTTY_ITEM_RESOLVE], [dnl
+brltty_item_unknown=true
+brltty_item_length=`expr length "${brltty_item}"`
+
+if test "${brltty_item_length}" -eq 2
+then
+   [brltty_item_entry=`expr "${brltty_items_left_$1}" : '.* \('"${brltty_item}"'-[^ ]*\)'`]
+   if test -n "${brltty_item_entry}"
+   then
+      brltty_item_code="${brltty_item}"
+      [brltty_item_name=`expr "${brltty_item_entry}" : '[^[.-.]]*-\(.*\)$'`]
+      brltty_item_unknown=false
+   fi
+elif test "${brltty_item_length}" -gt 2
+then
+   [brltty_item_entry=`expr "${brltty_items_left_$1}" : '.* \([^- ]*-'"${brltty_item}"'[^ ]*\)'`]
+   if test -z "${brltty_item_entry}"
+   then
+      BRLTTY_LOWERCASE_ASSIGN([brltty_lowercase], [${brltty_items_left_$1}])
+      [brltty_item_code=`expr "${brltty_lowercase}" : '.* \([^- ]*\)-'"${brltty_item}"`]
+      if test -n "${brltty_item_code}"
+      then
+         [brltty_item_entry=`expr "${brltty_items_left_$1}" : '.* \('"${brltty_item_code}"'-[^ ]*\)'`]
+      fi
+   fi
+
+   if test -n "${brltty_item_entry}"
+   then
+      [brltty_item_code=`expr "${brltty_item_entry}" : '\([^[.-.]]*\)'`]
+      [brltty_item_name=`expr "${brltty_item_entry}" : '[^[.-.]]*-\(.*\)$'`]
+      brltty_item_unknown=false
+   fi
+fi
+])
+
+AC_DEFUN([BRLTTY_ARG_DRIVER], [dnl
+BRLTTY_ARG_ITEM([$1], [driver])
+if test "${brltty_enabled_$1_support}" != "no"
+then
+   if test -n "${brltty_internal_codes_$1}"
+   then
+      [$1_driver_objects="`echo "${brltty_internal_names_$1}" | sed -e 's%\([^ ][^ ]*\)%$(BLD_TOP)Drivers/$2/\1/$1.$O%g'`"]
+      $1_help="$1-help"
+   fi
+
+   if test "${brltty_standalone_programs}" != "yes"
+   then
+      if test -n "${brltty_external_codes_$1}"
+      then
+         $1_drivers="$1-drivers"
+         install_drivers="install-drivers"
+      fi
+      BRLTTY_SUMMARY_ITEM([external-$1-drivers], [brltty_external_codes_$1])
+   fi
+
+   BRLTTY_SUMMARY_ITEM([internal-$1-drivers], [brltty_internal_codes_$1])
+   BRLTTY_ARG_PARAMETERS([$1], [$1 driver(s)], [DRIVER:])
+fi
+
+for brltty_driver in ${brltty_item_names_$1}
+do
+   brltty_build_directories="${brltty_build_directories} Drivers/$2/${brltty_driver}"
+done
+
+AC_SUBST([$1_driver_objects])
+AC_SUBST([$1_drivers])
+AC_SUBST([$1_help])
+])
+
+AC_DEFUN([BRLTTY_SUMMARY_BEGIN], [dnl
+brltty_summary_lines="Options Summary:"
+])
+
+AC_DEFUN([BRLTTY_SUMMARY_END], [dnl
+AC_CONFIG_COMMANDS([item-summary],
+   [AC_MSG_NOTICE([${brltty_summary_lines}])],
+   [brltty_summary_lines='${brltty_summary_lines}']
+)])
+
+AC_DEFUN([BRLTTY_SUMMARY_ITEM], [dnl
+brltty_summary_lines="${brltty_summary_lines}
+   $1: ${$2}"])
+
+AC_DEFUN([BRLTTY_PORTABLE_DIRECTORY], [dnl
+   BRLTTY_TOPLEVEL_DIRECTORY([$1], [$2], [prefix])])
+
+AC_DEFUN([BRLTTY_ARCHITECTURE_DIRECTORY], [dnl
+if test "${exec_prefix}" = "NONE"
+then
+   BRLTTY_TOPLEVEL_DIRECTORY([$1], [$2], [exec_prefix])
+fi])
+
+AC_DEFUN([BRLTTY_TOPLEVEL_DIRECTORY], [dnl
+if test "${prefix}" = "NONE"
+then
+   if test -z "${execute_root}"
+   then
+      [if test `expr "${$1} " : '\${$3}/[^/]*$'` -gt 0]
+      then
+         $1="`echo ${$1} | sed -e 's%/%$2/%'`"
+      fi
+   fi
+fi])
+
+AC_DEFUN([BRLTTY_EXECUTABLE_PATH], [dnl
+[if test `expr "${$1} " : '[^/ ][^/ ]*/'` -gt 0]
+then
+   $1="`pwd`/${$1}"
+fi])
+
+AC_DEFUN([BRLTTY_IF_PACKAGE], [dnl
+BRLTTY_ARG_WITH(
+   [$2], [DIRECTORY],
+   [where the $1 package is installed],
+   [$2_root], ["yes"]
+)
+
+$2_found=false
+m4_define([$2_find], ifelse(m4_eval($# > 4), 1, [true], [false]))
+ifelse($2_find, [true], [BRLTTY_HAVE_PACKAGE([$2], [$1], [$2_found=true], [:])])
+
+if test "${$2_root}" = "no"
+then
+   $2_root=""
+elif test "${$2_root}" = "yes"
+then
+   "${$2_found}" || {
+      $2_root=""
+      roots="/usr /usr/local /usr/local/$1 /usr/local/$2 /opt/$1 /opt/$2 /mingw /mingw/$1 /mingw/$2"
+
+      for root in ${roots}
+      do
+         test -f "${root}/$3" && {
+            $2_root="${root}"
+            AC_MSG_NOTICE([$1 root: ${$2_root}])
+            break
+         }
+      done
+
+      if test -z "${$2_root}"
+      then
+         AC_MSG_WARN([$1 package not found: ${roots}])
+      fi
+   }
+fi
+
+AC_SUBST([$2_root])
+BRLTTY_SUMMARY_ITEM([$2-root], [$2_root])
+
+test -n "${$2_root}" && {
+   ifelse($2_find, [true], [dnl
+      if test "${$2_root}" = "yes"
+      then
+         $2_root="/usr"
+      else
+         $2_includes="BRLTTY_WORDS_PREPEND([$5], [-I${$2_root}/])"
+         $2_libs="BRLTTY_WORDS_PREPEND([$6], [-L${$2_root}/]) BRLTTY_WORDS_PREPEND([$7], [-l])"
+      fi
+   ])
+
+   AC_DEFINE_UNQUOTED(BRLTTY_UPPERCASE_TRANSLATE([$2_root]), ["${$2_root}"],
+                      [Define this to be a string containing the path to the root of the $1 package.])
+
+   $4
+}
+])
+
+AC_DEFUN([BRLTTY_WORDS_PREPEND], [dnl
+patsubst([$1], [\(\S+\)], [$2\1])])
+
+AC_DEFUN([BRLTTY_HAVE_PACKAGE], [dnl
+$1_package=""
+$1_includes=""
+$1_libs=""
+
+for package_specification in $2
+do
+   ${PKG_CONFIG} --exists "${package_specification}" && {
+      AC_DEFINE(BRLTTY_UPPERCASE_TRANSLATE([HAVE_PKG_$1]))
+
+      $1_package="${package_specification%% *}"
+      AC_MSG_NOTICE([$1 package: ${$1_package}])
+
+      $1_includes=`${PKG_CONFIG} --cflags-only-I "${$1_package}"`
+      AC_MSG_NOTICE([$1 includes: ${$1_includes}])
+
+      $1_libs=`${PKG_CONFIG} ${pkgconfig_flags_libs} "${$1_package}"`
+      AC_MSG_NOTICE([$1 libs: ${$1_libs}])
+
+      $3
+      break
+   }
+done
+
+test -n "${$1_package}" || {
+   ifelse(len([$4]), 0, [AC_MSG_WARN([$1 support not available])], [$4])
+}
+
+AC_SUBST([$1_package])
+AC_SUBST([$1_includes])
+AC_SUBST([$1_libs])
+])
+
+AC_DEFUN([BRLTTY_HAVE_DBUS], [dnl
+   AC_CACHE_CHECK([if D-Bus is available], [brltty_cv_have_dbus], [dnl
+      BRLTTY_HAVE_PACKAGE([dbus], ["dbus-1 >= 1.0"], [dnl
+         brltty_cv_have_dbus=yes
+      ], [dnl
+         brltty_cv_have_dbus=no
+      ])
+   ])
+])
+
+AC_DEFUN([BRLTTY_IF_DBUS], [dnl
+AC_REQUIRE([BRLTTY_HAVE_DBUS])
+test "${brltty_cv_have_dbus}" = "yes" && {
+   $1
+}])
+
+AC_DEFUN([BRLTTY_HAVE_PTHREADS], [dnl
+   AC_CACHE_CHECK([if pthreads are available], [brltty_cv_have_pthreads], [dnl
+      SYSCFLAGS="${SYSCFLAGS} -D_REENTRANT"
+      case "${host_os}"
+      in
+         mingw32*)
+            brltty_cv_have_pthreads=yes
+            ;;
+         *)
+            brltty_cv_have_pthreads=no
+            AC_CHECK_HEADER([pthread.h], [dnl
+               case "${host_os}"
+               in
+                  solaris*) AC_SEARCH_LIBS([_getfp], [pthread], [brltty_cv_have_pthreads=yes]);;
+                  hpux*) AC_SEARCH_LIBS([__pthread_cancel_stack], [pthread], [brltty_cv_have_pthreads=yes]);;
+                  *) AC_SEARCH_LIBS([pthread_create], [pthread c_r], [brltty_cv_have_pthreads=yes]);;
+               esac
+            ])
+            ;;
+      esac
+   ])
+])
+
+AC_DEFUN([BRLTTY_IF_PTHREADS], [dnl
+AC_REQUIRE([BRLTTY_HAVE_PTHREADS])
+test "${brltty_cv_have_pthreads}" = "yes" && {
+   $1
+}])
+
+AC_DEFUN([BRLTTY_PACKAGE_CHOOSE], [dnl
+BRLTTY_ARG_WITH(
+   [translit([$1], [_], [-])], [PACKAGE],
+   [which translit([$1], [_], [ ]) package to use (BRLTTY_PACKAGE_LIST(m4_shift($@)))],
+   [$1_package], ["yes"]
+)
+if test "${$1_package}" = "no"
+then
+   $1_package=""
+elif test "${$1_package}" = "yes"
+then
+AC_CACHE_CHECK([which translit([$1], [_], [ ]) package to use], [brltty_cv_package_$1], [dnl
+   brltty_cv_package_$1=""
+   brltty_packages=""
+   BRLTTY_PACKAGE_DEFINE(m4_shift($@))
+
+   for brltty_package in ${brltty_packages}
+   do
+      eval 'brltty_headers="${brltty_headers_'"${brltty_package}"'}"'
+      test -n "${brltty_headers}" && {
+         brltty_found=true
+         for brltty_header in ${brltty_headers}
+         do
+            BRLTTY_HAVE_HEADER([${brltty_header}], [], [brltty_found=false])
+            "${brltty_found}" || break
+         done
+         "${brltty_found}" || continue
+      }
+
+      AC_CHECK_LIB([${brltty_package}], [main], [], [continue])
+      brltty_cv_package_$1="${brltty_package}"
+      break
+   done
+])
+   $1_package="${brltty_cv_package_$1}"
+else
+   BRLTTY_HAVE_LIBRARY([${$1_package}], [], [$1_package=""])
+fi
+AC_SUBST([$1_package])
+test -n "${$1_package}" && {
+   BRLTTY_UPPERCASE_ASSIGN([brltty_uc], [${$1_package}])
+   AC_DEFINE_UNQUOTED([HAVE_PKG_${brltty_uc}])
+   BRLTTY_SUMMARY_ITEM([translit([$1], [_], [-])-package], [$1_package])
+}])
+AC_DEFUN([BRLTTY_PACKAGE_DEFINE], [dnl
+ifelse($#, 0, [], [dnl
+
+set -- [$1]
+brltty_package="${1}"
+shift 1
+eval "brltty_headers_${brltty_package}"'="${*}"'
+brltty_packages="${brltty_packages} ${brltty_package}"
+ifelse($#, 1, [], [BRLTTY_PACKAGE_DEFINE(m4_shift($@))])])])
+AC_DEFUN([BRLTTY_PACKAGE_LIST], [dnl
+ifelse($#, 0, [], $#, 1, [BRLTTY_PACKAGE_NAME([$1])], [BRLTTY_PACKAGE_NAME([$1]) BRLTTY_PACKAGE_LIST(m4_shift($@))])])
+AC_DEFUN([BRLTTY_PACKAGE_NAME], [patsubst([$1], [^ *\(\w+\).*], [\1])])
+
+AC_DEFUN([BRLTTY_OPTIONS_LD2CC], [dnl
+`echo "$1" | sed -e '
+/^$/d
+s/^ */-Wl /
+s/ *$//
+s/  */,/g
+'`])
+
+AC_DEFUN([BRLTTY_HAVE_WINDOWS_LIBRARY], [dnl
+AC_CACHE_CHECK(
+   [if DLL $1 can be loaded],
+   [brltty_cv_dll_$1],
+   [
+      AC_RUN_IFELSE(
+         [
+            AC_LANG_SOURCE([[
+               #include <windows.h>
+               int main () {
+                  return !LoadLibrary("$1.DLL");
+               }
+            ]])
+         ],
+         [brltty_cv_dll_$1=yes],
+         [brltty_cv_dll_$1=no]
+      )
+   ]
+)
+if test "${brltty_cv_dll_$1}" = "yes"
+then
+   BRLTTY_HAVE_LIBRARY([$1])
+   $2
+else
+   :
+   $3
+fi])
+
+AC_DEFUN([BRLTTY_HAVE_WINDOWS_FUNCTION], [dnl
+AC_CACHE_CHECK(
+   [if function $1 in DLL $2 exists],
+   [brltty_cv_function_$1],
+   [
+      AC_RUN_IFELSE([
+         AC_LANG_SOURCE([[
+            #include <windows.h>
+            #include <stdio.h>
+            #include <errno.h>
+
+            int
+            main (void) {
+              HMODULE module;
+              HINSTANCE instance;
+              if (!(instance = LoadLibrary("$2.dll"))) return 1;
+              if (!(module = GetModuleHandle("$2.dll"))) return 2;
+              if (!(GetProcAddress(module, "$1"))) return 3;
+              return 0;
+            }
+         ]]),
+         [brltty_cv_function_$1=yes],
+         [brltty_cv_function_$1=no]
+      ])
+   ]
+)
+if test "${brltty_cv_function_$1}" = "yes"
+then
+   AC_DEFINE(BRLTTY_UPPERCASE_TRANSLATE([HAVE_$1]), [1],
+             [Define this if the function $1 is available.])
+   $3
+else
+   :
+   $4
+fi
+])
+
+AC_DEFUN([BRLTTY_BINDINGS], [dnl
+ifelse($#, 1, [dnl
+BRLTTY_BINDINGS([$1], m4_tolower([$1]), m4_toupper([$1]))dnl
+], [dnl
+BRLTTY_ARG_DISABLE(
+   [$2-bindings],
+   [$1 bindings for BrlAPI],
+   [],
+   [
+      BRLTTY_$3_BINDINGS
+      if "${$3_OK}"
+      then
+         api_bindings="${api_bindings} $1"
+      else
+         AC_MSG_WARN([$1 BrlAPI bindings not included])
+      fi
+   ]
+)dnl
+])])
+
+AC_DEFUN([BRLTTY_PKGCONFIG_VARIABLE], [dnl
+$1=$($PKG_CONFIG --silence-errors --variable="$3" -- "$2")
+test -n "${$1}" || $1="$4"
+AC_SUBST([$1])
+])
+
diff --git a/mk4build b/mk4build
new file mode 100755
index 0000000..47ad3df
--- /dev/null
+++ b/mk4build
@@ -0,0 +1,139 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+set -e
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+defaultSourceRoot="${programDirectory}"
+addProgramOption s string.directory sourceRoot "the top-level directory of the source tree" "${defaultSourceRoot}"
+parseProgramArguments "${@}"
+
+[ -n "${sourceRoot}" ] || sourceRoot="${defaultSourceRoot}"
+[ -d "${sourceRoot}" ] || semanticError "source root not found: ${sourceRoot}"
+sourceRoot=`cd "${sourceRoot}" && pwd` || exit "${?}"
+
+inputName="config"
+outputName="forbuild"
+
+headerExtension="h"
+logExtension="log"
+makeExtension="mk"
+sedExtension="sed"
+statusExtension="status"
+
+makeInput="${inputName}.${makeExtension}"
+makeOutput="${initialDirectory}/${outputName}.${makeExtension}"
+
+getMakeProperty() {
+   grep "^ *${1} *=" "${makeInput}" | sed -e 's/^[^=]*= *//' -e 's/ *$//'
+}
+
+buildSystem=`getMakeProperty BUILD_SYSTEM`
+hostSystem=`getMakeProperty HOST_SYSTEM`
+
+objectExtension='$(O_FOR_BUILD)'
+
+if [ "${hostSystem}" != "${buildSystem}" ]
+then
+   needTemporaryDirectory
+   objectExtension="build.${objectExtension}"
+
+   for variable in CC CFLAGS CXX CXXFLAGS LDFLAGS LDLIBS PKG_CONFIG PKG_CONFIG_SYSROOT_DIR PKG_CONFIG_LIBDIR PKG_CONFIG_PATH
+   do
+      unset "${variable}"
+      getVariable "${variable}_FOR_BUILD" value
+
+      [ -n "${value}" ] && {
+         setVariable "${variable}" "${value}"
+         export "${variable}"
+      }
+   done
+
+   "${sourceRoot}/configure" \
+      --disable-api \
+      --disable-expat \
+      --disable-gpm \
+      --disable-iconv \
+      --disable-icu \
+      --disable-liblouis \
+      --disable-polkit \
+      --disable-x \
+      \
+      --without-libbraille \
+      --with-braille-driver=-all \
+      \
+      --disable-speech-support \
+      --with-speech-driver=-all \
+      \
+      -with-screen-driver=-all \
+      \
+      --without-pgmpath-package \
+      --without-pgmprivs-package \
+      --without-service-package \
+      --without-params-package \
+      --without-dynld-package \
+      --without-rgx-package \
+      --without-charset-package \
+      --without-hostcmd-package \
+      --without-mntpt-package \
+      --without-mntfs-package \
+      --without-kbd-package \
+      --without-beep-package \
+      --without-pcm-package \
+      --without-midi-package \
+      --without-fm-package \
+      --without-serial-package \
+      --without-usb-package \
+      --without-bluetooth-package \
+      --without-ports-package \
+      \
+      --quiet
+fi
+
+sedScript="${outputName}.${sedExtension}"
+sed -n -e '
+s/^ *\([A-Za-z][A-Za-z0-9_]*\) *=.*$/\1/
+t found
+d
+:found
+h
+s/^\(.*\)$/s%^\\( *\\)\\(\1\\)\\( *=\\)%\\1\\2_FOR_BUILD\\3%/p
+g
+s/^\(.*\)$/s%\\(\\$(\\)\\(\1\\)\\([):]\\)%\\1\\2_FOR_BUILD\\3%g/p
+g
+s/^\(.\)$/s%\\$\\(\1\\)%$(\\1_FOR_BUILD)%g/p
+' <"${makeInput}" >"${sedScript}"
+
+echo "\$a\\
+B = ${objectExtension}
+" >>"${sedScript}"
+
+sed <"${makeInput}" >"${makeOutput}" -f "${sedScript}"
+rm "${sedScript}"
+
+for extension in "${headerExtension}" "${logExtension}" "${statusExtension}"
+do
+   inputFile="${inputName}.${extension}"
+
+   [ ! -f "${inputFile}" ] || {
+      cp "${inputFile}" "${initialDirectory}/${outputName}.${extension}"
+   }
+done
+
+exit 0
diff --git a/mkdocktb b/mkdocktb
new file mode 100755
index 0000000..22559ef
--- /dev/null
+++ b/mkdocktb
@@ -0,0 +1,373 @@
+#!/bin/bash -p
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+defaultOutputRoot="doc-ktb"
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+addProgramOption s string.directory sourceRoot "top-level directory of source tree" "the location of this script"
+addProgramOption b string.directory buildRoot "top-level directory of build tree" "the source tree"
+addProgramOption o string.directory outputRoot "top-level directory of output tree" "${defaultOutputRoot}"
+addProgramOption k flag keepFiles "don't remove intermediate files"
+parseProgramArguments "${@}"
+
+set -e
+umask 022
+shopt -s nullglob
+
+[ -n "${sourceRoot}" ] || sourceRoot="$(dirname "${0}")"
+verifyInputDirectory "${sourceRoot}"
+sourceRoot="$(cd "${sourceRoot}" && pwd)"
+
+[ -n "${buildRoot}" ] || buildRoot="${sourceRoot}"
+buildRoot="$(cd "${buildRoot}" && pwd)"
+
+[ -n "${outputRoot}" ] || outputRoot="${defaultOutputRoot}"
+verifyOutputDirectory "${outputRoot}"
+
+unset HOME
+export XDG_CONFIG_HOME=/
+export XDG_CONFIG_DIRS=/
+
+readonly newLine=$'\n'
+
+lineGroups=()
+newLineGroup() {
+   local variable="${1}"
+
+   local group="lineGroup${#lineGroups[*]}"
+   lineGroups+=("${group}")
+   setVariable "${variable}" "${group}"
+
+   declare -g -a "${group}"
+   eval "${group}=()"
+}
+
+addLineToGroup() {
+   local group="${1}"
+   local line="${2}"
+
+   eval "${group}+="'("${line}")'
+}
+
+newDocLines() {
+   newLineGroup docLines
+}
+
+beginTocEntries() {
+   local variable="${1}"
+
+   newLineGroup "${variable}"
+   newDocLines
+
+   addLineToGroup "${!variable}" "<ul>"
+}
+
+endTocEntries() {
+   local group="${1}"
+
+   addLineToGroup "${group}" "</ul>"
+}
+
+addTocEntry() {
+   local group="${1}"
+   local title="${2}"
+   local anchor="${3}"
+
+   addLineToGroup "${group}" "<li><a href=\"#${anchor}\">${title}</a></li>"
+}
+
+addDocLine() {
+   local line="${1}"
+
+   addLineToGroup "${docLines}" "${line}"
+}
+
+addSectionTerminator() {
+   addDocLine "<hr />"
+}
+
+addAnchor() {
+   local anchor="${1}"
+
+   addDocLine "<a name=\"${anchor}\"></a>"
+}
+
+beginDocument() {
+   local title="${1}"
+
+   newDocLines
+   headerLevel=0
+
+   addDocLine "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+   addDocLine "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
+
+   addDocLine "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">"
+   addDocLine "<head>"
+   addDocLine "<title>${title}</title>"
+   addDocLine "</head>"
+   addDocLine "<body>"
+}
+
+endDocument() {
+   local file="${1}"
+
+   addDocLine "</body>"
+   addDocLine "</html>"
+
+   exec 3>"${file}"
+      local group
+
+      for group in "${lineGroups[@]}"
+      do
+         eval set -- '"${'"${group}"'[@]}"'
+         local line
+
+         for line
+         do
+            echo >&3 "${line}"
+         done
+      done
+   exec 3>&-
+}
+
+beginSection() {
+   local title="${1}"
+
+   local anchor="${title,,?}"
+   anchor="${anchor//[^a-zA-Z_0-9]/-}"
+   addAnchor "${anchor}"
+
+   headerLevel=$((headerLevel + 1))
+   addDocLine "<h${headerLevel}>${title}</h${headerLevel}>"
+
+   if ((headerLevel == 1))
+   then
+      beginTocEntries tocSections
+      addSectionTerminator
+   else
+      addTocEntry "${tocSections}" "${title}" "${anchor}"
+   fi
+}
+
+endSection() {
+   if ((headerLevel == 1))
+   then
+      endTocEntries "${tocSections}"
+   else
+      addSectionTerminator
+   fi
+
+   headerLevel=$((headerLevel - 1))
+}
+
+beginLayoutList() {
+   layoutListed=false
+}
+
+endLayoutList() {
+   ! "${layoutListed}" || {
+      addDocLine "</ul>"
+   }
+}
+
+beginLayoutEntry() {
+   local prefix="${1}"
+   local outputExtension="${2}"
+
+   local layout="${inputFile##*/}"
+   layout="${layout%.*}"
+
+   outputFile="${outputRoot}/${prefix}-${layout}.${outputExtension}"
+}
+
+endLayoutEntry() {
+   local category="${1}"
+
+   exec 4<"${outputFile%.*}.${plainTextExtension}"
+   local description
+   read -r -u 4 description
+   exec 4<&-
+
+   description="${description} "
+   description="${description#*: }"
+   [ -z "${category}" ] || description="${description#${category} }"
+   description="${description% }"
+
+   local prefix="All Models"
+   [ -z "${description}" ] || {
+      if [ "${description}" = "${description#(}" ]
+      then
+         prefix=""
+      else
+         prefix="${prefix} "
+      fi
+   }
+   description="${prefix}${description}"
+
+   "${layoutListed}" || {
+      layoutListed=true
+      addDocLine "<ul>"
+   }
+
+   addDocLine "<li><a href=\"${outputFile##*/}\">${description}</a></li>"
+}
+
+listKeyTable() {
+   local driver="${1}"
+
+   local input="${inputFile##*/}"
+   local output="${outputFile%.*}"
+   local name="${output##*/}"
+
+   local command=( "${keyTableLister}" -D"${buildRoot}/lib" -T"${sourceRoot}/${tablesSubdirectory}" )
+   [ -n "${driver}" ] && command+=( -b "${driver}" )
+   "${command[@]}" -l "${input}" >"${output}.${plainTextExtension}" || {
+      rm -f "${output}.${plainTextExtension}"
+      return 1
+   }
+
+   "${command[@]}" -r "${input}" >"${output}.${reStructuredTextExtension}"
+   sed -e "2a\\${newLine}* \`Help Screen Version <${name}.${plainTextExtension}>\`_" -i "${output}.${reStructuredTextExtension}"
+
+   rst2html --config "${sourceRoot}/docutils.conf" "${output}.${reStructuredTextExtension}" "${output}.${hypertextExtension}"
+   "${keepFiles}" || rm "${output}.${reStructuredTextExtension}"
+}
+
+listBrailleKeyTables() {
+   beginSection "Braille Device Key Bindings"
+   addDocLine "The driver automatically selects the appropriate key table for the device model being used."
+   addDocLine "Click on the driver's name for general information about it."
+   addDocLine "Click on the device model for its key bindings."
+
+   local tocDrivers
+   beginTocEntries tocDrivers
+
+   addDocLine "<dl>"
+   local driverDirectory
+
+   for driverDirectory in "${driversDirectory}/"*
+   do
+      [ -d "${driverDirectory}" ] || continue
+
+      local driverName="${driverDirectory##*/}"
+      local driverCode="$(sed -n '/^DRIVER_CODE *=/s/^.*= *\([^ ]*\).*$/\1/p' "${driverDirectory}/Makefile.in")"
+
+      local header="${driverName}"
+      local inputFile="${driverDirectory}/README"
+
+      [ ! -f "${inputFile}" ] || {
+         local outputFile="${outputRoot}/${braillePrefix}-${driverCode}.${plainTextExtension}"
+         cp -a -- "${inputFile}" "${outputFile}"
+         header="<a href=\"${outputFile##*/}\">${header}</a>"
+      }
+
+      local anchor="${braillePrefix}-${driverCode}"
+      addTocEntry "${tocDrivers}" "${driverName}" "${anchor}"
+
+      addDocLine "<dt>"
+      addAnchor "${anchor}"
+      addDocLine "${header}"
+      addDocLine "</dt><dd>"
+
+      beginLayoutList
+      set -- "${tablesDirectory}/${brailleSubdirectory}/${driverCode}/"*".${keyTableExtension}"
+
+      if [ "${#}" -gt 0 ]
+      then
+         for inputFile
+         do
+            beginLayoutEntry "${braillePrefix}-${driverCode}" "${hypertextExtension}"
+            listKeyTable "${driverCode}" || continue
+            endLayoutEntry "${driverName}"
+         done
+      else
+         set -- "${tablesDirectory}/${brailleSubdirectory}/${driverCode}/"*".${plainTextExtension}"
+
+         for inputFile
+         do
+            beginLayoutEntry "${braillePrefix}-${driverCode}" "${plainTextExtension}"
+            cp -a -- "${inputFile}" "${outputFile}"
+            endLayoutEntry "${driverName}"
+         done
+      fi
+
+      endLayoutList
+      addDocLine "</dd>"
+   done
+
+   addDocLine "</dl>"
+   endTocEntries "${tocDrivers}"
+   endSection
+}
+
+listKeyboardKeyTables() {
+   beginSection "Keyboard Key Tables"
+   addDocLine "In order to use one of these keyboard tables,"
+   addDocLine "you need to explicitly specify it in one of the following ways:"
+
+   addDocLine "<ul>"
+   addDocLine "<li>Use BRLTTY's <tt>-k</tt> (or <tt>--keyboard-table=</tt>) command line option.</li>"
+   addDocLine "<li>Use the <tt>keyboard-table</tt> configuration file (<tt>brltty.conf</tt>) directive.</li>"
+   addDocLine "</ul>"
+
+   beginLayoutList
+   set -- "${tablesDirectory}/${keyboardSubdirectory}/"*".${keyTableExtension}"
+
+   local inputFile
+   for inputFile
+   do
+      beginLayoutEntry "${keyboardPrefix}" "${hypertextExtension}"
+      listKeyTable || continue
+      endLayoutEntry
+   done
+
+   endLayoutList
+   endSection
+}
+
+driversSubdirectory="Drivers/Braille"
+driversDirectory="${sourceRoot}/${driversSubdirectory}"
+
+tablesSubdirectory="Tables"
+tablesDirectory="${sourceRoot}/${tablesSubdirectory}"
+
+brailleSubdirectory="Input"
+keyboardSubdirectory="Keyboard"
+
+braillePrefix="brl"
+keyboardPrefix="kbd"
+
+plainTextExtension="txt"
+reStructuredTextExtension="rst"
+hypertextExtension="html"
+keyTableExtension="ktb"
+
+keyTableLister="${buildRoot}/Programs/brltty-ktb"
+make -C "${keyTableLister%/*}" -s "${keyTableLister##*/}"
+
+documentTitle="Key Binding Lists"
+beginDocument "${documentTitle}"
+
+beginSection "${documentTitle}"
+listBrailleKeyTables
+listKeyboardKeyTables
+endSection
+
+endDocument "${outputRoot}/index.${hypertextExtension}"
+exit 0
diff --git a/mkdocs b/mkdocs
new file mode 100755
index 0000000..314c51a
--- /dev/null
+++ b/mkdocs
@@ -0,0 +1,106 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "${0%/*}/prologue.sh"
+
+executeCommand() {
+   "${@}"
+}
+
+updateFiles() {
+   fromRoot="${1}"
+   fromDirectory="${2}"
+   toDirectory="${3}"
+   shift 3
+
+   executeCommand mkdir -p -- "${outputRoot}/${toDirectory}"
+   set -- `cd "${fromRoot}/${fromDirectory}" && echo ${*}`
+
+   for fromFile
+   do
+      fromPath="${fromRoot}/${fromDirectory}/${fromFile}"
+      [ -e "${fromPath}" ] && executeCommand cp -a -- "${fromPath}" "${outputRoot}/${toDirectory}/"
+   done
+}
+
+outputRoot=""
+sourceRoot=""
+buildRoot=""
+
+while getopts ":o:s:b:" option
+do
+   case "${option}"
+   in
+      o) outputRoot="${OPTARG}";;
+      s) sourceRoot="${OPTARG}";;
+      b) buildRoot="${OPTARG}";;
+
+      :) syntaxError "missing value: -${OPTARG}";;
+     \?) syntaxError "unknown option: -${OPTARG}";;
+      *) syntaxError "unimplemented option: -${option}";;
+   esac
+done
+
+shift `expr "${OPTIND}" - 1`
+[ "${#}" -eq 0 ] || syntaxError "too many parameters"
+
+[ -n "${outputRoot}" ] || outputRoot="doc"
+verifyOutputDirectory "${outputRoot}"
+
+[ -n "${sourceRoot}" ] || sourceRoot=`dirname "${0}"`
+verifyInputDirectory "${sourceRoot}"
+sourceRoot=`resolveDirectory "${sourceRoot}"`
+
+if [ -n "${buildRoot}" ]
+then
+   verifyInputDirectory "${buildRoot}"
+   buildRoot=`resolveDirectory "${buildRoot}"`
+else
+   buildRoot="${sourceRoot}"
+fi
+
+updateFiles "${sourceRoot}" "." "." "README" "LICENSE-*"
+
+sourceDocuments="${sourceRoot}/${documentsSubdirectory}"
+buildDocuments="${buildRoot}/${documentsSubdirectory}"
+
+for document in `cd "${sourceDocuments}" && echo README.*`
+do
+   make -s -C "${buildDocuments}" "${document#README.}.html"
+done
+
+updateFiles "${sourceRoot}" "${documentsSubdirectory}" "." "BUGS" "ChangeLog" "CONTRIBUTORS" "HISTORY" "TODO" "*.csv"
+updateFiles "${buildRoot}" "${documentsSubdirectory}" "." "brltty.conf" "*.html"
+
+for manual in `cd "${sourceDocuments}" && echo Manual-*/*`
+do
+   for documentExtension in doc htm html pdf sgml txt
+   do
+      updateFiles "${sourceRoot}" "${documentsSubdirectory}/${manual}" "${manual}" "*.${documentExtension}"
+   done
+done
+
+for manual in BrlAPIref
+do
+   updateFiles "${buildRoot}" "${documentsSubdirectory}/${manual}/html" "${manual}" "*.html"
+done
+
+executeCommand "${sourceRoot}/mkdocktb" -o "${outputRoot}/KeyTables" -s "${sourceRoot}" -b "${buildRoot}"
+
+exit 0
diff --git a/mkpot b/mkpot
new file mode 100755
index 0000000..051eeab
--- /dev/null
+++ b/mkpot
@@ -0,0 +1,62 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+addProgramOption p string.name packageName "specify the package name" "the name of the top-level directory of the source tree"
+addProgramOption n string.version packageVersion "specify the package version" "no version"
+addProgramOption o string.file outputFile "specify the output file" "<package-name>.pot in the current directory"
+addProgramOption b string.email bugsAddress "specify the email address for bug reports" "no address"
+addProgramOption c string.name copyrightHolder "specify the name of the copyright holder" "no copyright holder"
+addProgramParameter source sourceRoot "top-level directory of source tree"
+optionalProgramParameters
+addProgramParameter build buildRoot "top-level directory of build tree" "the source tree"
+parseProgramArguments "${@}"
+
+[ -n "${sourceRoot}" ] || sourceRoot="."
+sourceRoot="$(resolveDirectory "${sourceRoot}")" || semanticError
+
+if [ -n "${buildRoot}" ]
+then
+   buildRoot="$(resolveDirectory "${buildRoot}")" || semanticError
+else
+   buildRoot="${sourceRoot}"
+fi
+[ "${buildRoot}" != "${sourceRoot}" ] || buildRoot=""
+
+[ -n "${packageName}" ] || packageName=`basename "${sourceRoot}"`
+[ -n "${outputFile}" ] || outputFile="${packageName}.pot"
+[ "${outputFile#/}" != "${outputFile}" ] || outputFile="${initialDirectory}/${outputFile}"
+
+xgettext --output - --force-po --no-wrap --sort-output --from-code utf-8 \
+   ${packageName:+--package-name "${packageName}"} \
+   ${packageVersion:+--package-version "${packageVersion}"} \
+   ${bugsAddress:+--msgid-bugs-address "${bugsAddress}"} \
+   ${copyrightHolder:+--copyright-holder "${copyrightHolder}"} \
+   --keyword=strtext:1 --flag=logMessage:2:c-format \
+   --add-comments \
+   -- `find -H ${sourceRoot} ${buildRoot} -name '*.c' -o -name '*.h' | sort` |
+sed >"${outputFile}" -e "
+  s/PACKAGE/${packageName}/
+
+  /^#: /{
+    s% ${sourceRoot}/*% %g
+    s% ${buildRoot}/*% %g
+  }
+"
+exit "${?}"
diff --git a/mkrpm b/mkrpm
new file mode 100755
index 0000000..450e6e7
--- /dev/null
+++ b/mkrpm
@@ -0,0 +1,35 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+needTemporaryDirectory
+
+[ "${#}" -gt 0 ] || syntaxError "missing archive"
+archiveFile="${1}"
+[ "${archiveFile#/}" = "${archiveFile}" ] && archiveFile="${initialDirectory}/${archiveFile}"
+shift
+
+[ "${#}" -eq 0 ] || syntaxError "too many parameters"
+
+set -e
+mkdir SPECS SOURCES BUILD RPMS SRPMS
+rpmbuild -ta --clean --define "_topdir ${temporaryDirectory}" --define "_tmppath ${temporaryDirectory}" "${archiveFile}"
+mv -- SRPMS/*.rpm "${initialDirectory}"
+mv -- RPMS/*/*.rpm "${initialDirectory}"
+exit 0
diff --git a/mktar b/mktar
new file mode 100755
index 0000000..b64a260
--- /dev/null
+++ b/mktar
@@ -0,0 +1,84 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+readonly configureOptions=(
+   --without-libbraille
+   --without-flite
+   --without-mikropuhe
+   --without-speechd
+   --without-swift
+   --without-theta
+)
+
+. "`dirname "${0}"`/prologue.sh"
+
+[ "${#}" -gt 0 ] || syntaxError "missing source root"
+sourceRoot="${1}"
+shift
+cd "${sourceRoot}" || exit "${?}"
+sourceRoot="`pwd`"
+
+if [ "${#}" -eq 0 ]
+then
+   archiveName="`basename "${sourceRoot}"`"
+else
+   archiveName="${1}"
+   shift
+fi
+
+if [ "${#}" -eq 0 ]
+then
+   archiveFile="${archiveName}.tar"
+else
+   archiveFile="${1}"
+   shift
+fi
+[ "${archiveFile#/}" = "${archiveFile}" ] && archiveFile="${initialDirectory}/${archiveFile}"
+
+[ "${#}" -eq 0 ] || syntaxError "too many parameters"
+
+needTemporaryDirectory
+set -e
+
+cp -p -r -- "${sourceRoot}" "${archiveName}"
+cd "${archiveName}"
+
+"${sourceRoot}/getrevid" "${sourceRoot}" >REVISION
+./autogen
+./configure --quiet "${configureOptions[@]}"
+
+(  set -e
+   cd "${documentsSubdirectory}"
+   make -s clean
+   make -s all
+)
+"${sourceRoot}/mkdocs" -o "doc" -s "${sourceRoot}" -b "."
+
+specFile="brltty.spec"
+mv -- "${specFile}" "${temporaryDirectory}"
+make -s distclean
+mv -- "${temporaryDirectory}/${specFile}" .
+
+rm -f -r -- .git
+find . -name .gitignore -exec rm {} \;
+rm -f -r -- .svn
+
+cd ..
+tar --create --no-anchored --file "${archiveFile}" "${archiveName}"
+exit 0
diff --git a/mkzip b/mkzip
new file mode 100755
index 0000000..eef2574
--- /dev/null
+++ b/mkzip
@@ -0,0 +1,61 @@
+#!/bin/sh
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/brltty-prologue.sh"
+
+[ "${#}" -eq 0 ] && syntaxError "missing source root."
+sourceRoot="${1}"
+shift
+cd "${sourceRoot}" || exit "${?}"
+sourceRoot="`pwd`"
+
+if [ "${#}" -eq 0 ]
+then
+   archiveName="`basename "${sourceRoot}"`"
+else
+   archiveName="${1}"
+   shift
+fi
+
+if [ "${#}" -eq 0 ]
+then
+   archiveFile="${archiveName}.zip"
+else
+   archiveFile="${1}"
+   shift
+fi
+[ "${archiveFile#/}" = "${archiveFile}" ] && archiveFile="${initialDirectory}/${archiveFile}"
+
+[ "${#}" -eq 0 ] || syntaxError "too many parameters."
+
+needTemporaryDirectory
+set -e
+
+buildRoot="${temporaryDirectory}/build"
+cp -p -r -- "${sourceRoot}" "${buildRoot}"
+cd "${buildRoot}"
+
+installRoot="${temporaryDirectory}/${archiveName}"
+"./configure" --quiet --host=mingw32 --prefix="${installRoot}" --enable-relocatable-install --without-libbraille --without-flite --without-mikropuhe --without-speechd --without-swift --without-theta
+make -s
+make -s install
+
+cd ..
+zip -q -A -r "${archiveFile}" "${archiveName}"
+exit 0
diff --git a/prologue.mk b/prologue.mk
new file mode 100644
index 0000000..8056578
--- /dev/null
+++ b/prologue.mk
@@ -0,0 +1,29 @@
+# @configure_input@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+SRC_TOP = @top_srcdir@/
+SRC_DIR = @srcdir@
+# VPATH = @srcdir@
+
+BLD_TOP = ./@top_builddir@/
+BLD_DIR = @builddir@
+
+include $(SRC_TOP)common.mk
+include $(SRC_DIR)/reldeps.mk
+
diff --git a/prologue.sh b/prologue.sh
new file mode 100644
index 0000000..3a9127d
--- /dev/null
+++ b/prologue.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${BASH_SOURCE[0]}")/brltty-prologue.bash"
+
+setSourceRoot() {
+   findContainingDirectory BRLTTY_SOURCE_ROOT "${programDirectory}" brltty.pc.in || {
+      semanticError "source tree not found"
+   }
+
+   readonly sourceRoot="${BRLTTY_SOURCE_ROOT}"
+}
+
+setBuildRoot() {
+   local variable=BRLTTY_BUILD_ROOT
+   set -- brltty.pc
+
+   findContainingDirectory "${variable}" "${initialDirectory}" "${@}" || {
+      findContainingDirectory "${variable}" "${programDirectory}" "${@}" || {
+         semanticError "build tree not found"
+      }
+   }
+
+   readonly buildRoot="${BRLTTY_BUILD_ROOT}"
+}
+
+readonly documentsSubdirectory="Documents"
+readonly driversSubdirectory="Drivers"
+readonly programsSubdirectory="Programs"
+readonly tablesSubdirectory="Tables"
+readonly toolsSubdirectory="Tools"
+
diff --git a/prologue.tcl b/prologue.tcl
new file mode 100644
index 0000000..90a677f
--- /dev/null
+++ b/prologue.tcl
@@ -0,0 +1,95 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+source [file join [file dirname [info script]] "brltty-prologue.tcl"]
+set sourceRoot [file normalize [file dirname [info script]]]
+
+set documentsSubdirectory Documents
+set programsSubdirectory Programs
+set toolsSubdirectory Tools
+
+set tablesSubdirectory Tables
+set textTablesSubdirectory [file join $tablesSubdirectory Text]
+set contractionTablesSubdirectory [file join $tablesSubdirectory Contraction]
+set attributesTablesSubdirectory [file join $tablesSubdirectory Attributes]
+set keyboardTablesSubdirectory [file join $tablesSubdirectory Keyboard]
+set inputTablesSubdirectory [file join $tablesSubdirectory Input]
+
+set driversSubdirectory Drivers
+set brailleDriversSubdirectory [file join $driversSubdirectory Braille]
+set speechDriversSubdirectory [file join $driversSubdirectory Speech]
+set screenDriversSubdirectory [file join $driversSubdirectory Screen]
+
+proc setSourceRoot {} {
+   set variable ::env(BRLTTY_SOURCE_ROOT)
+
+   if {![findContainingDirectory $variable [pwd] [list brltty.pc.in]]} {
+      semanticError "source tree not found"
+   }
+
+   return [set ::sourceRoot [set $variable]]
+}
+
+proc setBuildRoot {} {
+   global initialDirectory scriptDirectory
+   set variable ::env(BRLTTY_BUILD_ROOT)
+
+   foreach directory [list $initialDirectory $scriptDirectory] {
+      if {[findContainingDirectory $variable $directory [list brltty.pc]]} {
+         return [set ::buildRoot [set $variable]]
+      }
+   }
+
+   semanticError "build tree not found"
+}
+
+proc getMakeFileProperty {file property {variable ""}} {
+   set pattern {^\s*(\w+)\s*=\s*(.*?)\s*$}
+
+   forEachLine line $file {
+      if {[regexp $pattern $line x name value]} {
+         if {[string equal $name $property]} {
+            if {[string length $variable] == 0} {
+               return $value
+            }
+
+            uplevel 1 [list set $variable $value]
+            return 1
+         }
+      }
+   }
+
+   if {[string length $variable] == 0} {
+      return -code error "property not found: $property"
+   }
+
+   return 0
+}
+
+proc getBrailleDriverProperty {driver property {variable ""}} {
+   return [uplevel 1 [list getMakeFileProperty [file join $::sourceRoot $::brailleDriversSubdirectory $driver Makefile.in] $property $variable]]
+}
+
+proc getBrailleDriverCode {driver {variable ""}} {
+   return [uplevel 1 [list getBrailleDriverProperty $driver DRIVER_CODE $variable]]
+}
+
+proc getBrailleDriverComment {driver {variable ""}} {
+   return [uplevel 1 [list getBrailleDriverProperty $driver DRIVER_COMMENT $variable]]
+}
+
diff --git a/relpath.awk b/relpath.awk
new file mode 100644
index 0000000..12b39bd
--- /dev/null
+++ b/relpath.awk
@@ -0,0 +1,36 @@
+function normalizePath(path) {
+  if (length(path) == 0) path = "."
+  path = path pathDelimiter
+  gsub((pathDelimiter "+"), pathDelimiter, path)
+  return path
+}
+
+BEGIN {
+  pathDelimiter = "/"
+
+  pathLength = length(path = normalizePath(path))
+  referenceLength = length(reference = normalizePath(reference))
+
+  minimumLength = pathLength
+  if (referenceLength < minimumLength) minimumLength = referenceLength
+
+  prefixLength = 0
+  for (i=1; i<=minimumLength; ++i) {
+    character = substr(reference, i, 1)
+    if (substr(path, i, 1) != character) break
+    if (character == pathDelimiter) prefixLength = i
+  }
+
+  if ((prefixLength > 0) || ((substr(path, 1, 1) == pathDelimiter) == (substr(reference, 1, 1) == pathDelimiter))) {
+    suffix = substr(path, prefixLength+1)
+    prefix = substr(reference, prefixLength+1)
+    gsub(("[^" pathDelimiter "]+"), "..", prefix)
+    path = normalizePath(prefix suffix)
+  }
+
+  path = normalizePath(path)
+  sub((pathDelimiter "$"), "", path)
+
+  printf "%s", path
+  exit 0
+}
diff --git a/run-brltty b/run-brltty
new file mode 100755
index 0000000..d644e0b
--- /dev/null
+++ b/run-brltty
@@ -0,0 +1,41 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/prologue.sh"
+command=brltty
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Run ${command} from its build tree.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through to ${command} itself.
+
+See run-command -h for more details.
+END-OF-HELP
+
+setSourceRoot
+setBuildRoot
+
+export BRLTTY_TABLES_DIRECTORY="${sourceRoot}/Tables"
+export BRLTTY_DRIVERS_DIRECTORY="${buildRoot}/lib"
+export BRLTTY_LOCALE_DIRECTORY="${buildRoot}/Messages/locale"
+
+exec "${programDirectory}/run-command" "${buildRoot}/Programs/${command}" -E "${@}"
+exit "${?}"
diff --git a/run-command b/run-command
new file mode 100755
index 0000000..1ebba0b
--- /dev/null
+++ b/run-command
@@ -0,0 +1,71 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "$(dirname "${0}")/prologue.sh"
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Execute the specified command, with the specified arguments,
+such that it picks up shared objects from the BRLTTY build tree.
+
+Syntax: ${programName} {-h | command [argument ...]}
+
+The working directory must be somewhere within the build tree that's to be used.
+If it isn't then the source tree will be used.
+
+Environment Variables:
+  <COMMAND>_VALGRIND_DIRECTORY=path
+    If set then write a valgrind memory usage log to a file named
+    <command>-yyyymmdd-HHMMSS.vg in the specified directory.
+END-OF-HELP
+
+setBuildRoot
+
+[ "${#}" -gt 0 ] || syntaxError "missing command"
+commandName="${1}"
+shift 1
+
+readonly commandPath="$(type -P "${commandName}")"
+[ -n "${commandPath}" ] || semanticError "command not found: ${commandName}"
+readonly commandName="${commandPath##*/}"
+set -- "${commandPath}" "${@}"
+
+[ -n "${LD_LIBRARY_PATH}" ] && LD_LIBRARY_PATH=":${LD_LIBRARY_PATH}"
+export LD_LIBRARY_PATH="${buildRoot}/Programs${LD_LIBRARY_PATH}"
+
+readonly variablePrefix="${commandName^^?}"
+getVariable "${variablePrefix}_VALGRIND_DIRECTORY" vgDirectory
+readonly vgDirectory
+
+[ -n "${vgDirectory}" ] && {
+   [ -e "${vgDirectory}" ] || semanticError "directory not found: ${vgDirectory}"
+   [ -d "${vgDirectory}" ] || semanticError "not a directory: ${vgDirectory}"
+   [ -w "${vgDirectory}" ] || semanticError "directory not writable: ${vgDirectory}"
+
+   vgFile="${vgDirectory}/${commandName}-$(date +%Y%m%d-%H%M%S).vg"
+   programMessage "valgrind log: ${vgFile}"
+
+   set -- valgrind \
+       --log-file="${vgFile}" \
+       --leak-check=full \
+       --show-reachable=yes \
+       "${@}"
+}
+
+exec "${@}"
+exit "${?}"
diff --git a/run-xbrlapi b/run-xbrlapi
new file mode 100755
index 0000000..651c8e6
--- /dev/null
+++ b/run-xbrlapi
@@ -0,0 +1,36 @@
+#!/bin/bash
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+. "`dirname "${0}"`/prologue.sh"
+command=xbrlapi
+
+handleInitialHelpOption "${@}" <<END-OF-HELP
+Run ${command} from its build tree.
+
+Syntax: ${programName} {-h | [-option ...]}
+
+All of the options are passed through to ${command} itself.
+
+See run-command -h for more details.
+END-OF-HELP
+
+setBuildRoot
+
+exec "${programDirectory}/run-command" "${buildRoot}/Programs/${command}" "$@"
+exit "${?}"
diff --git a/screen.mk b/screen.mk
new file mode 100644
index 0000000..4a2e6e9
--- /dev/null
+++ b/screen.mk
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+SCR_DEFS ='-DDRIVER_NAME=$(DRIVER_NAME)' '-DDRIVER_CODE=$(DRIVER_CODE)' '-DDRIVER_COMMENT="$(DRIVER_COMMENT)"' '-DDRIVER_VERSION="$(DRIVER_VERSION)"' '-DDRIVER_DEVELOPERS="$(DRIVER_DEVELOPERS)"'
+SCR_CFLAGS = $(LIBCFLAGS) $(SCR_DEFS)
+SCR_CXXFLAGS = $(LIBCXXFLAGS) $(SCR_DEFS)
+SCR_MOD_NAME = $(BLD_TOP)$(DRV_DIR)/$(MOD_NAME)x$(DRIVER_CODE)
+SCR_MOD_FILE = $(SCR_MOD_NAME).$(MOD_EXT)
+$(SCR_MOD_FILE): screen.$O
+	$(INSTALL_DIRECTORY) $(@D)
+	$(MKSHR) $(@) screen.$O $(SCR_OBJS)
+screen-driver: $(SCR_MOD_FILE)
+
+install::
+
+uninstall::
+
+clean::
+	-rm -f $(SCR_MOD_NAME).*
diff --git a/speech.mk b/speech.mk
new file mode 100644
index 0000000..8495b76
--- /dev/null
+++ b/speech.mk
@@ -0,0 +1,34 @@
+###############################################################################
+# BRLTTY - A background process providing access to the console screen (when in
+#          text mode) for a blind person using a refreshable braille display.
+#
+# Copyright (C) 1995-2023 by The BRLTTY Developers.
+#
+# BRLTTY comes with ABSOLUTELY NO WARRANTY.
+#
+# This is free software, placed 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. Please see the file LICENSE-LGPL for details.
+#
+# Web Page: http://brltty.app/
+#
+# This software is maintained by Dave Mielke <dave@mielke.cc>.
+###############################################################################
+
+SPK_DEFS ='-DDRIVER_NAME=$(DRIVER_NAME)' '-DDRIVER_CODE=$(DRIVER_CODE)' '-DDRIVER_COMMENT="$(DRIVER_COMMENT)"' '-DDRIVER_VERSION="$(DRIVER_VERSION)"' '-DDRIVER_DEVELOPERS="$(DRIVER_DEVELOPERS)"'
+SPK_CFLAGS = $(LIBCFLAGS) $(SPK_DEFS)
+SPK_CXXFLAGS = $(LIBCXXFLAGS) $(SPK_DEFS)
+SPK_MOD_NAME = $(BLD_TOP)$(DRV_DIR)/$(MOD_NAME)s$(DRIVER_CODE)
+SPK_MOD_FILE = $(SPK_MOD_NAME).$(MOD_EXT)
+$(SPK_MOD_FILE): speech.$O
+	$(INSTALL_DIRECTORY) $(@D)
+	$(MKSHR) $(@) speech.$O $(SPK_OBJS)
+speech-driver: $(SPK_MOD_FILE)
+
+install::
+
+uninstall::
+
+clean::
+	-rm -f $(SPK_MOD_NAME).*